@schemyx/mcp 0.1.1 → 0.1.3

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,1923 @@ 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-cols-\[([^\]]+)\]$/.test(base)) {
1645
+ declarations.push(declaration('grid-template-columns', arbitraryValue(base).replace(/_/g, ' ')));
1646
+ }
1647
+ if (/^grid-rows-(\d+)$/.test(base)) {
1648
+ const [, value] = base.match(/^grid-rows-(\d+)$/) ?? [];
1649
+ declarations.push(declaration('grid-template-rows', `repeat(${value}, minmax(0, 1fr))`));
1650
+ }
1651
+ if (/^grid-rows-\[([^\]]+)\]$/.test(base)) {
1652
+ declarations.push(declaration('grid-template-rows', arbitraryValue(base).replace(/_/g, ' ')));
1653
+ }
1654
+ if (base === 'auto-rows-fr') {
1655
+ declarations.push(declaration('grid-auto-rows', 'minmax(0, 1fr)'));
1656
+ }
1657
+ if (/^auto-rows-\[([^\]]+)\]$/.test(base)) {
1658
+ declarations.push(declaration('grid-auto-rows', arbitraryValue(base).replace(/_/g, ' ')));
1659
+ }
1660
+ if (base === 'flex-row')
1661
+ declarations.push(declaration('flex-direction', 'row'));
1662
+ if (base === 'flex-col')
1663
+ declarations.push(declaration('flex-direction', 'column'));
1664
+ if (base === 'flex-wrap')
1665
+ declarations.push(declaration('flex-wrap', 'wrap'));
1666
+ if (base === 'flex-nowrap')
1667
+ declarations.push(declaration('flex-wrap', 'nowrap'));
1668
+ if (base === 'object-cover')
1669
+ declarations.push(declaration('object-fit', 'cover'));
1670
+ if (base === 'object-contain')
1671
+ declarations.push(declaration('object-fit', 'contain'));
1672
+ if (base === 'object-fill')
1673
+ declarations.push(declaration('object-fit', 'fill'));
1674
+ if (base === 'object-center')
1675
+ declarations.push(declaration('object-position', 'center'));
1676
+ if (/^aspect-\[([^\]]+)\]$/.test(base)) {
1677
+ declarations.push(declaration('aspect-ratio', arbitraryValue(base).replace(/_/g, ' ')));
1678
+ }
1679
+ if (/^col-span-(\d+)$/.test(base)) {
1680
+ const [, value] = base.match(/^col-span-(\d+)$/) ?? [];
1681
+ declarations.push(declaration('grid-column', `span ${value} / span ${value}`));
1682
+ }
1683
+ if (/^row-span-(\d+)$/.test(base)) {
1684
+ const [, value] = base.match(/^row-span-(\d+)$/) ?? [];
1685
+ declarations.push(declaration('grid-row', `span ${value} / span ${value}`));
1686
+ }
1687
+ if (/^(?:relative|absolute|fixed|sticky|static)$/.test(base)) {
1688
+ declarations.push(declaration('position', base));
1689
+ }
1690
+ if (/^(?:inset|top|right|bottom|left)-(.+)$/.test(base)) {
1691
+ const [, property, token] = base.match(/^(inset|top|right|bottom|left)-(.+)$/) ?? [];
1692
+ const value = tailwindScaleValue(token);
1693
+ if (value)
1694
+ declarations.push(declaration(property, value));
1695
+ }
1696
+ if (/^z-\[([^\]]+)\]$/.test(base)) {
1697
+ declarations.push(declaration('z-index', arbitraryValue(base)));
1698
+ }
1699
+ if (/^z-(\d+)$/.test(base)) {
1700
+ const [, value] = base.match(/^z-(\d+)$/) ?? [];
1701
+ declarations.push(declaration('z-index', value));
1702
+ }
1703
+ return declarations;
1704
+ }
1705
+ function tailwindAlignmentDeclaration(base) {
1706
+ const alignItems = {
1707
+ 'items-start': 'flex-start',
1708
+ 'items-end': 'flex-end',
1709
+ 'items-center': 'center',
1710
+ 'items-baseline': 'baseline',
1711
+ 'items-stretch': 'stretch',
1712
+ };
1713
+ const justifyContent = {
1714
+ 'justify-start': 'flex-start',
1715
+ 'justify-end': 'flex-end',
1716
+ 'justify-center': 'center',
1717
+ 'justify-between': 'space-between',
1718
+ 'justify-around': 'space-around',
1719
+ 'justify-evenly': 'space-evenly',
1720
+ };
1721
+ if (alignItems[base])
1722
+ return { property: 'align-items', value: alignItems[base] };
1723
+ if (justifyContent[base])
1724
+ return { property: 'justify-content', value: justifyContent[base] };
1725
+ return undefined;
1726
+ }
1727
+ function tailwindSpacingDeclaration(base) {
1728
+ const spacingMatch = base.match(/^(p|px|py|pt|pr|pb|pl|m|mx|my|mt|mr|mb|ml|gap|gap-x|gap-y)-(.+)$/);
1729
+ if (!spacingMatch) {
1730
+ return [];
1731
+ }
1732
+ const [, prefix, token] = spacingMatch;
1733
+ const value = token === 'auto' ? 'auto' : tailwindScaleValue(token);
1734
+ if (!value) {
1735
+ return [];
1736
+ }
1737
+ const properties = {
1738
+ p: ['padding'],
1739
+ px: ['padding-left', 'padding-right'],
1740
+ py: ['padding-top', 'padding-bottom'],
1741
+ pt: ['padding-top'],
1742
+ pr: ['padding-right'],
1743
+ pb: ['padding-bottom'],
1744
+ pl: ['padding-left'],
1745
+ m: ['margin'],
1746
+ mx: ['margin-left', 'margin-right'],
1747
+ my: ['margin-top', 'margin-bottom'],
1748
+ mt: ['margin-top'],
1749
+ mr: ['margin-right'],
1750
+ mb: ['margin-bottom'],
1751
+ ml: ['margin-left'],
1752
+ gap: ['gap'],
1753
+ 'gap-x': ['column-gap'],
1754
+ 'gap-y': ['row-gap'],
1755
+ };
1756
+ return (properties[prefix] ?? []).map((property) => ({ property, value }));
1757
+ }
1758
+ function tailwindSizingDeclaration(base) {
1759
+ if (base === 'min-w-0')
1760
+ return [{ property: 'min-width', value: '0px' }];
1761
+ if (base === 'min-h-0')
1762
+ return [{ property: 'min-height', value: '0px' }];
1763
+ if (base === 'w-px')
1764
+ return [{ property: 'width', value: '1px' }];
1765
+ if (base === 'h-px')
1766
+ return [{ property: 'height', value: '1px' }];
1767
+ if (base === 'w-full')
1768
+ return [{ property: 'width', value: '100%' }];
1769
+ if (base === 'h-full')
1770
+ return [{ property: 'height', value: '100%' }];
1771
+ if (base === 'max-w-none')
1772
+ return [{ property: 'max-width', value: 'none' }];
1773
+ if (base === 'shrink-0')
1774
+ return [{ property: 'flex-shrink', value: '0' }];
1775
+ if (base === 'shrink')
1776
+ return [{ property: 'flex-shrink', value: '1' }];
1777
+ if (base === 'grow')
1778
+ return [{ property: 'flex-grow', value: '1' }];
1779
+ if (base === 'grow-0')
1780
+ return [{ property: 'flex-grow', value: '0' }];
1781
+ const sizeMatch = base.match(/^(size|w|h|min-w|min-h|max-w|max-h)-(.+)$/);
1782
+ if (!sizeMatch) {
1783
+ return [];
1784
+ }
1785
+ const [, prefix, token] = sizeMatch;
1786
+ const value = tailwindScaleValue(token) ?? tailwindNamedSizeValue(token);
1787
+ if (!value) {
1788
+ return [];
1789
+ }
1790
+ const properties = {
1791
+ size: ['width', 'height'],
1792
+ w: ['width'],
1793
+ h: ['height'],
1794
+ 'min-w': ['min-width'],
1795
+ 'min-h': ['min-height'],
1796
+ 'max-w': ['max-width'],
1797
+ 'max-h': ['max-height'],
1798
+ };
1799
+ return (properties[prefix] ?? []).map((property) => ({ property, value }));
1800
+ }
1801
+ function tailwindTypographyDeclaration(base) {
1802
+ const declarations = [];
1803
+ const textMatch = base.match(/^text-(.+)$/);
1804
+ if (textMatch) {
1805
+ const token = textMatch[1];
1806
+ const arbitrary = arbitraryTokenValue(token);
1807
+ if (arbitrary && looksLikeCssLength(arbitrary)) {
1808
+ declarations.push({ property: 'font-size', value: arbitrary });
1809
+ }
1810
+ else if (tailwindFontSizeScale[token]) {
1811
+ declarations.push({ property: 'font-size', value: tailwindFontSizeScale[token] });
1812
+ }
1813
+ }
1814
+ const weightMatch = base.match(/^font-(thin|extralight|light|normal|medium|semibold|bold|extrabold|black)$/);
1815
+ if (weightMatch)
1816
+ declarations.push({ property: 'font-weight', value: tailwindFontWeightScale[weightMatch[1]] });
1817
+ const leadingMatch = base.match(/^leading-(.+)$/);
1818
+ if (leadingMatch) {
1819
+ const token = leadingMatch[1];
1820
+ const arbitrary = arbitraryTokenValue(token);
1821
+ const value = arbitrary ?? tailwindLineHeightScale[token] ?? tailwindScaleValue(token);
1822
+ if (value)
1823
+ declarations.push({ property: 'line-height', value });
1824
+ }
1825
+ const trackingMatch = base.match(/^tracking-(.+)$/);
1826
+ if (trackingMatch) {
1827
+ const token = trackingMatch[1];
1828
+ const arbitrary = arbitraryTokenValue(token);
1829
+ const value = arbitrary ?? tailwindTrackingScale[token];
1830
+ if (value)
1831
+ declarations.push({ property: 'letter-spacing', value });
1832
+ }
1833
+ if (base === 'uppercase')
1834
+ declarations.push({ property: 'text-transform', value: 'uppercase' });
1835
+ if (base === 'lowercase')
1836
+ declarations.push({ property: 'text-transform', value: 'lowercase' });
1837
+ if (base === 'capitalize')
1838
+ declarations.push({ property: 'text-transform', value: 'capitalize' });
1839
+ if (base === 'normal-case')
1840
+ declarations.push({ property: 'text-transform', value: 'none' });
1841
+ if (base === 'text-left')
1842
+ declarations.push({ property: 'text-align', value: 'left' });
1843
+ if (base === 'text-center')
1844
+ declarations.push({ property: 'text-align', value: 'center' });
1845
+ if (base === 'text-right')
1846
+ declarations.push({ property: 'text-align', value: 'right' });
1847
+ return declarations;
1848
+ }
1849
+ function tailwindBorderDeclaration(base, styleContext) {
1850
+ if (base === 'border')
1851
+ return [{ property: 'border-width', value: '1px' }];
1852
+ if (/^border-(\d+)$/.test(base)) {
1853
+ const [, value] = base.match(/^border-(\d+)$/) ?? [];
1854
+ return [{ property: 'border-width', value: `${value}px` }];
1855
+ }
1856
+ const roundedMatch = base.match(/^rounded(?:-(.+))?$/);
1857
+ if (roundedMatch) {
1858
+ const token = roundedMatch[1] ?? 'DEFAULT';
1859
+ const arbitrary = arbitraryTokenValue(token);
1860
+ const value = arbitrary ?? tailwindRadiusScale[token] ?? cssTokenValue('radius', styleContext);
1861
+ return value ? [{ property: 'border-radius', value }] : [];
1862
+ }
1863
+ if (base.startsWith('border-') && !/^border-(?:t|r|b|l|x|y)$/.test(base)) {
1864
+ return [{ property: 'border-color', value: tailwindColorValue(base.slice('border-'.length), styleContext) }];
1865
+ }
1866
+ return [];
1867
+ }
1868
+ function tailwindColorDeclaration(base, styleContext) {
1869
+ const bgMatch = base.match(/^bg-(.+)$/);
1870
+ if (bgMatch) {
1871
+ return [{ property: 'background-color', value: tailwindColorValue(bgMatch[1], styleContext) }];
1872
+ }
1873
+ const textMatch = base.match(/^text-(.+)$/);
1874
+ if (textMatch && !tailwindFontSizeScale[textMatch[1]] && !looksLikeCssLength(arbitraryTokenValue(textMatch[1]) ?? '')) {
1875
+ return [{ property: 'color', value: tailwindColorValue(textMatch[1], styleContext) }];
1876
+ }
1877
+ const fillMatch = base.match(/^fill-(.+)$/);
1878
+ if (fillMatch)
1879
+ return [{ property: 'fill', value: tailwindColorValue(fillMatch[1], styleContext) }];
1880
+ const strokeMatch = base.match(/^stroke-(.+)$/);
1881
+ if (strokeMatch)
1882
+ return [{ property: 'stroke', value: tailwindColorValue(strokeMatch[1], styleContext) }];
1883
+ return [];
1884
+ }
1885
+ function tailwindColorValue(token, styleContext) {
1886
+ const arbitrary = arbitraryTokenValue(token);
1887
+ if (arbitrary) {
1888
+ return resolveCssValueTokens(arbitrary.replace(/_/g, ' '), styleContext?.cssVariables);
1889
+ }
1890
+ const [name, opacity] = token.split('/');
1891
+ const cssVariable = cssTokenValue(name, styleContext);
1892
+ const baseValue = cssVariable ?? colorKeywordValue(name) ?? `token(${name})`;
1893
+ return opacity ? `${baseValue} / ${opacityToPercent(opacity)}` : baseValue;
1894
+ }
1895
+ function cssTokenValue(token, styleContext) {
1896
+ const candidates = (0, utils_1.unique)([`--${token}`, `--color-${token}`, `--${token.replace(/\//g, '-')}`]);
1897
+ const match = candidates.find((candidate) => styleContext?.cssVariables.has(candidate));
1898
+ return match ? `var(${match})` : undefined;
1899
+ }
1900
+ function colorKeywordValue(token) {
1901
+ const colors = {
1902
+ transparent: 'transparent',
1903
+ current: 'currentColor',
1904
+ black: '#000000',
1905
+ white: '#ffffff',
1906
+ };
1907
+ return colors[token];
1908
+ }
1909
+ function opacityToPercent(value) {
1910
+ const arbitrary = arbitraryTokenValue(value);
1911
+ if (arbitrary) {
1912
+ const numericArbitrary = Number(arbitrary);
1913
+ if (Number.isFinite(numericArbitrary)) {
1914
+ return `${numericArbitrary * 100}%`;
1915
+ }
1916
+ return arbitrary;
1917
+ }
1918
+ const numeric = Number(value);
1919
+ if (Number.isFinite(numeric)) {
1920
+ return `${numeric}%`;
1921
+ }
1922
+ return value;
1923
+ }
1924
+ function tailwindScaleValue(token) {
1925
+ const arbitrary = arbitraryTokenValue(token);
1926
+ if (arbitrary) {
1927
+ return arbitrary.replace(/_/g, ' ');
1928
+ }
1929
+ const negative = token.startsWith('-');
1930
+ const lookup = negative ? token.slice(1) : token;
1931
+ const value = tailwindSpacingScale[lookup];
1932
+ return value ? `${negative ? '-' : ''}${value}` : undefined;
1933
+ }
1934
+ function tailwindNamedSizeValue(token) {
1935
+ const values = {
1936
+ auto: 'auto',
1937
+ full: '100%',
1938
+ screen: '100vw',
1939
+ svw: '100svw',
1940
+ lvw: '100lvw',
1941
+ dvw: '100dvw',
1942
+ min: 'min-content',
1943
+ max: 'max-content',
1944
+ fit: 'fit-content',
1945
+ prose: '65ch',
1946
+ xs: '20rem',
1947
+ sm: '24rem',
1948
+ md: '28rem',
1949
+ lg: '32rem',
1950
+ xl: '36rem',
1951
+ '2xl': '42rem',
1952
+ '3xl': '48rem',
1953
+ '4xl': '56rem',
1954
+ '5xl': '64rem',
1955
+ '6xl': '72rem',
1956
+ '7xl': '80rem',
1957
+ };
1958
+ return values[token];
1959
+ }
1960
+ function arbitraryTokenValue(token) {
1961
+ const match = token.match(/^\[(.+)\]$/);
1962
+ return match?.[1];
1963
+ }
1964
+ function arbitraryValue(base) {
1965
+ return base.match(/\[(.+)\]/)?.[1] ?? '';
1966
+ }
1967
+ function looksLikeCssLength(value) {
1968
+ return /^-?(?:\d|\.)/.test(value) || /^var\(--/.test(value) || /^clamp\(/.test(value);
1969
+ }
1970
+ function tailwindShadowValue(base) {
1971
+ const arbitrary = arbitraryValue(base);
1972
+ if (arbitrary) {
1973
+ return arbitrary.replace(/_/g, ' ');
1974
+ }
1975
+ if (base === 'shadow-none') {
1976
+ return 'none';
1977
+ }
1978
+ return `tailwind(${base})`;
1979
+ }
1980
+ function bootstrapDeclarationsForClass(className, styleContext) {
1981
+ const base = (0, utils_1.classBase)(className).replace(/^!/, '');
1982
+ if (!isBootstrapClass(base)) {
1983
+ return [];
1984
+ }
1985
+ const prefixes = bootstrapResponsivePrefixes(base);
1986
+ const normalized = stripBootstrapResponsiveInfix(base);
1987
+ const declaration = (property, value) => ({
1988
+ property,
1989
+ value: resolveCssValueTokens(value, styleContext?.cssVariables),
1990
+ source: 'bootstrap-utility',
1991
+ styleSystem: 'bootstrap',
1992
+ className,
1993
+ prefixes,
1994
+ cascadeRank: 20,
1995
+ });
1996
+ const declarations = [];
1997
+ declarations.push(...bootstrapComponentDeclarations(normalized).map((item) => declaration(item.property, item.value)));
1998
+ declarations.push(...bootstrapSpacingDeclarations(normalized).map((item) => declaration(item.property, item.value)));
1999
+ declarations.push(...bootstrapDisplayDeclarations(normalized).map((item) => declaration(item.property, item.value)));
2000
+ declarations.push(...bootstrapFlexDeclarations(normalized).map((item) => declaration(item.property, item.value)));
2001
+ declarations.push(...bootstrapSizingDeclarations(normalized).map((item) => declaration(item.property, item.value)));
2002
+ declarations.push(...bootstrapTypographyDeclarations(normalized).map((item) => declaration(item.property, item.value)));
2003
+ declarations.push(...bootstrapColorDeclarations(normalized).map((item) => declaration(item.property, item.value)));
2004
+ declarations.push(...bootstrapBorderDeclarations(normalized).map((item) => declaration(item.property, item.value)));
2005
+ declarations.push(...bootstrapGridDeclarations(normalized).map((item) => declaration(item.property, item.value)));
2006
+ return declarations;
2007
+ }
2008
+ function isBootstrapClass(className) {
2009
+ const base = stripBootstrapResponsiveInfix((0, utils_1.classBase)(className));
2010
+ 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);
2011
+ }
2012
+ function bootstrapResponsivePrefixes(className) {
2013
+ 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))-/);
2014
+ return match ? [match[1]] : [];
2015
+ }
2016
+ function stripBootstrapResponsiveInfix(className) {
2017
+ 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-');
2018
+ }
2019
+ function bootstrapComponentDeclarations(base) {
2020
+ const declarations = [];
2021
+ if (base === 'container' || base === 'container-fluid') {
2022
+ 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' });
2023
+ }
2024
+ if (base === 'row') {
2025
+ 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))' });
2026
+ }
2027
+ if (base === 'card') {
2028
+ 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)' });
2029
+ }
2030
+ if (base === 'card-body') {
2031
+ 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)' });
2032
+ }
2033
+ if (base === 'btn') {
2034
+ 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)' });
2035
+ }
2036
+ if (base.startsWith('btn-') && base !== 'btn-link') {
2037
+ const tone = base.replace(/^btn-outline-/, '').replace(/^btn-/, '');
2038
+ const outline = base.startsWith('btn-outline-');
2039
+ declarations.push({ property: 'color', value: outline ? `var(--bs-${tone})` : `var(--bs-btn-color)` }, {
2040
+ property: 'background-color',
2041
+ value: outline ? 'transparent' : `var(--bs-${tone})`,
2042
+ }, { property: 'border-color', value: `var(--bs-${tone})` });
2043
+ }
2044
+ if (base === 'btn-link') {
2045
+ 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' });
2046
+ }
2047
+ if (base === 'badge') {
2048
+ 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)' });
2049
+ }
2050
+ if (base === 'form-control' || base === 'form-select') {
2051
+ 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)' });
2052
+ }
2053
+ if (base === 'form-label') {
2054
+ declarations.push({ property: 'margin-bottom', value: '0.5rem' }, { property: 'display', value: 'inline-block' });
2055
+ }
2056
+ if (base === 'navbar') {
2057
+ 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)' });
2058
+ }
2059
+ if (base === 'nav') {
2060
+ 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' });
2061
+ }
2062
+ if (base === 'nav-link') {
2063
+ 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' });
2064
+ }
2065
+ if (base === 'alert') {
2066
+ 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)' });
2067
+ }
2068
+ return declarations;
2069
+ }
2070
+ function bootstrapSpacingDeclarations(base) {
2071
+ const match = base.match(/^(m|p)([stebxy]?)-([0-5]|auto)$/);
2072
+ if (!match) {
2073
+ return [];
2074
+ }
2075
+ const [, kind, direction, token] = match;
2076
+ const value = bootstrapSpacingScale[token];
2077
+ const propertyRoot = kind === 'm' ? 'margin' : 'padding';
2078
+ const directionMap = {
2079
+ '': [propertyRoot],
2080
+ t: [`${propertyRoot}-top`],
2081
+ e: [`${propertyRoot}-right`],
2082
+ b: [`${propertyRoot}-bottom`],
2083
+ s: [`${propertyRoot}-left`],
2084
+ x: [`${propertyRoot}-left`, `${propertyRoot}-right`],
2085
+ y: [`${propertyRoot}-top`, `${propertyRoot}-bottom`],
2086
+ };
2087
+ return (directionMap[direction] ?? []).map((property) => ({ property, value }));
2088
+ }
2089
+ function bootstrapDisplayDeclarations(base) {
2090
+ const match = base.match(/^d-(none|inline|inline-block|block|grid|inline-grid|table|table-row|table-cell|flex|inline-flex)$/);
2091
+ return match ? [{ property: 'display', value: match[1] }] : [];
2092
+ }
2093
+ function bootstrapFlexDeclarations(base) {
2094
+ const declarations = [];
2095
+ const justifyMap = {
2096
+ start: 'flex-start',
2097
+ end: 'flex-end',
2098
+ center: 'center',
2099
+ between: 'space-between',
2100
+ around: 'space-around',
2101
+ evenly: 'space-evenly',
2102
+ };
2103
+ const alignMap = {
2104
+ start: 'flex-start',
2105
+ end: 'flex-end',
2106
+ center: 'center',
2107
+ baseline: 'baseline',
2108
+ stretch: 'stretch',
2109
+ };
2110
+ if (base === 'flex-row')
2111
+ declarations.push({ property: 'flex-direction', value: 'row' });
2112
+ if (base === 'flex-column')
2113
+ declarations.push({ property: 'flex-direction', value: 'column' });
2114
+ if (base === 'flex-wrap')
2115
+ declarations.push({ property: 'flex-wrap', value: 'wrap' });
2116
+ if (base === 'flex-nowrap')
2117
+ declarations.push({ property: 'flex-wrap', value: 'nowrap' });
2118
+ const justify = base.match(/^justify-content-(start|end|center|between|around|evenly)$/);
2119
+ if (justify)
2120
+ declarations.push({ property: 'justify-content', value: justifyMap[justify[1]] });
2121
+ const alignItems = base.match(/^align-items-(start|end|center|baseline|stretch)$/);
2122
+ if (alignItems)
2123
+ declarations.push({ property: 'align-items', value: alignMap[alignItems[1]] });
2124
+ const alignSelf = base.match(/^align-self-(start|end|center|baseline|stretch)$/);
2125
+ if (alignSelf)
2126
+ declarations.push({ property: 'align-self', value: alignMap[alignSelf[1]] });
2127
+ return declarations;
2128
+ }
2129
+ function bootstrapSizingDeclarations(base) {
2130
+ const percentMatch = base.match(/^(w|h|mw|mh)-(\d+)$/);
2131
+ if (percentMatch) {
2132
+ const [, prefix, amount] = percentMatch;
2133
+ const propertyMap = {
2134
+ w: 'width',
2135
+ h: 'height',
2136
+ mw: 'max-width',
2137
+ mh: 'max-height',
2138
+ };
2139
+ return [{ property: propertyMap[prefix], value: `${amount}%` }];
2140
+ }
2141
+ if (base === 'min-vh-100')
2142
+ return [{ property: 'min-height', value: '100vh' }];
2143
+ if (base === 'min-vw-100')
2144
+ return [{ property: 'min-width', value: '100vw' }];
2145
+ if (base === 'vh-100')
2146
+ return [{ property: 'height', value: '100vh' }];
2147
+ if (base === 'vw-100')
2148
+ return [{ property: 'width', value: '100vw' }];
2149
+ return [];
2150
+ }
2151
+ function bootstrapTypographyDeclarations(base) {
2152
+ const declarations = [];
2153
+ const fsMatch = base.match(/^fs-([1-6])$/);
2154
+ if (fsMatch)
2155
+ declarations.push({ property: 'font-size', value: bootstrapFontSizeScale[fsMatch[1]] });
2156
+ const fwMap = {
2157
+ lighter: 'lighter',
2158
+ light: '300',
2159
+ normal: '400',
2160
+ semibold: '600',
2161
+ bold: '700',
2162
+ bolder: 'bolder',
2163
+ };
2164
+ const fwMatch = base.match(/^fw-(lighter|light|normal|semibold|bold|bolder)$/);
2165
+ if (fwMatch)
2166
+ declarations.push({ property: 'font-weight', value: fwMap[fwMatch[1]] });
2167
+ const textAlign = base.match(/^text-(start|end|center)$/);
2168
+ if (textAlign) {
2169
+ declarations.push({ property: 'text-align', value: textAlign[1] === 'start' ? 'left' : textAlign[1] === 'end' ? 'right' : 'center' });
2170
+ }
2171
+ if (base === 'text-uppercase')
2172
+ declarations.push({ property: 'text-transform', value: 'uppercase' });
2173
+ if (base === 'text-lowercase')
2174
+ declarations.push({ property: 'text-transform', value: 'lowercase' });
2175
+ if (base === 'text-capitalize')
2176
+ declarations.push({ property: 'text-transform', value: 'capitalize' });
2177
+ if (base === 'lead') {
2178
+ declarations.push({ property: 'font-size', value: '1.25rem' }, { property: 'font-weight', value: '300' });
2179
+ }
2180
+ if (base === 'small')
2181
+ declarations.push({ property: 'font-size', value: '0.875em' });
2182
+ const lhMap = { '1': '1', sm: '1.25', base: '1.5', lg: '2' };
2183
+ const lhMatch = base.match(/^lh-(1|sm|base|lg)$/);
2184
+ if (lhMatch)
2185
+ declarations.push({ property: 'line-height', value: lhMap[lhMatch[1]] });
2186
+ return declarations;
2187
+ }
2188
+ function bootstrapColorDeclarations(base) {
2189
+ const declarations = [];
2190
+ const bgMatch = base.match(/^bg-(primary|secondary|success|danger|warning|info|light|dark|body|white|transparent)$/);
2191
+ if (bgMatch) {
2192
+ declarations.push({
2193
+ property: 'background-color',
2194
+ value: bgMatch[1] === 'transparent' ? 'transparent' : bootstrapThemeColors[bgMatch[1]],
2195
+ });
2196
+ }
2197
+ const textMatch = base.match(/^text-(primary|secondary|success|danger|warning|info|light|dark|body|muted|white|black)$/);
2198
+ if (textMatch)
2199
+ declarations.push({ property: 'color', value: bootstrapThemeColors[textMatch[1]] });
2200
+ return declarations;
2201
+ }
2202
+ function bootstrapBorderDeclarations(base) {
2203
+ const declarations = [];
2204
+ if (base === 'border')
2205
+ declarations.push({ property: 'border', value: 'var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)' });
2206
+ if (base === 'border-0')
2207
+ declarations.push({ property: 'border', value: '0' });
2208
+ if (/^border-[1-5]$/.test(base)) {
2209
+ declarations.push({ property: 'border-width', value: `${base.replace('border-', '')}px` });
2210
+ }
2211
+ const borderColor = base.match(/^border-(primary|secondary|success|danger|warning|info|light|dark|white)$/);
2212
+ if (borderColor)
2213
+ declarations.push({ property: 'border-color', value: bootstrapThemeColors[borderColor[1]] });
2214
+ if (base === 'rounded')
2215
+ declarations.push({ property: 'border-radius', value: 'var(--bs-border-radius)' });
2216
+ if (base === 'rounded-0')
2217
+ declarations.push({ property: 'border-radius', value: '0' });
2218
+ if (base === 'rounded-1')
2219
+ declarations.push({ property: 'border-radius', value: 'var(--bs-border-radius-sm)' });
2220
+ if (base === 'rounded-2')
2221
+ declarations.push({ property: 'border-radius', value: 'var(--bs-border-radius)' });
2222
+ if (base === 'rounded-3')
2223
+ declarations.push({ property: 'border-radius', value: 'var(--bs-border-radius-lg)' });
2224
+ if (base === 'rounded-4')
2225
+ declarations.push({ property: 'border-radius', value: 'var(--bs-border-radius-xl)' });
2226
+ if (base === 'rounded-5')
2227
+ declarations.push({ property: 'border-radius', value: 'var(--bs-border-radius-xxl)' });
2228
+ if (base === 'rounded-pill')
2229
+ declarations.push({ property: 'border-radius', value: 'var(--bs-border-radius-pill)' });
2230
+ if (base === 'rounded-circle')
2231
+ declarations.push({ property: 'border-radius', value: '50%' });
2232
+ if (base === 'shadow')
2233
+ declarations.push({ property: 'box-shadow', value: 'var(--bs-box-shadow)' });
2234
+ if (base === 'shadow-sm')
2235
+ declarations.push({ property: 'box-shadow', value: 'var(--bs-box-shadow-sm)' });
2236
+ if (base === 'shadow-lg')
2237
+ declarations.push({ property: 'box-shadow', value: 'var(--bs-box-shadow-lg)' });
2238
+ if (base === 'shadow-none')
2239
+ declarations.push({ property: 'box-shadow', value: 'none' });
2240
+ return declarations;
2241
+ }
2242
+ function bootstrapGridDeclarations(base) {
2243
+ if (base === 'col') {
2244
+ return [
2245
+ { property: 'flex', value: '1 0 0%' },
2246
+ { property: 'min-width', value: '0' },
2247
+ ];
2248
+ }
2249
+ const colMatch = base.match(/^col-(\d{1,2})$/);
2250
+ if (colMatch) {
2251
+ const columns = Math.min(12, Math.max(1, Number(colMatch[1])));
2252
+ const width = `${(columns / 12) * 100}%`;
2253
+ return [
2254
+ { property: 'flex', value: '0 0 auto' },
2255
+ { property: 'width', value: width },
2256
+ ];
2257
+ }
2258
+ const gapMatch = base.match(/^gap-([0-5])$/);
2259
+ if (gapMatch)
2260
+ return [{ property: 'gap', value: bootstrapSpacingScale[gapMatch[1]] }];
2261
+ return [];
2262
+ }
2263
+ function categorizeResolvedStyleDeclarations(declarations) {
2264
+ const categories = {};
2265
+ for (const declaration of declarations) {
2266
+ const category = resolvedStyleCategory(declaration.property);
2267
+ categories[category] = (0, utils_1.uniqueBy)([...(categories[category] ?? []), declaration], (item) => `${item.className}:${item.property}:${item.value}:${item.source}:${item.selector ?? ''}`).slice(0, 80);
2268
+ }
2269
+ return categories;
2270
+ }
2271
+ function flattenResolvedStyleCategories(categories) {
2272
+ return (0, utils_1.uniqueBy)(Object.values(categories).flat(), (declaration) => `${declaration.className}:${declaration.property}:${declaration.value}:${declaration.source}:${declaration.selector ?? ''}`);
2273
+ }
2274
+ function createStyleSystemEvidence(classes, declarations, styleContext) {
2275
+ const classSystems = new Map();
2276
+ for (const className of classes) {
2277
+ const systems = new Set();
2278
+ const base = (0, utils_1.classBase)(className).replace(/^!/, '');
2279
+ if (styleContext?.classRules.has(className) ||
2280
+ styleContext?.classRules.has(base) ||
2281
+ styleContext?.classRules.has(base.replace(/^!/, ''))) {
2282
+ systems.add('raw-css');
2283
+ }
2284
+ if (tailwindDeclarationsForClass(className, styleContext).length || isLikelyTailwindClass(className)) {
2285
+ systems.add('tailwind');
2286
+ }
2287
+ if (bootstrapDeclarationsForClass(className, styleContext).length || isBootstrapClass(className)) {
2288
+ systems.add('bootstrap');
2289
+ }
2290
+ if (!systems.size) {
2291
+ systems.add('unknown');
2292
+ }
2293
+ classSystems.set(className, systems);
2294
+ }
2295
+ const observed = (0, utils_1.unique)([
2296
+ ...Array.from(classSystems.values()).flatMap((systems) => Array.from(systems)),
2297
+ ...declarations.map((declaration) => declaration.styleSystem),
2298
+ ]).filter((system) => system !== 'unknown');
2299
+ const unresolvedClasses = classes.filter((className) => classSystems.get(className)?.has('unknown'));
2300
+ const classCounts = Object.fromEntries(['raw-css', 'tailwind', 'bootstrap', 'unknown'].map((system) => [
2301
+ system,
2302
+ Array.from(classSystems.values()).filter((systems) => systems.has(system)).length,
2303
+ ]));
2304
+ return omitEmptyRecord({
2305
+ primary: observed.length === 1 ? observed[0] : observed.length > 1 ? 'mixed' : 'unknown',
2306
+ observed,
2307
+ projectDetected: styleContext?.detectedStyleSystems ?? [],
2308
+ classCounts,
2309
+ unresolvedClasses: unresolvedClasses.slice(0, 80),
2310
+ declarationCounts: Object.fromEntries(['raw-css', 'tailwind', 'bootstrap'].map((system) => [
2311
+ system,
2312
+ declarations.filter((declaration) => declaration.styleSystem === system).length,
2313
+ ])),
2314
+ });
2315
+ }
2316
+ function isLikelyTailwindClass(className) {
2317
+ const base = (0, utils_1.classBase)(className).replace(/^!/, '');
2318
+ return Boolean(className.includes(':') ||
2319
+ /\[[^\]]+\]/.test(base) ||
2320
+ /^(?: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));
2321
+ }
2322
+ function resolvedStyleCategory(property) {
2323
+ if (['color', 'background', 'background-color', 'fill', 'stroke'].includes(property)) {
2324
+ return 'color';
2325
+ }
2326
+ if ([
2327
+ 'font-size',
2328
+ 'font-weight',
2329
+ 'line-height',
2330
+ 'letter-spacing',
2331
+ 'text-transform',
2332
+ 'text-align',
2333
+ 'font-family',
2334
+ ].includes(property)) {
2335
+ return 'typography';
2336
+ }
2337
+ if (/^(?:border|outline|ring)|border-radius/.test(property)) {
2338
+ return 'border';
2339
+ }
2340
+ if (['box-shadow', 'opacity', 'filter', 'backdrop-filter'].includes(property)) {
2341
+ return 'effects';
2342
+ }
2343
+ if (['transition', 'transition-property', 'transition-duration', 'animation', 'transform'].includes(property)) {
2344
+ return 'motion';
2345
+ }
2346
+ if (property === 'display') {
2347
+ return 'display';
2348
+ }
2349
+ if (/^(?:align|justify|place)/.test(property)) {
2350
+ return 'alignment';
2351
+ }
2352
+ if (/^grid/.test(property)) {
2353
+ return 'grid';
2354
+ }
2355
+ if (/^flex/.test(property)) {
2356
+ return 'flex';
2357
+ }
2358
+ if (/^(?:padding|margin|gap|row-gap|column-gap)/.test(property)) {
2359
+ return 'spacing';
2360
+ }
2361
+ if (/^(?:width|height|min-|max-)/.test(property)) {
2362
+ return /^(?:min-|max-)/.test(property) ? 'minMax' : 'sizing';
2363
+ }
2364
+ if (['position', 'inset', 'top', 'right', 'bottom', 'left', 'z-index', 'object-fit'].includes(property)) {
2365
+ return 'position';
2366
+ }
2367
+ if (['overflow', 'overflow-x', 'overflow-y'].includes(property)) {
2368
+ return 'overflow';
2369
+ }
2370
+ if (['white-space', 'overflow-wrap', 'text-overflow', '-webkit-line-clamp'].includes(property)) {
2371
+ return 'textFlow';
2372
+ }
2373
+ return 'layout';
2374
+ }
2375
+ function mergeResolvedStyleCategories(current, next) {
2376
+ const merged = { ...current };
2377
+ for (const [category, declarations] of Object.entries(next)) {
2378
+ const existing = Array.isArray(merged[category])
2379
+ ? merged[category]
2380
+ : [];
2381
+ merged[category] = (0, utils_1.uniqueBy)([...existing, ...declarations], (declaration) => `${declaration.className}:${declaration.property}:${declaration.value}:${declaration.source}:${declaration.selector ?? ''}`).slice(0, 80);
2382
+ }
2383
+ return omitEmptyRecord(merged);
2384
+ }
2385
+ function classBucketRecord(value) {
2386
+ const buckets = {};
2387
+ for (const [key, item] of Object.entries(value)) {
2388
+ if (Array.isArray(item) && item.every((value) => typeof value === 'string')) {
2389
+ buckets[key] = item;
2390
+ }
2391
+ }
2392
+ return buckets;
2393
+ }
2394
+ function resolvedStyleRecord(value) {
2395
+ return isRecordValue(value) ? value : {};
2396
+ }
2397
+ function createUiContract(input) {
2398
+ const defaultClasses = (0, utils_1.unique)(input.defaultClasses?.length ? input.defaultClasses : input.classes);
2399
+ const conditionalClasses = (0, utils_1.unique)(input.conditionalClasses ?? []);
2400
+ const decorativeAccentClasses = (0, utils_1.unique)(input.decorativeAccentClasses ?? []);
2401
+ const allClasses = (0, utils_1.unique)([...defaultClasses, ...conditionalClasses, ...decorativeAccentClasses]);
2402
+ const grouped = input.classGroups ?? (0, extractors_1.groupTailwindClasses)(allClasses);
2403
+ const resolvedStyles = categorizeResolvedStyleDeclarations(resolveUiClassDeclarations(allClasses, input.styleContext));
2404
+ const styleSystemEvidence = createStyleSystemEvidence(allClasses, flattenResolvedStyleCategories(resolvedStyles), input.styleContext);
2405
+ const responsiveVariants = createUiResponsiveVariants(allClasses, input.styleContext);
2406
+ const interactionVariants = createUiInteractionVariants(allClasses, input.styleContext);
2407
+ return {
2408
+ identity: omitEmptyRecord({
2409
+ kind: input.kind,
2410
+ tag: input.tag,
2411
+ layoutRole: input.layoutRole,
2412
+ label: input.label,
2413
+ line: input.line,
2414
+ variants: input.variants,
2415
+ props: input.props,
2416
+ styleHelper: input.styleHelper,
2417
+ styleHelperVariants: input.styleHelperVariants,
2418
+ }),
2419
+ semantics: omitEmptyRecord({
2420
+ kind: input.kind,
2421
+ role: input.semanticRole ?? input.layoutRole,
2422
+ semanticRole: input.semanticRole,
2423
+ semanticProfile: input.semanticProfile,
2424
+ roleSignature: input.roleSignature,
2425
+ }),
2426
+ visual: omitEmptyRecord({
2427
+ color: grouped.color ?? [],
2428
+ typography: grouped.typography ?? [],
2429
+ border: grouped.border ?? [],
2430
+ effects: grouped.effects ?? [],
2431
+ motion: grouped.motion ?? [],
2432
+ decorativeAccentClasses,
2433
+ resolvedStyles: omitEmptyRecord({
2434
+ color: resolvedStyles.color,
2435
+ typography: resolvedStyles.typography,
2436
+ border: resolvedStyles.border,
2437
+ effects: resolvedStyles.effects,
2438
+ motion: resolvedStyles.motion,
2439
+ }),
2440
+ }),
2441
+ layout: omitEmptyRecord({
2442
+ display: filterUiContractClasses(allClasses, (className) => /^(?:flex|inline-flex|grid|inline-grid|block|inline|inline-block|hidden|contents|table)$/.test((0, utils_1.classBase)(className))),
2443
+ alignment: filterUiContractClasses(allClasses, (className) => /^(?:items-|justify-|content-|self-|place-)/.test((0, utils_1.classBase)(className))),
2444
+ grid: filterUiContractClasses(allClasses, (className) => /^(?:grid|grid-cols-|grid-rows-|auto-rows-|auto-cols-|col-|row-)/.test((0, utils_1.classBase)(className))),
2445
+ flex: filterUiContractClasses(allClasses, (className) => /^(?:flex|inline-flex|flex-|basis-|grow|shrink)/.test((0, utils_1.classBase)(className))),
2446
+ spacing: grouped.spacing ?? [],
2447
+ sizing: grouped.sizing ?? [],
2448
+ position: grouped.position ?? [],
2449
+ layout: grouped.layout ?? [],
2450
+ other: grouped.other ?? [],
2451
+ resolvedStyles: omitEmptyRecord({
2452
+ display: resolvedStyles.display,
2453
+ alignment: resolvedStyles.alignment,
2454
+ grid: resolvedStyles.grid,
2455
+ flex: resolvedStyles.flex,
2456
+ spacing: resolvedStyles.spacing,
2457
+ sizing: resolvedStyles.sizing,
2458
+ position: resolvedStyles.position,
2459
+ layout: resolvedStyles.layout,
2460
+ }),
2461
+ }),
2462
+ responsiveVariants,
2463
+ interactionVariants,
2464
+ safetyRules: omitEmptyRecord({
2465
+ layoutSafety: input.layoutSafety,
2466
+ scaleProfile: input.scaleProfile,
2467
+ roleConstraints: input.roleSignature
2468
+ ? omitEmptyRecord({
2469
+ preserveRole: input.roleSignature.role,
2470
+ preserveRoleGroup: input.roleSignature.roleGroup,
2471
+ preserveScale: input.roleSignature.scale,
2472
+ preserveDensity: input.roleSignature.density,
2473
+ preserveSurface: input.roleSignature.surface,
2474
+ preserveLayout: input.roleSignature.layout,
2475
+ exactClassFacts: input.roleSignature.exactClassFacts,
2476
+ flags: input.roleSignature.flags,
2477
+ })
2478
+ : undefined,
2479
+ resolvedStyles: omitEmptyRecord({
2480
+ textFlow: resolvedStyles.textFlow,
2481
+ overflow: resolvedStyles.overflow,
2482
+ minMax: resolvedStyles.minMax,
2483
+ }),
2484
+ }),
2485
+ sourceEvidence: omitEmptyRecord({
2486
+ extractor: 'ui.contract',
2487
+ path: input.path,
2488
+ line: input.line,
2489
+ sourceHash: input.sourceHash,
2490
+ confidence: input.confidence,
2491
+ evidence: input.evidence,
2492
+ classSource: input.classSource,
2493
+ styleSystems: styleSystemEvidence,
2494
+ }),
2495
+ };
2496
+ }
2497
+ function createUiContractForElement(element, file, confidence, styleContext) {
2498
+ return createUiContract({
2499
+ kind: element.kind,
2500
+ tag: element.originalTag,
2501
+ layoutRole: element.layoutRole,
2502
+ label: element.label,
2503
+ line: element.line,
2504
+ path: file?.relPath,
2505
+ sourceHash: file?.sourceHash,
2506
+ confidence,
2507
+ evidence: element.evidence,
2508
+ classSource: element.classSource,
2509
+ classes: element.classes,
2510
+ defaultClasses: element.defaultClasses,
2511
+ conditionalClasses: element.conditionalClasses,
2512
+ decorativeAccentClasses: element.decorativeAccentClasses,
2513
+ classGroups: element.classGroups,
2514
+ layoutSafety: element.layoutSafety,
2515
+ responsiveProfile: element.responsiveProfile,
2516
+ scaleProfile: element.scaleProfile,
2517
+ semanticProfile: element.semanticProfile,
2518
+ roleSignature: element.roleSignature,
2519
+ variants: element.variants,
2520
+ props: element.props,
2521
+ styleHelper: element.styleHelper,
2522
+ styleHelperVariants: element.styleHelperVariants,
2523
+ styleContext,
2524
+ });
2525
+ }
2526
+ function createUiContractForChild(child, styleContext) {
2527
+ return createUiContract({
2528
+ kind: child.kind,
2529
+ tag: child.originalTag,
2530
+ layoutRole: child.layoutRole,
2531
+ semanticRole: child.semanticRole,
2532
+ label: child.label,
2533
+ classes: child.classes,
2534
+ defaultClasses: child.defaultClasses,
2535
+ conditionalClasses: child.conditionalClasses,
2536
+ layoutSafety: child.layoutSafety,
2537
+ responsiveProfile: child.responsiveProfile,
2538
+ scaleProfile: child.scaleProfile,
2539
+ semanticProfile: child.semanticProfile,
2540
+ roleSignature: child.roleSignature,
2541
+ variants: child.variants,
2542
+ props: child.props,
2543
+ styleHelper: child.styleHelper,
2544
+ styleHelperVariants: child.styleHelperVariants,
2545
+ styleContext,
2546
+ });
2547
+ }
2548
+ function createUiPatternContract(input) {
2549
+ const contract = createUiContract(input);
2550
+ return {
2551
+ ...contract,
2552
+ identity: omitEmptyRecord({
2553
+ ...contract.identity,
2554
+ name: input.name,
2555
+ count: input.count,
2556
+ }),
2557
+ };
2558
+ }
2559
+ function createUiRelationships(input) {
2560
+ return {
2561
+ ...(input.parentContext ? { parentContext: input.parentContext } : {}),
2562
+ ...(input.childStructureContract
2563
+ ? { childStructureContract: input.childStructureContract }
2564
+ : {}),
2565
+ ...(input.compoundStructure ? { compoundStructure: input.compoundStructure } : {}),
2566
+ graphKeys: input.graphKeys ?? [],
2567
+ };
2568
+ }
2569
+ function createUiResponsiveVariants(classes, styleContext) {
2570
+ const variants = {};
2571
+ for (const className of classes) {
2572
+ const responsivePrefixes = responsivePrefixesForClass(className);
2573
+ for (const responsivePrefix of responsivePrefixes) {
2574
+ variants[responsivePrefix] = mergeUiContractClassBuckets(classBucketRecord(variants[responsivePrefix] ?? {}), uiContractClassBucket(className));
2575
+ const resolved = categorizeResolvedStyleDeclarations(resolveUiClassDeclarations([className], styleContext));
2576
+ variants[responsivePrefix].resolvedStyles = mergeResolvedStyleCategories(resolvedStyleRecord(variants[responsivePrefix].resolvedStyles), resolved);
2577
+ }
2578
+ }
2579
+ return variants;
2580
+ }
2581
+ function createUiInteractionVariants(classes, styleContext) {
2582
+ const variants = {};
2583
+ for (const className of classes) {
2584
+ const prefixes = className.split(':').slice(0, -1);
2585
+ const statePrefixes = prefixes.filter(isInteractionPrefix);
2586
+ for (const prefix of statePrefixes) {
2587
+ variants[prefix] = mergeUiContractClassBuckets(classBucketRecord(variants[prefix] ?? {}), uiContractClassBucket(className));
2588
+ const resolved = categorizeResolvedStyleDeclarations(resolveUiClassDeclarations([className], styleContext));
2589
+ variants[prefix].resolvedStyles = mergeResolvedStyleCategories(resolvedStyleRecord(variants[prefix].resolvedStyles), resolved);
2590
+ }
2591
+ }
2592
+ return variants;
2593
+ }
2594
+ function uiContractClassBucket(className) {
2595
+ const base = (0, utils_1.classBase)(className);
2596
+ if ((0, extractors_1.isLayoutClass)(className)) {
2597
+ return { layout: [className] };
2598
+ }
2599
+ if ((0, extractors_1.isSpacingClass)(className)) {
2600
+ return { spacing: [className] };
2601
+ }
2602
+ if ((0, extractors_1.isSizingClass)(className)) {
2603
+ return { sizing: [className] };
2604
+ }
2605
+ if ((0, extractors_1.isTypographyClass)(className)) {
2606
+ return { typography: [className] };
2607
+ }
2608
+ if (/^(?:bg-|from-|via-|to-|text-|decoration-|accent-|caret-|fill-|stroke-)/.test(base)) {
2609
+ return { color: [className] };
2610
+ }
2611
+ if (/^(?:rounded|border|divide-|outline|ring)/.test(base)) {
2612
+ return { border: [className] };
2613
+ }
2614
+ if (/^(?:shadow|opacity|blur|backdrop|mix-|bg-blend|drop-shadow|filter)/.test(base)) {
2615
+ return { effects: [className] };
2616
+ }
2617
+ if (/^(?:absolute|relative|fixed|sticky|z-|overflow-|object-)/.test(base)) {
2618
+ return { position: [className] };
2619
+ }
2620
+ if (/^(?:transition|duration|ease|delay|animate-|transform|scale-|rotate|translate-)/.test(base)) {
2621
+ return { motion: [className] };
2622
+ }
2623
+ return { other: [className] };
2624
+ }
2625
+ function mergeUiContractClassBuckets(current, next) {
2626
+ const merged = { ...current };
2627
+ for (const [bucket, classes] of Object.entries(next)) {
2628
+ merged[bucket] = (0, utils_1.unique)([...(merged[bucket] ?? []), ...classes]).slice(0, 120);
2629
+ }
2630
+ return merged;
2631
+ }
2632
+ function filterUiContractClasses(classes, predicate) {
2633
+ return (0, utils_1.unique)(classes.filter(predicate)).slice(0, 120);
2634
+ }
2635
+ function isResponsivePrefix(prefix) {
2636
+ return /^(?:sm|md|lg|xl|2xl|min-|max-)/.test(prefix);
2637
+ }
2638
+ function responsivePrefixesForClass(className) {
2639
+ const prefixes = className.split(':').slice(0, -1).filter(isResponsivePrefix);
2640
+ const bootstrapPrefixes = bootstrapResponsivePrefixes(className);
2641
+ return (0, utils_1.unique)([...prefixes, ...bootstrapPrefixes]);
2642
+ }
2643
+ function isInteractionPrefix(prefix) {
2644
+ return /^(?:hover|focus|focus-visible|focus-within|active|disabled|visited|checked|selected|open|dark|group|group-hover|peer|peer-hover|aria|data)/.test(prefix);
2645
+ }
2646
+ function omitEmptyRecord(value) {
2647
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => {
2648
+ if (item === undefined || item === null) {
2649
+ return false;
2650
+ }
2651
+ if (Array.isArray(item)) {
2652
+ return item.length > 0;
2653
+ }
2654
+ if (typeof item === 'object') {
2655
+ return Object.keys(item).length > 0;
2656
+ }
2657
+ return true;
2658
+ }));
2659
+ }
2660
+ function mergeLayoutSafetyProfiles(items) {
2661
+ const profiles = items.map((item) => item.layoutSafety).filter(isLayoutSafetyProfile);
2662
+ if (!profiles.length) {
2663
+ return undefined;
2664
+ }
2665
+ const merged = {
2666
+ wrapClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.wrapClasses)).slice(0, 80),
2667
+ overflowClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.overflowClasses)).slice(0, 80),
2668
+ minSizeClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.minSizeClasses)).slice(0, 80),
2669
+ maxSizeClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.maxSizeClasses)).slice(0, 80),
2670
+ fitClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.fitClasses)).slice(0, 80),
2671
+ flexBehaviorClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.flexBehaviorClasses)).slice(0, 80),
2672
+ layoutSafetyClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.layoutSafetyClasses)).slice(0, 120),
2673
+ };
2674
+ return merged.layoutSafetyClasses.length ? merged : undefined;
2675
+ }
2676
+ function mergeResponsiveProfiles(items) {
2677
+ const profiles = items.map((item) => item.responsiveProfile).filter(isResponsiveProfile);
2678
+ if (!profiles.length) {
2679
+ return undefined;
2680
+ }
2681
+ const breakpoints = {};
2682
+ for (const profile of profiles) {
2683
+ for (const [breakpoint, classes] of Object.entries(profile.breakpoints)) {
2684
+ breakpoints[breakpoint] = (0, utils_1.unique)([...(breakpoints[breakpoint] ?? []), ...classes]).slice(0, 80);
2685
+ }
2686
+ }
2687
+ const merged = {
2688
+ breakpoints,
2689
+ layoutClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.layoutClasses)).slice(0, 120),
2690
+ typographyClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.typographyClasses)).slice(0, 120),
2691
+ spacingClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.spacingClasses)).slice(0, 120),
2692
+ sizingClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.sizingClasses)).slice(0, 120),
2693
+ visibilityClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.visibilityClasses)).slice(0, 120),
2694
+ stateClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.stateClasses)).slice(0, 120),
2695
+ };
2696
+ return Object.keys(merged.breakpoints).length ? merged : undefined;
2697
+ }
2698
+ function mergeScaleProfiles(items) {
2699
+ const profiles = items.map((item) => item.scaleProfile).filter(isScaleProfile);
2700
+ if (!profiles.length) {
2701
+ return undefined;
2702
+ }
2703
+ const merged = {
2704
+ typographyClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.typographyClasses)).slice(0, 120),
2705
+ spacingClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.spacingClasses)).slice(0, 120),
2706
+ sizingClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.sizingClasses)).slice(0, 120),
2707
+ gapClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.gapClasses)).slice(0, 80),
2708
+ maxWidthClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.maxWidthClasses)).slice(0, 80),
2709
+ lineHeightClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.lineHeightClasses)).slice(0, 80),
2710
+ };
2711
+ return Object.values(merged).some((classes) => classes.length) ? merged : undefined;
2712
+ }
2713
+ function mergeSemanticProfiles(items) {
2714
+ const profiles = items.map((item) => item.semanticProfile).filter(isSemanticProfile);
2715
+ if (!profiles.length) {
2716
+ return undefined;
659
2717
  }
2718
+ return {
2719
+ roles: (0, utils_1.unique)(profiles.flatMap((profile) => profile.roles)).slice(0, 40),
2720
+ isCompound: profiles.some((profile) => profile.isCompound),
2721
+ hasIcon: profiles.some((profile) => profile.hasIcon),
2722
+ hasDivider: profiles.some((profile) => profile.hasDivider),
2723
+ hasImage: profiles.some((profile) => profile.hasImage),
2724
+ hasMultipleTextStyles: profiles.some((profile) => profile.hasMultipleTextStyles),
2725
+ textStyleCount: Math.max(...profiles.map((profile) => profile.textStyleCount)),
2726
+ childCount: Math.max(...profiles.map((profile) => profile.childCount)),
2727
+ };
660
2728
  }
