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.
@@ -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.31.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-11 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).",
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 = pc?.tests ? Object.keys(pc.tests).map((k) => `${unit.criteriaId}.${k}`) : [];
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 = Object.keys(crit?.tests ?? {});
49070
- out2.push(`- [ ] **${pack.name} ${pc.id}** \u2014 ${title2}${tests.length ? ` \xB7 ${s.tests}: ${tests.map((k) => `\`${pc.id}.${k}\``).join(" ")}` : ""}`);
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
- out2.push(
49512
- "",
49513
- `![${s.screenshotAlt(pg.name)}](${relative3(opts.outDir ?? ".", shot).split("\\").join("/")})`
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("", `![${s.screenshotAlt(pg.name)}](${href})`);
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
- out2.push(conform.length ? conform.map((x) => `- ${x.label}`).join("\n") : s.nothing, "");
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 ? Object.keys(getCriterion(pack5, x.id)?.tests ?? {}) : [];
49536
- const testRef = tests.length ? ` \u2014 ${s.testsToRule}: ${tests.map((k) => `\`${x.id}.${k}\``).join(" \xB7 ")}` : "";
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 = { id: c2.id, label: title2 ? `${c2.id} \u2014 ${title2}` : c2.id, status: c2.status, findings: c2.findings, justification: c2.justification };
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, tag } = standardTag(standard);
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 labels = u.advisory ? ["accessibility", tag, "recommendation", u.severity] : ["accessibility", tag, u.severity];
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 conformit\xE9",
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: "Conformance 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) => ({ id: c2.id, label: `${c2.id} ${scTitle(c2.id, lang) ?? ""}`.trim(), group: c2.guideline, status: c2.status }));
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.status]));
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(`![${s.screenshotAlt(page.name)}](${shot})`, "");
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
- out2.push(`| ${row.label} | ${MARK2[row.status]} |`);
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 === "manual" && !byId2.has(pc.id)) issues.push(`criterion ${pc.id}: missing from the adjudication (coverage gap)`);
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) if (!byId2.has(r.criteriaId)) issues.push(`criterion ${r.criteriaId}: missing from the adjudication (coverage gap)`);
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 {needs-rendered-dom, undecidable}`);
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 === "C" || c2.status === "NC");
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 une justification/finding/reason).",
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/finding/reason).",
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, not that you checked.
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
- copyFileSync(path, backup);
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 copyFileSync2, cpSync, existsSync as existsSync24, mkdirSync as mkdirSync12, readFileSync as readFileSync19, realpathSync as realpathSync3, statSync as statSync10 } from "fs";
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
- copyFileSync2(source, pin);
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 shotsFor = (fileDir) => {
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 shot = join45(PAGES_DIR, pg.id, "screen.png");
58358
- if (existsSync31(shot)) m.set(pg.id, relative5(fileDir, shot).split("\\").join("/"));
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: shotsFor(".") }));
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: shotsFor(outDir) })}
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 = shotsFor(outDir);
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) console.log(JSON.stringify({ paths, units: prdUnits(result, standard, lang), ...gh2 ? { gh: gh2 } : {} }, null, 2));
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) console.log(JSON.stringify({ ok: true, auditPath, applied: r.applied, stillManual: r.stillManual, grounding: r.grounding }, null, 2));
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}`