agentic-flow-kit 1.7.15 → 1.8.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 +50 -8
- package/modules/hosts/claude/commands/wf-build.md +1 -1
- package/modules/hosts/claude/commands/wf-design.md +5 -4
- package/modules/hosts/claude/commands/wf-maintain.md +1 -1
- package/modules/hosts/claude/commands/wf-new-task.md +47 -25
- package/modules/hosts/claude/commands/wf-plan.md +1 -1
- package/modules/hosts/claude/commands/wf-review.md +2 -2
- package/modules/hosts/claude/commands/wf-sync-hosts.md +7 -13
- package/modules/hosts/claude/commands/wf-test.md +2 -2
- package/modules/hosts/codex/commands/wf-build.md +1 -1
- package/modules/hosts/codex/commands/wf-design.md +5 -4
- package/modules/hosts/codex/commands/wf-maintain.md +1 -1
- package/modules/hosts/codex/commands/wf-new-task.md +47 -25
- package/modules/hosts/codex/commands/wf-plan.md +1 -1
- package/modules/hosts/codex/commands/wf-review.md +2 -2
- package/modules/hosts/codex/commands/wf-sync-hosts.md +7 -13
- package/modules/hosts/codex/commands/wf-test.md +2 -2
- package/modules/hosts/cursor/commands/wf-build.md +1 -1
- package/modules/hosts/cursor/commands/wf-design.md +5 -4
- package/modules/hosts/cursor/commands/wf-maintain.md +1 -1
- package/modules/hosts/cursor/commands/wf-new-task.md +47 -25
- package/modules/hosts/cursor/commands/wf-plan.md +1 -1
- package/modules/hosts/cursor/commands/wf-review.md +2 -2
- package/modules/hosts/cursor/commands/wf-sync-hosts.md +7 -13
- package/modules/hosts/cursor/commands/wf-test.md +2 -2
- package/modules/hosts/opencode/commands/wf-build.md +1 -1
- package/modules/hosts/opencode/commands/wf-design.md +5 -4
- package/modules/hosts/opencode/commands/wf-maintain.md +1 -1
- package/modules/hosts/opencode/commands/wf-new-task.md +47 -25
- package/modules/hosts/opencode/commands/wf-plan.md +1 -1
- package/modules/hosts/opencode/commands/wf-review.md +2 -2
- package/modules/hosts/opencode/commands/wf-sync-hosts.md +7 -13
- package/modules/hosts/opencode/commands/wf-test.md +2 -2
- package/modules/hosts/trae/commands/wf-build.md +1 -1
- package/modules/hosts/trae/commands/wf-design.md +5 -4
- package/modules/hosts/trae/commands/wf-maintain.md +1 -1
- package/modules/hosts/trae/commands/wf-new-task.md +47 -25
- package/modules/hosts/trae/commands/wf-plan.md +1 -1
- package/modules/hosts/trae/commands/wf-review.md +2 -2
- package/modules/hosts/trae/commands/wf-sync-hosts.md +7 -13
- package/modules/hosts/trae/commands/wf-test.md +2 -2
- package/package.json +1 -1
- package/src/cli.mjs +47 -4
- package/src/doctor.mjs +48 -3
- package/src/executor.mjs +262 -0
- package/src/executor.test.mjs +295 -0
- package/src/fresh-init.test.mjs +1 -1
- package/src/init.mjs +27 -3
- package/src/profiles.mjs +10 -3
- package/src/render.mjs +3 -3
- package/src/rule-budget.test.mjs +2 -2
- package/src/stack-profile.test.mjs +6 -0
- package/src/sync-hosts.mjs +37 -43
- package/src/sync-hosts.test.mjs +67 -56
- package/src/validate.mjs +6 -1
- package/src/validate.test.mjs +1 -1
- package/templates/.gitattributes +12 -1
- package/templates/AGENTS.md +3 -3
- package/templates/_agents/commands/build.md +1 -1
- package/templates/_agents/commands/design.md +5 -4
- package/templates/_agents/commands/maintain.md +1 -1
- package/templates/_agents/commands/new-task.md +47 -25
- package/templates/_agents/commands/plan.md +1 -1
- package/templates/_agents/commands/review.md +2 -2
- package/templates/_agents/commands/sync-hosts.md +7 -13
- package/templates/_agents/commands/test.md +2 -2
- package/templates/_agents/control-surfaces.txt +1 -1
- package/templates/_agents/engine-lock.json +334 -0
- package/templates/_agents/hooks/check-index-incremental.mjs +48 -0
- package/templates/_agents/hooks/check-pairing-incremental.mjs +66 -0
- package/templates/_agents/hooks/check-wiki-ledger.mjs +30 -0
- package/templates/_agents/lane-surfaces.txt +5 -2
- package/templates/_agents/level-hints.txt +30 -0
- package/templates/_agents/loop/papercut-debt/DESIGN.md +112 -0
- package/templates/_agents/mechanism-registry.md +27 -0
- package/templates/_agents/rule-budgets.txt +2 -2
- package/templates/_agents/scripts/agg-delegations.cjs +21 -3
- package/templates/_agents/scripts/agg-delegations.test.mjs +5 -3
- package/templates/_agents/scripts/agg-gate-stats.mjs +3 -3
- package/templates/_agents/scripts/agg-judgments.mjs +80 -0
- package/templates/_agents/scripts/agg-judgments.test.mjs +45 -0
- package/templates/_agents/scripts/archive-sweep.mjs +233 -0
- package/templates/_agents/scripts/archive-sweep.test.mjs +129 -0
- package/templates/_agents/scripts/check-attribution.mjs +37 -0
- package/templates/_agents/scripts/check-convergence.mjs +119 -0
- package/templates/_agents/scripts/check-engine-integrity.mjs +59 -127
- package/templates/_agents/scripts/check-engine-integrity.test.mjs +33 -45
- package/templates/_agents/scripts/check-funnel.mjs +56 -0
- package/templates/_agents/scripts/check-hygiene.mjs +5 -5
- package/templates/_agents/scripts/check-index-incremental.test.mjs +73 -0
- package/templates/_agents/scripts/check-lane-surface.mjs +74 -25
- package/templates/_agents/scripts/check-lane-surface.test.mjs +71 -5
- package/templates/_agents/scripts/check-ledger-invariant.mjs +427 -78
- package/templates/_agents/scripts/check-ledger-invariant.test.mjs +438 -4
- package/templates/_agents/scripts/check-loop-rev.test.mjs +13 -8
- package/templates/_agents/scripts/check-loop-unit.test.mjs +8 -0
- package/templates/_agents/scripts/check-loop.mjs +264 -75
- package/templates/_agents/scripts/check-loop.test.mjs +587 -61
- package/templates/_agents/scripts/check-metric-claims.mjs +4 -2
- package/templates/_agents/scripts/check-prejudgment.mjs +59 -0
- package/templates/_agents/scripts/check-registry.mjs +45 -0
- package/templates/_agents/scripts/check-scope.mjs +96 -0
- package/templates/_agents/scripts/check-scope.test.mjs +96 -0
- package/templates/_agents/scripts/check-scripts-syntax.mjs +61 -0
- package/templates/_agents/scripts/check-scripts-syntax.test.mjs +72 -0
- package/templates/_agents/scripts/check-spec-verif.mjs +47 -0
- 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 +136 -31
- package/templates/_agents/scripts/confirm-doc.test.mjs +180 -4
- package/templates/_agents/scripts/doctor.test.mjs +44 -1
- package/templates/_agents/scripts/edit-face-check.mjs +5 -3
- package/templates/_agents/scripts/edit-face-check.test.mjs +5 -4
- package/templates/_agents/scripts/fill-intent.mjs +7 -5
- package/templates/_agents/scripts/fill-intent.test.mjs +19 -1
- package/templates/_agents/scripts/fill-plan.mjs +10 -6
- package/templates/_agents/scripts/fill-plan.test.mjs +19 -3
- package/templates/_agents/scripts/fill-spec.mjs +6 -3
- package/templates/_agents/scripts/fill-spec.test.mjs +12 -4
- package/templates/_agents/scripts/fixture-scripts.mjs +153 -0
- package/templates/_agents/scripts/fixture-scripts.test.mjs +112 -0
- package/templates/_agents/scripts/gate-checklist.mjs +10 -9
- package/templates/_agents/scripts/gate-checklist.test.mjs +2 -11
- package/templates/_agents/scripts/gate-seg.mjs +1 -1
- package/templates/_agents/scripts/gen-effective-policy.mjs +107 -0
- package/templates/_agents/scripts/gen-effective-policy.test.mjs +100 -0
- package/templates/_agents/scripts/gen-verify-evidence.mjs +107 -0
- package/templates/_agents/scripts/gen-verify-evidence.test.mjs +84 -0
- package/templates/_agents/scripts/gen-workflow-dashboard.mjs +16 -14
- package/templates/_agents/scripts/gen-workflow-dashboard.test.mjs +2 -1
- package/templates/_agents/scripts/gen-workflow-metrics.mjs +12 -36
- package/templates/_agents/scripts/git-core.mjs +45 -0
- 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/judgment-ledger.mjs +65 -0
- package/templates/_agents/scripts/judgment-ledger.test.mjs +129 -0
- package/templates/_agents/scripts/kb-search.mjs +19 -4
- package/templates/_agents/scripts/kb-search.test.mjs +33 -0
- package/templates/_agents/scripts/lane-surface-audit.mjs +143 -0
- package/templates/_agents/scripts/lane-surface-audit.test.mjs +93 -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-shards.mjs +307 -0
- package/templates/_agents/scripts/ledger-shards.test.mjs +339 -0
- package/templates/_agents/scripts/ledger-ts-monotonic.test.mjs +135 -0
- package/templates/_agents/scripts/level-criteria.mjs +231 -0
- package/templates/_agents/scripts/level-criteria.test.mjs +281 -0
- package/templates/_agents/scripts/papercut-debt-controller.mjs +141 -0
- package/templates/_agents/scripts/papercut-debt-controller.test.mjs +127 -0
- package/templates/_agents/scripts/papercut-debt-sensor.mjs +170 -0
- package/templates/_agents/scripts/papercut-debt-sensor.test.mjs +146 -0
- package/templates/_agents/scripts/pipeline-run.mjs +10 -24
- package/templates/_agents/scripts/pipeline-run.test.mjs +22 -42
- package/templates/_agents/scripts/policy.mjs +75 -0
- package/templates/_agents/scripts/resolve-merge-conflicts.mjs +110 -0
- package/templates/_agents/scripts/resolve-merge-conflicts.test.mjs +87 -0
- package/templates/_agents/scripts/rewrite-file.mjs +133 -0
- package/templates/_agents/scripts/rewrite-file.test.mjs +148 -0
- package/templates/_agents/scripts/rule-budget.mjs +83 -0
- package/templates/_agents/scripts/run-tests.mjs +99 -0
- package/templates/_agents/scripts/run-tests.test.mjs +105 -0
- package/templates/_agents/scripts/solidify-task.test.mjs +1 -1
- package/templates/_agents/scripts/stage-gates.mjs +38 -21
- package/templates/_agents/scripts/trust-mode.mjs +4 -4
- package/templates/_agents/scripts/trust-mode.test.mjs +1 -1
- package/templates/_agents/scripts/verify.mjs +135 -58
- package/templates/_agents/scripts/verify.test.mjs +156 -47
- package/templates/_agents/scripts/wf-journal.mjs +8 -4
- package/templates/_agents/scripts/wf-journal.test.mjs +1 -1
- package/templates/_agents/scripts/wiki-search.test.mjs +5 -6
- package/templates/_agents/scripts/workflow-board-server.test.mjs +2 -2
- package/templates/_agents/scripts/workflow-enums.test.mjs +14 -5
- package/templates/_agents/scripts/workflows-check.mjs +1 -1
- package/templates/_agents/scripts-test-exempt.txt +1 -0
- package/templates/_agents/skills/papercut-fix/SKILL.md +60 -0
- package/templates/_agents/skills/papercut-fix/references/response-template.md +81 -0
- package/templates/_agents/workflow-enums.txt +8 -1
- package/templates/_agents/workflows/pipeline-closing.md +4 -5
- package/templates/_githooks/pre-commit +109 -120
- package/templates/_githooks/pre-merge-commit +27 -23
- package/templates/_githooks/pre-push +7 -6
- package/templates/_github/workflows/kit-ci.yml +4 -3
- package/templates/workflow/README.md +23 -5
- package/templates/workflow/incidents/_TEMPLATE.md +4 -1
- package/templates/workflow/intents/_TEMPLATE.md +4 -2
- package/templates/workflow/papercuts.md +2 -0
- package/templates/workflow/plans/_TEMPLATE.md +10 -5
- package/templates/workflow/regression-checklist.md +1 -0
- package/templates/workflow/specs/_TEMPLATE.md +17 -1
- package/modules/hosts/claude/commands/wf-source-sync-check.md +0 -89
- package/modules/hosts/codex/commands/wf-source-sync-check.md +0 -89
- package/modules/hosts/cursor/commands/wf-source-sync-check.md +0 -89
- package/modules/hosts/opencode/commands/wf-source-sync-check.md +0 -89
- package/modules/hosts/trae/commands/wf-source-sync-check.md +0 -90
- package/templates/_agents/commands/source-sync-check.md +0 -103
- package/templates/_agents/hooks/check-pairing-incremental.sh +0 -43
- package/templates/_agents/hooks/check-wiki-ledger.sh +0 -18
- package/templates/_agents/scripts/check-ledger.mjs +0 -109
- package/templates/_agents/scripts/check-ledger.test.mjs +0 -199
- package/templates/_agents/scripts/rule-budget.sh +0 -74
- package/templates/_agents/scripts/source-sync-check.mjs +0 -156
- package/templates/_agents/scripts/source-sync-check.test.mjs +0 -286
- package/templates/_agents/workflows/source-sync-repair.md +0 -30
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ledger-ts-monotonic.test.mjs — 台账行级校验 ts 口径修正 + 分片覆盖 fixture 套件(2026-10-12)
|
|
3
|
+
// 命题:并集合并 / 分片归并会制造**跨 doc** 的物理序 ts 乱序,这是正常协作而非篡改;判据必须从
|
|
4
|
+
// 「全表非降序」改为「同 doc 内非降序」,同时不能因此丢掉真问题的检出(同 doc 内倒挂照 hard 拦)。
|
|
5
|
+
// 覆盖面:跨 doc 乱序放行 + 计数出账 / 同 doc 内倒挂仍拦 / 分片行纳入行级校验 / 豁免行号不漂移。
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
10
|
+
import { spawnSync } from 'node:child_process';
|
|
11
|
+
|
|
12
|
+
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
// 夹具闭包(2026-10-12 fixture-closure-rootfix):整闭包自动跟随,不再手写依赖枚举(漏拷 = 假绿)
|
|
14
|
+
const { copyScriptClosure } = await import(pathToFileURL(path.join(SCRIPT_DIR, 'fixture-scripts.mjs')).href);
|
|
15
|
+
|
|
16
|
+
let pass = 0, fail = 0;
|
|
17
|
+
const check = (name, ok, detail = '') => {
|
|
18
|
+
if (ok) { pass++; console.log(` ok ${name}`); }
|
|
19
|
+
else { fail++; console.error(`FAIL ${name}${detail ? ' — ' + detail : ''}`); }
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// 建真 git fixture(行级校验只在有提交历史时才会跑到;--staged 也需要 HEAD 基线)
|
|
23
|
+
function mkRepo() {
|
|
24
|
+
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'tsmono-'));
|
|
25
|
+
const git = (...args) => spawnSync('git', args, { cwd: d, encoding: 'utf8' });
|
|
26
|
+
git('init', '-q', '.');
|
|
27
|
+
git('config', 'user.email', 't@t.io'); git('config', 'user.name', 't');
|
|
28
|
+
git('config', 'core.autocrlf', 'false');
|
|
29
|
+
fs.mkdirSync(path.join(d, '.agents', 'scripts'), { recursive: true });
|
|
30
|
+
copyScriptClosure(path.join(d, '.agents', 'scripts'), ['check-ledger-invariant.mjs']);
|
|
31
|
+
return { d, git };
|
|
32
|
+
}
|
|
33
|
+
const docFile = (d, rel) => {
|
|
34
|
+
const p = path.join(d, ...rel.split('/'));
|
|
35
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
36
|
+
fs.writeFileSync(p, '---\n状态: approved\n---\n# doc\n', 'utf8');
|
|
37
|
+
};
|
|
38
|
+
const row = (ts, doc, extra = {}) => JSON.stringify({
|
|
39
|
+
ts, doc, stage: 'approved', fingerprint: 'a'.repeat(64), source: 'tty', ...extra,
|
|
40
|
+
});
|
|
41
|
+
const writeLedger = (d, lines) => {
|
|
42
|
+
fs.mkdirSync(path.join(d, '.agents'), { recursive: true });
|
|
43
|
+
fs.writeFileSync(path.join(d, '.agents', 'confirmations.jsonl'), lines.filter(Boolean).join('\n') + '\n', 'utf8');
|
|
44
|
+
};
|
|
45
|
+
const runGate = (d) => spawnSync(process.execPath, ['.agents/scripts/check-ledger-invariant.mjs'], { cwd: d, encoding: 'utf8' });
|
|
46
|
+
|
|
47
|
+
// ============ 场景 1:跨 doc 物理序 ts 乱序(并集合并形态)→ 放行 + 计数出账 ============
|
|
48
|
+
// a 的末行 ts 晚于 b 的首行 ts——正是「A 分支先提交、B 分支的行被并集追加到尾部」产生的形态。
|
|
49
|
+
{
|
|
50
|
+
const { d, git } = mkRepo();
|
|
51
|
+
docFile(d, 'workflow/intents/2026-10-01-a.md');
|
|
52
|
+
docFile(d, 'workflow/intents/2026-10-01-b.md');
|
|
53
|
+
writeLedger(d, [
|
|
54
|
+
row('2026-10-10T00:00:00Z', 'workflow/intents/2026-10-01-a.md'), // 较晚
|
|
55
|
+
row('2026-10-01T00:00:00Z', 'workflow/intents/2026-10-01-b.md'), // 较早,跨 doc 乱序
|
|
56
|
+
]);
|
|
57
|
+
git('add', '-A'); git('commit', '-qm', 'x');
|
|
58
|
+
const r = runGate(d);
|
|
59
|
+
check('S1 跨 doc ts 乱序 → exit 0 放行(正常协作形态)', r.status === 0, `exit=${r.status} ${r.stderr}`);
|
|
60
|
+
check('S1 乱序计数出账且标明不阻断',
|
|
61
|
+
/跨 doc 物理序 ts 乱序 1 处/.test(r.stdout) && /不阻断/.test(r.stdout), r.stdout);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ============ 场景 2:同 doc 内 ts 倒挂 → 仍 hard 拦(真问题未被放松)============
|
|
65
|
+
{
|
|
66
|
+
const { d, git } = mkRepo();
|
|
67
|
+
docFile(d, 'workflow/intents/2026-10-01-a.md');
|
|
68
|
+
writeLedger(d, [
|
|
69
|
+
row('2026-10-10T00:00:00Z', 'workflow/intents/2026-10-01-a.md'),
|
|
70
|
+
row('2026-10-01T00:00:00Z', 'workflow/intents/2026-10-01-a.md'), // 同 doc 倒挂
|
|
71
|
+
]);
|
|
72
|
+
git('add', '-A'); git('commit', '-qm', 'x');
|
|
73
|
+
const r = runGate(d);
|
|
74
|
+
check('S2 同 doc 内 ts 倒挂 → exit 1 hard 拦', r.status === 1, `exit=${r.status}`);
|
|
75
|
+
check('S2 报文点名「追加时序倒挂」并带 doc',
|
|
76
|
+
/追加时序倒挂/.test(r.stderr) && /2026-10-01-a/.test(r.stderr), r.stderr);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ============ 场景 3:分片行纳入行级校验(分片里的坏行不能逃逸)============
|
|
80
|
+
{
|
|
81
|
+
const { d, git } = mkRepo();
|
|
82
|
+
docFile(d, 'workflow/intents/2026-10-01-a.md');
|
|
83
|
+
writeLedger(d, [row('2026-10-01T00:00:00Z', 'workflow/intents/2026-10-01-a.md')]);
|
|
84
|
+
// 分片里放一行 fingerprint 非 64 位 hex 的坏行——只扫主台账会漏掉它
|
|
85
|
+
fs.mkdirSync(path.join(d, '.agents', 'confirmations.d'), { recursive: true });
|
|
86
|
+
fs.writeFileSync(path.join(d, '.agents', 'confirmations.d', 'x.jsonl'),
|
|
87
|
+
JSON.stringify({ ts: '2026-10-02T00:00:00Z', doc: 'workflow/intents/2026-10-01-a.md', stage: 'approved', fingerprint: 'bad', source: 'tty' }) + '\n', 'utf8');
|
|
88
|
+
git('add', '-A'); git('commit', '-qm', 'x');
|
|
89
|
+
const r = runGate(d);
|
|
90
|
+
check('S3 分片内坏行被行级校验抓到 → exit 1', r.status === 1, `exit=${r.status} ${r.stdout}`);
|
|
91
|
+
check('S3 报文指出 fingerprint 非 64 位 hex', /fingerprint 非 64 位 hex/.test(r.stderr), r.stderr);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ============ 场景 4:分片内 doc 不存在 → 同样被抓 ============
|
|
95
|
+
{
|
|
96
|
+
const { d, git } = mkRepo();
|
|
97
|
+
docFile(d, 'workflow/intents/2026-10-01-a.md');
|
|
98
|
+
writeLedger(d, [row('2026-10-01T00:00:00Z', 'workflow/intents/2026-10-01-a.md')]);
|
|
99
|
+
fs.mkdirSync(path.join(d, '.agents', 'confirmations.d'), { recursive: true });
|
|
100
|
+
fs.writeFileSync(path.join(d, '.agents', 'confirmations.d', 'y.jsonl'),
|
|
101
|
+
row('2026-10-02T00:00:00Z', 'workflow/intents/2026-10-01-ghost.md') + '\n', 'utf8');
|
|
102
|
+
git('add', '-A'); git('commit', '-qm', 'x');
|
|
103
|
+
const r = runGate(d);
|
|
104
|
+
check('S4 分片内 doc 在当前树不存在 → exit 1', r.status === 1, `exit=${r.status}`);
|
|
105
|
+
check('S4 报文点名 ghost.md', /ghost\.md/.test(r.stderr), r.stderr);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ============ 场景 5:豁免行号不因引入分片而漂移 ============
|
|
109
|
+
// 视图序 = 主台账在前、分片在后 → 主台账第 2 行的豁免登记在引入分片后仍指向同一行。
|
|
110
|
+
{
|
|
111
|
+
const { d, git } = mkRepo();
|
|
112
|
+
docFile(d, 'workflow/intents/2026-10-01-a.md');
|
|
113
|
+
docFile(d, 'workflow/intents/2026-10-01-b.md');
|
|
114
|
+
// 第 2 行是「doc 不存在」的坏行(须靠豁免放行)
|
|
115
|
+
writeLedger(d, [
|
|
116
|
+
row('2026-10-01T00:00:00Z', 'workflow/intents/2026-10-01-a.md'),
|
|
117
|
+
row('2026-10-02T00:00:00Z', 'workflow/intents/2026-10-01-ghost.md'),
|
|
118
|
+
]);
|
|
119
|
+
fs.writeFileSync(path.join(d, '.agents', 'ledger-line-exempt.json'),
|
|
120
|
+
JSON.stringify([{ line: 2, doc: 'workflow/intents/2026-10-01-ghost.md', reason: '文档已归档' }]), 'utf8');
|
|
121
|
+
git('add', '-A'); git('commit', '-qm', 'x');
|
|
122
|
+
const before = runGate(d);
|
|
123
|
+
check('S5 前:无分片时豁免生效 → exit 0', before.status === 0, `exit=${before.status} ${before.stderr}`);
|
|
124
|
+
|
|
125
|
+
// 追加一个分片(视图序追加在主台账之后),豁免行号应仍指向 ghost 那行
|
|
126
|
+
fs.mkdirSync(path.join(d, '.agents', 'confirmations.d'), { recursive: true });
|
|
127
|
+
fs.writeFileSync(path.join(d, '.agents', 'confirmations.d', 'z.jsonl'),
|
|
128
|
+
row('2026-10-03T00:00:00Z', 'workflow/intents/2026-10-01-b.md') + '\n', 'utf8');
|
|
129
|
+
git('add', '-A'); git('commit', '-qm', 'y');
|
|
130
|
+
const after = runGate(d);
|
|
131
|
+
check('S5 后:引入分片,豁免行号未漂移 → 仍 exit 0', after.status === 0, `exit=${after.status} ${after.stderr}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
console.log(`\n[ledger-ts-monotonic.test] PASS ${pass} / FAIL ${fail}`);
|
|
135
|
+
process.exit(fail ? 1 : 0);
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// level-criteria.mjs — 风险级别判定的**机器单源**(2026-10-13 flow-kit-route)
|
|
3
|
+
//
|
|
4
|
+
// 为什么有它:风险级别(L1-L3;2026-10-13 L0 退役,自源仓收敛系列 1 回灌)此前只以**散文**形态存在于 `new-task.md`「级别判断」节——
|
|
5
|
+
// L2 四类触达面、L3 结构面、负例、自查句全都写给人读。后果有二:
|
|
6
|
+
// ① 定级是纯自由心证,AI 每次判级都可能不同,门禁无从校验「判低」是否守约;
|
|
7
|
+
// ② 想让 CLI 接管路由(`flow-kit route`),就没有可执行的判据可调,只能复读 md。
|
|
8
|
+
// 本文件把「**能被机器判定的部分**」从散文里抽出来做成单源,md 改为引用本文件而非承载判据。
|
|
9
|
+
//
|
|
10
|
+
// 分工(诚实声明——不是所有判据都能机器判定,别指望本文件替代判断力):
|
|
11
|
+
// 机器可判:档位闭集与就高不就低、**路径级最低档提示**(level-hints.txt)、混档取高、自查句与负例的**输出**。
|
|
12
|
+
// 机器不可判:「一处定义、多处消费」是**语义**性质——同一条路径改了可能只是修文案、也可能是改编码权威。
|
|
13
|
+
// 因此本文件对语义面**只给判据清单与自查句,不产出断言**。缺 hints 配置时按 L1 起步并提示补配置,
|
|
14
|
+
// 绝不假装判得出。
|
|
15
|
+
//
|
|
16
|
+
// 路径提示清单(`.agents/level-hints.txt`,装户 owned):`L3 <ERE>` / `L2 <ERE>` 每行一条,
|
|
17
|
+
// 命中即该改动**至少**是此档。缺文件或清单为空 = 纯语义定级(本文件全程静默,不阻断)。
|
|
18
|
+
// 语义参照 `lane-surfaces.txt`(那是「提交面」的触达判据,本文件是「意图面」的档位提示,两者不同档)。
|
|
19
|
+
import fs from 'node:fs';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
|
|
22
|
+
export const LEVELS = ['L1', 'L2', 'L3'];
|
|
23
|
+
export const HINTS_REL = '.agents/level-hints.txt';
|
|
24
|
+
|
|
25
|
+
// 档位语义(new-task.md 引用本段,不复述——单源纪律)
|
|
26
|
+
export const LEVEL_MEANING = {
|
|
27
|
+
L1: '实现级改动(未命中 L2/L3)——一份 plan 即入口(协作道,不另立 intent)',
|
|
28
|
+
L2: '规则/契约(一处改、多处依赖)——入口文档 + spec + plan 三件套',
|
|
29
|
+
L3: '数据与运行时结构(schema / 迁移 SQL / DI 链 / 认证与中间件管线)——同 L2 + spec 新会话独立复核',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// L2 四类触达面(闭集,机器不判定语义,只作为判据清单输出给人与 AI)
|
|
33
|
+
export const L2_SURFACES = [
|
|
34
|
+
'编码权威:一处生成、多处消费的编码/命名/取号规则(具体清单按项目 AGENTS.md)',
|
|
35
|
+
'共享契约:跨模块共享的基类/DTO 形状、跨模块公共接口签名',
|
|
36
|
+
'既有接口语义:既有参数或端点的行为变化(失效变生效、默认值或钳制口径改变)',
|
|
37
|
+
'全局横切口径:错误响应出口、分页与校验口径、聚合投影键',
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
// L3 结构面
|
|
41
|
+
export const L3_SURFACES = [
|
|
42
|
+
'schema / 数据模型定义',
|
|
43
|
+
'迁移 SQL(schema 变更、数据修复)',
|
|
44
|
+
'DI 链:注册、构造器依赖、组合根',
|
|
45
|
+
'认证与中间件管线',
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
// 落 L1 的负例(判低红线:命中即不得定 L2/L3)
|
|
49
|
+
export const L1_NEGATIVES = [
|
|
50
|
+
'纯页面表现 / 文案 / 布局 / 交互实现',
|
|
51
|
+
'纯新增端点或参数,且既有行为不变',
|
|
52
|
+
'单模块内部重构,且无对外契约变化',
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
// 自查句(人/AI 定级时的判定式,机器只输出不裁决)
|
|
56
|
+
export const SELF_CHECK = '能否列出 ≥2 个受影响的消费方(模块 / 端点 / 页面)?能 → 至少 L2。';
|
|
57
|
+
|
|
58
|
+
export function rankOf(level) {
|
|
59
|
+
const i = LEVELS.indexOf(String(level || '').toUpperCase());
|
|
60
|
+
return i < 0 ? null : i;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function maxLevel(a, b) {
|
|
64
|
+
const ra = rankOf(a), rb = rankOf(b);
|
|
65
|
+
if (ra === null) return rb === null ? '' : String(b).toUpperCase();
|
|
66
|
+
if (rb === null) return String(a).toUpperCase();
|
|
67
|
+
return LEVELS[Math.max(ra, rb)];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// parseLevelHints:读 hints 清单。行格式 `L3 <ERE>`,ERE 为 grep -E 兼容正则,匹配仓库相对路径(/ 分隔)。
|
|
71
|
+
// 非法档位 / 语法错误的行 → fail-loud 抛错(防静默空洞:写错配置比没配置更危险——没配置会提示,写错会误判)。
|
|
72
|
+
// 三条收紧(2026-10-13 executor-review,全部来自实测复现,不是假想):
|
|
73
|
+
// ① **只收 L2 / L3**。本清单语义是「哪些路径一碰就至少是 L2/L3」,L1 行不构成任何门槛——
|
|
74
|
+
// 而此前接受 L0 会被提交面判低门当真:`L0 ^docs/` 会让纯文档提交被硬拦,修法还让人
|
|
75
|
+
// 「升级至 L0 及以上」(L0 即最低档,荒谬;该档位已随 2026-10-13 L0 退役整体消失)。
|
|
76
|
+
// 写这种行的人本意多半是「这里是低风险区」,那正是不该登记的面——故按写错配置处理,fail-loud 点名。
|
|
77
|
+
// ② **行内 `#` 注释 fail-loud**。ERE 取整行剩余部分(`(\S.*)$`),所以 `L3 ^a\.sql$ # 注释`
|
|
78
|
+
// 会把注释编进正则:若路径含 `$` 锚点,该条**永不命中**且无任何报错——路径下限与提交面兜底
|
|
79
|
+
// 同时静默失效,而 `--check` 还报「N 条有效」。这正是随包模板原先示例的写法(评审实测)。
|
|
80
|
+
// 注释请单独成行;确需匹配路径里的 `#` 写 `\#`。
|
|
81
|
+
// ③ 档位大写归一:只接受 `L2` / `L3`(原正则 `L[0-3]` 已含大小写外的写法,见下)。
|
|
82
|
+
export function parseLevelHints(text) {
|
|
83
|
+
const out = [];
|
|
84
|
+
const errs = [];
|
|
85
|
+
String(text || '').split(/\r?\n/).forEach((raw, i) => {
|
|
86
|
+
const line = raw.trim();
|
|
87
|
+
if (!line || line.startsWith('#')) return;
|
|
88
|
+
const m = line.match(/^(L[0-3])\s+(\S.*)$/i);
|
|
89
|
+
if (!m) { errs.push(`第 ${i + 1} 行:格式须为 \`L2 <ERE>\` 或 \`L3 <ERE>\`(实际「${line.slice(0, 40)}」)`); return; }
|
|
90
|
+
const level = m[1].toUpperCase();
|
|
91
|
+
const re = m[2];
|
|
92
|
+
if (level !== 'L2' && level !== 'L3') {
|
|
93
|
+
errs.push(`第 ${i + 1} 行:档位只能是 L2 或 L3(实际 ${level})——本清单只登记「一碰就至少是中/高风险面」的路径下限;`
|
|
94
|
+
+ `L1 不构成门槛(L0 已退役),登记了只会让提交面判低门对无关改动出手。若本意是「这里是低风险区」,那就不该登记`);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// 行内注释检测:空白后的未转义 #。ERE 里的 `#` 若被转义(\#)或前面不是空白(形如 `^src/#old/`)则放行。
|
|
98
|
+
const inline = /(^|\s)#/.exec(re);
|
|
99
|
+
if (inline) {
|
|
100
|
+
errs.push(`第 ${i + 1} 行:ERE 与行内注释不得同一行(实际「${line.slice(0, 40)}」)——注释会被编进正则,`
|
|
101
|
+
+ `若含 $ 锚点则该条永不命中且无任何报错;请把注释放到上一行单独成行(确需匹配路径里的 # 请写 \\#)`);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
out.push({ level, re, rx: new RegExp(re), source: line });
|
|
106
|
+
} catch (e) {
|
|
107
|
+
errs.push(`第 ${i + 1} 行:正则语法错误(${e.message})`);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
if (errs.length) {
|
|
111
|
+
const e = new Error(`${HINTS_REL} 配置有误:\n - ${errs.join('\n - ')}`);
|
|
112
|
+
e.code = 'LEVEL_HINTS_INVALID';
|
|
113
|
+
throw e;
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function loadLevelHints(root = process.cwd()) {
|
|
119
|
+
const p = path.join(root, ...HINTS_REL.split('/'));
|
|
120
|
+
if (!fs.existsSync(p)) return { hints: [], missing: true };
|
|
121
|
+
return { hints: parseLevelHints(fs.readFileSync(p, 'utf8')), missing: false };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// hintLevelFor:路径命中的**最低档**提示(多条命中取最高)。无命中返回 ''。
|
|
125
|
+
export function hintLevelFor(hints, files) {
|
|
126
|
+
let hit = '';
|
|
127
|
+
const hitRe = [];
|
|
128
|
+
for (const f of files || []) {
|
|
129
|
+
const norm = String(f).replace(/\\/g, '/').replace(/^\.\//, '');
|
|
130
|
+
for (const h of hints) {
|
|
131
|
+
if (h.rx.test(norm)) { hit = maxLevel(hit, h.level); hitRe.push(`${h.level}←${norm}`); }
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return { level: hit, matched: [...new Set(hitRe)] };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// assessLevel:定级判定面。输入 = 改动文件 + 调用方给的建议档;输出 = 机器可判的部分。
|
|
138
|
+
// 语义面(是否「一处定义多处消费」)**不由本函数裁决**——它只保证:hints 提示档是下限、混档取高。
|
|
139
|
+
export function assessLevel(root, { files = [], proposed = '' } = {}) {
|
|
140
|
+
const { hints, missing } = loadLevelHints(root);
|
|
141
|
+
const { level: hintLevel, matched } = hintLevelFor(hints, files);
|
|
142
|
+
const finalLevel = maxLevel(hintLevel, proposed);
|
|
143
|
+
return {
|
|
144
|
+
level: finalLevel,
|
|
145
|
+
hintLevel,
|
|
146
|
+
matched,
|
|
147
|
+
proposed: proposed ? String(proposed).toUpperCase() : '',
|
|
148
|
+
hintsConfigured: !missing && hints.length > 0,
|
|
149
|
+
// 调用方给的建议档若低于 hint 下限 → 提示;不硬拦(判级纪律仍由 lane-surface 触达面门在提交面兜)
|
|
150
|
+
underHint: !!(hintLevel && rankOf(proposed) !== null && rankOf(proposed) < rankOf(hintLevel)),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// routeAdvice:给人/AI 的**判据面**文本(route 命令直接输出这份)。
|
|
155
|
+
// 2026-10-13 executor-review:原先这里还兼报「hints 未配置」「建议档低于下限」两条**状态**信息,
|
|
156
|
+
// 而 route 的降级块(executor.mjs degradations)讲的是同一件事 → 文本模式把同一事实打了两遍。
|
|
157
|
+
// 口径:**判据归本函数、状态归降级块**,单一出口。故 ctx(原 hintsConfigured / underHint /
|
|
158
|
+
// hintLevel / matched)保留在签名里仅为兼容既有调用方,本函数不再读取——故参数名带下划线前缀。
|
|
159
|
+
export function routeAdvice(level, _ctx = {}) {
|
|
160
|
+
const L = String(level || 'L1').toUpperCase();
|
|
161
|
+
const lines = [];
|
|
162
|
+
lines.push(`建议级别:${L} —— ${LEVEL_MEANING[L] || '(未知档)'}`);
|
|
163
|
+
if (L === 'L2' || L === 'L3') {
|
|
164
|
+
lines.push('');
|
|
165
|
+
lines.push(`【L2 触达面 · 闭集,逐条判】`);
|
|
166
|
+
for (const s of L2_SURFACES) lines.push(` · ${s}`);
|
|
167
|
+
}
|
|
168
|
+
if (L === 'L3') {
|
|
169
|
+
lines.push('');
|
|
170
|
+
lines.push(`【L3 结构面】`);
|
|
171
|
+
for (const s of L3_SURFACES) lines.push(` · ${s}`);
|
|
172
|
+
}
|
|
173
|
+
if (L === 'L1') {
|
|
174
|
+
lines.push('');
|
|
175
|
+
lines.push(`【负例 · 命中即不得升 L2/L3】`);
|
|
176
|
+
for (const s of L1_NEGATIVES) lines.push(` · ${s}`);
|
|
177
|
+
}
|
|
178
|
+
lines.push('');
|
|
179
|
+
lines.push(`【自查句】${SELF_CHECK}`);
|
|
180
|
+
lines.push('提示:级别是**意图面**的语义判定(「一处定义、多处消费」),机器只兜「就高不就低」与路径下限。');
|
|
181
|
+
return lines.join('\n');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ---- 体检入口(2026-10-13 hint-doctor)------------------------------------
|
|
185
|
+
// 为什么需要:非法 hints 此前只在**跑 flow-kit route 时**才抛 LEVEL_HINTS_INVALID,而 route 是
|
|
186
|
+
// 可选调用 → 写错的清单能静默存活很久,直到某天有人碰巧路由一次才炸。写错配置比没配置危险
|
|
187
|
+
// (没配置会提示补,写错会误判),故给 doctor 一条**主动体检**通道。
|
|
188
|
+
// 口径纪律:doctor 不自己复刻解析器(复刻=判据双源漂移),改为 spawn 本文件执行本节——
|
|
189
|
+
// 解析逻辑永远只有 parseLevelHints 一份。
|
|
190
|
+
// 用法:node level-criteria.mjs --check <清单路径>
|
|
191
|
+
// 退出码:0 通过(含文件缺失/空清单=未启用);1 配置有误(stderr 出逐行原因)。无参数 → 退出 2。
|
|
192
|
+
export function checkHintsFile(file) {
|
|
193
|
+
if (!fs.existsSync(file)) return { configured: false, entries: 0, note: '文件不存在(默认空=纯语义定级)' };
|
|
194
|
+
const text = fs.readFileSync(file, 'utf8');
|
|
195
|
+
let hints;
|
|
196
|
+
try {
|
|
197
|
+
hints = parseLevelHints(text);
|
|
198
|
+
} catch (e) {
|
|
199
|
+
return { configured: true, ok: false, entries: 0, error: e.message, code: e.code || 'LEVEL_HINTS_INVALID' };
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
configured: true,
|
|
203
|
+
ok: true,
|
|
204
|
+
entries: hints.length,
|
|
205
|
+
levels: [...new Set(hints.map((h) => h.level))].sort(),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// CLI 自运行守卫:仅当本文件被直接执行时进入体检模式(被 import 时不跑——库用法不受影响)
|
|
210
|
+
if (process.argv[1] && path.resolve(process.argv[1]).endsWith(path.join('scripts', 'level-criteria.mjs'))) {
|
|
211
|
+
const argv = process.argv.slice(2);
|
|
212
|
+
const at = argv.indexOf('--check');
|
|
213
|
+
if (at >= 0) {
|
|
214
|
+
const file = argv[at + 1];
|
|
215
|
+
if (!file || argv[at + 1].startsWith('--')) {
|
|
216
|
+
console.error('用法:node level-criteria.mjs --check <level-hints.txt 路径>');
|
|
217
|
+
process.exit(2);
|
|
218
|
+
}
|
|
219
|
+
const r = checkHintsFile(path.resolve(file));
|
|
220
|
+
if (r.ok === false) {
|
|
221
|
+
console.error(`[level-hints 配置有误] ${file}\n${String(r.error).split('\n').map((l) => ` ${l}`).join('\n')}`);
|
|
222
|
+
console.error(' 修法:按 `<档位> <ERE>` 格式修正(档位 ∈ L2|L3,ERE 为 grep -E 兼容正则)——'
|
|
223
|
+
+ '写错配置比没配置危险:没配置会提示补配置,写错会误判档位');
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
console.log(r.configured
|
|
227
|
+
? `level-hints:${r.entries} 条有效(${(r.levels || []).join('/') || '—'})`
|
|
228
|
+
: `level-hints:未配置(${r.note})`);
|
|
229
|
+
process.exit(0);
|
|
230
|
+
}
|
|
231
|
+
}
|