@fragments-sdk/cli 0.14.3 → 0.15.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.
- package/README.md +0 -3
- package/dist/{ai-client-I6MDWNYA.js → ai-client-LSLQGOMM.js} +1 -2
- package/dist/bin.js +4745 -3817
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-TXFCEDOC.js → chunk-2WXKALIG.js} +2 -2
- package/dist/{chunk-I34BC3CU.js → chunk-32LIWN2P.js} +1006 -3
- package/dist/chunk-32LIWN2P.js.map +1 -0
- package/dist/chunk-5JF26E55.js +1255 -0
- package/dist/chunk-5JF26E55.js.map +1 -0
- package/dist/{chunk-APTQIBS5.js → chunk-6SQPP47U.js} +153 -1342
- package/dist/chunk-6SQPP47U.js.map +1 -0
- package/dist/chunk-7DZC4YEV.js +294 -0
- package/dist/chunk-7DZC4YEV.js.map +1 -0
- package/dist/{chunk-PJT5IZ37.js → chunk-BJE3425I.js} +19 -52
- package/dist/{chunk-PJT5IZ37.js.map → chunk-BJE3425I.js.map} +1 -1
- package/dist/{chunk-55KERLWL.js → chunk-HQ6A6DTV.js} +1587 -1073
- package/dist/chunk-HQ6A6DTV.js.map +1 -0
- package/dist/chunk-MHIBEEW4.js +511 -0
- package/dist/chunk-MHIBEEW4.js.map +1 -0
- package/dist/{chunk-5A6X2Y73.js → chunk-ONUP6Z4W.js} +25 -13
- package/dist/chunk-ONUP6Z4W.js.map +1 -0
- package/dist/chunk-QCN35LJU.js +630 -0
- package/dist/chunk-QCN35LJU.js.map +1 -0
- package/dist/chunk-T47OLCSF.js +36 -0
- package/dist/chunk-T47OLCSF.js.map +1 -0
- package/dist/codebase-scanner-MQHUZC2G.js +21 -0
- package/dist/converter-7XM3Y6NJ.js +33 -0
- package/dist/converter-7XM3Y6NJ.js.map +1 -0
- package/dist/core/index.js +43 -2
- package/dist/create-IH4R45GE.js +806 -0
- package/dist/create-IH4R45GE.js.map +1 -0
- package/dist/{generate-RYWIPDN2.js → generate-PVOLUAAC.js} +4 -6
- package/dist/{generate-RYWIPDN2.js.map → generate-PVOLUAAC.js.map} +1 -1
- package/dist/govern-scan-OYFZYOQW.js +413 -0
- package/dist/govern-scan-OYFZYOQW.js.map +1 -0
- package/dist/index.d.ts +4 -23
- package/dist/index.js +15 -14
- package/dist/index.js.map +1 -1
- package/dist/{init-WRUSW7R5.js → init-SSGUSP7Z.js} +131 -129
- package/dist/init-SSGUSP7Z.js.map +1 -0
- package/dist/{init-cloud-REQ3XLHO.js → init-cloud-3DNKPWFB.js} +30 -5
- package/dist/{init-cloud-REQ3XLHO.js.map → init-cloud-3DNKPWFB.js.map} +1 -1
- package/dist/mcp-bin.js +5 -37
- package/dist/mcp-bin.js.map +1 -1
- package/dist/node-37AUE74M.js +65 -0
- package/dist/push-contracts-WY32TFP6.js +84 -0
- package/dist/push-contracts-WY32TFP6.js.map +1 -0
- package/dist/scan-PKSYSTRR.js +15 -0
- package/dist/{scan-generate-TFZVL3BT.js → scan-generate-VY27PIOX.js} +340 -52
- package/dist/scan-generate-VY27PIOX.js.map +1 -0
- package/dist/scanner-4KZNOXAK.js +12 -0
- package/dist/{service-HKJ6B7P7.js → service-QJGWUIVL.js} +41 -30
- package/dist/{snapshot-C5DYIGIV.js → snapshot-WIJMEIFT.js} +2 -3
- package/dist/{snapshot-C5DYIGIV.js.map → snapshot-WIJMEIFT.js.map} +1 -1
- package/dist/{static-viewer-DUVC4UIM.js → static-viewer-7QIBQZRC.js} +3 -4
- package/dist/static-viewer-7QIBQZRC.js.map +1 -0
- package/dist/{test-JW7JIDFG.js → test-64Z5BKBA.js} +4 -7
- package/dist/{test-JW7JIDFG.js.map → test-64Z5BKBA.js.map} +1 -1
- package/dist/token-normalizer-TEPOVBPV.js +312 -0
- package/dist/token-normalizer-TEPOVBPV.js.map +1 -0
- package/dist/token-parser-32KOIOFN.js +22 -0
- package/dist/token-parser-32KOIOFN.js.map +1 -0
- package/dist/{tokens-KE73G5JC.js → tokens-NZWFQIAB.js} +10 -9
- package/dist/{tokens-KE73G5JC.js.map → tokens-NZWFQIAB.js.map} +1 -1
- package/dist/tokens-generate-5JQSJ27E.js +85 -0
- package/dist/tokens-generate-5JQSJ27E.js.map +1 -0
- package/dist/tokens-push-HY3KO36V.js +148 -0
- package/dist/tokens-push-HY3KO36V.js.map +1 -0
- package/package.json +8 -6
- package/src/bin.ts +300 -48
- package/src/commands/__fixtures__/shadcn-label-wrapper/package.json +7 -0
- package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/label.contract.json +42 -0
- package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/label.tsx +11 -0
- package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/primitive.contract.json +20 -0
- package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/primitive.tsx +14 -0
- package/src/commands/__fixtures__/shadcn-label-wrapper/tsconfig.app.json +23 -0
- package/src/commands/__tests__/build-freshness.test.ts +231 -0
- package/src/commands/__tests__/create.test.ts +71 -0
- package/src/commands/__tests__/drift-sync.test.ts +1 -1
- package/src/commands/__tests__/govern.test.ts +258 -0
- package/src/commands/__tests__/init.test.ts +113 -0
- package/src/commands/__tests__/scan-generate.test.ts +189 -70
- package/src/commands/__tests__/verify.test.ts +91 -0
- package/src/commands/build.ts +54 -1
- package/src/commands/context.ts +1 -1
- package/src/commands/create.ts +536 -0
- package/src/commands/discover.ts +151 -0
- package/src/commands/doctor.ts +3 -2
- package/src/commands/enhance.ts +3 -1
- package/src/commands/govern-scan.ts +565 -0
- package/src/commands/govern.ts +67 -4
- package/src/commands/init-cloud.ts +32 -4
- package/src/commands/init.ts +152 -28
- package/src/commands/inspect.ts +290 -0
- package/src/commands/migrate-contract.ts +85 -0
- package/src/commands/push-contracts.ts +112 -0
- package/src/commands/scan-generate.ts +439 -51
- package/src/commands/scan.ts +14 -0
- package/src/commands/setup.ts +27 -50
- package/src/commands/sync.ts +2 -2
- package/src/commands/tokens-generate.ts +113 -0
- package/src/commands/tokens-push.ts +199 -0
- package/src/commands/verify.ts +195 -1
- package/src/core/__fixtures__/shadcn-input/input.tsx +7 -0
- package/src/core/__fixtures__/shadcn-input/tsconfig.json +14 -0
- package/src/core/__fixtures__/shadcn-label/label.tsx +11 -0
- package/src/core/__fixtures__/shadcn-label/primitive.tsx +14 -0
- package/src/core/__fixtures__/shadcn-label/tsconfig.json +14 -0
- package/src/core/__fixtures__/shadcn-radix-label/label.tsx +11 -0
- package/src/core/__fixtures__/shadcn-radix-label/node_modules/radix-ui/index.d.ts +12 -0
- package/src/core/__fixtures__/shadcn-radix-label/tsconfig.json +14 -0
- package/src/core/__tests__/contract-parity.test.ts +316 -0
- package/src/core/__tests__/token-resolver.test.ts +1 -1
- package/src/core/component-extractor.test.ts +40 -1
- package/src/core/config.ts +2 -1
- package/src/core/discovery.ts +13 -2
- package/src/core/drift-verifier.ts +123 -0
- package/src/core/extractor-adapter.ts +80 -0
- package/src/index.ts +3 -3
- package/src/mcp/__tests__/projectFields.test.ts +1 -1
- package/src/mcp/utils.ts +1 -50
- package/src/migrate/converter.ts +3 -3
- package/src/migrate/fragment-to-contract.ts +253 -0
- package/src/migrate/report.ts +1 -1
- package/src/scripts/token-benchmark.ts +121 -0
- package/src/service/__tests__/props-extractor.test.ts +94 -0
- package/src/service/__tests__/token-normalizer.test.ts +690 -0
- package/src/service/ast-utils.ts +4 -23
- package/src/service/babel-config.ts +23 -0
- package/src/service/enhance/converter.ts +61 -0
- package/src/service/enhance/props-extractor.ts +25 -8
- package/src/service/enhance/scanner.ts +5 -24
- package/src/service/index.ts +8 -0
- package/src/service/snippet-validation.ts +9 -3
- package/src/service/tailwind-v4-parser.ts +314 -0
- package/src/service/token-normalizer.ts +510 -0
- package/src/service/token-parser.ts +56 -0
- package/src/setup.ts +10 -39
- package/src/shared/index.ts +1 -0
- package/src/shared/project-fields.ts +46 -0
- package/src/theme/__tests__/component-contrast.test.ts +2 -2
- package/src/theme/__tests__/serializer.test.ts +1 -1
- package/src/theme/generator.ts +16 -1
- package/src/theme/schema.ts +8 -0
- package/src/theme/serializer.ts +13 -9
- package/src/theme/types.ts +8 -0
- package/src/validators.ts +1 -2
- package/src/viewer/__tests__/viewer-integration.test.ts +8 -8
- package/src/viewer/style-utils.ts +27 -412
- package/src/viewer/vite-plugin.ts +2 -2
- package/dist/chunk-55KERLWL.js.map +0 -1
- package/dist/chunk-5A6X2Y73.js.map +0 -1
- package/dist/chunk-APTQIBS5.js.map +0 -1
- package/dist/chunk-EYXVAMEX.js +0 -626
- package/dist/chunk-EYXVAMEX.js.map +0 -1
- package/dist/chunk-I34BC3CU.js.map +0 -1
- package/dist/chunk-LOYS64QS.js +0 -2453
- package/dist/chunk-LOYS64QS.js.map +0 -1
- package/dist/chunk-Z7EY4VHE.js +0 -50
- package/dist/chunk-ZKTFKHWN.js +0 -324
- package/dist/chunk-ZKTFKHWN.js.map +0 -1
- package/dist/discovery-VDANZAJ2.js +0 -28
- package/dist/init-WRUSW7R5.js.map +0 -1
- package/dist/sass.node-4XJK6YBF.js +0 -130708
- package/dist/sass.node-4XJK6YBF.js.map +0 -1
- package/dist/scan-YJHQIRKG.js +0 -14
- package/dist/scan-generate-TFZVL3BT.js.map +0 -1
- package/dist/viewer-2TZS3NDL.js +0 -2730
- package/dist/viewer-2TZS3NDL.js.map +0 -1
- package/src/build.ts +0 -612
- package/src/commands/dev.ts +0 -107
- package/src/core/auto-props.ts +0 -464
- package/src/core/component-extractor.ts +0 -1030
- package/src/core/token-resolver.ts +0 -155
- /package/dist/{ai-client-I6MDWNYA.js.map → ai-client-LSLQGOMM.js.map} +0 -0
- /package/dist/{chunk-TXFCEDOC.js.map → chunk-2WXKALIG.js.map} +0 -0
- /package/dist/{chunk-Z7EY4VHE.js.map → codebase-scanner-MQHUZC2G.js.map} +0 -0
- /package/dist/{discovery-VDANZAJ2.js.map → node-37AUE74M.js.map} +0 -0
- /package/dist/{scan-YJHQIRKG.js.map → scan-PKSYSTRR.js.map} +0 -0
- /package/dist/{service-HKJ6B7P7.js.map → scanner-4KZNOXAK.js.map} +0 -0
- /package/dist/{static-viewer-DUVC4UIM.js.map → service-QJGWUIVL.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/scan.ts"],"sourcesContent":["/**\n * fragments scan - Zero-config fragments.json generation from source code\n *\n * Automatically extracts component documentation by:\n * 1. Discovering components from source files and barrel exports\n * 2. Extracting props from TypeScript interfaces\n * 3. Scanning codebase for usage patterns\n * 4. Parsing Storybook stories for examples\n * 5. Inferring component relationships from usage data\n * 6. Generating complete fragments.json without manual documentation\n */\n\nimport { writeFile, mkdir } from \"node:fs/promises\";\nimport { resolve, join, dirname, relative } from \"node:path\";\nimport pc from \"picocolors\";\nimport {\n BRAND,\n type CompiledFragmentsFile,\n type CompiledFragment,\n type PropDefinition,\n} from \"../core/index.js\";\nimport {\n loadConfig,\n discoverAllComponents,\n findConfigFile,\n type DiscoveredComponent,\n} from \"../core/node.js\";\nimport {\n scanCodebase,\n extractPropsFromFile,\n parseAllStories,\n inferAllRelations,\n generateComponentContext,\n generateEnhancementSuggestions,\n filterBoilerplate,\n convertToFragmentProps,\n type UsageAnalysis,\n type ComponentRelation,\n type PropsExtractionResult,\n type ParsedStoryFile,\n} from \"../service/index.js\";\nimport { getGeneratorVersion } from '@fragments-sdk/compiler';\n\nexport interface ScanOptions {\n /** Path to config file */\n config?: string;\n /** Output file path (default: fragments.json) */\n output?: string;\n /** Component patterns to scan */\n componentPatterns?: string[];\n /** Barrel export files to parse */\n barrelFiles?: string[];\n /** Directory to scan for usage patterns */\n usageDir?: string;\n /** Skip usage analysis */\n skipUsage?: boolean;\n /** Skip Storybook parsing */\n skipStorybook?: boolean;\n /** Verbose output */\n verbose?: boolean;\n /** Suppress all console output (for use as a sub-step) */\n quiet?: boolean;\n}\n\nexport interface ScanResult {\n success: boolean;\n outputPath: string;\n componentCount: number;\n propsExtracted: number;\n usagesFound: number;\n relationsInferred: number;\n storiesParsed: number;\n errors: Array<{ component: string; error: string }>;\n warnings: Array<{ component: string; warning: string }>;\n}\n\n/**\n * Scan codebase and generate fragments.json directly from source\n */\nexport async function scan(options: ScanOptions = {}): Promise<ScanResult> {\n const startTime = Date.now();\n const errors: Array<{ component: string; error: string }> = [];\n const warnings: Array<{ component: string; warning: string }> = [];\n\n // In quiet mode, suppress all console output\n const log = options.quiet ? (() => {}) : console.log.bind(console);\n\n // Load config or use defaults\n let configDir: string;\n let outputFile: string;\n let componentPatterns: string[] | undefined;\n\n try {\n const loaded = await loadConfig(options.config);\n configDir = loaded.configDir;\n outputFile = options.output || loaded.config.outFile || \"fragments.json\";\n componentPatterns = options.componentPatterns || loaded.config.components;\n } catch {\n // No config file, use defaults\n configDir = process.cwd();\n outputFile = options.output || \"fragments.json\";\n componentPatterns = options.componentPatterns;\n }\n\n log(pc.cyan(`\\n${BRAND.name} Scan\\n`));\n log(pc.dim(\"Zero-config fragments.json generation from source code\\n\"));\n\n // Phase 1: Discover components\n log(pc.dim(\"Phase 1: Discovering components...\"));\n const components = await discoverAllComponents(configDir, {\n patterns: componentPatterns,\n exclude: [\"**/*.test.*\", \"**/*.spec.*\", \"**/__tests__/**\"],\n barrelFiles: options.barrelFiles,\n });\n\n if (components.length === 0) {\n log(pc.yellow(\"No components found. Check your patterns or config.\"));\n return {\n success: false,\n outputPath: resolve(configDir, outputFile),\n componentCount: 0,\n propsExtracted: 0,\n usagesFound: 0,\n relationsInferred: 0,\n storiesParsed: 0,\n errors: [{ component: \"*\", error: \"No components found\" }],\n warnings: [],\n };\n }\n\n log(pc.green(` Found ${components.length} components`));\n if (options.verbose) {\n for (const comp of components.slice(0, 10)) {\n log(pc.dim(` - ${comp.name}: ${comp.relativePath}`));\n }\n if (components.length > 10) {\n log(pc.dim(` ... and ${components.length - 10} more`));\n }\n }\n\n // Phase 2: Extract props from TypeScript\n log(pc.dim(\"\\nPhase 2: Extracting props from TypeScript...\"));\n const propsMap = new Map<string, ReturnType<typeof convertToFragmentProps>>();\n const propsResults = new Map<string, PropsExtractionResult>();\n let propsExtracted = 0;\n\n for (const comp of components) {\n try {\n const extraction = await extractPropsFromFile(comp.sourcePath, {\n propsTypeName: `${comp.name}Props`,\n componentName: comp.name,\n });\n\n propsResults.set(comp.name, extraction);\n\n if (extraction.success && extraction.props.length > 0) {\n propsMap.set(comp.name, convertToFragmentProps(extraction.props));\n propsExtracted++;\n }\n } catch (e) {\n if (options.verbose) {\n warnings.push({\n component: comp.name,\n warning: `Props extraction failed: ${e instanceof Error ? e.message : String(e)}`,\n });\n }\n }\n }\n\n log(pc.green(` Extracted props for ${propsExtracted} components`));\n\n // Phase 3: Scan for usage patterns\n let usageAnalysis: UsageAnalysis | undefined;\n let usagesFound = 0;\n let allRelations = new Map<string, ComponentRelation[]>();\n\n if (!options.skipUsage) {\n log(pc.dim(\"\\nPhase 3: Scanning for usage patterns...\"));\n const usageDir = options.usageDir || configDir;\n\n try {\n // Get component names for filtering\n const componentNames = components.map((c) => c.name);\n\n usageAnalysis = await scanCodebase({\n rootDir: usageDir,\n include: [\"**/*.tsx\", \"**/*.ts\", \"**/*.jsx\", \"**/*.js\"],\n exclude: [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/*.stories.*\",\n \"**/*.test.*\",\n \"**/*.spec.*\",\n ],\n componentNames,\n useCache: true,\n });\n\n // Count total usages across all components\n usagesFound = Object.values(usageAnalysis.components).reduce(\n (sum, comp) => sum + comp.totalUsages,\n 0\n );\n\n // Infer relations\n allRelations = inferAllRelations(usageAnalysis);\n log(pc.green(` Found ${usagesFound} usages across ${usageAnalysis.totalFiles} files`));\n log(pc.green(` Inferred relations for ${allRelations.size} components`));\n } catch (e) {\n warnings.push({\n component: \"*\",\n warning: `Usage scanning failed: ${e instanceof Error ? e.message : String(e)}`,\n });\n log(pc.yellow(` Usage scanning failed: ${e instanceof Error ? e.message : \"unknown error\"}`));\n }\n } else {\n log(pc.dim(\"\\nPhase 3: Skipping usage analysis\"));\n }\n\n // Phase 4: Parse Storybook stories\n const storiesMap = new Map<string, ParsedStoryFile>();\n let storiesParsed = 0;\n\n if (!options.skipStorybook) {\n log(pc.dim(\"\\nPhase 4: Parsing Storybook stories...\"));\n\n try {\n const allStories = await parseAllStories(configDir);\n\n for (const [name, stories] of Object.entries(allStories)) {\n if (stories && stories.stories.length > 0) {\n storiesMap.set(name, stories);\n storiesParsed++;\n }\n }\n\n log(pc.green(` Parsed stories for ${storiesParsed} components`));\n } catch (e) {\n warnings.push({\n component: \"*\",\n warning: `Storybook parsing failed: ${e instanceof Error ? e.message : String(e)}`,\n });\n log(pc.yellow(` Storybook parsing failed: ${e instanceof Error ? e.message : \"unknown error\"}`));\n }\n } else {\n log(pc.dim(\"\\nPhase 4: Skipping Storybook parsing\"));\n }\n\n // Phase 5: Generate fragments\n log(pc.dim(\"\\nPhase 5: Generating fragments...\"));\n const fragments: Record<string, CompiledFragment> = {};\n\n for (const comp of components) {\n try {\n const fragment = generateFragmentFromData(\n comp,\n configDir,\n propsMap.get(comp.name),\n usageAnalysis?.components[comp.name],\n allRelations.get(comp.name),\n storiesMap.get(comp.name)\n );\n\n fragments[comp.name] = fragment;\n } catch (e) {\n errors.push({\n component: comp.name,\n error: e instanceof Error ? e.message : String(e),\n });\n }\n }\n\n // Write output\n const outputPath = resolve(configDir, outputFile);\n await mkdir(dirname(outputPath), { recursive: true });\n const generatorVersion = await getGeneratorVersion();\n const buildInputs = components\n .map((component) => relative(configDir, component.sourcePath).split(\"\\\\\").join(\"/\"))\n .sort();\n const configPath = options.config ? resolve(process.cwd(), options.config) : findConfigFile(configDir);\n if (configPath) {\n buildInputs.push(relative(configDir, configPath).split(\"\\\\\").join(\"/\"));\n buildInputs.sort();\n }\n\n const output: CompiledFragmentsFile = {\n version: \"1.0.0\",\n generatedAt: new Date().toISOString(),\n generatorVersion,\n buildInputs,\n fragments,\n };\n\n await writeFile(outputPath, JSON.stringify(output, null, 2));\n\n const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);\n\n // Summary\n log(pc.dim(\"\\n────────────────────────────────────────\"));\n log(pc.green(`\\n✓ Generated fragments.json in ${elapsed}s`));\n log(pc.dim(` Output: ${relative(process.cwd(), outputPath)}`));\n log(pc.dim(` Components: ${Object.keys(fragments).length}`));\n log(pc.dim(` Props extracted: ${propsExtracted}`));\n log(pc.dim(` Usages found: ${usagesFound}`));\n log(pc.dim(` Relations inferred: ${allRelations.size}`));\n log(pc.dim(` Stories parsed: ${storiesParsed}`));\n\n if (warnings.length > 0) {\n log(pc.yellow(`\\n ${warnings.length} warning(s)`));\n if (options.verbose) {\n for (const w of warnings) {\n log(pc.dim(` ${w.component}: ${w.warning}`));\n }\n }\n }\n\n if (errors.length > 0) {\n log(pc.red(`\\n ${errors.length} error(s)`));\n for (const e of errors) {\n log(pc.dim(` ${e.component}: ${e.error}`));\n }\n }\n\n log();\n\n return {\n success: errors.length === 0,\n outputPath,\n componentCount: Object.keys(fragments).length,\n propsExtracted,\n usagesFound,\n relationsInferred: allRelations.size,\n storiesParsed,\n errors,\n warnings,\n };\n}\n\n/**\n * Generate a CompiledFragment from extracted data\n */\nfunction generateFragmentFromData(\n comp: DiscoveredComponent,\n configDir: string,\n props: Record<string, PropDefinition> | undefined,\n usageAnalysis: UsageAnalysis[\"components\"][string] | undefined,\n relations: ComponentRelation[] | undefined,\n storyFile: ParsedStoryFile | undefined\n): CompiledFragment {\n // Generate context for AI suggestions\n const context = generateComponentContext(\n comp.name,\n usageAnalysis,\n undefined, // No extracted docs yet\n storyFile,\n undefined, // No props extraction result (we have the converted props already)\n relations\n );\n\n // Generate enhancement suggestions from context\n const suggestions = generateEnhancementSuggestions(context);\n\n // Filter boilerplate from suggestions\n const when = filterBoilerplate(suggestions.suggestions.when);\n const whenNot = filterBoilerplate(suggestions.suggestions.whenNot);\n\n // Infer category from path\n const category = inferCategory(comp.relativePath);\n\n // Infer status from path\n const status = inferStatus(comp.relativePath);\n\n // Build variants from stories\n const variants: CompiledFragment[\"variants\"] = [];\n if (storyFile?.stories) {\n for (const story of storyFile.stories) {\n variants.push({\n name: story.name,\n description: story.description || `${story.displayName} variant`,\n code: story.code,\n });\n }\n }\n\n // Build relations\n const compiledRelations: CompiledFragment[\"relations\"] = [];\n if (relations && relations.length > 0) {\n for (const rel of relations.slice(0, 10)) {\n compiledRelations.push({\n component: rel.component,\n relationship: rel.relationship,\n note: `Appears together ${rel.frequency} times`,\n });\n }\n }\n\n // Generate description\n const description = generateDescription(comp.name, props, usageAnalysis);\n\n return {\n filePath: comp.relativePath,\n meta: {\n name: comp.name,\n description,\n category,\n status,\n },\n usage: {\n when: when.length > 0 ? when : [`Use ${comp.name} for its intended purpose`],\n whenNot: whenNot,\n },\n props: props || {},\n relations: compiledRelations.length > 0 ? compiledRelations : undefined,\n variants,\n _generated: {\n source: \"ai\",\n sourceFile: comp.relativePath,\n confidence: calculateConfidence(props, usageAnalysis, storyFile),\n timestamp: new Date().toISOString(),\n },\n };\n}\n\n/**\n * Infer category from file path\n */\nfunction inferCategory(relativePath: string): string {\n const parts = relativePath.toLowerCase().split(\"/\");\n\n // Common category patterns\n const categoryPatterns: Record<string, string[]> = {\n \"Actions\": [\"button\", \"action\", \"cta\"],\n \"Forms\": [\"form\", \"input\", \"select\", \"checkbox\", \"radio\", \"textarea\", \"field\"],\n \"Layout\": [\"layout\", \"container\", \"grid\", \"flex\", \"stack\", \"box\", \"divider\", \"spacer\"],\n \"Navigation\": [\"nav\", \"menu\", \"breadcrumb\", \"tab\", \"link\", \"pagination\"],\n \"Feedback\": [\"alert\", \"toast\", \"notification\", \"message\", \"badge\", \"indicator\", \"progress\", \"spinner\", \"loading\"],\n \"Data Display\": [\"table\", \"list\", \"card\", \"avatar\", \"stat\", \"timeline\", \"tree\"],\n \"Overlays\": [\"modal\", \"dialog\", \"drawer\", \"popover\", \"tooltip\", \"dropdown\"],\n \"Typography\": [\"text\", \"heading\", \"title\", \"label\", \"paragraph\"],\n \"Media\": [\"image\", \"video\", \"icon\", \"avatar\"],\n };\n\n for (const [category, patterns] of Object.entries(categoryPatterns)) {\n for (const pattern of patterns) {\n if (parts.some((part) => part.includes(pattern))) {\n return category;\n }\n }\n }\n\n // Check path structure for category\n const componentIdx = parts.findIndex((p) => p === \"components\");\n if (componentIdx !== -1 && parts.length > componentIdx + 1) {\n const categoryPart = parts[componentIdx + 1];\n return categoryPart.charAt(0).toUpperCase() + categoryPart.slice(1);\n }\n\n return \"Components\";\n}\n\n/**\n * Infer status from file path\n */\nfunction inferStatus(\n relativePath: string\n): \"stable\" | \"beta\" | \"experimental\" | \"deprecated\" {\n const lowerPath = relativePath.toLowerCase();\n\n if (lowerPath.includes(\"/experimental/\") || lowerPath.includes(\"/labs/\")) {\n return \"experimental\";\n }\n if (lowerPath.includes(\"/beta/\")) {\n return \"beta\";\n }\n if (lowerPath.includes(\"/deprecated/\") || lowerPath.includes(\"/legacy/\")) {\n return \"deprecated\";\n }\n\n return \"stable\";\n}\n\n/**\n * Generate component description\n */\nfunction generateDescription(\n name: string,\n props: Record<string, PropDefinition> | undefined,\n usageAnalysis: UsageAnalysis[\"components\"][string] | undefined\n): string {\n // Convert name to readable form\n const words = name\n .replace(/([A-Z])/g, \" $1\")\n .trim()\n .toLowerCase();\n\n // Check props for hints\n const hasOnClick = props && (\"onClick\" in props || \"onPress\" in props);\n const hasValue = props && (\"value\" in props || \"defaultValue\" in props);\n const hasChildren = props && \"children\" in props;\n const hasHref = props && \"href\" in props;\n\n // Check usage contexts\n const topContext = usageAnalysis?.contexts[0]?.type;\n\n if (hasHref) {\n return `Navigational ${words} for linking to other pages or resources`;\n }\n\n if (hasOnClick && !hasValue) {\n return `Interactive ${words} for triggering actions`;\n }\n\n if (hasValue) {\n return `Form ${words} for user input`;\n }\n\n if (topContext && topContext !== \"unknown\") {\n return `${words.charAt(0).toUpperCase() + words.slice(1)} commonly used in ${topContext} contexts`;\n }\n\n if (hasChildren) {\n return `Container ${words} for composing UI`;\n }\n\n return `${words.charAt(0).toUpperCase() + words.slice(1)} component`;\n}\n\n/**\n * Calculate confidence score based on available data\n */\nfunction calculateConfidence(\n props: Record<string, PropDefinition> | undefined,\n usageAnalysis: UsageAnalysis[\"components\"][string] | undefined,\n storyFile: ParsedStoryFile | undefined\n): number {\n let confidence = 0;\n\n // Props give +30 confidence\n if (props && Object.keys(props).length > 0) {\n confidence += 30;\n }\n\n // Usage data gives up to +40 confidence\n if (usageAnalysis) {\n if (usageAnalysis.totalUsages > 10) confidence += 40;\n else if (usageAnalysis.totalUsages > 5) confidence += 30;\n else if (usageAnalysis.totalUsages > 0) confidence += 20;\n }\n\n // Stories give +30 confidence\n if (storyFile && storyFile.stories.length > 0) {\n confidence += 30;\n }\n\n return Math.min(confidence, 100);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
import { createRequire as __banner_createRequire } from 'module'; const require = __banner_createRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
scanFile
|
|
4
|
+
} from "./chunk-7DZC4YEV.js";
|
|
5
|
+
|
|
6
|
+
// src/service/enhance/codebase-scanner.ts
|
|
7
|
+
import fg from "fast-glob";
|
|
8
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
9
|
+
import { relative, resolve, dirname as dirname2 } from "path";
|
|
10
|
+
|
|
11
|
+
// src/service/enhance/aggregator.ts
|
|
12
|
+
function aggregateComponentUsages(componentName, sourceFile, usages, imports) {
|
|
13
|
+
const uniqueFiles = new Set(usages.map((u) => u.filePath));
|
|
14
|
+
const patternMap = /* @__PURE__ */ new Map();
|
|
15
|
+
for (const usage of usages) {
|
|
16
|
+
const propKey = createPropKey(usage.props.static);
|
|
17
|
+
const existing = patternMap.get(propKey);
|
|
18
|
+
if (existing) {
|
|
19
|
+
existing.count++;
|
|
20
|
+
if (!existing.files.includes(usage.filePath)) {
|
|
21
|
+
existing.files.push(usage.filePath);
|
|
22
|
+
}
|
|
23
|
+
if (existing.sampleContexts.length < 3) {
|
|
24
|
+
existing.sampleContexts.push(usage.context);
|
|
25
|
+
}
|
|
26
|
+
} else {
|
|
27
|
+
patternMap.set(propKey, {
|
|
28
|
+
props: { ...usage.props.static },
|
|
29
|
+
count: 1,
|
|
30
|
+
files: [usage.filePath],
|
|
31
|
+
sampleContexts: [usage.context]
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const patterns = Array.from(patternMap.values()).sort(
|
|
36
|
+
(a, b) => b.count - a.count
|
|
37
|
+
);
|
|
38
|
+
const contextMap = /* @__PURE__ */ new Map();
|
|
39
|
+
for (const usage of usages) {
|
|
40
|
+
const context = classifyFileContext(usage.filePath, usage.context);
|
|
41
|
+
const existing = contextMap.get(context);
|
|
42
|
+
if (existing) {
|
|
43
|
+
existing.count++;
|
|
44
|
+
if (!existing.files.includes(usage.filePath)) {
|
|
45
|
+
existing.files.push(usage.filePath);
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
contextMap.set(context, { count: 1, files: [usage.filePath] });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const contexts = Array.from(contextMap.entries()).map(([type, data]) => ({ type, ...data })).sort((a, b) => b.count - a.count);
|
|
52
|
+
return {
|
|
53
|
+
name: componentName,
|
|
54
|
+
sourceFile,
|
|
55
|
+
totalUsages: usages.length,
|
|
56
|
+
uniqueFiles: uniqueFiles.size,
|
|
57
|
+
patterns,
|
|
58
|
+
contexts,
|
|
59
|
+
usages,
|
|
60
|
+
imports
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function aggregateAllUsages(usages, imports, componentSources) {
|
|
64
|
+
const usagesByComponent = /* @__PURE__ */ new Map();
|
|
65
|
+
for (const usage of usages) {
|
|
66
|
+
const existing = usagesByComponent.get(usage.componentName) ?? [];
|
|
67
|
+
existing.push(usage);
|
|
68
|
+
usagesByComponent.set(usage.componentName, existing);
|
|
69
|
+
}
|
|
70
|
+
const importsByComponent = /* @__PURE__ */ new Map();
|
|
71
|
+
for (const imp of imports) {
|
|
72
|
+
const existing = importsByComponent.get(imp.componentName) ?? [];
|
|
73
|
+
existing.push(imp);
|
|
74
|
+
importsByComponent.set(imp.componentName, existing);
|
|
75
|
+
}
|
|
76
|
+
const components = {};
|
|
77
|
+
const allComponentNames = /* @__PURE__ */ new Set([
|
|
78
|
+
...usagesByComponent.keys(),
|
|
79
|
+
...importsByComponent.keys()
|
|
80
|
+
]);
|
|
81
|
+
for (const componentName of allComponentNames) {
|
|
82
|
+
const componentUsages = usagesByComponent.get(componentName) ?? [];
|
|
83
|
+
const componentImports = importsByComponent.get(componentName) ?? [];
|
|
84
|
+
const sourceFile = componentSources.get(componentName) ?? "unknown";
|
|
85
|
+
components[componentName] = aggregateComponentUsages(
|
|
86
|
+
componentName,
|
|
87
|
+
sourceFile,
|
|
88
|
+
componentUsages,
|
|
89
|
+
componentImports
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
const allFiles = /* @__PURE__ */ new Set();
|
|
93
|
+
for (const usage of usages) {
|
|
94
|
+
allFiles.add(usage.filePath);
|
|
95
|
+
}
|
|
96
|
+
for (const imp of imports) {
|
|
97
|
+
allFiles.add(imp.filePath);
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
101
|
+
rootDir: "",
|
|
102
|
+
// Will be set by caller
|
|
103
|
+
totalFiles: allFiles.size,
|
|
104
|
+
totalComponents: Object.keys(components).length,
|
|
105
|
+
components,
|
|
106
|
+
errorFiles: []
|
|
107
|
+
// Will be populated by scanner
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function createPropKey(props) {
|
|
111
|
+
const entries = Object.entries(props).sort(([a], [b]) => a.localeCompare(b));
|
|
112
|
+
return entries.map(([k, v]) => `${k}=${JSON.stringify(v)}`).join("|");
|
|
113
|
+
}
|
|
114
|
+
function classifyFileContext(filePath, context) {
|
|
115
|
+
const lowerPath = filePath.toLowerCase();
|
|
116
|
+
const lowerContext = context.toLowerCase();
|
|
117
|
+
if (lowerPath.includes("/modal") || lowerPath.includes("modal.") || lowerContext.includes("<modal")) {
|
|
118
|
+
return "modal";
|
|
119
|
+
}
|
|
120
|
+
if (lowerPath.includes("/dialog") || lowerPath.includes("dialog.") || lowerContext.includes("<dialog")) {
|
|
121
|
+
return "dialog";
|
|
122
|
+
}
|
|
123
|
+
if (lowerPath.includes("/form") || lowerPath.includes("form.") || lowerContext.includes("<form") || lowerContext.includes("onsubmit")) {
|
|
124
|
+
return "form";
|
|
125
|
+
}
|
|
126
|
+
if (lowerPath.includes("/page") || lowerPath.includes("pages/") || lowerPath.includes("/routes/")) {
|
|
127
|
+
return "page";
|
|
128
|
+
}
|
|
129
|
+
if (lowerPath.includes("/layout") || lowerPath.includes("layout.") || lowerPath.includes("/layouts/")) {
|
|
130
|
+
return "layout";
|
|
131
|
+
}
|
|
132
|
+
if (lowerPath.includes("/card") || lowerPath.includes("card.") || lowerContext.includes("<card")) {
|
|
133
|
+
return "card";
|
|
134
|
+
}
|
|
135
|
+
if (lowerPath.includes("/table") || lowerPath.includes("table.") || lowerContext.includes("<table") || lowerContext.includes("<tr")) {
|
|
136
|
+
return "table";
|
|
137
|
+
}
|
|
138
|
+
if (lowerPath.includes("/list") || lowerPath.includes("list.") || lowerContext.includes("<ul") || lowerContext.includes("<ol") || lowerContext.includes("<li")) {
|
|
139
|
+
return "list";
|
|
140
|
+
}
|
|
141
|
+
if (lowerPath.includes("/nav") || lowerPath.includes("navigation") || lowerContext.includes("<nav")) {
|
|
142
|
+
return "navigation";
|
|
143
|
+
}
|
|
144
|
+
if (lowerPath.includes("/header") || lowerPath.includes("header.") || lowerContext.includes("<header")) {
|
|
145
|
+
return "header";
|
|
146
|
+
}
|
|
147
|
+
if (lowerPath.includes("/footer") || lowerPath.includes("footer.") || lowerContext.includes("<footer")) {
|
|
148
|
+
return "footer";
|
|
149
|
+
}
|
|
150
|
+
if (lowerPath.includes("/sidebar") || lowerPath.includes("sidebar.") || lowerContext.includes("<aside")) {
|
|
151
|
+
return "sidebar";
|
|
152
|
+
}
|
|
153
|
+
return "unknown";
|
|
154
|
+
}
|
|
155
|
+
function findCommonPropCombinations(usages, minOccurrences = 3) {
|
|
156
|
+
const combinations = /* @__PURE__ */ new Map();
|
|
157
|
+
for (const usage of usages) {
|
|
158
|
+
const allProps = [
|
|
159
|
+
...Object.keys(usage.props.static),
|
|
160
|
+
...usage.props.dynamic
|
|
161
|
+
].sort();
|
|
162
|
+
for (let size = 2; size <= Math.min(allProps.length, 4); size++) {
|
|
163
|
+
for (const combo of combinations_k(allProps, size)) {
|
|
164
|
+
const key = combo.join(",");
|
|
165
|
+
combinations.set(key, (combinations.get(key) ?? 0) + 1);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return Array.from(combinations.entries()).filter(([, count]) => count >= minOccurrences).map(([key, count]) => ({ props: key.split(","), count })).sort((a, b) => b.count - a.count);
|
|
170
|
+
}
|
|
171
|
+
function* combinations_k(arr, k) {
|
|
172
|
+
if (k === 0) {
|
|
173
|
+
yield [];
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (arr.length < k) return;
|
|
177
|
+
for (let i = 0; i <= arr.length - k; i++) {
|
|
178
|
+
for (const combo of combinations_k(arr.slice(i + 1), k - 1)) {
|
|
179
|
+
yield [arr[i], ...combo];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function inferRelations(componentName, analysis, allAnalyses) {
|
|
184
|
+
const relations = [];
|
|
185
|
+
const parentCounts = /* @__PURE__ */ new Map();
|
|
186
|
+
const siblingCounts = /* @__PURE__ */ new Map();
|
|
187
|
+
for (const usage of analysis.usages) {
|
|
188
|
+
if (usage.parentElement && usage.parentElement !== "div" && usage.parentElement !== "span") {
|
|
189
|
+
const parent = usage.parentElement;
|
|
190
|
+
const existing = parentCounts.get(parent);
|
|
191
|
+
if (existing) {
|
|
192
|
+
existing.count++;
|
|
193
|
+
if (!existing.files.includes(usage.filePath)) {
|
|
194
|
+
existing.files.push(usage.filePath);
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
parentCounts.set(parent, { count: 1, files: [usage.filePath] });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const filesWithThisComponent = new Set(analysis.usages.map((u) => u.filePath));
|
|
202
|
+
for (const [otherName, otherAnalysis] of Object.entries(allAnalyses)) {
|
|
203
|
+
if (otherName === componentName) continue;
|
|
204
|
+
let sharedFiles = 0;
|
|
205
|
+
const sharedFileList = [];
|
|
206
|
+
for (const usage of otherAnalysis.usages) {
|
|
207
|
+
if (filesWithThisComponent.has(usage.filePath)) {
|
|
208
|
+
if (!sharedFileList.includes(usage.filePath)) {
|
|
209
|
+
sharedFiles++;
|
|
210
|
+
sharedFileList.push(usage.filePath);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (sharedFiles >= 3) {
|
|
215
|
+
siblingCounts.set(otherName, {
|
|
216
|
+
count: sharedFiles,
|
|
217
|
+
files: sharedFileList.slice(0, 5)
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
for (const [parent, data] of parentCounts) {
|
|
222
|
+
if (data.count >= 2) {
|
|
223
|
+
relations.push({
|
|
224
|
+
component: parent,
|
|
225
|
+
relationship: "parent",
|
|
226
|
+
frequency: data.count,
|
|
227
|
+
sampleFiles: data.files.slice(0, 3)
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
for (const [sibling, data] of siblingCounts) {
|
|
232
|
+
relations.push({
|
|
233
|
+
component: sibling,
|
|
234
|
+
relationship: "sibling",
|
|
235
|
+
frequency: data.count,
|
|
236
|
+
sampleFiles: data.files.slice(0, 3)
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
relations.sort((a, b) => b.frequency - a.frequency);
|
|
240
|
+
return relations;
|
|
241
|
+
}
|
|
242
|
+
function inferAllRelations(analysis) {
|
|
243
|
+
const allRelations = /* @__PURE__ */ new Map();
|
|
244
|
+
for (const [componentName, componentAnalysis] of Object.entries(analysis.components)) {
|
|
245
|
+
const relations = inferRelations(componentName, componentAnalysis, analysis.components);
|
|
246
|
+
if (relations.length > 0) {
|
|
247
|
+
allRelations.set(componentName, relations);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return allRelations;
|
|
251
|
+
}
|
|
252
|
+
function summarizePatternsForPrompt(analysis, maxPatterns = 10, maxContexts = 5) {
|
|
253
|
+
const lines = [];
|
|
254
|
+
lines.push(`## ${analysis.name}`);
|
|
255
|
+
lines.push(`Total usages: ${analysis.totalUsages} across ${analysis.uniqueFiles} files`);
|
|
256
|
+
lines.push("");
|
|
257
|
+
lines.push("### Usage Patterns (by frequency)");
|
|
258
|
+
const topPatterns = analysis.patterns.slice(0, maxPatterns);
|
|
259
|
+
for (const pattern of topPatterns) {
|
|
260
|
+
const propsStr = Object.entries(pattern.props).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(", ");
|
|
261
|
+
lines.push(`- ${propsStr || "(no props)"}: ${pattern.count} usages in ${pattern.files.length} files`);
|
|
262
|
+
}
|
|
263
|
+
lines.push("");
|
|
264
|
+
lines.push("### File Contexts");
|
|
265
|
+
const topContexts = analysis.contexts.slice(0, maxContexts);
|
|
266
|
+
for (const ctx of topContexts) {
|
|
267
|
+
lines.push(`- ${ctx.type}: ${ctx.count} usages`);
|
|
268
|
+
}
|
|
269
|
+
lines.push("");
|
|
270
|
+
lines.push("### Sample Usages");
|
|
271
|
+
for (const pattern of topPatterns.slice(0, 3)) {
|
|
272
|
+
if (pattern.sampleContexts[0]) {
|
|
273
|
+
lines.push("```tsx");
|
|
274
|
+
lines.push(pattern.sampleContexts[0].trim());
|
|
275
|
+
lines.push("```");
|
|
276
|
+
lines.push("");
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return lines.join("\n");
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// src/service/enhance/cache.ts
|
|
283
|
+
import { readFile, writeFile } from "fs/promises";
|
|
284
|
+
import { existsSync } from "fs";
|
|
285
|
+
import { createHash } from "crypto";
|
|
286
|
+
import { join, dirname } from "path";
|
|
287
|
+
import { mkdir } from "fs/promises";
|
|
288
|
+
var CURRENT_CACHE_VERSION = 1;
|
|
289
|
+
var CACHE_FILENAME = "analysis-cache.json";
|
|
290
|
+
function getCachePath(rootDir) {
|
|
291
|
+
return join(rootDir, ".fragments", CACHE_FILENAME);
|
|
292
|
+
}
|
|
293
|
+
async function loadCache(rootDir) {
|
|
294
|
+
const cachePath = getCachePath(rootDir);
|
|
295
|
+
if (!existsSync(cachePath)) {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
const content = await readFile(cachePath, "utf-8");
|
|
300
|
+
const cache = JSON.parse(content);
|
|
301
|
+
if (cache.version !== CURRENT_CACHE_VERSION) {
|
|
302
|
+
console.warn(
|
|
303
|
+
`Cache version mismatch: expected ${CURRENT_CACHE_VERSION}, got ${cache.version}. Invalidating cache.`
|
|
304
|
+
);
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
if (cache.rootDir !== rootDir) {
|
|
308
|
+
console.warn(`Cache root mismatch. Invalidating cache.`);
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
return cache;
|
|
312
|
+
} catch (error) {
|
|
313
|
+
console.warn(`Failed to load cache: ${error.message}`);
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
async function saveCache(rootDir, cache) {
|
|
318
|
+
const cachePath = getCachePath(rootDir);
|
|
319
|
+
const cacheDir = dirname(cachePath);
|
|
320
|
+
if (!existsSync(cacheDir)) {
|
|
321
|
+
await mkdir(cacheDir, { recursive: true });
|
|
322
|
+
}
|
|
323
|
+
cache.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
324
|
+
await writeFile(cachePath, JSON.stringify(cache, null, 2));
|
|
325
|
+
}
|
|
326
|
+
function createEmptyCache(rootDir) {
|
|
327
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
328
|
+
return {
|
|
329
|
+
version: CURRENT_CACHE_VERSION,
|
|
330
|
+
createdAt: now,
|
|
331
|
+
updatedAt: now,
|
|
332
|
+
rootDir,
|
|
333
|
+
files: {}
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
function computeFileHash(content) {
|
|
337
|
+
return createHash("md5").update(content).digest("hex");
|
|
338
|
+
}
|
|
339
|
+
function getCachedFile(cache, filePath) {
|
|
340
|
+
return cache.files[filePath] ?? null;
|
|
341
|
+
}
|
|
342
|
+
function updateCacheFile(cache, filePath, hash, imports, usages) {
|
|
343
|
+
cache.files[filePath] = {
|
|
344
|
+
hash,
|
|
345
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
346
|
+
imports,
|
|
347
|
+
usages
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
function removeCacheFile(cache, filePath) {
|
|
351
|
+
delete cache.files[filePath];
|
|
352
|
+
}
|
|
353
|
+
async function detectFileChanges(cache, currentFiles, getFileHash) {
|
|
354
|
+
const changes = {
|
|
355
|
+
added: [],
|
|
356
|
+
modified: [],
|
|
357
|
+
deleted: [],
|
|
358
|
+
unchanged: []
|
|
359
|
+
};
|
|
360
|
+
const currentFileSet = new Set(currentFiles);
|
|
361
|
+
const cachedFileSet = new Set(Object.keys(cache.files));
|
|
362
|
+
for (const filePath of currentFiles) {
|
|
363
|
+
const entry = cache.files[filePath];
|
|
364
|
+
if (!entry) {
|
|
365
|
+
changes.added.push(filePath);
|
|
366
|
+
} else {
|
|
367
|
+
const currentHash = await getFileHash(filePath);
|
|
368
|
+
if (currentHash !== entry.hash) {
|
|
369
|
+
changes.modified.push(filePath);
|
|
370
|
+
} else {
|
|
371
|
+
changes.unchanged.push(filePath);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
for (const cachedFile of cachedFileSet) {
|
|
376
|
+
if (!currentFileSet.has(cachedFile)) {
|
|
377
|
+
changes.deleted.push(cachedFile);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return changes;
|
|
381
|
+
}
|
|
382
|
+
function getCacheStats(cache) {
|
|
383
|
+
let totalImports = 0;
|
|
384
|
+
let totalUsages = 0;
|
|
385
|
+
for (const entry of Object.values(cache.files)) {
|
|
386
|
+
totalImports += entry.imports.length;
|
|
387
|
+
totalUsages += entry.usages.length;
|
|
388
|
+
}
|
|
389
|
+
const ageMs = Date.now() - new Date(cache.updatedAt).getTime();
|
|
390
|
+
const ageMinutes = Math.round(ageMs / 6e4);
|
|
391
|
+
const cacheAge = ageMinutes < 60 ? `${ageMinutes} minutes ago` : ageMinutes < 1440 ? `${Math.round(ageMinutes / 60)} hours ago` : `${Math.round(ageMinutes / 1440)} days ago`;
|
|
392
|
+
return {
|
|
393
|
+
totalFiles: Object.keys(cache.files).length,
|
|
394
|
+
totalImports,
|
|
395
|
+
totalUsages,
|
|
396
|
+
cacheAge
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// src/service/enhance/codebase-scanner.ts
|
|
401
|
+
var DEFAULT_INCLUDE = [
|
|
402
|
+
"**/*.tsx",
|
|
403
|
+
"**/*.ts",
|
|
404
|
+
"**/*.jsx",
|
|
405
|
+
"**/*.js"
|
|
406
|
+
];
|
|
407
|
+
var DEFAULT_EXCLUDE = [
|
|
408
|
+
"**/node_modules/**",
|
|
409
|
+
"**/dist/**",
|
|
410
|
+
"**/build/**",
|
|
411
|
+
"**/.next/**",
|
|
412
|
+
"**/coverage/**",
|
|
413
|
+
"**/__tests__/**",
|
|
414
|
+
"**/*.test.*",
|
|
415
|
+
"**/*.spec.*",
|
|
416
|
+
"**/*.stories.*",
|
|
417
|
+
"**/*.fragment.*",
|
|
418
|
+
"**/storybook-static/**"
|
|
419
|
+
];
|
|
420
|
+
async function scanCodebase(options) {
|
|
421
|
+
const {
|
|
422
|
+
rootDir,
|
|
423
|
+
include = DEFAULT_INCLUDE,
|
|
424
|
+
exclude = DEFAULT_EXCLUDE,
|
|
425
|
+
componentNames,
|
|
426
|
+
useCache = true,
|
|
427
|
+
onProgress
|
|
428
|
+
} = options;
|
|
429
|
+
const absoluteRoot = resolve(rootDir);
|
|
430
|
+
let cache = null;
|
|
431
|
+
if (useCache) {
|
|
432
|
+
cache = await loadCache(absoluteRoot);
|
|
433
|
+
}
|
|
434
|
+
if (!cache) {
|
|
435
|
+
cache = createEmptyCache(absoluteRoot);
|
|
436
|
+
}
|
|
437
|
+
onProgress?.({
|
|
438
|
+
current: 0,
|
|
439
|
+
total: 0,
|
|
440
|
+
currentFile: "",
|
|
441
|
+
phase: "discovering"
|
|
442
|
+
});
|
|
443
|
+
const files = await fg(include, {
|
|
444
|
+
cwd: absoluteRoot,
|
|
445
|
+
ignore: exclude,
|
|
446
|
+
absolute: true,
|
|
447
|
+
onlyFiles: true
|
|
448
|
+
});
|
|
449
|
+
const totalFiles = files.length;
|
|
450
|
+
const trackedComponents = componentNames ? new Set(componentNames) : void 0;
|
|
451
|
+
const allImports = [];
|
|
452
|
+
const allUsages = [];
|
|
453
|
+
const errorFiles = [];
|
|
454
|
+
const componentSources = /* @__PURE__ */ new Map();
|
|
455
|
+
for (let i = 0; i < files.length; i++) {
|
|
456
|
+
const filePath = files[i];
|
|
457
|
+
const relativePath = relative(absoluteRoot, filePath);
|
|
458
|
+
onProgress?.({
|
|
459
|
+
current: i + 1,
|
|
460
|
+
total: totalFiles,
|
|
461
|
+
currentFile: relativePath,
|
|
462
|
+
phase: "scanning"
|
|
463
|
+
});
|
|
464
|
+
try {
|
|
465
|
+
const content = await readFile2(filePath, "utf-8");
|
|
466
|
+
const fileHash = computeFileHash(content);
|
|
467
|
+
const cachedEntry = getCachedFile(cache, filePath);
|
|
468
|
+
if (cachedEntry && cachedEntry.hash === fileHash) {
|
|
469
|
+
allImports.push(...cachedEntry.imports);
|
|
470
|
+
allUsages.push(...cachedEntry.usages);
|
|
471
|
+
for (const imp of cachedEntry.imports) {
|
|
472
|
+
if (!componentSources.has(imp.componentName)) {
|
|
473
|
+
const sourceFile = resolveImportSource(filePath, imp.source);
|
|
474
|
+
if (sourceFile) {
|
|
475
|
+
componentSources.set(imp.componentName, sourceFile);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
const { imports, usages } = await scanFile(filePath, trackedComponents);
|
|
482
|
+
updateCacheFile(cache, filePath, fileHash, imports, usages);
|
|
483
|
+
allImports.push(...imports);
|
|
484
|
+
allUsages.push(...usages);
|
|
485
|
+
for (const imp of imports) {
|
|
486
|
+
if (!componentSources.has(imp.componentName)) {
|
|
487
|
+
const sourceFile = resolveImportSource(filePath, imp.source);
|
|
488
|
+
if (sourceFile) {
|
|
489
|
+
componentSources.set(imp.componentName, sourceFile);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
} catch (error) {
|
|
494
|
+
errorFiles.push(relativePath);
|
|
495
|
+
console.warn(`Error scanning ${relativePath}:`, error.message);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (useCache) {
|
|
499
|
+
await saveCache(absoluteRoot, cache);
|
|
500
|
+
}
|
|
501
|
+
onProgress?.({
|
|
502
|
+
current: totalFiles,
|
|
503
|
+
total: totalFiles,
|
|
504
|
+
currentFile: "",
|
|
505
|
+
phase: "aggregating"
|
|
506
|
+
});
|
|
507
|
+
const analysis = aggregateAllUsages(allUsages, allImports, componentSources);
|
|
508
|
+
analysis.rootDir = absoluteRoot;
|
|
509
|
+
analysis.totalFiles = totalFiles;
|
|
510
|
+
analysis.errorFiles = errorFiles;
|
|
511
|
+
return analysis;
|
|
512
|
+
}
|
|
513
|
+
async function incrementalScan(rootDir, changes, existingCache, onProgress) {
|
|
514
|
+
const absoluteRoot = resolve(rootDir);
|
|
515
|
+
const cache = { ...existingCache, files: { ...existingCache.files } };
|
|
516
|
+
for (const filePath of changes.deleted) {
|
|
517
|
+
removeCacheFile(cache, filePath);
|
|
518
|
+
}
|
|
519
|
+
const filesToScan = [...changes.added, ...changes.modified];
|
|
520
|
+
const totalFiles = filesToScan.length;
|
|
521
|
+
const allImports = [];
|
|
522
|
+
const allUsages = [];
|
|
523
|
+
const errorFiles = [];
|
|
524
|
+
const componentSources = /* @__PURE__ */ new Map();
|
|
525
|
+
for (const filePath of changes.unchanged) {
|
|
526
|
+
const cachedEntry = getCachedFile(cache, filePath);
|
|
527
|
+
if (cachedEntry) {
|
|
528
|
+
allImports.push(...cachedEntry.imports);
|
|
529
|
+
allUsages.push(...cachedEntry.usages);
|
|
530
|
+
for (const imp of cachedEntry.imports) {
|
|
531
|
+
if (!componentSources.has(imp.componentName)) {
|
|
532
|
+
const sourceFile = resolveImportSource(filePath, imp.source);
|
|
533
|
+
if (sourceFile) {
|
|
534
|
+
componentSources.set(imp.componentName, sourceFile);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
for (let i = 0; i < filesToScan.length; i++) {
|
|
541
|
+
const filePath = filesToScan[i];
|
|
542
|
+
const relativePath = relative(absoluteRoot, filePath);
|
|
543
|
+
onProgress?.({
|
|
544
|
+
current: i + 1,
|
|
545
|
+
total: totalFiles,
|
|
546
|
+
currentFile: relativePath,
|
|
547
|
+
phase: "scanning"
|
|
548
|
+
});
|
|
549
|
+
try {
|
|
550
|
+
const content = await readFile2(filePath, "utf-8");
|
|
551
|
+
const fileHash = computeFileHash(content);
|
|
552
|
+
const { imports, usages } = await scanFile(filePath);
|
|
553
|
+
updateCacheFile(cache, filePath, fileHash, imports, usages);
|
|
554
|
+
allImports.push(...imports);
|
|
555
|
+
allUsages.push(...usages);
|
|
556
|
+
for (const imp of imports) {
|
|
557
|
+
if (!componentSources.has(imp.componentName)) {
|
|
558
|
+
const sourceFile = resolveImportSource(filePath, imp.source);
|
|
559
|
+
if (sourceFile) {
|
|
560
|
+
componentSources.set(imp.componentName, sourceFile);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
} catch (error) {
|
|
565
|
+
errorFiles.push(relativePath);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
await saveCache(absoluteRoot, cache);
|
|
569
|
+
const analysis = aggregateAllUsages(allUsages, allImports, componentSources);
|
|
570
|
+
analysis.rootDir = absoluteRoot;
|
|
571
|
+
analysis.totalFiles = changes.unchanged.length + filesToScan.length;
|
|
572
|
+
analysis.errorFiles = errorFiles;
|
|
573
|
+
return { analysis, cache };
|
|
574
|
+
}
|
|
575
|
+
function resolveImportSource(importingFile, source) {
|
|
576
|
+
if (!source.startsWith(".") && !source.startsWith("/")) {
|
|
577
|
+
return null;
|
|
578
|
+
}
|
|
579
|
+
const importDir = dirname2(importingFile);
|
|
580
|
+
const extensions = ["", ".tsx", ".ts", ".jsx", ".js", "/index.tsx", "/index.ts"];
|
|
581
|
+
for (const ext of extensions) {
|
|
582
|
+
const fullPath = resolve(importDir, source + ext);
|
|
583
|
+
if (ext === "" && source.endsWith(".tsx")) {
|
|
584
|
+
return fullPath;
|
|
585
|
+
}
|
|
586
|
+
if (ext) {
|
|
587
|
+
return resolve(importDir, source) + ext;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return resolve(importDir, source);
|
|
591
|
+
}
|
|
592
|
+
function getScanStats(analysis) {
|
|
593
|
+
const totalUsages = Object.values(analysis.components).reduce(
|
|
594
|
+
(sum, c) => sum + c.totalUsages,
|
|
595
|
+
0
|
|
596
|
+
);
|
|
597
|
+
const topComponents = Object.values(analysis.components).map((c) => ({ name: c.name, usages: c.totalUsages })).sort((a, b) => b.usages - a.usages).slice(0, 10);
|
|
598
|
+
return {
|
|
599
|
+
totalFiles: analysis.totalFiles,
|
|
600
|
+
totalComponents: analysis.totalComponents,
|
|
601
|
+
totalUsages,
|
|
602
|
+
topComponents
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
async function hasCachedAnalysis(rootDir) {
|
|
606
|
+
const cache = await loadCache(resolve(rootDir));
|
|
607
|
+
if (!cache) return false;
|
|
608
|
+
const stats = getCacheStats(cache);
|
|
609
|
+
return stats.totalFiles > 0;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
export {
|
|
613
|
+
aggregateComponentUsages,
|
|
614
|
+
aggregateAllUsages,
|
|
615
|
+
findCommonPropCombinations,
|
|
616
|
+
inferRelations,
|
|
617
|
+
inferAllRelations,
|
|
618
|
+
summarizePatternsForPrompt,
|
|
619
|
+
loadCache,
|
|
620
|
+
saveCache,
|
|
621
|
+
createEmptyCache,
|
|
622
|
+
computeFileHash,
|
|
623
|
+
detectFileChanges,
|
|
624
|
+
getCacheStats,
|
|
625
|
+
scanCodebase,
|
|
626
|
+
incrementalScan,
|
|
627
|
+
getScanStats,
|
|
628
|
+
hasCachedAnalysis
|
|
629
|
+
};
|
|
630
|
+
//# sourceMappingURL=chunk-QCN35LJU.js.map
|