Briefed minimal translation updates
基于简报的最小化翻译更新
The [bilingual pairing contract](2026-07-02-bilingual-docs-and-pairing-gate.md) already prescribed minimal counterpart updates — diff the edited side against its last-confirmed state, patch the counterpart, never re-translate — but the committed workflow made every update pay whole-document overheads. The translating subagent loaded the full guidance corpus (skill, pairing contract, translation rules, the 192-line te
English
Problem
The bilingual pairing contract already prescribed minimal counterpart updates — diff the edited side against its last-confirmed state, patch the counterpart, never re-translate — but the committed workflow made every update pay whole-document overheads. The translating subagent loaded the full guidance corpus (skill, pairing contract, translation rules, the 192-line terminology table, style samples, prose standard) before touching a two-line diff; it re-derived the last-confirmed diff by hand through git cat-file; and each iteration re-ran the corpus-wide pairing gate, which parses every pair in the tree to validate one. A small English prose edit routinely cost tens of times its proportional share of tokens and minutes, which taxes exactly the behavior the contract wants — bringing the counterpart along in the same PR.
Decision
The extended manual workflow runs pair updates on a generated briefing instead of the guidance corpus; new pairs in that workflow still use the unchanged whole-document path. Routine agent work uses the direct path defined by the lightweight-translation decision.
pnpm run gen-translation-brief [--apply] [pair...](scripts/gen-translation-brief.ts, assembly in scripts/translation-brief.ts) prints, per out-of-sync pair, the authored side's diff from its recorded last-confirmed blob to the working tree plus the change mapped at the narrowest safely aligned granularity, deterministically widening on mapping failure: a change confined to the pair's byte-identical code fences is computed outright (--applysplices it into the counterpart and validates the result against the pairing gate's structural signature before writing); otherwise changed Markdown units (headings, paragraphs, table rows, list items, code fences, block quotes, HTML blocks, thematic breaks, link definitions — matched by container-scoped kind sequences) each carry their last-confirmed source, current source, and current counterpart text with line numbers; units that do not align fall back to depth-matched heading sections; and when sections do not align either, or both sides drifted, the briefing says so and withholds the mapping instead of guessing. Terminology rows are matched against the changed spans only (word-boundary English matching with plural inflections), and for Chinese targets the briefing tracks each relevant term's document-wide first occurrence — when an edit moves it, the vacated and receiving spans join the briefing with an explanatory note, since the 首次出现 annotation must move with it. The unit mapping, code splice, and first-occurrence mechanics adopt the planner design from the incremental prompt-pipeline work; its provider-backed bake-off independently validated the same scope ladder for the automated pipeline. The briefing is the translator's whole working set; the full sources of truth remain the escalation path for decisions the briefing cannot answer.- When explicitly invoked, the update path in dsh-translate-docs consumes the briefing: mechanical (code-fence-only) changes are applied with
--apply, no subagent; prose diffs go to a subagent whose prompt is the briefing, not the corpus; verification is clause-by-clause on the changed spans, not the whole document. - The pairing gate takes pair arguments.
verify-translation-pairing [pair...]checks just the named pairs (any of a pair's three files, or the bare stem, names it); the corpus-wide sweep remains the no-argument form thatdoc-syncand CI run.--writenow requires naming the confirmed pairs — bare--writerefuses, and re-recording everything is an explicit--write --all— because the old bare form silently blessed every drifted pair in the tree, including ones the caller never looked at, and a prose-only drift would then stay green forever. Each record's comment names its own scoped command. Before recording,--writestores each side's exact bytes withgit hash-object -w --stdinand pins the blob under a content-addressed localrefs/dsh/translation-pairing/snapshots/ref; an uncommitted last-confirmed snapshot is therefore available to the briefing generator's latergit cat-file, not merely named by a hash that Git cannot resolve or left vulnerable to garbage collection.
Benchmark
The decision followed a controlled replay of ten real pair updates from this repo's history (July 2026; 1-64 changed English lines each, READMEs, RFCs, Agent Notes, and user docs). Each example was reconstructed in a scratch repo at its true last-confirmed state with the English edit uncommitted, then run through competing workflows with fresh subagents: the status-quo corpus-loading path, the briefed path, a no-guidance control, whole-document re-translation, the briefed path on a small model, and a three-pairs-per-agent batch. Outputs were gated mechanically and scored blind by judges who also received the real historical update and the untouched stale counterpart as controls.
- The briefed path matched the status-quo path on judged faithfulness, preservation, and fluency — both at or above the real historical updates — while spending roughly a third of the tokens and wall clock on the stall-free examples (medians across all ten: 276k vs 595k relative token-cost units, 14 vs 32 turns).
- Re-translation was confirmed harmful, not merely wasteful: judged preservation collapsed (4.4/10 vs 9.8) because it discards reviewed phrasing, it drifted established terminology the update arms kept (the counterpart's own text carries the renderings), and it was the most expensive arm.
- The no-guidance control held quality too — the binding context for an update is the diff plus the counterpart's own reviewed text, not the corpus — but the briefing buys a fixed working set, inline terminology, and the both-sides-drifted warning at negligible cost over it.
- On the briefing, a small model performed at parity with the large one, so the update path no longer assumes a frontier translator.
- Batching three pairs into one subagent showed no reliable saving over three briefed runs and couples unrelated failures; it was rejected.
A second head-to-head replay on the same ten examples compared this note's shipped briefing against its earlier section-only form (no unit tier, no computed mechanical path, counterpart-only context, no first-occurrence tracking). Prose quality and cost were at parity — pairwise blind verdicts split with only stylistic margins — and the shipped form won on two objective outcomes: the two code-fence-only examples were completed byte-identical to the human-reviewed historical updates in under a second with no model tokens, and on the example whose edit moved a term's document-wide first occurrence, the shipped briefing's flagged move reproduced the human-reviewed gloss relocation while the section-only form left a 首次出现 violation for review to catch.
Alternatives considered
- Keep the workflow, just scope the gate — the gate scan was the smaller cost; the corpus loads and archaeology dominated. Scoping alone would have left the ~3x overhead in place.
- Whole-document re-translation as the update path (what a naive pipeline does) — rejected on benchmark evidence: preservation collapse, terminology drift, highest cost. The contract's minimal-update rule survives with data behind it.
- Batching several pairs per subagent — rejected: no measured saving (briefings already deduplicate the fixed content), and one stalled or confused pair holds the others hostage.
- Per-paragraph translation-memory records in the sidecar (segment hashes instead of whole-file hashes) — rejected: paragraph boundaries may legitimately differ across the pair, either side can be authored first, and the records would bloat and conflict in merges. Span mapping computed on demand from the existing whole-file hashes recovers the same alignment when it is trustworthy and says so when it is not.
- An update mode in the automated prompt pipeline (prompt-v5) — deferred, not designed here: nothing drives scripts/translation-prompt.ts today, and the agent path was the live cost center. The pipeline keeps its whole-document v4 contract until it has a consumer.
Consequences
- In the explicitly invoked extended workflow, a small prose edit's counterpart update costs a briefing generation plus one small focused task — no corpus reads, no archaeology, no corpus-wide scans inside the loop — and the same PR obligation holds.
- The briefing generator is a second consumer of the consistency records: recorded blob hashes now also drive diff recovery and section mapping, strengthening the incentive to keep records honest.
- Each distinct confirmed snapshot retains a content-addressed local ref and object. An abandoned re-record may therefore leave an extra durable pin, but it changes no branch or commit history; this local retention is the tradeoff that prevents garbage collection from invalidating an accepted pairing record.
--writewithout arguments no longer works; muscle-memory callers must name pairs or pass--all. That is the point — the bulk bless is now a visible, deliberate act.- Scoped checks mean an update loop can be green while an unrelated pair elsewhere is red; the corpus-wide check in
doc-sync/CI still owns the tree-level invariant. - Span mapping trusts an alignment only when the kind sequences match across the last-confirmed source, current source, and current counterpart; a mapping failure widens deterministically (units → sections → whole document) rather than guessing, so a restructured document gets an explicit "locate the regions yourself" briefing, never a wrong map.
- A first-occurrence move can enlarge a briefing beyond the directly changed spans; that cost is an explicit consequence of the 首次出现 contract, not an alignment heuristic.
Testing
scripts/translation-brief.spec.ts pins unit and section span extraction (container-scoped kinds, depth-only section alignment so translated heading text still maps, preamble), alignment and changed-index detection, the mechanical code splice and each of its refusal conditions, terminology row matching in both directions with word-boundary and plural-inflection discipline, first-occurrence movement tracking, fence escalation, and the rendered briefing's contract (unit bundles with three-way context, mechanical/sections/document scopes, per-direction digests, scoped finish commands). scripts/translation-pairing.spec.ts pins exact-byte persistence and recovery for uncommitted snapshots, failure before an unavailable object can enter a sidecar, argument normalization (any pair file or bare stem to the anchor), and the CLI matrix: scoped check, bare --write refusal, --write <pair>, --write --all, --list exclusivity, unknown flags.
中文
问题
双语配对约定早已规定对侧文件按最小幅度更新:把被改的一侧与其上次确认状态做 diff,据此修补对侧文件,绝不整篇重译;但仓库内置的工作流让每次更新都付出整篇文档级别的开销。负责翻译的 subagent 在动手处理一个两行的 diff 之前,要先加载完整的指导语料(guidance corpus),即 skill(技能)、配对约定、翻译规则、192 行的术语表、语体样例与行文标准;要通过 git cat-file 手工重新推导上次确认状态以来的 diff;每轮迭代还要重跑全语料配对门禁,而该门禁为校验一个配对要解析整棵树里的每一个配对。一次小的英文行文修改,动辄花掉数十倍于其应得份额的 token 用量与分钟数,被惩罚的恰恰是约定想要的行为:在同一个 PR(Pull Request)里把对侧文件一并带上。
决策
扩展的手动工作流使用生成的简报(briefing)而非指导语料来更新配对;该工作流中的新配对仍采用保持不变的整篇文档路径。常规 agent 工作采用由轻量翻译决策定义的直接路径。
pnpm run gen-translation-brief [--apply] [pair...](scripts/gen-translation-brief.ts,组装逻辑在 scripts/translation-brief.ts)针对每个失去同步的配对,打印被改一侧从其记录在案的上次确认 blob 到当前工作区的 diff,并附上以能安全对齐的最窄粒度映射的这次改动,映射失败时粒度确定性地逐级放宽:仅落在配对中逐字节一致的围栏代码块内的改动会直接算出(--apply会把它拼接进对侧文件,并在写入前用配对门禁的结构签名校验所得结果);否则,每个有改动的 Markdown 单元(标题、段落、表格行、列表项、围栏代码块、块引用、HTML 块、分隔线、链接定义;匹配依据是以容器为作用域的种类序列)都带上各自的上次确认源文、当前源文与当前对侧文本及行号;无法对齐的单元回退到按深度匹配的标题章节;当章节也无法对齐或两侧同时漂移时,简报会明说这一点并省略映射,而不是靠猜。术语表行只与改动块匹配(英文术语按词边界匹配,含复数变形);当目标侧是中文时,简报还会跟踪每个相关术语在整篇文档中的首次出现:一旦某次编辑使其移位,腾出的与接收的两处区间就会附一条解释性说明加入简报,因为「首次出现」括注必须随之移动。单元映射、代码拼接与首次出现机制采纳了增量提示词流水线工作的规划器设计;该项工作中接入提供方的对比评测,已为自动流水线独立验证了同一套范围阶梯。简报就是译者的全部工作集;简报回答不了的决策,仍以完整的真源文档作为升级求证路径。- 显式调用时,dsh-translate-docs 中的更新路径消费这份简报:机械类改动(只涉及围栏代码块)用
--apply应用,不动用 subagent;行文类 diff 交给 subagent,其提示词就是简报本身,而非指导语料;核验只对改动块逐句进行,不覆盖整篇文档。 - 配对门禁接受配对参数。
verify-translation-pairing [pair...]只检查被点名的配对(配对三个文件中的任意一个,或其裸词干,都能指代该配对);全语料扫描仍是doc-sync(文档同步门禁)与 CI 运行的无参数形式。--write现在要求点名已确认的配对:裸--write会拒绝执行,重新记录全部配对必须显式写--write --all;原因是旧的裸形式会默默为树中每一个漂移的配对背书,包括调用者从未看过的那些,纯行文层面的漂移于是可以永远保持绿灯。每份记录的注释都写明针对该配对自身的按对命令。写下记录之前,--write用git hash-object -w --stdin存入每一侧的精确字节,并在内容寻址的本地refs/dsh/translation-pairing/snapshots/ref 下固定该 blob;未提交的上次确认快照因此能被简报生成器之后的git cat-file取回,而不只是留下一个 Git 无法解析的 hash 名称或暴露于垃圾回收。
基准测试
该决策来自对本仓库历史上十次真实配对更新的受控回放(2026 年 7 月;每例改动 1 到 64 行英文,涵盖 README、RFC、Agent Note 与用户文档)。每个样例都在临时仓库中重建到其真实的上次确认状态,英文改动保持未提交,再用全新的 subagent 分别跑过相互竞争的各条工作流:维持现状的语料加载路径、简报路径、无指导对照组、整篇重译、小模型上的简报路径,以及每个 agent(智能体)一次处理三对文档的批量方案。产出先经机械门禁把关,再由评委盲评打分;评委还同时收到真实的历史更新与原样未动的陈旧对侧文件作为对照。
- 简报路径在盲评的忠实性、保留度与流畅度上与现状路径打平(两者都达到或超过真实历史更新的水平),而在未发生停滞的样例上只花费约三分之一的 token 用量与墙钟时间(全部十例的中位数:相对 token 成本单位 276k 对 595k,轮次数 14 对 32)。
- 整篇重译被证实有害,而不只是浪费:它丢弃经评审的措辞,盲评保留度因此崩塌(4.4/10 对 9.8);它还使各更新组保持住的既定术语发生漂移(既定译法本就写在对侧文件自身的正文里);而且它是成本最高的一组。
- 无指导对照组的质量同样立得住(对一次更新有约束力的上下文,是 diff 加上对侧文件自身经评审的正文,而非指导语料),但简报以几乎可忽略的额外成本,换来固定的工作集、内联的术语,以及两侧同时漂移的警告。
- 以简报为输入,小模型的表现与大模型持平,因此更新路径不再假定翻译必须由前沿模型完成。
- 把三对文档合并给同一个 subagent,相比三次各自带简报的运行没有可靠的节省,还把互不相关的失败耦合在一起;该方案被否决。
在同样这十个样例上进行的第二次正面对比回放,把本文最终交付的简报与其早前仅按章节的形态(没有单元层级、没有直接算出的机械路径、上下文只含对侧文件、不跟踪首次出现)相对照。行文质量与成本两相持平(两两盲评裁定各有胜负,差距仅在文风),而最终交付的形态在两项客观结果上胜出:两个只涉及围栏代码块的样例在一秒之内完成且不消耗任何模型 token,产出与经人工评审的历史更新逐字节一致;而在那个编辑使某术语在整篇文档中的首次出现发生移位的样例上,最终交付的简报所标记的移位复现了经人工评审的括注迁移,仅按章节的形态则留下一处「首次出现」违例,留待评审去捕捉。
曾考虑的替代方案
- 保留原工作流,只让门禁支持按对检查:门禁扫描本是较小的开销,大头在语料加载与翻查历史。只收窄检查范围,约 3 倍的开销仍会原地保留。
- 把整篇重译作为更新路径(朴素流水线的做法):依据基准测试证据否决,理由是保留度崩塌、术语漂移、成本最高。约定的最小更新规则得以延续,且从此有数据支撑。
- 每个 subagent 批量处理多对文档:否决。没有实测出节省(简报本身已对固定内容做了去重),而且一对文档停滞或陷入混乱会把其余配对一并拖住。
- 在伴随记录中保存逐段的翻译记忆条目(用分段 hash 取代整文件 hash):否决。配对两侧的段落边界可以合理地不同,任一侧都可能先撰写,这类条目还会不断膨胀并在合并时产生冲突。基于现有整文件 hash 按需计算的区间映射,在对齐可信时能恢复同样的对齐关系,不可信时会明确说明。
- 给自动提示词流水线加一个更新模式(prompt-v5):推迟,本文不做设计。今天没有任何调用方在驱动 scripts/translation-prompt.ts,实际的成本中心是 agent 路径。流水线在拥有消费方之前,维持其整篇文档的 v4 约定。
后果
- 在显式调用的扩展工作流中,一次小的行文修改,其对侧文件更新只需生成一份简报,外加一个小而聚焦的任务(不读指导语料、不翻查历史、循环内不做全语料扫描),同一 PR 内完成更新的义务保持不变。
- 简报生成器成为一致性记录的第二个消费方:记录的 blob hash 如今还驱动 diff 还原与章节映射,这进一步强化了如实维护记录的动机。
- 每个不同的已确认快照都会保留一个内容寻址的本地 ref 和对象。因此,中途放弃的重新记录可能留下额外的持久固定项,但它不会改变任何分支或提交历史;这种本地保留正是防止垃圾回收让已接受配对记录失效所付出的代价。
- 不带参数的
--write不再可用;靠肌肉记忆的调用者必须点名配对或传--all。这正是目的所在:批量背书如今是一个可见的、有意为之的动作。 - 按对检查意味着一个更新循环可以在别处某个无关配对处于红灯时自己保持绿灯;
doc-sync/CI 中的全语料检查仍然承载树级不变式。 - 区间映射只在上次确认源文、当前源文与当前对侧文本三方的种类序列一致时才信任一处对齐;映射失败时粒度确定性地逐级放宽(单元 → 章节 → 整篇文档)而不是靠猜,因此被重构过的文档拿到的是一份明确写着「请自行定位相关区域」的简报,绝不会是一张错误的地图。
- 「首次出现」的一次移位可能让简报扩大到直接改动块之外;这一成本是「首次出现」约定的明确后果,而非对齐启发式。
测试
scripts/translation-brief.spec.ts 固定单元与章节的区间提取(以容器为作用域的种类、只按深度对齐章节从而让已翻译的标题文字仍能映射、首个标题前的序言)、对齐与改动索引检测、机械代码拼接及其每一个拒绝条件、带词边界与复数变形约束的双向术语行匹配、首次出现移位跟踪、围栏升级,以及渲染后简报的约定(带三方上下文的单元条目、机械/章节/整篇文档三种范围、分方向的规则摘要、按对的收尾命令)。scripts/translation-pairing.spec.ts 固定未提交快照的精确字节持久化与取回、在不可用对象进入伴随记录前失败、参数归一化(配对的任一文件或裸词干都归一到锚点),以及 CLI(命令行界面)用例矩阵:按对检查、裸 --write 拒绝执行、--write <pair>、--write --all、--list 的互斥性、未知标志。