@diegovelasquezweb/a11y-engine 0.8.2 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,76 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.8.2] — 2026-03-16
9
+
10
+ ### Changed
11
+
12
+ - **Smarter AI source file selection** — `fetchSourceFilesForFindings` now scores candidate files by how many terms extracted from the finding's selector, class names, IDs, and title match the file path. Files most relevant to the specific failing element are fetched first instead of picking the first 3 files by extension.
13
+ - Extracted `extractSearchTermsFromFinding()` and `scoreFilePath()` helpers for reusable relevance scoring logic.
14
+
15
+ ---
16
+
17
+ ## [0.8.1] — 2026-03-16
18
+
19
+ ### Added
20
+
21
+ - **Custom AI system prompt** — `enrichWithAI()` now accepts `options.systemPrompt` to override the default Claude system prompt at runtime.
22
+ - `enrich.mjs` reads `AI_SYSTEM_PROMPT` env var and passes it to `enrichWithAI()` — enabling per-scan prompt customization without code changes.
23
+ - `audit.mjs` forwards `AI_SYSTEM_PROMPT` env var to the `enrich.mjs` child process.
24
+
25
+ ---
26
+
27
+ ## [0.8.0] — 2026-03-16
28
+
29
+ ### Changed
30
+
31
+ - **AI enrichment no longer overwrites original fix** — `enrich.mjs` now preserves the original `fix_description`/`fix_code` from the engine and stores Claude's output in separate fields: `ai_fix_description`, `ai_fix_code`, `ai_fix_code_lang`. Findings improved by AI are flagged with `aiEnhanced: true`.
32
+ - **AI system prompt rewritten** — Claude is now explicitly instructed to go beyond the generic fix: explain why the issue matters for real users, what specifically to look for in the codebase, and provide a production-quality code example different from the existing one.
33
+ - Default AI model updated to `claude-haiku-4-5-20251001`.
34
+
35
+ ---
36
+
37
+ ## [0.7.9] — 2026-03-16
38
+
39
+ ### Added
40
+
41
+ - **AI enrichment CLI step** — `audit.mjs` now runs `src/ai/enrich.mjs` after the analyzer step when `ANTHROPIC_API_KEY` env var is present. Non-fatal: if AI fails, the pipeline continues with unenriched findings.
42
+ - `src/ai/enrich.mjs` — new CLI script that reads `a11y-findings.json`, calls `enrichWithAI()`, and writes enriched findings back. Reads `A11Y_REPO_URL` and `GH_TOKEN` env vars for repo-aware enrichment.
43
+ - `src/ai/claude.mjs` — Claude AI enrichment module. Enriches Critical and Serious findings with context-aware fix descriptions and code snippets. Uses `claude-haiku-4-5-20251001` by default. Fetches source files from the GitHub repo when `repoUrl` is available.
44
+
45
+ ---
46
+
47
+ ## [0.7.8] — 2026-03-16
48
+
49
+ ### Fixed
50
+
51
+ - **pa11y ruleId normalization** — pa11y violation IDs (e.g. `WCAG2AAA.Principle1.Guideline1_4.1_4_6.G17`) are now normalized to a short, readable form (e.g. `pa11y-g17`) by taking only the last segment of the dotted code. Previously the full dotted path was used, producing unreadable badges like `Pa11y Wcag2aaa Principle1 Guideline1 4 1 4 6 G17`.
52
+
53
+ ---
54
+
55
+ ## [0.7.7] — 2026-03-15
56
+
57
+ ### Added
58
+
59
+ - **`--repo-url` and `--github-token` CLI flags** — `audit.mjs` now accepts `--repo-url <github-url>` and `--github-token <token>`. When a repo URL is provided, the engine fetches `package.json` via the GitHub API to detect the project framework before running the analyzer, and passes the detected framework to both the analyzer and the source pattern scanner. No `git clone` required.
60
+ - `source-scanner.mjs` CLI now accepts `--repo-url` and `--github-token`. When `--repo-url` is provided (without `--project-dir`), it runs `scanPatternRemote()` against the GitHub API instead of the local filesystem.
61
+ - `detectProjectContext()` is now called in `audit.mjs` when a remote repo is provided, enabling framework-aware fix suggestions without a local clone.
62
+
63
+ ### Changed
64
+
65
+ - `source-scanner.mjs`: `--project-dir` is no longer required when `--repo-url` is provided. `main()` is now async to support remote API calls.
66
+ - `audit.mjs`: pattern scanning is now triggered when either `--project-dir` or `--repo-url` is provided.
67
+
68
+ ---
69
+
70
+ ## [0.7.6] — 2026-03-15
71
+
72
+ ### Changed
73
+
74
+ - HTML report renderer: updated Tailwind class syntax (`flex-shrink-0` → `shrink-0`, `bg-gradient-to-br` → `bg-linear-to-br`, `max-h-[360px]` → `max-h-90`).
75
+
76
+ ---
77
+
8
78
  ## [0.4.2] — 2026-03-15
9
79
 
