2026.07.06

AIの「作るもの」の認識ズレを防ぐ 〜OpenSpecを実プロジェクトでどう使ったか〜

2026.06.22

AIの「作るもの」の認識ズレを防ぐ 〜OpenSpecを実プロジェクトでどう使ったか〜

結論ファースト

  • OpenSpecは「コードの前に仕様で合意する」をAIコーディングに持ち込む仕組みで、会話に流れて消えていた要件を、永続する仕様レイヤーとして残す。
  • 実運用では、まず proposal.md でAIの大まかな理解を確認し、spec.mddesign.md を行き来しながら認識ズレを潰す。
  • archiveしておくと判断理由が残り、後から人間もAIも「なぜこの形にしたのか」を参照できる。

アジェンダ

  1. 課題
    • AIの「作るもの」の認識がいつの間にかズレている
  2. 解決策:OpenSpec
    • OpenSpecが解決する課題
    • specsとchangesという2つの構成要素
    • 使い方:propose → apply → archive と delta spec
    • 技術仕様の詳細:/opsx スラッシュコマンドの正体
  3. 実践・導入効果
    • 実行環境・技術要素
    • 実プロジェクトでは何をレビューしているか
    • 勉強会で実際に出た質問
    • 今後やりたいこと
  4. まとめ

1. 課題:AIの「作るもの」の認識がいつの間にかズレている

こんにちは。グループ研究開発本部 次世代システム研究室のN.Uです。

AIにコードを書かせていると、いつも同じところで困ります。チャットで要件をすり合わせて実装まで持っていったのに、会話が長くなるにつれて最初に決めた前提がどこかに流れていく。数日後に同じ機能をいじろうとすると、AIが何を前提に書いたのかが追えず、人間とAIの「作るもの」の認識がいつの間にかズレている。結局、要件は会話履歴の中にしか残っていないので、再現も監査もできません。

 

2. 解決策:OpenSpec

OpenSpecが解決する課題

この困りごとに対する一つの答えが、今回紹介する OpenSpec です。OpenSpecはAIコーディングを前提にした仕様駆動開発(Spec-Driven Development)のフレームワークです。コードを書く前に「何を作るか」を人とAIで合意した仕様として文章で固定し、それに沿ってAIに実装させます。

従来のAIコーディングでは、要件はチャット履歴の中にしか存在しませんでした。会話が流れれば要件はブレるし消える。AIが何を前提に書いたのかは後から追えず、人とAIの認識のズレに気付くのはたいてい手戻りが発生したあとです。

この問題に対するOpenSpecの答えは、チャット履歴ではなく、永続する 仕様レイヤー を人とAIのあいだに挟むことです。作る前に何を作るかを文章で固定し、その文章を source of truth として実装させる。要件が構造化された文書として残るので、長い会話履歴を読み直さずに済み、いつ・何を・なぜ変えたかも追えるようになります。

提供元はFission-AIで、OSSとして公開されています(GitHubで5万を超えるstarが付いています)。npm install -g @fission-ai/openspec でインストールでき(セットアップ手順)、Node.js 20.19以降が前提です。Claude / Copilot / Cursor / Codexなど20以上のAIツールに対応していて、特定のツールに縛られないのも特徴です。

設計思想として、README には次のような方針が掲げられています。

  • 厳密なフェーズゲートを持たずいつでも修正できる(Fluid not rigid)
  • 反復的に成果物を磨く(Iterative not waterfall)
  • 儀式やオーバーヘッドを最小にする(Easy not complex)
  • 新規だけでなく既存プロジェクトでも使える(Brownfield-focused)

これらの新規プロジェクト専用のツールではなく、既存コードがある現場でこそ使える前提になっている点が、実務に投入しやすい理由になっています。

specsとchangesという2つの構成要素

OpenSpecを理解するうえで押さえるべきは、specschanges という2つの要素です。前者が仕様の本体、後者がそこに加える変更の単位にあたります。

specs は現在の確定した振る舞い、つまり source of truth です。openspec/specs/ に置かれ、例えるなら「今のアプリの取扱説明書」にあたります。一方の changes は、これからやる1つの変更の作業フォルダです。openspec/changes/<name>/ に置かれ、「次のリリースの企画書と設計とToDoをまとめたもの」と考えるとわかりやすいです。

