OpenSpec(Fission-AI 团队,MIT 开源,5k+ Stars)是一套规约驱动开发工具。SDD 的理念所有人都同意,为什么几乎没人真的在用? OpenSpec 用两个工程决策回答了这个问题。这篇文章拆解这两个决策是什么,以及它们怎么在代码里落地的。

文章目录


一、SSD 是什么,以及为什么它一直没人用

1. 规约驱动开发——一份行为契约,先对齐再动手

规约驱动开发(Spec-Driven Development,SDD)的理念用一句话就能说清楚:先写清楚要做什么,再动手写代码。 这里的"写清楚"不是写需求文档,是写行为契约——系统在什么条件下产生什么行为、输入和输出的边界在哪、错误路径怎么处理。

行为契约和实现方案的关键区别:它描述 what,不描述 how。"用户登录失败三次后账号锁定 15 分钟"是行为。"用 Redis 存计数器、在 middleware 里检查、返回 423 状态码"是实现。行为契约不绑定实现——换一种技术方案,只要外部可观察行为不变,规约不用改。

这个分离产生了一个直接后果——可验证性。行为描述天然可被测试:GIVEN 一个被锁定的账号,WHEN 用户在锁定期内尝试登录,THEN 返回错误且不增加失败计数。

既然描述的是行为且可测试,规约就应该按业务领域组织而非按代码模块——auth/ 管认证,payments/ 管支付,notifications/ 管通知。一份 spec.md 一个领域,跟代码仓库一起存在 Git 里,不绑定代码目录结构。

当规约按领域组织且随代码一起存在版本库里,它就变成了活的真相源——每次变更不仅改了代码,也改了规约。六个月后回来看,规约告诉你系统现在怎么运作,不需要从代码反推设计意图。

这个理念几十年前就有了,从未有过争议。但它在今天被重新提出来,有一个新的理由。

2. Agent 编码让"不写规约"的成本翻倍了——为什么今天重提 SDD

人写代码时,隐性共识存在脑子里——团队约定、历史决策、没写下来的架构意图。沟通靠 PR review、靠 Slack 上的半句话、靠"你去看一下上次那个 PR 就知道了"。效率不高,但人慢,上下文在脑子里,写出来的代码大致不跑偏。

Agent 写代码的逻辑完全不同。上下文不在脑子里——在 prompt 里。你打了一句"帮我加个暗黑模式",Agent 不会追问"你是想用 CSS 变量还是 Tailwind 的 dark: 前缀?要不要跟随系统偏好?现有色板有没有定义语义色?"——它直接写 400 行代码。方向可能对,技术方案大概率不是你想要的。

人的思考先于打字,所以人的上下文损失发生在写完之后(发现跟预期不一致)。Agent 的上下文必须在打字阶段就压缩进 prompt——prompt 的质量直接决定了代码的偏差度。而一份写清楚的行为规约,就是最高质量的 prompt。

成本结构也因此翻转了。以前"写规约"的人力投入看起来比"写代码"还大——花了半天写文档,不如直接写代码。现在 AI 把写代码的成本压到接近零,规约成了流程里唯一不可压缩的人力投入。SDD 从"好习惯"变成了"不做的代价超过做"。

但问题是:如果 SDD 这么好用,几十年前就流行了。它一直卡在一个死循环上。

3. 冷启动死循环——要写规约需要全局理解,要全局理解需要先写完规约

SDD 落地时撞上的第一个问题,是它自己的冷启动悖论。

要写出一份有用的规约,你需要理解系统的全局——哪些行为已经存在、哪些接口已经被依赖、改了这里会不会影响那里。但要对系统有全局理解,你需要先把规约写出来——把散落在代码、注释、老人脑子里的隐性知识收拢成一份结构化的真相源。

这是一个死循环。打破它需要一次性投入大量时间,在一个写完规约之前没有任何可见产出的阶段。管理者不同意。同事不理解。你自己写到第三周也怀疑了——“我到底是来写代码的还是来写文档的?”

于是 SSD 工具被分成了两派,各放弃了一个关键场景。

4. 场景受限与易用性问题——历史上 SSD 工具的两个致命短板

第一派:绿地派。 只在新项目启动时有用。你从零开始写规约,规约写完再写代码,一切干净。但世界上的大部分代码不在新项目里。当你接手一个跑了三年的系统、要在 5 万行代码里加一个功能时,绿地派的回答是"先为整个系统写好规约"——这在工程上等于拒绝。

第二派:重量派。 代表是 GitHub 的 Spec-Kit。Python 环境、数据库、严格的阶段门控——planning → implementation → verification,不能跳步,每步需要人类审批。这套流程在质量上是对的,但摩擦太大。你打开一个独立的 Dashboard,填完一份模板,等审批通过,再回到编辑器里动手——三周后你放弃了。问题不是功能不够,是每次用都要离开你已经在用的工具

两个致命短板——场景受限、易用性差——让 SSD 几十年来停在"应该做"的层面,进不了"正在做"的日常。

SDD 的问题从来不是"规约有没有用"——所有人都知道有用。问题是写规约的门槛一直没有低于写错代码的返工成本。


二、OpenSpec 的决策一——用 Delta Specs 描述变化,解决场景受限问题

1. 快照 vs 增量——两种数据模型决定两个生态位

传统规约管理是快照式的:一个 spec 文件描述系统的完整行为矩阵。每次修改,编辑完整文件,保存一个新版本。这要求先有一份完整的 spec 文件——这就是前面说的冷启动死循环。

Delta Specs 换了一个思路:不描述目标状态,描述变化本身。 你不需要先有一份完整的规约。你只写这次变更涉及的行为变化——三个操作覆盖所有场景:ADDED、MODIFIED、REMOVED。然后归档时,这些 delta 自动合并到主规约里——不需要你手动对比、手动粘贴、手动检查冲突。

这就是 Delta Specs 和传统快照式规约的本质区别:快照描述"系统是什么样",delta 描述"系统变什么"。快照模式要求你在变更前就拥有完整真相源,delta 模式允许真相源随每一次变更逐步累积。两种数据模型决定两个生态位——快照适合从一开始就被严格管理的绿地项目,delta 适合每次只碰一小块的存量系统。

2. 只写增删改,不写整个系统——Delta Specs 的三类操作

一份 delta spec 长这样:

## ADDED Requirements
### Requirement: Theme Selection
The system SHALL allow users to choose between light and dark themes.

## MODIFIED Requirements
### Requirement: Session Timeout
The system SHALL expire sessions after 30 minutes of inactivity.
(Previously: 60 minutes)

## REMOVED Requirements
### Requirement: Remember Me
(Deprecated in favor of 2FA)

三类操作。ADDED 是新增行为——系统多了什么能力。MODIFIED 是改变已有行为——原来 60 分钟,现在 30 分钟,括号里的 “(Previously: …)” 是给审查者看的,归档时不进入主规约。REMOVED 是删除行为——为什么删、被什么替代,归因写在括号里。还有一个 RENAMED,处理需求标题变更的边界场景。

这里的"只写"是关键。你不需要理解整个系统的行为矩阵。你只需要知道这一次变更要增删改哪几条需求。

3. 存量项目可用——5 万行老项目也能从一条 delta 开始

这是 Delta Specs 的工程后果:存量项目终于能用 SDD 了。

你接手一个跑了三年的系统。不需要先为它写完整规约。你挑一个功能——比如"加一个暗黑模式"——写一份 proposal、写五条 delta requirement(系统 SHALL 支持主题切换、系统 SHALL 检测系统偏好、系统 SHALL 持久化用户选择、系统 SHALL 切换时不需要刷新页面、系统 SHALL 在 system preference 变化时自动跟随),然后走通整条 SDD 流程。

规约不是一次性写完的,而是随每次增量变更逐步生长。第一次变更写了 5 条 requirement,第二次变更补了 3 条,第三次变更又加了 4 条——一年后,这份 spec 就是系统行为的活文档,而且它的每一行都能追溯到具体的变更记录。

这跟 Git 的 diff 模式逻辑完全一样:Git 不要求你 commit 整个代码库的快照,只 commit 这次改了什么。文档管理用了二十年才追上版本控制的思路。

4. 归档时自动合入主规约——delta 怎么变成系统真相的一部分

归档是 Delta Specs 的闭环。一次变更的 task 全部完成后,/opsx:archive 触发归档流程——delta spec 里的 ADDED/MODIFIED/REMOVED 操作被应用到主规约文件上,变更本身移到 changes/archive/ 目录下保留为历史记录。

具体的合并流程图:

读入现有 spec.md → 解析为 Requirement 块列表(AST)
读入 delta spec.md → 解析 ADDED / MODIFIED / REMOVED / RENAMED 四个区块
对每个区块:
  ADDED    → 追加到 AST 末尾
  MODIFIED → 按标题匹配,替换对应 Requirement
  REMOVED  → 从 AST 中移除
  RENAMED  → 修改标题
