11.4 讲了怎么生成初版 SPEC,再用二次澄清补齐关键歧义。你审批通过后,SPEC 才会冻结。 但冻结不等于永远不变。系统会迭代、需求会演变、团队会换人——SPEC 本身需要一套管理机制。 本节讲 OpenSpec:怎么用"主 SPEC + 增量变更"的模式,让 SPEC 像代码一样可追溯、可回滚、可验证。
6.1 SPEC 不是写一次就完的
你花了一下午,把胶囊系统的 SPEC 写完了。数据模型、业务规则、验收场景,一条条写得清清楚楚。Agent 按 SPEC 开发,系统上线,一切正常。
两周后,你发现一个问题:有些文章是重复的,你收藏了同一个链接两次,系统里存了两份一模一样的正文。你心想,加个去重功能吧。
于是你打开 SPEC 文件,找到数据模型那一节,在文章表里加了一行:
- url_hash: string(URL 的哈希值,用于去重)
又找到业务规则,加了一条:
- 新增文章时,先检查 url_hash 是否已存在,存在则跳过
改完保存,告诉 Agent 按新的 SPEC 开发。Agent 照做了,去重功能上线。
看起来没问题对吧?但三个月后,新来的同事接手这个系统,打开 SPEC 文件,看到 url_hash 字段,不知道它是什么时候加的、为什么加、之前有没有其他方案被否决过。他想改数据结构,又不敢动,因为不知道这个字段还有哪些地方依赖它。
问题出在哪里?出在你直接改了 SPEC 文件,而没有留下任何变更记录。
这就是 SPEC 管理最典型的痛点:直接改文件,没有上下文,没有记录,没有回滚能力。一次两次还好,迭代十次之后,SPEC 文件里全是"历史遗迹"——你不知道哪些字段是核心设计、哪些是临时补丁、哪些已经废弃了。你不敢删,不敢改,只能往上堆。最后 SPEC 比代码还乱。
你可能说,我用 Git 管理不就行了?Git 能记录谁在什么时候改了哪一行,但它回答不了几个更关键的问题:这次变更的动机是什么?审批通过了吗?验收通过了吗?当前生效的到底是哪个版本?
所以你需要一套专门的管理机制。
6.2 OpenSpec 是什么
OpenSpec 是一套 SPEC 管理规范,核心理念就一句话:
所有变更,都先写成 SPEC,验收通过了再执行。
这不是什么新发明。你想想软件开发里怎么做需求变更的——先写变更申请,评审通过,排期开发,测试验证,上线。OpenSpec 把同样的流程搬到 SPEC 管理上。
核心区别在于:传统开发里,变更流程是人的事,开会、写邮件、签审批单。OpenSpec 把整个流程做成了一套文件结构 + 工作流,Agent 可以自动执行大部分步骤,你只需要做你最擅长的事——审批。
具体来说,这套机制长这样:
- 你提出一个变更想法(比如"加去重")
- Agent 自动生成变更 SPEC
- 你审批,通过或打回
- 通过后 Agent 按变更 SPEC 执行
- 执行完自动验证,验证通过后合入主 SPEC
- 锁定当前版本
每一步都有记录,每一步都可追溯。
6.3 三层结构
OpenSpec 把 SPEC 分为三层,每层解决一个问题。
第一层:变更层
目录结构:changes/<change-name>/SPEC.md
每次变更单独一个目录,里面放这份变更的 SPEC。变更 SPEC 只描述"这次要改什么",不涉及整个系统的完整规范。
比如你要加去重功能,就新建一个 changes/add-dedup/SPEC.md,里面只写去重相关的数据模型、业务规则、验收场景。简单、聚焦、独立。
第二层:主规范层
目录结构:specs/<domain>/SPEC.md
这是当前系统行为的完整规范。所有已验收的变更,最终都要合入这里。主 SPEC 就是系统的"宪法"——任何时候你想知道系统应该怎么做,看这里。
第三层:锁定层
文件:SPEC.lock.md
当前生效版本的快照。锁定的意思是:Agent 在执行任务时,只认这个版本的 SPEC。主 SPEC 可能还在编辑中,但 lock 文件指向的是上一个已验收的稳定版本。
这三层的关系,你可以类比前端开发里的依赖管理:
- 变更层 = 你每次
npm install <package>时加的那条记录 - 主规范层 =
package.json,记录了所有依赖的声明 - 锁定层 =
package-lock.json,锁定了当前所有依赖的确切版本
你每次装一个新包,package.json 里多一条记录,package-lock.json 更新锁定版本。哪天出问题了,回滚 lock 文件就能回到上一个稳定状态。OpenSpec 的三层结构也是同样的逻辑。
6.4 实际操作:给胶囊系统加"内容去重"
拿胶囊系统来走一遍完整的变更流程。
第一步:你提出变更
你在 Hermes 里说:"胶囊系统现在有重复文章的问题,同一个链接收藏两次,系统存了两份。我想加个去重功能。"
第二步:Agent 生成变更 SPEC
Agent 收到你的需求,去看了当前的主 SPEC,然后生成了一份变更 SPEC:
changes/add-dedup/SPEC.md
---
变更概要:新增内容去重功能
影响范围:数据模型(article 表)、业务规则(新增流程)
数据模型变更:
- 新增 article.url_hash: string(URL 的 SHA256 哈希值)
- 新增 article.dedup_key: string(URL 哈希的首次出现标识)
业务规则变更:
- 新增文章流程:收到新链接 → 计算 url_hash → 检查 dedup_key 是否存在
- 存在则返回已有文章 ID,不创建新记录
- 不存在则创建新文章,写入 dedup_key
验收场景:
- 输入相同 URL 两次,系统只存一份
- 输入不同 URL,即使内容相似,也不误判为重复
- 存量数据不受影响,新增数据才去重
第三步:你审批
你看了一遍,发现一个问题:Agent 写的验收场景里没有提到"用户再次收藏同一个链接时,应该返回什么"。你加了一条:
- 用户收藏已存在的 URL 时,系统提示"已收藏过",并跳转到原有文章
然后点了通过。
第四步:Agent 执行变更
Agent 按变更 SPEC 修改代码。加字段、改新增流程、写去重逻辑、改前端提示。改完之后自动跑验收场景——三个场景全部通过。
第五步:合入主 SPEC
Agent 把变更 SPEC 的内容合并到主 SPEC 里。specs/domain/SPEC.md 里多了 url_hash 和 dedup_key 字段的说明,多了去重相关的业务规则,多了对应的验收场景。
第六步:更新锁定
Agent 更新 SPEC.lock.md,锁定当前版本。现在所有 Agent 在执行任务时,都认这个新版本。
整个流程走完,你去重功能上线了,而且留下了完整的变更记录。三个月后新同事来了,打开 changes/add-dedup/ 目录,看到这份变更 SPEC,就知道当时为什么加去重、怎么加的、验收了什么。他想改,也知道从哪里入手。
对比一下直接改文件的方式——没有对比就没有伤害。
6.5 OpenSpec 和 Shell Engineering 的关系
说到这里,你可能已经感觉到了:OpenSpec 不只是一个"管理 SPEC 的工具",它就是 Shell Engineering 的工程化实现。
回顾一下 11.2 的五阶段链路:想法 → 需求 → SPEC → Agent 开发 → 部署。OpenSpec 把第三阶段(SPEC)和第四阶段(Agent 开发)串起来了——所有变更都先写成 SPEC,验收通过再执行。Agent 不是自由发挥,而是严格按照 SPEC 的变更流程来工作。
换句话说,Shell Engineering 的"严格"体现在哪里?就体现在这套流程里。没有 OpenSpec,你说"让 Agent 按规范开发",但规范本身在变、没人记录、没人审批,Agent 拿到的规范和昨天拿到的可能不一样。有了 OpenSpec,规范本身被管理起来了,Agent 永远只认锁定层的版本,变更必须走完整的 propose → 审批 → apply → 验收 → 合入 → 锁定流程。
这就是形态四的 Shell + OpenSpec 工作流:一个完整的、可落地的工程化方案。
6.6 全章总结
回到 11.2 的五阶段图:想法 → 需求 → SPEC → Agent 开发 → 部署。
现在你已经走通了每一步:
- 11.3:模糊想法 → grill-me 拷问 → 需求简报
- 11.4:需求简报 → 初版 SPEC → 二次澄清 → 审批后的 SPEC
- 11.5:SPEC 冻结 → plan(技术方案)→ converge(验证)→ 交付部署
- 11.6:SPEC 工程化管理 → OpenSpec 三层结构 → 变更可追溯
整条链路由三件事贯穿:
SDD(规范先行开发)——先写 SPEC,再写代码。顺序不能乱。先写 SPEC,Agent 不会跑偏;先写代码,Agent 跑偏了你还不知道。
Shell Engineering(交付形态)——最终交付的不是代码,是一个可用的 Shell 项目。用户不需要关心你用了什么技术、数据存在哪里,他们只需要在对话里说一句话,系统就能完成一件事。
OpenSpec(规范管理)——SPEC 不是一次性的。系统会演进、需求会变更、团队会换人,SPEC 需要工程化管理。OpenSpec 的三层结构(变更层 + 主规范层 + 锁定层)让 SPEC 可追溯、可回滚、可协同。
这三件事是一条链,缺一环都不行。没有 SPEC,Agent 不知道做什么;没有 OpenSpec,SPEC 会腐烂;没有 Shell Engineering,项目交付不了。
走通一次,以后做任何 Shell 项目,你都有章法了。
6.7 ■ 学点英语
| 中文 | English | 音标 | 说明 |
|---|---|---|---|
| 规范变更管理 | OpenSpec | /ˈoʊpən spek/ | 用主规范和增量变更记录管理需求演进的工作方式 |
| 分布式版本控制 | Git | /ɡɪt/ | 记录文件变化并支持比较、协作和回退的版本控制系统 |
| 锁定 | Lock | /lɑːk/ | 把当前已经验收的规范版本固定下来 |
| 哈希 | Hash | /hæʃ/ | 把输入转换为固定长度摘要,用于比较内容是否一致 |
| 软件包 | Package | /ˈpækɪdʒ/ | 可以被项目安装和复用的软件单元 |
| 版本 | Version | /ˈvɜːrʒən/ | 用来区分规范或依赖在不同时间状态的编号 |
| 提议 | Propose | /prəˈpoʊz/ | 在变更流程中正式提出一项修改 |
| 应用变更 | Apply | /əˈplaɪ/ | 将已经批准的变更落实到项目中 |