やってみた 2026年7月18日

code-review-graphを使い捨てコンテナで動かしたら、shallow cloneで肝心の機能が黙って死んでいた

XECIN PythonMCPtree-sitterコードレビュー

GitHubのトレンドを眺めていたら、tirth8205/code-review-graph というのが上のほうに来ていました。「AIコーディングツールがレビューのたびにコードベースを読み直して大量のトークンを焼くのを、tree-sitterで構造マップを作ってMCP経由で必要な分だけ渡すことで直す」——という触れ込みです。

私はふだんレビュー支援まわりのツールを何度か試しては、結局「結果が読めない・セットアップが重い」で外してきた経験があるので、この手の「トークンを削る」系は正直あまり期待していませんでした。ただ、pip install 一発でローカル完結、外部APIキーも要らないと書いてある。それなら使い捨てのコンテナに放り込んで、どこまで本当に動くか見てみようと思ったわけです。

結論から言うと、ツール自体は驚くほどあっさり動きました。むしろハマったのは自分の準備のほうで、しかもそのハマり方が「このツールの設計思想を分かってなかった」という類のもので、個人的にはそこが一番の収穫でした。順番に残しておきます。

試した環境

  • ホストはWindows + Docker Desktop、検証は使い捨ての python:3.12-bookworm コンテナの中だけで完結
  • コンテナ内: Python 3.12.13 / pip 25.0.1 / git 2.39.5(Debian 12 bookworm)
  • 対象: code-review-graph(PyPI版 2.3.6、リポジトリの clone は commit 50a8ad3

対象リポジトリのcloneも依存のインストールも、全部コンテナの中でやっています。ホストには何も入れていません。

pip install はほぼ一瞬

まず素直にQuick Startどおり入れてみます。

$ pip install code-review-graph
...
real    0m10.327s

$ pip show code-review-graph | grep -E "Version|Requires"
Version: 2.3.6
Requires: fastmcp, mcp, networkx, tree-sitter, tree-sitter-language-pack, watchdog

10秒ちょっと。依存も networkx(グラフ構造)・tree-sitter(パーサ)・fastmcp/mcp(MCPサーバ)・watchdog(ファイル監視)と、名前を見ただけで「あぁ、そういう作りね」と想像がつく最小構成でした。重い機械学習系の依存が一切ぶら下がってこないのは好印象です。埋め込み(embedding)による意味検索は別サブコマンドで、必要な人だけが入れる建て付けになっているようでした。

CLIのサブコマンドは想像より多くて、build / update / status / visualize / wiki / detect-changes / serve(MCPサーバ起動)あたりが並びます。まずはツール自身のソースをそのまま対象にしてグラフを作ってみます。

$ code-review-graph build
INFO: Schema version 1 -> 9: running migrations
INFO: Running migration v2
...
INFO: Migrations complete, now at schema version 9
INFO: Progress: 185/185 files parsed
INFO: FTS index rebuilt: 3515 rows indexed
INFO: Loaded 3330 unique nodes, 25858 edges
Full build: 185 files, 3539 nodes, 26112 edges (postprocess=full)

real    0m5.523s

185ファイルを 5.5秒 でパースして、3539ノード・26112エッジのグラフができました。初回起動でSQLiteのスキーマをv1→v9まで自動マイグレーションしていくログが流れるのは、地味ですが「ちゃんとバージョン管理された永続DBなんだな」と伝わってきて、こういうところは経験上あとで効いてきます。

ちなみに build の最後は 3539 nodes / 26112 edges と言っているのに、直後の status は少し数字が減ります。

$ code-review-graph status
Nodes: 3515
Edges: 25858
Files: 185
Languages: bash, javascript, typescript, python, java, tsx, csharp, r, c, cpp, dart, ...
Built at commit: 50a8ad3aeb91

build の生パース数と、FTS索引に載る status の数がずれる、というだけの話だと思いますが(ここは推測です)、レビュー支援の文脈で「数が合わない」と一瞬ドキッとするので、最初に知っておくと落ち着けます。あとサポート言語が32種類(bash, python, go, rust, kotlin, swift, solidity, zig …)もあって、tree-sitterの言語パックをそのまま食わせている強みが出ていました。

ハマったのは「浅いclone」

ここからが本題です。私はコンテナ内で対象を git clone --depth 1 で持ってきていました。ディスクも時間も節約できるので、検証ではほぼ反射的にこうしてしまいます。

この状態で目玉機能の detect-changes(変更の影響範囲とリスクを、既存グラフに照らして読み取る)を叩くと、こうなりました。

$ code-review-graph detect-changes
WARNING: git diff failed (rc=128): fatal: bad revision 'HEAD~1'
{
  "summary": "Analyzed 1 changed file(s):\n  - 0 changed function(s)/class(es)\n  - 0 affected flow(s)\n  - 0 test gap(s)\n  - Overall risk score: 0.00",
  "risk_score": 0.0,
  "changed_functions": [],
  ...
}

WARNING は出るものの、コマンド自体は成功(exit 0)で返ってきて、JSONは「変更0件・リスク0.00」。ここが今回一番の落とし穴でした。警告は1行だけ、そのあとは正常っぽい空の結果が返るので、気づかないとそのまま「変更なし」と受け取ってしまうんですよね。

原因は単純で、detect-changes は既定で HEAD~1HEAD の差分(=直前のコミットで何が変わったか)を見にいく設計なのに、--depth 1 の浅いcloneには HEAD~1 が存在しないからでした。ツールが悪いのではなく、私が「レビュー対象の履歴を1コミットしか持ってこなかった」のが敗因です。

試しに git fetch --unshallow で履歴を全部(564コミット)持ってきてから、まったく同じコマンドを叩き直すと、別物のように喋りはじめました。

$ git fetch --unshallow          # 564 commits
$ code-review-graph detect-changes
{
  "summary": "Analyzed 6 changed file(s):\n  - 19 changed function(s)/class(es)\n  - 11 test gap(s)\n  - Overall risk score: 0.55\n  - Untested: _parse_numstat, compute_file_churn, compute_risk_score, ...",
  "risk_score": 0.55,
  "changed_functions": [
    { "name": "_parse_numstat", "file_path": ".../changes.py", "line_start": 178, "risk_score": 0.45 },
    ...
  ]
}

6ファイル・19関数の変更、テストギャップ11件、総合リスク 0.55、そして「まだテストされていない関数」の名前まで並びます。context_savings として「このレビューでトークンを43%節約できた」といった見積もりも返ってきました。同じコマンド・同じコードベースで、cloneの深さを変えただけでこの差です。

cloneの状態detect-changesの結果
—depth 1(浅い)WARNING 1行+変更0件・リスク0.00(実質、何も分からない)
fetch —unshallow(全履歴)6ファイル・19関数・テストギャップ11・リスク0.55+未テスト関数一覧

「レビュー支援ツールを浅いcloneのCIに雑に組み込むと、警告を握りつぶした瞬間に”いつも変更なし”を返す置物になる」——これは自分がCIに入れる立場だったら絶対に踏んでいた地雷なので、先に踏めてよかったです。ここは改善の余地というより運用側の注意点ですが、個人的には「警告が出たらexitを非ゼロにしてくれてもいいのに」とは思いました。このあたりは好みが分かれるところだと思います。

グラフを絵で見る

もうひとつ触っておきたかったのが visualize です。--format html を付けると、3.8MBほどの単一HTMLが吐かれます(中身はD3.jsで、外部CDNを1本読むだけの自己完結ファイル)。これをブラウザで開くとこうなりました。

code-review-graphが生成したインタラクティブなコード知識グラフ。コミュニティ(fixtures-user、code-review-graph-tool、backend-graph、tools-func、eval-benchmarkなど)ごとに色分けされた円がクロスコミュニティのエッジで結ばれ、下部にNodes 3515 / Edges 25858 / Files 185 / 32言語と表示されている

コミュニティ検出でクラスタリングされた塊が、fixtures-usercode-review-graph-toolbackend-grapheval-benchmark といったラベル付きで浮かんでいます。円をダブルクリックすると中に潜って個別ノードまで下りられる作りで、下端に Nodes 3515 / Edges 25858 / Files 185 / Mode community がそのまま出ているのが分かります。status で見た数字と絵がちゃんと一致しているのは、当たり前のようで安心できるポイントでした。

同じ構造から wiki サブコマンドを叩くと、コミュニティ単位でMarkdownのページが17枚生成されました。グラフを人が読む用に落とす導線まで用意されているのは、なるほどよく出来ているなと思います。

試してみての所感

ライブラリ選定でいつも気にするのは「動かないときに理由が読めるか」なんですが、今回の detect-changes は、動かない(=空を返す)ときにちゃんと WARNING を1行残していました。私は最初その1行を軽く見て沼ったわけですが、裏を返せば手がかりは出してくれていたわけで、そこは誠実な作りだと感じます。

pip 一発・ローカル完結・APIキー不要で、185ファイルが5.5秒。この軽さなら、まず自分の手元の中規模リポジトリで buildvisualize して構造を眺めるところから使ってみたいです。一方でCIに detect-changes を組み込むなら、cloneを浅くしない(あるいは警告を検知したら落とす)ラッパーを噛ませるのが前提だな、というのが今回の一番の学びでした。

もっと良い運用の仕方があったら教えてください。