输出合并后的完整 spec.md → 写入主规约

整个合并引擎不到 200 行,在 specs-apply.ts 里,纯字符串操作,零外部依赖。

这里有一个刻意为之的设计约束:每次变更只操作自己领域内的 spec 文件。 加暗黑模式只改 ui/spec.md,加 OAuth 只改 auth/spec.md。两个人同时做不同变更——一个改 ui/spec.md 的暗黑模式,一个改 ui/spec.md 的响应式布局——只要他们操作的是不同的 Requirement 条目,归档时不会冲突。这是 delta 模式对快照模式的天然架构优势:两个 delta 改同一个 spec、各自不同行,合并是纯文本操作,不需要人为协调。

Git 用 diff 管理代码所有人都不觉得奇怪。文档管理的 diff 模式迟了二十年。


三、OpenSpec 的决策二——把 SSD 嵌入编码工具,解决易用性问题

1. 日常操作用斜杠命令,CLI 只管搭环境——不切窗口的设计

决策一让存量项目能用 SSD。但光有这个不够。如果每次用 SSD 都要离开编辑器、切到一个独立 Dashboard、填表单、等审批——摩擦会把节省下来的返工成本全部吃掉。

OpenSpec 的处理方式是:SDD 的日常操作全部是斜杠命令,打在 AI 编码工具的聊天框里。

/opsx:propose   →  AI 起草 proposal + specs + design + tasks,你在聊天框里审阅
/opsx:apply     →  AI 按任务清单逐步实现,边写代码边勾 checkbox
/opsx:archive   →  规约归档,delta 合并到主 spec

CLI 只管一次性的初始化工作——openspec init 创建目录结构、生成技能文件和命令文件,跑完一次就不用再碰。日常循环全部在聊天框里完成。

这个设计的后果是:SDD 不打断工作流。 你在 Claude Code 里写完一段代码,在同一个聊天框里敲 /opsx:propose 就开始下一个变更的规约。不切窗口,不换工具,不需要"进入规约模式"。

2. 不管你用 Claude Code 还是 Cursor——一套规约,所有 AI 工具通用

如果你用的是 Claude Code,但团队里有人用 Cursor、有人用 Windsurf、有人用 GitHub Copilot——SDD 工具怎么处理这种多工具环境?

OpenSpec 的做法是两层生成系统。openspec init 时:

  • 系统 A(Skill 生成):一套通用的 Markdown 模板 → 写入各工具的 skills 目录。支持 Skill 规范的工具(Claude Code、Cursor 等)零额外开发。
  • 系统 B(Command 生成):工作流定义 + 工具适配器 → 写入工具特定的命令文件。为 25 种工具各写了一个适配器——每个适配器只管一件事:把同一份工作流内容翻译成该工具的命令格式和文件路径。

两个系统分离的结果是:支持一个新工具的成本 = 一个适配器文件 + 一行注册。 规约内容本身是工作流模板,跟工具无关。适配器只管"翻译"——内容到格式的映射。

3. 输出给 AI 看的和给人看的分两条通道——为什么 CLI 要两套输出

CLI 里每个命令的执行路径都分叉:

if (options?.json) {
  // 机器路径:结构化 JSON → stdout
  // 错误也是 JSON,带 fix 建议
} else {
  // 人类路径:chalk 彩色输出 + ora spinner + 交互式提示
}

这个分离不是"加一个 --json 选项"那么简单。它背后有一个事实:OpenSpec 的"用户"有一半是 AI Agent。 当你在聊天框里敲 /opsx:propose 时,AI 工具底层会调用 openspec instructions proposal --json 来获取当前的 artifact 状态、模板路径、依赖图信息——如果这个命令的输出是彩色 emoji + spinner 动画,AI 完全无法解析。

给 AI 看的输出是一套一致的 JSON 格式——每个命令的失败也遵循同一份 Agent Contract:{status: [{severity, code, message, fix}]},AI 拿到后知道怎么给你修复建议。给人看的输出是 chalk 彩色 + 交互式提示,对视觉友好。

两条通道完全独立,但服务于同一套数据模型。人在终端里看到 openspec list 的彩色表格,和 AI 在 --json 通道里拿到的结构化列表,是同一次查询的不同渲染方式。

工具的重量不体现在安装包大小,体现在它在你工作流里制造了多少次中断。


四、两个决策怎么落地——四个代码证据

