[控场AI]
· 13 分钟阅读· 6,727 字

OpenSpec:让AI编码代理先写规格再写代码

OpenSpec:让AI编码代理先写规格再写代码

OpenSpec 把需求约定移入仓库,用增量规格让 AI 编码代理先对需求达成一致再写代码。

OpenSpec 是一个开源、轻量的规格驱动开发框架,支持 30 多种 AI 编码工具。它把需求约定从聊天窗口迁到仓库中:`openspec/specs/` 记录系统当前行为,`openspec/changes/` 存放每个提议的变更文件夹,内含提案、增量规格、设计与任务。核心是增量规格,只描述新增、修改或删除的部分,因而适合已有代码库。工作流分为探索、提议、人工审查、应用、验证和归档六步,其中人工审查最为关键,改一段话远比改一个 Pull Request 便宜。验证环节存在“谁来检查检查者”的陷阱:同一代理实现并验证容易重复同样的误读,可通过具体场景、Apply 前锁定隐藏验收测试、干净会话验证和 CI 最终裁决来降低风险。OpenSpec 与 ADR 各司其职,前者驱动代理构建当前变更,后者记录长期架构决策,二者在 design.md 交汇。

使用AI编码代理时,最让人头疼的问题往往不是代码写得不对,而是它从一开始就理解错了你的需求。当你直接在对话框里描述一个功能,这个需求只存在于那次会话中——代理按自己的方式解读,你往往在它写了400行代码之后才发现方向偏了。关闭会话,上下文也随之消失。

OpenSpec 试图扭转这个顺序:在写任何代码之前,你和代理先在仓库里用文字约定好要构建什么。它是一个开源(MIT)、轻量的规格驱动开发框架,支持 Claude Code、Codex、Cursor、GitHub Copilot、Gemini CLI、OpenCode 等30多种工具。它不是一个需要迁移过去的新IDE或平台,而是仓库里的一个 Markdown 文件夹、一个小型 CLI,以及一套给代理的指令。

rss source: Spec Before Code: How OpenSpec Keeps AI Coding Agents Building the Right Thing

安装只需两条命令:

npm install -g @fission-ai/openspec@latest
cd your-project
openspec init

openspec init 会询问你使用哪些工具,并为每个工具安装对应的命令和指令。

OpenSpec 的目录结构:规格与变更分离

所有内容都放在 openspec/ 文件夹下,分为两部分:

openspec/
├── specs/                  # 事实来源:系统当前如何行为
│   ├── auth/
│   │   └── spec.md
│   └── payments/
│       └── spec.md
└── changes/                # 提议的修改,每个变更一个文件夹
    ├── add-password-reset/
    │   ├── proposal.md     # 为什么做、做什么
    │   ├── design.md       # 怎么做(技术方案)
    │   ├── tasks.md        # 实现清单
    │   └── specs/
    │       └── auth/
    │           └── spec.md # 增量:auth 中改变的部分
    └── archive/            # 已完成的变更,保留历史

specs/ 按领域描述系统当前的行为,changes/ 存放提议的修改。每个变更是一个自包含的文件夹,因此两个变更可以同时进行而互不干扰。

核心概念是增量规格(delta spec)。一个变更不会重述整个规格,只描述新增、修改或删除的部分。这使 OpenSpec 在已有代码库上表现良好——而这正是大多数实际工作发生的场景。

规格驱动开发(Spec-Driven Development, SDD) 是一类以书面规格为唯一事实来源的开发方法,强调先定义行为契约、再编写实现代码。它在 AI 编码代理场景中重新流行,是因为语言模型容易在长上下文里遗漏或扭曲约束。与传统的“测试驱动开发”不同,SDD 把验收标准提前到自然语言层面,让非工程角色(产品、安全、合规)也能参与审查。OpenSpec 的增量规格正是 SDD 在已有代码库上的变体:不要求从零重写规格,而是用变更文件夹记录每个提议的差异,归档时再合并回主规格。这种“变更即补丁”的思路让规格能随代码演进,而不会迅速过时,也降低了团队引入规格框架的迁移成本。

工作流:从探索到归档

实际操作流程如下:

  1. 探索(/opsx:explore):代理阅读代码库,帮你理解问题和可选方案。不写代码,不做承诺。
  2. 提议(/opsx:propose <name>):它起草变更文件夹:提案、增量规格、设计和任务。
  3. 你来审查。这是最重要的一步。改一段话远比改一个 Pull Request 便宜。
  4. 应用(/opsx:apply):代理实现任务,完成一项勾掉一项。
  5. 验证(/opsx:verify):检查实现是否与规格匹配。这一步属于扩展工作流配置,通过 openspec config profile 启用。
  6. 归档(/opsx:archive):增量合并进 specs/,变更文件夹移入 changes/archive/ 并加上日期前缀。

