@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,364 @@
1
+ /**
2
+ * Registry Generator
3
+ * Generates .fragments/registry.json and .fragments/index.json
4
+ *
5
+ * Philosophy: Generate only what AI agents and humans can't easily get from source.
6
+ * - Props: AI can read TypeScript directly (skip by default)
7
+ * - Paths: Essential for file navigation (always include)
8
+ * - Enrichment: Human knowledge that doesn't exist elsewhere (include references)
9
+ */
10
+
11
+ import { readFileSync, existsSync } from "node:fs";
12
+ import { relative, dirname, basename, join } from "node:path";
13
+ import fg from "fast-glob";
14
+ import type {
15
+ Fragment,
16
+ FragmentRegistry,
17
+ FragmentIndex,
18
+ RegistryComponentEntry,
19
+ } from "../fragment-types.js";
20
+ import { extractPropsFromFile } from "./typescript-extractor.js";
21
+ import { BRAND } from "../constants.js";
22
+ import type { RegistryOptions } from "../types.js";
23
+
24
+ /**
25
+ * Options for registry generation
26
+ */
27
+ export interface RegistryGeneratorOptions {
28
+ /** Project root directory */
29
+ projectRoot: string;
30
+ /** Glob patterns for component files */
31
+ componentPatterns?: string[];
32
+ /** Glob patterns for story files */
33
+ storyPatterns?: string[];
34
+ /** Path to .fragments directory */
35
+ fragmentsDir?: string;
36
+ /** Registry options from config */
37
+ registryOptions?: RegistryOptions;
38
+ }
39
+
40
+ /**
41
+ * Result of registry generation
42
+ */
43
+ export interface RegistryGeneratorResult {
44
+ /** Generated registry (full metadata) */
45
+ registry: FragmentRegistry;
46
+ /** Generated index (minimal name -> path) */
47
+ index: FragmentIndex;
48
+ /** Errors encountered during generation */
49
+ errors: Array<{ file: string; error: string }>;
50
+ /** Warnings */
51
+ warnings: Array<{ file: string; warning: string }>;
52
+ }
53
+
54
+ /**
55
+ * Generate a fragment registry and index by scanning the project
56
+ */
57
+ export async function generateRegistry(
58
+ options: RegistryGeneratorOptions
59
+ ): Promise<RegistryGeneratorResult> {
60
+ const {
61
+ projectRoot,
62
+ componentPatterns = ["src/**/*.tsx", "src/**/*.ts"],
63
+ storyPatterns = ["src/**/*.stories.tsx", "src/**/*.stories.ts"],
64
+ fragmentsDir = join(projectRoot, BRAND.dataDir),
65
+ registryOptions = {},
66
+ } = options;
67
+
68
+ const {
69
+ requireStory = false,
70
+ publicOnly = false,
71
+ categoryDepth = 1,
72
+ includeProps = false,
73
+ embedFragments = false,
74
+ } = registryOptions;
75
+
76
+ const errors: Array<{ file: string; error: string }> = [];
77
+ const warnings: Array<{ file: string; warning: string }> = [];
78
+
79
+ // Find all story files first (for requireStory filtering)
80
+ const storyFiles = await fg(storyPatterns, {
81
+ cwd: projectRoot,
82
+ ignore: ["**/node_modules/**"],
83
+ absolute: true,
84
+ });
85
+
86
+ // Build a map of story files by component directory and base name
87
+ const storyMap = new Map<string, string>();
88
+ for (const storyPath of storyFiles) {
89
+ const storyDir = dirname(storyPath);
90
+ const storyBase = basename(storyPath).replace(/\.stories\.(tsx?|jsx?)$/, "");
91
+ storyMap.set(`${storyDir}/${storyBase}`, storyPath);
92
+ }
93
+
94
+ // Find all component files
95
+ const componentFiles = await fg(componentPatterns, {
96
+ cwd: projectRoot,
97
+ ignore: [
98
+ "**/node_modules/**",
99
+ "**/*.stories.*",
100
+ "**/*.test.*",
101
+ "**/*.spec.*",
102
+ "**/*.d.ts",
103
+ ],
104
+ absolute: true,
105
+ });
106
+
107
+ // Find all fragment files
108
+ const fragmentPattern = join(
109
+ BRAND.dataDir,
110
+ BRAND.componentsDir,
111
+ `*${BRAND.fileExtension}`
112
+ );
113
+ const fragmentFiles = await fg(fragmentPattern, {
114
+ cwd: projectRoot,
115
+ absolute: true,
116
+ });
117
+
118
+ // Build fragment lookup map
119
+ const fragmentMap = new Map<string, { path: string; fragment: Fragment }>();
120
+ for (const fragmentPath of fragmentFiles) {
121
+ const fragmentName = basename(fragmentPath).replace(BRAND.fileExtension, "");
122
+ try {
123
+ const content = readFileSync(fragmentPath, "utf-8");
124
+ const fragment = JSON.parse(content) as Fragment;
125
+ fragmentMap.set(fragmentName, {
126
+ path: relative(projectRoot, fragmentPath),
127
+ fragment,
128
+ });
129
+ } catch (e) {
130
+ errors.push({
131
+ file: fragmentPath,
132
+ error: `Failed to parse fragment: ${e instanceof Error ? e.message : String(e)}`,
133
+ });
134
+ }
135
+ }
136
+
137
+ // Build component index
138
+ const components: Record<string, RegistryComponentEntry> = {};
139
+ const indexComponents: Record<string, string> = {};
140
+ const categories: Record<string, string[]> = {};
141
+
142
+ // Process component files
143
+ for (const filePath of componentFiles) {
144
+ try {
145
+ const extracted = extractPropsFromFile(filePath);
146
+ if (!extracted || !extracted.componentName) {
147
+ continue;
148
+ }
149
+
150
+ const componentName = extracted.componentName;
151
+ const relativePath = relative(projectRoot, filePath);
152
+
153
+ // Check if component is exported (for publicOnly filter)
154
+ if (publicOnly && !extracted.exports.includes(componentName)) {
155
+ continue;
156
+ }
157
+
158
+ // Find matching story file
159
+ const componentDir = dirname(filePath);
160
+ const baseNameWithoutExt = basename(filePath).replace(/\.(tsx?|jsx?)$/, "");
161
+ const storyPath = storyMap.get(`${componentDir}/${baseNameWithoutExt}`);
162
+
163
+ // Apply requireStory filter
164
+ if (requireStory && !storyPath) {
165
+ continue;
166
+ }
167
+
168
+ // Get fragment data if exists
169
+ const fragmentData = fragmentMap.get(componentName);
170
+
171
+ // Determine category from directory structure
172
+ const category = fragmentData?.fragment?.meta?.status
173
+ ? undefined // Don't use status as category
174
+ : getCategoryFromPath(relativePath, categoryDepth);
175
+
176
+ // Check if fragment has meaningful enrichment (not just a skeleton)
177
+ const hasEnrichment = fragmentData
178
+ ? hasRealEnrichment(fragmentData.fragment)
179
+ : false;
180
+
181
+ // Build minimal component entry
182
+ const entry: RegistryComponentEntry = {
183
+ path: relativePath,
184
+ };
185
+
186
+ // Add optional fields only if present
187
+ if (storyPath) {
188
+ entry.storyPath = relative(projectRoot, storyPath);
189
+ }
190
+
191
+ if (fragmentData) {
192
+ entry.fragmentPath = fragmentData.path;
193
+ if (hasEnrichment) {
194
+ entry.hasEnrichment = true;
195
+ }
196
+ if (fragmentData.fragment.description) {
197
+ entry.description = fragmentData.fragment.description;
198
+ }
199
+ if (fragmentData.fragment.meta?.status) {
200
+ entry.status = fragmentData.fragment.meta.status;
201
+ }
202
+ // Only embed full fragment if explicitly requested
203
+ if (embedFragments) {
204
+ entry.fragment = fragmentData.fragment;
205
+ }
206
+ }
207
+
208
+ if (category) {
209
+ entry.category = category;
210
+ // Add to categories index
211
+ if (!categories[category]) {
212
+ categories[category] = [];
213
+ }
214
+ categories[category].push(componentName);
215
+ }
216
+
217
+ // Only include props if explicitly requested
218
+ if (includeProps && extracted.props) {
219
+ entry.props = extracted.props;
220
+ }
221
+
222
+ // Only include exports if multiple exports
223
+ if (extracted.exports.length > 1) {
224
+ entry.exports = extracted.exports;
225
+ }
226
+
227
+ components[componentName] = entry;
228
+ indexComponents[componentName] = relativePath;
229
+ } catch (e) {
230
+ errors.push({
231
+ file: filePath,
232
+ error: `Failed to extract component: ${e instanceof Error ? e.message : String(e)}`,
233
+ });
234
+ }
235
+ }
236
+
237
+ const componentCount = Object.keys(components).length;
238
+
239
+ // Build registry
240
+ const registry: FragmentRegistry = {
241
+ $schema: "https://fragments.dev/schema/registry-v1.json",
242
+ version: "1.0",
243
+ generatedAt: new Date().toISOString(),
244
+ componentCount,
245
+ components,
246
+ categories,
247
+ };
248
+
249
+ // Build minimal index
250
+ const index: FragmentIndex = {
251
+ version: "1.0",
252
+ generatedAt: registry.generatedAt,
253
+ components: indexComponents,
254
+ categories,
255
+ };
256
+
257
+ return { registry, index, errors, warnings };
258
+ }
259
+
260
+ /**
261
+ * Get category from file path based on directory structure
262
+ *
263
+ * Examples:
264
+ * - src/components/buttons/Button.tsx -> "buttons"
265
+ * - src/components/forms/inputs/TextInput.tsx -> "forms" (depth 1) or "forms/inputs" (depth 2)
266
+ * - src/components/Button/Button.tsx -> "Button" (component folder)
267
+ */
268
+ function getCategoryFromPath(relativePath: string, depth: number = 1): string | undefined {
269
+ const parts = relativePath.split("/");
270
+ const componentsIndex = parts.findIndex((p) => p === "components");
271
+
272
+ if (componentsIndex === -1) {
273
+ return undefined;
274
+ }
275
+
276
+ // Get the parts after "components/"
277
+ const afterComponents = parts.slice(componentsIndex + 1);
278
+
279
+ // If it's just components/Button.tsx, no category
280
+ if (afterComponents.length <= 1) {
281
+ return undefined;
282
+ }
283
+
284
+ // If it's components/Button/Button.tsx (component folder pattern), no category
285
+ const folderName = afterComponents[0];
286
+ const fileName = afterComponents[afterComponents.length - 1].replace(/\.(tsx?|jsx?)$/, "");
287
+ if (afterComponents.length === 2 && folderName === fileName) {
288
+ return undefined;
289
+ }
290
+
291
+ // Get category parts based on depth
292
+ const categoryParts = afterComponents.slice(0, Math.min(depth, afterComponents.length - 1));
293
+
294
+ // Filter out component folders (where folder name matches file name)
295
+ const lastCategoryPart = categoryParts[categoryParts.length - 1];
296
+ if (lastCategoryPart === fileName) {
297
+ categoryParts.pop();
298
+ }
299
+
300
+ if (categoryParts.length === 0) {
301
+ return undefined;
302
+ }
303
+
304
+ return categoryParts.join("/");
305
+ }
306
+
307
+ /**
308
+ * Check if a fragment has meaningful enrichment beyond a skeleton
309
+ */
310
+ function hasRealEnrichment(fragment: Fragment): boolean {
311
+ // Has description beyond just the name
312
+ if (fragment.description && fragment.description.length > 20) {
313
+ return true;
314
+ }
315
+
316
+ // Has usage guidelines
317
+ if (fragment.usage?.when && fragment.usage.when.length > 0) {
318
+ return true;
319
+ }
320
+ if (fragment.usage?.doNot && fragment.usage.doNot.length > 0) {
321
+ return true;
322
+ }
323
+ if (fragment.usage?.patterns && fragment.usage.patterns.length > 0) {
324
+ return true;
325
+ }
326
+
327
+ // Has accessibility info
328
+ if (fragment.accessibility?.requirements && fragment.accessibility.requirements.length > 0) {
329
+ return true;
330
+ }
331
+
332
+ // Has figma links
333
+ if (fragment.figma?.nodeId || fragment.figma?.variants) {
334
+ return true;
335
+ }
336
+
337
+ // Has related components
338
+ if (fragment.related?.similar && fragment.related.similar.length > 0) {
339
+ return true;
340
+ }
341
+
342
+ return false;
343
+ }
344
+
345
+ /**
346
+ * Resolve component name to path using registry
347
+ */
348
+ export function resolveComponentPath(
349
+ componentName: string,
350
+ registry: FragmentRegistry
351
+ ): string | undefined {
352
+ const entry = registry.components[componentName];
353
+ return entry?.path;
354
+ }
355
+
356
+ /**
357
+ * Get all components by category
358
+ */
359
+ export function getComponentsByCategory(
360
+ category: string,
361
+ registry: FragmentRegistry
362
+ ): string[] {
363
+ return registry.categories?.[category] || [];
364
+ }