1. 决策一——delta 怎么变成规约:格式 + 合并引擎

Delta Specs 的数据模型极其简单。所有 delta 文件共享同一套 Markdown 结构——## ADDED Requirements / ## MODIFIED Requirements / ## REMOVED Requirements / ## RENAMED Requirements。每个 Section 下是标准的 Requirement + Scenario 块。

合并引擎 specs-apply.tsbuildUpdatedSpec() 函数做的事:读入现有 spec.md → 解析为 Requirement 块列表(AST)→ 读入 delta spec.md → 解析四个 Section → 按操作类型修改 AST → 输出合并后的完整 spec.md。整个函数不到 200 行,纯字符串操作和正则匹配。

200 行的后果不是代码少,是边界清晰。合并逻辑不需要理解 Spec 的完整语义——不需要知道 authpayments 的业务规则有什么不同——只需要知道四个操作(ADDED/MODIFIED/REMOVED/RENAMED)如何改变 Requirement 块列表。这意味着合并引擎和业务域是解耦的——新增一个业务领域不需要改合并引擎。

2. 决策一——归档时怎么保证不出错:先构建再落盘

archive.ts 里的 ArchiveCommand.run() 实现了一个多阶段状态机:

FIND_SPEC_UPDATES  → 找到所有受影响的 spec 文件
BUILD_UPDATES      → 在内存中构建合并后的内容——不写盘
VALIDATE_REBUILT   → 逐个验证合并后内容的格式正确性
WRITE_SPECS        → 全部验证通过后才写盘
MOVE_TO_ARCHIVE    → 变更文件夹移到 archive/

设计意图在 BUILD → VALIDATE → WRITE 的排序上:如果三个 spec 文件需要更新,前两个验证通过但第三个失败——不会写任何文件。 所有构建在内存中完成,所有验证全部通过后统一落盘。没引入数据库,但实现了事务的原子性语义。

这是手工实现的事务模式——在文件系统上保证"要么全改,要么全不改"。代价是不能处理并发写入(两个进程同时归档),但在 SDD 的工作流里,"同时归档两个变更"本身就不会发生——每个变更的归档是串行的。这个取舍是合理的:牺牲不必要的并发能力,换取了零依赖的简单性。

3. 决策二——工作流怎么变成命令:同一套内容,25 种工具各自翻译

Command Generation 系统由两层组成:工作流模板 + 工具适配器。

工作流模板是一个纯内容的定义——“这个命令做什么、需要什么上下文、按什么步骤执行”。跟任何工具无关。例如 propose 模板定义了 /opsx:propose 的核心行为:读取已有 spec → 创建 change 目录 → 生成 proposal → 生成 delta specs → 生成 design → 生成 tasks。

工具适配器是一个接口,每个工具的适配器只实现三个方法:

getFilePath(workflow: string): string;     // 命令文件放哪个路径
formatCommand(content: string): string;     // 内容用什么格式
getCommandSyntax(commandName: string): string; // 命令怎么被调用

Claude Code 的适配器把模板内容写入 .claude/commands/opsx.propose.md,Cursor 的适配器写入 .cursor/commands/opsx-propose.md,Codex 用 .codex/commands/。同一份内容,25 种文件路径和格式。新增一个工具 = 一个适配器文件 + 在注册表里加一行。

这个架构的要点:内容和格式完全分离。 工作流模板的维护者不需要关心 25 种工具的命令语法差异。适配器的维护者不需要关心工作流的业务逻辑。两层之间的接口只有三个方法——够用,且没有过度设计的余地。

4. 决策二——输出怎么同时给人看和给 AI 看:人和 AI 各看各的,互不干扰

CLI 里每个命令的 --json 通道不只是输出格式的差异,它是一个独立的设计约束:给 AI 看的输出必须机器可解析,即使命令执行失败。

failWithError() 函数根据 json 标志选择输出路径:

  • JSON 路径:{status: [{severity, code, message, fix}]} + process.exitCode = 1
  • 人类路径:ora.spinner 的红色错误消息 + 可选的修复提示

每个错误输出都带 fix 字段——AI 拿到错误后可以自动给出修复建议(“你需要指定一个 change 名称”、“这个 store 的注册路径已经过期,用 openspec store doctor 修复”)。所有支持 --json 的命令共享同一套错误格式——AI 不需要为不同命令写不同的错误解析逻辑。

这不是"加一个选项"。这是承认工具的半数用户是代码——并为他们单独设计了一条通道。

