跨端: 采纳"记录投影"根治:删掉整条源码扫描器(含五轮补丁与自检),四个计数器改为从 records 投影;新判据是"记录数==文件数"的行为判据

pi 2026-09-15 两条都成立,第二条我**采纳并实现了**(`ef1c1a0` 之前的这轮改动)。

## 一★ "正则字面量里的引号"是**假绿方向** —— 我实测,比你写的还准

我用真 `stripStrings` 跑了你说的样本,**方向确认为"躲",而且躲掉的是真写**:

```
输入:   const RE = /["']/; totalTests += 1;
抹除后: const RE = /["                     ← 从这个 " 起一路吞到下一个 "
命中:   0 处          ← 你写的 0,我复现
```

★ 更要紧的是**假绿那一支**,我用"把第二处写藏进被吞区间"直接验了:

```
输入:   const RE = /["x]; totalTests += 1; OK"]/ ; totalTests += 1;
抹除后: const RE = /["                       "]/ ; totalTests += 1;
命中:   1 处(**未抹除时是 2 处**)
```

⇒ 真身**有两处写**,判据数出 **1** ⇒ **绿**。而"第二处写"正是这条判据**唯一存在的理由**
⇒ 这是**绕过**,不是误伤。我注释里原来写的是"能躲或误伤" —— 词是对的,**但读不出严重性**,
已按你的话改成"**这一侧是假绿**",并写明**绿的准确含义**:
"**在它能看见的文本里**没有第二处写",不是"没有第二处写"。

★ 你指的第二点也修了:三条样本原来只要求 `truePosRaw > 0`("至少一条命中")
⇒ 其中一条哪天不再是假阳性,自检**照样绿**。现在**逐条断言**"未抹 >0 且抹后 0",
并且失败报文分开说**是哪一侧**(未抹=0 ⇒ 这条样本白测了;真写被吃 ⇒ 假绿方向)。
变异验过:把样本 #1 的分号去掉 ⇒ 自检立刻响(旧写法**照样绿**)。

## 二★★ 投影:我**采纳了**,而且它确实把整族一起消掉了

你说得对,纯函数**仍然可以被调用两次** —— 那只是把"覆盖哪条路径"换个地方问一遍。
按你的形状改了:

```js
records.push({ file, checks: fileChecks, fail: …, exitCode: r.status });   // 唯一的写点
// 循环之后:
const reportedRecords = records.filter(r => r.checks !== null);
totalTests = reportedRecords.reduce((n, r) => n + r.checks, 0);
totalFail  = reportedRecords.reduce((n, r) => n + r.fail, 0);
ranFilesCount  = reportedRecords.length;
unreportedFiles = records.length - reportedRecords.length;
```

**删掉的东西**(这才是重点,不是"又加了一版"):

- 整条**源码扫描器**(注释 → 字符串 → `= X + n`/`X++` → 锚钉行首 vs 语句起点 → 正则引号,**五轮补丁**);
- 它的**抹除器自检**(`mkWrite` / 三条样本 / 逐条断言)—— **没有对象可扫了**;
- `stripStrings` 在 `run-all.mjs` 的 **import**(留着不用的 import 就是下一次"看着像还在用"的源头)。
  `stripStrings` 本身留在 `read.mjs`(通用工具),但它那条"正则引号"的边界**现在没有任何判据依赖** ——
  这一族的风险从"判据可能假绿"降级成"一个没被用的工具函数有已知边界"。

**行为判据**(替代那条文本判据):

```js
if (records.length !== SUITE.length) { … 并列出重复的文件 … }
```

**它抓得住那个形状**(注入"同一个文件 push 两次"实测):

```
RESULT files=25 ran=50 checks=800 …
✗ 套件自检 5:跑出的记录数 50 ≠ 套件文件数 25 —— 每个文件**恰好一条**记录
    重复的文件:test/xxx.test.mjs×2
```

**行为等价性**:`RESULT files=25 ran=25 checks=400 pass=399 fail=1 red=4 broken=0 unreported=0`
与改前**逐字节相同**。

★ 顺带修掉一处**我自己造的**同类毛病:`ok 5` 的报文原来还写着"四个计数器各只有一处'写',
**由源码扫描判**" —— 判据已经换了,文案没换。已改成"四个计数器由 25 条记录**投影**而来 ——
判的是记录数==文件数这个**行为**"。**改了判据却留着旧文案,就是又一次"说法与实现不一致"。**

## 三、★ 我要修正你一句措辞(实测后才敢说)

你写"**'把一个文件计两次'没有对应的语句可以写**"。我实测后认为**过强**:
`records.push` **仍然可以被复制粘贴两次**(上面那条注入就是),
而且那正是历史上真实发生的形状(搬动累加块时把旧路径留在原地)。

准确的说法是:**重复不再表现为"某个计数器被加两次",而表现为"同一条记录出现两次"** ⇒
它可以被**行为**判据抓住,而不是只能靠猜文本。

★ **这是"文本判据 → 行为判据",不是"可能错 → 不可能错"。**
前者可测,后者只是话好听 —— 我不想把这次改进说成后者。
(我第一版就在注释里写了"写不出来",然后自己想了想不对,改成上面这句。)

## 四、残余与射程

- 上面那条"记录数==文件数"是**唯一**的新判据;`ranFilesCount > SUITE.length` 留着当
  "投影被改坏"的探测器(投影下有构造性保证,所以它是冗余的 —— 我在注释里写明了"冗余",
  不假装它还是主力)。
- 【本轮未做】纯函数形状**没有**保留(按你的建议直接上投影了)。
- 其余状态:`ok 5`/`ok 6` 绿、红线仍 4 条(都不是我的)、探针残留 0。
This commit is contained in:
2026-09-15 13:34:22 +08:00
parent b50a81c362
commit 406bb34b47
2 changed files with 135 additions and 148 deletions

View File

@ -53,11 +53,26 @@ export function code(path) {
* ★ **行号必须不变**(与 `stripComments` 同一条硬要求):字符串里的每个换行
* 换成等价数量的空行。而且**保留引号本身**,这样"这里原本有个字符串"仍然看得见。
*
* ⚠️ 已知限制,写清楚免得下一个人以为它比实际更强:
* 逐字符扫描,**不区分正则字面量** —— `/a"b/` 里的 `"` 会被当成字符串开始
* (随后一直吃到下一个 `"`)。本仓判据里没有把真计数器名写进正则字面量的情形,
* 但这是**这个实现的边界**,不是"已经解决"。
* (真正根治是"判行为而不是判文本":把被扫的逻辑抽成纯函数,拿合成输入喂它。)
* ⚠️ 已知限制,**方向是"假绿",不是"误伤"** —— 这一点我第一版写错了,pi 2026-09-15 测出真方向:
* 逐字符扫描,**不区分正则字面量**。单个**不成对**的引号就够把后面一大段当字符串吞掉:
*
* 输入: const RE = /["']/; totalTests += 1;
* 抹除后: const RE = /[" ← 从那个 " 起,一路吞到**下一个 "**
* 命中: 0 处
*
* 两种后果,**要防的是第二种**:
* ① 常见的是**假红**(唯一那处写被吞 ⇒ 报"出现 0 处",而文案还指不到真因);
* ② **假绿**:被吞的区间里正好藏着**第二处写**,而第一处写在区间之外 ⇒ 计数仍是 1
* ⇒ **"重复"就在那里,判据却绿**。
* 实测:`const RE = /["x]; totalTests += 1; OK"]/ ; totalTests += 1;`
* ⇒ 真身 **2** 处,抹除后只数出 **1** 处。
*
* ★ 而"第二处写"正是这条判据**唯一存在的理由** ⇒ ② 是**绕过**。
* 所以这条判据的绿只能保证:"**在它能看见的文本里**没有第二处写" —— 不是"没有第二处写"。
* **把绿当成证明,就会在这里栽。**
*
* (修它需要词法状态机 —— 不划算,因为下一版要改成"记录投影",
* 那时"计两次"根本没有对应的语句可写,这一族连扫描对象都不存在。)
*/
export function stripStrings(src) {
let out = '';