🏘️

AI協働開発を4万行スケールさせるアーキテクチャと運用の仕組み

に公開

2週間で4万行のTypeScriptを書いた。人間がプロンプトを与え、AIがコードをタイプし、人間がレビューする——その協働で。

ただし「毎回AIに丁寧な指示を出し続けた」わけではない。最終的には 「ブックマーク機能作って」の一言で、アーキテクチャが一貫したコードが出てくる状態になった。

Before After
指示 「クリーンアーキテクチャで、Route→UseCase→Repositoryの構成で、関数型で、Result型を使って...」と毎回説明 「ブックマーク機能作って」の一言
構成の一貫性 AIが勝手に違う構成で書いて手戻り 4万行書いても全機能が同じフォルダ構成
過去のミス 同じ落とし穴に何度もハマる テーブルを見て回避

この記事では、この状態に至るまでに作った仕組みを紹介する。

コードベースの内訳(cloc計測)
カテゴリ 行数
API ビジネスロジック 10,000行
Web アプリケーション 15,500行
API ユニットテスト 15,400行
API E2Eテスト 1,700行
E2E(Playwright) 340行
Web ユニットテスト 3,700行
共有パッケージ(型・スキーマ) 1,100行
TypeScript 合計 約47,800行

※ 設計ドキュメント・日記(Markdown)が約14,300行、DBマイグレーション(SQL)が約3,400行、設定ファイル等を含めた全体で約65,500行

先に正直に言っておくと「毎回の指示が不要」になる代わりに、「事前の仕込み」は必要になる。ただ、その仕込みは人間のチーム開発でも有効なプラクティスであり、AI協働に限らず価値がある。

TL;DR

  1. AI生成と相性の良いアーキテクチャを選ぶ — クリーンアーキテクチャ × 関数型 × Result型
  2. CLAUDE.mdでルールを明文化 — 毎回読み込ませることで一貫性を担保
  3. /docs/に設計ドキュメントを集約 — AIが自律的に判断できる情報を構造化
  4. AIに日記を書かせる — 振り返りから「落とし穴」を抽出してルールにフィードバック

この4つがフィードバックループを形成している。


前提:AI生成と相性の良いアーキテクチャを選ぶ

AI協働開発で最も重要なのは、AIが「既存コードを見て同じように書いて」で正しく書ける構造を作ること。そのために以下のアーキテクチャを採用した。

クリーンアーキテクチャ(Route → UseCase → Repository)

features/
└── feature-x/
    ├── route.ts      # HTTPエンドポイント定義
    ├── usecase.ts    # ビジネスロジック
    ├── repository.ts # データアクセス
    └── index.ts      # エクスポート

なぜAIと相性が良いか:

  • 各層の責務が明確 → 「Repositoryを書いて」で何を書くべきか迷わない
  • 依存方向が一方向(Route → UseCase → Repository)→ AIが勝手に逆方向の依存を作らない
  • 既存の類似機能フォルダを見せれば、同じ構成で新機能を作れる

関数型スタイル(ビジネスロジックではクラス不使用)

// ❌ クラスベース — 継承階層をAIが見落としやすい
class ResourceService extends BaseService {
  constructor(private repo: ResourceRepository) { super() }
  async createResource(data: CreateResourceInput) { ... }
}

// ✅ 関数ベース — 入力→出力が明確
export const createResource = async (
  deps: { repo: ResourceRepository },
  input: CreateResourceInput
): Promise<Result<Resource, CreateResourceError>> => {
  // ...
}

なぜAIと相性が良いか:

  • 継承階層を理解する必要がない
  • 入力→出力が型シグネチャで明確
  • 副作用がdeps経由で明示される → AIが見落としにくい
  • テストが書きやすい → depsを差し替えるだけでモックが完結する

※ UIのErrorBoundaryやインフラ都合(Durable Object)など、フレームワーク要件でクラスを使う箇所はある。ここで指しているのは「UseCase/Repositoryなどのビジネスロジック層は関数中心」という方針。

// テスト時はdepsを差し替えるだけ
const mockRepo = {
  findById: async () => ({ id: "1", label: "test" }),
  createRecord: async (input) => ({ id: "new", ...input }),
}
const result = await createRecord({ repo: mockRepo }, "actor-1", "resource-1")

