canicode 0.9.0 → 0.10.0

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/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import 'crypto';
6
6
  import { homedir } from 'os';
7
7
 
8
8
  // package.json
9
- var version = "0.9.0";
9
+ var version = "0.10.0";
10
10
  var SeveritySchema = z.enum([
11
11
  "blocking",
12
12
  "risk",
@@ -274,6 +274,92 @@ var VisualCompareCliOptionsSchema = z.object({
274
274
  figmaScale: figmaScaleNumber.optional(),
275
275
  expandRoot: z.boolean().optional()
276
276
  });
277
+ var GradeSchema = z.enum(["S", "A+", "A", "B+", "B", "C+", "C", "D", "F"]);
278
+ var CategoryScoreResultSchema = z.object({
279
+ category: CategorySchema,
280
+ score: z.number(),
281
+ maxScore: z.number(),
282
+ percentage: z.number(),
283
+ issueCount: z.number().int().min(0),
284
+ uniqueRuleCount: z.number().int().min(0),
285
+ weightedIssueCount: z.number(),
286
+ densityScore: z.number(),
287
+ diversityScore: z.number(),
288
+ bySeverity: z.object({
289
+ blocking: z.number().int().min(0),
290
+ risk: z.number().int().min(0),
291
+ "missing-info": z.number().int().min(0),
292
+ suggestion: z.number().int().min(0)
293
+ })
294
+ });
295
+ var McpIssueSchema = z.object({
296
+ ruleId: z.string(),
297
+ subType: z.string().optional(),
298
+ severity: SeveritySchema,
299
+ nodeId: z.string(),
300
+ nodePath: z.string(),
301
+ message: z.string()
302
+ });
303
+ var McpAnalyzeResponseSchema = z.object({
304
+ version: z.string(),
305
+ analyzedAt: z.string(),
306
+ fileKey: z.string().optional(),
307
+ fileName: z.string(),
308
+ nodeCount: z.number().int().min(0),
309
+ maxDepth: z.number().int().min(0),
310
+ issueCount: z.number().int().min(0),
311
+ isReadyForCodeGen: z.boolean(),
312
+ blockingIssueCount: z.number().int().min(0),
313
+ scores: z.object({
314
+ overall: z.object({
315
+ score: z.number(),
316
+ maxScore: z.number(),
317
+ percentage: z.number(),
318
+ grade: GradeSchema
319
+ }),
320
+ categories: z.record(CategorySchema, CategoryScoreResultSchema)
321
+ }),
322
+ issuesByRule: z.record(z.string(), z.number().int().min(0)),
323
+ issues: z.array(McpIssueSchema),
324
+ summary: z.string(),
325
+ failedRules: z.array(z.string()).optional()
326
+ });
327
+ var GradeSchema2 = z.enum(["S", "A+", "A", "B+", "B", "C+", "C", "D", "F"]);
328
+ var InstanceContextSchema = z.object({
329
+ parentInstanceNodeId: z.string(),
330
+ sourceNodeId: z.string(),
331
+ sourceComponentId: z.string().optional(),
332
+ sourceComponentName: z.string().optional()
333
+ });
334
+ var RuleApplyStrategySchema = z.enum([
335
+ "property-mod",
336
+ "structural-mod",
337
+ "annotation",
338
+ "auto-fix"
339
+ ]);
340
+ var TargetPropertySchema = z.union([z.string(), z.array(z.string())]);
341
+ var AnnotationPropertySchema = z.object({ type: z.string() });
342
+ var GotchaSurveyQuestionSchema = z.object({
343
+ nodeId: z.string(),
344
+ nodeName: z.string(),
345
+ ruleId: z.string(),
346
+ severity: SeveritySchema,
347
+ question: z.string(),
348
+ hint: z.string(),
349
+ example: z.string(),
350
+ instanceContext: InstanceContextSchema.optional(),
351
+ applyStrategy: RuleApplyStrategySchema,
352
+ targetProperty: TargetPropertySchema.optional(),
353
+ annotationProperties: z.array(AnnotationPropertySchema).optional(),
354
+ suggestedName: z.string().optional(),
355
+ isInstanceChild: z.boolean(),
356
+ sourceChildId: z.string().optional()
357
+ });
358
+ var GotchaSurveySchema = z.object({
359
+ designGrade: GradeSchema2,
360
+ isReadyForCodeGen: z.boolean(),
361
+ questions: z.array(GotchaSurveyQuestionSchema)
362
+ });
277
363
 
278
364
  // src/core/rules/rule-config.ts
279
365
  var RULE_ID_CATEGORY = {
@@ -451,6 +537,44 @@ function getConfigsWithPreset(preset) {
451
537
  }
452
538
  return configs;
453
539
  }
540
+ var RULE_ANNOTATION_PROPERTIES = {
541
+ "missing-size-constraint": {
542
+ default: [{ type: "width" }, { type: "height" }]
543
+ },
544
+ "irregular-spacing": {
545
+ bySubType: {
546
+ gap: [{ type: "itemSpacing" }],
547
+ padding: [{ type: "padding" }]
548
+ }
549
+ },
550
+ "fixed-size-in-auto-layout": {
551
+ default: [{ type: "width" }, { type: "height" }, { type: "layoutMode" }]
552
+ },
553
+ "raw-value": {
554
+ bySubType: {
555
+ color: [{ type: "fills" }],
556
+ font: [
557
+ { type: "fontSize" },
558
+ { type: "fontFamily" },
559
+ { type: "fontWeight" },
560
+ { type: "lineHeight" }
561
+ ],
562
+ spacing: [{ type: "itemSpacing" }, { type: "padding" }]
563
+ }
564
+ },
565
+ "absolute-position-in-auto-layout": {
566
+ default: [{ type: "layoutMode" }]
567
+ }
568
+ };
569
+ function getAnnotationProperties(ruleId, subType) {
570
+ const entry = RULE_ANNOTATION_PROPERTIES[ruleId];
571
+ if (!entry) return void 0;
572
+ if (subType !== void 0 && entry.bySubType) {
573
+ const match = entry.bySubType[subType];
574
+ if (match) return match;
575
+ }
576
+ return entry.default;
577
+ }
454
578
  function getRuleOption(ruleId, optionKey, defaultValue) {
455
579
  const config = RULE_CONFIGS[ruleId];
456
580
  if (!config.options) return defaultValue;
@@ -714,6 +838,95 @@ function analyzeFile(file, options) {
714
838
  return engine.analyze(file);
715
839
  }
716
840
 
841
+ // src/core/adapters/instance-id-parser.ts
842
+ function isInstanceChildNodeId(nodeId) {
843
+ return nodeId.startsWith("I") && nodeId.includes(";");
844
+ }
845
+ function parseInstanceChildNodeId(nodeId) {
846
+ if (!isInstanceChildNodeId(nodeId)) return null;
847
+ const segments = nodeId.split(";");
848
+ if (segments.length < 2) return null;
849
+ const parentInstanceId = segments[0].replace(/^I/, "");
850
+ const sourceNodeId = segments[segments.length - 1];
851
+ if (!parentInstanceId || !sourceNodeId) return null;
852
+ return { parentInstanceId, sourceNodeId };
853
+ }
854
+
855
+ // src/core/gotcha/apply-context.ts
856
+ var STRATEGY_BY_RULE = {
857
+ // Strategy A — property modification
858
+ "no-auto-layout": "property-mod",
859
+ "fixed-size-in-auto-layout": "property-mod",
860
+ "missing-size-constraint": "property-mod",
861
+ "irregular-spacing": "property-mod",
862
+ "non-semantic-name": "property-mod",
863
+ // Strategy B — structural modification (needs user confirmation)
864
+ "non-layout-container": "structural-mod",
865
+ "deep-nesting": "structural-mod",
866
+ "missing-component": "structural-mod",
867
+ "detached-instance": "structural-mod",
868
+ // Strategy C — annotation only
869
+ "absolute-position-in-auto-layout": "annotation",
870
+ "variant-structure-mismatch": "annotation",
871
+ // Strategy D — auto-fix lower-severity issues from analyze output
872
+ "non-standard-naming": "auto-fix",
873
+ "inconsistent-naming-convention": "auto-fix",
874
+ "raw-value": "auto-fix",
875
+ "missing-interaction-state": "auto-fix",
876
+ "missing-prototype": "auto-fix"
877
+ };
878
+ function resolveTargetProperty(ruleId, subType) {
879
+ switch (ruleId) {
880
+ case "no-auto-layout":
881
+ return ["layoutMode", "itemSpacing"];
882
+ case "fixed-size-in-auto-layout":
883
+ if (subType === "horizontal") return "layoutSizingHorizontal";
884
+ return ["layoutSizingHorizontal", "layoutSizingVertical"];
885
+ case "missing-size-constraint":
886
+ if (subType === "wrap") return "minWidth";
887
+ if (subType === "max-width") return "maxWidth";
888
+ return ["minWidth", "maxWidth"];
889
+ case "irregular-spacing":
890
+ if (subType === "gap") return "itemSpacing";
891
+ return ["paddingTop", "paddingRight", "paddingBottom", "paddingLeft"];
892
+ case "non-semantic-name":
893
+ return "name";
894
+ case "non-layout-container":
895
+ return "layoutMode";
896
+ case "non-standard-naming":
897
+ case "inconsistent-naming-convention":
898
+ return "name";
899
+ case "deep-nesting":
900
+ case "missing-component":
901
+ case "detached-instance":
902
+ case "absolute-position-in-auto-layout":
903
+ case "variant-structure-mismatch":
904
+ case "raw-value":
905
+ case "missing-interaction-state":
906
+ case "missing-prototype":
907
+ return void 0;
908
+ }
909
+ }
910
+ function computeApplyContext(violation, instanceContext) {
911
+ const ruleId = violation.ruleId;
912
+ const applyStrategy = STRATEGY_BY_RULE[ruleId] ?? "annotation";
913
+ const targetProperty = resolveTargetProperty(ruleId, violation.subType);
914
+ const annotationProperties = getAnnotationProperties(
915
+ ruleId,
916
+ violation.subType
917
+ );
918
+ const parsed = parseInstanceChildNodeId(violation.nodeId);
919
+ const isInstanceChild = parsed !== null || isInstanceChildNodeId(violation.nodeId);
920
+ const sourceChildId = parsed?.sourceNodeId;
921
+ return {
922
+ applyStrategy,
923
+ ...targetProperty !== void 0 ? { targetProperty } : {},
924
+ ...annotationProperties !== void 0 ? { annotationProperties } : {},
925
+ isInstanceChild,
926
+ ...sourceChildId !== void 0 ? { sourceChildId } : {}
927
+ };
928
+ }
929
+
717
930
  // src/core/engine/scoring.ts
718
931
  function computeTotalScorePerCategory(configs) {
719
932
  const totals = Object.fromEntries(
@@ -741,6 +954,9 @@ function calculateGrade(percentage) {
741
954
  if (percentage >= 50) return "D";
742
955
  return "F";
743
956
  }
957
+ function isReadyForCodeGen(grade) {
958
+ return grade === "S" || grade === "A+" || grade === "A";
959
+ }
744
960
  function gradeToClassName(grade) {
745
961
  return grade.replace("+", "plus");
746
962
  }
@@ -903,14 +1119,24 @@ function buildResultJson(fileName, result, scores, options) {
903
1119
  const id = issue.violation.ruleId;
904
1120
  issuesByRule[id] = (issuesByRule[id] ?? 0) + 1;
905
1121
  }
906
- const issues = result.issues.map((issue) => ({
907
- ruleId: issue.violation.ruleId,
908
- ...issue.violation.subType && { subType: issue.violation.subType },
909
- severity: issue.config.severity,
910
- nodeId: issue.violation.nodeId,
911
- nodePath: issue.violation.nodePath,
912
- message: issue.violation.message
913
- }));
1122
+ const issues = result.issues.map((issue) => {
1123
+ const applyContext = computeApplyContext(issue.violation);
1124
+ const suggestedName = issue.violation.suggestedName;
1125
+ return {
1126
+ ruleId: issue.violation.ruleId,
1127
+ ...issue.violation.subType && { subType: issue.violation.subType },
1128
+ severity: issue.config.severity,
1129
+ nodeId: issue.violation.nodeId,
1130
+ nodePath: issue.violation.nodePath,
1131
+ message: issue.violation.message,
1132
+ applyStrategy: applyContext.applyStrategy,
1133
+ ...applyContext.targetProperty !== void 0 ? { targetProperty: applyContext.targetProperty } : {},
1134
+ ...applyContext.annotationProperties !== void 0 ? { annotationProperties: applyContext.annotationProperties } : {},
1135
+ ...suggestedName !== void 0 ? { suggestedName } : {},
1136
+ isInstanceChild: applyContext.isInstanceChild,
1137
+ ...applyContext.sourceChildId !== void 0 ? { sourceChildId: applyContext.sourceChildId } : {}
1138
+ };
1139
+ });
914
1140
  const json = {
915
1141
  version,
916
1142
  analyzedAt: result.analyzedAt,
@@ -919,6 +1145,8 @@ function buildResultJson(fileName, result, scores, options) {
919
1145
  nodeCount: result.nodeCount,
920
1146
  maxDepth: result.maxDepth,
921
1147
  issueCount: result.issues.length,
1148
+ isReadyForCodeGen: isReadyForCodeGen(scores.overall.grade),
1149
+ blockingIssueCount: scores.summary.blocking,
922
1150
  scores: {
923
1151
  overall: scores.overall,
924
1152
  categories: scores.byCategory
@@ -1698,6 +1926,36 @@ function tokenDeltaToDifficulty(baselineTokens, strippedTokens) {
1698
1926
  return "failed";
1699
1927
  }
1700
1928
 
1929
+ // src/core/gotcha/resolve-apply-target.ts
1930
+ function resolveGotchaApplyTarget(nodeId, instanceContext) {
1931
+ if (instanceContext) {
1932
+ const componentPhrase = instanceContext.sourceComponentName ? `inside component ${instanceContext.sourceComponentName}` : "inside the source component";
1933
+ return {
1934
+ sceneNodeId: nodeId,
1935
+ definitionNodeId: instanceContext.sourceNodeId,
1936
+ strategy: "prefer-definition",
1937
+ optIn: { preferDefinitionForLayoutProps: true },
1938
+ guidance: `This question targets a node inside an instance. Overrides may fail on the scene node. For layout and min/max sizing, apply changes on node ${instanceContext.sourceNodeId} ${componentPhrase} (parent instance ${instanceContext.parentInstanceNodeId}). Confirm with the user first \u2014 definition edits propagate to every instance of that component in the file.`
1939
+ };
1940
+ }
1941
+ if (isInstanceChildNodeId(nodeId)) {
1942
+ return {
1943
+ sceneNodeId: nodeId,
1944
+ definitionNodeId: void 0,
1945
+ strategy: "definition-unknown",
1946
+ optIn: { preferDefinitionForLayoutProps: true },
1947
+ guidance: "The node id looks like a Figma instance child (`I...;...`) but no `instanceContext` was attached. Parse the segment after the last `;` as the source definition id, resolve the parent INSTANCE, and use `getMainComponentAsync()` when `figma.getNodeById(sourceId)` is not enough (e.g. nested instances)."
1948
+ };
1949
+ }
1950
+ return {
1951
+ sceneNodeId: nodeId,
1952
+ definitionNodeId: void 0,
1953
+ strategy: "scene-only",
1954
+ optIn: { preferDefinitionForLayoutProps: false },
1955
+ guidance: ""
1956
+ };
1957
+ }
1958
+
1701
1959
  // src/core/rules/node-semantics.ts
1702
1960
  function isContainerNode(node) {
1703
1961
  return node.type === "FRAME" || node.type === "GROUP" || node.type === "COMPONENT" || node.type === "INSTANCE";
@@ -2678,6 +2936,10 @@ var variantStructureMismatch = defineRule({
2678
2936
  });
2679
2937
 
2680
2938
  // src/core/rules/naming/index.ts
2939
+ function capitalize(s) {
2940
+ if (!s) return s;
2941
+ return s.charAt(0).toUpperCase() + s.slice(1);
2942
+ }
2681
2943
  function detectNamingConvention(name) {
2682
2944
  if (/^[a-z]+(-[a-z]+)*$/.test(name)) return "kebab-case";
2683
2945
  if (/^[a-z]+(_[a-z]+)*$/.test(name)) return "snake_case";
@@ -2803,6 +3065,7 @@ var inconsistentNamingConventionCheck = (node, context) => {
2803
3065
  ruleId: inconsistentNamingConventionDef.id,
2804
3066
  nodeId: node.id,
2805
3067
  nodePath: context.path.join(" > "),
3068
+ suggestedName: suggested,
2806
3069
  ...inconsistentNamingMsg(node.name, nodeConvention, dominantConvention, suggested)
2807
3070
  };
2808
3071
  }
@@ -2840,6 +3103,7 @@ var nonStandardNamingCheck = (node, context) => {
2840
3103
  subType: "state-name",
2841
3104
  nodeId: node.id,
2842
3105
  nodePath: context.path.join(" > "),
3106
+ suggestedName: capitalize(suggestion),
2843
3107
  ...nonStandardNamingMsg.stateName(node.name, opt, suggestion)
2844
3108
  };
2845
3109
  }
@@ -4572,7 +4836,7 @@ async function loadFromApi(fileKey, nodeId, token) {
4572
4836
  return { file, nodeId };
4573
4837
  }
4574
4838
 
4575
- // src/agents/orchestrator.ts
4839
+ // src/agents/calibration-compute.ts
4576
4840
  function normalizeActualImpact(impact) {
4577
4841
  const mapping = {
4578
4842
  none: "easy",
@@ -4782,6 +5046,6 @@ var ActivityLogger = class {
4782
5046
  }
4783
5047
  };
4784
5048
 
4785
- export { ALL_STRIP_TYPES, ActivityLogger, AnalysisFileSchema, AnalysisNodeSchema, AnalysisNodeTypeSchema, CATEGORIES, CATEGORY_LABELS, CalibrationConfigSchema, CalibrationStatusSchema, CategorySchema, CategoryScoreSchema, ConfidenceSchema, ConversionRecordSchema, DEPTH_WEIGHT_CATEGORIES, DESIGN_TREE_INFO_TYPES, DifficultySchema, FigmaClient, FigmaClientError, FigmaFileLoadError, FigmaUrlInfoSchema, FigmaUrlParseError, GapAnalyzerOutputSchema, GapEntrySchema, GridChildAlignSchema, IssueSchema, LayoutAlignSchema, LayoutConstraintSchema, LayoutModeSchema, LayoutPositioningSchema, LayoutWrapSchema, MismatchCaseSchema, MismatchTypeSchema, NewRuleProposalSchema, NodeIssueSummarySchema, OverflowDirectionSchema, RULE_CONFIGS, RULE_ID_CATEGORY, ReportMetadataSchema, ReportSchema, RuleConfigSchema, RuleDefinitionSchema, RuleEngine, RuleImpactAssessmentSchema, RuleRelatedStruggleSchema, SEVERITY_LABELS, SEVERITY_WEIGHT, SamplingStrategySchema, ScoreAdjustmentSchema, SeveritySchema, StripDeltaResultSchema, StripDeltasArraySchema, StripTypeEnum, UncoveredStruggleSchema, UncoveredStrugglesInputSchema, version as VERSION, VisualCompareCliOptionsSchema, absolutePositionInAutoLayout, analyzeFile, buildFigmaDeepLink, buildResultJson, calculateScores, collectComponentIds, collectInteractionDestinationIds, createRuleEngine, deepNesting, defineRule, detachedInstance, extractRuleScores, fixedSizeInAutoLayout, formatScoreSummary, generateCalibrationReport, generateDesignTree, generateDesignTreeWithStats, getAnalysisState, getCategoryLabel, getConfigsWithPreset, getRuleOption, getSeverityLabel, gradeToClassName, inconsistentNamingConvention, irregularSpacing, loadFigmaFileFromJson, missingComponent, missingInteractionState, missingPrototype, missingSizeConstraint, noAutoLayout, nonLayoutContainer, nonSemanticName, nonStandardNaming, parseFigmaJson, parseFigmaUrl, rawValue, resolveComponentDefinitions, resolveInteractionDestinations, ruleRegistry, runAnalysisAgent, runCalibrationAnalyze, runCalibrationEvaluate, runEvaluationAgent, runTuningAgent, stripDeltaToDifficulty, stripDesignTree, supportsDepthWeight, toCommentableNodeId, tokenDeltaToDifficulty, transformComponentMasterNodes, transformFigmaResponse, transformFileNodesResponse, variantStructureMismatch };
5049
+ export { ALL_STRIP_TYPES, ActivityLogger, AnalysisFileSchema, AnalysisNodeSchema, AnalysisNodeTypeSchema, AnnotationPropertySchema, CATEGORIES, CATEGORY_LABELS, CalibrationConfigSchema, CalibrationStatusSchema, CategorySchema, CategoryScoreSchema, ConfidenceSchema, ConversionRecordSchema, DEPTH_WEIGHT_CATEGORIES, DESIGN_TREE_INFO_TYPES, DifficultySchema, FigmaClient, FigmaClientError, FigmaFileLoadError, FigmaUrlInfoSchema, FigmaUrlParseError, GapAnalyzerOutputSchema, GapEntrySchema, GotchaSurveyQuestionSchema, GotchaSurveySchema, GridChildAlignSchema, InstanceContextSchema, IssueSchema, LayoutAlignSchema, LayoutConstraintSchema, LayoutModeSchema, LayoutPositioningSchema, LayoutWrapSchema, McpAnalyzeResponseSchema, MismatchCaseSchema, MismatchTypeSchema, NewRuleProposalSchema, NodeIssueSummarySchema, OverflowDirectionSchema, RULE_ANNOTATION_PROPERTIES, RULE_CONFIGS, RULE_ID_CATEGORY, ReportMetadataSchema, ReportSchema, RuleApplyStrategySchema, RuleConfigSchema, RuleDefinitionSchema, RuleEngine, RuleImpactAssessmentSchema, RuleRelatedStruggleSchema, SEVERITY_LABELS, SEVERITY_WEIGHT, SamplingStrategySchema, ScoreAdjustmentSchema, SeveritySchema, StripDeltaResultSchema, StripDeltasArraySchema, StripTypeEnum, UncoveredStruggleSchema, UncoveredStrugglesInputSchema, version as VERSION, VisualCompareCliOptionsSchema, absolutePositionInAutoLayout, analyzeFile, buildFigmaDeepLink, buildResultJson, calculateScores, collectComponentIds, collectInteractionDestinationIds, createRuleEngine, deepNesting, defineRule, detachedInstance, extractRuleScores, fixedSizeInAutoLayout, formatScoreSummary, generateCalibrationReport, generateDesignTree, generateDesignTreeWithStats, getAnalysisState, getAnnotationProperties, getCategoryLabel, getConfigsWithPreset, getRuleOption, getSeverityLabel, gradeToClassName, inconsistentNamingConvention, irregularSpacing, isInstanceChildNodeId, isReadyForCodeGen, loadFigmaFileFromJson, missingComponent, missingInteractionState, missingPrototype, missingSizeConstraint, noAutoLayout, nonLayoutContainer, nonSemanticName, nonStandardNaming, parseFigmaJson, parseFigmaUrl, parseInstanceChildNodeId, rawValue, resolveComponentDefinitions, resolveGotchaApplyTarget, resolveInteractionDestinations, ruleRegistry, runAnalysisAgent, runCalibrationAnalyze, runCalibrationEvaluate, runEvaluationAgent, runTuningAgent, stripDeltaToDifficulty, stripDesignTree, supportsDepthWeight, toCommentableNodeId, tokenDeltaToDifficulty, transformComponentMasterNodes, transformFigmaResponse, transformFileNodesResponse, variantStructureMismatch };
4786
5050
  //# sourceMappingURL=index.js.map
4787
5051
  //# sourceMappingURL=index.js.map