docs-kit ポストモーテム——設計書は、トークンを節約しなかった
7月30日に公開した docs-kit を、失敗と総括しました。狙った効果——AIに設計書を効率よく読ませ、トークンを節約する——は得られず、逆に全コミットの63%が文書の同期作業に消えました。何を間違え、どこで気づけたはずだったのか。同じ問題を解こうとする人のための一次資料として、記録を公開します。
7月30日に Labs で公開した docs-kitを、その約1週間後、失敗と総括しました。狙った効果——AIコーディングエージェントに設計書を効率よく読ませ、トークンを節約する——は得られませんでした。
成功の告知は世の中にあふれていますが、失敗の記録は少ない。この記事は、docs-kit のリポジトリに残したポストモーテムの要約です。何を狙い、何が起き、どこで間違えたのか。同じ問題を解こうとしている方の時間を1週間でも節約できるなら、公開する価値があると考えました。
何を狙ったか
解こうとした問題は2つです。エージェントが設計意図を知らないまま実装すること、そして古い設計書を疑わずに信じること。
docs-kit はこれを、front matter 付き Markdown を単一の情報源として解こうとしました。各文書に「責任を持つコード範囲(scope)」と「最後に実装と突き合わせたコミット(verified_at)」を持たせ、鮮度を git の履歴から機械的に判定する。そして逆引きコマンド which に「これから触るファイル」を渡すと、関係する文書だけが返ってくる——という設計です。
想定していた流れは、こうでした。
実際に起きたこと
実プロジェクト(グリーンフィールドの macOS アプリ)に投入した2巡のフィールドレポートで、この写像は両方向に破れました。
1巡目。コードが1行も存在しない状態で、実装計画を含む17本の文書を先に書きました。which は scope の glob マッチから始まるため、マッチする実ファイルがなく何も返しませんでした。規約に従ったエージェントは、17文書のどれも読まずに実装を始めました。front matter のスキーマを知るために、エージェントがわざと不正な値を lint に食わせてエラーメッセージを読むという一幕もありました。スキーマは README に全部書いてあったのに、セッションの中から到達する経路がなかったのです。
2巡目はさらに深刻でした。1ファイルの逆引きが ADR 19本・141KB を返す一方で、status: current の要件文書とロードマップ計40KB——規約が「仕様として信頼せよ」と指定している文書——は、scope を持たないため構造的に返りません。結果、ロードマップの全項目を閉じた後も機能が未実装のまま残りました。
そして約4,800行のコードがどの文書の scope にも属さない状態で、lint は OK、stale は全て ok を出し続けていました。セッションの中からは、健全な状態とオーナー不在の半分のコードベースが、同じに見えていたのです。
数字が示していたこと
振り返れば、リポジトリ自身の統計が問題を物語っていました。
ツール本体は Go で4,009行。それに対して、使い方を説明する文書が約150KB。本体より、本体の説明のほうが重い。 そしてコミットの3分の2は、コードではなく文書を実装に合わせ直す作業でした。
何が悪かったか
文書は、コードの代替にならなかった
トークン節約が成立する条件はただ一つ、文書を読むことでコードを読まずに済むことです。実装作業ではこれが成立しません。仕様書を読んでも、変更する関数のシグネチャや既存の呼び出し元は結局コードを読まないと分からない。文書は「コードの代わり」ではなく「コードに加えて読むもの」、つまり純粋な追加コストになりました。
とりわけ「現在の仕様」を書き写す spec 層は、コードから導出可能な情報の劣化コピーでした。コードが真実で、写しは必ず遅れる。その遅れを検出するために鮮度検証を作り、遅れを埋める同期作業がコミットの63%を食いました。
規約は、エージェントの自然な行動に勝てなかった
「作業前に which を実行し、返ってきた文書だけを読む」という規約は全セッションに載せていました。しかしこれは、エージェントが本来やりたがる行動——対象ファイルを直接読み、必要なら grep する——より弱い誘導でしかありません。しかも2巡目では、規約に従った結果として、必要な40KBを読まず不要な141KBを読んでいます。規約が守られたかどうかを外から観測する手段もなく、失敗は静かに起きました。
「関連文書を返す」は、glob では解けなかった
which の中核である「変更対象のパス→読むべき文書」という写像を、scope の glob マッチと依存グラフ1ホップで実装しました。しかし「関連しているか」の判定は本質的に意味論の問題であり、この規模のヒューリスティックでは近似できません。精度を上げようとすると戻り値が増え、トークン節約という目的と正面から衝突します。修正のたびに出口が「文書を増やす・戻り値を増やす」方向にしかなかったことが、最大の兆候でした。
鮮度検証は、問題を義務に変換しただけだった
「古い文書を機械的に検出する」ことには成功しました。しかしそれは「文書が腐る」問題を解いたのではなく、「腐らないよう常に手入れし続ける義務」に変換しただけでした。しかもその手入れは AI に任せることになり、節約したかったトークンをここで消費しました。削ろうとしていたコストと、払っていたコストは同じものだったのです。
そして、目的を一度も測らなかった
最後の修正で書いた設計記録に、こういう一文があります。
docs-kit の全ての検査は一方向にしか走らない。文書から出発し、コードがまだ文書に合っているかを問う。コードから出発して、どの文書がそれを説明しているかを問う検査は、ひとつもない。
同じ非対称性が、プロジェクトの評価そのものにもありました。「ツールが正しく動いているか」は毎回検証していたのに、「使った結果、実際にトークンが減ったか」は一度も測っていません。ベースラインも取っていない。手段の正しさの検証を、目的の達成の証拠と取り違えていました。
1巡目のフィールドレポート——グリーンフィールドで which が何も返さない——の時点で、問うべきは「どう直すか」ではなく「この写像は原理的に解けるのか」でした。実際にはツールを直す方向に進み、2本の追加設計を経て、同じ場所に戻ってきました。
拾えたもの
全てが無駄だったわけではありません。
- ADR(決定と、その理由の記録)。 なぜその設計にしたか、何を検討して捨てたかは、コードから導出できない情報です。読む価値がトークンコストを上回り、過去の決定は過去の決定のままなので腐らない。そしてツールは要りません。ディレクトリに Markdown を置くだけで維持できます。却下した案とその理由を残す形式は、同じ議論の再燃を実際に防ぎました。
- 短い CLAUDE.md に、コードから読み取れない制約だけを書く運用。「直接依存は3本まで」「標準ライブラリ寄りに書く」のような制約は効きました。
- 常時読ませるものと、必要な瞬間にだけ読ませるものを分けるという発想。文書作成時の指針を全セッション共通の規約からテンプレートへ移した判断は正しく、後継でも使えます。
一方、spec 層——コードから導出可能な仕様の書き写し——が失敗の中核であり、ここを踏襲してはいけません。
後継への6つの問い
次の仕組みを設計する前に答えるべき問いを、ポストモーテムの末尾に残しました。答えられないなら、作らない。
- この文書を読むことで、読まずに済むコードは具体的に何行か
- その情報はコードから導出できるか。できるなら書かない
- 腐ったとき誰が直すか。その維持コストは、削減しようとしているコストを打ち消さないか
- エージェントがそれを読むことを、規約以外の何が保証するか
- 成功をどう測るか。ベースラインは取ったか
- 最初の実地投入で根本的な欠陥が出たら、修正する前に前提を1度疑うと決めてあるか
docs-kit の機能追加は止めています。ADR を書く習慣だけは、ツールなしで続けています。失敗と分かるまでに要したのは公開から約1週間——それを可能な限り速く判定できたことが、この試みで唯一誇れる点かもしれません。
公開から約1週間、実プロジェクトでのフィールドレポート2巡で失敗と判定。ツール本体4,009行に対し、その使い方を説明する文書が約150KB。コミットの3分の2は文書の同期作業でした。
コードから導出できる情報は書かない。エージェントの行動は規約ではなく経路で設計する。そして目的が「トークン削減」なら、最初に測定方法を決めてベースラインを取る。