@schemyx/mcp 0.1.1 → 0.1.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.
@@ -4,7 +4,15 @@ exports.createBundle = createBundle;
4
4
  exports.addEntry = addEntry;
5
5
  exports.hasStyleSurface = hasStyleSurface;
6
6
  exports.isBackendRouteFile = isBackendRouteFile;
7
+ exports.hasDatabaseSurface = hasDatabaseSurface;
8
+ exports.addDatabaseSurfaceEntries = addDatabaseSurfaceEntries;
9
+ exports.databaseSurfaceText = databaseSurfaceText;
10
+ exports.databaseSurfaceConfidence = databaseSurfaceConfidence;
11
+ exports.databaseAccessDependencies = databaseAccessDependencies;
12
+ exports.databaseAccessEdgeType = databaseAccessEdgeType;
13
+ exports.databaseEnumKey = databaseEnumKey;
7
14
  exports.backendFileContext = backendFileContext;
15
+ exports.databaseFileContext = databaseFileContext;
8
16
  exports.findEndpointContract = findEndpointContract;
9
17
  exports.createBackendEndpointFlow = createBackendEndpointFlow;
10
18
  exports.backendCallDependencies = backendCallDependencies;
@@ -15,8 +23,99 @@ exports.sourceInfo = sourceInfo;
15
23
  exports.toGraphNode = toGraphNode;
16
24
  exports.toGraphEdge = toGraphEdge;
17
25
  exports.addRenderEdges = addRenderEdges;
26
+ exports.inferStyleDefinitionVariantRoles = inferStyleDefinitionVariantRoles;
27
+ exports.inferActionVariantRole = inferActionVariantRole;
28
+ exports.createStyleDefinitionGuidance = createStyleDefinitionGuidance;
29
+ exports.summarizeUiElementPatterns = summarizeUiElementPatterns;
30
+ exports.uiElementPatternUsage = uiElementPatternUsage;
31
+ exports.rootUiElementsForKind = rootUiElementsForKind;
32
+ exports.isLayoutSafetyProfile = isLayoutSafetyProfile;
33
+ exports.isResponsiveProfile = isResponsiveProfile;
34
+ exports.isScaleProfile = isScaleProfile;
35
+ exports.isSemanticProfile = isSemanticProfile;
36
+ exports.createStyleResolutionContext = createStyleResolutionContext;
37
+ exports.detectProjectStyleSystems = detectProjectStyleSystems;
38
+ exports.classNamesFromCssSelector = classNamesFromCssSelector;
39
+ exports.unescapeCssIdentifier = unescapeCssIdentifier;
40
+ exports.cssSelectorStatePrefixes = cssSelectorStatePrefixes;
41
+ exports.cssRuleMediaContext = cssRuleMediaContext;
42
+ exports.normalizeCssDeclarationValue = normalizeCssDeclarationValue;
43
+ exports.cssDeclarationImportant = cssDeclarationImportant;
44
+ exports.estimateSelectorSpecificity = estimateSelectorSpecificity;
45
+ exports.resolveCssValueTokens = resolveCssValueTokens;
46
+ exports.resolveUiClassDeclarations = resolveUiClassDeclarations;
47
+ exports.tailwindDeclarationsForClass = tailwindDeclarationsForClass;
48
+ exports.tailwindAlignmentDeclaration = tailwindAlignmentDeclaration;
49
+ exports.tailwindSpacingDeclaration = tailwindSpacingDeclaration;
50
+ exports.tailwindSizingDeclaration = tailwindSizingDeclaration;
51
+ exports.tailwindTypographyDeclaration = tailwindTypographyDeclaration;
52
+ exports.tailwindBorderDeclaration = tailwindBorderDeclaration;
53
+ exports.tailwindColorDeclaration = tailwindColorDeclaration;
54
+ exports.tailwindColorValue = tailwindColorValue;
55
+ exports.cssTokenValue = cssTokenValue;
56
+ exports.colorKeywordValue = colorKeywordValue;
57
+ exports.opacityToPercent = opacityToPercent;
58
+ exports.tailwindScaleValue = tailwindScaleValue;
59
+ exports.tailwindNamedSizeValue = tailwindNamedSizeValue;
60
+ exports.arbitraryTokenValue = arbitraryTokenValue;
61
+ exports.arbitraryValue = arbitraryValue;
62
+ exports.looksLikeCssLength = looksLikeCssLength;
63
+ exports.tailwindShadowValue = tailwindShadowValue;
64
+ exports.bootstrapDeclarationsForClass = bootstrapDeclarationsForClass;
65
+ exports.isBootstrapClass = isBootstrapClass;
66
+ exports.bootstrapResponsivePrefixes = bootstrapResponsivePrefixes;
67
+ exports.stripBootstrapResponsiveInfix = stripBootstrapResponsiveInfix;
68
+ exports.bootstrapComponentDeclarations = bootstrapComponentDeclarations;
69
+ exports.bootstrapSpacingDeclarations = bootstrapSpacingDeclarations;
70
+ exports.bootstrapDisplayDeclarations = bootstrapDisplayDeclarations;
71
+ exports.bootstrapFlexDeclarations = bootstrapFlexDeclarations;
72
+ exports.bootstrapSizingDeclarations = bootstrapSizingDeclarations;
73
+ exports.bootstrapTypographyDeclarations = bootstrapTypographyDeclarations;
74
+ exports.bootstrapColorDeclarations = bootstrapColorDeclarations;
75
+ exports.bootstrapBorderDeclarations = bootstrapBorderDeclarations;
76
+ exports.bootstrapGridDeclarations = bootstrapGridDeclarations;
77
+ exports.categorizeResolvedStyleDeclarations = categorizeResolvedStyleDeclarations;
78
+ exports.flattenResolvedStyleCategories = flattenResolvedStyleCategories;
79
+ exports.createStyleSystemEvidence = createStyleSystemEvidence;
80
+ exports.isLikelyTailwindClass = isLikelyTailwindClass;
81
+ exports.resolvedStyleCategory = resolvedStyleCategory;
82
+ exports.mergeResolvedStyleCategories = mergeResolvedStyleCategories;
83
+ exports.classBucketRecord = classBucketRecord;
84
+ exports.resolvedStyleRecord = resolvedStyleRecord;
85
+ exports.createUiContract = createUiContract;
86
+ exports.createUiContractForElement = createUiContractForElement;
87
+ exports.createUiContractForChild = createUiContractForChild;
88
+ exports.createUiPatternContract = createUiPatternContract;
89
+ exports.createUiRelationships = createUiRelationships;
90
+ exports.createUiResponsiveVariants = createUiResponsiveVariants;
91
+ exports.createUiInteractionVariants = createUiInteractionVariants;
92
+ exports.uiContractClassBucket = uiContractClassBucket;
93
+ exports.mergeUiContractClassBuckets = mergeUiContractClassBuckets;
94
+ exports.filterUiContractClasses = filterUiContractClasses;
95
+ exports.isResponsivePrefix = isResponsivePrefix;
96
+ exports.responsivePrefixesForClass = responsivePrefixesForClass;
97
+ exports.isInteractionPrefix = isInteractionPrefix;
98
+ exports.omitEmptyRecord = omitEmptyRecord;
99
+ exports.mergeLayoutSafetyProfiles = mergeLayoutSafetyProfiles;
100
+ exports.mergeResponsiveProfiles = mergeResponsiveProfiles;
101
+ exports.mergeScaleProfiles = mergeScaleProfiles;
102
+ exports.mergeSemanticProfiles = mergeSemanticProfiles;
103
+ exports.summarizePatternLayoutSafety = summarizePatternLayoutSafety;
104
+ exports.summarizePatternResponsiveProfile = summarizePatternResponsiveProfile;
105
+ exports.summarizePatternScaleProfile = summarizePatternScaleProfile;
106
+ exports.summarizePatternSemanticProfile = summarizePatternSemanticProfile;
107
+ exports.summarizePatternRoleSignatures = summarizePatternRoleSignatures;
108
+ exports.summarizeParentContexts = summarizeParentContexts;
109
+ exports.summarizeElementChildContract = summarizeElementChildContract;
110
+ exports.summarizePatternChildStructure = summarizePatternChildStructure;
111
+ exports.summarizeCompoundStructure = summarizeCompoundStructure;
112
+ exports.createUiGenerationConstraints = createUiGenerationConstraints;
113
+ exports.createUiGenerationGuidance = createUiGenerationGuidance;
114
+ exports.summarizeActionUsages = summarizeActionUsages;
115
+ exports.actionVariantNamesForElement = actionVariantNamesForElement;
18
116
  exports.addUiPatternRules = addUiPatternRules;
19
117
  exports.addUiCompositionRules = addUiCompositionRules;
118
+ exports.addGlobalUiContractRules = addGlobalUiContractRules;
20
119
  exports.addConnectedPatternRules = addConnectedPatternRules;
21
120
  exports.buildConnectedPatternClusters = buildConnectedPatternClusters;
22
121
  exports.createFrontendPatternInstances = createFrontendPatternInstances;
@@ -26,7 +125,17 @@ exports.isConnectedUiPatternRoot = isConnectedUiPatternRoot;
26
125
  exports.normalizeConnectedChildren = normalizeConnectedChildren;
27
126
  exports.withoutConnectedChildSignature = withoutConnectedChildSignature;
28
127
  exports.connectedChildKind = connectedChildKind;
128
+ exports.isRecordValue = isRecordValue;
129
+ exports.isRoleSignatureRecord = isRoleSignatureRecord;
130
+ exports.summarizeConnectedLayoutSafety = summarizeConnectedLayoutSafety;
131
+ exports.summarizeConnectedResponsiveProfile = summarizeConnectedResponsiveProfile;
132
+ exports.summarizeConnectedScaleProfile = summarizeConnectedScaleProfile;
133
+ exports.summarizeConnectedSemanticProfile = summarizeConnectedSemanticProfile;
134
+ exports.summarizeConnectedRoleSignatures = summarizeConnectedRoleSignatures;
135
+ exports.summarizeConnectedCompoundStructure = summarizeConnectedCompoundStructure;
136
+ exports.summarizeConnectedParentContexts = summarizeConnectedParentContexts;
29
137
  exports.summarizeConnectedChildren = summarizeConnectedChildren;
138
+ exports.mergeChildVariants = mergeChildVariants;
30
139
  exports.compactConnectedPatternInstance = compactConnectedPatternInstance;
31
140
  exports.scoreConnectedPatternGroup = scoreConnectedPatternGroup;
32
141
  exports.connectedPatternConfidence = connectedPatternConfidence;
@@ -43,9 +152,11 @@ exports.frontendPatternWeight = frontendPatternWeight;
43
152
  exports.backendPatternWeight = backendPatternWeight;
44
153
  exports.hasBackendPatternSurface = hasBackendPatternSurface;
45
154
  exports.backendPatternConcept = backendPatternConcept;
155
+ exports.databaseFieldShapeInputs = databaseFieldShapeInputs;
46
156
  exports.summarizeModelFieldShape = summarizeModelFieldShape;
47
157
  exports.createConnectedPatternGuidance = createConnectedPatternGuidance;
48
158
  exports.connectedPatternSlug = connectedPatternSlug;
159
+ exports.roleSignatureKey = roleSignatureKey;
49
160
  exports.classSignature = classSignature;
50
161
  exports.normalizeLayoutRole = normalizeLayoutRole;
51
162
  exports.normalizeTagForSignature = normalizeTagForSignature;
@@ -54,13 +165,16 @@ exports.labelSignature = labelSignature;
54
165
  exports.addImportEdges = addImportEdges;
55
166
  exports.addApiCallEdges = addApiCallEdges;
56
167
  exports.addStyleUsageEdges = addStyleUsageEdges;
168
+ exports.addDatabaseUsageEdges = addDatabaseUsageEdges;
57
169
  exports.addModelUsageEdges = addModelUsageEdges;
170
+ exports.addDatabaseReviewItems = addDatabaseReviewItems;
58
171
  exports.addConceptClusters = addConceptClusters;
59
172
  exports.applyUsedBy = applyUsedBy;
60
173
  exports.detectProjectFacts = detectProjectFacts;
61
174
  exports.detectFrameworkForFile = detectFrameworkForFile;
62
175
  const backend_1 = require("./backend");
63
176
  const constants_1 = require("./constants");
177
+ const database_1 = require("./database");
64
178
  const extractors_1 = require("./extractors");
65
179
  const utils_1 = require("./utils");
