SDD(规范驱动开发)和传统的前期文档区别不在于写什而在于谁来读它。传统文档写给人看的,而SDD 规范写给 AI agent 看,其结构让模型能够在生成过程中引用它、对照它检查输出、在一个 session 结束、新 session 开始时借此恢复上下文。
为什么选 OpenSpec
我评估过两个主要的开源方案——GitHub 的 SpecKit 和 Fission-AI 的 OpenSpec。
SpecKit 有一个 constitution模型,能把跨领域的规则(TDD 要求、编码标准、合规约束)编码进每一个功能的工作流中;对企业合规场景这个很不错。OpenSpec 是另一个方向:三个命令,不需要事先写 constitution,开箱即用支持 25 个以上的 AI 工具。它对每份规范都有一个硬性的 50KB 上下文限制——这一点为什么重要,后面会展开说。
作者用的是 Claude Code、偶尔用 Cursor,所以OpenSpec 更适合我这种场景。不过如果你在受监管的行业,需要强制执行"每个 API 端点都必须有 OpenAPI 文档"这类规则,SpecKit 的 constitution 模型可能更合适,这是一个合理的取舍,并没有对错之分。
三阶段工作流
OpenSpec 强制执行一个严格的状态机,没有任何阶段可选,也没有任何阶段能跳过。下面是它实际的运作方式。
阶段一:Propose
起点是一个意图,不是代码。
/opsx:propose "add rate limiting to the public API endpoints"
agent 会先读取现有的
openspec/specs/
(关于当前系统的真实来源),然后生成一个新文件夹:
openspec/changes/add-rate-limiting/
,里面包含四份文件。
proposal.md 是结构化文档,涵盖:解决什么问题、会有什么变化(标记为 ADDED、MODIFIED 或 REMOVED)、什么不变、关键风险和依赖。specs/ 是以 GIVEN/WHEN/THEN 格式写成的行为场景,也就是验收标准,具体到可以直接当测试用例用。design.md是技术方案:库、模式、架构决策。tasks.md 是拆成小块、可独立审查的实现清单。
人要在写下任何一行实现代码之前审查完这一切,批准了阶段二才开始;提议有问题,就在这里纠正,不能等到实现之后再回来纠正。
ADDED/MODIFIED/REMOVED 这几个增量标记值它们迫使写规范的人和 AI 都明确说清楚现在存在什么、之后会存在什么。听起来有点繁琐,但实际效果是,能在很多"哦等等,那个模块已经这么做了"的时刻变成技术债之前把它们揪出来。
阶段二:Apply
提议获批之后:
/opsx:apply
AI 逐一处理
tasks.md
里的任务,每一步都读规范文件,不是凭记忆读 prompt。这是它和无结构 AI 编码的关键区别,每个任务足够小,也可以独立测试;哪个任务失败了、输出不对,能准确看到违反了哪条规范场景,反馈循环很紧。
Apply 跑完之后运行:
/opsx:verify
这一步对照规范检查实现,不只是跑测试,而是验证代码的行为是否符合 GIVEN/WHEN/THEN 场景。发现差距时会准确告诉哪个场景没被满足。
我在大概三十个功能上跑过 verify,大约三分之一会发现问题。总是一些小地方:一个缺失的错误状态,tasks 里没覆盖到的边界情况。能在合并之前把这些揪出来,是这个框架真正的价值所在。
阶段三:Archive
/opsx:archive
变更文件夹移动到
openspec/changes/archive/
,增量规范合并进主
openspec/specs/
,即项目统一的真实来源。
这是 OpenSpec 在结构上和 SpecKit 拉开差距的地方。SpecKit 会无限期地为每个功能维护独立的规范文件;OpenSpec 把一切整合进一份代表系统当前状态的活文档里。
所以任何时间点都可以直接读
openspec/specs/
理解整个系统,不用从几十个功能文件里拼凑上下文。AI 也读这份文档——每次新提议开始时都会读。
50KB 上下文限制:是重要的特性,不是缺陷
这个看似随意的约束,其实是框架里最重要的设计决策之一。
现代 LLM 的上下文窗口很大,有些超过一百万 token,很容易让人觉得干脆把所有东西都塞进去——所有规范、所有代码、所有历史记录。问题是大上下文不等于聚焦的上下文。给 agent 塞进 800KB 的规范和代码,它不会平等地读完所有内容,会更关注某些部分,而它关注的部分未必是当前这个具体任务真正在意的部分。
OpenSpec 的 50KB 限制逼着人对规范里放什么保持克制,不能一股脑塞进去,得想清楚 agent 实现这个功能到底需要知道什么。
这种克制带来一个副作用:规范本身会变得更好——简洁、有针对性,是一份简报,不是信息倾泻。对于需要在大型代码库上并行跑多个 agent 的团队,这个约束还意味着可以同时跑好几个 agent 而不互相抢上下文预算,这在规模化之后是个很实在的运维问题。
一个真实例子:速率限制功能
具体一点。这是我正在做的一个项目里,一份提议的精简版本。
proposal.md节选:
## What is changing
ADDED: Rate limiting middleware on all /api/v1/* routes
MODIFIED: Express app configuration to mount rate limiter before routes
ADDED: Redis-backed rate limit counter (per API key, per 15-minute window)
## What is NOT changing
Authentication flow, response format, existing error codes.
## Risk
Redis dependency — if Redis is unavailable, the middleware must fail open
(allow requests) not fail closed (block all traffic).
specs/rate-limiting.md节选:
GIVEN a valid API key making requests
WHEN the key exceeds 100 requests per 15-minute window
THEN the API returns 429 with a Retry-After header
GIVEN Redis is unavailable
WHEN a request arrives at the rate limiter
THEN the request proceeds as normal (fail-open behaviour)
第二个场景,Redis 故障时的 fail-open 行为,不是 AI 第一版草稿里的内容是在审查时加进去的,来自对故障模式的思考。
这正是规范审查该起的作用:逼着人去想整个系统,而不只是描述一个功能。最终代码优雅地处理了 Redis 故障,不是因为 AI 对容错有多懂,而是规范告诉了它需要实现的行为。
什么样的规范算好规范
写了几十份之后,总结出几条区分好坏的标准。
描述行为,不要规定实现方式。"用户必须在 30 秒内收到通知"是个好规范;"使用 WebSocket 推送通知"是实现决策,该放进 design.md。
明确指出边界情况。正常路径谁都会写,规范的价值恰恰体现在边界情况上:失败了会怎样?被调用两次会怎样?输入为空会怎样?
每个场景保持原子性,一个场景一个 GIVEN/WHEN/THEN,不要嵌套条件;要表达复杂逻辑,就拆成多个场景。
用平实的语言写,平实到可以拿给非技术的同事看。一个场景如果要铺垫三段话才能讲清楚背景,说明它太复杂了。
总结
这一篇讲的是 OpenSpec 在单一仓库内如何落地:一个 agent,一份代码库,三个阶段走完一个功能。这套流程在这个前提下是成立的——proposal 有地方审查,spec 有地方沉淀,verify 有唯一一份代码可以对照检查。
意图先于代码写清楚,在实现开始前就有机会纠错,AI 每一步都对照 spec 而不是凭记忆生成,verify 又把实现和场景重新核对一遍。50KB 的限制看似只是个容量约束,实际上逼着规范写得足够聚焦,这也是整套流程能跑得动的前提之一。
规范全程被 AI 使用而不是写完就闲置。
by Apurv Sheth