コード知識グラフが3ヶ月で腐る — 私が計測した4つの劣化パターン
3ヶ月前に構築したコード知識グラフを、先週、私はほとんど信用できなくなっていました。
構築時はレビュー精度が明らかに上がったのを覚えています。UserService.createのシグネチャを触ったとき、blast radiusが「この28ファイルに影響する」と正しく返してきました。当時は感動していました。3ヶ月経った先週、同じ関数を触りました。KGは「12ファイルに影響」と返してきました。実際にCIを走らせたら41ファイルが落ちました。KGは私を助けるどころか、私を油断させる装置になっていました。
この記事は、Tree-sitter + MCPで組んだ約5,000ノードのコード知識グラフを、3ヶ月放置したときに何が起きたかの実測ログです。
結論から書くと、劣化パターンは4種類あって、それぞれ独立に対処する必要がありました。
構築側の話は~の下にBookでまとめていますが、この記事は運用側です。
まず、劣化を数字で見せる
主観の話にすると議論が空中戦になるので、先に数字を並べます。同じリポジトリ(約30万行、Python + TypeScript混在)を対象にして、KG構築直後と3ヶ月後で測ったものが以下です。
| 指標 | 構築直後 | 3ヶ月後 | 変化 |
|---|---|---|---|
| 総ノード数 | 4,821 | 5,102 | +5.8% |
| 総エッジ数 | 12,357 | 12,891 | +4.3% |
| 動的呼び出し検出漏れ率 | 6.2% | 22.7% | +16.5pt |
| blast radius 平均ノード数 (Hop=3) | 23.4 | 8.7 | -62.8% |
| MCP tool 呼び出しでの誤答率 | 4.1% | 18.9% | +14.8pt |
ノードとエッジの数はほぼ変わっていません。ここが最初の罠でした。「まだ生きてる」と錯覚しました。しかし内部の質は崩れていました。特にblast radius が3分の1になっているのは重大です。実際のコード規模は成長しているのに、KGが返す影響範囲は縮んでいる。KGが現実を追いきれていない証拠です。
ここから、その内訳を4パターンに分解します。
劣化パターン1: 動的呼び出しエッジの漏れが累積する
一番影響が大きかったのはこれでした。
Tree-sitterで取れるのは静的な呼び出しだけです。obj.method()のように書かれていれば取れる。しかしgetattr(obj, method_name)()や、TypeScriptでthis[key]()と書かれた瞬間、Pass 1では取り逃します。構築直後、静的解析(Pass 1)だけで得られる CALLS エッジのカバレッジは93.8%でした。残り6.2%は動的呼び出しで、これはPass 2(実行トレースからの補完)で埋める設計にしていました。
問題は、Pass 2 を「四半期ごとに1回」というルールにしてしまったことでした。
3ヶ月の間に新規機能が入り、その多くがdispatch tableやプラグイン機構を使っていました。動的呼び出しが増える。しかしPass 2が回っていないので、KGは新しい動的呼び出しを1つも知りません。結果として、動的呼び出し検出漏れ率は 22.7% にまで悪化しました。5本に1本の呼び出しが KG から見えていない状態です。
対処: Pass 2 を週次にしました。 実行トレースの収集は本番のログ集約基盤に相乗りしていて、実行コストはほぼゼロです。ボトルネックだったのは「Pass 2 を回そう」という判断を人間がしていたことでした。人間の判断ループを外して cron に移した瞬間、この劣化は止まりました。
劣化パターン2: 削除ノードのゴースト残留
2つめは、削除された関数がKGに残り続けるパターンです。
Tree-sitterの増分parseは、ファイルの追加と変更には強いです。しかし「関数ごと消されたケース」を明示的にpurgeしないと、KG側にノードだけ残ります。エッジも残ります。呼び出し元も残ります。3ヶ月の間に、私は少なくとも12個の関数を削除していました。commit hashで追跡できます。KG側に残っていたのは9個でした。つまり、実在しない9個の関数について、KGは「呼び出し元がこれこれある」と平然と返してきていた。
ゴーストです。
このゴーストが厄介なのは、blast radius の分子を膨らませることです。「この12ファイルに影響」と返ってきても、そのうち3ファイルはゴーストノード経由の偽の影響でした。人間側は「KGが3ファイル多く言ってきた」に気づけません。
対処: 週次のfull-rebuildを走らせるという力技に落ち着きました。増分parseを信じきらず、週1で全ノードをdrop→再構築します。5,000ノード規模なら6-8分で終わるので、この規模ではfull-rebuildが正解でした。もっと大きなリポジトリではPathfinderを別に組む必要があるはずですが、それはこの記事の範囲外です。
劣化パターン3: 共有ユーティリティが blast radius を膨張させる
3つめは Book でも触れた「共有ユーティリティ罠」の実運用版です。
構築直後はutils.format_dateのような汎用関数を除外リストに入れていました。20個ぐらいでした。しかし3ヶ月経つと、新しい共有関数が生まれています。utils.sanitize_input、helpers.normalize_locale、common.retry_with_backoffなど。除外リストのメンテナンスを誰もしていませんでした。結果として、これらの新しい共有関数を触ると blast radius が「287ファイル」のような数字を返してきました。事実上、リポジトリ全体です。レビュー支援としては役に立ちません。しかもこれの逆パターンもあって、正当な共有関数を過剰に除外リストに入れてしまうと今度は blast radius が過小評価される。前述の「平均23.4→8.7」の一部はこの過剰除外でした。
対処: 除外リストを「呼び出し元カウント」で自動判定するように変えました。呼び出し元が閾値(私の環境では45)を超えたら自動除外、下回ったら復帰。人間のリスト管理を外しました。
これも本質はパターン1と同じで、人間の判断ループがボトルネックになる構造は必ず腐るという話です。
劣化パターン4: MCP tool 側の返答仕様が古びる
4つめは KG そのものではなく、Claude Codeなどのエージェントに KG を晒すMCP tool 側の話です。
構築初期、MCP toolはget_blast_radius(function_name)という素朴なsignatureでした。エージェントはfunction名だけを渡してきます。3ヶ月の間に、リポジトリ側で同名関数が複数箇所に生まれました。user_service.createとadmin_service.createが別ファイルで共存する状況です。当初はcreateという関数名は8個でしたが、3ヶ月後には23個ありました。MCP toolは相変わらずfunction_nameしか受け取れないので、エージェントが「createのblast radiusを見せて」と聞いてきたときに、23個のうちどれを返すべきか特定できません。私の実装は先頭一致で返していたので、平均して事実と違う関数の blast radius を返していました。誤答率18.9%の内訳の大半はこれでした。
対処: MCP toolのsignatureを(file_path, function_name)のペアに変更しました。 これは breaking change なのでエージェント側のプロンプトも更新が要ります。ですが、この変更なしでは MCP tool は徐々に嘘つき機械になります。
より根本的に、自然言語エージェントハーネスの研究を読んで、tool signature 側でエージェントの曖昧性を吸収する設計が主流になりつつあると理解しました。この方向はもう1本書けそうです。
4パターンを1枚にまとめる

