Commit 105a2e5c69725734f3fad79546758dfafb8d5524

Authored by zichun
1 parent bc2388c0

coding.mjs: trim duplicated rationale comments and dedupe identical schemas/prompt blocks

Showing 1 changed file with 58 additions and 143 deletions
workflows/coding.mjs
1 // workflows/coding.mjs 1 // workflows/coding.mjs
2 // 2 //
3 // 整个 ERP Coding(B 阶段)= 一个静默、全自动的 Workflow 脚本。 3 // 整个 ERP Coding(B 阶段)= 一个静默、全自动的 Workflow 脚本。
4 -//  
5 -// 设计原则(详见仓库根 README.md 「阶段 B」 节 与 「设计原则」 节):  
6 -// - 所有 stage 都是 agent() 子代理,物理上无法 AskUserQuestion → 编码期结构性静默。  
7 -// - 缺值不再问人:派生 stage 把具体阻塞点写进产物并 throw(fail-fast,合法 halt → 终止态,非对话框)。  
8 -// - 后端 / 前端功能循环由同一份 featureLoop(items, phase) 驱动;phase 切换 reviewer checklist、  
9 -// 测试命令、路径作用域(backend/ vs frontend/)、id 格式(REQ-XXX-NNN vs FE-NN)。  
10 -// - **featureLoop 采用顺序 for-await**(不是 pipeline)。两条理由:  
11 -// (1) tdd/fix stage 会在共享工作树 + 同一功能分支上 git commit / 编辑源码;并发会争 .git/index.lock  
12 -// 并撞 migration 版本号;  
13 -// (2) pipeline 的语义是"stage 抛异常 → 该 item 掉 null、pipeline 永不 reject",会把  
14 -// reviewWithFixLoop / verify / tdd 的 HALT throw 静默吞掉,使 fail-fast 在功能链层级失效,  
15 -// 残缺模块照样会被 testGate/report/milestone 推进。顺序 for-await 让 throw 自然冒泡到  
16 -// 模块主循环的 try/catch,被捕获后整阶段 fail-fast break。  
17 -// - 状态账本 = docs/08 §二/§三 + git tag;halt 后重跑 coding-start,router 从账本+tag 重算进度。  
18 -// - reviewer 统一为 agents/code-reviewer.md,review stage 用 agentType:'code-reviewer'。  
19 -//  
20 -// 运行时约束:Workflow 运行时禁用非确定性内建(取当天日期 / 随机数的 API)。本脚本不调用它们;  
21 -// 凡需要"当天日期"的产物路径(<YYYY-MM-DD>-<id>.md),一律由子代理在其自身上下文中解析并落盘,  
22 -// 脚本只负责编排,不计算日期 / 随机数。 4 +// 设计原则见仓库根 README.md「阶段 B」与「设计原则」节;featureLoop 顺序 for-await 的取舍
  5 +// 详见 featureLoop 函数处的注释。运行时禁用日期 / 随机数 builtin,所有"今天"由子代理解析。
