canicode 0.10.2 → 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 +1 -1
- package/dist/cli/index.js +57 -2
- 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
|
---
|
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;
|