Commit ad94075f1112a5117b1c28a2ef1e161dba74e45d
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 无操作性说明尾句。
Showing
2 changed files
with
30 additions
and
28 deletions
skills/plan/add-req/SKILL.md
| ... | ... | @@ -47,15 +47,11 @@ node ${CLAUDE_PLUGIN_ROOT}/lib/req-ledger.mjs scan <root> |
| 47 | 47 | [ERP-HALT] 基线已建立,等待人工追加需求。 |
| 48 | 48 | ``` |
| 49 | 49 | (理由:首跑若把存量全当新增,会重复生成已有的 docs/03/05 工件并误重跑整仓。基线建立后,后续改动才能被准确 diff。) |
| 50 | -- `true` → 进入步骤 1。 | |
| 50 | +- `true` → 直接用本次 scan 的输出进入步骤 1 解析(不再重跑 scan)。 | |
| 51 | 51 | |
| 52 | 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 | 56 | - `new` 与 `changed` 均为空 → 打印 `[add-req] 无新增 / 变更需求,无需处理。[ERP-HALT]` **停下**(不提交台账)。 |
| 61 | 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 <root> |
| 126 | 122 | git -C <root> commit -m "plan(add-req): 增量需求 <新增/变更 id 摘要>(docs/03+05+02+08 delta + V_n + 台账)" |
| 127 | 123 | ``` |
| 128 | 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 | 126 | - commit 后用 `git -C <root> status --porcelain` 复核工作树**干净**(无残留);若仍有未跟踪/未提交项,排查后补提交,确保交给 coding-start 时是干净树。 |
| 131 | 127 | |
| 132 | 128 | 3. 然后打印横幅并**停下**(不自动进编码,与「Plan 完不自动进 B」一致): | ... | ... |
workflows/coding.mjs
| ... | ... | @@ -924,6 +924,9 @@ const adjGuidance = (g) => g ? `\n\n## 隞脰迤誘嚗甈⊿ |
| 924 | 924 | |
| 925 | 925 | // 瘚銝餃蝑敹tage 蝻箏潭銝恕/閫粉嚗霈啣甇歹犖撌乩恣 |
| 926 | 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 | 930 | function recordDecisions(site, decisions) { |
| 928 | 931 | if (!Array.isArray(decisions)) return |
| 929 | 932 | for (const d of decisions) { |
| ... | ... | @@ -1036,6 +1039,18 @@ async function runAction(makePrompt, { site, grp, label, allowContinue = false } |
| 1036 | 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 | 1054 | // 蝏剛 handoff嚗ESUME.md嚗alt / 摰餈賢頝刻扇敶 |
| 1040 | 1055 | // 餈漲潔 git tag嚗ilestone/req-done嚗+ 撌 commit module-reports / specs 撌乩辣 |
| 1041 | 1056 | // 餈歇霈拍′銝剜 Router 甇&蝏剛er-feature 蝑撌乩辣ESUME.md 銵亦 |
| ... | ... | @@ -1064,11 +1079,8 @@ function resumeJournalPromptM(sectionMd) { |
| 1064 | 1079 | } |
| 1065 | 1080 | async function recordResume(sectionMd) { |
| 1066 | 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 | 1086 | // 瘙韐血蝥選1#3嚗oding 擐 .req-ledger.json 蝻箏仃嚗撱箇僎漱 |
| ... | ... | @@ -1091,11 +1103,8 @@ function ledgerBaselinePromptM() { |
| 1091 | 1103 | } |
| 1092 | 1104 | async function ensureLedgerBaseline() { |
| 1093 | 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 | 1109 | function preflightPromptM() { |
| 1101 | 1110 | return [ |
| ... | ... | @@ -2141,6 +2150,7 @@ if (todo.length) { |
| 2141 | 2150 | |
| 2142 | 2151 | const results = [] |
| 2143 | 2152 | let haltedAtIdx = -1 |
| 2153 | +let flushedCount = 0 | |
| 2144 | 2154 | for (const [idx, module] of todo.entries()) { |
| 2145 | 2155 | const decStart = autonomousDecisions.length // 璅∪銝餃蝑偌雿瑪嚗1#1 憓 flush 嚗 |
| 2146 | 2156 | try { |
| ... | ... | @@ -2187,16 +2197,14 @@ for (const [idx, module] of todo.entries()) { |
| 2187 | 2197 | // P1#1 憓 flush嚗葵璅∪摰餈賢 RESUME.md嚗蝙**蝖砌葉**嚗◤嚗銝蜓敺芰撠橘 |
| 2188 | 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 | 2202 | '## 璅∪ `' + module.id + '` milestone/' + module.id + '嚗<ts>嚗', |
| 2196 | 2203 | '', |
| 2197 | 2204 | '- **璅∪銝駁恕蝑**嚗撩潭圾霂鳴秤挽嚗', |
| 2198 | - digest, | |
| 2205 | + decisionDigestMd(moduleDecisions, '嚗璅∪銝駁恕霈啣'), | |
| 2199 | 2206 | ].join('\n')) |
| 2207 | + if (ok) flushedCount = autonomousDecisions.length // 隞餈偌雿瑪嚗lush 憭梯揖 loop-end ‘霈 | |
| 2200 | 2208 | } |
| 2201 | 2209 | } catch (e) { |
| 2202 | 2210 | const reason = String(e.message || e) |
| ... | ... | @@ -2221,16 +2229,14 @@ const pending = haltedAtIdx >= 0 |
| 2221 | 2229 | // 雿蹂活 coding-start 憭活銝箔 halt / 鈭銝餃挽 / 餈鈭芋 |
| 2222 | 2230 | // 蝖砌葉嚗◤嚗銝銝蝘 halt 霈堆 tag+撌乩辣撌脣 Router 蝏剛 |
| 2223 | 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 | 2233 | if (halted) { |
| 2228 | 2234 | const pend = pending.length ? pending.map(p => `\`${p.module}\``).join('') : '' |
| 2229 | 2235 | await recordResume([ |
| 2230 | 2236 | '## HALT 璅∪ `' + halted.module + '`嚗<ts>嚗', |
| 2231 | 2237 | '', |
| 2232 | 2238 | `- **halt **嚗${halted.reason || '(蝛)'}`, |
| 2233 | - '- **甈∟銝駁恕蝑**嚗撩潭圾霂鳴秤挽嚗窈憭嚗', | |
| 2239 | + '- **甈∟銝駁恕蝑**嚗鋡恍芋閬歇摰芋蝑芋嚗窈憭嚗', | |
| 2234 | 2240 | decDigest, |
| 2235 | 2241 | `- **halt 頝芋**嚗${pend}`, |
| 2236 | 2242 | '- **銝甇**嚗犖撌乩耨憭憛 `/erp-workflow:coding-start`嚗outer git tag 蝏剛歇摰芋頝唾', |
| ... | ... | @@ -2241,7 +2247,7 @@ if (halted) { |
| 2241 | 2247 | '## 摰<ts>嚗', |
| 2242 | 2248 | '', |
| 2243 | 2249 | `- **甈∪芋**嚗${doneList}`, |
| 2244 | - '- **甈∟銝駁恕蝑**嚗', | |
| 2250 | + '- **甈∟銝駁恕蝑**嚗鋡恍芋閬', | |
| 2245 | 2251 | decDigest, |
| 2246 | 2252 | ].join('\n')) |
| 2247 | 2253 | } | ... | ... |