ultra11y 5.28.0 → 5.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.opencode/plugins/ultra11y.js +1 -1
- package/package.json +2 -1
- package/scripts/build-standards.mjs +42 -4
- package/scripts/ultra11y.mjs +102 -78
- package/skills/review-a11y/SKILL.md +1 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +102 -78
- package/skills/ultra11y/SKILL.md +1 -1
- package/skills/ultra11y/scripts/ultra11y.mjs +102 -78
- package/src/annotate.ts +157 -83
- package/src/types.ts +1 -1
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"name": "ultra11y",
|
|
11
11
|
"source": "./",
|
|
12
12
|
"description": "Automatic accessibility review of the code under change before every commit, push and pull request, plus full WCAG 2.2 AA / RGAA audits and conformance deliverables on demand.",
|
|
13
|
-
"version": "5.
|
|
13
|
+
"version": "5.29.0",
|
|
14
14
|
"author": { "name": "maxgfr" },
|
|
15
15
|
"homepage": "https://github.com/maxgfr/ultra11y",
|
|
16
16
|
"repository": "https://github.com/maxgfr/ultra11y",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultra11y",
|
|
3
3
|
"description": "Accessibility for WCAG 2.2 AA and country standards (RGAA…): an automatic review of the code under change before every commit, push and pull request, plus full audits and conformance deliverables on demand.",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.29.0",
|
|
5
5
|
"author": { "name": "maxgfr" },
|
|
6
6
|
"homepage": "https://github.com/maxgfr/ultra11y",
|
|
7
7
|
"repository": "https://github.com/maxgfr/ultra11y",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultra11y",
|
|
3
3
|
"description": "Accessibility for WCAG 2.2 AA and country standards (RGAA…): an automatic review of the code under change before every commit, push and pull request, plus full audits and conformance deliverables on demand.",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.29.0",
|
|
5
5
|
"author": { "name": "maxgfr", "url": "https://github.com/maxgfr" },
|
|
6
6
|
"homepage": "https://github.com/maxgfr/ultra11y",
|
|
7
7
|
"repository": "https://github.com/maxgfr/ultra11y",
|
|
@@ -27,7 +27,7 @@ import { fileURLToPath } from "node:url";
|
|
|
27
27
|
// Ownership marker: `ultra11y install --opencode` refuses to overwrite a plugin file that
|
|
28
28
|
// does not carry it, so a user's own ultra11y.js is never clobbered.
|
|
29
29
|
const ULTRA11Y_OPENCODE_PLUGIN = "ULTRA11Y_OPENCODE_PLUGIN";
|
|
30
|
-
const ULTRA11Y_PLUGIN_VERSION = "5.
|
|
30
|
+
const ULTRA11Y_PLUGIN_VERSION = "5.29.0";
|
|
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.
|
|
3
|
+
"version": "5.29.0",
|
|
4
4
|
"description": "Audit HTML/CSS/JSX against WCAG 2.2 AA and produce a dated compliance report, or author/review accessible markup (native-HTML-first). A skills.sh agent skill: an install-free static engine (bundled, no npm install) plus the agent's judgment, with check/verify gates against hallucinated non-conformities, and pluggable in-repo standards packs (RGAA France first; Section 508 & EN 301 549 next).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@10.33.0",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"verify:bundle": "node scripts/verify-skill-bundle.mjs",
|
|
58
58
|
"check:build": "tsup && node scripts/copy-bundle.mjs && git diff --exit-code -- scripts/ultra11y.mjs skills/ultra11y/scripts/ultra11y.mjs skills/review-a11y/scripts/ultra11y.mjs dist && node scripts/build-extension-icons.mjs --check && node scripts/verify-skill-bundle.mjs && node scripts/sync-engine.mjs --check",
|
|
59
59
|
"build:wcag": "node scripts/build-standards.mjs",
|
|
60
|
+
"check:standards": "node scripts/build-standards.mjs --check",
|
|
60
61
|
"build:pack:rgaa": "node scripts/build-pack-rgaa.mjs",
|
|
61
62
|
"check:pack:rgaa": "node scripts/build-pack-rgaa.mjs --check",
|
|
62
63
|
"build:guidance:wcag": "node scripts/build-guidance-wcag.mjs",
|
|
@@ -42,6 +42,29 @@ function biomeFormat(text, relPath) {
|
|
|
42
42
|
return execFileSync(BIOME, ["format", `--stdin-file-path=${relPath}`], { input: text, encoding: "utf8" });
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
// CHECK MODE — the gate this generator was the only one of the three to lack.
|
|
46
|
+
//
|
|
47
|
+
// scripts/build-pack-rgaa.mjs and scripts/build-guidance-wcag.mjs both compare their
|
|
48
|
+
// generated text to the committed file and exit 1 on drift; this one only ever wrote. So a
|
|
49
|
+
// hand edit to src/data/wcag.json passed every gate and was found by the NIGHTLY refresh
|
|
50
|
+
// instead — on main, a day later, in a workflow nobody watches. Measured: SC 1.3.1 carried
|
|
51
|
+
// three presentational-* rules that RULE_SC_COVERAGE below never cited, and
|
|
52
|
+
// standards-refresh went red every night for two days before anyone read the log.
|
|
53
|
+
//
|
|
54
|
+
// Every write goes through `emit` so the check compares EXACTLY what a build would write —
|
|
55
|
+
// a check that recomputed the text a second way could agree with itself and still be wrong.
|
|
56
|
+
const CHECK = process.argv.includes("--check");
|
|
57
|
+
const drift = [];
|
|
58
|
+
function emit(relPath, text) {
|
|
59
|
+
const abs = join(root, relPath);
|
|
60
|
+
if (CHECK) {
|
|
61
|
+
if (!existsSync(abs) || readFileSync(abs, "utf8") !== text) drift.push(relPath);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
65
|
+
writeFileSync(abs, text);
|
|
66
|
+
}
|
|
67
|
+
|
|
45
68
|
// ---------------------------------------------------------------------------
|
|
46
69
|
// Refresh mode: parse a w3c/wcag checkout into the vendored SC snapshot.
|
|
47
70
|
// SC numbers are positional within guidelines/index.html (principle → guideline →
|
|
@@ -623,6 +646,7 @@ const RULE_SC_COVERAGE = {
|
|
|
623
646
|
"sortable-header-no-aria-sort", "nav-landmark-missing", "nav-landmark-unnamed", "aria-required-parent",
|
|
624
647
|
"headers-attr-dangling", "th-no-data-cells",
|
|
625
648
|
"radio-checkbox-group-ungrouped", "table-empty-data-cell", "css-generated-content-informative",
|
|
649
|
+
"presentational-element", "presentational-attribute", "presentational-spacing",
|
|
626
650
|
],
|
|
627
651
|
"1.3.5": ["field-purpose-incomplete", "autocomplete-token-invalid"],
|
|
628
652
|
"1.4.2": ["autoplay-media"],
|
|
@@ -708,8 +732,7 @@ function buildUniverse(shipped) {
|
|
|
708
732
|
provenance: univ.provenance,
|
|
709
733
|
criteria,
|
|
710
734
|
};
|
|
711
|
-
|
|
712
|
-
writeFileSync(join(DATA, "wcag-universe.json"), biomeFormat(JSON.stringify(out, null, 2) + "\n", "src/data/wcag-universe.json"));
|
|
735
|
+
emit("src/data/wcag-universe.json", biomeFormat(JSON.stringify(out, null, 2) + "\n", "src/data/wcag-universe.json"));
|
|
713
736
|
|
|
714
737
|
const coreIds = new Set(criteria.filter((c) => c.status === "core-AA").map((c) => c.id));
|
|
715
738
|
const shippedIds = new Set(shipped.map((c) => c.sc));
|
|
@@ -724,6 +747,7 @@ function buildUniverse(shipped) {
|
|
|
724
747
|
}
|
|
725
748
|
const tally = { "core-AA": 0, "out-of-core": 0, removed: 0 };
|
|
726
749
|
for (const c of criteria) tally[c.status]++;
|
|
750
|
+
if (CHECK) return;
|
|
727
751
|
console.log(
|
|
728
752
|
`build-standards: ${criteria.length} WCAG 2.x SCs classified — core-AA ${tally["core-AA"]}, out-of-core ${tally["out-of-core"]}, removed ${tally.removed} → src/data/wcag-universe.json`,
|
|
729
753
|
);
|
|
@@ -839,8 +863,7 @@ function build() {
|
|
|
839
863
|
// resolve in either language off one list.
|
|
840
864
|
glossaryFr: frGlossary,
|
|
841
865
|
};
|
|
842
|
-
|
|
843
|
-
writeFileSync(join(DATA, "wcag.json"), biomeFormat(JSON.stringify(out, null, 2) + "\n", "src/data/wcag.json"));
|
|
866
|
+
emit("src/data/wcag.json", biomeFormat(JSON.stringify(out, null, 2) + "\n", "src/data/wcag.json"));
|
|
844
867
|
|
|
845
868
|
// --- guards
|
|
846
869
|
const all = new Set(criteria.map((c) => c.sc));
|
|
@@ -863,6 +886,7 @@ function build() {
|
|
|
863
886
|
|
|
864
887
|
const tally = { static: 0, "needs-rendering": 0, judgment: 0 };
|
|
865
888
|
for (const c of criteria) tally[c.automatability]++;
|
|
889
|
+
if (CHECK) return void buildUniverse(criteria);
|
|
866
890
|
console.log(`build-standards: ${criteria.length} WCAG 2.2 A/AA criteria → src/data/wcag.json`);
|
|
867
891
|
console.log(`build-standards: automatability — static ${tally.static}, needs-rendering ${tally["needs-rendering"]}, judgment ${tally.judgment}`);
|
|
868
892
|
console.log(`build-standards: seeded techniques from ${sources.length ? sources.map((s) => s.replace(root + "/", "")).join(", ") : "(no pack found — empty)"}`);
|
|
@@ -872,12 +896,26 @@ function build() {
|
|
|
872
896
|
|
|
873
897
|
async function main() {
|
|
874
898
|
const refreshIdx = process.argv.indexOf("--refresh");
|
|
899
|
+
// A check that first goes and fetches is not a check: it would compare the committed
|
|
900
|
+
// dataset against a source that moved under it, and call an upstream change a local drift.
|
|
901
|
+
// `--check` reads the vendored snapshots and nothing else.
|
|
902
|
+
const refreshing = refreshIdx !== -1 || process.argv.some((a) => a.startsWith("--refresh-"));
|
|
903
|
+
if (CHECK && refreshing) {
|
|
904
|
+
console.error("build-standards --check: --check is offline by construction; it cannot be combined with a --refresh flag.");
|
|
905
|
+
process.exit(1);
|
|
906
|
+
}
|
|
875
907
|
if (refreshIdx !== -1) deriveSnapshot(process.argv[refreshIdx + 1]);
|
|
876
908
|
if (process.argv.includes("--refresh-universe")) await deriveUniverse();
|
|
877
909
|
if (process.argv.includes("--refresh-core")) refreshCore();
|
|
878
910
|
if (process.argv.includes("--refresh-fr")) await deriveFr();
|
|
879
911
|
if (process.argv.includes("--refresh-text")) await deriveText();
|
|
880
912
|
build();
|
|
913
|
+
if (!CHECK) return;
|
|
914
|
+
if (drift.length) {
|
|
915
|
+
console.error(`build-standards --check: OUT OF DATE vs the vendored source — re-run \`pnpm run build:wcag\`: ${drift.join(", ")}`);
|
|
916
|
+
process.exit(1);
|
|
917
|
+
}
|
|
918
|
+
console.log("build-standards --check: src/data/wcag.json and src/data/wcag-universe.json match the vendored source.");
|
|
881
919
|
}
|
|
882
920
|
|
|
883
921
|
main().catch((e) => {
|
package/scripts/ultra11y.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { join as join51, relative as relative5, resolve as resolve16, sep as sep
|
|
|
6
6
|
import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
|
|
7
7
|
|
|
8
8
|
// src/types.ts
|
|
9
|
-
var VERSION = "5.
|
|
9
|
+
var VERSION = "5.29.0";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
var INAPPLICABLE_STATUS = "C";
|
|
12
12
|
|
|
@@ -47016,14 +47016,14 @@ var tableEmptyDataCell = {
|
|
|
47016
47016
|
const out2 = [];
|
|
47017
47017
|
for (const t3 of doc.elements) {
|
|
47018
47018
|
if (t3.tag !== "table" || isLayoutTable(t3)) continue;
|
|
47019
|
-
for (const
|
|
47020
|
-
if (
|
|
47021
|
-
if (mayInjectContent(
|
|
47022
|
-
if (
|
|
47023
|
-
if ((attr(
|
|
47024
|
-
const txt = visibleText(
|
|
47019
|
+
for (const cell2 of descendants(t3)) {
|
|
47020
|
+
if (cell2.tag !== "td") continue;
|
|
47021
|
+
if (mayInjectContent(cell2)) continue;
|
|
47022
|
+
if (cell2.children.some((c2) => c2.type === "element")) continue;
|
|
47023
|
+
if ((attr(cell2, "aria-label") ?? "").trim() || hasAttr(cell2, "aria-labelledby")) continue;
|
|
47024
|
+
const txt = visibleText(cell2);
|
|
47025
47025
|
if (txt !== "" && txt !== "-") continue;
|
|
47026
|
-
out2.push({ criteriaId: "1.3.1", el:
|
|
47026
|
+
out2.push({ criteriaId: "1.3.1", el: cell2, msgId: "table-empty-data-cell", advisory: true });
|
|
47027
47027
|
}
|
|
47028
47028
|
}
|
|
47029
47029
|
return out2;
|
|
@@ -63298,8 +63298,9 @@ var S = {
|
|
|
63298
63298
|
criterion: "Crit\xE8re",
|
|
63299
63299
|
where: "Emplacement",
|
|
63300
63300
|
what: "Constat",
|
|
63301
|
-
|
|
63302
|
-
|
|
63301
|
+
moreCriteria: (n) => `\u2026 et ${n} autre(s) crit\xE8re(s).`,
|
|
63302
|
+
moreDefects: (n) => `\u2026 et ${n} autre(s) d\xE9faut(s) distinct(s) sur ce crit\xE8re \u2014 voir le rapport de l'artefact.`,
|
|
63303
|
+
moreGroups: (n) => `\u2026 et ${n} autre(s) crit\xE8re(s) \u2014 voir le r\xE9sum\xE9 de job.`,
|
|
63303
63304
|
perPage: "Bilan page par page",
|
|
63304
63305
|
pageCriteriaTitle: (name2) => `${name2} \u2014 le d\xE9tail crit\xE8re par crit\xE8re`,
|
|
63305
63306
|
conformingList: "Conformes",
|
|
@@ -63314,14 +63315,16 @@ var S = {
|
|
|
63314
63315
|
source: "source",
|
|
63315
63316
|
occurrences: "Occ.",
|
|
63316
63317
|
pagesCol: "Pages",
|
|
63317
|
-
|
|
63318
|
-
|
|
63318
|
+
defectsCol: "D\xE9fauts",
|
|
63319
|
+
byCriterion: (criteria, defects, occ) => `${criteria} crit\xE8re(s) \xB7 ${defects} d\xE9faut(s) distinct(s) \xB7 ${occ} occurrence(s)`,
|
|
63320
|
+
criterionDefects: (n) => `${n} d\xE9faut(s) distinct(s)`,
|
|
63321
|
+
criterionNote: "Une ligne par crit\xE8re du r\xE9f\xE9rentiel \u2014 d\xE9pliez-en un pour ses d\xE9fauts distincts (r\xE8gle, s\xE9lecteur, emplacement). Un m\xEAme d\xE9faut de design system r\xE9p\xE9t\xE9 sur toutes les routes compte pour un ; les colonnes Occ. et Pages disent l'ampleur r\xE9elle.",
|
|
63319
63322
|
verdictFail: (n) => `\u{1F534} ${n} non-conformit\xE9(s) bloquante(s) \u2014 la porte est rouge.`,
|
|
63320
63323
|
verdictWarn: "\u{1F7E0} Aucune non-conformit\xE9 bloquante ; des constats majeurs ou mineurs restent \xE0 traiter.",
|
|
63321
63324
|
verdictPass: "\u2705 Aucune non-conformit\xE9 relev\xE9e par le moteur statique.",
|
|
63322
63325
|
artifact: (name2) => `Rapport complet (HTML, captures annot\xE9es) : artefact **${name2}** du run.`,
|
|
63323
63326
|
runLink: (url) => `[Voir le run et son r\xE9sum\xE9 de job](${url})`,
|
|
63324
|
-
clamped: (n) => `_${n}
|
|
63327
|
+
clamped: (n) => `_${n} crit\xE8re(s) retir\xE9(s) de ce commentaire pour tenir dans la limite de GitHub \u2014 le r\xE9sum\xE9 de job les porte tous._`,
|
|
63325
63328
|
sectionsDropped: (names) => `_Sections retir\xE9es de ce commentaire pour tenir dans la limite de GitHub (64 Kio), en entier et jamais tronqu\xE9es : ${names.map((n) => `**${n}**`).join(" \xB7 ")}. Elles sont dans le rapport de l'artefact, \xE0 l'identique._`,
|
|
63326
63329
|
unanchored: (n) => `${n} constat(s) rattach\xE9(s) \xE0 une URL, sans ligne de code \xE0 annoter \u2014 voir le rapport.`,
|
|
63327
63330
|
unattributed: (n) => `${n} constat(s) ne sont rattach\xE9s \xE0 aucune page (code partag\xE9, fichier hors routes) \u2014 compt\xE9s dans l'audit global, jamais r\xE9partis d'office.`,
|
|
@@ -63347,10 +63350,10 @@ var S = {
|
|
|
63347
63350
|
pageDefects: "D\xE9fauts",
|
|
63348
63351
|
fullGrid: "Grille compl\xE8te \u2014 chaque crit\xE8re du r\xE9f\xE9rentiel, page par page",
|
|
63349
63352
|
allDefects: "D\xE9fauts distincts \u2014 o\xF9 corriger",
|
|
63350
|
-
allDefectsNote: "
|
|
63353
|
+
allDefectsNote: "Group\xE9s par crit\xE8re : un d\xE9faut distinct = une (r\xE8gle, s\xE9lecteur), les occurrences r\xE9p\xE9t\xE9es sont repli\xE9es, et un crit\xE8re n'appara\xEEt qu'une fois. C'est la moiti\xE9 \xAB actionnable \xBB du digest, reprise ici pour que ce commentaire soit le seul \xE0 lire.",
|
|
63351
63354
|
gridLegend: "`C` conforme \xB7 `NC` non conforme \xB7 `\u2014` non applicable \xB7 `?` \xE0 \xE9valuer",
|
|
63352
63355
|
gridDropped: "_La grille compl\xE8te ne tient pas dans un commentaire GitHub (64 Kio) \u2014 elle est dans la fiche par page du livrable._",
|
|
63353
|
-
|
|
63356
|
+
pageMoreCriteria: (n) => `_\u2026 et ${n} autre(s) crit\xE8re(s) non conforme(s) sur cette page \u2014 voir la fiche de page dans l'artefact._`,
|
|
63354
63357
|
noCriterionForFindings: (n) => `${n} constat(s) sur cette page ne rendent aucun crit\xE8re du r\xE9f\xE9rentiel non conforme : leur r\xE8gle sort du p\xE9rim\xE8tre d'application de chacun. Ils comptent dans les colonnes ci-dessus, et sont d\xE9taill\xE9s dans l'artefact.`
|
|
63355
63358
|
},
|
|
63356
63359
|
en: {
|
|
@@ -63363,8 +63366,9 @@ var S = {
|
|
|
63363
63366
|
criterion: "Criterion",
|
|
63364
63367
|
where: "Location",
|
|
63365
63368
|
what: "Finding",
|
|
63366
|
-
|
|
63367
|
-
|
|
63369
|
+
moreCriteria: (n) => `\u2026 and ${n} more criterion(ia).`,
|
|
63370
|
+
moreDefects: (n) => `\u2026 and ${n} more distinct defect(s) on this criterion \u2014 see the artifact's report.`,
|
|
63371
|
+
moreGroups: (n) => `\u2026 and ${n} more criterion(ia) \u2014 see the job summary.`,
|
|
63368
63372
|
perPage: "Page-by-page scoreboard",
|
|
63369
63373
|
pageCriteriaTitle: (name2) => `${name2} \u2014 criterion by criterion`,
|
|
63370
63374
|
conformingList: "Conforming",
|
|
@@ -63379,14 +63383,16 @@ var S = {
|
|
|
63379
63383
|
source: "source",
|
|
63380
63384
|
occurrences: "Occ.",
|
|
63381
63385
|
pagesCol: "Pages",
|
|
63382
|
-
|
|
63383
|
-
|
|
63386
|
+
defectsCol: "Defects",
|
|
63387
|
+
byCriterion: (criteria, defects, occ) => `${criteria} criterion(ia) \xB7 ${defects} distinct defect(s) \xB7 ${occ} occurrence(s)`,
|
|
63388
|
+
criterionDefects: (n) => `${n} distinct defect(s)`,
|
|
63389
|
+
criterionNote: "One row per criterion of the standard \u2014 unfold one for its distinct defects (rule, selector, location). One design-system defect repeated across every route counts once; the Occ. and Pages columns carry the real scale.",
|
|
63384
63390
|
verdictFail: (n) => `\u{1F534} ${n} blocking non-conformity(ies) \u2014 the gate is red.`,
|
|
63385
63391
|
verdictWarn: "\u{1F7E0} No blocking non-conformity; major or minor findings remain.",
|
|
63386
63392
|
verdictPass: "\u2705 No non-conformity found by the static engine.",
|
|
63387
63393
|
artifact: (name2) => `Full report (HTML, annotated crops): artifact **${name2}** of this run.`,
|
|
63388
63394
|
runLink: (url) => `[See the run and its job summary](${url})`,
|
|
63389
|
-
clamped: (n) => `_${n}
|
|
63395
|
+
clamped: (n) => `_${n} criterion(ia) dropped from this comment to fit GitHub's limit \u2014 the job summary carries them all._`,
|
|
63390
63396
|
sectionsDropped: (names) => `_Sections dropped from this comment to fit GitHub's 64 KiB limit, whole and never truncated: ${names.map((n) => `**${n}**`).join(" \xB7 ")}. They are in the artifact's report, identical._`,
|
|
63391
63397
|
unanchored: (n) => `${n} finding(s) keyed to a URL, with no code line to annotate \u2014 see the report.`,
|
|
63392
63398
|
unattributed: (n) => `${n} finding(s) are attributed to no page (shared code, file outside any route) \u2014 counted in the overall audit, never spread across pages.`,
|
|
@@ -63412,14 +63418,15 @@ var S = {
|
|
|
63412
63418
|
pageDefects: "Defects",
|
|
63413
63419
|
fullGrid: "Full grid \u2014 every criterion of the standard, page by page",
|
|
63414
63420
|
allDefects: "Distinct defects \u2014 where to fix",
|
|
63415
|
-
allDefectsNote: "
|
|
63421
|
+
allDefectsNote: "Grouped by criterion: one distinct defect = one (rule, selector), repeated occurrences are folded, and a criterion appears once. This is the digest's actionable half, carried here so this comment is the only one to read.",
|
|
63416
63422
|
gridLegend: "`C` conforming \xB7 `NC` non-conforming \xB7 `\u2014` not applicable \xB7 `?` to assess",
|
|
63417
63423
|
gridDropped: "_The full grid does not fit in a GitHub comment (64 KiB) \u2014 it is in the deliverable's per-page sheet._",
|
|
63418
|
-
|
|
63424
|
+
pageMoreCriteria: (n) => `_\u2026 and ${n} more non-conforming criterion(ia) on this page \u2014 see its sheet in the artifact._`,
|
|
63419
63425
|
noCriterionForFindings: (n) => `${n} finding(s) on this page make no criterion of the standard non-conforming: their rule falls outside every criterion's applicability. They are counted in the columns above, and detailed in the artifact.`
|
|
63420
63426
|
}
|
|
63421
63427
|
};
|
|
63422
63428
|
var MAX_ROWS = 50;
|
|
63429
|
+
var MAX_DEFECTS_PER_CRITERION = 20;
|
|
63423
63430
|
var COMMENT_ROWS = 10;
|
|
63424
63431
|
var COMMENT_LIMIT = 65536;
|
|
63425
63432
|
var BYTES = new TextEncoder();
|
|
@@ -63449,7 +63456,29 @@ function groupFindings(findings, standard, lang, baseDir) {
|
|
|
63449
63456
|
pageSet: new Set(f.page ? [f.page] : [])
|
|
63450
63457
|
});
|
|
63451
63458
|
}
|
|
63452
|
-
return [...groups.values()].map(({ pageSet, ...g }) => ({ ...g, 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));
|
|
63459
|
+
return [...groups.values()].map(({ pageSet, ...g }) => ({ ...g, pages: pageSet.size, pageIds: [...pageSet].sort() })).sort((a, b) => SEV_ORDER4.indexOf(a.severity) - SEV_ORDER4.indexOf(b.severity) || b.occurrences - a.occurrences || a.criterion.localeCompare(b.criterion));
|
|
63460
|
+
}
|
|
63461
|
+
function groupByCriterion(groups) {
|
|
63462
|
+
const byCriterion = /* @__PURE__ */ new Map();
|
|
63463
|
+
for (const g of groups) {
|
|
63464
|
+
const c2 = byCriterion.get(g.criterion);
|
|
63465
|
+
if (c2) {
|
|
63466
|
+
c2.defects.push(g);
|
|
63467
|
+
c2.occurrences += g.occurrences;
|
|
63468
|
+
for (const p of g.pageIds) c2.pageSet.add(p);
|
|
63469
|
+
if (SEV_ORDER4.indexOf(g.severity) < SEV_ORDER4.indexOf(c2.severity)) c2.severity = g.severity;
|
|
63470
|
+
continue;
|
|
63471
|
+
}
|
|
63472
|
+
byCriterion.set(g.criterion, {
|
|
63473
|
+
criterion: g.criterion,
|
|
63474
|
+
severity: g.severity,
|
|
63475
|
+
defects: [g],
|
|
63476
|
+
occurrences: g.occurrences,
|
|
63477
|
+
pages: 0,
|
|
63478
|
+
pageSet: new Set(g.pageIds)
|
|
63479
|
+
});
|
|
63480
|
+
}
|
|
63481
|
+
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));
|
|
63453
63482
|
}
|
|
63454
63483
|
function runRate(result, standard, lang) {
|
|
63455
63484
|
const groups = isCore(standard) ? reportGroups(result, lang) : packReportGroups(result, loadPack(standard), lang);
|
|
@@ -63457,15 +63486,37 @@ function runRate(result, standard, lang) {
|
|
|
63457
63486
|
const agentRuled = groups.some((g) => g.rows.some((r) => r.decidedBy === "agent" && r.status === "C"));
|
|
63458
63487
|
return { text: `${formatRate(decided === 0 ? null : result.conformancePct, decided, total)}${agentRuled ? "*" : ""}`, agentRuled };
|
|
63459
63488
|
}
|
|
63460
|
-
|
|
63461
|
-
|
|
63489
|
+
var cell = (v) => v.replace(/\|/g, "\\|");
|
|
63490
|
+
function criterionTableHead(s) {
|
|
63491
|
+
return [`| ${s.severity} | ${s.criterion} | ${s.defectsCol} | ${s.occurrences} | ${s.pagesCol} |`, "| --- | --- | ---: | ---: | ---: |"];
|
|
63462
63492
|
}
|
|
63463
|
-
function
|
|
63464
|
-
|
|
63465
|
-
return `| ${ICON5[g.severity]} ${g.severity} | ${cell(g.criterion)} | \`${cell(g.where)}\` (\`${cell(g.selectorHint)}\`) | ${cell(mdText(g.message))} | ${g.occurrences} | ${g.pages || "\u2014"} |`;
|
|
63493
|
+
function criterionRow(c2) {
|
|
63494
|
+
return `| ${ICON5[c2.severity]} ${c2.severity} | ${cell(c2.criterion)} | ${c2.defects.length} | ${c2.occurrences} | ${c2.pages || "\u2014"} |`;
|
|
63466
63495
|
}
|
|
63467
|
-
function
|
|
63468
|
-
return [
|
|
63496
|
+
function defectRow(g) {
|
|
63497
|
+
return `| ${ICON5[g.severity]} ${g.severity} | \`${cell(g.where)}\` (\`${cell(g.selectorHint)}\`) | ${cell(mdText(g.message))} | ${g.occurrences} | ${g.pages || "\u2014"} |`;
|
|
63498
|
+
}
|
|
63499
|
+
function criterionDetails(c2, s) {
|
|
63500
|
+
const out2 = [
|
|
63501
|
+
"<details>",
|
|
63502
|
+
`<summary><b>${cell(c2.criterion)}</b> \u2014 ${s.criterionDefects(c2.defects.length)}</summary>`,
|
|
63503
|
+
// GFM only renders Markdown inside <details> after a blank line — at EVERY level of
|
|
63504
|
+
// nesting, and these folds sit inside the page and digest folds.
|
|
63505
|
+
"",
|
|
63506
|
+
`| ${s.severity} | ${s.where} | ${s.what} | ${s.occurrences} | ${s.pagesCol} |`,
|
|
63507
|
+
"| --- | --- | --- | ---: | ---: |",
|
|
63508
|
+
...c2.defects.slice(0, MAX_DEFECTS_PER_CRITERION).map(defectRow)
|
|
63509
|
+
];
|
|
63510
|
+
if (c2.defects.length > MAX_DEFECTS_PER_CRITERION) out2.push("", s.moreDefects(c2.defects.length - MAX_DEFECTS_PER_CRITERION));
|
|
63511
|
+
out2.push("", "</details>");
|
|
63512
|
+
return out2;
|
|
63513
|
+
}
|
|
63514
|
+
function criterionSection(criteria, s, maxRows, more) {
|
|
63515
|
+
const shown = criteria.slice(0, maxRows);
|
|
63516
|
+
const out2 = [...criterionTableHead(s), ...shown.map(criterionRow)];
|
|
63517
|
+
if (criteria.length > maxRows) out2.push("", more(criteria.length - maxRows));
|
|
63518
|
+
for (const c2 of shown) out2.push("", ...criterionDetails(c2, s));
|
|
63519
|
+
return out2;
|
|
63469
63520
|
}
|
|
63470
63521
|
function stepSummary(result, opts = {}) {
|
|
63471
63522
|
const standard = opts.standard ?? CORE2;
|
|
@@ -63485,10 +63536,10 @@ function stepSummary(result, opts = {}) {
|
|
|
63485
63536
|
return out2.join("\n");
|
|
63486
63537
|
}
|
|
63487
63538
|
const grouped = groupFindings(all, standard, lang, baseDir);
|
|
63488
|
-
|
|
63489
|
-
out2.push(
|
|
63490
|
-
out2.push(
|
|
63491
|
-
|
|
63539
|
+
const criteria = groupByCriterion(grouped);
|
|
63540
|
+
out2.push(`### ${s.findings} \u2014 ${s.byCriterion(criteria.length, grouped.length, all.length)}`, "");
|
|
63541
|
+
out2.push(`> ${s.criterionNote}`, "");
|
|
63542
|
+
out2.push(...criterionSection(criteria, s, MAX_ROWS, s.moreCriteria));
|
|
63492
63543
|
out2.push("");
|
|
63493
63544
|
const unanchored = all.filter((f) => isUrl2(f.file)).length;
|
|
63494
63545
|
if (unanchored) out2.push(`> ${s.unanchored(unanchored)}`, "");
|
|
@@ -63529,6 +63580,7 @@ function prComment(result, opts = {}) {
|
|
|
63529
63580
|
const blocking = normative.filter((f) => f.severity === "bloquant").length;
|
|
63530
63581
|
const rate = runRate(result, standard, lang);
|
|
63531
63582
|
const grouped = groupFindings(all, standard, lang, baseDir);
|
|
63583
|
+
const criteria = groupByCriterion(grouped);
|
|
63532
63584
|
const orphans = unattributedFindings(result).filter((f) => !f.advisory).length;
|
|
63533
63585
|
const head = [];
|
|
63534
63586
|
head.push(`### ${s.title} \u2014 ${stdLabel}`, "");
|
|
@@ -63547,15 +63599,15 @@ function prComment(result, opts = {}) {
|
|
|
63547
63599
|
if (sizeOf(assembled) <= COMMENT_LIMIT) return assembled;
|
|
63548
63600
|
const assemble = (rows2) => {
|
|
63549
63601
|
const digest = [];
|
|
63550
|
-
if (
|
|
63551
|
-
digest.push(s.
|
|
63552
|
-
digest.push(...
|
|
63553
|
-
const omitted =
|
|
63602
|
+
if (criteria.length) {
|
|
63603
|
+
digest.push(s.byCriterion(criteria.length, grouped.length, all.length), "");
|
|
63604
|
+
digest.push(...criterionSection(criteria.slice(0, rows2), s, rows2, s.moreCriteria), "");
|
|
63605
|
+
const omitted = criteria.length - rows2;
|
|
63554
63606
|
if (omitted > 0) digest.push(rows2 < COMMENT_ROWS ? s.clamped(omitted) : s.moreGroups(omitted), "");
|
|
63555
63607
|
}
|
|
63556
63608
|
return [...head, ...digest, ...tail].join("\n").trimEnd();
|
|
63557
63609
|
};
|
|
63558
|
-
let rows = Math.min(COMMENT_ROWS,
|
|
63610
|
+
let rows = Math.min(COMMENT_ROWS, criteria.length);
|
|
63559
63611
|
while (rows > 0 && sizeOf(assemble(rows)) > COMMENT_LIMIT) rows--;
|
|
63560
63612
|
return assemble(rows);
|
|
63561
63613
|
}
|
|
@@ -63638,14 +63690,13 @@ function basisCaveats(result, derived, s, lang) {
|
|
|
63638
63690
|
if (notAudited && derived.some((p) => p.basis === "not-audited")) out2.push(`> ${notAudited}`, "");
|
|
63639
63691
|
return out2;
|
|
63640
63692
|
}
|
|
63641
|
-
var
|
|
63693
|
+
var PAGE_CRITERIA_SHOWN = 6;
|
|
63642
63694
|
function pageBlock(result, page, standard, lang, baseDir) {
|
|
63643
63695
|
const s = S[lang];
|
|
63644
63696
|
const rows = pageCriterionRows(result, page, standard, lang);
|
|
63645
63697
|
const nc = rows.filter((r) => r.status === "NC");
|
|
63646
63698
|
const occurrences = page.findings.filter((f) => !f.advisory).length;
|
|
63647
63699
|
if (!nc.length && !occurrences) return void 0;
|
|
63648
|
-
const cell = (v) => v.replace(/\|/g, "\\|");
|
|
63649
63700
|
const counts = `\u{1F534} ${severityCount(page, "bloquant")} \xB7 \u{1F7E0} ${severityCount(page, "majeur")} \xB7 \u{1F7E1} ${severityCount(page, "mineur")}`;
|
|
63650
63701
|
const withTests = nc.some((r) => r.tests.length);
|
|
63651
63702
|
const out2 = [
|
|
@@ -63671,24 +63722,13 @@ function pageBlock(result, page, standard, lang, baseDir) {
|
|
|
63671
63722
|
lang,
|
|
63672
63723
|
baseDir
|
|
63673
63724
|
);
|
|
63674
|
-
const blocking = defects.filter((g) => g.severity === "bloquant");
|
|
63675
|
-
const rest = defects.filter((g) => g.severity !== "bloquant");
|
|
63676
|
-
const shownBlocking = Math.min(blocking.length,
|
|
63677
|
-
const shownRest = Math.min(rest.length, Math.max(0,
|
|
63725
|
+
const blocking = groupByCriterion(defects.filter((g) => g.severity === "bloquant"));
|
|
63726
|
+
const rest = groupByCriterion(defects.filter((g) => g.severity !== "bloquant"));
|
|
63727
|
+
const shownBlocking = Math.min(blocking.length, PAGE_CRITERIA_SHOWN);
|
|
63728
|
+
const shownRest = Math.min(rest.length, Math.max(0, PAGE_CRITERIA_SHOWN - shownBlocking));
|
|
63678
63729
|
const half = (rows2, shown, title2) => {
|
|
63679
63730
|
if (!rows2.length) return [];
|
|
63680
|
-
|
|
63681
|
-
"",
|
|
63682
|
-
`**${title2}**`,
|
|
63683
|
-
"",
|
|
63684
|
-
`| ${s.severity} | ${s.where} | ${s.what} | ${s.occurrences} |`,
|
|
63685
|
-
"| --- | --- | --- | ---: |",
|
|
63686
|
-
...rows2.slice(0, shown).map(
|
|
63687
|
-
(g) => `| ${ICON5[g.severity]} ${g.severity} | \`${cell(g.where)}\` (\`${cell(g.selectorHint)}\`) | ${cell(mdText(g.message))} | ${g.occurrences} |`
|
|
63688
|
-
)
|
|
63689
|
-
];
|
|
63690
|
-
if (rows2.length > shown) lines.push("", s.pageMoreDefects(rows2.length - shown));
|
|
63691
|
-
return lines;
|
|
63731
|
+
return ["", `**${title2}**`, "", ...criterionSection(rows2, s, shown, s.pageMoreCriteria)];
|
|
63692
63732
|
};
|
|
63693
63733
|
out2.push(...half(blocking, shownBlocking, s.blockingNc), ...half(rest, shownRest, s.nonBlockingNc));
|
|
63694
63734
|
out2.push("", "</details>");
|
|
@@ -63697,7 +63737,6 @@ function pageBlock(result, page, standard, lang, baseDir) {
|
|
|
63697
63737
|
function fullGridBlock(result, derived, standard, s, lang) {
|
|
63698
63738
|
const { rows, status } = pageGridModel(result, derived, standard, lang);
|
|
63699
63739
|
if (!rows.length || !derived.length) return [];
|
|
63700
|
-
const cell = (v) => v.replace(/\|/g, "\\|");
|
|
63701
63740
|
const origin = commonOrigin(derived);
|
|
63702
63741
|
const originNote = pageOriginNote(origin, lang);
|
|
63703
63742
|
const head = [s.criterion, ...derived.map((p) => cell(pageColumnLabel(p, origin)))];
|
|
@@ -63721,8 +63760,7 @@ function fullGridBlock(result, derived, standard, s, lang) {
|
|
|
63721
63760
|
function orphansBlock(result, standard, s, lang, baseDir) {
|
|
63722
63761
|
const orphans = unattributedFindings(result).filter((f) => !f.advisory);
|
|
63723
63762
|
if (!orphans.length) return [];
|
|
63724
|
-
const
|
|
63725
|
-
const groups = groupFindings(orphans, standard, lang, baseDir);
|
|
63763
|
+
const criteria = groupByCriterion(groupFindings(orphans, standard, lang, baseDir));
|
|
63726
63764
|
const counts = SEV_ORDER4.map((sev) => `${ICON5[sev]} ${orphans.filter((f) => f.severity === sev).length}`).join(" \xB7 ");
|
|
63727
63765
|
const out2 = [
|
|
63728
63766
|
"<details>",
|
|
@@ -63731,40 +63769,26 @@ function orphansBlock(result, standard, s, lang, baseDir) {
|
|
|
63731
63769
|
"",
|
|
63732
63770
|
`> ${s.orphansNote}`,
|
|
63733
63771
|
"",
|
|
63734
|
-
|
|
63735
|
-
"| --- | --- | --- | --- | ---: |"
|
|
63772
|
+
...criterionSection(criteria, s, MAX_ROWS, s.moreCriteria)
|
|
63736
63773
|
];
|
|
63737
|
-
for (const g of groups.slice(0, MAX_ROWS)) {
|
|
63738
|
-
out2.push(
|
|
63739
|
-
`| ${ICON5[g.severity]} ${g.severity} | ${cell(g.criterion)} | \`${cell(g.where)}\` (\`${cell(g.selectorHint)}\`) | ${cell(mdText(g.message))} | ${g.occurrences} |`
|
|
63740
|
-
);
|
|
63741
|
-
}
|
|
63742
|
-
if (groups.length > MAX_ROWS) out2.push("", s.more(groups.length - MAX_ROWS));
|
|
63743
63774
|
out2.push("", "</details>");
|
|
63744
63775
|
return out2;
|
|
63745
63776
|
}
|
|
63746
63777
|
function allDefectsBlock(result, standard, s, lang, baseDir) {
|
|
63747
63778
|
const all = findingsForStandard(result, standard).filter((f) => !f.advisory);
|
|
63748
63779
|
if (!all.length) return [];
|
|
63749
|
-
const cell = (v) => v.replace(/\|/g, "\\|");
|
|
63750
63780
|
const groups = groupFindings(all, standard, lang, baseDir);
|
|
63781
|
+
const criteria = groupByCriterion(groups);
|
|
63751
63782
|
const counts = SEV_ORDER4.map((sev) => `${ICON5[sev]} ${all.filter((f) => f.severity === sev).length}`).join(" \xB7 ");
|
|
63752
63783
|
const out2 = [
|
|
63753
63784
|
"<details>",
|
|
63754
|
-
`<summary><b>${s.allDefects}</b> \u2014 ${groups.length
|
|
63785
|
+
`<summary><b>${s.allDefects}</b> \u2014 ${s.byCriterion(criteria.length, groups.length, all.length)} \xB7 ${counts}</summary>`,
|
|
63755
63786
|
// GFM only renders Markdown inside <details> after a blank line.
|
|
63756
63787
|
"",
|
|
63757
63788
|
`> ${s.allDefectsNote}`,
|
|
63758
63789
|
"",
|
|
63759
|
-
|
|
63760
|
-
"| --- | --- | --- | --- | ---: |"
|
|
63790
|
+
...criterionSection(criteria, s, MAX_ROWS, s.moreCriteria)
|
|
63761
63791
|
];
|
|
63762
|
-
for (const g of groups.slice(0, MAX_ROWS)) {
|
|
63763
|
-
out2.push(
|
|
63764
|
-
`| ${ICON5[g.severity]} ${g.severity} | ${cell(g.criterion)} | \`${cell(g.where)}\` (\`${cell(g.selectorHint)}\`) | ${cell(mdText(g.message))} | ${g.occurrences} |`
|
|
63765
|
-
);
|
|
63766
|
-
}
|
|
63767
|
-
if (groups.length > MAX_ROWS) out2.push("", s.more(groups.length - MAX_ROWS));
|
|
63768
63792
|
out2.push("", "</details>");
|
|
63769
63793
|
return out2;
|
|
63770
63794
|
}
|
|
@@ -5,7 +5,7 @@ when_to_use: "Invoke WITHOUT being asked whenever changed front-end code should
|
|
|
5
5
|
allowed-tools: Bash(node ${CLAUDE_SKILL_DIR}/scripts/ultra11y.mjs *), Read, Grep, Glob
|
|
6
6
|
license: MIT
|
|
7
7
|
metadata:
|
|
8
|
-
version: 5.
|
|
8
|
+
version: 5.29.0
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# review-a11y — accessibility review of changed code
|