仕様駆動開発(SDD):バイブコーディングをやめてAI生成コードを本番に送り出す方法
デモは皆さん見たことがあるはずです。エンジニアがAIコーディングエージェントに「SaaSダッシュボード作って」と入力すると、3分で認証、データベース、決済フロー付きの完全なReactアプリが出来上がる。拍手が起き、ツイートがバズる。
ところが実際のプロジェクトで試してみると話が違うんですよね。200ファイル、カスタム認証レイヤー、外部API連携3つ、モノレポ構造のプロジェクトで、エージェントは自信満々にDBスキーマを書き換え、重要なミドルウェアを削除し、Ctrl+Cを押す前にセキュリティ脆弱性を4つ導入してくれる。
これがバイブコーディングの罠なんです。「デモで動く」と「本番で動く」の間にある距離は、ツールの問題じゃない。方法論の問題なんです。そしてそのギャップを埋める方法論には名前があります。**仕様駆動開発(Specification-Driven Development、SDD)**です。
SDDはドキュメントを増やす話じゃありません。AIエージェントが確実に動くための制約を正確に与える仕組みなんです。specファイル、テストファーストループ、そして「生成して祈る」を「仕様化 → 設計 → 分割 → 実装」に置き換える4フェーズワークフローです。SDDを導入したチームからは、AI起因のリグレッションが60-80%減った、自分が書いていないコードのデバッグ時間が劇的に減ったという報告が一貫して出ています。
このガイドでは方法論の全体をカバーします。バイブコーディングがスケールで失敗する理由、4フェーズSDDループ、specファイルの構造化方法(CLAUDE.md、cursor rules、AGENTS.md)、AIエージェントとTDDの統合、そして実際のコードベースでエージェントをオーケストレーションするプロダクションパターンまで。
バイブコーディングが崩壊する理由
何が、なぜ失敗するのか正確に見ていきましょう。バイブコーディングとは、AIに自然言語でプロンプトを与えて出力をそのままデプロイする手法です。グリーンフィールドのプロトタイプでは驚くほどうまくいく。でもプロダクションコードベースでは、4つの具体的な理由で体系的に失敗します。
1. コンテキスト崩壊問題
AIコーディングエージェントはステートレスです。新しいセッションはすべてゼロから始まる。200ファイルのプロジェクトには暗黙知がいたるところに埋め込まれています。命名規約、エラーハンドリングパターン、認証レイヤーのuserIdがUUIDなのにレガシーAPIのuser_idはシーケンシャル整数であるという事実。明示的に提供しない限り、エージェントのコンテキストにはこれらが存在しません。
バイブコーディングにはこれを解決するメカニズムがないんですよね。プロンプトを入力すると、エージェントは学習データに基づいてコードを生成するんですが、出力はプロジェクトのパターンじゃなくてエージェントのデフォルトパターンになる。結果、単独では「動く」けど微妙な不整合が蓄積してアーキテクチャドリフトを引き起こすコードが生まれます。
コンテキスト崩壊の実例:
プロジェクトの規約: エージェントが生成するコード:
────────────── ──────────────────────────
camelCase統一 新規ファイルにsnake_case
Zodでバリデーション 手動ifチェック
カスタムエラークラス throw new Error()直接使用
Repositoryパターン DB直接呼び出し
UUID主キー Auto-increment整数
2. ハッピーパスバイアス
LLMは主にチュートリアルコード、ドキュメントの例、Stack Overflowの回答で学習しています。この学習データは圧倒的に「うまくいく場合」だけを示している。結果として、メインフローはきれいに処理するけれど境界条件で崩壊するコードが生まれます。
- ネットワークタイムアウト? 未処理。
- 同じリソースへの並行リクエスト? レースコンディション。
- DBコネクションプール枯渇? クラッシュ。
- 不正なユーザー入力? バリデーションなし。
- 外部APIのレート制限? 無視。
2025年のEndor Labsの研究によると、AI生成コードの62%がセキュリティ上の弱点または設計上の欠陥を含んでおり、AIが提案した依存関係の44-49%が既知の脆弱性を抱えていました。Verizon DBIR 2025レポートでも、サードパーティ関連の侵害が前年比倍増の30%に達したと報告されており、検証されていないAI生成コードがソフトウェアサプライチェーンに流入するリスクの拡大が示されています。
3. オーナーシップの空白
バイブコーディングは、オーナー本人が中身を理解できていないコードを量産します。デプロイはしたけど、制御フローも、エラーハンドリング戦略も、なぜそのライブラリを選んだかも説明できない。深夜2時に本番バグが発生したら、エラー状態の伝播について全く異なる前提で書かれた、いわばエイリアンが書いたコードと格闘することになる。
これ、理論的な懸念じゃないんです。実際、2026年のエンジニアリングマネージャーが最も多く挙げるAI支援開発の不満がまさにこれ。コードは動かなくなるまで動く、でも動かなくなったときに、深く理解している人が誰もいないから素早く直せない。
4. 複利で膨らむ技術的負債
バイブコーディングセッションを重ねるたびに、プロジェクトの規約じゃなくエージェント独自のパターンに従ったコードが増えていく。10セッションも経つと、コードベースじゃなくて、微妙に異なる10種類のコーディングスタイルが混在した堆積物になる。この技術的負債は複利で膨らみます。新しいAIセッションが前回の混乱をそのまま引き継ぐから。
4フェーズSDDループ
仕様駆動開発は「プロンプト → コード」という単一ステップを、コード生成前にコンテキストを構築する4フェーズループに置き換えます。各フェーズは、AIエージェントが後続フェーズで参照できるドキュメント成果物を生成します。
SDDループ:
フェーズ1: 要件定義 ──→ requirements.md
↓ 「何を作るのか?」
フェーズ2: 設計 ──→ design.md
↓ 「どう作るのか?」
フェーズ3: タスク分割 ──→ tasks.md
↓ 「どの順序で何を実装するか?」
フェーズ4: 実装 ──→ コード + テスト
↓ 「作って、テストして、検証する。」
└──── フィードバック ──→ スペック更新、繰り返し。
フェーズ1: 要件定義
すべての機能は、何を作るのかを明確にするところから始めます。コードを求めないでください。要件分析を求めてください。
AIエージェントへのプロンプト: 「SaaSアプリにチーム招待システムを追加する必要があります。 コードを書く前に、requirements.mdを作成してください: - 招待フローのユーザーストーリー - エッジケース(期限切れの招待、重複メール、ロール競合) - セキュリティ要件(レート制限、トークン検証) - 既存認証システムとの統合ポイント - このフェーズで作らないもの」
「このフェーズで作らないもの」という制約が重要です。明示的なスコープ境界がないと、AIエージェントは過剰に作る傾向がある。要求されていない機能の追加、早すぎる抽象化の導入、コードベースの半分に影響が及ぶまでスコープを拡大してしまいます。
フェーズ2: 設計
要件が固まったら、技術的な意思決定に変換します。まだコードは書きません。アーキテクチャだけ。
AIエージェントへのプロンプト: 「requirements.mdを読んでください。design.mdを作成してください: - DBスキーマ変更(テーブル/カラム、マイグレーション戦略付き) - APIエンドポイント(メソッド、パス、リクエスト/レスポンス形状) - サービスレイヤーアーキテクチャ(関数、依存関係) - エラーハンドリング戦略(エラー種別、HTTPコード、メッセージ) - 招待ライフサイクルの状態マシン(保留 → 承認/期限切れ/取消) src/services/とsrc/api/の既存パターンを参照してください。 実装コードは絶対に書かないでください。」
このフェーズでアーキテクチャの間違いを捕捉します。コードが存在する前に。設計ドキュメントを修正するのは、半分実装された機能をデバッグするよりも無限に安い。
フェーズ3: タスク分割
設計を個別の、依存関係順の実装ステップに分解します。各タスクは、AIエージェントが1回の集中セッションで完了できる大きさにします。
tasks.md例: ## チーム招待システム — 実装タスク ### データベースレイヤー - [x] Task 1 (S): `team_invitations`マイグレーション作成 - カラム: id, team_id, email, role, token, status, expires_at - テスト: マイグレーションup/down、制約検証 ### サービスレイヤー - [ ] Task 2 (M): `InvitationService.create()`実装 - 依存: Task 1 - 検証: メール形式、重複チェック、チームメンバー上限 - テスト: ハッピーパス、重複拒否、上限強制 - [ ] Task 3 (M): `InvitationService.accept()`実装 - 依存: Task 2 - 検証: トークン存在、未期限切れ、未使用 - テスト: 正常承認、期限切れトークン、使用済みトークン ### APIレイヤー - [ ] Task 4 (M): POST /api/teams/:id/invitations - 依存: Task 2 - 認証: チーム管理者ロール必須 - テスト: 201成功、403非管理者、409重複、429レート制限 - [ ] Task 5 (M): POST /api/invitations/:token/accept - 依存: Task 3 - 認証: ログイン必須、メール一致必須 - テスト: 200成功、404無効トークン、410期限切れ
フェーズ4: 実装
ここで、ようやく、AIがコードを書きます。ただし巨大なプロンプト1つではなく、1タスクずつ完全なコンテキスト付きで渡します。
AIエージェントへのプロンプト: 「requirements.md、design.md、tasks.mdを読んでください。 Task 2を実装してください:InvitationService.create() 要件: - src/services/TeamService.tsの既存サービスパターンに従う - 入力バリデーションにZodを使用 - エラーハンドリングにカスタムAppErrorクラスを使用 - テストを先に書く(テストファイル、次に実装) - 実装後にテストを実行して検証 既存ファイルはimport追加以外は変更しないこと。 Task 3-5はまだ実装しないこと。」
このプロンプトの制約が重要な仕事をしています。「既存パターンに従う」がコンテキスト崩壊を防ぐ。「テストを先に書く」がTDDを強制する。「既存ファイル変更不可」がエージェントの「親切な」リファクタリングを防ぐ。「Task 3-5はまだ」がスコープクリープを防ぐ。
Specファイルの構造化
SDDループは永続的コンテキストに依存しています。セッションをまたいで存続し、すべてのAIエージェントにプロジェクトでの振る舞い方を教えるファイルです。
CLAUDE.md / Cursor Rules / AGENTS.md
これらのファイルは異なるツールで同じ役割を果たします。毎セッション開始時にAIが読むプロジェクトの「ブリーフィングドキュメント」です。
# CLAUDE.md(または.cursor/rules、またはAGENTS.md) ## プロジェクト概要 Next.js 16、TypeScript、Drizzle ORM、PostgreSQLで構築した EコマースSaaSプラットフォーム。Turborepoでモノレポ管理。 ## 技術スタック - フレームワーク: Next.js 16 (App Router, Server Components) - 言語: TypeScript 6.0 (strictモード) - データベース: PostgreSQL 17 + Drizzle ORM - バリデーション: Zod v3 - スタイリング: Tailwind CSS v4 - テスト: Vitest + Playwright - 認証: カスタムJWT + リフレッシュトークンローテーション ## アーキテクチャルール 1. すべてのDB操作はsrc/repositories/のリポジトリクラスを経由 2. ビジネスロジックはsrc/services/のサービスクラスに配置 3. APIルートはサービスを呼び出す薄いコントローラー 4. すべての入力はsrc/schemas/のZodスキーマで検証 5. エラーはカスタムAppErrorクラスを使用(src/lib/errors.ts参照) 6. すべてのIDはcrypto.randomUUID()で生成したUUID ## コーディング規約 - named exportを使用、default export禁止 - すべてのpublic関数に明示的な戻り値型 - エラーメッセージパターン: "[エンティティ].[アクション] failed: [理由]" - ファイル名: kebab-case、クラス名: PascalCase - import: 外部 → 内部 → 型でグループ化 ## 絶対禁止事項 - `any`型の使用禁止。`unknown` + 型ナローイングを使用。 - default export禁止。named exportのみ。 - マイグレーションファイルの変更禁止。変更は新規ファイルで。 - .envファイルのコミット禁止。.env.exampleで文書化。
核心原則:ルーティングであって、ダンプではない
ルートspecファイルは200-300行以下に保ちましょう。エージェントに「何をすべきか」と「詳細はどこにあるか」を教えるべきであって、プロジェクトのすべてのルールを詰め込むべきではありません。
## 参照ドキュメント - アーキテクチャ決定: /docs/architecture.md - API設計規約: /docs/api-conventions.md - データベーススキーマ: /docs/schema.md - 機能スペック: /docs/specs/[機能名].md
AIエージェントはルートファイルを読み、関連領域の作業時に参照ドキュメントをオンデマンドで読みます。これはプログレッシブディスクロージャーです。良いソフトウェアを作るのと同じUX原則を、AIのコンテキスト管理に適用したものです。
ディレクトリ別ルール
多くのAIツールはディレクトリスコープのルールをサポートしています。ドメイン固有の制約に活用しましょう:
# src/services/.rules(またはsrc/services/.cursorrules) ## サービスレイヤールール - すべてのサービスメソッドはasyncにすること - サービスはコンストラクタインジェクションで依存関係を受け取る - サービスはsrc/api/からインポートしない(循環依存禁止) - すべてのpublicメソッドに対応するテストを用意する - 複数テーブル操作にはトランザクションを使用 - 重要な操作の入出力をlogger.info()でログ出力
TDDとAIエージェント:Red-Green-Refactorループ
TDDは単にAIエージェントと互換性があるだけではなく、AIエージェントにとって理想的なワークフローです。テストが、AIエージェントが最も必要としているもの、すなわち「正しい」の客観的で検証可能な定義を提供してくれるからです。
TDDがAIとの組み合わせで優れている理由
テストなしでは、AIにコードを生成させた後、手動で正確性を確認しなければなりません。認知的に疲弊する作業で、エラーも起きやすい。
TDDでは、まず正しさを定義し、AIにテストが通るまでコードを生成させます。実装の詳細をレビューするのではなく、結果をレビューする。テストスイートが自動検証器になるわけです。
従来のフロー(脆弱):
プロンプト → コード → 手動レビュー → 「合ってそう?」 → デプロイ → バグ
TDDフロー(堅牢):
スペック → テスト(失敗) → AIに指示 → コード → テスト実行 → 通過? → デプロイ
↓
失敗 → AIが自動で反復
スペック → テスト → 実装パターン
ステップ1: テストスペック作成(人間主導)
// __tests__/services/invitation-service.test.ts import { describe, it, expect, beforeEach } from 'vitest'; import { InvitationService } from '@/services/invitation-service'; describe('InvitationService.create', () => { it('有効なメールとロールで招待を作成すること', async () => { const result = await service.create({ teamId: 'team-uuid-1', email: '[email protected]', role: 'member', invitedBy: 'admin-uuid-1', }); expect(result.id).toBeDefined(); expect(result.status).toBe('pending'); expect(result.token).toHaveLength(64); expect(result.expiresAt).toBeInstanceOf(Date); }); it('同じメールへの重複招待を拒否すること', async () => { await service.create({ teamId: 'team-uuid-1', email: '[email protected]', role: 'member', invitedBy: 'admin-uuid-1' }); await expect( service.create({ teamId: 'team-uuid-1', email: '[email protected]', role: 'member', invitedBy: 'admin-uuid-1' }) ).rejects.toThrow('Invitation.create failed: duplicate invitation'); }); it('チームメンバー上限を強制すること', async () => { await expect( service.create({ teamId: 'full-team', email: '[email protected]', role: 'member', invitedBy: 'admin-uuid-1' }) ).rejects.toThrow('Invitation.create failed: team member limit reached'); }); });
ステップ2: AIが実装(エージェント主導)
エージェントがコードを生成し、テストを実行し、失敗を確認し、自動で反復します。Red-Greenループですが、AIがサイクルを回している。「何を」はテストで定義し、「どうやって」はAIが解決するわけです。
ステップ3: 人間がレビュー(結果重点)
テストが通ったら実装をチェックします。プロジェクトのパターンに沿っているか、パフォーマンスの懸念はないか、セキュリティの懸念はないか。見慣れないコードを1行ずつ読んでバグを探すのではなく、目的を持ってレビューする。
失敗パターンとSDDの防御メカニズム
| 失敗パターン | 根本原因 | SDDによる防御 |
|---|---|---|
| コンテキスト崩壊 | エージェントがプロジェクト規約を知らない | CLAUDE.mdのアーキテクチャルール + コーディング規約 |
| ハッピーパスバイアス | エラーハンドリングを生成しない | テストがエラーシナリオを明示的に定義 |
| スコープクリープ | 過剰実装または無関係なコードの変更 | タスク分割 + 「しないこと」の制約 |
| アーキテクチャドリフト | プロジェクトのパターンでなく自身のパターンを使用 | 設計ドキュメント + ディレクトリ別ルール |
| リグレッション | 新コードが既存機能を破壊 | テストファーストワークフローが即座にキャッチ |
| セキュリティホール | 脅威モデルを考慮しない | フェーズ1のセキュリティ要件 + セキュリティテストケース |
| オーナーシップの空白 | 理解していないコードをデプロイ | フェーズ2の設計レビューが実装前の理解を強制 |
| 技術的負債 | セッション間で不整合なパターン | CLAUDE.mdが全セッションで一貫性を確保 |
プロダクションワークフロー:すべてをまとめる
機能リクエストからマージまでの実践的SDDワークフロー:
1. 機能ブリーフ
## 機能:チームロール管理 チームメンバーのロール変更(admin → member、member → admin) とメンバーのチームからの除外が必要。チーム管理者のみが これらの操作を実行可能。
2. AI支援の要件定義
Prompt: 「上のブリーフと、既存の認証システム src/services/auth-service.tsを読んでください。 ユーザーストーリー、エッジケース、セキュリティ要件、 そして作らないものをカバーするrequirements.mdを生成してください。 最後の管理者が自分のロールを変更しようとしたら 何が起きるかも考慮してください。」
3. AI支援の設計
Prompt: 「requirements.mdを読んでください。DBスキーマ変更、 APIエンドポイント、サービスメソッド、ロール遷移の状態 ダイアグラムを含むdesign.mdを作成してください。既存の コードベースのパターンに従ってください。人間のレビューが 必要な設計判断にはフラグを立ててください。」
4. 人間レビューチェックポイント
これがhuman-in-the-loopの重要な瞬間です。設計ドキュメントをレビュー:
- データベーススキーマは妥当か?
- APIエンドポイントはRESTfulで既存APIと一貫しているか?
- AIは「最後の管理者」エッジケースを捉えているか?
- AIが見逃したセキュリティ上の問題はないか?
コードを1行も書く前に設計ドキュメントを修正します。
5. AI支援のタスク分割
Prompt: 「requirements.mdとdesign.mdを読んでください。 依存関係順の実装ステップを含むtasks.mdを作成してください。 各タスクにテストカバレッジ要件を含めてください。」
6. 反復的な実装
Prompt: 「tasks.mdのTask 1を実装してください。テストを先に 書いて、次に実装。テストを実行して検証。完了したらtasks.mdに 完了マークをつけてください。Task 2には進まないでください。」
タスクごとに繰り返し。各タスクを独立してレビューできるため、巨大なPR1つに立ち向かうよりもコードレビューが扱いやすくなります。
7. 統合検証
Prompt: 「tasks.mdのすべてのタスクが完了しました。 `pnpm test`でテストスイート全体を実行してください。 失敗があれば修正。次に`pnpm lint`を実行して 問題があれば修正してください。」
SDDのスケーリング:チームパターン
共有Specリポジトリ
チームでは、specファイルをコードと並べてバージョン管理します:
project/
├── .claude/
│ └── rules/
│ ├── general.md
│ ├── api-conventions.md
│ └── testing.md
├── docs/
│ └── specs/
│ ├── team-invitations/
│ │ ├── requirements.md
│ │ ├── design.md
│ │ └── tasks.md
│ └── role-management/
├── CLAUDE.md
├── AGENTS.md
└── src/
AI支援作業のPRテンプレート
AI生成コードを含むPRは必ずspecを参照するようにしましょう:
## PRチェックリスト(AI支援) - [ ] /docs/specs/[機能名]/に機能スペックが存在 - [ ] requirements.md テックリードのレビュー・承認済み - [ ] design.md テックリードのレビュー・承認済み - [ ] tasks.mdのすべてのタスクが完了・チェック済み - [ ] テストカバレッジ: 要件の全パスがテスト済み - [ ] 機能スコープ外のファイル変更なし - [ ] `pnpm test` パス - [ ] `pnpm lint` パス
重要な指標
| 指標 | SDD導入前 | SDD導入後 | 意味 |
|---|---|---|---|
| スプリントあたりのAIリグレッション | 8-15件 | 1-3件 | コード品質の直接測定 |
| AIコードのデバッグ時間 | バグあたり2-4時間 | バグあたり15-30分 | オーナーシップ空白の解消 |
| PRレビュー時間 | 45-60分 | 15-20分 | レビュー効率 |
| 機能全体のデリバリー時間 | 同等(コーディング速い、デバッグ遅い) | 正味30-40%短縮 | 実質生産性向上 |
| セキュリティ問題検出率 | 20-30% | 80-90%(テスト+スペック) | セキュリティ改善 |
よくある反論(と反論への反論)
「SDDはただの文書化オーバーヘッドじゃない?」
違います。SDDのドキュメントはAIが生成し、人間がレビューし、AIが消費します。人間の実質的な文書化作業は機能あたり10-15分(レビュー時間)です。代替案は、理解していないコードをデバッグするのにバグあたり2-4時間使うこと。
「プロジェクトが小さすぎるんですが」
プロジェクトが十分に小さくて全体をすべて頭の中に入れておけるなら、バイブコーディングでもうまくいくかもしれません。SDDはコードベースがワーキングメモリを超える時点で不可欠になります。目安として、相互に接続されたロジックを持つ10-20ファイル程度が境界線です。
「テストは開発を遅くするんじゃ?」
テストは最初の1時間を遅くします。以降のすべての時間を速くする。AIエージェントでは、このトレードオフはさらに有利です。エージェントがテストに合わせて実装を書きますが、たいてい1回目か2回目の試行で合わせてくる。テスト作成は人間が5-10分、実装はAIが30-60秒。
「もっと良いプロンプトを書けばいいのでは?」
プロンプトの品質は重要です。でもプロンプトは一時的なものです。セッションが終われば消える。SDDのspecファイルは永続的で、時間とともに改善され、異なるAIツール間で機能します。CLAUDE.mdは今日のセッションだけでなく、すべてのセッション、すべてのチームメンバー、すべてのAIツールを助けます。
成熟度モデル
SDDを導入するチームは通常、3つのステージを経て進化します。
ステージ 1: 反応的(現在の大半のチーム)
- その場のプロンプトでAIコード生成
- 頻繁なデバッグ、苦痛なレビュー
- 永続的コンテキストファイルなし
- 毎セッションゼロからスタート
ステージ 2: 構造化(SDD導入)
- CLAUDE.md / AGENTS.md確立
- 複雑な機能に4フェーズループ適用
- AIワークフローにTDD統合
- ドメイン別ディレクトリルール
- 実装前の設計レビュー
ステージ 3: 体系化(SDD成熟)
- コードと並行してspecリポジトリを管理
- AIエージェントが作業中にspecドキュメントを更新
- 指標の追跡と最適化
- specドキュメントによる新メンバーのオンボーディング
- ツール間一貫性(Cursor、Claude Code、Copilotが同じspecに従う)
大半のチームが1スプリントでステージ2に到達できます。ステージ3はspecファイルが蓄積されパターンが安定するにつれ、自然に2-3ヶ月で発展します。
エンジニアリングの現実
AIコーディングエージェントは、高速にコードを生成する史上最強のツールです。同時に、高速にバグを量産する史上最強のツールでもある。この2つを分けるのは、モデルでもプロンプトでもツールでもない。方法論なんです。
バイブコーディングはAIをエンジニアリング規律の代替として使う。SDDはAIをエンジニアリング規律のブースターとして使う。4フェーズループは官僚主義じゃありません。シニアエンジニアがいつも実践してきた同じプロセスを、AIエージェントも従えるように明文化しただけなんです。
AIエージェントは、これまで雇った中で最も生産的なジュニアデベロッパーです。SDDはそのジュニアを正しくオンボーディングする方法なんです。明確な要件、ドキュメント化されたパターン、定義されたタスク、検証可能な受け入れ基準。オンボーディングを省略すれば皆が不満を言う混乱が訪れ、オンボーディングに投資すれば皆が夢見る生産性の倍増が手に入る。
選択肢はAIコーディングエージェントを使うかどうかじゃない。エンジニアリング規律を持って使うか、持たずに使うかです。SDDがその答えを明確にしてくれます。
関連ツールを見る
Pockitの無料開発者ツールを試してみましょう