@noemuch/bridge-ds 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noemuch/bridge-ds",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "AI-powered design generation in Figma — 100% design system compliant. Connects Claude Code to Figma via MCP for spec-first, token-bound, component-native design.",
5
5
  "main": "lib/cli.js",
6
6
  "bin": {
@@ -46,6 +46,12 @@ Parse from spec:
46
46
 
47
47
  All paths relative to `.claude/skills/design-workflow/references/knowledge-base/`.
48
48
 
49
+ **Registry key validation (BLOCKING):**
50
+ Before using any registry, verify that entries contain `key` fields (not just `id` or `name`):
51
+ - Components must have `key` (hex hash like `"abc123..."`) — NOT node IDs like `"1008:174"`
52
+ - Variables must have `key` — NOT just name paths like `"color/background/neutral/boldest"`
53
+ - If ANY registry is missing `key` fields → **STOP** and run `schemas/validation.md` procedure before continuing
54
+
49
55
  ### 1b. Pattern Matching (BLOCKING)
50
56
 
51
57
  **This step is MANDATORY. No design generation without completing it.**
@@ -109,14 +115,26 @@ Never generate a full design in a single script. Split into small, sequential st
109
115
 
110
116
  **CRITICAL — Pre-script element audit (BLOCKING, Rule 18):**
111
117
 
112
- Before writing EACH script, list every visual element it will create and verify against registries:
118
+ Before writing EACH script, list every visual element it will create and cross-reference against the spec's "DS Components Used" table AND the registries:
119
+
113
120
  ```
114
- Elements in this step:
115
- - Avatar components.json key: xxx ✓
116
- - Divider → components.json key: xxx ✓
117
- - Label text raw text (layout frame label, no DS component)
121
+ PRE-SCRIPT AUDIT Step {n}:
122
+ Spec requires: Registry match: Script will use:
123
+ ─────────────────────────────────────────────────────────────────────
124
+ AssetLogo (logo) logos.json key: abc123 → importComponentByKeyAsync
125
+ Tag v2 (label) → components.json key: def456 → importComponentSetByKeyAsync ✓
126
+ CardBase (container) → components.json key: ghi789 → importComponentByKeyAsync ✓
127
+ Section title → NO DS component → raw text (justified) ✓
128
+ Layout wrapper → NO DS component → raw frame (structural) ✓
118
129
  ```
119
- If an element exists in `components.json`, it MUST be imported via `importComponentByKeyAsync`. NEVER recreate as raw frame/text/shape. NEVER hardcode hex colors — always bind variables.
130
+
131
+ **BLOCKING RULES:**
132
+ - If a spec-listed DS component is planned as a raw element → **STOP. Rewrite the script.**
133
+ - If an element exists in ANY registry (components, icons, logos, illustrations) → **MUST import it**. No exceptions.
134
+ - If you're about to use `figma.createEllipse()`, `figma.createRectangle()`, or `figma.createFrame()` for something that looks like a DS component → **STOP and check registries first.**
135
+ - Only create raw elements for pure structural layout frames or when NO DS component exists (document WHY in the audit).
136
+
137
+ NEVER recreate as raw frame/text/shape. NEVER hardcode hex colors — always bind variables.
120
138
 
121
139
  **Standard steps for a screen:**
122
140
 
@@ -159,10 +159,10 @@ text.fontSize = 32;
159
159
 
160
160
  // CORRECT (key from registries/text-styles.json)
161
161
  var style = await figma.importStyleByKeyAsync("YOUR_TEXT_STYLE_KEY");
162
- text.textStyleId = style.id;
162
+ await text.setTextStyleIdAsync(style.id); // MUST use async version — see Rule 21
163
163
  ```
164
164
 
165
- **Load text style keys from `registries/text-styles.json`.**
165
+ **Load text style keys from `registries/text-styles.json`.** Always use `setTextStyleIdAsync()`, not `textStyleId =`.
166
166
 
167
167
  ---
168
168
 
@@ -356,28 +356,38 @@ The `return` before the IIFE is mandatory — without it, the Promise is lost.
356
356
 
357
357
  ## Rule 18: DS component reuse — NEVER recreate existing components
358
358
 
359
- **This is the most critical rule. Violations require script rewrite.**
359
+ **This is the most critical rule. Violations require FULL script rewrite.**
360
360
 
361
361
  Before creating ANY visual element, check the registries:
362
- 1. `registries/components.json` → Buttons, Tags, Inputs, Avatars, Dividers, etc.
362
+ 1. `registries/components.json` → Buttons, Tags, Inputs, Avatars, Dividers, Cards, etc.
363
363
  2. `registries/icons.json` → Icons (if exists)
364
- 3. `registries/logos.json` → Logos (if exists)
364
+ 3. `registries/logos.json` → Logos, brand assets (if exists)
365
365
  4. `registries/illustrations.json` → Illustrations (if exists)
366
366
 
367
367
  **NEVER:**
368
- - Create a raw frame/ellipse for an Avatar → import the DS component
369
- - Create a raw rectangle for a Divider → import the DS component
370
- - Create a raw frame with text for a Tag/Badge → import the DS component
368
+ - `figma.createEllipse()` for a logo → import from logos.json via `importComponentByKeyAsync`
369
+ - `figma.createFrame()` for a Tag/Badge → import from components.json via `importComponentSetByKeyAsync`
370
+ - `figma.createFrame()` for a Card → import the DS Card component
371
+ - `figma.createRectangle()` for a Divider → import the DS Divider component
371
372
  - Hardcode hex colors → always bind variables
372
373
 
373
- **Pre-script checklist (mandatory):**
374
+ **Why this matters:** Using raw elements cascades into multiple failures:
375
+ - No `INSTANCE_SWAP` props possible (can't swap a logo on an ellipse)
376
+ - No inherited component properties (size, variant, state)
377
+ - Wrong proportions (raw frames don't match DS component sizing)
378
+ - Breaks the design system contract — the whole point of Bridge
379
+
380
+ **Pre-script audit (MANDATORY — must appear before every script):**
374
381
  ```
375
- Elements in this step:
376
- - Avatar components.json key: xxx ✓
377
- - Divider → components.json key: xxx ✓
378
- - Label raw text (no DS component for this)
382
+ PRE-SCRIPT AUDIT Step {n}:
383
+ Spec requires: Registry match: Script will use:
384
+ ─────────────────────────────────────────────────────────────────────
385
+ {element} {registry}.json key: {key} → import{method}
386
+ {element} → NO DS component → raw {type} (reason) ✓
379
387
  ```
380
388
 
389
+ **If ANY spec-listed DS component is planned as a raw element → STOP and fix before writing the script.**
390
+
381
391
  ---
382
392
 
383
393
  ## Rule 19: Canvas positioning — never stack components
@@ -400,6 +410,42 @@ mainComponent.y = 400;
400
410
 
401
411
  ---
402
412
 
413
+ ## Rule 20: Component key vs Node ID (CRITICAL)
414
+
415
+ Figma components have TWO different identifiers:
416
+
417
+ | | Node ID (`id`) | Component Key (`key`) |
418
+ |---|---|---|
419
+ | **Format** | `"1008:174"` (colon-separated numbers) | `"abc123def456..."` (hex hash) |
420
+ | **Used for** | `figma.getNodeByIdAsync()` | `importComponentByKeyAsync()` |
421
+ | **Available from** | Any node | Only published components/variables/styles |
422
+
423
+ **These are NOT interchangeable.** Import APIs (`importComponentByKeyAsync`, `importComponentSetByKeyAsync`, `importVariableByKeyAsync`, `importStyleByKeyAsync`) all require the `key`, NOT the `id`.
424
+
425
+ Similarly, variables have:
426
+ - `name`: `"color/background/neutral/boldest"` — human-readable path
427
+ - `key`: `"VariableID:55:114"` or hex hash — for `importVariableByKeyAsync`
428
+
429
+ **Always verify registries contain `key` fields before writing import scripts. If a registry only has `id` or `name` values, re-run extraction to capture keys.**
430
+
431
+ ---
432
+
433
+ ## Rule 21: setTextStyleIdAsync (not textStyleId) in dynamic-page context
434
+
435
+ When running scripts via `figma_execute` (which uses `documentAccess: "dynamic-page"`), text style assignment MUST use the async API:
436
+
437
+ ```js
438
+ // WRONG — crashes in dynamic-page context
439
+ text.textStyleId = style.id;
440
+
441
+ // CORRECT
442
+ await text.setTextStyleIdAsync(style.id);
443
+ ```
444
+
445
+ Same applies to `setFillStyleIdAsync`, `setStrokeStyleIdAsync`, `setEffectStyleIdAsync`.
446
+
447
+ ---
448
+
403
449
  ## Standard Script Boilerplate
404
450
 
405
451
  ```js
@@ -6,6 +6,13 @@ This directory contains your design system's complete documentation, generated b
6
6
 
7
7
  ```
8
8
  knowledge-base/
9
+ schemas/ ← Registry format definitions (read during setup)
10
+ components.md ← components.json schema (required fields, extraction scripts)
11
+ variables.md ← variables.json schema
12
+ text-styles.md ← text-styles.json schema
13
+ assets.md ← icons/logos/illustrations schema
14
+ validation.md ← Post-extraction validation procedure
15
+
9
16
  registries/ ← Raw DS data (auto-extracted from Figma)
10
17
  components.json ← Component names, keys, variants, properties
11
18
  variables.json ← Variable names, keys, types, values by mode
@@ -42,10 +49,12 @@ knowledge-base/
42
49
 
43
50
  Run `/design-workflow setup` in Claude Code. Claude will:
44
51
 
45
- 1. **Extract** your DS from Figma via `figma_get_design_system_kit`
46
- 2. **Analyze** the raw data and write intelligent guides
47
- 3. **Ask for screenshots** of your product's key screens
48
- 4. **Generate** layout pattern documentation from the screenshots
52
+ 1. **Extract** your DS from Figma via MCP tools
53
+ 2. **Write registries** following the schemas in `schemas/` (every entry must have a `key` field)
54
+ 3. **Validate** registries by test-importing sample keys via `figma_execute`
55
+ 4. **Analyze** the raw data and write intelligent guides
56
+ 5. **Ask for screenshots** of your product's key screens
57
+ 6. **Generate** layout pattern documentation from the screenshots
49
58
 
50
59
  ## How to update
51
60
 
@@ -0,0 +1,144 @@
1
+ # Schema: icons.json, logos.json, illustrations.json
2
+
3
+ > **Read this BEFORE writing asset registries during setup.**
4
+
5
+ ---
6
+
7
+ ## Required Structure (same for all 3 files)
8
+
9
+ ```json
10
+ {
11
+ "meta": {
12
+ "source": "Library file name",
13
+ "fileKey": "FigmaFileKey",
14
+ "extractedAt": "YYYY-MM-DD",
15
+ "totalComponents": 344
16
+ },
17
+ "items": [
18
+ {
19
+ "name": "icon/utility/check",
20
+ "key": "abc123def456...",
21
+ "type": "COMPONENT"
22
+ },
23
+ {
24
+ "name": "icon/utility/chevron-up",
25
+ "key": "def789ghi012...",
26
+ "type": "COMPONENT"
27
+ }
28
+ ],
29
+ "categories": {
30
+ "utility": {
31
+ "description": "UI action and navigation icons",
32
+ "count": 280,
33
+ "namingPattern": "icon/utility/{name}"
34
+ },
35
+ "flag": {
36
+ "description": "Country flag icons",
37
+ "count": 40,
38
+ "namingPattern": "icon/flag/{country}"
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ ---
45
+
46
+ ## Field Requirements
47
+
48
+ ### Per asset entry (`items` array)
49
+
50
+ | Field | Required | Description |
51
+ |-------|----------|-------------|
52
+ | `name` | **YES** | Component name/path |
53
+ | `key` | **YES** | Component key for `importComponentByKeyAsync`. NOT a node ID. |
54
+ | `type` | **YES** | `"COMPONENT"` or `"COMPONENT_SET"` |
55
+
56
+ ### Categories (documentation only)
57
+
58
+ Categories provide human-readable organization and naming patterns. They help Claude find the right asset without scanning all items.
59
+
60
+ ---
61
+
62
+ ## Large Libraries (500+ items)
63
+
64
+ For very large asset libraries (e.g., 1300+ financial logos), extracting every key upfront is impractical:
65
+
66
+ 1. **Extract keys for commonly-used items** (top 50-100) into the `items` array
67
+ 2. **Document naming patterns** in `categories` for the rest
68
+ 3. **On-demand extraction**: When a specific asset is needed during `design`, use `figma_execute` to search by name and get the key:
69
+
70
+ ```js
71
+ return (async function() {
72
+ var node = figma.currentPage.findOne(function(n) {
73
+ return n.name === "TARGET_NAME" && (n.type === "COMPONENT" || n.type === "COMPONENT_SET");
74
+ });
75
+ if (node) {
76
+ return { name: node.name, key: node.key, type: node.type };
77
+ }
78
+ return { error: "Not found" };
79
+ })();
80
+ ```
81
+
82
+ ---
83
+
84
+ ## How to Extract Keys
85
+
86
+ ### Via figma_execute
87
+
88
+ ```js
89
+ return (async function() {
90
+ var results = [];
91
+ // Get all components on the current page
92
+ var nodes = figma.currentPage.findAll(function(n) {
93
+ return n.type === "COMPONENT" || n.type === "COMPONENT_SET";
94
+ });
95
+
96
+ for (var i = 0; i < nodes.length; i++) {
97
+ var n = nodes[i];
98
+ // Skip individual variants inside component sets
99
+ if (n.type === "COMPONENT" && n.parent && n.parent.type === "COMPONENT_SET") continue;
100
+
101
+ results.push({
102
+ name: n.name,
103
+ key: n.key, // ← REQUIRED for importComponentByKeyAsync
104
+ type: n.type
105
+ });
106
+ }
107
+
108
+ return { items: results, total: results.length };
109
+ })();
110
+ ```
111
+
112
+ Run on each page of the asset library file. For multi-page libraries, run once per page.
113
+
114
+ ---
115
+
116
+ ## Usage in Scripts
117
+
118
+ ```js
119
+ // Import icon by key (from registries/icons.json)
120
+ var checkIcon = await figma.importComponentByKeyAsync("abc123def456...");
121
+ var iconInstance = checkIcon.createInstance();
122
+
123
+ // Import logo with variants by key
124
+ var logo = await figma.importComponentSetByKeyAsync("xyz789...");
125
+ var defaultVariant = logo.defaultVariant;
126
+ var logoInstance = defaultVariant.createInstance();
127
+ ```
128
+
129
+ ---
130
+
131
+ ## File-specific Notes
132
+
133
+ ### icons.json
134
+ - Icons are typically single components (`type: "COMPONENT"`)
135
+ - Naming convention: `icon/{category}/{name}` (e.g., `icon/utility/check`)
136
+
137
+ ### logos.json
138
+ - Logos may be component sets with mode variants (dark/light)
139
+ - Naming convention: `{brand}` or `logo/{brand}/{variant}`
140
+
141
+ ### illustrations.json
142
+ - Illustrations often have theme variants (dark/light/brand)
143
+ - May be component sets (`type: "COMPONENT_SET"`)
144
+ - Naming convention: `{category}/{name}` (e.g., `asset/crypto`, `utility/empty-state`)
@@ -0,0 +1,124 @@
1
+ # Schema: components.json
2
+
3
+ > **Read this BEFORE writing components.json during setup.**
4
+
5
+ ---
6
+
7
+ ## Required Structure
8
+
9
+ ```json
10
+ {
11
+ "meta": {
12
+ "source": "Library file name",
13
+ "fileKey": "FigmaFileKey",
14
+ "extractedAt": "YYYY-MM-DD",
15
+ "totalComponents": 100,
16
+ "totalComponentSets": 76
17
+ },
18
+ "components": {
19
+ "categoryName": [
20
+ {
21
+ "name": "Button",
22
+ "key": "abc123def456789...",
23
+ "id": "1008:174",
24
+ "type": "COMPONENT_SET",
25
+ "variants": 150,
26
+ "properties": {
27
+ "label": "TEXT",
28
+ "hasTrailingIcon": "BOOLEAN",
29
+ "leadingIcon": "INSTANCE_SWAP",
30
+ "variant": "VARIANT(primary,secondary,ghost)",
31
+ "state": "VARIANT(default,hover,disabled)",
32
+ "size": "VARIANT(large,medium,small)"
33
+ }
34
+ }
35
+ ]
36
+ }
37
+ }
38
+ ```
39
+
40
+ ---
41
+
42
+ ## Field Requirements
43
+
44
+ | Field | Required | Description |
45
+ |-------|----------|-------------|
46
+ | `name` | **YES** | Human-readable component name |
47
+ | `key` | **YES** | Component key for `importComponentByKeyAsync` / `importComponentSetByKeyAsync`. A hex hash like `"abc123def456..."` (NOT a node ID). |
48
+ | `id` | optional | Figma node ID like `"1008:174"`. Useful for lookups but NOT for imports. |
49
+ | `type` | **YES** | `"COMPONENT"` or `"COMPONENT_SET"`. Determines which import API to use. |
50
+ | `variants` | optional | Number of variants (only for COMPONENT_SET) |
51
+ | `properties` | **YES** | Object mapping property names to types: `TEXT`, `BOOLEAN`, `INSTANCE_SWAP`, `VARIANT(...)` |
52
+
53
+ ---
54
+
55
+ ## CRITICAL: `key` vs `id`
56
+
57
+ These are TWO DIFFERENT identifiers in Figma:
58
+
59
+ | | `id` (node ID) | `key` (component key) |
60
+ |---|---|---|
61
+ | **Looks like** | `"1008:174"` | `"abc123def456789abcdef..."` |
62
+ | **Used for** | `figma.getNodeById()` | `importComponentByKeyAsync()` |
63
+ | **Available from** | Any node | Only published components |
64
+
65
+ **`importComponentByKeyAsync` REQUIRES the `key`, NOT the `id`.** Using a node ID as a key will crash the script silently.
66
+
67
+ ---
68
+
69
+ ## How to Extract Keys
70
+
71
+ ### Option A: Via figma_execute (RECOMMENDED)
72
+
73
+ ```js
74
+ return (async function() {
75
+ var results = [];
76
+ var nodes = figma.currentPage.findAll(function(n) {
77
+ return n.type === "COMPONENT_SET" || (n.type === "COMPONENT" && !n.parent.type !== "COMPONENT_SET");
78
+ });
79
+ for (var i = 0; i < nodes.length; i++) {
80
+ var n = nodes[i];
81
+ results.push({
82
+ name: n.name,
83
+ key: n.key, // ← THIS is what importComponentByKeyAsync needs
84
+ id: n.id, // ← This is just the node ID (different!)
85
+ type: n.type,
86
+ variants: n.type === "COMPONENT_SET" ? n.children.length : undefined,
87
+ properties: n.type === "COMPONENT_SET" ? n.componentPropertyDefinitions : undefined
88
+ });
89
+ }
90
+ return { components: results };
91
+ })();
92
+ ```
93
+
94
+ Run this script on each page of the DS library file.
95
+
96
+ ### Option B: Via figma_get_design_system_kit
97
+
98
+ If the MCP tool returns component data, look for the `key` field in the response. If only `id` is returned, use Option A to get the actual keys.
99
+
100
+ ---
101
+
102
+ ## Import API Decision
103
+
104
+ | `type` value | Import API |
105
+ |---|---|
106
+ | `COMPONENT_SET` | `figma.importComponentSetByKeyAsync(key)` |
107
+ | `COMPONENT` | `figma.importComponentByKeyAsync(key)` |
108
+
109
+ Check the `type` field to decide which API to use. Using the wrong one will fail.
110
+
111
+ ---
112
+
113
+ ## Category Grouping
114
+
115
+ Group components by semantic category:
116
+ - `actions` — Buttons, IconButtons, Links
117
+ - `formControls` — Inputs, Selects, Checkboxes, Radios, Toggles
118
+ - `dataDisplay` — Tables, Lists, Cards, Badges, Tags
119
+ - `feedback` — Alerts, Toasts, Modals, Dialogs
120
+ - `navigation` — Tabs, Sidebar, Breadcrumbs, Pagination
121
+ - `layout` — Dividers, Spacers, Containers
122
+ - `indicators` — Avatars, Status, Progress
123
+
124
+ Use whatever categories match the DS's own organization.
@@ -0,0 +1,117 @@
1
+ # Schema: text-styles.json
2
+
3
+ > **Read this BEFORE writing text-styles.json during setup.**
4
+
5
+ ---
6
+
7
+ ## Required Structure
8
+
9
+ ```json
10
+ {
11
+ "meta": {
12
+ "source": "Library file name",
13
+ "fileKey": "FigmaFileKey",
14
+ "extractedAt": "YYYY-MM-DD",
15
+ "totalStyles": 56
16
+ },
17
+ "textStyles": {
18
+ "display": [
19
+ { "name": "display/lg", "key": "a9b866d18e77632daa1f4056b905af688933c4c7", "nodeId": "60:169" }
20
+ ],
21
+ "heading": [
22
+ { "name": "heading/xl/regular", "key": "930df26f44235299875c572ef52bd60cbf5ae956", "nodeId": "60:172" },
23
+ { "name": "heading/xl/accent", "key": "225299706052d97c6259e70545f448cb44e84f9f", "nodeId": "60:173" }
24
+ ],
25
+ "body": [
26
+ { "name": "body/lg/regular", "key": "fe5e50c7cbdc2b607aecb70747cfae7ac4da49ee", "nodeId": "60:206" }
27
+ ]
28
+ },
29
+ "effectStyles": {
30
+ "shadow": [
31
+ { "name": "shadow/xsmall", "key": "1b34455796052a6ec745a03064597491bb8db256", "nodeId": "2620:254" }
32
+ ]
33
+ },
34
+ "sizeMap": {
35
+ "display/lg": { "fontSize": 40, "lineHeight": 88, "fontFamily": "Inter" },
36
+ "heading/xl": { "fontSize": 40, "lineHeight": 48, "fontFamily": "Inter" }
37
+ }
38
+ }
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Field Requirements
44
+
45
+ ### Per text style entry
46
+
47
+ | Field | Required | Description |
48
+ |-------|----------|-------------|
49
+ | `name` | **YES** | Style name like `"heading/xl/regular"` |
50
+ | `key` | **YES** | Style key (40-char hex hash) for `importStyleByKeyAsync` |
51
+ | `nodeId` | optional | Figma node ID for reference |
52
+
53
+ ### sizeMap (optional but recommended)
54
+
55
+ Maps style names to font specs for quick human reference. Not used in scripts (scripts use the `key` to import the full style).
56
+
57
+ ---
58
+
59
+ ## How to Extract Keys
60
+
61
+ ### Via figma_execute
62
+
63
+ ```js
64
+ return (async function() {
65
+ var textStyles = await figma.getLocalTextStylesAsync();
66
+ var effectStyles = await figma.getLocalEffectStylesAsync();
67
+
68
+ var result = { textStyles: [], effectStyles: [] };
69
+
70
+ for (var i = 0; i < textStyles.length; i++) {
71
+ var s = textStyles[i];
72
+ result.textStyles.push({
73
+ name: s.name,
74
+ key: s.key, // ← 40-char hex hash for importStyleByKeyAsync
75
+ nodeId: s.id,
76
+ fontSize: s.fontSize,
77
+ lineHeight: s.lineHeight,
78
+ fontFamily: s.fontName.family,
79
+ fontStyle: s.fontName.style
80
+ });
81
+ }
82
+
83
+ for (var i = 0; i < effectStyles.length; i++) {
84
+ var s = effectStyles[i];
85
+ result.effectStyles.push({
86
+ name: s.name,
87
+ key: s.key,
88
+ nodeId: s.id
89
+ });
90
+ }
91
+
92
+ return result;
93
+ })();
94
+ ```
95
+
96
+ ---
97
+
98
+ ## Usage in Scripts
99
+
100
+ ```js
101
+ // Import text style by key (from registries/text-styles.json)
102
+ var headingStyle = await figma.importStyleByKeyAsync("930df26f44235299875c572ef52bd60cbf5ae956");
103
+ await textNode.setTextStyleIdAsync(headingStyle.id); // MUST use async version (Rule 20)
104
+ ```
105
+
106
+ ---
107
+
108
+ ## Grouping
109
+
110
+ Group text styles by their first path segment:
111
+ - `display` — Large hero/display text
112
+ - `heading` — Section and page headings
113
+ - `body` — Body copy, descriptions
114
+ - `caption` — Small labels, metadata
115
+ - `code` — Monospace / code text
116
+
117
+ Weight/emphasis variants are the second segment: `regular`, `accent`, `emphasis`, `bold`.
@@ -0,0 +1,182 @@
1
+ # Registry Validation Procedure
2
+
3
+ > **Run this AFTER writing all registries and BEFORE generating guides.**
4
+ > This step is BLOCKING — do not proceed until all validations pass.
5
+
6
+ ---
7
+
8
+ ## Phase 1 — Structural Check
9
+
10
+ Read each registry file and verify:
11
+
12
+ ### components.json
13
+ - [ ] Every entry has a `key` field (hex hash, NOT a node ID with `:` separator)
14
+ - [ ] Every entry has a `type` field (`"COMPONENT"` or `"COMPONENT_SET"`)
15
+ - [ ] Every entry has a `properties` object
16
+ - [ ] `key` values look like hex hashes (long alphanumeric strings), NOT like `"1008:174"`
17
+
18
+ ### variables.json
19
+ - [ ] Every variable entry has a `key` field
20
+ - [ ] Every variable entry has a `name` field (human-readable path)
21
+ - [ ] Every variable entry has `valuesByMode`
22
+ - [ ] `key` values are NOT the same as `name` values (names look like `color/background/...`, keys look like `VariableID:...` or hex hashes)
23
+
24
+ ### text-styles.json
25
+ - [ ] Every text style entry has a `key` field (40-char hex hash)
26
+ - [ ] Every entry has a `name` field
27
+
28
+ ### icons.json / logos.json / illustrations.json (if present)
29
+ - [ ] Every entry in `items` array has a `key` field
30
+ - [ ] Every entry has a `type` field
31
+
32
+ **If ANY check fails:** Re-extract the failing registry using the extraction scripts in `schemas/components.md`, `schemas/variables.md`, etc.
33
+
34
+ ---
35
+
36
+ ## Phase 2 — Import Test
37
+
38
+ Run a validation script via `figma_execute` that test-imports a sample of keys from each registry.
39
+
40
+ ### Validation Script Template
41
+
42
+ ```js
43
+ return (async function() {
44
+ var results = [];
45
+
46
+ // ── TEST COMPONENT KEYS ──
47
+ // Pick 3-5 component keys from registries/components.json
48
+ var componentTests = [
49
+ { name: "COMPONENT_NAME_1", key: "KEY_1", type: "COMPONENT_SET" },
50
+ { name: "COMPONENT_NAME_2", key: "KEY_2", type: "COMPONENT" },
51
+ { name: "COMPONENT_NAME_3", key: "KEY_3", type: "COMPONENT_SET" }
52
+ ];
53
+
54
+ for (var i = 0; i < componentTests.length; i++) {
55
+ var t = componentTests[i];
56
+ try {
57
+ if (t.type === "COMPONENT_SET") {
58
+ await figma.importComponentSetByKeyAsync(t.key);
59
+ } else {
60
+ await figma.importComponentByKeyAsync(t.key);
61
+ }
62
+ results.push({ type: "component", name: t.name, status: "OK" });
63
+ } catch(e) {
64
+ results.push({ type: "component", name: t.name, status: "FAIL", error: e.message });
65
+ }
66
+ }
67
+
68
+ // ── TEST VARIABLE KEYS ──
69
+ // Pick 3-5 variable keys from registries/variables.json (mix of color + spacing)
70
+ var variableTests = [
71
+ { name: "VARIABLE_NAME_1", key: "KEY_1" },
72
+ { name: "VARIABLE_NAME_2", key: "KEY_2" },
73
+ { name: "VARIABLE_NAME_3", key: "KEY_3" }
74
+ ];
75
+
76
+ for (var i = 0; i < variableTests.length; i++) {
77
+ var t = variableTests[i];
78
+ try {
79
+ await figma.variables.importVariableByKeyAsync(t.key);
80
+ results.push({ type: "variable", name: t.name, status: "OK" });
81
+ } catch(e) {
82
+ results.push({ type: "variable", name: t.name, status: "FAIL", error: e.message });
83
+ }
84
+ }
85
+
86
+ // ── TEST TEXT STYLE KEYS ──
87
+ // Pick 2-3 text style keys from registries/text-styles.json
88
+ var styleTests = [
89
+ { name: "STYLE_NAME_1", key: "KEY_1" },
90
+ { name: "STYLE_NAME_2", key: "KEY_2" }
91
+ ];
92
+
93
+ for (var i = 0; i < styleTests.length; i++) {
94
+ var t = styleTests[i];
95
+ try {
96
+ await figma.importStyleByKeyAsync(t.key);
97
+ results.push({ type: "textStyle", name: t.name, status: "OK" });
98
+ } catch(e) {
99
+ results.push({ type: "textStyle", name: t.name, status: "FAIL", error: e.message });
100
+ }
101
+ }
102
+
103
+ // ── SUMMARY ──
104
+ var passed = results.filter(function(r) { return r.status === "OK"; }).length;
105
+ var failed = results.filter(function(r) { return r.status === "FAIL"; }).length;
106
+
107
+ return {
108
+ total: results.length,
109
+ passed: passed,
110
+ failed: failed,
111
+ results: results
112
+ };
113
+ })();
114
+ ```
115
+
116
+ ### How to Use
117
+
118
+ 1. Read the registries you just wrote
119
+ 2. Pick sample keys from each (prioritize commonly-used components like Button, Input, Tag + core variables like spacing/medium, background colors + main text styles)
120
+ 3. Replace the placeholder names and keys in the script above
121
+ 4. Run via `figma_execute`
122
+ 5. Check results
123
+
124
+ ---
125
+
126
+ ## Phase 3 — Remediation
127
+
128
+ If any keys fail validation:
129
+
130
+ ### Component key failed
131
+ → The key is likely a node ID instead of a component key. Re-extract using:
132
+ ```js
133
+ return (async function() {
134
+ var node = await figma.getNodeByIdAsync("NODE_ID_FROM_REGISTRY");
135
+ if (node) {
136
+ return { name: node.name, correctKey: node.key, incorrectId: node.id };
137
+ }
138
+ return { error: "Node not found" };
139
+ })();
140
+ ```
141
+ Update the registry entry with the `correctKey`.
142
+
143
+ ### Variable key failed
144
+ → The key is likely the variable name instead of the variable key. Re-extract using:
145
+ ```js
146
+ return (async function() {
147
+ var collections = await figma.variables.getLocalVariableCollectionsAsync();
148
+ for (var c = 0; c < collections.length; c++) {
149
+ for (var v = 0; v < collections[c].variableIds.length; v++) {
150
+ var variable = await figma.variables.getVariableByIdAsync(collections[c].variableIds[v]);
151
+ if (variable && variable.name === "VARIABLE_NAME") {
152
+ return { name: variable.name, correctKey: variable.key };
153
+ }
154
+ }
155
+ }
156
+ return { error: "Variable not found" };
157
+ })();
158
+ ```
159
+
160
+ ### Text style key failed
161
+ → Likely a wrong key format. Re-extract using the script in `schemas/text-styles.md`.
162
+
163
+ ---
164
+
165
+ ## Validation Output
166
+
167
+ After validation passes, log the summary:
168
+
169
+ ```
170
+ Registry validation passed:
171
+ - Components: {N}/{N} keys verified ✓
172
+ - Variables: {N}/{N} keys verified ✓
173
+ - Text styles: {N}/{N} keys verified ✓
174
+ - Assets: {N}/{N} keys verified ✓ (or "skipped — no asset registries")
175
+
176
+ All import tests passed. Proceeding to guide generation.
177
+ ```
178
+
179
+ If validation fails after remediation attempts, ask the user to verify:
180
+ 1. The DS library is published (not just local)
181
+ 2. The library is enabled in the target Figma file
182
+ 3. The correct Figma file was used for extraction
@@ -0,0 +1,153 @@
1
+ # Schema: variables.json
2
+
3
+ > **Read this BEFORE writing variables.json during setup.**
4
+
5
+ ---
6
+
7
+ ## Required Structure
8
+
9
+ ```json
10
+ {
11
+ "meta": {
12
+ "source": "Library file name",
13
+ "fileKey": "FigmaFileKey",
14
+ "extractedAt": "YYYY-MM-DD",
15
+ "totalVariables": 856
16
+ },
17
+ "collections": {
18
+ "color": {
19
+ "id": "VariableCollectionId:19:113",
20
+ "modes": ["dark", "light"],
21
+ "variables": [
22
+ { "name": "color/background/neutral/boldest", "key": "VariableID:19:114", "valuesByMode": { "dark": "#FFFFFF", "light": "#000000" } },
23
+ { "name": "color/background/neutral/bolder", "key": "VariableID:19:115", "valuesByMode": { "dark": "#E5E5E5", "light": "#1A1A1A" } }
24
+ ]
25
+ },
26
+ "layout": {
27
+ "id": "VariableCollectionId:55:113",
28
+ "modes": ["value"],
29
+ "variables": [
30
+ { "name": "layout/spacing/none", "key": "VariableID:55:114", "valuesByMode": { "value": 0 } },
31
+ { "name": "layout/spacing/xsmall", "key": "VariableID:55:115", "valuesByMode": { "value": 8 } },
32
+ { "name": "layout/spacing/medium", "key": "VariableID:55:118", "valuesByMode": { "value": 16 } },
33
+ { "name": "layout/spacing/large", "key": "VariableID:55:119", "valuesByMode": { "value": 24 } },
34
+ { "name": "layout/radius/medium", "key": "VariableID:55:130", "valuesByMode": { "value": 12 } }
35
+ ]
36
+ }
37
+ }
38
+ }
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Field Requirements
44
+
45
+ ### Per variable entry
46
+
47
+ | Field | Required | Description |
48
+ |-------|----------|-------------|
49
+ | `name` | **YES** | Variable path like `"color/background/neutral/boldest"` |
50
+ | `key` | **YES** | Variable key for `importVariableByKeyAsync`. Format varies (e.g., `"VariableID:55:114"` or a hash). |
51
+ | `valuesByMode` | **YES** | Object mapping mode names to resolved values |
52
+
53
+ ### Per collection
54
+
55
+ | Field | Required | Description |
56
+ |-------|----------|-------------|
57
+ | `id` | optional | Collection ID |
58
+ | `modes` | **YES** | Array of mode names |
59
+ | `variables` | **YES** | Array of variable entries |
60
+
61
+ ---
62
+
63
+ ## CRITICAL: Variable `name` vs `key`
64
+
65
+ These are TWO DIFFERENT things:
66
+
67
+ | | `name` (path) | `key` (variable key) |
68
+ |---|---|---|
69
+ | **Looks like** | `"color/background/neutral/boldest"` | `"VariableID:55:114"` or hex hash |
70
+ | **Used for** | Human reading, organization | `importVariableByKeyAsync()` |
71
+ | **Available from** | `variable.name` | `variable.key` |
72
+
73
+ **`importVariableByKeyAsync` REQUIRES the `key`, NOT the `name`.** The name is useful for documentation but cannot be used for script imports.
74
+
75
+ ---
76
+
77
+ ## How to Extract Keys
78
+
79
+ ### Via figma_execute (RECOMMENDED)
80
+
81
+ ```js
82
+ return (async function() {
83
+ var collections = await figma.variables.getLocalVariableCollectionsAsync();
84
+ var result = {};
85
+
86
+ for (var c = 0; c < collections.length; c++) {
87
+ var col = collections[c];
88
+ var collectionData = {
89
+ id: col.id,
90
+ modes: col.modes.map(function(m) { return m.name; }),
91
+ variables: []
92
+ };
93
+
94
+ for (var v = 0; v < col.variableIds.length; v++) {
95
+ var variable = await figma.variables.getVariableByIdAsync(col.variableIds[v]);
96
+ if (!variable) continue;
97
+
98
+ var valuesByMode = {};
99
+ for (var m = 0; m < col.modes.length; m++) {
100
+ var mode = col.modes[m];
101
+ var val = variable.valuesByMode[mode.modeId];
102
+ // Resolve color values to hex
103
+ if (val && typeof val === "object" && "r" in val) {
104
+ var r = Math.round(val.r * 255).toString(16).padStart(2, "0");
105
+ var g = Math.round(val.g * 255).toString(16).padStart(2, "0");
106
+ var b = Math.round(val.b * 255).toString(16).padStart(2, "0");
107
+ valuesByMode[mode.name] = "#" + r + g + b;
108
+ } else if (val && typeof val === "object" && "type" in val) {
109
+ // Alias — store the referenced variable name
110
+ valuesByMode[mode.name] = "→ alias";
111
+ } else {
112
+ valuesByMode[mode.name] = val;
113
+ }
114
+ }
115
+
116
+ collectionData.variables.push({
117
+ name: variable.name,
118
+ key: variable.key, // ← THIS is what importVariableByKeyAsync needs
119
+ valuesByMode: valuesByMode
120
+ });
121
+ }
122
+
123
+ // Use first segment of variable names as collection key (e.g., "color", "layout")
124
+ var collName = col.name.toLowerCase().replace(/\s+/g, "-");
125
+ result[collName] = collectionData;
126
+ }
127
+
128
+ return { collections: result };
129
+ })();
130
+ ```
131
+
132
+ ### Important Notes
133
+
134
+ - For large collections (400+ variables), you may need to extract in batches per collection
135
+ - The `key` format varies: it might be `"VariableID:55:114"` or a different format depending on the Figma version
136
+ - Always verify keys work by running the validation script (see `schemas/validation.md`)
137
+
138
+ ---
139
+
140
+ ## Usage in Scripts
141
+
142
+ ```js
143
+ // Load variable by key (from registries/variables.json)
144
+ var spMedium = await figma.variables.importVariableByKeyAsync("VariableID:55:118");
145
+
146
+ // Bind to frame padding
147
+ frame.setBoundVariable('paddingTop', spMedium);
148
+ frame.setBoundVariable('itemSpacing', spMedium);
149
+
150
+ // Bind to color fill
151
+ var bgColor = await figma.variables.importVariableByKeyAsync("VariableID:19:114");
152
+ frame.fills = mf(bgColor); // using the mf() helper
153
+ ```
@@ -68,9 +68,22 @@ What do you want to design? (component or screen)
68
68
  ```
69
69
 
70
70
  3. **Write registries** (raw JSON, deterministic):
71
- - `registries/components.json` — component names, keys, variants, properties
72
- - `registries/variables.json` variable names, keys, types, values by mode
73
- - `registries/text-styles.json` text style names, keys, font specs
71
+
72
+ **CRITICAL: Read the schema for each registry BEFORE writing it:**
73
+ - `schemas/components.md` → `registries/components.json` (component names, **keys**, variants, properties)
74
+ - `schemas/variables.md` → `registries/variables.json` (variable names, **keys**, types, values by mode)
75
+ - `schemas/text-styles.md` → `registries/text-styles.json` (text style names, **keys**, font specs)
76
+ - `schemas/assets.md` → `registries/icons.json`, `registries/logos.json`, `registries/illustrations.json` (if applicable)
77
+
78
+ All paths relative to `knowledge-base/`. The schemas contain extraction scripts and required field definitions. The `key` field is MANDATORY for every component, variable, and style entry — without it, `design` generation will fail.
79
+
80
+ 3b. **Validate registries (BLOCKING):**
81
+
82
+ Read `schemas/validation.md` and execute the full validation procedure:
83
+ 1. **Structural check** — verify all registries match their schemas (every entry has `key`, correct types)
84
+ 2. **Import test** — run validation script via `figma_execute` to test-import 3-5 sample keys per registry
85
+ 3. **Remediation** — if ANY validation fails, re-extract the failing items using the remediation scripts
86
+ 4. **Gate** — ALL validation checks MUST pass before proceeding to guide generation
74
87
 
75
88
  4. **Generate intelligent guides** (Claude analyzes registries and writes):
76
89
 
@@ -114,10 +127,11 @@ What do you want to design? (component or screen)
114
127
 
115
128
  7. **Validation summary:**
116
129
  ```
117
- Knowledge base built:
118
- - {N} components documented ({N} with variants)
119
- - {N} variables ({N} colors, {N} spacing, {N} radius)
120
- - {N} text styles
130
+ Knowledge base built and validated:
131
+ - {N} components documented ({N} with variants) — {N} keys verified via import test ✓
132
+ - {N} variables ({N} colors, {N} spacing, {N} radius) — {N} keys verified ✓
133
+ - {N} text styles — {N} keys verified ✓
134
+ - {N} asset items (icons/logos/illustrations) — {N} keys verified ✓
121
135
  - {N} layout patterns extracted from {N} screenshots
122
136
 
123
137
  Ready to design. Run: `spec {name}`
@@ -10,6 +10,10 @@
10
10
  | Connected to Figma Desktop | Yes (before design) | Status has `setup.valid: true` |
11
11
  | DS libraries enabled | Yes (before design) | User confirmation |
12
12
  | Knowledge base exists | Yes (before spec) | `registries/` has JSON files |
13
+ | Registry schemas followed | Yes | All registry files match schemas in `schemas/` — every entry has `key` field |
14
+ | Registry keys validated | Yes | Sample import test passed via `figma_execute` (3-5 keys per registry) |
15
+ | No node IDs as keys | Yes | Component entries have `key` (hex hash), NOT just `id` (node ID like `1008:174`) |
16
+ | Variable keys present | Yes | Variables have `key` field, not just `name` paths |
13
17
 
14
18
  ---
15
19