はじめに
AIコーディングで「動くけど意図と違う」「手戻りが減らない」「AIコーディングには持続可能性がない」と感じている方向けの記事です。
本記事の実践はClaude(Claude Code)を前提としています。他のAIエージェントで同様の効果が得られるかは未検証です。
本記事で紹介する 境界制約開発(Boundary-Constraint Development, BCD) は、既存の開発手法に見当たらなかったため便宜上つけた名前です。
境界制約開発(Boundary-Constraint Development, BCD) の定義:
人間が境界と判断基準を定め、AIが境界内で実装する。
「何を作るか」を詳しく書くより、「何が壊れてはいけないか」を先に定めます。仕様の精度より制約の明確さを優先し、AIの行動空間を制限することで品質を担保するアプローチです。
AIコーディングの手法はいくつかあります。プロンプトで会話しながら作るvibe-coding、仕様書をSource of Truthとする仕様書駆動開発(SDD)、ADRをメインに置いた意図駆動開発(IDD)など。
BCDはこれらと排他的ではなく、部分的に組み合わせて使えます。
本記事は一ヶ月の個人的な実践(ミニマムなプロジェクト、1〜2人規模)から抽出した方法であり、体感ベースの主張を含みます。その他のAIコーディング手法に部分的に取り入れられるようになっています。
BCDの全体像
BCDは3つの行為で構成されます。
- Boundary(境界定義): 何が壊れてはいけないか、何をしてはいけないかを定める
- Constraint(制約伝達): 定義した境界を実装まで届ける
- Discipline(規律): 止める・記録する・学ぶ
本記事ではまず「試すための最小ステップ」を示した後、境界を段階的に精緻化していった経緯に沿って説明します。
PRINCIPLE.md → Task Scaffold(タスクレベルの指示書) → 要求仕様の体系化 と進むにつれて、AIの行動空間をより正確に制限できるようになりました。
精緻化の手段は本記事で紹介する EARS+ に限らず、詳細設計書など他の形式でも同じ役割を果たせます。
試すための最小ステップ
Task Scaffoldを1枚書いてみる
次のタスクで、AIに渡す前にこれだけ書いてみてください。
# Task Scaffold: [タスク名]
## Must(やること)
- ...
## Must NOT(やってはいけないこと)
- ...
## Gray zone(判断に困ったらAIは停止、人間が判断)
- ...
## DoD(Definition of Done、完了すべきことチェックリスト)
- [ ] ...
Must NOTとGray zoneを書く過程で、「ここは自分が決めるべきだ」という判断点が浮かび上がります。これがBCDの出発点です。
手戻りを1件、分類してみる
最近の手戻りを1件、以下の4分類に当てはめてみてください。
| 分類 | 意味 |
|---|---|
| domain | 発注者(人間)のドメイン知識不足 |
| spec | 不変条件・敵対条件などの仕様の記述漏れ |
| bug | 実装上の純粋なコーディングエラー |
| ai | 仕様は明確だったのにAIが契約違反 |
手戻りの原因がどこにあるか可視化するだけで、次に何をすべきかが見えてきます。
PRINCIPLE.md:判断軸を置く
BCDの最初のステップは PRINCIPLE.md です。プロジェクトの冒頭に設計原則・優先順位・判断基準を1ファイルに書きます。
記述するのは技術詳細ではなく、判断の軸です。たとえば「安定性が第一義。最新機能より互換性」「作り込みすぎない、現場が調整する余地を残す」のような方針レベルの宣言です。
成功基準は 「発注者(人間)に聞かなくても、この文書を読めば判断できるか」 です。
逆に言えば、AIが PRINCIPLE.md を読んでも判断に迷う記述は、人間にとっても曖昧ということになります。
PRINCIPLE.md は単体でも効果があります。BCDの他の要素を導入しなくても、プロジェクトの判断軸を1ファイルに書くだけで、AIの出力のブレが減ります。
主な要素:
- Problem: 「なぜこれを作るか」の原点。現状の困っていること・目指す状態を各1-3文で記述
- Design Principles: 設計判断の価値軸。3〜5個。各原則に「説明」と「理由」を併記
- Principle Priorities: 原則間の衝突解決機構。デフォルト優先順序(一般法)+衝突シナリオ(特別法)の二層構造
- Non-Goals: 意図的にやらないことの明示。スコープクリープ防止。「まだやってないこと」ではない
- Update Policy: この文書自体の更新トリガー条件(ADR作成時・重大な設計変更時等)。Living Document宣言
Task Scaffold:実装の境界を引く
PRINCIPLE.md が判断の軸なら、 Task Scaffold は個々のタスクの境界線です。
Task Scaffold は仕様書ではなく 一時的なアダプター です。タスクの問題空間と解空間の境界を明示し、実装者AIが「迷わず止まれる」設計になっています。
タスクが完了したら削除します。
Task Scaffoldの5本柱
Task Scaffold のコアは5つのセクションで構成されます。
Problem Space は「何を解くか」の定義です。解き方ではありません。壊れてはいけないもの(存在論的判断、ビジネス的判断)をここで明示します。「Webhook受信時に署名検証が必要」は解き方の話で、「未検証のリクエストがシステムを変更してはならない」が問題空間の記述です。
What We Decided は決定内容の要約です。Problem Spaceに対する解答を簡潔にまとめます。
Implementation Boundaries が Task Scaffold の核心です。5つの区分で境界を定義します。
# Implementation Boundaries例
## Must do
- Webhook受信エンドポイントを作成する
- 署名検証を実装する
## Must NOT do
- 既存のSlack通知ロジックを変更しない
- 環境変数のデフォルト値をハードコードしない
## Gray zone(AIは停止、人間が判断)
- エラー時のリトライ戦略(指数バックオフ or 固定間隔)
- ログレベルの基準(INFO or DEBUG)
## Non-functional Boundaries
- レスポンスタイム: 3秒以内
- ログはRemediation(復旧)に繋げられる粒度
## External Constraints
- API Gatewayのヘッダー挙動に依存
- GitHub Webhook署名の検証仕様
Must NOTが「AIの行動空間の上限」を設定し、Gray zoneが「AIが判断してはいけない点」を明示します。
Mustは通常の仕様書でも書きますが、Must NOTとGray zoneが Task Scaffold 固有の価値です。
Output Contract は外部通知の仕様で、UXに直結する部分です。Slack・メール等で「誰に何が届き、何をさせるか」を定義します。通知がないタスクでは省略します。
Why This Design は採用案・却下案・理由のセットです。実装中に「なぜこの方式にしたのか」と迷ったとき、ここを参照して手を止めずに済みます。
実行管理とTeardown
Task Scaffold には実行管理セクションもあります。
Task Plan(フェーズ構造とチェックリスト)、DoD(実装者AIのDoDとユーザーのDoDを分離)、Deliverables(成果物と移譲先の追跡)です。
実装者AIは自身のDoDの条件を満たしたら止まって報告するルールです。
タスク完了時には Teardown (情報蒸発防止作業)を行います。
Task Scaffold の各セクションから恒久的な知見を抽出し、ADR・README・CLAUDE.md等に移譲してから Task Scaffold を削除します。
Task Scaffold は一時的なものなので、情報が適切な場所に移譲されれば役目を終えます。
Contextual Commit
移譲先の一つとしてコミットメッセージがあります。
Contextual Commits(Conventional Commitsのbodyに意思決定の文脈を記録する規約)を使うと、Task Scaffoldの情報をcommit履歴に残せます。
diffで分かることは書かず、diffに見えない情報だけを記録します:
refactor(payments): 単一通貨から多通貨対応に移行
intent(payments): エンタープライズ顧客がEUR・GBPを必要としている
decision(currency): トランザクション単位で通貨を持つ設計を採用
rejected(currency): アカウントレベルのデフォルト通貨:マーケットプレイスセラーに制約が強すぎる
Task Scaffold にはこの変換に必要な要素が揃っています。
Why This Designの採用案は decision、棄却案は rejected、Non-functional BoundariesとExternal Constraintsは constraint、Gray zone判断結果は learned、Problem Spaceのユーザー意図は intent にそれぞれ対応します。
PRINCIPLE.md + Task Scaffoldの限界
PRINCIPLE.md + Task Scaffold の組み合わせで、基本的なAIコーディングは回り始めます。しかし、しばらく運用すると足りないものが見えてきました。
「手戻りログ」を分類した結果、domain(人間のドメイン知識不足)とspec(仕様の記述漏れ)が多く、AI側の契約違反(指示の違反)は少数でした。
手戻りの多くはAIに渡す前の段階(知識不足や制約漏れ)に起因していました。
ただし、あらゆる手戻りは事後的に「伝達が足りなかった」とできるため、これはBCD実践の観察結果です。
特にdomain分類の手戻りが多い場合、BCDの制約記述では対処できず、ドメイン知識の獲得そのものが先決になります。
Task Scaffold は「書いたものの精度」を上げます。しかし「書かれなかったもの」は伝わりません。 Task Scaffold を書くたびに「何を書き忘れているか」を考える負荷がかかります。
これらの問題に対処するために、境界の精緻化をさらに進めることになりました。
境界をさらに精緻にする: EARS+という選択肢
Task Scaffold の限界に対処するには、「何が壊れてはいけないか」を体系的に列挙する仕組みが必要です。ここで求められるのは、書き忘れを防ぐ役割です。
この役割を果たせる形式はいくつかあります。詳細設計書で不変条件・制約を網羅的に書く方法もありますし、本記事で紹介するEARS+のような構造化記法を使う方法もあります。
BCDが求めているのは特定の記法ではなく、 「不変条件(常に真であるべきこと)」と「敵対条件(防ぐべきこと)」が明示的に書かれていること です。
本記事ではEARS+を例に説明します。
EARS+とは
EARS(Easy Approach to Requirements Syntax)は要求記述のための構造化記法で、曖昧さを排除し検証可能な形で仕様を書けます。
BCDではEARSを拡張した EARS+ を使っています。通常のEARS記法(WHEN〜THE SYSTEM SHALL〜)に加え、以下の2種類を追加しています。
- 不変条件 [INV]: システムで常に真であるべきこと(例:認証済みユーザーのみがリソースにアクセスできる)
- 敵対条件 [ADV]: 防ぐべきこと(例:未認証リクエストがデータを変更してはならない)
通常のEARSが「何をするか」を記述するのに対し、 [INV] と [ADV] は「何が壊れてはいけないか」を記述します。
AIに「賢く作れ」と言うより「これを壊すな」と言う方が、行動の制限として機能します。
書き忘れに対する機能
重要なのは 完成度より存在 です。
下書きは番号なし箇条書きでも構いません。ただし [INV] と [ADV] のラベルだけはつけます。これは書く時点で「何が壊れてはいけないか」を意識させる仕掛けです。
# 下書き例(これでも効果がある)
- [INV] 認証済みユーザーのみがリソースにアクセスできる
- [ADV] 未認証リクエストがデータを変更してはならない
- WHEN ユーザーがファイルをアップロードする THE SYSTEM SHALL マルウェアスキャンを実行する
詳細設計書で同じことをする場合は、「不変条件」「敵対条件」のセクションを設けるだけでも同等の効果が得られるはずです。
記法は手段であり、目的は「書き忘れの構造的な防止」です。
実践で気づいたこととして、仕様を伝えてAIにEARSを書かせた場合、敵対条件に対する人間のツッコミが他の要求種別より多くなります。
敵対条件は「何が壊れてはいけないか」=存在論的(ビジネス的)な判断であり、AIが自力で導出しにくい領域です。
Task ScaffoldとEARSの関係
Task Scaffold とEARSは相互にフィードバックする関係にあります。
Task Scaffold で実装の概要が見えた段階でEARSを書くと、手戻りが少なくなります。EARSを書く過程で発見した制約は、 Task Scaffold のMust NOTやGray zoneにフィードバックします。
EARS+も PRINCIPLE.md や Task Scaffold と同様、部分的に導入できます。既存のプロジェクトに [INV] と [ADV] のラベル付きで制約を追記するだけでも、AIへの指示の質が変わるはずです。
補足:ドメインモデルとADR
EARS+の手前で、存在と関係の規則(オントロジー)をドメインモデルとして定義しておくと、不変条件の粒度が上がります。何が存在しうるかを先に決め、EARS+と行き来しながら詳細に決定していきます。
技術選択の根拠はADR(Architecture Decision Record)として記録します。決定事項だけでなく、棄却した代替案とその理由を含めます。
制約をコードまで届ける: Constraint
BCDでは制約の伝達経路を明示的に設計します。
トレーサビリティコメント
コードに要求仕様のIDを埋め込みます。EARS+を使う場合は以下のようなフォーマットになります。
# @see EARS-001#REQ-U001
詳細設計書を使う場合は、設計書のセクション番号やID体系に合わせます。形式は問いませんが、「この実装はどの要求に基づいているか」をコードから逆引きできることが重要です。
テストファイルでも同様に、テストクラスのdocstringに @see を記述します。
要求仕様を基点にテストを書かせると、要求ID単位でテストクラスが構造化されます。
要求仕様なしでテストを書かせた場合とのカバレッジ差が顕著で、要求仕様が「テスト設計の入力」として機能します。
このフォーマットからEARS+の内容を取得するパーサーを作ると便利です。
スケルトン承認フロー
実装の最初のフェーズで、AIにディレクトリ構造・インターフェース定義を生成させます。
人間が承認してから本実装に進みます。構造レベルでの合意を先に取ることで、後段の手戻りを減らせます。
止める・記録する・学ぶ:Discipline
停止ルール
Task Scaffold のGray zone項目に遭遇したら、AIが実装を停止します。人間が判断し、結果を Task Scaffold に反映してから再開します。
Why Comment
コーディング判断の根拠をインラインコメントで残します。事後的ではなく、コーディング中のAIに書かせます。
# Why: HTTPヘッダーキーの正規化
# API Gatewayはヘッダーのケースをそのまま渡すため、
# 比較前にlower()で正規化する必要がある
書くべき場面は、他の妥当な実装方法があった / コンテキストがないと理解できない / 将来の読者が「なぜこうしなかった?」と問いそうなときです。
機能レベルの判断(なぜこの機能を作るか)は対象外です。
手戻りログ
手戻りが発生したら分類・記録し、予防策をフィードバックします。
### 手戻りタイトル YYYY/MM/DD [git commit hash]
分類: domain / spec / bug / ai
検出フェーズ: 仕様作成後 / スケルトン作成後 / ユーザーレビュー / criticレビュー / コード実行後
予防策:
ai は独立カテゴリにしているのは、AIを「仕様に基づいて動くエージェント」として扱う前提を反映しています。
仕様が明確だったのに意図と違う実装が出た場合、それはAI側の契約違反として記録します。
計測サイクル
corder(コンポーネント単位のエージェント)→ critic(レビューエージェント)→ retrospective で手戻りログを確認し、恒久的な知見をCLAUDE.mdや.claude/rules/に反映します。
何をAIに委ねるか:判断の分担
BCDでは「AIに何を委ねるか」を判断の性質で分けます。
| 判断の性質 | 問い | 担当 | 例 |
|---|---|---|---|
| 何が存在しうるか、何が壊れてはいけないか | ドメインの構造を決める | 人間主導(AI協働) | ドメインモデル、不変条件 |
| この事象は何を意味するか | 解釈・分類を決める | 人間主導(AI協働) | Gray zone判断 |
| どう実現・検知するか | 実装方法を決める | AI主導 | 実装、テスト生成 |
この分類は理論から演繹したものではなく、実践から帰納したものです。
きっかけは失敗体験でした。ドメインの構造(何が存在しうるか)を定義しないままLLMに渡したところ、DDLレベルで意図と乖離した出力が生成されました。
AIは一般的な制約を列挙できますが、「このシステムで何が壊れてはいけないか」はビジネスの文脈から人間が決めるものです。
この分担の前提として、人間側にドメイン知識があることが必要です。知識自体がない場合はBCDの外(ドメイン調査、ドメインエキスパートへのヒアリング、プロトタイピング等)で先に獲得する必要があります。
具体例:手戻りログから見るBCD
実際に起きた手戻りを紹介します。
事例: GitHub Webhook修正
起きたこと: AWS Lambdaのテストで、Google・Slack連携は正常動作を確認。しかしGitHub Webhookのrepository.createdイベントで401エラー(Missing signature)が発生しました。
分類: domain / bug 検出フェーズ: コード実行後
原因: API Gatewayがヘッダーのケースをそのまま渡すため、GitHubが送るX-Hub-Signature-256(大文字始まり)とコードのx-hub-signature-256(小文字)が不一致でした。
BCDの観点: BoundaryとConstraintの問題が重なっています。domain分類(API Gatewayのヘッダー挙動を把握していなかった)とbug分類(ヘッダーキーの正規化漏れ)の複合です。予防策は「仕様確認を計画時に入れる」——Boundary行為の時点で外部サービスの挙動を確認するプロセスを組み込むことです。
最後に
本記事の実践はClaude(Claude Code)を前提としています。他のAIエージェントで同様の効果が得られるかは未検証です。特にGray zone停止ルールの遵守はモデルの指示追従能力に依存します。
Disciplineは3行為の中で最も属人的です。手戻りログの記録、Why Commentの記述、retrospectiveの実施は、すべて人間が怠れば機能しません。
Claude Codeのhookやファイル監視などのシステム化をすると楽になると考えます。