uilint-core 0.1.2 → 0.1.5

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.
@@ -23,6 +23,7 @@ Focus on:
23
23
  2. Inconsistent font sizes or weights
24
24
  3. Spacing values that don't follow a consistent scale (e.g., 4px base unit)
25
25
  4. Mixed border-radius values
26
+ 5. If utility/Tailwind classes are present in the summary, treat them as the styling surface area and flag inconsistent utility usage (e.g., mixing px-4 and px-5 for the same component type)
26
27
 
27
28
  Be concise and actionable. Only report significant inconsistencies.
28
29
 
@@ -50,6 +51,7 @@ Generate a style guide with these sections:
50
51
  2. Typography - Font families, sizes, and weights
51
52
  3. Spacing - Base unit and common spacing values
52
53
  4. Components - Common component patterns
54
+ 5. Tailwind (if utility classes are present) - list commonly used utilities and any relevant theme tokens
53
55
 
54
56
  Use this format:
55
57
  # UI Style Guide
@@ -91,45 +93,6 @@ ${query}
91
93
 
92
94
  Provide a clear, concise answer based on the style guide above. If the style guide doesn't contain the information needed, say so and suggest what might be missing.`;
93
95
  }
94
- function buildValidationPrompt(code, styleGuide) {
95
- const guideSection = styleGuide ? `## Style Guide
96
- ${styleGuide}
97
-
98
- ` : "## No Style Guide\nValidate for general best practices.\n\n";
99
- return `You are a UI code validator. Check the following code against the style guide and best practices.
100
-
101
- ${guideSection}
102
-
103
- ## Code to Validate
104
- \`\`\`tsx
105
- ${code}
106
- \`\`\`
107
-
108
- Respond with a JSON object containing:
109
- - valid: boolean (true if no errors found)
110
- - issues: array of issues, each with:
111
- - type: "error" or "warning"
112
- - message: description of the issue
113
- - suggestion: how to fix it
114
-
115
- Focus on:
116
- 1. Colors not in the style guide
117
- 2. Spacing values not following the design system
118
- 3. Typography inconsistencies
119
- 4. Accessibility issues (missing alt text, etc.)
120
-
121
- Example response:
122
- {
123
- "valid": false,
124
- "issues": [
125
- {
126
- "type": "warning",
127
- "message": "Color #FF0000 is not in the style guide",
128
- "suggestion": "Use the error color #EF4444 instead"
129
- }
130
- ]
131
- }`;
132
- }
133
96
 
134
97
  // src/ollama/client.ts
135
98
  var DEFAULT_BASE_URL = "http://localhost:11434";
@@ -191,19 +154,6 @@ var OllamaClient = class {
191
154
  return "Failed to query style guide. Please try again.";
192
155
  }
193
156
  }
194
- /**
195
- * Validates code against the style guide
196
- */
197
- async validateCode(code, styleGuide) {
198
- const prompt = buildValidationPrompt(code, styleGuide);
199
- try {
200
- const response = await this.generate(prompt);
201
- return this.parseValidationResponse(response);
202
- } catch (error) {
203
- console.error("[UILint] Validation failed:", error);
204
- return { valid: true, issues: [] };
205
- }
206
- }
207
157
  /**
208
158
  * Core generate method that calls Ollama API
209
159
  */
@@ -243,21 +193,6 @@ var OllamaClient = class {
243
193
  return [];
244
194
  }
245
195
  }
246
- /**
247
- * Parses validation result from LLM response
248
- */
249
- parseValidationResponse(response) {
250
- try {
251
- const parsed = JSON.parse(response);
252
- return {
253
- valid: parsed.valid ?? true,
254
- issues: parsed.issues || []
255
- };
256
- } catch {
257
- console.warn("[UILint] Failed to parse validation response");
258
- return { valid: true, issues: [] };
259
- }
260
- }
261
196
  /**
262
197
  * Checks if Ollama is available
263
198
  */
@@ -293,6 +228,65 @@ function getOllamaClient(options) {
293
228
  return defaultClient;
294
229
  }
295
230
 
