← ブログに戻る

GraphRAGでコードレビューを7段階に分解

この記事を含む総合ガイド Claude Code 実戦運用ガイド

500行の差分を渡された金曜の夕方、私はまだgrepでimport文を追いかけていた。「多分これで全部の呼び出し元は追えたはず」と祈りながらApproveボタンを押す、あの気持ちを一度でも味わうと、コードレビューは実は「探す作業」だと気づいてしまう。「読む作業」ではない。探す作業は自動化できる。ソースコードは本質的にグラフ構造を持っていて、Knowledge Graph (KG) にすると「変更の影響範囲」が2秒で返ってくる。私はここ数ヶ月、コードKGとMCP (Model Context Protocol) を組み合わせてPRレビューを再設計した。

手順は7段階に分かれ、落とし穴は3つある。

なぜKGなのか、なぜファイル単位ではダメなのか

ファイルは人間が編集する単位で、影響が伝播する単位ではない。auth.py を1行変えたら、middleware.py が呼び出しに巻き込まれ、テストが3ファイル、間接依存で conftest.py まで届く。この波及は関数呼び出しグラフの「hop距離」で表現できる。コードをグラフに変換すると、「hop 0 = 変更ファイル自体」「hop 1 = 直接呼び出す先」「hop 2 = 間接呼び出し」というふうに、影響範囲が数字で降ってくる。

grepで30分かけていた作業が、Cypherクエリ1本で終わる。

コードをKGに変換した際のノード・エッジ構造 (関数・クラス・呼び出し関係)

7ステップの分解

Step 1: Tree-sitterでASTを取る

コードKGの入口は決定論的AST解析だ。Tree-sitterは19以上のプログラミング言語に対応していて、LLMを使わずローカルで高速に走る。

import tree_sitter_python as tspython
from tree_sitter import Language, Parser

parser = Parser(Language(tspython.language()))
tree = parser.parse(open("service.py", "rb").read())

ここで得られるのは、関数・クラス・変数の位置情報を持った構文木。LLMに投げる前に、まず「機械的に確実にわかること」を抽出しきる。

この分離が後で効いてくる。

Step 2: ノードとエッジを設計する

ノードは File / Class / Function / Module。エッジは CALLS / IMPORTS / INHERITS / CONTAINS。

(:Function {name: "get_user", file: "service.py", line: 12})
  -[:CALLS]-> 
(:Function {name: "db.query", file: "db.py", line: 45})

粒度で迷ったら、まずは関数レベル。ステートメントまで落とすとノード数が爆発し、Neo4jのメモリが先に音を上げる。

Step 3: Neo4jに投入する

グラフDBの選択肢は3つ。組込型で試すならKuzuDB (pip install kuzu)、本番ならNeo4j、FalkorDBはRedis互換で使い勝手がいい。今回はNeo4jで進める。投入自体は UNWIND バッチで数万ノードなら数秒。ここで手を抜くと後段のクエリが遅くなるので、CREATE INDEX はノードラベルごとに必ず張っておく。

Step 4: MCPサーバーを立てる

ここが本題。

mcp-neo4j-cypher (v0.6.0、2026-04リリース) を使うと、Neo4jのグラフをMCPツールとしてClaude CodeやCursorから叩けるようになる。公開されているツールは3つだけ。

  • get_neo4j_schema — ノードラベル・属性・リレーションシップを一覧
  • read_neo4j_cypher — 読み取りクエリを実行
  • write_neo4j_cypher — 書き込みクエリを実行

Claude Code の設定ファイルにサーバーを登録すると、AIアシスタントは自然言語で受けた質問を裏でCypherに翻訳して投げてくれる。PRレビューの文脈だと、read_neo4j_cypher の1本があれば9割は回る。

Step 5: PR diff から blast radius を引く

変更ファイル auth.py の影響範囲を4 hop先まで取るCypher。

