ultra11y 2.22.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 (153) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +223 -0
  3. package/docker/Dockerfile +7 -0
  4. package/docker/package.json +9 -0
  5. package/docker/runner.mjs +23 -0
  6. package/package.json +73 -0
  7. package/scripts/bench.mjs +151 -0
  8. package/scripts/build-act-corpus.mjs +143 -0
  9. package/scripts/build-adjudication.mjs +503 -0
  10. package/scripts/build-pack-rgaa.mjs +397 -0
  11. package/scripts/build-standards.mjs +548 -0
  12. package/scripts/copy-bundle.mjs +39 -0
  13. package/scripts/import-pack.mjs +107 -0
  14. package/scripts/sync-engine.mjs +84 -0
  15. package/scripts/sync-version.mjs +43 -0
  16. package/scripts/ultra11y.mjs +52739 -0
  17. package/scripts/vendor/act-testcases.json +12297 -0
  18. package/scripts/vendor/rgaa/SOURCE.json +8 -0
  19. package/scripts/vendor/rgaa/criteres.json +4144 -0
  20. package/scripts/vendor/rgaa/glossaire.json +480 -0
  21. package/scripts/vendor/wcag-2.2-fr.json +82 -0
  22. package/scripts/vendor/wcag-2.2-sc-universe.json +950 -0
  23. package/scripts/vendor/wcag-2.2-sc.json +574 -0
  24. package/scripts/verify-rgaa-source.mjs +126 -0
  25. package/scripts/verify-skill-bundle.mjs +91 -0
  26. package/skills/review-a11y/SKILL.md +123 -0
  27. package/skills/review-a11y/scripts/ultra11y.mjs +52739 -0
  28. package/skills/ultra11y/SKILL.md +292 -0
  29. package/skills/ultra11y/references/act.md +163 -0
  30. package/skills/ultra11y/references/adjudication.md +599 -0
  31. package/skills/ultra11y/references/audit.md +100 -0
  32. package/skills/ultra11y/references/authoring.md +37 -0
  33. package/skills/ultra11y/references/automation.md +113 -0
  34. package/skills/ultra11y/references/correction.md +41 -0
  35. package/skills/ultra11y/references/criteria.md +129 -0
  36. package/skills/ultra11y/references/cross-file.md +82 -0
  37. package/skills/ultra11y/references/dynamic.md +152 -0
  38. package/skills/ultra11y/references/false-positives.md +81 -0
  39. package/skills/ultra11y/references/fix.md +46 -0
  40. package/skills/ultra11y/references/focus-and-logic.md +64 -0
  41. package/skills/ultra11y/references/forbidden-patterns.md +61 -0
  42. package/skills/ultra11y/references/forms-and-errors.md +147 -0
  43. package/skills/ultra11y/references/guidance.md +55 -0
  44. package/skills/ultra11y/references/judgment.md +90 -0
  45. package/skills/ultra11y/references/media-and-motion.md +116 -0
  46. package/skills/ultra11y/references/methodology.md +72 -0
  47. package/skills/ultra11y/references/naming.md +117 -0
  48. package/skills/ultra11y/references/orchestration.md +44 -0
  49. package/skills/ultra11y/references/packs.md +193 -0
  50. package/skills/ultra11y/references/prd.md +104 -0
  51. package/skills/ultra11y/references/rendered.md +110 -0
  52. package/skills/ultra11y/references/scale.md +49 -0
  53. package/skills/ultra11y/references/standards.md +97 -0
  54. package/skills/ultra11y/references/structure.md +138 -0
  55. package/skills/ultra11y/references/verify.md +88 -0
  56. package/skills/ultra11y/references/widgets.md +106 -0
  57. package/skills/ultra11y/scripts/ultra11y.mjs +52739 -0
  58. package/src/act.ts +441 -0
  59. package/src/adjudicate.ts +630 -0
  60. package/src/aria.ts +230 -0
  61. package/src/audit.ts +396 -0
  62. package/src/auditor.ts +399 -0
  63. package/src/autofill.ts +102 -0
  64. package/src/axe-map.ts +294 -0
  65. package/src/baseline.ts +75 -0
  66. package/src/capture.ts +182 -0
  67. package/src/check.ts +243 -0
  68. package/src/cli.ts +1927 -0
  69. package/src/color.ts +102 -0
  70. package/src/config.ts +182 -0
  71. package/src/crawl.ts +75 -0
  72. package/src/criteria.ts +183 -0
  73. package/src/data/adjudication.json +770 -0
  74. package/src/data/guidance/rgaa.json +2395 -0
  75. package/src/data/standards/rgaa.glossary.json +478 -0
  76. package/src/data/standards/rgaa.json +3454 -0
  77. package/src/data/wcag-universe.json +530 -0
  78. package/src/data/wcag.json +1178 -0
  79. package/src/discover.ts +203 -0
  80. package/src/fix/codemods.ts +76 -0
  81. package/src/fix/diff.ts +64 -0
  82. package/src/fix/edits.ts +126 -0
  83. package/src/fix.ts +215 -0
  84. package/src/gh.ts +214 -0
  85. package/src/glob.ts +206 -0
  86. package/src/graph/build.ts +114 -0
  87. package/src/graph/engine-resolve.ts +0 -0
  88. package/src/graph/graph.ts +126 -0
  89. package/src/graph/imports.ts +359 -0
  90. package/src/graph/resolve.ts +73 -0
  91. package/src/graph/tsconfig.ts +113 -0
  92. package/src/grounding.ts +119 -0
  93. package/src/guidance/index.ts +45 -0
  94. package/src/guidance/types.ts +36 -0
  95. package/src/init.ts +113 -0
  96. package/src/mcp/handlers.ts +388 -0
  97. package/src/mcp/http.ts +260 -0
  98. package/src/mcp/prompts.ts +183 -0
  99. package/src/mcp/protocol.ts +187 -0
  100. package/src/mcp/resources.ts +136 -0
  101. package/src/mcp/server.ts +228 -0
  102. package/src/mcp/stdio.ts +119 -0
  103. package/src/mcp/tools.ts +347 -0
  104. package/src/messages.ts +1011 -0
  105. package/src/name.ts +174 -0
  106. package/src/orchestrate-templates.ts +231 -0
  107. package/src/orchestrate.ts +186 -0
  108. package/src/output.ts +108 -0
  109. package/src/pack.ts +187 -0
  110. package/src/parse/html.ts +258 -0
  111. package/src/parse/jsx-ast.ts +67 -0
  112. package/src/parse/jsx-bridge.ts +247 -0
  113. package/src/parse/jsx.ts +19 -0
  114. package/src/parse/source.ts +75 -0
  115. package/src/prd.ts +406 -0
  116. package/src/project-lock.ts +42 -0
  117. package/src/render.ts +307 -0
  118. package/src/report.ts +399 -0
  119. package/src/rules/colors.ts +125 -0
  120. package/src/rules/cross-registry.ts +264 -0
  121. package/src/rules/cross-rule.ts +83 -0
  122. package/src/rules/forms.ts +590 -0
  123. package/src/rules/frames.ts +33 -0
  124. package/src/rules/headings.ts +227 -0
  125. package/src/rules/images.ts +200 -0
  126. package/src/rules/links.ts +193 -0
  127. package/src/rules/mandatory.ts +173 -0
  128. package/src/rules/multimedia.ts +77 -0
  129. package/src/rules/navigation.ts +190 -0
  130. package/src/rules/presentation.ts +75 -0
  131. package/src/rules/registry.ts +57 -0
  132. package/src/rules/rule.ts +122 -0
  133. package/src/rules/scripts-aria.ts +636 -0
  134. package/src/rules/tables.ts +264 -0
  135. package/src/rules/timing.ts +55 -0
  136. package/src/sample.ts +176 -0
  137. package/src/scan-local.ts +866 -0
  138. package/src/scan.ts +538 -0
  139. package/src/standards/derive.ts +195 -0
  140. package/src/standards/index.ts +31 -0
  141. package/src/standards/pack-rules.ts +111 -0
  142. package/src/standards/pack.ts +81 -0
  143. package/src/standards/registry.ts +100 -0
  144. package/src/standards/types.ts +202 -0
  145. package/src/standards/validate.ts +544 -0
  146. package/src/standards/vocabulary.ts +75 -0
  147. package/src/types.ts +355 -0
  148. package/src/util.ts +32 -0
  149. package/src/vendor/codeindex-engine.d.mts +1009 -0
  150. package/src/vendor/codeindex-engine.mjs +13701 -0
  151. package/src/vendor/engine.meta.json +9 -0
  152. package/src/verify.ts +275 -0
  153. package/src/wcag.ts +128 -0
