real-prototypes-skill 2.0.0

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 (60) hide show
  1. package/.claude/skills/agent-browser-skill/SKILL.md +252 -0
  2. package/.claude/skills/real-prototypes-skill/.gitignore +188 -0
  3. package/.claude/skills/real-prototypes-skill/ACCESSIBILITY.md +668 -0
  4. package/.claude/skills/real-prototypes-skill/INSTALL.md +259 -0
  5. package/.claude/skills/real-prototypes-skill/LICENSE +21 -0
  6. package/.claude/skills/real-prototypes-skill/PUBLISH.md +310 -0
  7. package/.claude/skills/real-prototypes-skill/QUICKSTART.md +240 -0
  8. package/.claude/skills/real-prototypes-skill/README.md +442 -0
  9. package/.claude/skills/real-prototypes-skill/SKILL.md +329 -0
  10. package/.claude/skills/real-prototypes-skill/capture/capture-engine.js +1153 -0
  11. package/.claude/skills/real-prototypes-skill/capture/config.schema.json +170 -0
  12. package/.claude/skills/real-prototypes-skill/cli.js +596 -0
  13. package/.claude/skills/real-prototypes-skill/docs/TROUBLESHOOTING.md +278 -0
  14. package/.claude/skills/real-prototypes-skill/docs/schemas/capture-config.md +167 -0
  15. package/.claude/skills/real-prototypes-skill/docs/schemas/design-tokens.md +183 -0
  16. package/.claude/skills/real-prototypes-skill/docs/schemas/manifest.md +169 -0
  17. package/.claude/skills/real-prototypes-skill/examples/CLAUDE.md.example +73 -0
  18. package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/CLAUDE.md +136 -0
  19. package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/FEATURES.md +222 -0
  20. package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/README.md +82 -0
  21. package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/references/design-tokens.json +87 -0
  22. package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/references/screenshots/homepage-viewport.png +0 -0
  23. package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/references/screenshots/prototype-chatbot-final.png +0 -0
  24. package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/references/screenshots/prototype-fullpage-v2.png +0 -0
  25. package/.claude/skills/real-prototypes-skill/references/accessibility-fixes.md +298 -0
  26. package/.claude/skills/real-prototypes-skill/references/accessibility-report.json +253 -0
  27. package/.claude/skills/real-prototypes-skill/scripts/CAPTURE-ENHANCEMENTS.md +344 -0
  28. package/.claude/skills/real-prototypes-skill/scripts/IMPLEMENTATION-SUMMARY.md +517 -0
  29. package/.claude/skills/real-prototypes-skill/scripts/QUICK-START.md +229 -0
  30. package/.claude/skills/real-prototypes-skill/scripts/QUICKSTART-layout-analysis.md +148 -0
  31. package/.claude/skills/real-prototypes-skill/scripts/README-analyze-layout.md +407 -0
  32. package/.claude/skills/real-prototypes-skill/scripts/analyze-layout.js +880 -0
  33. package/.claude/skills/real-prototypes-skill/scripts/capture-platform.js +203 -0
  34. package/.claude/skills/real-prototypes-skill/scripts/comprehensive-capture.js +597 -0
  35. package/.claude/skills/real-prototypes-skill/scripts/create-manifest.js +338 -0
  36. package/.claude/skills/real-prototypes-skill/scripts/enterprise-pipeline.js +428 -0
  37. package/.claude/skills/real-prototypes-skill/scripts/extract-tokens.js +468 -0
  38. package/.claude/skills/real-prototypes-skill/scripts/full-site-capture.js +738 -0
  39. package/.claude/skills/real-prototypes-skill/scripts/generate-tailwind-config.js +296 -0
  40. package/.claude/skills/real-prototypes-skill/scripts/integrate-accessibility.sh +161 -0
  41. package/.claude/skills/real-prototypes-skill/scripts/manifest-schema.json +302 -0
  42. package/.claude/skills/real-prototypes-skill/scripts/setup-prototype.sh +167 -0
  43. package/.claude/skills/real-prototypes-skill/scripts/test-analyze-layout.js +338 -0
  44. package/.claude/skills/real-prototypes-skill/scripts/test-validation.js +307 -0
  45. package/.claude/skills/real-prototypes-skill/scripts/validate-accessibility.js +598 -0
  46. package/.claude/skills/real-prototypes-skill/scripts/validate-manifest.js +499 -0
  47. package/.claude/skills/real-prototypes-skill/scripts/validate-output.js +361 -0
  48. package/.claude/skills/real-prototypes-skill/scripts/validate-prerequisites.js +319 -0
  49. package/.claude/skills/real-prototypes-skill/scripts/verify-layout-analysis.sh +77 -0
  50. package/.claude/skills/real-prototypes-skill/templates/dashboard-widget.tsx.template +91 -0
  51. package/.claude/skills/real-prototypes-skill/templates/data-table.tsx.template +193 -0
  52. package/.claude/skills/real-prototypes-skill/templates/form-section.tsx.template +250 -0
  53. package/.claude/skills/real-prototypes-skill/templates/modal-dialog.tsx.template +239 -0
  54. package/.claude/skills/real-prototypes-skill/templates/nav-item.tsx.template +265 -0
  55. package/.claude/skills/real-prototypes-skill/validation/validation-engine.js +559 -0
  56. package/.env.example +74 -0
  57. package/LICENSE +21 -0
  58. package/README.md +444 -0
  59. package/bin/cli.js +319 -0
  60. package/package.json +59 -0
