@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.
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/research/business-researcher.d.ts +42 -0
- package/dist/research/business-researcher.d.ts.map +1 -0
- package/dist/research/business-researcher.js +888 -0
- package/dist/research/business-researcher.js.map +1 -0
- package/dist/research/design-synthesizer.d.ts +47 -0
- package/dist/research/design-synthesizer.d.ts.map +1 -0
- package/dist/research/design-synthesizer.js +628 -0
- package/dist/research/design-synthesizer.js.map +1 -0
- package/dist/research/research-cache.d.ts +7 -0
- package/dist/research/research-cache.d.ts.map +1 -0
- package/dist/research/research-cache.js +62 -0
- package/dist/research/research-cache.js.map +1 -0
- package/dist/research/types.d.ts +99 -0
- package/dist/research/types.d.ts.map +1 -0
- package/dist/research/types.js +6 -0
- package/dist/research/types.js.map +1 -0
- package/dist/templates/design-md.d.ts +53 -0
- package/dist/templates/design-md.d.ts.map +1 -0
- package/dist/templates/design-md.js +315 -0
- package/dist/templates/design-md.js.map +1 -0
- package/dist/templates/prompts.d.ts +32 -0
- package/dist/templates/prompts.d.ts.map +1 -0
- package/dist/templates/prompts.js +39 -0
- package/dist/templates/prompts.js.map +1 -0
- package/dist/tokens/dtcg-converter.d.ts +62 -0
- package/dist/tokens/dtcg-converter.d.ts.map +1 -0
- package/dist/tokens/dtcg-converter.js +385 -0
- package/dist/tokens/dtcg-converter.js.map +1 -0
- package/dist/utils/prompt-enhancer.d.ts +22 -0
- package/dist/utils/prompt-enhancer.d.ts.map +1 -0
- package/dist/utils/prompt-enhancer.js +104 -0
- package/dist/utils/prompt-enhancer.js.map +1 -0
- package/dist/utils/validators.d.ts +126 -0
- package/dist/utils/validators.d.ts.map +1 -0
- package/dist/utils/validators.js +110 -0
- package/dist/utils/validators.js.map +1 -0
- package/dist/validation/design-validator.d.ts +45 -0
- package/dist/validation/design-validator.d.ts.map +1 -0
- package/dist/validation/design-validator.js +396 -0
- package/dist/validation/design-validator.js.map +1 -0
- package/dist/validation/output-validator.d.ts +46 -0
- package/dist/validation/output-validator.d.ts.map +1 -0
- package/dist/validation/output-validator.js +146 -0
- package/dist/validation/output-validator.js.map +1 -0
- package/dist/validation/rules/alt-text.d.ts +8 -0
- package/dist/validation/rules/alt-text.d.ts.map +1 -0
- package/dist/validation/rules/alt-text.js +35 -0
- package/dist/validation/rules/alt-text.js.map +1 -0
- package/dist/validation/rules/business-alignment.d.ts +7 -0
- package/dist/validation/rules/business-alignment.d.ts.map +1 -0
- package/dist/validation/rules/business-alignment.js +98 -0
- package/dist/validation/rules/business-alignment.js.map +1 -0
- package/dist/validation/rules/color-adherence.d.ts +8 -0
- package/dist/validation/rules/color-adherence.d.ts.map +1 -0
- package/dist/validation/rules/color-adherence.js +92 -0
- package/dist/validation/rules/color-adherence.js.map +1 -0
- package/dist/validation/rules/empty-body.d.ts +6 -0
- package/dist/validation/rules/empty-body.d.ts.map +1 -0
- package/dist/validation/rules/empty-body.js +24 -0
- package/dist/validation/rules/empty-body.js.map +1 -0
- package/dist/validation/rules/heading-hierarchy.d.ts +8 -0
- package/dist/validation/rules/heading-hierarchy.d.ts.map +1 -0
- package/dist/validation/rules/heading-hierarchy.js +48 -0
- package/dist/validation/rules/heading-hierarchy.js.map +1 -0
- package/dist/validation/rules/index.d.ts +21 -0
- package/dist/validation/rules/index.d.ts.map +1 -0
- package/dist/validation/rules/index.js +66 -0
- package/dist/validation/rules/index.js.map +1 -0
- package/dist/validation/rules/no-centered-everything.d.ts +6 -0
- package/dist/validation/rules/no-centered-everything.d.ts.map +1 -0
- package/dist/validation/rules/no-centered-everything.js +59 -0
- package/dist/validation/rules/no-centered-everything.js.map +1 -0
- package/dist/validation/rules/no-default-fonts.d.ts +8 -0
- package/dist/validation/rules/no-default-fonts.d.ts.map +1 -0
- package/dist/validation/rules/no-default-fonts.js +111 -0
- package/dist/validation/rules/no-default-fonts.js.map +1 -0
- package/dist/validation/rules/no-div-soup.d.ts +6 -0
- package/dist/validation/rules/no-div-soup.d.ts.map +1 -0
- package/dist/validation/rules/no-div-soup.js +45 -0
- package/dist/validation/rules/no-div-soup.js.map +1 -0
- package/dist/validation/rules/no-duplicate-ctas.d.ts +6 -0
- package/dist/validation/rules/no-duplicate-ctas.d.ts.map +1 -0
- package/dist/validation/rules/no-duplicate-ctas.js +32 -0
- package/dist/validation/rules/no-duplicate-ctas.js.map +1 -0
- package/dist/validation/rules/no-generic-hero.d.ts +7 -0
- package/dist/validation/rules/no-generic-hero.d.ts.map +1 -0
- package/dist/validation/rules/no-generic-hero.js +46 -0
- package/dist/validation/rules/no-generic-hero.js.map +1 -0
- package/dist/validation/rules/no-icon-grid.d.ts +8 -0
- package/dist/validation/rules/no-icon-grid.d.ts.map +1 -0
- package/dist/validation/rules/no-icon-grid.js +43 -0
- package/dist/validation/rules/no-icon-grid.js.map +1 -0
- package/dist/validation/rules/no-lorem-ipsum.d.ts +6 -0
- package/dist/validation/rules/no-lorem-ipsum.d.ts.map +1 -0
- package/dist/validation/rules/no-lorem-ipsum.js +66 -0
- package/dist/validation/rules/no-lorem-ipsum.js.map +1 -0
- package/dist/validation/rules/no-missing-meta.d.ts +6 -0
- package/dist/validation/rules/no-missing-meta.d.ts.map +1 -0
- package/dist/validation/rules/no-missing-meta.js +49 -0
- package/dist/validation/rules/no-missing-meta.js.map +1 -0
- package/dist/validation/rules/no-missing-responsive.d.ts +6 -0
- package/dist/validation/rules/no-missing-responsive.d.ts.map +1 -0
- package/dist/validation/rules/no-missing-responsive.js +30 -0
- package/dist/validation/rules/no-missing-responsive.js.map +1 -0
- package/dist/validation/rules/no-placeholder-images.d.ts +6 -0
- package/dist/validation/rules/no-placeholder-images.d.ts.map +1 -0
- package/dist/validation/rules/no-placeholder-images.js +73 -0
- package/dist/validation/rules/no-placeholder-images.js.map +1 -0
- package/dist/validation/rules/no-saas-speak.d.ts +6 -0
- package/dist/validation/rules/no-saas-speak.d.ts.map +1 -0
- package/dist/validation/rules/no-saas-speak.js +74 -0
- package/dist/validation/rules/no-saas-speak.js.map +1 -0
- package/dist/validation/rules/no-slop-gradients.d.ts +6 -0
- package/dist/validation/rules/no-slop-gradients.d.ts.map +1 -0
- package/dist/validation/rules/no-slop-gradients.js +24 -0
- package/dist/validation/rules/no-slop-gradients.js.map +1 -0
- package/dist/validation/rules/no-uniform-spacing.d.ts +6 -0
- package/dist/validation/rules/no-uniform-spacing.d.ts.map +1 -0
- package/dist/validation/rules/no-uniform-spacing.js +64 -0
- package/dist/validation/rules/no-uniform-spacing.js.map +1 -0
- package/dist/validation/rules/types.d.ts +36 -0
- package/dist/validation/rules/types.d.ts.map +1 -0
- package/dist/validation/rules/types.js +7 -0
- package/dist/validation/rules/types.js.map +1 -0
- package/dist/validation/tailwind-parser.d.ts +36 -0
- package/dist/validation/tailwind-parser.d.ts.map +1 -0
- package/dist/validation/tailwind-parser.js +214 -0
- package/dist/validation/tailwind-parser.js.map +1 -0
- 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"}
|