OpenSpec 实战指南:从工作流到落地
为什么需要 OpenSpec
在 AI 编程时代,真正的难点往往不是“AI 会不会写代码”,而是“AI 能不能稳定写出你真正想要的代码”。问题往往不在模型能力,而在于需求、边界、约束和验收标准没有被稳定地表达出来。当意图没有沉淀为可复用的工程事实,AI 就只能在模糊上下文里“猜”。
OpenSpec 解决的正是这个问题。它的核心思想可以概括成一句话:先对齐规范,再生成代码(align before code)。与其把 AI 当成一个只看提示词的即时执行器,不如把它放进一套可追溯、可迭代、可沉淀的规范工作流里。
OpenSpec 既不是重量级流程平台,也不是传统瀑布式文档系统。从实践上看,它更像一套轻量的仓库内协议:
- 用
specs/保存系统当前已经成立的事实; - 用
changes/保存本次准备引入的未来变化; - 用 proposal、spec、design、tasks 把“为什么改、改成什么、怎么实现”拆开表达;
- 用 sync 和 archive 把一次变更逐步沉淀为下一次变更的上下文。
它的设计哲学,基本可以概括为四点:
- Fluid not rigid:规范是活文档,不是一次性文书。
- Iterative not waterfall:支持边做边校正,而不是阶段锁死。
- Easy not complex:只保留真正影响实现质量的文档。
- Brownfield-first:优先服务已有项目,而非只适配全新仓库。
这也是 OpenSpec 最值得重视的地方:它不要求团队先建立一套庞大的流程,再允许 AI 参与;它的目标恰恰相反——用最小的文档负担,换取更稳定的人机对齐质量。
命令分工与推荐工作流
如果只先记一条主线,记住下面这条工作流就够了:
1 | |
这条主线概括了 OpenSpec 中较常见的一种节奏:
- 先建立工作目录。
- 再为一次变更生成工件。
- 在实现前完成人类审查。
- 随后按任务清单实现代码。
- 最后按需校验、沉淀并归档。
安装与初始化
1 | |
初始化后,项目里通常会出现一个 openspec/ 工作目录:
1 | |
有些 AI 工具还会在仓库根目录维护自己的配置文件,例如 AGENTS.md 或 CLAUDE.md。这类文件更适合视为AI 工具层配置,而不是 OpenSpec 自身的流程工件。它们可以和 openspec/ 共存,但职责不同,后文会单独说明。
核心命令怎么分工
| 命令 | 作用 | 何时使用 | 典型产出 |
|---|---|---|---|
openspec init |
初始化工作目录 | 第一次接入 OpenSpec | openspec/ 基础骨架 |
/opsx:new |
创建 change 容器并给出首个工件模板 | 想逐步推进时 | 一个新的 change 目录 |
/opsx:continue |
按依赖顺序继续生成下一个工件 | new 之后继续补齐工件 |
单个新增工件 |
/opsx:propose |
一次生成到可实现状态 | 需求比较清楚,想快速进入审查 | proposal.md、specs/、design.md、tasks.md |
/opsx:ff |
快速生成到 apply-ready | 与 propose 类似,强调更快进入实现 | 同上 |
/opsx:explore |
通常用于调研、澄清、比较方案,可按需回写工件 | 需求不清、方案摇摆、边界不明 | 分析结论,可选的工件更新 |
/opsx:apply |
按任务清单实现代码 | 工件已审查通过后 | 代码变更、任务勾选 |
/opsx:verify |
扩展工作流中的一致性核对动作 | 希望在归档前补充核查时 | completeness / correctness / coherence 报告 |
/opsx:sync |
扩展工作流中的 spec 合并动作 | 想在归档前单独预览或执行 spec 合并时 | 更新后的主 Specs |
/opsx:archive |
默认工作流里的收口动作:验证变更、更新主 Specs 并归档 change | 一个变更收口时 | 更新后的主 Specs + 归档目录 |
两个 Profile:默认命令集与扩展命令集
在理解命令分工之前,有一个关键背景必须先说清楚:OpenSpec 的命令集分为两个 profile,它们决定了你能用哪些命令。
core profile(默认):安装后开箱即用,只包含 4 个命令:
1 | |
这是大多数用户的日常路径。archive 是唯一的收口动作,它会在归档时自动处理 spec 合并。
expanded workflow(扩展命令集):需要手动切换,才能解锁额外命令:
1 | |
切换方式:执行 openspec config profile,选择 workflows,再执行 openspec update。
这两个 profile 的区别,就是本文后续提到"默认工作流"和"扩展工作流"时所指的实质差异。如果你只用默认 core profile,sync 和 verify 命令根本不会出现在你的工具集里;它们是扩展命令集里的可选动作,专门服务于需要更精细控制的场景。但这并不意味着 core 用户永远用不到 openspec/specs/——archive 在收口时会自动把 Delta Specs 合并进主 openspec/specs/,core 用户每次归档都在更新它,只是这个合并动作由 archive 内部完成,不需要单独调用 sync。
更稳妥的理解:命令是动作,不是关卡
OpenSpec 的强项不在于把你锁进固定阶段,而在于把一次变更拆成几个可回退、可重做、可校正的动作:
propose负责形成可讨论、可审查的变更表达;explore通常用于澄清不确定性、调查问题和比较方案;apply负责把已确认的规范转成实现;verify(扩展命令)负责检查"做出来的东西"是否真的对齐"说清楚的东西";archive负责完成一次变更的收口,内部会自动处理 spec 合并;sync(扩展命令)则可以把这个合并动作单独提前执行。
因此,更稳妥的理解是:默认主流程通常围绕 propose → explore/apply → archive 展开;如果你希望在归档前补充核查或单独处理 spec 合并,则需要先切换到扩展命令集,再引入 verify 与 sync。如果你把这条主线记住,后面的大部分细节都会自然落位。
核心概念
要真正把 OpenSpec 用顺,需要抓住的其实只有三组概念:当前事实与未来变化、变更工件、沉淀动作。
1. specs/:系统当前事实
openspec/specs/ 存放的是系统已经成立的行为契约。它回答的问题是:
这个系统现在已经具备什么能力?
典型写法会使用 SHALL / MUST 一类语义约束词,再配合 Gherkin 风格场景描述行为边界:
1 | |
这里描述的不是“打算做什么”,而是“已经应该成立的行为”。因此,specs/ 更像长期知识库,而不是临时草稿区。
2. changes/:本次准备引入的未来变化
openspec/changes/ 不是单个文件,而是一组变更目录。每一个 change 目录代表一次独立的需求、重构或能力调整。对应地,它回答的问题是:
这次准备把系统改成什么样?
可以把两者记成一句话:
specs/= current truthchanges/= proposed future state
这也是 OpenSpec 区别于很多“需求文档 + 代码实现”松散流程的关键:它把当前事实和未来变更明确分层了。
3. change 目录里的四类工件
一个典型的 change 目录通常长这样:
1 | |
四类工件的职责分别是:
| 工件 | 作用 | 重点问题 |
|---|---|---|
proposal.md |
说明为什么改、改什么、影响什么 | Why / What / Impact |
specs/.../spec.md |
把需求翻译成可验证的行为契约 | 系统应该如何表现 |
design.md |
记录实现方案、技术权衡、关键设计 | 准备怎么做 |
tasks.md |
把实现拆成 AI 可执行任务清单 | 先做什么,后做什么 |
一个常见误解,是把 proposal、spec、tasks 当成彼此重复的文档。实际上,它们承担的是不同语义:
1 | |
OpenSpec 之所以通常先产出 proposal,再产出 spec 和 tasks,不是因为 spec 不重要,而是因为没有先对齐方向,再精确描述行为,往往只会得到“方向错误但表达严谨”的产物。
4. Delta Specs:只描述这次变更的增量
change 目录里的 specs/ 不是主 Specs 的完整拷贝,而是增量规范。它通常围绕三类动作展开:
ADDED:新增需求MODIFIED:修改需求REMOVED:删除需求
这种设计有两个直接好处:
- 一次变更的边界更清楚;
- AI 更容易理解“这次到底改哪里”。
5. sync 与 archive 的关系,以及主 specs/ 是怎么更新的
这是全文里最容易混淆的一组动作,也是最值得说清楚的地方。
先回答一个根本问题:什么命令会把内容写入 openspec/specs/(主 Specs)?
答案是 sync 和 archive,而且两者做的是同一件事的不同触发时机:把 change 目录里的 Delta Specs(增量规范)合并进主 openspec/specs/ 目录。具体来说,Delta Specs 里的 ADDED 段落会追加到主 Specs,MODIFIED 段落会替换主 Specs 中对应的需求,REMOVED 段落会从主 Specs 中删除对应内容。
两个命令的区别在于:
-
/opsx:sync(扩展命令集):只做合并,不归档。执行后,Delta Specs 的内容被合并进openspec/specs/,但 change 目录本身保持不动,仍然是活跃状态。适合长期运行的变更、多个并行变更需要共享最新 specs 基线,或者想在归档前单独预览合并结果的场景。 -
/opsx:archive(默认命令集):先合并,再归档。执行时,如果 Delta Specs 还没有被 sync 过,archive 会先提示你确认是否执行合并(或自动执行),把增量内容写入openspec/specs/;然后再把整个 change 目录(包含 proposal、design、tasks、Delta Specs 等所有工件)移动到openspec/changes/archive/YYYY-MM-DD-<name>/,作为历史记录保留。
因此,archive 之后,openspec/specs/ 里存放的是合并了本次变更的最新主 Specs;而 openspec/changes/archive/ 里存放的是整个 change 目录的完整历史快照,包括 Delta Specs 在内的所有工件都会随之归档。两个目录各司其职,不会混淆。
用一句话总结:sync 是把"增量合并进主 Specs"这个动作单独提前执行;archive 是在收口时自动完成这个合并,然后再把整个 change 目录移入历史存档。 因此,core profile 用户同样会更新 openspec/specs/——只是这个动作由 archive 在收口时一并完成,而不需要单独调用 sync。
下图展示了两条路径下各目录的文件流向:
1 | |
core profile 路径(/opsx:archive):Delta Specs → 合并进 openspec/specs/ → 整个 change 目录移动到 archive/,一步到位。
expanded workflow 路径(/opsx:sync + /opsx:archive):先单独执行 sync 把 Delta Specs 合并进 openspec/specs/,change 目录保持活跃;之后再执行 archive 完成归档(此时 sync 已做过,archive 跳过合并步骤,直接移动目录)。
6. config.yaml:可选的项目配置入口
openspec/config.yaml 不是强制前置条件,但它通常值得填写。官方文档里,它不只是项目上下文入口,还常用于补充 schema 与工件规则。常见用途包括:
context:项目背景、技术栈、架构模式、领域信息;rules:团队规则、接口约束、错误处理约定等;schema相关配置:指定当前项目使用哪套工作流 schema(即工件类型、依赖顺序、生成模板)。注意:schema 定义的是"生成什么工件、按什么顺序",而不是"工件内容的 Markdown 格式"——specs、proposal、design、tasks等 md 文件的内部结构(如## Requirements、### Requirement:等标题层级)在当前版本是硬编码的,无法通过 schema 配置修改。如果你想自定义工作流(比如增减工件类型、调整依赖关系),可以用openspec schema init或openspec schema fork创建自定义 schema,但工件内容的格式约定仍然固定。
例如:
1 | |
它的作用并不神秘:为 OpenSpec 工作流提供结构化、稳定、可复用的项目配置。如果项目简单,AI 也许能从代码库自行推断出足够信息;如果项目复杂,config.yaml 往往能明显改善 proposal 或其他工件草案的质量。
7. 目录结构应该怎么读
把前面的概念合起来,OpenSpec 目录可以这样理解:
1 | |
如果只用一句话总结:specs/ 是沉淀层,changes/ 是工作层,config.yaml 是上下文层。
完整实操步骤(以“新增下单接口”为例)
把前面的概念放进一次具体变更里,整条链路就会清楚很多。下面用一个常见的后端需求演示一遍:新增 POST /orders 接口,下单时同步扣减库存,库存不足返回 409。
步骤一:初始化并补充上下文
先执行:
1 | |
如果项目已经比较复杂,建议补充 config.yaml。你可以让 AI 先生成初稿,再手动审查:
1 | |
这一步的目标不是“写文档”,而是减少后续 proposal 的理解偏差。
步骤二:创建 change
如果你想一步到位生成主要工件,可以直接使用 /opsx:propose:
1 | |
生成后的 change 目录可能类似这样:
1 | |
如果你更想逐步控制,也可以先 /opsx:new,再通过 /opsx:continue 一件件补齐。
步骤三:审查工件
这是整条链路中最关键的人类审查节点。在这里不要急着进入实现,而是先问四个问题:
proposal.md是否把范围和影响说清楚了?spec.md是否覆盖了关键场景和边界情况?design.md是否解释了关键技术选择?tasks.md是否足够具体,能被 AI 顺序执行?
例如,一个简洁的 proposal.md 可以长这样:
1 | |
对应的增量 spec 可以这样表达:
1 | |
而 tasks.md 则应该把实现拆成可执行步骤:
1 | |
如果这里发现问题,先修工件,再进入实现。不要把这一步省略掉。
步骤四:执行实现
确认工件可接受之后,再进入 apply:
1 | |
或者直接用自然语言告诉 AI:
1 | |
这时 AI 的工作重点,不再是“理解需求”,而是“按已确认的任务清单落地实现”。这也正是前面先补齐 proposal、spec、tasks 的价值。
步骤五:在需要时补充实现对账
如果你希望在归档前额外做一次结构化核对,可以执行:
1 | |
verify 更适合被理解为扩展工作流里的核对动作。它关注的不是单纯"测试跑没跑",而是至少三个维度:
- Completeness:任务与需求是否完整落地;
- Correctness:实现是否偏离 spec;
- Coherence:实现是否违背设计或项目既有模式。
如果报告中还有关键问题,先修掉,再进入下一步。
/opsx:verify 深度解析
verify 是 OpenSpec 工作流中最容易被低估的命令。它不是一个简单的"跑测试"按钮,而是一次结构化的人机协作对账——让 AI 扮演审计员,系统性地检查"你说要做的"和"你实际做的"之间是否存在偏差。
什么时候应该用 verify
verify 的核心使用时机是:apply 完成之后、archive 之前。
1 | |
更具体地说,以下几种场景特别适合触发 verify:
- AI 完成了较大范围的实现:任务清单超过 5 项,或涉及多个模块的联动;
- 实现过程中出现过反复修改:apply 期间有过多次迭代,担心局部修复引入了新的不一致;
- spec 在 apply 过程中被更新过:规范和实现的同步状态不确定;
- 准备归档前的最后一道防线:希望在 change 正式成为历史记录之前,做一次完整性确认。
反过来,以下情况可以酌情跳过 verify:
- 极小的改动(如修改一行注释、调整一个配置值);
- 紧急热修复,时间窗口极短;
- 实验性变更,本来就不打算归档。
但即便如此,verify 本身执行很快,不会阻塞后续操作,跑一遍通常利大于弊。
三个验证维度详解
verify 的核心价值在于它把"实现是否正确"这个模糊问题,拆解成三个可独立评估的维度:
Completeness(完整性)
所有任务是否完成?所有需求是否落地?所有场景是否覆盖?
这一维度检查的是数量层面的覆盖:
tasks.md中的所有复选框是否已勾选;- spec 中定义的每一个 Scenario 是否都有对应的实现;
- 是否存在"写了一半"的功能——代码存在但逻辑不完整。
典型问题:tasks.md 里有 8 项任务,但 apply 只完成了 6 项,剩余 2 项被遗漏。
Correctness(正确性)
实现是否符合 spec 的意图?边界情况是否被正确处理?错误状态是否与 spec 定义一致?
这一维度检查的是语义层面的对齐:
- 接口返回码是否与 spec 中定义的一致(如库存不足应返回 409,而非 400);
- 边界场景的处理逻辑是否与 spec 描述的行为吻合;
- 错误处理路径是否覆盖了 spec 中所有的异常场景。
典型问题:spec 定义"库存服务超时返回 503,订单不创建",但实现中超时时仍然创建了订单。
Coherence(一致性)
设计决策是否体现在代码结构中?命名约定是否与 design.md 一致?模式是否保持统一?
这一维度检查的是风格层面的统一:
典型问题:design.md 说明使用 CSS 变量管理主题色,但实现中直接硬编码了颜色值。
verify 的输出格式
verify 会输出一份结构化报告,问题按严重程度分为三级:
1 | |
三个问题级别的含义:
| 级别 | 含义 | 是否阻塞归档 |
|---|---|---|
| CRITICAL | 必须修复,实现与规范存在根本性偏差 | 是 |
| WARNING | 建议修复,存在潜在风险或不一致 | 否 |
| SUGGESTION | 可选改进,不影响功能正确性 | 否 |
verify 失败后的正确处理顺序
发现问题后,处理顺序至关重要:
1 | |
这个顺序的关键在于:如果只修代码不修规范,下次 AI 再读取过时的 spec,旧问题会重新出现。这就是 spec drift 的典型成因。
与 profile 的关系
需要注意的是,/opsx:verify 属于 OpenSpec 的扩展工作流,在默认的 core profile 下不一定开箱即用。如果你发现命令不可用,可以通过以下方式启用:
1 | |
这也解释了为什么文章前面把 verify 描述为"扩展工作流"里的动作——它是可选的增强层,而非默认主流程的强制步骤。
/opsx:explore 深度解析
explore 是 OpenSpec 命令族中最"轻"的一个——它不创建任何文件,不修改任何目录结构,甚至不要求你已经有一个 change。它的全部价值在于:在你还没想清楚之前,给你一个有结构的思考空间。
explore 到底做了什么
当你执行:
1 | |
AI 会做以下几件事:
- 读取项目上下文:加载
config.yaml、现有的specs/、以及项目结构 - 围绕你的问题展开分析:不是直接给答案,而是列出可能的方案、各自的 trade-off、以及需要进一步澄清的问题
- 不产生任何文件输出:整个过程是纯对话式的,所有内容只存在于当前会话中
这和直接问 AI "帮我想想退款怎么做"有什么区别?区别在于 explore 会把项目的现有 spec 和架构约束纳入思考范围。它不是在真空中头脑风暴,而是在你的项目上下文里做有约束的探索。
什么时候应该用 explore
1 | |
explore 与 propose 的关系
一个常见的工作模式是 explore → propose:
1 | |
explore 帮你收窄选项,propose 帮你正式启动。两者之间没有技术上的依赖关系——你完全可以跳过 explore 直接 propose——但在复杂场景下,先 explore 再 propose 能显著减少后续返工。
explore 的局限性
最大的局限是没有持久化。如果你在 explore 中得出了重要结论,需要手动记录下来,或者立即转入 propose/new 流程。一旦会话结束,explore 的分析结果就消失了。
这是一个有意的设计选择:explore 的定位就是"零成本试探",如果它也要创建文件和目录,就失去了轻量级的优势。
/opsx:propose 深度解析
propose 是 OpenSpec core profile 下的默认启动命令。如果说 explore 是"想清楚要不要做",那 propose 就是"决定做了,一步到位把规划文档全部生成出来"。
propose 的一站式行为
当你执行:
1 | |
AI 会在一次操作中完成以下所有动作:
- 创建 change 目录:
openspec/changes/<change-id>/ - 生成
proposal.md:描述变更的 Why、What、Impact - 生成 Delta Specs:
specs/<capability>/spec.md,用 ADDED/MODIFIED/REMOVED 标记增量变化 - 生成
design.md:技术方案和架构决策 - 生成
tasks.md:可执行的实现任务清单
换句话说,propose = new + 一次性生成所有工件。这是它和 expanded profile 下 new + ff 组合的核心区别。
propose 与 new + ff 的对比
| 维度 | /opsx:propose |
/opsx:new + /opsx:ff |
|---|---|---|
| 所属 profile | core(默认) | expanded(需切换) |
| 操作步骤 | 一步完成 | 两步:先 new 创建骨架,再 ff 填充工件 |
| 中间审查机会 | 无,一次性全部生成 | 有,new 之后可以先审查再 ff |
| 适用场景 | 需求明确,想快速启动 | 需求复杂,想分步控制 |
| change 命名 | AI 自动生成 | new 时可以手动指定 |
在实际使用中,大多数场景用 propose 就够了。只有当你需要在创建 change 骨架后、生成工件前做一些手动调整(比如先修改 config.yaml 的上下文信息),才需要切换到 expanded profile 使用 new + ff。
propose 生成工件的质量控制
propose 一次性生成的工件质量,高度依赖两个因素:
-
你的 prompt 质量:越具体的需求描述,生成的工件越精准。“加个下单接口"和"新增 POST /orders 接口,下单时同步调用库存服务扣减库存,库存不足返回 409”,生成结果差异巨大。
-
config.yaml的完整度:如果你在 config 中声明了技术栈、架构模式、命名规范,AI 生成的 design 和 tasks 会自动遵循这些约束。反之,它只能靠猜测。
这也是为什么前面"步骤一"强调要先补充 config.yaml——它不是可选的锦上添花,而是直接影响 propose 输出质量的关键输入。
propose 的幂等性问题
需要注意的是,propose 不是幂等的。每次执行都会创建一个新的 change 目录。如果你对第一次 propose 的结果不满意,不要再执行一次 propose(这会创建第二个 change),而应该直接修改已有 change 中的工件文件。
/opsx:new 与 /opsx:continue 深度解析
new 和 continue 是 OpenSpec expanded profile 下的一对搭档命令。如果说 propose 是"一键生成所有",那 new + continue 就是"先搭骨架,再逐件填充"。
new:只建目录,不生成工件
当你执行:
1 | |
AI 只做一件事:创建 change 的文件夹结构和元数据。
1 | |
注意这里没有 proposal.md、design.md、tasks.md。这些工件需要通过后续的 continue 或 ff 来生成。
这个设计的意图是:给你一个干净的起点,让你在生成工件之前有机会做准备工作。比如:
- 先手动调整
config.yaml,补充这次变更特有的上下文 - 先在
specs/目录下手动放入一些参考文档 - 先和团队讨论确认方向,再让 AI 生成正式工件
continue:按依赖链逐步生成
continue 的行为是每次只生成依赖链中的下一个工件。OpenSpec 的工件依赖关系是:
1 | |
所以当你连续执行 continue 时,生成顺序是:
1 | |
每一步之间,你都有机会审查和修改上一步的输出。这是 continue 相比 propose 的核心优势:你可以在每个工件生成后介入,确保下一个工件基于正确的输入。
continue 的审查-修正循环
一个典型的 continue 工作流:
1 | |
这种"生成 → 审查 → 修正 → 继续"的循环,特别适合以下场景:
- 需求复杂度高:一次性生成容易遗漏细节
- 团队协作:每个工件需要不同角色审查
- 学习阶段:想逐步理解 OpenSpec 的工件是怎么关联的
ff:批量生成的快捷方式
/opsx:ff(fast-forward)是 continue 的批量版本:一次性生成所有剩余工件。
1 | |
ff 和 propose 的区别在于:ff 需要先有一个通过 new 创建的 change,而 propose 是从零开始。如果你已经通过 new 创建了 change 并做了一些手动准备,ff 可以帮你快速补齐剩余工件。
ff 也支持从中间状态继续。比如你已经通过 continue 生成了 proposal 和 specs,此时执行 ff 只会生成 design 和 tasks。
三种启动模式的选择指南
1 | |
/opsx:apply 深度解析
apply 是 OpenSpec 工作流中从规划到实现的转折点。前面的所有命令(explore、propose、new、continue、ff)都在生成"说明书",而 apply 是第一个真正"动手写代码"的命令。
apply 的核心行为
当你执行:
1 | |
AI 会做以下事情:
- 定位当前活跃的 change:找到
changes/目录下未归档的 change - 读取
tasks.md:这是 apply 的核心输入,它按照任务清单逐项执行 - 逐项实现:按照 tasks 中的 checkbox 列表,依次编写代码
- 标记完成:每完成一个任务,在
tasks.md中将对应的[ ]改为[x]
这个过程的关键在于:apply 不是"理解需求后自由发挥",而是"严格按照已审查的任务清单执行"。这也是为什么前面反复强调 tasks.md 的质量——它直接决定了 apply 的输出质量。
apply 的输入优先级
apply 在实现时会参考多个工件,但优先级不同:
1 | |
如果 tasks.md 和 spec.md 之间存在矛盾,apply 会优先遵循 tasks.md。这是一个重要的设计决策:tasks 是经过人类审查确认的执行计划,spec 是 AI 生成的需求描述,前者的可信度更高。
apply 的中断与恢复
apply 最实用的特性之一是支持中断恢复。如果 AI 在执行到第三个任务时因为上下文窗口耗尽或其他原因中断,你只需要再次执行:
1 | |
AI 会检查 tasks.md 中哪些任务已经被标记为 [x],然后从第一个未完成的任务继续。这个机制依赖于 tasks.md 中的 checkbox 状态,所以不要手动修改已完成任务的标记,除非你确实想让 AI 重新实现某个任务。
apply 与直接让 AI 写代码的区别
你可能会问:为什么不直接告诉 AI “按照这个需求写代码”,而要走 apply 这条路?
区别在于上下文的结构化程度:
| 维度 | 直接让 AI 写代码 | /opsx:apply |
|---|---|---|
| 上下文来源 | 你的 prompt + AI 的理解 | tasks + specs + design + config |
| 任务粒度 | 一整块需求 | 拆分好的 checkbox 列表 |
| 进度追踪 | 无 | tasks.md 中的 checkbox 状态 |
| 中断恢复 | 需要重新描述上下文 | 自动从断点继续 |
| 一致性保证 | 依赖 AI 的记忆 | 依赖持久化的工件文件 |
简单来说,apply 把"AI 写代码"这件事从一次性的对话行为,变成了可追踪、可恢复、可审计的结构化过程。
apply 的常见陷阱
-
tasks 粒度过粗:如果一个 task 是"实现整个订单模块",apply 的输出质量会急剧下降。好的 task 应该是"创建 CreateOrderRequest DTO"这样的粒度。
-
跳过审查直接 apply:如果 proposal 或 spec 有问题,apply 会忠实地按照错误的规划去实现。垃圾进,垃圾出。
-
在 apply 过程中手动改代码:apply 依赖 tasks.md 的状态来判断进度。如果你在 apply 过程中手动修改了代码但没有更新 tasks.md,可能导致 AI 重复实现或跳过某些步骤。
/opsx:sync 深度解析
sync 是 OpenSpec 工作流中一个容易被忽视但非常实用的命令。它做的事情很简单:把 change 中的 Delta Specs 合并到主 specs/ 目录,但不归档 change。
sync 与 archive 的区别
这是理解 sync 的关键:
1 | |
两者都会执行 spec 合并,但 sync 之后 change 仍然是"活跃"的,你可以继续修改它。而 archive 之后 change 就"关闭"了。
什么时候应该用 sync 而不是 archive
sync 的典型使用场景是长周期变更:
1 | |
sync 的合并机制
sync 不是简单的文件复制。它的合并逻辑基于 Delta Specs 中的语义标记:
1 | |
这意味着 sync 是一个有意图的合并操作,而不是盲目的文件覆盖。如果 Delta Spec 中标记了 MODIFIED: Requirement X,sync 会找到主 spec 中的 Requirement X 并替换它,而不是覆盖整个文件。
sync 后的状态
执行 sync 后,项目状态变成:
1 | |
change 中的 Delta Spec 文件不会被删除——它们会一直保留到你执行 archive 为止。这意味着你可以多次 sync(比如每完成一个阶段 sync 一次),每次 sync 都会把最新的 Delta Spec 状态合并到主 specs。
/opsx:archive 深度解析
archive 是 OpenSpec 工作流的终点命令——它标志着一次变更的正式完结。但"归档"这个词容易让人误解为"删除"或"隐藏",实际上 archive 做的事情比这丰富得多。
archive 的完整行为
当你执行:
1 | |
AI 会按顺序执行以下操作:
- 合并 Delta Specs:将
changes/add-create-order/specs/中的增量规范合并到主openspec/specs/(和 sync 相同的逻辑) - 移动 change 目录:将整个
changes/add-create-order/移动到changes/archive/add-create-order/ - 保留完整历史:归档后的 change 包含所有原始工件(proposal、design、tasks、specs),作为"为什么这么改"的历史记录
archive 后的目录状态
1 | |
archive 的不可逆性
需要特别注意的是,archive 是一个有副作用的操作:
- Delta Specs 已经合并到主 specs,这个合并不会自动回滚
- change 目录已经移动到 archive,不再出现在活跃 change 列表中
- 如果你想"撤销"一次 archive,需要手动把 change 从 archive 移回 changes,并手动回滚主 specs 的变化
这也是为什么建议在 archive 之前先执行 verify——确保实现和规范一致后再归档,避免把有问题的 spec 合并到主线。
bulk-archive:批量归档
当你有多个已完成的 change 需要归档时,可以使用:
1 | |
bulk-archive 会自动发现所有可归档的 change,并按顺序逐个归档。它的关键行为是:
- 按创建时间排序:先创建的 change 先归档,确保 spec 合并顺序正确
- 冲突检测:如果两个 change 修改了同一个 spec 的同一部分,bulk-archive 会在冲突处停下来,要求你手动解决
- 原子性:每个 change 的归档是独立的,一个 change 归档失败不会影响其他 change
archive 与 Git 的关系
一个常见的疑问是:archive 和 git commit 是什么关系?
答案是:它们是正交的。archive 操作的是 OpenSpec 的文件结构,git commit 操作的是版本控制。一个推荐的实践是:
1 | |
把 archive 的结果作为一个独立的 commit,这样在 git 历史中也能清晰地看到"这次 commit 归档了哪个 change"。
步骤六:在需要时单独同步主 Specs
1 | |
这一步会把 changes/add-create-order/specs/ 中的 Delta Specs 合并进主 openspec/specs/。它更适合在你希望不归档、只先处理 spec 合并时使用。重点不是整文件覆盖,而是按意图更新主规范。
步骤七:归档本次变更
1 | |
归档意味着这次变更结束了,但它不会抹去上下文。相反,整个 change 目录会进入 changes/archive/,成为以后回溯“为什么这么改”的历史依据;与此同时,future-state specs 也会在归档过程中并入主 specs/,成为新的 current truth。
到这里,一个完整的 change 才算真正闭环。若采用默认主流程,可以理解为:
1 | |
如果团队需要更强的中间检查,也可以在归档前补充:
1 | |
人机交互边界与修正工作流
要把 OpenSpec 用稳,关键不在于第一次 propose 是否成功,而在于发现问题后能否按正确顺序回退和修正。最常见的问题不是命令不会用,而是:
- proposal / spec 有问题时该改哪里;
- apply 之后发现结果不对,应该先改代码还是先改 spec;
- 人到底能不能直接改这些 Markdown 工件。
原则一:人负责意图与判断,AI 负责结构化与执行
在 propose 阶段,更合理的协作边界是:
- 人类提供目标、上下文、约束、边界和取舍;
- AI把这些信息整理成 proposal、spec、design、tasks;
- 人类再审查这些工件是否真的表达了自己的意图。
因此,proposal、spec、design、tasks 都不是神圣不可改的中间产物,而是可迭代的人机协作载体。
原则二:发现问题时,先分清是“实现问题”还是“规范问题”
apply 之后如果出现缺陷,根因通常只有两类:
1 | |
这个区分非常重要,因为它直接决定修复顺序。
三种常见修正路径
1. 用 /opsx:explore 先澄清,再决定是否回写工件
当问题本质上是“需求没想清楚”或“方案还在摇摆”时,/opsx:explore 是很自然的入口。官方文档对它的强调重点是思考、调研、澄清和收敛问题,因此更稳妥的理解是:它通常先服务于分析与澄清,而不是把它当作默认实现入口。
典型场景包括:
- 需求范围需要收缩或扩展;
- 某个边界场景在 proposal / spec 中表达得不够准确;
- 技术方案需要先比较几种实现路径;
- apply 前审查发现工件之间存在不一致。
如果 explore 的结论稳定了,再让 AI 把结论落实到 proposal、spec、design、tasks 中。
2. 用自然语言直接更新特定工件
如果你已经很明确知道问题在哪里,也可以直接告诉 AI 修改具体文件,例如:
1 | |
这种方式的优点是精确,缺点是你需要主动关注跨文件一致性。
3. 人工直接编辑 Markdown
这也是允许的。OpenSpec 的工件本质上就是普通 Markdown 文件,你完全可以手工修改它们。更稳妥的做法是:
- 先明确你改动了哪些工件;
- 再让 AI 检查 proposal / spec / design / tasks 是否仍然一致;
- 在实现后用
verify再做一次对账。
Apply 后的正确修复顺序
如果问题来自实现偏离 spec,那就直接修代码即可。
但如果问题来自spec 本身有缺陷,顺序必须反过来:
1 | |
例如,下单接口最初只定义了“库存充足”和“库存不足”两个场景。后来测试发现:库存服务超时时,系统仍然创建了订单。此时真正的问题,不一定是代码写错,而可能是 spec 根本没有定义“库存服务超时”这个行为。
这类情况下,先补 spec 才有意义:
1 | |
然后再去更新 design、tasks 和实现代码。原因很简单:spec 是 AI 后续工作的标准答案。如果你只改了代码,却不改 spec,那么下次 AI 再读规范时,仍然会基于错误前提继续工作。这就是典型的 spec drift。
一条更实用的修正循环
把上面的原则浓缩起来,更实用的修正循环其实很简单:
1 | |
OpenSpec 的价值,不只是“帮助 AI 开工”,更在于帮助你在问题暴露后,仍然用同一套规范体系把事情收回来。
什么时候看哪些文件,什么时候改哪些文件
这一节可以当作速查表使用。
生命周期速查表
| 阶段 | 重点文件 | 你要做什么 |
|---|---|---|
| 初始化 | openspec/config.yaml |
可选地补充项目上下文与规则 |
| 理解现状 | openspec/specs/*/spec.md |
了解系统当前已经承诺的行为 |
| 创建提案 | changes/<id>/proposal.md |
审查变更范围、动机与影响 |
| 审查需求 | changes/<id>/specs/**/spec.md |
审查新增或修改的行为场景 |
| 审查方案 | changes/<id>/design.md |
审查关键设计和实现权衡 |
| 审查执行计划 | changes/<id>/tasks.md |
确认任务拆分是否足够具体 |
| 实现 | 代码文件 + tasks.md |
让 AI 按任务顺序落地并更新勾选状态 |
| 对账 | verify 报告 |
判断实现是否完整、正确、一致 |
| 沉淀 | 主 specs/ + changes/archive/ |
sync 更新事实,archive 保留历史 |
决策速查
1 | |
注意事项与最佳实践
1. 不要跳过人工审查
OpenSpec 的核心收益来自“在编码前对齐”。如果 proposal 和 spec 没看就直接 apply,本质上仍然是在让 AI 自由发挥,只是多绕了一层目录结构。
2. 不要把 sync 和 archive 混为一谈
更稳妥的理解是:archive 本身就是默认收口动作;如果你希望在归档前先单独核查或单独合并 specs,再引入 verify 与 sync。这样既能适配默认工作流,也能覆盖扩展工作流。
3. 不要混用不同工件的时态
specs/讲的是当前事实;proposal.md讲的是本次准备做什么;tasks.md讲的是实现步骤。
一旦把“未来工作项”写进主 spec,或者把“系统现状”写进 tasks,AI 和人都会很难读懂上下文。
4. config.yaml 很重要,但不是绝对前提
它常用于补充项目上下文、默认 schema 和工件规则,在复杂项目中尤其有价值;但 OpenSpec 的工作流不应被误解为“没有 config.yaml 就无法启动”。
5. 用渐进式方式接入 OpenSpec
没有必要一夜之间把整个项目重写成 Spec。更现实的方式,是从新功能、关键接口、经常变动的模块开始,让 specs/ 随着一次次 sync 慢慢沉淀起来。
6. 警惕 spec drift
只修代码、不修规范,短期看起来省事,长期会不断制造重复劳动。每当 AI 下一次再读取过时的 spec,旧问题就会重新出现。
7. 把 archive 当成历史资产,而不是回收站
归档目录里保存的是一次变更完整的上下文:为什么改、怎么设计、怎么拆任务、最后沉淀了哪些规范。它对回溯设计决策非常有价值。
验收标准与 ATDD:让 Spec 真正可测试
OpenSpec 的 spec 非常适合描述行为,但要把规范真正变成可执行验收标准,仍然需要进一步思考一个问题:
“可读的场景”如何变成“可验证的标准”?
Gherkin 场景擅长行为,不天然擅长量化指标
例如,下面这种写法表达了目标,但仍然不够可测:
1 | |
问题在于:
- 没说明是单次请求、平均值,还是 P95 / P99;
- 没说明并发与负载条件;
- AI 也很难直接据此生成精确断言。
更实用的做法:分层表达验收标准
比较稳妥的实践是把验收标准拆成三层:
例如:
1 | |
然后在 design.md 中说明测试策略,例如使用 Gatling 做压测、用 JUnit 覆盖功能场景。这样做的好处是:
- 行为契约仍然留在 spec;
- 量化目标有明确归属;
- AI 在 apply 阶段更容易同时生成功能代码与测试代码。
ATDD 在 OpenSpec 里的落点
如果把验收测试驱动开发(ATDD)放进 OpenSpec 语境里,可以把它理解成:
1 | |
也就是说,OpenSpec 最适合作为 ATDD 的上游规范层。它不替代测试框架,但它能让测试目标在编码前就变得清晰。
OpenSpec Commands / Skills:如何理解这套生态
在支持 AI 命令和技能机制的工具里,OpenSpec 常常会以命令、skills 或两者结合的方式交付:
- 显式命令:例如
/opsx:propose、/opsx:apply、/opsx:archive; - 自动激活的 skills:当你用自然语言描述意图时,AI 工具可按其集成方式匹配对应能力。
更稳妥的理解是:它们常常服务于同一套工作流,但具体呈现方式取决于工具集成与交付配置:
- 命令更适合显式控制、重复执行和标准化步骤;
- skills 更适合自然语言驱动和低摩擦交互。
一个很实用的理解方式是:
- 新手阶段优先用显式命令,因为边界更清楚;
- 熟练阶段可以更多使用自然语言,让 AI 自动匹配;
- 复杂场景常常是两者混用:先用自然语言澄清问题,再用显式命令推进关键步骤。
三个最容易讲错的点
/opsx:new 不是“一次生成所有工件”
它更像“建立 change 容器并开始第一步”,适合想自己掌控生成节奏的人。
/opsx:explore 不是“提前开始实现”
它的价值在于思考、调研、比较、澄清,以及在必要时把结论回写到工件,而不是直接产出业务代码。
/opsx:archive 不是“只做历史归档”的别名
archive 的意义是收口与保留历史,同时在默认流程里也会更新主 Specs;sync 更适合在需要时把 spec 合并动作单独拿出来执行。
项目配置搭配建议
把 OpenSpec 放进真实项目时,最容易困惑的一点是:AGENTS.md、CLAUDE.md、规则文件、openspec/ 到底是什么关系。
最简单的理解方式是把它们分成三层:
1. AI 工具层
例如 AGENTS.md、CLAUDE.md 或某些工具自己的 rules 文件。它们主要负责:
- 项目级提示;
- Agent 行为约束;
- 编码规范、协作规则、调用策略。
它们回答的问题更像是:
AI 在这个仓库里应当如何工作?
2. OpenSpec 上下文层
主要是 openspec/config.yaml。它常用于:
- 提供项目背景;
- 提供规则与约束;
- 提供默认 schema 或工件级规则,并改善 proposal 等工件草案的质量。
它回答的问题是:
这个项目是什么样的?
3. OpenSpec 规范层
主要是 openspec/specs/ 与 openspec/changes/。它们负责:
- 描述当前事实;
- 表达本次变更;
- 沉淀历史与知识。
它回答的问题是:
系统现在是什么,以及这次准备把它改成什么?
推荐搭配方式
如果你只想用一句话记住:
- AI 工具层负责“怎么协作”;
- OpenSpec 上下文层负责“项目是什么”;
- OpenSpec 规范层负责“系统行为与变更是什么”。
三者并不冲突,反而是互补关系。对复杂项目来说,这种分层往往比把所有信息都塞进同一个 Markdown 文件里更稳定。





