ultra11y 2.31.2 → 2.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.opencode/plugins/ultra11y.js +1 -1
- package/package.json +1 -1
- package/scripts/build-pack-rgaa.mjs +31 -0
- package/scripts/ultra11y.mjs +277 -63
- package/scripts/vendor/wcag-2.2-fr.json +1 -1
- package/scripts/vendor/wcag-2.2-sc-universe.json +1 -1
- package/skills/review-a11y/SKILL.md +1 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +277 -63
- package/skills/ultra11y/SKILL.md +11 -5
- package/skills/ultra11y/references/orchestrators.md +176 -0
- package/skills/ultra11y/references/pages.md +7 -3
- package/skills/ultra11y/scripts/ultra11y.mjs +277 -63
- package/src/adjudicate.ts +161 -14
- package/src/auditor.ts +2 -2
- package/src/cli.ts +70 -13
- package/src/data/standards/rgaa.json +10 -5
- package/src/data/wcag-universe.json +1 -1
- package/src/gh.ts +56 -9
- package/src/llm.ts +23 -2
- package/src/pages-report.ts +51 -12
- package/src/prd.ts +3 -2
- package/src/report.ts +70 -19
- package/src/standards/derive.ts +33 -2
- package/src/standards/pack.ts +13 -0
- package/src/standards/types.ts +13 -0
- package/src/types.ts +1 -1
package/scripts/ultra11y.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { realpathSync as realpathSync6, writeFileSync as writeFileSync17, mkdirSync as mkdirSync15, existsSync as existsSync31, readFileSync as readFileSync27, appendFileSync } from "fs";
|
|
4
|
+
import { realpathSync as realpathSync6, writeFileSync as writeFileSync17, mkdirSync as mkdirSync15, existsSync as existsSync31, readFileSync as readFileSync27, appendFileSync, copyFileSync as copyFileSync4 } from "fs";
|
|
5
5
|
import { join as join45, relative as relative5, sep as sep7, dirname as dirname14 } from "path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
|
|
7
7
|
|
|
8
8
|
// src/types.ts
|
|
9
|
-
var VERSION = "2.
|
|
9
|
+
var VERSION = "2.32.0";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
|
|
12
12
|
// src/audit.ts
|
|
@@ -1197,7 +1197,7 @@ var wcag_universe_default = {
|
|
|
1197
1197
|
wcagVersion: "2.2",
|
|
1198
1198
|
source: "https://www.w3.org/TR/WCAG22/",
|
|
1199
1199
|
criteriaSource: "https://github.com/w3c/wcag",
|
|
1200
|
-
provenance: "Full WCAG 2.x SC universe (all levels incl. AAA, and the removed 4.1.1 Parsing) fetched from raw.githubusercontent.com/w3c/wcag@main on 2026-08-
|
|
1200
|
+
provenance: "Full WCAG 2.x SC universe (all levels incl. AAA, and the removed 4.1.1 Parsing) fetched from raw.githubusercontent.com/w3c/wcag@main on 2026-08-12 via `node scripts/build-standards.mjs --refresh-universe`. Classification: core-AA = ships in src/data/wcag.json (the shipped WCAG 2.2 AA core); out-of-core = WCAG AAA; removed = obsolete (4.1.1).",
|
|
1201
1201
|
criteria: [
|
|
1202
1202
|
{
|
|
1203
1203
|
id: "1.1.1",
|
|
@@ -41345,7 +41345,8 @@ var rgaa_default = {
|
|
|
41345
41345
|
wcag: ["1.4.2"],
|
|
41346
41346
|
appliesTo: {
|
|
41347
41347
|
ruleIds: ["autoplay-media", "axe:no-autoplay-audio"]
|
|
41348
|
-
}
|
|
41348
|
+
},
|
|
41349
|
+
judgment: true
|
|
41349
41350
|
},
|
|
41350
41351
|
{
|
|
41351
41352
|
id: "4.11",
|
|
@@ -41984,7 +41985,8 @@ var rgaa_default = {
|
|
|
41984
41985
|
wcag: ["3.1.1"],
|
|
41985
41986
|
appliesTo: {
|
|
41986
41987
|
ruleIds: ["axe:html-lang-valid", "lang-invalid"]
|
|
41987
|
-
}
|
|
41988
|
+
},
|
|
41989
|
+
judgment: true
|
|
41988
41990
|
},
|
|
41989
41991
|
{
|
|
41990
41992
|
id: "8.5",
|
|
@@ -42020,7 +42022,8 @@ var rgaa_default = {
|
|
|
42020
42022
|
wcag: ["2.4.2"],
|
|
42021
42023
|
appliesTo: {
|
|
42022
42024
|
ruleIds: []
|
|
42023
|
-
}
|
|
42025
|
+
},
|
|
42026
|
+
judgment: true
|
|
42024
42027
|
},
|
|
42025
42028
|
{
|
|
42026
42029
|
id: "8.7",
|
|
@@ -43414,7 +43417,8 @@ var rgaa_default = {
|
|
|
43414
43417
|
wcag: ["1.1.1", "1.3.1", "1.3.2", "2.4.1", "2.4.3", "3.1.1", "4.1.2"],
|
|
43415
43418
|
appliesTo: {
|
|
43416
43419
|
ruleIds: []
|
|
43417
|
-
}
|
|
43420
|
+
},
|
|
43421
|
+
judgment: true
|
|
43418
43422
|
},
|
|
43419
43423
|
{
|
|
43420
43424
|
id: "13.4",
|
|
@@ -43436,7 +43440,8 @@ var rgaa_default = {
|
|
|
43436
43440
|
wcag: ["1.1.1", "1.3.1", "1.3.2", "2.4.1", "2.4.3", "3.1.1", "4.1.2"],
|
|
43437
43441
|
appliesTo: {
|
|
43438
43442
|
ruleIds: []
|
|
43439
|
-
}
|
|
43443
|
+
},
|
|
43444
|
+
judgment: true
|
|
43440
43445
|
},
|
|
43441
43446
|
{
|
|
43442
43447
|
id: "13.5",
|
|
@@ -45444,6 +45449,10 @@ function title(pack, c2, lang) {
|
|
|
45444
45449
|
function titlePlain(pack, c2, lang) {
|
|
45445
45450
|
return localize(pack, c2.titlePlain, lang);
|
|
45446
45451
|
}
|
|
45452
|
+
function packTestIds(pack, id) {
|
|
45453
|
+
const tests = getCriterion(pack, id)?.tests;
|
|
45454
|
+
return tests ? Object.keys(tests).map((k) => `${id}.${k}`) : [];
|
|
45455
|
+
}
|
|
45447
45456
|
function resolveGlossary(packKey, anchor) {
|
|
45448
45457
|
return packGlossary(packKey)?.[anchor];
|
|
45449
45458
|
}
|
|
@@ -48357,7 +48366,7 @@ function decide(payload, deps = {}) {
|
|
|
48357
48366
|
}
|
|
48358
48367
|
|
|
48359
48368
|
// src/report.ts
|
|
48360
|
-
import { existsSync as existsSync16, mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
48369
|
+
import { copyFileSync, existsSync as existsSync16, mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
48361
48370
|
import { join as join29, relative as relative3 } from "path";
|
|
48362
48371
|
|
|
48363
48372
|
// src/prd.ts
|
|
@@ -48384,7 +48393,7 @@ function packCriteriaForFinding(pack, finding) {
|
|
|
48384
48393
|
return pack.criteria.filter((pc) => pc.wcag.includes(finding.criteriaId) && (!pc.appliesTo || ruleMatches(finding.ruleId, pc.appliesTo.ruleIds))).map((pc) => pc.id);
|
|
48385
48394
|
}
|
|
48386
48395
|
function packConformancePct(derived) {
|
|
48387
|
-
const c2 = derived.filter((d) => d.status === "C").length;
|
|
48396
|
+
const c2 = derived.filter((d) => d.status === "C" && d.decidedBy !== "agent").length;
|
|
48388
48397
|
const nc = derived.filter((d) => d.status === "NC").length;
|
|
48389
48398
|
return c2 + nc === 0 ? 100 : Math.round(c2 / (c2 + nc) * 100);
|
|
48390
48399
|
}
|
|
@@ -48413,6 +48422,10 @@ function applySecondaryMappings(base, pc, enabled, sources, defaultLocale) {
|
|
|
48413
48422
|
if (added.some((f) => !f.advisory)) return { id: pc.id, theme: pc.theme, status: aggregate2([base.status, "NC"]), findings, scs: base.scs };
|
|
48414
48423
|
return { ...base, findings };
|
|
48415
48424
|
}
|
|
48425
|
+
function judgmentGuard(r, pc) {
|
|
48426
|
+
if (!pc.judgment || r.status !== "C") return r;
|
|
48427
|
+
return { ...r, status: "manual", judgment: true };
|
|
48428
|
+
}
|
|
48416
48429
|
function derivePackResults(audit2, packKey) {
|
|
48417
48430
|
const pack = loadPack(packKey);
|
|
48418
48431
|
const byScId = new Map(audit2.criteria.map((c2) => [c2.id, c2]));
|
|
@@ -48460,7 +48473,7 @@ function derivePackResults(audit2, packKey) {
|
|
|
48460
48473
|
decidedBy: "agent"
|
|
48461
48474
|
};
|
|
48462
48475
|
}
|
|
48463
|
-
const base = deriveBase(pc);
|
|
48476
|
+
const base = judgmentGuard(deriveBase(pc), pc);
|
|
48464
48477
|
return enabledSecondary.length ? applySecondaryMappings(base, pc, enabledSecondary, secondarySources, pack.defaultLocale) : base;
|
|
48465
48478
|
});
|
|
48466
48479
|
}
|
|
@@ -48654,7 +48667,7 @@ function renderAuditorUnit(unit, standard, lang, opts = {}) {
|
|
|
48654
48667
|
const pc = pack.criteria.find((c2) => c2.id === unit.criteriaId);
|
|
48655
48668
|
if (pc) out2.push(`**${v.theme}** : ${pc.theme}. ${themeName(pack, pc.theme, lang) ?? ""}`.trimEnd());
|
|
48656
48669
|
out2.push(`**${v.criterion}** : ${unit.criteriaId} \u2014 ${unit.title}`);
|
|
48657
|
-
const testNums =
|
|
48670
|
+
const testNums = packTestIds(pack, unit.criteriaId);
|
|
48658
48671
|
if (testNums.length) out2.push(`**${v.test}(s)** : ${testNums.join(" \xB7 ")}`);
|
|
48659
48672
|
if (unit.refs.length) out2.push(`**WCAG** : ${unit.refs.map((sc) => `${sc}${scLevel(sc)}`).join(" \xB7 ")}`);
|
|
48660
48673
|
}
|
|
@@ -49066,8 +49079,8 @@ function toRuleOnSection(r, standard, lang) {
|
|
|
49066
49079
|
for (const pc of manual) {
|
|
49067
49080
|
const crit = pack.criteria.find((c2) => c2.id === pc.id);
|
|
49068
49081
|
const title2 = crit ? titlePlain(pack, crit, lang) : pc.id;
|
|
49069
|
-
const tests =
|
|
49070
|
-
out2.push(`- [ ] **${pack.name} ${pc.id}** \u2014 ${title2}${tests.length ? ` \xB7 ${s.tests}: ${tests.map((
|
|
49082
|
+
const tests = packTestIds(pack, pc.id);
|
|
49083
|
+
out2.push(`- [ ] **${pack.name} ${pc.id}** \u2014 ${title2}${tests.length ? ` \xB7 ${s.tests}: ${tests.map((t2) => `\`${t2}\``).join(" ")}` : ""}`);
|
|
49071
49084
|
}
|
|
49072
49085
|
out2.push("");
|
|
49073
49086
|
return out2;
|
|
@@ -49340,6 +49353,8 @@ var L4 = {
|
|
|
49340
49353
|
sev: { bloquant: "Bloquant", majeur: "Majeur", mineur: "Mineur" },
|
|
49341
49354
|
none: "Aucune non-conformit\xE9 d\xE9tect\xE9e par le moteur statique.",
|
|
49342
49355
|
cTitle: "3. Crit\xE8res conformes (C)",
|
|
49356
|
+
cAgentTitle: "Conformes par adjudication de l'agent (jugement, non prouv\xE9 par le moteur)",
|
|
49357
|
+
cAgentNote: "Ces crit\xE8res ont \xE9t\xE9 tranch\xE9s par l'agent IA \xE0 partir des \xE9vidences cit\xE9es, non d\xE9cid\xE9s par le moteur d\xE9terministe. Ils sont gat\xE9s (chaque verdict cite une \xE9vidence r\xE9solvable) mais restent un jugement : ils ne sont pas compt\xE9s dans le taux de r\xE9ussite automatique ci-dessus.",
|
|
49343
49358
|
naTitle: "4. Crit\xE8res non applicables (NA)",
|
|
49344
49359
|
manualTitle: "5. Crit\xE8res \xE0 adjuger (jugement / rendu) \u2014 non d\xE9cid\xE9s par le moteur statique",
|
|
49345
49360
|
manualWarn: "Adjugez-les avec `verify --manual` (l'agent d\xE9cide depuis la source, de fa\xE7on gat\xE9e) ; les crit\xE8res de rendu passent par `scan`. Aucun ne doit \xEAtre marqu\xE9 \xAB conforme \xBB sans justification enregistr\xE9e et gat\xE9e.",
|
|
@@ -49347,6 +49362,7 @@ var L4 = {
|
|
|
49347
49362
|
manualHowTo: "G\xE9n\xE9rez la worklist : `verify --manual --in <audit.json> --standard <pack> --out <dir>`. Chaque item y porte l'\xE9nonc\xE9 complet de ses tests, sa note technique, ses cas particuliers, sa guidance et les termes que le r\xE9f\xE9rentiel d\xE9finit.",
|
|
49348
49363
|
outOfScope: "Hors p\xE9rim\xE8tre moteur \u2014 mapp\xE9 sur des SC hors WCAG 2.2 AA ; v\xE9rification manuelle.",
|
|
49349
49364
|
scopedOut: "Les non-conformit\xE9s WCAG relev\xE9es concernent des \xE9l\xE9ments hors du p\xE9rim\xE8tre de ce crit\xE8re \u2014 \xE0 \xE9valuer s\xE9par\xE9ment.",
|
|
49365
|
+
judgment: "L\u2019\xE9nonc\xE9 du crit\xE8re demande davantage que les CS WCAG auxquels il est rattach\xE9 \u2014 le moteur n\u2019y a pas r\xE9pondu, \xE0 trancher.",
|
|
49350
49366
|
nothing: "Aucun.",
|
|
49351
49367
|
dedup: "D\xE9dup",
|
|
49352
49368
|
canonical: "fichier(s) canonique(s) audit\xE9(s)",
|
|
@@ -49392,6 +49408,8 @@ var L4 = {
|
|
|
49392
49408
|
sev: { bloquant: "Blocking", majeur: "Major", mineur: "Minor" },
|
|
49393
49409
|
none: "No non-conformity detected by the static engine.",
|
|
49394
49410
|
cTitle: "3. Conforming criteria (C)",
|
|
49411
|
+
cAgentTitle: "Conforming by agent adjudication (judgement, not proven by the engine)",
|
|
49412
|
+
cAgentNote: "These criteria were ruled on by the AI agent from the evidence it cited, not decided by the deterministic engine. They are gated (every verdict cites resolvable evidence) but remain a judgement: they are not counted in the automatic pass rate above.",
|
|
49395
49413
|
naTitle: "4. Not-applicable criteria (NA)",
|
|
49396
49414
|
manualTitle: "5. Criteria to adjudicate (judgment / rendering) \u2014 not decided by the static engine",
|
|
49397
49415
|
manualWarn: "Adjudicate these with `verify --manual` (the agent decides from source, gated); rendering criteria go to `scan`. None may be marked \u201Cconforming\u201D without a recorded, gated justification.",
|
|
@@ -49399,6 +49417,7 @@ var L4 = {
|
|
|
49399
49417
|
manualHowTo: "Generate the worklist: `verify --manual --in <audit.json> --standard <pack> --out <dir>`. Each item carries the full wording of its tests, its technical note, its particular cases, its guidance and the terms the standard defines.",
|
|
49400
49418
|
outOfScope: "Out of engine scope \u2014 mapped to SCs outside WCAG 2.2 AA; manual verification.",
|
|
49401
49419
|
scopedOut: "The WCAG failures found concern elements outside this criterion's scope \u2014 assess separately.",
|
|
49420
|
+
judgment: "The criterion asks more than the WCAG SCs it maps to \u2014 the engine did not answer it; rule on it.",
|
|
49402
49421
|
nothing: "None.",
|
|
49403
49422
|
dedup: "Dedup",
|
|
49404
49423
|
canonical: "canonical file(s) audited",
|
|
@@ -49507,11 +49526,18 @@ function render(r, lang, opts) {
|
|
|
49507
49526
|
const notes = pageScope.find((x) => x.id === pg.id)?.notes;
|
|
49508
49527
|
if (notes) out2.push(`- _${notes}_`);
|
|
49509
49528
|
const shot = join29(PAGES_DIR, pg.id, "screen.png");
|
|
49510
|
-
if (existsSync16(shot))
|
|
49511
|
-
|
|
49512
|
-
|
|
49513
|
-
|
|
49514
|
-
|
|
49529
|
+
if (existsSync16(shot)) {
|
|
49530
|
+
let href = relative3(opts.outDir ?? ".", shot).split("\\").join("/");
|
|
49531
|
+
if (opts.outDir) {
|
|
49532
|
+
try {
|
|
49533
|
+
mkdirSync7(join29(opts.outDir, "assets"), { recursive: true });
|
|
49534
|
+
copyFileSync(shot, join29(opts.outDir, "assets", `${pg.id}.png`));
|
|
49535
|
+
href = `./assets/${pg.id}.png`;
|
|
49536
|
+
} catch {
|
|
49537
|
+
}
|
|
49538
|
+
}
|
|
49539
|
+
out2.push("", ``);
|
|
49540
|
+
}
|
|
49515
49541
|
for (const f of nc.slice(0, 30)) {
|
|
49516
49542
|
const crits = pack ? packCriteriaForFinding(pack, f) : [];
|
|
49517
49543
|
const label = crits.length ? crits.join(", ") : f.criteriaId;
|
|
@@ -49522,7 +49548,16 @@ function render(r, lang, opts) {
|
|
|
49522
49548
|
}
|
|
49523
49549
|
out2.push(`## ${s.cTitle}`, "");
|
|
49524
49550
|
const conform = rows.filter((x) => x.status === "C");
|
|
49525
|
-
|
|
49551
|
+
const byEngine = conform.filter((x) => x.decidedBy !== "agent");
|
|
49552
|
+
const byAgent = conform.filter((x) => x.decidedBy === "agent");
|
|
49553
|
+
if (!conform.length) out2.push(s.nothing, "");
|
|
49554
|
+
else {
|
|
49555
|
+
if (byEngine.length) out2.push(...byEngine.map((x) => `- ${x.label}`), "");
|
|
49556
|
+
if (byAgent.length) {
|
|
49557
|
+
out2.push(`### ${s.cAgentTitle}`, "", `> ${s.cAgentNote}`, "");
|
|
49558
|
+
out2.push(...byAgent.map((x) => `- ${x.label}${x.justification ? ` \u2014 _${x.justification}_` : ""}`), "");
|
|
49559
|
+
}
|
|
49560
|
+
}
|
|
49526
49561
|
out2.push(`## ${s.naTitle}`, "");
|
|
49527
49562
|
const na = rows.filter((x) => x.status === "NA");
|
|
49528
49563
|
out2.push(na.length ? na.map((x) => `- ${x.label}${x.justification ? ` \u2014 _${x.justification}_` : ""}`).join("\n") : s.nothing, "");
|
|
@@ -49532,8 +49567,8 @@ function render(r, lang, opts) {
|
|
|
49532
49567
|
else {
|
|
49533
49568
|
const pack5 = isCore(opts.standard) ? void 0 : loadPack(opts.standard);
|
|
49534
49569
|
for (const x of manual) {
|
|
49535
|
-
const tests = pack5 ?
|
|
49536
|
-
const testRef = tests.length ? ` \u2014 ${s.testsToRule}: ${tests.map((
|
|
49570
|
+
const tests = pack5 ? packTestIds(pack5, x.id) : [];
|
|
49571
|
+
const testRef = tests.length ? ` \u2014 ${s.testsToRule}: ${tests.map((t2) => `\`${t2}\``).join(" \xB7 ")}` : "";
|
|
49537
49572
|
out2.push(`- ${x.label}${x.justification ? ` \u2014 _${x.justification}_` : ""}${testRef}`);
|
|
49538
49573
|
}
|
|
49539
49574
|
out2.push("", `> ${s.manualHowTo}`, "");
|
|
@@ -49545,7 +49580,14 @@ function renderReport(r, lang = "en", outDir) {
|
|
|
49545
49580
|
const byGuideline = /* @__PURE__ */ new Map();
|
|
49546
49581
|
for (const c2 of r.criteria) {
|
|
49547
49582
|
const title2 = scTitle(c2.id, lang);
|
|
49548
|
-
const row = {
|
|
49583
|
+
const row = {
|
|
49584
|
+
id: c2.id,
|
|
49585
|
+
label: title2 ? `${c2.id} \u2014 ${title2}` : c2.id,
|
|
49586
|
+
status: c2.status,
|
|
49587
|
+
findings: c2.findings,
|
|
49588
|
+
justification: c2.justification,
|
|
49589
|
+
decidedBy: c2.decidedBy
|
|
49590
|
+
};
|
|
49549
49591
|
(byGuideline.get(c2.guideline) ?? byGuideline.set(c2.guideline, []).get(c2.guideline)).push(row);
|
|
49550
49592
|
}
|
|
49551
49593
|
const groups = r.guidelines.map((g) => ({ key: g.key, title: guidelineTitle(g.key, lang) ?? g.title, rows: byGuideline.get(g.key) ?? [] }));
|
|
@@ -49564,9 +49606,10 @@ function renderPackReport(r, pack, lang = "en", outDir) {
|
|
|
49564
49606
|
label: `${pack.name} ${pr.id} \u2014 ${title(pack, pc, lang)}`,
|
|
49565
49607
|
status: pr.status,
|
|
49566
49608
|
findings: pr.findings,
|
|
49609
|
+
...pr.decidedBy ? { decidedBy: pr.decidedBy } : {},
|
|
49567
49610
|
// outOfScope / scopedOut criteria are "manual" (not NA) with their own dedicated
|
|
49568
49611
|
// justification — never mixed with the ordinary NA reason (see the manual section above).
|
|
49569
|
-
...pr.outOfScope ? { justification: s.outOfScope } : pr.scopedOut ? { justification: s.scopedOut } : pr.status === "NA" ? { justification: naReason } : {}
|
|
49612
|
+
...pr.outOfScope ? { justification: s.outOfScope } : pr.scopedOut ? { justification: s.scopedOut } : pr.judgment ? { justification: s.judgment } : pr.status === "NA" ? { justification: naReason } : {}
|
|
49570
49613
|
};
|
|
49571
49614
|
(byTheme.get(pr.theme) ?? byTheme.set(pr.theme, []).get(pr.theme)).push(row);
|
|
49572
49615
|
}
|
|
@@ -49578,7 +49621,11 @@ function renderPackReport(r, pack, lang = "en", outDir) {
|
|
|
49578
49621
|
derivedOf: std,
|
|
49579
49622
|
standard: pack.key,
|
|
49580
49623
|
partialAudit: untestedNeedsRendering(r),
|
|
49581
|
-
headerRatePct: packConformancePct(derived)
|
|
49624
|
+
headerRatePct: packConformancePct(derived),
|
|
49625
|
+
// Forwarded, unlike before: without it the per-page screenshots resolved against the
|
|
49626
|
+
// CWD instead of the report's own directory, so a pack report written to `audits/`
|
|
49627
|
+
// carried links that only worked when read from the repo root.
|
|
49628
|
+
outDir
|
|
49582
49629
|
});
|
|
49583
49630
|
}
|
|
49584
49631
|
function writeReport(r, opts) {
|
|
@@ -49651,6 +49698,22 @@ function issueBody(unit, lang, standard = "wcag", format = "audit") {
|
|
|
49651
49698
|
}
|
|
49652
49699
|
return lines.join("\n");
|
|
49653
49700
|
}
|
|
49701
|
+
function issueLabels(unit, standard = "wcag") {
|
|
49702
|
+
const { tag } = standardTag(standard);
|
|
49703
|
+
return unit.advisory ? ["accessibility", tag, "recommendation", unit.severity] : ["accessibility", tag, unit.severity];
|
|
49704
|
+
}
|
|
49705
|
+
function issueSet(units, lang, standard = "wcag", format = "audit") {
|
|
49706
|
+
const { label } = standardTag(standard);
|
|
49707
|
+
return units.map((u) => ({
|
|
49708
|
+
criteriaId: u.criteriaId,
|
|
49709
|
+
title: issueTitle(u, label),
|
|
49710
|
+
body: issueBody(u, lang, standard, format),
|
|
49711
|
+
labels: issueLabels(u, standard),
|
|
49712
|
+
severity: u.severity,
|
|
49713
|
+
advisory: u.advisory === true,
|
|
49714
|
+
occurrences: u.findings.map((f) => ({ file: f.file, line: f.line, selector: f.selectorHint, message: resolveMessage(f, lang) }))
|
|
49715
|
+
}));
|
|
49716
|
+
}
|
|
49654
49717
|
function createIssue(title2, body2, labels) {
|
|
49655
49718
|
const base = ["issue", "create", "--title", title2, "--body-file", "-"];
|
|
49656
49719
|
try {
|
|
@@ -49670,7 +49733,7 @@ function recordFailure(result, reason) {
|
|
|
49670
49733
|
if (reason && !result.errors.includes(reason)) result.errors.push(reason);
|
|
49671
49734
|
}
|
|
49672
49735
|
function pushIssues(units, lang, standard = "wcag", format = "audit") {
|
|
49673
|
-
const { label
|
|
49736
|
+
const { label } = standardTag(standard);
|
|
49674
49737
|
const existing = existingIssueTitles();
|
|
49675
49738
|
const result = { created: 0, skipped: 0, failed: 0, createdTitles: [], errors: [] };
|
|
49676
49739
|
for (const u of units) {
|
|
@@ -49679,8 +49742,7 @@ function pushIssues(units, lang, standard = "wcag", format = "audit") {
|
|
|
49679
49742
|
result.skipped++;
|
|
49680
49743
|
continue;
|
|
49681
49744
|
}
|
|
49682
|
-
const
|
|
49683
|
-
const r = createIssue(title2, issueBody(u, lang, standard, format), labels);
|
|
49745
|
+
const r = createIssue(title2, issueBody(u, lang, standard, format), issueLabels(u, standard));
|
|
49684
49746
|
if (r.ok) {
|
|
49685
49747
|
result.created++;
|
|
49686
49748
|
result.createdTitles.push(title2);
|
|
@@ -50462,9 +50524,12 @@ var L6 = {
|
|
|
50462
50524
|
viewport: "Fen\xEAtre",
|
|
50463
50525
|
producer: "Producteur",
|
|
50464
50526
|
auth: "Authentification requise",
|
|
50465
|
-
rate: "Taux de
|
|
50527
|
+
rate: "Taux de r\xE9ussite automatique (v\xE9rifications statiques)",
|
|
50466
50528
|
rateNote: "sous-ensemble d\xE9cidable : C \xF7 (C + NC)",
|
|
50467
50529
|
tally: (c2, nc, na, m) => `${c2} conforme(s) \xB7 ${nc} non conforme(s) \xB7 ${na} non applicable(s) \xB7 ${m} \xE0 \xE9valuer`,
|
|
50530
|
+
coverage: (decided, total) => `Couverture : ${decided}/${total} crit\xE8re(s) \xE9valu\xE9(s) \u2014 le taux ci-dessus ne porte que sur eux, et ne dit rien des ${total - decided} autres.`,
|
|
50531
|
+
tests: "Tests",
|
|
50532
|
+
agentMark: "`C*` : conformit\xE9 tranch\xE9e par l'agent IA \xE0 partir des \xE9vidences cit\xE9es (gat\xE9), et non prouv\xE9e par le moteur d\xE9terministe.",
|
|
50468
50533
|
screenshotAlt: (n) => `Capture d'\xE9cran de la page ${n}`,
|
|
50469
50534
|
noScreenshot: "Aucune capture d'\xE9cran pour cette page (le producteur n'en a pas fourni) \u2014 le tier pixel est donc inactif ici.",
|
|
50470
50535
|
gridTitle: "Grille des crit\xE8res",
|
|
@@ -50499,9 +50564,12 @@ var L6 = {
|
|
|
50499
50564
|
viewport: "Viewport",
|
|
50500
50565
|
producer: "Producer",
|
|
50501
50566
|
auth: "Authentication required",
|
|
50502
|
-
rate: "
|
|
50567
|
+
rate: "Automatic static-check pass rate",
|
|
50503
50568
|
rateNote: "decidable subset: C \xF7 (C + NC)",
|
|
50504
50569
|
tally: (c2, nc, na, m) => `${c2} conforming \xB7 ${nc} non-conforming \xB7 ${na} not applicable \xB7 ${m} to assess`,
|
|
50570
|
+
coverage: (decided, total) => `Coverage: ${decided}/${total} criteria assessed \u2014 the rate above covers only those, and says nothing about the other ${total - decided}.`,
|
|
50571
|
+
tests: "Tests",
|
|
50572
|
+
agentMark: "`C*`: conformity ruled by the AI agent from the evidence it cited (gated), not proven by the deterministic engine.",
|
|
50505
50573
|
screenshotAlt: (n) => `Screenshot of the ${n} page`,
|
|
50506
50574
|
noScreenshot: "No screenshot for this page (the producer supplied none) \u2014 the pixel tier is therefore inactive here.",
|
|
50507
50575
|
gridTitle: "Criteria grid",
|
|
@@ -50524,15 +50592,24 @@ var L6 = {
|
|
|
50524
50592
|
};
|
|
50525
50593
|
function rowsFor(result, page, standard, lang) {
|
|
50526
50594
|
if (isCore(standard)) {
|
|
50527
|
-
return [...page.criteria].sort((a, b) => compareSC(a.id, b.id)).map((c2) => ({
|
|
50595
|
+
return [...page.criteria].sort((a, b) => compareSC(a.id, b.id)).map((c2) => ({
|
|
50596
|
+
id: c2.id,
|
|
50597
|
+
label: `${c2.id} ${scTitle(c2.id, lang) ?? ""}`.trim(),
|
|
50598
|
+
group: c2.guideline,
|
|
50599
|
+
status: c2.status,
|
|
50600
|
+
tests: [],
|
|
50601
|
+
decidedBy: c2.decidedBy
|
|
50602
|
+
}));
|
|
50528
50603
|
}
|
|
50529
50604
|
const pack = loadPack(standard);
|
|
50530
|
-
const byId2 = new Map(derivePackResults(pageView(result, page), standard).map((r) => [r.id, r
|
|
50605
|
+
const byId2 = new Map(derivePackResults(pageView(result, page), standard).map((r) => [r.id, r]));
|
|
50531
50606
|
return pack.criteria.map((pc) => ({
|
|
50532
50607
|
id: pc.id,
|
|
50533
50608
|
label: `${pc.id} \u2014 ${titlePlain(pack, pc, lang)}`,
|
|
50534
50609
|
group: `${pc.theme}. ${themeName(pack, pc.theme, lang) ?? ""}`.trim(),
|
|
50535
|
-
status: byId2.get(pc.id) ?? "manual"
|
|
50610
|
+
status: byId2.get(pc.id)?.status ?? "manual",
|
|
50611
|
+
tests: packTestIds(pack, pc.id),
|
|
50612
|
+
decidedBy: byId2.get(pc.id)?.decidedBy
|
|
50536
50613
|
}));
|
|
50537
50614
|
}
|
|
50538
50615
|
function tally(rows) {
|
|
@@ -50562,22 +50639,28 @@ function renderPageReport(result, page, opts = {}) {
|
|
|
50562
50639
|
const rows = rowsFor(result, page, standard, lang);
|
|
50563
50640
|
const t2 = tally(rows);
|
|
50564
50641
|
out2.push(`- **${s.rate}** : **${ratePct(rows)} %** _(${s.rateNote})_`);
|
|
50565
|
-
out2.push(`- ${s.tally(t2.c, t2.nc, t2.na, t2.manual)}
|
|
50642
|
+
out2.push(`- ${s.tally(t2.c, t2.nc, t2.na, t2.manual)}`);
|
|
50643
|
+
out2.push(`- ${s.coverage(t2.c + t2.nc, rows.length)}`, "");
|
|
50566
50644
|
if (page.basis !== "snapshot") out2.push(`> \u26A0\uFE0F ${s.sourceWarn}`, "");
|
|
50567
50645
|
const shot = opts.screenshots?.get(page.id);
|
|
50568
50646
|
if (shot) out2.push(``, "");
|
|
50569
50647
|
else out2.push(`_${s.noScreenshot}_`, "");
|
|
50648
|
+
const withTests = rows.some((r) => r.tests.length);
|
|
50570
50649
|
out2.push(`${h}# ${s.gridTitle}`, "", `> ${s.gridNote}`, "");
|
|
50571
|
-
out2.push(`| ${s.criterion} | ${s.status}
|
|
50650
|
+
out2.push(withTests ? `| ${s.criterion} | ${s.tests} | ${s.status} |` : `| ${s.criterion} | ${s.status} |`);
|
|
50651
|
+
out2.push(withTests ? "| --- | --- | --- |" : "| --- | --- |");
|
|
50572
50652
|
let group = "";
|
|
50573
50653
|
for (const row of rows) {
|
|
50574
50654
|
if (row.group !== group) {
|
|
50575
50655
|
group = row.group;
|
|
50576
|
-
out2.push(`| **${group}** | |`);
|
|
50656
|
+
out2.push(withTests ? `| **${group}** | | |` : `| **${group}** | |`);
|
|
50577
50657
|
}
|
|
50578
|
-
|
|
50658
|
+
const mark = row.decidedBy === "agent" && row.status === "C" ? `${MARK2[row.status]}*` : MARK2[row.status];
|
|
50659
|
+
if (withTests) out2.push(`| ${row.label} | ${row.tests.map((t3) => `\`${t3}\``).join(" ")} | ${mark} |`);
|
|
50660
|
+
else out2.push(`| ${row.label} | ${mark} |`);
|
|
50579
50661
|
}
|
|
50580
50662
|
out2.push("", `> ${s.manualWarn}`, "");
|
|
50663
|
+
if (rows.some((r) => r.decidedBy === "agent" && r.status === "C")) out2.push(`> ${s.agentMark}`, "");
|
|
50581
50664
|
const units = prdUnits(pageView(result, page), standard, lang);
|
|
50582
50665
|
const ncUnits = units.filter((u) => !u.advisory);
|
|
50583
50666
|
const advUnits = units.filter((u) => u.advisory);
|
|
@@ -51561,6 +51644,18 @@ var adjudication_default = {
|
|
|
51561
51644
|
|
|
51562
51645
|
// src/adjudicate.ts
|
|
51563
51646
|
var ADJUDICATE_MAX_EVIDENCE = 30;
|
|
51647
|
+
function adjudicationContract() {
|
|
51648
|
+
return {
|
|
51649
|
+
verdicts: [...VERDICTS],
|
|
51650
|
+
manualReasons: [...MANUAL_REASON_VALUES],
|
|
51651
|
+
requires: {
|
|
51652
|
+
C: "a non-empty justification AND citations[] naming the harvested evidence it cleared (each anchor resolvable and drawn from this criterion's own evidence); a criterion with no harvested evidence cannot be C at all",
|
|
51653
|
+
NA: "a non-empty justification",
|
|
51654
|
+
NC: "at least one groundable finding, each citing a normativeRef that resolves against the active standard",
|
|
51655
|
+
manual: `a reason \u2208 {${MANUAL_REASON_VALUES.join(", ")}}`
|
|
51656
|
+
}
|
|
51657
|
+
};
|
|
51658
|
+
}
|
|
51564
51659
|
var selectorFor = (el) => {
|
|
51565
51660
|
const id = el.attribs.id ? `#${el.attribs.id}` : "";
|
|
51566
51661
|
const cls = el.attribs.class ? `.${el.attribs.class.trim().split(/\s+/)[0]}` : "";
|
|
@@ -51762,6 +51857,31 @@ function buildAdjudicationWorklist(audit2, opts = {}) {
|
|
|
51762
51857
|
}
|
|
51763
51858
|
var NC_SEVERITY_DEFAULT = "majeur";
|
|
51764
51859
|
var MANUAL_REASONS = /* @__PURE__ */ new Set(["needs-rendered-dom", "undecidable"]);
|
|
51860
|
+
var VERDICTS = ["C", "NC", "NA", "manual"];
|
|
51861
|
+
var MANUAL_REASON_VALUES = ["needs-rendered-dom", "undecidable"];
|
|
51862
|
+
function normalizeVerdict2(v) {
|
|
51863
|
+
if (typeof v !== "string") return void 0;
|
|
51864
|
+
const k = v.trim().toLowerCase();
|
|
51865
|
+
return VERDICTS.find((x) => x.toLowerCase() === k);
|
|
51866
|
+
}
|
|
51867
|
+
function normalizeManualReason(r) {
|
|
51868
|
+
if (typeof r !== "string") return void 0;
|
|
51869
|
+
const k = r.trim().toLowerCase();
|
|
51870
|
+
return MANUAL_REASON_VALUES.find((x) => x === k);
|
|
51871
|
+
}
|
|
51872
|
+
function canonicalizeAdjudication(adj) {
|
|
51873
|
+
for (const it of adj.items) {
|
|
51874
|
+
if (it.verdict !== null) {
|
|
51875
|
+
const v = normalizeVerdict2(it.verdict);
|
|
51876
|
+
if (v !== void 0) it.verdict = v;
|
|
51877
|
+
}
|
|
51878
|
+
if (it.reason !== null && it.reason !== void 0) {
|
|
51879
|
+
const r = normalizeManualReason(it.reason);
|
|
51880
|
+
if (r !== void 0) it.reason = r;
|
|
51881
|
+
}
|
|
51882
|
+
}
|
|
51883
|
+
return adj;
|
|
51884
|
+
}
|
|
51765
51885
|
function normativeRefResolves(ref, standard, itemCriterionId) {
|
|
51766
51886
|
const r = (ref ?? "").trim();
|
|
51767
51887
|
if (!r) return false;
|
|
@@ -51784,14 +51904,26 @@ function normativeRefResolves(ref, standard, itemCriterionId) {
|
|
|
51784
51904
|
}
|
|
51785
51905
|
function applyAdjudication(audit2, adj, opts = {}) {
|
|
51786
51906
|
const issues = [];
|
|
51907
|
+
canonicalizeAdjudication(adj);
|
|
51787
51908
|
const byId2 = new Map(adj.items.map((it) => [it.criteriaId, it]));
|
|
51788
51909
|
const packMode = !isCore(adj.standard);
|
|
51910
|
+
const open = /* @__PURE__ */ new Set();
|
|
51789
51911
|
if (packMode) {
|
|
51790
51912
|
for (const pc of derivePackResults(audit2, adj.standard)) {
|
|
51791
|
-
if (pc.status
|
|
51913
|
+
if (pc.status !== "manual") continue;
|
|
51914
|
+
open.add(pc.id);
|
|
51915
|
+
if (!byId2.has(pc.id)) issues.push(`criterion ${pc.id}: missing from the adjudication (coverage gap)`);
|
|
51792
51916
|
}
|
|
51793
51917
|
} else {
|
|
51794
|
-
for (const r of audit2.residualRisks)
|
|
51918
|
+
for (const r of audit2.residualRisks) {
|
|
51919
|
+
open.add(r.criteriaId);
|
|
51920
|
+
if (!byId2.has(r.criteriaId)) issues.push(`criterion ${r.criteriaId}: missing from the adjudication (coverage gap)`);
|
|
51921
|
+
}
|
|
51922
|
+
}
|
|
51923
|
+
for (const it of adj.items) {
|
|
51924
|
+
if (!open.has(it.criteriaId)) {
|
|
51925
|
+
issues.push(`criterion ${it.criteriaId}: not open for adjudication \u2014 the engine already decided it, or it is not part of ${adj.standard}`);
|
|
51926
|
+
}
|
|
51795
51927
|
}
|
|
51796
51928
|
const groundInputs = [];
|
|
51797
51929
|
for (const it of adj.items) {
|
|
@@ -51800,6 +51932,26 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
51800
51932
|
issues.push(`criterion ${it.criteriaId}: unadjudicated (verdict is null)`);
|
|
51801
51933
|
} else if (v === "C" || v === "NA") {
|
|
51802
51934
|
if (!it.justification || !it.justification.trim()) issues.push(`criterion ${it.criteriaId}: a ${v} verdict requires a justification`);
|
|
51935
|
+
const cites = it.citations ?? [];
|
|
51936
|
+
if (it.evidence.length === 0) {
|
|
51937
|
+
if (v === "C") {
|
|
51938
|
+
issues.push(
|
|
51939
|
+
`criterion ${it.criteriaId}: a C verdict needs evidence to cite, and none was harvested for this criterion \u2014 record "manual" (reason "undecidable"), or "NA" if nothing in scope is concerned`
|
|
51940
|
+
);
|
|
51941
|
+
}
|
|
51942
|
+
} else if (cites.length === 0) {
|
|
51943
|
+
issues.push(
|
|
51944
|
+
`criterion ${it.criteriaId}: a ${v} verdict must cite at least one of the ${it.evidence.length} evidence item(s) it was shown (citations: [{file, line, \u2026}])`
|
|
51945
|
+
);
|
|
51946
|
+
} else {
|
|
51947
|
+
const anchors = new Set(it.evidence.map((e) => `${e.file}:${e.line}`));
|
|
51948
|
+
for (const c2 of cites) {
|
|
51949
|
+
if (!anchors.has(`${c2.file}:${c2.line}`)) {
|
|
51950
|
+
issues.push(`criterion ${it.criteriaId}: citation ${c2.file}:${c2.line} is not among this criterion's harvested evidence (fabricated?)`);
|
|
51951
|
+
}
|
|
51952
|
+
groundInputs.push({ file: c2.file, line: c2.line, selector: c2.selector, snippet: c2.snippet });
|
|
51953
|
+
}
|
|
51954
|
+
}
|
|
51803
51955
|
} else if (v === "NC") {
|
|
51804
51956
|
if (!it.findings || it.findings.length === 0) issues.push(`criterion ${it.criteriaId}: an NC verdict requires at least one groundable finding`);
|
|
51805
51957
|
for (const f of it.findings ?? []) {
|
|
@@ -51814,9 +51966,9 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
51814
51966
|
}
|
|
51815
51967
|
} else if (v === "manual") {
|
|
51816
51968
|
if (!it.reason || !MANUAL_REASONS.has(it.reason))
|
|
51817
|
-
issues.push(`criterion ${it.criteriaId}: a manual verdict requires reason \u2208 {
|
|
51969
|
+
issues.push(`criterion ${it.criteriaId}: a manual verdict requires reason \u2208 {${MANUAL_REASON_VALUES.join(", ")}}`);
|
|
51818
51970
|
} else {
|
|
51819
|
-
issues.push(`criterion ${it.criteriaId}: unknown verdict "${String(v)}"`);
|
|
51971
|
+
issues.push(`criterion ${it.criteriaId}: unknown verdict "${String(v)}" \u2014 expected one of ${VERDICTS.join(" | ")}`);
|
|
51820
51972
|
}
|
|
51821
51973
|
for (const rec of it.recommendations ?? []) groundInputs.push({ file: rec.file, line: rec.line, selector: rec.selector, snippet: rec.snippet });
|
|
51822
51974
|
}
|
|
@@ -51921,7 +52073,7 @@ function recomputeTallies(a) {
|
|
|
51921
52073
|
g.na = inG.filter((c2) => c2.status === "NA").length;
|
|
51922
52074
|
g.manual = inG.filter((c2) => c2.status === "manual").length;
|
|
51923
52075
|
}
|
|
51924
|
-
const decided = a.criteria.filter((c2) => c2.status === "
|
|
52076
|
+
const decided = a.criteria.filter((c2) => c2.status === "NC" || c2.status === "C" && c2.decidedBy !== "agent");
|
|
51925
52077
|
const conform = decided.filter((c2) => c2.status === "C").length;
|
|
51926
52078
|
a.conformancePct = decided.length === 0 ? 100 : Math.round(conform / decided.length * 100);
|
|
51927
52079
|
}
|
|
@@ -51932,13 +52084,13 @@ var T2 = {
|
|
|
51932
52084
|
title: "# Adjudication des crit\xE8res \xE0 \xE9valuer (ultra11y)",
|
|
51933
52085
|
intro: "Pour CHAQUE crit\xE8re, lisez les \xE9vidences ci-dessous (extraites de la source audit\xE9e) et attribuez un verdict dans `ADJUDICATE.todo.json` (champ `verdict`) :",
|
|
51934
52086
|
verdicts: [
|
|
51935
|
-
"- `C` \u2014 conforme (renseignez `justification`) ;",
|
|
52087
|
+
"- `C` \u2014 conforme (renseignez `justification` ET `citations[]` : les \xE9vidences que vous avez lev\xE9es, `file`/`line` recopi\xE9s depuis la liste du crit\xE8re) ;",
|
|
51936
52088
|
"- `NC` \u2014 non conforme (ajoutez au moins un `findings[]` : file/line/message, avec un `snippet` groundable ET un `normativeRef` citant le test pr\xE9cis \xE9chou\xE9) ;",
|
|
51937
|
-
"- `NA` \u2014 non applicable (renseignez `justification`) ;",
|
|
52089
|
+
"- `NA` \u2014 non applicable (renseignez `justification` ; si des \xE9vidences sont pr\xE9sent\xE9es, citez-les aussi dans `citations[]` pour dire lesquelles sortent du p\xE9rim\xE8tre) ;",
|
|
51938
52090
|
"- `manual` \u2014 ind\xE9cidable statiquement (renseignez `reason` : `needs-rendered-dom` \u2192 `scan`, ou `undecidable`)."
|
|
51939
52091
|
],
|
|
51940
|
-
rule: "> Ne signalez une NC que si un test pr\xE9cis du r\xE9f\xE9rentiel actif \xE9choue \u2014 citez-le (`normativeRef`). Une bonne pratique sans test normatif est une recommandation (`recommendations[]`, non normative). Une simple pr\xE9occupation UX n'est ni l'un ni l'autre.",
|
|
51941
|
-
then: "Puis : `ultra11y verify --apply ADJUDICATE.todo.json --in <audit.json> --out <dir>` (\xE9choue si un verdict manque
|
|
52092
|
+
rule: "> Ne signalez une NC que si un test pr\xE9cis du r\xE9f\xE9rentiel actif \xE9choue \u2014 citez-le (`normativeRef`). Une bonne pratique sans test normatif est une recommandation (`recommendations[]`, non normative). Une simple pr\xE9occupation UX n'est ni l'un ni l'autre.\n>\n> Sym\xE9triquement, un `C` se cite comme une NC : il faut nommer dans `citations[]` les \xE9vidences lev\xE9es. **Un crit\xE8re pr\xE9sent\xE9 sans aucune \xE9vidence ne peut pas \xEAtre `C`** \u2014 c'est `manual` (`undecidable`), ou `NA` si rien n'est concern\xE9.",
|
|
52093
|
+
then: "Puis : `ultra11y verify --apply ADJUDICATE.todo.json --in <audit.json> --out <dir>` (\xE9choue si un verdict manque sa justification, ses citations, son finding ou sa raison).",
|
|
51942
52094
|
evidence: "\xC9vidences",
|
|
51943
52095
|
none: "(aucune \xE9vidence automatique \u2014 d\xE9cidez depuis la source, ou laissez `manual` avec une raison)",
|
|
51944
52096
|
questions: "\xC0 v\xE9rifier manuellement",
|
|
@@ -51955,13 +52107,13 @@ var T2 = {
|
|
|
51955
52107
|
title: "# Criteria adjudication (ultra11y)",
|
|
51956
52108
|
intro: "For EACH criterion, read the evidence below (harvested from the audited source) and set a verdict in `ADJUDICATE.todo.json` (field `verdict`):",
|
|
51957
52109
|
verdicts: [
|
|
51958
|
-
"- `C` \u2014 conformant (fill `justification`);",
|
|
52110
|
+
"- `C` \u2014 conformant (fill `justification` AND `citations[]`: the evidence you cleared, `file`/`line` copied from the criterion's own list);",
|
|
51959
52111
|
"- `NC` \u2014 non-conformant (add at least one `findings[]`: file/line/message, with a groundable `snippet` AND a `normativeRef` citing the precise failed test);",
|
|
51960
|
-
"- `NA` \u2014 not applicable (fill `justification`);",
|
|
52112
|
+
"- `NA` \u2014 not applicable (fill `justification`; when evidence is presented, cite it too in `citations[]` to say which items are out of scope);",
|
|
51961
52113
|
"- `manual` \u2014 not statically decidable (fill `reason`: `needs-rendered-dom` \u2192 `scan`, or `undecidable`)."
|
|
51962
52114
|
],
|
|
51963
|
-
rule: "> Report NC only if a precise test of the active standard fails \u2014 cite it (`normativeRef`). A good practice without a normative test is a recommendation (`recommendations[]`, non-normative). A purely UX concern is neither.",
|
|
51964
|
-
then: "Then: `ultra11y verify --apply ADJUDICATE.todo.json --in <audit.json> --out <dir>` (fails if any verdict lacks its justification
|
|
52115
|
+
rule: "> Report NC only if a precise test of the active standard fails \u2014 cite it (`normativeRef`). A good practice without a normative test is a recommendation (`recommendations[]`, non-normative). A purely UX concern is neither.\n>\n> A `C` is cited the same way an NC is: name the evidence you cleared in `citations[]`. **A criterion presented with no evidence at all cannot be `C`** \u2014 record `manual` (`undecidable`), or `NA` if nothing in scope is concerned.",
|
|
52116
|
+
then: "Then: `ultra11y verify --apply ADJUDICATE.todo.json --in <audit.json> --out <dir>` (fails if any verdict lacks its justification, citations, finding or reason).",
|
|
51965
52117
|
evidence: "Evidence",
|
|
51966
52118
|
none: "(no automatic evidence \u2014 decide from source, or leave `manual` with a reason)",
|
|
51967
52119
|
questions: "To verify manually",
|
|
@@ -52074,6 +52226,7 @@ function writeAdjudication(items, outDir, opts) {
|
|
|
52074
52226
|
schemaVersion: SCHEMA_VERSION,
|
|
52075
52227
|
standard: opts.standard,
|
|
52076
52228
|
auditDate: opts.auditDate,
|
|
52229
|
+
contract: adjudicationContract(),
|
|
52077
52230
|
items
|
|
52078
52231
|
};
|
|
52079
52232
|
writeFileSync10(todoPath, JSON.stringify(file, null, 2) + "\n");
|
|
@@ -52135,6 +52288,20 @@ var VERDICT_TOOL = {
|
|
|
52135
52288
|
required: ["file", "line", "message", "normativeRef"]
|
|
52136
52289
|
}
|
|
52137
52290
|
},
|
|
52291
|
+
citations: {
|
|
52292
|
+
type: "array",
|
|
52293
|
+
description: "Required for C and NA when evidence was presented: the evidence items you cleared (or ruled out of scope). Copy `file`, `line` and `snippet` VERBATIM from the evidence list of this criterion \u2014 a citation that is not among them, or that no longer matches the source, is rejected.",
|
|
52294
|
+
items: {
|
|
52295
|
+
type: "object",
|
|
52296
|
+
properties: {
|
|
52297
|
+
file: { type: "string" },
|
|
52298
|
+
line: { type: "number" },
|
|
52299
|
+
selector: { type: "string" },
|
|
52300
|
+
snippet: { type: "string" }
|
|
52301
|
+
},
|
|
52302
|
+
required: ["file", "line"]
|
|
52303
|
+
}
|
|
52304
|
+
},
|
|
52138
52305
|
recommendations: {
|
|
52139
52306
|
type: "array",
|
|
52140
52307
|
description: "Non-normative good practices. They never change a status.",
|
|
@@ -52163,7 +52330,7 @@ var SYSTEM = `You are an accessibility auditor ruling on the criteria a static e
|
|
|
52163
52330
|
Rules, in order of importance:
|
|
52164
52331
|
1. NEVER assert conformity you did not verify. "manual" with a reason is always available and is a correct answer.
|
|
52165
52332
|
2. An NC must cite a real file:line taken from the evidence you were given, and the criterion's OWN numbered test as normativeRef. A citation that does not resolve against the real source is rejected downstream and wastes the whole batch.
|
|
52166
|
-
3. C and NA require a justification that says what you saw,
|
|
52333
|
+
3. C and NA require a justification that says what you saw, AND a "citations" array naming the evidence items you cleared \u2014 file and line copied verbatim from the evidence presented for that criterion. A criterion presented with NO evidence cannot be C: record "manual" (reason "undecidable"), or NA if nothing in scope is concerned.
|
|
52167
52334
|
4. A criterion that needs a rendered page (computed contrast, visible focus, zoom, reflow) and was given only source evidence is "manual" with reason "needs-rendered-dom".
|
|
52168
52335
|
5. Rule only on the criteria presented. Never introduce another.`;
|
|
52169
52336
|
var sleep = (ms) => ms <= 0 ? Promise.resolve() : new Promise((r) => setTimeout(r, ms));
|
|
@@ -52247,6 +52414,8 @@ function applyRawVerdicts(items, verdicts) {
|
|
|
52247
52414
|
item.justification = v.justification ?? "";
|
|
52248
52415
|
item.reason = v.verdict === "manual" ? v.reason ?? null : null;
|
|
52249
52416
|
item.findings = v.verdict === "NC" ? v.findings ?? [] : [];
|
|
52417
|
+
if (v.verdict === "C" || v.verdict === "NA") item.citations = v.citations ?? [];
|
|
52418
|
+
else delete item.citations;
|
|
52250
52419
|
if (v.recommendations?.length) item.recommendations = v.recommendations;
|
|
52251
52420
|
filled++;
|
|
52252
52421
|
}
|
|
@@ -54213,7 +54382,7 @@ function writeCi(root, enginePath, failOn) {
|
|
|
54213
54382
|
}
|
|
54214
54383
|
|
|
54215
54384
|
// src/install/json-edit.ts
|
|
54216
|
-
import { copyFileSync, existsSync as existsSync22, mkdirSync as mkdirSync11, readFileSync as readFileSync17, renameSync as renameSync2, rmSync as rmSync4, writeFileSync as writeFileSync14 } from "fs";
|
|
54385
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync22, mkdirSync as mkdirSync11, readFileSync as readFileSync17, renameSync as renameSync2, rmSync as rmSync4, writeFileSync as writeFileSync14 } from "fs";
|
|
54217
54386
|
import { dirname as dirname10 } from "path";
|
|
54218
54387
|
var SettingsParseError = class extends Error {
|
|
54219
54388
|
constructor(path, cause) {
|
|
@@ -54232,7 +54401,7 @@ function writeTextWithBackup(path, content, marker = "ultra11y") {
|
|
|
54232
54401
|
let backup;
|
|
54233
54402
|
if (existsSync22(path)) {
|
|
54234
54403
|
backup = `${path}.${marker}-backup-${stamp()}`;
|
|
54235
|
-
|
|
54404
|
+
copyFileSync2(path, backup);
|
|
54236
54405
|
}
|
|
54237
54406
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
54238
54407
|
try {
|
|
@@ -54405,7 +54574,7 @@ import { existsSync as existsSync25, readFileSync as readFileSync20 } from "fs";
|
|
|
54405
54574
|
import { join as join37 } from "path";
|
|
54406
54575
|
|
|
54407
54576
|
// src/install/paths.ts
|
|
54408
|
-
import { copyFileSync as
|
|
54577
|
+
import { copyFileSync as copyFileSync3, cpSync, existsSync as existsSync24, mkdirSync as mkdirSync12, readFileSync as readFileSync19, realpathSync as realpathSync3, statSync as statSync10 } from "fs";
|
|
54409
54578
|
import { homedir as homedir2 } from "os";
|
|
54410
54579
|
import { dirname as dirname11, join as join36 } from "path";
|
|
54411
54580
|
function codexHome() {
|
|
@@ -54435,7 +54604,7 @@ function installedCliCommand() {
|
|
|
54435
54604
|
const pin = pinnedEnginePath();
|
|
54436
54605
|
if (source) {
|
|
54437
54606
|
mkdirSync12(join36(pin, ".."), { recursive: true });
|
|
54438
|
-
|
|
54607
|
+
copyFileSync3(source, pin);
|
|
54439
54608
|
}
|
|
54440
54609
|
return `node ${JSON.stringify(pin)}`;
|
|
54441
54610
|
}
|
|
@@ -57681,6 +57850,9 @@ Options:
|
|
|
57681
57850
|
technique + Contexte de reproduction) for a pure-auditor block
|
|
57682
57851
|
--gh-issues prd: also create one GitHub issue per criterion via the gh CLI (opt-in)
|
|
57683
57852
|
--gh-single prd: file the whole audit as ONE consolidated GitHub issue (opt-in; wins over --gh-issues)
|
|
57853
|
+
--issues-json prd: write the issue set (one item per criterion: title, body, labels,
|
|
57854
|
+
severity, occurrences) to <out>/issues-<date>.json for a non-GitHub
|
|
57855
|
+
tracker to file \u2014 same items --gh-issues would create, no gh needed
|
|
57684
57856
|
--scaffold render: write an SSR-snapshot harness (default: ultra11y-render.tsx)
|
|
57685
57857
|
--setup render: install the zero-touch test-render capture harvester (.ultra11y/capture-setup.mjs) + print the runner wiring
|
|
57686
57858
|
--coverage render: report rendered-capture coverage (covered vs blind-spot components); with --json emits the coverage object
|
|
@@ -57897,6 +58069,7 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
57897
58069
|
"no-technical",
|
|
57898
58070
|
"gh-issues",
|
|
57899
58071
|
"gh-single",
|
|
58072
|
+
"issues-json",
|
|
57900
58073
|
"override",
|
|
57901
58074
|
"local",
|
|
57902
58075
|
"docker",
|
|
@@ -58351,27 +58524,39 @@ async function cmdPages(p) {
|
|
|
58351
58524
|
const derived = derivePages(result, scope);
|
|
58352
58525
|
const split = p.flags.split === "page";
|
|
58353
58526
|
const outDir = typeof p.flags.out === "string" && p.flags.out ? p.flags.out : void 0;
|
|
58354
|
-
const
|
|
58527
|
+
const shotOf = (id) => join45(PAGES_DIR, id, "screen.png");
|
|
58528
|
+
const shotsRelative = (fileDir) => {
|
|
58529
|
+
const m = /* @__PURE__ */ new Map();
|
|
58530
|
+
for (const pg of derived) {
|
|
58531
|
+
if (existsSync31(shotOf(pg.id))) m.set(pg.id, relative5(fileDir, shotOf(pg.id)).split("\\").join("/"));
|
|
58532
|
+
}
|
|
58533
|
+
return m;
|
|
58534
|
+
};
|
|
58535
|
+
const shotsCopiedInto = (dir) => {
|
|
58355
58536
|
const m = /* @__PURE__ */ new Map();
|
|
58537
|
+
const assets = join45(dir, "assets");
|
|
58356
58538
|
for (const pg of derived) {
|
|
58357
|
-
const
|
|
58358
|
-
if (existsSync31(
|
|
58539
|
+
const src = shotOf(pg.id);
|
|
58540
|
+
if (!existsSync31(src)) continue;
|
|
58541
|
+
mkdirSync15(assets, { recursive: true });
|
|
58542
|
+
copyFileSync4(src, join45(assets, `${pg.id}.png`));
|
|
58543
|
+
m.set(pg.id, `./assets/${pg.id}.png`);
|
|
58359
58544
|
}
|
|
58360
58545
|
return m;
|
|
58361
58546
|
};
|
|
58362
58547
|
if (!outDir) {
|
|
58363
|
-
console.log(renderPagesDocument(result, derived, { standard, lang, screenshots:
|
|
58548
|
+
console.log(renderPagesDocument(result, derived, { standard, lang, screenshots: shotsRelative(".") }));
|
|
58364
58549
|
return 0;
|
|
58365
58550
|
}
|
|
58366
58551
|
mkdirSync15(outDir, { recursive: true });
|
|
58367
58552
|
if (!split) {
|
|
58368
58553
|
const file = join45(outDir, `pages-${result.date}.md`);
|
|
58369
|
-
writeFileSync17(file, `${renderPagesDocument(result, derived, { standard, lang, screenshots:
|
|
58554
|
+
writeFileSync17(file, `${renderPagesDocument(result, derived, { standard, lang, screenshots: shotsCopiedInto(outDir) })}
|
|
58370
58555
|
`);
|
|
58371
58556
|
console.log(file);
|
|
58372
58557
|
return 0;
|
|
58373
58558
|
}
|
|
58374
|
-
const shots =
|
|
58559
|
+
const shots = shotsCopiedInto(outDir);
|
|
58375
58560
|
const sheet = (id) => `page-${id}.md`;
|
|
58376
58561
|
const hrefs = new Map(derived.map((pg) => [pg.id, `./${sheet(pg.id)}`]));
|
|
58377
58562
|
for (const pg of derived) {
|
|
@@ -58658,6 +58843,19 @@ async function cmdPrd(p) {
|
|
|
58658
58843
|
const paths = writePrd(result, { out: out2, lang, split, format, standard, technical });
|
|
58659
58844
|
const json = p.flags.json === true;
|
|
58660
58845
|
if (!json) for (const path of paths) console.log(path);
|
|
58846
|
+
let issuesPath;
|
|
58847
|
+
if (p.flags["issues-json"] === true) {
|
|
58848
|
+
const issueFormat = format === "remediation" ? "remediation" : "audit";
|
|
58849
|
+
const issues = issueSet(prdUnits(result, standard, lang), lang, standard, issueFormat);
|
|
58850
|
+
mkdirSync15(out2, { recursive: true });
|
|
58851
|
+
issuesPath = join45(out2, `issues-${result.date}.json`);
|
|
58852
|
+
writeFileSync17(
|
|
58853
|
+
issuesPath,
|
|
58854
|
+
`${JSON.stringify({ tool: "ultra11y", kind: "issues", schemaVersion: 1, standard, date: result.date, count: issues.length, issues }, null, 2)}
|
|
58855
|
+
`
|
|
58856
|
+
);
|
|
58857
|
+
if (!json) console.log(issuesPath);
|
|
58858
|
+
}
|
|
58661
58859
|
const ghMode = p.flags["gh-single"] === true ? "single" : p.flags["gh-issues"] === true ? "per-criterion" : null;
|
|
58662
58860
|
let gh2;
|
|
58663
58861
|
if (ghMode) {
|
|
@@ -58681,7 +58879,8 @@ async function cmdPrd(p) {
|
|
|
58681
58879
|
}
|
|
58682
58880
|
}
|
|
58683
58881
|
}
|
|
58684
|
-
if (json)
|
|
58882
|
+
if (json)
|
|
58883
|
+
console.log(JSON.stringify({ paths, ...issuesPath ? { issuesPath } : {}, units: prdUnits(result, standard, lang), ...gh2 ? { gh: gh2 } : {} }, null, 2));
|
|
58685
58884
|
return gh2 && gh2.failed > 0 ? 1 : 0;
|
|
58686
58885
|
}
|
|
58687
58886
|
function depsAt(root) {
|
|
@@ -59077,7 +59276,22 @@ function applyAdjudicationFile(p, adj, lang) {
|
|
|
59077
59276
|
mkdirSync15(out2, { recursive: true });
|
|
59078
59277
|
const auditPath = join45(out2, "audit-latest.json");
|
|
59079
59278
|
writeFileSync17(auditPath, JSON.stringify(r.audit, null, 2) + "\n");
|
|
59080
|
-
if (p.flags.json)
|
|
59279
|
+
if (p.flags.json)
|
|
59280
|
+
console.log(
|
|
59281
|
+
JSON.stringify(
|
|
59282
|
+
{
|
|
59283
|
+
ok: true,
|
|
59284
|
+
auditPath,
|
|
59285
|
+
applied: r.applied,
|
|
59286
|
+
stillManual: r.stillManual,
|
|
59287
|
+
conformancePct: r.audit.conformancePct,
|
|
59288
|
+
findings: r.audit.findings.length,
|
|
59289
|
+
grounding: r.grounding
|
|
59290
|
+
},
|
|
59291
|
+
null,
|
|
59292
|
+
2
|
|
59293
|
+
)
|
|
59294
|
+
);
|
|
59081
59295
|
else
|
|
59082
59296
|
console.log(
|
|
59083
59297
|
lang === "fr" ? `\u2713 ${r.applied} crit\xE8re(s) adjug\xE9(s), ${r.stillManual} laiss\xE9(s) en r\xE9siduel \u2192 ${auditPath}` : `\u2713 ${r.applied} criterion(ia) adjudicated, ${r.stillManual} left residual \u2192 ${auditPath}`
|