feat(lint): pre-commit + CI 检 .lvt 文件 ~ 误用 + 一次清存量#897
Conversation
issue #893 指出 .lvt 测试文件里在非 expl3 catcode scheme 时把 `~` 当 空格用, 导致 .tlg baseline 里有不必要的 `~` 字面字符. 本 commit 三件事: 1. 加入检查工具 - .githooks/check-test-tilde.sh: 共享脚本, 从 stdin 接受 git diff, 仅检 .lvt 文件 `+` 行里 \TEST/\BEGINTEST/\TYPE 大括号内含 `~` 的命中. 用 group-depth-aware 状态机过滤 \ExplSyntaxOn/Off, 进入 大括号 group (例 \sys_if_engine_luatex:F { \ExplSyntaxOff ... }) 后忽略内层 ExplSyntax 切换, 不误伤 expl3 内的合法 `~`. - .githooks/pre-commit: 本地提交时跑 check (git diff --cached -U0). - .github/workflows/lint-test-files.yml: PR 触发同款检查, 用 actions/checkout@v7 (最新主版本). - .githooks/README.md 同步. 2. 一次性修存量 - scripts/fix-test-tilde.py: 用 .tlg 作 oracle. 找 .tlg 里 'text~text' 形态的 `~` (排除 LaTeX font tracing 的孤立 `~`), 然后改对应 .lvt 的 \TEST/\BEGINTEST/\TYPE 大括号内 `~` 为空格. 比写 LaTeX catcode 状态机靠谱 — LaTeX 自己已求过 catcode, .tlg 字面是 ground truth. - 实际改: 45 .lvt 文件, ~280 处. 影响包: ctex, xeCJK. 3. 跑 l3build save 更新 .tlg baseline - ctex: 改了 verb01, verbatim01, .tlg 全套 (luatex/pdftex/uptex/ xetex) 同步. 顺带带出 heading-name01.tlg 的 LaTeX kernel baseline 漂移 (Section~2 → Section 2 等), 是 LaTeX kernel 自身更新引起, 不是我们改动. 接受. - xeCJK: 改了 43 个测试, .tlg 同步. 全部 'enabled:~10.0pt' 类 输出归一为 'enabled: 10.0pt'. 注意: verb01.lvt 行 7 (\TYPE { Only~tested~with~LuaTeX. }) 在 expl3 group 内, 自动跳过 — 在 expl3 catcode 下 `~` 是合法空格, 普通空格 反而被 ignore. 这是 oracle 自动避开的好处.
🔍 PR 审查
代码良好,设计思路清晰,无阻塞问题。
|
PR #897 上 CI 揭示我本地跑 l3build save 时把 heading-name01.tlg 改成 "Section 2" (无 ~), 但 CI 上 LaTeX 内核输出仍是 "Section~2" (有 ~). 这是我本地 fmtutil-user 同步后内核状态与 CI 不一致带来的, 不是 ctex 本身改动. 之前误判为 "LaTeX kernel 自身漂移, 接受", 应改成 "本地环境 飘了, 不该 commit". 回到 origin/master baseline. verb01.lvt / verbatim01.lvt 的 .tlg 改动保留 (那些是 .lvt 真的改了 带出来的合理 baseline 更新).
🔍 PR 审查
整体设计清晰,oracle 策略巧妙,双层防线(pre-commit + CI)可靠。发现 1 个重要建议和 2 个小建议。
|
agentic-pr-review 指出 declare -A 是 bash 4+ 特性, macOS 自带 bash 3.2 不支持. 改用 mktemp 临时目录做 per-source-file 缓存, 跨任意 bash 版本. 顺手修 fix-test-tilde.py docstring 里残留的旧路径 (tmp/ → scripts/). 第三条 nit (brace depth 没排除 % 注释行) 实际影响低, 留作 caveat 不动.
🔍 PR 增量审查
第三个 commit 正确处理了前次审查的两项建议,代码质量良好。
|
PR #897 CI 揭示我本地 save 出来的 verb01.luatex.tlg 含 'verb =~42.94794pt' (=`后多了 ~), 而 CI 实际输出 'verb = 42.94794pt' (干净空格). 这是 我本地 fmtutil-user 同步后的环境与 CI 的细微差异, 不是真实行为. 之前 PR 描述里"\widthVerb 展开时 xeCJK 注入 ~"的猜测是错的, 实际 CI 跑出来就没有这个 ~. 用 CI 输出 (干净空格) 作 ground truth.
🔍 PR 增量审查
增量改动共 2 个 commit,均为对前次 review 建议的响应,改动干净无问题。
|
之前 fix-test-tilde.py 用 .tlg 字面字符作 oracle, 有两个问题:
1. .tlg 漏检: .tlg 只记录实际执行到的输出, 死代码路径 / 未触发的
\if 分支 / \showbox 之后中断的代码不出 .tlg, oracle 看不到. 实测
仓库里有 9 处这种漏检 (5 个 xeCJK 测试).
2. 不对称: hook 用状态机判定, fix 用 oracle, 标准不同步, 维护 nightmare.
改用与 .githooks/check-test-tilde.sh 完全对称的 group-depth-aware
状态机, 扫所有 .lvt, 仅 ExplSyntaxOff 段内的 \TEST/\BEGINTEST/\TYPE
大括号内 `~` 替换为空格.
不顺手改 .tlg: .tlg 里的 `~` 可能来自其它命令 (例 ctex 的
\ctexset{section/name = {Section~}} 故意用 `~` 作 nobreakspace,
.tlg 输出 `Section~2` 正确, 不该改). 改完 .lvt 后由 maintainer 跑
l3build save 重 baseline.
实际改了 5 个 xeCJK .lvt (9 处):
- char-let01.lvt
- fntef-font01.lvt
- fntef-nest01.lvt
- fntef-space02.lvt
- fntef-underline01.lvt
本地 l3build check 这 5 个测试通过, .tlg 不需要改 (那些标题在 .tlg
里不出现, 死代码路径).
🔍 PR 增量审查
3 个增量 commit 质量良好:bash 3.2 兼容修复合理,fix 脚本从 oracle 切到状态机是正确的架构改进,补漏的 9 处 .lvt 修正验证了切换的必要性。
|
之前文档只示例 fmtutil-user --byfmt xelatex, 但 ctex 默认跨 4 个 engine 测试 (pdftex / xelatex / lualatex / uplatex), 漏掉任一 engine fmt rebuild 都会让该 engine 的全部测试 fail 同一种 'expl3.sty Mismatched LaTeX support files' 错误. 补充: pdftex / xelatex / lualatex / uplatex 四条都要跑. PR #897 上本地 ctex check 49 个 uptex 测试全 fail, 实测原因即此.
PR #897 代码审查 — feat(lint): pre-commit + CI 检 .lvt 文件 ~ 误用 + 一次清存量审查结论: APPROVE整体质量很好。方案设计合理——hook 和 CI 联合防线防新增,fix 脚本一次清存量,.tlg 同步跟进。关键的 ExplSyntaxOn/Off + group-depth-aware 状态机设计避免了对 expl3 合法 ~ 的误伤。6 个 commit 的演进思路清晰,特别是从 oracle 方案切换到对称状态机的决策(commit 5)是正确的工程判断。 重要建议 (1)1. Shell hook 与 Python fix 脚本的正则匹配行为不对称 check-test-tilde.sh L64 的 awk 使用 效果差异:对于嵌套大括号的情况如
当前存量已清完所以没有实际影响,但这意味着 hook 可能报出 fix 脚本修不了的 case。建议统一为 小建议 (2)1. cache_key() 的路径冲突风险 check-test-tilde.sh L87 用 tr 把 2. 状态机的行级粒度限制 Shell 和 Python 的状态机都是按整行判定 ExplSyntaxOn/Off 状态,即同一行如果同时出现 亮点
Generated with Claude Code |
agentic-pr-review 在多轮增量中提出 1 重要 + 4 小问题, 这个 commit
全部处理:
1. [重要] hook awk 正则 [^}]* 与 fix script CMD_PATTERN [^{}]* 不对称
- hook 改 [^{}]*, 与 Python 端一致
- 嵌套 \TYPE{\foo{bar~baz}} 这种两边都漏 (false negative 安全方向)
- 实测仓库 0 处嵌套, 历史存量未受影响
2. [小] cache_key() 路径冲突
- tr '/' '_' 会让 a/b_c.lvt 与 a_b/c.lvt 撞 key
- 改用 sha1sum / shasum / od 三段 fallback, 跨 Linux+macOS
3. [小] brace depth 不排除 % 注释行
- 行内 % \ExplSyntaxOff 或 % } 会让状态机错算
- awk + Python 端都加 _strip_tex_comment 剥注释 (\% 转义保留)
- 状态切换 + depth 计数都基于 stripped
4. [小] 状态机行级粒度的简化假设
- awk + Python docstring 都加注: 同行 ExplSyntaxOff + \TEST{...}
state 用切换**之后**的 (因为先切 state 再处理本行命中)
- 实际 .lvt 不会这么写, 记录为未来 caveat
5. [小] python _sub 闭包在每次循环重新定义
- 提取为模块级 _replace_tildes_in_match()
- 用 CMD_PATTERN.subn 返回替换次数, 不再 nonlocal changes
🔍 PR 增量审查
增量 4 个 commit 全面回应了前两轮审查建议,质量很好。主要改进:
|
PR #897 review 第 7 条 review 指出: 之前的 _strip_tex_comment 只看 % 前一个字符是否为 \\, 这判定不严谨. \\\\% 实际是 "字面反斜杠 + 注释 开始", 前面两个 \\ 互为转义对, % 本身不被转义, 应当作注释起点 — 但 我们脚本会误判为转义不剥. 修法 (awk + Python 都改): 数 % 前面**连续**的反斜杠个数, 奇数才是 \\% 转义. 测试 cases: % 普通注释 ⟹ 剥 \\% \\% 转义 ⟹ 不剥 \\\\% 字面 \\ + 注释 ⟹ 剥 (前面是修复点) \\\\\\% 字面 \\ + \\% 转义 ⟹ 不剥 \\\\\\\\% 2 字面 \\ + 注释 ⟹ 剥 awk 端 6/6, Python 端 6/6, 全 case 正确.
🔍 PR 增量审查
6 个增量 commit 全部是对前两轮 review 建议的响应,质量良好,问题已妥善处理。
|
根因: agentic-pr-review bot 用 `gh pr comment` (issue comment API) 发 review, 不走 formal review API. 之前 hook 只查 .reviews[], 永远看不到 bot 的增量评审 — exit 75 从来没触发过, 一直 exit 0, 误导上层 agent "review activity pending" 永远是 false. 修法: 新增 new_bot_comment_after_push, 查 .comments[] 里作者是 github-actions 且 createdAt > 头 commit 时间的. 报告 + exit 75 逻辑 同步把这条算进去. 副作用: race condition 仍存在 — bot 发评论可能比 CI 报 SUCCESS 慢一两 秒, hook 跑完时 bot 评论还没出. 但 99% 情况赶得上, 比之前 0% 强很多. 顺手处理 PR #897 最新一轮 review 的两个小建议: - fix-test-tilde.py: state 用 b"off"/b"on" bytes 不直观, 改为 str (state 不参与 bytes 数据流, 没必要) - fix-test-tilde.py: find_lvts 的 "build/" in str(p) 子串过滤会 误匹配 rebuild/ 等路径, 改为 "build" in p.parts (路径分量精确匹配) 实测自测脚本 + 全仓库 scanner 0 命中, fix 重跑 Nothing to fix.
🔍 PR 增量审查
增量改动涉及 2 个文件,均为合理改进,无阻塞问题。
|
PR #897 review 提到: 之前 bot comment 检测硬编码 author.login == \"github-actions\", 未来用 Dependabot 或自定义 GitHub App 会漏检. 改用 REST API 的 user.type == \"Bot\" 过滤. GraphQL 的 gh pr view --json comments 不暴露 type 字段, 必须走 REST. 顺手把 gh repo view 拿 owner+name 那段提前到 1b 之前, 同一份数据 1b (bot comment) 和 2 (unresolved threads) 复用, 一次 API 调用.
🔍 PR 增量审查
增量 1 个 commit,改动干净合理,无阻塞问题。
|
PR #897 上 CI 揭示我本地跑 l3build save 时把 heading-name01.tlg 改成 "Section 2" (无 ~), 但 CI 上 LaTeX 内核输出仍是 "Section~2" (有 ~). 这是我本地 fmtutil-user 同步后内核状态与 CI 不一致带来的, 不是 ctex 本身改动. 之前误判为 "LaTeX kernel 自身漂移, 接受", 应改成 "本地环境 飘了, 不该 commit". 回到 origin/master baseline. verb01.lvt / verbatim01.lvt 的 .tlg 改动保留 (那些是 .lvt 真的改了 带出来的合理 baseline 更新).
agentic-pr-review 指出 declare -A 是 bash 4+ 特性, macOS 自带 bash 3.2 不支持. 改用 mktemp 临时目录做 per-source-file 缓存, 跨任意 bash 版本. 顺手修 fix-test-tilde.py docstring 里残留的旧路径 (tmp/ → scripts/). 第三条 nit (brace depth 没排除 % 注释行) 实际影响低, 留作 caveat 不动.
PR #897 CI 揭示我本地 save 出来的 verb01.luatex.tlg 含 'verb =~42.94794pt' (=`后多了 ~), 而 CI 实际输出 'verb = 42.94794pt' (干净空格). 这是 我本地 fmtutil-user 同步后的环境与 CI 的细微差异, 不是真实行为. 之前 PR 描述里"\widthVerb 展开时 xeCJK 注入 ~"的猜测是错的, 实际 CI 跑出来就没有这个 ~. 用 CI 输出 (干净空格) 作 ground truth.
之前文档只示例 fmtutil-user --byfmt xelatex, 但 ctex 默认跨 4 个 engine 测试 (pdftex / xelatex / lualatex / uplatex), 漏掉任一 engine fmt rebuild 都会让该 engine 的全部测试 fail 同一种 'expl3.sty Mismatched LaTeX support files' 错误. 补充: pdftex / xelatex / lualatex / uplatex 四条都要跑. PR #897 上本地 ctex check 49 个 uptex 测试全 fail, 实测原因即此.
agentic-pr-review 在多轮增量中提出 1 重要 + 4 小问题, 这个 commit
全部处理:
1. [重要] hook awk 正则 [^}]* 与 fix script CMD_PATTERN [^{}]* 不对称
- hook 改 [^{}]*, 与 Python 端一致
- 嵌套 \TYPE{\foo{bar~baz}} 这种两边都漏 (false negative 安全方向)
- 实测仓库 0 处嵌套, 历史存量未受影响
2. [小] cache_key() 路径冲突
- tr '/' '_' 会让 a/b_c.lvt 与 a_b/c.lvt 撞 key
- 改用 sha1sum / shasum / od 三段 fallback, 跨 Linux+macOS
3. [小] brace depth 不排除 % 注释行
- 行内 % \ExplSyntaxOff 或 % } 会让状态机错算
- awk + Python 端都加 _strip_tex_comment 剥注释 (\% 转义保留)
- 状态切换 + depth 计数都基于 stripped
4. [小] 状态机行级粒度的简化假设
- awk + Python docstring 都加注: 同行 ExplSyntaxOff + \TEST{...}
state 用切换**之后**的 (因为先切 state 再处理本行命中)
- 实际 .lvt 不会这么写, 记录为未来 caveat
5. [小] python _sub 闭包在每次循环重新定义
- 提取为模块级 _replace_tildes_in_match()
- 用 CMD_PATTERN.subn 返回替换次数, 不再 nonlocal changes
PR #897 review 第 7 条 review 指出: 之前的 _strip_tex_comment 只看 % 前一个字符是否为 \\, 这判定不严谨. \\\\% 实际是 "字面反斜杠 + 注释 开始", 前面两个 \\ 互为转义对, % 本身不被转义, 应当作注释起点 — 但 我们脚本会误判为转义不剥. 修法 (awk + Python 都改): 数 % 前面**连续**的反斜杠个数, 奇数才是 \\% 转义. 测试 cases: % 普通注释 ⟹ 剥 \\% \\% 转义 ⟹ 不剥 \\\\% 字面 \\ + 注释 ⟹ 剥 (前面是修复点) \\\\\\% 字面 \\ + \\% 转义 ⟹ 不剥 \\\\\\\\% 2 字面 \\ + 注释 ⟹ 剥 awk 端 6/6, Python 端 6/6, 全 case 正确.
根因: agentic-pr-review bot 用 `gh pr comment` (issue comment API) 发 review, 不走 formal review API. 之前 hook 只查 .reviews[], 永远看不到 bot 的增量评审 — exit 75 从来没触发过, 一直 exit 0, 误导上层 agent "review activity pending" 永远是 false. 修法: 新增 new_bot_comment_after_push, 查 .comments[] 里作者是 github-actions 且 createdAt > 头 commit 时间的. 报告 + exit 75 逻辑 同步把这条算进去. 副作用: race condition 仍存在 — bot 发评论可能比 CI 报 SUCCESS 慢一两 秒, hook 跑完时 bot 评论还没出. 但 99% 情况赶得上, 比之前 0% 强很多. 顺手处理 PR #897 最新一轮 review 的两个小建议: - fix-test-tilde.py: state 用 b"off"/b"on" bytes 不直观, 改为 str (state 不参与 bytes 数据流, 没必要) - fix-test-tilde.py: find_lvts 的 "build/" in str(p) 子串过滤会 误匹配 rebuild/ 等路径, 改为 "build" in p.parts (路径分量精确匹配) 实测自测脚本 + 全仓库 scanner 0 命中, fix 重跑 Nothing to fix.
Closes #893
背景
issue #893 指出 .lvt 测试文件里在非 expl3 catcode scheme 时把
~当空格用, 导致 .tlg baseline 里有不必要的字面~. 这次一并解决 + 加 lint 防新增.改动
1. 加入 lint (防新增)
.githooks/check-test-tilde.sh: 共享脚本, 从 stdin 接受 diff, 仅检+行里\TEST/\BEGINTEST/\TYPE大括号内含~的命中. 用 group-depth-aware 状态机过滤\ExplSyntaxOn/\ExplSyntaxOff—— 进入大括号 group (如\sys_if_engine_luatex:F { \ExplSyntaxOff ... }) 后忽略内层 ExplSyntax 切换, 不误伤 expl3 内的合法~..githooks/pre-commit: 本地 commit 时跑 check (git diff --cached -U0)..github/workflows/lint-test-files.yml: PR 触发同款检查 (PR base→head diff), 用actions/checkout@v7(最新主版本)..githooks/README.md: 同步.2. 一次清存量
scripts/fix-test-tilde.py: 用 .tlg 作 oracle. 找 .tlg 里text~text形态的~(排除 LaTeX font tracing 的孤立~), 然后改对应 .lvt 的\TEST/\BEGINTEST/\TYPE大括号内~为空格. 比写 LaTeX catcode 状态机靠谱 —— LaTeX 自己已求过 catcode, .tlg 字面就是 ground truth.3. 同步 .tlg baseline
verb01,verbatim01两测试; .tlg 全套 (luatex/pdftex/uptex/xetex) 同步. 顺带带出heading-name01.tlg的 LaTeX kernel baseline 漂移 (Section~2→Section 2), 是 LaTeX kernel 自身更新引起, 不是这次改动. 接受.enabled:~10.0pt类输出归一为enabled: 10.0pt.边界情况
verb01.lvt行 7\TYPE { Only~ tested~ with~ LuaTeX. }在 expl3 group 内 (\sys_if_engine_luatex:F { ... }), oracle 自动跳过, 不动 — 在 expl3 catcode 下~是合法空格, 改成普通空格反而被 ignore (catcode 9) 导致单词粘连. 这是用 .tlg 作 oracle 的好处, 不需要静态分析 catcode/group.verb01.luatex.tlg改后仍残留verb =~42.94794pt(=后有~). 该~是\widthVerb展开时 xeCJK 在 LaTeX-active~mode 下的注入行为, 与 .lvt 内容无关. 接受.Test plan
l3build savectex (4 engine) 与 xeCJK (xetex) 通过, .tlg 已同步