231
+ // src/tailwind/class-tokens.ts
232
+ function extractClassTokensFromHtml(html, options = {}) {
233
+ const { maxTokens = 2e4 } = options;
234
+ const utilities = /* @__PURE__ */ new Map();
235
+ const variants = /* @__PURE__ */ new Map();
236
+ if (!html) return { utilities, variants };
237
+ const attrPattern = /\b(?:class|className)\s*=\s*["']([^"']+)["']/g;
238
+ let tokenBudget = maxTokens;
239
+ let match;
240
+ while ((match = attrPattern.exec(html)) && tokenBudget > 0) {
241
+ const raw = match[1];
242
+ if (!raw) continue;
243
+ const tokens = raw.split(/\s+/g).filter(Boolean);
244
+ for (const token of tokens) {
245
+ if (tokenBudget-- <= 0) break;
246
+ const { base, variantList } = splitVariants(token);
247
+ const normalizedBase = normalizeUtility(base);
248
+ if (!normalizedBase) continue;
249
+ increment(utilities, normalizedBase);
250
+ for (const v of variantList) increment(variants, v);
251
+ }
252
+ }
253
+ return { utilities, variants };
254
+ }
255
+ function topEntries(map, limit) {
256
+ return [...map.entries()].sort((a, b) => b[1] - a[1]).slice(0, limit).map(([token, count]) => ({ token, count }));
257
+ }
258
+ function increment(map, key) {
259
+ map.set(key, (map.get(key) || 0) + 1);
260
+ }
261
+ function normalizeUtility(token) {
262
+ const t = token.trim();
263
+ if (!t) return null;
264
+ const noImportant = t.startsWith("!") ? t.slice(1) : t;
265
+ if (!noImportant || noImportant === "{" || noImportant === "}") return null;
266
+ return noImportant;
267
+ }
268
+ function splitVariants(token) {
269
+ const parts = [];
270
+ let buf = "";
271
+ let bracketDepth = 0;
272
+ for (let i = 0; i < token.length; i++) {
273
+ const ch = token[i];
274
+ if (ch === "[") bracketDepth++;
275
+ if (ch === "]" && bracketDepth > 0) bracketDepth--;
276
+ if (ch === ":" && bracketDepth === 0) {
277
+ parts.push(buf);
278
+ buf = "";
279
+ continue;
280
+ }
281
+ buf += ch;
282
+ }
283
+ parts.push(buf);
284
+ if (parts.length <= 1) return { base: token, variantList: [] };
285
+ const base = parts[parts.length - 1] || "";
286
+ const variantList = parts.slice(0, -1).map((v) => v.trim()).filter(Boolean);
287
+ return { base, variantList };
288
+ }
289
+
296
290
  // src/scanner/style-extractor.ts
