docs(tasks): English edition han-leak brief (agent loop, issue #89)

140 fixed-seed fictional charts reproduce 40% han_leak. Fix at the source
with English twin fields, no tail glossary entries; Chinese output must stay
byte-identical; 200-chart sweep in full pytest plus a smoke file in the
quick gate. Recurrence of BUG-1272.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LutgzyJqNhPpZy1nFsJtN3
This commit is contained in:
Jesse_Chen
2026-10-21 22:39:37 +08:00
co-authored by Claude Opus 5.5
parent 07730e3924
commit 9812f31737
2 changed files with 138 additions and 0 deletions
+1
View File
@@ -514,3 +514,4 @@
| `TASK-other-subject-birth-time-edit-20261021.md` | — | 其他人的出生时间可以修改(去掉 `chart_profiles_guard_birth` 的填报时间不可改;推翻 people-archive P1 §验收 2 该条;active 锁不变) | 排队(等 #86 合入) | `codex/other-subject-birth-time-edit-20261021`;动表;不进循环 |
| `TASK-chart-ascendant-slash-20261021.md` | — | #87 上升状态格「不适用」改「/」+ 悬停 / 点按说明;报告上升行同改并在表下加说明(推翻 10-15 口径) | 待领取(等 #86 合入) | `codex/chart-ascendant-slash-20261021` |
| `TASK-report-export-dialog-polish-20261021.md` | — | #90 导出弹窗:英文不可用说明改顶部浮层(每次打开一次);选中态描边 / 圆点 / 勾选框用主题色、底色中性(推翻 DESIGN「Selected states use canvas-muted + ink」,BUG-1078 主语保留) | 待领取(可与 #89 并行) | `codex/report-export-dialog-polish-20261021` |
| `TASK-report-english-han-leak-20261021.md` | `PROGRESS-report-english-han-leak-20261021.md` | #89 个人报告英文版约 4 成整份丢弃:漏译汉字源头补英文孪生字段、修吃空格;200 张固定种子回归 + 快速门 smoke(复发自 BUG-1272);中文逐字节不变 | 待领取(最先做;#91/#92 在后) | `codex/report-english-han-leak-20261021` |
@@ -0,0 +1,137 @@
# 任务书:个人报告英文版约 4 成被整份丢弃——漏译汉字与吃空格(issue #89,BUG-1272 复发)(2026-10-21)
## 基线
- `origin/staging` = `bd3ce0e7b`。开工前 `git fetch origin --prune`,以 `origin/staging` 为基线。
- 执行方:**Grok(Windows + Docker)**。分支 `codex/report-english-han-leak-20261021`,工作树 `.worktrees/report-english-han-leak-20261021`,**只推这个分支**,不推 staging、不碰 main。
- 关联:BUG-1272(本单复发自它)、BUG-1218 / BUG-1225(Rath 版 Narayana 并列展示)、BUG-1273(生产镜像无 PyJHora)、BUG-1276、BUG-1277。
- **串行顺序**:本单最先做。#91、#92 之后改同一批渲染文件(`pl9_reader_export.py`、`pl9_full_data_export.py`),并沿用本单的 200 张盘测试。本单合入 staging 前,#91 不开工。
## 事故实证
Claude 在 `bd3ce0e7b` 上复现:两组固定种子各生成 60 张、80 张随机虚构盘,屏蔽 PyJHora(模拟生产镜像),走 `professional_report_reference._english_edition`。
| 项 | 结果 |
| --- | --- |
| 140 张盘中被判 `han_leak`、整份英文丢弃 | **56 张(40%)**,与 issue 的 16/40 一致 |
| 是否与 PyJHora 有关 | 无关。同一批盘装与不装 PyJHora,泄漏完全相同 |
| 现有测试的 3 张虚构盘(`tests/test_report_english_edition.py` 的 `CASES`) | 3 张都干净,所以测试一直是绿的 |
| 每张盘耗时(Linux,本机) | 建资料包约 2.5 s,英文渲染约 0.9 s,`_english_edition` 全程约 1.0 s |
### 漏译汉字(按出现盘数)
| # | 残留汉字 | 140 张中出现 | 英文里的样子 | 来源(按符号定位) |
| --- | --- | --- | --- | --- |
| L1 | 座内 | 21 | `period start Libra(house 5 level: lord[source text retained: 座内]degreecount)` | `narayana_legacy_label.rath_summary_line_zh`:英文版也调用它,把 `STRENGTH_LEVEL_ZH[5]`「宫主星座内度数」交给末尾词表拆着译。`STRENGTH_LEVEL_EN` 已有 `lord's degree in sign`,没人用 |
| L2 | 凶 | 21 | `\| overall nature \| [source text retained: 凶](Asubha) \|` | `muhurta.calc_panchanga` 的 `overall_quality` 只有中文(`吉(Subha)` / `中(Mixed)` / `凶(Asubha)`);`pl9_reader_export.render_pl9_parity_markdown` 的 Panchanga 小表不分语言打印标签「综合性质」和这个值 |
| L3 | 计都、反向 | 5 | `- order: [source text retained: 计都]inperiod start: [source text retained: 反向](Table 12)` | `narayana_legacy_label.rath_report_lines_zh` 的「- 顺序:」行用 `direction_text`(中文)。`direction_text_en` 已在 `rath_narayana_view` 里存好,没人用 |
| L4 | 计都、反向(issue 未列) | 9 | `- sub-period direction: Dashasignpresent[source text retained: 计都]: [source text retained: 反向](Table 14), retrograde(the book itself disagrees, …` | `narayana_legacy_label._ad_direction_lines_zh` 用 `rule_text`(中文)。`narayana_rath` 已给出 `rule_text_en` |
| L5 | 观察 | 7 | `\| Punarphoo Yoga \| relationship_observation \| [source text retained: 观察] \| …` | `render_pl9_parity_markdown` 瑜伽表(表头 `\| Yoga \| Category \| Strength \|`)英文分支换了 `effects_en`、`combination_en`,**强度列仍打印 `row['strength']`**。`yoga_engine` 已为每行写 `strength_en`(Punarphoo 规则自带 `observational`,其他走 `STRENGTH_EN`)。「强 / 中 / 弱」之所以没漏,是末尾词表碰巧有单字条目 |
| L6 | 完全(issue 未列) | 7 | `\| Kaal Sarp Dosha \| True \| … \| [source text retained: 完全]Kaal Sarp Dosha——atpresent7planetsinRahu(6)—Ketubetween the axis \|` | `yogas_doshas.calc_kaal_sarp_dosha` 的 `summary` 只有中文;`render_pl9_parity_markdown` 的 Dosha 结果表(`### p163-p171 Mangala / Sade Sati / Dosha Results`)打印 `row['summary']` |
### 吃空格与错译(不留汉字,但英文读不通)
末尾词表把中文按字词换成英文,换出来的词之间不加空格;「第」一律换成 `house `。在 140 张盘里这些不是偶发,而是**每张盘都有**:
| 粘连 / 错译 | 140 张中出现 | 来源线索 |
| --- | --- | --- |
| `scoreandtimeline`、`house seven-round decision`(「第七轮裁定」)、`house 5 level`(「第 5 级」) | 每张盘 | `RATH_LABEL_ZH` 与 `rath_summary_line_zh` 的「第 N 级」;`RATH_LABEL_EN` 已存在 |
| `inperiod start`、`Dashasignpresent`、`aturninperiod` | 与 L3 / L4 同盘 | 同上,Rath 顺序与子运方向行 |
| `atpresent7planetsinRahu`、`planetsinbetween`、`partialKaal Sarp Dosha——houses 6/7 condition planets…`(「6/7 颗行星」被译成 `6/7 condition planets`,意思错了) | 约 2 成 | `yogas_doshas.calc_kaal_sarp_dosha` 的 full / partial 两句 |
| `read together withSaturnjudge from the actual position whetherinunderway` | 每张盘 | `yogas_doshas` 的 Sade Sati 框架摘要(「需结合Saturn实际位置判断是否在进行中」) |
| `careerplanetAmK`、`careerdirection`、`marriagequality` | 每张盘 | Jaimini 星位表:「事业星AmK」等名称与 `jaimini.py` 的职责描述(如 `'Amatyakaraka': '事业方向、主要谋士、权力代理'`) |
| `supportingstrongdegree` | 每张盘 | 标题 `### Vimsopaka / Pushkara / Vargottama …` 的中文后半 |
| `beneficfactor count` | 每张盘 | Panchanga 小表标签「吉性要素数量」(随 T3 一并改) |
| `comparativelystrong`、`occupiesplanetcount` | 每张盘 / 约 4 成 | Rath 子运比较行「较强」与 `STRENGTH_LEVEL_ZH[1]` |
| `lordandstrong`、`lordstrongand`、`weakafflicted`、`partialreducedweak`、`debilitatedsign`、`planetenterssign`、`planetsdistributed`、`beneficlocated` | 2~8 成 | 瑜伽表组合列:部分规则没有 `combination_en`(或只有中文 `combo_template`),英文分支回落到中文再被拆译 |
`pl9_full_data_export._sanitize_pl9_ai_density_markdown_en` 的 `phrase_terms` 里有 `"在": "in"`、`"与": "and"`、`"第": "house "`、`"宫": ""` 这类单字条目,且按长度从长到短一次替换;`pl9_language_terms.normalize_en_markdown_terms` → `apply_en_residual_han` 再补一轮。长短词表分在不同轮次,先跑的短词会把后面才有的长句拆碎。这就是 L1~L6 和粘连的共同机制。
## 根因
1. **英文版不是按语言渲染出来的,而是中文渲染后再在末尾逐词替换。** `render_full_data_markdown` 的英文分支仍然调用 `render_pl9_parity_markdown`,而它对 Rath 段落、Panchanga、Dosha 摘要、瑜伽强度列等处不分语言,打印中文。英文能否干净,取决于末尾三套词表(`_sanitize_pl9_ai_density_markdown_en` 内的 `replacements` / `reverse_terms` / `phrase_terms`,以及 `pl9_language_terms` 的 `_EN_RESIDUAL_HAN`)是否恰好覆盖、替换顺序是否恰好不拆句。只要某张盘触发一条没有覆盖的句子,就会留下汉字,整份英文被撤回。
2. **英文孪生字段早已存在却没接上。** `strength_en`、`direction_text_en`、`strength_level_text_en`、`rule_text_en`、`method_note_en`、`RATH_LABEL_EN`、`LEGACY_NOTE_EN` 都已经在资料包或常量里,渲染层没用。旧阅读版有 `pl9_reader_english._english_packet` 负责把 `strength_en` 换进去,完整数据版的 `_english_edition` 只做了 `deepcopy` + `report_language = "en"`,没有这一步。
3. **BUG-1272 的测试为什么没拦住**:
- 只有 3 张固定虚构盘(另 3 张公开盘只在当时手工跑过,没进测试)。泄漏都是按盘触发的:Rath 起运宫裁到第 5 级约 15%,Panchanga 综合为凶约 15%,计都在起运宫或子运方向走 Table 14 约 10%,月土合相 ≤10° 约 5%,完全 Kaal Sarp 约 5%,合起来约 40%。3 张盘恰好都没碰上。
- `tests/test_narayana_legacy_label.py::test_reference_edition_shows_both_and_english_terms_cover_the_rath_lines` 断言 Rath 各行能被 `pl9_reader_english_terms.PHRASES_EN / REGEX_EN` 译干净。**完整数据版英文根本不走这张表**(只有旧阅读版走),所以这条测试一直通过,生产路径却漏。
- `tests/test_report_english_edition.py` 不在快速门清单(`scripts/run_quality_gate.py` 的 `CORE_PYTEST_TARGETS`),staging 门禁不跑它,只有手动的 release 门禁全量会跑。
- BUG-1272 的修法本身是「把拆开后剩下的碎片补进英文术语」,即继续往末尾词表加条目;遇到新的句子或新的盘况就会再漏。
4. **BUG-1272 的防复发措施仍在**:`test_english_edition_has_no_chinese`(3 张盘汉字为 0)、`test_api_drops_english_when_chinese_would_leak`(人为写入汉字时接口撤回英文)、前端 `acceptEnglishEdition` 发现汉字时判 `han_leak`、`frontend/tests/report-english-edition.test.tsx` 的对应断言,都还在,且都通过。它们没有失效,只是覆盖面太窄。
## 决策记录(产品负责人,issue #89)
| # | 决定 |
| --- | --- |
| D1 | **从源头补齐英文**:英文分支直接用已有的英文孪生字段或新增的英文孪生字段渲染(例如 Punarphoo 强度走 `strength_en` / `STRENGTH_EN`)。**不准只在末尾做字符串替换**——本单不得为修这些泄漏往 `_sanitize_pl9_ai_density_markdown_en` 的三张表、`pl9_language_terms._EN_RESIDUAL_HAN`、`pl9_en_glossary`、`pl9_reader_english_terms` 新增条目 |
| D2 | 修好吃空格(`inperiod start`、`scoreandtimeline` 等),同样在源头改 |
| D3 | 回归测试:**200 张固定种子的随机虚构盘**,英文汉字数为 0,且不出现 `[source text retained`。时长允许放进快速门,否则放进全量测试并在 PROGRESS 写明(见 T6 的拆分) |
| D4 | 不需要迁移。已生成的报告要重新生成才有英文版(CHANGELOG 写明) |
| D5 | `docs/BUG_HISTORY.md` 标明**复发自 BUG-1272**,并写清当时 4 张盘的测试为什么没拦住(用上面「根因 3」) |
| D6 | 顺序:最先做,#91 / #92 在后 |
| — | 接口与前端「发现汉字就整份撤回英文」的判定**保留不变**(BUG-1272 既有设计);`_retain_remaining_cjk_lines` 的标记方式也保留。本单是让英文不再产生汉字,不是放宽判定 |
## 硬红线
1. **中文版逐字节不变**。用同一组固定种子(至少 30 张,含 L1~L6 各至少一张),在 `bd3ce0e7b` 与本分支各渲染一次中文完整数据版,SHA-256 全部相同。生产者里只能**新增** `*_en` 字段,不得改中文字段的值。
2. 不得放宽 `_english_edition` 与 `acceptEnglishEdition` 的汉字判定;不得删 `[source text retained` 标记逻辑去「凑」汉字为 0。
3. 不往末尾词表加条目(D1)。若某一处确实只能靠词表(例如上游 PL9 原样搬来的整段说明),先停下来在 PROGRESS 列出,交 Claude 判断。
4. Rath 版仍然只做展示:英文行函数放在 `narayana_legacy_label.py`,不得让 `narayana_rath` 被第二个文件导入(`tests/test_narayana_rath_book_tables.py::test_rath_profile_is_display_only`);不得碰冻结的打分文件(`references/rectification_sealed_holdout.v1.json` 列出的文件,ERR-110 / ERR-115)。
5. 测试必须屏蔽 PyJHora(照 `tests/test_special_lagnas_birth_instant.py` 的 `_block_jhora` 做法;多进程时在每个子进程初始化里装同样的屏蔽),种子、`today`、`target_year` 固定,同一提交重跑结果相同。
6. 改任何既有断言写「原值 / 新值 / 原因」三栏(AGENTS §7.3);先 grep 按源码文本断言的合同测试(§7.8),见「开工前置命令」。
7. 不改 `.gitea/workflows/**`,不改数据库,不升级依赖。
8. 测试与文档只用虚构盘,不写任何真实出生资料(AGENTS §8)。
## 任务分解
| # | 内容 | 验收标准 |
| --- | --- | --- |
| T1 | **Rath 段按语言渲染**(L1、L3、L4 与 Rath 段全部粘连):在 `narayana_legacy_label.py` 新增 `rath_summary_line_en` / `rath_report_lines_en` / `_ad_direction_lines_en`,用 `RATH_LABEL_EN`、`strength_level_text_en`、`direction_text_en`、`method_note_en`、`dual_lord_basis_en`、`rule_text_en`(含书内分歧那几条的 `rule_text_en`),「第 N 级」写成 `level N`,表头用英文。`render_pl9_parity_markdown` 的 Narayana 段英文分支改调英文函数,并改用 `LEGACY_NOTE_EN` 写算法行;去掉现在英文分支里拿 `dual_lord_basis` 做字符串替换的那段(被新函数取代);防重复判断(现为 `startswith('- 并列:')`)按语言各判各的 | 单测:用一个 Rath 视图分别覆盖第 1~7 级、Table 10 / 11 / 12、子运方向带书内分歧,英文行无汉字、无 `house N level`,含 `level 5: lord's degree in sign`、`Ketu in the start sign: reversed (Table 12)` 原文;中文行与改前逐字相同 |
| T2 | **瑜伽强度列**(L5):`render_pl9_parity_markdown` 瑜伽表英文分支用 `row['strength_en']`,缺了再用 `yoga_engine.STRENGTH_EN`;两者都没有时不得回落到中文——让 200 张盘测试暴露出来,并在生产者补 `strength_en`。同一分支里,组合列中仍回落到中文的规则(`lordandstrong`、`weakafflicted`、`debilitatedsign` 等)补 `combo_template_en` / `combination_en`,改 `references/yoga_rules.json` 或对应检测器,只加英文字段 | 单测:Punarphoo 行英文强度列不含汉字;200 张盘中瑜伽表组合列无上表所列粘连词 |
| T3 | **Panchanga 综合性质**(L2):`muhurta.calc_panchanga` 新增 `overall_quality_en`(`Auspicious (Subha)` / `Mixed` / `Inauspicious (Asubha)`,按同一分数档位);`render_pl9_parity_markdown` 的 Panchanga 小表英文分支用英文标签(`Overall quality`、`Auspicious elements`、`Total elements`)和 `overall_quality_en` | 单测:三个档位英文值正确;中文值不变。若新增字段让 `tests/golden/*keypaths*` 一类键路径快照变化,按三栏更新并说明 |
| T4 | **Dosha 摘要**(L6 与 Kaal Sarp / Sade Sati 粘连):`yogas_doshas` 中 Mangal、Kaal Sarp、Pitra、Sade Sati 的检测函数各新增 `summary_en`(`calc_kaal_sarp_dosha` 的 full / partial / none 三句;partial 句的「N/M 颗行星」写成 `N of M planets`);Dosha 结果表与 Sade Sati 小节英文分支用 `summary_en` 并把「当前阶段 / 是否 active / 摘要」等标签按语言写 | 单测:完全与部分 Kaal Sarp 的英文摘要原文断言;200 张盘中无 `atpresent`、`planetsinbetween`、`whetherinunderway`、`withSaturn` 粘连 |
| T5 | **其余每张盘都有的粘连**:Jaimini 星位表的名称与职责(`careerplanetAmK`、`careerdirection`、`marriagequality`,看 `jaimini.py` 的职责表与渲染处)、`### Vimsopaka / Pushkara / Vargottama` 标题后半。都按语言渲染或补英文孪生字段 | 200 张盘中这些词计数为 0 |
| T6 | **回归测试,分两个文件**:① `tests/test_report_english_sweep.py`:200 张固定种子虚构盘(年 1940–2015、月日时分随机、纬度 −45~60、经度 −120~150、时区按经度取半小时、`ayanamsa` 在 lahiri / raman / kp 间、`node_mode` 在 mean / true 间随机;`today` / `target_year` 固定),屏蔽 PyJHora,对每张盘调 `_english_edition(packet, True)`:必须返回 `markdown_en`、不得有 `english_unavailable`;`markdown_en` 与 `reader_dasha_applicability_en` 汉字数为 0、不含 `[source text retained`;不含粘连黑名单(本任务书「吃空格」表里出现的全部词)。允许用进程池(不超过 4 个进程)。失败信息列出盘号与残留片段,不打印出生资料以外的东西也够定位。另提供 `--dump-dir`(或环境变量)把 200 份英文 Markdown 写到临时目录,供 Claude 验收时跑前端判定。② `tests/test_report_english_sweep_smoke.py`:从同一生成器中挑 12~16 张,必须覆盖 L1~L6 每一类至少一张,断言同上;**把这个文件加进 `scripts/run_quality_gate.py` 的 `CORE_PYTEST_TARGETS`**(注释写明 issue #89 / BUG 号)。旧的 `test_reference_edition_shows_both_and_english_terms_cover_the_rath_lines` 改为断言完整数据版英文路径本身(三栏:原值「Rath 中文行经 `PHRASES_EN` 可译干净」/ 新值「完整数据版英文渲染的 Rath 段无汉字且用英文函数」/ 原因「完整数据版不走 `PHRASES_EN`,旧断言测的不是生产路径」) | 两个文件在本分支全绿;把 T1 的英文函数临时改回中文时,smoke 文件会红(PROGRESS 写这次变异的结果);PROGRESS 记录 Windows 上两个文件的耗时(串行与进程池各一次) |
| T7 | **文档**:`docs/BUG_HISTORY.md` 新增两条(见「BUG 编号」);`CHANGELOG.md` 写「英文版不再因个别漏译整份消失;已生成的报告需重新生成才有英文版」;`docs/tasks/PROGRESS-report-english-han-leak-20261021.md` 写清改了哪些源头、各粘连词前后计数、两份测试耗时、中文逐字节对比结果、真机复测步骤(登录 staging → 新建一份个人报告 → 揭幕后切到 English → 全文搜不到汉字、L1~L6 对应段落读得通 → 导出英文版) | 文档齐;BUG 记录写明复发自 BUG-1272 与根因 3 的四点;不含任何真实资料(推送前跑 `python -m pytest tests/test_repo_privacy_markers.py -q`) |
### 自跑与验收分工
| 检查 | Grok(Windows + Docker)自跑 | Claude(Linux)验收 |
| --- | --- | --- |
| 改到的 Python 测试文件(含 T6 两个新文件、`test_report_english_edition.py`、`test_narayana_legacy_label.py`、`test_narayana_rath_book_tables.py`、`test_year_lord_blocked_no_fallback.py`、`test_year_lord_basis_label.py`、`test_report_full_data_fix.py`、`test_report_chart_blank_columns.py`、`test_runtime_jhora_import_scan.py`,以及改到的 muhurta / yoga 相关测试) | 必跑(工作树 `.venv`) | 复验 |
| 中文逐字节对比(红线 1) | 必跑,结果写 PROGRESS | 复验(独立重算哈希) |
| `tsc --noEmit`、`npm run lint`、改到的前端测试单文件 | 只在动了 `frontend/` 时跑 | 复验 |
| Python 快速门 `scripts/run_quality_gate.py --profile quick` | 不跑 | **以 Claude 为准** |
| Python 全量 pytest,失败名单与 `bd3ce0e7b` 逐名比对 | 不跑 | **以 Claude 为准**(本单改的是引擎输出文字,必须全量) |
| 前端全量 `npm test` | 不跑 | 以 Claude 为准 |
| 200 份英文 Markdown 过前端判定:`acceptEnglishEdition`(含 `ordinaryOutputLeaks(…, "en")` 与 `assembleReportFactTables(…, "en")` 的汉字检查) | 不跑 | Claude 用 T6 的 `--dump-dir` 输出跑一次;有 `public_leak` / `fact_tables_han` 时另开修复单 |
| `npm run test:db` | 不涉及数据库,不跑 | — |
## 让步顺序
1. 不可让步:英文汉字为 0 与无 `[source text retained`(200 张)、中文逐字节不变、不往末尾词表加条目、Rath 只做展示。
2. 先交 T1~T4(issue 点名的泄漏与粘连)+ T6 + T7,作为第一个提交;T5 作为第二个提交。
3. 若 T5 中某一处源头改动牵涉过大(例如要改上游原样搬来的整段说明),可以把该处从 T6 粘连黑名单中拿出,在 PROGRESS 列出词、出现盘数和源头位置,留给 #91 一并处理;汉字为 0 不在可让步之列。
4. 若 200 张盘测试在 Windows 上即便用进程池也超过 10 分钟,在 PROGRESS 写明实测时间,Claude 决定是否请产品负责人改为只在验收时跑;不得自行减少盘数。
## 开工前置命令
```bash
git fetch origin --prune
git worktree add -b codex/report-english-han-leak-20261021 .worktrees/report-english-han-leak-20261021 origin/staging
cd .worktrees/report-english-han-leak-20261021
git status -sb
# 按源码文本断言、或直接 import 这些符号的测试(§7.8)
grep -rln "rath_report_lines_zh\|rath_summary_line_zh\|_ad_direction_lines_zh\|STRENGTH_LEVEL_\|DIRECTION_ZH\|RATH_LABEL_" tests frontend/tests
grep -rln "overall_quality\|calc_panchanga\|calc_kaal_sarp_dosha\|summary_en\|strength_en\|STRENGTH_EN" tests frontend/tests
grep -rln "source text retained\|han_leak\|PHRASES_EN\|REGEX_EN" tests frontend/tests
grep -n "CORE_PYTEST_TARGETS" -A5 scripts/run_quality_gate.py
grep -rln "CORE_PYTEST_TARGETS\|test_report_english" tests
# BUG 编号:取整个文件的最大号(文件里有整段重复,见下)
grep -o "BUG-1[0-9]\{3\}" docs/BUG_HISTORY.md | sort -u | tail -3
```
## BUG 编号
- 当前 `docs/BUG_HISTORY.md` 最大号 BUG-1403;#85 已预留 1404 起的若干号。**开工时取全文件最大号 + 1**,预计在 BUG-1410 前后。
- 建议两条:① 英文版漏译致整份丢弃(复发自 BUG-1272;写根因 3 的四点);② 英文版吃空格与「第 N 级」错译成 house(关联 BUG-1272、BUG-1218)。
- 注意:`docs/BUG_HISTORY.md` 自 `400af57e8`(2026-10-14 合并)起整份内容重复了一遍(同一编号出现两次)。本单**只在文件末尾追加**,不顺手去重(另行处理)。