ultra11y 5.42.0 → 5.43.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 (60) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/.claude-plugin/plugin.json +3 -3
  3. package/.codex-plugin/plugin.json +4 -4
  4. package/.opencode/plugins/ultra11y.js +1 -1
  5. package/README.md +10 -10
  6. package/dist/cypress-plugin.d.ts +1 -1
  7. package/dist/cypress.d.ts +2 -2
  8. package/dist/{payload-Bb1uQH1q.d.ts → payload-BMFQTm5s.d.ts} +4 -1
  9. package/dist/playwright.d.ts +1 -1
  10. package/dist/playwright.mjs +30 -3
  11. package/package.json +3 -3
  12. package/scripts/build-pack-rgaa.mjs +9 -0
  13. package/scripts/ultra11y.mjs +3992 -2901
  14. package/scripts/vendor/wcag-2.2-fr.json +95 -78
  15. package/scripts/vendor/wcag-2.2-sc-universe.json +1 -1
  16. package/scripts/vendor/wcag-2.2-techniques.json +5 -5
  17. package/scripts/vendor/wcag-2.2-text.json +1 -1
  18. package/skills/review-a11y/SKILL.md +1 -1
  19. package/skills/review-a11y/scripts/ultra11y.mjs +3992 -2901
  20. package/skills/ultra11y/SKILL.md +1 -1
  21. package/skills/ultra11y/references/audit.md +7 -5
  22. package/skills/ultra11y/references/ci.md +12 -12
  23. package/skills/ultra11y/references/dynamic.md +1 -1
  24. package/skills/ultra11y/references/harnesses.md +3 -3
  25. package/skills/ultra11y/references/methodology.md +44 -6
  26. package/skills/ultra11y/references/verify.md +2 -0
  27. package/skills/ultra11y/scripts/ultra11y.mjs +3992 -2901
  28. package/skills/ultra11y/templates/gitlab-ci.yml +2 -2
  29. package/src/audit.ts +8 -1
  30. package/src/auditor.ts +128 -4
  31. package/src/check.ts +34 -2
  32. package/src/cli.ts +21 -11
  33. package/src/data/guidance/wcag.json +2 -2
  34. package/src/data/standards/rgaa.json +23 -0
  35. package/src/data/wcag-universe.json +1 -1
  36. package/src/data/wcag.json +67 -69
  37. package/src/graph/engine-resolve.ts +0 -0
  38. package/src/html-report.ts +91 -7
  39. package/src/init.ts +3 -3
  40. package/src/install/codex.ts +2 -2
  41. package/src/install/index.ts +1 -1
  42. package/src/integrations/payload.ts +4 -1
  43. package/src/integrations/playwright.ts +14 -2
  44. package/src/mcp/handlers.ts +7 -2
  45. package/src/md.ts +15 -1
  46. package/src/pages.ts +65 -0
  47. package/src/probes.ts +57 -1
  48. package/src/report.ts +1045 -269
  49. package/src/sarif.ts +1 -1
  50. package/src/scan.ts +10 -0
  51. package/src/snapshot.ts +7 -0
  52. package/src/standards/types.ts +12 -0
  53. package/src/standards/validate.ts +21 -0
  54. package/src/types.ts +3 -1
  55. package/src/util.ts +7 -0
  56. package/src/vendor/codeindex-engine.d.mts +4 -2
  57. package/src/vendor/codeindex-engine.mjs +565 -358
  58. package/scripts/check-artifact-recall.mjs +0 -199
  59. package/scripts/sync-engine.mjs +0 -158
  60. package/src/vendor/engine.meta.json +0 -9
@@ -10,10 +10,10 @@
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.42.0",
13
+ "version": "5.43.0",
14
14
  "author": { "name": "maxgfr" },
15
- "homepage": "https://github.com/maxgfr/ultra11y",
16
- "repository": "https://github.com/maxgfr/ultra11y",
15
+ "homepage": "https://github.com/SocialGouv/ultra11y",
16
+ "repository": "https://github.com/SocialGouv/ultra11y",
17
17
  "license": "MIT",
18
18
  "category": "quality",
19
19
  "keywords": ["accessibility", "a11y", "wcag", "rgaa", "code-review", "audit"]
