AI 改完不敢上线?OpenSpec 规约驱动开发实战
最近读到一篇讲「Spec 规约编程 + 可视化 Skills / 代码地图」的文章,核心观点很戳人:面对几十万行的老工程,人和 AI 都缺一张全览图,所以 AI 改完的代码没人敢 Review、更不敢上线。文章给的答案是「用技能生成一张可交互的代码地图」。
我的补充是:地图解决的是「看得见」,还差一步「说得清、验得了」 。看得见结构,不等于对「改成什么样算完成」达成共识。这一步要靠规约(Spec)来兜。本文就聊一个把这件事做得相当轻量的开源工具 ------ OpenSpec。
一、问题不在 AI 写得差,在需求没落地
先把三类典型现场摆出来:
- 需求只活在聊天记录里。 你跟 AI 说了三句话,它改了八个文件。三天后要复盘「当初为什么要这么设计」,只能翻对话记录,而对话记录早被压缩、清上下文、换会话冲掉了。
- AI 的输出无法验收。 没有事先约定的验收条件,Review 就退化成「看起来没报错」。改完之后你要读一遍自己没参与写的实现,比自己写还累 ------ 尤其在存量老工程里。
- 系统没有单一事实源。 新人问「这个列表默认展示几列、什么情况下会记住用户的选择」,答案散落在代码、Wiki、某次群聊和某个已离职同事的记忆里。
这三点加起来,就是所谓的 spec drift(规约漂移):代码在往前跑,对系统的共识却停在原地。
Spec-Driven Development(SDD,规约驱动开发) 的思路很朴素:把「系统应该表现出什么行为」写成结构化的、可评审的、进 Git 的 Markdown,让人类和 AI 在写第一行代码之前先对齐。Spec 是契约,代码只是契约的一种实现。
这里有个容易被忽略的分工,正好能跟那篇「代码地图」文章对上:
| 层 | 回答的问题 | 典型载体 |
|---|---|---|
| Skills / 工作流 | 这件事按什么步骤做 | 技能包、slash command |
| 代码地图 / 全景图 | 代码在哪、链路怎么走、影响面多大 | 可交互 HTML、流程图、时序图 |
| Spec 规约 | 改成什么样算完成、如何验收 | openspec/specs/、changes/ |
三者是互补关系,不是替代关系。地图和技能让 AI「会干活」,规约让人「能拍板」。
二、OpenSpec 是什么
OpenSpec 是 Fission-AI 开源的轻量级规约框架(MIT 协议),给 AI 编码助手加一层「规格层」。它在 README 里写的设计哲学,基本决定了它跟同类工具的气质差异:
text
→ fluid not rigid 流动,而非僵化
→ iterative not waterfall 迭代,而非瀑布
→ easy not complex 简单,而非复杂
→ built for brownfield 为存量工程而生,不只服务新项目
→ scalable from personal projects to enterprises 个人到团队都能用
几个关键特征:
- 工具无关 :支持 30+ 种 AI 编程工具(Claude Code、Cursor、CodeBuddy、Codex、Copilot、Trae、Qoder、Cline 等),本质就是往项目里写 skills / commands /
AGENTS.md,不绑定任何 IDE 或模型。 - 纯 Markdown:没有私有语法、没有数据库、没有服务端,产物全在 Git 里,天然可 diff、可 Code Review。
- 为 brownfield 设计 :双目录模型 ------
specs/描述系统当前 行为(唯一事实源),changes/存放进行中的变更提案。老项目可以一点点把行为沉淀进 specs,不需要一次性补齐文档。 - 无阶段门禁:任何产物随时可以回头改,不要求「先冻结需求再开工」。
三、五分钟跑起来
3.1 安装与初始化
官方提供多种安装方式(以 README 为准):
bash
# macOS / Linux:独立二进制安装
curl -fsSL https://openspec.dev/cli/install.sh | sh
# Windows PowerShell
irm https://openspec.dev/cli/install.ps1 | iex
# 或走包管理器(npm 方式要求 Node.js 22.12.0+)
npm install -g @fission-ai/openspec@latest
brew install openspec
进到项目里初始化,选择你正在用的 AI 工具:
bash
cd your-project
openspec init # 交互式挑选工具
openspec init --tools claude,cursor # 非交互,直接指定
openspec init --tools none # 只要 openspec/ 结构,不写工具文件
初始化之后,项目里多了这些东西:
text
your-project/
├── openspec/
│ ├── config.yaml # 项目级配置:schema、context、rules
│ ├── specs/ # 唯一事实源:系统当前行为,按 capability 组织
│ │ └── <capability>/spec.md
│ └── changes/ # 进行中的变更,一个需求一个文件夹
│ └── <change-name>/
│ ├── .openspec.yaml
│ ├── proposal.md
│ ├── specs/ # delta spec(增量规约)
│ ├── design.md
│ └── tasks.md
├── .claude/skills/ # 或 .cursor/、.codebuddy/ 等,按所选工具生成
└── AGENTS.md # 交给 AI 的接入口
提示 :装完记得重启 IDE / 会话,slash command 是启动时加载的。命令写法因工具而异:Claude Code、CodeBuddy 是
/opsx:apply,Cursor、Copilot 是/opsx-apply,Amazon Q 是@opsx-apply,Codex 走 skill 名$openspec-apply-change。openspec init会打印你所选工具对应的正确写法,别照抄别人的。
默认 profile 是 core,包含 explore / propose / apply / archive。想要更细的工作流(/opsx:new、continue、ff、verify、bulk-archive、onboard),执行:
bash
openspec config profile # 交互式选择
openspec update # 让新工作流生效
3.2 五步闭环
OpenSpec 的每次变更都走同一个环:
text
1 Explore → 2 Propose → 3 Review → 4 Apply → 5 Archive
想清楚 AI 出计划 人改计划 AI 按任务实现 规约吸收变更
↑ │
└──────────────────── 下一个 change ←────────────────────┘
① Explore ------ 先想,别急着写
bash
/opsx:explore 帮我梳理一下「表格列偏好」在这个后台系统里应该怎么设计
这是一个「只思考不落地」的模式:AI 读代码、给选项、质疑你的假设,不写任何代码,也不写产物文件。几十万行的老工程尤其需要这一步 ------ 先让 AI 把影响面摊开,比直接让它动手安全得多。想清楚了,一句「把刚才讨论的提案写出来」就能带着上下文进入 Propose。
② Propose ------ 产出可评审的计划
sql
/opsx:propose add-column-preferences
AI 会创建 openspec/changes/add-column-preferences/ 及四个产物,但到此为止不写业务代码。这就是 OpenSpec 最重要的一道闸:代码出现之前,计划必须先被人看过。
③ Review ------ 改的是文字,不是代码
按官方推荐的顺序读:
proposal.md:问题对不对?范围是不是太大?specs/:最高价值的一份。这些需求,你能不能接受它作为「完成」的定义?tasks.md:任务是否覆盖了所有 spec,又有没有夹带私货?
要改,直接手改 Markdown,或者告诉 AI「spec 少了未登录场景,补上」。此时修改成本是几分钟,等代码铺开再改就是几小时。
④ Apply ------ 按清单施工
sql
/opsx:apply add-column-preferences
官方有两个很实用的建议:开一个新会话 (干净的上下文窗口实现质量更高),以及进度只看 tasks.md 的复选框。中断了、上下文爆了,新开会话再 apply 一次,AI 会从第一个未勾选的任务继续,没有隐藏状态。计划中途发现不对?回去改产物,再继续 apply,不用推倒重来。
⑤ Archive ------ 让规约追上代码
sql
/opsx:archive add-column-preferences
归档做两件事:把 delta spec 合并进 openspec/specs/,把整个 change 文件夹移到 openspec/changes/archive/ 留作审计历史。合并语义是:
text
ADDED → 追加到主 spec
MODIFIED → 替换主 spec 中的同名 Requirement(整块替换)
REMOVED → 从主 spec 删除
RENAMED → 只改名字,FROM:/TO:
至此,openspec/specs/ 重新成为系统当前行为的唯一事实源,下一个 change 从它出发。这是整个闭环的价值所在:规约不会随时间腐烂。
四、四件套产物,各自管什么
默认 schema 叫 spec-driven,四个产物的依赖顺序是:
text
┌─ specs ──┐
proposal ────┤ ├── tasks ── apply
└─ design ─┘
| 产物 | 回答 | 关键约束 |
|---|---|---|
proposal.md |
为什么做、改什么、影响面 | 只讲 why / what,实现细节留给 design |
specs/<capability>/spec.md |
什么算「完成」 | 行为契约,不写类名、框架、步骤 |
design.md |
技术上怎么建 | 技术决策、取舍、风险;条件不触发时可省略 |
tasks.md |
分几步做完 | 带 - [ ] 复选框,进度即状态 |
proposal.md 的模板:
markdown
# Proposal
## Why
<!-- 1-2 句:解决什么问题,为什么现在做 -->
## What Changes
<!-- 具体列出新增/修改/移除,破坏性变更标 **BREAKING** -->
## Capabilities
### New Capabilities
- `<capability-path>`: <这个能力覆盖什么>
### Modified Capabilities
- `<existing-capability-path>`: <哪条需求要变>
## Impact
<!-- 受影响的代码、API、依赖、系统 -->
这里最容易踩的是 Capabilities 命名 :要按「持久的系统行为」命名(如 user-auth、column-preferences),而不是按这次的开发任务命名(如 add-login-button)。官方建议先跑 openspec list --specs 盘点已有能力,复用精确路径,别造近义词重复能力。纯重构、文档、工具链改动没有行为变化,就在 .openspec.yaml 里设 skip_specs: true,别为了过校验硬编一条需求。
delta spec:整个框架的灵魂
规约不是每次全量重写,而是描述相对当前 spec 的增量。规范格式:
markdown
# Spec Delta
## Purpose
<!-- 仅新建 capability 需要,50+ 字符说明这个能力是干什么的 -->
## ADDED Requirements
### Requirement: 保存列偏好
系统 SHALL 在用户调整列显示后持久化该选择,并在下次进入同一列表时恢复。
#### Scenario: 用户勾选新列后重新进入列表
- **WHEN** 用户在「订单列表」勾选「下单时间」列并离开页面
- **THEN** 再次进入「订单列表」时该列保持可见
#### Scenario: 未登录用户
- **WHEN** 未登录用户调整列显示
- **THEN** 选择仅在当前会话生效,且不发起任何写接口请求
几条硬规则,写错会静默失败或归档丢信息:
- Scenario 必须用 恰好 4 个
#(#### Scenario:),写成###不会被正确识别; - 每条 Requirement 至少一个 Scenario,每个 Scenario 至少要有 WHEN/GIVEN 与 THEN;
- 用
SHALL/MUST表达规范性要求,别写「应该」「可以」; - 支持
## ADDED / MODIFIED / REMOVED / RENAMED Requirements四种操作; - MODIFIED 必须贴完整的旧 Requirement 块(含全部 Scenario)再改,只贴片段会在归档时丢掉原有细节;新增关注点而旧行为没变,用 ADDED 而不是 MODIFIED;
- REMOVED 要写清 Reason 和 Migration;
- delta 文件必须叫
specs/<capability-path>/spec.md,写成specs/xxx.md校验和归档都不认。
design.md 与 tasks.md 的模板很克制:
markdown
# Design
## Context
## Goals / Non-Goals
**Goals:**
**Non-Goals:**
## Decisions
## Risks / Trade-offs
markdown
# Tasks
## 1. <阶段名>
- [ ] 1.1 <具体任务>
- [ ] 1.2 <具体任务>
## 2. <阶段名>
- [ ] 2.1 <具体任务>
一个完整小例子
需求:后台订单列表支持用户自定义列,并记住选择。
text
openspec/changes/add-column-preferences/
├── .openspec.yaml
├── proposal.md Why: 用户每次进列表都要重勾列
│ Capabilities: New = column-preferences
│ Modified = list-view(默认列需求变更)
├── specs/
│ ├── column-preferences/spec.md ADDED: 保存列偏好、未登录降级
│ └── list-view/spec.md MODIFIED: 默认列展示规则
│ REMOVED: 旧的「固定 8 列」要求 + Reason/Migration
├── design.md localStorage 优先 + 用户配置接口兜底;评估 SSR 首屏闪动
└── tasks.md 1. 数据层 / 2. UI 层 / 3. 测试(含未登录、跨页面恢复)
openspec/specs/list-view/spec.md 里原本那条「列表固定展示 8 列」的需求,在这次 change 里被 MODIFIED 成「按用户已保存偏好展示,无偏好时回退默认 8 列」。归档后主 spec 自动更新,下一个人接手时看到的是新规则,而不是一段历史对话。
五、CLI 速查
Agent 在工作流里会自己调这些,人掌握常用的几个就够:
| 命令 | 用途 |
|---|---|
openspec init [--tools <ids>] |
初始化项目与工具集成 |
openspec update [--force] [--dry-run] |
刷新 AI 指令文件;升级 CLI 后必跑 |
openspec list / openspec list --specs |
列进行中的 change / 列已有能力 |
openspec show <id> --type spec [--json] |
查看 change 或 spec 全文 |
openspec status --change <name> --json |
查看四件套完成度 |
openspec instructions <artifact> --change <name> --json |
取某个产物的生成指令(Agent 用得多) |
openspec validate [name] --all --strict |
校验结构与格式,--strict 把告警也当错误 |
openspec archive <name> --yes |
合并 delta 并归档 |
openspec view |
终端/页面上看 specs 与 changes 总览 |
openspec config profile / config list |
切换工作流集合、看全局配置 |
openspec schemas / openspec schema fork spec-driven my-workflow |
查看 / 派生自定义 schema |
openspec store / doctor / context / workset |
多仓库规约仓库(beta)相关 |
openspec version / install / completion |
版本、独立二进制安装、补全 |
程序化调用一律加 --json。另外 openspec change / openspec spec 是旧版名词式命令,已标记废弃,新脚本别再用。
六、团队落地:从「个人习惯」到「工程约束」
单人用时,OpenSpec 主要解决「和 AI 对齐」。到了团队,价值会转移到协作与传承,几个可以直接抄的做法:
- PR 里同时提交代码和 change 文件夹。 Reviewer 先读
proposal.md和specs/,再读 diff。评审对象从「这 800 行代码对不对」变成「需求定义对不对 + 实现是否覆盖」,认知负担小一个量级。 - CI 跑
openspec validate --strict。 Scenario 层级写错、需求缺场景、描述过长这类问题在合并前就拦住。 - 用
openspec/config.yaml固化团队约定。 这是最轻的一层定制,把技术栈、架构约束、测试要求注入每次工作流,比如「tasks 必须包含测试任务」「proposal 必须写回滚方案」:
yaml
schema: spec-driven
context: |
技术栈:TypeScript + Vue 3 + Vite
测试:Vitest + Playwright
rules:
tasks:
- 每个 change 的 tasks 必须包含对应的单测/E2E 任务
proposal:
- 涉及接口变更时必须写明兼容与回滚方案
- 约定不够用时再 fork schema。
config.yaml只能在标准流程上叠加,改不了产物结构和文件名。想去掉 design、加自定义章节,用openspec schema fork spec-driven my-workflow派生自己的 schema 与模板。 - 跨仓库需求用 Stores(beta)。 一个功能横跨 API、Web、组件库三个仓库时,把
openspec/放进独立的规约仓库,各代码仓库只读引用,避免「平台团队在 Wiki 写一遍,三个仓库各自理解一遍」。 - 新会话 + 高推理模型。 官方明确建议在干净的上下文窗口里做实现,规划与实现都优先用推理能力强的模型。上下文卫生对这个框架的效果影响很大。
- 归档后再决定要不要留存档。
openspec/changes/archive/保留完整的 proposal / design / tasks / delta。如果以 Git 作为规约历史,可以先提交归档再删掉其中的specs/子目录,减少检索噪音;openspec validate --archived依然可用。
七、和同类方案怎么选
| 方案 | 特点 | 代价 |
|---|---|---|
| 无规约(纯 prompt) | 零成本、起步最快 | 需求漂移、结果不可预测、无法 Review |
| GitHub Spec Kit | 流程完备、阶段清晰 | 阶段门禁偏重、Markdown 产物多、依赖 Python 环境 |
| Kiro | IDE 内体验完整 | 绑定自家 IDE 与模型 |
| OpenSpec | 轻量、流动、工具/模型无关、面向存量工程 | 靠自觉与流程约束,不强制「先写规约再写代码」 |
这是 OpenSpec 官方 README 给出的对比口径,我基本认同:它不试图做最严谨的那一个,而是做最容易在真实老项目里坚持下去的那一个。规约框架最大的失败模式从来不是不够强,而是团队用两周就嫌麻烦弃用了。
顺带说回开头那篇「代码地图」文章。两者完全可以串成一条流水线:
text
代码地图 / Skills → 摸清工程结构与影响面
↓
proposal.md → 把影响面写进 Impact,划定能力边界
↓
delta spec → 定义「改成什么样算完成」与验收场景
↓
tasks.md + apply → 按清单施工,代码地图复核改动范围是否越界
↓
archive → 行为共识回写主 spec,成为下一轮的起点
地图让 AI 知道「动哪里」,规约让人确定「凭什么算对」。前者是可视化,后者是契约,缺哪一半都撑不起「敢上线」。
八、踩坑清单
#### Scenario写成###:validate会静默不认,归档后主 spec 缺场景。- 把 spec 写成实现方案 :出现类名、框架名、步骤编号就该挪到
design.md/tasks.md。判断标准是:实现可以换掉,但外部可见行为不变的内容,不配写进 spec。 - capability 按任务命名 :
add-login-button用两次就废了,应该是user-auth这种能长期拥有需求的行为边界。 - 零 delta 的 change 被 validate 拒绝 :纯重构/文档改动设
skip_specs: true,不要硬凑需求。 - MODIFIED 只贴片段:归档是整块替换,会丢历史场景。复制完整 Requirement 块再改。
- 忘了
openspec update:升级 CLI 后,工具目录里的 skills / commands 还是旧版,表现是「新命令敲了没反应」。 - 跨工具照抄命令 :
/opsx:apply、/opsx-apply、@opsx-apply、$openspec-apply-change是同一工作流在不同工具里的不同拼法。 - apply 时上下文没清:把 Explore 阶段一大堆探索对话带进实现,质量明显下降。开新会话。
- archive 太晚或太早 :任务没勾完就归档,规约会先于代码变成「事实」;change 长期不归档,
specs/就不再是事实源。代码与 change 文件夹一起提交,合并后尽快归档。
九、小结
AI 编码把「写代码」的成本压得很低,于是瓶颈转移到了**「对齐」和「验收」**。OpenSpec 没有造新语言、没上平台,只是用几个 Markdown 文件加一套五步闭环,把「需求 → 计划 → 实现 → 归档」重新变成一条可评审、可追溯、可 diff 的工程链路。
对存量几十万行的项目,它的好处尤其实在:不用推倒重来,可以从下一个需求开始,一次沉淀一个 capability。跑上三五个 change 之后,openspec/specs/ 就会变成这个项目里最值得新人先读的文档 ------ 而且它永远和代码同步。
参考资料
- OpenSpec GitHub:github.com/Fission-AI/...
- OpenSpec 官方文档:openspec.dev/docs
- Quickstart:openspec.dev/docs/quicks...
- spec-driven schema:openspec.dev/docs/schema...
- CLI 参考:openspec.dev/docs/cli
- 写作灵感:微信公众号「bugstack 虫洞栈」《Spec 规约编程,可视化 Skills 技能》 mp.weixin.qq.com/s/XPgeCPpaM...
本文基于 2026 年 11 月的官方文档整理,命令与产物格式请以项目 README / docs 的最新版本为准。