a11y-loop 0.1.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 (36) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +409 -0
  3. package/THIRD-PARTY-NOTICES.md +32 -0
  4. package/package.json +51 -0
  5. package/skill/a11y-loop/SKILL.md +332 -0
  6. package/skill/a11y-loop/evals/evals.json +168 -0
  7. package/skill/a11y-loop/evals/trigger-evals.json +20 -0
  8. package/skill/a11y-loop/references/ai-failure-modes.md +272 -0
  9. package/skill/a11y-loop/references/apg-patterns.md +264 -0
  10. package/skill/a11y-loop/references/manual-testing.md +224 -0
  11. package/skill/a11y-loop/references/wcag22-quick-ref.md +224 -0
  12. package/src/cli.js +207 -0
  13. package/src/commands/audit.js +125 -0
  14. package/src/commands/contrast.js +141 -0
  15. package/src/commands/diff.js +65 -0
  16. package/src/lib/axe-runner.js +400 -0
  17. package/src/lib/browser-utils.js +221 -0
  18. package/src/lib/checks/dialog.js +341 -0
  19. package/src/lib/checks/div-button.js +87 -0
  20. package/src/lib/checks/focus-visible.js +296 -0
  21. package/src/lib/checks/keyboard.js +235 -0
  22. package/src/lib/checks/link-text.js +83 -0
  23. package/src/lib/checks/reduced-motion.js +139 -0
  24. package/src/lib/checks/reflow.js +101 -0
  25. package/src/lib/checks/target-size.js +128 -0
  26. package/src/lib/contrast-math.js +189 -0
  27. package/src/lib/diff.js +118 -0
  28. package/src/lib/finding.js +164 -0
  29. package/src/lib/fingerprint.js +0 -0
  30. package/src/lib/format/checklist.js +281 -0
  31. package/src/lib/format/human.js +175 -0
  32. package/src/lib/format/json.js +139 -0
  33. package/src/lib/format/sarif.js +111 -0
  34. package/src/lib/serve.js +189 -0
  35. package/src/lib/suggest-color.js +169 -0
  36. package/src/lib/wcag-map.js +271 -0
