@softspark/ai-toolkit 4.21.0 → 4.22.1

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 (35) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/README.md +40 -28
  3. package/app/.claude-plugin/plugin.json +1 -1
  4. package/app/agents/code-reviewer.md +32 -15
  5. package/app/skills/a11y-validate/SKILL.md +61 -179
  6. package/app/skills/a11y-validate/reference/scanner-categories.md +174 -0
  7. package/app/skills/brainstorm/SKILL.md +174 -0
  8. package/app/skills/brand-voice/SKILL.md +1 -1
  9. package/app/skills/ci/SKILL.md +2 -1
  10. package/app/skills/debug/SKILL.md +3 -2
  11. package/app/skills/deploy/SKILL.md +1 -1
  12. package/app/skills/explore/SKILL.md +1 -1
  13. package/app/skills/explore/scripts/visualize.py +15 -1
  14. package/app/skills/fix/SKILL.md +3 -3
  15. package/app/skills/hipaa-validate/SKILL.md +25 -221
  16. package/app/skills/hipaa-validate/reference/scanner-categories.md +224 -0
  17. package/app/skills/migrate/SKILL.md +1 -1
  18. package/app/skills/pr/SKILL.md +2 -2
  19. package/app/skills/review/SKILL.md +50 -5
  20. package/app/skills/rollback/SKILL.md +1 -1
  21. package/app/skills/seo-validate/SKILL.md +63 -309
  22. package/app/skills/seo-validate/reference/scanner-categories.md +304 -0
  23. package/app/surface.json +296 -0
  24. package/benchmarks/ecosystem-doctor-snapshot.json +15 -19
  25. package/kb/procedures/post-release-testing-sop.md +92 -3
  26. package/kb/procedures/release-preparation-sop.md +44 -2
  27. package/kb/reference/architecture-overview.md +3 -3
  28. package/kb/reference/skills-catalog.md +10 -14
  29. package/llms-full.txt +150 -22
  30. package/manifest.json +3 -3
  31. package/package.json +6 -3
  32. package/scripts/check_split.py +493 -0
  33. package/scripts/surface_manifest.py +246 -0
  34. package/scripts/sync_badges.py +133 -0
  35. package/scripts/validate.py +118 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,109 @@ Versioning follows [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ---
9
9
 
10
+ ## v4.22.1 — nine skills, not four, could not find their own scripts (2026-08-06)
11
+
12
+ v4.22.0 claimed to fix four skills whose documented script path never resolved.
13
+ The first real run of the post-release SOP found five more, plus a wrong
14
+ interpreter and a script that crashed on `--help`. The v4.22.0 fix had grepped
15
+ for one spelling of the bug and missed the others.
16
+
17
+ ### Fixed
18
+
19
+ - **Five more skills ran their own script through a path that cannot resolve
20
+ after install**: `brand-voice` (repo-relative `app/skills/.../measure.py`),
21
+ `ci`, `deploy`, `migrate` and `rollback` (cwd-relative `scripts/*.py`). Four
22
+ further example lines in `debug`, `fix`, `pr` and `review` had the same defect
23
+ in a secondary snippet while the primary invocation was already correct.
24
+ - **`rollback` ran a Python file through `bash`.** `bash scripts/rollback_info.py`
25
+ was wrong twice over.
26
+ - **`explore` used `python` rather than `python3`**, which is missing or Python 2
27
+ on many systems.
28
+ - **`explore`'s `visualize.py` crashed with a traceback on `--help`**, treating
29
+ the flag as a directory to scan. It now prints usage, and reports a JSON error
30
+ with exit 1 for a path that does not exist.
31
+
32
+ ### Added
33
+
34
+ - **`validate.py` fails the build on the whole class.**
35
+ `_validate_skill_script_invocations` checks every documented command that runs
36
+ a skill-owned script: the path must go through `${CLAUDE_SKILL_DIR}`, and the
37
+ interpreter must match the file. `python` instead of `python3` is a warning.
38
+ Repo-level scripts such as `scripts/validate.py` are correctly ignored, and the
39
+ frontmatter `scripts:` list stays relative because it declares rather than runs.
40
+ - **Post-release SOP phases 4b and 4c** (`post-release-testing-sop.md` v1.1.0):
41
+ run every skill's documented invocation from the installed copy, and prove
42
+ scanner-wrapping skills actually scan a fixture with known defects. Phase 4b
43
+ found every defect in this release on its first run.
44
+
45
+ ---
46
+
47
+ ## v4.22.0 — the public surface is a test, not a promise (2026-08-06)
48
+
49
+ ### Added
50
+
51
+ - **`scripts/surface_manifest.py` + `app/surface.json`** — 275 entries of public
52
+ surface (skills, agents, frontmatter fields, CLI commands, hook scripts and
53
+ events, KB categories, plugin packs) snapshotted and checked in `npm test`.
54
+ Removing any of them fails the build; adding is free, because a surface nobody
55
+ has installed has no users to break. The manifest is deliberately not
56
+ auto-regenerated — if it were, deleting a skill would delete its entry in the
57
+ same breath and the check would prove nothing.
58
+ - **`BACKWARD_COMPATIBILITY.md`** — which surfaces are load-bearing, what may
59
+ change freely, and the deprecation path when a break is unavoidable.
60
+ - **`DECISIONS.md`** — why things are the way they are, including what was
61
+ rejected and what may yet turn out to be ceremony.
62
+ - **`scripts/check_split.py`** — five gates proving a `SKILL.md` → `reference/`
63
+ refactor lost nothing: fenced code lines survive, removed prose is traceable,
64
+ always-loaded sections stay in the body, `description` is byte-identical, and
65
+ every relative link still resolves. It caught a heading corrupted inside a
66
+ fenced example on its first real use.
67
+ - **`scripts/sync_badges.py`** — README count badges derived from the tree and
68
+ rewritten inside `generate:all`, before `validate.py --strict` reads them.
69
+ Hand-maintained badges broke the build three times in one session.
70
+ - **`/brainstorm`** — the first planning skill allowed to end in "do not build
71
+ this". Prices the zero option as a real candidate and sends the conclusion to a
72
+ separate challenger agent before routing anywhere.
73
+ - **Skill body budget in `validate.py`** — error above 20,000 bytes, warn above
74
+ 18,000, with the current headroom printed on every run and a ratchet step in
75
+ the release SOP.
76
+ - **Hook events `MessageDisplay` and `DirectoryAdded`** added to
77
+ `VALID_HOOK_EVENTS`. Both are documented Claude Code events; without them
78
+ `validate.py` rejected a user's hook on either as an invalid name.
79
+
80
+ ### Changed
81
+
82
+ - **`a11y-validate` and `seo-validate` now run the scanners they ship.**
83
+ `a11y-scanner.py` (643 lines) and `seo-scanner.py` (553 lines) were on disk and
84
+ invoked by nothing — the model was told to grep the pattern tables by hand.
85
+ Both skills now run their script for a deterministic baseline, then take an
86
+ explicit manual pass, with a measured coverage table in the body saying which
87
+ part is which. `a11y-scanner.py` covers 13 of ~50 documented WCAG success
88
+ criteria; `seo-scanner.py` covers 8 of 10 categories and none of category 7
89
+ (Rendering & Crawlability) or 10 (Topical Authority).
90
+ - **Review severity is one four-tier scale.** `/review` asked for
91
+ `Critical/Major/Minor/Nit` while the `code-reviewer` agent it delegates to
92
+ reported `CRITICAL/HIGH/MEDIUM/LOW/INFO`. The agent won at runtime, so the
93
+ skill never received the format it specified. Both now use
94
+ `blocker/major/minor/nit` with a mechanical verdict rule, and severity (impact)
95
+ is explicitly separated from confidence (certainty).
96
+ - **`/review`, `/ci` and `/debug` collect every failing signal before judging**
97
+ instead of stopping at the first red one.
98
+ - **Three validator skills split into `reference/`**: `hipaa-validate`
99
+ 23,905 → 10,433 B, `a11y-validate` 25,306 → 13,269 B, `seo-validate`
100
+ 35,066 → 13,430 B. 47 KB off the hot path.
101
+ - **`a11y-validate` and `seo-validate` gained `## Gotchas` and
102
+ `## When NOT to Use`**, written from the traps the scanner wiring introduced.
103
+
104
+ ### Fixed
105
+
106
+ - **Four skills invoked their scripts through a path that never resolved.**
107
+ `debug`, `fix`, `pr` and `hipaa-validate` used `$(dirname "$0")`, which expands
108
+ to the shell's directory, not the skill's. All four now use
109
+ `${CLAUDE_SKILL_DIR}`, matching the other 14.
110
+
111
+ ---
112
+
10
113
  ## v4.21.0 — one KB taxonomy, in one place (2026-07-28)
11
114
 
12
115
  ### Fixed
package/README.md CHANGED
@@ -1,34 +1,46 @@
1
1
  # ai-toolkit
2
2
 
3
- > Professional-grade AI coding toolkit with multi-platform support. Machine-enforced safety, 108 skills, 44 agents, expanded lifecycle hooks, persona presets, experimental opt-in plugin packs, and benchmark tooling — works with Claude Code, Claude Chat/Cowork, Cursor, Devin, Copilot, Gemini, Cline, Roo/Zoo Code, Aider, Augment, Google Antigravity, Codex CLI, and opencode.
3
+ > Professional-grade AI coding toolkit with multi-platform support. Machine-enforced safety, 109 skills, 44 agents, expanded lifecycle hooks, persona presets, experimental opt-in plugin packs, and benchmark tooling — works with Claude Code, Claude Chat/Cowork, Cursor, Devin, Copilot, Gemini, Cline, Roo/Zoo Code, Aider, Augment, Google Antigravity, Codex CLI, and opencode.
4
4
 
5
5
  [![CI](https://github.com/softspark/ai-toolkit/actions/workflows/ci.yml/badge.svg)](https://github.com/softspark/ai-toolkit/actions/workflows/ci.yml)
6
6
  [![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
7
- [![Skills](https://img.shields.io/badge/skills-108-brightgreen)](app/skills/)
7
+ [![Skills](https://img.shields.io/badge/skills-109-brightgreen)](app/skills/)
8
8
  [![Agents](https://img.shields.io/badge/agents-44-blue)](app/agents/)
9
- [![Tests](https://img.shields.io/badge/tests-1473%20passing-success)](tests/)
10
-
11
- ## What's New in v4.21.0
12
-
13
- v4.21.0 makes the KB taxonomy one list instead of three, and teaches
14
- `validate.py` to enforce it.
15
-
16
- - **The taxonomy is eight categories**: `reference`, `howto`, `procedures`,
17
- `troubleshooting`, `best-practices`, `decisions`, `runbooks`, `planning`.
18
- `decisions` and `runbooks` were missing while the `kb-migration` SOP had been
19
- telling people to create those directories for months — a correctly-filed ADR
20
- failed validation.
21
- - **The `documentation-standards` skill no longer contradicts itself.** Its
22
- frontmatter said "5-category", its table listed five, and a line below named
23
- six. Author and validator disagreed, and only the validator got a vote.
24
- - **`section:` is documented as a legacy alias for `category:`**, and
25
- `validate.py` rejects a document carrying both with different values that
26
- document is indexed twice and found once.
27
- - **A document's category must name its directory**, checked only for
28
- directories that are category names: `kb/history/completed/` is a lifecycle
29
- location, not a type, and a finished plan filed there is still `planning`.
30
- - **`procedures` and `runbooks` stopped describing each other.** A procedure is
31
- followed by a person; a runbook is run against a live system.
9
+ [![Tests](https://img.shields.io/badge/tests-1513%20passing-success)](tests/)
10
+
11
+ ## What's New in v4.22.1
12
+
13
+ **v4.22.1** nine skills, not four, could not find their own scripts. v4.22.0
14
+ grepped for one spelling of the bug and missed five more, a `.py` file run
15
+ through `bash`, and a script that crashed on `--help`. `validate.py` now fails
16
+ the build on the entire class, and the post-release SOP runs every skill's
17
+ documented command from the installed copy — which is how these were found.
18
+
19
+ ## What's New in v4.22.0
20
+
21
+ v4.22.0 turns compatibility promises into tests, and makes two validator skills
22
+ actually run the scanners they ship.
23
+
24
+ - **The public surface is checked, not documented.** `app/surface.json` pins 275
25
+ entries skills, agents, frontmatter fields, CLI commands, hook scripts and
26
+ events, KB categories, plugin packs. Removing any of them fails `npm test`.
27
+ Adding is free: a surface nobody has installed has no users to break.
28
+ `BACKWARD_COMPATIBILITY.md` says which surfaces are load-bearing and how to
29
+ deprecate one properly.
30
+ - **`a11y-validate` and `seo-validate` shipped scanners that nothing invoked.**
31
+ 1,196 lines of working Python sat on disk while the model was told to grep the
32
+ pattern tables by hand. Both now run their script for a deterministic baseline,
33
+ then take an explicit manual pass — with a measured coverage table saying which
34
+ criteria the script handles and which are yours.
35
+ - **`/brainstorm`** — the first planning skill allowed to conclude "do not build
36
+ this". Every other one produces an artifact; a pipeline that cannot say no is a
37
+ yes-machine.
38
+ - **Skill bodies have a budget.** Error above 20,000 bytes, warn above 18,000,
39
+ headroom printed every run. Three validator skills were split into
40
+ `reference/`, taking 47 KB off the hot path, with `scripts/check_split.py`
41
+ proving nothing was lost.
42
+ - **Four skills invoked their scripts through a path that never resolved.**
43
+ `$(dirname "$0")` expands to the shell's directory, not the skill's.
32
44
 
33
45
  See [CHANGELOG.md](CHANGELOG.md) for full history.
34
46
 
@@ -60,7 +72,7 @@ ai-toolkit install
60
72
  npx @softspark/ai-toolkit install
61
73
  ```
62
74
 
63
- **That's it.** Claude Code picks up 108 skills, 44 agents, quality hooks, and the safety constitution automatically.
75
+ **That's it.** Claude Code picks up 109 skills, 44 agents, quality hooks, and the safety constitution automatically.
64
76
 
65
77
  **Windows:** WSL is the recommended runtime. Native Windows works when Git Bash is available for hook scripts; dependency hints cover `winget`, Chocolatey, and Scoop. See [Windows Support](kb/reference/windows-support.md).
66
78
 
@@ -149,7 +161,7 @@ See [CLI Reference](kb/reference/cli-reference.md) for all commands and options.
149
161
  | Component | Count | Description |
150
162
  |-----------|-------|-------------|
151
163
  | `skills/` (task) | 32 | Slash commands: `/commit`, `/build`, `/deploy`, `/test`, `/mcp-builder`, ... |
152
- | `skills/` (hybrid) | 30 | Slash commands with agent knowledge base |
164
+ | `skills/` (hybrid) | 31 | Slash commands with agent knowledge base |
153
165
  | `skills/` (knowledge) | 46 | Domain knowledge auto-loaded by agents (includes 13 `<lang>-rules` skills) |
154
166
  | `agents/` | 44 | Specialized agents across 10 categories |
155
167
  | `hooks/` | 28 entries / 14 events + statusLine | Quality gates, path safety, prompt governance, loop guard, session lifecycle |
@@ -166,7 +178,7 @@ See [CLI Reference](kb/reference/cli-reference.md) for all commands and options.
166
178
  ai-toolkit/
167
179
  ├── app/
168
180
  │ ├── agents/ # 44 agent definitions
169
- │ ├── skills/ # 108 skills (task / hybrid / knowledge)
181
+ │ ├── skills/ # 109 skills (task / hybrid / knowledge)
170
182
  │ ├── rules/ # Source rules synced into Claude/editor rule files
171
183
  │ ├── hooks/ # Hook scripts (29 entries, 14 lifecycle events)
172
184
  │ ├── claude-app/ # Generated Chat/Cowork plugin rules, hooks, instructions
@@ -3,7 +3,7 @@
3
3
  "name": "ai-toolkit",
4
4
  "displayName": "AI Toolkit",
5
5
  "description": "Professional-grade engineering skills, agents, rules, and lifecycle guardrails for Claude Code, Claude Chat, and Cowork.",
6
- "version": "4.21.0",
6
+ "version": "4.22.1",
7
7
  "author": {
8
8
  "name": "SoftSpark",
9
9
  "url": "https://github.com/softspark"
@@ -61,7 +61,7 @@ Before reviewing, gather context using available tools:
61
61
 
62
62
  ## Review Checklist
63
63
 
64
- ### Security (CRITICAL)
64
+ ### Security (check first)
65
65
  - [ ] No hardcoded secrets or credentials
66
66
  - [ ] Input validation on all user data
67
67
  - [ ] Output encoding for XSS prevention
@@ -86,13 +86,29 @@ Before reviewing, gather context using available tools:
86
86
 
87
87
  ## Severity Levels
88
88
 
89
- | Level | Description | Action |
90
- |-------|-------------|--------|
91
- | 🔴 CRITICAL | Security vulnerability, data exposure | Block deployment |
92
- | 🟠 HIGH | Major bug, significant risk | Must fix before merge |
93
- | 🟡 MEDIUM | Code quality issue | Should fix |
94
- | 🟢 LOW | Suggestion, improvement | Nice to have |
95
- | ℹ️ INFO | Note, observation | FYI |
89
+ Four tiers, identical to the `review` skill. This agent backs that skill — the two
90
+ must never report on different scales.
91
+
92
+ | Tier | Description | Merge impact |
93
+ |------|-------------|--------------|
94
+ | `blocker` | Security vulnerability, data exposure, data loss, money | Blocks merge, no exceptions |
95
+ | `major` | Real defect that will bite in production | Blocks merge unless waived in writing |
96
+ | `minor` | Code quality issue worth fixing | Does not block |
97
+ | `nit` | Suggestion, taste, polish | Does not block |
98
+
99
+ **Verdict rule** — mechanical, not impressionistic:
100
+
101
+ - any `blocker` → `rejected`
102
+ - any `major` without a documented waiver (who waived it, why, what the follow-up is) → `rejected`
103
+ - only `minor` / `nit`, or majors that are all waived → `approved_with_changes`
104
+ - nothing above `nit` → `approved`
105
+
106
+ ## Collect All Signals Before Judging
107
+
108
+ Gather every failing signal — merge conflict, red CI, lint failure — record each as
109
+ a `blocker` finding, then review the change in full anyway. Do not end the run on
110
+ the first red signal: the tracker already showed the author that, and the finding
111
+ they have not seen yet is the one worth the cycle.
96
112
 
97
113
  ## Output Format
98
114
 
@@ -102,19 +118,20 @@ agent: code-reviewer
102
118
  status: completed
103
119
  findings:
104
120
  security:
105
- - "🔴 CRITICAL: Hardcoded API key in config.py:42"
106
- - "🟢 PASS: No SQL injection vulnerabilities"
121
+ - "blocker: Hardcoded API key in config.py:42"
122
+ - "pass: No SQL injection vulnerabilities"
107
123
  quality:
108
- - "🟡 MEDIUM: Function exceeds 50 lines - consider splitting"
109
- - "🟢 PASS: Error handling comprehensive"
124
+ - "minor: Function exceeds 50 lines - consider splitting"
125
+ - "pass: Error handling comprehensive"
110
126
  performance:
111
- - "🟠 HIGH: N+1 query in get_users() - add eager loading"
112
- approval: approved_with_changes | approved | rejected
127
+ - "major: N+1 query in get_users() - add eager loading"
128
+ approval: rejected # 1 blocker present — verdict rule, clause 1
129
+ verdict_reason: "rejected — 1 blocker (config.py:42), 1 major (get_users)"
113
130
  kb_references:
114
131
  - kb/best-practices/security-checklist.md
115
132
  next_agent: devops-implementer | infrastructure-validator
116
133
  instructions: |
117
- Fix CRITICAL and HIGH issues before proceeding
134
+ Fix every blocker and every unwaived major before proceeding
118
135
  ---
119
136
  ```
120
137
 
@@ -62,9 +62,10 @@ Complements `/seo-validate` (which only covers SEO-a11y overlap shallowly). Use
62
62
  ## What This Command Does
63
63
 
64
64
  1. **Detect framework** from `package.json`, `pubspec.yaml`, and entry HTML.
65
- 2. **Scan the codebase** using `Grep` / `Glob` / `Read` against framework-aware patterns per category in scope.
66
- 3. **Interpret findings** with specific fixes tied to the detected framework.
67
- 4. **Report** findings sorted by severity with WCAG / EN 301 549 citations.
65
+ 2. **Run the scanner script** for a deterministic baseline over 13 WCAG success criteria.
66
+ 3. **Extend the scan by hand** using `Grep` / `Glob` / `Read` against framework-aware patterns for everything the script does not cover.
67
+ 4. **Interpret findings** with specific fixes tied to the detected framework.
68
+ 5. **Report** findings sorted by severity with WCAG / EN 301 549 citations.
68
69
 
69
70
  ## Steps
70
71
 
@@ -90,11 +91,27 @@ Run detection before scanning. Same logic as `/seo-validate` plus mobile entries
90
91
 
91
92
  Also detect a11y libraries: `@react-aria/*`, `@reach/*`, `@angular/cdk/a11y`, `vue-a11y`, `svelte-a11y`, `react-axe`, `axe-core`. Their presence is INFO.
92
93
 
93
- ### Step 2: Run Category Scans
94
+ ### Step 2: Run the Scanner Script
94
95
 
95
- For each category in `--scope`, apply the pattern set below using `Grep` + `Read`. Patterns adapt to the detected framework.
96
+ ```bash
97
+ python3 ${CLAUDE_SKILL_DIR}/scripts/a11y-scanner.py [path] [--severity high|warn|info|all] [--output json|text]
98
+ ```
99
+
100
+ Deterministic regex checks over 13 WCAG success criteria — images, headings,
101
+ language, forms, keyboard, focus, colour contrast, media, ARIA and target size.
102
+ No framework awareness and no heuristics: what it reports is real, what it misses
103
+ is Step 3's job. Exit code is non-zero when HIGH findings exist.
104
+
105
+ ### Step 3: Extend the Scan by Hand
96
106
 
97
- ### Step 3: Interpret and Enrich
107
+ Read [reference/scanner-categories.md](reference/scanner-categories.md) in full,
108
+ then work every category in `--scope` with `Grep` + `Read`, skipping only the
109
+ success criteria Step 2 already reported. Patterns adapt to the detected framework.
110
+
111
+ The coverage table under [Scanner Reference](#scanner-reference) says which criteria
112
+ Step 2 handles per category. Everything else in the category is yours.
113
+
114
+ ### Step 4: Interpret and Enrich
98
115
 
99
116
  For each finding:
100
117
  1. **Read the flagged file/lines** to confirm the match.
@@ -102,186 +119,34 @@ For each finding:
102
119
  3. **Mark confidence** — `definitive` for regex matches, `heuristic` for co-occurrence / absence / target-size estimation.
103
120
  4. **Skip false positives** when context shows the concern is addressed (e.g., aria-label set via intl translation key).
104
121
 
105
- ### Step 4: Report
122
+ ### Step 5: Report
106
123
 
107
124
  Present findings sorted by severity (HIGH → WARN → INFO), then file path.
125
+ State which findings came from the script and which from the manual pass — a reader
126
+ needs to know how much of the result is deterministic.
108
127
 
109
128
  ---
110
129
 
111
130
  ## Scanner Reference
112
131
 
113
- ### Category 1: Semantic Structure & Landmarks
114
-
115
- WCAG 1.3.1 (Info and Relationships), 2.4.1 (Bypass Blocks), 2.4.6 (Headings and Labels), 3.1.1 (Language of Page), 3.1.2 (Language of Parts).
116
-
117
- | Pattern | Severity | Confidence | Description |
118
- |---------|----------|------------|-------------|
119
- | `<html>` missing `lang` attribute | HIGH | definitive | WCAG 3.1.1 |
120
- | Mixed-language content without `<span lang="...">` wrapper (heuristic: non-Latin characters in otherwise-Latin content) | WARN | heuristic | WCAG 3.1.2 |
121
- | Page/route component with >1 `<h1>` | WARN | heuristic | WCAG 1.3.1 |
122
- | Heading level skip (h1 h3 without h2) | WARN | heuristic | WCAG 1.3.1 |
123
- | No landmark roles / semantic elements (`<main>`, `<nav>`, `<header>`, `<footer>`) | WARN | heuristic | WCAG 1.3.1, 2.4.1 |
124
- | `role="presentation"` / `role="none"` on semantic element | WARN | definitive | Strips meaning; misuse of ARIA |
125
- | Multiple `<main>` per page | HIGH | definitive | WCAG 1.3.1 only one `<main>` allowed |
126
-
127
- ### Category 2: Text Alternatives & Non-Text Content
128
-
129
- WCAG 1.1.1 (Non-text Content).
130
-
131
- | Pattern | Severity | Confidence | Description |
132
- |---------|----------|------------|-------------|
133
- | `<img>` without `alt` attribute | HIGH | definitive | WCAG 1.1.1 — required even if empty |
134
- | `<img alt="">` on informational image (heuristic: image inside `<article>`, `<figure>`, or with adjacent caption) | WARN | heuristic | Empty alt only for decorative |
135
- | `<img alt="image">` / `<img alt="photo">` / `<img alt="picture">` (redundant/meaningless) | WARN | definitive | Alt should describe content |
136
- | `<svg>` without `<title>` + `role="img"` + `aria-label`, used in interactive context | WARN | heuristic | Inline SVG needs alternative |
137
- | Icon font (`<i class="fa-...">`, `<span class="material-icons">`) without `aria-label` or text alternative | WARN | definitive | WCAG 1.1.1 |
138
- | `<img>` used for text content (heuristic: `alt` contains a full sentence like "Click here to...") | WARN | heuristic | WCAG 1.4.5 Images of Text |
139
- | Complex image (`<img>` with `src` matching `chart|graph|diagram|infographic`) without long description (`aria-describedby` or `longdesc` or linked description) | WARN | heuristic | WCAG 1.1.1 for complex content |
140
-
141
- ### Category 3: Keyboard & Focus
142
-
143
- WCAG 2.1.1 (Keyboard), 2.1.2 (No Keyboard Trap), 2.4.3 (Focus Order), 2.4.7 (Focus Visible).
144
-
145
- | Pattern | Severity | Confidence | Description |
146
- |---------|----------|------------|-------------|
147
- | `tabindex` value >0 (positive) | HIGH | definitive | WCAG 2.4.3 — breaks natural tab order |
148
- | `tabindex="-1"` on natively interactive element (`<button>`, `<a href>`, `<input>`, etc.) | WARN | definitive | Removes from tab order |
149
- | `outline: none` or `outline: 0` on focusable selector without `:focus-visible` replacement | HIGH | definitive | WCAG 2.4.7 |
150
- | `onClick` / `onKeyDown` handler on `<div>` / `<span>` without `role="button"` + `tabindex="0"` + keydown handler for Enter/Space | HIGH | heuristic | WCAG 2.1.1 — not keyboard-accessible |
151
- | No skip link (`<a href="#main">`, `<a href="#content">`) on page with navigation | WARN | heuristic | WCAG 2.4.1 Bypass Blocks |
152
- | Potential keyboard trap: `event.preventDefault()` / `event.stopPropagation()` in keydown handler on modal/dialog without Escape handling | WARN | heuristic | WCAG 2.1.2 |
153
- | Custom dropdown / combobox without `aria-expanded` + `aria-haspopup` + keyboard handlers | WARN | heuristic | WAI-ARIA Authoring Practices |
154
- | `autofocus` on page load on non-critical input (distracts keyboard users, moves focus unexpectedly) | WARN | definitive | Confuses assistive tech |
155
- | `contenteditable="true"` without `aria-label` / `aria-labelledby` | WARN | definitive | WCAG 4.1.2 |
156
-
157
- ### Category 4: Color, Contrast & Visual Cues
158
-
159
- WCAG 1.4.1 (Use of Color), 1.4.3 (Contrast Minimum), 1.4.11 (Non-text Contrast).
160
-
161
- **Static analysis limitation**: actual contrast ratios depend on the CSS cascade, custom properties, theme switching, and background images. The skill flags patterns where contrast is AT RISK; pair with runtime tools (axe-core, Lighthouse) for definitive measurement.
162
-
163
- | Pattern | Severity | Confidence | Description |
164
- |---------|----------|------------|-------------|
165
- | Hardcoded foreground+background color pairs in CSS where computed contrast is <4.5:1 (normal) or <3:1 (large text) | WARN | heuristic | WCAG 1.4.3 — verify at runtime |
166
- | Link inside body text without underline/border AND only `color` distinguishing it | WARN | heuristic | WCAG 1.4.1 — color-only signalling |
167
- | Error/required field indicated only by red color (no icon, text, or shape) | WARN | heuristic | WCAG 1.4.1 |
168
- | Required form field marked only with `*` character without `aria-required="true"` + text explanation | WARN | definitive | WCAG 1.4.1 + 3.3.2 |
169
- | CSS uses `color: red`/`color: green` as sole signal (success vs error) | WARN | heuristic | WCAG 1.4.1 |
170
- | Focus indicator with <3:1 contrast against background (heuristic from color values) | WARN | heuristic | WCAG 1.4.11 |
171
- | Button/input border color with <3:1 contrast against adjacent color | WARN | heuristic | WCAG 1.4.11 |
172
- | CSS `text-shadow`/`opacity` on body text reducing effective contrast | INFO | heuristic | May affect 1.4.3 |
173
-
174
- ### Category 5: Forms, Labels & Errors
175
-
176
- WCAG 1.3.5 (Identify Input Purpose), 3.3.1 (Error Identification), 3.3.2 (Labels or Instructions), 3.3.3 (Error Suggestion), 4.1.2 (Name, Role, Value).
177
-
178
- | Pattern | Severity | Confidence | Description |
179
- |---------|----------|------------|-------------|
180
- | `<input>` / `<select>` / `<textarea>` without `<label for="...">` AND without `aria-label` / `aria-labelledby` | HIGH | heuristic | WCAG 3.3.2, 4.1.2 |
181
- | `<label>` without `for` attribute (implicit association only works if input is a child) | WARN | definitive | WCAG 3.3.2 |
182
- | `<input type="email"/tel/name/password/address">` without `autocomplete` attribute | WARN | definitive | WCAG 1.3.5 |
183
- | Missing `autocomplete="one-time-code"` on OTP input with `inputmode="numeric"` | INFO | definitive | Improves user experience |
184
- | Radio / checkbox group without `<fieldset>` + `<legend>` | WARN | heuristic | WCAG 1.3.1 |
185
- | Error messages displayed visually but not linked via `aria-describedby` to the input | WARN | heuristic | WCAG 3.3.1 |
186
- | `required` attribute without accompanying `aria-required="true"` (belt-and-suspenders for assistive tech consistency) | INFO | definitive | WCAG 4.1.2 (modern SR handle `required` but legacy may not) |
187
- | Error uses `role="alert"` without being updated dynamically (static alert on page load) | INFO | heuristic | WCAG 4.1.3 |
188
- | Placeholder used as label (no visible label, only `placeholder`) | WARN | heuristic | WCAG 3.3.2 — placeholder disappears on focus |
189
- | `<input type="email">` without `inputmode="email"` (mobile UX) | INFO | definitive | EN 301 549 mobile |
190
-
191
- ### Category 6: Media (Audio, Video, Embeds)
192
-
193
- WCAG 1.2.1–1.2.5 (Captions, audio description, sign language), 1.4.2 (Audio Control).
194
-
195
- **EAA is specifically strict about media** — video without captions is a common legal-risk finding.
196
-
197
- | Pattern | Severity | Confidence | Description |
198
- |---------|----------|------------|-------------|
199
- | `<video>` without `<track kind="captions">` child (or `<track kind="subtitles">` for foreign-language) | HIGH | definitive | WCAG 1.2.2 — EAA legal risk |
200
- | `<video>` without transcript link or `<track kind="descriptions">` | WARN | heuristic | WCAG 1.2.3 / 1.2.5 |
201
- | `<audio>` without transcript link or `<track kind="captions">` | HIGH | definitive | WCAG 1.2.1 |
202
- | `<video autoplay>` without `muted` | HIGH | definitive | WCAG 1.4.2 — auto-playing audio |
203
- | `<video autoplay loop>` running >5 seconds without pause control | WARN | heuristic | WCAG 1.4.2, 2.2.2 |
204
- | YouTube/Vimeo embed URL without `cc_load_policy=1` or equivalent CC parameter | INFO | definitive | Platform-dependent captioning |
205
- | YouTube embed via `<iframe src="https://www.youtube.com/embed/...">` without accessibility enhancements | INFO | definitive | Note: platform controls most a11y |
206
- | Live media without real-time caption indication | WARN | heuristic | WCAG 1.2.4 |
207
- | Background video (hero section) without pause button in DOM | WARN | heuristic | WCAG 2.2.2 |
208
-
209
- ### Category 7: ARIA, Live Regions & Dynamic Content
210
-
211
- WCAG 4.1.2 (Name, Role, Value), 4.1.3 (Status Messages).
212
-
213
- | Pattern | Severity | Confidence | Description |
214
- |---------|----------|------------|-------------|
215
- | `aria-hidden="true"` on focusable element | HIGH | definitive | Creates orphaned focus — serious a11y bug |
216
- | `role="button"` on native `<button>` (redundant ARIA) | WARN | definitive | ARIA Authoring: avoid redundant roles |
217
- | `role="link"` on `<a href>` / `role="heading"` on `<h1–h6>` (redundant ARIA) | WARN | definitive | Same |
218
- | Conflicting roles (`<button role="link">`, `<a role="button">`) | WARN | definitive | WAI-ARIA — wrong role |
219
- | Custom toggle (disclosure, menu, accordion) without `aria-expanded` + `aria-controls` | WARN | heuristic | WAI-ARIA |
220
- | `aria-labelledby` referencing non-existent ID | HIGH | heuristic | Broken reference |
221
- | `aria-describedby` referencing non-existent ID | HIGH | heuristic | Broken reference |
222
- | `aria-live` region without `role="status"` / `role="alert"` AND async updates in component (heuristic) | WARN | heuristic | WCAG 4.1.3 |
223
- | Toast/notification component without `role="status"` or `role="alert"` | WARN | heuristic | WCAG 4.1.3 |
224
- | Modal / dialog without `role="dialog"` + `aria-modal="true"` + focus trap | WARN | heuristic | WAI-ARIA Authoring Practices |
225
- | Tabs without proper roles (`role="tablist"` + `role="tab"` + `role="tabpanel"`) | WARN | heuristic | WAI-ARIA Authoring Practices |
226
-
227
- ### Category 8: Motion, Target Size, Mobile & EAA Docs
228
-
229
- #### 8a. Motion & Animation
230
-
231
- WCAG 2.2.2 (Pause, Stop, Hide), 2.3.3 (Animation from Interactions — AAA but EAA-recommended).
232
-
233
- | Pattern | Severity | Confidence | Description |
234
- |---------|----------|------------|-------------|
235
- | CSS animation / transition / transform without matching `@media (prefers-reduced-motion: reduce)` override | WARN | heuristic | WCAG 2.3.3 |
236
- | JS animation library (GSAP, framer-motion, anime.js) without `matchMedia('(prefers-reduced-motion: reduce)')` check | WARN | heuristic | WCAG 2.3.3 |
237
- | Parallax scrolling without opt-out | WARN | heuristic | WCAG 2.3.3 |
238
- | Infinite animation (CSS `animation: name infinite`) on content element without pause control | WARN | heuristic | WCAG 2.2.2 |
239
- | `<marquee>` / `<blink>` (deprecated) | HIGH | definitive | WCAG 2.2.2 |
240
-
241
- #### 8b. Target Size (WCAG 2.2 Minimum AA 2.5.8 + EN 301 549)
242
-
243
- | Pattern | Severity | Confidence | Description |
244
- |---------|----------|------------|-------------|
245
- | Interactive target with declared size <24×24 px (heuristic from CSS: `width`/`height`/`padding` on buttons/links/inputs) | WARN | heuristic | WCAG 2.2 2.5.8 / EN 301 549 |
246
- | Touch-target spacing <8 px between adjacent interactive elements | INFO | heuristic | Best practice |
247
-
248
- #### 8c. Viewport & Zoom
249
-
250
- | Pattern | Severity | Confidence | Description |
251
- |---------|----------|------------|-------------|
252
- | `<meta name="viewport">` containing `user-scalable=no` / `user-scalable=0` | HIGH | definitive | WCAG 1.4.4 — blocks zoom |
253
- | `<meta name="viewport">` with `maximum-scale=1` / `maximum-scale=1.0` | HIGH | definitive | WCAG 1.4.4 |
254
- | Content rendered via `<img>` for text (text-as-image) | WARN | heuristic | WCAG 1.4.5 |
255
-
256
- #### 8d. Mobile (React Native + Flutter)
257
-
258
- EN 301 549 mobile chapter. Critical for EAA scope since consumer apps are in-scope.
259
-
260
- | Pattern | Severity | Confidence | Description |
261
- |---------|----------|------------|-------------|
262
- | **React Native**: `<TouchableOpacity>` / `<TouchableHighlight>` / `<Pressable>` without `accessibilityLabel` | HIGH | definitive | EN 301 549 mobile |
263
- | **React Native**: `<Image>` without `accessibilityLabel` or `accessible={false}` | WARN | heuristic | EN 301 549 |
264
- | **React Native**: Missing `accessibilityRole` on custom components that behave as buttons/links | WARN | heuristic | EN 301 549 |
265
- | **React Native**: `Alert.alert` for error flow without `AccessibilityInfo.announceForAccessibility` fallback | INFO | heuristic | — |
266
- | **Flutter**: Interactive widget (`GestureDetector`, `InkWell`, `TextButton`, `IconButton`) without `Semantics()` wrapper or `semanticLabel` parameter | HIGH | definitive | EN 301 549 mobile |
267
- | **Flutter**: `Image()` / `Image.asset()` / `Image.network()` without `semanticLabel` (or `excludeFromSemantics: true` for decorative) | WARN | definitive | EN 301 549 |
268
- | **Flutter**: Missing `ExcludeSemantics` / `MergeSemantics` where child semantics conflict | INFO | heuristic | Semantics tree cleanup |
269
-
270
- #### 8e. EAA Accessibility Documentation
271
-
272
- **Activated by `--standard eaa`.** EAA Article 14 + member-state transpositions require consumer-facing services to publish an accessibility statement. Missing statement = HIGH legal finding.
273
-
274
- | Pattern | Severity | Confidence | Description |
275
- |---------|----------|------------|-------------|
276
- | No route at any of: `/accessibility`, `/accessibility-statement`, `/a11y`, `/dostepnosc` (PL), `/barrierefreiheit` (DE), `/declaration-accessibilite` (FR), `/declaración-accesibilidad` (ES), `/dichiarazione-accessibilita` (IT), `/toegankelijkheidsverklaring` (NL) | HIGH | heuristic | EAA Article 14 |
277
- | Footer / sitemap lacks link to accessibility statement | HIGH | heuristic | EAA visibility requirement |
278
- | Accessibility statement present but missing required elements: (a) conformance level (WCAG/EN 301 549), (b) list of non-conformant content, (c) feedback mechanism, (d) enforcement procedure link | WARN | heuristic | Member-state template requirement |
279
- | No contact mechanism (email / form) for accessibility feedback referenced in statement | WARN | heuristic | EAA Article 14 |
280
- | `robots.txt` disallows `/accessibility*` path (accidentally blocks statement from crawlers + assistive tech) | WARN | definitive | Discoverability |
281
-
282
- See: [reference/eaa-compliance.md](reference/eaa-compliance.md) for directive text, member-state deadlines, statement templates.
283
-
284
- ---
132
+ `scripts/a11y-scanner.py` gives a deterministic baseline over 13 WCAG success
133
+ criteria. Everything else in each category is a manual pass, and the full pattern
134
+ tables for it live in
135
+ [reference/scanner-categories.md](reference/scanner-categories.md).
136
+
137
+ | # | Category | Covered by the script | Manual pass |
138
+ |---|----------|----------------------|-------------|
139
+ | 1 | Semantic Structure & Landmarks | 1.3.1, 3.1.1 | rest of the category |
140
+ | 2 | Text Alternatives & Non-Text Content | 1.1.1 | rest of the category |
141
+ | 3 | Keyboard & Focus | 2.1.1, 2.4.3, 2.4.7 | rest of the category |
142
+ | 4 | Color, Contrast & Visual Cues | 1.4.3 | rest of the category |
143
+ | 5 | Forms, Labels & Errors | 3.3.2 | rest of the category |
144
+ | 6 | Media (Audio, Video, Embeds) | 1.2.1, 1.2.2, 1.4.2 | rest of the category |
145
+ | 7 | ARIA, Live Regions & Dynamic Content | 4.1.2 | rest of the category |
146
+ | 8 | Motion, Target Size, Mobile & EAA Docs | 2.5.8 | rest of the category |
147
+
148
+ The script never covers a whole category. Treat its output as the floor, not the
149
+ result: a run that reports only script findings has skipped most of WCAG 2.1 AA.
285
150
 
286
151
  ## Output Format
287
152
 
@@ -362,6 +227,23 @@ For these, use: `axe-core`, `pa11y`, Lighthouse accessibility audit, manual assi
362
227
  - **No auto-fix**: A11y fixes often require design/content decisions that exceed pattern matching.
363
228
  - **Don't flag missing ARIA when native semantics suffice**: Prefer native HTML elements; flag redundant ARIA, not absence when the native element is already there.
364
229
 
230
+ ## Gotchas
231
+
232
+ - `scripts/a11y-scanner.py` exits **1 when it finds HIGH findings**, which is success for this skill, not failure. A wrapper that treats non-zero as an error will report a clean scan on the codebase with the most problems.
233
+ - The script covers 13 success criteria; `reference/wcag-2-1-aa.md` documents ~50. Reporting only script output looks like a full WCAG 2.1 AA pass and is not one — Step 3 is where most of the standard actually gets checked.
234
+ - Colour contrast is computed from **hardcoded hex values in source**. A theme built on CSS custom properties, `oklch()`, or a design-token pipeline yields no matches at all, and "no contrast findings" then means "nothing was measurable", not "contrast is fine".
235
+ - `alt=""` is correct for decorative images and wrong for meaningful ones. The scanner cannot see which is which — it flags missing `alt`, not useless `alt`, so a codebase that blanket-added `alt=""` scans clean while being less accessible than one that omitted the attribute.
236
+ - React Native and Flutter have no DOM. Categories written around HTML elements (landmarks, heading order, `lang`) do not transfer; use `reference/mobile-eaa.md` instead of reporting the whole category as passing.
237
+ - `--standard eaa` findings are **legal** risk with a June 2025 deadline, not code-quality suggestions. Downgrading a missing accessibility statement to WARN because it "isn't a code problem" misreports regulatory exposure.
238
+
239
+ ## When NOT to Use
240
+
241
+ - To prove WCAG conformance for an audit or a VPAT — this is static analysis; conformance needs assistive-tech testing with disabled participants.
242
+ - To check contrast in a themed or token-driven design system — run `axe-core` or Lighthouse against the rendered page instead.
243
+ - For a runtime-only concern (focus traps, screen-reader announcement order, zoom reflow at 200%/400%) — see [Out of Scope](#out-of-scope-static-analysis-cannot-detect).
244
+ - For the a11y subset that affects search ranking — use `/seo-validate`, category 9.
245
+ - To fix what was found — this skill is read-only by contract; fixes usually need design and content decisions.
246
+
365
247
  ## Reference Documents
366
248
 
367
249
  - [reference/wcag-2-1-aa.md](reference/wcag-2-1-aa.md) — All 50 Level A + AA success criteria with detection status (statically detectable vs runtime-only).