1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AI(Claude Code)と仕様駆動開発で EC サイトを 1 日で作る — 21 タスク・20 PR・実装衝突ゼロの記録

1
Last updated at Posted at 2026-07-04

AI(Claude Code)と仕様駆動開発で EC サイトを 1 日で作る — 21 タスク・20 PR・実装衝突ゼロの記録

はじめに

「AI にコードを書かせると速いが、雑になる」— そう感じている人に向けて、仕様駆動開発(SDD: Spec-Driven Development)のプロセスに AI を組み込むと何が起きるかを、実際に 1 日で EC サイトを作った記録として残します。

やったことの要約:

  • スーパーマーケットの EC デモ(商品カタログ → カート → モック決済の注文 → 履歴)を、要件定義 → 基本設計 → タスク分解 → 実装 → 統合検証の 5 フェーズで構築
  • 実装は 21 タスク / 20 Pull Request(+6,836 行)。AI のサブエージェントを git worktree で分離して最大 4 本並列で実装し、タスク間の実装衝突はゼロ
  • 人間の役割は各フェーズの承認と全 PR のマージ判断(差し戻し 0 件)
  • 一番の収穫は、タスクの完了条件を厳しく設計していたおかげで、基盤層の潜在バグを統合前に検出できたこと(後述)

環境: Claude Code(AI コーディングエージェント)+ cc-sdd(SDD ワークフローのスキル集)。ベースは JAWS-UG 磐田コミュニティの教材テンプレート(公開ミラー)で、FastAPI + Vue 3 + PostgreSQL + Terraform のモノレポです。本記事の EC デモは検証用ブランチ上の演習で、公開ミラーには含まれていません。

何を作ったか

スーパーらしいドメイン制約を意図的に含めました:

  • 軽減税率 8% / 10% 混在・税込総額表示(内税は税率ごとに合算 → 切り捨て 1 回で逆算)
  • 在庫検証(カート追加時・表示時・注文確定時の 3 段階)と上限 20 点
  • 利用者別カート(TTL 30 日)・注文のスナップショット記録・履歴の本人スコープ

品質ゲートは既存テンプレートのものをそのまま維持: backend カバレッジ 90%(実績 98.67%)・axe 重大違反 0・Lighthouse ≥ 0.9・バンドル予算・E2E キーボード完走。

01-product-list.png

04-cart.png

05-order-detail-banner.png

進め方 — SDD 5 フェーズ + 人の承認ゲート

cc-sdd の /kiro-* スキル(要件 → 設計 → タスク → 実装 → 検証)に、次の運用ルールを重ねました:

  1. 全工程・全タスクを issue 管理(1 タスク = 1 issue = 1 PR)
  2. 各フェーズ・各 PR は人の承認が付くまで先へ進まない(AI は勝手にマージしない)
  3. フェーズ内の独立タスクは並列サブエージェント + git worktree で実施
  4. 着手・原因発見・完了を該当 issue にコメントで記録(後から追える状態を維持)
フェーズ 成果物
1. ステアリング プロジェクトの前提知識(product / tech / structure)を AI の長期記憶として整備
2. 要件定義 EARS 形式の受け入れ基準 45 個(10 要件)
3. 基本設計 境界コミットメント・API 契約・ER 図・エラーボディ規約
4. タスク分解 21 サブタスク(依存関係・並列可否・変更してよいファイル境界を明記)
5. 実装・統合検証 20 PR + 統合検証 GO 判定

数字で見る結果

指標
実装タスク / PR 21 タスク / 20 PR
差分規模 +6,836 / -131 行(OpenAPI 生成型 +683 行含む)
実装期間 初回マージから最終マージまで約 10 時間
並列ウェーブ 4 回(3 本 / 3 本 / 2 本 / 4 本)+ E2E 2 本
タスク間の実装衝突 0 件
テスト backend 80(cov 98.67%)/ frontend unit 72 / E2E 15(axe 違反 0)
人の差し戻し 0 件(CI red のままマージ依頼した PR も 0 件)

設計のキモ

AI に書かせる前に設計で決め切ったことが、後の並列実装を支えました。

  • 金額は整数円(税込)だけで持つ: JPY に小数はないので float / Decimal を使わない。内税は「税率ごとに合算 → floor 1 回」で逆算し、税計算はバックエンドの純関数に一元化。フロントはサーバ計算値を表示するだけ
  • 在庫の検証と引当は SQL 1 文: UPDATE products SET stock = stock - :qty WHERE id = :id AND stock >= :qty の rowcount で成否判定。「読んで・確かめて・書く」の分離レースを最初から作らない
  • 注文はスナップショット: 注文明細は商品テーブルへの FK を持たない複製列。商品が改名・削除されても履歴は不変
  • prerender は公開ページだけ: vite-ssg の対象をトップと商品一覧に限定し、ユーザー状態依存 UI(カートバッジ)は ClientOnly で包む。静的 HTML に誰かの状態が焼き込まれないことを dist の grep まで確認
  • E2E は page.route モックで決定的に: CI のフロントジョブはバックエンドを起動しない。実 API の検証はバックエンドの pytest + CI 上の PostgreSQL が担う、と役割分担

