← ブログに戻る

AIレビューがトークン80%焼く問題、Tree-sitter+MCPで8-49倍削減

Claude Code に「このリポジトリ全体を読んで最近の PR をレビューして」と頼んだ夜、返ってきたレビュー6項目のうち5項目が、その PR で変更されていない場所への指摘でした。翌朝読み返して初めて、見るべき1行のロジックバグが末尾に小さく書かれていることに気づきました。私はこれを「全ファイル症候群」と呼んでいます。AI に全部読ませると、AI は全部について語ります。私が知りたかったのは、その1行が他のどこに波及するか、それだけでした。

同じ問題を、Tree-sitter で構造抽出し、MCP で必要箇所だけロードする2段構えで解いたら、実測で入力トークンが 8-49 倍削減されました。本記事は、その削減がなぜこのレンジで出るのか、どう自分のプロジェクトで再現するのかを、実装レベルで書きます。

なお本記事は「グラフ DB の選び方」の話ではありません。SQLite vs Kuzu vs Neo4j のような DB 比較記事は別に立てるべきトピックで、ここでは扱いません。焦点は「AI に何を渡すか」の設計です。

「全部読ませる」がトークンの80%を焼く内訳

30万行規模のコードベースを想定して、全ファイル読みが何を消費しているかを分解してみます。

  • リポジトリ全体のソースコード: 約 240万トークン
  • 依存パッケージのソース: 約 600万トークン
  • README、ドキュメント、設定ファイル: 約 30万トークン
  • 合計: 約 870万トークン

現代の主要 LLM のコンテキストウインドウの上限(2026年時点で Claude Sonnet 4.6 が 200k、GPT-5.3 Codex が 400k)にすら収まりません。仮に無理やり詰め込めたとしても、注意機構は無限ではなく、入力が長くなるほど末尾の情報を取りこぼしやすくなることが各種ベンチマークで報告されています。

サイバーエージェントの Developers Blog が「Cursor、Claude Code、Codex 導入後にコミット数が約2倍に増えたが、レビュー側がボトルネックになった」と報告しています。生産性向上の実際のボトルネックは「コードを書く」から「コードを理解する」に移りました。そして理解の効率は、AI に何を渡すかで決まります。全ファイルか、変更の影響範囲だけか。

Tree-sitter を選ぶ理由:306言語 grammar と構文エラー耐性

「絞る」を機械的に支える出発点が Tree-sitter です。2026年7月時点で 306 言語のプリコンパイル済み grammar が pack として利用可能で、grep や ctags や LSP を差し置いて Tree-sitter を実務上の第一候補にすべき理由が4つあります。

  • General: あらゆるプログラミング言語をパースできる汎用パーサー
  • Fast: エディタが各キーストロークごとにパースし直せる速度
  • Robust: 構文エラーを含むコードに対しても有用な部分木を返す
  • Dependency-free: 純粋な C11 ランタイムで、任意のアプリに埋め込み可能

このうち PR レビューで地味に効くのが Robust です。書きかけの関数、リファクタリング途中のシグネチャ、削除しきれず残った片割れ。こうしたコードは構文木としては壊れた状態ですが、Tree-sitter はエラー箇所を ERROR ノードとして木に残し、周囲の正常な部分は通常通り取得できます。

import tree_sitter_python as tspython
from tree_sitter import Language, Parser

PY_LANGUAGE = Language(tspython.language())
parser = Parser(PY_LANGUAGE)

broken = b"def hello(name return 'Hi'"  # コロン欠落
tree = parser.parse(broken)
print(tree.root_node.has_error)  # True
# それでも function_definition のノード自体は取れる

GitHub Action で PR をフックして影響範囲を計算するとき、PR ブランチが構文エラーを含んでいても処理を続行できれば「変更箇所の周辺で何が壊れる可能性があるか」だけは返せます。完全に動かないより、部分的に動く方が現場では遥かに価値があります。

Pass 1 と Pass 2:決定論と LLM の役割分担

Tree-sitter だけでは「動的呼び出し」や「設計意図」は取れません。ここで LLM を組み合わせる 2 段構えが効きます。

  • Pass 1(機械抽出): AST から決定論的に取れる情報:関数定義、静的な呼び出し、import、継承関係、型注釈。すべて confidence: 1.0
  • Pass 2(LLM セマンティック抽出): 設計意図、ドメイン概念タグ、動的呼び出しの推測。confidence: 0.0-0.9

Pass 2 は LLM 呼び出しなのでコストが Pass 1 の 100 倍以上違います。30万行に全行 Pass 2 を回すと Claude Sonnet で数百ドル単位になり、現実的ではありません。

そこで「ホット/コールド」戦略を取ります。

  • コールド(全体): Pass 1 だけを 1 回走らせてベースラインのグラフを作る
  • ホット(差分): 変更されたばかりの関数だけ Pass 2 で意図抽出

Pass 2 で LLM に渡す入力も工夫が要ります。素朴に「ファイル全体」を渡すと、Pass 2 の中で再び「全ファイル症候群」が発生します。私が渡すのはこの4点だけです。

  • 対象関数の本体(必須)
  • 属するクラスやモジュールの docstring
  • 呼び出し元 1-2 関数のシグネチャ
  • 呼び出し先 1-2 関数のシグネチャ

