canicode 0.10.1 → 0.10.3

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 CHANGED
@@ -44,7 +44,7 @@ Rule scores aren't guesswork. The calibration pipeline converts real Figma desig
44
44
  - ✅ **scene write succeeded** — the property was written directly to the scene node or instance override.
45
45
  - 📝 **annotated the scene node** — the skill left a structured annotation instead of writing the property. This is the [ADR-012](.claude/docs/ADR.md) default for instance-child layout writes, because propagating a property to the component definition (and therefore every instance of it) is almost never what the user wants. **A summary full of 📝 markers is correct behavior, not failure.**
46
46
  - 🌐 **definition write propagated** — the property was written to the component definition and every instance inherited it. Only happens when the user opted in up front with `allowDefinitionWrite`.
47
- 4. **Re-analyze** — verify the grade improved. Repeat step 2 if new gotchas surface.
47
+ 4. **Re-analyze** — verify the gotchas were addressed (grade movement is a side-effect). Repeat step 2 if new gotchas surface.
48
48
  5. **Hand off** to `figma-implement-design` — canicode's scope ends here ([ADR-013](.claude/docs/ADR.md)). Figma's official code-generation skill takes the now-clean design and produces code.
49
49
 
50
50
  ---
@@ -166,6 +166,8 @@ Saves your Figma token AND installs three skills into `./.claude/skills/`:
166
166
  - **canicode-gotchas** — standalone gotcha survey (use `/canicode-gotchas <figma-url>`)
167
167
  - **canicode-roundtrip** — full analyze → gotcha → apply roundtrip (use `/canicode-roundtrip <figma-url>`)
168
168
 
169
+ > **Next:** install the Figma MCP server (`claude mcp add -s project -t http figma https://mcp.figma.com/mcp`) and restart Claude Code so both the skills and the MCP tools load. See the **MCP Server** section above for context.
170
+
169
171
  Flags: `--global` installs into `~/.claude/skills/` instead. `--no-skills` skips skill install (token only). `--force` overwrites existing skill files without prompting. Run `canicode docs setup` for the full setup guide.
170
172
 
171
173
  </details>
package/dist/cli/index.js CHANGED
@@ -3286,6 +3286,15 @@ function hasStateInComponentMaster(node, context, statePattern) {
3286
3286
  if (!master) return false;
3287
3287
  return hasStateInVariantProps(master, statePattern);
3288
3288
  }
