第 7 章 / 共 10 章
规格漂移:实现推翻规格时怎么办
7.1 规格失效的三种典型时刻
一位实践者的复盘很有代表性:规格写完不到一小时,实现就暴露出文档从未预料到的约束和决策,于是规格变成了那个被永久修补的对象,而不是可以稳固建造其上的地基。
这几乎是所有人的经历。但仔细看,规格失效通常只有三种时刻:
| 时刻 | 例子 | 该怎么办 |
|---|---|---|
| 技术现实推翻方案 | 发现 OPML 分组必须嵌套,而现有数据结构是扁平的 | 改 plan,不改 spec——目标没变 |
| 需求本身被想清楚了 | 做到一半意识到导出必须支持筛选,否则没人用 | 改 spec,走变更流程 |
| 规格本身写错了 | 验收标准 3 的提示文案和产品其他地方不一致 | 直接改 spec,记一笔即可 |
大多数「规格漂移」其实是第一种,改的是计划而不是规格。把这两者分开之后,真正需要走流程的变更会少很多。
7.2 变更提案:把修改本身变成可评审的对象
第二种情况才是重点。这时候最糟的做法是直接在原规格上覆盖修改——你会失去「原来打算做什么、为什么改」的记录,而这份记录恰恰是几周后回看时最值钱的东西。
OpenSpec 的做法值得借鉴:变更本身是一等公民。它的目录结构是这样的:
openspec/specs/—— 当前生效的需求与场景openspec/changes/—— 正在进行中的变更openspec/changes/archive/—— 已完成并归档的变更
流程是三段:/opsx:propose 提出变更提案 → /opsx:apply 实施 → /opsx:archive 归档。

你不用装工具也能用这套结构。在项目里建三个目录,一份变更提案就是一个 Markdown 文件:
# 变更:导出支持按分组筛选
## 现状
当前规格(v1)要求一次导出全部订阅。
## 为什么要改
实际使用中发现,多数用户只想迁移某几个分组。
全量导出后再手工删减,反而比不导出更麻烦。
## 变更内容
- 新增验收标准 5:当用户在导出前选择了分组时,系统应仅导出所选分组下的订阅。
- 修改验收标准 3:若所选范围内没有订阅,则系统应提示「所选分组暂无订阅」。
## 影响
- src/lib/opml.ts 的 serializeToOpml 需接受可选的 folder 过滤参数
- Settings.tsx 需新增分组多选控件
- 已完成的 T1 需补一组测试;T2 需返工
## 不变的部分
目标、OPML 2.0 格式约束、非目标全部不变。
写这份提案花五分钟,但它做到了三件事:让你在动手前判断这次改动是否值得;给 agent 一份明确的增量指令,而不是让它重读整份规格猜哪里变了;留下了「为什么这么改」的记录。
7.3 双向同步不会自动发生
Kiro 在产品层面处理了另一个方向的漂移:代码改了,规格没跟上。它的做法是让规格与代码库保持同步——开发者可以写完代码后让 Kiro 更新规格,也可以手动改规格来刷新任务。产品公告里点明了要解决的病症:开发者在实现过程中停止更新原始产物,导致文档与代码不匹配。
但要清楚一点:同步是人触发的,不是自动的。工具能帮你生成 diff、提示不一致,但「这次改动要不要回写规格」永远是一个人的判断。
一个低成本的习惯:在提交前问一句——
对照 SPEC.md 检查这次的改动,指出代码里已经发生、但规格里没有体现的行为变化。
有变化就顺手更新,没有就过。三十秒的事,能挡住绝大部分文档腐化。
7.4 什么时候该丢掉规格
也有该丢的时候。如果一份规格在一周内被改了五次,且每次都是需求本身在变,说明你处在探索阶段——这时候写规格是在给一个还没稳定的目标做记录,成本高收益低。
正确的动作是:停止写规格,改用探索模式跑几个原型,等方向稳定了再回来补一份规格。OpenSpec 专门为此留了一个 /opsx:explore——不做承诺的探索性思考。承认「现在还不到写规格的时候」,本身也是一种规格纪律。