canicode 0.10.2 → 0.10.4

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/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "canicode",
3
- "version": "0.10.2",
3
+ "version": "0.10.4",
4
4
  "mcpName": "io.github.let-sunny/canicode",
5
- "description": "Score your Figma designs with AI-calibrated rules. CLI + MCP server.",
5
+ "description": "Lint Figma designs for AI code-gen and roundtrip the answers back into the file. CLI + MCP server.",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
@@ -27,6 +27,7 @@
27
27
  "lint": "tsc --noEmit",
28
28
  "build:plugin": "bash scripts/build-plugin.sh",
29
29
  "sync-docs": "tsx scripts/sync-rule-docs.ts",
30
+ "check:skill-determinism": "tsx scripts/check-skill-determinism.ts",
30
31
  "clean": "rm -rf dist skills"
31
32
  },
32
33
  "files": [
@@ -9,7 +9,7 @@ Run a gotcha survey on a Figma design to identify implementation pitfalls, colle
9
9
 
10
10
  ## Prerequisites
11
11
 
12
- - **canicode MCP server** (preferred): `claude mcp add canicode -e FIGMA_TOKEN=figd_xxx -- npx -y -p canicode canicode-mcp`
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
13
  - **Without canicode MCP** (fallback): the `canicode gotcha-survey --json` CLI produces the same response shape — no MCP installation required.
14
14
  - **FIGMA_TOKEN** configured for live Figma URLs
15
15
 
@@ -43,21 +43,50 @@ If `isReadyForCodeGen` is `true` or `questions` is empty:
43
43
 
44
44
  ### Step 3: Present questions to the user
45
45
 
46
- For each question in the `questions` array, present it to the user one at a time:
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:
47
47
 
48
- ```
49
- **[{severity}] {ruleId}** — node: {nodeName}
48
+ For every `batch` in `groupedQuestions.groups.flatMap((g) => g.batches)`:
50
49
 
51
- {question}
50
+ - **Single-question batch (`batch.questions.length === 1`)** — render the standard prompt for `batch.questions[0]`:
52
51
 
53
- > Hint: {hint}
54
- > Example: {example}
55
- ```
52
+ ```
53
+ **[{severity}] {ruleId}** — node: {nodeName}
54
+
55
+ {question}
56
+
57
+ > Hint: {hint}
58
+ > Example: {example}
59
+ ```
60
+
61
+ - **Batch of N ≥ 2 with `batch.batchable === true`** (#369) — render one shared prompt covering every member:
62
+
63
+ ```
64
+ **[{severity}] {ruleId}** — {batch.questions.length} instances:
65
+ - {nodeName₁}
66
+ - {nodeName₂}
67
+ - …
68
+
69
+ {sharedQuestionPrompt}
70
+
71
+ Reply with one answer to apply to all {batch.questions.length}, or **split** to answer each individually.
72
+
73
+ > Hint: {hint}
74
+ > Example: {example}
75
+ ```
76
+
77
+ 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).
56
78
 
57
- Wait for the user's answer before moving to the next question. The user may:
58
- - Answer the question directly
59
- - Say "skip" to skip a question
60
- - Say "n/a" if the question is not applicable
79
+ - **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).
80
+
81
+ Wait for the user's answer before moving to the next batch. The user may:
82
+ - Answer the question / batch directly
83
+ - Say **split** (batch only) to fall back to per-question prompting for that batch
84
+ - Say **skip** to skip the question / the entire batch
85
+ - Say **n/a** if the question / the entire batch is not applicable
86
+
87
+ When applying the batched answer, expand back to per-question records in Step 4 — the gotcha section format stores one record per `nodeId`.
88
+
89
+ > 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.
61
90
 
62
91
  ### Step 4: Upsert the gotcha section
63
92
 
@@ -69,26 +98,35 @@ After collecting all answers, **upsert** this design's section into the `# Colle
69
98
 
70
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.
71
100
 
