docs(i18n): document automatic pairing merges

This commit is contained in:
Tianyi Cui
2026-08-08 21:11:48 +08:00
parent 542c029c80
commit 3abe813fd3
10 changed files with 118 additions and 20 deletions

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md
2026-08-08-automatic-translation-pairing-merges.md: a1b92c97240e2019b188b606dc079b2211a89238
2026-08-08-automatic-translation-pairing-merges.zh.md: 5c7249dc436d24aa955d9c042c8d1bdc84655868

View File

@@ -0,0 +1,41 @@
# Agent Note: Automatically compose translation pairing records
Status: implemented
English | [中文](2026-08-08-automatic-translation-pairing-merges.zh.md)
## Problem
A bilingual consistency record contains the two owner files' exact blob hashes. Two branches that independently update different parts of the same confirmed pair therefore conflict on both hash lines even when Git cleanly composes both Markdown owners. Selecting either side leaves stale hashes, while regenerating the record by hand repeats a deterministic operation and prevents an otherwise automatic merge.
## Decision
`*.i18n.yaml` uses the repository-owned `dsh-translation-pairing` merge driver. The worktree-local Git installer registers its command alongside Lefthook setup; Git configuration remains local because a tracked attribute can name a driver but cannot carry its executable command.
The driver parses the ancestor, current, and other records and loads the six owner blobs named by their hashes. It independently runs Git's default three-way text merge for the English and Chinese triplets, requires both merges to be clean, verifies language switchers and the pairing structural signature, stores the two merged blobs, and writes their hashes as the canonical record. This composes confirmations already present in both parents; it never records an ordinary one-sided documentation edit.
The driver fails with an ordinary unresolved sidecar when a record is malformed, an object is missing, an owner uses another merge strategy, either owner has content conflicts, or the merged pair violates structural checks. Add/delete and rename shapes remain manual because their path ownership is not the same three-record operation.
`pnpm run resolve-translation-pairing-conflicts` applies the same algorithm after a merge has already stopped. Before writing any sidecar, it proves that the staged owner blob IDs and working-tree bytes equal its independent merges; then it writes and stages every safe record as one batch while leaving unrelated conflicts untouched.
`pre-merge-commit` and `pre-commit` verify staged `.i18n.yaml` files against the exact index bytes of their owners. They validate driver output but do not regenerate records, so bypassing a hook cannot silently bless translation drift; the corpus-wide `doc-sync` check remains authoritative in CI.
## Verification
Script tests exercise clean composition through a real custom-driver Git merge, explicit recovery from an unresolved index, owner-content conflicts, record parsing, and worktree-local installation. The existing corpus verifier continues to prove that a committed record matches its two owners.
## Alternatives considered
**Take ours or use Git's union driver.** Either parent record names pre-merge content, while union produces duplicate or unordered hash keys. Neither represents the merged owners.
**Regenerate in `post-merge` or only in a commit hook.** `post-merge` does not run after a conflicted merge and cannot affect its outcome. Commit hooks are reached only after the index has no unresolved entries, so a hook alone cannot clear the generated conflict.
**Wrap every merge in a repository command.** A wrapper can resolve the conflict from the populated index, and the explicit resolver retains that recovery path, but raw Git, stack tooling, rebases, and cherry-picks would still stop before it. The merge driver is the file-level extension point shared by those operations.
**Resolve on GitHub through Actions or an app.** Hosted automation could update PR branches, but it adds credentials, concurrency control, and branch mutation. Local and agent-driven merge-forward workflows already have a checkout and push authority; the repository keeps remote automation out of this mechanism.
## Consequences
Installed worktrees automatically remove pairing-record-only conflicts while preserving human judgment for owner conflicts and translation quality. GitHub's hosted mergeability calculation does not run the worktree-local executable, so a contributor or agent must still merge the base and push the resulting commit before the remote conflict badge clears.
The installer reserves `merge.dsh-translation-pairing.*` in worktree configuration and refuses a conflicting custom value. Merge execution depends on the installed Node dependencies, like the repository's contributor hooks; missing setup fails rather than selecting stale metadata.

View File

@@ -0,0 +1,41 @@
# 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,因为受跟踪的属性可以指定驱动,却无法携带其可执行命令。
驱动会解析祖先、当前和另一侧的记录,并加载其中 hash 指向的六个 Markdown blob。它分别对英文文档的三个 blob 与中文文档的三个 blob 运行 Git 默认的三方文本合并,要求两次合并都干净完成,校验语言切换行与配对结构签名,存储两个合并后的 blob,再将其 hash 写入规范记录。这一过程组合两个父提交中已经存在的确认;它绝不会为普通的单侧文档编辑写入记录。
记录格式错误、对象缺失、配对文档采用其他合并策略、任一配对文档存在内容冲突,或合并后的配对违反结构检查时,驱动会让伴随文件保持为普通的未解决冲突。新增、删除及重命名情形仍需手工处理,因为它们的路径归属关系无法由上述基于三份记录的操作处理。
`pnpm run resolve-translation-pairing-conflicts` 会在合并已经停止后应用同一算法。写入任何伴随文件之前,它会先验证暂存区中配对文档的 blob ID 以及工作树中的文件字节,都与工具独立完成的合并结果一致;随后它会将所有可安全生成的记录作为一个批次写入并暂存,同时不改动无关冲突。
`pre-merge-commit` 与 `pre-commit` 通过限定范围的配对检查,对照两侧文档在索引中的精确字节校验暂存的 `.i18n.yaml` 文件。这些钩子会验证驱动输出,但不会重新生成记录,因此绕过钩子也无法默默确认发生漂移的译文;CI 中全语料的 `doc-sync`(文档同步门禁)仍是权威检查。
## 验证
脚本测试通过一次使用真实自定义驱动的 Git 合并来覆盖无冲突组合,并覆盖从含未解决条目的索引中显式恢复、配对文档内容冲突、记录解析和 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 依赖;安装未完成时,合并会失败,而不是选择陈旧元数据。