Commit bc9f593a3e5becc264c293481e9f8692dea9d5eb

Authored by yanghl
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>
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 &#39;node:fs&#39;
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(&#39;computeUnits: req + fe 合并&#39;, () =&gt; {
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 &lt;root&gt;
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 &lt;root&gt;
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 &lt;root&gt;
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  
... ...