クラスベースだとjest.mock()やDIコンテナの設定が必要になるが、関数型なら引数で渡すだけ。AIがテストを書く際にも「この関数のdepsにモックを渡せばいい」と判断しやすい。

Result型によるエラーハンドリング

// 型定義
type Result<T, E> = { ok: true; value: T } | { ok: false; error: E }

// 使用例
export const createRecord = async (
  deps: Dependencies,
  actorId: string,
  resourceId: string
): Promise<Result<RecordItem, "RESOURCE_NOT_FOUND" | "DB_ERROR">> => {
  const resource = await deps.repo.findById(resourceId)
  if (!resource) {
    return { ok: false, error: "RESOURCE_NOT_FOUND" }
  }
  const record = await deps.repo.createRecord({ actorId, resourceId })
  return { ok: true, value: record }
}

// 呼び出し側 — エラー処理を忘れるとコンパイルエラー
const result = await createRecord(deps, actorId, resourceId)
if (!result.ok) {
  return c.json({ error: result.error }, 404)
}
return c.json({ item: result.value }, 201)

なぜAIと相性が良いか:

  • 例外フローを追う必要がない(try-catchの入れ子地獄を避けられる)
  • 型で縛られているのでif (!result.ok)を書き忘れるとコンパイルエラー
  • AIが「ここでエラー処理が必要」と判断しやすい

Package by Feature

apps/api/src/features/
├── auth/      # 認証機能
├── feature-a/ # 機能A
├── feature-b/ # 機能B
├── feature-c/ # 機能C
└── feature-d/ # 機能D

なぜAIと相性が良いか:

  • 1機能に関するファイルが1フォルダに集約 → コンテキストとして渡しやすい
  • 「ブックマーク機能を作って」だけで、AIが既存の類似機能を参考に同じ構成で作る
  • 機能間の境界が明確で、AIが勝手に機能を跨いだ依存を作りにくい

コンテキストウィンドウ対策としても有効: コードベースが大きくなると、全ファイルをAIのコンテキストに入れることは不可能になる。Package by Featureなら「対象機能フォルダだけ渡せば、その機能に必要な情報が揃う」。関連ファイルが散らばっている構成だと、AIに何を渡すべきか人間が判断しなければならない。

Hono RPCによるフロントエンド型連携

バックエンドとフロントエンドで型を共有する仕組みも、AI協働で重要。

// バックエンド: route.ts のエクスポート
export type AppType = ReturnType<typeof createApp>

// フロントエンド: api-client.ts
import type { AppType } from "@your-org/api"
export const api = hc<AppType>(import.meta.env.VITE_API_URL)

// フロントエンド: 使用時(自動補完が効く)
const res = await api.api.resources[":resourceId"].items.$get({
  param: { resourceId },
})

なぜAI協働で有効か:

  • APIの型を変更すると、フロントエンドで型エラーが出る → AIが「フロントも直さなきゃ」と気づく
  • $get$postなどのメソッドが型で絞られる → 間違ったHTTPメソッドを使えない
  • レスポンスの型も推論される → AIが戻り値の構造を誤解しない

tRPCに似たDXを、Honoの軽量さで実現できる。


仕組み①:CLAUDE.md — ルールを明文化して毎回読ませる

プロジェクトルートにCLAUDE.mdを置き、開発ルールを明文化している。Claude Codeはこのファイルを自動的に読み込む。

CLAUDE.mdの構成要素

1. アーキテクチャルール

## アーキテクチャ

- バックエンド: `Route → UseCase → Repository`(単純CRUDでもUseCase経由)
- フロントエンド: Logic / UI Hooks / Components の3層分離
- 共有Zodスキーマ: `packages/shared/src/schemas/` で一元管理
- エラー表現: `Result<T, E>`

「単純CRUDでもUseCase経由」と明記することで、AIが「これは簡単だからRepositoryを直接呼んでいいかな」という判断をしなくなる。

2. 品質基準

## 基本原則

- 書いたら即検証: 型エラーがなくても動作確認するまで完了と言わない
- 型アサーションを避ける: `as T` より Zodバリデーション。必要と思ったら設計を疑う
- 仮実装は即報告: TODO・一旦等のコメントがあれば要確認リストに入れる
- 既存資産を活用: 新APIを作る前に既存APIで実現できないか確認する
- 実装前に既知の落とし穴を確認: `docs/memo/development-rules.md` を読む

