agentic-flow-kit 1.7.0 → 1.7.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/README.md +81 -9
  2. package/modules/gates/README.md +1 -0
  3. package/modules/gates/test-protect/README.md +23 -0
  4. package/modules/gates/test-protect/check-test-protect.mjs +64 -0
  5. package/modules/gates/test-protect/local-pre-commit.line +1 -0
  6. package/modules/hosts/README.md +57 -0
  7. package/modules/hosts/claude/commands/wf-build.md +5 -24
  8. package/modules/hosts/claude/commands/wf-deploy.md +1 -0
  9. package/modules/hosts/claude/commands/wf-design.md +7 -6
  10. package/modules/hosts/claude/commands/wf-gate-checklist.md +1 -3
  11. package/modules/hosts/claude/commands/wf-new-task.md +42 -21
  12. package/modules/hosts/claude/commands/wf-plan.md +1 -1
  13. package/modules/hosts/claude/commands/wf-review.md +1 -1
  14. package/modules/hosts/claude/commands/wf-sync-hosts.md +8 -16
  15. package/modules/hosts/claude/commands/wf-test.md +5 -4
  16. package/modules/hosts/codex/commands/wf-build.md +5 -24
  17. package/modules/hosts/codex/commands/wf-deploy.md +1 -0
  18. package/modules/hosts/codex/commands/wf-design.md +7 -6
  19. package/modules/hosts/codex/commands/wf-gate-checklist.md +1 -3
  20. package/modules/hosts/codex/commands/wf-new-task.md +42 -21
  21. package/modules/hosts/codex/commands/wf-plan.md +1 -1
  22. package/modules/hosts/codex/commands/wf-review.md +1 -1
  23. package/modules/hosts/codex/commands/wf-sync-hosts.md +8 -16
  24. package/modules/hosts/codex/commands/wf-test.md +5 -4
  25. package/modules/hosts/cursor/commands/wf-build.md +5 -24
  26. package/modules/hosts/cursor/commands/wf-deploy.md +1 -0
  27. package/modules/hosts/cursor/commands/wf-design.md +7 -6
  28. package/modules/hosts/cursor/commands/wf-gate-checklist.md +1 -3
  29. package/modules/hosts/cursor/commands/wf-new-task.md +42 -21
  30. package/modules/hosts/cursor/commands/wf-plan.md +1 -1
  31. package/modules/hosts/cursor/commands/wf-review.md +1 -1
  32. package/modules/hosts/cursor/commands/wf-sync-hosts.md +8 -16
  33. package/modules/hosts/cursor/commands/wf-test.md +5 -4
  34. package/modules/hosts/opencode/commands/wf-build.md +5 -24
  35. package/modules/hosts/opencode/commands/wf-deploy.md +1 -0
  36. package/modules/hosts/opencode/commands/wf-design.md +7 -6
  37. package/modules/hosts/opencode/commands/wf-gate-checklist.md +1 -3
  38. package/modules/hosts/opencode/commands/wf-new-task.md +42 -21
  39. package/modules/hosts/opencode/commands/wf-plan.md +1 -1
  40. package/modules/hosts/opencode/commands/wf-review.md +1 -1
  41. package/modules/hosts/opencode/commands/wf-sync-hosts.md +8 -16
  42. package/modules/hosts/opencode/commands/wf-test.md +5 -4
  43. package/modules/hosts/trae/commands/wf-build.md +5 -24
  44. package/modules/hosts/trae/commands/wf-deploy.md +1 -0
  45. package/modules/hosts/trae/commands/wf-design.md +7 -6
  46. package/modules/hosts/trae/commands/wf-gate-checklist.md +1 -3
  47. package/modules/hosts/trae/commands/wf-new-task.md +42 -21
  48. package/modules/hosts/trae/commands/wf-plan.md +1 -1
  49. package/modules/hosts/trae/commands/wf-review.md +1 -1
  50. package/modules/hosts/trae/commands/wf-sync-hosts.md +8 -16
  51. package/modules/hosts/trae/commands/wf-test.md +5 -4
  52. package/package.json +2 -2
  53. package/src/add-gate.mjs +25 -1
  54. package/src/add-gate.test.mjs +103 -0
  55. package/src/cli.mjs +59 -5
  56. package/src/doctor.mjs +136 -12
  57. package/src/executor.mjs +245 -0
  58. package/src/executor.test.mjs +292 -0
  59. package/src/gates.test.mjs +17 -0
  60. package/src/init.mjs +14 -1
  61. package/src/init.test.mjs +19 -0
  62. package/src/instance-ref.test.mjs +62 -0
  63. package/src/jscpd-drift.test.mjs +144 -0
  64. package/src/profiles.mjs +3 -0
  65. package/src/render.mjs +6 -1
  66. package/src/rule-budget.test.mjs +103 -0
  67. package/src/sync-hosts.mjs +41 -45
  68. package/src/sync-hosts.test.mjs +67 -56
  69. package/src/trae-hooks.test.mjs +15 -3
  70. package/src/validate.mjs +72 -0
  71. package/src/validate.test.mjs +95 -0
  72. package/templates/.gitattributes +8 -0
  73. package/templates/AGENTS.md +2 -1
  74. package/templates/_agents/commands/build.md +5 -24
  75. package/templates/_agents/commands/deploy.md +1 -0
  76. package/templates/_agents/commands/design.md +7 -6
  77. package/templates/_agents/commands/gate-checklist.md +1 -3
  78. package/templates/_agents/commands/new-task.md +42 -21
  79. package/templates/_agents/commands/plan.md +1 -1
  80. package/templates/_agents/commands/review.md +1 -1
  81. package/templates/_agents/commands/sync-hosts.md +8 -16
  82. package/templates/_agents/commands/test.md +5 -4
  83. package/templates/_agents/control-surfaces.txt +53 -0
  84. package/templates/_agents/engine-lock.json +246 -0
  85. package/templates/_agents/lane-surfaces.txt +1 -1
  86. package/templates/_agents/level-hints.txt +30 -0
  87. package/templates/_agents/scripts/agg-delegations.cjs +51 -6
  88. package/templates/_agents/scripts/agg-delegations.test.mjs +84 -4
  89. package/templates/_agents/scripts/check-engine-integrity.mjs +217 -0
  90. package/templates/_agents/scripts/check-engine-integrity.test.mjs +215 -0
  91. package/templates/_agents/scripts/check-lane-surface.mjs +78 -24
  92. package/templates/_agents/scripts/check-lane-surface.test.mjs +66 -0
  93. package/templates/_agents/scripts/check-ledger-invariant.mjs +112 -14
  94. package/templates/_agents/scripts/check-ledger-invariant.test.mjs +5 -1
  95. package/templates/_agents/scripts/check-loop-rev.test.mjs +6 -4
  96. package/templates/_agents/scripts/check-loop.mjs +255 -45
  97. package/templates/_agents/scripts/check-loop.test.mjs +330 -15
  98. package/templates/_agents/scripts/check-metric-claims.mjs +4 -2
  99. package/templates/_agents/scripts/compact-ledgers.mjs +97 -0
  100. package/templates/_agents/scripts/compact-ledgers.test.mjs +162 -0
  101. package/templates/_agents/scripts/confirm-doc.mjs +54 -12
  102. package/templates/_agents/scripts/confirm-doc.test.mjs +59 -0
  103. package/templates/_agents/scripts/doctor.test.mjs +294 -1
  104. package/templates/_agents/scripts/draft-sediment.mjs +178 -0
  105. package/templates/_agents/scripts/draft-sediment.test.mjs +272 -0
  106. package/templates/_agents/scripts/edit-face-check.test.mjs +5 -4
  107. package/templates/_agents/scripts/fill-intent.mjs +2 -2
  108. package/templates/_agents/scripts/fixture-scripts.mjs +153 -0
  109. package/templates/_agents/scripts/fixture-scripts.test.mjs +107 -0
  110. package/templates/_agents/scripts/gate-checklist.mjs +6 -3
  111. package/templates/_agents/scripts/gate-checklist.test.mjs +2 -5
  112. package/templates/_agents/scripts/gen-wiki-board.mjs +61 -6
  113. package/templates/_agents/scripts/gen-wiki-board.test.mjs +74 -1
  114. package/templates/_agents/scripts/gen-workflow-dashboard.mjs +10 -13
  115. package/templates/_agents/scripts/gen-workflow-metrics.mjs +12 -30
  116. package/templates/_agents/scripts/git-merge-jsonl.mjs +63 -0
  117. package/templates/_agents/scripts/git-merge-jsonl.test.mjs +125 -0
  118. package/templates/_agents/scripts/kb-search.mjs +87 -12
  119. package/templates/_agents/scripts/kb-search.test.mjs +83 -0
  120. package/templates/_agents/scripts/ledger-doc-chain.test.mjs +166 -0
  121. package/templates/_agents/scripts/ledger-loader.mjs +108 -0
  122. package/templates/_agents/scripts/ledger-loader.test.mjs +131 -0
  123. package/templates/_agents/scripts/ledger-prune.mjs +96 -0
  124. package/templates/_agents/scripts/ledger-prune.test.mjs +120 -0
  125. package/templates/_agents/scripts/ledger-ts-monotonic.test.mjs +135 -0
  126. package/templates/_agents/scripts/level-criteria.mjs +232 -0
  127. package/templates/_agents/scripts/level-criteria.test.mjs +279 -0
  128. package/templates/_agents/scripts/pipeline-run.mjs +5 -3
  129. package/templates/_agents/scripts/policy.mjs +47 -0
  130. package/templates/_agents/scripts/solidify-task.mjs +1 -1
  131. package/templates/_agents/scripts/stage-gates.mjs +52 -10
  132. package/templates/_agents/scripts/stage-gates.test.mjs +105 -0
  133. package/templates/_agents/scripts/verify-wiki-consistency.mjs +34 -0
  134. package/templates/_agents/scripts/verify-wiki-consistency.test.mjs +44 -2
  135. package/templates/_agents/scripts/verify.mjs +1 -1
  136. package/templates/_agents/scripts/wf-journal.test.mjs +1 -1
  137. package/templates/_agents/scripts/wiki-search.test.mjs +5 -6
  138. package/templates/_agents/scripts/workflows-check.mjs +1 -1
  139. package/templates/_agents/scripts-test-exempt.txt +3 -1
  140. package/templates/_agents/skills/wiki/SKILL.md +5 -3
  141. package/templates/_agents/workflows/pipeline-closing.md +4 -5
  142. package/templates/_githooks/pre-commit +3 -34
  143. package/templates/_githooks/pre-push +3 -3
  144. package/templates/wiki/INDEX.md +44 -1
  145. package/templates/wiki//347/237/245/350/257/206/346/262/211/346/267/200/346/200/273/350/247/210.html +22 -1
  146. package/templates/workflow/README.md +11 -0
  147. package/templates/workflow/delegations.md +5 -4
  148. package/templates/workflow/incidents/_TEMPLATE.md +2 -0
  149. package/templates/workflow/intents/_TEMPLATE.md +3 -0
  150. package/templates/workflow/plans/_TEMPLATE.md +4 -0
  151. package/templates/workflow/specs/_TEMPLATE.md +23 -0
  152. package/modules/hosts/claude/commands/wf-source-sync-check.md +0 -91
  153. package/modules/hosts/codex/commands/wf-source-sync-check.md +0 -91
  154. package/modules/hosts/cursor/commands/wf-source-sync-check.md +0 -91
  155. package/modules/hosts/opencode/commands/wf-source-sync-check.md +0 -91
  156. package/modules/hosts/trae/commands/wf-source-sync-check.md +0 -92
  157. package/templates/_agents/commands/source-sync-check.md +0 -105
  158. package/templates/_agents/scripts/check-ledger.mjs +0 -109
  159. package/templates/_agents/scripts/check-ledger.test.mjs +0 -197
  160. package/templates/_agents/scripts/source-sync-check.mjs +0 -156
  161. package/templates/_agents/scripts/source-sync-check.test.mjs +0 -281
  162. package/templates/_agents/workflows/source-sync-repair.md +0 -30
