ultra11y 5.15.0 → 5.15.1

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.
@@ -10,7 +10,7 @@
10
10
  "name": "ultra11y",
11
11
  "source": "./",
12
12
  "description": "Automatic accessibility review of the code under change before every commit, push and pull request, plus full WCAG 2.2 AA / RGAA audits and conformance deliverables on demand.",
13
- "version": "5.15.0",
13
+ "version": "5.15.1",
14
14
  "author": { "name": "maxgfr" },
15
15
  "homepage": "https://github.com/maxgfr/ultra11y",
16
16
  "repository": "https://github.com/maxgfr/ultra11y",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ultra11y",
3
3
  "description": "Accessibility for WCAG 2.2 AA and country standards (RGAA…): an automatic review of the code under change before every commit, push and pull request, plus full audits and conformance deliverables on demand.",
4
- "version": "5.15.0",
4
+ "version": "5.15.1",
5
5
  "author": { "name": "maxgfr" },
6
6
  "homepage": "https://github.com/maxgfr/ultra11y",
7
7
  "repository": "https://github.com/maxgfr/ultra11y",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ultra11y",
3
3
  "description": "Accessibility for WCAG 2.2 AA and country standards (RGAA…): an automatic review of the code under change before every commit, push and pull request, plus full audits and conformance deliverables on demand.",
4
- "version": "5.15.0",
4
+ "version": "5.15.1",
5
5
  "author": { "name": "maxgfr", "url": "https://github.com/maxgfr" },
6
6
  "homepage": "https://github.com/maxgfr/ultra11y",
7
7
  "repository": "https://github.com/maxgfr/ultra11y",
@@ -27,7 +27,7 @@ import { fileURLToPath } from "node:url";
27
27
  // Ownership marker: `ultra11y install --opencode` refuses to overwrite a plugin file that
28
28
  // does not carry it, so a user's own ultra11y.js is never clobbered.
29
29
  const ULTRA11Y_OPENCODE_PLUGIN = "ULTRA11Y_OPENCODE_PLUGIN";
30
- const ULTRA11Y_PLUGIN_VERSION = "5.15.0";
30
+ const ULTRA11Y_PLUGIN_VERSION = "5.15.1";
31
31
 
32
32
  const here = dirname(fileURLToPath(import.meta.url));
33
33
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ultra11y",
3
- "version": "5.15.0",
3
+ "version": "5.15.1",
4
4
  "description": "Audit HTML/CSS/JSX against WCAG 2.2 AA and produce a dated compliance report, or author/review accessible markup (native-HTML-first). A skills.sh agent skill: an install-free static engine (bundled, no npm install) plus the agent's judgment, with check/verify gates against hallucinated non-conformities, and pluggable in-repo standards packs (RGAA France first; Section 508 & EN 301 549 next).",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.33.0",
@@ -6,7 +6,7 @@ import { join as join51, relative as relative5, resolve as resolve16, sep as sep
6
6
  import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
7
7
 
8
8
  // src/types.ts
9
- var VERSION = "5.15.0";
9
+ var VERSION = "5.15.1";
10
10
  var SCHEMA_VERSION = 2;
11
11
  var INAPPLICABLE_STATUS = "C";
12
12
 
@@ -58678,6 +58678,9 @@ function readLedger(path) {
58678
58678
  return void 0;
58679
58679
  }
58680
58680
  }