@@ -0,0 +1,361 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * POST-GENERATION VALIDATION
4
+ *
5
+ * This script validates that the generated prototype:
6
+ * 1. Uses ONLY colors from the extracted design tokens
7
+ * 2. Uses the correct font families
8
+ * 3. Doesn't contain any "made up" colors
9
+ *
10
+ * Exit codes:
11
+ * 0 = All validations passed
12
+ * 1 = Validation failed - colors don't match
13
+ */
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+
18
+ const REFERENCES_DIR = 'references';
19
+ const PROTOTYPE_DIR = 'prototype';
20
+
21
+ // Colors that are always allowed (CSS basics)
22
+ const ALLOWED_BASE_COLORS = [
23
+ '#fff', '#ffffff', '#000', '#000000',
24
+ 'transparent', 'inherit', 'currentColor'
25
+ ];
26
+
27
+ // Tailwind color classes that might sneak through
28
+ const FORBIDDEN_TAILWIND_PATTERNS = [
29
+ /\b(teal|cyan|emerald|violet|purple|pink|rose|fuchsia|indigo)-\d{2,3}\b/,
30
+ /\bbg-(teal|cyan|emerald|violet|purple|pink|rose|fuchsia|indigo)/,
31
+ /\btext-(teal|cyan|emerald|violet|purple|pink|rose|fuchsia|indigo)/,
32
+ /\bborder-(teal|cyan|emerald|violet|purple|pink|rose|fuchsia|indigo)/
33
+ ];
34
+
35
+ function log(type, message) {
36
+ const icons = {
37
+ error: '❌',
38
+ warning: '⚠️',
39
+ success: '✅',
40
+ info: 'ℹ️'
41
+ };
42
+ console.log(`${icons[type] || '•'} ${message}`);
43
+ }
44
+
45
+ function loadDesignTokens() {
46
+ const tokensPath = path.join(REFERENCES_DIR, 'design-tokens.json');
47
+ if (!fs.existsSync(tokensPath)) {
48
+ throw new Error('design-tokens.json not found. Run validation-prerequisites.js first.');
49
+ }
50
+ return JSON.parse(fs.readFileSync(tokensPath, 'utf8'));
51
+ }
52
+
53
+ function getAllowedColors(tokens) {
54
+ const allowed = new Set(ALLOWED_BASE_COLORS);
55
+
56
+ // Add all colors from rawColors
57
+ if (tokens.rawColors) {
58
+ tokens.rawColors.forEach(([color]) => {
59
+ allowed.add(color.toLowerCase());
60
+ });
61
+ }
62
+
63
+ // Add all categorized colors
64
+ function extractColors(obj) {
65
+ if (!obj) return;
66
+ if (typeof obj === 'string' && obj.startsWith('#')) {
67
+ allowed.add(obj.toLowerCase());
68
+ } else if (typeof obj === 'object') {
69
+ Object.values(obj).forEach(extractColors);
70
+ }
71
+ }
72
+
73
+ extractColors(tokens.colors);
74
+
75
+ return allowed;
76
+ }
77
+
78
+ function extractColorsFromCode(code) {
79
+ const colors = new Set();
80
+
81
+ // Extract hex colors
82
+ const hexMatches = code.match(/#[0-9a-fA-F]{3,8}/g) || [];
83
+ hexMatches.forEach(color => colors.add(color.toLowerCase()));
84
+
85
+ // Extract rgb/rgba colors
86
+ const rgbMatches = code.match(/rgba?\([^)]+\)/g) || [];
87
+ rgbMatches.forEach(color => colors.add(color.toLowerCase()));
88
+
89
+ return colors;
90
+ }
91
+
92
+ function findForbiddenTailwindClasses(code) {
93
+ const forbidden = [];
94
+
95
+ FORBIDDEN_TAILWIND_PATTERNS.forEach(pattern => {
96
+ const matches = code.match(pattern);
97
+ if (matches) {
98
+ forbidden.push(...matches);
99
+ }
100
+ });
101
+
102
+ return [...new Set(forbidden)];
103
+ }
104
+
105
+ function validateGeneratedFile(filePath, allowedColors, tokens) {
106
+ const code = fs.readFileSync(filePath, 'utf8');
107
+ const result = {
108
+ file: filePath,
109
+ passed: true,
110
+ invalidColors: [],
111
+ forbiddenClasses: [],
112
+ warnings: []
113
+ };
114
+
115
+ // Check for colors not in design tokens
116
+ const usedColors = extractColorsFromCode(code);
117
+ usedColors.forEach(color => {
118
+ // Normalize 3-digit hex to 6-digit
119
+ let normalizedColor = color;
120
+ if (/^#[0-9a-f]{3}$/i.test(color)) {
121
+ normalizedColor = `#${color[1]}${color[1]}${color[2]}${color[2]}${color[3]}${color[3]}`;
122
+ }
123
+
124
+ if (!allowedColors.has(normalizedColor) && !allowedColors.has(color)) {
125
+ result.invalidColors.push(color);
126
+ result.passed = false;
127
+ }
128
+ });
129
+
130
+ // Check for forbidden Tailwind classes
131
+ const forbiddenClasses = findForbiddenTailwindClasses(code);
132
+ if (forbiddenClasses.length > 0) {
133
+ result.forbiddenClasses = forbiddenClasses;
134
+ result.passed = false;
135
+ }
136
+
137
+ // Check if primary color is being used
138
+ const primaryColor = tokens.colors?.primary;
139
+ if (primaryColor && !code.includes(primaryColor)) {
140
+ result.warnings.push(`Primary color ${primaryColor} not found in file - buttons/links may have wrong color`);
141
+ }
142
+
143
+ // Check for font family usage
144
+ const fontFamily = tokens.fonts?.primary;
145
+ if (fontFamily && !code.toLowerCase().includes(fontFamily.toLowerCase().split(',')[0])) {
146
+ result.warnings.push(`Primary font "${fontFamily}" not found in file`);
147
+ }
148
+
149
+ return result;
150
+ }
151
+
152
+ function findPrototypeFiles(dir, files = []) {
153
+ const items = fs.readdirSync(dir);
154
+
155
+ items.forEach(item => {
156
+ const fullPath = path.join(dir, item);
157
+ const stat = fs.statSync(fullPath);
158
+
159
+ if (stat.isDirectory()) {
160
+ // Skip node_modules and .next
161
+ if (item !== 'node_modules' && item !== '.next') {
162
+ findPrototypeFiles(fullPath, files);
163
+ }
164
+ } else if (
165
+ item.endsWith('.tsx') ||
166
+ item.endsWith('.jsx') ||
167
+ item.endsWith('.ts') ||
168
+ item.endsWith('.css')
169
+ ) {
170
+ files.push(fullPath);
171
+ }
172
+ });
173
+
174
+ return files;
175
+ }
176
+
177
+ function generateReport(results, tokens) {
178
+ const report = {
179
+ timestamp: new Date().toISOString(),
180
+ passed: results.every(r => r.passed),
181
+ summary: {
182
+ filesChecked: results.length,
183
+ filesPassed: results.filter(r => r.passed).length,
184
+ filesFailed: results.filter(r => !r.passed).length,
185
+ totalInvalidColors: results.reduce((sum, r) => sum + r.invalidColors.length, 0),
186
+ totalForbiddenClasses: results.reduce((sum, r) => sum + r.forbiddenClasses.length, 0)
187
+ },
188
+ allowedPrimaryColor: tokens.colors?.primary,
189
+ allowedFontFamily: tokens.fonts?.primary,
190
+ results: results
191
+ };
192
+
193
+ const reportPath = path.join(REFERENCES_DIR, 'output-validation-report.json');
194
+ fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
195
+
196
+ return report;
197
+ }
198
+
199
+ function suggestFixes(results, tokens) {
200
+ const allInvalidColors = [...new Set(results.flatMap(r => r.invalidColors))];
201
+ const allForbiddenClasses = [...new Set(results.flatMap(r => r.forbiddenClasses))];
202
+
203
+ if (allInvalidColors.length === 0 && allForbiddenClasses.length === 0) {
204
+ return null;
205
+ }
206
+
207
+ console.log('\n📝 SUGGESTED FIXES:\n');
208
+
209
+ if (allInvalidColors.length > 0) {
210
+ console.log('Invalid colors found - replace with these design token colors:');
211
+ console.log('');
212
+
213
+ allInvalidColors.forEach(invalidColor => {
214
+ const suggestion = findClosestColor(invalidColor, tokens);
215
+ console.log(` ${invalidColor} → ${suggestion.color} (${suggestion.category})`);
216
+ });
217
+ }
218
+
219
+ if (allForbiddenClasses.length > 0) {
220
+ console.log('\nForbidden Tailwind classes - use inline styles with hex values:');
221
+ console.log('');
222
+
223
+ allForbiddenClasses.forEach(cls => {
224
+ console.log(` "${cls}" → style={{ color: "${tokens.colors?.primary || '#1c64f2'}" }}`);
225
+ });
226
+ }
227
+
228
+ return { invalidColors: allInvalidColors, forbiddenClasses: allForbiddenClasses };
229
+ }
230
+
231
+ function findClosestColor(targetColor, tokens) {
232
+ // Simple suggestion based on color characteristics
233
+ const hex = targetColor.startsWith('#') ? targetColor : null;
234
+ if (!hex) return { color: tokens.colors?.primary || '#1c64f2', category: 'primary' };
235
+
236
+ const rgb = hexToRgb(hex);
237
+ if (!rgb) return { color: tokens.colors?.primary || '#1c64f2', category: 'primary' };
238
+
239
+ const brightness = (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
240
+
241
+ // Suggest based on brightness
242
+ if (brightness > 240) {
243
+ return { color: tokens.colors?.background?.white || '#ffffff', category: 'background.white' };
244
+ } else if (brightness > 200) {
245
+ return { color: tokens.colors?.background?.light || '#f6f6f5', category: 'background.light' };
246
+ } else if (brightness > 150) {
247
+ return { color: tokens.colors?.border?.default || '#e7e7e6', category: 'border' };
248
+ } else if (brightness > 100) {
249
+ return { color: tokens.colors?.text?.secondary || '#6b7280', category: 'text.secondary' };
250
+ } else if (brightness > 50) {
251
+ return { color: tokens.colors?.text?.primary || '#191918', category: 'text.primary' };
252
+ } else {
253
+ return { color: tokens.colors?.sidebar?.dark || '#0e2933', category: 'sidebar' };
254
+ }
255
+ }
256
+
257
+ function hexToRgb(hex) {
258
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
259
+ return result ? {
260
+ r: parseInt(result[1], 16),
261
+ g: parseInt(result[2], 16),
262
+ b: parseInt(result[3], 16)
263
+ } : null;
264
+ }
265
+
266
+ async function runValidation(options = {}) {
267
+ console.log('\n' + '='.repeat(60));
268
+ console.log('🔍 POST-GENERATION VALIDATION');
269
+ console.log('='.repeat(60) + '\n');
270
+
271
+ // Load design tokens
272
+ let tokens;
273
+ try {
274
+ tokens = loadDesignTokens();
275
+ log('success', `Loaded design tokens (${tokens.totalColorsFound || 'unknown'} colors)`);
276
+ } catch (error) {
277
+ log('error', error.message);
278
+ return { passed: false, errors: [error.message] };
279
+ }
280
+
281
+ // Get allowed colors
282
+ const allowedColors = getAllowedColors(tokens);
283
+ log('info', `${allowedColors.size} colors in allowed palette`);
284
+
285
+ // Find all prototype files
286
+ const prototypeDir = options.prototypeDir || PROTOTYPE_DIR;
287
+ if (!fs.existsSync(prototypeDir)) {
288
+ log('error', `Prototype directory not found: ${prototypeDir}`);
289
+ return { passed: false, errors: ['Prototype directory not found'] };
290
+ }
291
+
292
+ const files = findPrototypeFiles(path.join(prototypeDir, 'src'));
293
+ log('info', `Found ${files.length} source files to validate`);
294
+
295
+ // Validate each file
296
+ const results = [];
297
+ files.forEach(file => {
298
+ const result = validateGeneratedFile(file, allowedColors, tokens);
299
+ results.push(result);
300
+
301
+ if (!result.passed) {
302
+ log('error', `${path.relative(prototypeDir, file)}`);
303
+ if (result.invalidColors.length > 0) {
304
+ console.log(` Invalid colors: ${result.invalidColors.join(', ')}`);
305
+ }
306
+ if (result.forbiddenClasses.length > 0) {
307
+ console.log(` Forbidden classes: ${result.forbiddenClasses.join(', ')}`);
308
+ }
309
+ } else if (result.warnings.length > 0) {
310
+ log('warning', `${path.relative(prototypeDir, file)}`);
311
+ result.warnings.forEach(w => console.log(` ${w}`));
312
+ } else {
313
+ log('success', `${path.relative(prototypeDir, file)}`);
314
+ }
315
+ });
316
+
317
+ // Generate report
318
+ const report = generateReport(results, tokens);
319
+
320
+ // Show fixes if needed
321
+ if (!report.passed) {
322
+ suggestFixes(results, tokens);
323
+ }
324
+
325
+ // Final summary
326
+ console.log('\n' + '='.repeat(60));
327
+ if (report.passed) {
328
+ console.log('✅ OUTPUT VALIDATION PASSED');
329
+ console.log(' All colors match the captured design tokens');
330
+ } else {
331
+ console.log('❌ OUTPUT VALIDATION FAILED');
332
+ console.log(` ${report.summary.totalInvalidColors} invalid colors found`);
333
+ console.log(` ${report.summary.totalForbiddenClasses} forbidden Tailwind classes found`);
334
+ console.log('\n Fix the issues above and run validation again.');
335
+ }
336
+ console.log('='.repeat(60) + '\n');
337
+
338
+ return {
339
+ passed: report.passed,
340
+ report: report
341
+ };
342
+ }
343
+
344
+ // CLI interface
345
+ if (require.main === module) {
346
+ const args = process.argv.slice(2);
347
+ const options = {};
348
+
349
+ for (let i = 0; i < args.length; i++) {
350
+ if (args[i] === '--dir' && args[i + 1]) {
351
+ options.prototypeDir = args[i + 1];
352
+ i++;
353
+ }
354
+ }
355
+
356
+ runValidation(options).then(result => {
357
+ process.exit(result.passed ? 0 : 1);
358
+ });
359
+ }
360
+
361
+ module.exports = { runValidation };
@@ -0,0 +1,319 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * PRE-GENERATION VALIDATION
4
+ *
5
+ * This script MUST pass before any prototype generation can begin.
6
+ * It validates that all required reference materials exist and are complete.
7
+ *
8
+ * Exit codes:
9
+ * 0 = All validations passed, safe to proceed
10
+ * 1 = Validation failed, DO NOT proceed with generation
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ const REFERENCES_DIR = 'references';
17
+
18
+ // Minimum requirements for generation
19
+ const REQUIREMENTS = {
20
+ minColors: 10,
21
+ minFonts: 1,
22
+ requiredColorCategories: ['primary', 'text', 'background', 'border'],
23
+ requiredFiles: [
24
+ 'design-tokens.json',
25
+ 'manifest.json'
26
+ ]
27
+ };
28
+
29
+ class ValidationError extends Error {
30
+ constructor(message, category) {
31
+ super(message);
32
+ this.category = category;
33
+ }
34
+ }
35
+
36
+ function log(type, message) {
37
+ const icons = {
38
+ error: '❌',
39
+ warning: '⚠️',
40
+ success: '✅',
41
+ info: 'ℹ️'
42
+ };
43
+ console.log(`${icons[type] || '•'} ${message}`);
44
+ }
45
+
46
+ function validateFileExists(filePath, description) {
47
+ const fullPath = path.join(REFERENCES_DIR, filePath);
48
+ if (!fs.existsSync(fullPath)) {
49
+ throw new ValidationError(
50
+ `Missing required file: ${filePath}\n ${description}`,
51
+ 'file'
52
+ );
53
+ }
54
+ return fullPath;
55
+ }
56
+
57
+ function validateDesignTokens() {
58
+ log('info', 'Validating design tokens...');
59
+
60
+ const tokensPath = validateFileExists(
61
+ 'design-tokens.json',
62
+ 'Run comprehensive-capture.js first to extract design tokens'
63
+ );
64
+
65
+ const tokens = JSON.parse(fs.readFileSync(tokensPath, 'utf8'));
66
+ const errors = [];
67
+
68
+ // Check total colors
69
+ const totalColors = tokens.totalColorsFound || Object.keys(tokens.rawColors || {}).length;
70
+ if (totalColors < REQUIREMENTS.minColors) {
71
+ errors.push(`Insufficient colors: found ${totalColors}, need at least ${REQUIREMENTS.minColors}`);
72
+ }
73
+
74
+ // Check required color categories
75
+ REQUIREMENTS.requiredColorCategories.forEach(category => {
76
+ if (!tokens.colors || !tokens.colors[category]) {
77
+ errors.push(`Missing color category: ${category}`);
78
+ } else if (category === 'primary' && !tokens.colors.primary) {
79
+ errors.push('Primary color not identified');
80
+ }
81
+ });
82
+
83
+ // Check fonts
84
+ if (!tokens.fonts || !tokens.fonts.families || tokens.fonts.families.length < REQUIREMENTS.minFonts) {
85
+ errors.push('No font families extracted');
86
+ }
87
+
88
+ // Check for specific critical colors
89
+ if (!tokens.colors?.primary) {
90
+ errors.push('Primary/accent color not identified - buttons and links will use wrong color');
91
+ }
92
+ if (!tokens.colors?.sidebar?.dark && !tokens.colors?.sidebar?.bg) {
93
+ // Not an error, just a warning
94
+ log('warning', 'Sidebar color not identified - may need manual verification');
95
+ }
96
+
97
+ if (errors.length > 0) {
98
+ throw new ValidationError(
99
+ `Design tokens incomplete:\n ${errors.join('\n ')}`,
100
+ 'tokens'
101
+ );
102
+ }
103
+
104
+ log('success', `Design tokens valid (${totalColors} colors, ${tokens.fonts.families.length} fonts)`);
105
+ return tokens;
106
+ }
107
+
108
+ function validateManifest() {
109
+ log('info', 'Validating capture manifest...');
110
+
111
+ const manifestPath = validateFileExists(
112
+ 'manifest.json',
113
+ 'Run comprehensive-capture.js first to create manifest'
114
+ );
115
+
116
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
117
+ const errors = [];
118
+
119
+ if (!manifest.platform?.baseUrl) {
120
+ errors.push('Missing platform baseUrl in manifest');
121
+ }
122
+
123
+ if (!manifest.pages || manifest.pages.length === 0) {
124
+ errors.push('No pages captured in manifest');
125
+ }
126
+
127
+ // Validate each page has screenshots
128
+ manifest.pages?.forEach(page => {
129
+ if (!page.captures || page.captures.length === 0) {
130
+ errors.push(`Page "${page.name}" has no captures`);
131
+ }
132
+ });
133
+
134
+ if (errors.length > 0) {
135
+ throw new ValidationError(
136
+ `Manifest incomplete:\n ${errors.join('\n ')}`,
137
+ 'manifest'
138
+ );
139
+ }
140
+
141
+ log('success', `Manifest valid (${manifest.pages.length} pages, ${manifest.totalScreenshots || 'unknown'} screenshots)`);
142
+ return manifest;
143
+ }
144
+
145
+ function validateScreenshots(pageName = null) {
146
+ log('info', 'Validating screenshots...');
147
+
148
+ const screenshotDir = path.join(REFERENCES_DIR, 'screenshots');
149
+ if (!fs.existsSync(screenshotDir)) {
150
+ throw new ValidationError(
151
+ 'Screenshots directory missing',
152
+ 'screenshots'
153
+ );
154
+ }
155
+
156
+ const screenshots = fs.readdirSync(screenshotDir).filter(f => f.endsWith('.png'));
157
+
158
+ if (screenshots.length === 0) {
159
+ throw new ValidationError(
160
+ 'No screenshots found in references/screenshots/',
161
+ 'screenshots'
162
+ );
163
+ }
164
+
165
+ // If specific page requested, check it exists
166
+ if (pageName) {
167
+ const pageScreenshot = screenshots.find(s =>
168
+ s.startsWith(pageName) || s.includes(pageName)
169
+ );
170
+ if (!pageScreenshot) {
171
+ throw new ValidationError(
172
+ `No screenshot found for page: ${pageName}\n Available: ${screenshots.join(', ')}`,
173
+ 'screenshots'
174
+ );
175
+ }
176
+ }
177
+
178
+ // Check screenshot file sizes (should be > 10KB for valid captures)
179
+ const smallScreenshots = screenshots.filter(s => {
180
+ const stats = fs.statSync(path.join(screenshotDir, s));
181
+ return stats.size < 10000; // 10KB minimum
182
+ });
183
+
184
+ if (smallScreenshots.length > 0) {
185
+ log('warning', `Small screenshots detected (may be incomplete): ${smallScreenshots.join(', ')}`);
186
+ }
187
+
188
+ log('success', `Screenshots valid (${screenshots.length} files)`);
189
+ return screenshots;
190
+ }
191
+
192
+ function validateComponentStyles() {
193
+ log('info', 'Validating component styles...');
194
+
195
+ const stylesPath = path.join(REFERENCES_DIR, 'component-styles.json');
196
+
197
+ if (!fs.existsSync(stylesPath)) {
198
+ log('warning', 'component-styles.json not found - component matching may be less accurate');
199
+ return null;
200
+ }
201
+
202
+ const styles = JSON.parse(fs.readFileSync(stylesPath, 'utf8'));
203
+ log('success', 'Component styles found');
204
+ return styles;
205
+ }
206
+
207
+ function generateValidationReport(results) {
208
+ const report = {
209
+ timestamp: new Date().toISOString(),
210
+ passed: results.passed,
211
+ errors: results.errors,
212
+ warnings: results.warnings,
213
+ summary: {
214
+ totalColors: results.tokens?.totalColorsFound || 0,
215
+ primaryColor: results.tokens?.colors?.primary || 'NOT FOUND',
216
+ fontFamily: results.tokens?.fonts?.primary || 'NOT FOUND',
217
+ pagesCaptures: results.manifest?.pages?.length || 0,
218
+ totalScreenshots: results.screenshots?.length || 0
219
+ }
220
+ };
221
+
222
+ const reportPath = path.join(REFERENCES_DIR, 'validation-report.json');
223
+ fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
224
+
225
+ return report;
226
+ }
227
+
228
+ async function runValidation(options = {}) {
229
+ console.log('\n' + '='.repeat(60));
230
+ console.log('🔍 PRE-GENERATION VALIDATION');
231
+ console.log('='.repeat(60) + '\n');
232
+
233
+ const results = {
234
+ passed: true,
235
+ errors: [],
236
+ warnings: [],
237
+ tokens: null,
238
+ manifest: null,
239
+ screenshots: null
240
+ };
241
+
242
+ try {
243
+ // 1. Validate design tokens (CRITICAL)
244
+ results.tokens = validateDesignTokens();
245
+ } catch (error) {
246
+ results.passed = false;
247
+ results.errors.push(error.message);
248
+ log('error', error.message);
249
+ }
250
+
251
+ try {
252
+ // 2. Validate manifest (CRITICAL)
253
+ results.manifest = validateManifest();
254
+ } catch (error) {
255
+ results.passed = false;
256
+ results.errors.push(error.message);
257
+ log('error', error.message);
258
+ }
259
+
260
+ try {
261
+ // 3. Validate screenshots (CRITICAL)
262
+ results.screenshots = validateScreenshots(options.pageName);
263
+ } catch (error) {
264
+ results.passed = false;
265
+ results.errors.push(error.message);
266
+ log('error', error.message);
267
+ }
268
+
269
+ try {
270
+ // 4. Validate component styles (OPTIONAL but recommended)
271
+ results.componentStyles = validateComponentStyles();
272
+ } catch (error) {
273
+ results.warnings.push(error.message);
274
+ log('warning', error.message);
275
+ }
276
+
277
+ // Generate report
278
+ const report = generateValidationReport(results);
279
+
280
+ // Final summary
281
+ console.log('\n' + '='.repeat(60));
282
+ if (results.passed) {
283
+ console.log('✅ VALIDATION PASSED - Safe to proceed with generation');
284
+ console.log('='.repeat(60));
285
+ console.log('\nKey values for generation:');
286
+ console.log(` Primary color: ${report.summary.primaryColor}`);
287
+ console.log(` Font family: ${report.summary.fontFamily}`);
288
+ console.log(` Screenshots: ${report.summary.totalScreenshots}`);
289
+ } else {
290
+ console.log('❌ VALIDATION FAILED - DO NOT proceed with generation');
291
+ console.log('='.repeat(60));
292
+ console.log('\nErrors that must be fixed:');
293
+ results.errors.forEach((e, i) => console.log(` ${i + 1}. ${e}`));
294
+ console.log('\nRun comprehensive-capture.js to fix these issues.');
295
+ }
296
+ console.log('');
297
+
298
+ return results;
299
+ }
300
+
301
+ // CLI interface
302
+ if (require.main === module) {
303
+ const args = process.argv.slice(2);
304
+ const options = {};
305
+
306
+ // Parse arguments
307
+ for (let i = 0; i < args.length; i++) {
308
+ if (args[i] === '--page' && args[i + 1]) {
309
+ options.pageName = args[i + 1];
310
+ i++;
311
+ }
312
+ }
313
+
314
+ runValidation(options).then(results => {
315
+ process.exit(results.passed ? 0 : 1);
316
+ });
317
+ }
318
+
319
+ module.exports = { runValidation };