ultra11y 5.3.3 → 5.3.4
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 +30 -5
- package/skills/review-a11y/SKILL.md +1 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +30 -5
- package/skills/ultra11y/SKILL.md +1 -1
- package/skills/ultra11y/references/judgment.md +25 -0
- package/skills/ultra11y/scripts/ultra11y.mjs +30 -5
- package/src/adjudicate.ts +70 -4
- package/src/cli.ts +13 -1
- 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.3.
|
|
13
|
+
"version": "5.3.4",
|
|
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.3.
|
|
4
|
+
"version": "5.3.4",
|
|
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.3.
|
|
4
|
+
"version": "5.3.4",
|
|
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.3.
|
|
30
|
+
const ULTRA11Y_PLUGIN_VERSION = "5.3.4";
|
|
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.3.
|
|
3
|
+
"version": "5.3.4",
|
|
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 join50, relative as relative5, resolve as resolve15, 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.3.
|
|
9
|
+
var VERSION = "5.3.4";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
|
|
12
12
|
// src/audit.ts
|
|
@@ -56773,11 +56773,11 @@ function packGuidanceBlock(standard, criterionId, lang) {
|
|
|
56773
56773
|
function plainTest(s) {
|
|
56774
56774
|
return s.replace(/\[([^\]]+)\]\(#[^)]*\)/g, "$1");
|
|
56775
56775
|
}
|
|
56776
|
-
function formatAdjudication(items, lang = "en", standard = CORE2) {
|
|
56776
|
+
function formatAdjudication(items, lang = "en", standard = CORE2, opts = {}) {
|
|
56777
56777
|
const s = T2[lang];
|
|
56778
56778
|
const pack = isCore(standard) ? void 0 : loadPack(standard);
|
|
56779
|
-
const out2 = [s.title, "", s.intro, "", ...s.verdicts, "", s.rule, "", s.then, ""];
|
|
56780
|
-
if (pack) out2.push(`> ${s.packIntro(pack.name)}`, "");
|
|
56779
|
+
const out2 = opts.preamble === false ? [] : [s.title, "", s.intro, "", ...s.verdicts, "", s.rule, "", s.then, ""];
|
|
56780
|
+
if (pack && opts.preamble !== false) out2.push(`> ${s.packIntro(pack.name)}`, "");
|
|
56781
56781
|
for (const it of items) {
|
|
56782
56782
|
out2.push(`## ${pack ? `${pack.name} ` : ""}${it.criteriaId}${it.title ? ` \u2014 ${it.title}` : ""} _(${it.automatability})_`);
|
|
56783
56783
|
out2.push("", `> ${s.evidence} (${it.evidence.length}${it.evidenceTruncated ? ` / ${it.evidenceTruncated.total}` : ""}):`, "");
|
|
@@ -56838,7 +56838,31 @@ function writeAdjudication(items, outDir, opts) {
|
|
|
56838
56838
|
};
|
|
56839
56839
|
writeFileSync10(todoPath, JSON.stringify(file, null, 2) + "\n");
|
|
56840
56840
|
writeFileSync10(mdPath, formatAdjudication(items, opts.lang ?? "en", opts.standard));
|
|
56841
|
-
|
|
56841
|
+
const verdictsPath = join35(outDir, "ADJUDICATE.verdicts.json");
|
|
56842
|
+
writeFileSync10(verdictsPath, JSON.stringify({ ...file, items: slimAdjudicationItems(items) }, null, 2) + "\n");
|
|
56843
|
+
const itemsDir = join35(outDir, "adjudicate");
|
|
56844
|
+
mkdirSync9(itemsDir, { recursive: true });
|
|
56845
|
+
for (const it of items) {
|
|
56846
|
+
writeFileSync10(join35(itemsDir, `${it.criteriaId}.md`), formatAdjudication([it], opts.lang ?? "en", opts.standard, { preamble: false }));
|
|
56847
|
+
}
|
|
56848
|
+
return { todoPath, mdPath, verdictsPath, itemsDir, count: items.length };
|
|
56849
|
+
}
|
|
56850
|
+
function slimAdjudicationItems(items) {
|
|
56851
|
+
return items.map((it) => {
|
|
56852
|
+
const { evidence: _evidence, evidenceTruncated: _truncated, ...rest } = it;
|
|
56853
|
+
return { ...rest, evidence: [] };
|
|
56854
|
+
});
|
|
56855
|
+
}
|
|
56856
|
+
function hydrateAdjudication(adj, audit2, opts = {}) {
|
|
56857
|
+
const needs = adj.items.filter((it) => !it.evidence || it.evidence.length === 0);
|
|
56858
|
+
if (!needs.length) return;
|
|
56859
|
+
const derived = new Map(buildAdjudicationWorklist(audit2, { ...opts, standard: adj.standard }).map((it) => [it.criteriaId, it]));
|
|
56860
|
+
for (const it of needs) {
|
|
56861
|
+
const full = derived.get(it.criteriaId);
|
|
56862
|
+
if (!full) continue;
|
|
56863
|
+
it.evidence = full.evidence;
|
|
56864
|
+
if (full.evidenceTruncated) it.evidenceTruncated = full.evidenceTruncated;
|
|
56865
|
+
}
|
|
56842
56866
|
}
|
|
56843
56867
|
|
|
56844
56868
|
// src/llm.ts
|
|
@@ -66559,6 +66583,7 @@ function applyAdjudicationFile(p, adj, lang) {
|
|
|
66559
66583
|
console.error(`ultra11y verify: --in file not found or not valid JSON: ${inFlag}.`);
|
|
66560
66584
|
return 2;
|
|
66561
66585
|
}
|
|
66586
|
+
hydrateAdjudication(adj, audit2, { cwd: typeof p.flags.cwd === "string" ? p.flags.cwd : void 0 });
|
|
66562
66587
|
const r = applyAdjudication(audit2, adj);
|
|
66563
66588
|
if (!r.ok) {
|
|
66564
66589
|
if (p.flags.json) console.log(JSON.stringify(r, null, 2));
|
|
@@ -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.3.
|
|
8
|
+
version: 5.3.4
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# review-a11y — accessibility review of changed code
|
|
@@ -6,7 +6,7 @@ import { join as join50, relative as relative5, resolve as resolve15, 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.3.
|
|
9
|
+
var VERSION = "5.3.4";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
|
|
12
12
|
// src/audit.ts
|
|
@@ -56773,11 +56773,11 @@ function packGuidanceBlock(standard, criterionId, lang) {
|
|
|
56773
56773
|
function plainTest(s) {
|
|
56774
56774
|
return s.replace(/\[([^\]]+)\]\(#[^)]*\)/g, "$1");
|
|
56775
56775
|
}
|
|
56776
|
-
function formatAdjudication(items, lang = "en", standard = CORE2) {
|
|
56776
|
+
function formatAdjudication(items, lang = "en", standard = CORE2, opts = {}) {
|
|
56777
56777
|
const s = T2[lang];
|
|
56778
56778
|
const pack = isCore(standard) ? void 0 : loadPack(standard);
|
|
56779
|
-
const out2 = [s.title, "", s.intro, "", ...s.verdicts, "", s.rule, "", s.then, ""];
|
|
56780
|
-
if (pack) out2.push(`> ${s.packIntro(pack.name)}`, "");
|
|
56779
|
+
const out2 = opts.preamble === false ? [] : [s.title, "", s.intro, "", ...s.verdicts, "", s.rule, "", s.then, ""];
|
|
56780
|
+
if (pack && opts.preamble !== false) out2.push(`> ${s.packIntro(pack.name)}`, "");
|
|
56781
56781
|
for (const it of items) {
|
|
56782
56782
|
out2.push(`## ${pack ? `${pack.name} ` : ""}${it.criteriaId}${it.title ? ` \u2014 ${it.title}` : ""} _(${it.automatability})_`);
|
|
56783
56783
|
out2.push("", `> ${s.evidence} (${it.evidence.length}${it.evidenceTruncated ? ` / ${it.evidenceTruncated.total}` : ""}):`, "");
|
|
@@ -56838,7 +56838,31 @@ function writeAdjudication(items, outDir, opts) {
|
|
|
56838
56838
|
};
|
|
56839
56839
|
writeFileSync10(todoPath, JSON.stringify(file, null, 2) + "\n");
|
|
56840
56840
|
writeFileSync10(mdPath, formatAdjudication(items, opts.lang ?? "en", opts.standard));
|
|
56841
|
-
|
|
56841
|
+
const verdictsPath = join35(outDir, "ADJUDICATE.verdicts.json");
|
|
56842
|
+
writeFileSync10(verdictsPath, JSON.stringify({ ...file, items: slimAdjudicationItems(items) }, null, 2) + "\n");
|
|
56843
|
+
const itemsDir = join35(outDir, "adjudicate");
|
|
56844
|
+
mkdirSync9(itemsDir, { recursive: true });
|
|
56845
|
+
for (const it of items) {
|
|
56846
|
+
writeFileSync10(join35(itemsDir, `${it.criteriaId}.md`), formatAdjudication([it], opts.lang ?? "en", opts.standard, { preamble: false }));
|
|
56847
|
+
}
|
|
56848
|
+
return { todoPath, mdPath, verdictsPath, itemsDir, count: items.length };
|
|
56849
|
+
}
|
|
56850
|
+
function slimAdjudicationItems(items) {
|
|
56851
|
+
return items.map((it) => {
|
|
56852
|
+
const { evidence: _evidence, evidenceTruncated: _truncated, ...rest } = it;
|
|
56853
|
+
return { ...rest, evidence: [] };
|
|
56854
|
+
});
|
|
56855
|
+
}
|
|
56856
|
+
function hydrateAdjudication(adj, audit2, opts = {}) {
|
|
56857
|
+
const needs = adj.items.filter((it) => !it.evidence || it.evidence.length === 0);
|
|
56858
|
+
if (!needs.length) return;
|
|
56859
|
+
const derived = new Map(buildAdjudicationWorklist(audit2, { ...opts, standard: adj.standard }).map((it) => [it.criteriaId, it]));
|
|
56860
|
+
for (const it of needs) {
|
|
56861
|
+
const full = derived.get(it.criteriaId);
|
|
56862
|
+
if (!full) continue;
|
|
56863
|
+
it.evidence = full.evidence;
|
|
56864
|
+
if (full.evidenceTruncated) it.evidenceTruncated = full.evidenceTruncated;
|
|
56865
|
+
}
|
|
56842
56866
|
}
|
|
56843
56867
|
|
|
56844
56868
|
// src/llm.ts
|
|
@@ -66559,6 +66583,7 @@ function applyAdjudicationFile(p, adj, lang) {
|
|
|
66559
66583
|
console.error(`ultra11y verify: --in file not found or not valid JSON: ${inFlag}.`);
|
|
66560
66584
|
return 2;
|
|
66561
66585
|
}
|
|
66586
|
+
hydrateAdjudication(adj, audit2, { cwd: typeof p.flags.cwd === "string" ? p.flags.cwd : void 0 });
|
|
66562
66587
|
const r = applyAdjudication(audit2, adj);
|
|
66563
66588
|
if (!r.ok) {
|
|
66564
66589
|
if (p.flags.json) console.log(JSON.stringify(r, null, 2));
|
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.3.
|
|
7
|
+
version: 5.3.4
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# ultra11y — audit WCAG 2.2 AA and write accessible markup
|
|
@@ -71,6 +71,31 @@ test).
|
|
|
71
71
|
the **produced** HTML (see `render` / audit the build), otherwise the verdict is a false
|
|
72
72
|
negative.
|
|
73
73
|
|
|
74
|
+
## When the adjudicator has no shell (CI)
|
|
75
|
+
|
|
76
|
+
`verify --manual` writes the worklist **twice**, plus one brief per criterion:
|
|
77
|
+
|
|
78
|
+
| file | for |
|
|
79
|
+
|---|---|
|
|
80
|
+
| `ADJUDICATE.todo.json` · `ADJUDICATE.md` | a session **with a shell**: evidence inline, edit in place, fold yourself |
|
|
81
|
+
| `ADJUDICATE.verdicts.json` | the **only file to write** when you have no shell — verdicts, no evidence |
|
|
82
|
+
| `adjudicate/<criteriaId>.md` | one small brief per criterion: its evidence, its protocol, its citable tests |
|
|
83
|
+
|
|
84
|
+
Why the split exists, measured rather than assumed: under RGAA the worklist is 96 criteria
|
|
85
|
+
carrying 1590 harvested anchors — **536 KB** of JSON and **466 KB** of Markdown. An agent given
|
|
86
|
+
`Read/Grep/Glob/Edit/Write` cannot work with that: reading either document swamps its context,
|
|
87
|
+
and filling 96 verdicts inside half a megabyte is 96 exact-match edits. A real CI run spent 75
|
|
88
|
+
of 424 turns, hit 17 permission denials trying to run the commands the runbook prescribes, and
|
|
89
|
+
returned the file untouched — so the fail-closed fold discarded all 96 verdicts and every
|
|
90
|
+
criterion stayed « to assess », in a job that reported success. The verdicts file is **37 KB**
|
|
91
|
+
for the same worklist, and each brief is a few KB.
|
|
92
|
+
|
|
93
|
+
`verify --apply` accepts either file. Given the verdicts-only one it **re-derives the evidence
|
|
94
|
+
from the audit** (the worklist is a pure function of the audit) and then runs the identical
|
|
95
|
+
fold: same coverage checks in both directions, same citation matching against *that criterion's*
|
|
96
|
+
own anchors, same refusals. The smaller surface buys nothing from the gate — a `C` that cites
|
|
97
|
+
an anchor it was never shown is refused exactly as before.
|
|
98
|
+
|
|
74
99
|
## Adversarial verification of the non-conformities — `verify --report`
|
|
75
100
|
|
|
76
101
|
Unchanged: `node scripts/ultra11y.mjs verify --report audits/wcag-YYYY-MM-DD.md` writes `VERIFY.md`
|
|
@@ -6,7 +6,7 @@ import { join as join50, relative as relative5, resolve as resolve15, 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.3.
|
|
9
|
+
var VERSION = "5.3.4";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
|
|
12
12
|
// src/audit.ts
|
|
@@ -56773,11 +56773,11 @@ function packGuidanceBlock(standard, criterionId, lang) {
|
|
|
56773
56773
|
function plainTest(s) {
|
|
56774
56774
|
return s.replace(/\[([^\]]+)\]\(#[^)]*\)/g, "$1");
|
|
56775
56775
|
}
|
|
56776
|
-
function formatAdjudication(items, lang = "en", standard = CORE2) {
|
|
56776
|
+
function formatAdjudication(items, lang = "en", standard = CORE2, opts = {}) {
|
|
56777
56777
|
const s = T2[lang];
|
|
56778
56778
|
const pack = isCore(standard) ? void 0 : loadPack(standard);
|
|
56779
|
-
const out2 = [s.title, "", s.intro, "", ...s.verdicts, "", s.rule, "", s.then, ""];
|
|
56780
|
-
if (pack) out2.push(`> ${s.packIntro(pack.name)}`, "");
|
|
56779
|
+
const out2 = opts.preamble === false ? [] : [s.title, "", s.intro, "", ...s.verdicts, "", s.rule, "", s.then, ""];
|
|
56780
|
+
if (pack && opts.preamble !== false) out2.push(`> ${s.packIntro(pack.name)}`, "");
|
|
56781
56781
|
for (const it of items) {
|
|
56782
56782
|
out2.push(`## ${pack ? `${pack.name} ` : ""}${it.criteriaId}${it.title ? ` \u2014 ${it.title}` : ""} _(${it.automatability})_`);
|
|
56783
56783
|
out2.push("", `> ${s.evidence} (${it.evidence.length}${it.evidenceTruncated ? ` / ${it.evidenceTruncated.total}` : ""}):`, "");
|
|
@@ -56838,7 +56838,31 @@ function writeAdjudication(items, outDir, opts) {
|
|
|
56838
56838
|
};
|
|
56839
56839
|
writeFileSync10(todoPath, JSON.stringify(file, null, 2) + "\n");
|
|
56840
56840
|
writeFileSync10(mdPath, formatAdjudication(items, opts.lang ?? "en", opts.standard));
|
|
56841
|
-
|
|
56841
|
+
const verdictsPath = join35(outDir, "ADJUDICATE.verdicts.json");
|
|
56842
|
+
writeFileSync10(verdictsPath, JSON.stringify({ ...file, items: slimAdjudicationItems(items) }, null, 2) + "\n");
|
|
56843
|
+
const itemsDir = join35(outDir, "adjudicate");
|
|
56844
|
+
mkdirSync9(itemsDir, { recursive: true });
|
|
56845
|
+
for (const it of items) {
|
|
56846
|
+
writeFileSync10(join35(itemsDir, `${it.criteriaId}.md`), formatAdjudication([it], opts.lang ?? "en", opts.standard, { preamble: false }));
|
|
56847
|
+
}
|
|
56848
|
+
return { todoPath, mdPath, verdictsPath, itemsDir, count: items.length };
|
|
56849
|
+
}
|
|
56850
|
+
function slimAdjudicationItems(items) {
|
|
56851
|
+
return items.map((it) => {
|
|
56852
|
+
const { evidence: _evidence, evidenceTruncated: _truncated, ...rest } = it;
|
|
56853
|
+
return { ...rest, evidence: [] };
|
|
56854
|
+
});
|
|
56855
|
+
}
|
|
56856
|
+
function hydrateAdjudication(adj, audit2, opts = {}) {
|
|
56857
|
+
const needs = adj.items.filter((it) => !it.evidence || it.evidence.length === 0);
|
|
56858
|
+
if (!needs.length) return;
|
|
56859
|
+
const derived = new Map(buildAdjudicationWorklist(audit2, { ...opts, standard: adj.standard }).map((it) => [it.criteriaId, it]));
|
|
56860
|
+
for (const it of needs) {
|
|
56861
|
+
const full = derived.get(it.criteriaId);
|
|
56862
|
+
if (!full) continue;
|
|
56863
|
+
it.evidence = full.evidence;
|
|
56864
|
+
if (full.evidenceTruncated) it.evidenceTruncated = full.evidenceTruncated;
|
|
56865
|
+
}
|
|
56842
56866
|
}
|
|
56843
56867
|
|
|
56844
56868
|
// src/llm.ts
|
|
@@ -66559,6 +66583,7 @@ function applyAdjudicationFile(p, adj, lang) {
|
|
|
66559
66583
|
console.error(`ultra11y verify: --in file not found or not valid JSON: ${inFlag}.`);
|
|
66560
66584
|
return 2;
|
|
66561
66585
|
}
|
|
66586
|
+
hydrateAdjudication(adj, audit2, { cwd: typeof p.flags.cwd === "string" ? p.flags.cwd : void 0 });
|
|
66562
66587
|
const r = applyAdjudication(audit2, adj);
|
|
66563
66588
|
if (!r.ok) {
|
|
66564
66589
|
if (p.flags.json) console.log(JSON.stringify(r, null, 2));
|
package/src/adjudicate.ts
CHANGED
|
@@ -909,11 +909,18 @@ function plainTest(s: string): string {
|
|
|
909
909
|
return s.replace(/\[([^\]]+)\]\(#[^)]*\)/g, "$1");
|
|
910
910
|
}
|
|
911
911
|
|
|
912
|
-
|
|
912
|
+
/** `preamble: false` renders the criterion sections ALONE.
|
|
913
|
+
*
|
|
914
|
+
* The preamble names the file to write into and the command to fold with, which is right for
|
|
915
|
+
* the one combined document and wrong for a per-criterion brief: repeated 96 times it is 96
|
|
916
|
+
* copies of instructions that contradict a CI adjudicator's toolset (it has no shell) and
|
|
917
|
+
* point at the wrong file. The briefs carry evidence and protocol; the instructions live once,
|
|
918
|
+
* in the worklist's `contract` field and the dispatch contract. */
|
|
919
|
+
export function formatAdjudication(items: AdjudicationItem[], lang: Lang = "en", standard: StandardId = CORE, opts: { preamble?: boolean } = {}): string {
|
|
913
920
|
const s = T[lang];
|
|
914
921
|
const pack = isCore(standard) ? undefined : loadPack(standard);
|
|
915
|
-
const out: string[] = [s.title, "", s.intro, "", ...s.verdicts, "", s.rule, "", s.then, ""];
|
|
916
|
-
if (pack) out.push(`> ${s.packIntro(pack.name)}`, "");
|
|
922
|
+
const out: string[] = opts.preamble === false ? [] : [s.title, "", s.intro, "", ...s.verdicts, "", s.rule, "", s.then, ""];
|
|
923
|
+
if (pack && opts.preamble !== false) out.push(`> ${s.packIntro(pack.name)}`, "");
|
|
917
924
|
for (const it of items) {
|
|
918
925
|
out.push(`## ${pack ? `${pack.name} ` : ""}${it.criteriaId}${it.title ? ` — ${it.title}` : ""} _(${it.automatability})_`);
|
|
919
926
|
out.push("", `> ${s.evidence} (${it.evidence.length}${it.evidenceTruncated ? ` / ${it.evidenceTruncated.total}` : ""}):`, "");
|
|
@@ -970,6 +977,10 @@ export function formatAdjudication(items: AdjudicationItem[], lang: Lang = "en",
|
|
|
970
977
|
export interface WriteAdjudicationResult {
|
|
971
978
|
todoPath: string;
|
|
972
979
|
mdPath: string;
|
|
980
|
+
/** The verdicts-only file, for an adjudicator that cannot edit half a megabyte of JSON. */
|
|
981
|
+
verdictsPath: string;
|
|
982
|
+
/** One small Markdown brief per criterion, for one that cannot read it either. */
|
|
983
|
+
itemsDir: string;
|
|
973
984
|
count: number;
|
|
974
985
|
}
|
|
975
986
|
|
|
@@ -992,5 +1003,60 @@ export function writeAdjudication(
|
|
|
992
1003
|
};
|
|
993
1004
|
writeFileSync(todoPath, JSON.stringify(file, null, 2) + "\n");
|
|
994
1005
|
writeFileSync(mdPath, formatAdjudication(items, opts.lang ?? "en", opts.standard));
|
|
995
|
-
|
|
1006
|
+
|
|
1007
|
+
// THE SPLIT SURFACE, for an adjudicator that cannot shell out.
|
|
1008
|
+
//
|
|
1009
|
+
// The two documents above are sized for a session with a shell: 96 RGAA criteria carrying
|
|
1010
|
+
// 30 evidence anchors each come to ~540 KB of JSON and ~470 KB of Markdown, and the runbook
|
|
1011
|
+
// tells the reader to edit the JSON in place and then run the engine to fold it. That is
|
|
1012
|
+
// fine locally. In CI the adjudicator is handed Read/Grep/Glob/Edit/Write and nothing else,
|
|
1013
|
+
// and against those tools the same instructions are impossible: reading either document
|
|
1014
|
+
// swamps the context, editing 96 verdicts inside half a megabyte of JSON is 96 exact-match
|
|
1015
|
+
// edits, and every prescribed `node …` command is denied. Measured on a real run: 17
|
|
1016
|
+
// permission denials, 75 of 424 turns used, and a todo file that came back untouched — so
|
|
1017
|
+
// the fail-closed fold correctly discarded all 96 and every criterion stayed "to assess".
|
|
1018
|
+
//
|
|
1019
|
+
// So the same worklist is also written as one small file to WRITE and one small file per
|
|
1020
|
+
// criterion to READ. Additive on purpose: the two documents above keep their exact shape and
|
|
1021
|
+
// every existing caller, gate and test is untouched.
|
|
1022
|
+
const verdictsPath = join(outDir, "ADJUDICATE.verdicts.json");
|
|
1023
|
+
writeFileSync(verdictsPath, JSON.stringify({ ...file, items: slimAdjudicationItems(items) }, null, 2) + "\n");
|
|
1024
|
+
const itemsDir = join(outDir, "adjudicate");
|
|
1025
|
+
mkdirSync(itemsDir, { recursive: true });
|
|
1026
|
+
for (const it of items) {
|
|
1027
|
+
writeFileSync(join(itemsDir, `${it.criteriaId}.md`), formatAdjudication([it], opts.lang ?? "en", opts.standard, { preamble: false }));
|
|
1028
|
+
}
|
|
1029
|
+
return { todoPath, mdPath, verdictsPath, itemsDir, count: items.length };
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
/** The worklist with the evidence stripped: what an adjudicator has to WRITE, without the
|
|
1033
|
+
* bulk it only has to READ. Every field the fold requires — verdict, justification, reason,
|
|
1034
|
+
* findings, citations, recommendations — is kept, so a filled slim file carries a complete
|
|
1035
|
+
* decision. The evidence comes back at fold time via `hydrateAdjudication`. */
|
|
1036
|
+
export function slimAdjudicationItems(items: AdjudicationItem[]): AdjudicationItem[] {
|
|
1037
|
+
return items.map((it) => {
|
|
1038
|
+
const { evidence: _evidence, evidenceTruncated: _truncated, ...rest } = it;
|
|
1039
|
+
return { ...rest, evidence: [] } as AdjudicationItem;
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
/** Put the evidence back on a slim adjudication, re-derived from the audit it was built from.
|
|
1044
|
+
*
|
|
1045
|
+
* Safe because the worklist is a pure function of the audit: same audit, same harvesters, same
|
|
1046
|
+
* anchors. This is what lets `verify --apply` accept a verdicts-only file WITHOUT relaxing a
|
|
1047
|
+
* single check — the citation gate still matches each cited anchor against this criterion's
|
|
1048
|
+
* own harvested evidence, exactly as it does for an inline worklist.
|
|
1049
|
+
*
|
|
1050
|
+
* An item the re-derivation does not know is left alone: it is a surplus verdict, and the
|
|
1051
|
+
* coverage check in `applyAdjudication` is what must reject it. */
|
|
1052
|
+
export function hydrateAdjudication(adj: AdjudicationFile, audit: AuditResult, opts: { cwd?: string } = {}): void {
|
|
1053
|
+
const needs = adj.items.filter((it) => !it.evidence || it.evidence.length === 0);
|
|
1054
|
+
if (!needs.length) return;
|
|
1055
|
+
const derived = new Map(buildAdjudicationWorklist(audit, { ...opts, standard: adj.standard }).map((it) => [it.criteriaId, it]));
|
|
1056
|
+
for (const it of needs) {
|
|
1057
|
+
const full = derived.get(it.criteriaId);
|
|
1058
|
+
if (!full) continue;
|
|
1059
|
+
it.evidence = full.evidence;
|
|
1060
|
+
if (full.evidenceTruncated) it.evidenceTruncated = full.evidenceTruncated;
|
|
1061
|
+
}
|
|
996
1062
|
}
|
package/src/cli.ts
CHANGED
|
@@ -42,7 +42,14 @@ import { runCriteria, renderCriteriaReference } from "./criteria.js";
|
|
|
42
42
|
import { checkReport, checkSemantic } from "./check.js";
|
|
43
43
|
import { buildWorklist, writeWorklist, applyVerdicts, VERIFY_MAX, type VerifyItem } from "./verify.js";
|
|
44
44
|
import { groundItems } from "./grounding.js";
|
|
45
|
-
import {
|
|
45
|
+
import {
|
|
46
|
+
buildAdjudicationWorklist,
|
|
47
|
+
formatAdjudication,
|
|
48
|
+
writeAdjudication,
|
|
49
|
+
applyAdjudication,
|
|
50
|
+
hydrateAdjudication,
|
|
51
|
+
type AdjudicationFile,
|
|
52
|
+
} from "./adjudicate.js";
|
|
46
53
|
import { BATCH_SIZE, apiKeyFromEnv, applyRawVerdicts, judgeAll, modelFromEnv } from "./llm.js";
|
|
47
54
|
import { runScan, runScanMany, runCrawlScan, runSampleScan, mergeDynamic, mergeSnapshotAudit, cleanDynamic, dockerAvailable } from "./scan.js";
|
|
48
55
|
import { runScanLocal, runScanManyLocal, runCrawlScanLocal, runSampleScanLocal, localAvailable } from "./scan-local.js";
|
|
@@ -2637,6 +2644,11 @@ function applyAdjudicationFile(p: ParsedArgs, adj: AdjudicationFile, lang: Lang)
|
|
|
2637
2644
|
console.error(`ultra11y verify: --in file not found or not valid JSON: ${inFlag}.`);
|
|
2638
2645
|
return 2;
|
|
2639
2646
|
}
|
|
2647
|
+
// A verdicts-only file (`ADJUDICATE.verdicts.json`) carries decisions without the evidence
|
|
2648
|
+
// they were made against. Put it back from the audit before folding: the worklist is a pure
|
|
2649
|
+
// function of the audit, so this reconstructs the very anchors the citation gate checks
|
|
2650
|
+
// against — the fold below is the same fold, with the same refusals.
|
|
2651
|
+
hydrateAdjudication(adj, audit, { cwd: typeof p.flags.cwd === "string" ? (p.flags.cwd as string) : undefined });
|
|
2640
2652
|
const r = applyAdjudication(audit, adj);
|
|
2641
2653
|
if (!r.ok) {
|
|
2642
2654
|
if (p.flags.json) console.log(JSON.stringify(r, null, 2));
|
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.3.
|
|
3
|
+
export const VERSION = "5.3.4";
|
|
4
4
|
export const SCHEMA_VERSION = 2;
|
|
5
5
|
|
|
6
6
|
// Lang = the UI FRAME's language — the `L` localization tables each module (report,
|