297
291
  function extractStyles(root, getComputedStyle) {
298
292
  const styles = {
@@ -372,7 +366,10 @@ function deserializeStyles(serialized) {
372
366
  borderRadius: new Map(Object.entries(serialized.borderRadius))
373
367
  };
374
368
  }
375
- function createStyleSummary(styles) {
369
+ function createStyleSummary(styles, options = {}) {
370
+ return createStyleSummaryWithOptions(styles, options);
371
+ }
372
+ function createStyleSummaryWithOptions(styles, options = {}) {
376
373
  const lines = [];
377
374
  lines.push("## Detected Styles Summary\n");
378
375
  lines.push("### Colors");
@@ -406,7 +403,9 @@ function createStyleSummary(styles) {
406
403
  });
407
404
  lines.push("");
408
405
  lines.push("### Spacing Values");
409
- const sortedSpacing = [...styles.spacing.entries()].sort((a, b) => b[1] - a[1]);
406
+ const sortedSpacing = [...styles.spacing.entries()].sort(
407
+ (a, b) => b[1] - a[1]
408
+ );
410
409
  sortedSpacing.slice(0, 15).forEach(([value, count]) => {
411
410
  lines.push(`- ${value}: ${count} occurrences`);
412
411
  });
@@ -418,6 +417,38 @@ function createStyleSummary(styles) {
418
417
  sortedBorderRadius.forEach(([value, count]) => {
419
418
  lines.push(`- ${value}: ${count} occurrences`);
420
419
  });
420
+ if (options.html) {
421
+ const tokens = extractClassTokensFromHtml(options.html);
422
+ const topUtilities = topEntries(tokens.utilities, 40);
423
+ const topVariants = topEntries(tokens.variants, 15);
424
+ lines.push("");
425
+ lines.push("### Utility Classes (from markup)");
426
+ if (topUtilities.length === 0) {
427
+ lines.push("- (none detected)");
428
+ } else {
429
+ topUtilities.forEach(({ token, count }) => {
430
+ lines.push(`- ${token}: ${count} occurrences`);
431
+ });
432
+ }
433
+ if (topVariants.length > 0) {
434
+ lines.push("");
435
+ lines.push("### Common Variants");
436
+ topVariants.forEach(({ token, count }) => {
437
+ lines.push(`- ${token}: ${count} occurrences`);
438
+ });
439
+ }
440
+ }
441
+ if (options.tailwindTheme) {
442
+ const tt = options.tailwindTheme;
443
+ lines.push("");
444
+ lines.push("### Tailwind Theme Tokens (from config)");
445
+ lines.push(`- configPath: ${tt.configPath}`);
446
+ lines.push(`- colors: ${tt.colors.length}`);
447
+ lines.push(`- spacingKeys: ${tt.spacingKeys.length}`);
448
+ lines.push(`- borderRadiusKeys: ${tt.borderRadiusKeys.length}`);
449
+ lines.push(`- fontFamilyKeys: ${tt.fontFamilyKeys.length}`);
450
+ lines.push(`- fontSizeKeys: ${tt.fontSizeKeys.length}`);
451
+ }
421
452
  return lines.join("\n");
422
453
  }
423
454
  function truncateHTML(html, maxLength = 5e4) {
@@ -671,9 +702,87 @@ function extractStyleValues(content) {
671
702
  }
672
703
  return result;
673
704
  }
705
+ function extractTailwindAllowlist(content) {
706
+ const empty = {
707
+ allowAnyColor: false,
708
+ allowStandardSpacing: false,
709
+ allowedTailwindColors: /* @__PURE__ */ new Set(),
710
+ allowedUtilities: /* @__PURE__ */ new Set(),
711
+ allowedSpacingKeys: /* @__PURE__ */ new Set(),
712
+ allowedBorderRadiusKeys: /* @__PURE__ */ new Set(),
713
+ allowedFontSizeKeys: /* @__PURE__ */ new Set(),
714
+ allowedFontFamilyKeys: /* @__PURE__ */ new Set()
715
+ };
716
+ const sections = parseStyleGuideSections(content);
717
+ const tailwindSection = sections["tailwind"] ?? // defensive: some styleguides use different casing/spacing
718
+ sections["tailwind utilities"] ?? "";
719
+ if (!tailwindSection) return empty;
720
+ const parsed = tryParseFirstJsonCodeBlock(tailwindSection);
721
+ if (parsed && typeof parsed === "object") {
722
+ const allowAnyColor = Boolean(parsed.allowAnyColor);
723
+ const allowStandardSpacing = Boolean(parsed.allowStandardSpacing);
724
+ const allowedUtilitiesArr = Array.isArray(parsed.allowedUtilities) ? parsed.allowedUtilities.filter(
725
+ (u) => typeof u === "string"
726
+ ) : [];
727
+ const themeTokens = parsed.themeTokens ?? {};
728
+ const themeColors = Array.isArray(themeTokens.colors) ? themeTokens.colors.filter((c) => typeof c === "string") : [];
729
+ const spacingKeys = Array.isArray(themeTokens.spacingKeys) ? themeTokens.spacingKeys.filter((k) => typeof k === "string") : [];
730
+ const borderRadiusKeys = Array.isArray(themeTokens.borderRadiusKeys) ? themeTokens.borderRadiusKeys.filter((k) => typeof k === "string") : [];
731
+ const fontFamilyKeys = Array.isArray(themeTokens.fontFamilyKeys) ? themeTokens.fontFamilyKeys.filter((k) => typeof k === "string") : [];
732
+ const fontSizeKeys = Array.isArray(themeTokens.fontSizeKeys) ? themeTokens.fontSizeKeys.filter((k) => typeof k === "string") : [];
733
+ const allowedTailwindColors = /* @__PURE__ */ new Set();
734
+ for (const c of themeColors) {
735
+ const raw = c.trim();
736
+ if (!raw) continue;
737
+ if (raw.toLowerCase().startsWith("tailwind:")) {
738
+ allowedTailwindColors.add(raw.toLowerCase());
739
+ continue;
740
+ }
741
+ const m = raw.match(/^([a-zA-Z]+)-(\d{2,3})$/);
742
+ if (m) {
743
+ allowedTailwindColors.add(`tailwind:${m[1].toLowerCase()}-${m[2]}`);
744
+ }
745
+ }
746
+ return {
747
+ allowAnyColor,
748
+ allowStandardSpacing,
749
+ allowedTailwindColors,
750
+ allowedUtilities: new Set(allowedUtilitiesArr.map((s) => s.trim()).filter(Boolean)),
751
+ allowedSpacingKeys: new Set(spacingKeys.map((s) => s.trim()).filter(Boolean)),
752
+ allowedBorderRadiusKeys: new Set(borderRadiusKeys.map((s) => s.trim()).filter(Boolean)),
753
+ allowedFontSizeKeys: new Set(fontSizeKeys.map((s) => s.trim()).filter(Boolean)),
754
+ allowedFontFamilyKeys: new Set(fontFamilyKeys.map((s) => s.trim()).filter(Boolean))
755
+ };
756
+ }
757
+ const backticked = [];
758
+ for (const m of tailwindSection.matchAll(/`([^`]+)`/g)) {
759
+ backticked.push(m[1]);
760
+ }
761
+ return {
762
+ ...empty,
763
+ allowedUtilities: new Set(
764
+ backticked.flatMap((s) => s.split(/[,\s]+/g)).map((s) => s.trim()).filter(Boolean)
765
+ )
766
+ };
767
+ }
768
+ function tryParseFirstJsonCodeBlock(section) {
769
+ const jsonBlocks = [...section.matchAll(/```json\s*([\s\S]*?)```/gi)];
770
+ const anyBlocks = [...section.matchAll(/```\s*([\s\S]*?)```/g)];
771
+ const candidates = (jsonBlocks.length ? jsonBlocks : anyBlocks).map((m) => m[1]);
772
+ for (const raw of candidates) {
773
+ const trimmed = raw.trim();
774
+ if (!trimmed) continue;
775
+ try {
776
+ return JSON.parse(trimmed);
777
+ } catch {
778
+ continue;
779
+ }
780
+ }
781
+ return null;
782
+ }
674
783
 
675
784
  // src/styleguide/generator.ts
676
- function generateStyleGuideFromStyles(styles) {
785
+ function generateStyleGuideFromStyles(styles, options = {}) {
677
786
  const lines = [];
678
787
  lines.push("# UI Style Guide");
679
788
  lines.push("");
@@ -681,9 +790,18 @@ function generateStyleGuideFromStyles(styles) {
681
790
  "> Auto-generated by UILint. Edit this file to define your design system."
682
791
  );
683
792
  lines.push("");
793
+ const html = options.html || "";
794
+ const mergedColors = new Map(styles.colors);
795
+ if (html) {
796
+ for (const m of html.matchAll(/#[A-Fa-f0-9]{6}\b/g)) {
797
+ const hex = (m[0] || "").toUpperCase();
798
+ if (!hex) continue;
799
+ mergedColors.set(hex, (mergedColors.get(hex) || 0) + 1);
800
+ }
801
+ }
684
802
  lines.push("## Colors");
685
803
  lines.push("");
686
- const sortedColors = [...styles.colors.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10);
804
+ const sortedColors = [...mergedColors.entries()].sort((a, b) => b[1] - a[1]);
687
805
  if (sortedColors.length > 0) {
688
806
  const colorNames = [
689
807
  "Primary",
@@ -763,6 +881,71 @@ function generateStyleGuideFromStyles(styles) {
763
881
  lines.push("- **Cards**: Define card styles here");
764
882
  lines.push("- **Inputs**: Define input styles here");
765
883
  lines.push("");
884
+ const classTokens = html ? extractClassTokensFromHtml(html) : null;
885
+ const topUtilities = classTokens ? topEntries(classTokens.utilities, 50) : [];
886
+ const topVariants = classTokens ? topEntries(classTokens.variants, 20) : [];
887
+ const allUtilities = classTokens ? [...classTokens.utilities.entries()].sort((a, b) => b[1] - a[1] ? b[1] - a[1] : a[0].localeCompare(b[0])).map(([token]) => token) : [];
888
+ const theme = options.tailwindTheme;
889
+ const themeColors = theme?.colors || [];
890
+ const themeSpacingKeys = theme?.spacingKeys || [];
891
+ const themeBorderRadiusKeys = theme?.borderRadiusKeys || [];
892
+ const themeFontFamilyKeys = theme?.fontFamilyKeys || [];
893
+ const themeFontSizeKeys = theme?.fontSizeKeys || [];
894
+ const hasTailwindSection = topUtilities.length > 0 || themeColors.length > 0 || themeSpacingKeys.length > 0 || themeBorderRadiusKeys.length > 0 || themeFontFamilyKeys.length > 0 || themeFontSizeKeys.length > 0;
895
+ if (hasTailwindSection) {
896
+ lines.push("## Tailwind");
897
+ lines.push("");
898
+ lines.push(
899
+ "> Optional. Captures Tailwind/utility class conventions for validation and consistency."
900
+ );
901
+ lines.push("");
902
+ if (topUtilities.length > 0) {
903
+ const utilityList = topUtilities.slice(0, 25).map((u) => `\`${u.token}\``).join(", ");
904
+ lines.push(`- **Observed utilities (top)**: ${utilityList}`);
905
+ } else {
906
+ lines.push("- **Observed utilities (top)**: (none detected)");
907
+ }
908
+ if (topVariants.length > 0) {
909
+ const variantList = topVariants.slice(0, 12).map((v) => `\`${v.token}\``).join(", ");
910
+ lines.push(`- **Common variants**: ${variantList}`);
911
+ }
912
+ if (theme) {
913
+ lines.push(`- **Config path**: \`${theme.configPath}\``);
914
+ if (themeColors.length > 0) {
915
+ lines.push(
916
+ `- **Theme colors (tokens)**: ${themeColors.slice(0, 25).map((t) => `\`${t}\``).join(", ")}${themeColors.length > 25 ? ", ..." : ""}`
917
+ );
918
+ } else {
919
+ lines.push("- **Theme colors (tokens)**: (none specified in config)");
920
+ }
921
+ if (themeSpacingKeys.length > 0) {
922
+ lines.push(
923
+ `- **Theme spacing keys**: ${themeSpacingKeys.slice(0, 25).map((k) => `\`${k}\``).join(", ")}${themeSpacingKeys.length > 25 ? ", ..." : ""}`
924
+ );
925
+ }
926
+ }
927
+ const payload = {
928
+ // IMPORTANT: include all observed utilities (not just "top N"), otherwise
929
+ // init+validate on the same file can be inconsistent.
930
+ allowedUtilities: allUtilities,
931
+ allowAnyColor: !!theme && themeColors.length === 0,
932
+ // avoid noisy warnings when config doesn't define colors
933
+ allowStandardSpacing: !!theme && themeSpacingKeys.length === 0,
934
+ themeTokens: theme ? {
935
+ configPath: theme.configPath,
936
+ colors: themeColors,
937
+ spacingKeys: themeSpacingKeys,
938
+ borderRadiusKeys: themeBorderRadiusKeys,
939
+ fontFamilyKeys: themeFontFamilyKeys,
940
+ fontSizeKeys: themeFontSizeKeys
941
+ } : null
942
+ };
943
+ lines.push("");
944
+ lines.push("```json");
945
+ lines.push(JSON.stringify(payload, null, 2));
946
+ lines.push("```");
947
+ lines.push("");
948
+ }
766
949
  return lines.join("\n");
767
950
  }