これで Pass 2 の入力トークンは典型的に 200-800 トークンに収まります。周辺情報は Pass 1 で構築済みのグラフから瞬時に取り出せます。Pass 1 と Pass 2 は互いを支え合います。

MCP ツール設計:ツール数が22に落ち着く理由

MCP(Model Context Protocol)は、AI アシスタントと外部システムを繋ぐ標準プロトコルです。コードKG を MCP サーバとして公開すれば、Claude Code や Cursor が「この関数の影響範囲は?」と自然言語で聞けるようになります。

先行 OSS の CodeGraph は 22 種類のツール、Code Pathfinder は 6 種類を露出しています。なぜこの数字なのか。1 ツールで「query(natural_language: str)」に全部任せる素朴実装が動かない理由は 3 つあります。

  • AI が何を聞けるかわからない: MCP のツール一覧は AI への「能力メニュー」として機能する。1 ツールでは能力範囲が不明瞭
  • 応答時間の予測がつかない: 軽い質問(関数定義取得)と重い質問(全リポジトリの影響範囲計算)が同じインターフェースで来る
  • 型安全性が失われる: JSON Schema で function_name: str のように具体的な引数を持たせれば、AI の誤呼び出しが減る

CodeGraph と Code Pathfinder のツール一覧を観察すると、機能は概ね 4 軸に分けられます。

質問例該当ツール例
構造の取得この関数はどこで定義されているか?get_function, get_class
依存追跡この関数は何を呼んでいる? 何から呼ばれている?get_callers, blast_radius
検索”auth” を含む関数を全部知りたいsearch_symbols, fuzzy_search
メトリクスこの関数の複雑度は?get_complexity, get_metrics

各軸で 5-6 ツール、合計 20-25 で「全部の質問に答えられる」ようになります。22 ツール前後の総数はここから逆算されます。

初期実装はフル 22 ツールを目指す必要はなく、次の 5 ツールで典型質問の 8 割に答えられます。

  1. get_function(name):シグネチャ、行範囲、所属クラス
  2. get_callers(name, max_hops=1):直接の呼び出し元
  3. blast_radius(name, max_hops=3, min_confidence=0.8):影響範囲
  4. search_symbols(query, type="function|class"):シンボル検索
  5. get_file_summary(path):ファイル単位のサマリ

残り2割が必要になったら拡張すれば十分です。

実測 8-49倍の中身:なぜ幅が広いか

全ファイル読みと Tree-sitter+MCP の比較

先行 OSS の README で目にする「8 倍から 49 倍削減」という数字は、幅が広すぎて半信半疑になります。私も最初はそうでした。この幅は 3 つの要因で説明できます。

  • コードベース規模: 1 万行では削減効果が薄い。100 万行では全ファイル渡しが物理的に不可能で、削減率が大きく見える
  • PR の局所性: 局所的な PR ほど影響範囲が小さく、削減率が高い
  • ベースラインの定義: 「全リポジトリ」を baseline にすると削減率が大きく見える。「PR 関連ファイルだけ」を baseline にすると削減率が小さく見える

「49 倍」は 100 万行コードベース × 局所的な PR × 全リポジトリ baseline の組み合わせ。「8 倍」は 1-10 万行 × 中規模 PR × PR 関連ファイル baseline あたりに相当します。

私の手元の実測(Python、約 5 万行、過去 30 PR から無作為に 10 件、tokenizer は tiktokencl100k_base)は次のような分布でした。

  • ベースライン入力: 平均 約 18,000 トークン / PR
  • コードKG 適用入力: 平均 約 2,400 トークン / PR
  • 削減率: 約 7.5 倍(個別 PR のレンジは 4 倍 - 22 倍)

先行 OSS の下端に近い水準です。自分のプロジェクトでも、まず 10 PR ぶん実測してレンジと中央値を出すのが判断の材料になります。

自分のプロジェクトで今日試せる最短ステップ

  • 過去 10 PR をランダムに選ぶ
  • 各 PR について「ベースライン入力トークン」と「Tree-sitter Pass 1 で絞った入力トークン」を計測
  • 削減率の中央値と分散を出す

これが「自分のプロジェクトでのトークン削減率」の現実的な数字です。8 倍に近いか、49 倍に近いか、あるいはもっと低いか高いか。導入判断はここから始まります。

まとめ

  • AI コードレビューが全ファイル読みでトークンの 80% を焼く問題は、Tree-sitter による構造抽出と MCP による必要箇所ロードの 2 段構えで実測 8-49 倍削減できる
  • Tree-sitter は 306 言語対応と構文エラー耐性で「壊れたコード」でも動く、PR レビュー自動化の前提を満たす
  • Pass 1(機械抽出、confidence 1.0)と Pass 2(LLM、confidence 0.0-0.9)を役割分担し、Pass 2 はホット領域だけに絞る
  • MCP ツールは 4 軸 × 5-6 個の分解で 22 ツール、初期実装は 5 ツールから
  • 削減率の幅はコードベース規模 × PR 局所性 × ベースライン定義の組み合わせで説明できる

コードベースを構造で扱う設計論を実装レベルで通しで追いたい方には、拙著のコードベース・ナレッジグラフ実践ガイドが役に立つはずです。Tree-sitter Pass 1 の 5 言語別実装、MCP 22 ツールの設計判断、GitHub Action への組み込み、精度評価の後ろ向き検証まで一冊で扱っています。