canicode 0.3.2 → 0.4.0

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.
@@ -10,6 +10,204 @@ import { join, resolve, basename } from 'path';
10
10
  import { readFile } from 'fs/promises';
11
11
  import { homedir } from 'os';
12
12
 
13
+ var __defProp = Object.defineProperty;
14
+ var __getOwnPropNames = Object.getOwnPropertyNames;
15
+ var __esm = (fn, res) => function __init() {
16
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
17
+ };
18
+ var __export = (target, all) => {
19
+ for (var name in all)
20
+ __defProp(target, name, { get: all[name], enumerable: true });
21
+ };
22
+
23
+ // src/monitoring/browser.ts
24
+ var browser_exports = {};
25
+ __export(browser_exports, {
26
+ initBrowserMonitoring: () => initBrowserMonitoring,
27
+ shutdownBrowserMonitoring: () => shutdownBrowserMonitoring,
28
+ trackBrowserError: () => trackBrowserError,
29
+ trackBrowserEvent: () => trackBrowserEvent
30
+ });
31
+ function getGlobal() {
32
+ return globalThis;
33
+ }
34
+ function injectScript(src) {
35
+ return new Promise((resolve4, reject) => {
36
+ const g = getGlobal();
37
+ const doc = g["document"];
38
+ if (!doc) {
39
+ resolve4();
40
+ return;
41
+ }
42
+ const script = doc["createElement"]("script");
43
+ script["src"] = src;
44
+ script["async"] = true;
45
+ script["onload"] = () => resolve4();
46
+ script["onerror"] = () => reject(new Error(`Failed to load script: ${src}`));
47
+ doc["head"]["appendChild"](script);
48
+ });
49
+ }
50
+ async function initBrowserMonitoring(config2) {
51
+ if (config2.enabled === false) return;
52
+ const g = getGlobal();
53
+ if (!g["document"]) return;
54
+ monitoringEnabled = true;
55
+ if (config2.posthogApiKey) {
56
+ try {
57
+ await injectScript("https://us-assets.i.posthog.com/static/array.js");
58
+ g["posthog"]?.["init"]?.(config2.posthogApiKey, {
59
+ api_host: "https://us.i.posthog.com",
60
+ autocapture: false,
61
+ capture_pageview: true
62
+ });
63
+ } catch {
64
+ }
65
+ }
66
+ if (config2.sentryDsn) {
67
+ try {
68
+ await injectScript("https://browser.sentry-cdn.com/8.0.0/bundle.min.js");
69
+ g["Sentry"]?.["init"]?.({
70
+ dsn: config2.sentryDsn,
71
+ environment: config2.environment ?? "web",
72
+ release: config2.version,
73
+ tracesSampleRate: 0
74
+ });
75
+ } catch {
76
+ }
77
+ }
78
+ }
79
+ function trackBrowserEvent(event, properties) {
80
+ if (!monitoringEnabled) return;
81
+ try {
82
+ getGlobal()["posthog"]?.["capture"]?.(event, properties);
83
+ } catch {
84
+ }
85
+ }
86
+ function trackBrowserError(error, context) {
87
+ if (!monitoringEnabled) return;
88
+ try {
89
+ getGlobal()["Sentry"]?.["captureException"]?.(
90
+ error,
91
+ context ? { extra: context } : void 0
92
+ );
93
+ } catch {
94
+ }
95
+ try {
96
+ getGlobal()["posthog"]?.["capture"]?.("error", {
97
+ error: error.message,
98
+ ...context
99
+ });
100
+ } catch {
101
+ }
102
+ }
103
+ async function shutdownBrowserMonitoring() {
104
+ monitoringEnabled = false;
105
+ }
106
+ var monitoringEnabled;
107
+ var init_browser = __esm({
108
+ "src/monitoring/browser.ts"() {
109
+ monitoringEnabled = false;
110
+ }
111
+ });
112
+
113
+ // src/monitoring/node.ts
114
+ var node_exports = {};
115
+ __export(node_exports, {
116
+ initNodeMonitoring: () => initNodeMonitoring,
117
+ shutdownNodeMonitoring: () => shutdownNodeMonitoring,
118
+ trackNodeError: () => trackNodeError,
119
+ trackNodeEvent: () => trackNodeEvent
120
+ });
121
+ async function initNodeMonitoring(config2) {
122
+ if (config2.enabled === false) return;
123
+ monitoringEnabled2 = true;
124
+ commonProps = {
125
+ _sdk: "canicode",
126
+ _sdk_version: config2.version ?? "unknown",
127
+ _env: config2.environment ?? "unknown"
128
+ };
129
+ if (config2.posthogApiKey) {
130
+ try {
131
+ const mod = await import('posthog-node');
132
+ const PostHog = mod.PostHog;
133
+ posthogClient = new PostHog(config2.posthogApiKey, {
134
+ host: "https://us.i.posthog.com",
135
+ flushAt: 10,
136
+ flushInterval: 1e4
137
+ });
138
+ } catch {
139
+ }
140
+ }
141
+ if (config2.sentryDsn) {
142
+ try {
143
+ const mod = await import('@sentry/node');
144
+ sentryModule = mod;
145
+ sentryModule.init({
146
+ dsn: config2.sentryDsn,
147
+ environment: config2.environment ?? "cli",
148
+ release: config2.version,
149
+ tracesSampleRate: 0
150
+ });
151
+ } catch {
152
+ }
153
+ }
154
+ }
155
+ function trackNodeEvent(event, properties) {
156
+ if (!monitoringEnabled2 || !posthogClient) return;
157
+ try {
158
+ const captureOpts = {
159
+ distinctId: "anonymous",
160
+ event
161
+ };
162
+ captureOpts.properties = { ...commonProps, ...properties };
163
+ posthogClient.capture(captureOpts);
164
+ } catch {
165
+ }
166
+ }
167
+ function trackNodeError(error, context) {
168
+ if (!monitoringEnabled2) return;
169
+ try {
170
+ sentryModule?.captureException(error, context ? { extra: context } : void 0);
171
+ } catch {
172
+ }
173
+ try {
174
+ posthogClient?.capture({
175
+ distinctId: "anonymous",
176
+ event: "cic_error",
177
+ properties: { ...commonProps, error: error.message, ...context }
178
+ });
179
+ } catch {
180
+ }
181
+ }
182
+ async function shutdownNodeMonitoring() {
183
+ if (!monitoringEnabled2) return;
184
+ const tasks = [];
185
+ if (posthogClient) {
186
+ tasks.push(
187
+ posthogClient.shutdown().catch(() => {
188
+ })
189
+ );
190
+ }
191
+ if (sentryModule) {
192
+ tasks.push(
193
+ sentryModule.close(2e3).catch(() => {
194
+ })
195
+ );
196
+ }
197
+ await Promise.allSettled(tasks);
198
+ posthogClient = null;
199
+ sentryModule = null;
200
+ monitoringEnabled2 = false;
201
+ }
202
+ var posthogClient, sentryModule, monitoringEnabled2, commonProps;
203
+ var init_node = __esm({
204
+ "src/monitoring/node.ts"() {
205
+ posthogClient = null;
206
+ sentryModule = null;
207
+ monitoringEnabled2 = false;
208
+ commonProps = {};
209
+ }
210
+ });
13
211
  var CategorySchema = z.enum([
14
212
  "layout",
15
213
  "token",
@@ -147,7 +345,7 @@ var RULE_CONFIGS = {
147
345
  score: -2,
148
346
  enabled: true,
149
347
  options: {
150
- gridBase: 8
348
+ gridBase: 4
151
349
  }
152
350
  },
153
351
  "magic-number-spacing": {
@@ -155,7 +353,7 @@ var RULE_CONFIGS = {
155
353
  score: -4,
156
354
  enabled: true,
157
355
  options: {
158
- gridBase: 8
356
+ gridBase: 4
159
357
  }
160
358
  },
161
359
  "raw-shadow": {
@@ -726,6 +924,12 @@ function transformNode(node) {
726
924
  if ("layoutPositioning" in node && node.layoutPositioning) {
727
925
  base.layoutPositioning = node.layoutPositioning;
728
926
  }
927
+ if ("layoutSizingHorizontal" in node && node.layoutSizingHorizontal) {
928
+ base.layoutSizingHorizontal = node.layoutSizingHorizontal;
929
+ }
930
+ if ("layoutSizingVertical" in node && node.layoutSizingVertical) {
931
+ base.layoutSizingVertical = node.layoutSizingVertical;
932
+ }
729
933
  if ("primaryAxisAlignItems" in node) {
730
934
  base.primaryAxisAlignItems = node.primaryAxisAlignItems;
731
935
  }
@@ -1018,6 +1222,15 @@ function getReportsDir() {
1018
1222
  function ensureReportsDir() {
1019
1223
  ensureDir(REPORTS_DIR);
1020
1224
  }
1225
+ function getTelemetryEnabled() {
1226
+ return readConfig().telemetry !== false;
1227
+ }
1228
+ function getPosthogApiKey() {
1229
+ return process.env["POSTHOG_API_KEY"] ?? readConfig().posthogApiKey;
1230
+ }
1231
+ function getSentryDsn() {
1232
+ return process.env["SENTRY_DSN"] ?? readConfig().sentryDsn;
1233
+ }
1021
1234
 
1022
1235
  // src/core/loader.ts
1023
1236
  function isFigmaUrl(input) {
@@ -1453,7 +1666,7 @@ ${figmaToken ? ` <script>
1453
1666
  const res = await fetch('https://api.figma.com/v1/files/' + fileKey + '/comments', {
1454
1667
  method: 'POST',
1455
1668
  headers: { 'X-FIGMA-TOKEN': FIGMA_TOKEN, 'Content-Type': 'application/json' },
1456
- body: JSON.stringify({ message: commentBody, client_meta: { node_id: nodeId } }),
1669
+ body: JSON.stringify({ message: commentBody, client_meta: { node_id: nodeId, node_offset: { x: 0, y: 0 } } }),
1457
1670
  });
1458
1671
  if (!res.ok) throw new Error(await res.text());
1459
1672
  btn.textContent = 'Sent \\u2713';
@@ -1707,6 +1920,83 @@ function createPromptBasedCheck(_cr) {
1707
1920
  };
1708
1921
  }
1709
1922
 
1923
+ // src/monitoring/events.ts
1924
+ var EVENT_PREFIX = "cic_";
1925
+ var EVENTS = {
1926
+ // Analysis
1927
+ ANALYSIS_STARTED: `${EVENT_PREFIX}analysis_started`,
1928
+ ANALYSIS_COMPLETED: `${EVENT_PREFIX}analysis_completed`,
1929
+ ANALYSIS_FAILED: `${EVENT_PREFIX}analysis_failed`,
1930
+ // Report
1931
+ REPORT_GENERATED: `${EVENT_PREFIX}report_generated`,
1932
+ COMMENT_POSTED: `${EVENT_PREFIX}comment_posted`,
1933
+ COMMENT_FAILED: `${EVENT_PREFIX}comment_failed`,
1934
+ // MCP
1935
+ MCP_TOOL_CALLED: `${EVENT_PREFIX}mcp_tool_called`,
1936
+ // CLI
1937
+ CLI_COMMAND: `${EVENT_PREFIX}cli_command`,
1938
+ CLI_INIT: `${EVENT_PREFIX}cli_init`
1939
+ };
1940
+
1941
+ // src/monitoring/index.ts
1942
+ var _trackEvent = () => {
1943
+ };
1944
+ var _trackError = () => {
1945
+ };
1946
+ var _shutdown = () => Promise.resolve();
1947
+ function isBrowser() {
1948
+ const g = globalThis;
1949
+ return typeof g["window"] !== "undefined" && typeof g["document"] !== "undefined";
1950
+ }
1951
+ async function initMonitoring(config2) {
1952
+ if (config2.enabled === false) return;
1953
+ if (!config2.posthogApiKey && !config2.sentryDsn) return;
1954
+ try {
1955
+ if (isBrowser()) {
1956
+ const { initBrowserMonitoring: initBrowserMonitoring2, trackBrowserEvent: trackBrowserEvent2, trackBrowserError: trackBrowserError2, shutdownBrowserMonitoring: shutdownBrowserMonitoring2 } = await Promise.resolve().then(() => (init_browser(), browser_exports));
1957
+ await initBrowserMonitoring2(config2);
1958
+ _trackEvent = trackBrowserEvent2;
1959
+ _trackError = trackBrowserError2;
1960
+ _shutdown = shutdownBrowserMonitoring2;
1961
+ } else {
1962
+ const { initNodeMonitoring: initNodeMonitoring2, trackNodeEvent: trackNodeEvent2, trackNodeError: trackNodeError2, shutdownNodeMonitoring: shutdownNodeMonitoring2 } = await Promise.resolve().then(() => (init_node(), node_exports));
1963
+ await initNodeMonitoring2(config2);
1964
+ _trackEvent = trackNodeEvent2;
1965
+ _trackError = trackNodeError2;
1966
+ _shutdown = shutdownNodeMonitoring2;
1967
+ }
1968
+ } catch {
1969
+ }
1970
+ }
1971
+ function trackEvent(event, properties) {
1972
+ try {
1973
+ _trackEvent(event, properties);
1974
+ } catch {
1975
+ }
1976
+ }
1977
+ function trackError(error, context) {
1978
+ try {
1979
+ _trackError(error, context);
1980
+ } catch {
1981
+ }
1982
+ }
1983
+ async function shutdownMonitoring() {
1984
+ try {
1985
+ await _shutdown();
1986
+ } catch {
1987
+ }
1988
+ }
1989
+
1990
+ // src/monitoring/keys.ts
1991
+ var POSTHOG_API_KEY = "phc_rBFeG140KqJLpUnlpYDEFgdMM6JozZeqQsf9twXf5Dq" ;
1992
+ var SENTRY_DSN = "https://80a836a8300b25f17ef5bbf23afb5b3a@o4511080656207872.ingest.us.sentry.io/4511080661319680" ;
1993
+
1994
+ // src/rules/excluded-names.ts
1995
+ var EXCLUDED_NAME_PATTERN = /(badge|close|dismiss|overlay|float|fab|dot|indicator|corner|decoration|tag|status|notification|icon|ico|image|asset|filter|dim|dimmed|bg|background|logo|avatar|divider|separator|nav|navigation|gnb|header|footer|sidebar|toolbar|modal|dialog|popup|toast|tooltip|dropdown|menu|sticky|spinner|loader|cursor|cta|chatbot|thumb|thumbnail|tabbar|tab-bar|statusbar|status-bar)/i;
1996
+ function isExcludedName(name) {
1997
+ return EXCLUDED_NAME_PATTERN.test(name);
1998
+ }
1999
+
1710
2000
  // src/rules/layout/index.ts
1711
2001
  function isContainerNode(node) {
1712
2002
  return node.type === "FRAME" || node.type === "GROUP" || node.type === "COMPONENT";
@@ -1748,7 +2038,6 @@ var absolutePositionInAutoLayoutDef = {
1748
2038
  impact: "Element will not respond to sibling changes, may overlap unexpectedly",
1749
2039
  fix: "Remove absolute positioning or use proper Auto Layout alignment"
1750
2040
  };
1751
- var INTENTIONAL_ABSOLUTE_PATTERNS = /^(badge|close|dismiss|overlay|float|fab|dot|indicator|corner|decoration|tag|status|notification|x|icon[-_ ]?(close|dismiss|x)|btn[-_ ]?(close|dismiss))/i;
1752
2041
  function isSmallRelativeToParent(node, parent) {
1753
2042
  const nodeBB = node.absoluteBoundingBox;
1754
2043
  const parentBB = parent.absoluteBoundingBox;
@@ -1762,7 +2051,8 @@ var absolutePositionInAutoLayoutCheck = (node, context) => {
1762
2051
  if (!context.parent) return null;
1763
2052
  if (!hasAutoLayout(context.parent)) return null;
1764
2053
  if (node.layoutPositioning !== "ABSOLUTE") return null;
1765
- if (INTENTIONAL_ABSOLUTE_PATTERNS.test(node.name)) return null;
2054
+ if (node.type === "VECTOR" || node.type === "BOOLEAN_OPERATION" || node.type === "LINE" || node.type === "ELLIPSE" || node.type === "STAR" || node.type === "REGULAR_POLYGON") return null;
2055
+ if (isExcludedName(node.name)) return null;
1766
2056
  if (isSmallRelativeToParent(node, context.parent)) return null;
1767
2057
  if (context.parent.type === "COMPONENT") return null;
1768
2058
  return {
@@ -1788,10 +2078,14 @@ var fixedWidthInResponsiveContextCheck = (node, context) => {
1788
2078
  if (!context.parent) return null;
1789
2079
  if (!hasAutoLayout(context.parent)) return null;
1790
2080
  if (!isContainerNode(node)) return null;
1791
- if (node.layoutAlign === "STRETCH") return null;
1792
- const bbox = node.absoluteBoundingBox;
1793
- if (!bbox) return null;
1794
- if (node.layoutAlign !== "INHERIT") return null;
2081
+ if (node.layoutSizingHorizontal) {
2082
+ if (node.layoutSizingHorizontal !== "FIXED") return null;
2083
+ } else {
2084
+ if (node.layoutAlign === "STRETCH") return null;
2085
+ if (!node.absoluteBoundingBox) return null;
2086
+ if (node.layoutAlign !== "INHERIT") return null;
2087
+ }
2088
+ if (isExcludedName(node.name)) return null;
1795
2089
  return {
1796
2090
  ruleId: fixedWidthInResponsiveContextDef.id,
1797
2091
  nodeId: node.id,
@@ -2064,7 +2358,7 @@ var inconsistentSpacingDef = {
2064
2358
  fix: "Use spacing values from the design system grid (e.g., 8pt increments)"
2065
2359
  };
2066
2360
  var inconsistentSpacingCheck = (node, context, options) => {
2067
- const gridBase = options?.["gridBase"] ?? getRuleOption("inconsistent-spacing", "gridBase", 8);
2361
+ const gridBase = options?.["gridBase"] ?? getRuleOption("inconsistent-spacing", "gridBase", 4);
2068
2362
  const paddings = [
2069
2363
  node.paddingLeft,
2070
2364
  node.paddingRight,
@@ -2106,7 +2400,7 @@ var magicNumberSpacingDef = {
2106
2400
  fix: "Round spacing to the nearest grid value or use spacing tokens"
2107
2401
  };
2108
2402
  var magicNumberSpacingCheck = (node, context, options) => {
2109
- const gridBase = options?.["gridBase"] ?? getRuleOption("magic-number-spacing", "gridBase", 8);
2403
+ const gridBase = options?.["gridBase"] ?? getRuleOption("magic-number-spacing", "gridBase", 4);
2110
2404
  const allSpacings = [
2111
2405
  node.paddingLeft,
2112
2406
  node.paddingRight,
@@ -2423,6 +2717,7 @@ var defaultNameDef = {
2423
2717
  };
2424
2718
  var defaultNameCheck = (node, context) => {
2425
2719
  if (!node.name) return null;
2720
+ if (isExcludedName(node.name)) return null;
2426
2721
  if (!isDefaultName(node.name)) return null;
2427
2722
  return {
2428
2723
  ruleId: defaultNameDef.id,
@@ -2445,6 +2740,7 @@ var nonSemanticNameDef = {
2445
2740
  };
2446
2741
  var nonSemanticNameCheck = (node, context) => {
2447
2742
  if (!node.name) return null;
2743
+ if (isExcludedName(node.name)) return null;
2448
2744
  if (!isNonSemanticName(node.name)) return null;
2449
2745
  if (!node.children || node.children.length === 0) {
2450
2746
  const shapeTypes = ["RECTANGLE", "ELLIPSE", "VECTOR", "LINE", "STAR", "REGULAR_POLYGON"];
@@ -2513,6 +2809,7 @@ var numericSuffixNameDef = {
2513
2809
  };
2514
2810
  var numericSuffixNameCheck = (node, context) => {
2515
2811
  if (!node.name) return null;
2812
+ if (isExcludedName(node.name)) return null;
2516
2813
  if (isDefaultName(node.name)) return null;
2517
2814
  if (!hasNumericSuffix(node.name)) return null;
2518
2815
  return {
@@ -2897,6 +3194,7 @@ Typical flow with Figma MCP:
2897
3194
  customRulesPath: z.string().optional().describe("Path to custom rules JSON file")
2898
3195
  },
2899
3196
  async ({ designData, input, fileKey, fileName, token, preset, targetNodeId, configPath, customRulesPath }) => {
3197
+ trackEvent(EVENTS.MCP_TOOL_CALLED, { tool: "analyze" });
2900
3198
  try {
2901
3199
  let file;
2902
3200
  let nodeId;
@@ -2942,6 +3240,13 @@ Typical flow with Figma MCP:
2942
3240
  });
2943
3241
  const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
2944
3242
  exec(`${openCmd} "${reportPath}"`);
3243
+ trackEvent(EVENTS.ANALYSIS_COMPLETED, {
3244
+ nodeCount: result.nodeCount,
3245
+ issueCount: result.issues.length,
3246
+ grade: scores.overall.grade,
3247
+ percentage: scores.overall.percentage,
3248
+ source: designData ? "mcp-data" : "url"
3249
+ });
2945
3250
  const issuesByRule = {};
2946
3251
  for (const issue of result.issues) {
2947
3252
  const id = issue.violation.ruleId;
@@ -2971,6 +3276,13 @@ Typical flow with Figma MCP:
2971
3276
  ]
2972
3277
  };
2973
3278
  } catch (error) {
3279
+ trackError(
3280
+ error instanceof Error ? error : new Error(String(error)),
3281
+ { tool: "analyze" }
3282
+ );
3283
+ trackEvent(EVENTS.ANALYSIS_FAILED, {
3284
+ error: error instanceof Error ? error.message : String(error)
3285
+ });
2974
3286
  return {
2975
3287
  content: [
2976
3288
  {
@@ -3065,9 +3377,24 @@ Use this when the user asks about customization, configuration, rule settings, o
3065
3377
  }
3066
3378
  );
3067
3379
  async function main() {
3380
+ const monitoringConfig = {
3381
+ environment: "mcp",
3382
+ version: pkg.version,
3383
+ enabled: getTelemetryEnabled()
3384
+ };
3385
+ const phKey = getPosthogApiKey() || POSTHOG_API_KEY;
3386
+ monitoringConfig.posthogApiKey = phKey;
3387
+ const sDsn = getSentryDsn() || SENTRY_DSN;
3388
+ monitoringConfig.sentryDsn = sDsn;
3389
+ await initMonitoring(monitoringConfig).catch(() => {
3390
+ });
3068
3391
  const transport = new StdioServerTransport();
3069
3392
  await server.connect(transport);
3070
3393
  }
3394
+ process.on("beforeExit", () => {
3395
+ shutdownMonitoring().catch(() => {
3396
+ });
3397
+ });
3071
3398
  main().catch(console.error);
3072
3399
  //# sourceMappingURL=server.js.map
3073
3400
  //# sourceMappingURL=server.js.map