@mmerterden/multi-agent-pipeline 20.6.0 → 20.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/docs/facts.json +1 -1
  3. package/manifest.json +71 -71
  4. package/package.json +1 -1
  5. package/pipeline/multi-agent-refs/features/design-conformance.md +62 -64
  6. package/pipeline/scripts/_notices.mjs +11 -0
  7. package/pipeline/scripts/gen-skills-index.mjs +1 -1
  8. package/pipeline/skills/.skill-manifest.json +39 -39
  9. package/pipeline/skills/shared/README.md +1 -1
  10. package/pipeline/skills/shared/external/agent-introspection-debugging/SKILL.md +1 -0
  11. package/pipeline/skills/shared/external/android-architecture/SKILL.md +2 -0
  12. package/pipeline/skills/shared/external/android-performance/SKILL.md +2 -0
  13. package/pipeline/skills/shared/external/android-security/SKILL.md +2 -0
  14. package/pipeline/skills/shared/external/backlog/BACKLOG.md +1 -1
  15. package/pipeline/skills/shared/external/backlog/SKILL.md +56 -33
  16. package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +1 -0
  17. package/pipeline/skills/shared/external/compose-components/SKILL.md +2 -0
  18. package/pipeline/skills/shared/external/compose-navigation/SKILL.md +3 -2
  19. package/pipeline/skills/shared/external/compose-testing/SKILL.md +2 -0
  20. package/pipeline/skills/shared/external/council/SKILL.md +1 -0
  21. package/pipeline/skills/shared/external/css-modern/SKILL.md +1 -0
  22. package/pipeline/skills/shared/external/database-patterns/SKILL.md +1 -0
  23. package/pipeline/skills/shared/external/evidence-github/SKILL.md +2 -0
  24. package/pipeline/skills/shared/external/evidence-registry/SKILL.md +2 -0
  25. package/pipeline/skills/shared/external/gradle-kotlin-dsl/SKILL.md +2 -0
  26. package/pipeline/skills/shared/external/html-semantic/SKILL.md +1 -0
  27. package/pipeline/skills/shared/external/humanizer/SKILL.md +1 -0
  28. package/pipeline/skills/shared/external/ios-coding-standard/SKILL.md +1 -0
  29. package/pipeline/skills/shared/external/ios-module-structure/SKILL.md +1 -0
  30. package/pipeline/skills/shared/external/ios-security/SKILL.md +2 -0
  31. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +91 -283
  32. package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +119 -151
  33. package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +60 -90
  34. package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +119 -156
  35. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +726 -787
  36. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +253 -288
  37. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +243 -304
  38. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +88 -104
  39. package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +181 -235
  40. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-key-shots.py +198 -263
  41. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +461 -466
  42. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +145 -151
  43. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +123 -141
  44. package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +146 -157
  45. package/pipeline/skills/shared/external/localization-reuse-map/scripts/snapshot-resources.sh +22 -19
  46. package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +156 -140
  47. package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +1 -0
  48. package/pipeline/skills/shared/external/play-store-review/SKILL.md +2 -0
  49. package/pipeline/skills/shared/external/python-patterns/SKILL.md +1 -0
  50. package/pipeline/skills/shared/external/react-best-practices/SKILL.md +1 -0
  51. package/pipeline/skills/shared/external/rest-api-design/SKILL.md +1 -0
  52. package/pipeline/skills/shared/external/retrofit-networking/SKILL.md +2 -0
  53. package/pipeline/skills/shared/external/room-database/SKILL.md +2 -0
  54. package/pipeline/skills/shared/external/search-first/SKILL.md +1 -0
  55. package/pipeline/skills/shared/external/signal-community/SKILL.md +2 -0
  56. package/pipeline/skills/shared/external/skill-creator/SKILL.md +80 -41
  57. package/pipeline/skills/shared/external/skill-creator/audit.md +63 -59
  58. package/pipeline/skills/shared/external/skill-creator/checklist.md +28 -20
  59. package/pipeline/skills/shared/external/skill-creator/examples.md +40 -40
  60. package/pipeline/skills/shared/external/skill-creator/label-check.md +48 -36
  61. package/pipeline/skills/shared/external/skill-creator/scripts/audit-panel.js +91 -100
  62. package/pipeline/skills/shared/external/skill-creator/template.md +51 -39
  63. package/pipeline/skills/shared/external/tailwind-css/SKILL.md +1 -0
  64. package/pipeline/skills/shared/external/testing-backend/SKILL.md +1 -0
  65. package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +1 -0
  66. package/pipeline/skills/shared/external/vue-composition/SKILL.md +1 -0
  67. package/pipeline/skills/shared/external/web-accessibility/SKILL.md +1 -0
  68. package/pipeline/skills/shared/external/web-performance/SKILL.md +1 -0
  69. package/pipeline/skills/shared/external/web-testing/SKILL.md +1 -0
