@muloka/figma-console-mcp 0.5.0 → 0.5.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.
Files changed (111) hide show
  1. package/README.md +3 -1
  2. package/dist/core/port-discovery.d.ts +9 -0
  3. package/dist/core/port-discovery.d.ts.map +1 -1
  4. package/dist/core/port-discovery.js +40 -14
  5. package/dist/core/port-discovery.js.map +1 -1
  6. package/dist/core/websocket-connector.d.ts +2 -1
  7. package/dist/core/websocket-connector.d.ts.map +1 -1
  8. package/dist/core/websocket-connector.js +17 -2
  9. package/dist/core/websocket-connector.js.map +1 -1
  10. package/dist/core/websocket-server.d.ts +8 -1
  11. package/dist/core/websocket-server.d.ts.map +1 -1
  12. package/dist/core/websocket-server.js +18 -2
  13. package/dist/core/websocket-server.js.map +1 -1
  14. package/dist/core/write-tools.d.ts +3 -1
  15. package/dist/core/write-tools.d.ts.map +1 -1
  16. package/dist/core/write-tools.js +34 -14
  17. package/dist/core/write-tools.js.map +1 -1
  18. package/dist/local.d.ts +2 -1
  19. package/dist/local.d.ts.map +1 -1
  20. package/dist/local.js +10 -14
  21. package/dist/local.js.map +1 -1
  22. package/package.json +1 -1
  23. package/dist/apps/design-system-dashboard/mcp-app.html +0 -521
  24. package/dist/apps/token-browser/mcp-app.html +0 -410
  25. package/dist/cloudflare/apps/design-system-dashboard/scoring/accessibility.js +0 -277
  26. package/dist/cloudflare/apps/design-system-dashboard/scoring/component-metadata.js +0 -357
  27. package/dist/cloudflare/apps/design-system-dashboard/scoring/consistency.js +0 -341
  28. package/dist/cloudflare/apps/design-system-dashboard/scoring/coverage.js +0 -230
  29. package/dist/cloudflare/apps/design-system-dashboard/scoring/engine.js +0 -92
  30. package/dist/cloudflare/apps/design-system-dashboard/scoring/naming-semantics.js +0 -308
  31. package/dist/cloudflare/apps/design-system-dashboard/scoring/token-architecture.js +0 -349
  32. package/dist/cloudflare/apps/design-system-dashboard/scoring/types.js +0 -40
  33. package/dist/cloudflare/apps/design-system-dashboard/server.js +0 -159
  34. package/dist/cloudflare/apps/token-browser/server.js +0 -136
  35. package/dist/cloudflare/browser-manager.js +0 -157
  36. package/dist/cloudflare/core/accessibility-tools.js +0 -361
  37. package/dist/cloudflare/core/annotation-tools.js +0 -230
  38. package/dist/cloudflare/core/cloud-websocket-connector.js +0 -419
  39. package/dist/cloudflare/core/cloud-websocket-relay.js +0 -198
  40. package/dist/cloudflare/core/comment-tools.js +0 -292
  41. package/dist/cloudflare/core/config.js +0 -153
  42. package/dist/cloudflare/core/console-monitor.js +0 -427
  43. package/dist/cloudflare/core/deep-component-tools.js +0 -128
  44. package/dist/cloudflare/core/design-code-tools.js +0 -2793
  45. package/dist/cloudflare/core/design-system-manifest.js +0 -265
  46. package/dist/cloudflare/core/design-system-tools.js +0 -882
  47. package/dist/cloudflare/core/diagnose-tool.js +0 -100
  48. package/dist/cloudflare/core/diff/changelog-formatter.js +0 -275
  49. package/dist/cloudflare/core/diff/diff-engine.js +0 -334
  50. package/dist/cloudflare/core/diff/property-compare.js +0 -36
  51. package/dist/cloudflare/core/diff/version-cache.js +0 -74
  52. package/dist/cloudflare/core/enrichment/enrichment-service.js +0 -374
  53. package/dist/cloudflare/core/enrichment/index.js +0 -7
  54. package/dist/cloudflare/core/enrichment/relationship-mapper.js +0 -351
  55. package/dist/cloudflare/core/enrichment/style-resolver.js +0 -346
  56. package/dist/cloudflare/core/figjam-tools.js +0 -547
  57. package/dist/cloudflare/core/figma-api.js +0 -509
  58. package/dist/cloudflare/core/figma-connector.js +0 -7
  59. package/dist/cloudflare/core/figma-reconstruction-spec.js +0 -402
  60. package/dist/cloudflare/core/figma-style-extractor.js +0 -311
  61. package/dist/cloudflare/core/figma-tools.js +0 -3353
  62. package/dist/cloudflare/core/identity.js +0 -96
  63. package/dist/cloudflare/core/library-tools.js +0 -580
  64. package/dist/cloudflare/core/logger.js +0 -53
  65. package/dist/cloudflare/core/port-discovery.js +0 -935
  66. package/dist/cloudflare/core/resolve-package-root.js +0 -11
  67. package/dist/cloudflare/core/slides-tools.js +0 -714
  68. package/dist/cloudflare/core/slot-tools.js +0 -334
  69. package/dist/cloudflare/core/snippet-injector.js +0 -96
  70. package/dist/cloudflare/core/tokens/alias-resolver.js +0 -205
  71. package/dist/cloudflare/core/tokens/config.js +0 -294
  72. package/dist/cloudflare/core/tokens/dialect.js +0 -232
  73. package/dist/cloudflare/core/tokens/figma-converter.js +0 -323
  74. package/dist/cloudflare/core/tokens/formatters/css-vars.js +0 -338
  75. package/dist/cloudflare/core/tokens/formatters/dtcg.js +0 -376
  76. package/dist/cloudflare/core/tokens/formatters/index.js +0 -45
  77. package/dist/cloudflare/core/tokens/formatters/json.js +0 -205
  78. package/dist/cloudflare/core/tokens/formatters/less.js +0 -4
  79. package/dist/cloudflare/core/tokens/formatters/scss.js +0 -258
  80. package/dist/cloudflare/core/tokens/formatters/stubs.js +0 -13
  81. package/dist/cloudflare/core/tokens/formatters/style-dictionary-v3.js +0 -213
  82. package/dist/cloudflare/core/tokens/formatters/tailwind-v3.js +0 -237
  83. package/dist/cloudflare/core/tokens/formatters/tailwind-v4.js +0 -335
  84. package/dist/cloudflare/core/tokens/formatters/tokens-studio.js +0 -256
  85. package/dist/cloudflare/core/tokens/formatters/ts-module.js +0 -198
  86. package/dist/cloudflare/core/tokens/index.js +0 -16
  87. package/dist/cloudflare/core/tokens/parsers/css-vars.js +0 -4
  88. package/dist/cloudflare/core/tokens/parsers/dtcg.js +0 -280
  89. package/dist/cloudflare/core/tokens/parsers/index.js +0 -138
  90. package/dist/cloudflare/core/tokens/parsers/json.js +0 -7
  91. package/dist/cloudflare/core/tokens/parsers/scss.js +0 -4
  92. package/dist/cloudflare/core/tokens/parsers/stubs.js +0 -20
  93. package/dist/cloudflare/core/tokens/parsers/style-dictionary-v3.js +0 -4
  94. package/dist/cloudflare/core/tokens/parsers/tailwind-v3.js +0 -4
  95. package/dist/cloudflare/core/tokens/parsers/tailwind-v4.js +0 -4
  96. package/dist/cloudflare/core/tokens/parsers/tokens-studio.js +0 -4
  97. package/dist/cloudflare/core/tokens/schemas.js +0 -152
  98. package/dist/cloudflare/core/tokens/transforms/color.js +0 -12
  99. package/dist/cloudflare/core/tokens/transforms/index.js +0 -29
  100. package/dist/cloudflare/core/tokens/transforms/size.js +0 -7
  101. package/dist/cloudflare/core/tokens/types.js +0 -18
  102. package/dist/cloudflare/core/tokens-tools.js +0 -1788
  103. package/dist/cloudflare/core/types/design-code.js +0 -4
  104. package/dist/cloudflare/core/types/enriched.js +0 -5
  105. package/dist/cloudflare/core/types/index.js +0 -4
  106. package/dist/cloudflare/core/variable-resolver.js +0 -85
  107. package/dist/cloudflare/core/version-tools.js +0 -1199
  108. package/dist/cloudflare/core/websocket-connector.js +0 -436
  109. package/dist/cloudflare/core/websocket-server.js +0 -1009
  110. package/dist/cloudflare/core/write-tools.js +0 -2506
  111. package/dist/cloudflare/index.js +0 -3010
@@ -1,308 +0,0 @@
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
- }
@@ -1,349 +0,0 @@
1
- /**
2
- * Token Architecture Scorer (weight: 0.20)
3
- *
4
- * Evaluates the depth and organization of the token system.
5
- * Checks collection organization, mode coverage, alias usage,
6
- * token tier depth, type distribution, and description coverage.
7
- */
8
- import { buildCollectionNameMap, clamp, getSeverity } from "./types.js";
9
- /** Maximum examples to include in a finding. */
10
- const MAX_EXAMPLES = 5;
11
- /**
12
- * Check if a value entry is a variable alias reference.
13
- */
14
- function isAlias(value) {
15
- return (typeof value === "object" &&
16
- value !== null &&
17
- value.type === "VARIABLE_ALIAS");
18
- }
19
- /** Check if variable data was unavailable (vs. genuinely empty). */
20
- function variableDataUnavailable(data) {
21
- return (data.dataAvailability !== undefined && !data.dataAvailability.variables);
22
- }
23
- /** Message explaining why variable data is missing. */
24
- function variableUnavailableMessage(data) {
25
- const reason = data.dataAvailability?.variableError ||
26
- "Figma Enterprise plan or Desktop Bridge plugin required";
27
- return `Variable data unavailable: ${reason}. Score reflects missing data, not actual design system quality.`;
28
- }
29
- /**
30
- * Score collection organization.
31
- * Variables should be organized into collections.
32
- */
33
- function scoreCollectionOrganization(data) {
34
- const count = data.collections.length;
35
- if (count === 0 && variableDataUnavailable(data)) {
36
- return {
37
- id: "token-collection-org",
38
- label: "Collection organization",
39
- score: 0,
40
- severity: "info",
41
- tooltip: "Variables should be grouped into collections by concern (colors, spacing, typography). More collections = better organization.",
42
- details: variableUnavailableMessage(data),
43
- };
44
- }
45
- let score;
46
- if (count >= 3) {
47
- score = 100;
48
- }
49
- else if (count === 2) {
50
- score = 80;
51
- }
52
- else if (count === 1) {
53
- score = 50;
54
- }
55
- else {
56
- score = 0;
57
- }
58
- return {
59
- id: "token-collection-org",
60
- label: "Collection organization",
61
- score: clamp(score),
62
- severity: getSeverity(score),
63
- tooltip: "Variables should be grouped into collections by concern (colors, spacing, typography). More collections = better organization.",
64
- details: count === 0
65
- ? "No variable collections found. Organize variables into collections."
66
- : `${count} collection${count === 1 ? "" : "s"} found.`,
67
- };
68
- }
69
- /**
70
- * Score mode coverage.
71
- * Collections should have multiple modes (e.g., light/dark).
72
- */
73
- function scoreModeCoverage(data) {
74
- if (data.collections.length === 0) {
75
- return {
76
- id: "token-mode-coverage",
77
- label: "Mode coverage",
78
- score: 0,
79
- severity: variableDataUnavailable(data) ? "info" : "fail",
80
- tooltip: "Collections should support multiple modes (e.g. Light/Dark). This enables theming without duplicating tokens.",
81
- details: variableDataUnavailable(data)
82
- ? variableUnavailableMessage(data)
83
- : "No collections found to evaluate mode coverage.",
84
- };
85
- }
86
- const maxModes = Math.max(...data.collections.map((c) => c.modes?.length ?? 0));
87
- let score;
88
- if (maxModes >= 2) {
89
- score = 100;
90
- }
91
- else if (maxModes === 1) {
92
- score = 50;
93
- }
94
- else {
95
- score = 0;
96
- }
97
- return {
98
- id: "token-mode-coverage",
99
- label: "Mode coverage",
100
- score: clamp(score),
101
- severity: getSeverity(score),
102
- tooltip: "Collections should support multiple modes (e.g. Light/Dark). This enables theming without duplicating tokens.",
103
- details: maxModes >= 2
104
- ? `Collections support up to ${maxModes} modes (e.g., light/dark).`
105
- : maxModes === 1
106
- ? "Collections have only 1 mode. Consider adding light/dark modes."
107
- : "No modes detected in collections.",
108
- };
109
- }
110
- /**
111
- * Score alias usage.
112
- * Higher alias percentage indicates better token layering.
113
- */
114
- function scoreAliasUsage(data) {
115
- if (data.variables.length === 0) {
116
- return {
117
- id: "token-alias-usage",
118
- label: "Alias usage",
119
- score: 0,
120
- severity: variableDataUnavailable(data) ? "info" : "fail",
121
- tooltip: "Semantic tokens should reference primitive tokens via aliases rather than hard-coding values. Aliases enable single-source-of-truth updates.",
122
- details: variableDataUnavailable(data)
123
- ? variableUnavailableMessage(data)
124
- : "No variables to evaluate alias usage.",
125
- };
126
- }
127
- const collectionNames = buildCollectionNameMap(data.collections);
128
- let aliasCount = 0;
129
- let totalValues = 0;
130
- const rawValueVars = [];
131
- for (const variable of data.variables) {
132
- if (!variable.valuesByMode)
133
- continue;
134
- let hasAnyAlias = false;
135
- for (const modeValues of Object.values(variable.valuesByMode)) {
136
- totalValues++;
137
- if (isAlias(modeValues)) {
138
- aliasCount++;
139
- hasAnyAlias = true;
140
- }
141
- }
142
- if (!hasAnyAlias) {
143
- rawValueVars.push(variable);
144
- }
145
- }
146
- if (totalValues === 0) {
147
- return {
148
- id: "token-alias-usage",
149
- label: "Alias usage",
150
- score: 0,
151
- severity: "fail",
152
- tooltip: "Semantic tokens should reference primitive tokens via aliases rather than hard-coding values. Aliases enable single-source-of-truth updates.",
153
- details: "No variable values found to evaluate.",
154
- };
155
- }
156
- const ratio = aliasCount / totalValues;
157
- const score = clamp(ratio * 100);
158
- return {
159
- id: "token-alias-usage",
160
- label: "Alias usage",
161
- score,
162
- severity: getSeverity(score),
163
- tooltip: "Semantic tokens should reference primitive tokens via aliases rather than hard-coding values. Aliases enable single-source-of-truth updates.",
164
- details: `${aliasCount} of ${totalValues} values are aliases (${Math.round(ratio * 100)}%). Higher alias usage indicates better token layering.`,
165
- examples: rawValueVars.length > 0 ? rawValueVars.slice(0, MAX_EXAMPLES).map((v) => v.name) : undefined,
166
- locations: rawValueVars.length > 0
167
- ? rawValueVars.slice(0, MAX_EXAMPLES).map((v) => ({
168
- name: v.name,
169
- collection: collectionNames.get(v.variableCollectionId),
170
- type: "variable",
171
- }))
172
- : undefined,
173
- };
174
- }
175
- /**
176
- * Build a map of variable ID to variable for alias chain tracing.
177
- */
178
- function buildVariableMap(variables) {
179
- const map = new Map();
180
- for (const v of variables) {
181
- if (v.id) {
182
- map.set(v.id, v);
183
- }
184
- }
185
- return map;
186
- }
187
- /**
188
- * Trace the depth of an alias chain starting from a given variable.
189
- * Returns the number of tiers (1 = raw value, 2 = one alias hop, etc.).
190
- */
191
- function traceAliasDepth(variable, variableMap, visited) {
192
- if (!variable.valuesByMode)
193
- return 1;
194
- const values = Object.values(variable.valuesByMode);
195
- let maxDepth = 1;
196
- for (const value of values) {
197
- if (isAlias(value)) {
198
- const targetId = value.id;
199
- if (visited.has(targetId))
200
- continue; // Prevent circular references
201
- const target = variableMap.get(targetId);
202
- if (target) {
203
- visited.add(targetId);
204
- const depth = 1 + traceAliasDepth(target, variableMap, visited);
205
- maxDepth = Math.max(maxDepth, depth);
206
- }
207
- }
208
- }
209
- return maxDepth;
210
- }
211
- /**
212
- * Score token tier depth.
213
- * Deeper alias chains indicate more sophisticated token architecture.
214
- */
215
- function scoreTokenTierDepth(data) {
216
- if (data.variables.length === 0) {
217
- return {
218
- id: "token-tier-depth",
219
- label: "Token tier depth",
220
- score: 0,
221
- severity: variableDataUnavailable(data) ? "info" : "fail",
222
- tooltip: "A layered token system (primitive -> semantic -> component) enables scalable theming. 3+ tiers indicates mature architecture.",
223
- details: variableDataUnavailable(data)
224
- ? variableUnavailableMessage(data)
225
- : "No variables to evaluate tier depth.",
226
- };
227
- }
228
- const variableMap = buildVariableMap(data.variables);
229
- let maxDepth = 1;
230
- for (const variable of data.variables) {
231
- const visited = new Set();
232
- if (variable.id)
233
- visited.add(variable.id);
234
- const depth = traceAliasDepth(variable, variableMap, visited);
235
- maxDepth = Math.max(maxDepth, depth);
236
- }
237
- let score;
238
- if (maxDepth >= 3) {
239
- score = 100;
240
- }
241
- else if (maxDepth === 2) {
242
- score = 67;
243
- }
244
- else {
245
- score = 33;
246
- }
247
- return {
248
- id: "token-tier-depth",
249
- label: "Token tier depth",
250
- score: clamp(score),
251
- severity: getSeverity(score),
252
- tooltip: "A layered token system (primitive -> semantic -> component) enables scalable theming. 3+ tiers indicates mature architecture.",
253
- details: `Maximum alias chain depth: ${maxDepth} tier${maxDepth === 1 ? "" : "s"}. 3+ tiers indicates a well-layered token architecture.`,
254
- };
255
- }
256
- /**
257
- * Score type distribution across variables.
258
- * A healthy system includes COLOR, FLOAT, and STRING variables.
259
- */
260
- function scoreTypeDistribution(data) {
261
- if (data.variables.length === 0) {
262
- return {
263
- id: "token-type-distribution",
264
- label: "Type distribution",
265
- score: 0,
266
- severity: variableDataUnavailable(data) ? "info" : "fail",
267
- tooltip: "A complete token system includes COLOR, FLOAT, and STRING variables. Missing types indicate gaps in the design language.",
268
- details: variableDataUnavailable(data)
269
- ? variableUnavailableMessage(data)
270
- : "No variables to evaluate type distribution.",
271
- };
272
- }
273
- const expectedTypes = ["COLOR", "FLOAT", "STRING"];
274
- const presentTypes = new Set(data.variables.map((v) => v.resolvedType));
275
- const matchCount = expectedTypes.filter((t) => presentTypes.has(t)).length;
276
- const score = clamp((matchCount / expectedTypes.length) * 100);
277
- return {
278
- id: "token-type-distribution",
279
- label: "Type distribution",
280
- score,
281
- severity: getSeverity(score),
282
- tooltip: "A complete token system includes COLOR, FLOAT, and STRING variables. Missing types indicate gaps in the design language.",
283
- details: `${matchCount} of ${expectedTypes.length} expected types (COLOR, FLOAT, STRING) present. Found: ${[...presentTypes].join(", ") || "none"}.`,
284
- };
285
- }
286
- /**
287
- * Score description coverage for variables.
288
- * Well-documented variables should have non-empty descriptions.
289
- */
290
- function scoreDescriptionCoverage(data) {
291
- if (data.variables.length === 0) {
292
- return {
293
- id: "token-description-coverage",
294
- label: "Description coverage",
295
- score: 0,
296
- severity: variableDataUnavailable(data) ? "info" : "fail",
297
- tooltip: "Variables should have descriptions explaining their purpose and usage context. Descriptions help consumers choose the right token.",
298
- details: variableDataUnavailable(data)
299
- ? variableUnavailableMessage(data)
300
- : "No variables to evaluate description coverage.",
301
- };
302
- }
303
- const collectionNames = buildCollectionNameMap(data.collections);
304
- const withDesc = data.variables.filter((v) => v.description && v.description.trim().length > 0);
305
- const withoutDesc = data.variables.filter((v) => !v.description || v.description.trim().length === 0);
306
- const ratio = withDesc.length / data.variables.length;
307
- const score = clamp(ratio * 100);
308
- return {
309
- id: "token-description-coverage",
310
- label: "Description coverage",
311
- score,
312
- severity: getSeverity(score),
313
- tooltip: "Variables should have descriptions explaining their purpose and usage context. Descriptions help consumers choose the right token.",
314
- details: `${withDesc.length} of ${data.variables.length} variables have descriptions (${Math.round(ratio * 100)}%).`,
315
- examples: withoutDesc.length > 0
316
- ? withoutDesc.slice(0, MAX_EXAMPLES).map((v) => v.name)
317
- : undefined,
318
- locations: withoutDesc.length > 0
319
- ? withoutDesc.slice(0, MAX_EXAMPLES).map((v) => ({
320
- name: v.name,
321
- collection: collectionNames.get(v.variableCollectionId),
322
- type: "variable",
323
- }))
324
- : undefined,
325
- };
326
- }
327
- /**
328
- * Token Architecture category scorer.
329
- * Returns the average score across all token architecture checks.
330
- */
331
- export function scoreTokenArchitecture(data) {
332
- const findings = [
333
- scoreCollectionOrganization(data),
334
- scoreModeCoverage(data),
335
- scoreAliasUsage(data),
336
- scoreTokenTierDepth(data),
337
- scoreTypeDistribution(data),
338
- scoreDescriptionCoverage(data),
339
- ];
340
- const score = clamp(findings.reduce((sum, f) => sum + f.score, 0) / findings.length);
341
- return {
342
- id: "token-architecture",
343
- label: "Token Architecture",
344
- shortLabel: "Tokens",
345
- score,
346
- weight: 0.2,
347
- findings,
348
- };
349
- }
@@ -1,40 +0,0 @@
1
- /**
2
- * Design System Dashboard — Shared Types
3
- *
4
- * Defines the JSON contract between the scoring engine (server-side),
5
- * the server registration (tools), and the UI (client-side rendering).
6
- */
7
- /** Build a map of collection ID → collection name for location context. */
8
- export function buildCollectionNameMap(collections) {
9
- const map = new Map();
10
- for (const col of collections) {
11
- if (col.id && col.name)
12
- map.set(col.id, col.name);
13
- }
14
- return map;
15
- }
16
- // ---------------------------------------------------------------------------
17
- // Thresholds and helpers
18
- // ---------------------------------------------------------------------------
19
- export const THRESHOLDS = {
20
- GOOD: 90,
21
- NEEDS_WORK: 50,
22
- };
23
- export function getStatus(score) {
24
- if (score >= THRESHOLDS.GOOD)
25
- return "good";
26
- if (score >= THRESHOLDS.NEEDS_WORK)
27
- return "needs-work";
28
- return "poor";
29
- }
30
- export function getSeverity(score) {
31
- if (score >= THRESHOLDS.GOOD)
32
- return "pass";
33
- if (score >= THRESHOLDS.NEEDS_WORK)
34
- return "warning";
35
- return "fail";
36
- }
37
- /** Clamp a number to 0-100. */
38
- export function clamp(value) {
39
- return Math.max(0, Math.min(100, Math.round(value)));
40
- }