ultra11y 4.1.0 → 4.2.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/README.md +13 -7
- package/package.json +1 -1
- package/scripts/ultra11y.mjs +99 -58
- package/skills/review-a11y/SKILL.md +1 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +99 -58
- package/skills/ultra11y/SKILL.md +1 -1
- package/skills/ultra11y/references/ci.md +33 -11
- package/skills/ultra11y/references/pages.md +28 -9
- package/skills/ultra11y/scripts/ultra11y.mjs +99 -58
- package/src/cli.ts +52 -24
- package/src/evidence.ts +72 -37
- package/src/html-emit.ts +20 -3
- package/src/html-report.ts +27 -4
- package/src/pages-report.ts +10 -4
- package/src/report.ts +16 -7
- package/src/tickets/providers/github.ts +9 -4
- 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": "4.
|
|
13
|
+
"version": "4.2.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": "4.
|
|
4
|
+
"version": "4.2.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": "4.
|
|
4
|
+
"version": "4.2.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 = "4.
|
|
30
|
+
const ULTRA11Y_PLUGIN_VERSION = "4.2.0";
|
|
31
31
|
|
|
32
32
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
33
33
|
|
package/README.md
CHANGED
|
@@ -411,10 +411,11 @@ node scripts/ultra11y.mjs check --report audits/wcag-YYYY-MM-DD.md # integrity
|
|
|
411
411
|
|
|
412
412
|
### A report someone will actually open
|
|
413
413
|
|
|
414
|
-
`--html` turns the audit into a page: `audits/index.html` as the entry point, plus
|
|
415
|
-
detachable single file that prints to PDF. Self-contained — no script, no
|
|
416
|
-
nothing pointing outside the directory — and it passes this engine's own
|
|
417
|
-
which is the least a tool like this owes.
|
|
414
|
+
`--html` turns the audit into a page: `audits/index.html` as the entry point, plus — from
|
|
415
|
+
`report` — a detachable single file that prints to PDF. Self-contained — no script, no
|
|
416
|
+
external asset, nothing pointing outside the directory — and it passes this engine's own
|
|
417
|
+
accessibility audit, which is the least a tool like this owes. `pages --html` writes the
|
|
418
|
+
navigable sheets only: **one composite per artifact**, or every inlined crop travels twice.
|
|
418
419
|
|
|
419
420
|
```sh
|
|
420
421
|
node scripts/ultra11y.mjs report --in audit.json --html --evidence --out audits
|
|
@@ -424,9 +425,14 @@ node scripts/ultra11y.mjs pages --in audit.json --format report --split page \
|
|
|
424
425
|
|
|
425
426
|
`--evidence` illustrates each non-conformity with an **annotated crop of the offending
|
|
426
427
|
element**, cut from the page snapshot: `selectorHint` is lossy, and a rendered-tier finding on
|
|
427
|
-
a client-rendered page is anchored at `dom.html:2` whatever it is really about. It
|
|
428
|
-
|
|
429
|
-
|
|
428
|
+
a client-rendered page is anchored at `dom.html:2` whatever it is really about. It illustrates
|
|
429
|
+
the Markdown conformance report as much as the HTML — one set of files in `assets/`, referenced
|
|
430
|
+
by every document that shows the defect — so `--html` is not required to get pictures.
|
|
431
|
+
|
|
432
|
+
It needs snapshots, and it says per page and per criterion what it could not draw and why — an
|
|
433
|
+
occurrence with no picture must never read as an occurrence with no defect. It distinguishes
|
|
434
|
+
an occurrence *folded* into another's picture from a distinct defect a limit *cut off*, because
|
|
435
|
+
only the second is a gap.
|
|
430
436
|
|
|
431
437
|
In the GitHub Action both are **on by default**, so the uploaded artifact has a front door
|
|
432
438
|
without changing a workflow. See `references/ci.md`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultra11y",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.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",
|
package/scripts/ultra11y.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { join as join47, relative as relative5, sep as sep7, dirname as dirname1
|
|
|
6
6
|
import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
|
|
7
7
|
|
|
8
8
|
// src/types.ts
|
|
9
|
-
var VERSION = "4.
|
|
9
|
+
var VERSION = "4.2.0";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
|
|
12
12
|
// src/audit.ts
|
|
@@ -49748,12 +49748,13 @@ function render(r, lang, opts) {
|
|
|
49748
49748
|
const group = ncUnits.filter((u) => u.severity === sev);
|
|
49749
49749
|
if (!group.length) continue;
|
|
49750
49750
|
out2.push(`### ${ICON3[sev]} ${s.sev[sev]} (${group.length})`, "");
|
|
49751
|
-
for (const u of group) out2.push(...renderAuditorUnit(u, opts.standard, lang, { heading: "####" }));
|
|
49751
|
+
for (const u of group) out2.push(...renderAuditorUnit(u, opts.standard, lang, { heading: "####", ...opts.cropFor ? { cropFor: opts.cropFor } : {} }));
|
|
49752
49752
|
}
|
|
49753
49753
|
}
|
|
49754
49754
|
if (advisoryUnits.length) {
|
|
49755
49755
|
out2.push(`## \u{1F4A1} ${s.recTitle}`, "", `> ${s.recNote}`, "");
|
|
49756
|
-
for (const u of advisoryUnits)
|
|
49756
|
+
for (const u of advisoryUnits)
|
|
49757
|
+
out2.push(...renderAuditorUnit(u, opts.standard, lang, { heading: "###", ...opts.cropFor ? { cropFor: opts.cropFor } : {} }));
|
|
49757
49758
|
}
|
|
49758
49759
|
const pageScope = pagesOf(r);
|
|
49759
49760
|
if (pageScope.length) attributePages(r, pageScope);
|
|
@@ -49837,9 +49838,9 @@ function reportGroups(r, lang = "en") {
|
|
|
49837
49838
|
}
|
|
49838
49839
|
return r.guidelines.map((g) => ({ key: g.key, title: guidelineTitle(g.key, lang) ?? g.title, rows: byGuideline.get(g.key) ?? [] }));
|
|
49839
49840
|
}
|
|
49840
|
-
function renderReport(r, lang = "en", outDir) {
|
|
49841
|
+
function renderReport(r, lang = "en", outDir, cropFor) {
|
|
49841
49842
|
const s = L4[lang];
|
|
49842
|
-
return render(r, lang, { std: s.wcagStd, groupHead: s.byGuideline, groups: reportGroups(r, lang), standard: CORE2, outDir });
|
|
49843
|
+
return render(r, lang, { std: s.wcagStd, groupHead: s.byGuideline, groups: reportGroups(r, lang), standard: CORE2, outDir, ...cropFor ? { cropFor } : {} });
|
|
49843
49844
|
}
|
|
49844
49845
|
function packReportGroups(r, pack, lang = "en") {
|
|
49845
49846
|
const derived = derivePackResults(r, pack.key);
|
|
@@ -49862,7 +49863,7 @@ function packReportGroups(r, pack, lang = "en") {
|
|
|
49862
49863
|
}
|
|
49863
49864
|
return pack.themes.map((t2) => ({ key: `${t2.number}.`, title: themeName(pack, t2.number, lang) ?? "", rows: byTheme.get(t2.number) ?? [] }));
|
|
49864
49865
|
}
|
|
49865
|
-
function renderPackReport(r, pack, lang = "en", outDir) {
|
|
49866
|
+
function renderPackReport(r, pack, lang = "en", outDir, cropFor) {
|
|
49866
49867
|
const derived = derivePackResults(r, pack.key);
|
|
49867
49868
|
const std = `${pack.name} ${pack.baseVersion}`;
|
|
49868
49869
|
return render(r, lang, {
|
|
@@ -49876,12 +49877,13 @@ function renderPackReport(r, pack, lang = "en", outDir) {
|
|
|
49876
49877
|
// Forwarded, unlike before: without it the per-page screenshots resolved against the
|
|
49877
49878
|
// CWD instead of the report's own directory, so a pack report written to `audits/`
|
|
49878
49879
|
// carried links that only worked when read from the repo root.
|
|
49879
|
-
outDir
|
|
49880
|
+
outDir,
|
|
49881
|
+
...cropFor ? { cropFor } : {}
|
|
49880
49882
|
});
|
|
49881
49883
|
}
|
|
49882
49884
|
function writeReport(r, opts) {
|
|
49883
49885
|
const core = isCore(opts.standard);
|
|
49884
|
-
const md = core ? renderReport(r, opts.lang, opts.out) : renderPackReport(r, loadPack(opts.standard), opts.lang, opts.out);
|
|
49886
|
+
const md = core ? renderReport(r, opts.lang, opts.out, opts.cropFor) : renderPackReport(r, loadPack(opts.standard), opts.lang, opts.out, opts.cropFor);
|
|
49885
49887
|
mkdirSync7(opts.out, { recursive: true });
|
|
49886
49888
|
const path = join29(opts.out, `${core ? "wcag" : opts.standard}-${r.date}.md`);
|
|
49887
49889
|
writeFileSync8(path, md);
|
|
@@ -50372,17 +50374,19 @@ function resolve4(opts) {
|
|
|
50372
50374
|
const mode = opts.transport ?? "auto";
|
|
50373
50375
|
const api = (env.GITHUB_API_URL || "https://api.github.com").replace(/\/+$/, "");
|
|
50374
50376
|
const token = env.GH_TOKEN || env.GITHUB_TOKEN;
|
|
50375
|
-
const
|
|
50377
|
+
const probe = opts.cliAvailable ?? ghAvailable;
|
|
50378
|
+
let cached;
|
|
50379
|
+
const cliOk = () => cached ??= probe();
|
|
50376
50380
|
if (mode === "cli") {
|
|
50377
|
-
return cliOk ? { transport: "cli", api } : { transport: "cli", api, reason: "`gh` is not installed or not authenticated (run `gh auth login`)" };
|
|
50381
|
+
return cliOk() ? { transport: "cli", api } : { transport: "cli", api, reason: "`gh` is not installed or not authenticated (run `gh auth login`)" };
|
|
50378
50382
|
}
|
|
50379
|
-
const repo = env.ULTRA11Y_GITHUB_REPO || env.GITHUB_REPOSITORY || (mode === "rest" || !cliOk ? gitRemoteSlug("github.com") : void 0);
|
|
50383
|
+
const repo = env.ULTRA11Y_GITHUB_REPO || env.GITHUB_REPOSITORY || (mode === "rest" || !cliOk() ? gitRemoteSlug("github.com") : void 0);
|
|
50380
50384
|
if (mode === "rest") {
|
|
50381
50385
|
if (!token) return { transport: "rest", api, reason: "no GitHub token \u2014 set GH_TOKEN or GITHUB_TOKEN" };
|
|
50382
50386
|
if (!repo) return { transport: "rest", api, token, reason: "no repository \u2014 set GITHUB_REPOSITORY (owner/name) or ULTRA11Y_GITHUB_REPO" };
|
|
50383
50387
|
return { transport: "rest", api, token, repo };
|
|
50384
50388
|
}
|
|
50385
|
-
if (cliOk) return { transport: "cli", api };
|
|
50389
|
+
if (cliOk()) return { transport: "cli", api };
|
|
50386
50390
|
if (token && repo) return { transport: "rest", api, token, repo };
|
|
50387
50391
|
const missing = [!token ? "a token (GH_TOKEN/GITHUB_TOKEN)" : "", !repo ? "a repository (GITHUB_REPOSITORY)" : ""].filter(Boolean);
|
|
50388
50392
|
return {
|
|
@@ -51516,7 +51520,8 @@ function renderPageReport(result, page, opts = {}) {
|
|
|
51516
51520
|
const shot = opts.screenshots?.get(page.id);
|
|
51517
51521
|
if (shot) out2.push(``, "");
|
|
51518
51522
|
else out2.push(`_${s.noScreenshot}_`, "");
|
|
51519
|
-
|
|
51523
|
+
const refused = opts.evidenceNotice?.(page.id) ?? [];
|
|
51524
|
+
if (refused.length) out2.push(...refused, "");
|
|
51520
51525
|
const withTests = rows.some((r) => r.tests.length);
|
|
51521
51526
|
out2.push(`${h}# ${s.gridTitle}`, "", `> ${s.gridNote}`, "");
|
|
51522
51527
|
out2.push(withTests ? `| ${s.criterion} | ${s.tests} | ${s.status} |` : `| ${s.criterion} | ${s.status} |`);
|
|
@@ -56586,7 +56591,7 @@ var S2 = {
|
|
|
56586
56591
|
alt: (sel, page) => `Capture recadr\xE9e de l'\xE9l\xE9ment ${sel} sur la page ${page}, entour\xE9 d'un cadre`,
|
|
56587
56592
|
notImaged: (n) => `${n} occurrence(s) ne sont pas illustr\xE9es :`,
|
|
56588
56593
|
reasons: {
|
|
56589
|
-
"no-snapshot": "
|
|
56594
|
+
"no-snapshot": "la capture de page que ce constat d\xE9signe n'est pas sur ce disque \u2014 sa r\xE9f\xE9rence fichier:ligne reste valable",
|
|
56590
56595
|
"no-screenshot": "le producteur n'a pas fourni de capture pour cette page, donc le niveau pixel est inactif ici",
|
|
56591
56596
|
"unreadable-image": "la capture n'est pas d\xE9codable (le moteur lit le truecolour 8 bits uniquement) \u2014 mieux vaut aucune image qu'une fausse",
|
|
56592
56597
|
"no-boxes": "les positions d'\xE9l\xE9ments ne se v\xE9rifient pas contre le DOM s\xE9rialis\xE9, elles ont donc \xE9t\xE9 refus\xE9es en bloc plut\xF4t que d'encadrer le mauvais \xE9l\xE9ment",
|
|
@@ -56597,14 +56602,15 @@ var S2 = {
|
|
|
56597
56602
|
"zero-area": "l'\xE9l\xE9ment n'occupe aucune surface peinte",
|
|
56598
56603
|
"below-the-fold": "l'\xE9l\xE9ment est hors de la capture (la capture couvre la fen\xEAtre, pas la page enti\xE8re) \u2014 sa r\xE9f\xE9rence fichier:ligne reste valable",
|
|
56599
56604
|
"unknown-scale": "le rapport pixels/CSS est ind\xE9termin\xE9 pour cette page ; un rapport devin\xE9 encadrerait le mauvais endroit",
|
|
56600
|
-
|
|
56605
|
+
deduplicated: "m\xEAme d\xE9faut sur le m\xEAme \xE9l\xE9ment : il est montr\xE9 par l'illustration d'une autre occurrence, rien n'a \xE9t\xE9 perdu",
|
|
56606
|
+
capped: "d\xE9faut distinct laiss\xE9 sans image : le plafond de vignettes de ce lot est atteint \u2014 relevez `--evidence-max` pour les obtenir"
|
|
56601
56607
|
}
|
|
56602
56608
|
},
|
|
56603
56609
|
en: {
|
|
56604
56610
|
alt: (sel, page) => `Cropped screenshot of the ${sel} element on the ${page} page, outlined`,
|
|
56605
56611
|
notImaged: (n) => `${n} occurrence(s) are not illustrated:`,
|
|
56606
56612
|
reasons: {
|
|
56607
|
-
"no-snapshot": "
|
|
56613
|
+
"no-snapshot": "the page snapshot this finding names is not on this disk \u2014 its file:line reference still holds",
|
|
56608
56614
|
"no-screenshot": "the producer supplied no screenshot for this page, so the pixel tier is inactive here",
|
|
56609
56615
|
"unreadable-image": "the screenshot does not decode (the engine reads 8-bit truecolour only) \u2014 no image beats a wrong one",
|
|
56610
56616
|
"no-boxes": "the element positions do not verify against the serialized DOM, so they were refused wholesale rather than outlining the wrong element",
|
|
@@ -56615,22 +56621,26 @@ var S2 = {
|
|
|
56615
56621
|
"zero-area": "the element paints no box",
|
|
56616
56622
|
"below-the-fold": "the element sits outside the capture (the screenshot covers the viewport, not the whole page) \u2014 its file:line reference still holds",
|
|
56617
56623
|
"unknown-scale": "the image-pixel to CSS-pixel ratio is indeterminate for this page; a guessed ratio would outline the wrong place",
|
|
56618
|
-
|
|
56624
|
+
deduplicated: "the same defect on the same element: it is shown by the illustration of another occurrence, nothing was lost",
|
|
56625
|
+
capped: "a distinct defect left undrawn: this run's crop limit was reached \u2014 raise `--evidence-max` to get them"
|
|
56619
56626
|
}
|
|
56620
56627
|
}
|
|
56621
56628
|
};
|
|
56622
|
-
function
|
|
56629
|
+
function evidenceRefusals(m, pageId, lang) {
|
|
56623
56630
|
const t2 = pageId === null ? m.totals : m.perPage.get(pageId);
|
|
56624
|
-
if (!t2) return
|
|
56631
|
+
if (!t2) return void 0;
|
|
56625
56632
|
const missing = t2.located - t2.imaged;
|
|
56626
|
-
if (missing <= 0) return
|
|
56633
|
+
if (missing <= 0) return void 0;
|
|
56634
|
+
const entries = Object.entries(t2.skipped).filter(([, n]) => n);
|
|
56627
56635
|
const s = S2[lang];
|
|
56628
|
-
|
|
56629
|
-
|
|
56630
|
-
|
|
56631
|
-
|
|
56632
|
-
|
|
56633
|
-
|
|
56636
|
+
return {
|
|
56637
|
+
headline: s.notImaged(missing),
|
|
56638
|
+
reasons: entries.sort((a, b) => b[1] - a[1]).map(([reason, n]) => `${n} \u2014 ${s.reasons[reason]}`)
|
|
56639
|
+
};
|
|
56640
|
+
}
|
|
56641
|
+
function evidenceNotice(m, pageId, lang) {
|
|
56642
|
+
const r = evidenceRefusals(m, pageId, lang);
|
|
56643
|
+
return r ? [r.headline, ...r.reasons.map((x) => `- ${x}`)] : [];
|
|
56634
56644
|
}
|
|
56635
56645
|
function bump(t2, reason) {
|
|
56636
56646
|
t2.skipped[reason] = (t2.skipped[reason] ?? 0) + 1;
|
|
@@ -56678,10 +56688,12 @@ function writeEvidence(result, opts) {
|
|
|
56678
56688
|
const id = findingId(f);
|
|
56679
56689
|
const key = `${f.ruleId}\0${f.selectorHint}`;
|
|
56680
56690
|
const ruleCount = perRule.get(f.ruleId) ?? 0;
|
|
56681
|
-
|
|
56682
|
-
|
|
56683
|
-
|
|
56684
|
-
|
|
56691
|
+
const overLimit = ruleCount >= caps.perRule || onPage >= caps.perPage || manifest.crops.size >= caps.total;
|
|
56692
|
+
const refusal = seen.has(key) ? "deduplicated" : overLimit ? "capped" : null;
|
|
56693
|
+
if (refusal) {
|
|
56694
|
+
manifest.skipped.set(id, refusal);
|
|
56695
|
+
bump(tally, refusal);
|
|
56696
|
+
bump(manifest.totals, refusal);
|
|
56685
56697
|
continue;
|
|
56686
56698
|
}
|
|
56687
56699
|
const crop = cropFinding(ctx, f, opts.geometry);
|
|
@@ -56888,6 +56900,13 @@ function renderHtmlDocument(doc) {
|
|
|
56888
56900
|
}
|
|
56889
56901
|
|
|
56890
56902
|
// src/html-report.ts
|
|
56903
|
+
function refusalBlocks(r) {
|
|
56904
|
+
if (!r) return [];
|
|
56905
|
+
return [
|
|
56906
|
+
{ kind: "note", tone: "warn", runs: [{ text: r.headline }] },
|
|
56907
|
+
{ kind: "list", items: r.reasons.map((text) => [{ text }]) }
|
|
56908
|
+
];
|
|
56909
|
+
}
|
|
56891
56910
|
var T4 = {
|
|
56892
56911
|
fr: {
|
|
56893
56912
|
reportTitle: "Rapport de conformit\xE9",
|
|
@@ -56937,8 +56956,7 @@ var T4 = {
|
|
|
56937
56956
|
openComposite: "Rapport complet, en un seul fichier (imprimable en PDF)",
|
|
56938
56957
|
openPages: "Rapport page par page",
|
|
56939
56958
|
noScreenshot: "Aucune capture d'\xE9cran pour cette page.",
|
|
56940
|
-
screenshotAlt: (n) => `Capture d'\xE9cran de la page ${n}
|
|
56941
|
-
notImaged: (n) => `${n} occurrence(s) n'ont pas pu \xEAtre illustr\xE9es.`
|
|
56959
|
+
screenshotAlt: (n) => `Capture d'\xE9cran de la page ${n}`
|
|
56942
56960
|
},
|
|
56943
56961
|
en: {
|
|
56944
56962
|
reportTitle: "Conformance report",
|
|
@@ -56988,8 +57006,7 @@ var T4 = {
|
|
|
56988
57006
|
openComposite: "Full report, in a single file (printable to PDF)",
|
|
56989
57007
|
openPages: "Page-by-page report",
|
|
56990
57008
|
noScreenshot: "No screenshot for this page.",
|
|
56991
|
-
screenshotAlt: (n) => `Screenshot of the ${n} page
|
|
56992
|
-
notImaged: (n) => `${n} occurrence(s) could not be illustrated.`
|
|
57009
|
+
screenshotAlt: (n) => `Screenshot of the ${n} page`
|
|
56993
57010
|
}
|
|
56994
57011
|
};
|
|
56995
57012
|
function ticks(text) {
|
|
@@ -57084,10 +57101,11 @@ function criterionBlocks(unit, standard, lang, level, crops) {
|
|
|
57084
57101
|
function resolveOccurrence(f, lang) {
|
|
57085
57102
|
return f.msg ? auditorUnitModel({ criteriaId: f.criteriaId, title: "", label: "", refs: [], severity: f.severity, findings: [f] }, CORE2, lang).messages[0] ?? f.message : f.message;
|
|
57086
57103
|
}
|
|
57087
|
-
function findingsBlocks(result, standard, lang, level, crops) {
|
|
57104
|
+
function findingsBlocks(result, standard, lang, level, crops, refusals) {
|
|
57088
57105
|
const t2 = T4[lang];
|
|
57089
57106
|
const { nc, advisory } = partitionUnits(prdUnits(result, standard, lang));
|
|
57090
57107
|
const out2 = [{ kind: "heading", level: 2, text: t2.ncTitle, id: "nc" }];
|
|
57108
|
+
out2.push(...refusalBlocks(refusals?.(null)));
|
|
57091
57109
|
if (!nc.length) out2.push({ kind: "para", runs: [{ text: t2.noNc }] });
|
|
57092
57110
|
for (const u of nc) out2.push(...criterionBlocks(u, standard, lang, level === 2 ? 3 : 4, crops));
|
|
57093
57111
|
if (advisory.length) {
|
|
@@ -57199,7 +57217,7 @@ function compositeDoc(result, opts = {}) {
|
|
|
57199
57217
|
const h = headline(result, standard, lang);
|
|
57200
57218
|
const blocks = [...synthesisBlocks(result, standard, lang)];
|
|
57201
57219
|
if (h.agentRuled) blocks.push({ kind: "note", tone: "warn", runs: ticks(agentMarkNote(lang)) });
|
|
57202
|
-
blocks.push(...findingsBlocks(result, standard, lang, 2, opts.crops));
|
|
57220
|
+
blocks.push(...findingsBlocks(result, standard, lang, 2, opts.crops, opts.refusals));
|
|
57203
57221
|
blocks.push(...scoreboardBlocks(result, standard, lang));
|
|
57204
57222
|
const scope = pagesOf(result);
|
|
57205
57223
|
if (scope.length) blocks.push(...crossGridBlocks(result, derivePages(result, scope), standard, lang));
|
|
@@ -57236,6 +57254,7 @@ function pageDoc(result, page, opts = {}) {
|
|
|
57236
57254
|
blocks.push(
|
|
57237
57255
|
opts.screenshot ? { kind: "figure", src: opts.screenshot, alt: t2.screenshotAlt(page.name) } : { kind: "para", runs: [{ text: t2.noScreenshot, em: true }] }
|
|
57238
57256
|
);
|
|
57257
|
+
blocks.push(...refusalBlocks(opts.refusals?.(page.id)));
|
|
57239
57258
|
blocks.push(...pageGridBlocks(result, page, standard, lang));
|
|
57240
57259
|
const view = { ...result, criteria: page.criteria, findings: page.findings };
|
|
57241
57260
|
const { nc, advisory } = partitionUnits(prdUnits(view, standard, lang));
|
|
@@ -57300,6 +57319,10 @@ function cropLookup(m, lang, hrefOf) {
|
|
|
57300
57319
|
return href ? { href, alt: c2.alt[lang] } : void 0;
|
|
57301
57320
|
};
|
|
57302
57321
|
}
|
|
57322
|
+
function refusalLookup(m, lang) {
|
|
57323
|
+
if (!m) return void 0;
|
|
57324
|
+
return (pageId) => evidenceRefusals(m, pageId, lang);
|
|
57325
|
+
}
|
|
57303
57326
|
function pickRung(cropBytes, shotBytes, budget) {
|
|
57304
57327
|
const sum = (xs) => xs.reduce((a, b) => a + b, 0);
|
|
57305
57328
|
if (sum(cropBytes) + sum(shotBytes) <= budget) return { steps: [], cropsPerCriterion: Number.POSITIVE_INFINITY, shots: true };
|
|
@@ -57361,9 +57384,16 @@ function writeHtml(result, opts) {
|
|
|
57361
57384
|
{ href: `./${compositeName}`, text: t2.docTitle },
|
|
57362
57385
|
...opts.pages ? [{ href: "./pages/index.html", text: t2.pagesTitle }] : []
|
|
57363
57386
|
];
|
|
57387
|
+
const refusals = refusalLookup(opts.evidence, lang);
|
|
57364
57388
|
let compositePath;
|
|
57365
57389
|
if (!flat) {
|
|
57366
|
-
const composite = compositeDoc(result, {
|
|
57390
|
+
const composite = compositeDoc(result, {
|
|
57391
|
+
standard,
|
|
57392
|
+
lang,
|
|
57393
|
+
crops: budgetedCrops,
|
|
57394
|
+
...refusals ? { refusals } : {},
|
|
57395
|
+
nav: nav.map((n) => ({ ...n, current: n.href === `./${compositeName}` }))
|
|
57396
|
+
});
|
|
57367
57397
|
if (notices.length) composite.blocks.unshift({ kind: "note", tone: "warn", runs: noticeRuns(notices) });
|
|
57368
57398
|
compositePath = join41(opts.outDir, compositeName);
|
|
57369
57399
|
writeFileSync16(compositePath, renderHtmlDocument(composite));
|
|
@@ -57401,6 +57431,7 @@ function writeHtml(result, opts) {
|
|
|
57401
57431
|
standard,
|
|
57402
57432
|
lang,
|
|
57403
57433
|
crops: fileCrops,
|
|
57434
|
+
...refusals ? { refusals } : {},
|
|
57404
57435
|
nav: sheetNav,
|
|
57405
57436
|
// Where `cmdPages`' screenshot copier already put the capture: `assets/<id>.png`,
|
|
57406
57437
|
// beside the entry point, seen from wherever this sheet sits.
|
|
@@ -59835,7 +59866,8 @@ Usage:
|
|
|
59835
59866
|
ultra11y audit [--format sarif|github] (CI: SARIF for code scanning, or inline annotations + job summary)
|
|
59836
59867
|
ultra11y audit --in <audit.json> [--fail-on blocking|major|minor] [--format sarif|github] (re-gate an audit already computed \u2014 e.g. one carrying adjudicated verdicts \u2014 without a second detection pass)
|
|
59837
59868
|
ultra11y report --in <audit.json> [--out <dir>] [--standard <pack>] [--format sarif|github] [--lang auto|en|fr]
|
|
59838
|
-
ultra11y report --in <audit.json>
|
|
59869
|
+
ultra11y report --in <audit.json> [--evidence [--evidence-max <n>]] [--out <dir>] (the Markdown report, illustrated with annotated crops)
|
|
59870
|
+
ultra11y report --in <audit.json> --html [--evidence] [--inline-budget <bytes>] [--out <dir>] (index.html + a printable single file)
|
|
59839
59871
|
ultra11y prd --in <audit.json> [--out <dir>] [--split criterion] [--format audit|doc|remediation] [--no-technical] [--standard <pack>] [--lang auto|en|fr]
|
|
59840
59872
|
ultra11y tickets --in <audit.json> [--provider auto|github|gitlab|jira] [--grain criterion|page|page-criterion|single|file] [--transport auto|cli|rest]
|
|
59841
59873
|
ultra11y tickets [--out <dir>] [--max-tickets <n>] [--dry-run] [--json] [--standard <pack>] [--format audit|remediation] [--lang auto|en|fr]
|
|
@@ -59860,7 +59892,7 @@ Usage:
|
|
|
59860
59892
|
ultra11y snapshot list [--root <dir>] [--json]
|
|
59861
59893
|
ultra11y pages --in <audit.json> [--standard <pack>] [--json] [--lang auto|en|fr] (the per-page criterion grid)
|
|
59862
59894
|
ultra11y pages --in <audit.json> --format report [--split page] [--out <dir>] (the per-page report, with screenshots)
|
|
59863
|
-
ultra11y pages --in <audit.json> --format report --out <dir> [--evidence] [--html] (annotated crops of each non-conformity, and the HTML site)
|
|
59895
|
+
ultra11y pages --in <audit.json> --format report --out <dir> [--evidence [--evidence-max <n>]] [--html] (annotated crops of each non-conformity, and the HTML site)
|
|
59864
59896
|
ultra11y pages discover --sitemap <url> | --crawl <url> | --from-snapshots [--depth <n>] [--max <n>] [--write] [--json] (build the page sample)
|
|
59865
59897
|
ultra11y pages --in <audit.json> --standard <pack> --diff <external.json> (hold the grid against an audit someone else performed)
|
|
59866
59898
|
ultra11y import --from file <report.json> | --from ara <id> [--source <adapter>] [--out <dir>] [--json]
|
|
@@ -60259,6 +60291,8 @@ var VALUE_FLAGS2 = /* @__PURE__ */ new Set([
|
|
|
60259
60291
|
"format",
|
|
60260
60292
|
// Bytes of image data the single-file HTML report may carry inline (src/html-emit.ts).
|
|
60261
60293
|
"inline-budget",
|
|
60294
|
+
// Hard cap on the crops one run writes (src/evidence.ts `DEFAULT_CAPS.total`).
|
|
60295
|
+
"evidence-max",
|
|
60262
60296
|
"guidance",
|
|
60263
60297
|
"runtime",
|
|
60264
60298
|
"cwd",
|
|
@@ -60903,7 +60937,7 @@ async function cmdPages(p) {
|
|
|
60903
60937
|
return 0;
|
|
60904
60938
|
}
|
|
60905
60939
|
mkdirSync17(outDir, { recursive: true });
|
|
60906
|
-
const manifest = wantEvidence ? writeEvidence(result, { outDir }) : void 0;
|
|
60940
|
+
const manifest = wantEvidence ? writeEvidence(result, { outDir, ...evidenceCapsOf(p) }) : void 0;
|
|
60907
60941
|
const cropFor = manifest ? (f) => {
|
|
60908
60942
|
const c2 = manifest.crops.get(findingId(f));
|
|
60909
60943
|
return c2 ? { href: c2.href, alt: c2.alt[lang] } : void 0;
|
|
@@ -60916,7 +60950,8 @@ async function cmdPages(p) {
|
|
|
60916
60950
|
for (const line of total) console.error(line);
|
|
60917
60951
|
}
|
|
60918
60952
|
const evidenceOpts = {
|
|
60919
|
-
...cropFor ? { cropFor } : {}
|
|
60953
|
+
...cropFor ? { cropFor } : {},
|
|
60954
|
+
...manifest ? { evidenceNotice: (id) => evidenceNotice(manifest, id, lang) } : {}
|
|
60920
60955
|
};
|
|
60921
60956
|
if (!split) {
|
|
60922
60957
|
const file = join47(outDir, `pages-${result.date}.md`);
|
|
@@ -60939,12 +60974,8 @@ async function cmdPages(p) {
|
|
|
60939
60974
|
const sheet = (id) => `page-${id}.md`;
|
|
60940
60975
|
const hrefs = new Map(derived.map((pg) => [pg.id, `./${sheet(pg.id)}`]));
|
|
60941
60976
|
for (const pg of derived) {
|
|
60942
|
-
|
|
60943
|
-
|
|
60944
|
-
join47(outDir, sheet(pg.id)),
|
|
60945
|
-
`${renderPageDocument(result, pg, { standard, lang, screenshots: shots, ...evidenceOpts, ...notice.length ? { evidenceNotice: notice } : {} })}
|
|
60946
|
-
`
|
|
60947
|
-
);
|
|
60977
|
+
writeFileSync19(join47(outDir, sheet(pg.id)), `${renderPageDocument(result, pg, { standard, lang, screenshots: shots, ...evidenceOpts })}
|
|
60978
|
+
`);
|
|
60948
60979
|
}
|
|
60949
60980
|
const index = join47(outDir, "index.md");
|
|
60950
60981
|
writeFileSync19(index, `${renderPagesIndex(result, derived, { standard, lang, hrefs })}
|
|
@@ -60962,11 +60993,20 @@ async function cmdPages(p) {
|
|
|
60962
60993
|
console.log(index);
|
|
60963
60994
|
return html?.imagesDropped ? 1 : 0;
|
|
60964
60995
|
}
|
|
60965
|
-
function
|
|
60966
|
-
const raw = p.flags[
|
|
60996
|
+
function positiveIntFlag(p, name2) {
|
|
60997
|
+
const raw = p.flags[name2];
|
|
60967
60998
|
if (typeof raw !== "string" || !raw) return void 0;
|
|
60968
60999
|
const n = Number.parseInt(raw, 10);
|
|
60969
|
-
|
|
61000
|
+
if (Number.isFinite(n) && n > 0) return n;
|
|
61001
|
+
console.error(`ultra11y: --${name2} expects a positive integer (got "${raw}"). Ignored \u2014 the default applies.`);
|
|
61002
|
+
return void 0;
|
|
61003
|
+
}
|
|
61004
|
+
function budgetOf(p) {
|
|
61005
|
+
return positiveIntFlag(p, "inline-budget");
|
|
61006
|
+
}
|
|
61007
|
+
function evidenceCapsOf(p) {
|
|
61008
|
+
const total = positiveIntFlag(p, "evidence-max");
|
|
61009
|
+
return total === void 0 ? {} : { caps: { total } };
|
|
60970
61010
|
}
|
|
60971
61011
|
function emitHtml(result, opts) {
|
|
60972
61012
|
const res = writeHtml(result, opts);
|
|
@@ -61205,17 +61245,18 @@ async function cmdReport(p) {
|
|
|
61205
61245
|
emitCiFormat(result, ciFormat, standard, lang);
|
|
61206
61246
|
return 0;
|
|
61207
61247
|
}
|
|
61208
|
-
const
|
|
61248
|
+
const manifest = p.flags.evidence === true ? writeEvidence(result, { outDir: out2, ...evidenceCapsOf(p) }) : void 0;
|
|
61249
|
+
if (manifest) {
|
|
61250
|
+
for (const line of evidenceNotice(manifest, null, lang)) console.error(line);
|
|
61251
|
+
}
|
|
61252
|
+
const cropFor = manifest ? (f) => {
|
|
61253
|
+
const c2 = manifest.crops.get(findingId(f));
|
|
61254
|
+
return c2 ? { href: c2.href, alt: c2.alt[lang] } : void 0;
|
|
61255
|
+
} : void 0;
|
|
61256
|
+
const path = writeReport(result, { out: out2, lang, standard, ...cropFor ? { cropFor } : {} });
|
|
61209
61257
|
let html;
|
|
61210
61258
|
if (p.flags.html === true) {
|
|
61211
|
-
const manifest = p.flags.evidence === true ? writeEvidence(result, { outDir: out2 }) : void 0;
|
|
61212
|
-
if (manifest) for (const line of evidenceNotice(manifest, null, lang)) console.error(line);
|
|
61213
61259
|
html = emitHtml(result, { outDir: out2, standard, lang, ...manifest ? { evidence: manifest } : {}, inlineBudget: budgetOf(p) });
|
|
61214
|
-
} else if (p.flags.evidence === true) {
|
|
61215
|
-
console.error(
|
|
61216
|
-
lang === "fr" ? "ultra11y report : --evidence n'illustre que le rapport HTML. Ajoutez --html, ou utilisez `pages --format report --evidence` pour des fiches Markdown illustr\xE9es." : "ultra11y report: --evidence only illustrates the HTML report. Add --html, or use `pages --format report --evidence` for illustrated Markdown sheets."
|
|
61217
|
-
);
|
|
61218
|
-
return 2;
|
|
61219
61260
|
}
|
|
61220
61261
|
const untested = isCore(standard) ? [] : untestedNeedsRendering(result);
|
|
61221
61262
|
const partial = untested.length > 0;
|
|
@@ -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: 4.
|
|
8
|
+
version: 4.2.0
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# review-a11y — accessibility review of changed code
|