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.
- package/README.md +36 -12
- package/modules/gates/README.md +24 -0
- package/modules/gates/generated-readonly/README.md +13 -0
- package/modules/gates/generated-readonly/check-generated.mjs +61 -0
- package/modules/gates/generated-readonly/local-pre-commit.line +1 -0
- package/modules/gates/node-layer/README.md +9 -0
- package/modules/gates/node-layer/check-node-layer.mjs +29 -0
- package/modules/gates/node-layer/local-pre-commit.line +1 -0
- package/modules/gates/py-import/README.md +9 -0
- package/modules/gates/py-import/check-py-import.mjs +30 -0
- package/modules/gates/py-import/local-pre-commit.line +1 -0
- package/modules/hosts/claude/agents/implementer.md +52 -0
- package/modules/hosts/claude/agents/independent-reviewer.md +49 -0
- package/modules/hosts/claude/agents/ui-verifier.md +48 -0
- package/modules/hosts/claude/commands/wf-build.md +79 -0
- package/modules/hosts/claude/commands/wf-deploy.md +57 -0
- package/modules/hosts/claude/commands/wf-design.md +42 -0
- package/modules/hosts/claude/commands/wf-gate-checklist.md +77 -0
- package/modules/hosts/claude/commands/wf-maintain.md +76 -0
- package/modules/hosts/claude/commands/wf-new-task.md +65 -0
- package/modules/hosts/claude/commands/wf-plan.md +41 -0
- package/modules/hosts/claude/commands/wf-review.md +47 -0
- package/modules/hosts/claude/commands/wf-source-sync-check.md +89 -0
- package/modules/hosts/claude/commands/wf-sync-hosts.md +66 -0
- package/modules/hosts/claude/commands/wf-test.md +66 -0
- package/modules/hosts/codex/agents/implementer.md +52 -0
- package/modules/hosts/codex/agents/independent-reviewer.md +49 -0
- package/modules/hosts/codex/agents/ui-verifier.md +48 -0
- package/modules/hosts/codex/commands/wf-build.md +79 -0
- package/modules/hosts/codex/commands/wf-deploy.md +57 -0
- package/modules/hosts/codex/commands/wf-design.md +42 -0
- package/modules/hosts/codex/commands/wf-gate-checklist.md +77 -0
- package/modules/hosts/codex/commands/wf-maintain.md +76 -0
- package/modules/hosts/codex/commands/wf-new-task.md +65 -0
- package/modules/hosts/codex/commands/wf-plan.md +41 -0
- package/modules/hosts/codex/commands/wf-review.md +47 -0
- package/modules/hosts/codex/commands/wf-source-sync-check.md +89 -0
- package/modules/hosts/codex/commands/wf-sync-hosts.md +66 -0
- package/modules/hosts/codex/commands/wf-test.md +66 -0
- package/modules/hosts/codex/skills/flow-kit/SKILL.md +14 -0
- package/modules/hosts/cursor/agents/implementer.md +52 -0
- package/modules/hosts/cursor/agents/independent-reviewer.md +49 -0
- package/modules/hosts/cursor/agents/ui-verifier.md +48 -0
- package/modules/hosts/cursor/commands/wf-build.md +79 -0
- package/modules/hosts/cursor/commands/wf-deploy.md +57 -0
- package/modules/hosts/cursor/commands/wf-design.md +42 -0
- package/modules/hosts/cursor/commands/wf-gate-checklist.md +77 -0
- package/modules/hosts/cursor/commands/wf-maintain.md +76 -0
- package/modules/hosts/cursor/commands/wf-new-task.md +65 -0
- package/modules/hosts/cursor/commands/wf-plan.md +41 -0
- package/modules/hosts/cursor/commands/wf-review.md +47 -0
- package/modules/hosts/cursor/commands/wf-source-sync-check.md +89 -0
- package/modules/hosts/cursor/commands/wf-sync-hosts.md +66 -0
- package/modules/hosts/cursor/commands/wf-test.md +66 -0
- package/modules/hosts/omp/agents/implementer.md +1 -1
- package/modules/hosts/omp/agents/independent-reviewer.md +1 -1
- package/modules/hosts/omp/agents/ui-verifier.md +1 -1
- package/modules/hosts/opencode/agents/implementer.md +1 -1
- package/modules/hosts/opencode/agents/independent-reviewer.md +1 -1
- package/modules/hosts/opencode/agents/ui-verifier.md +1 -1
- package/modules/hosts/opencode/commands/{build.md → wf-build.md} +3 -4
- package/modules/hosts/opencode/commands/{design.md → wf-design.md} +2 -2
- package/modules/hosts/opencode/commands/{maintain.md → wf-maintain.md} +1 -1
- package/modules/hosts/opencode/commands/{plan.md → wf-plan.md} +3 -3
- package/modules/hosts/opencode/commands/{review.md → wf-review.md} +1 -1
- package/modules/hosts/opencode/commands/{source-sync-check.md → wf-source-sync-check.md} +1 -1
- package/modules/hosts/opencode/commands/{sync-hosts.md → wf-sync-hosts.md} +1 -1
- package/modules/hosts/opencode/commands/{test.md → wf-test.md} +3 -1
- package/modules/hosts/trae/agents/implementer.md +1 -1
- package/modules/hosts/trae/agents/independent-reviewer.md +1 -1
- package/modules/hosts/trae/agents/ui-verifier.md +1 -1
- package/modules/hosts/trae/commands/wf-build.md +3 -4
- package/modules/hosts/trae/commands/wf-design.md +2 -2
- package/modules/hosts/trae/commands/wf-maintain.md +1 -1
- package/modules/hosts/trae/commands/wf-plan.md +3 -3
- package/modules/hosts/trae/commands/wf-review.md +1 -1
- package/modules/hosts/trae/commands/wf-source-sync-check.md +1 -1
- package/modules/hosts/trae/commands/wf-sync-hosts.md +1 -1
- package/modules/hosts/trae/commands/wf-test.md +3 -1
- package/modules/hosts/zcode/agents/implementer.md +1 -1
- package/modules/hosts/zcode/agents/independent-reviewer.md +1 -1
- package/modules/hosts/zcode/agents/ui-verifier.md +1 -1
- package/package.json +1 -1
- package/src/cli.mjs +16 -4
- package/src/doctor.mjs +58 -5
- package/src/fresh-init.test.mjs +36 -0
- package/src/gates.test.mjs +126 -0
- package/src/init.mjs +8 -1
- package/src/init.test.mjs +7 -1
- package/src/profiles.mjs +10 -4
- package/src/run-tests.mjs +3 -0
- package/src/stack-profile.test.mjs +89 -0
- package/src/sync-hosts.mjs +12 -6
- package/src/sync-hosts.test.mjs +40 -17
- package/templates/AGENTS.md +4 -4
- package/templates/_agents/board/index.html +3 -3
- package/templates/_agents/commands/build.md +4 -5
- package/templates/_agents/commands/design.md +4 -4
- package/templates/_agents/commands/maintain.md +1 -1
- package/templates/_agents/commands/new-task.md +13 -2
- package/templates/_agents/commands/plan.md +4 -4
- package/templates/_agents/commands/review.md +1 -1
- package/templates/_agents/commands/source-sync-check.md +1 -1
- package/templates/_agents/commands/sync-hosts.md +1 -1
- package/templates/_agents/commands/test.md +4 -2
- package/templates/_agents/hooks/commit-check.cjs +45 -24
- package/templates/_agents/lane-surfaces.txt +14 -0
- package/templates/_agents/roles/implementer.md +1 -1
- package/templates/_agents/roles/independent-reviewer.md +1 -1
- package/templates/_agents/roles/ui-verifier.md +1 -1
- package/templates/_agents/scripts/check-lane-surface.mjs +164 -0
- package/templates/_agents/scripts/check-lane-surface.test.mjs +200 -0
- package/templates/_agents/scripts/check-ledger.mjs +65 -0
- package/templates/_agents/scripts/check-ledger.test.mjs +127 -0
- package/templates/_agents/scripts/check-loop-rev.test.mjs +337 -0
- package/templates/_agents/scripts/check-loop.mjs +460 -268
- package/templates/_agents/scripts/check-loop.test.mjs +677 -17
- package/templates/_agents/scripts/check-metric-claims.mjs +275 -0
- package/templates/_agents/scripts/commit-check-trigger.test.mjs +64 -1
- package/templates/_agents/scripts/confirm-doc.mjs +159 -15
- package/templates/_agents/scripts/confirm-doc.test.mjs +362 -10
- package/templates/_agents/scripts/doctor.test.mjs +105 -4
- package/templates/_agents/scripts/fill-intent.mjs +8 -7
- package/templates/_agents/scripts/fill-intent.test.mjs +12 -3
- package/templates/_agents/scripts/fill-plan.mjs +27 -11
- package/templates/_agents/scripts/fill-plan.test.mjs +148 -8
- package/templates/_agents/scripts/fill-spec.mjs +18 -3
- package/templates/_agents/scripts/fill-spec.test.mjs +198 -1
- package/templates/_agents/scripts/gate-checklist.mjs +4 -1
- package/templates/_agents/scripts/gen-workflow-index.mjs +6 -2
- package/templates/_agents/scripts/gen-workflow-index.test.mjs +12 -0
- package/templates/_agents/scripts/policy.mjs +46 -0
- package/templates/_agents/scripts/solidify-task.mjs +101 -0
- package/templates/_agents/scripts/stage-gates.mjs +256 -0
- package/templates/_agents/scripts/trust-mode.mjs +67 -0
- package/templates/_agents/scripts/wf-journal.mjs +71 -2
- package/templates/_agents/scripts/wf-journal.test.mjs +66 -0
- package/templates/_agents/scripts/workflow-board-server.mjs +18 -15
- package/templates/_agents/scripts/workflow-board-server.test.mjs +30 -1
- package/templates/_agents/workflows/pipeline-closing.md +1 -1
- package/templates/_githooks/pre-commit +49 -2
- package/templates/_githooks/pre-push +56 -12
- package/templates/workflow/README.md +22 -3
- package/templates/workflow/incidents/_TEMPLATE.md +4 -0
- package/templates/workflow/intents/_TEMPLATE.md +1 -0
- /package/modules/hosts/opencode/commands/{deploy.md → wf-deploy.md} +0 -0
- /package/modules/hosts/opencode/commands/{gate-checklist.md → wf-gate-checklist.md} +0 -0
- /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);
|