ultra11y 5.45.0 → 5.46.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.
Files changed (45) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.opencode/plugins/ultra11y.js +1 -1
  5. package/README.md +10 -8
  6. package/package.json +4 -1
  7. package/scripts/build-pack-rgaa.mjs +6 -1
  8. package/scripts/build-site.mjs +67 -0
  9. package/scripts/build-standards.mjs +1 -1
  10. package/scripts/ultra11y.mjs +527 -75
  11. package/scripts/vendor/wcag-2.2-fr.json +1 -1
  12. package/scripts/vendor/wcag-2.2-sc-universe.json +1 -1
  13. package/scripts/vendor/wcag-2.2-techniques.json +1 -1
  14. package/scripts/vendor/wcag-2.2-text.json +1 -1
  15. package/skills/review-a11y/SKILL.md +1 -1
  16. package/skills/review-a11y/scripts/ultra11y.mjs +527 -75
  17. package/skills/ultra11y/SKILL.md +3 -3
  18. package/skills/ultra11y/references/act.md +48 -45
  19. package/skills/ultra11y/references/criteria.md +1 -1
  20. package/skills/ultra11y/references/rgaa-automation.md +5 -5
  21. package/skills/ultra11y/references/verify.md +11 -0
  22. package/skills/ultra11y/scripts/ultra11y.mjs +527 -75
  23. package/skills/ultra11y/templates/gitlab-ci.yml +1 -1
  24. package/src/act.ts +46 -15
  25. package/src/adjudicate.ts +9 -22
  26. package/src/data/standards/rgaa.json +21 -2
  27. package/src/data/wcag-universe.json +1 -1
  28. package/src/data/wcag.json +1 -1
  29. package/src/messages.ts +42 -0
  30. package/src/name.ts +51 -3
  31. package/src/orchestrate-templates.ts +1 -1
  32. package/src/refute.ts +10 -3
  33. package/src/rules/forms.ts +29 -2
  34. package/src/rules/images.ts +24 -12
  35. package/src/rules/links.ts +11 -1
  36. package/src/rules/mandatory.ts +42 -3
  37. package/src/rules/navigation.ts +72 -1
  38. package/src/rules/presentation.ts +9 -2
  39. package/src/rules/scripts-aria.ts +206 -20
  40. package/src/rules/tables.ts +135 -6
  41. package/src/rules/timing.ts +13 -3
  42. package/src/site-data.ts +471 -0
  43. package/src/types.ts +1 -1
  44. package/src/verdict-rules.ts +1 -1
  45. package/src/verify.ts +85 -14
@@ -10,7 +10,7 @@
10
10
  "name": "ultra11y",
11
11
  "source": "./",
12
12
  "description": "Automatic accessibility review before commit, push and pull request, plus detailed evidenced Markdown, HTML and per-page WCAG 2.2 AA / RGAA reports in Claude Code.",
13
- "version": "5.45.0",
13
+ "version": "5.46.0",
14
14
  "author": { "name": "maxgfr" },
15
15
  "homepage": "https://github.com/SocialGouv/ultra11y",
16
16
  "repository": "https://github.com/SocialGouv/ultra11y",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ultra11y",
3
3
  "description": "Accessibility for WCAG 2.2 AA and country standards (RGAA…): automatic review before commit, push and pull request, plus detailed evidenced Markdown, HTML and per-page conformance reports in interactive agent sessions.",
4
- "version": "5.45.0",
4
+ "version": "5.46.0",
5
5
  "author": { "name": "maxgfr" },
6
6
  "homepage": "https://github.com/SocialGouv/ultra11y",
7
7
  "repository": "https://github.com/SocialGouv/ultra11y",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ultra11y",
3
3
  "description": "Accessibility for WCAG 2.2 AA and country standards (RGAA…): automatic review before commit, push and pull request, plus detailed evidenced Markdown, HTML and per-page conformance reports in interactive agent sessions.",