661
- function backendCallKey(call) {
662
- return (0, utils_1.toRegistryKey)('service', (0, backend_1.backendReceiverToClassName)(call.receiver));
2729
+ function summarizePatternLayoutSafety(elements) {
2730
+ return mergeLayoutSafetyProfiles([
2731
+ ...elements,
2732
+ ...elements.flatMap((element) => element.childSummary ?? []),
2733
+ ]);
663
2734
  }
664
- function uniqueSideEffectsForRecipe(sideEffects) {
665
- return (0, utils_1.uniqueBy)(sideEffects, (sideEffect) => `${sideEffect.kind}.${sideEffect.target ?? ''}.${sideEffect.operation ?? ''}.${sideEffect.line}`);
2735
+ function summarizePatternResponsiveProfile(elements) {
2736
+ return mergeResponsiveProfiles([
2737
+ ...elements,
2738
+ ...elements.flatMap((element) => element.childSummary ?? []),
2739
+ ]);
666
2740
  }
667
- function sourceInfo(extractor, confidence, evidence, file) {
2741
+ function summarizePatternScaleProfile(elements) {
2742
+ return mergeScaleProfiles([
2743
+ ...elements,
2744
+ ...elements.flatMap((element) => element.childSummary ?? []),
2745
+ ]);
2746
+ }
2747
+ function summarizePatternSemanticProfile(elements) {
2748
+ return mergeSemanticProfiles(elements);
2749
+ }
2750
+ function summarizePatternRoleSignatures(elements) {
2751
+ return (0, utils_1.uniqueBy)(elements
2752
+ .map((element) => element.roleSignature)
2753
+ .filter((value) => Boolean(value)), (signature) => roleSignatureKey(signature)).slice(0, 12);
2754
+ }
2755
+ function summarizeParentContexts(elements) {
2756
+ const elementsWithParent = elements.filter((element) => element.parentClasses?.length);
2757
+ const grouped = (0, utils_1.groupBy)(elementsWithParent, (element) => [
2758
+ element.parentKind ?? 'unknown-parent',
2759
+ element.parentTag ?? 'unknown-tag',
2760
+ JSON.stringify(element.parentClassGroups ?? (0, extractors_1.groupTailwindClasses)(element.parentClasses ?? [])),
2761
+ ].join(':'));
2762
+ return Array.from(grouped.values())
2763
+ .map((group) => {
2764
+ const first = group[0];
2765
+ const parentClasses = (0, utils_1.unique)(group.flatMap((element) => element.parentClasses ?? [])).slice(0, 80);
2766
+ return {
2767
+ parentKind: first?.parentKind,
2768
+ parentTag: first?.parentTag,
2769
+ count: group.length,
2770
+ parentClasses,
2771
+ parentClassGroups: (0, extractors_1.mergeClassGroups)(group
2772
+ .map((element) => element.parentClassGroups)
2773
+ .filter((value) => Boolean(value))),
2774
+ childKinds: (0, utils_1.unique)(group.map((element) => element.kind)).slice(0, 24),
2775
+ layoutRoles: (0, utils_1.unique)(group
2776
+ .map((element) => element.layoutRole)
2777
+ .filter((value) => Boolean(value))).slice(0, 24),
2778
+ };
2779
+ })
2780
+ .sort((a, b) => Number(b.count) - Number(a.count))
2781
+ .slice(0, 12);
2782
+ }
2783
+ function summarizeElementChildContract(children, styleContext) {
2784
+ if (!children.length) {
2785
+ return undefined;
2786
+ }
668
2787
  return {
669
- extractor,
670
- confidence,
671
- evidence,
672
- path: file.relPath,
673
- hash: file.sourceHash,
2788
+ directChildCount: children.length,
2789
+ childOrder: children.slice(0, 12).map((child) => ({
2790
+ kind: child.kind,
2791
+ tag: child.originalTag,
2792
+ layoutRole: child.layoutRole,
2793
+ semanticRole: child.semanticRole,
2794
+ roleSignature: child.roleSignature,
2795
+ labelHint: child.label ? labelSignature(child.label) : undefined,
2796
+ label: child.label,
2797
+ uiContract: createUiContractForChild(child, styleContext),
2798
+ defaultClasses: (child.defaultClasses?.length ? child.defaultClasses : child.classes).slice(0, 48),
2799
+ layoutSafety: child.layoutSafety,
2800
+ scaleProfile: child.scaleProfile,
2801
+ classGroups: (0, extractors_1.groupTailwindClasses)(child.defaultClasses?.length ? child.defaultClasses : child.classes),
2802
+ })),
2803
+ childKinds: (0, utils_1.unique)(children.map((child) => child.kind)).slice(0, 24),
2804
+ semanticRoles: (0, utils_1.unique)(children
2805
+ .map((child) => child.semanticRole)
2806
+ .filter((value) => Boolean(value))).slice(0, 24),
2807
+ layoutRoles: (0, utils_1.unique)(children.map((child) => child.layoutRole).filter((value) => Boolean(value))).slice(0, 24),
674
2808
  };
675
2809
  }
676
- function toGraphNode(key, kind, name, relPath, file, extractor, facets) {
2810
+ function summarizePatternChildStructure(elements, styleContext) {
2811
+ const childSummaries = elements
2812
+ .map((element) => element.childSummary ?? [])
2813
+ .filter((children) => children.length);
2814
+ if (!childSummaries.length) {
2815
+ return undefined;
2816
+ }
2817
+ const childCounts = childSummaries.map((children) => children.length);
2818
+ const childOrderExamples = (0, utils_1.uniqueBy)(childSummaries
2819
+ .map((children) => summarizeElementChildContract(children, styleContext))
2820
+ .filter((summary) => Boolean(summary)), (summary) => JSON.stringify(summary)).slice(0, 8);
677
2821
  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),
2822
+ preserveDirectChildRelationships: true,
2823
+ directChildCountRange: {
2824
+ min: Math.min(...childCounts),
2825
+ max: Math.max(...childCounts),
2826
+ },
2827
+ childKinds: (0, utils_1.unique)(childSummaries.flatMap((children) => children.map((child) => child.kind))).slice(0, 32),
2828
+ childOrderExamples,
686
2829
  };
687
2830
  }
688
- function toGraphEdge(from, to, type, extractor, file, confidence = 'high') {
2831
+ function summarizeCompoundStructure(elements, styleContext) {
2832
+ const compoundElements = elements.filter((element) => element.semanticProfile?.isCompound);
2833
+ if (!compoundElements.length) {
2834
+ return undefined;
2835
+ }
2836
+ const childCounts = compoundElements.map((element) => element.childSummary?.length ?? 0);
2837
+ const childOrderExamples = (0, utils_1.uniqueBy)(compoundElements
2838
+ .map((element) => summarizeElementChildContract(element.childSummary ?? [], styleContext))
2839
+ .filter((summary) => Boolean(summary)), (summary) => JSON.stringify(summary)).slice(0, 8);
2840
+ const semanticProfile = summarizePatternSemanticProfile(compoundElements);
689
2841
  return {
690
- from,
691
- to,
692
- type,
693
- confidence,
694
- source: sourceInfo(extractor, confidence, [`${from} ${type} ${to}`], file),
2842
+ preserveCompoundChildren: true,
2843
+ preserveIconAndDividerRoles: Boolean(semanticProfile?.hasIcon || semanticProfile?.hasDivider),
2844
+ preserveMultipleTextSegments: Boolean(semanticProfile?.hasMultipleTextStyles),
2845
+ directChildCountRange: {
2846
+ min: Math.min(...childCounts),
2847
+ max: Math.max(...childCounts),
2848
+ },
2849
+ semanticRoles: semanticProfile?.roles ?? [],
2850
+ childOrderExamples,
695
2851
  };
696
2852
  }
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
- }
2853
+ function createUiGenerationConstraints(kind, elements, decorativeAccentClasses) {
2854
+ const layoutSafety = summarizePatternLayoutSafety(elements);
2855
+ const responsiveProfile = summarizePatternResponsiveProfile(elements);
2856
+ const scaleProfile = summarizePatternScaleProfile(elements);
2857
+ const roleSignatures = summarizePatternRoleSignatures(elements);
2858
+ const parentContexts = summarizeParentContexts(elements);
2859
+ const childStructure = summarizePatternChildStructure(elements);
2860
+ const compoundStructure = summarizeCompoundStructure(elements);
2861
+ return {
2862
+ preserveRootAndChildrenTogether: true,
2863
+ preserveParentLayoutContext: Boolean(parentContexts.length),
2864
+ preserveDirectChildRelationships: Boolean(childStructure),
2865
+ preserveCompoundChildRoles: Boolean(compoundStructure),
2866
+ avoidUnobservedChildren: ['card', 'form', 'nav', 'section', 'table', 'dialog'].includes(kind) ||
2867
+ Boolean(compoundStructure),
2868
+ preserveResponsiveBreakpoints: Boolean(responsiveProfile),
2869
+ preserveLayoutSafety: Boolean(layoutSafety),
2870
+ preserveScaleBounds: Boolean(scaleProfile),
2871
+ preserveRoleSignatures: Boolean(roleSignatures.length),
2872
+ avoidDefaultDecorativeAccents: Boolean(decorativeAccentClasses.length),
2873
+ };
2874
+ }
2875
+ function createUiGenerationGuidance(kind, elements) {
2876
+ const guidance = [];
2877
+ const elementClasses = new Set(elements.flatMap((element) => [
2878
+ ...element.classes,
2879
+ ...(element.defaultClasses ?? []),
2880
+ ...(element.conditionalClasses ?? []),
2881
+ ...(element.parentClasses ?? []),
2882
+ ]));
2883
+ if (kind === 'card') {
2884
+ 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
2885
  }
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
- }
2886
+ if (kind === 'button') {
2887
+ 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.');
2888
+ }
2889
+ if (kind === 'badge' && summarizeCompoundStructure(elements)) {
2890
+ 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.');
2891
+ }
2892
+ if (kind === 'heading') {
2893
+ guidance.push('Preserve observed heading scale, max-width, line-height, and responsive typography classes. Do not upscale headings beyond the captured scaleProfile.');
2894
+ }
2895
+ if (['section', 'layout', 'form', 'nav'].includes(kind)) {
2896
+ guidance.push('Preserve parent grid/flex context, responsive breakpoints, and child order as layout contracts instead of recreating the section from isolated tokens.');
2897
+ }
2898
+ if (elements.some((element) => element.conditionalClasses?.length)) {
2899
+ guidance.push('conditionalClasses describe state or variant changes; apply them only when the matching condition, selected state, or variant is present.');
2900
+ }
2901
+ if (elementClasses.has('section-frame') || elementClasses.has('sm:section-frame')) {
2902
+ 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.');
2903
+ }
2904
+ if (elementClasses.has('section-frame') && elementClasses.has('context-surface')) {
2905
+ guidance.push('When section-frame and context-surface appear together, keep the pairing so direct children remain above the parent grid overlay.');
2906
+ }
2907
+ if (elements.some((element) => (element.childSummary ?? []).some((child) => (child.defaultClasses ?? child.classes).some((className) => className === 'section-frame')))) {
2908
+ 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.');
2909
+ }
2910
+ if (summarizePatternLayoutSafety(elements)) {
2911
+ 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.');
2912
+ }
2913
+ if (summarizeParentContexts(elements).length) {
2914
+ guidance.push('parentContexts records the grid/flex surface that owns this pattern. Preserve that parent relationship when generating repeated cards or nested panels.');
2915
+ }
2916
+ if (summarizePatternResponsiveProfile(elements)) {
2917
+ guidance.push('responsiveProfile is part of the contract. Keep mobile, tablet, and desktop variants attached to the same elements as the source.');
2918
+ }
2919
+ if (summarizePatternRoleSignatures(elements).length) {
2920
+ 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.');
2921
+ }
2922
+ return guidance;
2923
+ }
2924
+ function summarizeActionUsages(elements) {
2925
+ return elements.slice(0, 80).flatMap((element) => {
2926
+ const variantNames = actionVariantNamesForElement(element);
2927
+ return variantNames.map((variantName) => ({
2928
+ label: element.label,
2929
+ tag: element.originalTag,
2930
+ line: element.line,
2931
+ variant: variantName,
2932
+ size: element.variants?.size,
2933
+ type: element.variants?.type ?? element.type,
2934
+ styleHelper: element.styleHelper,
2935
+ parentKind: element.parentKind,
2936
+ parentLayoutRole: element.parentKind ? element.layoutRole : undefined,
2937
+ ...inferActionVariantRole(variantName, [
2938
+ ...(element.defaultClasses ?? []),
2939
+ ...(element.classes ?? []),
2940
+ ]),
2941
+ }));
2942
+ });
2943
+ }
2944
+ function actionVariantNamesForElement(element) {
2945
+ const variantValue = element.variants?.variant ?? element.styleHelperVariants?.variant;
2946
+ if (!variantValue && /button/i.test(element.styleHelper ?? '')) {
2947
+ return ['default'];
718
2948
  }
2949
+ if (!variantValue) {
2950
+ return ['custom'];
2951
+ }
2952
+ const quotedValues = Array.from(variantValue.matchAll(/["']([^"']+)["']/g)).map((match) => match[1]);
2953
+ if (quotedValues.length) {
2954
+ return (0, utils_1.unique)(quotedValues).slice(0, 6);
2955
+ }
2956
+ return (0, utils_1.unique)(variantValue
2957
+ .split('|')
2958
+ .map((value) => value.trim())
2959
+ .filter(Boolean)).slice(0, 6);
719
2960
  }
720
- function addUiPatternRules(entries, usedKeys, nodes, edges, files) {
2961
+ function addUiPatternRules(entries, usedKeys, nodes, edges, files, styleContext) {
721
2962
  for (const file of files) {
2963
+ if ((0, extractors_1.isTestFile)(file.relPath)) {
2964
+ continue;
2965
+ }
722
2966
  const elementsByKind = (0, utils_1.groupBy)(file.uiElements, (element) => element.kind);
723
2967
  for (const [kind, elements] of elementsByKind) {
724
2968
  if (!elements.length || kind === 'unknown' || kind === 'link') {
@@ -734,6 +2978,62 @@ function addUiPatternRules(entries, usedKeys, nodes, edges, files) {
734
2978
  const decorativeAccentClasses = (0, extractors_1.filterClasses)(classes, extractors_1.isDecorativeAccentClass).slice(0, 40);
735
2979
  const classGroups = (0, extractors_1.mergeClassGroups)(elements.map((element) => element.classGroups ?? {}));
736
2980
  const variants = (0, extractors_1.mergeVariantProps)(elements.map((element) => element.variants ?? {}));
2981
+ const elementPatterns = summarizeUiElementPatterns(elements, styleContext);
2982
+ const layoutSafety = summarizePatternLayoutSafety(elements);
2983
+ const responsiveProfile = summarizePatternResponsiveProfile(elements);
2984
+ const scaleProfile = summarizePatternScaleProfile(elements);
2985
+ const semanticProfile = summarizePatternSemanticProfile(elements);
2986
+ const roleSignatures = summarizePatternRoleSignatures(elements);
2987
+ const parentContexts = summarizeParentContexts(elements);
2988
+ const childStructureContract = summarizePatternChildStructure(elements, styleContext);
2989
+ const compoundStructure = summarizeCompoundStructure(elements, styleContext);
2990
+ const generationConstraints = createUiGenerationConstraints(kind, elements, decorativeAccentClasses);
2991
+ const structuralRootElements = rootUiElementsForKind(elements);
2992
+ const rootDefaultClasses = (0, utils_1.unique)(structuralRootElements.flatMap((element) => element.defaultClasses ?? element.classes)).slice(0, 80);
2993
+ const recipeDefaultClasses = kind === 'card' && rootDefaultClasses.length ? rootDefaultClasses : defaultClasses;
2994
+ const nestedDefaultClasses = kind === 'card'
2995
+ ? (0, utils_1.unique)(elements
2996
+ .filter((element) => !structuralRootElements.includes(element))
2997
+ .flatMap((element) => element.defaultClasses ?? element.classes)
2998
+ .filter((className) => !(0, extractors_1.isDecorativeAccentClass)(className))).slice(0, 80)
2999
+ : undefined;
3000
+ const actionUsages = kind === 'button' ? summarizeActionUsages(elements) : undefined;
3001
+ const uiContract = createUiPatternContract({
3002
+ name: `${(0, utils_1.titleCase)(kind)} pattern`,
3003
+ count: elements.length,
3004
+ kind,
3005
+ tag: structuralRootElements[0]?.originalTag,
3006
+ layoutRole: structuralRootElements[0]?.layoutRole,
3007
+ label: structuralRootElements[0]?.label,
3008
+ line: structuralRootElements[0]?.line,
3009
+ path: file.relPath,
3010
+ sourceHash: file.sourceHash,
3011
+ confidence: elements.length > 1 ? 'medium' : 'low',
3012
+ evidence: structuralRootElements[0]?.evidence,
3013
+ classSource: structuralRootElements[0]?.classSource,
3014
+ classes,
3015
+ defaultClasses: recipeDefaultClasses,
3016
+ conditionalClasses,
3017
+ decorativeAccentClasses,
3018
+ classGroups,
3019
+ layoutSafety,
3020
+ responsiveProfile,
3021
+ scaleProfile,
3022
+ semanticProfile,
3023
+ roleSignature: roleSignatures[0],
3024
+ variants,
3025
+ styleContext,
3026
+ });
3027
+ uiContract.safetyRules = omitEmptyRecord({
3028
+ ...uiContract.safetyRules,
3029
+ generationConstraints,
3030
+ });
3031
+ const relationships = createUiRelationships({
3032
+ parentContext: parentContexts,
3033
+ childStructureContract,
3034
+ compoundStructure,
3035
+ graphKeys: dependencies,
3036
+ });
737
3037
  addEntry(entries, usedKeys, {
738
3038
  key: ruleKey,
739
3039
  group: 'rule',
@@ -744,8 +3044,30 @@ function addUiPatternRules(entries, usedKeys, nodes, edges, files) {
744
3044
  kind,
745
3045
  ...Object.keys(variants),
746
3046
  ...Object.values(variants).flatMap((values) => values),
747
- ...defaultClasses.slice(0, 20),
3047
+ ...recipeDefaultClasses.slice(0, 20),
748
3048
  ...(decorativeAccentClasses.length ? ['decorative-accent', 'highlight-only'] : []),
3049
+ ...(layoutSafety
3050
+ ? ['layout-safety', ...layoutSafety.layoutSafetyClasses.slice(0, 16)]
3051
+ : []),
3052
+ ...(responsiveProfile ? ['responsive-contract'] : []),
3053
+ ...(scaleProfile ? ['scale-contract'] : []),
3054
+ ...(roleSignatures.length
3055
+ ? [
3056
+ 'role-signature',
3057
+ ...roleSignatures.flatMap((signature) => [
3058
+ signature.role,
3059
+ signature.roleGroup,
3060
+ signature.scale,
3061
+ signature.density,
3062
+ signature.surface,
3063
+ signature.layout,
3064
+ ...signature.flags,
3065
+ ]),
3066
+ ]
3067
+ : []),
3068
+ ...(semanticProfile?.isCompound
3069
+ ? ['compound-ui', 'compound-badge', ...semanticProfile.roles]
3070
+ : []),
749
3071
  ]),
750
3072
  dependencies,
751
3073
  usedBy: [],
@@ -759,12 +3081,30 @@ function addUiPatternRules(entries, usedKeys, nodes, edges, files) {
759
3081
  concept: kind,
760
3082
  path: file.relPath,
761
3083
  classes,
762
- defaultClasses,
3084
+ defaultClasses: recipeDefaultClasses,
3085
+ rootDefaultClasses,
3086
+ ...(nestedDefaultClasses?.length ? { nestedDefaultClasses } : {}),
763
3087
  conditionalClasses,
764
3088
  decorativeAccentClasses,
765
- guidance: (0, extractors_1.createUiPatternGuidance)(kind, decorativeAccentClasses),
3089
+ ...(layoutSafety ? { layoutSafety } : {}),
3090
+ ...(responsiveProfile ? { responsiveProfile } : {}),
3091
+ ...(scaleProfile ? { scaleProfile } : {}),
3092
+ ...(semanticProfile ? { semanticProfile } : {}),
3093
+ ...(roleSignatures.length ? { roleSignatures } : {}),
3094
+ ...(parentContexts.length ? { parentContexts } : {}),
3095
+ ...(childStructureContract ? { childStructureContract } : {}),
3096
+ ...(compoundStructure ? { compoundStructure } : {}),
3097
+ generationConstraints,
3098
+ uiContract,
3099
+ relationships,
3100
+ guidance: (0, utils_1.unique)([
3101
+ ...(0, extractors_1.createUiPatternGuidance)(kind, decorativeAccentClasses),
3102
+ ...createUiGenerationGuidance(kind, elements),
3103
+ ]),
766
3104
  classGroups,
767
3105
  variants,
3106
+ elementPatterns,
3107
+ ...(actionUsages ? { actionUsages } : {}),
768
3108
  elements: elements.slice(0, 80),
769
3109
  sourceHash: file.sourceHash,
770
3110
  source: sourceInfo('ui.patterns', elements.length > 1 ? 'medium' : 'low', [`${kind}:${elements.length}`], file),
@@ -773,8 +3113,12 @@ function addUiPatternRules(entries, usedKeys, nodes, edges, files) {
773
3113
  nodes.push(toGraphNode(ruleKey, 'rule', `${kind} pattern`, file.relPath, file, 'ui.patterns', {
774
3114
  ui: {
775
3115
  concept: kind,
776
- classes: defaultClasses,
3116
+ classes: rootDefaultClasses.length ? rootDefaultClasses : defaultClasses,
777
3117
  decorativeAccentClasses,
3118
+ layoutSafety,
3119
+ responsiveProfile,
3120
+ scaleProfile,
3121
+ semanticProfile,
778
3122
  classGroups,
779
3123
  variants,
780
3124
  count: elements.length,
@@ -784,8 +3128,11 @@ function addUiPatternRules(entries, usedKeys, nodes, edges, files) {
784
3128
  }
785
3129
  }
786
3130
  }
787
- function addUiCompositionRules(entries, usedKeys, nodes, edges, files) {
3131
+ function addUiCompositionRules(entries, usedKeys, nodes, edges, files, styleContext) {
788
3132
  for (const file of files) {
3133
+ if ((0, extractors_1.isTestFile)(file.relPath)) {
3134
+ continue;
3135
+ }
789
3136
  if (!file.uiElements.length && !file.classExpressions.length) {
790
3137
  continue;
791
3138
  }
@@ -819,6 +3166,46 @@ function addUiCompositionRules(entries, usedKeys, nodes, edges, files) {
819
3166
  const classExpressionsByTarget = (0, extractors_1.groupClassExpressionsByTarget)(file.classExpressions);
820
3167
  const layoutRoles = (0, utils_1.unique)(file.uiElements.flatMap((element) => element.layoutRole ?? []));
821
3168
  const elementKinds = (0, utils_1.unique)(file.uiElements.map((element) => element.kind));
3169
+ const layoutSafety = summarizePatternLayoutSafety(file.uiElements);
3170
+ const responsiveProfile = summarizePatternResponsiveProfile(file.uiElements);
3171
+ const scaleProfile = summarizePatternScaleProfile(file.uiElements);
3172
+ const semanticProfile = summarizePatternSemanticProfile(file.uiElements);
3173
+ const parentContexts = summarizeParentContexts(file.uiElements);
3174
+ const childStructureContract = summarizePatternChildStructure(compositionElements, styleContext);
3175
+ const compoundStructure = summarizeCompoundStructure(file.uiElements, styleContext);
3176
+ const uiContract = createUiPatternContract({
3177
+ name: `${file.relPath} UI composition`,
3178
+ count: file.uiElements.length,
3179
+ kind: 'composition',
3180
+ tag: compositionElements[0]?.originalTag,
3181
+ layoutRole: layoutRoles[0],
3182
+ line: compositionElements[0]?.line,
3183
+ path: file.relPath,
3184
+ sourceHash: file.sourceHash,
3185
+ confidence: file.components.length || file.routes.length ? 'high' : 'medium',
3186
+ evidence: compositionElements[0]?.evidence,
3187
+ classSource: compositionElements[0]?.classSource,
3188
+ classes: baseClassReferences,
3189
+ defaultClasses: baseClassReferences,
3190
+ conditionalClasses,
3191
+ decorativeAccentClasses,
3192
+ classGroups: (0, extractors_1.mergeClassGroups)(compositionElements.map((element) => element.classGroups ?? {})),
3193
+ layoutSafety,
3194
+ responsiveProfile,
3195
+ scaleProfile,
3196
+ semanticProfile,
3197
+ styleContext,
3198
+ });
3199
+ uiContract.safetyRules = omitEmptyRecord({
3200
+ ...uiContract.safetyRules,
3201
+ compositionGuidance: (0, extractors_1.createUiCompositionGuidance)(file),
3202
+ });
3203
+ const relationships = createUiRelationships({
3204
+ parentContext: parentContexts,
3205
+ childStructureContract,
3206
+ compoundStructure,
3207
+ graphKeys: dependencies,
3208
+ });
822
3209
  addEntry(entries, usedKeys, {
823
3210
  key: ruleKey,
824
3211
  group: 'rule',
@@ -840,6 +3227,14 @@ function addUiCompositionRules(entries, usedKeys, nodes, edges, files) {
840
3227
  ...responsiveClasses.slice(0, 24),
841
3228
  ...surfaceClasses.slice(0, 24),
842
3229
  ...(decorativeAccentClasses.length ? ['decorative-accent', 'highlight-only'] : []),
3230
+ ...(layoutSafety
3231
+ ? ['layout-safety', ...layoutSafety.layoutSafetyClasses.slice(0, 24)]
3232
+ : []),
3233
+ ...(responsiveProfile ? ['responsive-contract'] : []),
3234
+ ...(scaleProfile ? ['scale-contract'] : []),
3235
+ ...(semanticProfile?.isCompound
3236
+ ? ['compound-ui', 'compound-badge', ...semanticProfile.roles]
3237
+ : []),
843
3238
  ...spacingClasses.slice(0, 24),
844
3239
  ...typographyClasses.slice(0, 24),
845
3240
  ]),
@@ -865,6 +3260,15 @@ function addUiCompositionRules(entries, usedKeys, nodes, edges, files) {
865
3260
  conditionalClasses,
866
3261
  decorativeAccentClasses,
867
3262
  typographyClasses,
3263
+ ...(layoutSafety ? { layoutSafety } : {}),
3264
+ ...(responsiveProfile ? { responsiveProfile } : {}),
3265
+ ...(scaleProfile ? { scaleProfile } : {}),
3266
+ ...(semanticProfile ? { semanticProfile } : {}),
3267
+ ...(parentContexts.length ? { parentContexts } : {}),
3268
+ ...(childStructureContract ? { childStructureContract } : {}),
3269
+ ...(compoundStructure ? { compoundStructure } : {}),
3270
+ uiContract,
3271
+ relationships,
868
3272
  componentSlots: (0, extractors_1.buildComponentSlots)(file.uiElements),
869
3273
  classExpressionsByTarget,
870
3274
  elements: compositionElements,
@@ -883,6 +3287,10 @@ function addUiCompositionRules(entries, usedKeys, nodes, edges, files) {
883
3287
  routes: file.routes,
884
3288
  layoutRoles,
885
3289
  elementKinds,
3290
+ layoutSafety,
3291
+ responsiveProfile,
3292
+ scaleProfile,
3293
+ semanticProfile,
886
3294
  },
887
3295
  }));
888
3296
  edges.push(toGraphEdge(file.key, ruleKey, 'defines_ui_composition', 'ui.composition', file));
@@ -894,8 +3302,175 @@ function addUiCompositionRules(entries, usedKeys, nodes, edges, files) {
894
3302
  }
895
3303
  }
896
3304
  }
897
- function addConnectedPatternRules(entries, usedKeys, nodes, edges, reviewItems, files) {
898
- const clusters = buildConnectedPatternClusters(files);
3305
+ function addGlobalUiContractRules(entries, usedKeys, nodes, edges, files) {
3306
+ const eligibleFiles = files.filter((file) => !(0, extractors_1.isTestFile)(file.relPath));
3307
+ const shellFiles = eligibleFiles.filter(hasPageShellContractEvidence);
3308
+ const framedSurfaceFiles = eligibleFiles.filter(hasFramedSurfaceContractEvidence);
3309
+ if (shellFiles.length) {
3310
+ const primary = shellFiles[0];
3311
+ const ruleKey = (0, utils_1.toRegistryKey)('rule', 'ui.page-shell.global');
3312
+ const cssRules = cssRuleSnapshots(shellFiles, shellCssRuleMatches).slice(0, 48);
3313
+ const classes = collectClassEvidence(shellFiles, (className) => /^(?:app-shell|bg-background|min-h-screen|flex|flex-col|text-foreground)$/.test(className));
3314
+ addEntry(entries, usedKeys, {
3315
+ key: ruleKey,
3316
+ group: 'rule',
3317
+ summary: 'Global page shell contract: body/app-shell owns the page background, route roots stay transparent.',
3318
+ tags: (0, utils_1.unique)([
3319
+ 'rule',
3320
+ 'ui',
3321
+ 'global',
3322
+ 'page-shell',
3323
+ 'app-shell',
3324
+ 'body-background',
3325
+ 'route-root',
3326
+ 'transparent-root',
3327
+ 'bg-background',
3328
+ ...classes,
3329
+ ]),
3330
+ dependencies: contractDependencies(shellFiles),
3331
+ usedBy: [],
3332
+ confidence: 'high',
3333
+ status: 'inferred',
3334
+ path: primary.relPath,
3335
+ value: {
3336
+ name: 'Global page shell contract',
3337
+ kind: 'rule',
3338
+ ruleType: 'ui-page-shell',
3339
+ backgroundOwner: 'body/app-shell',
3340
+ keepRouteRootsTransparent: true,
3341
+ classes,
3342
+ requiredShellClasses: collectClassEvidence(shellFiles, (className) => /^(?:app-shell|min-h-screen|flex|flex-col)$/.test(className)),
3343
+ avoidOnRouteRoot: ['bg-background', 'min-h-screen bg-background'],
3344
+ routeRootGuidance: [
3345
+ '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.',
3346
+ '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.',
3347
+ '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.',
3348
+ ],
3349
+ cssRules,
3350
+ sourceHash: primary.sourceHash,
3351
+ source: sourceInfo('ui.global-contracts', 'high', shellFiles.slice(0, 20).map((file) => `path:${file.relPath}`), primary),
3352
+ },
3353
+ });
3354
+ nodes.push(toGraphNode(ruleKey, 'rule', 'Global page shell contract', primary.relPath, primary, 'ui.global-contracts', {
3355
+ ui: {
3356
+ ruleType: 'ui-page-shell',
3357
+ backgroundOwner: 'body/app-shell',
3358
+ keepRouteRootsTransparent: true,
3359
+ classes,
3360
+ },
3361
+ }));
3362
+ for (const file of shellFiles.slice(0, 80)) {
3363
+ edges.push(toGraphEdge(file.key, ruleKey, 'defines_ui_contract', 'ui.global-contracts', file));
3364
+ if (hasStyleSurface(file)) {
3365
+ edges.push(toGraphEdge(styleKeyForFile(file), ruleKey, 'defines_ui_contract', 'ui.global-contracts', file));
3366
+ }
3367
+ }
3368
+ }
3369
+ if (framedSurfaceFiles.length) {
3370
+ const primary = framedSurfaceFiles[0];
3371
+ const ruleKey = (0, utils_1.toRegistryKey)('rule', 'ui.surface.section-frame-grid');
3372
+ const cssRules = cssRuleSnapshots(framedSurfaceFiles, framedSurfaceCssRuleMatches).slice(0, 48);
3373
+ const classes = collectClassEvidence(framedSurfaceFiles, (className) => /^(?:section-frame|sm:section-frame|context-surface|bg-card|bg-\[var\(--surface-soft\)\]|rounded)/.test(className));
3374
+ addEntry(entries, usedKeys, {
3375
+ key: ruleKey,
3376
+ group: 'rule',
3377
+ summary: 'Framed technical surface contract: section-frame paints a clipped grid overlay and context-surface protects child content.',
3378
+ tags: (0, utils_1.unique)([
3379
+ 'rule',
3380
+ 'ui',
3381
+ 'surface',
3382
+ 'framed-surface',
3383
+ 'section-frame',
3384
+ 'context-surface',
3385
+ 'grid-overlay',
3386
+ 'pseudo-element',
3387
+ 'nested-surface',
3388
+ ...classes,
3389
+ ]),
3390
+ dependencies: contractDependencies(framedSurfaceFiles),
3391
+ usedBy: [],
3392
+ confidence: 'high',
3393
+ status: 'inferred',
3394
+ path: primary.relPath,
3395
+ value: {
3396
+ name: 'Section frame grid surface contract',
3397
+ kind: 'rule',
3398
+ ruleType: 'ui-framed-surface',
3399
+ surfaceClasses: ['section-frame', 'context-surface'],
3400
+ classes,
3401
+ paintModel: {
3402
+ sectionFrame: 'section-frame is not only border/background; it owns the clipped section-frame::before grid overlay for that exact surface.',
3403
+ contextSurface: 'context-surface keeps direct children above the parent overlay, preserving readable content and separate child surfaces.',
3404
+ },
3405
+ nestingGuidance: [
3406
+ 'For parent technical panels, use section-frame with context-surface when content or child cards must sit above the parent grid.',
3407
+ '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.',
3408
+ 'Do not rely on one ancestor section-frame grid for child cards that visually have separate framed grids in the source design.',
3409
+ ],
3410
+ usageGuidance: [
3411
+ 'Use this contract for framed technical panels and code/context surfaces, not every ordinary repeated card.',
3412
+ 'Preserve responsive forms such as sm:section-frame when the source intentionally keeps mobile surfaces transparent or unframed.',
3413
+ ],
3414
+ avoid: [
3415
+ 'Do not add decorative glow/shadow classes to repeated cards just because they are framed.',
3416
+ 'Do not flatten parent and child frame classes onto one root; preserve the surface hierarchy.',
3417
+ ],
3418
+ cssRules,
3419
+ sourceHash: primary.sourceHash,
3420
+ source: sourceInfo('ui.global-contracts', 'high', framedSurfaceFiles.slice(0, 20).map((file) => `path:${file.relPath}`), primary),
3421
+ },
3422
+ });
3423
+ nodes.push(toGraphNode(ruleKey, 'rule', 'Section frame grid surface contract', primary.relPath, primary, 'ui.global-contracts', {
3424
+ ui: {
3425
+ ruleType: 'ui-framed-surface',
3426
+ surfaceClasses: ['section-frame', 'context-surface'],
3427
+ classes,
3428
+ },
3429
+ }));
3430
+ for (const file of framedSurfaceFiles.slice(0, 80)) {
3431
+ edges.push(toGraphEdge(file.key, ruleKey, 'defines_ui_contract', 'ui.global-contracts', file));
3432
+ if (hasStyleSurface(file)) {
3433
+ edges.push(toGraphEdge(styleKeyForFile(file), ruleKey, 'defines_ui_contract', 'ui.global-contracts', file));
3434
+ }
3435
+ }
3436
+ }
3437
+ }
3438
+ function hasPageShellContractEvidence(file) {
3439
+ return Boolean(file.classReferences.includes('app-shell') ||
3440
+ file.cssClasses.includes('app-shell') ||
3441
+ file.cssRules.some(shellCssRuleMatches) ||
3442
+ (file.relPath.endsWith('layout.tsx') && file.classReferences.includes('bg-background')));
3443
+ }
3444
+ function hasFramedSurfaceContractEvidence(file) {
3445
+ return Boolean(file.classReferences.some((className) => ['section-frame', 'sm:section-frame', 'context-surface'].includes(className)) ||
3446
+ file.cssClasses.some((className) => ['section-frame', 'context-surface'].includes(className)) ||
3447
+ file.cssRules.some(framedSurfaceCssRuleMatches));
3448
+ }
3449
+ function shellCssRuleMatches(rule) {
3450
+ return /(^|[,\s])(?:html|body|:root)(?:$|[,\s:{.#])|\.app-shell\b|\.bg-background\b/.test(rule.selector);
3451
+ }
3452
+ function framedSurfaceCssRuleMatches(rule) {
3453
+ return /\.section-frame\b|\.context-surface\b/.test(rule.selector);
3454
+ }
3455
+ function contractDependencies(files) {
3456
+ return (0, utils_1.unique)(files.flatMap((file) => [file.key, ...(hasStyleSurface(file) ? [styleKeyForFile(file)] : [])]));
3457
+ }
3458
+ function styleKeyForFile(file) {
3459
+ return (0, utils_1.toRegistryKey)('style', file.relPath.replace(/\.[^.]+$/, ''));
3460
+ }
3461
+ function collectClassEvidence(files, predicate) {
3462
+ return (0, utils_1.unique)(files.flatMap((file) => [...file.classReferences, ...file.cssClasses].filter(predicate))).slice(0, 120);
3463
+ }
3464
+ function cssRuleSnapshots(files, predicate) {
3465
+ return (0, utils_1.uniqueBy)(files.flatMap((file) => file.cssRules.filter(predicate).map((rule) => ({
3466
+ path: file.relPath,
3467
+ selector: rule.selector,
3468
+ line: rule.line,
3469
+ declarations: rule.declarations.slice(0, 24),
3470
+ }))), (rule) => `${rule.path}:${rule.selector}:${rule.line}`);
3471
+ }
3472
+ function addConnectedPatternRules(entries, usedKeys, nodes, edges, reviewItems, files, styleContext) {
3473
+ const clusters = buildConnectedPatternClusters(files, styleContext);
899
3474
  for (const cluster of clusters) {
900
3475
  addEntry(entries, usedKeys, {
901
3476
  key: cluster.key,
@@ -967,9 +3542,9 @@ function addConnectedPatternRules(entries, usedKeys, nodes, edges, reviewItems,
967
3542
  }
968
3543
  }
969
3544
  }
970
- function buildConnectedPatternClusters(files) {
3545
+ function buildConnectedPatternClusters(files, styleContext) {
971
3546
  const instances = [
972
- ...files.flatMap(createFrontendPatternInstances),
3547
+ ...files.flatMap((file) => createFrontendPatternInstances(file, styleContext)),
973
3548
  ...createBackendPatternInstances(files),
974
3549
  ...createDataPatternInstances(files),
975
3550
  ];
@@ -990,7 +3565,62 @@ function buildConnectedPatternClusters(files) {
990
3565
  const classGroups = (0, extractors_1.mergeClassGroups)(group
991
3566
  .map((instance) => instance.root?.classGroups)
992
3567
  .filter((value) => Boolean(value)));
3568
+ const layoutSafety = summarizeConnectedLayoutSafety(group);
3569
+ const responsiveProfile = summarizeConnectedResponsiveProfile(group);
3570
+ const scaleProfile = summarizeConnectedScaleProfile(group);
3571
+ const semanticProfile = summarizeConnectedSemanticProfile(group);
3572
+ const roleSignatures = summarizeConnectedRoleSignatures(group);
3573
+ const roleSignature = roleSignatures[0];
3574
+ const compoundStructure = summarizeConnectedCompoundStructure(group);
3575
+ const parentContexts = summarizeConnectedParentContexts(group);
993
3576
  const childStructure = summarizeConnectedChildren(group);
3577
+ const graphKeys = (0, utils_1.unique)(group.flatMap((instance) => instance.graphKeys)).slice(0, 80);
3578
+ const structureContract = {
3579
+ preserveRootAndChildrenTogether: true,
3580
+ preserveParentLayoutContext: Boolean(parentContexts.length),
3581
+ preserveDirectChildRelationships: Boolean(childStructure.length),
3582
+ preserveCompoundChildRoles: Boolean(compoundStructure),
3583
+ preserveResponsiveBreakpoints: Boolean(responsiveProfile),
3584
+ preserveLayoutSafety: Boolean(layoutSafety),
3585
+ preserveScaleBounds: Boolean(scaleProfile),
3586
+ preserveRoleSignature: Boolean(roleSignature),
3587
+ avoidDefaultDecorativeAccents: Boolean(decorativeAccentClasses.length),
3588
+ };
3589
+ const uiContract = createUiPatternContract({
3590
+ name: `${(0, utils_1.titleCase)(first.concept)} connected pattern`,
3591
+ count: group.length,
3592
+ kind: first.concept,
3593
+ tag: first.root?.tag,
3594
+ layoutRole: first.root?.layoutRole,
3595
+ path: first.file.relPath,
3596
+ sourceHash: first.file.sourceHash,
3597
+ confidence,
3598
+ evidence: first.evidence.label,
3599
+ classes: defaultClasses,
3600
+ defaultClasses,
3601
+ conditionalClasses,
3602
+ decorativeAccentClasses,
3603
+ classGroups,
3604
+ layoutSafety,
3605
+ responsiveProfile,
3606
+ scaleProfile,
3607
+ semanticProfile,
3608
+ roleSignature,
3609
+ styleContext,
3610
+ });
3611
+ uiContract.safetyRules = omitEmptyRecord({
3612
+ ...uiContract.safetyRules,
3613
+ structureContract,
3614
+ });
3615
+ const relationships = createUiRelationships({
3616
+ parentContext: parentContexts,
3617
+ childStructureContract: {
3618
+ children: childStructure,
3619
+ preserveDirectChildRelationships: Boolean(childStructure.length),
3620
+ },
3621
+ compoundStructure,
3622
+ graphKeys,
3623
+ });
994
3624
  const key = (0, utils_1.toRegistryKey)('cluster', `pattern.${first.domain}.${first.concept}.${connectedPatternSlug(first)}.${(0, utils_1.shortHash)(first.signature)}`);
995
3625
  const fileCount = new Set(group.map((instance) => instance.file.relPath)).size;
996
3626
  clusters.push({
@@ -1015,6 +3645,28 @@ function buildConnectedPatternClusters(files) {
1015
3645
  ...group.flatMap((instance) => instance.tags).slice(0, 80),
1016
3646
  ...defaultClasses.slice(0, 24),
1017
3647
  ...(decorativeAccentClasses.length ? ['decorative-accent', 'highlight-only'] : []),
3648
+ ...(layoutSafety
3649
+ ? ['layout-safety', ...layoutSafety.layoutSafetyClasses.slice(0, 24)]
3650
+ : []),
3651
+ ...(responsiveProfile ? ['responsive-contract'] : []),
3652
+ ...(scaleProfile ? ['scale-contract'] : []),
3653
+ ...(semanticProfile?.isCompound
3654
+ ? ['compound-ui', 'compound-badge', ...semanticProfile.roles]
3655
+ : []),
3656
+ ...(roleSignatures.length
3657
+ ? [
3658
+ 'role-signature',
3659
+ ...roleSignatures.flatMap((signature) => [
3660
+ signature.role,
3661
+ signature.roleGroup,
3662
+ signature.scale,
3663
+ signature.density,
3664
+ signature.surface,
3665
+ signature.layout,
3666
+ ...signature.flags,
3667
+ ]),
3668
+ ]
3669
+ : []),
1018
3670
  ]),
1019
3671
  dependencies,
1020
3672
  value: {
@@ -1034,9 +3686,19 @@ function buildConnectedPatternClusters(files) {
1034
3686
  conditionalClasses,
1035
3687
  decorativeAccentClasses,
1036
3688
  classGroups,
3689
+ ...(layoutSafety ? { layoutSafety } : {}),
3690
+ ...(responsiveProfile ? { responsiveProfile } : {}),
3691
+ ...(scaleProfile ? { scaleProfile } : {}),
3692
+ ...(semanticProfile ? { semanticProfile } : {}),
3693
+ ...(roleSignatures.length ? { roleSignatures } : {}),
3694
+ ...(parentContexts.length ? { parentContexts } : {}),
1037
3695
  childStructure,
3696
+ ...(compoundStructure ? { compoundStructure } : {}),
3697
+ structureContract,
3698
+ uiContract,
3699
+ relationships,
1038
3700
  examples,
1039
- graphKeys: (0, utils_1.unique)(group.flatMap((instance) => instance.graphKeys)).slice(0, 80),
3701
+ graphKeys,
1040
3702
  guidance: createConnectedPatternGuidance(first, group, decorativeAccentClasses),
1041
3703
  variants: connectedPatternVariants(first.domain, group),
1042
3704
  children: connectedPatternChildren(first.domain, first.concept, group, dependencies),
@@ -1080,8 +3742,11 @@ function buildConnectedPatternClusters(files) {
1080
3742
  }
1081
3743
  return clusters.sort((a, b) => b.score - a.score || a.key.localeCompare(b.key));
1082
3744
  }
1083
- function createFrontendPatternInstances(file) {
3745
+ function createFrontendPatternInstances(file, styleContext) {
1084
3746
  const instances = [];
3747
+ if ((0, extractors_1.isTestFile)(file.relPath)) {
3748
+ return instances;
3749
+ }
1085
3750
  for (const element of file.uiElements) {
1086
3751
  if (!isConnectedUiPatternRoot(element)) {
1087
3752
  continue;
@@ -1093,7 +3758,14 @@ function createFrontendPatternInstances(file) {
1093
3758
  ...(0, extractors_1.filterClasses)(element.classes, extractors_1.isDecorativeAccentClass),
1094
3759
  ]).slice(0, 60);
1095
3760
  const classGroups = (0, extractors_1.groupTailwindClasses)(defaultClasses.length ? defaultClasses : element.classes);
1096
- const children = normalizeConnectedChildren(element.childSummary ?? []);
3761
+ const children = normalizeConnectedChildren(element.childSummary ?? [], styleContext);
3762
+ const parentContext = summarizeParentContexts([element])[0];
3763
+ const childStructureContract = element.childSummary?.length
3764
+ ? summarizeElementChildContract(element.childSummary, styleContext)
3765
+ : undefined;
3766
+ const compoundStructure = element.semanticProfile?.isCompound
3767
+ ? summarizeCompoundStructure([element], styleContext)
3768
+ : undefined;
1097
3769
  const component = file.components[0];
1098
3770
  const route = file.routes[0];
1099
3771
  const graphKeys = (0, utils_1.unique)([
@@ -1110,10 +3782,44 @@ function createFrontendPatternInstances(file) {
1110
3782
  'frontend',
1111
3783
  element.kind,
1112
3784
  normalizeLayoutRole(element.layoutRole),
3785
+ roleSignatureKey(element.roleSignature),
1113
3786
  normalizeTagForSignature(element),
1114
3787
  classSignature(defaultClasses),
1115
3788
  children.map((child) => child.signature).join('|'),
1116
3789
  ].join(';');
3790
+ const uiContract = createUiContract({
3791
+ kind: element.kind,
3792
+ tag: element.originalTag,
3793
+ layoutRole: element.layoutRole,
3794
+ label: element.label,
3795
+ line: element.line,
3796
+ path: file.relPath,
3797
+ sourceHash: file.sourceHash,
3798
+ confidence: 'medium',
3799
+ evidence: element.evidence,
3800
+ classSource: element.classSource,
3801
+ classes: element.classes,
3802
+ defaultClasses,
3803
+ conditionalClasses,
3804
+ decorativeAccentClasses,
3805
+ classGroups,
3806
+ layoutSafety: element.layoutSafety,
3807
+ responsiveProfile: element.responsiveProfile,
3808
+ scaleProfile: element.scaleProfile,
3809
+ semanticProfile: element.semanticProfile,
3810
+ roleSignature: element.roleSignature,
3811
+ variants: element.variants,
3812
+ props: element.props,
3813
+ styleHelper: element.styleHelper,
3814
+ styleHelperVariants: element.styleHelperVariants,
3815
+ styleContext,
3816
+ });
3817
+ const relationships = createUiRelationships({
3818
+ parentContext,
3819
+ childStructureContract,
3820
+ compoundStructure,
3821
+ graphKeys,
3822
+ });
1117
3823
  instances.push({
1118
3824
  domain: 'frontend',
1119
3825
  concept: element.kind,
@@ -1129,6 +3835,12 @@ function createFrontendPatternInstances(file) {
1129
3835
  conditionalClasses,
1130
3836
  decorativeAccentClasses,
1131
3837
  classGroups,
3838
+ ...(element.layoutSafety ? { layoutSafety: element.layoutSafety } : {}),
3839
+ ...(element.responsiveProfile ? { responsiveProfile: element.responsiveProfile } : {}),
3840
+ ...(element.scaleProfile ? { scaleProfile: element.scaleProfile } : {}),
3841
+ ...(element.semanticProfile ? { semanticProfile: element.semanticProfile } : {}),
3842
+ ...(element.roleSignature ? { roleSignature: element.roleSignature } : {}),
3843
+ uiContract,
1132
3844
  },
1133
3845
  children: children.map(withoutConnectedChildSignature),
1134
3846
  graphKeys,
@@ -1142,6 +3854,25 @@ function createFrontendPatternInstances(file) {
1142
3854
  ...file.components,
1143
3855
  ...file.routes,
1144
3856
  ...Object.keys(classGroups),
3857
+ ...(element.layoutSafety ? ['layout-safety'] : []),
3858
+ ...(element.responsiveProfile ? ['responsive-contract'] : []),
3859
+ ...(element.scaleProfile ? ['scale-contract'] : []),
3860
+ ...(element.roleSignature
3861
+ ? [
3862
+ 'role-signature',
3863
+ element.roleSignature.role,
3864
+ element.roleSignature.roleGroup,
3865
+ element.roleSignature.scale,
3866
+ element.roleSignature.density,
3867
+ element.roleSignature.surface,
3868
+ element.roleSignature.layout,
3869
+ ...element.roleSignature.flags,
3870
+ ...element.roleSignature.exactClassFacts.slice(0, 24),
3871
+ ]
3872
+ : []),
3873
+ ...(element.semanticProfile?.isCompound
3874
+ ? ['compound-ui', 'compound-badge', ...element.semanticProfile.roles]
3875
+ : []),
1145
3876
  ]),
1146
3877
  evidence: {
1147
3878
  path: file.relPath,
@@ -1160,8 +3891,18 @@ function createFrontendPatternInstances(file) {
1160
3891
  conditionalClasses,
1161
3892
  decorativeAccentClasses,
1162
3893
  classGroups,
3894
+ ...(element.layoutSafety ? { layoutSafety: element.layoutSafety } : {}),
3895
+ ...(element.responsiveProfile ? { responsiveProfile: element.responsiveProfile } : {}),
3896
+ ...(element.scaleProfile ? { scaleProfile: element.scaleProfile } : {}),
3897
+ ...(element.semanticProfile ? { semanticProfile: element.semanticProfile } : {}),
3898
+ ...(element.roleSignature ? { roleSignature: element.roleSignature } : {}),
3899
+ uiContract,
1163
3900
  },
1164
3901
  children: children.map(withoutConnectedChildSignature),
3902
+ ...(parentContext ? { parentContext } : {}),
3903
+ ...(childStructureContract ? { childStructureContract } : {}),
3904
+ ...(compoundStructure ? { compoundStructure } : {}),
3905
+ relationships,
1165
3906
  },
1166
3907
  baseWeight: frontendPatternWeight(file, element, children.length, defaultClasses),
1167
3908
  });
@@ -1185,6 +3926,7 @@ function createBackendPatternInstances(files) {
1185
3926
  ...file.services.map((service) => (0, utils_1.toRegistryKey)('service', service)),
1186
3927
  ...file.modules.map((moduleName) => (0, utils_1.toRegistryKey)('module', moduleName)),
1187
3928
  ...file.models.map((model) => (0, utils_1.toRegistryKey)('model', model)),
3929
+ ...file.databaseAccesses.flatMap(databaseAccessDependencies),
1188
3930
  ...file.routes.map((route) => (0, utils_1.toRegistryKey)('route', route)),
1189
3931
  ]);
1190
3932
  const signature = [
@@ -1195,7 +3937,7 @@ function createBackendPatternInstances(files) {
1195
3937
  `methods:${methodShapes.join(',') || 'none'}`,
1196
3938
  `controller:${Boolean(file.controllers.length)}`,
1197
3939
  `service:${Boolean(file.services.length)}`,
1198
- `model:${Boolean(file.models.length)}`,
3940
+ `model:${Boolean(file.models.length || file.databaseAccesses.length)}`,
1199
3941
  `auth:${Boolean(file.authHints.length || file.middleware.length)}`,
1200
3942
  `events:${Boolean(file.events.length || file.jobs.length)}`,
1201
3943
  `calls:${Boolean(file.apiCalls.length)}`,
@@ -1234,6 +3976,7 @@ function createBackendPatternInstances(files) {
1234
3976
  modules: file.modules,
1235
3977
  routes: file.routes,
1236
3978
  models: file.models,
3979
+ databaseAccesses: file.databaseAccesses,
1237
3980
  apiCalls: file.apiCalls,
1238
3981
  envVars: file.envVars,
1239
3982
  authHints: file.authHints,
@@ -1258,12 +4001,19 @@ function createDataPatternInstances(files) {
1258
4001
  const instances = [];
1259
4002
  for (const file of files) {
1260
4003
  for (const model of file.models) {
1261
- const fields = file.modelFields[model] ?? [];
4004
+ const databaseModel = file.databaseModels.find((candidate) => candidate.name === model);
4005
+ const fields = databaseModel
4006
+ ? databaseFieldShapeInputs(databaseModel)
4007
+ : (file.modelFields[model] ?? []);
1262
4008
  const fieldShape = summarizeModelFieldShape(fields);
4009
+ const accessGraphKeys = file.databaseAccesses
4010
+ .filter((access) => access.model === model || access.table === databaseModel?.tableName)
4011
+ .map((access) => (0, utils_1.toRegistryKey)('database', `access.${file.relPath.replace(/\.[^.]+$/, '')}.${access.line}.${access.operation}.${access.model ?? access.table ?? access.receiver ?? 'unknown'}`));
1263
4012
  const signature = [
1264
4013
  'data',
1265
- 'model',
1266
- detectFrameworkForFile(file) ?? file.language,
4014
+ databaseModel?.kind ?? 'model',
4015
+ databaseModel?.provider ?? detectFrameworkForFile(file) ?? file.language,
4016
+ databaseModel?.tableName ?? 'no-table',
1267
4017
  fieldShape.signature,
1268
4018
  ].join(';');
1269
4019
  instances.push({
@@ -1272,8 +4022,24 @@ function createDataPatternInstances(files) {
1272
4022
  signature,
1273
4023
  summary: `${model} data model shape in ${file.relPath}.`,
1274
4024
  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]),
4025
+ graphKeys: (0, utils_1.unique)([
4026
+ file.key,
4027
+ (0, utils_1.toRegistryKey)('model', model),
4028
+ ...(hasDatabaseSurface(file)
4029
+ ? [(0, utils_1.toRegistryKey)('database', file.relPath.replace(/\.[^.]+$/, ''))]
4030
+ : []),
4031
+ ...accessGraphKeys,
4032
+ ]),
4033
+ tags: (0, utils_1.unique)([
4034
+ 'data',
4035
+ 'database',
4036
+ databaseModel?.kind ?? 'model',
4037
+ 'schema',
4038
+ model,
4039
+ databaseModel?.tableName ?? '',
4040
+ databaseModel?.provider ?? file.language,
4041
+ ...fieldShape.tags,
4042
+ ]),
1277
4043
  evidence: {
1278
4044
  path: file.relPath,
1279
4045
  key: (0, utils_1.toRegistryKey)('model', model),
@@ -1281,18 +4047,35 @@ function createDataPatternInstances(files) {
1281
4047
  },
1282
4048
  value: {
1283
4049
  model,
1284
- fields,
4050
+ fields: databaseModel?.fieldNames ?? fields,
4051
+ fieldDetails: databaseModel?.fields ?? [],
1285
4052
  fieldShape,
1286
- framework: detectFrameworkForFile(file),
4053
+ tableName: databaseModel?.tableName,
4054
+ provider: databaseModel?.provider,
4055
+ primaryKey: databaseModel?.primaryKey ?? [],
4056
+ uniqueFields: databaseModel?.uniqueFields ?? [],
4057
+ indexes: databaseModel?.indexes ?? [],
4058
+ relations: databaseModel?.relations ?? [],
4059
+ enumRefs: databaseModel?.enumRefs ?? [],
4060
+ accesses: file.databaseAccesses.filter((access) => access.model === model || access.table === databaseModel?.tableName),
4061
+ framework: databaseModel?.framework ?? detectFrameworkForFile(file),
1287
4062
  },
1288
- baseWeight: 3 + Math.min(fields.length / 2, 8),
4063
+ baseWeight: 3 +
4064
+ Math.min(fields.length / 2, 8) +
4065
+ Math.min((databaseModel?.relations.length ?? 0) * 0.75, 4) +
4066
+ Math.min(accessGraphKeys.length * 0.5, 4),
1289
4067
  });
1290
4068
  }
1291
4069
  }
1292
4070
  return instances;
1293
4071
  }
1294
4072
  function isConnectedUiPatternRoot(element) {
1295
- if (element.kind === 'unknown' || element.kind === 'link' || element.kind === 'text') {
4073
+ if (element.kind === 'unknown' ||
4074
+ element.kind === 'link' ||
4075
+ element.kind === 'text' ||
4076
+ element.kind === 'icon' ||
4077
+ element.kind === 'image' ||
4078
+ element.kind === 'divider') {
1296
4079
  return false;
1297
4080
  }
1298
4081
  if (['button', 'input', 'badge'].includes(element.kind)) {
@@ -1300,9 +4083,12 @@ function isConnectedUiPatternRoot(element) {
1300
4083
  }
1301
4084
  return (0, extractors_1.isCompositionElement)(element) || Boolean(element.childSummary?.length);
1302
4085
  }
1303
- function normalizeConnectedChildren(children) {
4086
+ function normalizeConnectedChildren(children, styleContext) {
1304
4087
  return children
1305
- .filter((child) => child.kind !== 'unknown' || child.classes.length || child.label)
4088
+ .filter((child) => child.kind !== 'unknown' ||
4089
+ child.classes.length ||
4090
+ child.label ||
4091
+ Boolean(child.semanticRole))
1306
4092
  .slice(0, 10)
1307
4093
  .map((child) => {
1308
4094
  const defaultClasses = (0, extractors_1.filterClasses)(child.defaultClasses?.length ? child.defaultClasses : child.classes, (className) => !(0, extractors_1.isDecorativeAccentClass)(className)).slice(0, 48);
@@ -1311,6 +4097,8 @@ function normalizeConnectedChildren(children) {
1311
4097
  const signature = [
1312
4098
  normalizedKind,
1313
4099
  normalizeLayoutRole(child.layoutRole),
4100
+ child.semanticRole ?? 'unspecified-semantic-role',
4101
+ roleSignatureKey(child.roleSignature),
1314
4102
  normalizeChildTagForSignature(child),
1315
4103
  classSignature(defaultClasses),
1316
4104
  ].join(':');
@@ -1320,8 +4108,17 @@ function normalizeConnectedChildren(children) {
1320
4108
  sourceKind: child.kind,
1321
4109
  tag: child.originalTag,
1322
4110
  layoutRole: child.layoutRole,
4111
+ semanticRole: child.semanticRole,
4112
+ uiContract: createUiContractForChild(child, styleContext),
1323
4113
  defaultClasses,
1324
4114
  conditionalClasses: child.conditionalClasses?.slice(0, 32) ?? [],
4115
+ layoutSafety: child.layoutSafety,
4116
+ responsiveProfile: child.responsiveProfile,
4117
+ scaleProfile: child.scaleProfile,
4118
+ roleSignature: child.roleSignature,
4119
+ variants: child.variants ?? {},
4120
+ styleHelper: child.styleHelper,
4121
+ styleHelperVariants: child.styleHelperVariants ?? {},
1325
4122
  classGroups,
1326
4123
  labelHint: child.label ? labelSignature(child.label) : undefined,
1327
4124
  };
@@ -1331,25 +4128,125 @@ function withoutConnectedChildSignature(child) {
1331
4128
  return Object.fromEntries(Object.entries(child).filter(([key]) => key !== 'signature'));
1332
4129
  }
1333
4130
  function connectedChildKind(child) {
4131
+ if (child.semanticRole === 'icon' || child.semanticRole === 'component-icon') {
4132
+ return 'icon';
4133
+ }
4134
+ if (child.semanticRole === 'image') {
4135
+ return 'image';
4136
+ }
4137
+ if (child.semanticRole === 'divider') {
4138
+ return 'divider';
4139
+ }
1334
4140
  if (child.layoutRole === 'eyebrow-or-badge') {
1335
4141
  return 'badge';
1336
4142
  }
1337
4143
  return child.kind;
1338
4144
  }
4145
+ function isRecordValue(value) {
4146
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value));
4147
+ }
4148
+ function isRoleSignatureRecord(value) {
4149
+ return (isRecordValue(value) &&
4150
+ typeof value.role === 'string' &&
4151
+ typeof value.roleGroup === 'string' &&
4152
+ typeof value.scale === 'string' &&
4153
+ typeof value.density === 'string' &&
4154
+ typeof value.surface === 'string' &&
4155
+ typeof value.layout === 'string' &&
4156
+ Array.isArray(value.flags) &&
4157
+ Array.isArray(value.exactClassFacts));
4158
+ }
4159
+ function summarizeConnectedLayoutSafety(instances) {
4160
+ return mergeLayoutSafetyProfiles([
4161
+ ...instances.map((instance) => ({ layoutSafety: instance.root?.layoutSafety })),
4162
+ ...instances.flatMap((instance) => (instance.children ?? []).map((child) => ({
4163
+ layoutSafety: isLayoutSafetyProfile(child.layoutSafety) ? child.layoutSafety : undefined,
4164
+ }))),
4165
+ ]);
4166
+ }
4167
+ function summarizeConnectedResponsiveProfile(instances) {
4168
+ return mergeResponsiveProfiles([
4169
+ ...instances.map((instance) => ({ responsiveProfile: instance.root?.responsiveProfile })),
4170
+ ...instances.flatMap((instance) => (instance.children ?? []).map((child) => ({
4171
+ responsiveProfile: isResponsiveProfile(child.responsiveProfile)
4172
+ ? child.responsiveProfile
4173
+ : undefined,
4174
+ }))),
4175
+ ]);
4176
+ }
4177
+ function summarizeConnectedScaleProfile(instances) {
4178
+ return mergeScaleProfiles([
4179
+ ...instances.map((instance) => ({ scaleProfile: instance.root?.scaleProfile })),
4180
+ ...instances.flatMap((instance) => (instance.children ?? []).map((child) => ({
4181
+ scaleProfile: isScaleProfile(child.scaleProfile) ? child.scaleProfile : undefined,
4182
+ }))),
4183
+ ]);
4184
+ }
4185
+ function summarizeConnectedSemanticProfile(instances) {
4186
+ return mergeSemanticProfiles(instances.map((instance) => ({ semanticProfile: instance.root?.semanticProfile })));
4187
+ }
4188
+ function summarizeConnectedRoleSignatures(instances) {
4189
+ return (0, utils_1.uniqueBy)(instances
4190
+ .map((instance) => instance.root?.roleSignature)
4191
+ .filter((value) => Boolean(value)), (signature) => roleSignatureKey(signature)).slice(0, 12);
4192
+ }
4193
+ function summarizeConnectedCompoundStructure(instances) {
4194
+ const compoundInstances = instances.filter((instance) => instance.root?.semanticProfile?.isCompound);
4195
+ if (!compoundInstances.length) {
4196
+ return undefined;
4197
+ }
4198
+ const childCounts = compoundInstances.map((instance) => instance.children?.length ?? 0);
4199
+ const childOrderExamples = (0, utils_1.uniqueBy)(compoundInstances
4200
+ .map((instance) => instance.value.childStructureContract)
4201
+ .filter((value) => isRecordValue(value)), (value) => JSON.stringify(value)).slice(0, 8);
4202
+ const semanticProfile = summarizeConnectedSemanticProfile(compoundInstances);
4203
+ return {
4204
+ preserveCompoundChildren: true,
4205
+ preserveIconAndDividerRoles: Boolean(semanticProfile?.hasIcon || semanticProfile?.hasDivider),
4206
+ preserveMultipleTextSegments: Boolean(semanticProfile?.hasMultipleTextStyles),
4207
+ directChildCountRange: {
4208
+ min: Math.min(...childCounts),
4209
+ max: Math.max(...childCounts),
4210
+ },
4211
+ semanticRoles: semanticProfile?.roles ?? [],
4212
+ childOrderExamples,
4213
+ };
4214
+ }
4215
+ function summarizeConnectedParentContexts(instances) {
4216
+ return (0, utils_1.uniqueBy)(instances
4217
+ .map((instance) => instance.value.parentContext)
4218
+ .filter((value) => isRecordValue(value)), (context) => JSON.stringify(context)).slice(0, 12);
4219
+ }
1339
4220
  function summarizeConnectedChildren(instances) {
1340
4221
  const childValues = instances.flatMap((instance) => instance.children ?? []);
1341
4222
  const grouped = (0, utils_1.groupBy)(childValues, (child) => [
1342
4223
  String(child.kind ?? ''),
1343
4224
  String(child.layoutRole ?? ''),
4225
+ String(child.semanticRole ?? ''),
4226
+ roleSignatureKey(isRoleSignatureRecord(child.roleSignature) ? child.roleSignature : undefined),
1344
4227
  JSON.stringify(child.classGroups ?? {}),
1345
4228
  ].join(':'));
1346
4229
  return Array.from(grouped.values())
1347
4230
  .map((children) => {
1348
4231
  const first = children[0] ?? {};
4232
+ const layoutSafety = mergeLayoutSafetyProfiles(children.map((child) => ({
4233
+ layoutSafety: isLayoutSafetyProfile(child.layoutSafety) ? child.layoutSafety : undefined,
4234
+ })));
4235
+ const responsiveProfile = mergeResponsiveProfiles(children.map((child) => ({
4236
+ responsiveProfile: isResponsiveProfile(child.responsiveProfile)
4237
+ ? child.responsiveProfile
4238
+ : undefined,
4239
+ })));
4240
+ const scaleProfile = mergeScaleProfiles(children.map((child) => ({
4241
+ scaleProfile: isScaleProfile(child.scaleProfile) ? child.scaleProfile : undefined,
4242
+ })));
1349
4243
  return {
1350
4244
  kind: first.kind,
1351
4245
  tag: first.tag,
1352
4246
  layoutRole: first.layoutRole,
4247
+ semanticRole: first.semanticRole,
4248
+ roleSignature: first.roleSignature,
4249
+ uiContract: first.uiContract,
1353
4250
  count: children.length,
1354
4251
  defaultClasses: (0, utils_1.unique)(children.flatMap((child) => Array.isArray(child.defaultClasses)
1355
4252
  ? child.defaultClasses.filter((item) => typeof item === 'string')
@@ -1357,11 +4254,29 @@ function summarizeConnectedChildren(instances) {
1357
4254
  classGroups: (0, extractors_1.mergeClassGroups)(children
1358
4255
  .map((child) => child.classGroups)
1359
4256
  .filter((value) => Boolean(value))),
4257
+ ...(layoutSafety ? { layoutSafety } : {}),
4258
+ ...(responsiveProfile ? { responsiveProfile } : {}),
4259
+ ...(scaleProfile ? { scaleProfile } : {}),
4260
+ variants: mergeChildVariants(children),
4261
+ styleHelpers: (0, utils_1.unique)(children
4262
+ .map((child) => child.styleHelper)
4263
+ .filter((value) => typeof value === 'string' && Boolean(value))).slice(0, 12),
1360
4264
  };
1361
4265
  })
1362
4266
  .sort((a, b) => Number(b.count) - Number(a.count))
1363
4267
  .slice(0, 16);
1364
4268
  }
4269
+ function mergeChildVariants(children) {
4270
+ const variants = children
4271
+ .map((child) => child.variants)
4272
+ .filter((value) => {
4273
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
4274
+ return false;
4275
+ }
4276
+ return Object.values(value).every((item) => typeof item === 'string');
4277
+ });
4278
+ return (0, extractors_1.mergeVariantProps)(variants);
4279
+ }
1365
4280
  function compactConnectedPatternInstance(instance) {
1366
4281
  return {
1367
4282
  path: instance.file.relPath,
@@ -1548,7 +4463,9 @@ function createConceptPatternDecision(name, scores, shouldReview, roleCandidateC
1548
4463
  function connectedPatternVariants(domain, instances) {
1549
4464
  const preferredPrefixes = domain === 'frontend'
1550
4465
  ? ['component.', 'route.', 'style.', 'rule.']
1551
- : ['api.', 'controller.', 'service.', 'module.', 'model.', 'route.'];
4466
+ : domain === 'data'
4467
+ ? ['database.', 'model.', 'service.', 'api.', 'route.']
4468
+ : ['api.', 'controller.', 'service.', 'module.', 'model.', 'route.'];
1552
4469
  return (0, utils_1.unique)(instances.flatMap((instance) => [
1553
4470
  ...instance.file.components.map((component) => (0, utils_1.toRegistryKey)('component', component)),
1554
4471
  ...instance.file.routes.map((route) => (0, utils_1.toRegistryKey)('route', route)),
@@ -1557,6 +4474,9 @@ function connectedPatternVariants(domain, instances) {
1557
4474
  ...instance.file.services.map((service) => (0, utils_1.toRegistryKey)('service', service)),
1558
4475
  ...instance.file.modules.map((moduleName) => (0, utils_1.toRegistryKey)('module', moduleName)),
1559
4476
  ...instance.file.models.map((model) => (0, utils_1.toRegistryKey)('model', model)),
4477
+ ...(hasDatabaseSurface(instance.file)
4478
+ ? [(0, utils_1.toRegistryKey)('database', instance.file.relPath.replace(/\.[^.]+$/, ''))]
4479
+ : []),
1560
4480
  ...instance.graphKeys.filter((key) => preferredPrefixes.some((prefix) => key.startsWith(prefix))),
1561
4481
  ])).slice(0, 80);
1562
4482
  }
@@ -1572,6 +4492,9 @@ function connectedPatternChildren(domain, concept, instances, dependencies) {
1572
4492
  const graphChildren = dependencies.filter((key) => ['component.', 'style.', 'rule.', 'route.'].some((prefix) => key.startsWith(prefix)));
1573
4493
  return (0, utils_1.unique)([...semanticChildren, ...graphChildren]).slice(0, 80);
1574
4494
  }
4495
+ if (domain === 'data') {
4496
+ return (0, utils_1.unique)(dependencies.filter((key) => ['database.', 'model.', 'service.', 'api.', 'route.', 'file.'].some((prefix) => key.startsWith(prefix)))).slice(0, 80);
4497
+ }
1575
4498
  return (0, utils_1.unique)(dependencies.filter((key) => ['api.', 'controller.', 'service.', 'module.', 'model.', 'route.', 'file.'].some((prefix) => key.startsWith(prefix)))).slice(0, 80);
1576
4499
  }
1577
4500
  function derivePatternOwners(filePaths) {
@@ -1635,6 +4558,7 @@ function backendPatternWeight(file) {
1635
4558
  file.controllers.length * 2 +
1636
4559
  file.services.length * 2 +
1637
4560
  file.models.length * 1.5 +
4561
+ file.databaseAccesses.length * 1.2 +
1638
4562
  file.authHints.length * 0.5 +
1639
4563
  file.events.length * 0.5 +
1640
4564
  file.jobs.length * 0.5);
@@ -1644,6 +4568,7 @@ function hasBackendPatternSurface(file) {
1644
4568
  file.controllers.length ||
1645
4569
  file.services.length ||
1646
4570
  file.modules.length ||
4571
+ file.databaseAccesses.length ||
1647
4572
  file.apiCalls.length ||
1648
4573
  file.middleware.length ||
1649
4574
  file.jobs.length ||
@@ -1651,13 +4576,17 @@ function hasBackendPatternSurface(file) {
1651
4576
  }
1652
4577
  function backendPatternConcept(file) {
1653
4578
  if (file.controllers.length) {
1654
- return file.services.length || file.models.length ? 'api-controller-flow' : 'api-controller';
4579
+ return file.services.length || file.models.length || file.databaseAccesses.length
4580
+ ? 'api-controller-flow'
4581
+ : 'api-controller';
1655
4582
  }
1656
4583
  if (file.apiHandlers.length) {
1657
- return file.models.length || file.authHints.length ? 'api-data-flow' : 'api-route';
4584
+ return file.models.length || file.databaseAccesses.length || file.authHints.length
4585
+ ? 'api-data-flow'
4586
+ : 'api-route';
1658
4587
  }
1659
4588
  if (file.services.length) {
1660
- return file.models.length ? 'service-data-flow' : 'service';
4589
+ return file.models.length || file.databaseAccesses.length ? 'service-data-flow' : 'service';
1661
4590
  }
1662
4591
  if (file.jobs.length || file.events.length) {
1663
4592
  return 'async-flow';
@@ -1667,6 +4596,22 @@ function backendPatternConcept(file) {
1667
4596
  }
1668
4597
  return 'backend-flow';
1669
4598
  }
4599
+ function databaseFieldShapeInputs(model) {
4600
+ return model.fields.map((field) => [
4601
+ field.name,
4602
+ field.type ?? '',
4603
+ field.id ? '@id' : '',
4604
+ field.unique ? '@unique' : '',
4605
+ field.optional ? '?' : '',
4606
+ field.list ? '[]' : '',
4607
+ field.default ? `default:${field.default}` : '',
4608
+ field.enumName ? `enum:${field.enumName}` : '',
4609
+ field.relation ? '@relation' : '',
4610
+ field.relation?.onDelete ? `onDelete:${field.relation.onDelete}` : '',
4611
+ ]
4612
+ .filter(Boolean)
4613
+ .join(' '));
4614
+ }
1670
4615
  function summarizeModelFieldShape(fields) {
1671
4616
  const counts = {
1672
4617
  id: 0,
@@ -1718,7 +4663,25 @@ function createConnectedPatternGuidance(first, instances, decorativeAccentClasse
1718
4663
  `Pattern score is based on usage count, file spread, component/route evidence, and structural specificity.`,
1719
4664
  ];
1720
4665
  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.');
4666
+ 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.');
4667
+ if (summarizeConnectedLayoutSafety(instances)) {
4668
+ guidance.push('Preserve layoutSafety classes with their original element. They prevent wrapped buttons, overflowing card text, collapsed columns, and mobile clipping.');
4669
+ }
4670
+ if (summarizeConnectedParentContexts(instances).length) {
4671
+ guidance.push('Preserve parentContexts when placing this pattern. Repeated cards and nested panels should inherit the same grid/flex parent relationship as the source.');
4672
+ }
4673
+ if (summarizeConnectedScaleProfile(instances)) {
4674
+ guidance.push('Preserve scaleProfile for typography, spacing, max-width, and line-height. Do not make headings or section spacing larger than observed source bounds.');
4675
+ }
4676
+ if (summarizeConnectedRoleSignatures(instances).length) {
4677
+ guidance.push('Preserve roleSignature and roleConstraints as hard constraints. Lookalike patterns with different role, density, surface, layout, or exactClassFacts are different generation targets.');
4678
+ }
4679
+ if (summarizeConnectedResponsiveProfile(instances)) {
4680
+ guidance.push('Preserve responsiveProfile exactly enough that mobile and desktop layouts use the same breakpoints and visibility behavior as the source.');
4681
+ }
4682
+ if (summarizeConnectedCompoundStructure(instances)) {
4683
+ 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.');
4684
+ }
1722
4685
  }
1723
4686
  if (decorativeAccentClasses.length) {
1724
4687
  guidance.push('decorativeAccentClasses are opt-in highlight treatments. They must not become default card/surface styling.');
@@ -1737,6 +4700,7 @@ function createConnectedPatternGuidance(first, instances, decorativeAccentClasse
1737
4700
  function connectedPatternSlug(instance) {
1738
4701
  const parts = [
1739
4702
  instance.concept,
4703
+ instance.root?.roleSignature?.role,
1740
4704
  instance.root?.layoutRole,
1741
4705
  instance.root?.tag,
1742
4706
  ...Object.keys(instance.root?.classGroups ?? {}).slice(0, 3),
@@ -1751,6 +4715,22 @@ function connectedPatternSlug(instance) {
1751
4715
  .slice(0, 5)
1752
4716
  .join('.') || instance.concept);
1753
4717
  }
4718
+ function roleSignatureKey(signature) {
4719
+ if (!signature) {
4720
+ return 'role-signature:unspecified';
4721
+ }
4722
+ return [
4723
+ signature.role,
4724
+ signature.roleGroup,
4725
+ signature.scale,
4726
+ signature.density,
4727
+ signature.surface,
4728
+ signature.layout,
4729
+ signature.flags.join(','),
4730
+ ]
4731
+ .filter(Boolean)
4732
+ .join('|');
4733
+ }
1754
4734
  function classSignature(classes) {
1755
4735
  if (!classes.length) {
1756
4736
  return 'no-classes';
@@ -1771,7 +4751,7 @@ function normalizeTagForSignature(element) {
1771
4751
  return element.originalTag.toLowerCase();
1772
4752
  }
1773
4753
  function normalizeChildTagForSignature(child) {
1774
- if (['button', 'input', 'badge', 'heading', 'text', 'link'].includes(child.kind)) {
4754
+ if (['button', 'input', 'badge', 'heading', 'text', 'link', 'icon', 'image', 'divider'].includes(child.kind)) {
1775
4755
  return child.kind;
1776
4756
  }
1777
4757
  return child.originalTag.toLowerCase();
@@ -1840,6 +4820,30 @@ function addStyleUsageEdges(entries, files, edges) {
1840
4820
  }
1841
4821
  }
1842
4822
  }
4823
+ function addDatabaseUsageEdges(entries, files, edges) {
4824
+ const modelEntries = entries.filter((entry) => entry.group === 'model');
4825
+ const modelKeysByName = new Map();
4826
+ for (const entry of modelEntries) {
4827
+ const name = typeof entry.value.name === 'string' ? entry.value.name : '';
4828
+ const tableName = typeof entry.value.tableName === 'string' ? entry.value.tableName : '';
4829
+ if (name) {
4830
+ modelKeysByName.set(name.toLowerCase(), entry.key);
4831
+ }
4832
+ if (tableName) {
4833
+ modelKeysByName.set(tableName.toLowerCase(), entry.key);
4834
+ }
4835
+ }
4836
+ for (const file of files) {
4837
+ for (const access of file.databaseAccesses) {
4838
+ const targetName = access.model ?? access.table;
4839
+ const targetKey = targetName ? modelKeysByName.get(targetName.toLowerCase()) : undefined;
4840
+ if (!targetKey) {
4841
+ continue;
4842
+ }
4843
+ edges.push(toGraphEdge(file.key, targetKey, databaseAccessEdgeType(access), `database.${access.framework}.access`, file, access.confidence));
4844
+ }
4845
+ }
4846
+ }
1843
4847
  function addModelUsageEdges(entries, files, edges) {
1844
4848
  const modelEntries = entries.filter((entry) => entry.group === 'model');
1845
4849
  for (const file of files) {
@@ -1870,6 +4874,50 @@ function addModelUsageEdges(entries, files, edges) {
1870
4874
  }
1871
4875
  }
1872
4876
  }
4877
+ function addDatabaseReviewItems(entries, reviewItems) {
4878
+ const modelEntries = entries.filter((entry) => entry.group === 'model');
4879
+ const byName = (0, utils_1.groupBy)(modelEntries, (entry) => String(entry.value.name ?? entry.key).toLowerCase());
4880
+ for (const [name, candidates] of byName) {
4881
+ if (candidates.length < 2) {
4882
+ continue;
4883
+ }
4884
+ const signatures = (0, utils_1.unique)(candidates.map((candidate) => {
4885
+ const fields = Array.isArray(candidate.value.fields)
4886
+ ? candidate.value.fields.map(String).sort()
4887
+ : [];
4888
+ const provider = String(candidate.value.provider ?? '');
4889
+ const tableName = String(candidate.value.tableName ?? '');
4890
+ return `${provider}:${tableName}:${fields.join('|')}`;
4891
+ }));
4892
+ if (signatures.length <= 1) {
4893
+ continue;
4894
+ }
4895
+ reviewItems.push({
4896
+ id: `review.data.model.${name}.conflict`,
4897
+ type: 'concept_collision',
4898
+ status: 'open',
4899
+ group: 'model',
4900
+ s: `Multiple ${name} data model shapes were found. Confirm the canonical model/table before broad generation.`,
4901
+ candidates: candidates.map((candidate) => candidate.key).slice(0, 40),
4902
+ });
4903
+ }
4904
+ const riskyAccessCandidates = entries
4905
+ .filter((entry) => entry.group === 'database' &&
4906
+ entry.value.kind === 'database-access' &&
4907
+ Array.isArray(entry.value.risk) &&
4908
+ entry.value.risk.includes('manual-review'))
4909
+ .map((entry) => entry.key);
4910
+ if (riskyAccessCandidates.length) {
4911
+ reviewItems.push({
4912
+ id: 'review.data.access.manual-review',
4913
+ type: 'low_confidence',
4914
+ status: 'open',
4915
+ group: 'database',
4916
+ s: 'Raw SQL or high-risk database accesses were found. Review before using them as broad generation patterns.',
4917
+ candidates: riskyAccessCandidates.slice(0, 40),
4918
+ });
4919
+ }
4920
+ }
1873
4921
  function addConceptClusters(entries, usedKeys, nodes, edges, reviewItems) {
1874
4922
  const concepts = [
1875
4923
  {
@@ -2082,9 +5130,25 @@ function detectProjectFacts(files) {
2082
5130
  }
2083
5131
  if (dependencies.has('prisma') ||
2084
5132
  dependencies.has('@prisma/client') ||
2085
- files.some((file) => file.models.length)) {
5133
+ files.some((file) => file.databaseModels.some((model) => model.provider === 'prisma'))) {
2086
5134
  frameworks.add('prisma');
2087
5135
  }
5136
+ if (dependencies.has('drizzle-orm') ||
5137
+ files.some((file) => file.databaseModels.some((model) => model.provider === 'drizzle'))) {
5138
+ frameworks.add('drizzle');
5139
+ }
5140
+ if (dependencies.has('typeorm') ||
5141
+ files.some((file) => file.databaseModels.some((model) => model.provider === 'typeorm'))) {
5142
+ frameworks.add('typeorm');
5143
+ }
5144
+ if (dependencies.has('sequelize') ||
5145
+ files.some((file) => file.databaseModels.some((model) => model.provider === 'sequelize'))) {
5146
+ frameworks.add('sequelize');
5147
+ }
5148
+ if (dependencies.has('mongoose') ||
5149
+ files.some((file) => file.databaseModels.some((model) => model.provider === 'mongoose'))) {
5150
+ frameworks.add('mongoose');
5151
+ }
2088
5152
  if (files.some((file) => file.language === 'css' || file.language === 'scss')) {
2089
5153
  frameworks.add('css');
2090
5154
  }
@@ -2119,7 +5183,9 @@ function detectProjectFacts(files) {
2119
5183
  if (files.some((file) => file.language === 'go')) {
2120
5184
  frameworks.add('go');
2121
5185
  }
2122
- if (files.some((file) => file.language === 'sql')) {
5186
+ if (files.some((file) => file.language === 'sql' ||
5187
+ file.databaseModels.some((model) => model.provider === 'sql') ||
5188
+ file.databaseMigrations.some((migration) => migration.provider === 'sql'))) {
2123
5189
  frameworks.add('sql');
2124
5190
  }
2125
5191
  return {
@@ -2137,8 +5203,10 @@ function detectFrameworkForFile(file) {
2137
5203
  if (file.controllers.length || file.services.length || file.modules.length) {
2138
5204
  return 'nestjs';
2139
5205
  }
2140
- if (file.models.length) {
2141
- return 'prisma';
5206
+ if (file.databaseModels.length || file.databaseEnums.length || file.databaseMigrations.length) {
5207
+ return (file.databaseModels[0]?.provider ??
5208
+ file.databaseEnums[0]?.provider ??
5209
+ file.databaseMigrations[0]?.provider);
2142
5210
  }
2143
5211
  if (file.kind.includes('style') || file.kind.includes('css')) {
2144
5212
  return 'css';