ultra11y 2.31.2 → 2.32.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.
- 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/package.json +1 -1
- package/scripts/build-pack-rgaa.mjs +31 -0
- package/scripts/ultra11y.mjs +277 -63
- package/scripts/vendor/wcag-2.2-fr.json +1 -1
- package/scripts/vendor/wcag-2.2-sc-universe.json +1 -1
- package/skills/review-a11y/SKILL.md +1 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +277 -63
- package/skills/ultra11y/SKILL.md +11 -5
- package/skills/ultra11y/references/orchestrators.md +176 -0
- package/skills/ultra11y/references/pages.md +7 -3
- package/skills/ultra11y/scripts/ultra11y.mjs +277 -63
- package/src/adjudicate.ts +161 -14
- package/src/auditor.ts +2 -2
- package/src/cli.ts +70 -13
- package/src/data/standards/rgaa.json +10 -5
- package/src/data/wcag-universe.json +1 -1
- package/src/gh.ts +56 -9
- package/src/llm.ts +23 -2
- package/src/pages-report.ts +51 -12
- package/src/prd.ts +3 -2
- package/src/report.ts +70 -19
- package/src/standards/derive.ts +33 -2
- package/src/standards/pack.ts +13 -0
- package/src/standards/types.ts +13 -0
- package/src/types.ts +1 -1
package/skills/ultra11y/SKILL.md
CHANGED
|
@@ -4,7 +4,7 @@ description: "Use to AUDIT existing HTML/CSS/JSX against WCAG 2.2 AA accessibili
|
|
|
4
4
|
when_to_use: "Invoke when the user asks for an accessibility AUDIT or a formal deliverable: audit a repo, site or page against WCAG 2.2 AA or a country standard (RGAA, Section 508, EN 301 549); produce a dated conformance report, a criterion grid, a PRD remediation backlog or GitHub issues; author accessible markup from scratch; wire the repo gate (init --hook/--ci) or a rendered-DOM capture pipeline. For a review of the code UNDER CHANGE (diff, branch, PR), use the `review-a11y` skill instead."
|
|
5
5
|
license: MIT
|
|
6
6
|
metadata:
|
|
7
|
-
version: 2.
|
|
7
|
+
version: 2.32.0
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# ultra11y — audit WCAG 2.2 AA and write accessible markup
|
|
@@ -38,21 +38,26 @@ adjudicates. So the engine's clean run is a starting point, never a verdict —
|
|
|
38
38
|
> **Core rules:**
|
|
39
39
|
> 1. **Never invent a non-conformity**: every `NC` cites a real, resolvable element (`check` verifies it).
|
|
40
40
|
> 2. **Native HTML first, ARIA last**; never duplicate implicit semantics.
|
|
41
|
-
> 3. **
|
|
41
|
+
> 3. **Never invent a conformity either**: a `C` is cited exactly like an `NC` — it names in
|
|
42
|
+
> `citations[]` the harvested evidence it cleared, each anchor resolvable and drawn from
|
|
43
|
+
> the evidence that criterion was actually shown. A criterion presented with *no* evidence
|
|
44
|
+
> cannot be `C` at all; it stays `manual`. And an adjudicated `C` is never merged with an
|
|
45
|
+
> engine-decided one: reports list it separately and keep it out of the automatic pass rate.
|
|
46
|
+
> 4. **Residual is explicit, never silently conforming**: the AI agent *adjudicates* every
|
|
42
47
|
> *judgment* criterion itself (`verify --manual`, gated), and the *rendering* criteria go to
|
|
43
48
|
> `scan`; any criterion still unproven stays "to assess manually" — no status without a
|
|
44
49
|
> recorded, justified verdict.
|
|
45
|
-
>
|
|
50
|
+
> 5. **The FINAL rendered semantic HTML must be correct.** The engine sees only source; a
|
|
46
51
|
> component library (DSFR/MUI…) or `.vue`/`.svelte`/`.astro` SFC hides the real markup, so
|
|
47
52
|
> a green source audit is not proof. Verify the produced semantic HTML — install the
|
|
48
53
|
> zero-touch **capture** harvester (`render --setup`) so every component your tests render
|
|
49
54
|
> is serialized to `.ultra11y/captures` and audited, with `audit --require-captures` gating
|
|
50
55
|
> the blind spots. See `references/automation.md` / `rendered.md`.
|
|
51
|
-
>
|
|
56
|
+
> 6. **Language**: ALWAYS pass `--lang` matching the language of your conversation with
|
|
52
57
|
> the user; ask the user when ambiguous. Without the flag the CLI auto-detects (repo
|
|
53
58
|
> `<html lang>` → the active standard's default locale → English) — a scripted/CI
|
|
54
59
|
> fallback, not a substitute for passing `--lang` yourself.
|
|
55
|
-
>
|
|
60
|
+
> 7. **Technical tokens stay in English, even in French prose.** In any French deliverable
|
|
56
61
|
> you write (report commentary, PRD, GitHub issues, judgment verdicts), attribute/
|
|
57
62
|
> element/role names and their values are code, not prose — never translate them:
|
|
58
63
|
> `aria-live` stays `aria-live` (never « région live »), same for `tabindex`, `alt`,
|
|
@@ -85,6 +90,7 @@ Domain knowledge first, then the tooling. Read the one that matches the question
|
|
|
85
90
|
| `references/false-positives.md` | Where a finding may still be wrong, and how to refute it |
|
|
86
91
|
| `references/rendered.md` · `references/automation.md` | Auditing produced HTML, captures, hooks and CI |
|
|
87
92
|
| `references/harnesses.md` | Where this runs: Claude Code, Codex CLI, OpenCode, and the AGENTS.md fallback |
|
|
93
|
+
| `references/orchestrators.md` | Driving the engine from a workflow engine / non-GitHub CI: pinning, the six-step pipeline, exit codes, `--issues-json` for any tracker |
|
|
88
94
|
| `references/ci.md` | `--format sarif\|github`: inline PR annotations, code scanning, job summary |
|
|
89
95
|
| `references/pages.md` | Page snapshots + the per-page criterion grid (`pages`), and its honesty rules |
|
|
90
96
|
| `references/e2e.md` | `render --e2e`: auditing a page during your Playwright/Cypress run |
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Drive the engine from an orchestrator
|
|
2
|
+
|
|
3
|
+
The skill and the GitHub Action assume a person or a coding harness is in the loop. An
|
|
4
|
+
**orchestrator** — an agent workflow engine, a CI job that is not GitHub, a cron runner —
|
|
5
|
+
is different: nothing reads a banner, nothing retries on a hunch, and the only things that
|
|
6
|
+
travel between steps are exit codes and JSON. This page is the contract for that caller.
|
|
7
|
+
|
|
8
|
+
The shape that matters: **the engine finds, the agent adjudicates, the gates refuse.** Keep
|
|
9
|
+
the detection deterministic and there is no model between "a defect exists" and "a defect is
|
|
10
|
+
counted" — which is the whole reason to wire the engine in rather than ask a model to audit.
|
|
11
|
+
|
|
12
|
+
## Resolving the engine
|
|
13
|
+
|
|
14
|
+
Pin the version. `npx` resolves `latest` otherwise, so what runs can change with no commit
|
|
15
|
+
anywhere.
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm_config_update_notifier=false npx -y ultra11y@2.31.2 --version # → 2.31.2, exit 0
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- **`npm_config_update_notifier=false` is not optional.** Without it npm prints its
|
|
22
|
+
"New major version available" notice into the output a step is about to parse.
|
|
23
|
+
- **`ULTRA11Y_BIN`** points at an engine you already have (a vendored `ultra11y.mjs`, a
|
|
24
|
+
container layer). The engine is one self-contained file with no runtime dependencies, so
|
|
25
|
+
vendoring is a supported path — that is exactly what the skills and the Action do.
|
|
26
|
+
- **Node ≥ 22.18.** No other system dependency, no API key. (`judge` takes a key; nothing
|
|
27
|
+
else does. `scan` needs a browser tier — see `references/dynamic.md`.)
|
|
28
|
+
- **Verify what you resolved, and fail if it is not what you asked for.** A step that lets a
|
|
29
|
+
missing engine fall through reports zero findings, which reads exactly like a clean audit.
|
|
30
|
+
This is the one failure mode worth being loud about.
|
|
31
|
+
|
|
32
|
+
## The pipeline
|
|
33
|
+
|
|
34
|
+
Six steps. Four are deterministic; only step 3 involves a model.
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
ENGINE="npx -y ultra11y@2.31.2" # or "node $ULTRA11Y_BIN"
|
|
38
|
+
RUN=/tmp/a11y-run # out-of-tree: see "Where output lands"
|
|
39
|
+
|
|
40
|
+
# 1. FIND — the static engine. No model. This is the finding set.
|
|
41
|
+
$ENGINE audit "<globs>" --graph --out "$RUN" --json --lang en > /dev/null
|
|
42
|
+
# …or, on a pull request, exactly what the branch introduced:
|
|
43
|
+
$ENGINE audit --since "$BASE_REF" --graph --out "$RUN" --json --lang en > /dev/null
|
|
44
|
+
|
|
45
|
+
# 2. WORKLIST — a draft report, then the criteria a static pass cannot decide.
|
|
46
|
+
$ENGINE report --in "$RUN/audit-latest.json" --out "$RUN" --lang en > /dev/null
|
|
47
|
+
$ENGINE verify --report "$RUN"/wcag-*.md --in "$RUN/audit-latest.json" --manual --out "$RUN" --json
|
|
48
|
+
|
|
49
|
+
# 3. ADJUDICATE — hand $RUN/ADJUDICATE.todo.json to the agent; it fills every `verdict`.
|
|
50
|
+
# The file carries its own `contract` (see below) — the agent needs nothing else.
|
|
51
|
+
|
|
52
|
+
# 4. FOLD — fail-closed. Exit 1 with per-criterion reasons if a verdict is unsupported.
|
|
53
|
+
$ENGINE verify --apply "$RUN/adjudication.json" --in "$RUN/audit-latest.json" --out "$RUN" --json
|
|
54
|
+
|
|
55
|
+
# 5. REPORT — deterministic rendering of the adjudicated audit.
|
|
56
|
+
$ENGINE report --in "$RUN/audit-latest.json" --out audits --standard rgaa --lang fr
|
|
57
|
+
|
|
58
|
+
# 6. GATE — integrity. Exit ≠ 0 on an invented criterion or unjustified NA.
|
|
59
|
+
$ENGINE check --report audits/rgaa-*.md --in "$RUN/audit-latest.json" --json
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Step 4 is the one to understand: it is a **deterministic gate on the model's output**. A
|
|
63
|
+
verdict with no justification, an NC with no resolvable `normativeRef`, a criterion missing
|
|
64
|
+
from the adjudication — each is refused by name, and the run stops. That is what keeps the
|
|
65
|
+
final report defensible.
|
|
66
|
+
|
|
67
|
+
## The adjudication contract
|
|
68
|
+
|
|
69
|
+
`ADJUDICATE.todo.json` declares what may be written into it, so the filler never has to read
|
|
70
|
+
ultra11y's source:
|
|
71
|
+
|
|
72
|
+
```jsonc
|
|
73
|
+
{
|
|
74
|
+
"contract": {
|
|
75
|
+
"verdicts": ["C", "NC", "NA", "manual"],
|
|
76
|
+
"manualReasons": ["needs-rendered-dom", "undecidable"],
|
|
77
|
+
"requires": {
|
|
78
|
+
"C": "a non-empty justification AND citations[] naming the harvested evidence it cleared …",
|
|
79
|
+
"NA": "a non-empty justification",
|
|
80
|
+
"NC": "at least one groundable finding, each citing a normativeRef that resolves against the active standard",
|
|
81
|
+
"manual": "a reason ∈ {needs-rendered-dom, undecidable}"
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"items": [ /* one per criterion the engine could not decide; fill `verdict` */ ]
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Note what `C` costs: a clearing verdict is gated exactly like an accusing one. It must
|
|
89
|
+
name in `citations[]` the evidence it cleared, each anchor resolvable and drawn from that
|
|
90
|
+
criterion's own harvested evidence — and a criterion presented with **no** evidence cannot
|
|
91
|
+
be `C` at all. This is what stops "C everywhere with a plausible sentence" from passing.
|
|
92
|
+
|
|
93
|
+
The header is **advisory**. `verify --apply` validates against its own constants, so editing
|
|
94
|
+
the contract in the file widens nothing.
|
|
95
|
+
|
|
96
|
+
Verdicts are matched **case-insensitively** (`na` is `NA`, `MANUAL` is `manual`) — a spelling
|
|
97
|
+
accident is not a disagreement about accessibility. A verdict outside the vocabulary is still
|
|
98
|
+
refused, and the message names the four that are not.
|
|
99
|
+
|
|
100
|
+
`manual` is the honest verdict for anything that genuinely needs a rendered page. It keeps
|
|
101
|
+
the criterion in `residualRisks`, where the report declares it as a residual risk. **Never
|
|
102
|
+
map an undecided criterion to `C`** — that is the one way to turn this pipeline back into
|
|
103
|
+
the thing it replaces.
|
|
104
|
+
|
|
105
|
+
## Filing findings on a tracker that is not GitHub
|
|
106
|
+
|
|
107
|
+
`--gh-issues` shells out to the `gh` CLI. For any other board, take the same items as JSON:
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
$ENGINE prd --in "$RUN/audit-latest.json" --out "$RUN" --issues-json --lang en
|
|
111
|
+
# → $RUN/issues-<date>.json
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```jsonc
|
|
115
|
+
{
|
|
116
|
+
"tool": "ultra11y", "kind": "issues", "schemaVersion": 1,
|
|
117
|
+
"standard": "wcag", "date": "2026-08-12", "count": 4,
|
|
118
|
+
"issues": [{
|
|
119
|
+
"criteriaId": "1.1.1",
|
|
120
|
+
"title": "[a11y] WCAG 1.1.1 — Non-text Content", // the de-dupe grain: stable across runs
|
|
121
|
+
"body": "…", // the auditor block, rendered
|
|
122
|
+
"labels": ["accessibility", "wcag", "bloquant"],
|
|
123
|
+
"severity": "bloquant",
|
|
124
|
+
"advisory": false, // true ⇒ a good practice, NOT a non-conformity
|
|
125
|
+
"occurrences": [{ "file": "src/Page.tsx", "line": 4, "selector": "img", "message": "…" }]
|
|
126
|
+
}]
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
One issue per criterion, not per occurrence — a missing `alt` on forty images is one item
|
|
131
|
+
with forty `occurrences`, not forty tickets. De-dupe on `title` and re-running files nothing
|
|
132
|
+
twice. `advisory: true` marks a non-normative recommendation: it must not be triaged as a
|
|
133
|
+
non-conformity, and it never enters the conformance rate.
|
|
134
|
+
|
|
135
|
+
## Where output lands
|
|
136
|
+
|
|
137
|
+
`--out <dir>` fully contains what a command writes: pointed outside the checkout, an audit
|
|
138
|
+
leaves the target repository byte-identical. That matters when the orchestrator audits a
|
|
139
|
+
repo it does not own — write the run's intermediates out-of-tree and let only the final
|
|
140
|
+
dated report land in the project, if at all.
|
|
141
|
+
|
|
142
|
+
`.ultra11y/pages/` is written only by the page-snapshot commands (`scan`, `snapshot`,
|
|
143
|
+
`dev`); `--root` relocates it. `audit`, `report`, `prd`, `verify` and `check` never touch it.
|
|
144
|
+
|
|
145
|
+
## Exit codes
|
|
146
|
+
|
|
147
|
+
| | 0 | 1 | 2 |
|
|
148
|
+
|---|---|---|---|
|
|
149
|
+
| `audit` | reported (no `--fail-on`), or clean | findings ≥ `--fail-on` | usage / bad input |
|
|
150
|
+
| `verify --apply` | folded | the adjudication was refused (see `issues[]`) | usage / bad input |
|
|
151
|
+
| `check` | report is sound | integrity failure | report not found / bad input |
|
|
152
|
+
| `prd` | written | `--gh-*` had failures | usage / bad input |
|
|
153
|
+
|
|
154
|
+
Read exit codes from the process, **not** from a pipeline — `cmd --json | head` reports
|
|
155
|
+
`head`'s status, which is how a failing gate reads as a pass.
|
|
156
|
+
|
|
157
|
+
## What the JSON tells you
|
|
158
|
+
|
|
159
|
+
- `audit --json` → the `AuditResult` (also at `<out>/audit-latest.json`): `findings[]` with
|
|
160
|
+
stable `findingId`, `criteriaId`, `severity`, `file`, `line`; `residualRisks[]`;
|
|
161
|
+
`conformancePct` — the **automatic static pass rate**, not a conformance rate.
|
|
162
|
+
- `verify --apply --json` → `{ ok, auditPath, applied, stillManual, conformancePct, findings, grounding }`
|
|
163
|
+
on success; `{ ok: false, issues[], audit }` when refused.
|
|
164
|
+
- `check --json` → `{ ok, issues[] }`.
|
|
165
|
+
|
|
166
|
+
## Honesty rules an orchestrator has to carry
|
|
167
|
+
|
|
168
|
+
The engine is explicit about what it did not decide; a wrapper that flattens that away
|
|
169
|
+
produces exactly the false clean bill of health this design exists to prevent.
|
|
170
|
+
|
|
171
|
+
1. **A missing engine is a failure, never an empty finding set.**
|
|
172
|
+
2. **Report the residual criteria.** The rendering criteria (contrast, focus, zoom, reflow)
|
|
173
|
+
are undecided until `scan` runs. Say so; do not let a step summary imply full coverage.
|
|
174
|
+
3. **`conformancePct` is the static pass rate.** Label it as such.
|
|
175
|
+
4. **Never let a model write the counts.** Take them from the JSON — the numbers exist
|
|
176
|
+
precisely so nothing has to be re-derived by a model that may drop a field.
|
|
@@ -205,9 +205,13 @@ Two things it deliberately does NOT do:
|
|
|
205
205
|
page sheet, the compliance report, the PRD and the GitHub issue are the same block — and
|
|
206
206
|
the occurrence lines stay parseable by `verify`.
|
|
207
207
|
|
|
208
|
-
The screenshot is
|
|
209
|
-
|
|
210
|
-
|
|
208
|
+
The screenshot is **copied next to the report** when you pass `--out`: each page's
|
|
209
|
+
`.ultra11y/pages/<id>/screen.png` lands in `<out>/assets/<id>.png` and is linked relatively
|
|
210
|
+
from there. That is what makes the output directory self-contained — CI uploads `audits/`
|
|
211
|
+
alone as the artefact, and a `../../.ultra11y/…` reference would be a broken image in every
|
|
212
|
+
sheet the reviewer opens. Without `--out` (streaming to stdout) the path stays relative,
|
|
213
|
+
since there is no directory to keep intact. A page with no screenshot says so, rather than
|
|
214
|
+
showing nothing.
|
|
211
215
|
|
|
212
216
|
Sheets are named `page-<id>.md`: a page whose id is `index` — the ordinary id for an
|
|
213
217
|
`index.html` target — would otherwise be written over the index itself.
|