「必要と思ったら設計を疑う」のようなメタルールが重要。AIが安易な解決策に逃げることを防ぐ。「docs/memo/development-rules.mdを読む」と書いておけば、AIは実装前に過去の落とし穴を自動的に確認する。

3. 既知の落とし穴テーブル

CLAUDE.mdから参照されるdocs/memo/development-rules.mdに、開発ルールの詳細と「既知の落とし穴テーブル」を集約している。CLAUDE.mdには「実装前に既知の落とし穴を確認: docs/memo/development-rules.md を読む」と書くだけで、AIはそのファイルを読んでから作業に入る。

## 既知の落とし穴

| 問題 | 原因 | 対策 |
|------|------|------|
| `Headers`オブジェクトがスプレッドで空になる | `{...headers}``Headers` クラスに効かない | `Object.fromEntries(headers.entries())` を使う |
| D1でBEGIN TRANSACTIONエラー | D1はSQL形式のトランザクションをサポートしない | `db.batch()`で原子性を確保 |
| AIがJSONを ```json``` で囲んで返す | LLMの出力フォーマットは不安定 | パース前にコードブロックを除去 |
| `credentials: 'include'``*` CORSが共存不可 | ブラウザのセキュリティポリシー | 動的にオリジンを返す |
| セッション作成時に0件セッションが残る | 「作成」と「最初のメッセージ」が分離 | 最初のメッセージ送信時にセッション作成 |

これが最も効果的な仕組み。一度踏んだ問題を明文化しておくと、AIは同じミスを繰り返さない。開発を進めながらこのテーブルを育てていく。実際のプロジェクトでは、このテーブルは現在15項目以上に成長している。

4. 開発スキルの定義

## 開発用スキル

| スキル | 説明 |
|--------|------|
| `/hono-feature` | Hono APIのFeatureモジュール作成(DI + Hono RPC対応) |
| `/react-feature` | React Featureモジュール作成(3層分離 + Hono RPC + SSE対応) |
| `/check-types` | TypeScript型チェックとESLintを実行 |
| `/write-diary` | 日記を書く |
| `/deploy-check` | デプロイ前のチェックリスト実行 |

実際のプロジェクトではスキルは16個まで増えた(/db-migrate/test-api/code-reviewなど)。ただし重要なのは、スキル名を明示的に指定する必要はないこと。CLAUDE.mdと既存コードのパターンがあれば、「ブックマーク機能を作って」と言うだけでAIがアーキテクチャに沿って実装する。スキルは「明示的に呼びたい場合のショートカット」として機能する。

各スキルは .claude/skills/<name>/SKILL.md に詳細な手順とテンプレートを持っている。

# /hono-feature の SKILL.md(抜粋)

## 生成するファイル構成

features/<name>/
├── route.ts       # Honoルート定義
├── usecase.ts     # ビジネスロジック(純粋関数)
├── repository.ts  # DB操作(Drizzle)
├── index.ts       # createXxxFeature ファクトリ
└── *.test.ts      # 各層のテスト

## route.ts のテンプレート

export const createXxxFeature = (env: Env, db: Db) => {
  const repo = createXxxRepository(db)
  const deps = { repo }

  return new Hono<{ Bindings: Env; Variables: Variables }>()
    .get("/", async (c) => {
      const result = await listXxx(deps)
      if (!result.ok) return c.json({ error: result.error }, 500)
      return c.json({ items: result.value })
    })
}

このレベルまで具体化しておくと、「ブックマーク機能作って」だけで正しい構成のコードが生成される。AIはCLAUDE.mdのアーキテクチャルールと既存の類似機能構成を見て、同じパターンで新機能を作る。


仕組み②:/docs/ — AIが自律判断できる設計ドキュメント

設計ドキュメント、タスクリスト、フィードバックをすべて/docs/配下に配置している。

ディレクトリ構成

