figma-relai 0.3.0 → 0.4.1

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/README.md CHANGED
@@ -4,9 +4,9 @@ English | [日本語](README.ja.md) | [中文](README.zh.md)
4
4
 
5
5
  [figma-relai.vercel.app](https://figma-relai.vercel.app)
6
6
 
7
- **Your AI, on the canvas.** Relai connects Claude Code, Cursor, Codex — any MCP client — to Figma, so you can read, edit, audit, and build design systems by talking to the model you already use. It works on every Figma plan, because writes go through a Figma plugin rather than the paid REST API.
7
+ **Your AI, on the canvas.** Relai connects Claude Code, Cursor, Codex — any MCP client — to Figma, so you can read, edit, audit, and build design systems by talking to the model you already use. Writes go through a Figma plugin rather than the paid REST API, so it works on every Figma plan and the file itself keeps your rules and rulings as you go.
8
8
 
9
- Relai is built on a simple position: the AI era should put designers *more* in charge, not less taste and judgment stay with you, the labor moves to a model you already trust, with every step visible.
9
+ Relai's position: the AI era should put designers *more* in charge, not less. Taste and judgment stay with you; the labor moves to a model you already trust, with every step visible.
10
10
 
11
11
  <img src="assets/plugin-ui.png" alt="The Relai plugin: activity feed, connection status, and a Stop button" width="380" />
12
12
 
@@ -22,7 +22,7 @@ Relai is built on a simple position: the AI era should put designers *more* in c
22
22
 
23
23
  Every command shows up in the plugin as it runs, with timing and success or failure. Click an entry to jump to that layer on the canvas. Press **Stop** if you change your mind — the rest of the batch is cancelled.
24
24
 
25
- Relai is how its author maintains a production design system. From one of those sessions — July 2026, numbers unedited:
25
+ The numbers scale. Relai is how its author maintains a production design system, and this excerpt is from one of those sessions — July 2026, unedited:
26
26
 
27
27
  ```text
28
28
  audit_colors 45,509 nodes scanned · 3.9s
@@ -51,38 +51,60 @@ For Cursor, add this to `.cursor/mcp.json`:
51
51
  { "mcpServers": { "Relai": { "command": "npx", "args": ["-y", "figma-relai"] } } }
52
52
  ```
53
53
 
54
- **3. Ask for something.** Pairing is automatic; there is nothing to copy between windows. The `join_room` tool exists for one rare case only: two Figma files running the plugin at the same time.
54
+ **3. Ask for something.** Pairing is automatic; there is nothing to copy between windows.
55
55
 
56
56
  ## What it's good at
57
57
 
58
58
  Understanding a design. "How is this screen put together?" gets you structure, colors, layout, and token usage in one pass, and the AI can take a screenshot to actually look at the canvas rather than guess.
59
59
 
60
- Bulk edits. "Translate every button label to English" or "recolor this for dark mode" become one round-trip across dozens of nodes instead of an afternoon of clicking.
60
+ Bulk edits. "Translate every button label to English" or "recolor this for dark mode" become one round-trip across dozens of layers instead of an afternoon of clicking.
61
61
 
62
- Audits. `analyze_design` checks color-token coverage, auto-layout quality, component health, and accessibility (WCAG contrast, touch targets, text sizes) — or all four at once as a weighted 0–100 health score you can put in a review.
62
+ Audits. `analyze_design` checks color-token coverage, auto-layout quality, component health, and accessibility (WCAG contrast, touch targets, text sizes) — or all four at once as a weighted 0–100 health score you can put in a review. It also scores agent-readiness (how prepared the file is for AI work, with top gaps), fingerprints the file's voice (its radius/spacing/type signature), and runs a ghost census for references to soft-deleted variables.
63
63
 
64
64
  Design systems. Variable collections with modes, token binding, shared styles, components with proper variants, team-library imports. `get_design_system` inventories what the file — and the libraries it uses — already has, so the AI builds from your components instead of redrawing near-copies; `analyze_design`'s tokens aspect finds hardcoded values that visually match an existing variable, and one `tokenize` call binds them all. These run as declarative operations with precondition checks, so the same request behaves the same way every time, and a failure tells the AI what to do next ("call set_layout_mode first") instead of dumping a stack trace.
65
65
 
66
66
  Everything else. `execute_figma` runs JavaScript against the Figma Plugin API directly — the same escape-hatch approach as Figma's official MCP — with a `relai.*` helper library that makes the correct pattern the shortest one, hints attached to known errors, and a lint that flags silent mistakes. If you'd rather the AI never ran code, turn it off with the plugin's "Allow code execution" toggle.
67
67
 
68
+ ## The file carries the law
69
+
70
+ Rules you'd normally paste into every prompt live inside the Figma file itself, so any future session — from any MCP client, by anyone who opens the file — starts already briefed.
71
+
72
+ **Conventions** are a little CLAUDE.md stored in the file: naming rules, token routing, spacing habits. The AI reads them before working.
73
+
74
+ **Memory** holds your precedents. Rule once — "this gap is on purpose" — and the ruling is recorded in the file; any later edit that touches what it references gets your words attached to its result:
75
+
76
+ ```text
77
+ you "this gap is on purpose — remember it"
78
+ file record_precedent · saved ✓
79
+
80
+ (another session, a different AI, about to "fix" the gap)
81
+ file precedent attached — "…is on purpose" · the edit backs off
82
+ ```
83
+
84
+ The panel's Memory row lists every entry; delete any of them any time. Nothing is recorded silently.
85
+
86
+ **AI no-go zones** fence off whole pages — brand masters, legal, the one that is already pixel-perfect. Writes into a guarded page are rejected before they run, with the reason on the receipt, and only you edit the guard list.
87
+
88
+ **Confirmation** is one level with four stops — OPEN · RISK · BULK · ALL. At RISK, the default, only dangerous operations ask (deleting variables or styles, detach, flatten); everything else keeps moving. Set it in the panel; agents can't move it.
89
+
68
90
  ## You stay in control
69
91
 
70
- The plugin is the designer's side of the deal: a live activity feed of everything the AI does, an "AI connected" indicator that means an agent is actually paired (not just that a server is running), and a Stop button that cancels pending work. Selection and page changes you make flow back to the AI as events, so "now do the same to this one" works without re-explaining. The relay is local: file contents move only between Figma, your machine, and the AI client you already trust.
92
+ The plugin is the designer's side of the deal: a live activity feed of everything the AI does, an AI-connected indicator that means an agent is actually paired (not just that a server is running), and a Stop button that cancels pending work. Selection and page changes you make flow back to the AI as events, so "now do the same to this one" works without re-explaining.
71
93
 
72
- Three dials go further when you want them. **Approvals** ("Ask before big edits") holds bulk writes and code execution until you press Approve in the panel. **Lock to selection** rejects edits outside whatever you've selected — the AI gets a clear error, not a silent pass. And **file conventions** are a little CLAUDE.md stored inside the Figma file itself: naming rules, spacing habits, do-not-touch pages — every future session, from any AI client, reads it before working. Since 0.3 the file also keeps **memory**: rulings you make ("this deviation is intent, not drift") are recorded as precedents inside the file, and any future edit that touches what they reference gets the precedent attached to its result — the AI is corrected by your own case law, in the moment. The panel's Memory row shows every entry; delete any of them any time. The UI speaks English, 日本語, and 中文.
94
+ **Lock to selection** rejects edits outside whatever you've selected — the AI gets a clear error, not a silent pass. The relay is local: file contents move only between Figma, your machine, and the AI client you already trust. The UI speaks English, 日本語, and 中文.
73
95
 
74
96
  ## How it works
75
97
 
76
98
  ```
77
99
  AI (any MCP client)
78
100
  ↕ stdio
79
- MCP server 30 tools · analysis · verification
101
+ MCP server 32 tools · analysis · verification
80
102
  (embedded relay) WebSocket room hub on 127.0.0.1:9055
81
103
  ↕ WebSocket
82
104
  Figma plugin executes Plugin API calls
83
105
  ```
84
106
 
85
- The relay lives inside the MCP server, so there is no extra process to keep alive. When several MCP clients run at once, the first one hosts the relay and the others connect to it; if the host exits, a survivor takes over. Both sides remember their room, rejoin after restarts or sleep, and find each other without any copy-pasting.
107
+ The relay lives inside the MCP server, so there is no extra process to keep alive. When several MCP clients run at once, the first one hosts the relay and the others connect to it; if the host exits, a survivor takes over. Both sides remember their room, rejoin after restarts or sleep, and find each other without any copy-pasting. The `join_room` tool exists for one rare case only: two Figma files running the plugin at the same time.
86
108
 
87
109
  Ports are fixed by Figma's plugin sandbox: the manifest allowlists `ws://localhost:9055–9057`, and other ports cannot work without editing `manifest.json`. That's why there is no port setting in the UI.
88
110
 
@@ -103,11 +125,11 @@ Ports are fixed by Figma's plugin sandbox: the manifest allowlists `ws://localho
103
125
  | Comments | `manage_comments` (needs a token — see below) |
104
126
  | Advanced | `batch_execute` · `execute_figma` · `join_room` |
105
127
 
106
- Each tool is self-describing, so the AI sees full parameter docs. The same contract also exists as a file: `npx figma-relai manifest` prints a machine-readable JSON of every tool schema, plugin command, and known pitfall — generated from the running code on every build (committed as `docs/manifest.json`), so it cannot drift — and `npx figma-relai docs <tool>` renders it for humans. Ten skill documents ship alongside as MCP prompts: token strategy, component conventions, audit workflows, a Plugin API cheat sheet for `execute_figma`, file memory & precedents, and recipes for design-system-first building, bulk cleanup, and comment-driven collaboration. Your own skills load too: drop markdown files with a name/description frontmatter into `~/.figma-relai/skills/` and they register as `user:` prompts.
128
+ Each tool is self-describing, so the AI sees full parameter docs. The same contract also exists as a file: `npx figma-relai manifest` prints a machine-readable JSON of every tool schema, plugin command, and known pitfall — generated from the running code on every build (committed as `docs/manifest.json`), so it cannot drift — and `npx figma-relai docs <tool>` renders it for humans. Eleven skill documents ship alongside as MCP prompts: token strategy, component conventions, audit workflows, the QA gate, a Plugin API cheat sheet for `execute_figma`, file memory & precedents, and recipes for design-system-first building, bulk cleanup, and comment-driven collaboration. Your own skills load too: drop markdown files with a name/description frontmatter into `~/.figma-relai/skills/` and they register as `user:` prompts.
107
129
 
108
130
  ## Relai and Figma's official MCP
109
131
 
110
- Figma's own AI has grown fast — the official MCP server now writes to the canvas, and the Figma Design Agent collaborates right inside the editor. Both are excellent, and both are reserved for full seats on paid plans, with usage metered in AI credits and models chosen by Figma. Relai is the open-source counterpart on the other side of that line: every plan including free, whatever model and subscription you already use, everything running on your machine, and the designer holding the controls. If you have the seats, the two coexist happily — run both.
132
+ Figma's own AI has grown fast — the official MCP server now writes to the canvas, and the Figma Design Agent collaborates right inside the editor. Both are reserved for full seats on paid plans, with usage metered in AI credits and models chosen by Figma. Relai is the open-source counterpart on the other side of that line: every plan including free, whatever model and subscription you already use, everything running on your machine, and the designer holding the controls. If you have the seats, the two coexist happily — run both.
111
133
 
112
134
  ## Optional: comments
113
135
 
@@ -12,7 +12,7 @@ function createServer() {
12
12
  return new McpServer(
13
13
  {
14
14
  name: "Relai",
15
- version: "0.3.0"
15
+ version: "0.4.1"
16
16
  },
17
17
  {
18
18
  instructions: `
@@ -153,6 +153,12 @@ var FIGMA_COMMANDS = [
153
153
  "list_precedents",
154
154
  "update_precedent",
155
155
  "remove_precedent",
156
+ "get_guards",
157
+ "set_guards",
158
+ "audit_voice",
159
+ "audit_voice_drift",
160
+ "audit_readiness",
161
+ "audit_ghosts",
156
162
  // Components
157
163
  "get_local_components",
158
164
  "get_instance_overrides",
@@ -1357,10 +1363,10 @@ import { z as z9 } from "zod";
1357
1363
  function register8(server, sendCommand) {
1358
1364
  server.tool(
1359
1365
  "manage_pages",
1360
- "Page operations: list all pages, create/rename/delete a page, or set a page's background color. To make a page current, use navigate with switch_page.",
1366
+ "Page operations: list all pages, create/rename/delete a page, or set a page's background color. To make a page current, use navigate with switch_page. Guards: list_guards shows the designer's AI no-go zones; guard/unguard change them \u2014 ONLY do that when the designer explicitly asks (guards are theirs; every change shows in their activity feed). Writes into a guarded page are rejected at dispatch.",
1361
1367
  {
1362
- action: z9.enum(["list", "create", "rename", "delete", "set_background"]),
1363
- pageId: z9.string().optional().describe("Target page (rename/delete/set_background)"),
1368
+ action: z9.enum(["list", "create", "rename", "delete", "set_background", "list_guards", "guard", "unguard"]),
1369
+ pageId: z9.string().optional().describe("Target page (rename/delete/set_background/guard/unguard)"),
1364
1370
  name: z9.string().optional().describe("Page name (create/rename)"),
1365
1371
  color: colorSchema.optional().describe("Background color (set_background)")
1366
1372
  },
@@ -1383,6 +1389,18 @@ function register8(server, sendCommand) {
1383
1389
  case "set_background":
1384
1390
  result = await sendCommand("set_page_background", { pageId, color });
1385
1391
  break;
1392
+ case "list_guards":
1393
+ result = await sendCommand("get_guards", {});
1394
+ break;
1395
+ case "guard":
1396
+ case "unguard": {
1397
+ if (!pageId) throw new Error(`${action} needs pageId`);
1398
+ const state = await sendCommand("get_guards", {});
1399
+ const current = state.pages.filter((p) => p.guarded).map((p) => p.id);
1400
+ const next = action === "guard" ? [.../* @__PURE__ */ new Set([...current, pageId])] : current.filter((id) => id !== pageId);
1401
+ result = await sendCommand("set_guards", { pages: next });
1402
+ break;
1403
+ }
1386
1404
  }
1387
1405
  return jsonResult(result);
1388
1406
  } catch (error) {
@@ -2531,25 +2549,65 @@ function register19(server, sendCommand) {
2531
2549
  registerAnalysisTools(interceptor, sendCommand);
2532
2550
  server.tool(
2533
2551
  "analyze_design",
2534
- "Audit the design from one aspect: color (token coverage, unbound fills/strokes), layout (auto-layout quality, spacing consistency), components (detached instances, component health), accessibility (WCAG contrast incl. large-text thresholds, touch targets, minimum text sizes), tokens (hardcoded values that match an existing variable \u2014 each finding names the variable to bind; fix in one shot with manage_variables action:tokenize fix:true), or overall (runs color/layout/components/accessibility and returns a weighted 0-100 health score \u2014 good for audits and reports). Defaults to the current selection; tokens defaults to the current page.",
2552
+ `Audit the design from one aspect: color (token coverage, unbound fills/strokes), layout (auto-layout quality, spacing consistency), components (detached instances, component health), accessibility (WCAG contrast incl. large-text thresholds, touch targets, minimum text sizes), tokens (hardcoded values that match an existing variable \u2014 each finding names the variable to bind; fix in one shot with manage_variables action:tokenize fix:true), overall (runs color/layout/components/accessibility and returns a weighted 0-100 health score \u2014 good for audits and reports), voice (the file's statistical fingerprint: radius/spacing/type signatures, tokenized-paint and instance rates \u2014 what "sounds like this file"), readiness (0-100 agent-readiness score with top gaps: conventions, precedents, semantic tokens, components), or ghosts (stale references to soft-deleted variables \u2014 they still render but are invisible in pickers and dead on publish). Defaults to the current selection; tokens/voice/ghosts default to the current page.`,
2535
2553
  {
2536
- aspect: z20.enum(["color", "layout", "components", "accessibility", "tokens", "overall"]),
2537
- nodeId: z20.string().optional().describe("Root node to analyze (default: current selection)")
2554
+ aspect: z20.enum(["color", "layout", "components", "accessibility", "tokens", "overall", "voice", "readiness", "ghosts"]),
2555
+ nodeId: z20.string().optional().describe("Root node to analyze (default: current selection)"),
2556
+ pageIds: z20.array(z20.string()).optional().describe("voice/ghosts: pages to scan (default: current page)")
2538
2557
  },
2539
2558
  { readOnlyHint: true },
2540
- async ({ aspect, nodeId }) => {
2559
+ async ({ aspect, nodeId, pageIds }) => {
2541
2560
  if (aspect === "overall") {
2542
2561
  return runOverallAudit(aspectHandlers, nodeId);
2543
2562
  }
2544
2563
  if (aspect === "tokens") {
2545
2564
  return runTokenDrift(sendCommand, nodeId);
2546
2565
  }
2566
+ if (aspect === "voice") {
2567
+ return runVoice(sendCommand, pageIds);
2568
+ }
2569
+ if (aspect === "readiness") {
2570
+ return runReadiness(sendCommand);
2571
+ }
2572
+ if (aspect === "ghosts") {
2573
+ return runGhosts(sendCommand, pageIds);
2574
+ }
2547
2575
  const handler = aspectHandlers.get(ASPECT_TOOL[aspect]);
2548
2576
  if (!handler) throw new Error(`Unknown aspect: ${aspect}`);
2549
2577
  return handler({ nodeId });
2550
2578
  }
2551
2579
  );
2552
2580
  }
2581
+ async function runVoice(sendCommand, pageIds) {
2582
+ const data = await sendCommand("audit_voice", { pageIds }, 12e4);
2583
+ const sig = (k) => (data.signatures?.[k] ?? []).slice(0, 3).map((e) => `${e.value} (${Math.round(e.share * 100)}%)`).join(", ") || "\u2014";
2584
+ return standardResult({
2585
+ summary: `Voice of ${data.pages?.join(", ")}: radius ${sig("cornerRadius")} \xB7 spacing ${sig("spacing")} \xB7 type ${sig("fontSize")} \xB7 ${Math.round((data.tokenizedPaintRate ?? 0) * 100)}% tokenized paint \xB7 ${Math.round((data.instanceRate ?? 0) * 100)}% instance rate (${data.nodesScanned} nodes).`,
2586
+ data,
2587
+ recommended_next: [
2588
+ { tool: "validate_design_rules", reason: "Check any node against this voice (voice_drift rule, advisory)" }
2589
+ ]
2590
+ });
2591
+ }
2592
+ async function runReadiness(sendCommand) {
2593
+ const data = await sendCommand("audit_readiness", {}, 12e4);
2594
+ const gaps = (data.topGaps ?? []).map((g) => `${g.dimension} (\u2212${g.lost}): ${g.fix}`);
2595
+ return standardResult({
2596
+ summary: `Agent-readiness: ${data.score ?? 0}/100.${gaps.length ? ` Top gaps: ${gaps.join(" | ")}` : " Fully wired."}`,
2597
+ data,
2598
+ recommended_next: (data.score ?? 0) < 80 ? [{ tool: "manage_conventions", reason: "Close the cheapest gap first \u2014 conventions and precedents compound" }] : []
2599
+ });
2600
+ }
2601
+ async function runGhosts(sendCommand, pageIds) {
2602
+ const data = await sendCommand("audit_ghosts", { pageIds }, 3e5);
2603
+ const ghosts = data.ghostRefs ?? 0;
2604
+ return standardResult({
2605
+ summary: ghosts === 0 ? `No ghost references on ${data.pages?.join(", ")} (${data.refsScanned} refs across ${data.nodesScanned} nodes; dangling: ${data.danglingRefs ?? 0}).` : `${ghosts} ghost reference(s) to ${data.ghostCount} soft-deleted variable(s) on ${data.pages?.join(", ")} \u2014 they still render but are invisible in pickers and dead on publish.`,
2606
+ data,
2607
+ warnings: ghosts > 0 ? [{ category: "general", message: `${ghosts} bindings reference soft-deleted variables` }] : [],
2608
+ recommended_next: ghosts > 0 ? [{ tool: "manage_variables", reason: "Rebind to live twins (compare terminal values, collection-aware) \u2014 never delete-and-recreate" }] : []
2609
+ });
2610
+ }
2553
2611
  async function runTokenDrift(sendCommand, nodeId) {
2554
2612
  const data = await sendCommand("scan_token_drift", { nodeId, fix: false }, 12e4);
2555
2613
  const matched = data.stats?.matched ?? 0;
@@ -3292,6 +3350,19 @@ function register20(server, sendCommand) {
3292
3350
  nodeId: targetId,
3293
3351
  fix: hasDefaultName ? { tool: "update_node", reason: "Rename with a semantic name", args: { nodeId: targetId } } : void 0
3294
3352
  });
3353
+ try {
3354
+ const drift = await sendCommand("audit_voice_drift", { nodeId: targetId }, 6e4);
3355
+ const count = drift?.flagCount ?? 0;
3356
+ const sample = (drift?.flags ?? []).slice(0, 3).map((f) => `${f.name}: ${f.property}=${f.value}`).join("; ");
3357
+ results.push({
3358
+ rule: "voice_drift",
3359
+ passed: true,
3360
+ severity: "info",
3361
+ message: count === 0 ? "On voice: every sampled value sits in this file's signature." : `${count} value(s) off this file's voice (advisory \u2014 may be intent): ${sample}${count > 3 ? " \u2026" : ""}. If intentional, record a precedent; if drift, align to the file's signature.`,
3362
+ nodeId: targetId
3363
+ });
3364
+ } catch {
3365
+ }
3295
3366
  if (nodeInfo.type === "INSTANCE" || nodeInfo.name?.toLowerCase().includes("button")) {
3296
3367
  const w = nodeInfo.width ?? 0;
3297
3368
  const h = nodeInfo.height ?? 0;
@@ -3531,7 +3602,7 @@ var design_tokens_strategy_default = "# Design Tokens Strategy \u2014 4-Tier Tok
3531
3602
  var component_architecture_strategy_default = "# Component Architecture \u2014 Component Conventions\n\n## Token-First Principle\nEvery visual property must reference a token. No raw values in components.\n- Colors \u2192 Tier 2 semantic tokens or Tier 3 component tokens\n- Sizes (height, spacing) \u2192 Tier 2 or Tier 3 size tokens\n- Typography \u2192 Tier 2 typography tokens\n- Border radius/width \u2192 Tier 2 border tokens\n\n## Tier 3 Token Pattern\nWhen a component needs brand-differentiated values:\n\n`{component}/color/{role}/{state}`\n- Roles: background, border, content\n- States: default, hover, pressed, disabled\n- Selected states: selected, selected-hover, selected-pressed, selected-disabled\n- Variants: primary, ai, critical, ghost (prefix before role)\n\nExample \u2014 Button tokens:\n- `button/color/background/default` \u2192 neutral button bg\n- `button/primary/color/background/hover` \u2192 primary variant hover\n- `button/size/sm` / `button/size/md` / `button/size/lg`\n\n## State Management\nStandard interactive states:\n- default \u2192 hover \u2192 pressed \u2192 disabled (always present)\n- selected \u2192 selected-hover \u2192 selected-pressed \u2192 selected-disabled (for toggleable elements)\n\n## Size Variants\nUse sm / md / lg consistently. Define as Tier 3 FLOAT tokens when brand-differentiated.\n- `{component}/size/sm`, `{component}/size/md`, `{component}/size/lg`\n\n## Component Structure\n1. Use auto-layout (VERTICAL or HORIZONTAL) for all containers\n2. Use HUG for content-driven sizes, FILL for responsive elements\n3. Use component properties for configurable aspects:\n - BOOLEAN: toggle visibility (icons, labels)\n - INSTANCE_SWAP: swap nested components\n - VARIANT: select between variant sets\n - TEXT: configurable text content\n\n## Workflow\n1. `get_selection_context` \u2192 understand current component structure\n2. `manage_components` (list) \u2192 check existing components\n3. Design the component with auto-layout\n4. Create variants for states using `manage_components` (create_set)\n5. `manage_variables` (bind) \u2192 attach Tier 2/3 tokens to all visual properties\n6. `manage_components` (set_props) \u2192 configure instance properties\n\n## Verification\n- `analyze_design` (aspect: components) \u2192 find detached instances, unused components\n- `verify_changes` \u2192 confirm properties match expected values\n- `get_node_data` (detail: variables) \u2192 ensure all fills/strokes are token-bound\n\n---\n\n*Tool parameter contracts are deliberately not duplicated in this document \u2014 every tool is self-describing over MCP, and `npx figma-relai docs <tool>` (or the generated `docs/manifest.json`) is the always-current reference.*\n";
3532
3603
 
3533
3604
  // ../../docs/skills/design-audit-strategy.md
3534
- var design_audit_strategy_default = "# Design Audit Strategy\n\n## Phase 1: Overview\n1. `get_document_overview` \u2192 pages, component/style/variable counts\n2. `get_design_tokens` \u2192 token system structure (collections, modes, coverage)\n\n## Phase 2: Color & Token Audit\n1. `analyze_design` (aspect: color) \u2192 find colors not backed by variables\n2. `get_node_data` (detail: variables) on key nodes \u2192 verify correct tier references\n3. Check: components should reference Tier 2/3 tokens, never Tier 1 or raw values\n\n## Phase 3: Layout Quality\n1. `analyze_design` (aspect: layout) \u2192 detect missing auto-layout, inconsistent spacing\n2. Check: all containers should use auto-layout (not absolute positioning)\n3. Check: spacing should use token values, not arbitrary px\n\n## Phase 4: Component Health\n1. `analyze_design` (aspect: components) \u2192 detached instances, unused components\n2. `manage_components` (list) \u2192 review component inventory\n3. Check: all instances should be connected to their main component\n\n## Phase 5: Accessibility\n1. `analyze_design` (aspect: accessibility) \u2192 contrast ratios, touch target sizes\n2. Check: minimum contrast 4.5:1 for text, 3:1 for large text\n3. Check: touch targets \u2265 44\xD744px\n\n## Phase 6: Comprehensive Validation\n1. `validate_design_rules` \u2192 runs all checks in one pass:\n - token_coverage: % of nodes with variable bindings\n - auto_layout: % of frames using auto-layout\n - naming_convention: layer naming quality\n - touch_target_size: minimum size compliance\n\n## Reporting\n- `screenshot` before and after fixes\n- `annotate` (set) / `annotate` (set_multiple) to flag issues directly in the design\n- Summarize findings by severity (critical \u2192 warning \u2192 info)\n\n---\n\n*Tool parameter contracts are deliberately not duplicated in this document \u2014 every tool is self-describing over MCP, and `npx figma-relai docs <tool>` (or the generated `docs/manifest.json`) is the always-current reference.*\n";
3605
+ var design_audit_strategy_default = "# Design Audit Strategy\n\n## Phase 1: Overview\n1. `get_document_overview` \u2192 pages, component/style/variable counts\n2. `get_design_tokens` \u2192 token system structure (collections, modes, coverage)\n\n## Phase 2: Color & Token Audit\n1. `analyze_design` (aspect: color) \u2192 find colors not backed by variables\n2. `get_node_data` (detail: variables) on key nodes \u2192 verify correct tier references\n3. Check: components should reference Tier 2/3 tokens, never Tier 1 or raw values\n\n## Phase 3: Layout Quality\n1. `analyze_design` (aspect: layout) \u2192 detect missing auto-layout, inconsistent spacing\n2. Check: all containers should use auto-layout (not absolute positioning)\n3. Check: spacing should use token values, not arbitrary px\n\n## Phase 4: Component Health\n1. `analyze_design` (aspect: components) \u2192 detached instances, unused components\n2. `manage_components` (list) \u2192 review component inventory\n3. Check: all instances should be connected to their main component\n\n## Phase 5: Accessibility\n1. `analyze_design` (aspect: accessibility) \u2192 contrast ratios, touch target sizes\n2. Check: minimum contrast 4.5:1 for text, 3:1 for large text\n3. Check: touch targets \u2265 44\xD744px\n\n## Phase 6: Comprehensive Validation\n1. `validate_design_rules` \u2192 runs all checks in one pass:\n - token_coverage: % of nodes with variable bindings\n - auto_layout: % of frames using auto-layout\n - naming_convention: layer naming quality\n - touch_target_size: minimum size compliance\n\n## Reporting\n- `screenshot` before and after fixes\n- `annotate` (set) / `annotate` (set_multiple) to flag issues directly in the design\n- Summarize findings by severity (critical \u2192 warning \u2192 info)\n\n---\n\n*Tool parameter contracts are deliberately not duplicated in this document \u2014 every tool is self-describing over MCP, and `npx figma-relai docs <tool>` (or the generated `docs/manifest.json`) is the always-current reference.*\n\n## Gauges (0.4+)\n\nFold the gauges into audits where they fit: `aspect:readiness` opens the report (one number + top gaps), `aspect:ghosts` is a publish blocker when nonzero (live-list criterion), `aspect:voice` gives the file's signature so findings can distinguish drift from intent \u2014 and anything a precedent marks as intent is not a finding. For the full review-moment pass, load the `qa-gate` skill instead.\n";
3535
3606
 
3536
3607
  // ../../docs/skills/token-audit.md
3537
3608
  var token_audit_default = "# Token Audit \u2014 Token Hierarchy Compliance Check\n\n## Purpose\nVerify that a design correctly uses the 4-tier token hierarchy:\n- No raw color/size values (everything should be token-bound)\n- Correct tier references (components \u2192 Tier 2/3, never Tier 1 directly)\n- Consistent scope usage\n\n## Audit Process\n\n### Step 1: Scope the Audit\n- `get_selection_context` \u2192 identify the area to audit\n- Or `get_document_overview` \u2192 audit the full document\n\n### Step 2: Color Token Coverage\n- `analyze_design` (aspect: color) \u2192 find all colors and whether they're token-bound\n- Flag any raw hex values not backed by variables\n\n### Step 3: Variable Binding Check\n- `get_node_data` (detail: variables) on key nodes \u2192 check what's bound\n- For each binding, verify the variable comes from the correct tier:\n - \u2705 Component referencing Tier 2 (e.g., `color/content/default`)\n - \u2705 Component referencing Tier 3 (e.g., `button/color/background/default`)\n - \u274C Component referencing Tier 1 (e.g., `color-static/slate/500`)\n - \u274C Component referencing Tier 1 dynamic (e.g., `color-dynamic/slate/500`)\n\n### Step 4: Scope Validation\n- `manage_variables` (list) \u2192 check that scopes are set correctly:\n - Color content tokens \u2192 TEXT_FILL\n - Color background tokens \u2192 FRAME_FILL, SHAPE_FILL\n - Color border tokens \u2192 STROKE_COLOR\n - Size tokens \u2192 WIDTH_HEIGHT\n - Border radius \u2192 CORNER_RADIUS\n - Border width \u2192 STROKE_FLOAT\n\n### Step 5: Comprehensive Check\n- `validate_design_rules` \u2192 run all design rules at once\n\n## Output Format\nReport findings as:\n- **Coverage**: X% of nodes have token bindings\n- **Tier violations**: list of nodes referencing wrong tier\n- **Raw values**: list of unbound colors/sizes with suggested token mappings\n- **Scope issues**: variables with missing or incorrect scopes\n\n---\n\n*Tool parameter contracts are deliberately not duplicated in this document \u2014 every tool is self-describing over MCP, and `npx figma-relai docs <tool>` (or the generated `docs/manifest.json`) is the always-current reference.*\n";
@@ -3549,7 +3620,10 @@ var design_system_first_default = '# Design-system-first building\n\nThe differe
3549
3620
  var janitorial_cleanup_default = '# Janitorial cleanup\n\nThe chores designers postpone for months \u2014 layer renaming, token drift, spacing normalization \u2014 are where an agent earns trust fastest. These jobs are big, mechanical, and easy to verify. Do them conservatively and loudly.\n\n## Ground rules\n\n- **Preview before bulk.** Use `dryRun:true` on `batch_execute` / `set_properties`, or report findings first (`analyze_design`), and tell the designer the scale ("about 140 renames on this page \u2014 go?"). If the plugin\'s approval gate is on, big writes will ask them anyway.\n- **Scope tightly.** Work page by page (or within the designer\'s selection). Never sweep the whole file unasked.\n- **Never rename or restyle library instances\' internals** \u2014 only top-level frames/groups the team owns.\n\n## Recipes\n\n### Rename layers by content\n`Frame 427` tells nobody anything. Walk the target scope with `execute_figma` + `relai.query`, and derive names:\n- A frame whose only text child says "Submit" \u2192 `Button/Submit` (match the file\'s existing naming pattern from `get_design_system`, don\'t invent one).\n- Text nodes \u2192 their content, truncated (`Heading: Pricing plans`).\n- Skip nodes that already have deliberate names (anything not matching `/^(Frame|Group|Rectangle|Ellipse|Vector|Line) \\d+$/`).\nReturn the rename list; apply via one `batch_execute` of `rename_node` commands.\n\n### Tokenize hardcoded values\n`analyze_design aspect:"tokens"` finds hardcoded colors/numbers that visually match existing variables; `manage_variables action:"tokenize" fix:true` binds them in one pass. Report anything close-but-not-matching (deltaE just over tolerance) instead of silently snapping it.\n\n### Normalize spacing\nIn auto-layout scopes, collect `itemSpacing`/padding values; flag off-scale values (not on the file\'s 4/8pt grid or spacing variables) and propose the nearest on-scale value. Apply only after the designer confirms the mapping.\n\n### Sweep detached instances\n`analyze_design aspect:"components"` lists likely-detached instances. Offer to re-link obvious ones (matching component still exists) and just report the rest \u2014 re-attaching wrong is worse than detached.\n\n## Report format\n\nEnd with counts and jump-points: what changed, what was skipped and why, node ids for spot-checking (the designer can click entries in the plugin feed).\n\n---\n\n*Tool parameter contracts are deliberately not duplicated in this document \u2014 every tool is self-describing over MCP, and `npx figma-relai docs <tool>` (or the generated `docs/manifest.json`) is the always-current reference.*\n';
3550
3621
 
3551
3622
  // ../../docs/skills/memory-and-precedents.md
3552
- var memory_and_precedents_default = '# File memory & precedents\n\nEvery Figma file this plugin touches can carry its own case law: **precedents** \u2014 single adjudications the designer made, stored inside the file (shared plugin data) so they reach every future session from any AI client. Conventions are the statutes; precedents are the case law. Together they are the file\'s law, and `manage_conventions action:get` returns both.\n\n## Read before you work\n\nAt the start of any session that will modify the file, call `manage_conventions action:get`. Treat what comes back the way you treat user instructions:\n\n- `content` \u2014 the conventions doc (statutes).\n- `precedents` \u2014 recent adjudications (case law), newest first, with `precedentCount` for the total.\n\nA precedent outranks your own judgment about what "looks wrong". If a precedent says a deviation is intent, do not "fix" it \u2014 and do not re-ask about it.\n\n## When to record\n\nRecord a precedent (`action:record_precedent`) when the designer **rules** on something durable. The signal is adjudication language:\n\n- "This is intentional, not drift" \u2192 `kind: "intent"`\n- "Approved \u2014 but never restructure this table again" \u2192 `kind: "decision"`\n- "Don\'t do it that way; do X instead" \u2192 `kind: "correction"`\n\nOne precedent = one sentence (\u2264280 chars), written so a future session with zero context understands the ruling. Attach `refs` \u2014 the variable IDs, node IDs, or page IDs the ruling concerns \u2014 because refs power in-band surfacing: any future write that touches them gets the precedent attached to its result automatically.\n\n**Always say in your reply that you recorded it** ("\u5DF2\u8BB0\u5165\u6587\u4EF6\u8BB0\u5FC6: \u2026"). Recording silently breaks the designer\'s trust in what the file knows.\n\n## When NOT to record\n\n- Transient, one-off choices ("make this one red for the screenshot").\n- Anything already in the conventions doc (point to it instead; propose a conventions edit if it deserves statute status).\n- Secrets, personal information, or anything about people rather than the file.\n- Your own inferences the designer has not confirmed. Precedents are the DESIGNER\'s rulings, not your observations. When unsure, ask: "\u8BB0\u4E3A\u5224\u4F8B\u5417?"\n\n## Maintenance\n\n- The designer sees and can delete every entry in the plugin panel (Memory section). Their record, their rules.\n- Memory holds 200 entries / 64KB. When recording fails with a capacity error, offer a consolidation pass: `list_precedents`, merge superseded rulings into fewer summary entries (`record_precedent` the summaries, `remove_precedent` the merged ones), preserving every still-active ruling. Show the merge plan before applying it.\n- If a precedent contradicts current reality (the token it references is gone, the ruling was reversed), ask the designer whether to update or remove it \u2014 never silently drop case law.\n';
3623
+ var memory_and_precedents_default = '# File memory & precedents\n\nEvery Figma file this plugin touches can carry its own case law: **precedents** \u2014 single adjudications the designer made, stored inside the file (shared plugin data) so they reach every future session from any AI client. Conventions are the statutes; precedents are the case law. Together they are the file\'s law, and `manage_conventions action:get` returns both.\n\nWhen a write touches something a precedent references, **the file speaks**: the ruling arrives attached to your tool result. Treat it as the designer\'s voice from a past session calling a halt \u2014 the four-act shape is always the same: they said it once, the file kept it, you came to "fix" it knowing nothing, their words stop you. Back off before you undo something intentional.\n\n## Read before you work\n\nAt the start of any session that will modify the file, call `manage_conventions action:get`. Treat what comes back the way you treat user instructions:\n\n- `content` \u2014 the conventions doc (statutes).\n- `precedents` \u2014 recent adjudications (case law), newest first, with `precedentCount` for the total.\n\nA precedent outranks your own judgment about what "looks wrong". If a precedent says a deviation is intent, do not "fix" it \u2014 and do not re-ask about it.\n\n## When to record\n\nRecord a precedent (`action:record_precedent`) when the designer **rules** on something durable. The signal is adjudication language:\n\n- "This is intentional, not drift" \u2192 `kind: "intent"`\n- "Approved \u2014 but never restructure this table again" \u2192 `kind: "decision"`\n- "Don\'t do it that way; do X instead" \u2192 `kind: "correction"`\n\nOne precedent = one sentence (\u2264280 chars), written so a future session with zero context understands the ruling. Attach `refs` \u2014 the variable IDs, node IDs, or page IDs the ruling concerns \u2014 because refs power in-band surfacing: any future write that touches them gets the precedent attached to its result automatically.\n\n**Always say in your reply that you recorded it** ("\u5DF2\u8BB0\u5165\u6587\u4EF6\u8BB0\u5FC6: \u2026"). Recording silently breaks the designer\'s trust in what the file knows.\n\n## When NOT to record\n\n- Transient, one-off choices ("make this one red for the screenshot").\n- Anything already in the conventions doc (point to it instead; propose a conventions edit if it deserves statute status).\n- Secrets, personal information, or anything about people rather than the file.\n- Your own inferences the designer has not confirmed. Precedents are the DESIGNER\'s rulings, not your observations. When unsure, ask: "\u8BB0\u4E3A\u5224\u4F8B\u5417?"\n\n## Promote rulings into enforcement\n\nWhen conventions or repeated precedents describe something ENFORCEABLE, propose the promotion in chat \u2014 never enact it silently:\n\n- "Never touch page X" (in conventions or a repeated ruling) \u2192 offer: "\u8981\u628A X \u8BBE\u4E3A AI \u7981\u533A\u5417?(manage_pages action:guard)" \u2014 set it only after the designer agrees.\n- Repeated identical adjudications ("this is intent" three times on the same token family) \u2192 offer to consolidate them into one conventions line, then remove the superseded precedents.\n\nGuards belong to the designer: never guard/unguard on your own judgment, and mention any guard change in your reply.\n\n## Reporting style as trust matures\n\nEarly sessions: report every change. Once the designer clearly stops reading full receipts (they say "\u7B80\u62A5\u5C31\u597D" or approve digests), switch to digest mode: lead with **the two changes you are least sure about** and anything a precedent or guard touched, then one line for the rest ("\u5176\u4F59 N \u5904\u6309\u89C4\u7EA6"). Full detail must always remain one question away \u2014 digest is a summary, never a omission. Never digest away errors, guard rejections, or precedent hits.\n\n## Maintenance\n\n- The designer sees and can delete every entry in the plugin panel (Memory section). Their record, their rules.\n- Memory holds 200 entries / 64KB. When recording fails with a capacity error, offer a consolidation pass: `list_precedents`, merge superseded rulings into fewer summary entries (`record_precedent` the summaries, `remove_precedent` the merged ones), preserving every still-active ruling. Show the merge plan before applying it.\n- If a precedent contradicts current reality (the token it references is gone, the ruling was reversed), ask the designer whether to update or remove it \u2014 never silently drop case law.\n';
3624
+
3625
+ // ../../docs/skills/qa-gate.md
3626
+ var qa_gate_default = '# QA gate \u2014 the full physical, on demand\n\nHuman-triggered, review-moment audit of a file (or a set of pages). Run it when the designer says "\u4F53\u68C0" / "is this ready for review?" / before a library merge. It composes the gauges into one report with receipts.\n\n## The pass\n\nRun in this order (each is read-only):\n\n1. `manage_conventions action:get` \u2014 the law you\'ll be judging against (conventions + precedents). If precedents mark deviations as intent, they are NOT findings \u2014 the file already ruled. List them as "waived by precedent", never as problems.\n2. `analyze_design aspect:readiness` \u2014 agent-readiness score + top gaps.\n3. `analyze_design aspect:ghosts` (pass `pageIds` for a multi-page pass) \u2014 stale references to soft-deleted variables. Any ghost count > 0 is a red flag for publish.\n4. `analyze_design aspect:voice` \u2014 the file\'s signature (radius/spacing/type, tokenized-paint rate, instance rate).\n5. `analyze_design aspect:overall` on the key screens the designer names (health score 0-100).\n6. `analyze_design aspect:tokens` \u2014 hardcoded values that match existing variables (each is one `tokenize` away from bound).\n\n## The report\n\nProduce a compact markdown report, structured exactly like this:\n\n```\n# QA gate \u2014 <file name> \u2014 <date>\nverdict: PASS | PASS WITH NOTES | BLOCK\n\nreadiness NN/100 (top gap: \u2026)\nghosts N refs (0 required for publish)\nhealth NN/100 (screens: \u2026)\ntokens N unbound-but-matchable\nvoice radius \u2026, spacing \u2026, \u2026% tokenized\n\n## findings (ranked)\n1. [BLOCK|NOTE] \u2026 \u2014 evidence: <numbers/nodeIds> \u2014 fix: <one tool call>\n\u2026\n\n## precedent-protected (not findings)\n- \u2026 (per precedent "\u2026", YYYY-MM-DD)\n```\n\nVerdict rules: any ghosts or health < 60 on a named key screen \u2192 BLOCK; readiness < 50 \u2192 note it but readiness alone never blocks; everything protected by a precedent is listed separately, never as a finding.\n\n## Rules\n\n- Numbers unedited: report what the tools returned, never round a failing number into a passing one.\n- Findings must carry evidence (counts, nodeIds) and a one-call fix each.\n- Offer \u2014 do not perform \u2014 fixes. The QA gate is a read-only pass; repairs are a separate, approved session.\n- If the designer wants the report in-file: create a page named `\xB7 QA Report <date>` and write the report as text there (ask first \u2014 it adds a page to their file).\n- Big files: run ghosts page-set by page-set (`pageIds`) instead of one giant pass, and say which pages were covered. Silence about coverage reads as "covered everything" \u2014 never let it.\n';
3553
3627
 
3554
3628
  // src/prompts.ts
3555
3629
  var SKILLS = [
@@ -3602,6 +3676,11 @@ var SKILLS = [
3602
3676
  "memory-and-precedents",
3603
3677
  "The file's case law: when to record a designer adjudication as a precedent, when not to, and how memory is maintained. Load in any session that adjudicates or touches a file with existing precedents.",
3604
3678
  memory_and_precedents_default
3679
+ ],
3680
+ [
3681
+ "qa-gate",
3682
+ "Review-moment full physical: readiness + ghosts + voice + health + tokens composed into one verdicted report with evidence. Load when asked 'is this ready?' or before a library merge.",
3683
+ qa_gate_default
3605
3684
  ]
3606
3685
  ];
3607
3686
  function registerPrompts(server) {
@@ -3638,4 +3717,4 @@ export {
3638
3717
  listToolCatalog,
3639
3718
  registerPrompts
3640
3719
  };
3641
- //# sourceMappingURL=chunk-2MUSDWUC.js.map
3720
+ //# sourceMappingURL=chunk-KZVU4D6L.js.map