Skip to content

Add accessibility skill and wp-tooling a11y runner (pa11y-ci) #34

Description

@Adi-ty

Context

WordPress developers — and the coding agents working alongside them — need a fast, local loop to find and fix accessibility (WCAG2AA) problems in a plugin or theme. pa11y is already the org's established accessibility engine (installed and configured by the setup/pa11y scaffold), but there is no local, agent-driven command that turns pa11y's output into something an agent can act on and trace back to source.

This task adds a wp-tooling a11y command that runs the consumer-installed pa11y-ci command-line tool — pa11y's local, multi-URL runner (the -ci is only the package name; running it starts no CI and reads no CI results, it just runs the pa11y checks in a headless browser) — and hands back its accessibility results as a normalized report, plus a companion Claude Code accessibility skill that maps each violation's DOM selector to the template, block, or PHP that rendered it and fixes it with consent. Running the command gives you pa11y results locally, on demand — no separate browser setup of our own.

Accessibility is a rendered-DOM concern, so this stays pure Node reusing pa11y — no WordPress ability, no MCP. The selector→source mapping is done by grepping the repo the agent already has open; the WordPress runtime is never consulted.

Expected Outcome

A wp-tooling a11y command (a library core plus a thin CLI adapter) plus a scaffolded accessibility skill. pa11y-ci is installed into the consumer by the existing setup/pa11y scaffold — it is never a dependency of @rtcamp/wp-tooling, preserving the zero-runtime-dependencies rule.

src/a11y/
    errors.js         RunnerError { code, ...details } for the runner's failure modes
    resolve-bin.js    locate the consumer-installed pa11y-ci (local / hoisted / npx) + version probe
    urls.js           resolve URLs from --url flags or the .pa11yci.json urls[] list
    normalize.js      pa11y-ci --json  →  a stable normalized report (pure, unit-testable)
    run.js            runA11y() library core + runCli() CLI adapter
    index.js          barrel exposed as @rtcamp/wp-tooling/a11y
src/cli/commands/a11y.js   { name, summary, run } — auto-discovered by the dispatcher
package.json               add "./a11y" to the exports map
tests/a11y/                normalize + urls + resolve-bin + cli specs, with fixtures

skills/accessibility/SKILL.md + evals/evals.json
scaffolds/setup/claude-skills/   templates/accessibility-SKILL.md + accessibility-evals.json,
                                 two files[] entries, and a description update
skills/README.md           list the new skill in "What's here" + the copy snippets
CHANGELOG.md               Unreleased entry

Normalized report shape (the contract the skill and tests depend on):

{
  tool: 'pa11y-ci',
  standard: 'WCAG2AA',
  summary: { urls, violations, errors, warnings, notices, passedUrls },
  results: [
    {
      url,
      violations: [
        {
          id,             // pa11y issue code, e.g. WCAG2AA.Principle1.Guideline1_1.1_1_1.H37
          wcagCriterion,  // parsed criterion, e.g. '1.1.1' (null for axe rule ids)
          impact,         // 'error' | 'warning' | 'notice'
          runner,         // 'htmlcs' | 'axe'
          message,
          selector,       // CSS selector of the offending node
          context,        // the node's HTML snippet
          domHints: { tagName, classList, idAttr, attrs }, // extracted for the grep-to-source step
        },
      ],
    },
  ],
}

CLI behaviour (wp-tooling a11y):

  • --url <url> — URL to scan; repeatable; overrides the .pa11yci.json url list.
  • --config <path> — path to the pa11y config (default .pa11yci.json).
  • --output <text|json> — output format (default text).
  • --dry-run — print the resolved binary, resolved URLs, and the exact command; run nothing.
  • Exit codes: 0 clean · 1 run failure · 2 usage error or pa11y-ci missing · 3 violations found.

Implementation guidance

