ultra11y 5.9.2 → 5.10.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 +43 -6
- package/skills/review-a11y/SKILL.md +1 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +43 -6
- package/skills/ultra11y/SKILL.md +1 -1
- package/skills/ultra11y/scripts/ultra11y.mjs +43 -6
- package/src/adjudicate.ts +16 -1
- package/src/annotate.ts +55 -5
- 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.10.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.
|
|
4
|
+
"version": "5.10.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.
|
|
4
|
+
"version": "5.10.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.
|
|
30
|
+
const ULTRA11Y_PLUGIN_VERSION = "5.10.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.
|
|
3
|
+
"version": "5.10.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 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.10.1";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
|
|
12
12
|
// src/audit.ts
|
|
@@ -58208,10 +58208,16 @@ function writeAdjudication(items, outDir, opts) {
|
|
|
58208
58208
|
return { todoPath, mdPath, verdictsPath, itemsDir, count: items.length };
|
|
58209
58209
|
}
|
|
58210
58210
|
var PREFILLED_CITATIONS = 3;
|
|
58211
|
+
var CITATION_SNIPPET_MAX = 120;
|
|
58211
58212
|
function slimAdjudicationItems(items) {
|
|
58212
58213
|
return items.map((it) => {
|
|
58213
58214
|
const { evidence: _evidence, evidenceTruncated: _truncated, ...rest } = it;
|
|
58214
|
-
const citations = it.evidence.slice(0, PREFILLED_CITATIONS).map((e) => ({
|
|
58215
|
+
const citations = it.evidence.slice(0, PREFILLED_CITATIONS).map((e) => ({
|
|
58216
|
+
file: e.file,
|
|
58217
|
+
line: e.line,
|
|
58218
|
+
...e.selector ? { selector: e.selector } : {},
|
|
58219
|
+
...e.snippet ? { snippet: e.snippet.slice(0, CITATION_SNIPPET_MAX) } : {}
|
|
58220
|
+
}));
|
|
58215
58221
|
return { ...rest, evidence: [], ...citations.length ? { citations } : {} };
|
|
58216
58222
|
});
|
|
58217
58223
|
}
|
|
@@ -61417,6 +61423,9 @@ var S = {
|
|
|
61417
61423
|
pagesClamped: (n) => `_Le d\xE9tail de ${n} page(s) a \xE9t\xE9 retir\xE9 de ce commentaire pour tenir dans la limite de GitHub \u2014 l'artefact les porte toutes._`,
|
|
61418
61424
|
scoreboardClamped: (n) => `_${n} page(s) retir\xE9e(s) du tableau pour tenir dans la limite de GitHub \u2014 l'artefact les porte toutes._`,
|
|
61419
61425
|
pageDefects: "D\xE9fauts",
|
|
61426
|
+
fullGrid: "Grille compl\xE8te \u2014 chaque crit\xE8re du r\xE9f\xE9rentiel, page par page",
|
|
61427
|
+
gridLegend: "`C` conforme \xB7 `NC` non conforme \xB7 `\u2014` non applicable \xB7 `?` \xE0 \xE9valuer",
|
|
61428
|
+
gridDropped: "_La grille compl\xE8te ne tient pas dans un commentaire GitHub (64 Kio) \u2014 elle est dans la fiche par page du livrable._",
|
|
61420
61429
|
pageMoreDefects: (n) => `_\u2026 et ${n} autre(s) d\xE9faut(s) distinct(s) sur cette page \u2014 voir la fiche de page dans l'artefact._`,
|
|
61421
61430
|
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.`
|
|
61422
61431
|
},
|
|
@@ -61464,6 +61473,9 @@ var S = {
|
|
|
61464
61473
|
pagesClamped: (n) => `_The detail of ${n} page(s) was dropped from this comment to fit GitHub's limit \u2014 the artifact carries them all._`,
|
|
61465
61474
|
scoreboardClamped: (n) => `_${n} page(s) dropped from the table to fit GitHub's limit \u2014 the artifact carries them all._`,
|
|
61466
61475
|
pageDefects: "Defects",
|
|
61476
|
+
fullGrid: "Full grid \u2014 every criterion of the standard, page by page",
|
|
61477
|
+
gridLegend: "`C` conforming \xB7 `NC` non-conforming \xB7 `\u2014` not applicable \xB7 `?` to assess",
|
|
61478
|
+
gridDropped: "_The full grid does not fit in a GitHub comment (64 KiB) \u2014 it is in the deliverable's per-page sheet._",
|
|
61467
61479
|
pageMoreDefects: (n) => `_\u2026 and ${n} more distinct defect(s) on this page \u2014 see its sheet in the artifact._`,
|
|
61468
61480
|
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.`
|
|
61469
61481
|
}
|
|
@@ -61652,6 +61664,28 @@ function pageBlock(result, page, standard, lang, baseDir) {
|
|
|
61652
61664
|
out2.push("", "</details>");
|
|
61653
61665
|
return out2.join("\n");
|
|
61654
61666
|
}
|
|
61667
|
+
function fullGridBlock(result, derived, standard, s, lang) {
|
|
61668
|
+
const { rows, status } = pageGridModel(result, derived, standard, lang);
|
|
61669
|
+
if (!rows.length || !derived.length) return [];
|
|
61670
|
+
const cell = (v) => v.replace(/\|/g, "\\|");
|
|
61671
|
+
const head = [s.criterion, ...derived.map((p) => `${cell(p.name)}${p.auth ? " \u{1F512}" : ""}`)];
|
|
61672
|
+
const out2 = [
|
|
61673
|
+
"<details>",
|
|
61674
|
+
`<summary><b>${s.fullGrid}</b> \u2014 ${rows.length} \xD7 ${derived.length}</summary>`,
|
|
61675
|
+
// GFM only renders Markdown inside <details> after a blank line.
|
|
61676
|
+
"",
|
|
61677
|
+
`> ${s.gridLegend}`,
|
|
61678
|
+
"",
|
|
61679
|
+
`| ${head.join(" | ")} |`,
|
|
61680
|
+
`| ${head.map(() => "---").join(" | ")} |`
|
|
61681
|
+
];
|
|
61682
|
+
for (const row of rows) {
|
|
61683
|
+
out2.push(`| ${cell(row.label)} | ${derived.map((p) => GRID_MARK[status.get(row.id)?.get(p.id) ?? "manual"]).join(" | ")} |`);
|
|
61684
|
+
}
|
|
61685
|
+
out2.push("", "</details>");
|
|
61686
|
+
return out2;
|
|
61687
|
+
}
|
|
61688
|
+
var GRID_MARK = { C: "C", NC: "NC", NA: "\u2014", manual: "?" };
|
|
61655
61689
|
function pagesComment(result, opts = {}) {
|
|
61656
61690
|
const standard = opts.standard ?? CORE2;
|
|
61657
61691
|
const lang = opts.lang ?? "en";
|
|
@@ -61680,11 +61714,13 @@ function pagesComment(result, opts = {}) {
|
|
|
61680
61714
|
const blocks = [...derived].sort(
|
|
61681
61715
|
(a, b) => severityCount(b, "bloquant") - severityCount(a, "bloquant") || severityCount(b, "majeur") - severityCount(a, "majeur") || severityCount(b, "mineur") - severityCount(a, "mineur")
|
|
61682
61716
|
).map((p) => pageBlock(result, p, standard, lang, baseDir)).filter((b) => b !== void 0);
|
|
61683
|
-
const assemble = (nBlocks2, nRows2) => {
|
|
61717
|
+
const assemble = (nBlocks2, nRows2, withGrid = true) => {
|
|
61684
61718
|
const body3 = [...scoreboardTable(result, derived.slice(0, nRows2), standard, s, lang), ""];
|
|
61685
61719
|
if (nRows2 < derived.length) body3.push(s.scoreboardClamped(derived.length - nRows2), "");
|
|
61686
61720
|
body3.push(...basisCaveats(result, derived, s, lang));
|
|
61687
61721
|
if (redirected.length) body3.push(...renderRedirected(redirected, lang), "");
|
|
61722
|
+
if (withGrid) body3.push(...fullGridBlock(result, derived, standard, s, lang), "");
|
|
61723
|
+
else body3.push(s.gridDropped, "");
|
|
61688
61724
|
if (blocks.length) {
|
|
61689
61725
|
body3.push(`> ${s.pagesDetailNote}`, "");
|
|
61690
61726
|
body3.push(...blocks.slice(0, nBlocks2).flatMap((b) => [b, ""]));
|
|
@@ -61694,9 +61730,10 @@ function pagesComment(result, opts = {}) {
|
|
|
61694
61730
|
};
|
|
61695
61731
|
let nBlocks = blocks.length;
|
|
61696
61732
|
let nRows = derived.length;
|
|
61697
|
-
|
|
61698
|
-
while (
|
|
61699
|
-
|
|
61733
|
+
if (assemble(nBlocks, nRows).length <= COMMENT_LIMIT) return assemble(nBlocks, nRows);
|
|
61734
|
+
while (nBlocks > 0 && assemble(nBlocks, nRows, false).length > COMMENT_LIMIT) nBlocks--;
|
|
61735
|
+
while (nRows > 0 && assemble(nBlocks, nRows, false).length > COMMENT_LIMIT) nRows--;
|
|
61736
|
+
return assemble(nBlocks, nRows, false);
|
|
61700
61737
|
}
|
|
61701
61738
|
|
|
61702
61739
|
// src/evidence.ts
|
|
@@ -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.10.1
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# review-a11y — accessibility review of changed code
|
|
@@ -6,7 +6,7 @@ import { join as join51, relative as relative5, resolve as resolve16, sep as sep
|
|
|
6
6
|
import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
|
|
7
7
|
|
|
8
8
|
// src/types.ts
|
|
9
|
-
var VERSION = "5.
|
|
9
|
+
var VERSION = "5.10.1";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
|
|
12
12
|
// src/audit.ts
|
|
@@ -58208,10 +58208,16 @@ function writeAdjudication(items, outDir, opts) {
|
|
|
58208
58208
|
return { todoPath, mdPath, verdictsPath, itemsDir, count: items.length };
|
|
58209
58209
|
}
|
|
58210
58210
|
var PREFILLED_CITATIONS = 3;
|
|
58211
|
+
var CITATION_SNIPPET_MAX = 120;
|
|
58211
58212
|
function slimAdjudicationItems(items) {
|
|
58212
58213
|
return items.map((it) => {
|
|
58213
58214
|
const { evidence: _evidence, evidenceTruncated: _truncated, ...rest } = it;
|
|
58214
|
-
const citations = it.evidence.slice(0, PREFILLED_CITATIONS).map((e) => ({
|
|
58215
|
+
const citations = it.evidence.slice(0, PREFILLED_CITATIONS).map((e) => ({
|
|
58216
|
+
file: e.file,
|
|
58217
|
+
line: e.line,
|
|
58218
|
+
...e.selector ? { selector: e.selector } : {},
|
|
58219
|
+
...e.snippet ? { snippet: e.snippet.slice(0, CITATION_SNIPPET_MAX) } : {}
|
|
58220
|
+
}));
|
|
58215
58221
|
return { ...rest, evidence: [], ...citations.length ? { citations } : {} };
|
|
58216
58222
|
});
|
|
58217
58223
|
}
|
|
@@ -61417,6 +61423,9 @@ var S = {
|
|
|
61417
61423
|
pagesClamped: (n) => `_Le d\xE9tail de ${n} page(s) a \xE9t\xE9 retir\xE9 de ce commentaire pour tenir dans la limite de GitHub \u2014 l'artefact les porte toutes._`,
|
|
61418
61424
|
scoreboardClamped: (n) => `_${n} page(s) retir\xE9e(s) du tableau pour tenir dans la limite de GitHub \u2014 l'artefact les porte toutes._`,
|
|
61419
61425
|
pageDefects: "D\xE9fauts",
|
|
61426
|
+
fullGrid: "Grille compl\xE8te \u2014 chaque crit\xE8re du r\xE9f\xE9rentiel, page par page",
|
|
61427
|
+
gridLegend: "`C` conforme \xB7 `NC` non conforme \xB7 `\u2014` non applicable \xB7 `?` \xE0 \xE9valuer",
|
|
61428
|
+
gridDropped: "_La grille compl\xE8te ne tient pas dans un commentaire GitHub (64 Kio) \u2014 elle est dans la fiche par page du livrable._",
|
|
61420
61429
|
pageMoreDefects: (n) => `_\u2026 et ${n} autre(s) d\xE9faut(s) distinct(s) sur cette page \u2014 voir la fiche de page dans l'artefact._`,
|
|
61421
61430
|
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.`
|
|
61422
61431
|
},
|
|
@@ -61464,6 +61473,9 @@ var S = {
|
|
|
61464
61473
|
pagesClamped: (n) => `_The detail of ${n} page(s) was dropped from this comment to fit GitHub's limit \u2014 the artifact carries them all._`,
|
|
61465
61474
|
scoreboardClamped: (n) => `_${n} page(s) dropped from the table to fit GitHub's limit \u2014 the artifact carries them all._`,
|
|
61466
61475
|
pageDefects: "Defects",
|
|
61476
|
+
fullGrid: "Full grid \u2014 every criterion of the standard, page by page",
|
|
61477
|
+
gridLegend: "`C` conforming \xB7 `NC` non-conforming \xB7 `\u2014` not applicable \xB7 `?` to assess",
|
|
61478
|
+
gridDropped: "_The full grid does not fit in a GitHub comment (64 KiB) \u2014 it is in the deliverable's per-page sheet._",
|
|
61467
61479
|
pageMoreDefects: (n) => `_\u2026 and ${n} more distinct defect(s) on this page \u2014 see its sheet in the artifact._`,
|
|
61468
61480
|
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.`
|
|
61469
61481
|
}
|
|
@@ -61652,6 +61664,28 @@ function pageBlock(result, page, standard, lang, baseDir) {
|
|
|
61652
61664
|
out2.push("", "</details>");
|
|
61653
61665
|
return out2.join("\n");
|
|
61654
61666
|
}
|
|
61667
|
+
function fullGridBlock(result, derived, standard, s, lang) {
|
|
61668
|
+
const { rows, status } = pageGridModel(result, derived, standard, lang);
|
|
61669
|
+
if (!rows.length || !derived.length) return [];
|
|
61670
|
+
const cell = (v) => v.replace(/\|/g, "\\|");
|
|
61671
|
+
const head = [s.criterion, ...derived.map((p) => `${cell(p.name)}${p.auth ? " \u{1F512}" : ""}`)];
|
|
61672
|
+
const out2 = [
|
|
61673
|
+
"<details>",
|
|
61674
|
+
`<summary><b>${s.fullGrid}</b> \u2014 ${rows.length} \xD7 ${derived.length}</summary>`,
|
|
61675
|
+
// GFM only renders Markdown inside <details> after a blank line.
|
|
61676
|
+
"",
|
|
61677
|
+
`> ${s.gridLegend}`,
|
|
61678
|
+
"",
|
|
61679
|
+
`| ${head.join(" | ")} |`,
|
|
61680
|
+
`| ${head.map(() => "---").join(" | ")} |`
|
|
61681
|
+
];
|
|
61682
|
+
for (const row of rows) {
|
|
61683
|
+
out2.push(`| ${cell(row.label)} | ${derived.map((p) => GRID_MARK[status.get(row.id)?.get(p.id) ?? "manual"]).join(" | ")} |`);
|
|
61684
|
+
}
|
|
61685
|
+
out2.push("", "</details>");
|
|
61686
|
+
return out2;
|
|
61687
|
+
}
|
|
61688
|
+
var GRID_MARK = { C: "C", NC: "NC", NA: "\u2014", manual: "?" };
|
|
61655
61689
|
function pagesComment(result, opts = {}) {
|
|
61656
61690
|
const standard = opts.standard ?? CORE2;
|
|
61657
61691
|
const lang = opts.lang ?? "en";
|
|
@@ -61680,11 +61714,13 @@ function pagesComment(result, opts = {}) {
|
|
|
61680
61714
|
const blocks = [...derived].sort(
|
|
61681
61715
|
(a, b) => severityCount(b, "bloquant") - severityCount(a, "bloquant") || severityCount(b, "majeur") - severityCount(a, "majeur") || severityCount(b, "mineur") - severityCount(a, "mineur")
|
|
61682
61716
|
).map((p) => pageBlock(result, p, standard, lang, baseDir)).filter((b) => b !== void 0);
|
|
61683
|
-
const assemble = (nBlocks2, nRows2) => {
|
|
61717
|
+
const assemble = (nBlocks2, nRows2, withGrid = true) => {
|
|
61684
61718
|
const body3 = [...scoreboardTable(result, derived.slice(0, nRows2), standard, s, lang), ""];
|
|
61685
61719
|
if (nRows2 < derived.length) body3.push(s.scoreboardClamped(derived.length - nRows2), "");
|
|
61686
61720
|
body3.push(...basisCaveats(result, derived, s, lang));
|
|
61687
61721
|
if (redirected.length) body3.push(...renderRedirected(redirected, lang), "");
|
|
61722
|
+
if (withGrid) body3.push(...fullGridBlock(result, derived, standard, s, lang), "");
|
|
61723
|
+
else body3.push(s.gridDropped, "");
|
|
61688
61724
|
if (blocks.length) {
|
|
61689
61725
|
body3.push(`> ${s.pagesDetailNote}`, "");
|
|
61690
61726
|
body3.push(...blocks.slice(0, nBlocks2).flatMap((b) => [b, ""]));
|
|
@@ -61694,9 +61730,10 @@ function pagesComment(result, opts = {}) {
|
|
|
61694
61730
|
};
|
|
61695
61731
|
let nBlocks = blocks.length;
|
|
61696
61732
|
let nRows = derived.length;
|
|
61697
|
-
|
|
61698
|
-
while (
|
|
61699
|
-
|
|
61733
|
+
if (assemble(nBlocks, nRows).length <= COMMENT_LIMIT) return assemble(nBlocks, nRows);
|
|
61734
|
+
while (nBlocks > 0 && assemble(nBlocks, nRows, false).length > COMMENT_LIMIT) nBlocks--;
|
|
61735
|
+
while (nRows > 0 && assemble(nBlocks, nRows, false).length > COMMENT_LIMIT) nRows--;
|
|
61736
|
+
return assemble(nBlocks, nRows, false);
|
|
61700
61737
|
}
|
|
61701
61738
|
|
|
61702
61739
|
// src/evidence.ts
|
package/skills/ultra11y/SKILL.md
CHANGED
|
@@ -4,7 +4,7 @@ description: "Use to AUDIT existing HTML/CSS/JSX against WCAG 2.2 AA accessibili
|
|
|
4
4
|
when_to_use: "Invoke when the user asks for an accessibility AUDIT or a formal deliverable: audit a repo, site or page against WCAG 2.2 AA or a country standard (RGAA, Section 508, EN 301 549); produce a dated conformance report, a criterion grid, a PRD backlog, or tickets (GitHub/GitLab/Jira); author accessible markup from scratch; wire the repo gate (init --hook/--ci) or a rendered-DOM capture pipeline. For a review of the code UNDER CHANGE (diff, branch, PR), use the `review-a11y` skill instead."
|
|
5
5
|
license: MIT
|
|
6
6
|
metadata:
|
|
7
|
-
version: 5.
|
|
7
|
+
version: 5.10.1
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# ultra11y — audit WCAG 2.2 AA and write accessible markup
|
|
@@ -6,7 +6,7 @@ import { join as join51, relative as relative5, resolve as resolve16, sep as sep
|
|
|
6
6
|
import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
|
|
7
7
|
|
|
8
8
|
// src/types.ts
|
|
9
|
-
var VERSION = "5.
|
|
9
|
+
var VERSION = "5.10.1";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
|
|
12
12
|
// src/audit.ts
|
|
@@ -58208,10 +58208,16 @@ function writeAdjudication(items, outDir, opts) {
|
|
|
58208
58208
|
return { todoPath, mdPath, verdictsPath, itemsDir, count: items.length };
|
|
58209
58209
|
}
|
|
58210
58210
|
var PREFILLED_CITATIONS = 3;
|
|
58211
|
+
var CITATION_SNIPPET_MAX = 120;
|
|
58211
58212
|
function slimAdjudicationItems(items) {
|
|
58212
58213
|
return items.map((it) => {
|
|
58213
58214
|
const { evidence: _evidence, evidenceTruncated: _truncated, ...rest } = it;
|
|
58214
|
-
const citations = it.evidence.slice(0, PREFILLED_CITATIONS).map((e) => ({
|
|
58215
|
+
const citations = it.evidence.slice(0, PREFILLED_CITATIONS).map((e) => ({
|
|
58216
|
+
file: e.file,
|
|
58217
|
+
line: e.line,
|
|
58218
|
+
...e.selector ? { selector: e.selector } : {},
|
|
58219
|
+
...e.snippet ? { snippet: e.snippet.slice(0, CITATION_SNIPPET_MAX) } : {}
|
|
58220
|
+
}));
|
|
58215
58221
|
return { ...rest, evidence: [], ...citations.length ? { citations } : {} };
|
|
58216
58222
|
});
|
|
58217
58223
|
}
|
|
@@ -61417,6 +61423,9 @@ var S = {
|
|
|
61417
61423
|
pagesClamped: (n) => `_Le d\xE9tail de ${n} page(s) a \xE9t\xE9 retir\xE9 de ce commentaire pour tenir dans la limite de GitHub \u2014 l'artefact les porte toutes._`,
|
|
61418
61424
|
scoreboardClamped: (n) => `_${n} page(s) retir\xE9e(s) du tableau pour tenir dans la limite de GitHub \u2014 l'artefact les porte toutes._`,
|
|
61419
61425
|
pageDefects: "D\xE9fauts",
|
|
61426
|
+
fullGrid: "Grille compl\xE8te \u2014 chaque crit\xE8re du r\xE9f\xE9rentiel, page par page",
|
|
61427
|
+
gridLegend: "`C` conforme \xB7 `NC` non conforme \xB7 `\u2014` non applicable \xB7 `?` \xE0 \xE9valuer",
|
|
61428
|
+
gridDropped: "_La grille compl\xE8te ne tient pas dans un commentaire GitHub (64 Kio) \u2014 elle est dans la fiche par page du livrable._",
|
|
61420
61429
|
pageMoreDefects: (n) => `_\u2026 et ${n} autre(s) d\xE9faut(s) distinct(s) sur cette page \u2014 voir la fiche de page dans l'artefact._`,
|
|
61421
61430
|
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.`
|
|
61422
61431
|
},
|
|
@@ -61464,6 +61473,9 @@ var S = {
|
|
|
61464
61473
|
pagesClamped: (n) => `_The detail of ${n} page(s) was dropped from this comment to fit GitHub's limit \u2014 the artifact carries them all._`,
|
|
61465
61474
|
scoreboardClamped: (n) => `_${n} page(s) dropped from the table to fit GitHub's limit \u2014 the artifact carries them all._`,
|
|
61466
61475
|
pageDefects: "Defects",
|
|
61476
|
+
fullGrid: "Full grid \u2014 every criterion of the standard, page by page",
|
|
61477
|
+
gridLegend: "`C` conforming \xB7 `NC` non-conforming \xB7 `\u2014` not applicable \xB7 `?` to assess",
|
|
61478
|
+
gridDropped: "_The full grid does not fit in a GitHub comment (64 KiB) \u2014 it is in the deliverable's per-page sheet._",
|
|
61467
61479
|
pageMoreDefects: (n) => `_\u2026 and ${n} more distinct defect(s) on this page \u2014 see its sheet in the artifact._`,
|
|
61468
61480
|
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.`
|
|
61469
61481
|
}
|
|
@@ -61652,6 +61664,28 @@ function pageBlock(result, page, standard, lang, baseDir) {
|
|
|
61652
61664
|
out2.push("", "</details>");
|
|
61653
61665
|
return out2.join("\n");
|
|
61654
61666
|
}
|
|
61667
|
+
function fullGridBlock(result, derived, standard, s, lang) {
|
|
61668
|
+
const { rows, status } = pageGridModel(result, derived, standard, lang);
|
|
61669
|
+
if (!rows.length || !derived.length) return [];
|
|
61670
|
+
const cell = (v) => v.replace(/\|/g, "\\|");
|
|
61671
|
+
const head = [s.criterion, ...derived.map((p) => `${cell(p.name)}${p.auth ? " \u{1F512}" : ""}`)];
|
|
61672
|
+
const out2 = [
|
|
61673
|
+
"<details>",
|
|
61674
|
+
`<summary><b>${s.fullGrid}</b> \u2014 ${rows.length} \xD7 ${derived.length}</summary>`,
|
|
61675
|
+
// GFM only renders Markdown inside <details> after a blank line.
|
|
61676
|
+
"",
|
|
61677
|
+
`> ${s.gridLegend}`,
|
|
61678
|
+
"",
|
|
61679
|
+
`| ${head.join(" | ")} |`,
|
|
61680
|
+
`| ${head.map(() => "---").join(" | ")} |`
|
|
61681
|
+
];
|
|
61682
|
+
for (const row of rows) {
|
|
61683
|
+
out2.push(`| ${cell(row.label)} | ${derived.map((p) => GRID_MARK[status.get(row.id)?.get(p.id) ?? "manual"]).join(" | ")} |`);
|
|
61684
|
+
}
|
|
61685
|
+
out2.push("", "</details>");
|
|
61686
|
+
return out2;
|
|
61687
|
+
}
|
|
61688
|
+
var GRID_MARK = { C: "C", NC: "NC", NA: "\u2014", manual: "?" };
|
|
61655
61689
|
function pagesComment(result, opts = {}) {
|
|
61656
61690
|
const standard = opts.standard ?? CORE2;
|
|
61657
61691
|
const lang = opts.lang ?? "en";
|
|
@@ -61680,11 +61714,13 @@ function pagesComment(result, opts = {}) {
|
|
|
61680
61714
|
const blocks = [...derived].sort(
|
|
61681
61715
|
(a, b) => severityCount(b, "bloquant") - severityCount(a, "bloquant") || severityCount(b, "majeur") - severityCount(a, "majeur") || severityCount(b, "mineur") - severityCount(a, "mineur")
|
|
61682
61716
|
).map((p) => pageBlock(result, p, standard, lang, baseDir)).filter((b) => b !== void 0);
|
|
61683
|
-
const assemble = (nBlocks2, nRows2) => {
|
|
61717
|
+
const assemble = (nBlocks2, nRows2, withGrid = true) => {
|
|
61684
61718
|
const body3 = [...scoreboardTable(result, derived.slice(0, nRows2), standard, s, lang), ""];
|
|
61685
61719
|
if (nRows2 < derived.length) body3.push(s.scoreboardClamped(derived.length - nRows2), "");
|
|
61686
61720
|
body3.push(...basisCaveats(result, derived, s, lang));
|
|
61687
61721
|
if (redirected.length) body3.push(...renderRedirected(redirected, lang), "");
|
|
61722
|
+
if (withGrid) body3.push(...fullGridBlock(result, derived, standard, s, lang), "");
|
|
61723
|
+
else body3.push(s.gridDropped, "");
|
|
61688
61724
|
if (blocks.length) {
|
|
61689
61725
|
body3.push(`> ${s.pagesDetailNote}`, "");
|
|
61690
61726
|
body3.push(...blocks.slice(0, nBlocks2).flatMap((b) => [b, ""]));
|
|
@@ -61694,9 +61730,10 @@ function pagesComment(result, opts = {}) {
|
|
|
61694
61730
|
};
|
|
61695
61731
|
let nBlocks = blocks.length;
|
|
61696
61732
|
let nRows = derived.length;
|
|
61697
|
-
|
|
61698
|
-
while (
|
|
61699
|
-
|
|
61733
|
+
if (assemble(nBlocks, nRows).length <= COMMENT_LIMIT) return assemble(nBlocks, nRows);
|
|
61734
|
+
while (nBlocks > 0 && assemble(nBlocks, nRows, false).length > COMMENT_LIMIT) nBlocks--;
|
|
61735
|
+
while (nRows > 0 && assemble(nBlocks, nRows, false).length > COMMENT_LIMIT) nRows--;
|
|
61736
|
+
return assemble(nBlocks, nRows, false);
|
|
61700
61737
|
}
|
|
61701
61738
|
|
|
61702
61739
|
// src/evidence.ts
|
package/src/adjudicate.ts
CHANGED
|
@@ -1450,6 +1450,10 @@ export function writeAdjudication(
|
|
|
1450
1450
|
* for the population; the whole set is in the brief, and `occurrences` travels with each. */
|
|
1451
1451
|
const PREFILLED_CITATIONS = 3;
|
|
1452
1452
|
|
|
1453
|
+
/** Characters of the snippet a pre-filled citation carries. Enough to identify the element to
|
|
1454
|
+
* a reader of the diff; the grounding matches on the anchor, not on the length. */
|
|
1455
|
+
const CITATION_SNIPPET_MAX = 120;
|
|
1456
|
+
|
|
1453
1457
|
export function slimAdjudicationItems(items: AdjudicationItem[]): AdjudicationItem[] {
|
|
1454
1458
|
return items.map((it) => {
|
|
1455
1459
|
const { evidence: _evidence, evidenceTruncated: _truncated, ...rest } = it;
|
|
@@ -1471,7 +1475,18 @@ export function slimAdjudicationItems(items: AdjudicationItem[]): AdjudicationIt
|
|
|
1471
1475
|
// `population` and `evidenceComplete` are kept for the same reason they always were: they
|
|
1472
1476
|
// are two numbers, not bulk, and they tell the adjudicator how much of the subject it is
|
|
1473
1477
|
// actually looking at.
|
|
1474
|
-
|
|
1478
|
+
// MINIMAL, and that is not a detail: the first cut of this pre-filled the whole Evidence
|
|
1479
|
+
// object, `alsoAt` included — 167 sibling anchors on one criterion — and turned the file
|
|
1480
|
+
// the prompt calls "the small one to write" into 367 KB. Measured: the adjudicator stopped
|
|
1481
|
+
// after 22 turns of a 228 budget, filled nothing, and every criterion came back
|
|
1482
|
+
// `unadjudicated`. A citation only ever needs to say WHICH element; the population, the
|
|
1483
|
+
// siblings and the note belong to the brief, which is the file to READ.
|
|
1484
|
+
const citations = it.evidence.slice(0, PREFILLED_CITATIONS).map((e) => ({
|
|
1485
|
+
file: e.file,
|
|
1486
|
+
line: e.line,
|
|
1487
|
+
...(e.selector ? { selector: e.selector } : {}),
|
|
1488
|
+
...(e.snippet ? { snippet: e.snippet.slice(0, CITATION_SNIPPET_MAX) } : {}),
|
|
1489
|
+
}));
|
|
1475
1490
|
return { ...rest, evidence: [], ...(citations.length ? { citations } : {}) } as AdjudicationItem;
|
|
1476
1491
|
});
|
|
1477
1492
|
}
|
package/src/annotate.ts
CHANGED
|
@@ -23,7 +23,7 @@ import { resolveMessage, resolveRemediation } from "./messages.js";
|
|
|
23
23
|
import { findingsForStandard, packCriteriaForFinding } from "./standards/derive.js";
|
|
24
24
|
import { CORE, type StandardId, isCore, loadPack } from "./standards/index.js";
|
|
25
25
|
import { packReportGroups, reportCoverage, reportGroups } from "./report.js";
|
|
26
|
-
import type { AuditResult, Finding, Lang, PageResult, Severity } from "./types.js";
|
|
26
|
+
import type { AuditResult, Finding, Lang, PageResult, Severity, Status } from "./types.js";
|
|
27
27
|
import { isUrlPath, repoRelative } from "./util.js";
|
|
28
28
|
import {
|
|
29
29
|
agentMarkNote,
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
derivePages,
|
|
33
33
|
formatRate,
|
|
34
34
|
pageBasisWarning,
|
|
35
|
+
pageGridModel,
|
|
35
36
|
pagesOf,
|
|
36
37
|
renderRedirected,
|
|
37
38
|
unattributedFindings,
|
|
@@ -142,6 +143,9 @@ const S = {
|
|
|
142
143
|
`_Le détail de ${n} page(s) a été retiré de ce commentaire pour tenir dans la limite de GitHub — l'artefact les porte toutes._`,
|
|
143
144
|
scoreboardClamped: (n: number) => `_${n} page(s) retirée(s) du tableau pour tenir dans la limite de GitHub — l'artefact les porte toutes._`,
|
|
144
145
|
pageDefects: "Défauts",
|
|
146
|
+
fullGrid: "Grille complète — chaque critère du référentiel, page par page",
|
|
147
|
+
gridLegend: "`C` conforme · `NC` non conforme · `—` non applicable · `?` à évaluer",
|
|
148
|
+
gridDropped: "_La grille complète ne tient pas dans un commentaire GitHub (64 Kio) — elle est dans la fiche par page du livrable._",
|
|
145
149
|
pageMoreDefects: (n: number) => `_… et ${n} autre(s) défaut(s) distinct(s) sur cette page — voir la fiche de page dans l'artefact._`,
|
|
146
150
|
noCriterionForFindings: (n: number) =>
|
|
147
151
|
`${n} constat(s) sur cette page ne rendent aucun critère du référentiel non conforme : leur règle sort du périmètre d'application de chacun. Ils comptent dans les colonnes ci-dessus, et sont détaillés dans l'artefact.`,
|
|
@@ -196,6 +200,9 @@ const S = {
|
|
|
196
200
|
pagesClamped: (n: number) => `_The detail of ${n} page(s) was dropped from this comment to fit GitHub's limit — the artifact carries them all._`,
|
|
197
201
|
scoreboardClamped: (n: number) => `_${n} page(s) dropped from the table to fit GitHub's limit — the artifact carries them all._`,
|
|
198
202
|
pageDefects: "Defects",
|
|
203
|
+
fullGrid: "Full grid — every criterion of the standard, page by page",
|
|
204
|
+
gridLegend: "`C` conforming · `NC` non-conforming · `—` not applicable · `?` to assess",
|
|
205
|
+
gridDropped: "_The full grid does not fit in a GitHub comment (64 KiB) — it is in the deliverable's per-page sheet._",
|
|
199
206
|
pageMoreDefects: (n: number) => `_… and ${n} more distinct defect(s) on this page — see its sheet in the artifact._`,
|
|
200
207
|
noCriterionForFindings: (n: number) =>
|
|
201
208
|
`${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.`,
|
|
@@ -525,6 +532,41 @@ function pageBlock(result: AuditResult, page: PageResult, standard: StandardId,
|
|
|
525
532
|
return out.join("\n");
|
|
526
533
|
}
|
|
527
534
|
|
|
535
|
+
/** The whole criterion × page grid, collapsed — every criterion of the standard, and where
|
|
536
|
+
* each page stands on it.
|
|
537
|
+
*
|
|
538
|
+
* The scoreboard says HOW MANY criteria a page conforms to and the per-page blocks say which
|
|
539
|
+
* ones it FAILS; between the two, the criteria a page CONFORMS to were never named. Under a
|
|
540
|
+
* per-page norm that is most of the deliverable, and a reviewer asking « which of the 106 does
|
|
541
|
+
* this page pass? » had to download the artifact to find out.
|
|
542
|
+
*
|
|
543
|
+
* Drawn from `pageGridModel`, the same projection the artifact and the HTML use. A surface
|
|
544
|
+
* that recomputes a status is a surface that will eventually disagree with the report. */
|
|
545
|
+
function fullGridBlock(result: AuditResult, derived: PageResult[], standard: StandardId, s: (typeof S)[Lang], lang: Lang): string[] {
|
|
546
|
+
const { rows, status } = pageGridModel(result, derived, standard, lang);
|
|
547
|
+
if (!rows.length || !derived.length) return [];
|
|
548
|
+
const cell = (v: string): string => v.replace(/\|/g, "\\|");
|
|
549
|
+
const head = [s.criterion, ...derived.map((p) => `${cell(p.name)}${p.auth ? " 🔒" : ""}`)];
|
|
550
|
+
const out: string[] = [
|
|
551
|
+
"<details>",
|
|
552
|
+
`<summary><b>${s.fullGrid}</b> — ${rows.length} × ${derived.length}</summary>`,
|
|
553
|
+
// GFM only renders Markdown inside <details> after a blank line.
|
|
554
|
+
"",
|
|
555
|
+
`> ${s.gridLegend}`,
|
|
556
|
+
"",
|
|
557
|
+
`| ${head.join(" | ")} |`,
|
|
558
|
+
`| ${head.map(() => "---").join(" | ")} |`,
|
|
559
|
+
];
|
|
560
|
+
for (const row of rows) {
|
|
561
|
+
out.push(`| ${cell(row.label)} | ${derived.map((p) => GRID_MARK[status.get(row.id)?.get(p.id) ?? "manual"]).join(" | ")} |`);
|
|
562
|
+
}
|
|
563
|
+
out.push("", "</details>");
|
|
564
|
+
return out;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/** The marks the grid draws. Same vocabulary as the per-page sheet's. */
|
|
568
|
+
const GRID_MARK: Record<Status, string> = { C: "C", NC: "NC", NA: "—", manual: "?" };
|
|
569
|
+
|
|
528
570
|
/** The PAGE-BY-PAGE pull-request comment.
|
|
529
571
|
*
|
|
530
572
|
* A second document under a second marker, for a second tier. The digest above answers
|
|
@@ -584,11 +626,16 @@ export function pagesComment(result: AuditResult, opts: AnnotateOptions & { runU
|
|
|
584
626
|
// document at a byte offset lands mid-row and GFM then renders a broken table. The detail
|
|
585
627
|
// goes first because the scoreboard is the half that cannot be reconstructed from the
|
|
586
628
|
// artifact link alone.
|
|
587
|
-
const assemble = (nBlocks: number, nRows: number): string => {
|
|
629
|
+
const assemble = (nBlocks: number, nRows: number, withGrid = true): string => {
|
|
588
630
|
const body: string[] = [...scoreboardTable(result, derived.slice(0, nRows), standard, s, lang), ""];
|
|
589
631
|
if (nRows < derived.length) body.push(s.scoreboardClamped(derived.length - nRows), "");
|
|
590
632
|
body.push(...basisCaveats(result, derived, s, lang));
|
|
591
633
|
if (redirected.length) body.push(...renderRedirected(redirected, lang), "");
|
|
634
|
+
// The full grid, folded away so the scoreboard stays what a reviewer reads first. It is
|
|
635
|
+
// the biggest thing in the document — 106 criteria × 35 pages — so it is also the first
|
|
636
|
+
// thing the size clamp gives up, WHOLE and with a line saying where to find it.
|
|
637
|
+
if (withGrid) body.push(...fullGridBlock(result, derived, standard, s, lang), "");
|
|
638
|
+
else body.push(s.gridDropped, "");
|
|
592
639
|
if (blocks.length) {
|
|
593
640
|
body.push(`> ${s.pagesDetailNote}`, "");
|
|
594
641
|
body.push(...blocks.slice(0, nBlocks).flatMap((b) => [b, ""]));
|
|
@@ -599,7 +646,10 @@ export function pagesComment(result: AuditResult, opts: AnnotateOptions & { runU
|
|
|
599
646
|
|
|
600
647
|
let nBlocks = blocks.length;
|
|
601
648
|
let nRows = derived.length;
|
|
602
|
-
|
|
603
|
-
while
|
|
604
|
-
return assemble(nBlocks, nRows);
|
|
649
|
+
// The grid goes first when it does not fit: it is the one part that is reproducible in full
|
|
650
|
+
// from the artifact, while the scoreboard and the defect blocks are not.
|
|
651
|
+
if (assemble(nBlocks, nRows).length <= COMMENT_LIMIT) return assemble(nBlocks, nRows);
|
|
652
|
+
while (nBlocks > 0 && assemble(nBlocks, nRows, false).length > COMMENT_LIMIT) nBlocks--;
|
|
653
|
+
while (nRows > 0 && assemble(nBlocks, nRows, false).length > COMMENT_LIMIT) nRows--;
|
|
654
|
+
return assemble(nBlocks, nRows, false);
|
|
605
655
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Single source of truth for shared types + the embedded version. sync-version.mjs
|
|
2
2
|
// rewrites VERSION at release time (kept in lockstep with package.json + SKILL.md).
|
|
3
|
-
export const VERSION = "5.
|
|
3
|
+
export const VERSION = "5.10.1";
|
|
4
4
|
export const SCHEMA_VERSION = 2;
|
|
5
5
|
|
|
6
6
|
// Lang = the UI FRAME's language — the `L` localization tables each module (report,
|