なぜこれが存在するのか
コードベースの要約は、エージェントが物事を見つける助けにはなりません。助けになるのはルーティングです。これはその論証と、そこから導かれる設計です。
すでに見たことのある失敗
システムをまたぐ変更をコーディングエージェントに頼みます。エージェントはシンボルを grep し、 もっともらしい場所を見つけ、それを編集し、成功を報告します。
見逃したもの:
- 実際にその挙動を制御している、3 ディレクトリ離れた定数
- バッチパスにあるミラー実装
- いま変更したものを数えているメトリクス
- 古い挙動をアサートしているテスト
エージェントは、コードを「どう」書くかで混乱していたのではありません。コードがどこに あるかで混乱していたのです。そして、それを知る術がありませんでした。手元にあったのは テキスト検索と、リポジトリを収めるには小さすぎるコンテキストウィンドウだけだったからです。
なぜ要約では直らないのか
わかりやすい対応は「コードベースを要約して、その要約をエージェントに渡す」です。これは 特定の理由で失敗します:
要約は「これは何か?」に答えます。エージェントに必要なのは「どこにあるか?」です。
アップロードサブシステムについての美しく書かれた段落は、リトライバジェットが
worker/queue.py にもあって metrics/emit.py から読まれている、とはエージェントに教えて
くれません。さらに悪いことに、要約は「もっともらしい文章」です — エージェントは喜んで
その上で推論しますし、どの文が構造を支える事実で、どの文がモデルの言い換えなのかを
見分けられません。
3 つの失敗モードが続きます:
- アドレス指定できない。 文章が名指しするのは概念であって、パスや行範囲ではありません。
- 検証できない。 パースされた事実と推測を区別するものが、その中には何もありません。
- 腐る。 コードが変わった瞬間、要約は静かに間違いになり、そのことを示すものは何も ありません。
Handbook が代わりにやること
要約ではなく、索引を作る
出力はちょうど 1 つの問いに答えます: この変更は、どのファイル、関数、状態に触れなければ ならないか?
すべてのエントリはアドレス — パス、修飾名、行範囲 — であり、実際のパースに由来します。 それらのアドレスを取り囲む文章は人間が読むのを助けるためにあり、エージェントが行動の 根拠にすべきものでは明示的に「ありません」。SKILL パッケージは最初の行でそう述べています:
このハンドブックはコードベースの位置の索引であり、コードの説明ではない。変更が どのファイル、関数、状態に触れる必要があるかを決めるために使い、そのうえで実ソースを 読むこと。
事実と文章を、構造によって分離する
| 出所 | 間違いうるか? | |
|---|---|---|
| ファイル、関数、行範囲、呼び出しエッジ | tree-sitter | いいえ — パース結果です |
| どの呼び出しが解決できなかったか | tree-sitter | いいえ — 隔離されるのであって、推測されません |
| どのファイルがパースできなかったか | tree-sitter | いいえ — 開示されるのであって、捨てられません |
| ステージ構造 | LLM、その後機械的に検証 | 構造的には、いいえ。判断としては、あり得ます |
| purpose、ウォークスルー、概要 | LLM | はい — そして文章であるとラベル付けされます |
この分離は慣習ではなくパッケージ境界によって強制されます: アナライザ、レンダラ、 スキルパッケージャ、パッチャは LLM パッケージにまったく依存しません。
目に見える形で失敗する
ここでのすべての設計判断は 1 つのルールに従います: 何かがうまくいかないときは、そう言う。
- カード生成に失敗したファイルも、空の説明のまま現れます。
_coverage.jsonに列挙されます。 落とされることも、捏造されることも決してありません。 - アナライザが解決できなかった呼び出しは、カテゴリと生テキストと共に
dropped-calls.jsonに入ります。もっともらしいエッジとして推測されることは決してありません。 - アナライザが読めなかった、あるいは一部しかパースできなかったファイルは、その理由と共に
scan-coverage.jsonに入ります。カバー済みとして数えられることは決してありません — 誰も 開いていないファイルは、「関数のないファイル」ではないからです。 - 設定駆動エンジンで解析された言語は概要に名指しされるため、「ベストエフォートの 呼び出し関係」が「正確」と読まれることはあり得ません。
- 諦めたプランナーのランは非ゼロで終了するため、その謝罪文をプランと取り違えるスクリプトは ありません。
- 0 回、あるいは 2 回マッチするパッチアンカーは拒否されます。どちらか 1 つを選ぶことは 決してありません。
比例したコストで最新に保つ
ドキュメントが腐るのは、更新に執筆と同じだけのコストがかかるからです。resync は古い
コールグラフと新しいコールグラフを差分比較し、変わったものだけを再生成します — 触れられた
ファイルのカード、新しいファイルの割り当て、影響を受けたステージの文章。3 ファイル触れば、
3 ファイル分の支払いです。
残りはコンテンツハッシュのキャッシュが引き受けます: 入力の変わらなかったステージは、 そもそも再ナレーションされません。
経済性
生成が高価なステップであり、それは一度だけ起こります。その後のすべて — markdown への
レンダリング、HTML サイト、エージェント索引、llms.txt、SKILL としてのパッケージング、
そのパッケージの検証 — は決定的で無料です。コミットごとに実行できます。
この分割こそが、render と skill が generate のフラグではなく独立したコマンドである
理由であり、両者が事故でも LLM に到達できないパッケージに置かれている理由です。
これは何ではないか
- コード検索ツールではありません。
grepや LSP を置き換えません。エージェントに 「それらをどこへ向けるか」を教えます。 - 自律的なコーディングエージェントではありません。 プランナーは構造上読み取り専用で、
書き込みツールを持ちません。
applyはループにモデルのいない機械的な実行者です。その間で 判断するのは人間です。 - あなた自身のドキュメントの代替ではありません。 アーキテクチャ上の意思決定、 プロダクトの意図、チームの規約はコールグラフから導出できませんし、Handbooks はできる ふりをしません。