归档之后,规格描述的是系统现在的状态,而归档的变更保留了它为何变成这样的完整来龙去脉。

这些步骤并非刚性关卡。当你在实现过程中学到新东西时,随时可以回头更新提案或设计。这种灵活性是它相比更重的规格框架最让人喜欢的地方之一。

实战示例:密码重置功能

假设你想让用户通过邮件重置密码,链接15分钟后过期。主要产物如下:

proposal.md 捕捉意图,同样重要的是范围:


**GIVEN/WHEN/THEN** 是行为驱动开发(BDD)常用的场景描述格式,分别对应前置条件、触发动作和可观察结果。它之所以适合规格文档,是因为每个场景几乎可以机械地翻译成一个测试用例,从而消除自然语言中的模糊空间。在 OpenSpec 的增量规格里,场景被当作需求的验收标准:`ADDED Requirements` 下的每条 Requirement 后跟若干 Scenario,分别覆盖正常路径、边界和异常。审查者可以用“能否为这个场景写出断言”来判断需求是否足够具体;如果写不出测试,往往意味着场景中还隐藏着未定义的假设或缺失的输入输出约定。
# Proposal: Add password reset by email

## Intent
Users who forget their password currently need to contact support.
Let them reset it themselves with a secure, short-lived link.

## Scope
In scope:
- "Forgot password" request from the login screen
- Single-use reset link sent by email, valid for 15 minutes
- Revoking active sessions after a successful reset

Out of scope:
- SMS or authenticator-based recovery
- Changes to the sign-up flow

增量规格用具体场景描述可观察行为:

# Delta for Auth

## ADDED Requirements

### Requirement: Password reset by email
The system MUST let a user reset their password through a single-use
link sent to their registered email, valid for 15 minutes.

#### Scenario: Valid reset link
- GIVEN a user requested a password reset 5 minutes ago
- WHEN they open the link and submit a new valid password
- THEN the password is updated
- AND all of the user's active sessions are revoked
- AND the link can no longer be used

#### Scenario: Expired link
- GIVEN a reset link created 16 minutes ago
- WHEN the user opens it
- THEN the reset is refused with a "link expired" message
- AND the password is unchanged

#### Scenario: Unknown email
- GIVEN no account exists for the submitted email
- WHEN a reset is requested
- THEN the response is identical to the one for an existing account
- AND no email is sent

最后那个场景很好地说明了审查步骤为何重要。“不要暴露账户是否存在”在提示词中很容易被遗忘,而它是一项安全需求。写成场景后,它就变得可见、可审查、可测试。

design.md 存放技术方案(令牌如何生成和存储、限流、用哪个邮件服务),tasks.md 是代理在 Apply 阶段遵循的清单。

规格说的是系统必须做什么,设计和任务说的是怎么做。把两者分开,才能让你在审查行为时不被实现细节淹没。

审查规格时该看什么

既然审查是让其他一切值得的一步,就有必要刻意对待。以下是我会问的问题:

  • 范围对吗? 有没有不该在范围内却被放进来的,或者漏掉了本该排除的?
  • 场景具体吗? “链接会过期”是模糊的。“16分钟前创建的链接会被拒绝”才是可测试的。
  • 边界情况覆盖了吗? 错误、空状态、滥用、权限、并发。
  • 是否与已有决策冲突? 新库、新模式、不同的数据存储方式。
  • 每个场景都能写出测试吗? 如果不行,说明还是太含糊。

什么时候值得用

  • 触及系统多个部分的功能。 工作越分散,晚发现误解的代价越大。
  • 已有代码库。 代理需要在修改前理解已有内容,增量规格正是为此设计的。
  • 多人(或多个代理)协作同一份代码。 大家从同一份文档出发,而不是各自的聊天记录。
  • 需求仍在变化中。 更新规格并重新生成任务,比重写代码容易。

什么时候不值得:一行修复、小的视觉调整、一次性脚本。这些场景下流程碍事多于帮忙。OpenSpec 官方文档也持相同方向:大多数变更应使用轻量规格,完整流程留给 API 契约、迁移和安全变更等高风险工作。

我的经验法则:如果我需要先向同事解释这个任务才能开始,它就值得一份规格。

验证陷阱:谁来检查检查者?

在 LinkedIn 上发布关于 OpenSpec 的内容时,有人提出了一个每个使用规格驱动开发的团队都该思考的问题:

如果实现任务和验证实现的都是同一个代理,什么能阻止它在两步中用同样的方式误读规格?