23 6
24 export const meta = { 7 export const meta = {
25 name: 'erp-coding', 8 name: 'erp-coding',
@@ -38,12 +21,7 @@ const ROUTER_SCHEMA = { type:&#39;object&#39;, additionalProperties:false, @@ -38,12 +21,7 @@ const ROUTER_SCHEMA = { type:&#39;object&#39;, additionalProperties:false,
38 reqs:{type:'array',items:{type:'string'}}, 21 reqs:{type:'array',items:{type:'string'}},
39 feItems:{type:'array',items:{type:'string'}} } } } } } 22 feItems:{type:'array',items:{type:'string'}} } } } } }
40 23
41 -// REVIEW_SCHEMA:reviewer 返回的裁决。issues 改为结构化对象,避免"模糊一行 must-fix"  
42 -// 让 fix stage 无从下手就空转 5 轮(见 reviewWithFixLoop 的 must-fix 闸门)。  
43 -// - summary:人类可读的问题摘要(一句)。  
44 -// - locator:必须能让 fix stage 定位到文件(含 `<repo-relative-path>` 或 `<path>:<line>`),  
45 -// 否则在 reviewWithFixLoop 里直接判违约 HALT。  
46 -// - severity:blocker/high/medium/low,方便后续把 low/medium 降级为 suggestion 而不卡循环。 24 +// REVIEW_SCHEMA:reviewer 裁决;issues 结构化对象(summary/locator/severity)驱动 fix。
47 const REVIEW_SCHEMA = { type:'object', additionalProperties:false, 25 const REVIEW_SCHEMA = { type:'object', additionalProperties:false,
48 required:['verdict','round','issues'], properties:{ 26 required:['verdict','round','issues'], properties:{
49 verdict:{type:'string',enum:['approve','request-changes']}, 27 verdict:{type:'string',enum:['approve','request-changes']},
@@ -56,13 +34,7 @@ const REVIEW_SCHEMA = { type:&#39;object&#39;, additionalProperties:false, @@ -56,13 +34,7 @@ const REVIEW_SCHEMA = { type:&#39;object&#39;, additionalProperties:false,
56 locator:{type:'string'}, 34 locator:{type:'string'},
57 severity:{type:'string', enum:['blocker','high','medium','low']} } } } } } 35 severity:{type:'string', enum:['blocker','high','medium','low']} } } } } }
58 36
59 -// STAGE_RESULT_SCHEMA:派生 stage(spec/plan/tdd/verify/fix/report)的统一结构化返回。  
60 -// - status=ok:本步骤产出可用,artifactPath 必填(spec/plan/verify/report 的落盘文件),  
61 -// summary 可放给下游用作 prompt 上下文。tdd/fix 没有单一 artifact,artifactPath 可省。  
62 -// - status=halt:sub-agent 已经决定无法继续(缺值 / 越界 / 重试到顶),把阻塞点写进 reason,  
63 -// JS 端读到立即 throw `HALT …`,让 fail-fast 顺序 for-await 冒泡到模块主循环 try。  
64 -// - 无 schema 时,sub-agent 可以"写一段散文说我跑不下去了,但仍然算成功返回"——这是真正的  
65 -// fail-fast 漏洞;加 schema 后所有派生 stage 都有显式 halt 通道。 37 +// STAGE_RESULT_SCHEMA:派生 stage 统一返回,status=halt 时 JS 立即 throw HALT。
66 const STAGE_RESULT_SCHEMA = { type:'object', additionalProperties:false, 38 const STAGE_RESULT_SCHEMA = { type:'object', additionalProperties:false,
67 required:['status'], properties:{ 39 required:['status'], properties:{
68 status:{type:'string', enum:['ok','halt']}, 40 status:{type:'string', enum:['ok','halt']},
@@ -75,11 +47,6 @@ const GATE_SCHEMA = { type:&#39;object&#39;, additionalProperties:false, @@ -75,11 +47,6 @@ const GATE_SCHEMA = { type:&#39;object&#39;, additionalProperties:false,
75 failures:{type:'array',items:{type:'string'}} } } 47 failures:{type:'array',items:{type:'string'}} } }
76 48
77 // ── 微步骤 schemas(runBranchSetup / runMilestone / runCrossModule 用)───────── 49 // ── 微步骤 schemas(runBranchSetup / runMilestone / runCrossModule 用)─────────
78 -// 这三个阶段是纯机械的 git/文件操作 + 条件跳过;与其让子代理读"1. 2. 3. 若 X 则跳过"的散文  
79 -// 流程,不如把"observe → JS branch → execute"切成多个 agent 微步骤,每步带强 schema 返回。  
80 -// 这样:(a) 跳过/分支条件由 JS 判定(不再依赖 LLM 读散文条件),idempotency 一致;  
81 -// (b) 每步语义单一、prompt 短,schema 校验阻断畸形返回;  
82 -// (c) action 步统一返回 ACTION_RESULT_SCHEMA(success/error),失败可由 JS 抛错 halt。  
83 const WT_SCHEMA = { type:'object', additionalProperties:false, 50 const WT_SCHEMA = { type:'object', additionalProperties:false,
84 required:['clean'], properties:{ 51 required:['clean'], properties:{
85 clean:{type:'boolean'}, 52 clean:{type:'boolean'},
@@ -91,30 +58,20 @@ const DEFAULT_BRANCH_SCHEMA = { type:&#39;object&#39;, additionalProperties:false, @@ -91,30 +58,20 @@ const DEFAULT_BRANCH_SCHEMA = { type:&#39;object&#39;, additionalProperties:false,
91 const EXISTS_SCHEMA = { type:'object', additionalProperties:false, 58 const EXISTS_SCHEMA = { type:'object', additionalProperties:false,
92 required:['exists'], properties:{ exists:{type:'boolean'} } } 59 required:['exists'], properties:{ exists:{type:'boolean'} } }
93 60
94 -const CURRENT_BRANCH_SCHEMA = { type:'object', additionalProperties:false,  
95 - required:['branch'], properties:{ branch:{type:'string'} } }  
96 -  
97 const FIELD_VALUE_SCHEMA = { type:'object', additionalProperties:false, 61 const FIELD_VALUE_SCHEMA = { type:'object', additionalProperties:false,
98 required:['found','value'], properties:{ 62 required:['found','value'], properties:{
99 found:{type:'boolean'}, 63 found:{type:'boolean'},
100 value:{type:'string'}, 64 value:{type:'string'},
101 lineNumber:{type:'integer'} } } 65 lineNumber:{type:'integer'} } }
102 66
103 -// CHECKBOX_STATE_SCHEMA:docs/08 中 `- [ ] REQ-XXX-NNN ...` / `- [x] FE-NN ...` 这类功能行  
104 -// 的勾选态。把"审阅 approve 后 flip checkbox"从 reviewPrompt 的隐式 side-effect 改为可观测  
105 -// 的 read-then-write micro step(参见 reviewWithFixLoop 的 approve 分支)。  
106 -// 注意:state 必填——schema 只 require found 时,sub-agent 返回 {found:true} 而漏掉 state 仍合法,  
107 -// 上层 if (cb.state === 'unchecked') 会静默落入"已 checked"分支,docs/08 与 review 裁决悄悄背离。 67 +// CHECKBOX_STATE_SCHEMA:docs/08 功能行勾选态;state 必填——只 require found 时 cb.state 缺失会静默走 checked 分支。
108 const CHECKBOX_STATE_SCHEMA = { type:'object', additionalProperties:false, 68 const CHECKBOX_STATE_SCHEMA = { type:'object', additionalProperties:false,
109 required:['found','state'], properties:{ 69 required:['found','state'], properties:{
110 found:{type:'boolean'}, 70 found:{type:'boolean'},
111 state:{type:'string', enum:['checked','unchecked']}, 71 state:{type:'string', enum:['checked','unchecked']},
112 lineNumber:{type:'integer'} } } 72 lineNumber:{type:'integer'} } }
113 73
114 -// TAG_REPORT_FRESHNESS_SCHEMA:当 milestone tag 已存在(resume 或前一轮残留)时,校验  
115 -// tag 指向的 commit 是否包含已落地的 § ⑫ 值。旧版 bug:tag → § ⑫ commit 的错序会让 tag  
116 -// 指向 \`{{milestone_tag}}\` 占位符 commit;新顺序(report → tag)下不会再产生,但保留  
117 -// freshness 自检以发现历史残留。 74 +// TAG_REPORT_FRESHNESS_SCHEMA:保留以发现旧版 tag → § ⑫ 错序 bug 残留(tag 指向占位符 commit)。
118 const TAG_REPORT_FRESHNESS_SCHEMA = { type:'object', additionalProperties:false, 75 const TAG_REPORT_FRESHNESS_SCHEMA = { type:'object', additionalProperties:false,
119 required:['fresh'], properties:{ 76 required:['fresh'], properties:{
120 fresh:{type:'boolean'}, 77 fresh:{type:'boolean'},
@@ -150,20 +107,12 @@ const ACTION_RESULT_SCHEMA = { type:&#39;object&#39;, additionalProperties:false, @@ -150,20 +107,12 @@ const ACTION_RESULT_SCHEMA = { type:&#39;object&#39;, additionalProperties:false,
150 detail:{type:'string'} } } 107 detail:{type:'string'} } }
151 108
152 const ROOT = args?.projectRoot || '.' 109 const ROOT = args?.projectRoot || '.'
153 -// 子代理在 ${ROOT} 路径上跑 git -C / Read / Edit。相对路径 '.' 会绑定到子代理隐式 cwd,无保证。  
154 -// 必须由 coding-start 显式传绝对路径;否则 fail-fast 让人工修复入口而不是在错路径上静默打 tag。 110 +// ROOT 必须是绝对路径——相对 '.' 会绑定到子代理隐式 cwd,无保证。
155 if (ROOT === '.' || !(/^(?:\/|[A-Za-z]:[\\/])/.test(ROOT))) { 111 if (ROOT === '.' || !(/^(?:\/|[A-Za-z]:[\\/])/.test(ROOT))) {
156 throw new Error(`HALT invalid-projectRoot: must be absolute, got ${JSON.stringify(ROOT)}. coding-start 必须把绝对路径传入 args.projectRoot。`) 112 throw new Error(`HALT invalid-projectRoot: must be absolute, got ${JSON.stringify(ROOT)}. coding-start 必须把绝对路径传入 args.projectRoot。`)
157 } 113 }
158 114
159 -// ============================================================================  
160 -// Stage prompt builders(纯字符串构造;只用 ROOT / id / phase / 入参,不触非确定性内建)  
161 -//  
162 -// 每个 prompt 的共同契约(见 featureStageContract):  
163 -// - 子代理是非交互的,物理上无法弹窗;缺任何值都不要"问人"——把具体阻塞点写进产物并失败。  
164 -// - phase=backend 与 phase=frontend 的差异(路径作用域 / id 形态 / 测试命令来源)逐条写明。  
165 -// - 所有输出文档用中文。  
166 -// ============================================================================ 115 +// ── Feature-loop stage prompts(共享非交互契约见 featureStageContract)──
167 116
168 function isFrontend(phase) { return phase === 'frontend' } 117 function isFrontend(phase) { return phase === 'frontend' }
169 118
@@ -190,7 +139,9 @@ function featureStageContract(phase) { @@ -190,7 +139,9 @@ function featureStageContract(phase) {
190 return [ 139 return [
191 '## 硬约束(非交互子代理)', 140 '## 硬约束(非交互子代理)',
192 '- 你是 Workflow 派生的**非交互子代理**,物理上无法弹出 AskUserQuestion / 等待人类输入。**绝不要尝试问人**。', 141 '- 你是 Workflow 派生的**非交互子代理**,物理上无法弹出 AskUserQuestion / 等待人类输入。**绝不要尝试问人**。',
193 - '- 需要具体值时,按此顺序自行获取:(1) 先查 `.env.local`、`docs/07-环境配置.md`、`docs/04-技术规范.md`、`docs/05-API接口契约.md`、`docs/06-UI交互规范.md`、`CLAUDE.md`、现有代码;(2) 仍查不到 → **不要编造、不要留 `【人工填写:】` / `TBD` / `TODO` 占位**,而是把**具体的阻塞点**(缺哪个值、应在哪个 Plan 期闸门锁定、为何无法继续)写进产物,然后让本步骤**失败**(以非零结果 / 显式 throw 结束),由上层 Workflow 转为带诊断的 halt。', 142 + '- 缺值查找顺序:`.env.local` → `docs/07-环境配置.md` → `docs/04-技术规范.md` → `docs/05-API接口契约.md` → `docs/06-UI交互规范.md` → `CLAUDE.md` → 现有代码。',
  143 + '- 仍查不到 → **不要编造、不要留 `【人工填写:】` / `TBD` / `TODO` 占位**;把具体阻塞点(缺哪个值、应在哪个 Plan 闸门锁定、为何无法继续)写进产物。',
  144 + '- 然后让本步骤以非零结果 / 显式 throw 结束,由上层 Workflow 转为带诊断的 halt(fail-fast)。',
194 '- 全部输出文档**使用中文**。', 145 '- 全部输出文档**使用中文**。',
195 `- **阶段 = ${fe ? '前端(frontend)' : '后端(backend)'}**。路径作用域:${fe 146 `- **阶段 = ${fe ? '前端(frontend)' : '后端(backend)'}**。路径作用域:${fe
196 ? '实现文件必须落在 `frontend/`(或 `docs/09-项目目录结构.md § 前端目录结构` 声明的前端根)下;命中 `backend/` / `sql/` / `scripts/` 即越界,硬停。' 147 ? '实现文件必须落在 `frontend/`(或 `docs/09-项目目录结构.md § 前端目录结构` 声明的前端根)下;命中 `backend/` / `sql/` / `scripts/` 即越界,硬停。'
@@ -199,6 +150,17 @@ function featureStageContract(phase) { @@ -199,6 +150,17 @@ function featureStageContract(phase) {
199 ].join('\n') 150 ].join('\n')
200 } 151 }
201 152
  153 +// commitBlock:spec/plan/verify/review 共用的"写完 → add → commit → 失败 halt"四行块。
  154 +function commitBlock(addPath, msg, tail = '- commit 失败 → halt,把 stderr 摘要写进 reason。') {
  155 + return [
  156 + '## commit',
  157 + `- 写完后必须 commit(milestone 的 worktree-clean 前置依赖此 commit):`,
  158 + ` 1. \`git -C ${ROOT} add ${addPath}\``,
  159 + ` 2. \`git -C ${ROOT} commit -m "${msg}"\``,
  160 + tail,
  161 + ].join('\n')
  162 +}
  163 +
202 // Router:读 docs/08 §二/§三 + git tag,重算进度,返回 ROUTER_SCHEMA。 164 // Router:读 docs/08 §二/§三 + git tag,重算进度,返回 ROUTER_SCHEMA。
203 function routerPrompt(root) { 165 function routerPrompt(root) {
204 return [ 166 return [
@@ -264,11 +226,7 @@ function deriveSpecPrompt(id, phase) { @@ -264,11 +226,7 @@ function deriveSpecPrompt(id, phase) {
264 ? '- 规格至少含:关联 REQ + 关联原型;组件树(按页面 / 区域分块,推导自 prototype DOM);页面状态机(loading / empty / error / 正常 / 表单提交中 至少 5 态);消费的后端端点(对齐 docs/05);业务规则前端复刻清单(逐条:规则 / 触发时机 / 报错文案 / 来源 REQ);Design Tokens 引用清单(`var(--color-*)`)。' 226 ? '- 规格至少含:关联 REQ + 关联原型;组件树(按页面 / 区域分块,推导自 prototype DOM);页面状态机(loading / empty / error / 正常 / 表单提交中 至少 5 态);消费的后端端点(对齐 docs/05);业务规则前端复刻清单(逐条:规则 / 触发时机 / 报错文案 / 来源 REQ);Design Tokens 引用清单(`var(--color-*)`)。'
265 : '- 规格覆盖:goal / 输入输出 / 业务规则 / 约束 / schema / API 引用 / acceptance criteria。', 227 : '- 规格覆盖:goal / 输入输出 / 业务规则 / 约束 / schema / API 引用 / acceptance criteria。',
266 '', 228 '',
267 - '## commit',  
268 - `- 写完 spec 后必须 commit(milestone 的 worktree-clean 前置依赖此 commit):`,  
269 - ` 1. \`git -C ${ROOT} add <spec artifactPath>\``,  
270 - ` 2. \`git -C ${ROOT} commit -m "docs(spec:${id}): 派生规格"\``,  
271 - '- commit 失败 → halt,把 stderr 摘要写进 reason。', 229 + commitBlock('<spec artifactPath>', `docs(spec:${id}): 派生规格`),
272 '', 230 '',
273 '## 自审(inline 修,无须等待)', 231 '## 自审(inline 修,无须等待)',
274 `- 占位符扫描:\`TBD\` / \`TODO\` / \`【人工填写:】\`${fe ? ' / `controller` / `service` / `SQL` / `migration`(前端 spec 不应出现后端字样)' : ''} → 命中即修;修不掉的缺值按硬约束失败。`, 232 `- 占位符扫描:\`TBD\` / \`TODO\` / \`【人工填写:】\`${fe ? ' / `controller` / `service` / `SQL` / `migration`(前端 spec 不应出现后端字样)' : ''} → 命中即修;修不掉的缺值按硬约束失败。`,
@@ -315,11 +273,7 @@ function planPrompt(id, phase, specPath) { @@ -315,11 +273,7 @@ function planPrompt(id, phase, specPath) {
315 `- 落盘路径:\`docs/superpowers/plans/<同 spec 的 YYYY-MM-DD>-${id}.md\`,文件头含 Goal / Architecture / Tech Stack + checkbox 任务。`, 273 `- 落盘路径:\`docs/superpowers/plans/<同 spec 的 YYYY-MM-DD>-${id}.md\`,文件头含 Goal / Architecture / Tech Stack + checkbox 任务。`,
316 '- 自审:占位符扫描(按硬约束清单);spec coverage(spec 每节至少指向一个 task,补 gap);类型一致性(签名 / 方法名 / 错误码 / props 一致)。', 274 '- 自审:占位符扫描(按硬约束清单);spec coverage(spec 每节至少指向一个 task,补 gap);类型一致性(签名 / 方法名 / 错误码 / props 一致)。',
317 '', 275 '',
318 - '## commit',  
319 - `- 写完 plan 后必须 commit(milestone 的 worktree-clean 前置依赖此 commit):`,  
320 - ` 1. \`git -C ${ROOT} add <plan artifactPath>\``,  
321 - ` 2. \`git -C ${ROOT} commit -m "docs(plan:${id}): 任务级 TDD 计划"\``,  
322 - '- commit 失败 → halt,把 stderr 摘要写进 reason。', 276 + commitBlock('<plan artifactPath>', `docs(plan:${id}): 任务级 TDD 计划`),
323 '', 277 '',
324 '## 输出(必须符合下发的 STAGE_RESULT JSON schema)', 278 '## 输出(必须符合下发的 STAGE_RESULT JSON schema)',
325 '- 成功:`{ "status": "ok", "artifactPath": "docs/superpowers/plans/YYYY-MM-DD-' + id + '.md", "summary": "<1-2 句中文摘要:任务数 / 涉及文件作用域>" }`', 279 '- 成功:`{ "status": "ok", "artifactPath": "docs/superpowers/plans/YYYY-MM-DD-' + id + '.md", "summary": "<1-2 句中文摘要:任务数 / 涉及文件作用域>" }`',
@@ -398,11 +352,8 @@ function verifyPrompt(id, phase, implSummary, specPath, round = 0) { @@ -398,11 +352,8 @@ function verifyPrompt(id, phase, implSummary, specPath, round = 0) {
398 ].join('\n'), 352 ].join('\n'),
399 `- 证据落盘路径固定为 \`docs/superpowers/reviews/<同 spec 的 YYYY-MM-DD>-${id}-${suffix}.md\`(与 review 报告同目录;round=0 → \`-verify.md\`;round>=1 → \`-verify-r<N>.md\`,**每轮独立文件不覆盖前轮**)。同时把核心结构化结果摘要打印到会话便于上层 review stage 引用,**不要**自行另起目录或自由命名文件。`, 353 `- 证据落盘路径固定为 \`docs/superpowers/reviews/<同 spec 的 YYYY-MM-DD>-${id}-${suffix}.md\`(与 review 报告同目录;round=0 → \`-verify.md\`;round>=1 → \`-verify-r<N>.md\`,**每轮独立文件不覆盖前轮**)。同时把核心结构化结果摘要打印到会话便于上层 review stage 引用,**不要**自行另起目录或自由命名文件。`,
400 '', 354 '',
401 - '## commit',  
402 - `- 写完证据后必须 commit(milestone 的 worktree-clean 前置依赖此 commit):`,  
403 - ` 1. \`git -C ${ROOT} add <证据 artifactPath>\``,  
404 - ` 2. \`git -C ${ROOT} commit -m "docs(verify:${id}${round > 0 ? `:r${round}` : ''}): 证据验证"\``,  
405 - '- commit 失败 → halt,把 stderr 摘要写进 reason(仍要返回已写入的证据路径)。', 355 + commitBlock('<证据 artifactPath>', `docs(verify:${id}${round > 0 ? `:r${round}` : ''}): 证据验证`,
  356 + '- commit 失败 → halt,把 stderr 摘要写进 reason(仍要返回已写入的证据路径)。'),
406 '', 357 '',
407 '## 输出(必须符合下发的 STAGE_RESULT JSON schema)', 358 '## 输出(必须符合下发的 STAGE_RESULT JSON schema)',
408 `- 全部通过:\`{ "status": "ok", "artifactPath": "docs/superpowers/reviews/YYYY-MM-DD-${id}-${suffix}.md", "summary": "<exit_code / passed / failed / failed_list 摘要 ≤ 200 字>" }\`。`, 359 `- 全部通过:\`{ "status": "ok", "artifactPath": "docs/superpowers/reviews/YYYY-MM-DD-${id}-${suffix}.md", "summary": "<exit_code / passed / failed / failed_list 摘要 ≤ 200 字>" }\`。`,
@@ -439,11 +390,9 @@ function reviewPrompt(id, phase, round, lastVerifySummary, specPath) { @@ -439,11 +390,9 @@ function reviewPrompt(id, phase, round, lastVerifySummary, specPath) {
439 `- **不要**在本步骤里编辑 docs/08 的 \`- [ ]\` checkbox——该 side effect 由上层 Workflow 的 micro step 在 approve 后另行落盘(你只负责裁决)。`, 390 `- **不要**在本步骤里编辑 docs/08 的 \`- [ ]\` checkbox——该 side effect 由上层 Workflow 的 micro step 在 approve 后另行落盘(你只负责裁决)。`,
440 '- 不要返回额外字段(schema 是 `additionalProperties:false`)。', 391 '- 不要返回额外字段(schema 是 `additionalProperties:false`)。',
441 '', 392 '',
442 - '## commit',  
443 - `- 写完审阅报告后必须 commit(milestone 的 worktree-clean 前置依赖此 commit;该 commit 与 verdict 无关,approve 或 request-changes 都要 commit 报告本身):`,  
444 - ` 1. \`git -C ${ROOT} add docs/superpowers/reviews/<同 spec 的 YYYY-MM-DD>-${id}.md\``,  
445 - ` 2. \`git -C ${ROOT} commit -m "docs(review:${id}:r${round}): <verdict>"\``,  
446 - '- commit 失败时仍按 schema 返回 verdict / issues;commit 错误信息打印到日志即可(不要在 schema 中夹带额外字段)。', 393 + commitBlock(`docs/superpowers/reviews/<同 spec 的 YYYY-MM-DD>-${id}.md`,
  394 + `docs(review:${id}:r${round}): <verdict>`,
  395 + '- commit 失败时仍按 schema 返回 verdict / issues;commit 错误信息打印到日志即可(不要在 schema 中夹带额外字段)。'),
447 ].filter(Boolean).join('\n') 396 ].filter(Boolean).join('\n')
448 } 397 }
449 398
@@ -465,9 +414,6 @@ function fixPrompt(id, phase, issues) { @@ -465,9 +414,6 @@ function fixPrompt(id, phase, issues) {
465 '## 流程', 414 '## 流程',
466 '- 逐项编辑 locator 指向的代码文件(遵守阶段路径作用域护栏)。', 415 '- 逐项编辑 locator 指向的代码文件(遵守阶段路径作用域护栏)。',
467 `- 编辑前必须先校验 locator 文件存在:跑 \`git -C ${ROOT} cat-file -e HEAD:<locator 的文件部分>\`(locator 形如 \`path:line\` 时取 \`path\`)。文件不存在 → halt,把 locator 写进 reason,不要"修一个不存在的文件"。`, 416 `- 编辑前必须先校验 locator 文件存在:跑 \`git -C ${ROOT} cat-file -e HEAD:<locator 的文件部分>\`(locator 形如 \`path:line\` 时取 \`path\`)。文件不存在 → halt,把 locator 写进 reason,不要"修一个不存在的文件"。`,
468 - fe  
469 - ? '- **硬护栏(与 tdd 同款)**:命中 `backend/` / `sql/` / `scripts/` → halt 并把 file 路径写进 reason。'  
470 - : '- **硬护栏(与 tdd 同款)**:任一被编辑文件以 `frontend/` 开头 → halt 并把 file 路径写进 reason。',  
471 `- 修复后 commit:\`fix(<scope>): 修复 review must-fix ${fe ? `FE: ${id}` : `REQ: ${id}`}\`(不混合无关改动)。`, 417 `- 修复后 commit:\`fix(<scope>): 修复 review must-fix ${fe ? `FE: ${id}` : `REQ: ${id}`}\`(不混合无关改动)。`,
472 '- 修复完成后本步骤即结束;上层 Workflow 会重新跑 verify + review(下一轮)。', 418 '- 修复完成后本步骤即结束;上层 Workflow 会重新跑 verify + review(下一轮)。',
473 '', 419 '',
@@ -563,7 +509,7 @@ function currentBranchPromptM() { @@ -563,7 +509,7 @@ function currentBranchPromptM() {
563 microStepContract(), 509 microStepContract(),
564 '', 510 '',
565 `跑 \`git -C ${ROOT} rev-parse --abbrev-ref HEAD\`。`, 511 `跑 \`git -C ${ROOT} rev-parse --abbrev-ref HEAD\`。`,
566 - '## 输出(CURRENT_BRANCH_SCHEMA)', 512 + '## 输出(BRANCH_NAME_SCHEMA)',
567 '- `{ "branch": "<stdout 第一行去空白>" }`', 513 '- `{ "branch": "<stdout 第一行去空白>" }`',
568 ].join('\n') 514 ].join('\n')
569 } 515 }
@@ -646,25 +592,24 @@ function executeMergePromptM(defaultBranch, branch, phaseId) { @@ -646,25 +592,24 @@ function executeMergePromptM(defaultBranch, branch, phaseId) {
646 } 592 }
647 593
648 function readDocs08FieldPromptM(fe, id) { 594 function readDocs08FieldPromptM(fe, id) {
649 - if (fe) {  
650 - return [  
651 - '# 读 docs/08 § 三 `整体里程碑:` 字段当前值',  
652 - microStepContract(),  
653 - '',  
654 - `Read \`${ROOT}/docs/08-模块任务管理.md\`,定位 § 三(前端阶段)下的 \`- 整体里程碑: <value>\` 行。`,  
655 - '## 输出(FIELD_VALUE_SCHEMA)',  
656 - '- 命中:`{ "found": true, "value": "<冒号后去空白的当前值>", "lineNumber": <该行 1-based 行号> }`',  
657 - '- § 三 或该行不存在:`{ "found": false, "value": "" }`',  
658 - ].join('\n')  
659 - } 595 + const section = fe ? '§ 三' : '§ 二'
  596 + const title = fe
  597 + ? '# 读 docs/08 § 三 `整体里程碑:` 字段当前值'
  598 + : `# 读 docs/08 § 二 模块 \`${id}\` 的 \`里程碑:\` 字段当前值`
  599 + const locator = fe
  600 + ? `Read \`${ROOT}/docs/08-模块任务管理.md\`,定位 ${section}(前端阶段)下的 \`- 整体里程碑: <value>\` 行。`
  601 + : `Read \`${ROOT}/docs/08-模块任务管理.md\`,定位 ${section} 中 module id == \`${id}\` 的 bullet 段,取其 \` - 里程碑: <value>\` 子项。`
  602 + const missing = fe
  603 + ? '- § 三 或该行不存在:`{ "found": false, "value": "" }`'
  604 + : `- 模块 \`${id}\` 或该字段不存在:\`{ "found": false, "value": "" }\``
660 return [ 605 return [
661 - `# 读 docs/08 § 二 模块 \`${id}\` 的 \`里程碑:\` 字段当前值`, 606 + title,
662 microStepContract(), 607 microStepContract(),
663 '', 608 '',
664 - `Read \`${ROOT}/docs/08-模块任务管理.md\`,定位 § 二 中 module id == \`${id}\` 的 bullet 段,取其 \` - 里程碑: <value>\` 子项。`, 609 + locator,
665 '## 输出(FIELD_VALUE_SCHEMA)', 610 '## 输出(FIELD_VALUE_SCHEMA)',
666 '- 命中:`{ "found": true, "value": "<冒号后去空白的当前值>", "lineNumber": <行号> }`', 611 '- 命中:`{ "found": true, "value": "<冒号后去空白的当前值>", "lineNumber": <行号> }`',
667 - `- 模块 \`${id}\` 或该字段不存在:\`{ "found": false, "value": "" }\``, 612 + missing,
668 ].join('\n') 613 ].join('\n')
669 } 614 }
670 615
@@ -690,33 +635,22 @@ function writeDocs08FieldPromptM(fe, id, targetValue, phaseId, lineNumber) { @@ -690,33 +635,22 @@ function writeDocs08FieldPromptM(fe, id, targetValue, phaseId, lineNumber) {
690 ].join('\n') 635 ].join('\n')
691 } 636 }
692 637
693 -// ── 微步骤:docs/08 功能行 checkbox 勾选态(reviewer approve 后的可观测 side effect)──  
694 -// 原 reviewPrompt 让 reviewer 顺手 flip checkbox:reviewer 失败时 router/进度判定看不到,且  
695 -// reviewer agent 多了一项 file edit 副作用。拆为 read-then-write 两个 micro step:  
696 -// reviewWithFixLoop approve → read → 若 unchecked → write → assert success;  
697 -// 已 checked → 静默跳过(resume 幂等)。 638 +// ── 微步骤:docs/08 功能行 checkbox(reviewer approve 后的可观测 side effect;read-then-write)──
698 function readDocs08CheckboxPromptM(fe, id) { 639 function readDocs08CheckboxPromptM(fe, id) {
699 - if (fe) {  
700 - return [  
701 - `# 读 docs/08 § 三 功能 \`${id}\` 的勾选态(\`- [ ] ${id} ...\` / \`- [x] ${id} ...\`)`,  
702 - microStepContract(),  
703 - '',  
704 - `Read \`${ROOT}/docs/08-模块任务管理.md\`,定位 § 三(前端阶段)下的 \`功能:\` 项,从中找以 \`- [ ] ${id} \` 或 \`- [x] ${id} \` 开头的行(注意 id 后必须紧跟空格,避免误中前缀同名)。`,  
705 - '## 输出(CHECKBOX_STATE_SCHEMA)',  
706 - `- 命中 \`- [x] ${id} ...\`:\`{ "found": true, "state": "checked", "lineNumber": <行号> }\``,  
707 - `- 命中 \`- [ ] ${id} ...\`:\`{ "found": true, "state": "unchecked", "lineNumber": <行号> }\``,  
708 - `- 找不到:\`{ "found": false }\``,  
709 - ].join('\n')  
710 - } 640 + const section = fe ? '§ 三' : '§ 二'
  641 + const kind = fe ? '功能' : 'REQ'
  642 + const locator = fe
  643 + ? `Read \`${ROOT}/docs/08-模块任务管理.md\`,定位 ${section}(前端阶段)下的 \`功能:\` 项,从中找以 \`- [ ] ${id} \` 或 \`- [x] ${id} \` 开头的行(注意 id 后必须紧跟空格,避免误中前缀同名)。`
  644 + : `Read \`${ROOT}/docs/08-模块任务管理.md\`,定位 ${section},找以 \`- [ ] ${id} \` 或 \`- [x] ${id} \` 开头的行(id 后必须紧跟空格)。该行可能位于任一模块 bullet 下。`
711 return [ 645 return [
712 - `# 读 docs/08 § 二 REQ - `# 读 docs/08 § 二 REQ${id}- `# 读 docs/08 § 二 REQ 的勾选态(- `# 读 docs/08 § 二 REQ- [ ] ${id} ...- `# 读 docs/08 § 二 REQ / - `# 读 docs/08 § 二 REQ- [x] ${id} ...- `# 读 docs/08 § 二 REQ)`, 646 + `# 读 docs/08 ${section} ${kind} + `# 读 docs/08 ${section} ${kind}${id}+ `# 读 docs/08 ${section} ${kind} 的勾选态(+ `# 读 docs/08 ${section} ${kind}- [ ] ${id} ...+ `# 读 docs/08 ${section} ${kind} / + `# 读 docs/08 ${section} ${kind}- [x] ${id} ...+ `# 读 docs/08 ${section} ${kind})`,
713 microStepContract(), 647 microStepContract(),
714 '', 648 '',
715 - `Read \`${ROOT}/docs/08-模块任务管理.md\`,定位 § 二,找以 \`- [ ] ${id} \` 或 \`- [x] ${id} \` 开头的行(id 后必须紧跟空格)。该行可能位于任一模块 bullet 下。`, 649 + locator,
716 '## 输出(CHECKBOX_STATE_SCHEMA)', 650 '## 输出(CHECKBOX_STATE_SCHEMA)',
717 - `- \`- [x] ${id} ...\` → \`{ "found": true, "state": "checked", "lineNumber": <行号> }\``,  
718 - `- \`- [ ] ${id} ...\` → \`{ "found": true, "state": "unchecked", "lineNumber": <行号> }\``,  
719 - `- 找不到:\`{ "found": false }\``, 651 + `- 命中 \`- [x] ${id} ...\`:\`{ "found": true, "state": "checked", "lineNumber": <行号> }\``,
  652 + `- 命中 \`- [ ] ${id} ...\`:\`{ "found": true, "state": "unchecked", "lineNumber": <行号> }\``,
  653 + '- 找不到:`{ "found": false }`',
720 ].join('\n') 654 ].join('\n')
721 } 655 }
722 656
@@ -940,7 +874,7 @@ async function runBranchSetup(module) { @@ -940,7 +874,7 @@ async function runBranchSetup(module) {
940 if (!r.success) throw new Error(`HALT branchSetup-create ${branch}: ${r.error || ''}`) 874 if (!r.success) throw new Error(`HALT branchSetup-create ${branch}: ${r.error || ''}`)
941 } 875 }
942 876
943 - const head = await agent(currentBranchPromptM(), {label: lbl('head'), phase: 'Milestone', schema: CURRENT_BRANCH_SCHEMA}) 877 + const head = await agent(currentBranchPromptM(), {label: lbl('head'), phase: 'Milestone', schema: DEFAULT_BRANCH_SCHEMA})
944 if (head.branch !== branch) throw new Error(`HALT branchSetup-branch-mismatch ${branch}: HEAD on ${head.branch}`) 878 if (head.branch !== branch) throw new Error(`HALT branchSetup-branch-mismatch ${branch}: HEAD on ${head.branch}`)
945 879
946 log(`branch-setup: ${id} → ${branch}`) 880 log(`branch-setup: ${id} → ${branch}`)
@@ -1093,11 +1027,7 @@ async function featureLoop(items, phase) { @@ -1093,11 +1027,7 @@ async function featureLoop(items, phase) {
1093 } 1027 }
1094 } 1028 }
1095 1029
1096 -// 有界 5 轮修复;超出 → throw(终止态,非对话框)。  
1097 -// fix 后再跑 reverify 让 fix-stage 的 commit 有机会被新一轮 verify 看到;  
1098 -// verify 内部失败 throw 在顺序 for-await 下会冒泡到模块主循环 try。  
1099 -// approve 后通过独立 micro step 把 docs/08 对应 checkbox flip 为 `[x]`(拆出 reviewer side-effect,  
1100 -// 写失败可观测;幂等:已 checked 静默跳过)。 1030 +// 有界 5 轮修复;超出 → throw(终止态,非对话框)。approve 后独立 micro step flip docs/08 checkbox。
1101 async function reviewWithFixLoop(id, phase, verifyResult, specPath) { 1031 async function reviewWithFixLoop(id, phase, verifyResult, specPath) {
1102 const grp = phase === 'backend' ? 'Backend' : 'Frontend' 1032 const grp = phase === 'backend' ? 'Backend' : 'Frontend'
1103 const fe = isFrontend(phase) 1033 const fe = isFrontend(phase)
@@ -1105,19 +1035,14 @@ async function reviewWithFixLoop(id, phase, verifyResult, specPath) { @@ -1105,19 +1035,14 @@ async function reviewWithFixLoop(id, phase, verifyResult, specPath) {
1105 let lastIssuesCount = 0 1035 let lastIssuesCount = 0
1106 for (let round = 1; round <= 5; round++) { 1036 for (let round = 1; round <= 5; round++) {
1107 const lastVerifySummary = (lastVerify && (lastVerify.summary || lastVerify.reason)) || '' 1037 const lastVerifySummary = (lastVerify && (lastVerify.summary || lastVerify.reason)) || ''
1108 - // 命名碰撞警告:opts.phase = grp('Backend'/'Frontend'/'Milestone')是 harness 的 UI 分组,  
1109 - // 与 code-reviewer.md 文档里的"domain phase = backend|frontend"是**同名不同物**。  
1110 - // reviewer agent 从 prompt 正文 `**phase = ...**` 那一行读 domain phase,**不要**读 opts.phase。  
1111 - // 见 agents/code-reviewer.md "Domain phase resolution" 段。 1038 + // opts.phase = grp('Backend'/'Frontend')是 harness UI 分组;domain phase 见 agents/code-reviewer.md。
1112 const r = await agent( 1039 const r = await agent(
1113 reviewPrompt(id, phase, round, lastVerifySummary, specPath), 1040 reviewPrompt(id, phase, round, lastVerifySummary, specPath),
1114 {label:`review:${phase}:${id}:r${round}`, phase: grp, schema: REVIEW_SCHEMA, agentType:'code-reviewer'} 1041 {label:`review:${phase}:${id}:r${round}`, phase: grp, schema: REVIEW_SCHEMA, agentType:'code-reviewer'}
1115 ) 1042 )
1116 if (r.verdict === 'approve') { 1043 if (r.verdict === 'approve') {
1117 - // docs/08 checkbox flip(observable side effect,原 reviewer 隐式 Edit → micro step)  
1118 const cb = await agent(readDocs08CheckboxPromptM(fe, id), {label:`cb?:${phase}:${id}`, phase: grp, schema: CHECKBOX_STATE_SCHEMA}) 1044 const cb = await agent(readDocs08CheckboxPromptM(fe, id), {label:`cb?:${phase}:${id}`, phase: grp, schema: CHECKBOX_STATE_SCHEMA})
1119 if (!cb.found) throw new Error(`HALT docs08-checkbox-missing ${phase}:${id}: docs/08 ${fe?'§ 三':'§ 二'} 中找不到 \`- [ ] ${id} ...\` / \`- [x] ${id} ...\` 行`) 1045 if (!cb.found) throw new Error(`HALT docs08-checkbox-missing ${phase}:${id}: docs/08 ${fe?'§ 三':'§ 二'} 中找不到 \`- [ ] ${id} ...\` / \`- [x] ${id} ...\` 行`)
1120 - // 防御:即使 schema 已 require state,再做一次 JS 校验,杜绝"found:true 但 state 缺失/枚举外"静默走 checked 分支。  
1121 if (cb.state !== 'checked' && cb.state !== 'unchecked') { 1046 if (cb.state !== 'checked' && cb.state !== 'unchecked') {
1122 throw new Error(`HALT docs08-checkbox-state-invalid ${phase}:${id}: cb.state = ${JSON.stringify(cb.state)}`) 1047 throw new Error(`HALT docs08-checkbox-state-invalid ${phase}:${id}: cb.state = ${JSON.stringify(cb.state)}`)
1123 } 1048 }
@@ -1125,7 +1050,6 @@ async function reviewWithFixLoop(id, phase, verifyResult, specPath) { @@ -1125,7 +1050,6 @@ async function reviewWithFixLoop(id, phase, verifyResult, specPath) {
1125 const wr = await agent(writeDocs08CheckboxPromptM(fe, id, phase), {label:`cb:${phase}:${id}`, phase: grp, schema: ACTION_RESULT_SCHEMA}) 1050 const wr = await agent(writeDocs08CheckboxPromptM(fe, id, phase), {label:`cb:${phase}:${id}`, phase: grp, schema: ACTION_RESULT_SCHEMA})
1126 if (!wr.success) throw new Error(`HALT docs08-checkbox-write ${phase}:${id}: ${wr.error || ''}`) 1051 if (!wr.success) throw new Error(`HALT docs08-checkbox-write ${phase}:${id}: ${wr.error || ''}`)
1127 } 1052 }
1128 - // cb.state === 'checked' → 静默跳过(resume 幂等)  
1129 return { id, phase, approved:true, rounds:round } 1053 return { id, phase, approved:true, rounds:round }
1130 } 1054 }
1131 // request-changes 必须带 must-fix 清单(结构化对象数组);否则 fix 步无法定位 → 直接 halt 暴露 reviewer 契约违例。 1055 // request-changes 必须带 must-fix 清单(结构化对象数组);否则 fix 步无法定位 → 直接 halt 暴露 reviewer 契约违例。
@@ -1164,12 +1088,7 @@ async function testGate(module, phase) { @@ -1164,12 +1088,7 @@ async function testGate(module, phase) {
1164 phase('Router') 1088 phase('Router')
1165 const routed = await agent(routerPrompt(ROOT), {label:'router', phase:'Router', schema: ROUTER_SCHEMA}) 1089 const routed = await agent(routerPrompt(ROOT), {label:'router', phase:'Router', schema: ROUTER_SCHEMA})
1166 1090
1167 -// Router 语义运行时断言:后端模块 feItems 必空、frontend-phase 聚合模块 reqs 必空。  
1168 -// schema 用 additionalProperties:false 但不强制互斥;这里把契约违例在最早处暴露而不是让错配 phase 静默跑下去。  
1169 -//  
1170 -// 同时硬约束所有 id 形状为 /^[A-Za-z0-9_-]+$/:下游 micro step prompt 大量把 id / branch / phaseId  
1171 -// 模板进 `git ... ${id}` shell 命令字符串,未单引号包裹也未做字符校验。LLM 返回畸形 id(含 ;、`、  
1172 -// $()、空格等)会改变子代理执行的命令;这里在 Router 出口一次性把关,让 fail-fast 比 shell 注入早。 1091 +// Router 语义断言(feItems/reqs 互斥)+ id 形状硬约束(防 shell 注入:id 直接拼入 `git ... ${id}`)。
1173 const ID_PATTERN = /^[A-Za-z0-9_-]+$/ 1092 const ID_PATTERN = /^[A-Za-z0-9_-]+$/
1174 function assertSafeId(kind, value) { 1093 function assertSafeId(kind, value) {
1175 if (typeof value !== 'string' || !ID_PATTERN.test(value)) { 1094 if (typeof value !== 'string' || !ID_PATTERN.test(value)) {
@@ -1196,8 +1115,6 @@ const results = [] @@ -1196,8 +1115,6 @@ const results = []
1196 let haltedAtIdx = -1 1115 let haltedAtIdx = -1
1197 for (const [idx, module] of todo.entries()) { 1116 for (const [idx, module] of todo.entries()) {
1198 try { 1117 try {
1199 - // C1:进入模块前建/切功能分支(milestone 的 merge 源)。runBranchSetup 把"探测默认分支 /  
1200 - // 校验工作树 / 切或新建分支 / 校验 HEAD"分解为 4-5 个微 agent,分支判定全在 JS 里。  
1201 phase('Milestone') 1118 phase('Milestone')
1202 await runBranchSetup(module) 1119 await runBranchSetup(module)
1203 if (module.reqs.length) { // 后端段(frontend-phase 模块 reqs 为空 → 跳过) 1120 if (module.reqs.length) { // 后端段(frontend-phase 模块 reqs 为空 → 跳过)
@@ -1217,8 +1134,6 @@ for (const [idx, module] of todo.entries()) { @@ -1217,8 +1134,6 @@ for (const [idx, module] of todo.entries()) {
1217 phase('Milestone') 1134 phase('Milestone')
1218 const rep = await agent(reportPrompt(module), {label:`report:${module.id}`, phase:'Milestone', schema: STAGE_RESULT_SCHEMA}) 1135 const rep = await agent(reportPrompt(module), {label:`report:${module.id}`, phase:'Milestone', schema: STAGE_RESULT_SCHEMA})
1219 if (rep.status === 'halt') throw new Error(`HALT report ${module.id}: ${rep.reason || ''}`) 1136 if (rep.status === 'halt') throw new Error(`HALT report ${module.id}: ${rep.reason || ''}`)
1220 - // runMilestone:原 6 步散文(worktree / 默认分支 / merge / docs/08 / tag / report)由 JS 编排,  
1221 - // 每个"已是目标态则跳过"的条件由 JS 在 read 微 agent 的结构化返回上判定,跨重入幂等。  
1222 await runMilestone(module) 1137 await runMilestone(module)
1223 results.push({ module: module.id, status:'done' }) 1138 results.push({ module: module.id, status:'done' })
1224 } catch (e) { 1139 } catch (e) {