你大概见过这个场景。
让 Claude Code(或 Codex,或随便哪个 agent)接一个稍微大一点的活------比如新加一个 feature,涉及三四个文件。前半小时一切顺利,代码看起来挺像样。一小时之后回来 review,发现它在新 service 里调用了一个根本不存在的 helper,理由是"按惯例这种逻辑通常会有一个 utils 函数"。
它不是说谎,它是真的"忘了"。一小时之前它还认认真真读过那一层的实现,现在 context 已经飘到只剩"惯例"了。
这个问题有解,但不在 prompt 工程里------在工作流里。这篇就讲解法:Spec-Driven Development(SDD) ,以及它的一个具体工程化实现:OpenSpec。
一、SDD 是什么:一句话讲清楚
Spec-Driven Development = 写代码之前先把要做什么写下来;每写完一步,更新这份"写下来的东西" 。
听起来像废话。但和传统的"写需求文档"有两个本质区别。
第一,这份文档是给 agent 读的------所以它在 repo 里、用 markdown、结构固定、依赖关系显式。不是 Confluence 里那种写完就没人看的 PRD。
第二,文档本身是工作状态的载体 。开始写代码之后,agent 不是"参考"这份文档,是一边干活一边更新它 ------每完成一个步骤,在 markdown 里把那一行从 [ ] 改成 [x]。
为什么必须这么做?Anthropic 把答案写得很直白:
Progressive summarization risks: condensing numerical values, percentages, dates, and customer-stated expectations into vague summaries.
agent 跑久了 context 撑不下,会自动压缩历史。压完之后具体事实变成模糊散文------一开始的"客户要求 7 天内退 80%"会变成"客户希望尽快得到补偿"。
The role of scratchpad files for persisting key findings across context boundaries. Structured state persistence for crash recovery: each agent exports state to a known location, and the coordinator loads a manifest on resume.
意思就是:把关键状态从对话历史里搬到磁盘上的文件里。SDD 干的就是这件事。
二、OpenSpec 是什么
OpenSpec 是把 SDD 这套抽象工作流固化下来的一个 CLI + 一组 Claude Code skill。
它要求每一次任务(OpenSpec 里叫 "change")在 repo 里长这样:
bash
openspec/changes/<change-name>/
├── proposal.md # 这个 change 要做什么、为什么做
├── design.md # 架构选型、核心决策
└── tasks.md # 实现步骤,checklist 形式
工作流由四个命令(对应四个 skill)驱动:
bash
/opsx:explore → /opsx:propose → /opsx:apply → /opsx:archive
想清楚 写下来 干活 收尾
每一步都对应一个具体阶段。下面分别讲。
三、四步走
Step 1:/opsx:explore --- 想清楚,但坚决不动手
这一步最反直觉。skill 文件里有一条硬规则:
Explore mode is for thinking, not implementing. You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features.
读代码、画 ASCII 架构图、challenge 你的假设、对比两个方案的 tradeoff------这些都行。但不写一行实现代码。
什么时候用?拿到一个模糊的 ticket("加个实时协作功能");接手一段不熟的 legacy 代码;在两种实现方案之间拿不准;甚至只是想搞明白一个 bug 的根因------这些场景都先 explore。
产出是你脑子里清楚了。如果讨论中有结论值得固化,直接进入下一步。
Step 2:/opsx:propose --- 把想清楚的东西写成三份 markdown
explore 阶段达成共识之后,propose 把它落到磁盘。
skill 背后会跑这几个命令:
bash
openspec new change "<name>" # 建文件夹
openspec status --change "<name>" # 查看 artifact 依赖顺序
openspec instructions <id> --change ... # 拿每个 artifact 的生成规则
按 proposal → design → tasks 的顺序生成,每一步都把前面的产出当作 dependency 读入。
这不是凑流程。这是 explicit context passing------subagent 不会自动继承父 context,必须显式传。propose 里的依赖关系就是这种显式传递的工程化:design 是基于 proposal 写的,所以生成 design 之前先把 proposal 读一遍;tasks 是基于前两个写的,所以生成 tasks 之前两个都要读。
到这一步你还没动一行代码,但你已经有了一份后续任何 session(自己的、同事的、新开的 agent 的)都能续上的工作单。
Step 3:/opsx:apply --- 一边写一边打勾
这是真正写代码的阶段。skill 的循环很朴素:读 context 文件 → 挑下一个 [ ] 任务 → 实现 → 把 [ ] 改成 [x] → 下一个。
最关键的一行规则:
Mark task complete in the tasks file:
- [ ]→- [x]
这个动作看起来微不足道,效果是把"我刚刚做完了什么"从 agent 的对话历史里立刻搬到磁盘。
下次 context 飘了、session 崩了、你下周回来续------只要看 tasks.md,[x] 是已完成事实,[ ] 是剩余工作。状态永远在文件里,不在 agent 脑子里。
什么时候 pause?skill 写得很明确:任务模糊就停下来问;实现中发现 design 有问题,建议更新 artifact 而不是硬干;遇到 blocker 就报告等指示。不猜。
Step 4:/opsx:archive --- 把临时记录提升为永久知识
所有 task 打勾之后,archive 做两件事。
第一件:把整个 change 文件夹挪到 openspec/changes/archive/YYYY-MM-DD-<name>/。等于给本次工作打了一个时间戳归档。
第二件:把这个 change 里产出的 delta spec 同步回 main spec(openspec/specs/<capability>/spec.md)。
这步的意义在于区分两类记忆 。openspec/changes/<name>/ 是"工作中"的临时状态,为了完成这一次任务而存在;openspec/specs/ 是项目的长期 source of truth------下一个写相关功能的人(或 agent),直接读这里,不用翻 git history。
archive 是把临时记录提升为永久知识的那一刻。
四、一个完整的例子
我们团队最近在标准化 AI 工作流,RFC 里有一个真实例子,从一个 ClickUp ticket 走到 PR:
bash
/clickup fetch this ticket -<ticket-id>
↓
/opsx:explore
→ 搞清楚要做什么、有哪些边界条件
→ 把所有疑问问出来,挨个对答案
↓
/opsx:propose
→ 在 openspec/changes/ 下生成 proposal、design、tasks 三个文件
↓
/opsx:apply
→ review 完三个文件后,按 tasks 实现
→ 每完成一项,[ ] → [x]
↓
/opsx:archive
→ 检查对应的 feature 文件夹存不存在
→ 存在就追加,不存在就新建
→ 同步 spec
整个流程跑下来,中间任何一刻 agent 的 context 出问题,都不丢工作进度------因为关键状态全在 markdown 里。
五、怎么马上开始
OpenSpec 是开源 CLI,GitHub 上能找到。装好之后做三件事。
第一,熟悉文件夹约定 。repo 根目录会有 openspec/changes/(进行中)、openspec/changes/archive/(归档)、openspec/specs/(长期 source of truth)。这套结构是 OpenSpec 所有命令的前提。
第二,把对应的 skill 文件放到 Claude Code 能找到的地方 。项目级放 .claude/skills/(团队共享,跟着 git);个人实验放 ~/.claude/skills/(只对你生效)。
第三,拿一个真实的小 ticket 跑一遍完整流程。第一次会觉得慢------多了一步"先把事情想清楚再写"。但跑过两三次之后,会发现两个变化:AI 第一遍写出来的代码一次过的概率明显上升;回头改的时候,不再需要"重新解释一遍上下文",直接读 tasks.md 就行。
收个尾
ad hoc 用 AI 写代码会越用越散,这件事不是 AI 不行,是工作流不对。Anthropic 在认证大纲里其实把答案写得挺清楚:LLM 应用的可靠性,大头不在模型多聪明,而在状态管理多干净。
SDD 是这个原则的方法论,OpenSpec 是它的工程化实现。从 /opsx:explore 开始,五分钟之内你就能跑出第一份属于自己的 manifest。