SKILL.md 14.4 KB

SKILL: code-planning(v1)

训练对象:p2-verify:code-planner 评分表:训练材料 benchmark_v1.md(53 判据,及格线 90, 位于本体 train/digest/ 下,供人工审阅训练过程,不是本 SKILL 运行时依赖) 用法:收到一句话需求 → 严格按下面的模板产出 plan-<taskId>.md,逐节自查表全部打✅才算完。

0. 心法(写之前先想清楚)

规划的价值 = 你替 coder/tester/validator 消灭掉的不确定性总量。三个反问:

  1. 这份规划下游读完,还需要自己现编任何东西吗(签名?边界行为?验收命令?)?现编=你没做完。
  2. 哪些是我能自己拍板的(技术选型/边界细节),哪些是真正需要上级决策的(影响范围/预算/对外接口)?前者当场裁决写理由,后者才上报。
  3. 我是不是在复述需求换个格式抄一遍?如果把"目标"和"单元拆分"拼起来约等于原始需求,说明没有拆解增量。

1. 输出模板(六节 + 澄清节,逐字段照抄结构,不要发挥)

# plan-<taskId> · <一句话目标>

## 0. 需求澄清
- 歧义:<问题>(裁决:<选了什么> ,理由:<为什么>)
- 假设:<隐含前提>(裁决:<按什么处理>)
- (若真有必须上级拍板的)待定:<问题>。若<条件A>则<方案A>,若<条件B>则<方案B>,影响面:<受影响的单元/范围>
非范围:<在这里也可以先列 1-2 条最核心的排除项,正式表格见第2节>

## 1. 目标
<一句话,含反引号包裹的可执行命令>(可演示:<跑完看什么>)

## 2. 范围 / 非范围
**做:**
- <能力点1>
- <能力点2>

**不做:**
- <排除项1>
- <排除项2>
- <排除项3>(至少 3 条,非范围写不出来 = 边界没想清楚)

## 3. 单元拆分表
| 单元 ID | 做什么 | 依赖 | 预估行数 | 预估用例数 | 拆分理由 | 一句话完成判据 |
|---|---|---|---|---|---|---|
| U1 | <能力> | — | ~80行 | ~4条 | 理由:<变化边界/独立可测> | <判据> |

## 4. 接口契约(定死,coder 不得现编)
```js
// U1 <文件路径>
<函数名>(<参数名>: <类型>) -> <返回类型>
// 边界:<非正常输入> → <约定行为>

(每个对外有表面的单元都要有一个代码块;只写签名+边界,不写实现细节/私有变量/缓存)

5. 每单元验收标准

# U1
<可执行命令1>
<可执行命令2(负例/边界)>
# U2
...

全局基线(仅此一处,不在每个 unit 下重复):npx vitest run 全绿

6. 风险与待定

风险 命中单元 触发信号 影响面 预案
U2
规划本身可能切错 全局 同一 unit 打回 ≥2 次 全部下游 unit 返工 主动读回流记录,重切并重新发单,不等 coder 扛

## 2. 逐维度写作规则(对照 benchmark 判据编号)

### D1 需求澄清
- **C1.1** 澄清条目数 `n ≥ max(3, ceil(需求原文字数/100)*3)`。需求越长,澄清条目必须越多,别偷懒只写 3 条应付短需求之外的长需求。
- **C1.2** 每条澄清都要带裁决标记:`(裁决:...)`或`决策:`或`拍板`或`选 X`,裁决条数占比 ≥70%。
- **C1.3** 真正上报的条目必须写`若...则...影响面:`三件套;能自己拍板的不要伪装成"待定"去凑数。
- **C1.4** "目标"一节只写一句话(≤3 句),必须含用反引号包裹、且含 `node/npx/bash/sh/test/curl/git` 之一的可执行命令。
- **C1.5** 非范围 ≥3 条(第2节"不做"列表,不是散文)。
- **C1.6** 全文(尤其澄清/目标/范围段)禁止出现:合理、正确、健壮、高效、良好、尽量、大致、应该能、没问题、完善。出现 1 次直接 0 分,通篇自检删干净。
- **C1.7** 全文至少各命中一次:①运行时版本(如"Node ≥18")②依赖政策("零依赖"或"允许 npm i xxx")③输出编码/退出码约定("UTF-8"/"退出码")。放进目标或风险节都行,别漏。

### D2 任务拆解
- **C2.1** unit 数 3–8。需求 ≤40 字且只有 1 个能力名词时可以放宽到 2,其余情况严守下限 3(哪怕需求看着很小,也至少拆成"核心逻辑 / CLI接线 / 测试"三块)。
- **C2.2** 每个 unit 验收命令数 ≤5,超过就是塞了两件事,拆开。
- **C2.3/C2.4** 表格必须填"预估行数"和"预估用例数"两列,行数 ≤400、用例数 ≤15,缺列直接 0 分(不是可选项)。
- **C2.5** 需求里出现的每一个能力名词(工具类型、格式、模式名)都必须在某个 unit 的"做什么"里原词或同义出现,逐字核对需求原句,别漏掉任何一个名词短语。
- **C2.6** 每个 unit 的"做什么"必须能在"目标/范围"里找到出处,不能自己加戏(比如需求没提日志,就不要拆一个"日志 unit")。
- **C2.7** 每个 unit 的验收命令只能引用"本 unit 产出 + 已声明依赖的产出 + 仓库既有文件",不能引用还没到它这一波才产出的东西;每个 unit 至少有 1 条自己的验收命令(不能挂空)。
- **C2.8** 每个 unit 都要写"拆分理由"列:为什么这是一个独立单元(变化边界不同/可独立演化/可独立测试),不是"因为要分开写"这种空话。
- **反模式红线**:禁止按"读数据/算逻辑/写结果"这种流程步骤横切(`horizontal-split`)——那是同一个变化边界,coder 改一处三个 unit 全要跟着改。正确切法按"谁会因为需求变化而单独改"来分(解析规则变 ≠ 输出格式变 ≠ CLI 参数变,这才是三个独立单元)。

### D3 依赖排序
把拆分表转成 `{id, depends_on}` 数组后必须能通过下面这段 Kahn 算法(不许成环):
```js
function waves(units) {
  const groups = [], remaining = new Set(units.map(u => u.id));
  const deps = new Map(units.map(u => [u.id, (u.depends_on || []).slice()]));
  while (remaining.size) {
    const w = [...remaining].filter(id => deps.get(id).every(d => !remaining.has(d)));
    if (!w.length) throw new Error('cycle');
    w.forEach(id => remaining.delete(id));
    groups.push(w);
  }
  return groups;
}
  • C3.2/C3.3 首波(无依赖的 unit)不能有 0 个也不能全员并列成一片,波数 C3.4 落在 2–4 之间。
  • C3.5 至少有一波并行度 ≥2(unit 数 >2 时禁止写成一条直链 U1→U2→U3→U4,那是 full-serial,把没有互相依赖的 unit 摆在同一波)。
  • C3.6 依赖关系只写进"依赖"列,禁止散文里出现"当然要等/显然要在...之后"这种隐式依赖(implicit-dep)——想到依赖就立刻回填表格,不要留在正文里。
  • C3.7 依赖列只能写 unit 编号,不能写"依赖 U1 的内部解析函数"这种实现细节引用。

