agentic-flow-kit 0.7.0 → 0.9.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 (148) hide show
  1. package/README.md +36 -12
  2. package/modules/gates/README.md +24 -0
  3. package/modules/gates/generated-readonly/README.md +13 -0
  4. package/modules/gates/generated-readonly/check-generated.mjs +61 -0
  5. package/modules/gates/generated-readonly/local-pre-commit.line +1 -0
  6. package/modules/gates/node-layer/README.md +9 -0
  7. package/modules/gates/node-layer/check-node-layer.mjs +29 -0
  8. package/modules/gates/node-layer/local-pre-commit.line +1 -0
  9. package/modules/gates/py-import/README.md +9 -0
  10. package/modules/gates/py-import/check-py-import.mjs +30 -0
  11. package/modules/gates/py-import/local-pre-commit.line +1 -0
  12. package/modules/hosts/claude/agents/implementer.md +52 -0
  13. package/modules/hosts/claude/agents/independent-reviewer.md +49 -0
  14. package/modules/hosts/claude/agents/ui-verifier.md +48 -0
  15. package/modules/hosts/claude/commands/wf-build.md +79 -0
  16. package/modules/hosts/claude/commands/wf-deploy.md +57 -0
  17. package/modules/hosts/claude/commands/wf-design.md +42 -0
  18. package/modules/hosts/claude/commands/wf-gate-checklist.md +77 -0
  19. package/modules/hosts/claude/commands/wf-maintain.md +76 -0
  20. package/modules/hosts/claude/commands/wf-new-task.md +65 -0
  21. package/modules/hosts/claude/commands/wf-plan.md +41 -0
  22. package/modules/hosts/claude/commands/wf-review.md +47 -0
  23. package/modules/hosts/claude/commands/wf-source-sync-check.md +89 -0
  24. package/modules/hosts/claude/commands/wf-sync-hosts.md +66 -0
  25. package/modules/hosts/claude/commands/wf-test.md +66 -0
  26. package/modules/hosts/codex/agents/implementer.md +52 -0
  27. package/modules/hosts/codex/agents/independent-reviewer.md +49 -0
  28. package/modules/hosts/codex/agents/ui-verifier.md +48 -0
  29. package/modules/hosts/codex/commands/wf-build.md +79 -0
  30. package/modules/hosts/codex/commands/wf-deploy.md +57 -0
  31. package/modules/hosts/codex/commands/wf-design.md +42 -0
  32. package/modules/hosts/codex/commands/wf-gate-checklist.md +77 -0
  33. package/modules/hosts/codex/commands/wf-maintain.md +76 -0
  34. package/modules/hosts/codex/commands/wf-new-task.md +65 -0
  35. package/modules/hosts/codex/commands/wf-plan.md +41 -0
  36. package/modules/hosts/codex/commands/wf-review.md +47 -0
  37. package/modules/hosts/codex/commands/wf-source-sync-check.md +89 -0
  38. package/modules/hosts/codex/commands/wf-sync-hosts.md +66 -0
  39. package/modules/hosts/codex/commands/wf-test.md +66 -0
  40. package/modules/hosts/codex/skills/flow-kit/SKILL.md +14 -0
  41. package/modules/hosts/cursor/agents/implementer.md +52 -0
  42. package/modules/hosts/cursor/agents/independent-reviewer.md +49 -0
  43. package/modules/hosts/cursor/agents/ui-verifier.md +48 -0
  44. package/modules/hosts/cursor/commands/wf-build.md +79 -0
  45. package/modules/hosts/cursor/commands/wf-deploy.md +57 -0
  46. package/modules/hosts/cursor/commands/wf-design.md +42 -0
  47. package/modules/hosts/cursor/commands/wf-gate-checklist.md +77 -0
  48. package/modules/hosts/cursor/commands/wf-maintain.md +76 -0
  49. package/modules/hosts/cursor/commands/wf-new-task.md +65 -0
  50. package/modules/hosts/cursor/commands/wf-plan.md +41 -0
  51. package/modules/hosts/cursor/commands/wf-review.md +47 -0
  52. package/modules/hosts/cursor/commands/wf-source-sync-check.md +89 -0
  53. package/modules/hosts/cursor/commands/wf-sync-hosts.md +66 -0
  54. package/modules/hosts/cursor/commands/wf-test.md +66 -0
  55. package/modules/hosts/omp/agents/implementer.md +1 -1
  56. package/modules/hosts/omp/agents/independent-reviewer.md +1 -1
  57. package/modules/hosts/omp/agents/ui-verifier.md +1 -1
  58. package/modules/hosts/opencode/agents/implementer.md +1 -1
  59. package/modules/hosts/opencode/agents/independent-reviewer.md +1 -1
  60. package/modules/hosts/opencode/agents/ui-verifier.md +1 -1
  61. package/modules/hosts/opencode/commands/{build.md → wf-build.md} +3 -4
  62. package/modules/hosts/opencode/commands/{design.md → wf-design.md} +2 -2
  63. package/modules/hosts/opencode/commands/{maintain.md → wf-maintain.md} +1 -1
  64. package/modules/hosts/opencode/commands/{plan.md → wf-plan.md} +3 -3
  65. package/modules/hosts/opencode/commands/{review.md → wf-review.md} +1 -1
  66. package/modules/hosts/opencode/commands/{source-sync-check.md → wf-source-sync-check.md} +1 -1
  67. package/modules/hosts/opencode/commands/{sync-hosts.md → wf-sync-hosts.md} +1 -1
  68. package/modules/hosts/opencode/commands/{test.md → wf-test.md} +3 -1
  69. package/modules/hosts/trae/agents/implementer.md +1 -1
  70. package/modules/hosts/trae/agents/independent-reviewer.md +1 -1
  71. package/modules/hosts/trae/agents/ui-verifier.md +1 -1
  72. package/modules/hosts/trae/commands/wf-build.md +3 -4
  73. package/modules/hosts/trae/commands/wf-design.md +2 -2
  74. package/modules/hosts/trae/commands/wf-maintain.md +1 -1
  75. package/modules/hosts/trae/commands/wf-plan.md +3 -3
  76. package/modules/hosts/trae/commands/wf-review.md +1 -1
  77. package/modules/hosts/trae/commands/wf-source-sync-check.md +1 -1
  78. package/modules/hosts/trae/commands/wf-sync-hosts.md +1 -1
  79. package/modules/hosts/trae/commands/wf-test.md +3 -1
  80. package/modules/hosts/zcode/agents/implementer.md +1 -1
  81. package/modules/hosts/zcode/agents/independent-reviewer.md +1 -1
  82. package/modules/hosts/zcode/agents/ui-verifier.md +1 -1
  83. package/package.json +1 -1
  84. package/src/cli.mjs +16 -4
  85. package/src/doctor.mjs +58 -5
  86. package/src/fresh-init.test.mjs +36 -0
  87. package/src/gates.test.mjs +126 -0
  88. package/src/init.mjs +8 -1
  89. package/src/init.test.mjs +7 -1
  90. package/src/profiles.mjs +10 -4
  91. package/src/run-tests.mjs +3 -0
  92. package/src/stack-profile.test.mjs +89 -0
  93. package/src/sync-hosts.mjs +12 -6
  94. package/src/sync-hosts.test.mjs +40 -17
  95. package/templates/AGENTS.md +4 -4
  96. package/templates/_agents/board/index.html +3 -3
  97. package/templates/_agents/commands/build.md +4 -5
  98. package/templates/_agents/commands/design.md +4 -4
  99. package/templates/_agents/commands/maintain.md +1 -1
  100. package/templates/_agents/commands/new-task.md +13 -2
  101. package/templates/_agents/commands/plan.md +4 -4
  102. package/templates/_agents/commands/review.md +1 -1
  103. package/templates/_agents/commands/source-sync-check.md +1 -1
  104. package/templates/_agents/commands/sync-hosts.md +1 -1
  105. package/templates/_agents/commands/test.md +4 -2
  106. package/templates/_agents/hooks/commit-check.cjs +45 -24
  107. package/templates/_agents/lane-surfaces.txt +14 -0
  108. package/templates/_agents/roles/implementer.md +1 -1
  109. package/templates/_agents/roles/independent-reviewer.md +1 -1
  110. package/templates/_agents/roles/ui-verifier.md +1 -1
  111. package/templates/_agents/scripts/check-lane-surface.mjs +164 -0
  112. package/templates/_agents/scripts/check-lane-surface.test.mjs +200 -0
  113. package/templates/_agents/scripts/check-ledger.mjs +65 -0
  114. package/templates/_agents/scripts/check-ledger.test.mjs +127 -0
  115. package/templates/_agents/scripts/check-loop-rev.test.mjs +337 -0
  116. package/templates/_agents/scripts/check-loop.mjs +460 -268
  117. package/templates/_agents/scripts/check-loop.test.mjs +677 -17
  118. package/templates/_agents/scripts/check-metric-claims.mjs +275 -0
  119. package/templates/_agents/scripts/commit-check-trigger.test.mjs +64 -1
  120. package/templates/_agents/scripts/confirm-doc.mjs +159 -15
  121. package/templates/_agents/scripts/confirm-doc.test.mjs +362 -10
  122. package/templates/_agents/scripts/doctor.test.mjs +105 -4
  123. package/templates/_agents/scripts/fill-intent.mjs +8 -7
  124. package/templates/_agents/scripts/fill-intent.test.mjs +12 -3
  125. package/templates/_agents/scripts/fill-plan.mjs +27 -11
  126. package/templates/_agents/scripts/fill-plan.test.mjs +148 -8
  127. package/templates/_agents/scripts/fill-spec.mjs +18 -3
  128. package/templates/_agents/scripts/fill-spec.test.mjs +198 -1
  129. package/templates/_agents/scripts/gate-checklist.mjs +4 -1
  130. package/templates/_agents/scripts/gen-workflow-index.mjs +6 -2
  131. package/templates/_agents/scripts/gen-workflow-index.test.mjs +12 -0
  132. package/templates/_agents/scripts/policy.mjs +46 -0
  133. package/templates/_agents/scripts/solidify-task.mjs +101 -0
  134. package/templates/_agents/scripts/stage-gates.mjs +256 -0
  135. package/templates/_agents/scripts/trust-mode.mjs +67 -0
  136. package/templates/_agents/scripts/wf-journal.mjs +71 -2
  137. package/templates/_agents/scripts/wf-journal.test.mjs +66 -0
  138. package/templates/_agents/scripts/workflow-board-server.mjs +18 -15
  139. package/templates/_agents/scripts/workflow-board-server.test.mjs +30 -1
  140. package/templates/_agents/workflows/pipeline-closing.md +1 -1
  141. package/templates/_githooks/pre-commit +49 -2
  142. package/templates/_githooks/pre-push +56 -12
  143. package/templates/workflow/README.md +22 -3
  144. package/templates/workflow/incidents/_TEMPLATE.md +4 -0
  145. package/templates/workflow/intents/_TEMPLATE.md +1 -0
  146. /package/modules/hosts/opencode/commands/{deploy.md → wf-deploy.md} +0 -0
  147. /package/modules/hosts/opencode/commands/{gate-checklist.md → wf-gate-checklist.md} +0 -0
  148. /package/modules/hosts/opencode/commands/{new-task.md → wf-new-task.md} +0 -0
