agentic-flow-kit 1.7.16 → 1.8.1
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 +11 -7
- package/modules/hosts/claude/commands/wf-build.md +1 -1
- package/modules/hosts/claude/commands/wf-maintain.md +1 -1
- package/modules/hosts/claude/commands/wf-new-task.md +9 -8
- 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-test.md +1 -1
- package/modules/hosts/codex/commands/wf-build.md +1 -1
- package/modules/hosts/codex/commands/wf-maintain.md +1 -1
- package/modules/hosts/codex/commands/wf-new-task.md +9 -8
- 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-test.md +1 -1
- package/modules/hosts/cursor/commands/wf-build.md +1 -1
- package/modules/hosts/cursor/commands/wf-maintain.md +1 -1
- package/modules/hosts/cursor/commands/wf-new-task.md +9 -8
- 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-test.md +1 -1
- package/modules/hosts/opencode/commands/wf-build.md +1 -1
- package/modules/hosts/opencode/commands/wf-maintain.md +1 -1
- package/modules/hosts/opencode/commands/wf-new-task.md +9 -8
- 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-test.md +1 -1
- package/modules/hosts/trae/commands/wf-build.md +1 -1
- package/modules/hosts/trae/commands/wf-maintain.md +1 -1
- package/modules/hosts/trae/commands/wf-new-task.md +9 -8
- 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-test.md +1 -1
- package/package.json +1 -1
- package/src/cli.mjs +6 -2
- package/src/doctor.mjs +11 -2
- package/src/executor.mjs +25 -8
- package/src/executor.test.mjs +4 -1
- package/src/fresh-init.test.mjs +1 -1
- package/src/init.mjs +20 -3
- package/src/profiles.mjs +7 -3
- package/src/rule-budget.test.mjs +2 -2
- package/src/stack-profile.test.mjs +6 -0
- package/src/validate.mjs +6 -1
- package/src/validate.test.mjs +1 -1
- package/templates/.gitattributes +4 -1
- package/templates/AGENTS.md +2 -2
- package/templates/_agents/commands/build.md +1 -1
- package/templates/_agents/commands/maintain.md +1 -1
- package/templates/_agents/commands/new-task.md +9 -8
- package/templates/_agents/commands/plan.md +1 -1
- package/templates/_agents/commands/review.md +2 -2
- package/templates/_agents/commands/test.md +1 -1
- package/templates/_agents/engine-lock.json +130 -42
- 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 +4 -1
- package/templates/_agents/level-hints.txt +1 -1
- 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-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 +4 -1
- 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 +2 -2
- package/templates/_agents/scripts/check-lane-surface.test.mjs +5 -5
- package/templates/_agents/scripts/check-ledger-invariant.mjs +403 -152
- package/templates/_agents/scripts/check-ledger-invariant.test.mjs +433 -3
- package/templates/_agents/scripts/check-loop-rev.test.mjs +8 -5
- package/templates/_agents/scripts/check-loop-unit.test.mjs +8 -0
- package/templates/_agents/scripts/check-loop.mjs +215 -34
- package/templates/_agents/scripts/check-loop.test.mjs +582 -49
- package/templates/_agents/scripts/check-prejudgment.mjs +64 -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 +3 -3
- package/templates/_agents/scripts/confirm-doc.mjs +114 -38
- package/templates/_agents/scripts/confirm-doc.test.mjs +180 -4
- package/templates/_agents/scripts/edit-face-check.mjs +5 -3
- package/templates/_agents/scripts/fill-intent.mjs +5 -3
- 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 +2 -2
- package/templates/_agents/scripts/fixture-scripts.test.mjs +6 -1
- package/templates/_agents/scripts/gate-checklist.mjs +6 -0
- 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 +8 -3
- package/templates/_agents/scripts/gen-workflow-dashboard.test.mjs +2 -1
- package/templates/_agents/scripts/gen-workflow-metrics.mjs +3 -3
- package/templates/_agents/scripts/git-core.mjs +45 -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-shards.mjs +307 -0
- package/templates/_agents/scripts/ledger-shards.test.mjs +339 -0
- package/templates/_agents/scripts/level-criteria.mjs +9 -10
- package/templates/_agents/scripts/level-criteria.test.mjs +7 -5
- 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 +6 -22
- package/templates/_agents/scripts/pipeline-run.test.mjs +22 -42
- package/templates/_agents/scripts/policy.mjs +28 -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 +34 -12
- 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/workflow-board-server.test.mjs +2 -2
- package/templates/_agents/scripts/workflow-enums.test.mjs +14 -5
- 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/skills/review-method/SKILL.md +55 -0
- package/templates/_agents/workflow-enums.txt +8 -1
- package/templates/_githooks/pre-commit +110 -90
- package/templates/_githooks/pre-merge-commit +27 -23
- package/templates/_githooks/pre-push +4 -3
- package/templates/_github/workflows/kit-ci.yml +4 -3
- package/templates/workflow/README.md +14 -7
- 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/templates/_agents/hooks/check-pairing-incremental.sh +0 -43
- package/templates/_agents/hooks/check-wiki-ledger.sh +0 -18
- package/templates/_agents/scripts/rule-budget.sh +0 -74
|
@@ -1,38 +1,63 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// verify.mjs —
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
// --doc
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
2
|
+
// verify.mjs — 关单/CI 统一静态门编排(GATES 表版;2026-10-13 自 Shipyard.Material 参考实现回灌,
|
|
3
|
+
// 源仓沿革留痕在包源仓 git 历史与 CHANGELOG——装户径不引实例路径)
|
|
4
|
+
// 用途:关单或 CI 前一键过门——engine cohort 七门按表执行、逐项输出 pass/fail,任一门红即 fail-fast exit 1。
|
|
5
|
+
// 语义:单源口径。本地 verify 与装户远端门(kit-ci.yml 同一条命令)行为 100% 一致。
|
|
6
|
+
// 凭证落账:全绿才落绿凭证;任一门红即落失败行(suite=门 id),供根因分析——消费方(confirm-doc 前置 /
|
|
7
|
+
// check-loop 检查 8)只认 exitCode===0 的行,失败行零污染凭证判定。
|
|
8
|
+
// 七门照搬源仓 engine cohort(不搬 product cohort——frontend/backend 门是源仓项目私有;lane-surface-ci 门
|
|
9
|
+
// 依赖 kit 侧 --ci 支持核实,留后续批次评估)。源仓 --engine 增量语义不移植:本表已全为引擎段,
|
|
10
|
+
// 「部分绿 ≠ 全量凭证」的分野不存在。
|
|
11
|
+
// kit 特有语义(沿现口径保留):
|
|
12
|
+
// a) 装户仓判定——cwd 无 package.json → engine-tests 门显式跳过(跳过≠通过,不计入门数)且**不落
|
|
13
|
+
// 测试绿凭证**(凭证语义 = 测试真跑绿,防假绿),提示 --test-cmd;
|
|
14
|
+
// b) 凭证行形状 = {ts, exitCode:0, suite:'npm test', gates, headShort?, passed?, failed?, doc?, runId?}
|
|
15
|
+
// (gates/headShort 自源仓回灌新增——非 git 环境解析失败则省略、不伪造;消费方只读 ts/exitCode/doc,
|
|
16
|
+
// 增量字段零影响);计数捕获只认编排器权威行 `[run-tests] 合计: SUITES n / FAILED m`
|
|
17
|
+
// (嵌套套件同形行不回退——宁缺勿错,exitCode 才是硬事实);
|
|
18
|
+
// c) --doc 绑定语义保留(归一基准 = ROOT,与 confirm-doc / 检查 8 三处同源);
|
|
19
|
+
// d) --test-cmd 为 fixture 测试注入缝:替换全部生产门为 [test, check-loop] 两门夹具(夹具根里没有
|
|
20
|
+
// 脚本本体,check-loop 用绝对路径直调,root 归属仍由 CHECK_LOOP_ROOT 裁决);夹具模式不触发生效规则
|
|
21
|
+
// 视图门与摘要生成(假门凭证只进本地台账,不产 committed 摘要)。
|
|
22
|
+
// 未执行门清单:fail-fast 后序门状态未知≠通过——验证系统输出须完备(「没跑」与「过了」可区分)。
|
|
23
|
+
// 生效规则视图门(effective-policy --check):policy 变更后必须重生成 workflow/effective-policy.md 随同
|
|
24
|
+
// 提交——新装户由 init 生成器首跑种子该文件,缺文件对 --check 是漂移(红),不静默放行。
|
|
25
|
+
// 绿凭证摘要(gen-verify-evidence.mjs):全绿尾部自动重生成 workflow/verify-evidence.md——全绿事实与
|
|
26
|
+
// 仓内摘要同刻生成,消除「本地台账已绿、committed 摘要滞后」的双真相分叉;生成失败响亮告警但不推翻绿裁决。
|
|
27
|
+
// 用法:node .agents/scripts/verify.mjs [--list] [--test-cmd "<shell 命令>"] [--doc <本单 workflow 路径>]
|
|
28
|
+
// --list:按执行序打印门 id 后退出,不执行(与 --test-cmd 互斥)。
|
|
29
|
+
// --test-cmd 用于测试注入,或在无根 package.json 的装户仓指定项目测试命令;
|
|
30
|
+
// --doc 把凭证绑到本单,如 --doc workflow/intents/<日期>-<主题>.md
|
|
31
|
+
// 测试:node templates/_agents/scripts/verify.test.mjs(--test-cmd 注入假命令 + CHECK_LOOP_ROOT 注入夹具)
|
|
25
32
|
import fs from 'node:fs';
|
|
26
33
|
import { spawnSync } from 'node:child_process';
|
|
27
34
|
import path from 'node:path';
|
|
28
35
|
import { fileURLToPath } from 'node:url';
|
|
29
36
|
|
|
30
37
|
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
38
|
+
const ROOT = process.env.CHECK_LOOP_ROOT || path.resolve(SCRIPT_DIR, '..', '..');
|
|
39
|
+
|
|
40
|
+
// --- 门禁定义(engine cohort 七门,序即执行序;遇红即停) ---
|
|
41
|
+
const GATES = [
|
|
42
|
+
{ id: 'scripts-syntax', name: '引擎脚本语法自检', cmd: 'node .agents/scripts/check-scripts-syntax.mjs', folder: '.', type: 'shell' },
|
|
43
|
+
{ id: 'engine-tests', name: '测试门 (npm test)', cmd: 'npm test', folder: '.', type: 'shell' },
|
|
44
|
+
{ id: 'workflows-check', name: '编排脚本检查', cmd: 'node .agents/scripts/workflows-check.mjs', folder: '.', type: 'shell' },
|
|
45
|
+
{ id: 'rule-budget', name: '常驻面预算检查', cmd: 'node .agents/scripts/rule-budget.mjs --all', folder: '.', type: 'shell' },
|
|
46
|
+
{ id: 'effective-policy', name: '生效规则视图漂移检查', cmd: 'node .agents/scripts/gen-effective-policy.mjs --check', folder: '.', type: 'shell' },
|
|
47
|
+
// 默认硬门。卫生段不进关单(2026-10-13 B6b-1 分道随源仓口径):要看时另跑 check-loop.mjs --hygiene。
|
|
48
|
+
{ id: 'check-loop', name: '闭环门禁校验', cmd: 'node .agents/scripts/check-loop.mjs', folder: '.', type: 'shell' },
|
|
49
|
+
{ id: 'ledger-invariant', name: '确认台账不变量(历史全扫)', cmd: 'node .agents/scripts/check-ledger-invariant.mjs', folder: '.', type: 'shell' },
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
// fixture 夹具门(--test-cmd 注入时替换全部生产门,verify.test.mjs 断言依赖此口径)
|
|
53
|
+
// check-loop 用绝对路径直调真实脚本——夹具根里没有脚本本体,root 归属仍由 CHECK_LOOP_ROOT 裁决。
|
|
54
|
+
const fixtureGates = (cmd) => [
|
|
55
|
+
{ id: 'test', name: '测试步(注入)', cmd, folder: '.', type: 'shell' },
|
|
56
|
+
{ id: 'check-loop', name: '闭环门禁校验', cmd: `node "${path.join(SCRIPT_DIR, 'check-loop.mjs')}"`, folder: '.', type: 'shell' },
|
|
57
|
+
];
|
|
58
|
+
|
|
31
59
|
const args = process.argv.slice(2);
|
|
32
60
|
const explicitTestCmd = args.includes('--test-cmd') ? args[args.indexOf('--test-cmd') + 1] : null;
|
|
33
|
-
const testCmd = explicitTestCmd ?? 'npm test';
|
|
34
|
-
const checkLoop = path.join(SCRIPT_DIR, 'check-loop.mjs');
|
|
35
|
-
const ROOT = process.env.CHECK_LOOP_ROOT || path.resolve(SCRIPT_DIR, '..', '..');
|
|
36
61
|
|
|
37
62
|
// --doc 解析(沿 --test-cmd 同款取下一 argv 的写法;缺值报用法错误 exit 1 与 --test-cmd 对称)。
|
|
38
63
|
// 归一基准 = **ROOT**(不是 cwd)——消费方两处都以仓库根为基准(check-loop 检查 8 用
|
|
@@ -50,50 +75,89 @@ const docRel = docArg
|
|
|
50
75
|
? path.relative(ROOT, path.resolve(ROOT, docArg)).split(path.sep).join('/')
|
|
51
76
|
: null;
|
|
52
77
|
|
|
53
|
-
//
|
|
54
|
-
//
|
|
78
|
+
// 装户仓判定:engine-tests 门只在**当前目录**有 package.json 时才跑(npm test 亦在该目录执行)。
|
|
79
|
+
// 跳过时后续不落测试绿凭证(见全绿落账段)。显式 --test-cmd 不受此判定影响。
|
|
55
80
|
const hasRootPackageJson = fs.existsSync(path.join(process.cwd(), 'package.json'));
|
|
56
81
|
const skipTest = explicitTestCmd === null && !hasRootPackageJson;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
console.log(`[verify] ▶ 1/2 ${testCmd}`);
|
|
63
|
-
const t = spawnSync(testCmd, { shell: true, encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 });
|
|
64
|
-
if (t.stdout) process.stdout.write(t.stdout);
|
|
65
|
-
if (t.stderr) process.stderr.write(t.stderr);
|
|
66
|
-
if (t.status !== 0) {
|
|
67
|
-
console.error(`[verify] ❌ 1/2 ${testCmd} 未通过(exit ${t.status ?? t.error?.code})——修复后重跑,非绿不关单`);
|
|
82
|
+
const gates = explicitTestCmd ? fixtureGates(explicitTestCmd) : GATES;
|
|
83
|
+
|
|
84
|
+
if (args.includes('--list')) {
|
|
85
|
+
if (explicitTestCmd) {
|
|
86
|
+
console.error('[verify] --list 与 --test-cmd 互斥');
|
|
68
87
|
process.exit(1);
|
|
69
88
|
}
|
|
70
|
-
|
|
71
|
-
|
|
89
|
+
for (const g of gates) console.log(g.id);
|
|
90
|
+
process.exit(0);
|
|
72
91
|
}
|
|
73
92
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
93
|
+
// 失败留痕:失败行 {ts, exitCode, suite:门 id, note}——append-only 事实记录,供根因分析;
|
|
94
|
+
// 消费方只认 exitCode===0 的绿行,失败行零污染凭证判定。IO 失败出账不阻断。
|
|
95
|
+
const recordFailure = (step, exitInfo, note) => {
|
|
96
|
+
try {
|
|
97
|
+
const entry = { ts: new Date().toISOString(), exitCode: typeof exitInfo === 'number' ? exitInfo : -1, suite: step, note };
|
|
98
|
+
if (process.env.PIPELINE_RUN_ID) entry.runId = process.env.PIPELINE_RUN_ID;
|
|
99
|
+
fs.mkdirSync(path.join(ROOT, '.agents'), { recursive: true });
|
|
100
|
+
fs.appendFileSync(path.join(ROOT, '.agents', 'verifications.jsonl'), `${JSON.stringify(entry)}\n`);
|
|
101
|
+
console.log(`[verify] 🧾 失败行已留痕 verifications.jsonl(suite=${step})`);
|
|
102
|
+
} catch (e) {
|
|
103
|
+
console.error(`[verify] ⚠️ 失败行落账失败(${e.code || e.message})`);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
console.log(`[verify] 🚀 开始静态门验证 (共 ${gates.length} 项)${explicitTestCmd ? '【fixture 模式】' : ''}\n`);
|
|
108
|
+
|
|
109
|
+
let passedCount = 0;
|
|
110
|
+
const outputs = {};
|
|
111
|
+
|
|
112
|
+
for (let i = 0; i < gates.length; i++) {
|
|
113
|
+
const gate = gates[i];
|
|
114
|
+
// 装户仓:engine-tests 门显式跳过——跳过≠通过,不计入 passedCount、不算绿(全绿落账段据此不落凭证)
|
|
115
|
+
if (skipTest && gate.id === 'engine-tests') {
|
|
116
|
+
console.log(`[verify] ⏭ ${i + 1}/${gates.length} 跳过 ${gate.cmd}:当前目录无 package.json(装户仓)——请按项目口径手工跑测试,或用 --test-cmd "<项目测试命令>" 指定后重跑`);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
console.log(`[verify] ▶ ${i + 1}/${gates.length} ${gate.name}...`);
|
|
120
|
+
|
|
121
|
+
const result = spawnSync(gate.cmd, {
|
|
122
|
+
shell: true,
|
|
123
|
+
encoding: 'utf8',
|
|
124
|
+
cwd: path.join(ROOT, gate.folder),
|
|
125
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
129
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
130
|
+
|
|
131
|
+
if (result.status !== 0) {
|
|
132
|
+
const note = `exit ${result.status ?? result.error?.code}`;
|
|
133
|
+
console.error(`[verify] ❌ ${i + 1}/${gates.length} ${gate.name} 未通过 (${note})`);
|
|
134
|
+
// 未执行门清单:fail-fast 后序门状态未知≠通过——「没跑」在台账里与「过了」不可区分即输出不完备
|
|
135
|
+
const remaining = gates.slice(i + 1).map((g) => g.id);
|
|
136
|
+
if (remaining.length) console.error(`[verify] ⏭️ 未执行门(fail-fast,状态未知):${remaining.join('、')}`);
|
|
137
|
+
recordFailure(gate.id, result.status, note);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
outputs[gate.id] = result.stdout || '';
|
|
141
|
+
console.log(`[verify] ✅ ${i + 1}/${gates.length} ${gate.name} 通过\n`);
|
|
142
|
+
passedCount++;
|
|
79
143
|
}
|
|
80
|
-
console.log('[verify] ✅ 2/2 闭环校验通过');
|
|
81
144
|
|
|
82
|
-
//
|
|
145
|
+
// 凭证落账:全部门绿才落(任一门红已在上面 fail-fast 退出);**测试门被跳过同样不落**
|
|
83
146
|
// (测试未跑即无「测试绿」事实,落账即假绿);IO 失败出账不阻断
|
|
84
147
|
try {
|
|
85
148
|
if (skipTest) {
|
|
86
|
-
console.log('[verify] ⚠️
|
|
149
|
+
console.log('[verify] ⚠️ 测试门已跳过——本次不落测试绿凭证(凭证语义 = 测试真跑绿)');
|
|
87
150
|
} else {
|
|
88
|
-
// 计数行**只认编排器权威前缀**(2026-10-09 审查 P0-2):此前是 pop 通配「合计: PASS n / FAIL n」,
|
|
89
|
-
// 而全仓 45+ 套件每个都打印自己那行同形文本 → 取到的是字母序最后一个套件的场景数
|
|
90
|
-
// (本仓实况:workflows-check.test.mjs 的 15),落进「一行机器事实」的 passed 字段。
|
|
91
|
-
// 改为精确匹配 run-tests.mjs 的专属行 + 套件口径:嵌套套件同形行不再被误取,
|
|
92
|
-
// 非本编排器口径的 --test-cmd(装户项目自有测试命令)抓不到即省略计数键,不回退到错的事实。
|
|
93
|
-
const m = [...testStdout.matchAll(/\[run-tests\] 合计: SUITES (\d+) \/ FAILED (\d+)/g)].pop();
|
|
94
151
|
const entry = { ts: new Date().toISOString(), exitCode: 0, suite: 'npm test' };
|
|
95
|
-
//
|
|
96
|
-
|
|
152
|
+
// 门数:凭证明此记录「过全量门禁全绿」是几门(fixture 模式 = 2 门夹具,仍如实记)
|
|
153
|
+
entry.gates = passedCount;
|
|
154
|
+
// HEAD 短 SHA 钉进绿行本体(源仓回灌):确定性证据此前只存在于测试夹具、绿行无 SHA 可锚定验证对象;
|
|
155
|
+
// 非 git 环境(fixture 根在 os.tmpdir)解析失败则省略该键,不伪造。
|
|
156
|
+
const rev = spawnSync('git', ['rev-parse', '--short', 'HEAD'], { encoding: 'utf8', cwd: ROOT });
|
|
157
|
+
if (rev.status === 0 && rev.stdout) entry.headShort = rev.stdout.trim();
|
|
158
|
+
// 计数行**只认编排器权威前缀**:嵌套套件同形行不回退——非本编排器口径的 --test-cmd
|
|
159
|
+
//(装户项目自有测试命令)抓不到即省略计数键,不回退到错的事实(宁缺勿错,exitCode 才是硬事实)。
|
|
160
|
+
const m = [...(outputs['engine-tests'] || outputs['test'] || '').matchAll(/\[run-tests\] 合计: SUITES (\d+) \/ FAILED (\d+)/g)].pop();
|
|
97
161
|
if (m) { entry.passed = Number(m[1]); entry.failed = Number(m[2]); }
|
|
98
162
|
if (process.env.PIPELINE_RUN_ID) entry.runId = process.env.PIPELINE_RUN_ID;
|
|
99
163
|
// doc 键:未传 --doc 时**不写该键**(不是写 null)——消费方按 doc 精确匹配,undefined 与 null 都恒不命中,
|
|
@@ -106,4 +170,17 @@ try {
|
|
|
106
170
|
} catch (e) {
|
|
107
171
|
console.error(`[verify] ⚠️ 凭证落账失败(${e.code || e.message})——不阻断,关单门前置将按无凭证告警`);
|
|
108
172
|
}
|
|
109
|
-
|
|
173
|
+
|
|
174
|
+
// 摘要事件原子化:全绿事实与仓内摘要(workflow/verify-evidence.md)同刻生成,消除「本地台账已绿、
|
|
175
|
+
// committed 摘要滞后」的双真相分叉。仅生产模式(fixture 假门凭证不产 committed 摘要;测试门跳过时
|
|
176
|
+
// 本就无凭证);生成失败响亮告警但不推翻绿裁决(同落账口径)。
|
|
177
|
+
if (!explicitTestCmd && !skipTest) {
|
|
178
|
+
const gen = spawnSync(process.execPath, [path.join(SCRIPT_DIR, 'gen-verify-evidence.mjs'), '--root', ROOT], { encoding: 'utf8' });
|
|
179
|
+
if (gen.status === 0) {
|
|
180
|
+
if (gen.stdout) process.stdout.write(String(gen.stdout).trimEnd() + '\n');
|
|
181
|
+
} else {
|
|
182
|
+
console.error(`[verify] ⚠️ 绿凭证摘要生成失败(${String(gen.stderr || gen.stdout || '').trim().slice(0, 200)})——绿裁决不变,手动补跑 node .agents/scripts/gen-verify-evidence.mjs`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
console.log(`\n[verify] 🎉 全绿——可以关单(共 ${passedCount} 项通过;逐条勾验验收标准补证据后 intent/plan → done)`);
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// verify.mjs 的 fixture 测试(--test-cmd 注入假命令 + CHECK_LOOP_ROOT 注入夹具根,不触真实 npm test 与 workflow)
|
|
3
|
-
// 断言:①绿路径(假过 + 空夹具闭环)exit 0
|
|
4
|
-
// ③步骤 2 配对断裂夹具 exit 1(verify
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// ⑧--doc
|
|
3
|
+
// 断言:①绿路径(假过 + 空夹具闭环)exit 0;②步骤假败 exit 1 且失败行留痕(suite=test);
|
|
4
|
+
// ③步骤 2 配对断裂夹具 exit 1(verify 自身标记断言);④凭证落账(绿行 + 计数捕获);
|
|
5
|
+
// ④b fixture 绿不产 committed 摘要;⑤check-loop 红失败行留痕、无绿行;
|
|
6
|
+
// ⑥无汇总行省略计数键;⑦装户仓(cwd 无 package.json)跳过测试门、不落凭证、其余门全过 → exit 0;
|
|
7
|
+
// ⑧--doc 绑定五态;⑨⑩嵌套套件同形汇总行不得被误取;⑪--list 七门序;⑫--list 与 --test-cmd 互斥;
|
|
8
|
+
// ⑬生产模式中段红 → fail-fast + 未执行门清单 + 失败行留痕;
|
|
9
|
+
// ⑭生产模式全绿端到端(七门 + 凭证含门数/headShort/套件计数 + 绿凭证摘要生成)。
|
|
10
|
+
// 生产模式夹具经 fixture-scripts.mjs#copyScriptClosure 把门脚本依赖闭包拷进临时根(并种子
|
|
11
|
+
// workflow/effective-policy.md——生效规则视图门对缺文件判漂移,与 init 生成器首跑同款)。
|
|
8
12
|
// 用法:node templates/_agents/scripts/verify.test.mjs
|
|
9
13
|
import fs from 'node:fs';
|
|
10
14
|
import os from 'node:os';
|
|
11
15
|
import path from 'node:path';
|
|
12
16
|
import { spawnSync } from 'node:child_process';
|
|
13
17
|
import { fileURLToPath } from 'node:url';
|
|
18
|
+
import { copyScriptClosure } from './fixture-scripts.mjs';
|
|
14
19
|
|
|
15
20
|
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
16
21
|
const VERIFY = path.join(SCRIPT_DIR, 'verify.mjs');
|
|
@@ -35,7 +40,7 @@ const mkfix = () => {
|
|
|
35
40
|
'doc.status.abandoned=superseded cancelled',
|
|
36
41
|
'incident.status.all=open fixed closed',
|
|
37
42
|
'incident.status.active=open',
|
|
38
|
-
'level.all=
|
|
43
|
+
'level.all=L1 L2 L3',
|
|
39
44
|
'',
|
|
40
45
|
].join('\n'), 'utf8');
|
|
41
46
|
return d;
|
|
@@ -57,6 +62,33 @@ const mkCmd = (code, out = '') => {
|
|
|
57
62
|
};
|
|
58
63
|
const run = (argv, opts = {}) => spawnSync(process.execPath, [VERIFY, ...argv], { encoding: 'utf8', ...opts });
|
|
59
64
|
|
|
65
|
+
// 生产模式夹具:门脚本依赖闭包拷贝 + .agents/hooks 目录(语法门 fail-closed 要求)+
|
|
66
|
+
// workflow/effective-policy.md 种子(生效规则视图门对缺文件判漂移——与 init §6 生成器首跑同款)
|
|
67
|
+
const provision = (root) => {
|
|
68
|
+
fs.mkdirSync(path.join(root, '.agents', 'hooks'), { recursive: true });
|
|
69
|
+
copyScriptClosure(path.join(root, '.agents', 'scripts'), [
|
|
70
|
+
'check-scripts-syntax.mjs', 'workflows-check.mjs', 'rule-budget.mjs',
|
|
71
|
+
'gen-effective-policy.mjs', 'check-ledger-invariant.mjs', 'check-loop.mjs',
|
|
72
|
+
]);
|
|
73
|
+
const gen = spawnSync(process.execPath, [path.join(root, '.agents', 'scripts', 'gen-effective-policy.mjs'), '--root', root], { encoding: 'utf8' });
|
|
74
|
+
if (gen.status !== 0) throw new Error(`夹具种子生成失败:${gen.stdout}${gen.stderr}`);
|
|
75
|
+
return root;
|
|
76
|
+
};
|
|
77
|
+
// 生产夹具变 git 仓(台账不变量门在非 git 根会 fail-closed;commit 供凭证 headShort 钉 SHA)
|
|
78
|
+
const gitify = (root) => {
|
|
79
|
+
const g = (a) => spawnSync('git', ['-C', root, ...a], { encoding: 'utf8' });
|
|
80
|
+
g(['init']);
|
|
81
|
+
g(['-c', 'user.email=fixture@test', '-c', 'user.name=fixture', 'add', '-A']);
|
|
82
|
+
const c = g(['-c', 'user.email=fixture@test', '-c', 'user.name=fixture', 'commit', '-m', 'fixture init', '--no-gpg-sign', '-q']);
|
|
83
|
+
if (c.status !== 0) throw new Error(`夹具 git commit 失败:${c.stderr}`);
|
|
84
|
+
return g(['rev-parse', '--short', 'HEAD']).stdout.trim();
|
|
85
|
+
};
|
|
86
|
+
const readLedger = (root) => {
|
|
87
|
+
const p = path.join(root, '.agents', 'verifications.jsonl');
|
|
88
|
+
if (!fs.existsSync(p)) return [];
|
|
89
|
+
return fs.readFileSync(p, 'utf8').split(/\r?\n/).filter(Boolean).map((l) => JSON.parse(l));
|
|
90
|
+
};
|
|
91
|
+
|
|
60
92
|
// ---- 场景 1:绿路径——假过 + 空夹具闭环 → exit 0 ----
|
|
61
93
|
{
|
|
62
94
|
const root = mkfix();
|
|
@@ -66,12 +98,16 @@ const run = (argv, opts = {}) => spawnSync(process.execPath, [VERIFY, ...argv],
|
|
|
66
98
|
fs.rmSync(root, { recursive: true, force: true });
|
|
67
99
|
}
|
|
68
100
|
|
|
69
|
-
// ---- 场景 2
|
|
101
|
+
// ---- 场景 2:红路径测试门——假败 → exit 1、fail-fast 不出全绿;失败行留痕 ----
|
|
70
102
|
{
|
|
71
103
|
const root = mkfix();
|
|
72
104
|
const r = run(['--test-cmd', `node ${mkCmd(3)}`], { env: { ...process.env, CHECK_LOOP_ROOT: root } });
|
|
73
105
|
check('场景 2:exit 1', r.status === 1, `exit=${r.status}\n${r.stdout}\n${r.stderr}`);
|
|
74
|
-
check('场景 2
|
|
106
|
+
check('场景 2:步骤失败标记在 stderr,且不输出全绿', r.stderr.includes('❌ 1/2') && !r.stdout.includes('全绿'), `${r.stdout}\n${r.stderr}`);
|
|
107
|
+
const lines = readLedger(root);
|
|
108
|
+
check('场景 2:测试门红 → 失败行留痕(exitCode 3 + suite=test)',
|
|
109
|
+
lines.length === 1 && lines[0].exitCode === 3 && lines[0].suite === 'test' && typeof lines[0].ts === 'string',
|
|
110
|
+
`lines=${JSON.stringify(lines)}`);
|
|
75
111
|
fs.rmSync(root, { recursive: true, force: true });
|
|
76
112
|
}
|
|
77
113
|
|
|
@@ -84,65 +120,75 @@ const run = (argv, opts = {}) => spawnSync(process.execPath, [VERIFY, ...argv],
|
|
|
84
120
|
fs.rmSync(root, { recursive: true, force: true });
|
|
85
121
|
}
|
|
86
122
|
|
|
87
|
-
// ---- 场景 4
|
|
123
|
+
// ---- 场景 4:绿路径凭证落账——全绿后 fixture 台账出合法绿行(计数捕获 + testCmd 注入输出口径) ----
|
|
88
124
|
{
|
|
89
125
|
const root = mkfix();
|
|
90
126
|
const r = run(['--test-cmd', `node ${mkCmd(0, '[run-tests] 合计: SUITES 3 / FAILED 0\n')}`], { env: { ...process.env, CHECK_LOOP_ROOT: root } });
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
check('场景 4:全绿 → verifications.jsonl 落绿行(exitCode 0 + 计数捕获)',
|
|
127
|
+
const line = readLedger(root)[0] || null;
|
|
128
|
+
check('场景 4:全绿 → verifications.jsonl 落绿行(exitCode 0 + 计数捕获 + 门数)',
|
|
94
129
|
r.status === 0 && line !== null && line.exitCode === 0 && line.passed === 3 && line.failed === 0
|
|
95
|
-
&& typeof line.ts === 'string' && r.stdout.includes('凭证已落账'),
|
|
130
|
+
&& line.gates === 2 && typeof line.ts === 'string' && line.suite === 'npm test' && r.stdout.includes('凭证已落账'),
|
|
96
131
|
`exit=${r.status} line=${JSON.stringify(line)}`);
|
|
97
132
|
fs.rmSync(root, { recursive: true, force: true });
|
|
98
133
|
}
|
|
99
134
|
|
|
100
|
-
// ---- 场景
|
|
135
|
+
// ---- 场景 4b:fixture 全绿不得触发摘要生成(假门凭证只进本地台账,不产 committed 摘要) ----
|
|
136
|
+
{
|
|
137
|
+
const root = mkfix();
|
|
138
|
+
const r = run(['--test-cmd', `node ${mkCmd(0, '[run-tests] 合计: SUITES 3 / FAILED 0\n')}`], { env: { ...process.env, CHECK_LOOP_ROOT: root } });
|
|
139
|
+
const ev = path.join(root, 'workflow', 'verify-evidence.md');
|
|
140
|
+
check('场景 4b:fixture 绿 → 不生成 workflow/verify-evidence.md、输出无摘要生成标记',
|
|
141
|
+
r.status === 0 && !fs.existsSync(ev) && !r.stdout.includes('已生成'),
|
|
142
|
+
`exit=${r.status} exists=${fs.existsSync(ev)}\n${r.stdout}`);
|
|
143
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ---- 场景 5:check-loop 红 → 失败行留痕、无绿行(失败也留痕;绿行语义 = verify 全绿不变) ----
|
|
101
147
|
{
|
|
102
148
|
const root = mkBroken();
|
|
103
149
|
const r = run(['--test-cmd', `node ${mkCmd(0)}`], { env: { ...process.env, CHECK_LOOP_ROOT: root } });
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
150
|
+
const lines = readLedger(root);
|
|
151
|
+
check('场景 5:测试门绿但 check-loop 红 → 仅失败行(suite=check-loop),无绿行',
|
|
152
|
+
r.status === 1 && lines.length === 1 && lines[0].exitCode !== 0 && lines[0].suite === 'check-loop'
|
|
153
|
+
&& !lines.some((l) => l.exitCode === 0),
|
|
154
|
+
`exit=${r.status} lines=${JSON.stringify(lines)}`);
|
|
107
155
|
fs.rmSync(root, { recursive: true, force: true });
|
|
108
156
|
}
|
|
109
157
|
|
|
110
|
-
// ---- 场景 6:输出无汇总行 → 计数键省略(exitCode
|
|
158
|
+
// ---- 场景 6:输出无汇总行 → 计数键省略(exitCode 才是硬事实) ----
|
|
111
159
|
{
|
|
112
160
|
const root = mkfix();
|
|
113
161
|
const r = run(['--test-cmd', `node ${mkCmd(0, 'ok\n')}`], { env: { ...process.env, CHECK_LOOP_ROOT: root } });
|
|
114
|
-
const
|
|
115
|
-
const line = fs.existsSync(vp) ? JSON.parse(fs.readFileSync(vp, 'utf8').trim()) : null;
|
|
162
|
+
const line = readLedger(root)[0] || null;
|
|
116
163
|
check('场景 6:无汇总输出 → 绿行落盘但计数键省略',
|
|
117
164
|
r.status === 0 && line !== null && line.exitCode === 0 && !('passed' in line) && !('failed' in line),
|
|
118
165
|
`line=${JSON.stringify(line)}`);
|
|
119
166
|
fs.rmSync(root, { recursive: true, force: true });
|
|
120
167
|
}
|
|
121
168
|
|
|
122
|
-
// ---- 场景 7:装户仓(cwd 无 package.json)且未给 --test-cmd →
|
|
169
|
+
// ---- 场景 7:装户仓(cwd 无 package.json)且未给 --test-cmd → 跳过测试门、不落凭证、其余门全过 → exit 0 ----
|
|
123
170
|
{
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
r.stdout)
|
|
132
|
-
|
|
133
|
-
|
|
171
|
+
const root = provision(mkfix());
|
|
172
|
+
gitify(root);
|
|
173
|
+
const bare = fs.mkdtempSync(path.join(os.tmpdir(), 'verify-bare-'));
|
|
174
|
+
const r = run([], { cwd: bare, env: { ...process.env, CHECK_LOOP_ROOT: root } });
|
|
175
|
+
check('场景 7:exit 0', r.status === 0, `exit=${r.status}\n${r.stdout}\n${r.stderr}`);
|
|
176
|
+
check('场景 7:测试门显式跳过(⏭ 2/7)、末门通过且全绿', r.stdout.includes('⏭ 2/7') && r.stdout.includes('✅ 7/7') && r.stdout.includes('全绿'), r.stdout);
|
|
177
|
+
check('场景 7:跳过时不落测试绿凭证(防假绿)且不生成摘要',
|
|
178
|
+
readLedger(root).length === 0 && !fs.existsSync(path.join(root, 'workflow', 'verify-evidence.md')) && r.stdout.includes('不落测试绿凭证'),
|
|
179
|
+
r.stdout);
|
|
180
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
181
|
+
fs.rmSync(bare, { recursive: true, force: true });
|
|
134
182
|
}
|
|
135
183
|
|
|
136
|
-
// ---- 场景 8:--doc
|
|
184
|
+
// ---- 场景 8:--doc 凭证绑定 ----
|
|
137
185
|
// ① 带 --doc → 末行含 doc 且为规范化 posix 相对路径;② 不带 → 末行**无 doc 键**(不是 null);
|
|
138
186
|
// ③ win32 反斜杠 / posix / 绝对路径三种写法落同一值(归一是本单的地基,错一处=凭证永远匹配不上);
|
|
139
|
-
// ④ 缺值 → exit 1 用法错误;⑤
|
|
187
|
+
// ④ 缺值 → exit 1 用法错误;⑤ 跳过测试门时即便带 --doc 仍不落账(防假绿语义不得被 --doc 破坏)。
|
|
140
188
|
{
|
|
141
189
|
const readLast = (root) => {
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
const lines = fs.readFileSync(p, 'utf8').trim().split('\n').filter(Boolean);
|
|
145
|
-
return lines.length ? JSON.parse(lines[lines.length - 1]) : null;
|
|
190
|
+
const lines = readLedger(root);
|
|
191
|
+
return lines.length ? lines[lines.length - 1] : null;
|
|
146
192
|
};
|
|
147
193
|
|
|
148
194
|
const rootA = mkfix();
|
|
@@ -175,27 +221,27 @@ const run = (argv, opts = {}) => spawnSync(process.execPath, [VERIFY, ...argv],
|
|
|
175
221
|
check('场景 8④:--doc 缺值 → exit 1 + 用法提示',
|
|
176
222
|
rD.status === 1 && /--doc 缺值/.test(rD.stderr), `exit=${rD.status} ${rD.stderr}`);
|
|
177
223
|
|
|
178
|
-
const rootE = mkfix();
|
|
224
|
+
const rootE = provision(mkfix());
|
|
225
|
+
gitify(rootE);
|
|
179
226
|
const bare = fs.mkdtempSync(path.join(os.tmpdir(), 'verify-bare-doc-'));
|
|
180
227
|
const rE = run(['--doc', rel], { cwd: bare, env: { ...process.env, CHECK_LOOP_ROOT: rootE } });
|
|
181
|
-
check('场景 8
|
|
182
|
-
rE.status === 0 &&
|
|
228
|
+
check('场景 8⑤:测试门跳过时即便带 --doc 仍不落账(防假绿语义不被 --doc 破坏)',
|
|
229
|
+
rE.status === 0 && readLedger(rootE).length === 0 && rE.stdout.includes('不落测试绿凭证'),
|
|
183
230
|
`exit=${rE.status} ${rE.stdout}`);
|
|
184
231
|
|
|
185
232
|
for (const r of [rootA, rootB, rootC, rootD, rootE]) fs.rmSync(r, { recursive: true, force: true });
|
|
186
233
|
fs.rmSync(bare, { recursive: true, force: true });
|
|
187
234
|
}
|
|
188
235
|
|
|
189
|
-
// ---- 场景 9 / 10
|
|
190
|
-
// 缺陷复现:旧判据 pop 通配「合计: PASS n / FAIL n
|
|
191
|
-
//
|
|
236
|
+
// ---- 场景 9 / 10:嵌套套件同形汇总行不得被误取(计数口径=编排器权威套件数,回归锁) ----
|
|
237
|
+
// 缺陷复现:旧判据 pop 通配「合计: PASS n / FAIL n」,而全套件每个都打印自己那行同形文本,
|
|
238
|
+
// 取到的是字母序最后一个套件的场景数,落进凭证 passed。
|
|
192
239
|
// ⑨ 权威行与同形行并存 → 取权威套件数;⑩ 仅有同形行 → 省略计数键(宁缺勿错,不回退到错的事实)。
|
|
193
240
|
{
|
|
194
241
|
const root9 = mkfix();
|
|
195
242
|
const r9 = run(['--test-cmd', `node ${mkCmd(0, '合计: PASS 15 / FAIL 0\n[run-tests] 合计: SUITES 45 / FAILED 0\n')}`],
|
|
196
243
|
{ env: { ...process.env, CHECK_LOOP_ROOT: root9 } });
|
|
197
|
-
const
|
|
198
|
-
const l9 = fs.existsSync(vp9) ? JSON.parse(fs.readFileSync(vp9, 'utf8').trim()) : null;
|
|
244
|
+
const l9 = readLedger(root9)[0] || null;
|
|
199
245
|
check('场景 9:嵌套套件同形行不被误取,计数取编排器权威套件数',
|
|
200
246
|
r9.status === 0 && l9 !== null && l9.passed === 45 && l9.failed === 0,
|
|
201
247
|
`line=${JSON.stringify(l9)}`);
|
|
@@ -204,14 +250,77 @@ const run = (argv, opts = {}) => spawnSync(process.execPath, [VERIFY, ...argv],
|
|
|
204
250
|
const root10 = mkfix();
|
|
205
251
|
const r10 = run(['--test-cmd', `node ${mkCmd(0, '合计: PASS 15 / FAIL 0\n')}`],
|
|
206
252
|
{ env: { ...process.env, CHECK_LOOP_ROOT: root10 } });
|
|
207
|
-
const
|
|
208
|
-
const l10 = fs.existsSync(vp10) ? JSON.parse(fs.readFileSync(vp10, 'utf8').trim()) : null;
|
|
253
|
+
const l10 = readLedger(root10)[0] || null;
|
|
209
254
|
check('场景 10:仅有嵌套同形行 → 绿行落盘但省略计数键(不回退到错的事实)',
|
|
210
255
|
r10.status === 0 && l10 !== null && l10.exitCode === 0 && !('passed' in l10) && !('failed' in l10),
|
|
211
256
|
`line=${JSON.stringify(l10)}`);
|
|
212
257
|
fs.rmSync(root10, { recursive: true, force: true });
|
|
213
258
|
}
|
|
214
259
|
|
|
260
|
+
// ---- 场景 11:--list 锁定 engine cohort 七门序,不执行门 ----
|
|
261
|
+
{
|
|
262
|
+
const expect = ['scripts-syntax', 'engine-tests', 'workflows-check', 'rule-budget', 'effective-policy', 'check-loop', 'ledger-invariant'];
|
|
263
|
+
const r = run(['--list'], {});
|
|
264
|
+
const ids = (r.stdout || '').trim().split(/\r?\n/).filter(Boolean);
|
|
265
|
+
check('场景 11:--list exit 0 且恰七门、序不变、不执行',
|
|
266
|
+
r.status === 0 && ids.length === expect.length && expect.every((id, i) => ids[i] === id)
|
|
267
|
+
&& !String(r.stdout).includes('开始静态门'),
|
|
268
|
+
`exit=${r.status}\n${r.stdout}`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ---- 场景 12:--list 与 --test-cmd 互斥 ----
|
|
272
|
+
{
|
|
273
|
+
const r = run(['--list', '--test-cmd', 'node nothing.mjs'], {});
|
|
274
|
+
check('场景 12:--list + --test-cmd → exit 1', r.status === 1 && /互斥/.test(r.stderr), `exit=${r.status} ${r.stderr}`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ---- 场景 13:生产模式中段红 → fail-fast + 未执行门清单 + 失败行留痕 ----
|
|
278
|
+
// 编排脚本检查门注入一份缺 frontmatter 的坏编排(E1 error)→ 第 3 门红,后四门状态未知须点名
|
|
279
|
+
{
|
|
280
|
+
const root = provision(mkfix());
|
|
281
|
+
fs.mkdirSync(path.join(root, '.agents', 'workflows'), { recursive: true });
|
|
282
|
+
fs.writeFileSync(path.join(root, '.agents', 'workflows', 'bad.md'), '没有 frontmatter 的坏编排\n', 'utf8');
|
|
283
|
+
const bare = fs.mkdtempSync(path.join(os.tmpdir(), 'verify-bare-ff-'));
|
|
284
|
+
const r = run([], { cwd: bare, env: { ...process.env, CHECK_LOOP_ROOT: root } });
|
|
285
|
+
check('场景 13:exit 1 且不出全绿', r.status === 1 && !r.stdout.includes('全绿'), `exit=${r.status}\n${r.stdout}\n${r.stderr}`);
|
|
286
|
+
check('场景 13:fail-fast 点名未执行门(rule-budget、effective-policy、check-loop、ledger-invariant)',
|
|
287
|
+
r.stderr.includes('未执行门') && ['rule-budget', 'effective-policy', 'check-loop', 'ledger-invariant'].every((id) => r.stderr.includes(id)),
|
|
288
|
+
r.stderr);
|
|
289
|
+
const lines = readLedger(root);
|
|
290
|
+
check('场景 13:失败行留痕(suite=workflows-check)且无绿行',
|
|
291
|
+
lines.length === 1 && lines[0].suite === 'workflows-check' && lines[0].exitCode !== 0,
|
|
292
|
+
`lines=${JSON.stringify(lines)}`);
|
|
293
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
294
|
+
fs.rmSync(bare, { recursive: true, force: true });
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ---- 场景 14:生产模式全绿端到端——七门按序全过、凭证钉门数/headShort/套件计数、摘要生成 ----
|
|
298
|
+
{
|
|
299
|
+
const root = provision(mkfix());
|
|
300
|
+
// 根 package.json(装户带根 package.json 的形态):测试命令打印编排器权威汇总行
|
|
301
|
+
fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({
|
|
302
|
+
name: 'verify-e2e-fixture',
|
|
303
|
+
private: true,
|
|
304
|
+
scripts: { test: 'node .agents/probe-test.mjs' },
|
|
305
|
+
}, null, 2));
|
|
306
|
+
fs.writeFileSync(path.join(root, '.agents', 'probe-test.mjs'), "console.log('[run-tests] 合计: SUITES 3 / FAILED 0');\n", 'utf8');
|
|
307
|
+
const headShort = gitify(root);
|
|
308
|
+
const r = run([], { cwd: root, env: { ...process.env, CHECK_LOOP_ROOT: root } });
|
|
309
|
+
check('场景 14:exit 0 且七门逐门输出 + 全绿',
|
|
310
|
+
r.status === 0 && ['✅ 1/7', '✅ 2/7', '✅ 3/7', '✅ 4/7', '✅ 5/7', '✅ 6/7', '✅ 7/7'].every((s) => r.stdout.includes(s)) && r.stdout.includes('全绿'),
|
|
311
|
+
`exit=${r.status}\n${r.stdout}\n${r.stderr}`);
|
|
312
|
+
const line = readLedger(root)[0] || null;
|
|
313
|
+
check('场景 14:绿凭证钉门数 7 + headShort + 套件计数',
|
|
314
|
+
line !== null && line.exitCode === 0 && line.gates === 7 && line.headShort === headShort && line.passed === 3 && line.failed === 0,
|
|
315
|
+
`headShort=${line && line.headShort} expect=${headShort} line=${JSON.stringify(line)}`);
|
|
316
|
+
const ev = path.join(root, 'workflow', 'verify-evidence.md');
|
|
317
|
+
const evText = fs.existsSync(ev) ? fs.readFileSync(ev, 'utf8') : '';
|
|
318
|
+
check('场景 14:绿凭证摘要已生成且含本 SHA 一行(自动重生成接线)',
|
|
319
|
+
fs.existsSync(ev) && evText.includes(`\`${headShort}\``) && evText.includes('| 7 | PASS 3 / FAIL 0 |'),
|
|
320
|
+
evText);
|
|
321
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
322
|
+
}
|
|
323
|
+
|
|
215
324
|
console.log(`\n合计: PASS ${pass} / FAIL ${fail}`);
|
|
216
325
|
if (fail) {
|
|
217
326
|
console.log('\n用法:node templates/_agents/scripts/verify.test.mjs');
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
import fs from 'node:fs';
|
|
16
16
|
import path from 'node:path';
|
|
17
17
|
import { parseStages } from './workflows-check.mjs';
|
|
18
|
+
// 委派台账分片感知读(2026-10-13 ledger-shard-ledgers 回灌):单源 = ledger-shards.readDelegationsText。
|
|
19
|
+
import { readDelegationsText as readDelegationsShardText } from './ledger-shards.mjs';
|
|
18
20
|
|
|
19
21
|
function fail(msg) { console.error('❌ ' + msg); process.exit(1); }
|
|
20
22
|
|
|
@@ -162,8 +164,9 @@ if (isMain) {
|
|
|
162
164
|
fail(`未知命令:${a.cmd ?? ''}(add | status,用法见文件头注释)`);
|
|
163
165
|
}
|
|
164
166
|
function readDelegationsText(file) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
+
// 分片感知读(2026-10-13 ledger-shard-ledgers 回灌):单源 = ledger-shards.readDelegationsText。
|
|
168
|
+
//(file 为显式注入路径时只读该文件——测试语义不变;null/默认路径 → 主台账 ∪ delegations.d/*.md)
|
|
169
|
+
return readDelegationsShardText(process.cwd(), file || null);
|
|
167
170
|
}
|
|
168
171
|
|
|
169
172
|
function delegationSectionRows(text) {
|
|
@@ -172,8 +175,9 @@ function delegationSectionRows(text) {
|
|
|
172
175
|
const rows = [];
|
|
173
176
|
for (const line of lines) {
|
|
174
177
|
if (/^##\s+/.test(line)) {
|
|
175
|
-
|
|
176
|
-
|
|
178
|
+
// 不 break:合并文本(主台账 ∪ 分片)里 `## 委派结果` 会出现多次,break 会漏掉后续分片的行
|
|
179
|
+
// (2026-10-13 源仓实测踩中:分片行读不到 → 静默漏报「委派台账未记录」)。
|
|
180
|
+
inSec = /^##\s+委派结果\s*$/.test(line);
|
|
177
181
|
continue;
|
|
178
182
|
}
|
|
179
183
|
if (!inSec || !/^\s*\|/.test(line)) continue;
|
|
@@ -64,7 +64,7 @@ const CHECK_LOOP = path.join(SCRIPT_DIR, 'check-loop.mjs');
|
|
|
64
64
|
'doc.status.abandoned=superseded cancelled',
|
|
65
65
|
'incident.status.all=open fixed closed',
|
|
66
66
|
'incident.status.active=open',
|
|
67
|
-
'level.all=
|
|
67
|
+
'level.all=L1 L2 L3',
|
|
68
68
|
].join('\n') + '\n');
|
|
69
69
|
const w = (rel, c) => fs.writeFileSync(path.join(T, rel), c);
|
|
70
70
|
// 回路断档:incident 三件套选「是」但引用 intent 不存在
|
|
@@ -169,7 +169,7 @@ const CHECK_LOOP = path.join(SCRIPT_DIR, 'check-loop.mjs');
|
|
|
169
169
|
requirement: '在 runtime-env.md 补记 pipeline-run 运行态',
|
|
170
170
|
stage: 'done',
|
|
171
171
|
stopType: 'done',
|
|
172
|
-
triage: { kind: 'require', level: '
|
|
172
|
+
triage: { kind: 'require', level: 'L1', module: 'pipeline', topic: 'runtime-note', decidedBy: 'ai' },
|
|
173
173
|
updatedAt: '2026-10-02T10:19:22.566Z',
|
|
174
174
|
events: [
|
|
175
175
|
{ t: '2026-10-02T10:19:02.680Z', type: 'run-created', requirement: 'x' },
|