@@ -25,6 +25,7 @@ import fs from 'node:fs';
25
25
  import path from 'node:path';
26
26
  import { spawnSync } from 'node:child_process';
27
27
  import { loadKitPolicy } from './policy.mjs';
28
+ import { readLedgerRows } from './ledger-loader.mjs';
28
29
 
29
30
  export const ENTRY_STATUSES = ['approved', 'done'];
30
31
  export const INCIDENT_STATUSES = ['open', 'fixed', 'closed'];
@@ -48,16 +49,10 @@ function readBody(abs) {
48
49
  }
49
50
 
50
51
  // readLedger:confirmations.jsonl 追加式台账(坏行容忍跳过;与 check-loop 检查 15 同容错口径)
52
+ // 读法收敛到 ledger-loader 单源(2026-10-12 ledger-loader-p1):本函数此前自持 readFileSync+parse
53
+ // 复刻,与其余 5 处各写各的——分片台账落地时逐处加分支必然漏改。签名与返回值不变。
51
54
  export function readLedger(root) {
52
- const p = path.join(root, '.agents', 'confirmations.jsonl');
53
- let text = '';
54
- try { text = fs.readFileSync(p, 'utf8'); } catch { return []; }
55
- const out = [];
56
- for (const line of text.split('\n')) {
57
- if (!line.trim()) continue;
58
- try { out.push(JSON.parse(line)); } catch { /* 坏行跳过 */ }
59
- }
60
- return out;
55
+ return readLedgerRows(root);
61
56
  }
62
57
 
63
58
  // judgeDoc:状态枚举 + 机器事实源判定(供入口 / spec 复用)
@@ -204,7 +199,54 @@ export function draftGateFor(kind, root, base) {
204
199
  return { ok: true, entry };
205
200
  }
206
201
 
202
+ // ---- 待澄清标记(2026-10-12 needs-clarification,spec-kit NEEDS CLARIFICATION 对应物)----
203
+ // 动机:本项目的歧义处理此前只有一种形态——**停下来问**(确认门停机)。那是「事后阻塞」:AI 停住等
204
+ // 人,而人在上下文里未必答得上(他刚提的需求,细节是你算出来的)。spec-kit 的做法是**起草期把
205
+ // 歧义显式写下来**(`[NEEDS CLARIFICATION]`),不阻塞起草但禁臆测——「我不知道」是负责,「猜一个」
206
+ // 是失职。本件补这个表达:**零阻塞 + 不许糊过去**。
207
+ // 形态:正文行 `- 待澄清:<问题>`(行首可选列表符;半角冒号兼容)。行首 `- 已澄清:<问题>` **不算**
208
+ // (同一行改前缀即声明解决,不删行——保留「曾经不清楚过」的痕迹,供事后对质)。
209
+ // ⚠️ 与检查 2(模板占位符)刻意区分:占位符 = 「忘了填」(失职),本标记 = 「这里我不知道」
210
+ // (负责)。两者都要求作者动作,但语义相反,故不复用同一正则——复用即把负责判成失职。
211
+ // 边界(诚实声明):**起草门不阻断**(这才是零阻塞的意义),仅在 draft→approved 确认门点名——
212
+ // 确认即「我认可这份文档」,文档里还挂着未澄清项则确认本身失去含义,故此处拒绝落账。
213
+ // 例外:<日期>/YYYY-MM-DD 等占位由检查 2 管,本件不重复拦。
214
+ const CLARIFY_RE = /^\s*[-*]\s*(待澄清|需澄清|待确认细节)\s*[::]\s*(\S.*)$/;
215
+
216
+ // openClarifications:抽出正文里未澄清的行(pure,供直测)。返回 [{ lineNo, text }]
217
+ // HTML 注释行不计入——模板自带说明写在注释里,不该被判成作者留下的未澄清项。
218
+ export function openClarifications(body) {
219
+ const out = [];
220
+ const lines = String(body || '').replace(/\r\n/g, '\n').split('\n');
221
+ for (let i = 0; i < lines.length; i++) {
222
+ const l = lines[i];
223
+ if (/^\s*<!--/.test(l) || /-->\s*$/.test(l)) continue; // 整行注释
224
+ const m = l.match(CLARIFY_RE);
225
+ if (m) out.push({ lineNo: i + 1, text: m[2].trim(), kind: m[1] });
226
+ }
227
+ return out;
228
+ }
229
+
230
+ // clarifyGateFor:确认门「待澄清项已解决」前置判(confirm-doc draft→approved 用)
231
+ // 返回 { ok, items?, reason?, hint? }。ok=false 时 items 供逐条点名(不只说「有未澄清项」——
232
+ // 说清是哪几行哪几句,作者才不用回去逐行找)。
233
+ export function clarifyGateFor(abs) {
234
+ let body = '';
235
+ try { body = fs.readFileSync(abs, 'utf8'); } catch { return { ok: true, skipped: 'unreadable' }; }
236
+ const items = openClarifications(body);
237
+ if (!items.length) return { ok: true, items };
238
+ const list = items.map((i) => `第${i.lineNo}行「${i.text}」`).join(';');
239
+ return {
240
+ ok: false,
241
+ items,
242
+ reason: `文档含 ${items.length} 处未澄清项——${list}`,
243
+ hint: '逐条解决后把行首「待澄清」改为「已澄清」(保留行,便于事后对质曾不清楚过),或直接删行;确认门不接受挂着未澄清项的文档',
244
+ };
245
+ }
246
+
207
247
  // confirmGateFor:确认门总判(confirm-doc 用;docRel = workflow/specs/... 或 workflow/plans/...)
