ultra11y 4.0.1 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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, copyFileSync as copyFileSync4 } from "fs";
5
- import { join as join45, relative as relative5, sep as sep7, dirname as dirname14 } from "path";
4
+ import { realpathSync as realpathSync6, writeFileSync as writeFileSync19, mkdirSync as mkdirSync17, existsSync as existsSync32, readFileSync as readFileSync29, appendFileSync, copyFileSync as copyFileSync4 } from "fs";
5
+ import { join as join47, 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 = "4.0.1";
9
+ var VERSION = "4.2.0";
10
10
  var SCHEMA_VERSION = 2;
11
11
 
12
12
  // src/audit.ts
@@ -2435,9 +2435,9 @@ var Tokenizer = class {
2435
2435
  this.running = true;
2436
2436
  this.offset = 0;
2437
2437
  }
2438
- write(chunk) {
2438
+ write(chunk2) {
2439
2439
  this.offset += this.buffer.length;
2440
- this.buffer = chunk;
2440
+ this.buffer = chunk2;
2441
2441
  this.parse();
2442
2442
  }
2443
2443
  end() {
@@ -3725,14 +3725,14 @@ var Parser = class {
3725
3725
  * Parses a chunk of data and calls the corresponding callbacks.
3726
3726
  * @param chunk Chunk to parse.
3727
3727
  */
3728
- write(chunk) {
3728
+ write(chunk2) {
3729
3729
  if (this.ended) {
3730
3730
  this.cbs.onerror?.(new Error(".write() after done!"));
3731
3731
  return;
3732
3732
  }
3733
- this.buffers.push(chunk);
3733
+ this.buffers.push(chunk2);
3734
3734
  if (this.tokenizer.running) {
3735
- this.tokenizer.write(chunk);
3735
+ this.tokenizer.write(chunk2);
3736
3736
  this.writeIndex++;
3737
3737
  }
3738
3738
  }
@@ -3740,13 +3740,13 @@ var Parser = class {
3740
3740
  * Parses the end of the buffer and clears the stack, calls onend.
3741
3741
  * @param chunk Optional final chunk to parse.
3742
3742
  */
3743
- end(chunk) {
3743
+ end(chunk2) {
3744
3744
  if (this.ended) {
3745
3745
  this.cbs.onerror?.(new Error(".end() after done!"));
3746
3746
  return;
3747
3747
  }
3748
- if (chunk)
3749
- this.write(chunk);
3748
+ if (chunk2)
3749
+ this.write(chunk2);
3750
3750
  this.ended = true;
3751
3751
  this.tokenizer.end();
3752
3752
  }
@@ -18820,7 +18820,7 @@ function repoRelative(file, baseDir) {
18820
18820
  }
18821
18821
  async function readStdin() {
18822
18822
  const chunks = [];
18823
- for await (const chunk of process.stdin) chunks.push(chunk);
18823
+ for await (const chunk2 of process.stdin) chunks.push(chunk2);
18824
18824
  return Buffer.concat(chunks).toString("utf8");
18825
18825
  }
18826
18826
 
@@ -19448,14 +19448,14 @@ function isGitWorktree(dir) {
19448
19448
  function resolveBaseRef(dir, base) {
19449
19449
  const verify = (ref) => sh("git", [...gitArgs(dir), "rev-parse", "--verify", "--quiet", `${ref}^{commit}`]).ok;
19450
19450
  const mergeBase = (ref) => {
19451
- const mb = sh("git", [...gitArgs(dir), "merge-base", ref, "HEAD"]);
19452
- return mb.ok ? mb.stdout.trim() : void 0;
19451
+ const mb2 = sh("git", [...gitArgs(dir), "merge-base", ref, "HEAD"]);
19452
+ return mb2.ok ? mb2.stdout.trim() : void 0;
19453
19453
  };
19454
19454
  if (base) {
19455
19455
  if (!verify(base)) return { error: `base ref "${base}" not found (tried git rev-parse --verify)` };
19456
- const mb = mergeBase(base);
19457
- if (!mb) return { error: `no merge-base between "${base}" and HEAD` };
19458
- return { ref: base, mergeBase: mb };
19456
+ const mb2 = mergeBase(base);
19457
+ if (!mb2) return { error: `no merge-base between "${base}" and HEAD` };
19458
+ return { ref: base, mergeBase: mb2 };
19459
19459
  }
19460
19460
  const originHead = sh("git", [...gitArgs(dir), "symbolic-ref", "--quiet", "refs/remotes/origin/HEAD"]);
19461
19461
  const candidates2 = [
@@ -19467,8 +19467,8 @@ function resolveBaseRef(dir, base) {
19467
19467
  ];
19468
19468
  for (const c2 of candidates2) {
19469
19469
  if (!verify(c2)) continue;
19470
- const mb = mergeBase(c2);
19471
- if (mb) return { ref: c2, mergeBase: mb };
19470
+ const mb2 = mergeBase(c2);
19471
+ if (mb2) return { ref: c2, mergeBase: mb2 };
19472
19472
  }
19473
19473
  const head = sh("git", [...gitArgs(dir), "rev-parse", "HEAD"]);
19474
19474
  if (!head.ok) return { error: "cannot resolve HEAD \u2014 empty repository?" };
@@ -34007,15 +34007,15 @@ function deltaFor(repo, graph, symbols, opts = {}) {
34007
34007
  return computeDelta(graph, symbols, { files, hunks: diffHunks(repo, spec), base, notes }, opts.depth ?? DEFAULT_DELTA_DEPTH);
34008
34008
  }
34009
34009
  function formatDeltaPanel(res) {
34010
- const mb = res.base.mergeBase.slice(0, 7);
34010
+ const mb2 = res.base.mergeBase.slice(0, 7);
34011
34011
  const vs = `${res.base.staged ? "staged vs " : ""}${res.base.ref}`;
34012
34012
  if (!res.changes.length && !res.unindexed.length) {
34013
- return `codeindex: no changes vs ${vs} (merge-base ${mb})
34013
+ return `codeindex: no changes vs ${vs} (merge-base ${mb2})
34014
34014
  `;
34015
34015
  }
34016
34016
  const changedCount = res.changes.length + res.unindexed.length;
34017
34017
  const lines = [
34018
- `codeindex: delta vs ${vs} (merge-base ${mb}) \u2014 ${changedCount} changed file(s), ${res.modules.length} module(s)${res.indexCommit ? `, index @ ${res.indexCommit}` : ""}`
34018
+ `codeindex: delta vs ${vs} (merge-base ${mb2}) \u2014 ${changedCount} changed file(s), ${res.modules.length} module(s)${res.indexCommit ? `, index @ ${res.indexCommit}` : ""}`
34019
34019
  ];
34020
34020
  for (const n of res.notes) lines.push(` note: ${n}`);
34021
34021
  for (const m of res.modules) {
@@ -35329,6 +35329,16 @@ function readSnapshots(root) {
35329
35329
  }
35330
35330
  return out2;
35331
35331
  }
35332
+ function alignedBoxes(dom, boxes, doc) {
35333
+ const parsed = doc ?? parseHtml(dom, "snapshot");
35334
+ const out2 = /* @__PURE__ */ new Map();
35335
+ for (const e of boxes.entries) {
35336
+ const el = parsed.elements[e.i];
35337
+ if (!el || el.tag !== e.tag) return null;
35338
+ out2.set(e.i, e);
35339
+ }
35340
+ return out2;
35341
+ }
35332
35342
  function isSnapshotDom(file) {
35333
35343
  const posix3 = file.split("\\").join("/");
35334
35344
  return posix3.endsWith("/dom.html") && posix3.includes(`${PAGES_DIR}/`);
@@ -39288,6 +39298,112 @@ function dominantBackground(img, rect) {
39288
39298
  if (!best || best.n / total < DOMINANCE) return null;
39289
39299
  return { r: Math.round(best.r / best.n), g: Math.round(best.g / best.n), b: Math.round(best.b / best.n), a: 1 };
39290
39300
  }
39301
+ var CRC_TABLE = /* @__PURE__ */ (() => {
39302
+ const t2 = new Uint32Array(256);
39303
+ for (let n = 0; n < 256; n++) {
39304
+ let c2 = n;
39305
+ for (let k = 0; k < 8; k++) c2 = c2 & 1 ? 3988292384 ^ c2 >>> 1 : c2 >>> 1;
39306
+ t2[n] = c2 >>> 0;
39307
+ }
39308
+ return t2;
39309
+ })();
39310
+ function crc32(buf) {
39311
+ let c2 = 4294967295;
39312
+ for (let i2 = 0; i2 < buf.length; i2++) c2 = CRC_TABLE[(c2 ^ buf[i2]) & 255] ^ c2 >>> 8;
39313
+ return (c2 ^ 4294967295) >>> 0;
39314
+ }
39315
+ function chunk(type, data2) {
39316
+ const len = Buffer.alloc(4);
39317
+ len.writeUInt32BE(data2.length);
39318
+ const body2 = Buffer.concat([Buffer.from(type, "latin1"), data2]);
39319
+ const crc = Buffer.alloc(4);
39320
+ crc.writeUInt32BE(crc32(body2));
39321
+ return Buffer.concat([len, body2, crc]);
39322
+ }
39323
+ function encodePng(r) {
39324
+ const expected = r.width * r.height * 4;
39325
+ if (r.width <= 0 || r.height <= 0) throw new Error(`encodePng: refusing a ${r.width}\xD7${r.height} raster`);
39326
+ if (r.data.length !== expected) {
39327
+ throw new Error(`encodePng: ${r.width}\xD7${r.height} needs ${expected} bytes of RGBA, got ${r.data.length}`);
39328
+ }
39329
+ const stride = r.width * 4;
39330
+ const raw = Buffer.alloc(r.height * (stride + 1));
39331
+ for (let y = 0; y < r.height; y++) {
39332
+ raw[y * (stride + 1)] = 0;
39333
+ r.data.copy(raw, y * (stride + 1) + 1, y * stride, (y + 1) * stride);
39334
+ }
39335
+ const ihdr = Buffer.alloc(13);
39336
+ ihdr.writeUInt32BE(r.width, 0);
39337
+ ihdr.writeUInt32BE(r.height, 4);
39338
+ ihdr[8] = 8;
39339
+ ihdr[9] = 6;
39340
+ return Buffer.concat([Buffer.from(SIGNATURE), chunk("IHDR", ihdr), chunk("IDAT", deflateSync(raw, { level: 9 })), chunk("IEND", Buffer.alloc(0))]);
39341
+ }
39342
+ function cropImage(img, rect) {
39343
+ const r = clamp(img, rect);
39344
+ if (!r) return null;
39345
+ const data2 = Buffer.alloc(r.w * r.h * 4);
39346
+ let i2 = 0;
39347
+ for (let y = r.y; y < r.y + r.h; y++) {
39348
+ for (let x = r.x; x < r.x + r.w; x++) {
39349
+ const p = img.at(x, y);
39350
+ data2[i2] = p?.r ?? 0;
39351
+ data2[i2 + 1] = p?.g ?? 0;
39352
+ data2[i2 + 2] = p?.b ?? 0;
39353
+ data2[i2 + 3] = Math.round((p?.a ?? 1) * 255);
39354
+ i2 += 4;
39355
+ }
39356
+ }
39357
+ return { raster: { width: r.w, height: r.h, data: data2 }, rect: r };
39358
+ }
39359
+ function blend(raster, x, y, c2) {
39360
+ if (x < 0 || y < 0 || x >= raster.width || y >= raster.height) return;
39361
+ const p = (y * raster.width + x) * 4;
39362
+ const a = Math.min(1, Math.max(0, c2.a));
39363
+ if (a >= 1) {
39364
+ raster.data[p] = c2.r;
39365
+ raster.data[p + 1] = c2.g;
39366
+ raster.data[p + 2] = c2.b;
39367
+ raster.data[p + 3] = 255;
39368
+ return;
39369
+ }
39370
+ const inv = 1 - a;
39371
+ raster.data[p] = Math.round(c2.r * a + raster.data[p] * inv);
39372
+ raster.data[p + 1] = Math.round(c2.g * a + raster.data[p + 1] * inv);
39373
+ raster.data[p + 2] = Math.round(c2.b * a + raster.data[p + 2] * inv);
39374
+ raster.data[p + 3] = Math.max(raster.data[p + 3], Math.round(a * 255));
39375
+ }
39376
+ function fillRect(raster, rect, color) {
39377
+ const x0 = Math.max(0, Math.round(rect.x));
39378
+ const y0 = Math.max(0, Math.round(rect.y));
39379
+ const x1 = Math.min(raster.width, Math.round(rect.x + rect.w));
39380
+ const y1 = Math.min(raster.height, Math.round(rect.y + rect.h));
39381
+ for (let y = y0; y < y1; y++) for (let x = x0; x < x1; x++) blend(raster, x, y, color);
39382
+ }
39383
+ function strokeRect(raster, rect, color, width, dash) {
39384
+ const t2 = Math.max(1, Math.round(width));
39385
+ const x0 = Math.round(rect.x);
39386
+ const y0 = Math.round(rect.y);
39387
+ const x1 = Math.round(rect.x + rect.w);
39388
+ const y1 = Math.round(rect.y + rect.h);
39389
+ if (x1 <= x0 || y1 <= y0) return;
39390
+ const on = (coord) => {
39391
+ if (!dash || dash <= 1) return true;
39392
+ const half = dash / 2;
39393
+ return Math.floor(coord / half) % 2 === 0;
39394
+ };
39395
+ for (let y = y0; y < y1; y++) {
39396
+ for (let x = x0; x < x1; x++) {
39397
+ const top = y < y0 + t2;
39398
+ const bottom = y >= y1 - t2;
39399
+ const left = x < x0 + t2;
39400
+ const right = x >= x1 - t2;
39401
+ if (!top && !bottom && !left && !right) continue;
39402
+ if (!on(top || bottom ? x : y)) continue;
39403
+ blend(raster, x, y, color);
39404
+ }
39405
+ }
39406
+ }
39291
39407
 
39292
39408
  // src/rules/rendered.ts
39293
39409
  var SKIP_TAGS2 = /* @__PURE__ */ new Set(["script", "style", "head", "title", "meta", "noscript", "link", "html", "br", "wbr"]);
@@ -48677,8 +48793,10 @@ function groupOccurrences(findings, collapse2) {
48677
48793
  }
48678
48794
  return [...groups.values()];
48679
48795
  }
48680
- function renderOccurrenceDetails(out2, f, lang, s, indent) {
48796
+ function renderOccurrenceDetails(out2, f, lang, s, indent, cropFor) {
48681
48797
  if (f.secondary?.note) out2.push(`${indent} - \u21B3 ${f.secondary.note}`);
48798
+ const crop = cropFor?.(f);
48799
+ if (crop) out2.push(`${indent} - ![${crop.alt}](${crop.href})`);
48682
48800
  if (f.related) out2.push(indent + relatedLine(f.related, lang, { selector: true }));
48683
48801
  if (f.origin) {
48684
48802
  const comp = f.origin.component ?? f.origin.sourceFile ?? f.file;
@@ -48707,60 +48825,75 @@ function relatedLine(related, lang, opts) {
48707
48825
  function renderAuditorUnit(unit, standard, lang, opts = {}) {
48708
48826
  const s = L[lang];
48709
48827
  if (unit.advisory) return renderAdvisoryUnit(unit, standard, lang, opts);
48710
- const v = vocabularyFor(standard, lang);
48711
- const technical = opts.technical ?? true;
48828
+ const m = auditorUnitModel(unit, standard, lang, opts);
48712
48829
  const out2 = [];
48713
- if (opts.heading) out2.push(`${opts.heading} ${ICON[unit.severity]} ${unit.label}`, "");
48714
- out2.push(`> ${v.normativeNote ?? `${s.lead} \u2014 ${standardLabel(standard)}. ${s.tail}`}`, "");
48830
+ if (opts.heading) out2.push(`${opts.heading} ${m.icon} ${m.label}`, "");
48831
+ out2.push(`> ${m.normativeNote}`, "");
48832
+ for (const f of m.fields) out2.push(`**${f.label}** : ${f.value}`);
48833
+ out2.push("");
48834
+ out2.push(`**${s.finding} (${m.conformanceTerms.nonConformant})** : ${m.occurrences} ${s.occ} \u2014 ${m.messages.join(" ; ")}`);
48835
+ if (m.fixes.length) out2.push(`**${s.expected} (${m.conformanceTerms.conformant})** : ${m.fixes.join(" ; ")}`);
48836
+ out2.push(`**${s.verification}** : ${s.verify}`, "");
48837
+ for (const group of m.groups) {
48838
+ if (group.count > 1) out2.push(`- **\`${group.lead.selectorHint}\`** \u2014 ${resolveMessage(group.lead, lang)} \xB7 \xD7${group.count}`);
48839
+ for (const f of group.findings) {
48840
+ const indent = group.count > 1 ? " " : "";
48841
+ out2.push(indent + occurrenceLine(f, lang, { marker: "checkbox" }));
48842
+ renderOccurrenceDetails(out2, f, lang, s, indent, opts.cropFor);
48843
+ }
48844
+ }
48845
+ out2.push("");
48846
+ if (m.advisories.length) {
48847
+ out2.push(`_${s.associatedRec}_`, "");
48848
+ for (const f of m.advisories) out2.push(occurrenceLine(f, lang, { marker: "advisory" }));
48849
+ out2.push("");
48850
+ }
48851
+ if (opts.technical ?? true) {
48852
+ out2.push(...renderTechnicalSection({ ...unit, findings: m.normative }, unit, standard, lang, opts));
48853
+ out2.push(...renderReproductionContext(m.normative, lang));
48854
+ }
48855
+ return out2;
48856
+ }
48857
+ function auditorUnitModel(unit, standard, lang, opts = {}) {
48858
+ const s = L[lang];
48859
+ const v = vocabularyFor(standard, lang);
48860
+ const fields = [];
48715
48861
  if (isCore(standard)) {
48716
48862
  const sc = getSC(unit.criteriaId);
48717
48863
  if (sc) {
48718
48864
  const pr = `${sc.principle} ${principleTitle(sc.principle, lang) ?? ""}`.trim();
48719
48865
  const gl = `${sc.guideline} ${guidelineTitle(sc.guideline, lang) ?? ""}`.trim();
48720
- out2.push(`**${v.theme}** : ${[pr, gl].filter(Boolean).join(" \xB7 ")}`);
48866
+ fields.push({ label: v.theme, value: [pr, gl].filter(Boolean).join(" \xB7 ") });
48721
48867
  }
48722
- out2.push(`**${v.criterion}** : ${unit.criteriaId}${sc ? ` \u2014 ${unit.title}` : ""}`);
48868
+ fields.push({ label: v.criterion, value: `${unit.criteriaId}${sc ? ` \u2014 ${unit.title}` : ""}` });
48723
48869
  const techs = techniques(unit.criteriaId);
48724
- if (techs.length) out2.push(`**${v.test}** : ${techs.join(", ")}`);
48725
- out2.push(`**WCAG** : ${unit.criteriaId}${sc ? ` (${sc.level})` : ""}`);
48870
+ if (techs.length) fields.push({ label: v.test, value: techs.join(", ") });
48871
+ fields.push({ label: "WCAG", value: `${unit.criteriaId}${sc ? ` (${sc.level})` : ""}` });
48726
48872
  } else {
48727
48873
  const pack = loadPack(standard);
48728
48874
  const pc = pack.criteria.find((c2) => c2.id === unit.criteriaId);
48729
- if (pc) out2.push(`**${v.theme}** : ${pc.theme}. ${themeName(pack, pc.theme, lang) ?? ""}`.trimEnd());
48730
- out2.push(`**${v.criterion}** : ${unit.criteriaId} \u2014 ${unit.title}`);
48875
+ if (pc) fields.push({ label: v.theme, value: `${pc.theme}. ${themeName(pack, pc.theme, lang) ?? ""}`.trimEnd() });
48876
+ fields.push({ label: v.criterion, value: `${unit.criteriaId} \u2014 ${unit.title}` });
48731
48877
  const testNums = packTestIds(pack, unit.criteriaId);
48732
- if (testNums.length) out2.push(`**${v.test}(s)** : ${testNums.join(" \xB7 ")}`);
48733
- if (unit.refs.length) out2.push(`**WCAG** : ${unit.refs.map((sc) => `${sc}${scLevel(sc)}`).join(" \xB7 ")}`);
48878
+ if (testNums.length) fields.push({ label: `${v.test}(s)`, value: testNums.join(" \xB7 ") });
48879
+ if (unit.refs.length) fields.push({ label: "WCAG", value: unit.refs.map((sc) => `${sc}${scLevel(sc)}`).join(" \xB7 ") });
48734
48880
  }
48735
- out2.push(`**${s.priority}** : ${ICON[unit.severity]} ${SEV_LABEL[lang][unit.severity]}`);
48881
+ fields.push({ label: s.priority, value: `${ICON[unit.severity]} ${SEV_LABEL[lang][unit.severity]}` });
48736
48882
  const normative = unit.findings.filter((f) => !f.advisory);
48737
- const advisories = unit.findings.filter((f) => f.advisory);
48738
- const ncView = { ...unit, findings: normative };
48739
- const messages = uniq(normative.map((f) => resolveMessage(f, lang)));
48740
- const fixes = uniq(normative.map((f) => resolveRemediation(f, lang)));
48741
- out2.push("");
48742
- out2.push(`**${s.finding} (${v.nonConformant})** : ${normative.length} ${s.occ} \u2014 ${messages.join(" ; ")}`);
48743
- if (fixes.length) out2.push(`**${s.expected} (${v.conformant})** : ${fixes.join(" ; ")}`);
48744
- out2.push(`**${s.verification}** : ${s.verify}`, "");
48745
- for (const group of groupOccurrences(normative, opts.collapse === true)) {
48746
- if (group.length > 1) out2.push(`- **\`${group[0].selectorHint}\`** \u2014 ${resolveMessage(group[0], lang)} \xB7 \xD7${group.length}`);
48747
- for (const f of group) {
48748
- const indent = group.length > 1 ? " " : "";
48749
- out2.push(indent + occurrenceLine(f, lang, { marker: "checkbox" }));
48750
- renderOccurrenceDetails(out2, f, lang, s, indent);
48751
- }
48752
- }
48753
- out2.push("");
48754
- if (advisories.length) {
48755
- out2.push(`_${s.associatedRec}_`, "");
48756
- for (const f of advisories) out2.push(occurrenceLine(f, lang, { marker: "advisory" }));
48757
- out2.push("");
48758
- }
48759
- if (technical) {
48760
- out2.push(...renderTechnicalSection(ncView, unit, standard, lang, opts));
48761
- out2.push(...renderReproductionContext(normative, lang));
48762
- }
48763
- return out2;
48883
+ return {
48884
+ severity: unit.severity,
48885
+ icon: ICON[unit.severity],
48886
+ label: unit.label,
48887
+ normativeNote: v.normativeNote ?? `${s.lead} \u2014 ${standardLabel(standard)}. ${s.tail}`,
48888
+ fields,
48889
+ conformanceTerms: { conformant: v.conformant, nonConformant: v.nonConformant },
48890
+ normative,
48891
+ advisories: unit.findings.filter((f) => f.advisory),
48892
+ occurrences: normative.length,
48893
+ messages: uniq(normative.map((f) => resolveMessage(f, lang))),
48894
+ fixes: uniq(normative.map((f) => resolveRemediation(f, lang))),
48895
+ groups: groupOccurrences(normative, opts.collapse === true).map((g) => ({ lead: g[0], count: g.length, findings: g }))
48896
+ };
48764
48897
  }
48765
48898
  function renderTechnicalSection(ncView, unit, standard, lang, opts) {
48766
48899
  const s = L[lang];
@@ -49053,14 +49186,14 @@ function unitBlock(unit, lang, heading, standard) {
49053
49186
  out2.push("");
49054
49187
  return out2;
49055
49188
  }
49056
- function header(r, lang, title2, note = L2[lang].note, ratePct2) {
49189
+ function header(r, lang, title2, note = L2[lang].note, ratePct) {
49057
49190
  const s = L2[lang];
49058
49191
  return [
49059
49192
  `# ${title2}`,
49060
49193
  "",
49061
49194
  `- **${s.date}** : ${r.date}`,
49062
49195
  `- **${s.scope}** : ${r.scope.files} ${s.files} \u2014 ${r.scope.inputs.join(", ")}`,
49063
- `- **${s.rate}** : ${ratePct2 ?? r.conformancePct}%`,
49196
+ `- **${s.rate}** : ${ratePct ?? r.conformancePct}%`,
49064
49197
  "",
49065
49198
  `> ${note}`,
49066
49199
  ""
@@ -49069,8 +49202,8 @@ function header(r, lang, title2, note = L2[lang].note, ratePct2) {
49069
49202
  function renderBacklog(r, lang = "en", standard = "wcag") {
49070
49203
  const s = L2[lang];
49071
49204
  const units = prdUnits(r, standard, lang);
49072
- const ratePct2 = isCore(standard) ? void 0 : packConformancePct(derivePackResults(r, standard));
49073
- const out2 = header(r, lang, s.title(standardLabel(standard)), void 0, ratePct2);
49205
+ const ratePct = isCore(standard) ? void 0 : packConformancePct(derivePackResults(r, standard));
49206
+ const out2 = header(r, lang, s.title(standardLabel(standard)), void 0, ratePct);
49074
49207
  if (!units.length) {
49075
49208
  out2.push(s.none, "");
49076
49209
  out2.push(...toRuleOnSection(r, standard, lang));
@@ -49086,9 +49219,9 @@ function renderBacklog(r, lang = "en", standard = "wcag") {
49086
49219
  }
49087
49220
  function renderPerCriterion(r, lang = "en", standard = "wcag") {
49088
49221
  const s = L2[lang];
49089
- const ratePct2 = isCore(standard) ? void 0 : packConformancePct(derivePackResults(r, standard));
49222
+ const ratePct = isCore(standard) ? void 0 : packConformancePct(derivePackResults(r, standard));
49090
49223
  return prdUnits(r, standard, lang).map((u) => {
49091
- const out2 = header(r, lang, s.prdTitle(u.label), void 0, ratePct2);
49224
+ const out2 = header(r, lang, s.prdTitle(u.label), void 0, ratePct);
49092
49225
  out2.push(...unitBlock(u, lang, "##", standard));
49093
49226
  return { name: `prd-${u.criteriaId}-${r.date}.md`, content: out2.join("\n") };
49094
49227
  });
@@ -49146,8 +49279,8 @@ function toRuleOnSection(r, standard, lang) {
49146
49279
  function renderPrdDoc(r, lang = "en", standard = "wcag") {
49147
49280
  const s = L2[lang];
49148
49281
  const units = prdUnits(r, standard, lang);
49149
- const ratePct2 = isCore(standard) ? void 0 : packConformancePct(derivePackResults(r, standard));
49150
- const out2 = header(r, lang, s.title(standardLabel(standard)), s.docNote, ratePct2);
49282
+ const ratePct = isCore(standard) ? void 0 : packConformancePct(derivePackResults(r, standard));
49283
+ const out2 = header(r, lang, s.title(standardLabel(standard)), s.docNote, ratePct);
49151
49284
  if (!units.length) {
49152
49285
  out2.push(s.none, "");
49153
49286
  return out2.join("\n");
@@ -49327,7 +49460,8 @@ var L3 = {
49327
49460
  snapshot: "instantan\xE9",
49328
49461
  source: "source",
49329
49462
  notAudited: "non audit\xE9",
49330
- notAuditedNote: "Une page marqu\xE9e \xAB non audit\xE9 \xBB a bien un instantan\xE9, mais CET audit ne l'a pas lu (il ne portait que sur les sources). L'absence de constat n'y vaut donc PAS conformit\xE9 \u2014 relancez l'audit en incluant `.ultra11y/pages`."
49463
+ notAuditedNote: "Une page marqu\xE9e \xAB non audit\xE9 \xBB a bien un instantan\xE9, mais CET audit ne l'a pas lu (il ne portait que sur les sources). L'absence de constat n'y vaut donc PAS conformit\xE9 \u2014 relancez l'audit en incluant `.ultra11y/pages`.",
49464
+ 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."
49331
49465
  },
49332
49466
  en: {
49333
49467
  title: "Per-page grid",
@@ -49341,12 +49475,16 @@ var L3 = {
49341
49475
  snapshot: "snapshot",
49342
49476
  source: "source",
49343
49477
  notAudited: "not audited",
49344
- notAuditedNote: 'A page marked "not audited" does have a snapshot, but THIS audit never read it (it covered sources only). Absence of a finding there does NOT mean conforming \u2014 re-run the audit with `.ultra11y/pages` in scope.'
49478
+ notAuditedNote: 'A page marked "not audited" does have a snapshot, but THIS audit never read it (it covered sources only). Absence of a finding there does NOT mean conforming \u2014 re-run the audit with `.ultra11y/pages` in scope.',
49479
+ agentMark: "`C*`: conformity ruled by the AI agent from the evidence it cited (gated), not proven by the deterministic engine."
49345
49480
  }
49346
49481
  };
49347
49482
  function formatRate(rate, decided, total) {
49348
49483
  return `${rate === null ? "\u2014" : `${rate} %`} (${decided}/${total})`;
49349
49484
  }
49485
+ function agentMarkNote(lang) {
49486
+ return L3[lang].agentMark;
49487
+ }
49350
49488
  function pageBasisWarning(basis, lang) {
49351
49489
  if (basis === "snapshot") return void 0;
49352
49490
  return basis === "not-audited" ? L3[lang].notAuditedNote : L3[lang].basisNote;
@@ -49366,7 +49504,7 @@ function pageView(result, page) {
49366
49504
  ...result.packFindings ? { packFindings: result.packFindings.filter((f) => f.page === page.id) } : {}
49367
49505
  };
49368
49506
  }
49369
- function gridOf(result, derived, standard, lang) {
49507
+ function pageGridModel(result, derived, standard, lang) {
49370
49508
  const status = /* @__PURE__ */ new Map();
49371
49509
  const put = (rowId, pageId, s) => {
49372
49510
  const m = status.get(rowId) ?? /* @__PURE__ */ new Map();
@@ -49398,7 +49536,7 @@ function renderPageGrid(result, pages, standard = CORE2, lang = "en") {
49398
49536
  out2.push(`| ${head.join(" | ")} |`, `| ${head.map(() => "---").join(" | ")} |`);
49399
49537
  out2.push(`| **${s.rate}** | ${derived.map((p) => `**${formatRate(p.conformancePct, p.decided, p.total)}**`).join(" | ")} |`);
49400
49538
  out2.push(`| _${s.snapshot}?_ | ${derived.map((p) => `_${basisLabel(p.basis, lang)}_`).join(" | ")} |`);
49401
- const { rows, status } = gridOf(result, derived, standard, lang);
49539
+ const { rows, status } = pageGridModel(result, derived, standard, lang);
49402
49540
  let group = "";
49403
49541
  for (const row of rows) {
49404
49542
  if (row.group !== group) {
@@ -49550,6 +49688,29 @@ function partialAuditBanner(lang, untested = NEEDS_RENDERING.map((c2) => c2.sc))
49550
49688
  const labels = NEEDS_RENDERING.filter((c2) => set.has(c2.sc)).map((c2) => c2.label[lang]);
49551
49689
  return L4[lang].partialAudit(labels.join(", "));
49552
49690
  }
49691
+ function tallyRows(rows) {
49692
+ return {
49693
+ c: rows.filter((x) => x.status === "C").length,
49694
+ nc: rows.filter((x) => x.status === "NC").length,
49695
+ na: rows.filter((x) => x.status === "NA").length,
49696
+ manual: rows.filter((x) => x.status === "manual").length
49697
+ };
49698
+ }
49699
+ function reportTotals(groups) {
49700
+ const tot = { c: 0, nc: 0, na: 0, manual: 0 };
49701
+ for (const g of groups) {
49702
+ const t2 = tallyRows(g.rows);
49703
+ tot.c += t2.c;
49704
+ tot.nc += t2.nc;
49705
+ tot.na += t2.na;
49706
+ tot.manual += t2.manual;
49707
+ }
49708
+ return tot;
49709
+ }
49710
+ function reportCoverage(groups) {
49711
+ const t2 = reportTotals(groups);
49712
+ return { decided: t2.c + t2.nc, total: t2.c + t2.nc + t2.na + t2.manual };
49713
+ }
49553
49714
  function render(r, lang, opts) {
49554
49715
  const s = L4[lang];
49555
49716
  const out2 = [];
@@ -49572,18 +49733,11 @@ function render(r, lang, opts) {
49572
49733
  out2.push(`## ${s.synthTitle(opts.groupHead)}`, "");
49573
49734
  out2.push(`| ${th.join(" | ")} |`);
49574
49735
  out2.push(`|${"---|".repeat(th.length)}`);
49575
- const tot = { c: 0, nc: 0, na: 0, manual: 0 };
49576
49736
  for (const g of opts.groups) {
49577
- const c2 = g.rows.filter((x) => x.status === "C").length;
49578
- const nc = g.rows.filter((x) => x.status === "NC").length;
49579
- const na2 = g.rows.filter((x) => x.status === "NA").length;
49580
- const manual2 = g.rows.filter((x) => x.status === "manual").length;
49581
- out2.push(`| ${g.key} ${g.title} | ${c2} | ${nc} | ${na2} | ${manual2} |`);
49582
- tot.c += c2;
49583
- tot.nc += nc;
49584
- tot.na += na2;
49585
- tot.manual += manual2;
49737
+ const t2 = tallyRows(g.rows);
49738
+ out2.push(`| ${g.key} ${g.title} | ${t2.c} | ${t2.nc} | ${t2.na} | ${t2.manual} |`);
49586
49739
  }
49740
+ const tot = reportTotals(opts.groups);
49587
49741
  out2.push(`| **${s.total}** | **${tot.c}** | **${tot.nc}** | **${tot.na}** | **${tot.manual}** |`, "");
49588
49742
  out2.push(`## ${s.ncTitle}`, "");
49589
49743
  const { nc: ncUnits, advisory: advisoryUnits } = partitionUnits(prdUnits(r, opts.standard, lang));
@@ -49594,12 +49748,13 @@ function render(r, lang, opts) {
49594
49748
  const group = ncUnits.filter((u) => u.severity === sev);
49595
49749
  if (!group.length) continue;
49596
49750
  out2.push(`### ${ICON3[sev]} ${s.sev[sev]} (${group.length})`, "");
49597
- for (const u of group) out2.push(...renderAuditorUnit(u, opts.standard, lang, { heading: "####" }));
49751
+ for (const u of group) out2.push(...renderAuditorUnit(u, opts.standard, lang, { heading: "####", ...opts.cropFor ? { cropFor: opts.cropFor } : {} }));
49598
49752
  }
49599
49753
  }
49600
49754
  if (advisoryUnits.length) {
49601
49755
  out2.push(`## \u{1F4A1} ${s.recTitle}`, "", `> ${s.recNote}`, "");
49602
- for (const u of advisoryUnits) out2.push(...renderAuditorUnit(u, opts.standard, lang, { heading: "###" }));
49756
+ for (const u of advisoryUnits)
49757
+ out2.push(...renderAuditorUnit(u, opts.standard, lang, { heading: "###", ...opts.cropFor ? { cropFor: opts.cropFor } : {} }));
49603
49758
  }
49604
49759
  const pageScope = pagesOf(r);
49605
49760
  if (pageScope.length) attributePages(r, pageScope);
@@ -49667,8 +49822,7 @@ function render(r, lang, opts) {
49667
49822
  }
49668
49823
  return out2.join("\n");
49669
49824
  }
49670
- function renderReport(r, lang = "en", outDir) {
49671
- const s = L4[lang];
49825
+ function reportGroups(r, lang = "en") {
49672
49826
  const byGuideline = /* @__PURE__ */ new Map();
49673
49827
  for (const c2 of r.criteria) {
49674
49828
  const title2 = scTitle(c2.id, lang);
@@ -49682,12 +49836,14 @@ function renderReport(r, lang = "en", outDir) {
49682
49836
  };
49683
49837
  (byGuideline.get(c2.guideline) ?? byGuideline.set(c2.guideline, []).get(c2.guideline)).push(row);
49684
49838
  }
49685
- const groups = r.guidelines.map((g) => ({ key: g.key, title: guidelineTitle(g.key, lang) ?? g.title, rows: byGuideline.get(g.key) ?? [] }));
49686
- return render(r, lang, { std: s.wcagStd, groupHead: s.byGuideline, groups, standard: CORE2, outDir });
49839
+ return r.guidelines.map((g) => ({ key: g.key, title: guidelineTitle(g.key, lang) ?? g.title, rows: byGuideline.get(g.key) ?? [] }));
49840
+ }
49841
+ function renderReport(r, lang = "en", outDir, cropFor) {
49842
+ const s = L4[lang];
49843
+ return render(r, lang, { std: s.wcagStd, groupHead: s.byGuideline, groups: reportGroups(r, lang), standard: CORE2, outDir, ...cropFor ? { cropFor } : {} });
49687
49844
  }
49688
- function renderPackReport(r, pack, lang = "en", outDir) {
49845
+ function packReportGroups(r, pack, lang = "en") {
49689
49846
  const derived = derivePackResults(r, pack.key);
49690
- const std = `${pack.name} ${pack.baseVersion}`;
49691
49847
  const s = L4[lang];
49692
49848
  const naReason = lang === "fr" ? "Aucun crit\xE8re de succ\xE8s WCAG mapp\xE9 n'est applicable dans le p\xE9rim\xE8tre." : "No mapped WCAG success criterion is applicable in scope.";
49693
49849
  const byTheme = /* @__PURE__ */ new Map();
@@ -49705,11 +49861,15 @@ function renderPackReport(r, pack, lang = "en", outDir) {
49705
49861
  };
49706
49862
  (byTheme.get(pr.theme) ?? byTheme.set(pr.theme, []).get(pr.theme)).push(row);
49707
49863
  }
49708
- const groups = pack.themes.map((t2) => ({ key: `${t2.number}.`, title: themeName(pack, t2.number, lang) ?? "", rows: byTheme.get(t2.number) ?? [] }));
49864
+ return pack.themes.map((t2) => ({ key: `${t2.number}.`, title: themeName(pack, t2.number, lang) ?? "", rows: byTheme.get(t2.number) ?? [] }));
49865
+ }
49866
+ function renderPackReport(r, pack, lang = "en", outDir, cropFor) {
49867
+ const derived = derivePackResults(r, pack.key);
49868
+ const std = `${pack.name} ${pack.baseVersion}`;
49709
49869
  return render(r, lang, {
49710
49870
  std,
49711
49871
  groupHead: L4[lang].byTheme,
49712
- groups,
49872
+ groups: packReportGroups(r, pack, lang),
49713
49873
  derivedOf: std,
49714
49874
  standard: pack.key,
49715
49875
  partialAudit: untestedNeedsRendering(r),
@@ -49717,12 +49877,13 @@ function renderPackReport(r, pack, lang = "en", outDir) {
49717
49877
  // Forwarded, unlike before: without it the per-page screenshots resolved against the
49718
49878
  // CWD instead of the report's own directory, so a pack report written to `audits/`
49719
49879
  // carried links that only worked when read from the repo root.
49720
- outDir
49880
+ outDir,
49881
+ ...cropFor ? { cropFor } : {}
49721
49882
  });
49722
49883
  }
49723
49884
  function writeReport(r, opts) {
49724
49885
  const core = isCore(opts.standard);
49725
- const md = core ? renderReport(r, opts.lang, opts.out) : renderPackReport(r, loadPack(opts.standard), opts.lang, opts.out);
49886
+ const md = core ? renderReport(r, opts.lang, opts.out, opts.cropFor) : renderPackReport(r, loadPack(opts.standard), opts.lang, opts.out, opts.cropFor);
49726
49887
  mkdirSync7(opts.out, { recursive: true });
49727
49888
  const path = join29(opts.out, `${core ? "wcag" : opts.standard}-${r.date}.md`);
49728
49889
  writeFileSync8(path, md);
@@ -50213,17 +50374,19 @@ function resolve4(opts) {
50213
50374
  const mode = opts.transport ?? "auto";
50214
50375
  const api = (env.GITHUB_API_URL || "https://api.github.com").replace(/\/+$/, "");
50215
50376
  const token = env.GH_TOKEN || env.GITHUB_TOKEN;
50216
- const cliOk = (opts.cliAvailable ?? ghAvailable)();
50377
+ const probe = opts.cliAvailable ?? ghAvailable;
50378
+ let cached;
50379
+ const cliOk = () => cached ??= probe();
50217
50380
  if (mode === "cli") {
50218
- return cliOk ? { transport: "cli", api } : { transport: "cli", api, reason: "`gh` is not installed or not authenticated (run `gh auth login`)" };
50381
+ return cliOk() ? { transport: "cli", api } : { transport: "cli", api, reason: "`gh` is not installed or not authenticated (run `gh auth login`)" };
50219
50382
  }
50220
- const repo = env.ULTRA11Y_GITHUB_REPO || env.GITHUB_REPOSITORY || (mode === "rest" || !cliOk ? gitRemoteSlug("github.com") : void 0);
50383
+ const repo = env.ULTRA11Y_GITHUB_REPO || env.GITHUB_REPOSITORY || (mode === "rest" || !cliOk() ? gitRemoteSlug("github.com") : void 0);
50221
50384
  if (mode === "rest") {
50222
50385
  if (!token) return { transport: "rest", api, reason: "no GitHub token \u2014 set GH_TOKEN or GITHUB_TOKEN" };
50223
50386
  if (!repo) return { transport: "rest", api, token, reason: "no repository \u2014 set GITHUB_REPOSITORY (owner/name) or ULTRA11Y_GITHUB_REPO" };
50224
50387
  return { transport: "rest", api, token, repo };
50225
50388
  }
50226
- if (cliOk) return { transport: "cli", api };
50389
+ if (cliOk()) return { transport: "cli", api };
50227
50390
  if (token && repo) return { transport: "rest", api, token, repo };
50228
50391
  const missing = [!token ? "a token (GH_TOKEN/GITHUB_TOKEN)" : "", !repo ? "a repository (GITHUB_REPOSITORY)" : ""].filter(Boolean);
50229
50392
  return {
@@ -51236,7 +51399,6 @@ var L7 = {
51236
51399
  tally: (c2, nc, na, m) => `${c2} conforme(s) \xB7 ${nc} non conforme(s) \xB7 ${na} non applicable(s) \xB7 ${m} \xE0 \xE9valuer`,
51237
51400
  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.`,
51238
51401
  tests: "Tests",
51239
- 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.",
51240
51402
  screenshotAlt: (n) => `Capture d'\xE9cran de la page ${n}`,
51241
51403
  noScreenshot: "Aucune capture d'\xE9cran pour cette page (le producteur n'en a pas fourni) \u2014 le tier pixel est donc inactif ici.",
51242
51404
  gridTitle: "Grille des crit\xE8res",
@@ -51277,7 +51439,6 @@ var L7 = {
51277
51439
  tally: (c2, nc, na, m) => `${c2} conforming \xB7 ${nc} non-conforming \xB7 ${na} not applicable \xB7 ${m} to assess`,
51278
51440
  coverage: (decided, total) => `Coverage: ${decided}/${total} criteria assessed \u2014 the rate above covers only those, and says nothing about the other ${total - decided}.`,
51279
51441
  tests: "Tests",
51280
- agentMark: "`C*`: conformity ruled by the AI agent from the evidence it cited (gated), not proven by the deterministic engine.",
51281
51442
  screenshotAlt: (n) => `Screenshot of the ${n} page`,
51282
51443
  noScreenshot: "No screenshot for this page (the producer supplied none) \u2014 the pixel tier is therefore inactive here.",
51283
51444
  gridTitle: "Criteria grid",
@@ -51298,7 +51459,7 @@ var L7 = {
51298
51459
  indexNote: "One sheet per page. `X % (d/t)`: the rate covers only the `d` criteria decided out of `t` \u2014 it says nothing about the others. `\u2014` means no criterion was decided on this page, so it is NEITHER conformity NOR non-conformity."
51299
51460
  }
51300
51461
  };
51301
- function rowsFor(result, page, standard, lang) {
51462
+ function pageCriterionRows(result, page, standard, lang) {
51302
51463
  if (isCore(standard)) {
51303
51464
  return [...page.criteria].sort((a, b) => compareSC(a.id, b.id)).map((c2) => ({
51304
51465
  id: c2.id,
@@ -51320,7 +51481,7 @@ function rowsFor(result, page, standard, lang) {
51320
51481
  decidedBy: byId2.get(pc.id)?.decidedBy
51321
51482
  }));
51322
51483
  }
51323
- function tally(rows) {
51484
+ function pageTally(rows) {
51324
51485
  return {
51325
51486
  c: rows.filter((r) => r.status === "C").length,
51326
51487
  nc: rows.filter((r) => r.status === "NC").length,
@@ -51328,12 +51489,12 @@ function tally(rows) {
51328
51489
  manual: rows.filter((r) => r.status === "manual").length
51329
51490
  };
51330
51491
  }
51331
- function coverageOf(rows) {
51332
- const t2 = tally(rows);
51492
+ function pageCoverage(rows) {
51493
+ const t2 = pageTally(rows);
51333
51494
  return { decided: t2.c + t2.nc, total: rows.length };
51334
51495
  }
51335
- function ratePct(rows) {
51336
- const { c: c2, nc } = tally(rows);
51496
+ function pageRatePct(rows) {
51497
+ const { c: c2, nc } = pageTally(rows);
51337
51498
  return c2 + nc === 0 ? null : Math.round(c2 / (c2 + nc) * 100);
51338
51499
  }
51339
51500
  function renderPageReport(result, page, opts = {}) {
@@ -51348,10 +51509,10 @@ function renderPageReport(result, page, opts = {}) {
51348
51509
  meta2.push(`- **${s.basis}** : ${basisLabel(page.basis, lang)}`);
51349
51510
  if (page.auth) meta2.push(`- **${s.auth}** : \u2705`);
51350
51511
  out2.push(...meta2);
51351
- const rows = rowsFor(result, page, standard, lang);
51352
- const t2 = tally(rows);
51353
- const cov = coverageOf(rows);
51354
- const rate = ratePct(rows);
51512
+ const rows = pageCriterionRows(result, page, standard, lang);
51513
+ const t2 = pageTally(rows);
51514
+ const cov = pageCoverage(rows);
51515
+ const rate = pageRatePct(rows);
51355
51516
  out2.push(`- **${s.rate}** : **${rate === null ? "\u2014" : `${rate} %`}** _(${s.rateNote})_`);
51356
51517
  out2.push(`- ${s.tally(t2.c, t2.nc, t2.na, t2.manual)}`);
51357
51518
  out2.push(`- ${s.coverage(cov.decided, cov.total)}`, "");
@@ -51359,6 +51520,8 @@ function renderPageReport(result, page, opts = {}) {
51359
51520
  const shot = opts.screenshots?.get(page.id);
51360
51521
  if (shot) out2.push(`![${s.screenshotAlt(page.name)}](${shot})`, "");
51361
51522
  else out2.push(`_${s.noScreenshot}_`, "");
51523
+ const refused = opts.evidenceNotice?.(page.id) ?? [];
51524
+ if (refused.length) out2.push(...refused, "");
51362
51525
  const withTests = rows.some((r) => r.tests.length);
51363
51526
  out2.push(`${h}# ${s.gridTitle}`, "", `> ${s.gridNote}`, "");
51364
51527
  out2.push(withTests ? `| ${s.criterion} | ${s.tests} | ${s.status} |` : `| ${s.criterion} | ${s.status} |`);
@@ -51374,16 +51537,17 @@ function renderPageReport(result, page, opts = {}) {
51374
51537
  else out2.push(`| ${row.label} | ${mark} |`);
51375
51538
  }
51376
51539
  out2.push("", `> ${s.manualWarn}`, "");
51377
- if (rows.some((r) => r.decidedBy === "agent" && r.status === "C")) out2.push(`> ${s.agentMark}`, "");
51540
+ if (rows.some((r) => r.decidedBy === "agent" && r.status === "C")) out2.push(`> ${agentMarkNote(lang)}`, "");
51378
51541
  const units = prdUnits(pageView(result, page), standard, lang);
51379
51542
  const ncUnits = units.filter((u) => !u.advisory);
51380
51543
  const advUnits = units.filter((u) => u.advisory);
51544
+ const unit = { heading: `${h}##`, collapse: true, ...opts.cropFor ? { cropFor: opts.cropFor } : {} };
51381
51545
  out2.push(`${h}# ${s.ncTitle}`, "");
51382
51546
  if (!ncUnits.length) out2.push(s.noNc, "");
51383
- else for (const u of ncUnits) out2.push(...renderAuditorUnit(u, standard, lang, { heading: `${h}##`, collapse: true }));
51547
+ else for (const u of ncUnits) out2.push(...renderAuditorUnit(u, standard, lang, unit));
51384
51548
  if (advUnits.length) {
51385
51549
  out2.push(`${h}# \u{1F4A1} ${s.recTitle}`, "", `> ${s.recNote}`, "");
51386
- for (const u of advUnits) out2.push(...renderAuditorUnit(u, standard, lang, { heading: `${h}##`, collapse: true }));
51550
+ for (const u of advUnits) out2.push(...renderAuditorUnit(u, standard, lang, unit));
51387
51551
  }
51388
51552
  return out2.join("\n");
51389
51553
  }
@@ -51409,12 +51573,12 @@ function renderPagesIndex(result, pages, opts = {}) {
51409
51573
  out2.push(`| ${s.page} | ${s.url} | ${s.basis} | ${s.rateShort} | ${s.blocking} | ${s.major} | ${s.minor} | ${s.sheet} |`);
51410
51574
  out2.push("| --- | --- | --- | --- | --- | --- | --- | --- |");
51411
51575
  for (const p of pages) {
51412
- const rows = rowsFor(result, p, standard, lang);
51413
- const cov = coverageOf(rows);
51576
+ const rows = pageCriterionRows(result, p, standard, lang);
51577
+ const cov = pageCoverage(rows);
51414
51578
  const nc = [...p.findings, ...(result.packFindings ?? []).filter((f) => f.page === p.id)].filter((f) => !f.advisory);
51415
51579
  const href = opts.hrefs?.get(p.id);
51416
51580
  out2.push(
51417
- `| ${p.name}${p.auth ? " \u{1F512}" : ""} | \`${p.url}\` | ${basisLabel(p.basis, lang)} | ${formatRate(ratePct(rows), cov.decided, cov.total)} | ${nc.filter((f) => f.severity === "bloquant").length} | ${nc.filter((f) => f.severity === "majeur").length} | ${nc.filter((f) => f.severity === "mineur").length} | ${href ? `[${p.id}](${href})` : p.id} |`
51581
+ `| ${p.name}${p.auth ? " \u{1F512}" : ""} | \`${p.url}\` | ${basisLabel(p.basis, lang)} | ${formatRate(pageRatePct(rows), cov.decided, cov.total)} | ${nc.filter((f) => f.severity === "bloquant").length} | ${nc.filter((f) => f.severity === "majeur").length} | ${nc.filter((f) => f.severity === "mineur").length} | ${href ? `[${p.id}](${href})` : p.id} |`
51418
51582
  );
51419
51583
  }
51420
51584
  out2.push("");
@@ -56097,6 +56261,7 @@ var S = {
56097
56261
  where: "Emplacement",
56098
56262
  what: "Constat",
56099
56263
  more: (n) => `\u2026 et ${n} autre(s).`,
56264
+ moreGroups: (n) => `\u2026 et ${n} autre(s) groupe(s) \u2014 voir le r\xE9sum\xE9 de job.`,
56100
56265
  perPage: "Bilan page par page",
56101
56266
  page: "Page",
56102
56267
  count: "Constats",
@@ -56104,6 +56269,16 @@ var S = {
56104
56269
  pageRate: "Taux",
56105
56270
  snapshot: "instantan\xE9",
56106
56271
  source: "source",
56272
+ occurrences: "Occ.",
56273
+ pagesCol: "Pages",
56274
+ grouped: (groups, occ) => `${groups} d\xE9faut(s) distinct(s) \xB7 ${occ} occurrence(s)`,
56275
+ groupNote: "Une ligne par (crit\xE8re, r\xE8gle, s\xE9lecteur) : un m\xEAme d\xE9faut de design system r\xE9p\xE9t\xE9 sur toutes les routes compte pour un. Les colonnes Occ. et Pages disent l'ampleur r\xE9elle.",
56276
+ verdictFail: (n) => `\u{1F534} ${n} non-conformit\xE9(s) bloquante(s) \u2014 la porte est rouge.`,
56277
+ verdictWarn: "\u{1F7E0} Aucune non-conformit\xE9 bloquante ; des constats majeurs ou mineurs restent \xE0 traiter.",
56278
+ verdictPass: "\u2705 Aucune non-conformit\xE9 relev\xE9e par le moteur statique.",
56279
+ artifact: (name2) => `Rapport complet (HTML, captures annot\xE9es) : artefact **${name2}** du run.`,
56280
+ runLink: (url) => `[Voir le run et son r\xE9sum\xE9 de job](${url})`,
56281
+ clamped: (n) => `_${n} groupe(s) retir\xE9(s) de ce commentaire pour tenir dans la limite de GitHub \u2014 le r\xE9sum\xE9 de job les porte tous._`,
56107
56282
  unanchored: (n) => `${n} constat(s) rattach\xE9(s) \xE0 une URL, sans ligne de code \xE0 annoter \u2014 voir le rapport.`,
56108
56283
  unattributed: (n) => `${n} constat(s) ne sont rattach\xE9s \xE0 aucune page (code partag\xE9, fichier hors routes) \u2014 compt\xE9s dans l'audit global, jamais r\xE9partis d'office.`,
56109
56284
  sourceBasis: "Une page marqu\xE9e \xAB source \xBB n'a pas d'instantan\xE9 : l'absence de constat n'y vaut PAS conformit\xE9, et son taux ne porte que sur ce que le moteur a pu d\xE9cider ailleurs."
@@ -56119,6 +56294,7 @@ var S = {
56119
56294
  where: "Location",
56120
56295
  what: "Finding",
56121
56296
  more: (n) => `\u2026 and ${n} more.`,
56297
+ moreGroups: (n) => `\u2026 and ${n} more group(s) \u2014 see the job summary.`,
56122
56298
  perPage: "Page-by-page scoreboard",
56123
56299
  page: "Page",
56124
56300
  count: "Findings",
@@ -56126,20 +56302,75 @@ var S = {
56126
56302
  pageRate: "Rate",
56127
56303
  snapshot: "snapshot",
56128
56304
  source: "source",
56305
+ occurrences: "Occ.",
56306
+ pagesCol: "Pages",
56307
+ grouped: (groups, occ) => `${groups} distinct defect(s) \xB7 ${occ} occurrence(s)`,
56308
+ groupNote: "One row per (criterion, rule, selector): one design-system defect repeated across every route counts once. The Occ. and Pages columns carry the real scale.",
56309
+ verdictFail: (n) => `\u{1F534} ${n} blocking non-conformity(ies) \u2014 the gate is red.`,
56310
+ verdictWarn: "\u{1F7E0} No blocking non-conformity; major or minor findings remain.",
56311
+ verdictPass: "\u2705 No non-conformity found by the static engine.",
56312
+ artifact: (name2) => `Full report (HTML, annotated crops): artifact **${name2}** of this run.`,
56313
+ runLink: (url) => `[See the run and its job summary](${url})`,
56314
+ clamped: (n) => `_${n} group(s) dropped from this comment to fit GitHub's limit \u2014 the job summary carries them all._`,
56129
56315
  unanchored: (n) => `${n} finding(s) keyed to a URL, with no code line to annotate \u2014 see the report.`,
56130
56316
  unattributed: (n) => `${n} finding(s) are attributed to no page (shared code, file outside any route) \u2014 counted in the overall audit, never spread across pages.`,
56131
56317
  sourceBasis: 'A page marked "source" has no snapshot: the absence of a finding there does NOT mean conforming, and its rate covers only what the engine could decide elsewhere.'
56132
56318
  }
56133
56319
  };
56134
56320
  var MAX_ROWS = 50;
56321
+ var COMMENT_ROWS = 10;
56322
+ var COMMENT_LIMIT = 65536;
56323
+ function groupFindings(findings, standard, lang, baseDir) {
56324
+ const groups = /* @__PURE__ */ new Map();
56325
+ for (const f of findings) {
56326
+ const criterion = criterionLabel2(f, standard);
56327
+ const key = `${criterion}\0${f.ruleId}\0${f.selectorHint}`;
56328
+ const g = groups.get(key);
56329
+ if (g) {
56330
+ g.occurrences++;
56331
+ if (f.page) g.pageSet.add(f.page);
56332
+ continue;
56333
+ }
56334
+ groups.set(key, {
56335
+ criterion,
56336
+ ruleId: f.ruleId,
56337
+ selectorHint: f.selectorHint,
56338
+ severity: f.severity,
56339
+ message: resolveMessage(f, lang),
56340
+ where: isUrl2(f.file) ? f.file : `${repoRelative(f.file, baseDir)}:${Math.max(1, f.line)}`,
56341
+ occurrences: 1,
56342
+ pages: 0,
56343
+ pageSet: new Set(f.page ? [f.page] : [])
56344
+ });
56345
+ }
56346
+ return [...groups.values()].map(({ pageSet, ...g }) => ({ ...g, pages: pageSet.size })).sort((a, b) => SEV_ORDER4.indexOf(a.severity) - SEV_ORDER4.indexOf(b.severity) || b.occurrences - a.occurrences || a.criterion.localeCompare(b.criterion));
56347
+ }
56348
+ function runRate(result, standard, lang) {
56349
+ const groups = isCore(standard) ? reportGroups(result, lang) : packReportGroups(result, loadPack(standard), lang);
56350
+ const { decided, total } = reportCoverage(groups);
56351
+ const agentRuled = groups.some((g) => g.rows.some((r) => r.decidedBy === "agent" && r.status === "C"));
56352
+ return { text: `${formatRate(decided === 0 ? null : result.conformancePct, decided, total)}${agentRuled ? "*" : ""}`, agentRuled };
56353
+ }
56354
+ function groupTableHead(s) {
56355
+ return [`| ${s.severity} | ${s.criterion} | ${s.where} | ${s.what} | ${s.occurrences} | ${s.pagesCol} |`, "| --- | --- | --- | --- | ---: | ---: |"];
56356
+ }
56357
+ function groupRow(g) {
56358
+ const cell = (v) => v.replace(/\|/g, "\\|");
56359
+ return `| ${ICON5[g.severity]} ${g.severity} | ${cell(g.criterion)} | \`${cell(g.where)}\` (\`${cell(g.selectorHint)}\`) | ${cell(g.message)} | ${g.occurrences} | ${g.pages || "\u2014"} |`;
56360
+ }
56361
+ function groupTable(rows, s) {
56362
+ return [...groupTableHead(s), ...rows.map(groupRow)];
56363
+ }
56135
56364
  function stepSummary(result, opts = {}) {
56136
56365
  const standard = opts.standard ?? CORE2;
56137
56366
  const lang = opts.lang ?? "en";
56138
56367
  const s = S[lang];
56139
56368
  const stdLabel = isCore(standard) ? "WCAG 2.2 AA" : loadPack(standard).name;
56369
+ const rate = runRate(result, standard, lang);
56140
56370
  const out2 = [];
56141
56371
  out2.push(`## ${s.title} \u2014 ${stdLabel}`, "");
56142
- out2.push(`\`${result.date}\` \xB7 ${result.scope.files} ${s.files} \xB7 **${result.conformancePct}%** ${s.rate}`, "");
56372
+ out2.push(`\`${result.date}\` \xB7 ${result.scope.files} ${s.files} \xB7 **${rate.text}** ${s.rate}`, "");
56373
+ if (rate.agentRuled) out2.push(`> ${agentMarkNote(lang)}`, "");
56143
56374
  const baseDir = opts.baseDir ?? process.cwd();
56144
56375
  const all = findingsForStandard(result, standard);
56145
56376
  if (!all.length) {
@@ -56147,21 +56378,52 @@ function stepSummary(result, opts = {}) {
56147
56378
  out2.push(perPageTable(result, standard, lang));
56148
56379
  return out2.join("\n");
56149
56380
  }
56150
- const sorted = [...all].sort((a, b) => SEV_ORDER4.indexOf(a.severity) - SEV_ORDER4.indexOf(b.severity));
56151
- out2.push(`### ${s.findings} (${sorted.length})`, "");
56152
- out2.push(`| ${s.severity} | ${s.criterion} | ${s.where} | ${s.what} |`, "| --- | --- | --- | --- |");
56153
- for (const f of sorted.slice(0, MAX_ROWS)) {
56154
- const where = isUrl2(f.file) ? f.file : `${repoRelative(f.file, baseDir)}:${Math.max(1, f.line)}`;
56155
- const msg = resolveMessage(f, lang).replace(/\|/g, "\\|");
56156
- out2.push(`| ${ICON5[f.severity]} ${f.severity} | ${criterionLabel2(f, standard)} | \`${where}\` | ${msg} |`);
56157
- }
56158
- if (sorted.length > MAX_ROWS) out2.push("", s.more(sorted.length - MAX_ROWS));
56381
+ const grouped = groupFindings(all, standard, lang, baseDir);
56382
+ out2.push(`### ${s.findings} \u2014 ${s.grouped(grouped.length, all.length)}`, "");
56383
+ out2.push(`> ${s.groupNote}`, "");
56384
+ out2.push(...groupTable(grouped.slice(0, MAX_ROWS), s));
56385
+ if (grouped.length > MAX_ROWS) out2.push("", s.more(grouped.length - MAX_ROWS));
56159
56386
  out2.push("");
56160
56387
  const unanchored = all.filter((f) => isUrl2(f.file)).length;
56161
56388
  if (unanchored) out2.push(`> ${s.unanchored(unanchored)}`, "");
56162
56389
  out2.push(perPageTable(result, standard, lang));
56163
56390
  return out2.join("\n");
56164
56391
  }
56392
+ function prComment(result, opts = {}) {
56393
+ const standard = opts.standard ?? CORE2;
56394
+ const lang = opts.lang ?? "en";
56395
+ const s = S[lang];
56396
+ const baseDir = opts.baseDir ?? process.cwd();
56397
+ const stdLabel = isCore(standard) ? "WCAG 2.2 AA" : loadPack(standard).name;
56398
+ const all = findingsForStandard(result, standard);
56399
+ const normative = all.filter((f) => !f.advisory);
56400
+ const blocking = normative.filter((f) => f.severity === "bloquant").length;
56401
+ const rate = runRate(result, standard, lang);
56402
+ const grouped = groupFindings(all, standard, lang, baseDir);
56403
+ const orphans = unattributedFindings(result).filter((f) => !f.advisory).length;
56404
+ const head = [];
56405
+ head.push(`### ${s.title} \u2014 ${stdLabel}`, "");
56406
+ head.push(blocking ? s.verdictFail(blocking) : normative.length ? s.verdictWarn : s.verdictPass, "");
56407
+ head.push(`\`${result.date}\` \xB7 ${result.scope.files} ${s.files} \xB7 **${rate.text}** ${s.rate}`, "");
56408
+ if (rate.agentRuled) head.push(`> ${agentMarkNote(lang)}`, "");
56409
+ if (orphans) head.push(`> ${s.unattributed(orphans)}`, "");
56410
+ const tail = [];
56411
+ if (opts.artifactName) tail.push(s.artifact(opts.artifactName), "");
56412
+ if (opts.runUrl) tail.push(s.runLink(opts.runUrl), "");
56413
+ const assemble = (rows2) => {
56414
+ const body2 = [];
56415
+ if (grouped.length) {
56416
+ body2.push(s.grouped(grouped.length, all.length), "");
56417
+ body2.push(...groupTable(grouped.slice(0, rows2), s), "");
56418
+ const omitted = grouped.length - rows2;
56419
+ if (omitted > 0) body2.push(rows2 < COMMENT_ROWS ? s.clamped(omitted) : s.moreGroups(omitted), "");
56420
+ }
56421
+ return [...head, ...body2, ...tail].join("\n").trimEnd();
56422
+ };
56423
+ let rows = Math.min(COMMENT_ROWS, grouped.length);
56424
+ while (rows > 0 && assemble(rows).length > COMMENT_LIMIT) rows--;
56425
+ return assemble(rows);
56426
+ }
56165
56427
  function perPageTable(result, standard = CORE2, lang = "en") {
56166
56428
  const s = S[lang];
56167
56429
  const scope = pagesOf(result);
@@ -56186,10 +56448,1018 @@ function perPageTable(result, standard = CORE2, lang = "en") {
56186
56448
  return out2.join("\n");
56187
56449
  }
56188
56450
 
56451
+ // src/evidence.ts
56452
+ import { createHash as createHash6 } from "crypto";
56453
+ import { existsSync as existsSync28, mkdirSync as mkdirSync13, readFileSync as readFileSync23, writeFileSync as writeFileSync15 } from "fs";
56454
+ import { join as join40 } from "path";
56455
+ var DEFAULT_GEOMETRY = { pad: 24, minWidth: 320, minHeight: 120, maxWidth: 960, maxHeight: 540 };
56456
+ var DEFAULT_CAPS = { perRule: 6, perPage: 12, total: 200 };
56457
+ var HALO = { r: 255, g: 255, b: 255, a: 0.92 };
56458
+ var MARK3 = { r: 179, g: 38, b: 30, a: 1 };
56459
+ var RING = 3;
56460
+ function pageIdOfSnapshot(file) {
56461
+ if (!isSnapshotDom(file)) return null;
56462
+ const posix3 = file.split("\\").join("/");
56463
+ const at = posix3.lastIndexOf(`${PAGES_DIR}/`);
56464
+ if (at < 0) return null;
56465
+ const rest = posix3.slice(at + PAGES_DIR.length + 1);
56466
+ const id = rest.slice(0, rest.indexOf("/"));
56467
+ return id || null;
56468
+ }
56469
+ function readJson3(file) {
56470
+ try {
56471
+ return JSON.parse(readFileSync23(file, "utf8"));
56472
+ } catch {
56473
+ return void 0;
56474
+ }
56475
+ }
56476
+ function resolveScale(img, viewport, boxes) {
56477
+ const candidates2 = [viewport?.width, boxes.get(0)?.w];
56478
+ for (const w of candidates2) {
56479
+ if (!w || w <= 0) continue;
56480
+ const scale = img.width / w;
56481
+ if (scale >= 0.5 && scale <= 4) return scale;
56482
+ }
56483
+ return null;
56484
+ }
56485
+ function loadPageEvidence(root, pageId) {
56486
+ const dir = snapshotDir(root, pageId);
56487
+ const domFile = join40(dir, "dom.html");
56488
+ const shotFile = join40(dir, "screen.png");
56489
+ if (!existsSync28(domFile)) return { skip: "no-snapshot" };
56490
+ if (!existsSync28(shotFile)) return { skip: "no-screenshot" };
56491
+ let dom;
56492
+ let shot;
56493
+ try {
56494
+ dom = readFileSync23(domFile, "utf8");
56495
+ shot = readFileSync23(shotFile);
56496
+ } catch {
56497
+ return { skip: "no-snapshot" };
56498
+ }
56499
+ const img = decodePng(shot);
56500
+ if (!img) return { skip: "unreadable-image" };
56501
+ const digest = readJson3(join40(dir, "boxes.json"));
56502
+ if (!digest) return { skip: "no-boxes" };
56503
+ const doc = parseHtml(dom, domFile);
56504
+ const boxes = alignedBoxes(dom, digest, doc);
56505
+ if (!boxes) return { skip: "no-boxes" };
56506
+ const scale = resolveScale(img, metaOf(dir)?.viewport, boxes);
56507
+ if (scale === null) return { skip: "unknown-scale" };
56508
+ const ordinalOf = /* @__PURE__ */ new Map();
56509
+ for (let i2 = 0; i2 < doc.elements.length; i2++) {
56510
+ const start2 = doc.elements[i2]?.start;
56511
+ if (start2 !== void 0 && !ordinalOf.has(start2)) ordinalOf.set(start2, i2);
56512
+ }
56513
+ const viewport = metaOf(dir)?.viewport ?? { width: Math.round(img.width / scale), height: Math.round(img.height / scale) };
56514
+ return { pageId, img, scale, viewport, ordinalOf, boxes, truncated: Boolean(digest.truncated) };
56515
+ }
56516
+ function metaOf(dir) {
56517
+ const raw = readJson3(join40(dir, "meta.json"));
56518
+ if (raw === void 0) return void 0;
56519
+ const v = validateSnapshotMeta(raw);
56520
+ return v.ok && v.meta ? v.meta : void 0;
56521
+ }
56522
+ function cropRect(box, g, scale, img) {
56523
+ const bx = box.x * scale;
56524
+ const by = box.y * scale;
56525
+ const bw = box.w * scale;
56526
+ const bh = box.h * scale;
56527
+ const w = Math.min(Math.max(bw + g.pad * 2 * scale, g.minWidth * scale), g.maxWidth * scale, img.width);
56528
+ const h = Math.min(Math.max(bh + g.pad * 2 * scale, g.minHeight * scale), g.maxHeight * scale, img.height);
56529
+ let x = bx + bw / 2 - w / 2;
56530
+ let y = by + bh / 2 - h / 2;
56531
+ x = Math.max(0, Math.min(x, img.width - w));
56532
+ y = Math.max(0, Math.min(y, img.height - h));
56533
+ return { x, y, w, h };
56534
+ }
56535
+ function annotate(raster, target) {
56536
+ strokeRect(raster, { x: target.x - RING, y: target.y - RING, w: target.w + RING * 2, h: target.h + RING * 2 }, HALO, RING);
56537
+ strokeRect(raster, target, MARK3, RING);
56538
+ strokeRect(raster, { x: target.x + RING, y: target.y + RING, w: target.w - RING * 2, h: target.h - RING * 2 }, HALO, 1, 6);
56539
+ const arm = Math.max(6, Math.min(18, Math.round(Math.min(target.w, target.h) / 3)));
56540
+ const t2 = RING;
56541
+ const x0 = target.x - RING;
56542
+ const y0 = target.y - RING;
56543
+ const x1 = target.x + target.w + RING;
56544
+ const y1 = target.y + target.h + RING;
56545
+ for (const [cx, cy, dx, dy] of [
56546
+ [x0, y0, 1, 1],
56547
+ [x1, y0, -1, 1],
56548
+ [x0, y1, 1, -1],
56549
+ [x1, y1, -1, -1]
56550
+ ]) {
56551
+ fillRect(raster, { x: dx > 0 ? cx : cx - arm, y: dy > 0 ? cy - t2 : cy, w: arm, h: t2 }, MARK3);
56552
+ fillRect(raster, { x: dx > 0 ? cx - t2 : cx, y: dy > 0 ? cy : cy - arm, w: t2, h: arm }, MARK3);
56553
+ }
56554
+ }
56555
+ function cropFinding(ctx, f, geometry) {
56556
+ const g = { ...DEFAULT_GEOMETRY, ...geometry };
56557
+ if (f.sourceStart === void 0) return { skip: "no-offsets" };
56558
+ const ordinal = ctx.ordinalOf.get(f.sourceStart);
56559
+ if (ordinal === void 0) return { skip: "unjoinable" };
56560
+ if (ordinal === 0) return { skip: "page-scope" };
56561
+ const box = ctx.boxes.get(ordinal);
56562
+ if (!box) return { skip: ctx.truncated ? "truncated" : "no-boxes" };
56563
+ if (box.w <= 0 || box.h <= 0) return { skip: "zero-area" };
56564
+ const vpArea = ctx.viewport.width * ctx.viewport.height;
56565
+ if (vpArea > 0 && box.w * box.h / vpArea >= 0.7) return { skip: "page-scope" };
56566
+ const visibleTop = Math.max(0, box.y * ctx.scale);
56567
+ const visibleBottom = Math.min(ctx.img.height, (box.y + box.h) * ctx.scale);
56568
+ const visibleFraction = box.h > 0 ? (visibleBottom - visibleTop) / (box.h * ctx.scale) : 0;
56569
+ if (visibleFraction < 0.5) return { skip: "below-the-fold" };
56570
+ const rect = cropRect(box, g, ctx.scale, ctx.img);
56571
+ const crop = cropImage(ctx.img, rect);
56572
+ if (!crop) return { skip: "below-the-fold" };
56573
+ annotate(crop.raster, {
56574
+ x: box.x * ctx.scale - crop.rect.x,
56575
+ y: box.y * ctx.scale - crop.rect.y,
56576
+ w: box.w * ctx.scale,
56577
+ h: box.h * ctx.scale
56578
+ });
56579
+ return {
56580
+ png: encodePng(crop.raster),
56581
+ width: crop.raster.width,
56582
+ height: crop.raster.height,
56583
+ box,
56584
+ scale: ctx.scale,
56585
+ clipped: visibleFraction < 1
56586
+ };
56587
+ }
56588
+ var S2 = {
56589
+ fr: {
56590
+ // No `N.N —` sequence: src/check.ts scans the WHOLE document for criterion mentions.
56591
+ alt: (sel, page) => `Capture recadr\xE9e de l'\xE9l\xE9ment ${sel} sur la page ${page}, entour\xE9 d'un cadre`,
56592
+ notImaged: (n) => `${n} occurrence(s) ne sont pas illustr\xE9es :`,
56593
+ reasons: {
56594
+ "no-snapshot": "la capture de page que ce constat d\xE9signe n'est pas sur ce disque \u2014 sa r\xE9f\xE9rence fichier:ligne reste valable",
56595
+ "no-screenshot": "le producteur n'a pas fourni de capture pour cette page, donc le niveau pixel est inactif ici",
56596
+ "unreadable-image": "la capture n'est pas d\xE9codable (le moteur lit le truecolour 8 bits uniquement) \u2014 mieux vaut aucune image qu'une fausse",
56597
+ "no-boxes": "les positions d'\xE9l\xE9ments ne se v\xE9rifient pas contre le DOM s\xE9rialis\xE9, elles ont donc \xE9t\xE9 refus\xE9es en bloc plut\xF4t que d'encadrer le mauvais \xE9l\xE9ment",
56598
+ truncated: "la page d\xE9passe la limite de collecte : aucune position n'a \xE9t\xE9 enregistr\xE9e pour cet \xE9l\xE9ment",
56599
+ "no-offsets": "le constat ne porte pas d'ancrage dans la source, il ne peut pas \xEAtre joint \xE0 une position",
56600
+ unjoinable: "l'ancrage du constat ne correspond \xE0 aucun \xE9l\xE9ment du DOM s\xE9rialis\xE9",
56601
+ "page-scope": "le constat porte sur la page enti\xE8re \u2014 la capture de page ci-dessus est l'illustration",
56602
+ "zero-area": "l'\xE9l\xE9ment n'occupe aucune surface peinte",
56603
+ "below-the-fold": "l'\xE9l\xE9ment est hors de la capture (la capture couvre la fen\xEAtre, pas la page enti\xE8re) \u2014 sa r\xE9f\xE9rence fichier:ligne reste valable",
56604
+ "unknown-scale": "le rapport pixels/CSS est ind\xE9termin\xE9 pour cette page ; un rapport devin\xE9 encadrerait le mauvais endroit",
56605
+ deduplicated: "m\xEAme d\xE9faut sur le m\xEAme \xE9l\xE9ment : il est montr\xE9 par l'illustration d'une autre occurrence, rien n'a \xE9t\xE9 perdu",
56606
+ capped: "d\xE9faut distinct laiss\xE9 sans image : le plafond de vignettes de ce lot est atteint \u2014 relevez `--evidence-max` pour les obtenir"
56607
+ }
56608
+ },
56609
+ en: {
56610
+ alt: (sel, page) => `Cropped screenshot of the ${sel} element on the ${page} page, outlined`,
56611
+ notImaged: (n) => `${n} occurrence(s) are not illustrated:`,
56612
+ reasons: {
56613
+ "no-snapshot": "the page snapshot this finding names is not on this disk \u2014 its file:line reference still holds",
56614
+ "no-screenshot": "the producer supplied no screenshot for this page, so the pixel tier is inactive here",
56615
+ "unreadable-image": "the screenshot does not decode (the engine reads 8-bit truecolour only) \u2014 no image beats a wrong one",
56616
+ "no-boxes": "the element positions do not verify against the serialized DOM, so they were refused wholesale rather than outlining the wrong element",
56617
+ truncated: "the page exceeded the collection cap: no position was recorded for this element",
56618
+ "no-offsets": "the finding carries no source anchor, so it cannot be joined to a position",
56619
+ unjoinable: "the finding's anchor matches no element in the serialized DOM",
56620
+ "page-scope": "the finding is about the whole page \u2014 the page screenshot above is the illustration",
56621
+ "zero-area": "the element paints no box",
56622
+ "below-the-fold": "the element sits outside the capture (the screenshot covers the viewport, not the whole page) \u2014 its file:line reference still holds",
56623
+ "unknown-scale": "the image-pixel to CSS-pixel ratio is indeterminate for this page; a guessed ratio would outline the wrong place",
56624
+ deduplicated: "the same defect on the same element: it is shown by the illustration of another occurrence, nothing was lost",
56625
+ capped: "a distinct defect left undrawn: this run's crop limit was reached \u2014 raise `--evidence-max` to get them"
56626
+ }
56627
+ }
56628
+ };
56629
+ function evidenceRefusals(m, pageId, lang) {
56630
+ const t2 = pageId === null ? m.totals : m.perPage.get(pageId);
56631
+ if (!t2) return void 0;
56632
+ const missing = t2.located - t2.imaged;
56633
+ if (missing <= 0) return void 0;
56634
+ const entries = Object.entries(t2.skipped).filter(([, n]) => n);
56635
+ const s = S2[lang];
56636
+ return {
56637
+ headline: s.notImaged(missing),
56638
+ reasons: entries.sort((a, b) => b[1] - a[1]).map(([reason, n]) => `${n} \u2014 ${s.reasons[reason]}`)
56639
+ };
56640
+ }
56641
+ function evidenceNotice(m, pageId, lang) {
56642
+ const r = evidenceRefusals(m, pageId, lang);
56643
+ return r ? [r.headline, ...r.reasons.map((x) => `- ${x}`)] : [];
56644
+ }
56645
+ function bump(t2, reason) {
56646
+ t2.skipped[reason] = (t2.skipped[reason] ?? 0) + 1;
56647
+ }
56648
+ function cropName(f) {
56649
+ return `${createHash6("sha1").update(findingId(f)).digest("hex").slice(0, 12)}.png`;
56650
+ }
56651
+ function writeEvidence(result, opts) {
56652
+ const root = opts.root ?? process.cwd();
56653
+ const caps = { ...DEFAULT_CAPS, ...opts.caps };
56654
+ const manifest = {
56655
+ crops: /* @__PURE__ */ new Map(),
56656
+ skipped: /* @__PURE__ */ new Map(),
56657
+ perPage: /* @__PURE__ */ new Map(),
56658
+ totals: { located: 0, imaged: 0, skipped: {} }
56659
+ };
56660
+ const byPage = /* @__PURE__ */ new Map();
56661
+ for (const f of [...result.findings, ...result.packFindings ?? []]) {
56662
+ if (f.advisory) continue;
56663
+ const page = pageIdOfSnapshot(f.file);
56664
+ if (!page) continue;
56665
+ const list = byPage.get(page);
56666
+ if (list) list.push(f);
56667
+ else byPage.set(page, [f]);
56668
+ }
56669
+ for (const [pageId, findings] of [...byPage].sort((a, b) => a[0].localeCompare(b[0]))) {
56670
+ const tally = { located: findings.length, imaged: 0, skipped: {} };
56671
+ manifest.perPage.set(pageId, tally);
56672
+ manifest.totals.located += findings.length;
56673
+ const ctx = loadPageEvidence(root, pageId);
56674
+ if ("skip" in ctx) {
56675
+ for (const f of findings) {
56676
+ manifest.skipped.set(findingId(f), ctx.skip);
56677
+ bump(tally, ctx.skip);
56678
+ bump(manifest.totals, ctx.skip);
56679
+ }
56680
+ continue;
56681
+ }
56682
+ const dir = join40(opts.outDir, "assets", pageId);
56683
+ let wrote = false;
56684
+ const perRule = /* @__PURE__ */ new Map();
56685
+ const seen = /* @__PURE__ */ new Set();
56686
+ let onPage = 0;
56687
+ for (const f of findings) {
56688
+ const id = findingId(f);
56689
+ const key = `${f.ruleId}\0${f.selectorHint}`;
56690
+ const ruleCount = perRule.get(f.ruleId) ?? 0;
56691
+ const overLimit = ruleCount >= caps.perRule || onPage >= caps.perPage || manifest.crops.size >= caps.total;
56692
+ const refusal = seen.has(key) ? "deduplicated" : overLimit ? "capped" : null;
56693
+ if (refusal) {
56694
+ manifest.skipped.set(id, refusal);
56695
+ bump(tally, refusal);
56696
+ bump(manifest.totals, refusal);
56697
+ continue;
56698
+ }
56699
+ const crop = cropFinding(ctx, f, opts.geometry);
56700
+ if ("skip" in crop) {
56701
+ manifest.skipped.set(id, crop.skip);
56702
+ bump(tally, crop.skip);
56703
+ bump(manifest.totals, crop.skip);
56704
+ continue;
56705
+ }
56706
+ const name2 = cropName(f);
56707
+ const path = join40(dir, name2);
56708
+ try {
56709
+ if (!wrote) {
56710
+ mkdirSync13(dir, { recursive: true });
56711
+ wrote = true;
56712
+ }
56713
+ writeFileSync15(path, crop.png);
56714
+ } catch {
56715
+ manifest.skipped.set(id, "no-screenshot");
56716
+ bump(tally, "no-screenshot");
56717
+ bump(manifest.totals, "no-screenshot");
56718
+ continue;
56719
+ }
56720
+ seen.add(key);
56721
+ perRule.set(f.ruleId, ruleCount + 1);
56722
+ onPage++;
56723
+ tally.imaged++;
56724
+ manifest.totals.imaged++;
56725
+ manifest.crops.set(id, {
56726
+ findingId: id,
56727
+ page: pageId,
56728
+ ruleId: f.ruleId,
56729
+ criteriaId: f.criteriaId,
56730
+ href: `./assets/${pageId}/${name2}`,
56731
+ path,
56732
+ width: crop.width,
56733
+ height: crop.height,
56734
+ box: crop.box,
56735
+ scale: crop.scale,
56736
+ clipped: crop.clipped,
56737
+ alt: { fr: S2.fr.alt(f.selectorHint, pageId), en: S2.en.alt(f.selectorHint, pageId) }
56738
+ });
56739
+ }
56740
+ }
56741
+ return manifest;
56742
+ }
56743
+
56744
+ // src/html-emit.ts
56745
+ import { mkdirSync as mkdirSync14, readFileSync as readFileSync24, statSync as statSync11, writeFileSync as writeFileSync16 } from "fs";
56746
+ import { join as join41 } from "path";
56747
+
56748
+ // src/html.ts
56749
+ var MARK4 = { C: "C", NC: "NC", NA: "\u2014", manual: "?" };
56750
+ var CLASS = { C: "c", NC: "nc", NA: "na", manual: "m" };
56751
+ var T3 = {
56752
+ fr: {
56753
+ skip: "Aller au contenu",
56754
+ nav: "Documents du rapport",
56755
+ printHint: "Pour un PDF : imprimez cette page (Ctrl/Cmd + P) et choisissez \xAB Enregistrer au format PDF \xBB.",
56756
+ generated: "G\xE9n\xE9r\xE9 par ultra11y"
56757
+ },
56758
+ en: {
56759
+ skip: "Skip to content",
56760
+ nav: "Report documents",
56761
+ printHint: "For a PDF: print this page (Ctrl/Cmd + P) and choose \u201CSave as PDF\u201D.",
56762
+ generated: "Generated by ultra11y"
56763
+ }
56764
+ };
56765
+ function esc2(s) {
56766
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
56767
+ }
56768
+ var STYLE = `
56769
+ :root { color-scheme: light dark; --bg:#fff; --fg:#111; --line:#e3e3e6; --mut:#666; --soft:#f6f6f8;
56770
+ --c:#0a7d33; --nc:#b3261e; --m:#b06000; --accent:#0b5fa5; }
56771
+ @media (prefers-color-scheme: dark) { :root { --bg:#141416; --fg:#f2f2f7; --line:#2c2c2e; --mut:#a1a1a6; --soft:#1c1c1f;
56772
+ --c:#3fbf6f; --nc:#ff6b5e; --m:#d99a2b; --accent:#6cb6ff; } }
56773
+ * { box-sizing:border-box; }
56774
+ body { margin:0; background:var(--bg); color:var(--fg); font:15px/1.6 ui-sans-serif,system-ui,-apple-system,sans-serif; }
56775
+ .skip { position:absolute; left:-9999px; top:0; padding:8px 14px; background:var(--bg); color:var(--fg); border:2px solid var(--accent); z-index:9; }
56776
+ .skip:focus { left:8px; top:8px; }
56777
+ .wrap { max-width:78ch; margin:0 auto; padding:32px 20px 64px; }
56778
+ header.top { border-bottom:1px solid var(--line); }
56779
+ h1 { font-size:26px; line-height:1.25; margin:0 0 6px; }
56780
+ h2 { font-size:20px; margin:36px 0 10px; padding-top:8px; border-top:1px solid var(--line); }
56781
+ h3 { font-size:17px; margin:26px 0 8px; }
56782
+ h4 { font-size:15px; margin:20px 0 6px; }
56783
+ p { margin:0 0 12px; } p.sub { color:var(--mut); margin:0 0 4px; }
56784
+ a { color:var(--accent); }
56785
+ a:focus-visible, summary:focus-visible { outline:3px solid var(--accent); outline-offset:2px; }
56786
+ nav.docs ul { list-style:none; display:flex; flex-wrap:wrap; gap:6px 16px; margin:12px 0 0; padding:0; }
56787
+ nav.docs [aria-current="page"] { font-weight:700; }
56788
+ code, .mono { font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace; overflow-wrap:anywhere; }
56789
+ ul.body { margin:0 0 12px; padding-left:22px; }
56790
+ .note { margin:0 0 14px; padding:10px 14px; border-left:4px solid var(--mut); background:var(--soft); }
56791
+ .note.warn { border-left-color:var(--m); }
56792
+ .tw { overflow-x:auto; margin:0 0 18px; }
56793
+ table { border-collapse:collapse; width:100%; font-size:14px; }
56794
+ caption { text-align:left; font-weight:600; padding:0 0 8px; }
56795
+ th, td { padding:6px 10px; border-bottom:1px solid var(--line); text-align:left; vertical-align:top; }
56796
+ thead th { border-bottom:2px solid var(--line); background:var(--soft); }
56797
+ td.end, th.end { text-align:right; }
56798
+ tr.group th { background:var(--soft); font-weight:700; }
56799
+ .st { font-weight:700; }
56800
+ .st-c{color:var(--c)} .st-nc{color:var(--nc)} .st-na{color:var(--mut)} .st-m{color:var(--m)}
56801
+ figure { margin:0 0 18px; }
56802
+ figure img { max-width:100%; height:auto; border:1px solid var(--line); }
56803
+ figcaption { color:var(--mut); font-size:13px; margin-top:6px; }
56804
+ details { margin:0 0 14px; border:1px solid var(--line); padding:8px 12px; }
56805
+ summary { cursor:pointer; font-weight:600; }
56806
+ footer.bot { margin-top:48px; padding-top:12px; border-top:1px solid var(--line); color:var(--mut); font-size:13px; }
56807
+ @media print {
56808
+ :root { --bg:#fff; --fg:#000; --line:#bbb; --mut:#444; --soft:#f2f2f2; --c:#0a7d33; --nc:#b3261e; --m:#8a4b00; --accent:#00449e; }
56809
+ .skip, nav.docs { display:none; }
56810
+ .wrap { max-width:none; padding:0; }
56811
+ body { font-size:11pt; }
56812
+ /* One criterion per block, never split across a page break \u2014 the whole point of a
56813
+ printable deliverable is that a finding and its evidence stay on the same sheet. */
56814
+ h2, h3 { break-after:avoid-page; page-break-after:avoid; }
56815
+ figure, table, .note, details { break-inside:avoid-page; page-break-inside:avoid; }
56816
+ details { border:0; padding:0; } details > summary { list-style:none; }
56817
+ /* A collapsed section would print as a title with nothing under it. */
56818
+ details[open], details:not([open]) > *:not(summary) { display:revert; }
56819
+ figure img { max-width:16cm; }
56820
+ a { color:inherit; text-decoration:underline; }
56821
+ }`;
56822
+ function runHtml(r) {
56823
+ let inner = esc2(r.text);
56824
+ if (r.mono) inner = `<code>${inner}</code>`;
56825
+ if (r.strong) inner = `<strong>${inner}</strong>`;
56826
+ if (r.em) inner = `<em>${inner}</em>`;
56827
+ return r.href ? `<a href="${esc2(r.href)}">${inner}</a>` : inner;
56828
+ }
56829
+ var runsHtml = (runs) => runs.map(runHtml).join("");
56830
+ function cellHtml(c2, tag) {
56831
+ const attrs = [];
56832
+ if (tag === "th") attrs.push('scope="row"');
56833
+ if (c2.align === "end") attrs.push('class="end"');
56834
+ if (c2.colspan && c2.colspan > 1) attrs.push(`colspan="${c2.colspan}"`);
56835
+ let inner;
56836
+ if (c2.status) {
56837
+ inner = `<span class="st st-${CLASS[c2.status]}">${esc2(MARK4[c2.status])}</span>`;
56838
+ } else {
56839
+ inner = c2.text === "" ? "\u2014" : runHtml({ text: c2.text, mono: c2.mono, strong: c2.strong, ...c2.href ? { href: c2.href } : {} });
56840
+ }
56841
+ return `<${tag}${attrs.length ? ` ${attrs.join(" ")}` : ""}>${inner}</${tag}>`;
56842
+ }
56843
+ function blockHtml(b) {
56844
+ switch (b.kind) {
56845
+ case "heading":
56846
+ return [`<h${b.level}${b.id ? ` id="${esc2(b.id)}"` : ""}>${esc2(b.text)}</h${b.level}>`];
56847
+ case "para":
56848
+ return [`<p>${runsHtml(b.runs)}</p>`];
56849
+ case "note":
56850
+ return [`<div class="note${b.tone === "warn" ? " warn" : ""}">${runsHtml(b.runs)}</div>`];
56851
+ case "list":
56852
+ return [`<ul class="body">${b.items.map((i2) => `<li>${runsHtml(i2)}</li>`).join("")}</ul>`];
56853
+ case "figure":
56854
+ return [`<figure><img src="${esc2(b.src)}" alt="${esc2(b.alt)}">${b.caption ? `<figcaption>${esc2(b.caption)}</figcaption>` : ""}</figure>`];
56855
+ case "details":
56856
+ return [`<details open><summary>${esc2(b.summary)}</summary>`, ...b.blocks.flatMap(blockHtml), "</details>"];
56857
+ case "table": {
56858
+ const out2 = ['<div class="tw">', "<table>", `<caption>${esc2(b.caption)}</caption>`, "<thead><tr>"];
56859
+ for (const col of b.columns) out2.push(`<th scope="col"${col.align === "end" ? ' class="end"' : ""}>${esc2(col.text)}</th>`);
56860
+ out2.push("</tr></thead><tbody>");
56861
+ for (const row of b.rows) {
56862
+ const group = row.length === 1 && (row[0]?.colspan ?? 0) > 1;
56863
+ if (group) {
56864
+ const c2 = row[0];
56865
+ out2.push(`<tr class="group"><th scope="colgroup" colspan="${c2.colspan}">${esc2(c2.text)}</th></tr>`);
56866
+ continue;
56867
+ }
56868
+ out2.push(`<tr>${row.map((c2, i2) => cellHtml(c2, i2 === 0 || c2.header ? "th" : "td")).join("")}</tr>`);
56869
+ }
56870
+ out2.push("</tbody></table>", "</div>");
56871
+ return out2;
56872
+ }
56873
+ }
56874
+ }
56875
+ function renderHtmlDocument(doc) {
56876
+ const t2 = T3[doc.lang];
56877
+ const out2 = [];
56878
+ out2.push(`<!doctype html>`, `<html lang="${doc.lang}">`, "<head>");
56879
+ out2.push('<meta charset="utf-8">', '<meta name="viewport" content="width=device-width, initial-scale=1">');
56880
+ out2.push(`<title>${esc2(doc.title)}</title>`);
56881
+ out2.push(`<style>${STYLE}</style>`);
56882
+ out2.push("</head>", "<body>");
56883
+ out2.push(`<a class="skip" href="#content">${esc2(t2.skip)}</a>`);
56884
+ out2.push('<header class="top"><div class="wrap">');
56885
+ out2.push(`<h1>${esc2(doc.title)}</h1>`);
56886
+ if (doc.subtitle?.length) out2.push(`<p class="sub">${runsHtml(doc.subtitle)}</p>`);
56887
+ if (doc.nav?.length) {
56888
+ out2.push(`<nav class="docs" aria-label="${esc2(t2.nav)}"><ul>`);
56889
+ for (const l of doc.nav) out2.push(`<li><a href="${esc2(l.href)}"${l.current ? ' aria-current="page"' : ""}>${esc2(l.text)}</a></li>`);
56890
+ out2.push("</ul></nav>");
56891
+ }
56892
+ out2.push("</div></header>");
56893
+ out2.push('<main id="content" class="wrap">');
56894
+ for (const b of doc.blocks) out2.push(...blockHtml(b));
56895
+ out2.push("</main>");
56896
+ out2.push(`<footer class="bot"><div class="wrap"><p>${esc2(t2.generated)} \xB7 ${esc2(t2.printHint)}</p></div></footer>`);
56897
+ out2.push("</body>", "</html>");
56898
+ return `${out2.join("\n")}
56899
+ `;
56900
+ }
56901
+
56902
+ // src/html-report.ts
56903
+ function refusalBlocks(r) {
56904
+ if (!r) return [];
56905
+ return [
56906
+ { kind: "note", tone: "warn", runs: [{ text: r.headline }] },
56907
+ { kind: "list", items: r.reasons.map((text) => [{ text }]) }
56908
+ ];
56909
+ }
56910
+ var T4 = {
56911
+ fr: {
56912
+ reportTitle: "Rapport de conformit\xE9",
56913
+ indexTitle: "Rapport d'accessibilit\xE9",
56914
+ pagesTitle: "Rapport page par page",
56915
+ date: "Date",
56916
+ files: "fichiers",
56917
+ rate: "r\xE9ussite automatique",
56918
+ synthesis: "Synth\xE8se",
56919
+ synthCaption: (h) => `Nombre de crit\xE8res par statut, regroup\xE9s par ${h}.`,
56920
+ byGuideline: "r\xE8gle WCAG",
56921
+ byTheme: "th\xE9matique",
56922
+ total: "Total",
56923
+ conforming: "C",
56924
+ nonConforming: "NC",
56925
+ notApplicable: "NA",
56926
+ toAssess: "\xC0 \xE9valuer",
56927
+ group: "Groupe",
56928
+ coverage: (d, t2) => `Couverture : ${d}/${t2} crit\xE8re(s) d\xE9cid\xE9(s). Le taux ne porte que sur eux et ne dit rien des ${t2 - d} autres.`,
56929
+ ncTitle: "Non-conformit\xE9s",
56930
+ ncCaption: "Une entr\xE9e par crit\xE8re non conforme.",
56931
+ noNc: "Aucune non-conformit\xE9 relev\xE9e par le moteur statique. Les crit\xE8res \xAB \xE0 \xE9valuer \xBB restent \xE0 trancher.",
56932
+ recTitle: "Recommandations (non normatives)",
56933
+ recNote: "Bonnes pratiques sans test normatif en \xE9chec : elles ne rendent aucun crit\xE8re non conforme et n'entrent pas dans le taux.",
56934
+ occurrences: "Occurrences",
56935
+ occCaption: (id) => `Occurrences du crit\xE8re ${id}.`,
56936
+ where: "Emplacement",
56937
+ selector: "S\xE9lecteur",
56938
+ what: "Constat",
56939
+ evidence: "Preuve",
56940
+ perPage: "Bilan page par page",
56941
+ perPageCaption: "Taux et constats par page, avec la base sur laquelle chaque page a \xE9t\xE9 jug\xE9e.",
56942
+ page: "Page",
56943
+ url: "URL",
56944
+ basis: "Base",
56945
+ pageRate: "Taux",
56946
+ sheet: "Fiche",
56947
+ grid: "Grille des crit\xE8res",
56948
+ gridCaption: (n) => `Statut de chaque crit\xE8re pour la page ${n}.`,
56949
+ crossGridCaption: "Statut de chaque crit\xE8re, page par page.",
56950
+ criterion: "Crit\xE8re",
56951
+ status: "Statut",
56952
+ legend: "`C` conforme \xB7 `NC` non conforme \xB7 `\u2014` non applicable \xB7 `?` \xE0 \xE9valuer.",
56953
+ tally: (c2, nc, na, m) => `${c2} conforme(s) \xB7 ${nc} non conforme(s) \xB7 ${na} non applicable(s) \xB7 ${m} \xE0 \xE9valuer`,
56954
+ manualWarn: "Un crit\xE8re \xAB \xE0 \xE9valuer \xBB n'est ni conforme ni non conforme : personne ne l'a encore tranch\xE9. Les crit\xE8res de jugement s'adjugent avec `verify --manual`, ceux \xAB \xE0 restituer \xBB avec `scan`.",
56955
+ documents: "Documents",
56956
+ openComposite: "Rapport complet, en un seul fichier (imprimable en PDF)",
56957
+ openPages: "Rapport page par page",
56958
+ noScreenshot: "Aucune capture d'\xE9cran pour cette page.",
56959
+ screenshotAlt: (n) => `Capture d'\xE9cran de la page ${n}`
56960
+ },
56961
+ en: {
56962
+ reportTitle: "Conformance report",
56963
+ indexTitle: "Accessibility report",
56964
+ pagesTitle: "Page-by-page report",
56965
+ date: "Date",
56966
+ files: "files",
56967
+ rate: "automatic pass rate",
56968
+ synthesis: "Synthesis",
56969
+ synthCaption: (h) => `Criteria count per status, grouped by ${h}.`,
56970
+ byGuideline: "WCAG guideline",
56971
+ byTheme: "theme",
56972
+ total: "Total",
56973
+ conforming: "C",
56974
+ nonConforming: "NC",
56975
+ notApplicable: "NA",
56976
+ toAssess: "To assess",
56977
+ group: "Group",
56978
+ coverage: (d, t2) => `Coverage: ${d}/${t2} criteria decided. The rate covers only those and says nothing about the other ${t2 - d}.`,
56979
+ ncTitle: "Non-conformities",
56980
+ ncCaption: "One entry per non-conforming criterion.",
56981
+ noNc: "No non-conformity found by the static engine. The criteria left to assess are still open.",
56982
+ recTitle: "Recommendations (non-normative)",
56983
+ recNote: "Good practices with no failing normative test: they never make a criterion non-conforming and do not enter the rate.",
56984
+ occurrences: "Occurrences",
56985
+ occCaption: (id) => `Occurrences of criterion ${id}.`,
56986
+ where: "Location",
56987
+ selector: "Selector",
56988
+ what: "Finding",
56989
+ evidence: "Evidence",
56990
+ perPage: "Page-by-page scoreboard",
56991
+ perPageCaption: "Rate and findings per page, with the basis each page was judged on.",
56992
+ page: "Page",
56993
+ url: "URL",
56994
+ basis: "Basis",
56995
+ pageRate: "Rate",
56996
+ sheet: "Sheet",
56997
+ grid: "Criteria grid",
56998
+ gridCaption: (n) => `Each criterion's status for the ${n} page.`,
56999
+ crossGridCaption: "Each criterion's status, page by page.",
57000
+ criterion: "Criterion",
57001
+ status: "Status",
57002
+ legend: "`C` conforming \xB7 `NC` non-conforming \xB7 `\u2014` not applicable \xB7 `?` to assess.",
57003
+ tally: (c2, nc, na, m) => `${c2} conforming \xB7 ${nc} non-conforming \xB7 ${na} not applicable \xB7 ${m} to assess`,
57004
+ manualWarn: 'A criterion "to assess" is neither conforming nor non-conforming: nobody has ruled on it yet. Judgment criteria are adjudicated with `verify --manual`, rendering ones with `scan`.',
57005
+ documents: "Documents",
57006
+ openComposite: "Full report, in a single file (printable to PDF)",
57007
+ openPages: "Page-by-page report",
57008
+ noScreenshot: "No screenshot for this page.",
57009
+ screenshotAlt: (n) => `Screenshot of the ${n} page`
57010
+ }
57011
+ };
57012
+ function ticks(text) {
57013
+ return text.split(/`([^`]+)`/).flatMap((part, i2) => part === "" ? [] : i2 % 2 === 1 ? [{ text: part, mono: true }] : [{ text: part }]);
57014
+ }
57015
+ var stdName = (standard) => isCore(standard) ? "WCAG 2.2 AA" : loadPack(standard).name;
57016
+ function headline(result, standard, lang) {
57017
+ const t2 = T4[lang];
57018
+ const groups = isCore(standard) ? reportGroups(result, lang) : packReportGroups(result, loadPack(standard), lang);
57019
+ const { decided, total } = reportCoverage(groups);
57020
+ const agentRuled = groups.some((g) => g.rows.some((r) => r.decidedBy === "agent" && r.status === "C"));
57021
+ return {
57022
+ runs: [
57023
+ { text: result.date, mono: true },
57024
+ { text: ` \xB7 ${result.scope.files} ${t2.files} \xB7 ` },
57025
+ { text: `${formatRate(decided === 0 ? null : result.conformancePct, decided, total)}${agentRuled ? "*" : ""}`, strong: true },
57026
+ { text: ` ${t2.rate}` }
57027
+ ],
57028
+ agentRuled,
57029
+ decided,
57030
+ total
57031
+ };
57032
+ }
57033
+ function synthesisBlocks(result, standard, lang) {
57034
+ const t2 = T4[lang];
57035
+ const core = isCore(standard);
57036
+ const groups = core ? reportGroups(result, lang) : packReportGroups(result, loadPack(standard), lang);
57037
+ const tot = reportTotals(groups);
57038
+ const { decided, total } = reportCoverage(groups);
57039
+ const rows = groups.map((g) => {
57040
+ const x = tallyRows(g.rows);
57041
+ return [
57042
+ { text: `${g.key} ${g.title}`.trim() },
57043
+ { text: String(x.c), align: "end" },
57044
+ { text: String(x.nc), align: "end" },
57045
+ { text: String(x.na), align: "end" },
57046
+ { text: String(x.manual), align: "end" }
57047
+ ];
57048
+ });
57049
+ rows.push([
57050
+ { text: t2.total, strong: true },
57051
+ { text: String(tot.c), align: "end", strong: true },
57052
+ { text: String(tot.nc), align: "end", strong: true },
57053
+ { text: String(tot.na), align: "end", strong: true },
57054
+ { text: String(tot.manual), align: "end", strong: true }
57055
+ ]);
57056
+ return [
57057
+ { kind: "heading", level: 2, text: t2.synthesis, id: "synthesis" },
57058
+ {
57059
+ kind: "table",
57060
+ caption: t2.synthCaption(core ? t2.byGuideline : t2.byTheme),
57061
+ columns: [
57062
+ { text: t2.group },
57063
+ { text: t2.conforming, align: "end" },
57064
+ { text: t2.nonConforming, align: "end" },
57065
+ { text: t2.notApplicable, align: "end" },
57066
+ { text: t2.toAssess, align: "end" }
57067
+ ],
57068
+ rows
57069
+ },
57070
+ { kind: "note", tone: "warn", runs: ticks(t2.coverage(decided, total)) },
57071
+ { kind: "note", tone: "warn", runs: ticks(t2.manualWarn) }
57072
+ ];
57073
+ }
57074
+ function criterionBlocks(unit, standard, lang, level, crops) {
57075
+ const t2 = T4[lang];
57076
+ const m = auditorUnitModel(unit, standard, lang, { collapse: true });
57077
+ const out2 = [{ kind: "heading", level, text: `${m.icon} ${m.label}`, id: `c-${unit.criteriaId}` }];
57078
+ out2.push({ kind: "note", tone: "info", runs: ticks(m.normativeNote) });
57079
+ out2.push({ kind: "list", items: m.fields.map((f) => [{ text: `${f.label} : `, strong: true }, ...ticks(f.value)]) });
57080
+ out2.push({
57081
+ kind: "para",
57082
+ runs: [{ text: `${t2.what} (${m.conformanceTerms.nonConformant}) : `, strong: true }, { text: `${m.occurrences} \u2014 ${m.messages.join(" ; ")}` }]
57083
+ });
57084
+ if (m.fixes.length) out2.push({ kind: "para", runs: [{ text: `${m.conformanceTerms.conformant} : `, strong: true }, { text: m.fixes.join(" ; ") }] });
57085
+ const hasEvidence = crops ? m.normative.some((f) => crops(f)) : false;
57086
+ const columns = [{ text: t2.where }, { text: t2.selector }, { text: t2.what }, ...hasEvidence ? [{ text: t2.evidence }] : []];
57087
+ const rows = m.normative.map((f) => {
57088
+ const cells = [{ text: `${f.file}:${f.line}`, mono: true }, { text: f.selectorHint, mono: true }, { text: resolveOccurrence(f, lang) }];
57089
+ if (hasEvidence) cells.push({ text: crops?.(f) ? "\u25A3" : "" });
57090
+ return cells;
57091
+ });
57092
+ if (rows.length) out2.push({ kind: "table", caption: t2.occCaption(unit.criteriaId), columns, rows });
57093
+ if (crops) {
57094
+ for (const f of m.normative) {
57095
+ const c2 = crops(f);
57096
+ if (c2) out2.push({ kind: "figure", src: c2.href, alt: c2.alt, caption: `${f.file}:${f.line} \u2014 ${f.selectorHint}` });
57097
+ }
57098
+ }
57099
+ return out2;
57100
+ }
57101
+ function resolveOccurrence(f, lang) {
57102
+ return f.msg ? auditorUnitModel({ criteriaId: f.criteriaId, title: "", label: "", refs: [], severity: f.severity, findings: [f] }, CORE2, lang).messages[0] ?? f.message : f.message;
57103
+ }
57104
+ function findingsBlocks(result, standard, lang, level, crops, refusals) {
57105
+ const t2 = T4[lang];
57106
+ const { nc, advisory } = partitionUnits(prdUnits(result, standard, lang));
57107
+ const out2 = [{ kind: "heading", level: 2, text: t2.ncTitle, id: "nc" }];
57108
+ out2.push(...refusalBlocks(refusals?.(null)));
57109
+ if (!nc.length) out2.push({ kind: "para", runs: [{ text: t2.noNc }] });
57110
+ for (const u of nc) out2.push(...criterionBlocks(u, standard, lang, level === 2 ? 3 : 4, crops));
57111
+ if (advisory.length) {
57112
+ out2.push({ kind: "heading", level: 2, text: t2.recTitle, id: "rec" });
57113
+ out2.push({ kind: "note", tone: "info", runs: ticks(t2.recNote) });
57114
+ for (const u of advisory) out2.push(...criterionBlocks(u, standard, lang, level === 2 ? 3 : 4, crops));
57115
+ }
57116
+ return out2;
57117
+ }
57118
+ function scoreboardBlocks(result, standard, lang, sheetHref) {
57119
+ const t2 = T4[lang];
57120
+ const scope = pagesOf(result);
57121
+ if (!scope.length) return [];
57122
+ const derived = derivePages(result, scope);
57123
+ const rows = derived.map((p) => [
57124
+ { text: `${p.name}${p.auth ? " \u{1F512}" : ""}`, ...sheetHref ? { href: sheetHref(p.id) } : {} },
57125
+ { text: p.url, mono: true },
57126
+ { text: basisLabel(p.basis, lang) },
57127
+ { text: formatRate(p.conformancePct, p.decided, p.total), align: "end" }
57128
+ ]);
57129
+ const out2 = [
57130
+ { kind: "heading", level: 2, text: t2.perPage, id: "pages" },
57131
+ {
57132
+ kind: "table",
57133
+ caption: t2.perPageCaption,
57134
+ columns: [{ text: t2.page }, { text: t2.url }, { text: t2.basis }, { text: t2.pageRate, align: "end" }],
57135
+ rows
57136
+ }
57137
+ ];
57138
+ const orphans = unattributedFindings(result).filter((f) => !f.advisory).length;
57139
+ if (orphans) out2.push({ kind: "note", tone: "warn", runs: ticks(unattributedNote(orphans, lang)) });
57140
+ for (const basis of ["attributed", "not-audited"]) {
57141
+ const note = derived.some((p) => p.basis === basis) ? pageBasisWarning(basis, lang) : void 0;
57142
+ if (note) out2.push({ kind: "note", tone: "warn", runs: ticks(note) });
57143
+ }
57144
+ return out2;
57145
+ }
57146
+ function crossGridBlocks(result, derived, standard, lang) {
57147
+ const t2 = T4[lang];
57148
+ if (!derived.length) return [];
57149
+ const { rows, status } = pageGridModel(result, derived, standard, lang);
57150
+ if (!rows.length) return [];
57151
+ const table = {
57152
+ kind: "table",
57153
+ caption: t2.crossGridCaption,
57154
+ columns: [{ text: t2.criterion }, ...derived.map((p) => ({ text: `${p.name}${p.auth ? " \u{1F512}" : ""}` }))],
57155
+ rows: []
57156
+ };
57157
+ let group = "";
57158
+ for (const row of rows) {
57159
+ if (row.group !== group) {
57160
+ group = row.group;
57161
+ table.rows.push([{ text: group, colspan: derived.length + 1 }]);
57162
+ }
57163
+ table.rows.push([{ text: row.label }, ...derived.map((p) => ({ status: status.get(row.id)?.get(p.id) ?? "manual", text: "" }))]);
57164
+ }
57165
+ return [{ kind: "heading", level: 2, text: t2.grid, id: "grid" }, { kind: "note", tone: "info", runs: ticks(t2.legend) }, table];
57166
+ }
57167
+ function pageGridBlocks(result, page, standard, lang) {
57168
+ const t2 = T4[lang];
57169
+ const rows = pageCriterionRows(result, page, standard, lang);
57170
+ if (!rows.length) return [];
57171
+ const cov = pageCoverage(rows);
57172
+ const tally = pageTally(rows);
57173
+ const table = { kind: "table", caption: t2.gridCaption(page.name), columns: [{ text: t2.criterion }, { text: t2.status }], rows: [] };
57174
+ let group = "";
57175
+ for (const row of rows) {
57176
+ if (row.group !== group) {
57177
+ group = row.group;
57178
+ table.rows.push([{ text: group, colspan: 2 }]);
57179
+ }
57180
+ table.rows.push([{ text: row.label }, { status: row.status, text: row.decidedBy === "agent" && row.status === "C" ? "*" : "" }]);
57181
+ }
57182
+ const out2 = [
57183
+ { kind: "heading", level: 2, text: t2.grid },
57184
+ {
57185
+ kind: "para",
57186
+ runs: [
57187
+ { text: `${t2.pageRate} : `, strong: true },
57188
+ { text: formatRate(pageRatePct(rows), cov.decided, cov.total), strong: true }
57189
+ ]
57190
+ },
57191
+ { kind: "para", runs: [{ text: t2.tally(tally.c, tally.nc, tally.na, tally.manual) }] },
57192
+ { kind: "note", tone: "info", runs: ticks(t2.legend) },
57193
+ table
57194
+ ];
57195
+ if (rows.some((r) => r.decidedBy === "agent" && r.status === "C")) out2.push({ kind: "note", tone: "warn", runs: ticks(agentMarkNote(lang)) });
57196
+ return out2;
57197
+ }
57198
+ function indexDoc(result, opts = {}) {
57199
+ const standard = opts.standard ?? CORE2;
57200
+ const lang = opts.lang ?? "en";
57201
+ const t2 = T4[lang];
57202
+ const h = headline(result, standard, lang);
57203
+ const blocks = [];
57204
+ if (opts.links?.length) {
57205
+ blocks.push({ kind: "heading", level: 2, text: t2.documents, id: "documents" });
57206
+ blocks.push({ kind: "list", items: opts.links.map((l) => [{ text: l.text, href: l.href }]) });
57207
+ }
57208
+ blocks.push(...synthesisBlocks(result, standard, lang));
57209
+ if (h.agentRuled) blocks.push({ kind: "note", tone: "warn", runs: ticks(agentMarkNote(lang)) });
57210
+ blocks.push(...scoreboardBlocks(result, standard, lang));
57211
+ return { lang, title: `${t2.indexTitle} \u2014 ${stdName(standard)}`, subtitle: h.runs, ...opts.nav ? { nav: opts.nav } : {}, blocks };
57212
+ }
57213
+ function compositeDoc(result, opts = {}) {
57214
+ const standard = opts.standard ?? CORE2;
57215
+ const lang = opts.lang ?? "en";
57216
+ const t2 = T4[lang];
57217
+ const h = headline(result, standard, lang);
57218
+ const blocks = [...synthesisBlocks(result, standard, lang)];
57219
+ if (h.agentRuled) blocks.push({ kind: "note", tone: "warn", runs: ticks(agentMarkNote(lang)) });
57220
+ blocks.push(...findingsBlocks(result, standard, lang, 2, opts.crops, opts.refusals));
57221
+ blocks.push(...scoreboardBlocks(result, standard, lang));
57222
+ const scope = pagesOf(result);
57223
+ if (scope.length) blocks.push(...crossGridBlocks(result, derivePages(result, scope), standard, lang));
57224
+ return { lang, title: `${t2.reportTitle} ${standardLabel(standard)} \u2014 ${result.date}`, subtitle: h.runs, ...opts.nav ? { nav: opts.nav } : {}, blocks };
57225
+ }
57226
+ function pagesIndexDoc(result, opts = { sheetHref: (id) => `./page-${id}.html` }) {
57227
+ const standard = opts.standard ?? CORE2;
57228
+ const lang = opts.lang ?? "en";
57229
+ const t2 = T4[lang];
57230
+ const h = headline(result, standard, lang);
57231
+ const scope = pagesOf(result);
57232
+ const blocks = [...scoreboardBlocks(result, standard, lang, opts.sheetHref)];
57233
+ if (scope.length) blocks.push(...crossGridBlocks(result, derivePages(result, scope), standard, lang));
57234
+ return { lang, title: `${t2.pagesTitle} \u2014 ${stdName(standard)}`, subtitle: h.runs, ...opts.nav ? { nav: opts.nav } : {}, blocks };
57235
+ }
57236
+ function pageDoc(result, page, opts = {}) {
57237
+ const standard = opts.standard ?? CORE2;
57238
+ const lang = opts.lang ?? "en";
57239
+ const t2 = T4[lang];
57240
+ const blocks = [
57241
+ {
57242
+ kind: "list",
57243
+ items: [
57244
+ [
57245
+ { text: `${t2.url} : `, strong: true },
57246
+ { text: page.url, mono: true }
57247
+ ],
57248
+ [{ text: `${t2.basis} : `, strong: true }, { text: basisLabel(page.basis, lang) }]
57249
+ ]
57250
+ }
57251
+ ];
57252
+ const warn = pageBasisWarning(page.basis, lang);
57253
+ if (warn) blocks.push({ kind: "note", tone: "warn", runs: ticks(warn) });
57254
+ blocks.push(
57255
+ opts.screenshot ? { kind: "figure", src: opts.screenshot, alt: t2.screenshotAlt(page.name) } : { kind: "para", runs: [{ text: t2.noScreenshot, em: true }] }
57256
+ );
57257
+ blocks.push(...refusalBlocks(opts.refusals?.(page.id)));
57258
+ blocks.push(...pageGridBlocks(result, page, standard, lang));
57259
+ const view = { ...result, criteria: page.criteria, findings: page.findings };
57260
+ const { nc, advisory } = partitionUnits(prdUnits(view, standard, lang));
57261
+ blocks.push({ kind: "heading", level: 2, text: t2.ncTitle });
57262
+ if (!nc.length) blocks.push({ kind: "para", runs: [{ text: t2.noNc }] });
57263
+ for (const u of nc) blocks.push(...criterionBlocks(u, standard, lang, 3, opts.crops));
57264
+ if (advisory.length) {
57265
+ blocks.push({ kind: "heading", level: 2, text: t2.recTitle });
57266
+ blocks.push({ kind: "note", tone: "info", runs: ticks(t2.recNote) });
57267
+ for (const u of advisory) blocks.push(...criterionBlocks(u, standard, lang, 3, opts.crops));
57268
+ }
57269
+ return {
57270
+ lang,
57271
+ title: `${page.name} \u2014 ${stdName(standard)}`,
57272
+ subtitle: [{ text: result.date, mono: true }, { text: ` \xB7 ${findingsForStandard(view, standard).length} ${t2.what.toLowerCase()}` }],
57273
+ ...opts.nav ? { nav: opts.nav } : {},
57274
+ blocks
57275
+ };
57276
+ }
57277
+
57278
+ // src/html-emit.ts
57279
+ var DEFAULT_INLINE_BUDGET = 12 * 1024 * 1024;
57280
+ var T5 = {
57281
+ fr: {
57282
+ overComposite: (mb2, budget) => `Les images d\xE9passent le budget d'inclusion (${mb2} Mo pour ${budget} Mo) : les captures de page ne sont pas incluses dans le fichier composite. Elles restent dans le rapport page par page.`,
57283
+ overCrops: (mb2, budget) => `Les images d\xE9passent encore le budget (${mb2} Mo pour ${budget} Mo) : une seule vignette par crit\xE8re est incluse. Le rapport page par page les porte toutes.`,
57284
+ overNone: (budget) => `Aucune image ne tient dans le budget de ${budget} Mo : le fichier composite est sans illustration. Les non-conformit\xE9s, elles, y sont toutes. Augmentez \`--inline-budget\`, ou ouvrez le rapport page par page, qui r\xE9f\xE9rence les images au lieu de les inclure.`,
57285
+ docTitle: "Rapport complet, en un seul fichier (imprimable en PDF)",
57286
+ pagesTitle: "Rapport page par page",
57287
+ indexTitle: "Tableau de bord"
57288
+ },
57289
+ en: {
57290
+ overComposite: (mb2, budget) => `Images exceed the inline budget (${mb2} MB for ${budget} MB): page screenshots are not embedded in the composite file. They remain in the page-by-page report.`,
57291
+ overCrops: (mb2, budget) => `Images still exceed the budget (${mb2} MB for ${budget} MB): one crop per criterion is embedded. The page-by-page report carries them all.`,
57292
+ overNone: (budget) => `No image fits within the ${budget} MB budget: the composite file carries no illustration. Every non-conformity is still in it. Raise \`--inline-budget\`, or open the page-by-page report, which references images instead of embedding them.`,
57293
+ docTitle: "Full report, in a single file (printable to PDF)",
57294
+ pagesTitle: "Page-by-page report",
57295
+ indexTitle: "Dashboard"
57296
+ }
57297
+ };
57298
+ var mb = (n) => (n / (1024 * 1024)).toFixed(1);
57299
+ function inlineSize(path) {
57300
+ try {
57301
+ return Math.ceil(statSync11(path).size / 3) * 4;
57302
+ } catch {
57303
+ return 0;
57304
+ }
57305
+ }
57306
+ function dataUri(path) {
57307
+ try {
57308
+ return `data:image/png;base64,${readFileSync24(path).toString("base64")}`;
57309
+ } catch {
57310
+ return void 0;
57311
+ }
57312
+ }
57313
+ function cropLookup(m, lang, hrefOf) {
57314
+ if (!m) return () => void 0;
57315
+ return (f) => {
57316
+ const c2 = m.crops.get(findingId(f));
57317
+ if (!c2) return void 0;
57318
+ const href = hrefOf(c2.path, c2.href);
57319
+ return href ? { href, alt: c2.alt[lang] } : void 0;
57320
+ };
57321
+ }
57322
+ function refusalLookup(m, lang) {
57323
+ if (!m) return void 0;
57324
+ return (pageId) => evidenceRefusals(m, pageId, lang);
57325
+ }
57326
+ function pickRung(cropBytes, shotBytes, budget) {
57327
+ const sum = (xs) => xs.reduce((a, b) => a + b, 0);
57328
+ if (sum(cropBytes) + sum(shotBytes) <= budget) return { steps: [], cropsPerCriterion: Number.POSITIVE_INFINITY, shots: true };
57329
+ if (sum(cropBytes) <= budget) return { steps: ["screenshots"], cropsPerCriterion: Number.POSITIVE_INFINITY, shots: false };
57330
+ const smallest = [...cropBytes].sort((a, b) => a - b);
57331
+ let kept = 0;
57332
+ let total = 0;
57333
+ for (const b of smallest) {
57334
+ if (total + b > budget) break;
57335
+ total += b;
57336
+ kept++;
57337
+ }
57338
+ if (!kept) return { steps: ["screenshots", "crops", "none"], cropsPerCriterion: 0, shots: false };
57339
+ return { steps: ["screenshots", "crops"], cropsPerCriterion: 1, shots: false };
57340
+ }
57341
+ function writeHtml(result, opts) {
57342
+ const standard = opts.standard ?? CORE2;
57343
+ const lang = opts.lang ?? "en";
57344
+ const t2 = T5[lang];
57345
+ const budget = opts.inlineBudget ?? DEFAULT_INLINE_BUDGET;
57346
+ const stdKey = isCore(standard) ? "wcag" : loadPack(standard).key;
57347
+ mkdirSync14(opts.outDir, { recursive: true });
57348
+ const crops = [...opts.evidence?.crops.values() ?? []];
57349
+ const shots = [...opts.screenshots?.values() ?? []];
57350
+ const rung = pickRung(
57351
+ crops.map((c2) => inlineSize(c2.path)),
57352
+ shots.map(inlineSize),
57353
+ budget
57354
+ );
57355
+ const notices = [];
57356
+ if (rung.steps.includes("none")) notices.push(t2.overNone(mb(budget)));
57357
+ else if (rung.steps.includes("crops")) notices.push(t2.overCrops(mb(crops.reduce((a, c2) => a + inlineSize(c2.path), 0)), mb(budget)));
57358
+ else if (rung.steps.includes("screenshots"))
57359
+ notices.push(t2.overComposite(mb(crops.reduce((a, c2) => a + inlineSize(c2.path), 0) + shots.reduce((a, s) => a + inlineSize(s), 0)), mb(budget)));
57360
+ let inlinedBytes = 0;
57361
+ const perCriterion = /* @__PURE__ */ new Map();
57362
+ const inlineCrops = cropLookup(opts.evidence, lang, (path) => {
57363
+ if (rung.cropsPerCriterion === 0) return void 0;
57364
+ const uri = dataUri(path);
57365
+ if (!uri) return void 0;
57366
+ inlinedBytes += uri.length;
57367
+ return uri;
57368
+ });
57369
+ const budgetedCrops = (f) => {
57370
+ const c2 = opts.evidence?.crops.get(findingId(f));
57371
+ if (!c2) return void 0;
57372
+ const n = perCriterion.get(c2.criteriaId) ?? 0;
57373
+ if (n >= rung.cropsPerCriterion) return void 0;
57374
+ const drawn = inlineCrops(f);
57375
+ if (drawn) perCriterion.set(c2.criteriaId, n + 1);
57376
+ return drawn;
57377
+ };
57378
+ const flat = opts.layout === "pages";
57379
+ const sheetDir = flat ? opts.outDir : join41(opts.outDir, "pages");
57380
+ const up = flat ? "./" : "../";
57381
+ const compositeName = `ultra11y-${stdKey}-${result.date}.html`;
57382
+ const nav = flat ? [{ href: "./index.html", text: t2.pagesTitle }] : [
57383
+ { href: "./index.html", text: t2.indexTitle },
57384
+ { href: `./${compositeName}`, text: t2.docTitle },
57385
+ ...opts.pages ? [{ href: "./pages/index.html", text: t2.pagesTitle }] : []
57386
+ ];
57387
+ const refusals = refusalLookup(opts.evidence, lang);
57388
+ let compositePath;
57389
+ if (!flat) {
57390
+ const composite = compositeDoc(result, {
57391
+ standard,
57392
+ lang,
57393
+ crops: budgetedCrops,
57394
+ ...refusals ? { refusals } : {},
57395
+ nav: nav.map((n) => ({ ...n, current: n.href === `./${compositeName}` }))
57396
+ });
57397
+ if (notices.length) composite.blocks.unshift({ kind: "note", tone: "warn", runs: noticeRuns(notices) });
57398
+ compositePath = join41(opts.outDir, compositeName);
57399
+ writeFileSync16(compositePath, renderHtmlDocument(composite));
57400
+ }
57401
+ const fileCrops = cropLookup(opts.evidence, lang, (_p, href) => href.replace(/^\.\//, up));
57402
+ const indexNav = nav.map((n) => ({ ...n, current: n.href === "./index.html" }));
57403
+ const index = flat ? pagesIndexDoc(result, { standard, lang, nav: indexNav, sheetHref: (id) => `./page-${id}.html` }) : indexDoc(result, { standard, lang, nav: indexNav, links: nav.filter((n) => n.href !== "./index.html") });
57404
+ const indexPath = join41(opts.outDir, "index.html");
57405
+ writeFileSync16(indexPath, renderHtmlDocument(index));
57406
+ const sheets = [];
57407
+ if (opts.pages) {
57408
+ const derived = derivePages(result, pagesOf(result));
57409
+ mkdirSync14(sheetDir, { recursive: true });
57410
+ const sheetNav = flat ? [{ href: "./index.html", text: t2.pagesTitle }] : [
57411
+ { href: `${up}index.html`, text: t2.indexTitle },
57412
+ { href: `${up}${compositeName}`, text: t2.docTitle },
57413
+ { href: "./index.html", text: t2.pagesTitle }
57414
+ ];
57415
+ if (!flat) {
57416
+ writeFileSync16(
57417
+ join41(sheetDir, "index.html"),
57418
+ renderHtmlDocument(
57419
+ pagesIndexDoc(result, {
57420
+ standard,
57421
+ lang,
57422
+ nav: sheetNav.map((n) => ({ ...n, current: n.href === "./index.html" })),
57423
+ sheetHref: (id) => `./page-${id}.html`
57424
+ })
57425
+ )
57426
+ );
57427
+ sheets.push(join41(sheetDir, "index.html"));
57428
+ }
57429
+ for (const p of derived) {
57430
+ const doc = pageDoc(result, p, {
57431
+ standard,
57432
+ lang,
57433
+ crops: fileCrops,
57434
+ ...refusals ? { refusals } : {},
57435
+ nav: sheetNav,
57436
+ // Where `cmdPages`' screenshot copier already put the capture: `assets/<id>.png`,
57437
+ // beside the entry point, seen from wherever this sheet sits.
57438
+ ...opts.screenshots?.has(p.id) ? { screenshot: `${up}assets/${p.id}.png` } : {}
57439
+ });
57440
+ const path = join41(sheetDir, `page-${p.id}.html`);
57441
+ writeFileSync16(path, renderHtmlDocument(doc));
57442
+ sheets.push(path);
57443
+ }
57444
+ }
57445
+ return {
57446
+ index: indexPath,
57447
+ ...compositePath ? { composite: compositePath } : {},
57448
+ sheets,
57449
+ inlinedBytes,
57450
+ degraded: rung.steps,
57451
+ imagesDropped: rung.steps.includes("none"),
57452
+ notices
57453
+ };
57454
+ }
57455
+ function noticeRuns(notices) {
57456
+ return notices.flatMap((n, i2) => i2 ? [{ text: " " }, { text: n }] : [{ text: n }]);
57457
+ }
57458
+
56189
57459
  // src/dev.ts
56190
57460
  import { createServer } from "http";
56191
- import { mkdirSync as mkdirSync13, readFileSync as readFileSync23, writeFileSync as writeFileSync15 } from "fs";
56192
- import { dirname as dirname12, join as join40 } from "path";
57461
+ import { mkdirSync as mkdirSync15, readFileSync as readFileSync25, writeFileSync as writeFileSync17 } from "fs";
57462
+ import { dirname as dirname12, join as join42 } from "path";
56193
57463
  var DEV_DEFAULT_PORT = 4111;
56194
57464
  function criterionLabel3(f, standard) {
56195
57465
  if (isCore(standard)) return `WCAG ${f.criteriaId}`;
@@ -56367,71 +57637,19 @@ export function Ultra11yOverlay({ endpoint = "http://127.0.0.1:${port}" }) {
56367
57637
  export default Ultra11yOverlay;
56368
57638
  `;
56369
57639
  }
56370
- var MARK3 = { C: "C", NC: "NC", NA: "\u2014", manual: "?" };
56371
- var CLASS = { C: "c", NC: "nc", NA: "na", manual: "m" };
56372
- function esc2(s) {
56373
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
56374
- }
56375
57640
  function dashboardHtml(result, pages, standard, lang) {
56376
57641
  const fr = lang === "fr";
56377
- const stdLabel = isCore(standard) ? "WCAG 2.2 AA" : loadPack(standard).name;
56378
- const head = `<!doctype html><html lang="${fr ? "fr" : "en"}"><head><meta charset="utf-8">
56379
- <meta name="viewport" content="width=device-width, initial-scale=1">
56380
- <title>ultra11y \u2014 ${esc2(stdLabel)}</title>
56381
- <style>
56382
- :root { color-scheme: light dark; --bg:#fff; --fg:#111; --line:#e3e3e6; --mut:#666; }
56383
- @media (prefers-color-scheme: dark) { :root { --bg:#141416; --fg:#f2f2f7; --line:#2c2c2e; --mut:#a1a1a6; } }
56384
- body { margin:0; padding:24px; background:var(--bg); color:var(--fg); font:14px/1.5 ui-sans-serif,system-ui,sans-serif; }
56385
- h1 { font-size:18px; margin:0 0 4px; } p.sub { color:var(--mut); margin:0 0 20px; }
56386
- table { border-collapse:collapse; width:100%; } th,td { padding:5px 8px; border-bottom:1px solid var(--line); text-align:left; }
56387
- th { position:sticky; top:0; background:var(--bg); }
56388
- td.c,td.nc,td.na,td.m { text-align:center; font-weight:600; width:80px; }
56389
- td.c{color:#0a7d33} td.nc{color:#b3261e} td.na{color:var(--mut)} td.m{color:#b06000}
56390
- tr.theme td { font-weight:700; background:color-mix(in srgb, var(--fg) 6%, transparent); }
56391
- .rate { font-size:22px; font-weight:700; }
56392
- .empty { color:var(--mut); max-width:60ch; }
56393
- code { font:12px ui-monospace,monospace; color:var(--mut); }
56394
- </style></head><body>`;
57642
+ const title2 = `ultra11y \u2014 ${isCore(standard) ? "WCAG 2.2 AA" : loadPack(standard).name}`;
56395
57643
  if (!result || !pages.length) {
56396
- return `${head}<h1>ultra11y</h1>
56397
- <p class="empty">${fr ? "Aucune page captur\xE9e pour l'instant. Ouvrez votre application avec l'overlay actif : chaque page visit\xE9e appara\xEEtra ici." : "No page captured yet. Open your app with the overlay active: every page you visit shows up here."}</p></body></html>`;
56398
- }
56399
- const rows = [];
56400
- const status = /* @__PURE__ */ new Map();
56401
- if (isCore(standard)) {
56402
- for (const c2 of result.criteria) rows.push({ id: c2.id, label: c2.id, group: c2.guideline });
56403
- for (const p of pages) for (const c2 of p.criteria) (status.get(c2.id) ?? status.set(c2.id, /* @__PURE__ */ new Map()).get(c2.id)).set(p.id, c2.status);
56404
- } else {
56405
- const pack = loadPack(standard);
56406
- for (const pc of pack.criteria) rows.push({ id: pc.id, label: pc.id, group: `${pc.theme}. ${themeName(pack, pc.theme, lang) ?? ""}` });
56407
- for (const p of pages) {
56408
- const view = { ...result, criteria: p.criteria, findings: p.findings };
56409
- for (const pc of derivePackResults(view, standard)) (status.get(pc.id) ?? status.set(pc.id, /* @__PURE__ */ new Map()).get(pc.id)).set(p.id, pc.status);
56410
- }
56411
- }
56412
- const out2 = [head];
56413
- out2.push(`<h1>ultra11y \u2014 ${esc2(stdLabel)}</h1>`);
56414
- out2.push(`<p class="sub">${pages.length} ${fr ? "page(s) captur\xE9e(s)" : "page(s) captured"} \xB7 <code>${esc2(result.date)}</code></p>`);
56415
- out2.push("<table><thead><tr><th></th>");
56416
- for (const p of pages) out2.push(`<th>${esc2(p.name)}${p.auth ? " \u{1F512}" : ""}<br><code>${esc2(p.url)}</code></th>`);
56417
- out2.push("</tr></thead><tbody>");
56418
- out2.push(
56419
- `<tr><td>${fr ? "Taux" : "Rate"}</td>${pages.map((p) => `<td class="rate">${esc2(formatRate(p.conformancePct, p.decided, p.total))}</td>`).join("")}</tr>`
56420
- );
56421
- let group = "";
56422
- for (const row of rows) {
56423
- if (row.group !== group) {
56424
- group = row.group;
56425
- out2.push(`<tr class="theme"><td colspan="${pages.length + 1}">${esc2(group)}</td></tr>`);
56426
- }
56427
- const cells = pages.map((p) => {
56428
- const s = status.get(row.id)?.get(p.id) ?? "manual";
56429
- return `<td class="${CLASS[s]}">${MARK3[s]}</td>`;
56430
- });
56431
- out2.push(`<tr><td>${esc2(row.label)}</td>${cells.join("")}</tr>`);
57644
+ const empty = fr ? "Aucune page captur\xE9e pour l'instant. Ouvrez votre application avec l'overlay actif : chaque page visit\xE9e appara\xEEtra ici." : "No page captured yet. Open your app with the overlay active: every page you visit shows up here.";
57645
+ return renderHtmlDocument({ lang, title: title2, blocks: [{ kind: "para", runs: [{ text: empty }] }] });
56432
57646
  }
56433
- out2.push("</tbody></table></body></html>");
56434
- return out2.join("\n");
57647
+ return renderHtmlDocument({
57648
+ lang,
57649
+ title: title2,
57650
+ subtitle: [{ text: `${pages.length} ${fr ? "page(s) captur\xE9e(s)" : "page(s) captured"} \xB7 ` }, { text: result.date, mono: true }],
57651
+ blocks: [...scoreboardBlocks(result, standard, lang), ...crossGridBlocks(result, pages, standard, lang)]
57652
+ });
56435
57653
  }
56436
57654
  function cors(res) {
56437
57655
  res.setHeader("access-control-allow-origin", "*");
@@ -56463,13 +57681,13 @@ function auditCollected(root, payload) {
56463
57681
  ...payload.css ? { css: payload.css } : {},
56464
57682
  ...shot ? { screenshotBase64: shot } : {}
56465
57683
  });
56466
- return { ok: true, result: runAudit({ inputs: [join40(dir, "dom.html")] }) };
57684
+ return { ok: true, result: runAudit({ inputs: [join42(dir, "dom.html")] }) };
56467
57685
  }
56468
57686
  function projectPages(root) {
56469
57687
  const snaps = readSnapshots(root);
56470
57688
  if (!snaps.length) return { result: null, pages: [] };
56471
57689
  const scope = pageScopesFrom(snaps);
56472
- const result = runAudit({ inputs: [join40(root, ".ultra11y/pages")] });
57690
+ const result = runAudit({ inputs: [join42(root, ".ultra11y/pages")] });
56473
57691
  result.scope.pages = scope;
56474
57692
  attributePages(result, scope);
56475
57693
  foldRecordedAdjudication(root, result);
@@ -56483,7 +57701,7 @@ function judgePages(result) {
56483
57701
  function foldRecordedAdjudication(root, fresh) {
56484
57702
  let prior;
56485
57703
  try {
56486
- prior = JSON.parse(readFileSync23(join40(root, "audits", "audit-latest.json"), "utf8"));
57704
+ prior = JSON.parse(readFileSync25(join42(root, "audits", "audit-latest.json"), "utf8"));
56487
57705
  } catch {
56488
57706
  return;
56489
57707
  }
@@ -56598,9 +57816,9 @@ function startDevServer(opts) {
56598
57816
  });
56599
57817
  let auditPath;
56600
57818
  if (applied.ok) {
56601
- auditPath = join40(opts.root, "audits", "audit-latest.json");
56602
- mkdirSync13(dirname12(auditPath), { recursive: true });
56603
- writeFileSync15(auditPath, `${JSON.stringify(applied.audit, null, 2)}
57819
+ auditPath = join42(opts.root, "audits", "audit-latest.json");
57820
+ mkdirSync15(dirname12(auditPath), { recursive: true });
57821
+ writeFileSync17(auditPath, `${JSON.stringify(applied.audit, null, 2)}
56604
57822
  `);
56605
57823
  }
56606
57824
  res.writeHead(200, { "content-type": "application/json" }).end(
@@ -56914,11 +58132,11 @@ function e2eSetupPlan(runners, paths, lang = "en") {
56914
58132
  }
56915
58133
 
56916
58134
  // src/orchestrate.ts
56917
- import { existsSync as existsSync28, mkdirSync as mkdirSync14, readFileSync as readFileSync24, rmSync as rmSync7, writeFileSync as writeFileSync16 } from "fs";
56918
- import { join as join42, resolve as resolve10 } from "path";
58135
+ import { existsSync as existsSync29, mkdirSync as mkdirSync16, readFileSync as readFileSync26, rmSync as rmSync7, writeFileSync as writeFileSync18 } from "fs";
58136
+ import { join as join44, resolve as resolve10 } from "path";
56919
58137
 
56920
58138
  // src/orchestrate-templates.ts
56921
- import { join as join41 } from "path";
58139
+ import { join as join43 } from "path";
56922
58140
  var ONE_WRITER_FOOTER = `
56923
58141
  ## Return, don't write
56924
58142
 
@@ -56990,7 +58208,7 @@ var PHASE_SPECS = {
56990
58208
  title: "Adjudicate",
56991
58209
  schema: ADJUDICATE_SCHEMA,
56992
58210
  description: (n) => `Adjudicate the ${n} residual judgment criterion(ia) of an ultra11y audit (fan-out, fail-closed fold)`,
56993
- applyHint: (engine, worklist, run2) => `node ${engine} verify --apply ${worklist} --in ${join41(run2, "audit-latest.json")} --out ${run2}`
58211
+ applyHint: (engine, worklist, run2) => `node ${engine} verify --apply ${worklist} --in ${join43(run2, "audit-latest.json")} --out ${run2}`
56994
58212
  },
56995
58213
  "verify-report": {
56996
58214
  role: "refuter",
@@ -57012,7 +58230,7 @@ function toBatches(ids, batchSize) {
57012
58230
  }
57013
58231
  function phaseWorkflowScript(ph, runAbs, engineAbs, batchSize) {
57014
58232
  const spec = phaseSpec(ph.name);
57015
- const scriptPath = join41(runAbs, "orchestration", `${ph.name}.workflow.mjs`);
58233
+ const scriptPath = join43(runAbs, "orchestration", `${ph.name}.workflow.mjs`);
57016
58234
  const meta2 = { name: `ultra11y-${ph.name}`, description: spec.description(ph.items), phases: [{ title: spec.title }] };
57017
58235
  return [
57018
58236
  `export const meta = ${JSON.stringify(meta2)}`,
@@ -57056,7 +58274,7 @@ function agentContracts(runAbs, engineAbs) {
57056
58274
 
57057
58275
  You adjudicate the residual judgment criteria of an ultra11y audit \u2014 the ones the deterministic engine could not decide (alt-text relevance, link purpose in context, reading order\u2026). The ACTIVE STANDARD is recorded in the worklist's \`standard\` field: under a country standard (e.g. \`rgaa\`) the items are that standard's OWN criteria, each carrying its numbered tests \u2014 not WCAG success criteria.
57058
58276
 
57059
- Worklist: \`${join41(runAbs, "ADJUDICATE.todo.json")}\` (an object with \`kind: "adjudication"\` and \`items[]\`). Handle ONLY the criteria whose \`criteriaId\` is named in your prompt (\`ITEMS=<id,\u2026>\`).
58277
+ Worklist: \`${join43(runAbs, "ADJUDICATE.todo.json")}\` (an object with \`kind: "adjudication"\` and \`items[]\`). Handle ONLY the criteria whose \`criteriaId\` is named in your prompt (\`ITEMS=<id,\u2026>\`).
57060
58278
 
57061
58279
  For EACH of your criteria:
57062
58280
 
@@ -57074,7 +58292,7 @@ ${footer}`,
57074
58292
 
57075
58293
  You are an adversarial skeptic verifying the non-conformities of an ultra11y report. Your job is to try to REFUTE each claim: assume it is wrong until the source proves it.
57076
58294
 
57077
- Worklist: \`${join41(runAbs, "VERIFY.todo.json")}\` (a JSON array; each entry has \`n\`, \`criteriaId\`, \`file\`, \`line\`, \`selector\`, \`claim\`). Handle ONLY the entries whose \`n\` is named in your prompt (\`ITEMS=<n,\u2026>\`).
58295
+ Worklist: \`${join43(runAbs, "VERIFY.todo.json")}\` (a JSON array; each entry has \`n\`, \`criteriaId\`, \`file\`, \`line\`, \`selector\`, \`claim\`). Handle ONLY the entries whose \`n\` is named in your prompt (\`ITEMS=<n,\u2026>\`).
57078
58296
 
57079
58297
  For EACH of your entries:
57080
58298
 
@@ -57110,14 +58328,14 @@ ${status}
57110
58328
 
57111
58329
  ## The loop (play every role yourself, one item at a time)
57112
58330
 
57113
- 1. **Audit** (if not done): \`${engine} audit "<globs>" --graph --out ${runAbs}\` \u2192 \`${join41(runAbs, "audit-latest.json")}\`.
57114
- 2. **Adjudicate the residual criteria** \u2014 \`${engine} verify --manual --in ${join41(runAbs, "audit-latest.json")} --out ${runAbs}\` writes \`${join41(runAbs, "ADJUDICATE.todo.json")}\`. For EVERY item, apply \`${join41(runAbs, "orchestration", "agents", "adjudicator.md")}\` yourself (read the evidence, rule C/NC/NA/manual, fill the required justification/findings/reason IN the todo file). Then fold, fail-closed: \`${engine} verify --apply ${join41(runAbs, "ADJUDICATE.todo.json")} --in ${join41(runAbs, "audit-latest.json")} --out ${runAbs}\`.
57115
- 3. **Report**: \`${engine} report --in ${join41(runAbs, "audit-latest.json")} --out ${runAbs}\`.
57116
- 4. **Verify the report's claims** \u2014 \`${engine} verify --report <the report .md> --out ${runAbs}\` writes \`${join41(runAbs, "VERIFY.todo.json")}\`. For EVERY entry, apply \`${join41(runAbs, "orchestration", "agents", "refuter.md")}\` yourself (open file:line, verdict supported/partial/refuted/unsupported + note IN the todo file). Then: \`${engine} verify --apply ${join41(runAbs, "VERIFY.todo.json")} --report <the report .md>\`.
58331
+ 1. **Audit** (if not done): \`${engine} audit "<globs>" --graph --out ${runAbs}\` \u2192 \`${join43(runAbs, "audit-latest.json")}\`.
58332
+ 2. **Adjudicate the residual criteria** \u2014 \`${engine} verify --manual --in ${join43(runAbs, "audit-latest.json")} --out ${runAbs}\` writes \`${join43(runAbs, "ADJUDICATE.todo.json")}\`. For EVERY item, apply \`${join43(runAbs, "orchestration", "agents", "adjudicator.md")}\` yourself (read the evidence, rule C/NC/NA/manual, fill the required justification/findings/reason IN the todo file). Then fold, fail-closed: \`${engine} verify --apply ${join43(runAbs, "ADJUDICATE.todo.json")} --in ${join43(runAbs, "audit-latest.json")} --out ${runAbs}\`.
58333
+ 3. **Report**: \`${engine} report --in ${join43(runAbs, "audit-latest.json")} --out ${runAbs}\`.
58334
+ 4. **Verify the report's claims** \u2014 \`${engine} verify --report <the report .md> --out ${runAbs}\` writes \`${join43(runAbs, "VERIFY.todo.json")}\`. For EVERY entry, apply \`${join43(runAbs, "orchestration", "agents", "refuter.md")}\` yourself (open file:line, verdict supported/partial/refuted/unsupported + note IN the todo file). Then: \`${engine} verify --apply ${join43(runAbs, "VERIFY.todo.json")} --report <the report .md>\`.
57117
58335
  5. **Gate**: \`${engine} check --report <the report .md> --semantic\` must exit 0 before presenting anything.
57118
58336
  6. **Fix & re-audit**: \`${engine} fix <globs> --write --iterate\`, hand-apply the judgment fixes, then loop from step 1 until the gate stays green.
57119
58337
 
57120
- With subagents available, prefer the emitted workflows instead: \`orchestrate --run ${runAbs} --phase <p>\` then \`Workflow({ scriptPath: "${join41(runAbs, "orchestration", "<p>.workflow.mjs")}" })\` \u2014 you stay the sole writer either way.
58338
+ With subagents available, prefer the emitted workflows instead: \`orchestrate --run ${runAbs} --phase <p>\` then \`Workflow({ scriptPath: "${join43(runAbs, "orchestration", "<p>.workflow.mjs")}" })\` \u2014 you stay the sole writer either way.
57121
58339
  `;
57122
58340
  }
57123
58341
 
@@ -57127,12 +58345,12 @@ var SMALL_WORKLIST = 3;
57127
58345
  var BATCH_SIZE2 = 8;
57128
58346
  function listPhases(runDir, engineAbs) {
57129
58347
  const run2 = resolve10(runDir);
57130
- const adjPath = join42(run2, "ADJUDICATE.todo.json");
58348
+ const adjPath = join44(run2, "ADJUDICATE.todo.json");
57131
58349
  let adjIds = [];
57132
58350
  let adjReady = false;
57133
- if (existsSync28(adjPath)) {
58351
+ if (existsSync29(adjPath)) {
57134
58352
  try {
57135
- const f = JSON.parse(readFileSync24(adjPath, "utf8"));
58353
+ const f = JSON.parse(readFileSync26(adjPath, "utf8"));
57136
58354
  if (f && f.kind === "adjudication" && Array.isArray(f.items)) {
57137
58355
  adjReady = true;
57138
58356
  adjIds = f.items.map((i2) => i2.criteriaId);
@@ -57140,12 +58358,12 @@ function listPhases(runDir, engineAbs) {
57140
58358
  } catch {
57141
58359
  }
57142
58360
  }
57143
- const verPath = join42(run2, "VERIFY.todo.json");
58361
+ const verPath = join44(run2, "VERIFY.todo.json");
57144
58362
  let verIds = [];
57145
58363
  let verReady = false;
57146
- if (existsSync28(verPath)) {
58364
+ if (existsSync29(verPath)) {
57147
58365
  try {
57148
- const items = JSON.parse(readFileSync24(verPath, "utf8"));
58366
+ const items = JSON.parse(readFileSync26(verPath, "utf8"));
57149
58367
  if (Array.isArray(items)) {
57150
58368
  verReady = true;
57151
58369
  verIds = items.map((i2) => String(i2.n));
@@ -57160,7 +58378,7 @@ function listPhases(runDir, engineAbs) {
57160
58378
  worklist: adjPath,
57161
58379
  items: adjIds.length,
57162
58380
  ids: adjIds,
57163
- prerequisite: `node ${engineAbs} verify --manual --in ${join42(run2, "audit-latest.json")} --out ${run2}`
58381
+ prerequisite: `node ${engineAbs} verify --manual --in ${join44(run2, "audit-latest.json")} --out ${run2}`
57164
58382
  },
57165
58383
  {
57166
58384
  name: "verify-report",
@@ -57174,7 +58392,7 @@ function listPhases(runDir, engineAbs) {
57174
58392
  }
57175
58393
  function orchestrateRun(runDir, engineAbs, opts = {}) {
57176
58394
  const run2 = resolve10(runDir);
57177
- if (!existsSync28(run2)) {
58395
+ if (!existsSync29(run2)) {
57178
58396
  return { exitCode: 2, written: [], notices: [], errors: [`run dir not found: ${run2}`], phases: [] };
57179
58397
  }
57180
58398
  const phases = listPhases(run2, engineAbs);
@@ -57201,22 +58419,22 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
57201
58419
  }
57202
58420
  selected = [ph];
57203
58421
  }
57204
- const orchDir = join42(run2, "orchestration");
57205
- const agentsDir = join42(orchDir, "agents");
57206
- mkdirSync14(join42(orchDir, "out"), { recursive: true });
57207
- mkdirSync14(agentsDir, { recursive: true });
58422
+ const orchDir = join44(run2, "orchestration");
58423
+ const agentsDir = join44(orchDir, "agents");
58424
+ mkdirSync16(join44(orchDir, "out"), { recursive: true });
58425
+ mkdirSync16(agentsDir, { recursive: true });
57208
58426
  const written = [];
57209
58427
  const notices = [];
57210
58428
  for (const [name2, content] of Object.entries(agentContracts(run2, engineAbs))) {
57211
- const p = join42(agentsDir, `${name2}.md`);
57212
- writeFileSync16(p, content);
58429
+ const p = join44(agentsDir, `${name2}.md`);
58430
+ writeFileSync18(p, content);
57213
58431
  written.push(p);
57214
58432
  }
57215
58433
  const emitted = new Set(opts.eco ? [] : selected.filter((p) => p.items > 0).map((p) => p.name));
57216
58434
  for (const ph of phases) {
57217
58435
  if (emitted.has(ph.name)) continue;
57218
- const stale = join42(orchDir, `${ph.name}.workflow.mjs`);
57219
- if (existsSync28(stale)) {
58436
+ const stale = join44(orchDir, `${ph.name}.workflow.mjs`);
58437
+ if (existsSync29(stale)) {
57220
58438
  rmSync7(stale, { force: true });
57221
58439
  notices.push(`phase "${ph.name}": stale workflow removed \u2014 its worklist ${ph.ready ? "is now empty" : "no longer exists"}.`);
57222
58440
  }
@@ -57230,13 +58448,13 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
57230
58448
  if (ph.items <= SMALL_WORKLIST) {
57231
58449
  notices.push(`phase "${ph.name}": only ${ph.items} item(s) \u2014 the sequential --eco path is equivalent and cheaper.`);
57232
58450
  }
57233
- const p = join42(orchDir, `${ph.name}.workflow.mjs`);
57234
- writeFileSync16(p, phaseWorkflowScript(ph, run2, engineAbs, BATCH_SIZE2));
58451
+ const p = join44(orchDir, `${ph.name}.workflow.mjs`);
58452
+ writeFileSync18(p, phaseWorkflowScript(ph, run2, engineAbs, BATCH_SIZE2));
57235
58453
  written.push(p);
57236
58454
  }
57237
58455
  }
57238
- const rb = join42(orchDir, "RUNBOOK.md");
57239
- writeFileSync16(rb, runbookMd(phases, run2, engineAbs));
58456
+ const rb = join44(orchDir, "RUNBOOK.md");
58457
+ writeFileSync18(rb, runbookMd(phases, run2, engineAbs));
57240
58458
  written.push(rb);
57241
58459
  return { exitCode: 0, written, notices, errors: [], phases };
57242
58460
  }
@@ -57245,8 +58463,8 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
57245
58463
  import { createInterface as createInterface2 } from "readline";
57246
58464
 
57247
58465
  // src/mcp/handlers.ts
57248
- import { existsSync as existsSync29, readFileSync as readFileSync25, realpathSync as realpathSync4, statSync as statSync11 } from "fs";
57249
- import { isAbsolute as isAbsolute3, join as join43, resolve as resolve11, sep as sep5 } from "path";
58466
+ import { existsSync as existsSync30, readFileSync as readFileSync27, realpathSync as realpathSync4, statSync as statSync12 } from "fs";
58467
+ import { isAbsolute as isAbsolute3, join as join45, resolve as resolve11, sep as sep5 } from "path";
57250
58468
 
57251
58469
  // src/project-lock.ts
57252
58470
  var chains = /* @__PURE__ */ new Map();
@@ -57294,8 +58512,8 @@ function requiredCwd(args2, defaults) {
57294
58512
  const cwd = str2(args2.cwd) ?? defaults.defaultCwd;
57295
58513
  if (!cwd) throw new ToolError("`cwd` is required: an absolute path to the project root.");
57296
58514
  const abs = resolve11(cwd);
57297
- if (!existsSync29(abs)) throw new ToolError(`project root not found: ${abs}`);
57298
- if (!statSync11(abs).isDirectory()) throw new ToolError(`\`cwd\` is not a directory: ${abs}`);
58515
+ if (!existsSync30(abs)) throw new ToolError(`project root not found: ${abs}`);
58516
+ if (!statSync12(abs).isDirectory()) throw new ToolError(`\`cwd\` is not a directory: ${abs}`);
57299
58517
  return abs;
57300
58518
  }
57301
58519
  function standardOf(args2) {
@@ -57360,7 +58578,7 @@ function audit(args2, cwd) {
57360
58578
  process.chdir(cwd);
57361
58579
  const inputs = strArray2(args2.globs) ?? DEFAULT_GLOBS;
57362
58580
  const scopedToDiff = bool(args2.changed) || bool(args2.staged) || str2(args2.since) !== void 0;
57363
- const extra = [CAPTURES_DIR, PAGES_DIR].filter((d) => !scopedToDiff && existsSync29(d) && !inputs.includes(d));
58581
+ const extra = [CAPTURES_DIR, PAGES_DIR].filter((d) => !scopedToDiff && existsSync30(d) && !inputs.includes(d));
57364
58582
  const result = runAudit({
57365
58583
  inputs: [...inputs, ...extra],
57366
58584
  include: strArray2(args2.include),
@@ -57398,7 +58616,14 @@ function handleReport(args2, cwd) {
57398
58616
  const lang = langOf(args2);
57399
58617
  const r = audit(args2, cwd);
57400
58618
  const md = standard === "wcag" ? renderReport(r, lang) : renderPackReport(r, loadPack(standard), lang);
57401
- return { cwd, standard, lang, report: md, next: "Validate it with ultra11y_check before presenting it." };
58619
+ return {
58620
+ cwd,
58621
+ standard,
58622
+ lang,
58623
+ report: md,
58624
+ ...args2.html === true ? { html: renderHtmlDocument(compositeDoc(r, { standard, lang })) } : {},
58625
+ next: "Validate it with ultra11y_check before presenting it."
58626
+ };
57402
58627
  }
57403
58628
  function handlePrd(args2, cwd) {
57404
58629
  const standard = standardOf(args2);
@@ -57472,13 +58697,13 @@ function handlePackCheck(args2, cwd) {
57472
58697
  return { cwd, ...res };
57473
58698
  }
57474
58699
  function handleSampleCheck(cwd, standard) {
57475
- const file = join43(cwd, ".ultra11yrc.json");
57476
- if (!existsSync29(file)) {
58700
+ const file = join45(cwd, ".ultra11yrc.json");
58701
+ if (!existsSync30(file)) {
57477
58702
  throw new ToolError(`no .ultra11yrc.json at ${cwd} \u2014 a page sample must be declared before it can be linted.`);
57478
58703
  }
57479
58704
  let raw;
57480
58705
  try {
57481
- raw = JSON.parse(readFileSync25(file, "utf8"));
58706
+ raw = JSON.parse(readFileSync27(file, "utf8"));
57482
58707
  } catch (e) {
57483
58708
  throw new ToolError(`.ultra11yrc.json is not valid JSON: ${e.message}`);
57484
58709
  }
@@ -57522,6 +58747,7 @@ function handlePages(args2, cwd) {
57522
58747
  standard,
57523
58748
  lang,
57524
58749
  markdown,
58750
+ ...args2.html === true ? { html: renderHtmlDocument(pagesIndexDoc(r, { standard, lang, sheetHref: (id) => `#page-${id}` })) } : {},
57525
58751
  // `conformancePct` is null when this page decided nothing — a machine consumer must be able
57526
58752
  // to tell "no criterion was assessed" from "every assessed criterion passed", which a bare
57527
58753
  // 100 conflates. `decided`/`total` are the denominator that makes the number quotable.
@@ -57571,7 +58797,7 @@ function handleInit(args2, cwd) {
57571
58797
  const enginePath = resolve11("scripts/ultra11y.mjs");
57572
58798
  const written = [];
57573
58799
  if (bool(args2.hook)) written.push(writeHook(cwd, enginePath, failOn));
57574
- if (bool(args2.ci)) written.push(join43(cwd, ".github/workflows/a11y.yml"));
58800
+ if (bool(args2.ci)) written.push(join45(cwd, ".github/workflows/a11y.yml"));
57575
58801
  if (!written.length) {
57576
58802
  throw new ToolError("nothing to install \u2014 pass hook:true and/or ci:true.");
57577
58803
  }
@@ -57591,8 +58817,8 @@ function reportText(args2, tool) {
57591
58817
  if (inline) return inline;
57592
58818
  if (!file) throw new ToolError(`\`report_text\` is required \u2014 the report markdown for ultra11y_${tool} to work on.`);
57593
58819
  if (!isAbsolute3(file)) throw new ToolError("`report_file` must be an absolute path.");
57594
- if (!existsSync29(file)) throw new ToolError(`report file not found: ${file}`);
57595
- return readFileSync25(file, "utf8");
58820
+ if (!existsSync30(file)) throw new ToolError(`report file not found: ${file}`);
58821
+ return readFileSync27(file, "utf8");
57596
58822
  }
57597
58823
  function handleCriteria(args2) {
57598
58824
  const lang = langOf(args2);
@@ -57607,7 +58833,7 @@ function handleCriteria(args2) {
57607
58833
  function handleRead(args2, cwd) {
57608
58834
  const raw = str2(args2.path);
57609
58835
  if (!raw) throw new ToolError("`path` is required \u2014 relative to the project root, or an absolute path inside it.");
57610
- const target = isAbsolute3(raw) ? raw : join43(cwd, raw);
58836
+ const target = isAbsolute3(raw) ? raw : join45(cwd, raw);
57611
58837
  let real;
57612
58838
  try {
57613
58839
  real = realpathSync4(target);
@@ -57618,10 +58844,10 @@ function handleRead(args2, cwd) {
57618
58844
  if (real !== root && !real.startsWith(root + sep5)) {
57619
58845
  throw new ToolError(`path is outside the project: ${raw}. Use your own file tool for anything else.`);
57620
58846
  }
57621
- const st = statSync11(real);
58847
+ const st = statSync12(real);
57622
58848
  if (!st.isFile()) throw new ToolError(`not a file: ${raw}`);
57623
58849
  if (st.size > MAX_READ_BYTES) throw new ToolError(`file is too large to read (${st.size} bytes): ${raw}`);
57624
- const lines = readFileSync25(real, "utf8").split("\n");
58850
+ const lines = readFileSync27(real, "utf8").split("\n");
57625
58851
  const total = lines.length;
57626
58852
  const start2 = Math.max(1, Math.floor(num2(args2.start_line) ?? 1));
57627
58853
  if (start2 > total) throw new ToolError(`start_line ${start2} is past the end of the file (${total} lines).`);
@@ -57743,6 +58969,10 @@ var standardProp = {
57743
58969
  description: "Which standard to report against. Default: wcag (WCAG 2.2 AA)."
57744
58970
  };
57745
58971
  var langProp = { type: "string", enum: ["en", "fr"], description: "Language for the rendered prose. Default: en." };
58972
+ var htmlProp = {
58973
+ type: "boolean",
58974
+ description: "Also return the report as a self-contained HTML page (no script, no external asset). Images are not embedded \u2014 this returns a string, not files."
58975
+ };
57746
58976
  var COVERAGE_NOTE = "Static analysis alone decides only a few of the 55 WCAG 2.2 AA criteria \u2014 most need YOUR judgment (is this alt text actually relevant? is this link's purpose clear in context?) and some need a real browser (ultra11y_scan). A silent criterion is untested, never conformant.";
57747
58977
  var TOOLS2 = [
57748
58978
  {
@@ -57772,7 +59002,7 @@ var TOOLS2 = [
57772
59002
  description: "Turn an audit into the auditor-facing conformance report: one section per success criterion, with the evidence and the verdict. Says explicitly which criteria were not tested, because a report silent about its own coverage reads as a clean bill of health.",
57773
59003
  inputSchema: {
57774
59004
  type: "object",
57775
- properties: { cwd: cwdProp, globs: globsProp, standard: standardProp, lang: langProp },
59005
+ properties: { cwd: cwdProp, globs: globsProp, standard: standardProp, lang: langProp, html: htmlProp },
57776
59006
  required: ["cwd"]
57777
59007
  }
57778
59008
  },
@@ -57904,7 +59134,8 @@ var TOOLS2 = [
57904
59134
  type: "string",
57905
59135
  enum: ["grid", "report"],
57906
59136
  description: "`grid` (default) is the criterion \xD7 page matrix; `report` is the per-page dossiers."
57907
- }
59137
+ },
59138
+ html: htmlProp
57908
59139
  },
57909
59140
  required: ["cwd"]
57910
59141
  }
@@ -58162,25 +59393,25 @@ function str3(v) {
58162
59393
  var DECLARED = new Set([...TOOLS2, ...WRITE_TOOLS].map((t2) => t2.name));
58163
59394
 
58164
59395
  // src/mcp/resources.ts
58165
- import { existsSync as existsSync30, readdirSync as readdirSync6, readFileSync as readFileSync26, realpathSync as realpathSync5, statSync as statSync12 } from "fs";
58166
- import { basename as basename3, dirname as dirname13, join as join44, resolve as resolve12, sep as sep6 } from "path";
59396
+ import { existsSync as existsSync31, readdirSync as readdirSync6, readFileSync as readFileSync28, realpathSync as realpathSync5, statSync as statSync13 } from "fs";
59397
+ import { basename as basename3, dirname as dirname13, join as join46, resolve as resolve12, sep as sep6 } from "path";
58167
59398
  import { fileURLToPath as fileURLToPath4 } from "url";
58168
59399
  var SKILL_NAME = "ultra11y";
58169
59400
  var URI_SCHEME = "skill://";
58170
59401
  function resolveSkillRoot(moduleDir) {
58171
59402
  const here = moduleDir ?? dirname13(fileURLToPath4(import.meta.url));
58172
59403
  const candidates2 = [resolve12(here, ".."), resolve12(here, "..", "skills", SKILL_NAME), resolve12(here, "..", "..", "skills", SKILL_NAME)];
58173
- return candidates2.find((dir) => existsSync30(join44(dir, "SKILL.md")));
59404
+ return candidates2.find((dir) => existsSync31(join46(dir, "SKILL.md")));
58174
59405
  }
58175
59406
  function listResources(moduleDir) {
58176
59407
  const root = resolveSkillRoot(moduleDir);
58177
59408
  if (!root) return [];
58178
59409
  const out2 = [describe(root, "SKILL.md", `${SKILL_NAME}: the skill`)];
58179
- const refDir = join44(root, "references");
58180
- if (!existsSync30(refDir)) return out2;
59410
+ const refDir = join46(root, "references");
59411
+ if (!existsSync31(refDir)) return out2;
58181
59412
  for (const file of readdirSync6(refDir).sort()) {
58182
59413
  if (!file.endsWith(".md")) continue;
58183
- out2.push(describe(root, join44("references", file), `${SKILL_NAME} reference: ${basename3(file, ".md")}`));
59414
+ out2.push(describe(root, join46("references", file), `${SKILL_NAME} reference: ${basename3(file, ".md")}`));
58184
59415
  }
58185
59416
  return out2;
58186
59417
  }
@@ -58203,8 +59434,8 @@ function readResource(uri, moduleDir) {
58203
59434
  if (targetReal !== rootReal && !targetReal.startsWith(rootReal + sep6)) {
58204
59435
  throw new ResourceError(`resource path escapes the skill root: ${uri}`);
58205
59436
  }
58206
- if (!statSync12(targetReal).isFile()) throw new ResourceError(`not a file: ${uri}`);
58207
- return { uri, mimeType: "text/markdown", text: readFileSync26(targetReal, "utf8") };
59437
+ if (!statSync13(targetReal).isFile()) throw new ResourceError(`not a file: ${uri}`);
59438
+ return { uri, mimeType: "text/markdown", text: readFileSync28(targetReal, "utf8") };
58208
59439
  }
58209
59440
  var ResourceError = class extends Error {
58210
59441
  };
@@ -58215,14 +59446,14 @@ function describe(root, rel2, fallbackTitle) {
58215
59446
  title: fallbackTitle,
58216
59447
  mimeType: "text/markdown"
58217
59448
  };
58218
- const summary = firstProse(join44(root, rel2));
59449
+ const summary = firstProse(join46(root, rel2));
58219
59450
  if (summary) decl.description = summary;
58220
59451
  return decl;
58221
59452
  }
58222
59453
  function firstProse(file) {
58223
59454
  let text;
58224
59455
  try {
58225
- text = readFileSync26(file, "utf8");
59456
+ text = readFileSync28(file, "utf8");
58226
59457
  } catch {
58227
59458
  return void 0;
58228
59459
  }
@@ -58387,7 +59618,7 @@ async function runStdioServer(opts = {}) {
58387
59618
  let restore;
58388
59619
  if (!opts.captureStdout && output === process.stdout) {
58389
59620
  const original = process.stdout.write;
58390
- process.stdout.write = ((chunk, ...rest) => process.stderr.write(chunk, ...rest));
59621
+ process.stdout.write = ((chunk2, ...rest) => process.stderr.write(chunk2, ...rest));
58391
59622
  restore = () => {
58392
59623
  process.stdout.write = original;
58393
59624
  };
@@ -58635,6 +59866,8 @@ Usage:
58635
59866
  ultra11y audit [--format sarif|github] (CI: SARIF for code scanning, or inline annotations + job summary)
58636
59867
  ultra11y audit --in <audit.json> [--fail-on blocking|major|minor] [--format sarif|github] (re-gate an audit already computed \u2014 e.g. one carrying adjudicated verdicts \u2014 without a second detection pass)
58637
59868
  ultra11y report --in <audit.json> [--out <dir>] [--standard <pack>] [--format sarif|github] [--lang auto|en|fr]
59869
+ ultra11y report --in <audit.json> [--evidence [--evidence-max <n>]] [--out <dir>] (the Markdown report, illustrated with annotated crops)
59870
+ ultra11y report --in <audit.json> --html [--evidence] [--inline-budget <bytes>] [--out <dir>] (index.html + a printable single file)
58638
59871
  ultra11y prd --in <audit.json> [--out <dir>] [--split criterion] [--format audit|doc|remediation] [--no-technical] [--standard <pack>] [--lang auto|en|fr]
58639
59872
  ultra11y tickets --in <audit.json> [--provider auto|github|gitlab|jira] [--grain criterion|page|page-criterion|single|file] [--transport auto|cli|rest]
58640
59873
  ultra11y tickets [--out <dir>] [--max-tickets <n>] [--dry-run] [--json] [--standard <pack>] [--format audit|remediation] [--lang auto|en|fr]
@@ -58659,6 +59892,7 @@ Usage:
58659
59892
  ultra11y snapshot list [--root <dir>] [--json]
58660
59893
  ultra11y pages --in <audit.json> [--standard <pack>] [--json] [--lang auto|en|fr] (the per-page criterion grid)
58661
59894
  ultra11y pages --in <audit.json> --format report [--split page] [--out <dir>] (the per-page report, with screenshots)
59895
+ ultra11y pages --in <audit.json> --format report --out <dir> [--evidence [--evidence-max <n>]] [--html] (annotated crops of each non-conformity, and the HTML site)
58662
59896
  ultra11y pages discover --sitemap <url> | --crawl <url> | --from-snapshots [--depth <n>] [--max <n>] [--write] [--json] (build the page sample)
58663
59897
  ultra11y pages --in <audit.json> --standard <pack> --diff <external.json> (hold the grid against an audit someone else performed)
58664
59898
  ultra11y import --from file <report.json> | --from ara <id> [--source <adapter>] [--out <dir>] [--json]
@@ -59055,6 +60289,10 @@ var VALUE_FLAGS2 = /* @__PURE__ */ new Set([
59055
60289
  "model",
59056
60290
  "pack",
59057
60291
  "format",
60292
+ // Bytes of image data the single-file HTML report may carry inline (src/html-emit.ts).
60293
+ "inline-budget",
60294
+ // Hard cap on the crops one run writes (src/evidence.ts `DEFAULT_CAPS.total`).
60295
+ "evidence-max",
59058
60296
  "guidance",
59059
60297
  "runtime",
59060
60298
  "cwd",
@@ -59089,6 +60327,10 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
59089
60327
  "cross-file",
59090
60328
  "json",
59091
60329
  "quiet",
60330
+ // The visual tier. Both must be declared here or `parseArgs` swallows the value that
60331
+ // follows and the run prints "unknown flag (ignored)" for a flag that in fact works.
60332
+ "html",
60333
+ "evidence",
59092
60334
  "no-default-excludes",
59093
60335
  "no-captures",
59094
60336
  "require-captures",
@@ -59238,7 +60480,16 @@ function emitCiFormat(result, format, standard, lang, failOn) {
59238
60480
  console.error(md);
59239
60481
  }
59240
60482
  if (process.env.ULTRA11Y_PR_COMMENT === "1") {
59241
- const c2 = pushPrComment(md, standard);
60483
+ const digest = prComment(result, {
60484
+ standard,
60485
+ lang,
60486
+ // The run is known before the artifact exists, so the link is always safe. The artifact
60487
+ // NAME is only set by the action when it actually uploads one — naming an artifact that
60488
+ // was never uploaded sends the reader to a page that does not exist.
60489
+ ...process.env.ULTRA11Y_RUN_URL ? { runUrl: process.env.ULTRA11Y_RUN_URL } : {},
60490
+ ...process.env.ULTRA11Y_ARTIFACT_NAME ? { artifactName: process.env.ULTRA11Y_ARTIFACT_NAME } : {}
60491
+ });
60492
+ const c2 = pushPrComment(digest, standard);
59242
60493
  console.error(
59243
60494
  c2.ok ? lang === "fr" ? `ultra11y : commentaire de PR ${c2.action === "updated" ? "mis \xE0 jour" : c2.action === "created" ? "cr\xE9\xE9" : "ignor\xE9"}${c2.reason ? ` (${c2.reason})` : ""}.` : `ultra11y: PR comment ${c2.action}${c2.reason ? ` (${c2.reason})` : ""}.` : lang === "fr" ? `ultra11y : commentaire de PR impossible${c2.reason ? ` \u2014 ${c2.reason}` : ""}.` : `ultra11y: PR comment failed${c2.reason ? ` \u2014 ${c2.reason}` : ""}.`
59244
60495
  );
@@ -59324,9 +60575,9 @@ async function cmdAudit(p) {
59324
60575
  const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
59325
60576
  const capturesDir = capturesFlag ?? CAPTURES_DIR;
59326
60577
  const scopedToDiff = p.flags.changed === true || p.flags.staged === true || since !== void 0;
59327
- const capturesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && (capturesFlag !== void 0 || existsSync31(capturesDir));
60578
+ const capturesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && (capturesFlag !== void 0 || existsSync32(capturesDir));
59328
60579
  const useCaptures = capturesWanted && !scopedToDiff && !inputs.includes(capturesDir);
59329
- const pagesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && existsSync31(PAGES_DIR);
60580
+ const pagesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && existsSync32(PAGES_DIR);
59330
60581
  const usePages = pagesWanted && !scopedToDiff && !inputs.includes(PAGES_DIR);
59331
60582
  const pagesInScope = pagesWanted && !scopedToDiff;
59332
60583
  const auditInputs = [...inputs, ...useCaptures ? [capturesDir] : [], ...usePages ? [PAGES_DIR] : []];
@@ -59368,10 +60619,10 @@ async function cmdAudit(p) {
59368
60619
  if (typeof p.flags.out === "string") {
59369
60620
  const out2 = p.flags.out;
59370
60621
  const asFile = out2.toLowerCase().endsWith(".json");
59371
- const target = asFile ? out2 : join45(out2, "audit-latest.json");
60622
+ const target = asFile ? out2 : join47(out2, "audit-latest.json");
59372
60623
  try {
59373
- mkdirSync15(asFile ? dirname14(out2) : out2, { recursive: true });
59374
- writeFileSync17(target, JSON.stringify(result, null, 2) + "\n");
60624
+ mkdirSync17(asFile ? dirname14(out2) : out2, { recursive: true });
60625
+ writeFileSync19(target, JSON.stringify(result, null, 2) + "\n");
59375
60626
  console.error(lang === "fr" ? `\u2192 audit \xE9crit dans ${target}` : `\u2192 audit written to ${target}`);
59376
60627
  } catch {
59377
60628
  }
@@ -59390,7 +60641,7 @@ async function cmdAudit(p) {
59390
60641
  const baselineFlag = p.flags.baseline;
59391
60642
  if (typeof baselineFlag === "string" && baselineFlag) {
59392
60643
  let baseline = null;
59393
- if (existsSync31(baselineFlag)) {
60644
+ if (existsSync32(baselineFlag)) {
59394
60645
  try {
59395
60646
  const parsed = JSON.parse(readText(baselineFlag));
59396
60647
  if (isCurrentAudit(parsed)) baseline = parsed;
@@ -59444,11 +60695,11 @@ async function cmdDev(p) {
59444
60695
  return 2;
59445
60696
  }
59446
60697
  if (p.flags.next === true) {
59447
- const dir = join45(root, ".ultra11y", "next");
60698
+ const dir = join47(root, ".ultra11y", "next");
59448
60699
  const rel2 = ".ultra11y/next/overlay.jsx";
59449
60700
  try {
59450
- mkdirSync15(dir, { recursive: true });
59451
- writeFileSync17(join45(dir, "overlay.jsx"), nextOverlayComponent(port));
60701
+ mkdirSync17(dir, { recursive: true });
60702
+ writeFileSync19(join47(dir, "overlay.jsx"), nextOverlayComponent(port));
59452
60703
  } catch (e) {
59453
60704
  console.error(`ultra11y dev: could not write ${rel2}: ${e instanceof Error ? e.message : String(e)}`);
59454
60705
  return 1;
@@ -59497,7 +60748,7 @@ async function cmdPagesDiscover(p) {
59497
60748
  const snaps = sampleFromSnapshots(readSnapshots(root));
59498
60749
  if (!snaps.length) {
59499
60750
  console.error(
59500
- lang === "fr" ? `ultra11y pages discover : aucun instantan\xE9 sous ${join45(root, PAGES_DIR)} \u2014 lancez d'abord vos tests E2E avec checkA11y, ou \`scan --sample\`.` : `ultra11y pages discover: no snapshot under ${join45(root, PAGES_DIR)} \u2014 run your E2E tests with checkA11y first, or \`scan --sample\`.`
60751
+ lang === "fr" ? `ultra11y pages discover : aucun instantan\xE9 sous ${join47(root, PAGES_DIR)} \u2014 lancez d'abord vos tests E2E avec checkA11y, ou \`scan --sample\`.` : `ultra11y pages discover: no snapshot under ${join47(root, PAGES_DIR)} \u2014 run your E2E tests with checkA11y first, or \`scan --sample\`.`
59501
60752
  );
59502
60753
  return 1;
59503
60754
  }
@@ -59577,9 +60828,9 @@ ${found} page(s) discovered, ${merged.added.length} new. Nothing was written \u2
59577
60828
  }
59578
60829
  return 0;
59579
60830
  }
59580
- const file = join45(process.cwd(), ".ultra11yrc.json");
60831
+ const file = join47(process.cwd(), ".ultra11yrc.json");
59581
60832
  let doc = {};
59582
- if (existsSync31(file)) {
60833
+ if (existsSync32(file)) {
59583
60834
  try {
59584
60835
  doc = JSON.parse(readText(file));
59585
60836
  } catch {
@@ -59588,7 +60839,7 @@ ${found} page(s) discovered, ${merged.added.length} new. Nothing was written \u2
59588
60839
  }
59589
60840
  }
59590
60841
  doc.sample = v.sample;
59591
- writeFileSync17(file, `${JSON.stringify(doc, null, 2)}
60842
+ writeFileSync19(file, `${JSON.stringify(doc, null, 2)}
59592
60843
  `);
59593
60844
  if (!p.flags.json)
59594
60845
  console.log(
@@ -59648,50 +60899,121 @@ async function cmdPages(p) {
59648
60899
  const derived = derivePages(result, scope);
59649
60900
  const split = p.flags.split === "page";
59650
60901
  const outDir = typeof p.flags.out === "string" && p.flags.out ? p.flags.out : void 0;
59651
- const shotOf = (id) => join45(PAGES_DIR, id, "screen.png");
60902
+ const shotOf = (id) => join47(PAGES_DIR, id, "screen.png");
59652
60903
  const shotsRelative = (fileDir) => {
59653
60904
  const m = /* @__PURE__ */ new Map();
59654
60905
  for (const pg of derived) {
59655
- if (existsSync31(shotOf(pg.id))) m.set(pg.id, relative5(fileDir, shotOf(pg.id)).split("\\").join("/"));
60906
+ if (existsSync32(shotOf(pg.id))) m.set(pg.id, relative5(fileDir, shotOf(pg.id)).split("\\").join("/"));
59656
60907
  }
59657
60908
  return m;
59658
60909
  };
60910
+ const shotPaths = (pages) => {
60911
+ const m = /* @__PURE__ */ new Map();
60912
+ for (const pg of pages) if (existsSync32(shotOf(pg.id))) m.set(pg.id, shotOf(pg.id));
60913
+ return m;
60914
+ };
59659
60915
  const shotsCopiedInto = (dir) => {
59660
60916
  const m = /* @__PURE__ */ new Map();
59661
- const assets = join45(dir, "assets");
60917
+ const assets = join47(dir, "assets");
59662
60918
  for (const pg of derived) {
59663
60919
  const src = shotOf(pg.id);
59664
- if (!existsSync31(src)) continue;
59665
- mkdirSync15(assets, { recursive: true });
59666
- copyFileSync4(src, join45(assets, `${pg.id}.png`));
60920
+ if (!existsSync32(src)) continue;
60921
+ mkdirSync17(assets, { recursive: true });
60922
+ copyFileSync4(src, join47(assets, `${pg.id}.png`));
59667
60923
  m.set(pg.id, `./assets/${pg.id}.png`);
59668
60924
  }
59669
60925
  return m;
59670
60926
  };
60927
+ const wantEvidence = p.flags.evidence === true;
60928
+ const wantHtml = p.flags.html === true;
59671
60929
  if (!outDir) {
60930
+ if (wantEvidence || wantHtml) {
60931
+ console.error(
60932
+ lang === "fr" ? "ultra11y pages : --evidence et --html demandent --out <dir>. Le mode stdout n'a pas de r\xE9pertoire o\xF9 \xE9crire des images ni \xEAtre auto-suffisant." : "ultra11y pages: --evidence and --html require --out <dir>. Stdout mode has no directory to write images into, nor to be self-contained in."
60933
+ );
60934
+ return 2;
60935
+ }
59672
60936
  console.log(renderPagesDocument(result, derived, { standard, lang, screenshots: shotsRelative(".") }));
59673
60937
  return 0;
59674
60938
  }
59675
- mkdirSync15(outDir, { recursive: true });
60939
+ mkdirSync17(outDir, { recursive: true });
60940
+ const manifest = wantEvidence ? writeEvidence(result, { outDir, ...evidenceCapsOf(p) }) : void 0;
60941
+ const cropFor = manifest ? (f) => {
60942
+ const c2 = manifest.crops.get(findingId(f));
60943
+ return c2 ? { href: c2.href, alt: c2.alt[lang] } : void 0;
60944
+ } : void 0;
60945
+ if (manifest) {
60946
+ const total = evidenceNotice(manifest, null, lang);
60947
+ console.error(
60948
+ lang === "fr" ? `ultra11y : ${manifest.totals.imaged} vignette(s) \xE9crite(s) dans ${join47(outDir, "assets")}.` : `ultra11y: ${manifest.totals.imaged} crop(s) written to ${join47(outDir, "assets")}.`
60949
+ );
60950
+ for (const line of total) console.error(line);
60951
+ }
60952
+ const evidenceOpts = {
60953
+ ...cropFor ? { cropFor } : {},
60954
+ ...manifest ? { evidenceNotice: (id) => evidenceNotice(manifest, id, lang) } : {}
60955
+ };
59676
60956
  if (!split) {
59677
- const file = join45(outDir, `pages-${result.date}.md`);
59678
- writeFileSync17(file, `${renderPagesDocument(result, derived, { standard, lang, screenshots: shotsCopiedInto(outDir) })}
60957
+ const file = join47(outDir, `pages-${result.date}.md`);
60958
+ writeFileSync19(file, `${renderPagesDocument(result, derived, { standard, lang, screenshots: shotsCopiedInto(outDir), ...evidenceOpts })}
59679
60959
  `);
60960
+ const html2 = wantHtml ? emitHtml(result, {
60961
+ outDir,
60962
+ standard,
60963
+ lang,
60964
+ layout: "pages",
60965
+ pages: true,
60966
+ screenshots: shotPaths(derived),
60967
+ ...manifest ? { evidence: manifest } : {},
60968
+ inlineBudget: budgetOf(p)
60969
+ }) : void 0;
59680
60970
  console.log(file);
59681
- return 0;
60971
+ return html2?.imagesDropped ? 1 : 0;
59682
60972
  }
59683
60973
  const shots = shotsCopiedInto(outDir);
59684
60974
  const sheet = (id) => `page-${id}.md`;
59685
60975
  const hrefs = new Map(derived.map((pg) => [pg.id, `./${sheet(pg.id)}`]));
59686
60976
  for (const pg of derived) {
59687
- writeFileSync17(join45(outDir, sheet(pg.id)), `${renderPageDocument(result, pg, { standard, lang, screenshots: shots })}
60977
+ writeFileSync19(join47(outDir, sheet(pg.id)), `${renderPageDocument(result, pg, { standard, lang, screenshots: shots, ...evidenceOpts })}
59688
60978
  `);
59689
60979
  }
59690
- const index = join45(outDir, "index.md");
59691
- writeFileSync17(index, `${renderPagesIndex(result, derived, { standard, lang, hrefs })}
60980
+ const index = join47(outDir, "index.md");
60981
+ writeFileSync19(index, `${renderPagesIndex(result, derived, { standard, lang, hrefs })}
59692
60982
  `);
60983
+ const html = wantHtml ? emitHtml(result, {
60984
+ outDir,
60985
+ standard,
60986
+ lang,
60987
+ layout: "pages",
60988
+ pages: true,
60989
+ screenshots: shotPaths(derived),
60990
+ ...manifest ? { evidence: manifest } : {},
60991
+ inlineBudget: budgetOf(p)
60992
+ }) : void 0;
59693
60993
  console.log(index);
59694
- return 0;
60994
+ return html?.imagesDropped ? 1 : 0;
60995
+ }
60996
+ function positiveIntFlag(p, name2) {
60997
+ const raw = p.flags[name2];
60998
+ if (typeof raw !== "string" || !raw) return void 0;
60999
+ const n = Number.parseInt(raw, 10);
61000
+ if (Number.isFinite(n) && n > 0) return n;
61001
+ console.error(`ultra11y: --${name2} expects a positive integer (got "${raw}"). Ignored \u2014 the default applies.`);
61002
+ return void 0;
61003
+ }
61004
+ function budgetOf(p) {
61005
+ return positiveIntFlag(p, "inline-budget");
61006
+ }
61007
+ function evidenceCapsOf(p) {
61008
+ const total = positiveIntFlag(p, "evidence-max");
61009
+ return total === void 0 ? {} : { caps: { total } };
61010
+ }
61011
+ function emitHtml(result, opts) {
61012
+ const res = writeHtml(result, opts);
61013
+ for (const n of res.notices) console.error(`ultra11y: ${n}`);
61014
+ console.error(`ultra11y: ${res.index}`);
61015
+ if (res.composite) console.error(`ultra11y: ${res.composite}`);
61016
+ return res;
59695
61017
  }
59696
61018
  async function cmdSnapshot(p) {
59697
61019
  const sub = p.positionals[0];
@@ -59707,7 +61029,7 @@ async function cmdSnapshot(p) {
59707
61029
  2
59708
61030
  )
59709
61031
  );
59710
- else if (!snaps.length) console.log(lang === "fr" ? `Aucun instantan\xE9 dans ${join45(root, PAGES_DIR)}.` : `No snapshot in ${join45(root, PAGES_DIR)}.`);
61032
+ else if (!snaps.length) console.log(lang === "fr" ? `Aucun instantan\xE9 dans ${join47(root, PAGES_DIR)}.` : `No snapshot in ${join47(root, PAGES_DIR)}.`);
59711
61033
  else for (const s of snaps) console.log(`${s.meta.id} ${s.meta.name} ${s.meta.url}${s.meta.auth ? " [auth]" : ""}`);
59712
61034
  return 0;
59713
61035
  }
@@ -59754,7 +61076,7 @@ async function cmdSnapshot(p) {
59754
61076
  console.error(`ultra11y snapshot write: could not write the snapshot: ${e instanceof Error ? e.message : String(e)}`);
59755
61077
  return 1;
59756
61078
  }
59757
- const result = runAudit({ inputs: [join45(dir, "dom.html")], onWarn: (m) => console.error(m) });
61079
+ const result = runAudit({ inputs: [join47(dir, "dom.html")], onWarn: (m) => console.error(m) });
59758
61080
  const failOnRaw = p.flags["fail-on"];
59759
61081
  const failOnParsed = parseFailOn(failOnRaw);
59760
61082
  if (failOnRaw !== void 0 && failOnParsed === null) {
@@ -59786,9 +61108,9 @@ function cmdInit(p) {
59786
61108
  if (want.baseline) {
59787
61109
  const inputs = p.positionals.length ? p.positionals : ["."];
59788
61110
  const result = runAudit({ inputs, onWarn: (m) => console.error(m) });
59789
- mkdirSync15(join45(root, "audits"), { recursive: true });
59790
- const bp = join45(root, "audits", "baseline.json");
59791
- writeFileSync17(bp, JSON.stringify(result, null, 2) + "\n");
61111
+ mkdirSync17(join47(root, "audits"), { recursive: true });
61112
+ const bp = join47(root, "audits", "baseline.json");
61113
+ writeFileSync19(bp, JSON.stringify(result, null, 2) + "\n");
59792
61114
  wrote.push(bp);
59793
61115
  }
59794
61116
  if (want.hook) wrote.push(writeHook(root, engineRel, failOn, legacy ? "baseline" : "staged"));
@@ -59913,11 +61235,29 @@ async function cmdReport(p) {
59913
61235
  console.error(`ultra11y report: --format must be sarif|github (got "${String(p.flags.format)}").`);
59914
61236
  return 2;
59915
61237
  }
61238
+ if (ciFormat && p.flags.html === true) {
61239
+ console.error(
61240
+ lang === "fr" ? `ultra11y report : --html et --format ${ciFormat} sont incompatibles. --format nomme un canal CI, --html un document ; choisissez l'un ou l'autre.` : `ultra11y report: --html and --format ${ciFormat} are incompatible. --format names a CI channel, --html names a document; pick one.`
61241
+ );
61242
+ return 2;
61243
+ }
59916
61244
  if (ciFormat) {
59917
61245
  emitCiFormat(result, ciFormat, standard, lang);
59918
61246
  return 0;
59919
61247
  }
59920
- const path = writeReport(result, { out: out2, lang, standard });
61248
+ const manifest = p.flags.evidence === true ? writeEvidence(result, { outDir: out2, ...evidenceCapsOf(p) }) : void 0;
61249
+ if (manifest) {
61250
+ for (const line of evidenceNotice(manifest, null, lang)) console.error(line);
61251
+ }
61252
+ const cropFor = manifest ? (f) => {
61253
+ const c2 = manifest.crops.get(findingId(f));
61254
+ return c2 ? { href: c2.href, alt: c2.alt[lang] } : void 0;
61255
+ } : void 0;
61256
+ const path = writeReport(result, { out: out2, lang, standard, ...cropFor ? { cropFor } : {} });
61257
+ let html;
61258
+ if (p.flags.html === true) {
61259
+ html = emitHtml(result, { outDir: out2, standard, lang, ...manifest ? { evidence: manifest } : {}, inlineBudget: budgetOf(p) });
61260
+ }
59921
61261
  const untested = isCore(standard) ? [] : untestedNeedsRendering(result);
59922
61262
  const partial = untested.length > 0;
59923
61263
  if (partial && !p.flags.json) console.error(`\u{1F6A8} ${partialAuditBanner(lang, untested)}`);
@@ -59929,6 +61269,7 @@ async function cmdReport(p) {
59929
61269
  conformancePct: result.conformancePct,
59930
61270
  date: result.date,
59931
61271
  standard: typeof p.flags.standard === "string" ? p.flags.standard : "wcag",
61272
+ ...html ? { htmlPath: html.index, htmlSinglePath: html.composite } : {},
59932
61273
  ...partial ? { partialAudit: true, untestedCriteria: untested } : {}
59933
61274
  },
59934
61275
  null,
@@ -59936,7 +61277,7 @@ async function cmdReport(p) {
59936
61277
  )
59937
61278
  );
59938
61279
  else console.log(path);
59939
- return 0;
61280
+ return html?.imagesDropped ? 1 : 0;
59940
61281
  }
59941
61282
  async function cmdPrd(p) {
59942
61283
  const inFlag = p.flags.in;
@@ -60068,8 +61409,8 @@ async function cmdTickets(p) {
60068
61409
  const ticketsOut = typeof p.flags.out === "string" && p.flags.out ? p.flags.out : void 0;
60069
61410
  let setPath;
60070
61411
  if (ticketsOut) {
60071
- mkdirSync15(ticketsOut, { recursive: true });
60072
- setPath = join45(ticketsOut, `issues-${result.date}.json`);
61412
+ mkdirSync17(ticketsOut, { recursive: true });
61413
+ setPath = join47(ticketsOut, `issues-${result.date}.json`);
60073
61414
  const payload = {
60074
61415
  tool: "ultra11y",
60075
61416
  kind: "issues",
@@ -60080,7 +61421,7 @@ async function cmdTickets(p) {
60080
61421
  count: plan.tickets.length,
60081
61422
  issues: plan.tickets
60082
61423
  };
60083
- writeFileSync17(setPath, `${JSON.stringify(payload, null, 2)}
61424
+ writeFileSync19(setPath, `${JSON.stringify(payload, null, 2)}
60084
61425
  `);
60085
61426
  if (!json) console.log(setPath);
60086
61427
  }
@@ -60170,8 +61511,8 @@ async function cmdTickets(p) {
60170
61511
  return pushed.failed > 0 ? 1 : 0;
60171
61512
  }
60172
61513
  function depsAt(root) {
60173
- const pkgPath = join45(root, "package.json");
60174
- if (!existsSync31(pkgPath)) return {};
61514
+ const pkgPath = join47(root, "package.json");
61515
+ if (!existsSync32(pkgPath)) return {};
60175
61516
  try {
60176
61517
  const pkg = JSON.parse(readText(pkgPath));
60177
61518
  return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
@@ -60197,24 +61538,24 @@ function cmdRender(p) {
60197
61538
  console.error(`ultra11y render: --runner must be playwright|cypress|auto (got "${forced}").`);
60198
61539
  return 2;
60199
61540
  }
60200
- const detected = detectE2eRunner(depsAt(root), (f) => existsSync31(join45(root, f)));
61541
+ const detected = detectE2eRunner(depsAt(root), (f) => existsSync32(join47(root, f)));
60201
61542
  const runners = forced && forced !== "auto" ? [forced] : detected;
60202
61543
  if (!runners.length) {
60203
61544
  console.error(e2eSetupPlan([], {}, lang));
60204
61545
  return 1;
60205
61546
  }
60206
61547
  const engineRef = engineRefFor(root);
60207
- const dir = join45(root, ".ultra11y", "e2e");
61548
+ const dir = join47(root, ".ultra11y", "e2e");
60208
61549
  const paths = {};
60209
61550
  try {
60210
- mkdirSync15(dir, { recursive: true });
61551
+ mkdirSync17(dir, { recursive: true });
60211
61552
  if (runners.includes("playwright")) {
60212
- writeFileSync17(join45(dir, "playwright.mjs"), playwrightFixture(engineRef));
61553
+ writeFileSync19(join47(dir, "playwright.mjs"), playwrightFixture(engineRef));
60213
61554
  paths.playwright = ".ultra11y/e2e/playwright.mjs";
60214
61555
  }
60215
61556
  if (runners.includes("cypress")) {
60216
- writeFileSync17(join45(dir, "cypress-plugin.mjs"), cypressPlugin(engineRef));
60217
- writeFileSync17(join45(dir, "cypress-commands.mjs"), cypressCommands());
61557
+ writeFileSync19(join47(dir, "cypress-plugin.mjs"), cypressPlugin(engineRef));
61558
+ writeFileSync19(join47(dir, "cypress-commands.mjs"), cypressCommands());
60218
61559
  paths.cypressPlugin = ".ultra11y/e2e/cypress-plugin.mjs";
60219
61560
  paths.cypressCommands = ".ultra11y/e2e/cypress-commands.mjs";
60220
61561
  }
@@ -60229,7 +61570,7 @@ function cmdRender(p) {
60229
61570
  if (p.flags.scaffold === true) {
60230
61571
  const out2 = typeof p.flags.out === "string" && p.flags.out ? p.flags.out : "ultra11y-render.tsx";
60231
61572
  try {
60232
- writeFileSync17(out2, ssrHarness());
61573
+ writeFileSync19(out2, ssrHarness());
60233
61574
  } catch (e) {
60234
61575
  console.error(`ultra11y render: could not write ${out2}: ${e instanceof Error ? e.message : String(e)}`);
60235
61576
  return 1;
@@ -60243,29 +61584,29 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
60243
61584
  }
60244
61585
  if (p.flags.setup === true) {
60245
61586
  const rel2 = ".ultra11y/capture-setup.mjs";
60246
- const out2 = join45(root, rel2);
61587
+ const out2 = join47(root, rel2);
60247
61588
  try {
60248
- mkdirSync15(dirname14(out2), { recursive: true });
60249
- writeFileSync17(out2, captureSetup());
61589
+ mkdirSync17(dirname14(out2), { recursive: true });
61590
+ writeFileSync19(out2, captureSetup());
60250
61591
  } catch (e) {
60251
61592
  console.error(`ultra11y render: could not write ${out2}: ${e instanceof Error ? e.message : String(e)}`);
60252
61593
  return 1;
60253
61594
  }
60254
61595
  let setupDeps = {};
60255
- const setupPkg = join45(root, "package.json");
60256
- if (existsSync31(setupPkg)) {
61596
+ const setupPkg = join47(root, "package.json");
61597
+ if (existsSync32(setupPkg)) {
60257
61598
  try {
60258
61599
  const pkg = JSON.parse(readText(setupPkg));
60259
61600
  setupDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
60260
61601
  } catch {
60261
61602
  }
60262
61603
  }
60263
- const tr = detectTestRunner(setupDeps, (f) => existsSync31(join45(root, f)));
61604
+ const tr = detectTestRunner(setupDeps, (f) => existsSync32(join47(root, f)));
60264
61605
  console.log(captureSetupPlan(tr, rel2, lang));
60265
61606
  const gaLine = ".ultra11y/captures/*.html text eol=lf linguist-generated=true";
60266
- const gaPath = join45(root, ".gitattributes");
61607
+ const gaPath = join47(root, ".gitattributes");
60267
61608
  try {
60268
- const existing = existsSync31(gaPath) ? readFileSync27(gaPath, "utf8") : "";
61609
+ const existing = existsSync32(gaPath) ? readFileSync29(gaPath, "utf8") : "";
60269
61610
  if (!existing.includes(".ultra11y/captures/")) {
60270
61611
  appendFileSync(gaPath, (existing && !existing.endsWith("\n") ? "\n" : "") + gaLine + "\n");
60271
61612
  console.log(lang === "fr" ? `.gitattributes : ajout\xE9 \xAB ${gaLine} \xBB` : `.gitattributes: added "${gaLine}"`);
@@ -60273,8 +61614,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
60273
61614
  } catch {
60274
61615
  }
60275
61616
  try {
60276
- const giPath = join45(root, ".gitignore");
60277
- if (existsSync31(giPath) && /^\s*\/?\.ultra11y(\/\**)?\/?\s*$/m.test(readFileSync27(giPath, "utf8")))
61617
+ const giPath = join47(root, ".gitignore");
61618
+ if (existsSync32(giPath) && /^\s*\/?\.ultra11y(\/\**)?\/?\s*$/m.test(readFileSync29(giPath, "utf8")))
60278
61619
  console.error(
60279
61620
  lang === "fr" ? "\u26A0\uFE0F .ultra11y semble ignor\xE9 par .gitignore \u2014 les captures doivent \xEAtre committ\xE9es pour le gate (ajoutez \xAB !.ultra11y/captures/ \xBB)." : '\u26A0\uFE0F .ultra11y appears gitignored \u2014 captures must be committed for the gate (add "!.ultra11y/captures/").'
60280
61621
  );
@@ -60284,8 +61625,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
60284
61625
  }
60285
61626
  if (p.flags.storybook === true || typeof p.flags.storybook === "string") {
60286
61627
  const sbDir = p.positionals[0] ?? "storybook-static";
60287
- const indexPath = existsSync31(join45(sbDir, "index.json")) ? join45(sbDir, "index.json") : join45(sbDir, "stories.json");
60288
- if (!existsSync31(indexPath)) {
61628
+ const indexPath = existsSync32(join47(sbDir, "index.json")) ? join47(sbDir, "index.json") : join47(sbDir, "stories.json");
61629
+ if (!existsSync32(indexPath)) {
60289
61630
  console.error(
60290
61631
  lang === "fr" ? `ultra11y render : aucun index Storybook (index.json/stories.json) dans ${sbDir}.` : `ultra11y render: no Storybook index (index.json/stories.json) in ${sbDir}.`
60291
61632
  );
@@ -60295,7 +61636,7 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
60295
61636
  const provById = new Map(stories.map((s) => [s.id, storyProvenance(s)]));
60296
61637
  const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
60297
61638
  const htmlDir = capturesFlag ?? sbDir;
60298
- const htmlFiles = existsSync31(htmlDir) ? discover([htmlDir]).files.filter((f) => /\.html?$/i.test(f)) : [];
61639
+ const htmlFiles = existsSync32(htmlDir) ? discover([htmlDir]).files.filter((f) => /\.html?$/i.test(f)) : [];
60299
61640
  const outDir = ".ultra11y/captures";
60300
61641
  let attributed = 0;
60301
61642
  let skipped = 0;
@@ -60317,8 +61658,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
60317
61658
  continue;
60318
61659
  }
60319
61660
  try {
60320
- mkdirSync15(outDir, { recursive: true });
60321
- writeFileSync17(join45(outDir, `${hitId}.html`), `${formatCaptureComment(prov)}
61661
+ mkdirSync17(outDir, { recursive: true });
61662
+ writeFileSync19(join47(outDir, `${hitId}.html`), `${formatCaptureComment(prov)}
60322
61663
  ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
60323
61664
  attributed++;
60324
61665
  } catch {
@@ -60340,11 +61681,11 @@ ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
60340
61681
  }
60341
61682
  if (p.flags.coverage === true) {
60342
61683
  const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
60343
- const capturesDir = capturesFlag ?? join45(root, ".ultra11y/captures");
61684
+ const capturesDir = capturesFlag ?? join47(root, ".ultra11y/captures");
60344
61685
  const graphExt = [...GRAPH_ONLY_EXT, ...asList(p.flags.ext) ?? []];
60345
61686
  const sourceFiles = discover([root], { include: asList(p.flags.include), exclude: asList(p.flags.exclude), ext: graphExt }).files;
60346
61687
  const graph = buildGraphStreaming(sourceFiles);
60347
- const capFiles = existsSync31(capturesDir) ? discover([capturesDir]).files : [];
61688
+ const capFiles = existsSync32(capturesDir) ? discover([capturesDir]).files : [];
60348
61689
  const entries = capFiles.map((f) => ({ file: toPosix(f), provenance: parseCaptureProvenance(readText(f)) }));
60349
61690
  const cov = computeCaptureCoverage(graph, entries);
60350
61691
  if (p.flags.json) console.log(JSON.stringify(cov, null, 2));
@@ -60352,15 +61693,15 @@ ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
60352
61693
  return 0;
60353
61694
  }
60354
61695
  let deps = {};
60355
- const pkgPath = join45(root, "package.json");
60356
- if (existsSync31(pkgPath)) {
61696
+ const pkgPath = join47(root, "package.json");
61697
+ if (existsSync32(pkgPath)) {
60357
61698
  try {
60358
61699
  const pkg = JSON.parse(readText(pkgPath));
60359
61700
  deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
60360
61701
  } catch {
60361
61702
  }
60362
61703
  }
60363
- const detection = detectFrameworks(deps, (f) => existsSync31(join45(root, f)));
61704
+ const detection = detectFrameworks(deps, (f) => existsSync32(join47(root, f)));
60364
61705
  if (p.flags.json) console.log(JSON.stringify(detection, null, 2));
60365
61706
  else console.log(renderPlan(detection, lang));
60366
61707
  return 0;
@@ -60559,9 +61900,9 @@ function applyAdjudicationFile(p, adj, lang) {
60559
61900
  return 1;
60560
61901
  }
60561
61902
  const out2 = typeof p.flags.out === "string" ? p.flags.out : ".";
60562
- mkdirSync15(out2, { recursive: true });
60563
- const auditPath = join45(out2, "audit-latest.json");
60564
- writeFileSync17(auditPath, JSON.stringify(r.audit, null, 2) + "\n");
61903
+ mkdirSync17(out2, { recursive: true });
61904
+ const auditPath = join47(out2, "audit-latest.json");
61905
+ writeFileSync19(auditPath, JSON.stringify(r.audit, null, 2) + "\n");
60565
61906
  if (p.flags.json)
60566
61907
  console.log(
60567
61908
  JSON.stringify(
@@ -60669,9 +62010,9 @@ async function cmdJudge(p) {
60669
62010
  if (r.issues.length > 40) console.error(` \u2026 +${r.issues.length - 40}`);
60670
62011
  return 1;
60671
62012
  }
60672
- mkdirSync15(out2, { recursive: true });
60673
- const auditPath = join45(out2, "audit-latest.json");
60674
- writeFileSync17(auditPath, `${JSON.stringify(r.audit, null, 2)}
62013
+ mkdirSync17(out2, { recursive: true });
62014
+ const auditPath = join47(out2, "audit-latest.json");
62015
+ writeFileSync19(auditPath, `${JSON.stringify(r.audit, null, 2)}
60675
62016
  `);
60676
62017
  console.log(
60677
62018
  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}`
@@ -60740,7 +62081,7 @@ async function cmdScan(p) {
60740
62081
  return 0;
60741
62082
  }
60742
62083
  for (const target of p.positionals.filter((a) => a !== "-")) {
60743
- if (/^https?:\/\//i.test(target) || existsSync31(target)) continue;
62084
+ if (/^https?:\/\//i.test(target) || existsSync32(target)) continue;
60744
62085
  console.error(
60745
62086
  lang === "fr" ? `ultra11y scan : fichier introuvable (file not found) : ${target}. Passez une URL http(s):// ou un fichier HTML existant.` : `ultra11y scan: File not found: ${target}. Pass an http(s):// URL or an existing HTML file.`
60746
62087
  );
@@ -60867,7 +62208,7 @@ async function cmdScan(p) {
60867
62208
  }
60868
62209
  let merged = mergeDynamic(audit2, dynamic, lang);
60869
62210
  if (dynamic.snapshots?.length) {
60870
- const doms = dynamic.snapshots.map((id) => join45(snapshotRoot ?? ".", PAGES_DIR, id, "dom.html")).filter((f) => existsSync31(f));
62211
+ const doms = dynamic.snapshots.map((id) => join47(snapshotRoot ?? ".", PAGES_DIR, id, "dom.html")).filter((f) => existsSync32(f));
60871
62212
  if (doms.length) {
60872
62213
  const snapAudit = runAudit({ inputs: doms, onWarn: (m) => console.error(m) });
60873
62214
  merged = mergeSnapshotAudit(merged, snapAudit);
@@ -60878,12 +62219,12 @@ async function cmdScan(p) {
60878
62219
  attributePages(merged, scope);
60879
62220
  }
60880
62221
  }
60881
- mkdirSync15(out2, { recursive: true });
60882
- writeFileSync17(join45(out2, "audit-latest.json"), JSON.stringify(merged, null, 2) + "\n");
62222
+ mkdirSync17(out2, { recursive: true });
62223
+ writeFileSync19(join47(out2, "audit-latest.json"), JSON.stringify(merged, null, 2) + "\n");
60883
62224
  if (p.flags.json) console.log(JSON.stringify(merged, null, 2));
60884
62225
  else {
60885
62226
  console.log(
60886
- lang === "fr" ? `Audit statique + dynamique fusionn\xE9 \u2192 ${join45(out2, "audit-latest.json")} (${merged.conformancePct}% r\xE9ussite, ${merged.findings.length} findings).` : `Static + dynamic audit merged \u2192 ${join45(out2, "audit-latest.json")} (${merged.conformancePct}% pass rate, ${merged.findings.length} findings).`
62227
+ lang === "fr" ? `Audit statique + dynamique fusionn\xE9 \u2192 ${join47(out2, "audit-latest.json")} (${merged.conformancePct}% r\xE9ussite, ${merged.findings.length} findings).` : `Static + dynamic audit merged \u2192 ${join47(out2, "audit-latest.json")} (${merged.conformancePct}% pass rate, ${merged.findings.length} findings).`
60887
62228
  );
60888
62229
  if (dynamic.snapshots?.length)
60889
62230
  console.log(
@@ -61125,9 +62466,9 @@ async function cmdImport(p) {
61125
62466
  return 1;
61126
62467
  }
61127
62468
  if (outDir) {
61128
- mkdirSync15(outDir, { recursive: true });
61129
- const rawFile = join45(outDir, `external-${adapterId}-${arg}.raw.json`);
61130
- writeFileSync17(rawFile, rawText.endsWith("\n") ? rawText : `${rawText}
62469
+ mkdirSync17(outDir, { recursive: true });
62470
+ const rawFile = join47(outDir, `external-${adapterId}-${arg}.raw.json`);
62471
+ writeFileSync19(rawFile, rawText.endsWith("\n") ? rawText : `${rawText}
61131
62472
  `);
61132
62473
  if (!p.flags.json) console.log(rawFile);
61133
62474
  }
@@ -61161,9 +62502,9 @@ async function cmdImport(p) {
61161
62502
  return 0;
61162
62503
  }
61163
62504
  if (outDir) {
61164
- mkdirSync15(outDir, { recursive: true });
61165
- const file = join45(outDir, "external-latest.json");
61166
- writeFileSync17(file, `${JSON.stringify(audit2, null, 2)}
62505
+ mkdirSync17(outDir, { recursive: true });
62506
+ const file = join47(outDir, "external-latest.json");
62507
+ writeFileSync19(file, `${JSON.stringify(audit2, null, 2)}
61167
62508
  `);
61168
62509
  console.log(file);
61169
62510
  } else {
@@ -61268,7 +62609,7 @@ function cmdOrchestrate(p) {
61268
62609
  }
61269
62610
  const engineAbs = realpathSync6(fileURLToPath5(import.meta.url));
61270
62611
  if (p.flags.list === true) {
61271
- if (!existsSync31(runFlag)) {
62612
+ if (!existsSync32(runFlag)) {
61272
62613
  console.error(`ultra11y orchestrate: run dir not found: ${runFlag}.`);
61273
62614
  return 2;
61274
62615
  }
@@ -61295,7 +62636,7 @@ function cmdOrchestrate(p) {
61295
62636
  );
61296
62637
  } else {
61297
62638
  console.log(
61298
- lang === "fr" ? `Suivez ${join45(runFlag, "orchestration", "RUNBOOK.md")} s\xE9quentiellement (chemin \xE9co).` : `Follow ${join45(runFlag, "orchestration", "RUNBOOK.md")} sequentially (the eco path).`
62639
+ lang === "fr" ? `Suivez ${join47(runFlag, "orchestration", "RUNBOOK.md")} s\xE9quentiellement (chemin \xE9co).` : `Follow ${join47(runFlag, "orchestration", "RUNBOOK.md")} sequentially (the eco path).`
61299
62640
  );
61300
62641
  }
61301
62642
  if (p.flags.phase === void 0 && workflows.length === 0 && p.flags.eco !== true) {