@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.
- package/CHANGELOG.md +42 -0
- package/README.md +10 -6
- package/README.tr.md +143 -0
- package/docs/architecture.md +23 -8
- package/docs/ecosystem.md +237 -0
- package/install/_plugin-skills.mjs +16 -2
- package/install/codex.mjs +9 -4
- package/install/templates/copilot-instructions.md +12 -9
- package/package.json +1 -1
- package/pipeline/commands/multi-agent/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/analysis/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/autopilot/SKILL.md +6 -2
- package/pipeline/commands/multi-agent/channels/SKILL.md +15 -4
- package/pipeline/commands/multi-agent/create-jira/SKILL.md +4 -4
- package/pipeline/commands/multi-agent/dev/SKILL.md +10 -23
- package/pipeline/commands/multi-agent/dev-autopilot/SKILL.md +10 -2
- package/pipeline/commands/multi-agent/dev-local/SKILL.md +10 -24
- package/pipeline/commands/multi-agent/dev-local-autopilot/SKILL.md +10 -3
- package/pipeline/commands/multi-agent/jira/SKILL.md +13 -2
- package/pipeline/commands/multi-agent/language/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/local/SKILL.md +6 -2
- package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +6 -2
- package/pipeline/commands/multi-agent/log/SKILL.md +7 -1
- package/pipeline/commands/multi-agent/ship/SKILL.md +5 -1
- package/pipeline/commands/multi-agent/sync/SKILL.md +3 -3
- package/pipeline/commands/multi-agent/update/SKILL.md +1 -1
- package/pipeline/lib/extract-conventions.sh +44 -15
- package/pipeline/lib/fetch-figma-annotations.sh +8 -1
- package/pipeline/lib/fetch-fortify.sh +23 -8
- package/pipeline/lib/figma-screenshot.sh +11 -1
- package/pipeline/lib/issue-fetcher.sh +76 -9
- package/pipeline/lib/md2confluence-v3.py +16 -2
- package/pipeline/lib/plan-todos.sh +5 -2
- package/pipeline/lib/post-pr-review.sh +8 -6
- package/pipeline/lib/shadow-git.sh +50 -9
- package/pipeline/lib/submodule-detector.sh +8 -1
- package/pipeline/multi-agent-refs/_input-parser.md +1 -1
- package/pipeline/multi-agent-refs/channels/confluence.md +3 -0
- package/pipeline/multi-agent-refs/channels/issue-comment.md +2 -2
- package/pipeline/multi-agent-refs/channels/jira.md +13 -2
- package/pipeline/multi-agent-refs/channels/pr-review-actions.md +1 -1
- package/pipeline/multi-agent-refs/channels/pr.md +20 -0
- package/pipeline/multi-agent-refs/channels/wiki.md +1 -1
- package/pipeline/multi-agent-refs/cross-cli-contract.md +3 -3
- package/pipeline/multi-agent-refs/features/worktree-finalize.md +1 -1
- package/pipeline/multi-agent-refs/generate-issue.md +2 -2
- package/pipeline/multi-agent-refs/issue-jira-triad.md +3 -3
- package/pipeline/multi-agent-refs/payload-contracts.md +67 -0
- package/pipeline/multi-agent-refs/phases/modes.md +20 -0
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +1 -1
- package/pipeline/multi-agent-refs/phases/phase-6-commit.md +8 -40
- package/pipeline/multi-agent-refs/phases/phase-7-report.md +5 -3
- package/pipeline/multi-agent-refs/phases.md +6 -0
- package/pipeline/multi-agent-refs/rules.md +2 -0
- package/pipeline/schemas/prefs.schema.json +1 -1
- package/pipeline/scripts/audit-log-rotate.sh +10 -0
- package/pipeline/scripts/build-stack-plugins.mjs +8 -1
- package/pipeline/scripts/check-derived-drift.mjs +13 -1
- package/pipeline/scripts/diff-explain.mjs +41 -3
- package/pipeline/scripts/diff-risk-score.mjs +72 -8
- package/pipeline/scripts/gen-mode-dispatch.mjs +1 -1
- package/pipeline/scripts/learning-curve.mjs +8 -2
- package/pipeline/scripts/output-quality-check.sh +15 -4
- package/pipeline/scripts/phase-tracker.sh +21 -8
- package/pipeline/scripts/pre-commit-check.sh +69 -22
- package/pipeline/scripts/render-agent-log-cost.sh +8 -3
- package/pipeline/scripts/render-cost-summary.sh +42 -22
- package/pipeline/scripts/render-work-summary.sh +47 -13
- package/pipeline/scripts/review-scope.mjs +1 -1
- package/pipeline/scripts/run-aggregator.mjs +38 -14
- package/pipeline/scripts/smoke-schema-validation.sh +5 -1
- package/pipeline/scripts/test-gap-scan.mjs +45 -6
- package/pipeline/scripts/uninstall.mjs +39 -4
- package/pipeline/scripts/update-issue-progress.sh +12 -16
- package/pipeline/scripts/worktree-finalize.sh +23 -2
- package/pipeline/skills/.skills-index.json +11 -2
- package/pipeline/skills/shared/README.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-autopilot/SKILL.md +4 -0
- package/pipeline/skills/shared/core/multi-agent-create-jira/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-dev/SKILL.md +4 -17
- package/pipeline/skills/shared/core/multi-agent-dev-autopilot/SKILL.md +8 -0
- package/pipeline/skills/shared/core/multi-agent-dev-local/SKILL.md +5 -18
- package/pipeline/skills/shared/core/multi-agent-dev-local-autopilot/SKILL.md +8 -0
- package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-local/SKILL.md +4 -0
- package/pipeline/skills/shared/core/multi-agent-local-autopilot/SKILL.md +4 -0
- package/pipeline/skills/shared/core/multi-agent-ship/SKILL.md +4 -0
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +2 -2
- package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +302 -0
- package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +144 -0
- package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +156 -0
- package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +108 -0
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +175 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +865 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +335 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +344 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +130 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +264 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-key-shots.py +298 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +529 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +187 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +171 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +184 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/snapshot-resources.sh +26 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +173 -0
- package/pipeline/skills/skills-index.md +3 -2
package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md
ADDED
|
@@ -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.
|
package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md
ADDED
|
@@ -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).
|