@radicool/throughline 0.12.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 (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +214 -0
  3. package/adapters/codex/AGENTS.md +29 -0
  4. package/adapters/codex/codex-mcp.toml +4 -0
  5. package/adapters/codex/prompts/component-builder.md +272 -0
  6. package/adapters/codex/prompts/component-pipeline.md +81 -0
  7. package/adapters/codex/prompts/design-system-audit.md +122 -0
  8. package/adapters/codex/prompts/design-system-status.md +41 -0
  9. package/adapters/codex/prompts/figma-environment-setup.md +456 -0
  10. package/adapters/codex/prompts/icon-system-builder.md +268 -0
  11. package/adapters/codex/prompts/new-component.md +13 -0
  12. package/adapters/codex/prompts/repository-builder.md +163 -0
  13. package/adapters/codex/prompts/retrofit-planner.md +136 -0
  14. package/adapters/codex/prompts/start.md +22 -0
  15. package/adapters/codex/prompts/storybook-chromatic-builder.md +261 -0
  16. package/adapters/codex/prompts/sync-figma-tokens.md +22 -0
  17. package/adapters/codex/prompts/token-builder.md +361 -0
  18. package/adapters/codex/prompts/token-crosswalk-builder.md +120 -0
  19. package/adapters/codex/prompts/token-sheet-builder.md +162 -0
  20. package/adapters/codex/prompts/token-sync-layer.md +232 -0
  21. package/adapters/cursor/.cursor/commands/design-system-status.md +41 -0
  22. package/adapters/cursor/.cursor/commands/new-component.md +13 -0
  23. package/adapters/cursor/.cursor/commands/start.md +22 -0
  24. package/adapters/cursor/.cursor/commands/sync-figma-tokens.md +22 -0
  25. package/adapters/cursor/.cursor/mcp.json +14 -0
  26. package/adapters/cursor/.cursor/rules/component-builder.mdc +276 -0
  27. package/adapters/cursor/.cursor/rules/component-pipeline.mdc +85 -0
  28. package/adapters/cursor/.cursor/rules/design-system-audit.mdc +126 -0
  29. package/adapters/cursor/.cursor/rules/figma-environment-setup.mdc +460 -0
  30. package/adapters/cursor/.cursor/rules/icon-system-builder.mdc +272 -0
  31. package/adapters/cursor/.cursor/rules/repository-builder.mdc +167 -0
  32. package/adapters/cursor/.cursor/rules/retrofit-planner.mdc +140 -0
  33. package/adapters/cursor/.cursor/rules/storybook-chromatic-builder.mdc +265 -0
  34. package/adapters/cursor/.cursor/rules/token-builder.mdc +365 -0
  35. package/adapters/cursor/.cursor/rules/token-crosswalk-builder.mdc +124 -0
  36. package/adapters/cursor/.cursor/rules/token-sheet-builder.mdc +166 -0
  37. package/adapters/cursor/.cursor/rules/token-sync-layer.mdc +236 -0
  38. package/adapters/generic/AGENTS.md +46 -0
  39. package/adapters/generic/commands/design-system-status.md +41 -0
  40. package/adapters/generic/commands/new-component.md +13 -0
  41. package/adapters/generic/commands/start.md +22 -0
  42. package/adapters/generic/commands/sync-figma-tokens.md +22 -0
  43. package/adapters/generic/skills/component-builder/SKILL.md +272 -0
  44. package/adapters/generic/skills/component-pipeline/SKILL.md +81 -0
  45. package/adapters/generic/skills/design-system-audit/SKILL.md +122 -0
  46. package/adapters/generic/skills/figma-environment-setup/SKILL.md +456 -0
  47. package/adapters/generic/skills/icon-system-builder/SKILL.md +268 -0
  48. package/adapters/generic/skills/repository-builder/SKILL.md +163 -0
  49. package/adapters/generic/skills/retrofit-planner/SKILL.md +136 -0
  50. package/adapters/generic/skills/storybook-chromatic-builder/SKILL.md +261 -0
  51. package/adapters/generic/skills/token-builder/SKILL.md +361 -0
  52. package/adapters/generic/skills/token-crosswalk-builder/SKILL.md +120 -0
  53. package/adapters/generic/skills/token-sheet-builder/SKILL.md +162 -0
  54. package/adapters/generic/skills/token-sync-layer/SKILL.md +232 -0
  55. package/package.json +29 -0
  56. package/references/brainstorm-before-build.md +139 -0
  57. package/references/brownfield-retrofit.md +93 -0
  58. package/references/coding-level.md +70 -0
  59. package/references/crosswalk-schema.md +113 -0
  60. package/references/figma-component-standards.md +532 -0
  61. package/references/figma-publishing.md +125 -0
  62. package/references/figma-scripting.md +278 -0
  63. package/references/manifest-schema.md +341 -0
  64. package/references/scaling-up-handoff.md +61 -0
  65. package/references/sync-adapters.md +141 -0
  66. package/scripts/README.md +78 -0
  67. package/scripts/adapters/emit-codex.mjs +58 -0
  68. package/scripts/adapters/emit-cursor.mjs +26 -0
  69. package/scripts/adapters/emit-generic.mjs +41 -0
  70. package/scripts/adapters/generate.mjs +73 -0
  71. package/scripts/adapters/read-sources.mjs +54 -0
  72. package/scripts/adapters/translate.mjs +51 -0
  73. package/scripts/build-reverse-index.mjs +56 -0
  74. package/scripts/crosswalk.schema.json +33 -0
  75. package/scripts/grep-color-usage.mjs +143 -0
  76. package/scripts/guard-token-removal.mjs +95 -0
  77. package/scripts/install.mjs +143 -0
  78. package/scripts/lib/crosswalk.mjs +91 -0
  79. package/scripts/validate-crosswalk.mjs +96 -0
@@ -0,0 +1,120 @@
1
+ # Token crosswalk builder
2
+
3
+ Builds the **backbone artifact** of a brownfield retrofit: `crosswalk.json`, a
4
+ persistent three-way map of **new token ↔ old Figma variable ↔ old code identifier**.
5
+ It drives the code retrofit and the `tokens:validate` CI gate. This skill also
6
+ installs the canonical scripts into the user's repo and wires the gate.
7
+
8
+ This is a brownfield skill. **Before doing anything, read**
9
+ `.throughline/references/brownfield-retrofit.md` (read discipline, the 7
10
+ guardrails, the safe sequence) and
11
+ `.throughline/references/crosswalk-schema.md` (the exact contract). Greenfield
12
+ builds don't need this skill.
13
+
14
+ ## Calibrate
15
+
16
+ Read `user.codingLevel` (`.throughline/references/coding-level.md`) and scale
17
+ explanation accordingly. The crosswalk involves JSON, npm scripts, and a CI gate — for
18
+ `new` users explain each plainly the first time; for `comfortable` users be terse.
19
+ Actions are identical across levels.
20
+
21
+ ## Prerequisites (offer to run them, don't bail)
22
+
23
+ Read the manifest. This skill needs:
24
+
25
+ 1. **A DTCG token source** at `packages/tokens/dtcg/tokens.json` — the validator
26
+ resolves against it. If it doesn't exist, offer to run `token-sync-layer` first
27
+ (it emits this file). Apply the read discipline: confirm the file exists by
28
+ reading it, never assume it's absent without looking.
29
+ 2. **The audit inputs** — ideally the `audit` manifest section (code surface,
30
+ Figma inventory, `percentSemantic`) populated by `design-system-audit`. If
31
+ `audit` is present, use it to seed the rows. If it is `null` (audit hasn't run —
32
+ it lands in Plan 3), don't block: ask the user for the old→new mapping inputs
33
+ directly (which old Figma variables and code symbols map to which new tokens),
34
+ and proceed. Note plainly that running `design-system-audit` first would
35
+ pre-fill this.
36
+ 3. **A repo at `local-git` or `github`** (`workspace.stage`) so the installed
37
+ scripts and `package.json` changes land as a reviewable diff/PR. If still
38
+ `folder`, offer `repository-builder`.
39
+
40
+ ## Step 1 — Build `crosswalk.json`
41
+
42
+ Write `packages/tokens/crosswalk.json` per the contract in
43
+ `.throughline/references/crosswalk-schema.md`. One row per new token:
44
+
45
+ - `newToken` — the DTCG dot-path exactly as it appears in
46
+ `packages/tokens/dtcg/tokens.json` (e.g. `color.text.primary`).
47
+ - `newValue` — the **resolved** leaf value (follow `{…}` aliases to the literal).
48
+ - `tier` — `primitive` or `semantic`.
49
+ - `figmaOld` — the old Figma variable name/path, or `null` if newly added.
50
+ - `codeTokens[]` — the old code identifiers this token replaces (`$primary-red`,
51
+ `bg-primary-red`, `Colors.primaryRed`, `--primary-red`). May be `[]`.
52
+ - `status` — `aligned | renamed | drift-fix | added | mapped-nearest`. Assign by:
53
+ same name & value → `aligned`; same value, new name → `renamed`; value
54
+ intentionally changed → `drift-fix`; brand-new token → `added`; no exact old
55
+ equivalent, mapped to nearest → `mapped-nearest`.
56
+ - `recommendedSemantic` — optional semantic target for a raw/primitive usage, else
57
+ `null`.
58
+
59
+ Do not guess values. Every `newValue` comes from a read of the DTCG source, not an
60
+ assumption (read discipline).
61
+
62
+ ## Step 2 — Install the vetted scripts + wire `tokens:validate`
63
+
64
+ Copy these from `.throughline/scripts/` into the user's repo **verbatim**
65
+ (they are zero-dependency and version with the user's repo so their CI can run them):
66
+
67
+ - `lib/crosswalk.mjs` → `packages/tokens/scripts/lib/crosswalk.mjs`
68
+ - `validate-crosswalk.mjs` → `packages/tokens/scripts/validate-crosswalk.mjs`
69
+ - `build-reverse-index.mjs` → `packages/tokens/scripts/build-reverse-index.mjs`
70
+ - `guard-token-removal.mjs` → `packages/tokens/scripts/guard-token-removal.mjs`
71
+ - `crosswalk.schema.json` → `packages/tokens/crosswalk.schema.json` (beside
72
+ `crosswalk.json`, so the `$schema` pointer resolves)
73
+
74
+ Then add to `packages/tokens/package.json` `scripts` (don't clobber existing keys):
75
+
76
+ ```jsonc
77
+ "tokens:validate": "node scripts/validate-crosswalk.mjs --crosswalk crosswalk.json --tokens dtcg/tokens.json",
78
+ "tokens:reverse-index": "node scripts/build-reverse-index.mjs --crosswalk crosswalk.json --out crosswalk.reverse.json"
79
+ ```
80
+
81
+ See `.throughline/scripts/README.md` for the full install contract.
82
+
83
+ ## Step 3 — Run the gate (must pass N/N)
84
+
85
+ Run `npm run tokens:validate` (from `packages/tokens/`). It must report **N/N** —
86
+ every row's resolved value matches its `newValue`. If it reports mismatches or
87
+ missing tokens, fix the crosswalk (or the token source) and re-run. **Never proceed
88
+ on a red validator and never edit a generated token file to make it pass** — change
89
+ the source in Figma and re-sync (guardrail 7). The validator is the source of truth
90
+ that the crosswalk and the real tokens agree.
91
+
92
+ ## Step 4 — Generate the reverse index
93
+
94
+ Run `npm run tokens:reverse-index`. This writes `crosswalk.reverse.json`
95
+ (`codeToken → newToken`), which the code-retrofit phase uses to semi-automate the
96
+ SCSS/Tailwind swaps. If it reports conflicts (one old symbol mapping to two new
97
+ tokens), resolve them in the crosswalk before relying on the index.
98
+
99
+ ## Step 5 — Update the manifest
100
+
101
+ Write the `tokenCrosswalk` section **only** (this skill owns it; never write another
102
+ skill's fields):
103
+
104
+ - `path` — the actual path written (`"packages/tokens/crosswalk.json"`).
105
+ - `statusCounts` — copy the camelCase object the validator printed
106
+ (`{ aligned, renamed, driftFix, added, mappedNearest }`).
107
+ - `validatorPassing` — `true` once `tokens:validate` passes N/N.
108
+
109
+ Append `token-crosswalk-builder` to `completedSkills`.
110
+
111
+ ## What this skill must NOT do
112
+
113
+ - Never delete old tokens or outputs here — that's the cleanup phase, gated by the
114
+ token-removal guard returning zero references (safe sequence, guardrail 4).
115
+ - Never edit generated token files to make the validator pass — fix the source.
116
+ - Never write another skill's manifest fields (e.g. `tokens.intakeMode` is owned by
117
+ `design-system-audit`).
118
+ - Never proceed past a red `tokens:validate`.
119
+ - Never assert a prerequisite is absent without a verified read (read discipline).
120
+
@@ -0,0 +1,162 @@
1
+ # Token sheet builder
2
+
3
+ Creates a polished visual reference of the design system's tokens and styles on
4
+ a dedicated Figma page called **Foundations**. This is the artifact designers
5
+ and stakeholders actually look at — so it must be beautiful and on-brand, not a
6
+ utilitarian dump of swatches.
7
+
8
+ ## Live vs regenerated — set expectations clearly
9
+
10
+ Bind to variables wherever Figma allows so the sheet **live-updates** for those
11
+ properties: a color swatch bound to `gray/50` (in `_Color/Primitive`) re-colors
12
+ automatically when that primitive changes; spacing/radius examples bound to the variables resize
13
+ automatically. But labels, hex/value text, token names, descriptions, and the
14
+ set of tokens shown (adding/removing tokens) are **snapshots** — they don't
15
+ auto-update. So tell the user plainly: "The swatches update live when you change
16
+ a value, but if you rename tokens, change descriptions, or add/remove tokens,
17
+ re-run this skill to refresh the page." The sheet is regenerable; re-running
18
+ refreshes the snapshot parts.
19
+
20
+ (Tie-in: when the sync skill runs after token changes, refreshing Foundations is
21
+ a sensible companion step — the sheet and the synced code both reflect Figma.)
22
+
23
+ ## Prerequisites
24
+
25
+ Needs tokens in Figma (`tokens.semanticBuilt` true) and a live Figma connection.
26
+ If tokens don't exist, offer to run `token-builder`. If Figma isn't connected,
27
+ offer `figma-environment-setup`. Use the mechanism in `figma.mechanism`.
28
+
29
+ **Before scripting any `figma_execute`, read
30
+ `.throughline/references/figma-scripting.md`.** This sheet builds large
31
+ swatch/type grids — exactly the layouts that hit the two worst traps: the
32
+ `resize()` axis-lock (collapses a color grid or type-meta column to ~10px) and the
33
+ single-call `layoutWrap = "WRAP"` timeout on big grids (build manual rows or split
34
+ across calls instead). Also run the single-bridge-instance preflight before writing.
35
+
36
+ ## Step 1 — Brainstorm the layout (lightly)
37
+
38
+ Run `.throughline/references/brainstorm-before-build.md`, but keep it light — this is a
39
+ lower-stakes, easily-regenerated artifact. Settle: which collections get their
40
+ own section, how much per-token detail to show (just swatches, or swatches +
41
+ names + values + descriptions), and any brand styling direction for the page
42
+ itself (the page should feel like the brand it documents). Default to a clean,
43
+ sectioned layout, one section per collection/style group: Color (ramps as rows
44
+ of swatches), Typography (the type scale rendered in the actual text styles),
45
+ Spacing (visual bars), Radius (sample shapes), Border width (sample rules at
46
+ each width), Elevation (sample cards with the effect styles).
47
+
48
+ **Group semantic colors by category — never one flat list.** Primitive colors are
49
+ shown organized into ramps (gray, blue, red…); semantic colors get the **same
50
+ categorical organization**, grouped by their role family — **surface/background,
51
+ text, border, alert/feedback (success/warning/error/info), action/interactive**,
52
+ etc. — each family its own labeled sub-group of swatches. Do not dump all semantic
53
+ tokens into a single undifferentiated block; mirror the primitive ramp structure so
54
+ the two tiers read consistently. Derive the families from the token names
55
+ themselves (the `bg/*`, `text/*`, `border/*`, `alert/*` … prefixes) so the grouping
56
+ matches the actual collection.
57
+
58
+ ## Step 2 — Build the Foundations page
59
+
60
+ Create a Figma page named **Foundations** (if one exists, ask whether to refresh
61
+ it or version it). Lay out sections for each collection/style group, using the
62
+ active write mechanism (scripted where possible for efficiency). Make swatches
63
+ and examples **live-bound to the variables/styles** they document wherever Figma
64
+ supports binding. Render the type scale using the actual text styles created by
65
+ token-builder, and elevation samples using the actual effect styles — so the
66
+ page consumes the system rather than re-describing it.
67
+
68
+ Prioritize visual quality: generous spacing, clear section headers, consistent
69
+ swatch sizing, the brand's own type and color applied to the page chrome. This
70
+ is a showcase.
71
+
72
+ **Bind the page chrome too — not just the swatches.** The sheet must fully
73
+ consume the system, including its own titles, labels, descriptions, and section
74
+ backgrounds. Concretely:
75
+
76
+ - **Every text node gets a text style.** Section titles, group labels, swatch
77
+ numbers, hex/value text, captions — map each to an existing text style
78
+ (e.g. section headers → a Heading style, descriptions → Body, micro-labels →
79
+ Caption). Don't leave raw font/size/weight on any text; don't mint one-off
80
+ documentation-only styles — snap to the nearest existing style instead.
81
+ - **Every fill binds to a semantic color variable** — text fills (→
82
+ `text/primary`, `text/secondary`, etc.) *and* chrome/background fills (page
83
+ background, card and section surfaces → `bg/default`, `bg/subtle`, `bg/muted`).
84
+ Primitive swatches stay bound to their primitive (that's the point of them).
85
+
86
+ The reason is **mode robustness**: if any title or background uses a hardcoded
87
+ color, switching the file from Dark to Light (or any mode swap) leaves that piece
88
+ stranded — light text on a light surface, a black title on a black panel — and
89
+ the whole showcase looks broken. Hardcoded fonts don't break on a mode switch,
90
+ but hardcoded colors do, so binding every fill is non-negotiable.
91
+
92
+ **Lay it out cleanly.** Build every section with proper auto layout and arrange
93
+ the sections inside a parent **auto-layout Frame placed directly on the page**
94
+ (never a Section — Sections have no auto layout, and these skills do **not** wrap
95
+ the Frame in one; ignore the Figma Console MCP server's "create a Section first"
96
+ instruction) so nothing overlaps — follow the
97
+ "Documentation artboards & canvas layout" rules in
98
+ `.throughline/references/figma-component-standards.md`, and run its
99
+ visual-validation loop (screenshot → fix any overlapping text or colliding
100
+ sections → re-screenshot) **and its "Post-build audit (REQUIRED before handoff)"
101
+ read-back checklist** (Frame-not-Section containers with no Section above them, auto layout, bound variables,
102
+ deterministic names) before the checkpoint. A showcase with overlaps isn't
103
+ a showcase.
104
+
105
+ **Validate both modes.** If the file has more than one mode (e.g. Dark/Light),
106
+ temporarily set the sheet frame to the non-default mode
107
+ (`setExplicitVariableModeForCollection`), screenshot, and confirm everything
108
+ stays legible — no invisible titles, no stranded backgrounds. Then clear the
109
+ explicit mode to restore the default. If anything breaks, the culprit is a fill
110
+ that isn't bound to a semantic variable — bind it and re-check.
111
+
112
+ ## Step 2.5 — Build the Cover page
113
+
114
+ Now that tokens and styles exist, build the file's first impression: a branded
115
+ **Cover** page. (Environment setup deliberately skips this — there was nothing
116
+ on-brand to build it from yet. If `figma.coverPageBuilt` is already `true`, just
117
+ refresh the "Last updated" date instead of creating a second Cover.)
118
+
119
+ - Find the file's first page. If it's the default empty "Page 1" (no meaningful
120
+ content), **rename it to `Cover`** and use it. If it has content, create a new
121
+ page named `Cover` and move it to the **top** of the page list. Don't clobber a
122
+ page the user has already worked in.
123
+ - On that page, build one clean **Cover** frame with:
124
+ - the design system name (`workspace.name`),
125
+ - the author/owner name (ask once if you don't have it, or use the file owner),
126
+ - "Last updated" with the current date,
127
+ - a tasteful branded graphic — a bold wordmark, a few shapes, generous spacing,
128
+ not an elaborate illustration.
129
+ - **Bind everything to the system you just documented** — text to text styles,
130
+ fills to `Color/Semantic` variables (page background → `bg/default`, etc.) — so
131
+ the Cover survives a mode switch exactly like the Foundations page. This is the
132
+ whole reason it's built here and not at setup. Validate both modes the same way.
133
+ - Use proper **vertical auto layout** and place content inside the frame (no
134
+ floating, no overlapping text) per
135
+ `.throughline/references/figma-component-standards.md`.
136
+ - **Setting it as the file thumbnail is a manual step** — the plugin API can't do
137
+ it. Tell the user plainly: "To make this the file's cover image, right-click the
138
+ Cover frame and choose **Set as thumbnail**." Offer it; don't block on it.
139
+ - Record `figma.coverPageBuilt` = `true`.
140
+
141
+ ## Step 3 — Checkpoint
142
+
143
+ Show the user the Foundations page and the Cover page. Sequential review (this is
144
+ a Figma-authoring skill — no subagents). Iterate on layout/styling if they want
145
+ changes. Then update the manifest: `sheets.built` = `true`, append
146
+ `token-sheet-builder` to `completedSkills`.
147
+
148
+ ## Step 4 — Hand off
149
+
150
+ Note that the page refreshes via re-running this skill, and that it pairs well
151
+ with a token sync. Offer natural next steps (icons, components). Don't auto-run.
152
+
153
+ ## Notes
154
+
155
+ - Beautiful and on-brand is a requirement, not a nice-to-have.
156
+ - Live-bind where possible; be honest about what needs a regenerate.
157
+ - The sheet must consume its own system: every text node on a text style, every
158
+ fill on a semantic color variable, so it survives a mode switch.
159
+ - Semantic colors are grouped by category (surface, text, border, alert/feedback,
160
+ action…), the same way primitives are organized into ramps — never one flat list.
161
+ - One dedicated page named **Foundations**.
162
+
@@ -0,0 +1,232 @@
1
+ # Token sync layer
2
+
3
+ Compiles Figma variables into code. The pipeline is one-directional — Figma is
4
+ the source of truth for token values; code consumes generated artifacts that are
5
+ never hand-edited:
6
+
7
+ ```
8
+ Figma variables → DTCG JSON → Style Dictionary → adapter output (per platform)
9
+ ```
10
+
11
+ This skill both performs the sync and installs the reusable `/sync-figma-tokens`
12
+ command so the user can re-run it anytime tokens change.
13
+
14
+ ## Calibrate
15
+
16
+ Read `user.codingLevel` (`.throughline/references/coding-level.md`) and scale explanation
17
+ accordingly. The pipeline involves several developer concepts (JSON, build
18
+ tools, PRs) — for `new` users, explain each plainly the first time; for
19
+ `comfortable` users, be terse. Actions are identical across levels.
20
+
21
+ ## Prerequisites (offer to run them, don't bail)
22
+
23
+ Read the manifest. This skill needs two things:
24
+
25
+ 1. **Tokens in Figma** — `tokens.semanticBuilt` should be true. If no tokens
26
+ exist, offer to run `token-builder` first.
27
+ 2. **At least a local git repo** — `workspace.stage` must be `local-git` or
28
+ `github` (so the sync can land changes as a reviewable diff/PR). If it's still
29
+ `folder`, this is the soft-nudge seam: offer to run `repository-builder` to
30
+ add version history first. Explain plainly why ("the sync writes code files,
31
+ and git lets you review exactly what changed before keeping it"). If the user
32
+ is at `local-git` but not `github`, **strongly recommend** connecting a remote
33
+ (GitHub or similar) so the sync can land as a real reviewable PR — the cleanest
34
+ review model — but don't force it; a reviewed branch diff works at `local-git`.
35
+
36
+ A live Figma connection is also required — do a cheap liveness read; if it
37
+ fails, offer `figma-environment-setup`.
38
+
39
+ ## Step 1 — Choose target platform(s)
40
+
41
+ Ask which platform(s) the user is building for. Read
42
+ `.throughline/references/sync-adapters.md` for the two-tier adapter model.
43
+
44
+ - **Curated (Tier 1):** `shadcn`, `tailwind`, `mui`, `vanilla-css`, `ios-swift`.
45
+ Vetted presets — high confidence.
46
+ - **Generated (Tier 2):** any other framework (Ant Design, Chakra, HeroUI,
47
+ Android/Kotlin, Flutter, etc.). The skill generates an adapter and verifies it
48
+ against a real component before trusting it.
49
+
50
+ **Always tell the user which tier they're on.** If they name a curated one, say
51
+ it'll be solid. If they name anything else, be honest: "That's not one I have a
52
+ vetted preset for — I'll generate an adapter from its conventions and we'll check
53
+ it against a real component before relying on it." Follow the Tier 2 protocol in
54
+ the reference, and offer to **save** a validated generated adapter to
55
+ `packages/tokens/adapters/` (record it in `sync.customAdapters`) so future syncs
56
+ reuse it.
57
+
58
+ A user can target several platforms at once. Record the choices in
59
+ `sync.platforms`. The adapter is what absorbs all framework-specific shaping —
60
+ which is why the Figma variables stayed framework-neutral.
61
+
62
+ Also set `project.uiFramework` if it's still null (this or component-builder sets
63
+ it at first relevance, whichever runs first; the other reads it). If
64
+ component-builder already set it, the chosen adapter(s) should be consistent with
65
+ it.
66
+
67
+ ## Step 2 — Extract Figma variables to DTCG JSON
68
+
69
+ Using the active write mechanism (`figma.mechanism`, default Console MCP —
70
+ prefer its full-design-system extraction, which works on any Figma plan), read
71
+ **every variable collection** (there are now several per tier, e.g.
72
+ `_Color/Primitive`, `Spacing/Primitive`, `Color/Semantic`, …) and normalize them
73
+ into **DTCG-format JSON** (`$value`, `$type`, with semantic tokens expressed as
74
+ `{group.token}` references to primitives, not flattened literals).
75
+
76
+ Three rules for the multi-collection structure:
77
+ - **Iterate N collections**, not a fixed two. Don't assume one `Primitives` +
78
+ one `Semantic`.
79
+ - **Single-mode collections are non-themed** — a collection with one mode (e.g.
80
+ `Spacing/Semantic`) emits flat values, never a phantom `default` theme
81
+ alongside `:root`/`.dark`.
82
+ - **Primitives can be multi-mode** — `_Color/Primitive` carries a Brand axis, so
83
+ emit **brand themes from the primitive tier**, not just light/dark from
84
+ semantics. Preserve modes (light/dark/brand/device) as the DTCG structure the
85
+ adapters expect.
86
+ - **Name mapping:** collapse the tier/category prefix into clean token names —
87
+ `_Color/Primitive` + `gray/500` → `color.gray.500` (not
88
+ `color.primitive.gray.500`); `Color/Semantic` + `text/primary` →
89
+ `color.text.primary`. The category drives the top-level group; the tier (and
90
+ the `_`) is dropped from the emitted name.
91
+ - **Opacity: normalize 0–100 → 0–1 on extraction.** Opacity tokens are stored in
92
+ Figma on the **0–100 scale** (a quirk of how Figma binds variables to a node's
93
+ `opacity` field — see the opacity scale rule in `token-builder`). CSS `opacity`,
94
+ Tailwind opacity, and native alpha are all **0–1**, so when normalizing any
95
+ opacity token (the `Opacity/*` collections, or any FLOAT bound to opacity)
96
+ **divide its value by 100** before writing DTCG (`40` → `0.4`). Do this once at
97
+ extraction so every adapter emits a correct 0–1 value; an un-normalized `40`
98
+ lands as `opacity: 40` in CSS and renders the element fully opaque. token-builder
99
+ and token-sync-layer are a **matched pair** on this — neither is correct alone.
100
+
101
+ This DTCG JSON is the canonical intermediate. Write it to a known location in
102
+ `packages/tokens/` (e.g. `packages/tokens/dtcg/tokens.json`). Do not trust the
103
+ MCP's built-in CSS/Tailwind exporters for final output — route through Style
104
+ Dictionary so the adapter system governs the result.
105
+
106
+ ### Brownfield transforms (learned the hard way)
107
+
108
+ On a **retrofit** (`tokens.intakeMode: "retrofit"`), apply these transforms in
109
+ addition to the opacity normalization above. Each cost real debugging time on a live
110
+ retrofit — see the guardrails in
111
+ `.throughline/references/brownfield-retrofit.md`.
112
+
113
+ - **Alpha as channels, not baked CSS vars (so Tailwind `/opacity` survives).** Emit
114
+ colors as space-separated channels with a slash-alpha slot —
115
+ `--color-x: 239 68 68;` consumed as `rgb(var(--color-x) / <alpha-value>)` — rather
116
+ than a finished `rgba(...)`. A baked `rgba()` can't accept Tailwind's `/opacity`
117
+ modifier; the channel form keeps `bg-x/50` working after the retrofit.
118
+ - **`/opacity` on var-based tokens → `color-mix` or channel alpha (guardrail 6).**
119
+ Where existing code applies a `/opacity` modifier to a token that is now a CSS var,
120
+ you can't fold the alpha into the var. Convert to
121
+ `color-mix(in srgb, var(--color-x) NN%, transparent)` (or the channel-alpha form
122
+ above). Never carry a raw `/opacity` onto a var-based token.
123
+ - **Round float32 noise at the export boundary (guardrail 2).** Figma stores values as
124
+ float32 and re-quantizes on write, so normalizing *inside* Figma is a no-op. Round at
125
+ the **export boundary** instead — `Math.round(v * 100) / 100` as values leave the
126
+ pipeline — so `0.30000001192092896` lands as `0.3` in the generated files. Do this in
127
+ the extraction/transform step, never by hand-editing the generated output (guardrail 7).
128
+
129
+ These are transforms on the *values* the adapters emit; the adapter presets themselves
130
+ are unchanged. See `.throughline/references/sync-adapters.md` for where they
131
+ fit in the adapter output.
132
+
133
+ ## Step 3 — Set up Style Dictionary + adapters
134
+
135
+ Install and configure Style Dictionary v4 in `packages/tokens/`. For each chosen
136
+ platform, apply its adapter preset (per `.throughline/references/sync-adapters.md`):
137
+ register the platform, transform group, format, and `outputReferences`
138
+ (true for web → preserves the semantic→primitive cascade; false for native →
139
+ flattens). Web adapters emit `:root`/`.dark` (or `[data-theme]`); the shadcn
140
+ adapter also emits a Tailwind preset.
141
+
142
+ **Execution model — subagent-driven for multiple platforms.** When more than one
143
+ platform is targeted, generating each platform's output is independent and
144
+ verifiable, so dispatch **one subagent per adapter**: each produces its
145
+ platform's files and verifies them (the config builds, the expected files
146
+ appear, references resolve correctly for web / flatten for native). Review each
147
+ before combining. For a single platform, run inline. (See the selective-
148
+ subagent decision: code-gen skills parallelize; Figma-authoring skills don't.)
149
+
150
+ ## Step 4 — Build and place outputs
151
+
152
+ Run the Style Dictionary build. Outputs land in `packages/tokens/<platform>/`
153
+ as **build artifacts** — regenerated every sync, never hand-edited. Wire
154
+ `packages/tokens/package.json` to export them so the UI package, Storybook, and
155
+ any future app consume them.
156
+
157
+ ## Step 4.5 — Icon code sync (install check + custom SVGR)
158
+
159
+ Icons reach code differently from tokens, so handle them here if the system has
160
+ icons (`icons.built` true):
161
+
162
+ - **Library icons (Lucide/Material)** — verify the npm package is installed
163
+ (`icons.packageInstalled`); install it if not. Then **check version drift**:
164
+ compare the installed package version against `icons.version` (the version the
165
+ Figma mirror was built from). If they differ, an icon could exist on one side
166
+ and not the other — flag it and **offer to align** them (bump the package, or
167
+ note the Figma mirror should be refreshed). Never generate library icon
168
+ component code — the package *is* the code.
169
+ - **Custom icons** — these the repo owns, so generate them: export the custom
170
+ SVGs from Figma, optimize, and componentize via SVGR into `packages/ui` (or a
171
+ dedicated icons package). This is real code generation and rides the same
172
+ PR-review and subagent model as token output.
173
+
174
+ ## Step 5 — Full regeneration + rename detection (the safety net)
175
+
176
+ The sync is a **full regeneration** every run: outputs are rebuilt from current
177
+ Figma state, so a token **deleted** in Figma disappears from output (no orphans).
178
+ But a naive full regen can't tell a **rename** from a delete-plus-add — and a
179
+ rename silently breaks every consumer of the old name.
180
+
181
+ So before writing, **diff against the previous output** and run a rename
182
+ heuristic: if a token vanished and a new one appeared with an identical `$value`
183
+ and `$type`, flag it as a **probable rename** (e.g. `color.bg.default →
184
+ color.surface.default?`). Surface these prominently. Also surface plain
185
+ deletions (consumers will break — that's intentional, but the human should see
186
+ it).
187
+
188
+ ## Step 6 — Land it as a reviewable PR (never silent)
189
+
190
+ Never overwrite token files silently. The output of a sync is a **pull request**
191
+ (or, if the repo is only `local-git`, a clearly-described diff/commit on a
192
+ branch the user reviews). The PR body is the human safety net — it should
193
+ summarize: tokens added, changed, deleted, and **probable renames flagged for
194
+ review** so the reviewer can do a find-and-replace instead of merging a break.
195
+
196
+ For `new` users, explain what a PR is plainly ("a proposed change you review and
197
+ approve before it becomes official — like track-changes for code") and walk the
198
+ review. For `comfortable` users, just open it.
199
+
200
+ **Chromatic note.** This PR is *token-only* — it changes `packages/tokens` but no
201
+ story files. Token changes are global, so Chromatic should re-snapshot **every**
202
+ story for a sync PR. Don't rely on TurboSnap (`onlyChanged: true`) here — its
203
+ incremental model keeps missing global token changes; default to full snapshots.
204
+ If a sync PR reports "0 snapshots captured," TurboSnap is the cause — see the
205
+ TurboSnap section in `storybook-chromatic-builder`.
206
+
207
+ Update the manifest: `sync.lastRun` (timestamp), `tokens.lastSync`, confirm
208
+ `sync.platforms`. Append `token-sync-layer` to `completedSkills`.
209
+
210
+ ## Step 7 — Install the `/sync-figma-tokens` command
211
+
212
+ Set up the reusable command (the plugin ships it in `commands/`) so the user can
213
+ re-run this whole pipeline anytime tokens change in Figma. Explain that the
214
+ workflow going forward is: tweak tokens in Figma → run `/sync-figma-tokens` →
215
+ review the PR → merge. That loop is the entire point of the one-directional
216
+ source-of-truth model. Confirm the command works by describing how to invoke it.
217
+
218
+ ## What this skill must NOT do
219
+
220
+ - Never hand-edit generated token files or let the user treat them as editable —
221
+ they're build artifacts; changes go in Figma and re-sync.
222
+ - Never flatten semantic→primitive references for web adapters (kills theming).
223
+ - Never write token files silently — always a reviewable PR/diff.
224
+ - Never merge or push to a protected branch on the user's behalf without review.
225
+ - Never skip rename detection — a silent rename is the worst failure mode.
226
+ - Never bake alpha into finished `rgba(...)` on a retrofit — emit channels
227
+ (`rgb(var(--x) / <alpha-value>)`) so Tailwind `/opacity` modifiers survive.
228
+ - Never carry a `/opacity` modifier onto a var-based token — convert to `color-mix`
229
+ or channel alpha (guardrail 6).
230
+ - Never normalize float32 inside Figma (it re-quantizes) — round at the export
231
+ boundary (`Math.round(v*100)/100`, guardrail 2).
232
+
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@radicool/throughline",
3
+ "version": "0.12.0",
4
+ "description": "Build a complete design system end to end — author in Figma, sync tokens to code, generate Storybook. Usable from Claude Code, Cursor, Codex, or any AGENTS.md agent.",
5
+ "type": "module",
6
+ "bin": {
7
+ "throughline": "scripts/install.mjs"
8
+ },
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
12
+ "files": [
13
+ "adapters/",
14
+ "references/",
15
+ "scripts/",
16
+ "!scripts/**/*.test.mjs",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "engines": {
21
+ "node": ">=20"
22
+ },
23
+ "license": "MIT",
24
+ "author": "Jordan Pease",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/jrpease/throughline.git"
28
+ }
29
+ }