ブログに戻る

Claude Codeによるスペック駆動開発

Claude Codeによるスペック駆動開発

Claude Codeに「add user authentication」と入力する開発者は、毎回異なる結果を得る。JWTかもしれない。セッションCookieかもしれない。リフレッシュトークンとPKCE付きの完全なOAuth2フローかもしれない。エージェントはあなたが何を求めているか分からない。あなたが伝えたのは方向であって、目的地ではないからだ。

私が見てきた中で、Claude Codeから一貫して出荷可能なアウトプットを得ている開発者には共通の習慣がある。エージェントに作業を渡す前にスペックを書くのだ。小説ではない。3行のコンテキストが付いたJiraチケットでもない。誰もコードを書き始める前に「完了」がどういう状態かを定義する具体的なドキュメントだ。

これは新しい知見ではない。スペック・ファーストの開発はAIの何十年も前から存在する。しかしエージェントにおいては、スペックを省略するコストはより高く、書くことの恩恵はより大きい。人間の開発者は実装の途中で立ち止まって「待って、パスワード認証のこと?それともSSO?」と聞ける。エージェントは黙ってどちらかを選び、進み続ける。気づいた時には間違ったものが出来上がっていて、捨てるべきコードのレビューに20分を費やしている。

この記事では、私がClaude Codeで毎日使っているスペック駆動のライフサイクルを解説する。エージェントが実行できるスペックの書き方、誤解を早期に検出するplan-before-codeチェックポイント、そして「コンパイルが通った」より厳格な検証プロトコルについてだ。

「とりあえず作って」がエージェントで失敗する理由

失敗モードを具体的に見よう。Claude Codeに曖昧な指示を出すと、3つのことがうまくいかない。

サイレントな仮定。 エージェントはスペックのあらゆる隙間を自身の仮定で埋める。その仮定が妥当なこともある。そうでないこともある。アウトプットを読むまでどちらか分からない。曖昧な指示だと、スペックを書くのにかかったであろう以上の注意深さでアウトプットを読むことになる。

再現不可能な結果。 同じ曖昧なプロンプトを2回実行すると、2つの異なる実装が得られる。変数名やフォーマットだけでなく、アーキテクチャの決定が異なる。ライブラリが異なる。エラーハンドリング戦略が異なる。アウトプットを再現できなければ、その周りに信頼できるプロセスは構築できない。

レビューがボトルネックになる。 エージェントがすべての決定を下すと、すべての決定を検証しなければならない。各選択を理解している400行のdiffは5分でレビューできる。エージェントがデータベーススキーマ、APIの形、エラーコード、バリデーションロジックを選んだ400行のdiffは、実装からスペックを逆算するため30分かかる。

解決策はより良いプロンプトではない。重要な決定を、エージェントが実行できるドキュメントに前倒しすることだ。

スペック駆動のライフサイクル

ワークフローには5つのフェーズがある。それぞれに明確なエントリ条件とエグジット条件がある。

フェーズ1:ブレインストーム。 問題空間を探索する。制約は何か?どんなアプローチがあるか?以前何を試したか?ここでは一人で、またはClaude Codeの会話モードで思考を声に出す。エグジット条件:望ましいアプローチがあり、トレードオフを理解している。

フェーズ2:レビュー。 アプローチをストレステストする。何がうまくいかない可能性があるか?どんなエッジケースがあるか?コードベースの既存の何かと衝突しないか?複数のエージェントで作業している場合、ここでアーキテクチャエージェントやセカンドオピニオンが価値を持つ。エグジット条件:アプローチが堅実だと確信している。

フェーズ3:スペック。 決定したことを書き出す。問題の記述、提案するアプローチ、変更するファイル、機械的に検証可能な受け入れ基準、テストプラン。これが契約だ。エグジット条件:誰か(人間またはエージェント)がこのスペックを読んで、何を構築し、どう検証するか正確に分かる。

フェーズ4:実装。 エージェントがスペックに対して実行する。曖昧なアイデアに対してではない。テスト可能な基準を持つ具体的なドキュメントに対してだ。エグジット条件:エージェントが完了を宣言し、検証の証拠を投稿している。

フェーズ5:検証。 あなた(またはQAエージェント)が実装がスペックに一致することを確認する。「正しそうに見えるか」ではなく「各受け入れ基準を満たしているか」だ。エグジット条件:すべての基準がチェックされ、失敗したものはフェーズ4に戻る。