@@ -0,0 +1,332 @@
1
+ ---
2
+ name: a11y-loop
3
+ description: >-
4
+ Make accessible markup the default when writing UI, then verify it in a real
5
+ browser instead of assuming. Use whenever generating or modifying user
6
+ interface code (HTML, JSX/TSX, Vue, Svelte, Astro, CSS) — forms, modals,
7
+ menus, tabs, tables, navigation, pricing pages, dark mode, colors — and
8
+ whenever the user asks for an accessibility audit, an a11y check or fix, a
9
+ WCAG review, a contrast fix, or help with screen readers, keyboard
10
+ navigation, focus order, ARIA, alt text. Supplies standing generation rules
11
+ (semantic HTML first, ARIA discipline, APG keyboard contracts, labels, focus
12
+ visibility, AA contrast in light and dark, reduced motion, 24x24 targets), a
13
+ mandatory audit-fix-re-audit loop driven by the `a11y-loop` CLI (axe-core in
14
+ Chromium across default, dark, forced-colors, reduced-motion and 320px
15
+ passes), and honest reporting of what automation cannot judge. Keywords:
16
+ accessibility, a11y, WCAG 2.2 AA, ARIA, axe-core, contrast ratio, screen
17
+ reader. Not for backend-only work with no UI.
18
+ license: MIT
19
+ compatibility: >-
20
+ Verification needs Node.js >= 20 and Playwright Chromium. Install with
21
+ `npm i -g a11y-loop && npx playwright install chromium` (set
22
+ PLAYWRIGHT_BROWSERS_PATH first if you keep browsers off the system drive).
23
+ The generation and honesty rules apply with or without the CLI; every step
24
+ that says "audit" requires it.
25
+ metadata:
26
+ "a11y-loop/version": "0.1.0"
27
+ paths:
28
+ - "**/*.html"
29
+ - "**/*.tsx"
30
+ - "**/*.jsx"
31
+ - "**/*.vue"
32
+ - "**/*.svelte"
33
+ - "**/*.astro"
34
+ - "**/*.css"
35
+ allowed-tools: 'Bash(a11y-loop *) Bash(npx a11y-loop *) Bash(node ${CLAUDE_SKILL_DIR}/../../src/cli.js *)'
36
+ ---
37
+
38
+ # a11y-loop
39
+
40
+ These are standing instructions. **For the remainder of this session, whenever
41
+ you write or modify UI code, apply §1 as you write it, run §2 before you call
42
+ the work done, and speak about the result only in the terms allowed by §3.**
43
+ They stay in force across turns — you do not need to be reminded.
44
+
45
+ Prompting alone does not work. UIs generated from accessibility-oriented
46
+ prompts measure *slightly worse* than accessibility-agnostic ones (W4A'25,
47
+ 17.32% vs 15.93% violation rate). §1 is the setup; §2 is the product. Skipping
48
+ the loop reverts you to the baseline where 84% of AI-generated pages carry
49
+ accessibility failures.
50
+
51
+ ## Resolving the CLI
52
+
53
+ Resolve once per session, first form that answers `--version` wins. Call the
54
+ result `A11Y` below.
55
+
56
+ 1. `a11y-loop --version` — installed globally or as a project dependency.
57
+ 2. `npx a11y-loop --version` — no install needed.
58
+ 3. `node ${CLAUDE_SKILL_DIR}/../../src/cli.js --version` — this skill sitting
59
+ inside a checkout of the a11y-loop repo.
60
+
61
+ ```
62
+ A11Y audit <url> # dev server — primary loop path
63
+ A11Y audit --file path/to/page.html # served over 127.0.0.1 (never file://)
64
+ A11Y audit --html "<fragment>" # wrap + serve generated markup
65
+ --json --out report.json --sarif out.sarif --interact states.mjs
66
+ --headed --no-best-practice --quiet
67
+ A11Y contrast <fg> <bg> [--large] [--ui] [--fix] [--json]
68
+ A11Y diff --before before.json --after after.json
69
+ ```
70
+
71
+ Exit codes: `0` no violations · `1` violations found · `2` tool error.
72
+
73
+ Every audit runs five passes automatically: default 1280x720, dark mode,
74
+ forced-colors, reduced-motion, and 320x256 (the WCAG-sanctioned equivalent of
75
+ 400% zoom for SC 1.4.10). Dark-mode contrast failures are common and invisible
76
+ to a single default-mode scan, so never hand-roll a single-pass axe call
77
+ instead.
78
+
79
+ JSON report shape: `findings.violations`, `findings.needsReview`,
80
+ `findings.bestPractice`, `manualChecklist`, `summary`; each finding carries a
81
+ stable `fingerprint`, a `wcag` block (`sc`, `name`, `level`, `minVersion`,
82
+ `wcag22Only`), `act[]` rule IDs, and — for contrast — concrete
83
+ `suggestions[]` with hex values and before/after ratios.
84
+
85
+ ---
86
+
87
+ ## §1 Generation rules
88
+
89
+ Apply while writing, not afterwards.
90
+
91
+ **Semantic HTML first.** `<button>`, `<a href>`, `<input>`, `<select>`,
92
+ `<dialog>`, `<table>`, `<nav>`, `<main>`, `<h1>`–`<h6>`. Native elements bring
93
+ focus, keyboard behavior, and state announcement for free; a `div` brings none
94
+ of it. The four rules of ARIA use ([Using ARIA](https://www.w3.org/TR/using-aria/),
95
+ now a discontinued draft but still the canonical statement):
96
+
97
+ 1. "If you can use a native HTML element or attribute with the semantics and
98
+ behavior you require already built in, instead of re-purposing an element
99
+ and adding an ARIA role, state or property to make it accessible, then do
100
+ so."
101
+ 2. "Do not change native semantics, unless you really have to."
102
+ 3. "All interactive ARIA controls must be usable with the keyboard."
103
+ 4. "Do not use `role="presentation"` or `aria-hidden="true"` on a focusable
104
+ element."
105
+
106
+ **No ARIA is better than bad ARIA.** Wrong ARIA actively misrepresents the
107
+ interface to screen reader users — worse than none. Empirically, pages *with*
108
+ ARIA average more failures than pages without (WebAIM Million 2026: 59.1 vs
109
+ 42.0). Add ARIA only when you can name the native gap it fills.
110
+
111
+ **A role is a promise.** `role="button"` commits you to Enter and Space
112
+ activation, focusability, and a disabled state. `role="tab"` commits you to the
113
+ whole arrow-key contract. ARIA creates zero behavior; an unfulfilled promise is
114
+ a false accessibility interface. If you cannot implement the contract, use the
115
+ native element or ship the plain version.
116
+
117
+ **Every APG-pattern component follows its APG keyboard contract.** Dialog,
118
+ tabs, accordion, menu button, combobox, disclosure, radio group, switch,
119
+ tooltip, listbox — contracts in
120
+ [references/apg-patterns.md](references/apg-patterns.md). For any widget not in
121
+ that file, read its APG page before implementing it.
122
+
123
+ **Names and labels.**
124
+ - Every input has a programmatic label: `<label for>`, wrapping `<label>`, or
125
+ `aria-label`. A placeholder is not a label.
126
+ - Every link and button has a non-empty accessible name. Icon-only controls get
127
+ `aria-label` (or visually-hidden text); the icon `<svg>` gets
128
+ `aria-hidden="true"`. Empty links (46.3% of pages) and empty buttons (30.6%)
129
+ are overwhelmingly icon-only controls.
130
+ - Visible label text must be contained in the accessible name (SC 2.5.3), so
131
+ voice-control users can say what they see.
132
+ - Group related fields in `<fieldset>` with `<legend>`.
133
+
134
+ **Structure.** `<html lang="…">` on every document. One `<h1>`; heading levels
135
+ descend without skipping — pick the level from the outline, style with CSS.
136
+ Landmarks: `<header>`, `<nav>`, `<main>` (exactly one), `<footer>`. A skip link
137
+ to `<main>` on full pages.
138
+
139
+ **Focus.** Always ship a visible `:focus-visible` indicator with at least 3:1
140
+ contrast against what surrounds it. Never `outline: none` without a
141
+ replacement. Never a positive `tabindex`. `tabindex="-1"` only for
142
+ programmatic focus targets. After a route change or view swap in an SPA, move
143
+ focus deliberately (to the new `<h1>` or a `tabindex="-1"` container) and
144
+ announce it — focus otherwise stays on a destroyed node.
145
+
146
+ **Color.** Every text/background pair must meet WCAG AA in **both** light and
147
+ dark mode: 4.5:1 normal text, 3:1 large text (>=24px, or >=18.5px bold), 3:1
148
+ for UI component boundaries and meaningful graphics. Low-contrast text is the
149
+ single most common failure in the wild (83.9% of pages). When choosing or
150
+ adjusting a color, run `A11Y contrast <fg> <bg> --fix` and take a returned
151
+ candidate rather than guessing. Never encode meaning in color alone — pair it
152
+ with text, shape, or an icon.
153
+
154
+ **Motion and pointers.** Wrap non-essential animation in
155
+ `@media (prefers-reduced-motion: reduce)` and actually reduce it there. Pointer
156
+ targets are at least 24x24 CSS px, or adequately spaced (SC 2.5.8). Anything
157
+ draggable needs a single-pointer alternative — a click, a button, a field (SC
158
+ 2.5.7). No content that appears on hover unless it is dismissible, hoverable,
159
+ and persistent (SC 1.4.13).
160
+
161
+ **Dynamic content.** Async results, validation errors, toasts, and filter
162
+ counts must be announced: `role="status"` / `aria-live="polite"` (or
163
+ `role="alert"` for errors), with the live region present in the DOM *before*
164
+ the text arrives. Associate field errors via `aria-describedby` and set
165
+ `aria-invalid`. Reflect widget state in `aria-expanded`, `aria-checked`,
166
+ `aria-selected`, `aria-current` — and update it in the same handler that
167
+ changes the visuals.
168
+
169
+ **Before you save a UI file, re-read it against
170
+ [references/ai-failure-modes.md](references/ai-failure-modes.md)** — the
171
+ blacklist of failures generated code reproduces most: clickable `div`,
172
+ icon-button with no name, placeholder-as-label, skipped headings,
173
+ `outline: none`, `aria-labelledby` pointing at an id that does not exist,
174
+ `aria-hidden` on a focusable element, role without behavior, missing `lang`,
175
+ ignored reduced motion, SPA focus loss, unannounced updates, positive
176
+ `tabindex`, invented alt text.
177
+
178
+ ---
179
+
180
+ ## §2 The loop
181
+
182
+ **After any UI change, before reporting the work as done:**
183
+
184
+ 1. **Make it renderable.** Start or reuse the dev server, run the build, or
185
+ write the fragment to a file. Pick the input mode: `<url>` for a running app
186
+ (preferred — it audits what actually ships), `--file` for a static page,
187
+ `--html` for a component fragment you just generated.
188
+ 2. **Audit, including the states you just built.** A load-time scan finds
189
+ nothing inside a closed modal. Write a small interact module naming each
190
+ state your change introduced and pass it with `--interact`; each state is
191
+ driven and then audited:
192
+
193
+ ```js
194
+ // .a11y/states.mjs
195
+ export const states = {
196
+ 'settings-dialog-open': async (page) => {
197
+ await page.getByRole('button', { name: 'Settings' }).click();
198
+ },
199
+ 'signup-form-error': async (page) => {
200
+ await page.getByRole('button', { name: 'Create account' }).click();
201
+ },
202
+ 'results-loaded': async (page) => {
203
+ await page.getByLabel('Search').fill('wcag');
204
+ await page.getByRole('button', { name: 'Search' }).click();
205
+ await page.getByRole('list', { name: 'Results' }).waitFor();
206
+ },
207
+ };
208
+ ```
209
+
210
+ Opened a modal? Added an error state? Added a route, a tab panel, an
211
+ expanded menu, an async list? Each one is a state. Audit it.
212
+
213
+ ```
214
+ A11Y audit http://localhost:5173 --interact .a11y/states.mjs --json --out .a11y/run-1.json
215
+ ```
216
+
217
+ 3. **Read `findings.violations` and `findings.needsReview`.** Both. A
218
+ needsReview item is a place the engine knows it could not decide — usually
219
+ contrast over a gradient, image, or translucent layer. Resolve it by
220
+ reasoning about the source, not by ignoring it.
221
+ 4. **Fix in the source files**, never in the report or by suppressing a rule.
222
+ For contrast findings, use the report's `suggestions[]` or
223
+ `A11Y contrast <fg> <bg> --fix`, which returns both a lighter and a darker
224
+ passing candidate with hue preserved; pick the one closest to the design
225
+ intent and apply it to the design token, not to one element.
226
+ 5. **Re-audit to a new file, then diff.**
227
+
228
+ ```
229
+ A11Y audit http://localhost:5173 --interact .a11y/states.mjs --json --out .a11y/run-2.json
230
+ A11Y diff --before .a11y/run-1.json --after .a11y/run-2.json
231
+ ```
232
+
233
+ `diff` matches by fingerprint and reports FIXED / NEW / REMAINING. NEW means
234
+ your fix broke something else. If the same finding alternates between FIXED
235
+ and NEW across iterations you are oscillating — stop, read both findings
236
+ together, and change the approach instead of the value.
237
+ 6. **Repeat from 2 until `violations` is empty and `diff` reports no NEW.**
238
+ Then read the `manualChecklist` and carry it into your report (§3).
239
+
240
+ **Never declare UI work finished without a clean audit of the states you
241
+ built.** If the CLI cannot run — no Chromium, no dev server, a sandbox with no
242
+ browser — say so plainly, state that the change is unverified, and give the
243
+ exact command the user should run. Do not substitute your own reading of the
244
+ code for the audit.
245
+
246
+ For a fragment with no app around it, pass the markup inline with `--html`, or
247
+ write it to a scratch file and use `--file` (safer on any shell, and required
248
+ when the fragment contains quotes):
249
+
250
+ ```
251
+ A11Y audit --html "<button class='icon'><svg/></button>" --json --out .a11y/frag.json
252
+ A11Y audit --file .a11y/scratch/price-card.html --json --out .a11y/card.json
253
+ ```
254
+
255
+ `--headed` when you need to watch a state fail. `--no-best-practice` when
256
+ best-practice noise is drowning the real violations; the WCAG-mapped findings
257
+ are unaffected.
258
+
259
+ ---
260
+
261
+ ## §3 Honesty rules
262
+
263
+ The claims below are the ones that produced a $1,000,000 FTC penalty against an
264
+ accessibility overlay vendor in 2025 and that 800+ signatories of the
265
+ [Overlay Fact Sheet](https://overlayfactsheet.com/) exist to refute. They are
266
+ also simply not what an automated pass shows.
267
+
268
+ **Never say, about anything this loop produced:** "accessible", "fully
269
+ accessible", "WCAG compliant", "meets WCAG 2.2 AA", "passes WCAG",
270
+ "guarantees", "ensures", "certified", "100% accessible", "makes your app
271
+ accessible", "no manual testing needed", "reduces legal risk", "ADA
272
+ compliant". Never present a score or grade as a verdict.
273
+
274
+ **Say instead:** "`a11y-loop audit` found no automatically detectable failures
275
+ in the states I drove (default, dark, forced-colors, reduced-motion, 320px
276
+ reflow; states: settings-dialog-open, signup-form-error). Here is what still
277
+ needs human review."
278
+
279
+ **Always report the coverage limit with its denominator.** Automated testing
280
+ reaches a minority of WCAG failures, and the honest figures disagree because
281
+ they measure different things: Deque puts automation at **57% of issues by
282
+ volume of individual instances** across 13,000+ pages; Adrian Roselli counts
283
+ **17 of the 55** WCAG 2.2 A/AA criteria as having any ACT-approved automated
284
+ rule (31%), and not fully covered even there; accessible.org counts **7 of 55**
285
+ as reliably flagged (13%). Nine A/AA criteria cannot be meaningfully tested by
286
+ any tool. Cite one figure with its denominator, not a bare percentage.
287
+
288
+ **Always surface the `manualChecklist`.** It is a deliverable, not a
289
+ disclaimer — it names the criteria automation could not judge for the
290
+ components you actually built. Include it in your summary to the user, not just
291
+ in the JSON.
292
+
293
+ **`needsReview` findings are unresolved uncertainty, never passes.** Report
294
+ them as open questions with what you did about each.
295
+
296
+ **Mark every alt text and accessible name you wrote as DRAFT needing human
297
+ confirmation.** You cannot see the image, and hallucinated image descriptions
298
+ are a documented failure mode of generated code. `alt="decorative image"`
299
+ passes every automated check and is worse than `alt=""`. Write the draft, label
300
+ it, and ask for confirmation:
301
+
302
+ > `alt="Two people reviewing a document at a desk"` — **DRAFT**, written from
303
+ > filename and surrounding copy; please confirm it describes the actual image,
304
+ > or set `alt=""` if it is decorative.
305
+
306
+ **Recommend real assistive-technology testing before production**, and say
307
+ which is cheapest to start with: NVDA (free, Windows), VoiceOver (built into
308
+ macOS/iOS). Automated checks and your own reading cannot tell you whether the
309
+ interface is usable — only people using it can, and testing with disabled
310
+ users is what actually closes that gap. See
311
+ [references/manual-testing.md](references/manual-testing.md) for the full
312
+ not-automatable list and a keyboard script a non-expert can run in five
313
+ minutes.
314
+
315
+ **Attribute the engine.** Findings come from axe-core (MPL-2.0, Deque Systems)
316
+ and the WCAG success criteria are W3C's. Cite findings as
317
+ `SC 4.1.2 Name, Role, Value (Level A) · ACT 97a4e1 · axe: button-name` — the
318
+ success criterion is the obligation; ACT rule IDs are informative secondary
319
+ identifiers, not the source of the requirement.
320
+
321
+ ---
322
+
323
+ ## References
324
+
325
+ Read on demand; none of it costs context until you open it.
326
+
327
+ | File | Use it when |
328
+ |---|---|
329
+ | [references/wcag22-quick-ref.md](references/wcag22-quick-ref.md) | You need the exact SC number, level, contrast threshold, or what is new in WCAG 2.2 |
330
+ | [references/apg-patterns.md](references/apg-patterns.md) | You are building a dialog, tabs, accordion, menu, combobox, disclosure, radio group, switch, tooltip, or listbox |
331
+ | [references/ai-failure-modes.md](references/ai-failure-modes.md) | Before saving generated UI code, and when a finding needs a wrong-vs-right example |
332
+ | [references/manual-testing.md](references/manual-testing.md) | You are writing the §3 hand-off, or a user asks what the audit did not check |
@@ -0,0 +1,168 @@
1
+ {
2
+ "skill_name": "a11y-loop",
3
+ "schema_note": "skill-creator evals.json schema (skill_name + evals[] with id/prompt/expected_output/expectations). The non-standard `should_trigger` field is additive metadata for description tuning; the machine-readable trigger set the skill-creator run_eval.py script consumes is a bare array in trigger-evals.json alongside this file.",
4
+ "evals": [
5
+ {
6
+ "id": 1,
7
+ "should_trigger": true,
8
+ "prompt": "Create a signup page at src/signup.html with fields for full name, email, password and a country dropdown, plus a submit button. Style it with a plain stylesheet, no framework.",
9
+ "expected_output": "An accessible signup page whose generated markup was audited in a real browser and iterated to zero violations, reported without any conformance claim.",
10
+ "expectations": [
11
+ "Every input has an associated <label for> (or a wrapping <label>); no field relies on a placeholder as its label",
12
+ "Inputs carry appropriate autocomplete tokens such as name, email, current-password or new-password",
13
+ "The document has <html lang> set and a single <h1>",
14
+ "A visible :focus-visible indicator is defined in CSS and no rule sets outline: none without a replacement",
15
+ "An a11y-loop audit command was actually run against the generated page (--file or --url, not file://)",
16
+ "Any violations reported by the audit were fixed in the source file and the page was re-audited",
17
+ "The final summary does not claim the page is accessible, compliant, or that it passes WCAG",
18
+ "The final summary surfaces the manual checklist or otherwise names what still needs human review"
19
+ ]
20
+ },
21
+ {
22
+ "id": 2,
23
+ "should_trigger": true,
24
+ "prompt": "Add a Settings dialog to the React app in src/App.jsx. It should open from a gear icon button in the header and contain two toggles and a Save button.",
25
+ "expected_output": "A dialog implementing the APG modal-dialog contract, with the opened state driven and audited via --interact, not just the closed page.",
26
+ "expectations": [
27
+ "The gear icon button has a non-empty accessible name (aria-label or visually hidden text) and the icon itself is aria-hidden",
28
+ "The dialog has an accessible name via aria-labelledby or aria-label, focus moves into it on open, Escape closes it, and focus returns to the trigger on close",
29
+ "Focus is constrained to the dialog while it is open (native <dialog> showModal, inert, or an explicit trap)",
30
+ "The toggles use role=\"switch\" with aria-checked, or a native checkbox, and the state attribute is updated in the same handler as the visual change",
31
+ "An --interact module was written that opens the dialog, and the audit was run with it so the open state was audited",
32
+ "The response does not claim the dialog is accessible or compliant"
33
+ ]
34
+ },
35
+ {
36
+ "id": 3,
37
+ "should_trigger": true,
38
+ "prompt": "Build a three-tier pricing section for our marketing site: Starter, Team, Enterprise. Cards with a feature list each and a Choose plan button. Make it look modern.",
39
+ "expected_output": "A pricing section with a correct heading outline, named buttons, AA contrast in light and dark mode, and a clean audit including the 320px reflow pass.",
40
+ "expectations": [
41
+ "Heading levels descend without skipping (the plan names are not styled-down h4s under an h1) and visual size is set in CSS rather than by heading level",
42
+ "Each 'Choose plan' button has an accessible name that distinguishes which plan it selects",
43
+ "Feature lists use real list markup",
44
+ "Text and background color pairs were checked against WCAG AA, using a11y-loop contrast or the audit's contrast findings rather than being assumed",
45
+ "An a11y-loop audit was run and the 320x256 reflow pass produced no unresolved horizontal overflow",
46
+ "The final summary avoids the words compliant, guarantees, and fully accessible"
47
+ ]
48
+ },
49
+ {
50
+ "id": 4,
51
+ "should_trigger": true,
52
+ "prompt": "Our brand color is #7C9EC4 and we use it for body text links on white, and for button labels on a #F3F4F6 grey. Add a dark theme too. Update src/tokens.css.",
53
+ "expected_output": "Contrast-checked token values for light and dark themes, with concrete failing/passing ratios reported and candidates chosen from the CLI rather than guessed.",
54
+ "expectations": [
55
+ "a11y-loop contrast was run for the stated color pairs, including the --fix flag or the report suggestions, rather than the ratios being estimated",
56
+ "The reported contrast ratio for #7C9EC4 on white is identified as failing the 4.5:1 requirement for normal text",
57
+ "Replacement colors are applied to the design tokens rather than to individual elements",
58
+ "The dark theme values were contrast-checked separately, not assumed to inherit from the light theme",
59
+ "Link styling does not rely on color alone to distinguish links in body text",
60
+ "The response cites SC 1.4.3 (and 1.4.11 where UI boundaries are involved) with the ratio it measured"
61
+ ]
62
+ },
63
+ {
64
+ "id": 5,
65
+ "should_trigger": true,
66
+ "prompt": "The dashboard needs tabs: Overview, Billing, Members. Add them to src/components/Dashboard.tsx.",
67
+ "expected_output": "A tabs implementation following the APG contract, with each tab panel state audited.",
68
+ "expectations": [
69
+ "The implementation uses role=\"tablist\" / role=\"tab\" / role=\"tabpanel\" with aria-selected, aria-controls and aria-labelledby wired to ids that resolve",
70
+ "Roving tabindex is implemented: the selected tab has tabindex 0 and the others -1",
71
+ "Arrow key navigation between tabs plus Home and End are implemented, matching the APG keyboard contract",
72
+ "The apg-patterns.md reference was consulted or its contract is visibly followed rather than approximated",
73
+ "An audit was run covering more than the initially selected panel, for example via an --interact module that selects each tab",
74
+ "No conformance claim appears in the summary"
75
+ ]
76
+ },
77
+ {
78
+ "id": 6,
79
+ "should_trigger": true,
80
+ "prompt": "Run an accessibility audit on http://localhost:3000 and tell me what's wrong.",
81
+ "expected_output": "An audit run through the CLI with violations and needsReview both reported, coverage stated with a denominator, and the manual checklist surfaced.",
82
+ "expectations": [
83
+ "a11y-loop audit was run against the URL, producing JSON output",
84
+ "Both findings.violations and findings.needsReview are reported, with needsReview presented as unresolved uncertainty rather than as passes",
85
+ "Findings are cited by WCAG success criterion number, name and level",
86
+ "The coverage limitation is stated with its denominator (for example 57% of issue instances by volume, or 17 of 55 criteria having any automated rule)",
87
+ "The manual checklist is included in the response to the user, not only left in the JSON file",
88
+ "Real assistive technology testing is recommended before production",
89
+ "The response does not present a score or grade as a verdict"
90
+ ]
91
+ },
92
+ {
93
+ "id": 7,
94
+ "should_trigger": true,
95
+ "prompt": "A reviewer says the icon buttons in src/Toolbar.jsx aren't accessible. Can you check and fix them?",
96
+ "expected_output": "Accessible names added to icon-only controls, decorative icons hidden, target size checked, and the fix verified by audit rather than asserted.",
97
+ "expectations": [
98
+ "Each icon-only button receives an accessible name via aria-label or visually hidden text",
99
+ "The decorative svg or icon element is marked aria-hidden=\"true\"",
100
+ "Target size is considered against the 24x24 CSS px minimum of SC 2.5.8",
101
+ "The change was verified with an a11y-loop audit rather than only by inspection",
102
+ "Any accessible name inferred from an icon shape rather than existing text is flagged as needing human confirmation",
103
+ "SC 4.1.2 Name, Role, Value is cited"
104
+ ]
105
+ },
106
+ {
107
+ "id": 8,
108
+ "should_trigger": true,
109
+ "prompt": "Add alt text to all the images in src/pages/about.html.",
110
+ "expected_output": "Draft alt text clearly labelled as unverified, with decorative images given empty alt, and an explicit request for human confirmation.",
111
+ "expectations": [
112
+ "Every alt value the model wrote is explicitly labelled DRAFT or otherwise marked as needing human confirmation",
113
+ "The response states that the images were not seen and the descriptions are inferred from filename, context or surrounding copy",
114
+ "Images judged decorative are given alt=\"\" rather than a description or a phrase like 'decorative image'",
115
+ "The user is asked to confirm or replace each draft",
116
+ "The response does not claim the images are now accessible"
117
+ ]
118
+ },
119
+ {
120
+ "id": 9,
121
+ "should_trigger": true,
122
+ "prompt": "I fixed the contrast issues you found. Did it work, and did I break anything?",
123
+ "expected_output": "A re-audit plus a fingerprint diff against the previous report, reported as FIXED / NEW / REMAINING.",
124
+ "expectations": [
125
+ "A fresh audit was run to a new report file rather than reusing the earlier one",
126
+ "a11y-loop diff was run with --before and --after pointing at the two reports",
127
+ "The answer distinguishes FIXED, NEW and REMAINING findings",
128
+ "Any NEW findings are treated as regressions introduced by the fix and addressed rather than reported and left",
129
+ "The loop is only declared finished when violations is empty and diff reports no NEW"
130
+ ]
131
+ },
132
+ {
133
+ "id": 10,
134
+ "should_trigger": false,
135
+ "prompt": "Refactor the argument parsing in bin/deploy.js to use node:util parseArgs instead of the hand-rolled loop, and keep the existing flags working.",
136
+ "expected_output": "A straightforward CLI refactor with no accessibility work and no audit attempted.",
137
+ "expectations": [
138
+ "The refactor is completed using node:util parseArgs with the existing flags preserved",
139
+ "No a11y-loop command was run",
140
+ "No accessibility manual checklist, WCAG citation, or contrast discussion appears in the response",
141
+ "No unrequested ARIA, alt text, or label changes were introduced"
142
+ ]
143
+ },
144
+ {
145
+ "id": 11,
146
+ "should_trigger": false,
147
+ "prompt": "This query times out on the reports table. Can you rewrite it to use a window function instead of the correlated subquery? SELECT id, (SELECT COUNT(*) FROM events e WHERE e.report_id = r.id) AS n FROM reports r;",
148
+ "expected_output": "A rewritten SQL query with no accessibility content.",
149
+ "expectations": [
150
+ "A window-function rewrite of the query is provided",
151
+ "No a11y-loop command was run",
152
+ "No WCAG success criteria, contrast ratios, or accessibility checklist appear in the response"
153
+ ]
154
+ },
155
+ {
156
+ "id": 12,
157
+ "should_trigger": false,
158
+ "prompt": "There's a typo in README.md — 'recieve' should be 'receive'. Fix it.",
159
+ "expected_output": "A one-word documentation fix, nothing else.",
160
+ "expectations": [
161
+ "The typo is corrected in README.md",
162
+ "No a11y-loop command was run",
163
+ "No accessibility guidance, audit, or checklist appears in the response",
164
+ "No other files were modified"
165
+ ]
166
+ }
167
+ ]
168
+ }
@@ -0,0 +1,20 @@
1
+ [
2
+ { "query": "Create a signup page at src/signup.html with name, email, password and a country dropdown", "should_trigger": true },
3
+ { "query": "Add a Settings dialog to src/App.jsx that opens from a gear icon button in the header", "should_trigger": true },
4
+ { "query": "Build a three-tier pricing section with cards and a Choose plan button, make it look modern", "should_trigger": true },
5
+ { "query": "Our brand color is #7C9EC4 on white for body links — add a dark theme to src/tokens.css", "should_trigger": true },
6
+ { "query": "The dashboard needs tabs: Overview, Billing, Members. Add them to Dashboard.tsx", "should_trigger": true },
7
+ { "query": "Style the nav bar so it collapses into a hamburger menu on mobile", "should_trigger": true },
8
+ { "query": "Run an accessibility audit on http://localhost:3000 and tell me what's wrong", "should_trigger": true },
9
+ { "query": "A reviewer says the icon buttons in src/Toolbar.jsx aren't accessible, can you check and fix them", "should_trigger": true },
10
+ { "query": "Add alt text to all the images in src/pages/about.html", "should_trigger": true },
11
+ { "query": "Is this modal usable with a screen reader and the keyboard alone?", "should_trigger": true },
12
+ { "query": "Check whether our color palette meets WCAG AA contrast", "should_trigger": true },
13
+ { "query": "I fixed the contrast issues you found — did it work, and did I break anything?", "should_trigger": true },
14
+ { "query": "Refactor the argument parsing in bin/deploy.js to use node:util parseArgs", "should_trigger": false },
15
+ { "query": "This query times out, rewrite the correlated subquery as a window function", "should_trigger": false },
16
+ { "query": "There's a typo in README.md, 'recieve' should be 'receive'", "should_trigger": false },
17
+ { "query": "Add retry with exponential backoff to the S3 upload in workers/ingest.js", "should_trigger": false },
18
+ { "query": "Write a GitHub Actions workflow that runs the unit tests on push", "should_trigger": false },
19
+ { "query": "Why is my Express middleware running twice per request?", "should_trigger": false }
20
+ ]