OpenSpec 规范驱动开发实战:config.yaml 与 Claude Code 协作指南

发布时间:2026/10/27 6:23:21
OpenSpec 规范驱动开发实战:config.yaml 与 Claude Code 协作指南

1. 从一份 config.yaml 说起:OpenSpec 到底在解决什么问题

第一次接触 OpenSpec 是在一个多人协作的后端项目里,当时团队正被“需求文档和代码对不上”这件事反复折磨。产品经理在飞书文档里改了字段定义,后端同学没注意,接口返回结构跟前端约定不一致,联调当天才发现,返工到凌晨。后来有人丢了一个config.yaml到仓库根目录,说“以后接口规范都从这里生成,别再手写文档了”。那个文件就是 OpenSpec 的入口。

OpenSpec 是一套围绕SDD(Specification-Driven Development,规范驱动开发)理念构建的工程化工具链。它的核心思路很朴素:把接口规范、数据模型、字段约束这些“契约”用结构化的方式写在一个中心化的配置文件里,然后通过 CLI 自动生成文档、类型定义、Mock 数据甚至校验逻辑。你不再需要维护一份“给人看的文档”和一份“给机器用的代码”,两者从同一个源头派生。

这件事听起来像是“又一个 API 文档工具”,但实际用下来,它跟 Swagger/OpenAPI 那套的定位有微妙差别。OpenAPI 更偏向“描述已经存在的接口”,而 OpenSpec 更强调“先写规范,再写实现”,也就是 SDD 的核心——规范是源头,代码是产物。这个顺序上的差异,直接决定了团队协作方式的不同。

适合谁来参考这套东西?我的判断是三类人:一是中小团队里负责接口约定的后端或全栈,二是需要频繁跟前后端对齐字段的前端负责人,三是正在尝试把 AI 编码工具(比如 Claude Code)接入日常开发流程、想让 AI 产出更可控的工程师。如果你只是一个人写个小脚本,那确实用不上;但只要涉及两人以上的接口协作,OpenSpec 这套思路就值得花半小时了解一下。

下面我会从整体设计思路、核心配置细节、完整实操流程、常见问题排查四个层面,把我在实际项目里落地 OpenSpec 的过程拆开讲。中间会穿插config.yaml的写法、CLI 的用法,以及跟 Claude Code 配合时踩过的坑。

2. 整体设计与思路拆解:为什么是“规范先行”而不是“文档后补”

2.1 SDD 与传统开发流程的本质差异

传统开发流程里,规范文档往往是“事后补”的。接口写完了,顺手在 Apifox 或 YApi 上填一下字段,能跑就行。这种模式下,文档天然滞后于代码,而且没人有动力去维护它。等到新人接手或者前端联调,才发现文档里的字段跟实际返回对不上。

SDD 把这个顺序倒过来:先定义规范,再由规范驱动实现。OpenSpec 就是把这个理念工具化的载体。你在config.yaml里声明一个接口的路径、方法、请求体、响应体,CLI 会基于这份声明生成:

  • 接口文档(Markdown 或 HTML)
  • TypeScript 类型定义
  • Mock 数据
  • 请求参数校验规则

这意味着“文档”不再是独立产物,而是规范的副产品。规范改了,所有产物重新生成,不存在“忘了更新文档”这回事。

我选择这套方案的核心考量是单一数据源。以前一个字段的改动要同步到文档、类型定义、Mock、校验逻辑四个地方,现在只改config.yaml一处。改动成本从四次降到一次,出错概率也跟着降。

2.2 为什么用 YAML 而不是 JSON 或数据库

OpenSpec 选择config.yaml作为规范载体,这个决策值得说一下。JSON 也能表达同样的结构,但 YAML 有两个实际优势:一是支持注释,你可以在字段旁边写“这个字段是给风控用的,别删”,JSON 做不到;二是缩进表达层级,写起来比一堆花括号清爽,尤其是嵌套的请求体结构。

