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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
## Task 3: 部署脚本支持 dry-run 模式

**Description:** 给 deploy.sh 加一个 --dry-run 参数,打印将要执行的
命令但不真跑,方便 CI 调试。

**Acceptance criteria:**
- [ ] 部署脚本接受 --dry-run 参数
- [ ] dry-run 模式下打印 git pull / hexo clean / hexo deploy 三条命令
- [ ] dry-run 模式下不实际执行任何写操作
- [ ] help 文本里出现 --dry-run 说明

**Verification:**
- [ ] bash -n deploy.sh 语法检查通过
- [ ] ./deploy.sh --dry-run 输出包含三条命令字串
- [ ] git status 干净(dry-run 没改任何文件)
- [ ] ./deploy.sh 不带参数时行为与改造前一致

**Dependencies:** Task 1(参数解析工具函数)

**Files likely touched:**
- deploy.sh

**Estimated scope:** S (1-2 files)

这种结构的好处是把”模糊的成功”变成”可勾选的清单”。三个验收项以内的任务最理想;超过三项往往意味着这个任务该再拆。

任务依赖图与并行化

拆完任务后,第二件事是画依赖图。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
2
3
4
- Task 1: 重构 deploy.sh 的参数解析(支持 --help --dry-run --verbose)
- Task 2: 重构 deploy.sh 的 git 操作(加 stash、失败回滚)
- Task 3: 重构 deploy.sh 的 hexo 操作(加超时、重试)
- Task 4: 写 deploy.sh 的单元测试

每一步都是”完成了”——参数解析函数都写好了、stash 逻辑也都实现了、超时也加了。但跑一遍 ./deploy.sh,用户看到的还是去年的行为。所有改动都没交付价值,直到第四步加测试时才第一次”连起来”。问题是这时候已经改了 200 行,没人敢保证行为完全一致。

垂直切法(正确示范):

1
2
3
4
- Slice 1: 用户能看到 --help 输出(加 help 函数 + 入口分支)
- Slice 2: 用户能用 --dry-run 看到将要执行的命令(加 dry-run 标志 + echo 分支)
- Slice 3: 用户在 git pull 失败时能看到清晰的错误信息(加错误处理 + 非零退出)
- Slice 4: 用户在 hexo deploy 失败时能自动回滚到上次成功的版本(加 stash + 失败分支)

每一片都满足:

维度 切片 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
2
3
4
5
ENABLE_ROLLBACK="${DEPLOY_ROLLBACK:-false}"
if [ "$ENABLE_ROLLBACK" = "true" ]; then
# 新写的回滚逻辑
...
fi

这样可以把”还没写完的回滚”和”已经写完的 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
2
3
4
5
6
7
# RED: 写一个测试(用 bash 的 if 模拟)
if ./deploy.sh --help 2>&1 | grep -q "Usage:"; then
echo "PASS: help works"
else
echo "FAIL: help missing"
exit 1
fi
1
2
3
4
5
# GREEN: 加最小实现
if [ "$1" = "--help" ]; then
echo "Usage: deploy.sh [--dry-run] [--help]"
exit 0
fi

REFACTOR 步:测试绿了之后再清理。重复跑测试保证重构没破坏行为。

Prove-It Pattern:bug 修复时先写复现

TDD 在 bug 修复场景下有一个变体,叫 Prove-It Pattern。SKILL.md 把它单列出来,因为它太容易被跳过:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Bug report arrives


Write a test that demonstrates the bug


Test FAILS (confirming the bug exists)


Implement the fix


Test PASSES (proving the fix works)


Run full test suite (no regressions)

这条模式的价值在于:没复现的 bug 修复,就是没修复。很多时候你以为修好了,其实只是”在你这次复现的输入下修好了”。把复现用例固化进测试集,下次改动它会替你把关。

我们项目里用过的真实例子:deploy.sh 之前有过一个 bug——git pull 在 rebase 冲突时会以 0 退出码退出,脚本以为成功了继续往下走 hexo deploy,结果线上的版本错乱。Prove-It Pattern 的做法:

1
2
3
4
5
6
7
8
9
10
11
# RED: 写一个会失败的测试
# 制造一个会 rebase 冲突的 git 状态
git stash
# 改一个文件,不 commit
git pull --rebase # 预期:返回非零
if [ $? -ne 0 ]; then
echo "PASS: rebase conflict detected"
else
echo "FAIL: rebase conflict swallowed"
exit 1
fi

修好之后(用 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 脚本,单元测试就是用 bashgrep/exit 1 模拟的小脚本,集成测试是”在临时目录里 init 一个 hexo 博客然后跑 deploy.sh”,E2E 是”真的 push 到服务器并访问线上 URL 验证”。

Chrome DevTools MCP:浏览器侧的眼睛

SKILL.md 把 DevTools MCP 列为 TDD 的补充手段,特别适合前端改动。dev 模式下的工作流:

1
2
3
4
5
1. REPRODUCE: 导航到页面,触发 bug,截图
2. INSPECT: 控制台报错?DOM 结构?计算样式?网络响应?
3. DIAGNOSE: 对比实际 vs 预期——是 HTML、CSS、JS 还是数据?
4. FIX: 在源码里实现修复
5. VERIFY: 重新加载、截图、确认控制台干净、跑测试

这套流程和我们前面讲的”每一切片都验证”是同一套哲学——只不过从 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 怎么处理”测试已经绿了,但用户还是报错”这种调试分诊,以及五维度评审是怎么给代码把最后一道关的。


参考资料