@mmerterden/multi-agent-pipeline 14.2.1 → 14.2.2

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.
Files changed (106) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +10 -6
  3. package/README.tr.md +143 -0
  4. package/docs/architecture.md +23 -8
  5. package/docs/ecosystem.md +237 -0
  6. package/install/_plugin-skills.mjs +16 -2
  7. package/install/codex.mjs +9 -4
  8. package/install/templates/copilot-instructions.md +12 -9
  9. package/package.json +1 -1
  10. package/pipeline/commands/multi-agent/SKILL.md +2 -2
  11. package/pipeline/commands/multi-agent/analysis/SKILL.md +2 -2
  12. package/pipeline/commands/multi-agent/autopilot/SKILL.md +6 -2
  13. package/pipeline/commands/multi-agent/channels/SKILL.md +15 -4
  14. package/pipeline/commands/multi-agent/create-jira/SKILL.md +4 -4
  15. package/pipeline/commands/multi-agent/dev/SKILL.md +10 -23
  16. package/pipeline/commands/multi-agent/dev-autopilot/SKILL.md +10 -2
  17. package/pipeline/commands/multi-agent/dev-local/SKILL.md +10 -24
  18. package/pipeline/commands/multi-agent/dev-local-autopilot/SKILL.md +10 -3
  19. package/pipeline/commands/multi-agent/jira/SKILL.md +13 -2
  20. package/pipeline/commands/multi-agent/language/SKILL.md +1 -1
  21. package/pipeline/commands/multi-agent/local/SKILL.md +6 -2
  22. package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +6 -2
  23. package/pipeline/commands/multi-agent/log/SKILL.md +7 -1
  24. package/pipeline/commands/multi-agent/ship/SKILL.md +5 -1
  25. package/pipeline/commands/multi-agent/sync/SKILL.md +3 -3
  26. package/pipeline/commands/multi-agent/update/SKILL.md +1 -1
  27. package/pipeline/lib/extract-conventions.sh +44 -15
  28. package/pipeline/lib/fetch-figma-annotations.sh +8 -1
  29. package/pipeline/lib/fetch-fortify.sh +23 -8
  30. package/pipeline/lib/figma-screenshot.sh +11 -1
  31. package/pipeline/lib/issue-fetcher.sh +76 -9
  32. package/pipeline/lib/md2confluence-v3.py +16 -2
  33. package/pipeline/lib/plan-todos.sh +5 -2
  34. package/pipeline/lib/post-pr-review.sh +8 -6
  35. package/pipeline/lib/shadow-git.sh +50 -9
  36. package/pipeline/lib/submodule-detector.sh +8 -1
  37. package/pipeline/multi-agent-refs/_input-parser.md +1 -1
  38. package/pipeline/multi-agent-refs/channels/confluence.md +3 -0
  39. package/pipeline/multi-agent-refs/channels/issue-comment.md +2 -2
  40. package/pipeline/multi-agent-refs/channels/jira.md +13 -2
  41. package/pipeline/multi-agent-refs/channels/pr-review-actions.md +1 -1
  42. package/pipeline/multi-agent-refs/channels/pr.md +20 -0
  43. package/pipeline/multi-agent-refs/channels/wiki.md +1 -1
  44. package/pipeline/multi-agent-refs/cross-cli-contract.md +3 -3
  45. package/pipeline/multi-agent-refs/features/worktree-finalize.md +1 -1
  46. package/pipeline/multi-agent-refs/generate-issue.md +2 -2
  47. package/pipeline/multi-agent-refs/issue-jira-triad.md +3 -3
  48. package/pipeline/multi-agent-refs/payload-contracts.md +67 -0
  49. package/pipeline/multi-agent-refs/phases/modes.md +20 -0
  50. package/pipeline/multi-agent-refs/phases/phase-0-init.md +1 -1
  51. package/pipeline/multi-agent-refs/phases/phase-6-commit.md +8 -40
  52. package/pipeline/multi-agent-refs/phases/phase-7-report.md +5 -3
  53. package/pipeline/multi-agent-refs/phases.md +6 -0
  54. package/pipeline/multi-agent-refs/rules.md +2 -0
  55. package/pipeline/schemas/prefs.schema.json +1 -1
  56. package/pipeline/scripts/audit-log-rotate.sh +10 -0
  57. package/pipeline/scripts/build-stack-plugins.mjs +8 -1
  58. package/pipeline/scripts/check-derived-drift.mjs +13 -1
  59. package/pipeline/scripts/diff-explain.mjs +41 -3
  60. package/pipeline/scripts/diff-risk-score.mjs +72 -8
  61. package/pipeline/scripts/gen-mode-dispatch.mjs +1 -1
  62. package/pipeline/scripts/learning-curve.mjs +8 -2
  63. package/pipeline/scripts/output-quality-check.sh +15 -4
  64. package/pipeline/scripts/phase-tracker.sh +21 -8
  65. package/pipeline/scripts/pre-commit-check.sh +69 -22
  66. package/pipeline/scripts/render-agent-log-cost.sh +8 -3
  67. package/pipeline/scripts/render-cost-summary.sh +42 -22
  68. package/pipeline/scripts/render-work-summary.sh +47 -13
  69. package/pipeline/scripts/review-scope.mjs +1 -1
  70. package/pipeline/scripts/run-aggregator.mjs +38 -14
  71. package/pipeline/scripts/smoke-schema-validation.sh +5 -1
  72. package/pipeline/scripts/test-gap-scan.mjs +45 -6
  73. package/pipeline/scripts/uninstall.mjs +39 -4
  74. package/pipeline/scripts/update-issue-progress.sh +12 -16
  75. package/pipeline/scripts/worktree-finalize.sh +23 -2
  76. package/pipeline/skills/.skills-index.json +11 -2
  77. package/pipeline/skills/shared/README.md +1 -1
  78. package/pipeline/skills/shared/core/multi-agent-autopilot/SKILL.md +4 -0
  79. package/pipeline/skills/shared/core/multi-agent-create-jira/SKILL.md +1 -1
  80. package/pipeline/skills/shared/core/multi-agent-dev/SKILL.md +4 -17
  81. package/pipeline/skills/shared/core/multi-agent-dev-autopilot/SKILL.md +8 -0
  82. package/pipeline/skills/shared/core/multi-agent-dev-local/SKILL.md +5 -18
  83. package/pipeline/skills/shared/core/multi-agent-dev-local-autopilot/SKILL.md +8 -0
  84. package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +1 -1
  85. package/pipeline/skills/shared/core/multi-agent-local/SKILL.md +4 -0
  86. package/pipeline/skills/shared/core/multi-agent-local-autopilot/SKILL.md +4 -0
  87. package/pipeline/skills/shared/core/multi-agent-ship/SKILL.md +4 -0
  88. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +2 -2
  89. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +302 -0
  90. package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +144 -0
  91. package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +156 -0
  92. package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +108 -0
  93. package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +175 -0
  94. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +865 -0
  95. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +335 -0
  96. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +344 -0
  97. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +130 -0
  98. package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +264 -0
  99. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-key-shots.py +298 -0
  100. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +529 -0
  101. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +187 -0
  102. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +171 -0
  103. package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +184 -0
  104. package/pipeline/skills/shared/external/localization-reuse-map/scripts/snapshot-resources.sh +26 -0
  105. package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +173 -0
  106. package/pipeline/skills/skills-index.md +3 -2
