Skip to content

ci(prompts): 新增 prompt dict 必须带 zh-TW 的棘轮门禁(#2500 第 2 步) - #2569

Open
wehos wants to merge 20 commits into
mainfrom
claude/issue-2500-zh-tw-gate
Open

ci(prompts): 新增 prompt dict 必须带 zh-TW 的棘轮门禁(#2500 第 2 步)#2569
wehos wants to merge 20 commits into
mainfrom
claude/issue-2500-zh-tw-gate

Conversation

@wehos

@wehos wehos commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

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 打回,而每一种"更精确"的判据都各自破了一个总数天然处理的场景:

判据 破在哪
按 diff 行 已有 {en, ja} 表加一行 "zh" → 定义行不在 diff 里 → 漏检(正是门禁存在的理由);纯重命名 → 整个文件算新增 → 误报全部存量
按整个键集合 给已有违规表加一个无关 locale(新 'fr' 模板)→ 签名变了 → 读成全新表 → 误报
按简体键 schemezh / zh-CN 分桶) zhzh-CN 迁移 → 一桶减一、另一桶加一,而 Counter 相减只保留正数 → 误报。而这恰恰是 issue #2500 自己的终局动作,等于门禁会挡住它本该服务的迁移

总数对这三种全都免疫:重命名、改文案、加 locale、scheme 迁移都不改变总数,而一个新受规则约束的表会让它 +1。

已知盲点(写进 docstring 并钉进测试):删一个违规表 + 加一个违规表,净变化 0 会放行。刻意接受 —— 替代方案是跨版本按身份匹配 dict,而候选的每一种身份(位置、键集合、scheme)正是上面三轮试错。

定位:总数只说明"涨了"不说明"在哪",而 339 个存量太多不能全打印。所以 diff 行号仍用作提示,把报错 narrow 到本次改动碰过的表:

[PROMPT_ZH_TW] 1 more localized prompt dict(s) lack 'zh-TW' than at the merge-base.
        in lines this change touched: config/prompts/中文表.py:1
        (339 pre-existing offender(s) are exempt)

判定权威是总数,行号只影响提示(-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 非空即放弃判断。
  • diff 的 +++ 行仍被 C-quote:上一轮给 ls-tree 加了 -z,漏了 patch 输出这条通道。实测 +++ "b/config/prompts/\344\270\255...py" 匹配不到真实路径,该文件的定位提示被静默丢弃。已加 -c core.quotePath=false
  • 非 UTF-8 模块被静默跳过# coding: latin-1 是合法 Python,上一轮的 except UnicodeDecodeError 把它整个跳过 = 不检查一个真实模块。改为读 bytes 后经 tokenize.detect_encoding 解码,两侧对偶(磁盘侧 read_bytes、base 侧新增 _git_bytes),只有真正损坏的字节才跳过且必有诊断。

「什么算一个表」也收敛了四轮,每轮修一个引入下一个:

遍历方案 破在哪
平铺 ast.walk 把合并的每个 operand 单独判 → {**{en,zh}, **{zh-TW}} 误报前半
整棵剪枝 丢掉合成容器里按普通键放的独立表 → {**COMMON, "new": {en,zh}} 里的 "new" 从此不检查
压制 operand 但不解合并 BinOp 不是 dict 节点、两个 operand 又都被压制 → {"en"} | {"zh"} 整表逃检
静态求解整体 收敛

最终形态:resolve_keys() 递归解 mapping 表达式的键集合,穿透 dict literal 的 **dict() 的位置参数与 **、以及 |;能解就判结果,任何一部分不可知(spread 一个 Name、动态键、dict() 套变量)就放弃。片段误报不再靠「压制」避免,而是因为片段的键已经算进整体结果里。operand 压制改为无条件——BASE | {en,zh} 整体不可解但右侧仍是片段。

另外两处 IO 层的不对称

  • 纯删除 hunk 定位为空:删掉某表唯一的 "zh-TW" 是总数增长的真实路径,但 @@ -4 +3,0 @@ 新侧长度 0,range() 为空 → 判定正确报错却定位丢失。改为记录删除点两侧(max(1, start) 防文件首删除产生行号 0)。
  • symlink 两侧不对称:磁盘侧 read_bytes() 跟随链接读目标源码,base 侧 git show 拿到 symlink blob(内容即目标路径字符串)。引入 commit 净 0 过关,之后每个 PR 都误报。两侧一致排除(base 侧读 ls-tree 的 mode 跳过 120000,因此不能用 --name-only;磁盘侧 is_symlink()),都带 stderr 诊断。

Windows CI 红(本轮的真 bug,不是 flaky):

encodings\cp1252.py → UnicodeDecodeError: 'charmap' codec can't decode byte 0x9d

subprocesstext=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 条变异全部被抓,其中三条最初存活、靠变异验证才发现:

  1. main() 里的减法方向写反(门禁完全失效)—— 测试 helper 自己重算了一遍减法,没走 main()
  2. stdout 不强制 UTF-8 —— 那正是修 Windows CI 的改动,当时零覆盖
  3. merged 判据丢掉 dict() 位置参数 —— 参数化 case 同时带位置参数和 **,后者兜住了;补两个只有位置参数的 case 后被抓

端到端复验(走真实 git 与真实文件):非 ASCII 文件名的新增表定位到 中文表.py:1;latin-1 模块被检出在 legacy_latin1.py:2 而非静默跳过;纯重命名 exit 0;已有表加键 exit 1;子包 --count 计入。

tests/unit 全量 8506 passed, 45 skippedcheck_docstring_no_cjk / check_prompt_hygiene / check_module_layering 均 exit 0,门禁对本 PR 自身 exit 0。

回归报告 / Regression Report

新增一个 PR-only CI 步骤和一个脚本,不改任何运行时代码。

Refs #2500

🤖 Generated with Claude Code

Summary by CodeRabbit

  • CI 改进

    • 在拉取请求中检查新增多语言提示词字典是否包含 zh-TW,并支持规范化的单条例外标记。
    • 优化违规数量检测,仅当缺少 zh-TW 的条目总数增加时阻止合并。
    • 改进错误定位,支持复杂字典结构、嵌套内容、删除变更及非 ASCII 路径。
  • 测试

    • 大幅扩展相关自动化测试,覆盖多种字典写法、编码、路径、抑制规则及 Git 变更场景。

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>
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 42 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c68071d7-cb37-47de-b2d0-48978d806bf0

📥 Commits

Reviewing files that changed from the base of the PR and between 840e219 and 38233b0.

📒 Files selected for processing (3)
  • .github/workflows/analyze.yml
  • scripts/check_prompt_zh_tw.py
  • tests/unit/test_check_prompt_zh_tw.py

Walkthrough

新增 zh-TW 提示词字典检测脚本,改为按缺失总数执行 ratchet,支持 AST 静态解析、变更定位、编码与 Git 文件处理,并接入 PR 工作流;同时扩展单元测试覆盖核心逻辑与 CLI,喵。

Changes

提示词繁体中文校验

Layer / File(s) Summary
AST 字典检测与定位
scripts/check_prompt_zh_tw.py
通过 AST 识别缺少 zh-TW 的本地化字典,支持多种静态表达式、合并结构、noqa 抑制及按字典 span 定位。
Git 修订对比与 CLI 门禁
scripts/check_prompt_zh_tw.py
处理 base 与磁盘侧提示词源、编码、符号链接和 diff 行范围,并按 offending 总数增长决定检测结果。
PR 工作流接入
.github/workflows/analyze.yml
prompts-i18n 任务中加入仅限 PR 触发的 zh-TW ratchet 检查步骤。
字典检测与 ratchet 测试
tests/unit/test_check_prompt_zh_tw.py
覆盖字典识别、静态解析、嵌套表、ratchet、noqa 抑制和变更定位。
Git、文件扫描与 CLI 测试
tests/unit/test_check_prompt_zh_tw.py
覆盖编码、Git 输出、路径解析、文件过滤、磁盘回退、CLI 输出及返回值,喵。

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

  • #2500:该 PR 实现了缺失 zh-TW 字典检测和 CI ratchet 门禁目标,喵。

Poem

AST 小猫巡字典,喵
zh-TW 键别再躲喵
Git 比对数一数,喵
CI 门神守门笑喵
测试铃铛响叮当,喵

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 标题准确概括了新增 prompt dict 需包含 zh-TW 的 PR-only 棘轮门禁,和本次改动一致喵。
Description check ✅ Passed 描述已包含 Summary、规则、回归报告和 Testing,且未触发必须填写的不拆分理由,整体足够完整喵。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

本 PR 新增一个仅在拉取请求中运行的 CI 棘轮,阻止缺少 zh-TW 的本地化 prompt dict 总数继续增长,并补充覆盖字典形态、编码、Git diff、定位和 CLI 行为的测试喵。

Confidence Score: 5/5

先前的 scheme migration 误报已经由总数比较完整修复,当前 PR 看起来可以安全合并喵。

当前代码不再按 zhzh-CN 分桶比较,单表和批量方案迁移均保持违规总数不变,且没有仍需处理的既有发现喵。

Important Files Changed

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread scripts/check_prompt_zh_tw.py Outdated
Comment thread scripts/check_prompt_zh_tw.py Outdated
Comment thread scripts/check_prompt_zh_tw.py Outdated
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread scripts/check_prompt_zh_tw.py Outdated
Comment thread scripts/check_prompt_zh_tw.py Outdated
Comment thread scripts/check_prompt_zh_tw.py Outdated
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>
Comment thread scripts/check_prompt_zh_tw.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread scripts/check_prompt_zh_tw.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 366689a and d2ab927.

📒 Files selected for processing (3)
  • .github/workflows/analyze.yml
  • scripts/check_prompt_zh_tw.py
  • tests/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)

Comment thread .github/workflows/analyze.yml
Comment thread scripts/check_prompt_zh_tw.py
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread scripts/check_prompt_zh_tw.py Outdated
Comment thread scripts/check_prompt_zh_tw.py
Comment thread scripts/check_prompt_zh_tw.py Outdated
## 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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread scripts/check_prompt_zh_tw.py
Comment thread scripts/check_prompt_zh_tw.py Outdated
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>
Comment thread scripts/check_prompt_zh_tw.py

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread scripts/check_prompt_zh_tw.py Outdated
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread scripts/check_prompt_zh_tw.py
Comment thread scripts/check_prompt_zh_tw.py
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread scripts/check_prompt_zh_tw.py
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread scripts/check_prompt_zh_tw.py
wehos and others added 2 commits July 30, 2026 16:27
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread scripts/check_prompt_zh_tw.py
Comment thread scripts/check_prompt_zh_tw.py
wehos and others added 2 commits July 30, 2026 16:45
派了一轮对抗审查(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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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+2028U+2029U+000CU+000BU+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

📥 Commits

Reviewing files that changed from the base of the PR and between d2ab927 and 840e219.

📒 Files selected for processing (3)
  • .github/workflows/analyze.yml
  • scripts/check_prompt_zh_tw.py
  • tests/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

Comment thread scripts/check_prompt_zh_tw.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread scripts/check_prompt_zh_tw.py
Comment thread scripts/check_prompt_zh_tw.py
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread scripts/check_prompt_zh_tw.py
Comment thread scripts/check_prompt_zh_tw.py
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread scripts/check_prompt_zh_tw.py Outdated
Comment thread scripts/check_prompt_zh_tw.py Outdated
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread scripts/check_prompt_zh_tw.py
Comment thread scripts/check_prompt_zh_tw.py Outdated
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread scripts/check_prompt_zh_tw.py
Comment thread scripts/check_prompt_zh_tw.py
wehos and others added 2 commits July 30, 2026 17:37
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread scripts/check_prompt_zh_tw.py Outdated
name: str | None = None
if (
isinstance(node, ast.Assign)
and len(node.targets) == 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

确认,已修(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()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

确认,已修(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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant