canicode 0.10.4 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -104,7 +104,7 @@ Override score, severity, or enable/disable individual rules:
104
104
  | Rule ID | Default Score | Default Severity |
105
105
  |---------|--------------|-----------------|
106
106
  | `fixed-size-in-auto-layout` | -6 | risk |
107
- | `missing-size-constraint` | -8 | risk |
107
+ | `missing-size-constraint` | -1 | missing-info |
108
108
 
109
109
  **Code Quality (4 rules)**
110
110
 
@@ -134,8 +134,8 @@ Override score, severity, or enable/disable individual rules:
134
134
 
135
135
  | Rule ID | Default Score | Default Severity |
136
136
  |---------|--------------|-----------------|
137
- | `missing-interaction-state` | -1 | suggestion |
138
- | `missing-prototype` *(disabled)* | -3 | missing-info |
137
+ | `missing-interaction-state` | -1 | missing-info |
138
+ | `missing-prototype` | -1 | missing-info |
139
139
  <!-- RULE_TABLE_END -->
140
140
 
141
141
  ### Example Configs
@@ -176,6 +176,42 @@ Override score, severity, or enable/disable individual rules:
176
176
 
177
177
  ---
178
178
 
179
+ ## Cursor MCP (canicode)
180
+
181
+ Configure the canicode MCP server so Cursor exposes `analyze`, `gotcha-survey`, and other tools.
182
+
183
+ ### Project config (`.cursor/mcp.json`)
184
+
185
+ Create or merge into `.cursor/mcp.json` in your repository root:
186
+
187
+ ```json
188
+ {
189
+ "mcpServers": {
190
+ "canicode": {
191
+ "command": "npx",
192
+ "args": ["-y", "--package=canicode", "canicode-mcp"]
193
+ }
194
+ }
195
+ }
196
+ ```
197
+
198
+ Use long-form `--package` (short `-p` can confuse some parsers). Set your Figma token once with `npx canicode init --token figd_…` — the MCP server reads `~/.canicode/config.json`; you do not need `FIGMA_TOKEN` in the MCP block unless your team prefers env injection.
199
+
200
+ ### Gotcha survey in Cursor
201
+
202
+ 1. Add the MCP config above and restart Cursor (or reload MCP).
203
+ 2. Run `npx canicode init --token … --cursor-skills` to install **canicode**, **canicode-gotchas**, and **canicode-roundtrip** (with `helpers.js`) under `.cursor/skills/`, and ensure the shared answer file exists at `.claude/skills/canicode-gotchas/SKILL.md` when needed (or run full `canicode init` and add `--cursor-skills`). Authoring is single-source under `.claude/skills/` in the repo; the npm build writes `skills/cursor/` (gotchas strip `# Collected Gotchas`; other skills are full copies).
204
+ 3. In chat, @-mention **canicode**, **canicode-gotchas**, or **canicode-roundtrip** as needed. For roundtrip, the Figma MCP must expose **`use_figma`** in the session — same requirement as Claude Code.
205
+
206
+ ### Manual test checklist (#407)
207
+
208
+ - [ ] MCP: Cursor shows `canicode` connected and the tools list includes `gotcha-survey` (and `analyze` if testing roundtrip Step 1).
209
+ - [ ] Figma MCP: `use_figma` is available when testing **roundtrip** (install + restart host if tools are missing).
210
+ - [ ] Calling `gotcha-survey` with a local fixture path returns JSON with `designGrade` and `questions` / `isReadyForCodeGen`.
211
+ - [ ] After the Q&A loop, `npx canicode upsert-gotcha-section …` succeeds and updates `.claude/skills/canicode-gotchas/SKILL.md`.
212
+ - [ ] Optional: @ **canicode-roundtrip** — Step 4 reads `helpers.js` from `.cursor/skills/canicode-roundtrip/helpers.js` after `canicode init --cursor-skills`.
213
+
214
+ ---
179
215
 
180
216
  ## Telemetry
181
217
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canicode",
3
- "version": "0.10.4",
3
+ "version": "0.11.0",
4
4
  "mcpName": "io.github.let-sunny/canicode",
5
5
  "description": "Lint Figma designs for AI code-gen and roundtrip the answers back into the file. CLI + MCP server.",
6
6
  "type": "module",
@@ -1,17 +1,20 @@
1
1
  ---
2
2
  name: canicode-gotchas
3
- description: Gotcha survey workflow plus accumulating per-design answers one Workflow region on top, numbered sections appended per Figma design
3
+ description: Gotcha survey (Claude Code or Cursor)Q&A workflow; answers accumulate in .claude/skills/canicode-gotchas/SKILL.md for figma-implement-design
4
4
  ---
5
5
 
6
- # CanICode Gotchas -- Design Gotcha Survey & Skill Writer
6
+ # CanICode Gotchas Design Gotcha Survey
7
7
 
8
- Run a gotcha survey on a Figma design to identify implementation pitfalls, collect developer answers, and upsert them into this skill file so code generation agents can reference them automatically. The file has two regions: the **Workflow** below (installed by `canicode init`, never overwritten) and the **Collected Gotchas** region at the bottom (one numbered section per design, replaced in place on re-runs).
8
+ Run a gotcha survey on a Figma design to collect implementation context that Figma cannot encode natively, capture developer/designer answers, and upsert them into **`.claude/skills/canicode-gotchas/SKILL.md`** so downstream `figma-implement-design` runs have annotation-ready context. In this model, rules do rule-based best-practice detection, and gotcha is the annotation output from that detection. Some gotchas come from violation rules (what is wrong and how to resolve it); others come from info-collection rules (neutral context Figma cannot represent, like interaction intent/state).
9
+
10
+ **Install location:** The workflow prose may live under `.claude/skills/canicode-gotchas/SKILL.md` (default `canicode init`) or be copied to `.cursor/skills/canicode-gotchas/SKILL.md` (`canicode init --cursor-skills`). The **authoritative gotcha store** is always **`.claude/skills/canicode-gotchas/SKILL.md`** — the CLI `upsert-gotcha-section` writes there only. In the `.claude` copy, this file has two regions: the **Workflow** below (installed by `canicode init`, never overwritten manually) and the **Collected Gotchas** region at the bottom (one numbered section per design, replaced in place on re-runs).
9
11
 
