# 规格驱动与计划先行

> 让智能体直接动手，错了的代价是一份一千行的 diff；先把规格和计划写下来，错了的代价是改几行文字。本课讲探索、计划、实现、提交四步工作流，规格怎么写，大任务怎么拆成可验证的小步，以及什么时候别计划。

- 网址：https://coding.chengyu.dev/learn/engineering/spec-driven/
- 学习路径：AI 编程的工程方法
- 预计用时：30 分钟；核实日期：2026-10-25
- 官方来源：[Best practices for Claude Code（Claude Code 官方文档）](https://code.claude.com/docs/en/best-practices)，[Choose a permission mode（Claude Code 官方文档，含计划模式）](https://code.claude.com/docs/en/permission-modes)，[Explore the context window（Claude Code 官方文档）](https://code.claude.com/docs/en/context-window)，[Keep Claude working toward a goal（Claude Code 官方文档）](https://code.claude.com/docs/en/goal)，[Manage costs effectively（Claude Code 官方文档）](https://code.claude.com/docs/en/costs)，[Effective harnesses for long-running agents（Anthropic）](https://www.anthropic.com/research/effective-harnesses-for-long-running-agents)

## 学习目标

- 能用计划模式把探索和修改分开，并审阅智能体给出的计划
- 能写出自包含的规格文档：目标、范围外、接口、验收标准、端到端验证
- 能把大任务拆成每一步都有明确验证命令的小步
- 能判断一个任务该走完整流程、只做轻量计划，还是直接动手

你对智能体说「给后台加一个 Google 登录」。四十分钟后它交来一个一千二百行的 diff：引入了一个新的认证库，另起了一套会话存储，还顺手重构了用户表。代码能跑。问题是项目里本来就有会话模块，你只需要在它旁边加一个 OAuth 回调，三百行以内就能搞定。

这类失败的根源不是模型不会写，而是**它在你没来得及纠正的时候，已经替你做了一连串架构决定**。事后在一千二百行代码里找出哪些决定错了，是最贵的纠错方式；在一页计划里发现它打算「另起一套会话存储」，只需要划掉一行字。

规格驱动和计划先行，本质上就是**把错误拦截在最便宜的地方**。

## 工作流：探索、计划、实现、提交

Claude Code 官方最佳实践把推荐流程拆成四步，我认为这个划分对任何工具都成立：

1. **探索**：只读，不改。让智能体读代码、回答问题，建立对现状的理解。
2. **计划**：产出一份实现计划，你审阅、修改、批准。
3. **实现**：按计划写代码，并**对照计划**验证。
4. **提交**：写清楚的提交信息，开 PR。

前两步靠计划模式（plan mode）来保证「只读」。在 Claude Code 里有三种进入方式：按 `Shift+Tab` 循环切换直到状态栏显示 `⏸ plan mode on`；启动时用 `claude --permission-mode plan`；或者在单条提示前加 `/plan`。计划模式下，智能体可以读文件、搜索、回答问题，但在你批准计划之前不会修改源码。

计划就绪后，批准界面有三个选择：用自动模式继续、手动逐个批准编辑，或者「继续计划」。别急着点第一项，先按 `Ctrl+G`，把计划在你自己的编辑器里打开，**直接改**。计划是文字，改起来比改代码便宜得多，这是整个流程最值钱的一步。

两个机制值得知道：计划会写成文件，保存在 `~/.claude/plans/`（路径可配置）；长会话被压缩之后，计划文件会被重新注入，所以它是能挺过压缩的「锚」。如果你用的是别的工具，原则一样：**让计划落到文件里，而不是只存在对话里**。

```text
$ claude --permission-mode plan
> 阅读 src/auth/ 理解现有会话和登录流程，再看看环境变量里的密钥是怎么管理的。
⏺ （只读探索：Read、Grep……不修改任何文件）
> 我要加 Google OAuth。需要改哪些文件？复用现有会话模块，给出计划。
⏺ 计划：1) 新增 src/auth/google.ts … 2) 复用 session.create() …
# 按 Ctrl+G 在编辑器里改计划，再批准执行
```

## 规格文档：让计划有一个靠得住的源头

计划模式解决「想清楚再动手」，但如果需求本身是含糊的，计划只是把含糊换了个形式。较大的功能，应该先写**规格（spec）**，它比计划更稳定，由人拥有；计划是智能体为了完成规格临时生成的，可以丢弃重来。

官方推荐了一种很好用的办法：让智能体采访你。你只给一句话需求，让它用提问工具连续追问技术实现、边界情况、取舍，把你没想到的难点挖出来，最后写成 `SPEC.md`。**写完规格就开一个新会话去执行**，新会话的上下文全部用于实现，旁边还有一份书面规格可以对照。

**用采访产出规格**

```text
我想做：给管理后台加 Google 账号登录，已有邮箱密码登录要保留。

请先不要写任何代码。用提问工具对我做一次深入采访，重点挖我可能没想到的难点：技术实现、边界情况、与现有会话模块的关系、失败路径、安全问题、取舍。不要问显而易见的问题。

问到没有新问题为止，然后写一份 SPEC.md，包含：
1. 目标与非目标（明确写出「本次不做什么」）；
2. 涉及的文件和接口（写具体路径和函数名，不要写「相关模块」）；
3. 验收标准：每条都是别人能照着执行、并得出通过或失败的检查；
4. 最后一节「端到端验证」：从零开始，如何证明这个功能真的可用。
```

好规格有三个特征，都来自同一个要求：**自包含**，新会话只读它就能干活。

- **写具体的名字**：文件路径、接口、要复用的现有函数。「复用 `src/auth/session.ts` 的 `createSession`」比「和现有登录对接」强得多，它直接堵住了开头那个「另起一套会话存储」的错误。
- **写范围外**：智能体倾向于「顺手」做更多。把「不改用户表结构」「不引入新的认证库」写进去，等于提前划好围栏。
- **以端到端验证收尾**：怎样从头到尾证明它能用。这一条会在下一课展开，它决定了智能体能不能自己判断「做完了」。

规格不需要长。一页纸，几十行，够了：

```markdown
# SPEC: 管理后台 Google 登录

## 目标
管理员可用 Google 账号登录；邮箱密码登录保持不变。

## 不做
- 不改 users 表结构（Google 的 sub 存入已有的 external_id 字段）
- 不引入新的认证库，使用已安装的 openid-client
- 不做注销所有设备

## 接口
- 新增 `GET /auth/google` 与 `GET /auth/google/callback`（`src/auth/google.ts`）
- 登录成功后调用现有 `createSession(userId)`（`src/auth/session.ts`）

## 验收标准
1. 回调 state 不匹配时返回 400，且不创建会话
2. 首次登录的 Google 用户必须已存在于白名单邮箱，否则 403
3. `npm run verify` 通过，且新增测试覆盖上述两条

## 端到端验证
本地启动后，用测试 Google 账号走完整登录流程，并用 curl 验证
带会话 Cookie 能访问 /admin，不带则 401。
```

## 把大任务拆成可验证的小步

规格定义了终点，计划决定路径。**计划的质量，看每一步是否带着「怎么验证」。**

Anthropic 在长时任务的 harness 文章里记录了一个典型失败：给智能体一个高层目标，它会试图一口气做完，上下文用光时停在半成品上，后来的会话看到一堆半成品，甚至直接宣布「已完成」。他们的对策是把需求展开成一份功能清单，每项带验证步骤和一个 `passes` 标记，一次只做一项，做完提交并更新进度。你不需要搭整套 harness，但可以抄这个思路：

```markdown
# PLAN.md（由智能体起草，人审阅后冻结）

- [ ] 1. 新增 google.ts：授权跳转与 state 生成
      验证：`npm test -- src/auth/google.test.ts` 通过 state 相关用例
- [ ] 2. 实现回调：校验 state，换取令牌，解析邮箱
      验证：同一测试文件通过；伪造 state 的用例返回 400
- [ ] 3. 白名单检查与创建会话
      验证：新增 2 个用例；`npm run verify` 全绿
- [ ] 4. 路由接入与端到端检查
      验证：按 SPEC 的「端到端验证」手动走一遍，贴出 curl 输出
```

一个合格的步骤满足四点：

- **有完成条件**：一条命令或一个可观察的结果，通过或失败，没有「看起来差不多」。
- **小到一次 diff 能看完**：我自己的经验是，超过两三百行变更的步骤就该再拆；这是判断，不是标准。
- **顺序有依赖逻辑**：先有底层能力，后接入口，每一步结束时代码处于能跑的状态。
- **每步一提交**：出了问题 `git revert` 或 `/rewind` 都有清晰的落点。

审阅计划时，不必抠措辞，盯住三件事：**它打算改哪些文件**（有没有越界）、**它打算复用什么**（有没有重复造轮子）、**每一步怎么验证**（没有验证的步骤，等于没有步骤）。

计划批准之后，长任务可以交给 `/goal` 无人值守地推进：给一个可验证的终态，比如「`npm run verify` 退出码为 0，且没有改动测试文件以外的无关模块，或者在 20 轮后停止」。评估由另一个模型在每轮结束后做，它只能依据对话里已经出现的内容判断，所以要让智能体把命令输出打印出来。

## 什么时候不要计划

计划不是免费的，它有时间成本，也会产生「计划看起来很完整、现实一碰就变」的错觉。官方给的判断标准很实用：**如果你能用一句话描述这次 diff，就跳过计划。** 改个拼写、加一行日志、重命名一个变量，直接做。

还有几种情形，我倾向于轻量对待甚至跳过：

- **探索性原型**：你自己也不知道要什么，需要看到东西才能判断。此时模糊的提示加快速迭代，比写规格更有效，但要说好这份代码是一次性的。
- **修一个有清晰复现的 bug**：直接给复现步骤，要求先写失败的测试再修，比计划更管用。
- **计划已经比代码长**：说明任务本身该拆小，或者你在过度设计。

反过来，这些情形一定要计划：改动跨多个文件，对要改的代码不熟，方案有多种选择且选错代价大，或者涉及数据、权限、支付这类出错代价高的地方。

最后提醒一个陷阱：计划是活的。实现中发现计划错了，应该更新计划文件并告诉智能体，而不是让代码悄悄偏离计划。偏离而不更新，下一个会话读到的就是一份过期的谎话。

> 小测：下面哪一个计划步骤写法最合格？

> 小测：下面哪个任务最适合直接让智能体动手，而不是先走计划模式和规格流程？

- [ ] 挑一个跨多个文件的真实需求，用 `claude --permission-mode plan` 完成探索和计划，不要批准就退出
- [ ] 按 `Ctrl+G` 在编辑器里改一次计划：至少删掉一处越界改动，或补上一处复用
- [ ] 用「采访产出规格」提示词写一份 `SPEC.md`，检查它是否含有具体路径、范围外和端到端验证
- [ ] 把计划里每一步都补上验证命令，没法验证的步骤要么拆开要么删除
- [ ] 开一个新会话，只给它规格和计划，观察能否自己完成而不再向你追问

延伸阅读：从想法到规格的完整练习见[实战：从想法到规格](/learn/ship/idea-to-spec/)，上下文如何影响计划的存活见[上一课](/learn/engineering/context-engineering/)。下一课讲怎么让智能体自己证明做对了：[验证闭环](/learn/engineering/verification/)。
