Commit 105a2e5c69725734f3fad79546758dfafb8d5524
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:'object', additionalProperties:false, | @@ -38,12 +21,7 @@ const ROUTER_SCHEMA = { type:'object', 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:'object', additionalProperties:false, | @@ -56,13 +34,7 @@ const REVIEW_SCHEMA = { type:'object', 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:'object', additionalProperties:false, | @@ -75,11 +47,6 @@ const GATE_SCHEMA = { type:'object', 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:'object', additionalProperties:false, | @@ -91,30 +58,20 @@ const DEFAULT_BRANCH_SCHEMA = { type:'object', 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:'object', additionalProperties:false, | @@ -150,20 +107,12 @@ const ACTION_RESULT_SCHEMA = { type:'object', 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) { |