Commit 477f158718d0b2e416e6f628346716151479d075
1 parent
4c3fb21b
feat(coding): coding.mjs Workflow + unified code-reviewer + thin coding-start entry
Showing
3 changed files
with
702 additions
and
0 deletions
agents/code-reviewer.md
0 → 100644
| 1 | +--- | |
| 2 | +name: code-reviewer | |
| 3 | +description: | | |
| 4 | + Unified code reviewer for the ERP coding Workflow. Invoked by `workflows/coding.mjs` at the review stage via `agentType:'code-reviewer'` (see `reviewWithFixLoop`). Reviews a single completed feature against its plan, spec, and the project's coding standards. The `phase` parameter selects the dimension set: `phase=backend` runs the generic review dimensions; `phase=frontend` additionally runs the frontend 7-dimension checklist. Runs as a non-interactive subagent inside a bounded review/fix loop (max 5 rounds) — it MUST return a structured verdict and never block on a prompt. | |
| 5 | +model: inherit | |
| 6 | +--- | |
| 7 | + | |
| 8 | +You are a Senior Code Reviewer reviewing a single completed feature for the ERP coding Workflow. You are invoked non-interactively by `workflows/coding.mjs` (`agentType:'code-reviewer'`) inside a **bounded review/fix loop (max 5 rounds)**. You MUST return a structured verdict — never ask the user a question, never block on input. The `phase` parameter you receive (`backend` or `frontend`) and the round number determine your scope. | |
| 9 | + | |
| 10 | +## Output contract (required) | |
| 11 | + | |
| 12 | +Return a structured result matching the workflow's `REVIEW_SCHEMA`: | |
| 13 | + | |
| 14 | +- `verdict`: `approve` or `request-changes` | |
| 15 | +- `round`: the integer round number you were given | |
| 16 | +- `issues`: array of strings — each a concrete, actionable must-fix (empty when `verdict` is `approve`) | |
| 17 | + | |
| 18 | +Each `issues[]` entry should be self-contained: `file:line — what is wrong — how to fix`. Optionally acknowledge what was done well in prose before the structured result, but the structured result is what the workflow consumes. | |
| 19 | + | |
| 20 | +## Decision discipline (avoid non-deterministic loops) | |
| 21 | + | |
| 22 | +Because the loop is bounded to 5 rounds, you MUST be deterministic and stable: | |
| 23 | + | |
| 24 | +- Only return `request-changes` for **objective, verifiable defects** (broken behavior, plan/spec deviation, missing functionality, contract mismatch, security/perf defect, hard-coded values that violate a documented rule). | |
| 25 | +- Do **not** re-litigate the same code differently between rounds. If you approved a dimension in an earlier round, keep it approved unless the code changed. | |
| 26 | +- Subjective / best-effort dimensions (see frontend §3 a11y/contrast and §4 responsive below) **never** become the sole basis for `request-changes`. Flag obvious failures as suggestions only; if the only findings are subjective, return `approve`. | |
| 27 | +- Style/preference items are `Suggestions` (nice-to-have), not must-fix. | |
| 28 | + | |
| 29 | +## Generic review dimensions (all phases) | |
| 30 | + | |
| 31 | +1. **Plan Alignment** | |
| 32 | + - Compare the implementation against the feature's plan / step description and spec (`docs/01` REQ card, `docs/03`, `docs/05`). | |
| 33 | + - Identify deviations from the planned approach, architecture, or requirements; assess whether each is a justified improvement or a problematic departure. | |
| 34 | + - Verify all planned functionality for this feature is implemented. | |
| 35 | + | |
| 36 | +2. **Code Quality** | |
| 37 | + - Adherence to established patterns and conventions. | |
| 38 | + - Proper error handling, type safety, defensive programming. | |
| 39 | + - Code organization, naming, maintainability. | |
| 40 | + - Test coverage and quality of test implementations. | |
| 41 | + - Potential security vulnerabilities or performance issues. | |
| 42 | + | |
| 43 | +3. **Architecture & Design** | |
| 44 | + - SOLID principles and established architectural patterns. | |
| 45 | + - Proper separation of concerns and loose coupling. | |
| 46 | + - Clean integration with existing systems; scalability/extensibility considerations. | |
| 47 | + | |
| 48 | +4. **Documentation & Standards** | |
| 49 | + - Appropriate comments and documentation where the project requires them. | |
| 50 | + - Adherence to project-specific coding standards and conventions. | |
| 51 | + | |
| 52 | +5. **Issue Classification** | |
| 53 | + - Categorize each finding as Critical (must fix → goes in `issues[]`), Important (should fix → `issues[]` if it blocks correctness, else a suggestion), or Suggestion (nice to have → never in `issues[]`). | |
| 54 | + - For each `issues[]` entry, give the specific location and an actionable fix. | |
| 55 | + | |
| 56 | +## When phase=frontend, additionally | |
| 57 | + | |
| 58 | +Apply the frontend 7-dimension checklist **in addition to** the generic dimensions above. Strict scope rules: | |
| 59 | + | |
| 60 | +- Review ONLY frontend code (under `frontend/` or the project's frontend root per `docs/09-项目目录结构.md`). The feature id is `FE-NN`. | |
| 61 | +- Do NOT propose SQL / migration / controller / service / repository / transaction / DTO changes. The backend phase is already merged. If the diff contains backend files, flag a path violation and return `request-changes` with a single must-fix pointing the reviewee back to the backend phase. | |
| 62 | + | |
| 63 | +For each dimension below, classify Critical / Important / Suggestion as above. | |
| 64 | + | |
| 65 | +### 1. Prototype 一致性 (objective → can request-changes) | |
| 66 | +- Compare the rendered DOM structure (inferred from JSX/template) against the FE's `associated_prototypes` (from the spec header; one FE may span multiple prototype files or anchored regions like `prototype/dashboard.html#metrics-section`). | |
| 67 | +- Check per associated prototype / region: top navigation placement, grid columns, primary action button position, key region layout (header / filters / table / pagination). | |
| 68 | +- Allowed: implementation differences (class naming, component library syntax, framework idioms). | |
| 69 | +- Not allowed: structural deviation (e.g., moving the primary action from top-right to bottom-center, dropping a filter region the prototype shows). | |
| 70 | + | |
| 71 | +### 2. Design Tokens (objective → can request-changes) | |
| 72 | +- All color values MUST use `var(--color-*)` per `docs/06 § 二`. Hard-coded hex / rgb → `request-changes` with file:line. | |
| 73 | +- New tokens used in code without registration in `docs/06 § 二` and `tokens.css` → `request-changes`. | |
| 74 | + | |
| 75 | +### 3. 无障碍 (Accessibility) — best-effort, flag-obvious-only | |
| 76 | +- Form controls have `<label>` or `aria-label`. | |
| 77 | +- Interactive elements are keyboard reachable (correct tab order, Enter/Space triggers). | |
| 78 | +- Dangerous operations (delete / irreversible) have a confirmation dialog. | |
| 79 | +- **Color contrast is subjective/best-effort:** flag only obvious, unambiguous failures, and only as a Suggestion. Accessibility findings here **never** become the sole basis for `request-changes` — a missing `<label>` on a control that the spec requires may be a must-fix, but contrast judgments are not. | |
| 80 | + | |
| 81 | +### 4. 响应式 (Responsive) — best-effort, flag-obvious-only | |
| 82 | +- At the minimum resolution declared in `docs/06 § 一通用交互规则`, no horizontal scrollbar. | |
| 83 | +- Critical operations must not depend on hover-only interactions (must work on touch). | |
| 84 | +- **This dimension is subjective/best-effort:** flag only obvious failures, and as a Suggestion. Responsive findings **never** become the sole basis for `request-changes`. | |
| 85 | + | |
| 86 | +### 5. 业务校验前端复刻 (objective → can request-changes) | |
| 87 | +- Cross-reference the spec's "业务规则前端复刻" section. | |
| 88 | +- Each listed business rule must be implemented as form-level validation (not only relying on backend errors). | |
| 89 | +- Error messages must match backend semantics or convey equivalent meaning. | |
| 90 | + | |
| 91 | +### 6. API 调用一致性 (objective → can request-changes) | |
| 92 | +- Endpoints and request/response field names must match `docs/05-API接口契约.md`. | |
| 93 | +- API calls must go through the project's unified API client; raw `fetch` / `axios` with hand-built URLs → `request-changes`. | |
| 94 | + | |
| 95 | +### 7. 状态机覆盖 (objective → can request-changes) | |
| 96 | +- The 5 states from the spec (loading / empty / error / 正常 / 提交中) must each be handled in code. | |
| 97 | +- Missing state handling → `request-changes` for the specific state. | |
| 98 | + | |
| 99 | +## Closing | |
| 100 | + | |
| 101 | +Be thorough but actionable. Always explain WHY a finding matters (e.g., "hard-coded hex breaks token rotation in dark mode"). When you approve, briefly acknowledge what was done well. Then emit the structured `{ verdict, round, issues }` result. | ... | ... |
skills/coding-start/SKILL.md
0 → 100644
| 1 | +--- | |
| 2 | +name: coding-start | |
| 3 | +description: B 阶段(Coding)瘦入口。校验 Plan 终结闸(docs/08 §一 A0~A6 全勾、已在本地默认分支、工作树干净)后,读取 docs/08 §二/§三 概述模块/前端进度,然后调用 workflows/coding.mjs Workflow 在后台全自动、静默地跑完整个编码阶段(后端+前端功能循环、测试闸、里程碑 tag),跑完或 halt 时通知用户。本入口不写任何文件、不做编码决策。 | |
| 4 | +user-invocable: true | |
| 5 | +allowed-tools: Read Glob Workflow | |
| 6 | +--- | |
| 7 | + | |
| 8 | +**所有输出必须使用中文。** | |
| 9 | + | |
| 10 | +你是 B 阶段(Coding)的**瘦入口**。你的唯一职责是:**校验 Plan 终结闸 → 概述进度 → 启动 `workflows/coding.mjs` Workflow**。 | |
| 11 | + | |
| 12 | +编码阶段是**全自动、静默的 Workflow**——其子代理物理上无法弹窗问人。因此本入口**不做任何编码决策、不写任何文件、不调用其他 skill**;全部需求/配置必须已在 Plan 期(A0~A6)锁死。真正的进度判定与 `git tag` 核对由 `coding.mjs` 的 router stage 从 `docs/08` + tag 重算——本入口的进度概述仅为给用户的信息提要。 | |
| 13 | + | |
| 14 | +> 工具约束:本 skill 只允许 `Read` / `Glob` / `Workflow`,**不允许 Bash**。门禁与进度概述全部基于 `docs/08` 文本(其 `里程碑:` 字段已记录每模块/前端阶段的 tag 名),权威的 `git tag -l 'milestone/*'` 核对交由 Workflow 内 router stage 完成。 | |
| 15 | + | |
| 16 | +## 执行步骤 | |
| 17 | + | |
| 18 | +### 步骤 0:打印 B 阶段流程概览(模型直接输出,不用 cat) | |
| 19 | + | |
| 20 | +直接向用户输出以下横幅(逐字输出文本本身,**不要**用任何命令读文件): | |
| 21 | + | |
| 22 | +``` | |
| 23 | +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
| 24 | + [coding-start] B 阶段(Coding)= 一个全自动静默 Workflow | |
| 25 | + | |
| 26 | + Router → 读 docs/08 + git tag,算出未完成模块 | |
| 27 | + 每个模块: | |
| 28 | + 后端功能循环 spec → plan → tdd → verify → review(≤5轮) | |
| 29 | + 后端测试闸 test-gate(RED 自动重试 1 次,仍 RED → halt) | |
| 30 | + 前端功能循环 同一流水线,phase=frontend(FE-NN,限 frontend/) | |
| 31 | + 前端测试闸 test-gate | |
| 32 | + 跨模块记录 → 模块报告 → 里程碑(merge --no-ff + milestone/<id> tag) | |
| 33 | + 任一模块 halt → fail-fast 停在该模块,修复后重跑本入口即可续跑 | |
| 34 | + | |
| 35 | + 全程无 Q&A:缺值表现为带诊断的 halt,不是对话框。 | |
| 36 | +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
| 37 | +``` | |
| 38 | + | |
| 39 | +### 步骤 1:确认 docs/08 存在 | |
| 40 | + | |
| 41 | +用 `Glob` 检查 `docs/08-模块任务管理.md`。 | |
| 42 | +- 不存在 → 输出「⚠️ 项目尚未初始化,请先运行 `/erp-workflow:plan-start`」并**停下**,不启动 Workflow。 | |
| 43 | + | |
| 44 | +### 步骤 2:Plan 终结闸校验(HARD GATE) | |
| 45 | + | |
| 46 | +`Read` `docs/08-模块任务管理.md`,逐项校验,任一不满足即**拦截、不启动 Workflow**: | |
| 47 | + | |
| 48 | +1. **docs/08 § 一 A0~A6 全部勾选** | |
| 49 | + - 读 § 一 进度表,确认 A0/A1/A2/A3/A4/A5/A6(含各自子项)均为 `[x]`。 | |
| 50 | + - 任一未勾 → 缺口:`Plan 未完成(<未勾项>)→ 先运行 /erp-workflow:plan-start`。 | |
| 51 | + | |
| 52 | +2. **当前在本地默认分支(main / master)** | |
| 53 | + - 本入口无 Bash,无法直接查 git。改为信任用户:在放行横幅中**显式要求**用户确认当前已在默认分支。若用户已说明不在默认分支,则拦截。 | |
| 54 | + - (权威的分支/tag 状态由 `coding.mjs` 的 milestone stage 在 merge 时校验。) | |
| 55 | + | |
| 56 | +3. **工作树干净(Plan 产物已 commit)** | |
| 57 | + - 同样无法用 Bash 直接查。在放行横幅中**显式要求**用户确认工作树干净、Plan 产物已提交。 | |
| 58 | + | |
| 59 | +> 第 2/3 项受 `allowed-tools` 限制(无 Bash)无法程序化核对,故以放行横幅中的明确前置要求承担;Workflow 的 milestone stage 在本地 merge 时会再次校验分支与工作树,不干净则该 stage 失败并 halt。 | |
| 60 | + | |
| 61 | +任一缺口 → 输出拦截横幅,逐条列出缺口与回填位置,**停下**,不启动 Workflow: | |
| 62 | + | |
| 63 | +``` | |
| 64 | +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
| 65 | + [coding-start] ⛔ 未满足进入 B 阶段的前置条件 | |
| 66 | + | |
| 67 | + <逐条列出缺口,格式:[项] 缺口描述 → 回填/处理位置> | |
| 68 | + 例:[Plan 进度] A6 前端 scope 未勾 → 先运行 /erp-workflow:plan-start | |
| 69 | + | |
| 70 | + 处理后重新运行 /erp-workflow:coding-start。 | |
| 71 | +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
| 72 | +``` | |
| 73 | + | |
| 74 | +### 步骤 3:概述进度(信息提要) | |
| 75 | + | |
| 76 | +仅当步骤 2 的 § 一 校验通过后,`Read` `docs/08 § 二`(后端模块元数据 + `里程碑:` 字段)与 `§ 三`(前端阶段 `整体里程碑:` 字段),概述: | |
| 77 | + | |
| 78 | +- 后端:每个模块的 `里程碑:` 是否已是 `milestone/<module_id>`(已完成)还是 `—`(待跑)。 | |
| 79 | +- 前端:`§ 三 整体里程碑:` 是否已是 `milestone/frontend-phase`(已完成)还是 `—`(待跑)。 | |
| 80 | + | |
| 81 | +向用户简述「已完成 N 个模块 / 待跑 M 个模块;前端阶段:已完成 / 待跑」。 | |
| 82 | + | |
| 83 | +> 这是基于 docs/08 文本的提要;权威的 `git tag -l 'milestone/*'` 核对与未完成集合的最终判定由 Workflow 的 router stage 完成(router 以 docs/08 + tag 双重为准)。本入口不因提要里看似"全部完成"就跳过启动——是否有事可做由 router 决定。 | |
| 84 | + | |
| 85 | +### 步骤 4:启动 Coding Workflow | |
| 86 | + | |
| 87 | +用 `Workflow` 工具调用编码编排脚本(`<cwd>` 替换为当前项目根的绝对路径): | |
| 88 | + | |
| 89 | +``` | |
| 90 | +Workflow({ | |
| 91 | + scriptPath: "${CLAUDE_PLUGIN_ROOT}/workflows/coding.mjs", | |
| 92 | + args: { projectRoot: "<cwd>" } | |
| 93 | +}) | |
| 94 | +``` | |
| 95 | + | |
| 96 | +### 步骤 5:告知用户已后台启动 | |
| 97 | + | |
| 98 | +启动后向用户输出: | |
| 99 | + | |
| 100 | +``` | |
| 101 | +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
| 102 | + [coding-start] ✅ Coding Workflow 已在后台启动 | |
| 103 | + | |
| 104 | + 进度概述:<步骤 3 概述,如「待跑 3 模块 + 前端阶段」> | |
| 105 | + | |
| 106 | + ⚠️ 请确认(本入口无法程序化核对): | |
| 107 | + • 当前已在本地默认分支(main / master) | |
| 108 | + • 工作树干净,Plan 产物(docs/* + skeleton + DDL)已 commit | |
| 109 | + | |
| 110 | + Workflow 将按模块顺序全自动、静默推进;跑完所有模块或在某模块 | |
| 111 | + halt(测试闸持续 RED / review 5 轮未过 / 缺值阻塞等)时会通知你。 | |
| 112 | + halt 后请按诊断修复,再重新运行 /erp-workflow:coding-start 续跑。 | |
| 113 | +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
| 114 | +``` | |
| 115 | + | |
| 116 | +本 skill 到此结束,**不调用任何下游 skill**。 | |
| 117 | + | |
| 118 | +## 参考 | |
| 119 | + | |
| 120 | +- `docs/08-模块任务管理.md § 一`(A0~A6 Plan 进度,步骤 2 读取) | |
| 121 | +- `docs/08-模块任务管理.md § 二`(后端模块元数据 + 里程碑字段,步骤 3 读取) | |
| 122 | +- `docs/08-模块任务管理.md § 三`(前端阶段整体里程碑,步骤 3 读取) | |
| 123 | +- `workflows/coding.mjs`(B 阶段编排脚本,步骤 4 启动) | |
| 124 | +- `plan-start`(姊妹入口,A 阶段) | |
| 125 | +- `CLAUDE.md`(项目指令) | ... | ... |
workflows/coding.mjs
0 → 100644
| 1 | +// workflows/coding.mjs | |
| 2 | +// | |
| 3 | +// 整个 ERP Coding(B 阶段)= 一个静默、全自动的 Workflow 脚本。 | |
| 4 | +// | |
| 5 | +// 设计原则(见 docs/superpowers/specs/2026-05-26-workflow-migration-design.md): | |
| 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 | +// - 状态账本 = docs/08 §二/§三 + git tag;halt 后重跑 coding-start,router 从账本+tag 重算进度。 | |
| 11 | +// - reviewer 统一为 agents/code-reviewer.md,review stage 用 agentType:'code-reviewer'。 | |
| 12 | +// | |
| 13 | +// 运行时约束:Workflow 运行时禁用非确定性内建(Date.now / Math.random 等)。本脚本不调用它们; | |
| 14 | +// 凡需要"当天日期"的产物路径(<YYYY-MM-DD>-<id>.md),一律由子代理在其自身上下文中解析并落盘, | |
| 15 | +// 脚本只负责编排,不计算日期 / 随机数。 | |
| 16 | + | |
| 17 | +export const meta = { | |
| 18 | + name: 'erp-coding', | |
| 19 | + description: 'Run the entire ERP coding phase autonomously and silently: per-module backend+frontend feature loops, test gate, milestone tag.', | |
| 20 | + phases: [ | |
| 21 | + { title: 'Router' }, { title: 'Backend' }, { title: 'Frontend' }, | |
| 22 | + { title: 'Gate' }, { title: 'Milestone' }, | |
| 23 | + ], | |
| 24 | +} | |
| 25 | + | |
| 26 | +const ROUTER_SCHEMA = { type:'object', additionalProperties:false, | |
| 27 | + required:['modules'], properties:{ modules:{ type:'array', items:{ | |
| 28 | + type:'object', additionalProperties:false, | |
| 29 | + required:['id','done','reqs','feItems'], | |
| 30 | + properties:{ id:{type:'string'}, done:{type:'boolean'}, | |
| 31 | + reqs:{type:'array',items:{type:'string'}}, | |
| 32 | + feItems:{type:'array',items:{type:'string'}} } } } } } | |
| 33 | + | |
| 34 | +const REVIEW_SCHEMA = { type:'object', additionalProperties:false, | |
| 35 | + required:['verdict','round','issues'], properties:{ | |
| 36 | + verdict:{type:'string',enum:['approve','request-changes']}, | |
| 37 | + round:{type:'integer'}, issues:{type:'array',items:{type:'string'}} } } | |
| 38 | + | |
| 39 | +const GATE_SCHEMA = { type:'object', additionalProperties:false, | |
| 40 | + required:['status'], properties:{ status:{type:'string',enum:['green','red']}, | |
| 41 | + failures:{type:'array',items:{type:'string'}} } } | |
| 42 | + | |
| 43 | +const ROOT = args?.projectRoot || '.' | |
| 44 | + | |
| 45 | +// ============================================================================ | |
| 46 | +// Stage prompt builders(纯字符串构造;只用 ROOT / id / phase / 入参,不触非确定性内建) | |
| 47 | +// | |
| 48 | +// 每个 prompt 的共同契约(见 commonContract): | |
| 49 | +// - 子代理是非交互的,物理上无法弹窗;缺任何值都不要"问人"——把具体阻塞点写进产物并失败。 | |
| 50 | +// - phase=backend 与 phase=frontend 的差异(路径作用域 / id 形态 / 测试命令来源)逐条写明。 | |
| 51 | +// - 所有输出文档用中文。 | |
| 52 | +// ============================================================================ | |
| 53 | + | |
| 54 | +function isFrontend(phase) { return phase === 'frontend' } | |
| 55 | + | |
| 56 | +// 所有子代理共享的"非交互静默"硬约束。 | |
| 57 | +function commonContract(phase) { | |
| 58 | + const fe = isFrontend(phase) | |
| 59 | + return [ | |
| 60 | + '## 硬约束(非交互子代理)', | |
| 61 | + '- 你是 Workflow 派生的**非交互子代理**,物理上无法弹出 AskUserQuestion / 等待人类输入。**绝不要尝试问人**。', | |
| 62 | + '- 需要具体值时,按此顺序自行获取:(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。', | |
| 63 | + '- 全部输出文档**使用中文**。', | |
| 64 | + `- **阶段 = ${fe ? '前端(frontend)' : '后端(backend)'}**。路径作用域:${fe | |
| 65 | + ? '实现文件必须落在 `frontend/`(或 `docs/09-项目目录结构.md § 前端目录结构` 声明的前端根)下;命中 `backend/` / `sql/` / `scripts/` 即越界,硬停。' | |
| 66 | + : '产出范围限定 controller / service / repository / DTO / 校验 / SQL migration / REST 契约;**禁止**写 `frontend/` 路径下的实现(UI 推迟到前端阶段)。'}`, | |
| 67 | + `- id 形态:${fe ? '前端为 `FE-NN`(业务功能粒度,可关联多个 prototype 区域与多个 REQ)。' : '后端为 `REQ-XXX-NNN`。'}`, | |
| 68 | + ].join('\n') | |
| 69 | +} | |
| 70 | + | |
| 71 | +// Router:读 docs/08 §二/§三 + git tag,重算进度,返回 ROUTER_SCHEMA。 | |
| 72 | +function routerPrompt(root) { | |
| 73 | + return [ | |
| 74 | + '# Coding Router — 从账本重算进度', | |
| 75 | + '', | |
| 76 | + `项目根:\`${root}\``, | |
| 77 | + '', | |
| 78 | + '你是 Coding 阶段的路由子代理。**只读不写**(不改任何代码 / 文档),仅从状态账本重算"哪些模块还要跑",返回结构化结果。', | |
| 79 | + '', | |
| 80 | + '## 读取来源(账本 = docs/08 + git tag,二者一致才算完成)', | |
| 81 | + '1. `docs/08-模块任务管理.md § 二`(后端模块元数据):逐个模块取 `id`(英文蛇形 module id)、本模块的 REQ 列表(按 `docs/02-开发计划.md § 二 开发顺序清单` 的顺序,A5 约束保证同模块 REQ 连续),以及该模块的 `里程碑:` 字段。', | |
| 82 | + '2. `docs/08-模块任务管理.md § 三`(前端阶段元数据):取 `整体里程碑:` 字段,以及 `功能:` 项下所有 `- [ ] FE-NN ...` / `- [x] FE-NN ...` 行(FE 清单)。前端 item 形如 `FE-NN`。', | |
| 83 | + '3. `git -C <root> tag -l "milestone/*"`:列出已打的里程碑 tag。', | |
| 84 | + '', | |
| 85 | + '## 完成判定(每个模块独立)', | |
| 86 | + '- 后端模块 `done = true` 当且仅当:§二 该模块 `里程碑:` 字段 == `milestone/<module_id>` **且** `git tag -l "milestone/<module_id>"` 能查到该 tag。任一缺失 → `done = false`。', | |
| 87 | + '- 前端 item(FE-NN)归属一个"逻辑前端模块"。前端阶段整体 `done` 当且仅当 §三 `整体里程碑:` == `milestone/frontend-phase` 且 `git tag -l "milestone/frontend-phase"` 存在。', | |
| 88 | + '', | |
| 89 | + '## 输出(必须符合下发的 JSON schema)', | |
| 90 | + '- `modules`: 数组,按 `docs/02 § 二` 的模块顺序排列。每项:', | |
| 91 | + ' - `id`: 模块标识(后端为英文蛇形 module id;前端聚合为单一逻辑模块时用 `frontend-phase`)。', | |
| 92 | + ' - `done`: 该模块是否已完成(按上面的判定)。', | |
| 93 | + ' - `reqs`: 本模块**未完成**后端 REQ 的有序列表(已 `verdict=approve`(见 `docs/superpowers/reviews/*-<REQ>.md`)的 REQ 跳过)。模块已 done → 空数组。', | |
| 94 | + ' - `feItems`: 本模块关联的**未完成**前端 FE-NN 列表(已 approve 的 FE 跳过);无前端 → 空数组。', | |
| 95 | + '- 不要返回任何额外字段(schema 为 `additionalProperties:false`)。', | |
| 96 | + '', | |
| 97 | + '## 缺值处理', | |
| 98 | + '- docs/08 §二/§三 缺失 / 格式不符 / 无法解析 → **不要猜**:把具体的解析失败点写入返回前的诊断并使本步骤失败(让 Workflow halt),由人工修复 Plan 产物后重跑 `coding-start`。', | |
| 99 | + ].join('\n') | |
| 100 | +} | |
| 101 | + | |
| 102 | +// ---- 功能内循环 stage 1:派生 spec(原 feature-brainstorm / fe-feature-brainstorm)---- | |
| 103 | +function deriveSpecPrompt(id, phase) { | |
| 104 | + const fe = isFrontend(phase) | |
| 105 | + return [ | |
| 106 | + `# ${fe ? 'fe-feature-brainstorm' : 'feature-brainstorm'} — 派生规格 ${id}`, | |
| 107 | + '', | |
| 108 | + commonContract(phase), | |
| 109 | + '', | |
| 110 | + '## 目标', | |
| 111 | + `静默派生 \`${id}\` 的实现规格(无 Q&A)。需求歧义本应在 Plan 期的结构化 per-REQ 表单 / 前端 scope-lock 锁定;这里**只消费已锁定的事实**,不再澄清。`, | |
| 112 | + '', | |
| 113 | + '## 收集上下文', | |
| 114 | + fe | |
| 115 | + ? [ | |
| 116 | + `- 关联 REQ 卡片:\`${ROOT}/docs/01-需求清单/<module>/<REQ>.md\`(提取业务校验规则、acceptance、UI 描述)。`, | |
| 117 | + `- 关联 prototype:Read \`${ROOT}/prototype/**/*.html\`(含 anchor 时聚焦相应区域),作为页面布局权威。`, | |
| 118 | + `- API 契约:\`${ROOT}/docs/05-API接口契约.md\`,按本 FE 关联的 REQ 过滤出消费的端点。`, | |
| 119 | + `- Design Tokens:\`${ROOT}/docs/06-UI交互规范.md § 二\`(色值 / 状态色引用源)。`, | |
| 120 | + `- 前端组件库:\`${ROOT}/docs/04-技术规范.md § 零\` 的 \`frontend.ui_lib\`,决定组件选型。`, | |
| 121 | + ].join('\n') | |
| 122 | + : [ | |
| 123 | + `- REQ 卡片:\`${ROOT}/docs/01-需求清单/<module>/${id}.md\`。**忽略 UI 描述**(控件类型 / 按钮位置 / 列表布局),但校验规则、业务规则仍要落到后端 DTO + service。`, | |
| 124 | + `- 涉及的数据表定义:\`${ROOT}/docs/03-数据库设计文档.md\`(必要时实时查 mysql 只读)。`, | |
| 125 | + `- API 契约:\`${ROOT}/docs/05-API接口契约.md\` 中本 REQ 相关端点。`, | |
| 126 | + ].join('\n'), | |
| 127 | + '', | |
| 128 | + '## 写 spec', | |
| 129 | + `- 落盘 \`${ROOT}/docs/superpowers/specs/<当天日期 YYYY-MM-DD>-${id}.md\`(当天日期由你在自身上下文解析,脚本不传日期)。`, | |
| 130 | + fe | |
| 131 | + ? '- 规格至少含:关联 REQ + 关联原型;组件树(按页面 / 区域分块,推导自 prototype DOM);页面状态机(loading / empty / error / 正常 / 表单提交中 至少 5 态);消费的后端端点(对齐 docs/05);业务规则前端复刻清单(逐条:规则 / 触发时机 / 报错文案 / 来源 REQ);Design Tokens 引用清单(`var(--color-*)`)。' | |
| 132 | + : '- 规格覆盖:goal / 输入输出 / 业务规则 / 约束 / schema / API 引用 / acceptance criteria。', | |
| 133 | + '', | |
| 134 | + '## 自审(inline 修,无须等待)', | |
| 135 | + `- 占位符扫描:\`TBD\` / \`TODO\` / \`【人工填写:】\`${fe ? ' / `controller` / `service` / `SQL` / `migration`(前端 spec 不应出现后端字样)' : ''} → 命中即修;修不掉的缺值按硬约束失败。`, | |
| 136 | + '- 内部一致性 / 范围检查(单 plan 能消化吗)/ 歧义检查(任一 requirement 两种解读 → 挑一个写明)。', | |
| 137 | + '', | |
| 138 | + '## 结束', | |
| 139 | + `- 成功:输出一行 \`${fe ? 'fe-' : ''}feature-brainstorm: ${id} → <spec path>\`,把该 spec 路径作为本步骤结果返回(供下游 plan stage 使用)。`, | |
| 140 | + '- 不要输出"交给下一步 / 等待检查"之类的桥接叙述。', | |
| 141 | + ].join('\n') | |
| 142 | +} | |
| 143 | + | |
| 144 | +// ---- stage 2:spec → 任务级 TDD 计划(原 feature-plan / fe-feature-plan)---- | |
| 145 | +function planPrompt(id, phase, spec) { | |
| 146 | + const fe = isFrontend(phase) | |
| 147 | + return [ | |
| 148 | + `# ${fe ? 'fe-feature-plan' : 'feature-plan'} — 任务级计划 ${id}`, | |
| 149 | + '', | |
| 150 | + commonContract(phase), | |
| 151 | + '', | |
| 152 | + '## 输入', | |
| 153 | + `- 上游 spec:${spec ? `\`${spec}\`` : `\`${ROOT}/docs/superpowers/specs/<当天日期>-${id}.md\``}(不存在则失败)。`, | |
| 154 | + fe | |
| 155 | + ? `- \`${ROOT}/docs/04-技术规范.md § 一 前端架构\`(路由 / 状态库 / 组件目录约定 / 测试栈);\`${ROOT}/docs/09-项目目录结构.md § 前端目录结构\`(落盘位置)。用 Grep 在 \`${ROOT}/frontend/\` 定位现有文件。` | |
| 156 | + : `- \`${ROOT}/docs/04-技术规范.md\` 与 \`${ROOT}/docs/09-项目目录结构.md\`(编码规范 + 目录规范)。用 Grep 在现有代码定位待修改文件。`, | |
| 157 | + '', | |
| 158 | + '## 计划写作原则', | |
| 159 | + '- Plan 告诉 TDD 执行者**做什么**,不是**怎么写代码**(执行者是同模型、全上下文的 tdd stage)。', | |
| 160 | + `- Plan 锁定**文件边界 + 测试意图 + ${fe ? 'props 契约' : 'API 形状'} + 完成判据**;代码由 TDD 红绿循环产出。`, | |
| 161 | + '- **禁止 dump 整个文件内容**(pom.xml / entity / config / 组件源码)到 plan——避免双 source of truth 漂移。', | |
| 162 | + fe ? '- 每个任务标注"测试先行类型" = **jsdom 组件测试** OR **Playwright E2E**。' : '', | |
| 163 | + '- DRY、YAGNI、TDD、frequent commits。', | |
| 164 | + '', | |
| 165 | + '## 任务结构(每个 task = 一个 red-green-commit 单元,4 step)', | |
| 166 | + '1. 写失败测试(给 `test_file::test_name` + 测试意图);2. 实现最小代码(给 `impl_file`);3. 子会话验证 PASS;4. commit。任务粒度 2-5 分钟。', | |
| 167 | + fe | |
| 168 | + ? `- **硬护栏**:每个任务 \`impl_file\` 必须以 \`frontend/\`(或 docs/09 声明的前端根)开头;命中 \`backend/\` / \`sql/\` / \`scripts/\` → 修正后重渲染。` | |
| 169 | + : `- **硬护栏**:任务粒度限定后端文件(controller / service / repository / DTO / 校验 / SQL migration);**禁止**生成 \`frontend/\` 路径任务。`, | |
| 170 | + '- 允许写死的少数场景:DDL / migration 语句、合同级常量(错误码 / JWT claim / Redis key / 路由 path / API client 签名 / Design Tokens 名)、可选的测试断言 sketch。其余一律散文 + 签名描述。', | |
| 171 | + '- 首次出现的类 / 方法 / 组件 / hook / API client 函数必须给出签名;跨 task 的签名 / 错误码 / props 类型必须一致。', | |
| 172 | + '', | |
| 173 | + '## 写 plan + 自审', | |
| 174 | + `- 落盘 \`${ROOT}/docs/superpowers/plans/<当天日期 YYYY-MM-DD>-${id}.md\`,文件头含 Goal / Architecture / Tech Stack + checkbox 任务。`, | |
| 175 | + '- 自审:占位符扫描(按硬约束清单);spec coverage(spec 每节至少指向一个 task,补 gap);类型一致性(签名 / 方法名 / 错误码 / props 一致)。', | |
| 176 | + '', | |
| 177 | + '## 结束', | |
| 178 | + `- 成功:输出一行 \`${fe ? 'fe-' : ''}feature-plan: ${id} → <plan path>\`,把该 plan 路径作为结果返回。`, | |
| 179 | + ].filter(Boolean).join('\n') | |
| 180 | +} | |
| 181 | + | |
| 182 | +// ---- stage 3:按 plan 逐任务 TDD(原 feature-tdd / fe-feature-tdd)---- | |
| 183 | +function tddPrompt(id, phase, plan) { | |
| 184 | + const fe = isFrontend(phase) | |
| 185 | + return [ | |
| 186 | + `# ${fe ? 'fe-feature-tdd' : 'feature-tdd'} — 逐任务 TDD ${id}`, | |
| 187 | + '', | |
| 188 | + commonContract(phase), | |
| 189 | + '', | |
| 190 | + '## 输入', | |
| 191 | + `- 计划文件:${plan ? `\`${plan}\`` : `\`${ROOT}/docs/superpowers/plans/<当天日期>-${id}.md\``}(不存在则失败)。`, | |
| 192 | + `- 测试命令来源:\`${ROOT}/docs/04-技术规范.md § 零\`${fe | |
| 193 | + ? ' 的 `frontend.unit_test_runner` / `frontend.e2e_runner` / `frontend.test_command` / `frontend.e2e_command`(缺失则默认 `pnpm test:ci` / `pnpm e2e:ci`)。' | |
| 194 | + : ' 确认的后端测试命令(如 Maven profile / `./scripts/test.mjs`)。'}`, | |
| 195 | + '', | |
| 196 | + '## 流程', | |
| 197 | + fe ? '' : '- **Schema 改动前置**(仅当 plan 声明需要):第一个任务写 migration 文件 `V<n>__<snake_case>.sql`(`<n>` = 现有 `sql/migrations/V*.sql` 最大版本号 + 1,只含 DDL),**同步**把新 CREATE / ALTER 反向更新到 `docs/03-数据库设计文档.md` 对应表小节(docs/03 是 schema 的 SSoT),migration + docs/03 改动同一 commit。', | |
| 198 | + '- 按顺序处理每个代码类任务:(a) 在 `test_file::test_name` 写**失败**测试;(b) **派发 Agent 子会话**跑测试确认失败,子会话只返回 `{command, exit_code, failing_assertion}` JSON;(c) 写**最小**实现使测试通过;(d) 再派子会话确认通过;(e) commit(含 `REQ_ID` / REQ 标签)。', | |
| 199 | + fe | |
| 200 | + ? '- jsdom 类型用 vitest/jest 写组件单测;e2e 类型在 `frontend/e2e/` 写 Playwright(headless)。实现时:色值用 `var(--color-*)`(不硬编码 hex),业务校验按 spec 在 form-level 复刻。' | |
| 201 | + : '', | |
| 202 | + '', | |
| 203 | + '## 护栏', | |
| 204 | + '- **绝不**在主会话直接跑测试(mvn / pnpm / playwright / scripts/test.mjs)——必须通过 Agent 子会话。', | |
| 205 | + fe | |
| 206 | + ? '- **绝不**写非 `frontend/`(或 docs/09 前端根)路径的 `impl_file`;命中 `backend/` / `sql/` / `scripts/` → 硬停并打印 `不允许写非前端文件:<impl_file>`。' | |
| 207 | + : '- **后端阶段路径硬护栏**:任意 `impl_file` 以 `frontend/` 开头 → 硬停并打印 `后端阶段不允许写前端代码:<impl_file>`,不再继续 TDD。', | |
| 208 | + '- 每次 commit 含 REQ/FE 标签,不混合无关改动。', | |
| 209 | + '- **同一测试修复超过 10 次仍失败 → 立即失败(halt)**,把"哪个测试、失败断言、已尝试的修复"写进诊断;**不要**问人、不要无限重试。', | |
| 210 | + '', | |
| 211 | + '## 结束', | |
| 212 | + `- 全部任务通过:输出一行 \`${fe ? 'fe-' : ''}feature-tdd: ${id} 完成\`,把"已实现 + 已 commit"摘要作为结果返回(供 verify stage)。`, | |
| 213 | + ].filter(Boolean).join('\n') | |
| 214 | +} | |
| 215 | + | |
| 216 | +// ---- stage 4:把功能测试派子会话跑,渲染证据(原 feature-verify / fe-feature-verify)---- | |
| 217 | +function verifyPrompt(id, phase, impl) { | |
| 218 | + const fe = isFrontend(phase) | |
| 219 | + return [ | |
| 220 | + `# ${fe ? 'fe-feature-verify' : 'feature-verify'} — 证据验证 ${id}`, | |
| 221 | + '', | |
| 222 | + commonContract(phase), | |
| 223 | + '', | |
| 224 | + '## 目标', | |
| 225 | + `把 \`${id}\` 的功能测试**派发到 Agent 子会话**执行,按结构化结果渲染证据。**主会话从不直接跑测试,也不自由编写证据。**`, | |
| 226 | + impl ? `(上游 TDD 摘要:${impl})` : '', | |
| 227 | + '', | |
| 228 | + '## 流程', | |
| 229 | + fe | |
| 230 | + ? [ | |
| 231 | + `- 测试目标:从 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\`)。`, | |
| 232 | + '- 派子会话依次跑 unit + e2e,子会话只返回结构化 JSON:`{ unit:{command,exit_code,passed,failed,failed_list,stdout_excerpt}, e2e:{...同结构} }`(`stdout_excerpt` ≤ 30 行)。', | |
| 233 | + '- **任一目标 `exit_code != 0` 或 `failed > 0`** → 渲染证据后失败,不进入 review。', | |
| 234 | + ].join('\n') | |
| 235 | + : [ | |
| 236 | + `- 测试目标:从 plan 或项目标准命令确定(Maven profile / pnpm script / pytest path / \`${ROOT}/docs/04-技术规范.md § 零\` 的后端命令)。`, | |
| 237 | + '- 派子会话执行,子会话只返回结构化 JSON:`{command, exit_code, passed, failed, failed_list, stdout_excerpt}`(`stdout_excerpt` ≤ 30 行,不塞全文 stdout)。', | |
| 238 | + '- **`exit_code != 0` 或 `failed > 0`** → 渲染证据后失败,不进入 review。', | |
| 239 | + ].join('\n'), | |
| 240 | + `- 证据渲染并打印到会话;如需落盘,写 \`${ROOT}/docs/superpowers/reviews/\` 旁的证据位(沿用项目既有约定)。`, | |
| 241 | + '', | |
| 242 | + '## 结束', | |
| 243 | + `- 全部通过:输出一行 \`${fe ? 'fe-' : ''}feature-verify: ${id} 通过\`,把验证摘要作为结果返回(供 review stage)。`, | |
| 244 | + ].filter(Boolean).join('\n') | |
| 245 | +} | |
| 246 | + | |
| 247 | +// ---- stage 5a:AI 自审 diff(原 feature-review / fe-feature-review)——委托统一 reviewer agent ---- | |
| 248 | +function reviewPrompt(id, phase, round) { | |
| 249 | + const fe = isFrontend(phase) | |
| 250 | + return [ | |
| 251 | + `# ${fe ? 'fe-feature-review' : 'feature-review'} — AI 自审 ${id}(第 ${round} 轮)`, | |
| 252 | + '', | |
| 253 | + commonContract(phase), | |
| 254 | + '', | |
| 255 | + '## 目标', | |
| 256 | + `对 \`${id}\` 本轮引入的代码 diff 做 AI 自审,给出 \`approve\` 或 \`request-changes\` 裁决。`, | |
| 257 | + '', | |
| 258 | + '## 输入给 reviewer', | |
| 259 | + `- 本 ${fe ? 'FE' : 'REQ'} 引入的代码 diff + 规格 \`${ROOT}/docs/superpowers/specs/<当天日期>-${id}.md\`。`, | |
| 260 | + fe ? `- 本 FE 关联的所有 prototype 文件(spec 顶部"关联原型"列表),供对照渲染结构。` : '', | |
| 261 | + `- **phase = ${fe ? 'frontend → 附加前端 7 维 checklist(a11y / 对比度 / 响应式 等);主观维度仅标记明显问题,不因主观判断触发 request-changes(避免非确定性循环耗尽 5 轮)。' : 'backend → 通用代码审查维度(正确性 / 边界 / 错误处理 / 一致性)。'}**`, | |
| 262 | + '', | |
| 263 | + '## 输出(必须符合下发的 REVIEW JSON schema)', | |
| 264 | + `- \`verdict\`: \`approve\` | \`request-changes\`;\`round\`: 整数(本轮 = ${round});\`issues\`: must-fix 问题清单(approve 时可空数组)。`, | |
| 265 | + `- 渲染审阅报告写入 \`${ROOT}/docs/superpowers/reviews/<当天日期 YYYY-MM-DD>-${id}.md\`(\`verdict\` 字段与返回值一致——router / 进度判定靠它)。`, | |
| 266 | + `- approve 时,把 \`${ROOT}/docs/08-模块任务管理.md\` ${fe ? '§ 三' : '§ 二'} 中本 ${fe ? 'FE' : 'REQ'} 的 \`- [ ] ${id} ...\` 改为 \`- [x] ${id} ...\`(功能级可视化;模块完成仍以里程碑 tag 为准)。`, | |
| 267 | + '- 不要返回额外字段(schema 为 `additionalProperties:false`)。', | |
| 268 | + ].filter(Boolean).join('\n') | |
| 269 | +} | |
| 270 | + | |
| 271 | +// ---- stage 5b:按 review must-fix 修复并重新 commit(review 循环的 fix 步)---- | |
| 272 | +function fixPrompt(id, phase, issues) { | |
| 273 | + const fe = isFrontend(phase) | |
| 274 | + const list = Array.isArray(issues) && issues.length | |
| 275 | + ? issues.map((x, i) => ` ${i + 1}. ${x}`).join('\n') | |
| 276 | + : ' (上一轮 review 的 must-fix 清单——见 ' + (fe ? '§三' : '§二') + ' 对应 review 报告)' | |
| 277 | + return [ | |
| 278 | + `# ${fe ? 'fe-feature' : 'feature'} fix — 修复 review must-fix ${id}`, | |
| 279 | + '', | |
| 280 | + commonContract(phase), | |
| 281 | + '', | |
| 282 | + '## 待修复 must-fix', | |
| 283 | + list, | |
| 284 | + '', | |
| 285 | + '## 流程', | |
| 286 | + '- 逐项编辑 must-fix 指向的代码文件(遵守阶段路径作用域护栏)。', | |
| 287 | + `- 修复后 commit:\`fix(<scope>): 修复 review must-fix ${fe ? `REQ_ID: ${id}` : id}\`(不混合无关改动)。`, | |
| 288 | + '- 修复完成后本步骤即结束;上层 Workflow 会重新跑 verify + review(下一轮)。', | |
| 289 | + '- **缺值仍不要问人**:按硬约束把阻塞点写进诊断并失败。', | |
| 290 | + '', | |
| 291 | + '## 结束', | |
| 292 | + `- 输出一行 \`${fe ? 'fe-' : ''}feature-fix: ${id} 已修复 ${Array.isArray(issues) ? issues.length : ''} 项\`。`, | |
| 293 | + ].join('\n') | |
| 294 | +} | |
| 295 | + | |
| 296 | +// ---- 测试闸(原 test-gate)---- | |
| 297 | +function gatePrompt(module, phase) { | |
| 298 | + const fe = isFrontend(phase) | |
| 299 | + const id = module?.id ?? '<module>' | |
| 300 | + return [ | |
| 301 | + `# test-gate — ${fe ? '前端阶段' : `模块 ${id}`} 硬测试闸(phase=${phase})`, | |
| 302 | + '', | |
| 303 | + commonContract(phase), | |
| 304 | + '', | |
| 305 | + '## 目标', | |
| 306 | + `打里程碑 tag 前的唯一硬测试门。**派发 Agent 子会话**跑测试,绿则通过,红则失败。**绝不**在主会话直接跑测试,红色时**绝不**跳过。`, | |
| 307 | + '', | |
| 308 | + '## 命令', | |
| 309 | + fe | |
| 310 | + ? `- 前端:命令从 \`${ROOT}/docs/04-技术规范.md § 零 frontend.test_command\` / \`frontend.e2e_command\` 拼接(缺失则 \`pnpm test:ci && pnpm e2e:ci\`),跑 vitest + playwright(含全 FE 回归)。` | |
| 311 | + : `- 后端:跑 \`${ROOT}/scripts/test.mjs\`(跨平台 Node 测试入口;含本模块新增 + 已合并模块回归)。`, | |
| 312 | + '- 子会话只返回结构化 JSON:`{command, exit_code, passed, failed, stdout_excerpt}`(`stdout_excerpt` ≤ 30 行含 FAIL 摘要)。', | |
| 313 | + '', | |
| 314 | + '## 证据 + commit', | |
| 315 | + `- 渲染证据写入 \`${ROOT}/docs/superpowers/module-reports/${fe ? 'frontend-phase' : `${id}`}-test-gate.md\` 并 commit 到当前分支(保证证据随里程碑可审计)。`, | |
| 316 | + '', | |
| 317 | + '## 输出(必须符合下发的 GATE JSON schema)', | |
| 318 | + '- `status`: `green`(`exit_code = 0` 且 `failed = 0`)| `red`;`failures`: 失败用例摘要(green 时可省略 / 空数组)。', | |
| 319 | + '- 不要返回额外字段。**不要在本步骤内自动重试**——重试由上层 Workflow 控制。', | |
| 320 | + ].join('\n') | |
| 321 | +} | |
| 322 | + | |
| 323 | +// ---- 跨模块改动记录(替代被删的 cross-module hook + cross-module-log skill)---- | |
| 324 | +function crossModulePrompt(module) { | |
| 325 | + const id = module?.id ?? '<module>' | |
| 326 | + return [ | |
| 327 | + `# cross-module-log — 记录模块 ${id} 的跨模块改动`, | |
| 328 | + '', | |
| 329 | + commonContract('backend'), | |
| 330 | + '', | |
| 331 | + '## 目标', | |
| 332 | + `替代被删的 \`log-cross-module\` hook + \`cross-module-log\` skill:扫描本模块周期内对**非本模块**文件的改动,落跨模块日志(原因 + 影响评估),供 module-report § ⑦ 嵌入。`, | |
| 333 | + '', | |
| 334 | + '## 流程', | |
| 335 | + `- 用 \`git -C ${ROOT} diff --name-status\`(区间:模块分支起点 → HEAD)找出改动文件,判定哪些落在**其它模块**的目录下(按 docs/09 目录归属)。`, | |
| 336 | + `- 写 / 更新 \`${ROOT}/docs/superpowers/module-reports/${id}-cross-module.md\`,每行列:时间戳(你自身上下文解析当天,脚本不传)/ 目标模块 / 文件 / 改动摘要 / **原因**(本模块哪个 REQ 迫使改它)/ **影响评估**(目标模块哪些 API / 行为 / 调用方受影响、现有测试是否仍有效、是否需新测试,1-3 句)。`, | |
| 337 | + '- 无跨模块改动 → 输出 `cross-module-log: 无跨模块改动,跳过`,不创建文件。', | |
| 338 | + '- **不要留 `TBD(CC 补)`**:本步骤就是补齐的唯一时机;推不出原因/影响 → 按硬约束写阻塞点并失败。', | |
| 339 | + '', | |
| 340 | + '## 结束', | |
| 341 | + `- 输出一行 \`cross-module-log: 模块 ${id} 更新 N 行 / 跳过\`。`, | |
| 342 | + ].join('\n') | |
| 343 | +} | |
| 344 | + | |
| 345 | +// ---- 模块完成报告(原 module-report)---- | |
| 346 | +function reportPrompt(module) { | |
| 347 | + const id = module?.id ?? '<module>' | |
| 348 | + const fe = id === 'frontend-phase' | |
| 349 | + return [ | |
| 350 | + `# module-report — ${fe ? '前端阶段' : `模块 ${id}`} 12 节完成报告`, | |
| 351 | + '', | |
| 352 | + commonContract(fe ? 'frontend' : 'backend'), | |
| 353 | + '', | |
| 354 | + '## 目标', | |
| 355 | + `test-gate 绿后渲染标准化 **12 节**完成报告,commit 到当前分支(供 milestone 标记)。**只读 git 摘要,不读 diff 正文进上下文。**`, | |
| 356 | + '', | |
| 357 | + '## 前置', | |
| 358 | + `- 验证上游 test-gate 已绿:读 \`${ROOT}/docs/superpowers/module-reports/${fe ? 'frontend-phase' : `${id}`}-test-gate.md\`;红则停。`, | |
| 359 | + '', | |
| 360 | + '## 收集输入(取摘要而非正文)', | |
| 361 | + fe | |
| 362 | + ? [ | |
| 363 | + '- § ① `module_id = frontend-phase`,`module_name = 前端阶段(整体)`。', | |
| 364 | + `- § ② "FE 完成清单":扫 \`${ROOT}/docs/superpowers/{specs,plans,reviews}/<日期>-FE-*.md\`,按 FE-NN 顺序列出。`, | |
| 365 | + `- § ③ 文件变更:\`git -C ${ROOT} diff --stat\`(区间 \`frontend-phase\` 分支起点 → HEAD)。`, | |
| 366 | + '- § ④ 数据库使用表 / § ⑥ Migration / § ⑦ 跨模块:填 `N/A(前端阶段)`。', | |
| 367 | + `- § ⑤:读 \`${ROOT}/docs/superpowers/module-reports/frontend-phase-test-gate.md\`。`, | |
| 368 | + '- § ⑧ 偏离清单:额外审查"实际渲染 DOM 与各 FE 关联原型主结构的差异",逐 FE 列出。', | |
| 369 | + '- § ⑪ 下一模块预览:填"上线 / 部署后续步骤"。', | |
| 370 | + ].join('\n') | |
| 371 | + : [ | |
| 372 | + `- § ③ 文件变更:\`git -C ${ROOT} diff --stat\` / \`--name-status\` / \`git log --oneline\`(区间:module 分支起点 → HEAD)。`, | |
| 373 | + `- § ② / § ⑨:读 \`${ROOT}/docs/superpowers/{specs,plans,reviews}/<日期>-<本模块 REQ>.md\`。`, | |
| 374 | + `- § ⑤:读 \`${ROOT}/docs/superpowers/module-reports/${id}-test-gate.md\`。`, | |
| 375 | + `- § ⑥ Migration:\`git -C ${ROOT} diff --name-only --diff-filter=A -- 'sql/migrations/V*.sql'\` 列新增,每个读第一行作说明。`, | |
| 376 | + `- § ⑦ 跨模块改动:读 \`${ROOT}/docs/superpowers/module-reports/${id}-cross-module.md\`(如存在;其中不应再有 \`TBD(CC 补)\`,上一步 cross-module-log 已补齐)。`, | |
| 377 | + '- § ④ 读写的表:grep 定位涉 SQL 文件后按需读片段,**不全量读 docs/03**。', | |
| 378 | + ].join('\n'), | |
| 379 | + '', | |
| 380 | + '## 渲染 + 验证 + commit', | |
| 381 | + '- 渲染 12 节。硬验证:§ ⑧ 必须列举所有偏离(无则写"无偏离")。', | |
| 382 | + `- 写入 \`${ROOT}/docs/superpowers/module-reports/<当天日期 YYYY-MM-DD>-${fe ? 'frontend-phase' : `${id}`}.md\`,连同跨模块日志(如存在)一起 commit 到当前分支(milestone 的 worktree-clean 前置依赖此 commit)。`, | |
| 383 | + '', | |
| 384 | + '## 结束', | |
| 385 | + `- 输出一行 \`module-report: ${fe ? 'frontend-phase' : id} → <report path>\`。`, | |
| 386 | + ].join('\n') | |
| 387 | +} | |
| 388 | + | |
| 389 | +// ---- 里程碑:本地 merge --no-ff + tag + 回写 docs/08(原 milestone-tag,单 stage 内幂等)---- | |
| 390 | +function milestonePrompt(module) { | |
| 391 | + const id = module?.id ?? '<module>' | |
| 392 | + const fe = id === 'frontend-phase' | |
| 393 | + const phaseId = fe ? 'frontend-phase' : id | |
| 394 | + return [ | |
| 395 | + `# milestone-tag — ${fe ? '前端阶段' : `模块 ${id}`} 本地集成 + 打里程碑(幂等)`, | |
| 396 | + '', | |
| 397 | + commonContract(fe ? 'frontend' : 'backend'), | |
| 398 | + '', | |
| 399 | + '## 目标', | |
| 400 | + `把当前分支(${fe ? '`frontend-phase`' : `\`module-${id}\``})本地合并进默认分支并打 \`milestone/${phaseId}\` tag,把 tag 名回写 docs/08 + 报告 § ⑫。**全程无人工介入**;本 stage 内**重入幂等**(先写 docs/08,再打 tag,已存在则跳过)。`, | |
| 401 | + '', | |
| 402 | + '## 流程(顺序执行,任一硬错误 → 停下打印诊断,不自动 stash / 覆盖 / --abort)', | |
| 403 | + '1. **验证 worktree 干净**:`git -C ' + ROOT + ' status --porcelain` 非空 → 失败并打印 dirty 文件清单(检查 test-gate / module-report 是否都已 commit)。', | |
| 404 | + `2. **探测默认分支**:用 \`git -C ${ROOT} rev-parse --verify\` 依次试本地 \`main\` / \`master\`,取第一个存在的为 \`default_branch\`;都不存在 → 失败。`, | |
| 405 | + `3. **本地集成**:\`git -C ${ROOT} checkout <default_branch>\` 后 \`git -C ${ROOT} merge --no-ff ${fe ? 'frontend-phase' : `module-${id}`} -m "merge(${phaseId}): integrate ${fe ? 'frontend-phase' : `module-${id}`}"\`。合并冲突 → 失败并打印冲突文件清单(引导人工解决后重跑 coding-start)。`, | |
| 406 | + `4. **回写 docs/08 + commit**:在 default_branch 上 Edit \`${ROOT}/docs/08-模块任务管理.md\`:${fe | |
| 407 | + ? '§ 三 `- 整体里程碑: —` 改为 `- 整体里程碑: milestone/frontend-phase`' | |
| 408 | + : `§ 二 该模块 \` - 里程碑: —\` 改为 \` - 里程碑: milestone/${id}\``};commit \`chore(${phaseId}): record milestone/${phaseId} in docs/08\`。`, | |
| 409 | + `5. **打 annotated tag**(幂等):\`git -C ${ROOT} tag -a milestone/${phaseId} -m "milestone(${phaseId}): ${fe ? '前端' : '后端'}阶段完成"\`;tag 已存在则跳过。`, | |
| 410 | + `6. **追加 tag 到报告 § ⑫**:Edit 当天报告 \`${ROOT}/docs/superpowers/module-reports/<日期>-${phaseId}.md\` 的 § ⑫,把 \`{{milestone_tag}}\` 替换为 \`milestone/${phaseId}\`(已替换则跳过);commit \`docs(${phaseId}): record milestone/${phaseId} in completion report\`。`, | |
| 411 | + '', | |
| 412 | + '## 结束', | |
| 413 | + `- 输出一行 \`milestone-tag: ${phaseId} → milestone/${phaseId}\`。不要在本 stage 内回调 coding-start——推进下一模块由上层 Workflow 的循环负责。`, | |
| 414 | + ].join('\n') | |
| 415 | +} | |
| 416 | + | |
| 417 | +// ============================================================================ | |
| 418 | +// 编排逻辑(结构按 plan 骨架;featureLoop / reviewWithFixLoop / testGate / 顶层循环) | |
| 419 | +// ============================================================================ | |
| 420 | + | |
| 421 | +// ---- 单功能链(后端 / 前端同构)---- | |
| 422 | +async function featureLoop(items, phase) { | |
| 423 | + return pipeline(items, | |
| 424 | + (id) => agent(deriveSpecPrompt(id, phase), {label:`spec:${phase}:${id}`, phase: phase==='backend'?'Backend':'Frontend'}), | |
| 425 | + (spec, id) => agent(planPrompt(id, phase, spec), {label:`plan:${phase}:${id}`, phase: phase==='backend'?'Backend':'Frontend'}), | |
| 426 | + (plan, id) => agent(tddPrompt(id, phase, plan), {label:`tdd:${phase}:${id}`, phase: phase==='backend'?'Backend':'Frontend'}), | |
| 427 | + (impl, id) => agent(verifyPrompt(id, phase, impl), {label:`verify:${phase}:${id}`, phase: phase==='backend'?'Backend':'Frontend'}), | |
| 428 | + (v, id) => reviewWithFixLoop(id, phase, v), | |
| 429 | + ) | |
| 430 | +} | |
| 431 | + | |
| 432 | +// 有界 5 轮修复;超出 → throw(终止态,非对话框) | |
| 433 | +async function reviewWithFixLoop(id, phase, verifyResult) { | |
| 434 | + for (let round = 1; round <= 5; round++) { | |
| 435 | + 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'}) | |
| 436 | + if (r.verdict === 'approve') return { id, phase, approved:true, rounds:round } | |
| 437 | + await agent(fixPrompt(id, phase, r.issues), {label:`fix:${phase}:${id}:r${round}`, phase: phase==='backend'?'Backend':'Frontend'}) | |
| 438 | + } | |
| 439 | + throw new Error(`HALT review-unresolved ${phase}:${id} after 5 rounds`) | |
| 440 | +} | |
| 441 | + | |
| 442 | +async function testGate(module, phase) { | |
| 443 | + let g = await agent(gatePrompt(module, phase), {label:`gate:${phase}:${module.id}`, phase:'Gate', schema: GATE_SCHEMA}) | |
| 444 | + if (g.status === 'red') { // 自动重试 1 次(防 flaky) | |
| 445 | + g = await agent(gatePrompt(module, phase) + '\n(retry once for flakiness)', {label:`gate-retry:${phase}:${module.id}`, phase:'Gate', schema: GATE_SCHEMA}) | |
| 446 | + } | |
| 447 | + if (g.status === 'red') throw new Error(`HALT test-gate-red ${phase}:${module.id}: ${(g.failures||[]).join('; ')}`) | |
| 448 | + return g | |
| 449 | +} | |
| 450 | + | |
| 451 | +phase('Router') | |
| 452 | +const routed = await agent(routerPrompt(ROOT), {label:'router', phase:'Router', schema: ROUTER_SCHEMA}) | |
| 453 | +const todo = routed.modules.filter(m => !m.done) | |
| 454 | +log(`coding: ${todo.length}/${routed.modules.length} modules to run`) | |
| 455 | + | |
| 456 | +const results = [] | |
| 457 | +for (const module of todo) { | |
| 458 | + try { | |
| 459 | + await featureLoop(module.reqs, 'backend') | |
| 460 | + await testGate(module, 'backend') | |
| 461 | + if (module.feItems.length) { await featureLoop(module.feItems, 'frontend'); await testGate(module, 'frontend') } | |
| 462 | + await agent(crossModulePrompt(module), {label:`xmod:${module.id}`, phase:'Milestone'}) // 替代被删 hook | |
| 463 | + await agent(reportPrompt(module), {label:`report:${module.id}`, phase:'Milestone'}) | |
| 464 | + await agent(milestonePrompt(module), {label:`milestone:${module.id}`, phase:'Milestone'}) // git merge --no-ff + tag + 更新 docs/08(单 stage 内幂等) | |
| 465 | + results.push({ module: module.id, status:'done' }) | |
| 466 | + } catch (e) { | |
| 467 | + results.push({ module: module.id, status:'halted', reason: String(e.message||e) }) | |
| 468 | + break // 整阶段 fail-fast:halt 后停,等人工修复后重跑 coding-start | |
| 469 | + } | |
| 470 | +} | |
| 471 | + | |
| 472 | +// Workflow 结果:跑完 / halt 的逐模块摘要。 | |
| 473 | +// 注:plan 骨架原文是顶层 `return { results }`;裸 top-level return 在独立 ESM 模块下 | |
| 474 | +// 无法通过 `node --check`(Illegal return statement),故改为 `export default`—— | |
| 475 | +// Workflow 运行时读取模块的默认导出作为结果,语义等价、结构其余部分与骨架逐行一致。 | |
| 476 | +export default { results } | ... | ... |