至于为什么不用数据库存规范,我的理解是:规范应该跟代码一起进版本控制。config.yaml放在仓库里,谁改了什么字段、什么时候改的,git log一清二楚。数据库存规范就失去了这个追溯能力,而且本地开发还得连数据库才能生成文档,太重了。

提示:config.yaml建议放在仓库根目录或spec/目录下,跟业务代码同级,方便 CI 流程直接读取。

2.3 OpenSpec 与 Claude Code 的协作定位

热词里频繁出现 Claude Code,这不是偶然。OpenSpec 生成的规范文件,恰好是 Claude Code 这类 AI 编码工具最喜欢的输入格式——结构清晰、约束明确。你让 Claude Code “根据config.yaml里的/api/user/profile接口实现一个 handler”,它产出的代码大概率能直接跑,因为字段名、类型、必填项都在规范里写死了。

我实测下来,有 OpenSpec 规范约束的情况下,Claude Code 生成的接口代码一次通过率明显高于“口头描述需求”的方式。原因很简单:AI 不需要猜字段名是userName还是username,规范里写的是哪个就是哪个。

所以 OpenSpec 在这套工作流里的定位是“规范的守门人”,Claude Code 是“规范的执行者”。两者配合,SDD 的闭环才算真正跑通。

3. 核心细节解析与实操要点:config.yaml 怎么写才不踩坑

3.1 规范文件的基本结构

一份典型的config.yaml大致长这样:

version: "1.0" project: name: user-service baseUrl: /api/v1 models: User: type: object properties: id: type: integer required: true description: 用户唯一标识 username: type: string required: true minLength: 3 maxLength: 20 email: type: string format: email required: false endpoints: - path: /user/profile method: GET summary: 获取当前用户信息 response: ref: User

这个结构分三层:project定义全局信息,models定义可复用的数据模型,endpoints定义具体接口。models和endpoints分离的好处是,同一个User模型可以被多个接口引用,改一处全生效。

3.2 字段约束的写法与校验逻辑

字段约束是 OpenSpec 最实用的部分。上面username的minLength: 3和maxLength: 20,CLI 生成校验逻辑时会直接转成对应的校验代码。我试过在 Node.js 项目里,生成的校验函数大致是这样:

function validateUsername(value) { if (typeof value !== 'string') return 'username must be a string'; if (value.length < 3) return 'username too short'; if (value.length > 20) return 'username too long'; return null; }

这里有个坑要注意:required字段的校验和类型校验是分开的。如果字段是required: true但类型写错了,生成的校验逻辑可能先报类型错误再报缺失错误,顺序不对会让前端拿到奇怪的报错信息。我的做法是把required放在类型定义之前,让 CLI 优先处理必填校验。

注意:format: email这类语义化格式,OpenSpec 默认只生成正则校验,不保证覆盖所有边界情况。生产环境建议在生成的校验逻辑上再包一层业务校验。

3.3 模型复用的边界在哪里

models复用很爽,但别滥用。我踩过一个坑:把User模型同时用在“注册接口的请求体”和“查询接口的响应体”上,结果注册时id是必填的(因为响应体里id必填),但注册时根本没有id。后来拆成UserCreate和UserResponse两个模型才解决。

判断标准很简单:请求体和响应体的字段约束几乎不可能完全一致。请求体通常有更多校验规则(比如密码强度),响应体通常有更多只读字段(比如创建时间)。所以我的经验是,请求和响应各建一个模型,公共字段用$ref引用基础模型,差异字段单独写。

3.4 版本管理:规范也要有版本号

config.yaml顶部的version字段容易被忽略,但它很重要。接口一旦对外发布,规范就不能随便改,否则调用方会炸。我的做法是:version跟 API 版本号对齐,比如/api/v1对应version: "1.0"。要改破坏性变更,就升version并新建一个config-v2.yaml,老版本继续维护。

这样做的代价是仓库里会有多个规范文件,但换来的是调用方的稳定性。实测下来,这个取舍是值得的。