重要なインサイト:フェーズ1-3は安い。中規模の機能で10-20分だ。フェーズ4は実装にかかる時間分。フェーズ5は5-10分。フェーズ1-3を省略しても10-20分の節約にはならない。間違った方向に進んだ作業のレビュー、デバッグ、やり直しの時間がかかる。

良いエージェントスペックの姿

実際のスペックテンプレートを示す。ユーザーストーリーではない。プロダクト要件ドキュメントでもない。エージェントに正確に何を構築するか伝える作業ドキュメントだ。

## Problem
The filter bar resets when switching workspaces. Users lose their
filter state and have to re-apply filters every time they switch.

## Approach
Persist filter state per-workspace in localStorage. Key the stored
state by workspace database path so filters don't bleed across
workspaces.

## Files to Modify
- lib/local-storage.ts: Add getWorkspaceFilters / setWorkspaceFilters
- components/filter-bar.tsx: Read initial state from localStorage,
  write on every change
- hooks/use-workspace.ts: Trigger filter restore on workspace switch

## Acceptance Criteria
1. Select workspace A, set filters to status=open + type=bug
2. Switch to workspace B. Filters reset to defaults.
3. Switch back to workspace A. Filters restore to status=open + type=bug.
4. Close the browser tab, reopen. Filters for the active workspace
   are still applied.
5. bd list --status=open --type=bug output matches the filtered table.

## Out of Scope
- Server-side filter persistence
- Filter presets / saved filter combinations
- URL-based filter state (query params)

## Test Plan
- Unit test: getWorkspaceFilters returns stored value for matching
  workspace path
- Unit test: setWorkspaceFilters writes correct key format
- Manual test: steps 1-5 from acceptance criteria above

このスペックが何を含み、何を含まないかに注目してほしい。localStorageの仕組みは説明しない。Claude Codeは知っている。URL paramsではなくlocalStorageを選んだ理由は説明しない。それはブレインストームフェーズで行われた。エージェントが触るべきすべてのファイルをリストしている。つまりエージェントがこのリスト外のファイルを変更し始めたら、それは警告サインだ。スコープ外セクションがあり、エージェントのゴールドプレーティングを防ぐ。

受け入れ基準が最も重要な部分だ。各項目は観察可能な結果を伴う具体的なアクションだ。「フィルターが永続化されるべき」ではない。それは曖昧だ。「ワークスペースAに切り替え、フィルターがstatus=open + type=bugであることを確認」はテスト可能だ。エージェントが実行できる。QAレビュアーが検証できる。

Plan-before-codeパターン

実装ミスの大半をコードになる前に検出するプロトコルがある。エージェントに何かを書く前に実装計画を投稿させるのだ。

実践では、タスクへの構造化されたコメントとして現れる。エージェントはファイルを開く前に、何をするつもりか正確に書き出す。

PLAN: Persist filter state per-workspace using localStorage.

1. Add workspace-scoped storage helpers to lib/local-storage.ts:
   - getWorkspaceFilters(dbPath: string): FilterState | null
   - setWorkspaceFilters(dbPath: string, filters: FilterState): void
   - Key format: "beadbox:filters:{dbPath}"

2. Update components/filter-bar.tsx:
   - On mount, read stored filters for current workspace
   - On every filter change, write to localStorage
   - Accept databasePath as prop (currently not passed)

3. Update hooks/use-workspace.ts:
   - On workspace switch, emit event that filter-bar listens for
   - Filter-bar reads new workspace's stored filters on event

4. Threading databasePath:
   - filter-bar needs databasePath from the workspace context
   - Will add it to the WorkspaceProvider context value

Files: lib/local-storage.ts, components/filter-bar.tsx,
       hooks/use-workspace.ts, components/workspace-provider.tsx

Test: Unit tests for storage helpers, manual test of all 5
acceptance criteria.

エージェントがこれを生成するのに約30秒。あなたが読むのに約2分。その2分で、実装後に修正するのに20分かかる問題を検出できる:

  • エージェントはスペック外のファイルを触っているか?(workspace-provider.tsxの追加はスペックになかった。OKなのかスコープクリープか?)
  • アプローチは合理的か?(ワークスペース切り替えにイベントエミッターを使うのはオーバーエンジニアリングかもしれない。よりシンプルなprop変更で済むかもしれない。)
  • 欠けているステップはないか?(ワークスペースが削除された時の古いlocalStorageエントリのクリーンアップは?)

