3
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude Code で辿り着いた仕様駆動開発 — 仕様を「書く」のではなく、対話とコードから生成する

3
Posted at

この記事で言いたいこと(結論から)

Claude Code でアプリを開発していく中で、こういう形に辿り着きました。

仕様(spec)は「手で書いて維持するもの」ではなく、「対話とコードから生成するもの」にする。
設計仕様は Claude との対話から(/planplan.md)、実態の仕様は コードから(/flow-doc / /steering-docflow.md / steering)生成し、その生成をスキル + サブエージェント + GitHub Actions で回す。

「仕様駆動開発」というと、最初に仕様書を書いて、それを守りながら実装して、変更のたびに手で仕様書を直す……というイメージがあります。でも、手で維持する仕様は必ず腐ります。そこで発想を変えて、仕様そのものを生成物として扱うことにしました。仕様は2つの源から生まれます。

  • 対話から生まれる仕様/plan が Claude との対話(AskUserQuestion)で「何を・なぜ・どう作るか」を詰め切り、plan.md として確定させる(=これから作るものの仕様)
  • コードから生まれる仕様/flow-doc / /steering-doc が実コードを解析し、flow.md / steering を生成する(=今あるものの仕様)

これを 3 つのフェーズ(スキル)に分け、責務を切り分けています。

  1. /plan — 対話から設計仕様(plan.md)を生成する(コードは書かない
  2. /implplan.md(仕様)を正本に実装し、仕様を満たすまで検証ゲートでループ修正する。繰り返した失敗は agent-memory に記録し、次から同じミスを踏ませない
  3. /done — 締めの後片付け(不要物削除 + コードから flow.md / steering を生成)を一括で回す

そして最後の /done を GitHub Actions に載せて、人間が実装を main にマージするたびに自動実行しています。すると コードから生成し直された仕様(ドキュメント)の PR が勝手に上がってくる、という状態です(/done 自身が作った PR のマージでは再発火しません。後述)。

この記事は私個人の「完成形の紹介」であって、ベストプラクティスの主張ではありません。同じように AI エージェントで開発フローを組みたい人の叩き台になれば、という温度感で書いています。


なぜ「仕様を生成する」のか

AI エージェントに実装を任せていると、だいたい次の 2 つで詰まります。

  • 設計と実装が混ざる:「作りながら考える」と、途中で方針が二転三転して、何を作っているのか分からなくなる
  • ドキュメントが腐る:実装が進むたびに設計メモや処理フローの記述が実態とズレて、誰も信じなくなる

どちらも根っこは同じで、「仕様を人間が手で書いて、手で最新化し続ける」から破綻します。そこで、「決める場所」と「作る場所」と「記録する場所」を物理的に分離し、それぞれの仕様を別々の源から生成することにしました。これが出発点です。

関心事 正本(Single Source of Truth) 何から生成するか
何を・なぜ・どう作るか(設計) docs/screens/<screen>/plan.md /planClaude との対話から生成。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 で委譲し、検証ゲートで守る

/implplan.md を正本に、実装を実装エージェント(アーキテクチャ規約のスキルに従うサブエージェント)へ TDD(Red → Green → Refactor) で委譲します。ここでもメインは自分でコードを書きません。

肝は 1 イテレーション = 実装 → 3 段の検証ゲートという構造です。

  • ゲート①:検証コマンド — 私の場合は bun run check(lint + typecheck + format:check + test)をオーケストレーター自身が独立実行する(実装エージェントの自己申告を鵜呑みにしない)。要は「各自のスタックの lint / 型 / テストをまとめた 1 コマンド」があればよい
  • ゲート②:要件照合 + 品質レビューを並列
    • plan-checkerplan.md の各項目と実コード / git diff を 1 項目ずつ照合(○ / × / partial)。未実装・スコープ外(過剰実装)も検出
    • code-reviewer複数観点で並列レビュー(私の場合は「アーキテクチャ規約 / パフォーマンス / 実装作法」の3観点)。観点はプロジェクトのスタックに合わせて差し替える
  • ゲート③:モックとの一致確認 — 合意済みのモック(prototype.html)と実装コードを突き合わせ、画面の中身(要素・配置・見た目)がモックどおりに実装されているかをチェックする。/plan の段階でモックを合意しているので(フェーズ1)、「テストは通るがデザインがモックとズレている」を最後に潰せる。UI を持たないプロジェクトなら、このゲートは省いてよい

plan-checkercode-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-docdocs/steering/ を現在のコードに追記同期
  • /done:上記の同期 + 不要物削除を一括で回す

/flow-doc/steering-doc「コードが正」の追記同期で、人間が手で書いた背景・意図は保持します。設計の正本(plan.md)は締めフェーズでは書き換えません。plan.md は「これから」、flow.md / steering は「今」、と役割がきっぱり分かれています。

/done の中身

/done は締めを一括で回すオーケストレーターで、3 フェーズ構成です。

  1. Phase 1(順次・ゲート付き):不要コード / ファイルを読み取り専用エージェント(Explore)で検出 → 自動削除 → bun run check で破壊がないことを検証。赤なら削除した追跡ソースだけを対象明示で復元
  2. Phase 2(並列)flow-doc / steering-doc / agent-memory 掃除 の 3 タスクをサブエージェントで同時起動(書き込み先が非干渉なので安全に並列化できる)
  3. Phase 3:削除一覧と 3 タスクの結果を 1 本のレポートに集約

保護リスト(docs/spec.mddocs/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 つの分離でした。

  1. 決める / 作る / 記録するを分ける — plan(設計)・impl(実装)・done(記録同期)を別スキルに切り出す
  2. 正本を二層で持つ — plan.md は「これから」、flow.md / steering は「今(コードが正)」
  3. オーケストレーターは書かない — 実装は実装エージェント、検証は読み取り専用の plan-checker / code-reviewer に委譲し、メインは指示と検証だけ
  4. ドキュメントを「書く」から「コードから生成する」へ — 締め(flow-doc / steering-doc)を CI に載せ、マージされたコードから要件・フローを自動生成して PR で提示する。手で最新化しないので、ズレが人知れず溜まらない(差分は必ず PR として可視化され、マージすれば追従する)

「AI にコードを書かせる」だけなら誰でもできますが、設計とドキュメントをどう腐らせないかが本題でした。plan.md を「これから」の正本に据えつつ、flow.md / steering は「コードから生成する写し」として CI に任せる。人間が最新化を頑張るのではなく、乖離が起きない仕組みに寄せる。この形が、今の私の答えです。


おわりに

このワークフローの具体的な道具立て(実装エージェント・検証コマンド・レビュー観点・E2E)は、私のスタックに合わせた実装にすぎません。そこは各自のスタックの相当物に読み替えてください。

一方で、骨格 —— 「仕様を対話とコードから生成する」「plan を正本に実装し、締めを CI で自動化する」 —— は、Web・モバイル・バックエンドを問わず、AI エージェントを使うアプリ開発全般で汎用的に効くはずです。フロントに限った話ではありません。

同じ悩みを持つ人の参考になれば嬉しいです。

3
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?