ci(prompts): 新增 prompt dict 必须带 zh-TW 的棘轮门禁(#2500 第 2 步) - #2569
Conversation
issue #2500 第 2 步。存量 339 个 i18n prompt dict 缺 'zh-TW',而 issue 开出时是 320 —— 三天涨了 19 个,因为没有任何东西盯着。补齐是分批的长 活,这个门禁只负责让洞不再变大。 规则:config/prompts/ 下含 'en' 键且含 'zh' 或 'zh-CN' 的 dict 是本地化 prompt 表,必须也含 'zh-TW'。理由是 _loc 缺 key 回落 'en' 而不是 'zh' (prompts_sys.py:34),所以缺 zh-TW 的表会给繁中用户端出英文 prompt。 **棘轮键在 dict 自己的定义行**,不是整个行span:只对本次 diff 新增的表 生效,存量 339 个豁免,改已有表的文案也不触发。这样它能立刻进 CI 而不 需要先把存量补完。三种模式:默认 diff-ratchet;--full 列全部积压; --count 只报数量。单表逃生用 `# noqa: PROMPT_ZH_TW` 写在开括号那行。 git plumbing 与 CI 接线照 check_docstring_no_cjk.py 既有形态 (PR-only、BASE_REF 间接引用避 zizmor template-injection 告警)。 测试 tests/unit/test_check_prompt_zh_tw.py(18 项):表形状识别(短码 zh / 全码 zh-CN / 缺 en 锚 / 无中文键 / 非字符串键 / 嵌套多表)、棘轮 (定义行命中 vs 表内行命中 vs 空集 vs None 全扫)、noqa(开括号行生效 / 别的 code 不生效 / 表内行不生效)、CLI(--base HEAD 空 diff、--count)。 四条变异均验证会红:移除棘轮门、SIMPLIFIED_KEYS 丢掉 zh-CN、移除 noqa 抑制、ANCHOR_KEY 门失效。 --count 当前报 339,与独立 AST 统计吻合。self-check 对本 PR exit 0。 Refs #2500 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 42 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
Walkthrough新增 Changes提示词繁体中文校验
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related issues
Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
Greptile Summary本 PR 新增一个仅在拉取请求中运行的 CI 棘轮,阻止缺少 Confidence Score: 5/5先前的 scheme migration 误报已经由总数比较完整修复,当前 PR 看起来可以安全合并喵。 当前代码不再按
|
| Filename | Overview |
|---|---|
| scripts/check_prompt_zh_tw.py | 以 merge-base 与 HEAD 的违规总数差实现棘轮,并处理静态键解析、PEP 263 编码、符号链接和变更行定位喵。 |
| tests/unit/test_check_prompt_zh_tw.py | 广泛覆盖 prompt 表识别、方案迁移、已接受的净零盲点、Git/编码边界和 CLI 主流程喵。 |
| .github/workflows/analyze.yml | 在 PR 分析工作流中接入新的 zh-TW prompt 检查,并通过环境变量安全传递基础分支喵。 |
Reviews (20): Last reviewed commit: "fix(ci): 链式绑定纳入豁免;fromkeys 计入可见供给键" | Re-trigger Greptile
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5df502832f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
review 反馈(codex,三条 P2)逐条实测证实,全部是行号棘轮的固有缺陷:
1. scripts/check_prompt_zh_tw.py:219 —— 已在 base 里的 {en, ja} 表,PR 只
加一行 "zh" 键使其变成本地化表:dict 定义行不在 diff 里,漏检。**这正是
门禁最该拦的路径**(实测 exit 0)。
2. :217 —— 纯重命名(git mv,内容不变)让新路径每一行都算新增,报出该文件
全部存量缺口(实测误报 2 条)。
3. :159 —— diff 模式按 startswith("config/prompts/") 选文件(含子目录),
而 --full/--count 用 glob("*.py") 只扫顶层,两个模式口径不一致。
前两条同源。改为对比 merge-base 与 HEAD 的**键集合签名多重集**:
- 加中文键改变签名 → 计数 +1 → 抓住第 1 条
- 重命名与改文案都不改签名 → 消掉第 2 条
- 补 zh-TW 使某签名消失 → 净减,正确放行
第 3 条用 git ls-tree -r(base 侧)和 rglob(磁盘侧)统一口径。
已知盲点写进 docstring 并钉进测试:删一个表 + 加一个同签名表净变化 0 会
放行。替代方案是跨版本按位置匹配 dict,一次 reformat 就崩,所以这是刻意
接受。
签名对比的代价是定位变糊:{en, zh} 这种常见签名匹配一堆存量表。所以把
diff 行号作为**提示**加回来(-M 防 rename 把整个文件标成新增),报错时
narrowing 到本次改动碰过的表,其余折叠成"N 个存量表共享该签名且豁免"。
判定权威仍是签名,行号只影响提示。
测试从 18 项扩到 29 项。新增:签名棘轮的六种转移(新表/加键/重命名/改
文案/补 zh-TW/多重计数)、盲点、locate 的提示 narrowing、子包发现、以及
**main() 本身**。
最后一条是补前一轮的漏:原测试只调 signature_counter 自己做减法,导致把
main() 里的 `head - base` 写反成 `base - head`(门禁完全失效)时无一条
测试变红 —— 变异验证发现后才补上走 main() 的用例。
八条变异现在全部被抓:签名对比反向、SIMPLIFIED_KEYS 丢 zh-CN、移除 noqa、
ANCHOR_KEY 门失效、rglob 窄回 glob、ls-tree 去 -r、locate 忽略提示、
touched 去 -M。
端到端复验三个原始场景:重命名 exit 0;已有表加键 exit 1 且精确定位;
子包 --count 339→340、diff 模式 exit 1。
Refs #2500
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 83058253ed
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
review 第二轮(codex,三条 P2)逐条实测证实:
1. :290 —— 给已有的缺 zh-TW 表加一个无关 locale(如 fr)会误报。这是上一轮
签名方案自己引入的:签名用了**完整**键集合,`{en,zh}` → `{en,zh,fr}`
被当成新表,于是一个只加语言、没有扩大积压的 PR 会被卡。
2. :100 —— `dict(en="english", zh="简体")` 形式不被 ast.Dict 匹配,漏检。
3. :197 —— `git ls-tree --name-only` 对非 ASCII 路径加引号并八进制转义
(仓库 tests/testbench 下就有这类路径),解析不出真实路径。
修法:
1 —— 新增 rule_signature(),把键集合投影到「用的是哪个简体键」(zh 或
zh-CN)。计数含义变成「该方案下还缺 zh-TW 的表有几个」,正是棘轮真正
关心的量。加 fr 不改变它;加 zh 到 en-only 表会让它 +1;两个方案的计数
彼此独立。
2 —— _string_keys 同时识别 dict() 调用的 keyword 形式。
3 —— ls-tree 改 -z(NUL 分隔、不加引号)。
修 2 时发现一个自己引入的误报:dict() 无法用 keyword 表达 zh-TW(非法
标识符),所以想用 dict() 又要 zh-TW 只能走 `**` 展开 —— 而 `**` 的键静态
看不见,判成缺失就是误报。改为**遇到 `**` 展开就放弃判断**(字面量的
`{**other}` 同理)。门禁宁可漏检不可误报:误报会让人绕过门禁而不是满足它。
测试 29 → 34。新增:加无关 locale 不报、两方案计数独立、dict() 形式被抓、
`**` 展开放弃判断(call 与 literal 各一条)、ls-tree -z 的 NUL 解析(含
中文路径)。
十四条变异全部被抓,含本轮新增的六条:rule_signature 退回全键集合、去掉
dict() 支持、`**` 不再放弃判断(call/literal)、ls-tree 去 -z、NUL split
改回 splitlines。
另:合并了两个都跑 --count 的 CLI 用例。--count 会重新 AST 解析全部 22 个
prompt 模块(prompts_proactive.py 单文件约 5k 行),而 Windows CI 上同一
suite 里有 1 秒预算的线程时序用例,少一次子进程全量扫描是白拿的。
存量计数仍为 339(dict()/`**` 支持未改变仓库现状判定)。ruff 通过。
Refs #2500
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d2ab927239
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/analyze.yml:
- Around line 203-214: Update the workflow comment for “Require zh-TW on new
localized prompt dicts (PR-only)” to describe the implemented signature-based
multiset ratchet, not a dict-definition-line or line-based ratchet. Preserve the
existing explanation of newly introduced dictionaries, legacy backlog behavior,
and direct-push handling while explicitly stating that comparisons use rule
signatures rather than line numbers.
In `@scripts/check_prompt_zh_tw.py`:
- Around line 274-282: Update _sources_on_disk to catch UnicodeDecodeError
alongside OSError when reading UTF-8 prompt files, and emit the same clean
per-file stderr diagnostic instead of allowing decoding failures to abort the
gate.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 41932fa7-a49d-4790-8eb9-7242055e558e
📒 Files selected for processing (3)
.github/workflows/analyze.ymlscripts/check_prompt_zh_tw.pytests/unit/test_check_prompt_zh_tw.py
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Project-N-E-K-O/N.E.K.O.-PC(manual)
review 第三轮。三条,前一条是 P1 且很讽刺:门禁会拦死 issue #2500 自己的 终局迁移。 1. greptile P1 / codex P2(scripts/check_prompt_zh_tw.py:131)—— 把某张违规表的简体键从 'zh' 改成 'zh-CN'(方案迁移),在「按简体键分组 计数」的判据下表现为一组少一张、另一组多一张,而 Counter 相减只保留正 数,于是报出并不存在的增长。实测 base={'zh':1} head={'zh-CN':1} -> added={'zh-CN':1}。**issue #2500 的终局正是把全部 339 张表做这个改名**, 门禁会把自己要服务的迁移全部拦下。 判据收敛为**总数对比**:merge-base 与 HEAD 各有多少张缺 zh-TW 的表, HEAD 多了才失败。总数对重命名、改文案、加无关 locale、方案迁移全部不变 量,而一张新受规则约束的表会让它上升。 这是判据的第四版。前三版各自破在一个后一版免费覆盖的场景上(行号 / 全键集合 / 按简体键分组),脚本 docstring 里逐条记下了破法,避免以后 有人再走一遍。 2. coderabbit(.github/workflows/analyze.yml:214)—— CI 注释还在描述第一版 的「按 dict 定义行棘轮」,与脚本 docstring 明写的「行号判据是错的」直接 矛盾。我改了三次实现没同步这段注释,文档在撒谎。已重写。 3. coderabbit(:282)—— `except OSError` 抓不住 `UnicodeDecodeError`(它是 ValueError 子类),一个非 UTF-8 的 prompt 模块会让整个门禁带 traceback 崩掉而不是跳过该文件。改为 `(OSError, UnicodeDecodeError)`。 对偶地补了 base 侧:`_git` 走 `text=True` 读 `git show` 的内容,同样会在 解码时抛。加 `encoding="utf-8", errors="replace"` —— 替换后的源码大概率 过不了 ast.parse,由 _parse_source 报错并跳过,与磁盘侧同一结局。 测试 34 -> 38。新增:方案迁移(单表 + 5 张全量迁移,对应 #2500 终局)、 非 UTF-8 文件被跳过而非致命、_git 的 errors=replace 传参。 变异 17 条全部被抓,含本轮四条:计数相减方向反、grew<=0 改成 <0、except 收窄回 OSError、_git 去掉 errors=replace。另外单独验证了「退回按简体键 分组」这个真变异会红 3 条(两条 migration 测试 + 跨方案新表)。 存量计数仍 339。ruff / docstring / hygiene 门与 YAML 语法均通过。 Refs #2500 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4c85466082
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
## Windows CI 红(本轮的真 bug,非 flaky)
test_cli_against_head_is_clean 在 Windows runner 上确定性失败:
encodings\cp1252.py -> UnicodeDecodeError:
'charmap' codec can't decode byte 0x9d in position 3606
subprocess 的 text=True **不指定 encoding 时按 locale 解码**,CI runner 是
cp1252(本地是 cp936,所以本地一直绿)。失败还伪装成 returncode 断言,因为
异常发生在 subprocess 的 _readerthread 里。
两侧对偶修:
- 测试侧 _run() 显式 encoding="utf-8", errors="replace"
- 脚本侧 main() 开头把 stdout/stderr reconfigure 成 UTF-8 —— stdout 是
pipe 时 print 也用 locale 编码,输出非 ASCII 路径或含 CJK 源码的
SyntaxError 会从门禁内部抛 UnicodeEncodeError
## review 反馈三条(codex,均实测证实)
1. **:118 dict() 位置参数**。`dict(BASE, en=..., zh=...)` 的 BASE 可能正是
zh-TW 的来源,与 `**` 同属"键静态不可知",但我上一轮只挡了 `**`。
实测 `dict(BASE, en='e', zh='s')` 被误报。现在 node.args 非空即放弃判断。
2. **:250 diff 的 +++ 行仍被 C-quote**。上一轮给 ls-tree 加了 -z,但漏了
patch 输出这条通道。实测:
默认: +++ "b/config/prompts/\344\270\255\346\226\207\350\241\250.py"
core.quotePath=false: +++ b/config/prompts/中文表.py
前者匹配不到真实路径,该文件的定位提示被静默丢弃。已加 -c
core.quotePath=false。
3. **:274 非 UTF-8 模块被静默跳过**。带 PEP 263 声明(`# coding: latin-1`)
的模块是合法 Python,上一轮的 except UnicodeDecodeError 把它整个跳过 ——
等于不检查一个真实的 prompt 模块。改为读 bytes 后经 tokenize.detect_encoding
解码,两侧对偶(磁盘侧 read_bytes、base 侧新增 _git_bytes),只有真正
损坏的字节才跳过且必有诊断。
## 测试
45 -> 47 项。新增:dict() 位置参数三态、_touched_lines 传 quotePath 参数、
解析非 ASCII 路径 diff、PEP 263 三态(latin-1 声明/无声明默认 utf-8/真损坏)、
_git 文本解码容错、_git_bytes 保持原始字节、main() 强制 UTF-8 及无
reconfigure 流的兜底。
改掉一条过时测试:test_git_decodes_with_replacement 断言的是 subprocess
kwargs 形状(实现细节),_git 重构后失效。改为直接验证行为 —— 坏字节不崩
且被替换字符替代,比原断言更强。
16 条变异全部被抓,含本轮补的四条关键判定:总数对比反向、判定门 <=0 改
>=0(永不报,门禁完全失效)、改 <0(净 0 误报)、stdout 不强制 UTF-8。
最后一条最初存活 —— 那正是修 Windows CI 的改动,当时无任何覆盖。
端到端复验(走真实 git 与真实文件):非 ASCII 文件名的新增表定位到
`中文表.py:1`;latin-1 模块被检出在 `legacy_latin1.py:2` 而非静默跳过。
Refs #2500
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1764767f26
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
review 反馈两条(codex),均实测证实。
## 1. 跳过合成构造 ≠ 剪掉它的子树
上一轮让 `_string_keys` 对 `**` 和位置参数返回空集,以为就不判了。但
`ast.walk` 是平铺遍历 —— 外层 Call 返回空集被跳过,**内层字面量仍被单独
访问并判定**:
dict({"en": "e", "zh": "s"}, **{"zh-TW": "t"}) -> 误报内层
{**{"en": "e", "zh": "s"}, **{"zh-TW": "t"}} -> 误报内层
{"en": "e", "zh": "s"} | {"zh-TW": "t"} -> 误报左operand
三种写法组装出的表都含 zh-TW,却因为某个片段单独看缺 zh-TW 而被拦。
改为 `_table_nodes()` 显式栈遍历,遇到合成构造(dict literal 带 `**` /
`dict()` 带位置参数或 `**` / `|` BinOp)**连同子树一起剪掉**。普通嵌套
(`{"a": {...}}`)不是合成,内层仍是独立的表,照判。
栈遍历会打乱顺序,`find_violations` 补 `sorted()` 恢复源码顺序;
`locate_touched` 的 by_line 也改走 `_table_nodes`,否则片段的 lineno
会进定位表。
## 2. 纯删除 hunk 定位为空
删掉某表唯一的 `"zh-TW"` 成员 —— 这是总数增长的一条真实路径 —— git 产出
`@@ -4 +3,0 @@`,新侧长度 0,`range(3, 3+0)` 是空集,于是判定正确报错但
**定位丢失**,作者被指向 339 条的 `--full`。
改为 count==0 时记录删除点两侧(`{max(1, start), start + 1}`)。取
`max(1, ...)` 是因为文件首删除会给出 `+0,0`,而行号从 1 起。
## 测试
47 -> 56 项。新增:五种合成写法的片段不被判定(含只有位置参数、无 `**`
的两种 —— 这是唯一能证明位置参数那半判据对剪枝有用的 case)、普通嵌套仍
按源码顺序判定、纯删除 hunk 记录两侧行、文件首删除不产生行号 0、以及
删掉 zh-TW 后端到端定位到该表。
七条变异全部被抓:`_table_nodes` 退回 `ast.walk`、merged 判据分别丢掉
`|` / dict literal `**` / `dict()` 位置参数、去掉 `sorted`、纯删除 hunk
不记录行、记录行号 0。
其中「丢掉 dict() 位置参数」最初存活 —— 原参数化 case 同时带位置参数和
`**`,`**` 那半判据兜住了。补两个只有位置参数的 case 后被抓。
Refs #2500
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8b61171e0b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
review 反馈(codex :162)。上一轮修片段误报时剪掉了合成构造的整棵子树,
过头了 —— 合成容器里**按普通键放的值**是独立的表,不是合并片段:
P = {**COMMON, "new": {"en": "hello", "zh": "你好"}}
P = dict(BASE, extra={"en": "a", "zh": "b"})
实测三种写法下那个独立表全部漏检(exit 0),等于门禁对这类容器彻底失明。
要同时躲开两个失败模式:
- 平铺 ast.walk → 把每个合并操作数单独判定 → 片段误报
- 整棵剪枝 → 丢掉容器内的独立表 → 漏检
改为 `_merge_operands()` 只取"参与合并的那些子映射"(dict literal 里
`k is None` 对应的 value、`dict()` 的位置参数与 `**` 值、`|` 的两个
operand),把它们的 **root 标记为不判定**,但遍历照常穿过它们以及容器的
其余部分。
混合场景验证:`{**{"en","zh"}, "ind": {"en","zh"}}` 只报 "ind" 那个独立表。
## 测试
56 -> 60 项。新增:三种合成容器里的独立表被判定、片段与独立表并存时只报
独立表。
五条变异被抓:退回整棵剪枝、operands 分别丢掉 dict literal `**` 值 /
`dict()` 位置参数 / `|` operand、suppressed 检查失效(等价于退回
ast.walk,6 条红)。
Refs #2500
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: aa9fd9c6b0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
review 反馈两条(codex),均实测证实。
## 1. 静态可解的合并结果从没被判定过
上一轮"压制片段"方案漏了一件事:BinOp 不是 Dict/Call,`_table_nodes`
从不 yield 它。于是两个 operand 被压制、外层不被 yield,整个表逃检:
T = {"en": "e"} | {"zh": "s"} -> exit 0(结果缺 zh-TW)
T = {"en": "e", "zh": "s"} | {} -> exit 0
T = dict({"en":..,"zh":..}, note="x") -> exit 0
把判据从"压制片段"升级为"静态求解整体":新增 `resolve_keys()` 递归解
mapping 表达式的键集合,穿透 dict literal 的 `**`、`dict()` 的位置参数
与 `**`、以及 `|`。能解就判结果,任何一部分不可知(spread 一个 Name、
动态键、`dict()` 套变量)就返回 None 放弃 —— 不可知的那部分正是 zh-TW
可能藏身处。
这个升级同时更完整也更简洁:片段误报不再靠"压制"避免,而是因为片段的键
已经算进整体结果里了。
operand 压制改为**无条件**(不再只在可解时压制)。`BASE | {"en":..,"zh":..}`
整体不可解,但右侧仍是片段,单独判它是误报。无条件也自然处理嵌套 ——
被压制 operand 的 operand 是更深的片段。
行为变更两处,方向都是修漏检:
- `dict({"en":..,"zh":..}, note="x")` 从静默变成上报(它确实缺 zh-TW)
- `{**{"en":..,"zh":..}, "ind": {...}}` 外层从不判变成判(解出
{en,zh,ind},确实是缺 zh-TW 的表);测试期望从 [4] 改为 [2,4]
对真实仓库无影响:`--count` 仍是 339。
## 2. symlink 两侧不对称
磁盘侧 `read_bytes()` 跟随 symlink 读目标源码,base 侧 `git show` 拿到的
是 symlink blob(内容即目标路径字符串)。同一路径两侧计数不同 —— 引入
commit 净 0 过关,之后**每个 PR 都误报**一个没人引入的差异。
两侧一致排除:base 侧读 ls-tree 的 mode 跳过 120000(因此不能再用
`--name-only`),磁盘侧 `path.is_symlink()` 跳过,两处都写 stderr 诊断。
## 测试
60 -> 69 项。合并判定重组为三组主张:结果合规则静默、结果缺 zh-TW 则上报
(含 `{"en"} | {"zh"}` 与 union 空 dict)、不可解则放弃(含 spread Name、
`|` Name、动态键)。symlink 两侧各一条,ls-tree 解析改喂真实 mode+tab 格式
并断言 `--name-only` 不回归。
八条变异全部被抓:resolve_keys 分别不穿透 `|` / dict literal `**` /
`dict()` 位置参数、动态键改成忽略而非放弃、operand 压制改回仅在可解时、
symlink 两侧分别不排除、ls-tree 退回 `--name-only`。
Refs #2500
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d0506c269d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
review 反馈(codex :152)。`resolve_keys` 对 ast.DictComp 直接落到
return None,于是推导式写法整表逃检:
T = {key: value for key, value in (("en", "hello"), ("zh", "你好"))}
-> exit 0(键集合静态可知含 en/zh,缺 zh-TW)
比 codex 举的例子更现实的是另一种形态 —— 按 locale 列表批量生成同构模板:
T = {loc: f"..." for loc in ("en", "zh", "ja")}
这是合理写法,漏掉它是真盲区不是理论盲区。
新增 `_comprehension_keys()` 求解两种形态:单 Name target(`for loc in
(...)`)与 tuple 解包(`for k, v in ((k1,v1), ...)`,按 key 在 target 里的
位置取值)。要求单一 generator、无 ifs、非 async、key 是裸 Name、iter 是
内联 Tuple/List/Set 且元素全为字符串常量;任何一条不满足即返回 None。
**刻意不跟随符号**:`{lang: build(lang) for lang in _L10N}` 保持不可解。
config/prompts 下现存的三个推导式全是这一类(iter 分别是 Subscript.items()、
Name、Name.items()),它们的键是"支持语言列表"而不是模板,跟随符号去解会
把派生结构判成模板表。这个边界让本次改动对真实仓库零影响 —— `--count`
仍是 339。
iter 限定为字面量序列还有一层作用:不限定的话 Name / Call 会走到
`gen.iter.elts` 抛 AttributeError,把整个门禁带崩。
## 测试
69 -> 83 项。三组:可解推导式被判定(含 codex 原例与 locale 批量形态)、
可解且含 zh-TW 则静默、不可解则放弃(iter 是 Name / 调用、有 if、多
generator、key 是计算表达式、key 绑到 value 位、**key 是自由变量而非循环
变量**),外加非序列 iter 不崩。
七条变异全部被抓。其中三条最初存活:
- iter 类型限制放开 —— 原变异改成 `return set()` 与 `return None` 行为
等价(都不含 en 故都不报),换成放开到含 ast.Name 才暴露(崩溃,4 红)
- key==target 校验、key 在 names 里校验 —— 我漏了「key 是自由变量」的
case(`{other: 1 for loc in ("en","zh")}`),补上后被抓
Refs #2500
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c785664fae
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
check_docstring_no_cjk 在 CI 上红:_comprehension_keys 的 docstring 里 用了 "你好" 当示例值。示例值换成 ASCII。 我上一轮 commit 前只跑了本门禁自查,漏跑了 docstring 门禁——改了 docstring 就该跑它。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
两条来源不同但都是定位/求解的边界。
## 1. 嵌套表与父表同行时定位错位(对抗审查发现)
`locate_touched` 里 `by_line = {node.lineno: node for ...}` 按起始行建字典,
而 `T = {"en": {` 这种写法让内层表与父表 lineno 相同 —— 后者覆盖前者,
父表的 span 1..7 被替换成内层的 1..4。
后果:碰到第 5 行(在父表 body 内、内层 span 外)时两条都被算作
pre-existing,失败信息退化成「no touched table is missing it — run --full」,
把作者推去 339 条 backlog 里肉眼找。这正是 locate_touched docstring 说要
避免的事。
改为 `_offending_nodes()` 让每个节点带自己的 (lineno, end_lineno),
`find_violations` 变成它的薄封装;谓词抽成 `is_offender(keys)`。同一行两个
offender 的 label 去重 —— 打印同一位置两遍读起来像 bug 而不像两张表。
判定不受影响(pass/fail 走 count_offenders,locate_touched 只用于组装信息)。
审查实测:config/prompts 现有 22 个模块 339 条违规里 lineno 冲突 0 次,
303/342 个 dict 赋值用 bare `{` 风格 —— 所以这条在当前树上不会触发。但门禁
是前瞻性的(要判的表还不存在),而这写法是普通 Python,仓库 scripts/ 和
tests/ 里就有 14 处,且 ruff 只选了 ASYNC 规则、CI 无 format 步骤,没有任何
东西阻止它进新模块。失败是静默的(不崩,只是信息退化),会悄悄上线。
## 2. dict([pairs]) 整表逃检(codex :135)
T = dict([("en", "english"), ("zh", "s")]) -> exit 0
标准的 iterable-of-pairs 构造器。位置参数是 list 不是 mapping,
`resolve_keys` 对它返回 None 于是整表放弃。新增 `_pair_sequence_keys()`:
容器须是字面量 List/Tuple/Set,每个元素须是二元序列且首项为字符串常量,
任何一条不满足即 None。位置参数解析改为先试 mapping、再试 pair 序列。
## 测试
83 -> 93 项。新增:嵌套同行表两条都计数、定位用各自 span、同行 label 去重、
iterable-of-pairs 可解被判 / 含 zh-TW 静默 / 四种不可解形态放弃。
七条变异全部被抓:locate 退回按 lineno 建字典、label 不去重、
_offending_nodes 不排序、pair-sequence 路径移除、pair 不校验长度为 2 /
首元素是 str 常量 / 容器类型放开。
对真实仓库零影响:`--count` 仍是 339。
Refs #2500
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cf491ce554
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
派了一轮对抗审查(4 攻击面 × 独立复核,23 agent)。6 条 CONFIRMED,其中 by_line 那条已在 cf491ce 修掉,余下五条本次处理。审查 agent 逐条独立复现 过,还纠正了报告者的若干错误论断(见下)。 ## noqa 逃生舱比兄弟门禁弱(三条同源) `_has_noqa` 用 `rf"#\s*noqa:\s*{CODE}\b"`,把 code 锚死在 `noqa:` 紧后, 于是这些**全部失效**: # noqa -> 兄弟门禁 True,本门禁 False # noqa: DOCSTRING_CJK, PROMPT_ZH_TW -> 兄弟 True,本门禁 False # noqa: E501,PROMPT_ZH_TW -> 兄弟 True,本门禁 False 即:按仓库其余四个门禁的惯例书写,抑制会被静默忽略。改为照抄兄弟实现 (check_docstring_no_cjk.py:91,另两处逐字相同),行为对齐 ruff/flake8。 新增测试直接**加载兄弟门禁做逐形态比对**,九种写法必须一致。 第三条:noqa 写在**闭合行**(`}` 那行)不生效。改为接受起始行或结束行 —— 闭合括号是自然的落点,而且合并表达式的 node.lineno 是左操作数的行,不是 作者认为的"表开始"那行。 ## str.splitlines() 与 AST 行号不一致 `splitlines()` 额外在 \x0b \x0c \x1c \x1d \x1e \x85 U+2028 U+2029 上断行, CPython 一个都不算换行。字符串字面量里出现任一个,后续每行错位一位 —— noqa 匹配到邻居的表,被抑制的表反被上报、真 offender 反被漏掉(审查实测 count 从 0 变 1)。 改为 `re.split(r"\r\n|\r|\n", source)`。审查**否掉了报告者建议的 `split("\n")`**:纯 CR 行尾文件会塌成一行,noqa 全面失效(实测把带 noqa 的表也报出来)。测试覆盖五组行尾/控制字符。 ## HEAD 侧与 base 侧文件集合不同源 base 侧走 `git ls-tree`,HEAD 侧走 `PROMPTS_DIR.rglob("*.py")` 完全不问 git。凡磁盘有、tree 里没有的 .py 单向计入 grew —— 一个被 gitignore 的草稿 模块就能让门禁误报。审查在临时 repo 端到端复现了(base==HEAD 无 diff,落 一个 ignored 的 scratch.py 后 exit 1,输出与报告逐字同形)。 改为先问 `git ls-files --cached --others --exclude-standard`,与 rglob 结果 取交集。这条组合正好画对了线:已跟踪的留下、untracked 但未 ignore 的留下 (本地仍能看见 WIP 新模块)、ignored 的踢掉。git 答不出来时返回 None 表示 "不过滤",因为 --count/--full 在 checkout 之外也该能用。 端到端复验:无 diff -> exit 0;落 ignored 草稿 -> 仍 exit 0(修前 1);落 untracked 的 WIP 模块 -> exit 1(仍被检查)。 ## 测试 93 -> 115 项。八条变异全部被抓:noqa 退回锚死首位、noqa 只看开括号行、 行切分退回 splitlines()、行切分改成 split(newline)、磁盘侧不按 git 过滤、 ls-files 去掉 --exclude-standard / --others、git 失败时返回空集而非 None。 顺带修掉一个 ruff warning:注释里写全 `# noqa: CODE` 会被 ruff 当成真指令。 对真实仓库零影响:`--count` 仍是 339。 Refs #2500 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
review 反馈两条(codex),均实测证实。
## 1. dict.fromkeys 逃检(:138)
T = dict.fromkeys(("en", "zh"), "tpl") -> exit 0
`node.func` 是 `ast.Attribute` 而不是 `ast.Name`,`dict(...)` 分支看不见它,
而它也没有子 mapping 节点可供遍历兜底。这个构造器在 config/prompts 里已有
使用先例(prompts_emotion.py 用它做保序去重,参数是 Name/生成器,因此仍
保持不可解——本次改动对那两处零影响)。
新增 `_is_dict_fromkeys()` 与 `_literal_string_sequence()`:只在第一个位置
参数是字面量 Tuple/List/Set 时求解,且校验 `func.value` 确实是名为 dict 的
Name(否则 `other.fromkeys(...)` 会被误当成 dict 构造)。无位置参数时放弃
(不然 `node.args[0]` 抛 IndexError)。
推导式的单 Name target 分支改为复用同一个 helper——两处本来就是同一件事
「从字面量序列取字符串常量」。
## 2. 常量非字符串键让整表放弃(:124)
T = {None: "d", "en": "e", "zh": "s"} -> exit 0
`None` / `1` 这类**常量**非字符串键不可能是 'zh-TW',它藏不住任何东西,
所以该跳过继续读,而不是放弃整张表。只有**非常量**键才值得放弃——那个
可能就是 zh-TW 本身。
三处按同一规则对齐:dict literal 的键、`_pair_sequence_keys` 的首项、
`_literal_string_sequence` 的元素。
对真实仓库零影响:`--count` 仍是 339。
## 测试
115 -> 129 项。新增:fromkeys 字面量被判 / 含 zh-TW 静默 / 三种不可解放弃、
常量非 str 键三种形态被判 / 与 zh-TW 并存时静默、非常量键仍放弃、
**序列里含非常量元素时放弃**。
八条变异全部被抓。最后一条最初存活:把 `_literal_string_sequence` 的非常量
元素从「放弃」改成「跳过」时测试全绿——因为我缺了
`dict.fromkeys(("en", "zh", MAYBE_ZH_TW), t)` 这个 case。那个变异会造成
**误报**(解成 {en, zh} 报违规,而未知元素可能正是 zh-TW),补上后被抓。
Refs #2500
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
scripts/check_prompt_zh_tw.py (1)
626-629: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win帮助文案还留着旧的 "signature ratchet",说明文字也漏了 closing line 喵。
门禁早就改成总数对比了,
--help里却还写 signature ratchet;而且 Line 352-356 明确对 opening 或 closing 行都做豁免,失败提示却只告诉作者放在 opening line —— 作者照着} # noqa: PROMPT_ZH_TW写反而会以为自己写错了呢,笨蛋喵~🔧 建议修复
parser = argparse.ArgumentParser( description=( "Require 'zh-TW' on newly added localized prompt dicts " - "(signature ratchet against --base; --full scans everything)." + "(offender-count ratchet against --base; --full scans everything)." ) )"back to 'en', not 'zh', so Traditional Chinese users would get an " "English prompt. Add the template, or put '# noqa: PROMPT_ZH_TW' on the " - "dict's opening line if it genuinely does not need one.\n" + "dict's opening or closing line if it genuinely does not need one.\n"Also applies to: 690-694
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/check_prompt_zh_tw.py` around lines 626 - 629, Update the help descriptions around the prompt-checker argument definitions, including the `description` at the diff location and the corresponding text near the alternate occurrence, to describe the current total-count comparison instead of a “signature ratchet.” Also state that exemptions may be placed on either the opening or closing line, matching the logic around lines 352–356, so the failure guidance does not imply only the opening line is valid.
🧹 Nitpick comments (1)
tests/unit/test_check_prompt_zh_tw.py (1)
453-467: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value建议把不可见 marker 改成显式转义喵。 这组参数确实是
U+2028、U+2029、U+000C、U+000B、U+0085,没有空串或普通空格喵。只是直接写不可见字符不太直观,改成\u2028、\u2029、\x0c、\x0b、\u0085会更清楚喵。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/unit/test_check_prompt_zh_tw.py` around lines 453 - 467, Update the marker parameter values in test_line_split_matches_ast_line_numbers to use explicit escape sequences for U+2028, U+2029, U+000C, U+000B, and U+0085, while preserving the existing parameter set and test behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/check_prompt_zh_tw.py`:
- Around line 571-577: Update the subprocess invocation in the prompt-file
discovery function containing `subprocess.run` so a missing `git` executable is
caught and handled by returning `None`, matching the documented behavior;
preserve the existing nonzero-returncode fallback.
---
Outside diff comments:
In `@scripts/check_prompt_zh_tw.py`:
- Around line 626-629: Update the help descriptions around the prompt-checker
argument definitions, including the `description` at the diff location and the
corresponding text near the alternate occurrence, to describe the current
total-count comparison instead of a “signature ratchet.” Also state that
exemptions may be placed on either the opening or closing line, matching the
logic around lines 352–356, so the failure guidance does not imply only the
opening line is valid.
---
Nitpick comments:
In `@tests/unit/test_check_prompt_zh_tw.py`:
- Around line 453-467: Update the marker parameter values in
test_line_split_matches_ast_line_numbers to use explicit escape sequences for
U+2028, U+2029, U+000C, U+000B, and U+0085, while preserving the existing
parameter set and test behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 30f2c345-647c-4c7d-8c81-00ca9ba6946d
📒 Files selected for processing (3)
.github/workflows/analyze.ymlscripts/check_prompt_zh_tw.pytests/unit/test_check_prompt_zh_tw.py
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Project-N-E-K-O/N.E.K.O.-PC(manual)
🚧 Files skipped from review as they are similar to previous changes (1)
- .github/workflows/analyze.yml
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c6a6df5a8d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
review 反馈两条(coderabbit),都是我自己造成的文档/实现漂移。 ## 1. git 不在 PATH 时崩溃 `_git_visible_prompt_files` 的 docstring 写了「no repo, git missing」两种情况 都返回 None,但实现只看 returncode。git 可执行文件缺失时 `subprocess.run` 抛 的是 FileNotFoundError(OSError 子类),没人接住 —— 于是无 git 环境跑 `--count` / `--full` 吐 traceback,而那正是这个 fallback 存在的理由。 实测确认会崩。加 `except OSError: return None`。 ## 2. 文案漂移两处 - `--help` 还写着 "signature ratchet",而判据早已收敛为总数对比。读者照它 去找一个不存在的逻辑。 - 失败提示只说把 noqa 放 opening line,但实现(本轮早前)已接受 opening **或** closing 行。作者写 `} # noqa: PROMPT_ZH_TW` 反而会以为自己写错了。 一并把模块 docstring 的逃生舱说明补全(可与其他 code 逗号并列、顺序无关、 裸 noqa 亦可 —— 与兄弟门禁和 ruff 一致),以及 analyze.yml 里的同句。 ## 测试 129 -> 132 项。三条都做成了断言而非注释:git 缺失返回 None、`--help` 输出不 含 "signature ratchet" 且含新说法、失败提示串含 "opening or closing line"。 后两条读的是真实输出/源码,所以文案再漂移会直接红。 三条变异全部被抓。 Refs #2500 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7a5598abdd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
review 反馈两条(codex :317 / :348),都指向跨语句构建的表。分开处理,因为
两个方向的性质不同。
## 误报方向(修)
T = {"zh-TW": "t"}
T.update({"en": "e", "zh": "s"}) -> 报了 payload
payload 单独看缺 zh-TW,但运行时 T 是合规的。`update()` / `|=` 的 payload
与 spread operand 是同一件事 —— 合并进目标的片段,单独判定无意义。加进
`_merge_operands`,沿用既有的 fragment 压制机制。
写测试时撞出反方向的同一类误报:
T = {"en": "e", "zh": "s"}
T.update({"zh-TW": "t"}) -> 报了 base 表
新增 `_backfilled_table_nodes()`:预扫描出「被 mutation 补上 zh-TW」的名字
(`X.update({...zh-TW...})` / `X["zh-TW"] = ...` / `X |= {...zh-TW...}`),
压制绑定到这些名字的表。
**豁免刻意收窄到「mutation 确实提供了 zh-TW」**。放宽成"任意 mutation 就
免检"会让一句无关的 `T["other"] = x` 替真违规表开脱 —— 拿一个罕见误报换一
大片盲区,方向反了。名字匹配不做作用域分析:这里过度匹配只会豁免一个"名字
在别处被给过 zh-TW"的表,是安全的方向。
## 漏检方向(不修,声明为边界)
T = {"en": "e"}
T["zh"] = "s" -> 零 offender
要判它需要真正的过程内数据流分析(名字绑定 + 跨作用域/别名/分支/循环追踪
mutation)。做一半比不做更糟:漏掉一个条件分支里的 `T["zh-TW"] = ...` 就会
报一张运行时正常的表,而误报正是让门禁被绕过而非被满足的原因。已写进
docstring 的 "Scope" 段落。
## 真实仓库现状(实测,非推断)
`config/prompts` 下 `.update(<literal>)` 3 处、下标赋值 5 处、`|=` 0 处;
**被判违规的表中作为 mutation target 的:0 处**。所以 339 这个基数不含误报,
本次改动对它零影响(仍是 339)。
## 测试
132 -> 142 项。三组:payload 是 fragment(update / |= / 无绑定的 target)、
补 zh-TW 的三种 mutation 豁免目标、无关 mutation 不豁免(`T["other"]` /
`update({"ja"})` / `|= {"ja"}`),外加豁免按 name 匹配(A 免检 B 仍报)。
六条变异全部被抓:payload 不当 fragment(update / |= 各一)、豁免整体移除、
豁免放宽到任意 mutation、下标豁免不校验键是 zh-TW、豁免不按 name 匹配。
Refs #2500
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: faf4b0897f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
review 反馈两条(codex :451 / :494),都是误报/误豁免方向。
## 1. 命名 fragment 被当独立表(误报)
_FRAGMENT = {"en": "e", "zh": "s"}
T = {**_FRAGMENT, "zh-TW": "t"} -> 报了 _FRAGMENT
内联 operand 早已被当 fragment 压制,但换成命名引用就漏了这层。扩展
`_exempt_table_nodes`(原 `_backfilled_table_nodes`,已改名因为职责不止
backfill):凡作为 merge operand 出现过的 Name,其绑定的表都不单独判定。
`**name` / `name | ...` / `dict(name, ...)` / `update(name)` 四种入口统一。
代价是盲区:若同一个名字既当 fragment 又当完整表用,它就免检了。这是该
选的方向——误报会让门禁被绕过,漏检不会。
## 2. 字符串里的 noqa 文本被当指令(误豁免)
T = {"en": """# noqa: PROMPT_ZH_TW
more text""", "zh": "s"} -> 静默(应报)
多行模板的首行落在 dict 开括号行上,而按原始行文本搜 noqa 分不清注释与
字符串内容。真实触发条件比较窄(noqa 文本须延伸到行尾,否则引号会破坏
正则的 lookahead),但一旦命中就是让违规表静默通过。
改为用 `tokenize` 只收集 COMMENT token:新增 `_comment_lines(source)` 返回
按行对齐的注释文本列表,`_parse_source` 返回它而不是原始行——那个返回值
本来就只喂 noqa 查找,现在语义与用途对齐了。tokenize 失败时全行返回空串
(宁可不豁免,不可错豁免;语法错误已由 ast.parse 报过)。
测试 helper `_violations` 改走 `_parse_source`,所以全部 150 项用例都经真实
解析路径,而不是各自 `splitlines()`。
## 测试
142 -> 150 项。新增:三种命名 fragment 入口不误报、豁免按 name 匹配、
字符串里的 noqa 不豁免、三种真注释位置仍豁免、`_comment_lines` 按行对齐、
tokenize 失败时不豁免任何行。
六条变异全部被抓:命名 fragment 豁免移除 / 去掉 isinstance 守卫(31 红)、
noqa 退回读原始行、`_comment_lines` 不按行号对齐 / 失败时返回原始行 /
不筛 COMMENT 类型。
对真实仓库零影响:`--count` 仍是 339。
Refs #2500
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c76b1ae5bd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
review 反馈两条(codex :429 / :409),都源自上一轮我自己的实现。
## 1. 重新赋值的表继承了豁免(漏检)
T = {"en": "e", "zh": "s"}
T["zh-TW"] = "t"
T = {"en": "e2", "zh": "s2"} -> 零 offender(第三行是真违规)
我按 name 全模块匹配,于是同名的后续赋值也被豁免。原注释里我称这是"安全
方向",但它确实造成漏检 —— reviewer 指出得对。
改为豁免落在**mutation 之前最近的那次绑定**上:预扫描收集每个 name 的
(lineno, value id) 序列并排序,mutation 只豁免 `ln <= mutation.lineno` 里
最后一个。比较用 `<=` 而非 `<`,因为 `T = {...}; T["zh-TW"] = "t"` 写在
一行时两者 lineno 相同。
不做作用域分析:mutation 与它所改的绑定在实践中同作用域,且"最近前驱"在
两种嵌套方向下都取对(已验证)。
## 2. update 的 pair 序列 payload 不算 backfill(误报)
T = {"en": "e", "zh": "s"}
T.update([("zh-TW", "t")]) -> 报了 base 表(运行时合规)
backfill 检测只用 resolve_keys,而它对 list 返回 None。加 `_pair_sequence_keys`
兜底,与 `dict()` 位置参数的处理一致。
## 测试
150 -> 156 项。新增:重新赋值的表仍报、豁免不跨 name、两种 pair 形式的
update 算 backfill、**mutation 前有两次绑定时只豁免最近的那次**、
**绑定与 mutation 同行时仍豁免**。
后两条是变异验证逼出来的:`prior[-1]` 改成 `prior[0]`、`<=` 改成 `<` 时
测试全绿,因为原有 case 都太"最小"——只有一个前驱绑定、绑定与 mutation 不
同行,让两种实现选择等价。补上能区分的 case 后四条变异全部被抓。
对真实仓库零影响:`--count` 仍是 339。
Refs #2500
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5740a063b1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
review 反馈(codex :429 后续)。豁免的绑定收集只看 ast.Assign,漏了
ast.AnnAssign:
T: dict[str, str] = {"en": "e", "zh": "s"}
T["zh-TW"] = "t" -> 报了 base 表(运行时合规)
带类型标注的 prompt 常量是很普通的写法,漏掉它就是对这类模块的持续误报。
加 AnnAssign 分支(target 是 Name、value 非 None)。
## 测试
156 -> 161 项。新增:三种标注绑定被豁免(下标 / update / 命名 fragment)、
无 value 的裸声明 `T: dict` 不崩且不影响其他表、标注的违规表本身仍被报
(豁免针对 mutation 而非标注)。
「AnnAssign 分支移除」变异被抓(3 红)。
另一条变异「不校验 value 非 None」**存活**,如实记录:该检查是防御性的,
不是承重的 —— value 为 None 时进集合的是 `id(None)`,None 是单例、其 id
不会被复用给任何 dict 节点,所以行为等价。不为它硬凑测试。
对真实仓库零影响:`--count` 仍是 339。
Refs #2500
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
review 反馈(codex :452)。上一轮「凡作为 merge operand 出现的 Name 都豁免」
太宽,造出一个新盲区:
T = {"en": "e", "zh": "s"}
U = dict(T) -> 零 offender
T 因为是 operand 被豁免,U 因为 resolve_keys 不跟随 Name 而不可解 —— 一个
违规表加它的副本,加起来算零。
按 reviewer 的方向收窄:只在**包含它的构造确实提供 zh-TW** 时才豁免其命名
输入。新增 `_directly_visible_keys(node)` —— 与 resolve_keys 不同,它对不可
知的部分贡献空集而不是 None,所以能回答「这个构造自己写出了哪些键」。
{**_F, "zh-TW": ..} -> 可见 {zh-TW} -> 豁免 _F
{**_F, **{"zh-TW": ..}} -> 递归进 spread -> 豁免 _F
dict(_F, **{"zh-TW":..}) -> 可见 {zh-TW} -> 豁免 _F
dict(T) / {**T} / {**T, "ja": ..} -> 不可见 -> T 仍受规则约束
## 测试
161 -> 171 项。新增:三种复制形态不再豁免被复制的表、四种补 zh-TW 形态仍
豁免、zh-TW 藏在嵌套 spread 里也算、`_directly_visible_keys` 对不可知部分
返回空集而非 None。
五条变异全部被抓:去掉 zh-TW 条件(退回任意 operand 豁免)、
`_directly_visible_keys` 分别不递归 spread / 不看 dict() 的 ** / 不看 BinOp。
其中「不递归 spread」最初存活,因为原有 case 里 zh-TW 都是直接键,补
`{**_F, **{"zh-TW": ..}}` 后被抓。
对真实仓库零影响:`--count` 仍是 339。
Refs #2500
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d967aa9290
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| name: str | None = None | ||
| if ( | ||
| isinstance(node, ast.Assign) | ||
| and len(node.targets) == 1 |
There was a problem hiding this comment.
Recognize chained bindings when exempting backfills
When a table is bound through an ordinary chained assignment such as T = U = {"en": "e", "zh": "s"} and then completed with T["zh-TW"] = "t", both names reference the same now-compliant dictionary. Requiring exactly one assignment target prevents this binding from entering assignments, so the literal is still counted as an offender and a PR adding it fails the ratchet incorrectly; collect each simple Name target of a chained assignment.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
确认,已修(38233b015)。绑定收集要求 len(targets) == 1,链式赋值有两个 target 就整条被跳过。改为遍历所有简单 Name target——它们指向同一个对象,通过任一个做的 mutation 都完成了那张表。
覆盖三条:经 T 补、经 U 补(两个名字任一都行)、链式但无 backfill 时仍报。变异「退回只收第一个 target」会红。
🤖 Addressed by Claude Code
| return keys | ||
| if isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitOr): | ||
| return _directly_visible_keys(node.left) | _directly_visible_keys(node.right) | ||
| return set() |
There was a problem hiding this comment.
Include fromkeys results in visible merge keys
When a named fragment is completed with a supported static constructor, for example _F = {"en": "e", "zh": "s"}; T = _F | dict.fromkeys(("zh-TW",), "t"), the resulting table is compliant, but _directly_visible_keys() falls through for dict.fromkeys() even though resolve_keys() can resolve it. The enclosing union is therefore not recognized as supplying zh-TW, _F is not exempted as a fragment, and the PR fails on a false offender; handle statically resolvable dict.fromkeys() results here as well.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
确认,已修(38233b015)。resolve_keys 早已支持 dict.fromkeys,但 _directly_visible_keys 没跟上——同类支持只加了一处,是这个 PR 里反复出现的老毛病。
补上分支后 _F | dict.fromkeys(("zh-TW",), t) 和 {**_F, **dict.fromkeys(("zh-TW",), t)} 都能识别为供给 zh-TW。复用 _literal_string_sequence,不可解时它返回 None,转成空集——不可解就不算供给,所以 dict.fromkeys(LOCALES, t) 不会豁免 _F,这条也钉了测试。
三条变异被抓,含「不校验可解(硬返回 {zh-TW})」。
🤖 Addressed by Claude Code
review 反馈两条(codex :434 / :397),都是误报方向,都是我实现的不完整处。
## 1. 链式赋值不入豁免表
T = U = {"en": "e", "zh": "s"}
T["zh-TW"] = "t" -> 报了(运行时合规)
绑定收集要求 `len(targets) == 1`,链式赋值有两个 target 就整条跳过。改为
遍历所有简单 Name target —— 它们指向同一个对象,通过任一个做的 mutation
都完成了那张表。
## 2. dict.fromkeys 不算「可见供给 zh-TW」
_F = {"en": "e", "zh": "s"}
T = _F | dict.fromkeys(("zh-TW",), "t") -> 报了 _F
`resolve_keys` 早已支持这个构造器,但 `_directly_visible_keys` 没跟上,于是
这个 union 看起来什么都没提供,_F 不被当 fragment。补上分支,复用
`_literal_string_sequence`(不可解时返回 None,转成空集 —— 不可解就不算供给)。
## 测试
171 -> 178 项。新增:链式绑定经两个名字任一被豁免、链式但无 backfill 仍报、
两种 fromkeys 供给形态(`|` 与 spread)豁免、fromkeys 供的是别的 locale 或
不可解时仍报。
三条变异全部被抓:链式退回只收第一个 target、visible keys 去掉 fromkeys
分支、fromkeys 不校验可解(硬返回 {zh-TW})。
对真实仓库零影响:`--count` 仍是 339。
Refs #2500
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Summary
issue #2500 第 2 步。存量 339 个 i18n prompt dict 缺
'zh-TW',而 issue 开出时是 320 —— 三天涨了 19 个,因为没有任何东西盯着。补齐是分批的长活,这个门禁只负责让洞不再变大。与 #2568(七处 normalizer 收口)无依赖,可独立合并。
规则
config/prompts/下含'en'键且含'zh'或'zh-CN'的 dict 是本地化 prompt 表,必须也含'zh-TW'。理由:
_loc缺 key 回落'en'而不是'zh'(prompts_sys.py:34),所以缺 zh-TW 的表会给繁中用户端出英文 prompt,而不是退一步的简体。棘轮就是一个总数对比
比较 merge-base 与 HEAD 上「缺 zh-TW 的表」各有多少个,HEAD 更多就失败。没有行号,没有 per-dict 身份,没有分组。
走到这一步试错了三轮,每一轮都被 reviewer 打回,而每一种"更精确"的判据都各自破了一个总数天然处理的场景:
{en, ja}表加一行"zh"→ 定义行不在 diff 里 → 漏检(正是门禁存在的理由);纯重命名 → 整个文件算新增 → 误报全部存量'fr'模板)→ 签名变了 → 读成全新表 → 误报zh/zh-CN分桶)zh→zh-CN迁移 → 一桶减一、另一桶加一,而 Counter 相减只保留正数 → 误报。而这恰恰是 issue #2500 自己的终局动作,等于门禁会挡住它本该服务的迁移总数对这三种全都免疫:重命名、改文案、加 locale、scheme 迁移都不改变总数,而一个新受规则约束的表会让它 +1。
已知盲点(写进 docstring 并钉进测试):删一个违规表 + 加一个违规表,净变化 0 会放行。刻意接受 —— 替代方案是跨版本按身份匹配 dict,而候选的每一种身份(位置、键集合、scheme)正是上面三轮试错。
定位:总数只说明"涨了"不说明"在哪",而 339 个存量太多不能全打印。所以 diff 行号仍用作提示,把报错 narrow 到本次改动碰过的表:
判定权威是总数,行号只影响提示(
-M防 rename 把整个文件标成新增,core.quotePath=false防非 ASCII 路径被 C-quote)。三种模式:默认总数棘轮(CI 用);
--full列全部积压;--count只报数量。单表逃生用# noqa: PROMPT_ZH_TW写在开括号那行。CI 接线照check_docstring_no_cjk.py既有形态(PR-only、BASE_REF间接引用以避开 zizmor 的 template-injection 告警)。Review 修复
判据演进(三轮,见上表):Codex 报的行号漏检/rename 误报 → 键集合签名;Codex + Greptile 报的加 locale / scheme 迁移误报 → 总数对比。
收口不彻底的三处(本轮,均实测证实):
dict()位置参数:上一轮只挡了**,但dict(BASE, en=..., zh=...)的BASE同样可能是zh-TW的来源。实测被误报。现在node.args非空即放弃判断。+++行仍被 C-quote:上一轮给ls-tree加了-z,漏了 patch 输出这条通道。实测+++ "b/config/prompts/\344\270\255...py"匹配不到真实路径,该文件的定位提示被静默丢弃。已加-c core.quotePath=false。# coding: latin-1是合法 Python,上一轮的except UnicodeDecodeError把它整个跳过 = 不检查一个真实模块。改为读 bytes 后经tokenize.detect_encoding解码,两侧对偶(磁盘侧read_bytes、base 侧新增_git_bytes),只有真正损坏的字节才跳过且必有诊断。「什么算一个表」也收敛了四轮,每轮修一个引入下一个:
ast.walk{**{en,zh}, **{zh-TW}}误报前半{**COMMON, "new": {en,zh}}里的"new"从此不检查BinOp不是 dict 节点、两个 operand 又都被压制 →{"en"} | {"zh"}整表逃检最终形态:
resolve_keys()递归解 mapping 表达式的键集合,穿透 dict literal 的**、dict()的位置参数与**、以及|;能解就判结果,任何一部分不可知(spread 一个 Name、动态键、dict()套变量)就放弃。片段误报不再靠「压制」避免,而是因为片段的键已经算进整体结果里。operand 压制改为无条件——BASE | {en,zh}整体不可解但右侧仍是片段。另外两处 IO 层的不对称:
"zh-TW"是总数增长的真实路径,但@@ -4 +3,0 @@新侧长度 0,range()为空 → 判定正确报错却定位丢失。改为记录删除点两侧(max(1, start)防文件首删除产生行号 0)。read_bytes()跟随链接读目标源码,base 侧git show拿到 symlink blob(内容即目标路径字符串)。引入 commit 净 0 过关,之后每个 PR 都误报。两侧一致排除(base 侧读 ls-tree 的 mode 跳过120000,因此不能用--name-only;磁盘侧is_symlink()),都带 stderr 诊断。Windows CI 红(本轮的真 bug,不是 flaky):
subprocess的text=True不指定 encoding 时按 locale 解码,CI runner 是 cp1252(我本地是 cp936,所以本地一直绿)。而且异常发生在 subprocess 的_readerthread里,failure 伪装成 returncode 断言。两侧对偶修:测试侧_run()显式encoding="utf-8",脚本侧main()把 stdout/stderr reconfigure 成 UTF-8(stdout 是 pipe 时print同样用 locale 编码)。Testing
tests/unit/test_check_prompt_zh_tw.py(69 项):表形状识别(含dict()调用形式三态、**/ 位置参数放弃判断)、总数棘轮的各种转移(新表 / 加键 / 重命名 / 改文案 / 补 zh-TW / 加 locale / scheme 迁移 / 已知盲点)、定位提示 narrowing、noqa 三态、PEP 263 解码三态、_git与_git_bytes的编码契约、main()本身(涨→1、补齐→0、未变→0、强制 UTF-8、无reconfigure流兜底)、文件发现(子包、非.py)、CLI。36 条变异全部被抓,其中三条最初存活、靠变异验证才发现:
main()里的减法方向写反(门禁完全失效)—— 测试 helper 自己重算了一遍减法,没走main()dict()位置参数 —— 参数化 case 同时带位置参数和**,后者兜住了;补两个只有位置参数的 case 后被抓端到端复验(走真实 git 与真实文件):非 ASCII 文件名的新增表定位到
中文表.py:1;latin-1 模块被检出在legacy_latin1.py:2而非静默跳过;纯重命名 exit 0;已有表加键 exit 1;子包--count计入。tests/unit全量 8506 passed, 45 skipped。check_docstring_no_cjk/check_prompt_hygiene/check_module_layering均 exit 0,门禁对本 PR 自身 exit 0。回归报告 / Regression Report
新增一个 PR-only CI 步骤和一个脚本,不改任何运行时代码。
# noqa: PROMPT_ZH_TW逃生。直接 push 到 main 时跳过(无 diff base)Refs #2500
🤖 Generated with Claude Code
Summary by CodeRabbit
CI 改进
zh-TW,并支持规范化的单条例外标记。zh-TW的条目总数增加时阻止合并。测试