figma-relai 0.8.0 → 0.8.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 +8 -6
- package/dist/{chunk-62ZA4AUN.js → chunk-C5XQ37GD.js} +21 -16
- package/dist/chunk-C5XQ37GD.js.map +1 -0
- package/dist/index.js +32 -10
- package/dist/index.js.map +1 -1
- package/dist/{manifest-SP2IBSUR.js → manifest-VFN4YSLY.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-62ZA4AUN.js.map +0 -1
- /package/dist/{manifest-SP2IBSUR.js.map → manifest-VFN4YSLY.js.map} +0 -0
package/README.md
CHANGED
|
@@ -61,9 +61,9 @@ The panel's Memory row lists every entry; delete any of them any time. Nothing i
|
|
|
61
61
|
|
|
62
62
|
**Kits.** One designer usually runs one product, and its files share the same law. Save this file's rules under a name and other files can use it. Figma drops file-carried law on branch merges; the kit puts it back — one click, or automatically if you flip that kit's auto-restore on.
|
|
63
63
|
|
|
64
|
-
**AI no-go zones** fence off whole pages — brand masters, legal, the one that is already pixel-perfect. Writes through Relai into a guarded page are rejected before they run, with the reason on the receipt.
|
|
64
|
+
**AI no-go zones** fence off whole pages — brand masters, legal, the one that is already pixel-perfect. Writes through Relai into a guarded page are rejected before they run, with the reason on the receipt — commands inside a batch included. The fences are yours: an agent can add one, but a zone you set is lifted only in the panel. The one exception is a zone the agent added itself in the same plugin session, which it may take back. Every change shows in your activity feed. Protected branches, for the canvas.
|
|
65
65
|
|
|
66
|
-
**Confirmation** is a dial with four stops — OPEN · RISK · BULK · ALL. At RISK, the default, only destructive operations ask (deleting variables or styles, detach, flatten); everything else keeps moving. Set it in the panel; no Relai tool changes it
|
|
66
|
+
**Confirmation** is a dial with four stops — OPEN · RISK · BULK · ALL. At RISK, the default, only destructive operations ask (deleting variables or styles, detach, flatten); everything else keeps moving. Set it in the panel; no Relai tool changes it, and if a script rewrites the stored setting, Relai puts your value back and says so on the receipt. A `batch_execute` asks by what it wraps: at RISK a wrapped destructive operation asks, at BULK one wrapped script asks. ALL asks before every write and every script, because a script cannot be told apart from a write before it runs.
|
|
67
67
|
|
|
68
68
|
### How far the law reaches
|
|
69
69
|
|
|
@@ -71,11 +71,11 @@ The law is stored as plugin data inside the file — no extra layers on the canv
|
|
|
71
71
|
|
|
72
72
|
What the law can stop depends on how a write arrives:
|
|
73
73
|
|
|
74
|
-
- **Commands sent through Relai's tools** are checked in the plugin, on the file's side of the wire, before they run. A write aimed at a node on a fenced page, or outside your selection lock, is refused
|
|
75
|
-
- **Scripts sent through `execute_figma`** run in the same plugin sandbox as Relai's checks, so they cannot be screened before they run. Relai refuses a script outright when the current page is fenced off.
|
|
74
|
+
- **Commands sent through Relai's tools** are checked in the plugin, on the file's side of the wire, before they run. A write aimed at a node on a fenced page, or outside your selection lock, is refused, and so is each command inside a batch. A multi-node `set_text` is checked item by item; `annotate set_multiple`, which carries its node ids inside a list, is checked against the current page only. Variables and modes belong to no page, so no-go zones do not cover them.
|
|
75
|
+
- **Scripts sent through `execute_figma`** run in the same plugin sandbox as Relai's checks, so they cannot be screened before they run. Relai refuses a script outright when the current page is fenced off. When a script ends — with an error too — Relai checks the nodes it created through the `relai` helpers or returned, and names any that sit on a fenced page or outside your lock, on the receipt and in the panel. Those writes have already happened: scripts are checked after they run, not stopped. If a script rewrote Relai's own settings — the no-go zones, the dial, the lock, the code toggle — Relai puts them back and says so, and panel actions are not applied while a script runs or for three seconds after. A script can still edit the conventions and precedents stored in the file; Relai reports that and leaves the edit in place. Turn code execution off when you want every write to go through checked commands.
|
|
76
76
|
- **Writes that never pass through Relai** — another plugin, another MCP server, someone editing by hand, anything done while Relai is closed — are outside its reach. Relai neither refuses them nor notices them.
|
|
77
77
|
|
|
78
|
-
Precedents work at a different strength: they are your recorded words, arriving
|
|
78
|
+
Precedents work at a different strength: they are your recorded words, matched before an edit runs and arriving with it — first in the result, on each item of a batch, and on the approval card when one is shown. The agent backs off because the author shows up, not because anything blocked it. Any client that connects through Relai is bound by all of this, including ones you never configured — and everything it does leaves receipts.
|
|
79
79
|
|
|
80
80
|
None of this is setup you grind through — it accumulates. On day one the file is blank law, and the agent behaves like anyone's agent. A month in, it arrives briefed on your conventions and precedents and stops at your fences: the same prompt that produces the average of everyone's design in a fresh file produces yours in yours. Two long-time users are not holding the same tool. They are holding two different instruments.
|
|
81
81
|
|
|
@@ -127,6 +127,8 @@ The plugin is the designer's side of the deal: a live activity feed of everythin
|
|
|
127
127
|
|
|
128
128
|
**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 中文.
|
|
129
129
|
|
|
130
|
+
When something needs you — an approval, a kit that refused a push, a script that reached across a fence — it stays on the panel until you deal with it. An approval card counts down; one nobody answers is denied after two minutes, and its row says it timed out.
|
|
131
|
+
|
|
130
132
|
When something goes wrong anyway — a bad sweep, a wrong "fix" — the feed is your reconstruction and Figma's version history is your restore path. There is no magic undo, and Relai doesn't pretend to have one: `diff_nodes` checkpoints tell you exactly what changed; version history takes you back. And when the law itself is wrong — a stale precedent guarding a gap you've since changed your mind about — tell the agent to go ahead, then delete the entry from the panel's Memory row. Rulings are one click to remove; the law stays yours to amend.
|
|
131
133
|
|
|
132
134
|
## How it works
|
|
@@ -142,7 +144,7 @@ Figma plugin executes Plugin API calls
|
|
|
142
144
|
|
|
143
145
|
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.
|
|
144
146
|
|
|
145
|
-
Every
|
|
147
|
+
Every call that reached the plugin, reads included, ends with a `[relai]` line that says whether Figma changed: `applied`, `not_applied`, or `unknown` — it may have, so read the canvas before trying again. Failures are marked as errors and name a code; scripts can read the same receipt from the result's `_meta`. With several files open, a call can carry `room` to go to one plugin, or `expect_file` to refuse unless the target file has that exact name. Text longer than 40,000 characters is cut before it reaches the AI, and the full text is saved on your machine in `~/.figma-relai/results/` (readable only by you, the newest 50 files, none kept longer than a day); the cut notice names the file.
|
|
146
148
|
|
|
147
149
|
The receipts above are real timings: rule checks run in the plugin before each write, small edits land sub-second, and the 45,509-node audit took 3.9s.
|
|
148
150
|
|
|
@@ -1039,7 +1039,7 @@ var RelayCore = class {
|
|
|
1039
1039
|
};
|
|
1040
1040
|
|
|
1041
1041
|
// src/version.ts
|
|
1042
|
-
var VERSION = "0.8.
|
|
1042
|
+
var VERSION = "0.8.1";
|
|
1043
1043
|
|
|
1044
1044
|
// src/tools/index.ts
|
|
1045
1045
|
import { z as z24 } from "zod";
|
|
@@ -1123,7 +1123,7 @@ function looksLikeJson(text) {
|
|
|
1123
1123
|
}
|
|
1124
1124
|
|
|
1125
1125
|
// src/call-envelope.ts
|
|
1126
|
-
var MAX_TEXT_CHARS =
|
|
1126
|
+
var MAX_TEXT_CHARS = 4e4;
|
|
1127
1127
|
var RECEIPT_LABEL = "[relai]";
|
|
1128
1128
|
var scopes = new AsyncLocalStorage2();
|
|
1129
1129
|
var WRAPPED = /* @__PURE__ */ Symbol("relai.callEnvelope");
|
|
@@ -1171,6 +1171,7 @@ function withCallEnvelope(server, sendCommand) {
|
|
|
1171
1171
|
slot.closed = true;
|
|
1172
1172
|
const coded = toRelaiError(error);
|
|
1173
1173
|
if (scope) {
|
|
1174
|
+
if (slot.settled?.file) scope.file = slot.settled.file;
|
|
1174
1175
|
scope.receipts.push({ id: slot.settled?.id ?? "", command, outcome: coded.outcome, code: coded.code });
|
|
1175
1176
|
if (coded.partial) scope.partial = true;
|
|
1176
1177
|
if (slot.settled?.cancelRequested) scope.cancelRequested = true;
|
|
@@ -1294,8 +1295,7 @@ function finalizeResult(name, result, scope, readOnly) {
|
|
|
1294
1295
|
...scope.file || scope.room ? { target: { ...scope.room ? { room: scope.room } : {}, ...scope.file ? { file: scope.file } : {} } } : {},
|
|
1295
1296
|
...truncated ? { truncated } : {}
|
|
1296
1297
|
};
|
|
1297
|
-
const
|
|
1298
|
-
const showReceipt = isError || outcome === "unknown" || partial || scope.room !== void 0 || scope.expectFile !== void 0 || wrote && outcome !== void 0;
|
|
1298
|
+
const showReceipt = isError || outcome === "unknown" || partial || calls.length > 0 || scope.room !== void 0 || scope.expectFile !== void 0;
|
|
1299
1299
|
if (showReceipt) {
|
|
1300
1300
|
content.push({ type: "text", text: receiptLine(meta, last?.id, scope.cancelRequested) });
|
|
1301
1301
|
}
|
|
@@ -2106,7 +2106,7 @@ import { z as z10 } from "zod";
|
|
|
2106
2106
|
function register8(server, sendCommand) {
|
|
2107
2107
|
server.tool(
|
|
2108
2108
|
"manage_pages",
|
|
2109
|
-
"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
|
|
2109
|
+
"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 adds one \u2014 ONLY when the designer explicitly asks. unguard is refused (Designer-only) for a zone the designer set: lifting it is done in the panel; only a zone you added in this plugin session can be lifted. Every change shows in the designer's activity feed. Writes into a guarded page are rejected before they run, batch included.",
|
|
2110
2110
|
{
|
|
2111
2111
|
action: z10.enum(["list", "create", "rename", "delete", "set_background", "list_guards", "guard", "unguard"]),
|
|
2112
2112
|
pageId: z10.string().optional().describe("Target page (rename/delete/set_background/guard/unguard)"),
|
|
@@ -2137,13 +2137,16 @@ function register8(server, sendCommand) {
|
|
|
2137
2137
|
case "list_guards":
|
|
2138
2138
|
result = await sendCommand("get_guards", {});
|
|
2139
2139
|
break;
|
|
2140
|
-
case "guard":
|
|
2141
|
-
|
|
2142
|
-
if (!pageId) throw new Error(`${action} needs pageId`);
|
|
2140
|
+
case "guard": {
|
|
2141
|
+
if (!pageId) throw new Error("guard needs pageId");
|
|
2143
2142
|
const state = await sendCommand("get_guards", {});
|
|
2144
2143
|
const current = state.pages.filter((p) => p.guarded).map((p) => p.id);
|
|
2145
|
-
|
|
2146
|
-
|
|
2144
|
+
result = await sendCommand("set_guards", { add: [pageId], pages: [.../* @__PURE__ */ new Set([...current, pageId])] });
|
|
2145
|
+
break;
|
|
2146
|
+
}
|
|
2147
|
+
case "unguard": {
|
|
2148
|
+
if (!pageId) throw new Error("unguard needs pageId");
|
|
2149
|
+
result = await sendCommand("set_guards", { remove: [pageId] });
|
|
2147
2150
|
break;
|
|
2148
2151
|
}
|
|
2149
2152
|
}
|
|
@@ -2411,7 +2414,7 @@ function pluginTruncation(result) {
|
|
|
2411
2414
|
function register14(server, sendCommand) {
|
|
2412
2415
|
server.tool(
|
|
2413
2416
|
"execute_figma",
|
|
2414
|
-
"Run JavaScript against the Figma Plugin API inside the plugin sandbox \u2014 the escape hatch for anything the other tools don't cover. The code runs in an async function with `figma` AND `relai` in scope. relai helpers avoid the classic pitfalls: relai.text(parent, chars, {font,size,color}) loads fonts first; relai.autoLayout(direction, props) makes a hugging auto-layout frame; relai.set(node, props) applies layoutMode first and routes width/height through resize; relai.hug(node); relai.focusRing(node); await relai.page(p => ...) finds pages by content, not name; relai.query('FRAME[name^=Card] > TEXT') is a CSS-like search (types, name matchers, descendant/child, comma); relai.placeholder(node) shows a construction veil \u2014 remove with (node, false) when done. Errors carry a Hint with the fix; scripts are NOT atomic \u2014 on error, partial changes persist, so keep scripts small and idempotent. Nodes created via relai plus any node ids you RETURN are linted for silent mistakes (e.g. spread shadows without clipsContent) and come back as `warnings`. Return ALL created/mutated node ids. Work incrementally; verify with screenshot. When BUILDING UI: call get_design_system first and instantiate the file's own components / bind its variables instead of drawing raw shapes \u2014 hand-drawn near-copies of existing components read as drift to the design team. The designer can disable this tool via the plugin's 'Allow code execution' toggle.",
|
|
2417
|
+
"Run JavaScript against the Figma Plugin API inside the plugin sandbox \u2014 the escape hatch for anything the other tools don't cover. The code runs in an async function with `figma` AND `relai` in scope. relai helpers avoid the classic pitfalls: relai.text(parent, chars, {font,size,color}) loads fonts first; relai.autoLayout(direction, props) makes a hugging auto-layout frame; relai.set(node, props) applies layoutMode first and routes width/height through resize; relai.hug(node); relai.focusRing(node); await relai.page(p => ...) finds pages by content, not name; relai.query('FRAME[name^=Card] > TEXT') is a CSS-like search (types, name matchers, descendant/child, comma); relai.placeholder(node) shows a construction veil \u2014 remove with (node, false) when done. Errors carry a Hint with the fix; scripts are NOT atomic \u2014 on error, partial changes persist, so keep scripts small and idempotent. Nodes created via relai plus any node ids you RETURN are linted for silent mistakes (e.g. spread shadows without clipsContent) and come back as `warnings`. Return ALL created/mutated node ids. Work incrementally; verify with screenshot. When BUILDING UI: call get_design_system first and instantiate the file's own components / bind its variables instead of drawing raw shapes \u2014 hand-drawn near-copies of existing components read as drift to the design team. Scripts are checked after they run, not before: nodes on no-go pages or outside the scope lock come back as `violations` and warnings, and any change to Relai's own guards or settings is reverted and reported as `law`. The designer can disable this tool via the plugin's 'Allow code execution' toggle.",
|
|
2415
2418
|
{
|
|
2416
2419
|
code: z16.string().describe("JavaScript source. May use await. Return a JSON-serializable value."),
|
|
2417
2420
|
description: z16.string().optional().describe("One line shown in the plugin's activity feed describing what this does"),
|
|
@@ -4915,7 +4918,8 @@ You control Figma through ~30 consolidated tools. Pairing with the Figma plugin
|
|
|
4915
4918
|
batch_execute \u2014 run many plugin-level commands in one round-trip
|
|
4916
4919
|
execute_figma \u2014 run JavaScript against the Figma Plugin API directly
|
|
4917
4920
|
(the escape hatch when no tool fits; small incremental scripts,
|
|
4918
|
-
screenshot between steps;
|
|
4921
|
+
screenshot between steps; checked after it runs;
|
|
4922
|
+
the designer can disable it)
|
|
4919
4923
|
|
|
4920
4924
|
OPERATING PRINCIPLES:
|
|
4921
4925
|
- Inspect context BEFORE changing things; don't assume selection state
|
|
@@ -4927,13 +4931,14 @@ OPERATING PRINCIPLES:
|
|
|
4927
4931
|
- Errors name the node, its type, and the fix \u2014 read them, don't retry blindly
|
|
4928
4932
|
|
|
4929
4933
|
READING RESULTS:
|
|
4930
|
-
-
|
|
4934
|
+
- Every call that reached the plugin ends with a "[relai]" line saying
|
|
4935
|
+
whether Figma changed:
|
|
4931
4936
|
applied (the command ran) \xB7 not_applied (nothing changed) \xB7
|
|
4932
4937
|
unknown (it may have changed \u2014 read the canvas before retrying;
|
|
4933
4938
|
never resend a write blindly)
|
|
4934
4939
|
- Failures set isError and name an error code (e.g. timeout, guard_blocked);
|
|
4935
4940
|
the full receipt is in _meta["${CALL_META_KEY}"]
|
|
4936
|
-
- Text over
|
|
4941
|
+
- Text over 40,000 characters is cut; the cut notice names a local file
|
|
4937
4942
|
that holds the full result
|
|
4938
4943
|
|
|
4939
4944
|
TARGETING A FILE (every tool except join_room and get_events):
|
|
@@ -4976,7 +4981,7 @@ var design_system_first_default = '# Design-system-first building\n\nThe differe
|
|
|
4976
4981
|
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';
|
|
4977
4982
|
|
|
4978
4983
|
// ../../docs/skills/memory-and-precedents.md
|
|
4979
|
-
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 \u2014 never enact it silently:\n\n- "Never touch page X" (in conventions or a repeated ruling) \u2192 send a panel proposal: `propose_rule` (via batch_execute) with `kind: "page_guard"`, the `pageIds`, and a `reason` that QUOTES the sentence it comes from. The designer gets a card and decides there; do not wait for the verdict and do not guard on your own. Mention in your reply that you proposed it.\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
|
|
4984
|
+
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 \u2014 never enact it silently:\n\n- "Never touch page X" (in conventions or a repeated ruling) \u2192 send a panel proposal: `propose_rule` (via batch_execute) with `kind: "page_guard"`, the `pageIds`, and a `reason` that QUOTES the sentence it comes from. The designer gets a card and decides there; do not wait for the verdict and do not guard on your own. Mention in your reply that you proposed it.\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. Add one only when the designer asks; lifting a guard the designer set is refused (Designer-only) \u2014 point the designer to the panel. Mention any guard change in your reply.\n\n## Kits \u2014 the law\'s common ancestor\n\nA designer who runs one product keeps its shared law as a **kit** (`manage_rulesets`; the panel\'s KIT row) \u2014 named, stored by the plugin on their machine, spanning every file. The file\'s carried law stays the working copy; the kit is the ancestor it descends from. When speaking to the designer, say kit.\n\nThis matters because **Figma discards file-carried law on branch\u2192main merges**. At session start, if `manage_rulesets action:status` says `file-empty`, the law was wiped: say so, and restore it (`action:restore`) with the designer\'s consent \u2014 or note it happened automatically when the set\'s autoRestore is on. Other states: `drifted` means file and ancestor differ \u2014 the designer chooses pull (`restore`) or promote the file\'s version (`push`); never resolve drift on your own.\n\nWhen the designer rules something clearly product-wide (not one-file-local), offer to promote that precedent into the linked kit (`action:promote` with the precedent id) so every linked file inherits it on restore.\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. The size cap applies to rewrites too: an `update_precedent` that grows the memory past it is refused, while one that shrinks it always passes. A restore from a kit writes the conventions but skips the whole batch of seed precedents when they would not fit (`precedentsSkipped` in the result) \u2014 consolidate, then restore again. 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';
|
|
4980
4985
|
|
|
4981
4986
|
// ../../docs/skills/qa-gate.md
|
|
4982
4987
|
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';
|
|
@@ -5083,4 +5088,4 @@ export {
|
|
|
5083
5088
|
createServer,
|
|
5084
5089
|
registerPrompts
|
|
5085
5090
|
};
|
|
5086
|
-
//# sourceMappingURL=chunk-
|
|
5091
|
+
//# sourceMappingURL=chunk-C5XQ37GD.js.map
|