☰
OpenSpec规格驱动开发:让Claude Code与Codex精准生成代码的实操指南
2026/11/2 3:31:28 网站建设 项目流程

1. 规格驱动开发到底在解决什么问题

第一次接触 OpenSpec 的人,十有八九是被"规格驱动开发"这个词吸引过来的,但真正上手之后才发现,它要解决的根本不是"怎么写代码"的问题,而是"怎么让 AI 写对代码"的问题。这两件事看起来差不多,实际上差得很远。

传统开发流程里,需求文档、接口定义、数据库设计这些东西是给人看的,人看完之后脑子里形成一个模糊的模型,然后开始写代码。这个过程中信息损耗是巨大的,但人类开发者有足够的上下文理解能力去弥补这些损耗。可当你把写代码这件事交给 Claude Code、Codex 这类 AI 编程助手时,问题就暴露了——AI 不会"猜",它只会根据你给的信息做最直接的推断。你给它一句"帮我实现用户登录",它可能给你写一个完全不考虑你项目现有架构的登录模块,字段命名、错误处理、鉴权方式全都跟你现有的代码风格对不上。

OpenSpec 的核心思路就是:在让 AI 写代码之前,先把规格写清楚。这个规格不是给人看的模糊需求,而是结构化的、机器可读的、包含足够约束的规范文件。它规定了数据模型长什么样、接口的输入输出是什么、边界条件怎么处理、错误码怎么定义。AI 拿到这份规格之后,生成的代码就不再是"猜"出来的,而是"推导"出来的。

我拿一个实际场景来说明这个差别。假设你要给一个 Laravel 项目加一个订单导出功能。如果你直接跟 Claude Code 说"帮我加一个订单导出 CSV 的功能",它大概率会给你写一个控制器方法,里面用fputcsv循环写文件,然后返回一个下载响应。这个实现能跑,但它没有考虑:导出数据量大了怎么办、要不要走队列、字段权限怎么控制、导出记录要不要留痕、CSV 的编码和 BOM 怎么处理。这些细节如果你不写进规格里,AI 是不会主动帮你补全的。

而如果你先用 OpenSpec 写一份规格,把这些约束都定义清楚,AI 生成的代码就会自动带上队列任务、权限检查、导出日志、编码处理这些逻辑。这就是规格驱动开发的价值——把隐性的工程经验显性化,让 AI 按照你的标准来干活,而不是按照它的默认习惯来干活。

提示:规格驱动开发不是让你写更多的文档,而是让你把原本存在于脑子里的隐性约束,用结构化的方式表达出来。写规格的时间,本质上是在替 AI 做决策,省下来的是后面反复修改和调试的时间。

2. OpenSpec 的规格文件结构与核心概念

2.1 规格文件的基本组成

OpenSpec 的规格文件不是随便写写的 Markdown,它有一套相对固定的结构。理解这套结构,是能不能用好它的前提。一份完整的规格通常包含以下几个部分:

  • 实体定义(Entity):描述业务中的核心对象,包括字段名、类型、约束、默认值、关联关系。这部分直接对应数据库迁移和模型定义。
  • 接口定义(Endpoint):描述每个 API 的路径、方法、请求参数、响应结构、状态码。这部分对应路由和控制器。
  • 行为规则(Behavior):描述业务逻辑的边界条件,比如"订单金额为负时拒绝创建""库存不足时返回特定错误码"。这部分对应服务层和验证逻辑。
  • 验收标准(Acceptance):描述功能完成的判定条件,通常以"给定-当-那么"的格式书写。这部分对应测试用例。

这四部分不是必须全部都有,但实体定义和行为规则这两块,我建议任何规格都别省。因为这两块是 AI 最容易"自由发挥"的地方,你不约束它,它就会按自己的理解来。

2.2 实体定义怎么写才不踩坑

实体定义看起来简单,实际上最容易出问题。我见过太多人写实体定义的时候只写字段名和类型,结果 AI 生成的迁移文件里,字符串字段长度默认 255,金额字段用 float,时间字段没有时区处理。这些都是坑。