这个担忧是真实的。如果同一个代理、同样的上下文,既实现又验证,它倾向于重复同样的误读。Verify 步骤擅长捕捉遗漏或未做的事,但它不能保证最初的解读就是对的。

降低风险的做法:

  1. 让场景具体。 越具体,带有真实输入输出值,解读空间越小。规格审查就是消除歧义的时刻。
  2. 在 Apply 之前把场景变成测试。 本质上就是验收测试,由人或独立代理基于规格而非实现来编写。每个 GIVEN/WHEN/THEN 天然对应一个测试。
  3. 对实现代理隐藏这些测试。 只在 CI 中运行,或在代理权限中封禁该文件夹的读取权限。就像留出评估集一样,这能阻止代理为了让测试通过而写代码。
  4. 在干净的会话中验证, 不带实现阶段的历史。用不同的模型也有帮助。OpenSpec 官方文档建议在实现前清理上下文,同样的卫生习惯也适用于验证。
  5. 让 CI 说了算。 流水线中运行的测试说了算,而不是代理说一切匹配。

讨论还更进一步:如果团队之外的人,比如客户或审计员,怀疑一次绿色通过,他们怎么知道隐藏的测试没有在代码写完后被编辑过、而且确实针对那个确切构建运行?简短的回答涉及在 Apply 前锁定测试、在 CI 中固定其 commit,以及签名的构建溯源(SLSA、Sigstore)。这话题足够大,值得单独写一篇。

验收测试 是依据需求而非实现细节编写的测试,用于判定系统是否满足规格。在 AI 编码流程中,把场景提前转成验收测试有一层额外意义:它可以充当“评估集”。如果实现代理看不到这些测试,就无法为了通过而针对性写代码(即奖励黑客,reward hacking),从而让测试结果更接近对规格符合度的真实度量。要达到这一效果,通常需要在 Apply 之前锁定测试内容、在 CI 中固定其 commit,并用分支保护或权限限制阻止代理修改。更进一步,签名构建溯源(如 SLSA、Sigstore)可让外部审计者验证某次绿色通过确实针对该次构建运行,而不是事后调整过的测试。

OpenSpec 与 ADR:各司其职

经常被问到:如果我已经写 ADR,还需要规格吗?它们解决不同问题,配合得很好。

ADROpenSpec
回答我们为什么做这个决定?系统应该做什么?
单元一个决策一个变更或能力
生命周期不可变;被新 ADR 取代活的;每次归档变更都会更新
是否导向实现?否是:任务、应用、验证
视野长期,历史当前变更和当前状态

它们在 design.md 交汇。一个变更的设计常包含技术决策。如果决策只对该功能重要,可以留在那里。如果它超出该功能、影响整个项目(换数据库、改认证方式、引入队列),就写一份 ADR 并从设计中链接过去。

一个实用规则:如果两年后有人会想知道为什么这么做,写 ADR。如果你想让代理现在构建正确的东西,写规格。

ADR(Architecture Decision Record,架构决策记录) 是一种轻量文档,用来记录一项重要技术决策及其背景、备选方案和后果。典型 ADR 一旦写入就不再修改,被后续 ADR 取代或推翻,从而形成一条可追溯的决策历史。它与 OpenSpec 的互补关系在于:ADR 回答“为什么当初这样选”,面向长期和跨功能;OpenSpec 回答“系统现在应该做什么”,面向当前变更和代理执行。当一项技术选择只影响单个变更时,可以留在该变更的 design.md 中;当它超出该功能范围、影响整个项目(换数据库、改认证方式、引入消息队列)时,就应升级为 ADR,并从设计中链接过去,避免重要决策埋在某个变更文件夹里被遗忘。

上手建议

  1. 安装 CLI,在已有项目中运行 openspec init。
  2. 挑一个中等规模的真实功能,不要一行修复,也不要重写。
  3. 从 /opsx:explore 开始,让代理先摸清代码再提议任何东西。
  4. 运行 /opsx:propose,然后花真正的时间审查。把含糊的场景重写到每个都能变成测试。
  5. 应用、验证、归档,然后看看 specs/ 如何变化。

经历一两次变更后,你就会有感觉:哪些工作值得一份规格,哪些不值得。

总结

AI 代理非常擅长构建你要求的东西。难的是确保你要求的正是你真正想要的。OpenSpec 把这个约定从聊天窗口移到了仓库里,人和代理都能阅读、审查并在之后回来查看。它不是魔法,验证步骤需要好的测试才能可信。但在写代码之前先写规格,是与代理协作中最能改善工作质量的习惯之一。

#OpenSpec#AI编码代理#规格驱动开发#spec-driven development#AI编程#增量规格#ADR#Claude Code
分享:

相关推荐