The briefing now maps each update at the narrowest safely aligned granularity, widening deterministically on mapping failure: a change confined to the pair's byte-identical code fences is computed outright (--apply splices 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, 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; units that do not align fall back to depth-matched heading sections (depth only, so translated heading text still maps); and when sections do not align either, or both sides drifted, the briefing says so and withholds the mapping. Terminology rows now match the changed spans only, English terms on word boundaries with plural inflections, and Chinese-target briefings track each relevant term's document-wide first occurrence — a moved occurrence pulls the vacated and receiving spans into the briefing with an explanatory note. The unit mapping, mechanical code splice, and first-occurrence tracking adopt the planner design from the incremental prompt-pipeline PR (#684), whose provider-backed bake-off independently validated the same scope ladder; this PR carries those mechanics into the agent-facing briefing path so both consumers of the consistency records behave alike. The prior line-hunk section mapping and its heading-text alignment (which could not map cross-language sections) are replaced wholesale. Docs: SKILL.md update path, i18n README pair, development.md pair, and the briefed-updates Agent Note pair brought along; the development.md fence edit was applied with --apply itself, and the prose updates were made through the new unit/section briefings.
Bilingual documentation
English | 中文
This repo's documentation is read by people and agents both inside and outside the company, so every document in scope is maintained in English and Simplified Chinese. This page defines the pairing contract, enforcement gate, scope, and exclusions; translation-rules.md defines how to translate; terminology.md is the terminology source of truth. The committed agent workflow lives in .agents/skills/dsh-translate-docs.
The pairing contract
-
Both languages carry equal authority. A document may be authored and reviewed in either language first — a Chinese-first Agent Note is as legitimate as an English-first one — and the counterpart is translated from it. Neither file outranks the other; what binds them is that they must say the same thing.
-
A pair is three sibling files. The English
foo.md, the Chinesefoo.zh.md, and a consistency recordfoo.i18n.yaml, all in the same directory. No locale directories, no separate translation repo, no interleaved bilingual files. Pairs merge whole: a PR never lands one language without the other two files. -
The consistency record.
foo.i18n.yamlholds the full git blob hash of each side as of the last time the two were confirmed to say the same thing:foo.md: 3f786850e387550fdab836ed7e6dc881de23001b foo.zh.md: 89e6c98d92887913cadf06b2adb97f26cde4849bBlob hashes, not commit hashes, so the record is computable for files edited in the same PR (
git hash-object foo.md) and consistency is a pure content comparison. The recorded hashes also recover the exact last-confirmed text of either side, so an out-of-sync pair is updated by patching the counterpart minimally against the edited side's diff — never by re-translating whole files.pnpm run gen-translation-brief <pair>assembles that update's working set mechanically at the narrowest safely aligned granularity — changed Markdown units, then heading sections, then whole document — with the edited side's diff since last confirmation, each changed span's three-way text, the terminology rows the change touches, and the binding update rules; a change confined to the pair's byte-identical code fences is computed outright, and--applysplices it into the counterpart after structural validation (briefed-updates Agent Note). After bringing the pair back in line,pnpm run verify-translation-pairing --write <pair>re-records both hashes; that yaml diff is the reviewable act of confirming consistency, which is why--writerequires naming the pairs you confirmed (--write --allis the explicit corpus-wide form). -
Language switcher. Both files link to each other immediately after their H1 heading: the English file carries
English | [中文](foo.zh.md)and the Chinese file carries[English](foo.md) | 中文. -
Structure mirrors the counterpart. Heading depths and order, list kinds, ordered-list starts, list item counts, table row and column counts, link targets, and verbatim code blocks match one to one across the pair — see translation-rules.md for the full preservation rules. Existing Markdown gates apply to
.zh.mdfiles unchanged (verify-md-wrap,verify-md-links).
The gate: verify-translation-pairing
pnpm run verify-translation-pairing (part of doc-sync, which contributors run locally for documentation changes and CI runs exhaustively) enforces the contract mechanically:
- Every document in scope has a complete pair. README discovery is case-insensitive on the basename, so
missions/readme.mdis in scope alongside the other documentation roots. - Every pair artifact that exists at all is complete and consistent: all three files present, each side's current blob hash equals the recorded one (editing either side without re-confirming the pair goes red), both sides carry the language switcher, and the structural signatures match in order — heading depths, verbatim code blocks (info string and content), table row and column counts, list kinds, ordered-list starts, item counts, and every link target apart from the switcher.
- Files listed as
excludedhave no.zh.mdand no.i18n.yamlat all.
Source-oriented code gates consume an exact .zh.md fence sequence as a derivative of its unsuffixed sibling instead of compiling or manifesting the same code twice. The sequence must match in length, order, fence kind, and byte-exact body; otherwise both copies remain independently checked and the pairing gate reports the structural mismatch.
pnpm run verify-translation-pairing --list prints the current pairing state of every document in scope — missing, out-of-sync, or ok. It never fails; missing and out-of-sync rows identify violations that the normal check rejects.
pnpm run verify-translation-pairing <pair...> checks just the named pairs — any of a pair's three files (or its bare stem) names it — so an update loop verifies its own pair in seconds instead of re-scanning the corpus. The no-argument corpus-wide form is what doc-sync and CI run; a scoped green never substitutes for it at PR level.
The practical rule this gate creates: when a PR edits either side of a paired document, the same PR updates the counterpart and re-records the pair (run the dsh-translate-docs skill, then --write <pair>), exactly like the repo's existing doc-sync rule for code and READMEs. A PR that leaves a pair out of sync goes red in CI.
The gate's limit, stated plainly: a green gate means the pair was confirmed consistent at these exact contents, not that the confirmation was sound. It checks hashes and shape; it cannot judge whether the two sides actually say the same thing, or whether the wording is accurate, well-termed, and natural — that is the reviewer's half of the contract, per translation-rules.md. A re-recorded pair with a sloppy counterpart passes the gate; it must not pass review.
Scope and exclusions
Scope: every non-vendor README, plus every document under .agents/notes/**, docs/**, and python/**. README matching is case-insensitive on the basename and covers future directories without another manifest edit. Dependency and ignored build-output trees are discovery exclusions, not source documentation.
Excluded (never paired, and the gate rejects a .zh.md or .i18n.yaml for them):
docs/cordis-catalog/,docs/tool-catalog/,docs/config-catalog.md,docs/persistence-catalog.md,docs/module-graph.md,docs/agent-lifecycle.md,docs/capability-seams.md,docs/event-producer-consumer.md,docs/graph-atlas.md, anddocs/tool-execution-pipeline.md— generated files; their generators emit English only today, so a hand-written translation would go stale on every regeneration. The planned follow-up is to teach the generators to emit Chinese alongside English, at which point these leave the exclusion list.docs/AGENTS.md,.agents/notes/**/AGENTS.md, and theirCLAUDE.mdinstruction symlinks — agent instructions, maintained in English only like the rootAGENTS.md.docs/i18n/terminology.mdand style-samples.md — both are bilingual by construction.- translation-prompt.md — the automated pipeline's prompt template; its body is machine-consumed verbatim, so a paired translation would change pipeline behavior.
Universal requirement: every current or future document in scope must merge as a complete bilingual pair. scripts/translation-pairing.manifest.json contains only explicit exclusions; there is no per-file rollout list, date cutoff, or README-specific policy class.
Division of labor
Counterparts here are produced by an agent running dsh-translate-docs and reviewed by a human — inference is cheap here, review attention is the scarce resource. The gate checks pair completeness, recorded hashes, switchers, and its documented structural signature. Review still owns translation quality, terminology, and structural requirements that the signature does not encode. The prompt contract is executable: scripts/translation-prompt.ts renders the committed template (terminology injected; the template carries its own calibrated rules) into either direction and parses the three-section response, while verify-translation-prompt exercises both render directions and the checked-in example in doc-sync.