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
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
// 1) 敏感信息扫描(暂存区新增行含疑似密钥 -> BLOCK)
|
|
3
3
|
// 2) 条件构建验证(按 .agents/hooks/commit-check.config.json 的 builds 配置;--full 不做 ext/prefix 裁剪,仍尊重 when)
|
|
4
4
|
// 3) 质量检测(config.checks:lint / 类型检查 / vet 等秒级确定性检查;--full 不做 ext/prefix 裁剪,仍尊重 when)
|
|
5
|
+
// 旗标(2026-10-16 构建门后移 pre-push,自源仓 2ff869a9 回灌):
|
|
6
|
+
// --no-build:跳过段 2 条件构建——pre-commit 常规提交用(提交内环提速);构建在 pre-push 按推送范围
|
|
7
|
+
// 执法,合并提交仍走 --full 全量构建(合并结果是没人验证过的新状态)。
|
|
8
|
+
// --range <old> <new>:推送范围模式(pre-push 用)——文件面取 git diff --name-only old new;
|
|
9
|
+
// old 为全零 sha(新分支首推)= 全量匹配语义(等价 --full 的构建面);段 1 敏感扫描跳过
|
|
10
|
+
// (逐提交时刻 pre-commit 已扫,范围重扫无增量价值)。默认(无旗标)暂存区模式行为不变。
|
|
5
11
|
// config 由 flow-kit init 按技术栈生成初值,项目可自行增改;缺失或无 builds/checks 时跳过对应段。
|
|
6
12
|
// 配置示例(.agents/hooks/commit-check.config.json):
|
|
7
13
|
// {
|
|
@@ -43,12 +49,27 @@ function sh(cmd, cwd) {
|
|
|
43
49
|
}
|
|
44
50
|
}
|
|
45
51
|
|
|
52
|
+
// --- 模式旗标(2026-10-16 构建门后移 pre-push;sha 经 git 管道来仍做白名单校验——信任边界) ---
|
|
53
|
+
const NO_BUILD = process.argv.includes('--no-build');
|
|
54
|
+
const rangeIdx = process.argv.indexOf('--range');
|
|
55
|
+
const RANGE = rangeIdx >= 0 ? process.argv.slice(rangeIdx + 1, rangeIdx + 3) : null;
|
|
56
|
+
if (RANGE && (RANGE.length < 2 || !/^[0-9a-f]{4,40}$/i.test(RANGE[0]) || !/^[0-9a-f]{4,40}$/i.test(RANGE[1]))) {
|
|
57
|
+
console.log(`BLOCK: --range 参数须为两个 git sha(现「${(RANGE || []).join(' ')}」)`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
// 全零 old = 新分支首推:git diff 对全零 sha 无从执行,且本就取全量匹配语义——文件面置空即可
|
|
61
|
+
const RANGE_ZERO_OLD = RANGE !== null && /^0+$/.test(RANGE[0]);
|
|
62
|
+
|
|
46
63
|
let staged = [];
|
|
47
64
|
try {
|
|
48
|
-
|
|
49
|
-
|
|
65
|
+
const listCmd = !RANGE ? 'git diff --cached --name-only'
|
|
66
|
+
: RANGE_ZERO_OLD ? null
|
|
67
|
+
: `git diff --name-only ${RANGE[0]} ${RANGE[1]}`;
|
|
68
|
+
staged = listCmd === null ? []
|
|
69
|
+
: execSync(listCmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
|
|
70
|
+
.split('\n').map(s => s.trim()).filter(Boolean);
|
|
50
71
|
} catch (e) {
|
|
51
|
-
console.log('BLOCK:
|
|
72
|
+
console.log('BLOCK: 无法读取变更文件面(git diff 失败)');
|
|
52
73
|
process.exit(1);
|
|
53
74
|
}
|
|
54
75
|
|
|
@@ -74,16 +95,18 @@ try {
|
|
|
74
95
|
} catch (e) { /* 空暂存 */ }
|
|
75
96
|
const added = diffText.split('\n').filter(l => l.startsWith('+') && !l.startsWith('+++'));
|
|
76
97
|
const hits = added.filter(l => SECRET.test(l) && !(KNOWN && KNOWN.test(l)) && !SELF.test(l));
|
|
77
|
-
if (hits.length) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
98
|
+
if (hits.length) {
|
|
99
|
+
console.log('BLOCK: 暂存区疑似包含敏感信息(密钥/连接串/令牌),请检查:\n' + hits.slice(0, 5).join('\n'));
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
if (RANGE) console.log('SKIP: --range 模式跳过敏感扫描(逐提交时刻 pre-commit 已扫,2026-10-16 构建门后移)');
|
|
81
103
|
|
|
82
104
|
// --- 2) 条件构建验证 ---
|
|
83
105
|
// --full:合并提交(pre-merge-commit / 冲突解决后手动 commit)使用——合并结果是没人验证过的新状态,
|
|
84
106
|
// 按 ext/prefix 裁剪可能漏掉需要重跑的一侧,故不做路径裁剪。when 仍生效:没有对应能力就不跑。
|
|
107
|
+
// --range 全零 old sha(新分支首推)同取全量匹配语义。
|
|
85
108
|
// 引擎自身文件(.agents/、.githooks/)不参与构建匹配——工作流引擎不是项目构建产物。
|
|
86
|
-
const FULL = process.argv.includes('--full');
|
|
109
|
+
const FULL = process.argv.includes('--full') || RANGE_ZERO_OLD;
|
|
87
110
|
const stagedForBuild = staged.filter(f => !f.startsWith('.agents/') && !f.startsWith('.githooks/'));
|
|
88
111
|
|
|
89
112
|
// whenSatisfied:任一谓词满足即启用。pkg: 读 cwd 下的 package.json;其余谓词相对仓库根。
|
|
@@ -117,30 +140,34 @@ function skipWhen(kind, item) {
|
|
|
117
140
|
console.log(`SKIP: ${item.name}(未找到 ${shown},跳过${kind}——配置好后自动启用)`);
|
|
118
141
|
}
|
|
119
142
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (b.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
143
|
+
if (NO_BUILD) {
|
|
144
|
+
console.log('SKIP: --no-build(2026-10-16 构建门后移 pre-push——常规提交不再构建,推送时按范围执法)');
|
|
145
|
+
} else {
|
|
146
|
+
let consideredBuild = false;
|
|
147
|
+
for (const b of (config.builds || [])) {
|
|
148
|
+
if (!b || !b.command) continue;
|
|
149
|
+
const exts = Array.isArray(b.ext) ? b.ext : [];
|
|
150
|
+
const match = FULL || stagedForBuild.some(f => exts.some(e => f.endsWith(e)) || (b.prefix ? f.startsWith(b.prefix) : false));
|
|
151
|
+
if (!match) continue;
|
|
152
|
+
consideredBuild = true;
|
|
153
|
+
if (!whenSatisfied(b.when, b.cwd)) { skipWhen('构建', b); continue; }
|
|
154
|
+
const r = sh(b.command, b.cwd);
|
|
155
|
+
if (!r.ok) {
|
|
156
|
+
if (b.lockPattern && new RegExp(b.lockPattern).test(r.out)) {
|
|
157
|
+
console.log(`BLOCK: ${b.name} 构建被文件锁拦下(与代码无关)。${b.lockHint || '停掉占用进程后原路重试'}\n` +
|
|
158
|
+
'锁冲突行:\n' + r.out.split(/\r?\n/).filter(l => new RegExp(b.lockPattern).test(l)).slice(0, 3).join('\n'));
|
|
159
|
+
} else {
|
|
160
|
+
console.log(`BLOCK: ${b.name} 构建失败(${b.command})\n` + r.out.slice(0, 1500));
|
|
161
|
+
}
|
|
162
|
+
process.exit(1);
|
|
135
163
|
}
|
|
136
|
-
|
|
164
|
+
console.log(`OK: ${b.name} 构建通过(${b.command})`);
|
|
165
|
+
}
|
|
166
|
+
if (!consideredBuild) {
|
|
167
|
+
console.log(config.builds && config.builds.length
|
|
168
|
+
? 'SKIP: 无匹配的源码变更,跳过构建验证'
|
|
169
|
+
: 'SKIP: 未配置 builds(.agents/hooks/commit-check.config.json),跳过构建验证');
|
|
137
170
|
}
|
|
138
|
-
console.log(`OK: ${b.name} 构建通过(${b.command})`);
|
|
139
|
-
}
|
|
140
|
-
if (!consideredBuild) {
|
|
141
|
-
console.log(config.builds && config.builds.length
|
|
142
|
-
? 'SKIP: 无匹配的源码变更,跳过构建验证'
|
|
143
|
-
: 'SKIP: 未配置 builds(.agents/hooks/commit-check.config.json),跳过构建验证');
|
|
144
171
|
}
|
|
145
172
|
|
|
146
173
|
// --- 3) 质量检测(checks:lint / 类型检查 / vet 等秒级确定性检查;测试不放这里——关单在 test.md 阶段门) ---
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|---|---|---|---|---|---|---|
|
|
15
15
|
| 确认指纹对账(示例行) | check-loop 检查 15 | hard「指纹不配对/确认内容漂移」计数(月,经 agg-judgments 查判定流水) | 不退役(防伪造确认主干) | 示例:建门留档见包源仓 git 历史与 CHANGELOG | 现役 | — |
|
|
16
16
|
| 触达面判低门(示例行) | check-lane-surface(pre-commit/CI 泳道完整性) | hard「触达面判低/产品契约面」计数(月) | 不退役(级别闭环主干) | 示例:在用 | 现役 | — |
|
|
17
|
+
| 复核效果台账(示例行) | workflow/reviews.d/ 意见级处置(采纳/拒绝/误报)→ gen-workflow-metrics.mjs 月度聚合 | metrics.md「复核效果」表(采纳率/误报率;区分「门禁制造返工 vs 拦下问题」的意见级信号源) | 连续 6 个有数据月零记录且 delegations 复核采纳行恒 0 → 评估退役(数据面非门禁) | 示例:随 1.8.3 分发,建账口径见 review.md 落账子项 | 现役 | — |
|
|
17
18
|
|
|
18
19
|
> 以上两行为**示例行**,只示范登记口径,不参与装户对账;装户按自有机制增删行,
|
|
19
20
|
> 首次对账时可将示例行替换为真实运营数据。
|
|
@@ -24,4 +25,11 @@
|
|
|
24
25
|
- **月度对账**:每月初按「触发统计口径」列刷新计数(半自动:`node .agents/scripts/agg-judgments.mjs`
|
|
25
26
|
查判定流水 `.agents/judgments.jsonl`——check-loop 每次真实运行自动落账,替代人工 grep 输出;
|
|
26
27
|
git log 统计口径不变);达到阈值的行改「待退役」并填处置期限。
|
|
28
|
+
- **数据面机制的「现役」≠「有数据」**:复核效果台账 / 评审黄金集校准类数据面机制登记后,月度对账须区分两件事:① 资产是否存在(建账即存在);② 本月是否产生数据并写进留痕面——未产生 → 该月记「无跑数」,连续 6 个**有数据月**零记录才走退役阈值,缺月不算零命中(**年轻遥测不得制造假候选**,口径同 `agg-judgments --retire-candidates`)。
|
|
27
29
|
- **退役动作**:状态改「已退役」当日删除机制代码 + 其测试 + 本表更新,同一提交完成(不留死代码)。
|
|
30
|
+
- **模型代际触发**:每个机制都是对当前模型能力边界的假设(外部实证:harness 组件随模型代际逐件移动),既有退役阈值只有使用维度、缺代际维度——主力模型**代际变更**(主版本号或模型家族更换,如 GLM-4.x → 5.x;小版本/推理档变更不算)后的**下一次月度对账**须按下面分组逐件重评去留,结论写各机制「最近触发」列;重评本身不改状态列(退役仍走既有阈值)。首次对账时把本表现有机制按下组归类(此后新增机制在登记时即注明组别;本节用列表不用表格——防管道行被误当机制行解析):
|
|
31
|
+
- **A · 纪律假设**(假设:无机器检查时模型会跳步/声称未做之事,如配对完整性、留痕、占位符残留类检查)——重评动作:观察新代际下 WARN/hard 是否加速归零(是 → 按既有退役阈值自然走);**不因代际主动拆门**——判据已机器化、边际成本 ≈ 0,拆门收益低于一次漏拦。
|
|
32
|
+
- **B · 能力边界假设**(假设:机制强度绑在当前模型能力短板上,代际升级后假设可能失效,**须主动重评**,如常驻面体积预算、复核上下文独立性、判低逃逸类检查)——只重定阈值不轻拆门;其中信任边界/安全主干(凭据扫描、确认防伪类)**代际不撤门**,只重评执行成本。
|
|
33
|
+
- **C · 机器判据**(纯确定性检查,与模型能力无关——格式/枚举/漂移/同步类)——代际免评。
|
|
34
|
+
- **D · 宿主生态**(跟宿主软件版本走,不跟模型代际;宿主升级时随 Adapter 检查顺带过)。
|
|
35
|
+
- **E · 测量自举**(数据面,代际只影响测量结论的解释、不改机制本身;跨代际比较月度指标先读委派/复核台账的模型代际基线注)——如门禁耗时、复核效果台账、退役催办。
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# 引擎维护者面(engine-internals)
|
|
2
|
+
|
|
3
|
+
> 使用者只需 `.agents/commands/new-task.md` 的两问定级 + 路由表;本页收深水区:预判对账机理、
|
|
4
|
+
> kind 词表单源、规则生灭机制。环境事实单源在 `.agents/notes/runtime-env.md`(本页不涉及)。
|
|
5
|
+
> 本页属引擎面(`.agents/notes/`)。
|
|
6
|
+
|
|
7
|
+
## 预判留痕与 kind 词表(check-loop §25,2026-10-12 源仓建;kit 2026-10-13 B6b-2 回灌)
|
|
8
|
+
|
|
9
|
+
定级先做结构化预判(有结构化判定工具则对齐本仓判据,无则按两问自判),结果如实写进入口文档 frontmatter「预判: kind=<kind> level=<L1|L2|L3>」——与实际落位不符时 check-loop 出 WARN 留账(月度对账再议升格)。
|
|
10
|
+
|
|
11
|
+
**kind 词表**(单源 `pipeline-run.mjs` KINDS——§25 执法面 check-prejudgment.mjs 同款内联声明,注释互引、改任一侧须同步;任何列举处系摘录,增删以单源为准;非任务类型词):`require`(新需求,落 intents)/ `fix`(修复,落 incidents)/ `verify` / `review` / `deploy`。intents 只判 `require`、incidents 只判 `fix`;plans/specs 是两道共用件,不判类型只比对级别。终态文档(done/superseded/cancelled/fixed/closed)跳过形态/词表类告警——终态受检查 15 内容绑定不可修订,此类 WARN 永不可消;类型/级别比对照常。
|
|
12
|
+
|
|
13
|
+
## 规则生灭机制(2026-10-15 减重立项 自源仓回灌:锚日只升不降的对冲面)
|
|
14
|
+
|
|
15
|
+
- **生**:新检查以 advisory 灰度进入 → `effective-policy.md` 锚表按 policyVersion 升 hard。
|
|
16
|
+
- **死**(退役候选):`node .agents/scripts/agg-judgments.mjs --retire-candidates`——连续 6 个有数据月零命中(WARN+BLOCK=0)的规则列为候选;月度对账按 `.agents/mechanism-registry.md`「退役判据」列逐条裁决(安全主干/级别闭环主干等「不退役」行除外,附加停滞条件由人判)。
|
|
17
|
+
- **一进一出**:条目数预算指令 `<N>#count` 随 `rule-budget.mjs` 下发(`.agents/rule-budgets.txt` 填 `<路径> <N>#count` 即生效——匹配文件数上限,超限硬拦)。源仓以该指令锚定 check-loop 段数(26,段合并后 23);kit 检查段执行体在单体 check-loop.mjs 与 check-hygiene 模块内、无独立目录面,故模板不预设锚行——装户可自行为规则面(如 `.agents/rules/`)设锚。段编号承重不重排(§N 被锚名/机制台账跨文件引用),退役 = 段体删除,编号留疤。
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# 常驻面体积预算(单源:check-hygiene 检查 13(--all,WARN)与 .githooks/pre-commit(--staged,硬拦)共读)
|
|
2
2
|
# 格式:<路径或 glob> <上限字节>;路径以 / 结尾表示目录合计。上限 = 当前实测 + 余量。
|
|
3
|
+
# 条目数预算(2026-10-15 减重手段② 自源仓回灌):<路径或 glob> <N>#count = 匹配文件**条数**上限
|
|
4
|
+
# (规则面一进一出:新增一条须先退役一条,退役判定 node .agents/scripts/agg-judgments.mjs --retire-candidates)。
|
|
5
|
+
# 本表未预设条目数锚行——kit 无源仓 check-loop-sections/ 目录布局,检查段执行体在单体文件与 check-hygiene
|
|
6
|
+
# 模块内、段号承重稳定,条目数锚对象属装户自有决策面(指令能力随 rule-budget.mjs 下发,旧格式行零影响)。
|
|
3
7
|
# 超限含义:常驻面只增不减——须先删除或下沉被取代条目再增(一进一出)。
|
|
4
8
|
# 本文件会被 core.autocrlf 检出为 CRLF,读取方(rule-budget.mjs)按 /\r?\n/ 切分天然容 \r,禁止依赖行尾格式。
|
|
5
9
|
# 口径:预算比较用「索引 / HEAD 字节」(git cat-file,LF 归一);禁用工作区 wc -c 核对(CRLF 膨胀)。
|
|
@@ -7,6 +11,10 @@
|
|
|
7
11
|
# 超 8192 被拦)——单篇上限 8192→8704(与装户 Shipyard.Material 同值,512B 步进);目录合计未超不动。
|
|
8
12
|
# 2026-10-07 adopter-ci-github:AGENTS.md 门禁节补 CI 远端门一行(299B,实测 7969B 超 7680 被拦)——
|
|
9
13
|
# 新门入册属合法增长,同 512B 步进上调 7680→8192。
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
# 2026-10-16 减重系列回灌:new-task.md 增「预判留痕」指针行(§25 使用者入口 + engine-internals 维护者
|
|
15
|
+
# 面分离,自源仓减重手段⑤终态回灌)实测 8914B 超 8704 被拦——合法增长,同 512B 步进上调 8704→9216。
|
|
16
|
+
# 2026-10-18 立项质量批:AGENTS.md 项目适配区新登「工程原则」节(起草 / 复核对照面,不入机器判据)——
|
|
17
|
+
# 实测 8546B 超 8192 被拦(先压缩措辞再入账),合法结构新增,同 512B 步进上调 8192→8704。
|
|
18
|
+
AGENTS.md 8704
|
|
19
|
+
.agents/commands/*.md 9216
|
|
12
20
|
.agents/commands/ 65536
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// agg-judgments.mjs — check-loop 判定流水月度聚合(2026-10-11 judgment-ledger)
|
|
3
3
|
// 用法:node .agents/scripts/agg-judgments.mjs [--month=YYYY-MM]
|
|
4
|
+
// node .agents/scripts/agg-judgments.mjs --retire-candidates [--months=6]
|
|
4
5
|
// 源:.agents/judgments.jsonl(check-loop.mjs 每次真实运行追加一行,本地 gitignore;
|
|
5
6
|
// 缺文件 = 尚无运行记录,提示后 exit 0——与 delegations 台账「必在」不同,这是渐积数据)。
|
|
6
7
|
// 消费:mechanism-registry 月度对账(.agents/mechanism-registry.md「触发统计口径」列的 WARN/BLOCK 计数)
|
|
7
8
|
// 与 advisory→hard 升格 / 退役判定的直接查账面——替代原「人工 grep check-loop 输出」。
|
|
8
|
-
//
|
|
9
|
+
// --retire-candidates(2026-10-15 减重手段① 自源仓回灌,规则的死亡机制):列出「连续 N 个有数据月
|
|
10
|
+
// 零命中(WARN+BLOCK 全为 0)」的规则作为退役候选——月度对账时按 mechanism-registry「退役判据」
|
|
11
|
+
// 列逐条裁决(安全主干/级别闭环主干等「不退役」行除外;「且 X 停滞」类附加条件由人判)。
|
|
12
|
+
// 台账不足 N 个数据月 → 数据不充分,不产候选(年轻遥测不得制造假候选)。判定口径 = 尾部
|
|
13
|
+
// N 个「有运行的月」(非日历月——遥测断档的月不算零命中,防漏跑误判退役)。
|
|
14
|
+
// 测试:node .agents/scripts/agg-judgments.test.mjs(纯函数 aggFromRows / retireCandidates 进程内断言)
|
|
9
15
|
import fs from 'node:fs';
|
|
10
16
|
import path from 'node:path';
|
|
11
17
|
import { fileURLToPath } from 'node:url';
|
|
@@ -51,12 +57,82 @@ function readRows() {
|
|
|
51
57
|
return { rows, bad };
|
|
52
58
|
}
|
|
53
59
|
|
|
60
|
+
// retireCandidates(rows, windowMonths):退役候选 = 「尾部 windowMonths 个有数据月零命中」的规则。
|
|
61
|
+
// 口径:数据月 = 该月至少一次运行(断档月不参与,不按零命中计);候选须在全历史出现过(含只剩
|
|
62
|
+
// 零命中的月)且尾部窗口内 WARN+BLOCK 全 0。输出按全历史命中数升序(最冷门在前)。
|
|
63
|
+
// rows 不足 windowMonths 个数据月 → { sufficient:false }——年轻遥测不产候选。
|
|
64
|
+
export function retireCandidates(rows, windowMonths = 6) {
|
|
65
|
+
const { byMonth } = aggFromRows(rows);
|
|
66
|
+
const months = Object.keys(byMonth).sort();
|
|
67
|
+
if (months.length < windowMonths) {
|
|
68
|
+
return { sufficient: false, dataMonths: months.length, windowMonths, candidates: [], recentHits: [] };
|
|
69
|
+
}
|
|
70
|
+
const trailing = new Set(months.slice(-windowMonths));
|
|
71
|
+
const allRules = new Set();
|
|
72
|
+
for (const ym of months) for (const r of Object.keys(byMonth[ym].rules)) allRules.add(r);
|
|
73
|
+
const candidates = [];
|
|
74
|
+
const recentHits = [];
|
|
75
|
+
for (const rule of allRules) {
|
|
76
|
+
let total = 0;
|
|
77
|
+
let lastHit = null;
|
|
78
|
+
let inWindow = 0;
|
|
79
|
+
for (const ym of months) {
|
|
80
|
+
const cell = byMonth[ym].rules[rule];
|
|
81
|
+
const n = cell ? (cell.WARN + cell.BLOCK) : 0;
|
|
82
|
+
total += n;
|
|
83
|
+
if (n > 0) {
|
|
84
|
+
lastHit = ym;
|
|
85
|
+
if (trailing.has(ym)) inWindow += n;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const row = { rule, total, lastHit };
|
|
89
|
+
if (inWindow === 0) candidates.push(row);
|
|
90
|
+
else recentHits.push(row);
|
|
91
|
+
}
|
|
92
|
+
candidates.sort((a, b) => a.total - b.total || a.rule.localeCompare(b.rule));
|
|
93
|
+
recentHits.sort((a, b) => b.total - a.total || a.rule.localeCompare(b.rule));
|
|
94
|
+
return {
|
|
95
|
+
sufficient: true,
|
|
96
|
+
dataMonths: months.length,
|
|
97
|
+
windowMonths,
|
|
98
|
+
windowFrom: months.slice(-windowMonths)[0],
|
|
99
|
+
windowTo: months[months.length - 1],
|
|
100
|
+
candidates,
|
|
101
|
+
recentHits,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function printRetireCandidates(read) {
|
|
106
|
+
const wArg = Number((process.argv.find((a) => a.startsWith('--months=')) || '').split('=')[1]);
|
|
107
|
+
const windowMonths = Number.isFinite(wArg) && wArg >= 1 ? Math.floor(wArg) : 6;
|
|
108
|
+
const r = retireCandidates(read.rows, windowMonths);
|
|
109
|
+
if (!r.sufficient) {
|
|
110
|
+
console.log(`agg-judgments: 退役候选窗口 ${windowMonths} 个月——数据月仅 ${r.dataMonths} 个(台账 ${read.rows.length} 行),数据不充分不产候选。继续积累运行记录。`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
console.log(`退役候选(零命中窗口 ${r.windowFrom}..${r.windowTo},共 ${r.windowMonths} 个数据月)——仅机械筛查,裁决走 mechanism-registry「退役判据」列(「不退役」主干除外):`);
|
|
114
|
+
if (!r.candidates.length) {
|
|
115
|
+
console.log(' (无候选——尾部窗口内所有规则均有命中)');
|
|
116
|
+
}
|
|
117
|
+
for (const c of r.candidates) {
|
|
118
|
+
console.log(` 候选 ${c.rule.padEnd(24)} 全历史命中 ${String(c.total).padStart(4)} 末次命中 ${c.lastHit || '—'}`);
|
|
119
|
+
}
|
|
120
|
+
for (const c of r.recentHits) {
|
|
121
|
+
console.log(` 在役 ${c.rule.padEnd(24)} 全历史命中 ${String(c.total).padStart(4)} 末次命中 ${c.lastHit}`);
|
|
122
|
+
}
|
|
123
|
+
if (read.bad) console.error(`agg-judgments: 跳过坏行 ${read.bad}(JSON 解析失败,不中断聚合)`);
|
|
124
|
+
}
|
|
125
|
+
|
|
54
126
|
function main() {
|
|
55
127
|
const read = readRows();
|
|
56
128
|
if (!read) {
|
|
57
129
|
console.error(`agg-judgments: ${path.relative(ROOT, LEDGER)} 不存在——尚无运行记录(check-loop 真实运行一次即有一行)`);
|
|
58
130
|
process.exit(0);
|
|
59
131
|
}
|
|
132
|
+
if (process.argv.includes('--retire-candidates')) {
|
|
133
|
+
printRetireCandidates(read);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
60
136
|
const { byMonth } = aggFromRows(read.rows, argMonth);
|
|
61
137
|
const months = Object.keys(byMonth).sort();
|
|
62
138
|
if (!months.length) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// agg-judgments.test.mjs — 判定流水聚合纯函数套件(2026-10-11 judgment-ledger)
|
|
3
3
|
// aggFromRows 进程内断言:按月分桶 / --month 过滤 / WARN·BLOCK 计数 / 坏 rules 字段容忍。
|
|
4
|
-
|
|
4
|
+
// retireCandidates(2026-10-15 减重手段① 自源仓回灌):零命中窗口候选 / 数据不充分守卫 / 断档月不按零计。
|
|
5
|
+
import { aggFromRows, retireCandidates } from './agg-judgments.mjs';
|
|
5
6
|
|
|
6
7
|
let pass = 0;
|
|
7
8
|
let fail = 0;
|
|
@@ -41,5 +42,52 @@ const ROWS = [
|
|
|
41
42
|
check('S3 空行数组 → 空 byMonth', Object.keys(aggFromRows([]).byMonth).length === 0);
|
|
42
43
|
check('S3 null 入参不 throw', Object.keys(aggFromRows(null).byMonth).length === 0);
|
|
43
44
|
|
|
45
|
+
// ---- S4 retireCandidates:零命中窗口出候选(6 个数据月,2 条规则) ----
|
|
46
|
+
{
|
|
47
|
+
const mk = (ym, rule, level, count) => ({ ts: `${ym}-15T00:00:00Z`, mode: 'hard', rules: [{ rule, level, count }] });
|
|
48
|
+
const rows = [
|
|
49
|
+
// 规则甲:2026-03 有命中(窗口 04..09 之外),其后零命中 → 候选(全历史 3)
|
|
50
|
+
mk('2026-03', '规则甲', 'WARN', 3),
|
|
51
|
+
// 规则乙:尾部窗口内 2026-08 仍有命中 → 在役
|
|
52
|
+
mk('2026-08', '规则乙', 'BLOCK', 1),
|
|
53
|
+
// 规则丙:仅 2026-03 出现过(窗口外)→ 候选(全历史 5,末次 2026-03)
|
|
54
|
+
mk('2026-03', '规则丙', 'WARN', 5),
|
|
55
|
+
// 6 个数据月铺满窗口 2026-04..2026-09(每月至少一次运行)
|
|
56
|
+
...['2026-04', '2026-05', '2026-06', '2026-07', '2026-08', '2026-09'].map((ym) => mk(ym, '规则甲', 'WARN', 0)),
|
|
57
|
+
];
|
|
58
|
+
const r = retireCandidates(rows, 6);
|
|
59
|
+
check('S4 sufficient 且窗口起止正确', r.sufficient === true && r.windowFrom === '2026-04' && r.windowTo === '2026-09', JSON.stringify({ from: r.windowFrom, to: r.windowTo }));
|
|
60
|
+
const cand = r.candidates.map((c) => c.rule);
|
|
61
|
+
check('S4 规则甲/丙为候选、乙在役', cand.includes('规则甲') && cand.includes('规则丙') && !cand.includes('规则乙') && r.recentHits.some((x) => x.rule === '规则乙'), JSON.stringify(cand));
|
|
62
|
+
check('S4 候选按全历史命中升序(甲3 < 丙5)', cand[0] === '规则甲' && cand[1] === '规则丙', JSON.stringify(cand));
|
|
63
|
+
const jia = r.candidates.find((c) => c.rule === '规则甲');
|
|
64
|
+
const bing = r.candidates.find((c) => c.rule === '规则丙');
|
|
65
|
+
check('S4 全历史命中与末次命中月份', jia.total === 3 && jia.lastHit === '2026-03' && bing.total === 5 && bing.lastHit === '2026-03', JSON.stringify({ jia, bing }));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ---- S5 数据不充分守卫:数据月 < 窗口 → 不产候选 ----
|
|
69
|
+
{
|
|
70
|
+
const rows = [{ ts: '2026-10-01T00:00:00Z', mode: 'hard', rules: [{ rule: '规则甲', level: 'WARN', count: 1 }] }];
|
|
71
|
+
const r = retireCandidates(rows, 6);
|
|
72
|
+
check('S5 数据月 1 < 6 → sufficient=false 零候选', r.sufficient === false && r.dataMonths === 1 && r.candidates.length === 0, JSON.stringify(r));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ---- S6 断档月不按零命中计:窗口取「有数据的月」(末 N 个数据月),断档月不参与 ----
|
|
76
|
+
{
|
|
77
|
+
const mk = (ym, rule, count) => ({ ts: `${ym}-15T00:00:00Z`, mode: 'hard', rules: count ? [{ rule, level: 'WARN', count }] : [] });
|
|
78
|
+
// 数据月仅 2026-05 / 2026-08 / 2026-09(06-07 断档无运行);规则甲 05 有命中、08/09 无 → 窗口=末 2 个数据月(08..09)
|
|
79
|
+
const rows = [mk('2026-05', '规则甲', 2), mk('2026-08', '规则甲', 0), mk('2026-09', '规则甲', 0)];
|
|
80
|
+
const r = retireCandidates(rows, 2);
|
|
81
|
+
check('S6 窗口取末 2 个数据月(08..09,断档月跳过),甲窗口外命中 → 候选', r.sufficient === true && r.windowFrom === '2026-08' && r.windowTo === '2026-09' && r.candidates.some((c) => c.rule === '规则甲'), JSON.stringify(r));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ---- S7 窗口边界:末月有命中不算候选 ----
|
|
85
|
+
{
|
|
86
|
+
const mk = (ym, count) => ({ ts: `${ym}-15T00:00:00Z`, mode: 'hard', rules: count ? [{ rule: '规则乙', level: 'WARN', count }] : [] });
|
|
87
|
+
const rows = [...['2026-04', '2026-05', '2026-06', '2026-07', '2026-08'].map((ym) => mk(ym, 0)), mk('2026-09', 1)];
|
|
88
|
+
const r = retireCandidates(rows, 6);
|
|
89
|
+
check('S7 末月命中 → 在役非候选', r.sufficient === true && !r.candidates.some((c) => c.rule === '规则乙') && r.recentHits.some((x) => x.rule === '规则乙'), JSON.stringify(r.candidates));
|
|
90
|
+
}
|
|
91
|
+
|
|
44
92
|
console.log(`\n合计: PASS ${pass} / FAIL ${fail}`);
|
|
45
93
|
process.exit(fail ? 1 : 0);
|
|
@@ -111,7 +111,9 @@ export function buildDocList(root, dirs = DIRS) {
|
|
|
111
111
|
return docs;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
|
|
114
|
+
// loadLedgerTs(root):每篇文档的终态台账行最新日期(YYYY-MM-DD)。主台账+分片统一视图;
|
|
115
|
+
// 2026-10-15 起导出(gen-workflow-metrics 归档积压提醒复用——新消费方零复制)。
|
|
116
|
+
export function loadLedgerTs(root) {
|
|
115
117
|
const out = {};
|
|
116
118
|
// 台账统一视图(主台账+分片透明合并,坏行跳过)。源仓走 ledger-loader.loadAllConfirmations
|
|
117
119
|
// (同 doc|stage|fingerprint 键去重取末次);kit 读侧单源 = readLedgerRows(不去重)。对本判据
|
|
@@ -26,7 +26,11 @@ const CMD_RE = /npm\s+(run\s+)?test|verify\.mjs/;
|
|
|
26
26
|
const CITE_RE = /npm\s+(?:run\s+)?test|verify\.mjs/g;
|
|
27
27
|
// verify.mjs 全绿行(verifications.jsonl 绿凭证)实际覆盖的命令。不在此集的白名单命令没有对应绿记录。
|
|
28
28
|
const GATE_CMD = new Set(['npm test', 'npm run test', 'verify.mjs']);
|
|
29
|
-
|
|
29
|
+
// 路径提取(2026-10-16 自源仓 13f67ef7 回灌):路径体 = ASCII 路径字符 + 字母(中文目录放行),
|
|
30
|
+
// 扩展名加 .vue。单字符类 + 前置分隔锚,避免 (A|B)+ 在长说明上回溯;标点(如路径后的「(说明)」)
|
|
31
|
+
// 不在类内,仍截断。matchAll 取捕获组(剥前导分隔符),路径分隔符归一 posix。
|
|
32
|
+
const PATH_EXT = 'mjs|js|ts|tsx|vue|cs|md|json|html|sql';
|
|
33
|
+
const PATH_RE = new RegExp(`(?:^|[\\s\`'"((])([\\w.@\\-\\\\/\\p{L}]+\\.(?:${PATH_EXT}))`, 'gu');
|
|
30
34
|
const SEC_RE = /^##\s+.*验收判据/;
|
|
31
35
|
// 与 gen-verify-evidence.mjs 数据行同形态(kit 六列:短 SHA | ISO 时间 | 门数 | 套件计数 | 绑定单 | runId)。
|
|
32
36
|
// 只取日期,不复述门数。
|
|
@@ -102,7 +106,8 @@ export function run(ctx) {
|
|
|
102
106
|
if (hard && CMD_RE.test(ev)) ok = commandCovered(ev, d, greenDates);
|
|
103
107
|
else if (CMD_RE.test(ev)) ok = true; // advisory:命令白名单认声明
|
|
104
108
|
else {
|
|
105
|
-
const cands =
|
|
109
|
+
const cands = [];
|
|
110
|
+
for (const m of ev.matchAll(PATH_RE)) cands.push(m[1].replace(/\\/g, '/'));
|
|
106
111
|
ok = cands.some((p) => fs.existsSync(path.join(ROOT, p)));
|
|
107
112
|
}
|
|
108
113
|
}
|
|
@@ -110,7 +115,7 @@ export function run(ctx) {
|
|
|
110
115
|
}
|
|
111
116
|
if (missed.length) {
|
|
112
117
|
const detail = `${base} ${rows.length} 条判据中 ${missed.length} 条证据无效(${missed.join('; ')})`;
|
|
113
|
-
if (hard) blockers.push(`- [收敛未覆盖] ${detail}——状态列自报不作数;命令须是全量门(npm test / verify.mjs)且 workflow/verify-evidence.md 有不早于本 spec 日期的全绿行;路径只认存在(检查 23
|
|
118
|
+
if (hard) blockers.push(`- [收敛未覆盖] ${detail}——状态列自报不作数;命令须是全量门(npm test / verify.mjs)且 workflow/verify-evidence.md 有不早于本 spec 日期的全绿行;路径只认存在(检查 23,含 .vue 与中文目录)。若本 spec 已标 done:先标后补会与本门互为前提(verify 门序含 check-loop)——解锁序:改正文 → 状态改回 approved(勿改 draft,L3 必须 approved/done)→ confirm-doc 重录指纹 → 再标 done。中间态不要提交`);
|
|
114
119
|
else warnings.push(`- [WARN 收敛未覆盖] ${detail}——状态列自报不作数,证据须存在或为验证命令(检查 23)`);
|
|
115
120
|
}
|
|
116
121
|
}
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
// B 跳过(沿 pairing 门先例);配置行正则语法错误 → fail-closed(防静默空洞)。
|
|
23
23
|
// 已知边界:commit-time 门禁,CI / pre-push 复跑面无对应全量检查(push 时刻无暂存 diff 一手事实,
|
|
24
24
|
// 树级反推违背判据原则)——沿增量门禁 pre-commit-only 先例,声明于 spec 风险评估 R2。
|
|
25
|
-
// 用法:node check-lane-surface.mjs [--root <dir>] [--branch <名>] [--staged-file <清单文件>]
|
|
25
|
+
// 用法:node check-lane-surface.mjs [--merge] [--root <dir>] [--branch <名>] [--staged-file <清单文件>]
|
|
26
26
|
// (--root/--branch/--staged-file 为测试注入位;缺省自 git 发现。staged-file 每行一个仓库相对路径)
|
|
27
27
|
// 退出码:0 放行(含 advisory);1 阻断(stderr 出 [标签] 消息与修法)
|
|
28
28
|
import fs from 'node:fs';
|
|
@@ -52,6 +52,10 @@ const STAGED = stagedArg
|
|
|
52
52
|
: gitOut(ROOT, ['diff', '--cached', '--name-only', '--diff-filter=ACMR']).split(/\r?\n/).filter(Boolean);
|
|
53
53
|
|
|
54
54
|
const isExperiment = /^experiment\//.test(BRANCH);
|
|
55
|
+
// 合并进行中(2026-10-15 自源仓 13f67ef7 回灌):pre-merge-commit 显式传 --merge(git merge 自动提交
|
|
56
|
+
// 窗口 MERGE_HEAD 已不可靠,只认显式旗标);冲突解决后的手动 commit 由 MERGE_HEAD 探测兜底(此时
|
|
57
|
+
// 合并仍在进行、MERGE_HEAD 确定存在)。合并提交不引入新首发改动,各笔已在自身提交时刻过门 → 检查 A 跳过。
|
|
58
|
+
const FORCE_MERGE = argv.includes('--merge');
|
|
55
59
|
// 泳道协议锚(单源 policy.mjs):无 kit.json 的测试夹具回落 v1(无键=双字段协议从未生效,单字段判)
|
|
56
60
|
const RISK_LEVEL_SINCE = loadKitPolicy(ROOT).riskLevelSince;
|
|
57
61
|
|
|
@@ -175,7 +179,10 @@ const hits = patterns.length ? STAGED.filter((p) => patterns.some((re) => re.tes
|
|
|
175
179
|
// 本次提交触达到的**最低应然档位**:lane-surfaces 命中隐含 L2(沿原口径),hints 命中按登记档取。
|
|
176
180
|
// 混档取高——与 level-criteria maxLevel 同一口径(就高不就低在提交面同样成立)。
|
|
177
181
|
const requiredLevel = maxLevel(hits.length ? 'L2' : '', hintLevel);
|
|
178
|
-
|
|
182
|
+
const merging = FORCE_MERGE || gitOut(ROOT, ['rev-parse', '-q', '--verify', 'MERGE_HEAD']).trim() !== '';
|
|
183
|
+
if (requiredLevel && merging) {
|
|
184
|
+
advisories.push(`[触达面判低](advisory)合并进行中(${FORCE_MERGE ? '--merge 显式标记' : 'MERGE_HEAD 在场'})——检查 A 跳过:合并提交不引入新首发改动,各笔已在自身提交时刻过门`);
|
|
185
|
+
} else if (requiredLevel) {
|
|
179
186
|
const active = collectActive();
|
|
180
187
|
const requiredRank = rankOf(requiredLevel) ?? 1;
|
|
181
188
|
// 入口是否够格:级别 rank ≥ 应然档。**suspect(级别/risk_level 缺失、非法或分歧)一律不够格**——
|
|
@@ -31,10 +31,10 @@ const w = (root, rel, content) => fs.writeFileSync(path.join(root, rel), content
|
|
|
31
31
|
// eol='\r\n' 时整文件按 CRLF 写(平台边界:frontmatter 读取容忍性)
|
|
32
32
|
const intent = (name, fm, eol = '\n') => `---${eol}${fm}${eol}---${eol}# INTENT — ${name}${eol}`;
|
|
33
33
|
const incident = (fm) => `---\n${fm}\n---\n# INCIDENT\n`;
|
|
34
|
-
const run = (root, { branch = 'main', staged = [] } = {}) => {
|
|
34
|
+
const run = (root, { branch = 'main', staged = [], args = [] } = {}) => {
|
|
35
35
|
const listFile = path.join(root, 'staged.txt');
|
|
36
36
|
fs.writeFileSync(listFile, staged.join('\n') + '\n');
|
|
37
|
-
const r = spawnSync(process.execPath, [SCRIPT, '--root', root, '--branch', branch, '--staged-file', listFile], { encoding: 'utf8' });
|
|
37
|
+
const r = spawnSync(process.execPath, [SCRIPT, '--root', root, '--branch', branch, '--staged-file', listFile, ...args], { encoding: 'utf8' });
|
|
38
38
|
return { code: r.status, out: `${r.stdout || ''}${r.stderr || ''}` };
|
|
39
39
|
};
|
|
40
40
|
|
|
@@ -77,6 +77,20 @@ const FM_LOW_NEW = '状态: draft\n级别: L1\n日期: 2026-10-01\n模块: ui';
|
|
|
77
77
|
const r = run(d, { branch: 'experiment/poc', staged: ['templates/t.md'] });
|
|
78
78
|
check('S2c experiment 分支检查 A → advisory 放行', r.code === 0 && r.out.includes('advisory·experiment'), r.out);
|
|
79
79
|
}
|
|
80
|
+
// --- S2d/S2e:合并进行中 → 检查 A 跳过(2026-10-16 自源仓 13f67ef7 回灌)---
|
|
81
|
+
// git merge 自动提交窗口 MERGE_HEAD 已不可靠 → pre-merge-commit 显式传 --merge(fixture 无 MERGE_HEAD,
|
|
82
|
+
// 恰验证 --merge 独生效);对照:同夹具不带 --merge 时命中面+仅活跃 L1 照拦。
|
|
83
|
+
{
|
|
84
|
+
const d = mkfix();
|
|
85
|
+
base(d);
|
|
86
|
+
const r = run(d, { staged: ['templates/_agents/scripts/x.mjs'], args: ['--merge'] });
|
|
87
|
+
check('S2d --merge 显式标记 → 检查 A 跳过且明示来源',
|
|
88
|
+
r.code === 0 && r.out.includes('合并进行中(--merge 显式标记)') && !r.out.includes('[触达面判低] 暂存命中高风险面'),
|
|
89
|
+
r.out);
|
|
90
|
+
const r2 = run(d, { staged: ['templates/_agents/scripts/x.mjs'] });
|
|
91
|
+
check('S2e 无 --merge 同夹具照拦(对照:跳过仅由旗标/MERGE_HEAD 触发)',
|
|
92
|
+
r2.code === 1 && r2.out.includes('[触达面判低]'), r2.out);
|
|
93
|
+
}
|
|
80
94
|
// --- S3:命中 + 无活跃入口 → advisory 放行(done 不算活跃)---
|
|
81
95
|
{
|
|
82
96
|
const d = mkfix();
|
|
@@ -284,5 +298,5 @@ const FM_LOW_NEW = '状态: draft\n级别: L1\n日期: 2026-10-01\n模块: ui';
|
|
|
284
298
|
`${r.stdout || ''}${r.stderr || ''}`);
|
|
285
299
|
}
|
|
286
300
|
|
|
287
|
-
console.log(`\n${
|
|
301
|
+
console.log(`\n合计: PASS ${pass} / FAIL ${fail}`);
|
|
288
302
|
process.exit(fail ? 1 : 0);
|