wcag-a11y 0.3.6 → 0.4.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.
- package/README.md +165 -129
- package/dist/ai/anthropic.js +59 -0
- package/dist/ai/azure-openai.js +17 -0
- package/dist/ai/base.js +45 -0
- package/dist/ai/cohere.js +58 -0
- package/dist/ai/deepseek.js +6 -0
- package/dist/ai/fallback-fix.js +126 -0
- package/dist/ai/gemini.js +8 -39
- package/dist/ai/groq.js +6 -0
- package/dist/ai/index.js +78 -14
- package/dist/ai/mistral.js +6 -0
- package/dist/ai/ollama.js +3 -23
- package/dist/ai/openai-compat.js +65 -0
- package/dist/ai/openai.js +3 -84
- package/dist/ai/perplexity.js +6 -0
- package/dist/ai/prompt.js +13 -6
- package/dist/ai/together.js +6 -0
- package/dist/ai/xai.js +6 -0
- package/dist/cli.js +4 -4
- package/dist/config.js +51 -0
- package/dist/reporter/markdown.js +5 -1
- package/package.json +20 -2
package/README.md
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/Dannyplusplus12/WCAG-A11y/actions/workflows/ci.yml)
|
|
4
4
|
|
|
5
|
-
Most accessibility auditors stop at detection — they tell you *what* is broken and leave the rest to you. `wcag-a11y`
|
|
5
|
+
Most accessibility auditors stop at detection — they tell you *what* is broken and leave the rest to you. `wcag-a11y` crawls your running dev server with Playwright, runs 40+ WCAG 2.1/2.2 checks, and uses AI to generate ready-to-paste fix prompts **or write the fixes directly into your source files**.
|
|
6
6
|
|
|
7
7
|
Two modes:
|
|
8
|
-
- **`scan`** — find violations +
|
|
8
|
+
- **`scan`** — find violations + get AI prompts you paste into Cursor, Copilot, or Claude
|
|
9
9
|
- **`fix`** — find violations + patch source files automatically (dry-run by default, `--apply` to write)
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
## Try it instantly
|
|
14
14
|
|
|
15
|
-
No dev server, no config
|
|
15
|
+
No dev server, no config:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
18
|
npx wcag-a11y demo
|
|
@@ -22,41 +22,50 @@ npx wcag-a11y demo
|
|
|
22
22
|
|
|
23
23
|
## What the output looks like
|
|
24
24
|
|
|
25
|
-
Running a scan prints a violation summary per page, then AI-generated fixes for each rule:
|
|
26
|
-
|
|
27
25
|
```
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
✖ critical
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
[
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
26
|
+
WCAG A11y — scan complete
|
|
27
|
+
────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
✖ http://localhost:3000 3 critical 4 serious 3 moderate
|
|
30
|
+
|
|
31
|
+
[CRITICAL] Images must have an alt attribute WCAG 1.1.1
|
|
32
|
+
→ img
|
|
33
|
+
[CRITICAL] Form inputs must have an associated label WCAG 1.3.1
|
|
34
|
+
→ input[type="email"]
|
|
35
|
+
[CRITICAL] Buttons must have an accessible name WCAG 4.1.2
|
|
36
|
+
→ button[type="submit"]
|
|
37
|
+
[SERIOUS] Normal text must meet 4.5:1 contrast ratio WCAG 1.4.3
|
|
38
|
+
→ p
|
|
39
|
+
[SERIOUS] Links must have descriptive text WCAG 2.4.4
|
|
40
|
+
→ a[href="/sale"]
|
|
41
|
+
… and 5 more
|
|
42
|
+
|
|
43
|
+
────────────────────────────────────────────────────────────
|
|
44
|
+
Total: 3 critical · 4 serious · 3 moderate
|
|
45
|
+
|
|
46
|
+
AI Fix Prompts — paste any of these into Cursor, Copilot, or Claude
|
|
47
|
+
────────────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
[img-alt]
|
|
50
|
+
→ img
|
|
51
|
+
Screen reader users hear nothing for this image — branding, instructions,
|
|
52
|
+
or data it conveys is completely invisible to them.
|
|
53
|
+
┌─ Copy this prompt ──────────────────────────────────────
|
|
54
|
+
│ Fix WCAG 1.1.1 (Level A) — img is missing an alt attribute
|
|
55
|
+
│
|
|
56
|
+
│ Current HTML:
|
|
57
|
+
│ <img src="banner.jpg">
|
|
58
|
+
│
|
|
59
|
+
│ How to fix:
|
|
60
|
+
│ Add alt text describing the image content.
|
|
61
|
+
│ Use alt="" if the image is purely decorative.
|
|
62
|
+
│ Example: <img src="banner.jpg" alt="Summer sale — 50% off">
|
|
63
|
+
└─────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
… 9 more prompts — full report saved to a11y-report.md
|
|
55
66
|
```
|
|
56
67
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
With `--report`, the full output is also saved to `a11y-report.md`.
|
|
68
|
+
Each prompt tells you the WCAG criterion, shows the broken element, and gives the exact fix — ready to paste into your AI editor.
|
|
60
69
|
|
|
61
70
|
---
|
|
62
71
|
|
|
@@ -66,56 +75,21 @@ With `--report`, the full output is also saved to `a11y-report.md`.
|
|
|
66
75
|
npm install -g wcag-a11y
|
|
67
76
|
```
|
|
68
77
|
|
|
69
|
-
##
|
|
78
|
+
## Quick start
|
|
70
79
|
|
|
71
80
|
```bash
|
|
72
|
-
|
|
73
|
-
wcag-a11y init
|
|
74
|
-
wcag-a11y init --provider ollama # Local — no API key needed
|
|
75
|
-
```
|
|
81
|
+
# 1. Configure your AI provider (Gemini is free, no credit card)
|
|
82
|
+
wcag-a11y init
|
|
76
83
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
**Get a free Gemini key:** https://aistudio.google.com
|
|
80
|
-
**Get an OpenAI key:** https://platform.openai.com/api-keys
|
|
81
|
-
**Ollama (local):** install from https://ollama.com, then run `ollama serve`
|
|
82
|
-
|
|
83
|
-
---
|
|
84
|
-
|
|
85
|
-
## Commands
|
|
86
|
-
|
|
87
|
-
### `wcag-a11y demo`
|
|
88
|
-
|
|
89
|
-
Scan a built-in page with 10 intentional violations. No dev server or config required — useful for trying the tool before pointing it at your own project.
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
wcag-a11y demo # violations + AI fixes (default, requires config)
|
|
93
|
-
wcag-a11y demo --no-ai # violations only, no AI — faster
|
|
94
|
-
wcag-a11y demo --report # + save a11y-report.md
|
|
84
|
+
# 2. Start your dev server, then scan
|
|
85
|
+
wcag-a11y scan -u http://localhost:3000
|
|
95
86
|
```
|
|
96
87
|
|
|
97
|
-
|
|
98
|
-
|---|---|
|
|
99
|
-
| `--no-ai` | Skip AI fix generation — prints violations only, no prompts |
|
|
100
|
-
| `-r, --report` | Save the full report to `a11y-report.md` in the current directory |
|
|
88
|
+
Add `--pages / /about /contact` to scan specific routes, or `--crawl` to follow links automatically.
|
|
101
89
|
|
|
102
90
|
---
|
|
103
91
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
Create `a11y.config.json` in the current directory, pre-configured for your chosen provider.
|
|
107
|
-
|
|
108
|
-
```bash
|
|
109
|
-
wcag-a11y init # Gemini (default)
|
|
110
|
-
wcag-a11y init --provider openai # OpenAI
|
|
111
|
-
wcag-a11y init --provider ollama # Ollama (local)
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
| Flag | Description |
|
|
115
|
-
|---|---|
|
|
116
|
-
| `--provider <name>` | Which provider to configure: `gemini` (default), `openai`, or `ollama`. Determines which fields are written to the config file. |
|
|
117
|
-
|
|
118
|
-
---
|
|
92
|
+
## Commands
|
|
119
93
|
|
|
120
94
|
### `wcag-a11y scan`
|
|
121
95
|
|
|
@@ -124,21 +98,24 @@ Scan a running dev server for accessibility violations.
|
|
|
124
98
|
```bash
|
|
125
99
|
wcag-a11y scan -u http://localhost:3000
|
|
126
100
|
wcag-a11y scan -u http://localhost:3000 --pages / /about /contact
|
|
127
|
-
wcag-a11y scan -u http://localhost:3000 --crawl
|
|
101
|
+
wcag-a11y scan -u http://localhost:3000 --crawl
|
|
128
102
|
wcag-a11y scan -u http://localhost:3000 --no-ai --ci
|
|
103
|
+
wcag-a11y scan -u http://localhost:3000 --terminal --fast-mode
|
|
129
104
|
```
|
|
130
105
|
|
|
131
106
|
| Flag | Default | Description |
|
|
132
107
|
|---|---|---|
|
|
133
108
|
| `-u, --url <url>` | required | Base URL of your running dev server |
|
|
134
|
-
| `-p, --pages <
|
|
135
|
-
| `-c, --crawl` | off | Follow same-origin links and scan all reachable pages
|
|
136
|
-
|
|
|
137
|
-
| `--no-
|
|
138
|
-
| `--no-explain` |
|
|
139
|
-
| `--
|
|
140
|
-
| `--
|
|
141
|
-
| `--
|
|
109
|
+
| `-p, --pages <paths...>` | `/` | Paths to scan. Space-separated: `--pages / /about /contact` |
|
|
110
|
+
| `-c, --crawl` | off | Follow same-origin links and scan all reachable pages |
|
|
111
|
+
| `--no-ai` | — | Skip AI fix generation — scan runs faster, violations only |
|
|
112
|
+
| `--no-report` | — | Skip saving `a11y-report.md` |
|
|
113
|
+
| `--no-explain` | — | Omit explanations, show prompts only |
|
|
114
|
+
| `--terminal` | off | Print violations and AI prompts to terminal |
|
|
115
|
+
| `--fast-mode` | off | Output only the raw prompts — no summaries or decoration |
|
|
116
|
+
| `--group <strategy>` | `rule` | `rule`: one prompt per rule type. `none`: one prompt per element |
|
|
117
|
+
| `--ci` | off | Exit with code `1` if any violations are found |
|
|
118
|
+
| `--provider <name>` | from config | Override AI provider for this run |
|
|
142
119
|
|
|
143
120
|
---
|
|
144
121
|
|
|
@@ -147,34 +124,13 @@ wcag-a11y scan -u http://localhost:3000 --no-ai --ci
|
|
|
147
124
|
Scan for violations and apply AI-generated patches directly to your source files. Works with any framework — React, Vue, Angular, Svelte, or plain HTML.
|
|
148
125
|
|
|
149
126
|
```bash
|
|
150
|
-
#
|
|
151
|
-
wcag-a11y fix -u http://localhost:3000
|
|
152
|
-
|
|
153
|
-
# Preview specific pages
|
|
154
|
-
wcag-a11y fix -u http://localhost:3000 --pages / /about /contact
|
|
155
|
-
|
|
156
|
-
# Write fixes to disk
|
|
157
|
-
wcag-a11y fix -u http://localhost:3000 --apply
|
|
158
|
-
|
|
159
|
-
# Auto-discover pages + write fixes
|
|
160
|
-
wcag-a11y fix -u http://localhost:3000 --crawl --apply
|
|
161
|
-
|
|
162
|
-
# Skip rescanning — load violations from an existing report
|
|
163
|
-
wcag-a11y fix --from-report
|
|
164
|
-
wcag-a11y fix --from-report ./reports/a11y-report.md --apply
|
|
127
|
+
wcag-a11y fix -u http://localhost:3000 # dry-run: show diff, nothing written
|
|
128
|
+
wcag-a11y fix -u http://localhost:3000 --apply # write fixes to disk
|
|
129
|
+
wcag-a11y fix --from-report --apply # patch from an existing report
|
|
165
130
|
```
|
|
166
131
|
|
|
167
|
-
**How it works:**
|
|
168
|
-
|
|
169
|
-
1. Runs the same scan as `wcag-a11y scan` (or loads an existing report with `--from-report`)
|
|
170
|
-
2. For each violation, locates the source file — checks `violation.source` (React dev mode) first, then falls back to grepping `./src` for unique identifiers in the HTML snippet (`id=`, `name=`, `for=`, local `src=`, text content)
|
|
171
|
-
3. Groups violations by file (multiple violations in the same file → one AI call)
|
|
172
|
-
4. Sends the full file content + violation list to your configured AI provider and asks for the corrected file
|
|
173
|
-
5. Shows a colored diff before writing anything
|
|
174
|
-
6. With `--apply`, overwrites the file; without it, only prints the diff
|
|
175
|
-
|
|
176
132
|
```
|
|
177
|
-
src/components/Navbar.jsx — 2
|
|
133
|
+
src/components/Navbar.jsx — 2 violations
|
|
178
134
|
· [button-name] Buttons must have an accessible name
|
|
179
135
|
· [aria-valid-role] Elements must use valid ARIA roles
|
|
180
136
|
|
|
@@ -188,39 +144,85 @@ src/components/Navbar.jsx — 2 violation(s)
|
|
|
188
144
|
+ <li>Home</li>
|
|
189
145
|
```
|
|
190
146
|
|
|
147
|
+
**Common workflow:** scan first to review, then patch:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
wcag-a11y scan -u http://localhost:3000 # generates a11y-report.md
|
|
151
|
+
wcag-a11y fix --from-report --apply # patches files from that report, no second crawl
|
|
152
|
+
```
|
|
153
|
+
|
|
191
154
|
| Flag | Default | Description |
|
|
192
155
|
|---|---|---|
|
|
193
|
-
| `-u, --url <url>` | — | Base URL
|
|
194
|
-
| `-p, --pages <
|
|
156
|
+
| `-u, --url <url>` | — | Base URL. Required unless `--from-report` is used |
|
|
157
|
+
| `-p, --pages <paths...>` | `/` | Paths to scan |
|
|
195
158
|
| `-c, --crawl` | off | Auto-discover pages by following same-origin links |
|
|
196
|
-
| `--from-report [path]` | `a11y-report.md` | Load violations from an existing report instead of
|
|
197
|
-
| `--apply` | off | Write
|
|
198
|
-
| `--provider <name>` | from config | Override AI provider for this run
|
|
159
|
+
| `--from-report [path]` | `a11y-report.md` | Load violations from an existing report instead of rescanning |
|
|
160
|
+
| `--apply` | off | Write fixes to disk (dry-run without this flag) |
|
|
161
|
+
| `--provider <name>` | from config | Override AI provider for this run |
|
|
162
|
+
|
|
163
|
+
---
|
|
199
164
|
|
|
200
|
-
|
|
165
|
+
### `wcag-a11y init`
|
|
201
166
|
|
|
202
|
-
|
|
167
|
+
Create `a11y.config.json` pre-configured for your chosen provider.
|
|
203
168
|
|
|
204
169
|
```bash
|
|
205
|
-
wcag-a11y
|
|
206
|
-
wcag-a11y
|
|
170
|
+
wcag-a11y init # Gemini (free, default)
|
|
171
|
+
wcag-a11y init --provider openai
|
|
172
|
+
wcag-a11y init --provider anthropic
|
|
173
|
+
wcag-a11y init --provider ollama # local — no API key needed
|
|
174
|
+
# … and 8 more providers
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
### `wcag-a11y demo`
|
|
180
|
+
|
|
181
|
+
Scan a built-in page with 10 intentional violations. No dev server or config required — useful for trying the tool before pointing it at your own project.
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
wcag-a11y demo # violations + AI fix prompts (requires config)
|
|
185
|
+
wcag-a11y demo --no-ai # violations only, no AI
|
|
207
186
|
```
|
|
208
187
|
|
|
209
188
|
---
|
|
210
189
|
|
|
211
190
|
## AI Providers
|
|
212
191
|
|
|
213
|
-
|
|
214
|
-
|---|---|---|---|
|
|
215
|
-
| `gemini` (default) | `gemini-2.5-flash` | Free tier | [aistudio.google.com](https://aistudio.google.com) |
|
|
216
|
-
| `openai` | `gpt-4o-mini` | Pay-per-use | [platform.openai.com](https://platform.openai.com/api-keys) |
|
|
217
|
-
| `ollama` | `llama3` | Free (local) | None — run `ollama serve` |
|
|
192
|
+
12 providers supported. Configure once in `a11y.config.json`, or override per-run with `--provider`.
|
|
218
193
|
|
|
219
|
-
|
|
194
|
+
| Provider | `--provider` | Default model | Notes |
|
|
195
|
+
|---|---|---|---|
|
|
196
|
+
| Google Gemini | `gemini` *(default)* | `gemini-2.5-flash` | Free tier available |
|
|
197
|
+
| OpenAI | `openai` | `gpt-4o-mini` | |
|
|
198
|
+
| Anthropic | `anthropic` | `claude-sonnet-4-6` | |
|
|
199
|
+
| Mistral | `mistral` | `mistral-large-latest` | |
|
|
200
|
+
| Groq | `groq` | `llama-3.3-70b-versatile` | Fast inference |
|
|
201
|
+
| Cohere | `cohere` | `command-r-plus` | |
|
|
202
|
+
| xAI | `xai` | `grok-2` | |
|
|
203
|
+
| DeepSeek | `deepseek` | `deepseek-chat` | |
|
|
204
|
+
| Together AI | `together` | `meta-llama/Llama-3-70b-chat-hf` | Open-source models |
|
|
205
|
+
| Perplexity | `perplexity` | `llama-3.1-sonar-large-128k-online` | |
|
|
206
|
+
| Azure OpenAI | `azure-openai` | *(your deployment)* | |
|
|
207
|
+
| Ollama | `ollama` | `llama3` | Local — no API key |
|
|
208
|
+
|
|
209
|
+
All models are configurable. If the AI response is unparseable, the tool generates a fix prompt directly from the violation data — you always get something actionable.
|
|
220
210
|
|
|
221
211
|
---
|
|
222
212
|
|
|
223
|
-
## Config
|
|
213
|
+
## Config
|
|
214
|
+
|
|
215
|
+
Run `wcag-a11y init` to generate `a11y.config.json`. Only fill in the fields for your chosen provider. This file is gitignored by default.
|
|
216
|
+
|
|
217
|
+
```json
|
|
218
|
+
{
|
|
219
|
+
"provider": "gemini",
|
|
220
|
+
"apiKey": "YOUR_GEMINI_API_KEY"
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
<details>
|
|
225
|
+
<summary>Full config reference (all 12 providers)</summary>
|
|
224
226
|
|
|
225
227
|
```json
|
|
226
228
|
{
|
|
@@ -232,18 +234,50 @@ Set your provider in `a11y.config.json` or override it per-run with `--provider`
|
|
|
232
234
|
"openaiApiKey": "YOUR_OPENAI_API_KEY",
|
|
233
235
|
"openaiModel": "gpt-4o-mini",
|
|
234
236
|
|
|
237
|
+
"anthropicApiKey": "YOUR_ANTHROPIC_API_KEY",
|
|
238
|
+
"anthropicModel": "claude-sonnet-4-6",
|
|
239
|
+
|
|
240
|
+
"mistralApiKey": "YOUR_MISTRAL_API_KEY",
|
|
241
|
+
"mistralModel": "mistral-large-latest",
|
|
242
|
+
|
|
243
|
+
"groqApiKey": "YOUR_GROQ_API_KEY",
|
|
244
|
+
"groqModel": "llama-3.3-70b-versatile",
|
|
245
|
+
|
|
246
|
+
"cohereApiKey": "YOUR_COHERE_API_KEY",
|
|
247
|
+
"cohereModel": "command-r-plus",
|
|
248
|
+
|
|
249
|
+
"xaiApiKey": "YOUR_XAI_API_KEY",
|
|
250
|
+
"xaiModel": "grok-2",
|
|
251
|
+
|
|
252
|
+
"deepseekApiKey": "YOUR_DEEPSEEK_API_KEY",
|
|
253
|
+
"deepseekModel": "deepseek-chat",
|
|
254
|
+
|
|
255
|
+
"togetherApiKey": "YOUR_TOGETHER_API_KEY",
|
|
256
|
+
"togetherModel": "meta-llama/Llama-3-70b-chat-hf",
|
|
257
|
+
|
|
258
|
+
"perplexityApiKey": "YOUR_PERPLEXITY_API_KEY",
|
|
259
|
+
"perplexityModel": "llama-3.1-sonar-large-128k-online",
|
|
260
|
+
|
|
261
|
+
"azureOpenaiApiKey": "YOUR_AZURE_KEY",
|
|
262
|
+
"azureOpenaiEndpoint": "https://YOUR_RESOURCE.openai.azure.com",
|
|
263
|
+
"azureOpenaiDeployment": "YOUR_DEPLOYMENT_NAME",
|
|
264
|
+
"azureOpenaiApiVersion": "2024-10-01-preview",
|
|
265
|
+
|
|
235
266
|
"ollamaBaseUrl": "http://localhost:11434",
|
|
236
267
|
"ollamaModel": "llama3"
|
|
237
268
|
}
|
|
238
269
|
```
|
|
239
270
|
|
|
240
|
-
|
|
271
|
+
</details>
|
|
241
272
|
|
|
242
273
|
---
|
|
243
274
|
|
|
244
275
|
## What it checks
|
|
245
276
|
|
|
246
|
-
40+ rules across 10
|
|
277
|
+
40+ rules across 10 WCAG 2.1/2.2 categories: Text Alternatives, Color Contrast, Forms, Keyboard, ARIA, Structure, Links, Media, Tables, and Language.
|
|
278
|
+
|
|
279
|
+
<details>
|
|
280
|
+
<summary>Full rule list</summary>
|
|
247
281
|
|
|
248
282
|
### Text Alternatives — WCAG 1.1.1
|
|
249
283
|
|
|
@@ -346,6 +380,8 @@ Only the fields for your active provider are required. This file is gitignored b
|
|
|
346
380
|
| `html-lang` | serious | `<html>` must have a `lang` attribute |
|
|
347
381
|
| `html-lang-valid` | serious | `lang` attribute must be a valid BCP 47 language tag |
|
|
348
382
|
|
|
383
|
+
</details>
|
|
384
|
+
|
|
349
385
|
---
|
|
350
386
|
|
|
351
387
|
## Use in CI/CD
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { buildPrompt } from './prompt.js';
|
|
2
|
+
import { buildPatchPrompt } from './patch-prompt.js';
|
|
3
|
+
import { groupViolations } from './group.js';
|
|
4
|
+
import { BaseAIProvider } from './base.js';
|
|
5
|
+
export class AnthropicProvider extends BaseAIProvider {
|
|
6
|
+
apiKey;
|
|
7
|
+
model;
|
|
8
|
+
constructor(apiKey, model = 'claude-sonnet-4-6') {
|
|
9
|
+
super();
|
|
10
|
+
this.apiKey = apiKey;
|
|
11
|
+
this.model = model;
|
|
12
|
+
}
|
|
13
|
+
get headers() {
|
|
14
|
+
return {
|
|
15
|
+
'Content-Type': 'application/json',
|
|
16
|
+
'x-api-key': this.apiKey,
|
|
17
|
+
'anthropic-version': '2023-06-01',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
async generateFixes(violations, strategy = 'rule', framework) {
|
|
21
|
+
if (violations.length === 0)
|
|
22
|
+
return [];
|
|
23
|
+
const groups = groupViolations(violations, strategy);
|
|
24
|
+
const prompt = buildPrompt(groups, framework);
|
|
25
|
+
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
headers: this.headers,
|
|
28
|
+
body: JSON.stringify({
|
|
29
|
+
model: this.model,
|
|
30
|
+
max_tokens: 8192,
|
|
31
|
+
temperature: 0.2,
|
|
32
|
+
messages: [{ role: 'user', content: prompt }],
|
|
33
|
+
}),
|
|
34
|
+
});
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
throw new Error(`Anthropic API error: ${response.status} ${await response.text()}`);
|
|
37
|
+
}
|
|
38
|
+
const data = await response.json();
|
|
39
|
+
const text = data.content?.[0]?.text ?? '[]';
|
|
40
|
+
return this.parse(text, groups, framework);
|
|
41
|
+
}
|
|
42
|
+
async generateFilePatch(fileContent, violations, filePath, framework) {
|
|
43
|
+
const prompt = buildPatchPrompt(fileContent, violations, filePath, framework);
|
|
44
|
+
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers: this.headers,
|
|
47
|
+
body: JSON.stringify({
|
|
48
|
+
model: this.model,
|
|
49
|
+
max_tokens: 16384,
|
|
50
|
+
temperature: 0.1,
|
|
51
|
+
messages: [{ role: 'user', content: prompt }],
|
|
52
|
+
}),
|
|
53
|
+
});
|
|
54
|
+
if (!response.ok)
|
|
55
|
+
throw new Error(`Anthropic API error: ${response.status} ${await response.text()}`);
|
|
56
|
+
const data = await response.json();
|
|
57
|
+
return data.content?.[0]?.text ?? '';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { OpenAICompatProvider } from './openai-compat.js';
|
|
2
|
+
export class AzureOpenAIProvider extends OpenAICompatProvider {
|
|
3
|
+
apiVersion;
|
|
4
|
+
constructor(endpoint, apiKey, deployment, apiVersion = '2024-10-01-preview') {
|
|
5
|
+
super(`${endpoint}/openai/deployments/${deployment}`, apiKey, deployment, 'Azure OpenAI');
|
|
6
|
+
this.apiVersion = apiVersion;
|
|
7
|
+
}
|
|
8
|
+
buildUrl(path) {
|
|
9
|
+
return `${this.baseUrl}${path}?api-version=${this.apiVersion}`;
|
|
10
|
+
}
|
|
11
|
+
buildHeaders() {
|
|
12
|
+
return {
|
|
13
|
+
'Content-Type': 'application/json',
|
|
14
|
+
'api-key': this.apiKey,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
}
|
package/dist/ai/base.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { fallbackExplanation } from './fallback-explanation.js';
|
|
2
|
+
import { getFix, getFixCategory } from './fallback-fix.js';
|
|
3
|
+
export class BaseAIProvider {
|
|
4
|
+
parse(text, groups, framework) {
|
|
5
|
+
try {
|
|
6
|
+
const jsonMatch = text.match(/\[[\s\S]*\]/);
|
|
7
|
+
const fixes = jsonMatch ? JSON.parse(jsonMatch[0]) : [];
|
|
8
|
+
return groups.map((g) => {
|
|
9
|
+
const found = fixes.find((f) => f.ruleId === g.ruleId);
|
|
10
|
+
return found
|
|
11
|
+
? { ...found, selectors: g.selectors, instanceCount: g.count, fixCategory: getFixCategory(g.ruleId) }
|
|
12
|
+
: this.fallbackFix(g, framework);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return groups.map((g) => this.fallbackFix(g, framework));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
fallback(groups, framework) {
|
|
20
|
+
return groups.map((g) => this.fallbackFix(g, framework));
|
|
21
|
+
}
|
|
22
|
+
fallbackFix(g, framework) {
|
|
23
|
+
const v = g.representative;
|
|
24
|
+
const selectorList = g.selectors.map((s) => `- \`${s}\``).join('\n');
|
|
25
|
+
const explanation = fallbackExplanation(g.ruleId, g.description, g.wcag, g.level);
|
|
26
|
+
const fwNote = framework ? `This project uses ${framework}.\n\n` : '';
|
|
27
|
+
const fixInstructions = getFix(g.ruleId);
|
|
28
|
+
const fixSection = fixInstructions ? `\n\nHow to fix:\n${fixInstructions}` : '';
|
|
29
|
+
const prompt = g.count > 1
|
|
30
|
+
? `${fwNote}Fix WCAG 2.1 SC ${g.wcag} (Level ${g.level}) — ${g.description}\n\nAffected elements (${g.count} instances):\n${selectorList}\n\nRepresentative HTML:\n\`\`\`html\n${v.html.slice(0, 400)}\n\`\`\`${fixSection}\n\nApply this fix to all ${g.count} instances across the codebase.`
|
|
31
|
+
: `${fwNote}Fix WCAG 2.1 SC ${g.wcag} (Level ${g.level}) — ${g.description}\n\nAffected element:\n- Selector: \`${g.selectors[0]}\`\n\nCurrent HTML:\n\`\`\`html\n${v.html.slice(0, 400)}\n\`\`\`${fixSection}`;
|
|
32
|
+
const category = getFixCategory(g.ruleId);
|
|
33
|
+
const fixedCode = category === 'edit-element' || !category ? v.html : undefined;
|
|
34
|
+
return {
|
|
35
|
+
ruleId: g.ruleId,
|
|
36
|
+
selectors: g.selectors,
|
|
37
|
+
instanceCount: g.count,
|
|
38
|
+
explanation,
|
|
39
|
+
fixedCode,
|
|
40
|
+
fixCategory: getFixCategory(g.ruleId),
|
|
41
|
+
wcagReference: `WCAG 2.1 SC ${g.wcag}`,
|
|
42
|
+
optimalPrompt: prompt,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { buildPrompt } from './prompt.js';
|
|
2
|
+
import { buildPatchPrompt } from './patch-prompt.js';
|
|
3
|
+
import { groupViolations } from './group.js';
|
|
4
|
+
import { BaseAIProvider } from './base.js';
|
|
5
|
+
export class CohereProvider extends BaseAIProvider {
|
|
6
|
+
apiKey;
|
|
7
|
+
model;
|
|
8
|
+
constructor(apiKey, model = 'command-r-plus') {
|
|
9
|
+
super();
|
|
10
|
+
this.apiKey = apiKey;
|
|
11
|
+
this.model = model;
|
|
12
|
+
}
|
|
13
|
+
get headers() {
|
|
14
|
+
return {
|
|
15
|
+
'Content-Type': 'application/json',
|
|
16
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
async generateFixes(violations, strategy = 'rule', framework) {
|
|
20
|
+
if (violations.length === 0)
|
|
21
|
+
return [];
|
|
22
|
+
const groups = groupViolations(violations, strategy);
|
|
23
|
+
const prompt = buildPrompt(groups, framework);
|
|
24
|
+
const response = await fetch('https://api.cohere.com/v2/chat', {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: this.headers,
|
|
27
|
+
body: JSON.stringify({
|
|
28
|
+
model: this.model,
|
|
29
|
+
messages: [{ role: 'user', content: prompt }],
|
|
30
|
+
max_tokens: 8192,
|
|
31
|
+
temperature: 0.2,
|
|
32
|
+
}),
|
|
33
|
+
});
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
throw new Error(`Cohere API error: ${response.status} ${await response.text()}`);
|
|
36
|
+
}
|
|
37
|
+
const data = await response.json();
|
|
38
|
+
const text = data.message?.content?.[0]?.text ?? '[]';
|
|
39
|
+
return this.parse(text, groups, framework);
|
|
40
|
+
}
|
|
41
|
+
async generateFilePatch(fileContent, violations, filePath, framework) {
|
|
42
|
+
const prompt = buildPatchPrompt(fileContent, violations, filePath, framework);
|
|
43
|
+
const response = await fetch('https://api.cohere.com/v2/chat', {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: this.headers,
|
|
46
|
+
body: JSON.stringify({
|
|
47
|
+
model: this.model,
|
|
48
|
+
messages: [{ role: 'user', content: prompt }],
|
|
49
|
+
max_tokens: 16384,
|
|
50
|
+
temperature: 0.1,
|
|
51
|
+
}),
|
|
52
|
+
});
|
|
53
|
+
if (!response.ok)
|
|
54
|
+
throw new Error(`Cohere API error: ${response.status} ${await response.text()}`);
|
|
55
|
+
const data = await response.json();
|
|
56
|
+
return data.message?.content?.[0]?.text ?? '';
|
|
57
|
+
}
|
|
58
|
+
}
|