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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.opencode/plugins/ultra11y.js +1 -1
- package/package.json +1 -1
- package/scripts/ultra11y.mjs +14 -1
- package/skills/review-a11y/SKILL.md +1 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +14 -1
- package/skills/ultra11y/SKILL.md +1 -1
- package/skills/ultra11y/scripts/ultra11y.mjs +14 -1
- package/src/cli.ts +39 -1
- package/src/ledger.ts +20 -0
- package/src/types.ts +1 -1
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|
package/scripts/ultra11y.mjs
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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/skills/ultra11y/SKILL.md
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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,
|