4. 实操过程与核心环节实现:从零跑通 OpenSpec 工作流

4.1 环境准备与 CLI 安装

OpenSpec 的 CLI 安装方式取决于你的运行环境。在 Ubuntu 或 macOS 上,通常通过包管理器或直接下载二进制文件。我这边用的是 Ubuntu 22.04,安装步骤如下:

# 下载 CLI 二进制文件 curl -L -o openspec https://example.com/openspec-cli/latest/openspec-linux-amd64 # 赋予执行权限 chmod +x openspec # 移动到 PATH 目录 sudo mv openspec /usr/local/bin/ # 验证安装 openspec --version

Windows 环境下,下载对应的.exe文件后,把所在目录加入系统环境变量PATH即可。这里有个细节:Windows 的路径分隔符和 Linux 不同,如果config.yaml里写了相对路径引用其他文件,跨平台时要注意用正斜杠/,OpenSpec 内部会做转换。

提示:安装完成后先跑openspec init,它会在当前目录生成一份示例config.yaml,照着改比从零写快得多。

4.2 编写第一份规范文件

假设我们要做一个用户服务,先定义模型:

version: "1.0" project: name: user-service baseUrl: /api/v1 models: UserBase: type: object properties: username: type: string required: true minLength: 3 maxLength: 20 email: type: string format: email required: true UserCreate: allOf: - ref: UserBase - type: object properties: password: type: string required: true minLength: 8 UserResponse: allOf: - ref: UserBase - type: object properties: id: type: integer required: true createdAt: type: string format: date-time

这里用了allOf做模型组合,UserCreate和UserResponse都继承UserBase,各自扩展差异字段。这种写法比复制粘贴字段定义干净得多,改UserBase时两个子模型同步生效。

4.3 定义接口与生成产物

模型定义好之后,接口定义就很简单了:

endpoints: - path: /user/register method: POST summary: 用户注册 request: ref: UserCreate response: ref: UserResponse statusCode: 201 - path: /user/profile method: GET summary: 获取当前用户信息 response: ref: UserResponse

然后跑生成命令:

openspec generate --config config.yaml --output ./generated

生成的目录结构大致是:

generated/ ├── docs/ │ └── api.md # 接口文档 ├── types/ │ └── models.ts # TypeScript 类型定义 ├── mock/ │ └── handlers.js # Mock 数据 └── validators/ └── index.js # 校验逻辑

我实测下来,types/models.ts里的类型定义可以直接被前端项目引用,省掉了手写interface的功夫。mock/handlers.js配合 Express 或 Koa 能直接起一个 Mock 服务,前端不用等后端写完就能联调。

4.4 接入 Claude Code 的实操配置

这一步是热词里问得最多的。Claude Code 本身是一个命令行 AI 编码工具,安装方式这里不展开(不同平台差异较大,官方文档有详细说明)。重点讲怎么让它跟 OpenSpec 配合。

核心思路是:把config.yaml作为上下文喂给 Claude Code,让它基于规范生成实现代码。我的做法是在项目根目录放一个CLAUDE.md,内容大致是:

# 项目规范 本项目使用 OpenSpec 管理接口规范,规范文件位于 `config.yaml`。 生成接口实现时,请严格遵循以下规则: 1. 字段名、类型、必填项以 `config.yaml` 为准 2. 校验逻辑参考 `generated/validators/` 下的生成结果 3. 响应结构必须匹配 `generated/types/models.ts` 中的类型定义

然后在 Claude Code 里直接说:“根据config.yaml里的/user/register接口,用 Express 实现一个 handler。”它产出的代码会主动去读config.yaml和生成的类型文件,字段名不会写错。

我试过对比:没有CLAUDE.md约束时,Claude Code 生成的代码里username有时写成userName,有时写成user_name,得手动改。加上约束后,这个问题基本消失。

注意:Claude Code 读取文件有上下文长度限制,config.yaml太大时建议拆分成多个文件,按模块引用,避免超出限制导致规范被截断。