4パターンは独立しているようで、実は共通の構造を持っています。どれも「人間の判断ループがどこかに残っていた」から腐りました。
| パターン | 腐りの原因 | 対処 |
|---|---|---|
| 1. 動的呼び出し漏れ累積 | Pass 2を人間が判断して回していた | cron週次に移譲 |
| 2. 削除ノードのゴースト | 増分parseに削除検出が甘い | 週次full-rebuildで力技解決 |
| 3. 共有utils blast radius膨張 | 除外リストを人間が管理 | 呼び出し元カウントで自動判定 |
| 4. MCP tool signatureの陳腐化 | breaking changeを避けていた | 曖昧性吸収するsignatureに変更 |
裏側にあるメッセージは「知識グラフは自動化されないと腐る」です。1回作って喜んでいる期間は3ヶ月です。私はそれを実測しました。
この先に置いておきたい前提
もう1つだけ。
この記事は「4,821ノード規模、Python+TypeScript混在、Tree-sitter Pass 1 + 実行トレース Pass 2、Neo4j保存」という私の環境の話です。ノード数が1桁変われば、対処法も変わります。100万ノード級では週次full-rebuildは無理です。差分検出を真剣に組む必要があります。私がまだ触っていない領域です。10ノード級ならそもそも KG を作る意味が薄いです。ファイル数個ならエージェントに全部読ませればいい。
自分の規模で「腐る速度」を実測してから、対処の重さを決めるのが健全だと思います。
コード知識グラフをこれから作る、あるいは既に作って運用に困っている人には、この本を書いておきました。運用フェーズの罠を先に潰しておくと、感動から失望への落差が小さくなります。
関連書籍 ナレッジグラフ活用大全 ナレッジグラフ 活用大全 | GraphRAG・Neo4j・RDF・Property Graph・Emotion AI の実践書 書籍ページを見る → この記事は役に立ちましたか?