66
180
  function createBundle(rootPath, projectType, files) {
@@ -69,6 +183,7 @@ function createBundle(rootPath, projectType, files) {
69
183
  const nodes = [];
70
184
  const edges = [];
71
185
  const reviewItems = [];
186
+ const styleResolutionContext = createStyleResolutionContext(files);
72
187
  for (const file of files) {
73
188
  const fileSource = sourceInfo('core.files', 'high', [`path:${file.relPath}`], file);
74
189
  addEntry(entries, usedKeys, {
@@ -112,8 +227,12 @@ function createBundle(rootPath, projectType, files) {
112
227
  middleware: file.middleware,
113
228
  jobs: file.jobs,
114
229
  events: file.events,
115
- uiElements: file.uiElements.slice(0, 40),
230
+ uiElements: file.uiElements.slice(0, 80),
231
+ uiContracts: file.uiElements
232
+ .slice(0, 80)
233
+ .map((element) => createUiContractForElement(element, file, 'high', styleResolutionContext)),
116
234
  ...backendFileContext(file),
235
+ ...databaseFileContext(file),
117
236
  size: file.size,
118
237
  sourceHash: file.sourceHash,
119
238
  source: fileSource,
@@ -141,7 +260,7 @@ function createBundle(rootPath, projectType, files) {
141
260
  nodes.push(toGraphNode(dependencyKey, 'package', dependency, file.relPath, file, 'core.dependencies'));
142
261
  edges.push(toGraphEdge(file.key, dependencyKey, 'depends_on', 'core.dependencies', file));
143
262
  }
144
- for (const component of file.components) {
263
+ for (const component of (0, extractors_1.isTestFile)(file.relPath) ? [] : file.components) {
145
264
  const componentKey = (0, utils_1.toRegistryKey)('component', component);
146
265
  const styleKey = (0, utils_1.toRegistryKey)('style', file.relPath.replace(/\.[^.]+$/, ''));
147
266
  const compositionRuleKey = (0, utils_1.toRegistryKey)('rule', `ui.composition.${file.relPath.replace(/\.[^.]+$/, '')}`);
@@ -171,7 +290,10 @@ function createBundle(rootPath, projectType, files) {
171
290
  classExpressions: file.classExpressions.slice(0, 80),
172
291
  componentStyleDefinitions: file.componentStyleDefinitions.slice(0, 20),
173
292
  themeTokens: file.themeTokens.slice(0, 120),
174
- uiElements: file.uiElements.slice(0, 20),
293
+ uiElements: file.uiElements.slice(0, 80),
294
+ uiContracts: file.uiElements
295
+ .slice(0, 80)
296
+ .map((element) => createUiContractForElement(element, file, 'high', styleResolutionContext)),
175
297
  apiCalls: file.apiCalls,
176
298
  authHints: file.authHints,
177
299
  kind: 'component',
@@ -208,7 +330,7 @@ function createBundle(rootPath, projectType, files) {
208
330
  nodes.push(toGraphNode(hookKey, 'hook', hook, file.relPath, file, 'react'));
209
331
  edges.push(toGraphEdge(file.key, hookKey, 'exports', 'typescript', file));
210
332
  }
211
- for (const route of file.routes) {
333
+ for (const route of (0, extractors_1.isTestFile)(file.relPath) ? [] : file.routes) {
212
334
  const routeKey = (0, utils_1.toRegistryKey)('route', route);
213
335
  const routeLayer = isBackendRouteFile(file) ? 'backend' : 'frontend';
214
336
  const styleKey = (0, utils_1.toRegistryKey)('style', file.relPath.replace(/\.[^.]+$/, ''));
@@ -233,7 +355,10 @@ function createBundle(rootPath, projectType, files) {
233
355
  route,
234
356
  path: file.relPath,
235
357
  kind: file.kind,
236
- uiElements: file.uiElements.slice(0, 40),
358
+ uiElements: file.uiElements.slice(0, 80),
359
+ uiContracts: file.uiElements
360
+ .slice(0, 80)
361
+ .map((element) => createUiContractForElement(element, file, 'high', styleResolutionContext)),
237
362
  classReferences: file.classReferences.slice(0, 80),
238
363
  tailwindUtilities: file.tailwindUtilities.slice(0, 80),
239
364
  classExpressions: file.classExpressions.slice(0, 80),
@@ -417,31 +542,76 @@ function createBundle(rootPath, projectType, files) {
417
542
  nodes.push(toGraphNode(moduleKey, 'module', moduleName, file.relPath, file, (0, extractors_1.moduleExtractorForFile)(file)));
418
543
  edges.push(toGraphEdge(file.key, moduleKey, 'exports', (0, extractors_1.moduleExtractorForFile)(file), file));
419
544
  }
545
+ if (hasDatabaseSurface(file)) {
546
+ addDatabaseSurfaceEntries(entries, usedKeys, nodes, edges, file);
547
+ }
420
548
  for (const model of file.models) {
421
549
  const modelKey = (0, utils_1.toRegistryKey)('model', model);
550
+ const databaseModel = file.databaseModels.find((candidate) => candidate.name === model);
551
+ const modelRelations = databaseModel?.relations ?? [];
552
+ const enumRefs = databaseModel?.enumRefs ?? [];
553
+ const modelDependencies = (0, utils_1.unique)([
554
+ file.key,
555
+ ...modelRelations.map((relation) => (0, utils_1.toRegistryKey)('model', relation.toModel)),
556
+ ...enumRefs.map((enumName) => (0, utils_1.toRegistryKey)('database', `enum.${enumName}`)),
557
+ ]);
422
558
  addEntry(entries, usedKeys, {
423
559
  key: modelKey,
424
560
  group: 'model',
425
- summary: `${model} data model in ${file.relPath}.`,
426
- tags: ['model', 'schema'],
427
- dependencies: [file.key],
561
+ summary: `${model} ${databaseModel?.kind ?? 'data model'} in ${file.relPath}.`,
562
+ tags: (0, utils_1.unique)([
563
+ 'model',
564
+ 'schema',
565
+ 'database',
566
+ databaseModel?.kind ?? '',
567
+ databaseModel?.provider ?? '',
568
+ databaseModel?.tableName ?? '',
569
+ ...enumRefs,
570
+ ...modelRelations.map((relation) => relation.toModel),
571
+ ]),
572
+ dependencies: modelDependencies,
428
573
  usedBy: [],
429
- confidence: 'high',
574
+ confidence: databaseModel?.confidence ?? 'high',
430
575
  status: 'inferred',
431
576
  path: file.relPath,
432
577
  value: {
433
578
  name: model,
434
579
  path: file.relPath,
435
- fields: file.modelFields[model] ?? [],
436
- kind: 'model',
580
+ fields: file.modelFields[model] ?? databaseModel?.fieldNames ?? [],
581
+ fieldDetails: databaseModel?.fields ?? [],
582
+ tableName: databaseModel?.tableName,
583
+ modelKind: databaseModel?.kind,
584
+ provider: databaseModel?.provider,
585
+ primaryKey: databaseModel?.primaryKey ?? [],
586
+ uniqueFields: databaseModel?.uniqueFields ?? [],
587
+ indexes: databaseModel?.indexes ?? [],
588
+ relations: modelRelations,
589
+ enumRefs,
590
+ databaseContract: databaseModel,
591
+ kind: databaseModel?.kind === 'table' ? 'database-table' : 'model',
437
592
  sourceHash: file.sourceHash,
438
- source: sourceInfo((0, extractors_1.modelExtractorForFile)(file), 'high', [`model:${model}`], file),
593
+ source: sourceInfo(databaseModel ? `database.${databaseModel.provider}` : (0, extractors_1.modelExtractorForFile)(file), databaseModel?.confidence ?? 'high', [
594
+ `model:${model}`,
595
+ ...(databaseModel?.tableName ? [`table:${databaseModel.tableName}`] : []),
596
+ ], file),
439
597
  },
440
598
  });
441
- nodes.push(toGraphNode(modelKey, 'model', model, file.relPath, file, (0, extractors_1.modelExtractorForFile)(file)));
442
- edges.push(toGraphEdge(file.key, modelKey, 'contains', (0, extractors_1.modelExtractorForFile)(file), file));
599
+ nodes.push(toGraphNode(modelKey, databaseModel?.kind === 'table' ? 'database-table' : 'model', model, file.relPath, file, databaseModel ? `database.${databaseModel.provider}` : (0, extractors_1.modelExtractorForFile)(file), databaseModel
600
+ ? {
601
+ database: {
602
+ provider: databaseModel.provider,
603
+ tableName: databaseModel.tableName,
604
+ fields: databaseModel.fieldNames,
605
+ relations: databaseModel.relations.map((relation) => relation.toModel),
606
+ },
607
+ }
608
+ : undefined));
609
+ edges.push(toGraphEdge(file.key, modelKey, 'contains', databaseModel ? `database.${databaseModel.provider}` : (0, extractors_1.modelExtractorForFile)(file), file, databaseModel?.confidence ?? 'high'));
610
+ for (const relation of modelRelations) {
611
+ edges.push(toGraphEdge(modelKey, (0, utils_1.toRegistryKey)('model', relation.toModel), 'relates_to', databaseModel ? `database.${databaseModel.provider}.relations` : 'database.relations', file, relation.confidence));
612
+ }
443
613
  }
444
- if (hasStyleSurface(file)) {
614
+ if (hasStyleSurface(file) && !(0, extractors_1.isTestFile)(file.relPath)) {
445
615
  const styleKey = (0, utils_1.toRegistryKey)('style', file.relPath.replace(/\.[^.]+$/, ''));
446
616
  addEntry(entries, usedKeys, {
447
617
  key: styleKey,
@@ -469,6 +639,9 @@ function createBundle(rootPath, projectType, files) {
469
639
  themeTokens: file.themeTokens.slice(0, 240),
470
640
  inlineStyles: file.inlineStyles.slice(0, 100),
471
641
  uiElements: file.uiElements.slice(0, 80),
642
+ uiContracts: file.uiElements
643
+ .slice(0, 80)
644
+ .map((element) => createUiContractForElement(element, file, (0, extractors_1.styleConfidenceForFile)(file), styleResolutionContext)),
472
645
  sourceHash: file.sourceHash,
473
646
  source: sourceInfo((0, extractors_1.styleExtractorForFile)(file), (0, extractors_1.styleConfidenceForFile)(file), ['selectors, classes, tokens, or template UI elements'], file),
474
647
  },
@@ -476,7 +649,7 @@ function createBundle(rootPath, projectType, files) {
476
649
  nodes.push(toGraphNode(styleKey, 'style', file.relPath, file.relPath, file, (0, extractors_1.styleExtractorForFile)(file)));
477
650
  edges.push(toGraphEdge(file.key, styleKey, 'contains', (0, extractors_1.styleExtractorForFile)(file), file, (0, extractors_1.styleConfidenceForFile)(file)));
478
651
  }
479
- for (const styleDefinition of file.componentStyleDefinitions) {
652
+ for (const styleDefinition of (0, extractors_1.isTestFile)(file.relPath) ? [] : file.componentStyleDefinitions) {
480
653
  const styleDefinitionKey = (0, utils_1.toRegistryKey)('style', `${file.relPath.replace(/\.[^.]+$/, '')}.${styleDefinition.name}`);
481
654
  addEntry(entries, usedKeys, {
482
655
  key: styleDefinitionKey,
@@ -505,6 +678,8 @@ function createBundle(rootPath, projectType, files) {
505
678
  baseClasses: styleDefinition.baseClasses,
506
679
  variants: styleDefinition.variants,
507
680
  defaultVariants: styleDefinition.defaultVariants,
681
+ variantRoles: inferStyleDefinitionVariantRoles(styleDefinition),
682
+ generationGuidance: createStyleDefinitionGuidance(styleDefinition),
508
683
  classes: styleDefinition.classes,
509
684
  sourceSnippet: styleDefinition.source,
510
685
  sourceHash: file.sourceHash,
@@ -521,15 +696,18 @@ function createBundle(rootPath, projectType, files) {
521
696
  edges.push(toGraphEdge(file.key, styleDefinitionKey, 'defines_style', 'component-style-definitions', file, 'high'));
522
697
  }
523
698
  }
524
- addUiPatternRules(entries, usedKeys, nodes, edges, files);
525
- addUiCompositionRules(entries, usedKeys, nodes, edges, files);
526
- addConnectedPatternRules(entries, usedKeys, nodes, edges, reviewItems, files);
699
+ addUiPatternRules(entries, usedKeys, nodes, edges, files, styleResolutionContext);
700
+ addUiCompositionRules(entries, usedKeys, nodes, edges, files, styleResolutionContext);
701
+ addGlobalUiContractRules(entries, usedKeys, nodes, edges, files);
702
+ addConnectedPatternRules(entries, usedKeys, nodes, edges, reviewItems, files, styleResolutionContext);
527
703
  addRenderEdges(entries, files, edges);
528
704
  addImportEdges(entries, files, edges, rootPath);
529
705
  addApiCallEdges(entries, files, edges);
530
706
  addStyleUsageEdges(entries, files, edges);
707
+ addDatabaseUsageEdges(entries, files, edges);
531
708
  addModelUsageEdges(entries, files, edges);
532
709
  addConceptClusters(entries, usedKeys, nodes, edges, reviewItems);
710
+ addDatabaseReviewItems(entries, reviewItems);
533
711
  applyUsedBy(entries, edges);
534
712
  return {
535
713
  scanId: `${new Date().toISOString()}:${(0, utils_1.shortHash)(rootPath + entries.length)}`,
@@ -576,6 +754,215 @@ function isBackendRouteFile(file) {
576
754
  file.apiHandlers.length ||
577
755
  file.backendEndpointContracts.length);
578
756
  }
757
+ function hasDatabaseSurface(file) {
758
+ return Boolean(file.databaseModels.length ||
759
+ file.databaseEnums.length ||
760
+ file.databaseMigrations.length ||
761
+ file.databaseAccesses.length);
762
+ }
763
+ function addDatabaseSurfaceEntries(entries, usedKeys, nodes, edges, file) {
764
+ const extractor = (0, database_1.databaseExtractorForFile)(file.relPath, databaseSurfaceText(file));
765
+ const confidence = databaseSurfaceConfidence(file);
766
+ const databaseKey = (0, utils_1.toRegistryKey)('database', file.relPath.replace(/\.[^.]+$/, ''));
767
+ addEntry(entries, usedKeys, {
768
+ key: databaseKey,
769
+ group: 'database',
770
+ summary: `Database code surface in ${file.relPath}.`,
771
+ tags: (0, utils_1.unique)([
772
+ 'database',
773
+ 'data',
774
+ file.language,
775
+ extractor,
776
+ ...file.databaseModels.map((model) => model.provider),
777
+ ...file.databaseModels.map((model) => model.name),
778
+ ...file.databaseEnums.map((enumValue) => enumValue.name),
779
+ ...file.databaseAccesses.map((access) => access.operation),
780
+ ]),
781
+ dependencies: (0, utils_1.unique)([
782
+ file.key,
783
+ ...file.databaseModels.map((model) => (0, utils_1.toRegistryKey)('model', model.name)),
784
+ ...file.databaseEnums.map((enumValue) => databaseEnumKey(enumValue, file)),
785
+ ...file.databaseMigrations.map((migration) => (0, utils_1.toRegistryKey)('database', `migration.${migration.name}`)),
786
+ ...file.databaseAccesses.flatMap(databaseAccessDependencies),
787
+ ]),
788
+ usedBy: [],
789
+ confidence,
790
+ status: 'inferred',
791
+ path: file.relPath,
792
+ value: {
793
+ path: file.relPath,
794
+ kind: 'database-surface',
795
+ models: file.databaseModels,
796
+ enums: file.databaseEnums,
797
+ migrations: file.databaseMigrations,
798
+ accesses: file.databaseAccesses,
799
+ sourceHash: file.sourceHash,
800
+ source: sourceInfo(extractor, confidence, ['database models, enums, migrations, or code access patterns'], file),
801
+ },
802
+ });
803
+ nodes.push(toGraphNode(databaseKey, 'database', file.relPath, file.relPath, file, extractor));
804
+ edges.push(toGraphEdge(file.key, databaseKey, 'contains', 'database.surface', file, confidence));
805
+ for (const enumValue of file.databaseEnums) {
806
+ const enumKey = databaseEnumKey(enumValue, file);
807
+ addEntry(entries, usedKeys, {
808
+ key: enumKey,
809
+ group: 'database',
810
+ summary: `${enumValue.name} database enum with ${enumValue.values.length} values.`,
811
+ tags: (0, utils_1.unique)(['database', 'enum', enumValue.provider, enumValue.name, ...enumValue.values]),
812
+ dependencies: [file.key],
813
+ usedBy: [],
814
+ confidence: enumValue.confidence,
815
+ status: 'inferred',
816
+ path: file.relPath,
817
+ value: {
818
+ ...enumValue,
819
+ kind: 'database-enum',
820
+ sourceHash: file.sourceHash,
821
+ source: sourceInfo(`database.${enumValue.provider}`, enumValue.confidence, [`enum:${enumValue.name}`, `values:${enumValue.values.join(',')}`], file),
822
+ },
823
+ });
824
+ nodes.push(toGraphNode(enumKey, 'database-enum', enumValue.name, file.relPath, file, `database.${enumValue.provider}`, {
825
+ enum: { values: enumValue.values },
826
+ }));
827
+ edges.push(toGraphEdge(file.key, enumKey, 'defines_enum', `database.${enumValue.provider}`, file));
828
+ }
829
+ for (const migration of file.databaseMigrations) {
830
+ const migrationKey = (0, utils_1.toRegistryKey)('database', `migration.${migration.name}`);
831
+ const migrationDependencies = (0, utils_1.unique)([
832
+ file.key,
833
+ ...migration.operations
834
+ .map((operation) => operation.target ?? '')
835
+ .filter(Boolean)
836
+ .map((target) => (0, utils_1.toRegistryKey)('model', target)),
837
+ ]);
838
+ addEntry(entries, usedKeys, {
839
+ key: migrationKey,
840
+ group: 'database',
841
+ summary: `${migration.name} database migration: ${migration.operations.length} operations.`,
842
+ tags: (0, utils_1.unique)([
843
+ 'database',
844
+ 'migration',
845
+ migration.provider,
846
+ ...(migration.destructive ? ['destructive'] : []),
847
+ ...(migration.seedLike ? ['seed-like'] : []),
848
+ ...migration.creates.map((item) => `creates:${item}`),
849
+ ...migration.alters.map((item) => `alters:${item}`),
850
+ ...migration.drops.map((item) => `drops:${item}`),
851
+ ]),
852
+ dependencies: migrationDependencies,
853
+ usedBy: [],
854
+ confidence: migration.confidence,
855
+ status: migration.destructive ? 'needs_review' : 'inferred',
856
+ path: file.relPath,
857
+ value: {
858
+ ...migration,
859
+ kind: 'database-migration',
860
+ sourceHash: file.sourceHash,
861
+ source: sourceInfo(`database.${migration.provider}.migration`, migration.confidence, [`migration:${migration.name}`, `operations:${migration.operations.length}`], file),
862
+ },
863
+ });
864
+ nodes.push(toGraphNode(migrationKey, 'database-migration', migration.name, file.relPath, file, `database.${migration.provider}.migration`, {
865
+ migration: {
866
+ operations: migration.operations.length,
867
+ destructive: migration.destructive,
868
+ seedLike: migration.seedLike,
869
+ },
870
+ }));
871
+ edges.push(toGraphEdge(file.key, migrationKey, 'contains', `database.${migration.provider}.migration`, file, migration.confidence));
872
+ for (const dependency of migrationDependencies.filter((key) => key.startsWith('model.'))) {
873
+ edges.push(toGraphEdge(migrationKey, dependency, 'changes_schema', `database.${migration.provider}.migration`, file, migration.confidence));
874
+ }
875
+ }
876
+ for (const access of file.databaseAccesses) {
877
+ const accessKey = (0, utils_1.toRegistryKey)('database', `access.${file.relPath.replace(/\.[^.]+$/, '')}.${access.line}.${access.operation}.${access.model ?? access.table ?? access.receiver ?? 'unknown'}`);
878
+ const dependencies = (0, utils_1.unique)([file.key, ...databaseAccessDependencies(access)]);
879
+ addEntry(entries, usedKeys, {
880
+ key: accessKey,
881
+ group: 'database',
882
+ summary: `${(0, utils_1.titleCase)(access.operation)} database access${access.model || access.table ? ` for ${access.model ?? access.table}` : ''} in ${file.relPath}.`,
883
+ tags: (0, utils_1.unique)([
884
+ 'database',
885
+ 'data-access',
886
+ access.framework,
887
+ access.operation,
888
+ access.model ?? '',
889
+ access.table ?? '',
890
+ ...(access.raw ? ['raw-sql'] : []),
891
+ ...(access.transactional ? ['transaction'] : []),
892
+ ...access.risk,
893
+ ]),
894
+ dependencies,
895
+ usedBy: [],
896
+ confidence: access.confidence,
897
+ status: access.risk.includes('manual-review') ? 'needs_review' : 'inferred',
898
+ path: file.relPath,
899
+ value: {
900
+ ...access,
901
+ kind: 'database-access',
902
+ targetKeys: databaseAccessDependencies(access),
903
+ sourceHash: file.sourceHash,
904
+ source: sourceInfo(`database.${access.framework}.access`, access.confidence, [`${access.operation}:${access.model ?? access.table ?? access.receiver ?? 'unknown'}`], file),
905
+ },
906
+ });
907
+ nodes.push(toGraphNode(accessKey, 'database-access', `${access.operation}:${access.model ?? access.table ?? 'unknown'}`, file.relPath, file, `database.${access.framework}.access`, {
908
+ access: {
909
+ operation: access.operation,
910
+ model: access.model,
911
+ table: access.table,
912
+ risk: access.risk,
913
+ },
914
+ }));
915
+ edges.push(toGraphEdge(file.key, accessKey, 'contains', `database.${access.framework}.access`, file, access.confidence));
916
+ for (const dependency of dependencies.filter((key) => key.startsWith('model.'))) {
917
+ const edgeType = databaseAccessEdgeType(access);
918
+ edges.push(toGraphEdge(accessKey, dependency, edgeType, `database.${access.framework}.access`, file, access.confidence));
919
+ edges.push(toGraphEdge(file.key, dependency, edgeType, `database.${access.framework}.access`, file, access.confidence));
920
+ }
921
+ }
922
+ }
923
+ function databaseSurfaceText(file) {
924
+ return [
925
+ file.relPath,
926
+ file.language,
927
+ ...file.models,
928
+ ...file.databaseModels.map((model) => `${model.provider}:${model.name}:${model.tableName ?? ''}`),
929
+ ...file.databaseEnums.map((enumValue) => `enum:${enumValue.name}`),
930
+ ...file.databaseMigrations.map((migration) => `migration:${migration.name}`),
931
+ ...file.databaseAccesses.map((access) => `${access.framework}:${access.operation}:${access.model ?? access.table ?? ''}`),
932
+ ].join('\n');
933
+ }
934
+ function databaseSurfaceConfidence(file) {
935
+ if (file.databaseModels.some((model) => model.confidence === 'high') ||
936
+ file.databaseEnums.length) {
937
+ return 'high';
938
+ }
939
+ if (file.databaseMigrations.length ||
940
+ file.databaseAccesses.some((access) => access.confidence === 'medium')) {
941
+ return 'medium';
942
+ }
943
+ return 'low';
944
+ }
945
+ function databaseAccessDependencies(access) {
946
+ return (0, utils_1.unique)([
947
+ access.model ? (0, utils_1.toRegistryKey)('model', access.model) : '',
948
+ access.table ? (0, utils_1.toRegistryKey)('model', access.table) : '',
949
+ ]);
950
+ }
951
+ function databaseAccessEdgeType(access) {
952
+ if (['create', 'update', 'delete', 'upsert', 'write', 'raw-sql'].includes(access.operation)) {
953
+ return 'writes';
954
+ }
955
+ if (access.operation === 'transaction') {
956
+ return 'transaction_scope';
957
+ }
958
+ return 'reads';
959
+ }
960
+ function databaseEnumKey(enumValue, file) {
961
+ if (enumValue.provider === 'prisma' && /schema\.prisma$/i.test(file.relPath)) {
962
+ return (0, utils_1.toRegistryKey)('database', `enum.${enumValue.name}`);
963
+ }
964
+ return (0, utils_1.toRegistryKey)('database', `enum.${enumValue.provider}.${enumValue.name}.${(0, utils_1.shortHash)(`${file.relPath}:${enumValue.values.join('|')}`)}`);
965
+ }
579
966
  function backendFileContext(file) {
580
967
  if (!file.backendEndpointContracts.length &&
581
968
  !file.backendOperations.length &&
@@ -594,6 +981,19 @@ function backendFileContext(file) {
594
981
  },
595
982
  };
596
983
  }
984
+ function databaseFileContext(file) {
985
+ if (!hasDatabaseSurface(file)) {
986
+ return {};
987
+ }
988
+ return {
989
+ database: {
990
+ models: file.databaseModels,
991
+ enums: file.databaseEnums,
992
+ migrations: file.databaseMigrations,
993
+ accesses: file.databaseAccesses,
994
+ },
995
+ };
996
+ }
597
997
  function findEndpointContract(file, handler) {
598
998
  const [method, ...pathParts] = handler.split(/\s+/);
599
999
  const handlerPath = pathParts.join(' ');
@@ -646,79 +1046,1873 @@ function backendCallDependencies(calls) {
646
1046
  .filter((call) => ['service', 'repository', 'provider', 'client'].includes(call.kind))
647
1047
  .map(backendCallKey));
648
1048
  }
649
- function addBackendCallEdges(fromKey, calls, file, edges) {
650
- for (const call of calls) {
651
- const targetKey = backendCallKey(call);
652
- edges.push({
653
- from: fromKey,
654
- to: targetKey,
655
- type: call.kind === 'repository' ? 'calls_repository' : 'calls',
656
- confidence: call.kind === 'unknown' ? 'low' : 'medium',
657
- source: sourceInfo('backend.contract.calls', call.kind === 'unknown' ? 'low' : 'medium', [`${fromKey} calls ${call.receiver}.${call.method}`], file),
658
- });
1049
+ function addBackendCallEdges(fromKey, calls, file, edges) {
1050
+ for (const call of calls) {
1051
+ const targetKey = backendCallKey(call);
1052
+ edges.push({
1053
+ from: fromKey,
1054
+ to: targetKey,
1055
+ type: call.kind === 'repository' ? 'calls_repository' : 'calls',
1056
+ confidence: call.kind === 'unknown' ? 'low' : 'medium',
1057
+ source: sourceInfo('backend.contract.calls', call.kind === 'unknown' ? 'low' : 'medium', [`${fromKey} calls ${call.receiver}.${call.method}`], file),
1058
+ });
1059
+ }
1060
+ }
1061
+ function backendCallKey(call) {
1062
+ return (0, utils_1.toRegistryKey)('service', (0, backend_1.backendReceiverToClassName)(call.receiver));
1063
+ }
1064
+ function uniqueSideEffectsForRecipe(sideEffects) {
1065
+ return (0, utils_1.uniqueBy)(sideEffects, (sideEffect) => `${sideEffect.kind}.${sideEffect.target ?? ''}.${sideEffect.operation ?? ''}.${sideEffect.line}`);
1066
+ }
1067
+ function sourceInfo(extractor, confidence, evidence, file) {
1068
+ return {
1069
+ extractor,
1070
+ confidence,
1071
+ evidence,
1072
+ path: file.relPath,
1073
+ hash: file.sourceHash,
1074
+ };
1075
+ }
1076
+ function toGraphNode(key, kind, name, relPath, file, extractor, facets) {
1077
+ return {
1078
+ k: key,
1079
+ kind,
1080
+ name,
1081
+ path: relPath,
1082
+ language: file.language,
1083
+ framework: detectFrameworkForFile(file),
1084
+ ...(facets ? { facets } : {}),
1085
+ source: sourceInfo(extractor, 'high', [`path:${relPath}`], file),
1086
+ };
1087
+ }
1088
+ function toGraphEdge(from, to, type, extractor, file, confidence = 'high') {
1089
+ return {
1090
+ from,
1091
+ to,
1092
+ type,
1093
+ confidence,
1094
+ source: sourceInfo(extractor, confidence, [`${from} ${type} ${to}`], file),
1095
+ };
1096
+ }
1097
+ function addRenderEdges(entries, files, edges) {
1098
+ const componentKeysByName = new Map();
1099
+ for (const entry of entries.filter((item) => item.group === 'component')) {
1100
+ const name = typeof entry.value.name === 'string' ? entry.value.name : undefined;
1101
+ if (name) {
1102
+ componentKeysByName.set(name, entry.key);
1103
+ }
1104
+ }
1105
+ for (const file of files) {
1106
+ if (!file.routes.length || !file.jsxElements.length) {
1107
+ continue;
1108
+ }
1109
+ for (const route of file.routes) {
1110
+ const routeKey = (0, utils_1.toRegistryKey)('route', route);
1111
+ for (const jsxElement of file.jsxElements) {
1112
+ const componentKey = componentKeysByName.get(jsxElement);
1113
+ if (componentKey) {
1114
+ edges.push(toGraphEdge(routeKey, componentKey, 'renders', 'react-jsx', file));
1115
+ }
1116
+ }
1117
+ }
1118
+ }
1119
+ }
1120
+ function inferStyleDefinitionVariantRoles(styleDefinition) {
1121
+ const roles = {};
1122
+ for (const [groupName, variants] of Object.entries(styleDefinition.variants)) {
1123
+ if (!/^(?:variant|intent|tone|appearance|color)$/i.test(groupName)) {
1124
+ continue;
1125
+ }
1126
+ roles[groupName] = Object.fromEntries(Object.entries(variants).map(([variantName, classes]) => [
1127
+ variantName,
1128
+ inferActionVariantRole(variantName, classes),
1129
+ ]));
1130
+ }
1131
+ return roles;
1132
+ }
1133
+ function inferActionVariantRole(variantName, classes) {
1134
+ const name = variantName.toLowerCase();
1135
+ const classText = classes.join(' ').toLowerCase();
1136
+ const isGhostLike = /^(?:ghost|link|quiet|plain|unstyled|transparent)$/.test(name) ||
1137
+ (classText.includes('border-transparent') && classText.includes('bg-transparent')) ||
1138
+ classText.includes('rounded-none');
1139
+ const isPrimaryLike = /^(?:default|primary|solid|main)$/.test(name) ||
1140
+ classText.includes('bg-primary') ||
1141
+ classText.includes('text-primary-foreground');
1142
+ const isSecondaryLike = /^(?:secondary|subtle)$/.test(name) ||
1143
+ classText.includes('bg-secondary') ||
1144
+ classText.includes('text-secondary-foreground');
1145
+ const isOutlineLike = /^(?:outline|bordered)$/.test(name) ||
1146
+ (classText.includes('border-border') && classText.includes('bg-transparent'));
1147
+ if (isGhostLike) {
1148
+ return {
1149
+ visualRole: name === 'link' ? 'inline-link' : 'quiet-action',
1150
+ generationRole: 'low-emphasis chrome, navigation, icon, or inline action',
1151
+ ctaSafe: false,
1152
+ emphasis: 'low',
1153
+ avoidFor: ['primary CTA', 'card footer CTA', 'form submit', 'sales/beta conversion'],
1154
+ reason: 'Transparent or link-style variants can look like plain text until hover.',
1155
+ };
1156
+ }
1157
+ if (isPrimaryLike) {
1158
+ return {
1159
+ visualRole: 'primary-action',
1160
+ generationRole: 'main CTA or form submit',
1161
+ ctaSafe: true,
1162
+ emphasis: 'high',
1163
+ };
1164
+ }
1165
+ if (isSecondaryLike) {
1166
+ return {
1167
+ visualRole: 'secondary-action',
1168
+ generationRole: 'secondary CTA or alternate action',
1169
+ ctaSafe: true,
1170
+ emphasis: 'medium',
1171
+ };
1172
+ }
1173
+ if (isOutlineLike) {
1174
+ return {
1175
+ visualRole: 'outlined-action',
1176
+ generationRole: 'secondary CTA when a visible border is desired',
1177
+ ctaSafe: true,
1178
+ emphasis: 'medium',
1179
+ };
1180
+ }
1181
+ return {
1182
+ visualRole: 'custom-action',
1183
+ generationRole: 'use only when the task names this variant or matching source context',
1184
+ ctaSafe: false,
1185
+ emphasis: 'unknown',
1186
+ };
1187
+ }
1188
+ function createStyleDefinitionGuidance(styleDefinition) {
1189
+ const guidance = [
1190
+ 'When generating actions, combine baseClasses with the selected variant and size classes; do not use variant classes alone.',
1191
+ ];
1192
+ const variantRoles = inferStyleDefinitionVariantRoles(styleDefinition);
1193
+ const variantValues = Object.values(variantRoles).flatMap((group) => Object.entries(group));
1194
+ const hasQuietVariant = variantValues.some(([, role]) => role.ctaSafe === false);
1195
+ const hasPrimaryVariant = variantValues.some(([, role]) => role.visualRole === 'primary-action');
1196
+ if (hasPrimaryVariant) {
1197
+ guidance.push('Use primary/default action variants for main conversion CTAs, submit buttons, and card footer actions unless the source pattern says otherwise.');
1198
+ }
1199
+ if (hasQuietVariant) {
1200
+ guidance.push('Do not use ghost/link/quiet variants for primary CTAs; those variants are low-emphasis and may render as text until hover.');
1201
+ }
1202
+ if (Object.keys(styleDefinition.defaultVariants).length) {
1203
+ guidance.push(`Default variants: ${Object.entries(styleDefinition.defaultVariants)
1204
+ .map(([name, value]) => `${name}=${value}`)
1205
+ .join(', ')}.`);
1206
+ }
1207
+ return guidance;
1208
+ }
1209
+ function summarizeUiElementPatterns(elements, styleContext) {
1210
+ return elements.slice(0, 80).map((element) => {
1211
+ const childStructureContract = element.childSummary?.length
1212
+ ? summarizeElementChildContract(element.childSummary, styleContext)
1213
+ : undefined;
1214
+ const compoundStructure = element.semanticProfile?.isCompound
1215
+ ? summarizeCompoundStructure([element], styleContext)
1216
+ : undefined;
1217
+ return {
1218
+ usage: uiElementPatternUsage(element),
1219
+ kind: element.kind,
1220
+ tag: element.originalTag,
1221
+ ...(element.layoutRole ? { layoutRole: element.layoutRole } : {}),
1222
+ ...(element.parentKind ? { parentKind: element.parentKind } : {}),
1223
+ ...(element.label ? { label: element.label } : {}),
1224
+ line: element.line,
1225
+ uiContract: createUiContractForElement(element, undefined, undefined, styleContext),
1226
+ relationships: createUiRelationships({
1227
+ parentContext: summarizeParentContexts([element])[0],
1228
+ childStructureContract,
1229
+ compoundStructure,
1230
+ }),
1231
+ defaultClasses: element.defaultClasses ?? [],
1232
+ conditionalClasses: element.conditionalClasses ?? [],
1233
+ decorativeAccentClasses: element.decorativeAccentClasses ?? [],
1234
+ classGroups: element.classGroups ?? {},
1235
+ ...(element.layoutSafety ? { layoutSafety: element.layoutSafety } : {}),
1236
+ ...(element.responsiveProfile ? { responsiveProfile: element.responsiveProfile } : {}),
1237
+ ...(element.scaleProfile ? { scaleProfile: element.scaleProfile } : {}),
1238
+ ...(element.semanticProfile ? { semanticProfile: element.semanticProfile } : {}),
1239
+ ...(element.roleSignature ? { roleSignature: element.roleSignature } : {}),
1240
+ ...(childStructureContract ? { childStructure: childStructureContract } : {}),
1241
+ ...(element.variants ? { variants: element.variants } : {}),
1242
+ ...(element.styleHelper ? { styleHelper: element.styleHelper } : {}),
1243
+ ...(element.styleHelperVariants ? { styleHelperVariants: element.styleHelperVariants } : {}),
1244
+ };
1245
+ });
1246
+ }
1247
+ function uiElementPatternUsage(element) {
1248
+ if (element.layoutRole?.includes('slot')) {
1249
+ return 'slot';
1250
+ }
1251
+ if (!element.parentKind || element.parentKind !== element.kind) {
1252
+ return 'root';
1253
+ }
1254
+ return 'nested';
1255
+ }
1256
+ function rootUiElementsForKind(elements) {
1257
+ const rootElements = elements.filter((element) => uiElementPatternUsage(element) === 'root');
1258
+ const depthValues = rootElements.map((element) => element.depth ?? 0);
1259
+ const minimumDepth = Math.min(...depthValues);
1260
+ return rootElements.filter((element) => (element.depth ?? 0) === minimumDepth);
1261
+ }
1262
+ function isLayoutSafetyProfile(value) {
1263
+ return Boolean(value &&
1264
+ typeof value === 'object' &&
1265
+ Array.isArray(value.layoutSafetyClasses));
1266
+ }
1267
+ function isResponsiveProfile(value) {
1268
+ return Boolean(value &&
1269
+ typeof value === 'object' &&
1270
+ typeof value.breakpoints === 'object' &&
1271
+ !Array.isArray(value.breakpoints));
1272
+ }
1273
+ function isScaleProfile(value) {
1274
+ return Boolean(value &&
1275
+ typeof value === 'object' &&
1276
+ Array.isArray(value.typographyClasses));
1277
+ }
1278
+ function isSemanticProfile(value) {
1279
+ return Boolean(value && typeof value === 'object' && Array.isArray(value.roles));
1280
+ }
1281
+ const tailwindSpacingScale = {
1282
+ '0': '0px',
1283
+ px: '1px',
1284
+ '0.5': '0.125rem',
1285
+ '1': '0.25rem',
1286
+ '1.5': '0.375rem',
1287
+ '2': '0.5rem',
1288
+ '2.5': '0.625rem',
1289
+ '3': '0.75rem',
1290
+ '3.5': '0.875rem',
1291
+ '4': '1rem',
1292
+ '5': '1.25rem',
1293
+ '6': '1.5rem',
1294
+ '7': '1.75rem',
1295
+ '8': '2rem',
1296
+ '9': '2.25rem',
1297
+ '10': '2.5rem',
1298
+ '11': '2.75rem',
1299
+ '12': '3rem',
1300
+ '14': '3.5rem',
1301
+ '16': '4rem',
1302
+ '20': '5rem',
1303
+ '24': '6rem',
1304
+ '28': '7rem',
1305
+ '32': '8rem',
1306
+ '36': '9rem',
1307
+ '40': '10rem',
1308
+ '44': '11rem',
1309
+ '48': '12rem',
1310
+ '52': '13rem',
1311
+ '56': '14rem',
1312
+ '60': '15rem',
1313
+ '64': '16rem',
1314
+ '72': '18rem',
1315
+ '80': '20rem',
1316
+ '96': '24rem',
1317
+ };
1318
+ const tailwindFontSizeScale = {
1319
+ xs: '0.75rem',
1320
+ sm: '0.875rem',
1321
+ base: '1rem',
1322
+ lg: '1.125rem',
1323
+ xl: '1.25rem',
1324
+ '2xl': '1.5rem',
1325
+ '3xl': '1.875rem',
1326
+ '4xl': '2.25rem',
1327
+ '5xl': '3rem',
1328
+ '6xl': '3.75rem',
1329
+ '7xl': '4.5rem',
1330
+ '8xl': '6rem',
1331
+ '9xl': '8rem',
1332
+ };
1333
+ const tailwindFontWeightScale = {
1334
+ thin: '100',
1335
+ extralight: '200',
1336
+ light: '300',
1337
+ normal: '400',
1338
+ medium: '500',
1339
+ semibold: '600',
1340
+ bold: '700',
1341
+ extrabold: '800',
1342
+ black: '900',
1343
+ };
1344
+ const tailwindLineHeightScale = {
1345
+ none: '1',
1346
+ tight: '1.25',
1347
+ snug: '1.375',
1348
+ normal: '1.5',
1349
+ relaxed: '1.625',
1350
+ loose: '2',
1351
+ };
1352
+ const tailwindTrackingScale = {
1353
+ tighter: '-0.05em',
1354
+ tight: '-0.025em',
1355
+ normal: '0em',
1356
+ wide: '0.025em',
1357
+ wider: '0.05em',
1358
+ widest: '0.1em',
1359
+ };
1360
+ const tailwindRadiusScale = {
1361
+ none: '0px',
1362
+ sm: '0.125rem',
1363
+ DEFAULT: '0.25rem',
1364
+ md: '0.375rem',
1365
+ lg: '0.5rem',
1366
+ xl: '0.75rem',
1367
+ '2xl': '1rem',
1368
+ '3xl': '1.5rem',
1369
+ full: '9999px',
1370
+ };
1371
+ const bootstrapSpacingScale = {
1372
+ '0': '0',
1373
+ '1': '0.25rem',
1374
+ '2': '0.5rem',
1375
+ '3': '1rem',
1376
+ '4': '1.5rem',
1377
+ '5': '3rem',
1378
+ auto: 'auto',
1379
+ };
1380
+ const bootstrapFontSizeScale = {
1381
+ '1': '2.5rem',
1382
+ '2': '2rem',
1383
+ '3': '1.75rem',
1384
+ '4': '1.5rem',
1385
+ '5': '1.25rem',
1386
+ '6': '1rem',
1387
+ };
1388
+ const bootstrapThemeColors = {
1389
+ primary: 'var(--bs-primary)',
1390
+ secondary: 'var(--bs-secondary)',
1391
+ success: 'var(--bs-success)',
1392
+ danger: 'var(--bs-danger)',
1393
+ warning: 'var(--bs-warning)',
1394
+ info: 'var(--bs-info)',
1395
+ light: 'var(--bs-light)',
1396
+ dark: 'var(--bs-dark)',
1397
+ body: 'var(--bs-body-color)',
1398
+ muted: 'var(--bs-secondary-color)',
1399
+ white: '#fff',
1400
+ black: '#000',
1401
+ };
1402
+ function createStyleResolutionContext(files) {
1403
+ const classRules = new Map();
1404
+ const cssVariables = new Map();
1405
+ const detectedStyleSystems = detectProjectStyleSystems(files);
1406
+ let ruleOrder = 0;
1407
+ for (const file of files) {
1408
+ for (const variable of file.cssVariableValues) {
1409
+ const values = cssVariables.get(variable.name) ?? [];
1410
+ values.push({
1411
+ value: variable.value,
1412
+ selector: variable.selector,
1413
+ path: file.relPath,
1414
+ line: variable.line,
1415
+ });
1416
+ cssVariables.set(variable.name, values);
1417
+ }
1418
+ for (const rule of file.cssRules) {
1419
+ const classNames = classNamesFromCssSelector(rule.selector);
1420
+ ruleOrder += 1;
1421
+ for (const className of classNames) {
1422
+ const declarations = rule.declarations.map((declaration) => ({
1423
+ property: declaration.property,
1424
+ value: normalizeCssDeclarationValue(declaration.value),
1425
+ resolvedValue: resolveCssValueTokens(declaration.value, cssVariables),
1426
+ source: 'css-rule',
1427
+ styleSystem: 'raw-css',
1428
+ className,
1429
+ selector: rule.selector,
1430
+ path: file.relPath,
1431
+ line: rule.line,
1432
+ prefixes: cssSelectorStatePrefixes(rule.selector),
1433
+ media: rule.media ?? cssRuleMediaContext(rule.selector),
1434
+ specificity: estimateSelectorSpecificity(rule.selector),
1435
+ ruleOrder,
1436
+ cascadeRank: estimateSelectorSpecificity(rule.selector) * 10000 +
1437
+ ruleOrder +
1438
+ (cssDeclarationImportant(declaration.value) ? 100000000 : 0),
1439
+ important: cssDeclarationImportant(declaration.value),
1440
+ }));
1441
+ classRules.set(className, [
1442
+ ...(classRules.get(className) ?? []),
1443
+ ...declarations,
1444
+ ].slice(-320));
1445
+ }
1446
+ }
1447
+ }
1448
+ return { classRules, cssVariables, detectedStyleSystems };
1449
+ }
1450
+ function detectProjectStyleSystems(files) {
1451
+ const systems = new Set();
1452
+ const allClasses = (0, utils_1.unique)(files.flatMap((file) => [
1453
+ ...file.classReferences,
1454
+ ...file.tailwindUtilities,
1455
+ ...file.cssClasses,
1456
+ ...file.classExpressions.flatMap((expression) => expression.classes),
1457
+ ]));
1458
+ const dependencyText = files.flatMap((file) => file.dependencies).join(' ').toLowerCase();
1459
+ const importText = files.flatMap((file) => file.imports).join(' ').toLowerCase();
1460
+ const pathText = files.map((file) => file.relPath).join(' ').toLowerCase();
1461
+ if (files.some((file) => file.cssRules.length || file.cssVariableValues.length || file.cssClasses.length)) {
1462
+ systems.add('raw-css');
1463
+ }
1464
+ if (files.some((file) => file.tailwindUtilities.length) ||
1465
+ /tailwind/.test(dependencyText) ||
1466
+ /tailwind/.test(importText) ||
1467
+ /tailwind\.config|tailwind-theme|@tailwind/.test(pathText)) {
1468
+ systems.add('tailwind');
1469
+ }
1470
+ if (allClasses.some(isBootstrapClass) ||
1471
+ /\bbootstrap\b/.test(dependencyText) ||
1472
+ /\bbootstrap\b/.test(importText)) {
1473
+ systems.add('bootstrap');
1474
+ }
1475
+ return Array.from(systems).length ? Array.from(systems) : ['unknown'];
1476
+ }
1477
+ function classNamesFromCssSelector(selector) {
1478
+ return (0, utils_1.unique)(Array.from(selector.matchAll(/\.((?:\\.|[A-Za-z0-9_-])+)/g))
1479
+ .map((match) => unescapeCssIdentifier(match[1]))
1480
+ .filter(Boolean)).slice(0, 24);
1481
+ }
1482
+ function unescapeCssIdentifier(value) {
1483
+ return value
1484
+ .replace(/\\:/g, ':')
1485
+ .replace(/\\\//g, '/')
1486
+ .replace(/\\\[/g, '[')
1487
+ .replace(/\\\]/g, ']')
1488
+ .replace(/\\\(/g, '(')
1489
+ .replace(/\\\)/g, ')')
1490
+ .replace(/\\,/g, ',')
1491
+ .replace(/\\\./g, '.')
1492
+ .replace(/\\/g, '');
1493
+ }
1494
+ function cssSelectorStatePrefixes(selector) {
1495
+ const prefixes = [];
1496
+ if (/:hover\b/.test(selector)) {
1497
+ prefixes.push('hover');
1498
+ }
1499
+ if (/:focus-visible\b/.test(selector)) {
1500
+ prefixes.push('focus-visible');
1501
+ }
1502
+ else if (/:focus\b/.test(selector)) {
1503
+ prefixes.push('focus');
1504
+ }
1505
+ if (/:active\b/.test(selector)) {
1506
+ prefixes.push('active');
1507
+ }
1508
+ if (/:disabled\b|\[disabled\]/.test(selector)) {
1509
+ prefixes.push('disabled');
1510
+ }
1511
+ if (/\[data-[^\]]+\]/.test(selector)) {
1512
+ prefixes.push('data');
1513
+ }
1514
+ return prefixes;
1515
+ }
1516
+ function cssRuleMediaContext(selector) {
1517
+ const mediaMatch = selector.match(/@media\s*([^{]+)\s+(.+)$/);
1518
+ return mediaMatch?.[1]?.trim();
1519
+ }
1520
+ function normalizeCssDeclarationValue(value) {
1521
+ return value.replace(/\s*!important\s*$/i, '').trim();
1522
+ }
1523
+ function cssDeclarationImportant(value) {
1524
+ return /\s!important\s*$/i.test(value);
1525
+ }
1526
+ function estimateSelectorSpecificity(selector) {
1527
+ const idCount = (selector.match(/#[A-Za-z0-9_-]+/g) ?? []).length;
1528
+ const classCount = (selector.match(/\.[A-Za-z0-9_\\-]+|\[[^\]]+\]|:[A-Za-z-]+/g) ?? []).length;
1529
+ const elementCount = (selector.match(/(^|[\s>+~])([A-Za-z][A-Za-z0-9-]*)/g) ?? []).length;
1530
+ return idCount * 100 + classCount * 10 + elementCount;
1531
+ }
1532
+ function resolveCssValueTokens(value, cssVariables) {
1533
+ return value.replace(/var\((--[A-Za-z0-9_-]+)(?:,[^)]+)?\)/g, (match, variableName) => {
1534
+ const variableValues = cssVariables?.get(variableName);
1535
+ const latest = variableValues?.[variableValues.length - 1];
1536
+ return latest ? `${match} /* ${latest.value} */` : match;
1537
+ });
1538
+ }
1539
+ function resolveUiClassDeclarations(classes, styleContext) {
1540
+ const declarations = [];
1541
+ for (const className of classes) {
1542
+ const candidates = (0, utils_1.unique)([className, (0, utils_1.classBase)(className), (0, utils_1.classBase)(className).replace(/^!/, '')]);
1543
+ for (const candidate of candidates) {
1544
+ declarations.push(...(styleContext?.classRules.get(candidate) ?? []).map((declaration) => ({
1545
+ ...declaration,
1546
+ className,
1547
+ })));
1548
+ }
1549
+ declarations.push(...tailwindDeclarationsForClass(className, styleContext));
1550
+ declarations.push(...bootstrapDeclarationsForClass(className, styleContext));
1551
+ }
1552
+ return (0, utils_1.uniqueBy)(declarations, (declaration) => [
1553
+ declaration.className,
1554
+ declaration.property,
1555
+ declaration.value,
1556
+ declaration.source,
1557
+ declaration.selector ?? '',
1558
+ declaration.path ?? '',
1559
+ declaration.line ?? '',
1560
+ ].join(':'))
1561
+ .sort((a, b) => (a.cascadeRank ?? 0) - (b.cascadeRank ?? 0))
1562
+ .slice(0, 360);
1563
+ }
1564
+ function tailwindDeclarationsForClass(className, styleContext) {
1565
+ const base = (0, utils_1.classBase)(className).replace(/^!/, '');
1566
+ const prefixes = className.split(':').slice(0, -1);
1567
+ const declaration = (property, value) => ({
1568
+ property,
1569
+ value,
1570
+ source: 'tailwind-utility',
1571
+ styleSystem: 'tailwind',
1572
+ className,
1573
+ prefixes,
1574
+ cascadeRank: 10,
1575
+ });
1576
+ const declarations = [];
1577
+ if (base === 'flex')
1578
+ declarations.push(declaration('display', 'flex'));
1579
+ if (base === 'inline-flex')
1580
+ declarations.push(declaration('display', 'inline-flex'));
1581
+ if (base === 'grid')
1582
+ declarations.push(declaration('display', 'grid'));
1583
+ if (base === 'inline-grid')
1584
+ declarations.push(declaration('display', 'inline-grid'));
1585
+ if (base === 'block')
1586
+ declarations.push(declaration('display', 'block'));
1587
+ if (base === 'inline')
1588
+ declarations.push(declaration('display', 'inline'));
1589
+ if (base === 'inline-block')
1590
+ declarations.push(declaration('display', 'inline-block'));
1591
+ if (base === 'hidden')
1592
+ declarations.push(declaration('display', 'none'));
1593
+ if (base === 'contents')
1594
+ declarations.push(declaration('display', 'contents'));
1595
+ if (base === 'table')
1596
+ declarations.push(declaration('display', 'table'));
1597
+ if (base === 'flow-root')
1598
+ declarations.push(declaration('display', 'flow-root'));
1599
+ const alignment = tailwindAlignmentDeclaration(base);
1600
+ if (alignment)
1601
+ declarations.push(declaration(alignment.property, alignment.value));
1602
+ const spacing = tailwindSpacingDeclaration(base);
1603
+ declarations.push(...spacing.map((item) => declaration(item.property, item.value)));
1604
+ const sizing = tailwindSizingDeclaration(base);
1605
+ declarations.push(...sizing.map((item) => declaration(item.property, item.value)));
1606
+ const typography = tailwindTypographyDeclaration(base);
1607
+ declarations.push(...typography.map((item) => declaration(item.property, item.value)));
1608
+ const border = tailwindBorderDeclaration(base, styleContext);
1609
+ declarations.push(...border.map((item) => declaration(item.property, item.value)));
1610
+ const color = tailwindColorDeclaration(base, styleContext);
1611
+ declarations.push(...color.map((item) => declaration(item.property, item.value)));
1612
+ if (base === 'overflow-hidden')
1613
+ declarations.push(declaration('overflow', 'hidden'));
1614
+ if (base === 'overflow-visible')
1615
+ declarations.push(declaration('overflow', 'visible'));
1616
+ if (base === 'overflow-auto')
1617
+ declarations.push(declaration('overflow', 'auto'));
1618
+ if (base === 'overflow-scroll')
1619
+ declarations.push(declaration('overflow', 'scroll'));
1620
+ if (base === 'whitespace-nowrap')
1621
+ declarations.push(declaration('white-space', 'nowrap'));
1622
+ if (base === 'whitespace-normal')
1623
+ declarations.push(declaration('white-space', 'normal'));
1624
+ if (base === 'break-words')
1625
+ declarations.push(declaration('overflow-wrap', 'break-word'));
1626
+ if (base === 'truncate') {
1627
+ declarations.push(declaration('overflow', 'hidden'), declaration('text-overflow', 'ellipsis'), declaration('white-space', 'nowrap'));
1628
+ }
1629
+ if (/^line-clamp-(\d+)$/.test(base)) {
1630
+ const [, lines] = base.match(/^line-clamp-(\d+)$/) ?? [];
1631
+ declarations.push(declaration('overflow', 'hidden'), declaration('display', '-webkit-box'), declaration('-webkit-line-clamp', lines), declaration('-webkit-box-orient', 'vertical'));
1632
+ }
1633
+ if (/^shadow/.test(base)) {
1634
+ declarations.push(declaration('box-shadow', tailwindShadowValue(base)));
1635
+ }
1636
+ if (/^opacity-(\d+)$/.test(base)) {
1637
+ const [, value] = base.match(/^opacity-(\d+)$/) ?? [];
1638
+ declarations.push(declaration('opacity', String(Number(value) / 100)));
1639
+ }
1640
+ if (/^grid-cols-(\d+)$/.test(base)) {
1641
+ const [, value] = base.match(/^grid-cols-(\d+)$/) ?? [];
1642
+ declarations.push(declaration('grid-template-columns', `repeat(${value}, minmax(0, 1fr))`));
1643
+ }
1644
+ if (/^grid-rows-(\d+)$/.test(base)) {
1645
+ const [, value] = base.match(/^grid-rows-(\d+)$/) ?? [];
1646
+ declarations.push(declaration('grid-template-rows', `repeat(${value}, minmax(0, 1fr))`));
1647
+ }
1648
+ if (/^col-span-(\d+)$/.test(base)) {
1649
+ const [, value] = base.match(/^col-span-(\d+)$/) ?? [];
1650
+ declarations.push(declaration('grid-column', `span ${value} / span ${value}`));
1651
+ }
1652
+ if (/^row-span-(\d+)$/.test(base)) {
1653
+ const [, value] = base.match(/^row-span-(\d+)$/) ?? [];
1654
+ declarations.push(declaration('grid-row', `span ${value} / span ${value}`));
1655
+ }
1656
+ if (/^(?:relative|absolute|fixed|sticky|static)$/.test(base)) {
1657
+ declarations.push(declaration('position', base));
1658
+ }
1659
+ if (/^(?:inset|top|right|bottom|left)-(.+)$/.test(base)) {
1660
+ const [, property, token] = base.match(/^(inset|top|right|bottom|left)-(.+)$/) ?? [];
1661
+ const value = tailwindScaleValue(token);
1662
+ if (value)
1663
+ declarations.push(declaration(property, value));
1664
+ }
1665
+ if (/^z-\[([^\]]+)\]$/.test(base)) {
1666
+ declarations.push(declaration('z-index', arbitraryValue(base)));
1667
+ }
1668
+ if (/^z-(\d+)$/.test(base)) {
1669
+ const [, value] = base.match(/^z-(\d+)$/) ?? [];
1670
+ declarations.push(declaration('z-index', value));
1671
+ }
1672
+ return declarations;
1673
+ }
1674
+ function tailwindAlignmentDeclaration(base) {
1675
+ const alignItems = {
1676
+ 'items-start': 'flex-start',
1677
+ 'items-end': 'flex-end',
1678
+ 'items-center': 'center',
1679
+ 'items-baseline': 'baseline',
1680
+ 'items-stretch': 'stretch',
1681
+ };
1682
+ const justifyContent = {
1683
+ 'justify-start': 'flex-start',
1684
+ 'justify-end': 'flex-end',
1685
+ 'justify-center': 'center',
1686
+ 'justify-between': 'space-between',
1687
+ 'justify-around': 'space-around',
1688
+ 'justify-evenly': 'space-evenly',
1689
+ };
1690
+ if (alignItems[base])
1691
+ return { property: 'align-items', value: alignItems[base] };
1692
+ if (justifyContent[base])
1693
+ return { property: 'justify-content', value: justifyContent[base] };
1694
+ return undefined;
1695
+ }
1696
+ function tailwindSpacingDeclaration(base) {
1697
+ const spacingMatch = base.match(/^(p|px|py|pt|pr|pb|pl|m|mx|my|mt|mr|mb|ml|gap|gap-x|gap-y)-(.+)$/);
1698
+ if (!spacingMatch) {
1699
+ return [];
1700
+ }
1701
+ const [, prefix, token] = spacingMatch;
1702
+ const value = token === 'auto' ? 'auto' : tailwindScaleValue(token);
1703
+ if (!value) {
1704
+ return [];
1705
+ }
1706
+ const properties = {
1707
+ p: ['padding'],
1708
+ px: ['padding-left', 'padding-right'],
1709
+ py: ['padding-top', 'padding-bottom'],
1710
+ pt: ['padding-top'],
1711
+ pr: ['padding-right'],
1712
+ pb: ['padding-bottom'],
1713
+ pl: ['padding-left'],
1714
+ m: ['margin'],
1715
+ mx: ['margin-left', 'margin-right'],
1716
+ my: ['margin-top', 'margin-bottom'],
1717
+ mt: ['margin-top'],
1718
+ mr: ['margin-right'],
1719
+ mb: ['margin-bottom'],
1720
+ ml: ['margin-left'],
1721
+ gap: ['gap'],
1722
+ 'gap-x': ['column-gap'],
1723
+ 'gap-y': ['row-gap'],
1724
+ };
1725
+ return (properties[prefix] ?? []).map((property) => ({ property, value }));
1726
+ }
1727
+ function tailwindSizingDeclaration(base) {
1728
+ if (base === 'min-w-0')
1729
+ return [{ property: 'min-width', value: '0px' }];
1730
+ if (base === 'min-h-0')
1731
+ return [{ property: 'min-height', value: '0px' }];
1732
+ if (base === 'w-px')
1733
+ return [{ property: 'width', value: '1px' }];
1734
+ if (base === 'h-px')
1735
+ return [{ property: 'height', value: '1px' }];
1736
+ if (base === 'w-full')
1737
+ return [{ property: 'width', value: '100%' }];
1738
+ if (base === 'h-full')
1739
+ return [{ property: 'height', value: '100%' }];
1740
+ if (base === 'max-w-none')
1741
+ return [{ property: 'max-width', value: 'none' }];
1742
+ if (base === 'shrink-0')
1743
+ return [{ property: 'flex-shrink', value: '0' }];
1744
+ if (base === 'shrink')
1745
+ return [{ property: 'flex-shrink', value: '1' }];
1746
+ if (base === 'grow')
1747
+ return [{ property: 'flex-grow', value: '1' }];
1748
+ if (base === 'grow-0')
1749
+ return [{ property: 'flex-grow', value: '0' }];
1750
+ const sizeMatch = base.match(/^(size|w|h|min-w|min-h|max-w|max-h)-(.+)$/);
1751
+ if (!sizeMatch) {
1752
+ return [];
1753
+ }
1754
+ const [, prefix, token] = sizeMatch;
1755
+ const value = tailwindScaleValue(token) ?? tailwindNamedSizeValue(token);
1756
+ if (!value) {
1757
+ return [];
1758
+ }
1759
+ const properties = {
1760
+ size: ['width', 'height'],
1761
+ w: ['width'],
1762
+ h: ['height'],
1763
+ 'min-w': ['min-width'],
1764
+ 'min-h': ['min-height'],
1765
+ 'max-w': ['max-width'],
1766
+ 'max-h': ['max-height'],
1767
+ };
1768
+ return (properties[prefix] ?? []).map((property) => ({ property, value }));
1769
+ }
1770
+ function tailwindTypographyDeclaration(base) {
1771
+ const declarations = [];
1772
+ const textMatch = base.match(/^text-(.+)$/);
1773
+ if (textMatch) {
1774
+ const token = textMatch[1];
1775
+ const arbitrary = arbitraryTokenValue(token);
1776
+ if (arbitrary && looksLikeCssLength(arbitrary)) {
1777
+ declarations.push({ property: 'font-size', value: arbitrary });
1778
+ }
1779
+ else if (tailwindFontSizeScale[token]) {
1780
+ declarations.push({ property: 'font-size', value: tailwindFontSizeScale[token] });
1781
+ }
1782
+ }
1783
+ const weightMatch = base.match(/^font-(thin|extralight|light|normal|medium|semibold|bold|extrabold|black)$/);
1784
+ if (weightMatch)
1785
+ declarations.push({ property: 'font-weight', value: tailwindFontWeightScale[weightMatch[1]] });
1786
+ const leadingMatch = base.match(/^leading-(.+)$/);
1787
+ if (leadingMatch) {
1788
+ const token = leadingMatch[1];
1789
+ const arbitrary = arbitraryTokenValue(token);
1790
+ const value = arbitrary ?? tailwindLineHeightScale[token] ?? tailwindScaleValue(token);
1791
+ if (value)
1792
+ declarations.push({ property: 'line-height', value });
1793
+ }
1794
+ const trackingMatch = base.match(/^tracking-(.+)$/);
1795
+ if (trackingMatch) {
1796
+ const token = trackingMatch[1];
1797
+ const arbitrary = arbitraryTokenValue(token);
1798
+ const value = arbitrary ?? tailwindTrackingScale[token];
1799
+ if (value)
1800
+ declarations.push({ property: 'letter-spacing', value });
1801
+ }
1802
+ if (base === 'uppercase')
1803
+ declarations.push({ property: 'text-transform', value: 'uppercase' });
1804
+ if (base === 'lowercase')
1805
+ declarations.push({ property: 'text-transform', value: 'lowercase' });
1806
+ if (base === 'capitalize')
1807
+ declarations.push({ property: 'text-transform', value: 'capitalize' });
1808
+ if (base === 'normal-case')
1809
+ declarations.push({ property: 'text-transform', value: 'none' });
1810
+ if (base === 'text-left')
1811
+ declarations.push({ property: 'text-align', value: 'left' });
1812
+ if (base === 'text-center')
1813
+ declarations.push({ property: 'text-align', value: 'center' });
1814
+ if (base === 'text-right')
1815
+ declarations.push({ property: 'text-align', value: 'right' });
1816
+ return declarations;
1817
+ }
1818
+ function tailwindBorderDeclaration(base, styleContext) {
1819
+ if (base === 'border')
1820
+ return [{ property: 'border-width', value: '1px' }];
1821
+ if (/^border-(\d+)$/.test(base)) {
1822
+ const [, value] = base.match(/^border-(\d+)$/) ?? [];
1823
+ return [{ property: 'border-width', value: `${value}px` }];
1824
+ }
1825
+ const roundedMatch = base.match(/^rounded(?:-(.+))?$/);
1826
+ if (roundedMatch) {
1827
+ const token = roundedMatch[1] ?? 'DEFAULT';
1828
+ const arbitrary = arbitraryTokenValue(token);
1829
+ const value = arbitrary ?? tailwindRadiusScale[token] ?? cssTokenValue('radius', styleContext);
1830
+ return value ? [{ property: 'border-radius', value }] : [];
1831
+ }
1832
+ if (base.startsWith('border-') && !/^border-(?:t|r|b|l|x|y)$/.test(base)) {
1833
+ return [{ property: 'border-color', value: tailwindColorValue(base.slice('border-'.length), styleContext) }];
1834
+ }
1835
+ return [];
1836
+ }
1837
+ function tailwindColorDeclaration(base, styleContext) {
1838
+ const bgMatch = base.match(/^bg-(.+)$/);
1839
+ if (bgMatch) {
1840
+ return [{ property: 'background-color', value: tailwindColorValue(bgMatch[1], styleContext) }];
1841
+ }
1842
+ const textMatch = base.match(/^text-(.+)$/);
1843
+ if (textMatch && !tailwindFontSizeScale[textMatch[1]] && !looksLikeCssLength(arbitraryTokenValue(textMatch[1]) ?? '')) {
1844
+ return [{ property: 'color', value: tailwindColorValue(textMatch[1], styleContext) }];
1845
+ }
1846
+ const fillMatch = base.match(/^fill-(.+)$/);
1847
+ if (fillMatch)
1848
+ return [{ property: 'fill', value: tailwindColorValue(fillMatch[1], styleContext) }];
1849
+ const strokeMatch = base.match(/^stroke-(.+)$/);
1850
+ if (strokeMatch)
1851
+ return [{ property: 'stroke', value: tailwindColorValue(strokeMatch[1], styleContext) }];
1852
+ return [];
1853
+ }
1854
+ function tailwindColorValue(token, styleContext) {
1855
+ const arbitrary = arbitraryTokenValue(token);
1856
+ if (arbitrary) {
1857
+ return resolveCssValueTokens(arbitrary.replace(/_/g, ' '), styleContext?.cssVariables);
1858
+ }
1859
+ const [name, opacity] = token.split('/');
1860
+ const cssVariable = cssTokenValue(name, styleContext);
1861
+ const baseValue = cssVariable ?? colorKeywordValue(name) ?? `token(${name})`;
1862
+ return opacity ? `${baseValue} / ${opacityToPercent(opacity)}` : baseValue;
1863
+ }
1864
+ function cssTokenValue(token, styleContext) {
1865
+ const candidates = (0, utils_1.unique)([`--${token}`, `--color-${token}`, `--${token.replace(/\//g, '-')}`]);
1866
+ const match = candidates.find((candidate) => styleContext?.cssVariables.has(candidate));
1867
+ return match ? `var(${match})` : undefined;
1868
+ }
1869
+ function colorKeywordValue(token) {
1870
+ const colors = {
1871
+ transparent: 'transparent',
1872
+ current: 'currentColor',
1873
+ black: '#000000',
1874
+ white: '#ffffff',
1875
+ };
1876
+ return colors[token];
1877
+ }
1878
+ function opacityToPercent(value) {
1879
+ const numeric = Number(value);
1880
+ if (Number.isFinite(numeric)) {
1881
+ return `${numeric}%`;
1882
+ }
1883
+ return value;
1884
+ }
1885
+ function tailwindScaleValue(token) {
1886
+ const arbitrary = arbitraryTokenValue(token);
1887
+ if (arbitrary) {
1888
+ return arbitrary.replace(/_/g, ' ');
1889
+ }
1890
+ const negative = token.startsWith('-');
1891
+ const lookup = negative ? token.slice(1) : token;
1892
+ const value = tailwindSpacingScale[lookup];
1893
+ return value ? `${negative ? '-' : ''}${value}` : undefined;
1894
+ }
1895
+ function tailwindNamedSizeValue(token) {
1896
+ const values = {
1897
+ auto: 'auto',
1898
+ full: '100%',
1899
+ screen: '100vw',
1900
+ svw: '100svw',
1901
+ lvw: '100lvw',
1902
+ dvw: '100dvw',
1903
+ min: 'min-content',
1904
+ max: 'max-content',
1905
+ fit: 'fit-content',
1906
+ prose: '65ch',
1907
+ };
1908
+ return values[token];
1909
+ }
1910
+ function arbitraryTokenValue(token) {
1911
+ const match = token.match(/^\[(.+)\]$/);
1912
+ return match?.[1];
1913
+ }
1914
+ function arbitraryValue(base) {
1915
+ return base.match(/\[(.+)\]/)?.[1] ?? '';
1916
+ }
1917
+ function looksLikeCssLength(value) {
1918
+ return /^-?(?:\d|\.)/.test(value) || /^var\(--/.test(value) || /^clamp\(/.test(value);
1919
+ }
1920
+ function tailwindShadowValue(base) {
1921
+ const arbitrary = arbitraryValue(base);
1922
+ if (arbitrary) {
1923
+ return arbitrary.replace(/_/g, ' ');
1924
+ }
1925
+ if (base === 'shadow-none') {
1926
+ return 'none';
1927
+ }
1928
+ return `tailwind(${base})`;
1929
+ }
1930
+ function bootstrapDeclarationsForClass(className, styleContext) {
1931
+ const base = (0, utils_1.classBase)(className).replace(/^!/, '');
1932
+ if (!isBootstrapClass(base)) {
1933
+ return [];
1934
+ }
1935
+ const prefixes = bootstrapResponsivePrefixes(base);
1936
+ const normalized = stripBootstrapResponsiveInfix(base);
1937
+ const declaration = (property, value) => ({
1938
+ property,
1939
+ value: resolveCssValueTokens(value, styleContext?.cssVariables),
1940
+ source: 'bootstrap-utility',
1941
+ styleSystem: 'bootstrap',
1942
+ className,
1943
+ prefixes,
1944
+ cascadeRank: 20,
1945
+ });
1946
+ const declarations = [];
1947
+ declarations.push(...bootstrapComponentDeclarations(normalized).map((item) => declaration(item.property, item.value)));
1948
+ declarations.push(...bootstrapSpacingDeclarations(normalized).map((item) => declaration(item.property, item.value)));
1949
+ declarations.push(...bootstrapDisplayDeclarations(normalized).map((item) => declaration(item.property, item.value)));
1950
+ declarations.push(...bootstrapFlexDeclarations(normalized).map((item) => declaration(item.property, item.value)));
1951
+ declarations.push(...bootstrapSizingDeclarations(normalized).map((item) => declaration(item.property, item.value)));
1952
+ declarations.push(...bootstrapTypographyDeclarations(normalized).map((item) => declaration(item.property, item.value)));
1953
+ declarations.push(...bootstrapColorDeclarations(normalized).map((item) => declaration(item.property, item.value)));
1954
+ declarations.push(...bootstrapBorderDeclarations(normalized).map((item) => declaration(item.property, item.value)));
1955
+ declarations.push(...bootstrapGridDeclarations(normalized).map((item) => declaration(item.property, item.value)));
1956
+ return declarations;
1957
+ }
1958
+ function isBootstrapClass(className) {
1959
+ const base = stripBootstrapResponsiveInfix((0, utils_1.classBase)(className));
1960
+ return /^(?:container|container-fluid|row|col|col-\d+|card|card-(?:body|title|text|header|footer)|btn|btn-(?:primary|secondary|success|danger|warning|info|light|dark|link|outline-primary|outline-secondary|outline-success|outline-danger|outline-warning|outline-info|outline-light|outline-dark)|badge|rounded|rounded-\d|rounded-pill|rounded-circle|border|border-\d|border-(?:0|top|end|bottom|start|primary|secondary|success|danger|warning|info|light|dark|white)|bg-|text-|fw-|fs-|lh-|display-|lead|small|d-|flex-|justify-content-|align-items-|align-self-|order-|m[stebxy]?-|p[stebxy]?-|gap-|w-|h-|mw-|mh-|min-vh-|min-vw-|vw-|vh-|form-|input-group|navbar|nav|nav-|list-group|modal|alert|shadow|overflow-|position-|top-|bottom-|start-|end-|translate-middle|visually-hidden)/.test(base);
1961
+ }
1962
+ function bootstrapResponsivePrefixes(className) {
1963
+ const match = className.match(/^(?:d|col|offset|order|m[stebxy]?|p[stebxy]?|gap|text|float|flex|justify-content|align-items|align-self)-((?:sm|md|lg|xl|xxl))-/);
1964
+ return match ? [match[1]] : [];
1965
+ }
1966
+ function stripBootstrapResponsiveInfix(className) {
1967
+ return className.replace(/^(d|col|offset|order|m[stebxy]?|p[stebxy]?|gap|text|float|flex|justify-content|align-items|align-self)-(sm|md|lg|xl|xxl)-/, '$1-');
1968
+ }
1969
+ function bootstrapComponentDeclarations(base) {
1970
+ const declarations = [];
1971
+ if (base === 'container' || base === 'container-fluid') {
1972
+ declarations.push({ property: 'width', value: '100%' }, { property: 'padding-right', value: 'calc(var(--bs-gutter-x, 1.5rem) * 0.5)' }, { property: 'padding-left', value: 'calc(var(--bs-gutter-x, 1.5rem) * 0.5)' }, { property: 'margin-right', value: 'auto' }, { property: 'margin-left', value: 'auto' });
1973
+ }
1974
+ if (base === 'row') {
1975
+ declarations.push({ property: 'display', value: 'flex' }, { property: 'flex-wrap', value: 'wrap' }, { property: 'margin-top', value: 'calc(-1 * var(--bs-gutter-y, 0))' }, { property: 'margin-right', value: 'calc(-0.5 * var(--bs-gutter-x, 1.5rem))' }, { property: 'margin-left', value: 'calc(-0.5 * var(--bs-gutter-x, 1.5rem))' });
1976
+ }
1977
+ if (base === 'card') {
1978
+ declarations.push({ property: 'position', value: 'relative' }, { property: 'display', value: 'flex' }, { property: 'flex-direction', value: 'column' }, { property: 'min-width', value: '0' }, { property: 'color', value: 'var(--bs-body-color)' }, { property: 'background-color', value: 'var(--bs-card-bg)' }, { property: 'border', value: 'var(--bs-card-border-width) solid var(--bs-card-border-color)' }, { property: 'border-radius', value: 'var(--bs-card-border-radius)' });
1979
+ }
1980
+ if (base === 'card-body') {
1981
+ declarations.push({ property: 'flex', value: '1 1 auto' }, { property: 'padding', value: 'var(--bs-card-spacer-y) var(--bs-card-spacer-x)' }, { property: 'color', value: 'var(--bs-card-color)' });
1982
+ }
1983
+ if (base === 'btn') {
1984
+ declarations.push({ property: 'display', value: 'inline-block' }, { property: 'padding', value: 'var(--bs-btn-padding-y) var(--bs-btn-padding-x)' }, { property: 'font-size', value: 'var(--bs-btn-font-size)' }, { property: 'font-weight', value: 'var(--bs-btn-font-weight)' }, { property: 'line-height', value: 'var(--bs-btn-line-height)' }, { property: 'text-align', value: 'center' }, { property: 'text-decoration', value: 'none' }, { property: 'vertical-align', value: 'middle' }, { property: 'cursor', value: 'pointer' }, { property: 'border', value: 'var(--bs-btn-border-width) solid var(--bs-btn-border-color)' }, { property: 'border-radius', value: 'var(--bs-btn-border-radius)' });
1985
+ }
1986
+ if (base.startsWith('btn-') && base !== 'btn-link') {
1987
+ const tone = base.replace(/^btn-outline-/, '').replace(/^btn-/, '');
1988
+ const outline = base.startsWith('btn-outline-');
1989
+ declarations.push({ property: 'color', value: outline ? `var(--bs-${tone})` : `var(--bs-btn-color)` }, {
1990
+ property: 'background-color',
1991
+ value: outline ? 'transparent' : `var(--bs-${tone})`,
1992
+ }, { property: 'border-color', value: `var(--bs-${tone})` });
1993
+ }
1994
+ if (base === 'btn-link') {
1995
+ declarations.push({ property: 'font-weight', value: '400' }, { property: 'color', value: 'var(--bs-link-color)' }, { property: 'text-decoration', value: 'underline' }, { property: 'background-color', value: 'transparent' }, { property: 'border-color', value: 'transparent' });
1996
+ }
1997
+ if (base === 'badge') {
1998
+ declarations.push({ property: 'display', value: 'inline-block' }, { property: 'padding', value: '0.35em 0.65em' }, { property: 'font-size', value: '0.75em' }, { property: 'font-weight', value: '700' }, { property: 'line-height', value: '1' }, { property: 'color', value: '#fff' }, { property: 'text-align', value: 'center' }, { property: 'white-space', value: 'nowrap' }, { property: 'vertical-align', value: 'baseline' }, { property: 'border-radius', value: 'var(--bs-border-radius)' });
1999
+ }
2000
+ if (base === 'form-control' || base === 'form-select') {
2001
+ declarations.push({ property: 'display', value: 'block' }, { property: 'width', value: '100%' }, { property: 'padding', value: '0.375rem 0.75rem' }, { property: 'font-size', value: '1rem' }, { property: 'font-weight', value: '400' }, { property: 'line-height', value: '1.5' }, { property: 'color', value: 'var(--bs-body-color)' }, { property: 'background-color', value: 'var(--bs-body-bg)' }, { property: 'border', value: 'var(--bs-border-width) solid var(--bs-border-color)' }, { property: 'border-radius', value: 'var(--bs-border-radius)' });
2002
+ }
2003
+ if (base === 'form-label') {
2004
+ declarations.push({ property: 'margin-bottom', value: '0.5rem' }, { property: 'display', value: 'inline-block' });
2005
+ }
2006
+ if (base === 'navbar') {
2007
+ declarations.push({ property: 'position', value: 'relative' }, { property: 'display', value: 'flex' }, { property: 'flex-wrap', value: 'wrap' }, { property: 'align-items', value: 'center' }, { property: 'justify-content', value: 'space-between' }, { property: 'padding', value: 'var(--bs-navbar-padding-y) var(--bs-navbar-padding-x)' });
2008
+ }
2009
+ if (base === 'nav') {
2010
+ declarations.push({ property: 'display', value: 'flex' }, { property: 'flex-wrap', value: 'wrap' }, { property: 'padding-left', value: '0' }, { property: 'margin-bottom', value: '0' }, { property: 'list-style', value: 'none' });
2011
+ }
2012
+ if (base === 'nav-link') {
2013
+ declarations.push({ property: 'display', value: 'block' }, { property: 'padding', value: 'var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x)' }, { property: 'font-size', value: 'var(--bs-nav-link-font-size)' }, { property: 'font-weight', value: 'var(--bs-nav-link-font-weight)' }, { property: 'color', value: 'var(--bs-nav-link-color)' }, { property: 'text-decoration', value: 'none' });
2014
+ }
2015
+ if (base === 'alert') {
2016
+ declarations.push({ property: 'position', value: 'relative' }, { property: 'padding', value: 'var(--bs-alert-padding-y) var(--bs-alert-padding-x)' }, { property: 'margin-bottom', value: '1rem' }, { property: 'border', value: 'var(--bs-alert-border) solid var(--bs-alert-border-color)' }, { property: 'border-radius', value: 'var(--bs-alert-border-radius)' });
2017
+ }
2018
+ return declarations;
2019
+ }
2020
+ function bootstrapSpacingDeclarations(base) {
2021
+ const match = base.match(/^(m|p)([stebxy]?)-([0-5]|auto)$/);
2022
+ if (!match) {
2023
+ return [];
2024
+ }
2025
+ const [, kind, direction, token] = match;
2026
+ const value = bootstrapSpacingScale[token];
2027
+ const propertyRoot = kind === 'm' ? 'margin' : 'padding';
2028
+ const directionMap = {
2029
+ '': [propertyRoot],
2030
+ t: [`${propertyRoot}-top`],
2031
+ e: [`${propertyRoot}-right`],
2032
+ b: [`${propertyRoot}-bottom`],
2033
+ s: [`${propertyRoot}-left`],
2034
+ x: [`${propertyRoot}-left`, `${propertyRoot}-right`],
2035
+ y: [`${propertyRoot}-top`, `${propertyRoot}-bottom`],
2036
+ };
2037
+ return (directionMap[direction] ?? []).map((property) => ({ property, value }));
2038
+ }
2039
+ function bootstrapDisplayDeclarations(base) {
2040
+ const match = base.match(/^d-(none|inline|inline-block|block|grid|inline-grid|table|table-row|table-cell|flex|inline-flex)$/);
2041
+ return match ? [{ property: 'display', value: match[1] }] : [];
2042
+ }
2043
+ function bootstrapFlexDeclarations(base) {
2044
+ const declarations = [];
2045
+ const justifyMap = {
2046
+ start: 'flex-start',
2047
+ end: 'flex-end',
2048
+ center: 'center',
2049
+ between: 'space-between',
2050
+ around: 'space-around',
2051
+ evenly: 'space-evenly',
2052
+ };
2053
+ const alignMap = {
2054
+ start: 'flex-start',
2055
+ end: 'flex-end',
2056
+ center: 'center',
2057
+ baseline: 'baseline',
2058
+ stretch: 'stretch',
2059
+ };
2060
+ if (base === 'flex-row')
2061
+ declarations.push({ property: 'flex-direction', value: 'row' });
2062
+ if (base === 'flex-column')
2063
+ declarations.push({ property: 'flex-direction', value: 'column' });
2064
+ if (base === 'flex-wrap')
2065
+ declarations.push({ property: 'flex-wrap', value: 'wrap' });
2066
+ if (base === 'flex-nowrap')
2067
+ declarations.push({ property: 'flex-wrap', value: 'nowrap' });
2068
+ const justify = base.match(/^justify-content-(start|end|center|between|around|evenly)$/);
2069
+ if (justify)
2070
+ declarations.push({ property: 'justify-content', value: justifyMap[justify[1]] });
2071
+ const alignItems = base.match(/^align-items-(start|end|center|baseline|stretch)$/);
2072
+ if (alignItems)
2073
+ declarations.push({ property: 'align-items', value: alignMap[alignItems[1]] });
2074
+ const alignSelf = base.match(/^align-self-(start|end|center|baseline|stretch)$/);
2075
+ if (alignSelf)
2076
+ declarations.push({ property: 'align-self', value: alignMap[alignSelf[1]] });
2077
+ return declarations;
2078
+ }
2079
+ function bootstrapSizingDeclarations(base) {
2080
+ const percentMatch = base.match(/^(w|h|mw|mh)-(\d+)$/);
2081
+ if (percentMatch) {
2082
+ const [, prefix, amount] = percentMatch;
2083
+ const propertyMap = {
2084
+ w: 'width',
2085
+ h: 'height',
2086
+ mw: 'max-width',
2087
+ mh: 'max-height',
2088
+ };
2089
+ return [{ property: propertyMap[prefix], value: `${amount}%` }];
2090
+ }
2091
+ if (base === 'min-vh-100')
2092
+ return [{ property: 'min-height', value: '100vh' }];
2093
+ if (base === 'min-vw-100')
2094
+ return [{ property: 'min-width', value: '100vw' }];
2095
+ if (base === 'vh-100')
2096
+ return [{ property: 'height', value: '100vh' }];
2097
+ if (base === 'vw-100')
2098
+ return [{ property: 'width', value: '100vw' }];
2099
+ return [];
2100
+ }
2101
+ function bootstrapTypographyDeclarations(base) {
2102
+ const declarations = [];
2103
+ const fsMatch = base.match(/^fs-([1-6])$/);
2104
+ if (fsMatch)
2105
+ declarations.push({ property: 'font-size', value: bootstrapFontSizeScale[fsMatch[1]] });
2106
+ const fwMap = {
2107
+ lighter: 'lighter',
2108
+ light: '300',
2109
+ normal: '400',
2110
+ semibold: '600',
2111
+ bold: '700',
2112
+ bolder: 'bolder',
2113
+ };
2114
+ const fwMatch = base.match(/^fw-(lighter|light|normal|semibold|bold|bolder)$/);
2115
+ if (fwMatch)
2116
+ declarations.push({ property: 'font-weight', value: fwMap[fwMatch[1]] });
2117
+ const textAlign = base.match(/^text-(start|end|center)$/);
2118
+ if (textAlign) {
2119
+ declarations.push({ property: 'text-align', value: textAlign[1] === 'start' ? 'left' : textAlign[1] === 'end' ? 'right' : 'center' });
2120
+ }
2121
+ if (base === 'text-uppercase')
2122
+ declarations.push({ property: 'text-transform', value: 'uppercase' });
2123
+ if (base === 'text-lowercase')
2124
+ declarations.push({ property: 'text-transform', value: 'lowercase' });
2125
+ if (base === 'text-capitalize')
2126
+ declarations.push({ property: 'text-transform', value: 'capitalize' });
2127
+ if (base === 'lead') {
2128
+ declarations.push({ property: 'font-size', value: '1.25rem' }, { property: 'font-weight', value: '300' });
2129
+ }
2130
+ if (base === 'small')
2131
+ declarations.push({ property: 'font-size', value: '0.875em' });
2132
+ const lhMap = { '1': '1', sm: '1.25', base: '1.5', lg: '2' };
2133
+ const lhMatch = base.match(/^lh-(1|sm|base|lg)$/);
2134
+ if (lhMatch)
2135
+ declarations.push({ property: 'line-height', value: lhMap[lhMatch[1]] });
2136
+ return declarations;
2137
+ }
2138
+ function bootstrapColorDeclarations(base) {
2139
+ const declarations = [];
2140
+ const bgMatch = base.match(/^bg-(primary|secondary|success|danger|warning|info|light|dark|body|white|transparent)$/);
2141
+ if (bgMatch) {
2142
+ declarations.push({
2143
+ property: 'background-color',
2144
+ value: bgMatch[1] === 'transparent' ? 'transparent' : bootstrapThemeColors[bgMatch[1]],
2145
+ });
2146
+ }
2147
+ const textMatch = base.match(/^text-(primary|secondary|success|danger|warning|info|light|dark|body|muted|white|black)$/);
2148
+ if (textMatch)
2149
+ declarations.push({ property: 'color', value: bootstrapThemeColors[textMatch[1]] });
2150
+ return declarations;
2151
+ }
2152
+ function bootstrapBorderDeclarations(base) {
2153
+ const declarations = [];
2154
+ if (base === 'border')
2155
+ declarations.push({ property: 'border', value: 'var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)' });
2156
+ if (base === 'border-0')
2157
+ declarations.push({ property: 'border', value: '0' });
2158
+ if (/^border-[1-5]$/.test(base)) {
2159
+ declarations.push({ property: 'border-width', value: `${base.replace('border-', '')}px` });
2160
+ }
2161
+ const borderColor = base.match(/^border-(primary|secondary|success|danger|warning|info|light|dark|white)$/);
2162
+ if (borderColor)
2163
+ declarations.push({ property: 'border-color', value: bootstrapThemeColors[borderColor[1]] });
2164
+ if (base === 'rounded')
2165
+ declarations.push({ property: 'border-radius', value: 'var(--bs-border-radius)' });
2166
+ if (base === 'rounded-0')
2167
+ declarations.push({ property: 'border-radius', value: '0' });
2168
+ if (base === 'rounded-1')
2169
+ declarations.push({ property: 'border-radius', value: 'var(--bs-border-radius-sm)' });
2170
+ if (base === 'rounded-2')
2171
+ declarations.push({ property: 'border-radius', value: 'var(--bs-border-radius)' });
2172
+ if (base === 'rounded-3')
2173
+ declarations.push({ property: 'border-radius', value: 'var(--bs-border-radius-lg)' });
2174
+ if (base === 'rounded-4')
2175
+ declarations.push({ property: 'border-radius', value: 'var(--bs-border-radius-xl)' });
2176
+ if (base === 'rounded-5')
2177
+ declarations.push({ property: 'border-radius', value: 'var(--bs-border-radius-xxl)' });
2178
+ if (base === 'rounded-pill')
2179
+ declarations.push({ property: 'border-radius', value: 'var(--bs-border-radius-pill)' });
2180
+ if (base === 'rounded-circle')
2181
+ declarations.push({ property: 'border-radius', value: '50%' });
2182
+ if (base === 'shadow')
2183
+ declarations.push({ property: 'box-shadow', value: 'var(--bs-box-shadow)' });
2184
+ if (base === 'shadow-sm')
2185
+ declarations.push({ property: 'box-shadow', value: 'var(--bs-box-shadow-sm)' });
2186
+ if (base === 'shadow-lg')
2187
+ declarations.push({ property: 'box-shadow', value: 'var(--bs-box-shadow-lg)' });
2188
+ if (base === 'shadow-none')
2189
+ declarations.push({ property: 'box-shadow', value: 'none' });
2190
+ return declarations;
2191
+ }
2192
+ function bootstrapGridDeclarations(base) {
2193
+ if (base === 'col') {
2194
+ return [
2195
+ { property: 'flex', value: '1 0 0%' },
2196
+ { property: 'min-width', value: '0' },
2197
+ ];
2198
+ }
2199
+ const colMatch = base.match(/^col-(\d{1,2})$/);
2200
+ if (colMatch) {
2201
+ const columns = Math.min(12, Math.max(1, Number(colMatch[1])));
2202
+ const width = `${(columns / 12) * 100}%`;
2203
+ return [
2204
+ { property: 'flex', value: '0 0 auto' },
2205
+ { property: 'width', value: width },
2206
+ ];
2207
+ }
2208
+ const gapMatch = base.match(/^gap-([0-5])$/);
2209
+ if (gapMatch)
2210
+ return [{ property: 'gap', value: bootstrapSpacingScale[gapMatch[1]] }];
2211
+ return [];
2212
+ }
2213
+ function categorizeResolvedStyleDeclarations(declarations) {
2214
+ const categories = {};
2215
+ for (const declaration of declarations) {
2216
+ const category = resolvedStyleCategory(declaration.property);
2217
+ categories[category] = (0, utils_1.uniqueBy)([...(categories[category] ?? []), declaration], (item) => `${item.className}:${item.property}:${item.value}:${item.source}:${item.selector ?? ''}`).slice(0, 80);
2218
+ }
2219
+ return categories;
2220
+ }
2221
+ function flattenResolvedStyleCategories(categories) {
2222
+ return (0, utils_1.uniqueBy)(Object.values(categories).flat(), (declaration) => `${declaration.className}:${declaration.property}:${declaration.value}:${declaration.source}:${declaration.selector ?? ''}`);
2223
+ }
2224
+ function createStyleSystemEvidence(classes, declarations, styleContext) {
2225
+ const classSystems = new Map();
2226
+ for (const className of classes) {
2227
+ const systems = new Set();
2228
+ const base = (0, utils_1.classBase)(className).replace(/^!/, '');
2229
+ if (styleContext?.classRules.has(className) ||
2230
+ styleContext?.classRules.has(base) ||
2231
+ styleContext?.classRules.has(base.replace(/^!/, ''))) {
2232
+ systems.add('raw-css');
2233
+ }
2234
+ if (tailwindDeclarationsForClass(className, styleContext).length || isLikelyTailwindClass(className)) {
2235
+ systems.add('tailwind');
2236
+ }
2237
+ if (bootstrapDeclarationsForClass(className, styleContext).length || isBootstrapClass(className)) {
2238
+ systems.add('bootstrap');
2239
+ }
2240
+ if (!systems.size) {
2241
+ systems.add('unknown');
2242
+ }
2243
+ classSystems.set(className, systems);
2244
+ }
2245
+ const observed = (0, utils_1.unique)([
2246
+ ...Array.from(classSystems.values()).flatMap((systems) => Array.from(systems)),
2247
+ ...declarations.map((declaration) => declaration.styleSystem),
2248
+ ]).filter((system) => system !== 'unknown');
2249
+ const unresolvedClasses = classes.filter((className) => classSystems.get(className)?.has('unknown'));
2250
+ const classCounts = Object.fromEntries(['raw-css', 'tailwind', 'bootstrap', 'unknown'].map((system) => [
2251
+ system,
2252
+ Array.from(classSystems.values()).filter((systems) => systems.has(system)).length,
2253
+ ]));
2254
+ return omitEmptyRecord({
2255
+ primary: observed.length === 1 ? observed[0] : observed.length > 1 ? 'mixed' : 'unknown',
2256
+ observed,
2257
+ projectDetected: styleContext?.detectedStyleSystems ?? [],
2258
+ classCounts,
2259
+ unresolvedClasses: unresolvedClasses.slice(0, 80),
2260
+ declarationCounts: Object.fromEntries(['raw-css', 'tailwind', 'bootstrap'].map((system) => [
2261
+ system,
2262
+ declarations.filter((declaration) => declaration.styleSystem === system).length,
2263
+ ])),
2264
+ });
2265
+ }
2266
+ function isLikelyTailwindClass(className) {
2267
+ const base = (0, utils_1.classBase)(className).replace(/^!/, '');
2268
+ return Boolean(className.includes(':') ||
2269
+ /\[[^\]]+\]/.test(base) ||
2270
+ /^(?:bg|text|border|rounded|shadow|opacity|blur|backdrop|flex|grid|inline-flex|items|justify|content|self|place|gap|p[trblxy]?|m[trblxy]?|w|h|min-w|min-h|max-w|max-h|size|font|leading|tracking|uppercase|lowercase|capitalize|line-clamp|overflow|whitespace|truncate|relative|absolute|fixed|sticky|inset|top|right|bottom|left|z|col-span|row-span|from|via|to|fill|stroke)-/.test(base));
2271
+ }
2272
+ function resolvedStyleCategory(property) {
2273
+ if (['color', 'background', 'background-color', 'fill', 'stroke'].includes(property)) {
2274
+ return 'color';
2275
+ }
2276
+ if ([
2277
+ 'font-size',
2278
+ 'font-weight',
2279
+ 'line-height',
2280
+ 'letter-spacing',
2281
+ 'text-transform',
2282
+ 'text-align',
2283
+ 'font-family',
2284
+ ].includes(property)) {
2285
+ return 'typography';
2286
+ }
2287
+ if (/^(?:border|outline|ring)|border-radius/.test(property)) {
2288
+ return 'border';
2289
+ }
2290
+ if (['box-shadow', 'opacity', 'filter', 'backdrop-filter'].includes(property)) {
2291
+ return 'effects';
2292
+ }
2293
+ if (['transition', 'transition-property', 'transition-duration', 'animation', 'transform'].includes(property)) {
2294
+ return 'motion';
2295
+ }
2296
+ if (property === 'display') {
2297
+ return 'display';
2298
+ }
2299
+ if (/^(?:align|justify|place)/.test(property)) {
2300
+ return 'alignment';
2301
+ }
2302
+ if (/^grid/.test(property)) {
2303
+ return 'grid';
2304
+ }
2305
+ if (/^flex/.test(property)) {
2306
+ return 'flex';
2307
+ }
2308
+ if (/^(?:padding|margin|gap|row-gap|column-gap)/.test(property)) {
2309
+ return 'spacing';
2310
+ }
2311
+ if (/^(?:width|height|min-|max-)/.test(property)) {
2312
+ return /^(?:min-|max-)/.test(property) ? 'minMax' : 'sizing';
2313
+ }
2314
+ if (['position', 'inset', 'top', 'right', 'bottom', 'left', 'z-index', 'object-fit'].includes(property)) {
2315
+ return 'position';
2316
+ }
2317
+ if (['overflow', 'overflow-x', 'overflow-y'].includes(property)) {
2318
+ return 'overflow';
2319
+ }
2320
+ if (['white-space', 'overflow-wrap', 'text-overflow', '-webkit-line-clamp'].includes(property)) {
2321
+ return 'textFlow';
2322
+ }
2323
+ return 'layout';
2324
+ }
2325
+ function mergeResolvedStyleCategories(current, next) {
2326
+ const merged = { ...current };
2327
+ for (const [category, declarations] of Object.entries(next)) {
2328
+ const existing = Array.isArray(merged[category])
2329
+ ? merged[category]
2330
+ : [];
2331
+ merged[category] = (0, utils_1.uniqueBy)([...existing, ...declarations], (declaration) => `${declaration.className}:${declaration.property}:${declaration.value}:${declaration.source}:${declaration.selector ?? ''}`).slice(0, 80);
2332
+ }
2333
+ return omitEmptyRecord(merged);
2334
+ }
2335
+ function classBucketRecord(value) {
2336
+ const buckets = {};
2337
+ for (const [key, item] of Object.entries(value)) {
2338
+ if (Array.isArray(item) && item.every((value) => typeof value === 'string')) {
2339
+ buckets[key] = item;
2340
+ }
2341
+ }
2342
+ return buckets;
2343
+ }
2344
+ function resolvedStyleRecord(value) {
2345
+ return isRecordValue(value) ? value : {};
2346
+ }
2347
+ function createUiContract(input) {
2348
+ const defaultClasses = (0, utils_1.unique)(input.defaultClasses?.length ? input.defaultClasses : input.classes);
2349
+ const conditionalClasses = (0, utils_1.unique)(input.conditionalClasses ?? []);
2350
+ const decorativeAccentClasses = (0, utils_1.unique)(input.decorativeAccentClasses ?? []);
2351
+ const allClasses = (0, utils_1.unique)([...defaultClasses, ...conditionalClasses, ...decorativeAccentClasses]);
2352
+ const grouped = input.classGroups ?? (0, extractors_1.groupTailwindClasses)(allClasses);
2353
+ const resolvedStyles = categorizeResolvedStyleDeclarations(resolveUiClassDeclarations(allClasses, input.styleContext));
2354
+ const styleSystemEvidence = createStyleSystemEvidence(allClasses, flattenResolvedStyleCategories(resolvedStyles), input.styleContext);
2355
+ const responsiveVariants = createUiResponsiveVariants(allClasses, input.styleContext);
2356
+ const interactionVariants = createUiInteractionVariants(allClasses, input.styleContext);
2357
+ return {
2358
+ identity: omitEmptyRecord({
2359
+ kind: input.kind,
2360
+ tag: input.tag,
2361
+ layoutRole: input.layoutRole,
2362
+ label: input.label,
2363
+ line: input.line,
2364
+ variants: input.variants,
2365
+ props: input.props,
2366
+ styleHelper: input.styleHelper,
2367
+ styleHelperVariants: input.styleHelperVariants,
2368
+ }),
2369
+ semantics: omitEmptyRecord({
2370
+ kind: input.kind,
2371
+ role: input.semanticRole ?? input.layoutRole,
2372
+ semanticRole: input.semanticRole,
2373
+ semanticProfile: input.semanticProfile,
2374
+ roleSignature: input.roleSignature,
2375
+ }),
2376
+ visual: omitEmptyRecord({
2377
+ color: grouped.color ?? [],
2378
+ typography: grouped.typography ?? [],
2379
+ border: grouped.border ?? [],
2380
+ effects: grouped.effects ?? [],
2381
+ motion: grouped.motion ?? [],
2382
+ decorativeAccentClasses,
2383
+ resolvedStyles: omitEmptyRecord({
2384
+ color: resolvedStyles.color,
2385
+ typography: resolvedStyles.typography,
2386
+ border: resolvedStyles.border,
2387
+ effects: resolvedStyles.effects,
2388
+ motion: resolvedStyles.motion,
2389
+ }),
2390
+ }),
2391
+ layout: omitEmptyRecord({
2392
+ display: filterUiContractClasses(allClasses, (className) => /^(?:flex|inline-flex|grid|inline-grid|block|inline|inline-block|hidden|contents|table)$/.test((0, utils_1.classBase)(className))),
2393
+ alignment: filterUiContractClasses(allClasses, (className) => /^(?:items-|justify-|content-|self-|place-)/.test((0, utils_1.classBase)(className))),
2394
+ grid: filterUiContractClasses(allClasses, (className) => /^(?:grid|grid-cols-|col-|row-)/.test((0, utils_1.classBase)(className))),
2395
+ flex: filterUiContractClasses(allClasses, (className) => /^(?:flex|inline-flex|flex-|basis-|grow|shrink)/.test((0, utils_1.classBase)(className))),
2396
+ spacing: grouped.spacing ?? [],
2397
+ sizing: grouped.sizing ?? [],
2398
+ position: grouped.position ?? [],
2399
+ layout: grouped.layout ?? [],
2400
+ other: grouped.other ?? [],
2401
+ resolvedStyles: omitEmptyRecord({
2402
+ display: resolvedStyles.display,
2403
+ alignment: resolvedStyles.alignment,
2404
+ grid: resolvedStyles.grid,
2405
+ flex: resolvedStyles.flex,
2406
+ spacing: resolvedStyles.spacing,
2407
+ sizing: resolvedStyles.sizing,
2408
+ position: resolvedStyles.position,
2409
+ layout: resolvedStyles.layout,
2410
+ }),
2411
+ }),
2412
+ responsiveVariants,
2413
+ interactionVariants,
2414
+ safetyRules: omitEmptyRecord({
2415
+ layoutSafety: input.layoutSafety,
2416
+ scaleProfile: input.scaleProfile,
2417
+ roleConstraints: input.roleSignature
2418
+ ? omitEmptyRecord({
2419
+ preserveRole: input.roleSignature.role,
2420
+ preserveRoleGroup: input.roleSignature.roleGroup,
2421
+ preserveScale: input.roleSignature.scale,
2422
+ preserveDensity: input.roleSignature.density,
2423
+ preserveSurface: input.roleSignature.surface,
2424
+ preserveLayout: input.roleSignature.layout,
2425
+ exactClassFacts: input.roleSignature.exactClassFacts,
2426
+ flags: input.roleSignature.flags,
2427
+ })
2428
+ : undefined,
2429
+ resolvedStyles: omitEmptyRecord({
2430
+ textFlow: resolvedStyles.textFlow,
2431
+ overflow: resolvedStyles.overflow,
2432
+ minMax: resolvedStyles.minMax,
2433
+ }),
2434
+ }),
2435
+ sourceEvidence: omitEmptyRecord({
2436
+ extractor: 'ui.contract',
2437
+ path: input.path,
2438
+ line: input.line,
2439
+ sourceHash: input.sourceHash,
2440
+ confidence: input.confidence,
2441
+ evidence: input.evidence,
2442
+ classSource: input.classSource,
2443
+ styleSystems: styleSystemEvidence,
2444
+ }),
2445
+ };
2446
+ }
2447
+ function createUiContractForElement(element, file, confidence, styleContext) {
2448
+ return createUiContract({
2449
+ kind: element.kind,
2450
+ tag: element.originalTag,
2451
+ layoutRole: element.layoutRole,
2452
+ label: element.label,
2453
+ line: element.line,
2454
+ path: file?.relPath,
2455
+ sourceHash: file?.sourceHash,
2456
+ confidence,
2457
+ evidence: element.evidence,
2458
+ classSource: element.classSource,
2459
+ classes: element.classes,
2460
+ defaultClasses: element.defaultClasses,
2461
+ conditionalClasses: element.conditionalClasses,
2462
+ decorativeAccentClasses: element.decorativeAccentClasses,
2463
+ classGroups: element.classGroups,
2464
+ layoutSafety: element.layoutSafety,
2465
+ responsiveProfile: element.responsiveProfile,
2466
+ scaleProfile: element.scaleProfile,
2467
+ semanticProfile: element.semanticProfile,
2468
+ roleSignature: element.roleSignature,
2469
+ variants: element.variants,
2470
+ props: element.props,
2471
+ styleHelper: element.styleHelper,
2472
+ styleHelperVariants: element.styleHelperVariants,
2473
+ styleContext,
2474
+ });
2475
+ }
2476
+ function createUiContractForChild(child, styleContext) {
2477
+ return createUiContract({
2478
+ kind: child.kind,
2479
+ tag: child.originalTag,
2480
+ layoutRole: child.layoutRole,
2481
+ semanticRole: child.semanticRole,
2482
+ label: child.label,
2483
+ classes: child.classes,
2484
+ defaultClasses: child.defaultClasses,
2485
+ conditionalClasses: child.conditionalClasses,
2486
+ layoutSafety: child.layoutSafety,
2487
+ responsiveProfile: child.responsiveProfile,
2488
+ scaleProfile: child.scaleProfile,
2489
+ semanticProfile: child.semanticProfile,
2490
+ roleSignature: child.roleSignature,
2491
+ variants: child.variants,
2492
+ props: child.props,
2493
+ styleHelper: child.styleHelper,
2494
+ styleHelperVariants: child.styleHelperVariants,
2495
+ styleContext,
2496
+ });
2497
+ }
2498
+ function createUiPatternContract(input) {
2499
+ const contract = createUiContract(input);
2500
+ return {
2501
+ ...contract,
2502
+ identity: omitEmptyRecord({
2503
+ ...contract.identity,
2504
+ name: input.name,
2505
+ count: input.count,
2506
+ }),
2507
+ };
2508
+ }
2509
+ function createUiRelationships(input) {
2510
+ return {
2511
+ ...(input.parentContext ? { parentContext: input.parentContext } : {}),
2512
+ ...(input.childStructureContract
2513
+ ? { childStructureContract: input.childStructureContract }
2514
+ : {}),
2515
+ ...(input.compoundStructure ? { compoundStructure: input.compoundStructure } : {}),
2516
+ graphKeys: input.graphKeys ?? [],
2517
+ };
2518
+ }
2519
+ function createUiResponsiveVariants(classes, styleContext) {
2520
+ const variants = {};
2521
+ for (const className of classes) {
2522
+ const responsivePrefixes = responsivePrefixesForClass(className);
2523
+ for (const responsivePrefix of responsivePrefixes) {
2524
+ variants[responsivePrefix] = mergeUiContractClassBuckets(classBucketRecord(variants[responsivePrefix] ?? {}), uiContractClassBucket(className));
2525
+ const resolved = categorizeResolvedStyleDeclarations(resolveUiClassDeclarations([className], styleContext));
2526
+ variants[responsivePrefix].resolvedStyles = mergeResolvedStyleCategories(resolvedStyleRecord(variants[responsivePrefix].resolvedStyles), resolved);
2527
+ }
2528
+ }
2529
+ return variants;
2530
+ }
2531
+ function createUiInteractionVariants(classes, styleContext) {
2532
+ const variants = {};
2533
+ for (const className of classes) {
2534
+ const prefixes = className.split(':').slice(0, -1);
2535
+ const statePrefixes = prefixes.filter(isInteractionPrefix);
2536
+ for (const prefix of statePrefixes) {
2537
+ variants[prefix] = mergeUiContractClassBuckets(classBucketRecord(variants[prefix] ?? {}), uiContractClassBucket(className));
2538
+ const resolved = categorizeResolvedStyleDeclarations(resolveUiClassDeclarations([className], styleContext));
2539
+ variants[prefix].resolvedStyles = mergeResolvedStyleCategories(resolvedStyleRecord(variants[prefix].resolvedStyles), resolved);
2540
+ }
2541
+ }
2542
+ return variants;
2543
+ }
2544
+ function uiContractClassBucket(className) {
2545
+ const base = (0, utils_1.classBase)(className);
2546
+ if ((0, extractors_1.isLayoutClass)(className)) {
2547
+ return { layout: [className] };
2548
+ }
2549
+ if ((0, extractors_1.isSpacingClass)(className)) {
2550
+ return { spacing: [className] };
2551
+ }
2552
+ if ((0, extractors_1.isSizingClass)(className)) {
2553
+ return { sizing: [className] };
2554
+ }
2555
+ if ((0, extractors_1.isTypographyClass)(className)) {
2556
+ return { typography: [className] };
2557
+ }
2558
+ if (/^(?:bg-|from-|via-|to-|text-|decoration-|accent-|caret-|fill-|stroke-)/.test(base)) {
2559
+ return { color: [className] };
2560
+ }
2561
+ if (/^(?:rounded|border|divide-|outline|ring)/.test(base)) {
2562
+ return { border: [className] };
2563
+ }
2564
+ if (/^(?:shadow|opacity|blur|backdrop|mix-|bg-blend|drop-shadow|filter)/.test(base)) {
2565
+ return { effects: [className] };
2566
+ }
2567
+ if (/^(?:absolute|relative|fixed|sticky|z-|overflow-|object-)/.test(base)) {
2568
+ return { position: [className] };
2569
+ }
2570
+ if (/^(?:transition|duration|ease|delay|animate-|transform|scale-|rotate|translate-)/.test(base)) {
2571
+ return { motion: [className] };
2572
+ }
2573
+ return { other: [className] };
2574
+ }
2575
+ function mergeUiContractClassBuckets(current, next) {
2576
+ const merged = { ...current };
2577
+ for (const [bucket, classes] of Object.entries(next)) {
2578
+ merged[bucket] = (0, utils_1.unique)([...(merged[bucket] ?? []), ...classes]).slice(0, 120);
2579
+ }
2580
+ return merged;
2581
+ }
2582
+ function filterUiContractClasses(classes, predicate) {
2583
+ return (0, utils_1.unique)(classes.filter(predicate)).slice(0, 120);
2584
+ }
2585
+ function isResponsivePrefix(prefix) {
2586
+ return /^(?:sm|md|lg|xl|2xl|min-|max-)/.test(prefix);
2587
+ }
2588
+ function responsivePrefixesForClass(className) {
2589
+ const prefixes = className.split(':').slice(0, -1).filter(isResponsivePrefix);
2590
+ const bootstrapPrefixes = bootstrapResponsivePrefixes(className);
2591
+ return (0, utils_1.unique)([...prefixes, ...bootstrapPrefixes]);
2592
+ }
2593
+ function isInteractionPrefix(prefix) {
2594
+ return /^(?:hover|focus|focus-visible|focus-within|active|disabled|visited|checked|selected|open|dark|group|group-hover|peer|peer-hover|aria|data)/.test(prefix);
2595
+ }
2596
+ function omitEmptyRecord(value) {
2597
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => {
2598
+ if (item === undefined || item === null) {
2599
+ return false;
2600
+ }
2601
+ if (Array.isArray(item)) {
2602
+ return item.length > 0;
2603
+ }
2604
+ if (typeof item === 'object') {
2605
+ return Object.keys(item).length > 0;
2606
+ }
2607
+ return true;
2608
+ }));
2609
+ }
2610
+ function mergeLayoutSafetyProfiles(items) {
2611
+ const profiles = items.map((item) => item.layoutSafety).filter(isLayoutSafetyProfile);
2612
+ if (!profiles.length) {
2613
+ return undefined;
2614
+ }
2615
+ const merged = {
2616
+ wrapClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.wrapClasses)).slice(0, 80),
2617
+ overflowClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.overflowClasses)).slice(0, 80),
2618
+ minSizeClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.minSizeClasses)).slice(0, 80),
2619
+ maxSizeClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.maxSizeClasses)).slice(0, 80),
2620
+ fitClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.fitClasses)).slice(0, 80),
2621
+ flexBehaviorClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.flexBehaviorClasses)).slice(0, 80),
2622
+ layoutSafetyClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.layoutSafetyClasses)).slice(0, 120),
2623
+ };
2624
+ return merged.layoutSafetyClasses.length ? merged : undefined;
2625
+ }
2626
+ function mergeResponsiveProfiles(items) {
2627
+ const profiles = items.map((item) => item.responsiveProfile).filter(isResponsiveProfile);
2628
+ if (!profiles.length) {
2629
+ return undefined;
2630
+ }
2631
+ const breakpoints = {};
2632
+ for (const profile of profiles) {
2633
+ for (const [breakpoint, classes] of Object.entries(profile.breakpoints)) {
2634
+ breakpoints[breakpoint] = (0, utils_1.unique)([...(breakpoints[breakpoint] ?? []), ...classes]).slice(0, 80);
2635
+ }
2636
+ }
2637
+ const merged = {
2638
+ breakpoints,
2639
+ layoutClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.layoutClasses)).slice(0, 120),
2640
+ typographyClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.typographyClasses)).slice(0, 120),
2641
+ spacingClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.spacingClasses)).slice(0, 120),
2642
+ sizingClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.sizingClasses)).slice(0, 120),
2643
+ visibilityClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.visibilityClasses)).slice(0, 120),
2644
+ stateClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.stateClasses)).slice(0, 120),
2645
+ };
2646
+ return Object.keys(merged.breakpoints).length ? merged : undefined;
2647
+ }
2648
+ function mergeScaleProfiles(items) {
2649
+ const profiles = items.map((item) => item.scaleProfile).filter(isScaleProfile);
2650
+ if (!profiles.length) {
2651
+ return undefined;
2652
+ }
2653
+ const merged = {
2654
+ typographyClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.typographyClasses)).slice(0, 120),
2655
+ spacingClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.spacingClasses)).slice(0, 120),
2656
+ sizingClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.sizingClasses)).slice(0, 120),
2657
+ gapClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.gapClasses)).slice(0, 80),
2658
+ maxWidthClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.maxWidthClasses)).slice(0, 80),
2659
+ lineHeightClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.lineHeightClasses)).slice(0, 80),
2660
+ };
2661
+ return Object.values(merged).some((classes) => classes.length) ? merged : undefined;
2662
+ }
2663
+ function mergeSemanticProfiles(items) {
2664
+ const profiles = items.map((item) => item.semanticProfile).filter(isSemanticProfile);
2665
+ if (!profiles.length) {
2666
+ return undefined;
659
2667
  }
2668
+ return {
2669
+ roles: (0, utils_1.unique)(profiles.flatMap((profile) => profile.roles)).slice(0, 40),
2670
+ isCompound: profiles.some((profile) => profile.isCompound),
2671
+ hasIcon: profiles.some((profile) => profile.hasIcon),
2672
+ hasDivider: profiles.some((profile) => profile.hasDivider),
2673
+ hasImage: profiles.some((profile) => profile.hasImage),
2674
+ hasMultipleTextStyles: profiles.some((profile) => profile.hasMultipleTextStyles),
2675
+ textStyleCount: Math.max(...profiles.map((profile) => profile.textStyleCount)),
2676
+ childCount: Math.max(...profiles.map((profile) => profile.childCount)),
2677
+ };
660
2678
  }