MATCH (start:File {path: "auth.py"})-[*1..4]-(affected)
RETURN DISTINCT affected.path AS file, 
       length(shortestPath((start)-[*]-(affected))) AS hop
ORDER BY hop
LIMIT 50

初めて叩いたとき、7ファイルのリストが2秒で返ってきた。自分が30分かけてgrepしていたのとほぼ同じ結果だった。

あの30分は何だったのか、という気持ちになる。

blast radius のホップ距離別可視化 (hop 0-3 の色分け)

Step 6: リスクスコアで優先度を出す

hop 数と「影響ファイル数 × 変更行数」を掛けたスコアで、レビュー優先度を機械的に決める。

  • スコア 8-10: 分散した影響、レビュー時に呼び出し元を全部見る
  • スコア 5-7: 局所的、テスト側の変更で十分
  • スコア 0-4: リスク低、diffだけで判断可

私は「hop 2以上まで届いたら追加レビュアーを呼ぶ」ルールを機械的に置いている。人間の目視では『多分大丈夫』で流していた領域が、数字で線が引けるようになる。

Step 7: AIレビュアーに渡すコンテキストを絞る

これがKGの最大の投資対効果ポイント。

従来のAIコードレビューは「念のため関連ファイル50個」を全部コンテキストに入れていた。blast radius で特定した7ファイルだけ渡すと、トークン消費が数倍から数十倍削減される。おまけに、レビュー品質は上がる。ノイズが減るのだから当然だ。

従来: 変更ファイル + 周辺50個 = 150,000 tokens
KG絞り込み: 変更ファイル + hop1-2の7個 = 18,000 tokens

同じPRレビューで、3つのsub-agentに独立で見せて40%が食い違った実測を以前書いた。あの実験もコンテキストを絞ってからやると、食い違いパターンが変わる。「見ていないから食い違う」のと「見ているけど解釈が違う」のは切り分けたほうがいい。

3つの落とし穴

落とし穴1: 全関数をノードにする

初回で必ずやる。

10万行のリポジトリで関数レベルまで全部ノードにすると、Neo4jのメモリが飛ぶし、クエリも遅い。対策は「エントリポイントと公開APIだけノードにする」。privateなヘルパー関数はエッジの属性に押し込む。粒度は後から細かくできるが、粗くするのは大変だ。

落とし穴2: エッジの信頼度をフラットに扱う

Tree-sitterのAST解析は「確実にわかるエッジ」しか出さない。でも動的言語 (Python/JS) では、getattr() や **kwargs 経由の呼び出しは静的解析で追いきれない。

落とし穴3: グラフを更新しない

一度作ったKGは、リポジトリの変更に合わせて更新しないと3週間で嘘をつく。手動更新は続かない。CIに graph rebuild ステップを入れる。1万ファイル規模でも差分更新なら30秒程度。「グラフの新鮮さ」は運用の質の指標になる。ここで手を抜くと、レビュアーが「グラフはもう当てにならない」と言い出して、また grep に戻る。

まとめ

コードをKGにすると、PRレビューが「読む」から「クエリする」に変わる。7ステップは Tree-sitter → ノード/エッジ設計 → Neo4j投入 → mcp-neo4j-cypher → blast radius → リスクスコア → AI渡し。落とし穴は粒度・信頼度・更新の3つ。

「多分これで全部」でApproveを押す必要は、もうない。

References


もっと深く知りたい方へ

本記事はコードKGの入口だけ。Neo4jスキーマ設計、Property Graph vs RDFの選択、code-review-graph・GitNexus・CodeGraphContextなどのツール比較、企業導入事例までを1冊にまとめたのが ナレッジグラフ活用大全 です。

ナレッジグラフ活用大全 関連書籍 ナレッジグラフ活用大全 ナレッジグラフ 活用大全 | GraphRAG・Neo4j・RDF・Property Graph・Emotion AI の実践書 書籍ページを見る →