// workflows/coding.mjs // // 整个 ERP Coding(B 阶段)= 一个静默、全自动的 Workflow 脚本。 // // 设计原则(见 docs/superpowers/specs/2026-05-26-workflow-migration-design.md): // - 所有 stage 都是 agent() 子代理,物理上无法 AskUserQuestion → 编码期结构性静默。 // - 缺值不再问人:派生 stage 把具体阻塞点写进产物并 throw(fail-fast,合法 halt → 终止态,非对话框)。 // - 后端 / 前端功能循环由同一份 featureLoop(items, phase) 驱动;phase 切换 reviewer checklist、 // 测试命令、路径作用域(backend/ vs frontend/)、id 格式(REQ-XXX-NNN vs FE-NN)。 // - 状态账本 = docs/08 §二/§三 + git tag;halt 后重跑 coding-start,router 从账本+tag 重算进度。 // - reviewer 统一为 agents/code-reviewer.md,review stage 用 agentType:'code-reviewer'。 // // 运行时约束:Workflow 运行时禁用非确定性内建(Date.now / Math.random 等)。本脚本不调用它们; // 凡需要"当天日期"的产物路径(-.md),一律由子代理在其自身上下文中解析并落盘, // 脚本只负责编排,不计算日期 / 随机数。 export const meta = { name: 'erp-coding', description: 'Run the entire ERP coding phase autonomously and silently: per-module backend+frontend feature loops, test gate, milestone tag.', phases: [ { title: 'Router' }, { title: 'Backend' }, { title: 'Frontend' }, { title: 'Gate' }, { title: 'Milestone' }, ], } const ROUTER_SCHEMA = { type:'object', additionalProperties:false, required:['modules'], properties:{ modules:{ type:'array', items:{ type:'object', additionalProperties:false, required:['id','done','reqs','feItems'], properties:{ id:{type:'string'}, done:{type:'boolean'}, reqs:{type:'array',items:{type:'string'}}, feItems:{type:'array',items:{type:'string'}} } } } } } const REVIEW_SCHEMA = { type:'object', additionalProperties:false, required:['verdict','round','issues'], properties:{ verdict:{type:'string',enum:['approve','request-changes']}, round:{type:'integer'}, issues:{type:'array',items:{type:'string'}} } } const GATE_SCHEMA = { type:'object', additionalProperties:false, required:['status'], properties:{ status:{type:'string',enum:['green','red']}, failures:{type:'array',items:{type:'string'}} } } const ROOT = args?.projectRoot || '.' // ============================================================================ // Stage prompt builders(纯字符串构造;只用 ROOT / id / phase / 入参,不触非确定性内建) // // 每个 prompt 的共同契约(见 commonContract): // - 子代理是非交互的,物理上无法弹窗;缺任何值都不要"问人"——把具体阻塞点写进产物并失败。 // - phase=backend 与 phase=frontend 的差异(路径作用域 / id 形态 / 测试命令来源)逐条写明。 // - 所有输出文档用中文。 // ============================================================================ function isFrontend(phase) { return phase === 'frontend' } // 所有子代理共享的"非交互静默"硬约束。 function commonContract(phase) { const fe = isFrontend(phase) return [ '## 硬约束(非交互子代理)', '- 你是 Workflow 派生的**非交互子代理**,物理上无法弹出 AskUserQuestion / 等待人类输入。**绝不要尝试问人**。', '- 需要具体值时,按此顺序自行获取:(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。', '- 全部输出文档**使用中文**。', `- **阶段 = ${fe ? '前端(frontend)' : '后端(backend)'}**。路径作用域:${fe ? '实现文件必须落在 `frontend/`(或 `docs/09-项目目录结构.md § 前端目录结构` 声明的前端根)下;命中 `backend/` / `sql/` / `scripts/` 即越界,硬停。' : '产出范围限定 controller / service / repository / DTO / 校验 / SQL migration / REST 契约;**禁止**写 `frontend/` 路径下的实现(UI 推迟到前端阶段)。'}`, `- id 形态:${fe ? '前端为 `FE-NN`(业务功能粒度,可关联多个 prototype 区域与多个 REQ)。' : '后端为 `REQ-XXX-NNN`。'}`, ].join('\n') } // Router:读 docs/08 §二/§三 + git tag,重算进度,返回 ROUTER_SCHEMA。 function routerPrompt(root) { return [ '# Coding Router — 从账本重算进度', '', `项目根:\`${root}\``, '', '你是 Coding 阶段的路由子代理。**只读不写**(不改任何代码 / 文档),仅从状态账本重算"哪些模块还要跑",返回结构化结果。', '', '## 读取来源(账本 = docs/08 + git tag,二者一致才算完成)', '1. `docs/08-模块任务管理.md § 二`(后端模块元数据):逐个模块取 `id`(英文蛇形 module id)、本模块的 REQ 列表(按 `docs/02-开发计划.md § 二 开发顺序清单` 的顺序,A5 约束保证同模块 REQ 连续),以及该模块的 `里程碑:` 字段。', '2. `docs/08-模块任务管理.md § 三`(前端阶段元数据):取 `整体里程碑:` 字段,以及 `功能:` 项下所有 `- [ ] FE-NN ...` / `- [x] FE-NN ...` 行(FE 清单)。前端 item 形如 `FE-NN`。', '3. `git -C tag -l "milestone/*"`:列出已打的里程碑 tag。', '', '## 完成判定(每个模块独立)', '- 后端模块 `done = true` 当且仅当:§二 该模块 `里程碑:` 字段 == `milestone/` **且** `git tag -l "milestone/"` 能查到该 tag。任一缺失 → `done = false`。', '- 前端 item(FE-NN)归属一个"逻辑前端模块"。前端阶段整体 `done` 当且仅当 §三 `整体里程碑:` == `milestone/frontend-phase` 且 `git tag -l "milestone/frontend-phase"` 存在。', '', '## 输出(必须符合下发的 JSON schema)', '- `modules`: 数组,按 `docs/02 § 二` 的模块顺序排列。每项:', ' - `id`: 模块标识(后端为英文蛇形 module id;前端聚合为单一逻辑模块时用 `frontend-phase`)。', ' - `done`: 该模块是否已完成(按上面的判定)。', ' - `reqs`: 本模块**未完成**后端 REQ 的有序列表(已 `verdict=approve`(见 `docs/superpowers/reviews/*-.md`)的 REQ 跳过)。模块已 done → 空数组。', ' - `feItems`: 本模块关联的**未完成**前端 FE-NN 列表(已 approve 的 FE 跳过);无前端 → 空数组。', '- 不要返回任何额外字段(schema 为 `additionalProperties:false`)。', '', '## 缺值处理', '- docs/08 §二/§三 缺失 / 格式不符 / 无法解析 → **不要猜**:把具体的解析失败点写入返回前的诊断并使本步骤失败(让 Workflow halt),由人工修复 Plan 产物后重跑 `coding-start`。', ].join('\n') } // ---- 功能内循环 stage 1:派生 spec(原 feature-brainstorm / fe-feature-brainstorm)---- function deriveSpecPrompt(id, phase) { const fe = isFrontend(phase) return [ `# ${fe ? 'fe-feature-brainstorm' : 'feature-brainstorm'} — 派生规格 ${id}`, '', commonContract(phase), '', '## 目标', `静默派生 \`${id}\` 的实现规格(无 Q&A)。需求歧义本应在 Plan 期的结构化 per-REQ 表单 / 前端 scope-lock 锁定;这里**只消费已锁定的事实**,不再澄清。`, '', '## 收集上下文', fe ? [ `- 关联 REQ 卡片:\`${ROOT}/docs/01-需求清单//.md\`(提取业务校验规则、acceptance、UI 描述)。`, `- 关联 prototype:Read \`${ROOT}/prototype/**/*.html\`(含 anchor 时聚焦相应区域),作为页面布局权威。`, `- API 契约:\`${ROOT}/docs/05-API接口契约.md\`,按本 FE 关联的 REQ 过滤出消费的端点。`, `- Design Tokens:\`${ROOT}/docs/06-UI交互规范.md § 二\`(色值 / 状态色引用源)。`, `- 前端组件库:\`${ROOT}/docs/04-技术规范.md § 零\` 的 \`frontend.ui_lib\`,决定组件选型。`, ].join('\n') : [ `- REQ 卡片:\`${ROOT}/docs/01-需求清单//${id}.md\`。**忽略 UI 描述**(控件类型 / 按钮位置 / 列表布局),但校验规则、业务规则仍要落到后端 DTO + service。`, `- 涉及的数据表定义:\`${ROOT}/docs/03-数据库设计文档.md\`(必要时实时查 mysql 只读)。`, `- API 契约:\`${ROOT}/docs/05-API接口契约.md\` 中本 REQ 相关端点。`, ].join('\n'), '', '## 写 spec', `- 落盘 \`${ROOT}/docs/superpowers/specs/<当天日期 YYYY-MM-DD>-${id}.md\`(当天日期由你在自身上下文解析,脚本不传日期)。`, fe ? '- 规格至少含:关联 REQ + 关联原型;组件树(按页面 / 区域分块,推导自 prototype DOM);页面状态机(loading / empty / error / 正常 / 表单提交中 至少 5 态);消费的后端端点(对齐 docs/05);业务规则前端复刻清单(逐条:规则 / 触发时机 / 报错文案 / 来源 REQ);Design Tokens 引用清单(`var(--color-*)`)。' : '- 规格覆盖:goal / 输入输出 / 业务规则 / 约束 / schema / API 引用 / acceptance criteria。', '', '## 自审(inline 修,无须等待)', `- 占位符扫描:\`TBD\` / \`TODO\` / \`【人工填写:】\`${fe ? ' / `controller` / `service` / `SQL` / `migration`(前端 spec 不应出现后端字样)' : ''} → 命中即修;修不掉的缺值按硬约束失败。`, '- 内部一致性 / 范围检查(单 plan 能消化吗)/ 歧义检查(任一 requirement 两种解读 → 挑一个写明)。', '', '## 结束', `- 成功:输出一行 \`${fe ? 'fe-' : ''}feature-brainstorm: ${id} → \`,把该 spec 路径作为本步骤结果返回(供下游 plan stage 使用)。`, '- 不要输出"交给下一步 / 等待检查"之类的桥接叙述。', ].join('\n') } // ---- stage 2:spec → 任务级 TDD 计划(原 feature-plan / fe-feature-plan)---- function planPrompt(id, phase, spec) { const fe = isFrontend(phase) return [ `# ${fe ? 'fe-feature-plan' : 'feature-plan'} — 任务级计划 ${id}`, '', commonContract(phase), '', '## 输入', `- 上游 spec:${spec ? `\`${spec}\`` : `\`${ROOT}/docs/superpowers/specs/<当天日期>-${id}.md\``}(不存在则失败)。`, fe ? `- \`${ROOT}/docs/04-技术规范.md § 一 前端架构\`(路由 / 状态库 / 组件目录约定 / 测试栈);\`${ROOT}/docs/09-项目目录结构.md § 前端目录结构\`(落盘位置)。用 Grep 在 \`${ROOT}/frontend/\` 定位现有文件。` : `- \`${ROOT}/docs/04-技术规范.md\` 与 \`${ROOT}/docs/09-项目目录结构.md\`(编码规范 + 目录规范)。用 Grep 在现有代码定位待修改文件。`, '', '## 计划写作原则', '- Plan 告诉 TDD 执行者**做什么**,不是**怎么写代码**(执行者是同模型、全上下文的 tdd stage)。', `- Plan 锁定**文件边界 + 测试意图 + ${fe ? 'props 契约' : 'API 形状'} + 完成判据**;代码由 TDD 红绿循环产出。`, '- **禁止 dump 整个文件内容**(pom.xml / entity / config / 组件源码)到 plan——避免双 source of truth 漂移。', fe ? '- 每个任务标注"测试先行类型" = **jsdom 组件测试** OR **Playwright E2E**。' : '', '- DRY、YAGNI、TDD、frequent commits。', '', '## 任务结构(每个 task = 一个 red-green-commit 单元,4 step)', '1. 写失败测试(给 `test_file::test_name` + 测试意图);2. 实现最小代码(给 `impl_file`);3. 子会话验证 PASS;4. commit。任务粒度 2-5 分钟。', fe ? `- **硬护栏**:每个任务 \`impl_file\` 必须以 \`frontend/\`(或 docs/09 声明的前端根)开头;命中 \`backend/\` / \`sql/\` / \`scripts/\` → 修正后重渲染。` : `- **硬护栏**:任务粒度限定后端文件(controller / service / repository / DTO / 校验 / SQL migration);**禁止**生成 \`frontend/\` 路径任务。`, '- 允许写死的少数场景:DDL / migration 语句、合同级常量(错误码 / JWT claim / Redis key / 路由 path / API client 签名 / Design Tokens 名)、可选的测试断言 sketch。其余一律散文 + 签名描述。', '- 首次出现的类 / 方法 / 组件 / hook / API client 函数必须给出签名;跨 task 的签名 / 错误码 / props 类型必须一致。', '', '## 写 plan + 自审', `- 落盘 \`${ROOT}/docs/superpowers/plans/<当天日期 YYYY-MM-DD>-${id}.md\`,文件头含 Goal / Architecture / Tech Stack + checkbox 任务。`, '- 自审:占位符扫描(按硬约束清单);spec coverage(spec 每节至少指向一个 task,补 gap);类型一致性(签名 / 方法名 / 错误码 / props 一致)。', '', '## 结束', `- 成功:输出一行 \`${fe ? 'fe-' : ''}feature-plan: ${id} → \`,把该 plan 路径作为结果返回。`, ].filter(Boolean).join('\n') } // ---- stage 3:按 plan 逐任务 TDD(原 feature-tdd / fe-feature-tdd)---- function tddPrompt(id, phase, plan) { const fe = isFrontend(phase) return [ `# ${fe ? 'fe-feature-tdd' : 'feature-tdd'} — 逐任务 TDD ${id}`, '', commonContract(phase), '', '## 输入', `- 计划文件:${plan ? `\`${plan}\`` : `\`${ROOT}/docs/superpowers/plans/<当天日期>-${id}.md\``}(不存在则失败)。`, `- 测试命令来源:\`${ROOT}/docs/04-技术规范.md § 零\`${fe ? ' 的 `frontend.unit_test_runner` / `frontend.e2e_runner` / `frontend.test_command` / `frontend.e2e_command`(缺失则默认 `pnpm test:ci` / `pnpm e2e:ci`)。' : ' 确认的后端测试命令(如 Maven profile / `./scripts/test.mjs`)。'}`, '', '## 流程', fe ? '' : '- **Schema 改动前置**(仅当 plan 声明需要):第一个任务写 migration 文件 `V__.sql`(`` = 现有 `sql/migrations/V*.sql` 最大版本号 + 1,只含 DDL),**同步**把新 CREATE / ALTER 反向更新到 `docs/03-数据库设计文档.md` 对应表小节(docs/03 是 schema 的 SSoT),migration + docs/03 改动同一 commit。', '- 按顺序处理每个代码类任务:(a) 在 `test_file::test_name` 写**失败**测试;(b) **派发 Agent 子会话**跑测试确认失败,子会话只返回 `{command, exit_code, failing_assertion}` JSON;(c) 写**最小**实现使测试通过;(d) 再派子会话确认通过;(e) commit(含 `REQ_ID` / REQ 标签)。', fe ? '- jsdom 类型用 vitest/jest 写组件单测;e2e 类型在 `frontend/e2e/` 写 Playwright(headless)。实现时:色值用 `var(--color-*)`(不硬编码 hex),业务校验按 spec 在 form-level 复刻。' : '', '', '## 护栏', '- **绝不**在主会话直接跑测试(mvn / pnpm / playwright / scripts/test.mjs)——必须通过 Agent 子会话。', fe ? '- **绝不**写非 `frontend/`(或 docs/09 前端根)路径的 `impl_file`;命中 `backend/` / `sql/` / `scripts/` → 硬停并打印 `不允许写非前端文件:`。' : '- **后端阶段路径硬护栏**:任意 `impl_file` 以 `frontend/` 开头 → 硬停并打印 `后端阶段不允许写前端代码:`,不再继续 TDD。', '- 每次 commit 含 REQ/FE 标签,不混合无关改动。', '- **同一测试修复超过 10 次仍失败 → 立即失败(halt)**,把"哪个测试、失败断言、已尝试的修复"写进诊断;**不要**问人、不要无限重试。', '', '## 结束', `- 全部任务通过:输出一行 \`${fe ? 'fe-' : ''}feature-tdd: ${id} 完成\`,把"已实现 + 已 commit"摘要作为结果返回(供 verify stage)。`, ].filter(Boolean).join('\n') } // ---- stage 4:把功能测试派子会话跑,渲染证据(原 feature-verify / fe-feature-verify)---- function verifyPrompt(id, phase, impl) { const fe = isFrontend(phase) return [ `# ${fe ? 'fe-feature-verify' : 'feature-verify'} — 证据验证 ${id}`, '', commonContract(phase), '', '## 目标', `把 \`${id}\` 的功能测试**派发到 Agent 子会话**执行,按结构化结果渲染证据。**主会话从不直接跑测试,也不自由编写证据。**`, impl ? `(上游 TDD 摘要:${impl})` : '', '', '## 流程', fe ? [ `- 测试目标:从 plan 取 \`测试先行类型 = jsdom\` 的 test_file → 拼 vitest/jest 过滤模式;\`= e2e\` 的 → 拼 Playwright spec 过滤模式。命令从 \`${ROOT}/docs/04-技术规范.md § 零 frontend.test_command\` / \`frontend.e2e_command\` 取(缺失默认 \`pnpm test:ci\` / \`pnpm e2e:ci\`)。`, '- 派子会话依次跑 unit + e2e,子会话只返回结构化 JSON:`{ unit:{command,exit_code,passed,failed,failed_list,stdout_excerpt}, e2e:{...同结构} }`(`stdout_excerpt` ≤ 30 行)。', '- **任一目标 `exit_code != 0` 或 `failed > 0`** → 渲染证据后失败,不进入 review。', ].join('\n') : [ `- 测试目标:从 plan 或项目标准命令确定(Maven profile / pnpm script / pytest path / \`${ROOT}/docs/04-技术规范.md § 零\` 的后端命令)。`, '- 派子会话执行,子会话只返回结构化 JSON:`{command, exit_code, passed, failed, failed_list, stdout_excerpt}`(`stdout_excerpt` ≤ 30 行,不塞全文 stdout)。', '- **`exit_code != 0` 或 `failed > 0`** → 渲染证据后失败,不进入 review。', ].join('\n'), `- 证据渲染并打印到会话;如需落盘,写 \`${ROOT}/docs/superpowers/reviews/\` 旁的证据位(沿用项目既有约定)。`, '', '## 结束', `- 全部通过:输出一行 \`${fe ? 'fe-' : ''}feature-verify: ${id} 通过\`,把验证摘要作为结果返回(供 review stage)。`, ].filter(Boolean).join('\n') } // ---- stage 5a:AI 自审 diff(原 feature-review / fe-feature-review)——委托统一 reviewer agent ---- function reviewPrompt(id, phase, round) { const fe = isFrontend(phase) return [ `# ${fe ? 'fe-feature-review' : 'feature-review'} — AI 自审 ${id}(第 ${round} 轮)`, '', commonContract(phase), '', '## 目标', `对 \`${id}\` 本轮引入的代码 diff 做 AI 自审,给出 \`approve\` 或 \`request-changes\` 裁决。`, '', '## 输入给 reviewer', `- 本 ${fe ? 'FE' : 'REQ'} 引入的代码 diff + 规格 \`${ROOT}/docs/superpowers/specs/<当天日期>-${id}.md\`。`, fe ? `- 本 FE 关联的所有 prototype 文件(spec 顶部"关联原型"列表),供对照渲染结构。` : '', `- **phase = ${fe ? 'frontend → 附加前端 7 维 checklist(a11y / 对比度 / 响应式 等);主观维度仅标记明显问题,不因主观判断触发 request-changes(避免非确定性循环耗尽 5 轮)。' : 'backend → 通用代码审查维度(正确性 / 边界 / 错误处理 / 一致性)。'}**`, '', '## 输出(必须符合下发的 REVIEW JSON schema)', `- \`verdict\`: \`approve\` | \`request-changes\`;\`round\`: 整数(本轮 = ${round});\`issues\`: must-fix 问题清单(approve 时可空数组)。`, `- 渲染审阅报告写入 \`${ROOT}/docs/superpowers/reviews/<当天日期 YYYY-MM-DD>-${id}.md\`(\`verdict\` 字段与返回值一致——router / 进度判定靠它)。`, `- approve 时,把 \`${ROOT}/docs/08-模块任务管理.md\` ${fe ? '§ 三' : '§ 二'} 中本 ${fe ? 'FE' : 'REQ'} 的 \`- [ ] ${id} ...\` 改为 \`- [x] ${id} ...\`(功能级可视化;模块完成仍以里程碑 tag 为准)。`, '- 不要返回额外字段(schema 为 `additionalProperties:false`)。', ].filter(Boolean).join('\n') } // ---- stage 5b:按 review must-fix 修复并重新 commit(review 循环的 fix 步)---- function fixPrompt(id, phase, issues) { const fe = isFrontend(phase) const list = Array.isArray(issues) && issues.length ? issues.map((x, i) => ` ${i + 1}. ${x}`).join('\n') : ' (上一轮 review 的 must-fix 清单——见 ' + (fe ? '§三' : '§二') + ' 对应 review 报告)' return [ `# ${fe ? 'fe-feature' : 'feature'} fix — 修复 review must-fix ${id}`, '', commonContract(phase), '', '## 待修复 must-fix', list, '', '## 流程', '- 逐项编辑 must-fix 指向的代码文件(遵守阶段路径作用域护栏)。', `- 修复后 commit:\`fix(): 修复 review must-fix ${fe ? `REQ_ID: ${id}` : id}\`(不混合无关改动)。`, '- 修复完成后本步骤即结束;上层 Workflow 会重新跑 verify + review(下一轮)。', '- **缺值仍不要问人**:按硬约束把阻塞点写进诊断并失败。', '', '## 结束', `- 输出一行 \`${fe ? 'fe-' : ''}feature-fix: ${id} 已修复 ${Array.isArray(issues) ? issues.length : ''} 项\`。`, ].join('\n') } // ---- 测试闸(原 test-gate)---- function gatePrompt(module, phase) { const fe = isFrontend(phase) const id = module?.id ?? '' return [ `# test-gate — ${fe ? '前端阶段' : `模块 ${id}`} 硬测试闸(phase=${phase})`, '', commonContract(phase), '', '## 目标', `打里程碑 tag 前的唯一硬测试门。**派发 Agent 子会话**跑测试,绿则通过,红则失败。**绝不**在主会话直接跑测试,红色时**绝不**跳过。`, '', '## 命令', fe ? `- 前端:命令从 \`${ROOT}/docs/04-技术规范.md § 零 frontend.test_command\` / \`frontend.e2e_command\` 拼接(缺失则 \`pnpm test:ci && pnpm e2e:ci\`),跑 vitest + playwright(含全 FE 回归)。` : `- 后端:跑 \`${ROOT}/scripts/test.mjs\`(跨平台 Node 测试入口;含本模块新增 + 已合并模块回归)。`, '- 子会话只返回结构化 JSON:`{command, exit_code, passed, failed, stdout_excerpt}`(`stdout_excerpt` ≤ 30 行含 FAIL 摘要)。', '', '## 证据 + commit', `- 渲染证据写入 \`${ROOT}/docs/superpowers/module-reports/${fe ? 'frontend-phase' : `${id}`}-test-gate.md\` 并 commit 到当前分支(保证证据随里程碑可审计)。`, '', '## 输出(必须符合下发的 GATE JSON schema)', '- `status`: `green`(`exit_code = 0` 且 `failed = 0`)| `red`;`failures`: 失败用例摘要(green 时可省略 / 空数组)。', '- 不要返回额外字段。**不要在本步骤内自动重试**——重试由上层 Workflow 控制。', ].join('\n') } // ---- 跨模块改动记录(替代被删的 cross-module hook + cross-module-log skill)---- function crossModulePrompt(module) { const id = module?.id ?? '' return [ `# cross-module-log — 记录模块 ${id} 的跨模块改动`, '', commonContract('backend'), '', '## 目标', `替代被删的 \`log-cross-module\` hook + \`cross-module-log\` skill:扫描本模块周期内对**非本模块**文件的改动,落跨模块日志(原因 + 影响评估),供 module-report § ⑦ 嵌入。`, '', '## 流程', `- 用 \`git -C ${ROOT} diff --name-status\`(区间:模块分支起点 → HEAD)找出改动文件,判定哪些落在**其它模块**的目录下(按 docs/09 目录归属)。`, `- 写 / 更新 \`${ROOT}/docs/superpowers/module-reports/${id}-cross-module.md\`,每行列:时间戳(你自身上下文解析当天,脚本不传)/ 目标模块 / 文件 / 改动摘要 / **原因**(本模块哪个 REQ 迫使改它)/ **影响评估**(目标模块哪些 API / 行为 / 调用方受影响、现有测试是否仍有效、是否需新测试,1-3 句)。`, '- 无跨模块改动 → 输出 `cross-module-log: 无跨模块改动,跳过`,不创建文件。', '- **不要留 `TBD(CC 补)`**:本步骤就是补齐的唯一时机;推不出原因/影响 → 按硬约束写阻塞点并失败。', '', '## 结束', `- 输出一行 \`cross-module-log: 模块 ${id} 更新 N 行 / 跳过\`。`, ].join('\n') } // ---- 模块完成报告(原 module-report)---- function reportPrompt(module) { const id = module?.id ?? '' const fe = id === 'frontend-phase' return [ `# module-report — ${fe ? '前端阶段' : `模块 ${id}`} 12 节完成报告`, '', commonContract(fe ? 'frontend' : 'backend'), '', '## 目标', `test-gate 绿后渲染标准化 **12 节**完成报告,commit 到当前分支(供 milestone 标记)。**只读 git 摘要,不读 diff 正文进上下文。**`, '', '## 前置', `- 验证上游 test-gate 已绿:读 \`${ROOT}/docs/superpowers/module-reports/${fe ? 'frontend-phase' : `${id}`}-test-gate.md\`;红则停。`, '', '## 收集输入(取摘要而非正文)', fe ? [ '- § ① `module_id = frontend-phase`,`module_name = 前端阶段(整体)`。', `- § ② "FE 完成清单":扫 \`${ROOT}/docs/superpowers/{specs,plans,reviews}/<日期>-FE-*.md\`,按 FE-NN 顺序列出。`, `- § ③ 文件变更:\`git -C ${ROOT} diff --stat\`(区间 \`frontend-phase\` 分支起点 → HEAD)。`, '- § ④ 数据库使用表 / § ⑥ Migration / § ⑦ 跨模块:填 `N/A(前端阶段)`。', `- § ⑤:读 \`${ROOT}/docs/superpowers/module-reports/frontend-phase-test-gate.md\`。`, '- § ⑧ 偏离清单:额外审查"实际渲染 DOM 与各 FE 关联原型主结构的差异",逐 FE 列出。', '- § ⑪ 下一模块预览:填"上线 / 部署后续步骤"。', ].join('\n') : [ `- § ③ 文件变更:\`git -C ${ROOT} diff --stat\` / \`--name-status\` / \`git log --oneline\`(区间:module 分支起点 → HEAD)。`, `- § ② / § ⑨:读 \`${ROOT}/docs/superpowers/{specs,plans,reviews}/<日期>-<本模块 REQ>.md\`。`, `- § ⑤:读 \`${ROOT}/docs/superpowers/module-reports/${id}-test-gate.md\`。`, `- § ⑥ Migration:\`git -C ${ROOT} diff --name-only --diff-filter=A -- 'sql/migrations/V*.sql'\` 列新增,每个读第一行作说明。`, `- § ⑦ 跨模块改动:读 \`${ROOT}/docs/superpowers/module-reports/${id}-cross-module.md\`(如存在;其中不应再有 \`TBD(CC 补)\`,上一步 cross-module-log 已补齐)。`, '- § ④ 读写的表:grep 定位涉 SQL 文件后按需读片段,**不全量读 docs/03**。', ].join('\n'), '', '## 渲染 + 验证 + commit', '- 渲染 12 节。硬验证:§ ⑧ 必须列举所有偏离(无则写"无偏离")。', `- 写入 \`${ROOT}/docs/superpowers/module-reports/<当天日期 YYYY-MM-DD>-${fe ? 'frontend-phase' : `${id}`}.md\`,连同跨模块日志(如存在)一起 commit 到当前分支(milestone 的 worktree-clean 前置依赖此 commit)。`, '', '## 结束', `- 输出一行 \`module-report: ${fe ? 'frontend-phase' : id} → \`。`, ].join('\n') } // ---- 里程碑:本地 merge --no-ff + tag + 回写 docs/08(原 milestone-tag,单 stage 内幂等)---- function milestonePrompt(module) { const id = module?.id ?? '' const fe = id === 'frontend-phase' const phaseId = fe ? 'frontend-phase' : id return [ `# milestone-tag — ${fe ? '前端阶段' : `模块 ${id}`} 本地集成 + 打里程碑(幂等)`, '', commonContract(fe ? 'frontend' : 'backend'), '', '## 目标', `把当前分支(${fe ? '`frontend-phase`' : `\`module-${id}\``})本地合并进默认分支并打 \`milestone/${phaseId}\` tag,把 tag 名回写 docs/08 + 报告 § ⑫。**全程无人工介入**;本 stage 内**重入幂等**(先写 docs/08,再打 tag,已存在则跳过)。`, '', '## 流程(顺序执行,任一硬错误 → 停下打印诊断,不自动 stash / 覆盖 / --abort)', '1. **验证 worktree 干净**:`git -C ' + ROOT + ' status --porcelain` 非空 → 失败并打印 dirty 文件清单(检查 test-gate / module-report 是否都已 commit)。', `2. **探测默认分支**:用 \`git -C ${ROOT} rev-parse --verify\` 依次试本地 \`main\` / \`master\`,取第一个存在的为 \`default_branch\`;都不存在 → 失败。`, `3. **本地集成**:\`git -C ${ROOT} checkout \` 后 \`git -C ${ROOT} merge --no-ff ${fe ? 'frontend-phase' : `module-${id}`} -m "merge(${phaseId}): integrate ${fe ? 'frontend-phase' : `module-${id}`}"\`。合并冲突 → 失败并打印冲突文件清单(引导人工解决后重跑 coding-start)。`, `4. **回写 docs/08 + commit**:在 default_branch 上 Edit \`${ROOT}/docs/08-模块任务管理.md\`:${fe ? '§ 三 `- 整体里程碑: —` 改为 `- 整体里程碑: milestone/frontend-phase`' : `§ 二 该模块 \` - 里程碑: —\` 改为 \` - 里程碑: milestone/${id}\``};commit \`chore(${phaseId}): record milestone/${phaseId} in docs/08\`。`, `5. **打 annotated tag**(幂等):\`git -C ${ROOT} tag -a milestone/${phaseId} -m "milestone(${phaseId}): ${fe ? '前端' : '后端'}阶段完成"\`;tag 已存在则跳过。`, `6. **追加 tag 到报告 § ⑫**:Edit 当天报告 \`${ROOT}/docs/superpowers/module-reports/<日期>-${phaseId}.md\` 的 § ⑫,把 \`{{milestone_tag}}\` 替换为 \`milestone/${phaseId}\`(已替换则跳过);commit \`docs(${phaseId}): record milestone/${phaseId} in completion report\`。`, '', '## 结束', `- 输出一行 \`milestone-tag: ${phaseId} → milestone/${phaseId}\`。不要在本 stage 内回调 coding-start——推进下一模块由上层 Workflow 的循环负责。`, ].join('\n') } // ============================================================================ // 编排逻辑(结构按 plan 骨架;featureLoop / reviewWithFixLoop / testGate / 顶层循环) // ============================================================================ // ---- 单功能链(后端 / 前端同构)---- async function featureLoop(items, phase) { return pipeline(items, (id) => agent(deriveSpecPrompt(id, phase), {label:`spec:${phase}:${id}`, phase: phase==='backend'?'Backend':'Frontend'}), (spec, id) => agent(planPrompt(id, phase, spec), {label:`plan:${phase}:${id}`, phase: phase==='backend'?'Backend':'Frontend'}), (plan, id) => agent(tddPrompt(id, phase, plan), {label:`tdd:${phase}:${id}`, phase: phase==='backend'?'Backend':'Frontend'}), (impl, id) => agent(verifyPrompt(id, phase, impl), {label:`verify:${phase}:${id}`, phase: phase==='backend'?'Backend':'Frontend'}), (v, id) => reviewWithFixLoop(id, phase, v), ) } // 有界 5 轮修复;超出 → throw(终止态,非对话框) async function reviewWithFixLoop(id, phase, verifyResult) { for (let round = 1; round <= 5; round++) { const r = await agent(reviewPrompt(id, phase, round), {label:`review:${phase}:${id}:r${round}`, phase: phase==='backend'?'Backend':'Frontend', schema: REVIEW_SCHEMA, agentType:'code-reviewer'}) if (r.verdict === 'approve') return { id, phase, approved:true, rounds:round } await agent(fixPrompt(id, phase, r.issues), {label:`fix:${phase}:${id}:r${round}`, phase: phase==='backend'?'Backend':'Frontend'}) } throw new Error(`HALT review-unresolved ${phase}:${id} after 5 rounds`) } async function testGate(module, phase) { let g = await agent(gatePrompt(module, phase), {label:`gate:${phase}:${module.id}`, phase:'Gate', schema: GATE_SCHEMA}) if (g.status === 'red') { // 自动重试 1 次(防 flaky) g = await agent(gatePrompt(module, phase) + '\n(retry once for flakiness)', {label:`gate-retry:${phase}:${module.id}`, phase:'Gate', schema: GATE_SCHEMA}) } if (g.status === 'red') throw new Error(`HALT test-gate-red ${phase}:${module.id}: ${(g.failures||[]).join('; ')}`) return g } phase('Router') const routed = await agent(routerPrompt(ROOT), {label:'router', phase:'Router', schema: ROUTER_SCHEMA}) const todo = routed.modules.filter(m => !m.done) log(`coding: ${todo.length}/${routed.modules.length} modules to run`) const results = [] for (const module of todo) { try { await featureLoop(module.reqs, 'backend') await testGate(module, 'backend') if (module.feItems.length) { await featureLoop(module.feItems, 'frontend'); await testGate(module, 'frontend') } await agent(crossModulePrompt(module), {label:`xmod:${module.id}`, phase:'Milestone'}) // 替代被删 hook await agent(reportPrompt(module), {label:`report:${module.id}`, phase:'Milestone'}) await agent(milestonePrompt(module), {label:`milestone:${module.id}`, phase:'Milestone'}) // git merge --no-ff + tag + 更新 docs/08(单 stage 内幂等) results.push({ module: module.id, status:'done' }) } catch (e) { results.push({ module: module.id, status:'halted', reason: String(e.message||e) }) break // 整阶段 fail-fast:halt 后停,等人工修复后重跑 coding-start } } // Workflow 结果:跑完 / halt 的逐模块摘要。 // 注:Workflow 运行时在异步包装上下文中执行脚本体,顶层 `return` 即为结果(与 `export const meta` // 并存)。这是 Workflow 脚本的契约,**不是**独立 ESM 模块——因此 `node --check` 会报 Illegal // return statement,但运行时正确(不要据 node --check 改成 export default,那会让结果丢失)。 return { results }