fix(pi-bridge): --self-check 此前**一次都没在真实路径上跑过** —— 自检改进程内、默认路径先跑、量纲分三档

dsh 2026-09-18 在本插件里实测报的第二例(同一形状:判据存在但不在路径上)。

## 洞(实测,不是读代码)

· `package.json` 的 `npm test` = `env-preflight && run-suite`,**不带 `--self-check`**;
  `deploy/install.sh` 也是 `npm test`,同样不带;全仓 `--self-check` 零引用,
  本插件下**没有任何 .md** 提到它 ⇒ 它从没在真实路径上跑过。
· 实测:把 `findDuplicates` 改成恒返回 `[]` ⇒ **`npm test` 照样 `exit=0`**(带 flag 才红)。
· 后果不是小事:它是**跨文件重名**判据的引擎,而它**唯一**的分辨力判据就是这个自检
  ⇒ 引擎哪天退化成恒空,重名判据**安静地永远放行**,而整套测试全绿。
· ⚠️ 判"在不在路径上"**不能 `grep` 数命中**:`grep -c '判据自检'` 在默认跑里得 **16 次**,
  全是若干 `.test.mjs` 的**用例名**恰好含这四个字,而 `selfCheck()` 自己的输出
  (`^  (通过|失败)  `、`干净样本`)**一次都没有**。**数命中 = 数到的是词,不是调用。**

## 修

1. `selfCheck()` 去掉 `process.exit`,改为**返回失败条数**(否则没法进程内调);
   `--self-check` 那条 CLI 行为不变。
2. **默认路径先跑自检**:坏了就 `exit 3` 并明说"工具坏了、本次结论不可信"。
   顺序排在跑套件**之前** —— 引擎坏了时套件那份结论本来就不可信,
   而且省一整轮 509 条测试进程。
3. **量纲分三档**(本文件已约定的那套 + 新增一档):
   `0`=全绿且无重名;`1`=断言失败/发现重名(**代码问题**);
   `2`=环境;**`3`=判据引擎自己坏了(工具问题)** —— 不复用 `1`,
   否则 `install.sh` 会把"引擎坏了"读成"代码有问题"。`install.sh` 本次不动
   (它只判"插件装好没有",非零即中止,3 与 1 对它的行为一致;改它的退出码语义是另一件事)。

## 验证(都跑过)

· 干净树:`npm test` rc=**0**,默认路径出现 `判据自检:全部通过。`(此前 0 次)。
· **M**:`findDuplicates → []` ⇒ `npm test` rc=**3** + `判据自检 2 条失败 —— findDuplicates 的分辨力坏了…`。
· **反向对照**:真造一个跨文件重名 ⇒ rc=**1** + 点名 `2× 平台名字正常时派生别名并原样带标题`
  ⇒ 两个量纲**确实分开**,没把"代码问题"和"工具问题"混成一个码。
· `node --check` 通过;`--self-check` 单独跑 rc=0(行为不变)。

(补 dsh 交回的实测读数:`--self-check` 在干净树 4/4 通过、变异后 2/4 失败、默认跑 0 次。)
This commit is contained in:
2026-09-18 07:19:26 +08:00
parent 957ec5ec77
commit cc8beb79db

View File

@ -106,6 +106,24 @@ const SELF_CHECK_SAMPLES = [
},
];
/**
* 判据自检:拿合成 TAP 样本验 `findDuplicates` 的**分辨力**。
*
* ★★ 返回失败条数,**不自己 `process.exit`** —— 这样它能在默认路径里被调用。
* 为什么必须能(pi 2026-09-18 实测报的洞):
* · `package.json` 的 `npm test` = `env-preflight && run-suite`,**不带 `--self-check`**;
* `deploy/install.sh` 也是 `npm test`,同样不带。全仓 `--self-check` 零引用,
* 且本插件下**没有任何 .md** 提到它。
* · ⇒ 它此前**一次都没在真实路径上跑过**。实测:把 `findDuplicates` 改成恒返回 `[]`
* ⇒ **`npm test` 照样 `exit=0`**(而带 `--self-check` 才红)。
* · 后果不是小事:`findDuplicates` 是**跨文件重名**那条判据的引擎,
* 而它**唯一**的分辨力判据就是这个自检 ⇒ 它哪天退化成恒空,
* 重名判据会**安静地永远放行**,而整套测试全绿。
* ⚠️ 判"自检在不在路径上"**不能靠 `grep` 数命中**:本仓库实测 `grep -c '判据自检'`
* 在默认跑里得到 **16 次**,全是若干 `.test.mjs` 的**用例名**恰好含这四个字,
* 而 `selfCheck()` 自己的输出(`^ (通过|失败) `、`干净样本`…)**一次都没有**。
* **数命中 = 数到的是词,不是调用。**
*/
function selfCheck() {
let bad = 0;
for (const s of SELF_CHECK_SAMPLES) {
@ -118,12 +136,40 @@ function selfCheck() {
}
}
console.log(bad === 0 ? '\n判据自检:全部通过。' : `\n判据自检:${bad} 条失败。`);
process.exit(bad === 0 ? 0 : 1);
return bad;
}
if (process.argv.includes('--self-check')) selfCheck();
// `--self-check`:只跑自检、不跑套件(原有行为不变)。
if (process.argv.includes('--self-check')) process.exit(selfCheck() === 0 ? 0 : 1);
// ── 跑套件 ────────────────────────────────────────────────────────
/*
* ★★ **默认路径也要先跑自检**(pi 2026-09-18)。
*
* 顺序要紧:**自检排在跑套件之前**。理由是量纲 —— 自检判的是"引擎的分辨力",
* 套件判的是"代码对不对"。引擎坏了的时候,套件那份结论**本来就不可信**,
* 拿它当主结论是把两件事混在一起(`docs/DEV-TOOLING.md:218` 那条 = 退出码也有量纲)。
* 而先跑自检还省一整轮测试进程(引擎坏了就没必要跑 509 条)。
*
* 退出码用本文件已经约定的那一套,并**与套件红分开**:
* 0 = 套件全绿且无重名(且自检通过)
* 1 = 断言失败 / 发现重名(代码问题)
* 2 = 环境(env-preflight 那套)—— 自检失败**不走这一格**
* ★ 自检失败 = **判据本身坏了**,这是"工具问题"而不是"被测代码问题",
* 所以它**不复用 `1`**(那会让 `install.sh` 把"引擎坏了"读成"代码有问题")。
* 这里用 **3** 表达"这次结论不可信(工具坏了,不是代码坏了)",
* 并在 stderr 里**明说**是哪个量纲 —— 量纲说不清就退化成"又一个红"。
* (`install.sh` 那侧的退出码语义**本次不动**:它只判"插件装好没有",
* `set -e` 下非零即中止,3 与 1 对它的行为一致;改它的语义是另一件事,
* 且要先想清楚 `install.sh` 该不该为此新增一档。)
*/
const selfCheckBad = selfCheck();
if (selfCheckBad > 0) {
console.error(`[tap-dup] 判据自检 ${selfCheckBad} 条失败 —— \`findDuplicates\` 的分辨力坏了,`
+ '**本次重名判据的结论不可信**(工具坏了,不是被测代码坏了)。');
process.exit(3);
}
// `--test-reporter=tap` 让报告格式稳定(`--test` 在 TTY 下会自动换格式,
// 那样就锚不住结果行)。输出走管道,本进程负责原样转写到 stderr。
const args = ['--test', '--test-reporter=tap', 'test/*.test.mjs'];