AI 编程失控?用 OpenSpec 规范驱动开发实战指南
AI 编程时代的规范驱动开发OpenSpec 实践指南过去半年我几乎把所有编码任务都交给了 AI 助手最初很爽后来发现一个问题AI 生成的代码越来越不可控。功能写着写着就跑偏改 A 模块顺手破坏了 B 模块的边界问它当初为什么这么设计它自己都说不清楚。代码量上去了可维护性下来了验收全靠肉眼上线全靠胆量。直到我把 OpenSpec 这套规范驱动的流程引入日常开发情况才有了本质变化。这篇东西不是官方文档的翻译是我实际跑了几个迭代周期之后的经验总结。如果你正在用 AI 写代码又被反复无常的需求变更、失控的上下文窗口、说不清道不明的验收标准折磨过这篇文章应该能帮到你。1. 为什么 AI 编程需要“规范驱动开发”1.1 AI 生成代码的三大痛点先说痛点。第一是方向漂移。你让 AI“加一个用户注册功能”它能在你本地代码里自由发挥出一个带邮箱验证、短信验证、第三方登录、验证码滑块的全套系统。问题不在于它做得太多而在于没人能约束它在当前迭代里做什么、不做什么。第二是上下文丢失。AI 对话窗口有限项目一大它就要么忘了前面约定好的接口签名要么凭空编造出不存在的工具函数。我见过最离谱的一次AI 为了“实现某个需求”在代码库里自动创建了三个它自以为需要的新文件——而这三个文件和已有模块完全重复没人 review 出来。第三是验收模糊。传统开发的验收标准是需求文档、测试用例、产品验收。AI 生成代码时代验收标准变成了你人肉去看它写了几百行 diff靠经验和直觉判断“好像没什么大问题”。这本质上是一种质量失控长期堆下去项目就会变得非常脆弱。1.2 从“对话驱动”到“规范驱动”传统 AI 编程方式是对话驱动你说了算AI 照做。但自然语言有歧义越聊越长AI 越往后越容易把前面的话忘掉。规范驱动不一样它把需求和约束先写成结构化的文本规范——就是让 AI 先看规范再照着规范实施。打个比方对话驱动像是你雇了一个很能干的实习生口头交代几句就让他干活规范驱动则像是你先把自己想要的东西画成图纸让实习生照着图施工。图纸可能不完美但至少偏差可控。OpenSpec 这套方法论的核心就是把人类意图翻译成一种 AI 和人都能读懂的规格文件specifications存在仓库里作为每次开发和评审的“锚点”。它不是文档生成器它的定位是一套变更协议——每次编码前、编码中、编码后AI 都要回到规范上来对齐。1.3 OpenSpec 是什么不是什么OpenSpec 本质上是一套基于文本文件的轻量级规范系统。它约定了一个仓库里 specs 目录的结构定义了 specification需求规格的核心模块——描述、需求、场景、测试矩阵——靠这些文件驱动 AI 完成开发任务。它不帮你写代码不替代测试框架也不做任务管理它做的是把“要做什么”和“怎么验收”固定下来让代码生成有据可依。这套方法特别适合三种情况代码库有一定规模AI 开始频繁失忆多人协作希望 AI 的改动可审查、可回滚、可追溯对交付质量有要求不想每次合并都提心吊胆2. OpenSpec 的核心概念与文件结构2.1 仓库里的三个关键文件初始化 OpenSpec 之后项目根目录会出现三个核心文件openspec/openspec.md、openspec/ai-codebase.md和openspec/project_context.md初始化时可选生成。这三个文件各司其职缺一不可。openspec.md是总纲定义了规范格式、组件component的概念、规范状态机的流转规则、验收标准的编写要求。你可以把它理解为项目里所有 AI 协作规则的“宪法”。ai-codebase.md是给 AI 看的代码库地图里面记录目录结构、模块职责、架构约束、已有的组件清单。AI 每次开工之前都要先读一遍这个文件避免在错误的地方乱改。project_context.md存的是项目背景、目标、已知限制、用户群体的上下文信息帮助 AI 做更高层的决策。这三个文件不是写完就完事它们在每个迭代里都会不断更新。比如新增了组件ai-codebase.md的组件清单就要同步。忘了更新AI 就会基于旧地图施工效果自然打折扣。2.2 Specification 的四大核心模块一个规范文件的核心由四块构成说明Description、需求Requirement、场景/用例Scenario/Use Case、测试矩阵Test Matrix。说明是人类可读的概览用两三句话解释这次变更要干什么。需求是结构化的功能要求列表每一条都被标记为“必须”“应当”或“不应”优先级的语义是后续测试和验收的基础。场景/用例描述的是端到端的实际行为轨迹比如“当用户点击注册按钮时系统应显示验证码框”。测试矩阵则直接映射到测试用例。我实际写的时候四个模块里最花心思的是需求模块。你写得越具体AI 实施起来越省事。写“系统应支持用户注册”和写“当用户以邮箱密码形式提交注册请求时系统应创建一条新用户记录并向该邮箱发送验证链接”后者的实施准确率会高出一大截。需求的最佳格式是“条件 行为 结果”即“在什么条件下系统应做什么达到什么结果”。2.3 组件的生命周期状态OpenSpec 有个状态机的概念每个 specification 都处于一个生命周期状态草稿draft→ 讨论discussing→ 已批准approved→ 已实施implemented→ 已验证verified。草稿状态表示规范还不稳定idea 可能随时改。讨论状态表示人在评审、调整措辞、查漏补缺。已批准是锁定的信号AI 可以开始干活了——这个状态的意义非常重大因为 AI 在实施过程中改需求是最常见的失控源头。已实施表示代码改了但还没跑测试。已验证表示测试矩阵全部通过变化可以合并了。很多人觉得状态流转是形式主义浪费时间。但正是这种显式的推进才让“谁该做什么”变得清楚。你如果想让 AI 自己持续推进某个任务就不能让它看到一个全是“草稿”状态的仓库它不知道该信哪条、改哪条。3. 从零到一OpenSpec 实操全过程3.1 环境准备与初始化我的开发机是 macOS项目是 Python 服务端 TypeScript 前端。为了体验 OpenSpec 的完整能力我用的是最新版的 OpenSpec CLI直接通过 Homebrew 安装brew install openspec如果是 Linux 或者想用二进制包去官方 Release 页拉一份对应平台的压缩包解压到 PATH 就行。装完先验证一下命令能用openspec --version接着初始化仓库。我一般直接在已有代码仓库根目录跑openspec init这个命令会在当前目录生成openspec/文件夹和三个核心文件并且自动生成.gitignore规则把openspec/.openspec临时状态目录忽略掉。初始化的输出大概长这样Initializing OpenSpec... Creating specification directory... Creating default agent... Copying openspec.md, ai-codebase.md... Done.初始化完成后我强烈建议把openspec/这个目录纳入版本控制提交一次“chore: init openspec”。这样整个团队的 AI 协作约定就从第一天开始有迹可循了。3.2 第一个场景为镜像管理工具添加自动清理策略为了把流程讲透我拿一个虚构的场景来演示某团队有一个镜像管理工具用户可以手动触发清理但希望支持“自动清理策略”即在满足某些条件时系统自动删除过期的镜像。准备工作完成之后我在终端里创建组件。命令格式是openspec component create image-cleanup这个命令会创建specs/image-cleanup/目录。结构大概是specs/ image-cleanup/ specification.md proposals/specification.md是空的规范文件proposals/目录用来存放相关方案讨论稿可选。组件名要有语义比如image-cleanup就是「镜像清理」策略不要叫feature1之类的名字。3.3 编写规范文件需求与场景的打磨接下来是核心环节——打开specification.md往里写内容。我拿我当时写的「自动清理策略」规范来举例# Specification: 自动清理策略 ## Description 本特性允许用户配置自动清理策略系统根据策略定期删除超过保留期限的镜像。 ## Requirement - REQ-1: 系统必须支持创建自动清理策略。 - REQ-2: 自动清理策略必须包含保留天数、执行时间、目标仓库范围字段。 - REQ-3: 系统必须根据策略的执行时间定期运行清理任务。 - REQ-4: 系统必须记录每次自动清理的执行日志。 ## Scenario ### SCEN-1: 创建清理策略 - **When** 用户提交一个包含保留天数、执行时间和目标仓库范围的策略 - **Then** 系统创建策略 - **And** 返回创建成功的策略 ID ### SCEN-2: 策略触发执行 - **Given** 已存在一个保留天数为 7 天的策略 - **When** 到达策略的执行时间 - **Then** 系统扫描所有目标仓库中的镜像 - **And** 删除所有超过保留天数的镜像写需求的时候有个隐含前提需求的标准句式是“must 行为 对象”场景用 BDD 风格的 Given-When-Then 叙述。我从实际测试中感受到假如需求写得像功能列表AI 生成的测试矩阵还行如果需求写得含糊不清测试矩阵就直接跑偏。还有个细节命名规范。每条需求、每个场景都要有唯一标识比如REQ-1、SCEN-1。后面测试矩阵、AI 实施记录、验收讨论都要引用这些 ID没有 ID 的规范文件是没有骨架的。写完规范后执行校验openspec validate image-cleanup校验全部通过后这个规范文件就可以进入评审状态了。3.4 用 CLI 管理生命周期从草稿到已批准我常用的生命周期相关命令只有三个# 把规范推进到已批准状态 openspec lifecycle image-cleanup approve # 实施完成后标记为已实施 openspec lifecycle image-cleanup implement # 测试全部跑通后标记为已验证 openspec lifecycle image-cleanup verifyCLI 还支持openspec review生成评审内容、openspec plan生成开发计划等命令不过我在实际操作中最常用的还是这三个状态流转命令。有个值得列一下的状态流转顺序草稿文件创建内容完了但还没人审讨论提了评审意见在改已批准评审通过锁定需求已实施代码改完了已验证测试矩阵跑过了从实际感受来说把approve当成分水岭是有道理的。approve之前的对话是需求澄清approve之后的对话是实施落实。不要混着来混着来 AI 就会在改代码的中途凭空增加需求项目就又回到不可控状态了。3.5 与 AI 助手协同工作区写入与上下文循环规范文件写好后如何让 AI 真正读到并执行这里有个实操技巧把规范文件内容写入 AI 的上下文。以我常用的 Codex CLI 为例命令行codex 从 openspec/ 和 specs/image-cleanup/specification.md 中读取规范然后基于当前规范实施 image-cleanup 功能的开发和测试这一步的目的是把 AI 的工作模式切换成“先读规范再写代码”。你不需要把所有需求都贴进对话里你只需要告诉它规范文件在哪里它会自己去读。关键是 AI 的 worktree 需包括openspec/和specs/目录否则它就读不到。OpenSpec 默认推荐把整个仓库作为 AI 的工作区这样 AI 可以看到所有规范也能在正确的位置创建新文件。我在实践中发现一个高频问题AI 读到规范之后会一次性把所有需求全部实现完不管架构允不允许。这时候需要用的招数是在对话里强调“按规范顺序实施每个需求完成后停一下等我 review”。这个节奏感对最终代码质量影响非常大。4. 工作流进阶结构化变更、验收标准与团队协作4.1 结构化变更的优势小步提交与可审查性OpenSpec 的核心方法论是把每次变更拆成“规范 → 实施 → 验证”的小循环。每个循环结束之后代码库才往前走一小步。步骤小意味着每一次提交的 diff 都可控。我对比过同样一个功能直接让 AI 一把梭写完 vs 用 OpenSpec 拆成规范逐步实施后者的代码 review 时间平均能缩短一半以上。具体做法是每次实施完一个 specification就强制产生一个提交。提交信息直接引用规范 ID比如git add . git commit -m feat(image-cleanup): implement REQ-1, REQ-2 and REQ-4这个提交信息让 git log 和规范文件两边的信息能对上未来追溯需求时你不可能靠记忆要靠 ID。4.2 验收标准从“我觉得可以”到“矩阵全绿”OpenSpec 的验收体系里测试矩阵是最后一道防线。在之前那个清理策略例子里测试矩阵的写法大概长这样## Test Matrix | 测试用例 ID | 对应需求 | 场景 | 预期结果 | |---|---|---|---| | TC-01 | REQ-1 | SCEN-1 | 创建策略成功 | | TC-02 | REQ-2 | SCEN-1 | 策略字段完整 | | TC-03 | REQ-3 | SCEN-2 | 到达执行时间后清理任务被触发 | | TC-04 | REQ-4 | SCEN-2 | 清理日志写入系统 |测试矩阵最忌讳的是“只测主路径”。每个需求都要有对应的测试用例尤其是边界情况和异常路径。比如「保留天数为 0 时怎么办」「执行时间为空时系统应报错还是自动默认」这类这些坑不在测试矩阵里列出来AI 是不会主动帮你补的。实操中可以让 AI 生成测试矩阵但人类必须 review。AI 倾向写“乐观测试”——只证明正常路径能跑而验收需要的往往是“悲观测试”——证伪一堆异常路径。规范文件的测试矩阵就是人机博弈的最后一块阵地。4.3 团队协作AI 代劳重活人管好规范OpenSpec 最理想的使用场景是“AI 干重活人看规范”。一个完整的迭代循环应当是人写规范草案 → 人机讨论完善 → 需求锁定 → AI 实施 → 人审代码 跑测试 → 更新规范状态 → 合入。这里有一个我后知后觉才重视的点规范的变更记录也是信息资产。每次规格文件的修订都应该有明确的变更描述。如果没有变更历史三个月后你根本看不出这条 REQ 是因为什么被砍掉的。OpenSpec 在初始化时提供了相关辅助文件但你可以选择用 git 的 blame 来追溯前提是每次规范变动都单独提交。另外一点OpenSpec 创建的默认 AI 助手模板agent 目录下非常好用。它会引导 AI 自己执行一系列规范相关指令。我用下来感觉不太需要手动改 agent 模板因为它本身已经收敛了较好的工作流但如果你有特殊要求比如要求 AI 每次修改都先跑一遍已有测试可以自行调整。5. 常见问题与排查技巧实录光说好话没有意义这篇实践指南里我把踩过的坑也写清楚。5.1 AI 角色卡住从未检测到更改使用 OpenSpec 时有个高频问题规范的批准状态已经到approved但 AI 却回复说“这些不是代码我没有实施任何更改”。这种情况通常出现在与 AI 助手的对话上下文中AI 认为规范文件只是文档不需要写代码。排查思路是这样的先确认 AI 的指令是不是“从规范中读取需求并实施到代码库”如果指令太弱比如只说“阅读规范”AI 当然只读不写再确认你给 AI 的仓库路径是否正确尤其是 monorepo 结构下如果它没有读取到正确的specification.md它会认为无事可做最后是检查当前对话里有没有残留的“只读模式”有些 AI 客户端有独立参数控制代码写入这个参数不小心被翻到“禁止写入”AI 就永远不会碰代码。我的解决办法在每次新的开发任务开始前用一句话把任务交代清楚——“现在请基于规格文件实施这些需求跳过 schema 设计直接修改代码”。这句话说完AI 基本就知道要干活了。5.2 校验失败引用不存在的文件某次我把一个原有系统描述写进规范文件引用了src/utils/cleanup-scheduler.ts这个路径但当时代码库里根本没有这个文件。执行openspec validate时直接报错因为校验器会检查引用路径与仓库当前文件系统的一致性如果配置了依赖检查。这种问题的根因是你把“目标状态”写进了规范但代码还处于“当前状态”。解决思路是调整措辞规范里应该描述行为目标而不是预先假定文件路径。更好的描述是“系统应有调度器组件负责依据策略触发清理任务”让 AI 自己决定实现路径。如果规范里必须引用现状代码那该代码必须真实存在。5.3 AI 一次性输出过长导致 token 耗尽会话断了AI 客户端一次性生成太长的代码内容时极易触发上下文窗口上限导致回复被截断甚至干脆报错。尤其是 AI 试图在单次回复里完成整个 specification 的所有 REQ 时。我的做法是把大规格拆成小批次。在对话里明确指令“只实施 REQ-1 和 REQ-2REQ-3 和 REQ-4 等下一轮再做”。每完成一小批就提交并更新规范状态。如果本身就断了会话就重新开一个对话并把规格文件路径和当前进度重新贴给 AI让它从那批继续。5.4 应用分析误判把大项目当成小项目有一次AI 在读取项目上下文时只扫了前几层目录就天真地认为这个项目只有十几个文件导致后续的计划和代码结构设计全部基于错误前提。这种情况在大型 monorepo 里尤其明显。解决办法是为 AI 提供更明确的结构描述要么让它先执行find . -maxdepth 3 -type d | sort | head -100要么直接把ai-codebase.md里维护的目录树给它。OpenSpec 的ai-codebase.md在这里的作用就是帮 AI 快速建立准确的地图而不是依赖它自己乱逛。5.5 重命名后 ID 变化导致引用断裂给组件改名或者移动 spec 文件路径之后最容易出现的问题是所有引用老 ID 的地方没有同步更新比如另一个 spec 的测试矩阵里引用了老的 component 名。OpenSpec 校验器遇到这种情况会直接报错。建议是尽量避免重命名如果非要重命名那么全局搜索旧 ID 一次性替换而不是靠肉眼。规范文件规模一旦到 10 个以上ID 引用链就会变得复杂手改一定漏。5.6 分支名带斜杠导致 CI 误判这是一个真实踩坑分支名feature/image-cleanup带斜杠在 CI 脚本里被当成目录层级解析导致warn: branch contains /的提示后面的发布流程直接没有执行。这不完全是 OpenSpec 的问题但用 OpenSpec 管理分支时容易遇到因为它的默认习惯是组件名加版本号创建分支。我的经验是分支名不要用斜杠改成feature-image-cleanup之类必须在 CI 配置里处理好分支名的清洗。没有这条你的流水线会静默失败而你以为代码已经完全验证过了。5.7 测试矩阵与真实测试不一致测试矩阵写了 TC-01 到 TC-04但实际执行测试时只跑了 TC-01 和 TC-03AI 就报告说“全部通过”。这种问题本质上是 AI 对“验证”的理解和人不一样——它认为代码能编译就等于验证了而你要求的是真实跑测。我建议在规范里显式写明验证方式比如“打开终端运行npm run test:components确保所有测试用例 ID 均在测试报告中出现”如果采用自定义验证器也要在给定指令里强调执行它。6. 关于规范质量的最后一点提醒现在回看OpenSpec 最有价值的不是我列出来的这些状态流转而是它逼着你在动手写代码之前先把“要做什么”想明白把“什么叫做完”界定清楚把“怎么验收”固化下来。我在持续使用 OpenSpec 后发现一个习惯性改变我不再直接跟 AI 说“帮我写个 XX”而是先花二十分钟把规格文件写出来读一遍改一遍然后再让 AI 进场。从结果看虽然前期多了规范写作的环节但后期省掉的返工时间远超前期投入。代码质量更稳更重要的是项目里所有参与的人都能在同一个维度上讨论“这次变更到底合不合规”——这种安心感是对话式 AI 编程给不了的。如果你正准备在一个稍微正经一点的项目里认真用 AI 写代码OpenSpec 这套规范驱动的思路值得尝试。先从一个小功能开始写一份规格文件跑完一轮生命周期你就能感受到它的分量。