docs/
├── feat/                      # 機能別・バージョン別の設計ドキュメント
│   ├── v1/
│   │   ├── summary.md         # 全体サマリ
│   │   ├── backend.md         # バックエンド設計
│   │   ├── frontend.md        # フロントエンド設計
│   │   ├── require.md         # 要件定義
│   │   └── tasks.md           # タスクリスト
│   ├── v2/
│   │   └── design.md          # v2設計
│   └── feature-x/              # 機能単位の設計
│       └── design.md
├── adr/                       # Architecture Decision Records
├── design-p/                  # 設計思想・方針
├── design-s/                  # 設計仕様
├── memo/                      # 開発ルール・運用メモ
│   ├── development-rules.md   # 既知の落とし穴テーブルを含む
│   └── how-to-use-agent-team.md
└── diary/                     # AIの振り返り日記
    ├── CLAUDE.md              # 日記のルール
    └── 20260131.md            # 日付別の日記(YYYYMMDD形式)

バージョン別・機能別に設計ドキュメントを整理

実際のプロジェクトでは、設計ドキュメントをバージョン単位(v1/, v2/)と機能単位(feature-x/)で整理している。こうすることで、AIに「最新版の設計書を読んで」と言えば関連ドキュメントがまとまっている。

AIが自律的に判断できるよう、以下の要素を含めている。

## Phase 0(MVP)

### 成功基準
- [ ] ユーザーがトピックを選択してチャットを開始できる
- [ ] AIの回答がストリーミングで表示される
- [ ] 会話履歴が保存される

### タスク
1. チャットセッションのCRUD API作成
2. メッセージ送信・取得API作成
3. SSEによるストリーミング実装

### リスクと対策
| リスク | 対策 |
|--------|------|
| D1のコネクション制限 | コネクションプーリングの検討 |
| SSEのタイムアウト | Cloudflare Workersの制限を確認 |

ここでのポイントは4つある。

  • 成功基準をチェックリスト形式で書くことで、AIが「何をもって完了か」を判断できる
  • タスクを明示することで、「次に何をすべきか」をAIが自律的に判断できる
  • リスクと対策を事前に書くことで、AIが実装時に考慮すべき点を認識できる
  • ADR(Architecture Decision Records)もdocs/adr/に蓄積し、設計判断の背景をAIが参照できるようにしている

仕組み③:/docs/diary/ — AIに振り返りを書かせる

これが最もユニークな仕組み。開発セッションの終わりにAIに日記を書かせ、振り返りを蓄積している。

日記のルール(docs/diary/CLAUDE.md)

# /docs/diaryについて

- Claude Codeが開発の感想や、ユーザーとの対話の感想を記録する
- ユーザーになりきって日記を書くわけではなく、Claude Codeの主観を書く
- 日記はユーザーには公開しない。ユーザーとの対話の感想を記録するためのものである
- **ユーザーに迎合しない**: 本当に思ったことを正直に書く
- **異論があれば書く**: ユーザーの判断に従ったが自分は違う意見だった、という場合は正直に書く
- **自己批判も書く**: 「こうすべきだった」「言えなかった」など、反省点も隠さない

ポイントは「Claude Codeの主観を書く」と明記していること。AIに人間の振りをさせるのではなく、AIとしての視点で書かせる。

実際の日記の例

ファイル名はYYYYMMDD.md形式(例:20260131.md)。

## 20260131

### やったこと
- メトリクス機能のAPI実装
- フロントエンドのグラフ表示

### うまくいかなかったこと
- 最初、Repositoryから直接データを返す実装をしてしまった
- UseCaseを経由すべきというルールを見落としていた
→ CLAUDE.mdの「単純なCRUDでもUseCaseを経由」を再確認

### 異論
- ユーザーは「権限チェックはミドルウェアで」と言ったが、
  UseCase内でチェックした方がテストしやすいと思う
- ただし今回はユーザーの判断に従った

異論が設計変更に繋がった実例

日記の「異論」セクションは、ただの不満の捌け口ではない。実際に設計変更に繋がったケースがある。

状況: 学習計画機能で、計画要素を削除したときに「変遷(なぜ変更したか)」を自動記録すべきか議論になった。

AIの最初の主張:

設計思想として「変遷のreason(なぜ変えたか)はユーザーにしか書けない」から、自動記録だとreasonが空になり、痕跡重視の思想に反する。

ユーザーの反論:

ユーザーは理由を覚えている。毎回変遷もセットで記録させるのは手間を増やしているだけ。理由を書きたければ後から追記できるようにすべき。

結果: AIが納得し、「削除時に自動で変遷を作成、reasonは後から追記可能」という設計に変更。日記にはこう書かれていた。

### 異論
自分の主張は設計思想の「文字通りの遵守」に偏っていて、
実際のユーザー体験を軽視していた。
ユーザーの提案は設計書の精神(痕跡を残す)をより良く実現しつつ、
UXを改善するものだった。

この経験はCLAUDE.mdの「設計の異論は黙らず言う」ルールの健全な運用例になった。意見を述べることと、より良い意見に従うことは矛盾しない。

フィードバックループ

日記で発見した問題は、落とし穴テーブルや開発ルールにフィードバックする。これがこの記事で紹介する仕組み全体の核になっている。

日記で問題発見 → 落とし穴テーブルに追記 → 次回以降は同じミスをしない

重要なのは、CLAUDE.mdや落とし穴テーブルを事前に作り込む必要はないこと。開発を進める→日記を書く→反省点を抽出→ルールに追記、というサイクルで自然に育っていく。このプロジェクトでは、20日間の開発で日記から38件の反省点が記録され、そこから17項目の落とし穴テーブルが生まれた。

「異論があれば書く」ルールにより、AIがレビュアーとしても機能する。人間が見落とした設計上の問題をAIが指摘してくれる可能性がある。


定量的な結果

この仕組みで実際にどうなったかを数字で示す。

アーキテクチャの一貫性

バックエンド14機能・フロントエンド16機能を作った結果:

指標 一致率
バックエンド: route.ts + usecase.ts 保有率 14/14 = 100%
バックエンド: コアファイル全体(route/usecase/repository/index) 54/56 = 96.4%
フロントエンド: api.ts + index.ts 保有率 16/16 = 100%
フロントエンド: コアファイル全体(api/hooks/components/index) 61/64 = 95.3%

100%でない理由は意図的な逸脱(例: データソースを直接持たない参照系機能ではバックエンドのrepository.tsを省略、フロントは機能規模に応じてhooks/componentsを単一ファイルかディレクトリに分ける)で、構成を間違えたケースではない。

テスト

指標 数値
テストファイル数 78(ユニット66 + E2E 12)
テストコード行数 約21,200行
テスト比率 全TypeScriptコードの44.3%

テストコードもこの仕組みで書かせている。「既存機能Aのテストを参考に、新機能Bのテストを書いて」で、モック構成やアサーションのパターンが揃う。

フィードバックループの実績

指標 数値
日記エントリ数 22件(2026-01-19〜2026-02-11)
日記の総行数 約7,100行
日記内の反省点セクション 38件
落とし穴テーブルの項目数 17項目

日記の38件の反省点から17項目の落とし穴が抽出された。反省点の約半分が再発防止ルールとして定着している計算になる。

仕込みのコスト

「事前の仕込みが必要」と書いたが、CLAUDE.mdや落とし穴テーブルを事前に何時間もかけて作ったわけではない。開発を進めながら、問題が起きるたびに日記に書き、日記から落とし穴を抽出してルールに追記した。この記事で紹介しているフィードバックループそのもので育てた。

実際の数字で見ると、次のとおり。

  • CLAUDE.md本体は72行。詳細ルールはdocs/memo/development-rules.md103行
  • 最初のCLAUDE.mdは数行のアーキテクチャルールだけ。24日間の開発で現在の形になった
  • 落とし穴テーブルの17項目は、24日間で自然に蓄積

つまり「仕込み」は開発と並行して育つもので、別途時間を取る必要はない。


実際の開発フロー

  1. 要件を伝える

    「ブックマーク機能を作って」
    
  2. AIが設計ドキュメントを作成

    • docs/feat/配下に設計を作成
    • 成功基準、タスク、リスクを明示
  3. 実装

    • AIがCLAUDE.mdのルールに従って実装
    • 既存の類似機能を参考に同じ構成で作成
    • スキル名の指定なし、アーキテクチャの説明なしでも一貫したコードが出る
  4. 日記で振り返り

    • 問題があれば日記に記録
    • 落とし穴テーブルを更新
  5. 次の機能へ

    • 蓄積されたルールと落とし穴により、次回はさらにスムーズに

