@tangle-network/browser-agent-driver 0.11.0 → 0.12.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.
- package/dist/brain/index.d.ts +1 -0
- package/dist/brain/index.d.ts.map +1 -1
- package/dist/brain/index.js +7 -1
- package/dist/brain/index.js.map +1 -1
- package/dist/cli-auth.d.ts +19 -0
- package/dist/cli-auth.d.ts.map +1 -0
- package/dist/cli-auth.js +195 -0
- package/dist/cli-auth.js.map +1 -0
- package/dist/cli-design-audit.d.ts +8 -0
- package/dist/cli-design-audit.d.ts.map +1 -1
- package/dist/cli-design-audit.js +608 -29
- package/dist/cli-design-audit.js.map +1 -1
- package/dist/cli-ui.d.ts.map +1 -1
- package/dist/cli-ui.js +18 -0
- package/dist/cli-ui.js.map +1 -1
- package/dist/cli.js +48 -1
- package/dist/cli.js.map +1 -1
- package/dist/drivers/playwright.d.ts +1 -0
- package/dist/drivers/playwright.d.ts.map +1 -1
- package/dist/drivers/playwright.js +21 -5
- package/dist/drivers/playwright.js.map +1 -1
- package/dist/drivers/types.d.ts +8 -0
- package/dist/drivers/types.d.ts.map +1 -1
- package/dist/runner/effect-verification.d.ts +12 -0
- package/dist/runner/effect-verification.d.ts.map +1 -0
- package/dist/runner/effect-verification.js +169 -0
- package/dist/runner/effect-verification.js.map +1 -0
- package/dist/runner/index.d.ts +1 -0
- package/dist/runner/index.d.ts.map +1 -1
- package/dist/runner/index.js +2 -0
- package/dist/runner/index.js.map +1 -1
- package/dist/runner/runner.d.ts.map +1 -1
- package/dist/runner/runner.js +10 -57
- package/dist/runner/runner.js.map +1 -1
- package/dist/runner.d.ts +1 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +2 -0
- package/dist/runner.js.map +1 -1
- package/dist/types.d.ts +47 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -1
package/dist/cli-design-audit.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import * as fs from 'node:fs';
|
|
8
8
|
import * as path from 'node:path';
|
|
9
|
+
import { execSync } from 'node:child_process';
|
|
9
10
|
import chalk from 'chalk';
|
|
10
11
|
import { chromium } from 'playwright';
|
|
11
12
|
import { Brain } from './brain/index.js';
|
|
@@ -67,55 +68,169 @@ MARKETING/LANDING PAGE AUDIT — evaluate as a potential customer deciding in 10
|
|
|
67
68
|
- Footer: is navigation complete? Legal links present?
|
|
68
69
|
|
|
69
70
|
CALIBRATION: Stripe, Linear, Vercel = 9. Average startup landing page = 5. Template sites = 3.`,
|
|
71
|
+
vibecoded: `
|
|
72
|
+
VIBECODED / AI-GENERATED APP AUDIT — evaluate as a design-literate user who can smell defaults:
|
|
73
|
+
|
|
74
|
+
TEMPLATE DETECTION (the #1 sin of vibecoded apps):
|
|
75
|
+
- Is this clearly an unmodified shadcn/ui, MUI, Ant Design, or Chakra template? Score ceiling: 4 if yes.
|
|
76
|
+
- Default border-radius (6-8px shadcn, 4px MUI), default color palette (zinc/slate grays, blue-600 primary)?
|
|
77
|
+
- Default component spacing with no customization? Standard card shadows? Stock empty states?
|
|
78
|
+
- "Looks like every other AI-generated app" = automatic 3-4 score.
|
|
79
|
+
|
|
80
|
+
HIERARCHY & INFORMATION ARCHITECTURE:
|
|
81
|
+
- Is everything the same visual weight? (Common AI pattern: all cards same size, no primary/secondary distinction)
|
|
82
|
+
- Is there clear information hierarchy? Primary action vs secondary vs tertiary?
|
|
83
|
+
- Does the layout have purpose or is it "centered column of cards" (the AI default)?
|
|
84
|
+
- Navigation: is it a dumped list of features or thoughtfully organized?
|
|
85
|
+
|
|
86
|
+
DESIGN SYSTEM COHERENCE:
|
|
87
|
+
- Are there more than 3 distinct border-radius values? (Incoherent)
|
|
88
|
+
- Color palette: intentional and limited (4-6 colors) or random accumulation?
|
|
89
|
+
- Spacing: consistent rhythm on an 8px grid, or arbitrary per-component?
|
|
90
|
+
- Typography: deliberate scale with 3-4 sizes, or every component picking its own?
|
|
91
|
+
- Are interactive states (hover, focus, active, disabled) designed or browser-default?
|
|
92
|
+
|
|
93
|
+
CRAFT SIGNALS (what separates 7 from 9):
|
|
94
|
+
- Custom icons or generic Lucide/Heroicons dump?
|
|
95
|
+
- Micro-interactions: button press feedback, page transitions, loading skeletons?
|
|
96
|
+
- Empty states: designed illustrations or "No data found" text?
|
|
97
|
+
- Error states: helpful messages with recovery actions or raw error strings?
|
|
98
|
+
- Dark mode (if present): properly designed or just "invert colors"?
|
|
99
|
+
- Content-first: does real content drive the layout, or is it a container waiting for content?
|
|
100
|
+
|
|
101
|
+
AGENTIC APP SPECIFICS:
|
|
102
|
+
- Agent status indicators: is it clear what the agent is doing? Progress feedback?
|
|
103
|
+
- Streaming/loading: smooth token streaming or janky text replacement?
|
|
104
|
+
- Conversation UI: proper message bubbles with timestamps, or plain text dump?
|
|
105
|
+
- Tool call visualization: can the user see what tools the agent used?
|
|
106
|
+
- Error recovery: when the agent fails, is there a clear retry/edit path?
|
|
107
|
+
|
|
108
|
+
CALIBRATION:
|
|
109
|
+
- 9-10: Custom design system, thoughtful hierarchy, polished interactions (Linear, Cursor, v0.dev)
|
|
110
|
+
- 7-8: Modified template with intentional design decisions, consistent system
|
|
111
|
+
- 5-6: Lightly customized template, functional but generic (most AI-generated apps)
|
|
112
|
+
- 3-4: Unmodified component library, no design investment, "it works" energy
|
|
113
|
+
- 1-2: Broken layout, clashing styles, unusable
|
|
114
|
+
|
|
115
|
+
Most vibecoded apps score 3-5. The ceiling for unmodified templates is 4 regardless of functionality.`,
|
|
70
116
|
};
|
|
71
117
|
// ---------------------------------------------------------------------------
|
|
72
118
|
// Upgraded system prompt — much more opinionated than the original
|
|
73
119
|
// ---------------------------------------------------------------------------
|
|
74
120
|
function buildAuditPrompt(profile) {
|
|
75
121
|
const rubric = PROFILE_RUBRICS[profile] || PROFILE_RUBRICS.general;
|
|
76
|
-
return `You are a
|
|
77
|
-
|
|
122
|
+
return `You are a principal design engineer who has shipped design systems at Linear, Stripe, and Vercel. You review with the precision of a typographer and the ruthlessness of a design director. You have built and maintained production design systems used by millions.
|
|
123
|
+
|
|
124
|
+
Your job: perform an exhaustive visual design audit of this page. You must be specific enough that a developer could fix every issue from your report alone — reference exact elements, computed values, pixel measurements, and CSS properties.
|
|
78
125
|
|
|
79
|
-
|
|
126
|
+
EVALUATION FRAMEWORK (score each area 1-10, then weight into overall):
|
|
80
127
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
128
|
+
1. LAYOUT & GRID (weight: 15%)
|
|
129
|
+
- Is there a consistent grid system? What grid unit? (4px, 8px, etc.)
|
|
130
|
+
- Column alignment: do content blocks align to the same left/right edges?
|
|
131
|
+
- Content width: is max-width appropriate? (prose: 65-75ch, app: fluid with sidebar)
|
|
132
|
+
- Responsive: does the layout reflow intentionally or just shrink?
|
|
133
|
+
- Z-index layering: any stacking context issues? Overlapping elements?
|
|
134
|
+
- Check for: orphaned elements floating outside the grid, inconsistent container padding
|
|
135
|
+
|
|
136
|
+
2. TYPOGRAPHY SYSTEM (weight: 15%)
|
|
137
|
+
- Type scale: is there a clear hierarchy? Count distinct font-size values — more than 5-6 suggests no scale.
|
|
138
|
+
- Line height: body text should be 1.4-1.6, headings 1.1-1.3. Flag violations.
|
|
139
|
+
- Letter spacing: headings often need negative (-0.01 to -0.03em). Is it tuned?
|
|
140
|
+
- Font pairing: max 2 families (heading + body). Flag 3+.
|
|
141
|
+
- Text rendering: are long paragraphs wider than 75ch? That harms readability.
|
|
142
|
+
- Orphans/widows: any single-word last lines in headings?
|
|
143
|
+
- Font loading: is there FOUT/FOIT? Font-display strategy?
|
|
144
|
+
|
|
145
|
+
3. COLOR & CONTRAST (weight: 15%)
|
|
146
|
+
- WCAG AA compliance: normal text needs 4.5:1, large text (18px+/14px+ bold) needs 3:1. ESTIMATE ratios.
|
|
147
|
+
- Palette size: count distinct hues. More than 5-6 non-neutral hues = incoherent.
|
|
148
|
+
- Semantic color usage: is the primary color used consistently for primary actions?
|
|
149
|
+
- Background layering: do nested surfaces have clear elevation (bg-0, bg-1, bg-2)?
|
|
150
|
+
- Gray scale: are grays consistent? All blue-gray, or mixed warm/cool? Mixed = incoherent.
|
|
151
|
+
- Accent usage: are accent colors used sparingly or splashed everywhere?
|
|
152
|
+
|
|
153
|
+
4. SPACING & RHYTHM (weight: 15%)
|
|
154
|
+
- Grid adherence: what % of spacing values are multiples of the base unit?
|
|
155
|
+
- Vertical rhythm: are section gaps consistent? Measure gap between each major section.
|
|
156
|
+
- Component internal spacing: is padding consistent within similar components (all cards, all inputs)?
|
|
157
|
+
- Whitespace ratio: is there enough breathing room, or is everything cramped?
|
|
158
|
+
- Margin collapse issues: any unintended spacing from margin collapse?
|
|
159
|
+
|
|
160
|
+
5. COMPONENT CONSISTENCY (weight: 15%)
|
|
161
|
+
- Button variants: how many distinct button styles? Are they intentional variants or accidents?
|
|
162
|
+
- Input styling: are all form inputs styled consistently? Border, focus ring, label position?
|
|
163
|
+
- Card patterns: same border-radius, shadow, padding across all cards?
|
|
164
|
+
- Icon system: consistent size (16/20/24px), stroke width, and style?
|
|
165
|
+
- Border radius: count distinct values. More than 3 (e.g., 4px, 8px, full) = incoherent.
|
|
166
|
+
- Shadow system: consistent elevation scale or random drop shadows?
|
|
167
|
+
|
|
168
|
+
6. INTERACTION DESIGN (weight: 10%)
|
|
169
|
+
- Hover states: do interactive elements have visible hover feedback?
|
|
170
|
+
- Focus indicators: are there visible focus rings for keyboard navigation?
|
|
171
|
+
- Active/pressed states: button feedback on click?
|
|
172
|
+
- Transitions: are they present? Consistent duration (150-300ms)? Appropriate easing?
|
|
173
|
+
- Loading states: skeleton screens, spinners, or no loading feedback at all?
|
|
174
|
+
- Cursor changes: does cursor change to pointer on clickable elements?
|
|
175
|
+
|
|
176
|
+
7. ACCESSIBILITY (weight: 10%)
|
|
177
|
+
- Semantic HTML: are headings in order (h1 → h2 → h3)? Are buttons actually <button>?
|
|
178
|
+
- ARIA labels: do icon-only buttons have labels? Do images have alt text?
|
|
179
|
+
- Keyboard navigation: can you tell what's focused? Is tab order logical?
|
|
180
|
+
- Touch targets: are mobile tap targets at least 44x44px?
|
|
181
|
+
- Screen reader: is content structured so screen reader users get meaningful navigation?
|
|
182
|
+
|
|
183
|
+
8. VISUAL POLISH (weight: 5%)
|
|
184
|
+
- Pixel precision: any elements off by 1px? Misaligned text baselines?
|
|
185
|
+
- Image quality: are images sharp on retina displays (2x resolution)?
|
|
186
|
+
- Icon consistency: all from the same set, or a mix of styles/weights?
|
|
187
|
+
- Empty states: designed or raw "no data" text?
|
|
188
|
+
- Error states: styled or browser-default?
|
|
189
|
+
- Favicon and meta: present and professional?
|
|
90
190
|
|
|
91
191
|
${rubric}
|
|
92
192
|
|
|
93
|
-
|
|
94
|
-
-
|
|
95
|
-
-
|
|
96
|
-
-
|
|
97
|
-
-
|
|
98
|
-
-
|
|
193
|
+
SPECIFICITY REQUIREMENTS — your findings must be THIS specific:
|
|
194
|
+
- BAD: "Spacing is inconsistent" (vague, useless)
|
|
195
|
+
- GOOD: "Section gap between hero and features is 48px, but features-to-pricing is 24px and pricing-to-footer is 64px. Use consistent 48px or 64px vertical rhythm throughout."
|
|
196
|
+
- BAD: "Colors don't look right" (vague)
|
|
197
|
+
- GOOD: "Body text (#6b7280) on white background has ~4.6:1 contrast ratio (barely passes AA). The same gray on the light-gray card background (#f9fafb) drops to ~3.8:1 — fails AA for normal text. Darken body text to #4b5563 (7:1+)."
|
|
198
|
+
- BAD: "Typography needs work" (vague)
|
|
199
|
+
- GOOD: "6 distinct font sizes detected (12, 13, 14, 16, 20, 32px) with no clear scale. Consolidate to a 4-step scale: 14px body, 16px large, 24px h2, 36px h1. Current h2 at 20px lacks sufficient contrast with 16px body text."
|
|
200
|
+
|
|
201
|
+
For EACH finding, you MUST include a concrete CSS fix in the suggestion field. Not "improve spacing" but "gap: 48px" or "font-size: 14px; line-height: 1.5".
|
|
99
202
|
|
|
100
203
|
RESPOND WITH ONLY a JSON object:
|
|
101
204
|
{
|
|
102
205
|
"score": 6,
|
|
103
|
-
"summary": "One-sentence overall assessment",
|
|
104
|
-
"strengths": ["Specific thing done well", "Another strength"],
|
|
206
|
+
"summary": "One-sentence overall assessment with the key design system failure mode",
|
|
207
|
+
"strengths": ["Specific thing done well with evidence", "Another measured strength"],
|
|
105
208
|
"findings": [
|
|
106
209
|
{
|
|
107
210
|
"category": "spacing",
|
|
108
211
|
"severity": "major",
|
|
109
|
-
"description": "Hero section has 64px top padding but only 16px bottom padding before the feature grid, creating visual imbalance",
|
|
110
|
-
"location": "Hero section → feature grid transition",
|
|
111
|
-
"suggestion": "
|
|
212
|
+
"description": "Hero section has 64px top padding but only 16px bottom padding before the feature grid, creating visual imbalance. The 4:1 ratio breaks vertical rhythm.",
|
|
213
|
+
"location": "Hero section → feature grid transition (main > section:nth-child(2))",
|
|
214
|
+
"suggestion": "padding-bottom: 48px on hero section. Standardize all section gaps to 48px or 64px.",
|
|
215
|
+
"cssSelector": "main > section:first-child",
|
|
216
|
+
"cssFix": "padding-bottom: 48px"
|
|
112
217
|
}
|
|
113
|
-
]
|
|
218
|
+
],
|
|
219
|
+
"designSystemScore": {
|
|
220
|
+
"layout": 7,
|
|
221
|
+
"typography": 5,
|
|
222
|
+
"color": 6,
|
|
223
|
+
"spacing": 4,
|
|
224
|
+
"components": 6,
|
|
225
|
+
"interactions": 3,
|
|
226
|
+
"accessibility": 5,
|
|
227
|
+
"polish": 4
|
|
228
|
+
}
|
|
114
229
|
}
|
|
115
230
|
|
|
116
231
|
Categories: visual-bug, layout, contrast, alignment, spacing, typography, accessibility, ux
|
|
117
|
-
Severities: critical (blocks usage), major (looks unprofessional), minor (polish
|
|
118
|
-
Score: 1-10 per calibration above. Most
|
|
232
|
+
Severities: critical (blocks usage or fails WCAG), major (looks unprofessional), minor (polish detail)
|
|
233
|
+
Score: 1-10 per calibration above. Most production apps score 5-7. Template apps score 3-5. Only world-class ships get 8+. Be honest — inflated scores help nobody.`;
|
|
119
234
|
}
|
|
120
235
|
// ---------------------------------------------------------------------------
|
|
121
236
|
// Page discovery — find key pages by crawling links
|
|
@@ -207,15 +322,29 @@ function generateReport(results, profile) {
|
|
|
207
322
|
lines.push(`- ${s}`);
|
|
208
323
|
lines.push('');
|
|
209
324
|
}
|
|
325
|
+
if (result.designSystemScore) {
|
|
326
|
+
lines.push('**Design System Breakdown:**');
|
|
327
|
+
lines.push('');
|
|
328
|
+
const ds = result.designSystemScore;
|
|
329
|
+
const dsKeys = ['layout', 'typography', 'color', 'spacing', 'components', 'interactions', 'accessibility', 'polish'];
|
|
330
|
+
for (const key of dsKeys) {
|
|
331
|
+
if (ds[key] !== undefined) {
|
|
332
|
+
const bar = '█'.repeat(Math.round(ds[key])) + '░'.repeat(10 - Math.round(ds[key]));
|
|
333
|
+
lines.push(`- ${key}: \`${bar}\` ${ds[key]}/10`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
lines.push('');
|
|
337
|
+
}
|
|
210
338
|
if (result.findings.length > 0) {
|
|
211
339
|
lines.push('**Findings:**');
|
|
212
340
|
lines.push('');
|
|
213
|
-
lines.push('| Sev | Category | Description | Location | Fix |');
|
|
214
|
-
lines.push('
|
|
341
|
+
lines.push('| Sev | Category | Description | Location | Fix | CSS |');
|
|
342
|
+
lines.push('|-----|----------|-------------|----------|-----|-----|');
|
|
215
343
|
for (const f of result.findings) {
|
|
216
344
|
const esc = (s) => s.replace(/\|/g, '\\|').replace(/\n/g, ' ').slice(0, 120);
|
|
217
345
|
const icon = f.severity === 'critical' ? '🔴' : f.severity === 'major' ? '🟡' : '⚪';
|
|
218
|
-
|
|
346
|
+
const cssFix = f.cssFix ? `\`${esc(f.cssFix)}\`` : '';
|
|
347
|
+
lines.push(`| ${icon} ${f.severity} | ${f.category} | ${esc(f.description)} | ${esc(f.location)} | ${esc(f.suggestion)} | ${cssFix} |`);
|
|
219
348
|
}
|
|
220
349
|
lines.push('');
|
|
221
350
|
}
|
|
@@ -270,6 +399,7 @@ export async function runDesignAudit(opts) {
|
|
|
270
399
|
provider: opts.provider,
|
|
271
400
|
vision: true,
|
|
272
401
|
debug: opts.debug,
|
|
402
|
+
llmTimeoutMs: 120_000, // design audits generate ~8k tokens of structured JSON — need 2min
|
|
273
403
|
});
|
|
274
404
|
const driver = new PlaywrightDriver(page);
|
|
275
405
|
// Audit each page
|
|
@@ -320,8 +450,448 @@ export async function runDesignAudit(opts) {
|
|
|
320
450
|
if (screenshotDir)
|
|
321
451
|
console.log(` ${chalk.dim('Screenshots →')} ${screenshotDir}`);
|
|
322
452
|
console.log('');
|
|
453
|
+
// ── Reproducibility mode: run 3x and report variance ──
|
|
454
|
+
if (opts.reproducibility) {
|
|
455
|
+
console.log(` ${chalk.bold('Reproducibility test')} — running 2 additional audits…`);
|
|
456
|
+
const scores = [avgScore];
|
|
457
|
+
for (let rep = 0; rep < 2; rep++) {
|
|
458
|
+
const repResults = [];
|
|
459
|
+
for (const url of pages) {
|
|
460
|
+
const r = await auditSinglePage(brain, driver, page, url, profile);
|
|
461
|
+
repResults.push(r);
|
|
462
|
+
}
|
|
463
|
+
const repAvg = repResults.reduce((s, r) => s + r.score, 0) / repResults.length;
|
|
464
|
+
scores.push(repAvg);
|
|
465
|
+
console.log(` ${chalk.dim(` Rep ${rep + 2}:`)} ${repAvg.toFixed(1)}/10`);
|
|
466
|
+
}
|
|
467
|
+
const mean = scores.reduce((a, b) => a + b, 0) / scores.length;
|
|
468
|
+
const variance = Math.sqrt(scores.reduce((sum, s) => sum + (s - mean) ** 2, 0) / scores.length);
|
|
469
|
+
const pass = variance <= 0.5;
|
|
470
|
+
const varColor = pass ? chalk.green : chalk.red;
|
|
471
|
+
console.log(` ${chalk.dim('Scores:')} ${scores.map(s => s.toFixed(1)).join(', ')}`);
|
|
472
|
+
console.log(` ${chalk.dim('Mean:')} ${mean.toFixed(2)} ${chalk.dim('±')} ${varColor(variance.toFixed(2))} ${pass ? chalk.green('PASS (±0.5)') : chalk.red('FAIL (>±0.5)')}`);
|
|
473
|
+
console.log('');
|
|
474
|
+
if (opts.json) {
|
|
475
|
+
const repPath = path.join(outputDir, 'reproducibility.json');
|
|
476
|
+
fs.writeFileSync(repPath, JSON.stringify({ scores, mean, stddev: variance, pass }, null, 2));
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
// ── Evolve mode: closed-loop fix → re-audit ──
|
|
480
|
+
if (opts.evolve) {
|
|
481
|
+
// --evolve=css (or --evolve=true) → CSS injection
|
|
482
|
+
// --evolve=claude-code|codex|opencode|<custom> → agent dispatch
|
|
483
|
+
const evolveMode = opts.evolve === true || opts.evolve === 'true' || opts.evolve === 'css' ? 'css' : opts.evolve;
|
|
484
|
+
let evolveResult;
|
|
485
|
+
if (evolveMode !== 'css') {
|
|
486
|
+
// Agent-dispatched evolve — a coding agent edits the actual source code
|
|
487
|
+
const projectDir = opts.projectDir ?? process.cwd();
|
|
488
|
+
evolveResult = await runAgentEvolveLoop(brain, driver, page, pages, profile, results, outputDir, opts.evolveRounds ?? 3, evolveMode, projectDir, opts.debug);
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
// CSS-injection evolve — ephemeral fixes injected into the browser page
|
|
492
|
+
evolveResult = await runEvolveLoop(brain, driver, page, pages, profile, results, outputDir, opts.evolveRounds ?? 3);
|
|
493
|
+
}
|
|
494
|
+
// Write evolve report
|
|
495
|
+
const evolvePath = path.join(outputDir, 'evolve-report.md');
|
|
496
|
+
fs.writeFileSync(evolvePath, generateEvolveReport(evolveResult));
|
|
497
|
+
console.log(` ${chalk.dim('Evolve report →')} ${evolvePath}`);
|
|
498
|
+
// Write CSS override file (CSS-injection mode only)
|
|
499
|
+
if (evolveResult.cssOverride) {
|
|
500
|
+
const cssPath = path.join(outputDir, 'design-fixes.css');
|
|
501
|
+
fs.writeFileSync(cssPath, evolveResult.cssOverride);
|
|
502
|
+
console.log(` ${chalk.dim('CSS fixes →')} ${cssPath}`);
|
|
503
|
+
}
|
|
504
|
+
if (opts.json) {
|
|
505
|
+
const evJsonPath = path.join(outputDir, 'evolve.json');
|
|
506
|
+
fs.writeFileSync(evJsonPath, JSON.stringify(evolveResult, null, 2));
|
|
507
|
+
}
|
|
508
|
+
console.log('');
|
|
509
|
+
}
|
|
323
510
|
await browser.close();
|
|
324
511
|
}
|
|
512
|
+
async function runEvolveLoop(brain, driver, page, pages, profile, initialResults, outputDir, maxRounds) {
|
|
513
|
+
const initialAvg = initialResults.reduce((s, r) => s + r.score, 0) / initialResults.length;
|
|
514
|
+
const scoreHistory = [initialAvg];
|
|
515
|
+
const appliedFixes = [];
|
|
516
|
+
const skippedFixes = [];
|
|
517
|
+
let cumulativeCSS = '';
|
|
518
|
+
let currentResults = initialResults;
|
|
519
|
+
let currentAvg = initialAvg;
|
|
520
|
+
console.log('');
|
|
521
|
+
console.log(` ${chalk.bold('Design Evolve')} — ${maxRounds} rounds max`);
|
|
522
|
+
console.log(` ${chalk.dim('Initial score:')} ${currentAvg.toFixed(1)}/10`);
|
|
523
|
+
console.log('');
|
|
524
|
+
for (let round = 1; round <= maxRounds; round++) {
|
|
525
|
+
console.log(` ${chalk.dim(`Round ${round}/${maxRounds}`)}`);
|
|
526
|
+
// Collect all findings with CSS fixes across all pages
|
|
527
|
+
const fixableFixes = currentResults
|
|
528
|
+
.flatMap(r => r.findings)
|
|
529
|
+
.filter(f => f.cssSelector && f.cssFix);
|
|
530
|
+
if (fixableFixes.length === 0) {
|
|
531
|
+
console.log(` ${chalk.dim(' No CSS-fixable findings — generating fixes via LLM…')}`);
|
|
532
|
+
// Ask the LLM to generate CSS fixes for the top findings
|
|
533
|
+
const topFindings = currentResults
|
|
534
|
+
.flatMap(r => r.findings)
|
|
535
|
+
.filter(f => f.severity === 'critical' || f.severity === 'major')
|
|
536
|
+
.slice(0, 10);
|
|
537
|
+
if (topFindings.length === 0) {
|
|
538
|
+
console.log(` ${chalk.green(' No major/critical findings remaining')}`);
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
const fixPrompt = buildFixGenerationPrompt(topFindings);
|
|
542
|
+
const fixResult = await brain.auditDesign(await driver.observe(), 'Generate CSS fixes for the design issues listed below', [], fixPrompt);
|
|
543
|
+
// Parse generated fixes
|
|
544
|
+
try {
|
|
545
|
+
let text = fixResult.raw.trim();
|
|
546
|
+
if (text.startsWith('```'))
|
|
547
|
+
text = text.replace(/^```(?:json)?\n?/, '').replace(/\n?```$/, '');
|
|
548
|
+
const start = text.indexOf('{');
|
|
549
|
+
const end = text.lastIndexOf('}');
|
|
550
|
+
if (start >= 0 && end > start)
|
|
551
|
+
text = text.slice(start, end + 1);
|
|
552
|
+
const parsed = JSON.parse(text);
|
|
553
|
+
if (Array.isArray(parsed.fixes)) {
|
|
554
|
+
for (const fix of parsed.fixes) {
|
|
555
|
+
if (fix.cssSelector && fix.cssFix) {
|
|
556
|
+
fixableFixes.push({
|
|
557
|
+
category: 'ux',
|
|
558
|
+
severity: 'major',
|
|
559
|
+
description: fix.description || '',
|
|
560
|
+
location: fix.location || '',
|
|
561
|
+
suggestion: fix.cssFix,
|
|
562
|
+
cssSelector: fix.cssSelector,
|
|
563
|
+
cssFix: fix.cssFix,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
catch { /* failed to parse fixes */ }
|
|
570
|
+
}
|
|
571
|
+
if (fixableFixes.length === 0) {
|
|
572
|
+
console.log(` ${chalk.dim(' Could not generate fixable CSS — stopping')}`);
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
575
|
+
// Build CSS override from all fixable findings
|
|
576
|
+
const roundCSS = fixableFixes
|
|
577
|
+
.map(f => `/* ${f.severity}: ${f.description?.slice(0, 80)} */\n${f.cssSelector} { ${f.cssFix} }`)
|
|
578
|
+
.join('\n\n');
|
|
579
|
+
cumulativeCSS += '\n' + roundCSS;
|
|
580
|
+
// Track applied fixes
|
|
581
|
+
for (const f of fixableFixes) {
|
|
582
|
+
appliedFixes.push({
|
|
583
|
+
cssSelector: f.cssSelector,
|
|
584
|
+
cssFix: f.cssFix,
|
|
585
|
+
finding: f.description,
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
console.log(` ${chalk.dim(` Applying ${fixableFixes.length} CSS fixes…`)}`);
|
|
589
|
+
// Re-audit each page with CSS injected
|
|
590
|
+
const roundResults = [];
|
|
591
|
+
for (const url of pages) {
|
|
592
|
+
try {
|
|
593
|
+
await page.goto(url, { waitUntil: 'networkidle', timeout: 20_000 }).catch(() => page.goto(url, { waitUntil: 'domcontentloaded', timeout: 15_000 }));
|
|
594
|
+
await page.waitForTimeout(1500);
|
|
595
|
+
// Inject cumulative CSS fixes
|
|
596
|
+
await page.addStyleTag({ content: cumulativeCSS });
|
|
597
|
+
await page.waitForTimeout(500);
|
|
598
|
+
// Take screenshot of fixed state
|
|
599
|
+
const screenshotDir = path.join(outputDir, `screenshots-round-${round}`);
|
|
600
|
+
fs.mkdirSync(screenshotDir, { recursive: true });
|
|
601
|
+
const result = await auditSinglePage(brain, driver, page, url, profile, screenshotDir);
|
|
602
|
+
roundResults.push(result);
|
|
603
|
+
}
|
|
604
|
+
catch {
|
|
605
|
+
roundResults.push({
|
|
606
|
+
url,
|
|
607
|
+
score: currentAvg,
|
|
608
|
+
summary: 'Re-audit failed',
|
|
609
|
+
strengths: [],
|
|
610
|
+
findings: [],
|
|
611
|
+
error: 'Re-audit with CSS injection failed',
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
const roundAvg = roundResults.reduce((s, r) => s + r.score, 0) / roundResults.length;
|
|
616
|
+
scoreHistory.push(roundAvg);
|
|
617
|
+
const delta = roundAvg - currentAvg;
|
|
618
|
+
const deltaStr = delta >= 0 ? chalk.green(`+${delta.toFixed(1)}`) : chalk.red(delta.toFixed(1));
|
|
619
|
+
console.log(` ${chalk.dim(' Score:')} ${roundAvg.toFixed(1)}/10 (${deltaStr})`);
|
|
620
|
+
currentResults = roundResults;
|
|
621
|
+
currentAvg = roundAvg;
|
|
622
|
+
// Check convergence — if no improvement, stop
|
|
623
|
+
if (delta <= 0.1 && round > 1) {
|
|
624
|
+
console.log(` ${chalk.dim(' Converged — no further improvement')}`);
|
|
625
|
+
break;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
const totalDelta = currentAvg - initialAvg;
|
|
629
|
+
const deltaColor = totalDelta >= 2 ? chalk.green : totalDelta > 0 ? chalk.yellow : chalk.red;
|
|
630
|
+
console.log('');
|
|
631
|
+
console.log(` ${chalk.bold('Evolve complete')}`);
|
|
632
|
+
console.log(` ${chalk.dim('Score:')} ${initialAvg.toFixed(1)} → ${currentAvg.toFixed(1)} (${deltaColor(`+${totalDelta.toFixed(1)}`)})`);
|
|
633
|
+
console.log(` ${chalk.dim('Rounds:')} ${scoreHistory.length - 1}`);
|
|
634
|
+
console.log(` ${chalk.dim('Fixes applied:')} ${appliedFixes.length}`);
|
|
635
|
+
console.log('');
|
|
636
|
+
return {
|
|
637
|
+
beforeScore: initialAvg,
|
|
638
|
+
afterScore: currentAvg,
|
|
639
|
+
delta: totalDelta,
|
|
640
|
+
rounds: scoreHistory.length - 1,
|
|
641
|
+
appliedFixes,
|
|
642
|
+
skippedFixes,
|
|
643
|
+
scoreHistory,
|
|
644
|
+
cssOverride: cumulativeCSS.trim(),
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
function buildFixGenerationPrompt(findings) {
|
|
648
|
+
const findingList = findings.map((f, i) => `${i + 1}. [${f.severity}/${f.category}] ${f.description}\n Location: ${f.location}\n Suggestion: ${f.suggestion}`).join('\n');
|
|
649
|
+
return `You are a CSS engineer fixing design issues. For each finding, generate a precise CSS fix.
|
|
650
|
+
|
|
651
|
+
FINDINGS TO FIX:
|
|
652
|
+
${findingList}
|
|
653
|
+
|
|
654
|
+
RULES:
|
|
655
|
+
- Use specific, targeted CSS selectors. Prefer class-based or semantic selectors.
|
|
656
|
+
- Each fix should be a single CSS rule (selector + property:value pairs).
|
|
657
|
+
- Fixes must not break other elements — be surgical.
|
|
658
|
+
- For spacing: use consistent values (multiples of 4 or 8px).
|
|
659
|
+
- For colors: ensure WCAG AA contrast (4.5:1 for text, 3:1 for large text).
|
|
660
|
+
- For typography: use a limited scale (14px, 16px, 20px, 24px, 32px, 48px).
|
|
661
|
+
|
|
662
|
+
RESPOND WITH ONLY a JSON object:
|
|
663
|
+
{
|
|
664
|
+
"fixes": [
|
|
665
|
+
{
|
|
666
|
+
"cssSelector": "main > section:first-child",
|
|
667
|
+
"cssFix": "padding-bottom: 48px; margin-bottom: 0",
|
|
668
|
+
"description": "Standardize hero section bottom spacing",
|
|
669
|
+
"location": "Hero → features transition"
|
|
670
|
+
}
|
|
671
|
+
]
|
|
672
|
+
}`;
|
|
673
|
+
}
|
|
674
|
+
function generateEvolveReport(result) {
|
|
675
|
+
const lines = [];
|
|
676
|
+
lines.push('# Design Evolve Report');
|
|
677
|
+
lines.push('');
|
|
678
|
+
lines.push(`**Score:** ${result.beforeScore.toFixed(1)} → ${result.afterScore.toFixed(1)} (+${result.delta.toFixed(1)})`);
|
|
679
|
+
lines.push(`**Rounds:** ${result.rounds}`);
|
|
680
|
+
lines.push(`**Score progression:** ${result.scoreHistory.map(s => s.toFixed(1)).join(' → ')}`);
|
|
681
|
+
lines.push('');
|
|
682
|
+
if (result.appliedFixes.length > 0) {
|
|
683
|
+
lines.push('## Applied Fixes');
|
|
684
|
+
lines.push('');
|
|
685
|
+
for (const fix of result.appliedFixes) {
|
|
686
|
+
lines.push(`- \`${fix.cssSelector}\`: \`${fix.cssFix}\``);
|
|
687
|
+
if (fix.finding)
|
|
688
|
+
lines.push(` - ${fix.finding}`);
|
|
689
|
+
}
|
|
690
|
+
lines.push('');
|
|
691
|
+
}
|
|
692
|
+
if (result.cssOverride) {
|
|
693
|
+
lines.push('## Generated CSS Override');
|
|
694
|
+
lines.push('');
|
|
695
|
+
lines.push('```css');
|
|
696
|
+
lines.push(result.cssOverride);
|
|
697
|
+
lines.push('```');
|
|
698
|
+
lines.push('');
|
|
699
|
+
lines.push('Apply this CSS to your app to fix the identified design issues:');
|
|
700
|
+
lines.push('```html');
|
|
701
|
+
lines.push('<link rel="stylesheet" href="design-fixes.css">');
|
|
702
|
+
lines.push('```');
|
|
703
|
+
}
|
|
704
|
+
return lines.join('\n');
|
|
705
|
+
}
|
|
706
|
+
// ---------------------------------------------------------------------------
|
|
707
|
+
// Agent-dispatched evolve — sends findings to a coding agent that edits source
|
|
708
|
+
// ---------------------------------------------------------------------------
|
|
709
|
+
const AGENT_COMMANDS = {
|
|
710
|
+
'claude-code': (prompt, dir) => ['claude', '-p', prompt, '--dangerously-skip-permissions', '--add-dir', dir],
|
|
711
|
+
'codex': (prompt, dir) => ['codex', 'exec', prompt, '-c', `cwd="${dir}"`],
|
|
712
|
+
'opencode': (prompt, dir) => ['opencode', 'run', prompt],
|
|
713
|
+
};
|
|
714
|
+
function resolveAgentCommand(agent, prompt, projectDir) {
|
|
715
|
+
const builder = AGENT_COMMANDS[agent];
|
|
716
|
+
if (builder) {
|
|
717
|
+
const [cmd, ...args] = builder(prompt, projectDir);
|
|
718
|
+
return { cmd, args, cwd: projectDir };
|
|
719
|
+
}
|
|
720
|
+
// Custom command — treat the agent string as a command template
|
|
721
|
+
// e.g. "aider --message" becomes: aider --message "<prompt>"
|
|
722
|
+
const parts = agent.split(/\s+/);
|
|
723
|
+
return { cmd: parts[0], args: [...parts.slice(1), prompt], cwd: projectDir };
|
|
724
|
+
}
|
|
725
|
+
function buildAgentFixPrompt(results, profile, round) {
|
|
726
|
+
const allFindings = results.flatMap(r => r.findings);
|
|
727
|
+
const critical = allFindings.filter(f => f.severity === 'critical');
|
|
728
|
+
const major = allFindings.filter(f => f.severity === 'major');
|
|
729
|
+
const minor = allFindings.filter(f => f.severity === 'minor');
|
|
730
|
+
const findingsList = [...critical, ...major, ...minor.slice(0, 5)]
|
|
731
|
+
.map((f, i) => {
|
|
732
|
+
let entry = `${i + 1}. [${f.severity}/${f.category}] ${f.description}`;
|
|
733
|
+
entry += `\n Location: ${f.location}`;
|
|
734
|
+
entry += `\n Suggestion: ${f.suggestion}`;
|
|
735
|
+
if (f.cssSelector)
|
|
736
|
+
entry += `\n CSS Selector: ${f.cssSelector}`;
|
|
737
|
+
if (f.cssFix)
|
|
738
|
+
entry += `\n CSS Fix: ${f.cssFix}`;
|
|
739
|
+
return entry;
|
|
740
|
+
})
|
|
741
|
+
.join('\n\n');
|
|
742
|
+
const scoreBreakdowns = results
|
|
743
|
+
.filter(r => r.designSystemScore)
|
|
744
|
+
.map(r => {
|
|
745
|
+
const ds = r.designSystemScore;
|
|
746
|
+
return ` ${r.url}: ${Object.entries(ds).map(([k, v]) => `${k}=${v}`).join(', ')}`;
|
|
747
|
+
})
|
|
748
|
+
.join('\n');
|
|
749
|
+
return `You are fixing design issues found by an automated design audit.
|
|
750
|
+
|
|
751
|
+
AUDIT PROFILE: ${profile}
|
|
752
|
+
ROUND: ${round} (${round === 1 ? 'initial fixes' : 'fixing remaining issues from previous round'})
|
|
753
|
+
CURRENT SCORES:
|
|
754
|
+
Overall: ${(results.reduce((s, r) => s + r.score, 0) / results.length).toFixed(1)}/10
|
|
755
|
+
${scoreBreakdowns}
|
|
756
|
+
|
|
757
|
+
FINDINGS TO FIX (${critical.length} critical, ${major.length} major, ${minor.length} minor):
|
|
758
|
+
|
|
759
|
+
${findingsList}
|
|
760
|
+
|
|
761
|
+
INSTRUCTIONS:
|
|
762
|
+
1. Read the project's source files to understand the styling approach (Tailwind, CSS modules, plain CSS, styled-components, etc.)
|
|
763
|
+
2. Fix the findings by editing the ACTUAL SOURCE FILES — not by creating new CSS override files
|
|
764
|
+
3. Match the project's existing styling conventions
|
|
765
|
+
4. Fix the design SYSTEM (shared components, tokens, globals) not individual instances
|
|
766
|
+
5. Prioritize critical and major findings
|
|
767
|
+
6. Only change visual/styling properties — never change business logic, state, or event handlers
|
|
768
|
+
7. After making changes, verify the dev server is still running (no build errors)
|
|
769
|
+
|
|
770
|
+
Do NOT:
|
|
771
|
+
- Create new standalone CSS override files — edit the existing styles
|
|
772
|
+
- Add comments explaining what you changed — just change it
|
|
773
|
+
- Refactor unrelated code
|
|
774
|
+
- Change component structure or HTML semantics unless a finding specifically requires it`;
|
|
775
|
+
}
|
|
776
|
+
async function runAgentEvolveLoop(brain, driver, page, pages, profile, initialResults, outputDir, maxRounds, agentName, projectDir, debug) {
|
|
777
|
+
const initialAvg = initialResults.reduce((s, r) => s + r.score, 0) / initialResults.length;
|
|
778
|
+
const scoreHistory = [initialAvg];
|
|
779
|
+
const appliedFixes = [];
|
|
780
|
+
let currentResults = initialResults;
|
|
781
|
+
let currentAvg = initialAvg;
|
|
782
|
+
const resolvedProjectDir = path.resolve(projectDir);
|
|
783
|
+
if (!fs.existsSync(resolvedProjectDir)) {
|
|
784
|
+
cliError(`project directory not found: ${resolvedProjectDir}`);
|
|
785
|
+
process.exit(1);
|
|
786
|
+
}
|
|
787
|
+
console.log('');
|
|
788
|
+
console.log(` ${chalk.bold('Design Evolve')} ${chalk.dim('via')} ${chalk.cyan(agentName)}`);
|
|
789
|
+
console.log(` ${chalk.dim('Project:')} ${resolvedProjectDir}`);
|
|
790
|
+
console.log(` ${chalk.dim('Initial score:')} ${currentAvg.toFixed(1)}/10`);
|
|
791
|
+
console.log(` ${chalk.dim('Max rounds:')} ${maxRounds}`);
|
|
792
|
+
console.log('');
|
|
793
|
+
for (let round = 1; round <= maxRounds; round++) {
|
|
794
|
+
console.log(` ${chalk.dim(`Round ${round}/${maxRounds}`)}`);
|
|
795
|
+
// Build the prompt for the agent
|
|
796
|
+
const prompt = buildAgentFixPrompt(currentResults, profile, round);
|
|
797
|
+
// Write the prompt to a file for debugging
|
|
798
|
+
const promptPath = path.join(outputDir, `agent-prompt-round-${round}.txt`);
|
|
799
|
+
fs.writeFileSync(promptPath, prompt);
|
|
800
|
+
// Also write the full report JSON so the agent could read it
|
|
801
|
+
const findingsPath = path.join(outputDir, `findings-round-${round}.json`);
|
|
802
|
+
fs.writeFileSync(findingsPath, JSON.stringify({
|
|
803
|
+
round,
|
|
804
|
+
score: currentAvg,
|
|
805
|
+
results: currentResults.map(r => ({
|
|
806
|
+
url: r.url,
|
|
807
|
+
score: r.score,
|
|
808
|
+
designSystemScore: r.designSystemScore,
|
|
809
|
+
findings: r.findings,
|
|
810
|
+
})),
|
|
811
|
+
}, null, 2));
|
|
812
|
+
// Dispatch to the coding agent
|
|
813
|
+
const { cmd, args, cwd } = resolveAgentCommand(agentName, prompt, resolvedProjectDir);
|
|
814
|
+
console.log(` ${chalk.dim(` Dispatching to ${agentName}…`)}`);
|
|
815
|
+
if (debug) {
|
|
816
|
+
console.log(` ${chalk.dim(` cmd: ${cmd} ${args.map(a => a.length > 80 ? a.slice(0, 80) + '…' : a).join(' ')}`)}`);
|
|
817
|
+
}
|
|
818
|
+
try {
|
|
819
|
+
const result = execSync(`${cmd} ${args.map(a => JSON.stringify(a)).join(' ')}`, {
|
|
820
|
+
cwd,
|
|
821
|
+
stdio: debug ? 'inherit' : 'pipe',
|
|
822
|
+
timeout: 300_000, // 5min max per agent round
|
|
823
|
+
env: { ...process.env },
|
|
824
|
+
});
|
|
825
|
+
if (!debug && result) {
|
|
826
|
+
const agentOutputPath = path.join(outputDir, `agent-output-round-${round}.txt`);
|
|
827
|
+
fs.writeFileSync(agentOutputPath, result.toString());
|
|
828
|
+
}
|
|
829
|
+
console.log(` ${chalk.dim(' Agent completed')}`);
|
|
830
|
+
}
|
|
831
|
+
catch (err) {
|
|
832
|
+
const exitCode = err.status ?? 'unknown';
|
|
833
|
+
console.log(` ${chalk.yellow(` Agent exited with code ${exitCode} — continuing with re-audit`)}`);
|
|
834
|
+
// Write stderr if available
|
|
835
|
+
const stderr = err.stderr;
|
|
836
|
+
if (stderr) {
|
|
837
|
+
const errPath = path.join(outputDir, `agent-error-round-${round}.txt`);
|
|
838
|
+
fs.writeFileSync(errPath, stderr.toString());
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
// Wait for hot reload to settle
|
|
842
|
+
console.log(` ${chalk.dim(' Waiting for hot reload…')}`);
|
|
843
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
844
|
+
// Re-audit
|
|
845
|
+
console.log(` ${chalk.dim(' Re-auditing…')}`);
|
|
846
|
+
const roundResults = [];
|
|
847
|
+
const roundScreenshotDir = path.join(outputDir, `screenshots-round-${round}`);
|
|
848
|
+
fs.mkdirSync(roundScreenshotDir, { recursive: true });
|
|
849
|
+
for (const url of pages) {
|
|
850
|
+
const result = await auditSinglePage(brain, driver, page, url, profile, roundScreenshotDir);
|
|
851
|
+
roundResults.push(result);
|
|
852
|
+
}
|
|
853
|
+
const roundAvg = roundResults.reduce((s, r) => s + r.score, 0) / roundResults.length;
|
|
854
|
+
scoreHistory.push(roundAvg);
|
|
855
|
+
const delta = roundAvg - currentAvg;
|
|
856
|
+
const deltaStr = delta >= 0 ? chalk.green(`+${delta.toFixed(1)}`) : chalk.red(delta.toFixed(1));
|
|
857
|
+
console.log(` ${chalk.dim(' Score:')} ${roundAvg.toFixed(1)}/10 (${deltaStr})`);
|
|
858
|
+
// Track what changed
|
|
859
|
+
const prevFindingCount = currentResults.flatMap(r => r.findings).length;
|
|
860
|
+
const newFindingCount = roundResults.flatMap(r => r.findings).length;
|
|
861
|
+
const resolvedCount = Math.max(0, prevFindingCount - newFindingCount);
|
|
862
|
+
if (resolvedCount > 0) {
|
|
863
|
+
appliedFixes.push({
|
|
864
|
+
cssSelector: `round-${round}`,
|
|
865
|
+
cssFix: `${agentName} resolved ${resolvedCount} findings`,
|
|
866
|
+
finding: `Score: ${currentAvg.toFixed(1)} → ${roundAvg.toFixed(1)}`,
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
currentResults = roundResults;
|
|
870
|
+
currentAvg = roundAvg;
|
|
871
|
+
// Check convergence
|
|
872
|
+
if (delta <= 0.1 && round > 1) {
|
|
873
|
+
console.log(` ${chalk.dim(' Converged — no further improvement')}`);
|
|
874
|
+
break;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
const totalDelta = currentAvg - initialAvg;
|
|
878
|
+
const deltaColor = totalDelta >= 2 ? chalk.green : totalDelta > 0 ? chalk.yellow : chalk.red;
|
|
879
|
+
console.log('');
|
|
880
|
+
console.log(` ${chalk.bold('Evolve complete')} ${chalk.dim('via')} ${chalk.cyan(agentName)}`);
|
|
881
|
+
console.log(` ${chalk.dim('Score:')} ${initialAvg.toFixed(1)} → ${currentAvg.toFixed(1)} (${deltaColor(totalDelta >= 0 ? `+${totalDelta.toFixed(1)}` : totalDelta.toFixed(1))})`);
|
|
882
|
+
console.log(` ${chalk.dim('Rounds:')} ${scoreHistory.length - 1}`);
|
|
883
|
+
console.log('');
|
|
884
|
+
return {
|
|
885
|
+
beforeScore: initialAvg,
|
|
886
|
+
afterScore: currentAvg,
|
|
887
|
+
delta: totalDelta,
|
|
888
|
+
rounds: scoreHistory.length - 1,
|
|
889
|
+
appliedFixes,
|
|
890
|
+
skippedFixes: [],
|
|
891
|
+
scoreHistory,
|
|
892
|
+
cssOverride: '', // no CSS override in agent mode — agent edited source directly
|
|
893
|
+
};
|
|
894
|
+
}
|
|
325
895
|
// Direct page audit using brain.generate with custom prompt
|
|
326
896
|
async function auditSinglePage(brain, driver, page, url, profile, screenshotDir) {
|
|
327
897
|
try {
|
|
@@ -347,6 +917,7 @@ async function auditSinglePage(brain, driver, page, url, profile, screenshotDir)
|
|
|
347
917
|
const result = await brain.auditDesign(state, `Audit the design quality of this page: ${url}`, [], buildAuditPrompt(profile));
|
|
348
918
|
let summary = '';
|
|
349
919
|
let strengths = [];
|
|
920
|
+
let designSystemScore;
|
|
350
921
|
try {
|
|
351
922
|
let text = result.raw.trim();
|
|
352
923
|
if (text.startsWith('```'))
|
|
@@ -354,6 +925,13 @@ async function auditSinglePage(brain, driver, page, url, profile, screenshotDir)
|
|
|
354
925
|
const parsed = JSON.parse(text);
|
|
355
926
|
summary = parsed.summary || '';
|
|
356
927
|
strengths = Array.isArray(parsed.strengths) ? parsed.strengths : [];
|
|
928
|
+
if (parsed.designSystemScore && typeof parsed.designSystemScore === 'object') {
|
|
929
|
+
designSystemScore = {};
|
|
930
|
+
for (const [k, v] of Object.entries(parsed.designSystemScore)) {
|
|
931
|
+
if (typeof v === 'number')
|
|
932
|
+
designSystemScore[k] = v;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
357
935
|
}
|
|
358
936
|
catch { /* use defaults */ }
|
|
359
937
|
return {
|
|
@@ -364,6 +942,7 @@ async function auditSinglePage(brain, driver, page, url, profile, screenshotDir)
|
|
|
364
942
|
findings: result.findings,
|
|
365
943
|
screenshotPath,
|
|
366
944
|
tokensUsed: result.tokensUsed,
|
|
945
|
+
designSystemScore,
|
|
367
946
|
};
|
|
368
947
|
}
|
|
369
948
|
catch (err) {
|