@@ -0,0 +1,100 @@
1
+ # Audit existing code → WCAG report
2
+
3
+ Goal: produce a dated, reliable WCAG 2.2 AA compliance report. The engine decides the
4
+ automatable subset; the **AI agent** adjudicates the judgment criteria (`verify --manual`,
5
+ gated) and routes the rendering criteria to `scan`; the gates stop any hallucinated
6
+ non-conformity.
7
+
8
+ ## The loop
9
+
10
+ 1. **Scope it.** Which files / components? HTML, JSX/TSX and Vue/Svelte/Astro components
11
+ (`.vue`/`.svelte`/`.astro`) are walked by default; add server templates (Twig, ERB,
12
+ Handlebars…) with `--ext .twig,.erb`. **Test/spec/story markup** (`*.test.*`,
13
+ `*.spec.*`, `*.stories.*`, `__tests__/`) is excluded by default — it is bad-by-design
14
+ and never shipped; pass `--no-default-excludes` (or name the file) to audit it anyway.
15
+ **`.vue`/`.svelte`/`.astro` are audited as SOURCE templates**: slots, snippets and
16
+ dynamic bindings are invisible, so their findings are flagged `preliminary` and the run
17
+ carries a `scope.sourceTemplate` caveat — audit the rendered output to confirm (step 3,
18
+ and `references/rendered.md`).
19
+ 2. **Run the engine:**
20
+ ```
21
+ node scripts/ultra11y.mjs audit "src/**/*.html" --json > audit.json
22
+ ```
23
+ or on a snippet: `node scripts/ultra11y.mjs audit - < page.html --json`.
24
+ The `AuditResult` classes each success criterion as `C` / `NC` / `NA` (static) or
25
+ `manual` (rendering / judgment, listed in `residualRisks`). It also records the repo's
26
+ declared language(s) as `scope.langs` (the primary `<html lang>` subtags seen, most
27
+ frequent first, e.g. `["fr"]`) — the repo signal `--lang auto` resolves from downstream.
28
+ 3. **Triage the results:**
29
+ - engine `NC` = confirmed candidates (each finding cites `file:line`);
30
+ - `manual` *needs-rendering* criteria (contrast 1.4.3, focus visible 2.4.7, reflow
31
+ 1.4.10…) → route to `scan`, **never** silently `C`. Note: contrast on
32
+ **inline literal colours** is now decided statically (1.4.3 turns `NC` for that
33
+ subset); contrast via external CSS or variables stays residual (→ dynamic tier);
34
+ - `manual` *judgment* criteria (alt relevance under 1.1.1, link purpose 2.4.4, reading
35
+ / tab order…) → the AI agent adjudicates them from context via `verify --manual` (gated).
36
+ 4. **Decide each applicable criterion**: `C`, `NC` or `NA` (with a justification). For a
37
+ criterion's detail and grounding: `node scripts/ultra11y.mjs criteria 1.1.1`.
38
+ 5. **Render the report:**
39
+ ```
40
+ node scripts/ultra11y.mjs report --in audit.json --out audits
41
+ ```
42
+ → `audits/wcag-YYYY-MM-DD.md` (5 sections, see `references/methodology.md`); section 2
43
+ renders one **auditor conformance block** per NC criterion (theme, criterion + official
44
+ wording, test(s), WCAG mapping + level, finding, expected state, verification,
45
+ `file:line` occurrences), grouped by severity — the SAME block `prd` and `--gh-issues`
46
+ emit (see `references/prd.md`), so report and backlog never drift. For a country
47
+ standard, add `--standard rgaa` (see `references/standards.md`). Pass `--lang` to match
48
+ your conversation; without it, `auto` resolves the audit's `scope.langs` → the
49
+ standard's default locale → English. Add `--json` to `report` for a machine summary
50
+ (`{path, conformancePct, date, standard}`) instead of just the path.
51
+ 6. **Check integrity:**
52
+ ```
53
+ node scripts/ultra11y.mjs check --report audits/wcag-YYYY-MM-DD.md
54
+ ```
55
+ Fails if a section is missing, a cited criterion does not exist, or an `NA` is unjustified.
56
+ 7. **High assurance (optional)**: `references/verify.md` — prove every non-conformity is
57
+ real before you ship.
58
+
59
+ ## The normative page sample (échantillon) — country-standard audits
60
+
61
+ A file-tree audit answers "is this markup conformant?"; a **country-standard** audit (RGAA,
62
+ Section 508…) is normatively defined over a **representative page sample**, not the whole
63
+ repo. Declare that sample once in `.ultra11yrc.json`:
64
+
65
+ ```json
66
+ { "standard": "rgaa",
67
+ "sample": {
68
+ "pages": [
69
+ { "id": "accueil", "name": "Page d'accueil", "url": "http://localhost:3000/" },
70
+ { "id": "contact", "name": "Contact", "url": "http://localhost:3000/contact" },
71
+ { "id": "compte", "name": "Mon compte", "url": "http://localhost:3000/compte", "auth": true, "storageState": ".auth/user.json", "notes": "connecté" }
72
+ ],
73
+ "transverse": ["header", "navigation principale", "pied de page"]
74
+ } }
75
+ ```
76
+
77
+ - `sample.pages[]` are the audited URLs (each may sit behind `auth` with a per-page
78
+ `storageState`); `transverse` names the elements audited on **every** page (header, nav,
79
+ footer, modals…). The `storageState` **path** is used but its content is never read into
80
+ any output.
81
+ - **Lint the coverage**: `node scripts/ultra11y.mjs sample check` reports which **required
82
+ page kinds** the active standard's `sampleMethodology` expects but the sample lacks (RGAA:
83
+ accueil, contact, mentions légales, déclaration d'accessibilité, plan du site, aide,
84
+ authentification…). Advisory (exit 0) unless the `sample` block is malformed (exit 2).
85
+ - **Scan the sample**: `scan --sample --merge audits/audit-latest.json` iterates every page,
86
+ keeps each finding's page name + auth flag as provenance, and merges the rendered verdicts
87
+ in (see `references/dynamic.md`).
88
+ - **Partial-audit honesty**: a `--standard rgaa` report produced **without** a merged sample
89
+ scan is flagged **partial** — a CLI warning + a report banner naming the needs-rendering
90
+ criteria that were not tested. Say the audit is partial rather than implying full coverage.
91
+
92
+ ## Golden rules
93
+
94
+ - **Never invent a non-conformity**: every `NC` must cite a real, resolvable element
95
+ (`check` verifies it).
96
+ - **Residual is explicit, never silently `C`**: an unproven *needs-rendering* criterion goes to
97
+ `scan`; a *judgment* criterion the AI agent adjudicates via `verify --manual` (gated). Any
98
+ criterion still undecided stays in the "to assess manually" section with a recorded reason.
99
+ - The report's **automatic static-check pass rate** covers only the machine-decidable
100
+ subset; full WCAG conformance requires your manual review (it is not a conformance rate).
@@ -0,0 +1,37 @@
1
+ # Write / review accessible HTML/CSS (without regressions)
2
+
3
+ Doctrine: **semantic native HTML first, ARIA last** (native-first, ARIA last). Add ARIA
4
+ only when no native element fits, and never duplicate implicit semantics (see
5
+ `references/forbidden-patterns.md`).
6
+
7
+ ## Review loop
8
+
9
+ 1. **Structure & landmarks** — a single `<main>`, `<header>/<nav>/<footer>`, `h1…h6`
10
+ headings without level skips, real lists (`ul/ol/dl`).
11
+ 2. **Accessible names** — every link/button has a label; every field has an associated
12
+ `<label>` (not just a `placeholder`).
13
+ 3. **Images & icons** — relevant `alt` if informative, `alt=""` if decorative, a name on
14
+ icon-only controls.
15
+ 4. **Tables** — `<caption>`, `<th scope>`, cell↔header association.
16
+ 5. **Keyboard** — everything reachable and operable by keyboard; no positive `tabindex`;
17
+ visible focus (verify at render); a working skip link.
18
+ 6. **Language & title** — `<html lang>`, a relevant `<title>`, language changes marked.
19
+
20
+ ## Catch regressions early
21
+
22
+ Before shipping the code, audit the snippet:
23
+ ```
24
+ node scripts/ultra11y.mjs audit - < component.html
25
+ node scripts/ultra11y.mjs audit "src/components/**/*.tsx" --jsx
26
+ ```
27
+ Fix by priority: native semantics → names/labels → keyboard → focus → meaning not carried
28
+ by colour/shape alone → media.
29
+
30
+ ## Definition of Done
31
+
32
+ A change is "done" when: native semantics are correct, accessible names are present,
33
+ keyboard access is preserved, no regression is introduced, and the points not statically
34
+ verifiable (contrast, visible focus, zoom) are **flagged as residual risks** rather than
35
+ assumed OK.
36
+
37
+ For the exact meaning of a criterion: `node scripts/ultra11y.mjs criteria <sc>`.
@@ -0,0 +1,113 @@
1
+ # Automate in the repo — `init` (hook / CI)
2
+
3
+ ultra11y can stay an **on-demand** skill, or become a **gate** that runs on its own.
4
+ `init` wires it (zero-dependency, no husky):
5
+
6
+ ```
7
+ node scripts/ultra11y.mjs init --hook # .git/hooks/pre-commit — strict STAGED gate (default)
8
+ node scripts/ultra11y.mjs init # same as --hook
9
+ node scripts/ultra11y.mjs init --ci # .github/workflows/a11y.yml (PR diff vs base)
10
+ node scripts/ultra11y.mjs init --baseline … # opt into the legacy regression gate (below)
11
+ ```
12
+
13
+ ## Default: strict staged snapshot + safe auto-fix
14
+
15
+ The default pre-commit hook operates on **exactly what is about to be committed** — the
16
+ **staged index blobs**, read with `git show :./<path>`, *not* the working-tree copy.
17
+ It runs (fix first, then gate — so safe fixes are always applied, and only the judgment
18
+ issues that remain can block):
19
+
20
+ ```
21
+ node scripts/ultra11y.mjs fix --staged --write --safe # auto-apply SAFE fixes to the staged snapshot, re-stage
22
+ node scripts/ultra11y.mjs audit --staged --fail-on blocking # gate: block only if issues remain (need judgment)
23
+ ```
24
+
25
+ Why fix-first: every auto-fixable rule (positive `tabindex`, redundant `role`,
26
+ zoom-blocking viewport) is `major`/`minor`, while the `blocking` findings (missing `alt`,
27
+ missing label) are exactly the ones a codemod must **not** guess. So the safe pass cleans
28
+ up and re-stages what it can, and the gate blocks only on the judgment remainder for the AI
29
+ agent to adjudicate.
30
+
31
+ - **`--staged`** scopes to staged adds/mods only (not untracked, not unstaged-only edits)
32
+ and audits the exact bytes a commit would record — including `.gitattributes` clean
33
+ filters. `--staged` wins over `--changed`/`--since`.
34
+ - **`--safe`** applies only genuinely-**automatic** codemods. Placeholder inserts
35
+ (`alt="TODO"`, `lang="und"`, `title="TODO"`) are **not** applied: a stub would clear the
36
+ finding mechanically while still needing a real value, so those (and judgment proposals)
37
+ are left in place and keep the gate blocking.
38
+ - **Auto-fix + re-stage** happens only for **fully-staged** files (working tree == index).
39
+ A **partially-staged** file (unstaged edits present) is left untouched — writing
40
+ index-derived output over it and `git add`-ing would silently stage those edits — so its
41
+ findings keep the gate blocking with a message to stage/fix it manually.
42
+ - The commit proceeds only when no blocking issue remains after the safe pass. One-off
43
+ bypass: `SKIP_A11Y=1 git commit …`. Engine path override: `ULTRA11Y=/path/to/ultra11y.mjs`.
44
+
45
+ **When the hook blocks on judgment issues** (alt relevance, link purpose, labels,
46
+ heading structure) the agent should: open the cited **staged** code, apply the *real*
47
+ fix (not a placeholder), `git add` the file, and re-commit — or run `fix --staged
48
+ --write` to lay down placeholders and then fill them in.
49
+
50
+ ## MUST: the FINAL, rendered semantic HTML has to be correct
51
+
52
+ The static engine only sees **source**. When markup comes from a **component library**
53
+ (DSFR, MUI, Chakra…) or a `.vue`/`.svelte`/`.astro` SFC, the real **semantic HTML** only
54
+ exists **after rendering** — the source `<Button/>` tells you nothing about the `<button>`
55
+ (or `<div role>`) it emits. A green staged gate on opaque component source is **not**
56
+ proof of an accessible result.
57
+
58
+ **So make the produced semantic HTML the thing you gate — with rendered CAPTURES.** A
59
+ capture is the *rendered* DOM serialized to `.html` and committed, so the gate audits the
60
+ true markup, not the component call. Zero-touch setup (your tests do the rendering):
61
+
62
+ ```
63
+ node scripts/ultra11y.mjs render --setup # install the capture harvester into your test runner
64
+ npm test # every rendered component → .ultra11y/captures/*.html (provenance-tagged)
65
+ node scripts/ultra11y.mjs audit --require-captures # gate: every opaque/control component must have a capture
66
+ ```
67
+
68
+ - **Attribution + coverage.** A finding on a capture is reported against the SOURCE
69
+ component (via its `<!-- ultra11y:capture … -->` provenance), not the capture file.
70
+ `render --coverage` shows covered vs blind-spot components; `--require-captures` turns any
71
+ remaining blind spot into a failure. `.vue`/`.svelte`/`.astro` components now count in this
72
+ coverage too, so a previously-green gate can newly report blind spots on an SFC-heavy repo —
73
+ intended (a named blind spot beats invisibility); capture those components or scope the gate.
74
+ - **The gate sees the captures for the diffed components automatically.** In
75
+ `--changed`/`--staged`/`--since` mode the audit pulls in the committed captures whose
76
+ provenance points at a diffed source file (a capture is rarely itself part of the diff —
77
+ the SOURCE changed, its capture usually didn't), so the pre-commit gate audits the real
78
+ `<button>`/`<nav>`/heading structure/labels for every touched component, catching
79
+ regressions the opaque source hides. After a component change, re-run your tests so its
80
+ capture refreshes, and stage the refreshed capture with the source. `fix` never rewrites
81
+ captures (generated output). Storybook/build HTML and manual Testing-Library dumps also
82
+ work — anything under `.ultra11y/captures`, or point `--captures <dir>` (`--no-captures`
83
+ opts out entirely).
84
+ - In CI, audit the **built** output (`audit "dist/**/*.html"`), and use `scan` for the
85
+ computed rendering criteria (contrast, focus, zoom). See `references/rendered.md`.
86
+
87
+ Treat this as a hard rule: **do not sign off on a component/SFC change from the source
88
+ audit alone — verify the rendered semantic HTML, and prefer committed captures so the
89
+ gate keeps verifying it.**
90
+
91
+ ## Opt-in: baseline regression gate (legacy)
92
+
93
+ For large existing backlogs, keep the original "only block NEW regressions" gate. It is
94
+ opt-in via `--baseline` (or an explicit `--fail-on`), and is what `--ci` uses:
95
+
96
+ ```
97
+ node scripts/ultra11y.mjs init --baseline # writes audits/baseline.json (commit it)
98
+ node scripts/ultra11y.mjs init --hook --baseline
99
+ node scripts/ultra11y.mjs init --ci # audit --since <base> --baseline … in Actions
100
+ ```
101
+
102
+ The hook/CI then run `audit --changed --baseline audits/baseline.json --fail-on blocking`:
103
+ the audit is restricted to the **diff**, and only **NEW** non-conformities at/above the
104
+ threshold fail — the existing backlog never blocks. Finding identity `(rule, criterion,
105
+ file, source range)` is robust to line drift. **Commit `audits/baseline.json`** (add a
106
+ `!audits/baseline.json` negation if `audits/` is git-ignored) and refresh it as you burn
107
+ down backlog. `--fail-on blocking|major|minor` sets the threshold (French aliases
108
+ `bloquant|majeur|mineur` accepted).
109
+
110
+ > Either gate relies on the **static** engine. The **rendering** (contrast, focus, zoom) criteria
111
+ > go to `scan` and the **judgment** criteria are adjudicated by the AI agent (`verify --manual`,
112
+ > gated) in the full audit — the gate stops mechanical regressions, it is not the full audit
113
+ > (a human is at most optional oversight).
@@ -0,0 +1,41 @@
1
+ # Correction phase (put the fixes in place, by priority)
2
+
3
+ Fix without breaking or inventing anything. Doctrine (native HTML first, smallest possible
4
+ change, no regressions) applied in priority order.
5
+
6
+ ## Processing order
7
+
8
+ 1. **🔴 Blocking first**, then 🟠 major, then 🟡 minor — the order of the `prd` backlog and the
9
+ report. On a large repo, handle the shared templates first (layouts, design system): a fix
10
+ there covers the whole site.
11
+ 2. **Three classes of fix** (`fix`):
12
+ - **auto** — deterministic codemods (positive tabindex, redundant ARIA, blocked zoom); safe
13
+ to apply unsupervised.
14
+ - **placeholder** — inserts a valid attribute with a `TODO` (alt/lang/title/aria-label) that
15
+ you replace with a real, in-context value.
16
+ - **proposal** — judgment only; the engine never invents content (alt text, link purpose,
17
+ table structure) — the AI agent writes it and adjudicates the criterion (`verify --manual`,
18
+ gated).
19
+ 3. **Conforming / non-conforming examples**: for each pattern, start from the smallest change
20
+ that makes the **rendered** HTML conforming (not just the source). If a library component
21
+ does not instantiate correctly (e.g. an icon-only DSFR button with no `title`), fix at the
22
+ usage site and re-verify on the render.
23
+
24
+ ## Commands
25
+
26
+ ```
27
+ node scripts/ultra11y.mjs fix "src/**/*.html" # dry-run: proposed diff
28
+ node scripts/ultra11y.mjs fix "src/**/*.html" --write # apply (auto+placeholder)
29
+ node scripts/ultra11y.mjs fix "src/**/*.tsx" --write # real JSX: jsxSafe codemods only
30
+ node scripts/ultra11y.mjs fix "src/**" --write --iterate # re-audit + re-apply to a fixpoint
31
+ node scripts/ultra11y.mjs fix "src/**" --only img-alt-missing,positive-tabindex
32
+ node scripts/ultra11y.mjs prd --in audits/audit-latest.json # prioritized backlog of remaining fixes
33
+ ```
34
+
35
+ - **Anti-regression gate**: `--write` applies only if a re-audit proves no new non-conformity
36
+ is introduced (including no new finding type). An invalid placeholder is thus blocked.
37
+ - **JSX/TSX**: `fix --write` applies only the safe codemods (remove ARIA, insert valid React
38
+ attributes); it never rewrites an attribute name (no `tabIndex={5}` → `tabindex="0"`). The
39
+ rest stays a proposal.
40
+ - After fixing, **loop**: re-audit, re-judge the `proposal`s, until `check`/`verify` are green
41
+ again (see the SKILL's "loop" section).
@@ -0,0 +1,129 @@
1
+ <!-- GENERATED from src/data/wcag.json by `ultra11y criteria --generate` — do not edit by hand. -->
2
+
3
+ # WCAG 2.2 Level AA — success-criteria reference
4
+
5
+ The 55 Level A + AA success criteria across the 4 principles / 13 guidelines, with each
6
+ SC's level, the ultra11y automatability class (automatable / needs rendering / judgment),
7
+ the engine rules that cover it, and — one column per registered standards pack — the pack
8
+ criteria that map to it. SC ids, titles and levels are derived from the W3C source
9
+ (https://github.com/w3c/wcag); WCAG 2.2 © W3C.
10
+
11
+ ## 1.1 Text Alternatives
12
+
13
+ | SC | Title | Level | Automatability | Rules | RGAA |
14
+ |---|---|---|---|---|---|
15
+ | 1.1.1 | Non-text Content | A | judgment | img-alt-missing, canvas-fallback-missing, decorative-alt-misuse, input-image-alt-missing, object-embed-no-name, chart-no-accessible-name | 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.9, 4.7, 4.8, 4.9, 6.1, 6.2, 7.2, 10.2, 13.3, 13.4, 13.5, 13.6 |
16
+
17
+ ## 1.2 Time-based Media
18
+
19
+ | SC | Title | Level | Automatability | Rules | RGAA |
20
+ |---|---|---|---|---|---|
21
+ | 1.2.1 | Audio-only and Video-only (Prerecorded) | A | judgment | — | 4.1, 4.2 |
22
+ | 1.2.2 | Captions (Prerecorded) | A | judgment | media-no-track | 4.3, 4.4 |
23
+ | 1.2.3 | Audio Description or Media Alternative (Prerecorded) | A | judgment | — | 4.1, 4.2 |
24
+ | 1.2.4 | Captions (Live) | AA | judgment | — | — |
25
+ | 1.2.5 | Audio Description (Prerecorded) | AA | judgment | — | 4.5, 4.6 |
26
+
27
+ ## 1.3 Adaptable
28
+
29
+ | SC | Title | Level | Automatability | Rules | RGAA |
30
+ |---|---|---|---|---|---|
31
+ | 1.3.1 | Info and Relationships | A | judgment | fieldset-legend-missing, data-table-no-headers, table-caption-missing, layout-table-data-markup, heading-order-skip, h1-missing, h1-multiple, list-structure, dl-structure, empty-heading, label-for-dangling, missing-main-landmark, multiple-main-landmark, sortable-header-no-aria-sort, nav-landmark-missing, nav-landmark-unnamed, aria-required-parent, headers-attr-dangling, th-no-data-cells, radio-checkbox-group-ungrouped, table-empty-data-cell, css-generated-content-informative | 3.1, 5.1, 5.2, 5.4, 5.5, 5.6, 5.7, 5.8, 7.3, 8.9, 9.1, 9.2, 9.3, 9.4, 10.1, 10.2, 11.1, 11.5, 11.6, 11.7, 11.8, 12.6, 13.3, 13.4 |
32
+ | 1.3.2 | Meaningful Sequence | A | judgment | — | 5.3, 8.10, 10.1, 10.3, 10.8, 13.3, 13.4 |
33
+ | 1.3.3 | Sensory Characteristics | A | judgment | — | 10.9, 10.10 |
34
+ | 1.3.4 | Orientation | AA | needs-rendering | — | 13.9 |
35
+ | 1.3.5 | Identify Input Purpose | AA | judgment | field-purpose-incomplete, autocomplete-token-invalid | 11.13 |
36
+
37
+ ## 1.4 Distinguishable
38
+
39
+ | SC | Title | Level | Automatability | Rules | RGAA |
40
+ |---|---|---|---|---|---|
41
+ | 1.4.1 | Use of Color | A | needs-rendering | — | 3.1, 10.6, 10.7, 10.9, 10.10 |
42
+ | 1.4.2 | Audio Control | A | static | autoplay-media | 4.10 |
43
+ | 1.4.3 | Contrast (Minimum) | AA | needs-rendering | contrast-literal | 3.2, 10.5 |
44
+ | 1.4.4 | Resize Text | AA | needs-rendering | meta-viewport-zoom-block | 10.4 |
45
+ | 1.4.5 | Images of Text | AA | needs-rendering | — | 1.8 |
46
+ | 1.4.10 | Reflow | AA | needs-rendering | — | 10.11 |
47
+ | 1.4.11 | Non-text Contrast | AA | needs-rendering | — | 3.3 |
48
+ | 1.4.12 | Text Spacing | AA | needs-rendering | — | 10.12 |
49
+ | 1.4.13 | Content on Hover or Focus | AA | needs-rendering | — | 10.13 |
50
+
51
+ ## 2.1 Keyboard Accessible
52
+
53
+ | SC | Title | Level | Automatability | Rules | RGAA |
54
+ |---|---|---|---|---|---|
55
+ | 2.1.1 | Keyboard | A | judgment | clickable-noninteractive | 4.11, 4.12, 7.3, 10.14, 12.9, 12.11 |
56
+ | 2.1.2 | No Keyboard Trap | A | needs-rendering | — | 4.11, 4.12, 12.9 |
57
+ | 2.1.4 | Character Key Shortcuts | A | judgment | — | 12.10 |
58
+
59
+ ## 2.2 Enough Time
60
+
61
+ | SC | Title | Level | Automatability | Rules | RGAA |
62
+ |---|---|---|---|---|---|
63
+ | 2.2.1 | Timing Adjustable | A | judgment | meta-refresh-redirect | 13.1, 13.8 |
64
+ | 2.2.2 | Pause, Stop, Hide | A | judgment | autoplay-media, blink-marquee | 13.1, 13.8 |
65
+
66
+ ## 2.3 Seizures and Physical Reactions
67
+
68
+ | SC | Title | Level | Automatability | Rules | RGAA |
69
+ |---|---|---|---|---|---|
70
+ | 2.3.1 | Three Flashes or Below Threshold | A | needs-rendering | — | 13.7 |
71
+
72
+ ## 2.4 Navigable
73
+
74
+ | SC | Title | Level | Automatability | Rules | RGAA |
75
+ |---|---|---|---|---|---|
76
+ | 2.4.1 | Bypass Blocks | A | judgment | skip-link-target-missing | 9.1, 12.6, 12.7, 13.3, 13.4 |
77
+ | 2.4.2 | Page Titled | A | static | title-missing-empty | 8.5, 8.6 |
78
+ | 2.4.3 | Focus Order | A | judgment | positive-tabindex | 10.3, 12.7, 12.8, 13.3, 13.4 |
79
+ | 2.4.4 | Link Purpose (In Context) | A | judgment | link-empty-name, icon-only-control-unnamed | 6.1, 6.2 |
80
+ | 2.4.5 | Multiple Ways | AA | judgment | — | 12.1, 12.3, 12.4 |
81
+ | 2.4.6 | Headings and Labels | AA | judgment | — | 9.1, 11.1, 11.2 |
82
+ | 2.4.7 | Focus Visible | AA | needs-rendering | — | 7.3, 10.7 |
83
+ | 2.4.11 | Focus Not Obscured (Minimum) | AA | needs-rendering | — | — |
84
+
85
+ ## 2.5 Input Modalities
86
+
87
+ | SC | Title | Level | Automatability | Rules | RGAA |
88
+ |---|---|---|---|---|---|
89
+ | 2.5.1 | Pointer Gestures | A | judgment | — | 13.10 |
90
+ | 2.5.2 | Pointer Cancellation | A | judgment | — | 13.11 |
91
+ | 2.5.3 | Label in Name | A | judgment | label-in-name-mismatch | 6.1, 7.1, 11.2, 11.9 |
92
+ | 2.5.4 | Motion Actuation | A | judgment | — | 13.12 |
93
+ | 2.5.7 | Dragging Movements | AA | judgment | — | — |
94
+ | 2.5.8 | Target Size (Minimum) | AA | needs-rendering | — | — |
95
+
96
+ ## 3.1 Readable
97
+
98
+ | SC | Title | Level | Automatability | Rules | RGAA |
99
+ |---|---|---|---|---|---|
100
+ | 3.1.1 | Language of Page | A | static | html-lang-missing, lang-invalid | 8.3, 8.4, 13.3, 13.4 |
101
+ | 3.1.2 | Language of Parts | AA | judgment | inline-lang-change-missing, lang-invalid | 8.7, 8.8 |
102
+
103
+ ## 3.2 Predictable
104
+
105
+ | SC | Title | Level | Automatability | Rules | RGAA |
106
+ |---|---|---|---|---|---|
107
+ | 3.2.1 | On Focus | A | judgment | — | 7.4, 13.2 |
108
+ | 3.2.2 | On Input | A | judgment | on-input-context-change | 7.4 |
109
+ | 3.2.3 | Consistent Navigation | AA | judgment | — | 12.2, 12.4, 12.5, 12.7 |
110
+ | 3.2.4 | Consistent Identification | AA | judgment | — | 11.3 |
111
+ | 3.2.6 | Consistent Help | A | judgment | — | — |
112
+
113
+ ## 3.3 Input Assistance
114
+
115
+ | SC | Title | Level | Automatability | Rules | RGAA |
116
+ |---|---|---|---|---|---|
117
+ | 3.3.1 | Error Identification | A | judgment | aria-invalid-no-description, error-not-associated | 11.10 |
118
+ | 3.3.2 | Labels or Instructions | A | judgment | radio-checkbox-group-ungrouped, date-fields-ungrouped | 11.1, 11.2, 11.4, 11.5, 11.6, 11.7, 11.10 |
119
+ | 3.3.3 | Error Suggestion | AA | judgment | — | 11.11 |
120
+ | 3.3.4 | Error Prevention (Legal, Financial, Data) | AA | judgment | — | 11.12 |
121
+ | 3.3.7 | Redundant Entry | A | judgment | — | — |
122
+ | 3.3.8 | Accessible Authentication (Minimum) | AA | judgment | credential-entry-blocked | — |
123
+
124
+ ## 4.1 Compatible
125
+
126
+ | SC | Title | Level | Automatability | Rules | RGAA |
127
+ |---|---|---|---|---|---|
128
+ | 4.1.2 | Name, Role, Value | A | judgment | iframe-title-missing, invalid-aria-role, aria-ref-missing-id, redundant-aria, clickable-noninteractive, aria-required-children, aria-hidden-focusable, nested-interactive, invalid-aria-attr, invalid-aria-value, aria-required-attr, aria-prohibited-attr, duplicate-id, control-label-missing, placeholder-as-label, form-field-multiple-labels, select-has-option, button-empty-name, icon-only-control-unnamed, control-name-title-only, field-purpose-incomplete, disabled-context-content | 1.2, 1.3, 1.9, 2.1, 2.2, 4.13, 5.3, 7.1, 7.2, 8.2, 9.1, 10.8, 11.1, 11.9, 12.6, 13.3, 13.4 |
129
+ | 4.1.3 | Status Messages | AA | judgment | live-region-conflict, status-message-not-assertive | 7.5 |
@@ -0,0 +1,82 @@
1
+ # Cross-file analysis (`audit --graph`)
2
+
3
+ By default the engine audits **each file in isolation**. `--graph` (alias `--cross-file`)
4
+ adds a pass that **resolves imports between files**, builds a dependency + component graph,
5
+ and applies rules only the cross-file context makes visible — **without a browser** (no
6
+ Playwright). It complements the per-file static engine and the optional dynamic tier
7
+ (`scan`): all three feed the **same** `AuditResult`.
8
+
9
+ ```
10
+ node scripts/ultra11y.mjs audit "src/**/*.tsx" --graph --json > audit.json
11
+ node scripts/ultra11y.mjs audit --changed --graph # git diff, graph over the whole scope
12
+ ```
13
+
14
+ ## How it works (and why it scales)
15
+
16
+ - **Real AST**: `.jsx/.tsx` are parsed by a real JS/TS/JSX parser (`@babel/parser`, embedded
17
+ in the bundle — still "no install"), not regex normalization. `PascalCase` components keep
18
+ their case (per-file rules ignore them; only cross-file rules resolve them); native
19
+ elements stay lowercase.
20
+ - **Two passes, bounded memory**: pass 1 — read each file, extract a small *graph node*
21
+ (imports, components + render-control signals, ids, `<html lang>`), then **drop** the
22
+ AST/Doc. Pass 2 — the usual audit loop, which also runs the cross-file rules with the graph
23
+ in hand. The whole repo is never held in memory: O(number of files) small nodes.
24
+ - **`--changed --graph`**: the graph indexes the **whole** scope (to resolve a reference into
25
+ an unchanged definition), but only the diffed files are audited.
26
+ - **Plain `.ts`/`.js` modules are graphed, never audited.** The graph's own discovery widens
27
+ to `.ts`/`.js`/`.mjs`/`.cjs` on top of the markup allowlist: a **barrel**
28
+ (`components/index.ts` re-exporting `Button.tsx`) or a plain-JS component definition is
29
+ real cross-file structure that imports resolve through — chained re-exports
30
+ (`export { Button } from "./Button"`, barrels of barrels) are followed to the real
31
+ definition. Those files feed imports/re-exports/definitions/ids into the graph but never
32
+ enter the audit loop (no markup rule ever runs on them).
33
+ - **`.vue`/`.svelte`/`.astro` SFCs are first-class graph nodes.** Parsed with component case
34
+ preserved (same as the audit); the file's own `<script>` block — or an `.astro` file's
35
+ `---…---` **frontmatter** — is parsed separately as a script AST for its imports; and each
36
+ SFC synthesizes a self component definition (PascalCase basename), so a `.tsx` importing
37
+ `./Widget.vue` resolves cross-file and the SFC counts in capture coverage. Astro
38
+ frontmatter is also stripped (offset-preserving) before the template parse, so frontmatter
39
+ TypeScript (`Array<string>`) never produces phantom elements or findings.
40
+ - **Import resolution**: relative specifiers (`./IconButton`), **tsconfig-paths aliases**
41
+ (`@/components/Icon`, read from the nearest `tsconfig.json`'s `baseUrl`/`paths`), and
42
+ **namespace members** (`<UI.Button/>` from `import * as UI`) all resolve to a discovered
43
+ file. *Bounds (documented, never a silent false "conforming"):* bare `node_modules`
44
+ specifiers (`react`) are out of scope; alias bases are resolved relative to the working
45
+ dir (run the audit from the project root); React **context** value flow and dynamic
46
+ `import()` are not traced.
47
+
48
+ ## The cross-file rules
49
+
50
+ - **`cross-icon-only-unnamed`** (WCAG 2.4.4/4.1.2, *flag*): a component that renders an
51
+ **icon-only** control and *can* receive a name (`{...props}` / `aria-label={…}` /
52
+ `{children}`) is used **without a name**. The flag is placed **at the usage site**, with the
53
+ component **definition** in `related`.
54
+ - **`cross-prop-drilled-name-lost`** (WCAG 4.1.2, *flag*): a usage passes an accessible-name
55
+ prop (`aria-label`/`label`/…) to a component that renders a control but **neither spreads
56
+ `{...props}` nor forwards a name** to it → the name is silently dropped. Flagged at the
57
+ usage, with the control **definition** in `related`.
58
+ - **`cross-aria-ref-cross-file`** (WCAG 4.1.2, *suppression*): an `aria-labelledby`/
59
+ `aria-describedby`/`aria-controls` whose target id lives in **another** file →
60
+ suppresses the false `aria-ref-missing-id`. Suppressed only when **every** in-page-missing
61
+ id resolves elsewhere, so a genuinely-dangling reference is never hidden.
62
+ - **`cross-aria-forwarding`** (WCAG 4.1.2, *suppression*): a native control that forwards
63
+ `{...props}` is nameable by its parent → it **suppresses** the false
64
+ `button-empty-name`/`icon-only-control-unnamed` on the **definition**.
65
+ - **`cross-skip-link-target`** (WCAG 2.4.1, *suppression*): an `href="#id"` anchor whose
66
+ target lives in **another** file (imported layout/component) → suppresses the false "broken
67
+ anchor". A target missing everywhere stays a true positive.
68
+ - **`cross-page-lang`** (WCAG 3.1.1, *suppression*): an `<html>` without `lang` whose imported
69
+ layout/wrapper declares the language → suppresses the false positive.
70
+ - **`cross-name-ref-cross-file`** (WCAG 1.3.1, *suppression*): a `<label for="id">` or a
71
+ heading/`aria-labelledby` whose target id is **defined in another** file → suppresses the
72
+ false `label-for-dangling` / empty-heading. Suppressed only when the id resolves elsewhere.
73
+
74
+ ## Benefit for `fix`
75
+
76
+ Because the AST indexes the **real file** (exact offsets, non-*lossy* `Doc`), `fix` can apply
77
+ its *safe* codemods on JSX/TSX (remove redundant ARIA, insert `alt`/`lang`/`title`/
78
+ `aria-label`), always behind the anti-regression gate. Codemods that **rewrite** an attribute
79
+ name stay disabled on JSX (so `tabIndex={5}` is never turned into `tabindex="0"`).
80
+
81
+ > Cross-file flags merge into the same `AuditResult`: `report`, `prd`, `check` and `verify`
82
+ > consume them unchanged.