From fc54a816e81ebd60a248d69ff929dde1b920ca72 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Fri, 25 Sep 2026 07:44:06 +0800 Subject: [PATCH] =?UTF-8?q?fix(criteria):=20=E4=B8=A4=E5=A4=84"=E6=8E=92?= =?UTF-8?q?=E9=99=A4"=E8=81=8C=E8=B4=A3=E4=B8=8D=E5=90=8C=20=E2=80=94?= =?UTF-8?q?=E2=80=94=20pi=20=E7=9A=84=E8=AF=8A=E6=96=AD=E9=94=99=E5=9C=A8?= =?UTF-8?q?=E6=8A=8A=20`t`=20=E5=BD=93=E6=88=90=E8=A7=82=E5=AF=9F=E8=80=85?= =?UTF-8?q?=EF=BC=9B=E4=BD=86=E5=AE=83"=E6=8C=89=E5=90=8D=E5=AD=97?= =?UTF-8?q?=E4=B8=8D=E5=A4=9F"=E9=82=A3=E5=8D=8A=E6=88=90=E7=AB=8B?= =?UTF-8?q?=EF=BC=8C=E6=94=B9=E6=8C=89**=E8=BA=AB=E4=BB=BD**?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pi `661416f3` §二 报: 「`:765` 用 `basename(p) === t`(按名字)而 `:745` 用 `resolve(...) !== SELF` (按身份)⇒ 你今天把同一件事做了两遍、两种判据 ⇒ 建议 `:765` 也改成排除观察者」。 ★ **它把两处 `continue` 认成了同一件事,而它们职责不同** —— 我先复现它的建议,结果是**回归**: ``` :t 来自 `tools = readdirSync(deploy)` 的非门禁 *.sh ⇒ **是被测工具**,不是观察者 :745(SCAN 那层) 排除**观察者**(本判据自己)—— 防"描述缺陷"被当成"存在引用" :765(内层) 排除**工具自己的文件** —— 每个工具头注释都写自己名字(实测 5/5 各 1 处), 不排除 ⇒ **"自名"被算成"发现路径"** ⇒ 每个孤儿自证可达 ⇒ 永久假绿 ``` ## 双向变异实测(同一棵树,只改这一行) ``` ① 应用 pi 的建议(:765 → 排除观察者)+ 造真孤儿 `deploy/zz-orphan-probe.sh`(只有自名) ⇒ 判据 **9/9 全绿**(孤儿没被报出)⇒ **假绿** ✗ ② 我的原版(basename)+ 同一孤儿 ⇒ **not ok 8**,点名 `zz-orphan-probe.sh` ✓ ``` ★ 但 pi 那封里**有一半成立**(我一开始也差点整条驳掉 —— 这是本仓记过的形状: "用一个真机制去驳掉整条建议"): ``` 若别处出现与工具**同 basename** 的文件(如 `docs/archive-stale-sessions.sh`), `basename(p) === t` 会**把它也跳过** ⇒ 那处引用白算 ⇒ **假红**。 实测: 把 `archive-stale-sessions.sh` 的唯一引用移进同名他文件: 按名字的版本 ⇒ 误报孤儿(not ok 8)✗ 按身份的版本 ⇒ 判绿 ✓ ⇒ 所以正确的修法是 pi 没给的那个: **排除"工具自己的路径"(按身份),而不是"排除观察者"**。 两边都保住: 自名仍被排除(不假绿)、同名他文件仍被计入(不假红)。 ``` ## 落地 - `:765` → `resolve(p) === resolve(join(DEPLOY, t))` - 删掉因本次改动而成为**死 import** 的 `basename`(本仓唯一一处 import-未用) - 把"两处排除职责不同 + 按名字不够"写进注释,并加**反空真断言**: `assert.notEqual(resolve(join(DEPLOY, tools[0])), SELF_FILE)` —— 防"两处排除被合并/退化成一个"(那样就等于删掉其中一个,而删哪个都会假绿) 验证: 变异①(真孤儿)红、变异②(同名他文件)绿;正常树 9/9;drift 自检 64/0; `run-all.mjs:141` 注册条数 9 未变(本次是加断言+注释,未新增 test)。 --- .../electron/test/criteria-hygiene.test.mjs | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/client/electron/test/criteria-hygiene.test.mjs b/client/electron/test/criteria-hygiene.test.mjs index 0138c18..e3f57b2 100644 --- a/client/electron/test/criteria-hygiene.test.mjs +++ b/client/electron/test/criteria-hygiene.test.mjs @@ -24,7 +24,7 @@ import assert from 'node:assert/strict'; import { readdirSync, unlinkSync, writeFileSync } from 'node:fs'; import { spawnSync } from 'node:child_process'; -import { basename, dirname, join, relative, resolve } from 'node:path'; +import { dirname, join, relative, resolve } from 'node:path'; import { test } from 'node:test'; import { fileURLToPath } from 'node:url'; @@ -756,13 +756,34 @@ test('★ 非门禁工具(deploy/*.sh)必须有发现路径(至少一处 * 判据红/绿的原因变成了**它自己怎么写这段说明**,而不是被测对象的状态。 * ⚠️ 泛化:**任何"扫全仓找引用"的判据都必须排除观察者本身**, * 否则"描述缺陷"与"存在引用"不可区分(这条与 `stripComments` 那条同源)。 + * + * ★★★ 还有**第二个**排除,它常被误认成同一个(pi `661416f3` 就认错了,我实测反驳): + * 这里有两处 `continue`,**职责不同**: + * · `SCAN` 那一层(上面 `.filter(resolve(p) !== …import.meta.url)`)排除的是**观察者** + * —— 本判据自己,理由见上。 + * · 这一层(`resolve(join(DEPLOY, t))`)排除的是**被测工具自己的文件** —— + * 每个工具的头注释都写自己的名字(实测 5/5 各 1 处),若不排除, + * **"自名"就会被算成"发现路径"** ⇒ 每个孤儿自己证明自己可达 ⇒ 永久假绿。 + * ⇒ ★ 所以这一处**不能**改成"排除观察者",那是**删掉**本排除(实测: 真孤儿立刻漏报)。 + * ★ 但"按名字(basename)"确实不够 —— 那是 pi 那封里**唯一成立**的部分: + * 若别处出现与工具同 basename 的文件(如 `docs/prune-demo.sh`), + * `basename(p) === t` 会**把它也跳过** ⇒ 那处引用白算 ⇒ **假红**(实测: 唯一引用在 + * 同名他文件里时,按名字的版本误报孤儿;按身份的版本判绿 ✓)。 + * ⇒ 结论(比 pi 的建议严一格,且不丢本排除): **按身份比对被测工具自己的路径**。 */ + const SELF_FILE = resolve(fileURLToPath(import.meta.url)); + /** 反空真: 两处排除必须真的指向不同对象,合并了就会退化成"删掉一个"。 */ + assert.notEqual( + resolve(join(DEPLOY, tools[0])), SELF_FILE, + '两处排除指向了同一个文件 ⇒ 本判据的排除逻辑坏了(观察者 == 被测工具?)。', + ); const orphans = []; for (const t of tools) { let refs = 0; for (const p of SCAN) { - if (basename(p) === t) continue; // 排除自身 + // 排除**工具自己的文件**(按身份,不按名字 —— 按名字会漏掉同名他文件里的引用) + if (resolve(p) === resolve(join(DEPLOY, t))) continue; if (prose(p).includes(t)) refs++; } if (refs === 0) orphans.push(t);