661
- function backendCallKey(call) {
662
- return (0, utils_1.toRegistryKey)('service', (0, backend_1.backendReceiverToClassName)(call.receiver));
2679
+ function summarizePatternLayoutSafety(elements) {
2680
+ return mergeLayoutSafetyProfiles([
2681
+ ...elements,
2682
+ ...elements.flatMap((element) => element.childSummary ?? []),
2683
+ ]);
663
2684
  }
664
- function uniqueSideEffectsForRecipe(sideEffects) {
665
- return (0, utils_1.uniqueBy)(sideEffects, (sideEffect) => `${sideEffect.kind}.${sideEffect.target ?? ''}.${sideEffect.operation ?? ''}.${sideEffect.line}`);
2685
+ function summarizePatternResponsiveProfile(elements) {
2686
+ return mergeResponsiveProfiles([
2687
+ ...elements,
2688
+ ...elements.flatMap((element) => element.childSummary ?? []),
2689
+ ]);
666
2690
  }
667
- function sourceInfo(extractor, confidence, evidence, file) {
2691
+ function summarizePatternScaleProfile(elements) {
2692
+ return mergeScaleProfiles([
2693
+ ...elements,
2694
+ ...elements.flatMap((element) => element.childSummary ?? []),
2695
+ ]);
2696
+ }
2697
+ function summarizePatternSemanticProfile(elements) {
2698
+ return mergeSemanticProfiles(elements);
2699
+ }
2700
+ function summarizePatternRoleSignatures(elements) {
2701
+ return (0, utils_1.uniqueBy)(elements
2702
+ .map((element) => element.roleSignature)
2703
+ .filter((value) => Boolean(value)), (signature) => roleSignatureKey(signature)).slice(0, 12);
2704
+ }
2705
+ function summarizeParentContexts(elements) {
2706
+ const elementsWithParent = elements.filter((element) => element.parentClasses?.length);
2707
+ const grouped = (0, utils_1.groupBy)(elementsWithParent, (element) => [
2708
+ element.parentKind ?? 'unknown-parent',
2709
+ element.parentTag ?? 'unknown-tag',
2710
+ JSON.stringify(element.parentClassGroups ?? (0, extractors_1.groupTailwindClasses)(element.parentClasses ?? [])),
2711
+ ].join(':'));
2712
+ return Array.from(grouped.values())
2713
+ .map((group) => {
2714
+ const first = group[0];
2715
+ const parentClasses = (0, utils_1.unique)(group.flatMap((element) => element.parentClasses ?? [])).slice(0, 80);
2716
+ return {
2717
+ parentKind: first?.parentKind,
2718
+ parentTag: first?.parentTag,
2719
+ count: group.length,
2720
+ parentClasses,
2721
+ parentClassGroups: (0, extractors_1.mergeClassGroups)(group
2722
+ .map((element) => element.parentClassGroups)
2723
+ .filter((value) => Boolean(value))),
2724
+ childKinds: (0, utils_1.unique)(group.map((element) => element.kind)).slice(0, 24),
2725
+ layoutRoles: (0, utils_1.unique)(group
2726
+ .map((element) => element.layoutRole)
2727
+ .filter((value) => Boolean(value))).slice(0, 24),
2728
+ };
2729
+ })
2730
+ .sort((a, b) => Number(b.count) - Number(a.count))
2731
+ .slice(0, 12);
2732
+ }
2733
+ function summarizeElementChildContract(children, styleContext) {
2734
+ if (!children.length) {
2735
+ return undefined;
2736
+ }
668
2737
  return {
669
- extractor,
670
- confidence,
671
- evidence,
672
- path: file.relPath,
673
- hash: file.sourceHash,
2738
+ directChildCount: children.length,
2739
+ childOrder: children.slice(0, 12).map((child) => ({
2740
+ kind: child.kind,
2741
+ tag: child.originalTag,
2742
+ layoutRole: child.layoutRole,
2743
+ semanticRole: child.semanticRole,
2744
+ roleSignature: child.roleSignature,
2745
+ labelHint: child.label ? labelSignature(child.label) : undefined,
2746
+ label: child.label,
2747
+ uiContract: createUiContractForChild(child, styleContext),
2748
+ defaultClasses: (child.defaultClasses?.length ? child.defaultClasses : child.classes).slice(0, 48),
2749
+ layoutSafety: child.layoutSafety,
2750
+ scaleProfile: child.scaleProfile,
2751
+ classGroups: (0, extractors_1.groupTailwindClasses)(child.defaultClasses?.length ? child.defaultClasses : child.classes),
2752
+ })),
2753
+ childKinds: (0, utils_1.unique)(children.map((child) => child.kind)).slice(0, 24),
2754
+ semanticRoles: (0, utils_1.unique)(children
2755
+ .map((child) => child.semanticRole)
2756
+ .filter((value) => Boolean(value))).slice(0, 24),
2757
+ layoutRoles: (0, utils_1.unique)(children.map((child) => child.layoutRole).filter((value) => Boolean(value))).slice(0, 24),
674
2758
  };
675
2759
  }
676
- function toGraphNode(key, kind, name, relPath, file, extractor, facets) {
2760
+ function summarizePatternChildStructure(elements, styleContext) {
2761
+ const childSummaries = elements
2762
+ .map((element) => element.childSummary ?? [])
2763
+ .filter((children) => children.length);
2764
+ if (!childSummaries.length) {
2765
+ return undefined;
2766
+ }
2767
+ const childCounts = childSummaries.map((children) => children.length);
2768
+ const childOrderExamples = (0, utils_1.uniqueBy)(childSummaries
2769
+ .map((children) => summarizeElementChildContract(children, styleContext))
2770
+ .filter((summary) => Boolean(summary)), (summary) => JSON.stringify(summary)).slice(0, 8);
677
2771
  return {
678
- k: key,
679
- kind,
680
- name,
681
- path: relPath,
682
- language: file.language,
683
- framework: detectFrameworkForFile(file),
684
- ...(facets ? { facets } : {}),
685
- source: sourceInfo(extractor, 'high', [`path:${relPath}`], file),
2772
+ preserveDirectChildRelationships: true,
2773
+ directChildCountRange: {
2774
+ min: Math.min(...childCounts),
2775
+ max: Math.max(...childCounts),
2776
+ },
2777
+ childKinds: (0, utils_1.unique)(childSummaries.flatMap((children) => children.map((child) => child.kind))).slice(0, 32),
2778
+ childOrderExamples,
686
2779
  };
687
2780
  }
688
- function toGraphEdge(from, to, type, extractor, file, confidence = 'high') {
2781
+ function summarizeCompoundStructure(elements, styleContext) {
2782
+ const compoundElements = elements.filter((element) => element.semanticProfile?.isCompound);
2783
+ if (!compoundElements.length) {
2784
+ return undefined;
2785
+ }
2786
+ const childCounts = compoundElements.map((element) => element.childSummary?.length ?? 0);
2787
+ const childOrderExamples = (0, utils_1.uniqueBy)(compoundElements
2788
+ .map((element) => summarizeElementChildContract(element.childSummary ?? [], styleContext))
2789
+ .filter((summary) => Boolean(summary)), (summary) => JSON.stringify(summary)).slice(0, 8);
2790
+ const semanticProfile = summarizePatternSemanticProfile(compoundElements);
689
2791
  return {
690
- from,
691
- to,
692
- type,
693
- confidence,
694
- source: sourceInfo(extractor, confidence, [`${from} ${type} ${to}`], file),
2792
+ preserveCompoundChildren: true,
2793
+ preserveIconAndDividerRoles: Boolean(semanticProfile?.hasIcon || semanticProfile?.hasDivider),
2794
+ preserveMultipleTextSegments: Boolean(semanticProfile?.hasMultipleTextStyles),
2795
+ directChildCountRange: {
2796
+ min: Math.min(...childCounts),
2797
+ max: Math.max(...childCounts),
2798
+ },
2799
+ semanticRoles: semanticProfile?.roles ?? [],
2800
+ childOrderExamples,
695
2801
  };
696
2802
  }
697
- function addRenderEdges(entries, files, edges) {
698
- const componentKeysByName = new Map();
699
- for (const entry of entries.filter((item) => item.group === 'component')) {
700
- const name = typeof entry.value.name === 'string' ? entry.value.name : undefined;
701
- if (name) {
702
- componentKeysByName.set(name, entry.key);
703
- }
2803
+ function createUiGenerationConstraints(kind, elements, decorativeAccentClasses) {
2804
+ const layoutSafety = summarizePatternLayoutSafety(elements);
2805
+ const responsiveProfile = summarizePatternResponsiveProfile(elements);
2806
+ const scaleProfile = summarizePatternScaleProfile(elements);
2807
+ const roleSignatures = summarizePatternRoleSignatures(elements);
2808
+ const parentContexts = summarizeParentContexts(elements);
2809
+ const childStructure = summarizePatternChildStructure(elements);
2810
+ const compoundStructure = summarizeCompoundStructure(elements);
2811
+ return {
2812
+ preserveRootAndChildrenTogether: true,
2813
+ preserveParentLayoutContext: Boolean(parentContexts.length),
2814
+ preserveDirectChildRelationships: Boolean(childStructure),
2815
+ preserveCompoundChildRoles: Boolean(compoundStructure),
2816
+ avoidUnobservedChildren: ['card', 'form', 'nav', 'section', 'table', 'dialog'].includes(kind) ||
2817
+ Boolean(compoundStructure),
2818
+ preserveResponsiveBreakpoints: Boolean(responsiveProfile),
2819
+ preserveLayoutSafety: Boolean(layoutSafety),
2820
+ preserveScaleBounds: Boolean(scaleProfile),
2821
+ preserveRoleSignatures: Boolean(roleSignatures.length),
2822
+ avoidDefaultDecorativeAccents: Boolean(decorativeAccentClasses.length),
2823
+ };
2824
+ }
2825
+ function createUiGenerationGuidance(kind, elements) {
2826
+ const guidance = [];
2827
+ const elementClasses = new Set(elements.flatMap((element) => [
2828
+ ...element.classes,
2829
+ ...(element.defaultClasses ?? []),
2830
+ ...(element.conditionalClasses ?? []),
2831
+ ...(element.parentClasses ?? []),
2832
+ ]));
2833
+ if (kind === 'card') {
2834
+ guidance.push('Use rootDefaultClasses for the outer card shell. Use elementPatterns for nested panels, slots, bullets, and footer layout instead of merging every class onto the root.', 'Preserve the observed child structure for cards. Do not add decorative children, progress bars, icons, or extra badges unless those children exist in childStructure or the task explicitly asks for them.');
704
2835
  }
705
- for (const file of files) {
706
- if (!file.routes.length || !file.jsxElements.length) {
707
- continue;
708
- }
709
- for (const route of file.routes) {
710
- const routeKey = (0, utils_1.toRegistryKey)('route', route);
711
- for (const jsxElement of file.jsxElements) {
712
- const componentKey = componentKeysByName.get(jsxElement);
713
- if (componentKey) {
714
- edges.push(toGraphEdge(routeKey, componentKey, 'renders', 'react-jsx', file));
715
- }
716
- }
717
- }
2836
+ if (kind === 'button') {
2837
+ guidance.push('Choose button variants by actionUsages/variantRoles. Ghost and link-style actions are low-emphasis; do not use them for primary CTAs or form submits.', 'Preserve button display, height, padding, no-wrap, and shrink/min-width classes together so labels do not wrap or collapse.');
2838
+ }
2839
+ if (kind === 'badge' && summarizeCompoundStructure(elements)) {
2840
+ guidance.push('Compound badges, hero eyebrows, and brand chips are connected lockups. Preserve icon/image children, dividers, text segments, child order, compact spacing, and separate text styles instead of collapsing them into one text-only pill.');
2841
+ }
2842
+ if (kind === 'heading') {
2843
+ guidance.push('Preserve observed heading scale, max-width, line-height, and responsive typography classes. Do not upscale headings beyond the captured scaleProfile.');
2844
+ }
2845
+ if (['section', 'layout', 'form', 'nav'].includes(kind)) {
2846
+ guidance.push('Preserve parent grid/flex context, responsive breakpoints, and child order as layout contracts instead of recreating the section from isolated tokens.');
2847
+ }
2848
+ if (elements.some((element) => element.conditionalClasses?.length)) {
2849
+ guidance.push('conditionalClasses describe state or variant changes; apply them only when the matching condition, selected state, or variant is present.');
2850
+ }
2851
+ if (elementClasses.has('section-frame') || elementClasses.has('sm:section-frame')) {
2852
+ guidance.push('section-frame is a framed surface paint model with its own clipped section-frame::before grid overlay; preserve which element owns that frame.');
2853
+ }
2854
+ if (elementClasses.has('section-frame') && elementClasses.has('context-surface')) {
2855
+ guidance.push('When section-frame and context-surface appear together, keep the pairing so direct children remain above the parent grid overlay.');
2856
+ }
2857
+ if (elements.some((element) => (element.childSummary ?? []).some((child) => (child.defaultClasses ?? child.classes).some((className) => className === 'section-frame')))) {
2858
+ guidance.push('Nested framed child surfaces need their own section-frame/context-surface classes when the source design gives each child a separate grid origin.');
2859
+ }
2860
+ if (summarizePatternLayoutSafety(elements)) {
2861
+ guidance.push('layoutSafety records wrapping, overflow, min/max size, and flex behavior that prevent text collision and button/card overflow. Keep those classes with the element that owns them.');
2862
+ }
2863
+ if (summarizeParentContexts(elements).length) {
2864
+ guidance.push('parentContexts records the grid/flex surface that owns this pattern. Preserve that parent relationship when generating repeated cards or nested panels.');
2865
+ }
2866
+ if (summarizePatternResponsiveProfile(elements)) {
2867
+ guidance.push('responsiveProfile is part of the contract. Keep mobile, tablet, and desktop variants attached to the same elements as the source.');
2868
+ }
2869
+ if (summarizePatternRoleSignatures(elements).length) {
2870
+ guidance.push('roleSignatures are part of the contract. Similar-looking UI with different role, scale, density, surface, layout, or exactClassFacts is a different generation target.');
2871
+ }
2872
+ return guidance;
2873
+ }
2874
+ function summarizeActionUsages(elements) {
2875
+ return elements.slice(0, 80).flatMap((element) => {
2876
+ const variantNames = actionVariantNamesForElement(element);
2877
+ return variantNames.map((variantName) => ({
2878
+ label: element.label,
2879
+ tag: element.originalTag,
2880
+ line: element.line,
2881
+ variant: variantName,
2882
+ size: element.variants?.size,
2883
+ type: element.variants?.type ?? element.type,
2884
+ styleHelper: element.styleHelper,
2885
+ parentKind: element.parentKind,
2886
+ parentLayoutRole: element.parentKind ? element.layoutRole : undefined,
2887
+ ...inferActionVariantRole(variantName, [
2888
+ ...(element.defaultClasses ?? []),
2889
+ ...(element.classes ?? []),
2890
+ ]),
2891
+ }));
2892
+ });
2893
+ }
2894
+ function actionVariantNamesForElement(element) {
2895
+ const variantValue = element.variants?.variant ?? element.styleHelperVariants?.variant;
2896
+ if (!variantValue && /button/i.test(element.styleHelper ?? '')) {
2897
+ return ['default'];
718
2898
  }
2899
+ if (!variantValue) {
2900
+ return ['custom'];
2901
+ }
2902
+ const quotedValues = Array.from(variantValue.matchAll(/["']([^"']+)["']/g)).map((match) => match[1]);
2903
+ if (quotedValues.length) {
2904
+ return (0, utils_1.unique)(quotedValues).slice(0, 6);
2905
+ }
2906
+ return (0, utils_1.unique)(variantValue
2907
+ .split('|')
2908
+ .map((value) => value.trim())
2909
+ .filter(Boolean)).slice(0, 6);
719
2910
  }
720
- function addUiPatternRules(entries, usedKeys, nodes, edges, files) {
2911
+ function addUiPatternRules(entries, usedKeys, nodes, edges, files, styleContext) {
721
2912
  for (const file of files) {
2913
+ if ((0, extractors_1.isTestFile)(file.relPath)) {
2914
+ continue;
2915
+ }
722
2916
  const elementsByKind = (0, utils_1.groupBy)(file.uiElements, (element) => element.kind);
723
2917
  for (const [kind, elements] of elementsByKind) {
724
2918
  if (!elements.length || kind === 'unknown' || kind === 'link') {
@@ -734,6 +2928,62 @@ function addUiPatternRules(entries, usedKeys, nodes, edges, files) {
734
2928
  const decorativeAccentClasses = (0, extractors_1.filterClasses)(classes, extractors_1.isDecorativeAccentClass).slice(0, 40);
735
2929
  const classGroups = (0, extractors_1.mergeClassGroups)(elements.map((element) => element.classGroups ?? {}));
736
2930
  const variants = (0, extractors_1.mergeVariantProps)(elements.map((element) => element.variants ?? {}));
2931
+ const elementPatterns = summarizeUiElementPatterns(elements, styleContext);
2932
+ const layoutSafety = summarizePatternLayoutSafety(elements);
2933
+ const responsiveProfile = summarizePatternResponsiveProfile(elements);
2934
+ const scaleProfile = summarizePatternScaleProfile(elements);
2935
+ const semanticProfile = summarizePatternSemanticProfile(elements);
2936
+ const roleSignatures = summarizePatternRoleSignatures(elements);
2937
+ const parentContexts = summarizeParentContexts(elements);
2938
+ const childStructureContract = summarizePatternChildStructure(elements, styleContext);
2939
+ const compoundStructure = summarizeCompoundStructure(elements, styleContext);
2940
+ const generationConstraints = createUiGenerationConstraints(kind, elements, decorativeAccentClasses);
2941
+ const structuralRootElements = rootUiElementsForKind(elements);
2942
+ const rootDefaultClasses = (0, utils_1.unique)(structuralRootElements.flatMap((element) => element.defaultClasses ?? element.classes)).slice(0, 80);
2943
+ const recipeDefaultClasses = kind === 'card' && rootDefaultClasses.length ? rootDefaultClasses : defaultClasses;
2944
+ const nestedDefaultClasses = kind === 'card'
2945
+ ? (0, utils_1.unique)(elements
2946
+ .filter((element) => !structuralRootElements.includes(element))
2947
+ .flatMap((element) => element.defaultClasses ?? element.classes)
2948
+ .filter((className) => !(0, extractors_1.isDecorativeAccentClass)(className))).slice(0, 80)
2949
+ : undefined;
2950
+ const actionUsages = kind === 'button' ? summarizeActionUsages(elements) : undefined;
2951
+ const uiContract = createUiPatternContract({
2952
+ name: `${(0, utils_1.titleCase)(kind)} pattern`,
2953
+ count: elements.length,
2954
+ kind,
2955
+ tag: structuralRootElements[0]?.originalTag,
2956
+ layoutRole: structuralRootElements[0]?.layoutRole,
2957
+ label: structuralRootElements[0]?.label,
2958
+ line: structuralRootElements[0]?.line,
2959
+ path: file.relPath,
2960
+ sourceHash: file.sourceHash,
2961
+ confidence: elements.length > 1 ? 'medium' : 'low',
2962
+ evidence: structuralRootElements[0]?.evidence,
2963
+ classSource: structuralRootElements[0]?.classSource,
2964
+ classes,
2965
+ defaultClasses: recipeDefaultClasses,
2966
+ conditionalClasses,
2967
+ decorativeAccentClasses,
2968
+ classGroups,
2969
+ layoutSafety,
2970
+ responsiveProfile,
2971
+ scaleProfile,
2972
+ semanticProfile,
2973
+ roleSignature: roleSignatures[0],
2974
+ variants,
2975
+ styleContext,
2976
+ });
2977
+ uiContract.safetyRules = omitEmptyRecord({
2978
+ ...uiContract.safetyRules,
2979
+ generationConstraints,
2980
+ });
2981
+ const relationships = createUiRelationships({
2982
+ parentContext: parentContexts,
2983
+ childStructureContract,
2984
+ compoundStructure,
2985
+ graphKeys: dependencies,
2986
+ });
737
2987
  addEntry(entries, usedKeys, {
738
2988
  key: ruleKey,
739
2989
  group: 'rule',
@@ -744,8 +2994,30 @@ function addUiPatternRules(entries, usedKeys, nodes, edges, files) {
744
2994
  kind,
745
2995
  ...Object.keys(variants),
746
2996
  ...Object.values(variants).flatMap((values) => values),
747
- ...defaultClasses.slice(0, 20),
2997
+ ...recipeDefaultClasses.slice(0, 20),
748
2998
  ...(decorativeAccentClasses.length ? ['decorative-accent', 'highlight-only'] : []),
2999
+ ...(layoutSafety
3000
+ ? ['layout-safety', ...layoutSafety.layoutSafetyClasses.slice(0, 16)]
3001
+ : []),
3002
+ ...(responsiveProfile ? ['responsive-contract'] : []),
3003
+ ...(scaleProfile ? ['scale-contract'] : []),
3004
+ ...(roleSignatures.length
3005
+ ? [
3006
+ 'role-signature',
3007
+ ...roleSignatures.flatMap((signature) => [
3008
+ signature.role,
3009
+ signature.roleGroup,
3010
+ signature.scale,
3011
+ signature.density,
3012
+ signature.surface,
3013
+ signature.layout,
3014
+ ...signature.flags,
3015
+ ]),
3016
+ ]
3017
+ : []),
3018
+ ...(semanticProfile?.isCompound
3019
+ ? ['compound-ui', 'compound-badge', ...semanticProfile.roles]
3020
+ : []),
749
3021
  ]),
750
3022
  dependencies,
751
3023
  usedBy: [],
@@ -759,12 +3031,30 @@ function addUiPatternRules(entries, usedKeys, nodes, edges, files) {
759
3031
  concept: kind,
760
3032
  path: file.relPath,
761
3033
  classes,
762
- defaultClasses,
3034
+ defaultClasses: recipeDefaultClasses,
3035
+ rootDefaultClasses,
3036
+ ...(nestedDefaultClasses?.length ? { nestedDefaultClasses } : {}),
763
3037
  conditionalClasses,
764
3038
  decorativeAccentClasses,
765
- guidance: (0, extractors_1.createUiPatternGuidance)(kind, decorativeAccentClasses),
3039
+ ...(layoutSafety ? { layoutSafety } : {}),
3040
+ ...(responsiveProfile ? { responsiveProfile } : {}),
3041
+ ...(scaleProfile ? { scaleProfile } : {}),
3042
+ ...(semanticProfile ? { semanticProfile } : {}),
3043
+ ...(roleSignatures.length ? { roleSignatures } : {}),
3044
+ ...(parentContexts.length ? { parentContexts } : {}),
3045
+ ...(childStructureContract ? { childStructureContract } : {}),
3046
+ ...(compoundStructure ? { compoundStructure } : {}),
3047
+ generationConstraints,
3048
+ uiContract,
3049
+ relationships,
3050
+ guidance: (0, utils_1.unique)([
3051
+ ...(0, extractors_1.createUiPatternGuidance)(kind, decorativeAccentClasses),
3052
+ ...createUiGenerationGuidance(kind, elements),
3053
+ ]),
766
3054
  classGroups,
767
3055
  variants,
3056
+ elementPatterns,
3057
+ ...(actionUsages ? { actionUsages } : {}),
768
3058
  elements: elements.slice(0, 80),
769
3059
  sourceHash: file.sourceHash,
770
3060
  source: sourceInfo('ui.patterns', elements.length > 1 ? 'medium' : 'low', [`${kind}:${elements.length}`], file),
@@ -773,8 +3063,12 @@ function addUiPatternRules(entries, usedKeys, nodes, edges, files) {
773
3063
  nodes.push(toGraphNode(ruleKey, 'rule', `${kind} pattern`, file.relPath, file, 'ui.patterns', {
774
3064
  ui: {
775
3065
  concept: kind,
776
- classes: defaultClasses,
3066
+ classes: rootDefaultClasses.length ? rootDefaultClasses : defaultClasses,
777
3067
  decorativeAccentClasses,
3068
+ layoutSafety,
3069
+ responsiveProfile,
3070
+ scaleProfile,
3071
+ semanticProfile,
778
3072
  classGroups,
779
3073
  variants,
780
3074
  count: elements.length,
@@ -784,8 +3078,11 @@ function addUiPatternRules(entries, usedKeys, nodes, edges, files) {
784
3078
  }
785
3079
  }
786
3080
  }
787
- function addUiCompositionRules(entries, usedKeys, nodes, edges, files) {
3081
+ function addUiCompositionRules(entries, usedKeys, nodes, edges, files, styleContext) {
788
3082
  for (const file of files) {
3083
+ if ((0, extractors_1.isTestFile)(file.relPath)) {
3084
+ continue;
3085
+ }
789
3086
  if (!file.uiElements.length && !file.classExpressions.length) {
790
3087
  continue;
791
3088
  }
@@ -819,6 +3116,46 @@ function addUiCompositionRules(entries, usedKeys, nodes, edges, files) {
819
3116
  const classExpressionsByTarget = (0, extractors_1.groupClassExpressionsByTarget)(file.classExpressions);
820
3117
  const layoutRoles = (0, utils_1.unique)(file.uiElements.flatMap((element) => element.layoutRole ?? []));
821
3118
  const elementKinds = (0, utils_1.unique)(file.uiElements.map((element) => element.kind));
3119
+ const layoutSafety = summarizePatternLayoutSafety(file.uiElements);
3120
+ const responsiveProfile = summarizePatternResponsiveProfile(file.uiElements);
3121
+ const scaleProfile = summarizePatternScaleProfile(file.uiElements);
3122
+ const semanticProfile = summarizePatternSemanticProfile(file.uiElements);
3123
+ const parentContexts = summarizeParentContexts(file.uiElements);
3124
+ const childStructureContract = summarizePatternChildStructure(compositionElements, styleContext);
3125
+ const compoundStructure = summarizeCompoundStructure(file.uiElements, styleContext);
3126
+ const uiContract = createUiPatternContract({
3127
+ name: `${file.relPath} UI composition`,
3128
+ count: file.uiElements.length,
3129
+ kind: 'composition',
3130
+ tag: compositionElements[0]?.originalTag,
3131
+ layoutRole: layoutRoles[0],
3132
+ line: compositionElements[0]?.line,
3133
+ path: file.relPath,
3134
+ sourceHash: file.sourceHash,
3135
+ confidence: file.components.length || file.routes.length ? 'high' : 'medium',
3136
+ evidence: compositionElements[0]?.evidence,
3137
+ classSource: compositionElements[0]?.classSource,
3138
+ classes: baseClassReferences,
3139
+ defaultClasses: baseClassReferences,
3140
+ conditionalClasses,
3141
+ decorativeAccentClasses,
3142
+ classGroups: (0, extractors_1.mergeClassGroups)(compositionElements.map((element) => element.classGroups ?? {})),
3143
+ layoutSafety,
3144
+ responsiveProfile,
3145
+ scaleProfile,
3146
+ semanticProfile,
3147
+ styleContext,
3148
+ });
3149
+ uiContract.safetyRules = omitEmptyRecord({
3150
+ ...uiContract.safetyRules,
3151
+ compositionGuidance: (0, extractors_1.createUiCompositionGuidance)(file),
3152
+ });
3153
+ const relationships = createUiRelationships({
3154
+ parentContext: parentContexts,
3155
+ childStructureContract,
3156
+ compoundStructure,
3157
+ graphKeys: dependencies,
3158
+ });
822
3159
  addEntry(entries, usedKeys, {
823
3160
  key: ruleKey,
824
3161
  group: 'rule',
@@ -840,6 +3177,14 @@ function addUiCompositionRules(entries, usedKeys, nodes, edges, files) {
840
3177
  ...responsiveClasses.slice(0, 24),
841
3178
  ...surfaceClasses.slice(0, 24),
842
3179
  ...(decorativeAccentClasses.length ? ['decorative-accent', 'highlight-only'] : []),
3180
+ ...(layoutSafety
3181
+ ? ['layout-safety', ...layoutSafety.layoutSafetyClasses.slice(0, 24)]
3182
+ : []),
3183
+ ...(responsiveProfile ? ['responsive-contract'] : []),
3184
+ ...(scaleProfile ? ['scale-contract'] : []),
3185
+ ...(semanticProfile?.isCompound
3186
+ ? ['compound-ui', 'compound-badge', ...semanticProfile.roles]
3187
+ : []),
843
3188
  ...spacingClasses.slice(0, 24),
844
3189
  ...typographyClasses.slice(0, 24),
845
3190
  ]),
@@ -865,6 +3210,15 @@ function addUiCompositionRules(entries, usedKeys, nodes, edges, files) {
865
3210
  conditionalClasses,
866
3211
  decorativeAccentClasses,
867
3212
  typographyClasses,
3213
+ ...(layoutSafety ? { layoutSafety } : {}),
3214
+ ...(responsiveProfile ? { responsiveProfile } : {}),
3215
+ ...(scaleProfile ? { scaleProfile } : {}),
3216
+ ...(semanticProfile ? { semanticProfile } : {}),
3217
+ ...(parentContexts.length ? { parentContexts } : {}),
3218
+ ...(childStructureContract ? { childStructureContract } : {}),
3219
+ ...(compoundStructure ? { compoundStructure } : {}),
3220
+ uiContract,
3221
+ relationships,
868
3222
  componentSlots: (0, extractors_1.buildComponentSlots)(file.uiElements),
869
3223
  classExpressionsByTarget,
870
3224
  elements: compositionElements,
@@ -883,6 +3237,10 @@ function addUiCompositionRules(entries, usedKeys, nodes, edges, files) {
883
3237
  routes: file.routes,
884
3238
  layoutRoles,
885
3239
  elementKinds,
3240
+ layoutSafety,
3241
+ responsiveProfile,
3242
+ scaleProfile,
3243
+ semanticProfile,
886
3244
  },
887
3245
  }));
888
3246
  edges.push(toGraphEdge(file.key, ruleKey, 'defines_ui_composition', 'ui.composition', file));
@@ -894,8 +3252,175 @@ function addUiCompositionRules(entries, usedKeys, nodes, edges, files) {
894
3252
  }
895
3253
  }
896
3254
  }
897
- function addConnectedPatternRules(entries, usedKeys, nodes, edges, reviewItems, files) {
898
- const clusters = buildConnectedPatternClusters(files);
3255
+ function addGlobalUiContractRules(entries, usedKeys, nodes, edges, files) {
3256
+ const eligibleFiles = files.filter((file) => !(0, extractors_1.isTestFile)(file.relPath));
3257
+ const shellFiles = eligibleFiles.filter(hasPageShellContractEvidence);
3258
+ const framedSurfaceFiles = eligibleFiles.filter(hasFramedSurfaceContractEvidence);
3259
+ if (shellFiles.length) {
3260
+ const primary = shellFiles[0];
3261
+ const ruleKey = (0, utils_1.toRegistryKey)('rule', 'ui.page-shell.global');
3262
+ const cssRules = cssRuleSnapshots(shellFiles, shellCssRuleMatches).slice(0, 48);
3263
+ const classes = collectClassEvidence(shellFiles, (className) => /^(?:app-shell|bg-background|min-h-screen|flex|flex-col|text-foreground)$/.test(className));
3264
+ addEntry(entries, usedKeys, {
3265
+ key: ruleKey,
3266
+ group: 'rule',
3267
+ summary: 'Global page shell contract: body/app-shell owns the page background, route roots stay transparent.',
3268
+ tags: (0, utils_1.unique)([
3269
+ 'rule',
3270
+ 'ui',
3271
+ 'global',
3272
+ 'page-shell',
3273
+ 'app-shell',
3274
+ 'body-background',
3275
+ 'route-root',
3276
+ 'transparent-root',
3277
+ 'bg-background',
3278
+ ...classes,
3279
+ ]),
3280
+ dependencies: contractDependencies(shellFiles),
3281
+ usedBy: [],
3282
+ confidence: 'high',
3283
+ status: 'inferred',
3284
+ path: primary.relPath,
3285
+ value: {
3286
+ name: 'Global page shell contract',
3287
+ kind: 'rule',
3288
+ ruleType: 'ui-page-shell',
3289
+ backgroundOwner: 'body/app-shell',
3290
+ keepRouteRootsTransparent: true,
3291
+ classes,
3292
+ requiredShellClasses: collectClassEvidence(shellFiles, (className) => /^(?:app-shell|min-h-screen|flex|flex-col)$/.test(className)),
3293
+ avoidOnRouteRoot: ['bg-background', 'min-h-screen bg-background'],
3294
+ routeRootGuidance: [
3295
+ 'Route-level page roots inside app-shell should stay transparent; do not add bg-background or a solid page wrapper unless the source route deliberately owns an isolated full-screen surface.',
3296
+ 'Let body/app-shell own the global gradient/glow/background and min-height behavior; page content should add spacing, containers, and sections without covering that shell.',
3297
+ 'If a generated page needs full-height layout, prefer flex/min-height on the existing shell/layout contract instead of introducing a new bg-background root.',
3298
+ ],
3299
+ cssRules,
3300
+ sourceHash: primary.sourceHash,
3301
+ source: sourceInfo('ui.global-contracts', 'high', shellFiles.slice(0, 20).map((file) => `path:${file.relPath}`), primary),
3302
+ },
3303
+ });
3304
+ nodes.push(toGraphNode(ruleKey, 'rule', 'Global page shell contract', primary.relPath, primary, 'ui.global-contracts', {
3305
+ ui: {
3306
+ ruleType: 'ui-page-shell',
3307
+ backgroundOwner: 'body/app-shell',
3308
+ keepRouteRootsTransparent: true,
3309
+ classes,
3310
+ },
3311
+ }));
3312
+ for (const file of shellFiles.slice(0, 80)) {
3313
+ edges.push(toGraphEdge(file.key, ruleKey, 'defines_ui_contract', 'ui.global-contracts', file));
3314
+ if (hasStyleSurface(file)) {
3315
+ edges.push(toGraphEdge(styleKeyForFile(file), ruleKey, 'defines_ui_contract', 'ui.global-contracts', file));
3316
+ }
3317
+ }
3318
+ }
3319
+ if (framedSurfaceFiles.length) {
3320
+ const primary = framedSurfaceFiles[0];
3321
+ const ruleKey = (0, utils_1.toRegistryKey)('rule', 'ui.surface.section-frame-grid');
3322
+ const cssRules = cssRuleSnapshots(framedSurfaceFiles, framedSurfaceCssRuleMatches).slice(0, 48);
3323
+ const classes = collectClassEvidence(framedSurfaceFiles, (className) => /^(?:section-frame|sm:section-frame|context-surface|bg-card|bg-\[var\(--surface-soft\)\]|rounded)/.test(className));
3324
+ addEntry(entries, usedKeys, {
3325
+ key: ruleKey,
3326
+ group: 'rule',
3327
+ summary: 'Framed technical surface contract: section-frame paints a clipped grid overlay and context-surface protects child content.',
3328
+ tags: (0, utils_1.unique)([
3329
+ 'rule',
3330
+ 'ui',
3331
+ 'surface',
3332
+ 'framed-surface',
3333
+ 'section-frame',
3334
+ 'context-surface',
3335
+ 'grid-overlay',
3336
+ 'pseudo-element',
3337
+ 'nested-surface',
3338
+ ...classes,
3339
+ ]),
3340
+ dependencies: contractDependencies(framedSurfaceFiles),
3341
+ usedBy: [],
3342
+ confidence: 'high',
3343
+ status: 'inferred',
3344
+ path: primary.relPath,
3345
+ value: {
3346
+ name: 'Section frame grid surface contract',
3347
+ kind: 'rule',
3348
+ ruleType: 'ui-framed-surface',
3349
+ surfaceClasses: ['section-frame', 'context-surface'],
3350
+ classes,
3351
+ paintModel: {
3352
+ sectionFrame: 'section-frame is not only border/background; it owns the clipped section-frame::before grid overlay for that exact surface.',
3353
+ contextSurface: 'context-surface keeps direct children above the parent overlay, preserving readable content and separate child surfaces.',
3354
+ },
3355
+ nestingGuidance: [
3356
+ 'For parent technical panels, use section-frame with context-surface when content or child cards must sit above the parent grid.',
3357
+ 'Nested framed child surfaces need their own section-frame/context-surface pairing when the design expects each child to have an independent clipped grid origin.',
3358
+ 'Do not rely on one ancestor section-frame grid for child cards that visually have separate framed grids in the source design.',
3359
+ ],
3360
+ usageGuidance: [
3361
+ 'Use this contract for framed technical panels and code/context surfaces, not every ordinary repeated card.',
3362
+ 'Preserve responsive forms such as sm:section-frame when the source intentionally keeps mobile surfaces transparent or unframed.',
3363
+ ],
3364
+ avoid: [
3365
+ 'Do not add decorative glow/shadow classes to repeated cards just because they are framed.',
3366
+ 'Do not flatten parent and child frame classes onto one root; preserve the surface hierarchy.',
3367
+ ],
3368
+ cssRules,
3369
+ sourceHash: primary.sourceHash,
3370
+ source: sourceInfo('ui.global-contracts', 'high', framedSurfaceFiles.slice(0, 20).map((file) => `path:${file.relPath}`), primary),
3371
+ },
3372
+ });
3373
+ nodes.push(toGraphNode(ruleKey, 'rule', 'Section frame grid surface contract', primary.relPath, primary, 'ui.global-contracts', {
3374
+ ui: {
3375
+ ruleType: 'ui-framed-surface',
3376
+ surfaceClasses: ['section-frame', 'context-surface'],
3377
+ classes,
3378
+ },
3379
+ }));
3380
+ for (const file of framedSurfaceFiles.slice(0, 80)) {
3381
+ edges.push(toGraphEdge(file.key, ruleKey, 'defines_ui_contract', 'ui.global-contracts', file));
3382
+ if (hasStyleSurface(file)) {
3383
+ edges.push(toGraphEdge(styleKeyForFile(file), ruleKey, 'defines_ui_contract', 'ui.global-contracts', file));
3384
+ }
3385
+ }
3386
+ }
3387
+ }
3388
+ function hasPageShellContractEvidence(file) {
3389
+ return Boolean(file.classReferences.includes('app-shell') ||
3390
+ file.cssClasses.includes('app-shell') ||
3391
+ file.cssRules.some(shellCssRuleMatches) ||
3392
+ (file.relPath.endsWith('layout.tsx') && file.classReferences.includes('bg-background')));
3393
+ }
3394
+ function hasFramedSurfaceContractEvidence(file) {
3395
+ return Boolean(file.classReferences.some((className) => ['section-frame', 'sm:section-frame', 'context-surface'].includes(className)) ||
3396
+ file.cssClasses.some((className) => ['section-frame', 'context-surface'].includes(className)) ||
3397
+ file.cssRules.some(framedSurfaceCssRuleMatches));
3398
+ }
3399
+ function shellCssRuleMatches(rule) {
3400
+ return /(^|[,\s])(?:html|body|:root)(?:$|[,\s:{.#])|\.app-shell\b|\.bg-background\b/.test(rule.selector);
3401
+ }
3402
+ function framedSurfaceCssRuleMatches(rule) {
3403
+ return /\.section-frame\b|\.context-surface\b/.test(rule.selector);
3404
+ }
3405
+ function contractDependencies(files) {
3406
+ return (0, utils_1.unique)(files.flatMap((file) => [file.key, ...(hasStyleSurface(file) ? [styleKeyForFile(file)] : [])]));
3407
+ }
3408
+ function styleKeyForFile(file) {
3409
+ return (0, utils_1.toRegistryKey)('style', file.relPath.replace(/\.[^.]+$/, ''));
3410
+ }
3411
+ function collectClassEvidence(files, predicate) {
3412
+ return (0, utils_1.unique)(files.flatMap((file) => [...file.classReferences, ...file.cssClasses].filter(predicate))).slice(0, 120);
3413
+ }
3414
+ function cssRuleSnapshots(files, predicate) {
3415
+ return (0, utils_1.uniqueBy)(files.flatMap((file) => file.cssRules.filter(predicate).map((rule) => ({
3416
+ path: file.relPath,
3417
+ selector: rule.selector,
3418
+ line: rule.line,
3419
+ declarations: rule.declarations.slice(0, 24),
3420
+ }))), (rule) => `${rule.path}:${rule.selector}:${rule.line}`);
3421
+ }
3422
+ function addConnectedPatternRules(entries, usedKeys, nodes, edges, reviewItems, files, styleContext) {
3423
+ const clusters = buildConnectedPatternClusters(files, styleContext);
899
3424
  for (const cluster of clusters) {
900
3425
  addEntry(entries, usedKeys, {
901
3426
  key: cluster.key,
@@ -967,9 +3492,9 @@ function addConnectedPatternRules(entries, usedKeys, nodes, edges, reviewItems,
967
3492
  }
968
3493
  }
969
3494
  }
970
- function buildConnectedPatternClusters(files) {
3495
+ function buildConnectedPatternClusters(files, styleContext) {
971
3496
  const instances = [
972
- ...files.flatMap(createFrontendPatternInstances),
3497
+ ...files.flatMap((file) => createFrontendPatternInstances(file, styleContext)),
973
3498
  ...createBackendPatternInstances(files),
974
3499
  ...createDataPatternInstances(files),
975
3500
  ];
@@ -990,7 +3515,62 @@ function buildConnectedPatternClusters(files) {
990
3515
  const classGroups = (0, extractors_1.mergeClassGroups)(group
991
3516
  .map((instance) => instance.root?.classGroups)
992
3517
  .filter((value) => Boolean(value)));
3518
+ const layoutSafety = summarizeConnectedLayoutSafety(group);
3519
+ const responsiveProfile = summarizeConnectedResponsiveProfile(group);
3520
+ const scaleProfile = summarizeConnectedScaleProfile(group);
3521
+ const semanticProfile = summarizeConnectedSemanticProfile(group);
3522
+ const roleSignatures = summarizeConnectedRoleSignatures(group);
3523
+ const roleSignature = roleSignatures[0];
3524
+ const compoundStructure = summarizeConnectedCompoundStructure(group);
3525
+ const parentContexts = summarizeConnectedParentContexts(group);
993
3526
  const childStructure = summarizeConnectedChildren(group);
3527
+ const graphKeys = (0, utils_1.unique)(group.flatMap((instance) => instance.graphKeys)).slice(0, 80);
3528
+ const structureContract = {
3529
+ preserveRootAndChildrenTogether: true,
3530
+ preserveParentLayoutContext: Boolean(parentContexts.length),
3531
+ preserveDirectChildRelationships: Boolean(childStructure.length),
3532
+ preserveCompoundChildRoles: Boolean(compoundStructure),
3533
+ preserveResponsiveBreakpoints: Boolean(responsiveProfile),
3534
+ preserveLayoutSafety: Boolean(layoutSafety),
3535
+ preserveScaleBounds: Boolean(scaleProfile),
3536
+ preserveRoleSignature: Boolean(roleSignature),
3537
+ avoidDefaultDecorativeAccents: Boolean(decorativeAccentClasses.length),
3538
+ };
3539
+ const uiContract = createUiPatternContract({
3540
+ name: `${(0, utils_1.titleCase)(first.concept)} connected pattern`,
3541
+ count: group.length,
3542
+ kind: first.concept,
3543
+ tag: first.root?.tag,
3544
+ layoutRole: first.root?.layoutRole,
3545
+ path: first.file.relPath,
3546
+ sourceHash: first.file.sourceHash,
3547
+ confidence,
3548
+ evidence: first.evidence.label,
3549
+ classes: defaultClasses,
3550
+ defaultClasses,
3551
+ conditionalClasses,
3552
+ decorativeAccentClasses,
3553
+ classGroups,
3554
+ layoutSafety,
3555
+ responsiveProfile,
3556
+ scaleProfile,
3557
+ semanticProfile,
3558
+ roleSignature,
3559
+ styleContext,
3560
+ });
3561
+ uiContract.safetyRules = omitEmptyRecord({
3562
+ ...uiContract.safetyRules,
3563
+ structureContract,
3564
+ });
3565
+ const relationships = createUiRelationships({
3566
+ parentContext: parentContexts,
3567
+ childStructureContract: {
3568
+ children: childStructure,
3569
+ preserveDirectChildRelationships: Boolean(childStructure.length),
3570
+ },
3571
+ compoundStructure,
3572
+ graphKeys,
3573
+ });
994
3574
  const key = (0, utils_1.toRegistryKey)('cluster', `pattern.${first.domain}.${first.concept}.${connectedPatternSlug(first)}.${(0, utils_1.shortHash)(first.signature)}`);
995
3575
  const fileCount = new Set(group.map((instance) => instance.file.relPath)).size;
996
3576
  clusters.push({
@@ -1015,6 +3595,28 @@ function buildConnectedPatternClusters(files) {
1015
3595
  ...group.flatMap((instance) => instance.tags).slice(0, 80),
1016
3596
  ...defaultClasses.slice(0, 24),
1017
3597
  ...(decorativeAccentClasses.length ? ['decorative-accent', 'highlight-only'] : []),
3598
+ ...(layoutSafety
3599
+ ? ['layout-safety', ...layoutSafety.layoutSafetyClasses.slice(0, 24)]
3600
+ : []),
3601
+ ...(responsiveProfile ? ['responsive-contract'] : []),
3602
+ ...(scaleProfile ? ['scale-contract'] : []),
3603
+ ...(semanticProfile?.isCompound
3604
+ ? ['compound-ui', 'compound-badge', ...semanticProfile.roles]
3605
+ : []),
3606
+ ...(roleSignatures.length
3607
+ ? [
3608
+ 'role-signature',
3609
+ ...roleSignatures.flatMap((signature) => [
3610
+ signature.role,
3611
+ signature.roleGroup,
3612
+ signature.scale,
3613
+ signature.density,
3614
+ signature.surface,
3615
+ signature.layout,
3616
+ ...signature.flags,
3617
+ ]),
3618
+ ]
3619
+ : []),
1018
3620
  ]),
1019
3621
  dependencies,
1020
3622
  value: {
@@ -1034,9 +3636,19 @@ function buildConnectedPatternClusters(files) {
1034
3636
  conditionalClasses,
1035
3637
  decorativeAccentClasses,
1036
3638
  classGroups,
3639
+ ...(layoutSafety ? { layoutSafety } : {}),
3640
+ ...(responsiveProfile ? { responsiveProfile } : {}),
3641
+ ...(scaleProfile ? { scaleProfile } : {}),
3642
+ ...(semanticProfile ? { semanticProfile } : {}),
3643
+ ...(roleSignatures.length ? { roleSignatures } : {}),
3644
+ ...(parentContexts.length ? { parentContexts } : {}),
1037
3645
  childStructure,
3646
+ ...(compoundStructure ? { compoundStructure } : {}),
3647
+ structureContract,
3648
+ uiContract,
3649
+ relationships,
1038
3650
  examples,
1039
- graphKeys: (0, utils_1.unique)(group.flatMap((instance) => instance.graphKeys)).slice(0, 80),
3651
+ graphKeys,
1040
3652
  guidance: createConnectedPatternGuidance(first, group, decorativeAccentClasses),
1041
3653
  variants: connectedPatternVariants(first.domain, group),
1042
3654
  children: connectedPatternChildren(first.domain, first.concept, group, dependencies),
@@ -1080,8 +3692,11 @@ function buildConnectedPatternClusters(files) {
1080
3692
  }
1081
3693
  return clusters.sort((a, b) => b.score - a.score || a.key.localeCompare(b.key));
1082
3694
  }
1083
- function createFrontendPatternInstances(file) {
3695
+ function createFrontendPatternInstances(file, styleContext) {
1084
3696
  const instances = [];
3697
+ if ((0, extractors_1.isTestFile)(file.relPath)) {
3698
+ return instances;
3699
+ }
1085
3700
  for (const element of file.uiElements) {
1086
3701
  if (!isConnectedUiPatternRoot(element)) {
1087
3702
  continue;
@@ -1093,7 +3708,14 @@ function createFrontendPatternInstances(file) {
1093
3708
  ...(0, extractors_1.filterClasses)(element.classes, extractors_1.isDecorativeAccentClass),
1094
3709
  ]).slice(0, 60);
1095
3710
  const classGroups = (0, extractors_1.groupTailwindClasses)(defaultClasses.length ? defaultClasses : element.classes);
1096
- const children = normalizeConnectedChildren(element.childSummary ?? []);
3711
+ const children = normalizeConnectedChildren(element.childSummary ?? [], styleContext);
3712
+ const parentContext = summarizeParentContexts([element])[0];
3713
+ const childStructureContract = element.childSummary?.length
3714
+ ? summarizeElementChildContract(element.childSummary, styleContext)
3715
+ : undefined;
3716
+ const compoundStructure = element.semanticProfile?.isCompound
3717
+ ? summarizeCompoundStructure([element], styleContext)
3718
+ : undefined;
1097
3719
  const component = file.components[0];
1098
3720
  const route = file.routes[0];
1099
3721
  const graphKeys = (0, utils_1.unique)([
@@ -1110,10 +3732,44 @@ function createFrontendPatternInstances(file) {
1110
3732
  'frontend',
1111
3733
  element.kind,
1112
3734
  normalizeLayoutRole(element.layoutRole),
3735
+ roleSignatureKey(element.roleSignature),
1113
3736
  normalizeTagForSignature(element),
1114
3737
  classSignature(defaultClasses),
1115
3738
  children.map((child) => child.signature).join('|'),
1116
3739
  ].join(';');
3740
+ const uiContract = createUiContract({
3741
+ kind: element.kind,
3742
+ tag: element.originalTag,
3743
+ layoutRole: element.layoutRole,
3744
+ label: element.label,
3745
+ line: element.line,
3746
+ path: file.relPath,
3747
+ sourceHash: file.sourceHash,
3748
+ confidence: 'medium',
3749
+ evidence: element.evidence,
3750
+ classSource: element.classSource,
3751
+ classes: element.classes,
3752
+ defaultClasses,
3753
+ conditionalClasses,
3754
+ decorativeAccentClasses,
3755
+ classGroups,
3756
+ layoutSafety: element.layoutSafety,
3757
+ responsiveProfile: element.responsiveProfile,
3758
+ scaleProfile: element.scaleProfile,
3759
+ semanticProfile: element.semanticProfile,
3760
+ roleSignature: element.roleSignature,
3761
+ variants: element.variants,
3762
+ props: element.props,
3763
+ styleHelper: element.styleHelper,
3764
+ styleHelperVariants: element.styleHelperVariants,
3765
+ styleContext,
3766
+ });
3767
+ const relationships = createUiRelationships({
3768
+ parentContext,
3769
+ childStructureContract,
3770
+ compoundStructure,
3771
+ graphKeys,
3772
+ });
1117
3773
  instances.push({
1118
3774
  domain: 'frontend',
1119
3775
  concept: element.kind,
@@ -1129,6 +3785,12 @@ function createFrontendPatternInstances(file) {
1129
3785
  conditionalClasses,
1130
3786
  decorativeAccentClasses,
1131
3787
  classGroups,
3788
+ ...(element.layoutSafety ? { layoutSafety: element.layoutSafety } : {}),
3789
+ ...(element.responsiveProfile ? { responsiveProfile: element.responsiveProfile } : {}),
3790
+ ...(element.scaleProfile ? { scaleProfile: element.scaleProfile } : {}),
3791
+ ...(element.semanticProfile ? { semanticProfile: element.semanticProfile } : {}),
3792
+ ...(element.roleSignature ? { roleSignature: element.roleSignature } : {}),
3793
+ uiContract,
1132
3794
  },
1133
3795
  children: children.map(withoutConnectedChildSignature),
1134
3796
  graphKeys,
@@ -1142,6 +3804,25 @@ function createFrontendPatternInstances(file) {
1142
3804
  ...file.components,
1143
3805
  ...file.routes,
1144
3806
  ...Object.keys(classGroups),
3807
+ ...(element.layoutSafety ? ['layout-safety'] : []),
3808
+ ...(element.responsiveProfile ? ['responsive-contract'] : []),
3809
+ ...(element.scaleProfile ? ['scale-contract'] : []),
3810
+ ...(element.roleSignature
3811
+ ? [
3812
+ 'role-signature',
3813
+ element.roleSignature.role,
3814
+ element.roleSignature.roleGroup,
3815
+ element.roleSignature.scale,
3816
+ element.roleSignature.density,
3817
+ element.roleSignature.surface,
3818
+ element.roleSignature.layout,
3819
+ ...element.roleSignature.flags,
3820
+ ...element.roleSignature.exactClassFacts.slice(0, 24),
3821
+ ]
3822
+ : []),
3823
+ ...(element.semanticProfile?.isCompound
3824
+ ? ['compound-ui', 'compound-badge', ...element.semanticProfile.roles]
3825
+ : []),
1145
3826
  ]),
1146
3827
  evidence: {
1147
3828
  path: file.relPath,
@@ -1160,8 +3841,18 @@ function createFrontendPatternInstances(file) {
1160
3841
  conditionalClasses,
1161
3842
  decorativeAccentClasses,
1162
3843
  classGroups,
3844
+ ...(element.layoutSafety ? { layoutSafety: element.layoutSafety } : {}),
3845
+ ...(element.responsiveProfile ? { responsiveProfile: element.responsiveProfile } : {}),
3846
+ ...(element.scaleProfile ? { scaleProfile: element.scaleProfile } : {}),
3847
+ ...(element.semanticProfile ? { semanticProfile: element.semanticProfile } : {}),
3848
+ ...(element.roleSignature ? { roleSignature: element.roleSignature } : {}),
3849
+ uiContract,
1163
3850
  },
1164
3851
  children: children.map(withoutConnectedChildSignature),
3852
+ ...(parentContext ? { parentContext } : {}),
3853
+ ...(childStructureContract ? { childStructureContract } : {}),
3854
+ ...(compoundStructure ? { compoundStructure } : {}),
3855
+ relationships,
1165
3856
  },
1166
3857
  baseWeight: frontendPatternWeight(file, element, children.length, defaultClasses),
1167
3858
  });
@@ -1185,6 +3876,7 @@ function createBackendPatternInstances(files) {
1185
3876
  ...file.services.map((service) => (0, utils_1.toRegistryKey)('service', service)),
1186
3877
  ...file.modules.map((moduleName) => (0, utils_1.toRegistryKey)('module', moduleName)),
1187
3878
  ...file.models.map((model) => (0, utils_1.toRegistryKey)('model', model)),
3879
+ ...file.databaseAccesses.flatMap(databaseAccessDependencies),
1188
3880
  ...file.routes.map((route) => (0, utils_1.toRegistryKey)('route', route)),
1189
3881
  ]);
1190
3882
  const signature = [
@@ -1195,7 +3887,7 @@ function createBackendPatternInstances(files) {
1195
3887
  `methods:${methodShapes.join(',') || 'none'}`,
1196
3888
  `controller:${Boolean(file.controllers.length)}`,
1197
3889
  `service:${Boolean(file.services.length)}`,
1198
- `model:${Boolean(file.models.length)}`,
3890
+ `model:${Boolean(file.models.length || file.databaseAccesses.length)}`,
1199
3891
  `auth:${Boolean(file.authHints.length || file.middleware.length)}`,
1200
3892
  `events:${Boolean(file.events.length || file.jobs.length)}`,
1201
3893
  `calls:${Boolean(file.apiCalls.length)}`,
@@ -1234,6 +3926,7 @@ function createBackendPatternInstances(files) {
1234
3926
  modules: file.modules,
1235
3927
  routes: file.routes,
1236
3928
  models: file.models,
3929
+ databaseAccesses: file.databaseAccesses,
1237
3930
  apiCalls: file.apiCalls,
1238
3931
  envVars: file.envVars,
1239
3932
  authHints: file.authHints,
@@ -1258,12 +3951,19 @@ function createDataPatternInstances(files) {
1258
3951
  const instances = [];
1259
3952
  for (const file of files) {
1260
3953
  for (const model of file.models) {
1261
- const fields = file.modelFields[model] ?? [];
3954
+ const databaseModel = file.databaseModels.find((candidate) => candidate.name === model);
3955
+ const fields = databaseModel
3956
+ ? databaseFieldShapeInputs(databaseModel)
3957
+ : (file.modelFields[model] ?? []);
1262
3958
  const fieldShape = summarizeModelFieldShape(fields);
3959
+ const accessGraphKeys = file.databaseAccesses
3960
+ .filter((access) => access.model === model || access.table === databaseModel?.tableName)
3961
+ .map((access) => (0, utils_1.toRegistryKey)('database', `access.${file.relPath.replace(/\.[^.]+$/, '')}.${access.line}.${access.operation}.${access.model ?? access.table ?? access.receiver ?? 'unknown'}`));
1263
3962
  const signature = [
1264
3963
  'data',
1265
- 'model',
1266
- detectFrameworkForFile(file) ?? file.language,
3964
+ databaseModel?.kind ?? 'model',
3965
+ databaseModel?.provider ?? detectFrameworkForFile(file) ?? file.language,
3966
+ databaseModel?.tableName ?? 'no-table',
1267
3967
  fieldShape.signature,
1268
3968
  ].join(';');
1269
3969
  instances.push({
@@ -1272,8 +3972,24 @@ function createDataPatternInstances(files) {
1272
3972
  signature,
1273
3973
  summary: `${model} data model shape in ${file.relPath}.`,
1274
3974
  file,
1275
- graphKeys: (0, utils_1.unique)([file.key, (0, utils_1.toRegistryKey)('model', model)]),
1276
- tags: (0, utils_1.unique)(['data', 'model', 'schema', model, file.language, ...fieldShape.tags]),
3975
+ graphKeys: (0, utils_1.unique)([
3976
+ file.key,
3977
+ (0, utils_1.toRegistryKey)('model', model),
3978
+ ...(hasDatabaseSurface(file)
3979
+ ? [(0, utils_1.toRegistryKey)('database', file.relPath.replace(/\.[^.]+$/, ''))]
3980
+ : []),
3981
+ ...accessGraphKeys,
3982
+ ]),
3983
+ tags: (0, utils_1.unique)([
3984
+ 'data',
3985
+ 'database',
3986
+ databaseModel?.kind ?? 'model',
3987
+ 'schema',
3988
+ model,
3989
+ databaseModel?.tableName ?? '',
3990
+ databaseModel?.provider ?? file.language,
3991
+ ...fieldShape.tags,
3992
+ ]),
1277
3993
  evidence: {
1278
3994
  path: file.relPath,
1279
3995
  key: (0, utils_1.toRegistryKey)('model', model),
@@ -1281,18 +3997,35 @@ function createDataPatternInstances(files) {
1281
3997
  },
1282
3998
  value: {
1283
3999
  model,
1284
- fields,
4000
+ fields: databaseModel?.fieldNames ?? fields,
4001
+ fieldDetails: databaseModel?.fields ?? [],
1285
4002
  fieldShape,
1286
- framework: detectFrameworkForFile(file),
4003
+ tableName: databaseModel?.tableName,
4004
+ provider: databaseModel?.provider,
4005
+ primaryKey: databaseModel?.primaryKey ?? [],
4006
+ uniqueFields: databaseModel?.uniqueFields ?? [],
4007
+ indexes: databaseModel?.indexes ?? [],
4008
+ relations: databaseModel?.relations ?? [],
4009
+ enumRefs: databaseModel?.enumRefs ?? [],
4010
+ accesses: file.databaseAccesses.filter((access) => access.model === model || access.table === databaseModel?.tableName),
4011
+ framework: databaseModel?.framework ?? detectFrameworkForFile(file),
1287
4012
  },
1288
- baseWeight: 3 + Math.min(fields.length / 2, 8),
4013
+ baseWeight: 3 +
4014
+ Math.min(fields.length / 2, 8) +
4015
+ Math.min((databaseModel?.relations.length ?? 0) * 0.75, 4) +
4016
+ Math.min(accessGraphKeys.length * 0.5, 4),
1289
4017
  });
1290
4018
  }
1291
4019
  }
1292
4020
  return instances;
1293
4021
  }
1294
4022
  function isConnectedUiPatternRoot(element) {
1295
- if (element.kind === 'unknown' || element.kind === 'link' || element.kind === 'text') {
4023
+ if (element.kind === 'unknown' ||
4024
+ element.kind === 'link' ||
4025
+ element.kind === 'text' ||
4026
+ element.kind === 'icon' ||
4027
+ element.kind === 'image' ||
4028
+ element.kind === 'divider') {
1296
4029
  return false;
1297
4030
  }
1298
4031
  if (['button', 'input', 'badge'].includes(element.kind)) {
@@ -1300,9 +4033,12 @@ function isConnectedUiPatternRoot(element) {
1300
4033
  }
1301
4034
  return (0, extractors_1.isCompositionElement)(element) || Boolean(element.childSummary?.length);
1302
4035
  }
1303
- function normalizeConnectedChildren(children) {
4036
+ function normalizeConnectedChildren(children, styleContext) {
1304
4037
  return children
1305
- .filter((child) => child.kind !== 'unknown' || child.classes.length || child.label)
4038
+ .filter((child) => child.kind !== 'unknown' ||
4039
+ child.classes.length ||
4040
+ child.label ||
4041
+ Boolean(child.semanticRole))
1306
4042
  .slice(0, 10)
1307
4043
  .map((child) => {
1308
4044
  const defaultClasses = (0, extractors_1.filterClasses)(child.defaultClasses?.length ? child.defaultClasses : child.classes, (className) => !(0, extractors_1.isDecorativeAccentClass)(className)).slice(0, 48);
@@ -1311,6 +4047,8 @@ function normalizeConnectedChildren(children) {
1311
4047
  const signature = [
1312
4048
  normalizedKind,
1313
4049
  normalizeLayoutRole(child.layoutRole),
4050
+ child.semanticRole ?? 'unspecified-semantic-role',
4051
+ roleSignatureKey(child.roleSignature),
1314
4052
  normalizeChildTagForSignature(child),
1315
4053
  classSignature(defaultClasses),
1316
4054
  ].join(':');
@@ -1320,8 +4058,17 @@ function normalizeConnectedChildren(children) {
1320
4058
  sourceKind: child.kind,
1321
4059
  tag: child.originalTag,
1322
4060
  layoutRole: child.layoutRole,
4061
+ semanticRole: child.semanticRole,
4062
+ uiContract: createUiContractForChild(child, styleContext),
1323
4063
  defaultClasses,
1324
4064
  conditionalClasses: child.conditionalClasses?.slice(0, 32) ?? [],
4065
+ layoutSafety: child.layoutSafety,
4066
+ responsiveProfile: child.responsiveProfile,
4067
+ scaleProfile: child.scaleProfile,
4068
+ roleSignature: child.roleSignature,
4069
+ variants: child.variants ?? {},
4070
+ styleHelper: child.styleHelper,
4071
+ styleHelperVariants: child.styleHelperVariants ?? {},
1325
4072
  classGroups,
1326
4073
  labelHint: child.label ? labelSignature(child.label) : undefined,
1327
4074
  };
@@ -1331,25 +4078,125 @@ function withoutConnectedChildSignature(child) {
1331
4078
  return Object.fromEntries(Object.entries(child).filter(([key]) => key !== 'signature'));
1332
4079
  }
1333
4080
  function connectedChildKind(child) {
4081
+ if (child.semanticRole === 'icon' || child.semanticRole === 'component-icon') {
4082
+ return 'icon';
4083
+ }
4084
+ if (child.semanticRole === 'image') {
4085
+ return 'image';
4086
+ }
4087
+ if (child.semanticRole === 'divider') {
4088
+ return 'divider';
4089
+ }
1334
4090
  if (child.layoutRole === 'eyebrow-or-badge') {
1335
4091
  return 'badge';
1336
4092
  }
1337
4093
  return child.kind;
1338
4094
  }
4095
+ function isRecordValue(value) {
4096
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value));
4097
+ }
4098
+ function isRoleSignatureRecord(value) {
4099
+ return (isRecordValue(value) &&
4100
+ typeof value.role === 'string' &&
4101
+ typeof value.roleGroup === 'string' &&
4102
+ typeof value.scale === 'string' &&
4103
+ typeof value.density === 'string' &&
4104
+ typeof value.surface === 'string' &&
4105
+ typeof value.layout === 'string' &&
4106
+ Array.isArray(value.flags) &&
4107
+ Array.isArray(value.exactClassFacts));
4108
+ }
4109
+ function summarizeConnectedLayoutSafety(instances) {
4110
+ return mergeLayoutSafetyProfiles([
4111
+ ...instances.map((instance) => ({ layoutSafety: instance.root?.layoutSafety })),
4112
+ ...instances.flatMap((instance) => (instance.children ?? []).map((child) => ({
4113
+ layoutSafety: isLayoutSafetyProfile(child.layoutSafety) ? child.layoutSafety : undefined,
4114
+ }))),
4115
+ ]);
4116
+ }
4117
+ function summarizeConnectedResponsiveProfile(instances) {
4118
+ return mergeResponsiveProfiles([
4119
+ ...instances.map((instance) => ({ responsiveProfile: instance.root?.responsiveProfile })),
4120
+ ...instances.flatMap((instance) => (instance.children ?? []).map((child) => ({
4121
+ responsiveProfile: isResponsiveProfile(child.responsiveProfile)
4122
+ ? child.responsiveProfile
4123
+ : undefined,
4124
+ }))),
4125
+ ]);
4126
+ }
4127
+ function summarizeConnectedScaleProfile(instances) {
4128
+ return mergeScaleProfiles([
4129
+ ...instances.map((instance) => ({ scaleProfile: instance.root?.scaleProfile })),
4130
+ ...instances.flatMap((instance) => (instance.children ?? []).map((child) => ({
4131
+ scaleProfile: isScaleProfile(child.scaleProfile) ? child.scaleProfile : undefined,
4132
+ }))),
4133
+ ]);
4134
+ }
4135
+ function summarizeConnectedSemanticProfile(instances) {
4136
+ return mergeSemanticProfiles(instances.map((instance) => ({ semanticProfile: instance.root?.semanticProfile })));
4137
+ }
4138
+ function summarizeConnectedRoleSignatures(instances) {
4139
+ return (0, utils_1.uniqueBy)(instances
4140
+ .map((instance) => instance.root?.roleSignature)
4141
+ .filter((value) => Boolean(value)), (signature) => roleSignatureKey(signature)).slice(0, 12);
4142
+ }
4143
+ function summarizeConnectedCompoundStructure(instances) {
4144
+ const compoundInstances = instances.filter((instance) => instance.root?.semanticProfile?.isCompound);
4145
+ if (!compoundInstances.length) {
4146
+ return undefined;
4147
+ }
4148
+ const childCounts = compoundInstances.map((instance) => instance.children?.length ?? 0);
4149
+ const childOrderExamples = (0, utils_1.uniqueBy)(compoundInstances
4150
+ .map((instance) => instance.value.childStructureContract)
4151
+ .filter((value) => isRecordValue(value)), (value) => JSON.stringify(value)).slice(0, 8);
4152
+ const semanticProfile = summarizeConnectedSemanticProfile(compoundInstances);
4153
+ return {
4154
+ preserveCompoundChildren: true,
4155
+ preserveIconAndDividerRoles: Boolean(semanticProfile?.hasIcon || semanticProfile?.hasDivider),
4156
+ preserveMultipleTextSegments: Boolean(semanticProfile?.hasMultipleTextStyles),
4157
+ directChildCountRange: {
4158
+ min: Math.min(...childCounts),
4159
+ max: Math.max(...childCounts),
4160
+ },
4161
+ semanticRoles: semanticProfile?.roles ?? [],
4162
+ childOrderExamples,
4163
+ };
4164
+ }
4165
+ function summarizeConnectedParentContexts(instances) {
4166
+ return (0, utils_1.uniqueBy)(instances
4167
+ .map((instance) => instance.value.parentContext)
4168
+ .filter((value) => isRecordValue(value)), (context) => JSON.stringify(context)).slice(0, 12);
4169
+ }
1339
4170
  function summarizeConnectedChildren(instances) {
1340
4171
  const childValues = instances.flatMap((instance) => instance.children ?? []);
1341
4172
  const grouped = (0, utils_1.groupBy)(childValues, (child) => [
1342
4173
  String(child.kind ?? ''),
1343
4174
  String(child.layoutRole ?? ''),
4175
+ String(child.semanticRole ?? ''),
4176
+ roleSignatureKey(isRoleSignatureRecord(child.roleSignature) ? child.roleSignature : undefined),
1344
4177
  JSON.stringify(child.classGroups ?? {}),
1345
4178
  ].join(':'));
1346
4179
  return Array.from(grouped.values())
1347
4180
  .map((children) => {
1348
4181
  const first = children[0] ?? {};
4182
+ const layoutSafety = mergeLayoutSafetyProfiles(children.map((child) => ({
4183
+ layoutSafety: isLayoutSafetyProfile(child.layoutSafety) ? child.layoutSafety : undefined,
4184
+ })));
4185
+ const responsiveProfile = mergeResponsiveProfiles(children.map((child) => ({
4186
+ responsiveProfile: isResponsiveProfile(child.responsiveProfile)
4187
+ ? child.responsiveProfile
4188
+ : undefined,
4189
+ })));
4190
+ const scaleProfile = mergeScaleProfiles(children.map((child) => ({
4191
+ scaleProfile: isScaleProfile(child.scaleProfile) ? child.scaleProfile : undefined,
4192
+ })));
1349
4193
  return {
1350
4194
  kind: first.kind,
1351
4195
  tag: first.tag,
1352
4196
  layoutRole: first.layoutRole,
4197
+ semanticRole: first.semanticRole,
4198
+ roleSignature: first.roleSignature,
4199
+ uiContract: first.uiContract,
1353
4200
  count: children.length,
1354
4201
  defaultClasses: (0, utils_1.unique)(children.flatMap((child) => Array.isArray(child.defaultClasses)
1355
4202
  ? child.defaultClasses.filter((item) => typeof item === 'string')
@@ -1357,11 +4204,29 @@ function summarizeConnectedChildren(instances) {
1357
4204
  classGroups: (0, extractors_1.mergeClassGroups)(children
1358
4205
  .map((child) => child.classGroups)
1359
4206
  .filter((value) => Boolean(value))),
4207
+ ...(layoutSafety ? { layoutSafety } : {}),
4208
+ ...(responsiveProfile ? { responsiveProfile } : {}),
4209
+ ...(scaleProfile ? { scaleProfile } : {}),
4210
+ variants: mergeChildVariants(children),
4211
+ styleHelpers: (0, utils_1.unique)(children
4212
+ .map((child) => child.styleHelper)
4213
+ .filter((value) => typeof value === 'string' && Boolean(value))).slice(0, 12),
1360
4214
  };
1361
4215
  })
1362
4216
  .sort((a, b) => Number(b.count) - Number(a.count))
1363
4217
  .slice(0, 16);
1364
4218
  }
4219
+ function mergeChildVariants(children) {
4220
+ const variants = children
4221
+ .map((child) => child.variants)
4222
+ .filter((value) => {
4223
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
4224
+ return false;
4225
+ }
4226
+ return Object.values(value).every((item) => typeof item === 'string');
4227
+ });
4228
+ return (0, extractors_1.mergeVariantProps)(variants);
4229
+ }
1365
4230
  function compactConnectedPatternInstance(instance) {
1366
4231
  return {
1367
4232
  path: instance.file.relPath,
@@ -1548,7 +4413,9 @@ function createConceptPatternDecision(name, scores, shouldReview, roleCandidateC
1548
4413
  function connectedPatternVariants(domain, instances) {
1549
4414
  const preferredPrefixes = domain === 'frontend'
1550
4415
  ? ['component.', 'route.', 'style.', 'rule.']
1551
- : ['api.', 'controller.', 'service.', 'module.', 'model.', 'route.'];
4416
+ : domain === 'data'
4417
+ ? ['database.', 'model.', 'service.', 'api.', 'route.']
4418
+ : ['api.', 'controller.', 'service.', 'module.', 'model.', 'route.'];
1552
4419
  return (0, utils_1.unique)(instances.flatMap((instance) => [
1553
4420
  ...instance.file.components.map((component) => (0, utils_1.toRegistryKey)('component', component)),
1554
4421
  ...instance.file.routes.map((route) => (0, utils_1.toRegistryKey)('route', route)),
@@ -1557,6 +4424,9 @@ function connectedPatternVariants(domain, instances) {
1557
4424
  ...instance.file.services.map((service) => (0, utils_1.toRegistryKey)('service', service)),
1558
4425
  ...instance.file.modules.map((moduleName) => (0, utils_1.toRegistryKey)('module', moduleName)),
1559
4426
  ...instance.file.models.map((model) => (0, utils_1.toRegistryKey)('model', model)),
4427
+ ...(hasDatabaseSurface(instance.file)
4428
+ ? [(0, utils_1.toRegistryKey)('database', instance.file.relPath.replace(/\.[^.]+$/, ''))]
4429
+ : []),
1560
4430
  ...instance.graphKeys.filter((key) => preferredPrefixes.some((prefix) => key.startsWith(prefix))),
1561
4431
  ])).slice(0, 80);
1562
4432
  }
@@ -1572,6 +4442,9 @@ function connectedPatternChildren(domain, concept, instances, dependencies) {
1572
4442
  const graphChildren = dependencies.filter((key) => ['component.', 'style.', 'rule.', 'route.'].some((prefix) => key.startsWith(prefix)));
1573
4443
  return (0, utils_1.unique)([...semanticChildren, ...graphChildren]).slice(0, 80);
1574
4444
  }
4445
+ if (domain === 'data') {
4446
+ return (0, utils_1.unique)(dependencies.filter((key) => ['database.', 'model.', 'service.', 'api.', 'route.', 'file.'].some((prefix) => key.startsWith(prefix)))).slice(0, 80);
4447
+ }
1575
4448
  return (0, utils_1.unique)(dependencies.filter((key) => ['api.', 'controller.', 'service.', 'module.', 'model.', 'route.', 'file.'].some((prefix) => key.startsWith(prefix)))).slice(0, 80);
1576
4449
  }
1577
4450
  function derivePatternOwners(filePaths) {
@@ -1635,6 +4508,7 @@ function backendPatternWeight(file) {
1635
4508
  file.controllers.length * 2 +
1636
4509
  file.services.length * 2 +
1637
4510
  file.models.length * 1.5 +
4511
+ file.databaseAccesses.length * 1.2 +
1638
4512
  file.authHints.length * 0.5 +
1639
4513
  file.events.length * 0.5 +
1640
4514
  file.jobs.length * 0.5);
@@ -1644,6 +4518,7 @@ function hasBackendPatternSurface(file) {
1644
4518
  file.controllers.length ||
1645
4519
  file.services.length ||
1646
4520
  file.modules.length ||
4521
+ file.databaseAccesses.length ||
1647
4522
  file.apiCalls.length ||
1648
4523
  file.middleware.length ||
1649
4524
  file.jobs.length ||
@@ -1651,13 +4526,17 @@ function hasBackendPatternSurface(file) {
1651
4526
  }
1652
4527
  function backendPatternConcept(file) {
1653
4528
  if (file.controllers.length) {
1654
- return file.services.length || file.models.length ? 'api-controller-flow' : 'api-controller';
4529
+ return file.services.length || file.models.length || file.databaseAccesses.length
4530
+ ? 'api-controller-flow'
4531
+ : 'api-controller';
1655
4532
  }
1656
4533
  if (file.apiHandlers.length) {
1657
- return file.models.length || file.authHints.length ? 'api-data-flow' : 'api-route';
4534
+ return file.models.length || file.databaseAccesses.length || file.authHints.length
4535
+ ? 'api-data-flow'
4536
+ : 'api-route';
1658
4537
  }
1659
4538
  if (file.services.length) {
1660
- return file.models.length ? 'service-data-flow' : 'service';
4539
+ return file.models.length || file.databaseAccesses.length ? 'service-data-flow' : 'service';
1661
4540
  }
1662
4541
  if (file.jobs.length || file.events.length) {
1663
4542
  return 'async-flow';
@@ -1667,6 +4546,22 @@ function backendPatternConcept(file) {
1667
4546
  }
1668
4547
  return 'backend-flow';
1669
4548
  }
4549
+ function databaseFieldShapeInputs(model) {
4550
+ return model.fields.map((field) => [
4551
+ field.name,
4552
+ field.type ?? '',
4553
+ field.id ? '@id' : '',
4554
+ field.unique ? '@unique' : '',
4555
+ field.optional ? '?' : '',
4556
+ field.list ? '[]' : '',
4557
+ field.default ? `default:${field.default}` : '',
4558
+ field.enumName ? `enum:${field.enumName}` : '',
4559
+ field.relation ? '@relation' : '',
4560
+ field.relation?.onDelete ? `onDelete:${field.relation.onDelete}` : '',
4561
+ ]
4562
+ .filter(Boolean)
4563
+ .join(' '));
4564
+ }
1670
4565
  function summarizeModelFieldShape(fields) {
1671
4566
  const counts = {
1672
4567
  id: 0,
@@ -1718,7 +4613,25 @@ function createConnectedPatternGuidance(first, instances, decorativeAccentClasse
1718
4613
  `Pattern score is based on usage count, file spread, component/route evidence, and structural specificity.`,
1719
4614
  ];
1720
4615
  if (first.domain === 'frontend') {
1721
- guidance.push('Use defaultClasses for normal generation. Use childStructure to keep spacing, hierarchy, and slots aligned with the source pattern.');
4616
+ guidance.push('Use defaultClasses for normal generation. Use childStructure to keep spacing, hierarchy, and slots aligned with the source pattern.', 'Use structureContract, parentContexts, layoutSafety, responsiveProfile, and scaleProfile as hard generation constraints before adding any new visual detail.');
4617
+ if (summarizeConnectedLayoutSafety(instances)) {
4618
+ guidance.push('Preserve layoutSafety classes with their original element. They prevent wrapped buttons, overflowing card text, collapsed columns, and mobile clipping.');
4619
+ }
4620
+ if (summarizeConnectedParentContexts(instances).length) {
4621
+ guidance.push('Preserve parentContexts when placing this pattern. Repeated cards and nested panels should inherit the same grid/flex parent relationship as the source.');
4622
+ }
4623
+ if (summarizeConnectedScaleProfile(instances)) {
4624
+ guidance.push('Preserve scaleProfile for typography, spacing, max-width, and line-height. Do not make headings or section spacing larger than observed source bounds.');
4625
+ }
4626
+ if (summarizeConnectedRoleSignatures(instances).length) {
4627
+ guidance.push('Preserve roleSignature and roleConstraints as hard constraints. Lookalike patterns with different role, density, surface, layout, or exactClassFacts are different generation targets.');
4628
+ }
4629
+ if (summarizeConnectedResponsiveProfile(instances)) {
4630
+ guidance.push('Preserve responsiveProfile exactly enough that mobile and desktop layouts use the same breakpoints and visibility behavior as the source.');
4631
+ }
4632
+ if (summarizeConnectedCompoundStructure(instances)) {
4633
+ guidance.push('For compound badges, hero eyebrows, and brand chips, preserve compoundStructure: icon/image children, divider children, multiple text segments, child order, compact spacing, and separate text styles. Do not collapse the lockup into a single text-only pill.');
4634
+ }
1722
4635
  }
1723
4636
  if (decorativeAccentClasses.length) {
1724
4637
  guidance.push('decorativeAccentClasses are opt-in highlight treatments. They must not become default card/surface styling.');
@@ -1737,6 +4650,7 @@ function createConnectedPatternGuidance(first, instances, decorativeAccentClasse
1737
4650
  function connectedPatternSlug(instance) {
1738
4651
  const parts = [
1739
4652
  instance.concept,
4653
+ instance.root?.roleSignature?.role,
1740
4654
  instance.root?.layoutRole,
1741
4655
  instance.root?.tag,
1742
4656
  ...Object.keys(instance.root?.classGroups ?? {}).slice(0, 3),
@@ -1751,6 +4665,22 @@ function connectedPatternSlug(instance) {
1751
4665
  .slice(0, 5)
1752
4666
  .join('.') || instance.concept);
1753
4667
  }
4668
+ function roleSignatureKey(signature) {
4669
+ if (!signature) {
4670
+ return 'role-signature:unspecified';
4671
+ }
4672
+ return [
4673
+ signature.role,
4674
+ signature.roleGroup,
4675
+ signature.scale,
4676
+ signature.density,
4677
+ signature.surface,
4678
+ signature.layout,
4679
+ signature.flags.join(','),
4680
+ ]
4681
+ .filter(Boolean)
4682
+ .join('|');
4683
+ }
1754
4684
  function classSignature(classes) {
1755
4685
  if (!classes.length) {
1756
4686
  return 'no-classes';
@@ -1771,7 +4701,7 @@ function normalizeTagForSignature(element) {
1771
4701
  return element.originalTag.toLowerCase();
1772
4702
  }
1773
4703
  function normalizeChildTagForSignature(child) {
1774
- if (['button', 'input', 'badge', 'heading', 'text', 'link'].includes(child.kind)) {
4704
+ if (['button', 'input', 'badge', 'heading', 'text', 'link', 'icon', 'image', 'divider'].includes(child.kind)) {
1775
4705
  return child.kind;
1776
4706
  }
1777
4707
  return child.originalTag.toLowerCase();
@@ -1840,6 +4770,30 @@ function addStyleUsageEdges(entries, files, edges) {
1840
4770
  }
1841
4771
  }
1842
4772
  }
4773
+ function addDatabaseUsageEdges(entries, files, edges) {
4774
+ const modelEntries = entries.filter((entry) => entry.group === 'model');
4775
+ const modelKeysByName = new Map();
4776
+ for (const entry of modelEntries) {
4777
+ const name = typeof entry.value.name === 'string' ? entry.value.name : '';
4778
+ const tableName = typeof entry.value.tableName === 'string' ? entry.value.tableName : '';
4779
+ if (name) {
4780
+ modelKeysByName.set(name.toLowerCase(), entry.key);
4781
+ }
4782
+ if (tableName) {
4783
+ modelKeysByName.set(tableName.toLowerCase(), entry.key);
4784
+ }
4785
+ }
4786
+ for (const file of files) {
4787
+ for (const access of file.databaseAccesses) {
4788
+ const targetName = access.model ?? access.table;
4789
+ const targetKey = targetName ? modelKeysByName.get(targetName.toLowerCase()) : undefined;
4790
+ if (!targetKey) {
4791
+ continue;
4792
+ }
4793
+ edges.push(toGraphEdge(file.key, targetKey, databaseAccessEdgeType(access), `database.${access.framework}.access`, file, access.confidence));
4794
+ }
4795
+ }
4796
+ }
1843
4797
  function addModelUsageEdges(entries, files, edges) {
1844
4798
  const modelEntries = entries.filter((entry) => entry.group === 'model');
1845
4799
  for (const file of files) {
@@ -1870,6 +4824,50 @@ function addModelUsageEdges(entries, files, edges) {
1870
4824
  }
1871
4825
  }
1872
4826
  }
4827
+ function addDatabaseReviewItems(entries, reviewItems) {
4828
+ const modelEntries = entries.filter((entry) => entry.group === 'model');
4829
+ const byName = (0, utils_1.groupBy)(modelEntries, (entry) => String(entry.value.name ?? entry.key).toLowerCase());
4830
+ for (const [name, candidates] of byName) {
4831
+ if (candidates.length < 2) {
4832
+ continue;
4833
+ }
4834
+ const signatures = (0, utils_1.unique)(candidates.map((candidate) => {
4835
+ const fields = Array.isArray(candidate.value.fields)
4836
+ ? candidate.value.fields.map(String).sort()
4837
+ : [];
4838
+ const provider = String(candidate.value.provider ?? '');
4839
+ const tableName = String(candidate.value.tableName ?? '');
4840
+ return `${provider}:${tableName}:${fields.join('|')}`;
4841
+ }));
4842
+ if (signatures.length <= 1) {
4843
+ continue;
4844
+ }
4845
+ reviewItems.push({
4846
+ id: `review.data.model.${name}.conflict`,
4847
+ type: 'concept_collision',
4848
+ status: 'open',
4849
+ group: 'model',
4850
+ s: `Multiple ${name} data model shapes were found. Confirm the canonical model/table before broad generation.`,
4851
+ candidates: candidates.map((candidate) => candidate.key).slice(0, 40),
4852
+ });
4853
+ }
4854
+ const riskyAccessCandidates = entries
4855
+ .filter((entry) => entry.group === 'database' &&
4856
+ entry.value.kind === 'database-access' &&
4857
+ Array.isArray(entry.value.risk) &&
4858
+ entry.value.risk.includes('manual-review'))
4859
+ .map((entry) => entry.key);
4860
+ if (riskyAccessCandidates.length) {
4861
+ reviewItems.push({
4862
+ id: 'review.data.access.manual-review',
4863
+ type: 'low_confidence',
4864
+ status: 'open',
4865
+ group: 'database',
4866
+ s: 'Raw SQL or high-risk database accesses were found. Review before using them as broad generation patterns.',
4867
+ candidates: riskyAccessCandidates.slice(0, 40),
4868
+ });
4869
+ }
4870
+ }
1873
4871
  function addConceptClusters(entries, usedKeys, nodes, edges, reviewItems) {
1874
4872
  const concepts = [
1875
4873
  {
@@ -2082,9 +5080,25 @@ function detectProjectFacts(files) {
2082
5080
  }
2083
5081
  if (dependencies.has('prisma') ||
2084
5082
  dependencies.has('@prisma/client') ||
2085
- files.some((file) => file.models.length)) {
5083
+ files.some((file) => file.databaseModels.some((model) => model.provider === 'prisma'))) {
2086
5084
  frameworks.add('prisma');
2087
5085
  }
5086
+ if (dependencies.has('drizzle-orm') ||
5087
+ files.some((file) => file.databaseModels.some((model) => model.provider === 'drizzle'))) {
5088
+ frameworks.add('drizzle');
5089
+ }
5090
+ if (dependencies.has('typeorm') ||
5091
+ files.some((file) => file.databaseModels.some((model) => model.provider === 'typeorm'))) {
5092
+ frameworks.add('typeorm');
5093
+ }
5094
+ if (dependencies.has('sequelize') ||
5095
+ files.some((file) => file.databaseModels.some((model) => model.provider === 'sequelize'))) {
5096
+ frameworks.add('sequelize');
5097
+ }
5098
+ if (dependencies.has('mongoose') ||
5099
+ files.some((file) => file.databaseModels.some((model) => model.provider === 'mongoose'))) {
5100
+ frameworks.add('mongoose');
5101
+ }
2088
5102
  if (files.some((file) => file.language === 'css' || file.language === 'scss')) {
2089
5103
  frameworks.add('css');
2090
5104
  }
@@ -2119,7 +5133,9 @@ function detectProjectFacts(files) {
2119
5133
  if (files.some((file) => file.language === 'go')) {
2120
5134
  frameworks.add('go');
2121
5135
  }
2122
- if (files.some((file) => file.language === 'sql')) {
5136
+ if (files.some((file) => file.language === 'sql' ||
5137
+ file.databaseModels.some((model) => model.provider === 'sql') ||
5138
+ file.databaseMigrations.some((migration) => migration.provider === 'sql'))) {
2123
5139
  frameworks.add('sql');
2124
5140
  }
2125
5141
  return {
@@ -2137,8 +5153,10 @@ function detectFrameworkForFile(file) {
2137
5153
  if (file.controllers.length || file.services.length || file.modules.length) {
2138
5154
  return 'nestjs';
2139
5155
  }
2140
- if (file.models.length) {
2141
- return 'prisma';
5156
+ if (file.databaseModels.length || file.databaseEnums.length || file.databaseMigrations.length) {
5157
+ return (file.databaseModels[0]?.provider ??
5158
+ file.databaseEnums[0]?.provider ??
5159
+ file.databaseMigrations[0]?.provider);
2142
5160
  }
2143
5161
  if (file.kind.includes('style') || file.kind.includes('css')) {
2144
5162
  return 'css';