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,153 @@
|
|
|
1
|
+
// fixture-scripts.mjs — 测试夹具脚本闭包工具(2026-10-12 fixture-closure-rootfix)
|
|
2
|
+
//
|
|
3
|
+
// 为什么有它:夹具此前用**手工枚举**把被测脚本及其依赖拷进临时根(如 `const SCRIPTS = ['check-loop.mjs',
|
|
4
|
+
// 'policy.mjs', ...]`)。那份枚举必须与被测脚本真实的依赖闭包一致,但两者互不校验——给脚本加一行
|
|
5
|
+
// `import './x.mjs'`,枚举不会自动跟着变,而滞后的表现是**静默绿**:ESM resolve 失败 → 被测脚本根本没跑
|
|
6
|
+
// → 断言面为空 → 套件判绿。2026-10-12 那批台账改动就因此在 `check-loop.test` / `edit-face-check.test` /
|
|
7
|
+
// `ledger-prune.test` 三处先后假绿(各自补拷才转绿)。补齐是止血,消灭枚举才是根治。
|
|
8
|
+
//
|
|
9
|
+
// 本模块给两样东西,各自独立成立:
|
|
10
|
+
// ① scriptClosure / copyScriptClosure —— 自动求依赖传递闭包并整闭包拷贝,维护面归零(新增依赖自动跟随);
|
|
11
|
+
// ② assertFixtureScripts —— 拷完在**目标目录**独立复扫一遍,任一相对依赖解析不到就抛错报全量缺失清单。
|
|
12
|
+
// 这是纵深防御:即使将来有人又手写枚举(或绕过本 helper 直接拷),漏拷会在建夹具那一刻响亮炸掉,
|
|
13
|
+
// 而不是几十个场景之后以「门禁没跑」的形式悄悄绿掉。
|
|
14
|
+
//
|
|
15
|
+
// 依赖发现口径(刻意取宽):**静态 import/export from**、**动态 import()**、以及**裸文件名字面量**
|
|
16
|
+
// (形如 path.join(here, 'kb-search.mjs') 的 spawn 依赖——wiki-search.mjs 就只靠这一条挂住 kb-search;
|
|
17
|
+
// check-hygiene 靠它挂住 rule-budget.mjs 与两个生成器)。裸文件名只在**解析到同目录真实文件**时才计入,
|
|
18
|
+
// 故 `'../hooks/commit-check.cjs'` 这类跨目录字面量天然被排除(scripts/ 下不存在同名文件)。
|
|
19
|
+
// 宁可多拷(多一个文件不会让测试变假绿),不可漏拷(漏拷就是假绿)。
|
|
20
|
+
import fs from 'node:fs';
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
import { fileURLToPath } from 'node:url';
|
|
23
|
+
|
|
24
|
+
export const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
25
|
+
|
|
26
|
+
const SCRIPT_EXT = /\.(?:mjs|cjs|js|sh)$/;
|
|
27
|
+
// 裸文件名 / ./x / ../x 三种形态;含目录分隔的更长路径字面量(如 'templates/_agents/scripts/gate.mjs')不匹配。
|
|
28
|
+
// 点开头的隐藏名不匹配:check-loop.mjs 里有 `.replace(/\.mjs$/, '.test.mjs')`,那个 '.test.mjs'
|
|
29
|
+
// 是**后缀字面量**不是文件名,按名解析必然扑空(对每个测试夹具都误报「漏拷」→ 整套件红)。
|
|
30
|
+
const BARE_NAME = /^(?:\.{1,2}\/)?[A-Za-z0-9_][A-Za-z0-9_.-]*$/;
|
|
31
|
+
// 引号字面量(单/双引号皆可),逐行扫即可——模板字面量里的文件名由动态拼接给出,不在此列
|
|
32
|
+
const QUOTED = /'([^'\n]*)'|"([^"\n]*)"/g;
|
|
33
|
+
|
|
34
|
+
// stripComments:抹掉注释,保留字符串字面量内容。
|
|
35
|
+
// 刻意**不用正则**:正则版把 `confirmations.d/*.jsonl` 这类注释里的 `/*` 当块注释起点,跨注释一路吃到下一个
|
|
36
|
+
// 真 `*/`,把后面的 import 整段吃掉(check-ledger-invariant 实测:12141 字符被砍到 4198,43 行的
|
|
37
|
+
// import 直接消失 → 闭包漏依赖 → 回到假绿)。故走一个 40 行的逐字符状态机,只认三种真形态。
|
|
38
|
+
// 字符串内容**不抹**(我们要的就是字面量里的文件名),故正则里的 ".*?/*.jsonl" 这类串不被误当注释。
|
|
39
|
+
function stripComments(src) {
|
|
40
|
+
let out = '';
|
|
41
|
+
let i = 0;
|
|
42
|
+
const n = src.length;
|
|
43
|
+
while (i < n) {
|
|
44
|
+
const c = src[i];
|
|
45
|
+
const d = src[i + 1];
|
|
46
|
+
if (c === '/' && d === '/') { while (i < n && src[i] !== '\n') i++; continue; }
|
|
47
|
+
if (c === '/' && d === '*') {
|
|
48
|
+
i += 2;
|
|
49
|
+
while (i < n && !(src[i] === '*' && src[i + 1] === '/')) i++;
|
|
50
|
+
i += 2;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (c === '"' || c === "'" || c === '`') {
|
|
54
|
+
const quote = c;
|
|
55
|
+
out += c;
|
|
56
|
+
i++;
|
|
57
|
+
while (i < n) {
|
|
58
|
+
if (src[i] === '\\') { out += src[i] + (src[i + 1] || ''); i += 2; continue; }
|
|
59
|
+
out += src[i];
|
|
60
|
+
if (src[i] === quote) { i++; break; }
|
|
61
|
+
i++;
|
|
62
|
+
}
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
out += c;
|
|
66
|
+
i++;
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// depsOf:某脚本源码里引用的、可能落在同目录的脚本文件名(去重、保持出现序)。
|
|
72
|
+
// 纯文本解析——不查盘面,故「源码引用了但盘面没有的名字」也会被列出,由调用方按盘面存在性收窄。
|
|
73
|
+
export function depsOf(sourceText) {
|
|
74
|
+
const out = [];
|
|
75
|
+
const push = (v) => { if (v && !out.includes(v)) out.push(v); };
|
|
76
|
+
const text = stripComments(sourceText);
|
|
77
|
+
// import/export ... from '...' 与 import('...')
|
|
78
|
+
for (const m of text.matchAll(/(?:\bfrom\s*|\bimport\s*\(\s*)(['"])([^'"]+)\1/g)) push(m[2]);
|
|
79
|
+
// 其余裸文件名字面量(spawn / path.join 依赖)
|
|
80
|
+
for (const m of text.matchAll(QUOTED)) push(m[1] || m[2]);
|
|
81
|
+
return out.filter((v) => SCRIPT_EXT.test(v) && BARE_NAME.test(v));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// scriptClosure:从入口出发递归求同目录依赖传递闭包(BFS,稳定序;不存在的引用不炸——由 assert 层兜)
|
|
85
|
+
export function scriptClosure(entries, { from = SCRIPT_DIR } = {}) {
|
|
86
|
+
const queue = [...new Set(entries)].map((e) => path.basename(e));
|
|
87
|
+
const seen = new Set();
|
|
88
|
+
const order = [];
|
|
89
|
+
while (queue.length) {
|
|
90
|
+
const name = queue.shift();
|
|
91
|
+
if (seen.has(name)) continue;
|
|
92
|
+
seen.add(name);
|
|
93
|
+
order.push(name);
|
|
94
|
+
const abs = path.join(from, name);
|
|
95
|
+
if (!fs.existsSync(abs)) continue;
|
|
96
|
+
for (const dep of depsOf(fs.readFileSync(abs, 'utf8'))) {
|
|
97
|
+
const base = path.basename(dep);
|
|
98
|
+
if (!seen.has(base)) queue.push(base);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return order;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// copyScriptClosure:把入口的整个依赖闭包拷进 destScriptsDir,返回实际拷入的文件名。
|
|
105
|
+
// opts.extra —— 额外要拷的同级非脚本件(如 rule-budget.mjs 之外的 .txt 词表由 copyAgentsFiles 负责)。
|
|
106
|
+
export function copyScriptClosure(destScriptsDir, entries, opts = {}) {
|
|
107
|
+
const from = opts.from || SCRIPT_DIR;
|
|
108
|
+
fs.mkdirSync(destScriptsDir, { recursive: true });
|
|
109
|
+
const closure = scriptClosure(entries, { from });
|
|
110
|
+
const copied = [];
|
|
111
|
+
for (const name of closure) {
|
|
112
|
+
const src = path.join(from, name);
|
|
113
|
+
if (!fs.existsSync(src) || !fs.statSync(src).isFile()) continue;
|
|
114
|
+
const dest = path.join(destScriptsDir, name);
|
|
115
|
+
fs.copyFileSync(src, dest);
|
|
116
|
+
copied.push(name);
|
|
117
|
+
}
|
|
118
|
+
// 纵深防御:在**目标目录**独立复扫,不复用上面的闭包结果(这样才抓得到拷贝环节本身的错漏)
|
|
119
|
+
assertFixtureScripts(destScriptsDir);
|
|
120
|
+
return copied;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// copyAgentsFiles:把包源 `.agents/` 面的词表/配置(workflow-enums.txt 等)拷进夹具的 .agents/ 根
|
|
124
|
+
export function copyAgentsFiles(destAgentsDir, names, { from = path.join(SCRIPT_DIR, '..') } = {}) {
|
|
125
|
+
fs.mkdirSync(destAgentsDir, { recursive: true });
|
|
126
|
+
const copied = [];
|
|
127
|
+
for (const name of names) {
|
|
128
|
+
const src = path.join(from, name);
|
|
129
|
+
if (!fs.existsSync(src)) continue;
|
|
130
|
+
fs.copyFileSync(src, path.join(destAgentsDir, name));
|
|
131
|
+
copied.push(name);
|
|
132
|
+
}
|
|
133
|
+
return copied;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// assertFixtureScripts:扫目录内全部脚本,任一相对/裸文件名依赖解析不到 → 抛错(报全量缺失,不报第一个)
|
|
137
|
+
// 用法:夹具建好后立即调;漏拷在当场炸,而不是在「门禁静默不跑」之后绿掉。
|
|
138
|
+
export function assertFixtureScripts(dir) {
|
|
139
|
+
const missing = [];
|
|
140
|
+
if (!fs.existsSync(dir)) throw new Error(`assertFixtureScripts: 目录不存在 ${dir}`);
|
|
141
|
+
for (const f of fs.readdirSync(dir).filter((f) => SCRIPT_EXT.test(f)).sort()) {
|
|
142
|
+
const src = fs.readFileSync(path.join(dir, f), 'utf8');
|
|
143
|
+
for (const dep of depsOf(src)) {
|
|
144
|
+
if (dep.includes('/') && dep.startsWith('..')) continue; // 跨目录引用不在本夹具职责内
|
|
145
|
+
if (!fs.existsSync(path.join(dir, dep))) missing.push(`${f} → ${dep}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (missing.length) {
|
|
149
|
+
throw new Error(`夹具依赖缺失(漏拷 → 被测脚本 import 失败 → 门禁静默不跑 = 假绿):\n ${missing.join('\n ')}\n`
|
|
150
|
+
+ '修法:改用 copyScriptClosure(入口脚本) 让闭包自动跟随,不要手写枚举。');
|
|
151
|
+
}
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// fixture-scripts.test.mjs — 夹具闭包工具自身套件(2026-10-12 fixture-closure-rootfix)
|
|
3
|
+
// 命题:本工具**只能比手工枚举多拷,绝不能少拷**——多拷的文件不会让测试假绿,少拷的文件会让
|
|
4
|
+
// 被测脚本 import 失败、门禁静默不跑、套件假绿(2026-10-12 台账那批三处假绿的根因)。
|
|
5
|
+
// 覆盖面:多层传递依赖 / node: 内建跳过 / 注释与字符串字面量不误收 / 后缀字面量('.test.mjs')不误报 /
|
|
6
|
+
// 缺失即炸且报全量清单 / spawn 依赖(裸文件名字面量)也被收进闭包。
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import os from 'node:os';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { SCRIPT_DIR, depsOf, scriptClosure, copyScriptClosure, assertFixtureScripts, copyAgentsFiles } from './fixture-scripts.mjs';
|
|
11
|
+
|
|
12
|
+
let pass = 0, fail = 0;
|
|
13
|
+
const check = (name, ok, detail = '') => {
|
|
14
|
+
if (ok) { pass++; console.log(` ok ${name}`); }
|
|
15
|
+
else { fail++; console.error(`FAIL ${name}${detail ? ' — ' + detail : ''}`); }
|
|
16
|
+
};
|
|
17
|
+
const tmpRoot = (p) => fs.mkdtempSync(path.join(os.tmpdir(), `fxsc-${p}-`));
|
|
18
|
+
// 造一个合成 scripts 目录(不触真实盘面——本工具的依赖发现是纯文本解析,故合成目录可独立验证)
|
|
19
|
+
const synth = (files) => {
|
|
20
|
+
const dir = tmpRoot('synth');
|
|
21
|
+
for (const [name, body] of Object.entries(files)) fs.writeFileSync(path.join(dir, name), body, 'utf8');
|
|
22
|
+
return dir;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// ---- S1:多层传递依赖闭包 ----
|
|
26
|
+
{
|
|
27
|
+
const src = synth({
|
|
28
|
+
'a.mjs': `import './b.mjs';\nimport { x } from './sub/c.mjs';\n`,
|
|
29
|
+
'b.mjs': `import './c.mjs';\n`,
|
|
30
|
+
'c.mjs': `export const x = 1;\n`,
|
|
31
|
+
'd.mjs': `export const y = 2;\n`,
|
|
32
|
+
});
|
|
33
|
+
const cl = scriptClosure(['a.mjs'], { from: src });
|
|
34
|
+
check('S1 三层传递依赖全部收进闭包', ['a.mjs', 'b.mjs', 'c.mjs'].every((f) => cl.includes(f)) && !cl.includes('d.mjs'), cl.join(','));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ---- S2:node: 内建与跨目录字面量不误收 ----
|
|
38
|
+
{
|
|
39
|
+
const src = synth({
|
|
40
|
+
'a.mjs': `import fs from 'node:fs';\nimport { x } from './b.mjs';\nconst p = path.join(h, 'templates/_agents/scripts/gate.mjs');\n`,
|
|
41
|
+
'b.mjs': 'export const x=1;\n',
|
|
42
|
+
});
|
|
43
|
+
const cl = scriptClosure(['a.mjs'], { from: src });
|
|
44
|
+
check('S2 node: 内建与长路径字面量被排除', cl.length === 2, cl.join(','));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ---- S3:注释与字符串里的脚本名不算依赖 ----
|
|
48
|
+
{
|
|
49
|
+
const deps = depsOf(`// 拷 './gone.mjs' 进来\n/* from './gone2.mjs' */\nimport './real.mjs';\n`);
|
|
50
|
+
check('S3 注释里的文件名不算依赖', deps.length === 1 && deps[0] === './real.mjs', JSON.stringify(deps));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ---- S4:后缀字面量('.test.mjs')不误报——防「每个夹具都误报漏拷」 ----
|
|
54
|
+
// 这条是真实回归:check-loop.mjs 里就有 `.replace(/\.mjs$/, '.test.mjs')`,按名解析必然扑空。
|
|
55
|
+
{
|
|
56
|
+
const deps = depsOf(`f.replace(/\\.mjs$/, '.test.mjs')\nimport './real.mjs';\n`);
|
|
57
|
+
check('S4 后缀字面量 .test.mjs 不计入依赖', !deps.some((d) => d.includes('test')), JSON.stringify(deps));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ---- S5:spawn 依赖(裸文件名字面量)也进闭包——wiki-search 只靠 path.join(here,'kb-search.mjs') 挂住依赖 ----
|
|
61
|
+
{
|
|
62
|
+
const src = synth({
|
|
63
|
+
'wiki-search.mjs': `spawnSync(process.execPath, [path.join(here, 'kb-search.mjs')]);\n`,
|
|
64
|
+
'kb-search.mjs': `import './workflow-enums.mjs';\n`,
|
|
65
|
+
'workflow-enums.mjs': 'export const a=1;\n',
|
|
66
|
+
});
|
|
67
|
+
const cl = scriptClosure(['wiki-search.mjs'], { from: src });
|
|
68
|
+
check('S5 spawn 裸文件名依赖传递收进闭包', cl.includes('kb-search.mjs') && cl.includes('workflow-enums.mjs'), cl.join(','));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ---- S6:缺失即炸且报全量清单(非报第一个就停) ----
|
|
72
|
+
{
|
|
73
|
+
const dir = tmpRoot('missing');
|
|
74
|
+
fs.writeFileSync(path.join(dir, 'a.mjs'), `import './m1.mjs';\nimport './m2.mjs';\n`, 'utf8');
|
|
75
|
+
let msg = '';
|
|
76
|
+
try { assertFixtureScripts(dir); } catch (e) { msg = String(e.message); }
|
|
77
|
+
check('S6 缺依赖 → 抛错', !!msg, '未抛错');
|
|
78
|
+
check('S6 报全量缺失清单(m1+m2 都在)', msg.includes('m1.mjs') && msg.includes('m2.mjs'), msg);
|
|
79
|
+
check('S6 报文点明假绿与修法', msg.includes('假绿') && msg.includes('copyScriptClosure'), msg.slice(0, 120));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ---- S7:copyScriptClosure 对真实盘面跑通(check-loop 闭包 + 拷后自检不炸) ----
|
|
83
|
+
{
|
|
84
|
+
const dest = path.join(tmpRoot('copy'), 'scripts');
|
|
85
|
+
const copied = copyScriptClosure(dest, ['check-loop.mjs']);
|
|
86
|
+
check('S7 check-loop 闭包含新台账依赖(含 policy.mjs——B6b-1 分道/policy 锚随闭包自动跟随)', ['check-loop.mjs', 'ledger-loader.mjs', 'ledger-prune.mjs', 'policy.mjs'].every((f) => copied.includes(f)), copied.join(','));
|
|
87
|
+
// B6b-2 六段模块(2026-10-13):check-loop.mjs import 的段模块须随闭包自动带上——漏拷 = 被测脚本
|
|
88
|
+
// 在夹具里 resolve 失败 → 门禁静默不跑(本文件头注的假绿形态)。显式断言六件在场,不靠运气。
|
|
89
|
+
check('S7 check-loop 闭包含 B6b-2 六段模块(import 闭包自动跟随)',
|
|
90
|
+
['check-spec-verif.mjs', 'check-registry.mjs', 'check-convergence.mjs', 'check-attribution.mjs', 'check-prejudgment.mjs', 'check-funnel.mjs'].every((f) => copied.includes(f)),
|
|
91
|
+
copied.join(','));
|
|
92
|
+
check('S7 拷贝后 assertFixtureScripts 不炸(闭包自洽)', assertFixtureScripts(dest) === true);
|
|
93
|
+
// 真跑一次被测脚本,确认夹具不是「文件齐了但跑不起来」
|
|
94
|
+
const fx = path.dirname(dest);
|
|
95
|
+
fs.mkdirSync(path.join(fx, '.agents'), { recursive: true });
|
|
96
|
+
fs.copyFileSync(SCRIPT_DIR.replace(/scripts$/, 'workflow-enums.txt'), path.join(fx, '.agents', 'workflow-enums.txt'));
|
|
97
|
+
const { spawnSync } = await import('node:child_process');
|
|
98
|
+
const r = spawnSync(process.execPath, [path.join(dest, 'check-loop.mjs')], {
|
|
99
|
+
cwd: fx, encoding: 'utf8', env: { ...process.env, CHECK_LOOP_ROOT: fx },
|
|
100
|
+
});
|
|
101
|
+
check('S7 夹具内 check-loop 真能跑起来(非 ENOENT 崩)', !/Cannot find module|ERR_MODULE_NOT_FOUND/.test(`${r.stdout || ''}${r.stderr || ''}`), `${r.stdout || ''}${r.stderr || ''}`.slice(0, 200));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ---- S8:copyAgentsFiles 拷词表,缺失的静默跳过 ----
|
|
105
|
+
{
|
|
106
|
+
const dest = tmpRoot('agents');
|
|
107
|
+
const copied = copyAgentsFiles(dest, ['workflow-enums.txt', 'definitely-absent.txt']);
|
|
108
|
+
check('S8 存在词表拷入、缺席跳过', copied.length === 1 && copied[0] === 'workflow-enums.txt', copied.join(','));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
console.log(`\n[fixture-scripts] PASS ${pass} / FAIL ${fail}`);
|
|
112
|
+
process.exit(fail ? 1 : 0);
|
|
@@ -67,6 +67,7 @@ const PAIRS = [
|
|
|
67
67
|
{ doctor: '6.7', cl: null, note: '跨宿主薄适配——装户体检独有' },
|
|
68
68
|
{ doctor: '6.8', cl: null, note: 'workflows 编排脚本 lint——装户体检独有' },
|
|
69
69
|
{ doctor: '6.9', cl: null, note: '模板下发感知(2026-10-06 template-downstream)——装户体检独有(只读回显 sync 出账同源判据)' },
|
|
70
|
+
{ doctor: '6.10', cl: null, note: 'level-hints 配置体检(2026-10-13 hint-doctor)——装户体检独有;提交面判低由 check-lane-surface(pre-commit 增量面)承担,不在 check-loop 项内' },
|
|
70
71
|
{ doctor: '8', cl: null, note: '看板端口——信息级独有' },
|
|
71
72
|
// 经 §7 覆盖:doctor §7 整体运行 check-loop(#2/#11 已直接配对,不重复登记)
|
|
72
73
|
{ doctor: '7', cl: '1', note: '经 §7 整体运行覆盖' },
|
|
@@ -87,6 +88,12 @@ const PAIRS = [
|
|
|
87
88
|
{ doctor: '7', cl: '18', note: '经 §7 整体运行覆盖(委派台账对账)' },
|
|
88
89
|
{ doctor: '7', cl: '19', note: '经 §7 整体运行覆盖(逐阶段审计——起草先于入口确认 / 台账审批顺序倒置)' },
|
|
89
90
|
{ doctor: '7', cl: '20', note: '经 §7 整体运行覆盖(引擎脚本测试覆盖,2026-10-01 gate-script-test-coverage)' },
|
|
91
|
+
{ doctor: '7', cl: '21', note: '经 §7 整体运行覆盖(spec 验证落地核查,2026-10-13 B6b-2 回灌)' },
|
|
92
|
+
{ doctor: '7', cl: '22', note: '经 §7 整体运行覆盖(机制使用量台账,2026-10-13 B6b-2 回灌)' },
|
|
93
|
+
{ doctor: '7', cl: '23', note: '经 §7 整体运行覆盖(判据级收敛对账,2026-10-13 B6b-2 回灌)' },
|
|
94
|
+
{ doctor: '7', cl: '24', note: '经 §7 整体运行覆盖(incident 归因字段核查,2026-10-13 B6b-2 回灌)' },
|
|
95
|
+
{ doctor: '7', cl: '25', note: '经 §7 整体运行覆盖(路由预判一致性,2026-10-13 B6b-2 回灌)' },
|
|
96
|
+
{ doctor: '7', cl: '26', note: '经 §7 整体运行覆盖(闭环漏斗「协议前」占比动作阈值,2026-10-13 B6b-2 回灌)' },
|
|
90
97
|
];
|
|
91
98
|
|
|
92
99
|
// gateChecklist:按登记表对照两侧检查项(pure function;pairs 可注入供测试)
|
|
@@ -176,17 +183,11 @@ if (isMain) {
|
|
|
176
183
|
const pkgRoot = process.argv[2] && !process.argv[2].startsWith('--') ? process.argv[2] : null;
|
|
177
184
|
const base = pkgRoot || process.cwd();
|
|
178
185
|
const doctorPath = path.join(base, 'src', 'doctor.mjs');
|
|
179
|
-
// check-loop
|
|
186
|
+
// check-loop 与本脚本同目录:包源 templates/_agents/scripts/,装户副本 .agents/scripts/。
|
|
180
187
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
181
|
-
const
|
|
182
|
-
const clCandidates = [
|
|
183
|
-
clPathMjs,
|
|
184
|
-
path.join(base, '.agents', 'scripts', 'check-loop.sh'),
|
|
185
|
-
path.join(here, 'check-loop.mjs'),
|
|
186
|
-
];
|
|
187
|
-
const clPath = clCandidates.find((p) => fs.existsSync(p));
|
|
188
|
+
const clPath = path.join(here, 'check-loop.mjs');
|
|
188
189
|
if (!fs.existsSync(doctorPath)) fail('找不到 doctor.mjs:' + doctorPath);
|
|
189
|
-
if (!clPath) fail('找不到 check-loop
|
|
190
|
+
if (!fs.existsSync(clPath)) fail('找不到 check-loop.mjs:' + clPath);
|
|
190
191
|
const doctorSrc = fs.readFileSync(doctorPath, 'utf8');
|
|
191
192
|
const checkLoopSrc = fs.readFileSync(clPath, 'utf8');
|
|
192
193
|
const result = gateChecklist({ doctorSrc, checkLoopSrc });
|
|
@@ -152,17 +152,8 @@ const y = 2;
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
const realDoctor = () => fs.readFileSync(path.join(SRC_ROOT, 'src', 'doctor.mjs'), 'utf8');
|
|
155
|
-
// check-loop
|
|
156
|
-
|
|
157
|
-
// 即本测试同级;.agents/ 优先(装户径),包源径兜底(纯包源径),两者皆无 → hasReal 已经把守。
|
|
158
|
-
const realCl = () => {
|
|
159
|
-
const mjs = path.join(SRC_ROOT, '.agents', 'scripts', 'check-loop.mjs');
|
|
160
|
-
if (fs.existsSync(mjs)) return fs.readFileSync(mjs, 'utf8');
|
|
161
|
-
const sh = path.join(SRC_ROOT, '.agents', 'scripts', 'check-loop.sh');
|
|
162
|
-
if (fs.existsSync(sh)) return fs.readFileSync(sh, 'utf8');
|
|
163
|
-
const pkgMjs = path.join(SCRIPT_DIR, 'check-loop.mjs');
|
|
164
|
-
return fs.readFileSync(pkgMjs, 'utf8');
|
|
165
|
-
};
|
|
155
|
+
// check-loop 与 gate-checklist.mjs 同目录(包源 scripts/,拷进装户后是 .agents/scripts/)。
|
|
156
|
+
const realCl = () => fs.readFileSync(path.join(SCRIPT_DIR, 'check-loop.mjs'), 'utf8');
|
|
166
157
|
const hasReal = fs.existsSync(path.join(SRC_ROOT, 'src', 'doctor.mjs'));
|
|
167
158
|
|
|
168
159
|
// ---- S10 真实仓库 baseline:PAIRS 全量登记 → 0 断档 / 0 未登记 ----
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// 默认路径(无 --gate-stats)下 segs 只在内存里攒、不打印不写盘 → 输出契约逐字节不变(硬约束:
|
|
8
8
|
// pre-push / pre-commit 消费者 + doctor 的 WARN 行计数 + check-loop.test.mjs 223 断言全依赖它)。
|
|
9
9
|
//
|
|
10
|
-
// 耗时口径**刻意包含子进程时间**(检查 11 调两个生成器 --check、检查 13 调
|
|
10
|
+
// 耗时口径**刻意包含子进程时间**(检查 11 调两个生成器 --check、检查 13 调 rule-budget.mjs)——
|
|
11
11
|
// 门禁真实成本本就要含子进程;不做剔除,但看板与 spec 均显式声明该口径防误读。
|
|
12
12
|
export function gateSeg(collector, id, label) {
|
|
13
13
|
const now = Date.now();
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// gen-effective-policy.mjs — 当前生效规则视图生成器(2026-10-13 自 Shipyard.Material 参考实现回灌,
|
|
3
|
+
// 源仓沿革留痕在包源仓 git 历史与 CHANGELOG——装户径不引实例路径)
|
|
4
|
+
// 背景:豁免锚经 policy v1→v8 演进后,「存量豁免 vs 受管」的合成语义只活在 check-loop.mjs 源码里,
|
|
5
|
+
// 规则空间永久二分且不可读——本脚本把 policy.mjs 单源渲染为人可读的 workflow/effective-policy.md,
|
|
6
|
+
// 与 INDEX.md 同款「生成物 + --check 防漂移」纪律;verify.mjs 挂 --check 门(CI 强制随 policy 变更重生成)。
|
|
7
|
+
// 渲染内容:当前生效 policyVersion(单源 policy.mjs#loadKitPolicy 读 .agents/kit.json,缺省回落 v1)、
|
|
8
|
+
// 逐锚键的生效日 / 消费检查项 / 分级现状(hard·已生效 / advisory·已生效 / 未到期——锚日 > 今日的
|
|
9
|
+
// 升级锚标注「待升级」)。
|
|
10
|
+
// 判定单源:锚定义与版本回落 = policy.mjs POLICIES / loadKitPolicy(本脚本不写日期、不读第二份配置,
|
|
11
|
+
// 只读只渲染);键→检查项映射取自 check-loop.mjs 头部检查清单(kit 现役 1-20 项,编号是文档引用锚)。
|
|
12
|
+
// 用法:node .agents/scripts/gen-effective-policy.mjs [--root <dir>] [--check]
|
|
13
|
+
// --check:与磁盘 workflow/effective-policy.md 比对,漂移 exit 1(CI/verify 用)。
|
|
14
|
+
// 比对前双侧行尾归一(CRLF→LF)。生成器仍只写 LF,--check 不改文件。
|
|
15
|
+
// 干净的 autocrlf 检出是 CRLF,字节相等会假红,且重生成只留下不可提交的行尾差。
|
|
16
|
+
// 测试:node .agents/scripts/gen-effective-policy.test.mjs(fixture 注入 --root)
|
|
17
|
+
import fs from 'node:fs';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
import { POLICIES, loadKitPolicy } from './policy.mjs';
|
|
21
|
+
|
|
22
|
+
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const argv = process.argv.slice(2);
|
|
24
|
+
const argOf = (k) => {
|
|
25
|
+
const i = argv.indexOf(k);
|
|
26
|
+
return i >= 0 && i + 1 < argv.length ? argv[i + 1] : '';
|
|
27
|
+
};
|
|
28
|
+
const ROOT = argOf('--root') || path.resolve(SCRIPT_DIR, '..', '..');
|
|
29
|
+
const OUT = path.join(ROOT, 'workflow', 'effective-policy.md');
|
|
30
|
+
|
|
31
|
+
// 键 → 消费检查项与分级说明(单源 check-loop.mjs 头部检查清单编号;说明文本与本脚本共同演进)
|
|
32
|
+
const CONSUMERS = {
|
|
33
|
+
moduleSince: ['检查 12', 'frontmatter「模块:」枚举合法性(词表单源 .agents/workflow-modules.txt)', 'advisory'],
|
|
34
|
+
check14Since: ['检查 14', '新 done 的 spec/plan 须有 approved git 历史(kit 口径恒 advisory 永不升级 hard)', 'advisory'],
|
|
35
|
+
confirmDocsEffective: ['检查 15', 'docs 确认台账两条件准入(台账行 ∪ 自报日期≥锚)', 'hard'],
|
|
36
|
+
confirmIncidentsEffective: ['检查 15', 'incidents 档确认阈值(fixed/closed 配对)', 'hard'],
|
|
37
|
+
bindingTs: ['检查 15', '四终态内容绑定(台账行 ts≥锚 才比对 sha256)', 'hard'],
|
|
38
|
+
check17UnclosedAfter: ['检查 17', '发版提交树未收口三件套——版本 tag 锚(非日期)', 'hard'],
|
|
39
|
+
stageGateSince: ['检查 19', '逐阶段审计(起草先于入口确认 / 台账顺序)', 'advisory'],
|
|
40
|
+
riskLevelSince: ['检查 1', 'intent risk_level 双字段协议(laneOfEntry 判道的生效锚——锚前建档单字段口径不判高)', 'hard'],
|
|
41
|
+
delegationSince: ['检查 18', '委派台账对账——锚前存量豁免', 'advisory'],
|
|
42
|
+
verifySince: ['检查 8', '测试绿声明对账(无 SHA 声明「测试绿」须 24h 内凭证)+ confirm-doc →done 前置', 'advisory'],
|
|
43
|
+
verifyDocSince: ['检查 8', '凭证按 doc 精确绑定的生效时刻(→done 前置与对账按 doc 匹配,不带 --doc 的行不给任何单背书)', 'advisory'],
|
|
44
|
+
scopeSince: ['检查 15', 'scope 子项(机器派生改动面 scope{files,sha256}——缺 scope 灰度 warning、摘要不符 hard)', 'hard'],
|
|
45
|
+
ledgerDocChainSince: ['检查 15', '台账哈希链域从物理序改为按 doc 逻辑序(写入侧 confirm-doc 同批生效)', 'hard'],
|
|
46
|
+
// B6b-2 六段 + §3x + 复核上下文(2026-10-13 自源仓回灌,消费面自本批起在场)
|
|
47
|
+
incidentRegrSince: ['检查 3x', 'incident 防复发验证证据门(fixed/closed 须落自动化用例/回归清单引用)', 'hard'],
|
|
48
|
+
specVerifSince: ['检查 21', 'spec 验证落地核查(验证节无测试文件/命令引用——advisory)', 'advisory'],
|
|
49
|
+
attributionSince: ['检查 24', 'incident 归因字段(来源/影响环境必填且在枚举——advisory)', 'advisory'],
|
|
50
|
+
convergenceSince: ['检查 23', '判据级收敛对账 advisory 档(证据无效 WARN)', 'advisory'],
|
|
51
|
+
convergenceHardSince: ['检查 23', '判据级收敛对账 hard 档(证据无效 HARD-BLOCK)', 'hard'],
|
|
52
|
+
reviewerSince: ['检查 1', '独立复核上下文标识(复核行缺「复核上下文:」——advisory)', 'advisory'],
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// today:视图基准日。EFFECTIVE_POLICY_TODAY 为测试注入缝(跨锚日稳定性用;生产不设)。
|
|
56
|
+
const today = () => process.env.EFFECTIVE_POLICY_TODAY || new Date().toISOString().slice(0, 10);
|
|
57
|
+
|
|
58
|
+
function render(viewDateStr) {
|
|
59
|
+
const policy = loadKitPolicy(ROOT);
|
|
60
|
+
const v = POLICIES[policy.policyVersion] || {};
|
|
61
|
+
const todayStr = viewDateStr || today();
|
|
62
|
+
const lines = [];
|
|
63
|
+
lines.push('# 当前生效规则视图(生成物,勿手改)');
|
|
64
|
+
lines.push('');
|
|
65
|
+
lines.push('> 生成器 `node .agents/scripts/gen-effective-policy.mjs`(policy.mjs 单源渲染;漂移由 verify.mjs `effective-policy` 门 --check 把关——改 policy 后须重生成并随同提交)。');
|
|
66
|
+
lines.push(`> 当前生效:**policyVersion ${policy.policyVersion}**(kit.json 声明 ${policy.requestedVersion}${policy.requestedVersion === policy.policyVersion ? '' : ',不在 POLICIES 表内已回落 v1'});视图日期 ${todayStr}。`);
|
|
67
|
+
lines.push('');
|
|
68
|
+
lines.push('| 锚键 | 生效日/锚 | 消费检查项 | 语义 | 分级 | 现状 |');
|
|
69
|
+
lines.push('|---|---|---|---|---|---|');
|
|
70
|
+
for (const key of Object.keys(v)) {
|
|
71
|
+
const raw = v[key];
|
|
72
|
+
const c = CONSUMERS[key] || ['—', '(未登记消费方——policy 加新键时同步登 CONSUMERS)', '—'];
|
|
73
|
+
const isDate = /^\d{4}-\d{2}-\d{2}$/.test(String(raw));
|
|
74
|
+
// 升级锚语义:日期 > 今日 = 未到期(维持低档);其余 = 已生效
|
|
75
|
+
const status = !isDate ? '生效(版本锚)' : (String(raw) <= todayStr ? '已生效' : `待升级(${raw} 起)`);
|
|
76
|
+
lines.push(`| \`${key}\` | ${raw} | ${c[0]} | ${c[1]} | ${c[2]} | ${status} |`);
|
|
77
|
+
}
|
|
78
|
+
lines.push('');
|
|
79
|
+
lines.push('<!-- GENERATED:BEGIN — gen-effective-policy.mjs 整段重写,手工说明写在本行之前 -->');
|
|
80
|
+
return lines.join('\n') + '\n';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const text = render();
|
|
84
|
+
// 行尾归一(与 gen-workflow-index.mjs normEol 同句):只影响 --check 判定,写盘仍是 LF。
|
|
85
|
+
const normEol = (s) => String(s).replace(/\r\n/g, '\n');
|
|
86
|
+
if (argv.includes('--check')) {
|
|
87
|
+
let disk = '';
|
|
88
|
+
try {
|
|
89
|
+
disk = fs.readFileSync(OUT, 'utf8');
|
|
90
|
+
} catch { /* 缺文件视作空——走漂移分支(新装户由 init 生成器首跑种子本文件) */ }
|
|
91
|
+
// 锚日时间炸弹拆除(源仓 2026-10-12 CI 实证:锚日当天重渲染按当日判「已生效」→ 与仓内视图必差
|
|
92
|
+
// → --check 必红——任何未来日期锚都会在锚日引爆)。修法:--check 对**磁盘文件自带的视图日期**重渲染
|
|
93
|
+
// (生成物与自身生成日自洽,跨锚日字节稳定);显式重生成(默认模式)才前进视图日期并翻转状态列。
|
|
94
|
+
// 视图日期行缺失的存量件回落当日口径。
|
|
95
|
+
const mDate = disk.match(/视图日期 (\d{4}-\d{2}-\d{2})/);
|
|
96
|
+
const checkText = mDate ? render(mDate[1]) : text;
|
|
97
|
+
if (normEol(disk) === normEol(checkText)) {
|
|
98
|
+
console.log('[effective-policy] ✅ 与磁盘一致(无漂移;按磁盘视图日期重渲染比对)');
|
|
99
|
+
process.exit(0);
|
|
100
|
+
}
|
|
101
|
+
console.error('[effective-policy] ❌ 漂移:policy 变更后未重生成 workflow/effective-policy.md');
|
|
102
|
+
console.error(' 修法:node .agents/scripts/gen-effective-policy.mjs && git add workflow/effective-policy.md(随 policy 变更同笔提交)');
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
fs.mkdirSync(path.dirname(OUT), { recursive: true });
|
|
106
|
+
fs.writeFileSync(OUT, text);
|
|
107
|
+
console.log(`✅ 已生成 ${path.relative(process.cwd(), OUT)}`);
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// gen-effective-policy.test.mjs — 生效规则视图生成器 fixture 套件(2026-10-13 自 Shipyard.Material
|
|
3
|
+
// 参考实现回灌时随件移植;夹具口径改 kit:注入 .agents/kit.json 的 policyVersion 取值 / 缺失 / 非法三态)
|
|
4
|
+
// 方法:临时根注入 kit.json,断言生成内容与 --check 语义。
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { spawnSync } from 'node:child_process';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
|
|
11
|
+
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const SCRIPT = path.join(SCRIPT_DIR, 'gen-effective-policy.mjs');
|
|
13
|
+
|
|
14
|
+
let pass = 0;
|
|
15
|
+
let fail = 0;
|
|
16
|
+
const check = (desc, cond, detail = '') => {
|
|
17
|
+
if (cond) { pass++; console.log(`PASS ${desc}`); }
|
|
18
|
+
else { fail++; console.log(`FAIL ${desc}${detail ? '\n ' + detail : ''}`); }
|
|
19
|
+
};
|
|
20
|
+
const mkfix = (policyVersion) => {
|
|
21
|
+
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'eff-policy-test-'));
|
|
22
|
+
fs.mkdirSync(path.join(d, '.agents'), { recursive: true });
|
|
23
|
+
if (policyVersion !== undefined) fs.writeFileSync(path.join(d, '.agents', 'kit.json'), JSON.stringify({ kit: 'agentic-flow-kit', policyVersion }));
|
|
24
|
+
return d;
|
|
25
|
+
};
|
|
26
|
+
const run = (root, ...args) => {
|
|
27
|
+
const r = spawnSync(process.execPath, [SCRIPT, '--root', root, ...args], { encoding: 'utf8' });
|
|
28
|
+
return { code: r.status, out: `${r.stdout || ''}${r.stderr || ''}` };
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// 场景 1:v8 生成——当前版本行 + 全锚键行 + 表结构(真实 v8 锚均为历史日期,待升级分支由场景 4 注入钉住)
|
|
32
|
+
{
|
|
33
|
+
const d = mkfix(8);
|
|
34
|
+
const r = run(d);
|
|
35
|
+
const doc = fs.readFileSync(path.join(d, 'workflow', 'effective-policy.md'), 'utf8');
|
|
36
|
+
check('场景 1:v8 生成成功且含版本声明与锚键行',
|
|
37
|
+
r.code === 0 && doc.includes('**policyVersion 8**') && doc.includes('`ledgerDocChainSince`') && doc.includes('`bindingTs`') && doc.includes('kit.json 声明 8'), r.out + doc);
|
|
38
|
+
}
|
|
39
|
+
// 场景 2:--check 一致 → 0;篡改磁盘 → 1;再生成 → 恢复 0
|
|
40
|
+
{
|
|
41
|
+
const d = mkfix(8);
|
|
42
|
+
run(d);
|
|
43
|
+
check('场景 2a:生成后 --check 一致', run(d, '--check').code === 0);
|
|
44
|
+
fs.appendFileSync(path.join(d, 'workflow', 'effective-policy.md'), 'drift\n');
|
|
45
|
+
const r2 = run(d, '--check');
|
|
46
|
+
check('场景 2b:磁盘漂移 → --check exit 1 且含修法', r2.code === 1 && r2.out.includes('重生成'), r2.out);
|
|
47
|
+
run(d);
|
|
48
|
+
check('场景 2c:重生成后 --check 恢复 0', run(d, '--check').code === 0);
|
|
49
|
+
}
|
|
50
|
+
// 场景 2d/2e:行尾不算漂移,内容差仍算(autocrlf 假红)
|
|
51
|
+
{
|
|
52
|
+
const d = mkfix(8);
|
|
53
|
+
run(d);
|
|
54
|
+
const p = path.join(d, 'workflow', 'effective-policy.md');
|
|
55
|
+
const lf = fs.readFileSync(p, 'utf8');
|
|
56
|
+
fs.writeFileSync(p, lf.replace(/\n/g, '\r\n'));
|
|
57
|
+
check('场景 2d:CRLF 检出、内容相同 → --check 0', run(d, '--check').code === 0);
|
|
58
|
+
fs.writeFileSync(p, lf.replace(/\n/g, '\r\n').replace('policyVersion', 'policyVerslon'));
|
|
59
|
+
const r = run(d, '--check');
|
|
60
|
+
check('场景 2e:CRLF 且内容漂移 → --check exit 1', r.code === 1 && r.out.includes('重生成'), r.out);
|
|
61
|
+
}
|
|
62
|
+
// 场景 2f:锚日翻转稳定性(源仓 2026-10-12 CI 生产事故钉:视图生成于 D1,次日系统当日 --check
|
|
63
|
+
// 按当日重渲染判「已生效」→ 与仓内「待升级」必差 → 每个未来日期锚都在锚日引爆)。
|
|
64
|
+
// 修法语义:--check 对磁盘自带「视图日期」重渲染——不依赖未来锚存在,视图日期行本身在旧实现下就会漂移。
|
|
65
|
+
{
|
|
66
|
+
const d = mkfix(8);
|
|
67
|
+
const d1 = new Date(Date.now() - 24 * 3600 * 1000).toISOString().slice(0, 10);
|
|
68
|
+
const gen = spawnSync(process.execPath, [SCRIPT, '--root', d], { encoding: 'utf8', env: { ...process.env, EFFECTIVE_POLICY_TODAY: d1 } });
|
|
69
|
+
const doc = fs.readFileSync(path.join(d, 'workflow', 'effective-policy.md'), 'utf8');
|
|
70
|
+
const chk = run(d, '--check'); // 不带 env——按系统当日跑,模拟「次日对账」
|
|
71
|
+
check('场景 2f:视图日期 D1 生成的视图,次日 --check 仍一致(按磁盘视图日期重渲染,锚日不引爆)',
|
|
72
|
+
gen.status === 0 && doc.includes(`视图日期 ${d1}`) && chk.code === 0,
|
|
73
|
+
`gen=${gen.status} docViewDate=${(doc.match(/视图日期 (\S+)/) || [])[1]} check=${chk.code}\n${chk.out}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 场景 3:非法版本号 → 回落 v1(渲染回落声明);缺 kit.json → 同 v1
|
|
77
|
+
{
|
|
78
|
+
const d = mkfix(99);
|
|
79
|
+
const r = run(d);
|
|
80
|
+
const doc = fs.readFileSync(path.join(d, 'workflow', 'effective-policy.md'), 'utf8');
|
|
81
|
+
check('场景 3a:非法 policyVersion=99 → 回落 v1 且声明回落',
|
|
82
|
+
r.code === 0 && doc.includes('**policyVersion 1**') && doc.includes('kit.json 声明 99') && doc.includes('已回落'), doc);
|
|
83
|
+
const d2 = mkfix(undefined);
|
|
84
|
+
const r2 = run(d2);
|
|
85
|
+
const doc2 = fs.readFileSync(path.join(d2, 'workflow', 'effective-policy.md'), 'utf8');
|
|
86
|
+
check('场景 3b:缺 kit.json → v1 基线', r2.code === 0 && doc2.includes('**policyVersion 1**'), doc2);
|
|
87
|
+
}
|
|
88
|
+
// 场景 4:待升级判定——不依赖墙钟:EFFECTIVE_POLICY_TODAY 注入 2026-09-24,
|
|
89
|
+
// v8 的 check14Since=2026-09-26 > 视图基准日 → 「待升级」;moduleSince=2026-09-22 ≤ → 「已生效」
|
|
90
|
+
{
|
|
91
|
+
const d = mkfix(8);
|
|
92
|
+
const r = spawnSync(process.execPath, [SCRIPT, '--root', d], { encoding: 'utf8', env: { ...process.env, EFFECTIVE_POLICY_TODAY: '2026-09-24' } });
|
|
93
|
+
const doc = fs.readFileSync(path.join(d, 'workflow', 'effective-policy.md'), 'utf8');
|
|
94
|
+
check('场景 4:升级锚按视图基准日判定(moduleSince 已生效 / check14Since 待升级)',
|
|
95
|
+
r.status === 0 && doc.includes('`check14Since` | 2026-09-26 | 检查 14 | 新 done 的 spec/plan 须有 approved git 历史(kit 口径恒 advisory 永不升级 hard) | advisory | 待升级(2026-09-26 起)')
|
|
96
|
+
&& doc.includes('`moduleSince` | 2026-09-22 | 检查 12'), doc);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.log(`\n合计: PASS ${pass} / FAIL ${fail}`);
|
|
100
|
+
process.exit(fail ? 1 : 0);
|