@fragments-sdk/cli 0.7.0 → 0.7.1

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 (171) hide show
  1. package/dist/bin.js +245 -245
  2. package/dist/bin.js.map +1 -1
  3. package/dist/{chunk-XHUDJNN3.js → chunk-32VIEOQY.js} +18 -18
  4. package/dist/chunk-32VIEOQY.js.map +1 -0
  5. package/dist/{chunk-CVXKXVOY.js → chunk-5ITIP3ES.js} +27 -27
  6. package/dist/chunk-5ITIP3ES.js.map +1 -0
  7. package/dist/{chunk-RVRTRESS.js → chunk-DQHWLAUV.js} +29 -29
  8. package/dist/chunk-DQHWLAUV.js.map +1 -0
  9. package/dist/{chunk-TJ34N7C7.js → chunk-GCZMFLDI.js} +30 -32
  10. package/dist/chunk-GCZMFLDI.js.map +1 -0
  11. package/dist/{chunk-6JBGU74P.js → chunk-GHYYFAQN.js} +23 -23
  12. package/dist/chunk-GHYYFAQN.js.map +1 -0
  13. package/dist/{chunk-NWQ4CJOQ.js → chunk-GKX2HPZ6.js} +40 -40
  14. package/dist/chunk-GKX2HPZ6.js.map +1 -0
  15. package/dist/{chunk-7OPWMLOE.js → chunk-U6VTHBNI.js} +110 -110
  16. package/dist/chunk-U6VTHBNI.js.map +1 -0
  17. package/dist/{core-W2HYIQW6.js → core-SFHPYR5H.js} +24 -26
  18. package/dist/{generate-LMTISDIJ.js → generate-54GJAWUY.js} +5 -5
  19. package/dist/generate-54GJAWUY.js.map +1 -0
  20. package/dist/index.d.ts +23 -27
  21. package/dist/index.js +10 -10
  22. package/dist/{init-7CHRKQ7P.js → init-EIM5WNMP.js} +5 -5
  23. package/dist/{init-7CHRKQ7P.js.map → init-EIM5WNMP.js.map} +1 -1
  24. package/dist/mcp-bin.js +73 -73
  25. package/dist/mcp-bin.js.map +1 -1
  26. package/dist/scan-KQBKUS64.js +12 -0
  27. package/dist/{service-T2L7VLTE.js → service-ED2LNCTU.js} +6 -6
  28. package/dist/{static-viewer-GBR7YNF3.js → static-viewer-Q4F4QP5M.js} +4 -4
  29. package/dist/{test-OJRXNDO2.js → test-6VN2DA3S.js} +19 -19
  30. package/dist/test-6VN2DA3S.js.map +1 -0
  31. package/dist/{tokens-3BWDESVM.js → tokens-P2B7ZAM3.js} +5 -5
  32. package/dist/{viewer-SUFOISZM.js → viewer-GM7IQPPB.js} +199 -199
  33. package/dist/viewer-GM7IQPPB.js.map +1 -0
  34. package/package.json +2 -2
  35. package/src/ai.ts +5 -5
  36. package/src/analyze.ts +11 -11
  37. package/src/bin.ts +1 -1
  38. package/src/build.ts +33 -33
  39. package/src/commands/a11y.ts +6 -6
  40. package/src/commands/add.ts +11 -11
  41. package/src/commands/audit.ts +4 -4
  42. package/src/commands/baseline.ts +3 -3
  43. package/src/commands/build.ts +8 -8
  44. package/src/commands/compare.ts +20 -20
  45. package/src/commands/context.ts +16 -16
  46. package/src/commands/enhance.ts +36 -36
  47. package/src/commands/generate.ts +1 -1
  48. package/src/commands/graph.ts +3 -3
  49. package/src/commands/init.ts +1 -1
  50. package/src/commands/link/figma.ts +82 -82
  51. package/src/commands/link/index.ts +3 -3
  52. package/src/commands/link/storybook.ts +9 -9
  53. package/src/commands/list.ts +2 -2
  54. package/src/commands/reset.ts +15 -15
  55. package/src/commands/scan.ts +27 -27
  56. package/src/commands/storygen.ts +24 -24
  57. package/src/commands/validate.ts +2 -2
  58. package/src/commands/verify.ts +8 -8
  59. package/src/core/auto-props.ts +4 -4
  60. package/src/core/composition.test.ts +36 -36
  61. package/src/core/composition.ts +19 -19
  62. package/src/core/config.ts +6 -6
  63. package/src/core/{defineSegment.ts → defineFragment.ts} +16 -22
  64. package/src/core/discovery.ts +6 -6
  65. package/src/core/figma.ts +2 -2
  66. package/src/core/graph-extractor.test.ts +77 -77
  67. package/src/core/graph-extractor.ts +32 -32
  68. package/src/core/importAnalyzer.ts +1 -1
  69. package/src/core/index.ts +22 -23
  70. package/src/core/loader.ts +22 -22
  71. package/src/core/node.ts +5 -5
  72. package/src/core/parser.ts +31 -31
  73. package/src/core/previewLoader.ts +1 -1
  74. package/src/core/schema.ts +16 -16
  75. package/src/core/storyAdapter.test.ts +87 -87
  76. package/src/core/storyAdapter.ts +16 -16
  77. package/src/core/types.ts +21 -26
  78. package/src/diff.ts +22 -22
  79. package/src/index.ts +2 -2
  80. package/src/mcp/server.ts +80 -80
  81. package/src/migrate/__tests__/utils/utils.test.ts +3 -3
  82. package/src/migrate/bin.ts +4 -4
  83. package/src/migrate/converter.ts +16 -16
  84. package/src/migrate/index.ts +3 -3
  85. package/src/migrate/migrate.ts +3 -3
  86. package/src/migrate/parser.ts +8 -8
  87. package/src/migrate/report.ts +2 -2
  88. package/src/migrate/types.ts +4 -4
  89. package/src/screenshot.ts +22 -22
  90. package/src/service/__tests__/props-extractor.test.ts +15 -15
  91. package/src/service/analytics.ts +39 -39
  92. package/src/service/enhance/codebase-scanner.ts +1 -1
  93. package/src/service/enhance/index.ts +1 -1
  94. package/src/service/enhance/props-extractor.ts +2 -2
  95. package/src/service/enhance/types.ts +2 -2
  96. package/src/service/index.ts +2 -2
  97. package/src/service/metrics-store.ts +1 -1
  98. package/src/service/patch-generator.ts +1 -1
  99. package/src/setup.ts +52 -52
  100. package/src/shared/dev-server-client.ts +7 -7
  101. package/src/shared/fragment-loader.ts +59 -0
  102. package/src/shared/index.ts +1 -1
  103. package/src/shared/types.ts +4 -4
  104. package/src/static-viewer.ts +35 -35
  105. package/src/test/discovery.ts +6 -6
  106. package/src/test/index.ts +5 -5
  107. package/src/test/reporters/console.ts +1 -1
  108. package/src/test/reporters/junit.ts +1 -1
  109. package/src/test/runner.ts +7 -7
  110. package/src/test/types.ts +3 -3
  111. package/src/test/watch.ts +9 -9
  112. package/src/validators.ts +26 -26
  113. package/src/viewer/__tests__/render-utils.test.ts +28 -28
  114. package/src/viewer/__tests__/viewer-integration.test.ts +4 -4
  115. package/src/viewer/cli/health.ts +26 -26
  116. package/src/viewer/components/App.tsx +79 -79
  117. package/src/viewer/components/BottomPanel.tsx +17 -17
  118. package/src/viewer/components/CodePanel.tsx +3 -3
  119. package/src/viewer/components/CommandPalette.tsx +11 -11
  120. package/src/viewer/components/ComponentGraph.tsx +28 -28
  121. package/src/viewer/components/ComponentHeader.tsx +2 -2
  122. package/src/viewer/components/ContractPanel.tsx +6 -6
  123. package/src/viewer/components/FigmaEmbed.tsx +9 -9
  124. package/src/viewer/components/HealthDashboard.tsx +17 -17
  125. package/src/viewer/components/InteractionsPanel.tsx +2 -2
  126. package/src/viewer/components/IsolatedPreviewFrame.tsx +6 -6
  127. package/src/viewer/components/IsolatedRender.tsx +10 -10
  128. package/src/viewer/components/LeftSidebar.tsx +28 -28
  129. package/src/viewer/components/MultiViewportPreview.tsx +14 -14
  130. package/src/viewer/components/PreviewArea.tsx +11 -11
  131. package/src/viewer/components/PreviewFrameHost.tsx +51 -51
  132. package/src/viewer/components/RightSidebar.tsx +9 -9
  133. package/src/viewer/components/Sidebar.tsx +17 -17
  134. package/src/viewer/components/StoryRenderer.tsx +2 -2
  135. package/src/viewer/components/TokenStylePanel.tsx +1 -1
  136. package/src/viewer/components/UsageSection.tsx +2 -2
  137. package/src/viewer/components/VariantMatrix.tsx +11 -11
  138. package/src/viewer/components/VariantRenderer.tsx +3 -3
  139. package/src/viewer/components/VariantTabs.tsx +2 -2
  140. package/src/viewer/components/_future/CreatePage.tsx +6 -6
  141. package/src/viewer/composition-renderer.ts +11 -11
  142. package/src/viewer/entry.tsx +40 -40
  143. package/src/viewer/hooks/useFigmaIntegration.ts +1 -1
  144. package/src/viewer/hooks/usePreviewBridge.ts +5 -5
  145. package/src/viewer/hooks/useUrlState.ts +6 -6
  146. package/src/viewer/index.ts +2 -2
  147. package/src/viewer/intelligence/healthReport.ts +17 -17
  148. package/src/viewer/intelligence/styleDrift.ts +1 -1
  149. package/src/viewer/intelligence/usageScanner.ts +1 -1
  150. package/src/viewer/render-template.html +1 -1
  151. package/src/viewer/render-utils.ts +21 -21
  152. package/src/viewer/server.ts +18 -18
  153. package/src/viewer/utils/detectRelationships.ts +22 -22
  154. package/src/viewer/vite-plugin.ts +213 -213
  155. package/dist/chunk-6JBGU74P.js.map +0 -1
  156. package/dist/chunk-7OPWMLOE.js.map +0 -1
  157. package/dist/chunk-CVXKXVOY.js.map +0 -1
  158. package/dist/chunk-NWQ4CJOQ.js.map +0 -1
  159. package/dist/chunk-RVRTRESS.js.map +0 -1
  160. package/dist/chunk-TJ34N7C7.js.map +0 -1
  161. package/dist/chunk-XHUDJNN3.js.map +0 -1
  162. package/dist/generate-LMTISDIJ.js.map +0 -1
  163. package/dist/scan-WY23TJCP.js +0 -12
  164. package/dist/test-OJRXNDO2.js.map +0 -1
  165. package/dist/viewer-SUFOISZM.js.map +0 -1
  166. package/src/shared/segment-loader.ts +0 -59
  167. /package/dist/{core-W2HYIQW6.js.map → core-SFHPYR5H.js.map} +0 -0
  168. /package/dist/{scan-WY23TJCP.js.map → scan-KQBKUS64.js.map} +0 -0
  169. /package/dist/{service-T2L7VLTE.js.map → service-ED2LNCTU.js.map} +0 -0
  170. /package/dist/{static-viewer-GBR7YNF3.js.map → static-viewer-Q4F4QP5M.js.map} +0 -0
  171. /package/dist/{tokens-3BWDESVM.js.map → tokens-P2B7ZAM3.js.map} +0 -0