@@ -1,9 +1,9 @@
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.42.0",
4
+ "version": "5.43.0",
5
5
  "author": { "name": "maxgfr" },
6
- "homepage": "https://github.com/maxgfr/ultra11y",
7
- "repository": "https://github.com/maxgfr/ultra11y",
6
+ "homepage": "https://github.com/SocialGouv/ultra11y",
7
+ "repository": "https://github.com/SocialGouv/ultra11y",
8
8
  "license": "MIT"
9
9
  }
@@ -1,10 +1,10 @@
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.42.0",
4
+ "version": "5.43.0",
5
5
  "author": { "name": "maxgfr", "url": "https://github.com/maxgfr" },
6
- "homepage": "https://github.com/maxgfr/ultra11y",
7
- "repository": "https://github.com/maxgfr/ultra11y",
6
+ "homepage": "https://github.com/SocialGouv/ultra11y",
7
+ "repository": "https://github.com/SocialGouv/ultra11y",
8
8
  "license": "MIT",
9
9
  "keywords": ["accessibility", "a11y", "wcag", "rgaa", "code-review", "audit"],
10
10
  "skills": "./skills/",
@@ -15,7 +15,7 @@
15
15
  "developerName": "maxgfr",
16
16
  "category": "Productivity",
17
17
  "capabilities": ["Read", "Write"],
18
- "websiteURL": "https://github.com/maxgfr/ultra11y",
18
+ "websiteURL": "https://github.com/SocialGouv/ultra11y",
19
19
  "defaultPrompt": "Review my staged changes for accessibility."
20
20
  }
21
21
  }
@@ -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.42.0";
30
+ const ULTRA11Y_PLUGIN_VERSION = "5.43.0";
31
31
 
32
32
  const here = dirname(fileURLToPath(import.meta.url));
33
33
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ultra11y
2
2
 
