diff --git a/.agents/skills/prh-terminology/SKILL.md b/.agents/skills/prh-terminology/SKILL.md
new file mode 100644
index 0000000000..53063eb6ab
--- /dev/null
+++ b/.agents/skills/prh-terminology/SKILL.md
@@ -0,0 +1,152 @@
+---
+name: prh-terminology
+description: "Manage the translation terminology dictionary prh.yml for consistent Angular-ja documentation. Use before adopting a translation for a new technical term, when checking existing terminology rules, and when adding, changing, or removing rules in prh.yml."
+---
+
+# prh-terminology: Terminology dictionary management
+
+Maintain the Angular-ja project's translation terminology dictionary in `prh.yml` so that Japanese technical terms stay consistent across all Angular documentation translations.
+
+**Rule**: Never guess terminology. Check `prh.yml` (and apply this skill) before adopting a translation for a new technical term. Typical moments:
+
+- Before any translation work: check the existing rules for the terms in the file.
+- For a new technical term: decide a consistent Japanese form.
+- For katakana standardization: verify long vowel marks and spelling.
+- After translating: add rules so the chosen form is enforced project-wide.
+
+## prh.yml structure overview
+
+The `prh.yml` file uses the prh (Proofreading Helper) format to enforce consistent terminology:
+
+```yaml
+version: 1
+imports:
+ - path: ./node_modules/prh/prh-rules/files/markdown.yml
+rules:
+ - expected: 正しい表記 # Preferred term
+ pattern: # Terms to replace
+ - 間違った表記1
+ - 間違った表記2
+ options: # Optional settings
+ wordBoundary: true # Match whole words only
+```
+
+## Rule categories
+
+### 1. 言い換え (Paraphrasing rules)
+
+Standardizes Japanese technical expressions.
+
+- "依存性の注入" (preferred) vs "依存関係の注入" (to replace)
+- "変更検知" (preferred) vs "変更検出" (to replace)
+
+### 2. カタカナ語 (Katakana rules)
+
+Enforces consistent katakana spelling and English preservation.
+
+- **Long vowel consistency**: "サーバー" not "サーバ"
+- **English preservation**: "Promise" not "プロミス"
+- **Regex patterns**: `/pattern(?!suffix)/` to match specific cases
+
+## How to add or modify rules
+
+### Simple paraphrasing rule
+
+```yaml
+- expected: 推奨される表記
+ pattern:
+ - 置き換える表記1
+ - 置き換える表記2
+```
+
+### Katakana consistency rule
+
+```yaml
+- expected: サーバー
+ pattern: /サーバ(?!ー)/ # Matches "サーバ" not followed by "ー"
+```
+
+### English preservation rule
+
+```yaml
+- expected: Promise
+ pattern: プロミス
+```
+
+### Complex regex rule
+
+```yaml
+- expected: ため
+ pattern: /(行|作)?為(?!替)/
+ regexpMustEmpty: $1 # Captured group must be empty
+```
+
+### Word boundary rule
+
+```yaml
+- expected: Service Worker
+ pattern:
+ - サービスワーカー
+ options:
+ wordBoundary: true # Match whole words only
+```
+
+## Management procedures
+
+### Add a new rule
+
+1. **Identify category**: paraphrasing or katakana.
+2. **Determine the preferred form**: based on project standards.
+3. **List incorrect forms**: common variations to replace.
+4. **Add appropriate options**: `wordBoundary`, regex patterns.
+
+### Modify an existing rule
+
+1. **Locate the existing rule**: search by expected term.
+2. **Update patterns**: add or remove incorrect forms.
+3. **Adjust options**: modify regex or boundary settings.
+4. **Test impact**: consider existing translations.
+
+### Remove a rule
+
+1. **Verify necessity**: confirm the rule is no longer needed.
+2. **Check dependencies**: ensure no conflicts with existing translations.
+3. **Document the reason**: give a clear justification for the removal.
+
+## Angular-specific terminology guidelines
+
+### Technical terms (keep in English)
+
+- Component, Directive, Service, Pipe
+- Promise, Observable, Signal
+- Router, Guard, Resolver
+
+### Japanese translations (standardize)
+
+- "依存性の注入" for Dependency Injection
+- "変更検知" for Change Detection
+- "遅延読み込み" for Lazy Loading
+
+### Katakana consistency
+
+- Long vowel marks: アプリケーション, サーバー, ユーザー
+- Short forms: ブラウザ (not ブラウザー)
+
+## Validation process
+
+After modifying `prh.yml`:
+
+1. **Test with prh**: verify the syntax is valid.
+2. **Run textlint** (`pnpm lint`): check integration with linting.
+3. **Test on sample files**: verify the rules work correctly.
+4. **Document changes**: update the rule rationale.
+
+## Best practices
+
+1. **Consistency first**: align with existing project terminology.
+2. **Community input**: consider Angular-ja community preferences.
+3. **Technical accuracy**: maintain precision in technical terms.
+4. **Readability**: balance consistency with natural Japanese.
+5. **Incremental changes**: add rules gradually to avoid disruption.
+
+Always test rule changes against existing translations to ensure they improve consistency without introducing errors.
diff --git a/.agents/skills/translate-file/SKILL.md b/.agents/skills/translate-file/SKILL.md
new file mode 100644
index 0000000000..ae57e32e73
--- /dev/null
+++ b/.agents/skills/translate-file/SKILL.md
@@ -0,0 +1,66 @@
+---
+name: translate-file
+description: "Translate English Angular documentation file to Japanese following strict project standards. Use when translating new .en.md/.en.ts/.en.json files into Japanese with line-count preservation, anchor IDs, and terminology consistency."
+---
+
+Translate the file the user specifies (a path under `adev-ja/`) from English to Japanese following the angular-ja project's strict translation standards.
+
+You are an expert Japanese technical translator specializing in Angular documentation translation. Follow the same approach as the project's AI translation tool with two-stage processing: translation → proofreading.
+
+## Critical Translation Rules
+
+**NEVER wrap the entire text in code blocks.** Always maintain original formatting.
+
+**Structural Requirements:**
+- **Maintain EXACT line count** - input and output must have identical number of lines
+- **Preserve markdown structure absolutely** - never change heading levels, list markers, or indentation
+- **Keep empty lines as empty lines** - preserve all whitespace and spacing
+- **Maintain list hierarchy and markers** (*, -, +, 1., etc.)
+- **Preserve code blocks unchanged** - never translate content inside code blocks
+- **Keep URLs, filenames, and identifiers untranslated**
+- **Preserve HTML tags and special symbols exactly**
+
+**AI Translation tool**
+- For Markdown files, use the translator tool: `tools/translator`.
+
+**Heading ID Rules:**
+- For headings **below h1 level** (`
` and lower), add anchor IDs based on original English heading
+- Convert original heading to lowercase and remove all symbols except hyphens
+- Format: `## Japanese Translation {#original-heading-lowercase}`
+- Examples:
+ - `## How to use Angular` → `## Angularの使い方 {#how-to-use-angular}`
+ - `### `foo.bar`` → `### `foo.bar` {#foobar}` (remove all non-hyphen symbols)
+- **h1 level headings get NO anchor IDs**
+
+**Special Content Rules:**
+- **Never change special prefixes**: NOTE/TIP/HELPFUL/IMPORTANT/QUESTION/TLDR/CRITICAL remain in English
+ - Correct: `NOTE: これは重要な情報です。`
+ - Incorrect: `注: これは重要な情報です。`
+- **No spaces around English words**: `Angularの使い方` not `Angular の使い方`
+- **Preserve all Angular terminology**: component, directive, service, pipe, etc.
+
+**Quality Standards:**
+- Follow textlint rules for Japanese technical writing
+- Use appropriate katakana for foreign terms per Ministry of Education guidelines
+- Maintain consistency with existing project translations
+- Ensure all cross-references and internal links remain functional
+
+**CRITICAL: Use the `prh-terminology` skill for Terminology**
+- **BEFORE translating**: Follow the `prh-terminology` skill to check existing terminology rules in `prh.yml`
+- **When encountering new terms**: Follow the same skill for a consistent translation approach
+- **For uncertain katakana**: Check long vowel marks and standardization with the same skill
+- **After translation**: Add new terminology rules per the same skill if needed
+- The skill ensures all translations follow the project's terminology dictionary
+
+**File Management Rules:**
+- **Original content preservation**: Translated Japanese file has ALWAYS corresponding `.en.` file to preserve original content snapshot
+- **Structure matching**: Translated file MUST have the same line count and document structure to the original file
+- **Diff compatibility**: This enables easy comparison and tracking of changes between versions
+
+**Processing Approach:**
+1. **Block-based translation**: Process content in heading-based blocks like the AI tool
+2. **Two-stage validation**: Translate first, then apply textlint-based corrections
+3. **Preserve technical accuracy** while making content accessible to Japanese developers
+4. **Maintain line correspondence** for easy diff tracking between .en.md and .md files
+
+Always translate only the requested content, returning the translated text without additional explanations or wrapper text.
diff --git a/.agents/skills/update-origin/SKILL.md b/.agents/skills/update-origin/SKILL.md
new file mode 100644
index 0000000000..1367c8323b
--- /dev/null
+++ b/.agents/skills/update-origin/SKILL.md
@@ -0,0 +1,316 @@
+---
+name: update-origin
+description: "Single entry point for syncing Angular upstream changes to angular-ja. Orchestrates branch creation, origin sync, diff classification, delegated migration by role, deterministic verification, per-directory commits, and PR creation. Use whenever an origin update is requested."
+---
+
+# update-origin: Origin Sync Orchestrator
+
+Single source of truth for the origin-update workflow. All per-file work follows the role instruction files in `roles/`. When your harness can delegate to sub-agents, do not perform per-file migration in the orchestrating session itself; delegate it to the roles so the orchestrating context stays small.
+
+## Usage
+
+Start the workflow with the upstream commit to sync to:
+
+```
+update-origin
+```
+
+`` is the upstream `angular/angular` commit to sync to.
+
+## How roles are executed
+
+Per-file migration instructions live in `roles/.md`. For each batch:
+
+- If your harness supports delegating work to sub-agents, pass the matching `roles/.md` as the instructions, together with the inputs the role lists (file paths, mode, directory), and delegate. Independent batches (for example different classes within one directory) may run in parallel.
+- If it does not, follow the same `roles/.md` yourself, one batch at a time, in the order given by this document.
+
+Each role returns the report shape defined in its "Output" section. The orchestrator (this workflow) reads that report before verifying or committing. When you follow a role yourself, you are also the orchestrator: produce the same report, then continue with verification and commits as this document describes.
+
+## Phase 0: Determine the correct sync target (MANDATORY)
+
+Never accept the user-provided `` or a tag SHA at face value. The actual production angular.dev may run a commit **after** the tag (e.g. v22.0.0 was tagged at `1cb0524f82` but production was on `fa546f382d` with `versions.json` v22 entry added in between).
+
+```bash
+# 1. Fetch the production angular.dev footer "Built by Angular at +sha-".
+# Fetch https://angular.dev/ and extract the short SHA.
+# 2. Verify the short SHA resolves in the origin submodule.
+# 3. If the user-supplied hash differs from the production SHA, report and confirm.
+```
+
+For **major-version syncs only**, also classify the new content (added markdown / config under `adev/src/content/`) and ask the user which pages to translate inside this PR. The rest go in `fix: migrate untranslated files`.
+
+## Phase 1: Preparation
+
+Run all of these in order, halting on any failure:
+
+```bash
+# 1. Branch
+git checkout -b update-origin-
+
+# 2. Verify current patches apply cleanly
+pnpm test
+
+# 3. Sync origin submodule (this updates .en.* files in adev-ja)
+pnpm update-origin
+```
+
+## Phase 2: Auto-commit
+
+```bash
+# 2a. Origin submodule commit
+git add origin
+git commit -m "chore: update origin to "
+
+# 2b. Untranslated files commit (files with no .en counterpart)
+git add $(git status --porcelain | grep -E "^\s*M.*\.(md|ts|html|json)$" | grep -v "\.en\." | cut -c4-)
+# Only commit if there is anything staged.
+git diff --cached --quiet || git commit -m "fix: migrate untranslated files"
+```
+
+### 2c. Handle upstream-deleted pages (MANDATORY: `update-origin.ts` does NOT cleanup)
+
+`update-origin.ts` only **copies**. Files that upstream deleted remain in `adev-ja/` as orphans and silently break navigation. List them and decide per file:
+
+```bash
+# Enumerate translated files (.en.* exists) whose upstream counterpart is gone.
+# (Same idea for untranslated .md without .en.md counterpart.)
+```
+
+For each orphan, **inspect the upstream deletion commit** (`git -C origin log --diff-filter=D --oneline -- `) and read its commit message **plus the additions in the same commit** to determine:
+
+- **Deleted = retired**: no replacement → remove from `adev-ja/` (translated `.en.*` + `.md` + untranslated `.md`)
+- **Deleted = moved/merged**: replacement exists upstream → record the mapping; if the prior JA translation is salvageable as reference for the replacement, preserve the `.md` as `.md.bak` (same convention as class-d structural). Delete the `.en.md` either way (no upstream original to snapshot).
+
+Commit per outcome:
+```bash
+git commit -m "chore: remove pages deleted upstream"
+git commit -m "chore: preserve removed page translations as .md.bak" # when applicable
+```
+
+Carry the move-target mapping forward to Phase 5.4 (PR body).
+
+## Phase 3: Markdown migration
+
+### 3.1 Classify
+
+```bash
+pnpm exec tsx .agents/skills/update-origin/scripts/classify-md-diff.ts > /tmp/md-classification.json
+```
+
+Read `/tmp/md-classification.json`. **Group entries by directory** (a directory may contain multiple classes).
+
+### 3.2 Dispatch: directory by directory
+
+For each directory, run every class present **before verifying**. The verifier is directory-scoped, so leaving any class unprocessed within a directory makes verification fail. Roles for different classes within one directory may run in parallel when delegation is available.
+
+| Class | Role | Behavior |
+| --- | --- | --- |
+| a | `roles/migrate-md-class-a.md` | Verify scope, no `.md` change |
+| b | `roles/migrate-md-class-b.md` | Apply small diffs preserving line correspondence |
+| c | `roles/migrate-md-class-c.md` | Paragraph-level retranslation |
+| d-additive | `roles/defer-md-class-d.md` (additive mode) | Restore prior translation, insert new English sections **as-is** at corresponding positions, then refresh `.en.md` |
+| d-structural | `roles/defer-md-class-d.md` (structural mode) | Backup `.md` → `.md.bak`, remove `.en.md`, reset to upstream English (full defer) |
+
+**Class-d sub-classification (mandatory)**: Inspect the diff between old upstream English and new upstream English.
+
+**Default to d-additive.** Only escalate to d-structural when **headings (h2 or h3) are deleted AND the count is ≥3** OR an h2 is deleted (any count). API rename, identifier swap inside fenced code blocks, table-column header changes, prose rewording with same intent, and new section insertion are **NEVER** structural signals: they are class-a/b/c/d-additive work. Quick objective gate before defer:
+
+```bash
+# Count deleted headings in the upstream diff.
+git -C origin diff .. -- \
+ | grep -E '^-(## |### )' | wc -l
+```
+
+If the count is <3 AND no h2 deletion, the file is d-additive. Run the class-d role in additive mode: keep the existing JA translation, insert new English sections at corresponding positions, mirror token renames in prose and tables.
+
+Only when h2 deletion or ≥3 h3 deletions occur, use d-structural (backup `.md` → `.md.bak`, remove `.en.md`, reset to upstream English).
+
+**Read the diff content, not just the +/- counts**: a "+30/-8" hunk where the 8 deletions are pure English rewording counts as additive.
+
+**Special-cased files (use a dedicated role regardless of class)**:
+
+| File | Role | Reason |
+| --- | --- | --- |
+| `adev-ja/src/content/reference/roadmap.md` | `roles/migrate-md-roadmap.md` | The roadmap accumulates completed work as historical records; in-progress items move to "Completed projects" rather than being replaced. The generic d roles would discard previously translated Completed entries. |
+
+**Process files only through the roles** (delegated, or followed one batch at a time when delegation is unavailable).
+
+### 3.3 Verify (per directory, after all classes done)
+
+Once every class batch in the directory has reported `status: ok`, run BOTH gates in order:
+
+```bash
+# (a) line-count + structural-marker alignment (fences/headings/annotations)
+pnpm exec tsx .agents/skills/update-origin/scripts/verify-migration.ts
+
+# (b) code-block content sync: every diff-added line inside fenced code blocks
+# must appear identically in the JA-side .md at the same line index.
+pnpm exec tsx .agents/skills/update-origin/scripts/verify-codeblock-sync.ts main
+```
+
+Additionally, scan every changed file for stale backtick identifiers (token rename mirror leaks): inline Python equivalent until promoted to a script:
+
+```bash
+# For each changed .en.md vs (the origin commit before this sync):
+# removed_tokens = backtick-set(old.en.md) - backtick-set(new.en.md)
+# leftover = removed_tokens ∩ backtick-set(.md)
+# leftover MUST be empty
+```
+
+A non-empty `leftover` set indicates the JA side still references identifiers that upstream renamed/removed (e.g. `values` → `value`, `panelId` → `panel`, `readonly` → host-element-application). Apply the rename and re-verify.
+
+Both exit codes MUST be 0. If either fails, stop the workflow and report; do not commit. The codeblock check is a global scan (cheap), not a per-directory scan, so it doubles as a regression gate across all previously committed directories.
+
+### 3.4 Commit per class within the directory
+
+Even though processing is grouped per directory, **commits remain per `(class, directory)`** so git history stays granular. After verify passes, for each class present in the directory (process in alphabetical order: a → b → c → d):
+
+**Commit messages are a public surface. They must describe what changed in the documentation, never the internal class letter, agent name, or script name** (see "Public-surface hygiene" below). Use the message that matches the class, with the class itself left unstated:
+
+```bash
+# Stage only the files belonging to that class within this directory
+git add
+
+# class a
+git commit -m "fix(docs): mirror upstream code and link changes in "
+# class b
+git commit -m "fix(docs): apply upstream wording changes in "
+# class c
+git commit -m "fix(docs): retranslate updated sections in "
+# class d, additive mode
+git commit -m "chore(docs): insert new upstream sections untranslated in "
+# class d, structural mode
+git commit -m "chore(docs): reset rewritten pages to upstream English in "
+```
+
+Repeat 3.2 → 3.4 for each directory until all markdown batches are processed.
+
+## Phase 4: Source-code migration
+
+### 4.1 Classify
+
+```bash
+pnpm exec tsx .agents/skills/update-origin/scripts/classify-src-diff.ts > /tmp/src-classification.json
+```
+
+**Group entries by directory** (same rationale as Phase 3.1).
+
+### 4.2 Dispatch: directory by directory
+
+For each directory, run every class present before verifying.
+
+| Class | Role | Behavior |
+| --- | --- | --- |
+| a | `roles/migrate-src-class-a.md` | Mirror code-only changes |
+| b | `roles/migrate-src-class-b.md` | Apply ≤5 translatable token changes |
+| c | `roles/migrate-src-class-c.md` | Structural + multi-token changes |
+| d | `roles/escalate-src-class-d.md` | Escalate to user: do NOT auto-modify |
+
+### 4.3 Verify (per directory)
+
+```bash
+pnpm exec tsx .agents/skills/update-origin/scripts/verify-migration.ts
+```
+
+### 4.4 Commit per class within the directory
+
+After verify passes, for each class present (a → b → c, **excluding d**). Same public-surface rule as Phase 3.4: the class letter never appears in the message.
+
+```bash
+git add
+# class a
+git commit -m "fix(app): mirror upstream code changes in "
+# class b / c
+git commit -m "fix(app): apply upstream changes to translated strings in "
+```
+
+For class-d escalations, **stop and report to user**. Do not commit class-d files until the user resolves them.
+
+## Phase 5: Final verification & PR
+
+```bash
+# 5.1 Patch / lint full pipeline
+pnpm test
+pnpm lint
+
+# 5.2 Working tree must be clean
+git status --porcelain # must be empty
+
+# 5.3 Angular.jp-specific configuration sanity check
+grep -n "indexName" build/src/environments/environment.ts || true # if applicable
+# Confirm fixed values per "Angular.jp specific configuration rules" in `AGENTS.md`
+
+# 5.4 Push to your own fork and create the PR against angular/angular-ja.
+# Check `git remote -v` first and use the remote that points to your fork.
+git push -u update-origin-
+gh pr create --repo angular/angular-ja --base main --head :update-origin- \
+ --title "chore: update origin to " \
+ --body "$(cat <<'EOF'
+## Summary
+Sync angular-ja with upstream angular/angular @ .
+
+## Scope
+- N markdown files and M source files updated against the new upstream English.
+- Notable content changes: describe them by what a reader of the site would notice, page by page.
+
+## Removed / moved pages (mandatory section)
+For each upstream-deleted file, list: prior JA path → move target upstream path (or "retired"). For preserved `.md.bak`, note their intended re-use (reference for new page translation or archive only). If upstream deleted nothing, say so explicitly.
+
+## Untranslated / deferred (mandatory section)
+- New pages copied untranslated this PR: list
+- Pages reset to upstream English with the prior translation kept as `.md.bak`: list, with the upstream reason per page
+- Pages where new upstream sections were inserted untranslated in place: list
+
+## Test plan
+- [x] pnpm test
+- [x] pnpm lint
+- [x] Line-count and structural-marker parity checked for all `.en.*` / translated pairs
+- [x] Fenced code-block content verified identical between English and Japanese for all changed files
+- [x] No backtick identifier removed upstream remains on the Japanese side
+EOF
+)"
+```
+
+Note: the PR is created **from `:` to `angular/angular-ja:main`** (fork to upstream). The `--repo angular/angular-ja --head :` form is required; the bare `--head ` form fails with "No commits between main and ".
+
+## Rules (apply throughout)
+
+- **Always** process per-file migration through the roles: delegated when your harness supports it, otherwise followed one batch at a time.
+- **Never** combine multiple `(class, directory)` batches into one commit.
+- **Never** verify until all classes in the directory have been processed (the verifier is directory-scoped).
+- **Never** skip `verify-migration.ts`. Its exit code is the gate.
+- **Never** edit `.patch` files manually. Use the Safe Patch Update Procedure if patches fail.
+- **Always** match `wc -l ` exactly for markdown: no exceptions.
+- **Always** preserve **line-prefix** docs annotations `IMPORTANT:`, `NOTE:`, `HELPFUL:`, `TIP:`, `CRITICAL:`, `WARNING:`, `TLDR:` literally in uppercase English when they appear at the start of a line (optionally after blockquote `>`). Never lowercase or translate them (no `Tip:`, `重要:`, `警告:`, `ヒント:`). The adev build pipeline relies on these uppercase line-leading markers. The same words appearing inside heading text or mid-sentence are normal prose and may be translated.
+- **Always** stop and report on any role report with `status: needs_review` / `aborted` / `escalate`.
+
+## Public-surface hygiene (absolute)
+
+Commit messages, PR titles, and PR bodies are read by upstream maintainers and are permanent in GitHub's history. They must contain **nothing** from this workflow's internal tooling. The following vocabulary is forbidden on those surfaces:
+
+- Class letters and their labels: `class-a`, `md-class-b`, `src-class-c`, `class-d`, `d-additive`, `d-structural`, `md-roadmap`
+- Script names: `classify-md-diff.ts`, `classify-src-diff.ts`, `verify-migration.ts`, `verify-codeblock-sync.ts`
+- Role names: `migrate-md-class-*`, `defer-md-class-d`, `escalate-src-class-d`, `migrate-src-class-*`, `migrate-md-roadmap`, `prh-terminology`
+- Any `.agents/` path, any skill or role name, and any absolute home path
+- The word "orchestrator" and other descriptions of the internal agent topology
+
+Describe the work by what changed in the documentation and how it was checked, not by which internal machinery produced it. "Line-count and structural-marker parity checked for all pairs" is publishable; "verify-migration.ts exits 0" is not.
+
+Every commit message this workflow writes is subject to this rule. Applying it after the fact requires rewriting history on a pushed branch, which leaves the original text reachable through the PR's force-push timeline, so the leak is never fully undone. Get it right on the first commit.
+
+## Prose style (applies to PR bodies and any Japanese output)
+
+- Never use an em dash (`—`). Use a colon, parentheses, or a separate sentence.
+- Avoid the punctuation habits that mark machine-generated text: em-dash asides, "not X, but Y" parallelism as a tic, and decorative arrows in prose.
+
+## Scripts (in `scripts/` of this skill)
+
+- `scripts/classify-md-diff.ts`: markdown diff classifier
+- `scripts/classify-src-diff.ts`: source-code diff classifier
+- `scripts/verify-migration.ts`: deterministic verifier (presence + line count + structural-marker alignment)
+- `scripts/verify-codeblock-sync.ts`: verifies that every diff-added line inside fenced code blocks is mirrored to the JA-side `.md`
+
+## Roles (in `roles/` of this skill)
+
+- `roles/migrate-md-class-{a,b,c}.md`, `roles/defer-md-class-d.md`, `roles/migrate-md-roadmap.md`
+- `roles/migrate-src-class-{a,b,c}.md`, `roles/escalate-src-class-d.md`
diff --git a/.agents/skills/update-origin/roles/defer-md-class-d.md b/.agents/skills/update-origin/roles/defer-md-class-d.md
new file mode 100644
index 0000000000..f58c7b9189
--- /dev/null
+++ b/.agents/skills/update-origin/roles/defer-md-class-d.md
@@ -0,0 +1,136 @@
+# Role: defer-md-class-d
+
+Handle class-d markdown files during Phase 3 of the update-origin workflow. The orchestrator chooses one of two modes per file based on the actual content of the upstream diff.
+
+## Mode selection (assigned by orchestrator)
+
+The orchestrator inspects `git show main: | diff - ` and tells you which mode to run:
+
+**Default mode is d-additive.** Only escalate to d-structural under the objective gate below.
+
+- **`d-additive`** (default): Existing Japanese translation must survive; new English sections are inserted as-is at corresponding positions and flagged for follow-up translation. Token renames (`values` → `value`, `panelId` → `panel`, etc.) in prose and tables MUST be mirrored to JA on the same line. This is normal additive work, not a structural signal.
+- **`d-structural`**: Reserved for cases where the page has been so heavily restructured that the prior translation is unsalvageable. **Objective gate**: at least one h2 deletion OR ≥3 h3 deletions in the upstream diff. Count with:
+
+ ```bash
+ git -C origin diff cea6588bb3.. -- adev/.md | grep -E '^-(## |### )' | wc -l
+ ```
+
+ API rename, identifier swap, table-column changes, prose rewording, and new section insertion are **NEVER** structural signals by themselves. If the gate is not met, run additive even if you feel uncertain.
+
+## Input
+
+- A list of `.en.md` file paths under one directory.
+- A mode (`d-additive` or `d-structural`).
+
+The post-`pnpm update-origin` state is: `.en.md` already reflects new upstream English; `.md` still holds the old Japanese translation.
+
+---
+
+## Mode A: `d-additive`
+
+**Goal**: keep the existing Japanese translation; only insert the new English blocks (untranslated) at the corresponding positions, and refresh `.en.md`.
+
+### Procedure (per file)
+
+1. **Compute the additive diff** between old upstream English and new upstream English:
+ ```bash
+ git show main: > /tmp/old.en.md
+ diff /tmp/old.en.md
+ ```
+ Each hunk should be either pure additions (`>` lines only) or near-additive (≤2 `<` lines whose deletion does not affect the corresponding Japanese meaning).
+
+2. **Read the current `.md`** (existing Japanese translation, line-aligned with the OLD `.en.md`).
+
+3. **For each additive hunk**, find the anchor in the existing `.md` (use the surrounding unchanged context lines from the diff, which are the same in old `.en.md` and current `.md` line indices) and **insert the new English block as-is** at that position. Do NOT translate inserted blocks; that is a follow-up.
+
+4. **For near-additive hunks** (English rewording that does not change Japanese meaning), leave the existing Japanese line untouched. Do not replace working translations with English.
+
+5. **Verify line count parity** with the new `.en.md`:
+ ```bash
+ wc -l
+ ```
+ Counts MUST be identical.
+
+6. **Verify structural alignment** (fenced code fences, headings, line-prefix annotations) by running:
+ ```bash
+ pnpm exec tsx .agents/skills/update-origin/scripts/verify-migration.ts
+ ```
+ Exit code MUST be 0.
+
+### Output (additive)
+
+```json
+{
+ "class": "d",
+ "mode": "d-additive",
+ "directory": "",
+ "files": [
+ {
+ "file": "",
+ "status": "ok" | "needs_review",
+ "inserted_blocks": [""]
+ }
+ ]
+}
+```
+
+The orchestrator commits the result (message per Phase 3.4 of `SKILL.md`). Additive blocks are deferred translation work, just embedded in place rather than as `.md.bak`.
+
+---
+
+## Mode B: `d-structural`
+
+**Goal**: Full defer. Reset `.md` to upstream English; preserve prior Japanese as `.md.bak` for human re-translation reference.
+
+### Procedure (per file)
+
+1. **Back up the existing Japanese translation**:
+ ```bash
+ mv
+ ```
+
+2. **Delete the `.en.md`** (the new English source will replace it as plain English):
+ ```bash
+ rm
+ ```
+
+3. **Restore the upstream English file at the translated-file path** (untranslated state):
+ ```bash
+ cp origin/adev//.md
+ ```
+ Use the path relative to `origin/adev/` corresponding to the file under `adev-ja/`.
+
+ Result: `` is now plain English (matching upstream), `` holds the previous Japanese translation for future re-translation, and `` is absent, meaning "untranslated" by project convention.
+
+4. **Verify**:
+ - `` exists and matches `origin/adev/.../.md` byte-for-byte.
+ - `` exists.
+ - `` does NOT exist.
+
+### Output (structural)
+
+```json
+{
+ "class": "d",
+ "mode": "d-structural",
+ "directory": "",
+ "files": [
+ {
+ "file": "",
+ "backup": ".bak",
+ "status": "deferred"
+ }
+ ]
+}
+```
+
+The orchestrator commits the result (message per Phase 3.4 of `SKILL.md`).
+
+---
+
+## Rules (apply to both modes)
+
+- Do **not** translate any inserted English in this role. Translation is a follow-up.
+- Do **not** modify untouched Japanese content.
+- In `d-additive`, never silently switch to `d-structural` mid-file. If a hunk turns out to be non-additive, abort with `status: needs_review` and report the hunk; the orchestrator will re-route as `d-structural`.
+- Do **not** commit. The orchestrator commits per directory.
diff --git a/.agents/skills/update-origin/roles/escalate-src-class-d.md b/.agents/skills/update-origin/roles/escalate-src-class-d.md
new file mode 100644
index 0000000000..eff1c8c1b0
--- /dev/null
+++ b/.agents/skills/update-origin/roles/escalate-src-class-d.md
@@ -0,0 +1,65 @@
+# Role: escalate-src-class-d
+
+Escalate class-d source-code files (full rewrite, fundamental restructure) to user judgment. Source code cannot be reset to an untranslated state like markdown, so a human decision is required. Used in Phase 4 of the update-origin workflow.
+
+## Scope
+
+Class-d for source code means the file is fundamentally restructured:
+
+- The file is effectively a different file now
+- More than 100 total line changes with high translatable-token density
+- Auto-migration would risk breaking navigation / templates / app behavior
+
+Unlike markdown class-d, source-code files **cannot** be reset to "untranslated" state: they must remain functional. So this role **does NOT auto-defer**. It collects context and **escalates to the user**.
+
+## Input
+
+A list of `.en.{ts,html,json}` file paths.
+
+## Procedure
+
+For each `.en.*` file:
+
+1. **Capture diff and current state**:
+ ```bash
+ git diff HEAD -- > /tmp/.diff
+ wc -l
+ ```
+
+2. **Summarize**:
+ - File path
+ - Diff size (lines added / removed)
+ - Translatable token density estimate
+ - Risk areas (e.g., "removed 12 navigation entries that have Japanese translations", "wholesale template rewrite")
+
+3. **Leave the working tree as is**: keep both `.en.*` and translated file as currently checked out by `pnpm update-origin`. Do NOT commit, do NOT translate, do NOT delete.
+
+## Completion Conditions
+
+- Working tree is unchanged for these files (still showing the modified state).
+- A consolidated escalation report is produced.
+
+## Output
+
+```json
+{
+ "class": "d",
+ "kind": "src",
+ "status": "escalate",
+ "files": [
+ {
+ "file": "",
+ "diffSize": { "added": , "removed": },
+ "risk": "",
+ "recommendedAction": "manual-review" | "split-into-smaller-pr" | "defer-to-followup-pr"
+ }
+ ],
+ "message": "These source-code files require human review before the origin sync can complete. Proposed actions inline."
+}
+```
+
+## Rules
+
+- Do **not** translate, edit, or revert anything.
+- Do **not** commit.
+- Surface the escalation report to the orchestrator. The orchestrator pauses Phase 4 until the user resolves these files manually or instructs how to proceed.
diff --git a/.agents/skills/update-origin/roles/migrate-md-class-a.md b/.agents/skills/update-origin/roles/migrate-md-class-a.md
new file mode 100644
index 0000000000..270b8cd991
--- /dev/null
+++ b/.agents/skills/update-origin/roles/migrate-md-class-a.md
@@ -0,0 +1,77 @@
+# Role: migrate-md-class-a
+
+Apply class-a markdown migration: low-risk changes that nevertheless may need to be mirrored to the Japanese `.md` (code in fenced blocks, URLs, prose identifiers). Used in Phase 3 of the update-origin workflow for batches classified as class a by `classify-md-diff.ts`.
+
+## Scope
+
+Class-a means the diff is **small and structurally simple**, but it is NOT automatically a no-op for the Japanese side. You MUST mirror three categories of change to the `.md`:
+
+1. **Code inside fenced code blocks**: the code is shared verbatim with the upstream English; if upstream renames an import, identifier, or value, the Japanese-side fenced block must be updated to the same code.
+2. **URLs / link paths in markdown links**: the `](path)` value must match upstream exactly (e.g. `(guide/components)` → `(/guide/components)`).
+3. **Code identifiers in prose backticks**: when a backtick token in prose changes (e.g. `item.key` → `item.value`, `getHarnesses` → `getAllHarnesses`), the Japanese prose on the same line must adopt the new token.
+
+The Japanese `.md` only stays byte-identical when ALL of the following hold:
+
+- The diff is purely an English-prose typo (e.g. `manubar` → `menubar`, `exisiting` → `existing`) where the Japanese translation already conveyed the correct meaning naturally.
+- No code inside fenced blocks changed.
+- No URL / link path changed.
+- No backtick identifier in prose changed.
+
+## Input
+
+A list of `.en.md` file paths (already updated on disk by `pnpm update-origin`). All files share one directory.
+
+## Procedure
+
+1. **Inspect the diff**: For each `.en.md`, run:
+ ```bash
+ git diff HEAD --
+ ```
+ Walk EVERY hunk and classify each change:
+ - (a) Fenced code block content change → mirror to `.md`.
+ - (b) URL / link path change → mirror to `.md`.
+ - (c) Backtick identifier change in prose → mirror to `.md` on the same line (Japanese prose around it stays).
+ - (d) Pure English-prose typo with no semantic identifier change → leave `.md` unchanged.
+ - (e) Anything else (semantic prose change, structural change) → abort and report; this is not class-a.
+
+2. **Apply mirroring edits to `.md`** for any (a), (b), (c) changes from step 1. Each edit must be on the SAME line index as the corresponding change in `.en.md` (line correspondence is preserved). Do NOT translate; just replace the changed token / URL / code while keeping the surrounding Japanese prose intact.
+
+3. **Verify line count parity**:
+ ```bash
+ wc -l
+ ```
+ Counts MUST be identical.
+
+4. **Per-line spot check**: For each line touched in `.en.md`, read the same line index in `.md` and confirm:
+ - All backtick code tokens that appear in `.en.md`'s line also appear in `.md`'s line (substring match).
+ - All `](url)` link paths in `.en.md`'s line also appear in `.md`'s line.
+ - Any fenced code block content matches verbatim between sides.
+
+ If a token / URL is missing on the JA side, fix it before completing.
+
+## Completion Conditions (all required)
+
+- Every (a), (b), (c) hunk has been mirrored to the corresponding `.md` line.
+- `wc -l` matches between every `.en.md` and `.md`.
+- Per-line spot check passes for all changed lines.
+
+## Output
+
+Return a JSON-shaped summary:
+
+```json
+{
+ "class": "a",
+ "directory": "",
+ "files": ["", ...],
+ "status": "ok" | "aborted",
+ "reason": ""
+}
+```
+
+## Rules
+
+- Do **not** translate. Class-a only mirrors code / URL / identifier-token changes; surrounding Japanese prose stays.
+- Do **not** rewrite or restructure paragraphs. If the diff requires that, abort with `status: aborted` and report; the orchestrator will reclassify.
+- Do **not** run `pnpm lint --fix` here.
+- Do **not** commit. The orchestrator commits per directory.
diff --git a/.agents/skills/update-origin/roles/migrate-md-class-b.md b/.agents/skills/update-origin/roles/migrate-md-class-b.md
new file mode 100644
index 0000000000..7ee9fa85c8
--- /dev/null
+++ b/.agents/skills/update-origin/roles/migrate-md-class-b.md
@@ -0,0 +1,86 @@
+# Role: migrate-md-class-b
+
+Apply class-b markdown migration: small diffs (≤ 5 prose lines, no paragraph restructure). Used in Phase 3 of the update-origin workflow for batches classified as class b by `classify-md-diff.ts`.
+
+## Scope
+
+Class-b means the `.en.md` diff is small and localized:
+
+- Up to 5 prose lines changed (added/removed/modified)
+- No paragraph-level restructuring
+- Sentence-level rewording, supplementary phrases, link/text additions
+
+The corresponding `.md` file needs **partial diff application** preserving line-by-line correspondence with `.en.md`.
+
+## Input
+
+A list of `.en.md` file paths in a single directory.
+
+## Procedure
+
+For each `.en.md` file:
+
+1. **Read the diff**:
+ ```bash
+ git diff HEAD --
+ ```
+
+2. **Read the .en.md source** (post-update-origin state). This is the authoritative English text.
+
+3. **Read the corresponding `.md` file**.
+
+4. **Apply parallel changes to `.md`**:
+ - For each changed hunk in `.en.md`, locate the same line range in `.md`.
+ - Translate the changed English content to Japanese (follow the `prh-terminology` skill for terminology if uncertain).
+ - Preserve unchanged surrounding lines exactly.
+ - Maintain line-by-line correspondence: every `.en.md` line index must have a matching `.md` line index.
+ - **CRITICAL: Preserve special-prefix annotations at line start.** When a line begins with one of `IMPORTANT:`, `NOTE:`, `HELPFUL:`, `TIP:`, `CRITICAL:`, `WARNING:`, `TLDR:` (optionally after blockquote `>` markers), it is a docs annotation recognized by the adev build pipeline. The prefix MUST stay literally in uppercase English (e.g. `IMPORTANT: `; `Tip:` is wrong, `重要:` is wrong, `警告:` is wrong). Translate only the body after the colon. This rule applies ONLY to line-leading prefixes; the same words appearing inside headings or mid-sentence are normal text and may be translated naturally.
+
+5. **Verify line count parity**:
+ ```bash
+ wc -l
+ ```
+ Counts MUST be identical. If different, fix immediately.
+
+6. **Run lint on this file**:
+ ```bash
+ pnpm lint --fix
+ pnpm lint
+ ```
+ Both must succeed.
+
+After processing all files:
+
+7. **Run migration verifier on the directory**:
+ ```bash
+ pnpm exec tsx .agents/skills/update-origin/scripts/verify-migration.ts
+ ```
+ Exit code must be 0.
+
+## Completion Conditions (all required)
+
+- Every `.en.md` and `.md` pair has identical line count.
+- Every changed hunk has a corresponding Japanese-translated change in `.md`.
+- `pnpm lint` passes for all modified `.md`.
+- `verify-migration.ts ` exits 0.
+
+## Output
+
+```json
+{
+ "class": "b",
+ "directory": "",
+ "files": ["", ...],
+ "status": "ok" | "needs_review",
+ "issues": [{ "file": "", "issue": "" }]
+}
+```
+
+If line counts cannot be reconciled or translation requires paragraph-level rewrite, set status to `needs_review` and report. Do **not** silently escalate to class-c behavior.
+
+## Rules
+
+- Follow the `prh-terminology` skill for any uncertain terminology.
+- Apply the project translation conventions (see "Translation Rules Reference" in `AGENTS.md`).
+- Do **not** rewrite untouched paragraphs. Class-b is strictly localized changes.
+- Do **not** commit. The orchestrator commits per directory.
diff --git a/.agents/skills/update-origin/roles/migrate-md-class-c.md b/.agents/skills/update-origin/roles/migrate-md-class-c.md
new file mode 100644
index 0000000000..34ebdc632e
--- /dev/null
+++ b/.agents/skills/update-origin/roles/migrate-md-class-c.md
@@ -0,0 +1,90 @@
+# Role: migrate-md-class-c
+
+Apply class-c markdown migration: paragraph-level retranslation (≤ 30 prose lines, structural change within file). Used in Phase 3 of the update-origin workflow for batches classified as class c by `classify-md-diff.ts`.
+
+## Scope
+
+Class-c means the `.en.md` diff requires paragraph-level retranslation:
+
+- 6–30 prose lines changed
+- Paragraph rewriting, multiple sentence rewordings, structural reordering within the file
+- Page identity preserved (still the same topic / chapter)
+
+The corresponding `.md` file needs **paragraph-level retranslation** preserving heading IDs and line-by-line correspondence with `.en.md`.
+
+## Input
+
+A list of `.en.md` file paths in a single directory.
+
+## Procedure
+
+For each `.en.md` file:
+
+1. **Read the full diff** to understand intent:
+ ```bash
+ git diff HEAD --
+ ```
+
+2. **Read both `.en.md` (post-update) and `.md` in full** to ground retranslation in context.
+
+3. **Identify affected paragraphs** in `.en.md` (group changed hunks by paragraph).
+
+4. **Retranslate each affected paragraph**:
+ - Translate the new English paragraph to Japanese.
+ - Follow the `prh-terminology` skill for terminology consistency.
+ - Apply the project translation rules (see "Translation Rules Reference" in `AGENTS.md`):
+ - Italic: `*text*` not `_text_`
+ - Standardized terminology: 基本ガイド, 真の情報源, 即座に, 最新情報, etc.
+ - Heading IDs: `{#kebab-case}` on `##` and `###` headings
+ - **CRITICAL: Preserve special-prefix annotations at line start.** When a line begins with one of `IMPORTANT:`, `NOTE:`, `HELPFUL:`, `TIP:`, `CRITICAL:`, `WARNING:`, `TLDR:` (optionally after blockquote `>` markers), it is a docs annotation recognized by the adev build pipeline. The prefix MUST stay literally in uppercase English (e.g. `IMPORTANT: `; `Tip:` is wrong, `重要:` is wrong, `警告:` is wrong). Translate only the body after the colon. This rule applies ONLY to line-leading prefixes; the same words appearing inside headings or mid-sentence are normal text and may be translated naturally.
+ - Preserve untouched paragraphs verbatim.
+ - Maintain line-by-line correspondence with `.en.md`.
+
+5. **Verify line count parity**:
+ ```bash
+ wc -l
+ ```
+ Counts MUST be identical.
+
+6. **Run lint on this file**:
+ ```bash
+ pnpm lint --fix
+ pnpm lint
+ ```
+
+After processing all files:
+
+7. **Run migration verifier on the directory**:
+ ```bash
+ pnpm exec tsx .agents/skills/update-origin/scripts/verify-migration.ts
+ ```
+ Exit code must be 0.
+
+## Completion Conditions (all required)
+
+- Every `.en.md` and `.md` pair has identical line count.
+- All paragraph-level changes in `.en.md` are reflected in `.md` with quality Japanese translation.
+- Heading IDs are preserved or added per project convention.
+- `pnpm lint` passes for all modified `.md`.
+- `verify-migration.ts ` exits 0.
+
+## Output
+
+```json
+{
+ "class": "c",
+ "directory": "",
+ "files": ["", ...],
+ "status": "ok" | "needs_review",
+ "issues": [{ "file": "", "issue": "" }]
+}
+```
+
+If the change scope expanded beyond class-c during analysis (e.g., page identity changed, > 50% of file rewritten), set status to `needs_review` with reason. Do **not** silently proceed as if it were class-c. The orchestrator will route it to the class-d defer flow.
+
+## Rules
+
+- Always read the source `.en.md` before translating. Never modify `.md` based on "what sounds natural" alone.
+- Follow the `prh-terminology` skill for terminology decisions.
+- Preserve `.md` content for unchanged paragraphs verbatim. Do not opportunistically refactor.
+- Do **not** commit. The orchestrator commits per directory.
diff --git a/.agents/skills/update-origin/roles/migrate-md-roadmap.md b/.agents/skills/update-origin/roles/migrate-md-roadmap.md
new file mode 100644
index 0000000000..c5d8c80705
--- /dev/null
+++ b/.agents/skills/update-origin/roles/migrate-md-roadmap.md
@@ -0,0 +1,72 @@
+# Role: migrate-md-roadmap
+
+Specialized migration handler for `adev-ja/src/content/reference/roadmap.md`. The roadmap evolves by accumulating completed work as historical records, so it cannot be migrated by the generic class-a/b/c/d roles. Used in Phase 3 of the update-origin workflow whenever `roadmap.md` appears in the classifier output.
+
+## Why roadmap.md is special
+
+Each Angular release reshapes the roadmap by **moving in-progress items to "Completed projects"** as historical records, **adding new items**, and **occasionally deleting outdated items**. The previous Japanese translation must be preserved wherever possible: Completed projects already translated should remain in Japanese as historical documentation, even when the upstream restructured the surrounding sections.
+
+## Inputs
+
+- `adev-ja/src/content/reference/roadmap.md`: current state: upstream English (defer applied by the orchestrator before this role runs).
+- `adev-ja/src/content/reference/roadmap.md.bak`: previous Japanese translation.
+- Old upstream English: `git show main:adev-ja/src/content/reference/roadmap.en.md`.
+- New upstream English: same as current `.md`.
+
+## Hunk Classification (mandatory)
+
+Walk every hunk in `git show main:adev-ja/src/content/reference/roadmap.en.md | diff - adev-ja/src/content/reference/roadmap.md` and classify each:
+
+| Pattern | Action |
+| --- | --- |
+| **Pure addition in In-progress / Available / Future sections** (new `` or list entry, no removal) | Insert as English-as-is at the corresponding section; flag for follow-up translation |
+| **`` removed from in-progress AND added to "Completed projects" with `link="Completed in ..."`** | Move the **Japanese-translated** `` block (from `.md.bak`) to the "Completed projects" section; do NOT replace with English. Adjust the heading's link attribute to match upstream's `Completed in ...` text. |
+| **In-progress `` body rewritten without moving to Completed** (same title, different prose / additional context) | Keep the previous Japanese title and surrounding structure; replace the body prose with the new English as-is. Translation is a follow-up. |
+| **Whole section removed** (e.g. the "Future work, explorations, and prototyping" header + its body) | Delete the corresponding Japanese section entirely from the working copy (it is no longer in upstream). |
+| **Whole new section added** (new H2 with content) | Insert the new section in English as-is. |
+| **Goals list expanded** (e.g. "two goals" → "three goals", with new bullet) | Update the count word and add the new bullet in English at the corresponding position. |
+| **Stylistic English-only edit** (e.g. removing trailing `href=""` attribute, list markers `1.` vs `2.`) | Mirror the change so line correspondence stays exact; the visible Japanese prose around it remains. |
+
+## Procedure
+
+1. Verify the inputs exist (`.md`, `.md.bak`, plus `git show main:...en.md` succeeds). If not, abort.
+2. Compute the diff and classify every hunk per the table above. Build an internal plan listing each hunk and its action.
+3. Construct the result `.md` starting from `.md.bak` and applying the planned actions in upstream-line order:
+ - Preserve every Japanese sentence that has not been touched by upstream.
+ - Move Completed-projects entries from their old position to the new Completed-projects section, **keeping the Japanese text**.
+ - Insert / replace English sections per the classification.
+4. Recreate `.en.md` by copying `origin/adev/src/content/reference/roadmap.md`.
+5. Delete `.md.bak`.
+6. Verify `wc -l ` are identical.
+7. Per-line spot check: each hunk should have produced a `.md` line-aligned with the new `.en.md`.
+8. Run `pnpm exec tsx .agents/skills/update-origin/scripts/verify-migration.ts adev-ja/src/content/reference`.
+
+## Completion Conditions (all required)
+
+- Every hunk has been classified and applied.
+- Completed-projects entries that were merely moved (not rewritten) retain their previous Japanese translation.
+- New / replaced sections are inserted in English as-is, line-aligned.
+- Removed sections are gone.
+- `wc -l` parity holds.
+- `.md.bak` is deleted; `.en.md` is regenerated from upstream.
+
+## Output
+
+```json
+{
+ "file": "adev-ja/src/content/reference/roadmap.md",
+ "status": "ok" | "needs_review" | "aborted",
+ "hunks": [
+ { "kind": "added-in-progress" | "moved-to-completed" | "rewritten-in-place" | "section-removed" | "section-added" | "goal-list-update" | "stylistic", "summary": "...", "translation_preserved": true | false }
+ ],
+ "lines": ,
+ "follow_up_translation_needed": [""]
+}
+```
+
+## Rules
+
+- **Never** wholesale-replace the file with English (the generic d-structural defer). Roadmap accumulates history; previous Completed translations must survive.
+- **Never** translate inserted English (in-progress additions). That is a follow-up PR.
+- **Always** preserve Japanese for items that were merely repositioned (in-progress → Completed) without prose rewrite.
+- **Do not** commit; the orchestrator commits per directory.
diff --git a/.agents/skills/update-origin/roles/migrate-src-class-a.md b/.agents/skills/update-origin/roles/migrate-src-class-a.md
new file mode 100644
index 0000000000..02b8a32abb
--- /dev/null
+++ b/.agents/skills/update-origin/roles/migrate-src-class-a.md
@@ -0,0 +1,77 @@
+# Role: migrate-src-class-a
+
+Apply class-a source-code migration: changes that do NOT affect translatable tokens (logic / type / structure only). For `.ts` / `.html` / `.json` files. Used in Phase 4 of the update-origin workflow.
+
+## Scope
+
+Class-a means the diff in `.en.{ts,html,json}` does **not** touch translatable text:
+
+- Pure code-logic / type / import changes
+- Non-text attribute changes in HTML
+- Schema-only JSON changes (no user-facing strings)
+
+The corresponding `.{ts,html,json}` should follow the same code-level changes but its translated strings remain untouched.
+
+## Input
+
+A list of `.en.{ts,html,json}` file paths in a single directory.
+
+## Procedure
+
+For each `.en.*` file:
+
+1. **Read the diff**:
+ ```bash
+ git diff HEAD --
+ ```
+ Confirm no translatable token (label/title/description/text-node/JSON user-facing string) is affected. If any is, **abort and report**.
+
+2. **Apply the same code-level changes to the translated file** while preserving Japanese strings:
+ - For `.ts`: mirror import / type / logic changes, keep `label:` / `title:` etc. values in Japanese.
+ - For `.html`: mirror tag / attribute / directive changes, keep text nodes in Japanese.
+ - For `.json`: mirror non-string-value changes, keep translated string values intact.
+
+3. **Verify the translated file is syntactically valid**:
+ - `.ts` / `.html`: rely on `pnpm lint`.
+ - `.json`: ensure `JSON.parse` succeeds (project test step covers this).
+
+4. **Run lint on this file**:
+ ```bash
+ pnpm lint --fix
+ pnpm lint
+ ```
+
+After processing all files:
+
+5. **Run migration verifier on the directory**:
+ ```bash
+ pnpm exec tsx .agents/skills/update-origin/scripts/verify-migration.ts
+ ```
+ Exit code must be 0.
+
+## Completion Conditions
+
+- Every `.en.*` and translated pair exists.
+- Code-level changes mirrored in translated file.
+- No Japanese translation strings altered.
+- `pnpm lint` passes.
+- `verify-migration.ts` exits 0.
+
+## Output
+
+```json
+{
+ "class": "a",
+ "kind": "src",
+ "directory": "",
+ "files": ["", ...],
+ "status": "ok" | "aborted",
+ "reason": ""
+}
+```
+
+## Rules
+
+- Do **not** alter translated user-facing strings.
+- Source code line counts are NOT enforced (unlike markdown).
+- Do **not** commit. The orchestrator commits per directory.
diff --git a/.agents/skills/update-origin/roles/migrate-src-class-b.md b/.agents/skills/update-origin/roles/migrate-src-class-b.md
new file mode 100644
index 0000000000..fadb519335
--- /dev/null
+++ b/.agents/skills/update-origin/roles/migrate-src-class-b.md
@@ -0,0 +1,76 @@
+# Role: migrate-src-class-b
+
+Apply class-b source-code migration: ≤ 5 translatable token changes in `.ts` / `.html` / `.json`. Used in Phase 4 of the update-origin workflow.
+
+## Scope
+
+Class-b means the `.en.{ts,html,json}` diff includes a small number of translatable-token changes:
+
+- Up to 5 lines that contain translatable strings (`label:`, `title:`, text nodes, user-facing JSON values) added / changed
+- No structural reorganization
+
+## Input
+
+A list of `.en.{ts,html,json}` file paths in a single directory.
+
+## Procedure
+
+For each `.en.*` file:
+
+1. **Read the diff**:
+ ```bash
+ git diff HEAD --
+ ```
+
+2. **Identify translatable changes**:
+ - `.ts`: `label`, `title`, `description`, `message`, `summary`, `placeholder`, `tooltip`, `action`
+ - `.html`: text nodes, `aria-label`, `title=""`, `alt=""`
+ - `.json`: user-facing string values
+
+3. **Apply the changes to the translated file**:
+ - Mirror non-translatable changes (paths, identifiers, types, directives) verbatim.
+ - Translate new / changed user-facing strings to Japanese (follow the `prh-terminology` skill for terminology).
+ - Preserve technical identifiers, paths, `contentPath`, Angular directives.
+
+4. **Verify the file remains parseable**:
+ - For JSON: `node -e 'JSON.parse(require("fs").readFileSync("","utf8"))'` must succeed.
+
+5. **Run lint on this file**:
+ ```bash
+ pnpm lint --fix
+ pnpm lint
+ ```
+
+After processing all files:
+
+6. **Run migration verifier on the directory**:
+ ```bash
+ pnpm exec tsx .agents/skills/update-origin/scripts/verify-migration.ts
+ ```
+
+## Completion Conditions
+
+- All translatable token changes applied with Japanese translation.
+- Non-translatable changes mirrored.
+- File remains syntactically valid.
+- `pnpm lint` passes.
+- `verify-migration.ts` exits 0.
+
+## Output
+
+```json
+{
+ "class": "b",
+ "kind": "src",
+ "directory": "",
+ "files": ["", ...],
+ "status": "ok" | "needs_review",
+ "issues": [{ "file": "", "issue": "" }]
+}
+```
+
+## Rules
+
+- Follow the `prh-terminology` skill for any uncertain terminology.
+- Preserve existing Japanese translations for unchanged strings.
+- Do **not** commit. The orchestrator commits per directory.
diff --git a/.agents/skills/update-origin/roles/migrate-src-class-c.md b/.agents/skills/update-origin/roles/migrate-src-class-c.md
new file mode 100644
index 0000000000..d81e38fd4b
--- /dev/null
+++ b/.agents/skills/update-origin/roles/migrate-src-class-c.md
@@ -0,0 +1,80 @@
+# Role: migrate-src-class-c
+
+Apply class-c source-code migration: > 5 translatable token changes or structural changes in `.ts` / `.html` / `.json`. Used in Phase 4 of the update-origin workflow.
+
+## Scope
+
+Class-c means significant translatable-content change:
+
+- More than 5 lines with translatable strings changed, OR
+- Structural reorganization (e.g., navigation tree restructure in `sub-navigation-data.ts`, multi-section template changes)
+
+The corresponding translated file requires structural follow-through plus translation of all new / changed user-facing strings.
+
+## Input
+
+A list of `.en.{ts,html,json}` file paths in a single directory.
+
+## Procedure
+
+For each `.en.*` file:
+
+1. **Read the full diff**:
+ ```bash
+ git diff HEAD --
+ ```
+
+2. **Read both `.en.*` and translated file in full** to understand structural context.
+
+3. **Apply structural changes to the translated file**:
+ - Mirror new / removed / reordered entries.
+ - Preserve existing Japanese translations for entries that survived the restructure (match by path / identifier / key, NOT by line position).
+ - Translate new entries to Japanese (follow the `prh-terminology` skill for terminology).
+ - Preserve technical identifiers: `path`, `contentPath`, route names, component selectors, JSON technical keys.
+
+4. **Verify syntactic validity**:
+ - JSON: `JSON.parse` must succeed.
+ - TS / HTML: lint must pass.
+
+5. **Run lint on this file**:
+ ```bash
+ pnpm lint --fix
+ pnpm lint
+ ```
+
+After processing all files:
+
+6. **Run migration verifier on the directory**:
+ ```bash
+ pnpm exec tsx .agents/skills/update-origin/scripts/verify-migration.ts
+ ```
+
+## Completion Conditions
+
+- All structural changes mirrored.
+- All new / changed user-facing strings translated.
+- All previously-existing Japanese translations preserved (match by identifier, not position).
+- `pnpm lint` passes.
+- `verify-migration.ts` exits 0.
+
+## Output
+
+```json
+{
+ "class": "c",
+ "kind": "src",
+ "directory": "",
+ "files": ["", ...],
+ "status": "ok" | "needs_review",
+ "issues": [{ "file": "", "issue": "" }]
+}
+```
+
+If the change scope expanded beyond class-c (e.g., the file is fundamentally a different file now), set status to `needs_review` and report. The orchestrator routes it to the `escalate-src-class-d` role.
+
+## Rules
+
+- Always match preserved entries by identifier (`path`, key, selector), not by line number.
+- Follow the `prh-terminology` skill for terminology decisions.
+- Preserve `path` / `contentPath` / Angular directives / JSON technical keys verbatim.
+- Do **not** commit. The orchestrator commits per directory.
diff --git a/.agents/skills/update-origin/scripts/classify-md-diff.ts b/.agents/skills/update-origin/scripts/classify-md-diff.ts
new file mode 100644
index 0000000000..661d4313ef
--- /dev/null
+++ b/.agents/skills/update-origin/scripts/classify-md-diff.ts
@@ -0,0 +1,125 @@
+#!/usr/bin/env -S pnpm exec tsx
+/**
+ * classify-md-diff.ts
+ *
+ * Classify each modified .en.md file into one of four classes by diff scale:
+ * a — English-only change (no prose impact / code-block / URL / whitespace only)
+ * b — Small diff (≤ 5 prose lines changed, no paragraph restructure)
+ * c — Paragraph-level retranslation (≤ 30 prose lines changed)
+ * d — Page-level restructure (out of scope for /update-origin)
+ *
+ * Usage:
+ * pnpm exec tsx .agents/skills/update-origin/scripts/classify-md-diff.ts [base-ref]
+ *
+ * Output (JSON to stdout):
+ * [{ file, class, directory, metrics: { added, removed, codeOnly, totalLines } }, ...]
+ */
+
+import { execSync } from 'node:child_process';
+import * as path from 'node:path';
+
+type Class = 'a' | 'b' | 'c' | 'd';
+
+interface Metrics {
+ added: number;
+ removed: number;
+ codeOnly: boolean;
+ totalLines: number;
+}
+
+interface Classification {
+ file: string;
+ class: Class;
+ directory: string;
+ metrics: Metrics;
+}
+
+const baseRef = process.argv[2] ?? 'HEAD';
+
+function getChangedEnMdFiles(): string[] {
+ const out = execSync(`git diff --name-only ${baseRef} -- '*.en.md'`, {
+ encoding: 'utf8',
+ });
+ return out.trim().split('\n').filter(Boolean);
+}
+
+function getDiff(file: string): string {
+ return execSync(`git diff ${baseRef} -- "${file}"`, { encoding: 'utf8' });
+}
+
+function fileLineCount(file: string): number {
+ try {
+ const out = execSync(`wc -l < "${file}"`, { encoding: 'utf8' });
+ return parseInt(out.trim(), 10);
+ } catch {
+ return 0;
+ }
+}
+
+function analyzeDiff(diff: string): Omit {
+ const lines = diff.split('\n');
+ let added = 0;
+ let removed = 0;
+ let inCodeBlock = false;
+ let codeOnly = true;
+ let inHunk = false;
+
+ for (const line of lines) {
+ if (line.startsWith('@@')) {
+ inHunk = true;
+ // Reset code block tracking at hunk boundaries (heuristic).
+ inCodeBlock = false;
+ continue;
+ }
+ if (!inHunk) continue;
+ if (line.startsWith('---') || line.startsWith('+++')) continue;
+
+ const sigil = line[0];
+ const content = line.slice(1);
+ const isFence = /^```/.test(content.trimStart());
+
+ if (sigil === '+' || sigil === '-') {
+ if (sigil === '+') added++;
+ else removed++;
+
+ if (!inCodeBlock && !isFence && content.trim() !== '') {
+ codeOnly = false;
+ }
+ }
+
+ // Toggle code-block state for context lines and changed lines alike.
+ if (isFence) {
+ inCodeBlock = !inCodeBlock;
+ }
+ }
+
+ return { added, removed, codeOnly };
+}
+
+function classify(metrics: Metrics): Class {
+ const total = metrics.added + metrics.removed;
+ if (total === 0) return 'a';
+ if (metrics.codeOnly) return 'a';
+ if (total <= 5) return 'b';
+ if (total <= 30) return 'c';
+ return 'd';
+}
+
+function main(): void {
+ const files = getChangedEnMdFiles();
+ const results: Classification[] = files.map((file) => {
+ const diff = getDiff(file);
+ const partial = analyzeDiff(diff);
+ const totalLines = fileLineCount(file);
+ const metrics: Metrics = { ...partial, totalLines };
+ return {
+ file,
+ class: classify(metrics),
+ directory: path.dirname(file),
+ metrics,
+ };
+ });
+ process.stdout.write(JSON.stringify(results, null, 2) + '\n');
+}
+
+main();
diff --git a/.agents/skills/update-origin/scripts/classify-src-diff.ts b/.agents/skills/update-origin/scripts/classify-src-diff.ts
new file mode 100644
index 0000000000..26fff7f52a
--- /dev/null
+++ b/.agents/skills/update-origin/scripts/classify-src-diff.ts
@@ -0,0 +1,131 @@
+#!/usr/bin/env -S pnpm exec tsx
+/**
+ * classify-src-diff.ts
+ *
+ * Classify each modified .en.{ts,html,json} file into one of four classes
+ * based on the volume of translatable-token change:
+ * a — No translatable tokens affected (logic/structure only)
+ * b — ≤ 5 translatable token lines changed
+ * c — > 5 translatable token lines or significant structural change
+ * d — Full rewrite (escalation; do NOT auto-defer like markdown class d)
+ *
+ * Usage:
+ * pnpm exec tsx .agents/skills/update-origin/scripts/classify-src-diff.ts [base-ref]
+ *
+ * Output (JSON to stdout):
+ * [{ file, class, directory, kind, metrics }, ...]
+ */
+
+import { execSync } from 'node:child_process';
+import * as path from 'node:path';
+
+type Class = 'a' | 'b' | 'c' | 'd';
+type Kind = 'ts' | 'html' | 'json';
+
+interface Metrics {
+ added: number;
+ removed: number;
+ translatableTokens: number;
+}
+
+interface Classification {
+ file: string;
+ class: Class;
+ directory: string;
+ kind: Kind;
+ metrics: Metrics;
+}
+
+const baseRef = process.argv[2] ?? 'HEAD';
+
+function getChangedFiles(): string[] {
+ const out = execSync(
+ `git diff --name-only ${baseRef} -- '*.en.ts' '*.en.html' '*.en.json'`,
+ { encoding: 'utf8' },
+ );
+ return out.trim().split('\n').filter(Boolean);
+}
+
+function getDiff(file: string): string {
+ return execSync(`git diff ${baseRef} -- "${file}"`, { encoding: 'utf8' });
+}
+
+function detectKind(file: string): Kind {
+ if (file.endsWith('.en.ts')) return 'ts';
+ if (file.endsWith('.en.html')) return 'html';
+ if (file.endsWith('.en.json')) return 'json';
+ throw new Error(`Unknown file kind: ${file}`);
+}
+
+function isTranslatableLine(content: string, kind: Kind): boolean {
+ if (kind === 'ts') {
+ // Heuristics: known user-facing keys with string literals.
+ return /\b(label|title|description|message|summary|placeholder|tooltip|action)\s*:\s*['"`]/.test(
+ content,
+ );
+ }
+ if (kind === 'html') {
+ // Strip tags; if remaining text has prose-length content, treat as translatable.
+ const stripped = content.replace(/<[^>]+>/g, '').trim();
+ return stripped.length > 3 && /[A-Za-z-ヿ一-鿿]/.test(stripped);
+ }
+ if (kind === 'json') {
+ // Heuristic: long quoted string values likely user-facing.
+ return /:\s*"[^"]{4,}"/.test(content);
+ }
+ return false;
+}
+
+function analyzeDiff(diff: string, kind: Kind): Metrics {
+ const lines = diff.split('\n');
+ let added = 0;
+ let removed = 0;
+ let translatableTokens = 0;
+ let inHunk = false;
+
+ for (const line of lines) {
+ if (line.startsWith('@@')) {
+ inHunk = true;
+ continue;
+ }
+ if (!inHunk) continue;
+ if (line.startsWith('---') || line.startsWith('+++')) continue;
+
+ const sigil = line[0];
+ if (sigil !== '+' && sigil !== '-') continue;
+ const content = line.slice(1);
+
+ if (sigil === '+') added++;
+ else removed++;
+
+ if (isTranslatableLine(content, kind)) translatableTokens++;
+ }
+ return { added, removed, translatableTokens };
+}
+
+function classify(metrics: Metrics): Class {
+ const total = metrics.added + metrics.removed;
+ if (metrics.translatableTokens === 0) return 'a';
+ if (metrics.translatableTokens <= 5) return 'b';
+ if (total < 100) return 'c';
+ return 'd';
+}
+
+function main(): void {
+ const files = getChangedFiles();
+ const results: Classification[] = files.map((file) => {
+ const kind = detectKind(file);
+ const diff = getDiff(file);
+ const metrics = analyzeDiff(diff, kind);
+ return {
+ file,
+ class: classify(metrics),
+ directory: path.dirname(file),
+ kind,
+ metrics,
+ };
+ });
+ process.stdout.write(JSON.stringify(results, null, 2) + '\n');
+}
+
+main();
diff --git a/.agents/skills/update-origin/scripts/verify-codeblock-sync.ts b/.agents/skills/update-origin/scripts/verify-codeblock-sync.ts
new file mode 100644
index 0000000000..dbb3a8bfa5
--- /dev/null
+++ b/.agents/skills/update-origin/scripts/verify-codeblock-sync.ts
@@ -0,0 +1,120 @@
+#!/usr/bin/env -S pnpm exec tsx
+/**
+ * verify-codeblock-sync.ts
+ *
+ * Catches a recurring class-a migration bug: an upstream code change inside a
+ * fenced code block (```...```) was applied to the *.en.md but not mirrored to
+ * the corresponding *.md, even though wc -l parity holds.
+ *
+ * For every *.en.md that has changed since the merge-base with `main` (or a
+ * given base ref), we walk the diff and inspect each `+` line that lands
+ * inside a fenced code block on the new side. The same line index in *.md
+ * MUST contain the identical text — code blocks are shared verbatim.
+ *
+ * Exits with code 1 on any mismatch so that the orchestrator treats it as a
+ * hard gate.
+ *
+ * Usage:
+ * pnpm exec tsx .agents/skills/update-origin/scripts/verify-codeblock-sync.ts []
+ * # default base-ref: main
+ */
+import { execSync } from 'node:child_process';
+import * as fs from 'node:fs';
+
+interface Issue {
+ file: string;
+ line: number;
+ en: string;
+ ja: string;
+}
+
+function changedEnMd(base: string): string[] {
+ const out = execSync(`git diff --name-only ${base} -- "adev-ja/**/*.en.md"`, {
+ encoding: 'utf-8',
+ });
+ return out.split('\n').filter(Boolean);
+}
+
+function diffOf(base: string, file: string): string {
+ return execSync(`git diff ${base} -- ${file}`, { encoding: 'utf-8' });
+}
+
+function fenceRanges(lines: string[]): Array<[number, number]> {
+ const ranges: Array<[number, number]> = [];
+ let inCode = false;
+ let start = -1;
+ for (let i = 0; i < lines.length; i++) {
+ if (/^```/.test(lines[i])) {
+ if (!inCode) {
+ inCode = true;
+ start = i;
+ } else {
+ inCode = false;
+ ranges.push([start, i]);
+ }
+ }
+ }
+ return ranges;
+}
+
+function inAnyRange(i: number, ranges: Array<[number, number]>): boolean {
+ return ranges.some(([s, e]) => i > s && i < e);
+}
+
+function main(): void {
+ const base = process.argv[2] ?? 'main';
+ const enFiles = changedEnMd(base);
+ const issues: Issue[] = [];
+
+ for (const enFile of enFiles) {
+ const jaFile = enFile.replace(/\.en\.md$/, '.md');
+ if (!fs.existsSync(enFile) || !fs.existsSync(jaFile)) continue;
+ const en = fs.readFileSync(enFile, 'utf-8').split('\n');
+ const ja = fs.readFileSync(jaFile, 'utf-8').split('\n');
+ if (en.length !== ja.length) continue; // line_mismatch is reported elsewhere
+
+ const ranges = fenceRanges(en);
+ const diff = diffOf(base, enFile).split('\n');
+ let newLineNum = 0;
+ for (const l of diff) {
+ const m = l.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
+ if (m) {
+ newLineNum = parseInt(m[1], 10) - 1;
+ continue;
+ }
+ if (l.startsWith('+++') || l.startsWith('---')) continue;
+ if (l.startsWith('+')) {
+ newLineNum++;
+ const i = newLineNum - 1;
+ if (!inAnyRange(i, ranges)) continue;
+ const enLine = en[i] ?? '';
+ const jaLine = ja[i] ?? '';
+ if (enLine !== jaLine) {
+ issues.push({
+ file: jaFile,
+ line: newLineNum,
+ en: enLine.slice(0, 160),
+ ja: jaLine.slice(0, 160),
+ });
+ }
+ } else if (l.startsWith(' ')) {
+ newLineNum++;
+ }
+ }
+ }
+
+ if (issues.length === 0) {
+ console.log(`OK: code-block sync verified for ${enFiles.length} changed file(s) since ${base}`);
+ process.exit(0);
+ }
+
+ for (const x of issues) {
+ console.error(`CODEBLOCK_DRIFT: ${x.file}:${x.line}`);
+ console.error(` EN: ${x.en}`);
+ console.error(` JA: ${x.ja}`);
+ }
+ console.error(`FAILED: ${issues.length} code-block sync issue(s)`);
+ process.exit(1);
+}
+
+main();
diff --git a/.agents/skills/update-origin/scripts/verify-migration.ts b/.agents/skills/update-origin/scripts/verify-migration.ts
new file mode 100644
index 0000000000..ee930b1389
--- /dev/null
+++ b/.agents/skills/update-origin/scripts/verify-migration.ts
@@ -0,0 +1,172 @@
+#!/usr/bin/env -S pnpm exec tsx
+/**
+ * verify-migration.ts
+ *
+ * Deterministic post-migration verification. Exits with code 1 on any failure
+ * so that the calling skill / agent treats verification as a hard gate.
+ *
+ * Checks per target (file or directory):
+ * - For every *.en.md, the corresponding *.md exists and has identical line count.
+ * - For every *.en.{ts,html,json}, the corresponding translated file exists.
+ * (Line count is NOT enforced for source code.)
+ *
+ * Usage:
+ * pnpm exec tsx .agents/skills/update-origin/scripts/verify-migration.ts [ ...]
+ */
+
+import * as fs from 'node:fs';
+import * as path from 'node:path';
+import { execSync } from 'node:child_process';
+
+interface Failure {
+ kind: 'missing_translation' | 'line_mismatch' | 'structure_drift';
+ enFile: string;
+ jaFile: string;
+ detail?: string;
+}
+
+function collectEnFiles(target: string): string[] {
+ const stat = fs.statSync(target);
+ if (stat.isFile()) return /\.en\.(md|ts|html|json)$/.test(target) ? [target] : [];
+
+ const out: string[] = [];
+ const entries = fs.readdirSync(target, { withFileTypes: true, recursive: true });
+ for (const entry of entries) {
+ if (!entry.isFile()) continue;
+ if (!/\.en\.(md|ts|html|json)$/.test(entry.name)) continue;
+ // Node 22: parentPath is set; fall back to name resolution otherwise.
+ const dir = (entry as fs.Dirent & { parentPath?: string }).parentPath ?? target;
+ out.push(path.join(dir, entry.name));
+ }
+ return out;
+}
+
+function lineCount(file: string): number {
+ return fs.readFileSync(file, 'utf8').split('\n').length;
+}
+
+function jaPath(enFile: string): string {
+ return enFile.replace(/\.en\.(md|ts|html|json)$/, '.$1');
+}
+
+function changedSince(base: string): Set {
+ try {
+ const out = execSync(`git diff --name-only ${base}`, { encoding: 'utf-8' });
+ return new Set(out.split('\n').filter(Boolean));
+ } catch {
+ return new Set();
+ }
+}
+
+function verifyOne(enFile: string, structuralScope: Set | null): Failure[] {
+ const failures: Failure[] = [];
+ const ja = jaPath(enFile);
+
+ if (!fs.existsSync(ja)) {
+ failures.push({ kind: 'missing_translation', enFile, jaFile: ja });
+ return failures;
+ }
+
+ if (enFile.endsWith('.en.md')) {
+ // Strip leading BOM so structural-marker regex anchors at column 0.
+ const stripBom = (s: string) => (s.charCodeAt(0) === 0xfeff ? s.slice(1) : s);
+ const enLines = stripBom(fs.readFileSync(enFile, 'utf8')).split('\n');
+ const jaLines = stripBom(fs.readFileSync(ja, 'utf8')).split('\n');
+ if (enLines.length !== jaLines.length) {
+ failures.push({
+ kind: 'line_mismatch',
+ enFile,
+ jaFile: ja,
+ detail: `en=${enLines.length} ja=${jaLines.length}`,
+ });
+ return failures;
+ }
+ // Structural-marker alignment is only enforced for files within the
+ // migration scope (changed vs the configured base ref). Pre-existing drift
+ // in untouched files is out of scope for /update-origin.
+ if (structuralScope !== null && !structuralScope.has(enFile) && !structuralScope.has(ja)) {
+ return failures;
+ }
+ // Structural-marker alignment: fenced code fences (```), heading lines, and
+ // line-prefix docs annotations must sit at the SAME line index on both sides.
+ // wc -l parity is preserved by the agents but content-level drift (a missing
+ // blank line compensated by an extra one elsewhere) hides translation bugs.
+ const ANNOTATION = /^(>\s*)*(IMPORTANT|NOTE|HELPFUL|TIP|CRITICAL|WARNING|TLDR):/;
+ const FENCE = /^```/;
+ const HEADING = /^#{1,6}\s/;
+ const drifts: string[] = [];
+ for (let i = 0; i < enLines.length; i++) {
+ const e = enLines[i];
+ const j = jaLines[i];
+ if (FENCE.test(e) !== FENCE.test(j)) {
+ drifts.push(`L${i + 1}: code-fence drift`);
+ } else if (HEADING.test(e) !== HEADING.test(j)) {
+ drifts.push(`L${i + 1}: heading drift`);
+ } else if (ANNOTATION.test(e) !== ANNOTATION.test(j)) {
+ drifts.push(`L${i + 1}: annotation-prefix drift`);
+ }
+ if (drifts.length >= 5) break;
+ }
+ if (drifts.length > 0) {
+ failures.push({
+ kind: 'structure_drift',
+ enFile,
+ jaFile: ja,
+ detail: drifts.join('; '),
+ });
+ }
+ }
+ return failures;
+}
+
+function main(): void {
+ const args = process.argv.slice(2);
+ // Optional --base [ scopes structural-drift checks to files changed since
+ // that ref. Defaults to "main"; pass --base "" to check ALL files.
+ let base = 'main';
+ const targets: string[] = [];
+ for (let i = 0; i < args.length; i++) {
+ if (args[i] === '--base') {
+ base = args[++i] ?? 'main';
+ } else {
+ targets.push(args[i]);
+ }
+ }
+ if (targets.length === 0) {
+ console.error('Usage: verify-migration.ts [--base ][] [ ...]');
+ process.exit(2);
+ }
+
+ const structuralScope = base === '' ? null : changedSince(base);
+
+ const allEn: string[] = [];
+ for (const t of targets) {
+ if (!fs.existsSync(t)) {
+ console.error(`NOT_FOUND: ${t}`);
+ process.exit(2);
+ }
+ allEn.push(...collectEnFiles(t));
+ }
+
+ const failures: Failure[] = [];
+ for (const en of allEn) failures.push(...verifyOne(en, structuralScope));
+
+ if (failures.length === 0) {
+ console.log(`OK: verified ${allEn.length} file(s) under ${targets.join(', ')}`);
+ process.exit(0);
+ }
+
+ for (const f of failures) {
+ if (f.kind === 'missing_translation') {
+ console.error(`MISSING_TRANSLATION: ${f.jaFile} (for ${f.enFile})`);
+ } else if (f.kind === 'line_mismatch') {
+ console.error(`LINE_MISMATCH: ${f.enFile} vs ${f.jaFile} (${f.detail})`);
+ } else {
+ console.error(`STRUCTURE_DRIFT: ${f.enFile} vs ${f.jaFile} (${f.detail})`);
+ }
+ }
+ console.error(`FAILED: ${failures.length} issue(s) in ${allEn.length} file(s)`);
+ process.exit(1);
+}
+
+main();
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000000..834ab1844d
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,575 @@
+# AGENTS.md
+
+Guidance for AI coding agents working in this repository. Skills are in Agent Skills format under `.agents/skills//SKILL.md`.
+
+## Project Overview
+
+This is the Japanese translation project for Angular's official documentation site (angular.dev). The repository translates Angular documentation from English to Japanese and hosts the localized site at https://angular.jp.
+
+For contributor-facing background, see [CONTRIBUTING.md](./CONTRIBUTING.md) (setup, translation flow, guidelines), [UPDATE_ORIGIN.md](./UPDATE_ORIGIN.md) (manual origin update), and [docs/TRANSLATION_WITH_AI.md](./docs/TRANSLATION_WITH_AI.md) (AI translation tool). This file adds the rules agents must follow.
+
+## Repository Architecture
+
+### Submodule Structure
+
+- `origin/`: Git submodule containing the upstream `angular/angular` repository
+- `adev-ja/`: Japanese localized version containing both original English files (`.en.md`, `.en.ts`) and their Japanese translations (`.md`, `.ts`)
+- `tools/`: Build and translation automation tools
+
+### Translation File Patterns
+
+- **English source files**: `filename.en.md`, `filename.en.ts`, `filename.en.json` (snapshots of original content at translation time)
+- **Japanese translated files**: `filename.md`, `filename.ts`, `filename.json` (localized versions)
+- **Content location**: Primarily in `adev-ja/src/content/` for Markdown documentation
+- **Tutorial structure**: Interactive tutorials contain both content files (`README.md`) and configuration files (`config.json`) defining page titles and editor settings
+
+### Build System
+
+The project uses a custom TypeScript-based build system with Bazel integration:
+
+- Build initialization copies and patches files from `origin/adev/` to `build/` directory
+- Applies localization patches from `tools/adev-patches/`
+- Japanese content from `adev-ja/` overlays the build structure
+
+## Skills
+
+Workflow automation lives in `.agents/skills/`. Use the skills below before reaching for ad-hoc shell commands or new tooling.
+
+| Skill | Purpose | When to use |
+| --- | --- | --- |
+| `.agents/skills/update-origin/SKILL.md` | Single entry point for syncing upstream `angular/angular` into `adev-ja`. Covers branch, submodule update, diff classification, migration by role, verification, per-class commits, and PR. | Any origin sync. Per-file migration always follows the role files in `roles/` (delegated when possible). |
+| `.agents/skills/translate-file/SKILL.md` | First-time translation of a new `.en.md` / `.en.ts` / `.en.json` file into Japanese with strict line-count and anchor-ID rules. | A file is added upstream and you are ready to translate it. NOT for diff sync of an already-translated file (that is the `update-origin` flow). |
+| `.agents/skills/prh-terminology/SKILL.md` | Manage the `prh.yml` terminology dictionary. | **Always before adopting a translation for a new technical term** (see "Terminology management" below). |
+
+### Roles used by update-origin (`.agents/skills/update-origin/roles/`)
+
+These are instruction files for one batch of work each. If your harness supports delegating to sub-agents, pass the file as the instructions; otherwise follow it yourself in sequence. They are only used by the `update-origin` skill.
+
+| Role | Purpose |
+| --- | --- |
+| `migrate-md-class-a.md` | Mirror low-risk markdown changes (code blocks, URLs, prose backtick identifiers). |
+| `migrate-md-class-b.md` | Apply small line-localized markdown diffs preserving line correspondence. |
+| `migrate-md-class-c.md` | Paragraph-level retranslation. |
+| `defer-md-class-d.md` | Class-d defer in either `d-additive` or `d-structural` mode. |
+| `migrate-md-roadmap.md` | Special handler for `reference/roadmap.md` (Completed-projects history accumulation). |
+| `migrate-src-class-{a,b,c}.md` | Source-code (`.ts` / `.html` / `.json`) migration in three risk tiers. |
+| `escalate-src-class-d.md` | Stops the workflow and reports class-d source files for resolution by a person. |
+
+### Skill scripts (`.agents/skills/update-origin/scripts/`)
+
+Run with `pnpm exec tsx ]