768
951
  function findGCD(numbers) {
@@ -812,201 +995,10 @@ function styleGuideToMarkdown(guide) {
812
995
  return lines.join("\n");
813
996
  }
814
997
 
815
- // src/validation/validate.ts
816
- function validateCode(code, styleGuide) {
817
- const issues = [];
818
- if (!styleGuide) {
819
- return {
820
- valid: true,
821
- issues: [
822
- {
823
- type: "warning",
824
- message: "No style guide found. Create .uilint/styleguide.md to enable validation."
825
- }
826
- ]
827
- };
828
- }
829
- const styleValues = extractStyleValues(styleGuide);
830
- const codeColors = extractColorsFromCode(code);
831
- for (const color of codeColors) {
832
- if (!styleValues.colors.includes(color.toUpperCase())) {
833
- const similar = findSimilarColor(color, styleValues.colors);
834
- issues.push({
835
- type: "warning",
836
- message: `Color ${color} is not in the style guide`,
837
- suggestion: similar ? `Consider using ${similar} instead` : `Add ${color} to the style guide if intentional`
838
- });
839
- }
840
- }
841
- const hardcodedPixels = code.matchAll(
842
- /(?:margin|padding|gap)[-:].*?(\d+)px/gi
843
- );
844
- for (const match of hardcodedPixels) {
845
- const value = parseInt(match[1]);
846
- if (value % 4 !== 0) {
847
- issues.push({
848
- type: "warning",
849
- message: `Spacing value ${value}px doesn't follow the 4px grid`,
850
- suggestion: `Use ${Math.round(value / 4) * 4}px instead`
851
- });
852
- }
853
- }
854
- if (code.includes("style={{") || code.includes("style={")) {
855
- const inlineStyleCount = (code.match(/style=\{/g) || []).length;
856
- if (inlineStyleCount > 2) {
857
- issues.push({
858
- type: "warning",
859
- message: `Found ${inlineStyleCount} inline styles. Consider using CSS classes for consistency.`
860
- });
861
- }
862
- }
863
- return {
864
- valid: issues.filter((i) => i.type === "error").length === 0,
865
- issues
866
- };
867
- }
868
- function lintSnippet(code, styleGuide) {
869
- const issues = [];
870
- issues.push(...lintBasicPatterns(code));
871
- if (styleGuide) {
872
- issues.push(...lintAgainstStyleGuide(code, styleGuide));
873
- }
874
- const errorCount = issues.filter((i) => i.severity === "error").length;
875
- const warningCount = issues.filter((i) => i.severity === "warning").length;
876
- return {
877
- issues,
878
- summary: issues.length === 0 ? "No issues found" : `Found ${errorCount} errors and ${warningCount} warnings`
879
- };
880
- }
881
- function lintBasicPatterns(code) {
882
- const issues = [];
883
- const magicNumbers = code.matchAll(
884
- /(?:width|height|size):\s*(\d+)(?!px|rem|em|%)/g
885
- );
886
- for (const match of magicNumbers) {
887
- issues.push({
888
- severity: "warning",
889
- type: "spacing",
890
- message: `Magic number ${match[1]} found without unit`,
891
- code: match[0],
892
- suggestion: `Add a unit like ${match[1]}px or use a design token`
893
- });
894
- }
895
- const hardcodedTailwindColors = code.matchAll(
896
- /className=["'][^"']*(?:bg|text|border)-\[#[A-Fa-f0-9]+\][^"']*/g
897
- );
898
- for (const match of hardcodedTailwindColors) {
899
- issues.push({
900
- severity: "warning",
901
- type: "color",
902
- message: "Hardcoded color in Tailwind arbitrary value",
903
- code: match[0],
904
- suggestion: "Use a color from your Tailwind config or style guide"
905
- });
906
- }
907
- if (code.includes("<img") && !code.includes("alt=")) {
908
- issues.push({
909
- severity: "error",
910
- type: "accessibility",
911
- message: "Image without alt attribute",
912
- suggestion: 'Add alt="" for decorative images or descriptive alt text'
913
- });
914
- }
915
- if (code.includes("<button") && !code.match(/<button[^>]*>.*\S.*<\/button>/s)) {
916
- issues.push({
917
- severity: "warning",
918
- type: "accessibility",
919
- message: "Button may be missing accessible text",
920
- suggestion: "Ensure button has visible text or aria-label"
921
- });
922
- }
923
- const singleQuotes = (code.match(/className='/g) || []).length;
924
- const doubleQuotes = (code.match(/className="/g) || []).length;
925
- if (singleQuotes > 0 && doubleQuotes > 0) {
926
- issues.push({
927
- severity: "info",
928
- type: "component",
929
- message: "Mixed quote styles in className attributes",
930
- suggestion: "Use consistent quote style throughout"
931
- });
932
- }
933
- return issues;
934
- }
935
- function lintAgainstStyleGuide(code, styleGuide) {
936
- const issues = [];
937
- const values = extractStyleValues(styleGuide);
938
- const codeColors = code.matchAll(/#[A-Fa-f0-9]{6}\b/g);
939
- for (const match of codeColors) {
940
- const color = match[0].toUpperCase();
941
- if (!values.colors.includes(color)) {
942
- issues.push({
943
- severity: "warning",
944
- type: "color",
945
- message: `Color ${color} not in style guide`,
946
- code: match[0],
947
- suggestion: `Allowed colors: ${values.colors.slice(0, 5).join(", ")}${values.colors.length > 5 ? "..." : ""}`
948
- });
949
- }
950
- }
951
- const spacingValues = code.matchAll(/(?:p|m|gap)-(\d+)/g);
952
- for (const match of spacingValues) {
953
- const value = parseInt(match[1]);
954
- if (value > 12 && value % 4 !== 0) {
955
- issues.push({
956
- severity: "info",
957
- type: "spacing",
958
- message: `Spacing value ${match[0]} might not align with design system`,
959
- suggestion: "Consider using standard Tailwind spacing values (1-12, 16, 20, 24...)"
960
- });
961
- }
962
- }
963
- return issues;
964
- }
965
- function extractColorsFromCode(code) {
966
- const colors = [];
967
- const hexMatches = code.matchAll(/#[A-Fa-f0-9]{6}\b/g);
968
- for (const match of hexMatches) {
969
- colors.push(match[0].toUpperCase());
970
- }
971
- const tailwindMatches = code.matchAll(/(?:bg|text|border)-(\w+)-(\d+)/g);
972
- for (const match of tailwindMatches) {
973
- colors.push(`tailwind:${match[1]}-${match[2]}`);
974
- }
975
- return [...new Set(colors)];
976
- }
977
- function findSimilarColor(color, allowedColors) {
978
- const colorRgb = hexToRgb(color);
979
- if (!colorRgb) return null;
980
- let closest = null;
981
- let closestDistance = Infinity;
982
- for (const allowed of allowedColors) {
983
- const allowedRgb = hexToRgb(allowed);
984
- if (!allowedRgb) continue;
985
- const distance = Math.sqrt(
986
- Math.pow(colorRgb.r - allowedRgb.r, 2) + Math.pow(colorRgb.g - allowedRgb.g, 2) + Math.pow(colorRgb.b - allowedRgb.b, 2)
987
- );
988
- if (distance < closestDistance && distance < 50) {
989
- closestDistance = distance;
990
- closest = allowed;
991
- }
992
- }
993
- return closest;
994
- }
995
- function hexToRgb(hex) {
996
- const match = hex.match(/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i);
997
- if (!match) return null;
998
- return {
999
- r: parseInt(match[1], 16),
1000
- g: parseInt(match[2], 16),
1001
- b: parseInt(match[3], 16)
1002
- };
1003
- }
1004
-
1005
998
  export {
1006
999
  buildAnalysisPrompt,
1007
1000
  buildStyleGuidePrompt,
1008
1001
  buildQueryPrompt,
1009
- buildValidationPrompt,
1010
1002
  OllamaClient,
1011
1003
  getOllamaClient,
1012
1004
  extractStyles,
@@ -1025,9 +1017,8 @@ export {
1025
1017
  parseStyleGuide,
1026
1018
  parseStyleGuideSections,
1027
1019
  extractStyleValues,
1020
+ extractTailwindAllowlist,
1028
1021
  generateStyleGuideFromStyles,
1029
- styleGuideToMarkdown,
1030
- validateCode,
1031
- lintSnippet
1022
+ styleGuideToMarkdown
1032
1023
  };
1033
- //# sourceMappingURL=chunk-P5RPW6PI.js.map
1024
+ //# sourceMappingURL=chunk-P2QQGDQS.js.map