设计决策如果不改变代码结构,就只是一个意见。Delta Specs 改变了 spec 文件的存储格式,Enablers not gates 改变了 ArtifactGraph 的返回值类型,双通道输出改变了 CLI 里每个命令的输出逻辑——每个决策都在代码里留下了不可逆的痕迹。


五、三步启动——在你的项目里跑通第一次 SDD 循环

前面四章讲了 OpenSpec 做了什么以及为什么。如果你决定试试,这一章是怎么开始的。

1. openspec init——初始化做了什么,怎么选工具

在项目根目录执行 openspec init。它会做四件事:

  • 在项目下创建 openspec/ 目录树——specs/changes/changes/archive/
  • 生成一份默认的 config.yaml(指定 spec-driven 作为默认 workflow schema)
  • 扫描你的项目目录,检测当前在用的 AI 工具——.claude/ 目录存在 → Claude Code,.cursor/ → Cursor,.github/copilot-instructions.md → GitHub Copilot。然后交互式让你确认要配哪些工具
  • 对每个选中的工具,生成对应的 Skill 文件和斜杠命令文件

init 只需要跑一次。后续用 openspec update 刷新命令内容——模板更新时用,但不改你的工具选择和 Profile。

2. 一个变更的完整生命周期——/opsx:propose/opsx:archive

一次变更的四步流程:

/opsx:propose add-dark-mode
  → AI 在 openspec/changes/add-dark-mode/ 下创建 proposal + specs + design + tasks
  → 你在聊天框里审阅,改到满意

/opsx:apply
  → AI 按 tasks.md 逐条实现,勾 checkbox
  → 边实现边更新 design.md 或 specs/ 如果发现跟计划不一致

/opsx:archive
  → delta specs 合并到主规约 openspec/specs/ui/spec.md
  → 变更移到 openspec/changes/archive/2026-07-14-add-dark-mode/

每一步都在聊天框里完成,不需要离开编辑器。每一步的产物都是 Markdown 文件,可以直接在 Git 里 diff 和 review。

3. 想深入了解?OpenSpec 文档的推荐学习路径——按你的场景选入口

OpenSpec 的文档首页(docs/README.md)给出了一条"三十秒版本"的速览路径:

1. Install        npm install -g @fission-ai/openspec@latest
2. Initialize     cd your-project && openspec init
3. Explore        (在 AI 聊天框里)  /opsx:explore           ← 可选,但是最好的习惯
4. Propose        (在 AI 聊天框里)  /opsx:propose add-dark-mode
5. Build          (在 AI 聊天框里)  /opsx:apply
6. Archive        (在 AI 聊天框里)  /opsx:archive

第 1、2 步在终端,后面全部在 AI 聊天框里——这个分工是最容易搞混的地方,所以文档首页单独列了一栏:“如果只读两篇,读 Getting Started 和 How Commands Work,第二篇比看上去重要得多。”

文档首页的设计很特别——它不推一条线性的阅读路径,而是按"你是谁、你遇到什么问题"分入口:

  • “我完全没概念” → 先读 Getting Started,再扫一眼 Core Concepts at a Glance,遇到看不懂的词查 Glossary
  • “我有个存量项目” → 直接读 Using OpenSpec in an Existing Project,不需要先为整个系统写规约
  • “我想看个完整例子” → Examples & Recipes 里有一个小功能、一个 bug 修复、一个重构、一次探索的完整 walkthrough
  • “我想定制” → Customization 覆盖项目配置、自定义 schema、多语言生成
  • “我想养成最好的习惯” → Explore First 专门讲 /opsx:explore——不写代码,先跟 AI 把方案想清楚

OpenSpec 自己的 openspec/specs/ 目录也是学习材料——那是团队用 OpenSpec 管理 OpenSpec 的真实规约。openspec/changes/archive/ 里每个归档变更都是一份完整的"为什么做 + 怎么做 + 做了什么"的记录。


六、总结

SSD 的理念出现了几十年,一直停在"应该做"的层面。问题不是理念不对,是之前的工具没有同时解决两个工程条件:让存量项目能用,让使用过程更简单。 OpenSpec 用 Delta Specs 解决了第一个,用嵌入 AI 编码工具解决了第二个。两个决策都不是理论创新——Git 的 diff 模式、IDE 的插件机制,都是成熟思路——但它们第一次被同时搬到了规约管理领域。


相关链接:OpenSpec 开源仓库 · OpenSpec 文档 · GitHub Spec-Kit

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