
OpenSpec 使用教程把“需求文档”变成工程基础设施搞了这么多年软件研发我一直被一个问题困扰规格说明总是和代码脱节。需求散落在在线文档、聊天记录、会议纪要里开发做起功能来全凭口口相传等代码写完文档早就过期了下一个接手的人只能对着代码反推业务逻辑。前一阵子我接触到了 OpenSpec它提出的思路很直接——把规格说明本身当成代码仓库里的头等公民用一套结构化的规范文件替代乱七八糟的文档碎片。这玩意儿目前社区讨论热度正在起来但完整的中文教程还挺少。这篇文章把我最近用 OpenSpec 梳理项目需求的完整过程复盘一遍包括核心概念拆解、目录结构设计、变更集机制、实际操作步骤和踩过的几个坑适合正在苦恼需求文档难维护、变更追踪靠人肉记忆、评审全靠开会口述的独立开发者和中小团队参考。1. 项目整体设计与思路拆解1.1 OpenSpec 到底解决了什么问题先聊一下我看到的痛点。传统的文档驱动开发流程里一个功能从想法到落地通常要经历“需求文档 → 技术设计 → 编码 → 联调 → 上线”这么几个阶段。问题出在第二个环节到第三个环节之间技术设计往往写得很粗开发者拿到手以后凭经验把设计翻译成代码等代码写完了这份设计文档就成了历史文物再也没有人更新它。团队成员换几轮以后系统逻辑就变成了大爷们脑子里的传说。OpenSpec 的思路是把规格说明文件直接放进 Git 仓库用一套结构化的 markdown 文件来描述业务规则、使用场景、验收标准和实现细节然后通过命令行工具把这些说明文件变成可执行的工作流。你提交代码的时候顺便提交规格文件的变更代码评审的时候规格文件也要一起评审CI 流程里可以校验规格文件格式和覆盖情况。它的核心价值我用一句话总结让需求定义和代码实现同步演进让“文档过期”这件事从物理上变得不可能。1.2 设计理念当需求描述变成代码评审的对象我最欣赏 OpenSpec 的一点是它重新定义了“需求描述”在项目里的地位。传统方法里需求描述放在需求管理工具里和代码仓库是两套系统中间靠人肉同步。OpenSpec 把规格说明做成了代码仓库的一部分这意味着它天然具备版本管理、变更历史、分支隔离、评审流程这些工程能力。举个生活化的类比以前的需求管理方式就像家里贴满便利贴每张上写着一句“记得买酱油”贴得多了谁贴的、什么时候贴的、哪条已经过期了全乱了。OpenSpec 的做法是把便利贴整理成一本家庭账册每次更新都要记录改动人、改动时间和改动原因买东西之前先查账买完回来销账。这个设计的好处体现在三点变更可追溯任何一个规格文件的改动都有 commit 记录什么时候谁改了什么规则清清楚楚。评审前置代码评审时可以同时审查规格变更避免“代码实现了但是需求定义早就变了”的尴尬局面。自动化落地CLI 工具可以根据规格文件的组织结构自动生成工作流比如列出所有待评审的 proposal、检查某个需求是否被覆盖。2. 核心细节解析与实操要点2.1 规格目录结构一切从 opspec 文件夹开始先用一个真实项目的目录结构来说明 OpenSpec 是怎么组织信息的。在项目根目录下初始化 OpenSpec 以后会生成一个opspec/文件夹内部结构类似这样opspec/ ├── project.md ├── specs/ │ ├── cart/ │ │ ├── spec.md │ │ └── scenarios/ │ │ ├── add-to-cart/ │ │ │ └── adds-item-to-cart.md │ │ └── remove-from-cart/ │ │ └── removes-item-from-cart.md │ ├── checkout/ │ │ ├── spec.md │ │ └── scenarios/ │ │ └── process-payment/ │ │ └── processes-payment-successfully.md │ └── user/ │ ├── spec.md │ └── scenarios/ │ └── user-registration/ │ └── registers-new-user.md ├── changesets/ │ └── 20250212_add-user-registration/ │ ├── proposal.md │ └── user/ │ └── specification.md └── worktrees/核心目录是specs/和changesets/。specs/下按业务域如cart、checkout、user拆分每个域有一份spec.md描述整体规则一个scenarios/目录描述具体场景。changesets/则是待处理的变更集配合 Git worktree 使用形成一套提案-评审-合并流程。这里的组织方式和 Clean Architecture 的分层思路很像业务域是最高维度场景是业务域下面的能力点每个能力点对应一个输入输出的完整描述。写起来的时候不用想架构只需要想清楚“我这个系统有哪些业务域每个业务域下有什么场景”剩下的结构 OpenSpec 自动帮你铺好了。2.2 规格文件的书写规范与逻辑层级规格文件的书写有一套约定俗成的结构。spec.md是整个业务域的入口文件里面要声明该域的需求和场景清单。以购物车域为例# Cart Domain Specification ## Requirements - 用户可以添加商品到购物车 - 用户可以修改购物车中商品的数量 - 用户可以删除购物车中的商品 - 购物车中的商品数量不能超过 99 ## Scenarios - add-to-cart: 添加商品到购物车 - remove-from-cart: 从购物车移除商品每个场景文件则定义一条具体的规则描述用 Given-When-Then 的形式展开# Adds an item to the cart ## Requirements - 添加商品时如果商品已存在数量自动累加 - 添加商品后返回购物车的最新总金额 ## Scenario: 基础添加流程 - **When** 用户发送添加商品请求商品ID: 88数量: 1 - **Then** 购物车创建一条明细记录商品数量为 1 - **And** 返回购物车总金额为 88 元 ## Scenario: 重复添加商品 - **When** 用户再次添加商品ID: 88数量: 2 - **Then** 购物车原有数量从 1 累加为 3 - **And** 返回购物车总金额为 264 元我一开始对这种写法有点抵触觉得它像测试用例又不像测试用例。用了一段时间以后发现这种格式的价值在于强制你穷举场景边界。当你必须把“When... Then...”写清楚的时候那些含糊其辞的“用户应该能方便地管理购物车”之类的话就写不出去了。如果你发现自己写不清 When 和 Then大概率是你自己都没搞明白需求。2.3 变更集 Worktree 机制让需求评审像代码评审一样严谨changesets/是 OpenSpec 最值得称道的机制。它借鉴了 Git worktree 的场景隔离思路把每个待开发的需求提案放在独立的目录里目录名是提案唯一标识里面包含一份proposal.md和按域组织的规格变更文件。工作流程是这样的基于主仓库切一个 worktree 分支。在opspec/changesets/下创建变更集目录写清楚这个提案改了什么、为什么改。修改对应域的规格文件把新需求、新场景写进去。提交代码发起 Pull Request。评审通过后合并变更变更集里的规格文件会同步到主specs/目录。这个流程的好处是需求变更不再是一句轻飘飘的“改一下规则”而是一条有完整上下文的工程记录。Reviewer 看到的不只是“这里加了个 if 判断”而是“新增了一条规则购物车数量上限 99对应场景文件是 X测试用例是 Y”。3. 实操过程与核心环节实现3.1 环境准备与初始化 OpenSpecOpenSpec 目前提供 Node.js 版本的和 Rust 版本的 CLI建议直接用 Node.js 的 npm 包安装最稳npm install -g openspec/cli或者如果你在用 Rust 的工具链cargo install openspec安装完以后验证一下版本openspec --version在已有项目里初始化openspec init初始化会在项目根目录生成opspec/目录和基础的模板文件。如果你用的是已经存在的仓库建议先提交一版代码再初始化不然opspec/目录的变化会和项目现有改动混在一起看起来不干净。3.2 创建第一个变更集并编写提案假设我们想给电商系统加一个新功能用户注册成功后自动发送欢迎邮件。第一步创建变更集openspec changeset add-user-registration-email命令会在opspec/changesets/下生成一个以当前日期和描述命名的目录。进去以后编辑proposal.md# Add User Registration Email ## Why 用户注册完成后缺少邮件触达导致新用户无法及时了解平台权益完整体验度低。 ## What Changes - 用户注册成功事件触发后系统自动发送欢迎邮件 - 邮件内容包含用户名、注册时间和新手入门指引链接 - 邮件发送失败不阻断注册流程失败信息进入日志系统 ## Impact - User 域新增 event handler依赖邮件服务 - 新增一个异步任务处理邮件发送逻辑这份 proposal 的意义在于它让所有相关方在写代码前对齐三件事改什么、为什么改、影响范围有多大。我曾经见过太多需求阶段吵得不可开交、代码评审时候反而没有分歧的项目原因就是前期根本没有把“What”和“Impact”写清楚。3.3 修改规格文件并跟踪需求变更提案创建完以后在同一个变更集目录下按照域结构创建新的规格文件。OpenSpec 提供了一个交互命令辅助完成openspec butterfly这个命令会读取变更集目录下的所有 markdown自动提取信息并帮助你把当前域的需求和场景整合进临时 spec 文件。如果你更习惯手动控制也可以自己手写我一般用 butterfly 生成初稿后再手动调整效率更高。在变更集里user/域的规格文件增补内容大概长这样# User Domain Specification ## Requirements - [x] 用户可以注册新账号 - [ ] 注册完成后发送欢迎邮件 - [ ] 欢迎邮件发送失败可以重试 3 次 ## Scenarios - registration-email-notification: 注册成功后发送邮件通知注意这里新增了registration-email-notification场景那对应的场景文件也要补全# Sends registration email notification ## Requirements - 注册成功后 1 分钟内发送欢迎邮件 - 邮件内容包含用户昵称 - 邮件服务不可用时发送任务进入重试队列 ## Scenario: 正常发送 - **When** 用户提交注册表单并成功创建账号 - **Then** 系统在 60 秒内触发发送任务 - **And** 邮件内容包含用户昵称和注册时间 ## Scenario: 邮件服务不可用 - **When** 邮件服务返回超时错误 - **Then** 注册流程正常结束不抛异常 - **And** 发送任务进入重试队列最多重试 3 次3.4 验证规格文件格式并用工作流推进合并写完了变更集里的规格文件怎么确认格式没问题OpenSpec 提供了配套命令其中之一会在后台启动一个本地仪表盘让你在网页上可视化查看规格文件的覆盖情况和变更集状态openspec server跑起来以后浏览器打开http://localhost:3000就能看到项目的规格结构。这个页面特别适合团队评审投屏展示比大家干坐着看 IDE 里的 markdown 直观得多。另外一个核心命令是openspec check它会遍历整个opspec/目录检查所有 spec 文件的格式正确性比如每个 scenario 是否都有对应的 When/Then、Requirements 里有没有引用不存在的场景、文件命名是否符合 kebab-case 规范。我在集成 CI 的时候就把openspec check加进了 pipeline规格文件格式不过关直接不让合并省去了大量人工 review 格式的精力。检查通过以后用常规 Git 流程提交代码git add opspec/ git commit -m feat(spec): add user registration email proposal git push origin feature/user-registration-email合并完代码以后OpenSpec 会有一个步骤把变更集里的规格变更合并回主specs/目录完成规格的“落库”。这一步如果用的是 worktree 分支用openspec merge它会自动解析最近合并的变更集把提案中涉及的域合并进specs/对应位置。4. 常见问题与排查技巧实录4.1 变更集目录命名混乱导致合并冲突OpenSpec 默认用“日期 描述”作为变更集目录名比如20250212_add-user-registration-email。如果你一天提交多个变更集日期重复的概率很高如果团队里几个人同时创建变更集目录名冲突更是家常便饭。我一个比较深刻的教训是某次功能拆到一半同事和我同时跑了openspec changeset add-payment-webhook结果两个人的目录名一模一样合代码时先合并的人把另一个人的文件覆盖了找问题找了半小时。建议做法在变更集目录名里加上唯一的短标识比如需求编号或者 jira ticket 号20250212_ops-142_add-user-registration-email。同时约定一个规范变更集目录创建后proposal.md 第一个 commit 必须在 5 分钟内完成避免目录一直处于“半成品”状态。4.2 规格文件更新了但代码没跟上这是整个流程里最容易出现的“假闭环”。提案合并进主分支后规格文件永远领先于代码实现。OpenSpec 本身有一定的约束机制可以通过类似openspec status之类的功能查看变更集在代码中的落地状态但它本质上是文档工具管不住开发者不写代码。我的实操心得是把规格文件的合并当成开发任务的开始而不是结束。创建变更集只是完成了“需求定义”后续要在同一条分支上继续写实现代码然后在联调完成后回到方案阶段用真实验收结果对照规格文件里的 Scenario 过一遍。4.3 场景文件拆太细导致维护成本上升用 OpenSpec 一段时间后很容易犯一个毛病把所有能想到的边界情况都拆成独立场景文件。界面上的一个按钮拆出“正常点击”“快速双击”“点击后即关闭页面”“断网点击”四个场景。结果规格数量爆发式增长写文档的时间比写代码还多。这个问题的根源是把 OpenSpec 当成了测试用例管理工具而它的定位是需求规格定义工具。测试用例该归测试框架管OpenSpec 只需要描述清楚哪条业务规则存在、它的输入输出预期是什么。细节分支留给测试用例去穷举。我个人的经验法则一个业务域下 5-10 个场景是健康的超过 15 个就该回头审视是不是拆粒度过细了。4.4 变更集被 merge 了但主 specs 目录没更新我自己踩过的坑之一提案分支合并到主分支后opspec/specs/目录下的规格文件没有同步更新导致主分支的规格落后于实际代码。后面排查发现是中途手动 merge 代码时绕过了openspec merge这一步把变更集的推进动作漏掉了。避坑技巧在 pull request 模板里把“是否已执行 openspec merge”加进勾选清单或者配置一个手动 CI job专门检查当前 HEAD 下是否有已合并但未同步的 changeset。如果你合并操作比较频繁也可以在合并脚本里串上openspec merge命令做成一条龙操作。我个人在实际操作中的体会是OpenSpec 真正改变的不是文件组织方式而是团队对“需求定义”这件事的重视程度。以前需求评审大家靠嘴说现在所有讨论都沉淀在规格文件里新的开发者翻开opspec/specs/目录就能了解全貌这种体验是用多少并发需求文档都换不来的。最后再分享一个小技巧如果你刚上手玩这个项目别一开始就逼团队全量迁移先挑一个小业务域跑通提案-评审-合并流程等大家习惯了这种工作方式再逐步推广阻力会小很多。