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.
- package/dist/cli/index.js +26 -11
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +19 -5
- package/dist/index.js +8 -5
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +63 -16
- package/dist/mcp/server.js.map +1 -1
- package/docs/CUSTOMIZATION.md +2 -0
- package/package.json +2 -1
- package/skills/canicode-gotchas/SKILL.md +21 -5
- package/skills/canicode-roundtrip/SKILL.md +21 -9
- package/skills/canicode-roundtrip/helpers-bootstrap.js +1 -1
- package/skills/canicode-roundtrip/helpers-installer.js +1 -1
- package/skills/cursor/canicode-gotchas/SKILL.md +21 -5
- package/skills/cursor/canicode-roundtrip/SKILL.md +21 -9
- package/skills/cursor/canicode-roundtrip/helpers-bootstrap.js +1 -1
- package/skills/cursor/canicode-roundtrip/helpers-installer.js +1 -1
package/dist/mcp/server.js
CHANGED
|
@@ -1863,9 +1863,11 @@ function computeApplyContext(violation, instanceContext) {
|
|
|
1863
1863
|
}
|
|
1864
1864
|
|
|
1865
1865
|
// package.json
|
|
1866
|
-
var version = "0.11.
|
|
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
|
-
|
|
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
|
|
5182
|
-
|
|
5183
|
-
|
|
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, {
|
|
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
|