10
80
  ### Fixed
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @diegovelasquezweb/a11y-engine
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/@diegovelasquezweb/a11y-engine)](https://www.npmjs.com/package/@diegovelasquezweb/a11y-engine)
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
 
5
7
  ## What it does
@@ -12,7 +14,7 @@ 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 works with local paths or remote GitHub repos |
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 |
16
18
 
17
19
  ## Installation
18
20
 
@@ -50,7 +52,7 @@ import {
50
52
 
51
53
  #### runAudit
52
54
 
53
- Runs the full scan pipeline: route discovery, scan, merge, analyze, and optional AI enrichment. Returns a payload ready for `getFindings`.
55
+ Runs the full scan pipeline: route discovery, scan, merge, analyze, AI enrichment (when configured), and optional source pattern scanning. Returns a payload ready for `getFindings`.
54
56
 
55
57
  ```ts
56
58
  const payload = await runAudit({
@@ -58,6 +60,14 @@ const payload = await runAudit({
58
60
  maxRoutes: 5,
59
61
  axeTags: ["wcag2a", "wcag2aa", "best-practice"],
60
62
  engines: { axe: true, cdp: true, pa11y: true },
63
+ repoUrl: "https://github.com/owner/repo", // optional, enables source pattern scan and stack detection
64
+ githubToken: process.env.GH_TOKEN, // optional, required for private repos
65
+ ai: {
66
+ enabled: true,
67
+ apiKey: process.env.ANTHROPIC_API_KEY,
68
+ githubToken: process.env.GH_TOKEN,
69
+ systemPrompt: "Custom prompt...", // optional, overrides default Claude system prompt
70
+ },
61
71
  onProgress: (step, status, extra) => console.log(`${step}: ${status}`, extra),
62
72
  });
63
73
  ```
@@ -125,10 +135,21 @@ These functions expose scanner help content, persona explanations, conformance l
125
135
 
126
136
  See [API Reference](docs/api-reference.md) for exact options and return types.
127
137
 
128
- ## Optional CLI
138
+ ## CLI
139
+
140
+ 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.
141
+
142
+ ## AI enrichment
143
+
144
+ When `ANTHROPIC_API_KEY` is set, the engine runs a post-scan enrichment step that sends Critical and Serious findings to Claude. Claude generates:
145
+
146
+ - A specific fix description referencing the actual selector, colors, and violation data
147
+ - A production-quality code snippet in the correct framework syntax
148
+ - Context-aware suggestions when repo source files are available
149
+
150
+ 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`.
129
151
 
130
- If you need terminal execution, the package also exposes `a11y-audit`.
131
- See the [CLI Handbook](docs/cli-handbook.md) for command flags and examples.
152
+ The system prompt is fully customizable via `options.ai.systemPrompt` (programmatic API) or the `AI_SYSTEM_PROMPT` env var (CLI).
132
153
 
133
154
  ## Documentation
134
155
 
@@ -4,172 +4,444 @@
4
4
 
5
5
  ---
6
6
 
7
- ## Core API
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
- ### `runAudit(options)`
31
+ ---
10
32
 
11
- Runs route discovery, runtime scan, merge, analyzer enrichment, and optional AI enrichment. Supports local project paths or remote GitHub repos for stack detection and source pattern scanning.
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
- `options` (`RunAuditOptions`):
65
+ ---
14
66
 
15
- | Option | Type |
16
- | :--- | :--- |
17
- | `baseUrl` | `string` |
18
- | `maxRoutes` | `number` |
19
- | `crawlDepth` | `number` |
20
- | `routes` | `string` |
21
- | `waitMs` | `number` |
22
- | `timeoutMs` | `number` |
23
- | `headless` | `boolean` |
24
- | `waitUntil` | `string` |
25
- | `colorScheme` | `string` |
26
- | `viewport` | `{ width: number; height: number }` |
27
- | `axeTags` | `string[]` |
28
- | `onlyRule` | `string` |
29
- | `excludeSelectors` | `string[]` |
30
- | `ignoreFindings` | `string[]` |
31
- | `framework` | `string` |
32
- | `projectDir` | `string` |
33
- | `repoUrl` | `string` |
34
- | `githubToken` | `string` |
35
- | `skipPatterns` | `boolean` |
36
- | `screenshotsDir` | `string` |
37
- | `engines` | `{ axe?: boolean; cdp?: boolean; pa11y?: boolean }` |
38
- | `ai` | `{ enabled?: boolean; apiKey?: string; githubToken?: string; model?: string }` |
39
- | `onProgress` | `(step: string, status: string, extra?: Record<string, unknown>) => void` |
40
-
41
- Progress steps emitted via `onProgress`:
42
-
43
- | Step | When |
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
- | `page` | Always page load |
46
- | `axe` | Always axe-core scan |
47
- | `cdp` | Always CDP accessibility tree check |
48
- | `pa11y` | Always pa11y HTML CodeSniffer scan |
49
- | `merge` | Always finding deduplication |
50
- | `intelligence` | Always enrichment and WCAG mapping |
51
- | `repo` | When `repoUrl` is set |
52
- | `patterns` | When source scanning is active |
53
- | `ai` | When AI enrichment is configured |
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
- ### `getFindings(input, options?)`
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
- Normalizes and enriches findings and returns sorted enriched findings.
174
+ ---
175
+
176
+ ### `getFindings(input, options?)`
60
177
 
61
- - `input`: `ScanPayload` from `runAudit`
62
- - `options` (`EnrichmentOptions`):
63
- - `screenshotUrlBuilder?: (rawPath: string) => string`
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
+ ```
64
207
 
65
208
  Returns: `EnrichedFinding[]`
66
209
 
67
- ### `getOverview(findings, payload?)`
210
+ ---
68
211
 
69
- Computes totals, score, WCAG status, persona groups, quick wins, target URL, and detected stack.
212
+ ### `getOverview(findings, payload?)`
70
213
 
71
- - `findings`: `EnrichedFinding[]`
72
- - `payload`: `ScanPayload | null`
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
+ ```
73
239
 
74
240
  Returns: `AuditSummary`
75
241
 
242
+ ---
243
+
76
244
  ## Output API
77
245
 
78
246
  ### `getPDFReport(payload, options?)`
79
247
 
80
- - `payload`: `ScanPayload`
81
- - `options`: `ReportOptions`
82
- - `baseUrl?: string`
83
- - `target?: string`
248
+ Generates a formal A4 PDF compliance report.
249
+
250
+ ```ts
251
+ import { getPDFReport } from "@diegovelasquezweb/a11y-engine";
84
252
 
85
- Returns: `Promise<PDFReport>` (`{ buffer, contentType }`)
253
+ const { buffer, contentType } = await getPDFReport(payload, {
254
+ baseUrl: "https://example.com",
255
+ target: "WCAG 2.2 AA",
256
+ });
257
+
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
+ ---
86
265
 
87
266
  ### `getHTMLReport(payload, options?)`
88
267
 
89
- - `payload`: `ScanPayload`
90
- - `options`: `HTMLReportOptions`
91
- - `baseUrl?: string`
92
- - `target?: string`
93
- - `screenshotsDir?: string`
268
+ Generates an interactive HTML audit dashboard with finding cards, score gauge, and persona breakdown.
94
269
 
95
- Returns: `Promise<HTMLReport>` (`{ html, contentType }`)
270
+ ```ts
271
+ import { getHTMLReport } from "@diegovelasquezweb/a11y-engine";
272
+
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
+ ---
96
282
 
97
283
  ### `getChecklist(options?)`
98
284
 
99
- - `options`: `Pick<ReportOptions, "baseUrl">`
100
- - `baseUrl?: string`
285
+ Generates an interactive HTML manual testing checklist with 41 WCAG checks.
101
286
 
102
- Returns: `Promise<ChecklistReport>` (`{ html, contentType }`)
287
+ ```ts
288
+ import { getChecklist } from "@diegovelasquezweb/a11y-engine";
289
+
290
+ const { html, contentType } = await getChecklist({
291
+ baseUrl: "https://example.com",
292
+ });
293
+ ```
294
+
295
+ Returns: `Promise<{ html: string, contentType: string }>`
296
+
297
+ ---
103
298
 
104
299
  ### `getRemediationGuide(payload, options?)`
105
300
 
106
- - `payload`: `ScanPayload & { incomplete_findings?: unknown[] }`
107
- - `options`: `RemediationOptions`
108
- - `baseUrl?: string`
109
- - `target?: string`
110
- - `patternFindings?: Record<string, unknown> | null`
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";
111
305
 
112
- Returns: `Promise<RemediationGuide>` (`{ markdown, contentType }`)
306
+ const { markdown, contentType } = await getRemediationGuide(payload, {
307
+ baseUrl: "https://example.com",
308
+ patternFindings: payload.patternFindings ?? null,
309
+ });
310
+
311
+ // Write to disk or return as download
312
+ ```
313
+
314
+ Returns: `Promise<{ markdown: string, contentType: string }>`
315
+
316
+ ---
113
317
 
114
318
  ### `getSourcePatterns(projectDir, options?)`
115
319
 
116
- - `projectDir`: `string`
117
- - `options`: `SourcePatternOptions`
118
- - `framework?: string`
119
- - `onlyPattern?: string`
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
+ ```
120
349
 
121
350
  Returns: `Promise<SourcePatternResult>`
122
351
 
352
+ ---
353
+
123
354
  ## Knowledge API
124
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
+
125
380
  ### `getScannerHelp(options?)`
126
381
 
127
- - `options`: `KnowledgeOptions`
128
- - `locale?: string`
382
+ Returns scan option descriptions, allowed values, and engine metadata — used to render Advanced Settings UI.
129
383
 
130
- Returns: `ScannerHelp` (`{ locale, version, title, engines, options }`)
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
+ ---
131
391
 
132
392
  ### `getPersonaReference(options?)`
133
393
 
134
- - `options`: `KnowledgeOptions`
135
- - `locale?: string`
394
+ Returns persona labels, descriptions, and disability group definitions.
136
395
 
137
- Returns: `PersonaReference` (`{ locale, version, personas }`)
396
+ ```ts
397
+ const ref = getPersonaReference();
398
+ // ref.personas → [{ id: "screenReader", label: "Screen Readers", icon: "...", description: "..." }, ...]
399
+ ```
400
+
401
+ ---
138
402
 
139
403
  ### `getUiHelp(options?)`
140
404
 
141
- - `options`: `KnowledgeOptions`
142
- - `locale?: string`
405
+ Returns shared concept definitions and a glossary of accessibility terms.
143
406
 
144
- Returns: `UiHelp` (`{ locale, version, concepts, glossary }`)
407
+ ```ts
408
+ const ui = getUiHelp();
409
+ // ui.concepts → { wcag: "...", aria: "...", ... }
410
+ // ui.glossary → [{ term: "ARIA", definition: "..." }, ...]
411
+ ```
145
412
 
146
- ### `getConformanceLevels(options?)`
413
+ ---
147
414
 
148
- - `options`: `KnowledgeOptions`
149
- - `locale?: string`
415
+ ### `getConformanceLevels(options?)`
150
416
 
151
- Returns: `ConformanceLevelsResult` (`{ locale, version, conformanceLevels }`)
417
+ Returns WCAG conformance level definitions with their corresponding axe-core tag sets.
152
418
 
153
- ### `getWcagPrinciples(options?)`
419
+ ```ts
420
+ const { conformanceLevels } = getConformanceLevels();
421
+ // conformanceLevels[0] → { id: "AA", label: "WCAG 2.2 AA", axeTags: ["wcag2a", "wcag2aa", ...] }
422
+ ```
154
423
 
155
- - `options`: `KnowledgeOptions`
156
- - `locale?: string`
424
+ ---
157
425
 
158
- Returns: `WcagPrinciplesResult` (`{ locale, version, wcagPrinciples }`)
426
+ ### `getWcagPrinciples(options?)`
159
427
 
160
- ### `getSeverityLevels(options?)`
428
+ Returns the four WCAG principles (Perceivable, Operable, Understandable, Robust) with criterion prefix patterns.
161
429
 
162
- - `options`: `KnowledgeOptions`
163
- - `locale?: string`
430
+ ```ts
431
+ const { wcagPrinciples } = getWcagPrinciples();
432
+ // wcagPrinciples[0] → { id: "perceivable", label: "Perceivable", prefix: "1.", description: "..." }
433
+ ```
164
434
 
165
- Returns: `SeverityLevelsResult` (`{ locale, version, severityLevels }`)
435
+ ---
166
436
 
167
- ### `getKnowledge(options?)`
437
+ ### `getSeverityLevels(options?)`
168
438
 
169
- - `options`: `KnowledgeOptions`
170
- - `locale?: string`
439
+ Returns severity level definitions with labels, descriptions, and ordering.
171
440
 
172
- Returns: `EngineKnowledge` (`{ locale, version, scanner, personas, concepts, glossary, docs, conformanceLevels, wcagPrinciples, severityLevels }`)
441
+ ```ts
442
+ const { severityLevels } = getSeverityLevels();
443
+ // severityLevels[0] → { id: "Critical", label: "Critical", order: 0, description: "..." }
444
+ ```
173
445
 
174
446
  ---
175
447
 
@@ -33,12 +33,21 @@ flowchart TD
33
33
 
34
34
  M --> R[a11y-scan-results.json]
35
35
  R --> AN[Analyzer]
36
- AN --> F[a11y-findings.json]
37
36
 
38
- F --> MD[remediation.md]
39
- F --> HTML[report.html]
40
- F --> PDF[report.pdf]
41
- F --> CHK[checklist.html]
37
+ REPO[GitHub Repo] -->|fetchPackageJson| AN
38
+ REPO -->|scanPatternRemote| PAT[a11y-pattern-findings.json]
39
+
40
+ AN --> F[a11y-findings.json]
41
+ F --> AI{ANTHROPIC_API_KEY?}
42
+ AI -->|yes| CL[Claude AI enrichment]
43
+ AI -->|no| SKIP[skip]
44
+ CL --> F2[a11y-findings.json enriched]
45
+ SKIP --> F2
46
+
47
+ F2 --> MD[remediation.md]
48
+ F2 --> HTML[report.html]
49
+ F2 --> PDF[report.pdf]
50
+ F2 --> CHK[checklist.html]
42
51
  ```
43
52
 
44
53
  ## Execution Modes
@@ -75,7 +84,10 @@ flowchart LR
75
84
  | :--- | :--- |
76
85
  | `src/pipeline/dom-scanner.mjs` | Route discovery, engine execution (axe/CDP/pa11y), merge/dedup, progress updates, screenshots |
77
86
  | `src/enrichment/analyzer.mjs` | Rule enrichment, selector strategy, ownership hints, recommendations, scoring metadata |
78
- | `src/source-patterns/source-scanner.mjs` | Static source pattern detection for issues runtime engines cannot see |
87
+ | `src/ai/enrich.mjs` | CLI subprocess that runs AI enrichment after the analyzer. Reads `ANTHROPIC_API_KEY` and `AI_SYSTEM_PROMPT` env vars. Non-fatal. |
88
+ | `src/ai/claude.mjs` | Anthropic API client. Sends Critical/Serious findings to Claude and parses improved fix suggestions. Supports custom system prompt and repo source file context. |
89
+ | `src/core/github-api.mjs` | GitHub API client. Provides `fetchPackageJson`, `fetchRepoFile`, `listRepoFiles`, and `parseRepoUrl`. Used for remote repo scanning and AI source file fetching without cloning. |
90
+ | `src/source-patterns/source-scanner.mjs` | Source code pattern scanner. Works against local `--project-dir` or remote `--repo-url` via the GitHub API. |
79
91
  | `src/reports/*.mjs` | Report builders for markdown/html/pdf/checklist |
80
92
  | `src/reports/renderers/*.mjs` | Shared rendering and normalization helpers |
81
93
  | `src/core/asset-loader.mjs` | Centralized access to bundled assets |
@@ -10,9 +10,12 @@
10
10
  - [Prerequisites](#prerequisites)
11
11
  - [Flag groups](#flag-groups)
12
12
  - [Targeting & scope](#targeting--scope)
13
+ - [Repository & remote scanning](#repository--remote-scanning)
14
+ - [AI enrichment](#ai-enrichment)
13
15
  - [Audit intelligence](#audit-intelligence)
14
16
  - [Execution & emulation](#execution--emulation)
15
17
  - [Output generation](#output-generation)
18
+ - [Environment variables](#environment-variables)
16
19
  - [Examples](#examples)
17
20
  - [Exit codes](#exit-codes)
18
21
 
@@ -62,7 +65,7 @@ Controls what gets scanned.
62
65
  | `--max-routes` | `<num>` | `10` | Maximum unique same-origin paths to discover and scan. |
63
66
  | `--crawl-depth` | `<num>` | `2` | How deep to follow links during BFS discovery (1-3). Has no effect when `--routes` is set. |
64
67
  | `--routes` | `<csv>` | — | Explicit paths to scan (e.g. `/,/about,/contact`). Overrides auto-discovery entirely. |
65
- | `--project-dir` | `<path>` | — | Path to the audited project source. Enables the source code pattern scanner and framework auto-detection from package.json. |
68
+ | `--project-dir` | `<path>` | — | Path to the audited project source on disk. Enables source code pattern scanning and framework auto-detection from the local `package.json`. |
66
69
 
67
70
  **Route discovery logic**:
68
71
  1. If the target has a `sitemap.xml`, all listed URLs are used (up to `--max-routes`).
@@ -71,6 +74,41 @@ Controls what gets scanned.
71
74
 
72
75
  ---
73
76
 
77
+ ### Repository & remote scanning
78
+
79
+ Enables source code analysis via the GitHub API — no `git clone` required.
80
+
81
+ | Flag | Argument | Default | Description |
82
+ | :--- | :--- | :--- | :--- |
83
+ | `--repo-url` | `<url>` | — | GitHub repository URL (e.g. `https://github.com/owner/repo`). Fetches `package.json` for framework detection and runs source code pattern scanning against the repo via the GitHub API. Mutually exclusive with `--project-dir` for remote usage. |
84
+ | `--github-token` | `<token>` | — | GitHub personal access token. Increases the GitHub API rate limit from 60 to 5,000 req/hour. Required for private repositories. Falls back to `GH_TOKEN` env var if not provided. |
85
+
86
+ When `--repo-url` is provided:
87
+ 1. The engine fetches `package.json` via `raw.githubusercontent.com` to detect the project framework.
88
+ 2. Source code patterns are run against the repo file tree using the GitHub Trees API and Contents API, with no local filesystem access.
89
+ 3. The detected framework is passed to the analyzer for framework-specific fix notes.
90
+
91
+ ---
92
+
93
+ ### AI enrichment
94
+
95
+ Controls Claude-powered fix suggestion enrichment. Requires `ANTHROPIC_API_KEY` to be set.
96
+
97
+ | Flag | Argument | Default | Description |
98
+ | :--- | :--- | :--- | :--- |
99
+ | *(no flag)* | — | — | AI enrichment is activated automatically when `ANTHROPIC_API_KEY` env var is present. There is no `--ai-enabled` flag — set or unset the env var to control it. |
100
+
101
+ AI enrichment runs after the analyzer step and enriches Critical and Serious findings (up to 20 per scan) with:
102
+ - A specific fix description referencing the actual selector, colors, and violation data
103
+ - A production-quality code snippet in the correct framework syntax
104
+ - Context-aware suggestions when repo source files are available via `--repo-url`
105
+
106
+ Original engine fixes are always preserved. AI output is stored in separate fields (`ai_fix_description`, `ai_fix_code`). Enriched findings are flagged with `aiEnhanced: true`.
107
+
108
+ The system prompt is customizable via `AI_SYSTEM_PROMPT` env var.
109
+
110
+ ---
111
+
74
112
  ### Audit intelligence
75
113
 
76
114
  Controls how findings are interpreted and filtered.
@@ -117,6 +155,16 @@ Controls what artifacts are written.
117
155
 
118
156
  ---
119
157
 
158
+ ## Environment variables
159
+
160
+ | Variable | Description |
161
+ | :--- | :--- |
162
+ | `ANTHROPIC_API_KEY` | Enables Claude AI enrichment. Set to a valid Anthropic API key. When absent, AI enrichment is silently skipped. |
163
+ | `AI_SYSTEM_PROMPT` | Custom system prompt for Claude. Overrides the default prompt for the entire scan. Useful for domain-specific fix guidance or custom output formats. |
164
+ | `GH_TOKEN` | GitHub personal access token. Used by the AI enrichment step when fetching source files from the repo. Equivalent to `--github-token` but read from the environment. |
165
+
166
+ ---
167
+
120
168
  ## Examples
121
169
 
122
170
  ### Minimal scan
@@ -135,7 +183,7 @@ a11y-audit \
135
183
  --output ./audit/report.html
136
184
  ```
137
185
 
138
- ### Include source code intelligence
186
+ ### Include source code intelligence (local)
139
187
 
140
188
  ```bash
141
189
  a11y-audit \
@@ -145,6 +193,32 @@ a11y-audit \
145
193
  --output ./audit/report.html
146
194
  ```
147
195
 
196
+ ### Scan with remote GitHub repository (no clone)
197
+
198
+ ```bash
199
+ a11y-audit \
200
+ --base-url https://example.com \
201
+ --repo-url https://github.com/owner/repo \
202
+ --github-token ghp_...
203
+ ```
204
+
205
+ ### Scan with AI enrichment
206
+
207
+ ```bash
208
+ ANTHROPIC_API_KEY=sk-ant-... a11y-audit \
209
+ --base-url https://example.com \
210
+ --repo-url https://github.com/owner/repo \
211
+ --github-token ghp_...
212
+ ```
213
+
214
+ ### Scan with custom AI system prompt
215
+
216
+ ```bash
217
+ AI_SYSTEM_PROMPT="You are an expert in Vue.js accessibility. Focus on component-level fixes." \
218
+ ANTHROPIC_API_KEY=sk-ant-... \
219
+ a11y-audit --base-url https://example.com --repo-url https://github.com/owner/repo
220
+ ```
221
+
148
222
  ### Focused re-audit — single rule, single route
149
223
 
150
224
  ```bash
@@ -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
@@ -16,9 +24,12 @@ This document is the current technical inventory of the engine package.
16
24
  | `src/core/utils.mjs` | Logging, JSON I/O, shared helpers |
17
25
  | `src/core/asset-loader.mjs` | Centralized asset map and loader |
18
26
  | `src/core/toolchain.mjs` | Environment/toolchain checks |
27
+ | `src/core/github-api.mjs` | GitHub API client — `fetchPackageJson`, `fetchRepoFile`, `listRepoFiles`, `parseRepoUrl`. Used for remote repo scanning and AI source file fetching. |
19
28
  | `src/pipeline/dom-scanner.mjs` | Runtime scan stage (axe/CDP/pa11y + merge) |
20
29
  | `src/enrichment/analyzer.mjs` | Finding enrichment and metadata synthesis |
21
- | `src/source-patterns/source-scanner.mjs` | Static source-pattern scanner |
30
+ | `src/ai/claude.mjs` | Claude AI client — calls the Anthropic API to enrich findings with context-aware fix suggestions. Accepts custom system prompt via `options.systemPrompt`. |
31
+ | `src/ai/enrich.mjs` | CLI AI enrichment subprocess — reads `a11y-findings.json`, calls `enrichWithAI()`, writes enriched findings back. Activated by `ANTHROPIC_API_KEY` env var. |
32
+ | `src/source-patterns/source-scanner.mjs` | Source code pattern scanner — works with local `--project-dir` or remote `--repo-url` via GitHub API |
22
33
  | `src/reports/html.mjs` | HTML report builder |
23
34
  | `src/reports/pdf.mjs` | PDF report builder |
24
35
  | `src/reports/md.mjs` | Markdown remediation builder |
@@ -152,8 +152,22 @@ A single finding can match multiple personas. The persona configuration (`person
152
152
  The compliance score is computed from severity totals using weights defined in `assets/reporting/compliance-config.mjs`:
153
153
 
154
154
  1. **Severity totals** — counts findings by `Critical`, `Serious`, `Moderate`, `Minor` (excluding AAA and Best Practice findings).
155
- 2. **Score** — starts at 100, deducts weighted points per finding.
156
- 3. **Label** maps score ranges to grades (`Excellent`, `Good Compliance`, `Needs Improvement`, `Poor`, `Critical`).
155
+ 2. **Score** — starts at 100, deducts weighted points per finding:
156
+ - Critical: −15 per finding
157
+ - Serious: −5 per finding
158
+ - Moderate: −2 per finding
159
+ - Minor: −0.5 per finding
160
+ - Score is clamped to 0–100 and rounded to nearest integer.
161
+ 3. **Label** — maps score ranges to grades:
162
+
163
+ | Score | Label |
164
+ | :--- | :--- |
165
+ | 90 – 100 | `Excellent` |
166
+ | 75 – 89 | `Good` |
167
+ | 55 – 74 | `Fair` |
168
+ | 35 – 54 | `Poor` |
169
+ | 0 – 34 | `Critical` |
170
+
157
171
  4. **WCAG status** — `Pass` (no findings), `Conditional Pass` (only Moderate/Minor), or `Fail` (any Critical/Serious).
158
172
 
159
173
  The `overallAssessment` in metadata follows the same logic for the formal compliance verdict.
@@ -187,14 +201,54 @@ The source scanner (`src/source-patterns/source-scanner.mjs`) detects accessibil
187
201
 
188
202
  5. Output includes a summary with `total`, `confirmed`, and `potential` counts.
189
203
 
204
+ ### Remote scanning via GitHub API
205
+
206
+ When `--repo-url` (CLI) or `options.repoUrl` (programmatic API) is provided instead of `--project-dir`, the source scanner uses the GitHub API — no `git clone` required:
207
+
208
+ 1. `listRepoFiles()` fetches the repo file tree using the GitHub Trees API. Falls back to the Contents API for truncated responses (large repos).
209
+ 2. Files matching each pattern's `globs` are fetched individually via `raw.githubusercontent.com`.
210
+ 3. The same regex and context rejection logic runs against the fetched content.
211
+ 4. Results are identical to local scanning.
212
+
213
+ A GitHub token (`--github-token` or `GH_TOKEN` env var) increases the API rate limit from 60 to 5,000 req/hour and enables private repo access.
214
+
190
215
  ### Integration with the audit pipeline
191
216
 
192
- When `runAudit` is called with `projectDir` and without `skipPatterns`:
217
+ When `runAudit` is called with `projectDir` or `repoUrl` and without `skipPatterns`:
218
+
219
+ 1. The engine fetches `package.json` from the repo (remote) or reads it from disk (local) to detect the framework before the analyzer runs.
220
+ 2. The analyzer runs with the detected framework context.
221
+ 3. Source patterns run after enrichment.
222
+ 4. Pattern findings are attached to the payload as `patternFindings` with their own `generated_at`, `project_dir`, `findings`, and `summary`.
223
+ 5. The remediation guide (`getRemediationGuide`) renders pattern findings in a dedicated section.
224
+
225
+ ### pa11y ruleId normalization
226
+
227
+ pa11y reports violations using dotted WCAG criterion codes (e.g. `WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail`). The engine normalizes these in two places:
228
+
229
+ 1. **Equivalence mapping** (`assets/scanning/pa11y-config.mjs`, `equivalenceMap`) — known pa11y codes are mapped to their axe-core equivalent rule ID (e.g. `Principle1.Guideline1_4.1_4_3.G145` → `color-contrast`). These findings are merged and deduplicated with axe findings.
230
+
231
+ 2. **Fallback normalization** (`src/pipeline/dom-scanner.mjs`) — pa11y codes without an axe equivalent are shortened to their last segment (e.g. `WCAG2AAA.Principle1.Guideline1_4.1_4_6.G17` → `pa11y-g17`). This produces a readable rule ID without the full dotted path.
232
+
233
+ ## AI Enrichment
234
+
235
+ After the analyzer step, the engine optionally runs Claude-powered enrichment on Critical and Serious findings (up to 20 per scan).
236
+
237
+ ### How it works
238
+
239
+ 1. `src/ai/enrich.mjs` reads `a11y-findings.json`, identifies Critical and Serious findings, and sends them to `enrichWithAI()`.
240
+ 2. `src/ai/claude.mjs` calls the Anthropic API with a system prompt instructing Claude to generate specific, production-quality fix suggestions using the actual violation data (selector, colors, ratio, etc.).
241
+ 3. When a repo URL is available (`A11Y_REPO_URL` env var), Claude also receives relevant source files fetched via the GitHub API. File selection is scored by how well each file path matches terms extracted from the finding's selector and title.
242
+ 4. Claude returns a JSON array of improvements. Each improvement contains a `fixDescription` and `fixCode` specific to the finding's context.
243
+ 5. The engine stores Claude's output in separate fields (`ai_fix_description`, `ai_fix_code`, `ai_fix_code_lang`) — the original engine fixes are preserved unchanged. Improved findings are flagged with `aiEnhanced: true`.
244
+
245
+ ### Activation
246
+
247
+ AI enrichment runs automatically when `ANTHROPIC_API_KEY` is present in the environment. It is non-fatal — if the API call fails, the pipeline continues with unenriched findings.
248
+
249
+ ### Custom system prompt
193
250
 
194
- 1. The analyzer runs first to detect the framework.
195
- 2. Source patterns run after enrichment.
196
- 3. Pattern findings are attached to the payload as `patternFindings` with their own `generated_at`, `project_dir`, `findings`, and `summary`.
197
- 4. The remediation guide (`getRemediationGuide`) renders pattern findings in a dedicated section.
251
+ The default system prompt instructs Claude to go beyond the generic fix: explain why the issue matters for users, reference the specific selector and violation data, and provide a more complete code example than the engine's default. The prompt can be overridden per-scan via the `AI_SYSTEM_PROMPT` env var or `options.ai.systemPrompt` in the programmatic API.
198
252
 
199
253
  ## Assets Reference
200
254
 
package/docs/outputs.md CHANGED
@@ -10,6 +10,7 @@
10
10
  - [progress.json](#progressjson)
11
11
  - [a11y-scan-results.json](#a11y-scan-resultsjson)
12
12
  - [a11y-findings.json](#a11y-findingsjson)
13
+ - [a11y-pattern-findings.json](#a11y-pattern-findingsjson)
13
14
  - [remediation.md](#remediationmd)
14
15
  - [report.html](#reporthtml)
15
16
  - [report.pdf](#reportpdf)
@@ -90,7 +91,7 @@ Merged results from all three engines (axe-core + CDP + pa11y) per route. Writte
90
91
  }
91
92
  ```
92
93
 
93
- Each violation in the `violations` array includes a `source` field indicating which engine produced it (`undefined` for axe-core, `"cdp"` for CDP checks, `"pa11y"` for pa11y).
94
+ Each violation in the `violations` array includes a `source` field: `"cdp"` for CDP checks, `"pa11y"` for pa11y, and absent (field not set) for axe-core violations.
94
95
 
95
96
  This file is consumed by `analyzer.mjs` and also used by `--affected-only` to determine which routes to re-scan on subsequent runs.
96
97
 
@@ -176,6 +177,25 @@ The primary enriched data artifact. Written by `src/enrichment/analyzer.mjs`. Th
176
177
  | `verification_command_fallback` | `string\|null` | Fallback verify command |
177
178
  | `pages_affected` | `number\|null` | Number of pages with this violation |
178
179
  | `affected_urls` | `string[]\|null` | All URLs where this violation appears |
180
+ | `aiEnhanced` | `boolean` | `true` when Claude improved the fix for this finding. Only present on AI-enriched findings. |
181
+ | `ai_fix_description` | `string\|null` | Claude-generated fix description. More specific than `fix_description` — references the actual selector, colors, and violation data. Only present when `aiEnhanced` is `true`. |
182
+ | `ai_fix_code` | `string\|null` | Claude-generated code snippet in the correct framework syntax. Separate from the engine's `fix_code`. Only present when `aiEnhanced` is `true`. |
183
+ | `ai_fix_code_lang` | `string\|null` | Language of `ai_fix_code` (e.g. `jsx`, `tsx`, `vue`, `css`). Only present when `aiEnhanced` is `true`. |
184
+
185
+ > **Note on `ownership_status`**: Values are `"primary"` (issue is in the project's source), `"outside_primary_source"` (issue is in a third-party component), or `"unknown"`. These are different from the pattern finding `status` field which uses `"confirmed"` and `"potential"`.
186
+
187
+ ### Top-level payload keys (after AI enrichment)
188
+
189
+ When AI enrichment runs, the engine appends `ai_enriched_findings` to the payload root. `getFindings()` uses this as a fast path — if present, it returns `ai_enriched_findings` directly without re-normalizing the raw `findings` array.
190
+
191
+ ```json
192
+ {
193
+ "metadata": { ... },
194
+ "findings": [ ... ],
195
+ "ai_enriched_findings": [ ... ],
196
+ "incomplete_findings": [ ... ]
197
+ }
198
+ ```
179
199
 
180
200
  ### `incomplete_findings`
181
201
 
@@ -183,6 +203,45 @@ Violations that axe-core flagged as "needs review" (not confirmed pass or fail).
183
203
 
184
204
  ---
185
205
 
206
+ ## a11y-pattern-findings.json
207
+
208
+ Source code pattern scan results. Written by `src/source-patterns/source-scanner.mjs` when `--project-dir` or `--repo-url` is provided (and `--skip-patterns` is not set).
209
+
210
+ ```json
211
+ {
212
+ "generated_at": "2026-03-16T00:00:00.000Z",
213
+ "project_dir": "https://github.com/owner/repo",
214
+ "findings": [ ... ],
215
+ "summary": {
216
+ "total": 5,
217
+ "confirmed": 3,
218
+ "potential": 2
219
+ }
220
+ }
221
+ ```
222
+
223
+ ### Per-finding fields
224
+
225
+ | Field | Type | Description |
226
+ | :--- | :--- | :--- |
227
+ | `id` | `string` | Deterministic finding ID |
228
+ | `pattern_id` | `string` | Pattern definition ID (e.g. `placeholder-only-label`) |
229
+ | `title` | `string` | Pattern title |
230
+ | `severity` | `string` | `Critical`, `Serious`, `Moderate`, or `Minor` |
231
+ | `wcag` | `string` | WCAG success criterion string |
232
+ | `wcag_criterion` | `string` | WCAG criterion ID |
233
+ | `wcag_level` | `string` | `A`, `AA`, or `AAA` |
234
+ | `type` | `string` | Pattern type (`structural`, `css`, etc.) |
235
+ | `fix_description` | `string\|null` | How to fix this pattern |
236
+ | `status` | `string` | `confirmed` (regex match without reject context) or `potential` (match with uncertainty) |
237
+ | `file` | `string` | File path within the repo (e.g. `src/components/Button.tsx`) |
238
+ | `line` | `number` | Line number of the match |
239
+ | `match` | `string` | The matched line content |
240
+ | `context` | `string` | 7-line code context window around the match |
241
+ | `source` | `string` | Always `"code-pattern"` |
242
+
243
+ ---
244
+
186
245
  ## remediation.md
187
246
 
188
247
  AI agent-optimized remediation guide. Always generated (even without `--with-reports`). Written to `.audit/remediation.md`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegovelasquezweb/a11y-engine",
3
- "version": "0.8.2",
3
+ "version": "0.8.4",
4
4
  "description": "WCAG 2.2 accessibility audit engine — scanner, analyzer, and report builders",
5
5
  "type": "module",
6
6
  "license": "MIT",
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
- function buildSystemPrompt(context) {
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
- ${stackInfo ? `\nProject context:\n${stackInfo}` : ""}
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
  /**
@@ -856,8 +856,10 @@ function buildFindings(inputPayload, cliArgs) {
856
856
  selector: selectors.join(", "),
857
857
  impacted_users: getImpactedUsers(v.id, v.tags),
858
858
  primary_selector: bestSelector,
859
- actual:
860
- firstNode?.failureSummary || `Found ${nodes.length} instance(s).`,
859
+ actual: (() => {
860
+ const raw = firstNode?.failureSummary || `Found ${nodes.length} instance(s).`;
861
+ return raw.replace(/^Fix any of the following:\s*/i, "").trim();
862
+ })(),
861
863
  primary_failure_mode: failureInsights.primaryFailureMode,
862
864
  relationship_hint: failureInsights.relationshipHint,
863
865
  failure_checks: failureInsights.failureChecks,
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,6 +5,7 @@
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
11
  // Lazy-loaded asset cache
@@ -238,7 +239,7 @@ export function getFindings(input, options = {}) {
238
239
  recommendedFix: finding.recommended_fix,
239
240
  evidence: finding.evidence,
240
241
  totalInstances: finding.total_instances,
241
- effort: finding.effort,
242
+ effort: finding.effort ?? (finding.fix_code ? "low" : "high"),
242
243
  relatedRules: finding.related_rules,
243
244
  screenshotPath: finding.screenshot_path,
244
245
  falsePositiveRisk: finding.false_positive_risk,
@@ -583,6 +584,13 @@ export function getSeverityLevels(options = {}) {
583
584
  };
584
585
  }
585
586
 
587
+ export const VIEWPORT_PRESETS = [
588
+ { label: "Desktop", width: 1280, height: 800 },
589
+ { label: "Laptop", width: 1440, height: 900 },
590
+ { label: "Tablet", width: 768, height: 1024 },
591
+ { label: "Mobile", width: 375, height: 812 },
592
+ ];
593
+
586
594
  export function getKnowledge(options = {}) {
587
595
  const scanner = getScannerHelp(options);
588
596
  const personas = getPersonaReference(options);