@@ -1,176 +1,139 @@
1
- # Sources & retrieval recipes (the rigorous method)
1
+ # Sources and recipes
2
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.
3
+ Where every column of the map comes from, how to trace it, and the traps that produce a confidently wrong row. The whole point of the skill is a cross-reference: each column is checked against at least one other source, never skimmed from one.
6
4
 
7
- ## The data model - two sides, sources per side
5
+ ## Where each input lives
8
6
 
9
- | | UI element → key | key → translation value |
7
+ | Input | Source | Access |
10
8
  |---|---|---|
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. an item 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
- `ItemListGroup.Title`, `ItemListRow.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>/<Screen>ViewModel.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/<Field>TextInputCellPresentationModel.swift" -H "Accept: application/vnd.github.raw" | grep -oE '"[^"]+"\.localized'
68
- ```
69
- This is how `NewSurnameReq` / `OrderNumber` / `ShippingAddressPlaceHolder` 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):
9
+ | Figma structure (components, nodes, screenshot) | In-repo design export | Local files |
10
+ | CMS annotations and overlay geometry | Figma REST API (keychain `FIGMA_ACCESS_TOKEN`); Figma MCP in Dev Mode as the rate-limit-free alternative; the export as offline fallback | Network |
11
+ | Screenshot | `screens/<frame>/screenshot.png` in the design export | Local |
12
+ | Specs repo files | `gh api repos/<owner>/<repo>/contents/<path>` with `Accept: application/vnd.github.raw`; no checkout needed | Network |
13
+ | Redesign app and resources repo | Local checkouts | Local |
14
+ | Legacy values | `resources/Localization/Legacy/<lang>.json`, a flat snapshot that is refreshed from the label service | Local, network on refresh |
15
+
16
+ The Android app's committed `strings.xml` is only a key catalog: its values default to the key itself. Real legacy values live in the iOS bundled `language.plist` and, authoritatively, in the label service.
17
+
18
+ ## New keys: the three-way union
19
+
20
+ A screen's keys come from three places, and each one misses something the others see:
21
+
22
+ 1. **Design export**: read `screens/<frame>/components-used.md` of each state frame the screen has (default, error, loading, filled...).
23
+ 2. **Component registry**: each used component's `<node>.json -> localizationKeys`. These are keys the component renders internally, and they are often absent from the screen code.
24
+ 3. **Screen code**: on iOS, each call site of `LocalizationStringKey.<Namespace>.<leaf>` counts as one element.
25
+
26
+ Take the union and tag every key: screen-specific, component-owned, or shared (a reusable component or a shared primitive).
27
+
28
+ Registry caveat: a shared component such as `HeaderMain` or `BottomActions` may list keys that this screen never shows, since the screen supplies its own title. Check the code before a registry key goes into the map.
29
+
30
+ ### Keys the union cannot see
31
+
32
+ Error, validation and alert strings, and keys assembled at runtime, rarely appear in the design export or the registry. Run the scanner on the implemented screen:
33
+
95
34
  ```bash
96
- python3 scripts/resolve-legacy-values.py --langs all \
97
- --snapshot-root <specs-repo>/resources/Localization/Legacy \
98
- --keys "Continue,EmailAddress,PasswordRule,EmailAlreadyExists"
35
+ python3 scripts/scan-screen-keys.py --out scan.json --screen-path <screen-dir>
99
36
  ```
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
37
 
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:
38
+ Every `error` and `dynamic` hit becomes a row. Compare the `static` hits with the union; whatever remains is a key the code wires but the map lacks. Skipping this step drops those strings silently.
39
+
40
+ ## New values
41
+
42
+ Values are authored per key in `Suggested/<Key>.json`, in eight languages, and they are shared by all platforms: one value per key, only the key usage differs per platform.
43
+
107
44
  ```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'
45
+ python3 scripts/resolve-new-values.py --keys "<k1>,<k2>" --resources-root <res> --langs all \
46
+ --catalog <path/to/shipped/Localizable.xcstrings>
112
47
  ```
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
48
 
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.
49
+ Always pass `--catalog`. A missing Suggested file means "not in this snapshot", not "unauthored": the snapshot can lag the shipped catalog. With the catalog the value still resolves and the script names the stale snapshot so it can be refreshed with `snapshot-resources.sh`. Only a key absent from both the snapshot and the catalog is truly unauthored. Never open an authoring request for a key the catalog resolved.
123
50
 
