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
|
@@ -1,109 +0,0 @@
|
|
|
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
|
-
// 入库态口径(2026-10-08 papercuts-cleanup-batch 修 5,独立复核 P1 修正取值链):快检原比「工作树」,
|
|
8
|
-
// 而分批提交时提交树(= HEAD + 暂存合成,删除同样被应用)里的 kit.json sha 可能指向「工作区已改、
|
|
9
|
-
// 未提交」内容 → clone/CI 跑 doctor 必红(e8445c5 实证 6 项不符)。无参运行自适应:**暂存区非空即
|
|
10
|
-
// 入库态**(不判「提交集含 kit.json」——diff --cached 只列与 HEAD 有差异的文件,kit.json 无变化暂存
|
|
11
|
-
// 时不出现,但其合成提交树同样可能预 landing:只 add 件不 add 台账是姊妹漏洞)。入库态取值:
|
|
12
|
-
// 台账清单 = 暂存版 kit.json(`git show :.agents/kit.json`);暂存无而 HEAD 有 = 退出性提交(kit.json
|
|
13
|
-
// 被暂存删除)放行;managed 内容 = `git show :rel` 暂存版,**暂存无即 gone**(提交树=index——
|
|
14
|
-
// 未暂存改动的文件 index 仍在,`:rel` 必命中;`:rel` 缺失唯一可达路径是暂存删除,HEAD 兜底会把它
|
|
15
|
-
// 误读为「未触碰」放行 = 提交门绕过,复核 P1 实证)。
|
|
16
|
-
// 暂存区空(amend 等罕见态)→ 维持工作树口径零变化。
|
|
17
|
-
// 边界:仅包源环境由 .githooks/pre-commit 触发(装户 managed 手改走 doctor §4 WARN 口径,不硬拦);
|
|
18
|
-
// managed 删除即拦——sync 对 removed 只报不删,删件须手动同步装副本并重跑 sync 刷台账。
|
|
19
|
-
// 用法:node .agents/scripts/check-ledger.mjs(cwd = 仓库根;非零出口即阻断提交;无参自适应两口径)
|
|
20
|
-
// 单元测试:import { ledgerDrift } from './check-ledger.mjs'({staged:true} 显式入库态)
|
|
21
|
-
import fs from 'node:fs';
|
|
22
|
-
import path from 'node:path';
|
|
23
|
-
import { createHash } from 'node:crypto';
|
|
24
|
-
import { spawnSync } from 'node:child_process';
|
|
25
|
-
|
|
26
|
-
const gitText = (target, args) => {
|
|
27
|
-
const r = spawnSync('git', args, { cwd: target, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 });
|
|
28
|
-
return r.status === 0 ? r.stdout : null;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export function ledgerDrift(target = process.cwd(), { staged = false } = {}) {
|
|
32
|
-
const kitPath = path.join(target, '.agents', 'kit.json');
|
|
33
|
-
if (!fs.existsSync(kitPath)) return { skipped: true, note: '无 .agents/kit.json(非 flow-kit 安装)' };
|
|
34
|
-
// staged 模式:台账清单取暂存版 kit.json(提交树=index 合成语义);暂存无而 HEAD 有 = 退出性提交
|
|
35
|
-
// (kit.json 被暂存删除)放行;两者皆无(新仓无 kit.json)同样放行
|
|
36
|
-
let kit = null;
|
|
37
|
-
if (staged) {
|
|
38
|
-
const stagedKit = gitText(target, ['show', ':.agents/kit.json']);
|
|
39
|
-
const headKit = stagedKit === null ? gitText(target, ['show', 'HEAD:.agents/kit.json']) : null;
|
|
40
|
-
if (stagedKit === null) {
|
|
41
|
-
return { skipped: true, note: headKit !== null ? '提交树无 .agents/kit.json(退出 flow-kit 的提交),跳过' : '提交树无 .agents/kit.json(新仓),跳过', mode: 'staged' };
|
|
42
|
-
}
|
|
43
|
-
try {
|
|
44
|
-
kit = JSON.parse(stagedKit);
|
|
45
|
-
} catch (e) {
|
|
46
|
-
return { skipped: false, fatal: `kit.json(暂存版)解析失败:${e.message}`, modified: [], gone: [], total: 0, version: '?', mode: 'staged' };
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
if (!kit) {
|
|
50
|
-
try {
|
|
51
|
-
kit = JSON.parse(fs.readFileSync(kitPath, 'utf8'));
|
|
52
|
-
} catch (e) {
|
|
53
|
-
return { skipped: false, fatal: `kit.json 解析失败:${e.message}`, modified: [], gone: [], total: 0, version: '?', mode: staged ? 'staged' : 'worktree' };
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
if (!Array.isArray(kit.managed)) {
|
|
57
|
-
// fail-loud(独立复核 P2-3,2026-09-28):JSON 合法但 managed 缺失/非数组时静默「全对齐」= 假放,
|
|
58
|
-
// 同 doctor §4 的 Array.isArray 显式判口径,台账结构坏了必须响
|
|
59
|
-
return { skipped: false, fatal: 'kit.json managed 缺失或非数组', modified: [], gone: [], total: 0, version: kit.version, mode: staged ? 'staged' : 'worktree' };
|
|
60
|
-
}
|
|
61
|
-
const modified = [];
|
|
62
|
-
const gone = [];
|
|
63
|
-
const managed = kit.managed;
|
|
64
|
-
for (const f of managed) {
|
|
65
|
-
let content = null;
|
|
66
|
-
if (staged) {
|
|
67
|
-
// 入库态:提交树 = index(暂存版);`:rel` 缺失唯一可达路径是暂存删除 → gone 拦(复核 P1:
|
|
68
|
-
// HEAD 兜底会把暂存删除误读为「未触碰」放行——提交树已无该件而台账仍跟踪,clone/CI 必红)
|
|
69
|
-
content = gitText(target, ['show', `:${f.rel}`]);
|
|
70
|
-
if (content === null) { gone.push(f.rel); continue; }
|
|
71
|
-
} else {
|
|
72
|
-
const p = path.join(target, f.rel);
|
|
73
|
-
if (!fs.existsSync(p)) { gone.push(f.rel); continue; }
|
|
74
|
-
content = fs.readFileSync(p, 'utf8');
|
|
75
|
-
}
|
|
76
|
-
const h = createHash('sha256').update(content.replace(/\r\n/g, '\n'), 'utf8').digest('hex'); // LF 归一,同 doctor §4
|
|
77
|
-
if (h !== f.sha256) modified.push(f.rel);
|
|
78
|
-
}
|
|
79
|
-
return { skipped: false, fatal: '', modified, gone, total: managed.length, version: kit.version, mode: staged ? 'staged' : 'worktree' };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const isMain = process.argv[1] && process.argv[1].endsWith('check-ledger.mjs');
|
|
83
|
-
if (isMain) {
|
|
84
|
-
// 无参自适应:暂存区非空(有提交发生)→ 入库态口径;暂存区空(amend 等罕见态/非 git 仓)→ 工作树口径
|
|
85
|
-
const cached = gitText(process.cwd(), ['diff', '--cached', '--name-only']) || '';
|
|
86
|
-
const staged = cached.split(/\r?\n/).some((l) => l.trim() !== '');
|
|
87
|
-
const r = ledgerDrift(process.cwd(), { staged });
|
|
88
|
-
console.log('▶ flow-kit check-ledger');
|
|
89
|
-
if (r.skipped) {
|
|
90
|
-
console.log(' ' + r.note + ',跳过 ✅');
|
|
91
|
-
process.exit(0);
|
|
92
|
-
}
|
|
93
|
-
console.log(' kit.json v' + r.version + ':managed ' + r.total + ' 份,台账 ↔ ' + (r.mode === 'staged' ? '提交树入库态(暂存版=index,暂存删除即拦,LF 归一)' : '盘面(工作树,LF 归一)'));
|
|
94
|
-
if (r.fatal) console.error('❌ ' + r.fatal);
|
|
95
|
-
if (r.modified.length) {
|
|
96
|
-
console.error(' 台账漂移(盘面 ≠ 台账 sha)' + r.modified.length + ':');
|
|
97
|
-
for (const rel of r.modified) console.error(' - ' + rel);
|
|
98
|
-
}
|
|
99
|
-
if (r.gone.length) {
|
|
100
|
-
console.error(' managed 缺失 ' + r.gone.length + ':');
|
|
101
|
-
for (const rel of r.gone) console.error(' - ' + rel);
|
|
102
|
-
}
|
|
103
|
-
if (r.fatal || r.modified.length || r.gone.length) {
|
|
104
|
-
console.error(' 提交被拦:kit.json 与' + (r.mode === 'staged' ? '提交树(暂存面)' : '盘面') + '不一致(预 landing / 手改未随动)——跑 node bin/flow-kit.mjs sync 成对后原路重试');
|
|
105
|
-
process.exit(1);
|
|
106
|
-
}
|
|
107
|
-
console.log(' 全对齐 ✅');
|
|
108
|
-
process.exit(0);
|
|
109
|
-
}
|
|
@@ -1,199 +0,0 @@
|
|
|
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 if (!fs.existsSync(path.join(srcRoot, '.agents', 'kit.json'))) {
|
|
121
|
-
console.log('SKIP S9(纯包源仓无 .agents/ 装副本——装户端内容清理后形态,不计失败)');
|
|
122
|
-
} else {
|
|
123
|
-
const r = ledgerDrift(srcRoot);
|
|
124
|
-
check('S9 真实仓 baseline → 台账↔盘面全对齐', !r.skipped && r.modified.length === 0 && r.gone.length === 0, JSON.stringify({ modified: r.modified, gone: r.gone }));
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// ---- S10 入库态口径(2026-10-08 papercuts-cleanup-batch 修 5):git 仓 fixture,分批提交场景 ----
|
|
129
|
-
// 核心场景(papercuts 2026-09-30 行):sync 刷新 kit.json 后只 add 台账不 add 件 → 提交树内 kit.json
|
|
130
|
-
// 预 landing → 修前工作树口径「全对齐」放行(假绿),clone/CI doctor 必红;修后入库态模式拦。
|
|
131
|
-
{
|
|
132
|
-
const GIT = process.platform === 'win32' ? 'git.exe' : 'git';
|
|
133
|
-
const g = (cwd, ...args) => spawnSync(GIT, args, { cwd, encoding: 'utf8' });
|
|
134
|
-
const W2 = (root, rel, content) => {
|
|
135
|
-
fs.mkdirSync(path.join(root, path.dirname(rel)), { recursive: true });
|
|
136
|
-
fs.writeFileSync(path.join(root, rel), content);
|
|
137
|
-
};
|
|
138
|
-
const repo = fs.mkdtempSync(path.join(os.tmpdir(), 'fk-cl-git-'));
|
|
139
|
-
g(repo, 'init', '-q');
|
|
140
|
-
g(repo, 'config', 'user.email', 't@t');
|
|
141
|
-
g(repo, 'config', 'user.name', 't');
|
|
142
|
-
g(repo, 'config', 'core.autocrlf', 'false');
|
|
143
|
-
const kitOf = (aSha) => JSON.stringify({
|
|
144
|
-
kit: 't', version: '0.0.0-test',
|
|
145
|
-
managed: [{ rel: '.agents/scripts/a.mjs', sha256: aSha }],
|
|
146
|
-
}) + '\n';
|
|
147
|
-
W2(repo, '.agents/scripts/a.mjs', 'A v1\n');
|
|
148
|
-
W2(repo, '.agents/kit.json', kitOf(lfSha('A v1\n')));
|
|
149
|
-
g(repo, 'add', '-A');
|
|
150
|
-
g(repo, 'commit', '-q', '-m', 'v1');
|
|
151
|
-
// sync 模拟:件与台账同刷 v2(工作区自洽,均未提交)
|
|
152
|
-
W2(repo, '.agents/scripts/a.mjs', 'A v2\n');
|
|
153
|
-
W2(repo, '.agents/kit.json', kitOf(lfSha('A v2\n')));
|
|
154
|
-
|
|
155
|
-
// S10① 只 add kit.json(分批提交)→ 暂存台账(v2) vs 入库件(HEAD v1) → 拦
|
|
156
|
-
g(repo, 'add', '.agents/kit.json');
|
|
157
|
-
const r1 = ledgerDrift(repo, { staged: true });
|
|
158
|
-
check('S10① 入库态:台账(v2 暂存) vs 件(HEAD v1) → 拦(修前工作树口径假绿放行)',
|
|
159
|
-
r1.mode === 'staged' && r1.modified.includes('.agents/scripts/a.mjs'), JSON.stringify(r1));
|
|
160
|
-
// S10② 件与台账同 add → 入库态一致 → 过
|
|
161
|
-
g(repo, 'add', '.agents/scripts/a.mjs');
|
|
162
|
-
const r2 = ledgerDrift(repo, { staged: true });
|
|
163
|
-
check('S10② 入库态:件与台账同 add → 一致过', r2.mode === 'staged' && !r2.modified.length && !r2.gone.length, JSON.stringify(r2));
|
|
164
|
-
g(repo, 'commit', '-q', '-m', 'v2 both');
|
|
165
|
-
// S10③ 工作树删件未暂存 → index 仍有该件(`:rel` 命中 HEAD 内容)→ 不误报 gone(复核 P1 修后语义:
|
|
166
|
-
// HEAD 兜底已移除,判定路径 = `:rel` 命中而非兜底)
|
|
167
|
-
fs.rmSync(path.join(repo, '.agents/scripts/a.mjs'));
|
|
168
|
-
const r3 = ledgerDrift(repo, { staged: true });
|
|
169
|
-
check('S10③ 入库态:工作树删件未暂存 → index 仍在,不误报', !r3.gone.length && !r3.modified.length, JSON.stringify(r3));
|
|
170
|
-
// S10④ 暂存删除(git rm)→ 提交树已无该件而台账仍跟踪 → gone 拦(复核 P1:HEAD 兜底曾把它误读
|
|
171
|
-
// 为「未触碰」放行 = 提交门绕过;修后与模块头「managed 删除即拦」政策一致)
|
|
172
|
-
g(repo, 'rm', '-q', '--cached', '.agents/scripts/a.mjs');
|
|
173
|
-
const r4 = ledgerDrift(repo, { staged: true });
|
|
174
|
-
check('S10④ 入库态:暂存删除 → gone 拦(提交树=index,删除被应用)', r4.gone.includes('.agents/scripts/a.mjs'), JSON.stringify(r4));
|
|
175
|
-
// S10④b 退出性提交:kit.json 被暂存删除 → 放行(skip,语义与 managed 删除拦截区分)
|
|
176
|
-
g(repo, 'rm', '-q', '--cached', '.agents/kit.json');
|
|
177
|
-
const r4b = ledgerDrift(repo, { staged: true });
|
|
178
|
-
check('S10④b 入库态:kit.json 暂存删除(退出性提交)→ skip 放行', r4b.skipped === true, JSON.stringify(r4b));
|
|
179
|
-
g(repo, 'reset', '-q', '--hard', 'HEAD'); // 复原(防脏)
|
|
180
|
-
|
|
181
|
-
// S10⑤ isMain 无参自适应(子进程):暂存区空 → 「工作树」;暂存区非空 → 自动「入库态」
|
|
182
|
-
g(repo, 'reset', '-q', '--hard', 'HEAD'); // 清掉 ④ 的暂存删除,回到干净 v2 both
|
|
183
|
-
const s1 = runCli(repo); // 暂存区空 → 工作树口径
|
|
184
|
-
check('S10⑤ 暂存区空 → 工作树口径', s1.status === 0 && (s1.stdout || '').includes('工作树'), `out=${s1.stdout}`);
|
|
185
|
-
W2(repo, 'unrelated.txt', 'x\n');
|
|
186
|
-
g(repo, 'add', 'unrelated.txt'); // 暂存非空(无关文件即可)→ 自动入库态;kit.json 未暂存走 HEAD 兜底
|
|
187
|
-
const s2 = runCli(repo);
|
|
188
|
-
check('S10⑤ 暂存区非空 → 自动切入库态且一致 exit 0',
|
|
189
|
-
s2.status === 0 && (s2.stdout || '').includes('入库态'), `status=${s2.status} out=${s2.stdout}`);
|
|
190
|
-
// S10⑥ 姊妹漏洞(完备口径价值场景):只 add 件不 add 台账 → 提交树 = HEAD 台账(v1? v2) + 暂存件 → 错位拦
|
|
191
|
-
W2(repo, '.agents/scripts/a.mjs', 'A v3\n');
|
|
192
|
-
g(repo, 'add', '.agents/scripts/a.mjs');
|
|
193
|
-
const s3 = runCli(repo);
|
|
194
|
-
check('S10⑥ 只 add 件不 add 台账 → 入库态错位拦 exit 1', s3.status === 1 && (s3.stderr || '').includes('.agents/scripts/a.mjs'), `status=${s3.status} err=${s3.stderr}`);
|
|
195
|
-
fs.rmSync(repo, { recursive: true, force: true });
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
console.log(`\n合计: PASS ${pass} / FAIL ${failCount}`);
|
|
199
|
-
process.exit(failCount ? 1 : 0);
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
# 常驻面体积预算检查(单源:本脚本 + .agents/rule-budgets.txt;两个触发点共用)
|
|
3
|
-
# 用法:sh .agents/scripts/rule-budget.sh [--all|--staged]
|
|
4
|
-
# --all 查「提交后树」全量(check-loop.sh 检查 13 调用,warning 侧)
|
|
5
|
-
# --staged 仅查本次暂存触及的条目 + 触及目录的合计(.githooks/pre-commit 调用,超限即硬拦)
|
|
6
|
-
# 退出码:0 通过;1 有超限(违例逐条打到 stdout,调用方决定警告还是阻断)
|
|
7
|
-
# 「提交后树」= 索引(index)内容:staged 版优先于 HEAD;无 git(fixture 沙箱)时回退工作区文件。
|
|
8
|
-
# 预算表按字节读,显式去 \r——本仓库 core.autocrlf=true,表文件检出即 CRLF,行尾残留会让数值比较失配。
|
|
9
|
-
# 字节口径 = 索引 / HEAD 内容(git cat-file,LF 归一)。**禁用 wc -c 核对**——工作区文件是 CRLF,量出来系统性偏大
|
|
10
|
-
# (2026-09-21 实测 .agents/commands/ 工作区 39963B vs 索引 39451B,差 ~500B,一度误判「已超限」)。
|
|
11
|
-
cd "$(git rev-parse --show-toplevel 2>/dev/null)" || true # 非 git 环境(fixture)留在原地
|
|
12
|
-
MODE=${1:---all}
|
|
13
|
-
BUDGETS=.agents/rule-budgets.txt
|
|
14
|
-
[ -f "$BUDGETS" ] || exit 0
|
|
15
|
-
|
|
16
|
-
# 取「提交后」字节数:索引 → HEAD → 工作区
|
|
17
|
-
fsize() { # <path>
|
|
18
|
-
if git cat-file -e ":$1" 2>/dev/null; then git cat-file -s ":$1"
|
|
19
|
-
elif git cat-file -e "HEAD:$1" 2>/dev/null; then git cat-file -s "HEAD:$1"
|
|
20
|
-
else wc -c < "$1" 2>/dev/null | tr -d '[:space:]'
|
|
21
|
-
fi
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
staged_list=""
|
|
25
|
-
if [ "$MODE" = "--staged" ]; then
|
|
26
|
-
staged_list=$(git -c core.quotepath=off diff --cached --name-only 2>/dev/null)
|
|
27
|
-
fi
|
|
28
|
-
|
|
29
|
-
viol=0
|
|
30
|
-
while IFS= read -r line; do
|
|
31
|
-
line=$(printf '%s' "$line" | tr -d '\r')
|
|
32
|
-
pat=${line%% *}
|
|
33
|
-
lim=${line##* }
|
|
34
|
-
case "$pat" in ''|'#'*) continue ;; esac
|
|
35
|
-
case "$lim" in ''|*[!0-9]*) continue ;; esac
|
|
36
|
-
case "$pat" in
|
|
37
|
-
*/) # 目录合计:--staged 下仅当暂存触及该目录内文件才查(case 字面前缀匹配——pat 含 . 时不能当正则用,2026-09-24)
|
|
38
|
-
if [ "$MODE" = "--staged" ]; then
|
|
39
|
-
found=0
|
|
40
|
-
while IFS= read -r sf; do
|
|
41
|
-
case "$sf" in "$pat"*) found=1 ;; esac
|
|
42
|
-
done <<EOF
|
|
43
|
-
$staged_list
|
|
44
|
-
EOF
|
|
45
|
-
[ "$found" = "1" ] || continue
|
|
46
|
-
fi
|
|
47
|
-
total=0
|
|
48
|
-
for f in "$pat"*; do
|
|
49
|
-
[ -f "$f" ] || continue
|
|
50
|
-
total=$((total + $(fsize "$f")))
|
|
51
|
-
done
|
|
52
|
-
if [ "$total" -gt "$lim" ]; then
|
|
53
|
-
echo "常驻面超限:${pat} 合计 ${total}B > 上限 ${lim}B"
|
|
54
|
-
viol=1
|
|
55
|
-
fi
|
|
56
|
-
;;
|
|
57
|
-
*) # 逐文件(glob 刻意不加引号以展开;无命中时 [ -f ] 兜住)
|
|
58
|
-
for f in $pat; do
|
|
59
|
-
[ -f "$f" ] || continue
|
|
60
|
-
if [ "$MODE" = "--staged" ]; then
|
|
61
|
-
printf '%s\n' "$staged_list" | grep -qxF "$f" || continue
|
|
62
|
-
fi
|
|
63
|
-
b=$(fsize "$f")
|
|
64
|
-
if [ "$b" -gt "$lim" ]; then
|
|
65
|
-
echo "常驻面超限:$f = ${b}B > 上限 ${lim}B"
|
|
66
|
-
viol=1
|
|
67
|
-
fi
|
|
68
|
-
done
|
|
69
|
-
;;
|
|
70
|
-
esac
|
|
71
|
-
done < "$BUDGETS"
|
|
72
|
-
|
|
73
|
-
[ "$viol" -eq 0 ] || exit 1
|
|
74
|
-
exit 0
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
// flow-kit source-sync-check:装户面同步一致性检查(包源 templates/_agents/ vs 装副本 .agents/)
|
|
2
|
-
// 目的:包源改了 templates/_agents/ 但 .agents/ 装副本没刷新——这种"真空地带"sync.mjs 不扫、doctor.mjs §6.6 不扫
|
|
3
|
-
// 本工具扫 templates/_agents/ 下所有 .md / .mjs / .json / .txt(排除 cache/)vs .agents/ 对应路径
|
|
4
|
-
// 输出三类差异:缺失(包源有装副本无)/ 孤儿(包源无装副本有)/ 漂移(两者都有 sha 不一致)
|
|
5
|
-
// 排除装副本独有文件:kit.json / settings.json / hooks/commit-check.config.json(init 渲染产物,不属双源)
|
|
6
|
-
// 用法:node .agents/scripts/source-sync-check.mjs --diff
|
|
7
|
-
// node .agents/scripts/source-sync-check.mjs --json
|
|
8
|
-
// node .agents/scripts/source-sync-check.mjs --gate(门禁模式:缺失/漂移 exit 1,孤儿只报告不计失败
|
|
9
|
-
// ——pre-commit 双源一致性门禁用,2026-09-27 audit-gate-hardening;--diff/--json 仍恒 exit 0)
|
|
10
|
-
// node .agents/scripts/source-sync-check.mjs --pkg-root <path> --target <path>
|
|
11
|
-
// 零依赖;B-b 决策「只报告不修复」(--gate 只加 exit 码裁决,不改默认行为)
|
|
12
|
-
import fs from 'node:fs';
|
|
13
|
-
import path from 'node:path';
|
|
14
|
-
import { createHash } from 'node:crypto';
|
|
15
|
-
|
|
16
|
-
function fail(msg) { console.error('❌ ' + msg); process.exit(1); }
|
|
17
|
-
|
|
18
|
-
const VALID_EXTS = new Set(['.md', '.mjs', '.json', '.txt']);
|
|
19
|
-
// 装副本独有文件(init 渲染产物,不属双源结构)
|
|
20
|
-
const RENDER_OUTPUT_FILES = new Set(['kit.json', 'settings.json', 'hooks/commit-check.config.json']);
|
|
21
|
-
// **项目自持、按设计就该与包源骨架不同**的架子文件(2026-09-28 追加):
|
|
22
|
-
// `notes/runtime-env.md` 自述「记录本机/本项目的运行时差异……**本文件是骨架:项目按实际填写,
|
|
23
|
-
// 不随 flow-kit 升级覆盖**」——即包源只发骨架、装户填实况,**两处本就应当不同**。
|
|
24
|
-
// 但本工具此前按 `.md` 全量比对,导致装户一旦按文件自述填写,`--gate` 即 exit 1(CI 红):
|
|
25
|
-
// 实测(本仓自装)填写后 --gate 报 `漂移 1: notes/runtime-env.md`,回滚即恢复 0。
|
|
26
|
-
// 这属「门禁范围与文件自述冲突」,非真漂移——故按渲染产物同类处理(排除)。
|
|
27
|
-
// 判据:该文件是**项目内容**而非**引擎件**;引擎件(.mjs 脚本等)不在排除内,仍受双源门禁约束。
|
|
28
|
-
const PROJECT_OWNED_FILES = new Set(['notes/runtime-env.md']);
|
|
29
|
-
const TARGET_EXCLUDE = new Set([...RENDER_OUTPUT_FILES, ...PROJECT_OWNED_FILES]); // 向后兼容别名(原为 RENDER_OUTPUT_FILES)
|
|
30
|
-
|
|
31
|
-
function sha256(p) {
|
|
32
|
-
try {
|
|
33
|
-
// LF 归一(2026-09-27 closing-coverage):CRLF 盘面与 LF 克隆同值,行尾不一致不误报缺失/漂移/孤儿外差异
|
|
34
|
-
return createHash('sha256').update(fs.readFileSync(p, 'utf8').replace(/\r\n/g, '\n'), 'utf8').digest('hex');
|
|
35
|
-
} catch {
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// walkMd:递归扫目录,列出所有 validExt 文件(排除 cache/)
|
|
41
|
-
function walkMd(root) {
|
|
42
|
-
const out = [];
|
|
43
|
-
function walk(dir, relBase) {
|
|
44
|
-
if (!fs.existsSync(dir)) return;
|
|
45
|
-
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
46
|
-
const rel = relBase ? relBase + '/' + e.name : e.name;
|
|
47
|
-
const abs = path.join(dir, e.name);
|
|
48
|
-
// 排除 cache/ 目录(与 sync.mjs 既有约定一致)
|
|
49
|
-
if (rel === 'cache' || rel.startsWith('cache/')) continue;
|
|
50
|
-
if (e.isDirectory()) walk(abs, rel);
|
|
51
|
-
else if (VALID_EXTS.has(path.extname(e.name))) out.push(rel);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
walk(root, '');
|
|
55
|
-
return out;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function sourceSyncCheck({ pkgRoot, target }) {
|
|
59
|
-
const authorityRoot = path.join(pkgRoot, 'templates', '_agents');
|
|
60
|
-
const adapterRoot = path.join(target, '.agents');
|
|
61
|
-
// **两侧都要按 TARGET_EXCLUDE 过滤**(2026-09-28 修正):只过滤装副本一侧会把排除件
|
|
62
|
-
// 变成「包源有 / 装副本无」的**缺失**(实测:加排除后 --gate 从「漂移 1」变成「缺失 1」,
|
|
63
|
-
// 仍 exit 1)。排除的语义是「不参与双源比对」,故包源侧同样滤掉。
|
|
64
|
-
const pkgFiles = walkMd(authorityRoot).filter((f) => !TARGET_EXCLUDE.has(f));
|
|
65
|
-
const tgtFiles = walkMd(adapterRoot).filter((f) => !TARGET_EXCLUDE.has(f));
|
|
66
|
-
const pkgSet = new Set(pkgFiles);
|
|
67
|
-
const tgtSet = new Set(tgtFiles);
|
|
68
|
-
const missing = []; // 包源有 / 装副本无
|
|
69
|
-
const orphan = []; // 包源无 / 装副本有
|
|
70
|
-
const drift = []; // 两者都有但 sha 不一致
|
|
71
|
-
for (const f of pkgFiles) {
|
|
72
|
-
if (!tgtSet.has(f)) { missing.push({ rel: f }); continue; }
|
|
73
|
-
const pkgSha = sha256(path.join(authorityRoot, f));
|
|
74
|
-
const tgtSha = sha256(path.join(adapterRoot, f));
|
|
75
|
-
if (pkgSha !== null && tgtSha !== null && pkgSha !== tgtSha) {
|
|
76
|
-
drift.push({ rel: f, pkgSha, tgtSha });
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
for (const f of tgtFiles) {
|
|
80
|
-
if (!pkgSet.has(f)) orphan.push({ rel: f });
|
|
81
|
-
}
|
|
82
|
-
return {
|
|
83
|
-
pkgRoot, target,
|
|
84
|
-
pkgCount: pkgFiles.length,
|
|
85
|
-
tgtCount: tgtFiles.length,
|
|
86
|
-
missing: missing.sort((a, b) => a.rel.localeCompare(b.rel)),
|
|
87
|
-
orphan: orphan.sort((a, b) => a.rel.localeCompare(b.rel)),
|
|
88
|
-
drift: drift.sort((a, b) => a.rel.localeCompare(b.rel)),
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function printDiff(result) {
|
|
93
|
-
console.log('▶ flow-kit source-sync-check --diff');
|
|
94
|
-
console.log(' 包源 (templates/_agents/) ' + result.pkgCount + ' 份 | 装副本 (.agents/) ' + result.tgtCount + ' 份');
|
|
95
|
-
if (!result.missing.length && !result.orphan.length && !result.drift.length) {
|
|
96
|
-
console.log(' 无差异 ✅');
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
if (result.missing.length) {
|
|
100
|
-
console.log(' 缺失(包源有 / 装副本无)' + result.missing.length + ':');
|
|
101
|
-
for (const m of result.missing) console.log(' - ' + m.rel);
|
|
102
|
-
}
|
|
103
|
-
if (result.orphan.length) {
|
|
104
|
-
console.log(' 孤儿(包源无 / 装副本有)' + result.orphan.length + ':');
|
|
105
|
-
for (const o of result.orphan) console.log(' - ' + o.rel);
|
|
106
|
-
}
|
|
107
|
-
if (result.drift.length) {
|
|
108
|
-
console.log(' 漂移(包源装副本都有但 sha 不一致)' + result.drift.length + ':');
|
|
109
|
-
for (const d of result.drift) console.log(' - ' + d.rel + ' pkg=' + d.pkgSha.slice(0, 8) + '… tgt=' + d.tgtSha.slice(0, 8) + '…');
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const isMain = process.argv[1] && process.argv[1].endsWith('source-sync-check.mjs');
|
|
114
|
-
if (isMain) {
|
|
115
|
-
let jsonMode = false, gateMode = false;
|
|
116
|
-
let pkgRoot = null, target = null;
|
|
117
|
-
for (let i = 0; i < process.argv.length - 2; i++) {
|
|
118
|
-
const a = process.argv[2 + i];
|
|
119
|
-
if (a === '--json') jsonMode = true;
|
|
120
|
-
else if (a === '--diff') { /* diff 为默认输出模式,无需置位 */ }
|
|
121
|
-
else if (a === '--gate') gateMode = true;
|
|
122
|
-
else if (a === '--pkg-root') pkgRoot = process.argv[2 + i + 1];
|
|
123
|
-
else if (a === '--target') target = process.argv[2 + i + 1];
|
|
124
|
-
}
|
|
125
|
-
if (!pkgRoot) pkgRoot = process.cwd();
|
|
126
|
-
if (!target) target = process.cwd();
|
|
127
|
-
const authorityRoot = path.join(pkgRoot, 'templates', '_agents');
|
|
128
|
-
const adapterRoot = path.join(target, '.agents');
|
|
129
|
-
if (!fs.existsSync(authorityRoot)) fail('找不到包源:' + authorityRoot);
|
|
130
|
-
if (!fs.existsSync(adapterRoot)) fail('找不到装副本:' + adapterRoot);
|
|
131
|
-
const result = sourceSyncCheck({ pkgRoot, target });
|
|
132
|
-
if (jsonMode) {
|
|
133
|
-
process.stdout.write(JSON.stringify({
|
|
134
|
-
pkgCount: result.pkgCount,
|
|
135
|
-
tgtCount: result.tgtCount,
|
|
136
|
-
missing: result.missing.map((m) => m.rel),
|
|
137
|
-
orphan: result.orphan.map((o) => o.rel),
|
|
138
|
-
drift: result.drift.map((d) => ({ rel: d.rel, pkgSha: d.pkgSha.slice(0, 8), tgtSha: d.tgtSha.slice(0, 8) })),
|
|
139
|
-
}, null, 2) + '\n');
|
|
140
|
-
} else {
|
|
141
|
-
printDiff(result);
|
|
142
|
-
}
|
|
143
|
-
if (gateMode) {
|
|
144
|
-
// 门禁模式(pre-commit 双源一致性用):缺失/漂移非零阻断——正是「包源改了装副本没跟」的故障模式
|
|
145
|
-
// (doctor §4 台账 sha 对此静默:盘面=台账即 PASS;§4.5 只抓未登记)。孤儿只报告不计失败:
|
|
146
|
-
// 孤儿 = 装副本独有(装户自持内容如本地 skills),删留由人拍板,不入机器门。
|
|
147
|
-
if (result.missing.length || result.drift.length) {
|
|
148
|
-
console.error('❌ 双源不一致(缺失 ' + result.missing.length + ' / 漂移 ' + result.drift.length + ')——包源与装副本须同提交成对(双源纪律);跑 node bin/flow-kit.mjs sync 后原路重试(孤儿只报告不计失败)');
|
|
149
|
-
process.exit(1);
|
|
150
|
-
}
|
|
151
|
-
process.exit(0);
|
|
152
|
-
}
|
|
153
|
-
// diff/json 模式总 exit 0;差异存在不视作错误(人类阅读用;B-b 决策「只报告不修复」)
|
|
154
|
-
process.exit(0);
|
|
155
|
-
}
|
|
156
|
-
export default sourceSyncCheck;
|