@softspark/ai-toolkit 4.21.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 +66 -0
- package/README.md +32 -28
- package/app/.claude-plugin/plugin.json +1 -1
- package/app/agents/code-reviewer.md +32 -15
- package/app/skills/a11y-validate/SKILL.md +61 -179
- package/app/skills/a11y-validate/reference/scanner-categories.md +174 -0
- package/app/skills/brainstorm/SKILL.md +174 -0
- package/app/skills/ci/SKILL.md +1 -0
- package/app/skills/debug/SKILL.md +2 -1
- package/app/skills/fix/SKILL.md +1 -1
- package/app/skills/hipaa-validate/SKILL.md +25 -221
- package/app/skills/hipaa-validate/reference/scanner-categories.md +224 -0
- package/app/skills/pr/SKILL.md +1 -1
- package/app/skills/review/SKILL.md +49 -4
- package/app/skills/seo-validate/SKILL.md +63 -309
- package/app/skills/seo-validate/reference/scanner-categories.md +304 -0
- package/app/surface.json +296 -0
- package/benchmarks/ecosystem-doctor-snapshot.json +15 -19
- package/kb/procedures/release-preparation-sop.md +44 -2
- package/kb/reference/architecture-overview.md +3 -3
- package/kb/reference/skills-catalog.md +10 -14
- package/llms-full.txt +58 -19
- package/manifest.json +3 -3
- package/package.json +6 -3
- package/scripts/check_split.py +493 -0
- package/scripts/surface_manifest.py +246 -0
- package/scripts/sync_badges.py +133 -0
- package/scripts/validate.py +51 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,72 @@ 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
|
+
|
|
10
76
|
## v4.21.0 — one KB taxonomy, in one place (2026-07-28)
|
|
11
77
|
|
|
12
78
|
### Fixed
|
package/README.md
CHANGED
|
@@ -1,34 +1,38 @@
|
|
|
1
1
|
# ai-toolkit
|
|
2
2
|
|
|
3
|
-
> Professional-grade AI coding toolkit with multi-platform support. Machine-enforced safety,
|
|
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
|
[](https://github.com/softspark/ai-toolkit/actions/workflows/ci.yml)
|
|
6
6
|
[](LICENSE)
|
|
7
|
-
[](app/skills/)
|
|
8
8
|
[](app/agents/)
|
|
9
|
-
[](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.
|
|
32
36
|
|
|
33
37
|
See [CHANGELOG.md](CHANGELOG.md) for full history.
|
|
34
38
|
|
|
@@ -60,7 +64,7 @@ ai-toolkit install
|
|
|
60
64
|
npx @softspark/ai-toolkit install
|
|
61
65
|
```
|
|
62
66
|
|
|
63
|
-
**That's it.** Claude Code picks up
|
|
67
|
+
**That's it.** Claude Code picks up 109 skills, 44 agents, quality hooks, and the safety constitution automatically.
|
|
64
68
|
|
|
65
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).
|
|
66
70
|
|
|
@@ -149,7 +153,7 @@ See [CLI Reference](kb/reference/cli-reference.md) for all commands and options.
|
|
|
149
153
|
| Component | Count | Description |
|
|
150
154
|
|-----------|-------|-------------|
|
|
151
155
|
| `skills/` (task) | 32 | Slash commands: `/commit`, `/build`, `/deploy`, `/test`, `/mcp-builder`, ... |
|
|
152
|
-
| `skills/` (hybrid) |
|
|
156
|
+
| `skills/` (hybrid) | 31 | Slash commands with agent knowledge base |
|
|
153
157
|
| `skills/` (knowledge) | 46 | Domain knowledge auto-loaded by agents (includes 13 `<lang>-rules` skills) |
|
|
154
158
|
| `agents/` | 44 | Specialized agents across 10 categories |
|
|
155
159
|
| `hooks/` | 28 entries / 14 events + statusLine | Quality gates, path safety, prompt governance, loop guard, session lifecycle |
|
|
@@ -166,7 +170,7 @@ See [CLI Reference](kb/reference/cli-reference.md) for all commands and options.
|
|
|
166
170
|
ai-toolkit/
|
|
167
171
|
├── app/
|
|
168
172
|
│ ├── agents/ # 44 agent definitions
|
|
169
|
-
│ ├── skills/ #
|
|
173
|
+
│ ├── skills/ # 109 skills (task / hybrid / knowledge)
|
|
170
174
|
│ ├── rules/ # Source rules synced into Claude/editor rule files
|
|
171
175
|
│ ├── hooks/ # Hook scripts (29 entries, 14 lifecycle events)
|
|
172
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.
|
|
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 (
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
|
93
|
-
|
|
94
|
-
|
|
|
95
|
-
|
|
|
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
|
-
- "
|
|
106
|
-
- "
|
|
121
|
+
- "blocker: Hardcoded API key in config.py:42"
|
|
122
|
+
- "pass: No SQL injection vulnerabilities"
|
|
107
123
|
quality:
|
|
108
|
-
- "
|
|
109
|
-
- "
|
|
124
|
+
- "minor: Function exceeds 50 lines - consider splitting"
|
|
125
|
+
- "pass: Error handling comprehensive"
|
|
110
126
|
performance:
|
|
111
|
-
- "
|
|
112
|
-
approval:
|
|
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
|
|
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. **
|
|
66
|
-
3. **
|
|
67
|
-
4. **
|
|
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
|
|
94
|
+
### Step 2: Run the Scanner Script
|
|
94
95
|
|
|
95
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
|
121
|
-
|
|
|
122
|
-
|
|
|
123
|
-
|
|
|
124
|
-
|
|
|
125
|
-
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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).
|