Commit ad94075f1112a5117b1c28a2ef1e161dba74e45d

Authored by zichun
1 parent 0956071b

coding.mjs/add-req: 审计简化收敛——bestEffortAction、RESUME 去双写、单次 scan

- bestEffortAction(prompt, {label, phase, okMsg, failTag, errTag}):收敛 recordResume /
  ensureLedgerBaseline 逐行同构的 try/catch 包裹(失败/异常只 log 绝不阻断主流程;
  ≠ runAction:不经 adjudicate、绝不 halt)。日志文案与重构前逐字一致(errTag 区分
  失败/异常两条前缀),返回布尔供水位线判断。
- decisionDigestMd helper 消除决策摘要模板的字节级双份拷贝;flushedCount 水位线
  (仅 per-module flush 成功才推进)使 loop-end 条目只补「未被逐模块条目覆盖的增量」
  ——N 模块全绿不再每条决策在 RESUME.md 写两遍;flush 失败时 loop-end 兜底补记保留。
- add-req 步骤 0/1 合并为单次 scan(scan 输出本就同时含 ledgerExists 与
  new/changed/removed,重复扫描纯冗余);删步骤 6 无操作性说明尾句。
skills/plan/add-req/SKILL.md
@@ -47,15 +47,11 @@ node ${CLAUDE_PLUGIN_ROOT}/lib/req-ledger.mjs scan <root> @@ -47,15 +47,11 @@ node ${CLAUDE_PLUGIN_ROOT}/lib/req-ledger.mjs scan <root>
47 [ERP-HALT] 基线已建立,等待人工追加需求。 47 [ERP-HALT] 基线已建立,等待人工追加需求。
48 ``` 48 ```
49 (理由:首跑若把存量全当新增,会重复生成已有的 docs/03/05 工件并误重跑整仓。基线建立后,后续改动才能被准确 diff。) 49 (理由:首跑若把存量全当新增,会重复生成已有的 docs/03/05 工件并误重跑整仓。基线建立后,后续改动才能被准确 diff。)
50 -- `true` → 进入步骤 1 50 +- `true` → 直接用本次 scan 的输出进入步骤 1 解析(不再重跑 scan)
51 51
52 ## 步骤 1:检测增量 52 ## 步骤 1:检测增量
53 53
54 -```  
55 -node ${CLAUDE_PLUGIN_ROOT}/lib/req-ledger.mjs scan <root>  
56 -```  
57 -  
58 -解析 `new[]` / `changed[]` / `removed[]`(每项 `{id, kind}`,kind = `req` 后端卡片 / `fe` 前端功能行)。 54 +用步骤 0 已得到的 scan 输出解析 `new[]` / `changed[]` / `removed[]`(每项 `{id, kind}`,kind = `req` 后端卡片 / `fe` 前端功能行)。
59 55
60 - `new` 与 `changed` 均为空 → 打印 `[add-req] 无新增 / 变更需求,无需处理。[ERP-HALT]` **停下**(不提交台账)。 56 - `new` 与 `changed` 均为空 → 打印 `[add-req] 无新增 / 变更需求,无需处理。[ERP-HALT]` **停下**(不提交台账)。
61 - `removed` 非空 → **仅提示、不自动删**:打印 `检测到台账登记但 docs 已移除的单元:<列出>。下线需求请人工同步 docs/02/03/05 并删除对应 tag,本 skill 不自动执行删除。` 然后继续处理 new/changed(removed 不写回台账,留待人工,下次仍会提示)。 57 - `removed` 非空 → **仅提示、不自动删**:打印 `检测到台账登记但 docs 已移除的单元:<列出>。下线需求请人工同步 docs/02/03/05 并删除对应 tag,本 skill 不自动执行删除。` 然后继续处理 new/changed(removed 不写回台账,留待人工,下次仍会提示)。
@@ -126,7 +122,7 @@ node ${CLAUDE_PLUGIN_ROOT}/lib/req-ledger.mjs scan &lt;root&gt; @@ -126,7 +122,7 @@ node ${CLAUDE_PLUGIN_ROOT}/lib/req-ledger.mjs scan &lt;root&gt;
126 git -C <root> commit -m "plan(add-req): 增量需求 <新增/变更 id 摘要>(docs/03+05+02+08 delta + V_n + 台账)" 122 git -C <root> commit -m "plan(add-req): 增量需求 <新增/变更 id 摘要>(docs/03+05+02+08 delta + V_n + 台账)"
127 ``` 123 ```
128 - 覆盖范围 = 步骤 2~5 实际改动的全部文件:人工新写的 REQ 卡(docs/01)、新增 migration(sql/migrations/V_n)、同步的 docs/03、补的 docs/05/02 端点与顺序、docs/08 模块行/FE 行与复位的里程碑字段、`.req-ledger.json`。 124 - 覆盖范围 = 步骤 2~5 实际改动的全部文件:人工新写的 REQ 卡(docs/01)、新增 migration(sql/migrations/V_n)、同步的 docs/03、补的 docs/05/02 端点与顺序、docs/08 模块行/FE 行与复位的里程碑字段、`.req-ledger.json`。
129 - - 步骤 5 的 `git tag -d`(删 req-done/milestone)是对 ref 的操作,不产生工作树文件,不需进本 commit;但删 tag 应在本次 commit **之前或之后**都可(与文件提交独立) 125 + - 步骤 5 的 `git tag -d`(删 req-done/milestone)是对 ref 的操作,不产生工作树文件,不需进本 commit
130 - commit 后用 `git -C <root> status --porcelain` 复核工作树**干净**(无残留);若仍有未跟踪/未提交项,排查后补提交,确保交给 coding-start 时是干净树。 126 - commit 后用 `git -C <root> status --porcelain` 复核工作树**干净**(无残留);若仍有未跟踪/未提交项,排查后补提交,确保交给 coding-start 时是干净树。
131 127
132 3. 然后打印横幅并**停下**(不自动进编码,与「Plan 完不自动进 B」一致): 128 3. 然后打印横幅并**停下**(不自动进编码,与「Plan 完不自动进 B」一致):
workflows/coding.mjs
@@ -924,6 +924,9 @@ const adjGuidance = (g) =&gt; g ? `\n\n## 隞脰迤誘嚗甈⊿ @@ -924,6 +924,9 @@ const adjGuidance = (g) =&gt; g ? `\n\n## 隞脰迤誘嚗甈⊿
924 924
925 // 瘚銝餃蝑敹tage 蝻箏潭銝恕/閫粉嚗霈啣甇歹犖撌乩恣 925 // 瘚銝餃蝑敹tage 蝻箏潭銝恕/閫粉嚗霈啣甇歹犖撌乩恣
926 const autonomousDecisions = [] 926 const autonomousDecisions = []
  927 +const decisionDigestMd = (list, emptyText) => list.length
  928 + ? list.map(d => ` - [\`${d.site}\`] ${d.question || '?'} ${d.choice || '?'}嚗${d.confidence || '?'}嚗).join('\n')
  929 + : ` - ${emptyText}`
927 function recordDecisions(site, decisions) { 930 function recordDecisions(site, decisions) {
928 if (!Array.isArray(decisions)) return 931 if (!Array.isArray(decisions)) return
929 for (const d of decisions) { 932 for (const d of decisions) {
@@ -1036,6 +1039,18 @@ async function runAction(makePrompt, { site, grp, label, allowContinue = false } @@ -1036,6 +1039,18 @@ async function runAction(makePrompt, { site, grp, label, allowContinue = false }
1036 throw new Error(`HALT ${site}-adjudication-exhausted: ${ADJUDICATE_MAX} 頧桐輔鋆閫`) 1039 throw new Error(`HALT ${site}-adjudication-exhausted: ${ADJUDICATE_MAX} 頧桐輔鋆閫`)
1037 } 1040 }
1038 1041
  1042 +// best-effort 敺格郊撉斤ㄨ嚗仃韐/撘虜 log 蝏銝餅偌雿瑪餈嚗// runAction嚗 helper 銝 adjudicate halt
  1043 +async function bestEffortAction(prompt, { label, phase: ph, okMsg, failTag, errTag = failTag }) {
  1044 + try {
  1045 + const r = await agent(prompt, { label, phase: ph, schema: ACTION_RESULT_SCHEMA })
  1046 + if (r && r.success) { log(okMsg); return true }
  1047 + log(`${failTag}憭梯揖嚗嚗${(r && r.error) || ''}`)
  1048 + } catch (e) {
  1049 + log(`${errTag}撘虜嚗嚗${String(e?.message || e)}`)
  1050 + }
  1051 + return false
  1052 +}
  1053 +
1039 // 蝏剛 handoff嚗ESUME.md嚗alt / 摰餈賢頝刻扇敶 1054 // 蝏剛 handoff嚗ESUME.md嚗alt / 摰餈賢頝刻扇敶
1040 // 餈漲潔 git tag嚗ilestone/req-done嚗+ 撌 commit module-reports / specs 撌乩辣 1055 // 餈漲潔 git tag嚗ilestone/req-done嚗+ 撌 commit module-reports / specs 撌乩辣
1041 // 餈歇霈拍′銝剜 Router 甇&蝏剛er-feature 蝑撌乩辣ESUME.md 銵亦 1056 // 餈歇霈拍′銝剜 Router 甇&蝏剛er-feature 蝑撌乩辣ESUME.md 銵亦
@@ -1064,11 +1079,8 @@ function resumeJournalPromptM(sectionMd) { @@ -1064,11 +1079,8 @@ function resumeJournalPromptM(sectionMd) {
1064 } 1079 }
1065 async function recordResume(sectionMd) { 1080 async function recordResume(sectionMd) {
1066 // best-effort嚗賒頝敹仃韐亦銝餅蛹 resume 嚗砲 halt嚗 1081 // best-effort嚗賒頝敹仃韐亦銝餅蛹 resume 嚗砲 halt嚗
1067 - try {  
1068 - const r = await agent(resumeJournalPromptM(sectionMd), { label:'resume-journal', phase:'Milestone', schema: ACTION_RESULT_SCHEMA })  
1069 - if (r && r.success) log('resume-journal 撌脰蕭 RESUME.md')  
1070 - else log(`resume-journal 憭梯揖嚗嚗${(r && r.error) || ''}`)  
1071 - } catch (e) { log(`resume-journal 撘虜嚗嚗${String(e?.message || e)}`) } 1082 + return bestEffortAction(resumeJournalPromptM(sectionMd),
  1083 + { label: 'resume-journal', phase: 'Milestone', okMsg: 'resume-journal 撌脰蕭 RESUME.md', failTag: 'resume-journal ', errTag: 'resume-journal ' })
1072 } 1084 }
1073 1085
1074 // 瘙韐血蝥選1#3嚗oding 擐 .req-ledger.json 蝻箏仃嚗撱箇僎漱 1086 // 瘙韐血蝥選1#3嚗oding 擐 .req-ledger.json 蝻箏仃嚗撱箇僎漱
@@ -1091,11 +1103,8 @@ function ledgerBaselinePromptM() { @@ -1091,11 +1103,8 @@ function ledgerBaselinePromptM() {
1091 } 1103 }
1092 async function ensureLedgerBaseline() { 1104 async function ensureLedgerBaseline() {
1093 if (!PLUGIN) { log('req-ledger 蝥輯歲餈 pluginRoot嚗 /add-req 擐'); return } 1105 if (!PLUGIN) { log('req-ledger 蝥輯歲餈 pluginRoot嚗 /add-req 擐'); return }
1094 - try {  
1095 - const r = await agent(ledgerBaselinePromptM(), { label:'req-ledger-baseline', phase:'Router', schema: ACTION_RESULT_SCHEMA })  
1096 - if (r && r.success) log('req-ledger 蝥踹停蝏')  
1097 - else log(`req-ledger 蝥踹仃韐伐嚗${(r && r.error) || ''}`)  
1098 - } catch (e) { log(`req-ledger 蝥踹虜嚗嚗${String(e?.message || e)}`) } 1106 + return bestEffortAction(ledgerBaselinePromptM(),
  1107 + { label: 'req-ledger-baseline', phase: 'Router', okMsg: 'req-ledger 蝥踹停蝏', failTag: 'req-ledger 蝥' })
1099 } 1108 }
1100 function preflightPromptM() { 1109 function preflightPromptM() {
1101 return [ 1110 return [
@@ -2141,6 +2150,7 @@ if (todo.length) { @@ -2141,6 +2150,7 @@ if (todo.length) {
2141 2150
2142 const results = [] 2151 const results = []
2143 let haltedAtIdx = -1 2152 let haltedAtIdx = -1
  2153 +let flushedCount = 0
2144 for (const [idx, module] of todo.entries()) { 2154 for (const [idx, module] of todo.entries()) {
2145 const decStart = autonomousDecisions.length // 璅∪銝餃蝑偌雿瑪嚗1#1 憓 flush 嚗 2155 const decStart = autonomousDecisions.length // 璅∪銝餃蝑偌雿瑪嚗1#1 憓 flush 嚗
2146 try { 2156 try {
@@ -2187,16 +2197,14 @@ for (const [idx, module] of todo.entries()) { @@ -2187,16 +2197,14 @@ for (const [idx, module] of todo.entries()) {
2187 // P1#1 憓 flush嚗葵璅∪摰餈賢 RESUME.md嚗蝙**蝖砌葉**嚗◤嚗銝蜓敺芰撠橘 2197 // P1#1 憓 flush嚗葵璅∪摰餈賢 RESUME.md嚗蝙**蝖砌葉**嚗◤嚗銝蜓敺芰撠橘
2188 // 銋憭歇摰芋鈭銝餃挽 loop-end est-effort嚗 2198 // 銋憭歇摰芋鈭銝餃挽 loop-end est-effort嚗
2189 { 2199 {
2190 - const md = autonomousDecisions.slice(decStart)  
2191 - const digest = md.length  
2192 - ? md.map(d => ` - [\`${d.site}\`] ${d.question || '?'} ${d.choice || '?'}嚗${d.confidence || '?'}嚗).join('\n')  
2193 - : ' - 嚗璅∪銝駁恕霈啣'  
2194 - await recordResume([ 2200 + const moduleDecisions = autonomousDecisions.slice(decStart)
  2201 + const ok = await recordResume([
2195 '## 璅∪ `' + module.id + '` milestone/' + module.id + '嚗<ts>嚗', 2202 '## 璅∪ `' + module.id + '` milestone/' + module.id + '嚗<ts>嚗',
2196 '', 2203 '',
2197 '- **璅∪銝駁恕蝑**嚗撩潭圾霂鳴秤挽嚗', 2204 '- **璅∪銝駁恕蝑**嚗撩潭圾霂鳴秤挽嚗',
2198 - digest, 2205 + decisionDigestMd(moduleDecisions, '嚗璅∪銝駁恕霈啣'),
2199 ].join('\n')) 2206 ].join('\n'))
  2207 + if (ok) flushedCount = autonomousDecisions.length // 隞餈偌雿瑪嚗lush 憭梯揖 loop-end ‘霈
2200 } 2208 }
2201 } catch (e) { 2209 } catch (e) {
2202 const reason = String(e.message || e) 2210 const reason = String(e.message || e)
@@ -2221,16 +2229,14 @@ const pending = haltedAtIdx &gt;= 0 @@ -2221,16 +2229,14 @@ const pending = haltedAtIdx &gt;= 0
2221 // 雿蹂活 coding-start 憭活銝箔 halt / 鈭銝餃挽 / 餈鈭芋 2229 // 雿蹂活 coding-start 憭活銝箔 halt / 鈭銝餃挽 / 餈鈭芋
2222 // 蝖砌葉嚗◤嚗銝銝蝘 halt 霈堆 tag+撌乩辣撌脣 Router 蝏剛 2230 // 蝖砌葉嚗◤嚗銝銝蝘 halt 霈堆 tag+撌乩辣撌脣 Router 蝏剛
2223 const halted = results.find(r => r.status === 'halted') 2231 const halted = results.find(r => r.status === 'halted')
2224 -const decDigest = autonomousDecisions.length  
2225 - ? autonomousDecisions.map(d => ` - [\`${d.site}\`] ${d.question || '?'} ${d.choice || '?'}嚗${d.confidence || '?'}嚗).join('\n')  
2226 - : ' - 嚗甈∟銝駁恕霈啣' 2232 +const decDigest = decisionDigestMd(autonomousDecisions.slice(flushedCount), '嚗霈啣銝駁恕嚗歇摰芋蝑芋嚗')
2227 if (halted) { 2233 if (halted) {
2228 const pend = pending.length ? pending.map(p => `\`${p.module}\``).join('') : '' 2234 const pend = pending.length ? pending.map(p => `\`${p.module}\``).join('') : ''
2229 await recordResume([ 2235 await recordResume([
2230 '## HALT 璅∪ `' + halted.module + '`嚗<ts>嚗', 2236 '## HALT 璅∪ `' + halted.module + '`嚗<ts>嚗',
2231 '', 2237 '',
2232 `- **halt **嚗${halted.reason || '(蝛)'}`, 2238 `- **halt **嚗${halted.reason || '(蝛)'}`,
2233 - '- **甈∟銝駁恕蝑**嚗撩潭圾霂鳴秤挽嚗窈憭嚗', 2239 + '- **甈∟銝駁恕蝑**嚗鋡恍芋閬歇摰芋蝑芋嚗窈憭嚗',
2234 decDigest, 2240 decDigest,
2235 `- **halt 頝芋**嚗${pend}`, 2241 `- **halt 頝芋**嚗${pend}`,
2236 '- **銝甇**嚗犖撌乩耨憭憛 `/erp-workflow:coding-start`嚗outer git tag 蝏剛歇摰芋頝唾', 2242 '- **銝甇**嚗犖撌乩耨憭憛 `/erp-workflow:coding-start`嚗outer git tag 蝏剛歇摰芋頝唾',
@@ -2241,7 +2247,7 @@ if (halted) { @@ -2241,7 +2247,7 @@ if (halted) {
2241 '## 摰<ts>嚗', 2247 '## 摰<ts>嚗',
2242 '', 2248 '',
2243 `- **甈∪芋**嚗${doneList}`, 2249 `- **甈∪芋**嚗${doneList}`,
2244 - '- **甈∟銝駁恕蝑**嚗', 2250 + '- **甈∟銝駁恕蝑**嚗鋡恍芋閬',
2245 decDigest, 2251 decDigest,
2246 ].join('\n')) 2252 ].join('\n'))
2247 } 2253 }