# SKILL: code-planning(v1) > 训练对象:`p2-verify:code-planner` 评分表:训练材料 benchmark_v1.md(53 判据,及格线 90, > 位于本体 `train/digest/` 下,供人工审阅训练过程,不是本 SKILL 运行时依赖) > 用法:收到一句话需求 → 严格按下面的模板产出 `plan-.md`,逐节自查表全部打✅才算完。 ## 0. 心法(写之前先想清楚) 规划的价值 = 你替 coder/tester/validator 消灭掉的不确定性总量。三个反问: 1. 这份规划下游读完,还需要**自己现编**任何东西吗(签名?边界行为?验收命令?)?现编=你没做完。 2. 哪些是我能自己拍板的(技术选型/边界细节),哪些是真正需要上级决策的(影响范围/预算/对外接口)?前者当场裁决写理由,后者才上报。 3. 我是不是在复述需求换个格式抄一遍?如果把"目标"和"单元拆分"拼起来约等于原始需求,说明没有拆解增量。 ## 1. 输出模板(六节 + 澄清节,逐字段照抄结构,不要发挥) ```markdown # plan- · <一句话目标> ## 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. 每单元验收标准 ```bash # U1 <可执行命令1> <可执行命令2(负例/边界)> # U2 ... ``` 全局基线(仅此一处,不在每个 unit 下重复):`npx vitest run` 全绿 ## 6. 风险与待定 | 风险 | 命中单元 | 触发信号 | 影响面 | 预案 | |---|---|---|---|---| | <风险描述> | U2 | <可观测阈值,如"打回≥2次"> | <受影响范围> | <具体降级/替代方案,不是"注意/加强"> | | 规划本身可能切错 | 全局 | 同一 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<、下划线开头变量、cache。只写"做什么形状",不写"怎么做"。 - **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 的活。