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 +3 -1
- package/dist/cli/index.js +95 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +20 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +57 -2
- package/dist/mcp/server.js.map +1 -1
- package/package.json +2 -2
- package/skills/canicode-roundtrip/SKILL.md +122 -19
- package/skills/canicode-roundtrip/helpers.js +54 -10
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
6317
|
+
console.log(
|
|
6318
|
+
formatNextSteps({
|
|
6319
|
+
figmaMcpPresent: figmaMcpRegistered(),
|
|
6320
|
+
skillsInstalled: options.skills !== false
|
|
6321
|
+
})
|
|
6322
|
+
);
|
|
6232
6323
|
}
|
|
6233
6324
|
return;
|
|
6234
6325
|
}
|