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/cli/index.js
CHANGED
|
@@ -4245,9 +4245,11 @@ function computeApplyContext(violation, instanceContext) {
|
|
|
4245
4245
|
}
|
|
4246
4246
|
|
|
4247
4247
|
// package.json
|
|
4248
|
-
var version2 = "0.11.
|
|
4248
|
+
var version2 = "0.11.3";
|
|
4249
4249
|
|
|
4250
4250
|
// src/core/engine/scoring.ts
|
|
4251
|
+
var GRADE_ORDER = ["S", "A+", "A", "B+", "B", "C+", "C", "D", "F"];
|
|
4252
|
+
var DEFAULT_CODEGEN_READY_MIN_GRADE = "A";
|
|
4251
4253
|
function computeTotalScorePerCategory(configs) {
|
|
4252
4254
|
const totals = Object.fromEntries(
|
|
4253
4255
|
CATEGORIES.map((c) => [c, 0])
|
|
@@ -4274,8 +4276,9 @@ function calculateGrade(percentage) {
|
|
|
4274
4276
|
if (percentage >= 50) return "D";
|
|
4275
4277
|
return "F";
|
|
4276
4278
|
}
|
|
4277
|
-
function isReadyForCodeGen(grade) {
|
|
4278
|
-
|
|
4279
|
+
function isReadyForCodeGen(grade, minGrade) {
|
|
4280
|
+
const threshold = minGrade ?? DEFAULT_CODEGEN_READY_MIN_GRADE;
|
|
4281
|
+
return GRADE_ORDER.indexOf(grade) <= GRADE_ORDER.indexOf(threshold);
|
|
4279
4282
|
}
|
|
4280
4283
|
function clamp(value, min, max) {
|
|
4281
4284
|
return Math.max(min, Math.min(max, value));
|
|
@@ -4468,7 +4471,7 @@ function buildResultJson(fileName, result, scores, options) {
|
|
|
4468
4471
|
scope: result.scope,
|
|
4469
4472
|
issueCount: result.issues.length,
|
|
4470
4473
|
acknowledgedCount: scores.summary.acknowledgedCount,
|
|
4471
|
-
isReadyForCodeGen: isReadyForCodeGen(scores.overall.grade),
|
|
4474
|
+
isReadyForCodeGen: isReadyForCodeGen(scores.overall.grade, options?.codegenReadyMinGrade),
|
|
4472
4475
|
blockingIssueCount: scores.summary.blocking,
|
|
4473
4476
|
scores: {
|
|
4474
4477
|
overall: scores.overall,
|
|
@@ -4505,6 +4508,7 @@ var ConfigFileSchema = z.object({
|
|
|
4505
4508
|
excludeNodeTypes: z.array(z.string()).optional(),
|
|
4506
4509
|
excludeNodeNames: z.array(z.string()).optional(),
|
|
4507
4510
|
gridBase: z.number().int().positive().optional(),
|
|
4511
|
+
codegenReadyMinGrade: z.enum(["S", "A+", "A", "B+", "B", "C+", "C", "D", "F"]).optional(),
|
|
4508
4512
|
rules: z.record(z.string(), RuleOverrideSchema).superRefine((rules, ctx) => {
|
|
4509
4513
|
const unknown = Object.keys(rules).filter((id) => !VALID_RULE_IDS.has(id));
|
|
4510
4514
|
if (unknown.length > 0) {
|
|
@@ -5731,13 +5735,14 @@ var AnalyzeOptionsSchema = z.object({
|
|
|
5731
5735
|
noOpen: z.boolean().optional(),
|
|
5732
5736
|
json: z.boolean().optional(),
|
|
5733
5737
|
acknowledgments: z.string().optional(),
|
|
5734
|
-
scope: z.enum(["page", "component"]).optional()
|
|
5738
|
+
scope: z.enum(["page", "component"]).optional(),
|
|
5739
|
+
readyMinGrade: z.enum(["S", "A+", "A", "B+", "B", "C+", "C", "D", "F"]).optional()
|
|
5735
5740
|
});
|
|
5736
5741
|
function registerAnalyze(cli2) {
|
|
5737
5742
|
cli2.command("analyze <input>", "Analyze a Figma file or JSON fixture").option("--preset <preset>", "Analysis preset (relaxed | dev-friendly | ai-ready | strict)").option("--output <path>", "HTML report output path").option("--token <token>", "Figma API token (or use FIGMA_TOKEN env var)").option(
|
|
5738
5743
|
"--api",
|
|
5739
5744
|
"No-op for Figma URL inputs (file data is always fetched via REST). Accepted for CLI parity with `gotcha-survey` (#461)."
|
|
5740
|
-
).option("--screenshot", "Include screenshot comparison in report (requires ANTHROPIC_API_KEY)").option("--config <path>", "Path to config JSON file (override rule scores/settings)").option("--no-open", "Don't open report in browser after analysis").option("--json", "Output JSON results to stdout (same format as MCP)").option("--acknowledgments <path>", "(#371 / ADR-019) Path to JSON acknowledgments from canicode Figma annotations (nodeId, ruleId; optional intent / sceneWriteOutcome / codegenDirective per #444). Matching issues are flagged acknowledged and contribute half weight to density.").option("--scope <scope>", "(#404) Override analysis scope: `page` (screen/section \u2014 container bounds are required) or `component` (standalone reusable unit \u2014 root FILL is the design contract). Defaults to auto-detection from the root node type.").example(" canicode analyze https://www.figma.com/design/ABC123/MyDesign").example(" canicode analyze ./fixtures/my-design --output report.html").example(" canicode analyze ./fixtures/my-design --config ./my-config.json").action(async (input, rawOptions) => {
|
|
5745
|
+
).option("--screenshot", "Include screenshot comparison in report (requires ANTHROPIC_API_KEY)").option("--config <path>", "Path to config JSON file (override rule scores/settings)").option("--no-open", "Don't open report in browser after analysis").option("--json", "Output JSON results to stdout (same format as MCP)").option("--acknowledgments <path>", "(#371 / ADR-019) Path to JSON acknowledgments from canicode Figma annotations (nodeId, ruleId; optional intent / sceneWriteOutcome / codegenDirective per #444). Matching issues are flagged acknowledged and contribute half weight to density.").option("--scope <scope>", "(#404) Override analysis scope: `page` (screen/section \u2014 container bounds are required) or `component` (standalone reusable unit \u2014 root FILL is the design contract). Defaults to auto-detection from the root node type.").option("--ready-min-grade <grade>", "Minimum grade for code-gen readiness (S | A+ | A | B+ | B | C+ | C | D | F). Overrides configPath codegenReadyMinGrade. Default: A").example(" canicode analyze https://www.figma.com/design/ABC123/MyDesign").example(" canicode analyze ./fixtures/my-design --output report.html").example(" canicode analyze ./fixtures/my-design --config ./my-config.json").action(async (input, rawOptions) => {
|
|
5741
5746
|
const parseResult = AnalyzeOptionsSchema.safeParse(rawOptions);
|
|
5742
5747
|
if (!parseResult.success) {
|
|
5743
5748
|
const msg = parseResult.error.issues.map((i) => `--${i.path.join(".")}: ${i.message}`).join("\n");
|
|
@@ -5800,13 +5805,16 @@ Analyzing: ${file.name}`);
|
|
|
5800
5805
|
let configs = options.preset ? { ...getConfigsWithPreset(options.preset) } : { ...RULE_CONFIGS };
|
|
5801
5806
|
let excludeNodeNames;
|
|
5802
5807
|
let excludeNodeTypes;
|
|
5808
|
+
let codegenReadyMinGrade;
|
|
5803
5809
|
if (options.config) {
|
|
5804
5810
|
const configFile = await loadConfigFile(options.config);
|
|
5805
5811
|
configs = mergeConfigs(configs, configFile);
|
|
5806
5812
|
excludeNodeNames = configFile.excludeNodeNames;
|
|
5807
5813
|
excludeNodeTypes = configFile.excludeNodeTypes;
|
|
5814
|
+
codegenReadyMinGrade = configFile.codegenReadyMinGrade;
|
|
5808
5815
|
log(`Config loaded: ${options.config}`);
|
|
5809
5816
|
}
|
|
5817
|
+
const effectiveMinGrade = options.readyMinGrade ?? codegenReadyMinGrade;
|
|
5810
5818
|
let acknowledgments;
|
|
5811
5819
|
if (options.acknowledgments) {
|
|
5812
5820
|
const ackPath = resolve(options.acknowledgments);
|
|
@@ -5832,7 +5840,7 @@ Analyzing: ${file.name}`);
|
|
|
5832
5840
|
log(`Nodes: ${result.nodeCount} (max depth: ${result.maxDepth})`);
|
|
5833
5841
|
const scores = calculateScores(result, configs);
|
|
5834
5842
|
if (options.json) {
|
|
5835
|
-
console.log(JSON.stringify(buildResultJson(file.name, result, scores, { fileKey: file.fileKey, designKey: computeDesignKey(input) }), null, 2));
|
|
5843
|
+
console.log(JSON.stringify(buildResultJson(file.name, result, scores, { fileKey: file.fileKey, designKey: computeDesignKey(input), ...effectiveMinGrade ? { codegenReadyMinGrade: effectiveMinGrade } : {} }), null, 2));
|
|
5836
5844
|
if (scores.overall.grade === "F") {
|
|
5837
5845
|
process.exitCode = 1;
|
|
5838
5846
|
}
|
|
@@ -6115,7 +6123,7 @@ function generateGotchaSurvey(result, scores, options = {}) {
|
|
|
6115
6123
|
const suggestedDefaultApply = propagationCandidates >= PROPAGATION_CANDIDATE_THRESHOLD;
|
|
6116
6124
|
return {
|
|
6117
6125
|
designGrade: grade,
|
|
6118
|
-
isReadyForCodeGen: isReadyForCodeGen(grade),
|
|
6126
|
+
isReadyForCodeGen: isReadyForCodeGen(grade, options.codegenReadyMinGrade),
|
|
6119
6127
|
questions,
|
|
6120
6128
|
groupedQuestions,
|
|
6121
6129
|
designKey: options.designKey ?? "",
|
|
@@ -6273,24 +6281,31 @@ var GotchaSurveyOptionsSchema = z.object({
|
|
|
6273
6281
|
config: z.string().optional(),
|
|
6274
6282
|
targetNodeId: z.string().optional(),
|
|
6275
6283
|
json: z.boolean().optional(),
|
|
6276
|
-
scope: z.enum(["page", "component"]).optional()
|
|
6284
|
+
scope: z.enum(["page", "component"]).optional(),
|
|
6285
|
+
readyMinGrade: z.enum(["S", "A+", "A", "B+", "B", "C+", "C", "D", "F"]).optional()
|
|
6277
6286
|
});
|
|
6278
6287
|
async function runGotchaSurvey(input, options) {
|
|
6279
6288
|
void options.api;
|
|
6280
6289
|
const { file, nodeId } = await loadFile(input, options.token);
|
|
6281
6290
|
const effectiveNodeId = options.targetNodeId ?? nodeId;
|
|
6282
6291
|
let configs = options.preset ? { ...getConfigsWithPreset(options.preset) } : { ...RULE_CONFIGS };
|
|
6292
|
+
let codegenReadyMinGrade;
|
|
6283
6293
|
if (options.config) {
|
|
6284
6294
|
const configFile = await loadConfigFile(options.config);
|
|
6285
6295
|
configs = mergeConfigs(configs, configFile);
|
|
6296
|
+
codegenReadyMinGrade = configFile.codegenReadyMinGrade;
|
|
6286
6297
|
}
|
|
6298
|
+
const effectiveMinGrade = options.readyMinGrade ?? codegenReadyMinGrade;
|
|
6287
6299
|
const result = analyzeFile(file, {
|
|
6288
6300
|
configs,
|
|
6289
6301
|
...effectiveNodeId ? { targetNodeId: effectiveNodeId } : {},
|
|
6290
6302
|
...options.scope ? { scope: options.scope } : {}
|
|
6291
6303
|
});
|
|
6292
6304
|
const scores = calculateScores(result, configs);
|
|
6293
|
-
return generateGotchaSurvey(result, scores, {
|
|
6305
|
+
return generateGotchaSurvey(result, scores, {
|
|
6306
|
+
designKey: computeDesignKey(input),
|
|
6307
|
+
...effectiveMinGrade ? { codegenReadyMinGrade: effectiveMinGrade } : {}
|
|
6308
|
+
});
|
|
6294
6309
|
}
|
|
6295
6310
|
function formatHumanSummary(survey) {
|
|
6296
6311
|
const lines = [
|
|
@@ -6308,7 +6323,7 @@ function registerGotchaSurvey(cli2) {
|
|
|
6308
6323
|
cli2.command("gotcha-survey <input>", "Generate a gotcha survey from a Figma design analysis").option("--preset <preset>", "Analysis preset (relaxed | dev-friendly | ai-ready | strict)").option("--token <token>", "Figma API token (or use FIGMA_TOKEN env var)").option(
|
|
6309
6324
|
"--api",
|
|
6310
6325
|
"No-op for Figma URL inputs (file data is always fetched via REST). Accepted for CLI parity with `analyze` (#461)."
|
|
6311
|
-
).option("--config <path>", "Path to config JSON file (override rule scores/settings)").option("--target-node-id <id>", "Scope analysis to a specific node ID").option("--scope <scope>", "(#404) Override analysis scope: `page` or `component`. Defaults to auto-detection from the root node type.").option("--json", "Output GotchaSurvey JSON to stdout (same format as MCP)").example(" canicode gotcha-survey https://www.figma.com/design/ABC123/MyDesign --json").example(" canicode gotcha-survey ./fixtures/my-design --json").action(async (input, rawOptions) => {
|
|
6326
|
+
).option("--config <path>", "Path to config JSON file (override rule scores/settings)").option("--target-node-id <id>", "Scope analysis to a specific node ID").option("--scope <scope>", "(#404) Override analysis scope: `page` or `component`. Defaults to auto-detection from the root node type.").option("--json", "Output GotchaSurvey JSON to stdout (same format as MCP)").option("--ready-min-grade <grade>", "Minimum grade for code-gen readiness (S | A+ | A | B+ | B | C+ | C | D | F). Overrides configPath codegenReadyMinGrade. Default: A").example(" canicode gotcha-survey https://www.figma.com/design/ABC123/MyDesign --json").example(" canicode gotcha-survey ./fixtures/my-design --json").action(async (input, rawOptions) => {
|
|
6312
6327
|
const parseResult = GotchaSurveyOptionsSchema.safeParse(rawOptions);
|
|
6313
6328
|
if (!parseResult.success) {
|
|
6314
6329
|
const msg = parseResult.error.issues.map((i) => `--${i.path.join(".")}: ${i.message}`).join("\n");
|