248
+ // ⚠️ 只判**前置深度**(入口/spec 是否已确认)。待澄清项判定是**同一确认门上的另一条独立判据**,
249
+ // 由 clarifyGateFor 单独承担——两者不合并:合并后拒绝原因会指向「前置」,作者找错地方。
208
250
  export function confirmGateFor(root, docRel) {
209
251
  const base = path.basename(docRel).replace(/\.md$/, '');
210
252
  const kind = docRel.startsWith('workflow/specs/') ? 'spec' : 'plan';
@@ -276,4 +318,4 @@ export function resolveWorkspace(outputPath, rootOverride) {
276
318
  return null;
277
319
  }
278
320
 
279
- export default { entryConfirmed, specConfirmed, draftGateFor, confirmGateFor, doneGateFor, gitReady, approvedTraceHit, resolveWorkspace, readFm, readLedger };
321
+ export default { entryConfirmed, specConfirmed, draftGateFor, confirmGateFor, clarifyGateFor, openClarifications, doneGateFor, gitReady, approvedTraceHit, resolveWorkspace, readFm, readLedger };
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env node
2
+ // stage-gates.test.mjs — 逐阶段前置门 + 待澄清门的纯函数直测(2026-10-12 needs-clarification)
3
+ // 为什么单建本套件(stage-gates.mjs 此前登记 scripts-test-exempt,靠 check-loop.test 间接覆盖):
4
+ // 本批给该模块加了两条**此前不存在的判据族**(clarifyGateFor / openClarifications),
5
+ // 而这两条**没有 check-loop 侧的对应检查**——check-loop 不消费它们。间接覆盖天然盖不到
6
+ // 「确认门拒绝未澄清项」这条行为,纯函数直测是唯一能钉住它的位置(check 20 的豁免登记
7
+ // 在此更新为「同名直测 + 间接覆盖并有」)。
8
+ // 覆盖:CLARIFY_RE 形态矩阵(正例 5 / 负例 7)+ HTML 注释不计 + 已澄清前缀不计
9
+ // + clarifyGateFor 三态(无 / 有 / 不可读)+ 行号点名。
10
+ // 用法:node templates/_agents/scripts/stage-gates.test.mjs(npm test 随跑)
11
+ import fs from 'node:fs';
12
+ import os from 'node:os';
13
+ import path from 'node:path';
14
+ import { openClarifications, clarifyGateFor } from './stage-gates.mjs';
15
+
16
+ let pass = 0;
17
+ let fail = 0;
18
+ const check = (desc, cond, detail = '') => {
19
+ if (cond) { pass++; console.log(`PASS ${desc}`); }
20
+ else { fail++; console.log(`FAIL ${desc}${detail ? `\n${detail}` : ''}`); }
21
+ };
22
+
23
+ // ---- T1 正例形态矩阵(5 个写法都应抽出,行号正确)----
24
+ {
25
+ const body = [
26
+ '# S', // 1
27
+ '', // 2
28
+ '- 待澄清:错误率判据?', // 3 ← 行首列表符 + 全角冒号
29
+ '* 需澄清: 依赖版本未定', // 4 ← 星号 + 半角冒号 + 同义前缀
30
+ ' - 待确认细节:用户没说要不要兼容旧数据', // 5 ← 前导空格 + 第三同义前缀
31
+ '正文里提到「待澄清」二字不算(不是行首列表条目)', // 6 ← 负例
32
+ '', // 7
33
+ '- 待澄清了:已解决的说法不该被抽', // 8 ← 负例(词后跟了别的字符)
34
+ '待澄清:无列表符的行不算', // 9 ← 负例
35
+ '- 已澄清:这是已解决的', // 10 ← 负例(前缀已澄清)
36
+ ].join('\n');
37
+ const items = openClarifications(body);
38
+ check('T1 三个同义前缀 + 列表符变体 + 全/半角冒号全命中(5 个写法)', items.length === 3, JSON.stringify(items));
39
+ check('T1 行号正确(3 / 4 / 5)',
40
+ items.map((i) => i.lineNo).join(',') === '3,4,5', JSON.stringify(items.map((i) => i.lineNo)));
41
+ check('T1 抽出的是标记后的正文(不含前缀与冒号)',
42
+ items[0].text === '错误率判据?' && items[1].text === '依赖版本未定' && items[2].text === '用户没说要不要兼容旧数据',
43
+ JSON.stringify(items.map((i) => i.text)));
44
+ check('T1 负例四类全不误抽(正文提及 / 前缀粘连 / 无列表符 / 已澄清)',
45
+ !items.some((i) => /待澄清了|无列表符|已解决/.test(i.text)), JSON.stringify(items));
46
+ }
47
+
48
+ // ---- T2 HTML 注释行不计(模板自带说明写在注释里,不该被判成作者的未澄清项)----
49
+ {
50
+ const body = [
51
+ '# S',
52
+ '<!-- 起草时照实写一行 - 待澄清:<你卡在哪>,不要猜 -->', // 整行注释
53
+ '- 待澄清:真问题',
54
+ '<!-- - 待澄清:注释里的第二处 -->', // 整行注释
55
+ '行尾注释不算:- 待澄清:真的吗 <!-- 附注 -->', // 行尾注释
56
+ ].join('\n');
57
+ const items = openClarifications(body);
58
+ check('T2 整行 HTML 注释里的标记不计(只 1 处命中)', items.length === 1 && items[0].lineNo === 3, JSON.stringify(items));
59
+ check('T2 行尾注释形态不计(模板说明常这么写)', !items.some((i) => /真的吗/.test(i.text)), JSON.stringify(items));
60
+ }
61
+
62
+ // ---- T2b 已知边界(跨行 HTML 注释块)——如实钉住,不假装覆盖 ----
63
+ // 逐行过滤只认「整行注释」与「行尾注释」;跨行注释块 <!--\n - 待澄清:x\n--> 的中间行会被抽出。
64
+ // 为何不修:跨行注释在 workflow 文档里零先例(模板说明一律单行注释),为不存在的形态加状态机
65
+ // 是过度工程。**方向是保守的**(多抽一处 → 确认门拒一次,作者看到就改),不会漏放。
66
+ {
67
+ const items = openClarifications('<!--\n- 待澄清:跨行注释块中间行\n-->');
68
+ check('T2b 已知边界:跨行注释块中间行会被抽出(保守方向——多拒不漏放)',
69
+ items.length === 1 && items[0].lineNo === 2, JSON.stringify(items));
70
+ }
71
+
72
+ // ---- T3 CRLF 与空输入 ----
73
+ {
74
+ check('T3 CRLF 归一后形态不变(行号按 \\n 计)',
75
+ openClarifications('- a\r\n\r\n- 待澄清:x\r\n').length === 1);
76
+ check('T3 空 / undefined 输入 → 空数组(不抛)',
77
+ openClarifications('').length === 0 && openClarifications(undefined).length === 0 && openClarifications(null).length === 0);
78
+ }
79
+
80
+ // ---- T4 clarifyGateFor 三态 ----
81
+ {
82
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'stage-gates-test-'));
83
+ const p = path.join(tmp, 'doc.md');
84
+ fs.writeFileSync(p, '---\n状态: draft\n---\n# S\n- 待澄清:甲\n- 待澄清:乙\n');
85
+ const g = clarifyGateFor(p);
86
+ check('T4 挂未澄清项 → ok=false 且 items 含两处', !g.ok && g.items.length === 2, JSON.stringify(g));
87
+ check('T4 reason 逐条点名行号与内容(作者不必回去逐行找)',
88
+ /第5行「甲」/.test(g.reason) && /第6行「乙」/.test(g.reason), g.reason);
89
+ check('T4 reason 给出条数 + hint 给两条解决路径(改前缀 / 删行)',
90
+ /2 处未澄清项/.test(g.reason) && /已澄清/.test(g.hint) && /删行/.test(g.hint), JSON.stringify(g));
91
+ check('T4 ok=false 时必然带 reason 与 hint(出账完整,勿半截)', !!g.reason && !!g.hint);
92
+
93
+ fs.writeFileSync(p, '---\n状态: draft\n---\n# S\n- 已澄清:都解决了\n');
94
+ const g2 = clarifyGateFor(p);
95
+ check('T4 全部已澄清 → ok=true', g2.ok && g2.items.length === 0, JSON.stringify(g2));
96
+
97
+ const missing = clarifyGateFor(path.join(tmp, 'nope.md'));
98
+ check('T4 文件不可读 → ok=true + skipped=unreadable(fail-open,不因读不到而拒确认)',
99
+ missing.ok && missing.skipped === 'unreadable', JSON.stringify(missing));
100
+ fs.rmSync(tmp, { recursive: true, force: true });
101
+ }
102
+
103
+ console.log(`\n合计: PASS ${pass} / FAIL ${fail}`);
104
+ if (fail) { console.log('\n用法:node templates/_agents/scripts/stage-gates.test.mjs'); process.exit(1); }
105
+ process.exit(0);
@@ -209,6 +209,40 @@ for (const m2 of html.matchAll(/href="([^"]+)"/g)) {
209
209
  }
