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
|
@@ -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;
|
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: 4.
|
|
7
|
+
version: 4.2.0
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# ultra11y — audit WCAG 2.2 AA and write accessible markup
|
|
@@ -271,7 +271,7 @@ opens. `html: 'true'` and `evidence: 'true'` are on by default, so it now has a
|
|
|
271
271
|
audits/
|
|
272
272
|
├── index.html ← the entry point: rate, synthesis, page scoreboard, links
|
|
273
273
|
├── ultra11y-<std>-<date>.html ← the whole audit in ONE file, printable to PDF
|
|
274
|
-
├── <std>-<date>.md ← the Markdown report
|
|
274
|
+
├── <std>-<date>.md ← the Markdown report, illustrated by the same crops
|
|
275
275
|
├── audit-latest.json ← the machine-readable audit (unchanged)
|
|
276
276
|
├── assets/
|
|
277
277
|
│ ├── <page-id>.png ← the page screenshot
|
|
@@ -298,26 +298,46 @@ to a `::warning::` and cannot catch its own regression.
|
|
|
298
298
|
|
|
299
299
|
### The crops
|
|
300
300
|
|
|
301
|
-
An occurrence
|
|
301
|
+
An occurrence gains a sub-bullet showing the element, ringed:
|
|
302
302
|
|
|
303
303
|
```md
|
|
304
304
|
- [ ] `.ultra11y/pages/accueil/dom.html:412` (`div.card`) — <img> without an alt attribute…
|
|
305
305
|
- 
|
|
306
306
|
```
|
|
307
307
|
|
|
308
|
-
|
|
308
|
+
**Every document that shows a non-conformity shows its crop**: the Markdown conformance
|
|
309
|
+
report, the per-page sheets, and the single-file composite. They all reference one set of
|
|
310
|
+
files in `assets/`; only the composite pays the base64 tax, because only it has to travel
|
|
311
|
+
alone. `report --evidence` therefore works with or without `--html` — the images are not an
|
|
312
|
+
HTML-only tier. `prd`, `gh` and `jira` deliberately stay text: their output is read on
|
|
313
|
+
github.com, where a path relative to `audits/` resolves to nothing, and a broken image in an
|
|
314
|
+
issue is worse than no image.
|
|
315
|
+
|
|
316
|
+
Crops are derived AT RENDER TIME from the page snapshot, never stamped on the finding: a
|
|
309
317
|
rectangle in pixels is a property of the image, and a stamped box goes silently wrong the
|
|
310
318
|
moment the audit is re-rendered against a different capture. So `evidence` needs snapshots —
|
|
311
|
-
`snapshot: 'true'` (the default), the E2E plugins, or `ultra11y dev`.
|
|
319
|
+
`snapshot: 'true'` (the default), the E2E plugins, or `ultra11y dev`. Audited from source
|
|
320
|
+
alone, the tier is simply inactive and says nothing: a source finding never had pixels, so it
|
|
321
|
+
is not an occurrence that failed to be illustrated.
|
|
312
322
|
|
|
313
323
|
The mark is **not monochrome**: a white halo, a red ring and corner brackets whose *shape*
|
|
314
324
|
says "here". A tool that reports 1.4.1 failures cannot ship a deliverable that commits one.
|
|
315
325
|
|
|
316
326
|
When a crop cannot be drawn, the report says so per page and per criterion, with the reason —
|
|
317
|
-
`no-screenshot`, `below-the-fold`, `unknown-scale`,
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
327
|
+
`no-screenshot`, `below-the-fold`, `unknown-scale`, and nine more. **An occurrence without a
|
|
328
|
+
picture must never read as an occurrence without a defect.**
|
|
329
|
+
|
|
330
|
+
Two of those reasons look alike and are not:
|
|
331
|
+
|
|
332
|
+
| Reason | What it means | Costs the reader |
|
|
333
|
+
|---|---|---|
|
|
334
|
+
| `deduplicated` | the same defect on the same element, already shown by another occurrence's picture | nothing |
|
|
335
|
+
| `capped` | a **distinct** defect left undrawn because a limit ran out | a picture — raise `evidence-max` |
|
|
336
|
+
|
|
337
|
+
`evidence-max` (200 by default) is the run-wide fuse and the only one that is a setting: it
|
|
338
|
+
guards the upload's size. The per-rule (6) and per-page (12) limits are fixed, because they
|
|
339
|
+
are what makes one picture stand for a repeated defect — one design-system failure multiplied
|
|
340
|
+
by 38 routes is one defect, not 472 pictures.
|
|
321
341
|
|
|
322
342
|
### Printing it
|
|
323
343
|
|
|
@@ -326,9 +346,11 @@ The single file is the deliverable an auditor hands to a client: open
|
|
|
326
346
|
its evidence on one sheet rather than splitting them across a page break.
|
|
327
347
|
|
|
328
348
|
Its images travel inside it as `data:` URIs, and base64 costs a third more than the bytes it
|
|
329
|
-
encodes — so there is a budget (
|
|
330
|
-
screenshots go first, then all but one crop per
|
|
331
|
-
rung is written **into the document** and onto
|
|
349
|
+
encodes — so there is a budget (the `inline-budget` input, `--inline-budget` on the CLI, 12 MB
|
|
350
|
+
by default) and a ladder. Over budget, page screenshots go first, then all but one crop per
|
|
351
|
+
criterion, then the images entirely. Every rung is written **into the document** and onto
|
|
352
|
+
stderr, and the other documents still reference every crop as a file. Images degrade; the
|
|
353
|
+
non-conformities
|
|
332
354
|
never do.
|
|
333
355
|
|
|
334
356
|
### Publishing it as a page (optional, and it costs something)
|
|
@@ -318,21 +318,32 @@ The mark is not monochrome: a white halo, a red ring, corner brackets whose shap
|
|
|
318
318
|
and a dashed inner line that survives a monochrome print. This tool reports 1.4.1 failures; its
|
|
319
319
|
own deliverable cannot commit one.
|
|
320
320
|
|
|
321
|
-
**Nothing is cut in silence.**
|
|
322
|
-
page and per criterion in the sheet itself:
|
|
321
|
+
**Nothing is cut in silence.** Thirteen refusal reasons, each with its own sentence, counted
|
|
322
|
+
per page and per criterion in the sheet itself:
|
|
323
323
|
|
|
324
324
|
| Reason | What happened |
|
|
325
325
|
|---|---|
|
|
326
|
-
| `no-snapshot`
|
|
326
|
+
| `no-snapshot` | the capture this finding's own path names is not on this disk |
|
|
327
|
+
| `no-screenshot` | the producer captured no image for that page |
|
|
327
328
|
| `unreadable-image` · `no-boxes` · `truncated` | the capture or the box digest cannot be trusted |
|
|
328
329
|
| `no-offsets` · `unjoinable` | the finding has no byte range, or it resolves to no element |
|
|
329
330
|
| `page-scope` | the finding is about the document, not an element you can frame |
|
|
330
331
|
| `zero-area` · `below-the-fold` | the element is invisible, or outside a viewport-only capture |
|
|
331
332
|
| `unknown-scale` | the device pixel ratio could not be derived — a crop would be off-target |
|
|
332
|
-
| `
|
|
333
|
+
| `deduplicated` | the same defect on the same element — another occurrence's picture shows it |
|
|
334
|
+
| `capped` | a **distinct** defect went undrawn: 6 per rule, 12 per page, `--evidence-max` per run |
|
|
335
|
+
|
|
336
|
+
The last two are counted apart on purpose. `deduplicated` costs the reader nothing — the
|
|
337
|
+
defect is on screen, under another occurrence. `capped` is a real gap, and it is the only one
|
|
338
|
+
a setting can close.
|
|
339
|
+
|
|
340
|
+
A finding raised on SOURCE is in neither column: it never had pixels, so it is not an
|
|
341
|
+
occurrence that failed to be illustrated, and a repository audited from source alone gets no
|
|
342
|
+
notice at all rather than one line per finding.
|
|
333
343
|
|
|
334
344
|
An occurrence with no picture must never read as an occurrence with no defect, so the sheet
|
|
335
|
-
says how many were not illustrated and why.
|
|
345
|
+
says how many were not illustrated and why. So does the combined document, the HTML sheets and
|
|
346
|
+
the composite — every surface that can show a crop can say what it did not show.
|
|
336
347
|
|
|
337
348
|
Without `--evidence` the Markdown is **byte-identical** to what it was. The crop bullet carries
|
|
338
349
|
no `[ ]`, so `verify` builds the same worklist with or without it — indentation alone would not
|
|
@@ -340,10 +351,18 @@ be enough, since `AUDITOR_OCCURRENCE` is anchored `^\s*-\s\[ \]` and tolerates l
|
|
|
340
351
|
|
|
341
352
|
### The same, as a page (`--html`)
|
|
342
353
|
|
|
343
|
-
`--html` writes `index.html` and one `page-<id>.html` beside the Markdown they mirror
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
354
|
+
`--html` writes `index.html` and one `page-<id>.html` beside the Markdown they mirror.
|
|
355
|
+
Self-contained: no script, no external asset, nothing pointing outside the output directory.
|
|
356
|
+
|
|
357
|
+
**The printable single file comes from `report --html`, not from here.** One composite per
|
|
358
|
+
artifact: emitting one from both commands would put a second copy of every inlined crop into
|
|
359
|
+
the same upload, so `pages --html` deliberately writes none.
|
|
360
|
+
|
|
361
|
+
```sh
|
|
362
|
+
node scripts/ultra11y.mjs report --in audits/audit-latest.json --html --evidence --out audits
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
See `references/ci.md` for the artifact layout and the inline-size budget.
|
|
347
366
|
|
|
348
367
|
## Holding an external audit against the grid
|
|
349
368
|
|