10
12
  ## Prerequisites
11
13
 
12
- - **canicode MCP server** (preferred): `claude mcp add canicode -- npx --yes --package=canicode canicode-mcp` — long-form flags only; the short-form `-y -p` collides with `claude mcp add`'s parser (#366). The MCP server reads `FIGMA_TOKEN` from `~/.canicode/config.json` or the host environment, so do **not** pass `-e FIGMA_TOKEN=…` here (#364).
13
- - **Without canicode MCP** (fallback): the `canicode gotcha-survey --json` CLI produces the same response shape no MCP installation required.
14
- - **FIGMA_TOKEN** configured for live Figma URLs
14
+ - **canicode MCP** (recommended): Register the server with your host — **Claude Code:** `claude mcp add canicode -- npx --yes --package=canicode canicode-mcp` — long-form flags only; the short-form `-y -p` collides with `claude mcp add`'s parser (#366); do **not** pass `-e FIGMA_TOKEN=…` here (#364). **Cursor / other hosts:** add `canicode-mcp` to your MCP config — see [Customization guide](https://github.com/let-sunny/canicode/blob/main/docs/CUSTOMIZATION.md#cursor-mcp-canicode) (`~/.cursor/mcp.json` or project `.cursor/mcp.json`). The MCP server reads `FIGMA_TOKEN` from `~/.canicode/config.json` or the environment.
15
+ - **Without canicode MCP** (fallback): `npx canicode gotcha-survey "<input>" --json` same JSON shape as the MCP tool.
16
+ - **FIGMA_TOKEN** configured for live Figma URLs.
17
+ - **Gotcha destination on disk:** `.claude/skills/canicode-gotchas/SKILL.md` must exist before upsert — run `npx canicode init --token …` (add `--cursor-skills` if you also want the workflow file under `.cursor/skills/`).
15
18
 
16
19
  ## Workflow
17
20
 
@@ -38,12 +41,12 @@ Either channel returns:
38
41
 
39
42
  If `isReadyForCodeGen` is `true` or `questions` is empty:
40
43
  - Tell the user: "This design scored **{designGrade}** and is ready for code generation — no gotchas to resolve."
41
- - Do NOT write to the skill file.
44
+ - Do NOT write to `.claude/skills/canicode-gotchas/SKILL.md`.
42
45
  - Stop here.
43
46
 
44
47
  ### Step 3: Present questions to the user
45
48
 
46
- The survey response carries a pre-computed `groupedQuestions.groups[].batches[]` shape so the SKILL never has to sort, partition, or maintain a batchable-rule whitelist in prose. The sort key, `_no-source` sentinel, and batchable-rule list all live in `core/gotcha/group-and-batch-questions.ts` with vitest coverage (per ADR-016). Iterate over it:
49
+ The survey response carries a pre-computed `groupedQuestions.groups[].batches[]` shape so this skill never has to sort, partition, or maintain a batchable-rule whitelist in prose. The sort key, `_no-source` sentinel, and batchable-rule list all live in `core/gotcha/group-and-batch-questions.ts` with vitest coverage (per ADR-016). Iterate over it:
47
50
 
48
51
  For every `batch` in `groupedQuestions.groups.flatMap((g) => g.batches)`:
49
52
 
@@ -90,19 +93,19 @@ When applying the batched answer, expand back to per-question records in Step 4
90
93
 
91
94
  ### Step 4: Upsert the gotcha section
92
95
 
93
- After collecting all answers, **upsert** this design's section into the `# Collected Gotchas` region at the bottom of this file:
96
+ After collecting all answers, **upsert** this design's section into the `# Collected Gotchas` region at the bottom of:
94
97
 
95
98
  ```
96
99
  .claude/skills/canicode-gotchas/SKILL.md
97
100
  ```
98
101
 
99
- This file goes in the **user's project** (current working directory), NOT in the canicode repo. The Workflow region above **must never be modified** — only the `# Collected Gotchas` region below is touched.
102
+ That path is in the **user's project** (current working directory), NOT in the canicode repo. If you are following this workflow from a copy under `.cursor/skills/`, still upsert into **`.claude/skills/...`** only — never write gotcha answers into the `.cursor` copy. The Workflow region in the `.claude` file **must never be modified manually** — only the `# Collected Gotchas` region is touched (via the CLI below).
100
103
 
101
104
  #### Step 4a: Use the `designKey` from the survey response
102
105
 
103
106
  `designKey` uniquely identifies the design so re-running on the same URL replaces the existing section in place. The survey response carries it on `survey.designKey` — read it directly. Do **not** parse the input URL in prose.
104
107
 
105
- The `core/contracts/design-key.ts` helper (`computeDesignKey`) handles every shape with vitest coverage so the SKILL stays ADR-016-compliant:
108
+ The `core/contracts/design-key.ts` helper (`computeDesignKey`) handles every shape with vitest coverage so this workflow stays ADR-016-compliant:
106
109
 
107
110
  - **Figma URL** → `<fileKey>#<nodeId>` with `-` → `:` normalization on the nodeId. Example: `https://figma.com/design/abc123XYZ/My-File?node-id=42-100&t=ref` → `designKey = "abc123XYZ#42:100"`. Trailing query parameters (`?t=...`, `?mode=...`) are dropped.
108
111
  - **Figma URL without `node-id`** → just `<fileKey>` (file-level key).
