canicode 0.3.0 → 0.3.2

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.
@@ -464,11 +464,15 @@ var RuleEngine = class {
464
464
  enabledRuleIds;
465
465
  disabledRuleIds;
466
466
  targetNodeId;
467
+ excludeNamePattern;
468
+ excludeNodeTypes;
467
469
  constructor(options = {}) {
468
470
  this.configs = options.configs ?? RULE_CONFIGS;
469
471
  this.enabledRuleIds = options.enabledRules ? new Set(options.enabledRules) : null;
470
472
  this.disabledRuleIds = new Set(options.disabledRules ?? []);
471
473
  this.targetNodeId = options.targetNodeId;
474
+ this.excludeNamePattern = options.excludeNodeNames && options.excludeNodeNames.length > 0 ? new RegExp(`\\b(${options.excludeNodeNames.join("|")})\\b`, "i") : null;
475
+ this.excludeNodeTypes = options.excludeNodeTypes && options.excludeNodeTypes.length > 0 ? new Set(options.excludeNodeTypes) : null;
472
476
  }
473
477
  /**
474
478
  * Analyze a Figma file and return issues
@@ -522,6 +526,12 @@ var RuleEngine = class {
522
526
  */
523
527
  traverseAndCheck(node, file, rules, maxDepth, issues, depth, path, parent, siblings) {
524
528
  const nodePath = [...path, node.name];
529
+ if (this.excludeNodeTypes && this.excludeNodeTypes.has(node.type)) {
530
+ return;
531
+ }
532
+ if (this.excludeNamePattern && this.excludeNamePattern.test(node.name)) {
533
+ return;
534
+ }
525
535
  const context = {
526
536
  file,
527
537
  parent,
@@ -1380,7 +1390,7 @@ function generateHtmlReport(file, result, scores, options) {
1380
1390
  ${CATEGORIES.map((cat) => {
1381
1391
  const cs = scores.byCategory[cat];
1382
1392
  const desc = CATEGORY_DESCRIPTIONS[cat];
1383
- return ` <div class="flex flex-col items-center group relative">
1393
+ return ` <a href="#cat-${cat}" class="flex flex-col items-center group relative cursor-pointer no-underline text-foreground hover:opacity-80 transition-opacity">
1384
1394
  ${renderGaugeSvg(cs.percentage, 100, 7)}
1385
1395
  <span class="text-xs font-medium mt-2.5 text-center leading-tight">${CATEGORY_LABELS[cat]}</span>
1386
1396
  <span class="text-[11px] text-muted-foreground">${cs.issueCount} issues</span>
@@ -1388,7 +1398,7 @@ ${CATEGORIES.map((cat) => {
1388
1398
  ${esc(desc)}
1389
1399
  <div class="absolute top-full left-1/2 -translate-x-1/2 border-4 border-transparent border-t-zinc-900"></div>
1390
1400
  </div>
1391
- </div>`;
1401
+ </a>`;
1392
1402
  }).join("\n")}
1393
1403
  </div>
1394
1404
  </section>
@@ -1526,7 +1536,7 @@ function renderCategory(cat, scores, issues, fileKey, screenshotMap, figmaToken)
1526
1536
  for (const sev of SEVERITY_ORDER) bySeverity.set(sev, []);
1527
1537
  for (const issue of issues) bySeverity.get(issue.config.severity)?.push(issue);
1528
1538
  return `
1529
- <details class="bg-card border border-border rounded-lg shadow-sm overflow-hidden group"${hasProblems ? " open" : ""}>
1539
+ <details id="cat-${cat}" class="bg-card border border-border rounded-lg shadow-sm overflow-hidden group"${hasProblems ? " open" : ""}>
1530
1540
  <summary class="px-5 py-3.5 flex items-center gap-3 cursor-pointer hover:bg-muted/50 transition-colors select-none">
1531
1541
  <span class="inline-flex items-center justify-center w-10 h-6 rounded-md text-xs font-bold border ${scoreBadgeStyle(cs.percentage)}">${cs.percentage}</span>
1532
1542
  <div class="flex-1 min-w-0">
@@ -3000,6 +3010,60 @@ server.tool(
3000
3010
  };
3001
3011
  }
3002
3012
  );
3013
+ server.tool(
3014
+ "docs",
3015
+ `Get the customization guide for CanICode.
3016
+
3017
+ Returns documentation on:
3018
+ - Config overrides (excludeNodeNames, excludeNodeTypes, gridBase, colorTolerance, per-rule score/severity/enabled)
3019
+ - Custom rules (how to add project-specific checks)
3020
+ - All 39 rule IDs with default scores and severity
3021
+ - Example configs (strict, relaxed, mobile-first)
3022
+
3023
+ Use this when the user asks about customization, configuration, rule settings, or how to adjust scores.`,
3024
+ {
3025
+ topic: z.enum(["all", "config", "custom-rules", "rules"]).optional().describe("Specific topic: config (overrides), custom-rules (adding new rules), rules (all rule IDs). Default: all")
3026
+ },
3027
+ async ({ topic }) => {
3028
+ const { readFile: readFile4 } = await import('fs/promises');
3029
+ const { resolve: resolve4, dirname } = await import('path');
3030
+ const { fileURLToPath } = await import('url');
3031
+ try {
3032
+ const __dirname = dirname(fileURLToPath(import.meta.url));
3033
+ const docPath = resolve4(__dirname, "../../docs/CUSTOMIZATION.md");
3034
+ let content;
3035
+ try {
3036
+ content = await readFile4(docPath, "utf-8");
3037
+ } catch {
3038
+ const altPath = resolve4(__dirname, "../docs/CUSTOMIZATION.md");
3039
+ content = await readFile4(altPath, "utf-8");
3040
+ }
3041
+ if (topic && topic !== "all") {
3042
+ const sections = {
3043
+ "config": "## Config Overrides",
3044
+ "custom-rules": "## Custom Rules",
3045
+ "rules": "### All Rule IDs"
3046
+ };
3047
+ const header = sections[topic];
3048
+ if (header) {
3049
+ const startIdx = content.indexOf(header);
3050
+ if (startIdx !== -1) {
3051
+ const nextH2 = content.indexOf("\n## ", startIdx + header.length);
3052
+ content = nextH2 !== -1 ? content.slice(startIdx, nextH2) : content.slice(startIdx);
3053
+ }
3054
+ }
3055
+ }
3056
+ return {
3057
+ content: [{ type: "text", text: content }]
3058
+ };
3059
+ } catch {
3060
+ return {
3061
+ content: [{ type: "text", text: "Customization guide not found. See: https://github.com/let-sunny/canicode/blob/main/docs/CUSTOMIZATION.md" }],
3062
+ isError: true
3063
+ };
3064
+ }
3065
+ }
3066
+ );
3003
3067
  async function main() {
3004
3068
  const transport = new StdioServerTransport();
3005
3069
  await server.connect(transport);