ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

OpenSpec实战:规范驱动开发如何让需求像代码一样可管理

OpenSpec实战:规范驱动开发如何让需求像代码一样可管理

OpenSpec 这个词我最早是在一个技术社区的开源仓库 README 里看到的,当时它还没多少 star,但我一眼就被它的定位吸引住了——它不是一个代码框架,而是帮团队把“需求”这件事做成“工程化”的工具。如果你和我一样,受够了需求文档写完后没人维护、PR 里讨论的结论散落在聊天记录里、代码写完了但当初为什么这么设计找不到任何依据,那 OpenSpec 很可能是你一直在找的答案。这篇文章我打算从实际使用者的角度,把 OpenSpec 的安装、初始化、写规范、跑校验、接 CI、带团队落地这一整套流程完整拆一遍,适合后端工程师、技术负责人、以及所有被需求变更折磨过的同学参考。

1. OpenSpec 到底是什么,能解决什么痛点

1.1 一句话定义和适用场景

OpenSpec 是一个开源的结构化规范管理工具,核心思想是“规范驱动开发”。它把所有产品需求、技术方案、接口约定、变更记录全部写成带固定结构的 Markdown 文件,放在仓库里的openspec/目录下,再通过命令行工具来创建、校验、评审、合并这些规范文件。你可以把它理解成一套“需求代码化”的流程:普通代码走 Git 管理,OpenSpec 让规范也走同样的分支、PR、评审、合并流程。

适用场景非常明确。如果你所在的项目有 3 个以上的开发成员,需求文档还停留在 Word 或飞书文档,每次迭代结束文档就变成僵尸文档,那么 OpenSpec 能让你重新掌控信息。我之前带过一个 6 人的后端小组,需求流转靠的是微信群加腾讯文档,结果每次上线后想回溯某条业务逻辑的设计依据,都要翻半天聊天记录。后来我们引入了 OpenSpec,把需求决策全部沉淀到仓库里,至少再没出现过“没人知道这条规则是谁加的”这种问题。

从适用人群上说,它不单是给产品经理用的。OpenSpec 更贴合的是研发团队内部的技术规格管理,尤其是接口设计、数据模型变更、系统行为约定这类需要反复沟通的内容。前端和后端联调时,与其口头对齐,不如先写一份 OpenSpec 变更提案;微服务团队梳理领域边界,也可以用它的规格文件来做轻量级的架构决策记录。

1.2 它和普通需求文档、Jira 工单有什么本质区别

最大的区别在于:OpenSpec 把需求变成了“可执行”的资产,而不是一段被动阅读的文字。

普通需求文档是最终状态的快照,写完就结束了,后续任何讨论和修订都不再同步回文档,所以它慢慢腐烂。Jira 工单则是围绕“任务”组织信息,很难表达“系统的预期行为是什么”“为什么要这样设计”这些层次,任务完成之后知识就封闭在单张卡片里。OpenSpec 恰恰相反,它把需求拆成两个生命周期:一是specs/目录下长期有效的主规范,相当于系统当前行为的权威描述;二是changes/目录下的变更提案,相当于一笔笔待审阅的“需求 commit”。每次变更被批准后,通过openspec apply把变更合并进主规范,于是主规范永远跟最新实现对齐。

我用一个类比来解释:普通文档像拍照片,Jira 像便利贴,OpenSpec 像一套给工程用的“需求版 Git”。它能做到历史可追溯、版本可回滚、差异可评审。你在代码评审里习惯用的那些操作,在 OpenSpec 里都有对应能力,这是它和传统文档工具最本质的分水岭。

1.3 谁最应该用 OpenSpec

如果你是个人开发者写小玩具项目,OpenSpec 可能有点重,因为额外维护规范文件会拖慢你快速试错的节奏。但只要你开始和人协作,尤其是多人维护同一个服务、多个服务之间互相依赖,OpenSpec 的价值就会显现。