72
- #### Step 4a: Compute `designKey`
101
+ #### Step 4a: Use the `designKey` from the survey response
102
+
103
+ `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
+
105
+ The `core/contracts/design-key.ts` helper (`computeDesignKey`) handles every shape with vitest coverage so the SKILL stays ADR-016-compliant:
73
106
 
74
- `designKey` uniquely identifies the design so re-running on the same URL replaces the existing section in place. Parse it from the survey input:
107
+ - **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
+ - **Figma URL without `node-id`** → just `<fileKey>` (file-level key).
109
+ - **Fixture path / JSON file** → absolute path.
75
110
 
76
- - **Figma URL** — extract `fileKey` and `nodeId` from the URL and join them as `<fileKey>#<nodeId>`. Example: `https://figma.com/design/abc123XYZ/My-File?node-id=42-100` `designKey = "abc123XYZ#42:100"` (convert `-` to `:` in nodeId, the same normalization the Figma MCP uses). Drop any other query-string parameters — only `node-id` matters for the key.
77
- - **Fixture path** — use the absolute path, e.g. `/Users/me/project/fixtures/simple.json`.
111
+ #### Step 4b: Upsert via the canicode CLI
78
112
 
79
- Do **not** use the raw survey input URL as the key: trailing query parameters (`?t=...`, `?mode=...`) break string matching on re-runs.
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).
114
+
115
+ 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
+
117
+ ```bash
118
+ npx canicode upsert-gotcha-section \
119
+ --file .claude/skills/canicode-gotchas/SKILL.md \
120
+ --design-key "<designKey from Step 4a>" \
121
+ --section - # then pipe the rendered section markdown through stdin
122
+ ```
80
123
 
81
- #### Step 4b: Read the existing file and locate the target section
124
+ The CLI prints a JSON result `{ state, action, sectionNumber, wrote, userMessage }`:
82
125
 
83
- 1. Read `.claude/skills/canicode-gotchas/SKILL.md` if it exists.
84
- 2. Detect the file's state using the two structural markers that uniquely identify each case the YAML frontmatter (present on every `canicode init` install) and the `# Collected Gotchas` heading (present on every post-#340 install):
85
- - **File missing** → tell the user to run `canicode init` first, then re-invoke this skill. Stop here.
86
- - **File has YAML frontmatter AND a `# Collected Gotchas` heading** (the default shipped shape since #340) proceed to step 3 below.
87
- - **File has YAML frontmatter but no `# Collected Gotchas` heading** (an older workflow install, or a user-edited workflow that dropped the trailing heading) → preserve everything above unchanged and append a new `# Collected Gotchas` heading at the bottom, then proceed to step 3.
88
- - **File missing the YAML frontmatter** (a pre-#340 single-design clobber — the old overwrite rewrote the frontmatter's `description` to the per-design variant, so a well-formed canicode frontmatter is the cleanest discriminator) → **do not attempt to reconstruct the workflow inline**. Tell the user: "Your gotchas SKILL.md looks like the pre-#340 single-design format. Run `canicode init --force` to restore the workflow, then re-run this survey — your answers will land in a clean numbered section." Stop here.
89
- 3. Walk the existing `## #NNN — ...` sections under `# Collected Gotchas` and look for one whose `- **Design key**:` bullet matches the `designKey` from Step 4a. Substring match against the bullet value is sufficient.
90
- - **Found** → replace that section in place. **Preserve its `#NNN` number** so external references (downstream skills, user notes) remain stable.
91
- - **Not found** → append a new section at the bottom of the region. `#NNN = (highest existing number) + 1`, zero-padded to three digits. Never reuse a number that appeared earlier and was deleted; numbering is monotonic.
126
+ - `wrote: true` → success. `action` is `"replace"` (preserved `sectionNumber`) or `"append"` (next monotonic `sectionNumber`).
127
+ - `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.
128
+ - `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.
129
+ - `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.
92
130
 
93
131
  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.
94
132
 
@@ -125,7 +163,7 @@ Each per-design section in the `# Collected Gotchas` region has this exact shape
125
163
  | `designName` | Figma file name or fixture name from the input |
126
164
  | `YYYY-MM-DD` | Today's date (the day you are running the survey) |
127
165
  | `figmaUrl` | The input URL or fixture path provided by the user |
128
- | `designKey` | `<fileKey>#<nodeId>` for Figma URLs, absolute path for fixtures (see Step 4a) |
166
+ | `designKey` | `survey.designKey` from the gotcha-survey response (see Step 4a) |
129
167
  | `designGrade` | `designGrade` from gotcha-survey response |
130
168
  | `analyzedAt` | Current timestamp (ISO 8601) |
131
169
  | `ruleId` | `ruleId` from each question |