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,292 @@
1
+ ---
2
+ name: ultra11y
3
+ description: "Use to AUDIT existing HTML/CSS/JSX against WCAG 2.2 AA accessibility and produce a dated auditor-conformance report, OR to AUTHOR/REVIEW accessible markup (native-HTML-first, ARIA last). An install-free engine (`node scripts/ultra11y.mjs`, no keys) runs 72 static checks tied to WCAG criteria โ€” alt/lang/title, unlabeled fields, empty links/buttons, tables, headings, ARIA vocabulary, label-in-name, autocomplete โ€” measured against the official W3C ACT corpus. The engine decides 3 of the 55 criteria; the AI agent adjudicates the 38 judgment ones from harvested evidence + a per-criterion decision protocol (`verify --manual`, gated, fannable via `orchestrate`), and the 14 rendering ones go to `scan` โ€” never silently conforming. Library/SFC code is audited as RENDERED captures (`render --setup`); country standards are pluggable packs (`--standard rgaa`, `scan --sample`). check/verify reject invented non-conformities. Triggers: 'audit WCAG/a11y', 'make accessible', 'fix a11y', 'audit RGAA'."
4
+ license: MIT
5
+ metadata:
6
+ version: 2.22.0
7
+ ---
8
+
9
+ # ultra11y โ€” audit WCAG 2.2 AA and write accessible markup
10
+
11
+ On accessibility, an automated tool only sees part of the problem. `ultra11y` owns
12
+ that with a **division of labour**: the deterministic, install-free engine
13
+ (`node scripts/ultra11y.mjs <command>` โ€” no `npm install`, no key; the JSX/TSX parser
14
+ is embedded in the bundle) does the *mechanical* work โ€” detect the machine-checkable
15
+ non-conformities and tie each to the right **WCAG success criterion** โ€” and **the AI agent**
16
+ (Claude running this skill) *adjudicates the judgment criteria itself* โ€” alt relevance, link
17
+ purpose in context, reading order โ€” statically, from the evidence the engine harvests
18
+ (`verify --manual`), each verdict gated by `verify`/`check`. Only the truly **rendered-DOM**
19
+ criteria (computed contrast, visible focus, zoom/reflow, content-on-hover) fall to the `scan`
20
+ tier (axe-core in a real browser); a human is at most optional oversight. Gates stop any
21
+ hallucinated non-conformity from surviving, and nothing is ever silently "conforming".
22
+
23
+ **WCAG 2.2 Level AA is the worldwide core.** Country standards โ€” France's **RGAA**, the
24
+ US **Section 508**, the EU **EN 301 549** โ€” are pluggable *standards packs* that map their
25
+ criteria onto WCAG. Add `--standard rgaa` to re-key reports/criteria; **plug an external
26
+ pack at runtime** with `--pack ./pack.json` (or a `.ultra11yrc.json`), no rebuild; or
27
+ contribute your country (see `references/standards.md`). Packs (and their concrete
28
+ **implementation guidance** โ€” the RGAA SocialGouv/etalab good/bad patterns) can be
29
+ **AI-ingested** and gated by `pack check` so a fabricated mapping never passes โ€” see
30
+ `references/packs.md` and `references/guidance.md`.
31
+
32
+ **Most of the work is yours.** Of the 55 WCAG 2.2 AA success criteria, the engine can decide
33
+ **3** outright; **14** need a rendered page (`scan`) and **38** are judgment calls the agent
34
+ adjudicates. So the engine's clean run is a starting point, never a verdict โ€” and
35
+ `references/adjudication.md` is the page you actually work from.
36
+
37
+ > **Core rules:**
38
+ > 1. **Never invent a non-conformity**: every `NC` cites a real, resolvable element (`check` verifies it).
39
+ > 2. **Native HTML first, ARIA last**; never duplicate implicit semantics.
40
+ > 3. **Residual is explicit, never silently conforming**: the AI agent *adjudicates* every
41
+ > *judgment* criterion itself (`verify --manual`, gated), and the *rendering* criteria go to
42
+ > `scan`; any criterion still unproven stays "to assess manually" โ€” no status without a
43
+ > recorded, justified verdict.
44
+ > 4. **The FINAL rendered semantic HTML must be correct.** The engine sees only source; a
45
+ > component library (DSFR/MUIโ€ฆ) or `.vue`/`.svelte`/`.astro` SFC hides the real markup, so
46
+ > a green source audit is not proof. Verify the produced semantic HTML โ€” install the
47
+ > zero-touch **capture** harvester (`render --setup`) so every component your tests render
48
+ > is serialized to `.ultra11y/captures` and audited, with `audit --require-captures` gating
49
+ > the blind spots. See `references/automation.md` / `rendered.md`.
50
+ > 5. **Language**: ALWAYS pass `--lang` matching the language of your conversation with
51
+ > the user; ask the user when ambiguous. Without the flag the CLI auto-detects (repo
52
+ > `<html lang>` โ†’ the active standard's default locale โ†’ English) โ€” a scripted/CI
53
+ > fallback, not a substitute for passing `--lang` yourself.
54
+ > 6. **Technical tokens stay in English, even in French prose.** In any French deliverable
55
+ > you write (report commentary, PRD, GitHub issues, judgment verdicts), attribute/
56
+ > element/role names and their values are code, not prose โ€” never translate them:
57
+ > `aria-live` stays `aria-live` (never ยซ rรฉgion live ยป), same for `tabindex`, `alt`,
58
+ > `role="alert"`, landmark role names. The engine's own fr catalog follows this; match
59
+ > it. Normative standard vocabulary (RGAA wording such as ยซ lien d'รฉvitement ยป) keeps
60
+ > its official French.
61
+
62
+ ## The reference library
63
+
64
+ Domain knowledge first, then the tooling. Read the one that matches the question in front of you.
65
+
66
+ | Accessibility knowledge | |
67
+ |---|---|
68
+ | `references/adjudication.md` | How to decide each of the 52 criteria the engine hands you โ€” the decision rule, when NA is legitimate, the questions |
69
+ | `references/naming.md` | Accessible-name computation, 2.5.3 label-in-name, visually-hidden CSS, naming images/SVG/figures |
70
+ | `references/widgets.md` | ARIA APG composite patterns, keyboard contracts, focus management, SPA route changes, live regions |
71
+ | `references/forms-and-errors.md` | Labels and instructions, the `autocomplete` token list, error identification/suggestion/prevention, redundant entry, accessible authentication |
72
+ | `references/structure.md` | Headings, landmarks, lists and `dl`, simple and complex tables, meaningful sequence |
73
+ | `references/media-and-motion.md` | Captions/audio description/transcripts, pointer gestures, target size, orientation, reflow, reduced motion |
74
+ | `references/forbidden-patterns.md` | 15 anti-patterns: bad โ†’ why โ†’ native fix โ†’ criterion |
75
+ | `references/focus-and-logic.md` | The focus/keyboard criteria you own, and how to reason about them |
76
+ | `references/criteria.md` | The 55 criteria: level, automatability class, engine rules, pack mapping (generated) |
77
+ | `references/act.md` | What the engine is measured to catch โ€” and what it demonstrably does not (generated) |
78
+
79
+ | Running the tool | |
80
+ |---|---|
81
+ | `references/audit.md` | The audit loop end to end, and the normative page sample |
82
+ | `references/judgment.md` | The adjudication + refutation worklists, verdict vocabulary, fail-closed fold |
83
+ | `references/verify.md` | The `check`/`verify` gates |
84
+ | `references/false-positives.md` | Where a finding may still be wrong, and how to refute it |
85
+ | `references/rendered.md` ยท `references/automation.md` | Auditing produced HTML, captures, hooks and CI |
86
+ | `references/dynamic.md` | The `scan` tier: runtimes, probes, authenticated pages |
87
+ | `references/scale.md` | Focusing an audit on a large repository |
88
+ | `references/fix.md` ยท `references/correction.md` | Applying fixes, by priority, without regressions |
89
+ | `references/prd.md` | The auditor block as a backlog, and GitHub issues |
90
+ | `references/standards.md` ยท `references/packs.md` ยท `references/guidance.md` | Country standards, authoring a pack, implementation guidance |
91
+ | `references/methodology.md` | Statuses, pass rate, severities, report format |
92
+ | `references/cross-file.md` | `--graph`: imports and cross-file rules |
93
+ | `references/orchestration.md` | Fanning the judgment phases out across subagents |
94
+
95
+ ## Choose by situation
96
+
97
+ - **"Audit / compliance report"** โ†’ `node scripts/ultra11y.mjs audit โ€ฆ --json`, then
98
+ `report` (synthesis table + one **auditor conformance block** per NC criterion โ€” same
99
+ block `prd`/`--gh-issues` use), then `check`; read **`references/audit.md`**.
100
+ - **"Code rendered by a library (DSFR, MUIโ€ฆ) or a `.vue`/`.svelte`/`.astro` SFC / avoid
101
+ false negatives"** โ†’ audit the **produced HTML**, not the source template. Easiest:
102
+ install the zero-touch **capture** harvester (`render --setup`) so your tests serialize
103
+ every rendered component to `.ultra11y/captures` (auto-ingested, findings attributed to the
104
+ source component; `render --coverage` and `audit --require-captures` track blind spots). Or
105
+ `render` (buildโ†’audit recipe or SSR snapshot `--scaffold`) then `audit` on the output, and
106
+ `scan` for computed rendering. SFC-source findings are flagged `preliminary` (a
107
+ `scope.sourceTemplate` caveat); read **`references/rendered.md`**.
108
+ - **"A finding looks wrong / false positive on a component"** โ†’ the engine auto-suppresses
109
+ most component false positives (slot/prop-injected names, component children, dynamic
110
+ bindings, conditional headings) and marks SFC/library-source findings `preliminary`;
111
+ confirm or refute the rest with `verify --apply`; read **`references/false-positives.md`**.
112
+ - **"Large repo / audit smartly"** โ†’ focus: `--changed` (git diff), template
113
+ prioritization, dedup, `--max-files`; read **`references/scale.md`**.
114
+ - **"Cross-file analysis (tree + dependencies), JSX/TSX as a real AST"** โ†’
115
+ `audit --graph` resolves imports and applies cross-file rules (an icon-only component
116
+ used without a name, an anchor target in another fileโ€ฆ), no browser; read
117
+ **`references/cross-file.md`**.
118
+ - **"Generate the fix markdown / PRDs (โ†’ GitHub issues)"** โ†’ `prd` (the SAME auditor
119
+ conformance block `report`'s NC section renders โ€” theme/criterion/test/WCAG+level/
120
+ finding/expected/verification in the active standard's vocabulary โ€” as a backlog);
121
+ `--split criterion`, `--format doc` for a product-requirements doc, `--format remediation`
122
+ for the legacy dev backlog, `--gh-issues` for one issue per criterion (that block as the
123
+ body) or `--gh-single` for a single consolidated issue via the `gh` CLI); read
124
+ **`references/prd.md`**.
125
+ - **"Plug or author a standards pack (RGAA & beyond), AI-ingest external rules"** โ†’
126
+ `--pack`/`.ultra11yrc.json` to load at runtime, `pack check` to gate it (the
127
+ anti-hallucination guardrail), `pack scaffold` to start one; concrete before/after
128
+ implementation guidance attaches to findings/PRD; read **`references/packs.md`** and
129
+ **`references/guidance.md`**.
130
+ - **"Adjudicate the judgment criteria (judgment phase)"** โ†’ `verify --manual --in audit.json`
131
+ emits an ADJUDICATION worklist (`ADJUDICATE.todo.json` + `ADJUDICATE.md`), one item per
132
+ residual criterion, pre-loaded with the engine's harvested evidence (every alt, link text +
133
+ context, literal colour pairs, control labels, heading outline, ARIA state, tabindex,
134
+ lang-of-parts); the AI agent fills each `verdict` โ€” `C`/`NA` (with a `justification`), `NC`
135
+ (with a groundable finding), or `manual` (with a `reason`) โ€” then `verify --apply โ€ฆ --in
136
+ audit.json` folds them back FAIL-CLOSED; read **`references/judgment.md`**.
137
+ - **"Many items to adjudicate/verify (fan the judgment out to subagents)"** โ†’
138
+ `orchestrate --run <dir>` emits, from the run's CURRENT worklists, one launchable
139
+ multi-agent workflow per ready phase + the `agents/<role>.md` dispatch contracts +
140
+ a sequential `RUNBOOK.md` โ€” the default execution path on a subagent-capable harness;
141
+ see **Orchestration โ€” route by harness** below.
142
+ - **"Focus, keyboard & interaction logic (the interaction-logic part)"** โ†’ the engine marks
143
+ focus order/visible/trap and on-focus/on-input criteria as residual risks; the AI agent reads
144
+ the full component source and adjudicates the keyboard/focus behaviour (visible-focus and the
145
+ other rendered criteria go to `scan`); read **`references/focus-and-logic.md`**.
146
+ - **"Put the fixes in place"** โ†’ `fix` (dry-run by default, `--write` applies the safe
147
+ codemods, proposes the rest without inventing anything); read **`references/fix.md`**.
148
+ - **"Fix by priority, no regressions (correction phase)"** โ†’ `fix` (`--write`,
149
+ `--iterate`) + the `prd` backlog, blockingโ†’majorโ†’minor; read **`references/correction.md`**.
150
+ - **"Automatic repo gate (hook / CI)"** โ†’ `init --hook` writes a git pre-commit gate over
151
+ the **strict staged snapshot** (audits the exact index blobs, auto-applies safe fixes and
152
+ re-stages them, blocks only on judgment issues); `init --baseline`/`--ci` is the opt-in
153
+ "block only NEW non-conformities" variant. For library/SFC code, commit rendered
154
+ **captures** (`render --setup`) and stage them so the real semantic HTML is what's
155
+ checked (`audit --require-captures`); read **`references/automation.md`**.
156
+ - **"Make this code accessible / review it"** โ†’ audit the snippet
157
+ (`audit - < component.html`) native-first; read **`references/authoring.md`** and
158
+ **`references/forbidden-patterns.md`**.
159
+ - **"What does criterion X mean"** โ†’ `criteria` (e.g. `criteria 1.4.3`, or
160
+ `criteria --standard rgaa 8.3`); see **`references/criteria.md`**.
161
+ - **"How trustworthy is a given check / what does the engine NOT catch"** โ†’ the engine is
162
+ scored against the **W3C ACT-Rules test corpus** (~1 100 third-party examples): which
163
+ checks are consistent, where recall is partial, which deviations are deliberate, and
164
+ which statically-decidable rules are still missing โ€” read **`references/act.md`**. Use it
165
+ to calibrate how much of a criterion the engine really settles before you adjudicate.
166
+ - **"Country standard (RGAA, Section 508, EN 301 549)"** โ†’ `--standard <pack>` on
167
+ `report`/`prd`/`criteria`/`check`/`verify`; see **`references/standards.md`** and
168
+ **`references/methodology.md`**. **For a country-standard audit, PROPOSE the scan by
169
+ default**: such an audit is normatively defined over a declared page **sample**, and an
170
+ un-scanned `--standard rgaa` report is marked **partial** โ€” say so rather than implying
171
+ full coverage. The sample workflow (`sample check`, `scan --sample`, `--merge`) is in
172
+ **`references/audit.md`**.
173
+ - **"High-assurance audit"** โ†’ `verify --report โ€ฆ --semantic`; see **`references/verify.md`**.
174
+ - **"Check contrast / rendering (dynamic tier)"** โ†’ `scan <url> --merge โ€ฆ` runs axe-core in a
175
+ headless browser and, on the local runtime, probes focus visibility, 200% zoom, text
176
+ spacing and content-on-hover, with bounded stateful interactions and authenticated pages;
177
+ read **`references/dynamic.md`**.
178
+
179
+ ## Orchestration โ€” route by harness
180
+
181
+ The judgment phases fan out: `ADJUDICATE.todo.json` (one item per residual criterion) and
182
+ `VERIFY.todo.json` (one entry per NC claim) are independent per-item worklists, and
183
+ `orchestrate --run <dir>` emits a launchable workflow, per-role dispatch contracts and a
184
+ sequential RUNBOOK from whichever worklists currently exist. Fan-out is an optimization,
185
+ never a requirement: the gates are harness-independent, every phase has a sequential
186
+ fallback with identical artifacts, and the fail-closed `verify --apply` fold always stays
187
+ with you. Read **`references/orchestration.md`** for the routing table and the rules.
188
+
189
+ ## Command cheat sheet
190
+
191
+ The full surface is in `--help`; these are the lines you reach for.
192
+
193
+ ```
194
+ node scripts/ultra11y.mjs audit "src/**/*.tsx" --graph --out audits --json # the default audit (AST + cross-file, persisted)
195
+ node scripts/ultra11y.mjs audit - < component.html # a snippet via stdin
196
+ node scripts/ultra11y.mjs audit --changed --json # only the git diff (large repo)
197
+ node scripts/ultra11y.mjs audit --staged --fail-on blocking # gate EXACTLY the staged snapshot (pre-commit)
198
+ node scripts/ultra11y.mjs audit "dist/**/*.html" # the RENDERED HTML (reliable for DSFR/MUIโ€ฆ)
199
+ node scripts/ultra11y.mjs render --setup # install the capture harvester (tests โ†’ .ultra11y/captures)
200
+ node scripts/ultra11y.mjs audit --require-captures # gate the components with no rendered capture
201
+ node scripts/ultra11y.mjs scan http://localhost:3000 --runtime local --cwd . --merge audits/audit-latest.json
202
+ node scripts/ultra11y.mjs verify --report audits/wcag-YYYY-MM-DD.md --in audits/audit-latest.json --manual --out audits
203
+ node scripts/ultra11y.mjs verify --apply audits/ADJUDICATE.todo.json --in audits/audit-latest.json --out audits
204
+ node scripts/ultra11y.mjs orchestrate --run audits # fan the judgment phases out (--eco for the sequential path)
205
+ node scripts/ultra11y.mjs report --in audits/audit-latest.json --out audits # โ†’ audits/wcag-YYYY-MM-DD.md
206
+ node scripts/ultra11y.mjs prd --in audits/audit-latest.json --gh-issues # the same auditor block as a backlog
207
+ node scripts/ultra11y.mjs check --report audits/wcag-YYYY-MM-DD.md # integrity gate
208
+ node scripts/ultra11y.mjs criteria 1.4.3 # one success criterion (--list for all)
209
+ node scripts/ultra11y.mjs fix "src/**/*.html" --write --iterate # apply the safe codemods to a fixpoint
210
+ node scripts/ultra11y.mjs init --hook # pre-commit gate (--baseline for the regression variant)
211
+ node scripts/ultra11y.mjs pack check ./packs/section508.json # gate an (AI-)authored standards pack
212
+ ```
213
+ Add `--standard rgaa` to `report`/`prd`/`criteria`/`check`/`verify` for a country standard,
214
+ `--pack ./pack.json` to load one at runtime, and `--json` anywhere for machine output.
215
+ `--lang` follows the conversation (pass it explicitly โ€” Core rule 5).
216
+
217
+ ## The loop: audit โ†’ render โ†’ judge โ†’ fix โ†’ re-audit
218
+
219
+ To converge on conformance (not a single pass), chain the steps, letting the agent
220
+ drive the judgment and content stages:
221
+
222
+ 1. **Audit** the source (`audit โ€ฆ --graph`) for a first map; on library-rendered code,
223
+ **audit the render** (`render` โ†’ build/SSR โ†’ `audit`) for reliable verdicts (otherwise
224
+ the scope-risk note reminds you).
225
+ 2. **Adjudicate & refute** with `verify`, two worklists. (a) `verify --manual --in audit.json`
226
+ emits `ADJUDICATE.todo.json` โ€” one item per residual *judgment* criterion, pre-loaded with the
227
+ engine's harvested evidence โ€” which the AI agent rules on (`C`/`NC`/`NA`, or `manual` with a
228
+ `reason` when it truly `needs-rendered-dom`), each verdict carrying a `justification` or a
229
+ groundable finding; `verify --apply โ€ฆ --in audit.json` folds them back FAIL-CLOSED (agent NCs
230
+ become real `agent:<sc>` findings that re-render in ยง2). (b) `verify --report โ€ฆ [--semantic]`
231
+ builds `VERIFY.todo.json` to **refute any `preliminary`/SFC/library-source finding** the
232
+ rendered DOM disproves (verdicts `supported`/`partial`/`refuted`/`unsupported`);
233
+ `verify --apply` drops the refuted/unsupported ones (the anti-hallucination gate). This
234
+ includes **focus & interaction logic** (read the full component source: keyboard
235
+ operability, focus order/visibility, traps, on-focus/on-input changes; see
236
+ `references/focus-and-logic.md`) and the per-rule traps in `references/false-positives.md`.
237
+ Both worklists fan out (`orchestrate --run <dir> --phase adjudicate|verify-report` โ€”
238
+ see **Orchestration โ€” route by harness**); the `--apply` fold always stays with you.
239
+ 3. **Fix** by priority: `fix --write --iterate` for the mechanical part (anti-regression
240
+ gate), then hand-apply the judgment/content fixes (alt, labels, structure) guided by
241
+ `references/correction.md`.
242
+ 4. **Re-audit** (on the render where relevant) and repeat.
243
+ 5. **Deliver the auditor block.** `report` (compliance doc: synthesis + one auditor
244
+ conformance block โ€” theme/criterion/test/WCAG+level/finding/expected/verification โ€”
245
+ per NC criterion) and `prd` (the same blocks as an actionable backlog, `--gh-issues`
246
+ filing one GitHub issue per criterion with that identical block) are two views of the
247
+ ONE building block, in the language of this conversation (pass `--lang` explicitly โ€”
248
+ Core rule 5).
249
+
250
+ **Stop** when `check` and `verify --apply` are green again and only explicitly-named
251
+ residual risks remain. (To automate the outer cadence, the harness `/loop` command can
252
+ re-run this cycle.)
253
+
254
+ ## Combining engine, judgment and residual risk
255
+
256
+ `audit` classes each criterion `C`/`NC`/`NA` for the static subset and `manual` for the rest,
257
+ each carrying an `automatability` class that says WHY it is manual and how to close it:
258
+ **`static`** (the engine decides), **`needs-rendering`** (`scan`), **`judgment`** (you, from
259
+ the harvested evidence and the decision protocol). Engine `NC`s are confirmed candidates
260
+ with a cited `file:line`; a `preliminary: true` finding (SFC/library source) is provisional โ€”
261
+ confirm it against the render or refute it. A criterion is never silently marked conforming:
262
+ the report is complete only when every applicable criterion is a justified `C`/`NC`/`NA` and
263
+ every residual risk is named. See **`references/methodology.md`**.
264
+
265
+ **Advisory recommendations are a distinct class.** A good-practice signal with NO failing
266
+ normative test renders under a dedicated section and can never flip a criterion to `NC` nor
267
+ enter `conformancePct`. An `NC` needs a `normativeRef`; a recommendation does not. Do not
268
+ promote one to the other โ€” see **`references/false-positives.md`**.
269
+
270
+ ## Do not
271
+
272
+ - Invent a non-conformity the engine did not find and you cannot see (contrast on
273
+ **inline literal colours** is decided statically; **computed** contrast โ€” external CSS,
274
+ variables โ€” goes through `scan` (Docker tier) or is verified at render before being declared).
275
+ - Add ARIA that duplicates native semantics.
276
+ - Mark a rendering/judgment criterion "conforming" without a recorded, gated justification
277
+ (agent adjudication via `verify --manual`, or `scan` evidence).
278
+ - Hand-edit `references/criteria.md` (generated from the WCAG dataset via `criteria --generate`).
279
+
280
+ ## Scope
281
+
282
+ Static engine: offline, deterministic, install-free; inputs are HTML + JSX/TSX (real AST,
283
+ cross-file analysis via `--graph`) + stdin. The **rendering** criteria (computed contrast,
284
+ reflow) are covered by the optional `scan` tier (axe-core, Docker **or** `--runtime local`).
285
+ The local runtime additionally **probes** focus visibility (2.4.7), 200% text zoom (1.4.4),
286
+ text spacing (1.4.12) and content-on-hover (1.4.13) โ€” observed in the rendered page, raised
287
+ as NC only when the failure is seen (a clean probe leaves the SC `manual`, never silently
288
+ conforming); reading order and alt relevance are the AI agent's judgment, adjudicated from the
289
+ harvested evidence and gated (`verify --manual`).
290
+ Data: WCAG 2.2 ยฉ
291
+ W3C (W3C Document License); the RGAA pack is RGAA 4.1.2 ยฉ DINUM, Licence Ouverte / Etalab
292
+ 2.0 (see `NOTICE`).
@@ -0,0 +1,163 @@
1
+ <!-- GENERATED from the vendored W3C ACT corpus by `pnpm run build:act` โ€” do not edit by hand. -->
2
+
3
+ # ACT conformance โ€” how this engine scores against a corpus it did not write
4
+
5
+ The recall matrix in this repository proves that each rule fires on the defect written FOR it.
6
+ That is circular. The [W3C ACT-Rules Community Group](https://act-rules.github.io) publishes rules
7
+ with `passed` / `failed` / `inapplicable` examples authored independently of this project, so running
8
+ the engine over them measures something a self-authored fixture cannot.
9
+
10
+ **How to read this.** An ACT example asserts something about ITS OWN rule and nothing else, so each
11
+ case is judged only with the engine rules mapped to that ACT rule โ€” never with the whole engine.
12
+ Only normative findings count (an `advisory` recommendation never flips a criterion, so it cannot
13
+ fail a case either). Rules needing a rendered page or a human call are declared, not scored โ€” saying
14
+ so beats quietly scoring zero.
15
+
16
+ ## Summary
17
+
18
+ | | |
19
+ |---|---|
20
+ | Corpus | 1134 examples across 91 ACT rules |
21
+ | Source | https://act-rules.github.io/testcases.json |
22
+ | Rules scored | **32** |
23
+ | Failed examples caught | **103 / 149** |
24
+ | Clean examples left alone | **291 / 291** โ€” no unexplained false positive |
25
+ | Consistent | 5 |
26
+ | Divergent (documented, 9 cases) | 3 |
27
+ | Partially consistent | 24 |
28
+ | **Inconsistent** | **0** |
29
+ | Declared gaps (static, not implemented) | 13 |
30
+
31
+ *Partially consistent* is the ACT Rules Format's own term for a tool that never reports a false
32
+ positive but does not catch everything โ€” it is a coverage statement, not an error. *Divergent* means
33
+ every deviation on that rule is listed below with the argument for it.
34
+
35
+ ## Scored rules
36
+
37
+ | ACT rule | WCAG | Engine rules | Verdict | Caught |
38
+ |---|---|---|---|---|
39
+ | [Image has non-empty accessible name](https://act-rules.github.io/rules/23a2a8) | 1.1.1 | `img-alt-missing` | ๐ŸŸจ partially consistent | 4/5 |
40
+ | [HTML page has non-empty title](https://act-rules.github.io/rules/2779a5) | 2.4.2 | `title-missing-empty` | ๐ŸŸจ partially consistent | 4/5 |
41
+ | [Visible label is part of accessible name](https://act-rules.github.io/rules/2ee8b8) | 2.5.3 | `label-in-name-mismatch` | ๐ŸŸฆ divergent (documented) | 3/3 |
42
+ | [Id attribute value is unique](https://act-rules.github.io/rules/3ea0c8) | 4.1.1 | `duplicate-id` | โœ… consistent | 3/3 |
43
+ | [Audio or video element that plays automatically has a control mechanism](https://act-rules.github.io/rules/4c31df) | โ€” | `autoplay-media` | ๐ŸŸฆ divergent (documented) | 5/5 |
44
+ | [Element with role attribute has required states and properties](https://act-rules.github.io/rules/4e8ab6) | 4.1.2 | `aria-required-attr` | ๐ŸŸจ partially consistent | 1/2 |
45
+ | [Image button has non-empty accessible name](https://act-rules.github.io/rules/59796f) | 1.1.1, 4.1.2 | `input-image-alt-missing` | ๐ŸŸจ partially consistent | 2/3 |
46
+ | [ARIA state or property is permitted](https://act-rules.github.io/rules/5c01ea) | โ€” | `aria-prohibited-attr` | ๐ŸŸจ partially consistent | 0/2 |
47
+ | [ARIA attribute is defined in WAI-ARIA](https://act-rules.github.io/rules/5f99a7) | โ€” | `invalid-aria-attr` | โœ… consistent | 2/2 |
48
+ | [Role attribute has valid value](https://act-rules.github.io/rules/674b10) | 1.3.1 | `invalid-aria-role` | โœ… consistent | 2/2 |
49
+ | [ARIA state or property has valid value](https://act-rules.github.io/rules/6a7281) | โ€” | `invalid-aria-value`, `live-region-conflict` | ๐ŸŸจ partially consistent | 9/10 |
50
+ | [Element with aria-hidden has no content in sequential focus navigation](https://act-rules.github.io/rules/6cfa84) | 4.1.2 | `aria-hidden-focusable` | ๐ŸŸจ partially consistent | 5/6 |
51
+ | [Autocomplete attribute has valid value](https://act-rules.github.io/rules/73f2c2) | 1.3.5 | `autocomplete-token-invalid` | โœ… consistent | 5/5 |
52
+ | [SVG element with explicit role has non-empty accessible name](https://act-rules.github.io/rules/7d6734) | 1.1.1 | `img-alt-missing` | ๐ŸŸจ partially consistent | 2/4 |
53
+ | [Audio or video element avoids automatically playing audio](https://act-rules.github.io/rules/80f0bf) | 1.4.2 | `autoplay-media` | ๐ŸŸฆ divergent (documented) | 2/2 |
54
+ | [Object element rendering non-text content has non-empty accessible name](https://act-rules.github.io/rules/8fc3b6) | 1.1.1 | `object-embed-no-name` | ๐ŸŸจ partially consistent | 2/4 |
55
+ | [Button has non-empty accessible name](https://act-rules.github.io/rules/97a4e1) | 4.1.2 | `button-empty-name`, `icon-only-control-unnamed` | ๐ŸŸจ partially consistent | 4/5 |
56
+ | [Headers attribute specified on a cell refers to cells in the same table element](https://act-rules.github.io/rules/a25f45) | 1.3.1 | `headers-attr-dangling` | โœ… consistent | 4/4 |
57
+ | [Meta viewport allows for zoom](https://act-rules.github.io/rules/b4f0c3) | 1.4.4 | `meta-viewport-zoom-block` | ๐ŸŸจ partially consistent | 3/4 |
58
+ | [HTML page has lang attribute](https://act-rules.github.io/rules/b5c3f8) | 3.1.1 | `html-lang-missing` | ๐ŸŸจ partially consistent | 3/4 |
59
+ | [ARIA required owned elements](https://act-rules.github.io/rules/bc4a75) | 1.3.1 | `aria-required-children` | ๐ŸŸจ partially consistent | 3/7 |
60
+ | [Meta element has no refresh delay](https://act-rules.github.io/rules/bc659a) | 2.2.1, 2.2.4, 3.2.5 | `meta-refresh-redirect` | ๐ŸŸจ partially consistent | 3/4 |
61
+ | [HTML page lang attribute has valid language tag](https://act-rules.github.io/rules/bf051a) | 3.1.1 | `lang-invalid` | ๐ŸŸจ partially consistent | 1/4 |
62
+ | [Meta element has no refresh delay (no exception)](https://act-rules.github.io/rules/bisz58) | 2.2.4, 3.2.5 | `meta-refresh-redirect` | ๐ŸŸจ partially consistent | 2/4 |
63
+ | [Link has non-empty accessible name](https://act-rules.github.io/rules/c487ae) | 2.4.4, 2.4.9, 4.1.2 | `link-empty-name`, `icon-only-control-unnamed` | ๐ŸŸจ partially consistent | 7/11 |
64
+ | [Iframe element has non-empty accessible name](https://act-rules.github.io/rules/cae760) | 4.1.2 | `iframe-title-missing` | ๐ŸŸจ partially consistent | 3/4 |
65
+ | [Table header cell has assigned cells](https://act-rules.github.io/rules/d0f69e) | 1.3.1 | `th-no-data-cells` | ๐ŸŸจ partially consistent | 0/3 |
66
+ | [Element with lang attribute has valid language tag](https://act-rules.github.io/rules/de46e4) | 3.1.2 | `lang-invalid`, `inline-lang-change-missing` | ๐ŸŸจ partially consistent | 6/9 |
67
+ | [Form field has non-empty accessible name](https://act-rules.github.io/rules/e086e5) | 4.1.2 | `control-label-missing` | ๐ŸŸจ partially consistent | 4/7 |
68
+ | [Video element auditory content has captions](https://act-rules.github.io/rules/f51b46) | โ€” | `media-no-track` | ๐ŸŸจ partially consistent | 0/4 |
69
+ | [ARIA required context role](https://act-rules.github.io/rules/ff89c9) | 1.3.1 | `aria-required-parent` | ๐ŸŸจ partially consistent | 2/4 |
70
+ | [Heading has non-empty accessible name](https://act-rules.github.io/rules/ffd0e9) | 1.3.1 | `empty-heading` | ๐ŸŸจ partially consistent | 7/8 |
71
+
72
+ ### Notes and documented divergences
73
+
74
+ - **Visible label is part of accessible name** ยท reports on *Passed Example 6*: an icon FONT renders the text 'search' as a glyph, so the visible label is not the text in the DOM โ€” a fact only the rendered page carries
75
+ - **Audio or video element that plays automatically has a control mechanism** ยท reports on *Inapplicable Example 2*: the source cannot tell that the referenced media has no audio track
76
+ - **Audio or video element that plays automatically has a control mechanism** ยท reports on *Passed Example 3*: the pause mechanism is a custom button wired in JavaScript, not a native `controls` attribute
77
+ - **ARIA state or property is permitted** โ€” covers the name-prohibited roles; the full per-role permitted-attribute matrix is not encoded
78
+ - **Element with aria-hidden has no content in sequential focus navigation** ยท reports on *Passed Example 4*: a focus-sentinel link inside an aria-hidden wrapper: it IS reachable by Tab, and only a runtime focus handler bounces it back โ€” the same call axe-core makes
79
+ - **SVG element with explicit role has non-empty accessible name** โ€” role=img on <svg> is handled by the same rule
80
+ - **Audio or video element avoids automatically playing audio** ยท reports on *Inapplicable Example 2*: the source cannot tell that the referenced media file is silent
81
+ - **Audio or video element avoids automatically playing audio** ยท reports on *Passed Example 3*: the pause mechanism is a custom button wired in JavaScript, not a native `controls` attribute
82
+ - **Audio or video element avoids automatically playing audio** ยท reports on *Passed Example 2*: a #t= media fragment shortening playback below 3s is not resolvable from source
83
+ - **Meta element has no refresh delay (no exception)** โ€” this variant drops the >20h exception the engine honours, so long delays are knowingly not reported
84
+ - **Iframe element has non-empty accessible name** ยท reports on *Inapplicable Example 3*: a tabindex=-1 iframe is still exposed to assistive tech and still needs a title (RGAA 2.1 requires one unconditionally)
85
+ - **Table header cell has assigned cells** โ€” only the explicit `headers`-wired shape is decided; scope-based assignment needs a full table model
86
+ - **Element with lang attribute has valid language tag** ยท reports on *Inapplicable Example 2*: lang="" on an element carrying text declares no language for that text; ACT deems the rule inapplicable, this engine reports it under 3.1.2
87
+ - **Form field has non-empty accessible name** โ€” `placeholder-as-label` is deliberately NOT mapped: a placeholder does contribute to the accessible name, so ACT passes it โ€” ultra11y still reports it, as a stricter house rule under 3.3.2.
88
+ - **Video element auditory content has captions** โ€” presence of a caption track; whether the captions are accurate is a judgment call
89
+
90
+ ## Declared gaps โ€” statically decidable, not implemented yet
91
+
92
+ | ACT rule | WCAG | Why it is not covered |
93
+ |---|---|---|
94
+ | [Document has heading for non-repeated content](https://act-rules.github.io/rules/047fe0) | โ€” | a heading for the non-repeated content (h1-missing is advisory, so it does not count) |
95
+ | [Letter spacing in style attributes is not !important](https://act-rules.github.io/rules/24afc2) | 1.4.12 | letter-spacing:!important in a style attribute |
96
+ | [Element with presentational children has no focusable content](https://act-rules.github.io/rules/307n5z) | 4.1.2 | presentational-children conflict |
97
+ | [Element marked as decorative is not exposed](https://act-rules.github.io/rules/46ca7f) | โ€” | an element marked decorative that is still exposed (focusable, or with a name) |
98
+ | [HTML page lang and xml:lang attributes have matching values](https://act-rules.github.io/rules/5b7ae0) | 3.1.1 | lang vs xml:lang mismatch |
99
+ | [Line height in style attributes is not !important](https://act-rules.github.io/rules/78fd32) | 1.4.12 | line-height:!important in a style attribute |
100
+ | [Word spacing in style attributes is not !important](https://act-rules.github.io/rules/9e45ec) | 1.4.12 | word-spacing:!important in a style attribute |
101
+ | [Document has a landmark with non-repeated content](https://act-rules.github.io/rules/b40fd1) | โ€” | ACT asks that the non-repeated content sit in SOME landmark โ€” a weaker claim than `missing-main-landmark`, which requires a <main>, so the two are not equivalent |
102
+ | [Bypass Blocks of Repeated Content](https://act-rules.github.io/rules/cf77f2) | 2.4.1 | presence of a bypass mechanism (landmark or skip link) |
103
+ | [Attribute is not duplicated](https://act-rules.github.io/rules/e6952f) | 4.1.1 | duplicate attributes are collapsed by the parser; needs a raw-source scan |
104
+ | [No keyboard shortcut uses only printable characters](https://act-rules.github.io/rules/ffbc54) | 2.1.4 | 2.1.4 single-character keyboard shortcuts |
105
+ | [Menuitem has non-empty accessible name](https://act-rules.github.io/rules/m6b1q3) | 4.1.2 | role-specific accessible name (menuitem) |
106
+ | [Document has an instrument to move focus to non-repeated content](https://act-rules.github.io/rules/ye5d6e) | โ€” | presence of a bypass instrument |
107
+
108
+ ## Out of the static engine's reach โ€” routed, not ignored
109
+
110
+ | ACT rule | WCAG | Decided by |
111
+ |---|---|---|
112
+ | [Text has enhanced contrast](https://act-rules.github.io/rules/09o5cg) | 1.4.6 | rendered (`scan`) โ€” AAA enhanced contrast โ€” outside the AA core |
113
+ | [Scrollable element is keyboard accessible](https://act-rules.github.io/rules/0ssw9k) | 2.1.1, 2.1.3 | rendered (`scan`) |
114
+ | [HTML graphics contain no text](https://act-rules.github.io/rules/0va7u6) | 1.4.5, 1.4.9 | rendered (`scan`) โ€” text baked into a graphic |
115
+ | [Audio and visuals of video element have transcript](https://act-rules.github.io/rules/1a02b0) | 1.2.8 | judgment (agent) |
116
+ | [Video element visual content has audio description](https://act-rules.github.io/rules/1ea59c) | โ€” | judgment (agent) |
117
+ | [Video element visual content has strict accessible alternative](https://act-rules.github.io/rules/1ec09b) | 1.2.5 | judgment (agent) |
118
+ | [Audio element content has transcript](https://act-rules.github.io/rules/2eb176) | โ€” | judgment (agent) |
119
+ | [Error message describes invalid form field value](https://act-rules.github.io/rules/36b590) | 3.3.1 | judgment (agent) |
120
+ | [Block of repeated content is collapsible](https://act-rules.github.io/rules/3e12e1) | โ€” | rendered (`scan`) |
121
+ | [Iframe elements with identical accessible names have equivalent purpose](https://act-rules.github.io/rules/4b1c6c) | 4.1.2 | judgment (agent) |
122
+ | [Zoomed text node is not clipped with CSS overflow](https://act-rules.github.io/rules/59br37) | 1.4.4 | rendered (`scan`) โ€” clipping under zoom โ€” probed by scan --runtime local |
123
+ | [Link in context is descriptive](https://act-rules.github.io/rules/5effbb) | 2.4.4, 2.4.9 | judgment (agent) โ€” link purpose in context (2.4.4) |
124
+ | [Device motion based changes to the content can also be created from the user interface](https://act-rules.github.io/rules/7677a9) | 2.5.4 | judgment (agent) |
125
+ | [Focusable element has no keyboard trap](https://act-rules.github.io/rules/80af7b) | 2.1.2 | rendered (`scan`) |
126
+ | [Content has alternative for visual reference](https://act-rules.github.io/rules/9bd38c) | 1.3.3 | judgment (agent) โ€” sensory characteristics (1.3.3) |
127
+ | [DEPRECATED โ€” Image filename is accessible name for image](https://act-rules.github.io/rules/9eb3f6) | 1.1.1 | withdrawn upstream |
128
+ | [Focusable element has no keyboard trap via standard navigation](https://act-rules.github.io/rules/a1b64e) | โ€” | rendered (`scan`) |
129
+ | [Audio or video element that plays automatically has no audio that lasts more than 3 seconds](https://act-rules.github.io/rules/aaa1bf) | โ€” | rendered (`scan`) โ€” needs the media's duration |
130
+ | [Video element content is media alternative for text](https://act-rules.github.io/rules/ab4d13) | โ€” | judgment (agent) |
131
+ | [DEPRECATED โ€” Video element visual-only content has description track](https://act-rules.github.io/rules/ac7dc6) | โ€” | withdrawn upstream |
132
+ | [Audio element content is media alternative for text](https://act-rules.github.io/rules/afb423) | โ€” | judgment (agent) |
133
+ | [Text has minimum contrast](https://act-rules.github.io/rules/afw4f7) | 1.4.3, 1.4.6 | rendered (`scan`) โ€” computed contrast; only inline literal colour pairs are decided statically (contrast-literal) |
134
+ | [Link is descriptive](https://act-rules.github.io/rules/aizyf1) | 2.4.9 | judgment (agent) |
135
+ | [Iframe with negative tabindex has no interactive elements](https://act-rules.github.io/rules/akn7bn) | 2.1.1 | rendered (`scan`) โ€” needs the iframe's own document |
136
+ | [Links with identical accessible names have equivalent purpose](https://act-rules.github.io/rules/b20e66) | 2.4.9 | judgment (agent) |
137
+ | [Orientation of the page is not restricted using CSS transform property](https://act-rules.github.io/rules/b33eff) | 1.3.4 | rendered (`scan`) โ€” orientation lock via CSS transform |
138
+ | [Heading is descriptive](https://act-rules.github.io/rules/b49b2e) | 2.4.6 | judgment (agent) โ€” is the heading descriptive (2.4.6) |
139
+ | [Device motion based changes to the content can be disabled](https://act-rules.github.io/rules/c249d5) | 2.5.4 | judgment (agent) |
140
+ | [Video element visual-only content has accessible alternative](https://act-rules.github.io/rules/c3232f) | 1.2.1 | judgment (agent) |
141
+ | [HTML page title is descriptive](https://act-rules.github.io/rules/c4a8a4) | 2.4.2 | judgment (agent) |
142
+ | [Video element visual content has accessible alternative](https://act-rules.github.io/rules/c5a4ea) | 1.2.3, 1.2.5, 1.2.8 | judgment (agent) |
143
+ | [Form field label is descriptive](https://act-rules.github.io/rules/cc0f0a) | 2.4.6 | judgment (agent) |
144
+ | [Video element visual-only content has audio track alternative](https://act-rules.github.io/rules/d7ba54) | โ€” | judgment (agent) |
145
+ | [Audio element content has text alternative](https://act-rules.github.io/rules/e7aa44) | 1.2.1 | judgment (agent) |
146
+ | [Image not in the accessibility tree is decorative](https://act-rules.github.io/rules/e88epe) | 1.1.1 | judgment (agent) โ€” is the image genuinely decorative |
147
+ | [Video element auditory content has accessible alternative](https://act-rules.github.io/rules/eac66b) | 1.2.2 | judgment (agent) |
148
+ | [Focusable element has no keyboard trap via non-standard navigation](https://act-rules.github.io/rules/ebe86a) | โ€” | rendered (`scan`) |
149
+ | [Video element visual-only content has transcript](https://act-rules.github.io/rules/ee13b5) | โ€” | judgment (agent) |
150
+ | [Text content that changes automatically can be paused, stopped or hidden](https://act-rules.github.io/rules/efbfc7) | 2.2.2 | rendered (`scan`) |
151
+ | [DEPRECATED โ€” Video element visual content has description track](https://act-rules.github.io/rules/f196ce) | โ€” | withdrawn upstream |
152
+ | [Video element visual-only content is media alternative for text](https://act-rules.github.io/rules/fd26cf) | โ€” | judgment (agent) |
153
+ | [Links with identical accessible names and same context serve equivalent purpose](https://act-rules.github.io/rules/fd3a94) | 2.4.4, 2.4.9 | judgment (agent) |
154
+ | [HTML element language subtag matches language](https://act-rules.github.io/rules/off6ek) | 3.1.2 | judgment (agent) โ€” does the subtag match the actual text language |
155
+ | [Element in sequential focus order has visible focus](https://act-rules.github.io/rules/oj04fd) | 2.4.7 | rendered (`scan`) โ€” visible focus (2.4.7) โ€” probed by scan --runtime local |
156
+ | [Image accessible name is descriptive](https://act-rules.github.io/rules/qt1vmo) | 1.1.1 | judgment (agent) โ€” is the alt relevant (1.1.1) |
157
+ | [HTML page language subtag matches default language](https://act-rules.github.io/rules/ucwvc8) | 3.1.1 | judgment (agent) |
158
+
159
+ The corpus is vendored at `scripts/vendor/act-testcases.json` (refreshed by
160
+ `pnpm run build:act:refresh`, and daily by the act-refresh workflow) so the suite stays
161
+ offline and deterministic.
162
+ ACT-Rules Community Group test cases are ยฉ their contributors under the W3C Software and
163
+ Document License โ€” see `NOTICE`.