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 +29 -0
- package/README.md +10 -3
- package/VERSION +1 -1
- package/framework/.claude/agents/REGISTRY.md +1 -0
- package/framework/.claude/agents/code-reviewer.md +16 -0
- package/framework/.claude/agents/coder.md +6 -0
- package/framework/.claude/agents/doc-reviewer.md +24 -0
- package/framework/.claude/agents/i18n-translator.md +56 -0
- package/framework/.claude/agents/qa-sentinel.md +10 -0
- package/framework/.claude/skills/i18n/SKILL.md +109 -0
- package/framework/.claude/skills/i18n-adopt/SKILL.md +105 -0
- package/framework/.claude/skills/new/references/implement.md +14 -1
- package/framework/.claude/skills/prd/SKILL.md +12 -2
- package/framework/.claude/workflows/new-final-review.js +1 -1
- package/framework/.claude/workflows/new2.js +3 -0
- package/framework/AGENTS.md +4 -0
- package/framework/agents/i18n-protocol.md +120 -0
- package/framework/agents/index.md +2 -0
- package/framework/docs/I18N-LAYER.md +89 -0
- package/framework/routines/i18n-align.routine.yml +61 -0
- package/framework/routines/index.yml +11 -0
- package/framework/templates/baldart.config.template.yml +59 -0
- package/package.json +1 -1
- package/src/commands/configure.js +103 -0
- package/src/commands/doctor.js +58 -0
- package/src/commands/update.js +6 -0
- package/src/utils/i18n-gate.js +195 -0
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/**:
|
|
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 (
|
|
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 (
|
|
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.
|
|
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,
|
|
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
|
|
package/framework/AGENTS.md
CHANGED
|
@@ -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.
|