首页 / 资讯中心 / 文章详情

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

OpenSpec 规范驱动开发实战:config.yaml 与 Claude Code 协作指南 ★ FEATURED ARTICLE
1. 从一份 config.yaml 说起OpenSpec 到底在解决什么问题第一次接触 OpenSpec 是在一个多人协作的后端项目里当时团队正被“需求文档和代码对不上”这件事反复折磨。产品经理在飞书文档里改了字段定义后端同学没注意接口返回结构跟前端约定不一致联调当天才发现返工到凌晨。后来有人丢了一个config.yaml到仓库根目录说“以后接口规范都从这里生成别再手写文档了”。那个文件就是 OpenSpec 的入口。OpenSpec 是一套围绕SDDSpecification-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 或 HTMLTypeScript 类型定义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: 20CLI 生成校验逻辑时会直接转成对应的校验代码。我试过在 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 --versionWindows 环境下下载对应的.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不要追求一次写完美。规范是活的随着业务迭代慢慢补。先让流程跑起来再逐步完善细节比憋大招靠谱得多。
阅读完成 · 觉得有帮助?
咨询建站