124
- ## CMS values - the content team's actual copy (Figma Dev Mode annotations)
51
+ ## CMS copy (Figma annotations)
125
52
 
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):
53
+ The content team writes final copy as Dev Mode annotations in the category "Final UX Writing", usually as `TR: ...` and `EN: ...` lines. The text drawn in the design is often placeholder copy; the annotation is the real value.
130
54
 
131
55
  ```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
56
+ python3 scripts/fetch-annotations.py --out _annotations.json --mapping <map>.json
57
+ python3 scripts/fetch-annotations.py --out _annotations.json --from-mcp <raw-mcp-dump>.json
58
+ python3 scripts/fetch-annotations.py --out _annotations.json --local <design-export>/screens/<frame>
139
59
  ```
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:
60
+
61
+ Pair every annotation with its row through `nodeId`: the id is saved in the row as `cmsNodeId`, and the copy goes into `cms: {tr, en}`. An element without an annotation keeps a blank CMS cell. The script lists nodes that have TR but no EN; send those back to the content team.
62
+
63
+ ## Legacy keys: trace, do not skim
64
+
65
+ ### iOS
66
+
67
+ The spec names the entry point: `specs/<area>/<screen>-spec.md` carries a blockquote `> Legacy reference (iOS):` naming the class. Follow the chain from there:
68
+
69
+ view controller, then view model, then data source, then the cell presentation models, where the `"Key".localized` calls usually are.
70
+
71
+ Form-field labels are normally defaulted inside the cell presentation models (often shared input-cell models), not in the view model. Do not stop at the view model.
72
+
73
+ ### Android
74
+
75
+ Labels come from the layout XML as `@string/Key`; strings set at runtime come from the fragment as `R.string.<name>`. For form-field labels the layout is the most reliable place to read them.
76
+
77
+ ### Web
78
+
79
+ Call sites are `t('key')` or `i18n.t('key')` (react-i18next, next-intl) and `$t('key')` (vue-i18n), resolved against `locales/<lang>.json`. A template literal or a variable argument is a dynamic key. A namespace from `useTranslation('<ns>')` is prepended to the key, so `t('email_label')` under `useTranslation('signup')` is `signup.email_label`.
80
+
81
+ ### Cross-platform drift
82
+
83
+ The same element can carry different keys per platform (`AgentaUserAlreadyAdded` on one, `AgencyUserAlreadyAdded` on the other). That is a `review`. Keep every platform column that has a key and render a dash where a platform has none.
84
+
85
+ ## Legacy values
155
86
 
