ultra11y 5.9.0 → 5.9.2
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/package.json +1 -1
- package/scripts/ultra11y.mjs +39 -17
- package/skills/review-a11y/SKILL.md +1 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +39 -17
- package/skills/ultra11y/SKILL.md +1 -1
- package/skills/ultra11y/scripts/ultra11y.mjs +39 -17
- package/src/adjudicate-subjects.ts +21 -1
- package/src/adjudicate.ts +66 -18
- 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.9.
|
|
13
|
+
"version": "5.9.2",
|
|
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.9.
|
|
4
|
+
"version": "5.9.2",
|
|
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.9.
|
|
4
|
+
"version": "5.9.2",
|
|
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.9.
|
|
30
|
+
const ULTRA11Y_PLUGIN_VERSION = "5.9.2";
|
|
31
31
|
|
|
32
32
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
33
33
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultra11y",
|
|
3
|
-
"version": "5.9.
|
|
3
|
+
"version": "5.9.2",
|
|
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.
|
|
9
|
+
var VERSION = "5.9.2";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
|
|
12
12
|
// src/audit.ts
|
|
@@ -39652,6 +39652,21 @@ var SUBJECTS = {
|
|
|
39652
39652
|
(e) => h(d, e, `<html lang="${attr(e, "lang") ?? ""}"> \u2014 is it present, valid, and the language of the content?`, `doclang|${attr(e, "lang") ?? ""}`)
|
|
39653
39653
|
)
|
|
39654
39654
|
),
|
|
39655
|
+
// Frames, and the title each one carries. RGAA theme 2 is about nothing else: 2.1 asks
|
|
39656
|
+
// whether every frame HAS a title, 2.2 whether that title is pertinent. Both used to inherit
|
|
39657
|
+
// `aria` from WCAG 4.1.2 and be handed every ARIA attribute in the application — measured on
|
|
39658
|
+
// a real run, the adjudicator answered `undecidable` on both, correctly, because nothing it
|
|
39659
|
+
// had been shown could settle a question about `<iframe title>`.
|
|
39660
|
+
frames: (docs) => docs.flatMap(
|
|
39661
|
+
(d) => elementsByTag(d, "iframe", "frame").map(
|
|
39662
|
+
(e) => h(
|
|
39663
|
+
d,
|
|
39664
|
+
e,
|
|
39665
|
+
`<${e.tag}> title="${attr(e, "title") ?? ""}" name="${attr(e, "name") ?? ""}" src="${(attr(e, "src") ?? "").slice(0, 60)}" \u2014 un titre de cadre est-il pr\xE9sent, et d\xE9crit-il le contenu du cadre ?`,
|
|
39666
|
+
`frame|${attr(e, "title") ?? ""}|${attr(e, "src") ?? ""}`
|
|
39667
|
+
)
|
|
39668
|
+
)
|
|
39669
|
+
),
|
|
39655
39670
|
// The document's title, with the page it belongs to, so "is it pertinent?" is answerable.
|
|
39656
39671
|
docTitle: (docs) => docs.flatMap(
|
|
39657
39672
|
(d) => elementsByTag(d, "title").map((e) => h(d, e, `page="${pageOfDoc(d.file) ?? "source"}" <title>${t(e, 80)}</title>`, `doctitle|${t(e, 80)}`))
|
|
@@ -39880,6 +39895,9 @@ var PACK_SUBJECTS = {
|
|
|
39880
39895
|
// Theme 6 — links.
|
|
39881
39896
|
"6.1": ["links"],
|
|
39882
39897
|
"6.2": ["links"],
|
|
39898
|
+
// Theme 2 — frames. Both criteria are about the frame and its title, and nothing else.
|
|
39899
|
+
"2.1": ["frames"],
|
|
39900
|
+
"2.2": ["frames"],
|
|
39883
39901
|
// Theme 8 — the document itself. 8.1 maps onto no WCAG 2.2 criterion at all, so the pack
|
|
39884
39902
|
// is the ONLY place its subject can be named.
|
|
39885
39903
|
"8.1": ["doctype"],
|
|
@@ -39960,7 +39978,7 @@ var PACK_SUBJECTS = {
|
|
|
39960
39978
|
"13.11": ["pointerHandlers"]
|
|
39961
39979
|
}
|
|
39962
39980
|
};
|
|
39963
|
-
var EXISTENCE_SUBJECTS = /* @__PURE__ */ new Set(["images", "tables", "lists", "links", "controls", "autocomplete", "errors"]);
|
|
39981
|
+
var EXISTENCE_SUBJECTS = /* @__PURE__ */ new Set(["images", "tables", "lists", "links", "controls", "autocomplete", "errors", "frames"]);
|
|
39964
39982
|
var subjectsForSc = (sc) => SC_SUBJECTS[sc] ?? [];
|
|
39965
39983
|
function subjectsForPackCriterion(standard, id, scs) {
|
|
39966
39984
|
const own = PACK_SUBJECTS[standard]?.[id];
|
|
@@ -57794,10 +57812,11 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
57794
57812
|
let scopeCache;
|
|
57795
57813
|
const { citationDrift } = adjudicationLimits(opts.cwd);
|
|
57796
57814
|
const scopeFiles = () => scopeCache ??= auditFiles(audit2, opts.cwd);
|
|
57797
|
-
const toGround = (criteriaId, g) => {
|
|
57815
|
+
const toGround = (criteriaId, g, fallback) => {
|
|
57816
|
+
const entry = { g, ...fallback ? { fallback } : {} };
|
|
57798
57817
|
const list = groundInputs.get(criteriaId);
|
|
57799
|
-
if (list) list.push(
|
|
57800
|
-
else groundInputs.set(criteriaId, [
|
|
57818
|
+
if (list) list.push(entry);
|
|
57819
|
+
else groundInputs.set(criteriaId, [entry]);
|
|
57801
57820
|
};
|
|
57802
57821
|
for (const it of adj.items) {
|
|
57803
57822
|
const v = it.verdict;
|
|
@@ -57831,18 +57850,15 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
57831
57850
|
blame(it.criteriaId, `criterion ${it.criteriaId}: citation ${c2.file}:${c2.line} is not among this criterion's harvested evidence (fabricated?)`);
|
|
57832
57851
|
}
|
|
57833
57852
|
const anchor = anchorFor(it.evidence, c2, citationDrift);
|
|
57834
|
-
|
|
57835
|
-
|
|
57853
|
+
const cite = { file: c2.file, line: c2.line, selector: c2.selector, snippet: c2.snippet };
|
|
57854
|
+
if (anchor && recognisablySame(c2, anchor.at)) {
|
|
57855
|
+
toGround(
|
|
57836
57856
|
it.criteriaId,
|
|
57837
|
-
|
|
57857
|
+
cite,
|
|
57858
|
+
anchor.representative ? { file: anchor.at.file, line: anchor.at.line, selector: anchor.at.selector ?? c2.selector, snippet: anchor.at.snippet } : { file: c2.file, line: c2.line, selector: anchor.at.selector ?? c2.selector }
|
|
57838
57859
|
);
|
|
57839
|
-
}
|
|
57840
|
-
if (anchor?.representative) {
|
|
57841
|
-
toGround(it.criteriaId, { file: anchor.at.file, line: anchor.at.line, selector: anchor.at.selector ?? c2.selector, snippet: anchor.at.snippet });
|
|
57842
|
-
} else if (anchor) {
|
|
57843
|
-
toGround(it.criteriaId, { file: c2.file, line: c2.line, selector: anchor.at.selector ?? c2.selector });
|
|
57844
57860
|
} else {
|
|
57845
|
-
toGround(it.criteriaId,
|
|
57861
|
+
toGround(it.criteriaId, cite);
|
|
57846
57862
|
}
|
|
57847
57863
|
}
|
|
57848
57864
|
}
|
|
@@ -57869,8 +57885,12 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
57869
57885
|
}
|
|
57870
57886
|
const grounding = { grounded: 0, moved: 0, failed: 0, issues: [] };
|
|
57871
57887
|
for (const [criteriaId, inputs] of groundInputs) {
|
|
57872
|
-
for (const
|
|
57873
|
-
|
|
57888
|
+
for (const { g, fallback } of inputs) {
|
|
57889
|
+
let r = groundFinding(g, { cwd: opts.cwd });
|
|
57890
|
+
if (!r.ok && fallback) {
|
|
57891
|
+
const viaAnchor = groundFinding(fallback, { cwd: opts.cwd });
|
|
57892
|
+
if (viaAnchor.ok) r = { ok: true, moved: true };
|
|
57893
|
+
}
|
|
57874
57894
|
if (r.ok) {
|
|
57875
57895
|
grounding.grounded++;
|
|
57876
57896
|
if (r.moved) grounding.moved++;
|
|
@@ -58187,10 +58207,12 @@ function writeAdjudication(items, outDir, opts) {
|
|
|
58187
58207
|
}
|
|
58188
58208
|
return { todoPath, mdPath, verdictsPath, itemsDir, count: items.length };
|
|
58189
58209
|
}
|
|
58210
|
+
var PREFILLED_CITATIONS = 3;
|
|
58190
58211
|
function slimAdjudicationItems(items) {
|
|
58191
58212
|
return items.map((it) => {
|
|
58192
58213
|
const { evidence: _evidence, evidenceTruncated: _truncated, ...rest } = it;
|
|
58193
|
-
|
|
58214
|
+
const citations = it.evidence.slice(0, PREFILLED_CITATIONS).map((e) => ({ ...e }));
|
|
58215
|
+
return { ...rest, evidence: [], ...citations.length ? { citations } : {} };
|
|
58194
58216
|
});
|
|
58195
58217
|
}
|
|
58196
58218
|
function hydrateAdjudication(adj, audit2, opts = {}) {
|
|
@@ -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.9.
|
|
8
|
+
version: 5.9.2
|
|
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.
|
|
9
|
+
var VERSION = "5.9.2";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
|
|
12
12
|
// src/audit.ts
|
|
@@ -39652,6 +39652,21 @@ var SUBJECTS = {
|
|
|
39652
39652
|
(e) => h(d, e, `<html lang="${attr(e, "lang") ?? ""}"> \u2014 is it present, valid, and the language of the content?`, `doclang|${attr(e, "lang") ?? ""}`)
|
|
39653
39653
|
)
|
|
39654
39654
|
),
|
|
39655
|
+
// Frames, and the title each one carries. RGAA theme 2 is about nothing else: 2.1 asks
|
|
39656
|
+
// whether every frame HAS a title, 2.2 whether that title is pertinent. Both used to inherit
|
|
39657
|
+
// `aria` from WCAG 4.1.2 and be handed every ARIA attribute in the application — measured on
|
|
39658
|
+
// a real run, the adjudicator answered `undecidable` on both, correctly, because nothing it
|
|
39659
|
+
// had been shown could settle a question about `<iframe title>`.
|
|
39660
|
+
frames: (docs) => docs.flatMap(
|
|
39661
|
+
(d) => elementsByTag(d, "iframe", "frame").map(
|
|
39662
|
+
(e) => h(
|
|
39663
|
+
d,
|
|
39664
|
+
e,
|
|
39665
|
+
`<${e.tag}> title="${attr(e, "title") ?? ""}" name="${attr(e, "name") ?? ""}" src="${(attr(e, "src") ?? "").slice(0, 60)}" \u2014 un titre de cadre est-il pr\xE9sent, et d\xE9crit-il le contenu du cadre ?`,
|
|
39666
|
+
`frame|${attr(e, "title") ?? ""}|${attr(e, "src") ?? ""}`
|
|
39667
|
+
)
|
|
39668
|
+
)
|
|
39669
|
+
),
|
|
39655
39670
|
// The document's title, with the page it belongs to, so "is it pertinent?" is answerable.
|
|
39656
39671
|
docTitle: (docs) => docs.flatMap(
|
|
39657
39672
|
(d) => elementsByTag(d, "title").map((e) => h(d, e, `page="${pageOfDoc(d.file) ?? "source"}" <title>${t(e, 80)}</title>`, `doctitle|${t(e, 80)}`))
|
|
@@ -39880,6 +39895,9 @@ var PACK_SUBJECTS = {
|
|
|
39880
39895
|
// Theme 6 — links.
|
|
39881
39896
|
"6.1": ["links"],
|
|
39882
39897
|
"6.2": ["links"],
|
|
39898
|
+
// Theme 2 — frames. Both criteria are about the frame and its title, and nothing else.
|
|
39899
|
+
"2.1": ["frames"],
|
|
39900
|
+
"2.2": ["frames"],
|
|
39883
39901
|
// Theme 8 — the document itself. 8.1 maps onto no WCAG 2.2 criterion at all, so the pack
|
|
39884
39902
|
// is the ONLY place its subject can be named.
|
|
39885
39903
|
"8.1": ["doctype"],
|
|
@@ -39960,7 +39978,7 @@ var PACK_SUBJECTS = {
|
|
|
39960
39978
|
"13.11": ["pointerHandlers"]
|
|
39961
39979
|
}
|
|
39962
39980
|
};
|
|
39963
|
-
var EXISTENCE_SUBJECTS = /* @__PURE__ */ new Set(["images", "tables", "lists", "links", "controls", "autocomplete", "errors"]);
|
|
39981
|
+
var EXISTENCE_SUBJECTS = /* @__PURE__ */ new Set(["images", "tables", "lists", "links", "controls", "autocomplete", "errors", "frames"]);
|
|
39964
39982
|
var subjectsForSc = (sc) => SC_SUBJECTS[sc] ?? [];
|
|
39965
39983
|
function subjectsForPackCriterion(standard, id, scs) {
|
|
39966
39984
|
const own = PACK_SUBJECTS[standard]?.[id];
|
|
@@ -57794,10 +57812,11 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
57794
57812
|
let scopeCache;
|
|
57795
57813
|
const { citationDrift } = adjudicationLimits(opts.cwd);
|
|
57796
57814
|
const scopeFiles = () => scopeCache ??= auditFiles(audit2, opts.cwd);
|
|
57797
|
-
const toGround = (criteriaId, g) => {
|
|
57815
|
+
const toGround = (criteriaId, g, fallback) => {
|
|
57816
|
+
const entry = { g, ...fallback ? { fallback } : {} };
|
|
57798
57817
|
const list = groundInputs.get(criteriaId);
|
|
57799
|
-
if (list) list.push(
|
|
57800
|
-
else groundInputs.set(criteriaId, [
|
|
57818
|
+
if (list) list.push(entry);
|
|
57819
|
+
else groundInputs.set(criteriaId, [entry]);
|
|
57801
57820
|
};
|
|
57802
57821
|
for (const it of adj.items) {
|
|
57803
57822
|
const v = it.verdict;
|
|
@@ -57831,18 +57850,15 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
57831
57850
|
blame(it.criteriaId, `criterion ${it.criteriaId}: citation ${c2.file}:${c2.line} is not among this criterion's harvested evidence (fabricated?)`);
|
|
57832
57851
|
}
|
|
57833
57852
|
const anchor = anchorFor(it.evidence, c2, citationDrift);
|
|
57834
|
-
|
|
57835
|
-
|
|
57853
|
+
const cite = { file: c2.file, line: c2.line, selector: c2.selector, snippet: c2.snippet };
|
|
57854
|
+
if (anchor && recognisablySame(c2, anchor.at)) {
|
|
57855
|
+
toGround(
|
|
57836
57856
|
it.criteriaId,
|
|
57837
|
-
|
|
57857
|
+
cite,
|
|
57858
|
+
anchor.representative ? { file: anchor.at.file, line: anchor.at.line, selector: anchor.at.selector ?? c2.selector, snippet: anchor.at.snippet } : { file: c2.file, line: c2.line, selector: anchor.at.selector ?? c2.selector }
|
|
57838
57859
|
);
|
|
57839
|
-
}
|
|
57840
|
-
if (anchor?.representative) {
|
|
57841
|
-
toGround(it.criteriaId, { file: anchor.at.file, line: anchor.at.line, selector: anchor.at.selector ?? c2.selector, snippet: anchor.at.snippet });
|
|
57842
|
-
} else if (anchor) {
|
|
57843
|
-
toGround(it.criteriaId, { file: c2.file, line: c2.line, selector: anchor.at.selector ?? c2.selector });
|
|
57844
57860
|
} else {
|
|
57845
|
-
toGround(it.criteriaId,
|
|
57861
|
+
toGround(it.criteriaId, cite);
|
|
57846
57862
|
}
|
|
57847
57863
|
}
|
|
57848
57864
|
}
|
|
@@ -57869,8 +57885,12 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
57869
57885
|
}
|
|
57870
57886
|
const grounding = { grounded: 0, moved: 0, failed: 0, issues: [] };
|
|
57871
57887
|
for (const [criteriaId, inputs] of groundInputs) {
|
|
57872
|
-
for (const
|
|
57873
|
-
|
|
57888
|
+
for (const { g, fallback } of inputs) {
|
|
57889
|
+
let r = groundFinding(g, { cwd: opts.cwd });
|
|
57890
|
+
if (!r.ok && fallback) {
|
|
57891
|
+
const viaAnchor = groundFinding(fallback, { cwd: opts.cwd });
|
|
57892
|
+
if (viaAnchor.ok) r = { ok: true, moved: true };
|
|
57893
|
+
}
|
|
57874
57894
|
if (r.ok) {
|
|
57875
57895
|
grounding.grounded++;
|
|
57876
57896
|
if (r.moved) grounding.moved++;
|
|
@@ -58187,10 +58207,12 @@ function writeAdjudication(items, outDir, opts) {
|
|
|
58187
58207
|
}
|
|
58188
58208
|
return { todoPath, mdPath, verdictsPath, itemsDir, count: items.length };
|
|
58189
58209
|
}
|
|
58210
|
+
var PREFILLED_CITATIONS = 3;
|
|
58190
58211
|
function slimAdjudicationItems(items) {
|
|
58191
58212
|
return items.map((it) => {
|
|
58192
58213
|
const { evidence: _evidence, evidenceTruncated: _truncated, ...rest } = it;
|
|
58193
|
-
|
|
58214
|
+
const citations = it.evidence.slice(0, PREFILLED_CITATIONS).map((e) => ({ ...e }));
|
|
58215
|
+
return { ...rest, evidence: [], ...citations.length ? { citations } : {} };
|
|
58194
58216
|
});
|
|
58195
58217
|
}
|
|
58196
58218
|
function hydrateAdjudication(adj, audit2, opts = {}) {
|
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.9.
|
|
7
|
+
version: 5.9.2
|
|
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.
|
|
9
|
+
var VERSION = "5.9.2";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
|
|
12
12
|
// src/audit.ts
|
|
@@ -39652,6 +39652,21 @@ var SUBJECTS = {
|
|
|
39652
39652
|
(e) => h(d, e, `<html lang="${attr(e, "lang") ?? ""}"> \u2014 is it present, valid, and the language of the content?`, `doclang|${attr(e, "lang") ?? ""}`)
|
|
39653
39653
|
)
|
|
39654
39654
|
),
|
|
39655
|
+
// Frames, and the title each one carries. RGAA theme 2 is about nothing else: 2.1 asks
|
|
39656
|
+
// whether every frame HAS a title, 2.2 whether that title is pertinent. Both used to inherit
|
|
39657
|
+
// `aria` from WCAG 4.1.2 and be handed every ARIA attribute in the application — measured on
|
|
39658
|
+
// a real run, the adjudicator answered `undecidable` on both, correctly, because nothing it
|
|
39659
|
+
// had been shown could settle a question about `<iframe title>`.
|
|
39660
|
+
frames: (docs) => docs.flatMap(
|
|
39661
|
+
(d) => elementsByTag(d, "iframe", "frame").map(
|
|
39662
|
+
(e) => h(
|
|
39663
|
+
d,
|
|
39664
|
+
e,
|
|
39665
|
+
`<${e.tag}> title="${attr(e, "title") ?? ""}" name="${attr(e, "name") ?? ""}" src="${(attr(e, "src") ?? "").slice(0, 60)}" \u2014 un titre de cadre est-il pr\xE9sent, et d\xE9crit-il le contenu du cadre ?`,
|
|
39666
|
+
`frame|${attr(e, "title") ?? ""}|${attr(e, "src") ?? ""}`
|
|
39667
|
+
)
|
|
39668
|
+
)
|
|
39669
|
+
),
|
|
39655
39670
|
// The document's title, with the page it belongs to, so "is it pertinent?" is answerable.
|
|
39656
39671
|
docTitle: (docs) => docs.flatMap(
|
|
39657
39672
|
(d) => elementsByTag(d, "title").map((e) => h(d, e, `page="${pageOfDoc(d.file) ?? "source"}" <title>${t(e, 80)}</title>`, `doctitle|${t(e, 80)}`))
|
|
@@ -39880,6 +39895,9 @@ var PACK_SUBJECTS = {
|
|
|
39880
39895
|
// Theme 6 — links.
|
|
39881
39896
|
"6.1": ["links"],
|
|
39882
39897
|
"6.2": ["links"],
|
|
39898
|
+
// Theme 2 — frames. Both criteria are about the frame and its title, and nothing else.
|
|
39899
|
+
"2.1": ["frames"],
|
|
39900
|
+
"2.2": ["frames"],
|
|
39883
39901
|
// Theme 8 — the document itself. 8.1 maps onto no WCAG 2.2 criterion at all, so the pack
|
|
39884
39902
|
// is the ONLY place its subject can be named.
|
|
39885
39903
|
"8.1": ["doctype"],
|
|
@@ -39960,7 +39978,7 @@ var PACK_SUBJECTS = {
|
|
|
39960
39978
|
"13.11": ["pointerHandlers"]
|
|
39961
39979
|
}
|
|
39962
39980
|
};
|
|
39963
|
-
var EXISTENCE_SUBJECTS = /* @__PURE__ */ new Set(["images", "tables", "lists", "links", "controls", "autocomplete", "errors"]);
|
|
39981
|
+
var EXISTENCE_SUBJECTS = /* @__PURE__ */ new Set(["images", "tables", "lists", "links", "controls", "autocomplete", "errors", "frames"]);
|
|
39964
39982
|
var subjectsForSc = (sc) => SC_SUBJECTS[sc] ?? [];
|
|
39965
39983
|
function subjectsForPackCriterion(standard, id, scs) {
|
|
39966
39984
|
const own = PACK_SUBJECTS[standard]?.[id];
|
|
@@ -57794,10 +57812,11 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
57794
57812
|
let scopeCache;
|
|
57795
57813
|
const { citationDrift } = adjudicationLimits(opts.cwd);
|
|
57796
57814
|
const scopeFiles = () => scopeCache ??= auditFiles(audit2, opts.cwd);
|
|
57797
|
-
const toGround = (criteriaId, g) => {
|
|
57815
|
+
const toGround = (criteriaId, g, fallback) => {
|
|
57816
|
+
const entry = { g, ...fallback ? { fallback } : {} };
|
|
57798
57817
|
const list = groundInputs.get(criteriaId);
|
|
57799
|
-
if (list) list.push(
|
|
57800
|
-
else groundInputs.set(criteriaId, [
|
|
57818
|
+
if (list) list.push(entry);
|
|
57819
|
+
else groundInputs.set(criteriaId, [entry]);
|
|
57801
57820
|
};
|
|
57802
57821
|
for (const it of adj.items) {
|
|
57803
57822
|
const v = it.verdict;
|
|
@@ -57831,18 +57850,15 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
57831
57850
|
blame(it.criteriaId, `criterion ${it.criteriaId}: citation ${c2.file}:${c2.line} is not among this criterion's harvested evidence (fabricated?)`);
|
|
57832
57851
|
}
|
|
57833
57852
|
const anchor = anchorFor(it.evidence, c2, citationDrift);
|
|
57834
|
-
|
|
57835
|
-
|
|
57853
|
+
const cite = { file: c2.file, line: c2.line, selector: c2.selector, snippet: c2.snippet };
|
|
57854
|
+
if (anchor && recognisablySame(c2, anchor.at)) {
|
|
57855
|
+
toGround(
|
|
57836
57856
|
it.criteriaId,
|
|
57837
|
-
|
|
57857
|
+
cite,
|
|
57858
|
+
anchor.representative ? { file: anchor.at.file, line: anchor.at.line, selector: anchor.at.selector ?? c2.selector, snippet: anchor.at.snippet } : { file: c2.file, line: c2.line, selector: anchor.at.selector ?? c2.selector }
|
|
57838
57859
|
);
|
|
57839
|
-
}
|
|
57840
|
-
if (anchor?.representative) {
|
|
57841
|
-
toGround(it.criteriaId, { file: anchor.at.file, line: anchor.at.line, selector: anchor.at.selector ?? c2.selector, snippet: anchor.at.snippet });
|
|
57842
|
-
} else if (anchor) {
|
|
57843
|
-
toGround(it.criteriaId, { file: c2.file, line: c2.line, selector: anchor.at.selector ?? c2.selector });
|
|
57844
57860
|
} else {
|
|
57845
|
-
toGround(it.criteriaId,
|
|
57861
|
+
toGround(it.criteriaId, cite);
|
|
57846
57862
|
}
|
|
57847
57863
|
}
|
|
57848
57864
|
}
|
|
@@ -57869,8 +57885,12 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
57869
57885
|
}
|
|
57870
57886
|
const grounding = { grounded: 0, moved: 0, failed: 0, issues: [] };
|
|
57871
57887
|
for (const [criteriaId, inputs] of groundInputs) {
|
|
57872
|
-
for (const
|
|
57873
|
-
|
|
57888
|
+
for (const { g, fallback } of inputs) {
|
|
57889
|
+
let r = groundFinding(g, { cwd: opts.cwd });
|
|
57890
|
+
if (!r.ok && fallback) {
|
|
57891
|
+
const viaAnchor = groundFinding(fallback, { cwd: opts.cwd });
|
|
57892
|
+
if (viaAnchor.ok) r = { ok: true, moved: true };
|
|
57893
|
+
}
|
|
57874
57894
|
if (r.ok) {
|
|
57875
57895
|
grounding.grounded++;
|
|
57876
57896
|
if (r.moved) grounding.moved++;
|
|
@@ -58187,10 +58207,12 @@ function writeAdjudication(items, outDir, opts) {
|
|
|
58187
58207
|
}
|
|
58188
58208
|
return { todoPath, mdPath, verdictsPath, itemsDir, count: items.length };
|
|
58189
58209
|
}
|
|
58210
|
+
var PREFILLED_CITATIONS = 3;
|
|
58190
58211
|
function slimAdjudicationItems(items) {
|
|
58191
58212
|
return items.map((it) => {
|
|
58192
58213
|
const { evidence: _evidence, evidenceTruncated: _truncated, ...rest } = it;
|
|
58193
|
-
|
|
58214
|
+
const citations = it.evidence.slice(0, PREFILLED_CITATIONS).map((e) => ({ ...e }));
|
|
58215
|
+
return { ...rest, evidence: [], ...citations.length ? { citations } : {} };
|
|
58194
58216
|
});
|
|
58195
58217
|
}
|
|
58196
58218
|
function hydrateAdjudication(adj, audit2, opts = {}) {
|
|
@@ -496,6 +496,23 @@ export const SUBJECTS: Record<string, Subject> = {
|
|
|
496
496
|
),
|
|
497
497
|
),
|
|
498
498
|
|
|
499
|
+
// Frames, and the title each one carries. RGAA theme 2 is about nothing else: 2.1 asks
|
|
500
|
+
// whether every frame HAS a title, 2.2 whether that title is pertinent. Both used to inherit
|
|
501
|
+
// `aria` from WCAG 4.1.2 and be handed every ARIA attribute in the application — measured on
|
|
502
|
+
// a real run, the adjudicator answered `undecidable` on both, correctly, because nothing it
|
|
503
|
+
// had been shown could settle a question about `<iframe title>`.
|
|
504
|
+
frames: (docs) =>
|
|
505
|
+
docs.flatMap((d) =>
|
|
506
|
+
elementsByTag(d, "iframe", "frame").map((e) =>
|
|
507
|
+
h(
|
|
508
|
+
d,
|
|
509
|
+
e,
|
|
510
|
+
`<${e.tag}> title="${attr(e, "title") ?? ""}" name="${attr(e, "name") ?? ""}" src="${(attr(e, "src") ?? "").slice(0, 60)}" — un titre de cadre est-il présent, et décrit-il le contenu du cadre ?`,
|
|
511
|
+
`frame|${attr(e, "title") ?? ""}|${attr(e, "src") ?? ""}`,
|
|
512
|
+
),
|
|
513
|
+
),
|
|
514
|
+
),
|
|
515
|
+
|
|
499
516
|
// The document's title, with the page it belongs to, so "is it pertinent?" is answerable.
|
|
500
517
|
docTitle: (docs) =>
|
|
501
518
|
docs.flatMap((d) =>
|
|
@@ -790,6 +807,9 @@ export const PACK_SUBJECTS: Record<string, Record<string, string[]>> = {
|
|
|
790
807
|
// Theme 6 — links.
|
|
791
808
|
"6.1": ["links"],
|
|
792
809
|
"6.2": ["links"],
|
|
810
|
+
// Theme 2 — frames. Both criteria are about the frame and its title, and nothing else.
|
|
811
|
+
"2.1": ["frames"],
|
|
812
|
+
"2.2": ["frames"],
|
|
793
813
|
// Theme 8 — the document itself. 8.1 maps onto no WCAG 2.2 criterion at all, so the pack
|
|
794
814
|
// is the ONLY place its subject can be named.
|
|
795
815
|
"8.1": ["doctype"],
|
|
@@ -907,7 +927,7 @@ export const PACK_SUBJECTS: Record<string, Record<string, string[]>> = {
|
|
|
907
927
|
// hand-written applicability predicates in src/audit.ts, which are more careful than any
|
|
908
928
|
// subject here and which win outright where they exist. The pack criteria that map onto them
|
|
909
929
|
// inherit the NA through the derivation, so nothing is lost by leaving them out.
|
|
910
|
-
export const EXISTENCE_SUBJECTS: ReadonlySet<string> = new Set(["images", "tables", "lists", "links", "controls", "autocomplete", "errors"]);
|
|
930
|
+
export const EXISTENCE_SUBJECTS: ReadonlySet<string> = new Set(["images", "tables", "lists", "links", "controls", "autocomplete", "errors", "frames"]);
|
|
911
931
|
|
|
912
932
|
/** The subjects that decide a success criterion. Empty ⇒ the criterion has none declared,
|
|
913
933
|
* which `tests/harvest-coverage.test.ts` refuses for any criterion the engine hands over. */
|
package/src/adjudicate.ts
CHANGED
|
@@ -732,15 +732,21 @@ export function applyAdjudication(
|
|
|
732
732
|
// Per-item fail-closed validation. Grounding inputs are collected PER ITEM (not into one
|
|
733
733
|
// flat list) for the same reason as `blame`: a failed citation has to condemn its own
|
|
734
734
|
// criterion and no other.
|
|
735
|
-
|
|
735
|
+
type Ground = { file: string; line: number; selector?: string; snippet?: string };
|
|
736
|
+
const groundInputs = new Map<string, { g: Ground; fallback?: Ground }[]>();
|
|
736
737
|
// Memoised: only a citation that missed its criterion's own anchors ever asks for it.
|
|
737
738
|
let scopeCache: Set<string> | undefined;
|
|
738
739
|
const { citationDrift } = adjudicationLimits(opts.cwd);
|
|
739
740
|
const scopeFiles = (): Set<string> => (scopeCache ??= auditFiles(audit, opts.cwd));
|
|
740
|
-
const toGround = (
|
|
741
|
+
const toGround = (
|
|
742
|
+
criteriaId: string,
|
|
743
|
+
g: { file: string; line: number; selector?: string; snippet?: string },
|
|
744
|
+
fallback?: { file: string; line: number; selector?: string; snippet?: string },
|
|
745
|
+
) => {
|
|
746
|
+
const entry = { g, ...(fallback ? { fallback } : {}) };
|
|
741
747
|
const list = groundInputs.get(criteriaId);
|
|
742
|
-
if (list) list.push(
|
|
743
|
-
else groundInputs.set(criteriaId, [
|
|
748
|
+
if (list) list.push(entry);
|
|
749
|
+
else groundInputs.set(criteriaId, [entry]);
|
|
744
750
|
};
|
|
745
751
|
for (const it of adj.items) {
|
|
746
752
|
const v = it.verdict;
|
|
@@ -839,19 +845,33 @@ export function applyAdjudication(
|
|
|
839
845
|
//
|
|
840
846
|
// Anything OUTSIDE the harvest keeps the strict check below, unchanged. That is where
|
|
841
847
|
// a fabricated location would hide, and this must not become a way to launder one.
|
|
848
|
+
// THE CITATION FIRST, THE ANCHOR ONLY AS A FALLBACK — and the order is the whole
|
|
849
|
+
// point.
|
|
850
|
+
//
|
|
851
|
+
// Whatever the agent wrote is checked against the real file, exactly as before. That
|
|
852
|
+
// is the strongest proof there is, and it covers the case the drift window exists
|
|
853
|
+
// for: RGAA 5.2 asks about a table's summary, the harvest anchors the `<table>`, and
|
|
854
|
+
// the honest citation is the `<caption>` a line below. Grounding the anchor INSTEAD
|
|
855
|
+
// refused precisely that — measured on a real run, 5.2, 5.4, 5.5, 11.6, 11.7 and
|
|
856
|
+
// 11.9 all died on it, each a correct citation of a neighbour.
|
|
857
|
+
//
|
|
858
|
+
// Only when the citation does not ground — which is what a RETYPING looks like, and
|
|
859
|
+
// on a one-line snapshot that is most of them — does the harvested anchor stand in.
|
|
860
|
+
// It is authoritative there: the engine read it out of the file itself. And only
|
|
861
|
+
// then does recognisability have anything to say, because only then is the anchor
|
|
862
|
+
// being used to vouch for something the file did not confirm on its own.
|
|
842
863
|
const anchor = anchorFor(it.evidence, c, citationDrift);
|
|
843
|
-
|
|
844
|
-
|
|
864
|
+
const cite = { file: c.file, line: c.line, selector: c.selector, snippet: c.snippet };
|
|
865
|
+
if (anchor && recognisablySame(c, anchor.at)) {
|
|
866
|
+
toGround(
|
|
845
867
|
it.criteriaId,
|
|
846
|
-
|
|
868
|
+
cite,
|
|
869
|
+
anchor.representative
|
|
870
|
+
? { file: anchor.at.file, line: anchor.at.line, selector: anchor.at.selector ?? c.selector, snippet: anchor.at.snippet }
|
|
871
|
+
: { file: c.file, line: c.line, selector: anchor.at.selector ?? c.selector },
|
|
847
872
|
);
|
|
848
|
-
}
|
|
849
|
-
if (anchor?.representative) {
|
|
850
|
-
toGround(it.criteriaId, { file: anchor.at.file, line: anchor.at.line, selector: anchor.at.selector ?? c.selector, snippet: anchor.at.snippet });
|
|
851
|
-
} else if (anchor) {
|
|
852
|
-
toGround(it.criteriaId, { file: c.file, line: c.line, selector: anchor.at.selector ?? c.selector });
|
|
853
873
|
} else {
|
|
854
|
-
toGround(it.criteriaId,
|
|
874
|
+
toGround(it.criteriaId, cite);
|
|
855
875
|
}
|
|
856
876
|
}
|
|
857
877
|
}
|
|
@@ -893,8 +913,15 @@ export function applyAdjudication(
|
|
|
893
913
|
// flat `groundItems` produced.
|
|
894
914
|
const grounding: GroundingSummary = { grounded: 0, moved: 0, failed: 0, issues: [] };
|
|
895
915
|
for (const [criteriaId, inputs] of groundInputs) {
|
|
896
|
-
for (const
|
|
897
|
-
|
|
916
|
+
for (const { g, fallback } of inputs) {
|
|
917
|
+
let r = groundFinding(g, { cwd: opts.cwd });
|
|
918
|
+
// The harvested anchor vouches for a citation the file could not confirm on its own —
|
|
919
|
+
// an adjudicator's retyping of an element it did read. `moved` either way: what
|
|
920
|
+
// grounded is not verbatim what was cited.
|
|
921
|
+
if (!r.ok && fallback) {
|
|
922
|
+
const viaAnchor = groundFinding(fallback, { cwd: opts.cwd });
|
|
923
|
+
if (viaAnchor.ok) r = { ok: true, moved: true };
|
|
924
|
+
}
|
|
898
925
|
if (r.ok) {
|
|
899
926
|
grounding.grounded++;
|
|
900
927
|
if (r.moved) grounding.moved++;
|
|
@@ -1419,12 +1446,33 @@ export function writeAdjudication(
|
|
|
1419
1446
|
* bulk it only has to READ. Every field the fold requires — verdict, justification, reason,
|
|
1420
1447
|
* findings, citations, recommendations — is kept, so a filled slim file carries a complete
|
|
1421
1448
|
* decision. The evidence comes back at fold time via `hydrateAdjudication`. */
|
|
1449
|
+
/** Representatives pre-cited in the skeleton. Three is enough to show the shape and to stand
|
|
1450
|
+
* for the population; the whole set is in the brief, and `occurrences` travels with each. */
|
|
1451
|
+
const PREFILLED_CITATIONS = 3;
|
|
1452
|
+
|
|
1422
1453
|
export function slimAdjudicationItems(items: AdjudicationItem[]): AdjudicationItem[] {
|
|
1423
1454
|
return items.map((it) => {
|
|
1424
1455
|
const { evidence: _evidence, evidenceTruncated: _truncated, ...rest } = it;
|
|
1425
|
-
//
|
|
1426
|
-
//
|
|
1427
|
-
|
|
1456
|
+
// THE CITATIONS ARRIVE FILLED IN.
|
|
1457
|
+
//
|
|
1458
|
+
// This file is the only one a CI adjudicator writes, and it used to arrive with no evidence
|
|
1459
|
+
// and no citations: clearing a criterion meant authoring
|
|
1460
|
+
// `citations: [{file, line, selector, snippet}]` from scratch, by hand, cross-referencing a
|
|
1461
|
+
// separate brief, once per criterion. Measured on a real run: thirty criteria came back `C`
|
|
1462
|
+
// with a considered justification and no citations at all, and the fold refused every one
|
|
1463
|
+
// — « a C verdict must cite at least one of the N evidence item(s) it was shown ». The
|
|
1464
|
+
// agent had read the evidence. It had simply not copied an anchor into a second file.
|
|
1465
|
+
//
|
|
1466
|
+
// Pre-filling decides nothing. The verdict and the justification are still the agent's, and
|
|
1467
|
+
// a `C` still has to survive grounding, the coverage check and the complete-evidence rule.
|
|
1468
|
+
// What it removes is a transcription step that was costing correct verdicts — the same
|
|
1469
|
+
// failure, one level up, as an adjudicator retyping a snippet instead of copying it.
|
|
1470
|
+
//
|
|
1471
|
+
// `population` and `evidenceComplete` are kept for the same reason they always were: they
|
|
1472
|
+
// are two numbers, not bulk, and they tell the adjudicator how much of the subject it is
|
|
1473
|
+
// actually looking at.
|
|
1474
|
+
const citations = it.evidence.slice(0, PREFILLED_CITATIONS).map((e) => ({ ...e }));
|
|
1475
|
+
return { ...rest, evidence: [], ...(citations.length ? { citations } : {}) } as AdjudicationItem;
|
|
1428
1476
|
});
|
|
1429
1477
|
}
|
|
1430
1478
|
|
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.9.
|
|
3
|
+
export const VERSION = "5.9.2";
|
|
4
4
|
export const SCHEMA_VERSION = 2;
|
|
5
5
|
|
|
6
6
|
// Lang = the UI FRAME's language — the `L` localization tables each module (report,
|