cbrowser 19.2.2 → 19.3.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/analysis/accessibility-empathy.d.ts +48 -1
- package/dist/analysis/accessibility-empathy.d.ts.map +1 -1
- package/dist/analysis/accessibility-empathy.js +766 -92
- package/dist/analysis/accessibility-empathy.js.map +1 -1
- package/dist/analysis/agent-ready-audit.d.ts +28 -0
- package/dist/analysis/agent-ready-audit.d.ts.map +1 -1
- package/dist/analysis/agent-ready-audit.js +218 -21
- package/dist/analysis/agent-ready-audit.js.map +1 -1
- package/dist/analysis/bug-hunter.d.ts +45 -0
- package/dist/analysis/bug-hunter.d.ts.map +1 -1
- package/dist/analysis/bug-hunter.js +211 -64
- package/dist/analysis/bug-hunter.js.map +1 -1
- package/dist/analysis/competitive-benchmark.d.ts.map +1 -1
- package/dist/analysis/competitive-benchmark.js +4 -2
- package/dist/analysis/competitive-benchmark.js.map +1 -1
- package/dist/browser/session-manager.d.ts.map +1 -1
- package/dist/browser/session-manager.js +4 -2
- package/dist/browser/session-manager.js.map +1 -1
- package/dist/browser.d.ts +11 -2
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +64 -15
- package/dist/browser.js.map +1 -1
- package/dist/cli.js +2 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +19 -19
- package/dist/mcp-server.js.map +1 -1
- package/dist/mcp-tools/base/artifact-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/artifact-tools.js +29 -2
- package/dist/mcp-tools/base/artifact-tools.js.map +1 -1
- package/dist/mcp-tools/base/audit-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/audit-tools.js +114 -58
- package/dist/mcp-tools/base/audit-tools.js.map +1 -1
- package/dist/mcp-tools/base/browser-state-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/browser-state-tools.js +3 -2
- package/dist/mcp-tools/base/browser-state-tools.js.map +1 -1
- package/dist/mcp-tools/base/bug-analysis-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/bug-analysis-tools.js +4 -67
- package/dist/mcp-tools/base/bug-analysis-tools.js.map +1 -1
- package/dist/mcp-tools/base/index.js +2 -2
- package/dist/mcp-tools/base/index.js.map +1 -1
- package/dist/mcp-tools/base/interaction-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/interaction-tools.js +4 -0
- package/dist/mcp-tools/base/interaction-tools.js.map +1 -1
- package/dist/mcp-tools/base/visual-testing-tools.d.ts +2 -2
- package/dist/mcp-tools/base/visual-testing-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/visual-testing-tools.js +156 -38
- package/dist/mcp-tools/base/visual-testing-tools.js.map +1 -1
- package/dist/mcp-tools/security-layer.d.ts +33 -0
- package/dist/mcp-tools/security-layer.d.ts.map +1 -1
- package/dist/mcp-tools/security-layer.js +191 -1
- package/dist/mcp-tools/security-layer.js.map +1 -1
- package/dist/navigation-errors.d.ts +21 -0
- package/dist/navigation-errors.d.ts.map +1 -0
- package/dist/navigation-errors.js +119 -0
- package/dist/navigation-errors.js.map +1 -0
- package/dist/remediation/llms-txt.d.ts.map +1 -1
- package/dist/remediation/llms-txt.js +3 -1
- package/dist/remediation/llms-txt.js.map +1 -1
- package/dist/remediation/structured-data.d.ts.map +1 -1
- package/dist/remediation/structured-data.js +3 -1
- package/dist/remediation/structured-data.js.map +1 -1
- package/dist/security/script-gate.d.ts +7 -0
- package/dist/security/script-gate.d.ts.map +1 -1
- package/dist/security/script-gate.js +7 -0
- package/dist/security/script-gate.js.map +1 -1
- package/dist/testing/flaky-detection.js +1 -1
- package/dist/testing/flaky-detection.js.map +1 -1
- package/dist/testing/nl-test-suite.d.ts +12 -0
- package/dist/testing/nl-test-suite.d.ts.map +1 -1
- package/dist/testing/nl-test-suite.js +92 -14
- package/dist/testing/nl-test-suite.js.map +1 -1
- package/dist/testing/test-repair.js +3 -3
- package/dist/testing/test-repair.js.map +1 -1
- package/dist/types.d.ts +51 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/visual/attention-transport.d.ts +89 -11
- package/dist/visual/attention-transport.d.ts.map +1 -1
- package/dist/visual/attention-transport.js +148 -53
- package/dist/visual/attention-transport.js.map +1 -1
- package/dist/visual/cross-browser.d.ts.map +1 -1
- package/dist/visual/cross-browser.js +3 -1
- package/dist/visual/cross-browser.js.map +1 -1
- package/dist/visual/distance-metrics.js +6 -6
- package/dist/visual/distance-metrics.js.map +1 -1
- package/dist/visual/perceptual-transport.d.ts +14 -3
- package/dist/visual/perceptual-transport.d.ts.map +1 -1
- package/dist/visual/perceptual-transport.js +51 -9
- package/dist/visual/perceptual-transport.js.map +1 -1
- package/package.json +1 -1
|
@@ -14,8 +14,22 @@ import { z } from "zod";
|
|
|
14
14
|
import { readFileSync, existsSync, statSync } from "node:fs";
|
|
15
15
|
import { join, basename, extname } from "node:path";
|
|
16
16
|
import { artifactDir } from "../../artifact-store.js";
|
|
17
|
+
import { fitImageForInline } from "../inline-image.js";
|
|
18
|
+
import { MAX_RESPONSE_SIZE } from "../screenshot-utils.js";
|
|
17
19
|
/** Largest artifact returned inline. Base64 adds a third on top of this. */
|
|
18
20
|
const MAX_BYTES = 3_500_000;
|
|
21
|
+
/**
|
|
22
|
+
* Base64 budget for an image this tool returns by default.
|
|
23
|
+
*
|
|
24
|
+
* The comment above says it: hosts truncate a tool result near 150k chars. That
|
|
25
|
+
* holds for a view's callServerTool too. A 1280x800 attention heatmap PNG is
|
|
26
|
+
* ~666k chars of base64, so the attention view asked for it, the host cut it,
|
|
27
|
+
* and the panel showed no image -- while the tool result it rendered from
|
|
28
|
+
* carried a perfectly good inline preview it never reads (Alexa, 2026-10-09:
|
|
29
|
+
* "the attention analysis image doesn't load inline again"). Images over this
|
|
30
|
+
* are fitted (JPEG quality ladder, then downscale) unless `full: true`.
|
|
31
|
+
*/
|
|
32
|
+
const VIEW_IMAGE_BUDGET = MAX_RESPONSE_SIZE - 10_000;
|
|
19
33
|
const MIME = {
|
|
20
34
|
".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg",
|
|
21
35
|
".gif": "image/gif", ".webp": "image/webp",
|
|
@@ -26,6 +40,7 @@ export function registerArtifactTools(server) {
|
|
|
26
40
|
description: "Fetch a generated image artifact (heatmap, diff, capture frame) as inline image data. Called by interactive views to display images the widget sandbox cannot load by URL; not usually needed directly.",
|
|
27
41
|
inputSchema: {
|
|
28
42
|
file: z.string().describe("Artifact filename as returned by the producing tool, e.g. 'heatmap-abc123.png'"),
|
|
43
|
+
full: z.boolean().optional().describe("Return the original bytes even when they exceed the host's ~150k-char result cap (a host may then truncate the result). Default false: a larger still image comes back as a JPEG fitted under the cap."),
|
|
29
44
|
},
|
|
30
45
|
annotations: {
|
|
31
46
|
title: "Fetch Artifact",
|
|
@@ -37,7 +52,7 @@ export function registerArtifactTools(server) {
|
|
|
37
52
|
// Hidden from the model's tool list: it exists for views to call, and an
|
|
38
53
|
// agent reaching for it directly would be fetching bytes it cannot use.
|
|
39
54
|
_meta: { ui: { visibility: ["app"] } },
|
|
40
|
-
}, async ({ file }) => {
|
|
55
|
+
}, async ({ file, full }) => {
|
|
41
56
|
// basename strips any directory component, so a traversal attempt
|
|
42
57
|
// resolves to a plain name inside the artifact dir rather than escaping it.
|
|
43
58
|
// The name is then re-validated, because basename alone would happily
|
|
@@ -66,10 +81,22 @@ export function registerArtifactTools(server) {
|
|
|
66
81
|
isError: true,
|
|
67
82
|
};
|
|
68
83
|
}
|
|
84
|
+
const bytes = readFileSync(path);
|
|
85
|
+
const base64 = bytes.toString("base64");
|
|
86
|
+
// Animated GIFs are never re-encoded: a JPEG would keep one frame.
|
|
87
|
+
if (full !== true && ext !== ".gif" && base64.length > VIEW_IMAGE_BUDGET) {
|
|
88
|
+
const fitted = await fitImageForInline(bytes, VIEW_IMAGE_BUDGET).catch(() => null);
|
|
89
|
+
if (fitted) {
|
|
90
|
+
return {
|
|
91
|
+
content: [{ type: "image", data: fitted.data, mimeType: fitted.mimeType }],
|
|
92
|
+
_meta: { fitted: { width: fitted.width, height: fitted.height, sourceWidth: fitted.sourceWidth, sourceHeight: fitted.sourceHeight, quality: fitted.quality, originalBase64Chars: base64.length } },
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
69
96
|
return {
|
|
70
97
|
content: [{
|
|
71
98
|
type: "image",
|
|
72
|
-
data:
|
|
99
|
+
data: base64,
|
|
73
100
|
mimeType: MIME[ext],
|
|
74
101
|
}],
|
|
75
102
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"artifact-tools.js","sourceRoot":"","sources":["../../../src/mcp-tools/base/artifact-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"artifact-tools.js","sourceRoot":"","sources":["../../../src/mcp-tools/base/artifact-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAG3D,4EAA4E;AAC5E,MAAM,SAAS,GAAG,SAAS,CAAC;AAE5B;;;;;;;;;;GAUG;AACH,MAAM,iBAAiB,GAAG,iBAAiB,GAAG,MAAM,CAAC;AAErD,MAAM,IAAI,GAA2B;IACnC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY;IAChE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY;CAC3C,CAAC;AAEF,MAAM,UAAU,qBAAqB,CAAC,MAAiB;IACrD,MAAM,CAAC,YAAY,CAAC,gBAAgB,EAAE;QACpC,KAAK,EAAE,gBAAgB;QACvB,WAAW,EAAE,yMAAyM;QACtN,WAAW,EAAE;YACX,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,gFAAgF,CAAC;YAC3G,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,wMAAwM,CAAC;SAChP;QACD,WAAW,EAAE;YACX,KAAK,EAAE,gBAAgB;YACvB,YAAY,EAAE,IAAI;YAClB,eAAe,EAAE,KAAK;YACtB,cAAc,EAAE,IAAI;YACpB,aAAa,EAAE,KAAK;SACrB;QACD,yEAAyE;QACzE,wEAAwE;QACxE,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE;KACvC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE;QACxB,kEAAkE;QAClE,4EAA4E;QAC5E,sEAAsE;QACtE,yCAAyC;QACzC,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QACpC,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;QACxC,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1E,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,wBAAwB,EAAE,CAAC,EAAE,CAAC;gBACtF,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,CAAC,CAAC;QACvC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACtB,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,qBAAqB,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC;gBACzF,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC;QACjC,IAAI,IAAI,GAAG,SAAS,EAAE,CAAC;YACrB,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;4BAC7C,KAAK,EAAE,eAAe,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,gBAAgB,CAAC,SAAS,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,kBAAkB;yBAC5G,CAAC,EAAE,CAAC;gBACL,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;QACD,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;QACjC,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACxC,mEAAmE;QACnE,IAAI,IAAI,KAAK,IAAI,IAAI,GAAG,KAAK,MAAM,IAAI,MAAM,CAAC,MAAM,GAAG,iBAAiB,EAAE,CAAC;YACzE,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;YACnF,IAAI,MAAM,EAAE,CAAC;gBACX,OAAO;oBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,OAAgB,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;oBACnF,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,YAAY,EAAE,MAAM,CAAC,YAAY,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,EAAE;iBACnM,CAAC;YACJ,CAAC;QACH,CAAC;QACD,OAAO;YACL,OAAO,EAAE,CAAC;oBACR,IAAI,EAAE,OAAgB;oBACtB,IAAI,EAAE,MAAM;oBACZ,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAW;iBAC9B,CAAC;SACH,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit-tools.d.ts","sourceRoot":"","sources":["../../../src/mcp-tools/base/audit-tools.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,OAAO,KAAK,EAAE,SAAS,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"audit-tools.d.ts","sourceRoot":"","sources":["../../../src/mcp-tools/base/audit-tools.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,OAAO,KAAK,EAAE,SAAS,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AA0DtE;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,uBAAuB,GAAG,IAAI,CAqkC7F;AAED;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA+kBhE"}
|
|
@@ -10,8 +10,9 @@ import { randomBytes } from "crypto";
|
|
|
10
10
|
import { writeArtifact } from "../../artifact-store.js";
|
|
11
11
|
import { buildContentWithPreviews } from "../inline-image.js";
|
|
12
12
|
import { bridgeCapacityTraits } from "../../visual/cognitive-models.js";
|
|
13
|
-
import { runAgentReadyAudit, runCompetitiveBenchmark, runEmpathyAudit, runWebMCPReadyAudit, } from "../../analysis/index.js";
|
|
13
|
+
import { runAgentReadyAudit, pageAgentReadyFindings, runCompetitiveBenchmark, runEmpathyAudit, runWebMCPReadyAudit, } from "../../analysis/index.js";
|
|
14
14
|
import { listAccessibilityPersonas } from "../../personas.js";
|
|
15
|
+
import { barrierElementCount, countAffectedElements } from "../../analysis/accessibility-empathy.js";
|
|
15
16
|
import { getDefaultConfig } from "../../config.js";
|
|
16
17
|
import { writeFileSync, mkdirSync, existsSync } from "fs";
|
|
17
18
|
import { join } from "path";
|
|
@@ -69,6 +70,8 @@ export function registerAuditTools(server, context) {
|
|
|
69
70
|
inputSchema: {
|
|
70
71
|
url: z.string().url().describe("URL to audit"),
|
|
71
72
|
device: z.string().optional().describe("Device emulation: 'mobile', 'tablet', 'desktop', or specific device like 'iPhone 15'. Tests mobile AI-friendliness (responsive selectors, touch targets, viewport-specific layouts)."),
|
|
73
|
+
limit: z.number().optional().default(5).describe("How many issues (worst first) and recommendations (priority order) to return. Totals and severity counts always cover ALL findings."),
|
|
74
|
+
offset: z.number().optional().default(0).describe("Skip this many issues and recommendations before returning, for paging through a large result."),
|
|
72
75
|
},
|
|
73
76
|
annotations: {
|
|
74
77
|
title: "Agent-Ready Audit",
|
|
@@ -77,43 +80,18 @@ export function registerAuditTools(server, context) {
|
|
|
77
80
|
idempotentHint: false,
|
|
78
81
|
openWorldHint: true,
|
|
79
82
|
},
|
|
80
|
-
}, async ({ url, device }) => {
|
|
83
|
+
}, async ({ url, device, limit, offset }) => {
|
|
81
84
|
const result = await runAgentReadyAudit(url, { headless: true, ...(device ? { device } : {}) });
|
|
82
85
|
const responseData = {
|
|
83
86
|
url: result.url,
|
|
84
87
|
score: result.score,
|
|
85
88
|
grade: result.grade,
|
|
86
89
|
summary: result.summary,
|
|
87
|
-
// Worst-first, and the cut is reported.
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
// five low ones, with nothing saying anything had been dropped. Found
|
|
93
|
-
// when a new critical detector fired in the audit and was invisible in
|
|
94
|
-
// the tool that wraps it. (2026-08-01)
|
|
95
|
-
...(() => {
|
|
96
|
-
const rank = (sev) => ({ critical: 0, high: 1, medium: 2, low: 3, info: 4 }[String(sev).toLowerCase()] ?? 5);
|
|
97
|
-
const sorted = [...result.issues].sort((a, b) => rank(a.severity) - rank(b.severity));
|
|
98
|
-
const shown = sorted.slice(0, 5);
|
|
99
|
-
const omitted = sorted.length - shown.length;
|
|
100
|
-
return {
|
|
101
|
-
topIssues: shown,
|
|
102
|
-
issuesFound: result.issues.length,
|
|
103
|
-
...(omitted > 0
|
|
104
|
-
? {
|
|
105
|
-
issuesOmitted: omitted,
|
|
106
|
-
issuesNote: `Showing the 5 most severe of ${result.issues.length}. Severity counts across ALL findings: ` +
|
|
107
|
-
Object.entries(result.issues.reduce((m, i) => {
|
|
108
|
-
const k = String(i.severity);
|
|
109
|
-
m[k] = (m[k] ?? 0) + 1;
|
|
110
|
-
return m;
|
|
111
|
-
}, {})).map(([k, v]) => `${k} ${v}`).join(", ") + ".",
|
|
112
|
-
}
|
|
113
|
-
: {}),
|
|
114
|
-
};
|
|
115
|
-
})(),
|
|
116
|
-
topRecommendations: result.recommendations.slice(0, 5),
|
|
90
|
+
// Worst-first, paged, and the cut is reported (pageAgentReadyFindings).
|
|
91
|
+
// Worst-first since 2026-08-01, when a critical placed sixth in
|
|
92
|
+
// detection order was dropped for five lows; paged since 2026-10-09,
|
|
93
|
+
// when everything past the fifth finding was unreachable.
|
|
94
|
+
...pageAgentReadyFindings(result, { limit, offset }),
|
|
117
95
|
duration: result.duration,
|
|
118
96
|
};
|
|
119
97
|
// Auto-save handled by tier-gate wrapper
|
|
@@ -1246,8 +1224,8 @@ export function registerEmpathyAuditTool(server) {
|
|
|
1246
1224
|
// the numbers on the map read 1,2,3 down the list instead of
|
|
1247
1225
|
// whatever order the analyzer happened to emit.
|
|
1248
1226
|
const rank = (b) => {
|
|
1249
|
-
const { weight } = barrierWeightFor(testedPersona, String(b.type ?? ""), b.wcagCriteria);
|
|
1250
|
-
const { severity } = weightedSeverity(String(b.severity ?? "minor"), weight);
|
|
1227
|
+
const { weight } = barrierWeightFor(testedPersona, String(b.type ?? ""), b.wcagCriteria, b.weightKey);
|
|
1228
|
+
const { severity } = weightedSeverity(String(b.severity ?? "minor"), weight, { aboveAuditLevel: b.aboveAuditLevel });
|
|
1251
1229
|
const order = {
|
|
1252
1230
|
critical: 0, blocker: 0, high: 1, major: 1, serious: 1,
|
|
1253
1231
|
medium: 2, moderate: 2, low: 3, minor: 3, info: 4, notice: 4
|
|
@@ -1258,7 +1236,7 @@ export function registerEmpathyAuditTool(server) {
|
|
|
1258
1236
|
})();
|
|
1259
1237
|
// Group keys in list order; a rect's key looked up here gives its
|
|
1260
1238
|
// finding number.
|
|
1261
|
-
const findingKeys = chosenBarriers.map((b) => `${b.type}|${weightKeyFor(String(b.type ?? ""), b.wcagCriteria) ?? ""}`);
|
|
1239
|
+
const findingKeys = chosenBarriers.map((b) => `${b.type}|${weightKeyFor(String(b.type ?? ""), b.wcagCriteria, b.weightKey) ?? ""}`);
|
|
1262
1240
|
// The name that was measured, when it differs from the one asked for.
|
|
1263
1241
|
// "motor-tremor" measured motor-impairment-tremor while testedPersona
|
|
1264
1242
|
// echoed "motor-tremor" and only resultsSummary showed the substitution.
|
|
@@ -1286,7 +1264,7 @@ export function registerEmpathyAuditTool(server) {
|
|
|
1286
1264
|
...(result.outOfViewportBarriersDropped
|
|
1287
1265
|
? {
|
|
1288
1266
|
outOfViewportBarriersDropped: result.outOfViewportBarriersDropped,
|
|
1289
|
-
scopeFilterNote: `${result.outOfViewportBarriersDropped} barrier(s) outside the viewport were excluded from scoring. Use scope='full_page' to include them.`,
|
|
1267
|
+
scopeFilterNote: `${result.outOfViewportBarriersDropped} barrier(s) outside the viewport or hidden (zero size, e.g. display:none) were excluded from scoring. Use scope='full_page' to include them.`,
|
|
1290
1268
|
}
|
|
1291
1269
|
: {}),
|
|
1292
1270
|
resultsSummary: result.results.map((r) => {
|
|
@@ -1329,8 +1307,17 @@ export function registerEmpathyAuditTool(server) {
|
|
|
1329
1307
|
} : undefined,
|
|
1330
1308
|
barrierTypeCount: uniqueTypes.size,
|
|
1331
1309
|
barrierTypes: Array.from(uniqueTypes),
|
|
1332
|
-
|
|
1310
|
+
// Barrier RECORDS and affected ELEMENTS are different numbers and
|
|
1311
|
+
// both ship. affectedElements was barriers.length, so it could not
|
|
1312
|
+
// be reconciled with topBarriers' per-entry counts. It is now the
|
|
1313
|
+
// same sum topBarriers is built from: for a one-persona audit,
|
|
1314
|
+
// sum(topBarriers[].affectedElementCount) +
|
|
1315
|
+
// (topBarriersOmitted?.affectedElements ?? 0). (B7, 2026-10-09)
|
|
1316
|
+
barrierCount: r.barriers.length,
|
|
1317
|
+
affectedElements: countAffectedElements(r.barriers),
|
|
1333
1318
|
wcagViolationCount: r.wcagViolations.length,
|
|
1319
|
+
// Above-level findings, listed in `advisories` and never scored.
|
|
1320
|
+
...(r.advisories?.length ? { advisoryCount: r.advisories.length } : {}),
|
|
1334
1321
|
// v18.26.0: Perceptual transport metrics (Wasserstein-based)
|
|
1335
1322
|
perceptualTransport: r.perceptualTransport || undefined,
|
|
1336
1323
|
empathyScoreBarrierOnly: r.empathyScoreBarrierOnly || undefined,
|
|
@@ -1386,8 +1373,8 @@ export function registerEmpathyAuditTool(server) {
|
|
|
1386
1373
|
// more or less violated depending on who reads it -- so both ship
|
|
1387
1374
|
// and the weight that separates them ships with them.
|
|
1388
1375
|
return chosen.map((b, i) => {
|
|
1389
|
-
const { weight, key, defaulted } = barrierWeightFor(testedPersona, String(b.type ?? ""), b.wcagCriteria);
|
|
1390
|
-
const { severity, shifted } = weightedSeverity(String(b.severity ?? "minor"), weight);
|
|
1376
|
+
const { weight, key, defaulted } = barrierWeightFor(testedPersona, String(b.type ?? ""), b.wcagCriteria, b.weightKey);
|
|
1377
|
+
const { severity, shifted } = weightedSeverity(String(b.severity ?? "minor"), weight, { aboveAuditLevel: b.aboveAuditLevel });
|
|
1391
1378
|
return {
|
|
1392
1379
|
...b,
|
|
1393
1380
|
// Same number the overlay draws on the matching boxes.
|
|
@@ -1404,6 +1391,35 @@ export function registerEmpathyAuditTool(server) {
|
|
|
1404
1391
|
};
|
|
1405
1392
|
});
|
|
1406
1393
|
})(),
|
|
1394
|
+
// Findings above the audited WCAG level. Advisory by policy (Alexa,
|
|
1395
|
+
// 2026-10-09): reported here so nothing is hidden, never scored,
|
|
1396
|
+
// never escalated by persona weight, never "critical". (B6)
|
|
1397
|
+
...(result.advisories?.length
|
|
1398
|
+
? {
|
|
1399
|
+
advisories: result.advisories.map((a) => {
|
|
1400
|
+
const { weight, key } = barrierWeightFor(testedPersona, String(a.type ?? ""), a.wcagCriteria, a.weightKey);
|
|
1401
|
+
return {
|
|
1402
|
+
...a,
|
|
1403
|
+
severityForPersona: weightedSeverity(String(a.severity ?? "minor"), weight, { aboveAuditLevel: true }).severity,
|
|
1404
|
+
personaWeightKey: key,
|
|
1405
|
+
};
|
|
1406
|
+
}),
|
|
1407
|
+
advisoriesNote: `Findings whose WCAG level is above the audited level (${wcagLevel}). Advisory: reported for information, not scored, not counted in affectedElements, never escalated by persona weight. Re-run with wcagLevel set to their level to score them.`,
|
|
1408
|
+
}
|
|
1409
|
+
: {}),
|
|
1410
|
+
// Groups not shown in topBarriers, so the shown entries plus this
|
|
1411
|
+
// add up to resultsSummary.affectedElements. Dropped groups used to
|
|
1412
|
+
// vanish from the sum without a trace. (B7)
|
|
1413
|
+
...(() => {
|
|
1414
|
+
const shown = new Set(chosenBarriers);
|
|
1415
|
+
const omitted = result.topBarriers.filter((b) => !shown.has(b));
|
|
1416
|
+
return omitted.length > 0
|
|
1417
|
+
? { topBarriersOmitted: {
|
|
1418
|
+
groups: omitted.length,
|
|
1419
|
+
affectedElements: omitted.reduce((n, b) => n + (b.affectedElementCount ?? 0), 0),
|
|
1420
|
+
} }
|
|
1421
|
+
: {};
|
|
1422
|
+
})(),
|
|
1407
1423
|
topBarriersNote: "Ordered worst-first by severityForPersona — the persona-weighted grade — so an entry can sit above a higher WCAG severity when this persona is more susceptible to it. Any entries after the fifth are included because they carry a WCAG criterion the top five do not.",
|
|
1408
1424
|
// Asserting full coverage was itself a defect: the claim held only
|
|
1409
1425
|
// when a barrier existed for every criterion, and said nothing when
|
|
@@ -1466,23 +1482,63 @@ export function registerEmpathyAuditTool(server) {
|
|
|
1466
1482
|
// is stated. Reported rather than papered over, the same way
|
|
1467
1483
|
// cognitiveLoadReadings reports its disagreement.
|
|
1468
1484
|
barrierRectCoverage: (() => {
|
|
1469
|
-
//
|
|
1470
|
-
//
|
|
1471
|
-
//
|
|
1472
|
-
//
|
|
1473
|
-
//
|
|
1474
|
-
//
|
|
1475
|
-
//
|
|
1476
|
-
//
|
|
1477
|
-
//
|
|
1478
|
-
|
|
1479
|
-
|
|
1485
|
+
// Per BARRIER, with the reason each undrawn one has no box.
|
|
1486
|
+
//
|
|
1487
|
+
// This subtracted drawn rects from affected elements and gave
|
|
1488
|
+
// one hardcoded reason, "typically below the fold, hidden, or an
|
|
1489
|
+
// unresolvable selector". In a viewport audit that reason is
|
|
1490
|
+
// wrong by construction: located barriers below the fold were
|
|
1491
|
+
// already dropped (outOfViewportBarriersDropped), so every
|
|
1492
|
+
// undrawn one was a barrier that never had a rect at all. And
|
|
1493
|
+
// elements minus boxes is not a count of anything a reader can
|
|
1494
|
+
// list. (B7, 2026-10-09)
|
|
1495
|
+
//
|
|
1496
|
+
// Zero-area rects draw nothing and are undrawn: an element that
|
|
1497
|
+
// resolved to {0,0,0,0} (display:none, a collapsed menu item)
|
|
1498
|
+
// is undrawn in every sense that matters.
|
|
1499
|
+
const origin = scope === "full_page"
|
|
1500
|
+
? { x: 0, y: 0 }
|
|
1501
|
+
: (r.captureScroll ?? { x: 0, y: 0 });
|
|
1502
|
+
const size = r.screenshotSize
|
|
1503
|
+
?? { width: r.viewportSize?.width ?? Infinity,
|
|
1504
|
+
height: scope === "full_page" ? (r.documentHeight ?? Infinity) : (r.viewportSize?.height ?? Infinity) };
|
|
1505
|
+
const undrawnBarriers = [];
|
|
1506
|
+
let drawn = 0;
|
|
1507
|
+
for (const b of (r.barriers ?? [])) {
|
|
1508
|
+
let reason = null;
|
|
1509
|
+
if (!b.rect)
|
|
1510
|
+
reason = b.pageLevel ? "pageLevel" : "notLocated";
|
|
1511
|
+
else if (!(b.rect.width > 0 && b.rect.height > 0))
|
|
1512
|
+
reason = "zeroArea";
|
|
1513
|
+
else {
|
|
1514
|
+
const ix = b.rect.x - origin.x;
|
|
1515
|
+
const iy = b.rect.y - origin.y;
|
|
1516
|
+
if (iy + b.rect.height < 0 || ix + b.rect.width < 0 || iy > size.height || ix > size.width) {
|
|
1517
|
+
reason = "outsideCapture";
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
if (reason === null) {
|
|
1521
|
+
drawn++;
|
|
1522
|
+
continue;
|
|
1523
|
+
}
|
|
1524
|
+
undrawnBarriers.push({ type: b.type, element: b.element, reason,
|
|
1525
|
+
affectedElementCount: barrierElementCount(b) });
|
|
1526
|
+
}
|
|
1527
|
+
const legend = {
|
|
1528
|
+
pageLevel: "A finding about the page as a whole, or a page-wide aggregate (its elements are in members), with no single location to outline.",
|
|
1529
|
+
zeroArea: "The element resolved to a zero-size box at capture time (display:none, collapsed), so there is nothing to outline.",
|
|
1530
|
+
outsideCapture: "The element's box lies outside the captured screenshot.",
|
|
1531
|
+
notLocated: "The detector recorded no location for this barrier. A defect in the detector, not a property of the page.",
|
|
1532
|
+
};
|
|
1533
|
+
const present = Array.from(new Set(undrawnBarriers.map((u) => u.reason)));
|
|
1480
1534
|
return {
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1535
|
+
barriers: (r.barriers ?? []).length,
|
|
1536
|
+
drawn,
|
|
1537
|
+
affectedElements: countAffectedElements(r.barriers ?? []),
|
|
1538
|
+
...(undrawnBarriers.length > 0
|
|
1539
|
+
? { undrawn: undrawnBarriers.length,
|
|
1540
|
+
undrawnBarriers,
|
|
1541
|
+
undrawnReasons: Object.fromEntries(present.map((p) => [p, legend[p]])) }
|
|
1486
1542
|
: {}),
|
|
1487
1543
|
};
|
|
1488
1544
|
})(),
|
|
@@ -1552,13 +1608,13 @@ export function registerEmpathyAuditTool(server) {
|
|
|
1552
1608
|
// weighted audit one barrier was orange on the map and red in
|
|
1553
1609
|
// the list -- two scales, no label saying which was which.
|
|
1554
1610
|
// (2026-07-31)
|
|
1555
|
-
const { weight } = barrierWeightFor(r.persona, String(b.type ?? ""), b.wcagCriteria);
|
|
1556
|
-
const { severity: weighted } = weightedSeverity(String(b.severity ?? "minor"), weight);
|
|
1611
|
+
const { weight } = barrierWeightFor(r.persona, String(b.type ?? ""), b.wcagCriteria, b.weightKey);
|
|
1612
|
+
const { severity: weighted } = weightedSeverity(String(b.severity ?? "minor"), weight, { aboveAuditLevel: b.aboveAuditLevel });
|
|
1557
1613
|
// Which findings-list entry this box belongs to. Boxes are
|
|
1558
1614
|
// per-element and findings are grouped by type+criterion, so
|
|
1559
1615
|
// ten boxes can map to five findings; without the link there is
|
|
1560
1616
|
// no way to tell which box the list is talking about.
|
|
1561
|
-
const gk = `${b.type}|${weightKeyFor(String(b.type ?? ""), b.wcagCriteria) ?? ""}`;
|
|
1617
|
+
const gk = `${b.type}|${weightKeyFor(String(b.type ?? ""), b.wcagCriteria, b.weightKey) ?? ""}`;
|
|
1562
1618
|
const findingIndex = findingKeys.indexOf(gk);
|
|
1563
1619
|
return {
|
|
1564
1620
|
type: b.type, severity: b.severity, element: b.element,
|