ultra11y 5.25.0 → 5.26.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.opencode/plugins/ultra11y.js +1 -1
- package/dist/playwright.mjs +1 -1
- package/package.json +1 -1
- package/scripts/ultra11y.mjs +17 -16
- package/skills/review-a11y/SKILL.md +1 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +17 -16
- package/skills/ultra11y/SKILL.md +1 -1
- package/skills/ultra11y/scripts/ultra11y.mjs +17 -16
- package/src/adjudicate-subjects.ts +24 -0
- package/src/adjudicate.ts +2 -2
- package/src/audit.ts +1 -1
- package/src/dev.ts +1 -1
- package/src/glob.ts +2 -2
- package/src/graph/imports.ts +1 -1
- package/src/integrations/cypress-plugin.ts +1 -1
- package/src/integrations/cypress.ts +2 -2
- package/src/integrations/playwright.ts +5 -5
- package/src/mcp/handlers.ts +1 -1
- package/src/orchestrate-templates.ts +4 -1
- package/src/orchestrate.ts +1 -1
- package/src/rules/images.ts +1 -1
- package/src/rules/tables.ts +2 -2
- package/src/scan.ts +1 -1
- package/src/standards/pack-rules.ts +1 -1
- package/src/types.ts +1 -1
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"name": "ultra11y",
|
|
11
11
|
"source": "./",
|
|
12
12
|
"description": "Automatic accessibility review of the code under change before every commit, push and pull request, plus full WCAG 2.2 AA / RGAA audits and conformance deliverables on demand.",
|
|
13
|
-
"version": "5.
|
|
13
|
+
"version": "5.26.0",
|
|
14
14
|
"author": { "name": "maxgfr" },
|
|
15
15
|
"homepage": "https://github.com/maxgfr/ultra11y",
|
|
16
16
|
"repository": "https://github.com/maxgfr/ultra11y",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultra11y",
|
|
3
3
|
"description": "Accessibility for WCAG 2.2 AA and country standards (RGAA…): an automatic review of the code under change before every commit, push and pull request, plus full audits and conformance deliverables on demand.",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.26.0",
|
|
5
5
|
"author": { "name": "maxgfr" },
|
|
6
6
|
"homepage": "https://github.com/maxgfr/ultra11y",
|
|
7
7
|
"repository": "https://github.com/maxgfr/ultra11y",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultra11y",
|
|
3
3
|
"description": "Accessibility for WCAG 2.2 AA and country standards (RGAA…): an automatic review of the code under change before every commit, push and pull request, plus full audits and conformance deliverables on demand.",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.26.0",
|
|
5
5
|
"author": { "name": "maxgfr", "url": "https://github.com/maxgfr" },
|
|
6
6
|
"homepage": "https://github.com/maxgfr/ultra11y",
|
|
7
7
|
"repository": "https://github.com/maxgfr/ultra11y",
|
|
@@ -27,7 +27,7 @@ import { fileURLToPath } from "node:url";
|
|
|
27
27
|
// Ownership marker: `ultra11y install --opencode` refuses to overwrite a plugin file that
|
|
28
28
|
// does not carry it, so a user's own ultra11y.js is never clobbered.
|
|
29
29
|
const ULTRA11Y_OPENCODE_PLUGIN = "ULTRA11Y_OPENCODE_PLUGIN";
|
|
30
|
-
const ULTRA11Y_PLUGIN_VERSION = "5.
|
|
30
|
+
const ULTRA11Y_PLUGIN_VERSION = "5.26.0";
|
|
31
31
|
|
|
32
32
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
33
33
|
|
package/dist/playwright.mjs
CHANGED
|
@@ -720,7 +720,7 @@ var test = (() => {
|
|
|
720
720
|
const base = load(from).test;
|
|
721
721
|
if (!base?.extend) continue;
|
|
722
722
|
return base.extend({
|
|
723
|
-
//
|
|
723
|
+
// `any` for the same reason.
|
|
724
724
|
ultra11y: async ({ page }, use) => {
|
|
725
725
|
await use((opts) => checkA11y(page, opts));
|
|
726
726
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultra11y",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.26.0",
|
|
4
4
|
"description": "Audit HTML/CSS/JSX against WCAG 2.2 AA and produce a dated compliance report, or author/review accessible markup (native-HTML-first). A skills.sh agent skill: an install-free static engine (bundled, no npm install) plus the agent's judgment, with check/verify gates against hallucinated non-conformities, and pluggable in-repo standards packs (RGAA France first; Section 508 & EN 301 549 next).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@10.33.0",
|
package/scripts/ultra11y.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { join as join51, relative as relative5, resolve as resolve16, sep as sep
|
|
|
6
6
|
import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
|
|
7
7
|
|
|
8
8
|
// src/types.ts
|
|
9
|
-
var VERSION = "5.
|
|
9
|
+
var VERSION = "5.26.0";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
var INAPPLICABLE_STATUS = "C";
|
|
12
12
|
|
|
@@ -36908,7 +36908,7 @@ function makeFilter(opts = {}) {
|
|
|
36908
36908
|
const rel2 = toPosix(file);
|
|
36909
36909
|
if (include && !include(rel2)) return false;
|
|
36910
36910
|
if (exclude?.(rel2)) return false;
|
|
36911
|
-
if (!opts.noDefaultExcludes && isTestArtifact(rel2) && !
|
|
36911
|
+
if (!opts.noDefaultExcludes && isTestArtifact(rel2) && !include?.(rel2)) return false;
|
|
36912
36912
|
return true;
|
|
36913
36913
|
};
|
|
36914
36914
|
}
|
|
@@ -36974,7 +36974,7 @@ function expandInputs(inputs, opts = {}) {
|
|
|
36974
36974
|
const before = list.length;
|
|
36975
36975
|
list = list.filter((f) => {
|
|
36976
36976
|
const rel2 = toPosix(f);
|
|
36977
|
-
return !isTestArtifact(rel2) || explicit.has(f) || include
|
|
36977
|
+
return !isTestArtifact(rel2) || explicit.has(f) || include?.(rel2);
|
|
36978
36978
|
});
|
|
36979
36979
|
const dropped = before - list.length;
|
|
36980
36980
|
if (dropped) opts.onWarn?.(`ultra11y: skipped ${dropped} test/spec/story file(s) \u2014 pass --no-default-excludes to audit them.`);
|
|
@@ -45436,7 +45436,8 @@ var EXISTENCE_SUBJECTS = /* @__PURE__ */ new Set([
|
|
|
45436
45436
|
"autocomplete",
|
|
45437
45437
|
"errors",
|
|
45438
45438
|
"frames",
|
|
45439
|
-
"downloadDocs"
|
|
45439
|
+
"downloadDocs",
|
|
45440
|
+
"newWindow"
|
|
45440
45441
|
]);
|
|
45441
45442
|
var subjectsForSc = (sc) => SC_SUBJECTS[sc] ?? [];
|
|
45442
45443
|
function subjectsForPackCriterion(standard, id, scs) {
|
|
@@ -46968,7 +46969,7 @@ var tableEmptyDataCell = {
|
|
|
46968
46969
|
return out2;
|
|
46969
46970
|
}
|
|
46970
46971
|
};
|
|
46971
|
-
function ownerTable(el
|
|
46972
|
+
function ownerTable(el) {
|
|
46972
46973
|
return ancestors(el).find((a) => a.tag === "table");
|
|
46973
46974
|
}
|
|
46974
46975
|
var headersAttrDangling = {
|
|
@@ -46981,7 +46982,7 @@ var headersAttrDangling = {
|
|
|
46981
46982
|
if (el.tag !== "td" && el.tag !== "th") continue;
|
|
46982
46983
|
const raw = (attr(el, "headers") ?? "").trim();
|
|
46983
46984
|
if (!raw || raw.includes("{")) continue;
|
|
46984
|
-
const table = ownerTable(el
|
|
46985
|
+
const table = ownerTable(el);
|
|
46985
46986
|
if (!table) continue;
|
|
46986
46987
|
const inTable = new Map(
|
|
46987
46988
|
descendants(table).filter((d) => d.tag === "th" || d.tag === "td").map((d) => [attr(d, "id") ?? "", d])
|
|
@@ -48784,7 +48785,7 @@ function matchNode(el, node, depth) {
|
|
|
48784
48785
|
if (node.has || node.lacks) {
|
|
48785
48786
|
const desc = descendants(el);
|
|
48786
48787
|
if (node.has && !node.has.every((sub) => desc.some((d) => matchNode(d, sub, depth + 1)))) return false;
|
|
48787
|
-
if (node.lacks
|
|
48788
|
+
if (node.lacks?.some((sub) => desc.some((d) => matchNode(d, sub, depth + 1)))) return false;
|
|
48788
48789
|
}
|
|
48789
48790
|
return true;
|
|
48790
48791
|
}
|
|
@@ -49000,7 +49001,7 @@ function extractGraphNode(ast, doc, file, opts = {}) {
|
|
|
49000
49001
|
if (ast) {
|
|
49001
49002
|
for (const stmt of asNodes((asNode(ast.program) ?? ast).body)) {
|
|
49002
49003
|
const vd = stmt.type === "VariableDeclaration" ? stmt : stmt.type === "ExportNamedDeclaration" ? asNode(stmt.declaration) : void 0;
|
|
49003
|
-
if (
|
|
49004
|
+
if (vd?.type !== "VariableDeclaration") continue;
|
|
49004
49005
|
for (const d of asNodes(vd.declarations)) {
|
|
49005
49006
|
const idn = asNode(d.id);
|
|
49006
49007
|
const init2 = asNode(d.init);
|
|
@@ -49353,7 +49354,7 @@ function hasMedia(d) {
|
|
|
49353
49354
|
return d.elements.some((el) => {
|
|
49354
49355
|
if (el.tag === "object" || el.tag === "embed") {
|
|
49355
49356
|
const type = (el.attribs.type ?? "").toLowerCase();
|
|
49356
|
-
return !type || type.startsWith("audio/") || type.startsWith("video/") || type.startsWith("application/")
|
|
49357
|
+
return !type || type.startsWith("audio/") || type.startsWith("video/") || type.startsWith("application/");
|
|
49357
49358
|
}
|
|
49358
49359
|
if (el.tag !== "iframe") return false;
|
|
49359
49360
|
const hay = `${el.attribs.src ?? ""} ${el.attribs.title ?? ""} ${el.attribs.allow ?? ""}`;
|
|
@@ -59219,7 +59220,7 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
59219
59220
|
if (v === null) {
|
|
59220
59221
|
blame(it.criteriaId, `criterion ${it.criteriaId}: unadjudicated (verdict is null)`);
|
|
59221
59222
|
} else if (v === "C" || v === "NA") {
|
|
59222
|
-
if (!it.justification
|
|
59223
|
+
if (!it.justification?.trim()) blame(it.criteriaId, `criterion ${it.criteriaId}: a ${v} verdict requires a justification`);
|
|
59223
59224
|
const cites = (it.citations ?? []).map(readCitation).filter((c2) => c2 !== null);
|
|
59224
59225
|
if (v === "C" && it.evidenceComplete === false) {
|
|
59225
59226
|
blame(
|
|
@@ -59272,7 +59273,7 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
59272
59273
|
);
|
|
59273
59274
|
continue;
|
|
59274
59275
|
}
|
|
59275
|
-
if (!f.normativeRef
|
|
59276
|
+
if (!f.normativeRef?.trim()) {
|
|
59276
59277
|
blame(it.criteriaId, `criterion ${it.criteriaId}: an NC finding requires a normativeRef citing the failed test of the active standard`);
|
|
59277
59278
|
} else if (!normativeRefResolves(f.normativeRef, adj.standard, isCore(adj.standard) ? void 0 : it.criteriaId)) {
|
|
59278
59279
|
blame(
|
|
@@ -60873,7 +60874,7 @@ function mergeSnapshotAudit(base, snap) {
|
|
|
60873
60874
|
for (const c2 of merged.criteria) {
|
|
60874
60875
|
if (!c2.inapplicable) continue;
|
|
60875
60876
|
const measured = snapById.get(c2.id);
|
|
60876
|
-
if (
|
|
60877
|
+
if (measured?.status !== "C" || measured.inapplicable) continue;
|
|
60877
60878
|
c2.status = "C";
|
|
60878
60879
|
delete c2.inapplicable;
|
|
60879
60880
|
if (measured.justification) c2.justification = measured.justification;
|
|
@@ -64916,7 +64917,7 @@ function foldRecordedAdjudication(root, fresh) {
|
|
|
64916
64917
|
for (const c2 of prior.criteria) {
|
|
64917
64918
|
if (c2.decidedBy !== "agent") continue;
|
|
64918
64919
|
const target = byId2.get(c2.id);
|
|
64919
|
-
if (
|
|
64920
|
+
if (target?.status !== "manual") continue;
|
|
64920
64921
|
target.status = c2.status;
|
|
64921
64922
|
target.decidedBy = "agent";
|
|
64922
64923
|
if (c2.justification) target.justification = c2.justification;
|
|
@@ -65541,7 +65542,7 @@ For EACH entry:
|
|
|
65541
65542
|
Then fold: \`ultra11y verify --apply ${join47(runAbs, "VERIFY.todo.json")} --report <the report .md>\`. Without a shell, leave the fold to whoever gave you the file.
|
|
65542
65543
|
`;
|
|
65543
65544
|
}
|
|
65544
|
-
function agentContracts(runAbs,
|
|
65545
|
+
function agentContracts(runAbs, opts = {}) {
|
|
65545
65546
|
const footer = ONE_WRITER_FOOTER.replaceAll("<RUN>", runAbs);
|
|
65546
65547
|
if (opts.eco) return { adjudicator: ecoAdjudicatorContract(runAbs), refuter: ecoRefuterContract(runAbs) };
|
|
65547
65548
|
return {
|
|
@@ -65724,7 +65725,7 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
|
65724
65725
|
mkdirSync17(agentsDir, { recursive: true });
|
|
65725
65726
|
const written = [];
|
|
65726
65727
|
const notices = [];
|
|
65727
|
-
for (const [name2, content] of Object.entries(agentContracts(run2,
|
|
65728
|
+
for (const [name2, content] of Object.entries(agentContracts(run2, { eco: opts.eco === true }))) {
|
|
65728
65729
|
const p = join48(agentsDir, `${name2}.md`);
|
|
65729
65730
|
writeFileSync19(p, content);
|
|
65730
65731
|
written.push(p);
|
|
@@ -66230,7 +66231,7 @@ function bool(v) {
|
|
|
66230
66231
|
}
|
|
66231
66232
|
function strArray2(v) {
|
|
66232
66233
|
const a = Array.isArray(v) && v.every((x) => typeof x === "string") ? v : void 0;
|
|
66233
|
-
return a
|
|
66234
|
+
return a?.length ? a : void 0;
|
|
66234
66235
|
}
|
|
66235
66236
|
function positive(v, key) {
|
|
66236
66237
|
const n = num2(v);
|
|
@@ -5,7 +5,7 @@ when_to_use: "Invoke WITHOUT being asked whenever changed front-end code should
|
|
|
5
5
|
allowed-tools: Bash(node ${CLAUDE_SKILL_DIR}/scripts/ultra11y.mjs *), Read, Grep, Glob
|
|
6
6
|
license: MIT
|
|
7
7
|
metadata:
|
|
8
|
-
version: 5.
|
|
8
|
+
version: 5.26.0
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# review-a11y — accessibility review of changed code
|
|
@@ -6,7 +6,7 @@ import { join as join51, relative as relative5, resolve as resolve16, sep as sep
|
|
|
6
6
|
import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
|
|
7
7
|
|
|
8
8
|
// src/types.ts
|
|
9
|
-
var VERSION = "5.
|
|
9
|
+
var VERSION = "5.26.0";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
var INAPPLICABLE_STATUS = "C";
|
|
12
12
|
|
|
@@ -36908,7 +36908,7 @@ function makeFilter(opts = {}) {
|
|
|
36908
36908
|
const rel2 = toPosix(file);
|
|
36909
36909
|
if (include && !include(rel2)) return false;
|
|
36910
36910
|
if (exclude?.(rel2)) return false;
|
|
36911
|
-
if (!opts.noDefaultExcludes && isTestArtifact(rel2) && !
|
|
36911
|
+
if (!opts.noDefaultExcludes && isTestArtifact(rel2) && !include?.(rel2)) return false;
|
|
36912
36912
|
return true;
|
|
36913
36913
|
};
|
|
36914
36914
|
}
|
|
@@ -36974,7 +36974,7 @@ function expandInputs(inputs, opts = {}) {
|
|
|
36974
36974
|
const before = list.length;
|
|
36975
36975
|
list = list.filter((f) => {
|
|
36976
36976
|
const rel2 = toPosix(f);
|
|
36977
|
-
return !isTestArtifact(rel2) || explicit.has(f) || include
|
|
36977
|
+
return !isTestArtifact(rel2) || explicit.has(f) || include?.(rel2);
|
|
36978
36978
|
});
|
|
36979
36979
|
const dropped = before - list.length;
|
|
36980
36980
|
if (dropped) opts.onWarn?.(`ultra11y: skipped ${dropped} test/spec/story file(s) \u2014 pass --no-default-excludes to audit them.`);
|
|
@@ -45436,7 +45436,8 @@ var EXISTENCE_SUBJECTS = /* @__PURE__ */ new Set([
|
|
|
45436
45436
|
"autocomplete",
|
|
45437
45437
|
"errors",
|
|
45438
45438
|
"frames",
|
|
45439
|
-
"downloadDocs"
|
|
45439
|
+
"downloadDocs",
|
|
45440
|
+
"newWindow"
|
|
45440
45441
|
]);
|
|
45441
45442
|
var subjectsForSc = (sc) => SC_SUBJECTS[sc] ?? [];
|
|
45442
45443
|
function subjectsForPackCriterion(standard, id, scs) {
|
|
@@ -46968,7 +46969,7 @@ var tableEmptyDataCell = {
|
|
|
46968
46969
|
return out2;
|
|
46969
46970
|
}
|
|
46970
46971
|
};
|
|
46971
|
-
function ownerTable(el
|
|
46972
|
+
function ownerTable(el) {
|
|
46972
46973
|
return ancestors(el).find((a) => a.tag === "table");
|
|
46973
46974
|
}
|
|
46974
46975
|
var headersAttrDangling = {
|
|
@@ -46981,7 +46982,7 @@ var headersAttrDangling = {
|
|
|
46981
46982
|
if (el.tag !== "td" && el.tag !== "th") continue;
|
|
46982
46983
|
const raw = (attr(el, "headers") ?? "").trim();
|
|
46983
46984
|
if (!raw || raw.includes("{")) continue;
|
|
46984
|
-
const table = ownerTable(el
|
|
46985
|
+
const table = ownerTable(el);
|
|
46985
46986
|
if (!table) continue;
|
|
46986
46987
|
const inTable = new Map(
|
|
46987
46988
|
descendants(table).filter((d) => d.tag === "th" || d.tag === "td").map((d) => [attr(d, "id") ?? "", d])
|
|
@@ -48784,7 +48785,7 @@ function matchNode(el, node, depth) {
|
|
|
48784
48785
|
if (node.has || node.lacks) {
|
|
48785
48786
|
const desc = descendants(el);
|
|
48786
48787
|
if (node.has && !node.has.every((sub) => desc.some((d) => matchNode(d, sub, depth + 1)))) return false;
|
|
48787
|
-
if (node.lacks
|
|
48788
|
+
if (node.lacks?.some((sub) => desc.some((d) => matchNode(d, sub, depth + 1)))) return false;
|
|
48788
48789
|
}
|
|
48789
48790
|
return true;
|
|
48790
48791
|
}
|
|
@@ -49000,7 +49001,7 @@ function extractGraphNode(ast, doc, file, opts = {}) {
|
|
|
49000
49001
|
if (ast) {
|
|
49001
49002
|
for (const stmt of asNodes((asNode(ast.program) ?? ast).body)) {
|
|
49002
49003
|
const vd = stmt.type === "VariableDeclaration" ? stmt : stmt.type === "ExportNamedDeclaration" ? asNode(stmt.declaration) : void 0;
|
|
49003
|
-
if (
|
|
49004
|
+
if (vd?.type !== "VariableDeclaration") continue;
|
|
49004
49005
|
for (const d of asNodes(vd.declarations)) {
|
|
49005
49006
|
const idn = asNode(d.id);
|
|
49006
49007
|
const init2 = asNode(d.init);
|
|
@@ -49353,7 +49354,7 @@ function hasMedia(d) {
|
|
|
49353
49354
|
return d.elements.some((el) => {
|
|
49354
49355
|
if (el.tag === "object" || el.tag === "embed") {
|
|
49355
49356
|
const type = (el.attribs.type ?? "").toLowerCase();
|
|
49356
|
-
return !type || type.startsWith("audio/") || type.startsWith("video/") || type.startsWith("application/")
|
|
49357
|
+
return !type || type.startsWith("audio/") || type.startsWith("video/") || type.startsWith("application/");
|
|
49357
49358
|
}
|
|
49358
49359
|
if (el.tag !== "iframe") return false;
|
|
49359
49360
|
const hay = `${el.attribs.src ?? ""} ${el.attribs.title ?? ""} ${el.attribs.allow ?? ""}`;
|
|
@@ -59219,7 +59220,7 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
59219
59220
|
if (v === null) {
|
|
59220
59221
|
blame(it.criteriaId, `criterion ${it.criteriaId}: unadjudicated (verdict is null)`);
|
|
59221
59222
|
} else if (v === "C" || v === "NA") {
|
|
59222
|
-
if (!it.justification
|
|
59223
|
+
if (!it.justification?.trim()) blame(it.criteriaId, `criterion ${it.criteriaId}: a ${v} verdict requires a justification`);
|
|
59223
59224
|
const cites = (it.citations ?? []).map(readCitation).filter((c2) => c2 !== null);
|
|
59224
59225
|
if (v === "C" && it.evidenceComplete === false) {
|
|
59225
59226
|
blame(
|
|
@@ -59272,7 +59273,7 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
59272
59273
|
);
|
|
59273
59274
|
continue;
|
|
59274
59275
|
}
|
|
59275
|
-
if (!f.normativeRef
|
|
59276
|
+
if (!f.normativeRef?.trim()) {
|
|
59276
59277
|
blame(it.criteriaId, `criterion ${it.criteriaId}: an NC finding requires a normativeRef citing the failed test of the active standard`);
|
|
59277
59278
|
} else if (!normativeRefResolves(f.normativeRef, adj.standard, isCore(adj.standard) ? void 0 : it.criteriaId)) {
|
|
59278
59279
|
blame(
|
|
@@ -60873,7 +60874,7 @@ function mergeSnapshotAudit(base, snap) {
|
|
|
60873
60874
|
for (const c2 of merged.criteria) {
|
|
60874
60875
|
if (!c2.inapplicable) continue;
|
|
60875
60876
|
const measured = snapById.get(c2.id);
|
|
60876
|
-
if (
|
|
60877
|
+
if (measured?.status !== "C" || measured.inapplicable) continue;
|
|
60877
60878
|
c2.status = "C";
|
|
60878
60879
|
delete c2.inapplicable;
|
|
60879
60880
|
if (measured.justification) c2.justification = measured.justification;
|
|
@@ -64916,7 +64917,7 @@ function foldRecordedAdjudication(root, fresh) {
|
|
|
64916
64917
|
for (const c2 of prior.criteria) {
|
|
64917
64918
|
if (c2.decidedBy !== "agent") continue;
|
|
64918
64919
|
const target = byId2.get(c2.id);
|
|
64919
|
-
if (
|
|
64920
|
+
if (target?.status !== "manual") continue;
|
|
64920
64921
|
target.status = c2.status;
|
|
64921
64922
|
target.decidedBy = "agent";
|
|
64922
64923
|
if (c2.justification) target.justification = c2.justification;
|
|
@@ -65541,7 +65542,7 @@ For EACH entry:
|
|
|
65541
65542
|
Then fold: \`ultra11y verify --apply ${join47(runAbs, "VERIFY.todo.json")} --report <the report .md>\`. Without a shell, leave the fold to whoever gave you the file.
|
|
65542
65543
|
`;
|
|
65543
65544
|
}
|
|
65544
|
-
function agentContracts(runAbs,
|
|
65545
|
+
function agentContracts(runAbs, opts = {}) {
|
|
65545
65546
|
const footer = ONE_WRITER_FOOTER.replaceAll("<RUN>", runAbs);
|
|
65546
65547
|
if (opts.eco) return { adjudicator: ecoAdjudicatorContract(runAbs), refuter: ecoRefuterContract(runAbs) };
|
|
65547
65548
|
return {
|
|
@@ -65724,7 +65725,7 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
|
65724
65725
|
mkdirSync17(agentsDir, { recursive: true });
|
|
65725
65726
|
const written = [];
|
|
65726
65727
|
const notices = [];
|
|
65727
|
-
for (const [name2, content] of Object.entries(agentContracts(run2,
|
|
65728
|
+
for (const [name2, content] of Object.entries(agentContracts(run2, { eco: opts.eco === true }))) {
|
|
65728
65729
|
const p = join48(agentsDir, `${name2}.md`);
|
|
65729
65730
|
writeFileSync19(p, content);
|
|
65730
65731
|
written.push(p);
|
|
@@ -66230,7 +66231,7 @@ function bool(v) {
|
|
|
66230
66231
|
}
|
|
66231
66232
|
function strArray2(v) {
|
|
66232
66233
|
const a = Array.isArray(v) && v.every((x) => typeof x === "string") ? v : void 0;
|
|
66233
|
-
return a
|
|
66234
|
+
return a?.length ? a : void 0;
|
|
66234
66235
|
}
|
|
66235
66236
|
function positive(v, key) {
|
|
66236
66237
|
const n = num2(v);
|
package/skills/ultra11y/SKILL.md
CHANGED
|
@@ -4,7 +4,7 @@ description: "Use to AUDIT existing HTML/CSS/JSX against WCAG 2.2 AA accessibili
|
|
|
4
4
|
when_to_use: "Invoke when the user asks for an accessibility AUDIT or a formal deliverable: audit a repo, site or page against WCAG 2.2 AA or a country standard (RGAA, Section 508, EN 301 549); produce a dated conformance report, a criterion grid, a PRD backlog, or tickets (GitHub/GitLab/Jira); author accessible markup from scratch; wire the repo gate (init --hook/--ci) or a rendered-DOM capture pipeline. For a review of the code UNDER CHANGE (diff, branch, PR), use the `review-a11y` skill instead."
|
|
5
5
|
license: MIT
|
|
6
6
|
metadata:
|
|
7
|
-
version: 5.
|
|
7
|
+
version: 5.26.0
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# ultra11y — audit WCAG 2.2 AA and write accessible markup
|
|
@@ -6,7 +6,7 @@ import { join as join51, relative as relative5, resolve as resolve16, sep as sep
|
|
|
6
6
|
import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
|
|
7
7
|
|
|
8
8
|
// src/types.ts
|
|
9
|
-
var VERSION = "5.
|
|
9
|
+
var VERSION = "5.26.0";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
var INAPPLICABLE_STATUS = "C";
|
|
12
12
|
|
|
@@ -36908,7 +36908,7 @@ function makeFilter(opts = {}) {
|
|
|
36908
36908
|
const rel2 = toPosix(file);
|
|
36909
36909
|
if (include && !include(rel2)) return false;
|
|
36910
36910
|
if (exclude?.(rel2)) return false;
|
|
36911
|
-
if (!opts.noDefaultExcludes && isTestArtifact(rel2) && !
|
|
36911
|
+
if (!opts.noDefaultExcludes && isTestArtifact(rel2) && !include?.(rel2)) return false;
|
|
36912
36912
|
return true;
|
|
36913
36913
|
};
|
|
36914
36914
|
}
|
|
@@ -36974,7 +36974,7 @@ function expandInputs(inputs, opts = {}) {
|
|
|
36974
36974
|
const before = list.length;
|
|
36975
36975
|
list = list.filter((f) => {
|
|
36976
36976
|
const rel2 = toPosix(f);
|
|
36977
|
-
return !isTestArtifact(rel2) || explicit.has(f) || include
|
|
36977
|
+
return !isTestArtifact(rel2) || explicit.has(f) || include?.(rel2);
|
|
36978
36978
|
});
|
|
36979
36979
|
const dropped = before - list.length;
|
|
36980
36980
|
if (dropped) opts.onWarn?.(`ultra11y: skipped ${dropped} test/spec/story file(s) \u2014 pass --no-default-excludes to audit them.`);
|
|
@@ -45436,7 +45436,8 @@ var EXISTENCE_SUBJECTS = /* @__PURE__ */ new Set([
|
|
|
45436
45436
|
"autocomplete",
|
|
45437
45437
|
"errors",
|
|
45438
45438
|
"frames",
|
|
45439
|
-
"downloadDocs"
|
|
45439
|
+
"downloadDocs",
|
|
45440
|
+
"newWindow"
|
|
45440
45441
|
]);
|
|
45441
45442
|
var subjectsForSc = (sc) => SC_SUBJECTS[sc] ?? [];
|
|
45442
45443
|
function subjectsForPackCriterion(standard, id, scs) {
|
|
@@ -46968,7 +46969,7 @@ var tableEmptyDataCell = {
|
|
|
46968
46969
|
return out2;
|
|
46969
46970
|
}
|
|
46970
46971
|
};
|
|
46971
|
-
function ownerTable(el
|
|
46972
|
+
function ownerTable(el) {
|
|
46972
46973
|
return ancestors(el).find((a) => a.tag === "table");
|
|
46973
46974
|
}
|
|
46974
46975
|
var headersAttrDangling = {
|
|
@@ -46981,7 +46982,7 @@ var headersAttrDangling = {
|
|
|
46981
46982
|
if (el.tag !== "td" && el.tag !== "th") continue;
|
|
46982
46983
|
const raw = (attr(el, "headers") ?? "").trim();
|
|
46983
46984
|
if (!raw || raw.includes("{")) continue;
|
|
46984
|
-
const table = ownerTable(el
|
|
46985
|
+
const table = ownerTable(el);
|
|
46985
46986
|
if (!table) continue;
|
|
46986
46987
|
const inTable = new Map(
|
|
46987
46988
|
descendants(table).filter((d) => d.tag === "th" || d.tag === "td").map((d) => [attr(d, "id") ?? "", d])
|
|
@@ -48784,7 +48785,7 @@ function matchNode(el, node, depth) {
|
|
|
48784
48785
|
if (node.has || node.lacks) {
|
|
48785
48786
|
const desc = descendants(el);
|
|
48786
48787
|
if (node.has && !node.has.every((sub) => desc.some((d) => matchNode(d, sub, depth + 1)))) return false;
|
|
48787
|
-
if (node.lacks
|
|
48788
|
+
if (node.lacks?.some((sub) => desc.some((d) => matchNode(d, sub, depth + 1)))) return false;
|
|
48788
48789
|
}
|
|
48789
48790
|
return true;
|
|
48790
48791
|
}
|
|
@@ -49000,7 +49001,7 @@ function extractGraphNode(ast, doc, file, opts = {}) {
|
|
|
49000
49001
|
if (ast) {
|
|
49001
49002
|
for (const stmt of asNodes((asNode(ast.program) ?? ast).body)) {
|
|
49002
49003
|
const vd = stmt.type === "VariableDeclaration" ? stmt : stmt.type === "ExportNamedDeclaration" ? asNode(stmt.declaration) : void 0;
|
|
49003
|
-
if (
|
|
49004
|
+
if (vd?.type !== "VariableDeclaration") continue;
|
|
49004
49005
|
for (const d of asNodes(vd.declarations)) {
|
|
49005
49006
|
const idn = asNode(d.id);
|
|
49006
49007
|
const init2 = asNode(d.init);
|
|
@@ -49353,7 +49354,7 @@ function hasMedia(d) {
|
|
|
49353
49354
|
return d.elements.some((el) => {
|
|
49354
49355
|
if (el.tag === "object" || el.tag === "embed") {
|
|
49355
49356
|
const type = (el.attribs.type ?? "").toLowerCase();
|
|
49356
|
-
return !type || type.startsWith("audio/") || type.startsWith("video/") || type.startsWith("application/")
|
|
49357
|
+
return !type || type.startsWith("audio/") || type.startsWith("video/") || type.startsWith("application/");
|
|
49357
49358
|
}
|
|
49358
49359
|
if (el.tag !== "iframe") return false;
|
|
49359
49360
|
const hay = `${el.attribs.src ?? ""} ${el.attribs.title ?? ""} ${el.attribs.allow ?? ""}`;
|
|
@@ -59219,7 +59220,7 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
59219
59220
|
if (v === null) {
|
|
59220
59221
|
blame(it.criteriaId, `criterion ${it.criteriaId}: unadjudicated (verdict is null)`);
|
|
59221
59222
|
} else if (v === "C" || v === "NA") {
|
|
59222
|
-
if (!it.justification
|
|
59223
|
+
if (!it.justification?.trim()) blame(it.criteriaId, `criterion ${it.criteriaId}: a ${v} verdict requires a justification`);
|
|
59223
59224
|
const cites = (it.citations ?? []).map(readCitation).filter((c2) => c2 !== null);
|
|
59224
59225
|
if (v === "C" && it.evidenceComplete === false) {
|
|
59225
59226
|
blame(
|
|
@@ -59272,7 +59273,7 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
59272
59273
|
);
|
|
59273
59274
|
continue;
|
|
59274
59275
|
}
|
|
59275
|
-
if (!f.normativeRef
|
|
59276
|
+
if (!f.normativeRef?.trim()) {
|
|
59276
59277
|
blame(it.criteriaId, `criterion ${it.criteriaId}: an NC finding requires a normativeRef citing the failed test of the active standard`);
|
|
59277
59278
|
} else if (!normativeRefResolves(f.normativeRef, adj.standard, isCore(adj.standard) ? void 0 : it.criteriaId)) {
|
|
59278
59279
|
blame(
|
|
@@ -60873,7 +60874,7 @@ function mergeSnapshotAudit(base, snap) {
|
|
|
60873
60874
|
for (const c2 of merged.criteria) {
|
|
60874
60875
|
if (!c2.inapplicable) continue;
|
|
60875
60876
|
const measured = snapById.get(c2.id);
|
|
60876
|
-
if (
|
|
60877
|
+
if (measured?.status !== "C" || measured.inapplicable) continue;
|
|
60877
60878
|
c2.status = "C";
|
|
60878
60879
|
delete c2.inapplicable;
|
|
60879
60880
|
if (measured.justification) c2.justification = measured.justification;
|
|
@@ -64916,7 +64917,7 @@ function foldRecordedAdjudication(root, fresh) {
|
|
|
64916
64917
|
for (const c2 of prior.criteria) {
|
|
64917
64918
|
if (c2.decidedBy !== "agent") continue;
|
|
64918
64919
|
const target = byId2.get(c2.id);
|
|
64919
|
-
if (
|
|
64920
|
+
if (target?.status !== "manual") continue;
|
|
64920
64921
|
target.status = c2.status;
|
|
64921
64922
|
target.decidedBy = "agent";
|
|
64922
64923
|
if (c2.justification) target.justification = c2.justification;
|
|
@@ -65541,7 +65542,7 @@ For EACH entry:
|
|
|
65541
65542
|
Then fold: \`ultra11y verify --apply ${join47(runAbs, "VERIFY.todo.json")} --report <the report .md>\`. Without a shell, leave the fold to whoever gave you the file.
|
|
65542
65543
|
`;
|
|
65543
65544
|
}
|
|
65544
|
-
function agentContracts(runAbs,
|
|
65545
|
+
function agentContracts(runAbs, opts = {}) {
|
|
65545
65546
|
const footer = ONE_WRITER_FOOTER.replaceAll("<RUN>", runAbs);
|
|
65546
65547
|
if (opts.eco) return { adjudicator: ecoAdjudicatorContract(runAbs), refuter: ecoRefuterContract(runAbs) };
|
|
65547
65548
|
return {
|
|
@@ -65724,7 +65725,7 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
|
65724
65725
|
mkdirSync17(agentsDir, { recursive: true });
|
|
65725
65726
|
const written = [];
|
|
65726
65727
|
const notices = [];
|
|
65727
|
-
for (const [name2, content] of Object.entries(agentContracts(run2,
|
|
65728
|
+
for (const [name2, content] of Object.entries(agentContracts(run2, { eco: opts.eco === true }))) {
|
|
65728
65729
|
const p = join48(agentsDir, `${name2}.md`);
|
|
65729
65730
|
writeFileSync19(p, content);
|
|
65730
65731
|
written.push(p);
|
|
@@ -66230,7 +66231,7 @@ function bool(v) {
|
|
|
66230
66231
|
}
|
|
66231
66232
|
function strArray2(v) {
|
|
66232
66233
|
const a = Array.isArray(v) && v.every((x) => typeof x === "string") ? v : void 0;
|
|
66233
|
-
return a
|
|
66234
|
+
return a?.length ? a : void 0;
|
|
66234
66235
|
}
|
|
66235
66236
|
function positive(v, key) {
|
|
66236
66237
|
const n = num2(v);
|
|
@@ -1184,6 +1184,29 @@ export const PACK_SUBJECTS: Record<string, Record<string, string[]>> = {
|
|
|
1184
1184
|
// about nothing when there is nothing to download. Its recall is not perfect — a document
|
|
1185
1185
|
// served from an extensionless URL is missed — which is true of every subject here and is why
|
|
1186
1186
|
// absence is only ever read as « nothing of that kind is in scope », never as conformance.
|
|
1187
|
+
//
|
|
1188
|
+
// `newWindow` earns its place the same way, and it is worth spelling out because the criterion
|
|
1189
|
+
// it closes has a single test. RGAA 13.2.1 asks one thing — « vérifier qu'à l'ouverture du
|
|
1190
|
+
// document, aucune nouvelle fenêtre n'est ouverte » — and a browser has exactly three ways to
|
|
1191
|
+
// do it: a `target` that leaves this browsing context, a `<meta http-equiv="refresh">`, and a
|
|
1192
|
+
// literal `window.open(`. Two are markup and the third survives minification, so a page that
|
|
1193
|
+
// FAILS 13.2 cannot harvest nothing. Its recall is imperfect in the same way every subject here
|
|
1194
|
+
// is — `open` reached through an alias is missed — which is why the absence still reads only as
|
|
1195
|
+
// « nothing of that kind is in scope », never as conformance.
|
|
1196
|
+
//
|
|
1197
|
+
// Measured on tests/fixtures/realworld, crawled and scanned, engine v5.25.0: 13.2 was one of
|
|
1198
|
+
// only two criteria still reaching the adjudicator with `evidenceComplete` and a population of
|
|
1199
|
+
// zero. The engine already knew the answer and was billing a model to restate it — on the run
|
|
1200
|
+
// this comment was written from, both were ruled NA on the first pass with no gate refusal,
|
|
1201
|
+
// which is the definition of a paid turn that decided nothing.
|
|
1202
|
+
//
|
|
1203
|
+
// THE OTHER ONE, 7.4, STAYS OUT, and the asymmetry is the whole point of the rule above.
|
|
1204
|
+
// `contextChange` does not enumerate the ways a browser CAN change context, it enumerates the
|
|
1205
|
+
// ways we have seen a script do it — `.submit(`, `location.href`, `router.push`, a handful of
|
|
1206
|
+
// handler attributes. A component that navigates through a router the list does not name
|
|
1207
|
+
// (`history.push`, a custom `navigate()`, `location.reload()`) fails 7.4 and matches none of
|
|
1208
|
+
// them. Its silence is what a failing page looks like, so admitting it would buy a cheap NA at
|
|
1209
|
+
// the price of a hidden non-conformity.
|
|
1187
1210
|
export const EXISTENCE_SUBJECTS: ReadonlySet<string> = new Set([
|
|
1188
1211
|
"images",
|
|
1189
1212
|
"tables",
|
|
@@ -1194,6 +1217,7 @@ export const EXISTENCE_SUBJECTS: ReadonlySet<string> = new Set([
|
|
|
1194
1217
|
"errors",
|
|
1195
1218
|
"frames",
|
|
1196
1219
|
"downloadDocs",
|
|
1220
|
+
"newWindow",
|
|
1197
1221
|
]);
|
|
1198
1222
|
|
|
1199
1223
|
/** The subjects that decide a success criterion. Empty ⇒ the criterion has none declared,
|
package/src/adjudicate.ts
CHANGED
|
@@ -799,7 +799,7 @@ export function applyAdjudication(
|
|
|
799
799
|
if (v === null) {
|
|
800
800
|
blame(it.criteriaId, `criterion ${it.criteriaId}: unadjudicated (verdict is null)`);
|
|
801
801
|
} else if (v === "C" || v === "NA") {
|
|
802
|
-
if (!it.justification
|
|
802
|
+
if (!it.justification?.trim()) blame(it.criteriaId, `criterion ${it.criteriaId}: a ${v} verdict requires a justification`);
|
|
803
803
|
// A clearing verdict is gated exactly like an accusing one. Before this, the only
|
|
804
804
|
// check was "the justification is a non-empty string", so `"x"` cleared a criterion —
|
|
805
805
|
// and a model answering C to everything published a conformance nobody had assessed.
|
|
@@ -942,7 +942,7 @@ export function applyAdjudication(
|
|
|
942
942
|
}
|
|
943
943
|
// FAIL-CLOSED: every NC finding must cite a precise, resolvable test of the active
|
|
944
944
|
// standard. A good practice with no normative test is a recommendation, not an NC.
|
|
945
|
-
if (!f.normativeRef
|
|
945
|
+
if (!f.normativeRef?.trim()) {
|
|
946
946
|
blame(it.criteriaId, `criterion ${it.criteriaId}: an NC finding requires a normativeRef citing the failed test of the active standard`);
|
|
947
947
|
} else if (!normativeRefResolves(f.normativeRef, adj.standard, isCore(adj.standard) ? undefined : it.criteriaId)) {
|
|
948
948
|
blame(
|
package/src/audit.ts
CHANGED
|
@@ -184,7 +184,7 @@ function hasMedia(d: Doc): boolean {
|
|
|
184
184
|
if (el.tag === "object" || el.tag === "embed") {
|
|
185
185
|
const type = (el.attribs.type ?? "").toLowerCase();
|
|
186
186
|
// A declared non-media type rules it out; anything else is uncertain, so it counts.
|
|
187
|
-
return !type || type.startsWith("audio/") || type.startsWith("video/") || type.startsWith("application/")
|
|
187
|
+
return !type || type.startsWith("audio/") || type.startsWith("video/") || type.startsWith("application/");
|
|
188
188
|
}
|
|
189
189
|
if (el.tag !== "iframe") return false;
|
|
190
190
|
const hay = `${el.attribs.src ?? ""} ${el.attribs.title ?? ""} ${el.attribs.allow ?? ""}`;
|
package/src/dev.ts
CHANGED
|
@@ -356,7 +356,7 @@ function foldRecordedAdjudication(root: string, fresh: AuditResult): void {
|
|
|
356
356
|
const target = byId.get(c.id);
|
|
357
357
|
// A criterion the fresh run decided by itself is authoritative: the page moved, and a
|
|
358
358
|
// stale ruling must never override a live measurement.
|
|
359
|
-
if (
|
|
359
|
+
if (target?.status !== "manual") continue;
|
|
360
360
|
target.status = c.status;
|
|
361
361
|
target.decidedBy = "agent";
|
|
362
362
|
if (c.justification) target.justification = c.justification;
|
package/src/glob.ts
CHANGED
|
@@ -89,7 +89,7 @@ export function makeFilter(opts: ExpandOpts = {}): (file: string) => boolean {
|
|
|
89
89
|
if (include && !include(rel)) return false;
|
|
90
90
|
if (exclude?.(rel)) return false;
|
|
91
91
|
// Default test-artifact exclusion (re-admitted by an explicit --include match).
|
|
92
|
-
if (!opts.noDefaultExcludes && isTestArtifact(rel) && !
|
|
92
|
+
if (!opts.noDefaultExcludes && isTestArtifact(rel) && !include?.(rel)) return false;
|
|
93
93
|
return true;
|
|
94
94
|
};
|
|
95
95
|
}
|
|
@@ -197,7 +197,7 @@ export function expandInputs(inputs: string[], opts: ExpandOpts = {}): string[]
|
|
|
197
197
|
const before = list.length;
|
|
198
198
|
list = list.filter((f) => {
|
|
199
199
|
const rel = toPosix(f);
|
|
200
|
-
return !isTestArtifact(rel) || explicit.has(f) ||
|
|
200
|
+
return !isTestArtifact(rel) || explicit.has(f) || include?.(rel);
|
|
201
201
|
});
|
|
202
202
|
const dropped = before - list.length;
|
|
203
203
|
if (dropped) opts.onWarn?.(`ultra11y: skipped ${dropped} test/spec/story file(s) — pass --no-default-excludes to audit them.`);
|
package/src/graph/imports.ts
CHANGED
|
@@ -225,7 +225,7 @@ export function extractGraphNode(ast: AstFile | null, doc: Doc, file: string, op
|
|
|
225
225
|
if (ast) {
|
|
226
226
|
for (const stmt of asNodes((asNode(ast.program) ?? ast).body)) {
|
|
227
227
|
const vd = stmt.type === "VariableDeclaration" ? stmt : stmt.type === "ExportNamedDeclaration" ? asNode(stmt.declaration) : undefined;
|
|
228
|
-
if (
|
|
228
|
+
if (vd?.type !== "VariableDeclaration") continue;
|
|
229
229
|
for (const d of asNodes(vd.declarations)) {
|
|
230
230
|
const idn = asNode(d.id);
|
|
231
231
|
const init = asNode(d.init);
|
|
@@ -22,7 +22,7 @@ interface ScreenshotDetails {
|
|
|
22
22
|
path: string;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
//
|
|
25
|
+
// `any`: Cypress's `on` is not a dependency of this package.
|
|
26
26
|
type On = (event: string, handler: any) => void;
|
|
27
27
|
|
|
28
28
|
export interface TaskPayload extends SnapshotPayload {
|
|
@@ -14,10 +14,10 @@ import { type CheckOptions, buildPayload, slugify, stayedOnPage } from "./payloa
|
|
|
14
14
|
|
|
15
15
|
// Cypress's globals are not a dependency of this package; they exist at run time only.
|
|
16
16
|
declare const Cypress: {
|
|
17
|
-
//
|
|
17
|
+
// `any`: Cypress's own command signature.
|
|
18
18
|
Commands: { add(name: string, fn: (...args: any[]) => any): void };
|
|
19
19
|
};
|
|
20
|
-
//
|
|
20
|
+
// `any` for the same reason.
|
|
21
21
|
declare const cy: any;
|
|
22
22
|
|
|
23
23
|
export interface CypressCheckOptions extends CheckOptions {
|
|
@@ -88,7 +88,7 @@ function fromConfig(): { only?: string[]; limits?: Omit<ProbeTuning, "only"> } {
|
|
|
88
88
|
async function runAxe(page: PlaywrightPage, mode: boolean | "auto" | undefined): Promise<{ ran: true; violations: unknown[] } | undefined> {
|
|
89
89
|
if (mode === false) return undefined;
|
|
90
90
|
const demanded = mode === true;
|
|
91
|
-
//
|
|
91
|
+
// `any`: the builder's shape is axe's, which is not a dependency here.
|
|
92
92
|
let AxeBuilder: any;
|
|
93
93
|
// PROJECT FIRST, then relative to this module — the same order the Playwright fixture above
|
|
94
94
|
// and `scan --runtime local` use, and for the same reason: module-relative alone works for an
|
|
@@ -204,16 +204,16 @@ export async function checkA11y(page: PlaywrightPage, opts: PlaywrightCheckOptio
|
|
|
204
204
|
*
|
|
205
205
|
* Resolution is lazy and guarded either way, so importing this in a Cypress-only repo
|
|
206
206
|
* gives `undefined` rather than a crash. */
|
|
207
|
-
//
|
|
207
|
+
// `any`: the shape is Playwright's, which is not a dependency here.
|
|
208
208
|
export const test: any = (() => {
|
|
209
|
-
//
|
|
209
|
+
// `any` for the same reason.
|
|
210
210
|
const load = (from: string): any => createRequire(from)("@playwright/test");
|
|
211
211
|
for (const from of [`${process.cwd()}/package.json`, import.meta.url]) {
|
|
212
212
|
try {
|
|
213
213
|
const base = load(from).test;
|
|
214
214
|
if (!base?.extend) continue;
|
|
215
215
|
return base.extend({
|
|
216
|
-
//
|
|
216
|
+
// `any` for the same reason.
|
|
217
217
|
ultra11y: async ({ page }: any, use: any) => {
|
|
218
218
|
await use((opts: PlaywrightCheckOptions) => checkA11y(page, opts));
|
|
219
219
|
},
|
|
@@ -241,7 +241,7 @@ export interface SweepOptions {
|
|
|
241
241
|
* `waitForLoadState`, `waitForFunction`, locators — and Playwright is not a dependency of
|
|
242
242
|
* this package, so the narrow structural shape the collector needs would reject the very
|
|
243
243
|
* function this option exists to take. Same reasoning as `test` below. */
|
|
244
|
-
//
|
|
244
|
+
// `any`: the shape is Playwright's, which is not a dependency here.
|
|
245
245
|
settle?: (page: any) => Promise<void> | void;
|
|
246
246
|
/** Passed to every `checkA11y`. `failOn: false` (the default here) records without
|
|
247
247
|
* asserting: the durable output of a sweep is the snapshot, not a red test. */
|
package/src/mcp/handlers.ts
CHANGED
|
@@ -80,7 +80,7 @@ function bool(v: unknown): boolean {
|
|
|
80
80
|
|
|
81
81
|
function strArray(v: unknown): string[] | undefined {
|
|
82
82
|
const a = Array.isArray(v) && v.every((x) => typeof x === "string") ? (v as string[]) : undefined;
|
|
83
|
-
return a
|
|
83
|
+
return a?.length ? a : undefined;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
function positive(v: unknown, key: string): number | undefined {
|
|
@@ -260,7 +260,10 @@ Then fold: \`ultra11y verify --apply ${join(runAbs, "VERIFY.todo.json")} --repor
|
|
|
260
260
|
`;
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
-
|
|
263
|
+
// `engineAbs` is deliberately NOT a parameter: the emitted workflow injects `ENGINE` as a
|
|
264
|
+
// constant and the contract refers to it by name, so a second copy of the path here could only
|
|
265
|
+
// ever disagree with the one the agent is actually handed.
|
|
266
|
+
export function agentContracts(runAbs: string, opts: { eco?: boolean } = {}): Record<string, string> {
|
|
264
267
|
const footer = ONE_WRITER_FOOTER.replaceAll("<RUN>", runAbs);
|
|
265
268
|
if (opts.eco) return { adjudicator: ecoAdjudicatorContract(runAbs), refuter: ecoRefuterContract(runAbs) };
|
|
266
269
|
return {
|
package/src/orchestrate.ts
CHANGED
|
@@ -165,7 +165,7 @@ export function orchestrateRun(runDir: string, engineAbs: string, opts: Orchestr
|
|
|
165
165
|
// exists on the sequential path — least of all in CI, where the adjudicator has no shell and
|
|
166
166
|
// writes a file. Emitting the fan-out text there and then telling an agent to obey it VERBATIM
|
|
167
167
|
// is two contradictory sets of instructions, which is what a small model breaks on.
|
|
168
|
-
for (const [name, content] of Object.entries(agentContracts(run,
|
|
168
|
+
for (const [name, content] of Object.entries(agentContracts(run, { eco: opts.eco === true }))) {
|
|
169
169
|
const p = join(agentsDir, `${name}.md`);
|
|
170
170
|
writeFileSync(p, content);
|
|
171
171
|
written.push(p);
|
package/src/rules/images.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Theme 1 — Images.
|
|
2
2
|
import type { Doc, El } from "../parse/html.js";
|
|
3
|
-
import { attr,
|
|
3
|
+
import { attr, hasBoundAttr, boundAttr, hasDynamicSpread, visibleText, ancestors, descendants } from "../parse/html.js";
|
|
4
4
|
import { accessibleName, isNameExempt } from "../name.js";
|
|
5
5
|
import type { Rule, RuleFinding } from "./rule.js";
|
|
6
6
|
|
package/src/rules/tables.ts
CHANGED
|
@@ -181,7 +181,7 @@ const tableEmptyDataCell: Rule = {
|
|
|
181
181
|
};
|
|
182
182
|
|
|
183
183
|
/** The <table> a cell belongs to (nearest table ancestor), or undefined. */
|
|
184
|
-
function ownerTable(el: El
|
|
184
|
+
function ownerTable(el: El): El | undefined {
|
|
185
185
|
return ancestors(el).find((a) => a.tag === "table");
|
|
186
186
|
}
|
|
187
187
|
|
|
@@ -198,7 +198,7 @@ const headersAttrDangling: Rule = {
|
|
|
198
198
|
if (el.tag !== "td" && el.tag !== "th") continue;
|
|
199
199
|
const raw = (attr(el, "headers") ?? "").trim();
|
|
200
200
|
if (!raw || raw.includes("{")) continue;
|
|
201
|
-
const table = ownerTable(el
|
|
201
|
+
const table = ownerTable(el);
|
|
202
202
|
if (!table) continue;
|
|
203
203
|
const inTable = new Map(
|
|
204
204
|
descendants(table)
|
package/src/scan.ts
CHANGED
|
@@ -852,7 +852,7 @@ export function mergeSnapshotAudit(base: AuditResult, snap: AuditResult): AuditR
|
|
|
852
852
|
const measured = snapById.get(c.id);
|
|
853
853
|
// Only a snapshot that reached `C` WITHOUT the flag has falsified "nothing of that kind
|
|
854
854
|
// here" — that one looked at the rendered page and found the subject.
|
|
855
|
-
if (
|
|
855
|
+
if (measured?.status !== "C" || measured.inapplicable) continue;
|
|
856
856
|
c.status = "C";
|
|
857
857
|
delete c.inapplicable;
|
|
858
858
|
// Keep the claim citable: adopt what the measuring half said, rather than leaving a `C`
|
|
@@ -60,7 +60,7 @@ function matchNode(el: El, node: MatchNode, depth: number): boolean {
|
|
|
60
60
|
const desc = descendants(el);
|
|
61
61
|
if (node.has && !node.has.every((sub) => desc.some((d) => matchNode(d, sub, depth + 1)))) return false;
|
|
62
62
|
// `lacks`: NO listed sub-condition may match ANY descendant.
|
|
63
|
-
if (node.lacks
|
|
63
|
+
if (node.lacks?.some((sub) => desc.some((d) => matchNode(d, sub, depth + 1)))) return false;
|
|
64
64
|
}
|
|
65
65
|
return true;
|
|
66
66
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Single source of truth for shared types + the embedded version. sync-version.mjs
|
|
2
2
|
// rewrites VERSION at release time (kept in lockstep with package.json + SKILL.md).
|
|
3
|
-
export const VERSION = "5.
|
|
3
|
+
export const VERSION = "5.26.0";
|
|
4
4
|
export const SCHEMA_VERSION = 2;
|
|
5
5
|
|
|
6
6
|
// Lang = the UI FRAME's language — the `L` localization tables each module (report,
|