@diegovelasquezweb/a11y-engine 0.8.3 → 0.8.5
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 +10 -25
- package/docs/api-reference.md +371 -101
- package/docs/engine-manifest.md +8 -0
- package/package.json +1 -1
- package/src/ai/claude.mjs +17 -11
- package/src/ai/enrich.mjs +1 -1
- package/src/index.d.mts +18 -9
- package/src/index.mjs +9 -32
- package/src/pipeline/dom-scanner.mjs +2 -7
package/README.md
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# a11y Engine
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@diegovelasquezweb/a11y-engine)
|
|
2
4
|
|
|
3
5
|
Accessibility automation engine for web applications. It orchestrates multi engine scanning, stack aware enrichment, and report generation for apps and services through a stable API.
|
|
4
6
|
|
|
@@ -12,7 +14,8 @@ Accessibility automation engine for web applications. It orchestrates multi engi
|
|
|
12
14
|
| **Fix intelligence** | Enriches each finding with WCAG mapping, fix code snippets, framework and CMS specific notes, UI library ownership hints, effort estimates, and persona impact |
|
|
13
15
|
| **AI enrichment** | Optional Claude-powered analysis that adds contextual fix suggestions based on detected stack, repo structure, and finding patterns |
|
|
14
16
|
| **Report generation** | Produces HTML dashboard, PDF compliance report, manual testing checklist, and Markdown remediation guide |
|
|
15
|
-
| **Source code scanning** | Static regex analysis of project source for accessibility patterns that runtime engines cannot detect
|
|
17
|
+
| **Source code scanning** | Static regex analysis of project source for accessibility patterns that runtime engines cannot detect. Works with local paths or remote GitHub repos |
|
|
18
|
+
| **Knowledge API** | Exposes WCAG conformance levels, severity definitions, persona profiles, glossary, scanner help, and documentation so frontends and agents can render guidance from engine-owned data |
|
|
16
19
|
|
|
17
20
|
## Installation
|
|
18
21
|
|
|
@@ -58,13 +61,13 @@ const payload = await runAudit({
|
|
|
58
61
|
maxRoutes: 5,
|
|
59
62
|
axeTags: ["wcag2a", "wcag2aa", "best-practice"],
|
|
60
63
|
engines: { axe: true, cdp: true, pa11y: true },
|
|
61
|
-
repoUrl: "https://github.com/owner/repo", // optional
|
|
62
|
-
githubToken: process.env.GH_TOKEN, // optional
|
|
64
|
+
repoUrl: "https://github.com/owner/repo", // optional, enables source pattern scan and stack detection
|
|
65
|
+
githubToken: process.env.GH_TOKEN, // optional, required for private repos
|
|
63
66
|
ai: {
|
|
64
67
|
enabled: true,
|
|
65
68
|
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
66
69
|
githubToken: process.env.GH_TOKEN,
|
|
67
|
-
systemPrompt: "Custom prompt...", // optional
|
|
70
|
+
systemPrompt: "Custom prompt...", // optional, overrides default Claude system prompt
|
|
68
71
|
},
|
|
69
72
|
onProgress: (step, status, extra) => console.log(`${step}: ${status}`, extra),
|
|
70
73
|
});
|
|
@@ -135,25 +138,7 @@ See [API Reference](docs/api-reference.md) for exact options and return types.
|
|
|
135
138
|
|
|
136
139
|
## CLI
|
|
137
140
|
|
|
138
|
-
The package exposes an `a11y-audit` binary for terminal execution.
|
|
139
|
-
|
|
140
|
-
```bash
|
|
141
|
-
# Basic scan
|
|
142
|
-
pnpm exec a11y-audit --base-url https://example.com
|
|
143
|
-
|
|
144
|
-
# With source code pattern scanning via GitHub API (no clone)
|
|
145
|
-
pnpm exec a11y-audit --base-url https://example.com \
|
|
146
|
-
--repo-url https://github.com/owner/repo \
|
|
147
|
-
--github-token ghp_...
|
|
148
|
-
|
|
149
|
-
# With AI enrichment (set ANTHROPIC_API_KEY env var)
|
|
150
|
-
ANTHROPIC_API_KEY=sk-ant-... pnpm exec a11y-audit --base-url https://example.com
|
|
151
|
-
|
|
152
|
-
# With custom AI system prompt
|
|
153
|
-
AI_SYSTEM_PROMPT="You are..." ANTHROPIC_API_KEY=sk-ant-... pnpm exec a11y-audit --base-url https://example.com
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
See the [CLI Handbook](docs/cli-handbook.md) for all flags and examples.
|
|
141
|
+
The package exposes an `a11y-audit` binary for terminal execution. See the [CLI Handbook](docs/cli-handbook.md) for all flags, env vars, and examples.
|
|
157
142
|
|
|
158
143
|
## AI enrichment
|
|
159
144
|
|
|
@@ -163,7 +148,7 @@ When `ANTHROPIC_API_KEY` is set, the engine runs a post-scan enrichment step tha
|
|
|
163
148
|
- A production-quality code snippet in the correct framework syntax
|
|
164
149
|
- Context-aware suggestions when repo source files are available
|
|
165
150
|
|
|
166
|
-
AI output is stored in separate fields (`ai_fix_description`, `ai_fix_code`)
|
|
151
|
+
AI output is stored in separate fields (`ai_fix_description`, `ai_fix_code`). The original engine fixes are always preserved. Findings improved by AI are flagged with `aiEnhanced: true`.
|
|
167
152
|
|
|
168
153
|
The system prompt is fully customizable via `options.ai.systemPrompt` (programmatic API) or the `AI_SYSTEM_PROMPT` env var (CLI).
|
|
169
154
|
|
package/docs/api-reference.md
CHANGED
|
@@ -4,174 +4,444 @@
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Installation](#installation)
|
|
10
|
+
- [Import](#import)
|
|
11
|
+
- [End-to-end example](#end-to-end-example)
|
|
12
|
+
- [Core API](#core-api)
|
|
13
|
+
- [runAudit](#runauditoptions)
|
|
14
|
+
- [getFindings](#getfindingsinput-options)
|
|
15
|
+
- [getOverview](#getoverviewfindings-payload)
|
|
16
|
+
- [Output API](#output-api)
|
|
17
|
+
- [getPDFReport](#getpdfreportpayload-options)
|
|
18
|
+
- [getHTMLReport](#gethtmlreportpayload-options)
|
|
19
|
+
- [getChecklist](#getchecklistoptions)
|
|
20
|
+
- [getRemediationGuide](#getremediationguidepayload-options)
|
|
21
|
+
- [getSourcePatterns](#getsourcepatternsprojdir-options)
|
|
22
|
+
- [Knowledge API](#knowledge-api)
|
|
23
|
+
- [getKnowledge](#getknowledgeoptions)
|
|
24
|
+
- [getScannerHelp](#getscannerhelpoptions)
|
|
25
|
+
- [getPersonaReference](#getpersonareferenceoptions)
|
|
26
|
+
- [getUiHelp](#getuihelpoptions)
|
|
27
|
+
- [getConformanceLevels](#getconformancelevelsoptions)
|
|
28
|
+
- [getWcagPrinciples](#getwcagprinciplesoptions)
|
|
29
|
+
- [getSeverityLevels](#getseveritylevelsoptions)
|
|
8
30
|
|
|
9
|
-
|
|
31
|
+
---
|
|
10
32
|
|
|
11
|
-
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install @diegovelasquezweb/a11y-engine
|
|
37
|
+
npx playwright install chromium
|
|
38
|
+
npx puppeteer browsers install chrome
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Import
|
|
42
|
+
|
|
43
|
+
All functions are named exports from the package root:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import {
|
|
47
|
+
runAudit,
|
|
48
|
+
getFindings,
|
|
49
|
+
getOverview,
|
|
50
|
+
getPDFReport,
|
|
51
|
+
getHTMLReport,
|
|
52
|
+
getChecklist,
|
|
53
|
+
getRemediationGuide,
|
|
54
|
+
getSourcePatterns,
|
|
55
|
+
getKnowledge,
|
|
56
|
+
getScannerHelp,
|
|
57
|
+
getPersonaReference,
|
|
58
|
+
getUiHelp,
|
|
59
|
+
getConformanceLevels,
|
|
60
|
+
getWcagPrinciples,
|
|
61
|
+
getSeverityLevels,
|
|
62
|
+
} from "@diegovelasquezweb/a11y-engine";
|
|
63
|
+
```
|
|
12
64
|
|
|
13
|
-
|
|
65
|
+
---
|
|
14
66
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
67
|
+
## End-to-end example
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { runAudit, getFindings, getOverview } from "@diegovelasquezweb/a11y-engine";
|
|
71
|
+
|
|
72
|
+
// 1. Run the scan
|
|
73
|
+
const payload = await runAudit({
|
|
74
|
+
baseUrl: "https://example.com",
|
|
75
|
+
maxRoutes: 5,
|
|
76
|
+
engines: { axe: true, cdp: true, pa11y: true },
|
|
77
|
+
onProgress: (step, status) => console.log(`[${step}] ${status}`),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// 2. Get enriched findings
|
|
81
|
+
const findings = getFindings(payload);
|
|
82
|
+
|
|
83
|
+
// 3. Get compliance summary
|
|
84
|
+
const { score, scoreLabel, wcagStatus, totals, quickWins } = getOverview(findings, payload);
|
|
85
|
+
|
|
86
|
+
console.log(`Score: ${score}/100 (${scoreLabel})`);
|
|
87
|
+
console.log(`WCAG Status: ${wcagStatus}`);
|
|
88
|
+
console.log(`Critical: ${totals.Critical}, Serious: ${totals.Serious}`);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Core API
|
|
94
|
+
|
|
95
|
+
### `runAudit(options)`
|
|
96
|
+
|
|
97
|
+
Runs the full audit pipeline: route discovery → axe/CDP/pa11y scan → merge/dedup → analyzer enrichment → optional source pattern scanning → optional AI enrichment.
|
|
98
|
+
|
|
99
|
+
Returns a `ScanPayload` object consumed by all other functions.
|
|
100
|
+
|
|
101
|
+
**Options:**
|
|
102
|
+
|
|
103
|
+
| Option | Type | Default | Range / Values | Description |
|
|
104
|
+
| :--- | :--- | :--- | :--- | :--- |
|
|
105
|
+
| `baseUrl` | `string` | — | Required | Starting URL including protocol (`https://` or `http://`) |
|
|
106
|
+
| `maxRoutes` | `number` | `10` | 1 – 50 | Maximum unique pages to discover and scan |
|
|
107
|
+
| `crawlDepth` | `number` | `2` | 1 – 3 | BFS link-follow depth from `baseUrl`. Has no effect when `routes` is set |
|
|
108
|
+
| `routes` | `string` | — | CSV paths | Explicit paths to scan (e.g. `"/,/about,/contact"`). Overrides auto-discovery entirely |
|
|
109
|
+
| `waitUntil` | `string` | `"domcontentloaded"` | `"domcontentloaded"` \| `"load"` \| `"networkidle"` | Page load strategy. Use `"networkidle"` for SPAs that render after `DOMContentLoaded` |
|
|
110
|
+
| `waitMs` | `number` | `2000` | 0 – 10000 | Fixed delay (ms) after page load before engines run |
|
|
111
|
+
| `timeoutMs` | `number` | `30000` | 5000 – 120000 | Network timeout per page (ms) |
|
|
112
|
+
| `headless` | `boolean` | `true` | — | Set `false` to open a visible browser for debugging |
|
|
113
|
+
| `viewport` | `object` | `{ width: 1280, height: 800 }` | width: 320–2560, height: 320–2560 | Browser viewport dimensions in pixels |
|
|
114
|
+
| `colorScheme` | `string` | `"light"` | `"light"` \| `"dark"` | Emulates `prefers-color-scheme` media query |
|
|
115
|
+
| `engines` | `object` | all `true` | `{ axe?, cdp?, pa11y? }` | Which engines to run. At least one must be enabled |
|
|
116
|
+
| `axeTags` | `string[]` | WCAG 2.x A+AA | See below | axe-core rule tag filter. Also determines pa11y standard |
|
|
117
|
+
| `onlyRule` | `string` | — | axe rule ID | Run a single axe rule only (e.g. `"color-contrast"`) |
|
|
118
|
+
| `ignoreFindings` | `string[]` | — | axe rule IDs | Suppress specific rules from output entirely |
|
|
119
|
+
| `excludeSelectors` | `string[]` | — | CSS selectors | Skip elements matching these selectors during axe scan |
|
|
120
|
+
| `framework` | `string` | auto-detected | See below | Override framework detection for fix notes and source boundaries |
|
|
121
|
+
| `projectDir` | `string` | — | local path | Local project source directory. Enables source pattern scanning and package.json stack detection |
|
|
122
|
+
| `repoUrl` | `string` | — | GitHub URL | Remote repo URL. Enables source pattern scanning via GitHub API — no clone required |
|
|
123
|
+
| `githubToken` | `string` | — | GitHub PAT | Increases GitHub API rate limit from 60 to 5,000 req/hr. Required for private repos |
|
|
124
|
+
| `skipPatterns` | `boolean` | `false` | — | Disable source pattern scanning even when `projectDir` or `repoUrl` is set |
|
|
125
|
+
| `screenshotsDir` | `string` | `.audit/screenshots` | dir path | Directory where element screenshots are saved |
|
|
126
|
+
| `ai.enabled` | `boolean` | `false` | — | Enable Claude AI enrichment for Critical and Serious findings |
|
|
127
|
+
| `ai.apiKey` | `string` | — | Anthropic API key | Required when `ai.enabled` is `true` |
|
|
128
|
+
| `ai.githubToken` | `string` | — | GitHub PAT | Used to fetch source files from the repo for AI context |
|
|
129
|
+
| `ai.model` | `string` | `"claude-haiku-4-5-20251001"` | Anthropic model ID | Claude model to use |
|
|
130
|
+
| `ai.systemPrompt` | `string` | Built-in prompt | — | Overrides the default Claude system prompt for the entire scan |
|
|
131
|
+
| `onProgress` | `function` | — | — | Callback fired at each pipeline step |
|
|
132
|
+
|
|
133
|
+
**`axeTags` common values:**
|
|
134
|
+
|
|
135
|
+
| Tag | Covers |
|
|
44
136
|
| :--- | :--- |
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
51
|
-
| `
|
|
52
|
-
| `
|
|
53
|
-
|
|
137
|
+
| `wcag2a` | WCAG 2.0 Level A |
|
|
138
|
+
| `wcag2aa` | WCAG 2.0 Level AA |
|
|
139
|
+
| `wcag21a` | WCAG 2.1 Level A additions |
|
|
140
|
+
| `wcag21aa` | WCAG 2.1 Level AA additions |
|
|
141
|
+
| `wcag22a` | WCAG 2.2 Level A additions |
|
|
142
|
+
| `wcag22aa` | WCAG 2.2 Level AA additions |
|
|
143
|
+
| `wcag2aaa` | WCAG 2.0 Level AAA |
|
|
144
|
+
| `best-practice` | Non-WCAG best practices |
|
|
145
|
+
|
|
146
|
+
**Supported `framework` values:** `nextjs`, `gatsby`, `react`, `nuxt`, `vue`, `angular`, `astro`, `svelte`, `remix`, `shopify`, `wordpress`, `drupal`
|
|
147
|
+
|
|
148
|
+
**`onProgress` callback:**
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
onProgress: (step, status, extra) => {
|
|
152
|
+
// step: "page" | "axe" | "cdp" | "pa11y" | "merge" | "intelligence" | "repo" | "patterns" | "ai"
|
|
153
|
+
// status: "running" | "done" | "error" | "skipped"
|
|
154
|
+
// extra: { found?: number, merged?: number, ... } — step-specific data
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
const payload = await runAudit({
|
|
160
|
+
baseUrl: "https://example.com",
|
|
161
|
+
maxRoutes: 5,
|
|
162
|
+
engines: { axe: true, cdp: true, pa11y: true },
|
|
163
|
+
repoUrl: "https://github.com/owner/repo",
|
|
164
|
+
githubToken: process.env.GH_TOKEN,
|
|
165
|
+
ai: { enabled: true, apiKey: process.env.ANTHROPIC_API_KEY },
|
|
166
|
+
onProgress: (step, status) => console.log(`[${step}] ${status}`),
|
|
167
|
+
});
|
|
168
|
+
```
|
|
54
169
|
|
|
55
170
|
Returns: `Promise<ScanPayload>`
|
|
56
171
|
|
|
57
|
-
> **`ai_enriched_findings` fast path**: When AI enrichment runs,
|
|
172
|
+
> **`ai_enriched_findings` fast path**: When AI enrichment runs, `getFindings()` uses `payload.ai_enriched_findings` directly instead of re-normalizing the raw findings array.
|
|
58
173
|
|
|
59
|
-
|
|
174
|
+
---
|
|
60
175
|
|
|
61
|
-
|
|
176
|
+
### `getFindings(input, options?)`
|
|
62
177
|
|
|
63
|
-
-
|
|
64
|
-
|
|
65
|
-
|
|
178
|
+
Normalizes raw scan results into enriched, UI-ready findings sorted by severity.
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
import { getFindings } from "@diegovelasquezweb/a11y-engine";
|
|
182
|
+
|
|
183
|
+
const findings = getFindings(payload, {
|
|
184
|
+
// Optional: rewrite internal screenshot paths to app URLs
|
|
185
|
+
screenshotUrlBuilder: (rawPath) =>
|
|
186
|
+
`/api/scan/${scanId}/screenshot?path=${encodeURIComponent(rawPath)}`,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// findings[0] example:
|
|
190
|
+
// {
|
|
191
|
+
// id: "A11Y-001",
|
|
192
|
+
// ruleId: "color-contrast",
|
|
193
|
+
// title: "Elements must meet minimum color contrast ratio thresholds",
|
|
194
|
+
// severity: "Serious",
|
|
195
|
+
// wcag: "1.4.3",
|
|
196
|
+
// selector: ".hero-text",
|
|
197
|
+
// actual: "Element has insufficient color contrast of 2.5:1 ...",
|
|
198
|
+
// expected: "Text contrast ratio must be at least 4.5:1 ...",
|
|
199
|
+
// fixDescription: "Increase the foreground color contrast ...",
|
|
200
|
+
// fixCode: "/* Change #aaa to #767676 */",
|
|
201
|
+
// effort: "low",
|
|
202
|
+
// aiEnhanced: true, // present when AI ran
|
|
203
|
+
// aiFixDescription: "...", // Claude-generated (more specific)
|
|
204
|
+
// aiFixCode: "...", // Claude-generated code snippet
|
|
205
|
+
// }
|
|
206
|
+
```
|
|
66
207
|
|
|
67
208
|
Returns: `EnrichedFinding[]`
|
|
68
209
|
|
|
69
|
-
|
|
210
|
+
---
|
|
70
211
|
|
|
71
|
-
|
|
212
|
+
### `getOverview(findings, payload?)`
|
|
72
213
|
|
|
73
|
-
|
|
74
|
-
|
|
214
|
+
Computes the compliance score, WCAG status, severity totals, persona groups, and quick wins from enriched findings.
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
import { getFindings, getOverview } from "@diegovelasquezweb/a11y-engine";
|
|
218
|
+
|
|
219
|
+
const findings = getFindings(payload);
|
|
220
|
+
const overview = getOverview(findings, payload);
|
|
221
|
+
|
|
222
|
+
// overview example:
|
|
223
|
+
// {
|
|
224
|
+
// score: 72, // 0–100. Formula: 100 - (Critical×15) - (Serious×5) - (Moderate×2) - (Minor×0.5)
|
|
225
|
+
// label: "Fair", // "Excellent" (90–100) | "Good" (75–89) | "Fair" (55–74) | "Poor" (35–54) | "Critical" (0–34)
|
|
226
|
+
// wcagStatus: "Fail", // "Pass" | "Conditional Pass" | "Fail"
|
|
227
|
+
// totals: { Critical: 1, Serious: 3, Moderate: 5, Minor: 2 },
|
|
228
|
+
// personaGroups: {
|
|
229
|
+
// screenReader: { count: 4, findings: [...] },
|
|
230
|
+
// keyboard: { count: 2, findings: [...] },
|
|
231
|
+
// vision: { count: 3, findings: [...] },
|
|
232
|
+
// cognitive: { count: 1, findings: [...] },
|
|
233
|
+
// },
|
|
234
|
+
// quickWins: [...], // top Critical/Serious findings with fix code ready
|
|
235
|
+
// targetUrl: "https://example.com",
|
|
236
|
+
// detectedStack: { framework: "nextjs", cms: null, uiLibraries: ["radix-ui"] },
|
|
237
|
+
// }
|
|
238
|
+
```
|
|
75
239
|
|
|
76
240
|
Returns: `AuditSummary`
|
|
77
241
|
|
|
242
|
+
---
|
|
243
|
+
|
|
78
244
|
## Output API
|
|
79
245
|
|
|
80
246
|
### `getPDFReport(payload, options?)`
|
|
81
247
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
248
|
+
Generates a formal A4 PDF compliance report.
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
import { getPDFReport } from "@diegovelasquezweb/a11y-engine";
|
|
252
|
+
|
|
253
|
+
const { buffer, contentType } = await getPDFReport(payload, {
|
|
254
|
+
baseUrl: "https://example.com",
|
|
255
|
+
target: "WCAG 2.2 AA",
|
|
256
|
+
});
|
|
86
257
|
|
|
87
|
-
|
|
258
|
+
// In a Next.js API route:
|
|
259
|
+
return new Response(buffer, { headers: { "Content-Type": contentType } });
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Returns: `Promise<{ buffer: Buffer, contentType: string }>`
|
|
263
|
+
|
|
264
|
+
---
|
|
88
265
|
|
|
89
266
|
### `getHTMLReport(payload, options?)`
|
|
90
267
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
- `screenshotsDir?: string`
|
|
268
|
+
Generates an interactive HTML audit dashboard with finding cards, score gauge, and persona breakdown.
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
import { getHTMLReport } from "@diegovelasquezweb/a11y-engine";
|
|
96
272
|
|
|
97
|
-
|
|
273
|
+
const { html, contentType } = await getHTMLReport(payload, {
|
|
274
|
+
baseUrl: "https://example.com",
|
|
275
|
+
screenshotsDir: "/path/to/screenshots",
|
|
276
|
+
});
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Returns: `Promise<{ html: string, contentType: string }>`
|
|
280
|
+
|
|
281
|
+
---
|
|
98
282
|
|
|
99
283
|
### `getChecklist(options?)`
|
|
100
284
|
|
|
101
|
-
|
|
102
|
-
|
|
285
|
+
Generates an interactive HTML manual testing checklist with 41 WCAG checks.
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
import { getChecklist } from "@diegovelasquezweb/a11y-engine";
|
|
103
289
|
|
|
104
|
-
|
|
290
|
+
const { html, contentType } = await getChecklist({
|
|
291
|
+
baseUrl: "https://example.com",
|
|
292
|
+
});
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Returns: `Promise<{ html: string, contentType: string }>`
|
|
296
|
+
|
|
297
|
+
---
|
|
105
298
|
|
|
106
299
|
### `getRemediationGuide(payload, options?)`
|
|
107
300
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
301
|
+
Generates a Markdown remediation guide optimized for AI agents and developers. Includes finding details, fix code, verify commands, and source pattern findings.
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
import { getRemediationGuide } from "@diegovelasquezweb/a11y-engine";
|
|
305
|
+
|
|
306
|
+
const { markdown, contentType } = await getRemediationGuide(payload, {
|
|
307
|
+
baseUrl: "https://example.com",
|
|
308
|
+
patternFindings: payload.patternFindings ?? null,
|
|
309
|
+
});
|
|
113
310
|
|
|
114
|
-
|
|
311
|
+
// Write to disk or return as download
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Returns: `Promise<{ markdown: string, contentType: string }>`
|
|
315
|
+
|
|
316
|
+
---
|
|
115
317
|
|
|
116
318
|
### `getSourcePatterns(projectDir, options?)`
|
|
117
319
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
320
|
+
Scans a local project directory for source code accessibility patterns that runtime engines cannot detect.
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
import { getSourcePatterns } from "@diegovelasquezweb/a11y-engine";
|
|
324
|
+
|
|
325
|
+
const result = await getSourcePatterns("./", {
|
|
326
|
+
framework: "nextjs", // optional — scopes scan to framework source dirs
|
|
327
|
+
onlyPattern: "placeholder-only-label", // optional — run a single pattern
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// result example:
|
|
331
|
+
// {
|
|
332
|
+
// findings: [
|
|
333
|
+
// {
|
|
334
|
+
// id: "PAT-a1b2c3",
|
|
335
|
+
// pattern_id: "placeholder-only-label",
|
|
336
|
+
// title: "Input uses placeholder as its only label",
|
|
337
|
+
// severity: "Critical",
|
|
338
|
+
// status: "confirmed",
|
|
339
|
+
// file: "src/components/SearchBar.tsx",
|
|
340
|
+
// line: 12,
|
|
341
|
+
// match: ' <input placeholder="Search..." />',
|
|
342
|
+
// context: "...",
|
|
343
|
+
// fix_description: "Add an aria-label or visible <label> element",
|
|
344
|
+
// }
|
|
345
|
+
// ],
|
|
346
|
+
// summary: { total: 3, confirmed: 2, potential: 1 }
|
|
347
|
+
// }
|
|
348
|
+
```
|
|
122
349
|
|
|
123
350
|
Returns: `Promise<SourcePatternResult>`
|
|
124
351
|
|
|
352
|
+
---
|
|
353
|
+
|
|
125
354
|
## Knowledge API
|
|
126
355
|
|
|
356
|
+
These functions expose engine-owned content for UIs and agents to render. All accept an optional `{ locale?: string }` option (default: `"en"`).
|
|
357
|
+
|
|
358
|
+
### `getKnowledge(options?)`
|
|
359
|
+
|
|
360
|
+
Returns the full knowledge pack — combines all knowledge functions into one call. Useful for pre-loading UI help content.
|
|
361
|
+
|
|
362
|
+
```ts
|
|
363
|
+
import { getKnowledge } from "@diegovelasquezweb/a11y-engine";
|
|
364
|
+
|
|
365
|
+
const knowledge = getKnowledge({ locale: "en" });
|
|
366
|
+
|
|
367
|
+
// knowledge.scanner → scan options help and engine descriptions
|
|
368
|
+
// knowledge.personas → persona labels, icons, descriptions
|
|
369
|
+
// knowledge.concepts → UI concept definitions
|
|
370
|
+
// knowledge.glossary → accessibility glossary
|
|
371
|
+
// knowledge.conformanceLevels → WCAG A/AA/AAA definitions with axe tags
|
|
372
|
+
// knowledge.wcagPrinciples → the 4 WCAG principles
|
|
373
|
+
// knowledge.severityLevels → Critical/Serious/Moderate/Minor definitions
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Returns: `EngineKnowledge`
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
127
380
|
### `getScannerHelp(options?)`
|
|
128
381
|
|
|
129
|
-
|
|
130
|
-
- `locale?: string`
|
|
382
|
+
Returns scan option descriptions, allowed values, and engine metadata — used to render Advanced Settings UI.
|
|
131
383
|
|
|
132
|
-
|
|
384
|
+
```ts
|
|
385
|
+
const help = getScannerHelp();
|
|
386
|
+
// help.engines → [{ id: "axe", label: "axe-core", description: "..." }, ...]
|
|
387
|
+
// help.options → [{ id: "maxRoutes", label: "Max Routes", type: "number", ... }, ...]
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
---
|
|
133
391
|
|
|
134
392
|
### `getPersonaReference(options?)`
|
|
135
393
|
|
|
136
|
-
|
|
137
|
-
- `locale?: string`
|
|
394
|
+
Returns persona labels, descriptions, and disability group definitions.
|
|
138
395
|
|
|
139
|
-
|
|
396
|
+
```ts
|
|
397
|
+
const ref = getPersonaReference();
|
|
398
|
+
// ref.personas → [{ id: "screenReader", label: "Screen Readers", icon: "...", description: "..." }, ...]
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
---
|
|
140
402
|
|
|
141
403
|
### `getUiHelp(options?)`
|
|
142
404
|
|
|
143
|
-
|
|
144
|
-
- `locale?: string`
|
|
405
|
+
Returns shared concept definitions and a glossary of accessibility terms.
|
|
145
406
|
|
|
146
|
-
|
|
407
|
+
```ts
|
|
408
|
+
const ui = getUiHelp();
|
|
409
|
+
// ui.concepts → { wcag: "...", aria: "...", ... }
|
|
410
|
+
// ui.glossary → [{ term: "ARIA", definition: "..." }, ...]
|
|
411
|
+
```
|
|
147
412
|
|
|
148
|
-
|
|
413
|
+
---
|
|
149
414
|
|
|
150
|
-
|
|
151
|
-
- `locale?: string`
|
|
415
|
+
### `getConformanceLevels(options?)`
|
|
152
416
|
|
|
153
|
-
Returns
|
|
417
|
+
Returns WCAG conformance level definitions with their corresponding axe-core tag sets.
|
|
154
418
|
|
|
155
|
-
|
|
419
|
+
```ts
|
|
420
|
+
const { conformanceLevels } = getConformanceLevels();
|
|
421
|
+
// conformanceLevels[0] → { id: "AA", label: "WCAG 2.2 AA", axeTags: ["wcag2a", "wcag2aa", ...] }
|
|
422
|
+
```
|
|
156
423
|
|
|
157
|
-
|
|
158
|
-
- `locale?: string`
|
|
424
|
+
---
|
|
159
425
|
|
|
160
|
-
|
|
426
|
+
### `getWcagPrinciples(options?)`
|
|
161
427
|
|
|
162
|
-
|
|
428
|
+
Returns the four WCAG principles (Perceivable, Operable, Understandable, Robust) with criterion prefix patterns.
|
|
163
429
|
|
|
164
|
-
|
|
165
|
-
|
|
430
|
+
```ts
|
|
431
|
+
const { wcagPrinciples } = getWcagPrinciples();
|
|
432
|
+
// wcagPrinciples[0] → { id: "perceivable", label: "Perceivable", prefix: "1.", description: "..." }
|
|
433
|
+
```
|
|
166
434
|
|
|
167
|
-
|
|
435
|
+
---
|
|
168
436
|
|
|
169
|
-
### `
|
|
437
|
+
### `getSeverityLevels(options?)`
|
|
170
438
|
|
|
171
|
-
|
|
172
|
-
- `locale?: string`
|
|
439
|
+
Returns severity level definitions with labels, descriptions, and ordering.
|
|
173
440
|
|
|
174
|
-
|
|
441
|
+
```ts
|
|
442
|
+
const { severityLevels } = getSeverityLevels();
|
|
443
|
+
// severityLevels[0] → { id: "Critical", label: "Critical", order: 0, description: "..." }
|
|
444
|
+
```
|
|
175
445
|
|
|
176
446
|
---
|
|
177
447
|
|
package/docs/engine-manifest.md
CHANGED
|
@@ -4,6 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Source Modules](#1-source-modules)
|
|
10
|
+
- [Asset Modules](#2-asset-modules)
|
|
11
|
+
- [Test Suite](#3-test-suite)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
7
15
|
This document is the current technical inventory of the engine package.
|
|
8
16
|
|
|
9
17
|
## 1) Source Modules
|
package/package.json
CHANGED
package/src/ai/claude.mjs
CHANGED
|
@@ -25,18 +25,10 @@ const MAX_AI_FINDINGS = 20; // cap to control cost
|
|
|
25
25
|
* @param {object} context
|
|
26
26
|
* @returns {string}
|
|
27
27
|
*/
|
|
28
|
-
|
|
29
|
-
const { framework, cms, uiLibraries } = context.stack || {};
|
|
30
|
-
|
|
31
|
-
let stackInfo = "";
|
|
32
|
-
if (framework) stackInfo += `Framework: ${framework}\n`;
|
|
33
|
-
if (cms) stackInfo += `CMS: ${cms}\n`;
|
|
34
|
-
if (uiLibraries?.length) stackInfo += `UI Libraries: ${uiLibraries.join(", ")}\n`;
|
|
35
|
-
|
|
36
|
-
return `You are an expert web accessibility engineer specializing in WCAG 2.2 AA remediation.
|
|
28
|
+
export const DEFAULT_AI_SYSTEM_PROMPT = `You are an expert web accessibility engineer specializing in WCAG 2.2 AA remediation.
|
|
37
29
|
|
|
38
30
|
Your task is to provide a developer-friendly AI hint for each accessibility finding — something MORE USEFUL than the generic automated fix already provided.
|
|
39
|
-
|
|
31
|
+
|
|
40
32
|
For each finding, provide:
|
|
41
33
|
1. fixDescription: A 2-3 sentence explanation that goes BEYOND the generic fix. Explain WHY this matters for real users, WHAT specifically to look for in the codebase, and HOW to verify the fix works. Be specific to the selector and actual violation data provided.
|
|
42
34
|
2. fixCode: A ready-to-use, production-quality code snippet in the correct syntax for the stack. Do NOT copy the existing fix code — write a BETTER, more complete example that a developer can use directly.
|
|
@@ -48,6 +40,20 @@ Rules:
|
|
|
48
40
|
- Reference the actual selector or element from the finding when possible
|
|
49
41
|
- If the violation data contains specific values (colors, ratios, labels), use them in your response
|
|
50
42
|
- Respond in JSON only — no markdown, no explanation outside the JSON structure`;
|
|
43
|
+
|
|
44
|
+
function buildSystemPrompt(context) {
|
|
45
|
+
const { framework, cms, uiLibraries } = context.stack || {};
|
|
46
|
+
|
|
47
|
+
let stackInfo = "";
|
|
48
|
+
if (framework) stackInfo += `Framework: ${framework}\n`;
|
|
49
|
+
if (cms) stackInfo += `CMS: ${cms}\n`;
|
|
50
|
+
if (uiLibraries?.length) stackInfo += `UI Libraries: ${uiLibraries.join(", ")}\n`;
|
|
51
|
+
|
|
52
|
+
const base = DEFAULT_AI_SYSTEM_PROMPT;
|
|
53
|
+
return stackInfo ? base.replace(
|
|
54
|
+
"For each finding, provide:",
|
|
55
|
+
`Project context:\n${stackInfo}\nFor each finding, provide:`
|
|
56
|
+
) : base;
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
/**
|
|
@@ -230,7 +236,7 @@ async function fetchSourceFilesForFindings(findings, repoUrl, githubToken) {
|
|
|
230
236
|
try {
|
|
231
237
|
const content = await fetchRepoFile(repoUrl, filePath, githubToken);
|
|
232
238
|
if (content) sourceFiles[filePath] = content;
|
|
233
|
-
} catch {
|
|
239
|
+
} catch { }
|
|
234
240
|
}
|
|
235
241
|
}
|
|
236
242
|
|
package/src/ai/enrich.mjs
CHANGED
|
@@ -74,6 +74,6 @@ async function main() {
|
|
|
74
74
|
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
75
75
|
main().catch((err) => {
|
|
76
76
|
log.warn(`AI enrichment failed (non-fatal): ${err.message}`);
|
|
77
|
-
process.exit(0);
|
|
77
|
+
process.exit(0);
|
|
78
78
|
});
|
|
79
79
|
}
|
package/src/index.d.mts
CHANGED
|
@@ -403,15 +403,6 @@ export interface RunAuditOptions {
|
|
|
403
403
|
onProgress?: (step: string, status: string, extra?: Record<string, unknown>) => void;
|
|
404
404
|
}
|
|
405
405
|
|
|
406
|
-
export interface AiOptions {
|
|
407
|
-
enabled?: boolean;
|
|
408
|
-
apiKey?: string;
|
|
409
|
-
githubToken?: string;
|
|
410
|
-
model?: string;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
406
|
|
|
416
407
|
|
|
417
408
|
export interface EnrichmentOptions {
|
|
@@ -471,3 +462,21 @@ export function getWcagPrinciples(options?: KnowledgeOptions): WcagPrinciplesRes
|
|
|
471
462
|
export function getSeverityLevels(options?: KnowledgeOptions): SeverityLevelsResult;
|
|
472
463
|
|
|
473
464
|
export function getKnowledge(options?: KnowledgeOptions): EngineKnowledge;
|
|
465
|
+
|
|
466
|
+
export const DEFAULT_AI_SYSTEM_PROMPT: string;
|
|
467
|
+
|
|
468
|
+
export interface ViewportPreset {
|
|
469
|
+
label: string;
|
|
470
|
+
width: number;
|
|
471
|
+
height: number;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
export const VIEWPORT_PRESETS: ViewportPreset[];
|
|
475
|
+
|
|
476
|
+
export interface AiOptions {
|
|
477
|
+
enabled?: boolean;
|
|
478
|
+
apiKey?: string;
|
|
479
|
+
githubToken?: string;
|
|
480
|
+
model?: string;
|
|
481
|
+
systemPrompt?: string;
|
|
482
|
+
}
|
package/src/index.mjs
CHANGED
|
@@ -5,10 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { ASSET_PATHS, loadAssetJson } from "./core/asset-loader.mjs";
|
|
8
|
+
export { DEFAULT_AI_SYSTEM_PROMPT } from "./ai/claude.mjs";
|
|
8
9
|
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
10
|
// Lazy-loaded asset cache
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
11
|
|
|
13
12
|
let _intelligence = null;
|
|
14
13
|
let _pa11yConfig = null;
|
|
@@ -58,9 +57,7 @@ function resolveKnowledgeLocale(locale = "en") {
|
|
|
58
57
|
return "en";
|
|
59
58
|
}
|
|
60
59
|
|
|
61
|
-
// ---------------------------------------------------------------------------
|
|
62
60
|
// Pa11y rule canonicalization (internal)
|
|
63
|
-
// ---------------------------------------------------------------------------
|
|
64
61
|
|
|
65
62
|
function normalizePa11yToken(value) {
|
|
66
63
|
return value
|
|
@@ -97,9 +94,7 @@ function mapPa11yRuleToCanonical(ruleId, sourceRuleId = null, checkData = null)
|
|
|
97
94
|
return ruleId;
|
|
98
95
|
}
|
|
99
96
|
|
|
100
|
-
// ---------------------------------------------------------------------------
|
|
101
97
|
// Raw finding normalization (internal)
|
|
102
|
-
// ---------------------------------------------------------------------------
|
|
103
98
|
|
|
104
99
|
const SEVERITY_ORDER = { Critical: 1, Serious: 2, Moderate: 3, Minor: 4 };
|
|
105
100
|
|
|
@@ -169,9 +164,7 @@ function normalizeSingleFinding(item, index, screenshotUrlBuilder) {
|
|
|
169
164
|
};
|
|
170
165
|
}
|
|
171
166
|
|
|
172
|
-
// ---------------------------------------------------------------------------
|
|
173
167
|
// Finding enrichment
|
|
174
|
-
// ---------------------------------------------------------------------------
|
|
175
168
|
|
|
176
169
|
/**
|
|
177
170
|
* Normalizes and enriches raw findings with intelligence data.
|
|
@@ -188,19 +181,16 @@ export function getFindings(input, options = {}) {
|
|
|
188
181
|
const { screenshotUrlBuilder = null } = options;
|
|
189
182
|
const rules = getIntelligence().rules || {};
|
|
190
183
|
|
|
191
|
-
// If AI enrichment ran, return those findings directly (already normalized + enriched)
|
|
192
184
|
if (input?.ai_enriched_findings?.length > 0 && !screenshotUrlBuilder) {
|
|
193
185
|
return input.ai_enriched_findings;
|
|
194
186
|
}
|
|
195
187
|
|
|
196
188
|
const rawFindings = input?.findings || [];
|
|
197
189
|
|
|
198
|
-
// Normalize raw findings
|
|
199
190
|
const normalized = rawFindings.map((item, index) =>
|
|
200
191
|
normalizeSingleFinding(item, index, screenshotUrlBuilder)
|
|
201
192
|
);
|
|
202
193
|
|
|
203
|
-
// Enrich with intelligence and output camelCase-only findings
|
|
204
194
|
const enriched = normalized.map((finding) => {
|
|
205
195
|
const canonical = mapPa11yRuleToCanonical(
|
|
206
196
|
finding.rule_id,
|
|
@@ -208,7 +198,6 @@ export function getFindings(input, options = {}) {
|
|
|
208
198
|
finding.check_data,
|
|
209
199
|
);
|
|
210
200
|
|
|
211
|
-
// Build camelCase-only enriched finding
|
|
212
201
|
const enrichedFinding = {
|
|
213
202
|
id: finding.id,
|
|
214
203
|
ruleId: canonical,
|
|
@@ -238,7 +227,7 @@ export function getFindings(input, options = {}) {
|
|
|
238
227
|
recommendedFix: finding.recommended_fix,
|
|
239
228
|
evidence: finding.evidence,
|
|
240
229
|
totalInstances: finding.total_instances,
|
|
241
|
-
effort: finding.effort,
|
|
230
|
+
effort: finding.effort ?? (finding.fix_code ? "low" : "high"),
|
|
242
231
|
relatedRules: finding.related_rules,
|
|
243
232
|
screenshotPath: finding.screenshot_path,
|
|
244
233
|
falsePositiveRisk: finding.false_positive_risk,
|
|
@@ -280,7 +269,6 @@ export function getFindings(input, options = {}) {
|
|
|
280
269
|
return enrichedFinding;
|
|
281
270
|
});
|
|
282
271
|
|
|
283
|
-
// Sort by severity then by ID
|
|
284
272
|
enriched.sort((a, b) => {
|
|
285
273
|
const sa = SEVERITY_ORDER[a.severity] ?? 99;
|
|
286
274
|
const sb = SEVERITY_ORDER[b.severity] ?? 99;
|
|
@@ -291,9 +279,7 @@ export function getFindings(input, options = {}) {
|
|
|
291
279
|
return enriched;
|
|
292
280
|
}
|
|
293
281
|
|
|
294
|
-
// ---------------------------------------------------------------------------
|
|
295
282
|
// Score computation (internal)
|
|
296
|
-
// ---------------------------------------------------------------------------
|
|
297
283
|
|
|
298
284
|
function getComplianceScore(totals) {
|
|
299
285
|
const config = getComplianceConfig();
|
|
@@ -324,9 +310,7 @@ function getComplianceScore(totals) {
|
|
|
324
310
|
return { score, label, wcagStatus };
|
|
325
311
|
}
|
|
326
312
|
|
|
327
|
-
// ---------------------------------------------------------------------------
|
|
328
313
|
// Persona grouping (internal)
|
|
329
|
-
// ---------------------------------------------------------------------------
|
|
330
314
|
|
|
331
315
|
function getPersonaGroups(findings) {
|
|
332
316
|
const ref = getWcagReference();
|
|
@@ -383,9 +367,7 @@ function getPersonaGroups(findings) {
|
|
|
383
367
|
return groups;
|
|
384
368
|
}
|
|
385
369
|
|
|
386
|
-
// ---------------------------------------------------------------------------
|
|
387
370
|
// Audit summary
|
|
388
|
-
// ---------------------------------------------------------------------------
|
|
389
371
|
|
|
390
372
|
/**
|
|
391
373
|
* Computes a complete audit summary from enriched findings: severity totals,
|
|
@@ -443,9 +425,7 @@ export function getOverview(findings, payload = null) {
|
|
|
443
425
|
};
|
|
444
426
|
}
|
|
445
427
|
|
|
446
|
-
// ---------------------------------------------------------------------------
|
|
447
428
|
// Knowledge APIs
|
|
448
|
-
// ---------------------------------------------------------------------------
|
|
449
429
|
|
|
450
430
|
/**
|
|
451
431
|
* Returns scanner-facing help metadata including engine descriptions,
|
|
@@ -583,6 +563,13 @@ export function getSeverityLevels(options = {}) {
|
|
|
583
563
|
};
|
|
584
564
|
}
|
|
585
565
|
|
|
566
|
+
export const VIEWPORT_PRESETS = [
|
|
567
|
+
{ label: "Desktop", width: 1280, height: 800 },
|
|
568
|
+
{ label: "Laptop", width: 1440, height: 900 },
|
|
569
|
+
{ label: "Tablet", width: 768, height: 1024 },
|
|
570
|
+
{ label: "Mobile", width: 375, height: 812 },
|
|
571
|
+
];
|
|
572
|
+
|
|
586
573
|
export function getKnowledge(options = {}) {
|
|
587
574
|
const scanner = getScannerHelp(options);
|
|
588
575
|
const personas = getPersonaReference(options);
|
|
@@ -611,9 +598,7 @@ export function getKnowledge(options = {}) {
|
|
|
611
598
|
};
|
|
612
599
|
}
|
|
613
600
|
|
|
614
|
-
// ---------------------------------------------------------------------------
|
|
615
601
|
// Full audit pipeline
|
|
616
|
-
// ---------------------------------------------------------------------------
|
|
617
602
|
|
|
618
603
|
/**
|
|
619
604
|
* Runs a complete accessibility audit: crawl + scan (axe + CDP + pa11y) + analyze.
|
|
@@ -818,9 +803,7 @@ export async function runAudit(options) {
|
|
|
818
803
|
return findingsPayload;
|
|
819
804
|
}
|
|
820
805
|
|
|
821
|
-
// ---------------------------------------------------------------------------
|
|
822
806
|
// Report generation
|
|
823
|
-
// ---------------------------------------------------------------------------
|
|
824
807
|
|
|
825
808
|
import {
|
|
826
809
|
normalizeFindings as normalizeForReports,
|
|
@@ -1003,9 +986,7 @@ export async function getChecklist(options = {}) {
|
|
|
1003
986
|
};
|
|
1004
987
|
}
|
|
1005
988
|
|
|
1006
|
-
// ---------------------------------------------------------------------------
|
|
1007
989
|
// HTML Report
|
|
1008
|
-
// ---------------------------------------------------------------------------
|
|
1009
990
|
|
|
1010
991
|
/**
|
|
1011
992
|
* Generates an interactive HTML audit dashboard from raw scan findings.
|
|
@@ -1135,9 +1116,7 @@ export async function getHTMLReport(payload, options = {}) {
|
|
|
1135
1116
|
};
|
|
1136
1117
|
}
|
|
1137
1118
|
|
|
1138
|
-
// ---------------------------------------------------------------------------
|
|
1139
1119
|
// Remediation Guide (Markdown)
|
|
1140
|
-
// ---------------------------------------------------------------------------
|
|
1141
1120
|
|
|
1142
1121
|
/**
|
|
1143
1122
|
* Generates a Markdown remediation guide from raw scan findings.
|
|
@@ -1163,9 +1142,7 @@ export async function getRemediationGuide(payload, options = {}) {
|
|
|
1163
1142
|
};
|
|
1164
1143
|
}
|
|
1165
1144
|
|
|
1166
|
-
// ---------------------------------------------------------------------------
|
|
1167
1145
|
// Source Pattern Scanner
|
|
1168
|
-
// ---------------------------------------------------------------------------
|
|
1169
1146
|
|
|
1170
1147
|
/**
|
|
1171
1148
|
* Scans a project's source code for accessibility patterns not detectable by axe-core.
|
|
@@ -935,17 +935,12 @@ function mergeViolations(axeViolations, cdpViolations, pa11yViolations) {
|
|
|
935
935
|
}
|
|
936
936
|
}
|
|
937
937
|
|
|
938
|
-
// Step 3: pa11y findings —
|
|
938
|
+
// Step 3: pa11y findings — only skip if same rule + same target already exists
|
|
939
939
|
for (const v of pa11yViolations) {
|
|
940
940
|
const target = v.nodes?.[0]?.target?.[0] || "";
|
|
941
941
|
const key = `${v.id}::${target}`;
|
|
942
942
|
|
|
943
|
-
|
|
944
|
-
const isAxeEquivDuplicate = v.id && seenRuleTargets.has(v.id) && target && seenRuleTargets.get(v.id).has(target);
|
|
945
|
-
// Also check if any existing finding covers this exact target (broader dedup)
|
|
946
|
-
const selectorCovered = target && [...seen].some((k) => k.endsWith(`::${target}`));
|
|
947
|
-
|
|
948
|
-
if (!seen.has(key) && !isAxeEquivDuplicate && (!selectorCovered || !target)) {
|
|
943
|
+
if (!seen.has(key)) {
|
|
949
944
|
seen.add(key);
|
|
950
945
|
if (!seenRuleTargets.has(v.id)) seenRuleTargets.set(v.id, new Set());
|
|
951
946
|
seenRuleTargets.get(v.id).add(target);
|