@mp3wizard/figma-console-mcp 1.14.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.
Files changed (201) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +816 -0
  3. package/dist/apps/design-system-dashboard/scoring/accessibility.d.ts +14 -0
  4. package/dist/apps/design-system-dashboard/scoring/accessibility.d.ts.map +1 -0
  5. package/dist/apps/design-system-dashboard/scoring/accessibility.js +278 -0
  6. package/dist/apps/design-system-dashboard/scoring/accessibility.js.map +1 -0
  7. package/dist/apps/design-system-dashboard/scoring/component-metadata.d.ts +29 -0
  8. package/dist/apps/design-system-dashboard/scoring/component-metadata.d.ts.map +1 -0
  9. package/dist/apps/design-system-dashboard/scoring/component-metadata.js +358 -0
  10. package/dist/apps/design-system-dashboard/scoring/component-metadata.js.map +1 -0
  11. package/dist/apps/design-system-dashboard/scoring/consistency.d.ts +14 -0
  12. package/dist/apps/design-system-dashboard/scoring/consistency.d.ts.map +1 -0
  13. package/dist/apps/design-system-dashboard/scoring/consistency.js +342 -0
  14. package/dist/apps/design-system-dashboard/scoring/consistency.js.map +1 -0
  15. package/dist/apps/design-system-dashboard/scoring/coverage.d.ts +14 -0
  16. package/dist/apps/design-system-dashboard/scoring/coverage.d.ts.map +1 -0
  17. package/dist/apps/design-system-dashboard/scoring/coverage.js +231 -0
  18. package/dist/apps/design-system-dashboard/scoring/coverage.js.map +1 -0
  19. package/dist/apps/design-system-dashboard/scoring/engine.d.ts +27 -0
  20. package/dist/apps/design-system-dashboard/scoring/engine.d.ts.map +1 -0
  21. package/dist/apps/design-system-dashboard/scoring/engine.js +93 -0
  22. package/dist/apps/design-system-dashboard/scoring/engine.js.map +1 -0
  23. package/dist/apps/design-system-dashboard/scoring/naming-semantics.d.ts +14 -0
  24. package/dist/apps/design-system-dashboard/scoring/naming-semantics.d.ts.map +1 -0
  25. package/dist/apps/design-system-dashboard/scoring/naming-semantics.js +309 -0
  26. package/dist/apps/design-system-dashboard/scoring/naming-semantics.js.map +1 -0
  27. package/dist/apps/design-system-dashboard/scoring/token-architecture.d.ts +14 -0
  28. package/dist/apps/design-system-dashboard/scoring/token-architecture.d.ts.map +1 -0
  29. package/dist/apps/design-system-dashboard/scoring/token-architecture.js +350 -0
  30. package/dist/apps/design-system-dashboard/scoring/token-architecture.js.map +1 -0
  31. package/dist/apps/design-system-dashboard/scoring/types.d.ts +89 -0
  32. package/dist/apps/design-system-dashboard/scoring/types.d.ts.map +1 -0
  33. package/dist/apps/design-system-dashboard/scoring/types.js +41 -0
  34. package/dist/apps/design-system-dashboard/scoring/types.js.map +1 -0
  35. package/dist/apps/design-system-dashboard/server.d.ts +24 -0
  36. package/dist/apps/design-system-dashboard/server.d.ts.map +1 -0
  37. package/dist/apps/design-system-dashboard/server.js +160 -0
  38. package/dist/apps/design-system-dashboard/server.js.map +1 -0
  39. package/dist/apps/token-browser/server.d.ts +26 -0
  40. package/dist/apps/token-browser/server.d.ts.map +1 -0
  41. package/dist/apps/token-browser/server.js +137 -0
  42. package/dist/apps/token-browser/server.js.map +1 -0
  43. package/dist/browser/base.d.ts +58 -0
  44. package/dist/browser/base.d.ts.map +1 -0
  45. package/dist/browser/base.js +6 -0
  46. package/dist/browser/base.js.map +1 -0
  47. package/dist/browser/local.d.ts +87 -0
  48. package/dist/browser/local.d.ts.map +1 -0
  49. package/dist/browser/local.js +318 -0
  50. package/dist/browser/local.js.map +1 -0
  51. package/dist/cloudflare/apps/design-system-dashboard/scoring/accessibility.js +277 -0
  52. package/dist/cloudflare/apps/design-system-dashboard/scoring/component-metadata.js +357 -0
  53. package/dist/cloudflare/apps/design-system-dashboard/scoring/consistency.js +341 -0
  54. package/dist/cloudflare/apps/design-system-dashboard/scoring/coverage.js +230 -0
  55. package/dist/cloudflare/apps/design-system-dashboard/scoring/engine.js +92 -0
  56. package/dist/cloudflare/apps/design-system-dashboard/scoring/naming-semantics.js +308 -0
  57. package/dist/cloudflare/apps/design-system-dashboard/scoring/token-architecture.js +349 -0
  58. package/dist/cloudflare/apps/design-system-dashboard/scoring/types.js +40 -0
  59. package/dist/cloudflare/apps/design-system-dashboard/server.js +159 -0
  60. package/dist/cloudflare/apps/token-browser/server.js +136 -0
  61. package/dist/cloudflare/browser/base.js +5 -0
  62. package/dist/cloudflare/browser/cloudflare.js +156 -0
  63. package/dist/cloudflare/browser-manager.js +157 -0
  64. package/dist/cloudflare/core/cloud-websocket-connector.js +267 -0
  65. package/dist/cloudflare/core/cloud-websocket-relay.js +199 -0
  66. package/dist/cloudflare/core/comment-tools.js +292 -0
  67. package/dist/cloudflare/core/config.js +161 -0
  68. package/dist/cloudflare/core/console-monitor.js +427 -0
  69. package/dist/cloudflare/core/design-code-tools.js +2504 -0
  70. package/dist/cloudflare/core/design-system-manifest.js +260 -0
  71. package/dist/cloudflare/core/design-system-tools.js +863 -0
  72. package/dist/cloudflare/core/enrichment/enrichment-service.js +272 -0
  73. package/dist/cloudflare/core/enrichment/index.js +7 -0
  74. package/dist/cloudflare/core/enrichment/relationship-mapper.js +351 -0
  75. package/dist/cloudflare/core/enrichment/style-resolver.js +326 -0
  76. package/dist/cloudflare/core/figma-api.js +409 -0
  77. package/dist/cloudflare/core/figma-connector.js +7 -0
  78. package/dist/cloudflare/core/figma-desktop-connector.js +1184 -0
  79. package/dist/cloudflare/core/figma-reconstruction-spec.js +402 -0
  80. package/dist/cloudflare/core/figma-style-extractor.js +311 -0
  81. package/dist/cloudflare/core/figma-tools.js +2947 -0
  82. package/dist/cloudflare/core/logger.js +53 -0
  83. package/dist/cloudflare/core/port-discovery.js +282 -0
  84. package/dist/cloudflare/core/snippet-injector.js +96 -0
  85. package/dist/cloudflare/core/types/design-code.js +4 -0
  86. package/dist/cloudflare/core/types/enriched.js +5 -0
  87. package/dist/cloudflare/core/types/index.js +4 -0
  88. package/dist/cloudflare/core/websocket-connector.js +256 -0
  89. package/dist/cloudflare/core/websocket-server.js +646 -0
  90. package/dist/cloudflare/core/write-tools.js +2091 -0
  91. package/dist/cloudflare/index.js +2899 -0
  92. package/dist/cloudflare/test-browser.js +88 -0
  93. package/dist/core/comment-tools.d.ts +11 -0
  94. package/dist/core/comment-tools.d.ts.map +1 -0
  95. package/dist/core/comment-tools.js +293 -0
  96. package/dist/core/comment-tools.js.map +1 -0
  97. package/dist/core/config.d.ts +17 -0
  98. package/dist/core/config.d.ts.map +1 -0
  99. package/dist/core/config.js +162 -0
  100. package/dist/core/config.js.map +1 -0
  101. package/dist/core/console-monitor.d.ts +82 -0
  102. package/dist/core/console-monitor.d.ts.map +1 -0
  103. package/dist/core/console-monitor.js +428 -0
  104. package/dist/core/console-monitor.js.map +1 -0
  105. package/dist/core/design-code-tools.d.ts +127 -0
  106. package/dist/core/design-code-tools.d.ts.map +1 -0
  107. package/dist/core/design-code-tools.js +2505 -0
  108. package/dist/core/design-code-tools.js.map +1 -0
  109. package/dist/core/design-system-manifest.d.ts +272 -0
  110. package/dist/core/design-system-manifest.d.ts.map +1 -0
  111. package/dist/core/design-system-manifest.js +261 -0
  112. package/dist/core/design-system-manifest.js.map +1 -0
  113. package/dist/core/design-system-tools.d.ts +17 -0
  114. package/dist/core/design-system-tools.d.ts.map +1 -0
  115. package/dist/core/design-system-tools.js +864 -0
  116. package/dist/core/design-system-tools.js.map +1 -0
  117. package/dist/core/enrichment/enrichment-service.d.ts +52 -0
  118. package/dist/core/enrichment/enrichment-service.d.ts.map +1 -0
  119. package/dist/core/enrichment/enrichment-service.js +273 -0
  120. package/dist/core/enrichment/enrichment-service.js.map +1 -0
  121. package/dist/core/enrichment/index.d.ts +8 -0
  122. package/dist/core/enrichment/index.d.ts.map +1 -0
  123. package/dist/core/enrichment/index.js +8 -0
  124. package/dist/core/enrichment/index.js.map +1 -0
  125. package/dist/core/enrichment/relationship-mapper.d.ts +106 -0
  126. package/dist/core/enrichment/relationship-mapper.d.ts.map +1 -0
  127. package/dist/core/enrichment/relationship-mapper.js +352 -0
  128. package/dist/core/enrichment/relationship-mapper.js.map +1 -0
  129. package/dist/core/enrichment/style-resolver.d.ts +80 -0
  130. package/dist/core/enrichment/style-resolver.d.ts.map +1 -0
  131. package/dist/core/enrichment/style-resolver.js +327 -0
  132. package/dist/core/enrichment/style-resolver.js.map +1 -0
  133. package/dist/core/figma-api.d.ts +201 -0
  134. package/dist/core/figma-api.d.ts.map +1 -0
  135. package/dist/core/figma-api.js +410 -0
  136. package/dist/core/figma-api.js.map +1 -0
  137. package/dist/core/figma-connector.d.ts +48 -0
  138. package/dist/core/figma-connector.d.ts.map +1 -0
  139. package/dist/core/figma-connector.js +8 -0
  140. package/dist/core/figma-connector.js.map +1 -0
  141. package/dist/core/figma-desktop-connector.d.ts +265 -0
  142. package/dist/core/figma-desktop-connector.d.ts.map +1 -0
  143. package/dist/core/figma-desktop-connector.js +1184 -0
  144. package/dist/core/figma-desktop-connector.js.map +1 -0
  145. package/dist/core/figma-reconstruction-spec.d.ts +166 -0
  146. package/dist/core/figma-reconstruction-spec.d.ts.map +1 -0
  147. package/dist/core/figma-reconstruction-spec.js +403 -0
  148. package/dist/core/figma-reconstruction-spec.js.map +1 -0
  149. package/dist/core/figma-style-extractor.d.ts +76 -0
  150. package/dist/core/figma-style-extractor.d.ts.map +1 -0
  151. package/dist/core/figma-style-extractor.js +312 -0
  152. package/dist/core/figma-style-extractor.js.map +1 -0
  153. package/dist/core/figma-tools.d.ts +23 -0
  154. package/dist/core/figma-tools.d.ts.map +1 -0
  155. package/dist/core/figma-tools.js +2948 -0
  156. package/dist/core/figma-tools.js.map +1 -0
  157. package/dist/core/logger.d.ts +22 -0
  158. package/dist/core/logger.d.ts.map +1 -0
  159. package/dist/core/logger.js +54 -0
  160. package/dist/core/logger.js.map +1 -0
  161. package/dist/core/port-discovery.d.ts +110 -0
  162. package/dist/core/port-discovery.d.ts.map +1 -0
  163. package/dist/core/port-discovery.js +283 -0
  164. package/dist/core/port-discovery.js.map +1 -0
  165. package/dist/core/snippet-injector.d.ts +24 -0
  166. package/dist/core/snippet-injector.d.ts.map +1 -0
  167. package/dist/core/snippet-injector.js +97 -0
  168. package/dist/core/snippet-injector.js.map +1 -0
  169. package/dist/core/types/design-code.d.ts +262 -0
  170. package/dist/core/types/design-code.d.ts.map +1 -0
  171. package/dist/core/types/design-code.js +5 -0
  172. package/dist/core/types/design-code.js.map +1 -0
  173. package/dist/core/types/enriched.d.ts +213 -0
  174. package/dist/core/types/enriched.d.ts.map +1 -0
  175. package/dist/core/types/enriched.js +6 -0
  176. package/dist/core/types/enriched.js.map +1 -0
  177. package/dist/core/types/index.d.ts +112 -0
  178. package/dist/core/types/index.d.ts.map +1 -0
  179. package/dist/core/types/index.js +5 -0
  180. package/dist/core/types/index.js.map +1 -0
  181. package/dist/core/websocket-connector.d.ts +55 -0
  182. package/dist/core/websocket-connector.d.ts.map +1 -0
  183. package/dist/core/websocket-connector.js +257 -0
  184. package/dist/core/websocket-connector.js.map +1 -0
  185. package/dist/core/websocket-server.d.ts +191 -0
  186. package/dist/core/websocket-server.d.ts.map +1 -0
  187. package/dist/core/websocket-server.js +647 -0
  188. package/dist/core/websocket-server.js.map +1 -0
  189. package/dist/core/write-tools.d.ts +7 -0
  190. package/dist/core/write-tools.d.ts.map +1 -0
  191. package/dist/core/write-tools.js +2092 -0
  192. package/dist/core/write-tools.js.map +1 -0
  193. package/dist/local.d.ts +84 -0
  194. package/dist/local.d.ts.map +1 -0
  195. package/dist/local.js +5039 -0
  196. package/dist/local.js.map +1 -0
  197. package/figma-desktop-bridge/README.md +313 -0
  198. package/figma-desktop-bridge/code.js +2818 -0
  199. package/figma-desktop-bridge/manifest.json +67 -0
  200. package/figma-desktop-bridge/ui.html +1236 -0
  201. package/package.json +87 -0
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Coverage Scorer (weight: 0.10)
3
+ *
4
+ * Checks completeness of the design system.
5
+ * Evaluates token type coverage, core component presence,
6
+ * variable count health, and collection completeness.
7
+ */
8
+ import { clamp, getSeverity } from "./types.js";
9
+ /** All variable types that a complete token system should include. */
10
+ const ALL_VARIABLE_TYPES = ["COLOR", "FLOAT", "STRING", "BOOLEAN"];
11
+ /** Core component categories that a mature design system should have. */
12
+ const CORE_COMPONENT_PATTERNS = [
13
+ { name: "button", pattern: /button/i },
14
+ { name: "input", pattern: /input|text\s*field/i },
15
+ { name: "card", pattern: /card/i },
16
+ { name: "modal/dialog", pattern: /modal|dialog/i },
17
+ { name: "navigation", pattern: /nav|navigation|menu|sidebar/i },
18
+ { name: "alert/toast", pattern: /alert|toast|notification|snackbar/i },
19
+ ];
20
+ /**
21
+ * Score token type coverage.
22
+ * A complete system should include COLOR, FLOAT, STRING, and BOOLEAN variables.
23
+ */
24
+ function variableDataUnavailable(data) {
25
+ return (data.dataAvailability !== undefined && !data.dataAvailability.variables);
26
+ }
27
+ function variableUnavailableMessage(data) {
28
+ const reason = data.dataAvailability?.variableError ||
29
+ "Figma Enterprise plan or Desktop Bridge plugin required";
30
+ return `Variable data unavailable: ${reason}`;
31
+ }
32
+ function scoreTokenTypeCoverage(data) {
33
+ if (data.variables.length === 0) {
34
+ return {
35
+ id: "coverage-token-types",
36
+ label: "Token type coverage",
37
+ score: 0,
38
+ severity: variableDataUnavailable(data) ? "info" : "fail",
39
+ tooltip: "A complete design system should include all variable types: COLOR, FLOAT, STRING, and BOOLEAN. Missing types indicate gaps.",
40
+ details: variableDataUnavailable(data)
41
+ ? variableUnavailableMessage(data)
42
+ : "No variables found in the design system.",
43
+ };
44
+ }
45
+ const presentTypes = new Set(data.variables.map((v) => v.resolvedType));
46
+ const foundTypes = ALL_VARIABLE_TYPES.filter((t) => presentTypes.has(t));
47
+ let score;
48
+ switch (foundTypes.length) {
49
+ case 4:
50
+ score = 100;
51
+ break;
52
+ case 3:
53
+ score = 75;
54
+ break;
55
+ case 2:
56
+ score = 50;
57
+ break;
58
+ case 1:
59
+ score = 25;
60
+ break;
61
+ default:
62
+ score = 0;
63
+ }
64
+ const missingTypes = ALL_VARIABLE_TYPES.filter((t) => !presentTypes.has(t));
65
+ return {
66
+ id: "coverage-token-types",
67
+ label: "Token type coverage",
68
+ score: clamp(score),
69
+ severity: getSeverity(score),
70
+ tooltip: "A complete design system should include all variable types: COLOR, FLOAT, STRING, and BOOLEAN. Missing types indicate gaps.",
71
+ details: missingTypes.length > 0
72
+ ? `${foundTypes.length} of ${ALL_VARIABLE_TYPES.length} variable types present. Missing: ${missingTypes.join(", ")}.`
73
+ : "All variable types (COLOR, FLOAT, STRING, BOOLEAN) are present.",
74
+ };
75
+ }
76
+ /**
77
+ * Score core component presence.
78
+ * A mature design system should include button, input, card, modal, nav, and alert components.
79
+ */
80
+ function scoreCoreComponentPresence(data) {
81
+ if (data.components.length === 0) {
82
+ return {
83
+ id: "coverage-core-components",
84
+ label: "Core component presence",
85
+ score: 0,
86
+ severity: "fail",
87
+ tooltip: "A mature design system should include core UI patterns: button, input, card, modal/dialog, navigation, and alert/toast.",
88
+ details: "No components found in the design system.",
89
+ };
90
+ }
91
+ const foundCategories = [];
92
+ const missingCategories = [];
93
+ const matchExamples = [];
94
+ for (const category of CORE_COMPONENT_PATTERNS) {
95
+ const matching = data.components.filter((c) => category.pattern.test(c.name));
96
+ if (matching.length > 0) {
97
+ foundCategories.push(category.name);
98
+ matchExamples.push(`${category.name}: ${matching
99
+ .slice(0, 2)
100
+ .map((c) => c.name)
101
+ .join(", ")}`);
102
+ }
103
+ else {
104
+ missingCategories.push(category.name);
105
+ }
106
+ }
107
+ const ratio = foundCategories.length / CORE_COMPONENT_PATTERNS.length;
108
+ const score = clamp(ratio * 100);
109
+ return {
110
+ id: "coverage-core-components",
111
+ label: "Core component presence",
112
+ score,
113
+ severity: getSeverity(score),
114
+ tooltip: "A mature design system should include core UI patterns: button, input, card, modal/dialog, navigation, and alert/toast.",
115
+ details: missingCategories.length > 0
116
+ ? `${foundCategories.length} of ${CORE_COMPONENT_PATTERNS.length} core component categories found. Missing: ${missingCategories.join(", ")}.`
117
+ : "All core component categories are represented.",
118
+ examples: matchExamples.length > 0 ? matchExamples : undefined,
119
+ };
120
+ }
121
+ /**
122
+ * Score variable count health.
123
+ * A healthy design system should have a meaningful number of tokens.
124
+ */
125
+ function scoreVariableCountHealth(data) {
126
+ const count = data.variables.length;
127
+ let score;
128
+ let details;
129
+ if (count === 0 && variableDataUnavailable(data)) {
130
+ return {
131
+ id: "coverage-variable-count",
132
+ label: "Variable count health",
133
+ score: 0,
134
+ severity: "info",
135
+ tooltip: "A healthy token system typically has 50+ variables. Fewer tokens may indicate the system relies on hard-coded values.",
136
+ details: variableUnavailableMessage(data),
137
+ };
138
+ }
139
+ if (count === 0) {
140
+ score = 0;
141
+ details =
142
+ "No variables found. A design system needs tokens for colors, spacing, and typography.";
143
+ }
144
+ else if (count <= 10) {
145
+ score = 50;
146
+ details = `${count} variables found. Consider expanding the token set for better coverage.`;
147
+ }
148
+ else if (count <= 50) {
149
+ score = 75;
150
+ details = `${count} variables found. Good foundation; consider adding more semantic layers.`;
151
+ }
152
+ else {
153
+ score = 100;
154
+ details = `${count} variables found. Healthy token coverage.`;
155
+ }
156
+ return {
157
+ id: "coverage-variable-count",
158
+ label: "Variable count health",
159
+ score: clamp(score),
160
+ severity: getSeverity(score),
161
+ tooltip: "A healthy token system typically has 50+ variables. Fewer tokens may indicate the system relies on hard-coded values.",
162
+ details,
163
+ };
164
+ }
165
+ /**
166
+ * Score collection completeness.
167
+ * Collections should cover different design concerns (color, spacing, typography, etc.).
168
+ */
169
+ function scoreCollectionCompleteness(data) {
170
+ const count = data.collections.length;
171
+ let score;
172
+ let details;
173
+ if (count === 0 && variableDataUnavailable(data)) {
174
+ return {
175
+ id: "coverage-collection-completeness",
176
+ label: "Collection completeness",
177
+ score: 0,
178
+ severity: "info",
179
+ tooltip: "Multiple collections (3+) indicate good separation of concerns. Tokens should be organized by domain: colors, spacing, typography, etc.",
180
+ details: variableUnavailableMessage(data),
181
+ };
182
+ }
183
+ if (count === 0) {
184
+ score = 0;
185
+ details =
186
+ "No variable collections found. Organize tokens into logical collections.";
187
+ }
188
+ else if (count === 1) {
189
+ score = 50;
190
+ details =
191
+ "1 collection found. Consider splitting tokens into multiple collections by concern (color, spacing, typography).";
192
+ }
193
+ else if (count === 2) {
194
+ score = 75;
195
+ details = `${count} collections found. Good separation of concerns.`;
196
+ }
197
+ else {
198
+ score = 100;
199
+ details = `${count} collections found. Well-organized token architecture.`;
200
+ }
201
+ return {
202
+ id: "coverage-collection-completeness",
203
+ label: "Collection completeness",
204
+ score: clamp(score),
205
+ severity: getSeverity(score),
206
+ tooltip: "Multiple collections (3+) indicate good separation of concerns. Tokens should be organized by domain: colors, spacing, typography, etc.",
207
+ details,
208
+ };
209
+ }
210
+ /**
211
+ * Coverage category scorer.
212
+ * Returns the average score across all coverage checks.
213
+ */
214
+ export function scoreCoverage(data) {
215
+ const findings = [
216
+ scoreTokenTypeCoverage(data),
217
+ scoreCoreComponentPresence(data),
218
+ scoreVariableCountHealth(data),
219
+ scoreCollectionCompleteness(data),
220
+ ];
221
+ const score = clamp(findings.reduce((sum, f) => sum + f.score, 0) / findings.length);
222
+ return {
223
+ id: "coverage",
224
+ label: "Coverage",
225
+ shortLabel: "Coverage",
226
+ score,
227
+ weight: 0.1,
228
+ findings,
229
+ };
230
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Design System Health Dashboard — Scoring Engine
3
+ *
4
+ * Main orchestrator that runs all 6 category scorers against raw Figma data
5
+ * and produces a Lighthouse-style health score.
6
+ *
7
+ * Data flow:
8
+ * DesignSystemRawData → scoreDesignSystem() → DashboardData (JSON)
9
+ */
10
+ import { scoreAccessibility } from "./accessibility.js";
11
+ import { classifyComponents, scoreComponentMetadata, } from "./component-metadata.js";
12
+ import { scoreConsistency } from "./consistency.js";
13
+ import { scoreCoverage } from "./coverage.js";
14
+ import { scoreNamingSemantics } from "./naming-semantics.js";
15
+ import { scoreTokenArchitecture } from "./token-architecture.js";
16
+ import { getStatus } from "./types.js";
17
+ /**
18
+ * Collect actionable findings from scored categories.
19
+ * Returns up to 5 human-readable strings from the worst findings,
20
+ * prioritizing "fail" severity, then "warning", sorted by score ascending.
21
+ */
22
+ function generateSummary(categories) {
23
+ const actionableFindings = [];
24
+ for (const category of categories) {
25
+ for (const finding of category.findings) {
26
+ if (finding.severity === "fail" || finding.severity === "warning") {
27
+ actionableFindings.push({
28
+ ...finding,
29
+ categoryLabel: category.label,
30
+ });
31
+ }
32
+ }
33
+ }
34
+ // Sort: fail before warning, then by score ascending (worst first)
35
+ actionableFindings.sort((a, b) => {
36
+ const severityOrder = { fail: 0, warning: 1, pass: 2, info: 3 };
37
+ const aSev = severityOrder[a.severity];
38
+ const bSev = severityOrder[b.severity];
39
+ if (aSev !== bSev)
40
+ return aSev - bSev;
41
+ return a.score - b.score;
42
+ });
43
+ return actionableFindings
44
+ .slice(0, 5)
45
+ .map((f) => `[${f.categoryLabel}] ${f.label}: ${f.details ?? "Needs improvement."}`);
46
+ }
47
+ /**
48
+ * Score a design system's health from raw Figma data.
49
+ *
50
+ * Runs all 6 category scorers and produces a weighted overall score.
51
+ * Category weights sum to 1.0:
52
+ * - Naming & Semantics: 0.20
53
+ * - Token Architecture: 0.20
54
+ * - Component Metadata: 0.20
55
+ * - Accessibility: 0.15
56
+ * - Consistency: 0.15
57
+ * - Coverage: 0.10
58
+ *
59
+ * @param data - Raw Figma data (variables, collections, components, styles)
60
+ * @returns Complete dashboard payload with overall score, categories, and summary
61
+ */
62
+ export function scoreDesignSystem(data) {
63
+ const categories = [
64
+ scoreNamingSemantics(data),
65
+ scoreTokenArchitecture(data),
66
+ scoreComponentMetadata(data),
67
+ scoreAccessibility(data),
68
+ scoreConsistency(data),
69
+ scoreCoverage(data),
70
+ ];
71
+ const overall = Math.round(categories.reduce((sum, c) => sum + c.score * c.weight, 0));
72
+ const summary = generateSummary(categories);
73
+ const classification = classifyComponents(data);
74
+ return {
75
+ overall,
76
+ status: getStatus(overall),
77
+ categories,
78
+ summary,
79
+ meta: {
80
+ componentCount: data.components.length,
81
+ variableCount: data.variables.length,
82
+ collectionCount: data.collections.length,
83
+ styleCount: data.styles.length,
84
+ componentSetCount: data.componentSets.length,
85
+ standaloneCount: classification.standalone.length,
86
+ variantCount: classification.variants.length,
87
+ timestamp: Date.now(),
88
+ },
89
+ fileInfo: data.fileInfo,
90
+ dataAvailability: data.dataAvailability,
91
+ };
92
+ }
@@ -0,0 +1,308 @@
1
+ /**
2
+ * Naming & Semantics Scorer (weight: 0.20)
3
+ *
4
+ * Checks whether design system names describe intent rather than appearance.
5
+ * Evaluates variable naming, component naming, variant naming, and boolean
6
+ * naming conventions against semantic best practices.
7
+ */
8
+ import { buildCollectionNameMap, clamp, getSeverity } from "./types.js";
9
+ /** Maximum examples to include in a finding. */
10
+ const MAX_EXAMPLES = 5;
11
+ /** Color words that indicate visual (non-semantic) naming. */
12
+ const VISUAL_COLOR_WORDS = [
13
+ "red",
14
+ "blue",
15
+ "green",
16
+ "yellow",
17
+ "orange",
18
+ "purple",
19
+ "pink",
20
+ "black",
21
+ "white",
22
+ "gray",
23
+ "grey",
24
+ "cyan",
25
+ "magenta",
26
+ "teal",
27
+ "indigo",
28
+ "violet",
29
+ "brown",
30
+ "lime",
31
+ "amber",
32
+ "emerald",
33
+ "rose",
34
+ "sky",
35
+ "slate",
36
+ "zinc",
37
+ "stone",
38
+ "neutral",
39
+ ];
40
+ /** Visual variant values that should be replaced with semantic ones. */
41
+ const VISUAL_VARIANT_VALUES = [
42
+ "red",
43
+ "blue",
44
+ "green",
45
+ "yellow",
46
+ "orange",
47
+ "purple",
48
+ "pink",
49
+ "black",
50
+ "white",
51
+ "gray",
52
+ "grey",
53
+ "cyan",
54
+ ];
55
+ /** Semantic variant values that indicate good naming. */
56
+ const SEMANTIC_VARIANT_VALUES = [
57
+ "primary",
58
+ "secondary",
59
+ "tertiary",
60
+ "danger",
61
+ "warning",
62
+ "success",
63
+ "info",
64
+ "error",
65
+ "disabled",
66
+ "default",
67
+ "accent",
68
+ "muted",
69
+ "destructive",
70
+ "outline",
71
+ "ghost",
72
+ "link",
73
+ ];
74
+ const PASCAL_CASE_RE = /^[A-Z][a-zA-Z0-9]*$/;
75
+ const BOOLEAN_PREFIX_RE = /^(is|has|can|should|will|did|was|with|show|hide|enable|disable)/i;
76
+ /**
77
+ * Extract the leaf segment from a variable name.
78
+ * e.g. "color/action/primary" -> "primary", "color.blue.500" -> "500"
79
+ */
80
+ function getLeafName(name) {
81
+ const parts = name.split(/[/.]/);
82
+ return parts[parts.length - 1];
83
+ }
84
+ /**
85
+ * Check if a name contains visual color words in its leaf segments.
86
+ */
87
+ function containsVisualColorWord(name) {
88
+ const leaf = getLeafName(name).toLowerCase();
89
+ return VISUAL_COLOR_WORDS.some((word) => leaf === word || leaf.startsWith(`${word}-`));
90
+ }
91
+ /**
92
+ * Score variable naming for semantic quality.
93
+ * Variables should use semantic names (color.action.primary) rather than
94
+ * visual names (color.blue.500).
95
+ */
96
+ function scoreVariableNaming(data) {
97
+ const colorVars = data.variables.filter((v) => v.resolvedType === "COLOR");
98
+ if (colorVars.length === 0) {
99
+ return {
100
+ id: "naming-variable-semantic",
101
+ label: "Variable naming",
102
+ score: 100,
103
+ severity: "info",
104
+ tooltip: "Variables should describe intent (e.g. color/action/primary) rather than appearance (e.g. color/blue/500). Semantic names survive theme changes.",
105
+ details: "No color variables to evaluate.",
106
+ };
107
+ }
108
+ const collectionNames = buildCollectionNameMap(data.collections);
109
+ const visualVars = colorVars.filter((v) => containsVisualColorWord(v.name));
110
+ const semanticRatio = 1 - visualVars.length / colorVars.length;
111
+ const score = clamp(semanticRatio * 100);
112
+ return {
113
+ id: "naming-variable-semantic",
114
+ label: "Variable naming",
115
+ score,
116
+ severity: getSeverity(score),
117
+ tooltip: "Variables should describe intent (e.g. color/action/primary) rather than appearance (e.g. color/blue/500). Semantic names survive theme changes.",
118
+ details: visualVars.length > 0
119
+ ? `${visualVars.length} of ${colorVars.length} color variables use visual names instead of semantic names.`
120
+ : `All ${colorVars.length} color variables use semantic names.`,
121
+ examples: visualVars.length > 0
122
+ ? visualVars.slice(0, MAX_EXAMPLES).map((v) => v.name)
123
+ : undefined,
124
+ locations: visualVars.length > 0
125
+ ? visualVars.slice(0, MAX_EXAMPLES).map((v) => ({
126
+ name: v.name,
127
+ collection: collectionNames.get(v.variableCollectionId),
128
+ type: "variable",
129
+ }))
130
+ : undefined,
131
+ };
132
+ }
133
+ /**
134
+ * Score component naming for consistency.
135
+ * Components should use PascalCase and avoid mixed abbreviations.
136
+ */
137
+ function scoreComponentNaming(data) {
138
+ const components = data.components;
139
+ if (components.length === 0) {
140
+ return {
141
+ id: "naming-component-casing",
142
+ label: "Component naming",
143
+ score: 100,
144
+ severity: "info",
145
+ tooltip: "Component names should use consistent PascalCase (e.g. Button, IconStar). Consistent casing improves discoverability.",
146
+ details: "No components to evaluate.",
147
+ };
148
+ }
149
+ let pascalCount = 0;
150
+ const nonPascalComps = [];
151
+ for (const comp of components) {
152
+ // Component names may use path separators; check each segment
153
+ const segments = comp.name.split("/").map((s) => s.trim());
154
+ const allPascal = segments.every((seg) => PASCAL_CASE_RE.test(seg));
155
+ if (allPascal) {
156
+ pascalCount++;
157
+ }
158
+ else {
159
+ nonPascalComps.push(comp.name);
160
+ }
161
+ }
162
+ const ratio = pascalCount / components.length;
163
+ const score = clamp(ratio * 100);
164
+ return {
165
+ id: "naming-component-casing",
166
+ label: "Component naming",
167
+ score,
168
+ severity: getSeverity(score),
169
+ tooltip: "Component names should use consistent PascalCase (e.g. Button, IconStar). Consistent casing improves discoverability.",
170
+ details: `${pascalCount} of ${components.length} components use consistent PascalCase naming.`,
171
+ examples: nonPascalComps.length > 0
172
+ ? nonPascalComps.slice(0, MAX_EXAMPLES)
173
+ : undefined,
174
+ };
175
+ }
176
+ /**
177
+ * Score variant naming for semantic quality.
178
+ * Variant values should be semantic (primary, danger) rather than
179
+ * visual (blue, red). Also checks for consistent size naming.
180
+ */
181
+ function scoreVariantNaming(data) {
182
+ // Components with variants have a componentSetId (Plugin API),
183
+ // containing_frame.containingComponentSet (REST API), or component_set_id (file JSON)
184
+ const variantComponents = data.components.filter((c) => c.componentSetId ||
185
+ c.containing_frame?.containingComponentSet ||
186
+ c.component_set_id);
187
+ if (variantComponents.length === 0) {
188
+ return {
189
+ id: "naming-variant-semantic",
190
+ label: "Variant naming",
191
+ score: 100,
192
+ severity: "info",
193
+ tooltip: "Variant values should use semantic terms (primary, danger) rather than visual ones (red, blue). This decouples design from implementation.",
194
+ details: "No variant components to evaluate.",
195
+ };
196
+ }
197
+ // Extract variant info from component names (e.g., "Button/Primary" or "Size=Small, Color=Primary")
198
+ let semanticCount = 0;
199
+ let visualCount = 0;
200
+ const visualVariantComps = [];
201
+ for (const comp of variantComponents) {
202
+ const nameLower = comp.name.toLowerCase();
203
+ const hasVisual = VISUAL_VARIANT_VALUES.some((v) => nameLower === v ||
204
+ nameLower.includes(`=${v}`) ||
205
+ nameLower.includes(`, ${v}`));
206
+ const hasSemantic = SEMANTIC_VARIANT_VALUES.some((v) => nameLower === v ||
207
+ nameLower.includes(`=${v}`) ||
208
+ nameLower.includes(`, ${v}`));
209
+ if (hasSemantic)
210
+ semanticCount++;
211
+ if (hasVisual) {
212
+ visualCount++;
213
+ visualVariantComps.push(comp.name);
214
+ }
215
+ }
216
+ const totalEvaluated = semanticCount + visualCount;
217
+ if (totalEvaluated === 0) {
218
+ return {
219
+ id: "naming-variant-semantic",
220
+ label: "Variant naming",
221
+ score: 75,
222
+ severity: "warning",
223
+ tooltip: "Variant values should use semantic terms (primary, danger) rather than visual ones (red, blue). This decouples design from implementation.",
224
+ details: "Variant values could not be classified as semantic or visual.",
225
+ };
226
+ }
227
+ const ratio = semanticCount / totalEvaluated;
228
+ const score = clamp(ratio * 100);
229
+ return {
230
+ id: "naming-variant-semantic",
231
+ label: "Variant naming",
232
+ score,
233
+ severity: getSeverity(score),
234
+ tooltip: "Variant values should use semantic terms (primary, danger) rather than visual ones (red, blue). This decouples design from implementation.",
235
+ details: visualCount > 0
236
+ ? `${visualCount} variant values use visual names. Prefer semantic names like "primary", "danger".`
237
+ : "Variant values use semantic naming conventions.",
238
+ examples: visualVariantComps.length > 0
239
+ ? visualVariantComps.slice(0, MAX_EXAMPLES)
240
+ : undefined,
241
+ };
242
+ }
243
+ /**
244
+ * Score boolean variable naming.
245
+ * Boolean variables should follow is-, has-, can- prefix patterns.
246
+ */
247
+ function scoreBooleanNaming(data) {
248
+ const boolVars = data.variables.filter((v) => v.resolvedType === "BOOLEAN");
249
+ if (boolVars.length === 0) {
250
+ return {
251
+ id: "naming-boolean-prefix",
252
+ label: "Boolean naming",
253
+ score: 100,
254
+ severity: "info",
255
+ tooltip: "Boolean variables should start with is, has, can, show, or similar prefixes (e.g. isDisabled). This makes their purpose immediately clear.",
256
+ details: "No boolean variables to evaluate.",
257
+ };
258
+ }
259
+ const collectionNames = buildCollectionNameMap(data.collections);
260
+ const missingPrefix = boolVars.filter((v) => {
261
+ const leaf = getLeafName(v.name);
262
+ return !BOOLEAN_PREFIX_RE.test(leaf);
263
+ });
264
+ const correctCount = boolVars.length - missingPrefix.length;
265
+ const ratio = correctCount / boolVars.length;
266
+ const score = clamp(ratio * 100);
267
+ return {
268
+ id: "naming-boolean-prefix",
269
+ label: "Boolean naming",
270
+ score,
271
+ severity: getSeverity(score),
272
+ tooltip: "Boolean variables should start with is, has, can, show, or similar prefixes (e.g. isDisabled). This makes their purpose immediately clear.",
273
+ details: missingPrefix.length > 0
274
+ ? `${missingPrefix.length} of ${boolVars.length} boolean variables lack is*/has*/can* prefixes.`
275
+ : `All ${boolVars.length} boolean variables use proper prefixes.`,
276
+ examples: missingPrefix.length > 0
277
+ ? missingPrefix.slice(0, MAX_EXAMPLES).map((v) => v.name)
278
+ : undefined,
279
+ locations: missingPrefix.length > 0
280
+ ? missingPrefix.slice(0, MAX_EXAMPLES).map((v) => ({
281
+ name: v.name,
282
+ collection: collectionNames.get(v.variableCollectionId),
283
+ type: "variable",
284
+ }))
285
+ : undefined,
286
+ };
287
+ }
288
+ /**
289
+ * Naming & Semantics category scorer.
290
+ * Returns the average score across all naming checks.
291
+ */
292
+ export function scoreNamingSemantics(data) {
293
+ const findings = [
294
+ scoreVariableNaming(data),
295
+ scoreComponentNaming(data),
296
+ scoreVariantNaming(data),
297
+ scoreBooleanNaming(data),
298
+ ];
299
+ const score = clamp(findings.reduce((sum, f) => sum + f.score, 0) / findings.length);
300
+ return {
301
+ id: "naming-semantics",
302
+ label: "Naming & Semantics",
303
+ shortLabel: "Naming",
304
+ score,
305
+ weight: 0.2,
306
+ findings,
307
+ };
308
+ }