@@ -0,0 +1,108 @@
1
+ # Publish to <specs-repo> & the in-repo resource snapshot
2
+
3
+ > Bundled detail for [../SKILL.md](../SKILL.md). How the generated artifact lands in `<specs-repo>`, and
4
+ > how the redesign resources get snapshotted there so the whole flow is **single-repo** (like `design-export`).
5
+
6
+ ## Single-repo model - why snapshot into <specs-repo>
7
+
8
+ The skill reads four sources. Three already live in `<specs-repo>` (`design-export/`, legacy iOS/Android,
9
+ the live label service over the network). The fourth - the redesign **component registry** and **Suggested
10
+ translations** - lives in `<resources-repo>`. Snapshotting those two into `<specs-repo>`
11
+ (exactly as `design-export/` is a manual/CI snapshot of Figma) removes the cross-repo round-trip: every
12
+ read is one repo.
13
+
14
+ **Layout** (top-level, mirroring `design-export`'s convention):
15
+ ```
16
+ <specs-repo>/
17
+ ├── design-export/ # existing (Figma snapshot)
18
+ ├── resources/ # NEW snapshot - all data the mapper needs, in-repo
19
+ │ ├── Figma/Components/<node>.json # component registry (localizationKeys, componentName)
20
+ │ ├── Localization/Suggested/<Key>.json # NEW translation values (8 langs)
21
+ │ └── Localization/Legacy/<lang>.json # LEGACY values - label snapshot (flat key→value, one file per language)
22
+ └── localizations/ # NEW - the maps this skill publishes
23
+ └── <screen-slug>/
24
+ ├── <screen-slug>.md # the reuse map
25
+ ├── <screen-slug>.confluence.xml
26
+ ├── <screen-slug>.overlay.png # key↔UI overlay (when produced)
27
+ ├── <screen-slug>.keyshots.manifest.json # per-key screenpiece index (when produced)
28
+ ├── keyshots/ # per-key red-box screenpieces (render-key-shots.py)
29
+ │ └── keyshot__<NN>__<key>.png
30
+ └── screenshot.png
31
+ ```
32
+
33
+ **Refresh** - two steps, both "like a `design-export` update" (run by CI or manually):
34
+ ```bash
35
+ # 1. registry + new (Suggested) values - local copy from <resources-repo> (no network)
36
+ scripts/snapshot-resources.sh <resources-repo-root> <specs-repo-root>
37
+ # 2. legacy values - the ONLY network step: fetch the legacy labels for all 8 languages
38
+ scripts/fetch-legacy-labels.py --out <specs-repo>/resources/Localization/Legacy --langs all
39
+ ```
40
+ With the snapshot in place every read is in-repo - **no live label round-trip per run**:
41
+ - new values: `resolve-new-values.py --resources-root <specs-repo>/resources` (Suggested lives there)
42
+ - legacy values: `resolve-legacy-values.py --snapshot-root <specs-repo>/resources/Localization/Legacy`
43
+ (offline; `--live` only to verify/refresh, `--plist-root` as last resort).
44
+
45
+ ## Publishing a screen's map (the skill's last step)
46
+
47
+ Two destinations; **ask the user which** (and the target space/parent/formats) - they are not mutually
48
+ exclusive:
49
+
50
+ ### A) Confluence - live & idempotent (`scripts/publish-confluence.py`)
51
+
52
+ ```bash
53
+ python3 scripts/publish-confluence.py --xml "$OUT/$SLUG.confluence.xml" --screen "<name>" \
54
+ --screenshot "$OUT/screenshot.png" --overlay "$OUT/$SLUG.overlay.png" \
55
+ --keyshots "$OUT/$SLUG.keyshots.manifest.json" \
56
+ --attach "$OUT/$SLUG.localization.xlsx"
57
+ # defaults: --base $CONFLUENCE_BASE_URL --space <SPACE> --parent <PARENT-ID> --title "<prefix> - <name>"
58
+ ```
59
+ - **Target configuration.** Nothing about your wiki is baked in: pass `--base-url` / `--space` / `--parent`,
60
+ or set `CONFLUENCE_BASE_URL` / `CONFLUENCE_SPACE` / `CONFLUENCE_PARENT` once in the environment. Without a
61
+ target the script refuses rather than guessing. `--title-prefix` (env `LOCALIZATION_PAGE_PREFIX`) sets the
62
+ wording of the default page title.
63
+ - **Server/DC Bearer auth** (NOT Atlassian Cloud `user:token`): the PAT is read from the macOS keychain item
64
+ **`CONFLUENCE_API_TOKEN`** and passed to `curl` via a `chmod 600` `-K` config (never on the argv). HTTP goes
65
+ through `curl` so a corporate TLS proxy's trust store is honored (Python `urllib` fails cert verification
66
+ there).
67
+ - **Idempotent by title.** It looks the page up via the DB-backed `GET /rest/api/content?spaceKey=...&title=...`
68
+ (NOT CQL `/content/search`, whose async index lags a fresh page and would create a duplicate). Found →
69
+ **PUT `version+1`, updating the same page in place**; absent → **POST create** under `--parent`. Re-running
70
+ the mapper on a screen refreshes its page; it never duplicates.
71
+ - **Attachments.** `screenshot.png` + the overlay PNG are attached (create, or update-by-filename if present);
72
+ the storage `<ac:image><ri:attachment ri:filename="...">` tags resolve to them. Filenames must match what
73
+ `build-artifact.py` embedded (its basenames). **`--keyshots <manifest>`** attaches every per-key screenpiece
74
+ PNG listed in `$SLUG.keyshots.manifest.json` (render-key-shots.py) - without it the Summary table's
75
+ "Screenshot" cells reference attachments that don't exist and render as broken images.
76
+ **`--attach <file>` (repeatable)** attaches any extra document
77
+ - pass `$SLUG.localization.xlsx` (the CMS import sheet, build-spreadsheet.py); office/spreadsheet files also
78
+ get a `view-file` macro appended to the body so they **render inline** under a "CMS transfer file" heading.
79
+ - The skill asks the user for the space / parent / title when the environment doesn't already pin them.
80
+ - `build-artifact.py ... --print-upload` prints the exact command (plus the raw Bearer `curl`) for copy-paste.
81
+
82
+ ### B) <specs-repo> PR (in-repo record - optional)
83
+
84
+ Place the canonical set under `localizations/<slug>/` and open an additive PR:
85
+ ```bash
86
+ SLUG=sign-up-account-details
87
+ DST="$SPECS/localizations/$SLUG"; mkdir -p "$DST"
88
+ cp "$OUT/$SLUG".{md,confluence.xml} "$OUT/$SLUG.overlay.png" "$OUT/screenshot.png" \
89
+ "$OUT/$SLUG.keyshots.manifest.json" "$DST"/ 2>/dev/null
90
+ [ -d "$OUT/keyshots" ] && cp -R "$OUT/keyshots" "$DST"/ # per-key screenpieces (md embeds them relatively)
91
+ cd "$SPECS" && git switch -c "loc-map/$SLUG" \
92
+ && git add "localizations/$SLUG" \
93
+ && git commit -m "localizations: $SLUG reuse map" \
94
+ && git push -u origin "loc-map/$SLUG" \
95
+ && gh pr create --title "Localization reuse map - $SLUG" \
96
+ --body "Generated by the localization-reuse-map skill; screenshot + overlay included." --fill
97
+ ```
98
+ - The `.md` is the human-readable map (images embedded by filename - keep the PNGs beside it so they render
99
+ on GitHub). The `.confluence.xml` is the storage-format payload Confluence consumes.
100
+ - **Merge policy:** additive (new files under `localizations/`) → auto-/admin-merge cleanly.
101
+ - Need a file for the content team? Re-run `build-artifact.py` with `--docx` / `--pdf` / `--all` (CMS columns
102
+ + overlay embedded). Keep these heavy binaries **out** of the PR - share them directly.
103
+
104
+ ## Guardrails
105
+ - **Additive only.** This flow creates files under `localizations/` and `resources/`; it never edits
106
+ legacy source, `design-export/`, or `specs/`.
107
+ - The `resources/` snapshot is **generated, not authored** - never hand-edit it; refresh it from
108
+ `<resources-repo>` (its source of truth), same as `design-export/` is refreshed from Figma.
@@ -0,0 +1,175 @@
1
+ # Sources & retrieval recipes (the rigorous method)
2
+
3
+ > Bundled detail for [../SKILL.md](../SKILL.md). The data model and the exact, copy-paste recipe per
4
+ > source. **Retrieval is a cross-reference, not a skim** - that is the whole point of this skill. Every
5
+ > recipe reads files or uses `gh`; no other skill required.
6
+
7
+ ## The data model - two sides, sources per side
8
+
9
+ | | UI element → key | key → translation value |
10
+ |---|---|---|
11
+ | **New (redesign)** | `design-export` components-used **×** component registry `localizationKeys` **×** the screen's code (what it wires). Cross-reference all three. | `<resources-repo>` → `Sources/Suggested/<Key>.json` (8 langs) - `scripts/resolve-new-values.py` |
12
+ | **Legacy** | the screen **traced** through its code: iOS VC→VM→DataSource→**cell presentation models**; Android layout `@string/` + fragment `R.string.`; web component → `t('key')`/`$t('key')` call site | iOS shipped label map `<lang>.lproj/language.plist` (`Mobile-<Key>`, 8 langs) - `scripts/resolve-legacy-values.py`; gaps → labels-API snapshot; web → its `locales/<lang>.json` (or equivalent i18n catalog), same snapshot script |
13
+
14
+ Read locally: the redesign app (code) + `<resources-repo>` (new values + registry).
15
+ Over `gh` (no checkout): `<specs-repo>` - holds `design-export/`, the legacy iOS/Android source, the
16
+ shipped `language.plist`, and `specs/`. New translation **values are shared across platforms** (one
17
+ Suggested value per key); only key *usage* is per-platform.
18
+
19
+ **A third value source - the CMS (content team) copy.** Beyond `new` (design/resources) and `legacy`, the
20
+ content team writes the **actual final copy** as Figma **Dev Mode annotations** ("Final UX Writing", `TR:/EN:`).
21
+ That copy is fetched live (`fetch-annotations.py`) and shown additively as the **CMS** columns - see *CMS
22
+ values* below. And because the static element→key cross-reference misses runtime strings, a **scan of the
23
+ implemented screen** (`scan-screen-keys.py`) recovers error / dynamic / registry-missed keys - see *Catching
24
+ the keys the cross-reference misses* below.
25
+
26
+ ## New side - element → key (CROSS-REFERENCE, not code-only)
27
+
28
+ A component's registry `localizationKeys` are what it renders **internally**; the screen's code shows
29
+ what it **wires**; neither alone is complete. Do all three:
30
+
31
+ 1. **Which components + nodes the screen comprises** - `design-export` (single-repo, the snapshot model):
32
+ ```bash
33
+ gh api "repos/<org>/<specs-repo>/contents/design-export/<project>/screens/<screen-slug>/components-used.md" -H "Accept: application/vnd.github.raw"
34
+ ```
35
+ Read **every state frame** of the screen (empty / 1-item / filled) - components only present in the
36
+ filled state (e.g. a passenger list) still belong to the screen. The folder also has `screenshot.png`,
37
+ `tree.json` (node ids), `tokens.json`. *(Live alternative if a frame isn't exported: Figma REST via a
38
+ host Figma tool - REST works with a token; MCP/live needs Figma Desktop in Dev Mode. The export is
39
+ preferred - single-repo.)*
40
+ 2. **Each component's owned keys** - registry, local & instant:
41
+ ```bash
42
+ python3 -c "import json;d=json.load(open('Resources/Figma/Components/<NODE-DASH>.json'));print([k['key'] for k in d.get('localizationKeys',[])])"
43
+ ```
44
+ These include keys the component renders that **never appear in the screen code** (e.g.
45
+ `PassengerListGroup.Title`, `PassengerListRow.Delete`). Beware the inverse: a shared
46
+ component (e.g. `HeaderMain`, `BottomActions`) owns many keys the screen does **not** use because the
47
+ screen passes its own title in - confirm against the code.
48
+ 3. **What the screen wires** - read the scene/screen file: each `LocalizationStringKey.<Ns>.<leaf>` call
49
+ site is one element; the call site is the label. Screen-specific keys (`SignUpAccountDetails.*`) and
50
+ reused keys (`ModalsCustoms.Ok`) live here.
51
+
52
+ Union the three; tag each key **screen-specific / reusable-component / shared-primitive**. Component-only
53
+ keys: map once where that component is mapped - flag, don't duplicate per screen.
54
+
55
+ **New values:** `python3 scripts/resolve-new-values.py --resources-root <resources-repo> --keys "..." --langs all`.
56
+
57
+ ## Legacy side - element → key (TRACE the screen, don't skim the map)
58
+
59
+ 1. Start from `specs/<area>/<screen>-spec.md` - its `> Legacy reference (iOS):` blockquote names the entry
60
+ class (VC/VM).
61
+ 2. **iOS - follow the chain.** Fetch the VM raw; it builds sections/cells. Form-field labels are **not** in
62
+ the VM - they default inside the **cell presentation models** it instantiates. Follow them:
63
+ ```bash
64
+ gh api ".../contents/<path>/TKSStandaloneAddPassengerViewModel.swift" -H "Accept: application/vnd.github.raw" > /tmp/vm.swift
65
+ grep -nE 'PresentationModel|\.localized' /tmp/vm.swift # find the cell classes it creates
66
+ # then fetch each cell class and grep its keys:
67
+ gh api ".../Forms/TextInputCell/PresentationModels/TKSPnrSurnameTextInputCellPresentationModel.swift" -H "Accept: application/vnd.github.raw" | grep -oE '"[^"]+"\.localized'
68
+ ```
69
+ This is how `NewSurnameReq` / `PnrNumber` / `ReservationEticketPlaceHolder` surface - a VM-only grep
70
+ misses them. Keys are the string literal before `.localized`.
71
+ 3. **Android.** Layout XML carries labels as `@string/Key`; fragment carries runtime strings as `R.string.`:
72
+ ```bash
73
+ gh api ".../res/layout/<screen>.xml" -H "Accept: application/vnd.github.raw" | grep -oE '@string/[A-Za-z0-9_]+' | sort -u
74
+ ```
75
+ 4. **Web (when the legacy app is a web frontend).** The component wires the key as an i18n call, not a
76
+ string literal - grep the component tree for the call site, not the copy:
77
+ ```bash
78
+ gh api ".../src/screens/<Screen>/index.tsx" -H "Accept: application/vnd.github.raw" \
79
+ | grep -oE "(\\\$?t\(['\"]|useTranslation\(['\"])[A-Za-z0-9_.]+" | sort -u
80
+ ```
81
+ Covers `t('key')` / `i18n.t('key')` (react-i18next, next-intl) and `$t('key')` (vue-i18n). A template
82
+ literal or variable argument (`` t(`errors.${field}`) ``) is a **dynamic** key exactly like iOS
83
+ `String(format:)` - record the pattern, not a resolvable literal, same rule `scan-screen-keys.py`
84
+ applies. Namespace prefixes (`t('signup.email_label')` inside `useTranslation('signup')`) resolve to
85
+ `signup.email_label` - read the hook/provider call, don't assume the literal alone is the full key.
86
+
87
+ iOS, Android and web usually share the flat key for shared elements; when any platform differs it is
88
+ drift worth a **review** (e.g. `AgentaUserAlreadyAdded` iOS typo vs `AgencyUserAlreadyAdded` Android).
89
+ Record every column present; ` - ` where a platform has none.
90
+
91
+ ## Legacy translation values - the in-repo legacy-label snapshot (default), refreshed from the live service
92
+
93
+ Values come from your legacy backend’s label service (the same map the legacy apps render) - but **snapshotted
94
+ in-repo** so the mapper reads them offline, no network round-trip per run (single-repo, like design-export):
95
+ ```bash
96
+ python3 scripts/resolve-legacy-values.py --langs all \
97
+ --snapshot-root <specs-repo>/resources/Localization/Legacy \
98
+ --keys "Continue,EmailAddress,PasswordRule,EmailAlreadyExists"
99
+ ```
100
+ The snapshot is `resources/Localization/Legacy/<lang>.json` - a flat `{key: value}` map per language.
101
+ If your backend namespaces keys under a prefix the app strips (a stored `Mobile-Continue` for the app's
102
+ `Continue`), pass `--prefix Mobile-` and keep the mapping's keys bare; set the mapping's `legacyKeyPrefix`
103
+ so the rendered table still shows the content team the full stored key.
104
+
105
+ **Refresh** the snapshot (the only network step) with `fetch-legacy-labels.py`. Legacy translations are
106
+ served by *your* backend, so no endpoint is built in - you describe it once:
107
+ ```bash
108
+ python3 scripts/fetch-legacy-labels.py --out <specs-repo>/resources/Localization/Legacy --langs all \
109
+ --endpoint 'https://labels.example.internal/{env}/labels/{lang}' --env dev \
110
+ --headers-file .secrets/label-headers.json \
111
+ --fail-status '9999=gateway blocked the request' --fail-status '50=client version not accepted'
112
+ ```
113
+ - Keep tokens in `--headers-file` (a JSON object), never in `--header` - argv lands in shell history and CI logs.
114
+ - The response shape is discovered, not assumed: the largest flat `{string: string}` map anywhere in the JSON
115
+ wins, so an enveloped payload works without configuration.
116
+ - `--fail-status CODE=message` turns your backend's in-band error codes into a clear abort instead of an
117
+ empty snapshot. Find these once and record them in your project's own notes.
118
+
119
+ `resolve-legacy-values.py --live` (same `--endpoint` / `--headers-file`) bypasses the snapshot to read the
120
+ service directly, to verify or refresh. `--plist-root <dir>` is a last-resort offline fallback for a legacy
121
+ iOS app's bundled `<lang>.lproj/language.plist` - typically a stale subset, so a hit there deserves a note.
122
+ Never invent a legacy value - blank + a note beats a guess.
123
+
124
+ ## CMS values - the content team's actual copy (Figma Dev Mode annotations)
125
+
126
+ The content team annotates each text node with the **final** bilingual copy as a Dev Mode annotation
127
+ (category *Final UX Writing*), `TR: ...` / `EN: ...`. The design text is usually a placeholder ("Giriniz",
128
+ "Lorem ipsum") - the **annotation**, not the design text, is their real value. Figma is edited continuously,
129
+ so fetch it **live**, with fallbacks (priority order):
130
+
131
+ ```bash
132
+ # 1) REST (default) - needs a Figma token (keychain FIGMA_ACCESS_TOKEN, env, or --token):
133
+ python3 scripts/fetch-annotations.py --mapping <map>.json --out _annotations.json
134
+ # (or --file <fileKey> --nodes "1024:4096,1024:4112")
135
+ # 2) MCP (no rate limit; the host's Figma plugin dumped the nodes' annotations):
136
+ python3 scripts/fetch-annotations.py --from-mcp _annotations.raw.json --out _annotations.json
137
+ # 3) local fallback (offline; only if design-export tree.json carries annotations):
138
+ python3 scripts/fetch-annotations.py --local design-export/<project>/screens/<slug> --out _annotations.json
139
+ ```
140
+ It calls Figma `GET /v1/files/<key>/nodes?ids=...` (header `X-Figma-Token`), walks `node.annotations[].label`,
141
+ parses `TR:/EN:` (the same `split_lang` rules the web skill uses), and emits
142
+ `{nodeId, designText, tr, en, raw, mode}` per annotated node. Map each annotation onto its row by
143
+ **`nodeId`** (store it as the row's `cmsNodeId`) → fill the row's `cms:{tr,en}`. A `429` aborts with a "use
144
+ `--from-mcp`" hint. **Never invent a CMS value** - leave `cms` absent if there's no annotation. A CMS value
145
+ that differs from `new` is fine (the renderer marks it ⚠); a CMS-only `TR` with no `EN` is flagged back to
146
+ the content team.
147
+
148
+ ## Catching the keys the cross-reference misses - scan the implemented screen
149
+
150
+ The static element→key cross-reference (registry × design-export × code) is biased toward **statically-wired
151
+ literal** keys. It is blind to **error/validation/alert** copy (lives in validators, error mappers, alert
152
+ builders, VM error branches - not the VC→VM→cell chain) and to **dynamic/interpolated** keys
153
+ (`String(format:)`, variable `.localized` receivers, `LocalizationStringKey(<expr>)`, Android
154
+ `getString(<var>)`). Recover them by scanning the implemented screen directly:
155
+
156
+ ```bash
157
+ python3 scripts/scan-screen-keys.py --screen-path <impl-screen-dir> # [--category error|dynamic|static]
158
+ ```
159
+ It greps `.swift`/`.kt`/`.xml` and classifies each hit `static | error | dynamic` with file:line. **Fold the
160
+ `error` and `dynamic` hits into the mapping** (each still gets the normal new/legacy/CMS resolution); diff the
161
+ `static` hits against the registry union to catch keys wired in code but absent from the registry. Dynamic
162
+ keys that can't be statically resolved are mapped with a note (`verdict: review` / "dynamic - enumerate at
163
+ runtime"), never silently dropped.
164
+
165
+ ## Pitfalls
166
+ - **Code-only / map-skim misses keys.** New side needs registry × design-export × code; legacy side needs
167
+ the cell-chain trace. This is the difference between right and wrong keys.
168
+ - **Static cross-reference misses runtime strings.** Error/validation/alert copy and dynamic/interpolated
169
+ keys are invisible to the literal greps - run `scan-screen-keys.py` over the implemented screen and fold
170
+ in the `error`/`dynamic` hits, or they silently vanish from the map.
171
+ - The recursive Git tree of `<specs-repo>` truncates (~43k paths) - use `gh search code` / direct
172
+ `contents/` calls, not `git/trees/HEAD?recursive=1`.
173
+ - New namespace ≠ legacy flat key - "reuse" means reuse the *value* (or alias the key), not string equality.
174
+ - Legacy values default to the key in the committed Android `strings.xml` - that file is a key *catalog*,
175
+ not values; values live in the iOS `language.plist` (and the service).