Commit bc9f593a3e5becc264c293481e9f8692dea9d5eb
1 parent
094033f2
req-ledger: 跟踪 prototype/ 原型快照——修复 add-req 检测不到原型变动
add-req 的变更检测只哈希 docs/01 REQ 卡片与 docs/08 §三 FE 行文字,从不读 prototype/,而原型才是前端布局/页面/交互的权威。改原型但 FE 行文字不变时 台账无变化 → Router 跳过前端 → 原型改动永不重编。 - req-ledger.mjs:collectProtoUnit 把全部 prototype/**/*.html 聚成单一 __prototype__ 单元(kind=proto),relpath 归一+排序保证跨平台哈希稳定; 无 .html 不产生该单元。接入 computeUnits。 - add-req SKILL:步骤 1 说明 kind=proto + 老项目升级建基线语义;步骤 5 新增 proto 作废分支(删全部 req-done/FE-* + milestone/frontend-phase + 复位 §三里程碑,整体重跑前端阶段);横幅与 frontmatter 同步。 - README:lib 说明加 prototype。 - 测试:新增两用例证明改/删原型内容能被 diff 识别(FE 行文字不变也检测到)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Showing
4 changed files
with
49 additions
and
5 deletions
README.md
| ... | ... | @@ -121,7 +121,7 @@ erp-workflow-plugin/ |
| 121 | 121 | │ ├── validate-ddl.mjs # docs/03 ↔ DDL 4 维校验(替代 validate.sh) |
| 122 | 122 | │ ├── yaml-config.mjs # config-vars.yaml 极简 YAML 读取(2 层 map + 标量) |
| 123 | 123 | │ ├── apply-ddl.mjs # 解析 config-vars.yaml database: 段 + mysql2 apply |
| 124 | -│ ├── req-ledger.mjs # 需求台账:docs/01 REQ + docs/08 §三 FE 内容哈希,scan/commit 识别新增/变更(add-req 用) | |
| 124 | +│ ├── req-ledger.mjs # 需求台账:docs/01 REQ + docs/08 §三 FE + prototype/ 原型快照 内容哈希,scan/commit 识别新增/变更(add-req 用) | |
| 125 | 125 | │ └── *.test.mjs # 各助手的 node:test 单测 |
| 126 | 126 | ├── agents/ |
| 127 | 127 | │ └── code-reviewer.md # 统一 reviewer(coding.mjs review stage 调用,phase 选维度集) | ... | ... |
lib/req-ledger.mjs
No preview for this file type
lib/req-ledger.test.mjs
| ... | ... | @@ -5,8 +5,8 @@ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs' |
| 5 | 5 | import { tmpdir } from 'node:os' |
| 6 | 6 | import { join } from 'node:path' |
| 7 | 7 | import { |
| 8 | - hashContent, collectReqUnits, collectFeUnits, computeUnits, | |
| 9 | - loadLedger, writeLedger, diffLedger, | |
| 8 | + hashContent, collectReqUnits, collectFeUnits, collectProtoUnit, computeUnits, | |
| 9 | + loadLedger, writeLedger, diffLedger, PROTO_UNIT_ID, | |
| 10 | 10 | } from './req-ledger.mjs' |
| 11 | 11 | |
| 12 | 12 | // 搭一个最小项目目录:docs/01-需求清单/<mod>/{_module.md,index.md,<req>.md} + docs/08 |
| ... | ... | @@ -55,6 +55,42 @@ test('computeUnits: req + fe 合并', () => { |
| 55 | 55 | } finally { rmSync(root, { recursive: true, force: true }) } |
| 56 | 56 | }) |
| 57 | 57 | |
| 58 | +test('collectProtoUnit: 无 prototype/ → 空;有 .html → 单一 __prototype__ 单元', () => { | |
| 59 | + const root = scaffold() | |
| 60 | + try { | |
| 61 | + assert.equal(collectProtoUnit(root).size, 0) // scaffold 无 prototype/ | |
| 62 | + const protoDir = join(root, 'prototype', 'pages') | |
| 63 | + mkdirSync(protoDir, { recursive: true }) | |
| 64 | + writeFileSync(join(root, 'prototype', 'index.html'), '<h1>首页</h1>') | |
| 65 | + writeFileSync(join(protoDir, 'login.html'), '<form>登录</form>') | |
| 66 | + const u = collectProtoUnit(root) | |
| 67 | + assert.deepEqual([...u.keys()], [PROTO_UNIT_ID]) | |
| 68 | + assert.equal(u.get(PROTO_UNIT_ID).kind, 'proto') | |
| 69 | + } finally { rmSync(root, { recursive: true, force: true }) } | |
| 70 | +}) | |
| 71 | + | |
| 72 | +test('原型内容改动被 diff 识别为 changed(FE 行文字不变也能检测到)', () => { | |
| 73 | + const root = scaffold() | |
| 74 | + try { | |
| 75 | + writeFileSync(join(root, 'docs', '01-需求清单', 'index.md'), '# 索引\n') // 占位,无关 | |
| 76 | + mkdirSync(join(root, 'prototype'), { recursive: true }) | |
| 77 | + const proto = join(root, 'prototype', 'order.html') | |
| 78 | + writeFileSync(proto, '<div>订单页 v1</div>') | |
| 79 | + writeLedger(root, computeUnits(root)) // 建基线(含原型快照) | |
| 80 | + | |
| 81 | + // 只改原型内容,docs/08 §三 FE 行文字一字不动 | |
| 82 | + writeFileSync(proto, '<div>订单页 v2 改了布局</div>') | |
| 83 | + const diff = diffLedger(loadLedger(root), computeUnits(root)) | |
| 84 | + assert.deepEqual(diff.changed.map((x) => x.id), [PROTO_UNIT_ID]) | |
| 85 | + assert.equal(diff.changed[0].kind, 'proto') | |
| 86 | + | |
| 87 | + // 删除原型文件 → removed | |
| 88 | + rmSync(proto) | |
| 89 | + const diff2 = diffLedger(loadLedger(root), computeUnits(root)) | |
| 90 | + assert.deepEqual(diff2.removed.map((x) => x.id), [PROTO_UNIT_ID]) | |
| 91 | + } finally { rmSync(root, { recursive: true, force: true }) } | |
| 92 | +}) | |
| 93 | + | |
| 58 | 94 | test('diffLedger: 台账缺失 → 全部算 new', () => { |
| 59 | 95 | const root = scaffold() |
| 60 | 96 | try { | ... | ... |
skills/plan/add-req/SKILL.md
| 1 | 1 | --- |
| 2 | 2 | name: add-req |
| 3 | -description: 增量需求入口——初始 Plan 完结后追加 / 修改需求时运行。基于 lib/req-ledger.mjs 内容哈希台账识别新增 / 变更的 REQ 卡片与 FE 行,只对增量做 A3-delta(写 V_n migration + 同步 docs/03,绝不改 V1)+ A5-delta(补 docs/05 端点 / docs/02 顺序 / docs/08 模块行),并作废变更单元已有的 req-done/milestone tag,使 coding.mjs Router 只重跑增量、不必整仓重跑。 | |
| 3 | +description: 增量需求入口——初始 Plan 完结后追加 / 修改需求时运行。基于 lib/req-ledger.mjs 内容哈希台账识别新增 / 变更的 REQ 卡片、FE 行与原型快照(prototype/**/*.html),只对增量做 A3-delta(写 V_n migration + 同步 docs/03,绝不改 V1)+ A5-delta(补 docs/05 端点 / docs/02 顺序 / docs/08 模块行),并作废变更单元已有的 req-done/milestone tag(原型变更则整体重跑前端阶段),使 coding.mjs Router 只重跑增量、不必整仓重跑。 | |
| 4 | 4 | user-invocable: true |
| 5 | 5 | allowed-tools: Read Write Edit Grep Glob AskUserQuestion Bash(node *) Bash(git *) Bash(ls *) Bash(mkdir *) |
| 6 | 6 | --- |
| ... | ... | @@ -51,7 +51,9 @@ node ${CLAUDE_PLUGIN_ROOT}/lib/req-ledger.mjs scan <root> |
| 51 | 51 | |
| 52 | 52 | ## 步骤 1:检测增量 |
| 53 | 53 | |
| 54 | -用步骤 0 已得到的 scan 输出解析 `new[]` / `changed[]` / `removed[]`(每项 `{id, kind}`,kind = `req` 后端卡片 / `fe` 前端功能行)。 | |
| 54 | +用步骤 0 已得到的 scan 输出解析 `new[]` / `changed[]` / `removed[]`(每项 `{id, kind}`,kind = `req` 后端卡片 / `fe` 前端功能行 / `proto` 原型快照——全部 `prototype/**/*.html` 聚成的单一单元,id 固定 `__prototype__`)。 | |
| 55 | + | |
| 56 | +> **原型变更(kind=proto)**:原型是前端布局/页面/交互的权威,但 FE 行只哈希行文字,改原型而 FE 行文字不变时旧台账检测不到。`__prototype__` 单元把所有原型 html 内容纳入哈希,任一原型文件改动即被识别。**老项目首次升级到带原型跟踪的台账时**,`__prototype__` 会作为 `new` 出现(无历史哈希可比)——此为基线建立,步骤 5 不作废任何 tag,步骤 6 提交后才成基线;其后再改原型才会显示为 `changed` 并触发前端重跑。 | |
| 55 | 57 | |
| 56 | 58 | - `new` 与 `changed` 均为空 → 打印 `[add-req] 无新增 / 变更需求,无需处理。[ERP-HALT]` **停下**(不提交台账)。 |
| 57 | 59 | - `removed` 非空 → **仅提示、不自动删**:打印 `检测到台账登记但 docs 已移除的单元:<列出>。下线需求请人工同步 docs/02/03/05 并删除对应 tag,本 skill 不自动执行删除。` 然后继续处理 new/changed(removed 不写回台账,留待人工,下次仍会提示)。 |
| ... | ... | @@ -107,6 +109,11 @@ node ${CLAUDE_PLUGIN_ROOT}/lib/req-ledger.mjs scan <root> |
| 107 | 109 | - `git -C <root> tag -l "req-done/<FE-NN>"` 存在 → 删。 |
| 108 | 110 | - 前端阶段若已 `milestone/frontend-phase` → 删该 tag,并 `Edit` `docs/08 §三` `整体里程碑:` 复位 `—`。 |
| 109 | 111 | |
| 112 | +- **kind=proto**(`__prototype__`,原型快照变更):原型是整个前端的权威,FE 行与具体原型文件无显式映射,且前端在 Coding 阶段本就是单一 `frontend-phase` 里程碑——故任一原型文件改动按「前端阶段需整体重跑」处理: | |
| 113 | + - 删**全部**前端功能完成 tag:`git -C <root> tag -l "req-done/FE-*"` 列出后逐个 `git -C <root> tag -d <tag>`(Router 据 `req-done/<FE-NN>` 缺失把 FE 收回 `frontend-phase`,单删 milestone 不够)。 | |
| 114 | + - `milestone/frontend-phase` 存在 → `git -C <root> tag -d milestone/frontend-phase`,并 `Edit` `docs/08 §三` `整体里程碑:` 复位 `—`。 | |
| 115 | + - (若曾开 `frontendOverlap`:同时 `git -C <root> tag -l "fe-code-done/FE-*"` 逐个删,复位 phase1 重叠状态。缺省关时无此 tag,跳过。) | |
| 116 | + | |
| 110 | 117 | 逐条记录已删的 tag,供步骤 6 横幅汇总。 |
| 111 | 118 | |
| 112 | 119 | ## 步骤 6:更新台账 + git 提交全部增量产物 + 完成横幅 |
| ... | ... | @@ -134,6 +141,7 @@ node ${CLAUDE_PLUGIN_ROOT}/lib/req-ledger.mjs scan <root> |
| 134 | 141 | 新增 REQ:<列出 id 或 无> |
| 135 | 142 | 变更 REQ:<列出 id 或 无> |
| 136 | 143 | 新增 FE :<列出 FE-NN 或 无> |
| 144 | + 原型变更:<是(前端阶段整体重跑)/ 否> | |
| 137 | 145 | 新增 migration:<V_n 文件名 或 无> |
| 138 | 146 | 作废 tag:<列出已删的 req-done/* 与 milestone/* 或 无> |
| 139 | 147 | ... | ... |