fix(criteria): 两处"排除"职责不同 —— pi 的诊断错在把 t 当成观察者;但它"按名字不够"那半成立,改按**身份**
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)。
This commit is contained in:
@ -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);
|
||||
|
||||
Reference in New Issue
Block a user