ultra11y 5.38.0 → 5.38.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.opencode/plugins/ultra11y.js +1 -1
- package/README.md +6 -6
- package/dist/playwright.mjs +24 -4
- package/package.json +1 -1
- package/scripts/build-pack-rgaa.mjs +16 -9
- package/scripts/build-standards.mjs +2 -2
- package/scripts/sync-version.mjs +5 -0
- package/scripts/ultra11y.mjs +870 -335
- package/scripts/vendor/wcag-2.2-fr.json +1 -1
- package/scripts/vendor/wcag-2.2-sc-universe.json +1 -1
- package/scripts/vendor/wcag-2.2-techniques.json +1 -1
- package/scripts/vendor/wcag-2.2-text.json +1 -1
- package/scripts/verify-skill-bundle.mjs +6 -0
- package/skills/review-a11y/SKILL.md +1 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +870 -335
- package/skills/ultra11y/SKILL.md +4 -4
- package/skills/ultra11y/references/ci.md +1 -1
- package/skills/ultra11y/references/criteria.md +2 -2
- package/skills/ultra11y/references/judgment.md +2 -2
- package/skills/ultra11y/references/mcp.md +1 -1
- package/skills/ultra11y/references/pages.md +2 -2
- package/skills/ultra11y/references/rgaa-automation.md +7 -7
- package/skills/ultra11y/references/runbook.md +27 -17
- package/skills/ultra11y/scripts/ultra11y.mjs +870 -335
- package/skills/ultra11y/templates/gitlab-ci.yml +25 -7
- package/src/adjudicate.ts +148 -20
- package/src/audit.ts +22 -1
- package/src/check.ts +52 -13
- package/src/cli.ts +119 -71
- package/src/data/standards/rgaa.json +39 -29
- package/src/data/wcag-universe.json +1 -1
- package/src/data/wcag.json +2 -1
- package/src/grounding.ts +13 -1
- package/src/ledger.ts +111 -7
- package/src/llm.ts +18 -1
- package/src/name.ts +68 -8
- package/src/parse/html.ts +16 -0
- package/src/parse/jsx-bridge.ts +11 -2
- package/src/prd.ts +2 -1
- package/src/probes.ts +24 -4
- package/src/refute.ts +13 -2
- package/src/rules/forms.ts +24 -5
- package/src/rules/links.ts +108 -52
- package/src/rules/mandatory.ts +9 -1
- package/src/rules/presentation.ts +15 -1
- package/src/rules/scripts-aria.ts +1 -1
- package/src/scan.ts +68 -6
- package/src/standards/derive.ts +27 -9
- package/src/standards/document.ts +12 -5
- package/src/types.ts +1 -1
- package/src/verify.ts +114 -23
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"name": "ultra11y",
|
|
11
11
|
"source": "./",
|
|
12
12
|
"description": "Automatic accessibility review of the code under change before every commit, push and pull request, plus full WCAG 2.2 AA / RGAA audits and conformance deliverables on demand.",
|
|
13
|
-
"version": "5.38.
|
|
13
|
+
"version": "5.38.2",
|
|
14
14
|
"author": { "name": "maxgfr" },
|
|
15
15
|
"homepage": "https://github.com/maxgfr/ultra11y",
|
|
16
16
|
"repository": "https://github.com/maxgfr/ultra11y",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultra11y",
|
|
3
3
|
"description": "Accessibility for WCAG 2.2 AA and country standards (RGAA…): an automatic review of the code under change before every commit, push and pull request, plus full audits and conformance deliverables on demand.",
|
|
4
|
-
"version": "5.38.
|
|
4
|
+
"version": "5.38.2",
|
|
5
5
|
"author": { "name": "maxgfr" },
|
|
6
6
|
"homepage": "https://github.com/maxgfr/ultra11y",
|
|
7
7
|
"repository": "https://github.com/maxgfr/ultra11y",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultra11y",
|
|
3
3
|
"description": "Accessibility for WCAG 2.2 AA and country standards (RGAA…): an automatic review of the code under change before every commit, push and pull request, plus full audits and conformance deliverables on demand.",
|
|
4
|
-
"version": "5.38.
|
|
4
|
+
"version": "5.38.2",
|
|
5
5
|
"author": { "name": "maxgfr", "url": "https://github.com/maxgfr" },
|
|
6
6
|
"homepage": "https://github.com/maxgfr/ultra11y",
|
|
7
7
|
"repository": "https://github.com/maxgfr/ultra11y",
|
|
@@ -27,7 +27,7 @@ import { fileURLToPath } from "node:url";
|
|
|
27
27
|
// Ownership marker: `ultra11y install --opencode` refuses to overwrite a plugin file that
|
|
28
28
|
// does not carry it, so a user's own ultra11y.js is never clobbered.
|
|
29
29
|
const ULTRA11Y_OPENCODE_PLUGIN = "ULTRA11Y_OPENCODE_PLUGIN";
|
|
30
|
-
const ULTRA11Y_PLUGIN_VERSION = "5.38.
|
|
30
|
+
const ULTRA11Y_PLUGIN_VERSION = "5.38.2";
|
|
31
31
|
|
|
32
32
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
33
33
|
|
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ Full details, thresholds and off-switches under [Install](#install).</sub>
|
|
|
25
25
|
|
|
26
26
|
> Audit HTML/CSS/JSX against **WCAG 2.2 AA** accessibility and produce a dated compliance report — or author/review accessible markup without regressions. A [skills.sh](https://skills.sh) agent skill: a deterministic, zero-dependency static engine **plus** the agent's judgment, with `check`/`verify` gates against hallucinated non-conformities. **The central deliverable is the auditor conformance block** — theme, criterion + official wording, test(s), WCAG mapping + level, finding, expected state, verification, `file:line` occurrences — rendered identically by the `report` (compliance doc), the `prd` backlog and the GitHub issues, in the active standard's vocabulary and **in your language** (`--lang auto` follows the conversation/repo). **WCAG is the worldwide core; country standards (RGAA, …) are pluggable in-repo packs.**
|
|
27
27
|
|
|
28
|
-
ultra11y is built around an honest **division of labour**. Automated tools only catch a fraction of accessibility problems, so the engine does the *mechanical* work —
|
|
28
|
+
ultra11y is built around an honest **division of labour**. Automated tools only catch a fraction of accessibility problems, so the engine does the *mechanical* work — 93 machine-detectable static checks tied to the WCAG 2.2 success criteria — and is explicit about everything it can't decide statically. What it can't, the **AI agent adjudicates** (statically, from the evidence, gated by `verify`/`check`) — not a deferral to a human:
|
|
29
29
|
|
|
30
30
|
- **Automatable (engine):** missing `alt`/`lang`/`title`, unlabeled fields, empty links/buttons, icon-only controls, iframes without title, tables without headers, heading-level skips, empty/dangling headings & labels, duplicate ids, invalid/broken ARIA, positive `tabindex`, autoplay/timed-refresh/`blink`/`marquee` media…
|
|
31
31
|
- **Agent judgment (gated):** alt-text relevance, link purpose in context, reading/tab order, caption accuracy — the agent rules on these via `verify --manual` → `--apply`, each verdict carrying a justification (or a groundable NC), never a silent "conforming".
|
|
@@ -316,12 +316,12 @@ a test). See [`CONTRIBUTING.md`](CONTRIBUTING.md) and `skills/ultra11y/reference
|
|
|
316
316
|
|
|
317
317
|
### What RGAA automation actually covers
|
|
318
318
|
|
|
319
|
-
The engine's
|
|
319
|
+
The engine's 93 rules are not 93 RGAA criteria: several rules can evidence the same official
|
|
320
320
|
test, and a rule can detect a useful symptom without exhausting the criterion's alternatives,
|
|
321
321
|
exceptions or relevance checks. The generated contract classifies all **258 official RGAA
|
|
322
322
|
tests**:
|
|
323
323
|
|
|
324
|
-
- **
|
|
324
|
+
- **27 `static` tests across 19 criteria:** `1.1`, `2.1`, `5.7`, `5.8`, `6.2`, `7.1`,
|
|
325
325
|
`8.2`, `8.3`, `8.4`, `8.5`, `8.10`, `9.3`, `10.1`, `10.12`, `11.1`, `11.5`,
|
|
326
326
|
`11.6`, `11.8`, `11.9`.
|
|
327
327
|
- **3 `rendered` tests across 3 criteria:** `1.1`, `8.1`, `10.7`.
|
|
@@ -331,12 +331,12 @@ tests**:
|
|
|
331
331
|
41 receive candidate evidence, with overlap. One additional criterion receives only an
|
|
332
332
|
advisory signal. A candidate is forwarded to adjudication and never changes a verdict by
|
|
333
333
|
itself.
|
|
334
|
-
- Only **8.5 and 10.1** may earn `C` from a complete silent measurement. For the other
|
|
334
|
+
- Only **8.3, 8.5 and 10.1** may earn `C` from a complete silent measurement. For the other
|
|
335
335
|
mechanically detectable tests, silence can mean “no subject found” or “an alternative or
|
|
336
336
|
exception still needs judging”, so it cannot prove conformity.
|
|
337
|
-
- The remaining **
|
|
337
|
+
- The remaining **228 `judgment` tests across 92 criteria** all enter the AI worklist,
|
|
338
338
|
including an apparently absent subject whose `NA` still needs confirmation. Overall,
|
|
339
|
-
**
|
|
339
|
+
**103 of 106 criteria require adjudication to earn `C`**.
|
|
340
340
|
|
|
341
341
|
The number can grow, but the useful target is **more observed, exact failures**, not a larger
|
|
342
342
|
marketing count. The best next candidates are complete rendered probes for zoom/reflow and
|
package/dist/playwright.mjs
CHANGED
|
@@ -136,10 +136,30 @@ var COLLECT_SNAPSHOT = `(() => {
|
|
|
136
136
|
var PRELUDE = `
|
|
137
137
|
const __sel = (e) => {
|
|
138
138
|
if (!e || !e.tagName) return '\u2014';
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
|
|
139
|
+
const esc = (v) => (typeof CSS !== 'undefined' && CSS.escape) ? CSS.escape(v) : String(v).replace(/[^a-zA-Z0-9_-]/g, '\\\\$&');
|
|
140
|
+
const unique = (s) => { try { return document.querySelectorAll(s).length === 1; } catch { return false; } };
|
|
141
|
+
const short = (n) => {
|
|
142
|
+
const t = n.tagName.toLowerCase();
|
|
143
|
+
if (n.id) return t + '#' + esc(n.id);
|
|
144
|
+
const c = typeof n.className === 'string' ? n.className.trim().split(/\\s+/)[0] : '';
|
|
145
|
+
return c ? t + '.' + esc(c) : t;
|
|
146
|
+
};
|
|
147
|
+
const first = short(e);
|
|
148
|
+
if (unique(first)) return first;
|
|
149
|
+
const parts = [];
|
|
150
|
+
for (let n = e; n && n.tagName; n = n.parentElement) {
|
|
151
|
+
let part = short(n);
|
|
152
|
+
if (n.id && unique(part)) { parts.unshift(part); return parts.join(' > '); }
|
|
153
|
+
const parent = n.parentElement;
|
|
154
|
+
if (parent) {
|
|
155
|
+
const same = Array.from(parent.children).filter((x) => x.tagName === n.tagName);
|
|
156
|
+
if (same.length > 1) part += ':nth-of-type(' + (same.indexOf(n) + 1) + ')';
|
|
157
|
+
}
|
|
158
|
+
parts.unshift(part);
|
|
159
|
+
const path = parts.join(' > ');
|
|
160
|
+
if (unique(path)) return path;
|
|
161
|
+
}
|
|
162
|
+
return parts.join(' > ') || first;
|
|
143
163
|
};
|
|
144
164
|
const __vis = (e) => {
|
|
145
165
|
const r = e.getBoundingClientRect();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultra11y",
|
|
3
|
-
"version": "5.38.
|
|
3
|
+
"version": "5.38.2",
|
|
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",
|
|
@@ -221,6 +221,7 @@ const RULE_TO_CRITERIA = {
|
|
|
221
221
|
"aria-hidden-focusable": ["7.1"], "redundant-aria": ["7.1"], "nested-interactive": ["7.1"],
|
|
222
222
|
"cross-prop-drilled-name-lost": ["7.1"],
|
|
223
223
|
"presentational-children-focusable": ["7.1"], "menuitem-empty-name": ["7.1"],
|
|
224
|
+
"button-empty-name": ["7.1"], "label-in-name-mismatch": ["7.1"],
|
|
224
225
|
"clickable-noninteractive": ["7.3"], "live-region-conflict": ["7.5"], "status-message-not-assertive": ["7.5"],
|
|
225
226
|
// Dynamic tier (scan --local): the live-region probe projects onto WCAG 4.1.3 → RGAA 7.5
|
|
226
227
|
// (status messages) — the WCAG-faithful home. Ara ALSO classifies the source finding under
|
|
@@ -233,6 +234,7 @@ const RULE_TO_CRITERIA = {
|
|
|
233
234
|
"axe:aria-required-attr": ["7.1"], "axe:aria-required-children": ["7.1"], "axe:aria-required-parent": ["7.1"],
|
|
234
235
|
"axe:aria-valid-attr": ["7.1"], "axe:aria-valid-attr-value": ["7.1"], "axe:nested-interactive": ["7.1"],
|
|
235
236
|
"axe:aria-hidden-focus": ["7.1"], "axe:presentation-role-conflict": ["7.1"],
|
|
237
|
+
"axe:button-name": ["7.1"], "axe:input-button-name": ["7.1"],
|
|
236
238
|
// Theme 8 — document (8.2 valid code, 8.3 default lang, 8.4 lang relevant, 8.5 title, 8.7/8.8 lang changes)
|
|
237
239
|
"duplicate-id": ["8.2"], "axe:duplicate-id": ["8.2"], "axe:duplicate-id-aria": ["8.2"], "axe:duplicate-id-active": ["8.2"],
|
|
238
240
|
"html-lang-missing": ["8.3"], "axe:html-has-lang": ["8.3"],
|
|
@@ -258,11 +260,11 @@ const RULE_TO_CRITERIA = {
|
|
|
258
260
|
"control-label-missing": ["11.1"], "label-for-dangling": ["11.1"], "placeholder-as-label": ["11.1"],
|
|
259
261
|
"form-field-multiple-labels": ["11.1"], "select-has-option": ["11.1"], "control-name-title-only": ["11.1"],
|
|
260
262
|
"radio-checkbox-group-ungrouped": ["11.5"], "date-fields-ungrouped": ["11.5"],
|
|
261
|
-
"field-purpose-incomplete": ["11.1", "11.13"], "fieldset-legend-missing": ["11.6"], "button-empty-name": ["11.9"],
|
|
262
|
-
"autocomplete-token-invalid": ["11.13"], "label-in-name-mismatch": ["11.9"],
|
|
263
|
+
"field-purpose-incomplete": ["11.1", "11.13"], "fieldset-legend-missing": ["11.6"], "form-button-empty-name": ["11.9"],
|
|
264
|
+
"autocomplete-token-invalid": ["11.13"], "form-label-in-name-mismatch": ["11.9"],
|
|
263
265
|
"error-not-associated": ["11.10"], "aria-invalid-no-description": ["11.10"],
|
|
264
266
|
"axe:label": ["11.1"], "axe:form-field-multiple-labels": ["11.1"], "axe:select-name": ["11.1"], "axe:label-title-only": ["11.1"],
|
|
265
|
-
"axe:autocomplete-valid": ["11.13"], "axe:fieldset": ["11.6"],
|
|
267
|
+
"axe:autocomplete-valid": ["11.13"], "axe:fieldset": ["11.6"],
|
|
266
268
|
// Theme 12 — navigation (12.7 skip link, 12.8 tab order, 12.9 keyboard trap)
|
|
267
269
|
"skip-link-target-missing": ["12.7"], "axe:skip-link": ["12.7"], "axe:bypass": ["12.7"],
|
|
268
270
|
"positive-tabindex": ["12.8"], "axe:tabindex": ["12.8"],
|
|
@@ -316,15 +318,20 @@ const DECISIVE_RULE_TESTS = {
|
|
|
316
318
|
"11.1|control-label-missing": ["1"],
|
|
317
319
|
"11.6|axe:fieldset": ["1"],
|
|
318
320
|
"11.6|fieldset-legend-missing": ["1"],
|
|
319
|
-
"11.9|
|
|
320
|
-
"11.9|
|
|
321
|
-
"11.9|button-empty-name": ["1"],
|
|
322
|
-
"11.9|label-in-name-mismatch": ["2"],
|
|
321
|
+
"11.9|form-button-empty-name": ["1"],
|
|
322
|
+
"11.9|form-label-in-name-mismatch": ["2"],
|
|
323
323
|
"11.5|radio-checkbox-group-ungrouped": ["1"],
|
|
324
324
|
"5.7|headers-attr-dangling": ["4"],
|
|
325
325
|
"5.7|table-scope-invalid": ["2", "3"],
|
|
326
326
|
"5.8|layout-table-data-markup": ["1"],
|
|
327
|
-
|
|
327
|
+
// An absent accessible name fails the prerequisite name/role/value test (7.1.1).
|
|
328
|
+
// Test 7.1.3 asks whether an already-accessible name/role is pertinent; it cannot be the
|
|
329
|
+
// attachment for a component that has no name at all.
|
|
330
|
+
"7.1|menuitem-empty-name": ["1"],
|
|
331
|
+
"7.1|button-empty-name": ["1"],
|
|
332
|
+
"7.1|axe:button-name": ["1"],
|
|
333
|
+
"7.1|axe:input-button-name": ["1"],
|
|
334
|
+
"7.1|label-in-name-mismatch": ["3"],
|
|
328
335
|
};
|
|
329
336
|
|
|
330
337
|
// Candidate evidence is test-scoped too. The fallback remains all tests only when one rule id
|
|
@@ -388,7 +395,7 @@ const CANDIDATE_RULE_TESTS = {
|
|
|
388
395
|
"13.8|axe:marquee": ["1"],
|
|
389
396
|
};
|
|
390
397
|
|
|
391
|
-
const COMPLETE_BY_SILENCE = new Set(["8.5", "10.1"]);
|
|
398
|
+
const COMPLETE_BY_SILENCE = new Set(["8.3", "8.5", "10.1"]);
|
|
392
399
|
const renderedTier = (ruleId) =>
|
|
393
400
|
ruleId.startsWith("rendered-") || ruleId.startsWith("dyn-") || ruleId.startsWith("axe:") ? "rendered" : "static";
|
|
394
401
|
const mergeTestTier = (current, next) => (current === "static" || next === "static" ? "static" : next);
|
|
@@ -664,7 +664,7 @@ const RULE_SC_COVERAGE = {
|
|
|
664
664
|
"2.4.3": ["positive-tabindex"],
|
|
665
665
|
"2.4.7": ["rendered-focus-not-visible"],
|
|
666
666
|
"2.4.4": ["link-empty-name", "icon-only-control-unnamed"],
|
|
667
|
-
"2.5.3": ["label-in-name-mismatch"],
|
|
667
|
+
"2.5.3": ["label-in-name-mismatch", "form-label-in-name-mismatch"],
|
|
668
668
|
"3.2.2": ["on-input-context-change"],
|
|
669
669
|
"3.3.8": ["credential-entry-blocked"],
|
|
670
670
|
"3.1.1": ["html-lang-missing", "document-language-missing", "html-lang-xml-lang-mismatch", "lang-invalid"],
|
|
@@ -675,7 +675,7 @@ const RULE_SC_COVERAGE = {
|
|
|
675
675
|
"iframe-title-missing", "invalid-aria-role", "aria-ref-missing-id", "redundant-aria",
|
|
676
676
|
"clickable-noninteractive", "aria-required-children", "aria-hidden-focusable", "nested-interactive", "invalid-aria-attr", "invalid-aria-value", "aria-required-attr", "aria-prohibited-attr",
|
|
677
677
|
"duplicate-id", "duplicate-attribute", "control-label-missing", "placeholder-as-label", "form-field-multiple-labels",
|
|
678
|
-
"select-has-option", "button-empty-name", "icon-only-control-unnamed", "control-name-title-only",
|
|
678
|
+
"select-has-option", "button-empty-name", "form-button-empty-name", "icon-only-control-unnamed", "control-name-title-only",
|
|
679
679
|
"field-purpose-incomplete", "disabled-context-content", "presentational-children-focusable",
|
|
680
680
|
"decorative-marked-exposed", "menuitem-empty-name",
|
|
681
681
|
],
|
package/scripts/sync-version.mjs
CHANGED
|
@@ -41,6 +41,11 @@ edit("src/types.ts", (s) => s.replace(/(export const VERSION = ")[^"]+(";)/, `$1
|
|
|
41
41
|
edit("skills/ultra11y/SKILL.md", setVersionField);
|
|
42
42
|
edit("skills/review-a11y/SKILL.md", setVersionField);
|
|
43
43
|
|
|
44
|
+
// GitLab installs the published package rather than consuming the repository checkout. Keep
|
|
45
|
+
// its default exact pin on the release that owns the template, so a later `latest` cannot
|
|
46
|
+
// silently change an old pipeline's audit semantics.
|
|
47
|
+
edit("skills/ultra11y/templates/gitlab-ci.yml", (s) => s.replace(/(ULTRA11Y_VERSION:\s*')[^']+(')/, `$1${version}$2`));
|
|
48
|
+
|
|
44
49
|
// The plugin manifests — what carries the automatic-review hook. A stale version here is
|
|
45
50
|
// what makes an installed plugin report the wrong engine, so every harness's manifest is
|
|
46
51
|
// rewritten (verify-skill-bundle.mjs fails the build if they drift). Codex resolves
|