我觉得最典型的受益方有三类团队。第一,中后台业务系统团队,业务规则复杂、频繁变化,开发需要随时确认行为边界;第二,平台型团队,需要给上层业务提供稳定 API,接口契约必须明确;第三,刚起步但想建立工程文化的创业团队,与其引入流程重、周期长的传统需求管理工具,不如先从仓库里的规范文件做起。

而且 OpenSpec 的门槛很低,不是那种需要花两周培训的平台。它依赖的就是 Markdown 和 Git 这一套你本来就会的东西,团队成员只需要学会两三个命令就能参与规范的编写和评审。

2. 环境准备与 5 分钟快速上手

2.1 安装方式与版本选择

OpenSpec 的安装方式很常规,我一般推荐通过 npm 全局安装,因为后续升级方便:

npm install -g openspec-cli

如果你不愿意装 Node 环境,也可以直接从官方 GitHub Releases 页面下载对应平台的二进制文件,放到PATH目录下。macOS 上还能用 Homebrew 安装,前提是仓库已经合并到 Homebrew core,不过 npm 方案在所有平台上都最稳妥。

安装完成后,先用openspec --version验证一下。这里我额外提醒一点:OpenSpec 目前版本迭代速度很快,0.x 阶段的小版本之间可能有破坏性变更。我在 0.2.8 版本创建的变更提案,升级到 0.3.1 之后原本的openspec plan输出格式变了,导致我写的一个 CI 脚本失效。所以正式使用前,建议把版本号固定下来,用--save-exact锁死,或者单独封装一个安装脚本。

安装成功后还需要检查 Git 是否已配置好用户信息,因为 OpenSpec 在创建变更提案时会读取user.name和user.email作为作者信息。

2.2 初始化仓库结构

在项目根目录执行:

openspec init

它会自动创建下面的结构:

openspec/ ├── project.md ├── specs/ │ └── .gitkeep └── changes/ └── .gitkeep

project.md是整个规范仓库的入口文件,里面需要填写项目名称、项目描述、以及当前维护人列表。不要在project.md里写太详细的技术方案,它是给人快速了解项目背景用的,一般一两段话就够了。

初始化完成后,建议立刻把openspec/目录纳入 Git 管理并提交一次。如果你们的代码仓库本来就在包含业务代码的同一仓库里,那直接把openspec/提交进去即可;如果你们做的是比较大规模的组织,也可以把 OpenSpec 单独建一个specs-repo,让它在代码库之外独立演进。两种方式我都试过,小团队强烈建议放在业务代码仓库里,这样需求变更和代码变更天然对应,评审时不用在两个仓库之间跳来跳去。

2.3 用模板写一份最简规范

初始化之后你可能想马上体验,最快的方式是手工创建一份最小规范文件,不跑命令,直接写:

# 用户认证服务 ## 概述 负责处理用户注册、登录和令牌刷新。 ## 功能需求 - 用户可以使用邮箱和密码注册账号。 - 注册成功后系统自动发送验证邮件。 - 登录成功后返回访问令牌和刷新令牌。 ## 非功能需求 - 登录接口 P95 延迟低于 300ms。 - 密码必须使用 bcrypt 存储。

这是 OpenSpec 主规范文件最简单的形态。注意我没有加任何 YAML 头,因为在 0.3.x 版本里,主规范顶层只要求有一级标题和若干二级标题,解析器会通过标题层级来识别章节。这跟我们写的技术设计文档几乎一样,学习成本非常低。

写完保存为openspec/specs/user-auth/spec.md,然后运行:

openspec check

如果命令没有任何输出,说明文件通过校验。看到这里你会发现,OpenSpec 的上手其实不需要你先理解什么复杂概念,一个目录、一个 Markdown 文件、一个命令,就完成了第一次闭环。

3. 核心实操:把需求变成规范的全流程

3.1 提交一个变更提案:openspec new

初始化之后,真正的工作流程从“变更提案”开始。假设你们产品现在要求“用户密码输错 5 次后锁定账号 30 分钟”,那我可以执行:

openspec new "账号登录失败锁定策略" --type feature --owner @security-team

执行后,OpenSpec 会在openspec/changes/下创建一个新目录,命名一般是日期加标题的 slug:

openspec/changes/ └── 2025-06-17-account-lockout-policy/ ├── proposal.md ├── tasks.md └── changes/

proposal.md是变更提案的主体,tasks.md是后续由工具自动生成的任务清单,changes/目录用来放对主规范的具体修改内容。这种按时间目录组织的方式非常有利于并行开发,多个提案之间互不干扰,各自的评审状态也很清晰。

为什么我特别强调用openspec new而不是自己手动建目录?因为命令会生成带规范结构的模板,里面已经包含了很多字段,比如当前状态、影响范围、关联模块列表。手动建目录容易漏字段,到openspec check时再去补就很麻烦。

3.2 撰写规范正文的模板字段与要点

执行完openspec new后,打开proposal.md,你会看到类似这样的骨架:

--- name: account-lockout-policy title: 账号登录失败锁定策略 status: draft owner: "@security-team" created_at: 2025-06-17 --- ## 背景 ## 目标 ## 影响范围 ## 详细方案 ## 兼容性 ## 测试计划

这里的 YAML 头是 OpenSpec 非常看重的部分,解析器会用它做状态跟踪和权限判断,不能乱写。几个关键字段的填写建议:

  • status只能填 OpenAI 定义好的状态值,常见的有draft、in-review、approved、applied、rejected,不要自造值,否则校验直接报错。
  • owner填写负责人的 GitHub 用户名或团队别名,后续 CI 评审时可以用它来指派 reviewer。
  • name必须保持唯一,而且创建后尽量不要修改,因为它是目录命名和 Git 分支命名的依据。

详细方案部分不要写成散文,尽量采用列表和表格,描述“输入条件、系统行为、输出结果”的映射关系。比如:

## 详细方案 当用户登录失败时,执行以下逻辑: | 场景 | 行为 | | --- | --- | | 连续失败次数小于 5 | 返回“用户名或密码错误” | | 连续失败次数等于 5 | 锁定账号 30 分钟,发送告警邮件 | | 锁定期间再次登录 | 返回“账号已锁定,请稍后再试” |

这种表格在评审的时候最受欢迎,因为它能立刻暴露业务规则漏洞,比如“连续失败”的窗口期是多长?是按 IP 还是按账号维度?这些问题都能在评审阶段被提前抓住。

3.3 本地校验和干跑:check / plan

提案写完后,先不要急着发起 PR,先用openspec check做本地校验。这个命令会检查:

  • 目录结构和必填字段是否存在。
  • YAML 头里的状态值和枚举是否合法。
  • 变更提案里的格式是否正确,比如不能有重复标题。
  • 引用的其他规范模块是否存在。

校验通过后,运行openspec plan。这个命令很有意思,它不是简单的静态检查,而是根据提案里的行为描述,给出一份实施步骤建议,写到tasks.md里。实际输出有点像生成任务列表:

## Tasks 1. 在登录接口中加入失败计数逻辑。 2. 新建 "user_login_fail_count" Redis 存储。 3. 实现账号锁定状态的查询与写入。 4. 在登录响应中增加锁定剩余时间字段。 5. 补充锁定策略的单元测试和集成测试。

我一开始以为plan只是把标题复述一遍,但实际上它会结合影响范围和详细方案里的关键词来生成更细颗粒度的任务,这非常有用,特别是对于新同学,照着任务清单做就能覆盖大部分改动点。

需要注意的是,plan生成的只是建议,不是圣旨。我在实际使用中往往会在合并进主分支前手动调整tasks.md,把一些评审讨论中新增的改动补进去,保持任务和真正实施内容一致,否则后面的追踪会失真。

3.4 评审通过后如何落地:apply 与任务生成

提案经过团队评审、被标记为approved之后,就可以执行:

openspec apply

这个命令会把changes/目录下处于approved状态的提案内容,合并进specs/对应的主规范文件里。比如前面那份账号锁定策略,apply 之后specs/user-auth/spec.md的功能需求里就会自动多出一项“登录失败达到 5 次后锁定 30 分钟”。