プランはチェックポイントだ。正しく見えるならエージェントに続行させる。間違って見えるならプランを修正する。どちらにしても20分ではなく2分の投資だ。

Beadbox はこの問題を解決します。

エージェントフリート全体が今何をしているか、リアルタイムで把握できます。

ベータ期間中は無料でお試し →

検証は「コンパイルが通った」ではない

Claude Codeで最もよく見る失敗モードは、エージェントが悪いコードを書くことではない。アウトプットをスペックに対して検証しなかったことだ。

エージェントが「DONE」と言う。開発者がdiffをざっと見て、妥当に見えるのでマージする。2日後、その機能が受け入れ基準のエッジケース#3を処理していないことが発覚する。5分の検証ステップで検出する代わりに、本番環境でデバッグすることになる。

検証とは各受け入れ基準を機械的にチェックすることだ。「動いているようだ」ではない。「テストが通る」でもない(テストが間違っているか不完全な可能性がある)。スペックの各基準が具体的なチェックを受ける。

適切な完了レポートはこのように見える:

DONE: Filter bar now persists selected filters across workspace
switches using per-workspace localStorage.

Changes:
- lib/local-storage.ts: Added getWorkspaceFilters/setWorkspaceFilters
  with key format "beadbox:filters:{dbPath}"
- components/filter-bar.tsx: Reads stored filters on mount, writes
  on change. Accepts databasePath prop.
- hooks/use-workspace.ts: Triggers filter restore on workspace switch
  via callback prop.

QA Verification:
1. Open http://localhost:41420, select workspace A
2. Set filters to status=open, type=bug
3. Switch to workspace B via header dropdown
4. Switch back to workspace A
5. Verify filters are still status=open, type=bug
   -> Confirmed: filters restore correctly
6. Close tab, reopen. Filters persist.
   -> Confirmed: localStorage key present, filters applied on mount
7. Run: bd list --status=open --type=bug
   -> Output matches filtered table contents (14 beads)

Acceptance criteria:
- [x] Filters persist across workspace switches (steps 2-5)
- [x] Filters survive browser restart (step 6)
- [x] Filtered view matches bd CLI output (step 7)
- [x] Filters don't bleed between workspaces (step 3: workspace B
      shows defaults)

Unit tests: 3 added (storage read/write/key format). All passing.

Commit: a1b2c3d

これと「DONE: Fixed the filter bar」の違いは、5分のQAパスと30分の調査の違いだ。DONEコメントの各主張は特定のチェックに裏付けられている。各受け入れ基準は検証ステップにマッピングされている。レビュアーは何が構築されたか、どう検証されたか、おかしい点があればどこを見ればよいか正確に分かる。

スペックコンテナとしてのBeads

今説明したライフサイクルには存在する場所が必要だ。スペック、プランコメント、実装、完了レポート、検証結果。すべてが一つのタスクに、一つの場所に。

この問題を解決するのがbeadsだ。Beadsはまさにこのワークフローのために設計されたオープンソースのローカルファーストイシュートラッカーだ。各「bead」は、説明(スペック)、コメントスレッド(プランと完了レポート)、ステータス(open、in_progress、ready_for_qa、closed)、そして優先度、依存関係、割り当てなどのメタデータを持つタスクだ。

bd CLIを使った実践でのスペック駆動ライフサイクルを示す:

スペック付きでbeadを作成:

bd create --title "Persist filter state across workspace switches" \
  --description "## Problem
The filter bar resets when switching workspaces...

## Acceptance Criteria
1. Select workspace A, set filters...
2. Switch to workspace B..." \
  --type feature --priority p2

エージェントが作業を引き受けてプランを投稿:

bd update bb-a1b2 --claim --actor eng1
bd comments add bb-a1b2 --author eng1 "PLAN: Persist filter state
per-workspace using localStorage.

1. Add workspace-scoped storage helpers...
2. Update filter-bar component...
3. ..."

エージェントが作業を完了して完了レポートを投稿:

bd comments add bb-a1b2 --author eng1 "DONE: Filter bar now persists
selected filters across workspace switches.

QA Verification:
1. Open http://localhost:41420...

