@softspark/ai-toolkit 4.20.0 → 4.22.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/CHANGELOG.md CHANGED
@@ -7,6 +7,113 @@ Versioning follows [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ---
9
9
 
10
+ ## v4.22.0 — the public surface is a test, not a promise (2026-08-06)
11
+
12
+ ### Added
13
+
14
+ - **`scripts/surface_manifest.py` + `app/surface.json`** — 275 entries of public
15
+ surface (skills, agents, frontmatter fields, CLI commands, hook scripts and
16
+ events, KB categories, plugin packs) snapshotted and checked in `npm test`.
17
+ Removing any of them fails the build; adding is free, because a surface nobody
18
+ has installed has no users to break. The manifest is deliberately not
19
+ auto-regenerated — if it were, deleting a skill would delete its entry in the
20
+ same breath and the check would prove nothing.
21
+ - **`BACKWARD_COMPATIBILITY.md`** — which surfaces are load-bearing, what may
22
+ change freely, and the deprecation path when a break is unavoidable.
23
+ - **`DECISIONS.md`** — why things are the way they are, including what was
24
+ rejected and what may yet turn out to be ceremony.
25
+ - **`scripts/check_split.py`** — five gates proving a `SKILL.md` → `reference/`
26
+ refactor lost nothing: fenced code lines survive, removed prose is traceable,
27
+ always-loaded sections stay in the body, `description` is byte-identical, and
28
+ every relative link still resolves. It caught a heading corrupted inside a
29
+ fenced example on its first real use.
30
+ - **`scripts/sync_badges.py`** — README count badges derived from the tree and
31
+ rewritten inside `generate:all`, before `validate.py --strict` reads them.
32
+ Hand-maintained badges broke the build three times in one session.
33
+ - **`/brainstorm`** — the first planning skill allowed to end in "do not build
34
+ this". Prices the zero option as a real candidate and sends the conclusion to a
35
+ separate challenger agent before routing anywhere.
36
+ - **Skill body budget in `validate.py`** — error above 20,000 bytes, warn above
37
+ 18,000, with the current headroom printed on every run and a ratchet step in
38
+ the release SOP.
39
+ - **Hook events `MessageDisplay` and `DirectoryAdded`** added to
40
+ `VALID_HOOK_EVENTS`. Both are documented Claude Code events; without them
41
+ `validate.py` rejected a user's hook on either as an invalid name.
42
+
43
+ ### Changed
44
+
45
+ - **`a11y-validate` and `seo-validate` now run the scanners they ship.**
46
+ `a11y-scanner.py` (643 lines) and `seo-scanner.py` (553 lines) were on disk and
47
+ invoked by nothing — the model was told to grep the pattern tables by hand.
48
+ Both skills now run their script for a deterministic baseline, then take an
49
+ explicit manual pass, with a measured coverage table in the body saying which
50
+ part is which. `a11y-scanner.py` covers 13 of ~50 documented WCAG success
51
+ criteria; `seo-scanner.py` covers 8 of 10 categories and none of category 7
52
+ (Rendering & Crawlability) or 10 (Topical Authority).
53
+ - **Review severity is one four-tier scale.** `/review` asked for
54
+ `Critical/Major/Minor/Nit` while the `code-reviewer` agent it delegates to
55
+ reported `CRITICAL/HIGH/MEDIUM/LOW/INFO`. The agent won at runtime, so the
56
+ skill never received the format it specified. Both now use
57
+ `blocker/major/minor/nit` with a mechanical verdict rule, and severity (impact)
58
+ is explicitly separated from confidence (certainty).
59
+ - **`/review`, `/ci` and `/debug` collect every failing signal before judging**
60
+ instead of stopping at the first red one.
61
+ - **Three validator skills split into `reference/`**: `hipaa-validate`
62
+ 23,905 → 10,433 B, `a11y-validate` 25,306 → 13,269 B, `seo-validate`
63
+ 35,066 → 13,430 B. 47 KB off the hot path.
64
+ - **`a11y-validate` and `seo-validate` gained `## Gotchas` and
65
+ `## When NOT to Use`**, written from the traps the scanner wiring introduced.
66
+
67
+ ### Fixed
68
+
69
+ - **Four skills invoked their scripts through a path that never resolved.**
70
+ `debug`, `fix`, `pr` and `hipaa-validate` used `$(dirname "$0")`, which expands
71
+ to the shell's directory, not the skill's. All four now use
72
+ `${CLAUDE_SKILL_DIR}`, matching the other 14.
73
+
74
+ ---
75
+
76
+ ## v4.21.0 — one KB taxonomy, in one place (2026-07-28)
77
+
78
+ ### Fixed
79
+
80
+ - **The `documentation-standards` skill contradicted itself.** Its frontmatter
81
+ said "5-category taxonomy", its table listed those five, and three paragraphs
82
+ below a "Valid categories" line named six — adding `planning`. An author
83
+ reading the table and a validator reading the code disagreed about what was
84
+ legal, and only the validator got a vote.
85
+ - **`decisions` and `runbooks` were not valid categories**, while the
86
+ `kb-migration` SOP had been instructing people to create exactly those
87
+ directories for months. A correctly-filed ADR failed `scripts/validate.py`.
88
+ Both are now in the taxonomy, which is eight: `reference`, `howto`,
89
+ `procedures`, `troubleshooting`, `best-practices`, `decisions`, `runbooks`,
90
+ `planning`.
91
+ - **`procedures` and `runbooks` no longer describe each other.** The table gave
92
+ "SOPs, runbooks, operational processes" for `procedures`, which left no way to
93
+ choose between them. `procedures` is a process a person follows; `runbooks`
94
+ are run against a live system, usually under pressure.
95
+
96
+ ### Added
97
+
98
+ - **`section:` is documented as what it is: a legacy alias for `category:`.**
99
+ Older documents and the `kb-migration` SOP write `section:`; both names are
100
+ read in the wild. A document may carry both, and `validate.py` now rejects it
101
+ when they disagree — a document filed as `category: reference` and
102
+ `section: howto` is indexed twice and found once.
103
+ - **`validate.py` checks that a document's category names its directory.** The
104
+ rule is scoped to directories that *are* category names, deliberately:
105
+ `kb/history/completed/` is a lifecycle location rather than a type, and a
106
+ finished plan filed there is still a `planning` document. Fifteen of this
107
+ project's own documents are in exactly that position.
108
+
109
+ ### Changed
110
+
111
+ - `VALID_KB_CATEGORIES` in `scripts/validate.py` and the taxonomy table in
112
+ `app/skills/documentation-standards/SKILL.md` are one list in two places, and
113
+ each now says so. They were previously two lists that had drifted.
114
+
115
+ ---
116
+
10
117
  ## v4.20.0 — Apache-2.0, nine plugin packs removed (2026-07-27)
11
118
 
12
119
  ### Changed — licence: MIT to Apache-2.0
package/README.md CHANGED
@@ -1,31 +1,41 @@
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.20.0
12
-
13
- v4.20.0 relicenses the project to Apache-2.0 and removes nine plugin packs that installed nothing. Plugin count: 11 → 2.
14
-
15
- - **Licence: MIT → Apache-2.0.** Still permissive — fork it, modify it, ship it commercially. The change is what a redistributor owes back: the contents of [NOTICE](NOTICE) must travel with any redistribution (§4d), modified files must say they were modified (§4b), and there is now an express patent grant and a trademark reservation. Releases up to v4.20.0 stay available under MIT; nothing already granted is revoked. See [LICENSE](LICENSE) and [NOTICE](NOTICE).
16
-
17
- - **Nine packs removed**: `csharp`, `java`, `kotlin`, `ruby`, `rust`, `swift`, `frontend`, `research`, `security`. Each declared only skills and agents that already ship in the core install, so `plugin install` reported `(0 file items)` and wrote no file — on both runtimes and all three profiles. Eight owned nothing but a `README.md`. See [the measurement](kb/history/completed/no-op-plugin-packs-removed-20260727.md).
18
- - **Nothing is lost by the removal.** `rust-patterns`, `java-patterns`, `security-patterns`, `research-mastery` and the rest are **core skills**, still installed, still triggering as before. The packs never held them they only listed them.
19
- - **`memory-pack` and `enterprise-pack` stay**: the two packs that ship files of their own — hooks, scripts, and in memory-pack's case its own skill and a SQLite store.
20
- - **A pack must now earn its existence**: `plugin-creator`'s authoring rule required referencing core assets rather than forking them, which against a core install that ships everything produced a no-op every time. It now requires that a pack install files the core does not, with a verification step in the checklist.
21
- - **`rtk-pack` retired in v4.19.0**: it broke every command it rewrote — exit 127 on `git`, `ls`, `cat`, `find`, `grep`. Measured value was 0.0615% of input tokens against a 0.05% kill number. See [the retirement note](kb/history/completed/rtk-pack-retirement-20260727.md).
22
-
23
- If you installed any removed pack, run `ai-toolkit plugin remove <name>` to drop its bookkeeping entry. No skill or agent disappears.
9
+ [![Tests](https://img.shields.io/badge/tests-1508%20passing-success)](tests/)
10
+
11
+ ## What's New in v4.22.0
12
+
13
+ v4.22.0 turns compatibility promises into tests, and makes two validator skills
14
+ actually run the scanners they ship.
15
+
16
+ - **The public surface is checked, not documented.** `app/surface.json` pins 275
17
+ entries skills, agents, frontmatter fields, CLI commands, hook scripts and
18
+ events, KB categories, plugin packs. Removing any of them fails `npm test`.
19
+ Adding is free: a surface nobody has installed has no users to break.
20
+ `BACKWARD_COMPATIBILITY.md` says which surfaces are load-bearing and how to
21
+ deprecate one properly.
22
+ - **`a11y-validate` and `seo-validate` shipped scanners that nothing invoked.**
23
+ 1,196 lines of working Python sat on disk while the model was told to grep the
24
+ pattern tables by hand. Both now run their script for a deterministic baseline,
25
+ then take an explicit manual pass — with a measured coverage table saying which
26
+ criteria the script handles and which are yours.
27
+ - **`/brainstorm`** — the first planning skill allowed to conclude "do not build
28
+ this". Every other one produces an artifact; a pipeline that cannot say no is a
29
+ yes-machine.
30
+ - **Skill bodies have a budget.** Error above 20,000 bytes, warn above 18,000,
31
+ headroom printed every run. Three validator skills were split into
32
+ `reference/`, taking 47 KB off the hot path, with `scripts/check_split.py`
33
+ proving nothing was lost.
34
+ - **Four skills invoked their scripts through a path that never resolved.**
35
+ `$(dirname "$0")` expands to the shell's directory, not the skill's.
24
36
 
25
37
  See [CHANGELOG.md](CHANGELOG.md) for full history.
26
38
 
27
- ---
28
-
29
39
  ## Table of Contents
30
40
 
31
41
  - [Install](#install)
@@ -54,7 +64,7 @@ ai-toolkit install
54
64
  npx @softspark/ai-toolkit install
55
65
  ```
56
66
 
57
- **That's it.** Claude Code picks up 108 skills, 44 agents, quality hooks, and the safety constitution automatically.
67
+ **That's it.** Claude Code picks up 109 skills, 44 agents, quality hooks, and the safety constitution automatically.
58
68
 
59
69
  **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).
60
70
 
@@ -143,7 +153,7 @@ See [CLI Reference](kb/reference/cli-reference.md) for all commands and options.
143
153
  | Component | Count | Description |
144
154
  |-----------|-------|-------------|
145
155
  | `skills/` (task) | 32 | Slash commands: `/commit`, `/build`, `/deploy`, `/test`, `/mcp-builder`, ... |
146
- | `skills/` (hybrid) | 30 | Slash commands with agent knowledge base |
156
+ | `skills/` (hybrid) | 31 | Slash commands with agent knowledge base |
147
157
  | `skills/` (knowledge) | 46 | Domain knowledge auto-loaded by agents (includes 13 `<lang>-rules` skills) |
148
158
  | `agents/` | 44 | Specialized agents across 10 categories |
149
159
  | `hooks/` | 28 entries / 14 events + statusLine | Quality gates, path safety, prompt governance, loop guard, session lifecycle |
@@ -160,7 +170,7 @@ See [CLI Reference](kb/reference/cli-reference.md) for all commands and options.
160
170
  ai-toolkit/
161
171
  ├── app/
162
172
  │ ├── agents/ # 44 agent definitions
163
- │ ├── skills/ # 108 skills (task / hybrid / knowledge)
173
+ │ ├── skills/ # 109 skills (task / hybrid / knowledge)
164
174
  │ ├── rules/ # Source rules synced into Claude/editor rule files
165
175
  │ ├── hooks/ # Hook scripts (29 entries, 14 lifecycle events)
166
176
  │ ├── 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.20.0",
6
+ "version": "4.22.0",
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).