210
210
  if (deadLinks.length) problems.push(`看板链接指向不存在的路径(相对 wiki/ 解析): ${deadLinks.join(';')}`);
211
211
 
212
+ // ---- 9. 来源血缘(B3,2026-10-10):wiki frontmatter「来源: workflow/<类型>/<文件>」指回 workflow 归档 ----
213
+ // 与 intent/incident 的正向「沉淀:」字段对称闭环——双向链接任一端指向不存在的文件即断链。
214
+ // 校验面:活跃层知识文档(.md/.html,含子目录递归)。无 frontmatter / 无来源键的老文件零影响。
215
+ // drafts-archive 草稿的来源校验降级为提示(草稿是中间态,来源 intent 可能尚未在仓库——B2 落的草稿
216
+ // 在 intent done 前生成,此时断链属预期),不进 problems。
217
+ // 注意:activeTree 只含活跃主题目录(drafts-archive 在枚举处被排除),草稿须另行扫描归档树。
218
+ const fmGet = (text, key) => {
219
+ const m = text.match(/^---\r?\n([\s\S]*?)\r?\n---/);
220
+ if (!m) return null;
221
+ const kv = m[1].split(/\r?\n/).map((l) => l.match(/^(\S+):\s*(.*)$/)).find((x) => x && x[1] === key);
222
+ return kv ? kv[2].trim() : null;
223
+ };
224
+ let brokenSources = 0;
225
+ const checkSource = (f, isDraft) => {
226
+ let text;
227
+ try { text = fs.readFileSync(path.join(WIKI, f), 'utf8'); } catch { return; }
228
+ const src = fmGet(text, '来源');
229
+ if (!src) return;
230
+ if (fs.existsSync(src)) return;
231
+ if (isDraft) notes.push(`草稿 ${f} 来源未落地(草稿中间态,归类前允许):${src}`);
232
+ else { problems.push(`来源断链:${f} frontmatter「来源: ${src}」指向的文件不存在`); brokenSources++; }
233
+ };
234
+ for (const f of activeTree.files) {
235
+ if (!/\.(md|html)$/i.test(f)) continue;
236
+ checkSource(f, false);
237
+ }
238
+ if (fs.existsSync(ARCHIVE_DIR)) {
239
+ for (const f of walkTree(ARCHIVE_DIR).files) {
240
+ if (!/\.(md|html)$/i.test(f)) continue;
241
+ checkSource(`drafts-archive/${f}`, true);
242
+ }
243
+ }
244
+ if (brokenSources === 0) notes.push('来源血缘有效:活跃层 wiki 文件的「来源」均指向存在的 workflow 归档');
245
+
212
246
  // ---- 输出 ----
213
247
  for (const n of notes) console.log('ℹ️ ' + n);