如果提案被拒绝,也建议执行一次归档操作,把状态改成rejected,然后重新跑openspec check。OpenSpec 允许拒绝的提案保留在changes/目录里,方便事后查“为什么没有做某个功能”,这是我觉得比 Jira 强的地方——被否掉的结论本身也是一种重要知识。

apply 过程有一个细节要非常小心:它会根据提案里的changes/目录中的补丁文件,按行号或标题定位插入位置。如果主规范文件已经被其他分支大幅修改过,可能会产生合并冲突。所以我在团队里约定的流程是:提 PR 时只改提案文件和代码,等代码合并后再由专人执行openspec apply,并把 apply 产生的改动单独提交一个“chore: apply spec”的 commit。这样冲突能最小化。

4. 团队协作与 CI 自动化的落地细节

4.1 把 OpenSpec 嵌入 Git 工作流

OpenSpec 和 Git 的关系是天然契合的,因为它就是按 Git 分支模型设计的。我们团队的做法是:每个变更提案对应一个分支,分支名就用提案目录名,比如changes/2025-06-17-account-lockout-policy。

具体流程是这样的:

  1. 从main拉新分支feat/account-lockout-policy。
  2. 在该分支上执行openspec new "账号登录失败锁定策略"。
  3. 写完proposal.md后提交。
  4. 推送到远程,发起 PR。
  5. PR 里除了代码改动,还包含proposal.md和tasks.md,评审人一眼就能看懂代码要解决什么问题。
  6. 代码和提案都批准后,先合并代码分支,再执行openspec apply合并主规范。

这种做法的好处是,需求变更和代码变更完全同步,Reviewer 不再需要去外部系统找上下文。而且分支名和提案名绑定后,寻找历史需求变得非常容易,直接git log --name-only就能定位到对应规范文件。

4.2 在 GitHub Actions 里做规范校验

如果你们用 GitHub 托管代码,配置 OpenSpec 的 CI 其实非常简单。我贴一个我目前仍在用的 workflow 片段,你照着改改就能用:

name: openspec-check on: pull_request: branches: - main jobs: check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-node@v4 with: node-version: 20 - run: npm install -g openspec-cli@0.3.1 - run: openspec check --diff

这里的--diff参数非常关键,它会让 OpenSpec 只检查本次 PR 里新增或修改的提案文件,而不是全量检查整个specs/目录。否则每次只改一行代码,CI 却要把全仓库的规范都跑一遍,浪费时间。

此外,我还在同一个 workflow 里加了openspec plan --dry-run,确认tasks.md能正常生成。它不会改动文件,只是验证我们的提案结构是否足够清晰,能不能被解析成任务。

4.3 项目演进中的变更记录与历史追溯

OpenSpec 工程化之后,最让我觉得舒服的是变更记录和历史追溯能力。每一条业务规则,都能在specs/目录里找到当前状态,在changes/目录里找到它的演进过程。

有一次我们排查线上故障,发现某个用户的订单状态被错误地重开,大家争论了很久“重开订单”是不是允许的操作。后来我在openspec/changes/2025-03-02-order-reopen-policy/proposal.md里找到当时评审时的一段讨论,明确写了“只有管理员角色,且订单在 24 小时内才允许重开”。这个结论作为注释记录在提案里,瞬间终结了争论。

我建议每个团队都约定:在 proposal.md 的“背景”后面增加一个“评审记录”章节,不写流水账,只记录关键质疑和最终结论。尽量让每个决策都有据可查。

5. 常见问题与避坑指南

5.1 命令装不上或者版本冲突怎么办

我遇到过几种比较典型的安装问题。第一种是 npm 全局权限不足,在 Linux 或 macOS 上会报 EACCES,这时候不要偷懒去sudo npm install -g,正确做法是用 npm 的用户级目录,或者直接改用二进制包。第二种是公司内网 npm 镜像同步不及时,导致找到的版本还是旧的,可以先执行npm view openspec-cli version看远程最新版本,再对比本地openspec --version。第三种是 Node 版本太老,OpenSpec 0.3.x 要求 Node 18 以上,建议使用 nvm 切换。

