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 +34 -12
- package/dist/{chunk-2MUSDWUC.js → chunk-KZVU4D6L.js} +90 -11
- package/dist/chunk-KZVU4D6L.js.map +1 -0
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/{manifest-DRLMH5YW.js → manifest-3VQTFXMK.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-2MUSDWUC.js.map +0 -1
- /package/dist/{manifest-DRLMH5YW.js.map → manifest-3VQTFXMK.js.map} +0 -0
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.
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
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
|
-
|
|
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-
|
|
3720
|
+
//# sourceMappingURL=chunk-KZVU4D6L.js.map
|