| name | canicode-gotchas |
| description | Run a gotcha survey on a Figma design โ Q&A workflow that captures
designer/developer answers locally in .claude/skills/canicode-gotchas/SKILL.md so
figma-implement-design can read them at code-gen time. Memo-only: this skill never writes
to the Figma canvas.
TRIGGER when: the user shares a figma.com/design/... URL and asks to capture
implementation context, run a gotcha survey, collect design Q&A, or prepare context for
figma-implement-design without mutating the Figma file; the user wants to answer
implementation questions for a design before code generation; the user mentions "gotcha
survey" or "design Q&A".
SKIP when: the user wants the answers written back into the Figma file as annotations or
property fixes (route to canicode-roundtrip); the user only wants a one-shot readiness
report with no Q&A loop (route to canicode); the user wants to generate code directly
with no survey step (route to figma-implement-design).
|
CanICode Gotchas โ Design Gotcha Survey
Channel contrast: canicode-gotchas (this skill) persists answers only in local .claude/skills/canicode-gotchas/SKILL.md โ memo-only, no Plugin write to Figma. canicode-roundtrip writes to the canvas. Use gotchas when you want Q&A captured for code-gen context without mutating the file.
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).
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).
Prerequisites
- 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 (~/.cursor/mcp.json or project .cursor/mcp.json). The MCP server reads FIGMA_TOKEN from ~/.canicode/config.json or the environment.
- Without canicode MCP (fallback):
npx canicode gotcha-survey "<input>" --json โ same JSON shape as the MCP tool.
- FIGMA_TOKEN configured for live Figma URLs.
- 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/).
Workflow
Step 0: Verify canicode MCP tools are loaded (optional fast path)
Before Step 1, verify that gotcha-survey is callable in this session โ not merely listed in .mcp.json. Newly registered MCP servers usually need a host restart or MCP reload before tools appear (same pattern as /canicode-roundtrip Step 0 for the Figma MCP).
When you fall back to npx canicode gotcha-survey โฆ --json, tell the user explicitly: the canicode MCP may not be loaded yet. They should register it (claude mcp add canicode -- npx --yes --package=canicode canicode-mcp, or the Cursor/mcp.json equivalent in the Customization guide) and restart the IDE or reload MCP โ then the next session can use the MCP tool without spawning npx. The CLI fallback is correct behavior; silence makes users think registration failed (#433).
Step 1: Run the gotcha survey
If the gotcha-survey MCP tool is available, call it with the user's Figma URL:
gotcha-survey({ input: "<figma-url-or-fixture-path>" })
Without canicode MCP โ shell out to the CLI. The --json output parses identically:
npx canicode gotcha-survey "<figma-url-or-fixture-path>" --json
Either channel returns:
designGrade: overall grade (S, A+, A, B+, B, C+, C, D, F)
isReadyForCodeGen: recommendation flag โ true suggests the survey can be skipped, but the user always confirms
questions: array of gotcha questions (may be empty)
Step 2: Surface grade as informational banner
Show the grade as a preamble โ it is informational only, not a flow gate:
Design scored **{designGrade}**.
Then branch on questions:
If questions is empty (regardless of isReadyForCodeGen):
- Tell the user: "No gotchas surfaced โ nothing to write."
- Do NOT write to
.claude/skills/canicode-gotchas/SKILL.md.
- Stop here.
If questions is non-empty AND isReadyForCodeGen is true (high-grade design with optional questions):
- Tell the user: "{N} optional gotcha(s) surfaced. Would you like to review them?"
- yes โ proceed to Step 3.
- no โ do NOT write to
.claude/skills/canicode-gotchas/SKILL.md; stop here.
If questions is non-empty AND isReadyForCodeGen is false:
- Proceed to Step 3 (no additional message needed; the banner grade already communicates urgency).
Step 3 โ preamble: match the user's language
Before rendering any question, detect the user's conversation language from their recent messages in this session. Korean vs. English vs. other is usually unambiguous; when ambiguous, default to English and ask the user once which language they prefer.
When the user's language is non-English, localize only the human-readable strings rendered in the prompt templates below: the question text, the why line (if shown), the Hint: body, the Example: body, and the batch shared-prompt wording โ including the "Reply with one answer to apply to all โฆ, or split to answer each individually" sentence and the skip / n/a affordance sentence that follows it. Translate at render time only; the rule templates in core/rules/* stay English-only per CLAUDE.md and the issue's "Out of scope" list โ do not rewrite source.
Keep the following English even when localizing, because they are identifiers or structural markers that downstream tools grep for: ruleId, nodeId, nodeName, the severity label in brackets ([blocking], [risk], [missing-info], [suggestion]), and the entire markdown scaffolding of the Step 4 upsert section (## #NNN โ โฆ headings, Design key, #### Skipped (N), the per-record field labels). renderGotchaSection is the source of truth for that on-disk markdown (ADR-016) and its output stays English.
In Step 4, pass the user's answer through verbatim into the answers[<nodeId>].answer field โ do not back-translate answers to English. figma-implement-design is cross-language by design (see #461), and a round-trip to English introduces translation loss and defeats the "shared language for designer/PM" framing.
Step 3: Present questions to the user
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 both batchable-rule lists (BATCHABLE_RULE_IDS for safe mode, OPT_IN_BATCHABLE_RULE_IDS for opt-in mode) all live in core/gotcha/group-and-batch-questions.ts with vitest coverage (per ADR-016). Iterate over it:
Before presenting the first batch, display this shortcut notice once so the user knows they can exit early at any point:
Survey: {totalBatchCount} question(s) to answer.
Tip: reply `skip remaining` at any point to bypass the rest with a default no-op annotation and finish immediately.
Where totalBatchCount is groupedQuestions.groups.flatMap((g) => g.batches).length.
After every 3rd batch (i.e. after batches 3, 6, 9, โฆ), re-surface the shortcut as a brief reminder before presenting the next batch:
(You can still reply `skip remaining` to bypass the remaining questions.)
When the user replies skip remaining at any point during Step 3, immediately treat all unanswered batches as skipped ({ "skipped": true } for each unanswered nodeId) and proceed directly to Step 4 without asking further questions.
For every batch in groupedQuestions.groups.flatMap((g) => g.batches), branch on batch.batchMode:
-
batch.batchMode === "none" โ single-question batch; the helper guarantees batch.questions.length === 1. Render the standard prompt for batch.questions[0]:
**[{severity}] {ruleId}** โ node: {nodeName}
{question}
> Hint: {hint}
> Example: {example}
-
batch.batchMode === "safe" with batch.questions.length >= 2 (#369) โ rule in BATCHABLE_RULE_IDS; one answer is uniformly applicable. Render one shared prompt:
**[{severity}] {ruleId}** โ {batch.questions.length} instances:
- {nodeNameโ}
- {nodeNameโ}
- โฆ
{sharedQuestionPrompt}
Reply with one answer to apply to all {batch.questions.length}, or **split** to answer each individually.
> Hint: {hint}
> Example: {example}
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).
-
batch.batchMode === "opt-in" with batch.questions.length >= 2 (#426) โ rule in OPT_IN_BATCHABLE_RULE_IDS (currently missing-prototype). The same answer is usually shareable across siblings but may legitimately differ per node โ signal that explicitly so the user can opt out of the shared answer with split:
**[{severity}] {batch.ruleId}** โ {batch.questions.length} instances of the same rule:
- {nodeNameโ}
- {nodeNameโ}
- โฆ
{sharedQuestionPrompt}
Apply this answer to all {batch.questions.length} occurrences of `{batch.ruleId}`, or reply **split** to answer each individually.
> Hint: {hint}
> Example: {example}
Unlike safe batches, the prompt frames the answer as a suggested default, not a uniform truth โ reuse the rule's existing example (e.g. missing-prototype's "navigates to /product/{id} detail page") so the user knows the answer can be a pattern, not a literal string shared character-for-character.
-
Single-member safe or opt-in batch (batch.questions.length === 1) โ render the single-question template above; the shared-prompt framing collapses to the rule's own wording when there is only one node.
Wait for the user's answer before moving to the next batch. The user may:
- Answer the question / batch directly (single value or pattern covers all batch members)
- Say split (batch only) to fall back to per-question prompting for that batch โ works the same for both
safe and opt-in batches
- Say skip to skip the question / the entire batch
- Say n/a if the question / the entire batch is not applicable
- Say skip remaining to immediately skip all remaining unanswered batches and proceed to Step 4
When applying the batched answer, expand back to per-question records in Step 4 โ the gotcha section format stores one record per nodeId.
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.
Step 4: Upsert the gotcha section
After collecting all answers, upsert this design's section into the # Collected Gotchas region at the bottom of:
.claude/skills/canicode-gotchas/SKILL.md
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).
Step 4a: Use the designKey from the survey response
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.
The core/contracts/design-key.ts helper (computeDesignKey) handles every shape with vitest coverage so this workflow stays ADR-016-compliant:
- 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.
- Figma URL without
node-id โ just <fileKey> (file-level key).
- Fixture path / JSON file โ absolute path.
Step 4b: Upsert via the canicode CLI
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).
Build one JSON object on stdin for upsert-gotcha-section. The CLI renders the section markdown from survey + answers via renderGotchaSection in TypeScript (#439) โ severity, rule text, node ids, and instance context come verbatim from gotcha-survey --json; the skill must not paste LLM-authored section prose.
Payload shape:
{
"survey": {
"designKey": "<same as Step 4a>",
"designGrade": "<from gotcha-survey>",
"questions": "<full questions[] array from gotcha-survey โ preserve order>"
},
"answers": {
"<nodeId>": { "answer": "โฆ" }
},
"designName": "<Figma file name or fixture label>",
"figmaUrl": "<the user's input URL or path>",
"analyzedAt": "<ISO 8601 timestamp when you upsert>",
"today": "<YYYY-MM-DD local date for the section title>"
}
For skipped / n/a: use { "skipped": true } for that nodeId, or omit the key. Skipped questions do not get per-question rows; renderGotchaSection appends a compact #### Skipped (N) block listing each ruleId with a count (ruleId lines sorted lexically โ see src/core/gotcha/render-gotcha-section.ts).
Invoke (cac requires --input=-, not --input -, so the stdin sentinel survives parsing โ #420):
npx canicode upsert-gotcha-section \
--file .claude/skills/canicode-gotchas/SKILL.md \
--design-key "<designKey from Step 4a>" \
--input=-
Pipe the JSON object on stdin. --design-key must equal survey.designKey (the CLI validates the match).
The CLI prints JSON { state, action, sectionNumber, wrote, userMessage, designKey }:
wrote: true โ success. action is "replace" (preserved sectionNumber) or "append" (next monotonic sectionNumber).
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.
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.
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.
The Workflow region above must never be touched.
Edge Cases
- No questions returned: No gotchas surfaced. Inform the user and stop (Step 2). Do not touch
.claude/skills/canicode-gotchas/SKILL.md.
- 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.
- Re-run on a different design: Append a new section with the next
#NNN. Prior designs' sections are untouched.
- 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.
- 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.
- 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).
- Partial answers: If the user stops mid-survey, upsert the section with answers collected so far. Remaining questions count toward
#### Skipped (N) (omit keys or { "skipped": true }).
- 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).
Collected Gotchas