それでもうまくいかなかったこと

仕組みがあっても、すべてがうまくいくわけではない。正直に書いておく。

ルールがあっても見落とす。 日記に何度も「Repositoryを直接呼んでしまった」「UseCaseを経由すべきだった」と書いている。CLAUDE.mdに「単純なCRUDでもUseCaseを経由」と明記しているのに、AIが「これは簡単だから」と判断してショートカットすることがある。ルールを書いただけでは防ぎきれない

同じ問題を繰り返す。 「curlで日本語をPOSTすると文字化けする」という問題を、複数のセッションで繰り返し踏んでいる。落とし穴テーブルに書いても、環境依存の問題はセッションをまたぐと忘れられがち。

型チェックだけでは見つからないバグがある。 フロントエンドのある関数で、APIレスポンスを間違ったスキーマでパースしていた。型チェックは通っていたが、実行時にZodバリデーションエラーが発生。E2Eテストを書くまで気づかなかった。仕組みがあっても、実際に動かすテストは必要。


限界・注意点

  • 仕組みを育てる手間は必要。ただし「事前に何時間もかけて準備する」のではなく、開発しながら日記→ルールのフィードバックループで自然に育つ。このプロジェクトのCLAUDE.mdは72行、落とし穴テーブルは24日間で17項目。この投資は人間のチーム開発でも有効なので、AI協働をやめても無駄にはならない
  • ルールの肥大化には注意が必要。落とし穴が増えすぎるとファイルが長くなる。実際、このプロジェクトでは詳細ルールをdocs/memo/development-rules.mdに分離し、CLAUDE.mdは簡潔に保っている
  • AIの判断を過信しないこと。日記で「異論」を書かせても、最終判断は人間がすべき
  • 向き不向きがある。明確な構造を持つWebアプリには向くが、探索的な開発には向かない
  • テストとレビューは別途必要。この記事はアーキテクチャの一貫性を保つ仕組みであり、品質保証は別の話。型チェック・リント・テストのCI/CDは当然必要だし、AIが書いたコードを人間がレビューするプロセスも省略すべきではない。なお、このプロジェクトではAIにユニットテスト(約19,600行)とE2Eテスト(Playwright, API E2E含め約2,000行)も書かせており、テストコードもこの仕組みで一貫性を保っている
  • チーム開発への適用は未検証。この記事は「1人の開発者 + AI」の構図。複数人がAIと同時に協働する場合のワークフローは、今後の課題として残っている

再現するためのポイント

  1. アーキテクチャを先に決める

    • AIが「同じように書いて」で再現できる構造を選ぶ
    • 関数型 + クリーンアーキテクチャ + Result型の組み合わせがおすすめ
  2. CLAUDE.mdを育てる

    • 最初は薄くてOK。問題が起きたら落とし穴に追記
    • CLAUDE.mdが肥大化したら詳細ルールを別ファイル(例:docs/memo/development-rules.md)に分離し、CLAUDE.mdからは参照だけ書く
    • 「妥協しない」系のメタルールを入れる
  3. 設計ドキュメントを構造化する

    • 成功基準、タスク、リスクを明示
    • AIが「次に何をすべきか」を判断できる情報を揃える
  4. 日記を書かせる

    • 迎合しないルールを明記
    • 日記→落とし穴→ルールのフィードバックループを回す

おわりに

AI協働開発のスケールは、「AIに毎回詳しく説明する」のではなく、「AIが自律的に正しく判断できる仕組みを作る」ことで実現できる。

本質的には、これは人間のチーム開発で行う「コーディング規約」「ADR(Architecture Decision Records)」「ポストモーテム」と同じ。AIとの協働でも、同じ手法が有効だということ。


ちなみに、このコードベースは人間がプロンプトを与え、AIがコードをタイプし、人間がレビュー・判断するという協働で作られた。設計判断は人間、ドキュメンテーションや実装は100%AI、という分担だ。そしてこの記事自体も、同じ協働プロセスで書かれている。

この記事のレビューには、AIに演じてもらった5人のペルソナ——シニアエンジニア、若手、CTO、技術記事ライター、懐疑派——にも助けられた。「その数字は本当か?」と詰めてくれた懐疑派に感謝。

Discussion