agentic-flow-kit 0.4.0 → 0.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 (86) hide show
  1. package/README.md +1 -0
  2. package/modules/hosts/omp/agents/implementer.md +45 -6
  3. package/modules/hosts/omp/agents/independent-reviewer.md +42 -5
  4. package/modules/hosts/omp/agents/ui-verifier.md +41 -6
  5. package/modules/hosts/opencode/agents/implementer.md +45 -1
  6. package/modules/hosts/opencode/agents/independent-reviewer.md +42 -1
  7. package/modules/hosts/opencode/agents/ui-verifier.md +41 -1
  8. package/modules/hosts/opencode/commands/build.md +55 -7
  9. package/modules/hosts/opencode/commands/deploy.md +51 -6
  10. package/modules/hosts/opencode/commands/design.md +34 -6
  11. package/modules/hosts/opencode/commands/maintain.md +71 -6
  12. package/modules/hosts/opencode/commands/new-task.md +59 -6
  13. package/modules/hosts/opencode/commands/plan.md +32 -6
  14. package/modules/hosts/opencode/commands/review.md +40 -6
  15. package/modules/hosts/opencode/commands/test.md +56 -8
  16. package/modules/hosts/trae/agents/implementer.md +44 -2
  17. package/modules/hosts/trae/agents/independent-reviewer.md +42 -1
  18. package/modules/hosts/trae/agents/ui-verifier.md +39 -3
  19. package/modules/hosts/trae/commands/wf-build.md +55 -7
  20. package/modules/hosts/trae/commands/wf-deploy.md +51 -6
  21. package/modules/hosts/trae/commands/wf-design.md +34 -6
  22. package/modules/hosts/trae/commands/wf-maintain.md +71 -6
  23. package/modules/hosts/trae/commands/wf-new-task.md +59 -6
  24. package/modules/hosts/trae/commands/wf-plan.md +32 -6
  25. package/modules/hosts/trae/commands/wf-review.md +40 -6
  26. package/modules/hosts/trae/commands/wf-test.md +56 -7
  27. package/modules/hosts/zcode/agents/implementer.md +45 -6
  28. package/modules/hosts/zcode/agents/independent-reviewer.md +42 -5
  29. package/modules/hosts/zcode/agents/ui-verifier.md +41 -5
  30. package/package.json +6 -3
  31. package/src/cli.mjs +15 -2
  32. package/src/doctor.mjs +138 -11
  33. package/src/init.test.mjs +25 -2
  34. package/src/pack.test.mjs +24 -0
  35. package/src/run-tests.mjs +4 -11
  36. package/src/sync-hosts.mjs +193 -0
  37. package/src/sync-hosts.test.mjs +170 -0
  38. package/src/sync.mjs +1 -0
  39. package/src/sync.test.mjs +10 -0
  40. package/templates/AGENTS.md +1 -1
  41. package/templates/_agents/commands/build.md +20 -0
  42. package/templates/_agents/commands/design.md +1 -0
  43. package/templates/_agents/commands/gate-checklist.md +91 -0
  44. package/templates/_agents/commands/maintain.md +1 -1
  45. package/templates/_agents/commands/plan.md +2 -1
  46. package/templates/_agents/commands/review.md +1 -0
  47. package/templates/_agents/commands/source-sync-check.md +103 -0
  48. package/templates/_agents/commands/sync-hosts.md +79 -0
  49. package/templates/_agents/commands/test.md +2 -1
  50. package/templates/_agents/notes/runtime-env.md +1 -1
  51. package/templates/_agents/rule-budgets.txt +1 -1
  52. package/templates/_agents/scripts/check-loop.mjs +517 -0
  53. package/templates/_agents/scripts/check-loop.sh +5 -808
  54. package/templates/_agents/scripts/check-loop.test.mjs +481 -0
  55. package/templates/_agents/scripts/doctor.test.mjs +298 -0
  56. package/templates/_agents/scripts/ensure-board.mjs +1 -1
  57. package/templates/_agents/scripts/fill-intent.mjs +59 -0
  58. package/templates/_agents/scripts/fill-intent.test.mjs +73 -0
  59. package/templates/_agents/scripts/fill-plan.mjs +60 -0
  60. package/templates/_agents/scripts/fill-plan.test.mjs +67 -0
  61. package/templates/_agents/scripts/fill-spec.mjs +52 -0
  62. package/templates/_agents/scripts/fill-spec.test.mjs +61 -0
  63. package/templates/_agents/scripts/gate-checklist.mjs +192 -0
  64. package/templates/_agents/scripts/gate-checklist.test.mjs +204 -0
  65. package/templates/_agents/scripts/gen-workflow-index.mjs +4 -2
  66. package/templates/_agents/scripts/kb-search.mjs +3 -1
  67. package/templates/_agents/scripts/source-sync-check.mjs +131 -0
  68. package/templates/_agents/scripts/source-sync-check.test.mjs +156 -0
  69. package/templates/_agents/scripts/verify.mjs +8 -10
  70. package/templates/_agents/scripts/verify.test.mjs +13 -1
  71. package/templates/_agents/scripts/wf-journal.mjs +158 -0
  72. package/templates/_agents/scripts/wf-journal.test.mjs +150 -0
  73. package/templates/_agents/scripts/workflow-board-server.mjs +9 -8
  74. package/templates/_agents/scripts/workflow-enums.mjs +68 -0
  75. package/templates/_agents/scripts/workflow-enums.test.mjs +110 -0
  76. package/templates/_agents/scripts/workflows-check.mjs +260 -0
  77. package/templates/_agents/scripts/workflows-check.test.mjs +267 -0
  78. package/templates/_agents/workflow-enums.txt +19 -0
  79. package/templates/_agents/workflows/_TEMPLATE.md +74 -0
  80. package/templates/_agents/workflows/pipeline-closing.md +29 -0
  81. package/templates/_agents/workflows/source-sync-repair.md +30 -0
  82. package/templates/_agents/workflows/steps//347/244/272/344/276/213-/351/203/250/347/275/262/351/252/214/350/257/201.md +10 -0
  83. package/templates/_agents/workflows//347/244/272/344/276/213-/345/271/266/350/241/214/345/256/236/347/216/260/350/257/204/345/256/241.md +16 -0
  84. package/templates/workflow/README.md +1 -1
  85. package/templates/_agents/cache/kb-index.json +0 -1
  86. package/templates/_agents/scripts/check-loop.test.sh +0 -1042