如果你在安装时遇到依赖包编译失败,大多是因为缺少python和make环境,不过这种情况在 macOS 和主流 Linux 上很少见。最简单的兜底方案,永远是下载 GitHub Releases 里的独立二进制,不依赖 Node 也能跑。

5.2 校验一直不通过,问题大多出在哪

我见过刚接触 OpenSpec 的同事最常见的校验错误有这几个:

  • YAML 头里漏了必填字段,比如owner或created_at。
  • status使用了不存在的状态值,比如把in-review写成了reviewing。
  • Markdown 标题层级不连续,比如直接在二级标题下用了四级标题。
  • 提案目录名和proposal.md里的name字段不一致。

针对叼钻一点的格式问题,我的排查方法是先跑openspec check --verbose,它会把具体错误行号打印出来。如果还看不明白,就直接把有问题的文件一半一半注释掉,用二分法定位到出错章节,比肉眼逐个看整个文件快得多。

5.3 历史老项目怎么迁移到 OpenSpec

对一个已经运行了两三年的系统,想一次性把所有历史需求补录到 OpenSpec 是灾难性的,别这么干。我的建议是渐进式迁移:

第一步,先初始化 OpenSpec 仓库,在project.md里写清楚当前项目背景和核心模块清单。

第二步,只挑选最核心、最经常被人问起的模块,比如订单、用户、权限,给它们各写一份主规范。内容只需要概括当前系统行为,不必精确到每一个细分支节。

第三步,从当前迭代开始,所有新需求都必须走openspec new流程。老需求不强制补录,但遇到被新需求影响到的老逻辑,顺手把对应章节补一块。

我按这套方法带团队迁移过一个旧系统,两个月后核心模块的规范覆盖率就到了 70% 以上,期间完全没有影响正常发版节奏。

5.4 规范仓库会不会越来越臃肿

这个是很多团队真正担心的问题,毕竟代码仓库里多了一堆 Markdown,会不会让 clone 时间变长、在 IDE 里碍眼?

我的实际体验是,OpenSpec 的文件都是纯文本,一个几千人的项目,规范文件加起来也就几 MB,完全可以忽略不计。真正需要关注的是changes/目录下堆积了大量已拒绝和已过期的提案。建议每周或每双周做一次例行清理,把超过 90 天且状态为rejected的提案目录压缩归档,可以用 tar 打包后放到openspec/archive/,同时保留索引文件方便搜索。

也有人问我能不能把specs/放到单独的 Git LFS 或子模块里,我试过子模块方案,体验很差,因为需要额外的 clone 和同步步骤,直接放同一个仓库里最简单可靠。

6. 我的一点实际使用体会

上面都是操作层面的内容,最后聊几点真心话。OpenSpec 不是银弹,它不会自己消灭需求混乱,也不会替你做决策。它真正改变的是信息沉淀的方式:让需求像代码一样被 review、被版本化、被追溯。刚开始用的时候,团队会有一点点不适,毕竟多了一道维护规范文件的流程,但只要坚持两三个迭代,你会发现自己再也不需要靠“谁还记得当时怎么定的”来维持项目运转。

如果你正打算引入,我的建议是别追求一步到位。先挑一个模块,写一份细致的 spec,跑通new -> check -> plan -> apply这条主链路,让团队感受一下“需求可执行”是什么体验。后续再逐步添加 CI 校验、权限管理、提案模板这些复杂能力。

另外一个真实感受是:OpenSpec 的价值,和你团队本身的工程素养成正比。一个习惯写好 commit message、做详细 code review 的团队,用了 OpenSpec 会如虎添翼;而一个连 README 都懒得维护的团队,装再多工具也白搭。工具只是放大你的行为习惯,不会替你养成好习惯。

最后分享一个小技巧:我给自己的终端配了一个简化别名,osalias spec-check='openspec check && openspec plan --dry-run',每次本地开发前跑一下,既能确认规范仓库健康,又能在动代码前给自己提个醒——你现在做的一切,都会被记录成这个项目的一部分。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进