3
- [![CI](https://github.com/maxgfr/ultra11y/actions/workflows/ci.yml/badge.svg)](https://github.com/maxgfr/ultra11y/actions/workflows/ci.yml)
3
+ [![CI](https://github.com/SocialGouv/ultra11y/actions/workflows/ci.yml/badge.svg)](https://github.com/SocialGouv/ultra11y/actions/workflows/ci.yml)
4
4
  [![npm](https://img.shields.io/npm/v/ultra11y?logo=npm)](https://www.npmjs.com/package/ultra11y)
5
5
  [![agent plugin](https://img.shields.io/badge/Claude_Code_·_Codex_·_OpenCode-plugin-6b46c1)](#add-to-your-coding-agent)
6
6
  [![node](https://img.shields.io/node/v/ultra11y)](https://nodejs.org)
@@ -13,12 +13,12 @@ pull request — you never type the skill's name:
13
13
 
14
14
  | | |
15
15
  |---|---|
16
- | **Claude Code** | `/plugin marketplace add maxgfr/ultra11y` then `/plugin install ultra11y@ultra11y` |
17
- | **OpenAI Codex CLI** | `codex plugin marketplace add maxgfr/ultra11y` then `codex plugin add ultra11y@ultra11y` |
16
+ | **Claude Code** | `/plugin marketplace add SocialGouv/ultra11y` then `/plugin install ultra11y@ultra11y` |
17
+ | **OpenAI Codex CLI** | `codex plugin marketplace add SocialGouv/ultra11y` then `codex plugin add ultra11y@ultra11y` |
18
18
  | **OpenCode** | `npx ultra11y install --opencode` — or pin `"plugin": ["ultra11y@latest"]` in `opencode.json` |
19
19
  | **Anything else** | `npx ultra11y install --agents-md` (no hook API: see the caveat below) |
20
20
 
21
- <sub>Prefer the skills only, without the automatic hook? `npx skills add maxgfr/ultra11y`
21
+ <sub>Prefer the skills only, without the automatic hook? `npx skills add SocialGouv/ultra11y`
22
22
  (`--agent codex`, `--agent opencode`, …). Already on npm and just want the gate?
23
23
  `npx ultra11y install --all`, and `npx ultra11y status` to check what is actually wired.
24
24
  Full details, thresholds and off-switches under [Install](#install).</sub>
@@ -73,8 +73,8 @@ working diff, or branch vs merge-base) and reports like a code reviewer. Both bu
73
73
  install-free engine.
74
74
 
75
75
  ```sh
76
- npx skills add maxgfr/ultra11y # this agent
77
- npx skills add maxgfr/ultra11y --agent codex # …or opencode, cursor, gemini, amp, zed…
76
+ npx skills add SocialGouv/ultra11y # this agent
77
+ npx skills add SocialGouv/ultra11y --agent codex # …or opencode, cursor, gemini, amp, zed…
78
78
  ```
79
79
 
80
80
  **As an agent plugin** — the same two skills, plus the hook that makes the review
@@ -83,8 +83,8 @@ that carries accessibility findings is stopped once and the agent is handed the
83
83
  so `review-a11y` runs before the work goes out — you never type the skill's name.
84
84
 
85
85
  ```
86
- /plugin marketplace add maxgfr/ultra11y # Claude Code
87
- codex plugin marketplace add maxgfr/ultra11y # Codex CLI
86
+ /plugin marketplace add SocialGouv/ultra11y # Claude Code
87
+ codex plugin marketplace add SocialGouv/ultra11y # Codex CLI
88
88
  npx ultra11y install --opencode # OpenCode
89
89
  ```
90
90
 
@@ -125,7 +125,7 @@ claude mcp add ultra11y -- npx -y ultra11y mcp
125
125
  **As a GitHub Action** — audits the PR diff and, optionally, the served pages:
126
126
 
127
127
  ```yaml
128
- - uses: maxgfr/ultra11y@v5
128
+ - uses: SocialGouv/ultra11y@v5
129
129
  with: { since: auto, standard: rgaa, fail-on: blocking }
130
130
  ```
131
131
 
@@ -436,7 +436,7 @@ is the source of truth; CI rebuilds it from the vendored DINUM data and rejects
436
436
  block. It re-decides nothing and invents no format, so a page sheet and the compliance report
437
437
  cannot disagree; `check` gates it against invented criteria like any other report.
438
438
  See `references/pages.md`.
439
- - **A shipped GitHub Action** — `uses: maxgfr/ultra11y@v5` audits the **code** (PR diff) and,
439
+ - **A shipped GitHub Action** — `uses: SocialGouv/ultra11y@v5` audits the **code** (PR diff) and,
440
440
  optionally, the **pages** (it can start your app, wait for it, then scan real URLs or your
441
441
  declared sample). The engine ships inside the action, so there is nothing to install and no
442
442
  `setup-node`. With pages in scope, its job summary and `pages-status.md` artifact list every
@@ -1,4 +1,4 @@
1
- import { S as SnapshotPayload, F as FindingLike } from './payload-Bb1uQH1q.js';
1
+ import { S as SnapshotPayload, F as FindingLike } from './payload-BMFQTm5s.js';
2
2
 
3
3
  type On = (event: string, handler: any) => void;
4
4
  interface TaskPayload extends SnapshotPayload {
package/dist/cypress.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as CheckOptions } from './payload-Bb1uQH1q.js';
2
- export { s as slugify } from './payload-Bb1uQH1q.js';
1
+ import { C as CheckOptions } from './payload-BMFQTm5s.js';
2
+ export { s as slugify } from './payload-BMFQTm5s.js';
3
3
 
4
4
  interface CypressCheckOptions extends CheckOptions {
5
5
  /** Capture a viewport screenshot so the pixel tier can run. On by default — the plugin
@@ -80,7 +80,10 @@ interface CheckOptions {
80
80
  * for nothing but recording the sample and asserts nothing afterwards. `{ clicks: true }`
81
81
  * additionally presses `button[type=button]` (destructive-sounding names skipped, navigation
82
82
  * aborts the pass) — never the default on an authenticated session, where a server mutation
83
- * is invisible to any check this side of the network. */
83
+ * is invisible to any check this side of the network.
84
+ *
85
+ * PLAYWRIGHT ONLY, like `probes` beside it: the Cypress integration records a page and runs
86
+ * no live probe at all, so both are inert there. Stated rather than left to be discovered. */
84
87
  liveRegion?: boolean | {
85
88
  clicks?: boolean;
86
89
  };
@@ -1,4 +1,4 @@
1
- import { C as CheckOptions, A as AuditLike } from './payload-Bb1uQH1q.js';
1
+ import { C as CheckOptions, A as AuditLike } from './payload-BMFQTm5s.js';
2
2
 
3
3
  interface PlaywrightPage {
4
4
  evaluate(script: string): Promise<unknown>;
@@ -133,6 +133,7 @@ var COLLECT_SNAPSHOT = `(() => {
133
133
  })()`;
134
134
 
135
135
  // src/probes.ts
136
+ var PROBES_VERSION = 2;
136
137
  var PRELUDE = `
137
138
  const __sel = (e) => {
138
139
  if (!e || !e.tagName) return '\u2014';
@@ -240,8 +241,18 @@ function focusSetupExpr(scope = "", maxFocusables = PROBE_DEFAULTS.maxFocusables
240
241
  // from it is not merely unmeasured -- it is silently cleared. A page whose only control is a
241
242
  // <summary> tagged nothing at all, so the count was zero, so the ring was "vacuously whole",
242
243
  // so 2.4.7 and 2.4.11 closed without a single Tab press.
244
+ //
245
+ // AND NOT iframe / audio[controls] / video[controls], which were here for one release and
246
+ // had to come back out. They are genuinely focusable, but their focus lives in another
247
+ // document: everything below reads the PARENT'S activeElement, which stays the host element
248
+ // press after press while the user tabs through the controls inside. The trap walk reads
249
+ // exactly that as a cage, so an ordinary page with a video player, a payment frame, a map or
250
+ // a support widget was reported as a bloquant 2.1.2 -- a blocker manufactured out of our own
251
+ // blindness, able to fail somebody else's gate. The measurement we cannot make is not a
252
+ // finding; Tab still crosses them, the walk still records that it crossed something it never
253
+ // measured, and the page is not cleared either.
243
254
  // (No backticks in this comment: it lives inside a template literal.)
244
- const sel = 'a[href],area[href],button:not([disabled]),input:not([type=hidden]):not([disabled]),select:not([disabled]),textarea:not([disabled]),summary,iframe,audio[controls],video[controls],[contenteditable]:not([contenteditable="false"]),[tabindex]:not([tabindex="-1"]),[role=button]:not([disabled])';
255
+ const sel = 'a[href],area[href],button:not([disabled]),input:not([type=hidden]):not([disabled]),select:not([disabled]),textarea:not([disabled]),summary,[contenteditable]:not([contenteditable="false"]),[tabindex]:not([tabindex="-1"]),[role=button]:not([disabled])';
245
256
  // A BOX THAT IS ANIMATING CANNOT BE COMPARED ACROSS TIME, so it contributes a constant.
246
257
  //
247
258
  // The snapshot is taken before Tab and re-read after it, and the properties compared include
@@ -587,6 +598,7 @@ async function runLiveProbes(page, opts = {}) {
587
598
  const size = canResize ? page.viewportSize() ?? null : null;
588
599
  const restore = size ?? { width: 1280, height: 900 };
589
600
  const out = {
601
+ v: PROBES_VERSION,
590
602
  focusVisible: [],
591
603
  hover: [],
592
604
  keyboardTrap: [],
@@ -703,6 +715,12 @@ async function runLiveProbes(page, opts = {}) {
703
715
  else skip("4.1.3", r.why ?? "the live-region pass did not exercise the whole page");
704
716
  }
705
717
  }
718
+ for (const [bucket, sc] of UNREADABLE_BY_OLDER) {
719
+ if (out[bucket]?.length) {
720
+ const at = out.probed.indexOf(sc);
721
+ if (at >= 0) out.probed.splice(at, 1);
722
+ }
723
+ }
706
724
  return out;
707
725
  }
708
726
  var DESTRUCTIVE_NAME_RE = "\\b(supprim|retir|effac|envoy|valid|confirm|pay|achet|command|delete|remove|eras|clear|send|submit|buy|order)";
@@ -856,6 +874,11 @@ var REMOVE_TEXT_SPACING_STEP = `(() => {
856
874
  }
857
875
  return true;
858
876
  })()`;
877
+ var UNREADABLE_BY_OLDER = [
878
+ ["focusObscured", "2.4.11"],
879
+ ["keyboardTrap", "2.1.2"],
880
+ ["liveRegion", "4.1.3"]
881
+ ];
859
882
 
860
883
  // src/integrations/core.ts
861
884
  import { spawnSync } from "child_process";
@@ -1055,9 +1078,13 @@ async function checkA11y(page, opts = {}) {
1055
1078
  );
1056
1079
  }
1057
1080
  let probes;
1058
- if (opts.probes) {
1081
+ if (opts.probes || opts.liveRegion) {
1059
1082
  try {
1060
- probes = await runLiveProbes(page, { ...probeOptions(opts.probes), ...opts.liveRegion ? { liveRegion: opts.liveRegion } : {} });
1083
+ probes = await runLiveProbes(page, {
1084
+ ...probeOptions(opts.probes),
1085
+ ...opts.probes ? {} : { only: ["4.1.3"] },
1086
+ ...opts.liveRegion ? { liveRegion: opts.liveRegion } : {}
1087
+ });
1061
1088
  } catch (e) {
1062
1089
  console.warn(
1063
1090
  `ultra11y: the live probes failed on this page \u2014 ${e instanceof Error ? e.message : String(e)}. The snapshot was still recorded; the criteria they decide stay to assess.`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ultra11y",
3
- "version": "5.42.0",
3
+ "version": "5.43.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",
@@ -55,7 +55,7 @@
55
55
  "lint:fix": "biome check --write .",
56
56
  "format": "biome format --write .",
57
57
  "verify:bundle": "node scripts/verify-skill-bundle.mjs",
58
- "check:build": "tsup && node scripts/copy-bundle.mjs && git diff --exit-code -- scripts/ultra11y.mjs skills/ultra11y/scripts/ultra11y.mjs skills/review-a11y/scripts/ultra11y.mjs dist && node scripts/build-extension-icons.mjs --check && node scripts/verify-skill-bundle.mjs && node scripts/sync-engine.mjs --check",
58
+ "check:build": "tsup && node scripts/copy-bundle.mjs && git diff --exit-code -- scripts/ultra11y.mjs skills/ultra11y/scripts/ultra11y.mjs skills/review-a11y/scripts/ultra11y.mjs dist && node scripts/build-extension-icons.mjs --check && node scripts/verify-skill-bundle.mjs",
59
59
  "build:wcag": "node scripts/build-standards.mjs",
60
60
  "check:standards": "node scripts/build-standards.mjs --check",
61
61
  "build:pack:rgaa": "node scripts/build-pack-rgaa.mjs",
@@ -75,7 +75,7 @@
75
75
  "author": "maxgfr",
76
76
  "repository": {
77
77
  "type": "git",
78
- "url": "https://github.com/maxgfr/ultra11y"
78
+ "url": "https://github.com/SocialGouv/ultra11y"
79
79
  },
80
80
  "dependencies": {
81
81
  "@axe-core/playwright": "^4.13.0",
@@ -711,6 +711,15 @@ const DOWNLOAD_EXT = "pdf|docx?|pptx?|xlsx?|odt|ods|odp|rtf|csv|zip|rar|7z|gz|ep
711
711
  // Where DINUM publishes each criterion. Cited by the adjudication brief so a reader can
712
712
  // reach the normative page — the vendored text above stays the authority either way.
713
713
  criterionUrl: "https://accessibilite.numerique.gouv.fr/methode/criteres-et-tests/#{id}",
714
+ // The three levels a French declaration of accessibility may state, fixed by the arrêté of
715
+ // 20 September 2019 (accessibilite.numerique.gouv.fr, obligations): every applicable
716
+ // criterion validated, at least half of them, or fewer. The report summary names the
717
+ // audited site's level from this scale and says what separates it from the next one.
718
+ conformityLevels: [
719
+ { min: 100, label: { fr: "Totalement conforme", en: "Fully compliant" } },
720
+ { min: 50, label: { fr: "Partiellement conforme", en: "Partially compliant" } },
721
+ { min: 0, label: { fr: "Non conforme", en: "Non-compliant" } },
722
+ ],
714
723
  // Auditor-display vocabulary (FR): the nouns an RGAA auditor reads. Rendered by the
715
724
  // `prd` auditor block + GitHub issues; see src/standards/vocabulary.ts.
716
725
  vocabulary: {