156
87
  ```bash
157
- python3 scripts/scan-screen-keys.py --screen-path <impl-screen-dir> # [--category error|dynamic|static]
88
+ python3 scripts/resolve-legacy-values.py --keys "Continue,EmailAddress" \
89
+ --snapshot-root resources/Localization/Legacy --langs all --prefix Mobile-
90
+ ```
91
+
92
+ - Reading the snapshot needs no network. `fetch-legacy-labels.py` refreshes it and is the only step that goes online.
93
+ - `--live` checks against the service directly; if the live call fails, the script falls back to the snapshot, then to the plists.
94
+ - `--plist-root` reads the bundled `<lang>.lproj/language.plist` as a last resort. It is usually a stale subset, so a hit from it deserves a note.
95
+ - **Backend prefix.** When the backend stores `Mobile-Continue` but the app asks for `Continue`, pass `--prefix Mobile-`, keep the mapping keys bare, and set the mapping's `legacyKeyPrefix` so the table shows the full stored key.
96
+
97
+ ## Overlay geometry
98
+
99
+ The overlay does not depend on annotations. With zero annotations every keyed element still gets a gray "awaiting copy" card. The number on a card and on its dot is the index of the matched row in `mapping.rows`, counted from 1, so it agrees with the table rather than with the order on screen.
100
+
101
+ **REST (preferred).** Needs `figmaFileKey` and the real frame node. Take the frame node-id from the design export's `tree.json` root `nodeId`: folder-name suffixes and ids copied from older maps are often wrong. Check that the node text belongs to this screen; background nodes that match no row are dropped automatically.
102
+
103
+ **`--spec` (screenshot-anchored).** Use it when there is no token or when REST geometry is noisy. Measure the box of every visible element on the clean screenshot, give the rows synthetic `cmsNodeId`s (`n1`, `n2`, ...), and write:
104
+
105
+ ```json
106
+ {"fileKey": "...", "pages": [{"nodeId": "1024:4096", "image": "screenshot.png",
107
+ "frame": {"w": 375, "h": 812},
108
+ "nodes": [{"id": "n1", "x": 16, "y": 64, "w": 220, "h": 28, "side": "left"}]}]}
158
109
  ```
159
- It greps `.swift`/`.kt`/`.xml`/`.ts`/`.tsx`/`.js`/`.jsx`/`.vue` and classifies each hit `static | error |
160
- dynamic` with file:line. **Fold the
161
- `error` and `dynamic` hits into the mapping** (each still gets the normal new/legacy/CMS resolution); diff the
162
- `static` hits against the registry union to catch keys wired in code but absent from the registry. Dynamic
163
- keys that can't be statically resolved are mapped with a note (`verdict: review` / "dynamic - enumerate at
164
- runtime"), never silently dropped.
110
+
111
+ `image` is either a data URL or a PNG path, resolved from the folder of the spec file. `side` is `left` (default) or `right`: it puts the card on the side where its target sits, which two-column layouts need. Each side is numbered and stacked independently, and right-side connectors are mirrored.
112
+
113
+ The export's `tree.json` stops at component instances and carries no geometry per label, so take only the frame node-id from it.
114
+
115
+ Component-owned keys (list rows, promotion grids, summary cards) are marked on the overlay too, flagged `(owned by <Component>)`, with their keys taken from the registry `localizationKeys`.
116
+
117
+ **Multi-state screens.** Each state gets an overlay of its own, built from its own `--spec`, screenshot and `cmsNodeId`s; a row may keep the same `cmsNodeId` in several states. Stack the results vertically in a single PNG, padded to the widest one with a `#1e1e1e` gutter between them, point the mapping's `overlay` at that PNG, and keep the per-state images next to it.
118
+
119
+ **Isolating a component variant.** Once the library is re-versioned, node-ids exported from it stop resolving: `/components` may return nothing and `/images` returns `null`. Walk the live screen frame instead (`GET /v1/files/<key>/nodes?ids=<frame>&depth=6`), find the `INSTANCE` whose `name` matches, render that id, and take label boxes from its text nodes' `absoluteBoundingBox` multiplied by the render `scale`. Without a token, or when the render fails, cut the variant from a screenshot of one of the states.
120
+
121
+ ## Keyshots
122
+
123
+ For each matched row, `render-key-shots.py` saves a crop with the element outlined in red as `keyshot__<NN>__<key>.png` inside `keyshots/`, where NN is the row number in the table, and it writes `<slug>.keyshots.manifest.json`. A row with no match gets a dash, never a guessed crop. Set the mapping's `keyshots` to the manifest file name.
124
+
125
+ Geometry reuses the overlay inputs: REST (visible text only, filtered for ancestor visibility and cumulative opacity) or the same `--spec` files, one per state, repeatable; `image` may be a path such as the committed `screenshot.png`.
165
126
 
