mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
docs: require bilingual non-README documentation
This commit is contained in:
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write
|
# pnpm run verify-translation-pairing --write
|
||||||
2026-07-02-bilingual-docs-and-pairing-gate.md: 4bc02878a0ea3f998e411ecc2b064c1626eacf3c
|
2026-07-02-bilingual-docs-and-pairing-gate.md: 08e149ccc2342695d6dae4f1845896def6bf4388
|
||||||
2026-07-02-bilingual-docs-and-pairing-gate.zh.md: 90a0c2f07f68b0fb4e26cd1c4b537a30a829b10f
|
2026-07-02-bilingual-docs-and-pairing-gate.zh.md: 91f25f33c20337ea688257829552795085e0a8c0
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ This repo's README and docs tree are read by people and agents inside and outsid
|
|||||||
|
|
||||||
- **Paired sibling files with equal authority.** A documentation pair is three sibling files: English `foo.md`, Chinese `foo.zh.md`, and a consistency record `foo.i18n.yaml`. Neither language is canonical — a document may be authored and reviewed Chinese-first and translated to English afterwards, or the reverse; what binds the pair is that both sides must say the same thing, and pairs merge whole (both languages plus the record, never one alone). Policy: [docs/i18n/README.md](../../../../docs/i18n/README.md); translation rules: [docs/i18n/translation-rules.md](../../../../docs/i18n/translation-rules.md); terminology source of truth: [docs/i18n/terminology.md](../../../../docs/i18n/terminology.md).
|
- **Paired sibling files with equal authority.** A documentation pair is three sibling files: English `foo.md`, Chinese `foo.zh.md`, and a consistency record `foo.i18n.yaml`. Neither language is canonical — a document may be authored and reviewed Chinese-first and translated to English afterwards, or the reverse; what binds the pair is that both sides must say the same thing, and pairs merge whole (both languages plus the record, never one alone). Policy: [docs/i18n/README.md](../../../../docs/i18n/README.md); translation rules: [docs/i18n/translation-rules.md](../../../../docs/i18n/translation-rules.md); terminology source of truth: [docs/i18n/terminology.md](../../../../docs/i18n/terminology.md).
|
||||||
- **A sidecar record of both blob hashes makes consistency checkable.** `foo.i18n.yaml` holds the full git blob hash of each side as of the last confirmed-consistent state. An edit to either side without re-confirming the pair is then mechanically detectable as a pure content comparison — no history lookup — and the hashes are computable for files edited in the same PR, which a commit-hash record is not. Re-recording (`verify-translation-pairing --write`) produces a reviewable yaml diff: confirming consistency is an explicit, visible act in the PR.
|
- **A sidecar record of both blob hashes makes consistency checkable.** `foo.i18n.yaml` holds the full git blob hash of each side as of the last confirmed-consistent state. An edit to either side without re-confirming the pair is then mechanically detectable as a pure content comparison — no history lookup — and the hashes are computable for files edited in the same PR, which a commit-hash record is not. Re-recording (`verify-translation-pairing --write`) produces a reviewable yaml diff: confirming consistency is an explicit, visible act in the PR.
|
||||||
- **`verify-translation-pairing` joins `doc-sync`.** The gate ([scripts/verify-translation-pairing.ts](../../../../scripts/verify-translation-pairing.ts)) enforces: required pairs exist, every existing pair is complete (all three files) and consistent (both hashes match, switcher links both ways, structural signatures identical), excluded (generated or bilingual-by-construction) files stay unpaired, and date-named documents on or after the manifest's `requiredSince` cutoff have complete pairs. The `required` list in [scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) is a ratchet: each merged translation batch adds its files, so coverage only grows.
|
- **`verify-translation-pairing` joins `doc-sync`.** The gate ([scripts/verify-translation-pairing.ts](../../../../scripts/verify-translation-pairing.ts)) enforces: sources selected explicitly, by document class, or by the manifest's `requiredSince` cutoff have complete pairs; every existing pair is complete (all three files) and consistent (both hashes match, switcher links both ways, structural signatures identical); and excluded generated, instruction, or bilingual-by-construction files stay unpaired. The `requiredClasses` set in [scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) closes a translated class for all present and future files instead of relying on an enumerated snapshot.
|
||||||
- **The enforcement frontier advances in coherent review batches.** A related set enters `required` only when reviewers can evaluate it as a unit. The core frontier groups [architecture](../../../../docs/architecture.md), the [Cordis primer](../../../../docs/cordis-primer.md), [defensive patterns](../../../../docs/defensive-patterns.md), the [glossary](../../../../docs/glossary.md), and [testing](../../../../docs/testing.md) because their terminology, links, and contributor contracts inform one another; admitting only part would leave the enforced corpus internally inconsistent. A site-published pair uses `pairedPages()` so the root locale projects `.zh.md` and `/en/` projects `.md`; creating a counterpart alone does not publish it.
|
- **The enforcement frontier advances in coherent review batches, then closes by class.** Explicit `required` entries admit related files while their back-catalog is still being reviewed; after that catalog is complete, its `non-readme` or `readme` class enters `requiredClasses` and can no longer grow a backlog. A site-published pair uses `pairedPages()` so the root locale projects `.zh.md` and `/en/` projects `.md`; creating a counterpart alone does not publish it.
|
||||||
- **Pairing records are metadata, not Cordis Loader configuration.** Cordis configuration discovery accepts actual `.cordis.yml` and `.cordis.yaml` files while excluding `*.i18n.yaml`, even when the document name contains `cordis`. This preserves validation of executable Loader entries without parsing translation hashes as configuration.
|
- **Pairing records are metadata, not Cordis Loader configuration.** Cordis configuration discovery accepts actual `.cordis.yml` and `.cordis.yaml` files while excluding `*.i18n.yaml`, even when the document name contains `cordis`. This preserves validation of executable Loader entries without parsing translation hashes as configuration.
|
||||||
- **Translation is agent work with human review.** The committed workflow is [.agents/skills/dsh-translate-docs](../../../skills/dsh-translate-docs/SKILL.md), following the same pattern as [dsh-code-review](../../../skills/dsh-code-review/SKILL.md): the skill carries the workflow and defers to the docs as sources of truth. The skill directs the orchestrating agent to delegate translation writing to a subagent.
|
- **Translation is agent work with human review.** The committed workflow is [.agents/skills/dsh-translate-docs](../../../skills/dsh-translate-docs/SKILL.md), following the same pattern as [dsh-code-review](../../../skills/dsh-code-review/SKILL.md): the skill carries the workflow and defers to the docs as sources of truth. The skill directs the orchestrating agent to delegate translation writing to a subagent.
|
||||||
|
|
||||||
@@ -40,5 +40,5 @@ Paired sibling files with locale suffixes are the dominant Chinese big-tech conv
|
|||||||
- Every pair adds a third file to the tree. The record is machine-written (`--write`), so the cost is directory noise, not maintenance effort; in exchange, "who confirmed these consistent, and when" is answerable from git blame on the yaml.
|
- Every pair adds a third file to the tree. The record is machine-written (`--write`), so the cost is directory noise, not maintenance effort; in exchange, "who confirmed these consistent, and when" is answerable from git blame on the yaml.
|
||||||
- When the two sides disagree, no mechanical rule picks a winner — the PR review does. That is the price of equal authority, accepted deliberately: the alternative (a canonical language) forbids Chinese-first authoring.
|
- When the two sides disagree, no mechanical rule picks a winner — the PR review does. That is the price of equal authority, accepted deliberately: the alternative (a canonical language) forbids Chinese-first authoring.
|
||||||
- Generated docs (`cordis-catalog/`, `tool-catalog/`, `module-graph.md`) are excluded for now; the planned follow-up is to teach their generators to emit Chinese alongside English, at which point they leave the exclusion list.
|
- Generated docs (`cordis-catalog/`, `tool-catalog/`, `module-graph.md`) are excluded for now; the planned follow-up is to teach their generators to emit Chinese alongside English, at which point they leave the exclusion list.
|
||||||
- Rollout is incremental by design: documents outside `required` are visible backlog (`--list`), not red CI, so pairs land in reviewable batches without a big-bang PR. A date-named document dated on or after the manifest's `requiredSince` cutoff merges bilingual or not at all, so new date-named Agent Notes do not enlarge that backlog.
|
- Rollout remains incremental until a document class is complete: explicit `required` entries and the date cutoff prevent regression during review batches, while a closed class makes every current and future member mandatory. The non-README class is closed, so only the README class can still appear as backlog.
|
||||||
- The recorded hashes double as the update tool (`git cat-file -p <hash>` recovers either side's last-confirmed text for a minimal diff-based update), so re-translation of whole files is never forced by the mechanism.
|
- The recorded hashes double as the update tool (`git cat-file -p <hash>` recovers either side's last-confirmed text for a minimal diff-based update), so re-translation of whole files is never forced by the mechanism.
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ Status: implemented
|
|||||||
|
|
||||||
- **配对兄弟文件,两种语言同权。** 一对文档由三个兄弟文件组成:英文 `foo.md`、中文 `foo.zh.md`,以及一份一致性记录 `foo.i18n.yaml`。没有哪种语言是正典:一篇文档可以先用中文撰写和评审、之后再译成英文,反之亦可;约束配对的是:两侧必须表达相同的内容,且配对整体合并(两种语言加记录,绝不单独落一侧)。政策见 [docs/i18n/README.md](../../../../docs/i18n/README.md);翻译规则见 [docs/i18n/translation-rules.md](../../../../docs/i18n/translation-rules.md);术语真源见 [docs/i18n/terminology.md](../../../../docs/i18n/terminology.md)。
|
- **配对兄弟文件,两种语言同权。** 一对文档由三个兄弟文件组成:英文 `foo.md`、中文 `foo.zh.md`,以及一份一致性记录 `foo.i18n.yaml`。没有哪种语言是正典:一篇文档可以先用中文撰写和评审、之后再译成英文,反之亦可;约束配对的是:两侧必须表达相同的内容,且配对整体合并(两种语言加记录,绝不单独落一侧)。政策见 [docs/i18n/README.md](../../../../docs/i18n/README.md);翻译规则见 [docs/i18n/translation-rules.md](../../../../docs/i18n/translation-rules.md);术语真源见 [docs/i18n/terminology.md](../../../../docs/i18n/terminology.md)。
|
||||||
- **伴随记录保存两侧 blob hash,使一致性可检查。** `foo.i18n.yaml` 保存两侧文件在上一次确认一致时各自的完整 git blob hash。此后修改了任一侧而未重新确认配对,都能被机械检测出来(纯内容比较,无需查询历史),而且同一个 PR(Pull Request)内改动的文件也能计算出 hash,commit hash 式的记录做不到这一点。重新记录(`verify-translation-pairing --write`)会产生一份可评审的 yaml diff:确认一致在 PR 中是一个显式、可见的动作。
|
- **伴随记录保存两侧 blob hash,使一致性可检查。** `foo.i18n.yaml` 保存两侧文件在上一次确认一致时各自的完整 git blob hash。此后修改了任一侧而未重新确认配对,都能被机械检测出来(纯内容比较,无需查询历史),而且同一个 PR(Pull Request)内改动的文件也能计算出 hash,commit hash 式的记录做不到这一点。重新记录(`verify-translation-pairing --write`)会产生一份可评审的 yaml diff:确认一致在 PR 中是一个显式、可见的动作。
|
||||||
- **`verify-translation-pairing` 加入 `doc-sync`。** 门禁([scripts/verify-translation-pairing.ts](../../../../scripts/verify-translation-pairing.ts))强制执行以下规则:required 的配对必须存在;任何已存在的配对必须完整(三个文件齐全)且一致(两个 hash 匹配、切换行双向互链、结构签名一致);被排除的文件(生成物或本身即双语的)不得配对;凡文件名以日期开头且日期不早于 manifest(元数据清单)中 `requiredSince` 分界日期的文档,也必须有完整配对。[scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) 中的 `required` 清单只进不退:每个合并的翻译批次将自己的文件加入其中,覆盖面只增不减。
|
- **`verify-translation-pairing` 加入 `doc-sync`。** 门禁([scripts/verify-translation-pairing.ts](../../../../scripts/verify-translation-pairing.ts))强制执行以下规则:通过显式指定、文档类别或 manifest(元数据清单)的 `requiredSince` 分界日期选中的源文档必须有完整配对;任何已存在的配对必须完整(三个文件齐全)且一致(两个 hash 匹配、切换行双向互链、结构签名一致);被排除的生成文档、指令文档或本身即双语的文档不得配对。[scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) 中的 `requiredClasses` 集合会将已完成翻译的类别纳入强制范围,对其当前及今后所有文件强制执行契约,而不再依赖一份枚举式快照。
|
||||||
- **执行红线按连贯的评审批次推进。** 一组相关文档只有在评审者能够将其作为整体评估时,才进入 `required`。核心红线将[架构](../../../../docs/architecture.md)、[Cordis 入门](../../../../docs/cordis-primer.md)、[防御性模式](../../../../docs/defensive-patterns.md)、[术语表](../../../../docs/glossary.md)和[测试](../../../../docs/testing.md)归为一组,因为它们的术语、链接和贡献者契约相互关联;只纳入其中一部分会使受门禁约束的文档集合内部不一致。发布到文档站的配对使用 `pairedPages()`,由根 locale 投影 `.zh.md`,由 `/en/` 投影 `.md`;仅创建对侧文件并不会发布它。
|
- **执行红线按连贯的评审批次推进,再以类别为单位完成强制覆盖。** 在存量文档仍处于评审阶段时,显式 `required` 条目会纳入相关文件;存量文档全部完成后,其 `non-readme` 或 `readme` 类别进入 `requiredClasses`,不再产生新的 backlog。发布到文档站的配对使用 `pairedPages()`,由根 locale 投影 `.zh.md`,由 `/en/` 投影 `.md`;仅创建对侧文件并不会发布它。
|
||||||
- **配对记录是元数据,而不是 Cordis Loader 配置。** Cordis 配置发现会接受实际的 `.cordis.yml` 和 `.cordis.yaml` 文件,同时排除 `*.i18n.yaml`,即使文档名中包含 `cordis` 也不例外。这样既能继续校验可执行的 Loader 配置项,又不会把翻译 hash 当作配置来解析。
|
- **配对记录是元数据,而不是 Cordis Loader 配置。** Cordis 配置发现会接受实际的 `.cordis.yml` 和 `.cordis.yaml` 文件,同时排除 `*.i18n.yaml`,即使文档名中包含 `cordis` 也不例外。这样既能继续校验可执行的 Loader 配置项,又不会把翻译 hash 当作配置来解析。
|
||||||
- **翻译是 agent 的工作,由人评审。** 仓库内置的工作流是 [.agents/skills/dsh-translate-docs](../../../skills/dsh-translate-docs/SKILL.md),与 [dsh-code-review](../../../skills/dsh-code-review/SKILL.md) 模式相同:skill(技能)承载工作流,并将文档作为真源。该 skill 要求编排 agent 把翻译写作委派给 subagent。
|
- **翻译是 agent 的工作,由人评审。** 仓库内置的工作流是 [.agents/skills/dsh-translate-docs](../../../skills/dsh-translate-docs/SKILL.md),与 [dsh-code-review](../../../skills/dsh-code-review/SKILL.md) 模式相同:skill(技能)承载工作流,并将文档作为真源。该 skill 要求编排 agent 把翻译写作委派给 subagent。
|
||||||
|
|
||||||
@@ -40,5 +40,5 @@ Status: implemented
|
|||||||
- 每个配对给目录树多添一个文件。记录由机器写入(`--write`),代价是目录噪音而非维护负担;换来的是「谁在何时确认过这对文档一致」可以从 yaml 的 git blame 直接回答。
|
- 每个配对给目录树多添一个文件。记录由机器写入(`--write`),代价是目录噪音而非维护负担;换来的是「谁在何时确认过这对文档一致」可以从 yaml 的 git blame 直接回答。
|
||||||
- 两侧说法冲突时,没有机械规则裁决谁赢,由 PR 评审裁决。这是同权的代价,且是有意接受的:另一个选项(正典语言)会禁止中文先行撰写。
|
- 两侧说法冲突时,没有机械规则裁决谁赢,由 PR 评审裁决。这是同权的代价,且是有意接受的:另一个选项(正典语言)会禁止中文先行撰写。
|
||||||
- 生成文档(`cordis-catalog/`、`tool-catalog/`、`module-graph.md`)暂被排除;计划中的后续工作是让生成器在输出英文的同时输出中文,届时将这些文件移出排除清单。
|
- 生成文档(`cordis-catalog/`、`tool-catalog/`、`module-graph.md`)暂被排除;计划中的后续工作是让生成器在输出英文的同时输出中文,届时将这些文件移出排除清单。
|
||||||
- 推进天然是渐进的:`required` 之外的文档是可见的 backlog(待翻清单,`--list`),而非红色的 CI;因此配对按可评审的批次落地,无需一个巨型 PR。凡文件名以日期开头且日期不早于 manifest 中 `requiredSince` 分界日期的文档,都必须配齐双语文件,因此新建的日期命名 Agent Note 不会增加这份 backlog。
|
- 在文档类别全部完成之前,推进仍然是渐进的:显式 `required` 条目与日期分界可在评审批次期间防止回退,已纳入强制范围的类别则将其当前及今后的每个成员都列为必选项。非 README 类别已纳入强制范围,因此只有 README 类别仍可能出现 backlog(待翻清单)。
|
||||||
- 记录的 hash 兼作更新工具(`git cat-file -p <hash>` 能还原任一侧上次确认的文本,用于基于 diff 的最小更新),因此这套机制从不强迫整篇重译。
|
- 记录的 hash 兼作更新工具(`git cat-file -p <hash>` 能还原任一侧上次确认的文本,用于基于 diff 的最小更新),因此这套机制从不强迫整篇重译。
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write
|
# pnpm run verify-translation-pairing --write
|
||||||
README.md: 77d7b3210216c7c12d7d06b1ed16396d02ef1d16
|
README.md: a60572b0691702c949b44b82a7b1d732a888ed93
|
||||||
README.zh.md: de15fc3b5f30c1280ce6b38c1afd2475be7f9671
|
README.zh.md: 04ae032233dfa01c145b5d6bdbe7353a366e11f1
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ This repo's documentation is read by people and agents both inside and outside t
|
|||||||
|
|
||||||
`pnpm run verify-translation-pairing` (part of `doc-sync`, which contributors run locally for documentation changes and CI runs exhaustively) enforces the contract mechanically:
|
`pnpm run verify-translation-pairing` (part of `doc-sync`, which contributors run locally for documentation changes and CI runs exhaustively) enforces the contract mechanically:
|
||||||
|
|
||||||
1. Every file listed as `required` in [scripts/translation-pairing.manifest.json](../../scripts/translation-pairing.manifest.json) has a complete pair.
|
1. Every file listed as `required`, and every document whose class appears in `requiredClasses`, in [scripts/translation-pairing.manifest.json](../../scripts/translation-pairing.manifest.json) has a complete pair. The classes are `non-readme` and `readme`; class matching is case-insensitive on the basename, so `missions/readme.md` is a README.
|
||||||
2. Every pair that exists at all — required or not — 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.
|
2. Every pair that exists at all — required or not — 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.
|
||||||
3. Files listed as `excluded` have no `.zh.md` and no `.i18n.yaml` at all.
|
3. Files listed as `excluded` have no `.zh.md` and no `.i18n.yaml` at all.
|
||||||
4. Every date-named document (`yyyy-mm-dd-*.md`) dated on or after the manifest's `requiredSince` cutoff has a complete pair — new date-named Agent Notes merge bilingual from birth.
|
4. Every date-named document (`yyyy-mm-dd-*.md`) dated on or after the manifest's `requiredSince` cutoff has a complete pair — new date-named Agent Notes merge bilingual from birth.
|
||||||
@@ -41,11 +41,11 @@ The gate's limit, stated plainly: **a green gate means the pair was confirmed co
|
|||||||
**Excluded** (never paired, and the gate rejects a `.zh.md` or `.i18n.yaml` for them):
|
**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`, and `docs/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/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`, and `docs/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` and `.agents/notes/**/AGENTS.md` — agent instructions, maintained in English only like the root `AGENTS.md`.
|
- `docs/AGENTS.md`, `.agents/notes/**/AGENTS.md`, and their `CLAUDE.md` instruction symlinks — agent instructions, maintained in English only like the root `AGENTS.md`.
|
||||||
- `docs/i18n/terminology.md` and [style-samples.md](style-samples.md) — both are bilingual by construction.
|
- `docs/i18n/terminology.md` and [style-samples.md](style-samples.md) — both are bilingual by construction.
|
||||||
- [translation-prompt.md](translation-prompt.md) — the automated pipeline's prompt template; its body is machine-consumed verbatim, so a paired translation would change pipeline behavior.
|
- [translation-prompt.md](translation-prompt.md) — the automated pipeline's prompt template; its body is machine-consumed verbatim, so a paired translation would change pipeline behavior.
|
||||||
|
|
||||||
**Rollout**: a date-named document (`yyyy-mm-dd-*.md`, i.e. an Agent Note) dated on or after the manifest's `requiredSince` cutoff must merge with its pair. Earlier dates are backlog, including files created on the cutoff's eve. An Agent Note filename records its first-proposed date, so backdating past the cutoff is a review-visible violation. The manifest's `required` list is the current enforcement frontier, not the goal of full coverage. Translation batches add paths to `required`, ratcheting the gate forward. Unlisted documents remain visible in `--list`, while every existing pair is governed by the full contract. Because later edits must update both sides, expand `required` only as fast as translation review can support.
|
**Enforcement frontier**: `requiredClasses` closes a whole document class after its back-catalog has been translated. `non-readme` is closed: every current or future in-scope non-README document must merge bilingual. README coverage remains an explicit-file rollout until `readme` joins the closed set. The manifest's `required` list retains already-admitted files, and a date-named document (`yyyy-mm-dd-*.md`, i.e. an Agent Note) dated on or after `requiredSince` must merge with its pair regardless of class. `--list` reports any unclosed-class backlog while every existing pair remains governed by the full contract.
|
||||||
|
|
||||||
## Division of labor
|
## Division of labor
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
`pnpm run verify-translation-pairing`(`doc-sync`(文档同步门禁)的一环,贡献者会针对文档变更在本地运行,CI 则会完整运行)机械地强制执行这份契约:
|
`pnpm run verify-translation-pairing`(`doc-sync`(文档同步门禁)的一环,贡献者会针对文档变更在本地运行,CI 则会完整运行)机械地强制执行这份契约:
|
||||||
|
|
||||||
1. [scripts/translation-pairing.manifest.json](../../scripts/translation-pairing.manifest.json) 中 `required` 列出的每个文件都有完整配对。
|
1. [scripts/translation-pairing.manifest.json](../../scripts/translation-pairing.manifest.json) 中 `required` 列出的每个文件,以及所属文档类别出现在 `requiredClasses` 中的每篇文档,都有完整配对。类别分为 `non-readme` 和 `readme`;判断类别时,basename 不区分大小写,因此 `missions/readme.md` 也属于 README。
|
||||||
2. 任何已存在的配对(无论是否 required)都完整且一致:三个文件齐全、每一侧的当前 blob hash 等于记录值(改了任一侧而没重新确认配对就变红)、双方都带语言切换行、结构签名按序一致:标题深度、逐字节一致的代码块(信息字符串与内容)、表格行列数、列表类型、有序列表起始编号、列表项数量,以及除切换行之外的每个链接目标。
|
2. 任何已存在的配对(无论是否 required)都完整且一致:三个文件齐全、每一侧的当前 blob hash 等于记录值(改了任一侧而没重新确认配对就变红)、双方都带语言切换行、结构签名按序一致:标题深度、逐字节一致的代码块(信息字符串与内容)、表格行列数、列表类型、有序列表起始编号、列表项数量,以及除切换行之外的每个链接目标。
|
||||||
3. 列为 `excluded` 的文件完全没有 `.zh.md`,也没有 `.i18n.yaml`。
|
3. 列为 `excluded` 的文件完全没有 `.zh.md`,也没有 `.i18n.yaml`。
|
||||||
4. 凡文件名符合 `yyyy-mm-dd-*.md` 且日期不早于 manifest(元数据清单)中 `requiredSince` 分界日期的文档,都必须有完整配对;新建的日期命名 Agent Note 从创建起便须配齐中英文。
|
4. 凡文件名符合 `yyyy-mm-dd-*.md` 且日期不早于 manifest(元数据清单)中 `requiredSince` 分界日期的文档,都必须有完整配对;新建的日期命名 Agent Note 从创建起便须配齐中英文。
|
||||||
@@ -41,11 +41,11 @@
|
|||||||
**排除**(永不配对,门禁拒绝为它们建 `.zh.md` 或 `.i18n.yaml`):
|
**排除**(永不配对,门禁拒绝为它们建 `.zh.md` 或 `.i18n.yaml`):
|
||||||
|
|
||||||
- `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` 与 `docs/tool-execution-pipeline.md`:生成文件;生成器目前只输出英文,手写译文在每次重新生成时必然陈旧。计划中的后续工作是让生成器同时输出中文,届时这些文件移出排除清单。
|
- `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` 与 `docs/tool-execution-pipeline.md`:生成文件;生成器目前只输出英文,手写译文在每次重新生成时必然陈旧。计划中的后续工作是让生成器同时输出中文,届时这些文件移出排除清单。
|
||||||
- `docs/AGENTS.md` 与 `.agents/notes/**/AGENTS.md`:agent 指令,与根 `AGENTS.md` 一样只以英文维护。
|
- `docs/AGENTS.md`、`.agents/notes/**/AGENTS.md` 以及指向它们的 `CLAUDE.md` 指令符号链接:agent 指令,与根 `AGENTS.md` 一样只以英文维护。
|
||||||
- `docs/i18n/terminology.md` 与 [style-samples.md](style-samples.md):二者本身即为中英对照文档。
|
- `docs/i18n/terminology.md` 与 [style-samples.md](style-samples.md):二者本身即为中英对照文档。
|
||||||
- [translation-prompt.md](translation-prompt.md):自动翻译流水线的提示词模板;正文逐字进入模型请求,配对翻译会改变流水线行为。
|
- [translation-prompt.md](translation-prompt.md):自动翻译流水线的提示词模板;正文逐字进入模型请求,配对翻译会改变流水线行为。
|
||||||
|
|
||||||
**推进**:以日期命名的文档(`yyyy-mm-dd-*.md`,即 Agent Note),只要标注日期等于或晚于 manifest 的 `requiredSince` 分界日期,合并时就必须配齐双语文件。更早日期的文件属于 backlog(待翻清单),包括分界前夜创建的文件。Agent Note 文件名记录首次提出日期,因此倒填日期绕过分界属于评审可见的违规。manifest 中的 `required` 列表是当前执行红线,并非全量覆盖这一最终目标。翻译批次将路径加入 `required`,使门禁只向前收紧。未列入的文档仍可通过 `--list` 查看,而任何已存在的配对都受完整契约约束。后续修改必须同步更新两侧,因此 `required` 的扩展速度不能超过翻译评审的承载能力。
|
**执行红线**:某个文档类别的存量文档全部翻译完成后,`requiredClasses` 会将整个类别纳入强制范围。`non-readme` 已纳入强制范围:当前及今后所有纳入范围的非 README 文档,合并时都必须配齐双语文件。README 覆盖仍按显式文件逐步推进,直到 `readme` 加入这一强制范围。manifest 的 `required` 列表保留已纳入的文件;以日期命名的文档(`yyyy-mm-dd-*.md`,即 Agent Note)只要日期不早于 `requiredSince`,就无论所属类别都必须与对侧文件一同合并。`--list` 会报告尚未纳入强制范围的类别中的任何 backlog(待翻清单),而每个已存在的配对仍受完整契约约束。
|
||||||
|
|
||||||
## 分工
|
## 分工
|
||||||
|
|
||||||
|
|||||||
@@ -70,9 +70,9 @@
|
|||||||
|
|
||||||
## ⑦ 推进策略(长段拆分示范)
|
## ⑦ 推进策略(长段拆分示范)
|
||||||
|
|
||||||
> **Rollout**: date-named Agent Notes don't wait for a batch — one dated on or after the manifest's `requiredSince` cutoff must merge with its pair, so each new date-named Agent Note is bilingual from birth. For the back-catalog, the `required` list in the manifest is the enforcement frontier, not the goal. […] Pairing a document is a commitment: every later edit to either side must carry the counterpart along, so grow the frontier at the pace translation review is actually resourced, not ahead of it.
|
> **Enforcement frontier**: a document class enters the manifest's `requiredClasses` set only after its back-catalog has been translated and reviewed. The `non-readme` class is closed, so every current or future in-scope non-README document must merge bilingual; README coverage remains an explicit-file rollout until that class is ready to close. […] Pairing a document is a commitment: every later edit to either side must carry the counterpart along, so close a class only when translation review can sustain it.
|
||||||
|
|
||||||
**推进**:日期命名的 Agent Note 无需等待批量翻译。只要文件名中的日期不早于 manifest(元数据清单)的 `requiredSince` 分界日期,合入时就必须配齐中英文,因此此类 Agent Note 从创建起就要求双语齐备。对于存量文档,manifest 中的 `required` 列表只是当前的执行红线,并非最终目标。(……)一旦文档完成配对,后续修改任一侧都必须同步更新另一侧。因此,应根据实际可投入的翻译评审能力逐步扩展执行红线,不能超前。
|
**执行红线**:只有在某个文档类别的存量文档全部完成翻译和评审后,该类别才会进入 manifest(元数据清单)的 `requiredClasses` 集合。`non-readme` 类别已纳入强制范围,因此当前及今后所有纳入范围的非 README 文档,合入时都必须配齐双语文件;README 覆盖仍按显式文件逐步推进,直到该类别具备整体纳入强制范围的条件。(……)一旦文档完成配对,后续修改任一侧都必须同步更新另一侧。因此,只有在翻译评审能力足以持续支撑时,才应将整个类别纳入强制范围。
|
||||||
|
|
||||||
## 从样例提炼的要点
|
## 从样例提炼的要点
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,4 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"requiredClasses": [
|
||||||
|
"non-readme"
|
||||||
|
],
|
||||||
"requiredSince": "2026-07-14",
|
"requiredSince": "2026-07-14",
|
||||||
"required": [
|
"required": [
|
||||||
".agents/notes/README.md",
|
".agents/notes/README.md",
|
||||||
@@ -215,6 +218,7 @@
|
|||||||
"excluded": [
|
"excluded": [
|
||||||
".agents/notes/AGENTS.md",
|
".agents/notes/AGENTS.md",
|
||||||
".agents/notes/implemented/AGENTS.md",
|
".agents/notes/implemented/AGENTS.md",
|
||||||
|
".agents/notes/implemented/CLAUDE.md",
|
||||||
"docs/AGENTS.md",
|
"docs/AGENTS.md",
|
||||||
"docs/agent-lifecycle.md",
|
"docs/agent-lifecycle.md",
|
||||||
"docs/capability-seams.md",
|
"docs/capability-seams.md",
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import {
|
|||||||
parseTranslationMarkdown,
|
parseTranslationMarkdown,
|
||||||
parseTranslationPairingManifest,
|
parseTranslationPairingManifest,
|
||||||
requiresPairByDate,
|
requiresPairByDate,
|
||||||
|
requiresTranslationPair,
|
||||||
|
translationDocumentClass,
|
||||||
translationStructureDiff,
|
translationStructureDiff,
|
||||||
translationStructureSignature,
|
translationStructureSignature,
|
||||||
} from './translation-pairing.ts'
|
} from './translation-pairing.ts'
|
||||||
@@ -20,10 +22,12 @@ describe('translation pairing manifest', () => {
|
|||||||
expect(parseTranslationPairingManifest(JSON.stringify({
|
expect(parseTranslationPairingManifest(JSON.stringify({
|
||||||
requiredSince: '2026-07-14',
|
requiredSince: '2026-07-14',
|
||||||
required: ['README.md'],
|
required: ['README.md'],
|
||||||
|
requiredClasses: ['non-readme'],
|
||||||
excluded: ['docs/generated/'],
|
excluded: ['docs/generated/'],
|
||||||
}))).toEqual({
|
}))).toEqual({
|
||||||
requiredSince: '2026-07-14',
|
requiredSince: '2026-07-14',
|
||||||
required: ['README.md'],
|
required: ['README.md'],
|
||||||
|
requiredClasses: ['non-readme'],
|
||||||
excluded: ['docs/generated/'],
|
excluded: ['docs/generated/'],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -33,6 +37,7 @@ describe('translation pairing manifest', () => {
|
|||||||
expect(() => parseTranslationPairingManifest(JSON.stringify({
|
expect(() => parseTranslationPairingManifest(JSON.stringify({
|
||||||
requiredSince: cutoff,
|
requiredSince: cutoff,
|
||||||
required: [],
|
required: [],
|
||||||
|
requiredClasses: [],
|
||||||
excluded: [],
|
excluded: [],
|
||||||
}))).toThrow('requiredSince must be a valid YYYY-MM-DD date')
|
}))).toThrow('requiredSince must be a valid YYYY-MM-DD date')
|
||||||
})
|
})
|
||||||
@@ -41,9 +46,42 @@ describe('translation pairing manifest', () => {
|
|||||||
expect(() => parseTranslationPairingManifest(JSON.stringify({
|
expect(() => parseTranslationPairingManifest(JSON.stringify({
|
||||||
requiredSince: '2026-07-14',
|
requiredSince: '2026-07-14',
|
||||||
required: [42],
|
required: [42],
|
||||||
|
requiredClasses: [],
|
||||||
excluded: [],
|
excluded: [],
|
||||||
}))).toThrow('required must be an array of strings')
|
}))).toThrow('required must be an array of strings')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('rejects unknown and duplicate document classes', () => {
|
||||||
|
const manifest = (requiredClasses: string[]) => JSON.stringify({
|
||||||
|
requiredSince: '2026-07-14',
|
||||||
|
required: [],
|
||||||
|
requiredClasses,
|
||||||
|
excluded: [],
|
||||||
|
})
|
||||||
|
expect(() => parseTranslationPairingManifest(manifest(['guide']))).toThrow('requiredClasses must contain only')
|
||||||
|
expect(() => parseTranslationPairingManifest(manifest(['readme', 'readme']))).toThrow('requiredClasses must not contain duplicates')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('document-class pairing frontier', () => {
|
||||||
|
const manifest = parseTranslationPairingManifest(JSON.stringify({
|
||||||
|
requiredSince: '2026-07-14',
|
||||||
|
required: ['docs/legacy/README.md'],
|
||||||
|
requiredClasses: ['non-readme'],
|
||||||
|
excluded: [],
|
||||||
|
}))
|
||||||
|
|
||||||
|
it('classifies README basenames case-insensitively', () => {
|
||||||
|
expect(translationDocumentClass('packages/core/README.md')).toBe('readme')
|
||||||
|
expect(translationDocumentClass('missions/readme.md')).toBe('readme')
|
||||||
|
expect(translationDocumentClass('docs/readme-guide.md')).toBe('non-readme')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('requires every non-README while retaining explicit README entries', () => {
|
||||||
|
expect(requiresTranslationPair('docs/guide.md', manifest)).toBe(true)
|
||||||
|
expect(requiresTranslationPair('docs/legacy/README.md', manifest)).toBe(true)
|
||||||
|
expect(requiresTranslationPair('docs/new/README.md', manifest)).toBe(false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('date-based pairing frontier', () => {
|
describe('date-based pairing frontier', () => {
|
||||||
|
|||||||
@@ -12,11 +12,18 @@ import type { Nodes } from 'mdast'
|
|||||||
/** Validated shape of `scripts/translation-pairing.manifest.json`. */
|
/** Validated shape of `scripts/translation-pairing.manifest.json`. */
|
||||||
export interface TranslationPairingManifest {
|
export interface TranslationPairingManifest {
|
||||||
required: string[]
|
required: string[]
|
||||||
|
/** Document classes whose complete in-scope population must be paired. */
|
||||||
|
requiredClasses: TranslationDocumentClass[]
|
||||||
excluded: string[]
|
excluded: string[]
|
||||||
/** Date-named documents on or after this day must merge bilingual. */
|
/** Date-named documents on or after this day must merge bilingual. */
|
||||||
requiredSince: string
|
requiredSince: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Stable classes used to close one translation rollout without enumerating files. */
|
||||||
|
export type TranslationDocumentClass = 'readme' | 'non-readme'
|
||||||
|
|
||||||
|
const TRANSLATION_DOCUMENT_CLASSES: TranslationDocumentClass[] = ['readme', 'non-readme']
|
||||||
|
|
||||||
const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/
|
const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/
|
||||||
const DATED_DOCUMENT = /(?:^|\/)(\d{4}-\d{2}-\d{2})-[^/]*\.md$/
|
const DATED_DOCUMENT = /(?:^|\/)(\d{4}-\d{2}-\d{2})-[^/]*\.md$/
|
||||||
|
|
||||||
@@ -40,6 +47,19 @@ function stringArrayField(record: Record<string, unknown>, field: 'required' | '
|
|||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Read and validate the manifest's closed document-class set. */
|
||||||
|
function requiredClassesField(record: Record<string, unknown>): TranslationDocumentClass[] {
|
||||||
|
const value = record.requiredClasses
|
||||||
|
if (!Array.isArray(value) || !value.every((entry): entry is TranslationDocumentClass =>
|
||||||
|
typeof entry === 'string' && TRANSLATION_DOCUMENT_CLASSES.includes(entry as TranslationDocumentClass))) {
|
||||||
|
throw new Error('translation-pairing.manifest.json: requiredClasses must contain only "readme" and "non-readme"')
|
||||||
|
}
|
||||||
|
if (new Set(value).size !== value.length) {
|
||||||
|
throw new Error('translation-pairing.manifest.json: requiredClasses must not contain duplicates')
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
/** Parse and validate the checked-in bilingual manifest. */
|
/** Parse and validate the checked-in bilingual manifest. */
|
||||||
export function parseTranslationPairingManifest(content: string): TranslationPairingManifest {
|
export function parseTranslationPairingManifest(content: string): TranslationPairingManifest {
|
||||||
const value: unknown = JSON.parse(content)
|
const value: unknown = JSON.parse(content)
|
||||||
@@ -53,11 +73,24 @@ export function parseTranslationPairingManifest(content: string): TranslationPai
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
required: stringArrayField(record, 'required'),
|
required: stringArrayField(record, 'required'),
|
||||||
|
requiredClasses: requiredClassesField(record),
|
||||||
excluded: stringArrayField(record, 'excluded'),
|
excluded: stringArrayField(record, 'excluded'),
|
||||||
requiredSince,
|
requiredSince,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Classify a Markdown source by whether its basename is README, case-insensitively. */
|
||||||
|
export function translationDocumentClass(file: string): TranslationDocumentClass {
|
||||||
|
return /(?:^|\/)readme\.md$/i.test(file) ? 'readme' : 'non-readme'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Whether the manifest requires this in-scope source to have a complete pair. */
|
||||||
|
export function requiresTranslationPair(file: string, manifest: TranslationPairingManifest): boolean {
|
||||||
|
return manifest.required.includes(file)
|
||||||
|
|| manifest.requiredClasses.includes(translationDocumentClass(file))
|
||||||
|
|| requiresPairByDate(file, manifest.requiredSince)
|
||||||
|
}
|
||||||
|
|
||||||
/** Return the leading date of a `yyyy-mm-dd-*.md` basename, if present. */
|
/** Return the leading date of a `yyyy-mm-dd-*.md` basename, if present. */
|
||||||
export function datedDocumentDate(file: string): string | undefined {
|
export function datedDocumentDate(file: string): string | undefined {
|
||||||
return DATED_DOCUMENT.exec(file)?.[1]
|
return DATED_DOCUMENT.exec(file)?.[1]
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* Enforce complete English/Chinese pairs, matching structure, and recorded git
|
* Enforce complete English/Chinese pairs, matching structure, and recorded git
|
||||||
* blob hashes under the bilingual manifest. Required files and date-named docs
|
* blob hashes under the bilingual manifest. Required files and date-named docs
|
||||||
* at or after `requiredSince` must be paired; excluded docs may have neither a
|
* at or after `requiredSince`, plus every source in a required document class,
|
||||||
* counterpart nor sidecar. `--list` reports state and `--write` records both
|
* must be paired; excluded docs may have neither a counterpart nor sidecar.
|
||||||
* sides after human review. Translation quality remains a review responsibility.
|
* `--list` reports state and `--write` records both sides after human review.
|
||||||
|
* Translation quality remains a review responsibility.
|
||||||
* See `docs/i18n/README.md` for the owning contract.
|
* See `docs/i18n/README.md` for the owning contract.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -11,11 +12,11 @@ import { createHash } from 'node:crypto'
|
|||||||
import { existsSync, globSync, readFileSync, writeFileSync } from 'node:fs'
|
import { existsSync, globSync, readFileSync, writeFileSync } from 'node:fs'
|
||||||
import { basename, join, resolve, sep } from 'node:path'
|
import { basename, join, resolve, sep } from 'node:path'
|
||||||
import {
|
import {
|
||||||
datedDocumentDate,
|
|
||||||
linksTo,
|
linksTo,
|
||||||
parseTranslationMarkdown,
|
parseTranslationMarkdown,
|
||||||
parseTranslationPairingManifest,
|
parseTranslationPairingManifest,
|
||||||
requiresPairByDate,
|
requiresTranslationPair,
|
||||||
|
translationDocumentClass,
|
||||||
translationStructureDiff,
|
translationStructureDiff,
|
||||||
translationStructureSignature,
|
translationStructureSignature,
|
||||||
} from './translation-pairing.ts'
|
} from './translation-pairing.ts'
|
||||||
@@ -118,29 +119,21 @@ if (writeMode) {
|
|||||||
const errors: string[] = []
|
const errors: string[] = []
|
||||||
const state = new Map<string, 'ok' | 'out-of-sync' | 'missing'>()
|
const state = new Map<string, 'ok' | 'out-of-sync' | 'missing'>()
|
||||||
|
|
||||||
// 1. Required pairs exist.
|
// 1. Explicit manifest entries name existing source documents.
|
||||||
for (const req of manifest.required) {
|
for (const req of manifest.required) {
|
||||||
if (!existsSync(join(root, req))) {
|
if (!existsSync(join(root, req))) {
|
||||||
errors.push(`${req}: listed in translation-pairing.manifest.json \`required\` but the file does not exist`)
|
errors.push(`${req}: listed in translation-pairing.manifest.json \`required\` but the file does not exist`)
|
||||||
continue
|
|
||||||
}
|
|
||||||
const { zh } = pairPaths(req)
|
|
||||||
if (!existsSync(join(root, zh))) {
|
|
||||||
errors.push(`${req}: required to have a translation, but ${zh} does not exist`)
|
|
||||||
state.set(req, 'missing')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Date-named documents (Agent Notes) dated on/after the requiredSince cutoff merge
|
// 2. Every source selected explicitly, by document class, or by the dated-document
|
||||||
// bilingual: a new Agent Note lands with its pair or not at all. Deterministic from
|
// cutoff merges bilingual. Class enforcement closes a rollout for future files too.
|
||||||
// the filename alone — no git history, so it holds on shallow CI checkouts.
|
|
||||||
for (const source of sources) {
|
for (const source of sources) {
|
||||||
if (isExcluded(source)) continue
|
if (isExcluded(source)) continue
|
||||||
const date = datedDocumentDate(source)
|
if (!requiresTranslationPair(source, manifest)) continue
|
||||||
if (!requiresPairByDate(source, manifest.requiredSince) || date === undefined) continue
|
|
||||||
const { zh } = pairPaths(source)
|
const { zh } = pairPaths(source)
|
||||||
if (!existsSync(join(root, zh))) {
|
if (!existsSync(join(root, zh))) {
|
||||||
errors.push(`${source}: dated ${date} — documents dated on/after ${manifest.requiredSince} merge bilingual (docs/i18n/README.md); add the counterpart and record the pair`)
|
errors.push(`${source}: required to merge bilingual as a ${translationDocumentClass(source)} document (docs/i18n/README.md); add the counterpart and record the pair`)
|
||||||
state.set(source, 'missing')
|
state.set(source, 'missing')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -214,8 +207,8 @@ if (listMode) {
|
|||||||
const order = { 'out-of-sync': 0, missing: 1, ok: 2 } as const
|
const order = { 'out-of-sync': 0, missing: 1, ok: 2 } as const
|
||||||
const rows = [...state.entries()].sort((a, b) => order[a[1]] - order[b[1]] || a[0].localeCompare(b[0]))
|
const rows = [...state.entries()].sort((a, b) => order[a[1]] - order[b[1]] || a[0].localeCompare(b[0]))
|
||||||
for (const [file, status] of rows) {
|
for (const [file, status] of rows) {
|
||||||
const required = manifest.required.includes(file)
|
const required = requiresTranslationPair(file, manifest)
|
||||||
const tag = required ? ' (required)' : requiresPairByDate(file, manifest.requiredSince) ? ' (required by date)' : ' (backlog)'
|
const tag = required ? ` (required ${translationDocumentClass(file)})` : ' (backlog)'
|
||||||
console.log(`${status.padEnd(11)} ${file}${status === 'missing' ? tag : ''}`)
|
console.log(`${status.padEnd(11)} ${file}${status === 'missing' ? tag : ''}`)
|
||||||
}
|
}
|
||||||
const counts = { 'ok': 0, 'out-of-sync': 0, 'missing': 0 }
|
const counts = { 'ok': 0, 'out-of-sync': 0, 'missing': 0 }
|
||||||
@@ -225,7 +218,7 @@ if (listMode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (errors.length === 0) {
|
if (errors.length === 0) {
|
||||||
console.log(`verify-translation-pairing: ${pairAnchors.size} pair(s) checked against ${manifest.required.length} required, all consistent.`)
|
console.log(`verify-translation-pairing: ${pairAnchors.size} pair(s) checked against ${manifest.required.length} explicit requirements and required classes [${manifest.requiredClasses.join(', ')}], all consistent.`)
|
||||||
process.exit(0)
|
process.exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user