@@ -110,7 +113,7 @@ The `core/contracts/design-key.ts` helper (`computeDesignKey`) handles every sha
110
113
 
111
114
  #### Step 4b: Upsert via the canicode CLI
112
115
 
113
- File-state detection (4-way: missing / valid / missing-heading / clobbered) and section walking (find existing `## #NNN — ...` by `Design key` substring, otherwise compute the next monotonic zero-padded NNN) are deterministic markdown operations and live in `core/gotcha/upsert-gotcha-section.ts` with vitest coverage — the SKILL never re-implements them in prose (per ADR-016).
116
+ File-state detection (4-way: missing / valid / missing-heading / clobbered) and section walking (find existing `## #NNN — ...` by `Design key` substring, otherwise compute the next monotonic zero-padded NNN) are deterministic markdown operations and live in `core/gotcha/upsert-gotcha-section.ts` with vitest coverage — do not re-implement them in prose (per ADR-016).
114
117
 
115
118
  Render the per-design section markdown using the **Output Template** below with the literal string `{{SECTION_NUMBER}}` in the header (the CLI substitutes the right NNN for you — preserves it on replace, computes the next monotonic value on append). Then invoke:
116
119
 
@@ -168,7 +171,7 @@ Each per-design section in the `# Collected Gotchas` region has this exact shape
168
171
  | `analyzedAt` | Current timestamp (ISO 8601) |
169
172
  | `ruleId` | `ruleId` from each question |
170
173
  | `nodeName` | `nodeName` from each question |
