现状口径:/keil-build 与 /serial-boot-test 是 DSH 会话的技能调用名, 不是 shell 命令;「名 + 命令行参数」的命令形态改为可执行体 run_boot_test.py 的真实调用。 - AGENTS.md:别名说明重写为「技能调用名不是 shell 命令」,两条目改 「编译(/keil-build)」「串口回归(/serial-boot-test)」 - keil-build / serial-boot-test 两 skill 调用名与示例同步;task-worktree 方案文档去掉 @build/@test 惯例从句;@task 保留(不在点名范围) - 123 文件现状陈述全改;台架捕获、变更记录/时间线、rename 映射输入 等 41 行历史证据保留旧名 - gen_memory_svg.py 6 处措辞随改,3 张 SVG 产物同批重跑 - 顺手补并行方案同行漏网计数 51→52 钩子 验证:提交期快钩子对 123 文件实跑全 Passed;check_doc_links --all rc=0(3038 处、无法判定 0);check_mermaid --all rc=0;YAML 解析 52 钩子不变;skill-size rc=0;py_compile 全过;字节卫生零漂移。
5.7 KiB
bom-guard — 带 BOM 文件被剥掉即拦(防"假语法错")
一句话:编辑工具会静默剥掉文件原有的 UTF-8 BOM,ARMCC 于是换了一套解码假设,报出来的 却是
#870-D/#8/#274这类语法错——错的位置和内容都不指向真因。这条门禁让"补回 3 个字节"这件事在提交时就必须处理,而不是等下一个人构建失败再去猜。
1. 它在查什么
对 pre-commit 传来的、落在名单内的文件,比较 HEAD 版本与工作区版本的前 3 字节:
HEAD 是 EF BB BF 而工作区不是 → FAIL 并列出文件与补救动作。只此一条判定,没有存量豁免。
名单(三处互指,见 §6.2):Drivers/BSP/UI/**、App/task/ui_task.c、App/cfg/cfg_file.c、
Lib/lv_conf.h。
2. 为什么要它(拦下过什么真事)
- 立项直接来自一天内 5 次踩坑:2026-10-25 道E 在 task-15(UI 页面批次)里连踩 4 次,
道B 在批 1 踩过 1 次。每次的症状都一样:改完 UI 源文件、
/keil-build突然报一串语法错,而改动 本身是加注释或改常量;MDK-ARM/build_log.txt里的行号指向中文注释所在的上一行, 照着报错去查会查不出任何东西。真因只是文件开头少了 3 个字节。 - 别的门禁和编译器都不会响:MSVC(模拟器侧)带不带 BOM 都编得过、0 警告;
no-mojibake查的是U+FFFD(已经损坏的痕迹),BOM 刚丢时文件字节完全合法,它看不见;coding-standard也不看编码。也就是说,这条坑当时唯一的暴露点是"重编该文件",而历史 0 警告不代表安全。 - 人工审计只防得住记得做的人:立项前道E 的做法是每次提交前逐文件比 HEAD 查 BOM,
四次里有一次是靠"看图发现满屏横杠"顺手查的,不是靠纪律——纪律型检查不成立(同类口径见
docs/代码规范/pre-commit使用说明.md§5.5)。 - 顺带一条同族事实(写在这里而不是别处):本门禁自己的立项提交被
gitlint拦过一次—— 正文有一行 92 > 80,作者量出来了仍然按了提交。「拦下过什么真事」这一栏如果只挑别人的事写, 就等于在暗示写门禁的人不需要门禁,那是不成立的。
3. 触发面
.pre-commit-config.yaml 里 bom-guard 的 files 正则(等价于 §1 名单)。不在名单内的文件
不报也不提示——比如有人在 App/log/ 新建带 BOM 的文件,本门禁不管(那种文件按约定就不该
带 BOM;真带上了,编译器会替我们响)。
⚠ files 不命中时钩子是 Skipped,Skipped ≠ Passed:一次只改文档的提交里看不到它变绿
是正常的,不代表它检查过了。
4. 报错怎么读 / 单独跑
# 常规由 pre-commit 自动跑;人工核全仓(含没改过的文件):
& '.venv\Scripts\python.exe' scripts/check_bom_drift.py --all
# 自证判定(临时 git 仓库,含两条边界用例):
& '.venv\Scripts\python.exe' scripts/check_bom_drift.py --selftest
报错会点名文件并给补救动作:只把 3 个字节 EF BB BF 补回文件最前面。不要整文件重写
(Set-Content / 脚本按系统默认编码重打中文会把"可修的丢 BOM"变成"不可修的 U+FFFD",
那是 no-mojibake 都救不回来的损坏)。
5. 好处
- 把一类"只有重编译才暴露、且报错完全误导人"的缺陷挡在提交点,且提示直接给出正确修法;
- 逼着 BOM 名单保持单一口径(§6.2 的三处互指)——名单一旦散着改,本门禁立刻红;
- 判定极简(两个 3 字节比较),几乎不会有误报,也就不容易被
--no-verify搪塞过去。
6. 限制、盲区与刻意的不判定
6.1 只查"有 → 无"这一个方向
"HEAD 无 BOM、工作区加了 BOM" 不报。那是另一个方向的改动,可能是有意的(把某个文件纳入
BOM 名单),也可能是无意的;报红只会逼人 --no-verify,而养成绕门禁的习惯比漏检一次贵得多。
"新增文件没有 BOM" 同样不报:名单里的新文件(如新建 ui_page_xxx.c)由创建者负责加 BOM,
漏加会在 /keil-build 时以真错误暴露——本门禁不假装自己能替代那一步。
6.2 名单三处互指(防漂移)
AGENTS 铁律第 2 条的名单、钩子 files 正则、脚本内 BOM_LIST 必须一致;脚本每次运行都核一次,
不一致即 FAIL。比较时会先脱掉正则里的反斜杠与锚点——核的是意图一致,不是字节相同,否则
App/task/ui_task\.c$ 会被误判成"没覆盖 App/task/ui_task.c"。
6.3 不校验文件是否真的含非 ASCII
判定只看编码标记,不看内容。一个纯 ASCII 的名单内文件带不带 BOM 都不影响编译,本门禁不替 编译器做这种优化判断——它宁可少管,也不要给出"可以不带 BOM"的例外口径(例外一旦被记住, 就会被套用到带中文的文件上)。
6.4 绕过与提交后不可见
--no-verify 可以直接跳过;合入主干之后再手工删 BOM,要等下一次有人改这个文件才会被比出来。
这两条是所有 pre-commit 门禁的公共限度,不在本篇展开(→ docs/代码规范/pre-commit使用说明.md)。
7. 豁免与基线
无基线、无豁免、无 --write:要么 BOM 在,要么别提交。
8. 改这个门禁本身
- 改名单必须同时改三处(§6.2 会拦着);
- 新增判定分支必须先在
--selftest里加用例(含正反两向),它是这篇文档里唯一可执行的部分; - 别在这里加"自动修复":见 §4 的编码风险。