両者の関係はシンプルです。changesは完了したらarchiveでspecsにマージされる。だからspecsは常に「今ある仕様」を表し、changesはそれに対する差分(delta)を表す、という関係になっています。この差分という考え方が、後で出てくるdelta specの核になります。

使い方:propose → apply → archive と delta spec

openspec init を実行すると、次のようなディレクトリが生成されます。

openspec/
├── specs/        # 確定仕様(source of truth)
├── changes/      # 進行中の変更提案
│   └── archive/  # 完了した変更の履歴
└── config.yaml   # 設定(任意。技術スタックや規約を書ける)
.claude/
├── commands/opsx/  # スラッシュコマンド (propose/apply/archive/explore)
└── skills/         # 対応する Skill 定義

 

主要なライフサイクルは3ステップで、これに任意の explore を加えた4つで回します。順に見ていきます。

propose:提案を作る

/opsx:propose add-dark-mode のようにお題を渡すと、changesフォルダに4つのファイルが自動生成されます。

  • proposal.md:何を・なぜ作るか(Why / What Changes / Capabilities / Impact)
  • specs/<機能>/spec.md:要件の差分(Requirement: と、WHEN/THEN形式の Scenario:
  • design.md:どう作るか(Context / Goals・Non-Goals / Decisions / Risks)
  • tasks.md:実装手順(チェックボックス付きのタスク一覧)

依存順は proposal → specs → design → tasks です。後ろのファイルは前のファイルを読んで作られるので、何を・なぜ作るかが固まってから、どう作るか、どの順で実装するかが決まっていきます。

apply:実装する

/opsx:apply を実行すると、AIが tasks.md を上から消化し、完了したタスクのチェックを付けていきます。途中で設計を変えたくなったら成果物を直してよい、というのがfluidな運用です。フェーズゲートで固められていないので、実装しながら設計の不備に気付いたら、その場でdesign.mdに戻れます。

archive:確定させる

/opsx:archive で、changes内のdelta specを openspec/specs/ 本体にマージします。このとき、ADDED(新規要件)/ MODIFIED(既存要件の変更)/ REMOVED(廃止)が本体に反映されます。マージが終わった変更フォルダは、タイムスタンプ付きで changes/archive/ に移動されます。これがそのまま「いつ・何を・なぜ変えたか」の監査履歴になります。

delta spec:差分で書くという発想

ここがspec駆動の核です。changes内のspecは仕様の全文ではなく、差分で書きます。

## ADDED Requirements
### Requirement: ユーザーはダークモードを選択できる
#### Scenario: 設定画面でダークモードを選ぶ
- WHEN ユーザーが設定画面でテーマを「ダーク」に切り替える
- THEN 画面全体が即座にダークテーマで再描画される

各要件は Scenario(WHEN / THEN)という受け入れ条件を持ちます。「こう操作したら、こう動くべき」を実装の前に固定しておく。これが仕様駆動の実体で、AIに渡す合意の単位になります。

進行中の変更は次のCLIで確認できます。openspec list で進行中の変更一覧、openspec show <名前> で詳細表示、openspec validate <名前> でフォーマット検証、openspec view でインタラクティブなダッシュボードが見られます。

 

技術仕様の詳細:/opsx スラッシュコマンドの正体

少し踏み込んだ話をします。/opsx:propose のようなスラッシュコマンドは、裏で専用のエンジンが動いているように見えますが、実体はプロンプト(指示文)を会話に注入しているだけです。

仕組みはこうです。/opsx:explore と打つと、Claude Codeが .claude/commands/opsx/explore.md というMarkdownファイルを読み込み、その本文をAIへのメッセージとして会話に丸ごと差し込みます。ファイル先頭の ---(frontmatter)はコマンド一覧用のメタ情報で、本文がAIへの指示そのものです。AIはその指示に従って振る舞う、それだけです。

ここまでは主要な3つのコマンドについて説明しましたが、explore だけ毛色が違います。explore.mdに書かれているのは「考えろ、実装するな」「図を多用しろ」「決まったら成果物の保存を提案しろ、ただし勝手に保存するな」といった姿勢の固定です。発散用のガードレールで、コードは書かせません。残りの propose / apply / archive は、「openspec CLIをこう叩け」という具体的な手順書になっています。例えばapply.mdには、changeを選択してcontextファイルを全部読み、tasksを1つずつ実装して - [ ]- [x] に更新し、不明点や設計問題やエラーで一時停止する、という手順が書かれています。

この仕組みを知っておく価値は、自分たちのプロジェクトに合わせて指示書を書き換えられる点にあります。実際、私のプロジェクトでは .claude/skills に独自skillを足しています。レビューで繰り返し指摘された観点や、AIが一発で作れなかったケースを記録しておき、次回以降の生成に効かせるためのものです。

たとえば、レビューで「DB accessorのcatchで null を返してエラーを握りつぶさない」「固定値の集合は文字列リテラルではなくenumに寄せる」「同じ意味の欠落に nullundefined を混ぜない」といった指摘が何度も出た場合、それをskillにします。

レビュー指摘:
DB accessorで、DBエラー時に return null していた。
これだと「レコードが存在しない」と「DBエラー」の区別が消える。

抽象化したルール:
not-found は try 内で null を返す。
DBエラーは catch で Logger.error に記録し、再スローする。
service は accessor から返る null を「不在」として扱う。

さらに、変更ファイルだけを見てもプロジェクト固有の作法は見落としやすいので、「同種の既存ファイルを複数読んで、多数派の実装パターンと突き合わせる」というレビュー手順もskill化しています。AIレビューに「このコードベースではaccessor層のエラーハンドリングはどう揃っているか」を見に行かせることで、一般的なベストプラクティスではなく、プロジェクトの文脈に合っているかを確認できます。

3. 実践・導入効果

実行環境・技術要素

本記事で紹介しているOpenSpecの運用は、次のような環境で試しています。

  • 対象プロダクト:業務で使う比較的規模の大きなWebアプリケーション
  • client:Next.js / React / TypeScript
  • server:NestJS / GraphQL / TypeScript
  • infra:AWS CDK / ECS Fargate / ALB / CloudWatch / EventBridge Scheduler
  • AIコーディング環境:Claude Code
  • 利用モデル:Claude Opus 4.8
  • 仕様管理:OpenSpec CLI、openspec/.claude/commands/opsx/.claude/skills/

実プロジェクトでは何をレビューしているか

ここからは、実際に私のプロジェクトで作ったOpenSpecをもとに、どこを人間が見ているのかを具体的に書きます。公開記事なので社内固有名詞や一部のパスは丸めていますが、流れと判断の粒度は実際のものです。

私が一番大事だと感じているのは、OpenSpecを「AIが全部決めてくれる仕組み」として扱わないことです。AIに初稿を作らせるのは有効ですが、最初の認識がずれていると、その後のspec、design、tasksはきれいに間違います。なので人間は、順番にレビューするというより、各成果物を使ってAIの理解を検査していきます。

例:アクセスが少ない時間帯だけECSを停止する

たとえば、社内向けWebアプリのECS Fargateを、利用が少ない時間帯だけ停止してコストを下げる変更がありました。ALBアクセスログを分析すると、平日深夜帯と土日はほぼ利用がなく、一方で月末月初は夜間アクセスがありました。これをOpenSpecに起こすと、最初の proposal.md は次のようになります。

## Why
現在のPROD ECS Fargateは 24h x 4 task で常時稼働している。
ALBアクセスログを分析した結果、平日深夜帯と土日はほぼ実利用アクセスがなく、
稼働時間を絞ることで大きなコスト削減が見込める。

## What Changes
- 稼働タスク数を client 2 / server 2 から client 1 / server 1 に変更する
- 平日 7:00〜23:00 は常時1台稼働する
- 平日深夜帯と土日は ECS を desiredCount=0 にする
- 月末月初は請求業務の夜間アクセスを考慮して24h稼働する
- 停止時間帯にアクセスがあった場合は待機画面を返しつつ ECS を自動起動する

## Impact
- ECS Service の desiredCount
- ALB Listener Rule と Lambda Target Group
- EventBridge Scheduler
- CloudWatch Alarm
- ECS起動・停止を制御するLambda

ここで人間が見るのは、細かい実装ではありません。まず「AIは作りたいものを正しく理解しているか」を見ます。この例なら、単に「夜間にECSを止める」では足りません。月末月初は止めない、停止時間帯でもユーザーアクセスがあれば起動する、botや社外アクセスでは無駄に起動しない、朝イチでコールドスタートさせない、といった条件が重要です。

この proposal.md がずれていたら、以降の成果物は全部ずれます。たとえばAIが「常時1台だけ残す」と理解していたら、完全停止による削減効果も、停止中アクセス時の待機画面も、アイドル停止の設計も出てきません。なので最初のレビューでは、「実現したいこと」と「AIが言っていること」が同じかだけをかなり厳しく見ます。

specとdesignはぐるぐる回しながら直す

次に spec.mddesign.md を見ます。基本はspecを先に見ます。specは「ユーザーやシステムから見て何が成立すればよいか」を固定するものだからです。

### Requirement: 停止時間帯のアクセス自動起動とフィルタ

停止時間帯にユーザーアクセスがあった場合、access-handler Lambda が
待機画面を返しつつ state-controller を非同期 invoke して ECS を起動する。
ただし社内IPリスト外のアクセスは 403 で返し、ECS を起動しない。

#### Scenario: 社内IPからの実ユーザーアクセス
- WHEN ECS が停止状態かつ HTTP リクエストが届く
- AND リクエスト元IPが社内IPリストに含まれる
- THEN 待機画面を 200 で返す
- AND state-controller が ECS を起動する

#### Scenario: botや社外IPからのアクセス
- WHEN ECS が停止状態かつ HTTP リクエストが届く
- AND リクエスト元IPが社内IPリストに含まれない
- THEN 403 Forbidden を返す
- AND ECS は起動しない

この段階で見るのは、「正常系だけになっていないか」です。AIは放っておくと、ハッピーパスをきれいに書く一方で、業務上よく踏む境界条件を落とすことがあります。人間が勘違いしやすいところはAIも勘違いしやすい、というのが使っていての実感です。この例なら、月末月初、業務時間内の偶発的なアクセス0、CloudWatchメトリクスの遅延、社外アクセスでECSを起動しないこと、などがハマりどころでした。

ただし、specだけ見ても十分ではありません。designを見ると、実装上の制約やコストの都合で、specを定義し直した方がよいことがあります。

### Decision: スケジューラに EventBridge Scheduler を採用

Application Auto Scaling Scheduled Action は採用しない。
AWS cron の仕様上、平日かつ月末月初を除外するような複雑な条件を
1つのcronで安全に表現しづらいため。

### Decision: アイドル停止は CloudWatch Alarm から Lambda を直接呼び出す

SNS は挟まない。
通知先へファンアウトする要件がなく、構成要素を減らした方がシンプルなため。

ここで「specでこう決めたからdesignは従うだけ」とは考えません。design側で、インフラ条件を少し変えるだけでコストや運用負荷が大きく下がることがあります。逆に、designを詰めた結果「この条件は仕様として明記しておかないと危ない」と気付くこともあります。なので実際には、spec → design → spec → design のように行ったり来たりします。

このECS停止の例では、CloudWatchメトリクスに1〜5分程度の遅延があることがdesignで問題になりました。停止中アクセスで起動した直後にメトリクスを見に行くと、まだアクセスが反映されておらず、「アクセスなし」と誤判定する可能性があります。そこで、access-handler から呼ばれた場合はメトリクスを見ずにACTIVEと判定する、という設計にしました。この判断はdesignだけでなくspecにも反映し、「休止モード中のアクセスでの自動起動」というScenarioとして固定しました。

tasksは実装計画として妥当かを見る

最後に tasks.md を見ます。ここまで来ると、AIはコードベースと作成済みのOpenSpecをもとに実装計画を作ります。コードベースが整理されていて、既存パターンが明確なら、かなり良い粒度のtasksが出ます。

## 4. state-controller Lambda の実装
- [ ] event.source / 現在時刻 / 直近アクセスを入力に目標状態を判定する
- [ ] access 経由の場合はメトリクス確認をスキップする
- [ ] ACTIVE化: desiredCount=1 → health check待機 → ALB重みをECS 100%へ
- [ ] SUSPENDED化: ALB重みをLambda 100%へ → desiredCount=0

## 11. dev環境での挙動確認
- [ ] 業務時間入りで desiredCount=1 になる
- [ ] 業務時間外で直近アクセスなしなら停止する
- [ ] 停止時間帯のアクセスで待機画面を返して自動起動する
- [ ] 月末月初は終日ACTIVEになる
- [ ] 社外IPからのアクセスではECSが起動しない

tasksで見るのは、「実装順が現実的か」「検証タスクが抜けていないか」「既存コードの責務に沿っているか」です。コードベースに似た実装がすでにあればAIはそれに寄せます。逆に、既存コードが散らかっていたり、似た責務が複数箇所に分散していたりすると、AIも意図しない場所に処理を足そうとします。

ここは正直、プロジェクトをいくつか経験していないと見抜きづらい部分です。私もまだ完全にできているわけではありません。ただ、AIが出したtasksを見て「この実装場所は既存の責務と違う」「この検証順だと原因切り分けが難しい」「この境界条件は本番で踏みそう」と気付けるかどうかが、人間側の重要な仕事だと感じています。

人間が直しているもの

まとめると、人間が直しているのは文章の表現というより、AIの理解の向きです。

  • proposal.md:何を作るか、何を作らないか、なぜ今やるか、業務上の目的とスコープが合っているか。
  • spec.md:正常系だけでなく、境界条件、例外、権限、時間帯、失敗時の振る舞いがScenarioに落ちているか。
  • design.md:既存アーキテクチャに沿っているか、コストや運用負荷を増やしすぎていないか、却下案の理由が残っているか。
  • tasks.md:実装順、テスト、検証、デプロイ、ロールバックまで含めて、実際に作業できる単位になっているか。

OpenSpecは、AIに任せるための道具というより、AIの認識を人間がレビューできる形に変換する道具です。会話のままだと「なんとなく合っていそう」に見えるものが、proposal、spec、design、tasksに分かれることで、どこがずれているかを見つけやすくなります。

archiveしておくと、あとから判断理由をAIに渡せる

もう一つ、実際に使っていて価値を感じているのがarchiveです。まだ私のプロジェクトでも完璧に使いこなせているわけではありませんが、それでも「なぜこの実装にしたのか」が残っていて助かったことがあります。

たとえば、ログイン画面で現在のユーザーを返す me クエリの扱いを変えた変更では、archiveされたOpenSpecに次のような判断が残っていました。

## Why
ログイン画面では「未ログイン」は正常な状態である。
しかし従来の me クエリは未認証時に 401 を返すため、
ログイン画面でもエラー扱いになっていた。

## What Changes
- me クエリを nullable にする
- 未認証時は 401 ではなく null を返す
- me 以外の認証必須エンドポイントは従来通り 401 を返す

## Decisions
- GraphQL の viewer / me の慣習に合わせ、未認証を null として扱う
- 既存の JwtStrategy を再利用する OptionalJwtAuthGuard を追加する
- me のためだけに別の probe エンドポイントは作らない

この情報が残っていると、後から同じ箇所を触るときに「なぜ me は401ではなくnullなのか」で悩む時間が減ります。実装だけを見ると、単に「例外を握りつぶしているように見える」かもしれません。しかしOpenSpecを見ると、未ログインを正常状態として扱うための意図的な契約変更だと分かります。

これは人間にとっても便利ですが、AIにコンテキストとして渡せる点も大きいです。新しいチャットで「この機能を直して」と言うだけでは、AIは過去の判断理由を知りません。archiveされたspecやdesignを読ませれば、「なぜその形にしたのか」まで含めて実装を始められます。

勉強会で実際に出た質問

社内でOpenSpecの勉強会をしたときに出た質問のうち、答えを共有しておくと役に立ちそうなものを載せておきます。

Q. Spec KitやKiroと何が違うんですか?

A. 大きく3つのアプローチがあります。GitHubのSpec Kitはドキュメントが厚い反面、セットアップが重くフェーズが硬く、Python依存です。AWSのKiroはIDE統合が強力ですが、IDEにロックインされClaude専用です。OpenSpecは軽量・柔軟・ツール非依存という立ち位置で、その代わり自動ではなく意図的に運用する必要があります。「軽くて、どのツールでも使えて、ガチガチに縛らない」のが効くなら向いています。

Q. /opsx って裏で賢いことをやってるんですか?

A. いいえ、Markdownの指示書をAIに読ませているだけです。先ほど書いたとおり、特別なエンジンはありません。なので中身を読めば何をしているか全部わかるし、自分で書き換えることもできます。

Q. 既存のプロジェクトに後から入れられますか?

A. 入れられます。OpenSpecはbrownfield前提で設計されています。openspec init でディレクトリを作り、これからの変更分をchangesとして起票していけば、過去の全仕様を一気に書き起こさなくても運用を始められます。

Q. specって全部手で書くんですか?

A. 初稿はAIが起こします。proposal.md、spec.md、design.md、tasks.mdのたたき台をAIに書かせて、人間はそれをレビューして直す、という分担です。ゼロから仕様書を書くというより、AIの理解を成果物として出させて、それが自分の実現したいことと合っているかを確認します。

見る順番としては、まずproposalで大枠を確認します。ここでずれていたら全部ずれるので、「何を作るか」「何を作らないか」「なぜやるか」を重点的に見ます。次にspecとdesignを行ったり来たりしながら見ます。specが変わればdesignも変わるし、design上の制約やコストを見てspecを定義し直すこともあります。最後にtasksを見て、実装順と検証項目が現実的かを確認します。

Q. フェーズゲートで縛られて、途中で設計を変えられないのでは?

A. fluidな運用なので、どの成果物もいつでも修正できます。applyの最中にdesign.mdに戻って直しても構いません。厳密なゲートを持たないことが、むしろ実務で使いやすい理由になっています。

Q. 本当にトークン節約になりますか?

A. なります。要件が構造化文書として残るので、長いチャット履歴を毎回読み直さずに済みます。会話の最初から経緯を説明し直す必要がなくなるぶん、消費トークンが減るという理屈です。

今後やりたいこと

OpenSpecを開発に取り入れて手応えを感じている一方で、まだできていないこともあります。

一つは、設計段階でコードの全体像や過去の意思決定をAIに理解させた上で設計させることです。今は個々の変更単位ではうまく回っていますが、システム全体を踏まえた設計判断はまだ人間が補っています。

もう一つは、レビューで受けた指摘をLLMが適度に抽象化して記録し、次回以降は自動的にその視点でレビューしてくれる、自己更新型のレビューシステムです。今は、レビューで繰り返し出た観点を人間が読み直し、「これはプロジェクトの規約として残すべきだ」と判断したものを手でskillに書いています。

今後はこの流れを、もう少し自動化したいと考えています。たとえばPRレビューで「このDBエラーは握りつぶさず再スローしてください」と指摘されたら、それをそのまま保存するのではなく、LLMに「DB accessorでは not-found と DB error を区別する。catchでは log + 再スローする」という形に抽象化させる。そして次回以降の proposeapply、PR前レビューで自動的に参照させる、という流れです。

そして、それらの知見を全プロジェクト間で気軽に共有できる状態を作ること。今は個人のskillにとどまっているものを、チームや組織の資産にしていくのが次の課題です。

まとめ

  • OpenSpecは「コードの前に仕様で合意する」を持ち込む軽量なフレームワークで、会話に流れて消えていた要件を永続する仕様レイヤーに残す。
  • 流れは propose → apply → archive +任意のexplore。specsがsource of truth、changesがその差分(delta)で、archiveで合流して監査履歴になる。
  • 実運用では、proposalでAIの大まかな理解を確認し、specとdesignを行き来しながら境界条件と設計判断を固め、tasksで実装計画と検証項目を見る。
  • 人間が直しているのは文章ではなく、AIの認識の向き。人間が勘違いしやすい点はAIも勘違いしやすいので、そこを重点的に見る。
  • archiveしておくと、後から判断理由を人間もAIも参照できる。これはトークン節約だけでなく、認識合わせと監査性にも効く。
  • /opsx は指示書をAIに読ませているだけなので、中身を読めば全部わかるし、プロジェクトに合わせて書き換えられる。

参考リンク

最後に

グループ研究開発本部 次世代システム研究室では、最新のテクノロジーを調査・検証しながらインターネット上の高度なアプリケーション開発を行うエンジニア・アーキテクトを募集しています。募集職種一覧 からご応募をお待ちしています。

  • Twitter
  • Facebook
  • はてなブックマークに追加

グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。

 
  • AI研究開発室
  • 大阪研究開発グループ

関連記事