canicode 0.11.2 → 0.11.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.
@@ -1863,9 +1863,11 @@ function computeApplyContext(violation, instanceContext) {
1863
1863
  }
1864
1864
 
1865
1865
  // package.json
1866
- var version = "0.11.2";
1866
+ var version = "0.11.3";
1867
1867
 
1868
1868
  // src/core/engine/scoring.ts
1869
+ var GRADE_ORDER = ["S", "A+", "A", "B+", "B", "C+", "C", "D", "F"];
1870
+ var DEFAULT_CODEGEN_READY_MIN_GRADE = "A";
1869
1871
  function computeTotalScorePerCategory(configs) {
1870
1872
  const totals = Object.fromEntries(
1871
1873
  CATEGORIES.map((c) => [c, 0])
@@ -1892,8 +1894,9 @@ function calculateGrade(percentage) {
1892
1894
  if (percentage >= 50) return "D";
1893
1895
  return "F";
1894
1896
  }
1895
- function isReadyForCodeGen(grade) {
1896
- return grade === "S" || grade === "A+" || grade === "A";
1897
+ function isReadyForCodeGen(grade, minGrade) {
1898
+ const threshold = minGrade ?? DEFAULT_CODEGEN_READY_MIN_GRADE;
1899
+ return GRADE_ORDER.indexOf(grade) <= GRADE_ORDER.indexOf(threshold);
1897
1900
  }
1898
1901
  function clamp(value, min, max) {
1899
1902
  return Math.max(min, Math.min(max, value));
@@ -2086,7 +2089,7 @@ function buildResultJson(fileName, result, scores, options) {
2086
2089
  scope: result.scope,
2087
2090
  issueCount: result.issues.length,
2088
2091
  acknowledgedCount: scores.summary.acknowledgedCount,
2089
- isReadyForCodeGen: isReadyForCodeGen(scores.overall.grade),
2092
+ isReadyForCodeGen: isReadyForCodeGen(scores.overall.grade, options?.codegenReadyMinGrade),
2090
2093
  blockingIssueCount: scores.summary.blocking,
2091
2094
  scores: {
2092
2095
  overall: scores.overall,
@@ -2324,7 +2327,7 @@ function generateGotchaSurvey(result, scores, options = {}) {
2324
2327
  const suggestedDefaultApply = propagationCandidates >= PROPAGATION_CANDIDATE_THRESHOLD;
2325
2328
  return {
2326
2329
  designGrade: grade,
2327
- isReadyForCodeGen: isReadyForCodeGen(grade),
2330
+ isReadyForCodeGen: isReadyForCodeGen(grade, options.codegenReadyMinGrade),
2328
2331
  questions,
2329
2332
  groupedQuestions,
2330
2333
  designKey: options.designKey ?? "",
@@ -3580,6 +3583,7 @@ var ConfigFileSchema = z.object({
3580
3583
  excludeNodeTypes: z.array(z.string()).optional(),
3581
3584
  excludeNodeNames: z.array(z.string()).optional(),
3582
3585
  gridBase: z.number().int().positive().optional(),
3586
+ codegenReadyMinGrade: z.enum(["S", "A+", "A", "B+", "B", "C+", "C", "D", "F"]).optional(),
3583
3587
  rules: z.record(z.string(), RuleOverrideSchema).superRefine((rules, ctx) => {
3584
3588
  const unknown = Object.keys(rules).filter((id) => !VALID_RULE_IDS.has(id));
3585
3589
  if (unknown.length > 0) {
@@ -5178,10 +5182,24 @@ defineRule({
5178
5182
  config({ quiet: true });
5179
5183
  var require2 = createRequire(import.meta.url);
5180
5184
  var pkg = require2("../../package.json");
5181
- var server = new McpServer({
5182
- name: "canicode",
5183
- version: pkg.version
5184
- });
5185
+ var SERVER_INSTRUCTIONS = `canicode has three channels. The MCP tools below cover analysis only \u2014 design write-back is a separate channel.
5186
+
5187
+ - **MCP tools** (this server): \`analyze\`, \`gotcha-survey\`, \`list-rules\`, \`docs\`, \`version\`, \`visual-compare\`. Use these for read-only analysis and reports.
5188
+ - **Skills** (slash commands, NOT MCP tools): \`/canicode\`, \`/canicode-gotchas\`, \`/canicode-roundtrip\`. Install with \`npx canicode init\` (Claude Code) or \`npx canicode init --cursor-skills\` (Cursor). The roundtrip skill is the only path that writes back to the Figma file via Plugin API.
5189
+ - **CLI**: \`npx canicode <command>\` \u2014 same JSON shape as MCP tools, useful for CI / scripting.
5190
+
5191
+ If a user asks to "run the roundtrip" or "fix gotchas in Figma" and you don't see \`canicode-roundtrip\` as a slash command, the skill isn't installed yet \u2014 tell them to run \`npx canicode init\`. Don't try to compose roundtrip out of MCP tools; the write-back logic lives in the skill.
5192
+
5193
+ Call \`docs\` with topic \`channels\` for the full matrix.`;
5194
+ var server = new McpServer(
5195
+ {
5196
+ name: "canicode",
5197
+ version: pkg.version
5198
+ },
5199
+ {
5200
+ instructions: SERVER_INSTRUCTIONS
5201
+ }
5202
+ );
5185
5203
  server.tool(
5186
5204
  "analyze",
5187
5205
  `Analyze a Figma design for development-friendliness and AI-friendliness.
@@ -5195,7 +5213,8 @@ Provide a Figma URL or fixture path via the input parameter. Requires FIGMA_TOKE
5195
5213
  configPath: z.string().optional().describe("Path to config JSON file for rule overrides"),
5196
5214
  openReport: z.boolean().optional().describe("Open the generated HTML report in the user's browser. Defaults to false \u2014 opt in only when a visible report is the explicit user request (#365). The HTML file is always written to disk regardless."),
5197
5215
  acknowledgments: z.array(AcknowledgmentSchema).optional().describe("(#371 / ADR-019) Pre-resolved acknowledgments from canicode-authored Figma annotations (e.g. via readCanicodeAcknowledgments in a use_figma batch). Each entry includes nodeId and ruleId; newer annotations may also carry intent, sceneWriteOutcome, and codegenDirective from a canicode-json fenced block (#444). Matching issues are flagged acknowledged and contribute half weight to the density score."),
5198
- scope: z.enum(["page", "component"]).optional().describe("(#404) Override analysis scope \u2014 `page` (screen/section where container bounds are required) or `component` (standalone reusable unit where root FILL is the design contract). Defaults to auto-detection from the root node type: `COMPONENT` / `COMPONENT_SET` / `INSTANCE` roots resolve to `component`, everything else to `page`.")
5216
+ scope: z.enum(["page", "component"]).optional().describe("(#404) Override analysis scope \u2014 `page` (screen/section where container bounds are required) or `component` (standalone reusable unit where root FILL is the design contract). Defaults to auto-detection from the root node type: `COMPONENT` / `COMPONENT_SET` / `INSTANCE` roots resolve to `component`, everything else to `page`."),
5217
+ codegenReadyMinGrade: z.enum(["S", "A+", "A", "B+", "B", "C+", "C", "D", "F"]).optional().describe("Minimum grade for code-gen readiness. Overrides the codegenReadyMinGrade field in configPath. Default: A")
5199
5218
  },
5200
5219
  {
5201
5220
  readOnlyHint: false,
@@ -5203,16 +5222,19 @@ Provide a Figma URL or fixture path via the input parameter. Requires FIGMA_TOKE
5203
5222
  openWorldHint: true,
5204
5223
  title: "Analyze Figma Design"
5205
5224
  },
5206
- async ({ input, token, preset, targetNodeId, configPath, openReport, acknowledgments, scope }) => {
5225
+ async ({ input, token, preset, targetNodeId, configPath, openReport, acknowledgments, scope, codegenReadyMinGrade }) => {
5207
5226
  trackEvent(EVENTS.MCP_TOOL_CALLED, { tool: "analyze" });
5208
5227
  try {
5209
5228
  const { file, nodeId } = await loadFile(input, token);
5210
5229
  const effectiveNodeId = targetNodeId ?? nodeId;
5211
5230
  let configs = preset ? { ...getConfigsWithPreset(preset) } : { ...RULE_CONFIGS };
5231
+ let configFileMinGrade;
5212
5232
  if (configPath) {
5213
5233
  const configFile = await loadConfigFile(configPath);
5214
5234
  configs = mergeConfigs(configs, configFile);
5235
+ configFileMinGrade = configFile.codegenReadyMinGrade;
5215
5236
  }
5237
+ const effectiveMinGrade = codegenReadyMinGrade ?? configFileMinGrade;
5216
5238
  const result = analyzeFile(file, {
5217
5239
  configs,
5218
5240
  ...effectiveNodeId ? { targetNodeId: effectiveNodeId } : {},
@@ -5247,7 +5269,7 @@ Provide a Figma URL or fixture path via the input parameter. Requires FIGMA_TOKE
5247
5269
  content: [
5248
5270
  {
5249
5271
  type: "text",
5250
- text: JSON.stringify(buildResultJson(file.name, result, scores, { fileKey: file.fileKey, designKey: computeDesignKey(input) }), null, 2)
5272
+ text: JSON.stringify(buildResultJson(file.name, result, scores, { fileKey: file.fileKey, designKey: computeDesignKey(input), ...effectiveMinGrade ? { codegenReadyMinGrade: effectiveMinGrade } : {} }), null, 2)
5251
5273
  }
5252
5274
  ]
5253
5275
  };
@@ -5285,7 +5307,8 @@ Provide a Figma URL or fixture path via the input parameter. Requires FIGMA_TOKE
5285
5307
  preset: z.enum(["relaxed", "dev-friendly", "ai-ready", "strict"]).optional().describe("Analysis preset"),
5286
5308
  targetNodeId: z.string().optional().describe("Scope analysis to a specific node ID"),
5287
5309
  configPath: z.string().optional().describe("Path to config JSON file for rule overrides"),
5288
- scope: z.enum(["page", "component"]).optional().describe("(#404) Override analysis scope \u2014 `page` or `component`. Defaults to auto-detection from the root node type.")
5310
+ scope: z.enum(["page", "component"]).optional().describe("(#404) Override analysis scope \u2014 `page` or `component`. Defaults to auto-detection from the root node type."),
5311
+ codegenReadyMinGrade: z.enum(["S", "A+", "A", "B+", "B", "C+", "C", "D", "F"]).optional().describe("Minimum grade for code-gen readiness. Overrides the codegenReadyMinGrade field in configPath. Default: A")
5289
5312
  },
5290
5313
  {
5291
5314
  readOnlyHint: false,
@@ -5293,23 +5316,29 @@ Provide a Figma URL or fixture path via the input parameter. Requires FIGMA_TOKE
5293
5316
  openWorldHint: true,
5294
5317
  title: "Gotcha Survey"
5295
5318
  },
5296
- async ({ input, token, preset, targetNodeId, configPath, scope }) => {
5319
+ async ({ input, token, preset, targetNodeId, configPath, scope, codegenReadyMinGrade }) => {
5297
5320
  trackEvent(EVENTS.MCP_TOOL_CALLED, { tool: "gotcha-survey" });
5298
5321
  try {
5299
5322
  const { file, nodeId } = await loadFile(input, token);
5300
5323
  const effectiveNodeId = targetNodeId ?? nodeId;
5301
5324
  let configs = preset ? { ...getConfigsWithPreset(preset) } : { ...RULE_CONFIGS };
5325
+ let configFileMinGrade;
5302
5326
  if (configPath) {
5303
5327
  const configFile = await loadConfigFile(configPath);
5304
5328
  configs = mergeConfigs(configs, configFile);
5329
+ configFileMinGrade = configFile.codegenReadyMinGrade;
5305
5330
  }
5331
+ const effectiveMinGrade = codegenReadyMinGrade ?? configFileMinGrade;
5306
5332
  const result = analyzeFile(file, {
5307
5333
  configs,
5308
5334
  ...effectiveNodeId ? { targetNodeId: effectiveNodeId } : {},
5309
5335
  ...scope ? { scope } : {}
5310
5336
  });
5311
5337
  const scores = calculateScores(result, configs);
5312
- const survey = generateGotchaSurvey(result, scores, { designKey: computeDesignKey(input) });
5338
+ const survey = generateGotchaSurvey(result, scores, {
5339
+ designKey: computeDesignKey(input),
5340
+ ...effectiveMinGrade ? { codegenReadyMinGrade: effectiveMinGrade } : {}
5341
+ });
5313
5342
  trackEvent(EVENTS.ANALYSIS_COMPLETED, {
5314
5343
  nodeCount: result.nodeCount,
5315
5344
  issueCount: result.issues.length,
@@ -5398,6 +5427,7 @@ server.tool(
5398
5427
 
5399
5428
  Available topics:
5400
5429
  - setup: Installation and token configuration
5430
+ - channels: Which canicode features are MCP tools vs skills vs CLI \u2014 call this when a user asks for a feature you can't find as an MCP tool
5401
5431
  - scoring: Scoring model formula (density+diversity, severity weights, grades)
5402
5432
  - rules: All rule IDs with default scores and severity
5403
5433
  - config: Config overrides (scores, severity, node exclusions, thresholds)
@@ -5407,7 +5437,7 @@ Available topics:
5407
5437
 
5408
5438
  Use this when the user asks about how to use canicode, configuration, rules, visual comparison, or any feature.`,
5409
5439
  {
5410
- topic: z.enum(["all", "setup", "scoring", "rules", "config", "visual-compare", "design-tree"]).optional().describe("Topic to retrieve. Default: all")
5440
+ topic: z.enum(["all", "setup", "channels", "scoring", "rules", "config", "visual-compare", "design-tree"]).optional().describe("Topic to retrieve. Default: all")
5411
5441
  },
5412
5442
  {
5413
5443
  readOnlyHint: true,
@@ -5418,6 +5448,23 @@ Use this when the user asks about how to use canicode, configuration, rules, vis
5418
5448
  async ({ topic }) => {
5419
5449
  const selectedTopic = topic ?? "all";
5420
5450
  const inlineTopics = {
5451
+ "channels": `# Channels \u2014 Where Each Feature Lives
5452
+
5453
+ canicode is delivered through three channels. Knowing which is which prevents the common confusion where an agent searches the MCP tool list for a feature that actually lives in a skill.
5454
+
5455
+ | Feature | Channel | How to invoke |
5456
+ |---------|---------|---------------|
5457
+ | Analyze a Figma design | MCP tool / CLI | \`analyze\` (this server) or \`npx canicode analyze <url>\` |
5458
+ | Generate gotcha questions | MCP tool / CLI | \`gotcha-survey\` or \`npx canicode gotcha-survey <url> --json\` |
5459
+ | List rules / get docs / version | MCP tool | \`list-rules\`, \`docs\`, \`version\` |
5460
+ | Pixel-compare Figma vs HTML | MCP tool / CLI | \`visual-compare\` |
5461
+ | Q&A workflow that saves answers locally | **Skill** (slash command) | \`/canicode-gotchas <url>\` \u2014 install with \`npx canicode init\` |
5462
+ | Roundtrip: analyze \u2192 gotcha \u2192 write back to Figma \u2192 re-analyze | **Skill** (slash command) | \`/canicode-roundtrip <url>\` \u2014 install with \`npx canicode init\` |
5463
+ | Lightweight analyze-only skill (no MCP install) | **Skill** (slash command) | \`/canicode <url>\` \u2014 install with \`npx canicode init\` |
5464
+
5465
+ **Skills are NOT MCP tools.** They are slash commands installed under \`.claude/skills/\` (Claude Code) or \`.cursor/skills/\` (Cursor). If \`canicode-roundtrip\` is not in the slash-command list, the skill isn't installed \u2014 run \`npx canicode init\` (or \`--cursor-skills\` for Cursor).
5466
+
5467
+ The roundtrip skill is the **only** path that writes back to the Figma file (via the Figma MCP server's \`use_figma\`). Don't try to compose roundtrip out of \`analyze\` + \`gotcha-survey\` MCP calls \u2014 the write-back orchestration lives inside the skill.`,
5421
5468
  "setup": `# Setup
5422
5469
 
5423
5470
  ## CLI