baldart 4.51.0 → 4.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,35 @@ All notable changes to BALDART will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [4.52.0] - 2026-06-18
9
+
10
+ **BALDART becomes opinionated about internationalization: a new opt-in `features.has_i18n` layer makes consumers develop multi-language by default — zero hardcoded labels + a stack-agnostic context registry that makes LLM translation context-aware instead of blind.** The differentiator vs ordinary i18n is the **per-key context**: a stack-agnostic YAML registry (`paths.i18n_registry`, default `docs/i18n/registry.yml`) holds `source` + a *hyper-brief usage context* + `domain` for every label, so the translator (the new `i18n-translator` agent, or a delegated bulk backend) sees *what the label is for and where it appears* — the 2026 best-practice fix for "the void of context", the #1 translation-quality threat. **Translations live in the stack's NATIVE locale files** (i18next / next-intl / react-intl / lingui / vue-i18n); BALDART wraps the consumer's framework + best-in-class OSS rather than reimplementing i18n. The anti-hardcoded gate is a **deterministic, BALDART-owned standalone ESLint config** (`eslint.i18n.config.mjs` running `eslint-plugin-i18next` `no-literal-string` — the most-adopted at ~550k weekly downloads; framework-agnostic so it covers next-intl/vue too) written + installed by `configure`/`doctor` (`src/utils/i18n-gate.js`). It is a **dedicated ESLint run independent of the project's main lint** (so it works even on a **Biome-only toolchain**, which does not execute ESLint plugins) and **never touches the user's config**. The gate runs via `i18n.lint_command` in `qa-sentinel` (`/new`, `/qa`), the `new2` Phase-2 gate, and the `i18n-align` routine; `coder` STEP 9 and `code-reviewer` enforce the same rule at the agent level (real layers, not hollow — they do not assume the linter is wired). Key extraction reuses the stack's extractor (`i18next-cli`/`lingui`/`formatjs`, detection-only). Drift is contained in **3 tiers** mirroring the design-system discipline: per-task (`coder` STEP 9 populates the registry stub), per-merge (`doc-reviewer` curates context/orphans in `/new`), weekly (`i18n-align` routine translates the maintained `i18n.target_languages`, lints, and commits **directly to the trunk — no PR**, since translation files are low-risk). The always-on HARD rule ("never hardcode a user-facing string") lives in `framework/AGENTS.md` § 4.b gated on the flag. Strict specialization throughout: `coder` externalizes + populates, `doc-reviewer` curates, `i18n-translator` (`model: sonnet`, `effort: low`, bounded flag-not-guess) translates — none crosses lanes. **MINOR** (additive, opt-in: new agent + two new skills (`/i18n`, `/i18n-adopt`) + new routine + new flag/`i18n:` block + `paths.i18n_registry`; no removed surface). Schema-change propagation rule applies (`features.has_i18n` + `paths.i18n_registry` + `i18n.*` through template + configure + update detector + doctor + CHANGELOG).
11
+
12
+ ### Added
13
+
14
+ - **`framework/agents/i18n-protocol.md`** — the textual SSOT: authority model (code/native files own keys+translations; registry owns context/domain), registry schema, naming convention + ICU/no-concatenation, the BLOCKING anti-hardcoded cascade, drift codes (`I18N_REGISTRY_DRIFT` / `I18N_KEY_ORPHANED` / `I18N_CONTEXT_MISSING`), and strict ownership. Registered in `framework/agents/index.md`.
15
+ - **`framework/.claude/agents/i18n-translator.md`** — new context-aware translator agent (`model: sonnet`, `effort: low`): translates into native locale files using registry context, preserves ICU/placeholders, and flags anomalies (`needs-attention`) instead of guessing. Added to `REGISTRY.md`.
16
+ - **`framework/.claude/skills/i18n/SKILL.md`** — new `/i18n` skill (portable Claude + Codex): `audit` (registry ↔ `t()` ↔ native files drift report) and `translate` (spawns `i18n-translator` with review gate). Gated on `features.has_i18n`.
17
+ - **`framework/.claude/skills/i18n-adopt/SKILL.md`** — new `/i18n-adopt` skill (`effort: medium` — the orchestration is light; the spawned `coder` keeps its Opus model since it mutates production code, `i18n-translator`/`doc-reviewer` are Sonnet): one-shot migration that adopts the i18n layer on an EXISTING codebase — the gate linter finds every hardcoded user-facing string, `coder` replaces each with `t()` + registers key/context (STEP 9), `doc-reviewer` curates, `i18n-translator` translates the target languages, the gate + build verify. Full-auto on a dedicated branch `chore/i18n-adopt-*` (no mid-run pauses), idempotent + resumable via `.baldart/i18n-adopt/progress.json`, never auto-merges. The i18n analogue of `/design-system-init`.
18
+ - **`framework/routines/i18n-align.routine.yml`** — new 8th routine (weekly): translates the maintained target languages, lints, commits direct to trunk. Registered in `framework/routines/index.yml`. Optional (skips when feature off / no target languages).
19
+ - **`framework/docs/I18N-LAYER.md`** — operator guide (why / moving parts / lifecycle / invariants / fallback).
20
+ - **`baldart.config.template.yml`** — `features.has_i18n`, `paths.i18n_registry`, and the `i18n:` block (`framework`, `source_language`, `target_languages`, `locales_root`, `extract_command`, `bulk_translator`, `lint_command`).
21
+ - **`src/utils/i18n-gate.js`** — the standalone anti-hardcoded gate util: writes `eslint.i18n.config.mjs` (only when absent), installs `eslint` + `eslint-plugin-i18next` + `typescript-eslint`, and resolves the gate command (explicit `i18n.lint_command` → standalone config by convention → SKIP).
22
+
23
+ ### Changed
24
+
25
+ - **`src/commands/configure.js`** — autodetects the i18n framework from `package.json`, prompts the `has_i18n` flag + the `i18n:` block (source/target languages, locales root), populates `extract_command` defaults, sets `i18n.lint_command`, offers the standalone gate setup (`I18nGate.setup`), and points to `npx baldart routines install i18n-align`.
26
+ - **`src/commands/update.js`** — update detector diffs the `i18n:` block (`missingI18n`) alongside `graph:` / `toolchain:`.
27
+ - **`src/commands/doctor.js`** — detection-only i18n backfill: `i18n-gate-setup` (installs the gate deps + writes `eslint.i18n.config.mjs`) and `i18n-registry-missing` (registry absent); never installs the consumer's i18n framework.
28
+ - **`framework/.claude/agents/qa-sentinel.md`** + **`framework/.claude/workflows/new2.js`** — run the i18n anti-hardcoded gate (resolve `i18n.lint_command` → standalone config → SKIP) in addition to the normal lint gate when `features.has_i18n: true`.
29
+ - **`framework/.claude/agents/coder.md`** — STEP 9 (i18n registry population, gated on `features.has_i18n`): externalize strings + write the registry stub; enforcement of "no hardcode" is delegated to the linter (no prompt duplication).
30
+ - **`framework/.claude/agents/code-reviewer.md`** — i18n anti-hardcoded backstop (syntactically scoped HIGH finding for the residue the linter can't see).
31
+ - **`framework/.claude/agents/doc-reviewer.md`** — Internationalization Scope: owns registry curation (context completeness, dedup, orphan/missing), does not translate.
32
+ - **`framework/AGENTS.md`** — one gated MUST in § 4.b (no hardcoded user-facing strings when `has_i18n: true`); gating table row.
33
+ - **`framework/.claude/skills/prd/SKILL.md`** — HARD RULE 20 (cards with UI copy declare label keys + context).
34
+ - **`framework/.claude/skills/new/references/implement.md`** — coder mission briefing gains an i18n-constraint section (ambient flag, no orchestrator handoff).
35
+ - **`README.md` / `CLAUDE.md`** — counts (29 agents, 34 skills, 8 routines), inventory, and a new feature section / convention bullet.
36
+
8
37
  ## [4.51.0] - 2026-06-17
9
38
 
10
39
  **A second, more aggressive output style — `Terse Ultra` (caveman "ultra") — ships alongside `Terse` as an opt-in maximum-compression profile.** Research into the [caveman](https://github.com/JuliusBrussee/caveman) tiers (lite → full → ultra → wenyan) + its issues/reviews established the frontier: the community consensus is that `full` (≈ our `Terse`) is the sweet spot, `ultra` buys more compression at a small-but-real cost (it "occasionally drops important caveats" and is "a bit much when you actually need to understand what went wrong"), and `wenyan` (classical Chinese, −80/90%) is out of scope for a general dev framework. So `Terse Ultra` is shipped as a SECOND style, not a replacement: fragments, prose-word abbreviations (db/auth/config/req/res/fn/impl — **prose words only**), arrows for causality (`X → Y`), with HARDENED guardrails — code symbols / function & API identifiers / file paths / `path:line` / error strings / CLI commands / commit keywords stay **verbatim even in ultra**, and the Auto-clarity carve-out is **extended to the final answer/explanation** (per caveman issue #510) on top of the security / irreversible-action / multi-step / ambiguity cases. `baldart configure` still offers only `Terse` (the recommended default); `Terse Ultra` is discovered and activated manually via `/config` → Output Style. Distribution is free — the v4.50.0 merge picks up any new `.md` under `framework/.claude/output-styles/` automatically. **MINOR** (additive payload — a new selectable style; **no new `baldart.config.yml` key** — `outputStyle` is a native Claude Code settings key; no removed surface).
package/README.md CHANGED
@@ -82,10 +82,10 @@ No additional activation steps needed — once installed, Claude Code (and Codex
82
82
  ### Core Protocol
83
83
 
84
84
  - **AGENTS.md**: Mandatory coordination rules (MUST/SHOULD/OPTIONAL)
85
- - **agents/**: 23 domain modules (architecture, workflows, testing, security, card-schema, etc.)
85
+ - **agents/**: 24 domain modules (architecture, workflows, testing, security, card-schema, i18n-protocol, etc.)
86
86
  - **Routing**: If you touch X, read Y - minimize context loading
87
87
 
88
- ### AI Agents (28 specialized agents)
88
+ ### AI Agents (29 specialized agents)
89
89
 
90
90
  **Core (required for every project)**
91
91
  1. **codebase-architect**: MANDATORY before planning/implementation - understands codebase structure
@@ -121,6 +121,7 @@ No additional activation steps needed — once installed, Claude Code (and Codex
121
121
  25. **legal-counsel-gdpr**: GDPR compliance, privacy policies, data governance
122
122
  26. **deep-human-insight**: Psychological / sociological analysis for B2C UX and adoption
123
123
  27. **skill-improver**: Weekly auto-improvement of skills/agents based on review/QA findings
124
+ 28. **i18n-translator** (v4.52.0): Context-aware label translation into native locale files (Sonnet, low-effort, flag-not-guess) — invoked by `/i18n` + the `i18n-align` routine
124
125
 
125
126
  REGISTRY.md is the single source of truth for agent routing and capabilities.
126
127
 
@@ -208,7 +209,7 @@ Skills always-ask when required keys are missing — never silently default.
208
209
  never overwrites your file. Full guide:
209
210
  [`framework/docs/PROJECT-CONFIGURATION.md`](framework/docs/PROJECT-CONFIGURATION.md).
210
211
 
211
- ### Skills (33 portable skills)
212
+ ### Skills (35 portable skills)
212
213
 
213
214
  Skills live under `.claude/skills/` and are auto-discovered by Claude Code.
214
215
  Bundled skills:
@@ -218,6 +219,7 @@ Bundled skills:
218
219
  - **Design**: `frontend-design`, `ui-design`, `motion-design`, `gamification-design`, `design-system-init` (v3.11.0)
219
220
  - **Product**: `seo-audit`, `copywriting`, `api-design-principles`
220
221
  - **Knowledge**: `doc-writing-for-rag`, `capture` (LLM wiki overlay)
222
+ - **Internationalization**: `i18n` (v4.52.0 — audit the context registry + context-aware translation into native locale files), `i18n-adopt` (v4.52.0 — one-shot migration that externalizes all hardcoded strings on an existing codebase); both gated on `features.has_i18n`
221
223
  - **Integration**: `kie-ai`, `remotion-best-practices`
222
224
  - **Framework**: `baldart-push` (upstream contribution), `baldart-update` (v3.21.0 — agent-driven framework update), `overlay` (v3.19.0 — guided overlay author)
223
225
 
@@ -245,6 +247,10 @@ When `features.has_lsp_layer: true`, `codebase-architect` and the code-explorati
245
247
 
246
248
  When `features.has_code_graph: true`, agents prefer the [Graphify](https://github.com/safishamsi/graphify) code knowledge graph (tree-sitter, local/offline, native Leiden communities) for **structural / relational** queries — "what connects X to Y", blast-radius of a change, which modules cluster — via `graphify query`/`path`/`explain`/`affected`. The same graph **re-activates the LLM-wiki auto-learning loop** (dormant since the RAG removal in v4.20.0): `wiki-curator`, `/capture`, and the nightly `doc-graph-align` routine feed synthesis candidates from Graphify's native `GRAPH_REPORT.md` (god nodes, communities, suggested questions) — entirely offline. Graphify is a single language-agnostic tool (`pipx install graphifyy`); install via `baldart configure` or `/graphify-bootstrap` (never silent in CI — `baldart doctor` backfills). Falls back silently to LSP→Grep→Git. See [`framework/agents/code-graph-protocol.md`](framework/agents/code-graph-protocol.md) and [`framework/docs/CODE-GRAPH-LAYER.md`](framework/docs/CODE-GRAPH-LAYER.md).
247
249
 
250
+ ### Internationalization Layer (new in v4.52.0)
251
+
252
+ When `features.has_i18n: true`, BALDART makes consumers develop **multi-language by default**. Two ideas: (1) **no user-facing string is ever hardcoded** — every label goes through the stack's translation function, enforced by a deterministic anti-hardcoded gate (a BALDART-owned standalone `eslint.i18n.config.mjs` running `eslint-plugin-i18next` `no-literal-string` — works even on Biome-only projects, never touches your lint config) with `code-reviewer` as the semantic backstop; (2) a **stack-agnostic context registry** (`docs/i18n/registry.yml`) holds a *hyper-brief context* per key (what it is for / where it appears) + `domain`, so LLM translation is **context-aware instead of blind** — the 2026 "void of context" fix. Translations live in the stack's **native locale files** (i18next / next-intl / react-intl / lingui / vue-i18n); BALDART wraps the consumer's framework and best-in-class OSS rather than reimplementing i18n. The `/i18n` skill audits + translates on demand (via the `i18n-translator` agent — Sonnet, low-effort, bounded flag-not-guess); the weekly `i18n-align` routine keeps the maintained `i18n.target_languages` aligned and commits directly to the trunk. Ownership is strict: `coder` (STEP 9) externalizes + populates the registry, `doc-reviewer` curates it, `i18n-translator` translates. Opt-in at `baldart configure`; `baldart doctor` backfills the lint plugin + flags a missing registry. See [`framework/agents/i18n-protocol.md`](framework/agents/i18n-protocol.md) and [`framework/docs/I18N-LAYER.md`](framework/docs/I18N-LAYER.md).
253
+
248
254
  ### Curated Toolchain Layer (new in v4.41.0)
249
255
 
250
256
  When `features.has_toolchain: true`, BALDART becomes opinionated about the *tools* you build with, not just the workflow. On a JS/TS project `baldart configure` PRESELECTS and installs a curated set as devDependencies — **Biome** (format + lint + import organizer), **Vitest**, **tsc**, **Lefthook** (pre-commit) — and records literal gate commands in `toolchain.commands.*`. The quality-gate flows (`/new`, `/new2`, `/qa`, `qa-sentinel`, `coder`) then run those commands verbatim instead of hard-coding `eslint`/`tsc`/`jest`. Opinionated **but askable** (default Y, opt-out) and **non-destructive**: existing ESLint/Prettier/Jest/husky setups are detected and a migration is only ever PROPOSED, never automatic (`.husky/` is never overwritten). Never silent in CI (`baldart doctor` backfills); each gate falls back silently to the project-standard default when its command is unset, so non-JS projects and consumers with their own toolchain are unaffected. Install via `baldart configure` or `/toolchain-bootstrap`. See [`framework/agents/toolchain-protocol.md`](framework/agents/toolchain-protocol.md) and [`framework/docs/TOOLCHAIN-LAYER.md`](framework/docs/TOOLCHAIN-LAYER.md).
@@ -313,6 +319,7 @@ drift-detection loops actually run. Ship-bundled routines:
313
319
  | `skill-improve` | weekly Sun 02:00 UTC | skill-improver | Refines skills/agents from review patterns |
314
320
  | `ds-drift` | weekly Mon 03:00 UTC | doc-reviewer | Design-system drift check (optional) |
315
321
  | `full-sweep` | weekly Sun 03:00 UTC | doc-reviewer | Full SSOT audit (optional) |
322
+ | `i18n-align` | weekly Mon 04:00 UTC | i18n-translator | Translates target languages, lints, commits direct to trunk (optional, needs `has_i18n`) |
316
323
 
317
324
  Three backend adapters are bundled:
318
325
 
package/VERSION CHANGED
@@ -1 +1 @@
1
- 4.51.0
1
+ 4.52.0
@@ -20,6 +20,7 @@ Quick-reference for all custom agents. Use this to route tasks to the right spec
20
20
  | **security-reviewer** | Code | Review security-sensitive code, configs, auth, secrets, and infra changes | AppSec audit, threat modeling, hardening guidance | No | Security review, trust-boundary analysis |
21
21
  | **qa-sentinel** | QA | **Mechanical gate runner** — lint, typecheck, test suite, build, security audit, markdownlint. Returns PASS/FAIL verdict only. Does NOT analyze code, verify ACs, or review security/performance (those are Phase 2.5 and code-reviewer responsibilities). | Gate execution, verdicts | No (reports failures, coder fixes) | ESLint, tsc, node --test, npm run build, npm audit, markdownlint |
22
22
  | **hybrid-ml-architect** | ML | Design/implement ML systems end-to-end | Recommender, ranking, embeddings | Yes | Model design, evaluation, monitoring |
23
+ | **i18n-translator** | Localization | Translate user-facing labels into the maintained target languages (invoked by `/i18n` + the `i18n-align` routine). Gated on `features.has_i18n`. | Context-aware translation using the per-key registry context, ICU/placeholder preservation, glossary adherence; bounded "flag-not-guess" self-healing (`needs-attention`) | Yes (writes native locale files ONLY — never app code, never the registry) | Native locale files, optional bulk backends (lingo.dev/languine) |
23
24
  | **ui-expert** | Design | Design, implement, and review UI/UX | Mobile-first, accessibility, registry-first protocol gate (when `features.has_design_system: true`) — BLOCKING reads on `${paths.design_system}/INDEX.md` + `tokens-reference.md` + `components/<Name>.md` per `framework/agents/design-system-protocol.md`, Component Discovery cascade before any design | Yes (generates UI / HTML mockups in `ui-design` Step C; a separate fresh instance acts as design-quality evaluator in Step D) | ui-ux-pro-max, Playwright |
24
25
  | **visual-fidelity-verifier** | Design | Compare an implemented UI route against its mockup (or design-system spec) and return a severity-tagged JSON report. Auto-invoked by `/e2e-review` skill (Phase 4) — never spawned ad-hoc. Stateless. Strict severity taxonomy (Critical / Major / Minor) — never reads source code (anti assertion-fitting bias), never edits, never declares done. | Visual diff, severity taxonomy, design-system compliance check via registry cascade | No | Vision (multimodal), Read (screenshots + registry only), Playwright screenshots passed in by orchestrator |
25
26
  | **visual-designer** | Design | Generate visual assets via Kie.ai | Illustrations, icons, hero images | Yes | Kie.ai API, WebP optimization |
@@ -90,6 +90,22 @@ When `features.has_design_system: false`, the registry reads are skipped but
90
90
  the review still flags hardcoded values and inconsistencies against
91
91
  `${paths.ui_guidelines}`, and recommends `/design-system-init` in the report.
92
92
 
93
+ ## i18n Anti-Hardcoded Check (when `features.has_i18n: true`)
94
+
95
+ This is a REAL enforcement layer, not a fallback. The deterministic gate
96
+ (`eslint.i18n.config.mjs`, `no-literal-string` mode `jsx-only`) hard-blocks **JSX
97
+ text + user-facing JSX attributes** — so your job is the **residue it does NOT
98
+ deterministically catch**: flag as **HIGH** non-JSX user-facing hardcoded strings —
99
+ `toast(...)` / imperative call args, thrown user-facing errors, and strings built
100
+ by **concatenation**. (If the deterministic gate is unwired in this project — e.g.
101
+ a Biome-only toolchain that skipped `baldart doctor` — extend your check to JSX
102
+ text/attributes too; they are the linter's job when it runs, yours when it doesn't.)
103
+ *Excluded everywhere*: log lines, dev-facing errors, test files, enum/const keys,
104
+ `className`, internal identifiers, URLs, technical values. Also flag a key
105
+ referenced in `t()` with no registry entry as `I18N_REGISTRY_DRIFT`. See
106
+ [`framework/agents/i18n-protocol.md`](../../agents/i18n-protocol.md). When the flag
107
+ is `false`, this section is a no-op.
108
+
93
109
  ## Functional Traceability (MANDATORY for UI work — unconditional)
94
110
 
95
111
  Independent of `features.has_design_system`. For any diff that introduces UI
@@ -57,6 +57,12 @@ Load context in this exact order before touching any file:
57
57
  6. NEVER hardcode hex colors or shadow values — use the project's CSS custom properties / design tokens.
58
58
  7. For themed backgrounds, follow the project's text-on-background pairing rules (typically documented in `${paths.design_system}/patterns/<theme>.md`).
59
59
  8. For ANY chart / data-viz: follow the project's charting standard (typically `${paths.design_system}/patterns/charting.md`) and the approved chart library list. Exceptions require an ADR.
60
+ 9. **STEP 9 — i18n: no hardcoded labels + registry population (when `features.has_i18n: true`)**: BOTH halves are your responsibility — do NOT assume a linter will catch hardcoded strings (the deterministic gate may not be wired in this project / stack). See `framework/agents/i18n-protocol.md`.
61
+ 1. **HARD RULE (enforcement)**: NEVER write a user-facing string hardcoded in the code — not in JSX/template text, not in `label`/`placeholder`/`title`/`alt`/`aria-label` props, not in toast/error messages shown to the user. Every such string goes through the stack's translation function (`t('namespace.domain.key')` / `<FormattedMessage>` / equivalent). Never concatenate translated fragments — use **ICU MessageFormat** for plurals/gender/select. (Excluded from this rule: log lines, dev-facing errors, test fixtures, enum/const keys, `className`, internal identifiers, URLs, technical values.)
62
+ 2. Add the new key to the source-language native locale file (the `source` value).
63
+ 3. Add a **stub entry** to the registry (`${paths.i18n_registry}`): `source`, a **hyper-brief `context`** (what the label is for / where it appears — one line), and `domain` (the feature area). Set `char_limit`/`icu` when relevant. The QUALITY of `context` is curated by `doc-reviewer` — your job is to never leave the entry missing.
64
+ 4. Follow the naming convention (`namespace.domain.key`, semantic not literal, `global.*` for genuinely reused strings) unless an overlay overrides it.
65
+ 5. Do NOT translate into other languages (that is `i18n-translator` / the `/i18n` skill) and do NOT curate other keys (that is `doc-reviewer`). Skipping this step when the feature is on is a protocol violation: a hardcoded user-facing string, or a key referenced in `t()` with no registry entry (`I18N_REGISTRY_DRIFT`), fails review.
60
66
 
61
67
  ## Conditional Requirements — Binary-Outcome Items (MUST)
62
68
 
@@ -72,6 +72,30 @@ script (typically `scripts/design-system-drift-check.mjs`). Use the script outpu
72
72
  the starting point for your drift deliverable — it already enumerates
73
73
  the flags above.
74
74
 
75
+ ## Internationalization Scope (MUST — when `features.has_i18n: true`)
76
+
77
+ You **own the i18n context registry** (`${paths.i18n_registry}`) as documentation:
78
+ its `context`/`domain` fields are the curated SSOT that makes LLM translation
79
+ context-aware. You curate it; you do NOT translate (that is `i18n-translator`) and
80
+ you do NOT write app code. You MAY edit `${paths.i18n_registry}` — it is your domain.
81
+ See [`framework/agents/i18n-protocol.md`](../../agents/i18n-protocol.md).
82
+
83
+ When invoked after a feature (card scope), reconcile the registry against the new
84
+ `t()` keys in the diff:
85
+ - **Missing entry** — a key referenced in `t()` has no registry entry → add it (or
86
+ flag the coder's STEP 9 miss). Code `I18N_REGISTRY_DRIFT`.
87
+ - **Orphaned entry** — a registry entry has no `t()` call anywhere → mark `status:
88
+ deprecated` (do not delete silently). Code `I18N_KEY_ORPHANED`.
89
+ - **Missing/weak context** — an entry whose `context` is empty or a placeholder →
90
+ write the hyper-brief, accurate usage context (this is the value-add that
91
+ improves every downstream translation). Code `I18N_CONTEXT_MISSING`.
92
+ - **Dedup** — collapse genuinely identical meanings into one key; keep distinct keys
93
+ when the context differs (no "god keys").
94
+
95
+ Add a `## i18n Registry Drift` section to the deliverables when any of these fire.
96
+ The on-demand `/i18n audit` and the weekly `i18n-align` routine run the same
97
+ reconciliation outside card scope — your per-merge pass is the first line.
98
+
75
99
  ## Priority Stack (strict order)
76
100
  1. **Macro Feature Identification** -- determine which macro feature this work belongs to (ALWAYS FIRST)
77
101
  2. **Single Source of Truth sync** -- if the feature has a SSOT document, sync it BEFORE anything else
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: i18n-translator
3
+ description: "Context-aware translator for the i18n layer. Use to translate user-facing labels into the maintained target languages, using the per-key CONTEXT from the registry (not blind translation). Writes translations into the stack's native locale files; preserves ICU/placeholders; flags anomalies instead of guessing. Invoked by the /i18n skill and the i18n-align routine. Gated on features.has_i18n."
4
+ model: sonnet
5
+ effort: low
6
+ color: cyan
7
+ ---
8
+
9
+ > **YOLO MODE**: All agents spawned via the Task tool MUST use `mode: "bypassPermissions"`. No exceptions.
10
+
11
+ You are **i18n Translator** — a surgical, context-aware localization specialist. You translate user-facing labels into target languages using the per-key context that travels with each key, and you write the result into the project's **native locale files**.
12
+
13
+ **Your lane is translation + its mechanical envelope.** When an orchestrator (the `/i18n` skill) hands you a ready payload, you ONLY translate. When you run as the unattended `i18n-align` routine driver (no orchestrator, no nested subagents available), you ALSO do the mechanical plumbing around translation: enumerate keys (via `i18n.extract_command` / `rg`), diff source vs target locale files to find gaps, run the project's lint/validate on the files you wrote, and commit the translation files. **Two boundaries you NEVER cross either way**: you do not curate the registry `context`/`domain` (that is `doc-reviewer`'s domain — translate from it, never rewrite it) and you do not write or modify application code (that is `coder`'s). You never rename keys.
14
+
15
+ Protocol reference: read `framework/agents/i18n-protocol.md` for the authority model, registry schema, naming convention, and ownership boundaries. Gate on `features.has_i18n: true` — if it is false/absent, do nothing and say so.
16
+
17
+ ## Why context-aware (not blind)
18
+
19
+ The whole point of this agent is that you receive **what the label is for and where it appears** (`context`) and its **feature area** (`domain`), not just the source string. Use them to resolve ambiguity — sense ("Post" noun vs verb), register (formal/informal), and length. A blind literal translation is a failure even when grammatically correct. Your `effort: low` reasoning budget exists for exactly this disambiguation — use it, do not skip it.
20
+
21
+ ## Input (provided by the caller — you do not discover it)
22
+
23
+ For each label to translate you are given:
24
+ - `key`, `source` (in the source language), `context`, `domain`
25
+ - `char_limit` (optional UI constraint)
26
+ - `target_language` (BCP-47)
27
+ - the project **glossary / style guide** if one exists (terms that must translate a fixed way, brand voice)
28
+ - the path to the native locale file to write (e.g. `locales/<lang>.json`)
29
+
30
+ ## Output (what you produce)
31
+
32
+ - The translated string written into the **native locale file** for `target_language`, under the same `key`.
33
+ - **Preserve EXACTLY** every ICU construct (`{count, plural, ...}`, `{name}`) and interpolation placeholder — translate the human text around them, never the placeholder names or the ICU keywords.
34
+ - Apply the glossary verbatim. Respect `char_limit` (find a shorter natural phrasing; never truncate mid-word).
35
+ - Do NOT translate: identifiers, URLs, code, technical tokens, brand names that are not localized.
36
+
37
+ ## Self-healing is BOUNDED — flag, don't guess
38
+
39
+ When a label is anomalous, **do not produce a bad translation**. Leave the label untranslated and emit a structured `needs-attention` record instead. Anomalies include:
40
+ - `context` empty/ambiguous so you genuinely cannot pick the right sense or register.
41
+ - `char_limit` impossible to satisfy in `target_language` without distortion.
42
+ - ICU/placeholders in `source` that you cannot map 1:1 into a faithful translation.
43
+ - `source` looks technical (an identifier, a code token) — likely should not be user-facing at all.
44
+
45
+ `needs-attention` record shape (one per problem):
46
+ ```yaml
47
+ - key: <key>
48
+ target_language: <lang>
49
+ reason: <one short line — why you stopped>
50
+ ```
51
+
52
+ Return all `needs-attention` records to the caller (the `/i18n` skill or the `i18n-align` routine collects them into the report / commit message). **You never auto-fix outside your domain**: fixing the registry (context, dedup, orphans) is `doc-reviewer`'s job; changing keys or code is `coder`'s. Surfacing the problem IS the correct action.
53
+
54
+ ## Completion report
55
+
56
+ Return: count translated per language, the list of `needs-attention` records, and the files written. Never claim a label done unless you actually wrote a faithful, placeholder-preserving translation for it.
@@ -31,6 +31,16 @@ command wins; an empty/absent one falls back to the default below. A configured
31
31
  command that EXITS NON-ZERO is a real FAIL (do not fall back). Example: a project
32
32
  on Biome runs `npx biome check .` for lint; a Vitest project runs `npx vitest run`.
33
33
 
34
+ **i18n anti-hardcoded gate (since v4.52.0):** when `features.has_i18n: true`, run
35
+ the dedicated anti-hardcoded command **in addition** to the normal lint gate.
36
+ Resolve it: `i18n.lint_command` if set, ELSE the BALDART-owned standalone config by
37
+ convention when `eslint.i18n.config.mjs` exists at the repo root
38
+ (`npx eslint --config eslint.i18n.config.mjs .`). This is a separate ESLint run
39
+ just for this gate, so it works even on a Biome-only toolchain. A non-zero exit is
40
+ a real FAIL (a hardcoded user-facing string slipped through). If NEITHER resolves,
41
+ report `i18n-lint: SKIP (no anti-hardcoded gate wired — run \`baldart doctor\`)` as
42
+ an informational note, never a pass — do not claim the gate ran when it did not.
43
+
34
44
  Default pre-commit gates (MUST pass before any commit verdict) — used when no
35
45
  `toolchain.commands.*` is configured:
36
46
  1. Lint: `npx eslint --max-warnings=0 <changed source files>` (or project equivalent)
@@ -0,0 +1,109 @@
1
+ ---
2
+ name: i18n
3
+ effort: medium
4
+ description: >
5
+ On-demand internationalization audit + context-aware translation. Audits the
6
+ i18n context registry against the t() calls and native locale files (drift
7
+ codes I18N_REGISTRY_DRIFT / I18N_KEY_ORPHANED / I18N_CONTEXT_MISSING), then
8
+ translates missing/stale labels into the maintained target languages via the
9
+ i18n-translator agent — using the per-key context so translation is not blind.
10
+ Use when the user says /i18n, "traduci le label", "audit i18n", "allinea le
11
+ traduzioni", "i18n drift", or after adding user-facing strings. Gated on
12
+ features.has_i18n. Portable (Claude + Codex). The weekly i18n-align routine is
13
+ the scheduled twin of this skill.
14
+ ---
15
+
16
+ # i18n — audit + context-aware translation
17
+
18
+ Keep the translation layer honest: every user-facing label is externalized, the
19
+ context registry explains *what each label is for*, and the maintained languages
20
+ stay translated — with the context fed to the translator so it never guesses
21
+ blindly. This is the on-demand twin of the weekly `i18n-align` routine.
22
+
23
+ ## Project Context
24
+
25
+ **Reads from `baldart.config.yml`:** `features.has_i18n`, `paths.i18n_registry`, `i18n.framework`, `i18n.source_language`, `i18n.target_languages`, `i18n.locales_root`, `i18n.extract_command`, `i18n.bulk_translator`, `toolchain.commands.lint` / `i18n.lint_command`.
26
+ **Gated by features:** `features.has_i18n` — this skill refuses to run when the flag is `false`, prompting the user to flip it via `npx baldart configure` first.
27
+ **Overlay:** loads `.baldart/overlays/i18n.md` if present — project-specific key naming convention, glossary/style-guide location, locale file shape.
28
+ **On missing keys:** ask the user; do not assume defaults. See `framework/agents/project-context.md` § 3.
29
+
30
+ ## Effort
31
+
32
+ **Baseline:** `effort: medium` (frontmatter). **Inline override:** pass
33
+ `effort=<low|medium|high|xhigh|max>` anywhere in the invocation to scale
34
+ reasoning depth for this run — detect it once at kickoff and strip the token
35
+ before consuming user input. Level→behavior mapping, parsing contract, and
36
+ precedence caveats: `framework/agents/effort-protocol.md`.
37
+
38
+ ## Protocol reference
39
+
40
+ Read `framework/agents/i18n-protocol.md` for the authority model, registry
41
+ schema, naming convention, ICU rules, drift codes, and ownership boundaries.
42
+
43
+ ## Modes
44
+
45
+ Parse the invocation for a mode word; default to `audit` when none given.
46
+
47
+ - `/i18n` or `/i18n audit` → **audit only** (read-only report, no writes).
48
+ - `/i18n translate` → audit, then translate the gaps into the target languages.
49
+ - `/i18n translate <lang>` → restrict translation to one language.
50
+
51
+ For a **one-shot migration of an existing codebase** (find every hardcoded string,
52
+ replace with `t()`, register, translate) use **`/i18n-adopt`** instead — this skill
53
+ is for the steady-state audit/translate loop, not the bulk externalization.
54
+
55
+ ## Step 1 — Gate + load
56
+
57
+ 1. Read `baldart.config.yml`. If `features.has_i18n` is not `true`, STOP and tell
58
+ the user to run `npx baldart configure` (do not assume defaults).
59
+ 2. Resolve `paths.i18n_registry`, `i18n.locales_root`, `i18n.source_language`,
60
+ `i18n.target_languages`. If a required one is empty, ask the user.
61
+ 3. Load the overlay glossary/style-guide if present.
62
+
63
+ ## Step 2 — Audit (always)
64
+
65
+ 1. **Enumerate keys**: run `i18n.extract_command` if set (e.g.
66
+ `npx i18next-cli extract`); else `rg` the codebase for `t('…')` / the stack's
67
+ translation calls. This is the authoritative key set.
68
+ 2. Read the registry and the source-language native locale file.
69
+ 3. Reconcile and emit findings:
70
+ - `I18N_REGISTRY_DRIFT` — a key used in `t()` with no registry entry.
71
+ - `I18N_KEY_ORPHANED` — a registry entry with no `t()` usage.
72
+ - `I18N_CONTEXT_MISSING` — a registry entry whose `context` is empty/placeholder.
73
+ - Per target language: keys missing or stale in that language's locale file.
74
+ 4. Print a concise report (counts + the actionable lists). In `audit` mode, STOP here.
75
+
76
+ **Registry curation is `doc-reviewer`'s domain** — if registry findings
77
+ (`I18N_*`) are non-trivial, recommend running `doc-reviewer` rather than editing
78
+ the registry's `context` yourself here. This skill owns translation, not curation.
79
+
80
+ ## Step 3 — Translate (only in `translate` mode)
81
+
82
+ For each target language (or the one requested) with missing/stale keys:
83
+
84
+ 1. Build the per-key payload from the registry: `key`, `source`, `context`,
85
+ `domain`, `char_limit?`, plus the glossary/style-guide.
86
+ 2. Spawn the **`i18n-translator`** agent (Task tool, `subagent_type:
87
+ "i18n-translator"`) with that payload and the target locale file path. If
88
+ `i18n.bulk_translator` is set, the agent may delegate the bulk and review the
89
+ output — never ship unreviewed machine output.
90
+ 3. Collect the agent's `needs-attention` records (anomalies it refused to guess).
91
+ 4. **Review gate**: AI output is a first draft. Show the user a summary (counts +
92
+ any `needs-attention`) before treating it as done. The translator already
93
+ wrote the files; surface what it skipped and why.
94
+ 5. Run the lint/validate command (`toolchain.commands.lint` or `i18n.lint_command`)
95
+ to confirm no malformed ICU/placeholder was introduced.
96
+
97
+ ## Step 4 — Report
98
+
99
+ Summarize: keys audited, drift findings by code, keys translated per language,
100
+ and `needs-attention` items left for the user. Do NOT auto-commit — leave the
101
+ working tree for the user to review (the scheduled `i18n-align` routine is the
102
+ unattended path that commits directly to the trunk).
103
+
104
+ ## Boundaries
105
+
106
+ - This skill orchestrates; it does NOT translate inline (that is `i18n-translator`)
107
+ and does NOT curate the registry (that is `doc-reviewer`).
108
+ - Never hardcode the i18n framework, locale paths, or languages — read them from config.
109
+ - When `features.has_i18n: false`, refuse and point to `configure`.
@@ -0,0 +1,105 @@
1
+ ---
2
+ name: i18n-adopt
3
+ effort: medium
4
+ description: >
5
+ One-shot migration that ADOPTS the i18n layer on an existing codebase: scans
6
+ the whole project for hardcoded user-facing strings, methodically replaces each
7
+ with the stack's translation function t('key'), wires every key into the central
8
+ context registry (source + hyper-brief context + domain), and translates them
9
+ into the configured target languages. The i18n analogue of /design-system-init.
10
+ Full-auto on a dedicated branch (no mid-run pauses), idempotent + resumable,
11
+ verifies the gate + build before finishing, and leaves the result on the branch
12
+ for your review (never auto-merges). Use when the user says /i18n-adopt, "migra
13
+ a i18n", "adotta i18n", "externalize all strings", or introduces BALDART i18n on
14
+ an existing platform. Gated on features.has_i18n. Claude-only orchestration.
15
+ ---
16
+
17
+ # i18n-adopt — externalize an existing codebase into the i18n layer
18
+
19
+ Bring a platform that was built with hardcoded strings (e.g. an existing product)
20
+ onto the BALDART i18n layer in one methodical pass: find every hardcoded
21
+ user-facing string, replace it with `t('namespace.domain.key')`, register the key
22
+ + its context, and translate it. The deterministic gate we ship (`eslint.i18n.config.mjs`
23
+ `no-literal-string`) IS the finder — this skill orchestrates the migration around it.
24
+
25
+ ## Project Context
26
+
27
+ **Reads from `baldart.config.yml`:** `features.has_i18n`, `paths.i18n_registry`, `i18n.framework`, `i18n.source_language`, `i18n.target_languages`, `i18n.locales_root`, `i18n.lint_command`, `i18n.extract_command`, `git.trunk_branch`.
28
+ **Gated by features:** `features.has_i18n` — refuses to run when `false`; tell the user to run `npx baldart configure` first.
29
+ **Overlay:** loads `.baldart/overlays/i18n-adopt.md` if present — project naming convention, files/dirs to exclude from migration, glossary location.
30
+ **On missing keys:** ask the user; do not assume defaults. See `framework/agents/project-context.md` § 3.
31
+
32
+ ## Effort
33
+
34
+ **Baseline:** `effort: medium` (frontmatter). The **orchestration** is light
35
+ (sequence the phases, batch the files, track the ledger) — it does not need elevated
36
+ reasoning, so the skill runs at `medium`. The **agents keep their own models**: `coder`
37
+ stays **Opus** (it mutates production code across a real codebase — key naming,
38
+ not breaking code, ICU, user-facing vs technical are exactly where Opus quality
39
+ matters; do NOT downgrade it), while `i18n-translator` and `doc-reviewer` run on
40
+ Sonnet by their own design. **Inline override:** pass
41
+ `effort=<low|medium|high|xhigh|max>` to scale the orchestration's reasoning; detect it
42
+ once at kickoff and strip the token. See `framework/agents/effort-protocol.md`.
43
+
44
+ ## Protocol reference
45
+
46
+ Read `framework/agents/i18n-protocol.md` (authority model, registry schema, naming
47
+ convention, ICU, ownership). Strict specialization is MANDATORY here: the **linter
48
+ finds**, the **`coder` replaces code**, **`doc-reviewer` curates the registry**, the
49
+ **`i18n-translator` translates**, **`qa-sentinel`/the gate verifies**. The skill only
50
+ orchestrates — it never edits application code itself.
51
+
52
+ ## Safety model (full-auto, but safe by construction)
53
+
54
+ Full-auto means **no mid-run approval pauses** — NOT reckless:
55
+ - Runs on a **dedicated branch** `chore/i18n-adopt-{{YYYYMMDD}}` off `${git.trunk_branch}` (never on the trunk). **Never auto-merges** — a codebase-wide change is reviewed by the user.
56
+ - **Idempotent + resumable**: a ledger at `.baldart/i18n-adopt/progress.json` records per-file status (`pending|done|skipped`). Re-running resumes where it stopped; already-externalized files are skipped.
57
+ - **Verifies before finishing**: the i18n gate + typecheck + build + tests must pass (bounded self-heal loop), so the branch is never left red.
58
+ - The `coder` still uses judgement per string: it externalizes everything **user-facing** (incl. ambiguous `toast(...)` / dynamic / concatenated cases), but does NOT wrap genuinely non-user-facing strings (technical identifiers, URLs, enum/const keys, `className`, log lines) — wrapping those would be a correctness bug, not caution.
59
+
60
+ ## Phase 0 — Gate + preflight
61
+
62
+ 1. Read `baldart.config.yml`. If `features.has_i18n` ≠ `true`, STOP (point to `configure`).
63
+ 2. Confirm the anti-hardcoded gate is set up (`i18n.lint_command` resolves, or `eslint.i18n.config.mjs` / `.eslintrc.i18n.json` exists). If not, tell the user to run `npx baldart doctor` (it runs `i18n-gate-setup`) and STOP.
64
+ 3. Resolve `i18n.framework`, `i18n.source_language`, `i18n.locales_root`, `paths.i18n_registry`. Ask for any that are empty.
65
+ 4. Create (or check out) the dedicated branch `chore/i18n-adopt-{{YYYYMMDD}}` off `${git.trunk_branch}`. Create `.baldart/i18n-adopt/progress.json` if absent.
66
+
67
+ ## Phase 1 — Scan (the linter is the finder)
68
+
69
+ 1. Run the gate command over the WHOLE codebase in report mode and capture file:line:string for every finding — e.g. `npx eslint --config eslint.i18n.config.mjs . -f json > /tmp/i18n-adopt-scan.json` (or the legacy/`i18n.lint_command` equivalent). For broader discovery beyond JSX, you MAY temporarily raise the rule `mode` to `jsx-only`/`all` for the scan only (do not commit that change).
70
+ 2. Enumerate EXISTING `t()` keys (via `i18n.extract_command` or `rg`) + the current registry, so the migration reuses keys and never duplicates.
71
+ 3. Group the findings **by file**, then by feature **domain** (from the path). Write the work-list into the ledger as `pending` rows. Log the totals (N strings, M files, D domains).
72
+
73
+ ## Phase 2 — Replace (coder, batched per file, full-auto)
74
+
75
+ Process files in batches (a few files per `coder` invocation to keep context bounded). For each batch, spawn **`coder`** (its own model — Opus; it is mutating production code, do NOT override it to a weaker model) with: the file list, the per-file hardcoded-string findings, the naming convention, and the registry/locale paths. Mission:
76
+
77
+ - Replace each user-facing literal with `t('namespace.domain.key')` (semantic keys, `global.*` for reused, ICU for plurals/gender — never concatenation). Reuse an existing key when one already means the same thing.
78
+ - Apply **`coder.md` STEP 9**: add each key to the source-language native locale file (`source`) + a **stub registry entry** (`source` + hyper-brief `context` + `domain`).
79
+ - Skip genuinely non-user-facing strings (technical/identifiers/URLs/logs) — record them in the ledger as `skipped` with a one-line reason.
80
+ - Mark each file `done` in the ledger and commit per batch (`chore(i18n-adopt): externalize <area>`), so a crash resumes cleanly.
81
+
82
+ Do NOT pause for approval between batches (full-auto). Keep going until every `pending` file is `done` or `skipped`.
83
+
84
+ ## Phase 3 — Curate (doc-reviewer)
85
+
86
+ Spawn **`doc-reviewer`** over the new registry entries: context completeness, dedup, orphan/missing (`I18N_REGISTRY_DRIFT` / `I18N_KEY_ORPHANED` / `I18N_CONTEXT_MISSING`). It owns the registry — it fixes context quality; it does not translate or edit code.
87
+
88
+ ## Phase 4 — Translate (i18n-translator)
89
+
90
+ For each `i18n.target_languages`, translate ALL the newly-created keys: delegate to the `/i18n translate` flow (which spawns `i18n-translator` with the registry context). If `i18n.bulk_translator` is set, the agent may delegate bulk and review. Collect `needs-attention` records.
91
+
92
+ ## Phase 5 — Verify (bounded self-heal)
93
+
94
+ Run the i18n gate (`i18n.lint_command` / standalone config) — it MUST report **zero** hardcoded user-facing strings now — plus typecheck, build, and tests. On failure, spawn `coder` with the failing output (bounded retries, e.g. 2). Never finish on a red branch; if it cannot go green within the cap, stop and report the blocker (do not merge).
95
+
96
+ ## Phase 6 — Report (no auto-merge)
97
+
98
+ Summarize: strings externalized, files changed, keys created, languages translated, strings skipped (with reasons), and `needs-attention` items. State the branch name and that it is **left for the user to review and merge** (a codebase-wide change is never auto-merged). Point the user at `/i18n audit` to re-check and the `i18n-align` routine for ongoing language upkeep.
99
+
100
+ ## Boundaries
101
+
102
+ - Orchestrates only — never edits application code (coder), never curates the registry (doc-reviewer), never translates (i18n-translator) itself.
103
+ - Never runs on the trunk; never auto-merges.
104
+ - Reads all project facts from config — no hardcoded paths, frameworks, or languages.
105
+ - When `features.has_i18n: false`, refuse and point to `configure`.
@@ -114,6 +114,15 @@
114
114
  Read the design.html file and use it as the visual reference for your implementation.
115
115
  The design was approved by the user — your implementation MUST match it.
116
116
 
117
+ ### i18n constraint (include when `features.has_i18n: true`)
118
+ No user-facing string may be hardcoded. Externalize every label through the
119
+ stack's translation function and apply `coder.md § STEP 9 — i18n registry
120
+ population`: add the key to the source-language native locale file + a stub
121
+ entry in `${paths.i18n_registry}` (`source` + hyper-brief `context` + `domain`),
122
+ use ICU for plurals/gender. The `features.has_i18n` flag is ambient (read it
123
+ from `baldart.config.yml` directly — no orchestrator handoff). doc-reviewer
124
+ curates the registry afterwards; the anti-hardcoded linter is the gate.
125
+
117
126
  ### [UI-ONLY] Registry-first BLOCKING pre-work (include ONLY when briefing = "ui-expert")
118
127
  You are spawned as `ui-expert`. Before writing any UI code, you MUST complete
119
128
  the registry-first cascade defined in
@@ -344,9 +353,13 @@
344
353
  npx tsc --noEmit > /tmp/tsc-<CARD-ID>.txt 2>&1; echo "tsc:$?" # guard: when stack.language includes "typescript"
345
354
  npm test > /tmp/test-<CARD-ID>.txt 2>&1; echo "test:$?" # if tests exist
346
355
  npm run build > /tmp/build-<CARD-ID>.txt 2>&1; echo "build:$?"
356
+ # i18n anti-hardcoded gate — ONLY when features.has_i18n: true. Run i18n.lint_command
357
+ # if set, else the standalone config by convention; a non-zero exit is a real FAIL
358
+ # (a hardcoded user-facing string). Skip the line entirely when has_i18n is false.
359
+ ${i18n.lint_command:-npx eslint --config eslint.i18n.config.mjs .} > /tmp/i18n-<CARD-ID>.txt 2>&1; echo "i18n:$?" # guard: when features.has_i18n: true AND a gate config/command exists
347
360
  ```
348
361
  The `echo "<gate>:$?"` lines surface only the **exit code** inline — the full log stays on disk. When `stack.language` does NOT include `typescript`, skip the `tsc` line (no equivalent gate). Capturing to `/tmp/*-<CARD-ID>.txt` guarantees the failing output is available for step 9. On a non-zero exit, read a **bounded** extract only (`tail -n 30 /tmp/<gate>-<CARD-ID>.txt`), never the whole log — do NOT run the gates without redirect-to-file capture.
349
- 9. **If any check fails**: categorize the error (`lint | TypeScript | test | build`), log it in the tracker as `retry-cause: <category>`.
362
+ 9. **If any check fails**: categorize the error (`lint | TypeScript | test | build | i18n`), log it in the tracker as `retry-cause: <category>`. An `i18n` failure means a hardcoded user-facing string — the fix is to externalize it via `t()` + register the key (coder STEP 9), never to weaken the gate.
350
363
  **Code-recovery check (MUST do before rewriting)**: before spawning a fix agent or rewriting code, check whether lint-staged or a pre-commit hook removed code that is actually needed (e.g. a field "unused" at commit time but consumed by later code). Inspect the captured diff and `git diff`/`git log -p` for the worktree branch and restore the needed code by an explicit file write. **Do NOT `git stash pop` inside the worktree** — `refs/stash` is globally shared across worktrees (`$GIT_COMMON_DIR`), so a stash created or popped in a worktree can corrupt another worktree's state (see Phase 4 WORKTREE COMMIT RULE). If you need to set work aside in a worktree, make a clearly-labelled WIP commit and record its hash in the tracker for later squash, never a stash.
351
364
  When spawning a fix agent: scope it exclusively to this card's Edit-allowed files (from `## File Ownership Map`) — it MUST NOT touch files owned by other cards. Pass the fix agent: the **path** to the captured gate log (`/tmp/<gate>-<CARD-ID>.txt` — it Reads the log itself; do NOT inline-paste the full log into its prompt), the error category, and the explicit list of files it may edit. Do NOT ask the user — just fix and re-run. Fix the code, not the tests (unless the test itself is wrong). Repeat up to **3 times**. **Stuck-loop guard**: compare the failing error's fingerprint (file:line + message) between retries; if the SAME error reproduces on 2 consecutive retries, the fix is not converging (often a constraint outside the card's ownership map) — stop early, log `[STUCK-LOOP] <error>` in `## Issues & Flags`, and escalate to the user rather than burning the 3rd retry on an identical failure.
352
365
  10. If still failing after 3 retries (or on a stuck loop), log the failure in `## Issues & Flags` and ask the user before continuing.
@@ -11,8 +11,8 @@ cards. Anti-drift by design — rigid step sequence, persistent state, visible p
11
11
 
12
12
  ## Project Context
13
13
 
14
- **Reads from `baldart.config.yml`:** `paths.prd_dir`, `paths.backlog_dir`, `paths.references_dir`, `paths.design_system`, `paths.ui_guidelines`, `paths.metrics` (default `docs/metrics`), `git.trunk_branch`, `git.merge_strategy`, `identity.audience_segments`, `identity.language`.
15
- **Gated by features:** `features.has_prd_workflow` (skill REFUSES to run when `false`), `features.has_backlog` (Step 5 BLOCKING when `true`), `features.has_design_system` (Step 3 BLOCKING reads), `features.has_adrs` (architectural decisions get logged as ADRs in `paths.adrs_dir`).
14
+ **Reads from `baldart.config.yml`:** `paths.prd_dir`, `paths.backlog_dir`, `paths.references_dir`, `paths.design_system`, `paths.ui_guidelines`, `paths.i18n_registry`, `paths.metrics` (default `docs/metrics`), `git.trunk_branch`, `git.merge_strategy`, `identity.audience_segments`, `identity.language`.
15
+ **Gated by features:** `features.has_prd_workflow` (skill REFUSES to run when `false`), `features.has_backlog` (Step 5 BLOCKING when `true`), `features.has_design_system` (Step 3 BLOCKING reads), `features.has_adrs` (architectural decisions get logged as ADRs in `paths.adrs_dir`), `features.has_i18n` (cards with UI copy declare label keys + context — HARD RULE 20).
16
16
  **Overlay:** loads `.baldart/overlays/prd.md` if present — project-specific terminology, card layout overrides, segment names, mandated review gates.
17
17
  **On missing/empty keys:** ask the user; do not assume defaults. See `framework/agents/project-context.md` § 3.
18
18
 
@@ -258,6 +258,16 @@ message. You ask questions, wait for answers, and iterate.
258
258
  [obsidian-backref.md](references/obsidian-backref.md). See also discovery-phase.md Step
259
259
  1 point 2 and validation-phase.md Step 7.6.
260
260
 
261
+ 20. **i18n label declaration (when `features.has_i18n: true`).** For any card that
262
+ introduces user-facing copy, the PRD + the card MUST declare the **label keys**
263
+ to create and a **hyper-brief context** for each (what it is for / where it
264
+ appears), plus its `domain`. State as an acceptance criterion that every string
265
+ is externalized through the translation function (no hardcoded labels) with the
266
+ key recorded in `${paths.i18n_registry}`. This makes downstream translation
267
+ context-aware. Do NOT write the translations in the PRD — only the keys + context.
268
+ The coder fills the registry (STEP 9); doc-reviewer curates it. See
269
+ `framework/agents/i18n-protocol.md`.
270
+
261
271
  ---
262
272
 
263
273
  ## TEMPLATES
@@ -170,7 +170,7 @@ const apiPrompt =
170
170
  `Return findings with domain in {code, perf, migration, security}. Use the finding schema fields.`
171
171
 
172
172
  const qaPrompt =
173
- `Run MECHANICAL GATES ONLY over the batch scope, per ${protocolRef} Step F.3 (qa-sentinel row): lint, type-check, the full test suite, build, dependency audit, and markdownlint as applicable to this project. Do NOT read source for code findings, do NOT emit severities — return only a PASS/FAIL/SKIP gate table.\n\nWorktree: ${a.worktreePath || '(cwd)'}\nChanged files:\n${scope.join('\n')}`
173
+ `Run MECHANICAL GATES ONLY over the batch scope, per ${protocolRef} Step F.3 (qa-sentinel row): lint, type-check, the full test suite, build, dependency audit, markdownlint, AND — when features.has_i18n is true — the i18n anti-hardcoded gate (resolve i18n.lint_command, else the standalone eslint.i18n.config.mjs by convention; non-zero exit = FAIL; neither present = SKIP, never a silent pass) as applicable to this project. Do NOT read source for code findings, do NOT emit severities — return only a PASS/FAIL/SKIP gate table.\n\nWorktree: ${a.worktreePath || '(cwd)'}\nChanged files:\n${scope.join('\n')}`
174
174
 
175
175
  // F-041 — single-card batch: a per-card review already covered these exact files and a
176
176
  // 1-card batch has NO cross-card conflict to surface, so the duplicate Claude finders can be
@@ -237,6 +237,9 @@ const projectBrief = [
237
237
  (features.has_toolchain && cfg.toolchain && cfg.toolchain.commands)
238
238
  ? `Toolchain commands (run THESE verbatim for the baseline + any gate, per agents/toolchain-protocol.md; empty → project default): ${JSON.stringify(cfg.toolchain.commands)}.`
239
239
  : '',
240
+ features.has_i18n
241
+ ? `i18n (features.has_i18n=true, per agents/i18n-protocol.md): the coder MUST NOT hardcode any user-facing string — route every label through the stack t() function and apply coder.md STEP 9 (add the key + a hyper-brief context to the registry at ${paths.i18n_registry || 'paths.i18n_registry'}). The Phase-2 gate MUST run the anti-hardcoded gate IN ADDITION to lint: \`${(cfg.i18n && cfg.i18n.lint_command) || 'npx eslint --config eslint.i18n.config.mjs .'}\` (resolve i18n.lint_command, else the standalone eslint.i18n.config.mjs by convention; a non-zero exit FAILS the gate; if neither exists report i18n-lint SKIP, never a silent pass).`
242
+ : '',
240
243
  FLAGS.effort ? `Reasoning effort for this run: ${FLAGS.effort}.` : '',
241
244
  ].filter(Boolean).join('\n')
242
245
 
@@ -17,6 +17,7 @@ This file is generic. Paths referenced as `${paths.X}` resolve from `baldart.con
17
17
  | LLM wiki overlay sync | `features.has_wiki_overlay: true` |
18
18
  | Design-system blocking reads | `features.has_design_system: true` |
19
19
  | E2E review BLOCKING gate (Phase 2.6 of `/new` invokes `/e2e-review`) | `features.has_e2e_review: true` |
20
+ | No hardcoded user-facing strings — every label via `t()` + key in `${paths.i18n_registry}` (`context`+`domain`) | `features.has_i18n: true` |
20
21
 
21
22
  When a MUST rule's feature is `false` or its target file does not exist, the rule is **silent** — do not invent a workflow that isn't there. Project-specific terminology (audience segments, domain entities, brand voice) comes from `identity.audience_segments`, `agents/coding-standards.md`, and skill overlays under `.baldart/overlays/`. See `agents/project-context.md` for the full protocol.
22
23
 
@@ -132,6 +133,9 @@ Conflict steps (must follow in order):
132
133
  - **When `features.has_adrs: true`:**
133
134
  - MUST create complete ADRs (Context, Decision, Rationale, Alternatives, Consequences; no TODO placeholders) for architectural decisions: provider changes, auth changes, DB schema changes, API contract changes, external dependencies, deployment targets, performance/scalability decisions, data model changes affecting multiple features.
134
135
 
136
+ - **When `features.has_i18n: true`:**
137
+ - MUST NOT write any user-facing string hardcoded in the code — ever. Every label goes through the stack's translation function (`t('namespace.domain.key')`), with the key recorded in `${paths.i18n_registry}` (`source`+`context`+`domain`) and in the source-language native locale file. No string concatenation → ICU for plurals/gender. Enforced by the anti-hardcoded lint gate; the residue is a HIGH review finding. See `agents/i18n-protocol.md`.
138
+
135
139
  - **When the project ships a GitHub issue review convention** (declared in `.baldart/overlays/`):
136
140
  - MUST comment on every GitHub issue when resolving it and apply the project's review label (e.g. `vibe review`) per the overlay's labelling rule.
137
141
  - MUST create QA issues per test case (labels `qa` + area); when fixed add a fix summary, apply the review label, leave open; open QA issues are the source of truth.