@@ -0,0 +1,481 @@
1
+ #!/usr/bin/env node
2
+ // check-loop.test.mjs — 闭环门禁 fixture 套件(2026-09-26 check-loop-node:自 check-loop.test.sh 全量移植)
3
+ // 方法:现场构造临时 workflow 根(含枚举单源内联件),spawn node check-loop.mjs(CHECK_LOOP_ROOT 注入,
4
+ // git 模式场景改 cwd 注入),断言 exit code 与输出关键词——场景集与关键词断言沿 sh 版逐条移植。
5
+ // git 场景用 git init 现场建仓;rule-budget --staged 两场景直调 sh(rule-budget.sh 仍为 sh——
6
+ // git 钩子环境保证;无 sh 环境打印 SKIP 不算失败)。
7
+ // 用法:node templates/_agents/scripts/check-loop.test.mjs(npm test 随跑)
8
+ import fs from 'node:fs';
9
+ import os from 'node:os';
10
+ import path from 'node:path';
11
+ import { spawnSync } from 'node:child_process';
12
+ import { fileURLToPath } from 'node:url';
13
+
14
+ const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
15
+ const CHECK_LOOP = path.join(SCRIPT_DIR, 'check-loop.mjs');
16
+
17
+ let pass = 0;
18
+ let fail = 0;
19
+ const check = (desc, cond, detail = '') => {
20
+ if (cond) { pass++; console.log(`PASS ${desc}`); }
21
+ else { fail++; console.log(`FAIL ${desc}${detail ? '\n ' + detail : ''}`); }
22
+ };
23
+
24
+ // mkfix:空 fixture 根(四目录 + 枚举单源内联 8 键——fixture 自包含,测消费逻辑而非当前词表值)
25
+ const ENUMS_FIXTURE = [
26
+ 'doc.status.all=draft approved done superseded cancelled',
27
+ 'doc.status.confirmed=approved done superseded cancelled',
28
+ 'doc.status.active=draft approved',
29
+ 'doc.status.terminal=done superseded cancelled',
30
+ 'doc.status.abandoned=superseded cancelled',
31
+ 'incident.status.all=open fixed closed',
32
+ 'incident.status.active=open',
33
+ 'level.all=L0 L1 L2 L3',
34
+ ].join('\n') + '\n';
35
+
36
+ const mkfix = () => {
37
+ const d = fs.mkdtempSync(path.join(os.tmpdir(), 'check-loop-test-'));
38
+ for (const s of ['intents', 'specs', 'plans', 'incidents']) {
39
+ fs.mkdirSync(path.join(d, 'workflow', s), { recursive: true });
40
+ }
41
+ fs.mkdirSync(path.join(d, '.agents'), { recursive: true });
42
+ fs.writeFileSync(path.join(d, '.agents', 'workflow-enums.txt'), ENUMS_FIXTURE);
43
+ return d;
44
+ };
45
+ const w = (root, rel, content) => fs.writeFileSync(path.join(root, rel), content);
46
+
47
+ const INTENT = (name, fm, body = '') => `---\n${fm}\n---\n# INTENT — ${name}\n${body}`;
48
+ const PLAN = (name, fm, body = '') => `---\n${fm}\n---\n# PLAN — ${name}\n${body}`;
49
+ const SPEC = (name, fm, body = '') => `---\n${fm}\n---\n# SPEC — ${name}\n${body}`;
50
+ const 三件套 = (refs) => `## 复盘三件套(缺一不可)
51
+
52
+ 1. 结构性修复
53
+ - 修复 commit:abc1234
54
+ - 影响环境:dev
55
+ - 是否需要新 intent:
56
+ ${refs ? ' - 是 → ../intents/2026-09-12-ghost.md\n' : ' - 否 → 理由:实现 bug 单点修复\n'}
57
+ 2. 防复发验证
58
+ - 回归清单条目在案
59
+
60
+ 3. 规范条目
61
+ - AGENTS.md 某节
62
+ `;
63
+
64
+ // run:默认 CHECK_LOOP_ROOT 注入(fixture 模式);{git:true} 时 cwd 注入(真 git 模式,测 tracked 过滤)
65
+ const run = (root, opts = {}) => spawnSync(process.execPath, [CHECK_LOOP], {
66
+ ...(opts.git ? { cwd: root } : { cwd: ROOT_CWD, env: { ...process.env, CHECK_LOOP_ROOT: root } }),
67
+ encoding: 'utf8',
68
+ });
69
+ const ROOT_CWD = process.cwd();
70
+ const outOf = (r) => `${r.stdout || ''}${r.stderr || ''}`;
71
+ const expectHard = (desc, root, kw) => {
72
+ const r = run(root);
73
+ check(desc, r.status === 1 && outOf(r).includes(kw), `exit=${r.status}\n${outOf(r)}`);
74
+ };
75
+ const expectOk = (desc, root) => {
76
+ const r = run(root);
77
+ check(desc, r.status === 0, `exit=${r.status}\n${outOf(r)}`);
78
+ };
79
+ const gitInit = (root) => {
80
+ spawnSync(process.platform === 'win32' ? 'git.exe' : 'git', ['init', '-q'], { cwd: root });
81
+ };
82
+ const gitCommitAll = (root, msg) => {
83
+ const G = process.platform === 'win32' ? 'git.exe' : 'git';
84
+ spawnSync(G, ['add', '-A'], { cwd: root });
85
+ spawnSync(G, ['-c', 'user.email=t@t', '-c', 'user.name=t', 'commit', '-qm', msg], { cwd: root });
86
+ };
87
+ const rmfix = (root) => fs.rmSync(root, { recursive: true, force: true });
88
+
89
+ // ---- 场景 1:全合法闭环(intent+plan,L1,done 全勾验带证据)→ exit 0 ----
90
+ {
91
+ const T = mkfix();
92
+ w(T, 'workflow/intents/2026-09-12-ok.md', INTENT('ok', '状态: done\n级别: L1\n日期: 2026-09-12', '\n## 验收标准(可测试)\n- [x] 用例通过(证据:dotnet test 全绿)\n'));
93
+ w(T, 'workflow/plans/2026-09-12-ok.md', PLAN('ok', '状态: done\n级别: L1'));
94
+ expectOk('全合法闭环(L1 done 勾验带证据) → exit 0', T);
95
+ rmfix(T);
96
+ }
97
+
98
+ // ---- 场景 2:intent 缺 plan → hard ----
99
+ {
100
+ const T = mkfix();
101
+ w(T, 'workflow/intents/2026-09-12-a.md', INTENT('a', '状态: approved\n级别: L1\n日期: 2026-09-12'));
102
+ expectHard('intent 缺 plan → hard 配对断裂', T, '配对断裂');
103
+ rmfix(T);
104
+ }
105
+
106
+ // ---- 场景 3:L2 intent 缺 spec → hard ----
107
+ {
108
+ const T = mkfix();
109
+ w(T, 'workflow/intents/2026-09-12-b.md', INTENT('b', '状态: approved\n级别: L2\n日期: 2026-09-12'));
110
+ w(T, 'workflow/plans/2026-09-12-b.md', PLAN('b', '状态: approved\n级别: L2'));
111
+ expectHard('L2 intent 缺 spec → hard 配对断裂', T, 'intent 缺 spec');
112
+ rmfix(T);
113
+ }
114
+
115
+ // ---- 场景 4:L3 spec 缺确认三件 → hard ----
116
+ {
117
+ const T = mkfix();
118
+ w(T, 'workflow/intents/2026-09-12-c.md', INTENT('c', '状态: approved\n级别: L3\n日期: 2026-09-12'));
119
+ w(T, 'workflow/specs/2026-09-12-c.md', SPEC('c', '状态: approved\n级别: L3', '\n## 确认与复核\n'));
120
+ w(T, 'workflow/plans/2026-09-12-c.md', PLAN('c', '状态: approved\n级别: L3'));
121
+ expectHard('L3 spec 缺确认三件 → hard', T, 'L3 确认缺失');
122
+ rmfix(T);
123
+ }
124
+
125
+ // ---- 场景 5:L3 spec 确认三件齐全 → exit 0 ----
126
+ {
127
+ const T = mkfix();
128
+ w(T, 'workflow/intents/2026-09-12-d.md', INTENT('d', '状态: approved\n级别: L3\n日期: 2026-09-12'));
129
+ w(T, 'workflow/specs/2026-09-12-d.md', SPEC('d', '状态: approved\n级别: L3\n确认结果: approved\n确认时间: 2026-09-12', '\n## 确认与复核\n- 独立复核:已由 independent-reviewer 新会话完成,结论 approved\n'));
130
+ w(T, 'workflow/plans/2026-09-12-d.md', PLAN('d', '状态: approved\n级别: L3'));
131
+ expectOk('L3 确认三件齐全(frontmatter 2 件+正文独立复核) → exit 0', T);
132
+ rmfix(T);
133
+ }
134
+
135
+ // ---- 场景 6:新建 done intent 验收未勾验 → hard;带附注不影响判定(papercut #4 回归)----
136
+ {
137
+ const T = mkfix();
138
+ w(T, 'workflow/intents/2026-09-13-e.md', INTENT('e', '状态: done\n级别: L1\n日期: 2026-09-13\n备注: 回溯补档,原工作 2026-09-12 完成', '\n## 验收标准(可测试)\n- [ ] 用例通过\n'));
139
+ w(T, 'workflow/plans/2026-09-13-e.md', PLAN('e', '状态: done\n级别: L1'));
140
+ expectHard('新建 done 未勾验(附注在备注键) → hard 验收未对账', T, '验收未对账');
141
+ rmfix(T);
142
+ }
143
+
144
+ // ---- 场景 7:存量(<2026-09-12) done 未勾验 → warning,exit 0 ----
145
+ {
146
+ const T = mkfix();
147
+ w(T, 'workflow/intents/2026-09-01-f.md', INTENT('f', '状态: done\n级别: L1\n日期: 2026-09-01', '\n## 验收标准(可测试)\n- [ ] 用例通过\n'));
148
+ w(T, 'workflow/plans/2026-09-01-f.md', PLAN('f', '状态: done\n级别: L1'));
149
+ expectOk('存量 done 未勾验 → warning 不阻断', T);
150
+ rmfix(T);
151
+ }
152
+
153
+ // ---- 场景 8:incident 选「是」但 intent 不存在 → hard 回路断档 ----
154
+ {
155
+ const T = mkfix();
156
+ w(T, 'workflow/incidents/2026-09-12-g.md', `---\n状态: fixed\n级别: L2\n发现: 2026-09-12\n---\n# INCIDENT — g\n\n${三件套(true)}`);
157
+ w(T, 'workflow/plans/2026-09-12-g.md', PLAN('g', '状态: done\n级别: L2'));
158
+ w(T, 'workflow/specs/2026-09-12-g.md', SPEC('g', '状态: approved\n级别: L2'));
159
+ expectHard('incident 回路引用不存在 intent → hard 回路断档', T, '回路断档');
160
+ rmfix(T);
161
+ }
162
+
163
+ // ---- 场景 9:incident 流程 legacy → 豁免级别/配对检查,exit 0 ----
164
+ {
165
+ const T = mkfix();
166
+ w(T, 'workflow/incidents/2026-08-27-h.md', `---\n状态: closed\n流程: legacy\n发现: 2026-08-27\n---\n# INCIDENT — h(legacy 回填,无级别无配对)\n\n${三件套(false)}`);
167
+ expectOk('incident legacy 豁免配对 → exit 0', T);
168
+ rmfix(T);
169
+ }
170
+
171
+ // ---- 场景 10:无 frontmatter → 状态键缺失 warning,exit 0(非 L3) ----
172
+ {
173
+ const T = mkfix();
174
+ w(T, 'workflow/intents/2026-09-12-i.md', '# INTENT — i(未迁移旧格式)\n\n状态:draft\n');
175
+ w(T, 'workflow/plans/2026-09-12-i.md', PLAN('i', '状态: approved\n级别: L1'));
176
+ const r = run(T);
177
+ check('无 frontmatter → warning 提示状态键缺失', r.status === 0 && /WARN 状态未确认.*i\.md/.test(outOf(r)), outOf(r));
178
+ rmfix(T);
179
+ }
180
+
181
+ // ---- 场景 11:frontmatter 状态值域外(自造值)→ warning 带当前值,exit 0 ----
182
+ {
183
+ const T = mkfix();
184
+ w(T, 'workflow/intents/2026-09-12-j.md', INTENT('j', '状态: 进行中\n级别: L1\n日期: 2026-09-12'));
185
+ w(T, 'workflow/plans/2026-09-12-j.md', PLAN('j', '状态: approved\n级别: L1'));
186
+ const r = run(T);
187
+ check('状态值域外 → warning 且带当前值提示', r.status === 0 && /WARN 状态未确认.*j\.md.*进行中/.test(outOf(r)), outOf(r));
188
+ rmfix(T);
189
+ }
190
+
191
+ // ---- 场景 12:无 frontmatter 的旧格式 L3 声明 → warning 引导补 frontmatter(不回退读正文,双源防回归)----
192
+ {
193
+ const T = mkfix();
194
+ for (const s of ['intents', 'plans', 'specs']) {
195
+ w(T, `workflow/${s}/2026-09-12-k.md`, `# ${s.toUpperCase().slice(0, -1)} — k\n\n级别:L3\n状态:approved\n`);
196
+ }
197
+ const r = run(T);
198
+ check('无 frontmatter 旧格式 → warning 引导补 frontmatter(不回退读正文)',
199
+ r.status === 0 && /WARN 状态未确认.*k\.md(frontmatter 状态键当前值:『缺失』)/.test(outOf(r)), outOf(r));
200
+ rmfix(T);
201
+ }
202
+
203
+ // ---- 场景 13:git fixture——L1 intent 与迁移 SQL 同 commit → warning 疑似判低(检查 10)----
204
+ {
205
+ const T = mkfix();
206
+ gitInit(T);
207
+ w(T, 'workflow/intents/2026-09-12-m.md', INTENT('m', '状态: done\n级别: L1\n日期: 2026-09-12', '\n## 验收标准(可测试)\n- [x] 场景构造项(证据:fixture)\n'));
208
+ w(T, 'workflow/plans/2026-09-12-m.md', PLAN('m', '状态: done\n级别: L1'));
209
+ fs.mkdirSync(path.join(T, 'docs', 'scripts'), { recursive: true });
210
+ w(T, 'docs/scripts/01-add-table.sql', 'CREATE TABLE t(id INT);\n');
211
+ gitCommitAll(T, 'test');
212
+ const r = run(T);
213
+ check('L1 入口文档提交触及迁移 SQL → warning 疑似判低', r.status === 0 && /WARN 级别疑似判低.*m\.md/.test(outOf(r)), outOf(r));
214
+ rmfix(T);
215
+ }
216
+
217
+ // ---- 场景 14:git fixture——L3 intent 与迁移 SQL 同 commit → 级别一致不告警 ----
218
+ {
219
+ const T = mkfix();
220
+ gitInit(T);
221
+ w(T, 'workflow/intents/2026-09-12-n.md', INTENT('n', '状态: done\n级别: L3\n日期: 2026-09-12', '\n## 验收标准(可测试)\n- [x] 场景构造项(证据:fixture)\n'));
222
+ w(T, 'workflow/specs/2026-09-12-n.md', SPEC('n', '状态: done\n级别: L3\n确认结果: approved\n确认时间: 2026-09-12', '\n## 确认与复核\n- 独立复核:已由 independent-reviewer 新会话完成,结论 approved\n'));
223
+ w(T, 'workflow/plans/2026-09-12-n.md', PLAN('n', '状态: done\n级别: L3'));
224
+ fs.mkdirSync(path.join(T, 'docs', 'scripts'), { recursive: true });
225
+ w(T, 'docs/scripts/01-add-table.sql', 'CREATE TABLE t(id INT);\n');
226
+ gitCommitAll(T, 'test');
227
+ const r = run(T);
228
+ check('L3 入口文档提交触及迁移 SQL → 级别一致不告警', r.status === 0 && !outOf(r).includes('级别疑似判低'), outOf(r));
229
+ rmfix(T);
230
+ }
231
+
232
+ // ---- 场景 15:ZCode Adapter 缺失不告警(本地配置不入 git),存在且断线仍告警 ----
233
+ {
234
+ const T = mkfix();
235
+ w(T, 'workflow/intents/2026-09-12-o.md', INTENT('o', '状态: done\n级别: L1\n日期: 2026-09-12', '\n## 验收标准(可测试)\n- [x] 用例通过(证据:全绿)\n'));
236
+ w(T, 'workflow/plans/2026-09-12-o.md', PLAN('o', '状态: done\n级别: L1'));
237
+ fs.mkdirSync(path.join(T, '.agents', 'roles'), { recursive: true });
238
+ fs.mkdirSync(path.join(T, '.zcode', 'agents'), { recursive: true });
239
+ for (const r of ['implementer', 'independent-reviewer', 'ui-verifier']) w(T, `.agents/roles/${r}.md`, `# role ${r}\n`);
240
+ w(T, '.zcode/agents/implementer.md', '---\nname: implementer\n---\n断线内容,无角色引用\n');
241
+ const r = run(T);
242
+ check('ZCode 缺失跳过/存在断线仍告警',
243
+ r.status === 0 && outOf(r).includes('ZCode implementer 未引用公共角色') && !outOf(r).includes('ZCode 缺'), outOf(r));
244
+ rmfix(T);
245
+ }
246
+
247
+ // ---- 场景 16:skills 引用仓库内与用户级均不存在 → 仍报引用断档 ----
248
+ {
249
+ const T = mkfix();
250
+ w(T, 'workflow/README.md', '# WF README\n\n技能见 .agents/skills/fake-skill(仓库内与用户级均不存在)\n');
251
+ w(T, 'workflow/intents/2026-09-12-p.md', INTENT('p', '状态: done\n级别: L1\n日期: 2026-09-12', '\n## 验收标准(可测试)\n- [x] 用例通过(证据:全绿)\n'));
252
+ w(T, 'workflow/plans/2026-09-12-p.md', PLAN('p', '状态: done\n级别: L1'));
253
+ const r = run(T);
254
+ check('skills 引用仓库内与用户级均无 → 仍报引用断档', r.status === 0 && /引用断档.*fake-skill/.test(outOf(r)), outOf(r));
255
+ rmfix(T);
256
+ }
257
+
258
+ // ---- 场景 17:存量 done 含「存量对账豁免」声明 → 出账,无验收对账 warning ----
259
+ {
260
+ const T = mkfix();
261
+ w(T, 'workflow/intents/2026-09-01-q.md', INTENT('q', '状态: done\n级别: L1\n日期: 2026-09-01', '\n## 验收标准(可测试)\n> **存量对账豁免(2026-09-12,用户拍板不追溯)**:下方未勾项均为第二轮 UI/浏览器实测项。\n- [ ] 用例通过\n'));
262
+ w(T, 'workflow/plans/2026-09-01-q.md', PLAN('q', '状态: done\n级别: L1'));
263
+ const r = run(T);
264
+ check('存量含豁免声明 → 出账不计未对账', r.status === 0 && !outOf(r).includes('验收对账存量'), outOf(r));
265
+ rmfix(T);
266
+ }
267
+
268
+ // ---- 场景 18:新建 done intent 缺「## 验收标准」节 → hard(2026-09-16 补口回归)----
269
+ {
270
+ const T = mkfix();
271
+ w(T, 'workflow/intents/2026-09-13-r.md', INTENT('r', '状态: done\n级别: L1\n日期: 2026-09-13', '\n## 目标\n关单前未写验收标准节。\n'));
272
+ w(T, 'workflow/plans/2026-09-13-r.md', PLAN('r', '状态: done\n级别: L1'));
273
+ expectHard('新建 done 缺验收标准节 → hard 验收未对账', T, '缺「## 验收标准」节');
274
+ rmfix(T);
275
+ }
276
+
277
+ // ---- 场景 19:新建 done 勾选项缩进写法 → hard(口径放宽后仍拦)----
278
+ {
279
+ const T = mkfix();
280
+ w(T, 'workflow/intents/2026-09-13-s.md', INTENT('s', '状态: done\n级别: L1\n日期: 2026-09-13', '\n## 验收标准(可测试)\n - [ ] 缩进未勾项(此前后静默通过)\n'));
281
+ w(T, 'workflow/plans/2026-09-13-s.md', PLAN('s', '状态: done\n级别: L1'));
282
+ expectHard('新建 done 勾选项缩进 → hard 验收未对账', T, '验收未对账');
283
+ rmfix(T);
284
+ }
285
+
286
+ // ---- 场景 20:incident 状态值域外 → warning 带当前值 ----
287
+ {
288
+ const T = mkfix();
289
+ w(T, 'workflow/incidents/2026-09-13-t.md', `---\n状态: 已修复\n级别: L1\n发现: 2026-09-13\n---\n# INCIDENT — t\n\n${三件套(false)}`);
290
+ w(T, 'workflow/plans/2026-09-13-t.md', PLAN('t', '状态: done\n级别: L1'));
291
+ const r = run(T);
292
+ check('incident 状态值域外 → warning 且带当前值', r.status === 0 && /WARN 状态非法.*t\.md.*已修复/.test(outOf(r)), outOf(r));
293
+ rmfix(T);
294
+ }
295
+
296
+ // ---- 场景 21:incident 缺级别 → warning 明示配对检查已跳过 ----
297
+ {
298
+ const T = mkfix();
299
+ w(T, 'workflow/incidents/2026-09-13-u.md', `---\n状态: open\n发现: 2026-09-13\n---\n# INCIDENT — u\n\n${三件套(false)}`);
300
+ const r = run(T);
301
+ check('incident 缺级别 → warning 明示配对已跳过', r.status === 0 && /WARN 级别缺失.*u\.md.*配对检查已跳过/.test(outOf(r)), outOf(r));
302
+ rmfix(T);
303
+ }
304
+
305
+ // ---- 场景 22:存量 done 缺验收标准节 → 聚合 warning 不 hard ----
306
+ {
307
+ const T = mkfix();
308
+ w(T, 'workflow/intents/2026-09-01-v.md', INTENT('v', '状态: done\n级别: L1\n日期: 2026-09-01', '\n## 目标\n存量旧格式,缺验收标准节。\n'));
309
+ w(T, 'workflow/plans/2026-09-01-v.md', PLAN('v', '状态: done\n级别: L1'));
310
+ const r = run(T);
311
+ check('存量 done 缺节 → 聚合 warning 不 hard', r.status === 0 && outOf(r).includes('验收对账存量'), outOf(r));
312
+ rmfix(T);
313
+ }
314
+
315
+ // ---- 场景 23/24:git 仓库模式——未跟踪并行半成品不拦 push;提交进 HEAD 后仍 hard ----
316
+ {
317
+ const T = mkfix();
318
+ gitInit(T);
319
+ w(T, 'workflow/intents/2026-09-12-ok2.md', INTENT('ok2', '状态: done\n级别: L1\n日期: 2026-09-12', '\n## 验收标准(可测试)\n- [x] 场景构造项(证据:fixture)\n'));
320
+ w(T, 'workflow/plans/2026-09-12-ok2.md', PLAN('ok2', '状态: done\n级别: L1'));
321
+ gitCommitAll(T, 'base');
322
+ w(T, 'workflow/intents/2026-09-18-parallel-wip.md', INTENT('并行会话半成品', '状态: draft\n级别: L1\n日期: 2026-09-18'));
323
+ let r = run(T, { git: true });
324
+ check('仓库模式:未跟踪 draft intent(无 plan)不进扫描,不拦 push', r.status === 0 && !outOf(r).includes('parallel-wip'), `exit=${r.status}\n${outOf(r)}`);
325
+ gitCommitAll(T, 'wip');
326
+ r = run(T, { git: true });
327
+ check('仓库模式:已提交的入口缺 plan 断档仍 hard-block', r.status === 1 && /缺 plan.*parallel-wip/.test(outOf(r)), `exit=${r.status}\n${outOf(r)}`);
328
+ rmfix(T);
329
+ }
330
+
331
+ // ---- 场景 25:检索层——模块合法 + INDEX 一致 → exit 0 无新告警(拷真实生成器+读取器+词表测接线)----
332
+ {
333
+ const T = mkfix();
334
+ fs.mkdirSync(path.join(T, '.agents', 'scripts'), { recursive: true });
335
+ fs.copyFileSync(path.join(SCRIPT_DIR, 'gen-workflow-index.mjs'), path.join(T, '.agents', 'scripts', 'gen-workflow-index.mjs'));
336
+ fs.copyFileSync(path.join(SCRIPT_DIR, 'workflow-enums.mjs'), path.join(T, '.agents', 'scripts', 'workflow-enums.mjs'));
337
+ fs.copyFileSync(path.join(SCRIPT_DIR, '..', 'workflow-modules.txt'), path.join(T, '.agents', 'workflow-modules.txt'));
338
+ w(T, 'workflow/intents/2026-09-22-mod-ok.md', INTENT('mod ok', '状态: approved\n级别: L1\n日期: 2026-09-22\n模块: pipeline', '\n## 验收标准(可测试)\n- [ ] 用例通过\n'));
339
+ w(T, 'workflow/plans/2026-09-22-mod-ok.md', PLAN('mod ok', '状态: approved\n级别: L1\n模块: pipeline'));
340
+ spawnSync(process.execPath, ['.agents/scripts/gen-workflow-index.mjs'], { cwd: T });
341
+ const r = run(T);
342
+ check('检索层:模块合法+INDEX 一致 → exit 0 无新告警',
343
+ r.status === 0 && !outOf(r).includes('WARN 模块元数据') && !outOf(r).includes('WARN 索引漂移'), outOf(r));
344
+ rmfix(T);
345
+ }
346
+
347
+ // ---- 场景 26:模块枚举非法 + 2026-09-22 起新建缺字段 → WARN 不阻断 ----
348
+ {
349
+ const T = mkfix();
350
+ fs.copyFileSync(path.join(SCRIPT_DIR, '..', 'workflow-modules.txt'), path.join(T, '.agents', 'workflow-modules.txt'));
351
+ w(T, 'workflow/intents/2026-09-22-mod-bad.md', INTENT('mod bad', '状态: approved\n级别: L1\n日期: 2026-09-22\n模块: 不存在的模块'));
352
+ w(T, 'workflow/plans/2026-09-22-mod-bad.md', PLAN('mod bad', '状态: approved\n级别: L1'));
353
+ const r = run(T);
354
+ const o = outOf(r);
355
+ check('检索层:模块非法/缺字段 → WARN 且 exit 0',
356
+ r.status === 0 && o.includes('WARN 模块元数据') && o.includes('不在词表') && o.includes('缺「模块:」字段'), o);
357
+ rmfix(T);
358
+ }
359
+
360
+ // ---- 场景 27:INDEX 漂移 → WARN 索引漂移,exit 0(调生成器 --check 口径)----
361
+ {
362
+ const T = mkfix();
363
+ fs.mkdirSync(path.join(T, '.agents', 'scripts'), { recursive: true });
364
+ fs.copyFileSync(path.join(SCRIPT_DIR, 'gen-workflow-index.mjs'), path.join(T, '.agents', 'scripts', 'gen-workflow-index.mjs'));
365
+ fs.copyFileSync(path.join(SCRIPT_DIR, 'workflow-enums.mjs'), path.join(T, '.agents', 'scripts', 'workflow-enums.mjs'));
366
+ fs.copyFileSync(path.join(SCRIPT_DIR, '..', 'workflow-modules.txt'), path.join(T, '.agents', 'workflow-modules.txt'));
367
+ w(T, 'workflow/intents/2026-09-22-drift.md', INTENT('drift', '状态: approved\n级别: L1\n日期: 2026-09-22\n模块: pipeline'));
368
+ w(T, 'workflow/plans/2026-09-22-drift.md', PLAN('drift', '状态: approved\n级别: L1\n模块: pipeline'));
369
+ spawnSync(process.execPath, ['.agents/scripts/gen-workflow-index.mjs'], { cwd: T });
370
+ w(T, 'workflow/plans/2026-09-22-drift.md', PLAN('drift', '状态: done\n级别: L1\n模块: pipeline')); // 生成后改状态 → 漂移
371
+ const r = run(T);
372
+ check('检索层:INDEX 漂移 → WARN 且 exit 0', r.status === 0 && outOf(r).includes('WARN 索引漂移'), outOf(r));
373
+ rmfix(T);
374
+ }
375
+
376
+ // ---- 场景 28/29:常驻面预算——合规无告警;AGENTS.md 超限 → WARN 含一进一出 ----
377
+ {
378
+ const T = mkfix();
379
+ fs.mkdirSync(path.join(T, '.agents', 'scripts'), { recursive: true });
380
+ fs.copyFileSync(path.join(SCRIPT_DIR, 'rule-budget.sh'), path.join(T, '.agents', 'scripts', 'rule-budget.sh'));
381
+ fs.copyFileSync(path.join(SCRIPT_DIR, '..', 'rule-budgets.txt'), path.join(T, '.agents', 'rule-budgets.txt'));
382
+ w(T, 'AGENTS.md', '# AGENTS 小体积 fixture\n');
383
+ fs.mkdirSync(path.join(T, '.agents', 'commands'), { recursive: true });
384
+ w(T, '.agents/commands/plan.md', '# 小命令\n');
385
+ w(T, 'workflow/intents/2026-09-22-budget-ok.md', INTENT('budget ok', '状态: approved\n级别: L1\n日期: 2026-09-22\n模块: pipeline'));
386
+ w(T, 'workflow/plans/2026-09-22-budget-ok.md', PLAN('budget ok', '状态: approved\n级别: L1\n模块: pipeline'));
387
+ let r = run(T);
388
+ check('常驻面预算:合规 → 无超限告警且 exit 0', r.status === 0 && !outOf(r).includes('WARN 常驻面超限'), outOf(r));
389
+ w(T, 'AGENTS.md', 'x'.repeat(8000));
390
+ w(T, 'workflow/intents/2026-09-22-budget-bad.md', INTENT('budget bad', '状态: approved\n级别: L1\n日期: 2026-09-22\n模块: pipeline'));
391
+ w(T, 'workflow/plans/2026-09-22-budget-bad.md', PLAN('budget bad', '状态: approved\n级别: L1\n模块: pipeline'));
392
+ r = run(T);
393
+ const o = outOf(r);
394
+ check('常驻面预算:超限 → WARN(含一进一出提示) 且 exit 0', r.status === 0 && o.includes('WARN 常驻面超限') && o.includes('一进一出'), o);
395
+ rmfix(T);
396
+ }
397
+
398
+ // ---- 场景 30:rule-budget --staged 硬拦路径(rule-budget.sh 仍为 sh——无 sh 环境打印 SKIP)----
399
+ {
400
+ const probe = spawnSync('sh', ['-c', 'true']);
401
+ if (probe.error) {
402
+ console.log('SKIP rule-budget --staged 两场景(环境无 sh——rule-budget.sh 属 git 钩子 sh 环境面)');
403
+ } else {
404
+ const T = mkfix();
405
+ fs.mkdirSync(path.join(T, '.agents', 'scripts'), { recursive: true });
406
+ fs.copyFileSync(path.join(SCRIPT_DIR, 'rule-budget.sh'), path.join(T, '.agents', 'scripts', 'rule-budget.sh'));
407
+ fs.copyFileSync(path.join(SCRIPT_DIR, '..', 'rule-budgets.txt'), path.join(T, '.agents', 'rule-budgets.txt'));
408
+ gitInit(T);
409
+ w(T, 'AGENTS.md', 'x'.repeat(8000));
410
+ spawnSync(process.platform === 'win32' ? 'git.exe' : 'git', ['add', 'AGENTS.md'], { cwd: T });
411
+ let r = spawnSync('sh', ['.agents/scripts/rule-budget.sh', '--staged'], { cwd: T, encoding: 'utf8' });
412
+ check('rule-budget --staged:暂存超限 → exit 1(硬拦路径)',
413
+ r.status === 1 && `${r.stdout}${r.stderr}`.includes('常驻面超限:AGENTS.md'), `${r.stdout}${r.stderr}`);
414
+ w(T, 'AGENTS.md', '# 小体积\n');
415
+ spawnSync(process.platform === 'win32' ? 'git.exe' : 'git', ['add', 'AGENTS.md'], { cwd: T });
416
+ r = spawnSync('sh', ['.agents/scripts/rule-budget.sh', '--staged'], { cwd: T, encoding: 'utf8' });
417
+ check('rule-budget --staged:合规 → exit 0(不误拦)', r.status === 0, `${r.stdout}${r.stderr}`);
418
+ rmfix(T);
419
+ }
420
+ }
421
+
422
+ // ---- 场景 31/32/33:确认态留痕(检查 14)——无 approved 历史 WARN;先 approved 后 done 无 WARN;非 git 跳过 ----
423
+ {
424
+ const T = mkfix();
425
+ gitInit(T);
426
+ w(T, 'workflow/incidents/2026-09-23-confirm-gate.md', `---\n状态: closed\n级别: L1\n发现: 2026-09-23\n模块: material\n---\n# INCIDENT — confirm gate\n\n${三件套(false)}`);
427
+ w(T, 'workflow/plans/2026-09-23-confirm-gate.md', PLAN('confirm gate', '状态: done\n级别: L1\n模块: material'));
428
+ gitCommitAll(T, 'test');
429
+ let r = run(T);
430
+ check('新 done 无 approved 历史 → WARN 确认态缺失 且 exit 0',
431
+ r.status === 0 && /WARN 确认态缺失.*2026-09-23-confirm-gate\.md/.test(outOf(r)), outOf(r));
432
+ rmfix(T);
433
+ }
434
+ {
435
+ const T = mkfix();
436
+ gitInit(T);
437
+ w(T, 'workflow/incidents/2026-09-23-confirm-ok.md', `---\n状态: closed\n级别: L1\n发现: 2026-09-23\n模块: material\n---\n# INCIDENT — confirm ok\n\n${三件套(false)}`);
438
+ w(T, 'workflow/plans/2026-09-23-confirm-ok.md', PLAN('confirm ok', '状态: approved\n级别: L1\n模块: material'));
439
+ gitCommitAll(T, 'approved');
440
+ w(T, 'workflow/plans/2026-09-23-confirm-ok.md', PLAN('confirm ok', '状态: done\n级别: L1\n模块: material'));
441
+ gitCommitAll(T, 'done');
442
+ const r = run(T);
443
+ check('approved → done 留痕 → 无确认态缺失告警', r.status === 0 && !outOf(r).includes('WARN 确认态缺失'), outOf(r));
444
+ rmfix(T);
445
+ }
446
+ {
447
+ const T = mkfix();
448
+ w(T, 'workflow/incidents/2026-09-23-confirm-nogit.md', `---\n状态: closed\n级别: L1\n发现: 2026-09-23\n模块: material\n---\n# INCIDENT — confirm nogit\n\n${三件套(false)}`);
449
+ w(T, 'workflow/plans/2026-09-23-confirm-nogit.md', PLAN('confirm nogit', '状态: done\n级别: L1\n模块: material'));
450
+ const r = run(T);
451
+ check('非 git fixture → 检查项 14 跳过,不误报', r.status === 0 && !outOf(r).includes('WARN 确认态缺失'), outOf(r));
452
+ rmfix(T);
453
+ }
454
+
455
+ // ---- 场景 34/35/36:枚举单源——缺文件 fail-loud;缺键 fail-loud;CRLF 容忍 ----
456
+ {
457
+ const T = mkfix();
458
+ fs.rmSync(path.join(T, '.agents', 'workflow-enums.txt'));
459
+ w(T, 'workflow/intents/2026-09-12-ok.md', INTENT('ok', '状态: done\n级别: L1\n日期: 2026-09-12', '\n## 验收标准(可测试)\n- [x] 用例通过(证据:全绿)\n'));
460
+ expectHard('枚举单源文件缺失 → exit 1 fail-loud(不静默退化)', T, '枚举单源');
461
+ rmfix(T);
462
+ }
463
+ {
464
+ const T = mkfix();
465
+ const f = path.join(T, '.agents', 'workflow-enums.txt');
466
+ fs.writeFileSync(f, fs.readFileSync(f, 'utf8').replace(/^doc\.status\.confirmed=.*\n/m, ''));
467
+ expectHard('枚举单源缺键 → exit 1 fail-loud', T, '缺键');
468
+ rmfix(T);
469
+ }
470
+ {
471
+ const T = mkfix();
472
+ fs.writeFileSync(path.join(T, '.agents', 'workflow-enums.txt'), ENUMS_FIXTURE.replace(/\n/g, '\r\n'));
473
+ w(T, 'workflow/intents/2026-09-12-ok.md', INTENT('ok', '状态: approved\n级别: L1\n日期: 2026-09-12'));
474
+ w(T, 'workflow/plans/2026-09-12-ok.md', PLAN('ok', '状态: approved\n级别: L1'));
475
+ const r = run(T);
476
+ check('枚举单源 CRLF 行尾 → approved/done 判定不受 \\r 影响', r.status === 0 && !outOf(r).includes('状态未确认'), outOf(r));
477
+ rmfix(T);
478
+ }
479
+
480
+ console.log(`\n合计: PASS ${pass} / FAIL ${fail}`);
481
+ process.exit(fail ? 1 : 0);