Acceptance criteria:
- [x] Filters persist across workspace switches
- [x] Filters survive browser restart
...

Commit: a1b2c3d"

bd update bb-a1b2 --status ready_for_qa

QAが引き継いで検証:

bd show bb-a1b2  # スペックとDONEコメントを読む
# 検証ステップを実行
bd comments add bb-a1b2 --author qa1 "QA PASS: All 5 acceptance
criteria verified. Filters persist, restore, and match bd CLI output."

ライフサイクル全体がbeadの中にある。スペックは説明だ。プランはコメントだ。完了レポートはコメントだ。QA結果はコメントだ。6ヶ月後、誰かが「フィルター永続化はどう動いていて、なぜURL paramsではなくlocalStorageを選んだのか?」と聞いた時、答えはbeadのコメントスレッドにある。

一つのスペックをこのパイプラインに通す時は、ターミナルとbd showで十分だ。しかしこのワークフローの真価は、複数のスペックを並行して走らせている時に現れる。

スペック駆動開発のスケーリング

実際のシナリオを想像してほしい:3つのClaude Codeエージェントがあり、それぞれ異なるスペックを実装している。エージェントAはフィルター永続化機能を構築中。エージェントBはワークスペース統計用の新しいAPIエンドポイントを追加中。エージェントCはWebSocket再接続バグを修正中。各エージェントはスペック駆動ライフサイクルのどこかにいる。

ターミナルでは、bd listを実行してすべてのアクティブなbeadを確認し、それぞれにbd showでステータスと最新コメントを確認する必要がある。3つの並行ワークストリームのスナップショットを取るのに6つのコマンド。これを5つか10のエージェントに掛けると、プランのレビューよりステータス確認に多くの時間を費やすことになる。

ここでBeadboxが活きてくる。Beadboxはワークスペース内のすべてのbeadの状態をリアルタイムで表示するダッシュボードだ。どのスペックがオープンでエージェントを待っているか。どれにレビューが必要なプランが投稿されているか。どれが進行中か。どれがQA検証の準備ができているか。エージェントがbd CLIを通じてコメントを書きステータスを変更するたびにライブで更新される。

スペック駆動開発にBeadboxは必須ではない。CLIがライフサイクル全体を処理する。しかし複数のスペック駆動ワークフローを並行して走らせている時、各エージェントのステータスを個別にポーリングする代わりにパイプラインを一目で見られることが、プランのレビュー、エージェントのアンブロック、停滞した作業の検出の速度を変える。

Beadboxはベータ期間中は無料で、その上で動くbeads CLIはオープンソースだ。

ツールに関わらず変わらないこと

beads、GitHub Issues、Linear、プレーンテキストファイルのどれを使おうと、スペック駆動パターンが機能するのは、エージェントの動作における根本的な非対称性に対処しているからだ:実行は速いが判断は苦手。明確なスペックを書くのに費やす1分は、誤ったアウトプットのレビュー、サイレントな仮定のデバッグ、方向を誤った作業のやり直しの複数分を節約する。

原則:

  1. 「完了」を「開始」の前に定義する。 受け入れ基準はオプションではない。検証を可能にする唯一のものだ。

  2. プランはチェックポイントであり、官僚主義ではない。 30秒のプランコメントが20分のリライトを節約する。プランをレビューせよ、コードではなく。

  3. 検証はプロトコルであり、感覚ではない。 「良さそうに見える」は検証ではない。各受け入れ基準を具体的なチェックにマッピングすることが検証だ。

  4. スペックが唯一の真実の源だ。 実装とスペックが食い違った時、実装が間違っている。このルールが存在するのは、エージェントは悪いプランに疑問を呈さないからだ。忠実に実行し、忠実に間違ったアウトプットを生成する。

  5. スコープの境界がドリフトを防ぐ。 変更するファイルの明示的なリストとスコープ外セクションが、エージェントが頼まれていないものを「改善」することを防ぐ。

投資は小さい:実装に1時間かかる機能のスペックを書くのに10-20分。リターンは大きい:一貫した結果、レビュー可能なアウトプット、何が構築されなぜかの永続的な記録。

このようなワークフローを構築しているなら、GitHubでBeadboxにスターを。

Like what you read?

Beadbox is a real-time dashboard for AI agent coordination. Free during the beta.

Share