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,以及一套给代理的指令。

安装只需两条命令:
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 在已有代码库上的变体:不要求从零重写规格,而是用变更文件夹记录每个提议的差异,归档时再合并回主规格。这种“变更即补丁”的思路让规格能随代码演进,而不会迅速过时,也降低了团队引入规格框架的迁移成本。
工作流:从探索到归档
实际操作流程如下:
- 探索(
/opsx:explore):代理阅读代码库,帮你理解问题和可选方案。不写代码,不做承诺。 - 提议(
/opsx:propose <name>):它起草变更文件夹:提案、增量规格、设计和任务。 - 你来审查。这是最重要的一步。改一段话远比改一个 Pull Request 便宜。
- 应用(
/opsx:apply):代理实现任务,完成一项勾掉一项。 - 验证(
/opsx:verify):检查实现是否与规格匹配。这一步属于扩展工作流配置,通过openspec config profile启用。 - 归档(
/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 步骤擅长捕捉遗漏或未做的事,但它不能保证最初的解读就是对的。
降低风险的做法:
- 让场景具体。 越具体,带有真实输入输出值,解读空间越小。规格审查就是消除歧义的时刻。
- 在 Apply 之前把场景变成测试。 本质上就是验收测试,由人或独立代理基于规格而非实现来编写。每个 GIVEN/WHEN/THEN 天然对应一个测试。
- 对实现代理隐藏这些测试。 只在 CI 中运行,或在代理权限中封禁该文件夹的读取权限。就像留出评估集一样,这能阻止代理为了让测试通过而写代码。
- 在干净的会话中验证, 不带实现阶段的历史。用不同的模型也有帮助。OpenSpec 官方文档建议在实现前清理上下文,同样的卫生习惯也适用于验证。
- 让 CI 说了算。 流水线中运行的测试说了算,而不是代理说一切匹配。
讨论还更进一步:如果团队之外的人,比如客户或审计员,怀疑一次绿色通过,他们怎么知道隐藏的测试没有在代码写完后被编辑过、而且确实针对那个确切构建运行?简短的回答涉及在 Apply 前锁定测试、在 CI 中固定其 commit,以及签名的构建溯源(SLSA、Sigstore)。这话题足够大,值得单独写一篇。
验收测试 是依据需求而非实现细节编写的测试,用于判定系统是否满足规格。在 AI 编码流程中,把场景提前转成验收测试有一层额外意义:它可以充当“评估集”。如果实现代理看不到这些测试,就无法为了通过而针对性写代码(即奖励黑客,reward hacking),从而让测试结果更接近对规格符合度的真实度量。要达到这一效果,通常需要在 Apply 之前锁定测试内容、在 CI 中固定其 commit,并用分支保护或权限限制阻止代理修改。更进一步,签名构建溯源(如 SLSA、Sigstore)可让外部审计者验证某次绿色通过确实针对该次构建运行,而不是事后调整过的测试。
OpenSpec 与 ADR:各司其职
经常被问到:如果我已经写 ADR,还需要规格吗?它们解决不同问题,配合得很好。
| ADR | OpenSpec | |
|---|---|---|
| 回答 | 我们为什么做这个决定? | 系统应该做什么? |
| 单元 | 一个决策 | 一个变更或能力 |
| 生命周期 | 不可变;被新 ADR 取代 | 活的;每次归档变更都会更新 |
| 是否导向实现? | 否 | 是:任务、应用、验证 |
| 视野 | 长期,历史 | 当前变更和当前状态 |
它们在 design.md 交汇。一个变更的设计常包含技术决策。如果决策只对该功能重要,可以留在那里。如果它超出该功能、影响整个项目(换数据库、改认证方式、引入队列),就写一份 ADR 并从设计中链接过去。
一个实用规则:如果两年后有人会想知道为什么这么做,写 ADR。如果你想让代理现在构建正确的东西,写规格。
ADR(Architecture Decision Record,架构决策记录) 是一种轻量文档,用来记录一项重要技术决策及其背景、备选方案和后果。典型 ADR 一旦写入就不再修改,被后续 ADR 取代或推翻,从而形成一条可追溯的决策历史。它与 OpenSpec 的互补关系在于:ADR 回答“为什么当初这样选”,面向长期和跨功能;OpenSpec 回答“系统现在应该做什么”,面向当前变更和代理执行。当一项技术选择只影响单个变更时,可以留在该变更的 design.md 中;当它超出该功能范围、影响整个项目(换数据库、改认证方式、引入消息队列)时,就应升级为 ADR,并从设计中链接过去,避免重要决策埋在某个变更文件夹里被遗忘。
上手建议
- 安装 CLI,在已有项目中运行
openspec init。 - 挑一个中等规模的真实功能,不要一行修复,也不要重写。
- 从
/opsx:explore开始,让代理先摸清代码再提议任何东西。 - 运行
/opsx:propose,然后花真正的时间审查。把含糊的场景重写到每个都能变成测试。 - 应用、验证、归档,然后看看
specs/如何变化。
经历一两次变更后,你就会有感觉:哪些工作值得一份规格,哪些不值得。
总结
AI 代理非常擅长构建你要求的东西。难的是确保你要求的正是你真正想要的。OpenSpec 把这个约定从聊天窗口移到了仓库里,人和代理都能阅读、审查并在之后回来查看。它不是魔法,验证步骤需要好的测试才能可信。但在写代码之前先写规格,是与代理协作中最能改善工作质量的习惯之一。
相关推荐

前端转AI Agent开发:一线架构师解析职业路径与红利
一线AI架构师解析前端转AI Agent开发的行业变化与落地路径:纯前端岗位为何消失、Agent+全栈为何成为招聘主流,以及Agent开发工程师、FDE、远程接单三条转型路线和对应学习策略。

AI Agent开发课程怎么选?五条避坑指南
机械转行开发者踩坑两年总结的AI Agent开发课程筛选清单:完整交付链路、可写进简历的项目、真实开发视角、更新频率、陪跑反馈五个维度,附具体判断方法和避坑要点。

扣子(Coze)智能体搭建入门:零代码开发AI Agent实战指南
扣子(Coze)是字节跳动推出的一站式AI应用开发平台,支持零代码拖拽搭建AI智能体。本文基于B站教程梳理扣子的核心概念、三大优势、国内版与国际版差异,以及智能体与应用的区别,帮助初学者快速入门AI Agent开发。