開発者10人、docsフォルダは1つ
SuperpowersはClaude Codeのプラグインで(GitHubスター28.6万)、そのskillは仕様書と実装プランをmarkdownファイルとして書き、リポジトリにcommitする。デフォルトでは、それらのファイルはすべてdocs/superpowers/という1つのフォルダに集まる。6月、Pythonの金融ライブラリOkamaのメンテナーは、エージェントがそこに書いた8本の実装プランが、プラグインの意図どおりにそのままcommitされ、Read the Docs上で公開Webページとしてレンダリングされていたことに気づいた。リリース後に発覚した事件だった。
これを規模で考えてみよう。フロントエンドチーム、開発者10人、4スクワッド、リポジトリ1つ、そして全員がこれらのファイルを生成する。3つの問いがリードの手元に届く。何をversion管理の対象にするのか。2スプリント前に削除されたパスを名指す仕様書があるとき、それはドリフトの問題なのか。そしてこのフォルダを統治するのは誰なのか。Okamaの流出はcommitしたこと自体が原因ではない。ルールなしにcommitしたことが原因だった。この記事は5つのルールを示す。
どのツールもファイルをcommitする
Superpowersはcommitするかどうかを尋ねない。planningのskillの18行目が、すべてのプランをdocs/superpowers/plans/の下に、日付付きで、機能1つにつき1ファイルとして保存する。brainstormingのskillは設計文書を書き、まだプランを見ていないうちに同じステップでそれをcommitする。
これはSuperpowers特有の癖ではない。主要なspec-driven系のツールはどれも同じ選択をしている。
| Tool | Publisher | Where specs live |
|---|---|---|
| Superpowers (286,000 stars) | obra | docs/superpowers/、すべてを1つのフォルダに |
| Spec Kit (136,000 stars) | GitHub | 機能ごとに番号付きの1フォルダ、そのfeatureのbranch上 |
| Kiro | Amazon | .kiro/specs/、機能ごとに1フォルダ |
| AI-native SDLC playbook | Anthropic | ステージごとに1つのcommitされたartifact: intent、spec、plan、diff、review findings、incident record |
Kiroは機能ごとのフォルダを、チームメイトが別々の機能を同時に扱うための仕組みとして売り出している。8月に公開されたAnthropicのplaybookはさらに進んで、各ステージが次のステージが読めるartifactをcommitする。つまりcommitするかどうかは最初から問いではなく、どこに置くかはすべてのツールが答えを出している。誰がそのファイルを所有するのか、そしていつ死んだのかは、答えられていない。この隙間こそ、5つのルールが埋める対象だ。
3つの失敗パターン
commitされたプランは、3つの異なり方で失敗する。
失敗1: ドキュメントジェネレーター。 docs/以下のmarkdownファイルを、目次に載っているかどうかにかかわらずすべてレンダリングするdocsビルドは、プランフォルダをマニュアルと一緒に出荷してしまう。これがまさにOkamaに起きたことだ。
失敗2: branch。 Superpowersのissue #1246は、brainstormがプランをそのままmainにcommitしてしまうと報告している。あるユーザーは1セッションあたり10〜15回のcommitを数え、数回の変更ごとに新しいcommitが生まれるという。そのスレッドの要望は一行に収まる。planとspecをmainブランチに置かない、というものだ。
失敗3: ドリフト。 spec drift(仕様のドリフト)は静かな失敗だ。コードは進化するのに仕様書は進化しない。ThoughtworksのBirgitta Böckelerは、spec-driven developmentを3段階に分けている。
| Level | Meaning |
|---|---|
| Spec-first | 書かれて、一度だけ使われる |
| Spec-anchored | その機能が存在する間、維持され続ける |
| Spec-as-source | 人間が編集するのはspecだけ |
Superpowersはspec-firstな文書を書き、それを永久に保持する。つまりデフォルトでは、spec-anchoredな保存にfirst-draftの保守しか伴わないという状態になる。誰もそのファイルを更新せず、エージェントは翌四半期にそれを真実として読んでしまう。TrueFoundryは端的にこう言う。ドリフトはspec-firstな実践では避けられず、spec-anchoredな実践では管理できるが、それも divergence を検知できる場合に限る、と。
41本のspecと9本のagentファイルを抱えるリポジトリは、整理整頓の問題を抱えているわけではない。41個のversion管理されていない振る舞いの入力を抱えているのだ。Böckeler自身の反応はこうだ。これらのmarkdownファイルよりも、コードをレビューしたい。二度と読まれないプランは単なるゴミだ。しかしエージェントが読み込むプランは指示であり、古くなったそれは誤った指示になる。
ルール1: スクワッドごとに1つの拠点とowner
ルール1: フォルダにはownerがいる。そのownerはプラグインではなくスクワッドだ。version 5以降、SuperpowersはプロジェクトのCLAUDE.mdの指示を、自らのデフォルトより優先して尊重する。プランをどこに置くか書けば、そこに置かれる。
とはいえ、パスの表だけでは十分ではない。issue #939では、あるユーザーが出力先パスの表を書いたにもかかわらず、モデルはskill内の具体的なパスに従い、その表を読み飛ばした。skill自身のオーバーライドに関する注記は、括弧書きにすぎない。一行の断定的な命令文が、一発目で問題を解決した。specはここに保存しなければならない、そこではない、と、デフォルトの場所を避けるべき対象として明示した文だ。
レイアウトはスクワッドごとに1フォルダ(checkout、search、accounts、design-system)で、それぞれが自分のspecとplanを持つ。monorepoの場合は、その一行をそのパッケージ自身のCLAUDE.mdに書く。Claude Codeはサブディレクトリのファイルを、そこを読みに行ったタイミングでオンデマンドに読み込み、ドキュメントは各ディレクトリのownerが通常そのファイルを保守すると述べている。プラグインを一度も入れていないエンジニアには影響がない。この一行が発火するのは、skillが保存先を尋ねたときだけだ。
そのあとはプラットフォームにownershipを強制させる。スクワッドフォルダごとに1行のCODEOWNERSを置けば、すべてのspecレビューが、それと付き合っていく人たちのもとに届く。1つ注意点がある。このオーバーライドはプロンプトであり、設定ではない。プラグインをリリースするたびに、最初のspecを確認すること。
ルール2: 個人用と共有用
ルール2: すべてのプランがチームのartifactというわけではない。Superpowersに設定可能な保存先を最初に求めた人(issue #337)は、こう言い切った。プランファイルはプロジェクトのartifactというより、個人の作業ドキュメントであってよいはずだ、と。
個人用の側には、そのために作られたファイルがある。CLAUDE.local.mdは共有ファイルの隣に置かれ、その後に読み込まれ、ドキュメントは自分でgit-ignoreするようにと述べている。プライベートなフォルダへのリダイレクトはここに書けば、誰にも見られない。フォルダそのものについては、Gitには共有ツリーに一切触れないignoreファイルがある。.git/info/excludeはクローンごとのものであり、commitされることも、pull requestを必要とすることもない。プラグイン自身も、自分のscratch領域にこれを使っている。version 6.0.3以降、そのworking directoryは自分自身の中に*だけを含む.gitignoreを落とす。追跡対象のファイルに一切触れずに、自己ignoreする仕組みだ。
共有側は、brainstormが既に走らせているレビューゲートを通過したものすべてだ。specを書き、レビューを依頼し、もう一人の承認を得る。承認されればスクワッドのフォルダに移る。承認されなければローカルに留まる。どちらもbranch上で書かれ、mainには書かれない。共有のCLAUDE.mdにある2行がこれを実現する。specとplanを機能の開始点として扱うこと、そしてそれらを書く前にworktreeを作ること。
1つ限界がある。git-ignoreされたプランは移動しない。Okamaのメンテナーは最初にフォルダごとignoreすることを試し、それを撤回した。プランがマシンとエージェントの間で同期しなくなったからだ。個人用は個人用のままだ。
ルール3: ADRのようなライフサイクル
ルール3: specはステータスを持ち、死んだspecはそれを表明する。この考え自体は15年前からある。Michael Nygardは2011年、architecture decision record(ADR)をリポジトリの中に、番号付きの1ファイルずつ保持することを提案した。決定はまずproposedされ、次にacceptedされる。後の記録がそれを変更するとき、古い記録は置き換えの参照とともにdeprecatedまたはsupersededと記される。決定が覆された場合でも、古い記録は残され、supersededと記される。
これをspecに当てはめると、共有されるすべてのspecは、エージェントが既に読んでいる5行のヘッダーから始まる。
| Field | Purpose |
|---|---|
| status | proposed、accepted、superseded |
| superseded-by | 置き換え先のspec |
| owner | そのスクワッド |
| touches | そのspecが主張の対象とするパス |
| review date | 最後に確認した日 |
acceptedになったspecを別の決定に書き換えてはならない。次のspecを書き、それを指し戻す。そうすれば履歴は誠実なままだ。1つ例外がある。Anthropicのplaybookが述べているとおり、実装がplanから外れたときは、同じcommitでplanを更新すること。planはそれを破ったdiffと共に旅をし、これはhookで強制できる。
Spec Kitは、specが生き続ける3つの方法を挙げている。Flow-forward: 機能ごとに新しいフォルダを作り、古いものは履歴として残す。Living spec: specこそが契約であり、それ以外は再生成される。Flow-back: buildがspecを作り直す。彼らのルールは、specがまだ別のことを言っているのに、taskやコードにその変更を残したままにしないことだ。これは意図的にspec-anchoredを選んでいるということだ。
限界はこうだ。statusは人間が設定するmetadataだ。CLAUDE.mdがそう指示しない限り、エージェントは自分のspecをsupersededとマークしない。指示があったとしても、レビューがそれに気づく必要がある。
ルール4: CIにドリフトゲートを
ルール4: ツリーについて嘘をついているspecは、buildを失敗させる。この問題に名前をつけた論文は6月に公開され、silent spec-code drift(静かな仕様コードドリフト)と呼んだ。コードは進化し、仕様書は進化せず、divergenceは修復コストが高くなるまで見えないままになる、というものだ。その答えがdrift gate、mergeをブロックする条件だ。
このチェックは見た目より小さい。ヘッダーがacceptedと書いているすべてのspecについて、backtick内やtouches行に書かれたパスを取り出し、それぞれが存在するかを検証する。パスが見つからなければbuildを失敗させ、ログにspecの名前を残す。supersededとproposedのファイルはスキップされる。ツリーについて主張しているのはacceptedなspecだけだから、チェックされるのもacceptedなspecだけだ。TrueFoundryはこれを、インシデント後の考古学ではなく定期的なdiffとして位置づける。specはscaffoldであり、specの変更はpolicyの変更だ。
すでに人はこれを手動でやっている。あるコメント投稿者は、Claudeにspecファイルとコードベースを比較させ、欠けているものについてticketを立てさせている。このスクリプトは、それをすべてのpull requestで、無料でやるだけだ。
2つ目の防御策は、Okamaが実際に導入したものだ。docsビルドからそのフォルダを除外する。Sphinxの設定に一行加えるだけで、ファイルはGitの中に残りつつ、HTMLには届かなくなる。
限界はこうだ。パスのチェックは削除されたファイルを捕まえるが、変わった振る舞いは捕まえない。specは既存のファイルをすべて正しく名指ししたまま、もう存在しないAPIを説明し続けることもある。それをカバーするのがレビューと同commitルールだ。
ルール5: サイズ予算とそのコスト
ルール5: ほとんどの作業はspecに値しない。spec-drivenなオーバーヘッドについての証拠は一貫している。
| Experiment | Result |
|---|---|
| Marmelab、Spec Kitを現在の日付を表示するだけの機能に適用 | 8ファイル、1,300行のspecificationテキスト |
| OpenSpec bake-off、同じ要件をspecツール対Claude Code単体で実施 | コード50%増、循環的複雑度50%増、時間は2倍、コストは3倍 |
| spec-driven developmentを数ヶ月運用したチーム | トークン2〜3倍、時間はおよそ2倍、不要な変更に対する大きな協調コスト、コードが改善したという証拠なし |
このbake-offは1つの実験であり、ベンチマークではない。著者自身もそう述べている。しかし方向性は揺らがない。だからルールはこうなる。作業がスクワッドの境界を跨ぐとき、あるいは90日後にまた読まれるときにのみspecを書く。それ以外はすべてプロンプトで済ませる。
Superpowersはすでに、すべてのリクエストを3つの経路に振り分けている。spike、bounded、architectural。specを書くのはarchitecturalな経路だけだ。boundedな経路はboundedなままに保ち、specはおよそ300行に近く保つこと。実践者はさらに2行を付け加える。specを大きくしすぎないこと、そして生成されたspecを読むこと。
この限界は両方向に効く。同じbake-offは、素のまま実行したときに欠けていた3つのギャップを見つけている。specが買うのはコードのカバレッジであって速さではない。カバレッジが重要な場所にだけ、その対価を払うべきだ。
結論: commit、そして統治する
リードの3つの問いに戻ろう。何をversion管理するか。承認されたspecとplanを、branch上で、スクワッドのフォルダに置く。ドリフトはどうするか。statusヘッダーとCIのgateで扱う。ガバナンスはどうするか。ownerとサイズのルールで扱う。
5つのルールがあり、そのどれも今のところツールによって強制されてはいない。オーバーライドのissueは開いたままだ。mainブランチのissueも開いたままだ。指示の順序を並べ替えたpull requestはmergeされずにcloseされた。そして痛い目を見たチームは、その実践を続けている。8本のplanと6本のspecをcommitし、docsビルドからは除外して。
Böckelerの異論はそれでも成り立つ。statusヘッダーとサイズ予算があれば、読むのはacceptedなものだけ、しかもその数は少なくなる。手に入るのはAnthropicのplaybookがaudit trailと呼ぶものだ。誰が何を頼み、エージェントが何を作り、誰がそれを承認したか。
これはツールの話ではなく、プロセスの話だ。CLAUDE.mdファイル、CODEOWNERSファイル、ヘッダー、そして15行のスクリプト。そしてチームがCODEOWNERSのレビュー依頼にすら応じないなら、specのレビューにも応じないだろう。その場合は、.gitignoreこそが誠実な選択だった。
AIDive