agentic-flow-kit 1.3.0 → 1.5.0

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 (139) hide show
  1. package/README.md +10 -4
  2. package/modules/hosts/claude/agents/implementer.md +5 -6
  3. package/modules/hosts/claude/agents/independent-reviewer.md +6 -6
  4. package/modules/hosts/claude/agents/ui-verifier.md +5 -5
  5. package/modules/hosts/claude/commands/wf-build.md +21 -21
  6. package/modules/hosts/claude/commands/wf-deploy.md +2 -2
  7. package/modules/hosts/claude/commands/wf-design.md +8 -8
  8. package/modules/hosts/claude/commands/wf-gate-checklist.md +8 -8
  9. package/modules/hosts/claude/commands/wf-maintain.md +12 -12
  10. package/modules/hosts/claude/commands/wf-new-task.md +9 -9
  11. package/modules/hosts/claude/commands/wf-pipeline-run.md +3 -3
  12. package/modules/hosts/claude/commands/wf-plan.md +10 -11
  13. package/modules/hosts/claude/commands/wf-review.md +4 -4
  14. package/modules/hosts/claude/commands/wf-source-sync-check.md +10 -10
  15. package/modules/hosts/claude/commands/wf-sync-hosts.md +9 -9
  16. package/modules/hosts/claude/commands/wf-test.md +6 -7
  17. package/modules/hosts/codex/agents/implementer.md +5 -6
  18. package/modules/hosts/codex/agents/independent-reviewer.md +6 -6
  19. package/modules/hosts/codex/agents/ui-verifier.md +5 -5
  20. package/modules/hosts/codex/commands/wf-build.md +21 -21
  21. package/modules/hosts/codex/commands/wf-deploy.md +2 -2
  22. package/modules/hosts/codex/commands/wf-design.md +8 -8
  23. package/modules/hosts/codex/commands/wf-gate-checklist.md +8 -8
  24. package/modules/hosts/codex/commands/wf-maintain.md +12 -12
  25. package/modules/hosts/codex/commands/wf-new-task.md +9 -9
  26. package/modules/hosts/codex/commands/wf-pipeline-run.md +3 -3
  27. package/modules/hosts/codex/commands/wf-plan.md +10 -11
  28. package/modules/hosts/codex/commands/wf-review.md +4 -4
  29. package/modules/hosts/codex/commands/wf-source-sync-check.md +10 -10
  30. package/modules/hosts/codex/commands/wf-sync-hosts.md +9 -9
  31. package/modules/hosts/codex/commands/wf-test.md +6 -7
  32. package/modules/hosts/cursor/agents/implementer.md +5 -6
  33. package/modules/hosts/cursor/agents/independent-reviewer.md +6 -6
  34. package/modules/hosts/cursor/agents/ui-verifier.md +5 -5
  35. package/modules/hosts/cursor/commands/wf-build.md +21 -21
  36. package/modules/hosts/cursor/commands/wf-deploy.md +2 -2
  37. package/modules/hosts/cursor/commands/wf-design.md +8 -8
  38. package/modules/hosts/cursor/commands/wf-gate-checklist.md +8 -8
  39. package/modules/hosts/cursor/commands/wf-maintain.md +12 -12
  40. package/modules/hosts/cursor/commands/wf-new-task.md +9 -9
  41. package/modules/hosts/cursor/commands/wf-pipeline-run.md +3 -3
  42. package/modules/hosts/cursor/commands/wf-plan.md +10 -11
  43. package/modules/hosts/cursor/commands/wf-review.md +4 -4
  44. package/modules/hosts/cursor/commands/wf-source-sync-check.md +10 -10
  45. package/modules/hosts/cursor/commands/wf-sync-hosts.md +9 -9
  46. package/modules/hosts/cursor/commands/wf-test.md +6 -7
  47. package/modules/hosts/omp/agents/implementer.md +5 -6
  48. package/modules/hosts/omp/agents/independent-reviewer.md +6 -6
  49. package/modules/hosts/omp/agents/ui-verifier.md +5 -5
  50. package/modules/hosts/opencode/agents/implementer.md +5 -6
  51. package/modules/hosts/opencode/agents/independent-reviewer.md +6 -6
  52. package/modules/hosts/opencode/agents/ui-verifier.md +5 -5
  53. package/modules/hosts/opencode/commands/wf-build.md +21 -21
  54. package/modules/hosts/opencode/commands/wf-deploy.md +2 -2
  55. package/modules/hosts/opencode/commands/wf-design.md +8 -8
  56. package/modules/hosts/opencode/commands/wf-gate-checklist.md +8 -8
  57. package/modules/hosts/opencode/commands/wf-maintain.md +12 -12
  58. package/modules/hosts/opencode/commands/wf-new-task.md +9 -9
  59. package/modules/hosts/opencode/commands/wf-pipeline-run.md +3 -3
  60. package/modules/hosts/opencode/commands/wf-plan.md +10 -11
  61. package/modules/hosts/opencode/commands/wf-review.md +4 -4
  62. package/modules/hosts/opencode/commands/wf-source-sync-check.md +10 -10
  63. package/modules/hosts/opencode/commands/wf-sync-hosts.md +9 -9
  64. package/modules/hosts/opencode/commands/wf-test.md +6 -7
  65. package/modules/hosts/trae/agents/implementer.md +5 -6
  66. package/modules/hosts/trae/agents/independent-reviewer.md +6 -6
  67. package/modules/hosts/trae/agents/ui-verifier.md +5 -5
  68. package/modules/hosts/trae/commands/wf-build.md +21 -21
  69. package/modules/hosts/trae/commands/wf-deploy.md +2 -2
  70. package/modules/hosts/trae/commands/wf-design.md +8 -8
  71. package/modules/hosts/trae/commands/wf-gate-checklist.md +8 -8
  72. package/modules/hosts/trae/commands/wf-maintain.md +12 -12
  73. package/modules/hosts/trae/commands/wf-new-task.md +9 -9
  74. package/modules/hosts/trae/commands/wf-pipeline-run.md +3 -3
  75. package/modules/hosts/trae/commands/wf-plan.md +10 -11
  76. package/modules/hosts/trae/commands/wf-review.md +4 -4
  77. package/modules/hosts/trae/commands/wf-source-sync-check.md +10 -10
  78. package/modules/hosts/trae/commands/wf-sync-hosts.md +9 -9
  79. package/modules/hosts/trae/commands/wf-test.md +6 -7
  80. package/modules/hosts/zcode/agents/implementer.md +5 -6
  81. package/modules/hosts/zcode/agents/independent-reviewer.md +6 -6
  82. package/modules/hosts/zcode/agents/ui-verifier.md +5 -5
  83. package/package.json +7 -2
  84. package/src/doctor.mjs +1 -1
  85. package/src/init.test.mjs +1 -1
  86. package/src/run-tests.mjs +15 -1
  87. package/src/sync-hosts.test.mjs +4 -4
  88. package/src/sync.test.mjs +1 -1
  89. package/templates/_agents/commands/build.md +21 -21
  90. package/templates/_agents/commands/deploy.md +2 -2
  91. package/templates/_agents/commands/design.md +8 -8
  92. package/templates/_agents/commands/gate-checklist.md +8 -8
  93. package/templates/_agents/commands/maintain.md +12 -12
  94. package/templates/_agents/commands/new-task.md +9 -9
  95. package/templates/_agents/commands/pipeline-run.md +3 -3
  96. package/templates/_agents/commands/plan.md +10 -11
  97. package/templates/_agents/commands/review.md +4 -4
  98. package/templates/_agents/commands/source-sync-check.md +10 -10
  99. package/templates/_agents/commands/sync-hosts.md +9 -9
  100. package/templates/_agents/commands/test.md +6 -7
  101. package/templates/_agents/roles/implementer.md +5 -6
  102. package/templates/_agents/roles/independent-reviewer.md +6 -6
  103. package/templates/_agents/roles/ui-verifier.md +5 -5
  104. package/templates/_agents/scripts/agg-delegations.cjs +41 -13
  105. package/templates/_agents/scripts/agg-delegations.test.mjs +78 -3
  106. package/templates/_agents/scripts/agg-gate-stats.mjs +113 -0
  107. package/templates/_agents/scripts/agg-gate-stats.test.mjs +95 -0
  108. package/templates/_agents/scripts/check-hygiene.mjs +167 -0
  109. package/templates/_agents/scripts/check-hygiene.test.mjs +182 -0
  110. package/templates/_agents/scripts/check-ledger-invariant.mjs +0 -1
  111. package/templates/_agents/scripts/check-loop.mjs +112 -117
  112. package/templates/_agents/scripts/check-loop.test.mjs +115 -1
  113. package/templates/_agents/scripts/commit-check-trigger.test.mjs +0 -1
  114. package/templates/_agents/scripts/confirm-doc.mjs +27 -7
  115. package/templates/_agents/scripts/confirm-doc.test.mjs +35 -0
  116. package/templates/_agents/scripts/ensure-board.mjs +2 -2
  117. package/templates/_agents/scripts/fill-intent.mjs +2 -2
  118. package/templates/_agents/scripts/fill-plan.mjs +1 -2
  119. package/templates/_agents/scripts/gate-seg.mjs +54 -0
  120. package/templates/_agents/scripts/gate-seg.test.mjs +112 -0
  121. package/templates/_agents/scripts/gen-wiki-board.mjs +1 -1
  122. package/templates/_agents/scripts/gen-workflow-dashboard.mjs +76 -9
  123. package/templates/_agents/scripts/gen-workflow-dashboard.test.mjs +64 -3
  124. package/templates/_agents/scripts/gen-workflow-index.test.mjs +1 -1
  125. package/templates/_agents/scripts/gen-workflow-metrics.mjs +1 -1
  126. package/templates/_agents/scripts/kb-search.mjs +1 -2
  127. package/templates/_agents/scripts/kb-search.test.mjs +1 -1
  128. package/templates/_agents/scripts/pipeline-run-docs.mjs +76 -0
  129. package/templates/_agents/scripts/pipeline-run-run.mjs +63 -0
  130. package/templates/_agents/scripts/pipeline-run.mjs +8 -128
  131. package/templates/_agents/scripts/policy.mjs +37 -3
  132. package/templates/_agents/scripts/source-sync-check.mjs +2 -2
  133. package/templates/_agents/scripts/source-sync-check.test.mjs +1 -1
  134. package/templates/_agents/scripts/verify-wiki-consistency.mjs +0 -1
  135. package/templates/_agents/scripts/verify.mjs +27 -2
  136. package/templates/_agents/scripts/verify.test.mjs +53 -0
  137. package/templates/_agents/scripts/workflow-board-server.test.mjs +0 -1
  138. package/templates/_agents/scripts/workflow-enums.test.mjs +0 -1
  139. package/templates/_agents/scripts-test-exempt.txt +4 -1
