@tangle-network/browser-agent-driver 0.12.1 → 0.14.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/README.md +81 -0
- package/dist/brain/index.d.ts.map +1 -1
- package/dist/brain/index.js +88 -6
- package/dist/brain/index.js.map +1 -1
- package/dist/cli-design-audit.d.ts +2 -0
- package/dist/cli-design-audit.d.ts.map +1 -1
- package/dist/cli-design-audit.js +111 -303
- package/dist/cli-design-audit.js.map +1 -1
- package/dist/cli-view.d.ts +83 -0
- package/dist/cli-view.d.ts.map +1 -0
- package/dist/cli-view.js +397 -0
- package/dist/cli-view.js.map +1 -0
- package/dist/cli.js +32 -1
- package/dist/cli.js.map +1 -1
- package/dist/design/audit/classify.d.ts +20 -0
- package/dist/design/audit/classify.d.ts.map +1 -0
- package/dist/design/audit/classify.js +148 -0
- package/dist/design/audit/classify.js.map +1 -0
- package/dist/design/audit/evaluate.d.ts +49 -0
- package/dist/design/audit/evaluate.d.ts.map +1 -0
- package/dist/design/audit/evaluate.js +355 -0
- package/dist/design/audit/evaluate.js.map +1 -0
- package/dist/design/audit/measure/a11y.d.ts +25 -0
- package/dist/design/audit/measure/a11y.d.ts.map +1 -0
- package/dist/design/audit/measure/a11y.js +144 -0
- package/dist/design/audit/measure/a11y.js.map +1 -0
- package/dist/design/audit/measure/contrast.d.ts +18 -0
- package/dist/design/audit/measure/contrast.d.ts.map +1 -0
- package/dist/design/audit/measure/contrast.js +201 -0
- package/dist/design/audit/measure/contrast.js.map +1 -0
- package/dist/design/audit/measure/index.d.ts +14 -0
- package/dist/design/audit/measure/index.d.ts.map +1 -0
- package/dist/design/audit/measure/index.js +38 -0
- package/dist/design/audit/measure/index.js.map +1 -0
- package/dist/design/audit/pipeline.d.ts +33 -0
- package/dist/design/audit/pipeline.d.ts.map +1 -0
- package/dist/design/audit/pipeline.js +109 -0
- package/dist/design/audit/pipeline.js.map +1 -0
- package/dist/design/audit/roi.d.ts +49 -0
- package/dist/design/audit/roi.d.ts.map +1 -0
- package/dist/design/audit/roi.js +157 -0
- package/dist/design/audit/roi.js.map +1 -0
- package/dist/design/audit/rubric/fragments/domain-ai.md +19 -0
- package/dist/design/audit/rubric/fragments/domain-crypto.md +24 -0
- package/dist/design/audit/rubric/fragments/domain-devtools.md +21 -0
- package/dist/design/audit/rubric/fragments/domain-fintech.md +19 -0
- package/dist/design/audit/rubric/fragments/maturity-prototype.md +36 -0
- package/dist/design/audit/rubric/fragments/type-blog.md +22 -0
- package/dist/design/audit/rubric/fragments/type-docs.md +23 -0
- package/dist/design/audit/rubric/fragments/type-ecommerce.md +23 -0
- package/dist/design/audit/rubric/fragments/type-marketing.md +22 -0
- package/dist/design/audit/rubric/fragments/type-saas-app.md +20 -0
- package/dist/design/audit/rubric/fragments/universal-calibration.md +16 -0
- package/dist/design/audit/rubric/fragments/universal-effort-anchor.md +46 -0
- package/dist/design/audit/rubric/fragments/universal-foundation.md +49 -0
- package/dist/design/audit/rubric/loader.d.ts +49 -0
- package/dist/design/audit/rubric/loader.d.ts.map +1 -0
- package/dist/design/audit/rubric/loader.js +216 -0
- package/dist/design/audit/rubric/loader.js.map +1 -0
- package/dist/design/audit/types.d.ts +166 -0
- package/dist/design/audit/types.d.ts.map +1 -0
- package/dist/design/audit/types.js +8 -0
- package/dist/design/audit/types.js.map +1 -0
- package/dist/drivers/cursor-overlay.d.ts +26 -0
- package/dist/drivers/cursor-overlay.d.ts.map +1 -0
- package/dist/drivers/cursor-overlay.js +235 -0
- package/dist/drivers/cursor-overlay.js.map +1 -0
- package/dist/drivers/playwright.d.ts +32 -0
- package/dist/drivers/playwright.d.ts.map +1 -1
- package/dist/drivers/playwright.js +79 -0
- package/dist/drivers/playwright.js.map +1 -1
- package/dist/drivers/steel.d.ts +135 -0
- package/dist/drivers/steel.d.ts.map +1 -0
- package/dist/drivers/steel.js +236 -0
- package/dist/drivers/steel.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/provider-defaults.d.ts +19 -1
- package/dist/provider-defaults.d.ts.map +1 -1
- package/dist/provider-defaults.js +34 -0
- package/dist/provider-defaults.js.map +1 -1
- package/dist/test-runner.d.ts.map +1 -1
- package/dist/test-runner.js +51 -14
- package/dist/test-runner.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/viewer/viewer.html +940 -0
- package/package.json +3 -2
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Real WCAG 2.1 contrast measurement.
|
|
3
|
+
*
|
|
4
|
+
* Walks every visible text element in the page, computes its actual rendered
|
|
5
|
+
* text color and resolved background color (climbing the parent chain through
|
|
6
|
+
* transparent backgrounds), then calculates the WCAG relative luminance ratio.
|
|
7
|
+
*
|
|
8
|
+
* This is deterministic — pure math, no LLM. Replaces the LLM's "estimated
|
|
9
|
+
* contrast ratio" guesses with ground truth.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* In-page worker that walks the DOM and returns failing elements.
|
|
13
|
+
* Runs entirely in the page context — no Playwright round-trips per element.
|
|
14
|
+
*/
|
|
15
|
+
function inPageContrastCheck() {
|
|
16
|
+
// ── Color parsing ───────────────────────────────────────────────────────
|
|
17
|
+
function parseRGBA(value) {
|
|
18
|
+
const m = value.match(/rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)(?:\s*,\s*([\d.]+))?\s*\)/);
|
|
19
|
+
if (!m)
|
|
20
|
+
return null;
|
|
21
|
+
return {
|
|
22
|
+
r: parseFloat(m[1]),
|
|
23
|
+
g: parseFloat(m[2]),
|
|
24
|
+
b: parseFloat(m[3]),
|
|
25
|
+
a: m[4] !== undefined ? parseFloat(m[4]) : 1,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function rgbToHex(r, g, b) {
|
|
29
|
+
const h = (n) => Math.round(n).toString(16).padStart(2, '0');
|
|
30
|
+
return `#${h(r)}${h(g)}${h(b)}`;
|
|
31
|
+
}
|
|
32
|
+
// Composite a translucent color over an opaque background.
|
|
33
|
+
function composite(fg, bg) {
|
|
34
|
+
const a = fg.a + bg.a * (1 - fg.a);
|
|
35
|
+
if (a === 0)
|
|
36
|
+
return { r: 0, g: 0, b: 0, a: 0 };
|
|
37
|
+
return {
|
|
38
|
+
r: (fg.r * fg.a + bg.r * bg.a * (1 - fg.a)) / a,
|
|
39
|
+
g: (fg.g * fg.a + bg.g * bg.a * (1 - fg.a)) / a,
|
|
40
|
+
b: (fg.b * fg.a + bg.b * bg.a * (1 - fg.a)) / a,
|
|
41
|
+
a,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
// ── WCAG 2.1 contrast math ──────────────────────────────────────────────
|
|
45
|
+
function relativeLuminance(r, g, b) {
|
|
46
|
+
const norm = (c) => {
|
|
47
|
+
const v = c / 255;
|
|
48
|
+
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
|
49
|
+
};
|
|
50
|
+
return 0.2126 * norm(r) + 0.7152 * norm(g) + 0.0722 * norm(b);
|
|
51
|
+
}
|
|
52
|
+
function contrastRatio(fg, bg) {
|
|
53
|
+
const l1 = relativeLuminance(fg.r, fg.g, fg.b);
|
|
54
|
+
const l2 = relativeLuminance(bg.r, bg.g, bg.b);
|
|
55
|
+
const [hi, lo] = l1 > l2 ? [l1, l2] : [l2, l1];
|
|
56
|
+
return (hi + 0.05) / (lo + 0.05);
|
|
57
|
+
}
|
|
58
|
+
// Resolve the effective background color of an element by walking parents
|
|
59
|
+
// and compositing translucent layers.
|
|
60
|
+
function resolveBackground(el) {
|
|
61
|
+
let composed = { r: 255, g: 255, b: 255, a: 1 };
|
|
62
|
+
let current = el;
|
|
63
|
+
const stack = [];
|
|
64
|
+
while (current) {
|
|
65
|
+
const cs = window.getComputedStyle(current);
|
|
66
|
+
const bg = parseRGBA(cs.backgroundColor);
|
|
67
|
+
if (bg && bg.a > 0) {
|
|
68
|
+
stack.push(bg);
|
|
69
|
+
if (bg.a >= 1)
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
current = current.parentElement;
|
|
73
|
+
}
|
|
74
|
+
// Composite from outermost (root) to innermost (element)
|
|
75
|
+
composed = { r: 255, g: 255, b: 255, a: 1 };
|
|
76
|
+
for (let i = stack.length - 1; i >= 0; i--) {
|
|
77
|
+
composed = composite(stack[i], composed);
|
|
78
|
+
}
|
|
79
|
+
return composed;
|
|
80
|
+
}
|
|
81
|
+
// Generate a stable, human-readable selector for an element.
|
|
82
|
+
function selectorFor(el) {
|
|
83
|
+
if (el.id)
|
|
84
|
+
return `#${CSS.escape(el.id)}`;
|
|
85
|
+
const tag = el.tagName.toLowerCase();
|
|
86
|
+
const cls = (el.className && typeof el.className === 'string')
|
|
87
|
+
? el.className.split(/\s+/).filter(Boolean).slice(0, 2).map(c => '.' + CSS.escape(c)).join('')
|
|
88
|
+
: '';
|
|
89
|
+
let parent = el.parentElement;
|
|
90
|
+
let parentSel = '';
|
|
91
|
+
if (parent && parent !== document.body) {
|
|
92
|
+
const ptag = parent.tagName.toLowerCase();
|
|
93
|
+
const pid = parent.id ? `#${CSS.escape(parent.id)}` : '';
|
|
94
|
+
parentSel = `${ptag}${pid} > `;
|
|
95
|
+
}
|
|
96
|
+
return `${parentSel}${tag}${cls}`;
|
|
97
|
+
}
|
|
98
|
+
// Determine if an element qualifies as "large text" per WCAG 2.1:
|
|
99
|
+
// 18pt+ regular OR 14pt+ bold (1pt = 1.333px → 24px or 18.66px)
|
|
100
|
+
function isLargeText(fontSize, fontWeight) {
|
|
101
|
+
const weight = parseInt(fontWeight, 10) || 400;
|
|
102
|
+
if (fontSize >= 24)
|
|
103
|
+
return true;
|
|
104
|
+
if (fontSize >= 18.66 && weight >= 700)
|
|
105
|
+
return true;
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
// ── Walk the DOM ────────────────────────────────────────────────────────
|
|
109
|
+
const aaFailures = [];
|
|
110
|
+
const aaaFailures = [];
|
|
111
|
+
let totalChecked = 0;
|
|
112
|
+
// Only consider visible text-bearing elements.
|
|
113
|
+
const candidates = document.querySelectorAll('*');
|
|
114
|
+
for (const el of candidates) {
|
|
115
|
+
if (totalChecked >= 5000)
|
|
116
|
+
break; // safety bound
|
|
117
|
+
// Must have direct text content (not just nested children with text)
|
|
118
|
+
const hasDirectText = Array.from(el.childNodes).some(n => n.nodeType === Node.TEXT_NODE && (n.textContent ?? '').trim().length > 0);
|
|
119
|
+
if (!hasDirectText)
|
|
120
|
+
continue;
|
|
121
|
+
// Must be visible
|
|
122
|
+
if (el.offsetWidth === 0 || el.offsetHeight === 0)
|
|
123
|
+
continue;
|
|
124
|
+
const cs = window.getComputedStyle(el);
|
|
125
|
+
if (cs.visibility === 'hidden' || cs.display === 'none' || parseFloat(cs.opacity) === 0)
|
|
126
|
+
continue;
|
|
127
|
+
const fg = parseRGBA(cs.color);
|
|
128
|
+
if (!fg)
|
|
129
|
+
continue;
|
|
130
|
+
const bg = resolveBackground(el);
|
|
131
|
+
if (bg.a === 0)
|
|
132
|
+
continue;
|
|
133
|
+
// Composite text color over background if text has alpha
|
|
134
|
+
const compositedFg = fg.a < 1 ? composite(fg, bg) : fg;
|
|
135
|
+
const ratio = contrastRatio(compositedFg, bg);
|
|
136
|
+
const fontSize = parseFloat(cs.fontSize) || 16;
|
|
137
|
+
const large = isLargeText(fontSize, cs.fontWeight);
|
|
138
|
+
const aaRequired = large ? 3 : 4.5;
|
|
139
|
+
const aaaRequired = large ? 4.5 : 7;
|
|
140
|
+
totalChecked++;
|
|
141
|
+
if (ratio < aaRequired) {
|
|
142
|
+
const text = (el.textContent ?? '').trim().slice(0, 60);
|
|
143
|
+
aaFailures.push({
|
|
144
|
+
selector: selectorFor(el),
|
|
145
|
+
text,
|
|
146
|
+
color: rgbToHex(compositedFg.r, compositedFg.g, compositedFg.b),
|
|
147
|
+
background: rgbToHex(bg.r, bg.g, bg.b),
|
|
148
|
+
ratio: Math.round(ratio * 100) / 100,
|
|
149
|
+
required: aaRequired,
|
|
150
|
+
fontSize,
|
|
151
|
+
isLargeText: large,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
else if (ratio < aaaRequired) {
|
|
155
|
+
// Only record AAA failures for the first 50 elements to avoid bloat
|
|
156
|
+
if (aaaFailures.length < 50) {
|
|
157
|
+
const text = (el.textContent ?? '').trim().slice(0, 60);
|
|
158
|
+
aaaFailures.push({
|
|
159
|
+
selector: selectorFor(el),
|
|
160
|
+
text,
|
|
161
|
+
color: rgbToHex(compositedFg.r, compositedFg.g, compositedFg.b),
|
|
162
|
+
background: rgbToHex(bg.r, bg.g, bg.b),
|
|
163
|
+
ratio: Math.round(ratio * 100) / 100,
|
|
164
|
+
required: aaaRequired,
|
|
165
|
+
fontSize,
|
|
166
|
+
isLargeText: large,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return { totalChecked, aaFailures, aaaFailures };
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Run contrast measurement on a Playwright page.
|
|
175
|
+
* Returns ground-truth WCAG contrast failures.
|
|
176
|
+
*/
|
|
177
|
+
export async function measureContrast(page) {
|
|
178
|
+
try {
|
|
179
|
+
const result = await page.evaluate(inPageContrastCheck);
|
|
180
|
+
const totalAaPassed = result.totalChecked - result.aaFailures.length;
|
|
181
|
+
const totalAaaPassed = result.totalChecked - result.aaFailures.length - result.aaaFailures.length;
|
|
182
|
+
return {
|
|
183
|
+
totalChecked: result.totalChecked,
|
|
184
|
+
aaFailures: result.aaFailures,
|
|
185
|
+
aaaFailures: result.aaaFailures,
|
|
186
|
+
summary: {
|
|
187
|
+
aaPassRate: result.totalChecked > 0 ? totalAaPassed / result.totalChecked : 1,
|
|
188
|
+
aaaPassRate: result.totalChecked > 0 ? totalAaaPassed / result.totalChecked : 1,
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return {
|
|
194
|
+
totalChecked: 0,
|
|
195
|
+
aaFailures: [],
|
|
196
|
+
aaaFailures: [],
|
|
197
|
+
summary: { aaPassRate: 1, aaaPassRate: 1 },
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=contrast.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contrast.js","sourceRoot":"","sources":["../../../../src/design/audit/measure/contrast.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Measurement orchestrator — runs all deterministic measurements on a page
|
|
3
|
+
* and returns a single MeasurementBundle for downstream evaluation.
|
|
4
|
+
*
|
|
5
|
+
* Measurements run in parallel where independent. Failures are non-fatal:
|
|
6
|
+
* the audit continues even if a measurement crashes (it just won't have
|
|
7
|
+
* data from that source).
|
|
8
|
+
*/
|
|
9
|
+
import type { Page } from 'playwright';
|
|
10
|
+
import type { MeasurementBundle } from '../types.js';
|
|
11
|
+
export { measureContrast } from './contrast.js';
|
|
12
|
+
export { measureA11y, impactToSeverity } from './a11y.js';
|
|
13
|
+
export declare function gatherMeasurements(page: Page): Promise<MeasurementBundle>;
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/design/audit/measure/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACtC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAIpD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC/C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAEzD,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA4B/E"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Measurement orchestrator — runs all deterministic measurements on a page
|
|
3
|
+
* and returns a single MeasurementBundle for downstream evaluation.
|
|
4
|
+
*
|
|
5
|
+
* Measurements run in parallel where independent. Failures are non-fatal:
|
|
6
|
+
* the audit continues even if a measurement crashes (it just won't have
|
|
7
|
+
* data from that source).
|
|
8
|
+
*/
|
|
9
|
+
import { measureContrast } from './contrast.js';
|
|
10
|
+
import { measureA11y } from './a11y.js';
|
|
11
|
+
export { measureContrast } from './contrast.js';
|
|
12
|
+
export { measureA11y, impactToSeverity } from './a11y.js';
|
|
13
|
+
export async function gatherMeasurements(page) {
|
|
14
|
+
const [contrast, a11y] = await Promise.all([
|
|
15
|
+
measureContrast(page).catch(() => ({
|
|
16
|
+
totalChecked: 0,
|
|
17
|
+
aaFailures: [],
|
|
18
|
+
aaaFailures: [],
|
|
19
|
+
summary: { aaPassRate: 1, aaaPassRate: 1 },
|
|
20
|
+
})),
|
|
21
|
+
measureA11y(page).catch(() => ({
|
|
22
|
+
ran: false,
|
|
23
|
+
error: 'measurement failed',
|
|
24
|
+
violations: [],
|
|
25
|
+
passes: 0,
|
|
26
|
+
})),
|
|
27
|
+
]);
|
|
28
|
+
// Blocking issues: 5+ critical a11y violations OR > 25% contrast failure rate
|
|
29
|
+
const criticalA11y = a11y.violations.filter(v => v.impact === 'critical' || v.impact === 'serious').length;
|
|
30
|
+
const contrastFailRate = 1 - contrast.summary.aaPassRate;
|
|
31
|
+
const hasBlockingIssues = criticalA11y >= 5 || contrastFailRate > 0.25;
|
|
32
|
+
return {
|
|
33
|
+
contrast,
|
|
34
|
+
a11y,
|
|
35
|
+
hasBlockingIssues,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/design/audit/measure/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAEvC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC/C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAEzD,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,IAAU;IACjD,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACzC,eAAe,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;YACjC,YAAY,EAAE,CAAC;YACf,UAAU,EAAE,EAAE;YACd,WAAW,EAAE,EAAE;YACf,OAAO,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE;SAC3C,CAAC,CAAC;QACH,WAAW,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;YAC7B,GAAG,EAAE,KAAK;YACV,KAAK,EAAE,oBAAoB;YAC3B,UAAU,EAAE,EAAE;YACd,MAAM,EAAE,CAAC;SACV,CAAC,CAAC;KACJ,CAAC,CAAA;IAEF,8EAA8E;IAC9E,MAAM,YAAY,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CACzC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,UAAU,IAAI,CAAC,CAAC,MAAM,KAAK,SAAS,CACvD,CAAC,MAAM,CAAA;IACR,MAAM,gBAAgB,GAAG,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAA;IACxD,MAAM,iBAAiB,GAAG,YAAY,IAAI,CAAC,IAAI,gBAAgB,GAAG,IAAI,CAAA;IAEtE,OAAO;QACL,QAAQ;QACR,IAAI;QACJ,iBAAiB;KAClB,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit pipeline — orchestrates the full Gen 2 audit for one page.
|
|
3
|
+
*
|
|
4
|
+
* load page → classify → compose rubric → measure → evaluate → result
|
|
5
|
+
*
|
|
6
|
+
* The pipeline is the only place that knows about all stages. Each stage is
|
|
7
|
+
* a pure function elsewhere in this module — easy to unit test in isolation.
|
|
8
|
+
*/
|
|
9
|
+
import type { Page } from 'playwright';
|
|
10
|
+
import type { Brain } from '../../brain/index.js';
|
|
11
|
+
import { PlaywrightDriver } from '../../drivers/playwright.js';
|
|
12
|
+
import type { PageAuditResult } from './types.js';
|
|
13
|
+
export interface AuditOnePageOptions {
|
|
14
|
+
brain: Brain;
|
|
15
|
+
driver: PlaywrightDriver;
|
|
16
|
+
page: Page;
|
|
17
|
+
url: string;
|
|
18
|
+
/** When set, skip classification and use this profile fragment directly */
|
|
19
|
+
profileOverride?: string;
|
|
20
|
+
/** Directory to save the screenshot (skipped when undefined) */
|
|
21
|
+
screenshotDir?: string;
|
|
22
|
+
/** User-supplied rubric fragments directory */
|
|
23
|
+
userRubricsDir?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Audit one page through the full Gen 2 pipeline.
|
|
27
|
+
*/
|
|
28
|
+
export declare function auditOnePage(opts: AuditOnePageOptions): Promise<PageAuditResult>;
|
|
29
|
+
/**
|
|
30
|
+
* Persist a `PageAuditResult` to JSON for downstream tooling.
|
|
31
|
+
*/
|
|
32
|
+
export declare function writeResultJson(result: PageAuditResult, dir: string): void;
|
|
33
|
+
//# sourceMappingURL=pipeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../../src/design/audit/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACtC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAA;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AAK9D,OAAO,KAAK,EAAE,eAAe,EAAsB,MAAM,YAAY,CAAA;AAErE,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,KAAK,CAAA;IACZ,MAAM,EAAE,gBAAgB,CAAA;IACxB,IAAI,EAAE,IAAI,CAAA;IACV,GAAG,EAAE,MAAM,CAAA;IACX,2EAA2E;IAC3E,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,+CAA+C;IAC/C,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAqBD;;GAEG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,eAAe,CAAC,CAyEtF;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAG1E"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit pipeline — orchestrates the full Gen 2 audit for one page.
|
|
3
|
+
*
|
|
4
|
+
* load page → classify → compose rubric → measure → evaluate → result
|
|
5
|
+
*
|
|
6
|
+
* The pipeline is the only place that knows about all stages. Each stage is
|
|
7
|
+
* a pure function elsewhere in this module — easy to unit test in isolation.
|
|
8
|
+
*/
|
|
9
|
+
import * as fs from 'node:fs';
|
|
10
|
+
import * as path from 'node:path';
|
|
11
|
+
import { classifyPage, defaultClassification } from './classify.js';
|
|
12
|
+
import { composeRubric, composeRubricFromProfile } from './rubric/loader.js';
|
|
13
|
+
import { gatherMeasurements } from './measure/index.js';
|
|
14
|
+
import { evaluatePage } from './evaluate.js';
|
|
15
|
+
const COOKIE_BANNER_SELECTORS = [
|
|
16
|
+
'button:has-text("Accept all")',
|
|
17
|
+
'button:has-text("Accept")',
|
|
18
|
+
'button:has-text("Reject all")',
|
|
19
|
+
'button:has-text("Got it")',
|
|
20
|
+
'button:has-text("Close")',
|
|
21
|
+
];
|
|
22
|
+
async function dismissCookieBanners(page) {
|
|
23
|
+
for (const sel of COOKIE_BANNER_SELECTORS) {
|
|
24
|
+
const btn = page.locator(sel).first();
|
|
25
|
+
if (await btn.isVisible({ timeout: 500 }).catch(() => false)) {
|
|
26
|
+
await btn.click({ timeout: 2000 }).catch(() => null);
|
|
27
|
+
await page.waitForTimeout(500);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Audit one page through the full Gen 2 pipeline.
|
|
34
|
+
*/
|
|
35
|
+
export async function auditOnePage(opts) {
|
|
36
|
+
const { brain, driver, page, url, profileOverride, screenshotDir, userRubricsDir } = opts;
|
|
37
|
+
try {
|
|
38
|
+
// ── 1. Load the page ──
|
|
39
|
+
await page
|
|
40
|
+
.goto(url, { waitUntil: 'networkidle', timeout: 20_000 })
|
|
41
|
+
.catch(() => page.goto(url, { waitUntil: 'domcontentloaded', timeout: 15_000 }));
|
|
42
|
+
await page.waitForTimeout(2000);
|
|
43
|
+
await dismissCookieBanners(page);
|
|
44
|
+
const state = await driver.observe();
|
|
45
|
+
// ── 2. Save screenshot ──
|
|
46
|
+
let screenshotPath;
|
|
47
|
+
if (screenshotDir) {
|
|
48
|
+
const slug = new URL(url).pathname.replace(/\//g, '_').replace(/^_/, '') || 'index';
|
|
49
|
+
screenshotPath = path.join(screenshotDir, `${slug}.png`);
|
|
50
|
+
await page.screenshot({ path: screenshotPath, fullPage: false });
|
|
51
|
+
}
|
|
52
|
+
// ── 3. Classify (or use profile override) ──
|
|
53
|
+
let classification;
|
|
54
|
+
if (profileOverride) {
|
|
55
|
+
// Build a synthetic classification matching the explicit profile
|
|
56
|
+
classification = {
|
|
57
|
+
...defaultClassification(),
|
|
58
|
+
type: profileOverride,
|
|
59
|
+
confidence: 1,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
classification = await classifyPage(brain, state);
|
|
64
|
+
}
|
|
65
|
+
// ── 4. Compose rubric ──
|
|
66
|
+
const rubric = profileOverride
|
|
67
|
+
? composeRubricFromProfile(profileOverride)
|
|
68
|
+
: composeRubric(classification, undefined, userRubricsDir);
|
|
69
|
+
// Confidence fallback: if classifier isn't confident, fall back to general
|
|
70
|
+
if (!profileOverride && classification.confidence < 0.5) {
|
|
71
|
+
// Re-compose with universal-only fragments
|
|
72
|
+
const fallbackClass = {
|
|
73
|
+
...classification,
|
|
74
|
+
type: 'unknown',
|
|
75
|
+
};
|
|
76
|
+
Object.assign(rubric, composeRubric(fallbackClass, undefined, userRubricsDir));
|
|
77
|
+
}
|
|
78
|
+
// ── 5. Measure (deterministic, runs in parallel) ──
|
|
79
|
+
const measurements = await gatherMeasurements(page);
|
|
80
|
+
// ── 6. Evaluate ──
|
|
81
|
+
const result = await evaluatePage(brain, {
|
|
82
|
+
url,
|
|
83
|
+
state,
|
|
84
|
+
classification,
|
|
85
|
+
rubric,
|
|
86
|
+
measurements,
|
|
87
|
+
screenshotPath,
|
|
88
|
+
});
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
return {
|
|
93
|
+
url,
|
|
94
|
+
score: 0,
|
|
95
|
+
summary: 'Audit failed',
|
|
96
|
+
strengths: [],
|
|
97
|
+
findings: [],
|
|
98
|
+
error: err instanceof Error ? err.message : String(err),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Persist a `PageAuditResult` to JSON for downstream tooling.
|
|
104
|
+
*/
|
|
105
|
+
export function writeResultJson(result, dir) {
|
|
106
|
+
const slug = new URL(result.url).hostname.replace(/[^a-z0-9.-]/gi, '_');
|
|
107
|
+
fs.writeFileSync(path.join(dir, `${slug}-${Date.now()}.json`), JSON.stringify(result, null, 2));
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=pipeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../../src/design/audit/pipeline.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ROI scoring + cross-page systemic detection.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions, no LLM calls. Easy to unit test.
|
|
5
|
+
*
|
|
6
|
+
* The LLM produces impact/effort/blast for each finding (in turn 3 of the
|
|
7
|
+
* audit pipeline). This module:
|
|
8
|
+
* 1. Computes the final `roi` score from those raw values.
|
|
9
|
+
* 2. Detects findings that appear on multiple pages and elevates their
|
|
10
|
+
* blast to 'system' (with the count of affected pages).
|
|
11
|
+
* 3. Provides sort + selection helpers for "top fixes" surfacing.
|
|
12
|
+
*/
|
|
13
|
+
import type { DesignFinding } from '../../types.js';
|
|
14
|
+
/**
|
|
15
|
+
* Compute the ROI score for a single finding from its impact/effort/blast.
|
|
16
|
+
* Returns a positive number — higher means "fix this first."
|
|
17
|
+
*
|
|
18
|
+
* Formula: (impact * blastWeight) / effort
|
|
19
|
+
*
|
|
20
|
+
* Edge cases:
|
|
21
|
+
* - Missing fields → use defaults (impact=5, effort=5, blast=page) → roi = 1.0
|
|
22
|
+
* - effort = 0 is treated as 1 to avoid division by zero
|
|
23
|
+
*/
|
|
24
|
+
export declare function computeRoi(finding: DesignFinding): number;
|
|
25
|
+
/**
|
|
26
|
+
* Annotate every finding with its computed ROI in place.
|
|
27
|
+
* Returns the same array for chaining.
|
|
28
|
+
*/
|
|
29
|
+
export declare function annotateRoi(findings: DesignFinding[]): DesignFinding[];
|
|
30
|
+
/**
|
|
31
|
+
* Cross-page systemic detection.
|
|
32
|
+
*
|
|
33
|
+
* Groups findings from across all audited pages by `(category, normalized_description)`.
|
|
34
|
+
* Any group that appears on 2+ distinct pages becomes a single canonical
|
|
35
|
+
* finding with `blast: 'system'` and `pageCount` set, replacing the duplicates.
|
|
36
|
+
*
|
|
37
|
+
* Normalization: lowercased, first 80 chars only, trimmed.
|
|
38
|
+
* Conservative on purpose — better to under-merge than to merge unrelated findings.
|
|
39
|
+
*
|
|
40
|
+
* @param perPageFindings - array of finding arrays, one per audited page
|
|
41
|
+
* @returns flat array of findings, with cross-page duplicates collapsed
|
|
42
|
+
*/
|
|
43
|
+
export declare function detectSystemicFindings(perPageFindings: DesignFinding[][]): DesignFinding[];
|
|
44
|
+
/**
|
|
45
|
+
* Pick the top N findings by ROI (descending).
|
|
46
|
+
* Stable: ties broken by severity (critical > major > minor), then by description.
|
|
47
|
+
*/
|
|
48
|
+
export declare function topByRoi(findings: DesignFinding[], n: number): DesignFinding[];
|
|
49
|
+
//# sourceMappingURL=roi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"roi.d.ts","sourceRoot":"","sources":["../../../src/design/audit/roi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAsBnD;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAMzD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,aAAa,EAAE,CAKtE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CACpC,eAAe,EAAE,aAAa,EAAE,EAAE,GACjC,aAAa,EAAE,CA+CjB;AAkBD;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,aAAa,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,aAAa,EAAE,CAgB9E"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ROI scoring + cross-page systemic detection.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions, no LLM calls. Easy to unit test.
|
|
5
|
+
*
|
|
6
|
+
* The LLM produces impact/effort/blast for each finding (in turn 3 of the
|
|
7
|
+
* audit pipeline). This module:
|
|
8
|
+
* 1. Computes the final `roi` score from those raw values.
|
|
9
|
+
* 2. Detects findings that appear on multiple pages and elevates their
|
|
10
|
+
* blast to 'system' (with the count of affected pages).
|
|
11
|
+
* 3. Provides sort + selection helpers for "top fixes" surfacing.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Multipliers for converting blast scope into a leverage factor.
|
|
15
|
+
*
|
|
16
|
+
* A system-level fix touches every page that uses that component / token,
|
|
17
|
+
* so its impact is multiplied. The effect on ROI is asymmetric — these
|
|
18
|
+
* weights are deliberately conservative; we want ROI to favor systemic
|
|
19
|
+
* fixes without completely drowning out high-impact page-specific issues.
|
|
20
|
+
*/
|
|
21
|
+
const BLAST_WEIGHT = {
|
|
22
|
+
page: 1,
|
|
23
|
+
section: 1.25,
|
|
24
|
+
component: 1.75,
|
|
25
|
+
system: 2.5,
|
|
26
|
+
};
|
|
27
|
+
/** Default values when the LLM didn't produce ROI fields. */
|
|
28
|
+
const DEFAULT_IMPACT = 5;
|
|
29
|
+
const DEFAULT_EFFORT = 5;
|
|
30
|
+
const DEFAULT_BLAST = 'page';
|
|
31
|
+
/**
|
|
32
|
+
* Compute the ROI score for a single finding from its impact/effort/blast.
|
|
33
|
+
* Returns a positive number — higher means "fix this first."
|
|
34
|
+
*
|
|
35
|
+
* Formula: (impact * blastWeight) / effort
|
|
36
|
+
*
|
|
37
|
+
* Edge cases:
|
|
38
|
+
* - Missing fields → use defaults (impact=5, effort=5, blast=page) → roi = 1.0
|
|
39
|
+
* - effort = 0 is treated as 1 to avoid division by zero
|
|
40
|
+
*/
|
|
41
|
+
export function computeRoi(finding) {
|
|
42
|
+
const impact = finding.impact ?? DEFAULT_IMPACT;
|
|
43
|
+
const effort = Math.max(1, finding.effort ?? DEFAULT_EFFORT);
|
|
44
|
+
const blast = finding.blast ?? DEFAULT_BLAST;
|
|
45
|
+
const weight = BLAST_WEIGHT[blast];
|
|
46
|
+
return Math.round(((impact * weight) / effort) * 100) / 100;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Annotate every finding with its computed ROI in place.
|
|
50
|
+
* Returns the same array for chaining.
|
|
51
|
+
*/
|
|
52
|
+
export function annotateRoi(findings) {
|
|
53
|
+
for (const f of findings) {
|
|
54
|
+
f.roi = computeRoi(f);
|
|
55
|
+
}
|
|
56
|
+
return findings;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Cross-page systemic detection.
|
|
60
|
+
*
|
|
61
|
+
* Groups findings from across all audited pages by `(category, normalized_description)`.
|
|
62
|
+
* Any group that appears on 2+ distinct pages becomes a single canonical
|
|
63
|
+
* finding with `blast: 'system'` and `pageCount` set, replacing the duplicates.
|
|
64
|
+
*
|
|
65
|
+
* Normalization: lowercased, first 80 chars only, trimmed.
|
|
66
|
+
* Conservative on purpose — better to under-merge than to merge unrelated findings.
|
|
67
|
+
*
|
|
68
|
+
* @param perPageFindings - array of finding arrays, one per audited page
|
|
69
|
+
* @returns flat array of findings, with cross-page duplicates collapsed
|
|
70
|
+
*/
|
|
71
|
+
export function detectSystemicFindings(perPageFindings) {
|
|
72
|
+
// Map of normalized key → { canonical finding, set of page indices it appeared on }
|
|
73
|
+
const groups = new Map();
|
|
74
|
+
// Findings that don't dedupe stay in their original page bucket
|
|
75
|
+
const singletonsByPage = perPageFindings.map(() => []);
|
|
76
|
+
for (let pageIdx = 0; pageIdx < perPageFindings.length; pageIdx++) {
|
|
77
|
+
const findings = perPageFindings[pageIdx];
|
|
78
|
+
for (const finding of findings) {
|
|
79
|
+
const key = normalizeFindingKey(finding);
|
|
80
|
+
const existing = groups.get(key);
|
|
81
|
+
if (existing) {
|
|
82
|
+
existing.pages.add(pageIdx);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
groups.set(key, {
|
|
86
|
+
canonical: { ...finding },
|
|
87
|
+
pages: new Set([pageIdx]),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// For each group: if it appears on 2+ pages, promote to systemic.
|
|
93
|
+
// Otherwise, return it back to its original page bucket as a singleton.
|
|
94
|
+
const systemic = [];
|
|
95
|
+
for (const { canonical, pages } of groups.values()) {
|
|
96
|
+
if (pages.size >= 2) {
|
|
97
|
+
const promoted = {
|
|
98
|
+
...canonical,
|
|
99
|
+
blast: 'system',
|
|
100
|
+
pageCount: pages.size,
|
|
101
|
+
description: `[appears on ${pages.size} pages] ${canonical.description}`,
|
|
102
|
+
};
|
|
103
|
+
promoted.roi = computeRoi(promoted);
|
|
104
|
+
systemic.push(promoted);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
const onlyPage = pages.values().next().value;
|
|
108
|
+
if (onlyPage !== undefined) {
|
|
109
|
+
singletonsByPage[onlyPage].push(canonical);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Flatten: systemic findings first (higher ROI), then per-page singletons
|
|
114
|
+
const flat = [...systemic];
|
|
115
|
+
for (const page of singletonsByPage)
|
|
116
|
+
flat.push(...page);
|
|
117
|
+
return flat;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Normalize a finding into a key for cross-page grouping.
|
|
121
|
+
*
|
|
122
|
+
* Uses (category, description-prefix) — conservative enough to avoid
|
|
123
|
+
* merging unrelated findings, loose enough to catch the same issue
|
|
124
|
+
* worded slightly differently across pages.
|
|
125
|
+
*/
|
|
126
|
+
function normalizeFindingKey(finding) {
|
|
127
|
+
const desc = (finding.description ?? '')
|
|
128
|
+
.toLowerCase()
|
|
129
|
+
.replace(/\s+/g, ' ')
|
|
130
|
+
.trim()
|
|
131
|
+
.slice(0, 80);
|
|
132
|
+
return `${finding.category}|${desc}`;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Pick the top N findings by ROI (descending).
|
|
136
|
+
* Stable: ties broken by severity (critical > major > minor), then by description.
|
|
137
|
+
*/
|
|
138
|
+
export function topByRoi(findings, n) {
|
|
139
|
+
const SEVERITY_RANK = {
|
|
140
|
+
critical: 0,
|
|
141
|
+
major: 1,
|
|
142
|
+
minor: 2,
|
|
143
|
+
};
|
|
144
|
+
return [...findings]
|
|
145
|
+
.sort((a, b) => {
|
|
146
|
+
const roiA = a.roi ?? computeRoi(a);
|
|
147
|
+
const roiB = b.roi ?? computeRoi(b);
|
|
148
|
+
if (roiB !== roiA)
|
|
149
|
+
return roiB - roiA;
|
|
150
|
+
const sevDiff = SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity];
|
|
151
|
+
if (sevDiff !== 0)
|
|
152
|
+
return sevDiff;
|
|
153
|
+
return a.description.localeCompare(b.description);
|
|
154
|
+
})
|
|
155
|
+
.slice(0, n);
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=roi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"roi.js","sourceRoot":"","sources":["../../../src/design/audit/roi.ts"],"names":[],"mappings":"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"}
|