D4 接口契约

  • C4.1 第4节必须是代码块(至少 1 个 ```js fence),一个字都不许写成散文,散文契约直接触发 Gate G3(总分封顶 60)。每个对外有表面的 unit 都要有对应代码块。
  • C4.2 每条签名必须是 函数名(参数名: 类型) -> 返回类型 这种完整形态,CLI 类型契约要写 argv/参数名+默认值。
  • C4.3 每个契约至少 1 条"非正常但合法输入"的行为约定(空输入/缺字段/不存在/超时/重名 → 具体返回或 exit 码),只写正常路径叫 happy-path-only,会被打 0。
  • C4.4 契约里禁止出现实现细节词:内部、私有、缓存、临时、Map
  • C4.5 命名合规:函数名 camelCase,文件名 kebab-case.js,类名 PascalCase。
  • C4.6 全文错误表达方式只能选一种并贯彻到底(CLI 场景推荐:非 0 退出码 + stderr 单行原因),不要 throw / null / exit 混用。
  • C4.7 验收命令、拆分表里出现的每一个函数名/标识符,必须能在第4节契约里逐一对上;对不上 = coder 要现编,直接扣分。写完整份规划后回头拿验收命令里的函数名逐个核对契约。

D5 验收标准

  • C5.1 每条验收都以 node/npx/bash/sh/test/grep/cat/curl/diff/git 开头的可执行命令;一条都没有 = Gate G2(总分封顶 60)。
  • C5.2 每条命令要能机器判定:退出码类(&&/||/$?/exit)、文件类(test -f/grep -q/wc -l)、断言类(console.assert/vitest/assert()三选一,别写"人眼看输出对不对"这种。
  • C5.3 第5节内禁止出现:合理、健壮、高效、良好、尽量、大致、应该能、没问题、跑起来没问题、完善、正常工作。
  • C5.4 每个 unit 验收条数 1–5(0 条不行,>5 条也不行)。
  • C5.5 每个 unit 至少 1 条负例/边界验收(空输入、无 XX、不存在、缺参数、重名、非法输入 → 明确的失败判据)。
  • C5.6 验收命令里引用的每个 fixture 路径,要么本 unit/上游 unit 明确产出,要么是仓库既有文件——不要凭空引用一个没人产出的 fixtures/xxx。
  • C5.7 测试命令统一用 npx vitest run,不要混用 jest/mocha/ava/tap。
  • C5.8 全局基线(npx vitest run 全绿)只写一次,放在第5节末尾,不要每个 unit 底下重复抄一遍。

D6 风险预案

  • C6.1 风险条数 2–6 条。
  • C6.2 每条风险五要素齐全:描述 / 命中单元 / 触发信号 / 影响面 / 预案(模板已给 5 列表格,逐格填,不许留空)。
  • C6.3 预案必须是具体替代方案或范围裁剪("降级为 XX,移入非范围"),禁止"加强/注意/优化一下/多测试/多沟通/尽量/小心"这类空话。
  • C6.4 触发信号必须带具体阈值("打回≥2次"/"失败3次"/"exit 非0"/"超过5秒"),不能写"经常失败"这种模糊描述。
  • C6.5 必须有 1 条"规划本身可能切错"的自我风险(两条 fail 边都不指向 planner,只有自己主动巡检才会发现),模板已固定写死这条,别删。
  • C6.7 至少 1 条风险预案是"若 X 不成立就把 Y 移出范围"的显式降级路径。

D7 协议合规(仅当任务带 protocol_trace / 走真实四段式流程时参评)

  • C7.1 严格按 plan → execute → verify 顺序各跑一次 task transition,一次都不能漏、不能顺序错。
  • C7.4 curl 一律打 localhost:3010,不是 3000。
  • C7.5 复杂 JSON payload 一律先写 /tmp/*.json 文件,再 curl -d @/tmp/x.json,不要 inline JSON 字符串。
  • C7.7 规划者产出目录下不能出现任何 .js/.ts/.py 实现文件(契约代码块不算),越界写代码直接触发 Gate G4(总分封顶 60)。
  • C7.8 VERIFY 阶段的自查表要逐条列出结论符号(✅/❌)+ 理由,不能一句"已自查"带过,至少 5 条。
  • C7.9 report 时带全 taskId/from/to/status/summary,有 OKR 的任务还要带 goalId/nodeId。

2.5 高频失分点(实测踩过的坑,逐条对照)

  • C2.5:capability_nouns 必须在某单元「做什么」里逐字出现,不能只写同义改写(比如需求提"单元测试","做什么"里也要出现"单元测试"这四个字,不能只写"vitest 用例")。写完拆分表后,把需求原句里的每个名词短语抄一遍去核对。
  • C3.2:如果首波(无依赖的 unit)天然有 ≥2 个,不算失分——但要在单元拆分表正上方显式写一行「首波并行:U1、U2」,否则会被判成没想清楚入口。
  • C4.1:纯测试类 unit(如"vitest 用例覆盖 U1-U3")不需要单独契约代码块,但要在最后一个契约代码块的注释里点一句"U4 无独立对外接口,复用 U1/U2/U3 契约",让它的编号在第4节文本里出现,不要新开一个空块(新开块会拉低 C4.3 边界条款覆盖率)。
  • C4.6:CLI 场景下"非 0 退出码 + stderr 单行原因"算同一种错误形态,可以放心两者都写,不算混用;真正的混用是这个组合之外又出现 throw 或 return null。
  • C1.7 / C6.4:环境约束(Node 版本/依赖政策/编码退出码)放进第1节"目标"下面单独一行说清;风险表每一条"触发信号"都要带可数阈值("打回 ≥2 次"/"重名 ≥2 次"/"失败 3 次"),不能有一条是模糊描述。
  • 契约文本里禁止出现字面三个反引号(会打断代码块闭合、把后面内容判成截断的散文)。要提"代码块/代码围栏"这种意思时写"代码围栏"或"fenced block",别直接敲三个反引号。
  • C5.5 负例:每个 unit 至少 1 条验收命令要么用 test $? -ne 0 / exit 1 这种退出码断言,要么在同一行用行内注释显式点出"边界:空/无H2/不存在/缺参数/缺字段/重名/非法"等字样(纯 console.assert 断言函数返回值不含中文语境词时,光靠代码本身不会被判成负例覆盖,必须加注释锚点)。评分正则词表很窄,"超时""抛错""除零""越界"这类不在词表里的语义,统一改标"非法"兜底,不要生造新词。
  • C4.3 边界条款:"无 X → ..." 这个写法里,X 必须是纯 ASCII 单词(如 total/seed/id),且 X 和 → 之间只能有空格,不能插入中文括注("无 total(total=0)→" 这种写法会让判定失效,正确写法是"无 total → ...",把括注挪到箭头之后)。中文语境下的边界条件优先用"空数组/空串/缺参数/缺字段/不存在 →/非法"这几个词,不要发明新词。

3. 收尾自查表(写完 plan-.md 后逐条打勾,缺一条不许交付)

  1. 需求原文里的每个能力名词,我的单元拆分表里都出现了吗?
  2. 我的单元拆分表是按"变化边界"切的,还是按"读/算/写"流程横切的?
  3. 每个单元的验收命令是不是都以可执行命令词开头,一条形容词都没有?
  4. 第4节是不是全部是代码块,没有一个字的散文契约?
  5. 第4节的签名,跟第3节/第5节里出现的每个函数名,是不是逐一对得上?
  6. 风险表 5 列填满了吗?"规划本身可能切错"这条在不在?
  7. 波数/并行度我用 Kahn 算法在脑子里跑过一遍了吗?成环了吗?
  8. 非范围 ≥3 条了吗?
  9. 环境约束(Node 版本/依赖政策/编码退出码)三项都写了吗?
  10. 我是不是手痒写了可运行的实现代码?那是 coder 的活。