4
- "version": "5.45.0",
4
+ "version": "5.46.0",
5
5
  "author": { "name": "maxgfr", "url": "https://github.com/maxgfr" },
6
6
  "homepage": "https://github.com/SocialGouv/ultra11y",
7
7
  "repository": "https://github.com/SocialGouv/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.45.0";
30
+ const ULTRA11Y_PLUGIN_VERSION = "5.46.0";
31
31
 
32
32
  const here = dirname(fileURLToPath(import.meta.url));
33
33
 
package/README.md CHANGED
@@ -6,6 +6,8 @@
6
6
  [![node](https://img.shields.io/node/v/ultra11y)](https://nodejs.org)
7
7
  [![license](https://img.shields.io/npm/l/ultra11y)](LICENSE)
8
8
 
9
+ **Documentation (in French): [socialgouv.github.io/ultra11y](https://socialgouv.github.io/ultra11y/)** — what the tool does, what it cannot check, criterion-by-criterion RGAA coverage and how it is tested, with every number generated from the code.
10
+
9
11
  ### Add to your coding agent
10
12
 
11
13
  Two lines, and the accessibility review runs **by itself** before every commit, push and
@@ -25,7 +27,7 @@ Full details, thresholds and off-switches under [Install](#install).</sub>
25
27
 
26
28
  > 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
29
 
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 — 93 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:
30
+ 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 — 94 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
31
 
30
32
  - **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
33
  - **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".
@@ -41,11 +43,11 @@ A self-authored fixture can only prove that a rule fires on the defect written f
41
43
 
42
44
  | | |
43
45
  |---|---|
44
- | Rules scored | **40** (the ACT rules an equivalent engine check exists for) |
45
- | `failed` examples caught | **125 / 176** |
46
- | `passed` / `inapplicable` examples left alone | **364 / 364** — no unexplained false positive |
46
+ | Rules scored | **44** (the ACT rules an equivalent engine check exists for) |
47
+ | `failed` examples caught | **168 / 187** |
48
+ | `passed` / `inapplicable` examples left alone | **401 / 401** — no unexplained false positive |
47
49
  | Deliberate divergences | 9, each argued on the record |
48
- | Declared gaps (statically decidable, not implemented) | 5 |
50
+ | Declared gaps (statically decidable, not implemented) | 0 |
49
51
 
50
52
  Rules needing a rendered page (computed contrast, keyboard traps) or a human call (is this heading descriptive?) are **declared and routed**, not silently scored zero. The full per-rule matrix — including every gap and every divergence — is generated into [`skills/ultra11y/references/act.md`](skills/ultra11y/references/act.md) and re-checked on every build; the corpus itself is refreshed daily by a workflow, so the numbers cannot quietly go stale.
51
53
 
@@ -316,7 +318,7 @@ a test). See [`CONTRIBUTING.md`](CONTRIBUTING.md) and `skills/ultra11y/reference
316
318
 
317
319
  ### What RGAA automation actually covers
318
320
 
319
- The engine's 93 rules are not 93 RGAA criteria: several rules can evidence the same official
321
+ The engine's 94 rules are not 94 RGAA criteria: several rules can evidence the same official
320
322
  test, and a rule can detect a useful symptom without exhausting the criterion's alternatives,
321
323
  exceptions or relevance checks. The generated contract classifies all **258 official RGAA
322
324
  tests**:
@@ -346,8 +348,8 @@ The number can grow, but the useful target is **more observed, exact failures**,
346
348
  marketing count. The best next candidates are complete rendered probes for zoom/reflow and
347
349
  hover content (10.4, 10.11, 10.13), and contrast,
348
350
  link distinction or orientation checks (3.2, 3.3, 10.6, 13.9) that first rule out every
349
- official alternative and particular case. The 5 declared ACT gaps are another implementation
350
- queue, although adding one does not necessarily cover a new RGAA criterion. A candidate is
351
+ official alternative and particular case. The ACT corpus's remaining misses are another
352
+ implementation queue, although closing one does not necessarily cover a new RGAA criterion. A candidate is
351
353
  promoted only when a narrower rule demonstrably exhausts its cited official test; the two
352
354
  `C`-by-silence criteria stay unchanged until an equally complete proof exists.
353
355
 
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "ultra11y",
3
- "version": "5.45.0",
3
+ "version": "5.46.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
+ "homepage": "https://socialgouv.github.io/ultra11y/",
5
6
  "type": "module",
6
7
  "packageManager": "pnpm@10.33.0",
7
8
  "bin": {
@@ -67,6 +68,8 @@
67
68
  "build:act:refresh": "node scripts/build-act-corpus.mjs --refresh",
68
69
  "build:adjudication": "node scripts/build-adjudication.mjs && UPDATE_ADJUDICATION_MD=1 vitest run tests/adjudication-md-sync.test.ts",
69
70
  "check:adjudication": "node scripts/build-adjudication.mjs --check",
71
+ "build:site": "node scripts/build-site.mjs",
72
+ "check:site": "node scripts/build-site.mjs --check",
70
73
  "demo": "node scripts/ultra11y.mjs audit tests/fixtures/non-conforming/bad.html --out /tmp/ultra11y-demo --json > /dev/null && node scripts/ultra11y.mjs report --in /tmp/ultra11y-demo/audit-latest.json --out /tmp/ultra11y-demo && node scripts/ultra11y.mjs check --report /tmp/ultra11y-demo/wcag-*.md && node scripts/ultra11y.mjs criteria --list",
71
74
  "release": "semantic-release",
72
75
  "build:icons": "node scripts/build-extension-icons.mjs"
@@ -148,7 +148,7 @@ const RULE_SC = {
148
148
  "multiple-main-landmark": ["1.3.1"], "nav-landmark-missing": ["1.3.1"], "nav-landmark-unnamed": ["1.3.1"],
149
149
  "nested-interactive": ["4.1.2"], "object-embed-no-name": ["1.1.1"], "radio-checkbox-group-ungrouped": ["1.3.1", "3.3.2"],
150
150
  "placeholder-as-label": ["4.1.2"], "positive-tabindex": ["2.4.3"], "redundant-aria": ["4.1.2"], "select-has-option": ["4.1.2"],
151
- "skip-link-target-missing": ["2.4.1"], "sortable-header-no-aria-sort": ["1.3.1"], "status-message-not-assertive": ["4.1.3"],
151
+ "skip-link-target-missing": ["2.4.1"], "bypass-blocks-missing": ["2.4.1"], "sortable-header-no-aria-sort": ["1.3.1"], "status-message-not-assertive": ["4.1.3"],
152
152
  "table-caption-missing": ["1.3.1"], "table-empty-data-cell": ["1.3.1"], "title-missing-empty": ["2.4.2"],
153
153
  };
154
154
 
@@ -267,6 +267,11 @@ const RULE_TO_CRITERIA = {
267
267
  "axe:autocomplete-valid": ["11.13"], "axe:fieldset": ["11.6"],
268
268
  // Theme 12 — navigation (12.7 skip link, 12.8 tab order, 12.9 keyboard trap)
269
269
  "skip-link-target-missing": ["12.7"], "axe:skip-link": ["12.7"], "axe:bypass": ["12.7"],
270
+ // A page whose <nav> precedes content with no landmark, heading, button or in-page link at
271
+ // all: the content zone can be neither reached nor the navigation skipped (12.6), and no
272
+ // skip link exists (12.7). Candidate, not decisive: the tests also ask about the other zones
273
+ // and about links built in script, which this source-only rule does not see.
274
+ "bypass-blocks-missing": ["12.6", "12.7"],
270
275
  "positive-tabindex": ["12.8"], "axe:tabindex": ["12.8"],
271
276
  // 12.9 « la navigation ne doit pas contenir de piège au clavier ». The probe that answers it
272
277
  // has existed since `probeKeyboardTrap` landed, and it was mapped to WCAG 2.1.2 alone — so a
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+ // DEV-ONLY (not in `bin`). Generates the numbers of the documentation site (site/, published
3
+ // on GitHub Pages by .github/workflows/pages.yml) from the code itself: site/data.json, plus
4
+ // every `data-gen` value and `<!-- gen:… -->` block of site/*.html.
5
+ //
6
+ // The logic lives in src/site-data.ts so the test suite can import it directly
7
+ // (tests/site-data.test.ts fails when the committed site drifts). This script bundles that
8
+ // module with tsup — the same bundler as the engine — because plain `node` cannot resolve
9
+ // the `.js` → `.ts` imports of the source tree.
10
+ //
11
+ // Usage:
12
+ // node scripts/build-site.mjs # rewrite site/data.json and the generated regions
13
+ // node scripts/build-site.mjs --check # exit 1 when the committed site is stale
14
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
15
+ import { tmpdir } from "node:os";
16
+ import { dirname, join } from "node:path";
17
+ import { fileURLToPath, pathToFileURL } from "node:url";
18
+ import { build } from "tsup";
19
+
20
+ const root = join(dirname(fileURLToPath(import.meta.url)), "..");
21
+ const SITE = join(root, "site");
22
+ const CHECK = process.argv.includes("--check");
23
+
24
+ const tmp = mkdtempSync(join(tmpdir(), "ultra11y-site-"));
25
+ try {
26
+ await build({
27
+ entry: { site: join(root, "src", "site-data.ts") },
28
+ outDir: tmp,
29
+ format: ["esm"],
30
+ outExtension: () => ({ js: ".mjs" }),
31
+ platform: "node",
32
+ target: "node22",
33
+ bundle: true,
34
+ noExternal: [/.*/],
35
+ config: false,
36
+ silent: true,
37
+ dts: false,
38
+ sourcemap: false,
39
+ });
40
+ const { buildSiteData, renderData, renderPage, SITE_PAGES } = await import(pathToFileURL(join(tmp, "site.mjs")).href);
41
+ const data = buildSiteData(root);
42
+ const outputs = [["data.json", renderData(data)]];
43
+ for (const file of SITE_PAGES) outputs.push([file, renderPage(readFileSync(join(SITE, file), "utf8"), file, data)]);
44
+
45
+ const stale = outputs.filter(([file, text]) => {
46
+ try {
47
+ return readFileSync(join(SITE, file), "utf8") !== text;
48
+ } catch {
49
+ return true;
50
+ }
51
+ });
52
+ if (CHECK) {
53
+ if (stale.length) {
54
+ console.error(`site is stale: ${stale.map(([f]) => f).join(", ")} — run \`pnpm run build:site\``);
55
+ process.exit(1);
56
+ }
57
+ console.log("site is up to date");
58
+ } else {
59
+ for (const [file, text] of stale) writeFileSync(join(SITE, file), text);
60
+ const l = data.rgaa.levels;
61
+ console.log(
62
+ `site: ${stale.length} file(s) written · RGAA ${data.rgaa.criteria} criteria: ${l.det} deterministic / ${l.nc} NC proven / ${l.hint} signals / ${l.llm} judgment only · ACT ${data.act.caught}/${data.act.failedScored} on ${data.act.scoredRules} rules`,
63
+ );
64
+ }
65
+ } finally {
66
+ rmSync(tmp, { recursive: true, force: true });
67
+ }
@@ -659,7 +659,7 @@ const RULE_SC_COVERAGE = {
659
659
  "2.1.1": ["clickable-noninteractive"],
660
660
  "2.2.1": ["meta-refresh-redirect"],
661
661
  "2.2.2": ["autoplay-media", "blink-marquee"],
662
- "2.4.1": ["skip-link-target-missing"],
662
+ "2.4.1": ["skip-link-target-missing", "bypass-blocks-missing"],
663
663
  "2.4.2": ["title-missing-empty"],
664
664
  "2.4.3": ["positive-tabindex"],
665
665
  "2.4.7": ["rendered-focus-not-visible"],