正确的做法是,实体定义里要把字段的完整约束都写清楚。比如一个订单实体,你不能只写amount: decimal,你要写amount: decimal(10,2), 非负, 必填, 单位元。这样 AI 生成的迁移文件才会是decimal('amount', 10, 2),验证规则里才会带上min:0。

再比如关联关系,你要明确写出来"订单属于用户,一个用户可以有多个订单",AI 才会在模型里加上belongsTo和hasMany。如果你不写,它可能只给你建一个user_id字段,但不建关联方法。

还有一个容易被忽略的点是枚举值。如果你的订单状态有"待支付、已支付、已发货、已完成、已取消"这几种,你一定要在规格里列出来。否则 AI 可能给你用字符串存状态,而不是用枚举或者常量。字符串存状态的问题在于,后面做状态流转判断的时候,很容易因为拼写错误导致 bug。

2.3 行为规则的表达方式

行为规则是规格里最有价值的部分,也是最难写的部分。因为它要求你把脑子里那些"理所当然"的判断都写出来。比如"用户不能重复下单同一个商品",这个规则听起来很简单,但你要写清楚:是同一用户同一商品不能有未支付的订单,还是完全不能有?如果之前取消了,能不能再下单?

我通常用这样的格式来写行为规则:

规则:创建订单时检查库存 - 给定:商品库存为 0 - 当:用户尝试创建该商品的订单 - 那么:返回错误码 STOCK_INSUFFICIENT,HTTP 状态码 422

这种格式的好处是,它同时表达了触发条件、执行动作和预期结果,AI 拿到之后可以直接生成对应的验证逻辑和测试用例。而且这种格式写多了之后,你会发现自己在写规格的时候,就已经把很多边界情况想清楚了,这本身就是一种设计活动。

2.4 规格文件放在项目里的位置

OpenSpec 的规格文件通常放在项目根目录的specs/目录下,按功能模块分子目录。比如specs/order/下面放订单相关的规格,specs/user/下面放用户相关的规格。每个规格文件用 Markdown 格式,文件名用功能名,比如order-export.md。

这个目录结构不是强制的,但建议保持一致性。因为当你用 Claude Code 或者 Codex 来执行规格的时候,你需要告诉它去哪个目录找规格文件。如果目录结构混乱,AI 可能会找错文件,或者把不相关的规格混在一起处理。

注意:规格文件不要写得太长。一个规格文件对应一个功能模块,超过 500 行的规格文件建议拆分成多个。因为 AI 处理长文本的时候,注意力会分散,规格太长反而容易漏掉细节。

3. 把 OpenSpec 接入 Claude Code 和 Codex 的实操路径

3.1 环境准备与工具安装

在开始之前,你需要先把基础环境搭好。这里涉及几个工具:Claude Code、Codex CLI,以及 OpenSpec 本身。我按实际操作的顺序来说。

Claude Code 的安装,官方推荐的方式是通过 npm 全局安装。如果你用的是 macOS 或者 Linux,直接执行npm install -g @anthropic-ai/claude-code就行。Windows 用户要注意,Claude Code 的桌面版需要开启虚拟化平台支持,如果你在 Windows 上遇到 "requires the virtual machine platform" 这类提示,需要去系统设置里把虚拟机平台功能打开,然后重启。

Codex 的安装类似,也是通过 npm 或者直接下载安装包。Codex CLI 的安装命令是npm install -g @openai/codex。安装完之后,你需要配置 API 密钥才能使用。这里有个细节:Codex 和 Claude Code 的配置文件是分开的,不要搞混了。

OpenSpec 本身不是一个需要安装的软件,它更像是一套约定和规范。你只需要在项目里创建specs/目录,然后按照规范写规格文件就行。不过有一些社区维护的 OpenSpec 模板和校验工具,可以通过 npm 安装,用来检查你的规格文件是否符合规范。

3.2 让 Claude Code 读取规格并生成代码

Claude Code 的工作方式是,你在项目目录下启动它,它会自动读取项目里的文件作为上下文。所以你要做的第一件事,是确保规格文件在项目目录里,并且路径清晰。

启动 Claude Code 之后,你可以用这样的指令来让它执行规格:

请阅读 specs/order/order-export.md 规格文件,按照规格实现订单导出功能。 实现时请遵循项目现有的代码风格,迁移文件放在 database/migrations 目录, 控制器放在 app/Http/Controllers 目录,服务类放在 app/Services 目录。

这个指令里,我明确告诉了 Claude Code 三件事:读哪个规格文件、实现什么功能、代码放在哪里。这三件事缺一不可。如果你只说"按照规格实现订单导出",它可能会把代码放在它认为合理的地方,但那个地方可能不符合你的项目结构。

Claude Code 执行规格的时候,会先读规格文件,然后读项目里相关的现有代码,然后生成新的代码。这个过程通常需要几分钟,取决于规格的复杂度和项目的规模。生成完之后,它会给你一个变更列表,你可以逐个 review。

这里有个实操心得:不要让 Claude Code 一次性执行太大的规格。如果一个规格涉及十几个文件,它生成到后面可能会忘记前面的约束。我通常会把规格拆成小块,一次执行一个功能点,生成完之后确认没问题,再执行下一个。

3.3 Codex 执行规格的差异

Codex 和 Claude Code 在执行规格的时候,行为有一些差异。Codex 更倾向于一次性生成完整的代码块,而 Claude Code 更倾向于逐步修改现有文件。这个差异导致它们在处理规格时的策略不同。

用 Codex 执行规格的时候,我建议把规格文件的内容直接粘贴到对话里,而不是让它去读文件。因为 Codex 的文件读取能力相对弱一些,有时候会读不到或者读错。直接粘贴虽然麻烦一点,但能保证它拿到的是完整的规格内容。

Codex 生成代码之后,你需要手动把代码放到对应的文件里。它不会像 Claude Code 那样自动修改项目文件。这个差异在操作上要多一步,但好处是你可以先 review 再决定要不要用。

还有一个差异是,Codex 对 Laravel 项目的理解程度不如 Claude Code。如果你用的是 Laravel,Claude Code 生成的代码通常更符合 Laravel 的惯例,比如会自动用 Eloquent 的关联方法、会自动生成 FormRequest 验证类。Codex 有时候会生成比较通用的 PHP 代码,需要你手动调整成 Laravel 风格。

3.4 规格执行后的验证流程

代码生成完之后,不要急着提交。我通常按这样的顺序验证:

  1. 语法检查:先跑一遍php -l检查语法错误,或者用 IDE 的静态分析功能。
  2. 迁移检查:如果是数据库相关的规格,先跑php artisan migrate --pretend看看迁移语句是否符合预期。
  3. 单元测试:如果规格里写了验收标准,让 AI 同时生成测试用例,然后跑测试。
  4. 手动验证:启动本地服务,用 Postman 或者 curl 实际调一下接口,看看返回结果是否符合规格。

这个流程看起来繁琐,但能帮你省下大量调试时间。我见过太多人直接让 AI 生成代码就提交,结果线上出问题才发现规格里的约束没被执行。

提示:如果你在 Windows 上遇到 Claude Code 无法识别命令的问题,比如提示 "无法将 claude 项识别为 cmdlet",这通常是环境变量没配置好。检查一下 npm 的全局安装路径是否在 PATH 里,或者直接用 npx 来运行。

4. 规格驱动开发在 Laravel 项目中的落地细节

4.1 Laravel 项目的规格组织方式

Laravel 项目有比较明确的目录结构,这给规格组织提供了便利。我通常按这样的方式组织规格:

specs/ ├── models/ │ ├── order.md │ ├── user.md │ └── product.md ├── endpoints/ │ ├── order-api.md │ └── user-api.md ├── behaviors/ │ ├── order-workflow.md │ └── payment-rules.md └── acceptance/ ├── order-export.md └── user-register.md

这种组织方式的好处是,模型规格、接口规格、行为规格、验收规格分开存放,AI 在执行的时候可以按需读取。比如你只需要实现模型和迁移,就只让它读models/目录下的规格;需要实现完整功能,就让它读所有相关规格。

4.2 迁移文件与模型定义的规格写法

Laravel 的迁移文件和模型定义是紧密关联的,规格里要把这两者的对应关系写清楚。我通常会在模型规格里同时写迁移字段和模型配置。

以订单模型为例,规格里会这样写:

实体:Order 表名:orders 字段: - id: bigint, 主键, 自增 - user_id: bigint, 外键关联 users 表, 必填, 索引 - order_no: string(32), 唯一, 必填 - amount: decimal(10,2), 非负, 必填 - status: enum(pending, paid, shipped, completed, cancelled), 默认 pending - paid_at: timestamp, 可空 - created_at: timestamp - updated_at: timestamp 模型配置: - 使用 SoftDeletes - 关联 User: belongsTo - 关联 OrderItem: hasMany - 状态字段使用常量定义,不用魔术字符串

这样写的好处是,AI 生成迁移文件的时候会严格按照字段定义来,生成模型的时候会自动加上 SoftDeletes trait 和关联方法。而且状态字段用常量定义这个要求,能避免后面代码里到处出现'pending'这样的魔术字符串。

4.3 接口规格与 FormRequest 的对应

Laravel 的 FormRequest 是验证请求参数的标准方式,规格里要把验证规则写清楚,AI 才会生成对应的 FormRequest 类。

接口规格里我通常会这样写:

接口:POST /api/orders 请求参数: - product_id: required, integer, exists:products,id - quantity: required, integer, min:1, max:99 - remark: nullable, string, max:500 响应: - 成功:201, { "order_id": 1, "order_no": "xxx", "amount": 100.00 } - 库存不足:422, { "error": "STOCK_INSUFFICIENT", "message": "库存不足" } - 商品不存在:422, { "error": "PRODUCT_NOT_FOUND", "message": "商品不存在" }

这样写之后,AI 生成的 FormRequest 里会自动带上exists:products,id这样的验证规则,控制器里也会根据不同的错误情况返回对应的状态码和错误信息。

4.4 队列任务与异步处理的规格表达

Laravel 的队列是处理耗时任务的标准方案,但 AI 默认生成的代码往往不会主动用队列。你需要在规格里明确写出来。

比如订单导出功能,规格里要写:

行为:订单导出 - 导出请求不直接生成文件,而是推送到队列 - 队列任务类名:ExportOrdersJob - 队列名称:exports - 导出完成后发送通知给用户 - 导出文件保留 7 天,过期自动清理

这样 AI 生成的代码就会包含 Job 类、队列推送逻辑、通知发送逻辑,以及一个清理过期文件的定时任务。如果你不写这些,它可能就给你写一个同步导出的控制器方法,数据量一大就超时。

4.5 权限控制与中间件的规格描述

Laravel 的权限控制通常通过中间件和 Policy 来实现,规格里要把权限规则写清楚。

权限规则: - 只有 admin 角色的用户可以访问订单导出接口 - 普通用户只能导出自己的订单 - 导出接口需要 auth:sanctum 中间件 - 导出频率限制:每分钟最多 3 次

这样 AI 生成的代码就会带上auth:sanctum中间件、角色检查逻辑、频率限制配置。如果你不写,它可能只给你加一个基础的 auth 中间件,角色检查就漏掉了。

注意:Laravel 项目里有一个历史漏洞 CVE-2024-29291,涉及某些版本的文件上传处理。虽然这个漏洞跟 OpenSpec 没有直接关系,但如果你在用 AI 生成文件上传相关代码,建议在规格里明确写上文件类型白名单、大小限制、存储路径隔离这些约束,避免生成不安全的代码。

5. 规格执行过程中的常见故障与排查思路

5.1 AI 生成的代码偏离规格

这是最常见的问题。你明明在规格里写了金额字段是decimal(10,2),AI 生成的迁移文件里却是float。这种情况通常有几个原因:

第一个原因是规格文件太长,AI 读到后面忘了前面。解决办法是把规格拆短,或者把关键约束放在规格文件的开头。

第二个原因是规格里的表述有歧义。比如你写"金额字段用 decimal",AI 可能理解为"用 decimal 类型但不指定精度"。解决办法是写得更具体,decimal(10,2)而不是decimal。

第三个原因是 AI 的默认习惯覆盖了规格约束。比如 AI 默认喜欢用 float 存金额,即使你写了 decimal,它也可能"自作主张"改回 float。解决办法是在规格里加上强调性的说明,比如"金额字段必须使用 decimal(10,2),禁止使用 float 或 double"。

