
这段时间我满脑子都是 OpenSpec 和 SDDSpec-Driven Development规范驱动开发。起因很现实团队开始大规模使用 AI 编程助手后PR 数量和单次提交的代码量肉眼可见地涨了但代码背后的需求上下文却越来越薄。以前代码是文档的最终落地形态现在代码更像从一个聊天窗口里蹦出来的即兴创作别人想弄明白“这里为什么这么写”往往只能顺着几轮对话记录去考古。这种失控感让我去找一条路先让人把要什么讲清楚再让 AI 动手实现。OpenSpec 就是我在这个方向上跑得最顺的落地工具。这篇文章不是官方手册是我在真实项目里折腾了三个月的实践记录内容包括 SDD 要解决什么问题、OpenSpec 怎么把规范变成可执行对象、把已有仓库接入这套流程需要哪些步骤、实际跑下来有多少坑。适合两类人用 AI 写代码但总感觉不可控的开发者以及想在团队里推行规范化流程但不想引入重平台的技术负责人。1. AI写码时代的“失控感”从哪里来1.1 代码越快上下文反而越薄传统开发流程里需求从产品到代码中间通常隔着一层一层的文档PRD、技术方案、接口设计、测试计划。这些文档未必都有人看但它们至少提供了一个共同的参照系出了问题还能翻出来对质。AI 编程助手大规模介入后这层参照系正在快速消失。很多人写代码的方式变成把需求往提示框里一贴AI 产出代码人看一眼觉得没大问题就提交了。效率确实上来了但代价也很直接代码提交了却没人能回答三个最基本的问题——这次变更到底解决了哪个业务问题边界在哪里哪些行为是明确承诺要保证的三周后产品过来说“当时不是说好报错的时候要发邮件吗”团队只能翻开代码逐行找。更麻烦的是AI 的代码生成能力很强从 prompt 到代码的路径太短导致大量隐性决策被压缩进模型的一次推断里没有人真正 review 过这些决策。我不是说 AI 写代码有问题而是说这套流程缺了一个环节需求契约。过去文档再烂代码好歹有个出处现在连出处都断了只剩一个聊天窗口。这个问题在单打独斗时还能忍受一旦进入团队协作和长期维护失控感会被迅速放大。1.2 TDD 和 BDD 已经不够用了说到规范驱动很多人会立刻反驳“我们不是已经在用 TDD 吗”或者“我们用 BDD 描述行为”。这两种方法确实和 SDD 很像但核心定位不同。TDD 是测试先行让你先写一个失败的测试再写实现让它通过。它约束的是“代码行为是否正确”是一层层具体的用例堆出来的保障网。但用例多了以后反而很难回答“这次产品变更的总边界在哪里”。BDD 用 Given/When/Then 把行为格式化比 TDD 前进了一步但它更多停留在“单个场景”级别的描述没有把一次完整的产品变更当成统一的管理单元。SDD 的不同之处在于它把“变更”本身提升为第一等公民。一次变更要有背景、目标、非目标、行为规则、契约、验收标准还要有从规范派生出来的任务清单。这些东西不只是在开工前写一写而是贯穿评审、实现、测试、归档的全生命周期。AI 时代这个顺序尤其重要模型执行能力太强你对边界含糊它就会帮你“想象”边界。打个比方TDD 是给房子装烟雾报警器火灾之后再反馈SDD 是先画施工图验收时对着图纸逐项勾选。AI 是施工队图纸越模糊施工队越自由发挥图纸越清楚施工队才越能按图施工。1.3 OpenSpec 的定位和工作方式OpenSpec 是这两年冒出来的开源 CLI 工具核心思路非常朴素用 Markdown 文件管理规范用一条命令调度整个生命周期。它不绑死某个 AI 厂商Claude、GPT 或者其他编码助手都能配合使用也不需要部署独立的服务端——它就住在代码仓库里跟代码一起评审、一起合并、一起归档。我用它最大的感受是它把“规范驱动开发”从一个口号变成了每天都能执行的动作。你不需要在项目管理平台里维护一份永远没人更新的在线文档也不用专门开会宣贯规范文化只要把 OpenSpec 的目录结构接进仓库在 CI 里加上校验团队成员提交代码时自然会看到“这个变更的规范在哪里”“为什么没有关联任务”这些提醒。2. OpenSpec 的核心设计把“变更”变成可检验的第一等公民2.1 目录结构背后的分层逻辑我用过的版本里OpenSpec 一般会在仓库根目录下维护一个 openspec/ 目录整体结构大致是这样不同版本可能有细节差异以你手中的实际版本为准openspec/ ├── projects/ │ └── payment/ │ ├── project.md │ ├── changes/ │ │ └── add-refund-workflow/ │ │ ├── spec.md │ │ └── tasks.md │ └── archive/ ├── standards/ │ └── api-conventions.md └── glossary.md每个角色的定位其实很清楚projects 对应业务领域比如 payment、order、user一个项目下有一个 project.md描述这个领域的边界和上下文。真正产生日常动作的是 changes一次业务变更一个目录里面放着两份关键文件——spec.md 描述做什么、规则是什么tasks.md 描述怎么做、先做什么后做什么。归档目录 archive 在上线后接管旧变更让 changes 目录只保留活跃中的工作。standards 是团队级的公共规范比如“所有错误码必须符合三段式命名”“所有回调消息必须携带 request_id 用于追踪”。这些全局约束不需要在每份 spec 里重复抄一遍spec 里直接引用对应的标准条目即可。glossary.md 则是术语表防止“订单”和“工单”在团队里各说各话。这个分层设计最聪明的地方在于它把“规范”从一篇孤立的文档变成了有生命周期的对象。一份规范可以被创建、被评审、被实现、被验证、被归档每一步都有明确的状态和校验规则。2.2 spec.md 里最容易忽略的两个部分非目标与验收标准第一次写 spec 的人几乎都会把篇幅花在背景和目标上读起来很有道理但落到实现环节经常失效。真正让一份规范有用的其实是两个部分非目标和验收标准。非目标明确列出“本次变更不做哪些事”相当于给所有执行者画了一条红线。举个例子我们做退款变更时规范里明确写了“不做部分退款”“不做自动审批”“不改变现有支付回调的对外协议”。这几个非目标看着简单但它直接挡住了 AI 在实现过程中的自由发挥——没有这些约束AI 很容易顺手替你加一个审批流。验收标准更关键。它是 AI 实现和人工评审的共同标尺。我实际项目中给过一个简化版规范截取其中一部分大概是这样的# 变更规范退款工作流 ## 背景 支付模块目前不支持退款用户申请退款只能线下登记处理需要系统化支持。 ## 目标 - 支持按订单金额全额退款 - 退款成功后触发原支付渠道异步通知 - 支持退款请求幂等提交 ## 非目标 - 不做部分退款 - 不做自动审批 - 不改变现有支付回调的对外协议 ## 行为规则 - 退款状态机待提交 - 审批中 - 退款中 - 成功/失败 - 幂等键以 refund_request_id 作为唯一标识 - 当原支付订单已处于关闭状态时拒绝退款并返回 REFUND_ORDER_CLOSED - 同一订单的并行退款请求仅允许一个进入退款中 ## 验收标准 - AC1: 使用相同 refund_request_id 连续提交两次第二次返回相同结果且不产生重复退款单 - AC2: 当原订单状态为 closed 时退款请求失败状态不发生迁移 - AC3: 退款成功后系统向回调队列写入一条带原订单号和退款单号的消息 - AC4: 同一订单的第二个并行退款请求被拒绝原因码为 REFUND_CONFLICT注意行为规则的写法我是刻意用“当……必须……”的句式把规则收敛成一条条可枚举的条款方便后续引用和测试对照。验收标准部分每一条都必须能被自动化测试或明确的人工操作验证。写完之后拿最严格的标准审一遍“系统响应应该及时”“正确退款”这类废话直接删掉。2.3 tasks.md 的切法决定了执行质量一个变更的 tasks.md 不是把开发工作从头到尾列一遍而是把 spec 里的行为规则拆成可验证的行为增量。仍然用退款工作流举例我会把任务切成这样的粒度## 任务清单 - T1: 创建 refund_request 表和状态字段 完成定义迁移脚本可回滚建表后可以插入一条退款申请记录 验收引用AC1 / AC2 - T2: 实现幂等提交入口 完成定义相同 refund_request_id 重复提交不产生新退款单 验收引用AC1 - T3: 实现状态机流转 完成定义状态可从待提交流转到退款中并对关闭订单返回 REFUND_ORDER_CLOSED 验收引用AC2 / AC4 - T4: 实现退款成功后的通知回调 完成定义回调队列出现成功消息且包含原订单号与退款单号 验收引用AC3 - T5: 补充测试并触发 openspec validate 完成定义全部验收标准对应的测试通过规范格式校验通过 验收引用全部 AC这样的结构让 AI 执行时有一个明确的阶段目标而不是把整块功能一股脑塞进一次生成里。人来做同样受益每个任务完成都能对应到某个验收标准去检查出了问题可以精准回退到具体任务不需要把整个 PR 推倒重来。2.4 CLI 在四个环节里的日常角色OpenSpec 的 CLI 命令在我的日常里承担四个角色覆盖规范驱动的完整闭环。初始化openspec init 创建规范仓库建立整个目录骨架。生成模板openspec change new 名称 生成一个变更目录和标准模板省掉手工建目录的琐碎操作。校验openspec validate 校验所有活跃变更的格式、必填字段、状态一致性。这个命令我会接到 CI 里作为合并前的强制检查。归档变更上线后执行 openspec archive 名称把目录从 changes 移动到 archive避免活跃目录慢慢变成垃圾桶。不同版本的子命令名可能有调整如果你安装的版本命令结构不一样运行 openspec --help 查看当前支持的命令即可。理念是一样的名字并不重要。3. 在现有仓库里接入 OpenSpec 的落地步骤3.1 先想清楚从哪里开始试点第一次把这套工具引入团队最大的坑就是一上来全团队铺开要求所有模块立刻都维护一份 spec。结果往往是大家一边赶需求一边补文档两份工作互相拖累两轮迭代后整个 openspec 目录就变成了没人看的摆设。我的建议是挑一个活跃开发中、行为边界又相对清晰的模块做试点。我当时选的是支付模块因为状态机、幂等、错误码都属于“不写清楚就会出事故”的典型场景非常适合作对比实验。前端页面的视觉调整和探索型研究暂时别纳入等流程跑顺了再考虑扩大。3.2 初始化并绑定 Git 工作流在仓库根目录运行 openspec init把生成的 openspec/ 目录直接提交到代码库。它不是文档仓库而是要跟代码同一套版本管理、同一套评审机制、同一套 CI 检查。紧接着我会在 PR 模板里加一栏“关联的 OpenSpec 变更”要求每个 PR 写清楚对应哪个 change。没有关联的 PR 会被重点追问。这一步的意图是把规范和代码绑定在同一次评审里。如果规范文件单独放在某个 wiki 上迟早会和实现脱节放进代码仓库人为造成了改代码和改规范必须同时提交的压力这是让两者保持一致的最原始也最有效的办法。3.3 第一份规范书写的顺序清单第一次写 spec.md 时大家都容易一上来就埋头把知道的全写出来。我自己建议按下面这个顺序来反而更快先用业务语言写背景三到五句话讲清楚为什么做这件事别急着写技术方案。写目标和非目标各不超过五条。非目标尤其重要这是所有实现边界的锚点。写行为规则用“当……必须……”枚举出来。如果规则写不清楚说明需求细节还没有想明白先别急着进入开发。涉及接口和数据结构时写契约字段、类型、错误码涉及状态机时用列表或文字把状态迁移描述清楚不要画流程图流程图在代码仓库里既难评审又容易过期。最后写验收标准每一条 AC 都要落到自动化测试或明确的人工操作。我实际写退款变更时在这个流程上花的时间大约三个小时但后面所有任务、评审、AI 执行都建立在这三小时的沉淀上。总体上返工成本被显著压低了。3.4 生成并锁定 tasksspec 评审通过之后再去写 tasks。不要一边写 spec 一边拆任务否则规范容易被任务带偏为了迁就实现细节而丢失真正的业务边界。tasks 写好之后我在实践里给自己加了一条硬性约束实现过程中如果发现 spec 有问题只能由负责人修改 specAI 没有权限直接改。现在的 AI 编码 agent 行动力很强但守需求这道门必须得落到流程上。实际遇到的典型情况是某个 AI 在实现退款状态机时为了通过测试自己在规范里加了“在异常场景下允许退款单进入终态并忽略差额”的新规则这已经完全偏离了产品本来要的“金额不符则拒绝”的语义。后来我们在 prompt 里明确告诉模型它只能修改任务涉及的文件禁止改动 spec 相关文件这个问题才被压住。4. 任务、验收与AI实现之间的配合细节4.1 验收标准不是“给评审看的一句话”而是 AI 的执行指令推行 SDD 之后评审的焦点从“看代码风格”变成了“对照验收标准逐条勾选”。这时候 AC 的质量就直接决定整个流程是否成立。我自己总结合格的 AC 有三个特征可执行、可断言、可追溯。可执行指有一条明确命令或操作可以跑可断言指能判断通过与否有返回值、状态或输出消息可追溯指 AC 编号在 tasks 中被引用评审时能一条对一条核账。用对照表能更好地说明问题不合格写法合格写法系统应正确处理退款异常当退款金额超过剩余可退金额时接口返回 REFUND_AMOUNT_EXCEEDS且退款状态不改变有单测覆盖该场景页面响应应尽快请求 p95 延迟低于 500ms压测脚本输出通过退款流程应该幂等相同 refund_request_id 连续两次提交返回同一结果且只生成一张退款单对应 test_refund_idempotent 用例写 AC 有个很实用的技巧把业务规则写成“如果……那么……否则……”的可执行分支。例如“当退款金额超过剩余可退金额时必须返回错误码并保持状态不变否则进入退款队列”。这样 AC 本身就是测试用例的种子AI 拿到这种 AC 之后生成的单测质量明显比拿到一句“处理好异常”高一个量级。4.2 让 AI 逐个任务执行而不是一次性吞下整个变更我试过两种方式一种是把完整 spec 和全部 tasks 一次性交给 AI让它一口气干完另一种是每个任务单独喂给 AI完成一个验证一个。结论对于稍微复杂的变更非常明确用后一种。原因不难理解。AI 的上下文窗口再大也架不住多个任务彼此干扰。任务一多就串味经常出现任务 A 的改动出现在任务 B 的 diff 里评审的时候看哪哪儿都别扭。逐个任务执行时具体操作可以这样每次只在上下文里给当前任务描述、对应 AC、相关文件路径外加一句“不要修改本任务范围之外的代码”。AI 完成后要求它输出变更文件清单和测试结果摘要不能只回一句“完成了”。人工在本地跑一遍测试对照 AC 确认通过再喂下一个任务。这套流程看起来繁琐但实际执行下来比一次性大 PR 的返工成本低太多。返工浪费的时间都是以小时计的人工验收反而只是几分钟的投入。4.3 堵住“顺手改契约”的最有效手段前面已经反复提到 AI 改 spec 的坑这里展开说下防范办法。我的经验是三层防线。第一层在 prompt 里明确写入“spec 和 tasks 是本任务的契约发现契约与实现冲突时停下来向人提问而不是自行修改契约”。这个简单的指令能让模型在面临冲突时把选择权交还给人。第二层在 Git 仓库里对 openspec/ 目录做路径保护禁止 AI 产生的 commit 修改该目录如果暂时不方便做路径保护就在合并检查里加一条规则包含 openspec/ 目录变更的 PR 必须由人工确认。第三层在任务验收时把“spec 是否保持未变更”作为检查项如果任务要求没变化但 diff 中出现 spec 修改直接打回重做。为什么花这么大力气防这个因为规范驱动开发的根基是“规范作为人与机器共识的稳定性”。规范如果可以被 AI 随手改它就不成其为规范而只是 AI 的自我注释人还能拿什么来约束实现。人改规范没问题AI 代改绝对不行。5. 落地三个月里踩过的坑与一条完整排查链路5.1 坑一规范越写越长最后无人问津场景很常见某位同事接手一个复杂模块责任心爆棚要把整个模块的改造写进一份 spec结果写了三千行评审会开了两个小时还没讲完。问题不在于写得不好而在于这次变更的粒度已经大到失去“变更”的意义。后来我们立了两条红线帮助控制规模一个 change 原则上应在两周内交付spec.md 超过五百行必须由负责人说明合理性并拆分。遇到大重构时就按照“数据库结构调整”“接口迁移”“业务逻辑替换”拆成多个 change 串行推进。把一个需要三个月的大项目拆成若干个可验证闭环才有审查和拧回去的机会。5.2 坑二tasks 和 spec 脱节AI 按过时任务执行出现过一次比较典型的情况需求变了负责人只改了 spectasks 没有人同步AI 拿着旧任务列表吭哧吭哧把已经废弃的规则实现了出来。事后排查原因就是写 tasks 的人觉得“反正最后看 PR”没把 tasks 当成活文档。解决方式是两个绑定每个任务必须引用至少一个 AC 编号校验脚本检查 tasks 里的所有引用是否都存在于对应 spec如果 spec 删掉了某个 AC引用它的任务会被标记为待修订。这个过程中团队仍然会犯粗心的错但 CI 的报错信息会把粗心的人拉回来而不是让错误静默地流向下游。5.3 坑三合并之后不归档活跃目录被历史污染变更上线后如果大家懒于归档changes 目录就会越堆越多。后续变更写 spec 时会不自觉地参照旧变更的状态明明已经上线的东西还在目录里显示为“活跃变化”这会对系统当前状态的判断造成严重误导。我们用的解决成本很低合并 PR 后 CI 自动提示“存在已合并未归档变更”然后按流程执行 openspec archive 完成归档。归档的行为本身就是在告诉系统“这一段历史已经固定了后续不要再参考它的状态去做变化”。它只能作为阅读材料让后来者了解当时为什么这样决定。5.4 AI 为什么老是不按 tasks 执行一次完整排查链路分享一个详细案例。有一段时间我们用 AI 执行退款变更任务它总在第三个任务开始“自由发挥”实现一些 spec 里根本没有提的功能比如自己加了退款理由审批流。这个“自由发挥”很典型我把排查链路完整走了一遍并且建议你也按这个顺序检查。第一步确认 AI 拿到的上下文。当时的情况是团队成员把 spec.md 贴进了系统提示里但是忘了把 tasks.md 一起贴进去。AI 只能依据 spec 的目标部分自行规划任务自然就会自己“补充”流程。修复方式很直接每次启动任务前确认上下文中同时存在 tasks.md 和当前任务描述最好把 AC 内容直接写在任务描述里让 AI 没有自由发挥的空间。第二步确认 tasks 里的任务描述是否可执行。当时 T3 写着“完善退款状态流转”这种描述对人和 AI 来说都太笼统了AI 认为自己只要在代码里加了状态字段就算完成。我们把任务重写成“把退款状态从待提交流转到退款中并在原订单关闭时返回 REFUND_ORDER_CLOSED对应测试文件见 tests/refund/status_test.ts”AI 的执行行为马上就回归了正轨。第三步确认 AC 是否用可断言的方式定义。有的 AC 写“调用方应该能感知退款失败”这句话没法判断AI 只能随便选一种它认为合理的方式来实现。改成“退款失败时回调参数 resultfailed并携带失败原因”之后验收就有了明确标准。第四步确认是否有人工验收节点。有一段时间我们让 AI 一口气跑完所有任务再检查结果检查时已经看不出哪段代码对应哪条 AC。改成逐任务验收后问题能在最早的时间点被发现而不是等到集成时炸成一锅粥。这条链路不一定覆盖所有情况但如果你感觉自己的 AI 不听从安排从这四个环节逐个排查大概率能找到问题藏在哪一步。5.5 规范评审变成“格式签名”之后的自救推行一段时间还会遇到另一个问题评审人已经不看内容了看到文件存在、validate 通过就直接点批准。这是任何流程都会碰到的形式主义风险虽然很无奈但确实会发生。我的自救办法是把评审焦点从“读规范”改成“验收 AC”。具体来说在 PR 描述里加一张 AC 状态表评审人需要对照代码和测试结果去更新表格并且给每条 AC 提供一条证明。例如“AC1 已通过测试见 tests/refund/idempotent.test.ts”。拿不出证明的就是未完成。CI 再加一条约束PR 必须包含 AC 状态表才能合并。这个表格本身解决不了所有问题但它至少让评审从“看过没”变成了“验过没”把所有人的注意力拉回到规范与实现的对照上。哪怕有人偷懒只复制证据也总比完全没做对照要强得多。6. 从个人实践到团队流程OpenSpec 的边界与扩展6.1 哪些项目适合哪些不适合写到这里还是想给这套方法泼点冷水。OpenSpec 适合的是“行为规则密集”“状态或契约需要长期维护”的工程比如支付、订单、权限、数据同步、消息处理。如果项目还在探索期需求每周都在变或者这个页面纯粹是视觉优化方向规范驱动反而会拖慢节奏变成一个形式上的枷锁。我的判断原则很简单这个功能如果写错一行会不会产生不可逆影响或者重大返工如果会值得用规范驱动如果只是展示层微调走普通 PR 就好。规范是手段不是目的拿规范去约束每一个小小改动结果就是没人再把规范当回事。6.2 与 ADR、测试、全局规范的互补关系OpenSpec 落地后你可能会担心它跟团队既有的文档体系冲突。我自己的体验是它并不和 ADR 冲突ADR 记录一个重要决策“为什么选 A 不选 B”是叙事性的OpenSpec 的 spec 记录“这个决策之后我们应该遵守什么行为”是契约性的。前者解释来路后者约束未来。两者放在不同目录各管各的还能互相引用。全局规范层也能继续放大价值。我们在 standards 里固定了“所有错误码必须符合模块三段式命名”和“所有回调消息必须包含 request_id 用于追踪”。每份 spec 只需要引用这些标准不用重复抄一遍内容既保持全团队一致的风格又降低单份规范的篇幅压力。6.3 归档目录在团队里发挥出的另一个价值刚用 OpenSpec 时我只把它当成 AI 编程的输入规范工具。三个月之后我发现它的另一个价值体现在新人培养上。很多项目管理混乱的团队持续时间长了后连个正经文档都没有新人来了只能靠读 git log 和猜commit 来理解系统。归档后的 changes 目录就不一样它就像一本“行为史书”每一条变更记录了当时为什么做、承诺了什么、怎么实现的。新人按顺序读一遍 archive 里的变更历史能很快在自己脑海里建立起系统的行为地图。这个价值是在我们真正积累了几十条变更之后才感受到的。6.4 工具还年轻建议内部固化模板最后说下生态现状。OpenSpec 目前算是比较年轻的开源项目CLI 的命令和目录约定在不同版本之间会有变化这也是我在这篇文章里没有把所有命令都写死的原因。如果你想长期采用我的建议是先在团队内部固化一套自己的模板和校验脚本把对工具版本的依赖降到最低。具体来说可以生成一份内部的 spec 模板、一份 tasks 模板、一个 CI 校验脚本。这样即使上游工具迭代团队流程最核心的部分仍然稳定运行。工具会变但“先把规范写清楚再让 AI 干活”这个思路不会过时越早固定下来越省心。最后说一点个人体会。OpenSpec 不会替你写需求也不会自动让 AI 写出完美代码它只是强迫你把自己对需求的定义变成一份可验证的契约而这恰恰是 AI 协作中最稀缺的东西。AI 时代会写代码的人很多能把“要什么”讲清楚的人反而是瓶颈。如果你也在用 AI 编程不妨从下一个小功能开始试一次——先写五十行规范再让 AI 动手。哪怕不引入完整流程只把验收标准写在提示词里后续 review 和返工的体验都会不一样。另一个很实用的小技巧是每个变更的规范摘要直接贴在 PR 描述里团队成员的评审效率会提升得很明显。这些都是我在实际项目里亲身试验出来的也希望这套方法能在你的团队里跑出一条自己的节奏。