Runner

  • A pure library function returns the normalized report; a runCli(argv) adapter parses flags, supports --dry-run, and emits text or json. Invoke pa11y-ci through execFileSync with piped stdio and no shell.
  • pa11y-ci exits non-zero (typically 2) when it finds violations — treat that as a successful run and read the report from the error's stdout; only a run with no parseable report is a failure (EBINFAIL).
  • Resolve the binary by walking up node_modules/.bin, then fall back to npx --no-install so it never fetches from the network; use a --version probe to drive availability and the --dry-run output. When it is missing, surface the wp-tooling add setup/pa11y install hint.
  • Parse the WCAG criterion from the HTMLCS code (…1_1_1.H371.1.1); axe rule ids that carry no criterion map to null. Extract domHints from the context HTML so the skill can grep for source without re-parsing markup.

Skill

  • Add skills/accessibility/SKILL.md (frontmatter name + description) and skills/accessibility/evals/evals.json. The SKILL.md should plan-and-announce a TODO list first, then walk a numbered workflow, and close with a hard-rules section. tests/skills/evals-json.test.js auto-discovers the directory and enforces the eval schema, so the evals file is required.
  • Skill workflow: check preconditions (.pa11yci.json present and a reachable site) → run npx wp-tooling a11y --output json → triage by criterion and impact → map each selector to its source by grepping the repo (id or distinctive class → theme templates and block render.php / save.js; then literal attribute values; else the nearest wp-block-* ancestor → that block's source) → propose the minimal fix with consent → re-run to confirm → report file:line and the before/after delta.
  • Evals (at least three, with unique numeric ids): a missing-alt violation mapped to its template; pa11y-ci absent → surface wp-tooling add setup/pa11y rather than installing; a colour-contrast violation on a dynamic block mapped via its wp-block-* ancestor.

Scaffold wiring

  • Extend the setup/claude-skills scaffold so it also copies the accessibility skill into a consumer's .claude/skills/. Add two raw: true files[] entries — templates/accessibility-SKILL.md{{skills_dir}}/accessibility/SKILL.md and templates/accessibility-evals.json{{skills_dir}}/accessibility/evals/evals.json — add those two template files, and mention the new skill in the manifest description. Both the SKILL.md and the evals are shipped.
  • Do not modify setup/pa11y, the existing skill entries, or package.json files[].

Acceptance Criteria

  • wp-tooling a11y runs the pa11y accessibility check locally (using the pa11y-ci command-line tool) and emits the normalized report in both text and json.
  • The binary is resolved from the consumer; a clear wp-tooling add setup/pa11y hint is shown when it is missing; pa11y-ci is never added to @rtcamp/wp-tooling dependencies.
  • Exit codes behave as specified (0 clean / 1 failure / 2 usage-or-missing / 3 violations) and --dry-run runs nothing.
  • Unit tests cover normalize + urls + resolve-bin + cli against fixtures; npm run lint is clean.
  • The accessibility skill (SKILL.md + evals) is added and auto-validated by tests/skills/evals-json.test.js.
  • The setup/claude-skills scaffold copies the skill (SKILL.md + evals), and the bundled scaffold-manifest tests pass.
  • skills/README.md lists the skill, and a CHANGELOG.md entry is added under ## Unreleased.

Notes

  • The skill ships both the SKILL.md and the evals into consumers. There is no packaging change — skills/ stays out of package.json files[].
  • A companion performance skill (a Lighthouse lab runner, with an optional stitch to the wp-dev-tools field web-vitals and server telemetry) was discussed and is intentionally out of scope here — it belongs in a separate task.
  • PR target: release/v1.0.0. Branch: v1.0.0/task/a11y-skill. Commit subject: feat(a11y): add wp-tooling a11y runner and accessibility skill.

Metadata

Metadata

Assignees

Labels

Priority: P1High — sprint commitmentScope: ScaffoldsScaffoldRegistry + scaffold.json schemaType: TaskSelf-contained unit of work for a milestone

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions