@fragments-sdk/cli 0.2.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 (259) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +106 -0
  3. package/dist/bin.d.ts +1 -0
  4. package/dist/bin.js +4783 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/chunk-4FDQSGKX.js +786 -0
  7. package/dist/chunk-4FDQSGKX.js.map +1 -0
  8. package/dist/chunk-7H2MMGYG.js +369 -0
  9. package/dist/chunk-7H2MMGYG.js.map +1 -0
  10. package/dist/chunk-BSCG3IP7.js +619 -0
  11. package/dist/chunk-BSCG3IP7.js.map +1 -0
  12. package/dist/chunk-LY2CFFPY.js +898 -0
  13. package/dist/chunk-LY2CFFPY.js.map +1 -0
  14. package/dist/chunk-MUZ6CM66.js +6636 -0
  15. package/dist/chunk-MUZ6CM66.js.map +1 -0
  16. package/dist/chunk-OAENNG3G.js +1489 -0
  17. package/dist/chunk-OAENNG3G.js.map +1 -0
  18. package/dist/chunk-XHNKNI6J.js +235 -0
  19. package/dist/chunk-XHNKNI6J.js.map +1 -0
  20. package/dist/core-DWKLGY4N.js +68 -0
  21. package/dist/core-DWKLGY4N.js.map +1 -0
  22. package/dist/generate-4LQNJ7SX.js +249 -0
  23. package/dist/generate-4LQNJ7SX.js.map +1 -0
  24. package/dist/index.d.ts +775 -0
  25. package/dist/index.js +41 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/init-EMVI47QG.js +416 -0
  28. package/dist/init-EMVI47QG.js.map +1 -0
  29. package/dist/mcp-bin.d.ts +1 -0
  30. package/dist/mcp-bin.js +1117 -0
  31. package/dist/mcp-bin.js.map +1 -0
  32. package/dist/scan-4YPRF7FV.js +12 -0
  33. package/dist/scan-4YPRF7FV.js.map +1 -0
  34. package/dist/service-QSZMZJBJ.js +208 -0
  35. package/dist/service-QSZMZJBJ.js.map +1 -0
  36. package/dist/static-viewer-MIPGZ4Z7.js +12 -0
  37. package/dist/static-viewer-MIPGZ4Z7.js.map +1 -0
  38. package/dist/test-SQ5ZHXWU.js +1067 -0
  39. package/dist/test-SQ5ZHXWU.js.map +1 -0
  40. package/dist/tokens-HSGMYK64.js +173 -0
  41. package/dist/tokens-HSGMYK64.js.map +1 -0
  42. package/dist/viewer-YRF4SQE4.js +11101 -0
  43. package/dist/viewer-YRF4SQE4.js.map +1 -0
  44. package/package.json +107 -0
  45. package/src/ai.ts +266 -0
  46. package/src/analyze.ts +265 -0
  47. package/src/bin.ts +916 -0
  48. package/src/build.ts +248 -0
  49. package/src/commands/a11y.ts +302 -0
  50. package/src/commands/add.ts +313 -0
  51. package/src/commands/audit.ts +195 -0
  52. package/src/commands/baseline.ts +221 -0
  53. package/src/commands/build.ts +144 -0
  54. package/src/commands/compare.ts +337 -0
  55. package/src/commands/context.ts +107 -0
  56. package/src/commands/dev.ts +107 -0
  57. package/src/commands/enhance.ts +858 -0
  58. package/src/commands/generate.ts +391 -0
  59. package/src/commands/init.ts +531 -0
  60. package/src/commands/link/figma.ts +645 -0
  61. package/src/commands/link/index.ts +10 -0
  62. package/src/commands/link/storybook.ts +267 -0
  63. package/src/commands/list.ts +49 -0
  64. package/src/commands/metrics.ts +114 -0
  65. package/src/commands/reset.ts +242 -0
  66. package/src/commands/scan.ts +537 -0
  67. package/src/commands/storygen.ts +207 -0
  68. package/src/commands/tokens.ts +251 -0
  69. package/src/commands/validate.ts +93 -0
  70. package/src/commands/verify.ts +215 -0
  71. package/src/core/composition.test.ts +262 -0
  72. package/src/core/composition.ts +255 -0
  73. package/src/core/config.ts +84 -0
  74. package/src/core/constants.ts +111 -0
  75. package/src/core/context.ts +380 -0
  76. package/src/core/defineSegment.ts +137 -0
  77. package/src/core/discovery.ts +337 -0
  78. package/src/core/figma.ts +263 -0
  79. package/src/core/fragment-types.ts +214 -0
  80. package/src/core/generators/context.ts +389 -0
  81. package/src/core/generators/index.ts +23 -0
  82. package/src/core/generators/registry.ts +364 -0
  83. package/src/core/generators/typescript-extractor.ts +374 -0
  84. package/src/core/importAnalyzer.ts +217 -0
  85. package/src/core/index.ts +149 -0
  86. package/src/core/loader.ts +155 -0
  87. package/src/core/node.ts +63 -0
  88. package/src/core/parser.ts +551 -0
  89. package/src/core/previewLoader.ts +172 -0
  90. package/src/core/schema/fragment.schema.json +189 -0
  91. package/src/core/schema/registry.schema.json +137 -0
  92. package/src/core/schema.ts +182 -0
  93. package/src/core/storyAdapter.test.ts +571 -0
  94. package/src/core/storyAdapter.ts +761 -0
  95. package/src/core/token-types.ts +287 -0
  96. package/src/core/types.ts +754 -0
  97. package/src/diff.ts +323 -0
  98. package/src/index.ts +43 -0
  99. package/src/mcp/__tests__/projectFields.test.ts +130 -0
  100. package/src/mcp/bin.ts +36 -0
  101. package/src/mcp/index.ts +8 -0
  102. package/src/mcp/server.ts +1310 -0
  103. package/src/mcp/utils.ts +54 -0
  104. package/src/mcp-bin.ts +36 -0
  105. package/src/migrate/__tests__/argTypes/argTypes.test.ts +189 -0
  106. package/src/migrate/__tests__/args/args.test.ts +452 -0
  107. package/src/migrate/__tests__/meta/meta.test.ts +198 -0
  108. package/src/migrate/__tests__/stories/stories.test.ts +278 -0
  109. package/src/migrate/__tests__/utils/utils.test.ts +371 -0
  110. package/src/migrate/__tests__/values/values.test.ts +303 -0
  111. package/src/migrate/bin.ts +108 -0
  112. package/src/migrate/converter.ts +658 -0
  113. package/src/migrate/detect.ts +196 -0
  114. package/src/migrate/index.ts +45 -0
  115. package/src/migrate/migrate.ts +163 -0
  116. package/src/migrate/parser.ts +1136 -0
  117. package/src/migrate/report.ts +624 -0
  118. package/src/migrate/types.ts +169 -0
  119. package/src/screenshot.ts +249 -0
  120. package/src/service/__tests__/ast-utils.test.ts +426 -0
  121. package/src/service/__tests__/enhance-scanner.test.ts +200 -0
  122. package/src/service/__tests__/figma/figma.test.ts +652 -0
  123. package/src/service/__tests__/metrics-store.test.ts +409 -0
  124. package/src/service/__tests__/patch-generator.test.ts +186 -0
  125. package/src/service/__tests__/props-extractor.test.ts +365 -0
  126. package/src/service/__tests__/token-registry.test.ts +267 -0
  127. package/src/service/analytics.ts +659 -0
  128. package/src/service/ast-utils.ts +444 -0
  129. package/src/service/browser-pool.ts +339 -0
  130. package/src/service/capture.ts +267 -0
  131. package/src/service/diff.ts +279 -0
  132. package/src/service/enhance/aggregator.ts +489 -0
  133. package/src/service/enhance/cache.ts +275 -0
  134. package/src/service/enhance/codebase-scanner.ts +357 -0
  135. package/src/service/enhance/context-generator.ts +529 -0
  136. package/src/service/enhance/doc-extractor.ts +523 -0
  137. package/src/service/enhance/index.ts +131 -0
  138. package/src/service/enhance/props-extractor.ts +665 -0
  139. package/src/service/enhance/scanner.ts +445 -0
  140. package/src/service/enhance/storybook-parser.ts +552 -0
  141. package/src/service/enhance/types.ts +346 -0
  142. package/src/service/enhance/variant-renderer.ts +479 -0
  143. package/src/service/figma.ts +1008 -0
  144. package/src/service/index.ts +249 -0
  145. package/src/service/metrics-store.ts +333 -0
  146. package/src/service/patch-generator.ts +349 -0
  147. package/src/service/report.ts +854 -0
  148. package/src/service/storage.ts +401 -0
  149. package/src/service/token-fixes.ts +281 -0
  150. package/src/service/token-parser.ts +504 -0
  151. package/src/service/token-registry.ts +721 -0
  152. package/src/service/utils.ts +172 -0
  153. package/src/setup.ts +241 -0
  154. package/src/shared/command-wrapper.ts +81 -0
  155. package/src/shared/dev-server-client.ts +199 -0
  156. package/src/shared/index.ts +8 -0
  157. package/src/shared/segment-loader.ts +59 -0
  158. package/src/shared/types.ts +147 -0
  159. package/src/static-viewer.ts +715 -0
  160. package/src/test/discovery.ts +172 -0
  161. package/src/test/index.ts +281 -0
  162. package/src/test/reporters/console.ts +194 -0
  163. package/src/test/reporters/json.ts +190 -0
  164. package/src/test/reporters/junit.ts +186 -0
  165. package/src/test/runner.ts +598 -0
  166. package/src/test/types.ts +245 -0
  167. package/src/test/watch.ts +200 -0
  168. package/src/validators.ts +152 -0
  169. package/src/viewer/__tests__/jsx-parser.test.ts +502 -0
  170. package/src/viewer/__tests__/render-utils.test.ts +232 -0
  171. package/src/viewer/__tests__/style-utils.test.ts +404 -0
  172. package/src/viewer/bin.ts +86 -0
  173. package/src/viewer/cli/health.ts +256 -0
  174. package/src/viewer/cli/index.ts +33 -0
  175. package/src/viewer/cli/scan.ts +124 -0
  176. package/src/viewer/cli/utils.ts +174 -0
  177. package/src/viewer/components/AccessibilityPanel.tsx +1404 -0
  178. package/src/viewer/components/ActionCapture.tsx +172 -0
  179. package/src/viewer/components/ActionsPanel.tsx +371 -0
  180. package/src/viewer/components/App.tsx +638 -0
  181. package/src/viewer/components/BottomPanel.tsx +224 -0
  182. package/src/viewer/components/CodePanel.tsx +589 -0
  183. package/src/viewer/components/CommandPalette.tsx +336 -0
  184. package/src/viewer/components/ComponentGraph.tsx +394 -0
  185. package/src/viewer/components/ComponentHeader.tsx +85 -0
  186. package/src/viewer/components/ContractPanel.tsx +234 -0
  187. package/src/viewer/components/ErrorBoundary.tsx +85 -0
  188. package/src/viewer/components/FigmaEmbed.tsx +231 -0
  189. package/src/viewer/components/FragmentEditor.tsx +485 -0
  190. package/src/viewer/components/HealthDashboard.tsx +452 -0
  191. package/src/viewer/components/HmrStatusIndicator.tsx +71 -0
  192. package/src/viewer/components/Icons.tsx +417 -0
  193. package/src/viewer/components/InteractionsPanel.tsx +720 -0
  194. package/src/viewer/components/IsolatedPreviewFrame.tsx +321 -0
  195. package/src/viewer/components/IsolatedRender.tsx +111 -0
  196. package/src/viewer/components/KeyboardShortcutsHelp.tsx +89 -0
  197. package/src/viewer/components/LandingPage.tsx +441 -0
  198. package/src/viewer/components/Layout.tsx +22 -0
  199. package/src/viewer/components/LeftSidebar.tsx +391 -0
  200. package/src/viewer/components/MultiViewportPreview.tsx +429 -0
  201. package/src/viewer/components/PreviewArea.tsx +404 -0
  202. package/src/viewer/components/PreviewFrameHost.tsx +310 -0
  203. package/src/viewer/components/PreviewPane.tsx +150 -0
  204. package/src/viewer/components/PreviewToolbar.tsx +176 -0
  205. package/src/viewer/components/PropsEditor.tsx +512 -0
  206. package/src/viewer/components/PropsTable.tsx +98 -0
  207. package/src/viewer/components/RelationsSection.tsx +57 -0
  208. package/src/viewer/components/ResizablePanel.tsx +328 -0
  209. package/src/viewer/components/RightSidebar.tsx +118 -0
  210. package/src/viewer/components/ScreenshotButton.tsx +90 -0
  211. package/src/viewer/components/Sidebar.tsx +169 -0
  212. package/src/viewer/components/SkeletonLoader.tsx +156 -0
  213. package/src/viewer/components/StoryRenderer.tsx +128 -0
  214. package/src/viewer/components/ThemeProvider.tsx +96 -0
  215. package/src/viewer/components/Toast.tsx +67 -0
  216. package/src/viewer/components/TokenStylePanel.tsx +708 -0
  217. package/src/viewer/components/UsageSection.tsx +95 -0
  218. package/src/viewer/components/VariantMatrix.tsx +350 -0
  219. package/src/viewer/components/VariantRenderer.tsx +131 -0
  220. package/src/viewer/components/VariantTabs.tsx +84 -0
  221. package/src/viewer/components/ViewportSelector.tsx +165 -0
  222. package/src/viewer/components/_future/CreatePage.tsx +836 -0
  223. package/src/viewer/composition-renderer.ts +381 -0
  224. package/src/viewer/constants/index.ts +1 -0
  225. package/src/viewer/constants/ui.ts +185 -0
  226. package/src/viewer/entry.tsx +299 -0
  227. package/src/viewer/hooks/index.ts +2 -0
  228. package/src/viewer/hooks/useA11yCache.ts +383 -0
  229. package/src/viewer/hooks/useA11yService.ts +498 -0
  230. package/src/viewer/hooks/useActions.ts +138 -0
  231. package/src/viewer/hooks/useAppState.ts +124 -0
  232. package/src/viewer/hooks/useFigmaIntegration.ts +132 -0
  233. package/src/viewer/hooks/useHmrStatus.ts +109 -0
  234. package/src/viewer/hooks/useKeyboardShortcuts.ts +222 -0
  235. package/src/viewer/hooks/usePreviewBridge.ts +347 -0
  236. package/src/viewer/hooks/useScrollSpy.ts +78 -0
  237. package/src/viewer/hooks/useUrlState.ts +330 -0
  238. package/src/viewer/hooks/useViewSettings.ts +125 -0
  239. package/src/viewer/index.html +28 -0
  240. package/src/viewer/index.ts +14 -0
  241. package/src/viewer/intelligence/healthReport.ts +505 -0
  242. package/src/viewer/intelligence/styleDrift.ts +340 -0
  243. package/src/viewer/intelligence/usageScanner.ts +309 -0
  244. package/src/viewer/jsx-parser.ts +485 -0
  245. package/src/viewer/postcss.config.js +6 -0
  246. package/src/viewer/preview-frame-entry.tsx +25 -0
  247. package/src/viewer/preview-frame.html +109 -0
  248. package/src/viewer/render-template.html +68 -0
  249. package/src/viewer/render-utils.ts +170 -0
  250. package/src/viewer/server.ts +276 -0
  251. package/src/viewer/style-utils.ts +414 -0
  252. package/src/viewer/styles/globals.css +355 -0
  253. package/src/viewer/tailwind.config.js +37 -0
  254. package/src/viewer/types/a11y.ts +197 -0
  255. package/src/viewer/utils/a11y-fixes.ts +471 -0
  256. package/src/viewer/utils/actionExport.ts +372 -0
  257. package/src/viewer/utils/colorSchemes.ts +201 -0
  258. package/src/viewer/utils/detectRelationships.ts +256 -0
  259. package/src/viewer/vite-plugin.ts +2143 -0