@@ -0,0 +1,337 @@
1
+ #!/usr/bin/env node
2
+ // --rev 扫的是该提交的树,不是当前工作区。pre-push 把 stdin 里的本地 sha 传进去。
3
+ import { spawnSync } from 'node:child_process';
4
+ import fs from 'node:fs';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
10
+ // REPO:仅服务于「doctor 契约核对」一项(src/ 为包源独有)。包源位置 HERE=…/templates/_agents/scripts
11
+ // 与装副本位置 HERE=…/.agents/scripts 双兼容(2026-09-30 复核:旧式固定 ../../.. 在装副本位置解析出仓外,
12
+ // src/doctor.mjs 必然 ENOENT——shipped 扫掠必败)。
13
+ const REPO = fs.existsSync(path.join(HERE, '..', '..', 'src', 'doctor.mjs'))
14
+ ? path.resolve(HERE, '..', '..')
15
+ : path.resolve(HERE, '..', '..', '..');
16
+ const LOOP = path.join(HERE, 'check-loop.mjs');
17
+ // ENUMS:与脚本位置相对解析(包源、装副本、装户三处均成立)
18
+ const ENUMS = path.join(HERE, '..', 'workflow-enums.txt');
19
+ // HOOK:包源侧为 templates/_githooks、装副本 / 装户侧为 .githooks——段名不同,双候选探测;
20
+ // 均缺 → 相关场景 SKIP(2026-09-30 复核:旧式单一路径在两侧打不中)
21
+ const HOOK = [
22
+ path.join(HERE, '..', '..', '_githooks', 'pre-push'),
23
+ path.join(HERE, '..', '..', '.githooks', 'pre-push'),
24
+ ].find((p) => fs.existsSync(p)) || null;
25
+ const git = process.platform === 'win32' ? 'git.exe' : 'git';
26
+ const Z = '0'.repeat(40);
27
+
28
+ let pass = 0;
29
+ let fail = 0;
30
+ const check = (name, cond, detail = '') => {
31
+ if (cond) { pass++; console.log(`PASS ${name}`); }
32
+ else { fail++; console.log(`FAIL ${name}${detail ? `\n ${String(detail).slice(0, 800)}` : ''}`); }
33
+ };
34
+
35
+ function shBin() {
36
+ for (const bin of ['sh', 'bash']) {
37
+ const r = spawnSync(bin, ['-c', 'echo ok'], { encoding: 'utf8' });
38
+ if (!r.error && r.status === 0) return bin;
39
+ }
40
+ return null;
41
+ }
42
+
43
+ function gitRun(cwd, args) {
44
+ const r = spawnSync(git, args, { cwd, encoding: 'utf8' });
45
+ if (r.status !== 0) throw new Error(`${args.join(' ')}\n${r.stderr || r.stdout}`);
46
+ return (r.stdout || '').trim();
47
+ }
48
+
49
+ function loop(cwd, args = [], env = {}) {
50
+ return spawnSync(process.execPath, [LOOP, ...args], {
51
+ cwd, encoding: 'utf8', timeout: 120000,
52
+ env: { ...process.env, ...env },
53
+ });
54
+ }
55
+ const outOf = (r) => `${r.stdout || ''}${r.stderr || ''}`;
56
+
57
+ const intent = `---
58
+ 状态: approved
59
+ 级别: L1
60
+ 日期: 2026-09-01
61
+ 模块: pipeline
62
+ ---
63
+ # INTENT — ok
64
+
65
+ ## 验收标准(可测试)
66
+
67
+ - [x] 配了 plan(证据:同名 plan)
68
+ `;
69
+ const plan = `---
70
+ 状态: approved
71
+ 级别: L1
72
+ 日期: 2026-09-01
73
+ 模块: pipeline
74
+ ---
75
+ # PLAN — ok
76
+
77
+ ## 改动面
78
+
79
+ - 无
80
+ `;
81
+
82
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ck-rev-repo-'));
83
+ const roots = [root];
84
+ const planPath = path.join(root, 'workflow', 'plans', '2026-09-01-ok.md');
85
+ try {
86
+ gitRun(root, ['init', '-q']);
87
+ gitRun(root, ['config', 'user.email', 't@example.com']);
88
+ gitRun(root, ['config', 'user.name', 't']);
89
+ fs.mkdirSync(path.join(root, '.agents'), { recursive: true });
90
+ fs.copyFileSync(ENUMS, path.join(root, '.agents', 'workflow-enums.txt'));
91
+ fs.mkdirSync(path.join(root, 'workflow', 'intents'), { recursive: true });
92
+ fs.mkdirSync(path.join(root, 'workflow', 'plans'), { recursive: true });
93
+ fs.writeFileSync(path.join(root, 'workflow', 'intents', '2026-09-01-ok.md'), intent);
94
+ fs.writeFileSync(planPath, plan);
95
+ gitRun(root, ['add', '--', '.']);
96
+ gitRun(root, ['commit', '-q', '-m', 'good']);
97
+ const good = gitRun(root, ['rev-parse', 'HEAD']);
98
+
99
+ fs.rmSync(planPath);
100
+ gitRun(root, ['add', '--', '.']);
101
+ gitRun(root, ['commit', '-q', '-m', 'bad']);
102
+ const bad = gitRun(root, ['rev-parse', 'HEAD']);
103
+
104
+ fs.writeFileSync(planPath, plan);
105
+ gitRun(root, ['add', '--', '.']);
106
+ gitRun(root, ['commit', '-q', '-m', 'tip']);
107
+ const tip = gitRun(root, ['rev-parse', 'HEAD']);
108
+
109
+ const atGood = loop(root, ['--rev', good]);
110
+ check('好提交:工作区稍后改断也不影响,--rev 退出 0', atGood.status === 0, outOf(atGood));
111
+ const atBad = loop(root, ['--rev', bad]);
112
+ check('断档提交:--rev 退出 1', atBad.status === 1 && outOf(atBad).includes('配对断裂'), outOf(atBad));
113
+ const atTip = loop(root, ['--rev', tip]);
114
+ check('当前 HEAD 不是判定对象:--rev tip 退出 0', atTip.status === 0, outOf(atTip));
115
+
116
+ fs.rmSync(planPath);
117
+ const dirty = loop(root, []);
118
+ check('不带 --rev:工作区改断已跟踪文档仍 hard-block', dirty.status === 1 && outOf(dirty).includes('配对断裂'), outOf(dirty));
119
+ const dirtyRev = loop(root, ['--rev', tip]);
120
+ check('同一脏工作区:--rev tip 仍退出 0', dirtyRev.status === 0, outOf(dirtyRev));
121
+
122
+ const zeros = loop(root, ['--rev', Z]);
123
+ check('--rev 全 0 exit 1 并打印 sha', zeros.status === 1 && outOf(zeros).includes(Z), outOf(zeros));
124
+ const bogus = 'a'.repeat(40);
125
+ const peeled = loop(root, ['--rev', bogus]);
126
+ check('--rev 剥不到提交 exit 1 并打印 sha', peeled.status === 1 && outOf(peeled).includes(bogus), outOf(peeled));
127
+
128
+ gitRun(root, ['tag', '-a', 'vbad', '-m', 't', bad]);
129
+ const tag = gitRun(root, ['rev-parse', 'vbad']);
130
+ const viaTag = loop(root, ['--rev', tag]);
131
+ check('附注标签剥到提交后再读树', tag !== bad && viaTag.status === 1 && outOf(viaTag).includes('配对断裂'), outOf(viaTag));
132
+
133
+ const both = loop(root, ['--rev', tip], { CHECK_LOOP_ROOT: root });
134
+ check('CHECK_LOOP_ROOT 与 --rev 同时出现 exit 1', both.status === 1 && outOf(both).includes('不能同时使用'), outOf(both));
135
+
136
+ const doctorPath = path.join(REPO, 'src', 'doctor.mjs');
137
+ if (fs.existsSync(doctorPath)) {
138
+ const doctor = fs.readFileSync(doctorPath, 'utf8');
139
+ check('doctor 调用闭环扫描时不传 --rev',
140
+ doctor.includes("'.agents/scripts/check-loop.mjs'") && !doctor.includes('check-loop.mjs\', \'--rev\''));
141
+ } else {
142
+ console.log('SKIP doctor 契约核对(装户位置无包源 src/)');
143
+ }
144
+
145
+ const sh = shBin();
146
+ check('找到 sh 以跑 pre-push', !!sh);
147
+ if (sh && !HOOK) console.log('SKIP pre-push 相关场景(未找到 _githooks / .githooks 下的 pre-push)');
148
+ if (sh && HOOK) {
149
+ fs.mkdirSync(path.join(root, '.agents', 'scripts'), { recursive: true });
150
+ const node = process.execPath.replace(/\\/g, '/');
151
+ const loopSh = LOOP.replace(/\\/g, '/');
152
+ fs.writeFileSync(path.join(root, '.agents', 'scripts', 'check-loop.sh'), `#!/bin/sh\nexec "${node}" "${loopSh}" "$@"\n`);
153
+ const hookPath = path.join(root, 'pre-push');
154
+ fs.copyFileSync(HOOK, hookPath);
155
+ const push = (input) => spawnSync(sh, [hookPath], { cwd: root, input, encoding: 'utf8', timeout: 120000 });
156
+ const onlyDelete = push(`refs/heads/old ${Z} refs/heads/old ${'b'.repeat(40)}\n`);
157
+ check('stdin 只有删除行:不阻断', onlyDelete.status === 0, outOf(onlyDelete));
158
+ const mixed = push(
159
+ `refs/heads/old ${Z} refs/heads/old ${'b'.repeat(40)}\nrefs/heads/bad ${bad} refs/heads/bad ${Z}\n`,
160
+ );
161
+ check('删除行加断档 sha:整次失败', mixed.status !== 0 && outOf(mixed).includes('配对断裂'), outOf(mixed));
162
+ const empty = push('');
163
+ check('没有 stdin 行:扫工作区,脏树被拦住', empty.status !== 0 && outOf(empty).includes('配对断裂'), outOf(empty));
164
+ const goodThenBad = push(
165
+ `refs/heads/ok ${good} refs/heads/ok ${Z}\nrefs/heads/bad ${bad} refs/heads/bad ${Z}\n`,
166
+ );
167
+ check('stdin 先好后坏:整次失败', goodThenBad.status !== 0 && outOf(goodThenBad).includes('配对断裂'), outOf(goodThenBad));
168
+
169
+ // experiment 泳道 advisory + main 加固门(2026-09-30 hybrid-governance-explore-hardening)
170
+ const laneAdv = push(`refs/heads/experiment/poc ${bad} refs/heads/experiment/poc ${Z}\n`);
171
+ check('pre-push experiment/* 泳道:断档 sha 降级 advisory 不阻断', laneAdv.status === 0 && /advisory/.test(outOf(laneAdv)), outOf(laneAdv));
172
+ const toMainBad = push(`refs/heads/main ${bad} refs/heads/main ${Z}\n`);
173
+ check('pre-push 推 main:断档照拦(hard-block 语义不变)', toMainBad.status !== 0 && outOf(toMainBad).includes('配对断裂'), outOf(toMainBad));
174
+ // exploring 标记件:同一 sha 推 main → 加固门 hard 拦;推 experiment/* → advisory 放行
175
+ fs.writeFileSync(path.join(root, 'workflow', 'intents', '2026-09-30-exp.md'), intent
176
+ .replace('状态: approved', '状态: draft')
177
+ .replace('级别: L1', '级别: L1\nrisk_level: L1\n阶段: exploring')
178
+ .replace('# INTENT — ok', '# INTENT — exp'));
179
+ fs.writeFileSync(planPath, plan);
180
+ fs.writeFileSync(path.join(root, 'workflow', 'plans', '2026-09-30-exp.md'), plan);
181
+ gitRun(root, ['add', '--', '.']);
182
+ gitRun(root, ['commit', '-q', '-m', 'exploring']);
183
+ const expSha = gitRun(root, ['rev-parse', 'HEAD']);
184
+ const expToMain = push(`refs/heads/main ${expSha} refs/heads/main ${Z}\n`);
185
+ check('pre-push 推 main:exploring 未收口 → 加固门 hard 拦(--hardening 已传)',
186
+ expToMain.status !== 0 && /加固未过/.test(outOf(expToMain)), outOf(expToMain));
187
+ const expToLane = push(`refs/heads/experiment/poc2 ${expSha} refs/heads/experiment/poc2 ${Z}\n`);
188
+ check('pre-push 推 experiment/*:同一 exploring sha → advisory 放行', expToLane.status === 0, outOf(expToLane));
189
+ }
190
+
191
+ const outside = fs.mkdtempSync(path.join(os.tmpdir(), 'ck-rev-outside-'));
192
+ roots.push(outside);
193
+ const absent = 'a'.repeat(40);
194
+ const outsideRev = loop(outside, ['--rev', absent]);
195
+ check('仓库外 --rev:exit 1 并打印 sha', outsideRev.status === 1 && outOf(outsideRev).includes(absent), outOf(outsideRev));
196
+ const outsidePlain = loop(outside, []);
197
+ check('仓库外且不带 --rev:仍跳过且退出 0', outsidePlain.status === 0 && outOf(outsidePlain).includes('跳过扫描'), outOf(outsidePlain));
198
+
199
+ fs.writeFileSync(planPath, plan);
200
+ fs.writeFileSync(path.join(root, 'workflow', 'intents', '2026-09-02-extra.md'), intent.replace('# INTENT — ok', '# INTENT — extra'));
201
+ gitRun(root, ['add', '--', '.']);
202
+ gitRun(root, ['commit', '-q', '-m', 'extra']);
203
+ const extra = gitRun(root, ['rev-parse', 'HEAD']);
204
+ fs.rmSync(path.join(root, 'workflow', 'intents', '2026-09-02-extra.md'));
205
+ gitRun(root, ['add', '--', '.']);
206
+ gitRun(root, ['commit', '-q', '-m', 'drop-extra']);
207
+ const atExtra = loop(root, ['--rev', extra]);
208
+ check('断档文件不在当前 HEAD:--rev 仍退出 1', atExtra.status === 1 && outOf(atExtra).includes('2026-09-02-extra.md'), outOf(atExtra));
209
+ const headNow = loop(root, []);
210
+ check('当前 HEAD 没有该断档文件:不带 --rev 不点名它', headNow.status === 0 && !outOf(headNow).includes('2026-09-02-extra.md'), outOf(headNow));
211
+
212
+ const tree = fs.mkdtempSync(path.join(os.tmpdir(), 'ck-rev-tree-'));
213
+ roots.push(tree);
214
+ gitRun(tree, ['init', '-q']);
215
+ gitRun(tree, ['config', 'user.email', 't@example.com']);
216
+ gitRun(tree, ['config', 'user.name', 't']);
217
+ fs.mkdirSync(path.join(tree, '.agents', 'scripts'), { recursive: true });
218
+ fs.copyFileSync(ENUMS, path.join(tree, '.agents', 'workflow-enums.txt'));
219
+ fs.copyFileSync(path.join(HERE, 'gen-workflow-index.mjs'), path.join(tree, '.agents', 'scripts', 'gen-workflow-index.mjs'));
220
+ fs.copyFileSync(path.join(HERE, 'workflow-enums.mjs'), path.join(tree, '.agents', 'scripts', 'workflow-enums.mjs'));
221
+ fs.copyFileSync(path.join(HERE, 'rule-budget.sh'), path.join(tree, '.agents', 'scripts', 'rule-budget.sh'));
222
+ fs.writeFileSync(path.join(tree, '.agents', 'rule-budgets.txt'), 'big.txt 10\n');
223
+ fs.writeFileSync(path.join(tree, 'big.txt'), 'ok\n');
224
+ for (const sub of ['intents', 'specs', 'plans', 'incidents']) {
225
+ fs.mkdirSync(path.join(tree, 'workflow', sub), { recursive: true });
226
+ }
227
+ fs.writeFileSync(path.join(tree, 'workflow', 'intents', '2026-09-01-ok.md'), intent);
228
+ fs.writeFileSync(path.join(tree, 'workflow', 'plans', '2026-09-01-ok.md'), plan);
229
+ const gen = spawnSync(process.execPath, [path.join(tree, '.agents', 'scripts', 'gen-workflow-index.mjs')], { cwd: tree, encoding: 'utf8' });
230
+ if (gen.status !== 0) throw new Error(outOf(gen));
231
+ gitRun(tree, ['add', '--', '.']);
232
+ gitRun(tree, ['commit', '-q', '-m', 'aligned']);
233
+ const indexPath = path.join(tree, 'workflow', 'INDEX.md');
234
+ const indexBefore = fs.readFileSync(indexPath, 'utf8');
235
+ if (!indexBefore.includes('☑1/1')) throw new Error('INDEX 里没有 ☑1/1,无法制造漂移');
236
+ fs.writeFileSync(indexPath, indexBefore.replace('☑1/1', '☑0/1'));
237
+ gitRun(tree, ['add', '--', '.']);
238
+ gitRun(tree, ['commit', '-q', '-m', 'drift']);
239
+ const drift = gitRun(tree, ['rev-parse', 'HEAD']);
240
+ const drifted = loop(tree, ['--rev', drift]);
241
+ check('被推送提交索引损坏:--rev 报索引漂移', outOf(drifted).includes('索引漂移'), outOf(drifted));
242
+ fs.writeFileSync(path.join(tree, 'big.txt'), 'x'.repeat(100));
243
+ const restored = spawnSync(process.execPath, [path.join(tree, '.agents', 'scripts', 'gen-workflow-index.mjs')], { cwd: tree, encoding: 'utf8' });
244
+ if (restored.status !== 0) throw new Error(outOf(restored));
245
+ gitRun(tree, ['add', '--', '.']);
246
+ gitRun(tree, ['commit', '-q', '-m', 'fat']);
247
+ const fat = gitRun(tree, ['rev-parse', 'HEAD']);
248
+ fs.writeFileSync(path.join(tree, 'big.txt'), 'ok\n');
249
+ gitRun(tree, ['add', '--', '.']);
250
+ gitRun(tree, ['commit', '-q', '-m', 'slim']);
251
+ const atFat = loop(tree, ['--rev', fat]);
252
+ const atSlim = loop(tree, []);
253
+ check('预算只在被推送提交里超限:--rev 报 100B', outOf(atFat).includes('常驻面超限') && outOf(atFat).includes('100B'), outOf(atFat));
254
+ check('工作区预算未超限:不带 --rev 不报常驻面超限,也不报索引漂移',
255
+ !outOf(atSlim).includes('常驻面超限') && !outOf(atSlim).includes('索引漂移'), outOf(atSlim));
256
+ } catch (e) {
257
+ check('仓库准备', false, e.stack || e.message);
258
+ } finally {
259
+ for (const dir of roots) {
260
+ const listed = spawnSync(git, ['worktree', 'list', '--porcelain'], { cwd: dir, encoding: 'utf8' });
261
+ for (const line of (listed.stdout || '').split('\n')) {
262
+ const m = line.startsWith('worktree ') ? line.slice(9).trim() : '';
263
+ if (m && path.resolve(m) !== path.resolve(dir)) {
264
+ spawnSync(git, ['worktree', 'remove', '--force', m], { cwd: dir });
265
+ }
266
+ }
267
+ fs.rmSync(dir, { recursive: true, force: true });
268
+ }
269
+ }
270
+
271
+ // 委派表随 ROOT 读:--rev 看不到工作区后补的行。
272
+ {
273
+ const repo = fs.mkdtempSync(path.join(os.tmpdir(), 'ck-rev-del-'));
274
+ try {
275
+ gitRun(repo, ['init', '-q']);
276
+ gitRun(repo, ['config', 'user.email', 't@example.com']);
277
+ gitRun(repo, ['config', 'user.name', 't']);
278
+ fs.mkdirSync(path.join(repo, '.agents'), { recursive: true });
279
+ fs.copyFileSync(ENUMS, path.join(repo, '.agents', 'workflow-enums.txt'));
280
+ for (const sub of ['intents', 'specs', 'plans']) fs.mkdirSync(path.join(repo, 'workflow', sub), { recursive: true });
281
+ const name = '2026-09-12-gap.md';
282
+ fs.writeFileSync(path.join(repo, 'workflow', 'intents', name), `---
283
+ 状态: done
284
+ 级别: L2
285
+ 日期: 2026-09-12
286
+ ---
287
+ # INTENT — gap
288
+
289
+ ## 验收标准(可测试)
290
+
291
+ - [x] 用例通过(证据:fixture)
292
+ `);
293
+ fs.writeFileSync(path.join(repo, 'workflow', 'specs', name), `---
294
+ 状态: approved
295
+ 级别: L2
296
+ 日期: 2026-09-12
297
+ ---
298
+ # SPEC — gap
299
+ `);
300
+ fs.writeFileSync(path.join(repo, 'workflow', 'plans', name), `---
301
+ 状态: approved
302
+ 级别: L2
303
+ 日期: 2026-09-12
304
+ ---
305
+ # PLAN — gap
306
+ `);
307
+ gitRun(repo, ['add', '--', '.']);
308
+ gitRun(repo, ['commit', '-q', '-m', 'gap']);
309
+ const sha = gitRun(repo, ['rev-parse', 'HEAD']);
310
+ fs.writeFileSync(path.join(repo, 'workflow', 'delegations.md'), `## 委派结果
311
+
312
+ | 日期 | 被委派方 | 任务一句话 | 结果 | 备注 |
313
+ |------|----------|------------|------|------|
314
+ | 2026-09-12 | x | y | 一次通过 | ${name} |
315
+ `);
316
+ const atOld = loop(repo, ['--rev', sha]);
317
+ check('委派行只在工作区:--rev 旧提交仍输出委派台账',
318
+ outOf(atOld).includes('委派台账') && outOf(atOld).includes(`intents/${name}`),
319
+ outOf(atOld));
320
+ const dirty = loop(repo, []);
321
+ check('同一工作区不带 --rev:后补的委派行消掉警告',
322
+ dirty.status === 0 && !outOf(dirty).includes('委派台账'),
323
+ outOf(dirty));
324
+ } catch (e) {
325
+ check('委派 --rev 仓库准备', false, e.stack || e.message);
326
+ } finally {
327
+ const listed = spawnSync(git, ['worktree', 'list', '--porcelain'], { cwd: repo, encoding: 'utf8' });
328
+ for (const line of (listed.stdout || '').split('\n')) {
329
+ const m = line.startsWith('worktree ') ? line.slice(9).trim() : '';
330
+ if (m && path.resolve(m) !== path.resolve(repo)) spawnSync(git, ['worktree', 'remove', '--force', m], { cwd: repo });
331
+ }
332
+ fs.rmSync(repo, { recursive: true, force: true });
333
+ }
334
+ }
335
+
336
+ console.log(`\n合计: PASS ${pass} / FAIL ${fail}`);
337
+ process.exit(fail ? 1 : 0);