214
248
  if (problems.length) {
@@ -14,12 +14,19 @@ const check = (name, cond, detail = '') => {
14
14
  else { fail++; console.log(`FAIL ${name}${detail ? `——${detail}` : ''}`); }
15
15
  };
16
16
 
17
- function mkWiki({ extraDiskFile = false, noArchive = false } = {}) {
17
+ function mkWiki({ extraDiskFile = false, noArchive = false, draftSource = null } = {}) {
18
18
  const fx = fs.mkdtempSync(path.join(os.tmpdir(), 'vwc-'));
19
19
  fs.mkdirSync(path.join(fx, 'wiki', '主题A'), { recursive: true });
20
20
  fs.writeFileSync(path.join(fx, 'wiki', '主题A', '文档1.md'), '# 文档1\n\n无外链。\n');
21
21
  if (extraDiskFile) fs.writeFileSync(path.join(fx, 'wiki', '主题A', '文档2.md'), '# 文档2\n');
22
+ let archiveCount = 0;
22
23
  if (!noArchive) fs.mkdirSync(path.join(fx, 'wiki', 'drafts-archive'), { recursive: true });
24
+ if (draftSource) { // B2 落的草稿形态:drafts-archive/<日期-主题>/ 下带来源 frontmatter
25
+ fs.mkdirSync(path.join(fx, 'wiki', 'drafts-archive', '2026-09-12-主题'), { recursive: true });
26
+ fs.writeFileSync(path.join(fx, 'wiki', 'drafts-archive', '2026-09-12-主题', '草稿.md'),
27
+ `---\n来源: ${draftSource}\n---\n# 草稿\n`);
28
+ archiveCount = 1;
29
+ }
23
30
  fs.writeFileSync(path.join(fx, 'wiki', 'INDEX.md'), [
24
31
  '# Wiki 索引',
25
32
  '',
@@ -40,7 +47,7 @@ function mkWiki({ extraDiskFile = false, noArchive = false } = {}) {
40
47
  ].join('\n'));
41
48
  const DATA = {
42
49
  topics: [{ name: '主题A', files: [{ file: '文档1.md', dir: '主题A' }] }],
43
- summary: { total: 1, files: 1, topics: 1, archive: 0 },
50
+ summary: { total: 1, files: 1, topics: 1, archive: archiveCount },
44
51
  };
45
52
  fs.writeFileSync(path.join(fx, 'wiki', '知识沉淀总览.html'),
46
53
  '<!doctype html><script>\nconst DATA = ' + JSON.stringify(DATA, null, 2) + ';\n</script>\n');
@@ -77,5 +84,40 @@ const run = (fx) => spawnSync(process.execPath, [path.join(SRC, 'verify-wiki-con
77
84
  fs.rmSync(fx, { recursive: true, force: true });
78
85
  }
79
86
 
87
+ // ---- ④⑤⑥ 来源血缘校验(B3,2026-10-10,第 9 项)----
88
+ {
89
+ // ④ 活跃层来源断链 → exit 1
90
+ const fx = mkWiki();
91
+ fs.writeFileSync(path.join(fx, 'wiki', '主题A', '文档1.md'),
92
+ '---\n来源: workflow/intents/2026-09-12-不存在.md\n---\n# 文档1\n\n无外链。\n');
93
+ const r = run(fx);
94
+ const out = (r.stdout || '') + (r.stderr || '');
95
+ check('④ 活跃层来源断链 → exit 1 且点名断链',
96
+ r.status === 1 && out.includes('来源断链') && out.includes('不存在.md'), `status=${r.status}\n${out.slice(0, 500)}`);
97
+ fs.rmSync(fx, { recursive: true, force: true });
98
+ }
99
+ {
100
+ // ⑤ 活跃层来源指向存在的文件 → exit 0 且血缘有效提示
101
+ const fx = mkWiki();
102
+ fs.writeFileSync(path.join(fx, 'wiki', '主题A', '文档1.md'),
103
+ '---\n来源: workflow/intents/2026-09-12-真单.md\n---\n# 文档1\n\n无外链。\n');
104
+ fs.mkdirSync(path.join(fx, 'workflow', 'intents'), { recursive: true });
105
+ fs.writeFileSync(path.join(fx, 'workflow', 'intents', '2026-09-12-真单.md'), '# INTENT\n');
106
+ const r = run(fx);
107
+ const out = (r.stdout || '') + (r.stderr || '');
108
+ check('⑤ 活跃层来源有效 → exit 0 且提示血缘有效',
109
+ r.status === 0 && out.includes('来源血缘有效'), `status=${r.status}\n${out.slice(0, 500)}`);
110
+ fs.rmSync(fx, { recursive: true, force: true });
111
+ }
112
+ {
113
+ // ⑥ 草稿来源未落地 → 仅提示不阻断(草稿中间态,归类前允许)
114
+ const fx = mkWiki({ draftSource: 'workflow/incidents/2026-09-12-未闭环.md' });
115
+ const r = run(fx);
116
+ const out = (r.stdout || '') + (r.stderr || '');
117
+ check('⑥ 草稿来源未落地 → exit 0 且降级提示(不阻断)',
118
+ r.status === 0 && out.includes('草稿中间态'), `status=${r.status}\n${out.slice(0, 500)}`);
119
+ fs.rmSync(fx, { recursive: true, force: true });
120
+ }
121
+
80
122
  console.log(`\n合计: PASS ${pass} / FAIL ${fail}`);
81
123
  process.exit(fail ? 1 : 0);
@@ -21,7 +21,7 @@
21
21
  // 事实;这样的行不给任何单背书(消费方按 doc 精确匹配时 e.doc 为 undefined 恒不命中)。
22
22
  // 用法:node .agents/scripts/verify.mjs [--test-cmd "<shell 命令>"] [--doc <本单 workflow 路径>]
23
23
  // (--test-cmd 用于测试注入,或在无根 package.json 的装户仓指定项目测试命令;
24
- // --doc 把凭证绑到本单,如 --doc workflow/intents/2026-10-08-x.md)
24
+ // --doc 把凭证绑到本单,如 --doc workflow/intents/<日期>-<主题>.md)
25
25
  import fs from 'node:fs';
26
26
  import { spawnSync } from 'node:child_process';
27
27
  import path from 'node:path';
@@ -139,7 +139,7 @@ const STAGES = parseStages(`| id | after | role | step | task | files | accept |
139
139
  const j = path.join(os.tmpdir(), `wfj-real-${Date.now()}.jsonl`);
140
140
  const r = spawnSync(process.execPath, [JOURNAL_CLI, 'status', '--wf', 'pipeline-closing', '--journal', j], { encoding: 'utf8', cwd: ROOT });
141
141
  check('S9 真实 pipeline-closing:confirm 为 human 形态且无 journal 时 author 首层就绪',
142
- !!confirm && confirm.human && !confirm.role && stages.length >= 10
142
+ !!confirm && confirm.human && !confirm.role && stages.length >= 9
143
143
  && r.status === 0 && /就绪(第 0 层.*author/.test(r.stdout),
144
144
  JSON.stringify({ confirm, out: r.stdout }));
145
145
  fs.rmSync(j, { force: true });
@@ -6,22 +6,21 @@ import fs from 'node:fs';
6
6
  import path from 'node:path';
7
7
  import os from 'node:os';
8
8
  import { spawnSync } from 'node:child_process';
9
- import { fileURLToPath } from 'node:url';
10
9
 
11
- const SRC = path.dirname(fileURLToPath(import.meta.url));
12
10
  let pass = 0, fail = 0;
13
11
  const check = (name, cond, detail = '') => {
14
12
  if (cond) { pass++; console.log(`PASS ${name}`); }
15
13
  else { fail++; console.log(`FAIL ${name}${detail ? `——${detail}` : ''}`); }
16
14
  };
17
15
 
18
- const COPIED = ['wiki-search.mjs', 'kb-search.mjs', 'workflow-enums.mjs'];
16
+ // 夹具闭包(2026-10-12 fixture-closure-rootfix):入口只写 wiki-search.mjs,kb-search 及其传递依赖
17
+ // (workflow-enums.mjs 等)由闭包自动带入——此前手写枚举,漏拷即 import 失败 → 假绿。
18
+ const { copyScriptClosure, copyAgentsFiles } = await import('./fixture-scripts.mjs');
19
19
  function mkFixture() {
20
20
  const fx = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-'));
21
21
  fs.mkdirSync(path.join(fx, '.agents', 'scripts'), { recursive: true });
22
- for (const f of COPIED) fs.copyFileSync(path.join(SRC, f), path.join(fx, '.agents', 'scripts', f));
23
- for (const f of ['workflow-enums.txt', 'workflow-modules.txt'])
24
- fs.copyFileSync(path.join(SRC, '..', f), path.join(fx, '.agents', f));
22
+ copyScriptClosure(path.join(fx, '.agents', 'scripts'), ['wiki-search.mjs']);
23
+ copyAgentsFiles(path.join(fx, '.agents'), ['workflow-enums.txt', 'workflow-modules.txt']);
25
24
  fs.mkdirSync(path.join(fx, 'wiki', '测试主题'), { recursive: true });
26
25
  fs.writeFileSync(path.join(fx, 'wiki', '测试主题', '说明.md'), '# 说明\n\n编码模板的领域知识条目。\n');
27
26
  fs.mkdirSync(path.join(fx, 'workflow', 'intents'), { recursive: true });
@@ -27,7 +27,7 @@
27
27
  // 到首个非 `|` 行截止;仅此一张表——编排脚本按机制文档只允许一张 stages 表。
28
28
  // 用法:node .agents/scripts/workflows-check.mjs [--root <目标根>] [--json]
29
29
  // 默认根 = process.cwd()(doctor 以 cwd=target 调用;仓库根直跑即自扫)
30
- // 零依赖;只报告不修复(B-b 决策口径,同 source-sync-check)
30
+ // 零依赖;只报告不修复(B-b 决策口径)
31
31
  // 测试:node templates/_agents/scripts/workflows-check.test.mjs(fixture + 真实仓库 baseline)
32
32
  import fs from 'node:fs';
33
33
  import path from 'node:path';
@@ -6,7 +6,9 @@
6
6
  check-metric-claims.mjs | 经 check-loop.mjs 消费 | 由 check-loop.test.mjs 间接覆盖其调用路径
7
7
  ensure-board.mjs | 看板启动器 | 永久工具豁免(需真实端口/进程环境,自动化覆盖无性价比——2026-10-09 engine-quality-round2 A2 评估定性)
8
8
  policy.mjs | 库件 | 被 check-loop.test.mjs 直接 import 覆盖
9
- stage-gates.mjs | 库件 | 被 check-loop.test.mjs 直接 import 覆盖(fill-*/confirm-doc.test 消费其行为)
9
+ # 2026-10-12 needs-clarification:stage-gates.mjs 已建同名直测(stage-gates.test.mjs 15 断言),
10
+ # 覆盖待澄清门纯函数族(CLARIFY_RE 形态矩阵 / HTML 注释不计 / clarifyGateFor 三态 / 行号点名)——
11
+ # 这两条判据**没有 check-loop 侧对应检查**,间接覆盖盖不到,故必须直测。登记随之撤除。
10
12
  # 2026-10-08 engine-quality-batch:pipeline-run 三层拆分的库件层——纯函数/存储层由 pipeline-run.test.mjs 全量覆盖(断言零改动全过),单建同名测试属形式主义(plan 偏离留痕 ①)
11
13
  pipeline-run-docs.mjs | 库件(文档纯函数层,2026-10-08 engine-quality-batch 拆分) | 由 pipeline-run.test.mjs 全量覆盖(断言零改动 29 组全过)
12
14
  pipeline-run-run.mjs | 库件(run 存储层,同上拆分) | 由 pipeline-run.test.mjs 全量覆盖(含 PIPELINE_RUN_* 注入契约场景)
@@ -26,7 +26,9 @@ description: 项目 wiki 整理与验证技能。负责把项目内散落文档
26
26
  - **权威**:红线与闭环以 AGENTS.md / workflow 为准;wiki 承载当前态知识(领域 / 表 / 编码 / SQL 台账)。wiki 当前文档优于 `drafts-archive/`(归档仅历史快照)。
27
27
  - **双格式对(md + html 同内容)**:md 为权威内容源,html 为展示快照——内容冲突以 md 为准,改 md 后按需重出 html;速览表「用途」列标注权威关系。
28
28
  - **索引生成(2026-09-13 起)**:速览表计数/映射表/看板 DATA 由 `node .agents/scripts/gen-wiki-board.mjs` 自动生成——人工只维护磁盘文件 + 速览表「用途」列;收录/删除/移动文件后必跑生成器(新主题用途列为 `<待补>` 时补描述后重跑)。写盘前可先 `--dry-run` 看差异(`--help` 打印用法,未知参数 exit 1)。
29
- - **归档目录**:`wiki/drafts-archive/YYYY-MM-DD_<主题>/`,按整理批次日期+主题命名,保留原文件名。
29
+ - **归档目录**:`wiki/drafts-archive/YYYY-MM-DD-<主题>/`,按整理批次日期+主题命名,保留原文件名。
30
+ - **来源血缘(双向链接,2026-10-10 起)**:沉淀到 wiki 的知识在 frontmatter 记 `来源: workflow/<intents|incidents|...>/<文件>` 指回 workflow 归档;被沉淀的 workflow 文件记 `沉淀: wiki/<主题>/<文件>`(或 `沉淀: 无` 显式豁免)。两向字段由 `verify-wiki-consistency.mjs` 校验存在性(活跃层断链即拦,草稿未落地仅提示)——人工移动/改归类时须同步改这两行,否则 check-loop 的 `[WARN 沉淀断档]` 与验证脚本都会点名。
31
+ - **关单自动沉淀(2026-10-10 起)**:intent 置 done / incident 置 closed 时,跑 `node .agents/scripts/draft-sediment.mjs <源文件>` 自动抽取高价值节(intent:影响面/触达红线;incident:影响面/根因/为什么之前没拦住/复盘三件套)→ 落 `wiki/drafts-archive/<日期-主题>/` 草稿(带 `来源` 字段)→ 回写源文件 `沉淀` 字段 → 重跑 gen-wiki-board。草稿是**中间态**:人工读一遍,归类到 `wiki/<主题>/`(补主题与用途,保留 `来源`)后跑生成器 + §4 验证。`--dry-run` 预览不写盘;仅占位的高价值节会被拒抽(内容无沉淀价值)。
30
32
  - **排除**:`workflow/`、依赖锁文件、IDE 配置。
31
33
 
32
34
  ## 3. 标准操作流程
@@ -35,8 +37,8 @@ description: 项目 wiki 整理与验证技能。负责把项目内散落文档
35
37
 
36
38
  1. **识别范围**:列出候选文件后逐篇读首行判断主题——pwsh:`Get-ChildItem -Recurse -File -Include *.md` / `Get-Content <文件> -TotalCount 8`;POSIX:`find` / `head -8`。
37
39
  2. **主题判定**:主题由内容自决,不预设分类。当前已沉淀主题:`产品需求 / 项目计划 / 开发方案 / 用户功能 / 项目规范 / 测试报告 / 数据维护`。新增主题前先与用户对齐。
38
- 3. **建目录**:pwsh `New-Item -ItemType Directory -Force wiki/<主题>`(POSIX:`mkdir -p`);归档目录同理建 `wiki/drafts-archive/YYYY-MM-DD_<主题>`。
39
- 4. **复制并改名**:pwsh `Copy-Item 原路径 wiki/<主题>/<新名>`(POSIX:`cp`);来源为仓库内原位文件时,同时按原名复制一份到 `drafts-archive/YYYY-MM-DD_<主题>/` 留溯源。
40
+ 3. **建目录**:pwsh `New-Item -ItemType Directory -Force wiki/<主题>`(POSIX:`mkdir -p`);归档目录同理建 `wiki/drafts-archive/YYYY-MM-DD-<主题>`。
41
+ 4. **复制并改名**:pwsh `Copy-Item 原路径 wiki/<主题>/<新名>`(POSIX:`cp`);来源为仓库内原位文件时,同时按原名复制一份到 `drafts-archive/YYYY-MM-DD-<主题>/` 留溯源。
40
42
  5. **生成索引**:跑 `node .agents/scripts/gen-wiki-board.mjs`(速览计数/映射表/看板 DATA 自动重生成);新主题在速览表「用途」列补一行描述后重跑。
41
43
  6. **自检**:跑第 4 节"验证脚本",确认三方一致、目录无孤儿。
42
44
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: pipeline-closing
3
- description: 推广环节 6 阶段闭环——起草 intent + plan(双写)→ 用户对话内确认 → 按 plan 改动面实施 → 3 关静态门(source-sync-check / npm test / doctor)→ 关单留痕(intent + plan → done + docs commit)。可复用于本项目所有 L1 推广环节(新增工具 / 微改动 / 协议修补)
3
+ description: 推广环节 6 阶段闭环——起草 intent + plan(双写)→ 用户对话内确认 → 按 plan 改动面实施 → 2 关静态门(npm test / doctor)→ 关单留痕(intent + plan → done + docs commit)。可复用于本项目所有 L1 推广环节(新增工具 / 微改动 / 协议修补)
4
4
  concurrency: 2
5
5
  ---
6
6
  # workflow — pipeline-closing(推广环节 6 阶段闭环)
@@ -13,14 +13,13 @@ concurrency: 2
13
13
  | gate-doc | author | — | — | — | — | — | node .agents/scripts/fill-intent.test.mjs && node .agents/scripts/fill-plan.test.mjs | | |
14
14
  | confirm | gate-doc | — | — | plan 草稿全文过目,用户对话内一句"可以" → plan 状态 draft → approved + 回填「确认与复核」节 | — | plan 状态 approved + 「确认结果:approved(YYYY-MM-DD 用户对话内确认)」 | | 用户确认(plan 确认门) | |
15
15
  | plan-commit | confirm | implementer | — | 提交 plan 立项 commit(docs(workflow)) | workflow/intents/, workflow/plans/, workflow/INDEX.md | pre-commit 钩子全过;commit message 含 plan 改动面摘要 | | | 0 |
16
- | build | plan-commit | implementer | — | 按 plan §改动面 执行(含双源纪律:包源改了装副本手动同步) | 见 plan | plan 改动面全部完成;双源文件 sha 一致 | | | 1 |
17
- | gate-sync | build | — | — | — | — | — | node .agents/scripts/source-sync-check.mjs --diff(必须 0 差异) | | |
16
+ | build | plan-commit | implementer | — | 按 plan §改动面 执行 | 见 plan | plan 改动面全部完成 | | | 1 |
18
17
  | gate-test | build | — | — | — | — | — | npm test(必须全绿) | | |
19
18
  | gate-doctor | build | — | — | — | — | — | node bin/flow-kit.mjs doctor(必须 0 FAIL) | | |
20
- | feat-commit | gate-sync,gate-test,gate-doctor | implementer | — | 提交 feat commit(feat(模块名)) | 工作区所有变更文件 | pre-commit 钩子全过;commit message 含 build 改动面摘要 | | | 0 |
19
+ | feat-commit | gate-test,gate-doctor | implementer | — | 提交 feat commit(feat(模块名)) | 工作区所有变更文件 | pre-commit 钩子全过;commit message 含 build 改动面摘要 | | | 0 |
21
20
  | closeout | feat-commit | implementer | — | 补 intent 8 条验收勾验 + plan → done + 关单 docs commit 留痕;同步 INDEX.md(如适用) | workflow/intents/<date>-<topic>.md, workflow/plans/<date>-<topic>.md, workflow/INDEX.md | intent + plan 状态均 done;8 条验收勾验含 commit SHA 证据;commit message 含关单 commit + 8 条勾验摘要 | | | 0 |
22
21
 
23
- **编排语义**:第 0 层 author → 第 1 层 gate-doc + confirm(confirm 为 human 确认门形态——宿主 AI 停下询问用户,不派子智能体;通过才派下层)→ 第 2 层 plan-commit → 第 3 层 build → 第 4 层 3 关静态门(并行 fan-out)→ 第 5 层 feat-commit → 第 6 层 closeout。失败且 retries > 0 → 重派(全新上下文);gate 非零 / human 驳回 → 中止后续、汇总报告失败项。每个 stage 每次尝试出结果记 `wf-journal.mjs add`;中止续跑先 `wf-journal.mjs status --wf pipeline-closing`(已 pass 不重跑)。
22
+ **编排语义**:第 0 层 author → 第 1 层 gate-doc + confirm(confirm 为 human 确认门形态——宿主 AI 停下询问用户,不派子智能体;通过才派下层)→ 第 2 层 plan-commit → 第 3 层 build → 第 4 层 2 关静态门(并行 fan-out)→ 第 5 层 feat-commit → 第 6 层 closeout。失败且 retries > 0 → 重派(全新上下文);gate 非零 / human 驳回 → 中止后续、汇总报告失败项。每个 stage 每次尝试出结果记 `wf-journal.mjs add`;中止续跑先 `wf-journal.mjs status --wf pipeline-closing`(已 pass 不重跑)。
24
23
 
25
24
  **纪律**(先读):
26
25
  - 声明随 plan 确认后方可执行——本 workflow 是 plan 任务拆解的执行器;plan 草稿先经用户对话内确认。
@@ -1,6 +1,6 @@
1
1
  #!/bin/sh
2
2
  # Git 原生 pre-commit hook(经 core.hooksPath 挂载,git 执行、不依赖 agent 客户端)— agentic-flow-kit 通用编排
3
- # 通用门禁序:闭环配对(增量) → 泳道完整性(增量) → wiki 台账(增量) → 常驻面体积预算(增量) → 双源一致性(增量) → managed 台账快检(包源恒跑) → 项目门禁(local-pre-commit) → 敏感信息扫描+条件构建(commit-check.cjs)
3
+ # 通用门禁序:闭环配对(增量) → 泳道完整性(增量) → wiki 台账(增量) → 常驻面体积预算(增量) → 确认台账不变量(暂存触台账) → 项目门禁(local-pre-commit) → 敏感信息扫描+条件构建(commit-check.cjs)
4
4
  # 项目专属门禁(架构红线/业务守卫等)放 .agents/hooks/local-pre-commit——存在即在本钩子内执行,返回非零即阻断提交
5
5
  # 合并提交场景由 .githooks/pre-merge-commit 原样转发到本脚本(合并时 git 调的是 pre-merge-commit,
6
6
  # 不是 pre-commit)——门禁清单只此一份,避免两处分叉
@@ -21,7 +21,7 @@ run_gate() {
21
21
  # 闭环配对门禁(增量版,check-pairing-incremental.sh:本次暂存的 intents/incidents 入口须已有同名 plan)
22
22
  # experiment/* 泳道分支(2026-09-30 hybrid-governance-explore-hardening):降级为 advisory——快速 PoC
23
23
  # 迭代不因闭环文档断档被拦(断档报告照出),转正合入 main 由 pre-push 加固门(check-loop --hardening)兜底。
24
- # 仅降级闭环文档面:双源 / 台账 / 常驻面 / 敏感信息扫描等门禁不豁免。分支不可判(detached HEAD 等)走严格模式。
24
+ # 仅降级闭环文档面:常驻面 / 敏感信息扫描等门禁不豁免。分支不可判(detached HEAD 等)走严格模式。
25
25
  pairing_gate_mode=strict
26
26
  case "$(git rev-parse --abbrev-ref HEAD 2>/dev/null)" in
27
27
  experiment/*) pairing_gate_mode=advisory ;;
@@ -44,7 +44,7 @@ fi
44
44
  # 检查 B「探索标记缺失」——experiment/* 分支暂存 intent 缺「阶段: exploring」→ 拦(标记零成本,
45
45
  # advisory 免纪律不免标记——标记是加固门覆盖率的前提)。
46
46
  # 旧装副本无脚本自然跳过;node 缺失时本门跳过(其后 commit-check 节点检查仍会拦提交,无空洞)。
47
- # 注意:不经 run_gate(其实现为 sh "$@",会把 node 当脚本解释——直接 node 调用,模式同双源门禁块)。
47
+ # 注意:不经 run_gate(其实现为 sh "$@",会把 node 当脚本解释——直接 node 调用)。
48
48
  if [ -f .agents/scripts/check-lane-surface.mjs ] && command -v node >/dev/null 2>&1; then
49
49
  if ! node .agents/scripts/check-lane-surface.mjs; then
50
50
  echo "pre-commit: 泳道完整性(增量)未通过,提交已阻止" >&2
@@ -62,37 +62,6 @@ if [ -f .agents/scripts/rule-budget.sh ] && [ -f .agents/rule-budgets.txt ]; the
62
62
  run_gate "常驻面体积预算" .agents/scripts/rule-budget.sh --staged
63
63
  fi
64
64
 
65
- # 双源一致性(增量,2026-09-27 audit-gate-hardening):包源 templates/_agents/ 与装副本 .agents/ 须同提交成对。
66
- # 暂存触及任一侧时跑 source-sync-check --gate(缺失/漂移 exit 1,孤儿只报告)——拦「包源改了装副本没跟」
67
- # (doctor §4 台账 sha 对此静默:盘面=台账即 PASS;该故障模式曾两次真实发生,见 incidents/2026-09-25-wf-runtime)。
68
- # 比较工作树而非暂存区(口径沿 check-pairing-incremental 先例):两侧都已改好、分两笔提交时不拦——最终一致即可。
69
- # 已知边界(复核 P2-2 声明,2026-09-27 gate-hardening-p2-batch):包源删除文件不触发缺失/漂移——装副本残件
70
- # 退化为「孤儿」只报告不计失败,删包源时须手动同步清理装副本(sync 对 removed 同样仅报告不删盘)。
71
- # 装户环境(无 templates/_agents/ 包源)自然跳过。
72
- if [ -d templates/_agents ] && [ -f .agents/scripts/source-sync-check.mjs ] \
73
- && git -c core.quotepath=off diff --cached --name-only | grep -qE '^(templates/_agents/|\.agents/)'; then
74
- if ! node .agents/scripts/source-sync-check.mjs --gate; then
75
- echo "pre-commit: 双源一致性(增量)未通过,提交已阻止——单边改动先跑 node bin/flow-kit.mjs sync 成对后原路重试" >&2
76
- exit 1
77
- fi
78
- fi
79
-
80
- # managed 台账快检(包源环境恒跑,2026-09-28 ledger-precommit-gate;同日独立复核 P2-2 采纳):
81
- # kit.json 台账 sha ↔ 盘面工作树(LF 归一,同 doctor §4 口径)——拦「台账 sha 预 landing」:台账记了
82
- # 盘面尚不存在的内容 sha。双源门禁对此静默(templates↔.agents 成对回退时两侧一致),doctor §4 只在
83
- # 手动全量跑——该故障模式三次真实发生(0272a1c 预入 / ff3c721 还原窗口 / 537df18 闭合),本块补齐
84
- # 提交时刻的第三边。恒跑不作暂存触发:预 landing 是仓态而非暂存态,暂存 grep 有前缀缝(如 .githooks/*
85
- # managed 件单边漂移不命中),毫秒级 80 份 hash 不构成开销理由。比较工作树而非暂存区(沿双源门禁
86
- # 口径)。装户环境(无 templates/_agents/ 包源)自然跳过——装户 managed 手改走 doctor §4 WARN 口径,
87
- # 不硬拦。已知边界:managed 删除即拦(sync 对 removed 只报不删,删件须手动同步装副本并重跑 sync 刷
88
- # 台账)。
89
- if [ -d templates/_agents ] && [ -f .agents/scripts/check-ledger.mjs ]; then
90
- if ! node .agents/scripts/check-ledger.mjs; then
91
- echo "pre-commit: managed 台账快检未通过,提交已阻止——kit.json 与盘面不一致,跑 node bin/flow-kit.mjs sync 成对后原路重试" >&2
92
- exit 1
93
- fi
94
- fi
95
-
96
65
  # 确认台账不变量前置(--staged,2026-10-02 ledger-ci-invariant):暂存的 confirmations.jsonl 必须
97
66
  # 是 HEAD 的逐字节前缀扩展(append-only)——本地提交时刻即拦「顺手重写台账」,CI 历史全扫兜底。
98
67
  # guard 旧装副本自然跳过;暂存未触台账静默。
@@ -12,9 +12,9 @@
12
12
  # (intent approved/done + 同名 plan 在场 + L2/L3 另须同名 spec;判据见 check-loop.mjs 加固门段),
13
13
  # hard 失败即阻断 push,防「未文档化的探索件」进 main。
14
14
  #
15
- # 机器门:本地钩子(唯一机器门)——kit 不附带 CI 配置,装户仓裸装无跨环境保证。
16
- # kit 源仓:.github/workflows/ci.yml 于 push main / 全部 PR 复跑 check-loop / doctor / source-sync-check --gate / workflows-check 四道门。
17
- # 装户仓(无 templates/ 包源):无 CI——source-sync-check --gate / doctor / flow-kit 命令不适用(引用即失败),本地钩子即全部机器门。
15
+ # 机器门:本地钩子跑 check-loop.sh。装户仓的远端门是随包下发的 .github/workflows/kit-ci.yml(verify.mjs)。
16
+ # kit 源仓:.github/workflows/ci.yml 于 push main / 全部 PR 跑 npm test(lint + 全量套件)。
17
+ # 装户仓无包源 templates/:sync-hosts 等包源命令不适用。本地钩子仍是提交时刻的机器门。
18
18
  # 不要用 `git push --no-verify` 绕开——发现 hard-block 应先修 workflow 文档再推。
19
19
 
20
20
  cd "$(git rev-parse --show-toplevel)" || exit 1
@@ -16,12 +16,55 @@
16
16
  | 项目计划 | 1 | 计划、里程碑与会议纪要(按日期命名) |
17
17
  | 测试报告 | 1 | 测试报告与验收记录(按 日期-主题 命名) |
18
18
  | 数据维护 | 1 | schema 变更与数据修复台账(子目录按批次组织,批内 README 记账,不参与文件级登记) |
19
+ | drafts-archive | — | 原文档备份(按 日期-主题 分目录) |
19
20
 
20
21
  合计:**7 份**知识文档,**7 个主题**
21
22
 
22
23
  ## 文件 → 主题 → 归属目录 映射表
23
24
 
24
- (生成区:gen-wiki-board.mjs 维护)
25
+ ### 产品需求(1)
26
+
27
+ | 文件名 | 主题 | 归属目录 |
28
+ |---|---|---|
29
+ | README.md | 产品需求 | wiki/产品需求/ |
30
+
31
+ ### 用户功能(1)
32
+
33
+ | 文件名 | 主题 | 归属目录 |
34
+ |---|---|---|
35
+ | README.md | 用户功能 | wiki/用户功能/ |
36
+
37
+ ### 开发方案(1)
38
+
39
+ | 文件名 | 主题 | 归属目录 |
40
+ |---|---|---|
41
+ | README.md | 开发方案 | wiki/开发方案/ |
42
+
43
+ ### 项目规范(1)
44
+
45
+ | 文件名 | 主题 | 归属目录 |
46
+ |---|---|---|
47
+ | README.md | 项目规范 | wiki/项目规范/ |
48
+
49
+ ### 项目计划(1)
50
+
51
+ | 文件名 | 主题 | 归属目录 |
52
+ |---|---|---|
53
+ | README.md | 项目计划 | wiki/项目计划/ |
54
+
55
+ ### 测试报告(1)
56
+
57
+ | 文件名 | 主题 | 归属目录 |
58
+ |---|---|---|
59
+ | README.md | 测试报告 | wiki/测试报告/ |
60
+
61
+ ### 数据维护(1)
62
+
63
+ | 文件名 | 主题 | 归属目录 |
64
+ |---|---|---|
65
+ | README.md | 数据维护 | wiki/数据维护/ |
66
+
67
+ ---
25
68
 
26
69
  ## 命名规则
27
70
 
@@ -20,8 +20,29 @@
20
20
  <div id="app"></div>
21
21
  <script>
22
22
  const DATA = {
23
- "summary": { "total": 0, "topics": 0, "files": 0, "archive": 0 },
23
+ "summary": { "total": 7, "topics": 7, "files": 7, "archive": 1 },
24
24
  "topics": [
25
+ { "name": "产品需求", "files": [
26
+ { "file": "README.md", "topic": "产品需求", "dir": "./产品需求/" }
27
+ ]},
28
+ { "name": "用户功能", "files": [
29
+ { "file": "README.md", "topic": "用户功能", "dir": "./用户功能/" }
30
+ ]},
31
+ { "name": "开发方案", "files": [
32
+ { "file": "README.md", "topic": "开发方案", "dir": "./开发方案/" }
33
+ ]},
34
+ { "name": "项目规范", "files": [
35
+ { "file": "README.md", "topic": "项目规范", "dir": "./项目规范/" }
36
+ ]},
37
+ { "name": "项目计划", "files": [
38
+ { "file": "README.md", "topic": "项目计划", "dir": "./项目计划/" }
39
+ ]},
40
+ { "name": "测试报告", "files": [
41
+ { "file": "README.md", "topic": "测试报告", "dir": "./测试报告/" }
42
+ ]},
43
+ { "name": "数据维护", "files": [
44
+ { "file": "README.md", "topic": "数据维护", "dir": "./数据维护/" }
45
+ ]}
25
46
  ]
26
47
  };
27
48
  (function () {