@design-guard/core 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/dist/index.d.ts +20 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +18 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/research/business-researcher.d.ts +42 -0
  6. package/dist/research/business-researcher.d.ts.map +1 -0
  7. package/dist/research/business-researcher.js +888 -0
  8. package/dist/research/business-researcher.js.map +1 -0
  9. package/dist/research/design-synthesizer.d.ts +47 -0
  10. package/dist/research/design-synthesizer.d.ts.map +1 -0
  11. package/dist/research/design-synthesizer.js +628 -0
  12. package/dist/research/design-synthesizer.js.map +1 -0
  13. package/dist/research/research-cache.d.ts +7 -0
  14. package/dist/research/research-cache.d.ts.map +1 -0
  15. package/dist/research/research-cache.js +62 -0
  16. package/dist/research/research-cache.js.map +1 -0
  17. package/dist/research/types.d.ts +99 -0
  18. package/dist/research/types.d.ts.map +1 -0
  19. package/dist/research/types.js +6 -0
  20. package/dist/research/types.js.map +1 -0
  21. package/dist/templates/design-md.d.ts +53 -0
  22. package/dist/templates/design-md.d.ts.map +1 -0
  23. package/dist/templates/design-md.js +315 -0
  24. package/dist/templates/design-md.js.map +1 -0
  25. package/dist/templates/prompts.d.ts +32 -0
  26. package/dist/templates/prompts.d.ts.map +1 -0
  27. package/dist/templates/prompts.js +39 -0
  28. package/dist/templates/prompts.js.map +1 -0
  29. package/dist/tokens/dtcg-converter.d.ts +62 -0
  30. package/dist/tokens/dtcg-converter.d.ts.map +1 -0
  31. package/dist/tokens/dtcg-converter.js +385 -0
  32. package/dist/tokens/dtcg-converter.js.map +1 -0
  33. package/dist/utils/prompt-enhancer.d.ts +22 -0
  34. package/dist/utils/prompt-enhancer.d.ts.map +1 -0
  35. package/dist/utils/prompt-enhancer.js +104 -0
  36. package/dist/utils/prompt-enhancer.js.map +1 -0
  37. package/dist/utils/validators.d.ts +126 -0
  38. package/dist/utils/validators.d.ts.map +1 -0
  39. package/dist/utils/validators.js +110 -0
  40. package/dist/utils/validators.js.map +1 -0
  41. package/dist/validation/design-validator.d.ts +45 -0
  42. package/dist/validation/design-validator.d.ts.map +1 -0
  43. package/dist/validation/design-validator.js +396 -0
  44. package/dist/validation/design-validator.js.map +1 -0
  45. package/dist/validation/output-validator.d.ts +46 -0
  46. package/dist/validation/output-validator.d.ts.map +1 -0
  47. package/dist/validation/output-validator.js +146 -0
  48. package/dist/validation/output-validator.js.map +1 -0
  49. package/dist/validation/rules/alt-text.d.ts +8 -0
  50. package/dist/validation/rules/alt-text.d.ts.map +1 -0
  51. package/dist/validation/rules/alt-text.js +35 -0
  52. package/dist/validation/rules/alt-text.js.map +1 -0
  53. package/dist/validation/rules/business-alignment.d.ts +7 -0
  54. package/dist/validation/rules/business-alignment.d.ts.map +1 -0
  55. package/dist/validation/rules/business-alignment.js +98 -0
  56. package/dist/validation/rules/business-alignment.js.map +1 -0
  57. package/dist/validation/rules/color-adherence.d.ts +8 -0
  58. package/dist/validation/rules/color-adherence.d.ts.map +1 -0
  59. package/dist/validation/rules/color-adherence.js +92 -0
  60. package/dist/validation/rules/color-adherence.js.map +1 -0
  61. package/dist/validation/rules/empty-body.d.ts +6 -0
  62. package/dist/validation/rules/empty-body.d.ts.map +1 -0
  63. package/dist/validation/rules/empty-body.js +24 -0
  64. package/dist/validation/rules/empty-body.js.map +1 -0
  65. package/dist/validation/rules/heading-hierarchy.d.ts +8 -0
  66. package/dist/validation/rules/heading-hierarchy.d.ts.map +1 -0
  67. package/dist/validation/rules/heading-hierarchy.js +48 -0
  68. package/dist/validation/rules/heading-hierarchy.js.map +1 -0
  69. package/dist/validation/rules/index.d.ts +21 -0
  70. package/dist/validation/rules/index.d.ts.map +1 -0
  71. package/dist/validation/rules/index.js +66 -0
  72. package/dist/validation/rules/index.js.map +1 -0
  73. package/dist/validation/rules/no-centered-everything.d.ts +6 -0
  74. package/dist/validation/rules/no-centered-everything.d.ts.map +1 -0
  75. package/dist/validation/rules/no-centered-everything.js +59 -0
  76. package/dist/validation/rules/no-centered-everything.js.map +1 -0
  77. package/dist/validation/rules/no-default-fonts.d.ts +8 -0
  78. package/dist/validation/rules/no-default-fonts.d.ts.map +1 -0
  79. package/dist/validation/rules/no-default-fonts.js +111 -0
  80. package/dist/validation/rules/no-default-fonts.js.map +1 -0
  81. package/dist/validation/rules/no-div-soup.d.ts +6 -0
  82. package/dist/validation/rules/no-div-soup.d.ts.map +1 -0
  83. package/dist/validation/rules/no-div-soup.js +45 -0
  84. package/dist/validation/rules/no-div-soup.js.map +1 -0
  85. package/dist/validation/rules/no-duplicate-ctas.d.ts +6 -0
  86. package/dist/validation/rules/no-duplicate-ctas.d.ts.map +1 -0
  87. package/dist/validation/rules/no-duplicate-ctas.js +32 -0
  88. package/dist/validation/rules/no-duplicate-ctas.js.map +1 -0
  89. package/dist/validation/rules/no-generic-hero.d.ts +7 -0
  90. package/dist/validation/rules/no-generic-hero.d.ts.map +1 -0
  91. package/dist/validation/rules/no-generic-hero.js +46 -0
  92. package/dist/validation/rules/no-generic-hero.js.map +1 -0
  93. package/dist/validation/rules/no-icon-grid.d.ts +8 -0
  94. package/dist/validation/rules/no-icon-grid.d.ts.map +1 -0
  95. package/dist/validation/rules/no-icon-grid.js +43 -0
  96. package/dist/validation/rules/no-icon-grid.js.map +1 -0
  97. package/dist/validation/rules/no-lorem-ipsum.d.ts +6 -0
  98. package/dist/validation/rules/no-lorem-ipsum.d.ts.map +1 -0
  99. package/dist/validation/rules/no-lorem-ipsum.js +66 -0
  100. package/dist/validation/rules/no-lorem-ipsum.js.map +1 -0
  101. package/dist/validation/rules/no-missing-meta.d.ts +6 -0
  102. package/dist/validation/rules/no-missing-meta.d.ts.map +1 -0
  103. package/dist/validation/rules/no-missing-meta.js +49 -0
  104. package/dist/validation/rules/no-missing-meta.js.map +1 -0
  105. package/dist/validation/rules/no-missing-responsive.d.ts +6 -0
  106. package/dist/validation/rules/no-missing-responsive.d.ts.map +1 -0
  107. package/dist/validation/rules/no-missing-responsive.js +30 -0
  108. package/dist/validation/rules/no-missing-responsive.js.map +1 -0
  109. package/dist/validation/rules/no-placeholder-images.d.ts +6 -0
  110. package/dist/validation/rules/no-placeholder-images.d.ts.map +1 -0
  111. package/dist/validation/rules/no-placeholder-images.js +73 -0
  112. package/dist/validation/rules/no-placeholder-images.js.map +1 -0
  113. package/dist/validation/rules/no-saas-speak.d.ts +6 -0
  114. package/dist/validation/rules/no-saas-speak.d.ts.map +1 -0
  115. package/dist/validation/rules/no-saas-speak.js +74 -0
  116. package/dist/validation/rules/no-saas-speak.js.map +1 -0
  117. package/dist/validation/rules/no-slop-gradients.d.ts +6 -0
  118. package/dist/validation/rules/no-slop-gradients.d.ts.map +1 -0
  119. package/dist/validation/rules/no-slop-gradients.js +24 -0
  120. package/dist/validation/rules/no-slop-gradients.js.map +1 -0
  121. package/dist/validation/rules/no-uniform-spacing.d.ts +6 -0
  122. package/dist/validation/rules/no-uniform-spacing.d.ts.map +1 -0
  123. package/dist/validation/rules/no-uniform-spacing.js +64 -0
  124. package/dist/validation/rules/no-uniform-spacing.js.map +1 -0
  125. package/dist/validation/rules/types.d.ts +36 -0
  126. package/dist/validation/rules/types.d.ts.map +1 -0
  127. package/dist/validation/rules/types.js +7 -0
  128. package/dist/validation/rules/types.js.map +1 -0
  129. package/dist/validation/tailwind-parser.d.ts +36 -0
  130. package/dist/validation/tailwind-parser.d.ts.map +1 -0
  131. package/dist/validation/tailwind-parser.js +214 -0
  132. package/dist/validation/tailwind-parser.js.map +1 -0
  133. package/package.json +45 -0