@@ -0,0 +1,63 @@
1
+ // pipeline-run-run.mjs — run 存储层(2026-10-08 engine-quality-batch,L2 自 pipeline-run.mjs 拆出)
2
+ // ctxEnv / moduleList / runFileOf / loadRun / saveRun / emit / latestRunId / newRunId + 时间工具(nowIso/today/pad)——
3
+ // run 文件(.agents/cache/pipeline-runs/<id>.json)原子读写与事件流;run schema 零变化。
4
+ // 测试注入契约原样:PIPELINE_RUN_ROOT / PIPELINE_RUN_BIN / PIPELINE_RUN_GIT_BIN(ctxEnv)与
5
+ // PIPELINE_RUN_TODAY(today——时钟依赖消除锚,spec §测试注入契约)。
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+
9
+ export const nowIso = () => new Date().toISOString();
10
+ export const today = () => process.env.PIPELINE_RUN_TODAY || nowIso().slice(0, 10);
11
+ export const pad = (n) => String(n).padStart(2, '0');
12
+
13
+ // ── 环境解析(测试注入点) ─────────────────────────────────────────────
14
+ export function ctxEnv(overrideRoot) {
15
+ const root = path.resolve(overrideRoot || process.env.PIPELINE_RUN_ROOT || process.cwd());
16
+ const bin = path.resolve(process.env.PIPELINE_RUN_BIN || path.join(root, '.agents', 'scripts'));
17
+ const gitPrefix = (process.env.PIPELINE_RUN_GIT_BIN || 'git').split(/\s+/).filter(Boolean);
18
+ return { root, bin, gitPrefix, runsDir: path.join(root, '.agents', 'cache', 'pipeline-runs') };
19
+ }
20
+
21
+ export function moduleList(ctx) {
22
+ try {
23
+ return fs.readFileSync(path.join(ctx.root, '.agents', 'workflow-modules.txt'), 'utf8')
24
+ .split(/\r?\n/).map((l) => l.trim()).filter((l) => l && !l.startsWith('#'));
25
+ } catch { return ['backend', 'frontend', 'ui', 'infra', 'integration', 'pipeline', 'wiki']; } // 夹具缺表时的回退默认(warning 由调用方打)
26
+ }
27
+
28
+ // ── run 文件(原子写;事件 append-only) ──────────────────────────────
29
+ export function runFileOf(ctx, runId) { return path.join(ctx.runsDir, `${runId}.json`); }
30
+
31
+ export function loadRun(ctx, runId) {
32
+ const f = runFileOf(ctx, runId);
33
+ if (!fs.existsSync(f)) return null;
34
+ try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch { return null; }
35
+ }
36
+
37
+ export function saveRun(ctx, run) {
38
+ fs.mkdirSync(ctx.runsDir, { recursive: true });
39
+ run.updatedAt = nowIso();
40
+ const f = runFileOf(ctx, run.runId);
41
+ const tmp = f + '.tmp';
42
+ fs.writeFileSync(tmp, JSON.stringify(run, null, 2) + '\n');
43
+ fs.renameSync(tmp, f);
44
+ }
45
+
46
+ export function emit(run, ev) { run.events.push({ t: nowIso(), ...ev }); }
47
+
48
+ export function latestRunId(ctx) {
49
+ if (!fs.existsSync(ctx.runsDir)) return null;
50
+ const ids = fs.readdirSync(ctx.runsDir).filter((f) => f.endsWith('.json')).map((f) => f.slice(0, -5)).sort();
51
+ for (let i = ids.length - 1; i >= 0; i--) {
52
+ const r = loadRun(ctx, ids[i]);
53
+ if (r && !['done', 'aborted'].includes(r.stopType)) return ids[i];
54
+ }
55
+ return ids[ids.length - 1] || null;
56
+ }
57
+
58
+ export function newRunId(requirement) {
59
+ const d = new Date();
60
+ const ascii = (requirement.toLowerCase().match(/[a-z0-9]{2,}/g) || []).slice(0, 3).join('-') || 'task';
61
+ const rand = Math.random().toString(36).slice(2, 6);
62
+ return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}-${ascii}-${rand}`;
63
+ }
@@ -19,9 +19,7 @@
19
19
  import fs from 'node:fs';
20
20
  import path from 'node:path';
21
21
  import { spawnSync } from 'node:child_process';
22
- import { fileURLToPath } from 'node:url';
23
22
 
24
- const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
25
23
  const KINDS = ['require', 'fix', 'verify', 'review', 'deploy'];
26
24
  const LEVELS = ['L0', 'L1', 'L2', 'L3'];
27
25
  const TERMINAL = ['done', 'superseded', 'cancelled', 'closed'];
@@ -29,61 +27,13 @@ const FIX_CAP = 3;
29
27
  const GIT_NEVER_FLAGS = ['--no-verify', '-n']; // 不变量:任何 git 调用不得携带(防御性断言,正常路径不会拼出)
30
28
 
31
29
  function fail(msg, code = 1) { console.error('❌ ' + msg); process.exit(code); }
32
- const nowIso = () => new Date().toISOString();
33
- const today = () => process.env.PIPELINE_RUN_TODAY || nowIso().slice(0, 10);
34
- const pad = (n) => String(n).padStart(2, '0');
35
-
36
- // ── 环境解析(测试注入点) ─────────────────────────────────────────────
37
- function ctxEnv(overrideRoot) {
38
- const root = path.resolve(overrideRoot || process.env.PIPELINE_RUN_ROOT || process.cwd());
39
- const bin = path.resolve(process.env.PIPELINE_RUN_BIN || path.join(root, '.agents', 'scripts'));
40
- const gitPrefix = (process.env.PIPELINE_RUN_GIT_BIN || 'git').split(/\s+/).filter(Boolean);
41
- return { root, bin, gitPrefix, runsDir: path.join(root, '.agents', 'cache', 'pipeline-runs') };
42
- }
43
-
44
- function moduleList(ctx) {
45
- try {
46
- return fs.readFileSync(path.join(ctx.root, '.agents', 'workflow-modules.txt'), 'utf8')
47
- .split(/\r?\n/).map((l) => l.trim()).filter((l) => l && !l.startsWith('#'));
48
- } catch { return ['backend', 'frontend', 'ui', 'infra', 'integration', 'pipeline', 'wiki']; } // 夹具缺表时的回退默认(warning 由调用方打)
49
- }
50
30
 
51
- // ── run 文件(原子写;事件 append-only) ──────────────────────────────
52
- function runFileOf(ctx, runId) { return path.join(ctx.runsDir, `${runId}.json`); }
53
-
54
- function loadRun(ctx, runId) {
55
- const f = runFileOf(ctx, runId);
56
- if (!fs.existsSync(f)) return null;
57
- try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch { return null; }
58
- }
59
-
60
- function saveRun(ctx, run) {
61
- fs.mkdirSync(ctx.runsDir, { recursive: true });
62
- run.updatedAt = nowIso();
63
- const f = runFileOf(ctx, run.runId);
64
- const tmp = f + '.tmp';
65
- fs.writeFileSync(tmp, JSON.stringify(run, null, 2) + '\n');
66
- fs.renameSync(tmp, f);
67
- }
68
31
 
69
- function emit(run, ev) { run.events.push({ t: nowIso(), ...ev }); }
70
-
71
- function latestRunId(ctx) {
72
- if (!fs.existsSync(ctx.runsDir)) return null;
73
- const ids = fs.readdirSync(ctx.runsDir).filter((f) => f.endsWith('.json')).map((f) => f.slice(0, -5)).sort();
74
- for (let i = ids.length - 1; i >= 0; i--) {
75
- const r = loadRun(ctx, ids[i]);
76
- if (r && !['done', 'aborted'].includes(r.stopType)) return ids[i];
77
- }
78
- return ids[ids.length - 1] || null;
79
- }
80
-
81
- function newRunId(requirement) {
82
- const d = new Date();
83
- const ascii = (requirement.toLowerCase().match(/[a-z0-9]{2,}/g) || []).slice(0, 3).join('-') || 'task';
84
- const rand = Math.random().toString(36).slice(2, 6);
85
- return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}-${ascii}-${rand}`;
86
- }
32
+ import { nowIso, today, ctxEnv, moduleList, loadRun, saveRun, emit, latestRunId, newRunId } from './pipeline-run-run.mjs';
33
+ import { fmGet, placeholdersIn, sectionBody, validateDraftContent, closeoutComplete, summarizeDoc } from './pipeline-run-docs.mjs';
34
+ // 拆分(2026-10-08 engine-quality-batch):文档纯函数层 / run 存储层迁出本文件(判据注释随函数迁走,
35
+ // 对外导出面经下行 re-export 保持不变——stageBar/parseKV 仍在本文件);本文件 = CLI 编排层 + 状态机。
36
+ export { placeholdersIn, validateDraftContent, closeoutComplete } from './pipeline-run-docs.mjs';
87
37
 
