@schemyx/mcp 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -25,6 +25,7 @@ exports.extractModelFields = extractModelFields;
25
25
  exports.extractCssVariables = extractCssVariables;
26
26
  exports.extractCssVariableValues = extractCssVariableValues;
27
27
  exports.extractCssRules = extractCssRules;
28
+ exports.extractCssAtRuleBlocks = extractCssAtRuleBlocks;
28
29
  exports.extractCssClasses = extractCssClasses;
29
30
  exports.extractCssIds = extractCssIds;
30
31
  exports.extractCssVariableReferences = extractCssVariableReferences;
@@ -43,6 +44,9 @@ exports.readBalancedRange = readBalancedRange;
43
44
  exports.skipQuoted = skipQuoted;
44
45
  exports.isInsideQuotedString = isInsideQuotedString;
45
46
  exports.extractClassListsFromStrings = extractClassListsFromStrings;
47
+ exports.readPrimaryClassHelperUsage = readPrimaryClassHelperUsage;
48
+ exports.readClassHelperUsages = readClassHelperUsages;
49
+ exports.parseHelperVariantProps = parseHelperVariantProps;
46
50
  exports.analyzeClassSource = analyzeClassSource;
47
51
  exports.toClassSourceAnalysis = toClassSourceAnalysis;
48
52
  exports.isConditionalClassLiteral = isConditionalClassLiteral;
@@ -86,9 +90,20 @@ exports.isCompositionElement = isCompositionElement;
86
90
  exports.filterClasses = filterClasses;
87
91
  exports.isResponsiveClass = isResponsiveClass;
88
92
  exports.isSpacingClass = isSpacingClass;
93
+ exports.isLayoutClass = isLayoutClass;
89
94
  exports.isSurfaceClass = isSurfaceClass;
90
95
  exports.isDecorativeAccentClass = isDecorativeAccentClass;
91
96
  exports.isTypographyClass = isTypographyClass;
97
+ exports.isTypographyScaleBase = isTypographyScaleBase;
98
+ exports.isLayoutSafetyBase = isLayoutSafetyBase;
99
+ exports.isSizingClass = isSizingClass;
100
+ exports.isVisibilityClass = isVisibilityClass;
101
+ exports.isLineHeightClass = isLineHeightClass;
102
+ exports.isMaxWidthClass = isMaxWidthClass;
103
+ exports.createUiLayoutSafety = createUiLayoutSafety;
104
+ exports.createUiResponsiveProfile = createUiResponsiveProfile;
105
+ exports.createUiScaleProfile = createUiScaleProfile;
106
+ exports.responsivePrefix = responsivePrefix;
92
107
  exports.groupClassExpressionsByTarget = groupClassExpressionsByTarget;
93
108
  exports.buildComponentSlots = buildComponentSlots;
94
109
  exports.createUiCompositionGuidance = createUiCompositionGuidance;
@@ -97,6 +112,28 @@ exports.extractTailwindUtilities = extractTailwindUtilities;
97
112
  exports.extractPackageDependencies = extractPackageDependencies;
98
113
  exports.extractUiElements = extractUiElements;
99
114
  exports.summarizeDirectUiChildren = summarizeDirectUiChildren;
115
+ exports.createUiSemanticProfile = createUiSemanticProfile;
116
+ exports.createUiRoleSignature = createUiRoleSignature;
117
+ exports.exactRoleClassFacts = exactRoleClassFacts;
118
+ exports.createRoleScaleKey = createRoleScaleKey;
119
+ exports.createRoleDensityKey = createRoleDensityKey;
120
+ exports.createRoleSurfaceKey = createRoleSurfaceKey;
121
+ exports.createRoleLayoutKey = createRoleLayoutKey;
122
+ exports.isCompactHeroEyebrow = isCompactHeroEyebrow;
123
+ exports.isOverlayPill = isOverlayPill;
124
+ exports.isHeroHeadingScale = isHeroHeadingScale;
125
+ exports.isOversizedHeadingScale = isOversizedHeadingScale;
126
+ exports.isMetricValueScale = isMetricValueScale;
127
+ exports.isCompactStatValue = isCompactStatValue;
128
+ exports.isLargeMetricValue = isLargeMetricValue;
129
+ exports.isCompactStatCard = isCompactStatCard;
130
+ exports.isInteractiveServiceCard = isInteractiveServiceCard;
131
+ exports.isStaticArticleCard = isStaticArticleCard;
132
+ exports.isMediaPanelCard = isMediaPanelCard;
133
+ exports.isQuietAction = isQuietAction;
134
+ exports.isCompactAction = isCompactAction;
135
+ exports.roleClassSort = roleClassSort;
136
+ exports.deriveUiChildSemanticRole = deriveUiChildSemanticRole;
100
137
  exports.deriveLayoutRole = deriveLayoutRole;
101
138
  exports.isStateClass = isStateClass;
102
139
  exports.extractApiCalls = extractApiCalls;
@@ -150,6 +187,8 @@ exports.styleConfidenceForFile = styleConfidenceForFile;
150
187
  exports.entryMatchesConcept = entryMatchesConcept;
151
188
  exports.splitClassList = splitClassList;
152
189
  exports.classifyUiElement = classifyUiElement;
190
+ exports.looksLikeSurfaceContainer = looksLikeSurfaceContainer;
191
+ exports.looksLikePillBadge = looksLikePillBadge;
153
192
  exports.stripTags = stripTags;
154
193
  const path = require("node:path");
155
194
  const constants_1 = require("./constants");
@@ -562,7 +601,13 @@ function extractCssRules(content, relPath) {
562
601
  return [];
563
602
  }
564
603
  const rules = [];
604
+ const mediaBlocks = extractCssAtRuleBlocks(content, 'media');
605
+ const mediaRanges = mediaBlocks.map((block) => ({ start: block.start, end: block.end }));
565
606
  for (const match of content.matchAll(/([^{}@][^{}]{0,240})\{([^{}]{1,2400})\}/g)) {
607
+ const matchIndex = match.index ?? 0;
608
+ if (mediaRanges.some((range) => matchIndex >= range.start && matchIndex <= range.end)) {
609
+ continue;
610
+ }
566
611
  const selector = match[1].trim().replace(/\s+/g, ' ');
567
612
  if (!selector || selector.includes(';') || selector.startsWith('import')) {
568
613
  continue;
@@ -577,7 +622,59 @@ function extractCssRules(content, relPath) {
577
622
  line: (0, utils_1.lineNumberAt)(content, match.index ?? 0),
578
623
  });
579
624
  }
580
- return (0, utils_1.uniqueBy)(rules, (rule) => `${rule.selector}:${rule.line}`).slice(0, 200);
625
+ for (const block of mediaBlocks) {
626
+ for (const match of block.body.matchAll(/([^{}@][^{}]{0,240})\{([^{}]{1,2400})\}/g)) {
627
+ const selector = match[1].trim().replace(/\s+/g, ' ');
628
+ if (!selector || selector.includes(';') || selector.startsWith('import')) {
629
+ continue;
630
+ }
631
+ const declarations = parseCssDeclarations(match[2]).slice(0, 40);
632
+ if (!declarations.length) {
633
+ continue;
634
+ }
635
+ rules.push({
636
+ selector,
637
+ declarations,
638
+ line: (0, utils_1.lineNumberAt)(content, block.bodyStart + (match.index ?? 0)),
639
+ media: block.prelude,
640
+ });
641
+ }
642
+ }
643
+ return (0, utils_1.uniqueBy)(rules, (rule) => `${rule.selector}:${rule.line}:${rule.media ?? ''}`).slice(0, 320);
644
+ }
645
+ function extractCssAtRuleBlocks(content, atRule) {
646
+ const blocks = [];
647
+ const pattern = new RegExp(`@${atRule}\\s+([^{}]+)\\{`, 'g');
648
+ for (const match of content.matchAll(pattern)) {
649
+ const start = match.index ?? 0;
650
+ const openIndex = start + match[0].length - 1;
651
+ let depth = 0;
652
+ let end = -1;
653
+ for (let cursor = openIndex; cursor < content.length; cursor += 1) {
654
+ const char = content[cursor];
655
+ if (char === '{') {
656
+ depth += 1;
657
+ }
658
+ else if (char === '}') {
659
+ depth -= 1;
660
+ if (depth === 0) {
661
+ end = cursor;
662
+ break;
663
+ }
664
+ }
665
+ }
666
+ if (end <= openIndex) {
667
+ continue;
668
+ }
669
+ blocks.push({
670
+ prelude: match[1].trim().replace(/\s+/g, ' '),
671
+ body: content.slice(openIndex + 1, end),
672
+ start,
673
+ end,
674
+ bodyStart: openIndex + 1,
675
+ });
676
+ }
677
+ return blocks;
581
678
  }
