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 CHANGED
@@ -4245,9 +4245,11 @@ function computeApplyContext(violation, instanceContext) {
4245
4245
  }
4246
4246
 
4247
4247
  // package.json
4248
- var version2 = "0.11.2";
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
- return grade === "S" || grade === "A+" || grade === "A";
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, { designKey: computeDesignKey(input) });
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");