pi 提的三条,第一条落地前先按他的要求**贴真实样本实测**,结论与他的猜测不同(记在代码里)。 ## 1 runner 内部那个"配错 flag = 绿" —— 实测后换了个形状 pi 的猜测:给自定义 `check()` 的判据传 `--test`,runner 会报"0 个测试"并以 0 退出。 实测(node v22.22.2,两条真实样本): - `node --test <自定义 check() 判据>`:**退出码照样传出来**(文件 exit 1 → 命令行 exit 1), 没有被吞; - 但 `node --test <什么都不做的文件>` 报 `# tests 1 / # pass 1` —— **pass 计数不是"检查跑过"的证据**。 所以"解析 pass 计数、0 就判红"这条路两头不讨好:抓不到空判据(它报 1), 还会在 `narrow-layout` 上误报(它的汇总行是"窄屏布局:全部通过",里面没有数字)—— 正是 pi 提醒的"别照抄我的正则,先贴样本"。 换成两条**结构证据**: - **`--test` 不再手写**:由文件内容推导(源码里 `from 'node:test'` 就走 node:test), 清单里出现手写 `--test` 直接红 —— 配对错误不再靠记性维护; - **自检 4**:每条判据文件里必须存在"能红"的路径(`test(` / `check(` / `process.exit(1)`), 外加"跑完必须有输出"。一个都没有 = 它永远不会红,与"全通过"长得一模一样 (这是"判据自己不会跑"家族的第 6 个宿主,家族表和六种宿主都写进规范了)。 变异:清单手写 `--test` → 红;加一条"什么都不做、退出 0"的判据 → 红; 静默成功(有能红路径但一行不输出)→ 红。 ## 2 规范 §3 补一档:变异红了还要看**红在哪**(pi) "只报红了不算,要能指名红的是哪几条";**红在解析/加载失败上不算红**(先让变异 "语法正确、语义错");变异作用于被剥掉的注释也不算。 ## 3 `CRITERIA.md` 的可见性(pi 提的位置问题) 它管两个客户端的判据,却躺在 electron 的测试目录里。已在 `docs/HARMONY-ALIGN-PLAN.md` §四(验收纪律)加指针,并顺手把 pi 点名过的两条口径写死在那儿: **"未验"只能用于"步骤做过、结果没看",功能不存在必须写"没做"**; **"机制上确定不同"要判、不许记成"未验"**(深色档预设那次)。 ## 验证 `npm test` 退出码 0(12 个判据文件全绿 + vitest 258/258)。
7.7 KiB
判据规范(写判据前先读这份)
这份文件记的是判据本身的写法:什么样的判据能红、能红在对的地方、以及不会在"代码完全正确"时乱红。 不是"怎么用 node:test",而是这个仓库里踩出来的形状。
一句话版本:判据要钉结构与行为,不要钉字面与邻接。
1. 判结构必须配对/解析;看邻接或固定宽度都会被合法写法绕过
统一形式(这是同一个坑在本仓露头的第三次,所以升格成规则):
判结构要配对/解析(括号、块、AST);看"前一个字符"或"固定宽度窗口"都会被合法写法绕过。
三次露头:
| # | 当时的写法 | 被什么合法写法绕过 |
|---|---|---|
| ① | 固定宽度窗口正则 | 往规则里加一行注释就把 backdrop-filter 挤出窗口 —— 断言变红而 CSS 完全正确(background.test.mjs 的注释里记着这件事) |
| ② | 括号配对取代窗口 | 修掉了 ①,方向对 |
| ③ | "调用点往前看一个字符是不是 }" |
ArkUI 的链式修饰符:Row(){…}.backgroundColor(x).backgroundBlurStyle(A) 里调用点前面是 ) 不是 } → 判据静默失效(拿到 null 就放过去了) |
③ 的修法:往回扫时跳过成对的括号组再找 }。它同时暴露了"叠"有两种形态这件事
(同一组件叠两次 / 套在另一层玻璃的子树里),所以 §2 的形状在这些规则里反复出现。
推论:同一条规则里出现 [\s\S]{0,N} 时先问一句"N 是怎么来的"。答不出数字来源的,
就是窗口式判据,换成"取出这条规则的 {...} 体再断言"。
已知存量(pi 2026-09-14 点名,WebUI 侧归他,别人不要顺手改):
client/electron/test/background.test.mjs 里还有一批 \{[\s\S]{0,80}…、{0,160} 窗口。
下次碰那个文件时按本节形状改成"取规则体",不要在那里再加一条注释算了。
2. 合成的清单 + 空 allow-list:条目要结构化
"清单从 SDK/参考实现生成 + allow-list 目前为空"是个好形状,但它迟早被侵蚀: 上游升版会往清单里加新条目 → 某天早上套件突然红,且红在与本次改动无关的代码上。 这时人的第一反应是把名字塞进 allow-list —— 而 allow-list 一旦这么用, 就不再是"研究过的例外",只是"红的止痛药"。
所以条目写成三元组,并断言后两项非空:
const ALLOW = [ /* { name, replacement, why } */ ];
replacement必须非空:"暂时不想改"不是放行理由,"替代品要求的 API level 高于本机基线"才是;- 不要断言"名单必须为空"(那会挡住合理放行);要断言的是"有名字、没替代品 → 红"。 这样侵蚀发生时红的是放行这件事本身,而不是某天的新 SDK。
同一个形状的实例:cross-client-theme.test.mjs 的 SELF_OWNED_COLORS(手写色登记表,
每条带理由)、GLASS_REGISTRY(玻璃面登记,每条带"为什么这里要玻璃")。
3. 判据必须能红,而且红的地方要对
每条新判据配一次变异验证:把源码改成"错的样子",确认它红,并且红在那条上。 变异没红有两种可能,都要查清:一是判据没覆盖,二是变异没真的生效 (本仓真发生过:变异脚本的锚点不匹配、缩进不对,于是"变异后依然全绿"被当成判据有效)。
顺带:变异后不要用 git checkout 还原(会连同未提交的改动一起抹掉)。
先 cp 到备份,再从事先的备份还原。
变异红了,还要看红在哪(pi 2026-09-14 补的一档)
只报"红了"不算证据。反过来的那一半同样成立:变异后红了,也可能是假红 —— 比如变异把文件写坏了语法,判据红在"模块加载失败/解析不到源码",看起来像判据生效, 实际那条断言根本没执行。所以:
- 报结果要能指名红的是哪几条(本仓的变异表就是按这个写的: "红 2 条(B + 裸色值)"、"红 3 条(含品牌色防线)");
- 红在解析/加载失败上不算红 —— 先让变异"语法正确、语义错",再谈判据有没有生效;
- 同理,变异作用于注释(被剥掉的那部分)也不算:判据读的是剥注释后的源码, 文档里的"理由"断言才读原文(见 §4)。
判据自己不会跑:一个家族,六种宿主
这条家族在本仓已经露头六次,共同点都是"看起来全绿":
| # | 宿主 | 形态 |
|---|---|---|
| ① | && 链 |
前面红一条,后面全部不跑("红"不可信) |
| ② | 判据文件 | 没接进 SUITE(写好了但隐身) |
| ③ | 清单名字 | 文件名写错 = 静默跳过一条判据 |
| ④ | 文件末尾 | 判据写在 process.exit() 之后(并发写入总往末尾追加) |
| ⑤ | 验证手法 | 用 node --test 去验 runner,里面的 process.exit(1) 被吞(见 §6) |
| ⑥ | runner 内部 | 清单 flag 与判据写法配错 |
run-all.mjs 现在对 ②③④⑥ 都有静态自检,⑥ 的落地方式见下(不要照抄"解析 pass 计数"):
⚠️ 实测过两条真实样本,结论与直觉不同:
node --test <自定义 check() 的判据>:退出码照样传出来(文件 exit 1 → 命令行 exit 1), 并没有被 runner 吞掉;- 但
node --test <什么都不做的文件>会报# tests 1 / # pass 1—— 计数不是"检查跑过"的证据。所以"数 pass、0 就判红"既抓不到空判据(它报 1), 又会在汇总行没有数字的判据上误报。
改用结构证据:每条判据文件里必须存在"能红"的路径(test( / check( / process.exit(1)),
外加"跑完必须有输出"。先看真实输出再写规则——这一条本身就是 §1 的推论。
4. 读源码断言的两种模式,别混用
- 断代码行为:读剥掉注释的源码(注释里出现的调用不是调用);
- 断**"理由写清了没":读原文**(理由就在注释里)。 本仓真踩过:用剥离注释的读取去断"注释里写了为什么用 Canvas",永远红。
5. 取片段按行/按块,别用偏移算术两头夹
src.indexOf('build() {') 会撞上文件里更早的同名成员;偏移差一个字符会把最后一行拦腰截断
(现象是"这行只剩 57 个字符"这种看着像文案、其实像切片的怪事)。
取"某个成员的正文"要按行扫到下一个同级成员,并加一条自检断言切片没跨到别的成员上。
6. 判据要接线,扫描范围要有下限
-
新增判据文件必须进
test/run-all.mjs的SUITE(run-all 的自检 2 会直接红); -
扫目录的判据要断言"至少扫到 N 个文件",并列出必须包含的子目录 (
pages/common/model/api/…)—— 目录改名/只扫一个子目录会让判据变成 空判据而依然全绿。 -
验证要按真实入口跑:
npm test走的是node test/run-all.mjs && vitest run。 用node --test test/run-all.mjs去验时,runner 只报"0 个测试"、把 runner 内部的process.exit(1)吞掉,于是"变异后依然 exit 0"看起来像判据失效(本仓刚踩过这一次: 自检 3 其实是好的,是我用错了入口去验它)。验判据要模拟用户/CI 真正跑的那一行。
7. 判据要钉用户真正会点的那一层
(移交信里交代的头号纪律)判据通过了但用户点不到,等于没做。 所以断言尽量落在"用户会触发的那个入口/那条路径"上: 例如"点同意/拒绝后列表要变"要钉到那条链路上,而不是钉"某函数存在"。