171
- | `severity` | `severity` from each question (blocking / risk) |
174
+ | `severity` | `severity` from each question (blocking / risk / missing-info — the last surfaces only for info-collection rules per #406) |
172
175
  | `nodeId` | `nodeId` from each question |
173
176
  | `instanceContext` | When present on the question, copy `parentInstanceNodeId`, `sourceNodeId`, `sourceComponentId`, `sourceComponentName` into the bullet above (roundtrip / Plugin apply) |
174
177
  | `question` | `question` from each question |
@@ -186,7 +189,7 @@ This ensures the code generation agent knows the gotcha exists even if no answer
186
189
 
187
190
  ## Edge Cases
188
191
 
189
- - **No questions returned**: The design is ready for code generation. Inform the user and stop (Step 2). Do not touch the file.
192
+ - **No questions returned**: The design is ready for code generation. Inform the user and stop (Step 2). Do not touch `.claude/skills/canicode-gotchas/SKILL.md`.
190
193
  - **Re-run on the same design**: Replace that design's section in place (matched by `Design key`) — preserve the original `#NNN` number. Do NOT append a duplicate.
191
194
  - **Re-run on a different design**: Append a new section with the next `#NNN`. Prior designs' sections are untouched.
192
195
  - **Workflow region**: Never modified. If you notice the Workflow region has been edited by the user, leave their edits alone — only the `# Collected Gotchas` region is under skill control.
@@ -10,8 +10,8 @@ Orchestrate the full design-to-code roundtrip: analyze a Figma design for readin
10
10
 
11
11
  ## Prerequisites
12
12
 
13
- - **Figma MCP server** installed (provides `get_design_context`, `get_screenshot`, `use_figma`, and other Figma tools) — REQUIRED, there is no CLI fallback for `use_figma`
14
- - **canicode MCP server** (preferred): `claude mcp add canicode -- npx --yes --package=canicode canicode-mcp` — long-form flags only; the short-form `-y -p` collides with `claude mcp add`'s parser (#366). The MCP server reads `FIGMA_TOKEN` from `~/.canicode/config.json` or the host environment, so do **not** pass `-e FIGMA_TOKEN=…` here (#364).
13
+ - **Figma MCP server** installed (provides `get_design_context`, `get_screenshot`, `use_figma`, and other Figma tools) — REQUIRED, there is no CLI fallback for `use_figma`. Register it with your host (e.g. Claude Code: `claude mcp add -s project -t http figma https://mcp.figma.com/mcp`; Cursor: add the Figma MCP entry per host docs / project `.mcp.json`).
14
+ - **canicode MCP** (preferred): **Claude Code:** `claude mcp add canicode -- npx --yes --package=canicode canicode-mcp` — long-form flags only; short `-y -p` collides with `claude mcp add`'s parser (#366); do **not** pass `-e FIGMA_TOKEN=…` here (#364). **Cursor / other hosts:** add `canicode-mcp` to MCP config — see [Customization guide](https://github.com/let-sunny/canicode/blob/main/docs/CUSTOMIZATION.md#cursor-mcp-canicode). The server reads `FIGMA_TOKEN` from `~/.canicode/config.json` or the environment.
15
15
  - **Without canicode MCP** (fallback): Steps 1 (analyze) and 3 (gotcha-survey) shell out to `npx canicode <command> --json` — same JSON shape as the MCP tools. Step 4 (apply to Figma) still requires Figma MCP `use_figma`.
16
16
  - **FIGMA_TOKEN** configured for live Figma URLs
17
17
  - **Figma Full seat + file edit permission** (required for `use_figma` to modify the design)
@@ -20,13 +20,13 @@ Orchestrate the full design-to-code roundtrip: analyze a Figma design for readin
20
20
 
21
21
  ### Step 0: Verify Figma MCP tools are loaded
22
22
 
23
- Before Step 1, verify that `use_figma` is callable in **this** session — not merely listed in `.mcp.json`. Newly registered MCP servers (e.g. via `claude mcp add -s project -t http figma https://mcp.figma.com/mcp`) require a Claude Code restart to load their tools; reading `.mcp.json` is not a substitute for checking the live tool list you have access to right now.
23
+ Before Step 1, verify that `use_figma` is callable in **this** session — not merely listed in `.mcp.json`. Newly registered MCP servers require a **host restart or MCP reload** so tools appear (e.g. Claude Code: restart after `claude mcp add …`; Cursor: restart Cursor or reload MCP after editing `.cursor/mcp.json`). Reading `.mcp.json` is not a substitute for checking the live tool list you have access to right now.
24
24
 
25
25
  If `use_figma` is unavailable in the current session, **Do NOT proceed to Step 1**. Steps 1 (analyze) and 3 (gotcha-survey) spend real Figma API calls and 5–15 minutes of human survey time before Step 4 would otherwise discover `use_figma` is missing. Halt immediately and tell the user:
26
26
 
27
- 1. Confirm `.mcp.json` registers the Figma MCP entry (e.g. `figma` under `mcpServers`).
28
- 2. Restart Claude Code so the newly registered tools load.
29
- 3. Re-invoke `/canicode-roundtrip <url>`.
27
+ 1. Confirm `.mcp.json` (project or user) registers the Figma MCP entry (e.g. `figma` under `mcpServers`).
28
+ 2. Restart the IDE / agent host (or reload MCP) so the newly registered tools load.
29
+ 3. Re-invoke the roundtrip (Claude Code slash command `/canicode-roundtrip`, or Cursor: @ **canicode-roundtrip** with the Figma URL).
30
30
 
31
31
  See the Edge Case **No Figma MCP server** below for the one-way fallback when Figma MCP genuinely cannot be installed — the precheck above is for the common "installed but not restarted" case, not a replacement for that fallback.
32
32
 
@@ -227,7 +227,7 @@ The helper walks the tiers in order; variable binding is an alternative writeFn
227
227
 
228
228
  1. **Scene (instance) node** — `await figma.getNodeByIdAsync(question.nodeId)` and apply the write inside `try/catch`. If the answer names a design-system token (`{ variable: "name" }`), the helper calls `setBoundVariable` / `setBoundVariableForPaint` first and that binding bypasses the override gate — otherwise it performs a raw-value write. Success → done (local change only). Mark result with ✅.
229
229
  2. **Definition (source) node — opt-in only** — Runs only when the orchestrator passes `allowDefinitionWrite: true` on the helper context (after a batch-level confirmation naming the source component AND the propagation set). When the flag is off (the ADR-012 default), a recognized instance-override failure (override-error or silent-ignore) short-circuits here and routes directly to tier 3 — the definition node is never touched. When the flag is on, the helper loads `question.sourceChildId` (or walks `getMainComponentAsync()` if needed) and writes using the same bind-if-token-else-raw shape as tier 1; changes propagate to **every non-overridden instance** in the file (Experiment 10). Mark result with 🌐.
230
- 3. **Annotation fallback — default path** — Under the ADR-012 default this is where override-errors and silent-ignores land: the helper annotates the **scene** node with markdown that names the source component as the recommended write target and notes that the instance kept its current value to avoid unintended fan-out. When `allowDefinitionWrite` is on, this tier also catches any definition-tier throw (e.g. Experiment 10 external-library read-only case, `mainComponent.remote === true` / *"Cannot write to internal and read-only node"*, and the `mainComponent === null` branch where `getMainComponentAsync()` resolves with no definition to name — see Experiment 11 / ADR-011). Either way, mark result with 📝.
230
+ 3. **Annotation fallback — default path** — Under the ADR-012 default this is where override-errors and silent-ignores land: the helper annotates the **scene** node with markdown that names the actual no-op (the property silently ignored the write or the override was rejected) and points to the source component as the correct write target. When `allowDefinitionWrite` is on, this tier also catches any definition-tier throw (e.g. Experiment 10 external-library read-only case, `mainComponent.remote === true` / *"Cannot write to internal and read-only node"*, and the `mainComponent === null` branch where `getMainComponentAsync()` resolves with no definition to name — see Experiment 11 / ADR-011). Either way, mark result with 📝.
231
231
 
232
232
  **Confirmation is a batch-level concern — and only needed when opting in.** A `use_figma` call runs one JavaScript batch and cannot pause mid-batch for user input. Under the ADR-012 default (`allowDefinitionWrite: false`), no propagation happens, so no confirmation is required — override-errors annotate and move on. The orchestrator sets `allowDefinitionWrite: true` only after enumerating the likely propagation set to the user up-front and collecting **one confirmation for the whole batch** that names the source component(s) and the affected instance set. When describing impact, note that the write reaches every **non-overridden** instance — any instance with a local override for the same property keeps its override. The helper below never prompts — it assumes that if the flag is on, confirmation already happened.
233
233
 
@@ -276,11 +276,11 @@ Branches on `probe`:
276
276
 
277
277
  The probe is read-only and idempotent; running it before the picker adds one round-trip but saves the user a confusing "I opted in, why did I get annotations?" moment that #342 surfaced live on Simple Design System (Community).
278
278
 
279
- **Shared helpers (bundled)** — the deterministic helpers live in TypeScript at `src/core/roundtrip/*.ts` and are bundled to a single IIFE at `.claude/skills/canicode-roundtrip/helpers.js`. `use_figma` only accepts a self-contained JS string, so the source of truth is TypeScript (with vitest coverage) and the bundle is the delivery artifact.
279
+ **Shared helpers (bundled)** — the deterministic helpers live in TypeScript at `src/core/roundtrip/*.ts` and are bundled to a single IIFE shipped next to this skill as `helpers.js`. `use_figma` only accepts a self-contained JS string, so the source of truth is TypeScript (with vitest coverage) and the bundle is the delivery artifact.
280
280
 
281
281
  **Usage in a roundtrip session:**
282
282
 
283
- 1. Read `.claude/skills/canicode-roundtrip/helpers.js` once at the start of Step 4.
283
+ 1. Read `helpers.js` from the same directory as this skill once at the start of Step 4 — typically `.claude/skills/canicode-roundtrip/helpers.js` (Claude Code / default `canicode init`) or `.cursor/skills/canicode-roundtrip/helpers.js` (Cursor with `canicode init --cursor-skills`).
284
284
  2. Prepend its contents verbatim at the top of every `use_figma` batch body — it registers a single global `CanICodeRoundtrip`.
285
285
  3. Reference exposed globals as `CanICodeRoundtrip.*`:
286
286
  - `stripAnnotations(annotations)` — normalizes the D1 label/labelMarkdown mutex on readback.
@@ -402,7 +402,7 @@ Notes:
402
402
 
403
403
  #### Strategy D: Auto-fix lower-severity issues from analysis
404
404
 
405
- The gotcha survey covers only blocking/risk severity. Lower-severity rules appear in `analyzeResult.issues[]` without a survey question. Each issue carries the same pre-computed fields (`applyStrategy`, `targetProperty`, `annotationProperties`, `suggestedName`, `isInstanceChild`, `sourceChildId`). The bundled helper handles the loop, the filter (`applyStrategy === "auto-fix"`), the naming-vs-annotation branch, and the per-issue outcome accumulator in one call:
405
+ The gotcha survey covers blocking/risk severity plus `missing-info` severity from info-collection rules (#406 — currently `missing-prototype`, `missing-interaction-state`). All other lower-severity rules appear in `analyzeResult.issues[]` without a survey question. Each issue carries the same pre-computed fields (`applyStrategy`, `targetProperty`, `annotationProperties`, `suggestedName`, `isInstanceChild`, `sourceChildId`). The bundled helper handles the loop, the filter (`applyStrategy === "auto-fix"`), the naming-vs-annotation branch, and the per-issue outcome accumulator in one call:
406
406
 
407
407
  ```javascript
408
408
  const outcomes = await CanICodeRoundtrip.applyAutoFixes(analyzeResult.issues, { categories });
@@ -577,6 +577,7 @@ Follow the **figma-implement-design** skill workflow to generate code from the F
577
577
 
578
578
  - Gotchas with severity **blocking** MUST be addressed — the design cannot be implemented correctly without this information
579
579
  - Gotchas with severity **risk** SHOULD be addressed — they indicate potential issues that will surface later
580
+ - Gotchas with severity **missing-info** from info-collection rules (`purpose === "info-collection"`, e.g. `missing-prototype`, `missing-interaction-state`) are annotation-primary (#406): the answer describes implementation context Figma cannot encode (click target, state variants). Treat them as code-generation context rather than violations to fix — the rule's score impact is minimal by design
580
581
  - Reference the specific node IDs from gotcha answers to locate the affected elements in the design
581
582
  - Pass the Figma URL or `survey.designKey` to `figma-implement-design` so it can grep the matching `## #NNN — …` section in `.claude/skills/canicode-gotchas/SKILL.md` instead of reading the whole accumulated file
582
583
 
@@ -613,5 +614,5 @@ Code: <files generated / next-step pointer from figma-implement-design>
613
614
  - **Partial gotcha answers**: Apply only the answered questions. Skipped/n/a questions are neither applied nor annotated.
614
615
  - **use_figma call fails for a node**: Report the error for that specific node, continue with other nodes. Failed property modifications become annotations so the context is not lost.
615
616
  - **Re-analyze shows new issues**: Only address issues from the original gotcha survey. New issues may appear due to structural changes — report them but do not re-enter the gotcha loop.
616
- - **Very large design (many gotchas)**: The gotcha survey already deduplicates sibling nodes and filters to blocking/risk severity only. If there are still many questions, ask the user if they want to focus on blocking issues only.
617
+ - **Very large design (many gotchas)**: The gotcha survey already deduplicates sibling nodes and filters to blocking/risk plus `missing-info` from info-collection rules (#406). If there are still many questions, ask the user if they want to focus on blocking issues only.
617
618
  - **External library components**: Applies only when the orchestrator has set `allowDefinitionWrite: true`. Experiment 10's observed case is `getMainComponentAsync()` resolving with `mainComponent.remote === true` — writes then throw *"Cannot write to internal and read-only node"*. The `mainComponent === null` case is documented in the Plugin API but was not reproduced live in Experiment 10; Experiment 11 (#309) unit-test-covers the helper's routing for that branch (override-error + no `sourceChildId` → annotate with `could not apply automatically:` markdown — see ADR-011 Verification), so the code path is regression-locked while live Figma reproduction remains a manual fixture-seeding follow-up. Under the default (`allowDefinitionWrite: false`), the definition write never fires and this throw cannot surface. **The pre-flight `probeDefinitionWritability` (#357) detects both branches up-front** so the Definition write picker can drop the opt-in option entirely when every candidate is unwritable, saving the user a wasted decision before the runtime fallback kicks in.
@@ -96,7 +96,7 @@ ${footer}`;
96
96
  if (ctx.categories) {
97
97
  upsertCanicodeAnnotation(ctx.scene, {
98
98
  ruleId: ctx.question.ruleId,
99
- markdown: `Apply this fix on the source component **${componentName}** to share across all instances. This instance kept its current value to avoid unintended fan-out. Re-run with \`allowDefinitionWrite: true\` to propagate.`,
99
+ markdown: `The fix below could not be applied on this instance child \u2014 the property silently ignored the write or the override was rejected. Apply it on the source component **${componentName}** so every instance picks it up. Re-run with \`allowDefinitionWrite: true\` to let canicode propagate automatically.`,
100
100
  categoryId: ctx.categories.fallback
101
101
  });
102
102
  }
@@ -0,0 +1,76 @@
1
+ ---
2
+ name: canicode
3
+ description: Analyze Figma designs for development-friendliness and AI-friendliness scores
4
+ ---
5
+
6
+ # CanICode -- Figma Design Analysis
7
+
8
+ Analyze Figma design files to score how development-friendly and AI-friendly they are. Produces actionable reports with specific issues and fix suggestions.
9
+
10
+ ## Prerequisites
11
+
12
+ This skill works with either channel — the CLI or the canicode MCP server. Both return the same analysis; pick whichever is already set up. Requires either:
13
+ - A **saved fixture** (from `canicode calibrate-save-fixture`)
14
+ - A **FIGMA_TOKEN** for live Figma URLs
15
+
16
+ ## How to Analyze
17
+
18
+ ### From a Figma URL
19
+
20
+ ```bash
21
+ npx canicode analyze "https://www.figma.com/design/ABC123/MyDesign?node-id=1-234" --token YOUR_TOKEN
22
+ ```
23
+
24
+ Or if FIGMA_TOKEN is set in environment:
25
+ ```bash
26
+ npx canicode analyze "https://www.figma.com/design/ABC123/MyDesign?node-id=1-234"
27
+ ```
28
+
29
+ ### From a saved fixture
30
+
31
+ ```bash
32
+ npx canicode analyze fixtures/my-design
33
+ ```
34
+
35
+ ### Save a fixture for offline analysis
36
+
37
+ ```bash
38
+ npx canicode calibrate-save-fixture "https://www.figma.com/design/ABC123/MyDesign?node-id=1-234" --output fixtures/my-design
39
+ ```
40
+
41
+ ## Analysis Options
42
+
43
+ ### Presets
44
+ - `--preset relaxed` — Downgrades blocking to risk, reduces scores by 50%
45
+ - `--preset dev-friendly` — Enables only pixel-critical and responsive-critical rules, disables the rest
46
+ - `--preset ai-ready` — Sets pixel-critical and token-management rule scores to 150% of defaults
47
+ - `--preset strict` — Increases all scores by 150%
48
+
49
+ ### Config overrides
50
+ ```bash
51
+ npx canicode analyze <input> --config ./my-config.json
52
+ ```
53
+
54
+ ### JSON output
55
+ ```bash
56
+ npx canicode analyze <input> --json
57
+ ```
58
+
59
+ ### Via MCP (when `canicode-mcp` is installed)
60
+
61
+ If the user has the canicode MCP server installed, prefer the MCP tool — it avoids the `npx` spawn overhead and reuses a warm Figma client:
62
+
63
+ ```
64
+ analyze({ input: "<figma-url-or-fixture-path>" })
65
+ ```
66
+
67
+ Options mirror the CLI: `preset`, `token`, `config`, `targetNodeId`, `json`. The `json` response field matches `npx canicode analyze --json` byte-for-byte, so downstream code can parse either source.
68
+
69
+ ## What It Reports
70
+
71
+ 16 rules across 6 categories: Pixel Critical, Responsive Critical, Code Quality, Token Management, Interaction, Semantic.
72
+
73
+ Each issue includes:
74
+ - Rule ID and severity (blocking / risk / missing-info / suggestion)
75
+ - Affected node with Figma deep link
76
+ - Why it matters, impact, and how to fix
@@ -0,0 +1,199 @@
1
+ ---
2
+ name: canicode-gotchas
3
+ description: Gotcha survey (Claude Code or Cursor) — Q&A workflow; answers accumulate in .claude/skills/canicode-gotchas/SKILL.md for figma-implement-design
4
+ ---
5
+
6
+ # CanICode Gotchas — Design Gotcha Survey
7
+
8
+ Run a gotcha survey on a Figma design to collect implementation context that Figma cannot encode natively, capture developer/designer answers, and upsert them into **`.claude/skills/canicode-gotchas/SKILL.md`** so downstream `figma-implement-design` runs have annotation-ready context. In this model, rules do rule-based best-practice detection, and gotcha is the annotation output from that detection. Some gotchas come from violation rules (what is wrong and how to resolve it); others come from info-collection rules (neutral context Figma cannot represent, like interaction intent/state).
9
+
10
+ **Install location:** The workflow prose may live under `.claude/skills/canicode-gotchas/SKILL.md` (default `canicode init`) or be copied to `.cursor/skills/canicode-gotchas/SKILL.md` (`canicode init --cursor-skills`). The **authoritative gotcha store** is always **`.claude/skills/canicode-gotchas/SKILL.md`** — the CLI `upsert-gotcha-section` writes there only. In the `.claude` copy, this file has two regions: the **Workflow** below (installed by `canicode init`, never overwritten manually) and the **Collected Gotchas** region at the bottom (one numbered section per design, replaced in place on re-runs).
11
+
12
+ ## Prerequisites
13
+
14
+ - **canicode MCP** (recommended): Register the server with your host — **Claude Code:** `claude mcp add canicode -- npx --yes --package=canicode canicode-mcp` — long-form flags only; the short-form `-y -p` collides with `claude mcp add`'s parser (#366); do **not** pass `-e FIGMA_TOKEN=…` here (#364). **Cursor / other hosts:** add `canicode-mcp` to your MCP config — see [Customization guide](https://github.com/let-sunny/canicode/blob/main/docs/CUSTOMIZATION.md#cursor-mcp-canicode) (`~/.cursor/mcp.json` or project `.cursor/mcp.json`). The MCP server reads `FIGMA_TOKEN` from `~/.canicode/config.json` or the environment.
15
+ - **Without canicode MCP** (fallback): `npx canicode gotcha-survey "<input>" --json` — same JSON shape as the MCP tool.
16
+ - **FIGMA_TOKEN** configured for live Figma URLs.
17
+ - **Gotcha destination on disk:** `.claude/skills/canicode-gotchas/SKILL.md` must exist before upsert — run `npx canicode init --token …` (add `--cursor-skills` if you also want the workflow file under `.cursor/skills/`).
18
+
19
+ ## Workflow
20
+
21
+ ### Step 1: Run the gotcha survey
22
+
23
+ If the `gotcha-survey` MCP tool is available, call it with the user's Figma URL:
24
+
25
+ ```
26
+ gotcha-survey({ input: "<figma-url-or-fixture-path>" })
27
+ ```
28
+
29
+ **Without canicode MCP** — shell out to the CLI. The `--json` output parses identically:
30
+
31
+ ```bash
32
+ npx canicode gotcha-survey "<figma-url-or-fixture-path>" --json
33
+ ```
34
+
35
+ Either channel returns:
36
+ - `designGrade`: overall grade (S, A+, A, B+, B, C+, C, D, F)
37
+ - `isReadyForCodeGen`: whether the design can be implemented without gotchas
38
+ - `questions`: array of gotcha questions (may be empty)
39
+
40
+ ### Step 2: Check if survey is needed
41
+
42
+ If `isReadyForCodeGen` is `true` or `questions` is empty:
43
+ - Tell the user: "This design scored **{designGrade}** and is ready for code generation — no gotchas to resolve."
44
+ - Do NOT write to `.claude/skills/canicode-gotchas/SKILL.md`.
45
+ - Stop here.
46
+
47
+ ### Step 3: Present questions to the user
48
+
49
+ The survey response carries a pre-computed `groupedQuestions.groups[].batches[]` shape so this skill never has to sort, partition, or maintain a batchable-rule whitelist in prose. The sort key, `_no-source` sentinel, and batchable-rule list all live in `core/gotcha/group-and-batch-questions.ts` with vitest coverage (per ADR-016). Iterate over it:
50
+
51
+ For every `batch` in `groupedQuestions.groups.flatMap((g) => g.batches)`:
52
+
53
+ - **Single-question batch (`batch.questions.length === 1`)** — render the standard prompt for `batch.questions[0]`:
54
+
55
+ ```
56
+ **[{severity}] {ruleId}** — node: {nodeName}
57
+
58
+ {question}
59
+
60
+ > Hint: {hint}
61
+ > Example: {example}
62
+ ```
63
+
64
+ - **Batch of N ≥ 2 with `batch.batchable === true`** (#369) — render one shared prompt covering every member:
65
+
66
+ ```
67
+ **[{severity}] {ruleId}** — {batch.questions.length} instances:
68
+ - {nodeName₁}
69
+ - {nodeName₂}
70
+ - …
71
+
72
+ {sharedQuestionPrompt}
73
+
74
+ Reply with one answer to apply to all {batch.questions.length}, or **split** to answer each individually.
75
+
76
+ > Hint: {hint}
77
+ > Example: {example}
78
+ ```
79
+
80
+ Where `sharedQuestionPrompt` reuses the rule's `question` text with the per-node noun replaced by the rule's plural noun (e.g. "These layers all use FILL sizing without min/max constraints. What size boundaries should they share?" instead of repeating the singular phrasing N times).
81
+
82
+ - **Any batch with `batch.batchable === false`** is always rendered as a single-question prompt — the helper guarantees `questions.length === 1` for those (identity-typed answers like `non-semantic-name`, structural-mod rules).
83
+
84
+ Wait for the user's answer before moving to the next batch. The user may:
85
+ - Answer the question / batch directly
86
+ - Say **split** (batch only) to fall back to per-question prompting for that batch
87
+ - Say **skip** to skip the question / the entire batch
88
+ - Say **n/a** if the question / the entire batch is not applicable
89
+
90
+ When applying the batched answer, expand back to per-question records in Step 4 — the gotcha section format stores one record per `nodeId`.
91
+
92
+ > The `groupedQuestions.groups[].instanceContext` field exists for the `canicode-roundtrip` SKILL's "Instance note" hoist (#370). This skill ignores it — every record gets its own `Instance context` bullet in Step 4 anyway.
93
+
94
+ ### Step 4: Upsert the gotcha section
95
+
96
+ After collecting all answers, **upsert** this design's section into the `# Collected Gotchas` region at the bottom of:
97
+
98
+ ```
99
+ .claude/skills/canicode-gotchas/SKILL.md
100
+ ```
101
+
102
+ That path is in the **user's project** (current working directory), NOT in the canicode repo. If you are following this workflow from a copy under `.cursor/skills/`, still upsert into **`.claude/skills/...`** only — never write gotcha answers into the `.cursor` copy. The Workflow region in the `.claude` file **must never be modified manually** — only the `# Collected Gotchas` region is touched (via the CLI below).
103
+
104
+ #### Step 4a: Use the `designKey` from the survey response
105
+
106
+ `designKey` uniquely identifies the design so re-running on the same URL replaces the existing section in place. The survey response carries it on `survey.designKey` — read it directly. Do **not** parse the input URL in prose.
107
+
108
+ The `core/contracts/design-key.ts` helper (`computeDesignKey`) handles every shape with vitest coverage so this workflow stays ADR-016-compliant:
109
+
110
+ - **Figma URL** → `<fileKey>#<nodeId>` with `-` → `:` normalization on the nodeId. Example: `https://figma.com/design/abc123XYZ/My-File?node-id=42-100&t=ref` → `designKey = "abc123XYZ#42:100"`. Trailing query parameters (`?t=...`, `?mode=...`) are dropped.
111
+ - **Figma URL without `node-id`** → just `<fileKey>` (file-level key).
112
+ - **Fixture path / JSON file** → absolute path.
113
+
114
+ #### Step 4b: Upsert via the canicode CLI
115
+
116
+ File-state detection (4-way: missing / valid / missing-heading / clobbered) and section walking (find existing `## #NNN — ...` by `Design key` substring, otherwise compute the next monotonic zero-padded NNN) are deterministic markdown operations and live in `core/gotcha/upsert-gotcha-section.ts` with vitest coverage — do not re-implement them in prose (per ADR-016).
117
+
118
+ Render the per-design section markdown using the **Output Template** below with the literal string `{{SECTION_NUMBER}}` in the header (the CLI substitutes the right NNN for you — preserves it on replace, computes the next monotonic value on append). Then invoke:
119
+
120
+ ```bash
121
+ npx canicode upsert-gotcha-section \
122
+ --file .claude/skills/canicode-gotchas/SKILL.md \
123
+ --design-key "<designKey from Step 4a>" \
124
+ --section - # then pipe the rendered section markdown through stdin
125
+ ```
126
+
127
+ The CLI prints a JSON result `{ state, action, sectionNumber, wrote, userMessage }`:
128
+
129
+ - `wrote: true` → success. `action` is `"replace"` (preserved `sectionNumber`) or `"append"` (next monotonic `sectionNumber`).
130
+ - `wrote: false` with `state: "missing"` → tell the user: *"Your gotchas SKILL.md is not installed yet. Run `canicode init` first, then re-invoke this skill."* Stop here.
131
+ - `wrote: false` with `state: "clobbered"` → tell the user: *"Your gotchas SKILL.md is missing the canicode YAML frontmatter (pre-#340 single-design clobber). Run `canicode init --force` to restore the workflow, then re-run this survey — your answers will land in a clean numbered section."* Stop here.
132
+ - `wrote: true` with `state: "missing-heading"` → silent recovery. The CLI injected the `# Collected Gotchas` heading and appended the section; the workflow region above is untouched.
133
+
134
+ The Workflow region above must never be touched. Do NOT copy Workflow prose into the per-design section; the section only carries metadata + gotcha answers.
135
+
136
+ ## Output Template
137
+
138
+ Each per-design section in the `# Collected Gotchas` region has this exact shape:
139
+
140
+ ````markdown
141
+ ## #NNN — {designName} — {YYYY-MM-DD}
142
+
143
+ - **Figma URL**: {figmaUrl}
144
+ - **Design key**: {designKey}
145
+ - **Grade**: {designGrade}
146
+ - **Analyzed at**: {analyzedAt}
147
+
148
+ ### Gotchas
149
+
150
+ #### {ruleId} — {nodeName}
151
+
152
+ - **Severity**: {severity}
153
+ - **Node ID**: {nodeId}
154
+ - **Instance context** (omit this bullet if `instanceContext` was not in the survey question): parent instance `parentInstanceNodeId`, source node `sourceNodeId`, component `sourceComponentName` / `sourceComponentId` when present — roundtrip apply uses this to write on the source definition when instance overrides fail.
155
+ - **Question**: {question}
156
+ - **Answer**: {userAnswer}
157
+
158
+ (repeat for each question)
159
+ ````
160
+
161
+ ### Field mapping
162
+
163
+ | Field | Source |
164
+ |-------|--------|
165
+ | `NNN` | `sectionNumber` — zero-padded three-digit index. Preserved on re-run, incremented on append. |
166
+ | `designName` | Figma file name or fixture name from the input |
167
+ | `YYYY-MM-DD` | Today's date (the day you are running the survey) |
168
+ | `figmaUrl` | The input URL or fixture path provided by the user |
169
+ | `designKey` | `survey.designKey` from the gotcha-survey response (see Step 4a) |
170
+ | `designGrade` | `designGrade` from gotcha-survey response |
171
+ | `analyzedAt` | Current timestamp (ISO 8601) |
172
+ | `ruleId` | `ruleId` from each question |
173
+ | `nodeName` | `nodeName` from each question |
174
+ | `severity` | `severity` from each question (blocking / risk / missing-info — the last surfaces only for info-collection rules per #406) |
175
+ | `nodeId` | `nodeId` from each question |
176
+ | `instanceContext` | When present on the question, copy `parentInstanceNodeId`, `sourceNodeId`, `sourceComponentId`, `sourceComponentName` into the bullet above (roundtrip / Plugin apply) |
177
+ | `question` | `question` from each question |
178
+ | `userAnswer` | The answer collected from the user in Step 3 |
179
+
180
+ ### Skipped questions
181
+
182
+ If the user skipped a question or said "n/a", still include it in the section with:
183
+
184
+ ```markdown
185
+ - **Answer**: _(skipped)_
186
+ ```
187
+
188
+ This ensures the code generation agent knows the gotcha exists even if no answer was provided.
189
+
190
+ ## Edge Cases
191
+
192
+ - **No questions returned**: The design is ready for code generation. Inform the user and stop (Step 2). Do not touch `.claude/skills/canicode-gotchas/SKILL.md`.
193
+ - **Re-run on the same design**: Replace that design's section in place (matched by `Design key`) — preserve the original `#NNN` number. Do NOT append a duplicate.
194
+ - **Re-run on a different design**: Append a new section with the next `#NNN`. Prior designs' sections are untouched.
195
+ - **Workflow region**: Never modified. If you notice the Workflow region has been edited by the user, leave their edits alone — only the `# Collected Gotchas` region is under skill control.
196
+ - **Pre-#340 clobbered file** (the YAML frontmatter was rewritten to a per-design variant, so the canonical `canicode-gotchas` frontmatter is missing): tell the user to run `canicode init --force` to restore the workflow, then re-run the survey. The prior single-design content cannot be automatically migrated into a `## #001` section — the user re-runs and gets a clean section.
197
+ - **MCP tool not available**: Fall back to `npx canicode gotcha-survey <input> --json` — the CLI returns the same `GotchaSurvey` shape. If the CLI is also unavailable (e.g. no node runtime), tell the user to install the canicode MCP server or the `canicode` npm package (see Prerequisites).
198
+ - **Partial answers**: If the user stops mid-survey, upsert the section with answers collected so far. Mark remaining questions as _(skipped)_.
199
+ - **Manual section deletion**: If the user deletes a middle section by hand, do not renumber existing sections. The next new section still gets `(highest existing number) + 1`; numeric gaps are acceptable (same pattern as `.claude/docs/ADR.md`).