agentic-flow-kit 1.8.1 → 1.8.7
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 +1 -1
- package/modules/hosts/claude/commands/wf-build.md +1 -1
- package/modules/hosts/claude/commands/wf-design.md +1 -1
- package/modules/hosts/claude/commands/wf-new-task.md +1 -0
- package/modules/hosts/claude/commands/wf-review.md +1 -1
- package/modules/hosts/codex/commands/wf-build.md +1 -1
- package/modules/hosts/codex/commands/wf-design.md +1 -1
- package/modules/hosts/codex/commands/wf-new-task.md +1 -0
- package/modules/hosts/codex/commands/wf-review.md +1 -1
- package/modules/hosts/cursor/commands/wf-build.md +1 -1
- package/modules/hosts/cursor/commands/wf-design.md +1 -1
- package/modules/hosts/cursor/commands/wf-new-task.md +1 -0
- package/modules/hosts/cursor/commands/wf-review.md +1 -1
- package/modules/hosts/opencode/commands/wf-build.md +1 -1
- package/modules/hosts/opencode/commands/wf-design.md +1 -1
- package/modules/hosts/opencode/commands/wf-new-task.md +1 -0
- package/modules/hosts/opencode/commands/wf-review.md +1 -1
- package/modules/hosts/trae/commands/wf-build.md +1 -1
- package/modules/hosts/trae/commands/wf-design.md +1 -1
- package/modules/hosts/trae/commands/wf-new-task.md +1 -0
- package/modules/hosts/trae/commands/wf-review.md +1 -1
- package/package.json +1 -1
- package/src/jscpd-drift.test.mjs +3 -2
- package/templates/AGENTS.md +1 -0
- package/templates/_agents/commands/build.md +1 -1
- package/templates/_agents/commands/design.md +1 -1
- package/templates/_agents/commands/new-task.md +1 -0
- package/templates/_agents/commands/review.md +1 -1
- package/templates/_agents/engine-lock.json +32 -24
- package/templates/_agents/hooks/commit-check.cjs +57 -30
- package/templates/_agents/mechanism-registry.md +8 -0
- package/templates/_agents/notes/engine-internals.md +17 -0
- package/templates/_agents/rule-budgets.txt +10 -2
- package/templates/_agents/scripts/agg-judgments.mjs +77 -1
- package/templates/_agents/scripts/agg-judgments.test.mjs +49 -1
- package/templates/_agents/scripts/archive-sweep.mjs +3 -1
- package/templates/_agents/scripts/check-convergence.mjs +8 -3
- package/templates/_agents/scripts/check-lane-surface.mjs +9 -2
- package/templates/_agents/scripts/check-lane-surface.test.mjs +17 -3
- package/templates/_agents/scripts/check-ledger-invariant.mjs +109 -24
- package/templates/_agents/scripts/check-ledger-invariant.test.mjs +29 -1
- package/templates/_agents/scripts/check-loop-rev.test.mjs +8 -14
- package/templates/_agents/scripts/check-loop.mjs +41 -3
- package/templates/_agents/scripts/check-loop.test.mjs +37 -0
- package/templates/_agents/scripts/check-scope.test.mjs +4 -1
- package/templates/_agents/scripts/check-scripts-syntax.mjs +33 -10
- package/templates/_agents/scripts/closeout-l1.mjs +217 -0
- package/templates/_agents/scripts/closeout-l1.test.mjs +105 -0
- package/templates/_agents/scripts/commit-check-trigger.test.mjs +49 -0
- package/templates/_agents/scripts/confirm-doc.mjs +27 -8
- package/templates/_agents/scripts/confirm-doc.test.mjs +68 -9
- package/templates/_agents/scripts/gen-workflow-metrics.mjs +158 -9
- package/templates/_agents/scripts/gen-workflow-metrics.test.mjs +119 -0
- package/templates/_agents/scripts/lane-surface-audit.test.mjs +1 -1
- package/templates/_agents/scripts/ledger-shards.mjs +42 -5
- package/templates/_agents/scripts/ledger-shards.test.mjs +17 -0
- package/templates/_agents/scripts/mechanism-retire-watch.mjs +183 -0
- package/templates/_agents/scripts/mechanism-retire-watch.test.mjs +114 -0
- package/templates/_agents/scripts/pre-push.test.mjs +27 -11
- package/templates/_agents/scripts/rule-budget.mjs +14 -1
- package/templates/_agents/scripts/rule-budget.test.mjs +118 -0
- package/templates/_agents/scripts/solidify-task.mjs +3 -1
- package/templates/_agents/scripts/solidify-task.test.mjs +10 -10
- package/templates/_agents/scripts/trust-mode.test.mjs +1 -1
- package/templates/_agents/scripts/verify.mjs +237 -78
- package/templates/_agents/scripts/verify.test.mjs +44 -0
- package/templates/_agents/scripts-test-exempt.txt +2 -1
- package/templates/_agents/skills/review-method/SKILL.md +3 -0
- package/templates/_githooks/pre-commit +9 -4
- package/templates/_githooks/pre-merge-commit +4 -3
- package/templates/_githooks/pre-push +129 -75
- package/templates/workflow/README.md +2 -1
- package/templates/workflow/intents/_TEMPLATE.md +5 -0
- package/templates/workflow/specs/_TEMPLATE.md +1 -1
|
@@ -19,27 +19,40 @@
|
|
|
19
19
|
// d) --test-cmd 为 fixture 测试注入缝:替换全部生产门为 [test, check-loop] 两门夹具(夹具根里没有
|
|
20
20
|
// 脚本本体,check-loop 用绝对路径直调,root 归属仍由 CHECK_LOOP_ROOT 裁决);夹具模式不触发生效规则
|
|
21
21
|
// 视图门与摘要生成(假门凭证只进本地台账,不产 committed 摘要)。
|
|
22
|
-
// 未执行门清单:fail-fast
|
|
22
|
+
// 未执行门清单:fail-fast 后序门状态未知≠通过——验证系统输出须完备(「没跑」与「过了」可区分);
|
|
23
|
+
// 并发编排(2026-10-16 自源仓 verify-parallel 回灌)后该清单按「已出裁决」算:guardian 门
|
|
24
|
+
// (scripts-syntax)串行先跑、未过即止;其余门进工作池并发(--jobs N / --serial,缺省
|
|
25
|
+
// clamp(2..4, floor(cpus/4))),各自缓冲输出、完成即整块回放;任一门红 → 停排新门 + taskkill /T
|
|
26
|
+
// 树杀在跑门(Windows shell:true 起的是 cmd.exe,kill 只杀壳不杀孙进程)。
|
|
23
27
|
// 生效规则视图门(effective-policy --check):policy 变更后必须重生成 workflow/effective-policy.md 随同
|
|
24
28
|
// 提交——新装户由 init 生成器首跑种子该文件,缺文件对 --check 是漂移(红),不静默放行。
|
|
25
29
|
// 绿凭证摘要(gen-verify-evidence.mjs):全绿尾部自动重生成 workflow/verify-evidence.md——全绿事实与
|
|
26
30
|
// 仓内摘要同刻生成,消除「本地台账已绿、committed 摘要滞后」的双真相分叉;生成失败响亮告警但不推翻绿裁决。
|
|
27
|
-
// 用法:node .agents/scripts/verify.mjs [--list] [--test-cmd "<shell 命令>"] [--doc <本单 workflow 路径>]
|
|
28
|
-
// --list:按执行序打印门 id 后退出,不执行(与 --test-cmd 互斥)。
|
|
31
|
+
// 用法:node .agents/scripts/verify.mjs [--list] [--jobs N | --serial] [--test-cmd "<shell 命令>"] [--stub-gate "<命令>"] [--doc <本单 workflow 路径>]
|
|
32
|
+
// --list:按执行序打印门 id 后退出,不执行(与 --test-cmd / --stub-gate 互斥)。
|
|
33
|
+
// --jobs N / --serial:并发档位与串行回退(缺省 clamp(2..4, floor(cpus/4));--jobs 0 / 非整数 fail-loud)。
|
|
29
34
|
// --test-cmd 用于测试注入,或在无根 package.json 的装户仓指定项目测试命令;
|
|
35
|
+
// --stub-gate <命令>(2026-10-16 自源仓回灌):单门夹具但走**生产尾段**(落凭证 / 生成摘要)——
|
|
36
|
+
// 与 --test-cmd 的差异是不打 testCmd 标记、尾段按生产路径执行;**必须配 CHECK_LOOP_ROOT**(夹具根),
|
|
37
|
+
// 禁止在真实仓库写出可被当成关单凭证的绿行。
|
|
30
38
|
// --doc 把凭证绑到本单,如 --doc workflow/intents/<日期>-<主题>.md
|
|
31
39
|
// 测试:node templates/_agents/scripts/verify.test.mjs(--test-cmd 注入假命令 + CHECK_LOOP_ROOT 注入夹具)
|
|
32
40
|
import fs from 'node:fs';
|
|
33
|
-
import
|
|
41
|
+
import os from 'node:os';
|
|
42
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
34
43
|
import path from 'node:path';
|
|
44
|
+
import { performance } from 'node:perf_hooks';
|
|
35
45
|
import { fileURLToPath } from 'node:url';
|
|
36
46
|
|
|
37
47
|
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
38
48
|
const ROOT = process.env.CHECK_LOOP_ROOT || path.resolve(SCRIPT_DIR, '..', '..');
|
|
39
49
|
|
|
40
50
|
// --- 门禁定义(engine cohort 七门,序即执行序;遇红即停) ---
|
|
51
|
+
// guardian(2026-10-16 并行编排):本门未过则后序门一并不启动——引擎脚本不可解析时其余门无意义,
|
|
52
|
+
// 且夹具根(CHECK_LOOP_ROOT 指向无脚本目录)下若并行铺开,各门会在假根里乱跑。
|
|
53
|
+
// deps:门间依赖边(本门须待所列门**成功**后才启动;kit 门表当前无边——机制随编排器下发,装户增门可用)。
|
|
41
54
|
const GATES = [
|
|
42
|
-
{ id: 'scripts-syntax', name: '引擎脚本语法自检', cmd: 'node .agents/scripts/check-scripts-syntax.mjs', folder: '.', type: 'shell' },
|
|
55
|
+
{ id: 'scripts-syntax', guardian: true, name: '引擎脚本语法自检', cmd: 'node .agents/scripts/check-scripts-syntax.mjs', folder: '.', type: 'shell' },
|
|
43
56
|
{ id: 'engine-tests', name: '测试门 (npm test)', cmd: 'npm test', folder: '.', type: 'shell' },
|
|
44
57
|
{ id: 'workflows-check', name: '编排脚本检查', cmd: 'node .agents/scripts/workflows-check.mjs', folder: '.', type: 'shell' },
|
|
45
58
|
{ id: 'rule-budget', name: '常驻面预算检查', cmd: 'node .agents/scripts/rule-budget.mjs --all', folder: '.', type: 'shell' },
|
|
@@ -59,6 +72,24 @@ const fixtureGates = (cmd) => [
|
|
|
59
72
|
const args = process.argv.slice(2);
|
|
60
73
|
const explicitTestCmd = args.includes('--test-cmd') ? args[args.indexOf('--test-cmd') + 1] : null;
|
|
61
74
|
|
|
75
|
+
// --stub-gate(2026-10-16 自源仓 13f67ef7 回灌):单门夹具但走**生产尾段**(落凭证 / 生成摘要)。
|
|
76
|
+
// 与 --test-cmd 的关键差异:不打 testCmd 标记(fixture 模式绿行带 testCmd:true,生产凭证不与之混淆)。
|
|
77
|
+
// **必须配 CHECK_LOOP_ROOT**——禁止在真实仓库写出可被当成关单凭证的绿行(信任边界:夹具单门不是关单凭证)。
|
|
78
|
+
const stubIdx = args.indexOf('--stub-gate');
|
|
79
|
+
const stubCmd = stubIdx >= 0 ? args[stubIdx + 1] : null;
|
|
80
|
+
if (stubIdx >= 0 && !stubCmd) {
|
|
81
|
+
console.error('[verify] --stub-gate 需要一条命令');
|
|
82
|
+
process.exit(2);
|
|
83
|
+
}
|
|
84
|
+
if (stubCmd && explicitTestCmd) {
|
|
85
|
+
console.error('[verify] --stub-gate 与 --test-cmd 互斥');
|
|
86
|
+
process.exit(2);
|
|
87
|
+
}
|
|
88
|
+
if (stubCmd && !process.env.CHECK_LOOP_ROOT) {
|
|
89
|
+
console.error('[verify] --stub-gate 只允许配合 CHECK_LOOP_ROOT 夹具根(禁止在真实仓库落无关单凭证)');
|
|
90
|
+
process.exit(2);
|
|
91
|
+
}
|
|
92
|
+
|
|
62
93
|
// --doc 解析(沿 --test-cmd 同款取下一 argv 的写法;缺值报用法错误 exit 1 与 --test-cmd 对称)。
|
|
63
94
|
// 归一基准 = **ROOT**(不是 cwd)——消费方两处都以仓库根为基准(check-loop 检查 8 用
|
|
64
95
|
// path.relative(ROOT, intent);confirm-doc 的 docRelOf 用 path.resolve(root, doc))。若这里按 cwd
|
|
@@ -78,12 +109,14 @@ const docRel = docArg
|
|
|
78
109
|
// 装户仓判定:engine-tests 门只在**当前目录**有 package.json 时才跑(npm test 亦在该目录执行)。
|
|
79
110
|
// 跳过时后续不落测试绿凭证(见全绿落账段)。显式 --test-cmd 不受此判定影响。
|
|
80
111
|
const hasRootPackageJson = fs.existsSync(path.join(process.cwd(), 'package.json'));
|
|
81
|
-
const skipTest = explicitTestCmd === null && !hasRootPackageJson;
|
|
82
|
-
const gates = explicitTestCmd ? fixtureGates(explicitTestCmd)
|
|
112
|
+
const skipTest = explicitTestCmd === null && stubCmd === null && !hasRootPackageJson;
|
|
113
|
+
const gates = explicitTestCmd ? fixtureGates(explicitTestCmd)
|
|
114
|
+
: stubCmd ? [{ id: 'stub', name: '夹具单门', cmd: stubCmd, folder: '.', type: 'shell' }]
|
|
115
|
+
: GATES;
|
|
83
116
|
|
|
84
117
|
if (args.includes('--list')) {
|
|
85
|
-
if (explicitTestCmd) {
|
|
86
|
-
console.error('[verify] --list 与 --test-cmd 互斥');
|
|
118
|
+
if (explicitTestCmd || stubCmd) {
|
|
119
|
+
console.error('[verify] --list 与 --test-cmd / --stub-gate 互斥');
|
|
87
120
|
process.exit(1);
|
|
88
121
|
}
|
|
89
122
|
for (const g of gates) console.log(g.id);
|
|
@@ -104,83 +137,209 @@ const recordFailure = (step, exitInfo, note) => {
|
|
|
104
137
|
}
|
|
105
138
|
};
|
|
106
139
|
|
|
107
|
-
|
|
140
|
+
// 并发档位(2026-10-16 并行编排):门之间无共享状态(各自独立进程 + 独立工作目录),串行编排下
|
|
141
|
+
// 总耗时 = 各门之和;工作池并发后墙钟 ≈ max(最长门, 各门之和 / 并发)——门自身已内部并行
|
|
142
|
+
//(npm test 套件池 / check-loop),档位越高越互相抢核。缺省 = clamp(2..4, floor(cpus/4));
|
|
143
|
+
// --serial 回退串行(排障/对比);--jobs N 显式钉档(0 / 非整数 fail-loud)。
|
|
144
|
+
const serial = args.includes('--serial');
|
|
145
|
+
const jobsRaw = args.includes('--jobs') ? Number(args[args.indexOf('--jobs') + 1]) : null;
|
|
146
|
+
if (jobsRaw !== null && (!Number.isInteger(jobsRaw) || jobsRaw < 1)) {
|
|
147
|
+
console.error(`[verify] --jobs 须为正整数(现 ${args[args.indexOf('--jobs') + 1]})`);
|
|
148
|
+
process.exit(2);
|
|
149
|
+
}
|
|
150
|
+
const JOBS = serial ? 1 : Math.min(jobsRaw ?? Math.max(2, Math.min(4, Math.floor(os.cpus().length / 4))), gates.length);
|
|
151
|
+
// 单门输出上限(与旧 spawnSync maxBuffer 同口径):超限即树杀判红,不静默截断(截断后的输出不可当证据)
|
|
152
|
+
const MAX_GATE_OUTPUT = 32 * 1024 * 1024;
|
|
153
|
+
// 树杀(fail-fast 中止在跑门):Windows 下 shell:true 起的是 cmd.exe,kill 只杀壳不杀孙进程
|
|
154
|
+
//(npm→node 会成孤儿并占住文件锁)——故走 taskkill /T /F 整树。
|
|
155
|
+
const killTree = (child) => {
|
|
156
|
+
try {
|
|
157
|
+
if (process.platform === 'win32' && child.pid) {
|
|
158
|
+
spawnSync('taskkill', ['/pid', String(child.pid), '/T', '/F'], { stdio: 'ignore' });
|
|
159
|
+
} else {
|
|
160
|
+
child.kill('SIGTERM');
|
|
161
|
+
}
|
|
162
|
+
} catch { /* best-effort:杀不动不改变裁决 */ }
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
console.log(`[verify] 🚀 开始静态门验证 (共 ${gates.length} 项,并发 ${JOBS}${serial ? '·串行' : ''})${explicitTestCmd ? '【fixture 模式】' : ''}${stubCmd ? '【stub 尾段】' : ''}\n`);
|
|
166
|
+
|
|
167
|
+
async function main() {
|
|
168
|
+
let passedCount = 0;
|
|
169
|
+
const outputs = {};
|
|
170
|
+
const durations = {};
|
|
171
|
+
const doneIdx = new Set(); // 已出裁决的门序(成功/失败/中止;未列入者 = fail-fast 后状态未知)
|
|
172
|
+
const aborted = new Set(); // 因他门失败被树杀中止的门序
|
|
173
|
+
const running = new Map(); // 门序 -> child(树杀目标)
|
|
174
|
+
let failedIdx = -1;
|
|
175
|
+
|
|
176
|
+
const startGate = (i) => {
|
|
177
|
+
const gate = gates[i];
|
|
178
|
+
console.log(`[verify] ▶ ${i + 1}/${gates.length} ${gate.name}...`);
|
|
179
|
+
const t0 = performance.now();
|
|
180
|
+
const child = spawn(gate.cmd, { shell: true, cwd: path.join(ROOT, gate.folder) });
|
|
181
|
+
running.set(i, child);
|
|
182
|
+
const buf = { stdout: '', stderr: '', overflow: false };
|
|
183
|
+
const pump = (src, key) => {
|
|
184
|
+
src.setEncoding('utf8');
|
|
185
|
+
src.on('data', (d) => {
|
|
186
|
+
if (buf.overflow) return;
|
|
187
|
+
buf[key] += d;
|
|
188
|
+
// 与旧 spawnSync maxBuffer 同口径:超限即中止判红(不静默截断——截断后的输出不可当证据)
|
|
189
|
+
if (buf[key].length > MAX_GATE_OUTPUT) { buf.overflow = true; killTree(child); }
|
|
190
|
+
});
|
|
191
|
+
};
|
|
192
|
+
pump(child.stdout, 'stdout');
|
|
193
|
+
pump(child.stderr, 'stderr');
|
|
194
|
+
return new Promise((resolve) => {
|
|
195
|
+
let settled = false;
|
|
196
|
+
const settle = (status, error) => {
|
|
197
|
+
if (settled) return;
|
|
198
|
+
settled = true;
|
|
199
|
+
running.delete(i);
|
|
200
|
+
resolve({ status, error, ...buf, durMs: Math.round(performance.now() - t0) });
|
|
201
|
+
};
|
|
202
|
+
child.on('error', (e) => settle(null, e));
|
|
203
|
+
child.on('close', (status) => settle(status, null));
|
|
204
|
+
});
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// 出裁决:先回放该门缓冲输出(并发下输出按门整块回放,逐门可读,与 run-tests 同口径),再打结论行。
|
|
208
|
+
// 返回 true = 该门红(调用方据此 fail-fast)。
|
|
209
|
+
const finishGate = (i, r) => {
|
|
210
|
+
const gate = gates[i];
|
|
211
|
+
doneIdx.add(i);
|
|
212
|
+
durations[gate.id] = r.durMs;
|
|
213
|
+
if (r.stdout) process.stdout.write(r.stdout);
|
|
214
|
+
if (r.stderr) process.stderr.write(r.stderr);
|
|
215
|
+
if (aborted.has(i)) {
|
|
216
|
+
console.error(`[verify] ⏹ ${i + 1}/${gates.length} ${gate.name} 已中止(他门失败触发 fail-fast,状态未知)`);
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
if (r.overflow) {
|
|
220
|
+
console.error(`[verify] ❌ ${i + 1}/${gates.length} ${gate.name} 未通过(输出超 ${MAX_GATE_OUTPUT / 1024 / 1024}MB 上限,已中止)`);
|
|
221
|
+
recordFailure(gate.id, -1, 'output-overflow');
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
if (r.status !== 0) {
|
|
225
|
+
const note = `exit ${r.status ?? r.error?.code}`;
|
|
226
|
+
console.error(`[verify] ❌ ${i + 1}/${gates.length} ${gate.name} 未通过 (${note})`);
|
|
227
|
+
recordFailure(gate.id, r.status, note);
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
outputs[gate.id] = r.stdout || '';
|
|
231
|
+
console.log(`[verify] ✅ ${i + 1}/${gates.length} ${gate.name} 通过 [${r.durMs} ms]\n`);
|
|
232
|
+
passedCount++;
|
|
233
|
+
return false;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const wallStart = performance.now();
|
|
108
237
|
|
|
109
|
-
|
|
110
|
-
const
|
|
238
|
+
// 依赖边(deps):本门须待所列门**成功**后才启动;依赖门红/未跑 → 本门不启动(状态未知,由尾段清单兜底)
|
|
239
|
+
const idxOf = new Map(gates.map((g, i) => [g.id, i]));
|
|
240
|
+
const gatePromise = new Map(); // 门序 -> 该门结果 Promise(依赖方 await 用)
|
|
111
241
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
242
|
+
// 守卫段(guardian):串行先跑、未过即止——后序门一并不启动
|
|
243
|
+
for (let i = 0; i < gates.length && failedIdx < 0; i++) {
|
|
244
|
+
if (!gates[i].guardian) continue;
|
|
245
|
+
const p = startGate(i);
|
|
246
|
+
gatePromise.set(i, p);
|
|
247
|
+
if (finishGate(i, await p) && failedIdx < 0) failedIdx = i;
|
|
118
248
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
249
|
+
|
|
250
|
+
if (failedIdx < 0) {
|
|
251
|
+
// 主体段:工作池并发(完成一个补一个);任一门红 → 停排新门 + 树杀在跑门(fail-fast 语义保留)
|
|
252
|
+
const bodyIdx = gates.map((_, i) => i).filter((i) => !gates[i].guardian);
|
|
253
|
+
let cursor = 0;
|
|
254
|
+
const worker = async () => {
|
|
255
|
+
while (failedIdx < 0 && cursor < bodyIdx.length) {
|
|
256
|
+
const i = bodyIdx[cursor++];
|
|
257
|
+
const gate = gates[i];
|
|
258
|
+
// 装户仓:engine-tests 门显式跳过——跳过≠通过,不计入 passedCount、不算绿(全绿落账段据此不落凭证)
|
|
259
|
+
if (skipTest && gate.id === 'engine-tests') {
|
|
260
|
+
console.log(`[verify] ⏭ ${i + 1}/${gates.length} 跳过 ${gate.cmd}:当前目录无 package.json(装户仓)——请按项目口径手工跑测试,或用 --test-cmd "<项目测试命令>" 指定后重跑`);
|
|
261
|
+
doneIdx.add(i);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
const deps = gate.deps || [];
|
|
265
|
+
if (deps.length) {
|
|
266
|
+
const depResults = await Promise.all(deps.map((d) => gatePromise.get(idxOf.get(d)) ?? Promise.resolve(null)));
|
|
267
|
+
if (failedIdx >= 0) return;
|
|
268
|
+
if (depResults.some((r) => !r || r.status !== 0)) continue; // 依赖未过 → 本门不启动
|
|
269
|
+
}
|
|
270
|
+
const p = startGate(i);
|
|
271
|
+
gatePromise.set(i, p);
|
|
272
|
+
if (finishGate(i, await p) && failedIdx < 0) {
|
|
273
|
+
failedIdx = i;
|
|
274
|
+
for (const [j, child] of [...running]) { aborted.add(j); killTree(child); }
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
await Promise.all(Array.from({ length: Math.min(JOBS, bodyIdx.length) }, worker));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (failedIdx >= 0) {
|
|
282
|
+
// 未执行门清单(按「已出裁决」算,已中止的门同样状态未知——被树杀时无结论,一并点名):
|
|
283
|
+
// fail-fast 后状态未知≠通过——「没跑」在台账里与「过了」不可区分即输出不完备
|
|
284
|
+
const remaining = gates.map((g, i) => i).filter((i) => !doneIdx.has(i) || aborted.has(i)).map((i) => gates[i].id);
|
|
285
|
+
if (remaining.length) console.error(`[verify] ⏭️ 未执行门(fail-fast,状态未知;含被树杀的已中止门):${remaining.join('、')}`);
|
|
138
286
|
process.exit(1);
|
|
139
287
|
}
|
|
140
|
-
outputs[gate.id] = result.stdout || '';
|
|
141
|
-
console.log(`[verify] ✅ ${i + 1}/${gates.length} ${gate.name} 通过\n`);
|
|
142
|
-
passedCount++;
|
|
143
|
-
}
|
|
144
288
|
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
try {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
289
|
+
// ---- 全绿尾段(任一门红已在上方 exit 1 返回,能走到这里即全绿)----
|
|
290
|
+
// 凭证落账:全部门绿才落;**测试门被跳过同样不落**(测试未跑即无「测试绿」事实,落账即假绿);IO 失败出账不阻断
|
|
291
|
+
try {
|
|
292
|
+
if (skipTest) {
|
|
293
|
+
console.log('[verify] ⚠️ 测试门已跳过——本次不落测试绿凭证(凭证语义 = 测试真跑绿)');
|
|
294
|
+
} else {
|
|
295
|
+
const entry = { ts: new Date().toISOString(), exitCode: 0, suite: 'npm test' };
|
|
296
|
+
// 门数:凭证明此记录「过全量门禁全绿」是几门(fixture 模式 = 2 门夹具,仍如实记)
|
|
297
|
+
entry.gates = passedCount;
|
|
298
|
+
// HEAD 短 SHA 钉进绿行本体(源仓回灌):确定性证据此前只存在于测试夹具、绿行无 SHA 可锚定验证对象;
|
|
299
|
+
// 非 git 环境(fixture 根在 os.tmpdir)解析失败则省略该键,不伪造。
|
|
300
|
+
const rev = spawnSync('git', ['rev-parse', '--short', 'HEAD'], { encoding: 'utf8', cwd: ROOT });
|
|
301
|
+
if (rev.status === 0 && rev.stdout) entry.headShort = rev.stdout.trim();
|
|
302
|
+
// 计数行**只认编排器权威前缀**:嵌套套件同形行不回退——非本编排器口径的 --test-cmd
|
|
303
|
+
//(装户项目自有测试命令)抓不到即省略计数键,不回退到错的事实(宁缺勿错,exitCode 才是硬事实)。
|
|
304
|
+
const m = [...(outputs['engine-tests'] || outputs['test'] || '').matchAll(/\[run-tests\] 合计: SUITES (\d+) \/ FAILED (\d+)/g)].pop();
|
|
305
|
+
if (m) { entry.passed = Number(m[1]); entry.failed = Number(m[2]); }
|
|
306
|
+
// 门禁成本入账(自源仓回灌):totalMs = 各门之和(并发后已非墙钟,保留以续接历史口径);
|
|
307
|
+
// wallMs = 本次实际墙钟——「一次迭代的等待成本」是墙钟,读方以 wallMs 优先。
|
|
308
|
+
entry.totalMs = Object.values(durations).reduce((a, b) => a + b, 0);
|
|
309
|
+
entry.wallMs = Math.round(performance.now() - wallStart);
|
|
310
|
+
entry.durations = durations;
|
|
311
|
+
if (process.env.PIPELINE_RUN_ID) entry.runId = process.env.PIPELINE_RUN_ID;
|
|
312
|
+
// doc 键:未传 --doc 时**不写该键**(不是写 null)——消费方按 doc 精确匹配,undefined 与 null 都恒不命中,
|
|
313
|
+
// 但缺键让「本行没有绑定对象」这件事在台账盘面上直接可读。
|
|
314
|
+
if (docRel) entry.doc = docRel;
|
|
315
|
+
fs.mkdirSync(path.join(ROOT, '.agents'), { recursive: true });
|
|
316
|
+
fs.appendFileSync(path.join(ROOT, '.agents', 'verifications.jsonl'), `${JSON.stringify(entry)}\n`);
|
|
317
|
+
const top3 = Object.entries(durations).sort((a, b) => b[1] - a[1]).slice(0, 3)
|
|
318
|
+
.map(([id, ms]) => `${id}(${(ms / 1000).toFixed(1)}s)`).join(' ');
|
|
319
|
+
console.log(`[verify] 🧾 测试绿凭证已落账 verifications.jsonl${entry.passed !== undefined ? `(套件 ${entry.passed} / FAIL ${entry.failed})` : ''}${docRel ? ` 绑定本单 ${docRel}` : '(未带 --doc:不给任何单背书)'}`);
|
|
320
|
+
console.log(`[verify] ⏱️ 门禁成本:墙钟 ${(entry.wallMs / 1000).toFixed(1)}s|各门合计 ${(entry.totalMs / 1000).toFixed(1)}s|最慢三门 ${top3}`);
|
|
321
|
+
}
|
|
322
|
+
} catch (e) {
|
|
323
|
+
console.error(`[verify] ⚠️ 凭证落账失败(${e.code || e.message})——不阻断,关单门前置将按无凭证告警`);
|
|
169
324
|
}
|
|
170
|
-
} catch (e) {
|
|
171
|
-
console.error(`[verify] ⚠️ 凭证落账失败(${e.code || e.message})——不阻断,关单门前置将按无凭证告警`);
|
|
172
|
-
}
|
|
173
325
|
|
|
174
|
-
// 摘要事件原子化:全绿事实与仓内摘要(workflow/verify-evidence.md)同刻生成,消除「本地台账已绿、
|
|
175
|
-
// committed 摘要滞后」的双真相分叉。仅生产模式(fixture 假门凭证不产 committed 摘要;测试门跳过时
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (gen.
|
|
181
|
-
|
|
182
|
-
|
|
326
|
+
// 摘要事件原子化:全绿事实与仓内摘要(workflow/verify-evidence.md)同刻生成,消除「本地台账已绿、
|
|
327
|
+
// committed 摘要滞后」的双真相分叉。仅生产模式(fixture 假门凭证不产 committed 摘要;测试门跳过时
|
|
328
|
+
// 本就无凭证);stub 尾段按生产路径执行(摘要落在 CHECK_LOOP_ROOT 夹具根,不触真实仓)。
|
|
329
|
+
// 生成失败响亮告警但不推翻绿裁决(同落账口径)。
|
|
330
|
+
if (!explicitTestCmd && !skipTest) {
|
|
331
|
+
const gen = spawnSync(process.execPath, [path.join(SCRIPT_DIR, 'gen-verify-evidence.mjs'), '--root', ROOT], { encoding: 'utf8' });
|
|
332
|
+
if (gen.status === 0) {
|
|
333
|
+
if (gen.stdout) process.stdout.write(String(gen.stdout).trimEnd() + '\n');
|
|
334
|
+
} else {
|
|
335
|
+
console.error(`[verify] ⚠️ 绿凭证摘要生成失败(${String(gen.stderr || gen.stdout || '').trim().slice(0, 200)})——绿裁决不变,手动补跑 node .agents/scripts/gen-verify-evidence.mjs`);
|
|
336
|
+
}
|
|
183
337
|
}
|
|
338
|
+
|
|
339
|
+
console.log(`\n[verify] 🎉 全绿——可以关单(共 ${passedCount} 项通过;逐条勾验验收标准补证据后 intent/plan → done)`);
|
|
184
340
|
}
|
|
185
341
|
|
|
186
|
-
|
|
342
|
+
main().catch((e) => {
|
|
343
|
+
console.error(`[verify] ❌ 编排器异常:${e && e.stack ? e.stack : e}`);
|
|
344
|
+
process.exit(1);
|
|
345
|
+
});
|
|
@@ -257,6 +257,35 @@ const readLedger = (root) => {
|
|
|
257
257
|
fs.rmSync(root10, { recursive: true, force: true });
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
+
// ---- 场景 12:--stub-gate 走生产尾段(落凭证;2026-10-16 自源仓 13f67ef7 回灌)----
|
|
261
|
+
// 单门夹具但按生产路径落绿行(不打 testCmd 标记);必须配 CHECK_LOOP_ROOT。
|
|
262
|
+
{
|
|
263
|
+
const root = mkfix();
|
|
264
|
+
fs.mkdirSync(path.join(root, 'workflow'), { recursive: true });
|
|
265
|
+
const git = (args) => spawnSync('git', args, { cwd: root, encoding: 'utf8' });
|
|
266
|
+
git(['init', '-q']);
|
|
267
|
+
git(['config', 'user.email', 't@t']);
|
|
268
|
+
git(['config', 'user.name', 't']);
|
|
269
|
+
fs.writeFileSync(path.join(root, 'README.md'), 'stub\n');
|
|
270
|
+
git(['add', '-A']);
|
|
271
|
+
git(['commit', '-qm', 'base']);
|
|
272
|
+
const r = run(['--stub-gate', `node ${mkCmd(0)}`], { env: { ...process.env, CHECK_LOOP_ROOT: root, RUN_E2E: '' } });
|
|
273
|
+
const l = readLedger(root)[0] || null;
|
|
274
|
+
check('场景 12:stub 单门 exit 0', r.status === 0, `exit=${r.status}\n${r.stdout}\n${r.stderr}`);
|
|
275
|
+
check('场景 12:落绿行(suite=npm test)且无 testCmd 标记',
|
|
276
|
+
l !== null && l.suite === 'npm test' && l.exitCode === 0 && !('testCmd' in l), JSON.stringify(l));
|
|
277
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
278
|
+
}
|
|
279
|
+
// 场景 12b:无 CHECK_LOOP_ROOT → exit 2,不跑门(信任边界:夹具单门不得当真实凭证)
|
|
280
|
+
{
|
|
281
|
+
const env = { ...process.env, RUN_E2E: '' };
|
|
282
|
+
delete env.CHECK_LOOP_ROOT;
|
|
283
|
+
const r = run(['--stub-gate', 'node -e "process.exit(0)"'], { env });
|
|
284
|
+
check('场景 12b:--stub-gate 无 CHECK_LOOP_ROOT → exit 2 且不跑门',
|
|
285
|
+
r.status === 2 && String(r.stderr).includes('CHECK_LOOP_ROOT') && !String(r.stdout).includes('全绿'),
|
|
286
|
+
`exit=${r.status}\n${r.stdout}\n${r.stderr}`);
|
|
287
|
+
}
|
|
288
|
+
|
|
260
289
|
// ---- 场景 11:--list 锁定 engine cohort 七门序,不执行门 ----
|
|
261
290
|
{
|
|
262
291
|
const expect = ['scripts-syntax', 'engine-tests', 'workflows-check', 'rule-budget', 'effective-policy', 'check-loop', 'ledger-invariant'];
|
|
@@ -321,6 +350,21 @@ const readLedger = (root) => {
|
|
|
321
350
|
fs.rmSync(root, { recursive: true, force: true });
|
|
322
351
|
}
|
|
323
352
|
|
|
353
|
+
// ---- 场景 15:并发档位(2026-10-16 verify-parallel)——--jobs 2 正常绿 / --serial 串行回退 / --jobs 0 fail-loud ----
|
|
354
|
+
{
|
|
355
|
+
const root = mkfix();
|
|
356
|
+
const rJ = run(['--test-cmd', `node ${mkCmd(0)}`, '--jobs', '2'], { env: { ...process.env, CHECK_LOOP_ROOT: root } });
|
|
357
|
+
check('场景 15a:--jobs 2 → exit 0 且头行标注并发档位',
|
|
358
|
+
rJ.status === 0 && rJ.stdout.includes('并发 2') && rJ.stdout.includes('全绿'), `exit=${rJ.status}\n${rJ.stdout}\n${rJ.stderr}`);
|
|
359
|
+
const rS = run(['--test-cmd', `node ${mkCmd(0)}`, '--serial'], { env: { ...process.env, CHECK_LOOP_ROOT: root } });
|
|
360
|
+
check('场景 15b:--serial → exit 0 且头行标注串行(并发 1)',
|
|
361
|
+
rS.status === 0 && rS.stdout.includes('并发 1·串行'), `exit=${rS.status}\n${rS.stdout}\n${rS.stderr}`);
|
|
362
|
+
const rO = run(['--test-cmd', `node ${mkCmd(0)}`, '--jobs', '0'], { env: { ...process.env, CHECK_LOOP_ROOT: root } });
|
|
363
|
+
check('场景 15c:--jobs 0 → exit 2 fail-loud(不静默按缺省跑)',
|
|
364
|
+
rO.status === 2 && /--jobs 须为正整数/.test(rO.stderr), `exit=${rO.status}\n${rO.stderr}`);
|
|
365
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
366
|
+
}
|
|
367
|
+
|
|
324
368
|
console.log(`\n合计: PASS ${pass} / FAIL ${fail}`);
|
|
325
369
|
if (fail) {
|
|
326
370
|
console.log('\n用法:node templates/_agents/scripts/verify.test.mjs');
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
# 新增引擎脚本默认必须带同名 .test.mjs(README 规范条目);确属库件/工具才登记本文件。
|
|
5
5
|
# 2026-10-09 A2 评估:verify-wiki-consistency 已移出(补测 3 场景);wiki-search 已移出(转发壳,判定单源 kb-search.test 覆盖)。
|
|
6
6
|
check-metric-claims.mjs | 经 check-loop.mjs 消费 | 由 check-loop.test.mjs 间接覆盖其调用路径
|
|
7
|
-
|
|
7
|
+
# 2026-10-16 减重手段② 随附:rule-budget.mjs 登记撤除(补同名直测黑盒套件 T1-T8——字节语义不回归 +
|
|
8
|
+
# #count 条目数指令全形态;src/rule-budget.test.mjs 仍是包源预算自检门,职责不同并行保留)。
|
|
8
9
|
ensure-board.mjs | 看板启动器 | 永久工具豁免(需真实端口/进程环境,自动化覆盖无性价比——2026-10-09 engine-quality-round2 A2 评估定性)
|
|
9
10
|
policy.mjs | 库件 | 被 check-loop.test.mjs 直接 import 覆盖
|
|
10
11
|
# 2026-10-12 needs-clarification:stage-gates.mjs 已建同名直测(stage-gates.test.mjs 15 断言),
|
|
@@ -16,6 +16,9 @@ description: 代码复核走查方法(Standards×Spec 双轴 + 坏味道清单
|
|
|
16
16
|
|
|
17
17
|
宿主有全局 `code-review` / `review-verification-protocol` 技能时可叠加;无则本文件兜底。
|
|
18
18
|
|
|
19
|
+
## 校准集(golden-set.md,装户自建)
|
|
20
|
+
校准集是独立复核质量的已知答案样本库:正例 = 应报缺陷、负例 = 应放行,每条带「为何需本仓上下文才可判」要点(外部实证:注入仓库上下文的缺陷识别率远高于通用样本——校准集必须用本仓已闭环的 incidents/返工件构造才有校准力,通用样本不算数)。建集方法:从本仓已闭环件选样,同族第二例须带来新失败形态才入集(宁缺毋滥);答案锚定闭环时定性,代码后续演进不改答案。使用(**双臂**):复核员/新会话上岗前对样本盲评后对答案(命中/误报/漏报三档)自检校准差——A 臂(默认)装载本技能复核;B 臂(对照,月度抽 5–8 条即可)把同样题面交给干净会话,只给样本内联的仓库上下文、不载技能。**Δ = A 命中差 − B 命中差**是唯一能证明「上下文注入起作用」的数:两臂同样准 → 校准力来自样本内联而非技能文档;两臂同样差 → 该样本已知答案不可判——两种情形都记进留痕表「臂」列,不互相冒充。可疑判定(与已知答案不符、或与常识相悖的轻松命中)须**方差复跑**:同会话原样再评,判定翻转 → 计评分方差(题面或 rubric 有歧义),本轮不计命中差、另开 papercut 修题面;不翻转才计校准差——别让基础设施噪声冒充复核方差。月度对账抽样与 reviews.d 当月片互参(事件级计数 vs 样本级命中差,两种粒度不混算,只在备注关联);命中差连续两月 >20% 在 papercuts 登记校准债,**前提是先剔掉方差翻转的判定**(否则评的是题面、不是复核员)。盲评结果按月落 golden-set.md「月度盲评留痕」表(列架:月份|参评样本数|臂|命中/误报/漏报|Δ|方差复跑翻转次数|与 reviews.d 互参谋注)。样本文件放本技能同目录 `golden-set.md`(装户数据,kit 不随包分发);建集后在 mechanism-registry 登记一行(数据面,退役阈值参考:连续 6 个有数据月零盲评)。
|
|
21
|
+
|
|
19
22
|
## 双轴框架(分轴报告,禁合并排序)
|
|
20
23
|
| 轴 | 问的问题 | 依据 |
|
|
21
24
|
|---|---|---|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// 2026-10-13 门禁去 sh 化(B2,自 Shipyard.Material 参考实现回灌):钩子体不再含 sh 逻辑——git 仍以自带
|
|
4
4
|
// sh 启动本文件(钩子机制要求),但 shebang 即 node,PATH 无 sh 不再影响任何逻辑;语言单化后 CI 代理/
|
|
5
5
|
// 本地行为一致。写法为 CommonJS——无扩展名文件 node 缺省按 CJS 解析,不依赖 ≥22.7 的 ESM 语法探测。
|
|
6
|
-
// 通用门禁序:闭环配对(增量) → 泳道完整性(增量) → wiki 台账(增量) → 索引漂移(增量) → 常驻面体积预算(增量) → 确认台账不变量(暂存触台账) → 项目门禁(local-pre-commit) → 敏感信息扫描+条件构建(commit-check.cjs)
|
|
6
|
+
// 通用门禁序:闭环配对(增量) → 泳道完整性(增量) → wiki 台账(增量) → 索引漂移(增量) → 常驻面体积预算(增量) → 确认台账不变量(暂存触台账) → 项目门禁(local-pre-commit) → 敏感信息扫描+条件构建(commit-check.cjs;2026-10-16 起常规提交 --no-build——条件构建后移 pre-push 按推送范围执法,合并提交仍 --full 全量构建)
|
|
7
7
|
// 项目专属门禁(架构红线/业务守卫等)放 .agents/hooks/local-pre-commit——sh 挂载点(add-gate 生态按行接线),
|
|
8
8
|
// 存在即在本钩子内以 sh(缺则 bash)执行,返回非零即阻断提交
|
|
9
9
|
// 合并提交场景由 .githooks/pre-merge-commit 原样转发到本脚本(合并时 git 调的是 pre-merge-commit,
|
|
@@ -58,7 +58,10 @@ if (branchName.startsWith('experiment/')) {
|
|
|
58
58
|
// 检查 B「探索标记缺失」——experiment/* 分支暂存 intent 缺「阶段: exploring」→ 拦(标记零成本,
|
|
59
59
|
// advisory 免纪律不免标记——标记是加固门覆盖率的前提)。
|
|
60
60
|
// 脚本缺失 fail-closed 阻断(见 gate())。
|
|
61
|
-
|
|
61
|
+
// `git merge` 自动提交由 pre-merge-commit 传入 `--merge`(此时 MERGE_HEAD 已不可靠,不能靠探测);
|
|
62
|
+
// 冲突解决后的手动 `git commit` 仍由脚本自己看 MERGE_HEAD(2026-10-16 自源仓回灌)。
|
|
63
|
+
const mergeCommit = process.argv.includes('--merge');
|
|
64
|
+
gate('泳道完整性(增量)', '.agents/scripts/check-lane-surface.mjs', mergeCommit ? ['--merge'] : []);
|
|
62
65
|
|
|
63
66
|
// wiki 台账门禁(增量,check-wiki-ledger.mjs:暂存区触及 wiki/ 时跑 verify-wiki-consistency 三方一致性;
|
|
64
67
|
// 无 wiki/ 变更即跳过。脚本缺失 fail-closed)
|
|
@@ -105,12 +108,14 @@ if (fs.existsSync('.agents/hooks/local-pre-commit')) {
|
|
|
105
108
|
}
|
|
106
109
|
|
|
107
110
|
// 合并提交 → 构建验证覆盖【完整合并树】,不做按扩展名的条件裁剪。
|
|
111
|
+
// 常规提交 --no-build(2026-10-16 构建门后移 pre-push,自源仓回灌):提交内环只跑敏感扫描,条件构建在
|
|
112
|
+
// 推送时按 old..new 范围执法(.githooks/pre-push);合并结果是没人验证过的新状态,仍全量构建。
|
|
108
113
|
// 两条路径:`git merge` 自动提交由 .githooks/pre-merge-commit 显式传 `--full` 转发到本脚本;
|
|
109
114
|
// 冲突解决后手动 `git commit` 由 git 直接调本脚本,此处按 MERGE_HEAD 探测补上(此时合并仍在进行)。
|
|
110
|
-
let fullArgs = process.argv.slice(2);
|
|
115
|
+
let fullArgs = process.argv.slice(2).filter((a) => a !== '--merge');
|
|
111
116
|
if (fullArgs.length === 0) {
|
|
112
117
|
const mergeHead = spawnSync('git', ['rev-parse', '-q', '--verify', 'MERGE_HEAD'], { encoding: 'utf8' });
|
|
113
|
-
|
|
118
|
+
fullArgs = mergeHead.status === 0 ? ['--full'] : ['--no-build'];
|
|
114
119
|
}
|
|
115
120
|
|
|
116
121
|
const cc = spawnSync(process.execPath, ['.agents/hooks/commit-check.cjs', ...fullArgs], { stdio: 'inherit' });
|
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
// 靠人工补跑静态门 + 全量测试才覆盖到)。
|
|
9
9
|
//
|
|
10
10
|
// 做法:转发到 .githooks/pre-commit(门禁清单只此一份,避免两份拷贝日后分叉),
|
|
11
|
-
// 并显式传 `--full`——本钩子「只对 merge 触发」这一身份就是最强信号,无需再去探测
|
|
11
|
+
// 并显式传 `--full` 与 `--merge`——本钩子「只对 merge 触发」这一身份就是最强信号,无需再去探测
|
|
12
12
|
// MERGE_HEAD(2026-09-19 实测:本时点 MERGE_HEAD 已不可靠)。`--full` 令编译验证
|
|
13
13
|
// 覆盖【完整合并树】、不按扩展名的条件裁剪:合并结果是没人验证过的新状态,且其 diff
|
|
14
|
-
//
|
|
14
|
+
// 相对第一父,裁剪会漏掉需要重跑的一侧。`--merge` 令泳道检查 A 跳过(2026-10-16 自源仓回灌:
|
|
15
|
+
// 同窗口 MERGE_HEAD 已不在,已关单产品路径会被误判「触达面判低」)。
|
|
15
16
|
//
|
|
16
17
|
// 冲突解决后手动 `git commit` 走的是 pre-commit 自身,那条路径由 pre-commit 内部的
|
|
17
18
|
// MERGE_HEAD 探测覆盖(此时合并仍在进行,MERGE_HEAD 确定存在)。
|
|
@@ -24,5 +25,5 @@ if (top.status !== 0 || !top.stdout.trim()) {
|
|
|
24
25
|
process.exit(1);
|
|
25
26
|
}
|
|
26
27
|
const hook = path.join(top.stdout.trim(), '.githooks', 'pre-commit');
|
|
27
|
-
const r = spawnSync(process.execPath, [hook, '--full'], { stdio: 'inherit' });
|
|
28
|
+
const r = spawnSync(process.execPath, [hook, '--full', '--merge'], { stdio: 'inherit' });
|
|
28
29
|
process.exit(r.status === null ? 1 : r.status);
|