166
127
  ## Pitfalls
167
- - **Code-only / map-skim misses keys.** New side needs registry × design-export × code; legacy side needs
168
- the cell-chain trace. This is the difference between right and wrong keys.
169
- - **Static cross-reference misses runtime strings.** Error/validation/alert copy and dynamic/interpolated
170
- keys are invisible to the literal greps - run `scan-screen-keys.py` over the implemented screen and fold
171
- in the `error`/`dynamic` hits, or they silently vanish from the map.
172
- - The recursive Git tree of `<specs-repo>` truncates (~43k paths) - use `gh search code` / direct
173
- `contents/` calls, not `git/trees/HEAD?recursive=1`.
174
- - New namespace ≠ legacy flat key - "reuse" means reuse the *value* (or alias the key), not string equality.
175
- - Legacy values default to the key in the committed Android `strings.xml` - that file is a key *catalog*,
176
- not values; values live in the iOS `language.plist` (and the service).
128
+
129
+ - **Truncated trees.** On a large specs repo the recursive Git tree is cut off at roughly 43k paths. Search with `gh search code` or fetch through `contents/` calls rather than `git/trees/HEAD?recursive=1`.
130
+ - **Namespaced vs flat keys.** `SignUpAccountDetails.ContinueButton` against legacy `Continue`: reuse is about the value, not the key string.
131
+ - **Hidden form labels.** They sit in shared input-cell models; read them from the Android layout.
132
+ - **Blank is fine.** Empty Old and CMS columns are expected states, not errors.
133
+ - **Scanner skipped.** Error, validation, alert and dynamic keys never show up in the static cross-reference.
134
+ - **Frame origin drift.** A rendered frame PNG may not share the frame's `absoluteBoundingBox` origin. Subtracting the frame x/y from text-node x/y has shifted boxes by tens of pixels, a full row off. Check one crop by eye, and prefer boxes read off the actual screenshot to coordinates mixed from two sources.
135
+ - **Stale snapshot.** Missing from Suggested is not unauthored; pass `--catalog` and refresh the snapshot instead of filing requests.
136
+ - **Value anchoring.** Never anchor a keyshot by value across a whole file: short labels recur on unrelated screens and give a confidently wrong crop, which is worse than a dash. Recover a row only through its own `cmsNodeId`, and leave `characters` out of nodes handed to `render-key-shots.py --spec`, because text present turns value matching back on and can bind an unrelated row.
137
+ - **Confluence auth.** Server / Data Center takes a Bearer PAT (keychain `CONFLUENCE_API_TOKEN`), not the Cloud `user:token` form. Pages are looked up with the `content?title=` query, which reads the database; CQL is avoided because its index lags and leads to duplicate pages.
138
+ - **Secrets.** Never commit a label-service token, Confluence PAT or Figma token into a mapping, a taxonomy file or the docs.
139
+ - **TLS proxies.** Publishing runs through `curl` so a corporate proxy's trust store is honoured (Python `urllib` fails verification there). The token goes in a `chmod 600` config passed with `-K`, never on argv. For the Figma scripts behind such a proxy, export the system roots with `security find-certificate -a -p > roots.pem` and pass `--ca-file roots.pem`.