From c4279a95cedf8318bd3665fcc1621679edb9243d Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Fri, 25 Sep 2026 07:58:36 +0800 Subject: [PATCH] =?UTF-8?q?feat(criteria):=20=E6=96=87=E4=BB=B6=E5=90=8D?= =?UTF-8?q?=E4=B8=8D=E5=BE=97=E6=98=AF"=E5=8F=A5=E5=AD=90=E9=87=8C?= =?UTF-8?q?=E7=9A=84=E4=B8=80=E6=AE=B5"=20=E2=80=94=E2=80=94=20=E6=88=91?= =?UTF-8?q?=E4=B8=BA**=E8=87=AA=E5=B7=B1=E5=88=B6=E9=80=A0=E7=9A=84**?= =?UTF-8?q?=E9=82=A3=E4=B8=AA=E5=8C=96=E7=9F=B3=E5=8A=A0=E5=AE=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 我制造了什么(本会话 `34bcd3e9` 那轮,实测复现) ``` 我在 bash 里写了一句带嵌套未转义双引号 + 裸 `>` 的说明: echo " ⇒ 所以"mtime > 动作时刻是**必要但不充分**,且会被**后人无关的写**覆盖"" bash 把 `>` 读成**重定向** ⇒ **在仓库根建了一个文件**: 文件名 = `动作时刻是**必要但不充分**,且会被**后人无关的写**覆盖` 内容 = ` ⇒ 所以mtime`(18 字节) ⇒ 我在检查工作树时看到 `?? "动作时刻是…"` 才发现的。 用 /tmp 里的最小复现验证到**同名同内容** ⇒ 确认是我的 shell 引号事故,不是别人的。 ``` ## 为什么值得一条判据(它会被提交) ``` · 未跟踪文件**不会**被普通 `git add ` 带上 ⇒ 平时看不见、不会被门禁提醒; · 但本仓**明文记录过 `git add -A` 事故**(`docs/DEV-TOOLING.md`: 含把约 7000 行重排扫进功能提交那次) ⇒ 下一次 `-A` 就会把这个"句子文件"带进历史,而**进了历史就永远删不干净**; · 它的**名字本身就说明它是无意的** —— 没有人的文件名会是"…且会被**后人无关的写**覆盖"。 ``` ## 判据(零先例实测 ⇒ 不误伤) ``` 仓库内(排除 node_modules/.git/build/release/dist/.hvigor/.codegraph/oh_modules) 不得有名字含**中文标点**(,。、!?;:()「」“”《》【】)、**markdown 粗体 `**`**、或**换行**的文件 ★ 判**名字的形态**,不判目录 —— 按目录裁豁免正是本仓记过的"给逃逸指路"。 实测先例: 加这条之前全仓命中 **0**(根目录 0、全仓 0)⇒ 不误伤任何现存文件。 ``` ## 双向变异 ``` ① 重建那个句子文件名 ⇒ **not ok 10**,逐个点名 ✓(上面就是它抓到的输出) ② 删掉 ⇒ 10/10 绿 ✓ ``` ## 顺带 ``` run-all.mjs 注册条数 9 → **10**(新增 test 必须登记精确条数,本仓约定) ``` ★ 这条与我这轮前两条(`fc54a81` 两处排除、`b16c38a` "提到≠发现路径")是**同一族**: **"看起来像在表达一件事"的东西被当成了"它就是那件事"** —— 前两条在判据的**匹配口径**里,这条在**文件名**上(一段散文被当成了路径)。 验证: criteria-hygiene 10/10;drift 自检 64/0;两文件 `node --check` 通过; 现场: 探针已删(`git status` 只剩本提交的两个文件)。 --- .../electron/test/criteria-hygiene.test.mjs | 55 +++++++++++++++++++ client/electron/test/run-all.mjs | 2 +- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/client/electron/test/criteria-hygiene.test.mjs b/client/electron/test/criteria-hygiene.test.mjs index 5612a79..089fb4a 100644 --- a/client/electron/test/criteria-hygiene.test.mjs +++ b/client/electron/test/criteria-hygiene.test.mjs @@ -869,3 +869,58 @@ test('★ ⑤b 的负向清单在 JS 与 shell 两处都有副本,两份都必 + ' 而 `redeploy-gateway.sh` **没有** ⇒ 只守前者的话,删掉/写漂 shell 那份不会红,\n' + ' 而 shell 那份是**部署当时打印 `ok` 行**所用的文本。'); }); + +/* + * ★★ 仓库里不得有"**句子里的一段**"当文件名(shell 引号事故的化石)。 + * + * ## 为什么存在(我 `34bcd3e9` 那轮亲手制造了一个) + * + * 我在 bash 里写了一句带**嵌套未转义双引号 + 裸 `>`** 的说明文本,形状是: + * echo " ⇒ 所以"mtime > 动作时刻是**必要但不充分**,且会被…覆盖"" + * bash 把 `>` 读成**重定向** ⇒ **在仓库根建了一个文件**,文件名是那句话的后半截 + * (`动作时刻是**必要但不充分**,且会被**后人无关的写**覆盖`),内容是我 echo 的前半截。 + * 实测复现: 同名同内容(`⇒ 所以mtime`,18 字节)。 + * + * ## 为什么值得一条判据(不是我手滑,是它会**被提交**) + * + * · 未跟踪文件**不会**被普通 `git add ` 带上 ⇒ 平时看不见; + * · 但本仓**明文记录过三次 `git add -A` 事故**(`docs/DEV-TOOLING.md`,含把 7000 行 + * 重排扫进功能提交那次)⇒ 下一次 `-A` 就会把这个"句子文件"带进历史, + * 而它**永远删不干净**(进了历史就得改写)。 + * · 它的**名字本身就说明它是无意的** —— 人的文件名不会是"…且会被**后人无关的写**覆盖"。 + * + * ## 判据(零先例实测 ⇒ 不误伤) + * + * 仓库内(排除忽略目录)**不得有**名字含中文标点、markdown 粗体标记、或换行的文件。 + * 实测: 加上这条之前,全仓命中 **0**(根目录 0、全仓 0)⇒ 收紧不误伤任何现存文件。 + * ★ 判的是**名字**(形态),不判"哪个目录" —— 按目录裁豁免正是本仓记过的"给逃逸指路"。 + */ +test('★ 文件名不得是"句子里的一段"(shell 引号事故的化石;含中文标点/粗体标记)', () => { + // 忽略目录:构建产物与依赖,它们的名字不受本判据约束 + const SKIP = new Set(['node_modules', '.git', 'build', 'release', 'dist', '.hvigor', '.codegraph', 'oh_modules']); + const PUNCT = ',。、!?;:()「」“”《》【】'; + + const bad = []; + const walk = (dir) => { + let entries; + try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return; } + for (const e of entries) { + if (SKIP.has(e.name)) continue; + const full = join(dir, e.name); + if ([...PUNCT].some((c) => e.name.includes(c)) || e.name.includes('**') || e.name.includes('\n')) { + bad.push(relative(REPO_ROOT, full)); + } + if (e.isDirectory()) walk(full); + } + }; + walk(REPO_ROOT); + + assert.deepEqual(bad, [], + `这些**文件名是"句子里的一段"** —— 几乎必然是一次 shell 引号事故留下的化石:\n` + + ` ${bad.join('\n ')}\n` + + ' ⇒ 常见成因: `echo "…"某词 > 后半句"` —— 嵌套未转义双引号让 `>` 变成**重定向**,\n' + + ' bash 于是在该目录**新建一个文件**,名字是那句话的后半截。\n' + + ' ⇒ 处置: 删掉它;并把说明文本改用 `printf \'%s\\n\'` 或单引号包裹(避免 `>` 被解析)。\n' + + ' ⚠️ 为什么必须现在删: 未跟踪文件普通 `git add ` 带不上,**但它会被 `git add -A` 带上** ——\n' + + ' 而本仓已记录过三次 `-A` 事故;一旦进了历史,这个名字就永远在那儿了。'); +}); diff --git a/client/electron/test/run-all.mjs b/client/electron/test/run-all.mjs index c9f88d9..0c561ce 100644 --- a/client/electron/test/run-all.mjs +++ b/client/electron/test/run-all.mjs @@ -138,7 +138,7 @@ const SUITE = [ ['test/commit-hygiene.test.mjs', ['--experimental-strip-types', '--no-warnings'], 4], // 判据目录自身的卫生:读文本必须走 test/lib/read.mjs 的具名入口; // 非门禁工具必须有**发现路径**;⑤b 负向清单两处副本都要点名同一对失败类 - ['test/criteria-hygiene.test.mjs', [], 9], + ['test/criteria-hygiene.test.mjs', [], 10], // 用户管理页(P4c 同批):动作↔服务端调用同名 / 门禁只认严格 admin / // 启停只发 status / 「受限」徽标口径 / 页面零写死色值 / 接线(纯逻辑真被调用) ['test/harmony-admin.test.mjs', ['--experimental-strip-types', '--no-warnings'], 31],