把 billing 模块从单体应用里拆出来
Billing 逻辑散落在 API routes、database models、后台任务、retry 逻辑、invoice status、webhook handler 和 Admin UI 里。任务听起来简单:"把 billing 拆成独立模块,保留旧行为"。但 agent 一开始跑,新问题就暴露出来了:
- 哪些文件真正属于 scope:API、jobs、DB、UI 还是 webhook?
- 哪些行为必须保留:retry、幂等性、invoice status、payment failure?
- 现有测试覆盖了多少?
- 这个阶段是否隐式依赖某个 migration 或后台任务?
- 新代码构建通过但边界条件偏差了,谁来发现?
对于小任务,/ck:plan --fast 或默认的 /ck:plan 通常够用。对于复杂但还不到大规模重构的任务,--hard 往往更合适。对于大任务,前期缺乏 context 会在后期产生代价:cook 要重新猜测 scope,phase 文件模糊,test 写在代码之后,回归问题只有重构完才暴露。
核心
--deep帮助 plan 看清受影响的范围:文件清单、test 缺口、各阶段之间的依赖。--tdd强制在重构前记录当前行为,再用这些测试作为回归验证门。
这两个选项不是"更强的模式"——不是打开就更好。它们各自解决一个具体风险:不清楚 scope 和无法保护旧行为。
Mode 与 Flag,两个不同的概念
在继续之前,需要先厘清一个容易混淆的地方。/ck:plan 接受两类参数:
| 类型 | 示例 | 作用 |
|---|---|---|
| Mode | --fast, --hard, --deep, --parallel, --two | 选择 pipeline。每条命令只用一个 mode。 |
| Composable flag | --tdd, --no-tasks | 附加选项,可以搭配任意 mode 使用。 |
关键点:--deep 是 mode,会改变 planning pipeline。--tdd 是 flag,保持已选的 mode 不变,只在 phase 文件和 cook 流程里加入 tests-first 的部分。
argument-hint: "[task] [--fast|--hard|--deep|--parallel|--two] [--tdd|--no-tasks]"
各 mode 之间的 | 表示选其一。--tdd 在独立的 block 里,所以可以组合。因此 --deep --tdd 合法,而 --deep --hard 不合法。
--deep,当 plan 需要按阶段绘制地图
--deep 解决什么问题
大型任务通常会同时影响多个代码区域。以 billing 模块为例,scope 可能横跨:
- API routes:创建 invoice、重试支付、退款、webhook callback
- Database:invoice 表、payment attempt 表、status 历史
- 后台任务:重试调度器、对账任务、webhook replay
- Admin UI:invoice 详情、手动重试、退款操作
- 共享契约:前端 API 响应、webhook payload、事件名称
这类任务不只需要一份步骤清单,它们需要一张地图:
| 问题 | billing 示例中的对应 |
|---|---|
| 哪些文件需要 create/modify/delete? | Route、service、job、migration、UI action |
| 哪些测试在覆盖旧行为? | Retry、幂等性、退款、webhook replay |
| 哪个阶段依赖哪个阶段? | Migration 必须先于 service,service 先于 UI |
| 哪些接口需要测试保护? | API 响应、webhook payload、job 输入 |
| 哪条依赖边风险最高? | Job 读取旧 status 时 service 已改了枚举值 |
--deep 适合在"plan 出错的代价大于细致 plan 的成本"时使用。
Major refactor, 5+ areas, architectural debt -> use --deep
如果只改少量文件,--fast 或默认 /ck:plan 通常够用。如果 scope 复杂但架构还清晰,--hard 往往更合适。--deep 值得用的场景是:plan 出错会导致数小时返工——重写 phase、重写测试、重新分配所有权。
--deep 的 pipeline
先建立宽泛 context,再逐步聚焦到具体 phase。
--no-tasks。/ck:cook {path}/plan.md。与 --hard(通常对整个 plan 只 scout 一轮)相比,--deep 会对每个 phase 额外做一轮检查。成本更高,但换来的是更具体的 phase 文件:涉及哪些文件、缺少哪些测试、哪条依赖有风险。
用 --deep 时 phase 文件需要额外包含什么
- 文件清单表:操作类型 create/modify/delete、预估大小、测试影响
- 测试场景矩阵:critical/high/medium 路径
- 依赖图:当前 phase 链接到哪些其他 phase
- 函数/接口检查清单:哪些函数或接口需要测试保护
这是核心差异所在。一个 phase 不能只说"重构 billing service",它必须说清楚涉及哪些文件、测试、边界条件,以及依赖哪个 phase。
--tdd,当你担心重构破坏正在运行的行为
--tdd 解决什么问题
cook 的默认流程很熟悉:读取 plan、逐步实现每个 task、每个文件后做 type check,之后再单独跑测试。对于 greenfield 项目这没问题。对于正在运行的代码的重构,这还不够安全。
在 billing 示例中,旧行为可能包括:
- Retry 最多跑 3 次,之后把 invoice 改为
failed - 相同
event_id的 webhook 只能 apply 一次 - invoice 已经
voided时不能发起退款 - Admin UI 在 service 拆分后仍需正确显示 status 历史
Happy path 可能仍然通过。Bug 往往藏在边界条件里。--tdd 在改代码之前建立回归验证门。这里的"TDD"不是为新功能写测试,而是在重构前先记录当前行为。
--tdd 最有价值的场景
| 场景 | 是否适合用? | billing 中的对应 |
|---|---|---|
| 全新的 greenfield 代码 | 通常不需要 | 没有 invoice/retry 行为可以记录 |
| 重构正在被用户使用的模块 | 适合 | 需要保留 retry、幂等性、status 转换 |
| 更换支付提供商但保留旧契约 | 适合 | API 响应或 webhook 行为容易出现偏差 |
| 改动 1-2 行且原因明确 | 通常不需要 | 比如修改 Admin UI 里的 label 拼写 |
| 临时原型 | 不需要 | 测试验证门的成本不值得 |
--tdd 在具有异步模式、有状态工作流、数据库事务或公共 API 契约的模块中价值最大。Billing 恰好包含所有这些:后台任务、status 转换、事务边界、webhook payload、admin 操作。
在 plan 阶段
| phase 文件中的部分 | 作用 |
|---|---|
| 重构前的测试 | 在改代码前先写回归覆盖,记录当前行为。 |
| 重构 | 描述要改的代码,受上面的测试保护。 |
| 补充测试 | 为本阶段新增行为补充测试(如果有)。 |
| 阶段末验证门 | 具体的 compile + test 命令,重构后必须全部通过。 |
使用 --tdd 的 phase 通常按以下顺序执行:
1. 写测试保护当前行为
2. 如果旧代码难以测试,先拆解一个小依赖点
3. 重构代码
4. 重新跑 compile + tests
让旧代码可测试这一步经常被跳过。有时候旧代码根本无法直接测试:一个长函数内部直接调用数据库,无法注入依赖,无法隔离输出。这时需要先拆出一个小依赖点——保持行为不变,但让代码可测——然后再做真正的重构。
cook 运行时
使用 --tdd 时 cook 的执行流程
简单理解:先写的测试是旧行为的基准。重构后如果基准失败,cook 应该暂停修复行为偏差,再继续往下走。
版本说明本文对照 Engineer Kit
engineer@v2.19.1-beta.10撰写,可通过ck -V确认。此说明仅用于在ck:plan或ck:cook工作流日后变更时避免混淆。
为什么 --deep--tdd 经常一起用
--deep 和 --tdd 解决的是两个不同层次的风险。
如果任务同时存在两种风险,两个选项都打开。
--deep 提供地图,--tdd 提供验证层。大型重构通常两者都需要。| 风险 | 需要什么 | 选项 |
|---|---|---|
| 不清楚 scope | 按 phase scout、文件清单、依赖图 | --deep |
| 重构导致旧行为偏移 | 重构前先写测试,cook 时有 Regression Gate | --tdd |
大型重构通常同时存在两种风险。例如:
把 billing 模块从单体应用里拆出来。
保留前端现有的 API 契约。
保留 retry、幂等性、invoice status 的旧行为。
涉及:database schema、后台任务、API routes、Admin UI。
这个 billing 任务需要 --deep,因为 scope 广、依赖多。它也需要 --tdd,因为 retry、幂等性、退款和 webhook 都是需要保留的旧行为。
只开 --deep,plan 可能很清晰,但重构仍然缺少回归验证门。只开 --tdd,测试流程可能正确,但 phase 文件仍然模糊:测什么、在哪里、哪个模块受影响、哪条依赖需要先跑。
速记任务范围宽就用
--deep。有旧行为需要保留就加--tdd。两个风险都有才同时打开。
什么时候先用 /ck:brainstorm 或 /ck:scout
一个常见错误:在思路还模糊时就用 --deep --tdd。plan mode 不解决 ambiguity。它只是把已经选定的方案转化为更具体的步骤。
例如,如果还没有决定:
- 真正拆成独立 service,还是只在单体内模块化?
- 引入新队列,还是保留现有后台任务?
- 保留现有 invoice status 枚举,还是改用更清晰的状态机?
- 重写 Admin UI 流程,还是逐步重构?
这种情况应该先用 /ck:brainstorm 确定方案,再去 plan。
另一层是 /ck:scout。把它看作收集 context 的步骤,而不是做决策的步骤。当你不确定代码在哪里、哪些模块相关、哪些测试覆盖了当前行为时,scout 很有价值。
brief 还模糊时,不要直接跳到 plan。
--deep --tdd。/ck:scout "billing retry flow, invoice status, background jobs, admin UI"
/ck:brainstorm "把 billing 模块拆出来但保留现有 API 契约"
/ck:plan --deep --tdd "按已确定的方案重构 billing 模块..."
当你已经熟悉相关代码区域和主要权衡时,可以跳过单独的 /ck:scout 步骤。/ck:plan --deep 的 pipeline 里本身就包含 scout 步骤。但当 brief 还太笼统时,提前 scout 能避免一个隐性错误:agent 的推理听起来合理,却是基于一份错误的项目地图。
| 场景 | 从哪里开始 |
|---|---|
| 不熟悉项目 context / 不知道代码在哪里 | /ck:scout -> /ck:brainstorm |
| 清楚做什么和怎么做 | 直接用 /ck:plan |
| 清楚做什么,不确定怎么做 | /ck:brainstorm -> /ck:plan |
| 还不确定要不要做 | /ck:brainstorm -> 决定 -> plan 或放弃 |
决策矩阵
/ck:scout/ck:brainstorm/ck:plan --deep --tdd| 场景 | 命令 |
|---|---|
| 不熟悉项目 context | 先用 /ck:scout,再 brainstorm/plan |
| 不确定方案 | 先用 /ck:brainstorm |
| 小修改 1-2 个文件 | /ck:plan --fast |
| 中等规模新功能 | /ck:plan 自动 |
| 复杂功能、陌生领域 | /ck:plan --hard |
| 3+ 个独立模块、可并行 | /ck:plan --parallel |
| 纠结于 2 个具体方案 | /ck:plan --two |
| 重构 5+ 个区域、有架构债 | /ck:plan --deep |
| 重构正在运行/dogfood 的代码,担心回归 | 任意 mode + --tdd |
| 大型重构且代码库有需要保留的行为 | /ck:plan --deep --tdd |
--deep --tdd 不是一个单一机制,而是两件不同的事放在一起。--deep 提供地图,--tdd 提供验证层。planning 成本增加了,但 cook 不用猜测 scope,回归验证门也更清晰。
内部机制
本节深入介绍 --deep 和 --tdd 在 plan 阶段、cook 阶段的具体影响,以及运行成本。
7.1 --deep 更贵:researcher + 按阶段 scout
| Mode | Researcher | Red Team | Validation | 按 phase Scout |
|---|---|---|---|---|
--fast | 0 | 0 | 0 | No |
--hard | 2 | Yes | Optional | No |
--deep | 2-3 | Yes | Yes | Yes |
--parallel | 2 | Yes | Optional | No |
--two | 2+ | 选定后 | 选定后 | No |
--deep 的成本主要来自:2-3 个 researcher 做高层架构分析、red-team review、validation 步骤,以及按每个 phase 重新过一遍。
简单说,每个 phase 在 plan 最终确定前都会被单独审视:这个 phase 涉及哪些文件、依赖哪个 phase、还缺哪些测试、有没有容易遗漏的边界条件。
需要避免的误解:这里说的"scout"指的是按 phase 读取和复查的过程,不是承诺每次运行都会启动一个独立 agent。重要的是 --deep 强制 plan 经过更多轮次的小检查,而不是只在总览层面看一次 scope。
7.2 --tdd 不会启动新的 agent
容易误解的地方:--tdd 不会改变已选的 mode。它是一个附加 flag:你仍然照常跑 --fast、--hard 或 --deep,只是加了 tests-first 的结构。
这个 flag 只改变 2 件事:
- plan 阶段的 phase 文件。 普通 phase 有 overview、要求、架构、相关文件、实现步骤、成功标准、风险评估。打开
--tdd后,phase 会额外包含:重构前的测试、重构后的测试和阶段末验证门。 - cook 阶段的执行顺序。 先写保护旧行为的测试,再重构,然后重跑 compile/test 验证门。
--tdd 的成本远低于 --deep。它主要是在 phase 文件里加结构,并改变 cook 的执行顺序。
7.3 Regression Gate 需要具体的 command
cook spec 要求 Regression Gate 是具体的 compile/test 命令。以 Go 项目为例:
Regression Gate: go test ./... && go vet ./...
如果 phase 文件写得模糊,比如"run tests to verify",验证步骤就会变弱,因为 cook 没有精确命令可以执行。--tdd flag 不能保证在每个 repo 里都猜对工具。如果项目使用特殊命令,应该在 task description 或 plan 文件里明确写出。
Project 用 go test ./...,前端用 npm test,E2E 用 Playwright。
task 中明确了 tooling,plan 才有依据为每个 phase 写出正确的 Regression Gate。
怎么跑和通用模板
以下模板基于 ck:plan 和 ck:cook 的 source skill:plan 接受 task + mode/flag,cook 接受 plan path,如果 plan 已开启 --tdd,cook 也需要保留该 flag。
通用模板
/ck:plan [mode] [--tdd] "[要做的事情].
范围:[涉及的 module/file/stack].
保持不变:[API 契约、旧行为、兼容性].
涉及:[database、job、route、UI、shared type].
Tooling:[具体的 compile/test 命令].
已知 bug / 不在 scope 内:[如有]."
注意:如果 plan 用了 --tdd,运行 cook 时也需要加 --tdd:
/ck:cook /absolute/path/to/plan.md --tdd
将模板套用到几种常见场景:
--deep 用于 planning/inventory 阶段
/ck:plan --deep "制作 inventory plan,为把 billing 模块拆成独立包做准备。
Scope:API routes、invoice service、payment jobs、Admin UI、database schema。
需要输出:文件清单、依赖图、phase 所有权、风险列表。
本轮不重构正在运行的行为。"
--hard --tdd 用于重构正在运行/dogfood 的代码
/ck:plan --hard --tdd "重构 invoiceStatusService:将 transition rules
拆到单独文件,保留 retry、failed、refunded、voided 的精确行为。"
--deep --tdd 用于有需要保留行为的大型重构
/ck:plan --deep --tdd "把 billing 模块从单体应用里拆出来。
保留前端 API 契约,保留 retry/幂等性/退款/webhook 行为。
涉及:database schema、payment jobs、API routes、Admin UI。
Project 用 go test ./...,前端用 npm test。"
好的 task description 通常包含:
- 范围:涉及哪些 file/module/stack
- 约束:不能破坏什么、需要保持哪些兼容性
- Tooling:test 命令、compile 命令,对
--tdd尤其重要 - 期望结果:简洁但具体
过于模糊的输入,比如 "refactor billing",只会产出模糊的 plan。scout 没有具体锚点,输出很容易流于泛泛。
最佳实践与常见陷阱
cook 之前应该做的
plan 之前清理 worktree
如果工作目录有其他任务的未提交改动,scout 容易把当前代码和进行中的代码混淆。大型 plan 之前:commit 或 stash。更好的办法是通过 /ck:worktree 创建独立的 worktree。
cook 之前先 review phase 文件
--deep --tdd plan 跑起来比较耗时,所以在没有读完 plan.md 和各 phase 文件之前,不要直接跑 cook。
Phase review checklist
用以下 5 个问题读完 plan.md 和各 phase 文件,然后再让 cook 跑:
在 task 里声明 test 命令
使用 --tdd 时,如果 repo 用了特殊工具,比如 bun 替代 npm、mise 替代 asdf、task 替代 make,请直接声明。
Project 用 go test ./...,前端用 npm test,E2E 用 Playwright。
不声明的话 agent 会猜。猜错命令会让 Regression Gate 变弱。
plan 和 cook 之间用 /clear
大型任务的 plan context 通常很重:research 输出、scout 数据、red-team 反馈。推荐流程:plan 完成 -> /clear -> 重新打开 -> /ck:cook {absolute-path}/plan.md --tdd。cook 会从 plan 文件重新读取,不需要旧的 planning context。
需要避免的
/ck:cook 漏写 --tdd
plan 有 --tdd,cook 没有 -> cook 仍然能读到"重构前测试"的部分,但不会强制执行"先写测试再重构"的顺序。测试可能仍然被写,只是写在代码之后。应该使用 plan 输出里建议的 cook 命令。
小任务用 --deep
涉及文件不超过约 5 个时通常不需要。用 --hard 或 --fast 更简洁。
greenfield 代码用 --tdd
新代码没有现有行为可以记录。"重构前测试"部分几乎为空,agent 容易写出形式化的测试而不是真正的 TDD。Greenfield 用 --hard,让测试按正常流程写。
过度信任 phase 文件
--deep scout 更仔细,但仍然可能遗漏隐式依赖。cook 之前应该自问:后续 phase 是否隐式依赖前面 phase 还没创建的东西?
--tdd 把已有 bug 记录成"行为"
旧 billing 代码有潜在 bug,比如重复 webhook 偶尔会创建 2 条 payment attempt。如果重构前的测试把 bug 也记录成"当前行为",重构时意外修复了 bug 就会导致验证门失败,agent 可能反而把代码改回有 bug 的状态。解决方案:在 task 里声明已知 bug,或者拆成两个 plan:plan 1 修 bug,plan 2 重构。
强行把 --deep 和 --parallel 组合
--deep 和 --parallel 都是 mode,不能互相组合。如果需要多个 agent 并行跑,用 --parallel --tdd,并把 ownership 和 test scope 划分清楚。如果不确定 ownership 和 test isolation,放弃 --parallel,保留 --deep --tdd。
总结
--deep --tdd 不适合所有任务。
它适合同时具备以下两个特征的任务:
- scope 足够大,普通 plan 容易缺少地图
- 现有代码有需要在重构后保留的行为
--deep 让 planning 变慢,但换来更具体的 phase 文件:文件清单、test 缺口、依赖图、函数/接口检查清单。
--tdd 让 cook 变慢,但换来重构有回归验证门:先写测试、再重构、再验证。
如果你是 PM/founder/产品,只需要知道这两个选项的存在。当团队准备重构大模块(比如 billing)时,问一下 plan 里有没有按阶段 scout 和回归验证门。
如果你是开发者,在下一个大任务里试试。第一次可能会觉得多花了时间。但只要有一次"重构前测试"发现了隐性回归,这点成本就很容易接受。