4.5 把生成流程接入 CI

规范文件进了仓库,就要防止有人改了规范但忘了重新生成产物。我的做法是在 CI 里加一步校验:

# 重新生成产物 openspec generate --config config.yaml --output ./generated # 检查是否有未提交的变更 if [[ -n $(git status --porcelain ./generated) ]]; then echo "生成的产物与规范不一致,请重新运行 openspec generate 并提交" exit 1 fi

这样任何人改了config.yaml但没重新生成,CI 会直接拦下来。实测下来,这个检查拦住过好几次“改了规范忘了生成”的情况,省了不少联调时的扯皮。

5. 常见问题与排查技巧实录

5.1 生成产物与预期不符怎么排查

最常见的问题是生成的类型定义里字段类型不对。比如config.yaml里写了type: integer,生成的 TypeScript 却是string。这种情况九成是 YAML 缩进错了,type被解析成了properties的同级字段而不是子字段。

排查方法:用openspec validate --config config.yaml先校验规范文件本身是否合法。这个命令会输出具体的行号和错误原因,比对着生成结果猜快得多。

5.2 模型引用报错的处理

$ref或ref引用不存在的模型时,CLI 通常会报model not found。但有时候模型明明定义了却还是报错,原因往往是定义顺序。部分版本的 OpenSpec 要求被引用的模型必须在使用它的模型之前定义。我的习惯是把基础模型放最前面,派生模型放后面,按依赖顺序排列。

5.3 跨平台路径问题

Windows 上生成的产物路径可能带反斜杠,导致在 Linux CI 上跑校验时找不到文件。解决办法是在config.yaml里所有路径都用正斜杠,CLI 内部会做平台适配。如果 CLI 版本较老不支持自动转换,可以在 CI 里加一步路径替换。

5.4 常见问题速查表

问题现象可能原因解决方法
生成类型字段类型错误YAML 缩进错误跑openspec validate检查
模型引用报 not found定义顺序不对基础模型放前面
CI 校验失败改了规范没重新生成本地跑openspec generate后提交
Claude Code 字段名写错缺少规范约束文件添加CLAUDE.md并引用 config.yaml
生成产物路径找不到跨平台路径分隔符统一用正斜杠

5.5 几个我踩过的坑

第一个坑是规范文件写太细。一开始我把每个字段的description都写得很长,结果config.yaml膨胀到两千多行,Claude Code 读取时被截断,反而丢掉了关键约束。后来把详细说明挪到单独的docs/目录,config.yaml只保留结构化定义,问题解决。

第二个坑是模型复用过度。前面提过,请求体和响应体强行共用一个模型,导致必填项冲突。拆开之后清晰多了。

第三个坑是忘了版本号。有次改了一个已发布接口的字段类型,没升version,前端直接报错。后来养成习惯:任何影响调用方的改动,先升version再说。

6. 关于这套工作流的一些个人体会

OpenSpec 这套东西的价值不在于工具本身多强大,而在于它逼着团队把“规范”这件事前置。以前大家觉得写文档是负担,现在规范成了代码的一部分,写规范就是写代码,心理负担小了很多。

跟 Claude Code 配合是意外之喜。原本只是想让 AI 帮忙写点样板代码,结果发现有了规范约束之后,AI 产出的代码质量稳定了很多。这让我意识到,AI 编码工具的上限很大程度上取决于你给它的输入有多结构化。规范越清晰,AI 越靠谱。

如果让我给刚接触 OpenSpec 的人一个建议,那就是:先从一个小接口开始,别一上来就把整个项目的规范都写了。写一个接口,跑通生成流程,接入 CI,再接入 Claude Code,每一步都验证过之后再铺开。我见过有人一口气写了五百行config.yaml,结果生成报错,排查了半天发现是第三行的缩进问题,心态直接崩了。

另外,config.yaml不要追求一次写完美。规范是活的,随着业务迭代慢慢补。先让流程跑起来,再逐步完善细节,比憋大招靠谱得多。