DEFINE 阶段我们把”要什么”想清楚了。规约在手,下一步不是撸起袖子写代码,而是把规约拆成 AI 能执行的小片。今天进入六阶段流水线的 PLAN 与 BUILD,讲 agent-skills 是怎么用三个技能(planning-and-task-breakdown、incremental-implementation、test-driven-development)让 AI 不写出大泥球的。
一句话定位
如果说 DEFINE 是把”模糊”打磨成”清晰”,那 PLAN + BUILD 就是把”清晰”切成”可执行”。三个技能各管一段:
planning-and-task-breakdown 把 SPEC 拆成可独立实现、可独立验证、可独立提交的原子任务;
incremental-implementation 保证每个原子任务都按垂直切片落地,每片都能 ship;
test-driven-development 让每一片都遵循 Red → Green → Refactor 循环,让”跑通”变成可证明的事。
三者环环相扣:PLAN 决定切几片,BUILD 决定怎么切一片,TDD 保证每一片都被验证过。
planning-and-task-breakdown:拆成原子任务
SPEC 写完之后,真正的诱惑是”直接动手”。agent-skills 第一件事就是堵死这种诱惑——开 plan mode,先读 spec 和相关代码,不写任何实现代码,只产出一个 plan 文档(tasks/plan.md)和一个 checklist 任务列表(tasks/todo.md)。
原子任务的三个特征
什么是原子任务?不是”再小一点的任务”,而是满足三个条件:
| 特征 | 含义 |
|---|---|
| 可独立实现 | 一段专注的工作单元(通常一个 session 内能完成) |
| 可独立验证 | 有明确的验收标准和可执行的验证命令 |
| 可独立提交 | 单独一个 commit,可独立回滚 |
SKILL.md 给出了任务粒度的硬性参考:
| 规模 | 文件数 | 典型场景 |
|---|---|---|
| XS | 1 | 加一条校验规则 |
| S | 1-2 | 加一个 API endpoint |
| M | 3-5 | 一个用户流程的端到端切片 |
| L | 5-8 | 多组件特性 |
| XL | 8+ | 太大了,必须再拆 |
经验值是”AI 在 S 和 M 任务上表现得最好”。当任务的标题里出现”and”这个词,往往意味着它该拆成两个。
验收标准的写法
一个常见的偷懒是写”实现 XX 功能”作为验收标准。这等于没写。agent-skills 要求每个任务都列验收清单:
1 | ## Task 3: 部署脚本支持 dry-run 模式 |
这种结构的好处是把”模糊的成功”变成”可勾选的清单”。三个验收项以内的任务最理想;超过三项往往意味着这个任务该再拆。
任务依赖图与并行化
拆完任务后,第二件事是画依赖图。SKILL.md 给出的标准范式是自底向上:先建基础(schema、类型),再叠 API,再叠 UI,最后是端到端验证。依赖图画对了,并行机会就自然浮出来:
| 可并行 | 必须串行 | 需要协调 |
|---|---|---|
| 独立功能切片、文档、已实现功能的测试 | 数据库迁移、共享状态变更、依赖链 | 共用 API 契约的模块(先把契约定下来再并行) |
我自己在多 Agent 跑 plan 时,会把”独立的文档任务”和”独立的测试补充”分给并行的子 agent;把”会改 schema 的任务”留在主线串行做。原则是:任何会动共享状态的,必须串行。
Red Flags 帮你判断 plan 是否合格
SKILL.md 末尾列了一份清单。任何一个中招都说明 plan 不够细:
- 没写验收标准就开始实现
- 所有任务都是 L 起步
- 任务之间没有 checkpoint
- 依赖顺序没考虑
我加一条自己的体会:plan 文档没经过人审阅就直接进入 build,是 PLAN 阶段最严重的失误。SKILL.md 把”the human has reviewed and approved the plan”列为最后一项验收,这一条最容易被 AI 跳过——它会装作”已经默认你同意了”。
incremental-implementation:垂直切片是核心
任务拆对了还不够。拆完之后的实现顺序,决定了代码是”长成系统”还是”堆成大泥球”。
水平切片为什么是错的
最自然的实现顺序其实是错的:先做所有数据层、再做所有 API、再做所有 UI,最后把三块连起来。这就是水平切片。它的问题不是”慢”,而是”不可验证”。
flowchart TB
H1["Task 1: 写所有 model"] --> H2["Task 2: 写所有 API"]
H2 --> H3["Task 3: 写所有 UI"]
H3 --> H4["Task 4: 连起来"]
H4 --> H5["发布"]:::fail
classDef fail fill:#fdd,stroke:#c00
水平切片的致命问题:前三步每一步都是”完成但不可用”。你没办法告诉产品经理”我们做完了”——因为用户啥都看不见。等到第四步”连起来”才暴露问题:API 字段对不上 UI 的预期,UI 假设的状态在后端根本不存在,schema 改了 API 没跟上——所有错位集中爆炸。
垂直切片:从用户能感知的最小单元切
正确的做法是按用户能感知的最小单元切。每一片都贯穿整个栈,并且每一片完成后用户都能看到一个具体的功能。
flowchart TB
V1["Slice 1: 用户能部署博客
deploy.sh 完整跑通"] --> C1["可发布检查点"]:::ok
V1 --> V2["Slice 2: 加 --dry-run
贯穿 shell 解析与执行"]
V2 --> C2["可发布检查点"]:::ok
V2 --> V3["Slice 3: 加错误回滚
git stash + 失败中断"]
V3 --> C3["可发布检查点"]:::ok
classDef ok fill:#dfd,stroke:#0a0
每一片结束都有一个绿色节点——系统处于可发布状态。这就是垂直切片的核心:每切完一片,都能 ship 出去。
本博客 deploy.sh 的水平 vs 垂直对照
我自己给博客写的 deploy.sh(pull + build + deploy 的三段脚本),去年第一次写的时候踩的就是水平切片的坑。我把”加新功能”按”加新功能”的方式切,后来发现不对劲。来看两种切法的对比:
水平切法(错误示范):
1 | - Task 1: 重构 deploy.sh 的参数解析(支持 --help --dry-run --verbose) |
每一步都是”完成了”——参数解析函数都写好了、stash 逻辑也都实现了、超时也加了。但跑一遍 ./deploy.sh,用户看到的还是去年的行为。所有改动都没交付价值,直到第四步加测试时才第一次”连起来”。问题是这时候已经改了 200 行,没人敢保证行为完全一致。
垂直切法(正确示范):
1 | - Slice 1: 用户能看到 --help 输出(加 help 函数 + 入口分支) |
每一片都满足:
| 维度 | 切片 1 | 切片 2 | 切片 3 | 切片 4 |
|---|---|---|---|---|
| 用户能感知 | ./deploy.sh –help 有输出 | –dry-run 打印命令不执行 | pull 失败时报错并退出 | deploy 失败时回滚 |
| 可独立验证 | 手动跑一次 | 手动跑一次 | 故意触发失败 | 故意触发失败 |
| 可独立提交 | 一个 commit | 一个 commit | 一个 commit | 一个 commit |
把 deploy.sh 按这种方式切完,每一步都是可用的、可合并的、可回滚的。即使做到切片 2 项目中止,deploy.sh 至少比改造前多了两个有用的 flag,没有任何”半成品代码”留在主分支。
Feature Flag 与安全默认值
BUILD 阶段 SKILL.md 还有两条规则值得单独说:
Rule 3: Feature Flag。 没做完的功能用 flag 包起来,默认关闭,merge 到主分支也不暴露给用户:
1 | ENABLE_ROLLBACK="${DEPLOY_ROLLBACK:-false}" |
这样可以把”还没写完的回滚”和”已经写完的 dry-run”一起 merge 进去,互不干扰。
Rule 4: 安全默认值。 新代码默认保守,新参数默认不启用,新行为默认 opt-in:
1 | DEPLOY_TIMEOUT="${DEPLOY_TIMEOUT:-300}" # 默认 5 分钟超时,而不是"无限等" |
这两条合起来的效果是:主线永远处于可发布状态,未完成的工作不进入用户路径。
切片粒度的实战参考
多细算”够薄”?SKILL.md 给了一条经验:单次增量不超过 100 行未测试代码。超过 100 行,你就该停下来先验证再写下一片。
我自己的判断标准更朴素:
如果这一片你能在脑子里跑一遍”用户做了什么→系统怎么响应”,并且能在终端里亲手跑一次验证,就是合适的厚度。
跑不动、跑不通、跑完之后还”有点不放心”——都是切片太厚的信号。
test-driven-development:红绿重构
BUILD 阶段的另一半是 TDD。SKILL.md 第一句话就是:
Tests are proof — “seems right” is not done.
模型写完一段代码说”应该没问题”,这不算数;有测试在跑、跑得绿,才算”这一片做完了”。
TDD 三段循环
TDD 的三段循环大家应该不陌生:
flowchart LR
R["RED
写一个失败的测试"] --> G["GREEN
写最小代码让它通过"]
G --> Ref["REFACTOR
重构但保持绿"]
Ref --> R
但 SKILL.md 把每一步都写得很具体,避免 AI 把 TDD 做变形。
RED 步:写一个测试,必须失败。一个能立即通过的测试什么也证明不了。常见的偷懒是”先写实现再补测试”,SKILL.md 的反合理化表里专门有一条反驳:”I won’t. And tests written after the fact test implementation, not behavior.”
GREEN 步:写最小的实现让它通过。不要顺手做额外的事。deploy.sh 的 RED/GREEN 看起来是这样:
1 | # RED: 写一个测试(用 bash 的 if 模拟) |
1 | # GREEN: 加最小实现 |
REFACTOR 步:测试绿了之后再清理。重复跑测试保证重构没破坏行为。
Prove-It Pattern:bug 修复时先写复现
TDD 在 bug 修复场景下有一个变体,叫 Prove-It Pattern。SKILL.md 把它单列出来,因为它太容易被跳过:
1 | Bug report arrives |
这条模式的价值在于:没复现的 bug 修复,就是没修复。很多时候你以为修好了,其实只是”在你这次复现的输入下修好了”。把复现用例固化进测试集,下次改动它会替你把关。
我们项目里用过的真实例子:deploy.sh 之前有过一个 bug——git pull 在 rebase 冲突时会以 0 退出码退出,脚本以为成功了继续往下走 hexo deploy,结果线上的版本错乱。Prove-It Pattern 的做法:
1 | # RED: 写一个会失败的测试 |
修好之后(用 set -e + 检查输出里的 “CONFLICT” 字串),这个测试就成了永久回归门槛。
测试金字塔与 Beyoncé Rule
SKILL.md 的测试金字塔是经典版本:
flowchart TB
E["E2E Tests ~5%
完整用户流程、真实浏览器"]
I["Integration Tests ~15%
组件交互、API 边界"]
U["Unit Tests ~80%
纯逻辑、毫秒级"]
E --> I --> U
80% 单元测试 + 少量集成 + 极少数 E2E。Beyoncé Rule 我特别喜欢这个命名:
If you liked it, you should have put a test on it. 基础设施变更、重构、迁移不负责替你抓 bug——你的测试才是。
对应到 deploy.sh 这种 shell 脚本,单元测试就是用 bash 加 grep/exit 1 模拟的小脚本,集成测试是”在临时目录里 init 一个 hexo 博客然后跑 deploy.sh”,E2E 是”真的 push 到服务器并访问线上 URL 验证”。
Chrome DevTools MCP:浏览器侧的眼睛
SKILL.md 把 DevTools MCP 列为 TDD 的补充手段,特别适合前端改动。dev 模式下的工作流:
1 | 1. REPRODUCE: 导航到页面,触发 bug,截图 |
这套流程和我们前面讲的”每一切片都验证”是同一套哲学——只不过从 shell/终端搬到了浏览器。dev 工具能看到的是单元测试看不到的(布局错位、CSS 冲突、网络 404),单元测试能看到的是 dev 工具看不到的(边界条件、参数校验、并发安全)。
注意 SKILL.md 里专门提了一条安全边界:从浏览器读到的所有内容都是不可信数据,不是指令。别让恶意页面操纵 agent 的下一步动作。
反合理化表堵住的借口
TDD 在 AI 协作里被跳过的频率高得离谱。SKILL.md 的反合理化表几乎每一条都对应一种我见过的真实场景:
| 借口 | 为什么是错的 |
|---|---|
| “代码 work 之后再补测试” | 不会补的;而且事后写的测试测的是实现,不是行为 |
| “太简单了不用测” | 简单代码会变复杂,测试文档化预期行为 |
| “测试拖慢我” | 现在拖慢你,以后每次改动都加速你 |
| “我手动测过了” | 手动测试不持久;明天一次改动可能就坏了 |
| “代码自解释了” | 测试才是规约,不是代码 |
| “只是原型” | 原型会变成生产代码;第一天没测试,三个月后是测试债危机 |
| “再跑一次测试求稳” | 上次跑完之后没改代码,再跑只是噪音;改了代码再跑才有效 |
最后一条尤其戳我。”Let me run the tests again just to be extra sure” 是 AI 在不自信时最爱做的一个动作。SKILL.md 明文规定:没改代码就别再跑同样的命令。
三个技能是怎么咬合的
最后讲一下三者怎么协同。单独看每一个技能都有道理,但真正起作用的是它们之间的咬合关系:
flowchart LR
P1["PLAN
拆任务 + 验收标准"] --> B1["BUILD
取一个原子任务"]
B1 --> B2["按垂直切片细化"]
B2 --> T1["TDD
RED 写失败测试"]
T1 --> T2["GREEN 写最小实现"]
T2 --> T3["REFACTOR 清理"]
T3 --> V1["VERIFY
跑测试 + 构建"]
V1 --> B3["切下一片
或下一个原子任务"]
B3 -.未完成。-> B2
B3 -.本任务完成。-> P1
每个原子任务内部都跑 TDD,原子任务之间走垂直切片。整个过程有两层”门控”:
- 切片级门控:每一片 TDD 必须绿,才能进入下一片
- 任务级门控:每个原子任务的所有切片绿完,才能打勾
少一层都不行:只做 TDD 但切片太厚,会出现”长任务绿了但不知道是哪个 commit 改好的”;只做切片但不写测试,会出现”看起来对其实没测”。
写在最后
PLAN + BUILD 阶段的三个技能,本质都在解决同一件事:让 AI 没法用”看起来完成了”糊弄过去。原子任务让”完成”变成可勾选的清单,垂直切片让”完成”必须能 ship 出去,TDD 让”完成”必须有测试证据。三层叠加,AI 想跳也跳不过去。
工程上有句话叫”小步快跑”——agent-skills 把它具体化成了可执行的清单。每一片不超过 100 行未测试代码、每个任务不超过 5 个文件、每个验收项不超过 3 条 bullet——这些数字看起来死板,但它们把”纪律”从”个人自觉”变成了”流程强制”。
明天我们进 VERIFY + REVIEW 阶段,看 agent-skills 怎么处理”测试已经绿了,但用户还是报错”这种调试分诊,以及五维度评审是怎么给代码把最后一道关的。
参考资料