5.2 规格文件读取失败

有时候 Claude Code 或 Codex 会提示找不到规格文件,或者读取的内容不完整。这种情况通常是路径问题。

Claude Code 读取文件是相对于项目根目录的,所以你的规格文件必须在项目根目录下,或者用相对于根目录的路径来引用。如果你把规格文件放在项目外面,它就读不到。

Codex 的文件读取能力更弱一些,有时候即使路径正确也读不到。这种情况下,直接把规格内容粘贴到对话里是最稳妥的办法。

还有一个可能的原因是文件编码问题。如果你的规格文件是 GBK 编码,AI 读取的时候可能会乱码。建议统一用 UTF-8 编码保存规格文件。

5.3 生成的代码与现有代码冲突

AI 生成代码的时候,如果项目里已经有类似功能的代码,它可能会生成重复的类或者方法。比如项目里已经有一个OrderService,AI 又生成了一个OrderExportService,两个类里有重复的逻辑。

避免这个问题的方法是,在规格里明确写出"复用现有的 XX 类"或者"扩展现有的 XX 方法"。另外,在执行规格之前,先让 AI 读一遍相关的现有代码,它生成的时候就会考虑复用而不是重写。

如果已经生成了冲突的代码,不要急着删。先对比一下两边的逻辑,看看哪边的实现更符合规格要求,然后把好的部分合并进去。

5.4 队列任务不执行

用规格生成的队列任务,有时候推送到队列了但不执行。这种情况通常是队列配置问题,不是规格问题。

检查几个地方:队列驱动是否配置正确(QUEUE_CONNECTION环境变量)、队列监听进程是否启动(php artisan queue:work)、队列名称是否匹配(规格里写的队列名称和实际推送的队列名称是否一致)。

还有一个容易忽略的点是,如果规格里写了队列名称是exports,但你的config/queue.php里没有配置这个队列,任务就会被推到默认队列。所以规格里写的队列名称,要跟项目配置保持一致。

5.5 测试用例跑不过

规格里写的验收标准,AI 生成的测试用例有时候跑不过。这不一定是用例写错了,可能是实现代码没完全按照规格来。

排查的顺序是:先看测试用例的断言是否符合规格里的验收标准,再看实现代码是否满足断言,最后看规格本身是否有矛盾的地方。

我遇到过一种情况,规格里写"库存不足时返回 422",但验收标准里写"库存不足时返回 400",两个地方不一致,AI 生成的代码和测试用例就冲突了。所以规格写完之后,自己要通读一遍,确保前后一致。

提示:如果你在配置 Claude Code 或 Codex 的时候遇到网络相关的错误提示,比如连接超时或者端点无响应,先检查本地的网络配置和代理设置。这类问题通常跟工具本身无关,而是环境配置的问题。

6. 让规格真正落地的几个经验性做法

6.1 规格先行,代码后行

这是最核心的一条经验。很多人用 AI 编程的习惯是,先让 AI 生成代码,然后根据生成的代码反推需求。这个顺序是反的,会导致代码质量不可控。

正确的做法是,先写规格,规格确认无误之后,再让 AI 执行规格生成代码。写规格的过程,本质上是在做设计。你把数据模型、接口定义、行为规则都想清楚了,代码只是规格的翻译结果。

我自己的习惯是,每个功能在动手之前,先花 20 到 30 分钟写规格。这 20 分钟里,我会把字段类型、验证规则、错误处理、边界条件都过一遍。写完之后,再让 AI 执行。这样生成出来的代码,一次通过率能到 80% 以上,剩下的 20% 主要是代码风格和项目惯例的调整。

6.2 规格要跟着代码一起维护

规格不是一次性的东西。功能迭代的时候,规格也要跟着更新。否则下次让 AI 执行规格的时候,它会按照旧规格生成代码,跟现有代码冲突。

我通常把规格文件和代码放在同一个 commit 里提交。改了代码,就同步改规格。这样规格始终是代码的准确描述,AI 执行的时候不会跑偏。