58681
+ function unreadableCaptures(audit2, cwd = ".") {
58682
+ return (audit2.scope.pagesAudited ?? []).filter((id) => !existsSync21(join36(cwd, PAGES_DIR, id, "dom.html")));
58683
+ }
58681
58684
  function writeLedger(path, ledger) {
58682
58685
  mkdirSync10(dirname10(path), { recursive: true });
58683
58686
  const sorted = { ...ledger, entries: [...ledger.entries].sort((a, b) => a.criteriaId.localeCompare(b.criteriaId, "en", { numeric: true })) };
@@ -68713,6 +68716,11 @@ function applyAdjudicationFile(p, adj, lang) {
68713
68716
  writeFileSync20(auditPath, JSON.stringify(r.audit, null, 2) + "\n");
68714
68717
  const ledgerOut = ledgerTarget(p, adj.standard);
68715
68718
  if (ledgerOut) {
68719
+ const blind = unreadableCaptures(r.audit);
68720
+ if (blind.length)
68721
+ console.error(
68722
+ lang === "fr" ? `\u26A0 Registre \xE9crit sans les instantan\xE9s de ${blind.length} page(s) que cet audit dit avoir lues (${blind.slice(0, 5).join(", ")}${blind.length > 5 ? "\u2026" : ""}). L'\xE9vidence moissonn\xE9e ici est plus petite que celle qu'un run reconstruira, donc ces verdicts seront d\xE9clar\xE9s P\xC9RIM\xC9S au rejeu. Adjugez l\xE0 o\xF9 \`.ultra11y/pages/\` existe.` : `\u26A0 Ledger written without the snapshots of ${blind.length} page(s) this audit says it read (${blind.slice(0, 5).join(", ")}${blind.length > 5 ? "\u2026" : ""}). The evidence harvested here is smaller than the one a run will rebuild, so these verdicts will be dropped as STALE on replay. Adjudicate where \`.ultra11y/pages/\` exists.`
68723
+ );
68716
68724
  const refused = new Set(r.rejectedCriteria);
68717
68725
  const accepted = new Set(adj.items.map((it) => it.criteriaId).filter((id) => !refused.has(id)));
68718
68726
  const fresh = entriesFrom(adj, accepted, r.audit.date);
@@ -68845,6 +68853,11 @@ async function cmdJudge(p) {
68845
68853
  `);
68846
68854
  const ledgerOut = ledgerTarget(p, adj.standard);
68847
68855
  if (ledgerOut) {
68856
+ const blind = unreadableCaptures(r.audit);
68857
+ if (blind.length)
68858
+ console.error(
68859
+ lang === "fr" ? `\u26A0 Registre \xE9crit sans les instantan\xE9s de ${blind.length} page(s) que cet audit dit avoir lues (${blind.slice(0, 5).join(", ")}${blind.length > 5 ? "\u2026" : ""}). L'\xE9vidence moissonn\xE9e ici est plus petite que celle qu'un run reconstruira, donc ces verdicts seront d\xE9clar\xE9s P\xC9RIM\xC9S au rejeu. Adjugez l\xE0 o\xF9 \`.ultra11y/pages/\` existe.` : `\u26A0 Ledger written without the snapshots of ${blind.length} page(s) this audit says it read (${blind.slice(0, 5).join(", ")}${blind.length > 5 ? "\u2026" : ""}). The evidence harvested here is smaller than the one a run will rebuild, so these verdicts will be dropped as STALE on replay. Adjudicate where \`.ultra11y/pages/\` exists.`
68860
+ );
68848
68861
  const refused = new Set(r.rejectedCriteria);
68849
68862
  const accepted = new Set(adj.items.map((it) => it.criteriaId).filter((id) => !refused.has(id)));
68850
68863
  const fresh = entriesFrom(adj, accepted, r.audit.date);
@@ -5,7 +5,7 @@ when_to_use: "Invoke WITHOUT being asked whenever changed front-end code should
5
5
  allowed-tools: Bash(node ${CLAUDE_SKILL_DIR}/scripts/ultra11y.mjs *), Read, Grep, Glob
6
6
  license: MIT
7
7
  metadata:
8
- version: 5.15.0
8
+ version: 5.15.1
9
9
  ---
10
10
 
11
11
  # review-a11y — accessibility review of changed code
@@ -6,7 +6,7 @@ import { join as join51, relative as relative5, resolve as resolve16, sep as sep
6
6
  import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
7
7
 
8
8
  // src/types.ts
9
- var VERSION = "5.15.0";
9
+ var VERSION = "5.15.1";
10
10
  var SCHEMA_VERSION = 2;
11
11
  var INAPPLICABLE_STATUS = "C";
12
12
 
@@ -58678,6 +58678,9 @@ function readLedger(path) {
58678
58678
  return void 0;
58679
58679
  }
58680
58680
  }
58681
+ function unreadableCaptures(audit2, cwd = ".") {
58682
+ return (audit2.scope.pagesAudited ?? []).filter((id) => !existsSync21(join36(cwd, PAGES_DIR, id, "dom.html")));
58683
+ }
58681
58684
  function writeLedger(path, ledger) {
58682
58685
  mkdirSync10(dirname10(path), { recursive: true });
58683
58686
  const sorted = { ...ledger, entries: [...ledger.entries].sort((a, b) => a.criteriaId.localeCompare(b.criteriaId, "en", { numeric: true })) };
@@ -68713,6 +68716,11 @@ function applyAdjudicationFile(p, adj, lang) {
68713
68716
  writeFileSync20(auditPath, JSON.stringify(r.audit, null, 2) + "\n");
68714
68717
  const ledgerOut = ledgerTarget(p, adj.standard);
68715
68718
  if (ledgerOut) {
68719
+ const blind = unreadableCaptures(r.audit);
68720
+ if (blind.length)
68721
+ console.error(
68722
+ lang === "fr" ? `\u26A0 Registre \xE9crit sans les instantan\xE9s de ${blind.length} page(s) que cet audit dit avoir lues (${blind.slice(0, 5).join(", ")}${blind.length > 5 ? "\u2026" : ""}). L'\xE9vidence moissonn\xE9e ici est plus petite que celle qu'un run reconstruira, donc ces verdicts seront d\xE9clar\xE9s P\xC9RIM\xC9S au rejeu. Adjugez l\xE0 o\xF9 \`.ultra11y/pages/\` existe.` : `\u26A0 Ledger written without the snapshots of ${blind.length} page(s) this audit says it read (${blind.slice(0, 5).join(", ")}${blind.length > 5 ? "\u2026" : ""}). The evidence harvested here is smaller than the one a run will rebuild, so these verdicts will be dropped as STALE on replay. Adjudicate where \`.ultra11y/pages/\` exists.`
68723
+ );
68716
68724
  const refused = new Set(r.rejectedCriteria);
68717
68725
  const accepted = new Set(adj.items.map((it) => it.criteriaId).filter((id) => !refused.has(id)));
68718
68726
  const fresh = entriesFrom(adj, accepted, r.audit.date);
@@ -68845,6 +68853,11 @@ async function cmdJudge(p) {
68845
68853
  `);
68846
68854
  const ledgerOut = ledgerTarget(p, adj.standard);
68847
68855
  if (ledgerOut) {
68856
+ const blind = unreadableCaptures(r.audit);
68857
+ if (blind.length)
68858
+ console.error(
68859
+ lang === "fr" ? `\u26A0 Registre \xE9crit sans les instantan\xE9s de ${blind.length} page(s) que cet audit dit avoir lues (${blind.slice(0, 5).join(", ")}${blind.length > 5 ? "\u2026" : ""}). L'\xE9vidence moissonn\xE9e ici est plus petite que celle qu'un run reconstruira, donc ces verdicts seront d\xE9clar\xE9s P\xC9RIM\xC9S au rejeu. Adjugez l\xE0 o\xF9 \`.ultra11y/pages/\` existe.` : `\u26A0 Ledger written without the snapshots of ${blind.length} page(s) this audit says it read (${blind.slice(0, 5).join(", ")}${blind.length > 5 ? "\u2026" : ""}). The evidence harvested here is smaller than the one a run will rebuild, so these verdicts will be dropped as STALE on replay. Adjudicate where \`.ultra11y/pages/\` exists.`
68860
+ );
68848
68861
  const refused = new Set(r.rejectedCriteria);
68849
68862
  const accepted = new Set(adj.items.map((it) => it.criteriaId).filter((id) => !refused.has(id)));
68850
68863
  const fresh = entriesFrom(adj, accepted, r.audit.date);
@@ -4,7 +4,7 @@ description: "Use to AUDIT existing HTML/CSS/JSX against WCAG 2.2 AA accessibili
4
4
  when_to_use: "Invoke when the user asks for an accessibility AUDIT or a formal deliverable: audit a repo, site or page against WCAG 2.2 AA or a country standard (RGAA, Section 508, EN 301 549); produce a dated conformance report, a criterion grid, a PRD backlog, or tickets (GitHub/GitLab/Jira); author accessible markup from scratch; wire the repo gate (init --hook/--ci) or a rendered-DOM capture pipeline. For a review of the code UNDER CHANGE (diff, branch, PR), use the `review-a11y` skill instead."
5
5
  license: MIT
6
6
  metadata:
7
- version: 5.15.0
7
+ version: 5.15.1
8
8
  ---
9
9
 
10
10
  # ultra11y — audit WCAG 2.2 AA and write accessible markup
@@ -6,7 +6,7 @@ import { join as join51, relative as relative5, resolve as resolve16, sep as sep
6
6
  import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
7
7
 
8
8
  // src/types.ts
9
- var VERSION = "5.15.0";
9
+ var VERSION = "5.15.1";
10
10
  var SCHEMA_VERSION = 2;
11
11
  var INAPPLICABLE_STATUS = "C";
12
12
 
@@ -58678,6 +58678,9 @@ function readLedger(path) {
58678
58678
  return void 0;
58679
58679
  }
58680
58680
  }
58681
+ function unreadableCaptures(audit2, cwd = ".") {
58682
+ return (audit2.scope.pagesAudited ?? []).filter((id) => !existsSync21(join36(cwd, PAGES_DIR, id, "dom.html")));
58683
+ }
58681
58684
  function writeLedger(path, ledger) {
58682
58685
  mkdirSync10(dirname10(path), { recursive: true });
58683
58686
  const sorted = { ...ledger, entries: [...ledger.entries].sort((a, b) => a.criteriaId.localeCompare(b.criteriaId, "en", { numeric: true })) };
@@ -68713,6 +68716,11 @@ function applyAdjudicationFile(p, adj, lang) {
68713
68716
  writeFileSync20(auditPath, JSON.stringify(r.audit, null, 2) + "\n");
68714
68717
  const ledgerOut = ledgerTarget(p, adj.standard);
68715
68718
  if (ledgerOut) {
68719
+ const blind = unreadableCaptures(r.audit);
68720
+ if (blind.length)
68721
+ console.error(
68722
+ lang === "fr" ? `\u26A0 Registre \xE9crit sans les instantan\xE9s de ${blind.length} page(s) que cet audit dit avoir lues (${blind.slice(0, 5).join(", ")}${blind.length > 5 ? "\u2026" : ""}). L'\xE9vidence moissonn\xE9e ici est plus petite que celle qu'un run reconstruira, donc ces verdicts seront d\xE9clar\xE9s P\xC9RIM\xC9S au rejeu. Adjugez l\xE0 o\xF9 \`.ultra11y/pages/\` existe.` : `\u26A0 Ledger written without the snapshots of ${blind.length} page(s) this audit says it read (${blind.slice(0, 5).join(", ")}${blind.length > 5 ? "\u2026" : ""}). The evidence harvested here is smaller than the one a run will rebuild, so these verdicts will be dropped as STALE on replay. Adjudicate where \`.ultra11y/pages/\` exists.`
68723
+ );
68716
68724
  const refused = new Set(r.rejectedCriteria);
68717
68725
  const accepted = new Set(adj.items.map((it) => it.criteriaId).filter((id) => !refused.has(id)));
68718
68726
  const fresh = entriesFrom(adj, accepted, r.audit.date);
@@ -68845,6 +68853,11 @@ async function cmdJudge(p) {
68845
68853
  `);
68846
68854
  const ledgerOut = ledgerTarget(p, adj.standard);
68847
68855
  if (ledgerOut) {
68856
+ const blind = unreadableCaptures(r.audit);
68857
+ if (blind.length)
68858
+ console.error(
68859
+ lang === "fr" ? `\u26A0 Registre \xE9crit sans les instantan\xE9s de ${blind.length} page(s) que cet audit dit avoir lues (${blind.slice(0, 5).join(", ")}${blind.length > 5 ? "\u2026" : ""}). L'\xE9vidence moissonn\xE9e ici est plus petite que celle qu'un run reconstruira, donc ces verdicts seront d\xE9clar\xE9s P\xC9RIM\xC9S au rejeu. Adjugez l\xE0 o\xF9 \`.ultra11y/pages/\` existe.` : `\u26A0 Ledger written without the snapshots of ${blind.length} page(s) this audit says it read (${blind.slice(0, 5).join(", ")}${blind.length > 5 ? "\u2026" : ""}). The evidence harvested here is smaller than the one a run will rebuild, so these verdicts will be dropped as STALE on replay. Adjudicate where \`.ultra11y/pages/\` exists.`
68860
+ );
68848
68861
  const refused = new Set(r.rejectedCriteria);
68849
68862
  const accepted = new Set(adj.items.map((it) => it.criteriaId).filter((id) => !refused.has(id)));
68850
68863
  const fresh = entriesFrom(adj, accepted, r.audit.date);
package/src/cli.ts CHANGED
@@ -50,7 +50,7 @@ import {
50
50
  hydrateAdjudication,
51
51
  type AdjudicationFile,
52
52
  } from "./adjudicate.js";
53
- import { entriesFrom, isLedger, ledgerPath, mergeLedger, readLedger, replayLedger, type VerdictLedger, writeLedger } from "./ledger.js";
53
+ import { entriesFrom, isLedger, ledgerPath, mergeLedger, readLedger, replayLedger, unreadableCaptures, type VerdictLedger, writeLedger } from "./ledger.js";
54
54
  import { BATCH_SIZE, apiKeyFromEnv, applyRawVerdicts, judgeAll, modelFromEnv } from "./llm.js";
55
55
  import { runScan, runScanMany, runCrawlScan, runSampleScan, mergeDynamic, mergeSnapshotAudit, cleanDynamic, dockerAvailable } from "./scan.js";
56
56
  import { runScanLocal, runScanManyLocal, runCrawlScanLocal, runSampleScanLocal, localAvailable, localTierStatus } from "./scan-local.js";
@@ -2940,6 +2940,25 @@ function applyAdjudicationFile(p: ParsedArgs, adj: AdjudicationFile, lang: Lang)
2940
2940
  // next replay, which is the whole thing the gate exists to prevent.
2941
2941
  const ledgerOut = ledgerTarget(p, adj.standard);
2942
2942
  if (ledgerOut) {
2943
+ // A LEDGER WRITTEN WITHOUT THE CAPTURES IS STALE ON ARRIVAL.
2944
+ //
2945
+ // The fingerprint is over the evidence the harvest READ FROM DISK, so recording a verdict
2946
+ // where the audit's page captures are absent fingerprints a strictly smaller set than the
2947
+ // one CI rebuilds — and the replay drops it as stale, every run, silently. Measured on a
2948
+ // real repository: RGAA 12.3 recorded over 13 evidence items where the run harvests 22.
2949
+ // The entry looked well-formed, the fold accepted it, it was committed and reviewed, and
2950
+ // it never once replayed.
2951
+ //
2952
+ // A warning, not a refusal: the verdict itself is sound and still lands on THIS audit, and
2953
+ // a caller who genuinely has no captures (a source-only repository) has nothing to fix.
2954
+ // What must not happen is writing it and saying nothing.
2955
+ const blind = unreadableCaptures(r.audit);
2956
+ if (blind.length)
2957
+ console.error(
2958
+ lang === "fr"
2959
+ ? `⚠ Registre écrit sans les instantanés de ${blind.length} page(s) que cet audit dit avoir lues (${blind.slice(0, 5).join(", ")}${blind.length > 5 ? "…" : ""}). L'évidence moissonnée ici est plus petite que celle qu'un run reconstruira, donc ces verdicts seront déclarés PÉRIMÉS au rejeu. Adjugez là où \`.ultra11y/pages/\` existe.`
2960
+ : `⚠ Ledger written without the snapshots of ${blind.length} page(s) this audit says it read (${blind.slice(0, 5).join(", ")}${blind.length > 5 ? "…" : ""}). The evidence harvested here is smaller than the one a run will rebuild, so these verdicts will be dropped as STALE on replay. Adjudicate where \`.ultra11y/pages/\` exists.`,
2961
+ );
2943
2962
  const refused = new Set(r.rejectedCriteria);
2944
2963
  const accepted = new Set(adj.items.map((it) => it.criteriaId).filter((id) => !refused.has(id)));
2945
2964
  const fresh = entriesFrom(adj, accepted, r.audit.date);
@@ -3129,6 +3148,25 @@ async function cmdJudge(p: ParsedArgs): Promise<number> {
3129
3148
  // run can replay it for free.
3130
3149
  const ledgerOut = ledgerTarget(p, adj.standard);
3131
3150
  if (ledgerOut) {
3151
+ // A LEDGER WRITTEN WITHOUT THE CAPTURES IS STALE ON ARRIVAL.
3152
+ //
3153
+ // The fingerprint is over the evidence the harvest READ FROM DISK, so recording a verdict
3154
+ // where the audit's page captures are absent fingerprints a strictly smaller set than the
3155
+ // one CI rebuilds — and the replay drops it as stale, every run, silently. Measured on a
3156
+ // real repository: RGAA 12.3 recorded over 13 evidence items where the run harvests 22.
3157
+ // The entry looked well-formed, the fold accepted it, it was committed and reviewed, and
3158
+ // it never once replayed.
3159
+ //
3160
+ // A warning, not a refusal: the verdict itself is sound and still lands on THIS audit, and
3161
+ // a caller who genuinely has no captures (a source-only repository) has nothing to fix.
3162
+ // What must not happen is writing it and saying nothing.
3163
+ const blind = unreadableCaptures(r.audit);
3164
+ if (blind.length)
3165
+ console.error(
3166
+ lang === "fr"
3167
+ ? `⚠ Registre écrit sans les instantanés de ${blind.length} page(s) que cet audit dit avoir lues (${blind.slice(0, 5).join(", ")}${blind.length > 5 ? "…" : ""}). L'évidence moissonnée ici est plus petite que celle qu'un run reconstruira, donc ces verdicts seront déclarés PÉRIMÉS au rejeu. Adjugez là où \`.ultra11y/pages/\` existe.`
3168
+ : `⚠ Ledger written without the snapshots of ${blind.length} page(s) this audit says it read (${blind.slice(0, 5).join(", ")}${blind.length > 5 ? "…" : ""}). The evidence harvested here is smaller than the one a run will rebuild, so these verdicts will be dropped as STALE on replay. Adjudicate where \`.ultra11y/pages/\` exists.`,
3169
+ );
3132
3170
  const refused = new Set(r.rejectedCriteria);
3133
3171
  const accepted = new Set(adj.items.map((it) => it.criteriaId).filter((id) => !refused.has(id)));
3134
3172
  const fresh = entriesFrom(adj, accepted, r.audit.date);
package/src/ledger.ts CHANGED
@@ -31,6 +31,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
31
31
  import { dirname, join } from "node:path";
32
32
 
33
33
  import { type AdjudicationFile, type AdjudicationItem, type AgentFinding, buildAdjudicationWorklist, type Evidence, readCitation } from "./adjudicate.js";
34
+ import { PAGES_DIR } from "./snapshot.js";
34
35
  import { CORE, type StandardId } from "./standards/index.js";
35
36
  import type { AuditResult } from "./types.js";
36
37
  import { SCHEMA_VERSION } from "./types.js";
@@ -127,6 +128,25 @@ export function readLedger(path: string): VerdictLedger | undefined {
127
128
  }
128
129
  }
129
130
 
131
+ /** Would a ledger written from THIS harvest be replayable? Names what is missing when not.
132
+ *
133
+ * A ledger entry is keyed on a fingerprint of the evidence the verdict was ruled against, and
134
+ * the harvest RE-READS the audited files from disk. So a verdict recorded where the audit's
135
+ * page captures are absent is fingerprinted over a strictly smaller evidence set than the one
136
+ * CI will rebuild — and it is stale on arrival, every time, silently.
137
+ *
138
+ * Measured while adjudicating a real repository from a checkout with no `.ultra11y/pages/`:
139
+ * RGAA 12.3 was recorded over 13 evidence items where the run harvests 22. The entry looked
140
+ * perfectly well-formed, the fold accepted it, the file was committed and reviewed — and the
141
+ * replay dropped it as stale on every subsequent run, with nothing anywhere saying why.
142
+ *
143
+ * Returns the page ids the audit says it read and whose capture is not on disk. Empty when
144
+ * there is nothing to warn about — including on a source-only audit, which legitimately has
145
+ * no captures to miss. */
146
+ export function unreadableCaptures(audit: AuditResult, cwd = "."): string[] {
147
+ return (audit.scope.pagesAudited ?? []).filter((id) => !existsSync(join(cwd, PAGES_DIR, id, "dom.html")));
148
+ }
149
+
130
150
  export function writeLedger(path: string, ledger: VerdictLedger): void {
131
151
  mkdirSync(dirname(path), { recursive: true });
132
152
  // Entries id-sorted so the committed file has a stable diff — a ledger that reshuffles on
package/src/types.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // Single source of truth for shared types + the embedded version. sync-version.mjs
2
2
  // rewrites VERSION at release time (kept in lockstep with package.json + SKILL.md).
3
- export const VERSION = "5.15.0";
3
+ export const VERSION = "5.15.1";
4
4
  export const SCHEMA_VERSION = 2;
5
5
 
6
6
  // Lang = the UI FRAME's language — the `L` localization tables each module (report,