做后端开发的这些年,我越来越确定一件事:软件项目里最贵的从来不是写代码,而是把一群人脑子里对同一个功能的理解对齐到完全一致。尤其这两年大家开始用 AI 编码助手之后,问题变得更明显——你让 AI 改代码,它只能根据仓库里已有的东西猜测需求,而仓库里往往什么都没有。OpenSpec 就是冲着这个问题来的,它是一套规格驱动开发(Spec-Driven Development)的工具链:用结构化 Markdown 描述系统能力,用变更提案管理每一次改动,再通过配套 CLI 把规格变成人和 AI 都能直接消费的工程资产。这篇内容适合正在尝试 AI 辅助开发的团队,也适合那些被"文档过期、测试和需求脱节、改个功能要翻遍 PR 历史才能拼出需求全貌"折磨的开发者。我会从为什么需要规格驱动讲起,逐步拆解 OpenSpec 的目录结构、核心命令、完整工作流,以及我实际跑下来踩过的坑。
1. 为什么需要规格驱动开发:需求到代码之间那截断掉的路
1.1 传统流程里最贵的不是写代码
我见过太多项目是这个状态:需求出现在产品评审的 PPT 里,补充在 IM 聊天记录里,最终以某种被扭曲过的形态进入代码库。文档不是没写,是写了没人敢信——因为大家心里都清楚,文档更新永远滞后于代码。等到功能上线三个月后,新同事想改这块逻辑,只能顺着函数调用关系反推需求,推出来的版本还未必是产品当时拍板的版本。代码评审的时候,大家争论的往往也不是实现方式,而是"这个功能到底该不该这么做"。这不是执行力的问题,是流程里缺了一个环节:需求从来没有被沉淀成一份持续可信的资产。
测试先行的方法论(TDD)解决了一部分问题,它让代码有了可回归的保障,但单元测试验证的是"代码是否按我理解的意图运行",而不是"代码是不是团队真正想要的行为"。BDD 往前走了一步,用 Given/When/Then 描述行为场景,可它最终产出的仍然是测试代码,业务方看不懂,也不大愿意维护。规格驱动开发的思路完全不同:它把"系统应该具备什么能力、每个能力的行为边界是什么、每一次改动到底改变了什么"这三件事,直接变成仓库里的一等公民,和代码一样接受评审、校验、版本管理。
1.2 规格驱动开发和 TDD、BDD 的边界在哪里
规格驱动不是要替代测试,而是把测试前面那段"定义行为"的过程补上。TDD 回答"代码对不对",BDD 回答"功能是否符合预期",规格驱动回答的是更靠前的问题:"系统该具备什么能力,以及我们如何验收这个能力"。它产出的不是测试用例代码,而是任何人——包括产品、测试、新入职的工程师、以及没有感情的 AI 助手——都能读懂的规格描述,测试只是这些规格的验证手段之一。
| 方法论 | 核心产物 | 回答的问题 | 适用阶段 |
|---|---|---|---|
| TDD | 单元测试 | 代码是否按预期运行 | 实现细节 |
| BDD | 行为场景与验收标准 | 功能是否符合业务预期 | 需求验证 |
| 规格驱动开发 / OpenSpec | 能力规格 + 变更提案 | 系统该有什么能力、改动影响什么 | 需求定义与变更管理 |
这个区别在实际协作里非常明显。以前我拿到一个需求,第一反应是"先建分支写代码,边写边问产品"。用 OpenSpec 之后,第一反应变成"先把这条能力写进规格,把验收示例列清楚,评审通过再动手"。看起来多了一步,实际上砍掉了大量返工。
1.3 为什么 AI 时代规格驱动突然变得重要
AI 编码助手普及之前,规格写不写,影响的是"人跟人之间沟通的效率"。AI 普及之后,影响的是"AI 能不能在一开始就做对"。LLM 读代码的能力很强,但它只能根据上下文猜测需求;你要它改一个登录模块,它大概率会参考项目里现有的登录实现、或者它训练数据里的常见模式,而不是你产品经理脑子里那个"必须锁定 15 分钟"的规则。OpenSpec 从设计上就是给 AI 消费的:它有专门的命令把规格汇总成上下文文件,让编码助手在动手之前先读规格。这一点我后面会专门展开讲。把规格喂给 AI,它的生产力才真正踩在需求的地基上,而不是踩在它自己的想象力上。
2. 环境准备与初始化:跑通 OpenSpec 的本地链路
2.1 安装 CLI 和前置检查
我本地是 macOS,Node 环境本来就有,安装走 npm 很顺畅:
npm install -g openspec openspec --version如果你的机器没有 Node 环境,先去装 LTS 版本,再装这个 CLI。装完之后我建议先跑一条openspec doctor,它会检查当前仓库的状态、CLI 的配置是否完整、有没有缺依赖,相当于一个体检命令。不要觉得这一步多余,我第一次在旧项目里跑openspec init之前没体检,结果初始化之后 validate 一直不过,折腾了半天才发现是仓库里有一个历史遗留的openspec老目录,新旧结构冲突了。先诊断再动手,能省不少事。
提示:OpenSpec 的 CLI 还处在快速迭代期,不同小版本的子命令和参数可能略有差异。我在文里写的命令按我实际使用到的版本来,如果执行报错,优先看官方仓库 README 的最新用法。
2.2 openspec init 到底初始化了什么
在项目根目录执行:
openspec init会生成这样一个骨架:
openspec/ ├── project.md # 项目总体描述 ├── specs/ # 已确认的能力规格 └── changes/ # 待实施和已归档的变更注意,它不会给你生成任何示例代码,也不会动你现有的业务目录,只是把规格管理的骨架搭起来。我最开始误以为它会像脚手架一样生成一套完整的目录规范,实际上更接近"在仓库里建立一个规格工作区"。
project.md 是这个骨架里最容易被人忽略、但影响最大的文件。它用来描述项目是干什么的。我建议至少要写清楚三件事:项目解决什么问题、核心用户是谁、主要技术栈是什么。不要写"这是用户中心",要写"这是面向内部运营人员的用户权限中心,负责账号、角色、数据权限三块,前端 Vue,后端 Go,所有接口走内部网关"。这个文件后面会被 openspec ai 命令汇总成 AI 上下文,写得越具体,AI 对后续规格的理解偏差就越小。
2.3 目录结构逐层拆解:specs 和 changes 的分工
specs/下面是按领域(domain)拆分的目录,比如auth、billing、notification。每个领域目录里有两个文件:spec.md和capabilities.md。spec.md描述这个领域的边界和职责,相当于领域的简介;capabilities.md是这个领域的能力清单,也是整套路线的核心资产。能力清单里每条 capability 都包含一段行为描述和若干验收示例,后面我会详细讲怎么写。
changes/下面是变更提案目录,命名规范是YYYY-MM-DD_短横线名称,例如2025-01-20_add-login-lock。每个变更提案目录里默认有proposal.md、tasks.md、tests.md三个文件。可以这样理解:specs/是"系统当前应该长什么样",changes/是"我们打算把它改成什么样"。改动落地之后,变更提案会被归档,其内容回流到specs/里,这就是规格保持新鲜的机制。记住这个"提案-实施-归档"的循环,后面整个工作流都是围着它转的。
3. 核心工作流:一次真实的功能变更从提案到归档
3.1 第一步:把能力写清楚,和实现彻底解耦
假设我要给项目加一个"邮箱密码登录"的能力。在openspec/specs/auth/capabilities.md里,我会这样写:
# Capabilities: 用户认证 ## Capability: 邮箱密码登录 系统允许已注册用户使用邮箱和密码登录,登录成功后返回带过期时间的会话令牌。 ### Examples - [ ] 使用正确的邮箱和密码登录 -> 登录成功,返回令牌 - [ ] 使用未注册的邮箱登录 -> 返回 401,提示"用户不存在" - [ ] 连续输错密码 5 次 -> 账号锁定 15 分钟,并提示剩余锁定时间这里有两个关键点。第一,描述聚焦行为结果,不写实现细节。我明确不写"调用 UserService.login()"这类话,因为一旦写了实现,规格就和代码耦合了,代码重构时规格就废了。第二,验收示例用"前置条件 -> 预期结果"这种任何人能看懂的自然语言,而不是测试代码。这样的好处是产品经理能看懂、能纠错,测试人员能照着写用例,AI 也能照着实现。你甚至可以把它理解为"用人类语言写的契约测试"。
很多人在这一步会犯一个毛病:规格写得像需求文档,事无巨细,恨不得把按钮的颜色都写进去。我的经验是,规格只写"行为承诺",也就是外部能够观察和验证的东西;内部怎么实现、字段怎么命名、表怎么设计,都不是规格该管的事。管得越宽,维护成本越高,最后规格必死。
3.2 第二步:创建变更提案,把"为什么"和"做什么"分开
能力规格更新之后,接着创建变更提案。命令大概是:
openspec change add-login-lock执行后会在openspec/changes/2025-01-20_add-login-lock/下生成proposal.md、tasks.md、tests.md三个文件。我理解这个命令的定位是"开一个需求工单",它强制你在动手写代码之前,把变更的理由、范围、任务、测试计划一次性想清楚。
proposal.md要回答三个问题:为什么改(业务背景和动机)、改什么行为(对现有能力的修改增量)、影响什么(哪些既有能力可能被波及,是否需要迁移)。我见过写得好的 proposal,几乎可以当技术方案评审文档用。tasks.md是拆解后的实施任务,粒度要控制在一个任务对应一次可验证的改动,这样后续无论是人还是 AI 来执行,都能逐条勾选。tests.md写验收测试计划,注意这里是"计划",不是测试代码,它描述本轮变更需要验证哪些场景,真正落到代码里的测试在后面实现阶段完成。
创建提案这个动作本身就是在逼团队"先想清楚再动手"。我实测下来,凡是 proposal 写得含糊的改动,实现过程中返工率明显更高;凡是 tasks 拆得清楚的,AI 都能一步步执行到位。
3.3 第三步:validate 是质量闸门,不是摆设
提案写完之后,跑一遍:
openspec validate这个命令会检查规格和变更提案的合法性:目录结构是否完整、文件名是否符合规范、capability 的 ID 是否有冲突、change 目录命名是否带日期前缀、Markdown 结构是否合规。validate的意义在于,它把"规格是否合格"变成了一道机器可判定的检查,而不是靠人自觉。我会把它加进 CI,只要有 PR 动了openspec/目录,就跑一次校验,不合格直接红掉。
但一定注意:validate通过只代表规格的"形式"合法,不代表系统的行为真的符合规格。行为是否符合,要靠tests.md里定义的验收场景去验证。把 validate 理解成"格式和结构闸门",把真正的测试理解成"行为闸门",两个都要过,这个流程才算完整。
3.4 第四步:实施、验证、归档,规格绝不能漂移
实现阶段就按tasks.md逐条推进。如果是人肉实现,按任务清单开发、提交、自测;如果是 AI 协作,把能力和提案的上下文喂给它,让它按任务清单执行。我习惯在实现过程中随时对照规格里 Examples 的每一条,写一个过一条,确保行为最终能对上。
功能合并进主干之后,最后一步是归档:
openspec archive 2025-01-20_add-login-lock归档动作会把这次变更的能力更新写回specs/下的capabilities.md,并把changes/里对应的提案目录清理或标记为已归档。这个闭环是我认为 OpenSpec 最值钱的设计:它保证了规格不会被遗忘,也不会和代码漂移。归档的时机必须是功能合入主干之后,而不是实现过程中或者测试刚写完的时候,否则规格可能记录了还没真正生效的行为。
4. 命令行速查与进阶玩法:从单机使用到团队协作
4.1 常用命令速查表
我整理了一份自己日常用得最多的命令表,按使用频率排序:
| 命令 | 用途 | 我什么时候用 |
|---|---|---|
openspec init | 初始化 OpenSpec 工作区 | 新项目接入、旧项目首次搭建 |
openspec validate | 校验规格和变更的合法性 | 每次改完规范文件、CI 里必跑 |
openspec list | 列出所有领域、能力和变更 | 快速查看仓库里现在有什么规格 |
openspec change <name> | 创建变更提案 | 新需求开始之前 |
openspec archive <change-id> | 归档已合并的变更 | 功能合并主干后立刻执行 |
openspec doctor | 检查环境和仓库状态 | 遇到诡异报错时第一排查动作 |
openspec ai | 生成 AI 上下文文件 | 准备让 AI 编码助手接手任务时 |
openspec list我特别推荐养成习惯。它比翻目录结构直观得多,一个命令能看出整个系统当前暴露了哪些能力、有哪些变更还挂着没归档。每周跑一遍基本等于给自己做一次项目体检。
4.2 给 AI 编码助手喂规格:openspec ai 是怎么用的
这才是 OpenSpec 在当前这个节点最有想象力的功能。openspec ai会把整个规格工作区的关键信息汇总成专门的上下文文件(我的版本里生成的是 AI 友好的汇总说明,具体输出物随版本演进),把project.md、领域能力、正在进行的变更全都整理进去。实际协作时,我让 Copilot 或 Claude 之类的编码助手在动手前先读这个上下文,再让它按tasks.md逐条实现。
我一个很深的体会:让 AI 写代码之前,先花十分钟把规格喂好,比你在提示词里写一千字"你是一个资深工程师"有用得多。因为规格里是你们团队特有业务逻辑,这是 AI 训练数据里不可能有的东西。之前我让 AI 改过一个支付回调的逻辑,不喂规格时它给我写出了一套常见的通用处理流程,看着没问题,实际上和我们业务对账规则完全对不上;喂了规格和验收示例之后,它立刻按我们的业务行为来实现了。规格驱动开发解决的不只是人的沟通问题,也解决了 AI 幻觉问题。
4.3 多领域拆分和 CI 集成的实战建议
领域拆分的粒度需要拿捏。我见过把整个系统塞进一个specs/core的,结果文件巨大,验证和阅读都费劲;也见过拆得过于零碎,一个只有两条能力的小模块也单独建一个 domain。我的经验是:一个领域对应一个可以独立演进和独立验收的业务模块,比如认证、计费、通知这类边界清晰的模块就合适。两个领域之间如果经常一起改动,说明边界划错了,该合并。
CI 集成很简单,我在流水线里加了一个阶段:
openspec validate只要有改动碰了openspec/目录就触发。团队规范上我也定了一条硬规矩:没有提交变更提案的改动,不允许直接改规格。这不是工具限制能强制的事,得靠团队约定,但 OpenSpec 已经把检查手段给齐了,剩下的就是执行问题。
5. 常见问题与排查实录:我踩过的坑,希望你别踩
5.1 validate 报错的几类典型原因
我自己用下来,validate报错集中在这么几个原因:
| 报错现象 | 常见原因 | 解决办法 |
|---|---|---|
| 目录结构校验失败 | openspec/下缺project.md或目录名不规范 | 按 init 生成的骨架逐层比对,不要手搭 |
| capability 冲突 | 不同领域文件里出现了重复的 Capability 名称 | 改名前先openspec list查重 |
| change 命名不规范 | 目录名没带YYYY-MM-DD_日期前缀 | 重命名目录,或直接用openspec change创建 |
| Examples 格式解析失败 | 预期结果的写法不统一,有的少箭头分隔 | 每条示例统一用条件 -> 结果的格式 |
| 版本升级后老配置失效 | CLI 升级后配置格式有变动 | 跑openspec doctor,按提示重新生成配置 |
最气人的一次是:我手欠把changes/里的一个归档目录手动删了,结果validate报引用缺失,我花了半天才意识到是归档流程没用命令走,把specs/里的更新也绕过去了。从此我定了个规矩:归档必须用命令,决不手动改specs/和changes/的对应关系。
5.2 规格粒度怎么把控:写多细才算合格
这个可能是所有刚上手规格驱动的团队问得最多的。我的判断标准很简单:如果实现的时候你需要去翻代码才能理解这条规格,那就是太粗了;如果规格里出现了 SQL 语句、函数名、字段名,那就是太细了。合理的粒度是"行为承诺 + 可观察的验收结果",比如"登录成功后返回带过期时间的令牌",不写"生成一个 24 小时有效的 JWT,存在 Redis 里 key 为 user:{id}:token"。
还有一条经验:写规格时,把"系统会做什么"和"系统不会做什么"都写清楚。边界条件往往比主路径更能体现业务规则。比如"未注册邮箱返回 401"这条示例,比"正确登录返回 200"更能让实现者少犯错误。一条能力如果主干行为 3 条示例写得完,加上边界条件大概会到 5-8 条,这个数量级是比较健康的。
5.3 团队落地时真正难的不是工具,是习惯
最后说点工具之外的事。OpenSpec 这条链路,单人使用很简单,难的是团队一起用。我遇到过最大的阻力是:规格评审没人认真做,大家觉得这不过是"另一种文档",于是规格很快变成了新的没人看的文档。我的对策是把规格评审纳入 PR 流程——凡是涉及行为变更的 PR,必须附带对应的提案文件链接,评审人第一眼看提案,第二眼看代码。这个习惯养成之后,规格的质量和代码的质量基本就同步了。
另一个常见的团队问题是变更提案和 issue 的关系没理清。我现在的方式是:issue 里只写业务诉求,提案文件里写技术方案和行为变更,两边互相引用,但职责不混。issue 会关闭,提案会归档,规格会更新,整个链条是闭环的,不会出现"issue 关了但没人知道改了什么"的情况。
结尾:一点个人体会
把这些经历串起来看,我觉得规格驱动开发最大的价值,不是产出了一批漂亮的文档,而是倒逼团队在动手之前把行为说清楚。这种"说清楚"的价值,在没有 AI 的时候体现在减少返工和扯皮,在有 AI 的时候体现在让机器从一开始就站在正确的地基上。最后分享一个我坚持的小习惯:每周五花半小时,跑一遍openspec list,看看这周新增了哪些能力、哪些变更还挂着没归档。这个动作比写周报对我有用多了,它让我对项目的理解永远停留在最新状态。如果你也受够了需求和代码长期脱节的日子,不妨从一个小模块开始试试 OpenSpec,先写一条能力,跑通一个变更的提案到归档,你会明显感受到差异。