この記事で言いたいこと(結論から)
Claude Code でアプリを開発していく中で、こういう形に辿り着きました。
仕様(spec)は「手で書いて維持するもの」ではなく、「対話とコードから生成するもの」にする。
設計仕様は Claude との対話から(/plan→plan.md)、実態の仕様は コードから(/flow-doc//steering-doc→flow.md/steering)生成し、その生成をスキル + サブエージェント + GitHub Actions で回す。
「仕様駆動開発」というと、最初に仕様書を書いて、それを守りながら実装して、変更のたびに手で仕様書を直す……というイメージがあります。でも、手で維持する仕様は必ず腐ります。そこで発想を変えて、仕様そのものを生成物として扱うことにしました。仕様は2つの源から生まれます。
-
対話から生まれる仕様:
/planが Claude との対話(AskUserQuestion)で「何を・なぜ・どう作るか」を詰め切り、plan.mdとして確定させる(=これから作るものの仕様) -
コードから生まれる仕様:
/flow-doc//steering-docが実コードを解析し、flow.md/steeringを生成する(=今あるものの仕様)
これを 3 つのフェーズ(スキル)に分け、責務を切り分けています。
-
/plan— 対話から設計仕様(plan.md)を生成する(コードは書かない) -
/impl—plan.md(仕様)を正本に実装し、仕様を満たすまで検証ゲートでループ修正する。繰り返した失敗はagent-memoryに記録し、次から同じミスを踏ませない -
/done— 締めの後片付け(不要物削除 + コードから flow.md / steering を生成)を一括で回す
そして最後の /done を GitHub Actions に載せて、人間が実装を main にマージするたびに自動実行しています。すると コードから生成し直された仕様(ドキュメント)の PR が勝手に上がってくる、という状態です(/done 自身が作った PR のマージでは再発火しません。後述)。
この記事は私個人の「完成形の紹介」であって、ベストプラクティスの主張ではありません。同じように AI エージェントで開発フローを組みたい人の叩き台になれば、という温度感で書いています。
なぜ「仕様を生成する」のか
AI エージェントに実装を任せていると、だいたい次の 2 つで詰まります。
- 設計と実装が混ざる:「作りながら考える」と、途中で方針が二転三転して、何を作っているのか分からなくなる
- ドキュメントが腐る:実装が進むたびに設計メモや処理フローの記述が実態とズレて、誰も信じなくなる
どちらも根っこは同じで、「仕様を人間が手で書いて、手で最新化し続ける」から破綻します。そこで、「決める場所」と「作る場所」と「記録する場所」を物理的に分離し、それぞれの仕様を別々の源から生成することにしました。これが出発点です。
| 関心事 | 正本(Single Source of Truth) | 何から生成するか |
|---|---|---|
| 何を・なぜ・どう作るか(設計) | docs/screens/<screen>/plan.md |
/plan(Claude との対話から生成。flow.mdの中からどこをどう変更するかを明確化する) |
| 実際の処理フロー(実態) | docs/screens/<screen>/flow.md |
/flow-doc(コードから生成) |
| プロジェクト全体の地図 | docs/steering/{product,tech,structure}.md |
/steering-doc(コードから生成) |
ポイントは plan.md は「これから作る設計」の正本であり、flow.md / steering は「今あるコード」の写しだということ。前者は人間が承認して確定させ、後者はコードから機械的に追従させます。二層に分けたことで、設計の意図とコードの実態が別々に管理できるようになりました。
全体像
📌 スタックについて
ここから先に出てくる実装エージェント(architecture-implementer)・検証コマンド(bun run check)・レビュー観点・E2E などは、私のスタックでの具体化にすぎません。骨格 —「対話とコードから仕様を生成する」「締めを CI で自動化する」— はスタックに依存しません。 Web でもモバイルでもバックエンドでも、各自の実装エージェント・テスト・lint に読み替えれば同じように組めます。以降、具体名が出てきたら「自分のスタックの相当物」に置き換えて読んでください。
主役は 6 つのスキル(上の 3 フェーズ + 締めの /review・/flow-doc・/steering-doc)と 3 つのサブエージェントです。
- スキル:
/plan・/impl・/review・/flow-doc・/steering-doc・/done - サブエージェント:実装エージェント(実装)・
plan-checker(要件照合・読み取り専用)・code-reviewer(品質レビュー・読み取り専用)- ※ ここでいう 実装エージェント(この記事では
architecture-implementerと呼びます)とは、アーキテクチャ規約のスキルを読み込み、それに従ってコードを書くサブエージェントのことです。従わせる規約は各自のプロジェクトのもの。
- ※ ここでいう 実装エージェント(この記事では
「オーケストレーター(メインの会話)はコードを書かない」というのが全体の鉄則です。実装は必ず実装エージェントに委譲し、要件・品質のレビューは読み取り専用のエージェント(plan-checker / code-reviewer)に任せる。オーケストレーター自身が手を動かすのは、bun run check のような検証コマンドの実行と、各エージェントへの指示出しだけです。メインのコンテキストを綺麗に保つための役割分担です。
フェーズ 1:/plan — 決め切ってから作る
/plan は コードを一切書きません。やることは「何を・なぜ・どう作るか」を対話で詰め切って、plan.md に確定させることだけです。
-
段階ゲート:
AskUserQuestionで 何を(スコープ)→ なぜ(目的)→ どう(設計方針 2〜3 案 + 推奨) を一問ずつ確定させる。各段が決まるまで次に進まない -
実装場所の特定:実装済み画面は
flow.mdの責務マップから、未実装画面はdocs/spec.md/ モックから「触るファイル : シンボル」を洗い出す。コードや spec に裏付けのない実装場所は創作しない(不明は「未決事項」に隔離) -
モックの合意:UI 変更は先にモック(
prototype.html)を直してブラウザで見せ、合意してからplan.mdを書く(UIのモックはsuperpowers を読んでUIのモックを作っています) -
バリデーション・異常系パターンの洗い出し:入力の制約(必須 / 任意・形式・文字数・境界値)、エラーメッセージの文言、失敗時の分岐(不正な形式・通信エラー・権限エラーなど)を
plan.mdに列挙しておく。別プロジェクトでは、たとえばサインアップ画面の仕様に「メールは/^[^\s@]+@[^\s@]+\.[^\s@]+$/形式」「パスワードは8文字以上・英大小 + 数字」「不一致なら『パスワードが一致しません』を表示」といったレベルまで書き出していた。ここを詰めておくと、/implの TDD がそのままテストケースになり、ゲート②(plan-checker)の照合項目にもなる。逆に書き漏らすと「正常系しか動かない実装」になりがち
出力は 1 画面(=機能単位)= 1 つの plan.md。バージョン付きファイルを増やさないのがルールです。単位は「画面」でも「機能」でも「モジュール」でも、プロジェクトの区切りに合わせて構いません。ここで承認を取った plan.md が、次の /impl の「正本」になります。
迷ったら plan に戻る。設計が固まっていないまま実装に進むのが一番の事故なので、
/implは plan.md が無ければ動かない設計にしています。
フェーズ 2:/impl — TDD で委譲し、検証ゲートで守る
/impl は plan.md を正本に、実装を実装エージェント(アーキテクチャ規約のスキルに従うサブエージェント)へ TDD(Red → Green → Refactor) で委譲します。ここでもメインは自分でコードを書きません。
肝は 1 イテレーション = 実装 → 3 段の検証ゲートという構造です。
-
ゲート①:検証コマンド — 私の場合は
bun run check(lint + typecheck + format:check + test)をオーケストレーター自身が独立実行する(実装エージェントの自己申告を鵜呑みにしない)。要は「各自のスタックの lint / 型 / テストをまとめた 1 コマンド」があればよい -
ゲート②:要件照合 + 品質レビューを並列
-
plan-checker:plan.mdの各項目と実コード /git diffを 1 項目ずつ照合(○ / × / partial)。未実装・スコープ外(過剰実装)も検出 -
code-reviewer:複数観点で並列レビュー(私の場合は「アーキテクチャ規約 / パフォーマンス / 実装作法」の3観点)。観点はプロジェクトのスタックに合わせて差し替える
-
-
ゲート③:モックとの一致確認 — 合意済みのモック(
prototype.html)と実装コードを突き合わせ、画面の中身(要素・配置・見た目)がモックどおりに実装されているかをチェックする。/planの段階でモックを合意しているので(フェーズ1)、「テストは通るがデザインがモックとズレている」を最後に潰せる。UI を持たないプロジェクトなら、このゲートは省いてよい
plan-checker と code-reviewer を分けているのがポイントで、「要件を満たしているか」と「コードとして良いか」は別問題として両方チェックします。CRITICAL / HIGH の指摘・要件未充足・モック不一致があれば、まとめて実装エージェントに修正依頼して再走(最大 10 イテレーション)。全部 pass して初めて IMPL COMPLETE です。
要は、「実装 → 仕様と照合 → ズレていたら直す」を、仕様(plan.md)を満たすまでループさせているわけです。一発で当てにいくのではなく、plan.md という動かない基準に対して収束させる。人間は基準(仕様)を用意するだけで、そこに寄せる作業はエージェントとゲートが回してくれます。
失敗を「記録」して二度踏ませない — agent-memory
ここでもう一段効かせているのが、同じミスを繰り返させない仕組みです。イテレーションの中で実装エージェントが何度も同じ間違い(例:テストのモック手法、非同期レンダリングの扱い、循環 import)を踏んだら、その教訓を プロジェクト内の .claude/agent-memory/<エージェント>/ に feedback メモとして書き出します。
.claude/agent-memory/
├── architecture-implementer/ # 実装エージェントが踏んだ失敗の学習(feedback_*.md)
├── plan-checker/ # 要件照合で得た知見
└── code-reviewer/ # レビューで繰り返し指摘したパターン
各エージェントは次に起動されるとき、この agent-memory を読んでから作業に入ります。つまり 「同じ失敗を 2 回はしても、3 回目からはしない」 ように、エージェント自身が学習していく。セッションをまたいで蓄積されるので、プロジェクトが育つほどイテレーション回数が減っていきます(この agent-memory の掃除も、後述の /done が自動で面倒を見ます)。
フェーズ 3:締め — コードが正のドキュメントを追従させる
実装がコードに入った後、「コード → ドキュメント」の同期を回します。ここが仕様駆動の後半の肝です。
-
/review:変更差分を 3 観点で並列レビュー(読み取り専用・単発の品質チェック) -
/flow-doc:コード(私の場合はsrc/pages/*の UI / ロジック)を解析してflow.mdをコード基準で追記同期 -
/steering-doc:docs/steering/を現在のコードに追記同期 -
/done:上記の同期 + 不要物削除を一括で回す
/flow-doc と /steering-doc は 「コードが正」の追記同期で、人間が手で書いた背景・意図は保持します。設計の正本(plan.md)は締めフェーズでは書き換えません。plan.md は「これから」、flow.md / steering は「今」、と役割がきっぱり分かれています。
/done の中身
/done は締めを一括で回すオーケストレーターで、3 フェーズ構成です。
-
Phase 1(順次・ゲート付き):不要コード / ファイルを読み取り専用エージェント(
Explore)で検出 → 自動削除 →bun run checkで破壊がないことを検証。赤なら削除した追跡ソースだけを対象明示で復元 -
Phase 2(並列):
flow-doc/steering-doc/agent-memory 掃除の 3 タスクをサブエージェントで同時起動(書き込み先が非干渉なので安全に並列化できる) - Phase 3:削除一覧と 3 タスクの結果を 1 本のレポートに集約
保護リスト(docs/spec.md・docs/steering/**・設定ファイル等)は絶対に削除しない、git checkout HEAD -- .(全差分破棄)は使わない、といったガードを効かせています。
そして GitHub Actions へ — 締めを自動化する
ここが今回一番やりたかったことです。/done を GitHub Actions に載せて、人間が実装を main にマージするたびに自律実行しています(/done 自身が作った PR のマージだけは、無限ループ防止のため除外します。後述)。
この /done は、フェーズ3で紹介した締めスキルそのものです。手元で叩いてもいいのですが、CI に載せることで マージのたびに自動で締めが回り、普段は人間が /done を手で実行する必要がなくなります(=上の全体図で「手動で締める場合」とした /done を、マージ後の CI に肩代わりさせている)。
仕組みはシンプルで、done-on-merge.yaml が main への push をトリガーに Claude Code をヘッドレスで叩きます。
# .github/workflows/done-on-merge.yaml(抜粋)
on:
push:
branches: [main]
workflow_dispatch:
jobs:
done:
steps:
# ...(Bun / Claude Code のセットアップ)
- name: Run /done skill with Claude
env:
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
run: |
# プロンプトで「/done スキルを自律実行して」と明示的に指示する
cat > /tmp/prompt.txt << 'EOF'
.claude/skills/done/SKILL.md の /done スキルを最初から最後まで自律実行してください。
- AskUserQuestion は使わず、すべて自律的に判断する
- コミット・push はしない(後続の CI ステップが担う)
EOF
# -p(--print)でそのプロンプトを渡し、ヘッドレスで実行
claude --dangerously-skip-permissions -p "$(cat /tmp/prompt.txt)"
# 生成された差分を auto-pr/done ブランチにコミットして PR を作る
claude -p "..."(--print モード)は、渡したプロンプトを 1 回だけ実行してそのまま終了するヘッドレス実行です。ここに 「/done スキルを自律実行して」というプロンプトを流し込むことで、ローカルで /done と打つのと同じ処理を CI 上で走らせています。プロンプト本文には「AskUserQuestion は使わず全て自律判断」「コミット・push はスキル内ではしない(後続ステップが担う)」といった CI 固有の制約も添えています。CLI での実行結果はこうなります。
- 人間が機能実装を main にマージする
- →
/doneが自律実行され、不要物削除・flow-doc/steering-doc同期・agent-memory掃除を実施 - → 差分があれば
auto-pr/doneブランチに PR を自動起票(差分が無ければ何もしない / 古い PR を自動 close)
無限ループ防止として、自動生成した auto-pr/done のマージでは再発火しない条件を入れてあります。
if: |
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'push' &&
!(startsWith(github.event.head_commit.message, 'Merge pull request') &&
contains(github.event.head_commit.message, 'auto-pr/done')))
⚠️ もう一つ気をつけたいのがコンフリクトです。
実装がマージされるたびに/doneを走らせるので、自動生成の PR が古い main の上に積まれたままだと、後続のマージとすぐにコンフリクトします。なので PR を差分として積み増すのではなく、毎回「最新の main から作り直して上書きする」 発想にしています。具体的には自動 PR 用ブランチを毎回 最新 main から再作成し、force で上書きする(git checkout -B auto-pr/done origin/main+--force-with-lease)。こうすると自動 PR は常に「今の main に対する最新のドキュメント差分」だけを持ち、コンフリクトしません。
結果として、**「人間は実装に集中し、ドキュメント同期は CI が勝手に PR にしてくる」**という分業が成立しました。
ここで効いているのが、**flow-doc / steering-doc は「コードから要件・処理フローを生成する」**という性質です。人間が書いた設計メモを更新するのではなく、マージされた実コードを解析して flow.md / steering を再生成する。つまり CI が回るたびに、ドキュメントは常に「今のコード」から作り直されます。
- 実装を変える → コードが真実になる
- main にマージ → CI がそのコードから要件・フローを生成し直す
- → その差分が PR として毎回提示される(マージすればドキュメントがコードに追従する)
ここで正確に言うと、CI は main のドキュメントを直接上書きするわけではなく、「今のコードから生成し直したドキュメント」を PR として出してくるだけです。なので PR をマージするまでの間はコードとドキュメントがズレ得ます。ただし重要なのは、そのズレが必ず PR として可視化され、放置しても PR に溜まるだけで「気づけないズレ」にはならないこと。しかも古い PR は次の CI で最新 main から作り直されるので、常に「今のコードとの差分」だけが提示されます。
「ドキュメントを手で最新化する」から「コードを正としてドキュメントを自動生成し、差分を PR で提示する」へ切り替えたことで、ズレが人知れず溜まっていく、という一番ありがちな事故が起きなくなるわけです(あとは PR をマージするだけ)。
なお品質ゲートは別ワークフロー ci.yaml が担当していて、PR に対して lint / typecheck / format / test を回し、アプリ本体に関わる変更があるときだけ E2E smoke を走らせています(私の場合はモバイルなので Android エミュレータ実行。重い E2E はパスフィルタで絞る構成)。ここも各自のスタックの E2E に読み替えれば同じです。
辿り着いた原則
振り返ると、効いているのは次の 4 つの分離でした。
- 決める / 作る / 記録するを分ける — plan(設計)・impl(実装)・done(記録同期)を別スキルに切り出す
- 正本を二層で持つ — plan.md は「これから」、flow.md / steering は「今(コードが正)」
-
オーケストレーターは書かない — 実装は実装エージェント、検証は読み取り専用の
plan-checker/code-reviewerに委譲し、メインは指示と検証だけ - ドキュメントを「書く」から「コードから生成する」へ — 締め(flow-doc / steering-doc)を CI に載せ、マージされたコードから要件・フローを自動生成して PR で提示する。手で最新化しないので、ズレが人知れず溜まらない(差分は必ず PR として可視化され、マージすれば追従する)
「AI にコードを書かせる」だけなら誰でもできますが、設計とドキュメントをどう腐らせないかが本題でした。plan.md を「これから」の正本に据えつつ、flow.md / steering は「コードから生成する写し」として CI に任せる。人間が最新化を頑張るのではなく、乖離が起きない仕組みに寄せる。この形が、今の私の答えです。
おわりに
このワークフローの具体的な道具立て(実装エージェント・検証コマンド・レビュー観点・E2E)は、私のスタックに合わせた実装にすぎません。そこは各自のスタックの相当物に読み替えてください。
一方で、骨格 —— 「仕様を対話とコードから生成する」「plan を正本に実装し、締めを CI で自動化する」 —— は、Web・モバイル・バックエンドを問わず、AI エージェントを使うアプリ開発全般で汎用的に効くはずです。フロントに限った話ではありません。
同じ悩みを持つ人の参考になれば嬉しいです。