canicode 0.6.3 → 0.7.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.
- package/README.md +22 -269
- package/dist/cli/index.js +71 -54
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +16 -16
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +72 -55
- package/dist/mcp/server.js.map +1 -1
- package/package.json +2 -2
package/dist/mcp/server.js
CHANGED
|
@@ -41,7 +41,7 @@ var SEVERITY_LABELS = {
|
|
|
41
41
|
suggestion: "Suggestion"
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
// src/contracts/rule.ts
|
|
44
|
+
// src/core/contracts/rule.ts
|
|
45
45
|
z.object({
|
|
46
46
|
id: z.string(),
|
|
47
47
|
name: z.string(),
|
|
@@ -62,7 +62,7 @@ function supportsDepthWeight(category) {
|
|
|
62
62
|
return DEPTH_WEIGHT_CATEGORIES.includes(category);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
// src/rules/rule-config.ts
|
|
65
|
+
// src/core/rules/rule-config.ts
|
|
66
66
|
var RULE_CONFIGS = {
|
|
67
67
|
// ============================================
|
|
68
68
|
// Layout (11 rules)
|
|
@@ -357,7 +357,7 @@ function getRuleOption(ruleId, optionKey, defaultValue) {
|
|
|
357
357
|
return value ?? defaultValue;
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
-
// src/rules/rule-registry.ts
|
|
360
|
+
// src/core/rules/rule-registry.ts
|
|
361
361
|
var RuleRegistry = class {
|
|
362
362
|
rules = /* @__PURE__ */ new Map();
|
|
363
363
|
/**
|
|
@@ -418,7 +418,7 @@ function defineRule(rule) {
|
|
|
418
418
|
return rule;
|
|
419
419
|
}
|
|
420
420
|
|
|
421
|
-
// src/core/rule-engine.ts
|
|
421
|
+
// src/core/engine/rule-engine.ts
|
|
422
422
|
function calculateMaxDepth(node, currentDepth = 0) {
|
|
423
423
|
if (!node.children || node.children.length === 0) {
|
|
424
424
|
return currentDepth;
|
|
@@ -591,7 +591,7 @@ function analyzeFile(file, options) {
|
|
|
591
591
|
return engine.analyze(file);
|
|
592
592
|
}
|
|
593
593
|
|
|
594
|
-
// src/adapters/figma-client.ts
|
|
594
|
+
// src/core/adapters/figma-client.ts
|
|
595
595
|
var FIGMA_API_BASE = "https://api.figma.com/v1";
|
|
596
596
|
var FigmaClient = class _FigmaClient {
|
|
597
597
|
token;
|
|
@@ -699,7 +699,7 @@ var FigmaClientError = class extends Error {
|
|
|
699
699
|
}
|
|
700
700
|
};
|
|
701
701
|
|
|
702
|
-
// src/adapters/figma-transformer.ts
|
|
702
|
+
// src/core/adapters/figma-transformer.ts
|
|
703
703
|
function transformFigmaResponse(fileKey, response) {
|
|
704
704
|
return {
|
|
705
705
|
fileKey,
|
|
@@ -818,7 +818,7 @@ function transformStyles(styles) {
|
|
|
818
818
|
return result;
|
|
819
819
|
}
|
|
820
820
|
|
|
821
|
-
// src/adapters/figma-file-loader.ts
|
|
821
|
+
// src/core/adapters/figma-file-loader.ts
|
|
822
822
|
async function loadFigmaFileFromJson(filePath) {
|
|
823
823
|
const content = await readFile(filePath, "utf-8");
|
|
824
824
|
const data = JSON.parse(content);
|
|
@@ -916,7 +916,7 @@ function getDeviceId() {
|
|
|
916
916
|
return id;
|
|
917
917
|
}
|
|
918
918
|
|
|
919
|
-
// src/core/loader.ts
|
|
919
|
+
// src/core/engine/loader.ts
|
|
920
920
|
function isFigmaUrl(input) {
|
|
921
921
|
return input.includes("figma.com/");
|
|
922
922
|
}
|
|
@@ -959,7 +959,7 @@ async function loadFromApi(fileKey, nodeId, token) {
|
|
|
959
959
|
};
|
|
960
960
|
}
|
|
961
961
|
|
|
962
|
-
// src/adapters/figma-mcp-adapter.ts
|
|
962
|
+
// src/core/adapters/figma-mcp-adapter.ts
|
|
963
963
|
var TAG_TYPE_MAP = {
|
|
964
964
|
canvas: "CANVAS",
|
|
965
965
|
frame: "FRAME",
|
|
@@ -1090,7 +1090,7 @@ function parseMcpMetadataXml(xml, fileKey, fileName) {
|
|
|
1090
1090
|
};
|
|
1091
1091
|
}
|
|
1092
1092
|
|
|
1093
|
-
// src/core/design-data-parser.ts
|
|
1093
|
+
// src/core/engine/design-data-parser.ts
|
|
1094
1094
|
function parseDesignData(data, fileKey, fileName) {
|
|
1095
1095
|
const trimmed = data.trim();
|
|
1096
1096
|
if (trimmed.startsWith("<")) {
|
|
@@ -1108,7 +1108,7 @@ function parseDesignData(data, fileKey, fileName) {
|
|
|
1108
1108
|
);
|
|
1109
1109
|
}
|
|
1110
1110
|
|
|
1111
|
-
// src/core/scoring.ts
|
|
1111
|
+
// src/core/engine/scoring.ts
|
|
1112
1112
|
var SEVERITY_DENSITY_WEIGHT = {
|
|
1113
1113
|
blocking: 3,
|
|
1114
1114
|
risk: 2,
|
|
@@ -1271,7 +1271,7 @@ function formatScoreSummary(report) {
|
|
|
1271
1271
|
return lines.join("\n");
|
|
1272
1272
|
}
|
|
1273
1273
|
|
|
1274
|
-
// src/
|
|
1274
|
+
// src/core/ui-constants.ts
|
|
1275
1275
|
var GAUGE_R = 54;
|
|
1276
1276
|
var GAUGE_C = Math.round(2 * Math.PI * GAUGE_R);
|
|
1277
1277
|
var CATEGORY_DESCRIPTIONS = {
|
|
@@ -1282,12 +1282,31 @@ var CATEGORY_DESCRIPTIONS = {
|
|
|
1282
1282
|
"ai-readability": "Structure clarity for AI code generation, z-index, empty frames",
|
|
1283
1283
|
"handoff-risk": "Hardcoded values, text truncation, image placeholders, dev status"
|
|
1284
1284
|
};
|
|
1285
|
-
var SEVERITY_ORDER = [
|
|
1285
|
+
var SEVERITY_ORDER = [
|
|
1286
|
+
"blocking",
|
|
1287
|
+
"risk",
|
|
1288
|
+
"missing-info",
|
|
1289
|
+
"suggestion"
|
|
1290
|
+
];
|
|
1291
|
+
|
|
1292
|
+
// src/core/ui-helpers.ts
|
|
1286
1293
|
function gaugeColor(pct) {
|
|
1287
1294
|
if (pct >= 75) return "#22c55e";
|
|
1288
1295
|
if (pct >= 50) return "#f59e0b";
|
|
1289
1296
|
return "#ef4444";
|
|
1290
1297
|
}
|
|
1298
|
+
function escapeHtml(text) {
|
|
1299
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1300
|
+
}
|
|
1301
|
+
function severityDot(sev) {
|
|
1302
|
+
const map = {
|
|
1303
|
+
blocking: "bg-red-500",
|
|
1304
|
+
risk: "bg-amber-500",
|
|
1305
|
+
"missing-info": "bg-zinc-400",
|
|
1306
|
+
suggestion: "bg-green-500"
|
|
1307
|
+
};
|
|
1308
|
+
return map[sev];
|
|
1309
|
+
}
|
|
1291
1310
|
function severityBadge(sev) {
|
|
1292
1311
|
const map = {
|
|
1293
1312
|
blocking: "bg-red-500/10 text-red-600 border-red-500/20",
|
|
@@ -1302,15 +1321,24 @@ function scoreBadgeStyle(pct) {
|
|
|
1302
1321
|
if (pct >= 50) return "bg-amber-500/10 text-amber-700 border-amber-500/20";
|
|
1303
1322
|
return "bg-red-500/10 text-red-700 border-red-500/20";
|
|
1304
1323
|
}
|
|
1305
|
-
function
|
|
1306
|
-
const
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
"
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1324
|
+
function renderGaugeSvg(pct, size, strokeW, grade) {
|
|
1325
|
+
const offset = GAUGE_C * (1 - pct / 100);
|
|
1326
|
+
const color = gaugeColor(pct);
|
|
1327
|
+
if (grade) {
|
|
1328
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 120 120" class="gauge-svg block">
|
|
1329
|
+
<circle cx="60" cy="60" r="${GAUGE_R}" fill="none" stroke-width="${strokeW}" stroke="#e4e4e7" class="stroke-border" />
|
|
1330
|
+
<circle cx="60" cy="60" r="${GAUGE_R}" fill="none" stroke="${color}" stroke-width="${strokeW}" stroke-linecap="round" stroke-dasharray="${GAUGE_C}" stroke-dashoffset="${offset}" transform="rotate(-90 60 60)" class="gauge-fill" />
|
|
1331
|
+
<text x="60" y="60" text-anchor="middle" dominant-baseline="central" fill="currentColor" font-size="48" font-weight="700" font-family="Inter,-apple-system,sans-serif" class="font-sans">${escapeHtml(grade)}</text>
|
|
1332
|
+
</svg>`;
|
|
1333
|
+
}
|
|
1334
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 120 120" class="gauge-svg block">
|
|
1335
|
+
<circle cx="60" cy="60" r="${GAUGE_R}" fill="none" stroke-width="${strokeW}" stroke="#e4e4e7" class="stroke-border" />
|
|
1336
|
+
<circle cx="60" cy="60" r="${GAUGE_R}" fill="none" stroke="${color}" stroke-width="${strokeW}" stroke-linecap="round" stroke-dasharray="${GAUGE_C}" stroke-dashoffset="${offset}" transform="rotate(-90 60 60)" class="gauge-fill" />
|
|
1337
|
+
<text x="60" y="62" text-anchor="middle" dominant-baseline="central" fill="currentColor" font-size="28" font-weight="700" font-family="Inter,-apple-system,sans-serif" class="font-sans">${pct}</text>
|
|
1338
|
+
</svg>`;
|
|
1313
1339
|
}
|
|
1340
|
+
|
|
1341
|
+
// src/core/report-html/index.ts
|
|
1314
1342
|
function generateHtmlReport(file, result, scores, options) {
|
|
1315
1343
|
const screenshotMap = new Map(
|
|
1316
1344
|
(options?.nodeScreenshots ?? []).map((ns) => [ns.nodeId, ns])
|
|
@@ -1467,23 +1495,6 @@ ${figmaToken ? ` <script>
|
|
|
1467
1495
|
</body>
|
|
1468
1496
|
</html>`;
|
|
1469
1497
|
}
|
|
1470
|
-
function renderGaugeSvg(pct, size, strokeW, grade) {
|
|
1471
|
-
const offset = GAUGE_C * (1 - pct / 100);
|
|
1472
|
-
const color = gaugeColor(pct);
|
|
1473
|
-
if (grade) {
|
|
1474
|
-
return `<svg width="${size}" height="${size}" viewBox="0 0 120 120" class="block">
|
|
1475
|
-
<circle cx="60" cy="60" r="${GAUGE_R}" fill="none" stroke-width="${strokeW}" class="stroke-border" />
|
|
1476
|
-
<circle cx="60" cy="60" r="${GAUGE_R}" fill="none" stroke="${color}" stroke-width="${strokeW}" stroke-linecap="round" stroke-dasharray="${GAUGE_C}" stroke-dashoffset="${offset}" transform="rotate(-90 60 60)" class="gauge-fill" />
|
|
1477
|
-
<text x="60" y="60" text-anchor="middle" dominant-baseline="central" fill="currentColor" font-size="52" font-weight="700" class="font-sans">${esc(grade)}</text>
|
|
1478
|
-
</svg>`;
|
|
1479
|
-
}
|
|
1480
|
-
const fontSize = 32;
|
|
1481
|
-
return `<svg width="${size}" height="${size}" viewBox="0 0 120 120" class="block">
|
|
1482
|
-
<circle cx="60" cy="60" r="${GAUGE_R}" fill="none" stroke-width="${strokeW}" class="stroke-border" />
|
|
1483
|
-
<circle cx="60" cy="60" r="${GAUGE_R}" fill="none" stroke="${color}" stroke-width="${strokeW}" stroke-linecap="round" stroke-dasharray="${GAUGE_C}" stroke-dashoffset="${offset}" transform="rotate(-90 60 60)" class="gauge-fill" />
|
|
1484
|
-
<text x="60" y="62" text-anchor="middle" dominant-baseline="central" fill="currentColor" font-size="${fontSize}" font-weight="700" class="font-sans">${pct}</text>
|
|
1485
|
-
</svg>`;
|
|
1486
|
-
}
|
|
1487
1498
|
function renderSummaryDot(dotClass, count, label) {
|
|
1488
1499
|
return `<div class="flex items-center gap-2">
|
|
1489
1500
|
<span class="w-2.5 h-2.5 rounded-full ${dotClass}"></span>
|
|
@@ -1595,9 +1606,7 @@ function groupIssuesByCategory(issues) {
|
|
|
1595
1606
|
for (const issue of issues) grouped.get(issue.rule.definition.category).push(issue);
|
|
1596
1607
|
return grouped;
|
|
1597
1608
|
}
|
|
1598
|
-
|
|
1599
|
-
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1600
|
-
}
|
|
1609
|
+
var esc = escapeHtml;
|
|
1601
1610
|
var RuleOverrideSchema = z.object({
|
|
1602
1611
|
score: z.number().int().max(0).optional(),
|
|
1603
1612
|
severity: SeveritySchema.optional(),
|
|
@@ -1700,7 +1709,7 @@ var CustomRuleSchema = z.object({
|
|
|
1700
1709
|
});
|
|
1701
1710
|
var CustomRulesFileSchema = z.array(CustomRuleSchema);
|
|
1702
1711
|
|
|
1703
|
-
// src/rules/custom/custom-rule-loader.ts
|
|
1712
|
+
// src/core/rules/custom/custom-rule-loader.ts
|
|
1704
1713
|
async function loadCustomRules(filePath) {
|
|
1705
1714
|
const absPath = resolve(filePath);
|
|
1706
1715
|
const raw = await readFile(absPath, "utf-8");
|
|
@@ -1778,7 +1787,7 @@ function createPatternCheck(cr) {
|
|
|
1778
1787
|
};
|
|
1779
1788
|
}
|
|
1780
1789
|
|
|
1781
|
-
// src/monitoring/events.ts
|
|
1790
|
+
// src/core/monitoring/events.ts
|
|
1782
1791
|
var EVENT_PREFIX = "cic_";
|
|
1783
1792
|
var EVENTS = {
|
|
1784
1793
|
// Analysis
|
|
@@ -1796,11 +1805,13 @@ var EVENTS = {
|
|
|
1796
1805
|
CLI_INIT: `${EVENT_PREFIX}cli_init`
|
|
1797
1806
|
};
|
|
1798
1807
|
|
|
1799
|
-
// src/monitoring/capture.ts
|
|
1808
|
+
// src/core/monitoring/capture.ts
|
|
1800
1809
|
var monitoringEnabled = false;
|
|
1801
1810
|
var posthogApiKey;
|
|
1802
1811
|
var sentryDsn;
|
|
1803
1812
|
var distinctId = "anonymous";
|
|
1813
|
+
var environment = "unknown";
|
|
1814
|
+
var version = "unknown";
|
|
1804
1815
|
var commonProps = {};
|
|
1805
1816
|
function uuid4() {
|
|
1806
1817
|
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
@@ -1828,10 +1839,12 @@ function initCapture(config2) {
|
|
|
1828
1839
|
posthogApiKey = config2.posthogApiKey;
|
|
1829
1840
|
sentryDsn = config2.sentryDsn;
|
|
1830
1841
|
distinctId = config2.distinctId ?? "anonymous";
|
|
1842
|
+
environment = config2.environment ?? "unknown";
|
|
1843
|
+
version = config2.version ?? "unknown";
|
|
1831
1844
|
commonProps = {
|
|
1832
1845
|
_sdk: "canicode",
|
|
1833
|
-
_sdk_version:
|
|
1834
|
-
_env:
|
|
1846
|
+
_sdk_version: version,
|
|
1847
|
+
_env: environment
|
|
1835
1848
|
};
|
|
1836
1849
|
}
|
|
1837
1850
|
function captureEvent(event, properties) {
|
|
@@ -1866,6 +1879,8 @@ function captureError(error, context) {
|
|
|
1866
1879
|
event_id: eventId,
|
|
1867
1880
|
exception: { values: [{ type: error.name, value: error.message }] },
|
|
1868
1881
|
platform: "node",
|
|
1882
|
+
environment,
|
|
1883
|
+
release: `canicode@${version}`,
|
|
1869
1884
|
timestamp: Date.now() / 1e3,
|
|
1870
1885
|
extra: context
|
|
1871
1886
|
})
|
|
@@ -1890,10 +1905,12 @@ function shutdownCapture() {
|
|
|
1890
1905
|
posthogApiKey = void 0;
|
|
1891
1906
|
sentryDsn = void 0;
|
|
1892
1907
|
distinctId = "anonymous";
|
|
1908
|
+
environment = "unknown";
|
|
1909
|
+
version = "unknown";
|
|
1893
1910
|
commonProps = {};
|
|
1894
1911
|
}
|
|
1895
1912
|
|
|
1896
|
-
// src/monitoring/index.ts
|
|
1913
|
+
// src/core/monitoring/index.ts
|
|
1897
1914
|
function initMonitoring(config2) {
|
|
1898
1915
|
initCapture(config2);
|
|
1899
1916
|
}
|
|
@@ -1916,17 +1933,17 @@ function shutdownMonitoring() {
|
|
|
1916
1933
|
}
|
|
1917
1934
|
}
|
|
1918
1935
|
|
|
1919
|
-
// src/monitoring/keys.ts
|
|
1936
|
+
// src/core/monitoring/keys.ts
|
|
1920
1937
|
var POSTHOG_API_KEY = "phc_rBFeG140KqJLpUnlpYDEFgdMM6JozZeqQsf9twXf5Dq" ;
|
|
1921
1938
|
var SENTRY_DSN = "https://80a836a8300b25f17ef5bbf23afb5b3a@o4511080656207872.ingest.us.sentry.io/4511080661319680" ;
|
|
1922
1939
|
|
|
1923
|
-
// src/rules/excluded-names.ts
|
|
1940
|
+
// src/core/rules/excluded-names.ts
|
|
1924
1941
|
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;
|
|
1925
1942
|
function isExcludedName(name) {
|
|
1926
1943
|
return EXCLUDED_NAME_PATTERN.test(name);
|
|
1927
1944
|
}
|
|
1928
1945
|
|
|
1929
|
-
// src/rules/layout/index.ts
|
|
1946
|
+
// src/core/rules/layout/index.ts
|
|
1930
1947
|
function isContainerNode(node) {
|
|
1931
1948
|
return node.type === "FRAME" || node.type === "GROUP" || node.type === "COMPONENT";
|
|
1932
1949
|
}
|
|
@@ -2213,7 +2230,7 @@ defineRule({
|
|
|
2213
2230
|
check: inconsistentSiblingLayoutDirectionCheck
|
|
2214
2231
|
});
|
|
2215
2232
|
|
|
2216
|
-
// src/rules/token/index.ts
|
|
2233
|
+
// src/core/rules/token/index.ts
|
|
2217
2234
|
function hasStyleReference(node, styleType) {
|
|
2218
2235
|
return node.styles !== void 0 && styleType in node.styles;
|
|
2219
2236
|
}
|
|
@@ -2417,7 +2434,7 @@ defineRule({
|
|
|
2417
2434
|
check: multipleFillColorsCheck
|
|
2418
2435
|
});
|
|
2419
2436
|
|
|
2420
|
-
// src/rules/component/index.ts
|
|
2437
|
+
// src/core/rules/component/index.ts
|
|
2421
2438
|
function isComponentInstance(node) {
|
|
2422
2439
|
return node.type === "INSTANCE";
|
|
2423
2440
|
}
|
|
@@ -2591,7 +2608,7 @@ defineRule({
|
|
|
2591
2608
|
check: singleUseComponentCheck
|
|
2592
2609
|
});
|
|
2593
2610
|
|
|
2594
|
-
// src/rules/naming/index.ts
|
|
2611
|
+
// src/core/rules/naming/index.ts
|
|
2595
2612
|
var DEFAULT_NAME_PATTERNS = [
|
|
2596
2613
|
/^Frame\s*\d*$/i,
|
|
2597
2614
|
/^Group\s*\d*$/i,
|
|
@@ -2776,7 +2793,7 @@ defineRule({
|
|
|
2776
2793
|
check: tooLongNameCheck
|
|
2777
2794
|
});
|
|
2778
2795
|
|
|
2779
|
-
// src/rules/ai-readability/index.ts
|
|
2796
|
+
// src/core/rules/ai-readability/index.ts
|
|
2780
2797
|
function hasAutoLayout2(node) {
|
|
2781
2798
|
return node.layoutMode !== void 0 && node.layoutMode !== "NONE";
|
|
2782
2799
|
}
|
|
@@ -2953,7 +2970,7 @@ defineRule({
|
|
|
2953
2970
|
check: emptyFrameCheck
|
|
2954
2971
|
});
|
|
2955
2972
|
|
|
2956
|
-
// src/rules/handoff-risk/index.ts
|
|
2973
|
+
// src/core/rules/handoff-risk/index.ts
|
|
2957
2974
|
function hasAutoLayout3(node) {
|
|
2958
2975
|
return node.layoutMode !== void 0 && node.layoutMode !== "NONE";
|
|
2959
2976
|
}
|