582
679
  function extractCssClasses(content) {
583
680
  return (0, utils_1.unique)(Array.from(content.matchAll(/\.([a-zA-Z][a-zA-Z0-9_-]+)\b/g)).map((match) => match[1])).slice(0, 80);
@@ -617,6 +714,7 @@ function extractClassExpressions(content) {
617
714
  const classValue = match[2] ?? match[1] ?? '';
618
715
  const classes = splitClassList(classValue);
619
716
  if (classes.length) {
717
+ const helperUsage = readPrimaryClassHelperUsage(classValue);
620
718
  const analysis = analyzeClassSource(classValue);
621
719
  expressions.push({
622
720
  kind: 'attribute',
@@ -626,6 +724,10 @@ function extractClassExpressions(content) {
626
724
  conditionalClasses: analysis.conditionalClasses,
627
725
  line: (0, utils_1.lineNumberAt)(content, match.index ?? 0),
628
726
  target: readTagBefore(content, match.index ?? 0),
727
+ ...(helperUsage ? { styleHelper: helperUsage.name } : {}),
728
+ ...(helperUsage && Object.keys(helperUsage.variantProps).length
729
+ ? { styleHelperVariants: helperUsage.variantProps }
730
+ : {}),
629
731
  });
630
732
  }
631
733
  }
@@ -642,7 +744,8 @@ function extractClassExpressions(content) {
642
744
  }
643
745
  const analysis = analyzeClassSource(call);
644
746
  const classes = analysis.classes;
645
- if (classes.length) {
747
+ const helperUsage = readPrimaryClassHelperUsage(call);
748
+ if (classes.length || helperUsage) {
646
749
  expressions.push({
647
750
  kind: helperName === 'cva' ? 'variant-definition' : 'helper',
648
751
  raw: (0, utils_1.truncate)(call, 900),
@@ -651,10 +754,44 @@ function extractClassExpressions(content) {
651
754
  conditionalClasses: analysis.conditionalClasses,
652
755
  line: (0, utils_1.lineNumberAt)(content, match.index ?? 0),
653
756
  target: helperName,
757
+ ...(helperUsage ? { styleHelper: helperUsage.name } : {}),
758
+ ...(helperUsage && Object.keys(helperUsage.variantProps).length
759
+ ? { styleHelperVariants: helperUsage.variantProps }
760
+ : {}),
654
761
  });
655
762
  }
656
763
  }
657
764
  }
765
+ for (const match of content.matchAll(/\b([A-Za-z_$][\w$]*(?:Variants?|ClassNames?|Styles?))\s*\(/g)) {
766
+ const helperName = match[1];
767
+ if (/^(?:cn|clsx|classNames|twMerge|cva)$/i.test(helperName)) {
768
+ continue;
769
+ }
770
+ if (isInsideQuotedString(content, match.index ?? 0)) {
771
+ continue;
772
+ }
773
+ const call = readBalancedCall(content, match.index ?? 0);
774
+ if (!call) {
775
+ continue;
776
+ }
777
+ const helperUsage = readPrimaryClassHelperUsage(call);
778
+ if (!helperUsage) {
779
+ continue;
780
+ }
781
+ expressions.push({
782
+ kind: 'helper',
783
+ raw: (0, utils_1.truncate)(call, 900),
784
+ classes: [],
785
+ defaultClasses: [],
786
+ conditionalClasses: [],
787
+ line: (0, utils_1.lineNumberAt)(content, match.index ?? 0),
788
+ target: helperName,
789
+ styleHelper: helperUsage.name,
790
+ ...(Object.keys(helperUsage.variantProps).length
791
+ ? { styleHelperVariants: helperUsage.variantProps }
792
+ : {}),
793
+ });
794
+ }
658
795
  for (const match of content.matchAll(/(["'`])([^"'`]{5,900})\1/g)) {
659
796
  const classes = splitClassList(match[2]);
660
797
  if (classes.length >= 2 && looksLikeClassList(classes)) {
@@ -920,6 +1057,56 @@ function isInsideQuotedString(content, targetIndex) {
920
1057
  function extractClassListsFromStrings(source) {
921
1058
  return analyzeClassSource(source).classes;
922
1059
  }
1060
+ function readPrimaryClassHelperUsage(source) {
1061
+ const usages = readClassHelperUsages(source);
1062
+ return (usages.find((usage) => /button|cta|action/i.test(usage.name)) ??
1063
+ usages.find((usage) => Object.keys(usage.variantProps).length > 0) ??
1064
+ usages[0]);
1065
+ }
1066
+ function readClassHelperUsages(source) {
1067
+ const usages = [];
1068
+ const helperPattern = /\b([A-Za-z_$][\w$]*(?:Variants?|ClassNames?|Styles?))\s*\(/g;
1069
+ for (const match of source.matchAll(helperPattern)) {
1070
+ const name = match[1];
1071
+ if (/^(?:cn|clsx|classNames|twMerge|cva)$/i.test(name)) {
1072
+ continue;
1073
+ }
1074
+ const call = readBalancedCall(source, match.index ?? 0);
1075
+ if (!call) {
1076
+ continue;
1077
+ }
1078
+ usages.push({
1079
+ name,
1080
+ variantProps: parseHelperVariantProps(call),
1081
+ call: (0, utils_1.truncate)(call, 700),
1082
+ });
1083
+ }
1084
+ return (0, utils_1.uniqueBy)(usages, (usage) => `${usage.name}:${JSON.stringify(usage.variantProps)}`).slice(0, 12);
1085
+ }
1086
+ function parseHelperVariantProps(call) {
1087
+ const props = {};
1088
+ const openIndex = call.indexOf('{');
1089
+ if (openIndex < 0) {
1090
+ return props;
1091
+ }
1092
+ const block = readBalancedBlock(call, openIndex);
1093
+ if (!block) {
1094
+ return props;
1095
+ }
1096
+ for (const entry of parseTopLevelObjectEntries(block)) {
1097
+ const literalValues = readStringLiterals(entry.rawValue)
1098
+ .map((value) => value.trim())
1099
+ .filter(Boolean)
1100
+ .slice(0, 4);
1101
+ const value = literalValues.length > 1
1102
+ ? literalValues.join('|')
1103
+ : (literalValues[0] ?? cleanupBareValue(entry.rawValue));
1104
+ if (value) {
1105
+ props[entry.key] = value;
1106
+ }
1107
+ }
1108
+ return props;
1109
+ }
923
1110
  function analyzeClassSource(source) {
924
1111
  const classes = new Set();
925
1112
  const defaultClasses = new Set();
@@ -992,7 +1179,8 @@ function directClassCandidate(source) {
992
1179
  return undefined;
993
1180
  }
994
1181
  const withoutTemplateExpressions = trimmed.replace(/\$\{[\s\S]*?\}/g, ' ');
995
- if (/[{}"'`,;]/.test(withoutTemplateExpressions)) {
1182
+ const withoutArbitraryValues = withoutTemplateExpressions.replace(/\[[^\]]+\]/g, '[arbitrary]');
1183
+ if (/[{}"'`,;]/.test(withoutArbitraryValues)) {
996
1184
  return undefined;
997
1185
  }
998
1186
  return withoutTemplateExpressions;
@@ -1010,6 +1198,7 @@ function looksLikeClassList(classes) {
1010
1198
  function isLikelyStyleClass(className) {
1011
1199
  const base = className.split(':').pop() ?? className;
1012
1200
  return (/^(?:-?m[trblxy]?|-?p[trblxy]?|flex|grid|block|inline|hidden|items-|justify-|content-|self-|gap-|space-|w-|h-|min-|max-|size-|rounded|border|bg-|from-|via-|to-|text-|font-|leading-|tracking-|shadow|ring|outline|opacity|transition|duration|ease|animate-|hover|focus|active|disabled|dark|sm|md|lg|xl|2xl|container|sr-only|absolute|relative|fixed|sticky|inset-|top-|right-|bottom-|left-|z-|overflow-|object-|aspect-|backdrop-|blur|scale-|translate-|rotate-)/.test(className) ||
1201
+ /^(?:btn(?:-.+)?|card(?:-.+)?|badge(?:-.+)?|form(?:-.+)?|input-group(?:-.+)?|navbar(?:-.+)?|nav(?:-.+)?|row|col(?:-.+)?|container(?:-.+)?|d-(?:sm-|md-|lg-|xl-|xxl-)?|justify-content-|align-items-|align-self-|fw-|fs-|lh-|display-|lead|small|list-group(?:-.+)?|modal(?:-.+)?|alert(?:-.+)?|rounded-(?:pill|circle|\d)|shadow(?:-.+)?|w-\d+|h-\d+|mw-\d+|mh-\d+|min-vh-|min-vw-|vw-|vh-)/.test(className) ||
1013
1202
  /^(?:button|btn|card|panel|surface|badge|pill|chip|nav|field|input|dialog|modal|drawer|sheet|container|wrapper|section|hero|header|footer|sidebar|menu)$/.test(base) ||
1014
1203
  className.includes('[') ||
1015
1204
  className.includes(']'));
@@ -1468,22 +1657,28 @@ function groupTailwindClasses(classes) {
1468
1657
  addClassGroup(groups, 'state', className);
1469
1658
  }
1470
1659
  }
1471
- if (/^(flex|grid|block|inline|hidden|items-|justify-|content-|self-|place-|order-|col-|row-)/.test(base)) {
1660
+ if (/^(flex|grid|block|inline|hidden|items-|justify-|content-|self-|place-|order-|col-|row-|container|d-|justify-content-|align-items-|align-self-)/.test(base)) {
1472
1661
  addClassGroup(groups, 'layout', className);
1473
1662
  }
1474
1663
  else if (/^(?:-?m[trblxy]?|-?p[trblxy]?|gap-|space-|inset-|top-|right-|bottom-|left-)/.test(base)) {
1475
1664
  addClassGroup(groups, 'spacing', className);
1476
1665
  }
1477
- else if (/^(w-|h-|min-|max-|size-|aspect-|basis-|grow|shrink)/.test(base)) {
1666
+ else if (/^(w-|h-|mw-|mh-|vw-|vh-|min-|max-|size-|aspect-|basis-|grow|shrink)/.test(base)) {
1478
1667
  addClassGroup(groups, 'sizing', className);
1479
1668
  }
1669
+ else if (isTypographyScaleBase(base)) {
1670
+ addClassGroup(groups, 'typography', className);
1671
+ }
1672
+ else if (isLayoutSafetyBase(base)) {
1673
+ addClassGroup(groups, 'layout-safety', className);
1674
+ }
1480
1675
  else if (/^(bg-|from-|via-|to-|text-|decoration-|accent-|caret-|fill-|stroke-)/.test(base)) {
1481
1676
  addClassGroup(groups, 'color', className);
1482
1677
  }
1483
1678
  else if (/^(rounded|border|divide-|outline|ring)/.test(base)) {
1484
1679
  addClassGroup(groups, 'border', className);
1485
1680
  }
1486
- else if (/^(font-|text-|leading-|tracking-|line-clamp|list-|whitespace-|break-|truncate)/.test(base)) {
1681
+ else if (/^(list-)/.test(base)) {
1487
1682
  addClassGroup(groups, 'typography', className);
1488
1683
  }
1489
1684
  else if (/^(shadow|opacity|blur|backdrop|mix-|bg-blend|drop-shadow|filter)/.test(base)) {
@@ -1542,12 +1737,17 @@ function filterClasses(classes, predicate) {
1542
1737
  return (0, utils_1.unique)(classes.filter(predicate));
1543
1738
  }
1544
1739
  function isResponsiveClass(className) {
1545
- return /^(?:sm|md|lg|xl|2xl|max-|min-):/.test(className);
1740
+ return (/^(?:sm|md|lg|xl|2xl|min-[^:]+|max-[^:]+):/.test(className) ||
1741
+ /^(?:d|col|offset|order|m[stebxy]?|p[stebxy]?|gap|text|float|flex|justify-content|align-items|align-self)-(?:sm|md|lg|xl|xxl)-/.test(className));
1546
1742
  }
1547
1743
  function isSpacingClass(className) {
1548
1744
  const base = (0, utils_1.classBase)(className);
1549
1745
  return /^(?:-?m[trblxy]?|-?p[trblxy]?|gap-|space-|inset-|top-|right-|bottom-|left-)/.test(base);
1550
1746
  }
1747
+ function isLayoutClass(className) {
1748
+ const base = (0, utils_1.classBase)(className);
1749
+ return /^(?:flex|grid|block|inline|hidden|items-|justify-|content-|self-|place-|order-|col-|row-|container|d-|justify-content-|align-items-|align-self-)/.test(base);
1750
+ }
1551
1751
  function isSurfaceClass(className) {
1552
1752
  const base = (0, utils_1.classBase)(className);
1553
1753
  return (/^(?:bg-|border|rounded|shadow|ring|outline|divide-|opacity|backdrop|blur|drop-shadow)/.test(base) ||
@@ -1562,7 +1762,109 @@ function isDecorativeAccentClass(className) {
1562
1762
  }
1563
1763
  function isTypographyClass(className) {
1564
1764
  const base = (0, utils_1.classBase)(className);
1565
- return /^(?:text-|font-|leading-|tracking-|line-clamp|whitespace-|break-|truncate|max-w-|text-balance)/.test(base);
1765
+ return isTypographyScaleBase(base);
1766
+ }
1767
+ function isTypographyScaleBase(base) {
1768
+ return (/^(?:font-|leading-|tracking-|line-clamp|whitespace-|break-|truncate|text-balance|text-pretty|text-nowrap|text-wrap)/.test(base) ||
1769
+ /^(?:fs-|fw-|lh-|display-|lead|small|text-uppercase|text-lowercase|text-capitalize)/.test(base) ||
1770
+ /^text-(?:xs|sm|base|lg|xl|[2-9]xl|\[[^\]]+\])/.test(base) ||
1771
+ /^max-w-\[(?:\d+(?:\.\d+)?)?ch\]/.test(base));
1772
+ }
1773
+ function isLayoutSafetyBase(base) {
1774
+ return /^(?:overflow-|min-w-|min-h-|max-w-|max-h-|w-fit|h-fit|w-max|h-max|w-min|h-min|basis-|grow|shrink|flex-1|flex-none|self-|whitespace-|break-|truncate|line-clamp|text-ellipsis|text-nowrap|text-balance|text-pretty)/.test(base);
1775
+ }
1776
+ function isSizingClass(className) {
1777
+ const base = (0, utils_1.classBase)(className);
1778
+ return /^(?:w-|h-|mw-|mh-|vw-|vh-|min-|max-|size-|aspect-|basis-|grow|shrink|flex-1|flex-none)/.test(base);
1779
+ }
1780
+ function isVisibilityClass(className) {
1781
+ const base = (0, utils_1.classBase)(className);
1782
+ return /^(?:hidden|block|inline|inline-block|flex|inline-flex|grid|inline-grid|table|contents)$/.test(base);
1783
+ }
1784
+ function isLineHeightClass(className) {
1785
+ return (0, utils_1.classBase)(className).startsWith('leading-');
1786
+ }
1787
+ function isMaxWidthClass(className) {
1788
+ return (0, utils_1.classBase)(className).startsWith('max-w-');
1789
+ }
1790
+ function createUiLayoutSafety(classes) {
1791
+ const wrapClasses = filterClasses(classes, (className) => /^(?:whitespace-|break-|truncate|line-clamp|text-ellipsis|text-nowrap|text-balance|text-pretty|text-wrap)/.test((0, utils_1.classBase)(className))).slice(0, 40);
1792
+ const overflowClasses = filterClasses(classes, (className) => /^(?:overflow-|line-clamp|truncate|text-ellipsis)/.test((0, utils_1.classBase)(className))).slice(0, 40);
1793
+ const minSizeClasses = filterClasses(classes, (className) => /^(?:min-w-|min-h-)/.test((0, utils_1.classBase)(className))).slice(0, 40);
1794
+ const maxSizeClasses = filterClasses(classes, (className) => /^(?:max-w-|max-h-)/.test((0, utils_1.classBase)(className))).slice(0, 40);
1795
+ const fitClasses = filterClasses(classes, (className) => /^(?:w-fit|h-fit|w-max|h-max|w-min|h-min|basis-|self-)/.test((0, utils_1.classBase)(className))).slice(0, 40);
1796
+ const flexBehaviorClasses = filterClasses(classes, (className) => /^(?:grow|shrink|flex-1|flex-none|basis-)/.test((0, utils_1.classBase)(className))).slice(0, 40);
1797
+ const layoutSafetyClasses = (0, utils_1.unique)([
1798
+ ...wrapClasses,
1799
+ ...overflowClasses,
1800
+ ...minSizeClasses,
1801
+ ...maxSizeClasses,
1802
+ ...fitClasses,
1803
+ ...flexBehaviorClasses,
1804
+ ]).slice(0, 80);
1805
+ if (!layoutSafetyClasses.length) {
1806
+ return undefined;
1807
+ }
1808
+ return {
1809
+ wrapClasses,
1810
+ overflowClasses,
1811
+ minSizeClasses,
1812
+ maxSizeClasses,
1813
+ fitClasses,
1814
+ flexBehaviorClasses,
1815
+ layoutSafetyClasses,
1816
+ };
1817
+ }
1818
+ function createUiResponsiveProfile(classes) {
1819
+ const responsiveClasses = filterClasses(classes, isResponsiveClass).slice(0, 120);
1820
+ if (!responsiveClasses.length) {
1821
+ return undefined;
1822
+ }
1823
+ const breakpoints = {};
1824
+ for (const className of responsiveClasses) {
1825
+ const prefix = responsivePrefix(className);
1826
+ if (!prefix) {
1827
+ continue;
1828
+ }
1829
+ breakpoints[prefix] = (0, utils_1.unique)([...(breakpoints[prefix] ?? []), className]).slice(0, 60);
1830
+ }
1831
+ return {
1832
+ breakpoints,
1833
+ layoutClasses: filterClasses(responsiveClasses, isLayoutClass).slice(0, 60),
1834
+ typographyClasses: filterClasses(responsiveClasses, isTypographyClass).slice(0, 60),
1835
+ spacingClasses: filterClasses(responsiveClasses, isSpacingClass).slice(0, 60),
1836
+ sizingClasses: filterClasses(responsiveClasses, isSizingClass).slice(0, 60),
1837
+ visibilityClasses: filterClasses(responsiveClasses, isVisibilityClass).slice(0, 60),
1838
+ stateClasses: filterClasses(responsiveClasses, isStateClass).slice(0, 60),
1839
+ };
1840
+ }
1841
+ function createUiScaleProfile(classes) {
1842
+ const typographyClasses = filterClasses(classes, isTypographyClass).slice(0, 80);
1843
+ const spacingClasses = filterClasses(classes, isSpacingClass).slice(0, 80);
1844
+ const sizingClasses = filterClasses(classes, isSizingClass).slice(0, 80);
1845
+ const gapClasses = filterClasses(classes, (className) => /^(?:gap-|space-)/.test((0, utils_1.classBase)(className))).slice(0, 60);
1846
+ const maxWidthClasses = filterClasses(classes, isMaxWidthClass).slice(0, 60);
1847
+ const lineHeightClasses = filterClasses(classes, isLineHeightClass).slice(0, 60);
1848
+ if (!typographyClasses.length &&
1849
+ !spacingClasses.length &&
1850
+ !sizingClasses.length &&
1851
+ !gapClasses.length &&
1852
+ !maxWidthClasses.length &&
1853
+ !lineHeightClasses.length) {
1854
+ return undefined;
1855
+ }
1856
+ return {
1857
+ typographyClasses,
1858
+ spacingClasses,
1859
+ sizingClasses,
1860
+ gapClasses,
1861
+ maxWidthClasses,
1862
+ lineHeightClasses,
1863
+ };
1864
+ }
1865
+ function responsivePrefix(className) {
1866
+ return (className.split(':').find((part) => /^(?:sm|md|lg|xl|2xl|min-|max-)/.test(part)) ??
1867
+ 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)-/)?.[1]);
1566
1868
  }
1567
1869
  function groupClassExpressionsByTarget(expressions) {
1568
1870
  const grouped = {};
@@ -1630,6 +1932,15 @@ function createUiCompositionGuidance(file) {
1630
1932
  if (classes.has('section-frame') || classes.has('context-surface')) {
1631
1933
  guidance.push('Use section-frame/context-surface only for framed technical panels, not every repeated card.');
1632
1934
  }
1935
+ if (classes.has('section-frame')) {
1936
+ guidance.push('section-frame owns the clipped section-frame::before grid overlay for that surface; do not treat it as a simple border/background token.');
1937
+ }
1938
+ if (classes.has('section-frame') && classes.has('context-surface')) {
1939
+ guidance.push('Keep section-frame and context-surface paired for technical surfaces when child content must sit above the parent grid overlay.');
1940
+ }
1941
+ if (classes.has('sm:section-frame') || classes.has('bg-transparent')) {
1942
+ guidance.push('Preserve transparent mobile surfaces and only add framed behavior at the same responsive breakpoint as the source.');
1943
+ }
1633
1944
  if (file.classReferences.some(isDecorativeAccentClass)) {
1634
1945
  guidance.push('Treat brand-spectrum-card and purple glow shadow classes as accent/highlight-only; do not use them for default repeated cards unless the source element is explicitly current, highlighted, or featured.');
1635
1946
  }
@@ -1647,7 +1958,7 @@ function createUiPatternGuidance(kind, decorativeAccentClasses) {
1647
1958
  }
1648
1959
  function extractTailwindUtilities(classReferences) {
1649
1960
  return classReferences
1650
- .filter((className) => /^(?:-?m[trblxy]?|-?p[trblxy]?|flex|grid|block|inline|hidden|items-|justify-|gap-|space-|w-|h-|min-|max-|rounded|border|bg-|text-|font-|leading-|tracking-|shadow|ring|opacity|transition|duration|ease|hover:|focus:|active:|disabled:|dark:|sm:|md:|lg:|xl:|2xl:|container|sr-only)/.test(className))
1961
+ .filter((className) => /^(?:-?m[trblxy]?|-?p[trblxy]?|flex|grid|block|inline|hidden|items-|justify-|gap-|space-|w-|h-|min-|max-|size-|basis-|grow|shrink|aspect-|overflow-|object-|whitespace-|break-|truncate|line-clamp|text-balance|text-pretty|text-nowrap|rounded|border|bg-|text-|font-|leading-|tracking-|shadow|ring|opacity|transition|duration|ease|hover:|focus:|active:|disabled:|dark:|sm:|md:|lg:|xl:|2xl:|min-[^:]+:|max-[^:]+:|container|sr-only)/.test(className))
1651
1962
  .slice(0, 160);
1652
1963
  }
1653
1964
  function extractPackageDependencies(relPath, content) {
@@ -1701,10 +2012,17 @@ function extractUiElements(content, relPath) {
1701
2012
  const label = extractElementLabel(content, tagInfo);
1702
2013
  const classSource = readClassSource(attrs);
1703
2014
  const classAnalysis = classSource ? analyzeClassSource(classSource) : undefined;
2015
+ const styleHelper = classSource ? readPrimaryClassHelperUsage(classSource) : undefined;
1704
2016
  const classes = classAnalysis?.classes ?? [];
1705
2017
  const role = (0, utils_1.readAttr)(attrs, 'role');
1706
- const props = readUiProps(attrs);
1707
- const variants = readVariantProps(props);
2018
+ const props = {
2019
+ ...readUiProps(attrs),
2020
+ ...(styleHelper ? { styleHelper: styleHelper.name } : {}),
2021
+ };
2022
+ const variants = {
2023
+ ...readVariantProps(props),
2024
+ ...(styleHelper?.variantProps ?? {}),
2025
+ };
1708
2026
  const kind = classifyUiElement(tagInfo.originalTag, attrs, classes, role);
1709
2027
  const parentTagInfo = tagInfo.parentIndex === undefined ? undefined : tags[tagInfo.parentIndex];
1710
2028
  const parentClassSource = parentTagInfo ? readClassSource(parentTagInfo.attrs) : undefined;
@@ -1716,7 +2034,21 @@ function extractUiElements(content, relPath) {
1716
2034
  const layoutRole = deriveLayoutRole(tagInfo.originalTag, kind, classes, label);
1717
2035
  const responsiveClasses = classes.filter(isResponsiveClass);
1718
2036
  const stateClasses = classes.filter(isStateClass);
2037
+ const layoutSafety = createUiLayoutSafety(classes);
2038
+ const responsiveProfile = createUiResponsiveProfile(classes);
2039
+ const scaleProfile = createUiScaleProfile(classes);
1719
2040
  const childSummary = summarizeDirectUiChildren(content, tags, index);
2041
+ const semanticProfile = createUiSemanticProfile(kind, tagInfo.originalTag, classes, childSummary);
2042
+ const roleSignature = createUiRoleSignature({
2043
+ kind,
2044
+ tag: tagInfo.originalTag,
2045
+ classes,
2046
+ children: childSummary,
2047
+ label,
2048
+ layoutRole,
2049
+ parentKind,
2050
+ parentClasses,
2051
+ });
1720
2052
  if (kind === 'unknown') {
1721
2053
  continue;
1722
2054
  }
@@ -1748,6 +2080,11 @@ function extractUiElements(content, relPath) {
1748
2080
  classGroups: groupTailwindClasses(classes),
1749
2081
  ...(responsiveClasses.length ? { responsiveClasses } : {}),
1750
2082
  ...(stateClasses.length ? { stateClasses } : {}),
2083
+ ...(layoutSafety ? { layoutSafety } : {}),
2084
+ ...(responsiveProfile ? { responsiveProfile } : {}),
2085
+ ...(scaleProfile ? { scaleProfile } : {}),
2086
+ ...(semanticProfile ? { semanticProfile } : {}),
2087
+ ...(roleSignature ? { roleSignature } : {}),
1751
2088
  ...(childSummary.length ? { childSummary } : {}),
1752
2089
  ...((0, utils_1.readAttr)(attrs, 'id') ? { id: (0, utils_1.readAttr)(attrs, 'id') } : {}),
1753
2090
  ...(role ? { role } : {}),
@@ -1757,6 +2094,10 @@ function extractUiElements(content, relPath) {
1757
2094
  ...((0, utils_1.readAttr)(attrs, 'method') ? { method: (0, utils_1.readAttr)(attrs, 'method')?.toUpperCase() } : {}),
1758
2095
  ...(Object.keys(props).length ? { props } : {}),
1759
2096
  ...(Object.keys(variants).length ? { variants } : {}),
2097
+ ...(styleHelper ? { styleHelper: styleHelper.name } : {}),
2098
+ ...(styleHelper && Object.keys(styleHelper.variantProps).length
2099
+ ? { styleHelperVariants: styleHelper.variantProps }
2100
+ : {}),
1760
2101
  evidence: `<${tagInfo.originalTag}${classes.length ? ` class="${classes.slice(0, 6).join(' ')}"` : ''}>`,
1761
2102
  });
1762
2103
  }
@@ -1769,11 +2110,31 @@ function summarizeDirectUiChildren(content, tags, parentIndex) {
1769
2110
  .map(({ tagInfo, index }) => {
1770
2111
  const classSource = readClassSource(tagInfo.attrs);
1771
2112
  const classAnalysis = classSource ? analyzeClassSource(classSource) : undefined;
2113
+ const styleHelper = classSource ? readPrimaryClassHelperUsage(classSource) : undefined;
1772
2114
  const classes = classAnalysis?.classes ?? [];
1773
2115
  const role = (0, utils_1.readAttr)(tagInfo.attrs, 'role');
1774
2116
  const kind = classifyUiElement(tagInfo.originalTag, tagInfo.attrs, classes, role);
1775
2117
  const label = extractElementLabel(content, tagInfo);
1776
2118
  const layoutRole = deriveLayoutRole(tagInfo.originalTag, kind, classes, label);
2119
+ const layoutSafety = createUiLayoutSafety(classes);
2120
+ const responsiveProfile = createUiResponsiveProfile(classes);
2121
+ const scaleProfile = createUiScaleProfile(classes);
2122
+ const semanticRole = deriveUiChildSemanticRole(tagInfo.originalTag, kind, classes, label);
2123
+ const roleSignature = createUiRoleSignature({
2124
+ kind,
2125
+ tag: tagInfo.originalTag,
2126
+ classes,
2127
+ label,
2128
+ layoutRole,
2129
+ });
2130
+ const props = {
2131
+ ...readUiProps(tagInfo.attrs),
2132
+ ...(styleHelper ? { styleHelper: styleHelper.name } : {}),
2133
+ };
2134
+ const variants = {
2135
+ ...readVariantProps(props),
2136
+ ...(styleHelper?.variantProps ?? {}),
2137
+ };
1777
2138
  return {
1778
2139
  index,
1779
2140
  kind,
@@ -1787,12 +2148,496 @@ function summarizeDirectUiChildren(content, tags, parentIndex) {
1787
2148
  ...(classAnalysis?.conditionalClasses.length
1788
2149
  ? { conditionalClasses: classAnalysis.conditionalClasses.slice(0, 32) }
1789
2150
  : {}),
2151
+ ...(layoutSafety ? { layoutSafety } : {}),
2152
+ ...(responsiveProfile ? { responsiveProfile } : {}),
2153
+ ...(scaleProfile ? { scaleProfile } : {}),
2154
+ ...(semanticRole ? { semanticRole } : {}),
2155
+ ...(roleSignature ? { roleSignature } : {}),
2156
+ ...(Object.keys(props).length ? { props } : {}),
2157
+ ...(Object.keys(variants).length ? { variants } : {}),
2158
+ ...(styleHelper ? { styleHelper: styleHelper.name } : {}),
2159
+ ...(styleHelper && Object.keys(styleHelper.variantProps).length
2160
+ ? { styleHelperVariants: styleHelper.variantProps }
2161
+ : {}),
1790
2162
  ...(label ? { label } : {}),
1791
2163
  };
1792
2164
  })
1793
- .filter((child) => child.kind !== 'unknown' || child.classes.length || child.label)
2165
+ .filter((child) => child.kind !== 'unknown' ||
2166
+ child.classes.length ||
2167
+ child.label ||
2168
+ Boolean(child.semanticRole))
1794
2169
  .slice(0, 16);
1795
2170
  }
2171
+ function createUiSemanticProfile(kind, tag, classes, children) {
2172
+ const roles = (0, utils_1.unique)(children.flatMap((child) => child.semanticRole ?? [])).slice(0, 24);
2173
+ const textStyleSignatures = (0, utils_1.unique)(children
2174
+ .filter((child) => ['text', 'heading', 'badge'].includes(child.kind) || child.semanticRole)
2175
+ .map((child) => JSON.stringify(groupTailwindClasses(child.defaultClasses?.length ? child.defaultClasses : child.classes)))).filter(Boolean);
2176
+ const hasIcon = roles.includes('icon') || roles.includes('component-icon') || roles.includes('image');
2177
+ const hasDivider = roles.includes('divider');
2178
+ const hasImage = roles.includes('image');
2179
+ const hasMultipleTextStyles = textStyleSignatures.length > 1;
2180
+ const isCompound = children.length > 1 &&
2181
+ (hasIcon ||
2182
+ hasDivider ||
2183
+ hasMultipleTextStyles ||
2184
+ roles.includes('primary-text') ||
2185
+ roles.includes('secondary-text'));
2186
+ if (!isCompound && !roles.length) {
2187
+ return undefined;
2188
+ }
2189
+ return {
2190
+ roles,
2191
+ isCompound,
2192
+ hasIcon,
2193
+ hasDivider,
2194
+ hasImage,
2195
+ hasMultipleTextStyles,
2196
+ textStyleCount: textStyleSignatures.length,
2197
+ childCount: children.length,
2198
+ };
2199
+ }
2200
+ function createUiRoleSignature(input) {
2201
+ const { kind, tag, classes } = input;
2202
+ const children = input.children ?? [];
2203
+ const tagName = tag.toLowerCase();
2204
+ const bases = classes.map(utils_1.classBase);
2205
+ const baseSet = new Set(bases);
2206
+ const childRoles = (0, utils_1.unique)(children.flatMap((child) => [child.semanticRole, child.roleSignature?.role].filter((value) => typeof value === 'string' && Boolean(value)))).slice(0, 24);
2207
+ const childKinds = (0, utils_1.unique)(children.map((child) => child.kind).filter(Boolean)).slice(0, 24);
2208
+ const hasIcon = childRoles.some((role) => /(?:icon|image|logo|mark)/.test(role));
2209
+ const hasDivider = childRoles.includes('divider');
2210
+ const hasMultipleTextSegments = childRoles.filter((role) => /(?:primary|secondary|text|segment)/.test(role)).length > 1;
2211
+ const exactClassFacts = exactRoleClassFacts(classes);
2212
+ const flags = new Set();
2213
+ const layout = createRoleLayoutKey(kind, tagName, classes, children, input.parentClasses ?? []);
2214
+ const scale = createRoleScaleKey(classes, children);
2215
+ const density = createRoleDensityKey(classes, children);
2216
+ const surface = createRoleSurfaceKey(classes);
2217
+ let roleGroup = kind;
2218
+ let role = `${kind}-${input.layoutRole ?? 'element'}`;
2219
+ if (input.layoutRole) {
2220
+ flags.add(`layout-role:${input.layoutRole}`);
2221
+ }
2222
+ if (hasIcon)
2223
+ flags.add('has-icon');
2224
+ if (hasDivider)
2225
+ flags.add('has-divider');
2226
+ if (hasMultipleTextSegments)
2227
+ flags.add('multiple-text-segments');
2228
+ if (baseSet.has('uppercase'))
2229
+ flags.add('uppercase');
2230
+ if (classes.some((className) => className.includes('hover:')))
2231
+ flags.add('has-hover-state');
2232
+ if (baseSet.has('group'))
2233
+ flags.add('group-context');
2234
+ if (classes.some((className) => (0, utils_1.classBase)(className).startsWith('backdrop-blur'))) {
2235
+ flags.add('backdrop-blur');
2236
+ }
2237
+ if (kind === 'section') {
2238
+ roleGroup = 'section';
2239
+ if (baseSet.has('min-h-screen')) {
2240
+ role = 'hero-section-fullscreen';
2241
+ flags.add('full-screen-hero');
2242
+ }
2243
+ else if (layout === 'split-media-layout') {
2244
+ role = 'split-media-section';
2245
+ flags.add('split-media');
2246
+ }
2247
+ else if (layout === 'card-grid-layout') {
2248
+ role = 'card-grid-section';
2249
+ flags.add('card-grid');
2250
+ }
2251
+ else {
2252
+ role = 'section-shell';
2253
+ }
2254
+ }
2255
+ if (kind === 'layout') {
2256
+ roleGroup = 'layout';
2257
+ if (layout === 'split-media-layout') {
2258
+ role = 'split-media-layout';
2259
+ flags.add('split-media');
2260
+ }
2261
+ else if (layout === 'card-grid-layout') {
2262
+ role = 'card-grid-layout';
2263
+ flags.add('card-grid');
2264
+ }
2265
+ else if (layout === 'hero-content-layout') {
2266
+ role = 'hero-content-layout';
2267
+ flags.add('hero-content');
2268
+ }
2269
+ else {
2270
+ role = `layout-${layout}`;
2271
+ }
2272
+ }
2273
+ if (kind === 'heading') {
2274
+ roleGroup = tagName === 'h1' ? 'hero-heading' : 'heading';
2275
+ if (tagName === 'h1') {
2276
+ if (isOversizedHeadingScale(classes)) {
2277
+ role = 'hero-heading-oversized';
2278
+ flags.add('oversized-heading');
2279
+ }
2280
+ else if (isHeroHeadingScale(classes)) {
2281
+ role = 'hero-heading-standard';
2282
+ flags.add('hero-heading');
2283
+ }
2284
+ else {
2285
+ role = 'page-heading';
2286
+ }
2287
+ }
2288
+ else if (isMetricValueScale(classes)) {
2289
+ role = 'metric-heading';
2290
+ flags.add('metric-value');
2291
+ }
2292
+ else {
2293
+ role = 'section-heading';
2294
+ }
2295
+ }
2296
+ if (kind === 'badge') {
2297
+ roleGroup = 'badge';
2298
+ if (isOverlayPill(classes)) {
2299
+ role = 'overlay-disclaimer-pill';
2300
+ flags.add('overlay-pill');
2301
+ }
2302
+ else if (hasIcon || hasDivider || hasMultipleTextSegments) {
2303
+ role = 'brand-lockup-badge';
2304
+ flags.add('compound-badge');
2305
+ }
2306
+ else if (isCompactHeroEyebrow(classes)) {
2307
+ role = 'hero-eyebrow-compact';
2308
+ flags.add('compact-eyebrow');
2309
+ }
2310
+ else {
2311
+ role = 'badge-pill';
2312
+ }
2313
+ }
2314
+ if (kind === 'card') {
2315
+ roleGroup = 'surface';
2316
+ if (isCompactStatCard(classes, childRoles)) {
2317
+ role = 'hero-stat-card-compact';
2318
+ flags.add('compact-stat-card');
2319
+ }
2320
+ else if (isInteractiveServiceCard(classes)) {
2321
+ role = 'interactive-service-card';
2322
+ flags.add('interactive-card');
2323
+ }
2324
+ else if (isStaticArticleCard(classes)) {
2325
+ role = 'static-article-card';
2326
+ flags.add('static-article-surface');
2327
+ }
2328
+ else if (isMediaPanelCard(classes, childKinds, childRoles)) {
2329
+ role = 'media-panel-card';
2330
+ flags.add('media-panel');
2331
+ }
2332
+ else {
2333
+ role = 'surface-card';
2334
+ }
2335
+ }
2336
+ if (kind === 'text') {
2337
+ roleGroup = 'text';
2338
+ if (isCompactStatValue(classes)) {
2339
+ role = 'compact-stat-value';
2340
+ flags.add('compact-stat-value');
2341
+ }
2342
+ else if (isLargeMetricValue(classes)) {
2343
+ role = 'large-metric-value';
2344
+ flags.add('large-metric-value');
2345
+ }
2346
+ else if (isOverlayPill(classes)) {
2347
+ role = 'overlay-disclaimer-text';
2348
+ flags.add('overlay-pill');
2349
+ }
2350
+ else {
2351
+ role = 'body-copy';
2352
+ }
2353
+ }
2354
+ if (kind === 'button') {
2355
+ roleGroup = 'action';
2356
+ if (isQuietAction(classes)) {
2357
+ role = 'quiet-action';
2358
+ flags.add('low-emphasis-action');
2359
+ }
2360
+ else if (isCompactAction(classes)) {
2361
+ role = 'compact-action-button';
2362
+ flags.add('compact-action');
2363
+ }
2364
+ else {
2365
+ role = 'action-button';
2366
+ }
2367
+ }
2368
+ if (!classes.length && !childRoles.length && role === `${kind}-${input.layoutRole ?? 'element'}`) {
2369
+ return undefined;
2370
+ }
2371
+ return {
2372
+ role,
2373
+ roleGroup,
2374
+ scale,
2375
+ density,
2376
+ surface,
2377
+ layout,
2378
+ flags: Array.from(flags).sort(),
2379
+ exactClassFacts,
2380
+ ...(childRoles.length ? { childRoles } : {}),
2381
+ ...(childKinds.length ? { childKinds } : {}),
2382
+ };
2383
+ }
2384
+ function exactRoleClassFacts(classes) {
2385
+ return (0, utils_1.unique)(classes.filter((className) => {
2386
+ const base = (0, utils_1.classBase)(className);
2387
+ return (isTypographyClass(className) ||
2388
+ isSpacingClass(className) ||
2389
+ isSizingClass(className) ||
2390
+ isLayoutClass(className) ||
2391
+ isStateClass(className) ||
2392
+ /^(?:bg-|text-|border|rounded|shadow|backdrop|opacity|ring|outline|z-|absolute|relative|fixed|sticky|top-|right-|bottom-|left-|overflow-|object-|aspect-|group|transition|duration|ease)/.test(base));
2393
+ })).slice(0, 120);
2394
+ }
2395
+ function createRoleScaleKey(classes, children = []) {
2396
+ const scaleClasses = (0, utils_1.unique)([
2397
+ ...classes,
2398
+ ...children.flatMap((child) => child.classes.slice(0, 24)),
2399
+ ].filter((className) => {
2400
+ const base = (0, utils_1.classBase)(className);
2401
+ return (isTypographyClass(className) ||
2402
+ isSizingClass(className) ||
2403
+ /^(?:max-w-|min-w-|leading-|tracking-|text-|font-|h-|w-|min-h-|max-h-)/.test(base));
2404
+ }))
2405
+ .sort(roleClassSort)
2406
+ .slice(0, 40);
2407
+ return scaleClasses.length ? scaleClasses.join(' ') : 'scale-unspecified';
2408
+ }
2409
+ function createRoleDensityKey(classes, children = []) {
2410
+ const densityClasses = (0, utils_1.unique)([
2411
+ ...classes,
2412
+ ...children.flatMap((child) => child.classes.slice(0, 16)),
2413
+ ].filter((className) => {
2414
+ const base = (0, utils_1.classBase)(className);
2415
+ return /^(?:p[trblxy]?|m[trblxy]?|gap-|space-|h-|min-h-|max-h-|w-|min-w-|max-w-)/.test(base);
2416
+ }))
2417
+ .sort(roleClassSort)
2418
+ .slice(0, 40);
2419
+ if (!densityClasses.length) {
2420
+ return 'density-unspecified';
2421
+ }
2422
+ if (densityClasses.some((className) => /(?:^|:)py-(?:1|1\.5|2)$/.test(className)) ||
2423
+ densityClasses.some((className) => /(?:^|:)h-(?:8|9|10|11|12)$/.test(className))) {
2424
+ return `compact:${densityClasses.join(' ')}`;
2425
+ }
2426
+ if (densityClasses.some((className) => /(?:^|:)p-(?:8|10|12|16|20|24)$/.test(className)) ||
2427
+ densityClasses.some((className) => /(?:^|:)py-(?:8|10|12|16|20|24)$/.test(className))) {
2428
+ return `spacious:${densityClasses.join(' ')}`;
2429
+ }
2430
+ return densityClasses.join(' ');
2431
+ }
2432
+ function createRoleSurfaceKey(classes) {
2433
+ const surfaceClasses = (0, utils_1.unique)(classes.filter((className) => {
2434
+ const base = (0, utils_1.classBase)(className);
2435
+ return /^(?:bg-|border|rounded|shadow|ring|outline|backdrop|opacity|group|transition|hover:border|hover:bg)/.test(base);
2436
+ }))
2437
+ .sort(roleClassSort)
2438
+ .slice(0, 40);
2439
+ return surfaceClasses.length ? surfaceClasses.join(' ') : 'surface-unspecified';
2440
+ }
2441
+ function createRoleLayoutKey(kind, tagName, classes, children = [], parentClasses = []) {
2442
+ const bases = classes.map(utils_1.classBase);
2443
+ const baseSet = new Set(bases);
2444
+ const childKinds = new Set(children.map((child) => child.kind));
2445
+ const childRoles = new Set(children.flatMap((child) => [child.semanticRole, child.roleSignature?.role].filter(Boolean)));
2446
+ const childClassBases = children.flatMap((child) => child.classes.map(utils_1.classBase));
2447
+ const hasMediaChild = childKinds.has('image') ||
2448
+ childRoles.has('image') ||
2449
+ childRoles.has('media-panel-card') ||
2450
+ childClassBases.some((base) => /^(?:aspect-|object-|h-\[|min-h-\[)/.test(base));
2451
+ const hasCardChildren = children.filter((child) => child.kind === 'card').length >= 2;
2452
+ const hasTextChildren = children.some((child) => ['heading', 'text', 'badge'].includes(child.kind) ||
2453
+ (child.kind === 'layout' &&
2454
+ child.classes.some((className) => /^(?:max-w-|space-y-|prose|flex|grid)/.test((0, utils_1.classBase)(className)))));
2455
+ const parentBaseSet = new Set(parentClasses.map(utils_1.classBase));
2456
+ if (baseSet.has('min-h-screen')) {
2457
+ return 'full-screen-hero-layout';
2458
+ }
2459
+ if (baseSet.has('absolute') &&
2460
+ (baseSet.has('bottom-3') || baseSet.has('bottom-4')) &&
2461
+ (baseSet.has('right-3') || baseSet.has('right-4'))) {
2462
+ return 'absolute-bottom-right-overlay';
2463
+ }
2464
+ if ((baseSet.has('grid') || parentBaseSet.has('grid')) &&
2465
+ hasMediaChild &&
2466
+ hasTextChildren &&
2467
+ classes.some((className) => /(?:^|:)grid-cols-|(?:^|:)lg:grid-cols-/.test(className))) {
2468
+ return 'split-media-layout';
2469
+ }
2470
+ if ((baseSet.has('grid') || parentBaseSet.has('grid')) && hasCardChildren) {
2471
+ return 'card-grid-layout';
2472
+ }
2473
+ if (tagName === 'main' || (kind === 'layout' && baseSet.has('mx-auto'))) {
2474
+ return 'page-content-layout';
2475
+ }
2476
+ if (baseSet.has('flex') || baseSet.has('inline-flex')) {
2477
+ return 'flex-layout';
2478
+ }
2479
+ if (baseSet.has('grid')) {
2480
+ return 'grid-layout';
2481
+ }
2482
+ return 'layout-unspecified';
2483
+ }
2484
+ function isCompactHeroEyebrow(classes) {
2485
+ const bases = classes.map(utils_1.classBase);
2486
+ const baseSet = new Set(bases);
2487
+ return (baseSet.has('inline-flex') &&
2488
+ baseSet.has('rounded-full') &&
2489
+ baseSet.has('uppercase') &&
2490
+ classes.some((className) => /(?:^|:)text-xs$/.test(className)) &&
2491
+ classes.some((className) => /(?:^|:)sm:text-sm$/.test(className)) &&
2492
+ bases.some((base) => /^py-(?:1\.5|2)$/.test(base)) &&
2493
+ bases.some((base) => /^px-(?:3|4|5)$/.test(base)));
2494
+ }
2495
+ function isOverlayPill(classes) {
2496
+ const bases = classes.map(utils_1.classBase);
2497
+ const baseSet = new Set(bases);
2498
+ return (baseSet.has('absolute') &&
2499
+ baseSet.has('rounded-full') &&
2500
+ bases.some((base) => /^bottom-/.test(base)) &&
2501
+ bases.some((base) => /^right-/.test(base)) &&
2502
+ (bases.some((base) => /^text-\[(?:10|11)px\]$/.test(base)) ||
2503
+ classes.some((className) => /(?:^|:)sm:text-\[(?:10|11)px\]$/.test(className))));
2504
+ }
2505
+ function isHeroHeadingScale(classes) {
2506
+ const classSet = new Set(classes);
2507
+ const bases = classes.map(utils_1.classBase);
2508
+ return (classSet.has('text-4xl') &&
2509
+ classSet.has('sm:text-6xl') &&
2510
+ classSet.has('lg:text-7xl') &&
2511
+ bases.some((base) => /^leading-/.test(base)));
2512
+ }
2513
+ function isOversizedHeadingScale(classes) {
2514
+ const classSet = new Set(classes);
2515
+ return (classSet.has('text-6xl') ||
2516
+ classSet.has('sm:text-7xl') ||
2517
+ classSet.has('lg:text-8xl') ||
2518
+ classSet.has('xl:text-9xl') ||
2519
+ classes.some((className) => /(?:^|:)text-\[(?:8|9|10)\dpx\]/.test(className)));
2520
+ }
2521
+ function isMetricValueScale(classes) {
2522
+ return classes.some((className) => /(?:^|:)text-(?:lg|xl|2xl|3xl|4xl)$/.test(className));
2523
+ }
2524
+ function isCompactStatValue(classes) {
2525
+ const classSet = new Set(classes);
2526
+ const bases = classes.map(utils_1.classBase);
2527
+ return (classSet.has('text-lg') &&
2528
+ bases.some((base) => /^font-(?:bold|extrabold|black)$/.test(base)) &&
2529
+ bases.some((base) => /^text-\[|^text-(?:brand|accent|yellow|amber|gold)/.test(base)));
2530
+ }
2531
+ function isLargeMetricValue(classes) {
2532
+ const classSet = new Set(classes);
2533
+ return (classSet.has('text-2xl') ||
2534
+ classSet.has('sm:text-3xl') ||
2535
+ classSet.has('text-3xl') ||
2536
+ classSet.has('text-4xl'));
2537
+ }
2538
+ function isCompactStatCard(classes, childRoles) {
2539
+ const bases = classes.map(utils_1.classBase);
2540
+ const baseSet = new Set(bases);
2541
+ return (childRoles.includes('compact-stat-value') ||
2542
+ (baseSet.has('rounded-lg') &&
2543
+ baseSet.has('border') &&
2544
+ bases.some((base) => /^p-(?:4|5)$/.test(base)) &&
2545
+ !baseSet.has('group')));
2546
+ }
2547
+ function isInteractiveServiceCard(classes) {
2548
+ const bases = classes.map(utils_1.classBase);
2549
+ const baseSet = new Set(bases);
2550
+ return (baseSet.has('group') ||
2551
+ classes.some((className) => className.includes('hover:')) ||
2552
+ (baseSet.has('transition') && bases.includes('shadow-2xl')));
2553
+ }
2554
+ function isStaticArticleCard(classes) {
2555
+ const bases = classes.map(utils_1.classBase);
2556
+ const baseSet = new Set(bases);
2557
+ return (baseSet.has('flex') &&
2558
+ baseSet.has('h-full') &&
2559
+ baseSet.has('flex-col') &&
2560
+ baseSet.has('rounded-lg') &&
2561
+ baseSet.has('border') &&
2562
+ bases.includes('p-6') &&
2563
+ !baseSet.has('group') &&
2564
+ !classes.some((className) => className.includes('hover:')));
2565
+ }
2566
+ function isMediaPanelCard(classes, childKinds, childRoles) {
2567
+ const bases = classes.map(utils_1.classBase);
2568
+ return (childKinds.includes('image') ||
2569
+ childRoles.includes('image') ||
2570
+ bases.some((base) => /^(?:aspect-|object-|overflow-hidden)$/.test(base)));
2571
+ }
2572
+ function isQuietAction(classes) {
2573
+ const bases = classes.map(utils_1.classBase);
2574
+ return (bases.includes('bg-transparent') &&
2575
+ (bases.includes('border-transparent') ||
2576
+ bases.some((base) => /^text-(?:muted|secondary|slate|gray)/.test(base))));
2577
+ }
2578
+ function isCompactAction(classes) {
2579
+ const bases = classes.map(utils_1.classBase);
2580
+ return (bases.some((base) => /^h-(?:9|10|11|12)$/.test(base)) &&
2581
+ bases.some((base) => /^px-(?:4|5)$/.test(base)) &&
2582
+ classes.some((className) => /(?:^|:)text-sm$/.test(className)));
2583
+ }
2584
+ function roleClassSort(a, b) {
2585
+ const priority = (value) => {
2586
+ const base = (0, utils_1.classBase)(value);
2587
+ if (/^(?:max-w-|min-w-|w-|h-|min-h-|max-h-)/.test(base))
2588
+ return 0;
2589
+ if (/^(?:text-|font-|leading-|tracking-)/.test(base))
2590
+ return 1;
2591
+ if (/^(?:p|m|gap|space)-/.test(base))
2592
+ return 2;
2593
+ if (/^(?:bg-|border|rounded|shadow|backdrop)/.test(base))
2594
+ return 3;
2595
+ return 4;
2596
+ };
2597
+ return priority(a) - priority(b) || a.localeCompare(b);
2598
+ }
2599
+ function deriveUiChildSemanticRole(tag, kind, classes, label) {
2600
+ const tagName = tag.toLowerCase();
2601
+ const tagBase = tag.split('.')[0].split(':').pop() ?? tag;
2602
+ const lowerBase = tagBase.toLowerCase();
2603
+ const classBases = classes.map(utils_1.classBase);
2604
+ const classHaystack = [tagName, lowerBase, ...classBases].join(' ').toLowerCase();
2605
+ const labelHaystack = (label ?? '').toLowerCase();
2606
+ if (kind === 'icon') {
2607
+ return 'icon';
2608
+ }
2609
+ if (kind === 'image') {
2610
+ return 'image';
2611
+ }
2612
+ if (kind === 'divider') {
2613
+ return 'divider';
2614
+ }
2615
+ if (tagName === 'img' ||
2616
+ tagName === 'picture' ||
2617
+ tagName === 'svg' ||
2618
+ /(?:icon|logo|mark|glyph|avatar|image|img)$/.test(lowerBase) ||
2619
+ /\b(?:icon|logo|mark|glyph|avatar)\b/.test(classHaystack)) {
2620
+ return tagName === 'img' || tagName === 'picture' ? 'image' : 'icon';
2621
+ }
2622
+ if (classes.some((className) => /^(?:w-px|h-px|border-l|border-r|border-t|border-b|divide-x|divide-y)$/.test((0, utils_1.classBase)(className))) ||
2623
+ /\b(?:divider|separator|sep|rule)\b/.test(classHaystack)) {
2624
+ return 'divider';
2625
+ }
2626
+ if (kind === 'text' || kind === 'heading' || kind === 'badge') {
2627
+ if (classes.some((className) => /^(?:text-muted|text-muted-foreground|text-secondary|opacity-|font-normal)/.test((0, utils_1.classBase)(className)))) {
2628
+ return 'secondary-text';
2629
+ }
2630
+ if (classes.some((className) => /^(?:font-bold|font-semibold|font-black|text-primary|text-brand|text-accent|text-yellow|text-amber|text-gold|text-\[)/.test((0, utils_1.classBase)(className))) ||
2631
+ (labelHaystack.length > 0 && labelHaystack.length <= 32)) {
2632
+ return 'primary-text';
2633
+ }
2634
+ return 'text-segment';
2635
+ }
2636
+ if (/^[A-Z]/.test(tagBase) && !classes.length && !label) {
2637
+ return 'component-icon';
2638
+ }
2639
+ return undefined;
2640
+ }
1796
2641
  function deriveLayoutRole(tag, kind, classes, label) {
1797
2642
  const tagName = tag.toLowerCase();
1798
2643
  const tagBase = tag.split('.')[0].split(':').pop() ?? tag;
@@ -1857,7 +2702,8 @@ function deriveLayoutRole(tag, kind, classes, label) {
1857
2702
  return undefined;
1858
2703
  }
1859
2704
  function isStateClass(className) {
1860
- return /^(?:hover|focus|active|disabled|visited|dark|group|peer|aria|data):/.test(className);
2705
+ const prefixes = className.split(':').slice(0, -1);
2706
+ return prefixes.some((prefix) => /^(?:hover|focus|active|disabled|visited|dark|group|peer|aria|data)/.test(prefix));
1861
2707
  }
1862
2708
  function extractApiCalls(content) {
1863
2709
  const calls = new Set();
@@ -2314,7 +3160,8 @@ function isTemplateFile(relPath) {
2314
3160
  relPath.endsWith('.blade.php'));
2315
3161
  }
2316
3162
  function isTestFile(relPath) {
2317
- return (/(?:^|\/)(?:__tests__|tests?|spec)\//i.test(relPath) ||
3163
+ return (/(?:^|\/)(?:app|pages|src\/app|src\/pages)\/test\//i.test(relPath) ||
3164
+ /(?:^|\/)(?:__tests__|tests?|spec)\//i.test(relPath) ||
2318
3165
  /\.(test|spec)\.[A-Za-z0-9]+$/.test(relPath));
2319
3166
  }
2320
3167
  function isConfigFile(relPath) {
@@ -2527,6 +3374,8 @@ function classifyUiElement(tag, attrs, classes, role) {
2527
3374
  const tagBase = originalTag.split('.')[0].split(':').pop() ?? originalTag;
2528
3375
  const lowerBase = tagBase.toLowerCase();
2529
3376
  const classHaystack = [tagName, role ?? '', ...classes].join(' ').toLowerCase();
3377
+ const classHelper = readPrimaryClassHelperUsage(readClassSource(attrs) ?? '');
3378
+ const classHelperName = classHelper?.name.toLowerCase() ?? '';
2530
3379
  const propHaystack = [
2531
3380
  tagName,
2532
3381
  role ?? '',
@@ -2541,12 +3390,30 @@ function classifyUiElement(tag, attrs, classes, role) {
2541
3390
  if (/^h[1-6]$/.test(tagName) || /^(heading|title|headline)$/i.test(tagBase)) {
2542
3391
  return 'heading';
2543
3392
  }
3393
+ if (tagName === 'img' ||
3394
+ tagName === 'picture' ||
3395
+ /^(image|img|avatar)$/i.test(tagBase) ||
3396
+ /(?:image|img|avatar)$/.test(lowerBase)) {
3397
+ return 'image';
3398
+ }
3399
+ if (tagName === 'svg' ||
3400
+ /^(icon|logo|mark|glyph)$/i.test(tagBase) ||
3401
+ /(?:icon|logo|mark|glyph)$/.test(lowerBase) ||
3402
+ /\b(icon|logo|mark|glyph)\b/.test(classHaystack)) {
3403
+ return 'icon';
3404
+ }
3405
+ if (/^(divider|separator|rule)$/i.test(tagBase) ||
3406
+ /\b(divider|separator|rule)\b/.test(classHaystack) ||
3407
+ classes.some((className) => /^(?:w-px|h-px|border-l|border-r|border-t|border-b|divide-x|divide-y)$/.test((0, utils_1.classBase)(className)))) {
3408
+ return 'divider';
3409
+ }
2544
3410
  if (['input', 'textarea', 'select', 'option', 'label', 'fieldset'].includes(tagName) ||
2545
3411
  /^(input|textarea|select|field|fieldlabel|label|checkbox|switch|slider|radio|combobox)$/i.test(tagBase) ||
2546
3412
  /\b(field|input|textarea|select|checkbox|switch|slider|radio)\b/.test(classHaystack)) {
2547
3413
  return 'input';
2548
3414
  }
2549
- if (tagName === 'button' ||
3415
+ if (/(?:button|cta|action)/.test(classHelperName) ||
3416
+ tagName === 'button' ||
2550
3417
  role === 'button' ||
2551
3418
  /^(button|iconbutton|submitbutton|cta)$/i.test(tagBase) ||
2552
3419
  /\b(btn|button|cta|submit)\b/.test(classHaystack) ||
@@ -2574,12 +3441,15 @@ function classifyUiElement(tag, attrs, classes, role) {
2574
3441
  /\b(dialog|modal|popover|drawer|sheet)\b/.test(classHaystack)) {
2575
3442
  return 'dialog';
2576
3443
  }
2577
- if (/^(badge|chip|pill|tag|status|eyebrow)$/i.test(tagBase) ||
2578
- /\b(badge|chip|pill|tag|status|eyebrow)\b/.test(classHaystack)) {
3444
+ if (/(?:badge|chip|pill|status)/.test(classHelperName) ||
3445
+ /^(badge|chip|pill|tag|status|eyebrow)$/i.test(tagBase) ||
3446
+ /\b(badge|chip|pill|tag|status|eyebrow)\b/.test(classHaystack) ||
3447
+ looksLikePillBadge(tagName, classes)) {
2579
3448
  return 'badge';
2580
3449
  }
2581
3450
  if (/^(card|panel|tile|surface|sectionframe|frame|callout)$/i.test(tagBase) ||
2582
- /\b(card|panel|tile|surface|section-frame|frame|callout)\b/.test(classHaystack)) {
3451
+ /\b(card|panel|tile|section-frame|context-surface|frame|callout)\b/.test(classHaystack) ||
3452
+ looksLikeSurfaceContainer(tagName, tagBase, classes)) {
2583
3453
  return 'card';
2584
3454
  }
2585
3455
  if (tagName === 'section' || lowerBase === 'section') {
@@ -2594,6 +3464,49 @@ function classifyUiElement(tag, attrs, classes, role) {
2594
3464
  }
2595
3465
  return 'unknown';
2596
3466
  }
3467
+ function looksLikeSurfaceContainer(tagName, tagBase, classes) {
3468
+ const bases = classes.map(utils_1.classBase);
3469
+ const isContainerTag = ['div', 'article', 'section', 'aside', 'figure', 'li', 'main', 'header', 'footer'].includes(tagName) ||
3470
+ /^[A-Z]/.test(tagBase);
3471
+ const isTinyIndicator = tagName === 'span' &&
3472
+ bases.includes('rounded-full') &&
3473
+ bases.some((base) => /^(?:h|size)-(?:px|0\.5|1|1\.5|2|2\.5|3)$/.test(base)) &&
3474
+ bases.some((base) => /^(?:w|size)-(?:px|0\.5|1|1\.5|2|2\.5|3)$/.test(base));
3475
+ if (!isContainerTag || isTinyIndicator) {
3476
+ return false;
3477
+ }
3478
+ const hasSurfaceBackground = bases.some((base) => base === 'bg-card' ||
3479
+ base === 'section-frame' ||
3480
+ base === 'context-surface' ||
3481
+ /^bg-/.test(base) ||
3482
+ /^bg-\[var\(--(?:surface|card)-/.test(base));
3483
+ const hasContainerShape = bases.some((base) => /^(?:border|rounded|shadow|ring|outline|overflow-hidden)/.test(base));
3484
+ const hasContainerStructure = bases.some((base) => /^(?:p[trblxy]?|gap-|space-|min-h-|max-w-|w-full|flex|grid|relative)/.test(base));
3485
+ return hasSurfaceBackground && hasContainerShape && hasContainerStructure;
3486
+ }
3487
+ function looksLikePillBadge(tagName, classes) {
3488
+ const bases = classes.map(utils_1.classBase);
3489
+ const baseSet = new Set(bases);
3490
+ const allowedTag = ['div', 'span', 'small', 'p', 'li'].includes(tagName);
3491
+ const hasPillShape = baseSet.has('rounded-full') || bases.some((base) => /^rounded-\[/.test(base));
3492
+ const hasCompactSpacing = bases.some((base) => /^px-(?:2|2\.5|3|4|5|6)$/.test(base)) &&
3493
+ bases.some((base) => /^py-(?:1|1\.5|2|2\.5|3)$/.test(base));
3494
+ const hasBadgeText = baseSet.has('uppercase') ||
3495
+ classes.some((className) => /(?:^|:)text-(?:xs|sm|\[(?:10|11|12|13|14)px\])$/.test(className));
3496
+ const hasCompoundPillLayout = bases.some((base) => /^gap-/.test(base));
3497
+ const hasBadgeSurface = bases.some((base) => /^bg-/.test(base)) ||
3498
+ bases.some((base) => /^border/.test(base)) ||
3499
+ bases.some((base) => /^shadow/.test(base));
3500
+ const hasInlineLayout = baseSet.has('inline-flex') ||
3501
+ baseSet.has('inline-grid') ||
3502
+ (baseSet.has('absolute') && bases.some((base) => /^bottom-|^top-/.test(base)));
3503
+ return (allowedTag &&
3504
+ hasPillShape &&
3505
+ hasCompactSpacing &&
3506
+ (hasBadgeText || hasCompoundPillLayout) &&
3507
+ hasBadgeSurface &&
3508
+ hasInlineLayout);
3509
+ }
2597
3510
  function stripTags(value) {
2598
3511
  return value.replace(/<[^>]+>/g, ' ').replace(/\{[{%][\s\S]*?[}%]\}/g, ' ');
2599
3512
  }