88
38
  // ── 门执行(唯一子进程出口;记录真实退出码) ──────────────────────────
89
39
  function runNode(ctx, run, script, args) {
@@ -114,78 +64,8 @@ function runGit(ctx, run, args) {
114
64
  function readDoc(ctx, rel) {
115
65
  try { return fs.readFileSync(path.join(ctx.root, rel), 'utf8'); } catch { return null; }
116
66
  }
117
- function fmGet(text, key) {
118
- const m = text.split(/\r?\n/).find((l) => l.startsWith(`${key}:`));
119
- return m ? m.slice(key.length + 1).trim() : null;
120
- }
121
67
  function docState(ctx, rel) { const t = readDoc(ctx, rel); return t ? fmGet(t, '状态') : null; }
122
68
 
123
- // 占位符判定(复核 P1-3 收窄,2026-10-02):先剔除 code span(`…`)与引号串("…")——
124
- // 正文合法的命令语法占位(如 `next --delegated "<原话>"`)几乎总在 code span 内,
125
- // 模板实占位符(如 <为什么做;写明需求来源…>)从不包 code span;再匹配剩余裸尖括号。
126
- export function placeholdersIn(text) {
127
- const stripped = String(text)
128
- .replace(/^```[\s\S]*?^```$/gm, '') // 复核 P2-B:跨行 code fence 整块剔除(围栏内命令示例占位不属模板占位符)
129
- .replace(/`[^`\n]*`/g, '')
130
- .replace(/"[^"\n]*"/g, '');
131
- const hits = [];
132
- for (const re of [/<[^>\n]{1,60}>/g, /YYYY-MM-DD 用户/g]) {
133
- let m; while ((m = re.exec(stripped))) hits.push(m[0]);
134
- }
135
- return [...new Set(hits)].slice(0, 5);
136
- }
137
-
138
- function sectionBody(text, title) {
139
- const lines = text.split(/\r?\n/);
140
- const i = lines.findIndex((l) => l.trim() === `## ${title}` || l.trim() === `## ${title}(L1 微改动无实质内容可删本节,不硬填)` || l.trim().startsWith(`## ${title}`));
141
- if (i < 0) return null;
142
- const body = [];
143
- for (let j = i + 1; j < lines.length; j++) {
144
- if (/^##\s/.test(lines[j])) break;
145
- body.push(lines[j]);
146
- }
147
- return body.join('\n').trim();
148
- }
149
-
150
- export function validateDraftContent(text, kind, level) {
151
- const problems = [];
152
- if (!text) return ['文件不存在或为空'];
153
- const state = fmGet(text, '状态');
154
- if (!state) problems.push('frontmatter 缺「状态:」行');
155
- if (kind !== 'incident' && !fmGet(text, '级别')) problems.push('frontmatter 缺「级别:」行');
156
- const ph = placeholdersIn(text);
157
- if (ph.length) problems.push(`模板占位符残留:${ph.join('、')}`);
158
- const required = {
159
- intent: level === 'L1' ? ['背景与问题', '目标', '验收标准'] : ['背景与问题', '历史教训/防复发', '目标', '非目标', '约束', '验收标准'],
160
- spec: ['功能行为', '数据流', '系统改动', '约束遵守映射'],
161
- plan: level === 'L1' ? ['改动方案', '约束与风险', '验证计划'] : ['改动方案', '任务拆解', '执行顺序', '验证计划'],
162
- incident: ['时间线', '根因', '复盘三件套'],
163
- }[kind] || [];
164
- for (const sec of required) {
165
- const b = sectionBody(text, sec);
166
- if (b === null) { if (!(level === 'L1' && ['非目标', '约束', '任务拆解', '执行顺序'].includes(sec))) problems.push(`缺节:## ${sec}`); }
167
- else if (!b) problems.push(`节为空:## ${sec}`);
168
- }
169
- return problems;
170
- }
171
-
172
- export function closeoutComplete(text) {
173
- const body = sectionBody(text, '验收标准');
174
- if (body === null) return { ok: false, problems: ['缺「## 验收标准」节'] };
175
- const lines = body.split(/\r?\n/);
176
- const open = lines.filter((l) => /^\s*-\s\[\s\]\s/.test(l));
177
- // 复核 P2-6:证据冒号后须直接跟内容(非空白、非收括号)——「(证据:)」空值不算过
178
- const checkedNoEv = lines.filter((l) => /^\s*-\s\[x\]\s/.test(l) && !/证据[::][^)\s]/.test(l));
179
- const problems = [];
180
- if (open.length) problems.push(`未勾验收 ${open.length} 条`);
181
- if (checkedNoEv.length) problems.push(`已勾缺证据 ${checkedNoEv.length} 条`);
182
- return { ok: problems.length === 0, problems };
183
- }
184
-
185
- function summarizeDoc(text) {
186
- const body = text.split(/\r?\n/).filter((l) => l.trim() && !l.startsWith('---') && !/^状态:|^级别:|^日期:|^模块:|^备注:|^risk_level:/.test(l));
187
- return body.slice(0, 3).join(' / ').slice(0, 200);
188
- }
189
69
 
190
70
  // ── 工单构建 ───────────────────────────────────────────────────────────
191
71
  function wo(run, o) {
@@ -211,7 +91,7 @@ function stopConfirm(ctx, run, docRel, points, ledger) {
211
91
  printRun(ctx, run);
212
92
  process.exit(0);
213
93
  }
214
- function stopGateFail(ctx, run, stage, problems, exitHint) {
94
+ function stopGateFail(ctx, run, stage, problems) {
215
95
  run.workOrder = null;
216
96
  run.stopType = 'gate-fail';
217
97
  run.stage = stage;
@@ -318,11 +198,11 @@ function confirmGate(ctx, run, docRel, quote, to) {
318
198
  const tail = (r.stderr || r.stdout).split(/\r?\n/).filter(Boolean).slice(-6).join(' | ');
319
199
  return { ok: false, msg: `confirm-doc exit ${r.exit}:${tail}` };
320
200
  }
321
- emit(run, { type: 'confirm', doc: docRel, to: to || nextConfirmTarget(docState(ctx, docRel), docRel), source: quote ? 'chat-delegated' : 'tty', ...(quote ? { quote } : {}) });
201
+ emit(run, { type: 'confirm', doc: docRel, to: to || nextConfirmTarget(docState(ctx, docRel)), source: quote ? 'chat-delegated' : 'tty', ...(quote ? { quote } : {}) });
322
202
  return { ok: true };
323
203
  }
324
204
  // 语义上 confirm-doc 自行决定目标态(approved/done/fixed/closed);事件里记录结果态
325
- function nextConfirmTarget(curState, docRel) { return curState || 'approved'; }
205
+ function nextConfirmTarget(curState) { return curState || 'approved'; }
326
206
 
327
207
  // managedFaceCheck(复核 P2-4 修复 A′,2026-10-02 用户拍板):提交集命中 kit.json 台账面或包源
328
208
  // templates/_agents/ 时——包源环境(bin/flow-kit.mjs 可达)自动跑 sync 刷台账并把 kit.json 与 sync
@@ -69,6 +69,31 @@ export const POLICIES = {
69
69
  riskLevelSince: '2026-10-01',
70
70
  verifySince: '2026-10-07', // 测试绿机器凭证生效日(2026-10-07 verify-evidence);v1-v4 无此键 → 前置与对账整体跳过
71
71
  },
72
+ // 版本 6 = 版本 5 的全部键 + verifyDocSince(测试绿凭证与被证明对象的绑定日,2026-10-08 verify-doc-binding)。
73
+ // 背景(2026-10-08 verify-doc-binding):v5 的凭证行只记 {ts, exitCode, suite, passed, failed},
74
+ // **没有字段能承载「它是为哪一单落的」**,而两个消费方(confirm-doc 置 done 前置 / 检查 8 对账)
75
+ // 都只问「全仓任意一份 24h 内绿行」→ 跑一次 verify 就能给全仓任意一单的「测试绿」声明背书,
76
+ // 这是自证式验证的教科书形态(证据与被证明对象零绑定)。
77
+ // 本版本只加一个开关键:**缺键(v1-v5)时两个消费方维持 v5 的全局行为逐字不变**(存量装户与存量
78
+ // fixture 零变化、零新增告警);本仓升 v6 后才按 doc 精确匹配。抄全键仍是铁律(见 v2 注释)。
79
+ 6: {
80
+ moduleSince: '2026-09-22',
81
+ check14Since: '2026-09-26',
82
+ confirmDocsEffective: '2026-09-27',
83
+ confirmIncidentsEffective: '2026-09-28',
84
+ bindingTs: '2026-09-28',
85
+ check17UnclosedAfter: '0.8.0',
86
+ stageGateSince: '2026-09-29',
87
+ riskLevelSince: '2026-10-01',
88
+ verifySince: '2026-10-07',
89
+ // 凭证按 doc 绑定的生效**时刻**(ISO 8601,含 T/Z);v1-v5 无此键 → 两个消费方维持 v5 全局行为。
90
+ // 2026-10-09 verifydoc-anchor:值从日期 '2026-10-08' 升级为时刻——本仓 v6 上线当日(本地 17:37 =
91
+ // 09:37Z)之前 done 的单(selfmeasure 00:22Z / gate-roi-metrics 03:25Z)在日期粒度下被误纳入精确匹配、
92
+ // 产 3 条不可消退 advisory(它们关单时 --doc 机制尚不存在,无从补证);升级为时刻后按 done ts 精确比较,
93
+ // 机制上线前的存量单退回 v5 全局布尔(不误报、不伪造),与检查 15/18 的生效日锚同构。
94
+ // 消费方:检查 8 的 verifyDocInWindow(done ts ≥ 本值才启用精确匹配)。
95
+ verifyDocSince: '2026-10-08T09:37:35Z',
96
+ },
72
97
  };
73
98
 
74
99
  export function loadKitPolicy(root) {
@@ -90,17 +115,26 @@ export function auditEnabled(policy) {
90
115
  return policy.audit !== false;
91
116
  }
92
117
 
93
- // ---- 测试绿凭证判定(2026-10-07 verify-evidence)----
118
+ // ---- 测试绿凭证判定(2026-10-07 verify-evidence;2026-10-08 verify-doc-binding 增 doc 绑定)----
94
119
  // 单源(confirm-doc done 前置与 check-loop 检查 8 对账共用——防两处窗口字面量漂移,N3 教训):
95
- // hasFreshVerifyLine(lines, now):verifications.jsonl 的行数组里是否存在「ts 距 now ≤ 24h 且
120
+ // hasFreshVerifyLine(lines, now, doc):verifications.jsonl 的行数组里是否存在「ts 距 now ≤ 24h 且
96
121
  // exitCode === 0」的绿行。坏行(非 JSON / 缺 ts)跳过不抛;窗口 24h = 覆盖「跑 verify → 跨会话/
97
122
  // 隔夜 → 次日关单」节奏(spec 2026-10-07-verify-evidence 论证)。
98
- export function hasFreshVerifyLine(lines, now = Date.now()) {
123
+ //
124
+ // doc 绑定(2026-10-08 verify-doc-binding,第三参):
125
+ // doc 为空/缺省 → 逻辑与 2026-10-07 版**逐字相同**(全量行扫描)——v1-v5 装户走这条,零变化;
126
+ // doc 非空 → 额外要求该行 `e.doc === doc`(**精确等值**,不做前缀/子串/形态匹配——
127
+ // 沿 check8-digit-sha-misfire「文本启发式须可被实证兜底,禁以形态抢先分类」纪律)。
128
+ // 为什么必须精确:放宽成「包含即认」等于给跨单背书开后门,那正是本单要消灭的漏洞。
129
+ // 不带 doc 落账的行(CI 场景)**不给任何单背书**——doc 为 null 时 e.doc === doc 恒假。
130
+ export function hasFreshVerifyLine(lines, now = Date.now(), doc = null) {
131
+ const byDoc = typeof doc === 'string' && doc !== '';
99
132
  for (const line of lines || []) {
100
133
  if (!line || !line.trim()) continue;
101
134
  try {
102
135
  const e = JSON.parse(line);
103
136
  if (e && e.exitCode === 0 && typeof e.ts === 'string') {
137
+ if (byDoc && e.doc !== doc) continue;
104
138
  const t = Date.parse(e.ts);
105
139
  if (Number.isFinite(t) && now - t <= 24 * 60 * 60 * 1000) return true;
106
140
  }
@@ -112,12 +112,12 @@ function printDiff(result) {
112
112
 
113
113
  const isMain = process.argv[1] && process.argv[1].endsWith('source-sync-check.mjs');
114
114
  if (isMain) {
115
- let jsonMode = false, diffMode = false, gateMode = false;
115
+ let jsonMode = false, gateMode = false;
116
116
  let pkgRoot = null, target = null;
117
117
  for (let i = 0; i < process.argv.length - 2; i++) {
118
118
  const a = process.argv[2 + i];
119
119
  if (a === '--json') jsonMode = true;
120
- else if (a === '--diff') diffMode = true;
120
+ else if (a === '--diff') { /* diff 为默认输出模式,无需置位 */ }
121
121
  else if (a === '--gate') gateMode = true;
122
122
  else if (a === '--pkg-root') pkgRoot = process.argv[2 + i + 1];
123
123
  else if (a === '--target') target = process.argv[2 + i + 1];
@@ -125,7 +125,7 @@ if (!SRC_ROOT) {
125
125
  drift: r.drift.map((d) => ({ rel: d.rel, pkgSha: d.pkgSha.slice(0, 8), tgtSha: d.tgtSha.slice(0, 8) })),
126
126
  }, null, 2);
127
127
  let parsed = null;
128
- try { parsed = JSON.parse(jsonOut); } catch {}
128
+ try { parsed = JSON.parse(jsonOut); } catch { /* 非 JSON 输出时 parsed 保持 null,S6 判失败路径 */ }
129
129
  check('S6 --json 输出可解析', parsed && typeof parsed === 'object' && Array.isArray(parsed.missing));
130
130
  }
131
131
 
@@ -125,7 +125,6 @@ for (const line of fs.readFileSync(path.join(WIKI, 'INDEX.md'), 'utf8').split(/\
125
125
  }
126
126
 
127
127
  const diff = (a, b) => [...a].filter((x) => !b.has(x));
128
- const eq = (a, b) => a.size === b.size && diff(a, b).length === 0;
129
128
 
130
129
  // ---- 1. 文件级三方比对 ----
131
130
  for (const [label, extra] of [
@@ -14,7 +14,13 @@
14
14
  // 且**不落测试绿凭证**(凭证语义 = 测试真跑绿,跳过即无凭证,防假绿),并提示用
15
15
  // --test-cmd "<项目测试命令>" 指定;显式 --test-cmd 不受此判定影响(2026-10-07 papercut)。
16
16
  // 测试:node templates/_agents/scripts/verify.test.mjs(--test-cmd 注入假命令 + CHECK_LOOP_ROOT 注入夹具,不触真实 npm test 与 workflow)
17
- // 用法:node .agents/scripts/verify.mjs [--test-cmd "<shell 命令>"](--test-cmd 用于测试注入,或在无根 package.json 的装户仓指定项目测试命令)
17
+ // --doc 凭证绑定(2026-10-08 verify-doc-binding):落账行增 `doc` 字段,写入**相对仓库根的 posix 形态**
18
+ // (win32 反斜杠 / posix 斜杠 / 绝对路径三种写法经 resolve+relative+分隔符归一后落同一值),使凭证
19
+ // 与被证明的那一单绑定。不带 --doc 时仍落行但**不写该键**——CI(kit-ci.yml)无单次上下文,照常记录
20
+ // 事实;这样的行不给任何单背书(消费方按 doc 精确匹配时 e.doc 为 undefined 恒不命中)。
21
+ // 用法:node .agents/scripts/verify.mjs [--test-cmd "<shell 命令>"] [--doc <本单 workflow 路径>]
22
+ // (--test-cmd 用于测试注入,或在无根 package.json 的装户仓指定项目测试命令;
23
+ // --doc 把凭证绑到本单,如 --doc workflow/intents/2026-10-08-x.md)
18
24
  import fs from 'node:fs';
19
25
  import { spawnSync } from 'node:child_process';
20
26
  import path from 'node:path';
@@ -27,6 +33,22 @@ const testCmd = explicitTestCmd ?? 'npm test';
27
33
  const checkLoop = path.join(SCRIPT_DIR, 'check-loop.mjs');
28
34
  const ROOT = process.env.CHECK_LOOP_ROOT || path.resolve(SCRIPT_DIR, '..', '..');
29
35
 
36
+ // --doc 解析(沿 --test-cmd 同款取下一 argv 的写法;缺值报用法错误 exit 1 与 --test-cmd 对称)。
37
+ // 归一基准 = **ROOT**(不是 cwd)——消费方两处都以仓库根为基准(check-loop 检查 8 用
38
+ // path.relative(ROOT, intent);confirm-doc 的 docRelOf 用 path.resolve(root, doc))。若这里按 cwd
39
+ // 解析而进程不在仓库根(CI / 钩子 / CHECK_LOOP_ROOT 注入夹具),落下的 doc 会是跨目录长路径,
40
+ // 凭证永远匹配不上——**基准必须三处同源**。
41
+ // 不校验目标文件是否存在:凭证记录的是「跑了什么」而非「被证明对象是否有效」,存在性校验属裁决,
42
+ // 越界(沿本件「只编排不裁决」语义)。
43
+ if (args.includes('--doc') && !args[args.indexOf('--doc') + 1]) {
44
+ console.error('verify: --doc 缺值——用法 node .agents/scripts/verify.mjs [--test-cmd "<shell 命令>"] [--doc <本单 workflow 路径>]');
45
+ process.exit(1);
46
+ }
47
+ const docArg = args.includes('--doc') ? args[args.indexOf('--doc') + 1] : null;
48
+ const docRel = docArg
49
+ ? path.relative(ROOT, path.resolve(ROOT, docArg)).split(path.sep).join('/')
50
+ : null;
51
+
30
52
  // 装户仓判定:默认命令只在**当前目录**有 package.json 时才跑(npm test 亦在该目录执行)。
31
53
  // 跳过时后续不落测试绿凭证(见文末落账段)。
32
54
  const hasRootPackageJson = fs.existsSync(path.join(process.cwd(), 'package.json'));
@@ -66,9 +88,12 @@ try {
66
88
  const entry = { ts: new Date().toISOString(), exitCode: 0, suite: 'npm test' };
67
89
  if (m) { entry.passed = Number(m[1]); entry.failed = Number(m[2]); }
68
90
  if (process.env.PIPELINE_RUN_ID) entry.runId = process.env.PIPELINE_RUN_ID;
91
+ // doc 键:未传 --doc 时**不写该键**(不是写 null)——消费方按 doc 精确匹配,undefined 与 null 都恒不命中,
92
+ // 但缺键让「本行没有绑定对象」这件事在台账盘面上直接可读。
93
+ if (docRel) entry.doc = docRel;
69
94
  fs.mkdirSync(path.join(ROOT, '.agents'), { recursive: true });
70
95
  fs.appendFileSync(path.join(ROOT, '.agents', 'verifications.jsonl'), `${JSON.stringify(entry)}\n`);
71
- console.log(`[verify] 🧾 测试绿凭证已落账 verifications.jsonl${entry.passed !== undefined ? `(PASS ${entry.passed} / FAIL ${entry.failed})` : ''}`);
96
+ console.log(`[verify] 🧾 测试绿凭证已落账 verifications.jsonl${entry.passed !== undefined ? `(PASS ${entry.passed} / FAIL ${entry.failed})` : ''}${docRel ? ` 绑定本单 ${docRel}` : '(未带 --doc:不给任何单背书)'}`);
72
97
  }
73
98
  } catch (e) {
74
99
  console.error(`[verify] ⚠️ 凭证落账失败(${e.code || e.message})——不阻断,关单门前置将按无凭证告警`);
@@ -132,6 +132,59 @@ const run = (argv, opts = {}) => spawnSync(process.execPath, [VERIFY, ...argv],
132
132
  fs.rmSync(bare, { recursive: true, force: true });
133
133
  }
134
134
 
135
+ // ---- 场景 8:--doc 凭证绑定(2026-10-08 verify-doc-binding)----
136
+ // ① 带 --doc → 末行含 doc 且为规范化 posix 相对路径;② 不带 → 末行**无 doc 键**(不是 null);
137
+ // ③ win32 反斜杠 / posix / 绝对路径三种写法落同一值(归一是本单的地基,错一处=凭证永远匹配不上);
138
+ // ④ 缺值 → exit 1 用法错误;⑤ 跳过步骤 1 时即便带 --doc 仍不落账(防假绿语义不得被 --doc 破坏)。
139
+ {
140
+ const readLast = (root) => {
141
+ const p = path.join(root, '.agents', 'verifications.jsonl');
142
+ if (!fs.existsSync(p)) return null;
143
+ const lines = fs.readFileSync(p, 'utf8').trim().split('\n').filter(Boolean);
144
+ return lines.length ? JSON.parse(lines[lines.length - 1]) : null;
145
+ };
146
+
147
+ const rootA = mkfix();
148
+ const rel = 'workflow/intents/2026-10-08-demo.md';
149
+ const rA = run(['--test-cmd', `node ${mkCmd(0, '合计: PASS 3 / FAIL 0\n')}`, '--doc', rel],
150
+ { env: { ...process.env, CHECK_LOOP_ROOT: rootA } });
151
+ const eA = readLast(rootA);
152
+ check('场景 8①:带 --doc → 末行含 doc 且为 posix 相对路径',
153
+ rA.status === 0 && eA && eA.doc === rel && rA.stdout.includes('绑定本单'), `exit=${rA.status} entry=${JSON.stringify(eA)}`);
154
+
155
+ const rootB = mkfix();
156
+ run(['--test-cmd', `node ${mkCmd(0)}`], { env: { ...process.env, CHECK_LOOP_ROOT: rootB } });
157
+ const eB = readLast(rootB);
158
+ check('场景 8②:不带 --doc → 仍落行但**无 doc 键**(CI 路径不退化;此行不给任何单背书)',
159
+ eB && eB.exitCode === 0 && !('doc' in eB) && eB.suite === 'npm test', JSON.stringify(eB));
160
+
161
+ // 归一:cwd 设为仓库根时,win32 反斜杠 / posix / 绝对路径三种写法须落同一值
162
+ const rootC = mkfix();
163
+ const abs = path.join(rootC, rel);
164
+ const winStyle = rel.split('/').join(path.sep);
165
+ run(['--test-cmd', `node ${mkCmd(0)}`, '--doc', winStyle], { cwd: rootC, env: { ...process.env, CHECK_LOOP_ROOT: rootC } });
166
+ const eC1 = readLast(rootC);
167
+ run(['--test-cmd', `node ${mkCmd(0)}`, '--doc', abs], { cwd: rootC, env: { ...process.env, CHECK_LOOP_ROOT: rootC } });
168
+ const eC2 = readLast(rootC);
169
+ check('场景 8③:反斜杠 / posix / 绝对路径三种写法落同一 doc 值(归一是地基)',
170
+ eC1 && eC1.doc === rel && eC2 && eC2.doc === rel, `win=${JSON.stringify(eC1 && eC1.doc)} abs=${JSON.stringify(eC2 && eC2.doc)}`);
171
+
172
+ const rootD = mkfix();
173
+ const rD = run(['--test-cmd', `node ${mkCmd(0)}`, '--doc'], { env: { ...process.env, CHECK_LOOP_ROOT: rootD } });
174
+ check('场景 8④:--doc 缺值 → exit 1 + 用法提示',
175
+ rD.status === 1 && /--doc 缺值/.test(rD.stderr), `exit=${rD.status} ${rD.stderr}`);
176
+
177
+ const rootE = mkfix();
178
+ const bare = fs.mkdtempSync(path.join(os.tmpdir(), 'verify-bare-doc-'));
179
+ const rE = run(['--doc', rel], { cwd: bare, env: { ...process.env, CHECK_LOOP_ROOT: rootE } });
180
+ check('场景 8⑤:步骤 1 跳过时即便带 --doc 仍不落账(防假绿语义不被 --doc 破坏)',
181
+ rE.status === 0 && !fs.existsSync(path.join(rootE, '.agents', 'verifications.jsonl')) && rE.stdout.includes('不落测试绿凭证'),
182
+ `exit=${rE.status} ${rE.stdout}`);
183
+
184
+ for (const r of [rootA, rootB, rootC, rootD, rootE]) fs.rmSync(r, { recursive: true, force: true });
185
+ fs.rmSync(bare, { recursive: true, force: true });
186
+ }
187
+
135
188
  console.log(`\n合计: PASS ${pass} / FAIL ${fail}`);
136
189
  if (fail) {
137
190
  console.log('\n用法:node templates/_agents/scripts/verify.test.mjs');
@@ -14,7 +14,6 @@ import { spawnSync } from 'node:child_process';
14
14
  import { fileURLToPath } from 'node:url';
15
15
  import { parseLoopHardBlocks, parseAcceptance, detectAlerts, parseRunFile, runCard, safeRunPath } from './workflow-board-server.mjs';
16
16
  import { computeFingerprint } from './confirm-doc.mjs';
17
- import { stageBar } from './pipeline-run.mjs';
18
17
 
19
18
  let pass = 0;
20
19
  let fail = 0;
@@ -2,7 +2,6 @@
2
2
  // 判据:① 真实单源文件解析 + 不变量通过 ② 坏数据(坏行/重复键/重复值/缺键/子集破/重叠)fail-loud
3
3
  // ③ 缺文件 fail-loud ④ sh 消费方同口径(sed 可取到全部 8 键)
4
4
  // 用法:node templates/_agents/scripts/workflow-enums.test.mjs(npm test 随跑)
5
- import fs from 'node:fs';
6
5
  import path from 'node:path';
7
6
  import { execSync } from 'node:child_process';
8
7
  import { fileURLToPath } from 'node:url';
@@ -7,4 +7,7 @@ ensure-board.mjs | 看板启动器(需真实端口/进程环境) | 无(工
7
7
  policy.mjs | 库件 | 被 check-loop.test.mjs 直接 import 覆盖
8
8
  stage-gates.mjs | 库件 | 被 check-loop.test.mjs 直接 import 覆盖(fill-*/confirm-doc.test 消费其行为)
9
9
  verify-wiki-consistency.mjs | wiki 一致性校验工具 | 无(工具豁免,后续批次评估补测)
10
- wiki-search.mjs | 检索库件 | 无(工具豁免,后续批次评估补测)
10
+ wiki-search.mjs | 检索库件 | 无(工具豁免,后续批次评估补测)
11
+ # 2026-10-08 engine-quality-batch:pipeline-run 三层拆分的库件层——纯函数/存储层由 pipeline-run.test.mjs 全量覆盖(断言零改动全过),单建同名测试属形式主义(plan 偏离留痕 ①)
12
+ pipeline-run-docs.mjs | 库件(文档纯函数层,2026-10-08 engine-quality-batch 拆分) | 由 pipeline-run.test.mjs 全量覆盖(断言零改动 29 组全过)
13
+ pipeline-run-run.mjs | 库件(run 存储层,同上拆分) | 由 pipeline-run.test.mjs 全量覆盖(含 PIPELINE_RUN_* 注入契约场景)