ultra11y 4.0.0 → 4.1.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.0";
9
+ var VERSION = "4.1.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));
@@ -49667,8 +49821,7 @@ function render(r, lang, opts) {
49667
49821
  }
49668
49822
  return out2.join("\n");
49669
49823
  }
49670
- function renderReport(r, lang = "en", outDir) {
49671
- const s = L4[lang];
49824
+ function reportGroups(r, lang = "en") {
49672
49825
  const byGuideline = /* @__PURE__ */ new Map();
49673
49826
  for (const c2 of r.criteria) {
49674
49827
  const title2 = scTitle(c2.id, lang);
@@ -49682,12 +49835,14 @@ function renderReport(r, lang = "en", outDir) {
49682
49835
  };
49683
49836
  (byGuideline.get(c2.guideline) ?? byGuideline.set(c2.guideline, []).get(c2.guideline)).push(row);
49684
49837
  }
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 });
49838
+ return r.guidelines.map((g) => ({ key: g.key, title: guidelineTitle(g.key, lang) ?? g.title, rows: byGuideline.get(g.key) ?? [] }));
49687
49839
  }
49688
- function renderPackReport(r, pack, lang = "en", outDir) {
49840
+ function renderReport(r, lang = "en", outDir) {
49841
+ const s = L4[lang];
49842
+ return render(r, lang, { std: s.wcagStd, groupHead: s.byGuideline, groups: reportGroups(r, lang), standard: CORE2, outDir });
49843
+ }
49844
+ function packReportGroups(r, pack, lang = "en") {
49689
49845
  const derived = derivePackResults(r, pack.key);
49690
- const std = `${pack.name} ${pack.baseVersion}`;
49691
49846
  const s = L4[lang];
49692
49847
  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
49848
  const byTheme = /* @__PURE__ */ new Map();
@@ -49705,11 +49860,15 @@ function renderPackReport(r, pack, lang = "en", outDir) {
49705
49860
  };
49706
49861
  (byTheme.get(pr.theme) ?? byTheme.set(pr.theme, []).get(pr.theme)).push(row);
49707
49862
  }
49708
- const groups = pack.themes.map((t2) => ({ key: `${t2.number}.`, title: themeName(pack, t2.number, lang) ?? "", rows: byTheme.get(t2.number) ?? [] }));
49863
+ return pack.themes.map((t2) => ({ key: `${t2.number}.`, title: themeName(pack, t2.number, lang) ?? "", rows: byTheme.get(t2.number) ?? [] }));
49864
+ }
49865
+ function renderPackReport(r, pack, lang = "en", outDir) {
49866
+ const derived = derivePackResults(r, pack.key);
49867
+ const std = `${pack.name} ${pack.baseVersion}`;
49709
49868
  return render(r, lang, {
49710
49869
  std,
49711
49870
  groupHead: L4[lang].byTheme,
49712
- groups,
49871
+ groups: packReportGroups(r, pack, lang),
49713
49872
  derivedOf: std,
49714
49873
  standard: pack.key,
49715
49874
  partialAudit: untestedNeedsRendering(r),
@@ -51236,7 +51395,6 @@ var L7 = {
51236
51395
  tally: (c2, nc, na, m) => `${c2} conforme(s) \xB7 ${nc} non conforme(s) \xB7 ${na} non applicable(s) \xB7 ${m} \xE0 \xE9valuer`,
51237
51396
  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
51397
  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
51398
  screenshotAlt: (n) => `Capture d'\xE9cran de la page ${n}`,
51241
51399
  noScreenshot: "Aucune capture d'\xE9cran pour cette page (le producteur n'en a pas fourni) \u2014 le tier pixel est donc inactif ici.",
51242
51400
  gridTitle: "Grille des crit\xE8res",
@@ -51277,7 +51435,6 @@ var L7 = {
51277
51435
  tally: (c2, nc, na, m) => `${c2} conforming \xB7 ${nc} non-conforming \xB7 ${na} not applicable \xB7 ${m} to assess`,
51278
51436
  coverage: (decided, total) => `Coverage: ${decided}/${total} criteria assessed \u2014 the rate above covers only those, and says nothing about the other ${total - decided}.`,
51279
51437
  tests: "Tests",
51280
- agentMark: "`C*`: conformity ruled by the AI agent from the evidence it cited (gated), not proven by the deterministic engine.",
51281
51438
  screenshotAlt: (n) => `Screenshot of the ${n} page`,
51282
51439
  noScreenshot: "No screenshot for this page (the producer supplied none) \u2014 the pixel tier is therefore inactive here.",
51283
51440
  gridTitle: "Criteria grid",
@@ -51298,7 +51455,7 @@ var L7 = {
51298
51455
  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
51456
  }
51300
51457
  };
51301
- function rowsFor(result, page, standard, lang) {
51458
+ function pageCriterionRows(result, page, standard, lang) {
51302
51459
  if (isCore(standard)) {
51303
51460
  return [...page.criteria].sort((a, b) => compareSC(a.id, b.id)).map((c2) => ({
51304
51461
  id: c2.id,
@@ -51320,7 +51477,7 @@ function rowsFor(result, page, standard, lang) {
51320
51477
  decidedBy: byId2.get(pc.id)?.decidedBy
51321
51478
  }));
51322
51479
  }
51323
- function tally(rows) {
51480
+ function pageTally(rows) {
51324
51481
  return {
51325
51482
  c: rows.filter((r) => r.status === "C").length,
51326
51483
  nc: rows.filter((r) => r.status === "NC").length,
@@ -51328,12 +51485,12 @@ function tally(rows) {
51328
51485
  manual: rows.filter((r) => r.status === "manual").length
51329
51486
  };
51330
51487
  }
51331
- function coverageOf(rows) {
51332
- const t2 = tally(rows);
51488
+ function pageCoverage(rows) {
51489
+ const t2 = pageTally(rows);
51333
51490
  return { decided: t2.c + t2.nc, total: rows.length };
51334
51491
  }
51335
- function ratePct(rows) {
51336
- const { c: c2, nc } = tally(rows);
51492
+ function pageRatePct(rows) {
51493
+ const { c: c2, nc } = pageTally(rows);
51337
51494
  return c2 + nc === 0 ? null : Math.round(c2 / (c2 + nc) * 100);
51338
51495
  }
51339
51496
  function renderPageReport(result, page, opts = {}) {
@@ -51348,10 +51505,10 @@ function renderPageReport(result, page, opts = {}) {
51348
51505
  meta2.push(`- **${s.basis}** : ${basisLabel(page.basis, lang)}`);
51349
51506
  if (page.auth) meta2.push(`- **${s.auth}** : \u2705`);
51350
51507
  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);
51508
+ const rows = pageCriterionRows(result, page, standard, lang);
51509
+ const t2 = pageTally(rows);
51510
+ const cov = pageCoverage(rows);
51511
+ const rate = pageRatePct(rows);
51355
51512
  out2.push(`- **${s.rate}** : **${rate === null ? "\u2014" : `${rate} %`}** _(${s.rateNote})_`);
51356
51513
  out2.push(`- ${s.tally(t2.c, t2.nc, t2.na, t2.manual)}`);
51357
51514
  out2.push(`- ${s.coverage(cov.decided, cov.total)}`, "");
@@ -51359,6 +51516,7 @@ function renderPageReport(result, page, opts = {}) {
51359
51516
  const shot = opts.screenshots?.get(page.id);
51360
51517
  if (shot) out2.push(`![${s.screenshotAlt(page.name)}](${shot})`, "");
51361
51518
  else out2.push(`_${s.noScreenshot}_`, "");
51519
+ if (opts.evidenceNotice?.length) out2.push(...opts.evidenceNotice, "");
51362
51520
  const withTests = rows.some((r) => r.tests.length);
51363
51521
  out2.push(`${h}# ${s.gridTitle}`, "", `> ${s.gridNote}`, "");
51364
51522
  out2.push(withTests ? `| ${s.criterion} | ${s.tests} | ${s.status} |` : `| ${s.criterion} | ${s.status} |`);
@@ -51374,16 +51532,17 @@ function renderPageReport(result, page, opts = {}) {
51374
51532
  else out2.push(`| ${row.label} | ${mark} |`);
51375
51533
  }
51376
51534
  out2.push("", `> ${s.manualWarn}`, "");
51377
- if (rows.some((r) => r.decidedBy === "agent" && r.status === "C")) out2.push(`> ${s.agentMark}`, "");
51535
+ if (rows.some((r) => r.decidedBy === "agent" && r.status === "C")) out2.push(`> ${agentMarkNote(lang)}`, "");
51378
51536
  const units = prdUnits(pageView(result, page), standard, lang);
51379
51537
  const ncUnits = units.filter((u) => !u.advisory);
51380
51538
  const advUnits = units.filter((u) => u.advisory);
51539
+ const unit = { heading: `${h}##`, collapse: true, ...opts.cropFor ? { cropFor: opts.cropFor } : {} };
51381
51540
  out2.push(`${h}# ${s.ncTitle}`, "");
51382
51541
  if (!ncUnits.length) out2.push(s.noNc, "");
51383
- else for (const u of ncUnits) out2.push(...renderAuditorUnit(u, standard, lang, { heading: `${h}##`, collapse: true }));
51542
+ else for (const u of ncUnits) out2.push(...renderAuditorUnit(u, standard, lang, unit));
51384
51543
  if (advUnits.length) {
51385
51544
  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 }));
51545
+ for (const u of advUnits) out2.push(...renderAuditorUnit(u, standard, lang, unit));
51387
51546
  }
51388
51547
  return out2.join("\n");
51389
51548
  }
@@ -51409,12 +51568,12 @@ function renderPagesIndex(result, pages, opts = {}) {
51409
51568
  out2.push(`| ${s.page} | ${s.url} | ${s.basis} | ${s.rateShort} | ${s.blocking} | ${s.major} | ${s.minor} | ${s.sheet} |`);
51410
51569
  out2.push("| --- | --- | --- | --- | --- | --- | --- | --- |");
51411
51570
  for (const p of pages) {
51412
- const rows = rowsFor(result, p, standard, lang);
51413
- const cov = coverageOf(rows);
51571
+ const rows = pageCriterionRows(result, p, standard, lang);
51572
+ const cov = pageCoverage(rows);
51414
51573
  const nc = [...p.findings, ...(result.packFindings ?? []).filter((f) => f.page === p.id)].filter((f) => !f.advisory);
51415
51574
  const href = opts.hrefs?.get(p.id);
51416
51575
  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} |`
51576
+ `| ${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
51577
  );
51419
51578
  }
51420
51579
  out2.push("");
@@ -56097,6 +56256,7 @@ var S = {
56097
56256
  where: "Emplacement",
56098
56257
  what: "Constat",
56099
56258
  more: (n) => `\u2026 et ${n} autre(s).`,
56259
+ moreGroups: (n) => `\u2026 et ${n} autre(s) groupe(s) \u2014 voir le r\xE9sum\xE9 de job.`,
56100
56260
  perPage: "Bilan page par page",
56101
56261
  page: "Page",
56102
56262
  count: "Constats",
@@ -56104,6 +56264,16 @@ var S = {
56104
56264
  pageRate: "Taux",
56105
56265
  snapshot: "instantan\xE9",
56106
56266
  source: "source",
56267
+ occurrences: "Occ.",
56268
+ pagesCol: "Pages",
56269
+ grouped: (groups, occ) => `${groups} d\xE9faut(s) distinct(s) \xB7 ${occ} occurrence(s)`,
56270
+ 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.",
56271
+ verdictFail: (n) => `\u{1F534} ${n} non-conformit\xE9(s) bloquante(s) \u2014 la porte est rouge.`,
56272
+ verdictWarn: "\u{1F7E0} Aucune non-conformit\xE9 bloquante ; des constats majeurs ou mineurs restent \xE0 traiter.",
56273
+ verdictPass: "\u2705 Aucune non-conformit\xE9 relev\xE9e par le moteur statique.",
56274
+ artifact: (name2) => `Rapport complet (HTML, captures annot\xE9es) : artefact **${name2}** du run.`,
56275
+ runLink: (url) => `[Voir le run et son r\xE9sum\xE9 de job](${url})`,
56276
+ 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
56277
  unanchored: (n) => `${n} constat(s) rattach\xE9(s) \xE0 une URL, sans ligne de code \xE0 annoter \u2014 voir le rapport.`,
56108
56278
  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
56279
  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 +56289,7 @@ var S = {
56119
56289
  where: "Location",
56120
56290
  what: "Finding",
56121
56291
  more: (n) => `\u2026 and ${n} more.`,
56292
+ moreGroups: (n) => `\u2026 and ${n} more group(s) \u2014 see the job summary.`,
56122
56293
  perPage: "Page-by-page scoreboard",
56123
56294
  page: "Page",
56124
56295
  count: "Findings",
@@ -56126,20 +56297,75 @@ var S = {
56126
56297
  pageRate: "Rate",
56127
56298
  snapshot: "snapshot",
56128
56299
  source: "source",
56300
+ occurrences: "Occ.",
56301
+ pagesCol: "Pages",
56302
+ grouped: (groups, occ) => `${groups} distinct defect(s) \xB7 ${occ} occurrence(s)`,
56303
+ 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.",
56304
+ verdictFail: (n) => `\u{1F534} ${n} blocking non-conformity(ies) \u2014 the gate is red.`,
56305
+ verdictWarn: "\u{1F7E0} No blocking non-conformity; major or minor findings remain.",
56306
+ verdictPass: "\u2705 No non-conformity found by the static engine.",
56307
+ artifact: (name2) => `Full report (HTML, annotated crops): artifact **${name2}** of this run.`,
56308
+ runLink: (url) => `[See the run and its job summary](${url})`,
56309
+ clamped: (n) => `_${n} group(s) dropped from this comment to fit GitHub's limit \u2014 the job summary carries them all._`,
56129
56310
  unanchored: (n) => `${n} finding(s) keyed to a URL, with no code line to annotate \u2014 see the report.`,
56130
56311
  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
56312
  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
56313
  }
56133
56314
  };
56134
56315
  var MAX_ROWS = 50;
56316
+ var COMMENT_ROWS = 10;
56317
+ var COMMENT_LIMIT = 65536;
56318
+ function groupFindings(findings, standard, lang, baseDir) {
56319
+ const groups = /* @__PURE__ */ new Map();
56320
+ for (const f of findings) {
56321
+ const criterion = criterionLabel2(f, standard);
56322
+ const key = `${criterion}\0${f.ruleId}\0${f.selectorHint}`;
56323
+ const g = groups.get(key);
56324
+ if (g) {
56325
+ g.occurrences++;
56326
+ if (f.page) g.pageSet.add(f.page);
56327
+ continue;
56328
+ }
56329
+ groups.set(key, {
56330
+ criterion,
56331
+ ruleId: f.ruleId,
56332
+ selectorHint: f.selectorHint,
56333
+ severity: f.severity,
56334
+ message: resolveMessage(f, lang),
56335
+ where: isUrl2(f.file) ? f.file : `${repoRelative(f.file, baseDir)}:${Math.max(1, f.line)}`,
56336
+ occurrences: 1,
56337
+ pages: 0,
56338
+ pageSet: new Set(f.page ? [f.page] : [])
56339
+ });
56340
+ }
56341
+ 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));
56342
+ }
56343
+ function runRate(result, standard, lang) {
56344
+ const groups = isCore(standard) ? reportGroups(result, lang) : packReportGroups(result, loadPack(standard), lang);
56345
+ const { decided, total } = reportCoverage(groups);
56346
+ const agentRuled = groups.some((g) => g.rows.some((r) => r.decidedBy === "agent" && r.status === "C"));
56347
+ return { text: `${formatRate(decided === 0 ? null : result.conformancePct, decided, total)}${agentRuled ? "*" : ""}`, agentRuled };
56348
+ }
56349
+ function groupTableHead(s) {
56350
+ return [`| ${s.severity} | ${s.criterion} | ${s.where} | ${s.what} | ${s.occurrences} | ${s.pagesCol} |`, "| --- | --- | --- | --- | ---: | ---: |"];
56351
+ }
56352
+ function groupRow(g) {
56353
+ const cell = (v) => v.replace(/\|/g, "\\|");
56354
+ return `| ${ICON5[g.severity]} ${g.severity} | ${cell(g.criterion)} | \`${cell(g.where)}\` (\`${cell(g.selectorHint)}\`) | ${cell(g.message)} | ${g.occurrences} | ${g.pages || "\u2014"} |`;
56355
+ }
56356
+ function groupTable(rows, s) {
56357
+ return [...groupTableHead(s), ...rows.map(groupRow)];
56358
+ }
56135
56359
  function stepSummary(result, opts = {}) {
56136
56360
  const standard = opts.standard ?? CORE2;
56137
56361
  const lang = opts.lang ?? "en";
56138
56362
  const s = S[lang];
56139
56363
  const stdLabel = isCore(standard) ? "WCAG 2.2 AA" : loadPack(standard).name;
56364
+ const rate = runRate(result, standard, lang);
56140
56365
  const out2 = [];
56141
56366
  out2.push(`## ${s.title} \u2014 ${stdLabel}`, "");
56142
- out2.push(`\`${result.date}\` \xB7 ${result.scope.files} ${s.files} \xB7 **${result.conformancePct}%** ${s.rate}`, "");
56367
+ out2.push(`\`${result.date}\` \xB7 ${result.scope.files} ${s.files} \xB7 **${rate.text}** ${s.rate}`, "");
56368
+ if (rate.agentRuled) out2.push(`> ${agentMarkNote(lang)}`, "");
56143
56369
  const baseDir = opts.baseDir ?? process.cwd();
56144
56370
  const all = findingsForStandard(result, standard);
56145
56371
  if (!all.length) {
@@ -56147,21 +56373,52 @@ function stepSummary(result, opts = {}) {
56147
56373
  out2.push(perPageTable(result, standard, lang));
56148
56374
  return out2.join("\n");
56149
56375
  }
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));
56376
+ const grouped = groupFindings(all, standard, lang, baseDir);
56377
+ out2.push(`### ${s.findings} \u2014 ${s.grouped(grouped.length, all.length)}`, "");
56378
+ out2.push(`> ${s.groupNote}`, "");
56379
+ out2.push(...groupTable(grouped.slice(0, MAX_ROWS), s));
56380
+ if (grouped.length > MAX_ROWS) out2.push("", s.more(grouped.length - MAX_ROWS));
56159
56381
  out2.push("");
56160
56382
  const unanchored = all.filter((f) => isUrl2(f.file)).length;
56161
56383
  if (unanchored) out2.push(`> ${s.unanchored(unanchored)}`, "");
56162
56384
  out2.push(perPageTable(result, standard, lang));
56163
56385
  return out2.join("\n");
56164
56386
  }
56387
+ function prComment(result, opts = {}) {
56388
+ const standard = opts.standard ?? CORE2;
56389
+ const lang = opts.lang ?? "en";
56390
+ const s = S[lang];
56391
+ const baseDir = opts.baseDir ?? process.cwd();
56392
+ const stdLabel = isCore(standard) ? "WCAG 2.2 AA" : loadPack(standard).name;
56393
+ const all = findingsForStandard(result, standard);
56394
+ const normative = all.filter((f) => !f.advisory);
56395
+ const blocking = normative.filter((f) => f.severity === "bloquant").length;
56396
+ const rate = runRate(result, standard, lang);
56397
+ const grouped = groupFindings(all, standard, lang, baseDir);
56398
+ const orphans = unattributedFindings(result).filter((f) => !f.advisory).length;
56399
+ const head = [];
56400
+ head.push(`### ${s.title} \u2014 ${stdLabel}`, "");
56401
+ head.push(blocking ? s.verdictFail(blocking) : normative.length ? s.verdictWarn : s.verdictPass, "");
56402
+ head.push(`\`${result.date}\` \xB7 ${result.scope.files} ${s.files} \xB7 **${rate.text}** ${s.rate}`, "");
56403
+ if (rate.agentRuled) head.push(`> ${agentMarkNote(lang)}`, "");
56404
+ if (orphans) head.push(`> ${s.unattributed(orphans)}`, "");
56405
+ const tail = [];
56406
+ if (opts.artifactName) tail.push(s.artifact(opts.artifactName), "");
56407
+ if (opts.runUrl) tail.push(s.runLink(opts.runUrl), "");
56408
+ const assemble = (rows2) => {
56409
+ const body2 = [];
56410
+ if (grouped.length) {
56411
+ body2.push(s.grouped(grouped.length, all.length), "");
56412
+ body2.push(...groupTable(grouped.slice(0, rows2), s), "");
56413
+ const omitted = grouped.length - rows2;
56414
+ if (omitted > 0) body2.push(rows2 < COMMENT_ROWS ? s.clamped(omitted) : s.moreGroups(omitted), "");
56415
+ }
56416
+ return [...head, ...body2, ...tail].join("\n").trimEnd();
56417
+ };
56418
+ let rows = Math.min(COMMENT_ROWS, grouped.length);
56419
+ while (rows > 0 && assemble(rows).length > COMMENT_LIMIT) rows--;
56420
+ return assemble(rows);
56421
+ }
56165
56422
  function perPageTable(result, standard = CORE2, lang = "en") {
56166
56423
  const s = S[lang];
56167
56424
  const scope = pagesOf(result);
@@ -56186,10 +56443,992 @@ function perPageTable(result, standard = CORE2, lang = "en") {
56186
56443
  return out2.join("\n");
56187
56444
  }
56188
56445
 
56446
+ // src/evidence.ts
56447
+ import { createHash as createHash6 } from "crypto";
56448
+ import { existsSync as existsSync28, mkdirSync as mkdirSync13, readFileSync as readFileSync23, writeFileSync as writeFileSync15 } from "fs";
56449
+ import { join as join40 } from "path";
56450
+ var DEFAULT_GEOMETRY = { pad: 24, minWidth: 320, minHeight: 120, maxWidth: 960, maxHeight: 540 };
56451
+ var DEFAULT_CAPS = { perRule: 6, perPage: 12, total: 200 };
56452
+ var HALO = { r: 255, g: 255, b: 255, a: 0.92 };
56453
+ var MARK3 = { r: 179, g: 38, b: 30, a: 1 };
56454
+ var RING = 3;
56455
+ function pageIdOfSnapshot(file) {
56456
+ if (!isSnapshotDom(file)) return null;
56457
+ const posix3 = file.split("\\").join("/");
56458
+ const at = posix3.lastIndexOf(`${PAGES_DIR}/`);
56459
+ if (at < 0) return null;
56460
+ const rest = posix3.slice(at + PAGES_DIR.length + 1);
56461
+ const id = rest.slice(0, rest.indexOf("/"));
56462
+ return id || null;
56463
+ }
56464
+ function readJson3(file) {
56465
+ try {
56466
+ return JSON.parse(readFileSync23(file, "utf8"));
56467
+ } catch {
56468
+ return void 0;
56469
+ }
56470
+ }
56471
+ function resolveScale(img, viewport, boxes) {
56472
+ const candidates2 = [viewport?.width, boxes.get(0)?.w];
56473
+ for (const w of candidates2) {
56474
+ if (!w || w <= 0) continue;
56475
+ const scale = img.width / w;
56476
+ if (scale >= 0.5 && scale <= 4) return scale;
56477
+ }
56478
+ return null;
56479
+ }
56480
+ function loadPageEvidence(root, pageId) {
56481
+ const dir = snapshotDir(root, pageId);
56482
+ const domFile = join40(dir, "dom.html");
56483
+ const shotFile = join40(dir, "screen.png");
56484
+ if (!existsSync28(domFile)) return { skip: "no-snapshot" };
56485
+ if (!existsSync28(shotFile)) return { skip: "no-screenshot" };
56486
+ let dom;
56487
+ let shot;
56488
+ try {
56489
+ dom = readFileSync23(domFile, "utf8");
56490
+ shot = readFileSync23(shotFile);
56491
+ } catch {
56492
+ return { skip: "no-snapshot" };
56493
+ }
56494
+ const img = decodePng(shot);
56495
+ if (!img) return { skip: "unreadable-image" };
56496
+ const digest = readJson3(join40(dir, "boxes.json"));
56497
+ if (!digest) return { skip: "no-boxes" };
56498
+ const doc = parseHtml(dom, domFile);
56499
+ const boxes = alignedBoxes(dom, digest, doc);
56500
+ if (!boxes) return { skip: "no-boxes" };
56501
+ const scale = resolveScale(img, metaOf(dir)?.viewport, boxes);
56502
+ if (scale === null) return { skip: "unknown-scale" };
56503
+ const ordinalOf = /* @__PURE__ */ new Map();
56504
+ for (let i2 = 0; i2 < doc.elements.length; i2++) {
56505
+ const start2 = doc.elements[i2]?.start;
56506
+ if (start2 !== void 0 && !ordinalOf.has(start2)) ordinalOf.set(start2, i2);
56507
+ }
56508
+ const viewport = metaOf(dir)?.viewport ?? { width: Math.round(img.width / scale), height: Math.round(img.height / scale) };
56509
+ return { pageId, img, scale, viewport, ordinalOf, boxes, truncated: Boolean(digest.truncated) };
56510
+ }
56511
+ function metaOf(dir) {
56512
+ const raw = readJson3(join40(dir, "meta.json"));
56513
+ if (raw === void 0) return void 0;
56514
+ const v = validateSnapshotMeta(raw);
56515
+ return v.ok && v.meta ? v.meta : void 0;
56516
+ }
56517
+ function cropRect(box, g, scale, img) {
56518
+ const bx = box.x * scale;
56519
+ const by = box.y * scale;
56520
+ const bw = box.w * scale;
56521
+ const bh = box.h * scale;
56522
+ const w = Math.min(Math.max(bw + g.pad * 2 * scale, g.minWidth * scale), g.maxWidth * scale, img.width);
56523
+ const h = Math.min(Math.max(bh + g.pad * 2 * scale, g.minHeight * scale), g.maxHeight * scale, img.height);
56524
+ let x = bx + bw / 2 - w / 2;
56525
+ let y = by + bh / 2 - h / 2;
56526
+ x = Math.max(0, Math.min(x, img.width - w));
56527
+ y = Math.max(0, Math.min(y, img.height - h));
56528
+ return { x, y, w, h };
56529
+ }
56530
+ function annotate(raster, target) {
56531
+ strokeRect(raster, { x: target.x - RING, y: target.y - RING, w: target.w + RING * 2, h: target.h + RING * 2 }, HALO, RING);
56532
+ strokeRect(raster, target, MARK3, RING);
56533
+ strokeRect(raster, { x: target.x + RING, y: target.y + RING, w: target.w - RING * 2, h: target.h - RING * 2 }, HALO, 1, 6);
56534
+ const arm = Math.max(6, Math.min(18, Math.round(Math.min(target.w, target.h) / 3)));
56535
+ const t2 = RING;
56536
+ const x0 = target.x - RING;
56537
+ const y0 = target.y - RING;
56538
+ const x1 = target.x + target.w + RING;
56539
+ const y1 = target.y + target.h + RING;
56540
+ for (const [cx, cy, dx, dy] of [
56541
+ [x0, y0, 1, 1],
56542
+ [x1, y0, -1, 1],
56543
+ [x0, y1, 1, -1],
56544
+ [x1, y1, -1, -1]
56545
+ ]) {
56546
+ fillRect(raster, { x: dx > 0 ? cx : cx - arm, y: dy > 0 ? cy - t2 : cy, w: arm, h: t2 }, MARK3);
56547
+ fillRect(raster, { x: dx > 0 ? cx - t2 : cx, y: dy > 0 ? cy : cy - arm, w: t2, h: arm }, MARK3);
56548
+ }
56549
+ }
56550
+ function cropFinding(ctx, f, geometry) {
56551
+ const g = { ...DEFAULT_GEOMETRY, ...geometry };
56552
+ if (f.sourceStart === void 0) return { skip: "no-offsets" };
56553
+ const ordinal = ctx.ordinalOf.get(f.sourceStart);
56554
+ if (ordinal === void 0) return { skip: "unjoinable" };
56555
+ if (ordinal === 0) return { skip: "page-scope" };
56556
+ const box = ctx.boxes.get(ordinal);
56557
+ if (!box) return { skip: ctx.truncated ? "truncated" : "no-boxes" };
56558
+ if (box.w <= 0 || box.h <= 0) return { skip: "zero-area" };
56559
+ const vpArea = ctx.viewport.width * ctx.viewport.height;
56560
+ if (vpArea > 0 && box.w * box.h / vpArea >= 0.7) return { skip: "page-scope" };
56561
+ const visibleTop = Math.max(0, box.y * ctx.scale);
56562
+ const visibleBottom = Math.min(ctx.img.height, (box.y + box.h) * ctx.scale);
56563
+ const visibleFraction = box.h > 0 ? (visibleBottom - visibleTop) / (box.h * ctx.scale) : 0;
56564
+ if (visibleFraction < 0.5) return { skip: "below-the-fold" };
56565
+ const rect = cropRect(box, g, ctx.scale, ctx.img);
56566
+ const crop = cropImage(ctx.img, rect);
56567
+ if (!crop) return { skip: "below-the-fold" };
56568
+ annotate(crop.raster, {
56569
+ x: box.x * ctx.scale - crop.rect.x,
56570
+ y: box.y * ctx.scale - crop.rect.y,
56571
+ w: box.w * ctx.scale,
56572
+ h: box.h * ctx.scale
56573
+ });
56574
+ return {
56575
+ png: encodePng(crop.raster),
56576
+ width: crop.raster.width,
56577
+ height: crop.raster.height,
56578
+ box,
56579
+ scale: ctx.scale,
56580
+ clipped: visibleFraction < 1
56581
+ };
56582
+ }
56583
+ var S2 = {
56584
+ fr: {
56585
+ // No `N.N —` sequence: src/check.ts scans the WHOLE document for criterion mentions.
56586
+ alt: (sel, page) => `Capture recadr\xE9e de l'\xE9l\xE9ment ${sel} sur la page ${page}, entour\xE9 d'un cadre`,
56587
+ notImaged: (n) => `${n} occurrence(s) ne sont pas illustr\xE9es :`,
56588
+ reasons: {
56589
+ "no-snapshot": "constat lev\xE9 sur le code source, pas sur une page captur\xE9e \u2014 il n'y a pas de pixels \xE0 montrer",
56590
+ "no-screenshot": "le producteur n'a pas fourni de capture pour cette page, donc le niveau pixel est inactif ici",
56591
+ "unreadable-image": "la capture n'est pas d\xE9codable (le moteur lit le truecolour 8 bits uniquement) \u2014 mieux vaut aucune image qu'une fausse",
56592
+ "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",
56593
+ truncated: "la page d\xE9passe la limite de collecte : aucune position n'a \xE9t\xE9 enregistr\xE9e pour cet \xE9l\xE9ment",
56594
+ "no-offsets": "le constat ne porte pas d'ancrage dans la source, il ne peut pas \xEAtre joint \xE0 une position",
56595
+ unjoinable: "l'ancrage du constat ne correspond \xE0 aucun \xE9l\xE9ment du DOM s\xE9rialis\xE9",
56596
+ "page-scope": "le constat porte sur la page enti\xE8re \u2014 la capture de page ci-dessus est l'illustration",
56597
+ "zero-area": "l'\xE9l\xE9ment n'occupe aucune surface peinte",
56598
+ "below-the-fold": "l'\xE9l\xE9ment est hors de la capture (la capture couvre la fen\xEAtre, pas la page enti\xE8re) \u2014 sa r\xE9f\xE9rence fichier:ligne reste valable",
56599
+ "unknown-scale": "le rapport pixels/CSS est ind\xE9termin\xE9 pour cette page ; un rapport devin\xE9 encadrerait le mauvais endroit",
56600
+ capped: "occurrences regroup\xE9es : une illustration par \xE9l\xE9ment distinct, dans la limite fix\xE9e"
56601
+ }
56602
+ },
56603
+ en: {
56604
+ alt: (sel, page) => `Cropped screenshot of the ${sel} element on the ${page} page, outlined`,
56605
+ notImaged: (n) => `${n} occurrence(s) are not illustrated:`,
56606
+ reasons: {
56607
+ "no-snapshot": "raised on source code, not on a captured page \u2014 there are no pixels to show",
56608
+ "no-screenshot": "the producer supplied no screenshot for this page, so the pixel tier is inactive here",
56609
+ "unreadable-image": "the screenshot does not decode (the engine reads 8-bit truecolour only) \u2014 no image beats a wrong one",
56610
+ "no-boxes": "the element positions do not verify against the serialized DOM, so they were refused wholesale rather than outlining the wrong element",
56611
+ truncated: "the page exceeded the collection cap: no position was recorded for this element",
56612
+ "no-offsets": "the finding carries no source anchor, so it cannot be joined to a position",
56613
+ unjoinable: "the finding's anchor matches no element in the serialized DOM",
56614
+ "page-scope": "the finding is about the whole page \u2014 the page screenshot above is the illustration",
56615
+ "zero-area": "the element paints no box",
56616
+ "below-the-fold": "the element sits outside the capture (the screenshot covers the viewport, not the whole page) \u2014 its file:line reference still holds",
56617
+ "unknown-scale": "the image-pixel to CSS-pixel ratio is indeterminate for this page; a guessed ratio would outline the wrong place",
56618
+ capped: "occurrences grouped: one illustration per distinct element, up to the cap"
56619
+ }
56620
+ }
56621
+ };
56622
+ function evidenceNotice(m, pageId, lang) {
56623
+ const t2 = pageId === null ? m.totals : m.perPage.get(pageId);
56624
+ if (!t2) return [];
56625
+ const missing = t2.located - t2.imaged;
56626
+ if (missing <= 0) return [];
56627
+ const s = S2[lang];
56628
+ const out2 = [s.notImaged(missing)];
56629
+ for (const [reason, n] of Object.entries(t2.skipped).sort((a, b) => (b[1] ?? 0) - (a[1] ?? 0))) {
56630
+ if (!n) continue;
56631
+ out2.push(`- ${n} \u2014 ${s.reasons[reason]}`);
56632
+ }
56633
+ return out2;
56634
+ }
56635
+ function bump(t2, reason) {
56636
+ t2.skipped[reason] = (t2.skipped[reason] ?? 0) + 1;
56637
+ }
56638
+ function cropName(f) {
56639
+ return `${createHash6("sha1").update(findingId(f)).digest("hex").slice(0, 12)}.png`;
56640
+ }
56641
+ function writeEvidence(result, opts) {
56642
+ const root = opts.root ?? process.cwd();
56643
+ const caps = { ...DEFAULT_CAPS, ...opts.caps };
56644
+ const manifest = {
56645
+ crops: /* @__PURE__ */ new Map(),
56646
+ skipped: /* @__PURE__ */ new Map(),
56647
+ perPage: /* @__PURE__ */ new Map(),
56648
+ totals: { located: 0, imaged: 0, skipped: {} }
56649
+ };
56650
+ const byPage = /* @__PURE__ */ new Map();
56651
+ for (const f of [...result.findings, ...result.packFindings ?? []]) {
56652
+ if (f.advisory) continue;
56653
+ const page = pageIdOfSnapshot(f.file);
56654
+ if (!page) continue;
56655
+ const list = byPage.get(page);
56656
+ if (list) list.push(f);
56657
+ else byPage.set(page, [f]);
56658
+ }
56659
+ for (const [pageId, findings] of [...byPage].sort((a, b) => a[0].localeCompare(b[0]))) {
56660
+ const tally = { located: findings.length, imaged: 0, skipped: {} };
56661
+ manifest.perPage.set(pageId, tally);
56662
+ manifest.totals.located += findings.length;
56663
+ const ctx = loadPageEvidence(root, pageId);
56664
+ if ("skip" in ctx) {
56665
+ for (const f of findings) {
56666
+ manifest.skipped.set(findingId(f), ctx.skip);
56667
+ bump(tally, ctx.skip);
56668
+ bump(manifest.totals, ctx.skip);
56669
+ }
56670
+ continue;
56671
+ }
56672
+ const dir = join40(opts.outDir, "assets", pageId);
56673
+ let wrote = false;
56674
+ const perRule = /* @__PURE__ */ new Map();
56675
+ const seen = /* @__PURE__ */ new Set();
56676
+ let onPage = 0;
56677
+ for (const f of findings) {
56678
+ const id = findingId(f);
56679
+ const key = `${f.ruleId}\0${f.selectorHint}`;
56680
+ const ruleCount = perRule.get(f.ruleId) ?? 0;
56681
+ if (seen.has(key) || ruleCount >= caps.perRule || onPage >= caps.perPage || manifest.crops.size >= caps.total) {
56682
+ manifest.skipped.set(id, "capped");
56683
+ bump(tally, "capped");
56684
+ bump(manifest.totals, "capped");
56685
+ continue;
56686
+ }
56687
+ const crop = cropFinding(ctx, f, opts.geometry);
56688
+ if ("skip" in crop) {
56689
+ manifest.skipped.set(id, crop.skip);
56690
+ bump(tally, crop.skip);
56691
+ bump(manifest.totals, crop.skip);
56692
+ continue;
56693
+ }
56694
+ const name2 = cropName(f);
56695
+ const path = join40(dir, name2);
56696
+ try {
56697
+ if (!wrote) {
56698
+ mkdirSync13(dir, { recursive: true });
56699
+ wrote = true;
56700
+ }
56701
+ writeFileSync15(path, crop.png);
56702
+ } catch {
56703
+ manifest.skipped.set(id, "no-screenshot");
56704
+ bump(tally, "no-screenshot");
56705
+ bump(manifest.totals, "no-screenshot");
56706
+ continue;
56707
+ }
56708
+ seen.add(key);
56709
+ perRule.set(f.ruleId, ruleCount + 1);
56710
+ onPage++;
56711
+ tally.imaged++;
56712
+ manifest.totals.imaged++;
56713
+ manifest.crops.set(id, {
56714
+ findingId: id,
56715
+ page: pageId,
56716
+ ruleId: f.ruleId,
56717
+ criteriaId: f.criteriaId,
56718
+ href: `./assets/${pageId}/${name2}`,
56719
+ path,
56720
+ width: crop.width,
56721
+ height: crop.height,
56722
+ box: crop.box,
56723
+ scale: crop.scale,
56724
+ clipped: crop.clipped,
56725
+ alt: { fr: S2.fr.alt(f.selectorHint, pageId), en: S2.en.alt(f.selectorHint, pageId) }
56726
+ });
56727
+ }
56728
+ }
56729
+ return manifest;
56730
+ }
56731
+
56732
+ // src/html-emit.ts
56733
+ import { mkdirSync as mkdirSync14, readFileSync as readFileSync24, statSync as statSync11, writeFileSync as writeFileSync16 } from "fs";
56734
+ import { join as join41 } from "path";
56735
+
56736
+ // src/html.ts
56737
+ var MARK4 = { C: "C", NC: "NC", NA: "\u2014", manual: "?" };
56738
+ var CLASS = { C: "c", NC: "nc", NA: "na", manual: "m" };
56739
+ var T3 = {
56740
+ fr: {
56741
+ skip: "Aller au contenu",
56742
+ nav: "Documents du rapport",
56743
+ printHint: "Pour un PDF : imprimez cette page (Ctrl/Cmd + P) et choisissez \xAB Enregistrer au format PDF \xBB.",
56744
+ generated: "G\xE9n\xE9r\xE9 par ultra11y"
56745
+ },
56746
+ en: {
56747
+ skip: "Skip to content",
56748
+ nav: "Report documents",
56749
+ printHint: "For a PDF: print this page (Ctrl/Cmd + P) and choose \u201CSave as PDF\u201D.",
56750
+ generated: "Generated by ultra11y"
56751
+ }
56752
+ };
56753
+ function esc2(s) {
56754
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
56755
+ }
56756
+ var STYLE = `
56757
+ :root { color-scheme: light dark; --bg:#fff; --fg:#111; --line:#e3e3e6; --mut:#666; --soft:#f6f6f8;
56758
+ --c:#0a7d33; --nc:#b3261e; --m:#b06000; --accent:#0b5fa5; }
56759
+ @media (prefers-color-scheme: dark) { :root { --bg:#141416; --fg:#f2f2f7; --line:#2c2c2e; --mut:#a1a1a6; --soft:#1c1c1f;
56760
+ --c:#3fbf6f; --nc:#ff6b5e; --m:#d99a2b; --accent:#6cb6ff; } }
56761
+ * { box-sizing:border-box; }
56762
+ body { margin:0; background:var(--bg); color:var(--fg); font:15px/1.6 ui-sans-serif,system-ui,-apple-system,sans-serif; }
56763
+ .skip { position:absolute; left:-9999px; top:0; padding:8px 14px; background:var(--bg); color:var(--fg); border:2px solid var(--accent); z-index:9; }
56764
+ .skip:focus { left:8px; top:8px; }
56765
+ .wrap { max-width:78ch; margin:0 auto; padding:32px 20px 64px; }
56766
+ header.top { border-bottom:1px solid var(--line); }
56767
+ h1 { font-size:26px; line-height:1.25; margin:0 0 6px; }
56768
+ h2 { font-size:20px; margin:36px 0 10px; padding-top:8px; border-top:1px solid var(--line); }
56769
+ h3 { font-size:17px; margin:26px 0 8px; }
56770
+ h4 { font-size:15px; margin:20px 0 6px; }
56771
+ p { margin:0 0 12px; } p.sub { color:var(--mut); margin:0 0 4px; }
56772
+ a { color:var(--accent); }
56773
+ a:focus-visible, summary:focus-visible { outline:3px solid var(--accent); outline-offset:2px; }
56774
+ nav.docs ul { list-style:none; display:flex; flex-wrap:wrap; gap:6px 16px; margin:12px 0 0; padding:0; }
56775
+ nav.docs [aria-current="page"] { font-weight:700; }
56776
+ code, .mono { font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace; overflow-wrap:anywhere; }
56777
+ ul.body { margin:0 0 12px; padding-left:22px; }
56778
+ .note { margin:0 0 14px; padding:10px 14px; border-left:4px solid var(--mut); background:var(--soft); }
56779
+ .note.warn { border-left-color:var(--m); }
56780
+ .tw { overflow-x:auto; margin:0 0 18px; }
56781
+ table { border-collapse:collapse; width:100%; font-size:14px; }
56782
+ caption { text-align:left; font-weight:600; padding:0 0 8px; }
56783
+ th, td { padding:6px 10px; border-bottom:1px solid var(--line); text-align:left; vertical-align:top; }
56784
+ thead th { border-bottom:2px solid var(--line); background:var(--soft); }
56785
+ td.end, th.end { text-align:right; }
56786
+ tr.group th { background:var(--soft); font-weight:700; }
56787
+ .st { font-weight:700; }
56788
+ .st-c{color:var(--c)} .st-nc{color:var(--nc)} .st-na{color:var(--mut)} .st-m{color:var(--m)}
56789
+ figure { margin:0 0 18px; }
56790
+ figure img { max-width:100%; height:auto; border:1px solid var(--line); }
56791
+ figcaption { color:var(--mut); font-size:13px; margin-top:6px; }
56792
+ details { margin:0 0 14px; border:1px solid var(--line); padding:8px 12px; }
56793
+ summary { cursor:pointer; font-weight:600; }
56794
+ footer.bot { margin-top:48px; padding-top:12px; border-top:1px solid var(--line); color:var(--mut); font-size:13px; }
56795
+ @media print {
56796
+ :root { --bg:#fff; --fg:#000; --line:#bbb; --mut:#444; --soft:#f2f2f2; --c:#0a7d33; --nc:#b3261e; --m:#8a4b00; --accent:#00449e; }
56797
+ .skip, nav.docs { display:none; }
56798
+ .wrap { max-width:none; padding:0; }
56799
+ body { font-size:11pt; }
56800
+ /* One criterion per block, never split across a page break \u2014 the whole point of a
56801
+ printable deliverable is that a finding and its evidence stay on the same sheet. */
56802
+ h2, h3 { break-after:avoid-page; page-break-after:avoid; }
56803
+ figure, table, .note, details { break-inside:avoid-page; page-break-inside:avoid; }
56804
+ details { border:0; padding:0; } details > summary { list-style:none; }
56805
+ /* A collapsed section would print as a title with nothing under it. */
56806
+ details[open], details:not([open]) > *:not(summary) { display:revert; }
56807
+ figure img { max-width:16cm; }
56808
+ a { color:inherit; text-decoration:underline; }
56809
+ }`;
56810
+ function runHtml(r) {
56811
+ let inner = esc2(r.text);
56812
+ if (r.mono) inner = `<code>${inner}</code>`;
56813
+ if (r.strong) inner = `<strong>${inner}</strong>`;
56814
+ if (r.em) inner = `<em>${inner}</em>`;
56815
+ return r.href ? `<a href="${esc2(r.href)}">${inner}</a>` : inner;
56816
+ }
56817
+ var runsHtml = (runs) => runs.map(runHtml).join("");
56818
+ function cellHtml(c2, tag) {
56819
+ const attrs = [];
56820
+ if (tag === "th") attrs.push('scope="row"');
56821
+ if (c2.align === "end") attrs.push('class="end"');
56822
+ if (c2.colspan && c2.colspan > 1) attrs.push(`colspan="${c2.colspan}"`);
56823
+ let inner;
56824
+ if (c2.status) {
56825
+ inner = `<span class="st st-${CLASS[c2.status]}">${esc2(MARK4[c2.status])}</span>`;
56826
+ } else {
56827
+ inner = c2.text === "" ? "\u2014" : runHtml({ text: c2.text, mono: c2.mono, strong: c2.strong, ...c2.href ? { href: c2.href } : {} });
56828
+ }
56829
+ return `<${tag}${attrs.length ? ` ${attrs.join(" ")}` : ""}>${inner}</${tag}>`;
56830
+ }
56831
+ function blockHtml(b) {
56832
+ switch (b.kind) {
56833
+ case "heading":
56834
+ return [`<h${b.level}${b.id ? ` id="${esc2(b.id)}"` : ""}>${esc2(b.text)}</h${b.level}>`];
56835
+ case "para":
56836
+ return [`<p>${runsHtml(b.runs)}</p>`];
56837
+ case "note":
56838
+ return [`<div class="note${b.tone === "warn" ? " warn" : ""}">${runsHtml(b.runs)}</div>`];
56839
+ case "list":
56840
+ return [`<ul class="body">${b.items.map((i2) => `<li>${runsHtml(i2)}</li>`).join("")}</ul>`];
56841
+ case "figure":
56842
+ return [`<figure><img src="${esc2(b.src)}" alt="${esc2(b.alt)}">${b.caption ? `<figcaption>${esc2(b.caption)}</figcaption>` : ""}</figure>`];
56843
+ case "details":
56844
+ return [`<details open><summary>${esc2(b.summary)}</summary>`, ...b.blocks.flatMap(blockHtml), "</details>"];
56845
+ case "table": {
56846
+ const out2 = ['<div class="tw">', "<table>", `<caption>${esc2(b.caption)}</caption>`, "<thead><tr>"];
56847
+ for (const col of b.columns) out2.push(`<th scope="col"${col.align === "end" ? ' class="end"' : ""}>${esc2(col.text)}</th>`);
56848
+ out2.push("</tr></thead><tbody>");
56849
+ for (const row of b.rows) {
56850
+ const group = row.length === 1 && (row[0]?.colspan ?? 0) > 1;
56851
+ if (group) {
56852
+ const c2 = row[0];
56853
+ out2.push(`<tr class="group"><th scope="colgroup" colspan="${c2.colspan}">${esc2(c2.text)}</th></tr>`);
56854
+ continue;
56855
+ }
56856
+ out2.push(`<tr>${row.map((c2, i2) => cellHtml(c2, i2 === 0 || c2.header ? "th" : "td")).join("")}</tr>`);
56857
+ }
56858
+ out2.push("</tbody></table>", "</div>");
56859
+ return out2;
56860
+ }
56861
+ }
56862
+ }
56863
+ function renderHtmlDocument(doc) {
56864
+ const t2 = T3[doc.lang];
56865
+ const out2 = [];
56866
+ out2.push(`<!doctype html>`, `<html lang="${doc.lang}">`, "<head>");
56867
+ out2.push('<meta charset="utf-8">', '<meta name="viewport" content="width=device-width, initial-scale=1">');
56868
+ out2.push(`<title>${esc2(doc.title)}</title>`);
56869
+ out2.push(`<style>${STYLE}</style>`);
56870
+ out2.push("</head>", "<body>");
56871
+ out2.push(`<a class="skip" href="#content">${esc2(t2.skip)}</a>`);
56872
+ out2.push('<header class="top"><div class="wrap">');
56873
+ out2.push(`<h1>${esc2(doc.title)}</h1>`);
56874
+ if (doc.subtitle?.length) out2.push(`<p class="sub">${runsHtml(doc.subtitle)}</p>`);
56875
+ if (doc.nav?.length) {
56876
+ out2.push(`<nav class="docs" aria-label="${esc2(t2.nav)}"><ul>`);
56877
+ for (const l of doc.nav) out2.push(`<li><a href="${esc2(l.href)}"${l.current ? ' aria-current="page"' : ""}>${esc2(l.text)}</a></li>`);
56878
+ out2.push("</ul></nav>");
56879
+ }
56880
+ out2.push("</div></header>");
56881
+ out2.push('<main id="content" class="wrap">');
56882
+ for (const b of doc.blocks) out2.push(...blockHtml(b));
56883
+ out2.push("</main>");
56884
+ out2.push(`<footer class="bot"><div class="wrap"><p>${esc2(t2.generated)} \xB7 ${esc2(t2.printHint)}</p></div></footer>`);
56885
+ out2.push("</body>", "</html>");
56886
+ return `${out2.join("\n")}
56887
+ `;
56888
+ }
56889
+
56890
+ // src/html-report.ts
56891
+ var T4 = {
56892
+ fr: {
56893
+ reportTitle: "Rapport de conformit\xE9",
56894
+ indexTitle: "Rapport d'accessibilit\xE9",
56895
+ pagesTitle: "Rapport page par page",
56896
+ date: "Date",
56897
+ files: "fichiers",
56898
+ rate: "r\xE9ussite automatique",
56899
+ synthesis: "Synth\xE8se",
56900
+ synthCaption: (h) => `Nombre de crit\xE8res par statut, regroup\xE9s par ${h}.`,
56901
+ byGuideline: "r\xE8gle WCAG",
56902
+ byTheme: "th\xE9matique",
56903
+ total: "Total",
56904
+ conforming: "C",
56905
+ nonConforming: "NC",
56906
+ notApplicable: "NA",
56907
+ toAssess: "\xC0 \xE9valuer",
56908
+ group: "Groupe",
56909
+ 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.`,
56910
+ ncTitle: "Non-conformit\xE9s",
56911
+ ncCaption: "Une entr\xE9e par crit\xE8re non conforme.",
56912
+ noNc: "Aucune non-conformit\xE9 relev\xE9e par le moteur statique. Les crit\xE8res \xAB \xE0 \xE9valuer \xBB restent \xE0 trancher.",
56913
+ recTitle: "Recommandations (non normatives)",
56914
+ recNote: "Bonnes pratiques sans test normatif en \xE9chec : elles ne rendent aucun crit\xE8re non conforme et n'entrent pas dans le taux.",
56915
+ occurrences: "Occurrences",
56916
+ occCaption: (id) => `Occurrences du crit\xE8re ${id}.`,
56917
+ where: "Emplacement",
56918
+ selector: "S\xE9lecteur",
56919
+ what: "Constat",
56920
+ evidence: "Preuve",
56921
+ perPage: "Bilan page par page",
56922
+ perPageCaption: "Taux et constats par page, avec la base sur laquelle chaque page a \xE9t\xE9 jug\xE9e.",
56923
+ page: "Page",
56924
+ url: "URL",
56925
+ basis: "Base",
56926
+ pageRate: "Taux",
56927
+ sheet: "Fiche",
56928
+ grid: "Grille des crit\xE8res",
56929
+ gridCaption: (n) => `Statut de chaque crit\xE8re pour la page ${n}.`,
56930
+ crossGridCaption: "Statut de chaque crit\xE8re, page par page.",
56931
+ criterion: "Crit\xE8re",
56932
+ status: "Statut",
56933
+ legend: "`C` conforme \xB7 `NC` non conforme \xB7 `\u2014` non applicable \xB7 `?` \xE0 \xE9valuer.",
56934
+ tally: (c2, nc, na, m) => `${c2} conforme(s) \xB7 ${nc} non conforme(s) \xB7 ${na} non applicable(s) \xB7 ${m} \xE0 \xE9valuer`,
56935
+ 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`.",
56936
+ documents: "Documents",
56937
+ openComposite: "Rapport complet, en un seul fichier (imprimable en PDF)",
56938
+ openPages: "Rapport page par page",
56939
+ noScreenshot: "Aucune capture d'\xE9cran pour cette page.",
56940
+ screenshotAlt: (n) => `Capture d'\xE9cran de la page ${n}`,
56941
+ notImaged: (n) => `${n} occurrence(s) n'ont pas pu \xEAtre illustr\xE9es.`
56942
+ },
56943
+ en: {
56944
+ reportTitle: "Conformance report",
56945
+ indexTitle: "Accessibility report",
56946
+ pagesTitle: "Page-by-page report",
56947
+ date: "Date",
56948
+ files: "files",
56949
+ rate: "automatic pass rate",
56950
+ synthesis: "Synthesis",
56951
+ synthCaption: (h) => `Criteria count per status, grouped by ${h}.`,
56952
+ byGuideline: "WCAG guideline",
56953
+ byTheme: "theme",
56954
+ total: "Total",
56955
+ conforming: "C",
56956
+ nonConforming: "NC",
56957
+ notApplicable: "NA",
56958
+ toAssess: "To assess",
56959
+ group: "Group",
56960
+ coverage: (d, t2) => `Coverage: ${d}/${t2} criteria decided. The rate covers only those and says nothing about the other ${t2 - d}.`,
56961
+ ncTitle: "Non-conformities",
56962
+ ncCaption: "One entry per non-conforming criterion.",
56963
+ noNc: "No non-conformity found by the static engine. The criteria left to assess are still open.",
56964
+ recTitle: "Recommendations (non-normative)",
56965
+ recNote: "Good practices with no failing normative test: they never make a criterion non-conforming and do not enter the rate.",
56966
+ occurrences: "Occurrences",
56967
+ occCaption: (id) => `Occurrences of criterion ${id}.`,
56968
+ where: "Location",
56969
+ selector: "Selector",
56970
+ what: "Finding",
56971
+ evidence: "Evidence",
56972
+ perPage: "Page-by-page scoreboard",
56973
+ perPageCaption: "Rate and findings per page, with the basis each page was judged on.",
56974
+ page: "Page",
56975
+ url: "URL",
56976
+ basis: "Basis",
56977
+ pageRate: "Rate",
56978
+ sheet: "Sheet",
56979
+ grid: "Criteria grid",
56980
+ gridCaption: (n) => `Each criterion's status for the ${n} page.`,
56981
+ crossGridCaption: "Each criterion's status, page by page.",
56982
+ criterion: "Criterion",
56983
+ status: "Status",
56984
+ legend: "`C` conforming \xB7 `NC` non-conforming \xB7 `\u2014` not applicable \xB7 `?` to assess.",
56985
+ tally: (c2, nc, na, m) => `${c2} conforming \xB7 ${nc} non-conforming \xB7 ${na} not applicable \xB7 ${m} to assess`,
56986
+ 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`.',
56987
+ documents: "Documents",
56988
+ openComposite: "Full report, in a single file (printable to PDF)",
56989
+ openPages: "Page-by-page report",
56990
+ noScreenshot: "No screenshot for this page.",
56991
+ screenshotAlt: (n) => `Screenshot of the ${n} page`,
56992
+ notImaged: (n) => `${n} occurrence(s) could not be illustrated.`
56993
+ }
56994
+ };
56995
+ function ticks(text) {
56996
+ return text.split(/`([^`]+)`/).flatMap((part, i2) => part === "" ? [] : i2 % 2 === 1 ? [{ text: part, mono: true }] : [{ text: part }]);
56997
+ }
56998
+ var stdName = (standard) => isCore(standard) ? "WCAG 2.2 AA" : loadPack(standard).name;
56999
+ function headline(result, standard, lang) {
57000
+ const t2 = T4[lang];
57001
+ const groups = isCore(standard) ? reportGroups(result, lang) : packReportGroups(result, loadPack(standard), lang);
57002
+ const { decided, total } = reportCoverage(groups);
57003
+ const agentRuled = groups.some((g) => g.rows.some((r) => r.decidedBy === "agent" && r.status === "C"));
57004
+ return {
57005
+ runs: [
57006
+ { text: result.date, mono: true },
57007
+ { text: ` \xB7 ${result.scope.files} ${t2.files} \xB7 ` },
57008
+ { text: `${formatRate(decided === 0 ? null : result.conformancePct, decided, total)}${agentRuled ? "*" : ""}`, strong: true },
57009
+ { text: ` ${t2.rate}` }
57010
+ ],
57011
+ agentRuled,
57012
+ decided,
57013
+ total
57014
+ };
57015
+ }
57016
+ function synthesisBlocks(result, standard, lang) {
57017
+ const t2 = T4[lang];
57018
+ const core = isCore(standard);
57019
+ const groups = core ? reportGroups(result, lang) : packReportGroups(result, loadPack(standard), lang);
57020
+ const tot = reportTotals(groups);
57021
+ const { decided, total } = reportCoverage(groups);
57022
+ const rows = groups.map((g) => {
57023
+ const x = tallyRows(g.rows);
57024
+ return [
57025
+ { text: `${g.key} ${g.title}`.trim() },
57026
+ { text: String(x.c), align: "end" },
57027
+ { text: String(x.nc), align: "end" },
57028
+ { text: String(x.na), align: "end" },
57029
+ { text: String(x.manual), align: "end" }
57030
+ ];
57031
+ });
57032
+ rows.push([
57033
+ { text: t2.total, strong: true },
57034
+ { text: String(tot.c), align: "end", strong: true },
57035
+ { text: String(tot.nc), align: "end", strong: true },
57036
+ { text: String(tot.na), align: "end", strong: true },
57037
+ { text: String(tot.manual), align: "end", strong: true }
57038
+ ]);
57039
+ return [
57040
+ { kind: "heading", level: 2, text: t2.synthesis, id: "synthesis" },
57041
+ {
57042
+ kind: "table",
57043
+ caption: t2.synthCaption(core ? t2.byGuideline : t2.byTheme),
57044
+ columns: [
57045
+ { text: t2.group },
57046
+ { text: t2.conforming, align: "end" },
57047
+ { text: t2.nonConforming, align: "end" },
57048
+ { text: t2.notApplicable, align: "end" },
57049
+ { text: t2.toAssess, align: "end" }
57050
+ ],
57051
+ rows
57052
+ },
57053
+ { kind: "note", tone: "warn", runs: ticks(t2.coverage(decided, total)) },
57054
+ { kind: "note", tone: "warn", runs: ticks(t2.manualWarn) }
57055
+ ];
57056
+ }
57057
+ function criterionBlocks(unit, standard, lang, level, crops) {
57058
+ const t2 = T4[lang];
57059
+ const m = auditorUnitModel(unit, standard, lang, { collapse: true });
57060
+ const out2 = [{ kind: "heading", level, text: `${m.icon} ${m.label}`, id: `c-${unit.criteriaId}` }];
57061
+ out2.push({ kind: "note", tone: "info", runs: ticks(m.normativeNote) });
57062
+ out2.push({ kind: "list", items: m.fields.map((f) => [{ text: `${f.label} : `, strong: true }, ...ticks(f.value)]) });
57063
+ out2.push({
57064
+ kind: "para",
57065
+ runs: [{ text: `${t2.what} (${m.conformanceTerms.nonConformant}) : `, strong: true }, { text: `${m.occurrences} \u2014 ${m.messages.join(" ; ")}` }]
57066
+ });
57067
+ if (m.fixes.length) out2.push({ kind: "para", runs: [{ text: `${m.conformanceTerms.conformant} : `, strong: true }, { text: m.fixes.join(" ; ") }] });
57068
+ const hasEvidence = crops ? m.normative.some((f) => crops(f)) : false;
57069
+ const columns = [{ text: t2.where }, { text: t2.selector }, { text: t2.what }, ...hasEvidence ? [{ text: t2.evidence }] : []];
57070
+ const rows = m.normative.map((f) => {
57071
+ const cells = [{ text: `${f.file}:${f.line}`, mono: true }, { text: f.selectorHint, mono: true }, { text: resolveOccurrence(f, lang) }];
57072
+ if (hasEvidence) cells.push({ text: crops?.(f) ? "\u25A3" : "" });
57073
+ return cells;
57074
+ });
57075
+ if (rows.length) out2.push({ kind: "table", caption: t2.occCaption(unit.criteriaId), columns, rows });
57076
+ if (crops) {
57077
+ for (const f of m.normative) {
57078
+ const c2 = crops(f);
57079
+ if (c2) out2.push({ kind: "figure", src: c2.href, alt: c2.alt, caption: `${f.file}:${f.line} \u2014 ${f.selectorHint}` });
57080
+ }
57081
+ }
57082
+ return out2;
57083
+ }
57084
+ function resolveOccurrence(f, lang) {
57085
+ return f.msg ? auditorUnitModel({ criteriaId: f.criteriaId, title: "", label: "", refs: [], severity: f.severity, findings: [f] }, CORE2, lang).messages[0] ?? f.message : f.message;
57086
+ }
57087
+ function findingsBlocks(result, standard, lang, level, crops) {
57088
+ const t2 = T4[lang];
57089
+ const { nc, advisory } = partitionUnits(prdUnits(result, standard, lang));
57090
+ const out2 = [{ kind: "heading", level: 2, text: t2.ncTitle, id: "nc" }];
57091
+ if (!nc.length) out2.push({ kind: "para", runs: [{ text: t2.noNc }] });
57092
+ for (const u of nc) out2.push(...criterionBlocks(u, standard, lang, level === 2 ? 3 : 4, crops));
57093
+ if (advisory.length) {
57094
+ out2.push({ kind: "heading", level: 2, text: t2.recTitle, id: "rec" });
57095
+ out2.push({ kind: "note", tone: "info", runs: ticks(t2.recNote) });
57096
+ for (const u of advisory) out2.push(...criterionBlocks(u, standard, lang, level === 2 ? 3 : 4, crops));
57097
+ }
57098
+ return out2;
57099
+ }
57100
+ function scoreboardBlocks(result, standard, lang, sheetHref) {
57101
+ const t2 = T4[lang];
57102
+ const scope = pagesOf(result);
57103
+ if (!scope.length) return [];
57104
+ const derived = derivePages(result, scope);
57105
+ const rows = derived.map((p) => [
57106
+ { text: `${p.name}${p.auth ? " \u{1F512}" : ""}`, ...sheetHref ? { href: sheetHref(p.id) } : {} },
57107
+ { text: p.url, mono: true },
57108
+ { text: basisLabel(p.basis, lang) },
57109
+ { text: formatRate(p.conformancePct, p.decided, p.total), align: "end" }
57110
+ ]);
57111
+ const out2 = [
57112
+ { kind: "heading", level: 2, text: t2.perPage, id: "pages" },
57113
+ {
57114
+ kind: "table",
57115
+ caption: t2.perPageCaption,
57116
+ columns: [{ text: t2.page }, { text: t2.url }, { text: t2.basis }, { text: t2.pageRate, align: "end" }],
57117
+ rows
57118
+ }
57119
+ ];
57120
+ const orphans = unattributedFindings(result).filter((f) => !f.advisory).length;
57121
+ if (orphans) out2.push({ kind: "note", tone: "warn", runs: ticks(unattributedNote(orphans, lang)) });
57122
+ for (const basis of ["attributed", "not-audited"]) {
57123
+ const note = derived.some((p) => p.basis === basis) ? pageBasisWarning(basis, lang) : void 0;
57124
+ if (note) out2.push({ kind: "note", tone: "warn", runs: ticks(note) });
57125
+ }
57126
+ return out2;
57127
+ }
57128
+ function crossGridBlocks(result, derived, standard, lang) {
57129
+ const t2 = T4[lang];
57130
+ if (!derived.length) return [];
57131
+ const { rows, status } = pageGridModel(result, derived, standard, lang);
57132
+ if (!rows.length) return [];
57133
+ const table = {
57134
+ kind: "table",
57135
+ caption: t2.crossGridCaption,
57136
+ columns: [{ text: t2.criterion }, ...derived.map((p) => ({ text: `${p.name}${p.auth ? " \u{1F512}" : ""}` }))],
57137
+ rows: []
57138
+ };
57139
+ let group = "";
57140
+ for (const row of rows) {
57141
+ if (row.group !== group) {
57142
+ group = row.group;
57143
+ table.rows.push([{ text: group, colspan: derived.length + 1 }]);
57144
+ }
57145
+ table.rows.push([{ text: row.label }, ...derived.map((p) => ({ status: status.get(row.id)?.get(p.id) ?? "manual", text: "" }))]);
57146
+ }
57147
+ return [{ kind: "heading", level: 2, text: t2.grid, id: "grid" }, { kind: "note", tone: "info", runs: ticks(t2.legend) }, table];
57148
+ }
57149
+ function pageGridBlocks(result, page, standard, lang) {
57150
+ const t2 = T4[lang];
57151
+ const rows = pageCriterionRows(result, page, standard, lang);
57152
+ if (!rows.length) return [];
57153
+ const cov = pageCoverage(rows);
57154
+ const tally = pageTally(rows);
57155
+ const table = { kind: "table", caption: t2.gridCaption(page.name), columns: [{ text: t2.criterion }, { text: t2.status }], rows: [] };
57156
+ let group = "";
57157
+ for (const row of rows) {
57158
+ if (row.group !== group) {
57159
+ group = row.group;
57160
+ table.rows.push([{ text: group, colspan: 2 }]);
57161
+ }
57162
+ table.rows.push([{ text: row.label }, { status: row.status, text: row.decidedBy === "agent" && row.status === "C" ? "*" : "" }]);
57163
+ }
57164
+ const out2 = [
57165
+ { kind: "heading", level: 2, text: t2.grid },
57166
+ {
57167
+ kind: "para",
57168
+ runs: [
57169
+ { text: `${t2.pageRate} : `, strong: true },
57170
+ { text: formatRate(pageRatePct(rows), cov.decided, cov.total), strong: true }
57171
+ ]
57172
+ },
57173
+ { kind: "para", runs: [{ text: t2.tally(tally.c, tally.nc, tally.na, tally.manual) }] },
57174
+ { kind: "note", tone: "info", runs: ticks(t2.legend) },
57175
+ table
57176
+ ];
57177
+ if (rows.some((r) => r.decidedBy === "agent" && r.status === "C")) out2.push({ kind: "note", tone: "warn", runs: ticks(agentMarkNote(lang)) });
57178
+ return out2;
57179
+ }
57180
+ function indexDoc(result, opts = {}) {
57181
+ const standard = opts.standard ?? CORE2;
57182
+ const lang = opts.lang ?? "en";
57183
+ const t2 = T4[lang];
57184
+ const h = headline(result, standard, lang);
57185
+ const blocks = [];
57186
+ if (opts.links?.length) {
57187
+ blocks.push({ kind: "heading", level: 2, text: t2.documents, id: "documents" });
57188
+ blocks.push({ kind: "list", items: opts.links.map((l) => [{ text: l.text, href: l.href }]) });
57189
+ }
57190
+ blocks.push(...synthesisBlocks(result, standard, lang));
57191
+ if (h.agentRuled) blocks.push({ kind: "note", tone: "warn", runs: ticks(agentMarkNote(lang)) });
57192
+ blocks.push(...scoreboardBlocks(result, standard, lang));
57193
+ return { lang, title: `${t2.indexTitle} \u2014 ${stdName(standard)}`, subtitle: h.runs, ...opts.nav ? { nav: opts.nav } : {}, blocks };
57194
+ }
57195
+ function compositeDoc(result, opts = {}) {
57196
+ const standard = opts.standard ?? CORE2;
57197
+ const lang = opts.lang ?? "en";
57198
+ const t2 = T4[lang];
57199
+ const h = headline(result, standard, lang);
57200
+ const blocks = [...synthesisBlocks(result, standard, lang)];
57201
+ if (h.agentRuled) blocks.push({ kind: "note", tone: "warn", runs: ticks(agentMarkNote(lang)) });
57202
+ blocks.push(...findingsBlocks(result, standard, lang, 2, opts.crops));
57203
+ blocks.push(...scoreboardBlocks(result, standard, lang));
57204
+ const scope = pagesOf(result);
57205
+ if (scope.length) blocks.push(...crossGridBlocks(result, derivePages(result, scope), standard, lang));
57206
+ return { lang, title: `${t2.reportTitle} ${standardLabel(standard)} \u2014 ${result.date}`, subtitle: h.runs, ...opts.nav ? { nav: opts.nav } : {}, blocks };
57207
+ }
57208
+ function pagesIndexDoc(result, opts = { sheetHref: (id) => `./page-${id}.html` }) {
57209
+ const standard = opts.standard ?? CORE2;
57210
+ const lang = opts.lang ?? "en";
57211
+ const t2 = T4[lang];
57212
+ const h = headline(result, standard, lang);
57213
+ const scope = pagesOf(result);
57214
+ const blocks = [...scoreboardBlocks(result, standard, lang, opts.sheetHref)];
57215
+ if (scope.length) blocks.push(...crossGridBlocks(result, derivePages(result, scope), standard, lang));
57216
+ return { lang, title: `${t2.pagesTitle} \u2014 ${stdName(standard)}`, subtitle: h.runs, ...opts.nav ? { nav: opts.nav } : {}, blocks };
57217
+ }
57218
+ function pageDoc(result, page, opts = {}) {
57219
+ const standard = opts.standard ?? CORE2;
57220
+ const lang = opts.lang ?? "en";
57221
+ const t2 = T4[lang];
57222
+ const blocks = [
57223
+ {
57224
+ kind: "list",
57225
+ items: [
57226
+ [
57227
+ { text: `${t2.url} : `, strong: true },
57228
+ { text: page.url, mono: true }
57229
+ ],
57230
+ [{ text: `${t2.basis} : `, strong: true }, { text: basisLabel(page.basis, lang) }]
57231
+ ]
57232
+ }
57233
+ ];
57234
+ const warn = pageBasisWarning(page.basis, lang);
57235
+ if (warn) blocks.push({ kind: "note", tone: "warn", runs: ticks(warn) });
57236
+ blocks.push(
57237
+ opts.screenshot ? { kind: "figure", src: opts.screenshot, alt: t2.screenshotAlt(page.name) } : { kind: "para", runs: [{ text: t2.noScreenshot, em: true }] }
57238
+ );
57239
+ blocks.push(...pageGridBlocks(result, page, standard, lang));
57240
+ const view = { ...result, criteria: page.criteria, findings: page.findings };
57241
+ const { nc, advisory } = partitionUnits(prdUnits(view, standard, lang));
57242
+ blocks.push({ kind: "heading", level: 2, text: t2.ncTitle });
57243
+ if (!nc.length) blocks.push({ kind: "para", runs: [{ text: t2.noNc }] });
57244
+ for (const u of nc) blocks.push(...criterionBlocks(u, standard, lang, 3, opts.crops));
57245
+ if (advisory.length) {
57246
+ blocks.push({ kind: "heading", level: 2, text: t2.recTitle });
57247
+ blocks.push({ kind: "note", tone: "info", runs: ticks(t2.recNote) });
57248
+ for (const u of advisory) blocks.push(...criterionBlocks(u, standard, lang, 3, opts.crops));
57249
+ }
57250
+ return {
57251
+ lang,
57252
+ title: `${page.name} \u2014 ${stdName(standard)}`,
57253
+ subtitle: [{ text: result.date, mono: true }, { text: ` \xB7 ${findingsForStandard(view, standard).length} ${t2.what.toLowerCase()}` }],
57254
+ ...opts.nav ? { nav: opts.nav } : {},
57255
+ blocks
57256
+ };
57257
+ }
57258
+
57259
+ // src/html-emit.ts
57260
+ var DEFAULT_INLINE_BUDGET = 12 * 1024 * 1024;
57261
+ var T5 = {
57262
+ fr: {
57263
+ 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.`,
57264
+ 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.`,
57265
+ 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.`,
57266
+ docTitle: "Rapport complet, en un seul fichier (imprimable en PDF)",
57267
+ pagesTitle: "Rapport page par page",
57268
+ indexTitle: "Tableau de bord"
57269
+ },
57270
+ en: {
57271
+ 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.`,
57272
+ 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.`,
57273
+ 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.`,
57274
+ docTitle: "Full report, in a single file (printable to PDF)",
57275
+ pagesTitle: "Page-by-page report",
57276
+ indexTitle: "Dashboard"
57277
+ }
57278
+ };
57279
+ var mb = (n) => (n / (1024 * 1024)).toFixed(1);
57280
+ function inlineSize(path) {
57281
+ try {
57282
+ return Math.ceil(statSync11(path).size / 3) * 4;
57283
+ } catch {
57284
+ return 0;
57285
+ }
57286
+ }
57287
+ function dataUri(path) {
57288
+ try {
57289
+ return `data:image/png;base64,${readFileSync24(path).toString("base64")}`;
57290
+ } catch {
57291
+ return void 0;
57292
+ }
57293
+ }
57294
+ function cropLookup(m, lang, hrefOf) {
57295
+ if (!m) return () => void 0;
57296
+ return (f) => {
57297
+ const c2 = m.crops.get(findingId(f));
57298
+ if (!c2) return void 0;
57299
+ const href = hrefOf(c2.path, c2.href);
57300
+ return href ? { href, alt: c2.alt[lang] } : void 0;
57301
+ };
57302
+ }
57303
+ function pickRung(cropBytes, shotBytes, budget) {
57304
+ const sum = (xs) => xs.reduce((a, b) => a + b, 0);
57305
+ if (sum(cropBytes) + sum(shotBytes) <= budget) return { steps: [], cropsPerCriterion: Number.POSITIVE_INFINITY, shots: true };
57306
+ if (sum(cropBytes) <= budget) return { steps: ["screenshots"], cropsPerCriterion: Number.POSITIVE_INFINITY, shots: false };
57307
+ const smallest = [...cropBytes].sort((a, b) => a - b);
57308
+ let kept = 0;
57309
+ let total = 0;
57310
+ for (const b of smallest) {
57311
+ if (total + b > budget) break;
57312
+ total += b;
57313
+ kept++;
57314
+ }
57315
+ if (!kept) return { steps: ["screenshots", "crops", "none"], cropsPerCriterion: 0, shots: false };
57316
+ return { steps: ["screenshots", "crops"], cropsPerCriterion: 1, shots: false };
57317
+ }
57318
+ function writeHtml(result, opts) {
57319
+ const standard = opts.standard ?? CORE2;
57320
+ const lang = opts.lang ?? "en";
57321
+ const t2 = T5[lang];
57322
+ const budget = opts.inlineBudget ?? DEFAULT_INLINE_BUDGET;
57323
+ const stdKey = isCore(standard) ? "wcag" : loadPack(standard).key;
57324
+ mkdirSync14(opts.outDir, { recursive: true });
57325
+ const crops = [...opts.evidence?.crops.values() ?? []];
57326
+ const shots = [...opts.screenshots?.values() ?? []];
57327
+ const rung = pickRung(
57328
+ crops.map((c2) => inlineSize(c2.path)),
57329
+ shots.map(inlineSize),
57330
+ budget
57331
+ );
57332
+ const notices = [];
57333
+ if (rung.steps.includes("none")) notices.push(t2.overNone(mb(budget)));
57334
+ else if (rung.steps.includes("crops")) notices.push(t2.overCrops(mb(crops.reduce((a, c2) => a + inlineSize(c2.path), 0)), mb(budget)));
57335
+ else if (rung.steps.includes("screenshots"))
57336
+ notices.push(t2.overComposite(mb(crops.reduce((a, c2) => a + inlineSize(c2.path), 0) + shots.reduce((a, s) => a + inlineSize(s), 0)), mb(budget)));
57337
+ let inlinedBytes = 0;
57338
+ const perCriterion = /* @__PURE__ */ new Map();
57339
+ const inlineCrops = cropLookup(opts.evidence, lang, (path) => {
57340
+ if (rung.cropsPerCriterion === 0) return void 0;
57341
+ const uri = dataUri(path);
57342
+ if (!uri) return void 0;
57343
+ inlinedBytes += uri.length;
57344
+ return uri;
57345
+ });
57346
+ const budgetedCrops = (f) => {
57347
+ const c2 = opts.evidence?.crops.get(findingId(f));
57348
+ if (!c2) return void 0;
57349
+ const n = perCriterion.get(c2.criteriaId) ?? 0;
57350
+ if (n >= rung.cropsPerCriterion) return void 0;
57351
+ const drawn = inlineCrops(f);
57352
+ if (drawn) perCriterion.set(c2.criteriaId, n + 1);
57353
+ return drawn;
57354
+ };
57355
+ const flat = opts.layout === "pages";
57356
+ const sheetDir = flat ? opts.outDir : join41(opts.outDir, "pages");
57357
+ const up = flat ? "./" : "../";
57358
+ const compositeName = `ultra11y-${stdKey}-${result.date}.html`;
57359
+ const nav = flat ? [{ href: "./index.html", text: t2.pagesTitle }] : [
57360
+ { href: "./index.html", text: t2.indexTitle },
57361
+ { href: `./${compositeName}`, text: t2.docTitle },
57362
+ ...opts.pages ? [{ href: "./pages/index.html", text: t2.pagesTitle }] : []
57363
+ ];
57364
+ let compositePath;
57365
+ if (!flat) {
57366
+ const composite = compositeDoc(result, { standard, lang, crops: budgetedCrops, nav: nav.map((n) => ({ ...n, current: n.href === `./${compositeName}` })) });
57367
+ if (notices.length) composite.blocks.unshift({ kind: "note", tone: "warn", runs: noticeRuns(notices) });
57368
+ compositePath = join41(opts.outDir, compositeName);
57369
+ writeFileSync16(compositePath, renderHtmlDocument(composite));
57370
+ }
57371
+ const fileCrops = cropLookup(opts.evidence, lang, (_p, href) => href.replace(/^\.\//, up));
57372
+ const indexNav = nav.map((n) => ({ ...n, current: n.href === "./index.html" }));
57373
+ 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") });
57374
+ const indexPath = join41(opts.outDir, "index.html");
57375
+ writeFileSync16(indexPath, renderHtmlDocument(index));
57376
+ const sheets = [];
57377
+ if (opts.pages) {
57378
+ const derived = derivePages(result, pagesOf(result));
57379
+ mkdirSync14(sheetDir, { recursive: true });
57380
+ const sheetNav = flat ? [{ href: "./index.html", text: t2.pagesTitle }] : [
57381
+ { href: `${up}index.html`, text: t2.indexTitle },
57382
+ { href: `${up}${compositeName}`, text: t2.docTitle },
57383
+ { href: "./index.html", text: t2.pagesTitle }
57384
+ ];
57385
+ if (!flat) {
57386
+ writeFileSync16(
57387
+ join41(sheetDir, "index.html"),
57388
+ renderHtmlDocument(
57389
+ pagesIndexDoc(result, {
57390
+ standard,
57391
+ lang,
57392
+ nav: sheetNav.map((n) => ({ ...n, current: n.href === "./index.html" })),
57393
+ sheetHref: (id) => `./page-${id}.html`
57394
+ })
57395
+ )
57396
+ );
57397
+ sheets.push(join41(sheetDir, "index.html"));
57398
+ }
57399
+ for (const p of derived) {
57400
+ const doc = pageDoc(result, p, {
57401
+ standard,
57402
+ lang,
57403
+ crops: fileCrops,
57404
+ nav: sheetNav,
57405
+ // Where `cmdPages`' screenshot copier already put the capture: `assets/<id>.png`,
57406
+ // beside the entry point, seen from wherever this sheet sits.
57407
+ ...opts.screenshots?.has(p.id) ? { screenshot: `${up}assets/${p.id}.png` } : {}
57408
+ });
57409
+ const path = join41(sheetDir, `page-${p.id}.html`);
57410
+ writeFileSync16(path, renderHtmlDocument(doc));
57411
+ sheets.push(path);
57412
+ }
57413
+ }
57414
+ return {
57415
+ index: indexPath,
57416
+ ...compositePath ? { composite: compositePath } : {},
57417
+ sheets,
57418
+ inlinedBytes,
57419
+ degraded: rung.steps,
57420
+ imagesDropped: rung.steps.includes("none"),
57421
+ notices
57422
+ };
57423
+ }
57424
+ function noticeRuns(notices) {
57425
+ return notices.flatMap((n, i2) => i2 ? [{ text: " " }, { text: n }] : [{ text: n }]);
57426
+ }
57427
+
56189
57428
  // src/dev.ts
56190
57429
  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";
57430
+ import { mkdirSync as mkdirSync15, readFileSync as readFileSync25, writeFileSync as writeFileSync17 } from "fs";
57431
+ import { dirname as dirname12, join as join42 } from "path";
56193
57432
  var DEV_DEFAULT_PORT = 4111;
56194
57433
  function criterionLabel3(f, standard) {
56195
57434
  if (isCore(standard)) return `WCAG ${f.criteriaId}`;
@@ -56367,71 +57606,19 @@ export function Ultra11yOverlay({ endpoint = "http://127.0.0.1:${port}" }) {
56367
57606
  export default Ultra11yOverlay;
56368
57607
  `;
56369
57608
  }
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
57609
  function dashboardHtml(result, pages, standard, lang) {
56376
57610
  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>`;
57611
+ const title2 = `ultra11y \u2014 ${isCore(standard) ? "WCAG 2.2 AA" : loadPack(standard).name}`;
56395
57612
  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>`);
57613
+ 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.";
57614
+ return renderHtmlDocument({ lang, title: title2, blocks: [{ kind: "para", runs: [{ text: empty }] }] });
56432
57615
  }
56433
- out2.push("</tbody></table></body></html>");
56434
- return out2.join("\n");
57616
+ return renderHtmlDocument({
57617
+ lang,
57618
+ title: title2,
57619
+ subtitle: [{ text: `${pages.length} ${fr ? "page(s) captur\xE9e(s)" : "page(s) captured"} \xB7 ` }, { text: result.date, mono: true }],
57620
+ blocks: [...scoreboardBlocks(result, standard, lang), ...crossGridBlocks(result, pages, standard, lang)]
57621
+ });
56435
57622
  }
56436
57623
  function cors(res) {
56437
57624
  res.setHeader("access-control-allow-origin", "*");
@@ -56453,21 +57640,23 @@ function auditCollected(root, payload) {
56453
57640
  const v = validateSnapshotMeta(payload.meta);
56454
57641
  if (!v.ok || !v.meta) return { ok: false, error: v.issues.map((i2) => `${i2.path}: ${i2.message}`).join("; ") };
56455
57642
  if (typeof payload.dom !== "string" || !payload.dom.trim()) return { ok: false, error: "dom is required" };
57643
+ const shot = typeof payload.screenshot === "string" && payload.screenshot.trim() ? payload.screenshot : void 0;
56456
57644
  const dir = writeSnapshot(root, {
56457
57645
  meta: v.meta,
56458
57646
  dom: payload.dom,
56459
57647
  ...payload.styles ? { styles: payload.styles } : {},
56460
57648
  ...payload.boxes ? { boxes: payload.boxes } : {},
56461
57649
  ...payload.axtree ? { axtree: payload.axtree } : {},
56462
- ...payload.css ? { css: payload.css } : {}
57650
+ ...payload.css ? { css: payload.css } : {},
57651
+ ...shot ? { screenshotBase64: shot } : {}
56463
57652
  });
56464
- return { ok: true, result: runAudit({ inputs: [join40(dir, "dom.html")] }) };
57653
+ return { ok: true, result: runAudit({ inputs: [join42(dir, "dom.html")] }) };
56465
57654
  }
56466
57655
  function projectPages(root) {
56467
57656
  const snaps = readSnapshots(root);
56468
57657
  if (!snaps.length) return { result: null, pages: [] };
56469
57658
  const scope = pageScopesFrom(snaps);
56470
- const result = runAudit({ inputs: [join40(root, ".ultra11y/pages")] });
57659
+ const result = runAudit({ inputs: [join42(root, ".ultra11y/pages")] });
56471
57660
  result.scope.pages = scope;
56472
57661
  attributePages(result, scope);
56473
57662
  foldRecordedAdjudication(root, result);
@@ -56481,7 +57670,7 @@ function judgePages(result) {
56481
57670
  function foldRecordedAdjudication(root, fresh) {
56482
57671
  let prior;
56483
57672
  try {
56484
- prior = JSON.parse(readFileSync23(join40(root, "audits", "audit-latest.json"), "utf8"));
57673
+ prior = JSON.parse(readFileSync25(join42(root, "audits", "audit-latest.json"), "utf8"));
56485
57674
  } catch {
56486
57675
  return;
56487
57676
  }
@@ -56596,9 +57785,9 @@ function startDevServer(opts) {
56596
57785
  });
56597
57786
  let auditPath;
56598
57787
  if (applied.ok) {
56599
- auditPath = join40(opts.root, "audits", "audit-latest.json");
56600
- mkdirSync13(dirname12(auditPath), { recursive: true });
56601
- writeFileSync15(auditPath, `${JSON.stringify(applied.audit, null, 2)}
57788
+ auditPath = join42(opts.root, "audits", "audit-latest.json");
57789
+ mkdirSync15(dirname12(auditPath), { recursive: true });
57790
+ writeFileSync17(auditPath, `${JSON.stringify(applied.audit, null, 2)}
56602
57791
  `);
56603
57792
  }
56604
57793
  res.writeHead(200, { "content-type": "application/json" }).end(
@@ -56912,11 +58101,11 @@ function e2eSetupPlan(runners, paths, lang = "en") {
56912
58101
  }
56913
58102
 
56914
58103
  // src/orchestrate.ts
56915
- import { existsSync as existsSync28, mkdirSync as mkdirSync14, readFileSync as readFileSync24, rmSync as rmSync7, writeFileSync as writeFileSync16 } from "fs";
56916
- import { join as join42, resolve as resolve10 } from "path";
58104
+ import { existsSync as existsSync29, mkdirSync as mkdirSync16, readFileSync as readFileSync26, rmSync as rmSync7, writeFileSync as writeFileSync18 } from "fs";
58105
+ import { join as join44, resolve as resolve10 } from "path";
56917
58106
 
56918
58107
  // src/orchestrate-templates.ts
56919
- import { join as join41 } from "path";
58108
+ import { join as join43 } from "path";
56920
58109
  var ONE_WRITER_FOOTER = `
56921
58110
  ## Return, don't write
56922
58111
 
@@ -56988,7 +58177,7 @@ var PHASE_SPECS = {
56988
58177
  title: "Adjudicate",
56989
58178
  schema: ADJUDICATE_SCHEMA,
56990
58179
  description: (n) => `Adjudicate the ${n} residual judgment criterion(ia) of an ultra11y audit (fan-out, fail-closed fold)`,
56991
- applyHint: (engine, worklist, run2) => `node ${engine} verify --apply ${worklist} --in ${join41(run2, "audit-latest.json")} --out ${run2}`
58180
+ applyHint: (engine, worklist, run2) => `node ${engine} verify --apply ${worklist} --in ${join43(run2, "audit-latest.json")} --out ${run2}`
56992
58181
  },
56993
58182
  "verify-report": {
56994
58183
  role: "refuter",
@@ -57010,7 +58199,7 @@ function toBatches(ids, batchSize) {
57010
58199
  }
57011
58200
  function phaseWorkflowScript(ph, runAbs, engineAbs, batchSize) {
57012
58201
  const spec = phaseSpec(ph.name);
57013
- const scriptPath = join41(runAbs, "orchestration", `${ph.name}.workflow.mjs`);
58202
+ const scriptPath = join43(runAbs, "orchestration", `${ph.name}.workflow.mjs`);
57014
58203
  const meta2 = { name: `ultra11y-${ph.name}`, description: spec.description(ph.items), phases: [{ title: spec.title }] };
57015
58204
  return [
57016
58205
  `export const meta = ${JSON.stringify(meta2)}`,
@@ -57054,7 +58243,7 @@ function agentContracts(runAbs, engineAbs) {
57054
58243
 
57055
58244
  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.
57056
58245
 
57057
- 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>\`).
58246
+ 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>\`).
57058
58247
 
57059
58248
  For EACH of your criteria:
57060
58249
 
@@ -57072,7 +58261,7 @@ ${footer}`,
57072
58261
 
57073
58262
  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.
57074
58263
 
57075
- 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>\`).
58264
+ 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>\`).
57076
58265
 
57077
58266
  For EACH of your entries:
57078
58267
 
@@ -57108,14 +58297,14 @@ ${status}
57108
58297
 
57109
58298
  ## The loop (play every role yourself, one item at a time)
57110
58299
 
57111
- 1. **Audit** (if not done): \`${engine} audit "<globs>" --graph --out ${runAbs}\` \u2192 \`${join41(runAbs, "audit-latest.json")}\`.
57112
- 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}\`.
57113
- 3. **Report**: \`${engine} report --in ${join41(runAbs, "audit-latest.json")} --out ${runAbs}\`.
57114
- 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>\`.
58300
+ 1. **Audit** (if not done): \`${engine} audit "<globs>" --graph --out ${runAbs}\` \u2192 \`${join43(runAbs, "audit-latest.json")}\`.
58301
+ 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}\`.
58302
+ 3. **Report**: \`${engine} report --in ${join43(runAbs, "audit-latest.json")} --out ${runAbs}\`.
58303
+ 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>\`.
57115
58304
  5. **Gate**: \`${engine} check --report <the report .md> --semantic\` must exit 0 before presenting anything.
57116
58305
  6. **Fix & re-audit**: \`${engine} fix <globs> --write --iterate\`, hand-apply the judgment fixes, then loop from step 1 until the gate stays green.
57117
58306
 
57118
- 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.
58307
+ 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.
57119
58308
  `;
57120
58309
  }
57121
58310
 
@@ -57125,12 +58314,12 @@ var SMALL_WORKLIST = 3;
57125
58314
  var BATCH_SIZE2 = 8;
57126
58315
  function listPhases(runDir, engineAbs) {
57127
58316
  const run2 = resolve10(runDir);
57128
- const adjPath = join42(run2, "ADJUDICATE.todo.json");
58317
+ const adjPath = join44(run2, "ADJUDICATE.todo.json");
57129
58318
  let adjIds = [];
57130
58319
  let adjReady = false;
57131
- if (existsSync28(adjPath)) {
58320
+ if (existsSync29(adjPath)) {
57132
58321
  try {
57133
- const f = JSON.parse(readFileSync24(adjPath, "utf8"));
58322
+ const f = JSON.parse(readFileSync26(adjPath, "utf8"));
57134
58323
  if (f && f.kind === "adjudication" && Array.isArray(f.items)) {
57135
58324
  adjReady = true;
57136
58325
  adjIds = f.items.map((i2) => i2.criteriaId);
@@ -57138,12 +58327,12 @@ function listPhases(runDir, engineAbs) {
57138
58327
  } catch {
57139
58328
  }
57140
58329
  }
57141
- const verPath = join42(run2, "VERIFY.todo.json");
58330
+ const verPath = join44(run2, "VERIFY.todo.json");
57142
58331
  let verIds = [];
57143
58332
  let verReady = false;
57144
- if (existsSync28(verPath)) {
58333
+ if (existsSync29(verPath)) {
57145
58334
  try {
57146
- const items = JSON.parse(readFileSync24(verPath, "utf8"));
58335
+ const items = JSON.parse(readFileSync26(verPath, "utf8"));
57147
58336
  if (Array.isArray(items)) {
57148
58337
  verReady = true;
57149
58338
  verIds = items.map((i2) => String(i2.n));
@@ -57158,7 +58347,7 @@ function listPhases(runDir, engineAbs) {
57158
58347
  worklist: adjPath,
57159
58348
  items: adjIds.length,
57160
58349
  ids: adjIds,
57161
- prerequisite: `node ${engineAbs} verify --manual --in ${join42(run2, "audit-latest.json")} --out ${run2}`
58350
+ prerequisite: `node ${engineAbs} verify --manual --in ${join44(run2, "audit-latest.json")} --out ${run2}`
57162
58351
  },
57163
58352
  {
57164
58353
  name: "verify-report",
@@ -57172,7 +58361,7 @@ function listPhases(runDir, engineAbs) {
57172
58361
  }
57173
58362
  function orchestrateRun(runDir, engineAbs, opts = {}) {
57174
58363
  const run2 = resolve10(runDir);
57175
- if (!existsSync28(run2)) {
58364
+ if (!existsSync29(run2)) {
57176
58365
  return { exitCode: 2, written: [], notices: [], errors: [`run dir not found: ${run2}`], phases: [] };
57177
58366
  }
57178
58367
  const phases = listPhases(run2, engineAbs);
@@ -57199,22 +58388,22 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
57199
58388
  }
57200
58389
  selected = [ph];
57201
58390
  }
57202
- const orchDir = join42(run2, "orchestration");
57203
- const agentsDir = join42(orchDir, "agents");
57204
- mkdirSync14(join42(orchDir, "out"), { recursive: true });
57205
- mkdirSync14(agentsDir, { recursive: true });
58391
+ const orchDir = join44(run2, "orchestration");
58392
+ const agentsDir = join44(orchDir, "agents");
58393
+ mkdirSync16(join44(orchDir, "out"), { recursive: true });
58394
+ mkdirSync16(agentsDir, { recursive: true });
57206
58395
  const written = [];
57207
58396
  const notices = [];
57208
58397
  for (const [name2, content] of Object.entries(agentContracts(run2, engineAbs))) {
57209
- const p = join42(agentsDir, `${name2}.md`);
57210
- writeFileSync16(p, content);
58398
+ const p = join44(agentsDir, `${name2}.md`);
58399
+ writeFileSync18(p, content);
57211
58400
  written.push(p);
57212
58401
  }
57213
58402
  const emitted = new Set(opts.eco ? [] : selected.filter((p) => p.items > 0).map((p) => p.name));
57214
58403
  for (const ph of phases) {
57215
58404
  if (emitted.has(ph.name)) continue;
57216
- const stale = join42(orchDir, `${ph.name}.workflow.mjs`);
57217
- if (existsSync28(stale)) {
58405
+ const stale = join44(orchDir, `${ph.name}.workflow.mjs`);
58406
+ if (existsSync29(stale)) {
57218
58407
  rmSync7(stale, { force: true });
57219
58408
  notices.push(`phase "${ph.name}": stale workflow removed \u2014 its worklist ${ph.ready ? "is now empty" : "no longer exists"}.`);
57220
58409
  }
@@ -57228,13 +58417,13 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
57228
58417
  if (ph.items <= SMALL_WORKLIST) {
57229
58418
  notices.push(`phase "${ph.name}": only ${ph.items} item(s) \u2014 the sequential --eco path is equivalent and cheaper.`);
57230
58419
  }
57231
- const p = join42(orchDir, `${ph.name}.workflow.mjs`);
57232
- writeFileSync16(p, phaseWorkflowScript(ph, run2, engineAbs, BATCH_SIZE2));
58420
+ const p = join44(orchDir, `${ph.name}.workflow.mjs`);
58421
+ writeFileSync18(p, phaseWorkflowScript(ph, run2, engineAbs, BATCH_SIZE2));
57233
58422
  written.push(p);
57234
58423
  }
57235
58424
  }
57236
- const rb = join42(orchDir, "RUNBOOK.md");
57237
- writeFileSync16(rb, runbookMd(phases, run2, engineAbs));
58425
+ const rb = join44(orchDir, "RUNBOOK.md");
58426
+ writeFileSync18(rb, runbookMd(phases, run2, engineAbs));
57238
58427
  written.push(rb);
57239
58428
  return { exitCode: 0, written, notices, errors: [], phases };
57240
58429
  }
@@ -57243,8 +58432,8 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
57243
58432
  import { createInterface as createInterface2 } from "readline";
57244
58433
 
57245
58434
  // src/mcp/handlers.ts
57246
- import { existsSync as existsSync29, readFileSync as readFileSync25, realpathSync as realpathSync4, statSync as statSync11 } from "fs";
57247
- import { isAbsolute as isAbsolute3, join as join43, resolve as resolve11, sep as sep5 } from "path";
58435
+ import { existsSync as existsSync30, readFileSync as readFileSync27, realpathSync as realpathSync4, statSync as statSync12 } from "fs";
58436
+ import { isAbsolute as isAbsolute3, join as join45, resolve as resolve11, sep as sep5 } from "path";
57248
58437
 
57249
58438
  // src/project-lock.ts
57250
58439
  var chains = /* @__PURE__ */ new Map();
@@ -57292,8 +58481,8 @@ function requiredCwd(args2, defaults) {
57292
58481
  const cwd = str2(args2.cwd) ?? defaults.defaultCwd;
57293
58482
  if (!cwd) throw new ToolError("`cwd` is required: an absolute path to the project root.");
57294
58483
  const abs = resolve11(cwd);
57295
- if (!existsSync29(abs)) throw new ToolError(`project root not found: ${abs}`);
57296
- if (!statSync11(abs).isDirectory()) throw new ToolError(`\`cwd\` is not a directory: ${abs}`);
58484
+ if (!existsSync30(abs)) throw new ToolError(`project root not found: ${abs}`);
58485
+ if (!statSync12(abs).isDirectory()) throw new ToolError(`\`cwd\` is not a directory: ${abs}`);
57297
58486
  return abs;
57298
58487
  }
57299
58488
  function standardOf(args2) {
@@ -57358,7 +58547,7 @@ function audit(args2, cwd) {
57358
58547
  process.chdir(cwd);
57359
58548
  const inputs = strArray2(args2.globs) ?? DEFAULT_GLOBS;
57360
58549
  const scopedToDiff = bool(args2.changed) || bool(args2.staged) || str2(args2.since) !== void 0;
57361
- const extra = [CAPTURES_DIR, PAGES_DIR].filter((d) => !scopedToDiff && existsSync29(d) && !inputs.includes(d));
58550
+ const extra = [CAPTURES_DIR, PAGES_DIR].filter((d) => !scopedToDiff && existsSync30(d) && !inputs.includes(d));
57362
58551
  const result = runAudit({
57363
58552
  inputs: [...inputs, ...extra],
57364
58553
  include: strArray2(args2.include),
@@ -57396,7 +58585,14 @@ function handleReport(args2, cwd) {
57396
58585
  const lang = langOf(args2);
57397
58586
  const r = audit(args2, cwd);
57398
58587
  const md = standard === "wcag" ? renderReport(r, lang) : renderPackReport(r, loadPack(standard), lang);
57399
- return { cwd, standard, lang, report: md, next: "Validate it with ultra11y_check before presenting it." };
58588
+ return {
58589
+ cwd,
58590
+ standard,
58591
+ lang,
58592
+ report: md,
58593
+ ...args2.html === true ? { html: renderHtmlDocument(compositeDoc(r, { standard, lang })) } : {},
58594
+ next: "Validate it with ultra11y_check before presenting it."
58595
+ };
57400
58596
  }
57401
58597
  function handlePrd(args2, cwd) {
57402
58598
  const standard = standardOf(args2);
@@ -57470,13 +58666,13 @@ function handlePackCheck(args2, cwd) {
57470
58666
  return { cwd, ...res };
57471
58667
  }
57472
58668
  function handleSampleCheck(cwd, standard) {
57473
- const file = join43(cwd, ".ultra11yrc.json");
57474
- if (!existsSync29(file)) {
58669
+ const file = join45(cwd, ".ultra11yrc.json");
58670
+ if (!existsSync30(file)) {
57475
58671
  throw new ToolError(`no .ultra11yrc.json at ${cwd} \u2014 a page sample must be declared before it can be linted.`);
57476
58672
  }
57477
58673
  let raw;
57478
58674
  try {
57479
- raw = JSON.parse(readFileSync25(file, "utf8"));
58675
+ raw = JSON.parse(readFileSync27(file, "utf8"));
57480
58676
  } catch (e) {
57481
58677
  throw new ToolError(`.ultra11yrc.json is not valid JSON: ${e.message}`);
57482
58678
  }
@@ -57520,6 +58716,7 @@ function handlePages(args2, cwd) {
57520
58716
  standard,
57521
58717
  lang,
57522
58718
  markdown,
58719
+ ...args2.html === true ? { html: renderHtmlDocument(pagesIndexDoc(r, { standard, lang, sheetHref: (id) => `#page-${id}` })) } : {},
57523
58720
  // `conformancePct` is null when this page decided nothing — a machine consumer must be able
57524
58721
  // to tell "no criterion was assessed" from "every assessed criterion passed", which a bare
57525
58722
  // 100 conflates. `decided`/`total` are the denominator that makes the number quotable.
@@ -57569,7 +58766,7 @@ function handleInit(args2, cwd) {
57569
58766
  const enginePath = resolve11("scripts/ultra11y.mjs");
57570
58767
  const written = [];
57571
58768
  if (bool(args2.hook)) written.push(writeHook(cwd, enginePath, failOn));
57572
- if (bool(args2.ci)) written.push(join43(cwd, ".github/workflows/a11y.yml"));
58769
+ if (bool(args2.ci)) written.push(join45(cwd, ".github/workflows/a11y.yml"));
57573
58770
  if (!written.length) {
57574
58771
  throw new ToolError("nothing to install \u2014 pass hook:true and/or ci:true.");
57575
58772
  }
@@ -57589,8 +58786,8 @@ function reportText(args2, tool) {
57589
58786
  if (inline) return inline;
57590
58787
  if (!file) throw new ToolError(`\`report_text\` is required \u2014 the report markdown for ultra11y_${tool} to work on.`);
57591
58788
  if (!isAbsolute3(file)) throw new ToolError("`report_file` must be an absolute path.");
57592
- if (!existsSync29(file)) throw new ToolError(`report file not found: ${file}`);
57593
- return readFileSync25(file, "utf8");
58789
+ if (!existsSync30(file)) throw new ToolError(`report file not found: ${file}`);
58790
+ return readFileSync27(file, "utf8");
57594
58791
  }
57595
58792
  function handleCriteria(args2) {
57596
58793
  const lang = langOf(args2);
@@ -57605,7 +58802,7 @@ function handleCriteria(args2) {
57605
58802
  function handleRead(args2, cwd) {
57606
58803
  const raw = str2(args2.path);
57607
58804
  if (!raw) throw new ToolError("`path` is required \u2014 relative to the project root, or an absolute path inside it.");
57608
- const target = isAbsolute3(raw) ? raw : join43(cwd, raw);
58805
+ const target = isAbsolute3(raw) ? raw : join45(cwd, raw);
57609
58806
  let real;
57610
58807
  try {
57611
58808
  real = realpathSync4(target);
@@ -57616,10 +58813,10 @@ function handleRead(args2, cwd) {
57616
58813
  if (real !== root && !real.startsWith(root + sep5)) {
57617
58814
  throw new ToolError(`path is outside the project: ${raw}. Use your own file tool for anything else.`);
57618
58815
  }
57619
- const st = statSync11(real);
58816
+ const st = statSync12(real);
57620
58817
  if (!st.isFile()) throw new ToolError(`not a file: ${raw}`);
57621
58818
  if (st.size > MAX_READ_BYTES) throw new ToolError(`file is too large to read (${st.size} bytes): ${raw}`);
57622
- const lines = readFileSync25(real, "utf8").split("\n");
58819
+ const lines = readFileSync27(real, "utf8").split("\n");
57623
58820
  const total = lines.length;
57624
58821
  const start2 = Math.max(1, Math.floor(num2(args2.start_line) ?? 1));
57625
58822
  if (start2 > total) throw new ToolError(`start_line ${start2} is past the end of the file (${total} lines).`);
@@ -57741,6 +58938,10 @@ var standardProp = {
57741
58938
  description: "Which standard to report against. Default: wcag (WCAG 2.2 AA)."
57742
58939
  };
57743
58940
  var langProp = { type: "string", enum: ["en", "fr"], description: "Language for the rendered prose. Default: en." };
58941
+ var htmlProp = {
58942
+ type: "boolean",
58943
+ 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."
58944
+ };
57744
58945
  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.";
57745
58946
  var TOOLS2 = [
57746
58947
  {
@@ -57770,7 +58971,7 @@ var TOOLS2 = [
57770
58971
  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.",
57771
58972
  inputSchema: {
57772
58973
  type: "object",
57773
- properties: { cwd: cwdProp, globs: globsProp, standard: standardProp, lang: langProp },
58974
+ properties: { cwd: cwdProp, globs: globsProp, standard: standardProp, lang: langProp, html: htmlProp },
57774
58975
  required: ["cwd"]
57775
58976
  }
57776
58977
  },
@@ -57902,7 +59103,8 @@ var TOOLS2 = [
57902
59103
  type: "string",
57903
59104
  enum: ["grid", "report"],
57904
59105
  description: "`grid` (default) is the criterion \xD7 page matrix; `report` is the per-page dossiers."
57905
- }
59106
+ },
59107
+ html: htmlProp
57906
59108
  },
57907
59109
  required: ["cwd"]
57908
59110
  }
@@ -58160,25 +59362,25 @@ function str3(v) {
58160
59362
  var DECLARED = new Set([...TOOLS2, ...WRITE_TOOLS].map((t2) => t2.name));
58161
59363
 
58162
59364
  // src/mcp/resources.ts
58163
- import { existsSync as existsSync30, readdirSync as readdirSync6, readFileSync as readFileSync26, realpathSync as realpathSync5, statSync as statSync12 } from "fs";
58164
- import { basename as basename3, dirname as dirname13, join as join44, resolve as resolve12, sep as sep6 } from "path";
59365
+ import { existsSync as existsSync31, readdirSync as readdirSync6, readFileSync as readFileSync28, realpathSync as realpathSync5, statSync as statSync13 } from "fs";
59366
+ import { basename as basename3, dirname as dirname13, join as join46, resolve as resolve12, sep as sep6 } from "path";
58165
59367
  import { fileURLToPath as fileURLToPath4 } from "url";
58166
59368
  var SKILL_NAME = "ultra11y";
58167
59369
  var URI_SCHEME = "skill://";
58168
59370
  function resolveSkillRoot(moduleDir) {
58169
59371
  const here = moduleDir ?? dirname13(fileURLToPath4(import.meta.url));
58170
59372
  const candidates2 = [resolve12(here, ".."), resolve12(here, "..", "skills", SKILL_NAME), resolve12(here, "..", "..", "skills", SKILL_NAME)];
58171
- return candidates2.find((dir) => existsSync30(join44(dir, "SKILL.md")));
59373
+ return candidates2.find((dir) => existsSync31(join46(dir, "SKILL.md")));
58172
59374
  }
58173
59375
  function listResources(moduleDir) {
58174
59376
  const root = resolveSkillRoot(moduleDir);
58175
59377
  if (!root) return [];
58176
59378
  const out2 = [describe(root, "SKILL.md", `${SKILL_NAME}: the skill`)];
58177
- const refDir = join44(root, "references");
58178
- if (!existsSync30(refDir)) return out2;
59379
+ const refDir = join46(root, "references");
59380
+ if (!existsSync31(refDir)) return out2;
58179
59381
  for (const file of readdirSync6(refDir).sort()) {
58180
59382
  if (!file.endsWith(".md")) continue;
58181
- out2.push(describe(root, join44("references", file), `${SKILL_NAME} reference: ${basename3(file, ".md")}`));
59383
+ out2.push(describe(root, join46("references", file), `${SKILL_NAME} reference: ${basename3(file, ".md")}`));
58182
59384
  }
58183
59385
  return out2;
58184
59386
  }
@@ -58201,8 +59403,8 @@ function readResource(uri, moduleDir) {
58201
59403
  if (targetReal !== rootReal && !targetReal.startsWith(rootReal + sep6)) {
58202
59404
  throw new ResourceError(`resource path escapes the skill root: ${uri}`);
58203
59405
  }
58204
- if (!statSync12(targetReal).isFile()) throw new ResourceError(`not a file: ${uri}`);
58205
- return { uri, mimeType: "text/markdown", text: readFileSync26(targetReal, "utf8") };
59406
+ if (!statSync13(targetReal).isFile()) throw new ResourceError(`not a file: ${uri}`);
59407
+ return { uri, mimeType: "text/markdown", text: readFileSync28(targetReal, "utf8") };
58206
59408
  }
58207
59409
  var ResourceError = class extends Error {
58208
59410
  };
@@ -58213,14 +59415,14 @@ function describe(root, rel2, fallbackTitle) {
58213
59415
  title: fallbackTitle,
58214
59416
  mimeType: "text/markdown"
58215
59417
  };
58216
- const summary = firstProse(join44(root, rel2));
59418
+ const summary = firstProse(join46(root, rel2));
58217
59419
  if (summary) decl.description = summary;
58218
59420
  return decl;
58219
59421
  }
58220
59422
  function firstProse(file) {
58221
59423
  let text;
58222
59424
  try {
58223
- text = readFileSync26(file, "utf8");
59425
+ text = readFileSync28(file, "utf8");
58224
59426
  } catch {
58225
59427
  return void 0;
58226
59428
  }
@@ -58385,7 +59587,7 @@ async function runStdioServer(opts = {}) {
58385
59587
  let restore;
58386
59588
  if (!opts.captureStdout && output === process.stdout) {
58387
59589
  const original = process.stdout.write;
58388
- process.stdout.write = ((chunk, ...rest) => process.stderr.write(chunk, ...rest));
59590
+ process.stdout.write = ((chunk2, ...rest) => process.stderr.write(chunk2, ...rest));
58389
59591
  restore = () => {
58390
59592
  process.stdout.write = original;
58391
59593
  };
@@ -58633,6 +59835,7 @@ Usage:
58633
59835
  ultra11y audit [--format sarif|github] (CI: SARIF for code scanning, or inline annotations + job summary)
58634
59836
  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)
58635
59837
  ultra11y report --in <audit.json> [--out <dir>] [--standard <pack>] [--format sarif|github] [--lang auto|en|fr]
59838
+ ultra11y report --in <audit.json> --html [--evidence] [--inline-budget <bytes>] [--out <dir>] (index.html + a printable single file; --evidence adds annotated crops)
58636
59839
  ultra11y prd --in <audit.json> [--out <dir>] [--split criterion] [--format audit|doc|remediation] [--no-technical] [--standard <pack>] [--lang auto|en|fr]
58637
59840
  ultra11y tickets --in <audit.json> [--provider auto|github|gitlab|jira] [--grain criterion|page|page-criterion|single|file] [--transport auto|cli|rest]
58638
59841
  ultra11y tickets [--out <dir>] [--max-tickets <n>] [--dry-run] [--json] [--standard <pack>] [--format audit|remediation] [--lang auto|en|fr]
@@ -58657,6 +59860,7 @@ Usage:
58657
59860
  ultra11y snapshot list [--root <dir>] [--json]
58658
59861
  ultra11y pages --in <audit.json> [--standard <pack>] [--json] [--lang auto|en|fr] (the per-page criterion grid)
58659
59862
  ultra11y pages --in <audit.json> --format report [--split page] [--out <dir>] (the per-page report, with screenshots)
59863
+ ultra11y pages --in <audit.json> --format report --out <dir> [--evidence] [--html] (annotated crops of each non-conformity, and the HTML site)
58660
59864
  ultra11y pages discover --sitemap <url> | --crawl <url> | --from-snapshots [--depth <n>] [--max <n>] [--write] [--json] (build the page sample)
58661
59865
  ultra11y pages --in <audit.json> --standard <pack> --diff <external.json> (hold the grid against an audit someone else performed)
58662
59866
  ultra11y import --from file <report.json> | --from ara <id> [--source <adapter>] [--out <dir>] [--json]
@@ -59053,6 +60257,8 @@ var VALUE_FLAGS2 = /* @__PURE__ */ new Set([
59053
60257
  "model",
59054
60258
  "pack",
59055
60259
  "format",
60260
+ // Bytes of image data the single-file HTML report may carry inline (src/html-emit.ts).
60261
+ "inline-budget",
59056
60262
  "guidance",
59057
60263
  "runtime",
59058
60264
  "cwd",
@@ -59087,6 +60293,10 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
59087
60293
  "cross-file",
59088
60294
  "json",
59089
60295
  "quiet",
60296
+ // The visual tier. Both must be declared here or `parseArgs` swallows the value that
60297
+ // follows and the run prints "unknown flag (ignored)" for a flag that in fact works.
60298
+ "html",
60299
+ "evidence",
59090
60300
  "no-default-excludes",
59091
60301
  "no-captures",
59092
60302
  "require-captures",
@@ -59236,7 +60446,16 @@ function emitCiFormat(result, format, standard, lang, failOn) {
59236
60446
  console.error(md);
59237
60447
  }
59238
60448
  if (process.env.ULTRA11Y_PR_COMMENT === "1") {
59239
- const c2 = pushPrComment(md, standard);
60449
+ const digest = prComment(result, {
60450
+ standard,
60451
+ lang,
60452
+ // The run is known before the artifact exists, so the link is always safe. The artifact
60453
+ // NAME is only set by the action when it actually uploads one — naming an artifact that
60454
+ // was never uploaded sends the reader to a page that does not exist.
60455
+ ...process.env.ULTRA11Y_RUN_URL ? { runUrl: process.env.ULTRA11Y_RUN_URL } : {},
60456
+ ...process.env.ULTRA11Y_ARTIFACT_NAME ? { artifactName: process.env.ULTRA11Y_ARTIFACT_NAME } : {}
60457
+ });
60458
+ const c2 = pushPrComment(digest, standard);
59240
60459
  console.error(
59241
60460
  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}` : ""}.`
59242
60461
  );
@@ -59322,9 +60541,9 @@ async function cmdAudit(p) {
59322
60541
  const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
59323
60542
  const capturesDir = capturesFlag ?? CAPTURES_DIR;
59324
60543
  const scopedToDiff = p.flags.changed === true || p.flags.staged === true || since !== void 0;
59325
- const capturesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && (capturesFlag !== void 0 || existsSync31(capturesDir));
60544
+ const capturesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && (capturesFlag !== void 0 || existsSync32(capturesDir));
59326
60545
  const useCaptures = capturesWanted && !scopedToDiff && !inputs.includes(capturesDir);
59327
- const pagesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && existsSync31(PAGES_DIR);
60546
+ const pagesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && existsSync32(PAGES_DIR);
59328
60547
  const usePages = pagesWanted && !scopedToDiff && !inputs.includes(PAGES_DIR);
59329
60548
  const pagesInScope = pagesWanted && !scopedToDiff;
59330
60549
  const auditInputs = [...inputs, ...useCaptures ? [capturesDir] : [], ...usePages ? [PAGES_DIR] : []];
@@ -59366,10 +60585,10 @@ async function cmdAudit(p) {
59366
60585
  if (typeof p.flags.out === "string") {
59367
60586
  const out2 = p.flags.out;
59368
60587
  const asFile = out2.toLowerCase().endsWith(".json");
59369
- const target = asFile ? out2 : join45(out2, "audit-latest.json");
60588
+ const target = asFile ? out2 : join47(out2, "audit-latest.json");
59370
60589
  try {
59371
- mkdirSync15(asFile ? dirname14(out2) : out2, { recursive: true });
59372
- writeFileSync17(target, JSON.stringify(result, null, 2) + "\n");
60590
+ mkdirSync17(asFile ? dirname14(out2) : out2, { recursive: true });
60591
+ writeFileSync19(target, JSON.stringify(result, null, 2) + "\n");
59373
60592
  console.error(lang === "fr" ? `\u2192 audit \xE9crit dans ${target}` : `\u2192 audit written to ${target}`);
59374
60593
  } catch {
59375
60594
  }
@@ -59388,7 +60607,7 @@ async function cmdAudit(p) {
59388
60607
  const baselineFlag = p.flags.baseline;
59389
60608
  if (typeof baselineFlag === "string" && baselineFlag) {
59390
60609
  let baseline = null;
59391
- if (existsSync31(baselineFlag)) {
60610
+ if (existsSync32(baselineFlag)) {
59392
60611
  try {
59393
60612
  const parsed = JSON.parse(readText(baselineFlag));
59394
60613
  if (isCurrentAudit(parsed)) baseline = parsed;
@@ -59442,11 +60661,11 @@ async function cmdDev(p) {
59442
60661
  return 2;
59443
60662
  }
59444
60663
  if (p.flags.next === true) {
59445
- const dir = join45(root, ".ultra11y", "next");
60664
+ const dir = join47(root, ".ultra11y", "next");
59446
60665
  const rel2 = ".ultra11y/next/overlay.jsx";
59447
60666
  try {
59448
- mkdirSync15(dir, { recursive: true });
59449
- writeFileSync17(join45(dir, "overlay.jsx"), nextOverlayComponent(port));
60667
+ mkdirSync17(dir, { recursive: true });
60668
+ writeFileSync19(join47(dir, "overlay.jsx"), nextOverlayComponent(port));
59450
60669
  } catch (e) {
59451
60670
  console.error(`ultra11y dev: could not write ${rel2}: ${e instanceof Error ? e.message : String(e)}`);
59452
60671
  return 1;
@@ -59495,7 +60714,7 @@ async function cmdPagesDiscover(p) {
59495
60714
  const snaps = sampleFromSnapshots(readSnapshots(root));
59496
60715
  if (!snaps.length) {
59497
60716
  console.error(
59498
- 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\`.`
60717
+ 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\`.`
59499
60718
  );
59500
60719
  return 1;
59501
60720
  }
@@ -59575,9 +60794,9 @@ ${found} page(s) discovered, ${merged.added.length} new. Nothing was written \u2
59575
60794
  }
59576
60795
  return 0;
59577
60796
  }
59578
- const file = join45(process.cwd(), ".ultra11yrc.json");
60797
+ const file = join47(process.cwd(), ".ultra11yrc.json");
59579
60798
  let doc = {};
59580
- if (existsSync31(file)) {
60799
+ if (existsSync32(file)) {
59581
60800
  try {
59582
60801
  doc = JSON.parse(readText(file));
59583
60802
  } catch {
@@ -59586,7 +60805,7 @@ ${found} page(s) discovered, ${merged.added.length} new. Nothing was written \u2
59586
60805
  }
59587
60806
  }
59588
60807
  doc.sample = v.sample;
59589
- writeFileSync17(file, `${JSON.stringify(doc, null, 2)}
60808
+ writeFileSync19(file, `${JSON.stringify(doc, null, 2)}
59590
60809
  `);
59591
60810
  if (!p.flags.json)
59592
60811
  console.log(
@@ -59646,50 +60865,115 @@ async function cmdPages(p) {
59646
60865
  const derived = derivePages(result, scope);
59647
60866
  const split = p.flags.split === "page";
59648
60867
  const outDir = typeof p.flags.out === "string" && p.flags.out ? p.flags.out : void 0;
59649
- const shotOf = (id) => join45(PAGES_DIR, id, "screen.png");
60868
+ const shotOf = (id) => join47(PAGES_DIR, id, "screen.png");
59650
60869
  const shotsRelative = (fileDir) => {
59651
60870
  const m = /* @__PURE__ */ new Map();
59652
60871
  for (const pg of derived) {
59653
- if (existsSync31(shotOf(pg.id))) m.set(pg.id, relative5(fileDir, shotOf(pg.id)).split("\\").join("/"));
60872
+ if (existsSync32(shotOf(pg.id))) m.set(pg.id, relative5(fileDir, shotOf(pg.id)).split("\\").join("/"));
59654
60873
  }
59655
60874
  return m;
59656
60875
  };
60876
+ const shotPaths = (pages) => {
60877
+ const m = /* @__PURE__ */ new Map();
60878
+ for (const pg of pages) if (existsSync32(shotOf(pg.id))) m.set(pg.id, shotOf(pg.id));
60879
+ return m;
60880
+ };
59657
60881
  const shotsCopiedInto = (dir) => {
59658
60882
  const m = /* @__PURE__ */ new Map();
59659
- const assets = join45(dir, "assets");
60883
+ const assets = join47(dir, "assets");
59660
60884
  for (const pg of derived) {
59661
60885
  const src = shotOf(pg.id);
59662
- if (!existsSync31(src)) continue;
59663
- mkdirSync15(assets, { recursive: true });
59664
- copyFileSync4(src, join45(assets, `${pg.id}.png`));
60886
+ if (!existsSync32(src)) continue;
60887
+ mkdirSync17(assets, { recursive: true });
60888
+ copyFileSync4(src, join47(assets, `${pg.id}.png`));
59665
60889
  m.set(pg.id, `./assets/${pg.id}.png`);
59666
60890
  }
59667
60891
  return m;
59668
60892
  };
60893
+ const wantEvidence = p.flags.evidence === true;
60894
+ const wantHtml = p.flags.html === true;
59669
60895
  if (!outDir) {
60896
+ if (wantEvidence || wantHtml) {
60897
+ console.error(
60898
+ 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."
60899
+ );
60900
+ return 2;
60901
+ }
59670
60902
  console.log(renderPagesDocument(result, derived, { standard, lang, screenshots: shotsRelative(".") }));
59671
60903
  return 0;
59672
60904
  }
59673
- mkdirSync15(outDir, { recursive: true });
60905
+ mkdirSync17(outDir, { recursive: true });
60906
+ const manifest = wantEvidence ? writeEvidence(result, { outDir }) : void 0;
60907
+ const cropFor = manifest ? (f) => {
60908
+ const c2 = manifest.crops.get(findingId(f));
60909
+ return c2 ? { href: c2.href, alt: c2.alt[lang] } : void 0;
60910
+ } : void 0;
60911
+ if (manifest) {
60912
+ const total = evidenceNotice(manifest, null, lang);
60913
+ console.error(
60914
+ 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")}.`
60915
+ );
60916
+ for (const line of total) console.error(line);
60917
+ }
60918
+ const evidenceOpts = {
60919
+ ...cropFor ? { cropFor } : {}
60920
+ };
59674
60921
  if (!split) {
59675
- const file = join45(outDir, `pages-${result.date}.md`);
59676
- writeFileSync17(file, `${renderPagesDocument(result, derived, { standard, lang, screenshots: shotsCopiedInto(outDir) })}
60922
+ const file = join47(outDir, `pages-${result.date}.md`);
60923
+ writeFileSync19(file, `${renderPagesDocument(result, derived, { standard, lang, screenshots: shotsCopiedInto(outDir), ...evidenceOpts })}
59677
60924
  `);
60925
+ const html2 = wantHtml ? emitHtml(result, {
60926
+ outDir,
60927
+ standard,
60928
+ lang,
60929
+ layout: "pages",
60930
+ pages: true,
60931
+ screenshots: shotPaths(derived),
60932
+ ...manifest ? { evidence: manifest } : {},
60933
+ inlineBudget: budgetOf(p)
60934
+ }) : void 0;
59678
60935
  console.log(file);
59679
- return 0;
60936
+ return html2?.imagesDropped ? 1 : 0;
59680
60937
  }
59681
60938
  const shots = shotsCopiedInto(outDir);
59682
60939
  const sheet = (id) => `page-${id}.md`;
59683
60940
  const hrefs = new Map(derived.map((pg) => [pg.id, `./${sheet(pg.id)}`]));
59684
60941
  for (const pg of derived) {
59685
- writeFileSync17(join45(outDir, sheet(pg.id)), `${renderPageDocument(result, pg, { standard, lang, screenshots: shots })}
59686
- `);
60942
+ const notice = manifest ? evidenceNotice(manifest, pg.id, lang) : [];
60943
+ writeFileSync19(
60944
+ join47(outDir, sheet(pg.id)),
60945
+ `${renderPageDocument(result, pg, { standard, lang, screenshots: shots, ...evidenceOpts, ...notice.length ? { evidenceNotice: notice } : {} })}
60946
+ `
60947
+ );
59687
60948
  }
59688
- const index = join45(outDir, "index.md");
59689
- writeFileSync17(index, `${renderPagesIndex(result, derived, { standard, lang, hrefs })}
60949
+ const index = join47(outDir, "index.md");
60950
+ writeFileSync19(index, `${renderPagesIndex(result, derived, { standard, lang, hrefs })}
59690
60951
  `);
60952
+ const html = wantHtml ? emitHtml(result, {
60953
+ outDir,
60954
+ standard,
60955
+ lang,
60956
+ layout: "pages",
60957
+ pages: true,
60958
+ screenshots: shotPaths(derived),
60959
+ ...manifest ? { evidence: manifest } : {},
60960
+ inlineBudget: budgetOf(p)
60961
+ }) : void 0;
59691
60962
  console.log(index);
59692
- return 0;
60963
+ return html?.imagesDropped ? 1 : 0;
60964
+ }
60965
+ function budgetOf(p) {
60966
+ const raw = p.flags["inline-budget"];
60967
+ if (typeof raw !== "string" || !raw) return void 0;
60968
+ const n = Number.parseInt(raw, 10);
60969
+ return Number.isFinite(n) && n > 0 ? n : void 0;
60970
+ }
60971
+ function emitHtml(result, opts) {
60972
+ const res = writeHtml(result, opts);
60973
+ for (const n of res.notices) console.error(`ultra11y: ${n}`);
60974
+ console.error(`ultra11y: ${res.index}`);
60975
+ if (res.composite) console.error(`ultra11y: ${res.composite}`);
60976
+ return res;
59693
60977
  }
59694
60978
  async function cmdSnapshot(p) {
59695
60979
  const sub = p.positionals[0];
@@ -59705,7 +60989,7 @@ async function cmdSnapshot(p) {
59705
60989
  2
59706
60990
  )
59707
60991
  );
59708
- else if (!snaps.length) console.log(lang === "fr" ? `Aucun instantan\xE9 dans ${join45(root, PAGES_DIR)}.` : `No snapshot in ${join45(root, PAGES_DIR)}.`);
60992
+ else if (!snaps.length) console.log(lang === "fr" ? `Aucun instantan\xE9 dans ${join47(root, PAGES_DIR)}.` : `No snapshot in ${join47(root, PAGES_DIR)}.`);
59709
60993
  else for (const s of snaps) console.log(`${s.meta.id} ${s.meta.name} ${s.meta.url}${s.meta.auth ? " [auth]" : ""}`);
59710
60994
  return 0;
59711
60995
  }
@@ -59752,7 +61036,7 @@ async function cmdSnapshot(p) {
59752
61036
  console.error(`ultra11y snapshot write: could not write the snapshot: ${e instanceof Error ? e.message : String(e)}`);
59753
61037
  return 1;
59754
61038
  }
59755
- const result = runAudit({ inputs: [join45(dir, "dom.html")], onWarn: (m) => console.error(m) });
61039
+ const result = runAudit({ inputs: [join47(dir, "dom.html")], onWarn: (m) => console.error(m) });
59756
61040
  const failOnRaw = p.flags["fail-on"];
59757
61041
  const failOnParsed = parseFailOn(failOnRaw);
59758
61042
  if (failOnRaw !== void 0 && failOnParsed === null) {
@@ -59784,9 +61068,9 @@ function cmdInit(p) {
59784
61068
  if (want.baseline) {
59785
61069
  const inputs = p.positionals.length ? p.positionals : ["."];
59786
61070
  const result = runAudit({ inputs, onWarn: (m) => console.error(m) });
59787
- mkdirSync15(join45(root, "audits"), { recursive: true });
59788
- const bp = join45(root, "audits", "baseline.json");
59789
- writeFileSync17(bp, JSON.stringify(result, null, 2) + "\n");
61071
+ mkdirSync17(join47(root, "audits"), { recursive: true });
61072
+ const bp = join47(root, "audits", "baseline.json");
61073
+ writeFileSync19(bp, JSON.stringify(result, null, 2) + "\n");
59790
61074
  wrote.push(bp);
59791
61075
  }
59792
61076
  if (want.hook) wrote.push(writeHook(root, engineRel, failOn, legacy ? "baseline" : "staged"));
@@ -59911,11 +61195,28 @@ async function cmdReport(p) {
59911
61195
  console.error(`ultra11y report: --format must be sarif|github (got "${String(p.flags.format)}").`);
59912
61196
  return 2;
59913
61197
  }
61198
+ if (ciFormat && p.flags.html === true) {
61199
+ console.error(
61200
+ 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.`
61201
+ );
61202
+ return 2;
61203
+ }
59914
61204
  if (ciFormat) {
59915
61205
  emitCiFormat(result, ciFormat, standard, lang);
59916
61206
  return 0;
59917
61207
  }
59918
61208
  const path = writeReport(result, { out: out2, lang, standard });
61209
+ let html;
61210
+ if (p.flags.html === true) {
61211
+ const manifest = p.flags.evidence === true ? writeEvidence(result, { outDir: out2 }) : void 0;
61212
+ if (manifest) for (const line of evidenceNotice(manifest, null, lang)) console.error(line);
61213
+ html = emitHtml(result, { outDir: out2, standard, lang, ...manifest ? { evidence: manifest } : {}, inlineBudget: budgetOf(p) });
61214
+ } else if (p.flags.evidence === true) {
61215
+ console.error(
61216
+ lang === "fr" ? "ultra11y report : --evidence n'illustre que le rapport HTML. Ajoutez --html, ou utilisez `pages --format report --evidence` pour des fiches Markdown illustr\xE9es." : "ultra11y report: --evidence only illustrates the HTML report. Add --html, or use `pages --format report --evidence` for illustrated Markdown sheets."
61217
+ );
61218
+ return 2;
61219
+ }
59919
61220
  const untested = isCore(standard) ? [] : untestedNeedsRendering(result);
59920
61221
  const partial = untested.length > 0;
59921
61222
  if (partial && !p.flags.json) console.error(`\u{1F6A8} ${partialAuditBanner(lang, untested)}`);
@@ -59927,6 +61228,7 @@ async function cmdReport(p) {
59927
61228
  conformancePct: result.conformancePct,
59928
61229
  date: result.date,
59929
61230
  standard: typeof p.flags.standard === "string" ? p.flags.standard : "wcag",
61231
+ ...html ? { htmlPath: html.index, htmlSinglePath: html.composite } : {},
59930
61232
  ...partial ? { partialAudit: true, untestedCriteria: untested } : {}
59931
61233
  },
59932
61234
  null,
@@ -59934,7 +61236,7 @@ async function cmdReport(p) {
59934
61236
  )
59935
61237
  );
59936
61238
  else console.log(path);
59937
- return 0;
61239
+ return html?.imagesDropped ? 1 : 0;
59938
61240
  }
59939
61241
  async function cmdPrd(p) {
59940
61242
  const inFlag = p.flags.in;
@@ -60066,8 +61368,8 @@ async function cmdTickets(p) {
60066
61368
  const ticketsOut = typeof p.flags.out === "string" && p.flags.out ? p.flags.out : void 0;
60067
61369
  let setPath;
60068
61370
  if (ticketsOut) {
60069
- mkdirSync15(ticketsOut, { recursive: true });
60070
- setPath = join45(ticketsOut, `issues-${result.date}.json`);
61371
+ mkdirSync17(ticketsOut, { recursive: true });
61372
+ setPath = join47(ticketsOut, `issues-${result.date}.json`);
60071
61373
  const payload = {
60072
61374
  tool: "ultra11y",
60073
61375
  kind: "issues",
@@ -60078,7 +61380,7 @@ async function cmdTickets(p) {
60078
61380
  count: plan.tickets.length,
60079
61381
  issues: plan.tickets
60080
61382
  };
60081
- writeFileSync17(setPath, `${JSON.stringify(payload, null, 2)}
61383
+ writeFileSync19(setPath, `${JSON.stringify(payload, null, 2)}
60082
61384
  `);
60083
61385
  if (!json) console.log(setPath);
60084
61386
  }
@@ -60168,8 +61470,8 @@ async function cmdTickets(p) {
60168
61470
  return pushed.failed > 0 ? 1 : 0;
60169
61471
  }
60170
61472
  function depsAt(root) {
60171
- const pkgPath = join45(root, "package.json");
60172
- if (!existsSync31(pkgPath)) return {};
61473
+ const pkgPath = join47(root, "package.json");
61474
+ if (!existsSync32(pkgPath)) return {};
60173
61475
  try {
60174
61476
  const pkg = JSON.parse(readText(pkgPath));
60175
61477
  return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
@@ -60195,24 +61497,24 @@ function cmdRender(p) {
60195
61497
  console.error(`ultra11y render: --runner must be playwright|cypress|auto (got "${forced}").`);
60196
61498
  return 2;
60197
61499
  }
60198
- const detected = detectE2eRunner(depsAt(root), (f) => existsSync31(join45(root, f)));
61500
+ const detected = detectE2eRunner(depsAt(root), (f) => existsSync32(join47(root, f)));
60199
61501
  const runners = forced && forced !== "auto" ? [forced] : detected;
60200
61502
  if (!runners.length) {
60201
61503
  console.error(e2eSetupPlan([], {}, lang));
60202
61504
  return 1;
60203
61505
  }
60204
61506
  const engineRef = engineRefFor(root);
60205
- const dir = join45(root, ".ultra11y", "e2e");
61507
+ const dir = join47(root, ".ultra11y", "e2e");
60206
61508
  const paths = {};
60207
61509
  try {
60208
- mkdirSync15(dir, { recursive: true });
61510
+ mkdirSync17(dir, { recursive: true });
60209
61511
  if (runners.includes("playwright")) {
60210
- writeFileSync17(join45(dir, "playwright.mjs"), playwrightFixture(engineRef));
61512
+ writeFileSync19(join47(dir, "playwright.mjs"), playwrightFixture(engineRef));
60211
61513
  paths.playwright = ".ultra11y/e2e/playwright.mjs";
60212
61514
  }
60213
61515
  if (runners.includes("cypress")) {
60214
- writeFileSync17(join45(dir, "cypress-plugin.mjs"), cypressPlugin(engineRef));
60215
- writeFileSync17(join45(dir, "cypress-commands.mjs"), cypressCommands());
61516
+ writeFileSync19(join47(dir, "cypress-plugin.mjs"), cypressPlugin(engineRef));
61517
+ writeFileSync19(join47(dir, "cypress-commands.mjs"), cypressCommands());
60216
61518
  paths.cypressPlugin = ".ultra11y/e2e/cypress-plugin.mjs";
60217
61519
  paths.cypressCommands = ".ultra11y/e2e/cypress-commands.mjs";
60218
61520
  }
@@ -60227,7 +61529,7 @@ function cmdRender(p) {
60227
61529
  if (p.flags.scaffold === true) {
60228
61530
  const out2 = typeof p.flags.out === "string" && p.flags.out ? p.flags.out : "ultra11y-render.tsx";
60229
61531
  try {
60230
- writeFileSync17(out2, ssrHarness());
61532
+ writeFileSync19(out2, ssrHarness());
60231
61533
  } catch (e) {
60232
61534
  console.error(`ultra11y render: could not write ${out2}: ${e instanceof Error ? e.message : String(e)}`);
60233
61535
  return 1;
@@ -60241,29 +61543,29 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
60241
61543
  }
60242
61544
  if (p.flags.setup === true) {
60243
61545
  const rel2 = ".ultra11y/capture-setup.mjs";
60244
- const out2 = join45(root, rel2);
61546
+ const out2 = join47(root, rel2);
60245
61547
  try {
60246
- mkdirSync15(dirname14(out2), { recursive: true });
60247
- writeFileSync17(out2, captureSetup());
61548
+ mkdirSync17(dirname14(out2), { recursive: true });
61549
+ writeFileSync19(out2, captureSetup());
60248
61550
  } catch (e) {
60249
61551
  console.error(`ultra11y render: could not write ${out2}: ${e instanceof Error ? e.message : String(e)}`);
60250
61552
  return 1;
60251
61553
  }
60252
61554
  let setupDeps = {};
60253
- const setupPkg = join45(root, "package.json");
60254
- if (existsSync31(setupPkg)) {
61555
+ const setupPkg = join47(root, "package.json");
61556
+ if (existsSync32(setupPkg)) {
60255
61557
  try {
60256
61558
  const pkg = JSON.parse(readText(setupPkg));
60257
61559
  setupDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
60258
61560
  } catch {
60259
61561
  }
60260
61562
  }
60261
- const tr = detectTestRunner(setupDeps, (f) => existsSync31(join45(root, f)));
61563
+ const tr = detectTestRunner(setupDeps, (f) => existsSync32(join47(root, f)));
60262
61564
  console.log(captureSetupPlan(tr, rel2, lang));
60263
61565
  const gaLine = ".ultra11y/captures/*.html text eol=lf linguist-generated=true";
60264
- const gaPath = join45(root, ".gitattributes");
61566
+ const gaPath = join47(root, ".gitattributes");
60265
61567
  try {
60266
- const existing = existsSync31(gaPath) ? readFileSync27(gaPath, "utf8") : "";
61568
+ const existing = existsSync32(gaPath) ? readFileSync29(gaPath, "utf8") : "";
60267
61569
  if (!existing.includes(".ultra11y/captures/")) {
60268
61570
  appendFileSync(gaPath, (existing && !existing.endsWith("\n") ? "\n" : "") + gaLine + "\n");
60269
61571
  console.log(lang === "fr" ? `.gitattributes : ajout\xE9 \xAB ${gaLine} \xBB` : `.gitattributes: added "${gaLine}"`);
@@ -60271,8 +61573,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
60271
61573
  } catch {
60272
61574
  }
60273
61575
  try {
60274
- const giPath = join45(root, ".gitignore");
60275
- if (existsSync31(giPath) && /^\s*\/?\.ultra11y(\/\**)?\/?\s*$/m.test(readFileSync27(giPath, "utf8")))
61576
+ const giPath = join47(root, ".gitignore");
61577
+ if (existsSync32(giPath) && /^\s*\/?\.ultra11y(\/\**)?\/?\s*$/m.test(readFileSync29(giPath, "utf8")))
60276
61578
  console.error(
60277
61579
  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/").'
60278
61580
  );
@@ -60282,8 +61584,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
60282
61584
  }
60283
61585
  if (p.flags.storybook === true || typeof p.flags.storybook === "string") {
60284
61586
  const sbDir = p.positionals[0] ?? "storybook-static";
60285
- const indexPath = existsSync31(join45(sbDir, "index.json")) ? join45(sbDir, "index.json") : join45(sbDir, "stories.json");
60286
- if (!existsSync31(indexPath)) {
61587
+ const indexPath = existsSync32(join47(sbDir, "index.json")) ? join47(sbDir, "index.json") : join47(sbDir, "stories.json");
61588
+ if (!existsSync32(indexPath)) {
60287
61589
  console.error(
60288
61590
  lang === "fr" ? `ultra11y render : aucun index Storybook (index.json/stories.json) dans ${sbDir}.` : `ultra11y render: no Storybook index (index.json/stories.json) in ${sbDir}.`
60289
61591
  );
@@ -60293,7 +61595,7 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
60293
61595
  const provById = new Map(stories.map((s) => [s.id, storyProvenance(s)]));
60294
61596
  const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
60295
61597
  const htmlDir = capturesFlag ?? sbDir;
60296
- const htmlFiles = existsSync31(htmlDir) ? discover([htmlDir]).files.filter((f) => /\.html?$/i.test(f)) : [];
61598
+ const htmlFiles = existsSync32(htmlDir) ? discover([htmlDir]).files.filter((f) => /\.html?$/i.test(f)) : [];
60297
61599
  const outDir = ".ultra11y/captures";
60298
61600
  let attributed = 0;
60299
61601
  let skipped = 0;
@@ -60315,8 +61617,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
60315
61617
  continue;
60316
61618
  }
60317
61619
  try {
60318
- mkdirSync15(outDir, { recursive: true });
60319
- writeFileSync17(join45(outDir, `${hitId}.html`), `${formatCaptureComment(prov)}
61620
+ mkdirSync17(outDir, { recursive: true });
61621
+ writeFileSync19(join47(outDir, `${hitId}.html`), `${formatCaptureComment(prov)}
60320
61622
  ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
60321
61623
  attributed++;
60322
61624
  } catch {
@@ -60338,11 +61640,11 @@ ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
60338
61640
  }
60339
61641
  if (p.flags.coverage === true) {
60340
61642
  const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
60341
- const capturesDir = capturesFlag ?? join45(root, ".ultra11y/captures");
61643
+ const capturesDir = capturesFlag ?? join47(root, ".ultra11y/captures");
60342
61644
  const graphExt = [...GRAPH_ONLY_EXT, ...asList(p.flags.ext) ?? []];
60343
61645
  const sourceFiles = discover([root], { include: asList(p.flags.include), exclude: asList(p.flags.exclude), ext: graphExt }).files;
60344
61646
  const graph = buildGraphStreaming(sourceFiles);
60345
- const capFiles = existsSync31(capturesDir) ? discover([capturesDir]).files : [];
61647
+ const capFiles = existsSync32(capturesDir) ? discover([capturesDir]).files : [];
60346
61648
  const entries = capFiles.map((f) => ({ file: toPosix(f), provenance: parseCaptureProvenance(readText(f)) }));
60347
61649
  const cov = computeCaptureCoverage(graph, entries);
60348
61650
  if (p.flags.json) console.log(JSON.stringify(cov, null, 2));
@@ -60350,15 +61652,15 @@ ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
60350
61652
  return 0;
60351
61653
  }
60352
61654
  let deps = {};
60353
- const pkgPath = join45(root, "package.json");
60354
- if (existsSync31(pkgPath)) {
61655
+ const pkgPath = join47(root, "package.json");
61656
+ if (existsSync32(pkgPath)) {
60355
61657
  try {
60356
61658
  const pkg = JSON.parse(readText(pkgPath));
60357
61659
  deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
60358
61660
  } catch {
60359
61661
  }
60360
61662
  }
60361
- const detection = detectFrameworks(deps, (f) => existsSync31(join45(root, f)));
61663
+ const detection = detectFrameworks(deps, (f) => existsSync32(join47(root, f)));
60362
61664
  if (p.flags.json) console.log(JSON.stringify(detection, null, 2));
60363
61665
  else console.log(renderPlan(detection, lang));
60364
61666
  return 0;
@@ -60557,9 +61859,9 @@ function applyAdjudicationFile(p, adj, lang) {
60557
61859
  return 1;
60558
61860
  }
60559
61861
  const out2 = typeof p.flags.out === "string" ? p.flags.out : ".";
60560
- mkdirSync15(out2, { recursive: true });
60561
- const auditPath = join45(out2, "audit-latest.json");
60562
- writeFileSync17(auditPath, JSON.stringify(r.audit, null, 2) + "\n");
61862
+ mkdirSync17(out2, { recursive: true });
61863
+ const auditPath = join47(out2, "audit-latest.json");
61864
+ writeFileSync19(auditPath, JSON.stringify(r.audit, null, 2) + "\n");
60563
61865
  if (p.flags.json)
60564
61866
  console.log(
60565
61867
  JSON.stringify(
@@ -60667,9 +61969,9 @@ async function cmdJudge(p) {
60667
61969
  if (r.issues.length > 40) console.error(` \u2026 +${r.issues.length - 40}`);
60668
61970
  return 1;
60669
61971
  }
60670
- mkdirSync15(out2, { recursive: true });
60671
- const auditPath = join45(out2, "audit-latest.json");
60672
- writeFileSync17(auditPath, `${JSON.stringify(r.audit, null, 2)}
61972
+ mkdirSync17(out2, { recursive: true });
61973
+ const auditPath = join47(out2, "audit-latest.json");
61974
+ writeFileSync19(auditPath, `${JSON.stringify(r.audit, null, 2)}
60673
61975
  `);
60674
61976
  console.log(
60675
61977
  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}`
@@ -60738,7 +62040,7 @@ async function cmdScan(p) {
60738
62040
  return 0;
60739
62041
  }
60740
62042
  for (const target of p.positionals.filter((a) => a !== "-")) {
60741
- if (/^https?:\/\//i.test(target) || existsSync31(target)) continue;
62043
+ if (/^https?:\/\//i.test(target) || existsSync32(target)) continue;
60742
62044
  console.error(
60743
62045
  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.`
60744
62046
  );
@@ -60865,7 +62167,7 @@ async function cmdScan(p) {
60865
62167
  }
60866
62168
  let merged = mergeDynamic(audit2, dynamic, lang);
60867
62169
  if (dynamic.snapshots?.length) {
60868
- const doms = dynamic.snapshots.map((id) => join45(snapshotRoot ?? ".", PAGES_DIR, id, "dom.html")).filter((f) => existsSync31(f));
62170
+ const doms = dynamic.snapshots.map((id) => join47(snapshotRoot ?? ".", PAGES_DIR, id, "dom.html")).filter((f) => existsSync32(f));
60869
62171
  if (doms.length) {
60870
62172
  const snapAudit = runAudit({ inputs: doms, onWarn: (m) => console.error(m) });
60871
62173
  merged = mergeSnapshotAudit(merged, snapAudit);
@@ -60876,12 +62178,12 @@ async function cmdScan(p) {
60876
62178
  attributePages(merged, scope);
60877
62179
  }
60878
62180
  }
60879
- mkdirSync15(out2, { recursive: true });
60880
- writeFileSync17(join45(out2, "audit-latest.json"), JSON.stringify(merged, null, 2) + "\n");
62181
+ mkdirSync17(out2, { recursive: true });
62182
+ writeFileSync19(join47(out2, "audit-latest.json"), JSON.stringify(merged, null, 2) + "\n");
60881
62183
  if (p.flags.json) console.log(JSON.stringify(merged, null, 2));
60882
62184
  else {
60883
62185
  console.log(
60884
- 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).`
62186
+ 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).`
60885
62187
  );
60886
62188
  if (dynamic.snapshots?.length)
60887
62189
  console.log(
@@ -61123,9 +62425,9 @@ async function cmdImport(p) {
61123
62425
  return 1;
61124
62426
  }
61125
62427
  if (outDir) {
61126
- mkdirSync15(outDir, { recursive: true });
61127
- const rawFile = join45(outDir, `external-${adapterId}-${arg}.raw.json`);
61128
- writeFileSync17(rawFile, rawText.endsWith("\n") ? rawText : `${rawText}
62428
+ mkdirSync17(outDir, { recursive: true });
62429
+ const rawFile = join47(outDir, `external-${adapterId}-${arg}.raw.json`);
62430
+ writeFileSync19(rawFile, rawText.endsWith("\n") ? rawText : `${rawText}
61129
62431
  `);
61130
62432
  if (!p.flags.json) console.log(rawFile);
61131
62433
  }
@@ -61159,9 +62461,9 @@ async function cmdImport(p) {
61159
62461
  return 0;
61160
62462
  }
61161
62463
  if (outDir) {
61162
- mkdirSync15(outDir, { recursive: true });
61163
- const file = join45(outDir, "external-latest.json");
61164
- writeFileSync17(file, `${JSON.stringify(audit2, null, 2)}
62464
+ mkdirSync17(outDir, { recursive: true });
62465
+ const file = join47(outDir, "external-latest.json");
62466
+ writeFileSync19(file, `${JSON.stringify(audit2, null, 2)}
61165
62467
  `);
61166
62468
  console.log(file);
61167
62469
  } else {
@@ -61266,7 +62568,7 @@ function cmdOrchestrate(p) {
61266
62568
  }
61267
62569
  const engineAbs = realpathSync6(fileURLToPath5(import.meta.url));
61268
62570
  if (p.flags.list === true) {
61269
- if (!existsSync31(runFlag)) {
62571
+ if (!existsSync32(runFlag)) {
61270
62572
  console.error(`ultra11y orchestrate: run dir not found: ${runFlag}.`);
61271
62573
  return 2;
61272
62574
  }
@@ -61293,7 +62595,7 @@ function cmdOrchestrate(p) {
61293
62595
  );
61294
62596
  } else {
61295
62597
  console.log(
61296
- lang === "fr" ? `Suivez ${join45(runFlag, "orchestration", "RUNBOOK.md")} s\xE9quentiellement (chemin \xE9co).` : `Follow ${join45(runFlag, "orchestration", "RUNBOOK.md")} sequentially (the eco path).`
62598
+ lang === "fr" ? `Suivez ${join47(runFlag, "orchestration", "RUNBOOK.md")} s\xE9quentiellement (chemin \xE9co).` : `Follow ${join47(runFlag, "orchestration", "RUNBOOK.md")} sequentially (the eco path).`
61297
62599
  );
61298
62600
  }
61299
62601
  if (p.flags.phase === void 0 && workflows.length === 0 && p.flags.eco !== true) {