ultra11y 5.23.0 → 5.25.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.
- 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/README.md +2 -2
- package/package.json +1 -1
- package/scripts/build-pack-rgaa.mjs +68 -2
- package/scripts/ultra11y.mjs +934 -41
- package/scripts/vendor/rgaa/SOURCE.json +4 -3
- package/scripts/vendor/rgaa/methodologies.json +260 -0
- package/scripts/verify-rgaa-source.mjs +23 -6
- package/skills/review-a11y/SKILL.md +1 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +934 -41
- package/skills/ultra11y/SKILL.md +12 -3
- package/skills/ultra11y/references/ci.md +1 -1
- package/skills/ultra11y/references/criteria.md +1 -1
- package/skills/ultra11y/references/judgment.md +28 -2
- package/skills/ultra11y/references/mcp.md +1 -1
- package/skills/ultra11y/references/orchestration.md +5 -0
- package/skills/ultra11y/scripts/ultra11y.mjs +934 -41
- package/src/adjudicate-subjects.ts +165 -3
- package/src/adjudicate.ts +215 -23
- package/src/adjudication-data.ts +31 -0
- package/src/cli.ts +51 -2
- package/src/criteria-view.ts +18 -8
- package/src/criteria.ts +17 -1
- package/src/data/standards/rgaa.json +486 -1
- package/src/data/wcag.json +4 -1
- package/src/llm.ts +1 -1
- package/src/messages.ts +42 -0
- package/src/orchestrate-templates.ts +5 -4
- package/src/rules/presentation.ts +165 -2
- package/src/standards/coverage.ts +1 -1
- package/src/standards/derive.ts +13 -1
- package/src/standards/pack.ts +6 -0
- package/src/standards/types.ts +17 -0
- package/src/standards/validate.ts +30 -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.
|
|
13
|
+
"version": "5.25.0",
|
|
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.
|
|
4
|
+
"version": "5.25.0",
|
|
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.
|
|
4
|
+
"version": "5.25.0",
|
|
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.
|
|
30
|
+
const ULTRA11Y_PLUGIN_VERSION = "5.25.0";
|
|
31
31
|
|
|
32
32
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
33
33
|
|
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ Full details, thresholds and off-switches under [Install](#install).</sub>
|
|
|
25
25
|
|
|
26
26
|
> Audit HTML/CSS/JSX against **WCAG 2.2 AA** accessibility and produce a dated compliance report — or author/review accessible markup without regressions. A [skills.sh](https://skills.sh) agent skill: a deterministic, zero-dependency static engine **plus** the agent's judgment, with `check`/`verify` gates against hallucinated non-conformities. **The central deliverable is the auditor conformance block** — theme, criterion + official wording, test(s), WCAG mapping + level, finding, expected state, verification, `file:line` occurrences — rendered identically by the `report` (compliance doc), the `prd` backlog and the GitHub issues, in the active standard's vocabulary and **in your language** (`--lang auto` follows the conversation/repo). **WCAG is the worldwide core; country standards (RGAA, …) are pluggable in-repo packs.**
|
|
27
27
|
|
|
28
|
-
ultra11y is built around an honest **division of labour**. Automated tools only catch a fraction of accessibility problems, so the engine does the *mechanical* work —
|
|
28
|
+
ultra11y is built around an honest **division of labour**. Automated tools only catch a fraction of accessibility problems, so the engine does the *mechanical* work — 81 machine-detectable static checks tied to the WCAG 2.2 success criteria — and is explicit about everything it can't decide statically. What it can't, the **AI agent adjudicates** (statically, from the evidence, gated by `verify`/`check`) — not a deferral to a human:
|
|
29
29
|
|
|
30
30
|
- **Automatable (engine):** missing `alt`/`lang`/`title`, unlabeled fields, empty links/buttons, icon-only controls, iframes without title, tables without headers, heading-level skips, empty/dangling headings & labels, duplicate ids, invalid/broken ARIA, positive `tabindex`, autoplay/timed-refresh/`blink`/`marquee` media…
|
|
31
31
|
- **Agent judgment (gated):** alt-text relevance, link purpose in context, reading/tab order, caption accuracy — the agent rules on these via `verify --manual` → `--apply`, each verdict carrying a justification (or a groundable NC), never a silent "conforming".
|
|
@@ -226,7 +226,7 @@ from auditing out of memory. `cwd` is optional on all five; it selects whose pac
|
|
|
226
226
|
`ultra11y_method` is the one to call first. It partitions the standard into evidence tiers —
|
|
227
227
|
`source`, `cross-file`, `rendered-page`, `browser`, `judgment`, `out-of-scope` — from the
|
|
228
228
|
engine's own per-criterion rule applicability, never from guessing at the wording of a test.
|
|
229
|
-
For RGAA it reports that **
|
|
229
|
+
For RGAA it reports that **56 of 106 criteria declare that no engine rule can evidence them**.
|
|
230
230
|
That is the standard saying they are yours, and a plan that hides it reads as coverage that
|
|
231
231
|
does not exist.
|
|
232
232
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultra11y",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.25.0",
|
|
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,8 @@
|
|
|
6
6
|
// of its criteria onto WCAG success criteria (bare SC ids). The WCAG↔rule coverage
|
|
7
7
|
// lives in the core (scripts/build-standards.mjs). The RGAA content is Licence
|
|
8
8
|
// Ouverte / Etalab 2.0 — see NOTICE. The official source is vendored under
|
|
9
|
-
// scripts/vendor/rgaa/
|
|
9
|
+
// scripts/vendor/rgaa/ (criteres.json, glossaire.json, methodologies.json) so the
|
|
10
|
+
// build is reproducible offline.
|
|
10
11
|
// node scripts/build-pack-rgaa.mjs # build from the vendored source
|
|
11
12
|
// node scripts/build-pack-rgaa.mjs --offline # alias (the source is always local)
|
|
12
13
|
// node scripts/build-pack-rgaa.mjs --fetch # refresh the vendored source from DINUM, then build
|
|
@@ -76,6 +77,22 @@ const flattenNode = (v) => {
|
|
|
76
77
|
};
|
|
77
78
|
const toArr = (v) => flattenNode(v);
|
|
78
79
|
|
|
80
|
+
// The official méthodologie de test for one criterion, keyed by the criterion's OWN test
|
|
81
|
+
// numbers so it renders directly under the test it explains: `{ "1": "1. Retrouver…", … }`.
|
|
82
|
+
// DINUM's file is flat and keyed by full test id ("11.2.1"), so this is the re-key.
|
|
83
|
+
//
|
|
84
|
+
// Returns undefined when the criterion has none, and the field is then absent from the pack
|
|
85
|
+
// rather than present-and-empty: `methodology` is optional on PackCriterion, and an empty
|
|
86
|
+
// object would make every consumer test for two shapes instead of one.
|
|
87
|
+
function methodologyOf(methodologies, id, tests) {
|
|
88
|
+
const out = {};
|
|
89
|
+
for (const k of Object.keys(tests || {})) {
|
|
90
|
+
const text = methodologies[`${id}.${k}`];
|
|
91
|
+
if (typeof text === "string" && text.trim()) out[k] = text.trim();
|
|
92
|
+
}
|
|
93
|
+
return Object.keys(out).length ? { methodology: out } : undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
79
96
|
// crude HTML -> plaintext for glossary bodies
|
|
80
97
|
const deHtml = (s) =>
|
|
81
98
|
s
|
|
@@ -245,6 +262,12 @@ const RULE_TO_CRITERIA = {
|
|
|
245
262
|
async function main() {
|
|
246
263
|
const criteres = await source("criteres.json");
|
|
247
264
|
const glossaire = await source("glossaire.json");
|
|
265
|
+
// THE OFFICIAL TEST METHODOLOGY — the third file DINUM publishes beside the other two,
|
|
266
|
+
// keyed by TEST id ("11.2.1"), and the one thing an adjudicator was never given. The
|
|
267
|
+
// criterion's wording says WHAT is required; this says HOW it is tested, step by step, in
|
|
268
|
+
// the referential's own words. Vendored like the rest so it reaches CI, an offline run and
|
|
269
|
+
// a model prompt without a network call — see `methodology` in src/standards/types.ts.
|
|
270
|
+
const methodologies = await source("methodologies.json");
|
|
248
271
|
|
|
249
272
|
const glossary = {};
|
|
250
273
|
for (const e of glossaire.glossary) glossary[slug(e.title)] = { title: e.title, body: deHtml(e.body) };
|
|
@@ -267,6 +290,7 @@ async function main() {
|
|
|
267
290
|
title: { fr: criterium.title },
|
|
268
291
|
titlePlain: { fr: plain(criterium.title) },
|
|
269
292
|
tests: criterium.tests || {},
|
|
293
|
+
...(methodologyOf(methodologies, id, criterium.tests) ?? {}),
|
|
270
294
|
techniques: [...techniques],
|
|
271
295
|
...(criterium.technicalNote ? { technicalNote: toArr(criterium.technicalNote) } : {}),
|
|
272
296
|
...(criterium.particularCases ? { particularCases: toArr(criterium.particularCases) } : {}),
|
|
@@ -310,7 +334,10 @@ async function main() {
|
|
|
310
334
|
// (link explicitness, WCAG 2.4.4). ADVISORY: it surfaces as a recommendation in the RGAA
|
|
311
335
|
// projection and NEVER makes 6.1 non-conformant. This proves a pack ships its OWN
|
|
312
336
|
// detection without forking the engine (see skills/ultra11y/references/packs.md).
|
|
313
|
-
|
|
337
|
+
// Kept in step with the engine instead of retyped: two hand-maintained copies of a rule-id
|
|
338
|
+
// list are two chances for the pack to claim an instrument the engine does not ship.
|
|
339
|
+
const PRESENTATIONAL_RULE_IDS = ["presentational-element", "presentational-attribute", "presentational-spacing"];
|
|
340
|
+
const DOWNLOAD_EXT = "pdf|docx?|pptx?|xlsx?|odt|ods|odp|rtf|csv|zip|rar|7z|gz|epub|mp3|mp4|avi|mov";
|
|
314
341
|
const rules = [
|
|
315
342
|
{
|
|
316
343
|
id: "download-link-format",
|
|
@@ -423,6 +450,28 @@ async function main() {
|
|
|
423
450
|
c.appliesTo = { ruleIds: [...new Set([...c.appliesTo.ruleIds, `pack:rgaa:${rule.id}`])].sort() };
|
|
424
451
|
}
|
|
425
452
|
|
|
453
|
+
// ---- RGAA 10.1 — the engine's presentational-markup rules, and their normativity ----
|
|
454
|
+
//
|
|
455
|
+
// 10.1's three tests are « these must be absent », over lists the RGAA closes itself: the
|
|
456
|
+
// glossary entry « Présentation de l'information » names the forbidden elements and
|
|
457
|
+
// attributes. The engine ships the instruments (src/rules/presentation.ts); the pack says
|
|
458
|
+
// they decide THIS criterion, and re-normativizes them.
|
|
459
|
+
//
|
|
460
|
+
// They ship ADVISORY because `<center>` is obsolete HTML rather than a WCAG failure —
|
|
461
|
+
// nothing about it breaks assistive technology, and reporting a WCAG 1.3.1 non-conformity
|
|
462
|
+
// for it would be wrong. Under the RGAA it is a plain failure of test 10.1.1, so the pack
|
|
463
|
+
// flips it here. This is what `overrides` is for, and it is the first use of it.
|
|
464
|
+
//
|
|
465
|
+
// Before this, 10.1 had NO instrument and inherited `readingOrder` through WCAG 1.3.2 — a
|
|
466
|
+
// subject that answers a different question — so it reached the adjudicator with an empty
|
|
467
|
+
// harvest. Measured on run 32508717451 (Sonnet, RGAA, 3 passes): the model ruled `C` three
|
|
468
|
+
// times, the gate refused all three for citing nothing, and 10.1 was the ONE criterion of
|
|
469
|
+
// the 106 left « à évaluer », on the run and on every page.
|
|
470
|
+
const presentational = criteria.find((x) => x.id === "10.1");
|
|
471
|
+
if (!presentational) throw new Error("build-pack-rgaa: RGAA 10.1 is missing from the referential");
|
|
472
|
+
presentational.appliesTo = { ruleIds: [...new Set([...presentational.appliesTo.ruleIds, ...PRESENTATIONAL_RULE_IDS])].sort() };
|
|
473
|
+
const overrides = Object.fromEntries(PRESENTATIONAL_RULE_IDS.map((id) => [id, { advisory: false, severity: "majeur" }]));
|
|
474
|
+
|
|
426
475
|
const pack = {
|
|
427
476
|
key: "rgaa",
|
|
428
477
|
name: "RGAA",
|
|
@@ -437,6 +486,9 @@ async function main() {
|
|
|
437
486
|
source: "https://github.com/DISIC/accessibilite.numerique.gouv.fr",
|
|
438
487
|
attribution: "RGAA 4.1.2 © DINUM (Direction interministérielle du numérique) — Licence Ouverte / Etalab 2.0",
|
|
439
488
|
idPattern: "^\\d+\\.\\d+$",
|
|
489
|
+
// Where DINUM publishes each criterion. Cited by the adjudication brief so a reader can
|
|
490
|
+
// reach the normative page — the vendored text above stays the authority either way.
|
|
491
|
+
criterionUrl: "https://accessibilite.numerique.gouv.fr/methode/criteres-et-tests/#{id}",
|
|
440
492
|
// Auditor-display vocabulary (FR): the nouns an RGAA auditor reads. Rendered by the
|
|
441
493
|
// `prd` auditor block + GitHub issues; see src/standards/vocabulary.ts.
|
|
442
494
|
vocabulary: {
|
|
@@ -505,6 +557,7 @@ async function main() {
|
|
|
505
557
|
},
|
|
506
558
|
],
|
|
507
559
|
rules,
|
|
560
|
+
overrides,
|
|
508
561
|
themes,
|
|
509
562
|
criteria,
|
|
510
563
|
};
|
|
@@ -546,9 +599,22 @@ async function main() {
|
|
|
546
599
|
throw new Error(`build-pack-rgaa: ${stringified.length} normative text(s) stringified to "[object Object]" — extend flattenNode: ${stringified.join(", ")}`);
|
|
547
600
|
}
|
|
548
601
|
|
|
602
|
+
// THE METHODOLOGY TABLE MUST NOT GO STALE EITHER. A methodology whose test id does not
|
|
603
|
+
// exist in the referential is a curation mistake upstream OR a re-key bug here, and it
|
|
604
|
+
// would ship as dead weight in every bundle. A test with no methodology is legitimate
|
|
605
|
+
// (DINUM does not document every one), so that is counted, not refused.
|
|
606
|
+
const testIds = new Set();
|
|
607
|
+
for (const c of criteria) for (const k of Object.keys(c.tests ?? {})) testIds.add(`${c.id}.${k}`);
|
|
608
|
+
const orphans = Object.keys(methodologies).filter((k) => !testIds.has(k));
|
|
609
|
+
if (orphans.length) {
|
|
610
|
+
throw new Error(`build-pack-rgaa: ${orphans.length} methodology(ies) name a test the referential does not have: ${orphans.join(", ")}`);
|
|
611
|
+
}
|
|
612
|
+
const documented = criteria.reduce((n, c) => n + Object.keys(c.methodology ?? {}).length, 0);
|
|
613
|
+
|
|
549
614
|
const noWcag = criteria.filter((c) => c.wcag.length === 0).map((c) => c.id);
|
|
550
615
|
console.log(`build-pack-rgaa: ${themes.length} themes, ${criteria.length} criteria → src/data/standards/rgaa.json`);
|
|
551
616
|
console.log(`build-pack-rgaa: glossary ${Object.keys(glossary).length} entries → src/data/standards/rgaa.glossary.json`);
|
|
617
|
+
console.log(`build-pack-rgaa: official test methodology on ${documented}/${testIds.size} tests`);
|
|
552
618
|
console.log(`build-pack-rgaa: criteria with no WCAG mapping (pack-local): ${noWcag.length ? noWcag.join(", ") : "none"} (${noWcag.length})`);
|
|
553
619
|
}
|
|
554
620
|
|