arxiv-paper-zh 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arxiv-paper-zh",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Verify and translate arXiv papers into compilable Chinese editions with paired English and Chinese PDFs.",
5
5
  "author": {
6
6
  "name": "AndrewYq",
package/README.md CHANGED
@@ -34,12 +34,18 @@ npm 包地址:[arxiv-paper-zh](https://www.npmjs.com/package/arxiv-paper-zh)
34
34
  - 核对论文简称、标题、作者、摘要、URL 与 arXiv ID,避免翻错同名论文。
35
35
  - 按 `arxiv-paper/<paper-name>/` 统一保存原始源码、中文源码以及中英文 PDF。
36
36
  - 将可见英文切成紧凑任务包,公式、引用、URL、代码和注释使用可逆占位符,不再让模型重复读取整份 TeX。
37
- - 按词量自适应分配 worker;长论文并行、短论文自动减少 worker,单文件论文也能分片。
37
+ - 默认每个任务包最多包含 2,000 个估算可见英文词;任务包与 worker 数量分开控制,长论文分批并行、短论文自动减少 worker。
38
+ - 原文任务包只读,worker 只输出包含片段 ID 和译文的结果文件,避免重复输出原文。
38
39
  - 保留公式、数值、引用键、标签、人名、模型名、数据集名和常用缩写。
39
40
  - 翻译正文、章节标题、脚注、表头、表注和 caption。
40
41
  - 参考文献标题与条目保持原文,并在翻译分片和漏译审计中自动跳过。
41
42
  - 批量检查并安装缺失的 TeX 宏包,复用共享 TinyTeX/TeX Live 缓存。
42
43
  - 自动执行漏译审计、BibTeX/Biber 构建和引用收敛检查。
44
+ - 工具默认返回摘要和下一批任务;完整检查列表按需展开,构建与安装日志留在本地。
45
+ - 每包完成即可校验并保存断点;自动为失败片段生成修复包,保留已通过的译文。
46
+ - 支持恢复翻译进度和中断的源码合并,重复合并不会再次替换已完成的内容。
47
+ - 编译按辅助文件与引用的实际收敛情况结束;输入、依赖及成品哈希匹配时跳过重复构建。
48
+ - 页面图片按 PDF、分辨率和工具版本缓存,缺失或损坏时只补渲染受影响的页面。
43
49
  - 使用 XeLaTeX 生成中文 PDF,并要求逐页视觉核验。
44
50
 
45
51
  ## 项目结构
@@ -62,21 +68,32 @@ arxiv-paper-zh/
62
68
  │ ├── agents/
63
69
  │ │ └── openai.yaml
64
70
  │ ├── scripts/
71
+ │ │ ├── artifact_cache.py
65
72
  │ │ ├── audit_tex_translation.py
66
73
  │ │ ├── build_and_check.py
74
+ │ │ ├── finalize_output.py
67
75
  │ │ ├── inventory_and_shard.py
68
76
  │ │ ├── prepare_output_layout.py
69
77
  │ │ ├── prepare_tex_runtime.py
78
+ │ │ ├── render_pdf.py
70
79
  │ │ ├── translation_tasks.py
80
+ │ │ ├── translation_progress.py
71
81
  │ │ └── tex_translation_utils.py
72
82
  │ └── references/
73
- │ └── paper-translation-packages.txt
83
+ │ ├── build-and-render.md
84
+ │ ├── paper-translation-packages.txt
85
+ │ └── translation-recovery.md
74
86
  ├── install.sh
75
87
  ├── package.json
76
88
  ├── tests/
77
89
  │ ├── installer.test.mjs
78
90
  │ ├── test_bibliography_exclusion.py
79
- │ └── test_translation_tasks.py
91
+ │ ├── test_build_cache.py
92
+ │ ├── test_cli_output.py
93
+ │ ├── test_output_layout.py
94
+ │ ├── test_render_pdf.py
95
+ │ ├── test_translation_tasks.py
96
+ │ └── test_translation_progress.py
80
97
  ├── .gitignore
81
98
  └── README.md
82
99
  ```
@@ -90,6 +107,7 @@ arxiv-paper-zh/
90
107
  - 可执行 `curl`、`tar` 等常用命令。
91
108
  - Codex、Claude Code,或其他支持 [Agent Skills 开放格式](https://agentskills.io/) 的客户端。
92
109
  - 编译时需要 XeLaTeX。可以使用系统 TeX Live,也可以让 Skill 准备并复用 TinyTeX。
110
+ - 页面渲染需要 Poppler 的 `pdfinfo` 和 `pdftoppm`;中文字体还需用支持 CJK 的另一 PDF 引擎抽查。
93
111
  - 第一次下载论文源码或安装缺失宏包时需要网络连接。
94
112
 
95
113
  TeX 运行时不包含在本仓库中,避免让仓库体积增加数百 MB。不同论文缺少的宏包会被批量安装到共享运行时。
@@ -241,6 +259,8 @@ arxiv-paper/EST/
241
259
  - `paper-en/<paper-name>-en.pdf`:英文原版编译结果。
242
260
  - `paper-zh/<paper-name>-zh.pdf`:中文译版编译结果。
243
261
 
262
+ 原始下载物只允许保存为 `latex/source.tar`,不另建根级 `source/`、`source.tar` 或 `latex/source/` 中转路径。Agent 自建的渲染图、截图和诊断文件统一写入论文根目录的 `tmp/`;完整交付校验通过后自动删除该目录。
263
+
244
264
  `paper-name` 使用用户熟悉的简短名称并保留大小写,例如 `EST`、`Onetrans`,且只能包含英文字母、数字、点、下划线和连字符。
245
265
 
246
266
  整个参考文献部分保持原文,包括标题和全部条目;`.bib`、`.bbl`、内嵌 bibliography 环境和单独的参考文献 TeX 文件均不参与翻译。正文中的文献综述仍照常翻译。原图内部文字不修改,只翻译必要 caption。公式内的英文说明按“公式不变”原则保留。
@@ -251,23 +271,65 @@ arxiv-paper/EST/
251
271
  # 创建并输出固定的论文产物路径
252
272
  python3 skills/arxiv-paper-zh/scripts/prepare_output_layout.py EST --root arxiv-paper
253
273
 
254
- # 生成紧凑翻译任务包(3 是 worker 上限,小论文会自动减少)
255
- python3 skills/arxiv-paper-zh/scripts/translation_tasks.py prepare arxiv-paper/EST/latex/paper-zh --entry main.tex --workers 3 --json
274
+ # 生成只读任务包(每包最多 2000 个估算英文词,最多 3 个并发 worker)
275
+ python3 skills/arxiv-paper-zh/scripts/translation_tasks.py prepare arxiv-paper/EST/latex/paper-zh --entry main.tex --workers 3 --packet-words 2000 --json
256
276
 
257
- # worker 填写各自的 worker-*.task 后,检查进度并安全合并
277
+ # worker 读取 packet-*.task,将译文写入对应 packet-*.result.jsonl
278
+ # 查看填写进度和下一批任务;主 Agent 避免重复分配正在处理的任务
258
279
  python3 skills/arxiv-paper-zh/scripts/translation_tasks.py status arxiv-paper/EST/latex/paper-zh
280
+
281
+ # 每包完成立即校验;失败时生成仅含错误片段的修复包
282
+ python3 skills/arxiv-paper-zh/scripts/translation_tasks.py check arxiv-paper/EST/latex/paper-zh --packet packet-0001.task --json
283
+ python3 skills/arxiv-paper-zh/scripts/translation_tasks.py repair arxiv-paper/EST/latex/paper-zh --packet packet-0001.task --json
284
+
285
+ # worker 写好修复结果后,校验并接收,保留其余正确译文
286
+ python3 skills/arxiv-paper-zh/scripts/translation_tasks.py repair arxiv-paper/EST/latex/paper-zh --packet packet-0001.task --apply --json
287
+
288
+ # 中断后恢复进度;输入一致时也可完成中断的源码合并
289
+ python3 skills/arxiv-paper-zh/scripts/translation_tasks.py resume arxiv-paper/EST/latex/paper-zh --json
290
+
291
+ # 所有任务填写完后统一校验、合并
259
292
  python3 skills/arxiv-paper-zh/scripts/translation_tasks.py apply arxiv-paper/EST/latex/paper-zh
260
293
 
261
294
  # 检查预装包和论文专用宏包
262
295
  python3 skills/arxiv-paper-zh/scripts/prepare_tex_runtime.py arxiv-paper/EST/latex/paper-zh --preset --kpsewhich /path/to/kpsewhich
263
296
 
264
- # 扫描可能漏译的英文自然语言
297
+ # 扫描全部内容,默认展示前 10 条疑似漏译;有更多命中时加 --details 复核
265
298
  python3 skills/arxiv-paper-zh/scripts/audit_tex_translation.py arxiv-paper/EST/latex/paper-zh
266
299
 
267
- # 自动运行 XeLaTeX 与 BibTeX/Biber,检查构建日志
300
+ # 自动收敛构建,重复执行可命中缓存;完整输出保存在 EST/tmp/paper-zh-build.log
268
301
  python3 skills/arxiv-paper-zh/scripts/build_and_check.py arxiv-paper/EST/latex/paper-zh/main.tex --tex-bin /path/to/tex/bin
302
+
303
+ # 全部页面默认渲染为 90 DPI;检查本次返回 render_dir 中的全部页面
304
+ python3 skills/arxiv-paper-zh/scripts/render_pdf.py arxiv-paper/EST/latex/paper-zh/main.pdf --output arxiv-paper/EST/tmp/render-zh --json
305
+
306
+ # 只对可疑页增加分辨率
307
+ python3 skills/arxiv-paper-zh/scripts/render_pdf.py arxiv-paper/EST/latex/paper-zh/main.pdf --output arxiv-paper/EST/tmp/render-zh --dpi 180 --pages 2,5-6 --json
308
+
309
+ # 校验完整交付物,并在成功后删除论文根目录的 tmp/
310
+ python3 skills/arxiv-paper-zh/scripts/finalize_output.py arxiv-paper/EST
311
+ ```
312
+
313
+ 任务包和结果文件都位于中文源码的 `.translation-tasks/` 中。每个结果文件采用 JSONL,每行只有片段 ID 和译文,例如:
314
+
315
+ ```json
316
+ {"id":"s123456789abc","translation":"\\section{引言}\n本文提出了一种方法。\n"}
269
317
  ```
270
318
 
319
+ 使用正确的 JSON 转义保存 LaTeX 反斜杠、引号和换行,不回显 `SOURCE`。每包完成后用 `check` 校验任务包哈希、结果 ID、占位符、LaTeX 结构和源码快照;错误结果不会写入源码。新建任务使用 manifest 版本 3,额外保存完整 TeX 文件哈希;旧版 1/2 的任务仍可继续,不必重新翻译。
320
+
321
+ `--chunk-words` 默认 900,控制片段目标大小;`--packet-words` 默认 2000,限制每包的估算可见英文词数。单个片段超过包上限时会在生成任务前报错,需要检查并调整该处源码换行,或显式增加包上限。报告的 `packet_bytes` 和输入字节压缩率包含包头及标记开销;小任务的压缩率可以为负。这些数值都不等于模型的实际 token 用量。
322
+
323
+ `prepare` 默认只列首批任务,`status` 默认列最多一个 worker 批次的待处理任务;加 `--json` 获取精简摘要,加 `--details` 查看完整列表。`completed/validated` 表示通过格式和结构校验的片段数,语义质量与漏译仍需复核。`check` 保存校验断点,输入或校验规则变化时自动失效;`repair` 仅输出失败片段的原文、当前译文及错误原因,正确译文保留。
324
+
325
+ `resume`(或 `prepare --resume`)复用现有任务并返回下一步操作;源码合并先保存暂存结果和日志,中断后验证哈希再继续,已写入的文件不重复替换。合并后人工修正排版或译文时,恢复命令会提示继续审查与构建。恢复细节见 [translation-recovery.md](skills/arxiv-paper-zh/references/translation-recovery.md)。
326
+
327
+ `build_and_check.py` 默认最多编译 6 轮,辅助文件稳定且无未定义引用、重跑提示或缺字时结束。英文可用 `--engine pdflatex|xelatex|lualatex` 选择兼容引擎;中文默认 XeLaTeX。成功构建记录源码、实际依赖和成品哈希,未变化时编译轮数为 0;仅正文变化、文献控制信息和输入保持一致时可跳过 BibTeX/Biber。摘要包含缓存状态、实际轮数和耗时。
328
+
329
+ `render_pdf.py` 默认渲染全部页面;PDF 内容、DPI、工具或脚本变化时使用独立目录。重复运行校验各页图片哈希,只补缺失或损坏页,摘要返回 `rendered/reused` 数量及耗时。缓存不替代全部页面检查和中文字体抽查。构建与渲染缓存位于论文 `tmp/`,最终校验成功后清理;系统字体、CMap 或运行时更新后用 `--force`。完整失效条件与用法见 [build-and-render.md](skills/arxiv-paper-zh/references/build-and-render.md)。这些计数不是实际模型 token 用量或端到端加速比例。
330
+
331
+ 构建和依赖脚本支持 `--verbose`;原始命令输出保存在日志中,不必为查看已有诊断重复构建或安装。标准论文布局的日志放在论文 `tmp/`;独立构建默认保存到入口旁的 `<stem>.build.log`,可用 `--log-file` 指定位置;无论文路径的宏包预装日志放在系统临时目录。
332
+
271
333
  ## 兼容性说明
272
334
 
273
335
  本项目遵循以 `SKILL.md` 为入口的 Agent Skills 目录格式。核心翻译与构建步骤可以跨客户端复用,但以下能力取决于具体客户端:
@@ -277,7 +339,7 @@ python3 skills/arxiv-paper-zh/scripts/build_and_check.py arxiv-paper/EST/latex/p
277
339
  - 是否允许网络下载与安装宏包。
278
340
  - 是否提供可写文件系统和本地 XeLaTeX。
279
341
 
280
- 客户端不支持 subagent 时,Agent 应顺序填写任务包。任务包仍会剔除不需要发送给模型的内容,因此同样能降低上下文开销;只是不具备并行加速。
342
+ 客户端不支持 subagent 时,Agent 应顺序读取任务包并写入对应结果文件。任务包仍会剔除不需要发送给模型的内容,因此同样能降低上下文开销;只是不具备并行加速。
281
343
 
282
344
  ## 开发与发布检查
283
345
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arxiv-paper-zh",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Install the arxiv-paper-zh Agent Skill for Codex, Claude Code, and compatible agents.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,12 +27,16 @@ arxiv-paper/<paper-name>/
27
27
  python3 scripts/prepare_output_layout.py <paper-name> --root arxiv-paper
28
28
  ```
29
29
 
30
- 以脚本输出的绝对路径为准,不另建平行交付目录。`.translation-tasks/`、渲染图和日志属于论文目录内的临时文件,不得混入最终 PDF 目录。
30
+ 以脚本输出的绝对路径为准,不另建平行交付目录。原始下载物的唯一允许路径是 `latex/source.tar`;不得另建论文根目录下的 `source/`、`source.tar` 或 `latex/source/` 作为下载、解压中转。若 arXiv 源包自身包含 `source/` 子目录,可原样保留在 `latex/paper-en/` 内。
31
+
32
+ Agent 自建的下载中转、页面渲染、截图和诊断临时文件全部放在论文根目录的 `tmp/`。失败时保留它用于诊断;只有全部交付物通过最终校验后才删除。`.translation-tasks/` 位于中文源码目录,不得混入最终 PDF 目录。
31
33
 
32
34
  ## 工作流
33
35
 
36
+ 继续同一论文时,若中文源码已有 `.translation-tasks/manifest.json`,先运行 `translation_tasks.py resume <中文源码目录> --json`,按返回的 `next_action` 继续;不要重新下载、覆盖中文副本或强制生成任务。恢复与修复命令见 [references/translation-recovery.md](references/translation-recovery.md),仅在续跑、修复或合并中断时读取。
37
+
34
38
  1. 从 arXiv 摘要页或 API 核验规范化 ID、完整标题、作者、摘要、版本和日期。简称不是唯一标识;候选不唯一时让用户确认。向用户明确标题、作者和 arXiv ID。
35
- 2. 创建输出目录,将 `https://export.arxiv.org/e-print/<ID>` 保存为 `latex/source.tar` 并解压到 `latex/paper-en/`。用主 TeX 的标题、作者或 README 二次核验身份;不一致时停止。
39
+ 2. 创建输出目录,将 `https://export.arxiv.org/e-print/<ID>` 直接保存为脚本返回的 `latex/source.tar`;下载失败时删除不完整文件。直接解压到 `latex/paper-en/`,不得创建额外的 `source/` 中转目录。用主 TeX 的标题、作者或 README 二次核验身份;不一致时停止。
36
40
  3. 将英文源码内容完整复制到 `latex/paper-zh/`,不得多套一层目录;此后只修改中文副本。分别定位中英文入口文件。
37
41
  4. 先完成中文入口的 XeLaTeX/ctex 改造和模板可见字符串本地化,再生成翻译任务。通常加入 `\usepackage[UTF8,fontset=fandol]{ctex}`,移除仅适用于 pdfLaTeX 的 `inputenc` 和 T1 `fontenc`。生成任务后、合并任务前不要再编辑中文 TeX 源码,合并器会检查快照。
38
42
  5. 生成紧凑任务包。脚本包含入口文件,因此单文件论文也能按片段并行;它自动省略参考文献,并用可逆占位符保护公式、引用、URL、代码和注释:
@@ -40,19 +44,20 @@ python3 scripts/prepare_output_layout.py <paper-name> --root arxiv-paper
40
44
  ```bash
41
45
  python3 scripts/translation_tasks.py prepare \
42
46
  arxiv-paper/<paper-name>/latex/paper-zh \
43
- --entry main.tex --workers 3 --json
47
+ --entry main.tex --workers 3 --packet-words 2000 --json
44
48
  ```
45
49
 
46
- 默认 `--workers 3` 是上限;小论文会自动减少 worker,避免启动开销。只有需要改变速度/上下文折中才调整 `--chunk-words` 或 `--min-words-per-worker`。
47
- 6. 每个 `worker-*.task` 只交给一个 worker。支持隔离上下文时使用空/最小历史,而不是复制完整会话;任务提示只需:
50
+ `--packet-words 2000` 限制每包的估算可见英文词数,`--chunk-words 900` 是包内片段的目标词数,均不是模型 token 数。脚本将相邻片段依次装包,任务包数可超过 worker 数;`--workers 3` 仅为同时运行的 worker 上限,小论文自动减少。单个片段超出包上限时,脚本在生成任务包前报出位置;先检查并调整该处源码换行,或明确调大包上限后重新 prepare。
51
+ 6. 每个只读 `packet-*.task` 同时只交给一个 worker,译文写入对应 `packet-*.result.jsonl`。支持隔离上下文时使用空/最小历史;任务提示只需:
48
52
 
49
53
  ```text
50
- 翻译 <packet> 的全部 SOURCE 区块,把译文填入对应 TRANSLATION 区块。
51
- 严格遵守文件头规则,只编辑该任务包,不读取或修改论文源码。
54
+ 读取 <packet>,将全部 SOURCE 区块译为简体中文,结果写入 <result>。
55
+ 严格遵守文件头规则;只写结果文件,不修改任务包或读取、修改论文源码。
56
+ 结果用 JSONL,每行仅含 id 和 translation;完成后只返回结果路径和完成片段数。
52
57
  ```
53
58
 
54
- Worker 不需要读取本 Skill、整篇论文或其他任务包。不同 worker 并行编辑各自任务包;不支持 subagent 时顺序处理。主 agent 同时编译英文版、检查中文依赖,但不修改已快照的中文 TeX。
55
- 7. Worker 完成后只运行一次合并。合并器先整体校验任务完整性、占位符、LaTeX 结构和源文件哈希,任何错误都会在写文件前停止:
59
+ Worker 不需要读取本 Skill、完整 manifest 或其他任务包,不回显原文与译文。主 agent 按 prepare/status 返回的下一批任务调度,记录正在处理的任务,避免重复分配。每包完成后运行 `translation_tasks.py check <中文源码目录> --packet <packet> --json`,立即校验格式、ID、占位符、LaTeX 结构和源码快照,保存校验断点;失败时用 `repair` 生成仅含错误片段的修复包,修复后用 `repair --apply` 接收结果。空闲 worker 继续领取下一包,上下文过长时换用新 worker。不支持 subagent 时顺序处理。主 agent 同时编译英文版、检查中文依赖,但不修改已快照的中文 TeX。
60
+ 7. 全部任务校验通过后统一合并。合并前再次确认输入哈希,先暂存完整写入结果并记录合并日志,再替换源码;中断后由 `resume` 完成剩余写入,重复 `apply` 不会再次替换已合并的源码:
56
61
 
57
62
  ```bash
58
63
  python3 scripts/translation_tasks.py status arxiv-paper/<paper-name>/latex/paper-zh
@@ -60,16 +65,17 @@ python3 scripts/prepare_output_layout.py <paper-name> --root arxiv-paper
60
65
  python3 scripts/audit_tex_translation.py arxiv-paper/<paper-name>/latex/paper-zh
61
66
  ```
62
67
 
63
- `status` 未完成时只返工列出的任务;`apply` 报错时只检查对应 segment,不重新读取或重译全部论文。主 agent 必须人工复核全局审计命中。
64
- 8. 使用原论文声明或兼容引擎编译英文源码。中文使用自动构建脚本识别 BibTeX/Biber 并完成 XeLaTeX 收敛:
68
+ `status` 的 `completed/validated` 计数表示通过结构校验的片段;新源码、结果或校验规则会使旧校验缓存失效。默认只输出摘要和下一批任务,完整列表用 `--details`。这些检查不判断译文语义质量,主 agent 仍须复核漏译审计;命中超过 10 条时用 `audit_tex_translation.py ... --details` 查看全部。版本 1/2 的任务仍可继续,不为格式升级重做译文。
69
+ 8. 使用构建脚本分别编译中英文入口。英文用 `--engine pdflatex`、`xelatex` 或 `lualatex` 选择论文兼容引擎;中文默认 XeLaTeX。脚本根据辅助文件识别 BibTeX/Biber,引用与辅助文件稳定后结束,最多 6 轮,未收敛则失败:
65
70
 
66
71
  ```bash
67
72
  python3 scripts/build_and_check.py \
68
73
  arxiv-paper/<paper-name>/latex/paper-zh/main.tex --tex-bin /path/to/tex/bin
69
74
  ```
70
75
 
71
- 9. 分别低分辨率渲染中英文 PDF 的全部页面并检查裁切、重叠、溢出、图片和页数;只对可疑页面高分辨率渲染。另用支持 CJK 的系统 PDF 引擎抽查中文字体。Poppler 缺少 CMap 时不得把空白中文误判为正常。
72
- 10. 将英文成品复制为 `paper-en/<paper-name>-en.pdf`,中文成品复制为 `paper-zh/<paper-name>-zh.pdf`。回复列出论文身份、两套源码目录和两个 PDF 的绝对路径。
76
+ 同一源码、已记录依赖与成品哈希匹配时复用成功构建;正文修改但文献输入不变时可跳过文献处理器。默认返回轮数、缓存命中与耗时,完整日志保存在论文 `tmp/`;诊断时按需读取或用 `--verbose`。缓存范围、失效条件及参数见 [references/build-and-render.md](references/build-and-render.md),进入编译或页面检查时读取。
77
+ 9. 用 `render_pdf.py <PDF> --output <论文目录>/tmp/render-zh --json` 渲染中文版,英文改用 `tmp/render-en`。默认以 90 DPI 渲染全部页面;使用返回的 `render_dir` 检查全部页面的裁切、重叠、溢出、图片和页数,不混用旧目录。PDF、DPI 或渲染器变化时使用独立缓存,图片缺失或哈希不符时只补对应页面。可疑页用 `--dpi 180 --pages 2,5-6` 单独渲染。图片缓存不代表已完成视觉检查;另用支持 CJK 的系统 PDF 引擎抽查中文字体。Poppler 缺少 CMap 时不得把空白中文误判为正常;修复字体/CMap 后加 `--force` 重渲染并复查。
78
+ 10. 将英文成品复制为 `paper-en/<paper-name>-en.pdf`,中文成品复制为 `paper-zh/<paper-name>-zh.pdf`。最后运行 `python3 scripts/finalize_output.py arxiv-paper/<paper-name>`;它确认 `latex/source.tar`、两套源码和两个 PDF 均非空,拒绝额外的源码中转路径,并仅在校验成功后删除 `tmp/`。成功后再回复论文身份、两套源码目录和两个 PDF 的绝对路径。
73
79
 
74
80
  ## 翻译边界
75
81
 
@@ -89,13 +95,15 @@ python3 scripts/prepare_tex_runtime.py --preset \
89
95
  --tlmgr <shared-tex-root>/bin/<platform>/tlmgr --install
90
96
  ```
91
97
 
92
- 后续先离线检查;论文特有依赖缺失时再用同一脚本和一次 `tlmgr` 调用批量补装。只有没有可用运行时时才在可写缓存目录建立便携 TinyTeX,不使用 `sudo`。
98
+ 后续先离线检查;论文特有依赖缺失时再用同一脚本和一次 `tlmgr` 调用批量补装。默认只输出检查数量、缺包名称和安装结果,安装日志保留在本地;传入标准论文源码路径时放在论文 `tmp/`,仅预装共享环境时放在系统临时目录。`--verbose` 可显示完整包清单和安装输出。只有没有可用运行时时才在可写缓存目录建立便携 TinyTeX,不使用 `sudo`。
93
99
 
94
100
  ## 完成标准
95
101
 
96
102
  - arXiv 元数据与源码身份两次核验通过,`latex/paper-en/` 未修改。
103
+ - 原始下载包仅存在于非空的 `latex/source.tar`,没有 Agent 创建的 `source/` 或其他源码中转副本。
97
104
  - 中文任务全部合并,全局漏译审计已人工复核;所有可见自然语言均已翻译或明确允许保留。
98
105
  - 中英文 PDF 均构建成功,参考文献和交叉引用收敛,中文版无缺字。
99
106
  - 中英文 PDF 全部页面均已检查,中文字体经 CJK 能力正常的渲染器确认。
107
+ - `finalize_output.py` 校验成功,论文根目录的 `tmp/` 已移除。
100
108
 
101
109
  方案参考科学空间文章[《让 AI 翻译一篇完整的论文》](https://spaces.ac.cn/archives/11578),并结合紧凑任务包、并行 agent、依赖缓存和自动审计实现。
@@ -0,0 +1,41 @@
1
+ # 编译收敛与页面缓存
2
+
3
+ 以下命令中的 `PAPER` 表示论文根目录,例如 `arxiv-paper/EST`。缓存放在 `PAPER/tmp/`,供同一任务重试或恢复时复用;`finalize_output.py` 成功后会一并清理。缓存可随时重建,不作为交付物。
4
+
5
+ ## 构建
6
+
7
+ ```bash
8
+ python3 scripts/build_and_check.py PAPER/latex/paper-en/main.tex --engine pdflatex --tex-bin /path/to/tex/bin --json
9
+ python3 scripts/build_and_check.py PAPER/latex/paper-zh/main.tex --tex-bin /path/to/tex/bin --json
10
+ ```
11
+
12
+ 英文引擎按论文模板选择,中文默认 XeLaTeX。非标准目录默认以入口所在目录为源码根;入口在子目录而图表等位于上层时,用 `--source-root` 指定完整源码目录。构建工作目录仍为入口所在目录。
13
+
14
+ 脚本开启 `-recorder`,比较 `.aux`、目录和文献等辅助文件,并检查日志中的未定义引用、重跑提示、TeX 错误与缺字。辅助文件稳定且日志无上述问题才成功。默认最多 6 轮;确需更多时显式传入 `--max-runs`,不要把达到上限当成构建成功。
15
+
16
+ BibTeX 从入口与递归引用的 `.aux` 读取文献命令,Biber 从 `.bcf` 读取控制信息。控制信息、解析到的 `.bib/.bst`、文献工具和 `.bbl` 均不变时可以省略文献处理;缺少 `.bbl` 或文献输入改变时重新生成。
17
+
18
+ 成功记录保存在 `tmp/.build-cache/`。下列内容均匹配才会跳过整个构建:源码根内的可见输入文件、编译器与脚本内容、相关 TeX 环境变量、`.fls` 记录的实际外部依赖,以及 PDF、TeX 日志和辅助文件哈希。没有依赖记录、或文献输入无法定位时,允许完成编译但禁用该次缓存,并返回原因。失败构建不会生成成功记录。
19
+
20
+ 动态库、系统字体配置以及新安装文件改变搜索路径优先级,并不一定体现为已记录文件内容变化。更新 TeX 环境或字体后用 `--force` 强制构建(包括文献处理)。这也适用于含时间、随机数或外部命令的模板。首次构建或强制构建仍执行全部收敛检查。
21
+
22
+ 默认摘要包含 `cached`、`runs`、`bibliography_runs`、`elapsed_seconds` 和 `build_log`。`cached=true` 时两项轮数为 0,保留原日志。`--json` 的 stdout 为结构化结果;`--verbose` 同时显示完整命令输出,组合使用时诊断写到 stderr。标准布局日志为 `tmp/paper-en-build.log` 或 `tmp/paper-zh-build.log`;独立入口默认 `<stem>.build.log`,可通过 `--log-file` 指定,缓存跟随日志目录。
23
+
24
+ ## 页面渲染
25
+
26
+ 需要 Poppler 的 `pdfinfo` 与 `pdftoppm`;不在 PATH 中时使用 `--pdfinfo /path/to/pdfinfo --pdftoppm /path/to/pdftoppm`。
27
+
28
+ ```bash
29
+ # 全页低分辨率检查,中英文分别执行
30
+ python3 scripts/render_pdf.py PAPER/latex/paper-en/main.pdf --output PAPER/tmp/render-en --json
31
+ python3 scripts/render_pdf.py PAPER/latex/paper-zh/main.pdf --output PAPER/tmp/render-zh --json
32
+
33
+ # 仅对疑似问题页增加分辨率
34
+ python3 scripts/render_pdf.py PAPER/latex/paper-zh/main.pdf --output PAPER/tmp/render-zh --dpi 180 --pages 2,5-6 --json
35
+ ```
36
+
37
+ 每个 PDF 内容、DPI、工具与脚本版本组合使用独立的 `render_dir`,页面名为 `page-0001.png` 等。以本次返回目录为准;不要把缓存根目录下不同版本的 PNG 混在一起检查。默认列出最多 3 个示例路径,`requested_count` 是实际请求页数,`--details` 可列出全部路径。
38
+
39
+ 首次全页渲染只启动一次 `pdftoppm`。后续逐页检查图片哈希,完整页面直接复用,缺失或损坏页按连续范围批量补渲染。每个范围成功后保存断点;后续范围失败时,已完成范围仍能复用。PDF 在渲染期间变化会报错,失败日志和暂存图片留在 `render_dir` 便于诊断。
40
+
41
+ 摘要提供 `rendered`、`reused` 和 `elapsed_seconds`。它们表示本次实际生成、复用的图片数量及脚本耗时,不表示视觉检查已通过,也不是模型 token 节省比例。仍须低分辨率检查全部页面、复查疑似页,并使用 CJK 正常的另一 PDF 引擎抽查中文字体。字体、CMap 或 Poppler 运行库修复后用 `--force`;仅给出 `--pages` 时强制更新这些页,要更新整份 PDF 则不传页码范围。
@@ -0,0 +1,59 @@
1
+ # 翻译校验、局部修复与续跑
2
+
3
+ 以下命令的 `ROOT` 均指中文源码目录,例如 `arxiv-paper/EST/latex/paper-zh`。`PACKET` 是清单返回的任务包名称或绝对路径。主 agent 运行校验与恢复命令;worker 只读分配的任务包并写入对应结果文件。
4
+
5
+ ## 每包完成后
6
+
7
+ ```bash
8
+ python3 scripts/translation_tasks.py check ROOT --packet PACKET --json
9
+ ```
10
+
11
+ 返回 `packet_validated` 后继续调度。这个检查只针对指定包,不要求其他 worker 已完成;同一输入、结果和校验规则的检查会复用 `.checks/` 中的断点。状态里的通过计数仅代表格式和结构有效,不等于语义、漏译或视觉审查已完成。
12
+
13
+ ## 只修复出错片段
14
+
15
+ ```bash
16
+ python3 scripts/translation_tasks.py repair ROOT --packet PACKET --json
17
+ ```
18
+
19
+ 将返回的只读 `path` 和 `result_path` 交给一个 worker;路径均相对 `.translation-tasks/`。修复包只含失败或缺失片段的 `SOURCE`、`CURRENT` 和 `ERRORS`。过长的异常结果可能截断,完整原文始终保留。正确片段保存在原结果文件,不让 worker 重读或重译。摘要里的 ID 默认最多显示 5 个,`segment_count` 表示实际数量;worker 处理修复包中的全部片段,需要完整机器列表时加 `--details`。
20
+
21
+ Worker 按包头规则输出 JSONL,只包含本次要求的 ID 和修正译文。主 agent 接收:
22
+
23
+ ```bash
24
+ python3 scripts/translation_tasks.py repair ROOT --packet PACKET --apply --json
25
+ python3 scripts/translation_tasks.py check ROOT --packet PACKET --json
26
+ ```
27
+
28
+ 接收前再次确认原结果、源码和修复任务包未变化,并检查修正结果。只有整份修复通过才替换原结果,保留正确片段的译文。重复生成同一个修复不会清空已写入的修复结果。若所有已知 ID 都正确、仅存在无归属的多余行,`repair` 会备份原结果并直接规范化,无需模型翻译。
29
+
30
+ 源码或只读包被修改属于输入冲突,不生成修复包。先查明变化原因;不要通过 `--force` 清除断点来掩盖冲突。修复连续两次仍因同一原因失败时,主 agent 查看对应片段并处理根因,避免无限分派相同任务。
31
+
32
+ ## 中断后继续
33
+
34
+ ```bash
35
+ python3 scripts/translation_tasks.py resume ROOT --json
36
+ # 已有任务时的等价入口;复用原有分包参数
37
+ python3 scripts/translation_tasks.py prepare ROOT --resume --json
38
+ ```
39
+
40
+ 按返回值执行:
41
+
42
+ | 状态或动作 | 后续操作 |
43
+ | --- | --- |
44
+ | `process_packets` | 顶层调度动作:查看 `next_packets[]` 中每一项的 `action`,分别执行下列翻译或修复操作 |
45
+ | `translate` | 分配尚无结果的包;排除当前仍在运行的 worker 所持任务 |
46
+ | `repair` | 生成或继续修复包;有效片段不重译 |
47
+ | `repair-apply` | 已有修复结果,运行 `repair --apply` 校验并接收;文件存在不代表其内容已经完整有效 |
48
+ | `ready` / `apply` | 所有包通过结构校验,运行 `apply` |
49
+ | `applying` / `resume_merge` | 合并中断;`resume` 校验暂存文件与现有源码后完成剩余写入 |
50
+ | `applied` / `audit_and_build` | 已合并,继续漏译审查、编译及页面检查 |
51
+ | `blocked` / `inspect_inputs` | 检查报告的输入冲突;不覆盖不匹配的源码 |
52
+
53
+ `.merge/journal.json` 在首次改写源码之前落盘。每个目标文件须与写入前或写入后的哈希匹配,其他输入及结果也须保持一致;任何冲突都在继续写入前停止。已写入的文件不会再次替换。合并完成后允许继续修改排版或修正译文,`resume` 会报告这些变化并引导进入审查、构建;不要再次 `apply` 覆盖它们。
54
+
55
+ 不要删除 `.checks/`、`.repairs/` 或 `.merge/` 来处理失败。`--force` 是显式重建,会移除旧断点与结果;未完成的合并不能直接强制重建。版本 1/2 沿用其已有片段快照,新建版本 3 任务额外记录扫描到的完整 TeX 文件哈希。
56
+
57
+ 若旧任务已用旧版脚本合并、没有 `.merge/journal.json`,脚本无法追溯当时的写入状态。遇到源码快照冲突时,主 agent 检查现有中文源码和交付物,确认已完成合并后继续审查、构建;不要强制重建或再次覆盖源码。
58
+
59
+ 翻译恢复完成后分别运行构建与页面渲染脚本,自动复用哈希匹配的成功产物;详见 [build-and-render.md](build-and-render.md)。恢复命令本身不代替漏译审查、编译或视觉检查。
@@ -0,0 +1,49 @@
1
+ """Small file-based helpers for disposable build and render caches."""
2
+ from __future__ import annotations
3
+
4
+ import hashlib
5
+ import json
6
+ import os
7
+ from pathlib import Path
8
+ import tempfile
9
+
10
+
11
+ def file_hash(path: Path) -> str | None:
12
+ if not path.is_file():
13
+ return None
14
+ value = hashlib.sha256()
15
+ with path.open("rb") as source:
16
+ for block in iter(lambda: source.read(1024 * 1024), b""):
17
+ value.update(block)
18
+ return value.hexdigest()
19
+
20
+
21
+ def value_hash(value: object) -> str:
22
+ return hashlib.sha256(json.dumps(value, sort_keys=True, ensure_ascii=False).encode()).hexdigest()
23
+
24
+
25
+ def load_cache(path: Path) -> dict:
26
+ try:
27
+ value = json.loads(path.read_text(encoding="utf-8"))
28
+ return value if isinstance(value, dict) else {}
29
+ except (OSError, ValueError):
30
+ return {}
31
+
32
+
33
+ def save_cache(path: Path, value: dict) -> None:
34
+ path.parent.mkdir(parents=True, exist_ok=True)
35
+ with tempfile.NamedTemporaryFile("w", encoding="utf-8", dir=path.parent, delete=False) as output:
36
+ temporary = Path(output.name)
37
+ json.dump(value, output, ensure_ascii=False, indent=2)
38
+ output.write("\n")
39
+ try:
40
+ os.replace(temporary, path)
41
+ finally:
42
+ if temporary.exists():
43
+ temporary.unlink()
44
+
45
+
46
+ def hashes_match(files: dict) -> bool:
47
+ return isinstance(files, dict) and bool(files) and all(
48
+ isinstance(path, str) and isinstance(expected, str) and file_hash(Path(path)) == expected
49
+ for path, expected in files.items())
@@ -37,6 +37,7 @@ def main() -> int:
37
37
  parser = argparse.ArgumentParser()
38
38
  parser.add_argument("root", type=Path)
39
39
  parser.add_argument("--inventory", action="store_true")
40
+ parser.add_argument("--details", action="store_true", help="print all suspect lines; the default shows only the first 10")
40
41
  args = parser.parse_args()
41
42
  root = args.root.resolve()
42
43
  files = tex_files(root)
@@ -61,8 +62,11 @@ def main() -> int:
61
62
  excerpt = line.strip()
62
63
  if len(excerpt) > 220:
63
64
  excerpt = excerpt[:217] + "..."
64
- print(f"{path.relative_to(root)}:{number}: {excerpt}")
65
+ if args.details or hits <= 10:
66
+ print(f"{path.relative_to(root)}:{number}: {excerpt}")
65
67
  print(f"suspect_lines={hits}")
68
+ if not args.details and hits > 10:
69
+ print(f"more_suspect_lines={hits - 10}; use --details to review all")
66
70
  return 0
67
71
 
68
72