agentic-flow-kit 1.7.0 → 1.7.16
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 +81 -9
- package/modules/gates/README.md +1 -0
- package/modules/gates/test-protect/README.md +23 -0
- package/modules/gates/test-protect/check-test-protect.mjs +64 -0
- package/modules/gates/test-protect/local-pre-commit.line +1 -0
- package/modules/hosts/README.md +57 -0
- package/modules/hosts/claude/commands/wf-build.md +5 -24
- package/modules/hosts/claude/commands/wf-deploy.md +1 -0
- package/modules/hosts/claude/commands/wf-design.md +7 -6
- package/modules/hosts/claude/commands/wf-gate-checklist.md +1 -3
- package/modules/hosts/claude/commands/wf-new-task.md +42 -21
- package/modules/hosts/claude/commands/wf-plan.md +1 -1
- package/modules/hosts/claude/commands/wf-review.md +1 -1
- package/modules/hosts/claude/commands/wf-sync-hosts.md +8 -16
- package/modules/hosts/claude/commands/wf-test.md +5 -4
- package/modules/hosts/codex/commands/wf-build.md +5 -24
- package/modules/hosts/codex/commands/wf-deploy.md +1 -0
- package/modules/hosts/codex/commands/wf-design.md +7 -6
- package/modules/hosts/codex/commands/wf-gate-checklist.md +1 -3
- package/modules/hosts/codex/commands/wf-new-task.md +42 -21
- package/modules/hosts/codex/commands/wf-plan.md +1 -1
- package/modules/hosts/codex/commands/wf-review.md +1 -1
- package/modules/hosts/codex/commands/wf-sync-hosts.md +8 -16
- package/modules/hosts/codex/commands/wf-test.md +5 -4
- package/modules/hosts/cursor/commands/wf-build.md +5 -24
- package/modules/hosts/cursor/commands/wf-deploy.md +1 -0
- package/modules/hosts/cursor/commands/wf-design.md +7 -6
- package/modules/hosts/cursor/commands/wf-gate-checklist.md +1 -3
- package/modules/hosts/cursor/commands/wf-new-task.md +42 -21
- package/modules/hosts/cursor/commands/wf-plan.md +1 -1
- package/modules/hosts/cursor/commands/wf-review.md +1 -1
- package/modules/hosts/cursor/commands/wf-sync-hosts.md +8 -16
- package/modules/hosts/cursor/commands/wf-test.md +5 -4
- package/modules/hosts/opencode/commands/wf-build.md +5 -24
- package/modules/hosts/opencode/commands/wf-deploy.md +1 -0
- package/modules/hosts/opencode/commands/wf-design.md +7 -6
- package/modules/hosts/opencode/commands/wf-gate-checklist.md +1 -3
- package/modules/hosts/opencode/commands/wf-new-task.md +42 -21
- package/modules/hosts/opencode/commands/wf-plan.md +1 -1
- package/modules/hosts/opencode/commands/wf-review.md +1 -1
- package/modules/hosts/opencode/commands/wf-sync-hosts.md +8 -16
- package/modules/hosts/opencode/commands/wf-test.md +5 -4
- package/modules/hosts/trae/commands/wf-build.md +5 -24
- package/modules/hosts/trae/commands/wf-deploy.md +1 -0
- package/modules/hosts/trae/commands/wf-design.md +7 -6
- package/modules/hosts/trae/commands/wf-gate-checklist.md +1 -3
- package/modules/hosts/trae/commands/wf-new-task.md +42 -21
- package/modules/hosts/trae/commands/wf-plan.md +1 -1
- package/modules/hosts/trae/commands/wf-review.md +1 -1
- package/modules/hosts/trae/commands/wf-sync-hosts.md +8 -16
- package/modules/hosts/trae/commands/wf-test.md +5 -4
- package/package.json +2 -2
- package/src/add-gate.mjs +25 -1
- package/src/add-gate.test.mjs +103 -0
- package/src/cli.mjs +59 -5
- package/src/doctor.mjs +136 -12
- package/src/executor.mjs +245 -0
- package/src/executor.test.mjs +292 -0
- package/src/gates.test.mjs +17 -0
- package/src/init.mjs +14 -1
- package/src/init.test.mjs +19 -0
- package/src/instance-ref.test.mjs +62 -0
- package/src/jscpd-drift.test.mjs +144 -0
- package/src/profiles.mjs +3 -0
- package/src/render.mjs +6 -1
- package/src/rule-budget.test.mjs +103 -0
- package/src/sync-hosts.mjs +41 -45
- package/src/sync-hosts.test.mjs +67 -56
- package/src/trae-hooks.test.mjs +15 -3
- package/src/validate.mjs +72 -0
- package/src/validate.test.mjs +95 -0
- package/templates/.gitattributes +8 -0
- package/templates/AGENTS.md +2 -1
- package/templates/_agents/commands/build.md +5 -24
- package/templates/_agents/commands/deploy.md +1 -0
- package/templates/_agents/commands/design.md +7 -6
- package/templates/_agents/commands/gate-checklist.md +1 -3
- package/templates/_agents/commands/new-task.md +42 -21
- package/templates/_agents/commands/plan.md +1 -1
- package/templates/_agents/commands/review.md +1 -1
- package/templates/_agents/commands/sync-hosts.md +8 -16
- package/templates/_agents/commands/test.md +5 -4
- package/templates/_agents/control-surfaces.txt +53 -0
- package/templates/_agents/engine-lock.json +246 -0
- package/templates/_agents/lane-surfaces.txt +1 -1
- package/templates/_agents/level-hints.txt +30 -0
- package/templates/_agents/scripts/agg-delegations.cjs +51 -6
- package/templates/_agents/scripts/agg-delegations.test.mjs +84 -4
- package/templates/_agents/scripts/check-engine-integrity.mjs +217 -0
- package/templates/_agents/scripts/check-engine-integrity.test.mjs +215 -0
- package/templates/_agents/scripts/check-lane-surface.mjs +78 -24
- package/templates/_agents/scripts/check-lane-surface.test.mjs +66 -0
- package/templates/_agents/scripts/check-ledger-invariant.mjs +112 -14
- package/templates/_agents/scripts/check-ledger-invariant.test.mjs +5 -1
- package/templates/_agents/scripts/check-loop-rev.test.mjs +6 -4
- package/templates/_agents/scripts/check-loop.mjs +255 -45
- package/templates/_agents/scripts/check-loop.test.mjs +330 -15
- package/templates/_agents/scripts/check-metric-claims.mjs +4 -2
- package/templates/_agents/scripts/compact-ledgers.mjs +97 -0
- package/templates/_agents/scripts/compact-ledgers.test.mjs +162 -0
- package/templates/_agents/scripts/confirm-doc.mjs +54 -12
- package/templates/_agents/scripts/confirm-doc.test.mjs +59 -0
- package/templates/_agents/scripts/doctor.test.mjs +294 -1
- package/templates/_agents/scripts/draft-sediment.mjs +178 -0
- package/templates/_agents/scripts/draft-sediment.test.mjs +272 -0
- package/templates/_agents/scripts/edit-face-check.test.mjs +5 -4
- package/templates/_agents/scripts/fill-intent.mjs +2 -2
- package/templates/_agents/scripts/fixture-scripts.mjs +153 -0
- package/templates/_agents/scripts/fixture-scripts.test.mjs +107 -0
- package/templates/_agents/scripts/gate-checklist.mjs +6 -3
- package/templates/_agents/scripts/gate-checklist.test.mjs +2 -5
- package/templates/_agents/scripts/gen-wiki-board.mjs +61 -6
- package/templates/_agents/scripts/gen-wiki-board.test.mjs +74 -1
- package/templates/_agents/scripts/gen-workflow-dashboard.mjs +10 -13
- package/templates/_agents/scripts/gen-workflow-metrics.mjs +12 -30
- package/templates/_agents/scripts/git-merge-jsonl.mjs +63 -0
- package/templates/_agents/scripts/git-merge-jsonl.test.mjs +125 -0
- package/templates/_agents/scripts/kb-search.mjs +87 -12
- package/templates/_agents/scripts/kb-search.test.mjs +83 -0
- package/templates/_agents/scripts/ledger-doc-chain.test.mjs +166 -0
- package/templates/_agents/scripts/ledger-loader.mjs +108 -0
- package/templates/_agents/scripts/ledger-loader.test.mjs +131 -0
- package/templates/_agents/scripts/ledger-prune.mjs +96 -0
- package/templates/_agents/scripts/ledger-prune.test.mjs +120 -0
- package/templates/_agents/scripts/ledger-ts-monotonic.test.mjs +135 -0
- package/templates/_agents/scripts/level-criteria.mjs +232 -0
- package/templates/_agents/scripts/level-criteria.test.mjs +279 -0
- package/templates/_agents/scripts/pipeline-run.mjs +5 -3
- package/templates/_agents/scripts/policy.mjs +47 -0
- package/templates/_agents/scripts/solidify-task.mjs +1 -1
- package/templates/_agents/scripts/stage-gates.mjs +52 -10
- package/templates/_agents/scripts/stage-gates.test.mjs +105 -0
- package/templates/_agents/scripts/verify-wiki-consistency.mjs +34 -0
- package/templates/_agents/scripts/verify-wiki-consistency.test.mjs +44 -2
- package/templates/_agents/scripts/verify.mjs +1 -1
- package/templates/_agents/scripts/wf-journal.test.mjs +1 -1
- package/templates/_agents/scripts/wiki-search.test.mjs +5 -6
- package/templates/_agents/scripts/workflows-check.mjs +1 -1
- package/templates/_agents/scripts-test-exempt.txt +3 -1
- package/templates/_agents/skills/wiki/SKILL.md +5 -3
- package/templates/_agents/workflows/pipeline-closing.md +4 -5
- package/templates/_githooks/pre-commit +3 -34
- package/templates/_githooks/pre-push +3 -3
- package/templates/wiki/INDEX.md +44 -1
- package/templates/wiki//347/237/245/350/257/206/346/262/211/346/267/200/346/200/273/350/247/210.html +22 -1
- package/templates/workflow/README.md +11 -0
- package/templates/workflow/delegations.md +5 -4
- package/templates/workflow/incidents/_TEMPLATE.md +2 -0
- package/templates/workflow/intents/_TEMPLATE.md +3 -0
- package/templates/workflow/plans/_TEMPLATE.md +4 -0
- package/templates/workflow/specs/_TEMPLATE.md +23 -0
- package/modules/hosts/claude/commands/wf-source-sync-check.md +0 -91
- package/modules/hosts/codex/commands/wf-source-sync-check.md +0 -91
- package/modules/hosts/cursor/commands/wf-source-sync-check.md +0 -91
- package/modules/hosts/opencode/commands/wf-source-sync-check.md +0 -91
- package/modules/hosts/trae/commands/wf-source-sync-check.md +0 -92
- package/templates/_agents/commands/source-sync-check.md +0 -105
- package/templates/_agents/scripts/check-ledger.mjs +0 -109
- package/templates/_agents/scripts/check-ledger.test.mjs +0 -197
- package/templates/_agents/scripts/source-sync-check.mjs +0 -156
- package/templates/_agents/scripts/source-sync-check.test.mjs +0 -281
- package/templates/_agents/workflows/source-sync-repair.md +0 -30
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// compact-ledgers.test.mjs — 台账分片写入与 compact 归并 fixture 套件(2026-10-12 ledger-shard-write)
|
|
3
|
+
// 覆盖:分片写入定位 + 跨来源链衔接 / compact 默认 dry-run 不动盘 / 幂等 / 前缀不变式(既有行保序)/
|
|
4
|
+
// 读取失败中止 / 分片历史扫描与 compact 删除豁免 / 行未并入时仍拦丢行。
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
9
|
+
import { spawnSync } from 'node:child_process';
|
|
10
|
+
|
|
11
|
+
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const { compactLedgers } = await import(pathToFileURL(path.join(SCRIPT_DIR, 'compact-ledgers.mjs')).href);
|
|
13
|
+
const { appendLedger } = await import(pathToFileURL(path.join(SCRIPT_DIR, 'confirm-doc.mjs')).href);
|
|
14
|
+
const { ledgerChainHash } = await import(pathToFileURL(path.join(SCRIPT_DIR, 'policy.mjs')).href);
|
|
15
|
+
// 夹具闭包(2026-10-12 fixture-closure-rootfix):整闭包自动跟随,不再手写依赖枚举(漏拷 = 假绿)
|
|
16
|
+
const { copyScriptClosure } = await import(pathToFileURL(path.join(SCRIPT_DIR, 'fixture-scripts.mjs')).href);
|
|
17
|
+
|
|
18
|
+
let pass = 0, fail = 0;
|
|
19
|
+
const check = (name, ok, detail = '') => {
|
|
20
|
+
if (ok) { pass++; console.log(` ok ${name}`); }
|
|
21
|
+
else { fail++; console.error(`FAIL ${name}${detail ? ' — ' + detail : ''}`); }
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const DOC_A = 'workflow/intents/2026-10-01-a.md';
|
|
25
|
+
const DOC_B = 'workflow/intents/2026-10-01-b.md';
|
|
26
|
+
const baseEntry = (doc, stage = 'approved') => ({
|
|
27
|
+
ts: new Date().toISOString(), doc, stage, fingerprint: 'a'.repeat(64), source: 'tty',
|
|
28
|
+
});
|
|
29
|
+
// --shard 依赖 doc 链口径(policyVersion ≥ 8):分片与主台账是不同文件,只有 doc 链跨来源接续。
|
|
30
|
+
// 装 kit.json 的 policyVersion=8 来启用,与真实装户升级后的形态一致。
|
|
31
|
+
const enableDocChain = (root) => {
|
|
32
|
+
fs.mkdirSync(path.join(root, '.agents'), { recursive: true });
|
|
33
|
+
fs.writeFileSync(path.join(root, '.agents', 'kit.json'), JSON.stringify({ policyVersion: 8 }), 'utf8');
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// ============ 场景 1:--shard 写入定位到分片,且跨来源链接得上 ============
|
|
37
|
+
{
|
|
38
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'shard-write-'));
|
|
39
|
+
enableDocChain(root);
|
|
40
|
+
// 先用主台账写一行(模拟该 doc 的历史行在主台账)
|
|
41
|
+
appendLedger(root, baseEntry(DOC_A));
|
|
42
|
+
const mainPath = path.join(root, '.agents', 'confirmations.jsonl');
|
|
43
|
+
check('S1 默认写主台账', fs.existsSync(mainPath));
|
|
44
|
+
|
|
45
|
+
// 再对同一 doc 用 --shard 写一行:前驱必须接主台账那行,而不是从 '' 重启
|
|
46
|
+
appendLedger(root, baseEntry(DOC_A, 'done'), { shard: true });
|
|
47
|
+
const shardPath = path.join(root, '.agents', 'confirmations.d', '2026-10-01-a.jsonl');
|
|
48
|
+
check('S1 --shard 写入分片文件(按 doc 基名命名)', fs.existsSync(shardPath));
|
|
49
|
+
|
|
50
|
+
const mainRows = fs.readFileSync(mainPath, 'utf8').trim().split('\n').map((l) => JSON.parse(l));
|
|
51
|
+
const shardRows = fs.readFileSync(shardPath, 'utf8').trim().split('\n').map((l) => JSON.parse(l));
|
|
52
|
+
check('S1 分片行的 prevHash 接主台账同 doc 行(跨来源衔接,链不断)',
|
|
53
|
+
shardRows[0].prevHash === mainRows[0].hash, `${shardRows[0].prevHash} vs ${mainRows[0].hash}`);
|
|
54
|
+
// 行内重算仍一致(doc 链前提)
|
|
55
|
+
const { hash, prevHash, ...rest } = shardRows[0];
|
|
56
|
+
check('S1 分片行行内哈希可重算', ledgerChainHash(rest, prevHash) === hash);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ============ 场景 2:compact 默认 dry-run 不动盘 ============
|
|
60
|
+
{
|
|
61
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'shard-dry-'));
|
|
62
|
+
appendLedger(root, baseEntry(DOC_A));
|
|
63
|
+
appendLedger(root, baseEntry(DOC_B), { shard: true });
|
|
64
|
+
const mainPath = path.join(root, '.agents', 'confirmations.jsonl');
|
|
65
|
+
const before = fs.readFileSync(mainPath, 'utf8');
|
|
66
|
+
const shardPath = path.join(root, '.agents', 'confirmations.d', '2026-10-01-b.jsonl');
|
|
67
|
+
|
|
68
|
+
const r = compactLedgers(root); // 默认 execute:false
|
|
69
|
+
check('S2 dry-run 标记 skipped 且给出并入行数', r.ok && r.skipped && r.reason === 'dry-run' && r.added === 1, JSON.stringify(r));
|
|
70
|
+
check('S2 dry-run 主台账字节不变', fs.readFileSync(mainPath, 'utf8') === before);
|
|
71
|
+
check('S2 dry-run 分片仍在', fs.existsSync(shardPath));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ============ 场景 3:--execute 归并 + 前缀不变式 ============
|
|
75
|
+
{
|
|
76
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'shard-exec-'));
|
|
77
|
+
appendLedger(root, baseEntry(DOC_A));
|
|
78
|
+
const mainPath = path.join(root, '.agents', 'confirmations.jsonl');
|
|
79
|
+
const before = fs.readFileSync(mainPath, 'utf8');
|
|
80
|
+
appendLedger(root, baseEntry(DOC_B), { shard: true });
|
|
81
|
+
|
|
82
|
+
const r = compactLedgers(root, { execute: true });
|
|
83
|
+
const after = fs.readFileSync(mainPath, 'utf8');
|
|
84
|
+
check('S3 execute 归并成功并计入 1 行', r.ok && !r.skipped && r.added === 1, JSON.stringify(r));
|
|
85
|
+
check('S3 既有行严格保序在前(append-only 前缀不变式)', after.startsWith(before), JSON.stringify(after.slice(0, 40)));
|
|
86
|
+
check('S3 分片已清理', !fs.existsSync(path.join(root, '.agents', 'confirmations.d', '2026-10-01-b.jsonl')));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ============ 场景 4:幂等(重跑不重复追加)============
|
|
90
|
+
{
|
|
91
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'shard-idem-'));
|
|
92
|
+
enableDocChain(root);
|
|
93
|
+
appendLedger(root, baseEntry(DOC_A));
|
|
94
|
+
appendLedger(root, baseEntry(DOC_B), { shard: true });
|
|
95
|
+
compactLedgers(root, { execute: true });
|
|
96
|
+
const mainPath = path.join(root, '.agents', 'confirmations.jsonl');
|
|
97
|
+
const after1 = fs.readFileSync(mainPath, 'utf8');
|
|
98
|
+
const mergedLine = after1.trim().split('\n').pop(); // 归并时实际追加的那一行(含 prevHash/hash)
|
|
99
|
+
|
|
100
|
+
// 把**同一行原文**再放进分片重跑(模拟重复归并同批内容)——行文本逐字相同才会被去重
|
|
101
|
+
fs.mkdirSync(path.join(root, '.agents', 'confirmations.d'), { recursive: true });
|
|
102
|
+
fs.writeFileSync(path.join(root, '.agents', 'confirmations.d', '2026-10-01-b.jsonl'), mergedLine + '\n', 'utf8');
|
|
103
|
+
const r = compactLedgers(root, { execute: true });
|
|
104
|
+
const after2 = fs.readFileSync(mainPath, 'utf8');
|
|
105
|
+
check('S4 重复归并不重复追加(并集按整行去重)', r.ok && r.added === 0 && after1 === after2, `added=${r.added}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ============ 场景 5:无分片 / 空分片 → 跳过且不报错 ============
|
|
109
|
+
{
|
|
110
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'shard-none-'));
|
|
111
|
+
const r = compactLedgers(root);
|
|
112
|
+
check('S5 无分片目录 → 跳过且 ok', r.ok && r.skipped);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ============ 场景 6:分片历史扫描 + compact 删除豁免(真 git)============
|
|
116
|
+
{
|
|
117
|
+
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'shard-hist-'));
|
|
118
|
+
const git = (...args) => spawnSync('git', args, { cwd: d, encoding: 'utf8' });
|
|
119
|
+
git('init', '-q', '.'); git('config', 'user.email', 't@t.io'); git('config', 'user.name', 't');
|
|
120
|
+
git('config', 'core.autocrlf', 'false');
|
|
121
|
+
fs.mkdirSync(path.join(d, '.agents', 'scripts'), { recursive: true });
|
|
122
|
+
copyScriptClosure(path.join(d, '.agents', 'scripts'), ['check-ledger-invariant.mjs']);
|
|
123
|
+
const mkDoc = (rel) => {
|
|
124
|
+
const p = path.join(d, ...rel.split('/'));
|
|
125
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
126
|
+
fs.writeFileSync(p, '---\n状态: approved\n---\n# doc\n', 'utf8');
|
|
127
|
+
};
|
|
128
|
+
const rowObj = (doc) => JSON.stringify({ ts: '2026-10-01T00:00:00Z', doc, stage: 'approved', fingerprint: 'a'.repeat(64), source: 'tty' });
|
|
129
|
+
const runGate = () => spawnSync(process.execPath, ['.agents/scripts/check-ledger-invariant.mjs'], { cwd: d, encoding: 'utf8' });
|
|
130
|
+
|
|
131
|
+
mkDoc(DOC_A); mkDoc(DOC_B);
|
|
132
|
+
// 基线:主台账一行 + 分片一行(分片持有 B 的行)
|
|
133
|
+
fs.mkdirSync(path.join(d, '.agents', 'confirmations.d'), { recursive: true });
|
|
134
|
+
fs.writeFileSync(path.join(d, '.agents', 'confirmations.jsonl'), rowObj(DOC_A) + '\n', 'utf8');
|
|
135
|
+
fs.writeFileSync(path.join(d, '.agents', 'confirmations.d', '2026-10-01-b.jsonl'), rowObj(DOC_B) + '\n', 'utf8');
|
|
136
|
+
git('add', '-A'); git('commit', '-qm', 'base with shard');
|
|
137
|
+
let r = runGate();
|
|
138
|
+
check('S6 分片入库且未删 → 历史扫描放行', r.status === 0, `exit=${r.status} ${r.stderr}`);
|
|
139
|
+
|
|
140
|
+
// compact 归并后删分片:删除豁免应认领
|
|
141
|
+
fs.writeFileSync(path.join(d, '.agents', 'confirmations.jsonl'), rowObj(DOC_A) + '\n' + rowObj(DOC_B) + '\n', 'utf8');
|
|
142
|
+
fs.rmSync(path.join(d, '.agents', 'confirmations.d', '2026-10-01-b.jsonl'));
|
|
143
|
+
git('add', '-A'); git('commit', '-qm', 'compact merged');
|
|
144
|
+
r = runGate();
|
|
145
|
+
check('S6 compact 归并删分片 → 删除豁免认领,exit 0', r.status === 0, `exit=${r.status} ${r.stderr}`);
|
|
146
|
+
check('S6 豁免记录点名 compact 归并形态', /compact 归并形态/.test(r.stderr), r.stderr.slice(0, 200));
|
|
147
|
+
|
|
148
|
+
// 未归并就删分片(丢行)→ 仍 hard 拦。用**从未进过主台账**的独立 doc C,
|
|
149
|
+
// 否则它的行已在主台账里(早先 compact 并过),删除豁免会正确地放行——那正是豁免该有的行为。
|
|
150
|
+
const DOC_C = 'workflow/intents/2026-10-01-c.md';
|
|
151
|
+
mkDoc(DOC_C);
|
|
152
|
+
fs.writeFileSync(path.join(d, '.agents', 'confirmations.d', '2026-10-01-c.jsonl'), rowObj(DOC_C) + '\n', 'utf8');
|
|
153
|
+
git('add', '-A'); git('commit', '-qm', 'add shard c');
|
|
154
|
+
fs.rmSync(path.join(d, '.agents', 'confirmations.d', '2026-10-01-c.jsonl'));
|
|
155
|
+
git('add', '-A'); git('commit', '-qm', 'drop shard c without merge');
|
|
156
|
+
r = runGate();
|
|
157
|
+
check('S6 未归并即删分片(丢行)→ exit 1 hard 拦', r.status === 1, `exit=${r.status}`);
|
|
158
|
+
check('S6 报文点名「分片删除丢行」', /分片删除丢行/.test(r.stderr), r.stderr.slice(0, 300));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
console.log(`\n[compact-ledgers.test] PASS ${pass} / FAIL ${fail}`);
|
|
162
|
+
process.exit(fail ? 1 : 0);
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
// node .agents/scripts/confirm-doc.mjs <doc...> --delegated "<用户对话原话>"(委托代录)
|
|
26
26
|
// node .agents/scripts/confirm-doc.mjs <doc...> --delegated "<原话>" --batch(协作道批量:L0/L1 多份一次代录)
|
|
27
27
|
// node .agents/scripts/confirm-doc.mjs <doc...> --to superseded|cancelled(放弃态:取代/取消,留指纹与台账)
|
|
28
|
+
// node .agents/scripts/confirm-doc.mjs <doc...> --shard(分片写入:多 worktree 并行时各写各的
|
|
29
|
+
// .agents/confirmations.d/<doc>.jsonl,避免同文件并发覆盖;里程碑跑 compact-ledgers.mjs 归并)
|
|
28
30
|
// 多文档一次传入:TTY 模式天然逐份过目键入;--delegated 默认一次仅一份(2026-09-27
|
|
29
31
|
// confirm-gate-one-per-call:多份并录曾系统性塌掉 build.md「逐件确认」三道门),L0/L1 协作道可
|
|
30
32
|
// `--batch` 多份一次代录(2026-09-30 hybrid-governance-risk-lanes):逐份读「级别」,L2/L3 逐份拒绝
|
|
@@ -39,8 +41,9 @@ import path from 'node:path';
|
|
|
39
41
|
import readline from 'node:readline';
|
|
40
42
|
import { createHash, randomBytes } from 'node:crypto';
|
|
41
43
|
import { spawnSync } from 'node:child_process';
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
44
|
+
import { readLedgerEntries } from './ledger-loader.mjs';
|
|
45
|
+
import { confirmGateFor, clarifyGateFor, doneGateFor, laneOfEntry, laneOfDoc } from './stage-gates.mjs';
|
|
46
|
+
import { loadKitPolicy, hasFreshVerifyLine, ledgerChainHash, ledgerDocChainActive, ledgerDocChainPrev, scopeDigest } from './policy.mjs';
|
|
44
47
|
|
|
45
48
|
// hasFreshVerify(root, doc):读 <root>/.agents/verifications.jsonl 判 24h 窗口绿行(判定单源
|
|
46
49
|
// policy.hasFreshVerifyLine)。只读,台账缺失/不可读一律 false(=无凭证,advisory 层 fail-open)。
|
|
@@ -148,22 +151,38 @@ export function applyTransition(text, targetStage, fingerprint16) {
|
|
|
148
151
|
}
|
|
149
152
|
|
|
150
153
|
// appendLedger(root, entry):追加一行 JSON(目录不存在自动建;只 append 不重写)
|
|
151
|
-
// 哈希链(2026-10-09 engine-quality-round2 D):每行追加 prevHash
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
// 哈希链(2026-10-09 engine-quality-round2 D):每行追加 prevHash 与 hash(ledgerChainHash 单源计算,
|
|
155
|
+
// check-loop 15 同函数验链)——「就地改/删中间行」必留断链痕迹;历史行零回填(无 hash 行后链重启),
|
|
156
|
+
// 整文件重写不可机器防(边界声明见 policy.ledgerChainHash)。
|
|
157
|
+
// 链域(2026-10-12 ledger-doc-chain):policy.ledgerDocChainSince 存在且合法 → prevHash 取**同行 doc 的
|
|
158
|
+
// 最后一个带 hash 行**(ledgerDocChainPrev),不再取物理末行。理由见 POLICIES v8 注释:物理链隐含
|
|
159
|
+
// 「单写者顺序追加」前提,而并发 append / git 并集合并 / 分片归并三种**正常协作**都会打断它。
|
|
160
|
+
// 缺键(v1-v7)→ 走旧物理链口径,存量零变化。
|
|
161
|
+
// shard(2026-10-12 ledger-shard-write):多 worktree 并行时两个进程同时 append 同一台账文件会互相覆盖
|
|
162
|
+
// (各自读到同一末行 → 同 prevHash → 后写者覆盖前写者)。--shard 把本行写入分片
|
|
163
|
+
// `.agents/confirmations.d/<doc基名>.jsonl`,各 worktree 各写各的、互不冲突;里程碑跑 compact-ledgers.mjs
|
|
164
|
+
// 归并回主台账。**默认仍写主台账**——分片是有意的并发开关,不设则零影响。
|
|
165
|
+
export function appendLedger(root, entry, { shard = false } = {}) {
|
|
166
|
+
const p = shard && entry.doc
|
|
167
|
+
? path.join(root, '.agents', 'confirmations.d', `${path.basename(entry.doc, '.md')}.jsonl`)
|
|
168
|
+
: path.join(root, '.agents', 'confirmations.jsonl');
|
|
156
169
|
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
157
170
|
let prevHash = '';
|
|
158
171
|
try {
|
|
159
|
-
if (
|
|
172
|
+
if (ledgerDocChainActive(loadKitPolicy(root))) {
|
|
173
|
+
// doc 链取前驱走**台账视图**(主台账 + 分片)而非单个文件:--shard 写入时,该 doc 的历史行
|
|
174
|
+
// 很可能在主台账里,只读分片文件会从 '' 重启 → 链断裂。视图取法天然跨来源衔接。
|
|
175
|
+
const rows = readLedgerEntries(root).map((e) => e.row);
|
|
176
|
+
prevHash = ledgerDocChainPrev(rows, entry.doc);
|
|
177
|
+
} else if (fs.existsSync(p)) {
|
|
178
|
+
// 旧物理链口径:取**本次写入目标文件**的末行 hash(v1-v7 存量行为,逐字不变)
|
|
160
179
|
const lines = fs.readFileSync(p, 'utf8').split(/\r?\n/).filter((l) => l.trim());
|
|
161
180
|
if (lines.length) {
|
|
162
181
|
const last = JSON.parse(lines[lines.length - 1]);
|
|
163
182
|
prevHash = last && typeof last.hash === 'string' ? last.hash : '';
|
|
164
183
|
}
|
|
165
184
|
}
|
|
166
|
-
} catch { /*
|
|
185
|
+
} catch { /* 不可解析 → prevHash ''(链从本行重启;断裂由 check-loop 15 出账) */ }
|
|
167
186
|
const { hash: _h, prevHash: _p, ...rest } = entry;
|
|
168
187
|
const row = { ...rest, prevHash, hash: ledgerChainHash(rest, prevHash) };
|
|
169
188
|
fs.appendFileSync(p, JSON.stringify(row) + '\n');
|
|
@@ -190,6 +209,7 @@ if (isMain) {
|
|
|
190
209
|
let toTarget = null; // null = 默认前向跳转;superseded|cancelled = 放弃态显式目标
|
|
191
210
|
let autoMode = false; // AI 自治模式(信任等级授权,2026-09-30 ai-autonomy-trust)
|
|
192
211
|
let batchMode = false; // 协作道批量代录(--batch,2026-09-30 hybrid-governance-risk-lanes:仅 --delegated + L0/L1)
|
|
212
|
+
let shardWrite = false; // 分片写入(--shard,2026-10-12 ledger-shard-write):多 worktree 并行时各写各的分片,避免互相覆盖
|
|
193
213
|
const docs = [];
|
|
194
214
|
for (let i = 0; i < argv.length; i++) {
|
|
195
215
|
if (argv[i] === '--root') root = path.resolve(argv[++i]);
|
|
@@ -197,12 +217,21 @@ if (isMain) {
|
|
|
197
217
|
else if (argv[i] === '--to') toTarget = argv[++i] ?? '';
|
|
198
218
|
else if (argv[i] === '--auto') autoMode = true;
|
|
199
219
|
else if (argv[i] === '--batch') batchMode = true;
|
|
220
|
+
else if (argv[i] === '--shard') shardWrite = true;
|
|
200
221
|
else docs.push(argv[i].replace(/\\/g, '/'));
|
|
201
222
|
}
|
|
202
223
|
if (toTarget !== null && !ABANDON_TARGETS.includes(toTarget)) {
|
|
203
224
|
console.error(`用法:--to 仅接受 superseded|cancelled(现「${toTarget}」)——cancelled 自 draft/approved/open/fixed;superseded 自 approved/done/fixed/closed(未确认态只能 cancelled;终态间仅允许 done/closed→superseded)`);
|
|
204
225
|
process.exit(1);
|
|
205
226
|
}
|
|
227
|
+
// --shard 与 doc 链口径绑定(2026-10-12 ledger-shard-write):分片与主台账分属不同文件,
|
|
228
|
+
// 只有 doc 链能跨来源接续;旧物理链口径下分片行的 prevHash 会与主台账末行失配 → 提交即断链。
|
|
229
|
+
// 故缺锚时**参数期直接拒**(友好报错),而不是让用户写完台账再被门禁拦。
|
|
230
|
+
if (shardWrite && !ledgerDocChainActive(loadKitPolicy(root))) {
|
|
231
|
+
console.error('❌ --shard 需要台账 doc 链口径(policy.ledgerDocChainSince),当前 policyVersion 无此键。\n 原因:分片与主台账是不同文件,只有 doc 链能跨来源接续;旧物理链口径下分片行会与主台账断链。\n 修法:在 .agents/kit.json 设 policyVersion: 8(或更高的支持版本)后重试。');
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
|
|
206
235
|
const delegated = delegatedQuote !== null;
|
|
207
236
|
if (delegated && autoMode) {
|
|
208
237
|
console.error('❌ --auto 与 --delegated 互斥:自治模式是用户预先授权的机器放行,委托代录是单次对话确认');
|
|
@@ -358,6 +387,19 @@ if (isMain) {
|
|
|
358
387
|
refused++;
|
|
359
388
|
continue;
|
|
360
389
|
}
|
|
390
|
+
// 待澄清项门(2026-10-12 needs-clarification):起草期挂「待澄清」标记**不阻断**(零阻塞),
|
|
391
|
+
// 但 draft→approved 确认时若仍挂着未澄清项则拒绝——确认即「认可这份文档」,文档里还写着
|
|
392
|
+
// 「这里我不知道」,确认就没有意义。与上面的前置门分开出账:拒绝原因必须指向真正的原因
|
|
393
|
+
// (合并会让作者去查「前置」,而前置其实是好的)。
|
|
394
|
+
// ⚠️ 文案锚点硬约束:本段禁止并入上方 gate 分支的输出契约(既有 confirm-doc.test.mjs 断言
|
|
395
|
+
// 锚定「未过逐阶段前置门(stage-gates)」子串),故单列独立分支。
|
|
396
|
+
const cl = clarifyGateFor(abs);
|
|
397
|
+
if (!cl.ok) {
|
|
398
|
+
console.error(`❌ ${doc} 含未澄清项(needs-clarification):${cl.reason}`);
|
|
399
|
+
console.error(` ${cl.hint}(本份未落账、未写盘)`);
|
|
400
|
+
refused++;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
361
403
|
}
|
|
362
404
|
// done 前置门(2026-09-30 confirm-gate-approved-history):→done 须 git 历史已留 approved 态
|
|
363
405
|
// (先 done 后提交 / 两跳同批提交 → 拒绝;防「确认态缺失」缺陷复发;判据单源见 stage-gates.doneGateFor)。
|
|
@@ -407,7 +449,7 @@ if (isMain) {
|
|
|
407
449
|
}
|
|
408
450
|
fs.writeFileSync(abs, applyTransition(text, target, fp.slice(0, 16)));
|
|
409
451
|
appendLedger(root, {
|
|
410
|
-
ts: new Date().toISOString(), doc, stage: target, fingerprint: fp, prev: st,
|
|
452
|
+
shard: shardWrite, ts: new Date().toISOString(), doc, stage: target, fingerprint: fp, prev: st,
|
|
411
453
|
source: `ai-auto-trust-L${trustConfig.level}`, quote: `AI 自治放行 (Trust Level ${trustConfig.level})`,
|
|
412
454
|
batch, seq, of: docs.length, scope,
|
|
413
455
|
});
|
|
@@ -428,7 +470,7 @@ if (isMain) {
|
|
|
428
470
|
}
|
|
429
471
|
fs.writeFileSync(abs, applyTransition(text, target, fp.slice(0, 16)));
|
|
430
472
|
appendLedger(root, {
|
|
431
|
-
ts: new Date().toISOString(), doc, stage: target, fingerprint: fp, prev: st,
|
|
473
|
+
shard: shardWrite, ts: new Date().toISOString(), doc, stage: target, fingerprint: fp, prev: st,
|
|
432
474
|
source: 'chat-delegated', quote: String(delegatedQuote),
|
|
433
475
|
// 调用事实(batch-ledger-audit):seq=本次调用内件序(从 1),of=本次调用总份数
|
|
434
476
|
batch, seq, of: docs.length,
|
|
@@ -458,7 +500,7 @@ if (isMain) {
|
|
|
458
500
|
}
|
|
459
501
|
fs.writeFileSync(abs, applyTransition(text, target, fp.slice(0, 16)));
|
|
460
502
|
appendLedger(root, {
|
|
461
|
-
|
|
503
|
+
shard: shardWrite, ts: new Date().toISOString(), doc, stage: target, fingerprint: fp, prev: st,
|
|
462
504
|
source: 'tty',
|
|
463
505
|
batch, seq, of: docs.length, // 调用事实(形态无关:TTY 多文档天然逐件过目,of>1 不构成违规)
|
|
464
506
|
});
|
|
@@ -386,6 +386,65 @@ const readLedgerOf = (root) => {
|
|
|
386
386
|
fs.rmSync(root, { recursive: true, force: true });
|
|
387
387
|
}
|
|
388
388
|
|
|
389
|
+
// ---- S19b【待澄清门】spec 挂未澄清项 → 拒绝落账(2026-10-12 needs-clarification)----
|
|
390
|
+
// 夹具沿 S23 的正路入口(台账行 + approved),确保**前置门已过**——否则拒绝原因会指向前置而非待澄清,
|
|
391
|
+
// 用例就测不出新门。断言拒绝文案点名「needs-clarification」且**不含**前置门文案(两门分开出账)。
|
|
392
|
+
{
|
|
393
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'confirm-cl1-'));
|
|
394
|
+
for (const sub of ['intents', 'specs']) fs.mkdirSync(path.join(root, 'workflow', sub), { recursive: true });
|
|
395
|
+
fs.mkdirSync(path.join(root, '.agents'), { recursive: true });
|
|
396
|
+
const fp = 'c'.repeat(64);
|
|
397
|
+
fs.writeFileSync(path.join(root, 'workflow', 'intents', '2026-10-12-n.md'), `---\n状态: approved\n级别: L2\n日期: 2026-10-12\n确认指纹: ${fp.slice(0, 16)}\n---\n# I\n`);
|
|
398
|
+
fs.writeFileSync(path.join(root, '.agents', 'confirmations.jsonl'), JSON.stringify({ ts: '2026-10-12T02:00:00.000Z', doc: 'workflow/intents/2026-10-12-n.md', stage: 'approved', fingerprint: fp, prev: 'draft', source: 'chat-delegated', batch: 'n1', seq: 1, of: 1 }) + '\n');
|
|
399
|
+
const specP = path.join(root, 'workflow', 'specs', '2026-10-12-n.md');
|
|
400
|
+
fs.writeFileSync(specP, '---\n状态: draft\n级别: L2\n---\n# S\n\n## 功能行为\n- 待澄清:错误率的判据是 5xx 占比还是全量?\n');
|
|
401
|
+
const before = fs.readFileSync(specP, 'utf8');
|
|
402
|
+
const r = spawnSync(process.execPath, [CLI, 'workflow/specs/2026-10-12-n.md', '--delegated', '可以'], { cwd: root, encoding: 'utf8' });
|
|
403
|
+
const after = fs.readFileSync(specP, 'utf8');
|
|
404
|
+
const led = readLedgerOf(root);
|
|
405
|
+
check('S19b 待澄清门:挂未澄清项 → exit 2 + 点名行号与内容 + 未落盘 + 零新增台账',
|
|
406
|
+
r.status === 2 && /needs-clarification/.test(r.stderr) && /第8行/.test(r.stderr) && /5xx 占比还是全量/.test(r.stderr)
|
|
407
|
+
&& after === before && led.length === 1,
|
|
408
|
+
JSON.stringify({ status: r.status, stderr: r.stderr, ledger: led.length }));
|
|
409
|
+
check('S19b 待澄清门:拒绝原因不误指向前置门(两门分开出账,否则作者查错地方)',
|
|
410
|
+
!/逐阶段前置门/.test(r.stderr), r.stderr);
|
|
411
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// ---- S19c【待澄清门】解决后(行首改「已澄清」)→ 放行;纯删行亦可 ----
|
|
415
|
+
{
|
|
416
|
+
const mkClarified = (body) => {
|
|
417
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'confirm-cl2-'));
|
|
418
|
+
for (const sub of ['intents', 'specs']) fs.mkdirSync(path.join(root, 'workflow', sub), { recursive: true });
|
|
419
|
+
fs.mkdirSync(path.join(root, '.agents'), { recursive: true });
|
|
420
|
+
const fp = 'd'.repeat(64);
|
|
421
|
+
fs.writeFileSync(path.join(root, 'workflow', 'intents', '2026-10-12-o.md'), `---\n状态: approved\n级别: L2\n日期: 2026-10-12\n确认指纹: ${fp.slice(0, 16)}\n---\n# I\n`);
|
|
422
|
+
fs.writeFileSync(path.join(root, '.agents', 'confirmations.jsonl'), JSON.stringify({ ts: '2026-10-12T02:00:00.000Z', doc: 'workflow/intents/2026-10-12-o.md', stage: 'approved', fingerprint: fp, prev: 'draft', source: 'chat-delegated', batch: 'o1', seq: 1, of: 1 }) + '\n');
|
|
423
|
+
fs.writeFileSync(path.join(root, 'workflow', 'specs', '2026-10-12-o.md'), body);
|
|
424
|
+
return root;
|
|
425
|
+
};
|
|
426
|
+
for (const [name, body] of [
|
|
427
|
+
['改前缀为「已澄清」(保留行、留痕曾不清楚)', '---\n状态: draft\n级别: L2\n---\n# S\n\n- 已澄清:错误率判据 = 5xx 占比(与用户 10-12 对齐)\n'],
|
|
428
|
+
['直接删行', '---\n状态: draft\n级别: L2\n---\n# S\n\n## 功能行为\n- 错误率判据 = 5xx 占比\n'],
|
|
429
|
+
]) {
|
|
430
|
+
const root = mkClarified(body);
|
|
431
|
+
const r = spawnSync(process.execPath, [CLI, 'workflow/specs/2026-10-12-o.md', '--delegated', '可以'], { cwd: root, encoding: 'utf8' });
|
|
432
|
+
const after = fs.readFileSync(path.join(root, 'workflow', 'specs', '2026-10-12-o.md'), 'utf8');
|
|
433
|
+
check(`S19c 待澄清门:${name} → 放行落态`, r.status === 0 && after.includes('状态: approved'),
|
|
434
|
+
JSON.stringify({ status: r.status, stderr: r.stderr }));
|
|
435
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
436
|
+
}
|
|
437
|
+
// intents 不受本门约束(歧义标记的落点是 spec/plan 的设计判据;intent 是需求单,澄清天然发生在此处)
|
|
438
|
+
const root3 = fs.mkdtempSync(path.join(os.tmpdir(), 'confirm-cl3-'));
|
|
439
|
+
fs.mkdirSync(path.join(root3, 'workflow', 'intents'), { recursive: true });
|
|
440
|
+
fs.writeFileSync(path.join(root3, 'workflow', 'intents', '2026-10-12-p.md'), '---\n状态: draft\n级别: L1\n---\n# I\n\n- 待澄清:范围含不含移动端?\n');
|
|
441
|
+
const r3 = spawnSync(process.execPath, [CLI, 'workflow/intents/2026-10-12-p.md', '--delegated', '可以'], { cwd: root3, encoding: 'utf8' });
|
|
442
|
+
check('S19c 待澄清门:intent 不受本门约束(澄清天然发生在需求单处)',
|
|
443
|
+
r3.status === 0 && fs.readFileSync(path.join(root3, 'workflow', 'intents', '2026-10-12-p.md'), 'utf8').includes('状态: approved'),
|
|
444
|
+
JSON.stringify({ status: r3.status, stderr: r3.stderr }));
|
|
445
|
+
fs.rmSync(root3, { recursive: true, force: true });
|
|
446
|
+
}
|
|
447
|
+
|
|
389
448
|
// ---- S20【逐阶段门】入口存在但未确认(draft)→ 拒绝 ----
|
|
390
449
|
{
|
|
391
450
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'confirm-sg2-'));
|