如果项目比较大,规格文件比较多,可以写一个简单的校验脚本,定期检查规格文件和实际代码的一致性。比如检查规格里定义的字段是否都在迁移文件里存在,规格里定义的接口是否都在路由文件里注册。

6.3 规格的粒度控制

规格写得太粗,AI 会自由发挥;规格写得太细,写规格的时间比写代码还长。这个度怎么把握?

我的经验是,规格写到"决策点"为止。什么是决策点?就是那些有多种合理选择、需要人为决定的地方。比如字段类型是 decimal 还是 float,这是决策点,要写进规格。但具体用哪个函数来格式化金额,这不是决策点,不用写。

再比如,接口返回的错误码格式,这是决策点,要写。但错误信息的具体措辞,这不是决策点,可以让 AI 自己定。

按这个标准,一个中等复杂度的功能,规格大概在 100 到 200 行之间。超过 300 行就说明写得太细了,低于 50 行就说明写得太粗了。

6.4 用规格来传递项目规范

每个项目都有自己的编码规范,比如命名习惯、目录结构、错误处理方式。这些规范如果只存在于团队成员的脑子里,AI 是不知道的。但如果你把它们写进规格里,AI 就会遵守。

我通常会在项目里放一个specs/conventions.md文件,里面写项目的通用规范。比如"控制器方法名用驼峰式""服务类以 Service 结尾""错误码用大写下划线格式""所有数据库操作必须用事务包裹"。然后在每个具体规格的开头,引用这个文件。

这样 AI 在执行具体规格的时候,会先读通用规范,再读具体规格,生成的代码就会同时符合项目规范和功能需求。

6.5 规格的版本管理

规格文件也要做版本管理。当需求变更的时候,不要直接改规格文件,而是新建一个版本,或者在规格文件里用注释标注变更历史。

这样做的好处是,当 AI 生成的代码出问题的时候,你可以回溯到之前的规格版本,看看是哪个变更导致的。另外,如果项目有多个分支,不同分支的规格可能不同,版本管理能帮你理清这些差异。

我通常用 Git 来管理规格文件,跟代码用同一个仓库。规格的变更记录在 commit message 里写清楚,比如"更新订单导出规格,增加队列重试配置"。

7. 关于规格驱动开发的一些个人体会

用了大半年 OpenSpec 之后,我最大的感受是,它改变的不只是 AI 编程的效率,而是整个开发流程的思维方式。以前写代码的时候,很多决策是在写的过程中临时做的,做完就忘了。现在写规格的时候,这些决策被显性化地记录下来,后面回头看的时候,能清楚地知道当时为什么这么设计。

另一个感受是,规格驱动开发对团队协作的帮助很大。以前 code review 的时候,reviewer 要花很多时间去理解代码的意图。现在有了规格,reviewer 可以先看规格,再看代码,理解成本降低了很多。而且规格本身就是一种文档,新人接手项目的时候,看规格比看代码快得多。

当然,这套方法也不是没有代价。写规格需要时间,执行规格需要时间,验证规格执行结果也需要时间。如果你的项目很小,或者功能很简单,用规格驱动开发可能反而更慢。我的建议是,只在那些需要长期维护、多人协作、逻辑复杂的功能上使用规格驱动开发。一次性的脚本、简单的 CRUD、原型验证这些场景,直接让 AI 写代码就行,不用上规格。

还有一个体会是,规格的质量直接决定了 AI 生成代码的质量。规格写得清楚,AI 生成的代码就靠谱;规格写得模糊,AI 生成的代码就随机。所以与其花时间调 AI 的参数,不如花时间把规格写好。这个投入产出比是最高的。

最后说一个实操中的小技巧。如果你不确定规格该怎么写,可以先让 AI 帮你写一版规格草稿,然后你在草稿的基础上修改。比如你可以跟 Claude Code 说"我要实现一个订单导出功能,请帮我写一份 OpenSpec 规格草稿,包含实体定义、接口定义、行为规则和验收标准"。它生成的草稿可能不完美,但能给你一个起点,你在上面改比从零开始写要快得多。改完之后,再让 AI 按照修改后的规格生成代码。这个"AI 写规格草稿,人改规格,AI 执行规格"的流程,是我目前用下来效率最高的方式。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询