山場: 「409 なのに在庫が減っている」

このプロジェクトで一番価値があった瞬間は、注文確定 API の原子性テストが落ちたときです。

症状: 在庫不足で 409 Conflict を返したのに、同じ注文に含まれる別商品の在庫減算が永続化されている。

原因: 基盤タスクで導入した Unit of Work(1 リクエスト = 1 トランザクション)の実装にありました。

修正前(潜在バグ)
async def commit_scope(session: AsyncSession) -> AsyncIterator[AsyncSession]:
    try:
        yield session
    except Exception:
        await session.rollback()
        raise
    else:
        await session.commit()

async def get_session() -> AsyncIterator[AsyncSession]:
    async with AsyncSessionLocal() as session:
        async for scoped in commit_scope(session):  # ← ここが問題
            yield scoped

FastAPI はルート内の例外を依存関係の yield 地点に throw してくれますが、async for の本体で起きた例外は、内側のジェネレータ(commit_scope)には届きません。ジェネレータは suspend されたまま GC の GeneratorExit を受けるだけで、それは except Exception に掛からない。つまり rollback も commit もされず、トランザクションが開いたままになります。テストの共有セッションでは、次のリクエストの commit が前のリクエストの変更を巻き込んで永続化していました。

修正後
@asynccontextmanager
async def commit_scope(session: AsyncSession) -> AsyncIterator[AsyncSession]:
    try:
        yield session
    except Exception:
        await session.rollback()
        raise
    else:
        await session.commit()

async def get_session() -> AsyncIterator[AsyncSession]:
    async with AsyncSessionLocal() as session, commit_scope(session) as scoped:
        yield scoped  # async with は例外を確実にコンテキストへ転送する

このバグは、基盤タスク単体のテストでも、注文 API 単体のテストでも green でした。検出できたのは、注文タスクの完了条件に「409 の後に別セッションで在庫を読んで不変を確認する」というトランザクション横断の観察を要求していたからです。完了条件の設計は、テストの数より効きます。

並列実装で衝突ゼロにできた理由

4 本並列の画面実装ウェーブでも実装本体のコンフリクトはゼロでした。効いたのは 3 つ:

  1. 設計段階で「タスクごとに変更してよいファイル境界」を確定し、実装プロンプトに明記(「他タスクの View・部品には触れるな」)
  2. git worktree でサブエージェントごとに作業ツリーを分離(ブランチも 1 タスク 1 本)
  3. 共有ファイルに触る必要がある場合は設計時点で予見して記録(実際、共有スモークテストから「自分の担当画面の行を消す」変更だけは全タスクで重なり、マージのたびに 1 行単位の競合解消が必要だった — これは想定内のコスト)

うまくいかなかったこと(改善点)

正直に書きます。すべて改善 issue として起票済みです。

問題 何が起きたか
帳簿の更新漏れ 全タスク完了後もタスクリストのチェックボックスが未更新のまま。統合検証で発覚
設計ドリフト 設計に書いたエラーコード 5 種のうち 2 種が未実装のまま統合検証まで残存
非機能要件の所有者不在 「全画面で a11y 検査」「応答 1 秒」のようなクロスカッティング要件は、どのタスクも部分的にしか触らず、最後に締める担当がいなかった
ツール間の恒常衝突 prettier の整形と eslint-plugin-vue のルールが逆方向に働き、都度 eslint-disable で回避
並列実装の重複コード 金額フォーマッタが 6 箇所に重複(値は全部一致していたのが救い)

教訓 3 つ

  1. 完了条件に「境界を跨ぐ観察」を必ず 1 つ入れる。 単体で green でも、トランザクション・プロセス・レイヤの境界を跨いだ瞬間に壊れるバグは、完了条件でしか捕まえられない
  2. クロスカッティング要件には「最後に締めるタスク」を明示する。 「全画面で◯◯」系の要件は所有者を決めないと必ず漏れる
  3. 帳簿(タスクリスト・スペックの状態)はマージ・クローズと同じ手で更新する。 AI は指示された成果物は作るが、指示されていない帳簿は放置する

おわりに

「AI が書く」こと自体より、設計で境界を切り、完了条件で品質を縛り、人がゲートで承認するという構造のほうが効いた、というのが率直な感想です。SDD のプロセスは AI との相性がよく、特に「仕様 → タスク分解 → 境界指定」までを丁寧にやると、並列実装は安心して任せられました。

同じ構成(Claude Code + cc-sdd)はどちらも公開されているので、興味があれば試してみてください。ベースにした教材テンプレート(Dev Container + FastAPI + Vue 3 + Terraform、ガードレール込み)も公開ミラーにあります。

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?