ultra11y 5.35.0 → 5.35.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 +111 -46
- package/skills/review-a11y/SKILL.md +1 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +111 -46
- package/skills/ultra11y/SKILL.md +9 -1
- package/skills/ultra11y/references/ci.md +13 -4
- package/skills/ultra11y/references/methodology.md +11 -0
- package/skills/ultra11y/references/runbook.md +6 -0
- package/skills/ultra11y/scripts/ultra11y.mjs +111 -46
- package/src/adjudicate.ts +6 -0
- package/src/annotate.ts +53 -40
- package/src/cli.ts +2 -6
- package/src/llm.ts +49 -8
- package/src/standards/derive.ts +29 -2
- 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.35.
|
|
13
|
+
"version": "5.35.1",
|
|
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.35.
|
|
4
|
+
"version": "5.35.1",
|
|
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.35.
|
|
4
|
+
"version": "5.35.1",
|
|
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.35.
|
|
30
|
+
const ULTRA11Y_PLUGIN_VERSION = "5.35.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.35.
|
|
3
|
+
"version": "5.35.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 join53, 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.35.
|
|
9
|
+
var VERSION = "5.35.1";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
var INAPPLICABLE_STATUS = "C";
|
|
12
12
|
|
|
@@ -54611,8 +54611,24 @@ function derivePackResults(audit2, packKey, pageId) {
|
|
|
54611
54611
|
}
|
|
54612
54612
|
function findingsForStandard(audit2, standard) {
|
|
54613
54613
|
if (standard === CORE_KEY) return audit2.findings;
|
|
54614
|
-
const
|
|
54615
|
-
|
|
54614
|
+
const eligible = new Set(
|
|
54615
|
+
[
|
|
54616
|
+
...audit2.findings,
|
|
54617
|
+
...(audit2.packFindings ?? []).filter((finding) => finding.ruleId.startsWith(`pack:${standard}:`)),
|
|
54618
|
+
...audit2.packAdjudication?.standard === standard ? audit2.packAdjudication.criteria.flatMap((criterion) => criterion.findings) : []
|
|
54619
|
+
].map(findingId)
|
|
54620
|
+
);
|
|
54621
|
+
const seen = /* @__PURE__ */ new Set();
|
|
54622
|
+
const out2 = [];
|
|
54623
|
+
for (const criterion of derivePackResults(audit2, standard)) {
|
|
54624
|
+
for (const finding of criterion.findings) {
|
|
54625
|
+
const id = findingId(finding);
|
|
54626
|
+
if (!eligible.has(id) || seen.has(id)) continue;
|
|
54627
|
+
seen.add(id);
|
|
54628
|
+
out2.push(finding);
|
|
54629
|
+
}
|
|
54630
|
+
}
|
|
54631
|
+
return out2;
|
|
54616
54632
|
}
|
|
54617
54633
|
|
|
54618
54634
|
// src/standards/vocabulary.ts
|
|
@@ -59499,6 +59515,12 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
59499
59515
|
};
|
|
59500
59516
|
const uncovered = (criteriaId) => notFolded.add(criteriaId);
|
|
59501
59517
|
canonicalizeAdjudication(adj);
|
|
59518
|
+
const itemCounts = /* @__PURE__ */ new Map();
|
|
59519
|
+
for (const item of adj.items) itemCounts.set(item.criteriaId, (itemCounts.get(item.criteriaId) ?? 0) + 1);
|
|
59520
|
+
for (const [criteriaId, count] of itemCounts) {
|
|
59521
|
+
if (count > 1)
|
|
59522
|
+
blame(criteriaId, `criterion ${criteriaId}: duplicate criterion id appears ${count} times in the adjudication \u2014 no implicit winner is accepted`);
|
|
59523
|
+
}
|
|
59502
59524
|
const byId2 = new Map(adj.items.map((it) => [it.criteriaId, it]));
|
|
59503
59525
|
const packMode = !isCore(adj.standard);
|
|
59504
59526
|
const open = /* @__PURE__ */ new Set();
|
|
@@ -60415,6 +60437,20 @@ var BATCH_SIZE = 8;
|
|
|
60415
60437
|
var CONCURRENCY = 4;
|
|
60416
60438
|
var MAX_ATTEMPTS = 4;
|
|
60417
60439
|
var MAX_TOKENS = 16e3;
|
|
60440
|
+
function batchWorklist(items, render2, size = BATCH_SIZE) {
|
|
60441
|
+
if (!Number.isInteger(size) || size < 1) throw new Error(`batch size must be a positive integer (got ${size})`);
|
|
60442
|
+
const ids = items.map((item) => item.criteriaId);
|
|
60443
|
+
if (new Set(ids).size !== ids.length) {
|
|
60444
|
+
const duplicate = [...new Set(ids.filter((id, index) => ids.indexOf(id) !== index))];
|
|
60445
|
+
throw new Error(`worklist contains duplicate criterion id(s): ${duplicate.join(", ")}`);
|
|
60446
|
+
}
|
|
60447
|
+
const batches = [];
|
|
60448
|
+
for (let i2 = 0; i2 < items.length; i2 += size) {
|
|
60449
|
+
const slice = items.slice(i2, i2 + size);
|
|
60450
|
+
batches.push({ items: slice, prompt: render2(slice) });
|
|
60451
|
+
}
|
|
60452
|
+
return batches;
|
|
60453
|
+
}
|
|
60418
60454
|
function apiKeyFromEnv() {
|
|
60419
60455
|
const k = process.env.ANTHROPIC_API_KEY?.trim();
|
|
60420
60456
|
return k || void 0;
|
|
@@ -60536,7 +60572,7 @@ function verdictsOf(res) {
|
|
|
60536
60572
|
if (!Array.isArray(input?.verdicts)) throw new Error("ultra11y judge: the model's tool call carried no verdicts array.");
|
|
60537
60573
|
return input.verdicts;
|
|
60538
60574
|
}
|
|
60539
|
-
async function judgeBatch(
|
|
60575
|
+
async function judgeBatch(_items, prompt, opts) {
|
|
60540
60576
|
if (!opts.apiKey) throw new Error("ultra11y judge: the Messages backend needs an API key (ANTHROPIC_API_KEY or --api-key).");
|
|
60541
60577
|
const res = await callOnce(
|
|
60542
60578
|
{
|
|
@@ -60549,8 +60585,22 @@ async function judgeBatch(items, prompt, opts) {
|
|
|
60549
60585
|
},
|
|
60550
60586
|
opts
|
|
60551
60587
|
);
|
|
60552
|
-
|
|
60553
|
-
|
|
60588
|
+
return verdictsOf(res);
|
|
60589
|
+
}
|
|
60590
|
+
function validateBatchVerdicts(items, landed) {
|
|
60591
|
+
const expected = new Set(items.map((item) => item.criteriaId));
|
|
60592
|
+
const counts = /* @__PURE__ */ new Map();
|
|
60593
|
+
for (const verdict of landed) counts.set(verdict.criteriaId, (counts.get(verdict.criteriaId) ?? 0) + 1);
|
|
60594
|
+
const duplicate = new Set([...counts].filter(([, count]) => count > 1).map(([id]) => id));
|
|
60595
|
+
const unknown = [...new Set(landed.map((v) => v.criteriaId).filter((id) => !expected.has(id)))];
|
|
60596
|
+
const accepted = landed.filter((verdict) => expected.has(verdict.criteriaId) && !duplicate.has(verdict.criteriaId));
|
|
60597
|
+
const acceptedIds = new Set(accepted.map((verdict) => verdict.criteriaId));
|
|
60598
|
+
const missing = [...expected].filter((id) => !acceptedIds.has(id) && !duplicate.has(id));
|
|
60599
|
+
const failures = [];
|
|
60600
|
+
if (duplicate.size) failures.push(`duplicate criterion id(s) in batch response: ${[...duplicate].join(", ")}`);
|
|
60601
|
+
if (unknown.length) failures.push(`unknown criterion id(s) in batch response: ${unknown.join(", ")}`);
|
|
60602
|
+
if (missing.length) failures.push(`missing criterion id(s) in batch response: ${missing.join(", ")}`);
|
|
60603
|
+
return { accepted, failures };
|
|
60554
60604
|
}
|
|
60555
60605
|
async function judgeAll(batches, opts) {
|
|
60556
60606
|
const verdicts = [];
|
|
@@ -60564,8 +60614,10 @@ async function judgeAll(batches, opts) {
|
|
|
60564
60614
|
for (let b = queue.shift(); b !== void 0; b = queue.shift()) {
|
|
60565
60615
|
try {
|
|
60566
60616
|
const landed = await backend(b.items, b.prompt, opts);
|
|
60567
|
-
|
|
60568
|
-
|
|
60617
|
+
const checked = validateBatchVerdicts(b.items, landed);
|
|
60618
|
+
failures.push(...checked.failures);
|
|
60619
|
+
verdicts.push(...checked.accepted);
|
|
60620
|
+
if (checked.accepted.length) opts.onVerdicts?.(checked.accepted);
|
|
60569
60621
|
} catch (e) {
|
|
60570
60622
|
failures.push(e instanceof Error ? e.message : String(e));
|
|
60571
60623
|
}
|
|
@@ -60576,7 +60628,9 @@ async function judgeAll(batches, opts) {
|
|
|
60576
60628
|
return { verdicts, failures };
|
|
60577
60629
|
}
|
|
60578
60630
|
function applyRawVerdicts(items, verdicts) {
|
|
60579
|
-
const
|
|
60631
|
+
const counts = /* @__PURE__ */ new Map();
|
|
60632
|
+
for (const verdict of verdicts) counts.set(verdict.criteriaId, (counts.get(verdict.criteriaId) ?? 0) + 1);
|
|
60633
|
+
const byId2 = new Map(verdicts.filter((v) => counts.get(v.criteriaId) === 1).map((v) => [v.criteriaId, v]));
|
|
60580
60634
|
let filled = 0;
|
|
60581
60635
|
for (const item of items) {
|
|
60582
60636
|
const v = byId2.get(item.criteriaId);
|
|
@@ -64249,7 +64303,9 @@ var S = {
|
|
|
64249
64303
|
fr: {
|
|
64250
64304
|
title: "Audit d'accessibilit\xE9 ultra11y",
|
|
64251
64305
|
files: "fichiers",
|
|
64252
|
-
|
|
64306
|
+
coverage: (decided, total) => `${decided}/${total} crit\xE8res tranch\xE9s dans ce run`,
|
|
64307
|
+
remaining: (n) => `${n} \xE0 compl\xE9ter par scan ou adjudication`,
|
|
64308
|
+
provenance: { engine: "moteur", scan: "scan", agent: "agent" },
|
|
64253
64309
|
none: "\u2705 Aucune non-conformit\xE9 d\xE9tect\xE9e par le moteur statique.",
|
|
64254
64310
|
findings: "Non-conformit\xE9s",
|
|
64255
64311
|
severity: "S\xE9v\xE9rit\xE9",
|
|
@@ -64317,7 +64373,9 @@ var S = {
|
|
|
64317
64373
|
en: {
|
|
64318
64374
|
title: "ultra11y accessibility audit",
|
|
64319
64375
|
files: "files",
|
|
64320
|
-
|
|
64376
|
+
coverage: (decided, total) => `${decided}/${total} criteria decided in this run`,
|
|
64377
|
+
remaining: (n) => `${n} still to complete by scan or adjudication`,
|
|
64378
|
+
provenance: { engine: "engine", scan: "scan", agent: "agent" },
|
|
64321
64379
|
none: "\u2705 No non-conformity detected by the static engine.",
|
|
64322
64380
|
findings: "Non-conformities",
|
|
64323
64381
|
severity: "Severity",
|
|
@@ -64439,11 +64497,24 @@ function groupByCriterion(groups) {
|
|
|
64439
64497
|
}
|
|
64440
64498
|
return [...byCriterion.values()].map(({ pageSet, ...c2 }) => ({ ...c2, pages: pageSet.size })).sort((a, b) => SEV_ORDER4.indexOf(a.severity) - SEV_ORDER4.indexOf(b.severity) || b.occurrences - a.occurrences || a.criterion.localeCompare(b.criterion));
|
|
64441
64499
|
}
|
|
64442
|
-
function
|
|
64500
|
+
function runCoverage(result, standard, lang) {
|
|
64443
64501
|
const groups = isCore(standard) ? reportGroups(result, lang) : packReportGroups(result, loadPack(standard), lang);
|
|
64444
64502
|
const { decided, total } = reportCoverage(groups);
|
|
64503
|
+
const decidedRows = groups.flatMap((g) => g.rows).filter((r) => r.status === "C" || r.status === "NC");
|
|
64504
|
+
const by = {
|
|
64505
|
+
engine: decidedRows.filter((r) => !r.decidedBy || r.decidedBy === "engine").length,
|
|
64506
|
+
scan: decidedRows.filter((r) => r.decidedBy === "scan").length,
|
|
64507
|
+
agent: decidedRows.filter((r) => r.decidedBy === "agent").length
|
|
64508
|
+
};
|
|
64509
|
+
const s = S[lang];
|
|
64510
|
+
const provenance = ["engine", "scan", "agent"].filter((key2) => by[key2] > 0).map((key2) => `${by[key2]} ${s.provenance[key2]}`);
|
|
64511
|
+
const remaining = Math.max(0, total - decided);
|
|
64445
64512
|
const agentRuled = groups.some((g) => g.rows.some((r) => r.decidedBy === "agent" && r.status === "C"));
|
|
64446
|
-
return {
|
|
64513
|
+
return {
|
|
64514
|
+
text: s.coverage(decided, total),
|
|
64515
|
+
detail: [...provenance, ...remaining ? [s.remaining(remaining)] : []].join(" \xB7 "),
|
|
64516
|
+
agentRuled
|
|
64517
|
+
};
|
|
64447
64518
|
}
|
|
64448
64519
|
var cell = (v) => v.replace(/\|/g, "\\|");
|
|
64449
64520
|
function criterionTableHead(s) {
|
|
@@ -64482,11 +64553,11 @@ function stepSummary(result, opts = {}) {
|
|
|
64482
64553
|
const lang = opts.lang ?? "en";
|
|
64483
64554
|
const s = S[lang];
|
|
64484
64555
|
const stdLabel = isCore(standard) ? "WCAG 2.2 AA" : loadPack(standard).name;
|
|
64485
|
-
const
|
|
64556
|
+
const coverage = runCoverage(result, standard, lang);
|
|
64486
64557
|
const out2 = [];
|
|
64487
64558
|
out2.push(`## ${s.title} \u2014 ${stdLabel}`, "");
|
|
64488
|
-
out2.push(`\`${result.date}\` \xB7 ${result.scope.files} ${s.files} \xB7 **${
|
|
64489
|
-
if (
|
|
64559
|
+
out2.push(`\`${result.date}\` \xB7 ${result.scope.files} ${s.files} \xB7 **${coverage.text}**${coverage.detail ? ` \xB7 ${coverage.detail}` : ""}`, "");
|
|
64560
|
+
if (coverage.agentRuled) out2.push(`> ${agentMarkNote(lang)}`, "");
|
|
64490
64561
|
const baseDir = opts.baseDir ?? process.cwd();
|
|
64491
64562
|
const all = findingsForStandard(result, standard);
|
|
64492
64563
|
if (!all.length) {
|
|
@@ -64537,15 +64608,15 @@ function prComment(result, opts = {}) {
|
|
|
64537
64608
|
const all = findingsForStandard(result, standard);
|
|
64538
64609
|
const normative = all.filter((f) => !f.advisory);
|
|
64539
64610
|
const blocking = normative.filter((f) => f.severity === "bloquant").length;
|
|
64540
|
-
const
|
|
64611
|
+
const coverage = runCoverage(result, standard, lang);
|
|
64541
64612
|
const grouped = groupFindings(all, standard, lang, baseDir);
|
|
64542
64613
|
const criteria = groupByCriterion(grouped);
|
|
64543
|
-
const orphans =
|
|
64614
|
+
const orphans = all.filter((f) => !f.advisory && !f.page).length;
|
|
64544
64615
|
const head = [];
|
|
64545
64616
|
head.push(`### ${s.title} \u2014 ${stdLabel}`, "");
|
|
64546
64617
|
head.push(blocking ? s.verdictFail(blocking) : normative.length ? s.verdictWarn : s.verdictPass, "");
|
|
64547
|
-
head.push(`\`${result.date}\` \xB7 ${result.scope.files} ${s.files} \xB7 **${
|
|
64548
|
-
if (
|
|
64618
|
+
head.push(`\`${result.date}\` \xB7 ${result.scope.files} ${s.files} \xB7 **${coverage.text}**${coverage.detail ? ` \xB7 ${coverage.detail}` : ""}`, "");
|
|
64619
|
+
if (coverage.agentRuled) head.push(`> ${agentMarkNote(lang)}`, "");
|
|
64549
64620
|
if (orphans) head.push(`> ${s.unattributed(orphans)}`, "");
|
|
64550
64621
|
const tail = [];
|
|
64551
64622
|
if (opts.artifactName) tail.push(s.artifact(opts.artifactName), "");
|
|
@@ -64581,7 +64652,7 @@ function perPageTable(result, standard = CORE2, lang = "en") {
|
|
|
64581
64652
|
"",
|
|
64582
64653
|
...scoreboardTable(result, derived, standard, s, lang),
|
|
64583
64654
|
"",
|
|
64584
|
-
...basisCaveats(result, derived, s, lang),
|
|
64655
|
+
...basisCaveats(result, derived, standard, s, lang),
|
|
64585
64656
|
...fullGridBlock(result, derived, standard, s, lang),
|
|
64586
64657
|
"",
|
|
64587
64658
|
...derived.flatMap((pg) => [...namedCriteriaBlock(result, pg, standard, s, lang), ""])
|
|
@@ -64613,7 +64684,8 @@ function scoreboardTable(result, derived, standard, s, lang) {
|
|
|
64613
64684
|
"| --- | --- | ---: | ---: | ---: | ---: | ---: |"
|
|
64614
64685
|
];
|
|
64615
64686
|
for (const pg of derived) {
|
|
64616
|
-
const
|
|
64687
|
+
const pageFindings = findingsForStandard(pageView(result, pg), standard).filter((f) => !f.advisory);
|
|
64688
|
+
const n = (sev) => pageFindings.filter((f) => f.severity === sev).length;
|
|
64617
64689
|
const t3 = pageTally(pageCriterionRows(result, pg, standard, lang));
|
|
64618
64690
|
out2.push(
|
|
64619
64691
|
`| ${pg.name}${pg.auth ? " \u{1F512}" : ""} \u2014 \`${pg.url}\` | ${basisLabel(pg.basis, lang)} | ${t3.c} | ${t3.nc} | ${n("bloquant")} | ${n("majeur")} | ${n("mineur")} |`
|
|
@@ -64637,12 +64709,10 @@ function undecidedBlock(result, derived, standard, s, lang) {
|
|
|
64637
64709
|
out2.push("");
|
|
64638
64710
|
return out2;
|
|
64639
64711
|
}
|
|
64640
|
-
function
|
|
64641
|
-
return pg.findings.filter((f) => !f.advisory && f.severity === sev).length;
|
|
64642
|
-
}
|
|
64643
|
-
function basisCaveats(result, derived, s, lang) {
|
|
64712
|
+
function basisCaveats(result, derived, standard, s, lang) {
|
|
64644
64713
|
const out2 = [`> ${s.scoreboardNote}`, ""];
|
|
64645
|
-
const
|
|
64714
|
+
const projected = new Set(findingsForStandard(result, standard));
|
|
64715
|
+
const orphans = unattributedFindings(result).filter((f) => projected.has(f) && !f.advisory).length;
|
|
64646
64716
|
if (orphans) out2.push(`> ${s.unattributed(orphans)}`, "");
|
|
64647
64717
|
if (derived.some((p) => p.basis === "attributed")) out2.push(`> ${s.sourceBasis}`, "");
|
|
64648
64718
|
const notAudited = pageBasisWarning("not-audited", lang);
|
|
@@ -64654,9 +64724,11 @@ function pageBlock(result, page, standard, lang, baseDir) {
|
|
|
64654
64724
|
const s = S[lang];
|
|
64655
64725
|
const rows = pageCriterionRows(result, page, standard, lang);
|
|
64656
64726
|
const nc = rows.filter((r) => r.status === "NC");
|
|
64657
|
-
const
|
|
64727
|
+
const pageFindings = findingsForStandard(pageView(result, page), standard).filter((f) => !f.advisory);
|
|
64728
|
+
const occurrences = pageFindings.length;
|
|
64658
64729
|
if (!nc.length && !occurrences) return void 0;
|
|
64659
|
-
const
|
|
64730
|
+
const count = (severity) => pageFindings.filter((f) => f.severity === severity).length;
|
|
64731
|
+
const counts = `\u{1F534} ${count("bloquant")} \xB7 \u{1F7E0} ${count("majeur")} \xB7 \u{1F7E1} ${count("mineur")}`;
|
|
64660
64732
|
const withTests = nc.some((r) => r.tests.length);
|
|
64661
64733
|
const out2 = [
|
|
64662
64734
|
"<details>",
|
|
@@ -64675,12 +64747,7 @@ function pageBlock(result, page, standard, lang, baseDir) {
|
|
|
64675
64747
|
for (const r of nc) {
|
|
64676
64748
|
out2.push(withTests ? `| ${cell(r.label)} | ${r.tests.map((t3) => `\`${t3}\``).join(" ")} |` : `| ${cell(r.label)} |`);
|
|
64677
64749
|
}
|
|
64678
|
-
const defects = groupFindings(
|
|
64679
|
-
page.findings.filter((f) => !f.advisory),
|
|
64680
|
-
standard,
|
|
64681
|
-
lang,
|
|
64682
|
-
baseDir
|
|
64683
|
-
);
|
|
64750
|
+
const defects = groupFindings(pageFindings, standard, lang, baseDir);
|
|
64684
64751
|
const blocking = groupByCriterion(defects.filter((g) => g.severity === "bloquant"));
|
|
64685
64752
|
const rest = groupByCriterion(defects.filter((g) => g.severity !== "bloquant"));
|
|
64686
64753
|
const shownBlocking = Math.min(blocking.length, PAGE_CRITERIA_SHOWN);
|
|
@@ -64717,7 +64784,8 @@ function fullGridBlock(result, derived, standard, s, lang) {
|
|
|
64717
64784
|
return out2;
|
|
64718
64785
|
}
|
|
64719
64786
|
function orphansBlock(result, standard, s, lang, baseDir) {
|
|
64720
|
-
const
|
|
64787
|
+
const projected = new Set(findingsForStandard(result, standard));
|
|
64788
|
+
const orphans = unattributedFindings(result).filter((f) => projected.has(f) && !f.advisory);
|
|
64721
64789
|
if (!orphans.length) return [];
|
|
64722
64790
|
const criteria = groupByCriterion(groupFindings(orphans, standard, lang, baseDir));
|
|
64723
64791
|
const counts = SEV_ORDER4.map((sev) => `${ICON5[sev]} ${orphans.filter((f) => f.severity === sev).length}`).join(" \xB7 ");
|
|
@@ -64774,17 +64842,18 @@ function pagesComment(result, opts = {}) {
|
|
|
64774
64842
|
const derived = derivePages(result, scope);
|
|
64775
64843
|
const normative = findingsForStandard(result, standard).filter((f) => !f.advisory);
|
|
64776
64844
|
const blocking = normative.filter((f) => f.severity === "bloquant").length;
|
|
64777
|
-
const
|
|
64845
|
+
const coverage = runCoverage(result, standard, lang);
|
|
64778
64846
|
head.push(blocking ? s.verdictFail(blocking) : normative.length ? s.verdictWarn : s.verdictPass, "");
|
|
64779
|
-
head.push(`\`${result.date}\` \xB7 ${s.pagesCount(derived.length)} \xB7 **${
|
|
64780
|
-
if (
|
|
64847
|
+
head.push(`\`${result.date}\` \xB7 ${s.pagesCount(derived.length)} \xB7 **${coverage.text}**${coverage.detail ? ` \xB7 ${coverage.detail}` : ""}`, "");
|
|
64848
|
+
if (coverage.agentRuled) head.push(`> ${agentMarkNote(lang)}`, "");
|
|
64849
|
+
const severity = (page, level) => findingsForStandard(pageView(result, page), standard).filter((f) => !f.advisory && f.severity === level).length;
|
|
64781
64850
|
const blocks = [...derived].sort(
|
|
64782
|
-
(a, b) =>
|
|
64851
|
+
(a, b) => severity(b, "bloquant") - severity(a, "bloquant") || severity(b, "majeur") - severity(a, "majeur") || severity(b, "mineur") - severity(a, "mineur")
|
|
64783
64852
|
).map((p) => pageBlock(result, p, standard, lang, baseDir)).filter((b) => b !== void 0);
|
|
64784
64853
|
const assemble = (nBlocks2, nRows2, withGrid = true) => {
|
|
64785
64854
|
const body3 = [...scoreboardTable(result, derived.slice(0, nRows2), standard, s, lang), ""];
|
|
64786
64855
|
if (nRows2 < derived.length) body3.push(s.scoreboardClamped(derived.length - nRows2), "");
|
|
64787
|
-
body3.push(...basisCaveats(result, derived, s, lang));
|
|
64856
|
+
body3.push(...basisCaveats(result, derived, standard, s, lang));
|
|
64788
64857
|
body3.push(...undecidedBlock(result, derived, standard, s, lang));
|
|
64789
64858
|
if (redirected.length) body3.push(...renderRedirected(redirected, lang), "");
|
|
64790
64859
|
body3.push(...orphansBlock(result, standard, s, lang, baseDir), "");
|
|
@@ -71741,11 +71810,7 @@ async function cmdJudge(p) {
|
|
|
71741
71810
|
if (truncated) items = items.slice(0, max);
|
|
71742
71811
|
const size = grain === "criterion" ? 1 : BATCH_SIZE;
|
|
71743
71812
|
const render2 = (slice) => formatAdjudication(slice, lang, standard, { preamble: false, contract: false });
|
|
71744
|
-
const batches =
|
|
71745
|
-
for (let i2 = 0; i2 < items.length; i2 += size) {
|
|
71746
|
-
const slice = items.slice(i2, i2 + size);
|
|
71747
|
-
batches.push({ items: slice, prompt: render2(slice) });
|
|
71748
|
-
}
|
|
71813
|
+
const batches = batchWorklist(items, render2, size);
|
|
71749
71814
|
const explicitModel = typeof p.flags.model === "string" && p.flags.model ? p.flags.model : void 0;
|
|
71750
71815
|
const model = explicitModel ?? (runner === "claude" ? DEFAULT_CLI_MODEL : runner === "api" ? modelFromEnv() : void 0);
|
|
71751
71816
|
const modelLabel = model ?? "Codex account default";
|
|
@@ -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.35.
|
|
6
|
+
version: 5.35.1
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# review-a11y — accessibility review of changed code
|