@@ -71,9 +71,9 @@ export interface EnhanceOptions {
71
71
  }
72
72
 
73
73
  /**
74
- * Enhanced segment data
74
+ * Enhanced fragment data
75
75
  */
76
- export interface EnhancedSegment {
76
+ export interface EnhancedFragment {
77
77
  componentName: string;
78
78
  added: {
79
79
  when: string[];
@@ -90,7 +90,7 @@ export interface EnhancedSegment {
90
90
  */
91
91
  export interface EnhanceResult {
92
92
  success: boolean;
93
- enhanced: EnhancedSegment[];
93
+ enhanced: EnhancedFragment[];
94
94
  totalTokens: number;
95
95
  estimatedCost: number;
96
96
  /** Context output for IDE AI mode */
@@ -211,9 +211,9 @@ export async function enhance(options: EnhanceOptions = {}): Promise<EnhanceResu
211
211
  }
212
212
 
213
213
  // Search entire root dir, not just src/
214
- const segmentFiles = await findSegmentFiles(rootDir);
214
+ const fragmentFiles = await findFragmentFiles(rootDir);
215
215
 
216
- if (segmentFiles.length === 0) {
216
+ if (fragmentFiles.length === 0) {
217
217
  const msg = 'No fragment files found';
218
218
  if (format === 'json') {
219
219
  console.log(JSON.stringify({ success: false, error: msg }));
@@ -224,7 +224,7 @@ export async function enhance(options: EnhanceOptions = {}): Promise<EnhanceResu
224
224
  }
225
225
 
226
226
  if (isInteractive) {
227
- console.log(pc.green(` Found ${segmentFiles.length} fragment files`));
227
+ console.log(pc.green(` Found ${fragmentFiles.length} fragment files`));
228
228
  }
229
229
 
230
230
  // Filter components if specified
@@ -232,7 +232,7 @@ export async function enhance(options: EnhanceOptions = {}): Promise<EnhanceResu
232
232
  if (component && component !== 'all') {
233
233
  componentsToEnhance = [component];
234
234
  } else {
235
- componentsToEnhance = segmentFiles.map(f => extractComponentName(f));
235
+ componentsToEnhance = fragmentFiles.map(f => extractComponentName(f));
236
236
  }
237
237
 
238
238
  // Phase 4: Extract props from TypeScript source files
@@ -242,16 +242,16 @@ export async function enhance(options: EnhanceOptions = {}): Promise<EnhanceResu
242
242
 
243
243
  const propsExtractions = new Map<string, PropsExtractionResult>();
244
244
  for (const compName of componentsToEnhance) {
245
- const segmentFile = segmentFiles.find(f => extractComponentName(f) === compName);
246
- if (!segmentFile) continue;
245
+ const fragmentFile = fragmentFiles.find(f => extractComponentName(f) === compName);
246
+ if (!fragmentFile) continue;
247
247
 
248
- // Try to find the component source file relative to the segment file
249
- const segmentDir = segmentFile.replace(/\.segment\.(tsx?|jsx?)$/, '');
248
+ // Try to find the component source file relative to the fragment file
249
+ const fragmentDir = fragmentFile.replace(/\.fragment\.(tsx?|jsx?)$/, '');
250
250
  const possiblePaths = [
251
- `${segmentDir}.tsx`,
252
- `${segmentDir}.ts`,
253
- `${segmentDir}/index.tsx`,
254
- `${segmentDir}/index.ts`,
251
+ `${fragmentDir}.tsx`,
252
+ `${fragmentDir}.ts`,
253
+ `${fragmentDir}/index.tsx`,
254
+ `${fragmentDir}/index.ts`,
255
255
  join(rootDir, 'src', 'components', `${compName}.tsx`),
256
256
  join(rootDir, 'src', 'components', compName, `${compName}.tsx`),
257
257
  join(rootDir, 'src', 'components', compName, 'index.tsx'),
@@ -332,14 +332,14 @@ export async function enhance(options: EnhanceOptions = {}): Promise<EnhanceResu
332
332
  }
333
333
 
334
334
  // Build contexts for all components
335
- const contexts: Array<{ name: string; context: ComponentContext; segmentFile: string }> = [];
335
+ const contexts: Array<{ name: string; context: ComponentContext; fragmentFile: string }> = [];
336
336
  for (const compName of componentsToEnhance) {
337
337
  const analysis = usageAnalysis.components[compName];
338
338
  const stories = storyFiles.get(compName);
339
- const segmentFile = segmentFiles.find(f => extractComponentName(f) === compName);
339
+ const fragmentFile = fragmentFiles.find(f => extractComponentName(f) === compName);
340
340
  const propsExtraction = propsExtractions.get(compName);
341
341
 
342
- if (!segmentFile) continue;
342
+ if (!fragmentFile) continue;
343
343
 
344
344
  const context = generateComponentContext(
345
345
  compName,
@@ -348,7 +348,7 @@ export async function enhance(options: EnhanceOptions = {}): Promise<EnhanceResu
348
348
  stories,
349
349
  propsExtraction
350
350
  );
351
- contexts.push({ name: compName, context, segmentFile });
351
+ contexts.push({ name: compName, context, fragmentFile });
352
352
  }
353
353
 
354
354
  // Context-only mode: output prompts for IDE AI
@@ -361,13 +361,13 @@ export async function enhance(options: EnhanceOptions = {}): Promise<EnhanceResu
361
361
  console.log(pc.dim(`\nPhase 6: Generating AI enhancements for ${componentsToEnhance.length} component(s)...\n`));
362
362
  }
363
363
 
364
- const enhanced: EnhancedSegment[] = [];
364
+ const enhanced: EnhancedFragment[] = [];
365
365
  let totalTokens = 0;
366
366
 
367
367
  // Initialize AI client
368
368
  const aiClient = await createAIClient(provider, apiKey!);
369
369
 
370
- for (const { name: compName, context, segmentFile } of contexts) {
370
+ for (const { name: compName, context, fragmentFile } of contexts) {
371
371
  // Check if we have enough data
372
372
  if (!context.usageAnalysis || context.usageAnalysis.totalUsages < 2) {
373
373
  enhanced.push({
@@ -426,7 +426,7 @@ export async function enhance(options: EnhanceOptions = {}): Promise<EnhanceResu
426
426
  // Phase 7: Apply changes
427
427
  if (!dryRun) {
428
428
  if (isInteractive) {
429
- console.log(pc.dim('\nPhase 7: Updating segment files...'));
429
+ console.log(pc.dim('\nPhase 7: Updating fragment files...'));
430
430
  }
431
431
 
432
432
  for (const result of enhanced) {
@@ -434,17 +434,17 @@ export async function enhance(options: EnhanceOptions = {}): Promise<EnhanceResu
434
434
  continue;
435
435
  }
436
436
 
437
- const segmentFile = segmentFiles.find(f => extractComponentName(f) === result.componentName);
438
- if (!segmentFile) continue;
437
+ const fragmentFile = fragmentFiles.find(f => extractComponentName(f) === result.componentName);
438
+ if (!fragmentFile) continue;
439
439
 
440
440
  try {
441
- await updateSegmentFile(segmentFile, result.added);
441
+ await updateFragmentFile(fragmentFile, result.added);
442
442
  if (isInteractive) {
443
- console.log(pc.green(` Updated: ${relative(rootDir, segmentFile)}`));
443
+ console.log(pc.green(` Updated: ${relative(rootDir, fragmentFile)}`));
444
444
  }
445
445
  } catch {
446
446
  if (isInteractive) {
447
- console.log(pc.red(` Failed to update: ${relative(rootDir, segmentFile)}`));
447
+ console.log(pc.red(` Failed to update: ${relative(rootDir, fragmentFile)}`));
448
448
  }
449
449
  }
450
450
  }
@@ -488,7 +488,7 @@ export async function enhance(options: EnhanceOptions = {}): Promise<EnhanceResu
488
488
  * Handle context-only mode for IDE AI
489
489
  */
490
490
  function handleContextOnlyMode(
491
- contexts: Array<{ name: string; context: ComponentContext; segmentFile: string }>,
491
+ contexts: Array<{ name: string; context: ComponentContext; fragmentFile: string }>,
492
492
  format: string,
493
493
  isInteractive: boolean
494
494
  ): EnhanceResult {
@@ -544,7 +544,7 @@ For each component, provide your response in JSON format:
544
544
  console.log(pc.dim('─'.repeat(60)));
545
545
  console.log();
546
546
  console.log(pc.green('Tip: In Cursor, press Cmd+L to open chat and paste this prompt.'));
547
- console.log(pc.dim('After getting suggestions, manually update your segment files.'));
547
+ console.log(pc.dim('After getting suggestions, manually update your fragment files.'));
548
548
  console.log();
549
549
  }
550
550
 
@@ -783,11 +783,11 @@ function calculateCost(provider: AIProvider, tokens: number): number {
783
783
  }
784
784
 
785
785
  /**
786
- * Find all segment files in a directory
786
+ * Find all fragment files in a directory
787
787
  */
788
- async function findSegmentFiles(dir: string): Promise<string[]> {
788
+ async function findFragmentFiles(dir: string): Promise<string[]> {
789
789
  const fg = await import('fast-glob');
790
- return fg.default(['**/*.segment.tsx', '**/*.segment.ts'], {
790
+ return fg.default(['**/*.fragment.tsx', '**/*.fragment.ts'], {
791
791
  cwd: dir,
792
792
  absolute: true,
793
793
  ignore: ['**/node_modules/**', '**/dist/**'],
@@ -795,17 +795,17 @@ async function findSegmentFiles(dir: string): Promise<string[]> {
795
795
  }
796
796
 
797
797
  /**
798
- * Extract component name from segment file path
798
+ * Extract component name from fragment file path
799
799
  */
800
800
  function extractComponentName(filePath: string): string {
801
- const match = filePath.match(/([^/\\]+)\.segment\.(tsx?|jsx?)$/);
801
+ const match = filePath.match(/([^/\\]+)\.fragment\.(tsx?|jsx?)$/);
802
802
  return match ? match[1] : '';
803
803
  }
804
804
 
805
805
  /**
806
- * Update a segment file with new when/whenNot suggestions
806
+ * Update a fragment file with new when/whenNot suggestions
807
807
  */
808
- async function updateSegmentFile(
808
+ async function updateFragmentFile(
809
809
  filePath: string,
810
810
  suggestions: { when: string[]; whenNot: string[] }
811
811
  ): Promise<void> {
@@ -57,7 +57,7 @@ export async function generate(options: GenerateOptions = {}): Promise<GenerateR
57
57
  ignore: [
58
58
  "**/node_modules/**",
59
59
  "**/*.stories.*",
60
- "**/*.segment.*",
60
+ "**/*.fragment.*",
61
61
  "**/*.test.*",
62
62
  "**/*.spec.*",
63
63
  "**/*.d.ts",
@@ -8,7 +8,7 @@
8
8
  import pc from 'picocolors';
9
9
  import { readFile } from 'node:fs/promises';
10
10
  import { resolve } from 'node:path';
11
- import type { CompiledSegmentsFile } from '../core/index.js';
11
+ import type { CompiledFragmentsFile } from '../core/index.js';
12
12
  import { BRAND } from '../core/index.js';
13
13
  import { loadConfig } from '../core/node.js';
14
14
  import {
@@ -33,10 +33,10 @@ export async function graph(
33
33
  const { config, configDir } = await loadConfig(options.config);
34
34
  const outputPath = resolve(configDir, config.outFile ?? BRAND.outFile);
35
35
 
36
- let data: CompiledSegmentsFile;
36
+ let data: CompiledFragmentsFile;
37
37
  try {
38
38
  const content = await readFile(outputPath, 'utf-8');
39
- data = JSON.parse(content) as CompiledSegmentsFile;
39
+ data = JSON.parse(content) as CompiledFragmentsFile;
40
40
  } catch {
41
41
  console.error(
42
42
  pc.red(`Error: Could not load ${BRAND.outFile}. Run \`${BRAND.cliCommand} build\` first.`),
@@ -428,7 +428,7 @@ export async function init(options: InitOptions = {}): Promise<InitResult> {
428
428
  // Note: Stories are loaded separately by the viewer, not via include patterns
429
429
  const includePaths: string[] = [
430
430
  `${componentPath}/**/*.fragment.tsx`,
431
- `${componentPath}/**/*.segment.tsx`, // Legacy support
431
+ `${componentPath}/**/*.fragment.tsx`, // Legacy support
432
432
  ];
433
433
 
434
434
  // Create config file
@@ -1,12 +1,12 @@
1
1
  /**
2
- * fragments link figma - Link Figma components to segments
2
+ * fragments link figma - Link Figma components to fragments
3
3
  */
4
4
 
5
5
  import { readFile, writeFile } from 'node:fs/promises';
6
6
  import { relative } from 'node:path';
7
7
  import pc from 'picocolors';
8
8
  import { BRAND } from '../../core/index.js';
9
- import { loadConfig, discoverSegmentFiles } from '../../core/node.js';
9
+ import { loadConfig, discoverFragmentFiles } from '../../core/node.js';
10
10
  import { FigmaClient } from '../../service/index.js';
11
11
 
12
12
  /**
@@ -33,29 +33,29 @@ export interface LinkFigmaResult {
33
33
  }
34
34
 
35
35
  /**
36
- * Segment variant info
36
+ * Fragment variant info
37
37
  */
38
- interface SegmentVariantInfo {
38
+ interface FragmentVariantInfo {
39
39
  name: string;
40
40
  hasFigma: boolean;
41
41
  }
42
42
 
43
43
  /**
44
- * Segment info
44
+ * Fragment info
45
45
  */
46
- interface SegmentInfo {
46
+ interface FragmentInfo {
47
47
  name: string;
48
48
  filePath: string;
49
49
  relativePath: string;
50
50
  hasFigma: boolean;
51
- variants: SegmentVariantInfo[];
51
+ variants: FragmentVariantInfo[];
52
52
  }
53
53
 
54
54
  /**
55
55
  * Match result
56
56
  */
57
57
  interface Match {
58
- segment: SegmentInfo;
58
+ fragment: FragmentInfo;
59
59
  figmaComponent: {
60
60
  name: string;
61
61
  description: string;
@@ -145,20 +145,20 @@ export async function linkFigma(
145
145
  console.log();
146
146
  }
147
147
 
148
- // Discover local segments
149
- const segmentFiles = await discoverSegmentFiles(config, configDir);
148
+ // Discover local fragments
149
+ const fragmentFiles = await discoverFragmentFiles(config, configDir);
150
150
 
151
- if (segmentFiles.length === 0) {
152
- console.log(pc.yellow('No segment files found in codebase.'));
151
+ if (fragmentFiles.length === 0) {
152
+ console.log(pc.yellow('No fragment files found in codebase.'));
153
153
  console.log(pc.dim(`Looking for: ${config.include.join(', ')}`));
154
154
  process.exit(0);
155
155
  }
156
156
 
157
- console.log(pc.dim(`Found ${segmentFiles.length} segment file(s)\n`));
157
+ console.log(pc.dim(`Found ${fragmentFiles.length} fragment file(s)\n`));
158
158
 
159
- // Load segments to get names
160
- const segments: SegmentInfo[] = [];
161
- for (const file of segmentFiles) {
159
+ // Load fragments to get names
160
+ const fragments: FragmentInfo[] = [];
161
+ for (const file of fragmentFiles) {
162
162
  try {
163
163
  const content = await readFile(file.absolutePath, 'utf-8');
164
164
  // Extract name from meta.name in the file
@@ -167,15 +167,15 @@ export async function linkFigma(
167
167
  const hasFigma = /meta:\s*\{[^}]*figma:\s*['"]https?:/.test(content);
168
168
 
169
169
  // Extract variant names and their figma status
170
- const segmentVariants = extractVariants(content, nameMatch?.[1]);
170
+ const fragmentVariants = extractVariants(content, nameMatch?.[1]);
171
171
 
172
172
  if (nameMatch) {
173
- segments.push({
173
+ fragments.push({
174
174
  name: nameMatch[1],
175
175
  filePath: file.absolutePath,
176
176
  relativePath: file.relativePath,
177
177
  hasFigma,
178
- variants: segmentVariants,
178
+ variants: fragmentVariants,
179
179
  });
180
180
  }
181
181
  } catch {
@@ -185,15 +185,15 @@ export async function linkFigma(
185
185
 
186
186
  // Find matches
187
187
  const matches: Match[] = [];
188
- const unmatchedSegments: SegmentInfo[] = [];
188
+ const unmatchedFragments: FragmentInfo[] = [];
189
189
 
190
- for (const segment of segments) {
190
+ for (const fragment of fragments) {
191
191
  // Find best matching Figma component
192
192
  let bestMatch: typeof allFigmaComponents[0] | null = null;
193
193
  let bestScore = 0;
194
194
 
195
195
  for (const figmaComp of allFigmaComponents) {
196
- const score = calculateMatchScore(segment.name, figmaComp.name);
196
+ const score = calculateMatchScore(fragment.name, figmaComp.name);
197
197
 
198
198
  if (score > bestScore) {
199
199
  bestMatch = figmaComp;
@@ -206,19 +206,19 @@ export async function linkFigma(
206
206
 
207
207
  // Accept matches with 65%+ score
208
208
  if (bestMatch && bestScore >= 65) {
209
- const alreadyLinked = segment.hasFigma;
209
+ const alreadyLinked = fragment.hasFigma;
210
210
  if (alreadyLinked && !auto) {
211
- console.log(pc.dim(`⏭️ ${segment.name} (already linked)`));
211
+ console.log(pc.dim(`⏭️ ${fragment.name} (already linked)`));
212
212
  }
213
- matches.push({ segment, figmaComponent: bestMatch, score: bestScore, alreadyLinked });
213
+ matches.push({ fragment, figmaComponent: bestMatch, score: bestScore, alreadyLinked });
214
214
  } else {
215
- unmatchedSegments.push(segment);
215
+ unmatchedFragments.push(fragment);
216
216
  }
217
217
  }
218
218
 
219
- if (unmatchedSegments.length > 0) {
220
- console.log(pc.dim('Unmatched segments:'));
221
- for (const seg of unmatchedSegments) {
219
+ if (unmatchedFragments.length > 0) {
220
+ console.log(pc.dim('Unmatched fragments:'));
221
+ for (const seg of unmatchedFragments) {
222
222
  console.log(` ${pc.dim('•')} ${seg.name}`);
223
223
  }
224
224
  console.log();
@@ -230,7 +230,7 @@ export async function linkFigma(
230
230
 
231
231
  if (matches.length === 0) {
232
232
  console.log(pc.yellow('\nNo automatic matches found.'));
233
- console.log(pc.dim('You can manually add figma URLs to your segment definitions.'));
233
+ console.log(pc.dim('You can manually add figma URLs to your fragment definitions.'));
234
234
  process.exit(0);
235
235
  }
236
236
 
@@ -241,7 +241,7 @@ export async function linkFigma(
241
241
  for (const match of newMatches) {
242
242
  const scoreColor = match.score === 100 ? pc.green : pc.yellow;
243
243
  console.log(
244
- ` ${pc.green('✓')} ${pc.bold(match.segment.name)} → ${match.figmaComponent.name} ${scoreColor(`(${Math.round(match.score)}%)`)}`
244
+ ` ${pc.green('✓')} ${pc.bold(match.fragment.name)} → ${match.figmaComponent.name} ${scoreColor(`(${Math.round(match.score)}%)`)}`
245
245
  );
246
246
  }
247
247
  }
@@ -259,7 +259,7 @@ export async function linkFigma(
259
259
  for (const match of newMatches) {
260
260
  const scoreColor = match.score === 100 ? pc.green : pc.yellow;
261
261
  console.log(
262
- ` ${pc.green('✓')} ${pc.bold(match.segment.name)} → ${match.figmaComponent.name} ${scoreColor(`(${Math.round(match.score)}%)`)}`
262
+ ` ${pc.green('✓')} ${pc.bold(match.fragment.name)} → ${match.figmaComponent.name} ${scoreColor(`(${Math.round(match.score)}%)`)}`
263
263
  );
264
264
  }
265
265
  } else {
@@ -272,7 +272,7 @@ export async function linkFigma(
272
272
  const choices = newMatches.map((match) => {
273
273
  const scoreColor = match.score === 100 ? pc.green : pc.yellow;
274
274
  return {
275
- name: `${pc.bold(match.segment.name)} → ${match.figmaComponent.name} ${scoreColor(`(${Math.round(match.score)}%)`)}`,
275
+ name: `${pc.bold(match.fragment.name)} → ${match.figmaComponent.name} ${scoreColor(`(${Math.round(match.score)}%)`)}`,
276
276
  value: match,
277
277
  checked: true,
278
278
  };
@@ -295,13 +295,13 @@ export async function linkFigma(
295
295
  // Include already-linked matches for variant linking
296
296
  const allSelectedMatches = [...selectedMatches, ...alreadyLinkedMatches];
297
297
 
298
- // Update segment files (only for new matches, not already-linked)
298
+ // Update fragment files (only for new matches, not already-linked)
299
299
  let updated = 0;
300
300
  for (const match of selectedMatches) {
301
301
  if (match.alreadyLinked) continue;
302
302
 
303
303
  try {
304
- let content = await readFile(match.segment.filePath, 'utf-8');
304
+ let content = await readFile(match.fragment.filePath, 'utf-8');
305
305
  const figmaUrlToInsert = figmaClient.buildNodeUrl(
306
306
  match.figmaComponent.file_key,
307
307
  match.figmaComponent.node_id,
@@ -323,16 +323,16 @@ export async function linkFigma(
323
323
  );
324
324
  }
325
325
 
326
- await writeFile(match.segment.filePath, content);
326
+ await writeFile(match.fragment.filePath, content);
327
327
  updated++;
328
- console.log(` ${pc.green('✓')} Updated ${match.segment.relativePath}`);
328
+ console.log(` ${pc.green('✓')} Updated ${match.fragment.relativePath}`);
329
329
  } catch (error) {
330
- console.log(` ${pc.red('✗')} Failed to update ${match.segment.relativePath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
330
+ console.log(` ${pc.red('✗')} Failed to update ${match.fragment.relativePath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
331
331
  }
332
332
  }
333
333
 
334
334
  if (updated > 0) {
335
- console.log(pc.green(`\n✓ Updated ${updated} segment file(s)\n`));
335
+ console.log(pc.green(`\n✓ Updated ${updated} fragment file(s)\n`));
336
336
  }
337
337
 
338
338
  // Variant linking
@@ -357,10 +357,10 @@ export async function linkFigma(
357
357
  }
358
358
 
359
359
  /**
360
- * Extract variants from segment file content
360
+ * Extract variants from fragment file content
361
361
  */
362
- function extractVariants(content: string, componentName?: string): SegmentVariantInfo[] {
363
- const variants: SegmentVariantInfo[] = [];
362
+ function extractVariants(content: string, componentName?: string): FragmentVariantInfo[] {
363
+ const variants: FragmentVariantInfo[] = [];
364
364
 
365
365
  // Find variants: [ ... ] section
366
366
  const variantsArrayMatch = content.match(/variants:\s*\[/);
@@ -411,27 +411,27 @@ function extractVariants(content: string, componentName?: string): SegmentVarian
411
411
  /**
412
412
  * Calculate match score between two names
413
413
  */
414
- function calculateMatchScore(segmentName: string, figmaName: string): number {
414
+ function calculateMatchScore(fragmentName: string, figmaName: string): number {
415
415
  const normalizeForMatch = (s: string) =>
416
416
  s.toLowerCase().replace(/[^a-z0-9]/g, '');
417
417
 
418
- const normalizedSegment = normalizeForMatch(segmentName);
418
+ const normalizedFragment = normalizeForMatch(fragmentName);
419
419
  const normalizedFigma = normalizeForMatch(figmaName);
420
420
 
421
421
  // Exact match after normalization
422
- if (normalizedSegment === normalizedFigma) {
422
+ if (normalizedFragment === normalizedFigma) {
423
423
  return 100;
424
424
  }
425
425
 
426
- // Check if segment name appears at the START of figma name
427
- if (normalizedFigma.startsWith(normalizedSegment)) {
428
- const coverage = normalizedSegment.length / normalizedFigma.length;
426
+ // Check if fragment name appears at the START of figma name
427
+ if (normalizedFigma.startsWith(normalizedFragment)) {
428
+ const coverage = normalizedFragment.length / normalizedFigma.length;
429
429
  return Math.max(85, coverage * 100);
430
430
  }
431
431
 
432
- // Check if figma name appears at the START of segment name
433
- if (normalizedSegment.startsWith(normalizedFigma)) {
434
- const coverage = normalizedFigma.length / normalizedSegment.length;
432
+ // Check if figma name appears at the START of fragment name
433
+ if (normalizedFragment.startsWith(normalizedFigma)) {
434
+ const coverage = normalizedFigma.length / normalizedFragment.length;
435
435
  return Math.max(80, coverage * 100);
436
436
  }
437
437
 
@@ -445,24 +445,24 @@ function calculateMatchScore(segmentName: string, figmaName: string): number {
445
445
  .filter((w) => w.length > 0);
446
446
  };
447
447
 
448
- const segmentWords = getWords(segmentName);
448
+ const fragmentWords = getWords(fragmentName);
449
449
  const figmaWords = getWords(figmaName);
450
450
 
451
- // Check if all segment words appear in figma words
452
- const allSegmentWordsInFigma = segmentWords.every((sw) =>
451
+ // Check if all fragment words appear in figma words
452
+ const allFragmentWordsInFigma = fragmentWords.every((sw) =>
453
453
  figmaWords.some((fw) => fw === sw || fw.startsWith(sw) || sw.startsWith(fw))
454
454
  );
455
455
 
456
- if (allSegmentWordsInFigma && segmentWords.length > 0) {
457
- const wordOverlap = segmentWords.length / Math.max(segmentWords.length, figmaWords.length);
456
+ if (allFragmentWordsInFigma && fragmentWords.length > 0) {
457
+ const wordOverlap = fragmentWords.length / Math.max(fragmentWords.length, figmaWords.length);
458
458
  return Math.max(75, wordOverlap * 95);
459
459
  }
460
460
 
461
461
  // Partial containment
462
- if (normalizedFigma.includes(normalizedSegment)) {
462
+ if (normalizedFigma.includes(normalizedFragment)) {
463
463
  return 70;
464
464
  }
465
- if (normalizedSegment.includes(normalizedFigma)) {
465
+ if (normalizedFragment.includes(normalizedFigma)) {
466
466
  return 65;
467
467
  }
468
468
 
@@ -510,16 +510,16 @@ async function linkVariants(
510
510
  continue;
511
511
  }
512
512
 
513
- // Match segment variants to Figma variants
514
- const segmentVariants = match.segment.variants.filter((v) => !v.hasFigma);
515
- if (segmentVariants.length === 0) {
516
- console.log(pc.dim(` ⏭️ ${match.segment.name}: all variants already linked`));
513
+ // Match fragment variants to Figma variants
514
+ const fragmentVariants = match.fragment.variants.filter((v) => !v.hasFigma);
515
+ if (fragmentVariants.length === 0) {
516
+ console.log(pc.dim(` ⏭️ ${match.fragment.name}: all variants already linked`));
517
517
  continue;
518
518
  }
519
519
 
520
- console.log(pc.dim(` ${match.segment.name}: ${csWithVariants.variants.length} Figma variants`));
520
+ console.log(pc.dim(` ${match.fragment.name}: ${csWithVariants.variants.length} Figma variants`));
521
521
 
522
- for (const segmentVariant of segmentVariants) {
522
+ for (const fragmentVariant of fragmentVariants) {
523
523
  // Find matching Figma variants by score
524
524
  const variantMatches: Array<{
525
525
  figmaVariant: typeof csWithVariants.variants[0];
@@ -527,18 +527,18 @@ async function linkVariants(
527
527
  }> = [];
528
528
 
529
529
  for (const fv of csWithVariants.variants) {
530
- // Check if any property value matches the segment variant name
531
- const normalizedSegment = normalizeForMatch(segmentVariant.name);
530
+ // Check if any property value matches the fragment variant name
531
+ const normalizedFragment = normalizeForMatch(fragmentVariant.name);
532
532
 
533
533
  for (const value of fv.values) {
534
534
  const normalizedValue = normalizeForMatch(value);
535
535
 
536
- if (normalizedSegment === normalizedValue) {
536
+ if (normalizedFragment === normalizedValue) {
537
537
  variantMatches.push({ figmaVariant: fv, score: 100 });
538
538
  break;
539
- } else if (normalizedValue.includes(normalizedSegment)) {
539
+ } else if (normalizedValue.includes(normalizedFragment)) {
540
540
  variantMatches.push({ figmaVariant: fv, score: 85 });
541
- } else if (normalizedSegment.includes(normalizedValue)) {
541
+ } else if (normalizedFragment.includes(normalizedValue)) {
542
542
  variantMatches.push({ figmaVariant: fv, score: 75 });
543
543
  }
544
544
  }
@@ -557,11 +557,11 @@ async function linkVariants(
557
557
  );
558
558
 
559
559
  try {
560
- let content = await readFile(match.segment.filePath, 'utf-8');
560
+ let content = await readFile(match.fragment.filePath, 'utf-8');
561
561
 
562
562
  // Add figma URL after the variant's name field
563
563
  const namePattern = new RegExp(
564
- `(name:\\s*['"]${escapeRegExp(segmentVariant.name)}['"],?)`,
564
+ `(name:\\s*['"]${escapeRegExp(fragmentVariant.name)}['"],?)`,
565
565
  'g'
566
566
  );
567
567
 
@@ -572,14 +572,14 @@ async function linkVariants(
572
572
  return `${matchedStr}\n figma: '${variantUrl}',`;
573
573
  });
574
574
 
575
- await writeFile(match.segment.filePath, content);
575
+ await writeFile(match.fragment.filePath, content);
576
576
  variantUpdates++;
577
577
  console.log(
578
- ` ${pc.green('✓')} ${segmentVariant.name} → ${bestMatch.figmaVariant.name}`
578
+ ` ${pc.green('✓')} ${fragmentVariant.name} → ${bestMatch.figmaVariant.name}`
579
579
  );
580
580
  } catch (error) {
581
581
  console.log(
582
- ` ${pc.red('✗')} ${segmentVariant.name}: ${error instanceof Error ? error.message : 'Unknown error'}`
582
+ ` ${pc.red('✗')} ${fragmentVariant.name}: ${error instanceof Error ? error.message : 'Unknown error'}`
583
583
  );
584
584
  }
585
585
  } else if (variantMatches.length > 0) {
@@ -594,7 +594,7 @@ async function linkVariants(
594
594
 
595
595
  try {
596
596
  const selectedVariant = await select({
597
- message: ` Match for "${segmentVariant.name}":`,
597
+ message: ` Match for "${fragmentVariant.name}":`,
598
598
  choices,
599
599
  });
600
600
 
@@ -605,9 +605,9 @@ async function linkVariants(
605
605
  figmaData.fileName
606
606
  );
607
607
 
608
- let content = await readFile(match.segment.filePath, 'utf-8');
608
+ let content = await readFile(match.fragment.filePath, 'utf-8');
609
609
  const namePattern = new RegExp(
610
- `(name:\\s*['"]${escapeRegExp(segmentVariant.name)}['"],?)`,
610
+ `(name:\\s*['"]${escapeRegExp(fragmentVariant.name)}['"],?)`,
611
611
  'g'
612
612
  );
613
613
 
@@ -618,19 +618,19 @@ async function linkVariants(
618
618
  return `${matchedStr}\n figma: '${variantUrl}',`;
619
619
  });
620
620
 
621
- await writeFile(match.segment.filePath, content);
621
+ await writeFile(match.fragment.filePath, content);
622
622
  variantUpdates++;
623
623
  console.log(
624
- ` ${pc.green('✓')} ${segmentVariant.name} → ${selectedVariant.name}`
624
+ ` ${pc.green('✓')} ${fragmentVariant.name} → ${selectedVariant.name}`
625
625
  );
626
626
  } else {
627
- console.log(` ${pc.dim('⏭️')} ${segmentVariant.name} (skipped)`);
627
+ console.log(` ${pc.dim('⏭️')} ${fragmentVariant.name} (skipped)`);
628
628
  }
629
629
  } catch {
630
- console.log(` ${pc.dim('⏭️')} ${segmentVariant.name} (cancelled)`);
630
+ console.log(` ${pc.dim('⏭️')} ${fragmentVariant.name} (cancelled)`);
631
631
  }
632
632
  } else {
633
- console.log(` ${pc.yellow('?')} ${segmentVariant.name}: no matching Figma variant`);
633
+ console.log(` ${pc.yellow('?')} ${fragmentVariant.name}: no matching Figma variant`);
634
634
  }
635
635
  }
636
636
  }