ultra11y 5.45.0 → 5.45.1
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 +64 -31
- package/scripts/vendor/wcag-2.2-fr.json +1 -1
- package/scripts/vendor/wcag-2.2-sc-universe.json +1 -1
- package/scripts/vendor/wcag-2.2-techniques.json +1 -1
- package/scripts/vendor/wcag-2.2-text.json +1 -1
- package/skills/review-a11y/SKILL.md +1 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +64 -31
- package/skills/ultra11y/SKILL.md +1 -1
- package/skills/ultra11y/references/verify.md +11 -0
- package/skills/ultra11y/scripts/ultra11y.mjs +64 -31
- package/skills/ultra11y/templates/gitlab-ci.yml +1 -1
- package/src/adjudicate.ts +9 -22
- package/src/data/wcag-universe.json +1 -1
- package/src/orchestrate-templates.ts +1 -1
- package/src/refute.ts +10 -3
- package/src/types.ts +1 -1
- package/src/verdict-rules.ts +1 -1
- package/src/verify.ts +85 -14
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"name": "ultra11y",
|
|
11
11
|
"source": "./",
|
|
12
12
|
"description": "Automatic accessibility review before commit, push and pull request, plus detailed evidenced Markdown, HTML and per-page WCAG 2.2 AA / RGAA reports in Claude Code.",
|
|
13
|
-
"version": "5.45.
|
|
13
|
+
"version": "5.45.1",
|
|
14
14
|
"author": { "name": "maxgfr" },
|
|
15
15
|
"homepage": "https://github.com/SocialGouv/ultra11y",
|
|
16
16
|
"repository": "https://github.com/SocialGouv/ultra11y",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultra11y",
|
|
3
3
|
"description": "Accessibility for WCAG 2.2 AA and country standards (RGAA…): automatic review before commit, push and pull request, plus detailed evidenced Markdown, HTML and per-page conformance reports in interactive agent sessions.",
|
|
4
|
-
"version": "5.45.
|
|
4
|
+
"version": "5.45.1",
|
|
5
5
|
"author": { "name": "maxgfr" },
|
|
6
6
|
"homepage": "https://github.com/SocialGouv/ultra11y",
|
|
7
7
|
"repository": "https://github.com/SocialGouv/ultra11y",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultra11y",
|
|
3
3
|
"description": "Accessibility for WCAG 2.2 AA and country standards (RGAA…): automatic review before commit, push and pull request, plus detailed evidenced Markdown, HTML and per-page conformance reports in interactive agent sessions.",
|
|
4
|
-
"version": "5.45.
|
|
4
|
+
"version": "5.45.1",
|
|
5
5
|
"author": { "name": "maxgfr", "url": "https://github.com/maxgfr" },
|
|
6
6
|
"homepage": "https://github.com/SocialGouv/ultra11y",
|
|
7
7
|
"repository": "https://github.com/SocialGouv/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.45.
|
|
30
|
+
const ULTRA11Y_PLUGIN_VERSION = "5.45.1";
|
|
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.45.
|
|
3
|
+
"version": "5.45.1",
|
|
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 join52, relative as relative6, resolve as resolve17, 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.45.
|
|
9
|
+
var VERSION = "5.45.1";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
var INAPPLICABLE_STATUS = "C";
|
|
12
12
|
|
|
@@ -2298,7 +2298,7 @@ var wcag_universe_default = {
|
|
|
2298
2298
|
wcagVersion: "2.2",
|
|
2299
2299
|
source: "https://www.w3.org/TR/WCAG22/",
|
|
2300
2300
|
criteriaSource: "https://github.com/w3c/wcag",
|
|
2301
|
-
provenance: "Full WCAG 2.x SC universe (all levels incl. AAA, and the removed 4.1.1 Parsing) fetched from raw.githubusercontent.com/w3c/wcag@main on 2026-10-
|
|
2301
|
+
provenance: "Full WCAG 2.x SC universe (all levels incl. AAA, and the removed 4.1.1 Parsing) fetched from raw.githubusercontent.com/w3c/wcag@main on 2026-10-28 via `node scripts/build-standards.mjs --refresh-universe`. Classification: core-AA = ships in src/data/wcag.json (the shipped WCAG 2.2 AA core); out-of-core = WCAG AAA; removed = obsolete (4.1.1).",
|
|
2302
2302
|
criteria: [
|
|
2303
2303
|
{
|
|
2304
2304
|
id: "1.1.1",
|
|
@@ -62460,9 +62460,22 @@ function refuteSchema(items) {
|
|
|
62460
62460
|
function refuteSystemPrompt(lang = "en") {
|
|
62461
62461
|
const s = T[lang];
|
|
62462
62462
|
const setRule = lang === "fr" ? "Pour une conformit\xE9 qui porte `citations`, ouvrez et jugez le faisceau COMPLET : un crit\xE8re transversal n'a pas \xE0 \xEAtre prouv\xE9 par chaque ancre isol\xE9ment." : "For a conformity carrying `citations`, open and judge the COMPLETE evidence set: a cross-cutting criterion need not be proved by each anchor in isolation.";
|
|
62463
|
-
|
|
62464
|
-
|
|
62465
|
-
|
|
62463
|
+
const uncitedRule = lang === "fr" ? "Une conformit\xE9 marqu\xE9e `uncited` ne cite rien : c'est un \xAB non applicable \xBB d\xE9clar\xE9 sur un crit\xE8re dont rien n'a \xE9t\xE9 collect\xE9. Ouvrez les pages audit\xE9es et demandez-vous s'il n'y a VRAIMENT rien de ce dont parle le crit\xE8re ; l'absence de collecte n'est jamais une preuve \xE0 elle seule." : "A conformity marked `uncited` cites nothing: it is a \xAB not applicable \xBB declared on a criterion nothing was harvested for. Open the audited pages and ask whether there is REALLY nothing the criterion is about; an empty harvest is never proof on its own.";
|
|
62464
|
+
return [
|
|
62465
|
+
s.refuteRole,
|
|
62466
|
+
"",
|
|
62467
|
+
s.supported,
|
|
62468
|
+
s.partial,
|
|
62469
|
+
s.refuted,
|
|
62470
|
+
s.unsupported,
|
|
62471
|
+
"",
|
|
62472
|
+
s.refuteInverted,
|
|
62473
|
+
setRule,
|
|
62474
|
+
uncitedRule,
|
|
62475
|
+
s.refuteAttach,
|
|
62476
|
+
s.refuteDoubt,
|
|
62477
|
+
s.refuteNever
|
|
62478
|
+
].join("\n");
|
|
62466
62479
|
}
|
|
62467
62480
|
function conformityClaimsFromAudit(audit2, standard) {
|
|
62468
62481
|
const claim = (c2) => ({
|
|
@@ -62471,17 +62484,21 @@ function conformityClaimsFromAudit(audit2, standard) {
|
|
|
62471
62484
|
justification: c2.justification,
|
|
62472
62485
|
citations: c2.citations
|
|
62473
62486
|
});
|
|
62487
|
+
const clears = (status) => status === "C" || status === "NA";
|
|
62488
|
+
const seen = audit2.scope?.subjectsSeen ? new Set(audit2.scope.subjectsSeen) : void 0;
|
|
62489
|
+
const needsTrial = (c2, subjects) => (c2.citations?.length ?? 0) > 0 || seen === void 0 || !subjectsAbsent(subjects(), seen);
|
|
62474
62490
|
if (!isCore(standard)) {
|
|
62475
62491
|
const pa = audit2.packAdjudication;
|
|
62476
62492
|
if (!pa || pa.standard !== standard) return [];
|
|
62477
|
-
|
|
62493
|
+
const pack = loadPack(standard);
|
|
62494
|
+
return pa.criteria.filter((c2) => c2.decidedBy === "agent" && clears(c2.status)).filter((c2) => needsTrial(c2, () => subjectsForPackCriterion(standard, c2.id, getCriterion(pack, c2.id)?.wcag ?? []))).map(claim);
|
|
62478
62495
|
}
|
|
62479
|
-
return audit2.criteria.filter((c2) => c2.decidedBy === "agent" && c2.status
|
|
62496
|
+
return audit2.criteria.filter((c2) => c2.decidedBy === "agent" && clears(c2.status)).filter((c2) => needsTrial(c2, () => subjectsForSc(c2.id))).map(claim);
|
|
62480
62497
|
}
|
|
62481
62498
|
function buildConformityWorklist(claims, startAt = 0, max = VERIFY_MAX) {
|
|
62482
62499
|
const items = [];
|
|
62483
62500
|
for (const c2 of claims) {
|
|
62484
|
-
if (c2.verdict !== "C") continue;
|
|
62501
|
+
if (c2.verdict !== "C" && c2.verdict !== "NA") continue;
|
|
62485
62502
|
const citations = (c2.citations ?? []).map((cite) => ({
|
|
62486
62503
|
file: cite.file,
|
|
62487
62504
|
line: cite.line ?? 1,
|
|
@@ -62489,8 +62506,22 @@ function buildConformityWorklist(claims, startAt = 0, max = VERIFY_MAX) {
|
|
|
62489
62506
|
snippet: cite.snippet ?? ""
|
|
62490
62507
|
}));
|
|
62491
62508
|
const primary = citations[0];
|
|
62492
|
-
if (!primary) continue;
|
|
62493
62509
|
if (items.length >= max) return items;
|
|
62510
|
+
if (!primary) {
|
|
62511
|
+
items.push({
|
|
62512
|
+
n: startAt + items.length + 1,
|
|
62513
|
+
criteriaId: c2.criteriaId,
|
|
62514
|
+
file: "",
|
|
62515
|
+
line: 0,
|
|
62516
|
+
selector: "",
|
|
62517
|
+
claim: (c2.justification ?? "").replace(/\s+/g, " ").trim(),
|
|
62518
|
+
verdict: null,
|
|
62519
|
+
note: "",
|
|
62520
|
+
kind: "c",
|
|
62521
|
+
uncited: true
|
|
62522
|
+
});
|
|
62523
|
+
continue;
|
|
62524
|
+
}
|
|
62494
62525
|
items.push({
|
|
62495
62526
|
n: startAt + items.length + 1,
|
|
62496
62527
|
criteriaId: c2.criteriaId,
|
|
@@ -62523,6 +62554,8 @@ var T = {
|
|
|
62523
62554
|
conformityPartial: "- `partial` \u2014 elle l'\xE9tablit pour l'\xE9l\xE9ment cit\xE9, mais la justification d\xE9borde sur des cas qu'elle ne couvre pas ;",
|
|
62524
62555
|
conformityRefuted: "- `refuted` \u2014 l'\xE9vidence n'\xE9tablit pas la conformit\xE9 (elle constate une pr\xE9sence, pas une pertinence) ;",
|
|
62525
62556
|
conformityUnsupported: "- `unsupported` \u2014 l'\xE9vidence cit\xE9e ne permet pas de trancher.",
|
|
62557
|
+
uncitedScope: "_p\xE9rim\xE8tre audit\xE9 (aucune \xE9vidence cit\xE9e)_",
|
|
62558
|
+
uncitedQuestion: "Rien n'a \xE9t\xE9 collect\xE9 pour ce crit\xE8re et l'agent l'a d\xE9clar\xE9 non applicable. Ouvrez les pages audit\xE9es : n'y a-t-il VRAIMENT rien de ce dont parle le crit\xE8re ? L'absence de collecte n'est pas une preuve \u2014 une page avec du contenu a toujours un ordre de lecture, un ordre de tabulation, du texte \xE0 espacer, une mise en page \xE0 redistribuer ou \xE0 pivoter. `supported` seulement si l'absence est v\xE9rifiable sur les pages.",
|
|
62526
62559
|
refuteRole: "Vous \xEAtes le SECOND lecteur d'un audit d'accessibilit\xE9. Vous n'auditez pas : vous mettez \xE0 l'\xE9preuve des constats d\xE9j\xE0 \xE9crits. Pour chaque entr\xE9e, ouvrez le fichier \xE0 la ligne cit\xE9e, lisez l'\xE9l\xE9ment, et dites si l'\xE9vidence cit\xE9e \xE9taye la revendication :",
|
|
62527
62560
|
refuteInverted: "Les entr\xE9es marqu\xE9es \xAB conformit\xE9 revendiqu\xE9e \xBB posent la question INVERSE : l'\xE9vidence \xC9TABLIT-elle le crit\xE8re, ou constate-t-elle seulement que son sujet EXISTE ? Un `alt` pr\xE9sent n'est pas un `alt` pertinent ; un `<title>` pr\xE9sent n'est pas un titre qui d\xE9crit la page.",
|
|
62528
62561
|
refuteAttach: "V\xE9rifiez aussi le RATTACHEMENT : le r\xE9f\xE9rentiel s\xE9pare l'existence d'une chose et sa pertinence en deux crit\xE8res distincts. Un constat d'absence rang\xE9 sous le crit\xE8re de pertinence est `refuted` \u2014 il est r\xE9el, mais il appartient au crit\xE8re voisin.",
|
|
@@ -62556,6 +62589,8 @@ var T = {
|
|
|
62556
62589
|
conformityPartial: "- `partial` \u2014 it establishes it for the cited element, but the justification reaches beyond what it covers;",
|
|
62557
62590
|
conformityRefuted: "- `refuted` \u2014 the evidence does not establish conformity (it observes a presence, not a relevance);",
|
|
62558
62591
|
conformityUnsupported: "- `unsupported` \u2014 the cited evidence is not enough to decide.",
|
|
62592
|
+
uncitedScope: "_audited scope (no evidence cited)_",
|
|
62593
|
+
uncitedQuestion: "Nothing was harvested for this criterion and the agent declared it not applicable. Open the audited pages: is there REALLY nothing here the criterion is about? An empty harvest is not proof \u2014 a page with content always has a reading order, a focus order, text to space and a layout to reflow or rotate. `supported` only if the absence can be checked on the pages.",
|
|
62559
62594
|
refuteRole: "You are the SECOND reader of an accessibility audit. You are not auditing: you are putting already-written observations on trial. For each entry, open the file at the cited line, read the element, and say whether the cited evidence supports the claim:",
|
|
62560
62595
|
refuteInverted: "Entries marked \u201Cclaimed conformity\u201D ask the INVERTED question: does the evidence ESTABLISH the criterion, or does it only show that its subject EXISTS? A present `alt` is not a relevant `alt`; a present `<title>` is not a title that describes the page.",
|
|
62561
62596
|
refuteAttach: "Check the ATTACHMENT too: the standard splits the existence of a thing and its relevance into two separate criteria. An observation of absence filed under the relevance criterion is `refuted` \u2014 it is real, but it belongs to the neighbouring criterion.",
|
|
@@ -62592,7 +62627,8 @@ function formatWorklist(items, semantic, standard = "wcag", lang = "en") {
|
|
|
62592
62627
|
for (const it of list) renderItem(it);
|
|
62593
62628
|
};
|
|
62594
62629
|
const renderItem = (it) => {
|
|
62595
|
-
out2.push(`- [ ] #${it.n} **${it.criteriaId}** @
|
|
62630
|
+
if (it.uncited) out2.push(`- [ ] #${it.n} **${it.criteriaId}** @ ${s.uncitedScope} \u2014 ${it.claim}`, ` ${s.uncitedQuestion}`);
|
|
62631
|
+
else out2.push(`- [ ] #${it.n} **${it.criteriaId}** @ \`${it.file}:${it.line}\` (\`${it.selector}\`) \u2014 ${it.claim}`);
|
|
62596
62632
|
if (it.kind === "c" && (it.citations?.length ?? 0) > 1) {
|
|
62597
62633
|
out2.push(lang === "fr" ? " - Faisceau complet \xE0 v\xE9rifier ensemble :" : " - Complete evidence set to verify together:");
|
|
62598
62634
|
for (const cite of it.citations ?? []) {
|
|
@@ -62649,7 +62685,7 @@ var resumableItemKey = (it) => {
|
|
|
62649
62685
|
var legacyConformityKey = (criteriaId, file, line, selector, claim) => `${criteriaId}|${canonicalFile(file)}|${line}|${selector}|${claim}`;
|
|
62650
62686
|
function verifyGroundingInputs(items, fallbackFor) {
|
|
62651
62687
|
return items.flatMap(
|
|
62652
|
-
(it) => it.kind === "c" && it.citations?.length ? it.citations.map((cite) => {
|
|
62688
|
+
(it) => it.uncited ? [] : it.kind === "c" && it.citations?.length ? it.citations.map((cite) => {
|
|
62653
62689
|
const fallback = fallbackFor?.(it, cite);
|
|
62654
62690
|
return { file: cite.file, line: cite.line, selector: cite.selector, snippet: cite.snippet, ...fallback ? { fallback } : {} };
|
|
62655
62691
|
}) : [{ file: it.file, line: it.line, selector: it.selector, snippet: it.snippet }]
|
|
@@ -64127,14 +64163,6 @@ function blankItem(criteriaId, automatability2, title, harvested, limits, signal
|
|
|
64127
64163
|
decidedBy: "agent"
|
|
64128
64164
|
};
|
|
64129
64165
|
}
|
|
64130
|
-
var subjectsForSc2 = (sc) => SC_SUBJECTS[sc] ?? [];
|
|
64131
|
-
function subjectsForPackCriterion2(standard, id, scs) {
|
|
64132
|
-
const own = PACK_SUBJECTS[standard]?.[id];
|
|
64133
|
-
if (own) return own;
|
|
64134
|
-
const out2 = [];
|
|
64135
|
-
for (const sc of scs) for (const subject of subjectsForSc2(sc)) if (!out2.includes(subject)) out2.push(subject);
|
|
64136
|
-
return out2;
|
|
64137
|
-
}
|
|
64138
64166
|
function packResultNeedsAdjudication(pc, criterion) {
|
|
64139
64167
|
if (pc.status === "manual") return true;
|
|
64140
64168
|
return isProvisionalJudgmentInapplicable(pc, criterion);
|
|
@@ -64188,7 +64216,7 @@ function buildAdjudicationWorklist(audit2, opts = {}) {
|
|
|
64188
64216
|
// locales are not the UI frame's `Lang`: casting one to the other to satisfy a
|
|
64189
64217
|
// signature would be asserting something about a country standard that is not true.
|
|
64190
64218
|
crit ? localize(pack, crit.titlePlain, pack.defaultLocale) : void 0,
|
|
64191
|
-
harvestSubjects(
|
|
64219
|
+
harvestSubjects(subjectsForPackCriterion(standard, pc.id, scs), docs),
|
|
64192
64220
|
limits,
|
|
64193
64221
|
(pc.candidateFindings ?? []).map((finding) => ({
|
|
64194
64222
|
ruleId: finding.ruleId,
|
|
@@ -64218,7 +64246,7 @@ function buildAdjudicationWorklist(audit2, opts = {}) {
|
|
|
64218
64246
|
});
|
|
64219
64247
|
}
|
|
64220
64248
|
return audit2.residualRisks.map(
|
|
64221
|
-
(r) => blankItem(r.criteriaId, r.automatability, scTitle(r.criteriaId) ?? void 0, harvestSubjects(
|
|
64249
|
+
(r) => blankItem(r.criteriaId, r.automatability, scTitle(r.criteriaId) ?? void 0, harvestSubjects(subjectsForSc(r.criteriaId), docs), limits)
|
|
64222
64250
|
);
|
|
64223
64251
|
}
|
|
64224
64252
|
function evidenceForCriteria(audit2, criteriaIds, opts = {}) {
|
|
@@ -64231,7 +64259,7 @@ function evidenceForCriteria(audit2, criteriaIds, opts = {}) {
|
|
|
64231
64259
|
for (const id of criteriaIds) {
|
|
64232
64260
|
if (out2.has(id)) continue;
|
|
64233
64261
|
const crit = pack ? getCriterion(pack, id) : void 0;
|
|
64234
|
-
const subjects = pack && standard ?
|
|
64262
|
+
const subjects = pack && standard ? subjectsForPackCriterion(standard, id, crit?.wcag ?? []) : subjectsForSc(id);
|
|
64235
64263
|
let evidence = collapse2(harvestSubjects(subjects, docs), limits).evidence;
|
|
64236
64264
|
if (!evidence.length) evidence = candidateFindingEvidence(candidates2.get(id) ?? [], limits.maxClasses);
|
|
64237
64265
|
out2.set(id, evidence);
|
|
@@ -64511,7 +64539,7 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
64511
64539
|
if (v === "C") {
|
|
64512
64540
|
blame(
|
|
64513
64541
|
it.criteriaId,
|
|
64514
|
-
`criterion ${it.criteriaId}: a C verdict needs evidence to cite, and none was harvested for this criterion \u2014 record "manual" (reason "undecidable")
|
|
64542
|
+
`criterion ${it.criteriaId}: a C verdict needs evidence to cite, and none was harvested for this criterion \u2014 record "manual" (reason "undecidable"); "NA" only if the scope provably contains nothing the criterion is about \u2014 an uncited NA is put on trial by verify`
|
|
64515
64543
|
);
|
|
64516
64544
|
}
|
|
64517
64545
|
} else if (cites.length === 0 && (v === "C" || v === "NA")) {
|
|
@@ -64847,7 +64875,7 @@ var T2 = {
|
|
|
64847
64875
|
"- `NA` \u2014 not applicable (fill `justification`; when evidence is presented, cite it too in `citations[]` to say which items are out of scope);",
|
|
64848
64876
|
"- `manual` \u2014 not statically decidable (fill `reason`: `needs-rendered-dom` \u2192 `scan`, or `undecidable`)."
|
|
64849
64877
|
],
|
|
64850
|
-
rule: "> Report NC only if a precise test of the active standard fails \u2014 cite it (`normativeRef`). A good practice without a normative test is a recommendation (`recommendations[]`, non-normative). A purely UX concern is neither.\n>\n> A `C` is cited the same way an NC is: name the evidence you cleared in `citations[]`. **A criterion presented with no evidence at all cannot be `C`** \u2014 record `manual` (`undecidable`), or `NA` if nothing
|
|
64878
|
+
rule: "> Report NC only if a precise test of the active standard fails \u2014 cite it (`normativeRef`). A good practice without a normative test is a recommendation (`recommendations[]`, non-normative). A purely UX concern is neither.\n>\n> A `C` is cited the same way an NC is: name the evidence you cleared in `citations[]`. **A criterion presented with no evidence at all cannot be `C`** \u2014 record `manual` (`undecidable`), or `NA` only if the scope provably contains nothing the criterion is about. No harvested evidence is NOT that proof: a page with content always has a reading order, a focus order, text to space and a layout to reflow or rotate \u2014 an uncited NA is put on trial by `verify`.",
|
|
64851
64879
|
then: "Then: `ultra11y verify --apply ADJUDICATE.todo.json --in <audit.json> --out <dir>` (fails if any verdict lacks its justification, citations, finding or reason).",
|
|
64852
64880
|
evidence: "Evidence",
|
|
64853
64881
|
classes: "distinct content classes",
|
|
@@ -65148,11 +65176,13 @@ var WITHDRAWN = /* @__PURE__ */ new Set(["refuted", "unsupported"]);
|
|
|
65148
65176
|
var REASON = {
|
|
65149
65177
|
fr: {
|
|
65150
65178
|
nc: "Non-conformit\xE9 r\xE9fut\xE9e \xE0 la contre-expertise : le constat ne tient pas sur l'\xE9l\xE9ment cit\xE9. Le crit\xE8re retourne \xAB \xE0 \xE9valuer \xBB \u2014 r\xE9futer une non-conformit\xE9 n'\xE9tablit pas la conformit\xE9.",
|
|
65151
|
-
c: "Conformit\xE9 revendiqu\xE9e non \xE9tay\xE9e \xE0 la contre-expertise : l'\xE9vidence cit\xE9e constate une pr\xE9sence, pas une conformit\xE9. Le crit\xE8re retourne \xAB \xE0 \xE9valuer \xBB \u2014 il ne devient PAS une non-conformit\xE9."
|
|
65179
|
+
c: "Conformit\xE9 revendiqu\xE9e non \xE9tay\xE9e \xE0 la contre-expertise : l'\xE9vidence cit\xE9e constate une pr\xE9sence, pas une conformit\xE9. Le crit\xE8re retourne \xAB \xE0 \xE9valuer \xBB \u2014 il ne devient PAS une non-conformit\xE9.",
|
|
65180
|
+
na: "\xAB Non applicable \xBB non \xE9tay\xE9 \xE0 la contre-expertise : rien n'avait \xE9t\xE9 collect\xE9, et l'absence de collecte ne prouve pas que le p\xE9rim\xE8tre ne contient rien de concern\xE9. Le crit\xE8re retourne \xAB \xE0 \xE9valuer \xBB \u2014 il ne devient PAS une non-conformit\xE9."
|
|
65152
65181
|
},
|
|
65153
65182
|
en: {
|
|
65154
65183
|
nc: "Non-conformity refuted on review: the observation does not hold on the cited element. The criterion goes back to \u201Cto assess\u201D \u2014 refuting a non-conformity does not establish conformity.",
|
|
65155
|
-
c: "Claimed conformity unsupported on review: the cited evidence observes a presence, not a conformity. The criterion goes back to \u201Cto assess\u201D \u2014 it does NOT become a non-conformity."
|
|
65184
|
+
c: "Claimed conformity unsupported on review: the cited evidence observes a presence, not a conformity. The criterion goes back to \u201Cto assess\u201D \u2014 it does NOT become a non-conformity.",
|
|
65185
|
+
na: "\u201CNot applicable\u201D unsupported on review: nothing had been harvested, and an empty harvest does not prove the scope holds nothing concerned. The criterion goes back to \u201Cto assess\u201D \u2014 it does NOT become a non-conformity."
|
|
65156
65186
|
}
|
|
65157
65187
|
};
|
|
65158
65188
|
var key = (file, line, selector) => `${file.trim()}|${line}|${(selector ?? "").trim()}`;
|
|
@@ -65163,10 +65193,13 @@ function pruneRefuted(audit2, standard, items, lang = "en") {
|
|
|
65163
65193
|
const withdrawn = items.filter((it) => WITHDRAWN.has((it.verdict ?? "").trim().toLowerCase()));
|
|
65164
65194
|
const removedNc = /* @__PURE__ */ new Map();
|
|
65165
65195
|
const withdrawnC = /* @__PURE__ */ new Set();
|
|
65196
|
+
const withdrawnUncited = /* @__PURE__ */ new Set();
|
|
65166
65197
|
const notes = /* @__PURE__ */ new Map();
|
|
65167
65198
|
for (const it of withdrawn) {
|
|
65168
|
-
if (it.kind === "c")
|
|
65169
|
-
|
|
65199
|
+
if (it.kind === "c") {
|
|
65200
|
+
withdrawnC.add(it.criteriaId);
|
|
65201
|
+
if (it.uncited) withdrawnUncited.add(it.criteriaId);
|
|
65202
|
+
} else (removedNc.get(it.criteriaId) ?? removedNc.set(it.criteriaId, /* @__PURE__ */ new Set()).get(it.criteriaId)).add(key(it.file, it.line, it.selector));
|
|
65170
65203
|
if (it.note?.trim()) {
|
|
65171
65204
|
const criterionNotes = notes.get(it.criteriaId) ?? [];
|
|
65172
65205
|
if (!criterionNotes.includes(it.note.trim())) criterionNotes.push(it.note.trim());
|
|
@@ -65205,7 +65238,7 @@ function pruneRefuted(audit2, standard, items, lang = "en") {
|
|
|
65205
65238
|
if (withdrawnC.has(rec.id) && isAgent) {
|
|
65206
65239
|
acted.add(rec.id);
|
|
65207
65240
|
clearedConformities.push(rec.id);
|
|
65208
|
-
setOpen(withReview(rec.id, reason.c));
|
|
65241
|
+
setOpen(withReview(rec.id, withdrawnUncited.has(rec.id) ? reason.na : reason.c));
|
|
65209
65242
|
}
|
|
65210
65243
|
};
|
|
65211
65244
|
if (!isCore(standard) && next.packAdjudication?.standard === standard) {
|
|
@@ -65490,7 +65523,7 @@ import { delimiter, join as join36 } from "path";
|
|
|
65490
65523
|
|
|
65491
65524
|
// src/verdict-rules.ts
|
|
65492
65525
|
var VERDICT_KINDS = `Rule it (the apply gate is FAIL-CLOSED \u2014 a verdict missing its required field does not fold, and its criterion goes back to \xAB to assess \xBB carrying the refusal):
|
|
65493
|
-
- \`C\` (conforming) \u2014 REQUIRES \`justification\` explaining why the evidence satisfies the criterion, AND \`citations[]\` naming the evidence you cleared (\`file\`/\`line\` copied VERBATIM from this criterion's own evidence; an anchor that is not in that list is treated as fabricated). A criterion presented with NO evidence at all cannot be \`C\` \u2014 it is \`manual\` (\`undecidable\`), or \`NA\` if nothing
|
|
65526
|
+
- \`C\` (conforming) \u2014 REQUIRES \`justification\` explaining why the evidence satisfies the criterion, AND \`citations[]\` naming the evidence you cleared (\`file\`/\`line\` copied VERBATIM from this criterion's own evidence; an anchor that is not in that list is treated as fabricated). A criterion presented with NO evidence at all cannot be \`C\` \u2014 it is \`manual\` (\`undecidable\`), or \`NA\` only if the scope provably contains nothing the criterion is about. No harvested evidence is NOT that proof: a page with content always has a reading order, a focus order, text to space and a layout to reflow or rotate \u2014 an uncited NA is put on trial by \`verify\`.
|
|
65494
65527
|
- \`NC\` (non-conforming) \u2014 REQUIRES \`findings\`: at least one groundable \`{ file, line, selector?, message, snippet?, severity?, normativeRef }\` pointing at REAL source. The fold re-grounds every finding; an invented file:line is rejected, and so is a finding with no \`file\` at all. \`normativeRef\` MUST cite the precise failed test \u2014 under a country standard, one of the criterion's OWN numbered tests, listed in its brief under \xAB tests to rule on \xBB. A WCAG id looks alike, denotes an unrelated test, and is rejected.
|
|
65495
65528
|
- \`NA\` (not applicable) \u2014 REQUIRES \`justification\`, AND \`citations[]\` whenever evidence WAS presented, to say which of those items fall outside the criterion's scope.
|
|
65496
65529
|
- \`manual\` (still undecidable) \u2014 REQUIRES \`reason\`: \`needs-rendered-dom\` (only a rendered DOM can decide it, and no capture in this run carries its subject) or \`undecidable\` (the evidence cannot settle it either way).`;
|
|
@@ -71759,7 +71792,7 @@ For EACH of your criteria:
|
|
|
71759
71792
|
1. Read its worklist entry for the EVIDENCE: \`evidence[]\` holds source-anchored excerpts (\`file\`, \`line\`, \`selector\`, \`snippet\`) harvested from the audited code \u2014 open the cited files at the cited lines whenever the snippet alone cannot decide.
|
|
71760
71793
|
1b. Read \`${join48(runAbs, "adjudicate")}/<criteriaId>.md\` for the CRITERION ITSELF \u2014 its official wording, its numbered tests with the standard's own test methodology, the technical note, the particular cases and the glossary terms the tests are defined in terms of. The worklist JSON carries none of that: it holds the evidence and the slots your verdict goes into, and ruling on a country standard from the criterion's title alone is how a verdict ends up citing a test it never read. That brief also cites the standard's official page for the criterion: if a wording stays ambiguous and you have a web tool, you MAY read that page to settle it \u2014 never to contradict the vendored text, and a web page is never a \`normativeRef\`.
|
|
71761
71794
|
2. Rule it (the apply gate is FAIL-CLOSED \u2014 a verdict missing its required field does not fold):
|
|
71762
|
-
- \`C\` (conforming) \u2014 REQUIRES \`justification\` explaining why the evidence satisfies the criterion, AND \`citations[]\` naming the evidence you cleared (\`file\`/\`line\` copied VERBATIM from this item's own \`evidence[]\`; an anchor that is not in that list is treated as fabricated). A criterion whose \`evidence[]\` is empty cannot be \`C\` at all \u2014 it is \`manual\` (\`undecidable\`), or \`NA\` if nothing
|
|
71795
|
+
- \`C\` (conforming) \u2014 REQUIRES \`justification\` explaining why the evidence satisfies the criterion, AND \`citations[]\` naming the evidence you cleared (\`file\`/\`line\` copied VERBATIM from this item's own \`evidence[]\`; an anchor that is not in that list is treated as fabricated). A criterion whose \`evidence[]\` is empty cannot be \`C\` at all \u2014 it is \`manual\` (\`undecidable\`), or \`NA\` only if the scope provably contains nothing the criterion is about. No harvested evidence is NOT that proof: a page with content always has a reading order, a focus order, text to space and a layout to reflow or rotate \u2014 an uncited NA is put on trial by \`verify\`.
|
|
71763
71796
|
- \`NC\` (non-conforming) \u2014 REQUIRES \`findings\`: at least one groundable \`{ file, line, selector?, message, snippet?, severity?, normativeRef }\` pointing at REAL source. The fold re-grounds every finding; an invented file:line is rejected. \`normativeRef\` MUST cite the precise failed test \u2014 under a country standard, one of the item's OWN tests, which its brief lists for you under \xAB tests to rule on \xBB. A WCAG id looks alike, denotes an unrelated test, and is rejected.
|
|
71764
71797
|
- \`NA\` (not applicable) \u2014 REQUIRES \`justification\`, AND \`citations[]\` whenever evidence WAS presented, to say which of those items fall outside the criterion's scope.
|
|
71765
71798
|
- \`manual\` (still undecidable) \u2014 REQUIRES \`reason\`: \`needs-rendered-dom\` (only a rendered DOM can decide, e.g. computed contrast) or \`undecidable\` (the evidence cannot settle it either way).
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"wcagVersion": "2.2",
|
|
3
3
|
"source": "https://www.w3.org/TR/WCAG22/",
|
|
4
4
|
"criteriaSource": "https://github.com/w3c/wcag",
|
|
5
|
-
"provenance": "Full WCAG 2.x SC universe (all levels incl. AAA, and the removed 4.1.1 Parsing) fetched from raw.githubusercontent.com/w3c/wcag@main on 2026-10-
|
|
5
|
+
"provenance": "Full WCAG 2.x SC universe (all levels incl. AAA, and the removed 4.1.1 Parsing) fetched from raw.githubusercontent.com/w3c/wcag@main on 2026-10-28 via `node scripts/build-standards.mjs --refresh-universe`. Classification: core-AA = ships in src/data/wcag.json (the shipped WCAG 2.2 AA core); out-of-core = WCAG AAA; removed = obsolete (4.1.1).",
|
|
6
6
|
"principles": [
|
|
7
7
|
{
|
|
8
8
|
"number": 1,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"source": "https://github.com/w3c/wcag",
|
|
3
3
|
"understanding": "https://www.w3.org/WAI/WCAG22/Understanding/",
|
|
4
4
|
"license": "W3C Document License (code samples: W3C Software License)",
|
|
5
|
-
"fetchedAt": "2026-10-
|
|
5
|
+
"fetchedAt": "2026-10-28",
|
|
6
6
|
"criteria": {
|
|
7
7
|
"1.1.1": {
|
|
8
8
|
"slug": "non-text-content",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"source": "https://github.com/w3c/wcag",
|
|
3
3
|
"license": "W3C Document License",
|
|
4
|
-
"fetchedAt": "2026-10-
|
|
4
|
+
"fetchedAt": "2026-10-28",
|
|
5
5
|
"criteria": {
|
|
6
6
|
"1.1.1": "All non-text content that is presented to the user has a text alternative that serves the equivalent purpose, except for the situations listed below.\nControls, Input:\nIf non-text content is a control or accepts user input, then it has a name that describes its purpose. (Refer to Success Criterion 4.1.2 Name, Role, Value for additional requirements for controls and content that accepts user input.)\nTime-Based Media:\nIf non-text content is time-based media, then text alternatives at least provide descriptive identification of the non-text content. (Refer to Guideline 1.2 for additional requirements for media.)\nTest:\nIf non-text content is a test or exercise that would be invalid if presented in text, then text alternatives at least provide descriptive identification of the non-text content.\nSensory:\nIf non-text content is primarily intended to create a specific sensory experience, then text alternatives at least provide descriptive identification of the non-text content.\nCAPTCHA:\nIf the purpose of non-text content is to confirm that content is being accessed by a person rather than a computer, then text alternatives that identify and describe the purpose of the non-text content are provided, and alternative forms of CAPTCHA using output modes for different types of sensory perception are provided to accommodate different disabilities.\nDecoration, Formatting, Invisible:\nIf non-text content is pure decoration, is used only for visual formatting, or is not presented to users, then it is implemented in a way that it can be ignored by assistive technology.",
|
|
7
7
|
"1.2.1": "For prerecorded audio-only and prerecorded video-only media, the following are true, except when the audio or video is a media alternative for text and is clearly labeled as such:\nPrerecorded Audio-only:\nAn alternative for time-based media is provided that presents equivalent information for prerecorded audio-only content.\nPrerecorded Video-only:\nEither an alternative for time-based media or an audio track is provided that presents equivalent information for prerecorded video-only content.",
|
|
@@ -3,7 +3,7 @@ name: review-a11y
|
|
|
3
3
|
description: "Use to REVIEW changed frontend code for accessibility: staged files, a working diff, branch, or PR; prompts such as 'review a11y', 'is this accessible?', or 'anything to fix before merge?'; and hook or CI findings on a change. Run the bundled install-free engine with cross-file JSX/TSX analysis, then adjudicate candidates and return a severity-ranked WCAG 2.2 AA review with file:line fixes, residual rendering risks, and a verdict. Use `ultra11y` instead for whole-repo audits, dated conformance reports, country standards, PRD backlogs, or authoring accessible markup."
|
|
4
4
|
license: MIT
|
|
5
5
|
metadata:
|
|
6
|
-
version: 5.45.
|
|
6
|
+
version: 5.45.1
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# review-a11y — accessibility review of changed code
|