@@ -0,0 +1,396 @@
1
+ /**
2
+ * Design Quality Validator for DESIGN.md files.
3
+ *
4
+ * Scores a DESIGN.md across four dimensions — specificity, differentiation,
5
+ * completeness, and actionability — and surfaces concrete issues.
6
+ */
7
+ // ─── Hex distance utility ──────────────────────────────────────────
8
+ function parseHex(hex) {
9
+ const clean = hex.replace('#', '');
10
+ const r = parseInt(clean.substring(0, 2), 16);
11
+ const g = parseInt(clean.substring(2, 4), 16);
12
+ const b = parseInt(clean.substring(4, 6), 16);
13
+ return [r, g, b];
14
+ }
15
+ /**
16
+ * Euclidean distance between two hex colors in RGB space.
17
+ * Threshold for "too similar": distance < 50.
18
+ */
19
+ export function hexDistance(a, b) {
20
+ const [r1, g1, b1] = parseHex(a);
21
+ const [r2, g2, b2] = parseHex(b);
22
+ return Math.sqrt((r1 - r2) ** 2 + (g1 - g2) ** 2 + (b1 - b2) ** 2);
23
+ }
24
+ // ─── Section extraction helpers ────────────────────────────────────
25
+ function extractSection(markdown, sectionNumber) {
26
+ const pattern = new RegExp(`## ${sectionNumber}\\..*?\n([\\s\\S]*?)(?=## \\d+\\.|$)`);
27
+ const match = markdown.match(pattern);
28
+ return match ? match[1].trim() : '';
29
+ }
30
+ function extractDosSection(markdown) {
31
+ const match = markdown.match(/### Do\b\n([\s\S]*?)(?=### Don|$)/);
32
+ return match ? match[1].trim() : '';
33
+ }
34
+ function extractDontsSection(markdown) {
35
+ const match = markdown.match(/### Don'?t\b\n([\s\S]*?)(?=##|$)/);
36
+ return match ? match[1].trim() : '';
37
+ }
38
+ function extractColorHexes(markdown) {
39
+ const section = extractSection(markdown, 2);
40
+ const hexPattern = /#[0-9A-Fa-f]{6}/g;
41
+ return [...(section.match(hexPattern) || [])];
42
+ }
43
+ function countColorTableRows(markdown) {
44
+ const section = extractSection(markdown, 2);
45
+ const lines = section.split('\n');
46
+ let count = 0;
47
+ for (const line of lines) {
48
+ // Count table rows with hex values, skip header and separator
49
+ if (line.includes('|') && /#[0-9A-Fa-f]{6}/.test(line)) {
50
+ count++;
51
+ }
52
+ }
53
+ return count;
54
+ }
55
+ function countListItems(text) {
56
+ const items = text.split('\n').filter((l) => /^- /.test(l.trim()));
57
+ return items.length;
58
+ }
59
+ // ─── Sub-scorers ───────────────────────────────────────────────────
60
+ /**
61
+ * Scores how specific and concrete the DESIGN.md is (0-25).
62
+ * Penalizes placeholders, generic adjectives, missing hex values, etc.
63
+ */
64
+ export function scoreSpecificity(markdown) {
65
+ let score = 25;
66
+ const issues = [];
67
+ // -5 for each <!-- --> placeholder
68
+ const placeholders = (markdown.match(/<!--[\s\S]*?-->/g) || []).length;
69
+ score -= placeholders * 5;
70
+ // -3 for generic words used in isolation (not as part of specific rules)
71
+ const genericWords = ['modern', 'clean', 'professional', 'nice', 'beautiful'];
72
+ for (const word of genericWords) {
73
+ // Match word in isolation (not within a quoted string or compound rule)
74
+ const regex = new RegExp(`(?<![\\w"'])\\b${word}\\b(?![\\w"'])`, 'gi');
75
+ const matches = markdown.match(regex) || [];
76
+ // Only penalize if the word appears standalone-ish (not in a longer rule)
77
+ for (const _match of matches) {
78
+ // Check context: find the line containing this match
79
+ const lines = markdown.split('\n');
80
+ for (const line of lines) {
81
+ if (regex.test(line)) {
82
+ // If line is short (< 40 chars) and mostly just the generic word, penalize
83
+ const trimmed = line.replace(/^[-#|*\s]+/, '').trim();
84
+ if (trimmed.length < 40 || trimmed.toLowerCase() === word) {
85
+ score -= 3;
86
+ break;
87
+ }
88
+ }
89
+ }
90
+ }
91
+ }
92
+ // -2 if no hex color values found in color table
93
+ const hexes = extractColorHexes(markdown);
94
+ if (hexes.length === 0) {
95
+ score -= 2;
96
+ }
97
+ // -2 if typography section has no quoted font names
98
+ const typoSection = extractSection(markdown, 3);
99
+ if (!/"[^"]+"/.test(typoSection)) {
100
+ score -= 2;
101
+ }
102
+ // -2 for each section with no content, -1 for sections under 20 characters
103
+ for (let i = 1; i <= 8; i++) {
104
+ const section = extractSection(markdown, i);
105
+ if (section.length === 0) {
106
+ score -= 2;
107
+ }
108
+ else if (section.length < 20) {
109
+ score -= 1;
110
+ }
111
+ }
112
+ return Math.max(0, score);
113
+ }
114
+ /**
115
+ * Scores how differentiated the design is from competitors (0-25).
116
+ * Without competitor data, caps at 15 (cannot assess differentiation).
117
+ */
118
+ export function scoreDifferentiation(markdown, competitors) {
119
+ const hasCompetitors = competitors && competitors.length > 0;
120
+ let score = hasCompetitors ? 25 : 15;
121
+ if (!hasCompetitors)
122
+ return score;
123
+ // Extract primary color from markdown (try section 2 first, then full markdown)
124
+ const colorSection = extractSection(markdown, 2);
125
+ const searchText = colorSection || markdown;
126
+ const primaryLine = searchText
127
+ .split('\n')
128
+ .find((l) => /primary/i.test(l) && /#[0-9A-Fa-f]{6}/.test(l));
129
+ const primaryHexMatch = primaryLine?.match(/#[0-9A-Fa-f]{6}/);
130
+ const primaryHex = primaryHexMatch ? primaryHexMatch[0] : null;
131
+ if (primaryHex) {
132
+ for (const comp of competitors) {
133
+ const compDominant = comp.palette.dominantHex;
134
+ if (!compDominant)
135
+ continue;
136
+ // -8 for exact match
137
+ if (primaryHex.toLowerCase() === compDominant.toLowerCase()) {
138
+ score -= 8;
139
+ break;
140
+ }
141
+ // -4 if within distance 30
142
+ const dist = hexDistance(primaryHex, compDominant);
143
+ if (dist < 30) {
144
+ score -= 4;
145
+ break;
146
+ }
147
+ // Also check within distance 50 for a lesser penalty
148
+ if (dist < 50) {
149
+ score -= 2;
150
+ break;
151
+ }
152
+ }
153
+ }
154
+ // -5 if heading font matches any competitor's heading font
155
+ const typoSection = extractSection(markdown, 3) || markdown;
156
+ for (const comp of competitors) {
157
+ if (comp.typography.headingFont) {
158
+ if (typoSection.includes(comp.typography.headingFont)) {
159
+ score -= 5;
160
+ break;
161
+ }
162
+ }
163
+ }
164
+ // -3 if "Don't" section doesn't mention any competitor differentiation rule
165
+ const dontsSection = extractDontsSection(markdown) || markdown;
166
+ const hasCompetitorMention = competitors.some((c) => dontsSection.toLowerCase().includes(c.name.toLowerCase()) ||
167
+ (c.typography.headingFont &&
168
+ dontsSection.includes(c.typography.headingFont)));
169
+ if (!hasCompetitorMention) {
170
+ score -= 3;
171
+ }
172
+ return Math.max(0, score);
173
+ }
174
+ /**
175
+ * Scores structural completeness of the DESIGN.md (0-25).
176
+ * Checks for all 8 sections, color count, component patterns, do/don't counts.
177
+ */
178
+ export function scoreCompleteness(markdown) {
179
+ let score = 25;
180
+ // Check for all 8 section headers
181
+ for (let i = 1; i <= 8; i++) {
182
+ const pattern = new RegExp(`## ${i}\\.`);
183
+ if (!pattern.test(markdown)) {
184
+ score -= 4;
185
+ }
186
+ }
187
+ // Check color table has >= 5 rows with hex values
188
+ const colorRows = countColorTableRows(markdown);
189
+ if (colorRows < 5) {
190
+ score -= 3;
191
+ }
192
+ // Check >= 3 component patterns (subsections under section 5)
193
+ const compSection = extractSection(markdown, 5);
194
+ const componentHeaders = (compSection.match(/### /g) || []).length;
195
+ if (componentHeaders < 3) {
196
+ score -= 3;
197
+ }
198
+ // Check Do's has >= 3 items
199
+ const dosSection = extractDosSection(markdown);
200
+ const dosCount = countListItems(dosSection);
201
+ if (dosCount < 3) {
202
+ score -= 3;
203
+ }
204
+ // Check Don'ts has >= 3 items
205
+ const dontsSection = extractDontsSection(markdown);
206
+ const dontsCount = countListItems(dontsSection);
207
+ if (dontsCount < 3) {
208
+ score -= 3;
209
+ }
210
+ return Math.max(0, score);
211
+ }
212
+ /**
213
+ * Scores how actionable and implementation-ready the rules are (0-25).
214
+ * Penalizes vague language, color words without hex, non-numeric sizes.
215
+ */
216
+ export function scoreActionability(markdown) {
217
+ let score = 25;
218
+ const dosSection = extractDosSection(markdown);
219
+ const dontsSection = extractDontsSection(markdown);
220
+ const rulesText = `${dosSection}\n${dontsSection}`;
221
+ // -3 for each "should" or "consider" in Do's/Don'ts
222
+ const vagueMatches = rulesText.match(/\b(should|consider)\b/gi) || [];
223
+ score -= vagueMatches.length * 3;
224
+ // -2 for each rule that is too short to be actionable (< 15 chars)
225
+ const ruleLines = rulesText.split('\n').filter((l) => /^- /.test(l.trim()));
226
+ for (const rule of ruleLines) {
227
+ const content = rule.replace(/^-\s*/, '').trim();
228
+ if (content.length > 0 && content.length < 15) {
229
+ score -= 2;
230
+ }
231
+ }
232
+ // -2 for each color described with words instead of hex
233
+ const colorSection = extractSection(markdown, 2);
234
+ const colorWordPatterns = [
235
+ /\b(trustworthy|calming|vibrant|warm|cool)\s+(blue|red|green|yellow|orange|purple|pink|teal)\b/gi,
236
+ ];
237
+ for (const pattern of colorWordPatterns) {
238
+ const matches = colorSection.match(pattern) || [];
239
+ score -= matches.length * 2;
240
+ }
241
+ // -2 for each font size not in px/rem in typography section
242
+ const typoSection = extractSection(markdown, 3);
243
+ const sizeEntries = typoSection.match(/\|\s*\w+\s*\|\s*([^|]+)\s*\|/g) || [];
244
+ for (const entry of sizeEntries) {
245
+ // Skip header row
246
+ if (/Size/i.test(entry))
247
+ continue;
248
+ if (!/\d+(\.\d+)?(px|rem)/.test(entry)) {
249
+ score -= 2;
250
+ }
251
+ }
252
+ // -2 if spacing section has no numeric values
253
+ const spacingSection = extractSection(markdown, 4);
254
+ if (!/\d+/.test(spacingSection)) {
255
+ score -= 2;
256
+ }
257
+ // -1 for each "e.g." or "for example" in rules
258
+ const exampleMatches = rulesText.match(/\b(e\.g\.|for example)\b/gi) || [];
259
+ score -= exampleMatches.length;
260
+ return Math.max(0, score);
261
+ }
262
+ // ─── Cultural alignment ────────────────────────────────────────────
263
+ /**
264
+ * Checks for cultural alignment issues in the DESIGN.md.
265
+ */
266
+ export function checkCulturalAlignment(markdown, locale, audience) {
267
+ const issues = [];
268
+ const context = `${locale || ''} ${audience || ''}`.toLowerCase();
269
+ if (/\bes\b|mexic|latino|latina|español/.test(context)) {
270
+ // Check for Spanish copy guidance
271
+ const dosSection = extractDosSection(markdown);
272
+ const dontsSection = extractDontsSection(markdown);
273
+ const allRules = `${dosSection}\n${dontsSection}`.toLowerCase();
274
+ if (!allRules.includes('spanish') &&
275
+ !allRules.includes('español') &&
276
+ !allRules.includes('spanish-language')) {
277
+ issues.push({
278
+ section: "Do's and Don'ts",
279
+ severity: 'warning',
280
+ message: 'No Spanish copy guidance found for Spanish-locale audience — add rules for natural Spanish copy',
281
+ });
282
+ }
283
+ // Check imagery for American/European references
284
+ const imagerySection = extractSection(markdown, 7).toLowerCase();
285
+ if (imagerySection.includes('american') ||
286
+ imagerySection.includes('european')) {
287
+ issues.push({
288
+ section: 'Imagery Guidelines',
289
+ severity: 'info',
290
+ message: 'Imagery section references "American" or "European" stock — consider local imagery for this audience',
291
+ });
292
+ }
293
+ }
294
+ return issues;
295
+ }
296
+ // ─── Orchestrator ──────────────────────────────────────────────────
297
+ /**
298
+ * Orchestrates all sub-scorers and returns a complete quality score.
299
+ */
300
+ export function scoreDesignMd(markdown, research) {
301
+ const specificity = scoreSpecificity(markdown);
302
+ const differentiation = scoreDifferentiation(markdown, research?.competitors);
303
+ const completeness = scoreCompleteness(markdown);
304
+ const actionability = scoreActionability(markdown);
305
+ const total = specificity + differentiation + completeness + actionability;
306
+ const issues = [];
307
+ // Collect issues from specificity
308
+ const placeholders = (markdown.match(/<!--[\s\S]*?-->/g) || []).length;
309
+ if (placeholders > 0) {
310
+ issues.push({
311
+ section: 'General',
312
+ severity: 'error',
313
+ message: `Contains ${placeholders} placeholder comment(s) — replace with real content`,
314
+ });
315
+ }
316
+ // Check for vague language in rules
317
+ const dosSection = extractDosSection(markdown);
318
+ const dontsSection = extractDontsSection(markdown);
319
+ const rulesText = `${dosSection}\n${dontsSection}`;
320
+ if (/\b(should|consider)\b/i.test(rulesText)) {
321
+ issues.push({
322
+ section: "Do's and Don'ts",
323
+ severity: 'warning',
324
+ message: 'Contains "should" or "consider" — make rules definitive',
325
+ });
326
+ }
327
+ // Check for missing sections
328
+ for (let i = 1; i <= 8; i++) {
329
+ const section = extractSection(markdown, i);
330
+ if (section.length < 20) {
331
+ const sectionNames = {
332
+ 1: 'Visual Theme & Atmosphere',
333
+ 2: 'Color Palette & Roles',
334
+ 3: 'Typography',
335
+ 4: 'Spacing & Layout',
336
+ 5: 'Component Patterns',
337
+ 6: 'Iconography',
338
+ 7: 'Imagery Guidelines',
339
+ 8: "Do's and Don'ts",
340
+ };
341
+ issues.push({
342
+ section: sectionNames[i] || `Section ${i}`,
343
+ severity: 'warning',
344
+ message: `Section has very little content (${section.length} chars)`,
345
+ });
346
+ }
347
+ }
348
+ // Check competitor differentiation status
349
+ if (!research?.competitors || research.competitors.length === 0) {
350
+ issues.push({
351
+ section: 'Differentiation',
352
+ severity: 'info',
353
+ message: 'No competitor data available for differentiation scoring',
354
+ });
355
+ }
356
+ // Cultural alignment issues
357
+ if (research) {
358
+ const culturalIssues = checkCulturalAlignment(markdown, research.brief.locale, research.brief.targetAudience);
359
+ issues.push(...culturalIssues);
360
+ }
361
+ return {
362
+ specificity,
363
+ differentiation,
364
+ completeness,
365
+ actionability,
366
+ total,
367
+ issues,
368
+ };
369
+ }
370
+ // ─── Report formatter ──────────────────────────────────────────────
371
+ /**
372
+ * Formats a DesignQualityScore into a human-readable report.
373
+ */
374
+ export function formatDesignQualityReport(score) {
375
+ const lines = [];
376
+ lines.push(`Design Quality: ${score.total}/100`);
377
+ lines.push('');
378
+ lines.push(` Specificity: ${String(score.specificity).padStart(2)}/25`);
379
+ lines.push(` Differentiation: ${String(score.differentiation).padStart(2)}/25`);
380
+ lines.push(` Completeness: ${String(score.completeness).padStart(2)}/25`);
381
+ lines.push(` Actionability: ${String(score.actionability).padStart(2)}/25`);
382
+ if (score.issues.length > 0) {
383
+ lines.push('');
384
+ lines.push('Issues:');
385
+ for (const issue of score.issues) {
386
+ const icon = issue.severity === 'error'
387
+ ? '[!]'
388
+ : issue.severity === 'warning'
389
+ ? '[!]'
390
+ : '[i]';
391
+ lines.push(` ${icon} Section "${issue.section}": ${issue.message}`);
392
+ }
393
+ }
394
+ return lines.join('\n');
395
+ }
396
+ //# sourceMappingURL=design-validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"design-validator.js","sourceRoot":"","sources":["../../src/validation/design-validator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ export type IssueCategory = 'color' | 'typography' | 'layout' | 'content' | 'structure' | 'slop' | 'accessibility';
2
+ export interface ValidationIssue {
3
+ type: 'error' | 'warning' | 'info';
4
+ category: IssueCategory;
5
+ message: string;
6
+ }
7
+ export interface CategoryScore {
8
+ score: number;
9
+ issues: number;
10
+ maxPossible: number;
11
+ }
12
+ export interface ScoringBreakdown {
13
+ typography: CategoryScore;
14
+ color: CategoryScore;
15
+ layout: CategoryScore;
16
+ content: CategoryScore;
17
+ structure: CategoryScore;
18
+ slop: CategoryScore;
19
+ accessibility: CategoryScore;
20
+ }
21
+ export interface OutputValidationResult {
22
+ /** Overall quality score 0-100 */
23
+ score: number;
24
+ /** All detected issues */
25
+ issues: ValidationIssue[];
26
+ /** Whether the output passes the quality threshold (score >= 70) */
27
+ passed: boolean;
28
+ /** 0-100 confidence -- how much of the page we actually checked */
29
+ confidence: number;
30
+ /** Per-category score breakdown */
31
+ breakdown: ScoringBreakdown;
32
+ }
33
+ /**
34
+ * Validate generated HTML against design system and quality rules.
35
+ *
36
+ * Uses the modular rule registry -- each check is an independent LintRule.
37
+ * Returns an enhanced result with per-category breakdown and confidence metric.
38
+ *
39
+ * Scoring: error=-20, warning=-10, info=-5, threshold=70 (Frente C calibration).
40
+ */
41
+ export declare function validateOutput(html: string, designMdPath?: string): OutputValidationResult;
42
+ /**
43
+ * Format validation results for display.
44
+ */
45
+ export declare function formatValidationReport(result: OutputValidationResult): string;
46
+ //# sourceMappingURL=output-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-validator.d.ts","sourceRoot":"","sources":["../../src/validation/output-validator.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,YAAY,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,MAAM,GAAG,eAAe,CAAC;AAEnH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;IACnC,QAAQ,EAAE,aAAa,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,aAAa,CAAC;IAC1B,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,aAAa,CAAC;IACvB,SAAS,EAAE,aAAa,CAAC;IACzB,IAAI,EAAE,aAAa,CAAC;IACpB,aAAa,EAAE,aAAa,CAAC;CAC9B;AAED,MAAM,WAAW,sBAAsB;IACrC,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,0BAA0B;IAC1B,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,oEAAoE;IACpE,MAAM,EAAE,OAAO,CAAC;IAChB,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAC;IACnB,mCAAmC;IACnC,SAAS,EAAE,gBAAgB,CAAC;CAC7B;AA4CD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,sBAAsB,CAwE1F;AAMD;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,sBAAsB,GAAG,MAAM,CA8C7E"}
@@ -0,0 +1,146 @@
1
+ import { load } from 'cheerio';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { getAllRules } from './rules/index.js';
4
+ // ---------------------------------------------------------------------------
5
+ // Scoring engine (Frente C: error=-20, warn=-10, info=-5, threshold=70)
6
+ // ---------------------------------------------------------------------------
7
+ const DEDUCTION = {
8
+ error: 20,
9
+ warning: 10,
10
+ info: 5,
11
+ };
12
+ const PASS_THRESHOLD = 70;
13
+ const ALL_CATEGORIES = [
14
+ 'typography', 'color', 'layout', 'content', 'structure', 'slop', 'accessibility',
15
+ ];
16
+ function computeBreakdown(issues, rulesRanByCategory) {
17
+ const breakdown = {};
18
+ for (const cat of ALL_CATEGORIES) {
19
+ const catIssues = issues.filter(i => i.category === cat);
20
+ const maxPossible = rulesRanByCategory.get(cat) ?? 0;
21
+ const deductions = catIssues.reduce((sum, i) => sum + DEDUCTION[i.type], 0);
22
+ const rawScore = maxPossible > 0 ? Math.max(0, 100 - deductions) : 100;
23
+ breakdown[cat] = {
24
+ score: rawScore,
25
+ issues: catIssues.length,
26
+ maxPossible,
27
+ };
28
+ }
29
+ return breakdown;
30
+ }
31
+ // ---------------------------------------------------------------------------
32
+ // Main validator
33
+ // ---------------------------------------------------------------------------
34
+ /**
35
+ * Validate generated HTML against design system and quality rules.
36
+ *
37
+ * Uses the modular rule registry -- each check is an independent LintRule.
38
+ * Returns an enhanced result with per-category breakdown and confidence metric.
39
+ *
40
+ * Scoring: error=-20, warning=-10, info=-5, threshold=70 (Frente C calibration).
41
+ */
42
+ export function validateOutput(html, designMdPath) {
43
+ const $ = load(html);
44
+ // Build shared context once for all rules
45
+ const allStyles = $('style').text() +
46
+ $('[style]')
47
+ .map((_i, el) => $(el).attr('style'))
48
+ .get()
49
+ .join(' ');
50
+ const allClasses = $('[class]')
51
+ .map((_i, el) => $(el).attr('class'))
52
+ .get()
53
+ .join(' ');
54
+ // Load DESIGN.md content
55
+ const resolvedPath = designMdPath ?? 'DESIGN.md';
56
+ const designMdContent = existsSync(resolvedPath) ? readFileSync(resolvedPath, 'utf-8') : undefined;
57
+ const context = {
58
+ html,
59
+ allStyles,
60
+ allClasses,
61
+ $,
62
+ designMdContent,
63
+ };
64
+ // Run all applicable rules and track which ones ran
65
+ const allRules = getAllRules();
66
+ const allIssues = [];
67
+ const rulesRanByCategory = new Map();
68
+ const totalRulesAvailable = allRules.length;
69
+ let rulesChecked = 0;
70
+ for (const rule of allRules) {
71
+ // Skip rules that require DESIGN.md when it is not available
72
+ if (rule.requiresDesign && !designMdContent) {
73
+ continue;
74
+ }
75
+ rulesChecked++;
76
+ const cat = rule.category;
77
+ const count = rulesRanByCategory.get(cat) ?? 0;
78
+ rulesRanByCategory.set(cat, count + 1);
79
+ const ruleIssues = rule.check(context);
80
+ allIssues.push(...ruleIssues);
81
+ }
82
+ // Calculate confidence: what fraction of rules could actually run
83
+ const confidence = totalRulesAvailable > 0
84
+ ? Math.round((rulesChecked / totalRulesAvailable) * 100)
85
+ : 0;
86
+ // Calculate overall score
87
+ const errorCount = allIssues.filter(i => i.type === 'error').length;
88
+ const warningCount = allIssues.filter(i => i.type === 'warning').length;
89
+ const infoCount = allIssues.filter(i => i.type === 'info').length;
90
+ const score = Math.max(0, 100 - (errorCount * DEDUCTION.error) - (warningCount * DEDUCTION.warning) - (infoCount * DEDUCTION.info));
91
+ const breakdown = computeBreakdown(allIssues, rulesRanByCategory);
92
+ return {
93
+ score,
94
+ issues: allIssues,
95
+ passed: score >= PASS_THRESHOLD,
96
+ confidence,
97
+ breakdown,
98
+ };
99
+ }
100
+ // ---------------------------------------------------------------------------
101
+ // Report formatter
102
+ // ---------------------------------------------------------------------------
103
+ /**
104
+ * Format validation results for display.
105
+ */
106
+ export function formatValidationReport(result) {
107
+ const lines = [];
108
+ const passLabel = result.passed ? 'PASS' : 'NEEDS REVIEW';
109
+ lines.push(`Quality Score: ${result.score}/100 (${passLabel}) [confidence: ${result.confidence}%]`);
110
+ lines.push('');
111
+ // Category breakdown
112
+ lines.push('Category Breakdown:');
113
+ for (const cat of ALL_CATEGORIES) {
114
+ const b = result.breakdown[cat];
115
+ if (b.maxPossible > 0) {
116
+ lines.push(` ${cat.padEnd(14)} ${b.score}/100 (${b.issues} issue${b.issues !== 1 ? 's' : ''}, ${b.maxPossible} rule${b.maxPossible !== 1 ? 's' : ''} checked)`);
117
+ }
118
+ else {
119
+ lines.push(` ${cat.padEnd(14)} -- (no rules ran)`);
120
+ }
121
+ }
122
+ lines.push('');
123
+ if (result.issues.length === 0) {
124
+ lines.push('No issues detected.');
125
+ return lines.join('\n');
126
+ }
127
+ const errors = result.issues.filter(i => i.type === 'error');
128
+ const warnings = result.issues.filter(i => i.type === 'warning');
129
+ const infos = result.issues.filter(i => i.type === 'info');
130
+ if (errors.length > 0) {
131
+ lines.push('Errors:');
132
+ errors.forEach(e => lines.push(` [x] ${e.message}`));
133
+ lines.push('');
134
+ }
135
+ if (warnings.length > 0) {
136
+ lines.push('Warnings:');
137
+ warnings.forEach(w => lines.push(` [!] ${w.message}`));
138
+ lines.push('');
139
+ }
140
+ if (infos.length > 0) {
141
+ lines.push('Info:');
142
+ infos.forEach(i => lines.push(` [i] ${i.message}`));
143
+ }
144
+ return lines.join('\n');
145
+ }
146
+ //# sourceMappingURL=output-validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-validator.js","sourceRoot":"","sources":["../../src/validation/output-validator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { LintRule } from './types.js';
2
+ /**
3
+ * Checks for missing img alt attributes.
4
+ * Bug Fix (Frente A): detects data-alt without alt (Stitch pattern),
5
+ * separately counts data-alt-only vs fully missing alt.
6
+ */
7
+ export declare const altText: LintRule;
8
+ //# sourceMappingURL=alt-text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alt-text.d.ts","sourceRoot":"","sources":["../../../src/validation/rules/alt-text.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAe,MAAM,YAAY,CAAC;AAGxD;;;;GAIG;AACH,eAAO,MAAM,OAAO,EAAE,QAiCrB,CAAC"}