@@ -0,0 +1,659 @@
1
+ /**
2
+ * Analytics engine for design system insights.
3
+ *
4
+ * Analyzes compiled segments to provide:
5
+ * - Component inventory stats
6
+ * - Documentation coverage
7
+ * - Usage pattern insights
8
+ * - Quality scores
9
+ */
10
+
11
+ import type { CompiledSegmentsFile, CompiledSegment } from "../core/index.js";
12
+
13
+ /**
14
+ * Overall design system analytics
15
+ */
16
+ export interface DesignSystemAnalytics {
17
+ /** When the analysis was performed */
18
+ analyzedAt: Date;
19
+
20
+ /** Summary metrics */
21
+ summary: {
22
+ totalComponents: number;
23
+ totalVariants: number;
24
+ totalProps: number;
25
+ categories: string[];
26
+ overallScore: number; // 0-100
27
+ };
28
+
29
+ /** Component inventory */
30
+ inventory: ComponentInventory;
31
+
32
+ /** Documentation coverage */
33
+ coverage: CoverageAnalytics;
34
+
35
+ /** Quality insights */
36
+ quality: QualityAnalytics;
37
+
38
+ /** Distribution charts data */
39
+ distribution: DistributionAnalytics;
40
+
41
+ /** Recommendations for improvement */
42
+ recommendations: Recommendation[];
43
+ }
44
+
45
+ /**
46
+ * Component inventory breakdown
47
+ */
48
+ export interface ComponentInventory {
49
+ /** Components by category */
50
+ byCategory: Record<string, ComponentSummary[]>;
51
+
52
+ /** Components by status */
53
+ byStatus: Record<string, ComponentSummary[]>;
54
+
55
+ /** Components sorted by variant count (most to least) */
56
+ byVariantCount: ComponentSummary[];
57
+
58
+ /** Components sorted by prop count */
59
+ byPropCount: ComponentSummary[];
60
+ }
61
+
62
+ /**
63
+ * Summary info for a single component
64
+ */
65
+ export interface ComponentSummary {
66
+ name: string;
67
+ category: string;
68
+ status: string;
69
+ variantCount: number;
70
+ propCount: number;
71
+ hasUsageWhen: boolean;
72
+ hasUsageWhenNot: boolean;
73
+ hasGuidelines: boolean;
74
+ hasRelations: boolean;
75
+ documentationScore: number; // 0-100
76
+ }
77
+
78
+ /**
79
+ * Documentation coverage analytics
80
+ */
81
+ export interface CoverageAnalytics {
82
+ /** Overall coverage percentage */
83
+ overall: number;
84
+
85
+ /** Coverage by field */
86
+ fields: {
87
+ description: { covered: number; total: number; percentage: number };
88
+ usageWhen: { covered: number; total: number; percentage: number };
89
+ usageWhenNot: { covered: number; total: number; percentage: number };
90
+ guidelines: { covered: number; total: number; percentage: number };
91
+ accessibility: { covered: number; total: number; percentage: number };
92
+ relations: { covered: number; total: number; percentage: number };
93
+ propDescriptions: { covered: number; total: number; percentage: number };
94
+ propConstraints: { covered: number; total: number; percentage: number };
95
+ };
96
+
97
+ /** Components with incomplete documentation */
98
+ incomplete: Array<{
99
+ component: string;
100
+ missing: string[];
101
+ }>;
102
+ }
103
+
104
+ /**
105
+ * Quality insights
106
+ */
107
+ export interface QualityAnalytics {
108
+ /** Components with no whenNot guidance (anti-patterns) */
109
+ missingWhenNot: string[];
110
+
111
+ /** Components with no relations defined */
112
+ isolated: string[];
113
+
114
+ /** Deprecated components still in use */
115
+ deprecated: string[];
116
+
117
+ /** Components with very few variants (might need more examples) */
118
+ fewVariants: string[];
119
+
120
+ /** Props without descriptions */
121
+ undocumentedProps: Array<{ component: string; prop: string }>;
122
+
123
+ /** Props without constraints (might need validation rules) */
124
+ unconstrainedProps: Array<{ component: string; prop: string }>;
125
+ }
126
+
127
+ /**
128
+ * Distribution data for charts
129
+ */
130
+ export interface DistributionAnalytics {
131
+ /** Variants per component distribution */
132
+ variantsPerComponent: Array<{ name: string; count: number }>;
133
+
134
+ /** Props per component distribution */
135
+ propsPerComponent: Array<{ name: string; count: number }>;
136
+
137
+ /** Components per category */
138
+ componentsPerCategory: Array<{ category: string; count: number }>;
139
+
140
+ /** Status distribution */
141
+ statusDistribution: Array<{ status: string; count: number }>;
142
+
143
+ /** Tag frequency */
144
+ tagFrequency: Array<{ tag: string; count: number }>;
145
+ }
146
+
147
+ /**
148
+ * Actionable recommendation
149
+ */
150
+ export interface Recommendation {
151
+ priority: "high" | "medium" | "low";
152
+ category: "documentation" | "coverage" | "quality" | "organization";
153
+ title: string;
154
+ description: string;
155
+ components?: string[];
156
+ impact: string;
157
+ }
158
+
159
+ /**
160
+ * Analyze a compiled segments file and produce analytics
161
+ */
162
+ export function analyzeDesignSystem(
163
+ data: CompiledSegmentsFile
164
+ ): DesignSystemAnalytics {
165
+ const segments = Object.values(data.segments);
166
+ const analyzedAt = new Date();
167
+
168
+ // Build component summaries
169
+ const summaries = segments.map((s) => buildComponentSummary(s));
170
+
171
+ // Calculate coverage
172
+ const coverage = calculateCoverage(segments, summaries);
173
+
174
+ // Calculate quality insights
175
+ const quality = calculateQuality(segments, summaries);
176
+
177
+ // Build distribution data
178
+ const distribution = buildDistribution(segments, summaries);
179
+
180
+ // Build inventory
181
+ const inventory = buildInventory(summaries);
182
+
183
+ // Generate recommendations
184
+ const recommendations = generateRecommendations(coverage, quality, summaries);
185
+
186
+ // Calculate overall score
187
+ const overallScore = calculateOverallScore(coverage, quality, summaries);
188
+
189
+ // Collect all categories
190
+ const categories = [...new Set(segments.map((s) => s.meta.category))].sort();
191
+
192
+ return {
193
+ analyzedAt,
194
+ summary: {
195
+ totalComponents: segments.length,
196
+ totalVariants: segments.reduce((sum, s) => sum + s.variants.length, 0),
197
+ totalProps: segments.reduce(
198
+ (sum, s) => sum + Object.keys(s.props ?? {}).length,
199
+ 0
200
+ ),
201
+ categories,
202
+ overallScore,
203
+ },
204
+ inventory,
205
+ coverage,
206
+ quality,
207
+ distribution,
208
+ recommendations,
209
+ };
210
+ }
211
+
212
+ /**
213
+ * Build summary for a single component
214
+ */
215
+ function buildComponentSummary(segment: CompiledSegment): ComponentSummary {
216
+ const propCount = Object.keys(segment.props ?? {}).length;
217
+ const hasUsageWhen = (segment.usage?.when?.length ?? 0) > 0;
218
+ const hasUsageWhenNot = (segment.usage?.whenNot?.length ?? 0) > 0;
219
+ const hasGuidelines = (segment.usage?.guidelines?.length ?? 0) > 0;
220
+ const hasRelations = (segment.relations?.length ?? 0) > 0;
221
+
222
+ // Calculate documentation score
223
+ let docScore = 0;
224
+ let docTotal = 0;
225
+
226
+ // Description (20 points)
227
+ docTotal += 20;
228
+ if (segment.meta.description && segment.meta.description.length > 20) {
229
+ docScore += 20;
230
+ }
231
+
232
+ // Usage when (20 points)
233
+ docTotal += 20;
234
+ if (hasUsageWhen) docScore += 20;
235
+
236
+ // Usage whenNot (20 points) - key differentiator
237
+ docTotal += 20;
238
+ if (hasUsageWhenNot) docScore += 20;
239
+
240
+ // Guidelines (15 points)
241
+ docTotal += 15;
242
+ if (hasGuidelines) docScore += 15;
243
+
244
+ // Relations (10 points)
245
+ docTotal += 10;
246
+ if (hasRelations) docScore += 10;
247
+
248
+ // Props documented (15 points)
249
+ docTotal += 15;
250
+ if (propCount > 0) {
251
+ const documentedProps = Object.values(segment.props ?? {}).filter(
252
+ (p) => p.description && p.description.length > 5
253
+ ).length;
254
+ docScore += Math.round((documentedProps / propCount) * 15);
255
+ } else {
256
+ docScore += 15; // No props = full score for this section
257
+ }
258
+
259
+ return {
260
+ name: segment.meta.name,
261
+ category: segment.meta.category,
262
+ status: segment.meta.status ?? "stable",
263
+ variantCount: segment.variants.length,
264
+ propCount,
265
+ hasUsageWhen,
266
+ hasUsageWhenNot,
267
+ hasGuidelines,
268
+ hasRelations,
269
+ documentationScore: Math.round((docScore / docTotal) * 100),
270
+ };
271
+ }
272
+
273
+ /**
274
+ * Calculate coverage analytics
275
+ */
276
+ function calculateCoverage(
277
+ segments: CompiledSegment[],
278
+ summaries: ComponentSummary[]
279
+ ): CoverageAnalytics {
280
+ const total = segments.length;
281
+
282
+ const fields = {
283
+ description: {
284
+ covered: segments.filter(
285
+ (s) => s.meta.description && s.meta.description.length > 10
286
+ ).length,
287
+ total,
288
+ percentage: 0,
289
+ },
290
+ usageWhen: {
291
+ covered: summaries.filter((s) => s.hasUsageWhen).length,
292
+ total,
293
+ percentage: 0,
294
+ },
295
+ usageWhenNot: {
296
+ covered: summaries.filter((s) => s.hasUsageWhenNot).length,
297
+ total,
298
+ percentage: 0,
299
+ },
300
+ guidelines: {
301
+ covered: summaries.filter((s) => s.hasGuidelines).length,
302
+ total,
303
+ percentage: 0,
304
+ },
305
+ accessibility: {
306
+ covered: segments.filter((s) => (s.usage?.accessibility?.length ?? 0) > 0)
307
+ .length,
308
+ total,
309
+ percentage: 0,
310
+ },
311
+ relations: {
312
+ covered: summaries.filter((s) => s.hasRelations).length,
313
+ total,
314
+ percentage: 0,
315
+ },
316
+ propDescriptions: {
317
+ covered: 0,
318
+ total: 0,
319
+ percentage: 0,
320
+ },
321
+ propConstraints: {
322
+ covered: 0,
323
+ total: 0,
324
+ percentage: 0,
325
+ },
326
+ };
327
+
328
+ // Calculate prop coverage
329
+ for (const segment of segments) {
330
+ const props = Object.values(segment.props ?? {});
331
+ fields.propDescriptions.total += props.length;
332
+ fields.propConstraints.total += props.length;
333
+
334
+ fields.propDescriptions.covered += props.filter(
335
+ (p) => p.description && p.description.length > 5
336
+ ).length;
337
+ fields.propConstraints.covered += props.filter(
338
+ (p) => (p.constraints?.length ?? 0) > 0
339
+ ).length;
340
+ }
341
+
342
+ // Calculate percentages
343
+ for (const field of Object.values(fields)) {
344
+ field.percentage =
345
+ field.total > 0 ? Math.round((field.covered / field.total) * 100) : 100;
346
+ }
347
+
348
+ // Find incomplete components
349
+ const incomplete: CoverageAnalytics["incomplete"] = [];
350
+ for (const summary of summaries) {
351
+ const missing: string[] = [];
352
+ if (!summary.hasUsageWhen) missing.push("usage.when");
353
+ if (!summary.hasUsageWhenNot) missing.push("usage.whenNot");
354
+ if (!summary.hasGuidelines) missing.push("usage.guidelines");
355
+ if (!summary.hasRelations) missing.push("relations");
356
+
357
+ if (missing.length > 0) {
358
+ incomplete.push({ component: summary.name, missing });
359
+ }
360
+ }
361
+
362
+ // Overall coverage (weighted average)
363
+ const overall = Math.round(
364
+ fields.description.percentage * 0.1 +
365
+ fields.usageWhen.percentage * 0.2 +
366
+ fields.usageWhenNot.percentage * 0.25 +
367
+ fields.guidelines.percentage * 0.15 +
368
+ fields.relations.percentage * 0.1 +
369
+ fields.propDescriptions.percentage * 0.15 +
370
+ fields.propConstraints.percentage * 0.05
371
+ );
372
+
373
+ return { overall, fields, incomplete };
374
+ }
375
+
376
+ /**
377
+ * Calculate quality analytics
378
+ */
379
+ function calculateQuality(
380
+ segments: CompiledSegment[],
381
+ summaries: ComponentSummary[]
382
+ ): QualityAnalytics {
383
+ const missingWhenNot = summaries
384
+ .filter((s) => !s.hasUsageWhenNot)
385
+ .map((s) => s.name);
386
+
387
+ const isolated = summaries
388
+ .filter((s) => !s.hasRelations)
389
+ .map((s) => s.name);
390
+
391
+ const deprecated = summaries
392
+ .filter((s) => s.status === "deprecated")
393
+ .map((s) => s.name);
394
+
395
+ const fewVariants = summaries
396
+ .filter((s) => s.variantCount < 2)
397
+ .map((s) => s.name);
398
+
399
+ const undocumentedProps: QualityAnalytics["undocumentedProps"] = [];
400
+ const unconstrainedProps: QualityAnalytics["unconstrainedProps"] = [];
401
+
402
+ for (const segment of segments) {
403
+ for (const [propName, prop] of Object.entries(segment.props ?? {})) {
404
+ if (!prop.description || prop.description.length < 5) {
405
+ undocumentedProps.push({ component: segment.meta.name, prop: propName });
406
+ }
407
+ if (
408
+ (prop.constraints?.length ?? 0) === 0 &&
409
+ prop.type !== "boolean" &&
410
+ prop.type !== "function"
411
+ ) {
412
+ unconstrainedProps.push({
413
+ component: segment.meta.name,
414
+ prop: propName,
415
+ });
416
+ }
417
+ }
418
+ }
419
+
420
+ return {
421
+ missingWhenNot,
422
+ isolated,
423
+ deprecated,
424
+ fewVariants,
425
+ undocumentedProps,
426
+ unconstrainedProps,
427
+ };
428
+ }
429
+
430
+ /**
431
+ * Build distribution data for charts
432
+ */
433
+ function buildDistribution(
434
+ segments: CompiledSegment[],
435
+ summaries: ComponentSummary[]
436
+ ): DistributionAnalytics {
437
+ // Variants per component
438
+ const variantsPerComponent = summaries
439
+ .map((s) => ({ name: s.name, count: s.variantCount }))
440
+ .sort((a, b) => b.count - a.count);
441
+
442
+ // Props per component
443
+ const propsPerComponent = summaries
444
+ .map((s) => ({ name: s.name, count: s.propCount }))
445
+ .sort((a, b) => b.count - a.count);
446
+
447
+ // Components per category
448
+ const categoryMap = new Map<string, number>();
449
+ for (const s of summaries) {
450
+ categoryMap.set(s.category, (categoryMap.get(s.category) ?? 0) + 1);
451
+ }
452
+ const componentsPerCategory = Array.from(categoryMap.entries())
453
+ .map(([category, count]) => ({ category, count }))
454
+ .sort((a, b) => b.count - a.count);
455
+
456
+ // Status distribution
457
+ const statusMap = new Map<string, number>();
458
+ for (const s of summaries) {
459
+ statusMap.set(s.status, (statusMap.get(s.status) ?? 0) + 1);
460
+ }
461
+ const statusDistribution = Array.from(statusMap.entries())
462
+ .map(([status, count]) => ({ status, count }))
463
+ .sort((a, b) => b.count - a.count);
464
+
465
+ // Tag frequency
466
+ const tagMap = new Map<string, number>();
467
+ for (const segment of segments) {
468
+ for (const tag of segment.meta.tags ?? []) {
469
+ tagMap.set(tag, (tagMap.get(tag) ?? 0) + 1);
470
+ }
471
+ }
472
+ const tagFrequency = Array.from(tagMap.entries())
473
+ .map(([tag, count]) => ({ tag, count }))
474
+ .sort((a, b) => b.count - a.count);
475
+
476
+ return {
477
+ variantsPerComponent,
478
+ propsPerComponent,
479
+ componentsPerCategory,
480
+ statusDistribution,
481
+ tagFrequency,
482
+ };
483
+ }
484
+
485
+ /**
486
+ * Build inventory breakdown
487
+ */
488
+ function buildInventory(summaries: ComponentSummary[]): ComponentInventory {
489
+ const byCategory: Record<string, ComponentSummary[]> = {};
490
+ const byStatus: Record<string, ComponentSummary[]> = {};
491
+
492
+ for (const summary of summaries) {
493
+ if (!byCategory[summary.category]) {
494
+ byCategory[summary.category] = [];
495
+ }
496
+ byCategory[summary.category].push(summary);
497
+
498
+ if (!byStatus[summary.status]) {
499
+ byStatus[summary.status] = [];
500
+ }
501
+ byStatus[summary.status].push(summary);
502
+ }
503
+
504
+ // Sort within each group by name
505
+ for (const list of Object.values(byCategory)) {
506
+ list.sort((a, b) => a.name.localeCompare(b.name));
507
+ }
508
+ for (const list of Object.values(byStatus)) {
509
+ list.sort((a, b) => a.name.localeCompare(b.name));
510
+ }
511
+
512
+ return {
513
+ byCategory,
514
+ byStatus,
515
+ byVariantCount: [...summaries].sort((a, b) => b.variantCount - a.variantCount),
516
+ byPropCount: [...summaries].sort((a, b) => b.propCount - a.propCount),
517
+ };
518
+ }
519
+
520
+ /**
521
+ * Generate actionable recommendations
522
+ */
523
+ function generateRecommendations(
524
+ coverage: CoverageAnalytics,
525
+ quality: QualityAnalytics,
526
+ summaries: ComponentSummary[]
527
+ ): Recommendation[] {
528
+ const recommendations: Recommendation[] = [];
529
+
530
+ // High priority: Missing whenNot guidance
531
+ if (quality.missingWhenNot.length > 0) {
532
+ recommendations.push({
533
+ priority: "high",
534
+ category: "documentation",
535
+ title: 'Add "When NOT to use" guidance',
536
+ description: `${quality.missingWhenNot.length} component(s) lack guidance on when NOT to use them. This is critical for AI agents to make correct component choices.`,
537
+ components: quality.missingWhenNot.slice(0, 5),
538
+ impact: "Prevents AI from using components incorrectly",
539
+ });
540
+ }
541
+
542
+ // High priority: Low overall coverage
543
+ if (coverage.overall < 70) {
544
+ recommendations.push({
545
+ priority: "high",
546
+ category: "coverage",
547
+ title: "Improve documentation coverage",
548
+ description: `Overall documentation coverage is ${coverage.overall}%. Aim for at least 80% for effective AI assistance.`,
549
+ impact: "Better AI understanding and suggestions",
550
+ });
551
+ }
552
+
553
+ // Medium priority: Isolated components
554
+ if (quality.isolated.length > summaries.length * 0.3) {
555
+ recommendations.push({
556
+ priority: "medium",
557
+ category: "organization",
558
+ title: "Add component relationships",
559
+ description: `${quality.isolated.length} component(s) have no relationships defined. Adding relations helps AI understand component ecosystems.`,
560
+ components: quality.isolated.slice(0, 5),
561
+ impact: "Better alternative suggestions",
562
+ });
563
+ }
564
+
565
+ // Medium priority: Few variants
566
+ if (quality.fewVariants.length > 0) {
567
+ recommendations.push({
568
+ priority: "medium",
569
+ category: "coverage",
570
+ title: "Add more variant examples",
571
+ description: `${quality.fewVariants.length} component(s) have fewer than 2 variants. More examples help demonstrate component flexibility.`,
572
+ components: quality.fewVariants.slice(0, 5),
573
+ impact: "Better visual documentation",
574
+ });
575
+ }
576
+
577
+ // Low priority: Undocumented props
578
+ if (quality.undocumentedProps.length > 0) {
579
+ recommendations.push({
580
+ priority: "low",
581
+ category: "documentation",
582
+ title: "Document prop descriptions",
583
+ description: `${quality.undocumentedProps.length} prop(s) lack descriptions. Clear descriptions help AI generate correct code.`,
584
+ impact: "Clearer API documentation",
585
+ });
586
+ }
587
+
588
+ // Low priority: No constraints
589
+ if (quality.unconstrainedProps.length > 5) {
590
+ recommendations.push({
591
+ priority: "low",
592
+ category: "quality",
593
+ title: "Add prop constraints",
594
+ description: `${quality.unconstrainedProps.length} prop(s) could benefit from constraint documentation (validation rules, best practices).`,
595
+ impact: "Better AI adherence to design rules",
596
+ });
597
+ }
598
+
599
+ return recommendations;
600
+ }
601
+
602
+ /**
603
+ * Calculate overall design system quality score
604
+ */
605
+ function calculateOverallScore(
606
+ coverage: CoverageAnalytics,
607
+ quality: QualityAnalytics,
608
+ summaries: ComponentSummary[]
609
+ ): number {
610
+ // Base: coverage score (50%)
611
+ let score = coverage.overall * 0.5;
612
+
613
+ // Component documentation scores (30%)
614
+ const avgDocScore =
615
+ summaries.reduce((sum, s) => sum + s.documentationScore, 0) /
616
+ Math.max(summaries.length, 1);
617
+ score += avgDocScore * 0.3;
618
+
619
+ // Quality penalties (20%)
620
+ let qualityScore = 100;
621
+
622
+ // Penalize missing whenNot (most important)
623
+ const missingWhenNotRatio =
624
+ quality.missingWhenNot.length / Math.max(summaries.length, 1);
625
+ qualityScore -= missingWhenNotRatio * 40;
626
+
627
+ // Penalize isolated components
628
+ const isolatedRatio = quality.isolated.length / Math.max(summaries.length, 1);
629
+ qualityScore -= isolatedRatio * 20;
630
+
631
+ // Penalize few variants
632
+ const fewVariantsRatio =
633
+ quality.fewVariants.length / Math.max(summaries.length, 1);
634
+ qualityScore -= fewVariantsRatio * 10;
635
+
636
+ score += Math.max(qualityScore, 0) * 0.2;
637
+
638
+ return Math.round(Math.max(Math.min(score, 100), 0));
639
+ }
640
+
641
+ /**
642
+ * Get a letter grade from a numeric score
643
+ */
644
+ export function getGrade(score: number): string {
645
+ if (score >= 90) return "A";
646
+ if (score >= 80) return "B";
647
+ if (score >= 70) return "C";
648
+ if (score >= 60) return "D";
649
+ return "F";
650
+ }
651
+
652
+ /**
653
+ * Get a color for a score (for HTML reports)
654
+ */
655
+ export function getScoreColor(score: number): string {
656
+ if (score >= 80) return "#22c55e"; // green
657
+ if (score >= 60) return "#eab308"; // yellow
658
+ return "#ef4444"; // red
659
+ }