
先说一个可能不太讨喜的结论过去两年我用 AI 写代码真正被坑得最惨的时刻几乎都不是它写错了语法而是它写对了但写歪了。我描述的是 A 功能它顺手把 B 功能重构了我让它加个字段它把我原来那套校验逻辑删了三天后回来看 diff几百行改动我根本说不清哪些是必要的。后来我把工作流整个换掉用 OpenSpec 做规格驱动开发把AI 编程从随手聊天变成有提案、有规格、有任务清单、有归档的工程流程。这篇就按保姆级的粒度把我这套 AI 工作流从安装、初始化、写规格、走变更、排错到归档的完整链路摊开讲中间踩过的坑一个不落。不管你是刚用上终端型 AI 编程助手的新手还是已经在十几个项目里被 AI 改崩过代码的老手这套东西都能直接抄过去用。版本以 2026 年当下的稳定版为准命令行参数偶有微调遇到对不上的地方以你本机--help为准。1. 我把让AI直接写代码这条路堵死了OpenSpec要解决的真问题1.1 上下文漂移AI改代码时最容易失控的地方先讲清楚病根不然你装完工具也只会觉得它多此一举。AI 编程助手本质是一个看着你给的上下文做概率补全的系统它的输出质量高度依赖你喂给它的信息密度。而绝大多数人的用法是开个对话框打一句帮我加个订单导出功能然后它就基于当前打开的几个文件猜你想要什么。问题在于当前打开的几个文件远远不等于这个系统的真实行为约定。它看不到三个月前你们定的那条规则——导出必须限流、单次不超过五万条也看不到那个隐含约束——所有对外接口的时间字段统一用 UTC 存储。于是它写出来的代码局部看完全合理放到系统里就是一颗定时炸弹。我把这种现象叫上下文漂移随着会话轮次增加AI 对自己应该做什么的理解会缓慢偏离你真正的意图而你和它都没有一个锚点可以把方向拉回来。你越往后聊偏得越远最后还得自己动手收拾。这个问题的解法不是换更强的模型而是给整个过程装一个不随对话漂移的锚。1.2 规格先行的工作流到底改变了什么OpenSpec 提供的这个锚就是规格文件。它把工作流拆成两个明确的世界specs目录存放系统现在已经具备的行为是事实changes目录存放正在提议的修改是候选。AI 每次动手之前先被要求读规格、写变更提案、列任务清单人审过之后再动代码。这个改动听起来只是多写几个文档实际体验完全是两码事。第一意图被固化了。你说加个导出落成规格时必须写成当用户未选择时间区间时系统默认取最近七天这种可判定的句子。凡是写不成这种句子的需求说明你自己也没想清楚这时候拦下来比写完再改便宜得多。第二改动范围被框住了。一条变更提案只允许动它声明的那部分AI 想顺手重构别的模块你在 diff 里一眼就能看见。第三知识沉淀下来了。归档之后规格就是系统行为的权威描述下一个人或者下一轮 AI 会话不用再去翻三个月前的聊天记录。我实测下来最直观的收益是返工率。同一个中等规模的功能模块以前让 AI 直接改平均要来回拉扯四五轮才能收敛走规格流程之后前置多花二十分钟写规格实现阶段基本一到两轮就过。1.3 什么样的项目适合上这套流程不适合的也说清楚不是所有场景都值得上。我总结了一张表你可以对号入座场景特征建议原因多模块协作、有长期维护预期强烈建议上规格沉淀的收益随项目寿命线性增长团队里多人共用同一套代码库强烈建议上变更提案天然就是一份可评审的设计说明需求反复变更、需要追溯建议上归档记录清楚写明哪次改的、为什么改一次性脚本、验证性 Demo不建议写规格的时间比写代码还长纯亏单人小工具、两天就扔不建议收益覆盖不了学习成本遗留系统、行为没人说得清建议渐进上先从新增能力开始补规格别想一次补全还有一个容易被忽略的判断维度AI 参与的深度。如果你只是让 AI 补全一个函数、写个正则那完全不需要。但只要你让 AI理解整个模块然后改规格就是必需品。因为在这种深度下AI 的每一次理解都是一次可能出错的推断规格是唯一能校准它的东西。2. 从零落地init之后目录里到底多了什么2.1 版本与工具的硬性前提装之前先把环境确认一遍我在这上面白白浪费过一个下午。OpenSpec 是一个命令行工具跑在 Node 环境里对 Node 的版本有硬性下限要求太老的版本会在初始化阶段直接抛错而且报错信息不太友好容易让人以为是网络问题。node -v # 建议 20.19.0 及以上低于这个版本先升级 npm install -g openspec openspec --version如果全局安装遇到权限问题别急着加sudo用版本管理工具切换 Node 版本更干净。另外要注意openspec这个命令名在个别环境下可能和别的全局包冲突装完之后第一件事就是跑一下--version确认唤起的是正确的那个。工具链这边还有一个认知前提OpenSpec 本身不生成代码。它只负责管理规格和变更真正写代码的还是你用的那个 AI 编程助手。两者是配合关系不是替代关系。所以你得先有一个能读文件、能写文件、能执行命令的 AI 助手在手上否则这套流程只能走一半。2.2 初始化时那几个选择题怎么答进到项目根目录执行初始化cd your-project openspec init这一步会问几个问题我逐个说我的选法。第一个是用哪个 AI 工具。它会列出一串常见的终端型、IDE 型助手让你选选中的那个会被生成一份对应的规则文件有的叫AGENTS.md有的叫.cursorrules之类。选错也不要紧重新跑一次 init 就能补但不要在项目里同时留好几份规则文件AI 会读串我踩过这个坑——两份规则互相矛盾AI 每轮都在两条指令之间来回跳。第二个是项目上下文。这一步会让你填一段项目说明写进openspec/project.md。这是最容易被敷衍过去、又最影响后续效果的地方。我的建议是别写成这是一个电商系统这种废话要写清三件事技术栈和版本、目录结构约定、以及团队里那些不成文的硬规矩。比如所有数据库访问必须经过 repository 层不允许在 service 里直接调 ORM——这种话写进去AI 后面生成的代码质量差一个档。初始化完成后目录长这样your-project/ ├── openspec/ │ ├── project.md # 项目上下文AI 每次都会读 │ ├── AGENTS.md # 工作流指令告诉 AI 该怎么走流程 │ ├── specs/ # 当前系统的行为事实 │ │ └── capability/ │ │ └── spec.md │ └── changes/ # 正在进行的变更提案 │ └── change-id/ │ ├── proposal.md │ ├── tasks.md │ ├── design.md │ └── specs/ │ └── capability/ │ └── spec.md └── 你的源码2.3 三个核心目录的分工specs、changes、project.md这三个东西的分工必须在一开始就形成肌肉记忆否则后面一定会乱。project.md是长期上下文讲的是这个项目是什么样子的很少改改了说明项目本身发生了结构性变化。specs/是行为事实库讲的是系统现在能做什么按能力capability拆分一个能力一个目录每个目录一个spec.md。changes/是待办世界讲的是我们打算改成什么样一条变更一个目录。关键在于specs和changes/id/specs的关系。后者不是前者的复制而是增量——只写这次要新增、修改、删除的部分用特定的章节标题标记出来。归档的时候工具会把这个增量合并进specs里对应的能力文件。理解这一点为什么我不能直接改 specs这个问题就自解了直接改 specs 就没有评审、没有增量、没有归档记录等于把整个流程的意义抹掉了。我建议在第一次初始化之后立刻手动做一次小改动走完全流程哪怕只是给一个不重要的能力补一条规格。走一遍你对这套目录的理解会立刻从抽象概念变成具体路径。3. 双目录心智模型specs是现状changes是候选未来3.1 为什么不允许AI直接改specs这条规矩我必须单独拎出来讲因为它是整套流程里最容易被破坏、破坏后又最难察觉的一环。你想想看如果 AI 可以直接往specs/里写会发生什么它会在实现功能的同时顺手更新规格让规格去追认代码。这时候规格就退化成了一份事后说明书它记录的是代码现在长什么样而不是我们当初约定要做什么。这两者的差别巨大。前者你永远无法用它来校验代码是否正确——因为它是从代码里抄出来的。后者才有校验价值。所以我的做法是specs/目录只在归档动作发生时被写入其他任何时候都不许碰。这一条要明确写进AGENTS.md里用命令式的语气## 硬性约束 - 禁止直接修改 openspec/specs/ 下的任何文件 - 所有变更必须先在 openspec/changes/ 下创建变更提案 - 只有 openspec archive 命令可以写入 specs/我实测过不加这条约束AI 大概三次里有一次会好心地把规格一并改了让一切都看起来对得上。加上之后这个比例降到接近零。3.2 一份合格规格的四段式结构规格文件的写法是有套路的按这个结构来写起来快、看起来清楚、校验也容易过。# 订单导出能力 ## Purpose 把订单数据按筛选条件导出为 CSV供运营做离线分析。 ## Requirements ### Requirement: 按条件筛选导出 系统 SHALL 支持按时间区间与订单状态筛选后导出。 #### Scenario: 未选择时间区间 - **WHEN** 用户未选择时间区间 - **THEN** 系统默认取最近 7 天 #### Scenario: 命中数量超限 - **WHEN** 命中记录数超过 50000 条 - **THEN** 系统拒绝导出并提示缩小筛选范围四个部分标题、Purpose、Requirement、Scenario。其中Scenario 是灵魂。一条需求如果写不出至少一个场景说明它还不够具体工具在严格校验时也会直接报错。而场景必须用WHEN / THEN的句式因为这两个词强制你把触发条件和可观测结果分开写。凡是写不成这样的需求都是语感需求而不是规格需求。还有一个细节SHALL这个词不是装饰。它把建议这么做和必须这么做区分开了AI 在实现时对它的权重判断是完全不同的。想表达可选项就用MAY想表达禁止就用SHALL NOT。这套词表看起来官僚实测对减少 AI 的自由发挥非常有效。3.3 变更提案里的proposal和design各写什么一条变更目录下通常有四个文件很多人会写混。我给它们的分工是这样的文件回答的问题篇幅建议proposal.md为什么要做这件事做完之后用户看到什么变化半页到一页specs/cap/spec.md具体新增/修改/删除了哪些行为逐条可验收按需求数量design.md技术上怎么做有哪些取舍和风险只在复杂变更时需要tasks.md拆成哪些可独立完成的小步骤5 到 10 条proposal.md里最值钱的一句话是为什么不用别的方案。AI 起草时经常跳过这个但恰恰是这句话能帮你在评审阶段发现方向性问题。design.md不是必填项我的经验是改动只涉及一个模块就跳过它涉及跨模块、涉及数据迁移、涉及对外接口变更就老老实实写写完自己通读一遍能砍掉一大堆想当然的方案。tasks.md的作用被严重低估。它不只是个待办清单更是控制 AI 单次工作量的闸门。一条任务如果写不出一句能验证完成与否的话那它就不该是一条任务该拆。4. 走一遍完整链路给一个真实功能做一次规格驱动的开发下面用一个虚构的模拟项目演示完整流程场景是给一个内容管理系统的后台加批量修改文章标签的能力。整个过程按顺序走每一步我都标出人该干什么、AI 该干什么。4.1 第一步把模糊需求压成一条change原始需求大概长这样运营希望能一次给多篇文章打标签不然一篇篇改太慢了。这是一个典型的语感需求信息量极低。我会直接在 AI 助手里这样下指令按 openspec 工作流为后台批量修改文章标签创建一条变更提案。 先不要写任何代码也不要改 openspec/specs/。 change id 用动词开头的 kebab-case。AI 会创建openspec/changes/add-batch-tag-update/并起草proposal.md。这时候我关注两件事一是它有没有自己脑补出一堆我没提的功能比如同时支持批量删除标签二是它有没有写清楚边界最多选多少篇、标签总数上限、权限怎么判。有脑补就砍掉边界没写就补上。这一步的关键心态是宁可在这里多磨十分钟也不要在实现阶段返工一小时。我见过太多次需求阶段一句顺便支持一下 XX 呗最后变成三天的工作量。4.2 第二步写delta规格用WHEN/THEN把验收标准钉死接下来让 AI 在变更目录里写增量规格。注意路径是openspec/changes/add-batch-tag-update/specs/capability/spec.md并且必须带章节标记## ADDED Requirements ### Requirement: 批量追加标签 系统 SHALL 支持对选中的多篇文章一次性追加指定标签已存在的标签不重复添加。 #### Scenario: 部分文章已有该标签 - **WHEN** 选中 10 篇文章其中 3 篇已含目标标签 - **THEN** 这 3 篇保持原标签集合不变其余 7 篇追加成功 - **AND** 返回结果中分别列出成功与跳过的文章 ID #### Scenario: 选中数量超限 - **WHEN** 选中文章数超过 200 篇 - **THEN** 系统拒绝执行并提示当前上限 #### Scenario: 无操作权限 - **WHEN** 当前账号不具备目标文章所属栏目的编辑权限 - **THEN** 系统拒绝执行且不修改任何一篇文章的标签ADDED、MODIFIED、REMOVED这三个标记很关键它们决定了归档时怎么合并。MODIFIED的写法要特别小心——它必须包含修改后的完整需求文本而不是只写差异。这一点我在第 5 章会专门讲因为它是最容易踩的坑。写完跑一次校验别攒到最后openspec validate add-batch-tag-update --strict--strict这个参数建议一直带着。它会强制检查场景格式、需求是否有内容、标题层级是否正确。不带的话很多结构性问题要到归档时才暴露那时候改起来更麻烦。4.3 第三步拆tasks.md让AI一次只做一件事任务拆分是这套流程里最能体现经验的地方。我的拆法有一条铁律每条任务完成之后代码库应该处于一个能跑通的状态。这样任何时候中断、回滚代价都很小。一条实际的tasks.md长这样## 1. 数据层 - [ ] 1.1 新增批量查询文章标签的仓储方法返回文章 ID 到标签集合的映射 - [ ] 1.2 新增批量写入的仓储方法支持事务内部分失败回滚 ## 2. 业务层 - [ ] 2.1 实现批量追加的领域服务处理去重与权限校验 - [ ] 2.2 数量上限校验前置到服务入口超限直接拒绝 ## 3. 接口层 - [ ] 3.1 新增批量追加接口入参校验与错误码定义 - [ ] 3.2 返回结构包含成功列表与跳过列表 ## 4. 验证 - [ ] 4.1 补充覆盖三个场景的用例 - [ ] 4.2 手动跑一遍超限与无权限两条路径注意每条任务都以动词开头且都有明确的完成判据。实现批量追加功能这种写法是无效任务因为无法判断做完了没有。任务数控制在 5 到 10 条之间比较舒服。超过 12 条我会考虑把这条变更拆成两条因为 AI 在一次会话里处理超过十条任务时后半段的完成质量会明显下滑——它开始忘记前面的上下文了。4.4 第四步实现与逐条打勾真正让 AI 写代码时我的指令模板基本固定现在开始实现 openspec/changes/add-batch-tag-update 里的任务。 一次只做一条做完立刻把 tasks.md 里对应的复选框打上勾然后停下来告诉我。 不要动 specs/ 目录不要做任务清单之外的任何改动。一次只做一条做完停下来这个约束是血泪教训。如果直接说把任务全做完AI 会一口气写十几个文件然后你审的时候根本分不清哪段对应哪条任务。逐条来每条看一眼 diff五分钟的审查成本换来的是完全可控的改动范围。打勾这个动作也别小看。它是 AI 判断进度状态的唯一依据如果不打勾下一轮会话它会重新做一遍已经完成的事情。我遇到过一次因为忘了让它打勾同一个服务类被写了两遍第二遍还把第一遍的注释全删了。实现过程中如果发现任务拆错了回头改 tasks.md而不是让 AI 硬着头皮做。任务清单是可以变的变更提案也可以补充但代码库不能处于半成品状态。4.5 第五步validate → archive把变更落账代码写完、用例跑通之后收尾动作是两步# 先校验确保结构没问题 openspec validate add-batch-tag-update --strict # 再归档把增量合并进 specs openspec archive add-batch-tag-update归档之后这条变更会把增量合并进openspec/specs/下对应的能力文件然后整个changes/add-batch-tag-update/目录会被移走通常是挪到归档区。到这一步批量追加标签这个能力才正式成为系统行为事实的一部分后续任何 AI 会话读到规格都会知道这个能力存在以及它的边界。我个人很看重这个归档动作。它让整个仓库的历史变成了一条可读的线每条归档的变更都对应一次明确的能力演进比翻 git log 里的fix bugupdate有意义得多。5. 报错与翻车现场validate过不去、archive冲突、AI绕开流程5.1 validate --strict 最常见的四类报错第一类找不到增量规格。报错大概是说这条变更里没有包含任何 delta。原因通常是 AI 把增量规格写到了openspec/changes/id/spec.md少了一层specs/capability/目录。正确路径必须是changes/id/specs/capability/spec.md中间那两层不能省。第二类需求缺少场景。每条### Requirement下面必须有至少一个#### Scenario。这条规则初看很烦但它逼着你把需求想到可验收的粒度。我的处理方式是写不出场景的需求先别写进规格拉回 proposal 里当成待明确项。第三类场景格式不符合约定。WHEN / THEN必须是加粗的列表项且不能出现应该、尽量这种模糊词。这一条我在早期踩得最多因为我习惯用自然语言描述场景结果是校验过不了、AI 也读不懂。后来我干脆让 AI 先按模板生成我再改内容比手写快得多。第四类标题层级错了。## ADDED Requirements必须是二级标题### Requirement:是三级#### Scenario:是四级。有一次我手贱把 Scenario 写成了三级校验报了一堆莫名其妙的错排查了半小时才发现是井号数量的问题。排查这类问题的通用做法是先跑openspec show change-id把工具解析出来的结构打出来看一眼就能发现哪里没被识别。5.2 归档冲突MODIFIED对不上原文的排查链路这是最难缠的一类问题我完整记录过一次排查链路。现象openspec archive报错说某个MODIFIED需求在目标规格里找不到原文。第一步确认目标能力是否存在。跑到openspec/specs/下面看有没有对应的 capability 目录。如果没有那说明这条需求其实应该是ADDED而不是MODIFIED——你改的是一个还不存在的东西。第二步逐字比对需求标题。MODIFIED匹配原文靠的是### Requirement: xxx里那个标题字符串。差一个空格、差一个标点都匹配不上。我有一次把批量追加标签改成了批量追加标签 末尾多了个空格找了二十分钟。第三步检查是不是被同批次的另一条变更改过。两条变更同时修改同一条需求先归档的那条改了原文后归档的就对不上了。这种情况要么串行处理要么在后者里基于新原文重写MODIFIED的完整内容。第四步确认MODIFIED写的是完整需求。这是最常见的原因。很多人包括早期的我以为MODIFIED只写改动部分实际上它要求写修改后的完整需求文本包括所有场景。只写差异的话归档时会把原来的其他场景全丢掉或者直接校验失败。沿着这四步走我遇到的归档冲突基本都能定位。如果四步都不中最后一招是openspec diff change-id把变更和当前规格的差异打出来对着看问题一般就浮出来了。5.3 AI不理AGENTS.md怎么办第三类翻车是行为层面的AI 明明读了规则文件还是绕开流程直接改代码。我遇到过三种成因对应三种解法。成因一规则文件太长被忽略。AGENTS.md写到几百行之后关键约束会被淹没。解法是把它压到一屏以内只留最硬的几条细节挪到project.md或者单独的文档里。成因二约束写得太客气。建议先创建变更提案这种表述 AI 基本会忽略禁止在未创建变更的情况下修改业务代码才会被当回事。用祈使句用禁止/必须别用建议/尽量。成因三会话太长导致遗忘。长会话里前面的指令会衰减。解法是每做完一条任务就新开一轮或者在做之前先让它复述一遍当前约束。我现在的习惯是每个任务开始前丢一句复述一下当前变更的 id 和本次要做的任务编号成本极低但能挡住大部分跑偏。6. 把流程调成自己的样子模板瘦身与多人协作6.1 按项目体量裁剪模板默认模板是通用的直接用会有点重。我的裁剪原则是按项目体量分三档项目体量保留什么砍掉什么小单人、单一模块proposal specs tasksdesign 全部砍掉场景数量压到 1-2 个中多人、多模块全套保留proposal 压到半页能一句话说清就不展开大跨团队、有外部接口全套 额外的评审记录不减反而要加接口兼容性说明裁剪的边界是规格和任务清单不能砍。这两样是流程的骨架砍了就退化成普通聊天。可以砍的是描述性内容——背景介绍、方案对比、风险分析这些在快速迭代阶段可以简写但不能完全不写为什么不用别的方案这一句。我还会根据项目特点在project.md里加几条自定义约束。做过一个偏数据处理的模拟项目加的一条是所有涉及金额的计算必须用整数分存储禁止浮点。这条写进去之后AI 生成的相关代码一次都没错过。这类约束的价值远高于任何提示词技巧。6.2 多变更并行时的排序与依赖真实项目里很少一次只有一条变更。同时有三四条在跑的时候最容易出问题的是它们改同一个能力。我的处理方式是给每条变更在proposal.md顶部加一行依赖声明 依赖无 阻塞add-article-permission-check然后按依赖顺序串行归档。如果两条变更确实需要并行改同一个能力我会把它们合并成一条或者把公共部分先单独提一条出来做掉。强行并行带来的归档冲突排查成本远高于拆分的成本。还有一个小技巧变更 id 用统一的动词前缀扫一眼目录就知道在干什么。add-表新增update-表修改refactor-表重构remove-表下线。目录一多的时候这个约定能救命。6.3 提交进版本库的正确姿势openspec/整个目录都该进版本库包括changes/。这一点和很多人直觉相反——他们觉得进行中的变更是临时文件不该提交。但不提交的话变更提案就失去了评审价值也没法追踪这个需求是什么时候提的、谁审的、改了多久。我的提交粒度是这样的创建变更提案时提交一次内容包含 proposal 和增量规格任务实现过程中按任务批次提交commit message 带上变更 id归档时单独提交一次。这样 git 历史读起来就是一条清晰的能力演进线。提交信息里带变更 id 这个习惯是为了以后能反查。某段代码为什么长这样git log --grep add-batch-tag-update一搜提案、规格、任务、实现一条链全出来了。这比写一堆注释有用得多。7. 用了三个月之后我留下的几条硬规矩这套流程我用了几个月中间反复调整最后沉淀下来的规矩不多但每条都是踩出来的。第一条规格里的每一句话都必须是可判定的。响应要快不可判定P95 响应时间低于 300ms可判定。写不出可判定句子的时候说明需求还没想清楚回去想。第二条一条变更只解决一个问题。我最多允许一条变更包含 8 条需求超过就拆。拆的时候按能不能独立上线来切不能独立上线的才放在一起。第三条不要相信 AI 起草的第一版规格。它起草的规格通常看起来很完整但边界条件全是模糊的。我的固定动作是拿到草稿之后专门找三类问题空输入怎么办、超限怎么办、没权限怎么办。这三个问题问完规格基本就扎实了。第四条归档之前必须跑一遍完整校验。--strict一次都不能省。省下来的那十秒钟后面可能变成半小时的返工。第五条也是最反直觉的一条规格写得越细AI 写得越少。因为很多需求在写规格的阶段就被否掉了——你会发现这个功能其实没必要做。这大概是这套流程最大的隐性收益它把浪费挡在了动手之前。我个人在实际操作中的体会是刚开始用会觉得步骤多、节奏慢尤其是做小改动的时候写规格的功夫比改代码还长。但只要项目活过一个月账就算得过来了。真正让我彻底接受这套东西的是某次回滚一个已经上线三天的功能因为有完整的变更记录和归档规格我只花了二十分钟就把行为恢复到改动之前而且非常确定没有遗漏。以前遇到这种情况我至少要翻一整天代码。最后一个实用小技巧如果你手上有一堆历史遗留模块没规格别想着一次补全。挑一个接下来要动的模块下一次改它的时候顺手把规格补上用变更流程走一遍。三个月下来你会发现动过的模块都有规格了没动过的也不需要——因为不需要改的东西本来就不需要规格。