Skip to content

Commit 049686a

Browse files
fix(scripts): check:nul-bytes 按载体扫描所有被跟踪的文本文件 (#4890) (#4907)
这道门禁自己的报错文案写着它拦的是什么:一个裸 NUL 让 grep/ripgrep 把整个 文件当成二进制、静默返回零匹配。那是 grep 的行为,与文件是什么语言无关 —— 但扫描面此前是一份 JS/TS 扩展名清单,范围(用途)与理由(载体)对不上。 代价是 .claude/ 下的全部 markdown 同时落在三道门禁之外。#4890 就是这么暴露 的:PR #4885 要写的规则正是「不要写裸 NUL」,而写的过程中一个真的裸 NUL 落进 了 .claude/skills/pm-dispatch/SKILL.md,这道门禁报 OK。 改为扫描所有被 git 跟踪的文本文件。二进制判据是内容判断而非扩展名清单: 非常规文件(symlink / gitlink)跳过;UTF-16/32 BOM 开头跳过(那种编码里 NUL 是结构性的);其余先剔除 NUL 字节、再整文件按 UTF-8 严格解码,解不通才算 二进制。先剔除 NUL 是为了打破「有 NUL 所以是二进制所以不查 NUL」这个 git 掉 进去的循环;整文件解码而非只看前缀,是因为 git 只嗅前 8000 字节正是本文案里 记着的盲区。 同时按仓内惯例把 --self-test 接进 check:nul-bytes 脚本,自检在临时 git 仓库 里跑真实的 scan() 路径。 Claude-Session: https://claude.ai/code/session_018iARDqtrhQgz6fVHDeDkbQ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 07ce845 commit 049686a

3 files changed

Lines changed: 314 additions & 48 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
---
3+
4+
chore(scripts): `check:nul-bytes` 按载体划范围 —— 扫描所有被 git 跟踪的文本文件,而非只扫 JS/TS (#4890)
5+
6+
这条门禁自己的报错文案就写明了它拦的是什么:一个裸 NUL 会让 grep/ripgrep 把**整个文件**当成二进制、**静默返回零匹配**,文件因此从代码检索和每一条基于 grep 的 lint 里消失。那是 **grep 的行为**,与文件是什么语言无关 —— 但它的扫描面此前是一份 JS/TS 扩展名清单,于是范围(用途:源代码)和理由(载体:文本)对不上。
7+
8+
对不上的代价是 `.claude/` 下的全部 markdown 同时落在三道门禁之外:`check:nul-bytes` 只看 JS/TS,`check:doc-authoring``ROOTS` 是顶层 `skills/``content/`,eslint 的 `files` glob 只有 JS/TS 扩展名。#4890 就是这么暴露的:PR #4885 **要写的规则正是「不要写裸 NUL」**,而写的过程中一个真的裸 NUL 落进了 `.claude/skills/pm-dispatch/SKILL.md`,这道门禁报 OK —— 靠一次额外的、非常规的控制字符扫描才发现。一份带裸 NUL 的 SKILL.md 对 `grep -r` 隐形,agent 拿不到它本该遵守的规则,且没有任何信号。
9+
10+
现在扫描面是**所有被 git 跟踪的文本文件**。二进制判据是内容判断,不是扩展名清单(那只是把「为什么是这些文件」挪到下一个新目录去重演):非常规文件(symlink / gitlink)跳过;UTF-16/32 BOM 开头的跳过(那种编码里 NUL 是结构性的);其余**先剔除 NUL 字节、再整文件按 UTF-8 严格解码**,解不通才算二进制。先剔除 NUL 是关键 —— 否则「文件里有 NUL,所以是二进制,所以不检查它有没有 NUL」正是 git 掉进去的那个循环;整文件解码而非只看前缀,也正是因为 git 只嗅前 8000 字节是本检查文案里记着的那个盲区(protocol.ts 的 NUL 在第 147230 字节)。因此一个扩展名从没见过的新文本文件默认被扫到。
11+
12+
实测:2953 个文件 / ~150ms → 4974 个文本文件 / ~505ms(跳过 5 个真二进制资产与 1 个 symlink),main 上无既存文件判红。同时按仓内既有惯例把 `--self-test` 接进 `check:nul-bytes` 脚本(临时 git 仓库里跑**真实的 `scan()` 路径**),否则一条只观察到绿的门禁与一条什么都匹配不上的门禁从外部无法区分。工具链改动,不发版。

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"i18n:extract": "tsx packages/cli/bin/run-dev.js i18n extract packages/platform-objects/scripts/i18n-extract.config.ts --locales=zh-CN,ja-JP,es-ES --fill=default --out=packages/platform-objects/src/apps/translations",
3333
"check:i18n": "node scripts/check-i18n-bundles.mjs --self-test && node scripts/check-i18n-bundles.mjs",
3434
"check:i18n-coverage": "node scripts/check-i18n-coverage.mjs",
35-
"check:nul-bytes": "node scripts/check-nul-bytes.mjs",
35+
"check:nul-bytes": "node scripts/check-nul-bytes.mjs --self-test && node scripts/check-nul-bytes.mjs",
3636
"check:doc-authoring": "node scripts/check-doc-authoring.mjs",
3737
"check:role-word": "node scripts/check-role-word.mjs",
3838
"check:adr-anchors": "node scripts/check-adr-anchors.mjs",

0 commit comments

Comments
 (0)