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,200 @@
1
+ #!/usr/bin/env node
2
+ // check-lane-surface.test.mjs — 泳道完整性门禁 fixture 套件(2026-10-01 drift-hardening)
3
+ // 方法:现场构造临时根(workflow/{intents,incidents} + .agents/lane-surfaces.txt),经
4
+ // --root/--branch/--staged-file 三注入位 spawn 脚本,断言 exit code 与输出关键词。
5
+ // 场景集对齐同名 spec「功能行为」表 S1-S6 + 平台边界(CRLF / 非 ASCII / detached /
6
+ // 配置语法 / 状态活性 / incident 等价),另含 1 个 git 真仓端到端(quotepath=off 实路径)。
7
+ // 用法:node templates/_agents/scripts/check-lane-surface.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 SCRIPT = path.join(SCRIPT_DIR, 'check-lane-surface.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
+ const mkfix = () => {
25
+ const d = fs.mkdtempSync(path.join(os.tmpdir(), 'lane-surface-test-'));
26
+ for (const s of ['intents', 'incidents']) fs.mkdirSync(path.join(d, 'workflow', s), { recursive: true });
27
+ fs.mkdirSync(path.join(d, '.agents'), { recursive: true });
28
+ return d;
29
+ };
30
+ const w = (root, rel, content) => fs.writeFileSync(path.join(root, rel), content);
31
+ // eol='\r\n' 时整文件按 CRLF 写(平台边界:frontmatter 读取容忍性)
32
+ const intent = (name, fm, eol = '\n') => `---${eol}${fm}${eol}---${eol}# INTENT — ${name}${eol}`;
33
+ const incident = (fm) => `---\n${fm}\n---\n# INCIDENT\n`;
34
+ const run = (root, { branch = 'main', staged = [] } = {}) => {
35
+ const listFile = path.join(root, 'staged.txt');
36
+ fs.writeFileSync(listFile, staged.join('\n') + '\n');
37
+ const r = spawnSync(process.execPath, [SCRIPT, '--root', root, '--branch', branch, '--staged-file', listFile], { encoding: 'utf8' });
38
+ return { code: r.status, out: `${r.stdout || ''}${r.stderr || ''}` };
39
+ };
40
+
41
+ // 通用 fixture:面清单 + 一个活跃 L1 intent(2026-09-28)+ 可选第二个入口
42
+ const base = (root, { level = 'L1', status = 'approved', extra = null } = {}) => {
43
+ w(root, '.agents/lane-surfaces.txt', '^templates/\n^\\.agents/(scripts|hooks)/\n');
44
+ w(root, 'workflow/intents/2026-09-28-old-l1.md', intent('x', `状态: ${status}\n级别: ${level}\n日期: 2026-09-28\n模块: pipeline`));
45
+ if (extra) w(root, extra[0], intent('y', extra[1]));
46
+ };
47
+ const FM_L0_NEW = '状态: draft\n级别: L0\n日期: 2026-10-01\n模块: ui';
48
+
49
+ // --- S1:命中 + 活跃 L2 存在 → 豁免放行 ---
50
+ {
51
+ const d = mkfix();
52
+ base(d, { level: 'L2' });
53
+ const r = run(d, { staged: ['templates/_agents/scripts/x.mjs'] });
54
+ check('S1 命中面+活跃 L2 入口 → 放行且无触达面判低输出', r.code === 0 && !r.out.includes('触达面判低'), r.out);
55
+ }
56
+ // --- S2:命中 + 活跃全为 L0/L1 → 阻断 ---
57
+ {
58
+ const d = mkfix();
59
+ base(d);
60
+ const r = run(d, { staged: ['templates/_agents/scripts/x.mjs', 'docs/readme.md'] });
61
+ check('S2 命中面+仅活跃 L1 → 阻断且消息含标签/命中文件/入口名/修法',
62
+ r.code === 1 && r.out.includes('[触达面判低]') && r.out.includes('templates/_agents/scripts/x.mjs')
63
+ && r.out.includes('workflow/intents/2026-09-28-old-l1.md') && r.out.includes('修法'),
64
+ r.out);
65
+ }
66
+ // --- S2b:多个 L0/L1 活跃 → 点名最新(日期排序)---
67
+ {
68
+ const d = mkfix();
69
+ base(d, { extra: ['workflow/intents/2026-10-01-new-l0.md', FM_L0_NEW] });
70
+ const r = run(d, { staged: ['templates/t.md'] });
71
+ check('S2b 多活跃 L0/L1 → 关联入口取最新日期件', r.code === 1 && r.out.includes('2026-10-01-new-l0') && r.out.includes('共 2 件'), r.out);
72
+ }
73
+ // --- S2c:experiment 分支 → 检查 A 降级 advisory ---
74
+ {
75
+ const d = mkfix();
76
+ base(d);
77
+ const r = run(d, { branch: 'experiment/poc', staged: ['templates/t.md'] });
78
+ check('S2c experiment 分支检查 A → advisory 放行', r.code === 0 && r.out.includes('advisory·experiment'), r.out);
79
+ }
80
+ // --- S3:命中 + 无活跃入口 → advisory 放行(done 不算活跃)---
81
+ {
82
+ const d = mkfix();
83
+ w(d, '.agents/lane-surfaces.txt', '^templates/\n');
84
+ w(d, 'workflow/intents/2026-09-01-closed.md', intent('c', '状态: done\n级别: L1\n日期: 2026-09-01\n模块: ui'));
85
+ const r = run(d, { staged: ['templates/t.md'] });
86
+ check('S3 命中+无活跃(done 不算活跃)→ advisory 放行', r.code === 0 && r.out.includes('无任何活跃入口'), r.out);
87
+ }
88
+ // --- S4b:experiment 分支 intent 缺标记 → 阻断;补标记放行 ---
89
+ {
90
+ const d = mkfix();
91
+ w(d, 'workflow/intents/2026-10-01-poc.md', intent('p', '状态: draft\n级别: L1\n日期: 2026-10-01\n模块: ui'));
92
+ const r1 = run(d, { branch: 'experiment/poc', staged: ['workflow/intents/2026-10-01-poc.md'] });
93
+ check('S4b-1 experiment 分支无标记 intent → 阻断且含修法',
94
+ r1.code === 1 && r1.out.includes('[探索标记缺失]') && r1.out.includes('阶段: exploring'), r1.out);
95
+ w(d, 'workflow/intents/2026-10-01-poc.md', intent('p', '状态: draft\n级别: L1\n日期: 2026-10-01\n模块: ui\n阶段: exploring'));
96
+ const r2 = run(d, { branch: 'experiment/poc', staged: ['workflow/intents/2026-10-01-poc.md'] });
97
+ check('S4b-2 补「阶段: exploring」后放行', r2.code === 0, r2.out);
98
+ }
99
+ // --- S4b-c:口径同加固门——「状态: exploring」亦认 ---
100
+ {
101
+ const d = mkfix();
102
+ w(d, 'workflow/intents/2026-10-01-poc.md', intent('p', '状态: exploring\n级别: L1\n日期: 2026-10-01\n模块: ui'));
103
+ const r = run(d, { branch: 'experiment/poc', staged: ['workflow/intents/2026-10-01-poc.md'] });
104
+ check('S4b-c 「状态: exploring」亦认(口径同加固门)', r.code === 0, r.out);
105
+ }
106
+ // --- S6:非 experiment 分支 → 检查 B 不触发 ---
107
+ {
108
+ const d = mkfix();
109
+ w(d, 'workflow/intents/2026-10-01-poc.md', intent('p', '状态: draft\n级别: L1\n日期: 2026-10-01\n模块: ui'));
110
+ const r = run(d, { branch: 'main', staged: ['workflow/intents/2026-10-01-poc.md'] });
111
+ check('S6 非 experiment 分支无标记 intent → 放行(B 不触发)', r.code === 0 && !r.out.includes('探索标记缺失'), r.out);
112
+ }
113
+ // --- S5:配置缺失 → 检查 A 整体静默跳过(装户无感)---
114
+ {
115
+ const d = mkfix();
116
+ w(d, 'workflow/intents/2026-09-28-l1.md', intent('x', '状态: approved\n级别: L1\n日期: 2026-09-28\n模块: ui'));
117
+ const r = run(d, { staged: ['templates/t.md'] });
118
+ check('S5 无 lane-surfaces.txt → 静默跳过', r.code === 0 && r.out.trim() === '', JSON.stringify(r.out));
119
+ }
120
+ // --- incident 等价:open incident L2 豁免;fixed L1 算活跃低级 ---
121
+ {
122
+ const d = mkfix();
123
+ w(d, '.agents/lane-surfaces.txt', '^src/\n');
124
+ w(d, 'workflow/incidents/2026-09-30-inc.md', incident('状态: open\n级别: L2\n发现: 2026-09-30\n模块: backend'));
125
+ const r = run(d, { staged: ['src/a.ts'] });
126
+ check('incident 等价:活跃 open incident L2 → 豁免放行', r.code === 0, r.out);
127
+ }
128
+ {
129
+ const d = mkfix();
130
+ w(d, '.agents/lane-surfaces.txt', '^src/\n');
131
+ w(d, 'workflow/incidents/2026-09-30-inc.md', incident('状态: fixed\n级别: L1\n发现: 2026-09-30\n模块: backend'));
132
+ const r = run(d, { staged: ['src/a.ts'] });
133
+ check('incident 等价:fixed L1 算活跃低级入口 → 阻断',
134
+ r.code === 1 && r.out.includes('[触达面判低]') && r.out.includes('incidents/2026-09-30-inc.md'), r.out);
135
+ }
136
+ // --- 级别缺失:按低级计(不能自证 L2+ 即拦)---
137
+ {
138
+ const d = mkfix();
139
+ base(d, { level: '', status: 'draft' });
140
+ const r = run(d, { staged: ['templates/t.md'] });
141
+ check('级别缺失的活跃入口按低级计 → 阻断且消息含「级别缺失」', r.code === 1 && r.out.includes('级别缺失'), r.out);
142
+ }
143
+ // --- 平台边界:CRLF frontmatter ---
144
+ {
145
+ const d = mkfix();
146
+ w(d, '.agents/lane-surfaces.txt', '^templates/\n');
147
+ w(d, 'workflow/intents/2026-09-28-crlf.md', intent('x', '状态: approved\r\n级别: L1\r\n日期: 2026-09-28\r\n模块: ui', '\r\n'));
148
+ const r = run(d, { staged: ['templates/t.md'] });
149
+ check('CRLF frontmatter 正常解析(仍阻断)', r.code === 1 && r.out.includes('2026-09-28-crlf'), r.out);
150
+ }
151
+ // --- 平台边界:非 ASCII 路径透传(quotepath 教训的对账面)---
152
+ {
153
+ const d = mkfix();
154
+ w(d, '.agents/lane-surfaces.txt', '^模块/\n');
155
+ w(d, 'workflow/intents/2026-09-28-l1.md', intent('x', '状态: approved\n级别: L1\n日期: 2026-09-28\n模块: ui'));
156
+ const r = run(d, { staged: ['模块/接口契约.ts'] });
157
+ check('非 ASCII 暂存路径命中模式且消息原样透传', r.code === 1 && r.out.includes('模块/接口契约.ts'), r.out);
158
+ }
159
+ // --- 平台边界:detached HEAD(branch=HEAD)→ A strict、B 跳过 ---
160
+ {
161
+ const d = mkfix();
162
+ base(d);
163
+ w(d, 'workflow/intents/2026-10-01-poc.md', intent('p', '状态: draft\n级别: L1\n日期: 2026-10-01\n模块: ui'));
164
+ const r = run(d, { branch: 'HEAD', staged: ['templates/t.md', 'workflow/intents/2026-10-01-poc.md'] });
165
+ check('detached HEAD:A 按 strict 阻断、B 不触发', r.code === 1 && r.out.includes('[触达面判低]') && !r.out.includes('探索标记缺失'), r.out);
166
+ }
167
+ // --- 边界:_TEMPLATE.md 排除(不算活跃入口、不触发 B)---
168
+ {
169
+ const d = mkfix();
170
+ w(d, 'workflow/intents/_TEMPLATE.md', intent('t', '状态: draft\n级别: L1'));
171
+ const r = run(d, { branch: 'experiment/poc', staged: ['workflow/intents/_TEMPLATE.md'] });
172
+ check('_TEMPLATE.md 不触发检查 B,也不算活跃入口', r.code === 0, r.out);
173
+ }
174
+ // --- 配置边界:非法 ERE 行 → fail-closed ---
175
+ {
176
+ const d = mkfix();
177
+ w(d, '.agents/lane-surfaces.txt', '^ok/\n[invalid(\n');
178
+ const r = run(d, { staged: ['ok/a.ts'] });
179
+ check('配置含非法 ERE → fail-closed 阻断并指出坏行', r.code === 1 && r.out.includes('[面清单语法错误]') && r.out.includes('[invalid('), r.out);
180
+ }
181
+ // --- git 真仓端到端:走生产发现路径(quotepath=off + diff --cached)---
182
+ {
183
+ const d = mkfix();
184
+ const g = (a) => spawnSync('git', ['-c', 'core.quotepath=off', ...a], { cwd: d, encoding: 'utf8' });
185
+ g(['init', '-q']);
186
+ g(['config', 'user.email', 't@t']);
187
+ g(['config', 'user.name', 't']);
188
+ w(d, '.agents/lane-surfaces.txt', '^模块/\n');
189
+ w(d, 'workflow/intents/2026-09-28-l1.md', intent('x', '状态: approved\n级别: L1\n日期: 2026-09-28\n模块: ui'));
190
+ fs.mkdirSync(path.join(d, '模块'), { recursive: true });
191
+ w(d, '模块/接口契约.ts', 'export {};\n');
192
+ g(['add', '模块/接口契约.ts']);
193
+ const r = spawnSync(process.execPath, [SCRIPT, '--root', d], { cwd: d, encoding: 'utf8' });
194
+ check('git 端到端:真实暂存非 ASCII 路径 → 阻断且路径不转义',
195
+ r.status === 1 && `${r.stdout || ''}${r.stderr || ''}`.includes('模块/接口契约.ts'),
196
+ `${r.stdout || ''}${r.stderr || ''}`);
197
+ }
198
+
199
+ console.log(`\n${fail ? `❌ ${fail} failed` : '✅ all passed'}(${pass} passed)`);
200
+ process.exit(fail ? 1 : 0);
@@ -0,0 +1,65 @@
1
+ // check-ledger.mjs — managed 台账快检(2026-09-28 ledger-precommit-gate):kit.json 台账 sha ↔ 盘面工作树
2
+ // 补 pre-commit 盲区:双源门禁(source-sync-check)查 templates↔.agents 正文,两侧成对回退时静默;
3
+ // doctor §4 查台账↔盘面但只在手动全量跑——「台账 sha 预 landing」(台账记了盘面尚不存在的内容 sha)
4
+ // 随常规提交静默落 main,三次真实发生(0272a1c 预入 / ff3c721 还原窗口 / 537df18 闭合),本脚本在
5
+ // 提交时刻补齐第三边。sha 口径:LF 归一 utf8(同 doctor §4 / render.mjs shaText / source-sync-check
6
+ // 读取侧,跨 checkout 稳定;装户自包含约束决定独立实现,三处口径改动须同批)。
7
+ // 边界:仅包源环境由 .githooks/pre-commit 触发(装户 managed 手改走 doctor §4 WARN 口径,不硬拦);
8
+ // managed 删除即拦——sync 对 removed 只报不删,删件须手动同步装副本并重跑 sync 刷台账。
9
+ // 用法:node .agents/scripts/check-ledger.mjs(cwd = 仓库根;非零出口即阻断提交)
10
+ // 单元测试:import { ledgerDrift } from './check-ledger.mjs'
11
+ import fs from 'node:fs';
12
+ import path from 'node:path';
13
+ import { createHash } from 'node:crypto';
14
+
15
+ export function ledgerDrift(target = process.cwd()) {
16
+ const kitPath = path.join(target, '.agents', 'kit.json');
17
+ if (!fs.existsSync(kitPath)) return { skipped: true, note: '无 .agents/kit.json(非 flow-kit 安装)' };
18
+ let kit;
19
+ try {
20
+ kit = JSON.parse(fs.readFileSync(kitPath, 'utf8'));
21
+ } catch (e) {
22
+ return { skipped: false, fatal: `kit.json 解析失败:${e.message}`, modified: [], gone: [], total: 0, version: '?' };
23
+ }
24
+ if (!Array.isArray(kit.managed)) {
25
+ // fail-loud(独立复核 P2-3,2026-09-28):JSON 合法但 managed 缺失/非数组时静默「全对齐」= 假放,
26
+ // 同 doctor §4 的 Array.isArray 显式判口径,台账结构坏了必须响
27
+ return { skipped: false, fatal: 'kit.json managed 缺失或非数组', modified: [], gone: [], total: 0, version: kit.version };
28
+ }
29
+ const modified = [];
30
+ const gone = [];
31
+ const managed = kit.managed;
32
+ for (const f of managed) {
33
+ const p = path.join(target, f.rel);
34
+ if (!fs.existsSync(p)) { gone.push(f.rel); continue; }
35
+ const h = createHash('sha256').update(fs.readFileSync(p, 'utf8').replace(/\r\n/g, '\n'), 'utf8').digest('hex'); // LF 归一,同 doctor §4
36
+ if (h !== f.sha256) modified.push(f.rel);
37
+ }
38
+ return { skipped: false, fatal: '', modified, gone, total: managed.length, version: kit.version };
39
+ }
40
+
41
+ const isMain = process.argv[1] && process.argv[1].endsWith('check-ledger.mjs');
42
+ if (isMain) {
43
+ const r = ledgerDrift();
44
+ console.log('▶ flow-kit check-ledger');
45
+ if (r.skipped) {
46
+ console.log(' ' + r.note + ',跳过 ✅');
47
+ process.exit(0);
48
+ }
49
+ console.log(' kit.json v' + r.version + ':managed ' + r.total + ' 份,台账 ↔ 盘面(工作树,LF 归一)');
50
+ if (r.fatal) console.error('❌ ' + r.fatal);
51
+ if (r.modified.length) {
52
+ console.error(' 台账漂移(盘面 ≠ 台账 sha)' + r.modified.length + ':');
53
+ for (const rel of r.modified) console.error(' - ' + rel);
54
+ }
55
+ if (r.gone.length) {
56
+ console.error(' managed 缺失 ' + r.gone.length + ':');
57
+ for (const rel of r.gone) console.error(' - ' + rel);
58
+ }
59
+ if (r.fatal || r.modified.length || r.gone.length) {
60
+ console.error(' 提交被拦:kit.json 与盘面不一致(预 landing / 手改未随动)——跑 node bin/flow-kit.mjs sync 成对后原路重试');
61
+ process.exit(1);
62
+ }
63
+ console.log(' 全对齐 ✅');
64
+ process.exit(0);
65
+ }
@@ -0,0 +1,127 @@
1
+ // check-ledger.test.mjs — managed 台账快检测试(2026-09-28 ledger-precommit-gate)
2
+ // 方法:fixture 临时目录 .agents/kit.json + managed 件,调 ledgerDrift() + spawnSync CLI 出口码;
3
+ // 真实仓库 baseline(包源环境全对齐;装户环境 SKIP 不计失败,沿 source-sync-check 先例)
4
+ // 判据:S1 全对齐 S2 内容漂移逐份列出 S3 缺失 S4 CRLF×LF 归一不误报 S5 无 kit.json skip
5
+ // S6 kit.json 解析失败 fatal S7/S8 CLI 出口码 0/1 S9 真实仓 baseline
6
+ // 用法:node templates/_agents/scripts/check-ledger.test.mjs(装副本直跑亦然)
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import os from 'node:os';
10
+ import { spawnSync } from 'node:child_process';
11
+ import { fileURLToPath } from 'node:url';
12
+ import { createHash } from 'node:crypto';
13
+ import { ledgerDrift } from './check-ledger.mjs';
14
+
15
+ let pass = 0, failCount = 0;
16
+ function check(name, cond, detail = '') {
17
+ if (cond) { pass++; console.log('PASS ' + name); }
18
+ else { failCount++; console.log('FAIL ' + name + (detail ? '——' + detail : '')); }
19
+ }
20
+
21
+ // LF 归一 sha(同 check-ledger.mjs / doctor §4 口径)
22
+ const lfSha = (s) => createHash('sha256').update(s.replace(/\r\n/g, '\n'), 'utf8').digest('hex');
23
+
24
+ function mkFixture(managed, entries, kitRaw) {
25
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'fk-cl-'));
26
+ for (const [rel, content] of Object.entries(managed)) {
27
+ fs.mkdirSync(path.join(root, path.dirname(rel)), { recursive: true });
28
+ fs.writeFileSync(path.join(root, rel), content);
29
+ }
30
+ fs.mkdirSync(path.join(root, '.agents'), { recursive: true });
31
+ fs.writeFileSync(path.join(root, '.agents', 'kit.json'),
32
+ kitRaw !== undefined ? kitRaw : JSON.stringify({ kit: 't', version: '0.0.0-test', managed: entries }));
33
+ return root;
34
+ }
35
+
36
+ const SCRIPT = path.join(path.dirname(fileURLToPath(import.meta.url)), 'check-ledger.mjs');
37
+ const runCli = (cwd) => spawnSync(process.execPath, [SCRIPT], { encoding: 'utf8', cwd });
38
+
39
+ // ---- S1 全对齐 → 无漂移无缺失 ----
40
+ {
41
+ const root = mkFixture({ '.agents/scripts/a.mjs': 'A v1\n' }, [{ rel: '.agents/scripts/a.mjs', sha256: lfSha('A v1\n') }]);
42
+ const r = ledgerDrift(root);
43
+ check('S1 全对齐 → modified=0 gone=0 total=1', !r.skipped && r.modified.length === 0 && r.gone.length === 0 && r.total === 1, JSON.stringify(r));
44
+ fs.rmSync(root, { recursive: true, force: true });
45
+ }
46
+
47
+ // ---- S2 内容漂移 → 逐份列出 rel ----
48
+ {
49
+ const root = mkFixture({ '.agents/scripts/a.mjs': 'A 改了\n' }, [{ rel: '.agents/scripts/a.mjs', sha256: lfSha('A v1\n') }]);
50
+ const r = ledgerDrift(root);
51
+ check('S2 内容漂移 → modified=[a.mjs]', !r.skipped && r.modified.length === 1 && r.modified[0] === '.agents/scripts/a.mjs' && r.gone.length === 0, JSON.stringify(r));
52
+ fs.rmSync(root, { recursive: true, force: true });
53
+ }
54
+
55
+ // ---- S3 managed 缺失 → gone ----
56
+ {
57
+ const root = mkFixture({}, [{ rel: '.agents/scripts/gone.mjs', sha256: lfSha('x') }]);
58
+ const r = ledgerDrift(root);
59
+ check('S3 缺失 → gone=[gone.mjs]', !r.skipped && r.gone.length === 1 && r.gone[0] === '.agents/scripts/gone.mjs' && r.modified.length === 0, JSON.stringify(r));
60
+ fs.rmSync(root, { recursive: true, force: true });
61
+ }
62
+
63
+ // ---- S4 CRLF 盘面 × LF 台账 → 归一不误报 ----
64
+ {
65
+ const root = mkFixture({ '.agents/scripts/crlf.mjs': 'B v1\r\nline2\r\n' }, [{ rel: '.agents/scripts/crlf.mjs', sha256: lfSha('B v1\nline2\n') }]);
66
+ const r = ledgerDrift(root);
67
+ check('S4 CRLF 盘面 × LF 台账 → modified=0(归一口径)', !r.skipped && r.modified.length === 0 && r.gone.length === 0, JSON.stringify(r));
68
+ fs.rmSync(root, { recursive: true, force: true });
69
+ }
70
+
71
+ // ---- S5 无 kit.json → skipped(非 flow-kit 安装兼容) ----
72
+ {
73
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'fk-cl-'));
74
+ const r = ledgerDrift(root);
75
+ check('S5 无 kit.json → skipped=true', r.skipped === true && typeof r.note === 'string', JSON.stringify(r));
76
+ fs.rmSync(root, { recursive: true, force: true });
77
+ }
78
+
79
+ // ---- S6 kit.json 解析失败 → fatal ----
80
+ {
81
+ const root = mkFixture({}, [], '{ kit: 坏 json');
82
+ const r = ledgerDrift(root);
83
+ check('S6 解析失败 → fatal 非空', !r.skipped && typeof r.fatal === 'string' && r.fatal.includes('解析失败'), JSON.stringify(r));
84
+ fs.rmSync(root, { recursive: true, force: true });
85
+ }
86
+
87
+ // ---- S6b JSON 合法但 managed 缺失/非数组 → fatal(不静默全对齐;独立复核 P2-3) ----
88
+ {
89
+ const root = mkFixture({}, [], JSON.stringify({ kit: 't', version: '0.0.0-test' }));
90
+ const r = ledgerDrift(root);
91
+ check('S6b managed 非数组 → fatal 非空(fail-loud)', !r.skipped && typeof r.fatal === 'string' && r.fatal.includes('managed'), JSON.stringify(r));
92
+ fs.rmSync(root, { recursive: true, force: true });
93
+ }
94
+
95
+ // ---- S7 CLI 干净 fixture → exit 0 + 全对齐 ----
96
+ {
97
+ const root = mkFixture({ '.agents/scripts/a.mjs': 'A v1\n' }, [{ rel: '.agents/scripts/a.mjs', sha256: lfSha('A v1\n') }]);
98
+ const p = runCli(root);
99
+ check('S7 CLI 全对齐 → exit 0 + 「全对齐」', p.status === 0 && p.stdout.includes('全对齐'), `status=${p.status} out=${p.stdout}`);
100
+ fs.rmSync(root, { recursive: true, force: true });
101
+ }
102
+
103
+ // ---- S8 CLI 漂移 fixture → exit 1 + 列出 rel + sync 提示 ----
104
+ {
105
+ const root = mkFixture({ '.agents/scripts/a.mjs': 'A 改了\n' }, [{ rel: '.agents/scripts/a.mjs', sha256: lfSha('A v1\n') }]);
106
+ const p = runCli(root);
107
+ check('S8 CLI 漂移 → exit 1 + rel + sync 提示', p.status === 1 && p.stderr.includes('.agents/scripts/a.mjs') && p.stderr.includes('sync'), `status=${p.status} err=${p.stderr}`);
108
+ fs.rmSync(root, { recursive: true, force: true });
109
+ }
110
+
111
+ // ---- S9 真实仓 baseline:包源环境台账↔盘面全对齐;装户环境 SKIP 不计失败 ----
112
+ {
113
+ let srcRoot = path.dirname(fileURLToPath(import.meta.url));
114
+ for (let i = 0; i < 5 && srcRoot; i++) {
115
+ if (fs.existsSync(path.join(srcRoot, 'templates', '_agents'))) break;
116
+ srcRoot = path.dirname(srcRoot);
117
+ }
118
+ if (!srcRoot || !fs.existsSync(path.join(srcRoot, 'templates', '_agents'))) {
119
+ console.log('SKIP S9(装户环境无包源,不计失败)');
120
+ } else {
121
+ const r = ledgerDrift(srcRoot);
122
+ check('S9 真实仓 baseline → 台账↔盘面全对齐', !r.skipped && r.modified.length === 0 && r.gone.length === 0, JSON.stringify({ modified: r.modified, gone: r.gone }));
123
+ }
124
+ }
125
+
126
+ console.log(`\n合计: PASS ${pass} / FAIL ${failCount}`);
127
+ process.exit(failCount ? 1 : 0);