3289
+ function canDetermineVariants(node, context) {
3290
+ if (node.type === "COMPONENT") return true;
3291
+ if (node.componentPropertyDefinitions !== void 0) return true;
3292
+ if (node.componentId !== void 0) {
3293
+ const defs = context.file.componentDefinitions;
3294
+ if (defs && defs[node.componentId] !== void 0) return true;
3295
+ }
3296
+ return false;
3297
+ }
3289
3298
  var missingInteractionStateDef = {
3290
3299
  id: "missing-interaction-state",
3291
3300
  name: "Missing Interaction State",
@@ -3300,6 +3309,7 @@ var missingInteractionStateCheck = (node, context) => {
3300
3309
  if (!interactiveType) return null;
3301
3310
  const expectedStates = EXPECTED_STATES[interactiveType];
3302
3311
  if (!expectedStates) return null;
3312
+ if (!canDetermineVariants(node, context)) return null;
3303
3313
  const seen = getSeen(context);
3304
3314
  const nodePath = context.path.join(" > ");
3305
3315
  for (const state of expectedStates) {
@@ -4015,7 +4025,7 @@ function computeApplyContext(violation, instanceContext) {
4015
4025
  }
4016
4026
 
4017
4027
  // package.json
4018
- var version2 = "0.10.1";
4028
+ var version2 = "0.10.3";
4019
4029
 
4020
4030
  // src/core/engine/scoring.ts
4021
4031
  function computeTotalScorePerCategory(configs) {
@@ -5730,7 +5740,8 @@ function generateGotchaSurvey(result, scores) {
5730
5740
  );
5731
5741
  const deduped = deduplicateSiblingIssues(relevantIssues);
5732
5742
  const sorted = stableSortBySeverity(deduped);
5733
- const questions = sorted.map((issue) => mapToQuestion(issue, result.file)).filter((q) => q !== null);
5743
+ const mapped = sorted.map((issue) => mapToQuestion(issue, result.file)).filter((q) => q !== null);
5744
+ const questions = deduplicateBySourceComponent(mapped);
5734
5745
  return {
5735
5746
  designGrade: grade,
5736
5747
  isReadyForCodeGen: isReadyForCodeGen(grade),
@@ -5800,6 +5811,50 @@ function mapToQuestion(issue, file) {
5800
5811
  ...applyContext.sourceChildId !== void 0 ? { sourceChildId: applyContext.sourceChildId } : {}
5801
5812
  };
5802
5813
  }
5814
+ function deduplicateBySourceComponent(questions) {
5815
+ const groups = /* @__PURE__ */ new Map();
5816
+ const order = [];
5817
+ let uniqueCounter = 0;
5818
+ for (const q of questions) {
5819
+ const ic = q.instanceContext;
5820
+ let key;
5821
+ if (ic && ic.sourceComponentId && ic.sourceNodeId) {
5822
+ key = `${ic.sourceComponentId}::${ic.sourceNodeId}::${q.ruleId}`;
5823
+ } else {
5824
+ key = `__unique__${uniqueCounter++}`;
5825
+ }
5826
+ const bucket = groups.get(key);
5827
+ if (bucket) {
5828
+ bucket.push(q);
5829
+ } else {
5830
+ groups.set(key, [q]);
5831
+ order.push(key);
5832
+ }
5833
+ }
5834
+ return order.map((key) => {
5835
+ const group = groups.get(key);
5836
+ const first = group[0];
5837
+ if (group.length === 1) return first;
5838
+ const otherIds = group.slice(1).map((q) => q.nodeId);
5839
+ const sourceComponentName = first.instanceContext?.sourceComponentName;
5840
+ const template = GOTCHA_QUESTIONS[first.ruleId];
5841
+ const renamed = {
5842
+ ...first,
5843
+ replicas: group.length,
5844
+ replicaNodeIds: otherIds
5845
+ };
5846
+ if (sourceComponentName) {
5847
+ renamed.nodeName = sourceComponentName;
5848
+ if (template) {
5849
+ renamed.question = template.question.replace(
5850
+ "{nodeName}",
5851
+ sourceComponentName
5852
+ );
5853
+ }
5854
+ }
5855
+ return renamed;
5856
+ });
5857
+ }
5803
5858
  function buildInstanceContext(nodeId, file) {
5804
5859
  const parts = parseInstanceChildNodeId(nodeId);
5805
5860
  if (!parts) return null;
@@ -6166,6 +6221,38 @@ async function promptOverwriteBatch(candidates) {
6166
6221
  }
6167
6222
 
6168
6223
  // src/cli/commands/init.ts
6224
+ function figmaMcpRegistered(cwd = process.cwd()) {
6225
+ try {
6226
+ const raw = readFileSync(join(cwd, ".mcp.json"), "utf-8");
6227
+ const parsed = JSON.parse(raw);
6228
+ const figma = parsed?.mcpServers?.["figma"];
6229
+ return typeof figma === "object" && figma !== null;
6230
+ } catch {
6231
+ return false;
6232
+ }
6233
+ }
6234
+ function formatNextSteps(opts) {
6235
+ if (!opts.skillsInstalled) {
6236
+ return `
6237
+ Next: canicode analyze "https://www.figma.com/design/..."`;
6238
+ }
6239
+ if (opts.figmaMcpPresent) {
6240
+ return [
6241
+ "",
6242
+ " Next:",
6243
+ " 1. Restart Claude Code (the newly installed skills only load on a fresh session)",
6244
+ " 2. Run /canicode-roundtrip <figma-url>"
6245
+ ].join("\n");
6246
+ }
6247
+ return [
6248
+ "",
6249
+ " Next:",
6250
+ " 1. Install Figma MCP:",
6251
+ " claude mcp add -s project -t http figma https://mcp.figma.com/mcp",
6252
+ " 2. Restart Claude Code (so the new skills + Figma MCP tools both load)",
6253
+ " 3. Run /canicode-roundtrip <figma-url>"
6254
+ ].join("\n");
6255
+ }
6169
6256
  var InitOptionsSchema = z.object({
6170
6257
  token: z.string().optional(),
6171
6258
  global: z.boolean().optional(),
@@ -6227,8 +6314,12 @@ ${msg}`);
6227
6314
  ...skillSummary ?? {}
6228
6315
  });
6229
6316
  if (skillStepOk) {
6230
- console.log(`
6231
- Next: canicode analyze "https://www.figma.com/design/..."`);
6317
+ console.log(
6318
+ formatNextSteps({
6319
+ figmaMcpPresent: figmaMcpRegistered(),
6320
+ skillsInstalled: options.skills !== false
6321
+ })
6322
+ );
6232
6323
  }
6233
6324
  return;
6234
6325
  }