# Agent Note: 自动组合翻译配对记录

Status: implemented

[English](2026-08-08-automatic-translation-pairing-merges.md) | 中文

## 问题

一份双语一致性记录包含两侧 Markdown 文件的精确 blob hash。因此，当两个分支分别更新同一已确认配对的不同部分时，即使 Git 能干净合并两侧 Markdown 文件，记录中的两行 hash 仍会发生冲突。选择任一侧都会留下陈旧 hash；手工重新生成记录则会重复执行一项确定性操作，并阻止本可自动完成的合并。

## 决策

`*.i18n.yaml` 使用由仓库维护的 `dsh-translation-pairing` 合并驱动。worktree 本地的 Git 安装脚本在设置 Lefthook 的同时注册该驱动命令；Git 配置仍限定于当前 worktree，因为受跟踪的属性可以指定驱动，却无法携带其可执行命令。

安装脚本会在发布 worktree 集成前加载确切的 Node/tsx 入口点。Git 会调用仓库内已提交且不依赖 Node 的 shell 启动器；该启动器会在每次执行驱动前重复这项探测。运行时或入口点不可用时，即使文本合并干净完成，启动器也会将 Git 的普通三方文本合并结果写入伴随文件并返回冲突状态，使 Git 保留未合并的索引阶段，绝不接受未经验证的元数据。

驱动会解析祖先、当前和另一侧的记录，并加载其中 hash 指向的六个 Markdown blob。它分别对英文文档的三个 blob 与中文文档的三个 blob 运行 Git 默认的三方文本合并，要求两次合并都干净完成，校验语言切换行与配对结构签名，存储两个合并后的 blob，再将其 hash 写入规范记录。这一过程组合两个父提交中已经存在的确认；它绝不会为普通的单侧文档编辑写入记录。

记录格式错误、对象缺失、配对文档采用其他合并策略（包括原本未单独指定策略的路径继承了非文本 `merge.default`）、任一配对文档存在内容冲突，或合并后的配对违反结构检查时，驱动会让伴随文件保持为普通的未解决冲突。新增、删除及重命名情形仍需手工处理，因为它们的路径归属关系无法由上述基于三份记录的操作处理。

`pnpm run resolve-translation-pairing-conflicts` 会在合并已经停止后应用同一算法。写入任何伴随文件之前，它会验证伴随文件仍保留 Git 生成且未经改动的冲突结果，并验证暂存区中配对文档的 blob ID 以及工作树中的文件字节都与工具独立完成的合并结果一致。即使其他配对仍需手工处理，它也会将所有可安全生成的记录作为一个批次写入并暂存；随后报告剩余的配对冲突并以非零状态退出，确保调用方不会把仅部分解决误认为合并已经完成。

`pre-merge-commit` 与 `pre-commit` 会对照两侧文档在索引中的精确字节，校验暂存的 `.i18n.yaml` 文件。这些钩子会验证驱动输出，但不会重新生成记录，因此绕过钩子也无法默默确认发生漂移的译文；CI 中全语料的 `doc-sync`（文档同步门禁）仍是权威检查。

<a id="failure-contract"></a>

## 失败约定

| 普通 `git merge` 期间的失败 | 可观察状态 | 恢复 |
|---|---|---|
| 全新安装无法探测驱动或安装 Lefthook | 不会发布新的驱动或钩子路径配置；任何新增集成都会回滚到原有的钩子查找方式。 | 恢复依赖并重新运行 `node scripts/install-lefthook.mjs`。 |
| 安装后 Node、tsx 或驱动入口点变得不可用 | 合并停止，伴随文件状态为 `UU`；索引阶段 1/2/3 保留，worktree 中的伴随文件包含 Git 的文本结果，`MERGE_HEAD` 存在，且不创建提交。 | 恢复依赖并运行 `pnpm run resolve-translation-pairing-conflicts`，或运行 `git merge --abort`。 |
| 了解仓库状态的驱动拒绝记录 | 合并停止，伴随文件保持未解决状态且不创建提交；驱动会打印修复配对文档和运行显式冲突解决命令的路径。 | 修复配对文档冲突或记录，然后运行打印出的冲突解决工作流，或中止合并。 |
| 驱动进程以大于 128 的状态码崩溃 | Git 中止合并策略，既不写入 `MERGE_HEAD`，也不产生未合并的索引阶段。 | 修复运行时并重新执行合并。 |
| `pre-merge-commit` 拒绝原本能干净完成的文件合并 | 不再有未合并条目，完整结果已暂存且 `MERGE_HEAD` 存在，但不会创建合并提交。 | 修复钩子失败并运行 `git commit`，或运行 `git merge --abort`。 |

安装脚本回滚失败时，会同时报告最初的安装错误和每一项回滚错误。此时 worktree 配置可能只完成了一部分，因此贡献者会先修复或检查配置再执行合并，而不是依赖静默回退。

## 验证

脚本测试通过已安装的启动器覆盖无冲突组合，并覆盖运行时缺失与入口点损坏时的文本回退、安装脚本探测回滚、拒绝合并的 `pre-merge-commit` 钩子、从含未解决条目的索引中显式恢复、可安全解决的配对与配对文档冲突的配对并存、被编辑过的伴随文件、非文本默认合并配置、记录解析和 worktree 本地安装。既有的全语料校验器继续证明已提交的记录与两侧配对文档相符。

## 考虑过的替代方案

**选择 ours 或使用 Git 的 union 驱动。** 任一父提交的记录都指向合并前的内容，而 union 会产生重复或乱序的 hash 键。两者都无法表示合并后的配对文档。

**在 `post-merge` 中或仅通过提交钩子重新生成。** 发生冲突的合并不会运行 `post-merge`，而且该钩子无法改变合并结果。只有暂存区不含未解决条目后才会触发提交钩子，因此仅用钩子无法清除生成文件冲突。

**用仓库命令包装每次合并。** 包装命令可以根据已由合并填充的索引解决冲突，显式冲突解决命令也保留这条恢复路径，但直接运行 Git、stack 工具、rebase 和 cherry-pick 时仍会在此前停止。合并驱动才是这些操作共用的文件级扩展点。

**通过 GitHub Actions 或应用解决。** 托管自动化可以更新 PR（Pull Request）分支，却会引入凭据、并发控制与分支修改。本地执行或由 agent（智能体）驱动的 merge-forward 工作流已经具有检出目录与推送权限，因此仓库不把远程自动化纳入该机制。

## 后果

完成安装的 worktree 会自动消除仅由配对记录引起的冲突，同时仍由人工处理配对文档冲突并评判翻译质量。GitHub 托管的可合并性计算不会运行 worktree 本地的可执行命令，因此仍须由贡献者或 agent 将 base 分支合入当前分支并推送结果提交，远端冲突标记才会消失。

安装脚本会在 worktree 配置中预留 `merge.dsh-translation-pairing.*`，并拒绝与之冲突的自定义值。与仓库贡献者钩子一样，自动组合依赖已安装的 Node 依赖；运行时不可用时会产生可见的未解决文本结果，而不是选择陈旧元数据。
