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],