@diegovelasquezweb/a11y-engine 0.8.5 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/README.md +25 -35
- package/docs/api-reference.md +326 -116
- package/docs/architecture.md +1 -1
- package/docs/engine-manifest.md +2 -0
- package/docs/intelligence.md +1 -1
- package/docs/outputs.md +4 -0
- package/docs/testing.md +9 -4
- package/package.json +1 -1
- package/src/cli/audit.mjs +3 -0
- package/src/enrichment/analyzer.mjs +31 -2
- package/src/index.d.mts +4 -12
- package/src/index.mjs +13 -8
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,47 @@ 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.9.0] — 2026-03-16
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **Knowledge API consolidated** — `getScannerHelp`, `getPersonaReference`, `getUiHelp`, `getConformanceLevels`, `getWcagPrinciples`, and `getSeverityLevels` are no longer part of the public API. They remain as internal helpers consumed by `getKnowledge`. `getUiHelp` renamed to `getConceptsAndGlossary` internally. `getKnowledge` is the single exported entry point for all knowledge data.
|
|
13
|
+
- TypeScript declarations (`src/index.d.mts`) updated to remove the six individual knowledge functions.
|
|
14
|
+
- `tests/knowledge-api.test.mjs` updated to reflect the consolidated API shape.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## [0.8.5] — 2026-03-16
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **pa11y merge no longer drops findings with shared selectors** — the merge step was discarding pa11y findings whenever any prior finding (from axe or CDP) targeted the same selector, regardless of rule. Now pa11y findings are only de-duplicated when the exact same `rule_id + selector` combination already exists.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## [0.8.4] — 2026-03-15
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- **`DEFAULT_AI_SYSTEM_PROMPT` exported** — the default Claude system prompt is now part of the public API, allowing consumers to read, log, or extend it.
|
|
31
|
+
- **`VIEWPORT_PRESETS` exported** — four ready-made viewport presets (`Desktop`, `Laptop`, `Tablet`, `Mobile`) exported from the package root for use in scanner UI option pickers.
|
|
32
|
+
- **`dependabot.yml`** — automated dependency update configuration added.
|
|
33
|
+
- **Effort fallback** — `getFindings` now infers `effort` after intelligence enrichment so findings that gain a `fixCode` from the intelligence database are correctly rated `"low"`.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## [0.8.3] — 2026-03-15
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- **`actual` field no longer contains axe preamble** — the `"Fix any of the following:"` prefix from axe `failureSummary` strings is now stripped in `analyzer.mjs`, producing a cleaner violation description.
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- `SECURITY.md` — security policy and vulnerability reporting process.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
8
49
|
## [0.8.2] — 2026-03-16
|
|
9
50
|
|
|
10
51
|
### Changed
|
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Accessibility automation engine for web applications. It orchestrates multi engi
|
|
|
15
15
|
| **AI enrichment** | Optional Claude-powered analysis that adds contextual fix suggestions based on detected stack, repo structure, and finding patterns |
|
|
16
16
|
| **Report generation** | Produces HTML dashboard, PDF compliance report, manual testing checklist, and Markdown remediation guide |
|
|
17
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
|
|
18
|
+
| **Knowledge API** | Exposes WCAG conformance levels, severity definitions, persona profiles, glossary, scanner help, and documentation |
|
|
19
19
|
|
|
20
20
|
## Installation
|
|
21
21
|
|
|
@@ -41,12 +41,6 @@ import {
|
|
|
41
41
|
getChecklist,
|
|
42
42
|
getRemediationGuide,
|
|
43
43
|
getSourcePatterns,
|
|
44
|
-
getScannerHelp,
|
|
45
|
-
getPersonaReference,
|
|
46
|
-
getUiHelp,
|
|
47
|
-
getConformanceLevels,
|
|
48
|
-
getWcagPrinciples,
|
|
49
|
-
getSeverityLevels,
|
|
50
44
|
getKnowledge,
|
|
51
45
|
} from "@diegovelasquezweb/a11y-engine";
|
|
52
46
|
```
|
|
@@ -100,12 +94,18 @@ Computes severity totals, compliance score, WCAG pass/fail status, persona impac
|
|
|
100
94
|
```ts
|
|
101
95
|
const summary = getOverview(findings, payload);
|
|
102
96
|
// summary.score -> 72
|
|
103
|
-
// summary.label -> "Good"
|
|
104
|
-
// summary.wcagStatus -> "Fail"
|
|
97
|
+
// summary.label -> "Good" // "Excellent" | "Good" | "Fair" | "Poor" | "Critical"
|
|
98
|
+
// summary.wcagStatus -> "Fail" // "Pass" | "Conditional Pass" | "Fail"
|
|
105
99
|
// summary.totals -> { Critical: 1, Serious: 3, Moderate: 5, Minor: 2 }
|
|
106
|
-
// summary.personaGroups -> {
|
|
107
|
-
//
|
|
100
|
+
// summary.personaGroups -> {
|
|
101
|
+
// screenReader: { label: "Screen Readers", count: 4, icon: "screenReader" },
|
|
102
|
+
// keyboard: { label: "Keyboard Only", count: 2, icon: "keyboard" },
|
|
103
|
+
// ...
|
|
104
|
+
// }
|
|
105
|
+
// summary.quickWins -> [top 3 Critical/Serious findings with fixCode]
|
|
106
|
+
// summary.targetUrl -> "https://example.com"
|
|
108
107
|
// summary.detectedStack -> { framework: "nextjs", cms: null, uiLibraries: [] }
|
|
108
|
+
// summary.totalFindings -> 11
|
|
109
109
|
```
|
|
110
110
|
|
|
111
111
|
### Output API
|
|
@@ -122,42 +122,32 @@ These functions render final artifacts from scan payload data.
|
|
|
122
122
|
|
|
123
123
|
### Knowledge API
|
|
124
124
|
|
|
125
|
-
|
|
125
|
+
Returns all accessibility knowledge in a single call: scanner help, persona profiles, concepts, glossary, documentation, conformance levels, WCAG principles, and severity definitions.
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
127
|
+
```ts
|
|
128
|
+
const knowledge = getKnowledge({ locale: "en" });
|
|
129
|
+
// knowledge.scanner → engine and option descriptions
|
|
130
|
+
// knowledge.personas → persona labels and descriptions
|
|
131
|
+
// knowledge.concepts → concept definitions
|
|
132
|
+
// knowledge.glossary → accessibility glossary
|
|
133
|
+
// knowledge.docs → documentation articles
|
|
134
|
+
// knowledge.conformanceLevels → A/AA/AAA with axe tag mappings
|
|
135
|
+
// knowledge.wcagPrinciples → the four WCAG principles
|
|
136
|
+
// knowledge.severityLevels → Critical/Serious/Moderate/Minor definitions
|
|
137
|
+
```
|
|
136
138
|
|
|
137
|
-
See [API Reference](docs/api-reference.md) for
|
|
139
|
+
See [API Reference](docs/api-reference.md) for the full `EngineKnowledge` shape.
|
|
138
140
|
|
|
139
141
|
## CLI
|
|
140
142
|
|
|
141
143
|
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.
|
|
142
144
|
|
|
143
|
-
## AI enrichment
|
|
144
|
-
|
|
145
|
-
When `ANTHROPIC_API_KEY` is set, the engine runs a post-scan enrichment step that sends Critical and Serious findings to Claude. Claude generates:
|
|
146
|
-
|
|
147
|
-
- A specific fix description referencing the actual selector, colors, and violation data
|
|
148
|
-
- A production-quality code snippet in the correct framework syntax
|
|
149
|
-
- Context-aware suggestions when repo source files are available
|
|
150
|
-
|
|
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`.
|
|
152
|
-
|
|
153
|
-
The system prompt is fully customizable via `options.ai.systemPrompt` (programmatic API) or the `AI_SYSTEM_PROMPT` env var (CLI).
|
|
154
|
-
|
|
155
145
|
## Documentation
|
|
156
146
|
|
|
157
147
|
| Resource | Description |
|
|
158
148
|
| :--- | :--- |
|
|
159
149
|
| [Architecture](docs/architecture.md) | Multi-engine pipeline, merge logic, and execution model |
|
|
160
|
-
| [API Reference](docs/api-reference.md) | Function signatures, options,
|
|
150
|
+
| [API Reference](docs/api-reference.md) | Function signatures, options, return contracts, and exported constants |
|
|
161
151
|
| [CLI Handbook](docs/cli-handbook.md) | Full flag reference and usage examples |
|
|
162
152
|
| [Output Artifacts](docs/outputs.md) | Schema and structure of every generated file |
|
|
163
153
|
| [Engine Manifest](docs/engine-manifest.md) | Current inventory of source modules, assets, and tests |
|
package/docs/api-reference.md
CHANGED
|
@@ -21,12 +21,9 @@
|
|
|
21
21
|
- [getSourcePatterns](#getsourcepatternsprojdir-options)
|
|
22
22
|
- [Knowledge API](#knowledge-api)
|
|
23
23
|
- [getKnowledge](#getknowledgeoptions)
|
|
24
|
-
|
|
25
|
-
- [
|
|
26
|
-
- [
|
|
27
|
-
- [getConformanceLevels](#getconformancelevelsoptions)
|
|
28
|
-
- [getWcagPrinciples](#getwcagprinciplesoptions)
|
|
29
|
-
- [getSeverityLevels](#getseveritylevelsoptions)
|
|
24
|
+
- [Constants](#constants)
|
|
25
|
+
- [VIEWPORT_PRESETS](#viewport_presets)
|
|
26
|
+
- [DEFAULT_AI_SYSTEM_PROMPT](#default_ai_system_prompt)
|
|
30
27
|
|
|
31
28
|
---
|
|
32
29
|
|
|
@@ -53,12 +50,8 @@ import {
|
|
|
53
50
|
getRemediationGuide,
|
|
54
51
|
getSourcePatterns,
|
|
55
52
|
getKnowledge,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
getUiHelp,
|
|
59
|
-
getConformanceLevels,
|
|
60
|
-
getWcagPrinciples,
|
|
61
|
-
getSeverityLevels,
|
|
53
|
+
VIEWPORT_PRESETS,
|
|
54
|
+
DEFAULT_AI_SYSTEM_PROMPT,
|
|
62
55
|
} from "@diegovelasquezweb/a11y-engine";
|
|
63
56
|
```
|
|
64
57
|
|
|
@@ -81,9 +74,9 @@ const payload = await runAudit({
|
|
|
81
74
|
const findings = getFindings(payload);
|
|
82
75
|
|
|
83
76
|
// 3. Get compliance summary
|
|
84
|
-
const { score,
|
|
77
|
+
const { score, label, wcagStatus, totals, quickWins } = getOverview(findings, payload);
|
|
85
78
|
|
|
86
|
-
console.log(`Score: ${score}/100 (${
|
|
79
|
+
console.log(`Score: ${score}/100 (${label})`);
|
|
87
80
|
console.log(`WCAG Status: ${wcagStatus}`);
|
|
88
81
|
console.log(`Critical: ${totals.Critical}, Serious: ${totals.Serious}`);
|
|
89
82
|
```
|
|
@@ -169,6 +162,42 @@ const payload = await runAudit({
|
|
|
169
162
|
|
|
170
163
|
Returns: `Promise<ScanPayload>`
|
|
171
164
|
|
|
165
|
+
**`ScanPayload` shape:**
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
{
|
|
169
|
+
findings: RawFinding[], // Raw findings from axe/CDP/pa11y merge
|
|
170
|
+
metadata: {
|
|
171
|
+
target_url: string, // The baseUrl that was scanned
|
|
172
|
+
scanned_at: string, // ISO 8601 timestamp
|
|
173
|
+
engines: { // Which engines actually ran
|
|
174
|
+
axe: boolean,
|
|
175
|
+
cdp: boolean,
|
|
176
|
+
pa11y: boolean,
|
|
177
|
+
},
|
|
178
|
+
projectContext: { // Auto-detected or overridden stack
|
|
179
|
+
framework: string | null, // "nextjs" | "react" | "vue" | etc.
|
|
180
|
+
cms: string | null, // "wordpress" | "shopify" | etc.
|
|
181
|
+
uiLibraries: string[], // ["radix-ui", "tailwindcss", ...]
|
|
182
|
+
},
|
|
183
|
+
routes_scanned: number, // How many pages were actually scanned
|
|
184
|
+
discovery_method: string, // "crawl" | "explicit"
|
|
185
|
+
},
|
|
186
|
+
incomplete_findings?: RawFinding[], // axe "incomplete" results (needs-review)
|
|
187
|
+
patternFindings?: { // Only present if projectDir/repoUrl + !skipPatterns
|
|
188
|
+
generated_at: string,
|
|
189
|
+
project_dir: string, // Local path or repo URL
|
|
190
|
+
findings: SourcePatternFinding[],
|
|
191
|
+
summary: {
|
|
192
|
+
total: number,
|
|
193
|
+
confirmed: number,
|
|
194
|
+
potential: number,
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
ai_enriched_findings?: EnrichedFinding[], // Only present if ai.enabled + ai.apiKey
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
172
201
|
> **`ai_enriched_findings` fast path**: When AI enrichment runs, `getFindings()` uses `payload.ai_enriched_findings` directly instead of re-normalizing the raw findings array.
|
|
173
202
|
|
|
174
203
|
---
|
|
@@ -207,6 +236,85 @@ const findings = getFindings(payload, {
|
|
|
207
236
|
|
|
208
237
|
Returns: `EnrichedFinding[]`
|
|
209
238
|
|
|
239
|
+
**`EnrichedFinding` shape:**
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
{
|
|
243
|
+
// Identity
|
|
244
|
+
id: string, // "A11Y-001", "A11Y-002", ...
|
|
245
|
+
ruleId: string, // Canonical rule ID (e.g. "color-contrast")
|
|
246
|
+
source: string, // "axe" | "cdp" | "pa11y"
|
|
247
|
+
sourceRuleId: string | null, // Original engine rule ID before canonicalization
|
|
248
|
+
|
|
249
|
+
// Classification
|
|
250
|
+
title: string, // Human-readable issue title
|
|
251
|
+
severity: string, // "Critical" | "Serious" | "Moderate" | "Minor"
|
|
252
|
+
category: string | null, // "color", "forms", "structure", "aria", ...
|
|
253
|
+
wcag: string, // WCAG criterion (e.g. "1.4.3")
|
|
254
|
+
wcagCriterionId: string | null, // Full criterion ID (e.g. "1.4.3")
|
|
255
|
+
wcagClassification: string | null, // "A" | "AA" | "AAA" | "Best Practice"
|
|
256
|
+
|
|
257
|
+
// Location
|
|
258
|
+
area: string, // Page path (e.g. "/about")
|
|
259
|
+
url: string, // Full page URL
|
|
260
|
+
selector: string, // CSS selector of the violating element
|
|
261
|
+
primarySelector: string, // Preferred selector for targeting
|
|
262
|
+
|
|
263
|
+
// Problem description
|
|
264
|
+
actual: string, // What the engine found
|
|
265
|
+
expected: string, // What WCAG requires
|
|
266
|
+
impactedUsers: string, // "Screen reader users", "Keyboard users", etc.
|
|
267
|
+
primaryFailureMode: string | null, // "missing-label" | "low-contrast" | ...
|
|
268
|
+
relationshipHint: string | null, // How this relates to other findings
|
|
269
|
+
|
|
270
|
+
// Evidence
|
|
271
|
+
evidence: object[], // Raw evidence from the engine
|
|
272
|
+
failureChecks: object[], // axe check details
|
|
273
|
+
relatedContext: object[], // Related DOM elements
|
|
274
|
+
totalInstances: number | null, // How many elements are affected
|
|
275
|
+
pagesAffected: number | null, // How many pages have this issue
|
|
276
|
+
affectedUrls: string[] | null, // Specific URLs affected
|
|
277
|
+
|
|
278
|
+
// Fix guidance
|
|
279
|
+
fixDescription: string | null, // Human-readable fix explanation
|
|
280
|
+
fixCode: string | null, // Code snippet to fix the issue
|
|
281
|
+
fixCodeLang: string, // "html" | "css" | "jsx" | ...
|
|
282
|
+
recommendedFix: string, // Short fix summary
|
|
283
|
+
mdn: string | null, // MDN reference URL
|
|
284
|
+
effort: string, // "low" (has fixCode) | "high" (no fixCode)
|
|
285
|
+
fixDifficultyNotes: object | null, // Detailed difficulty breakdown
|
|
286
|
+
|
|
287
|
+
// Framework / CMS context
|
|
288
|
+
frameworkNotes: string | null, // Framework-specific fix guidance
|
|
289
|
+
cmsNotes: string | null, // CMS-specific fix guidance
|
|
290
|
+
managedByLibrary: string | null, // If the element is from a 3rd-party lib
|
|
291
|
+
componentHint: string | null, // Likely component name
|
|
292
|
+
fileSearchPattern: string | null, // Glob pattern to find source file
|
|
293
|
+
|
|
294
|
+
// Ownership & search
|
|
295
|
+
ownershipStatus: string, // "own" | "third-party" | "unknown"
|
|
296
|
+
ownershipReason: string | null, // Why it was classified that way
|
|
297
|
+
primarySourceScope: string[], // Directories to search for source
|
|
298
|
+
searchStrategy: string, // "verify_ownership_before_search" | ...
|
|
299
|
+
|
|
300
|
+
// Verification
|
|
301
|
+
verificationCommand: string | null, // CLI command to verify the fix
|
|
302
|
+
verificationCommandFallback: string | null,
|
|
303
|
+
screenshotPath: string | null, // Path or URL to element screenshot
|
|
304
|
+
|
|
305
|
+
// Metadata
|
|
306
|
+
relatedRules: string[], // Related axe rule IDs
|
|
307
|
+
falsePositiveRisk: string | null, // "low" | "medium" | "high"
|
|
308
|
+
guardrails: object | null, // Guardrail metadata from the engine
|
|
309
|
+
checkData: object | null, // Raw check data from the engine
|
|
310
|
+
|
|
311
|
+
// AI enrichment (only when ai.enabled ran)
|
|
312
|
+
aiEnhanced?: boolean, // true when AI enriched this finding
|
|
313
|
+
aiFixDescription?: string, // Claude-generated fix explanation
|
|
314
|
+
aiFixCode?: string, // Claude-generated code snippet
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
210
318
|
---
|
|
211
319
|
|
|
212
320
|
### `getOverview(findings, payload?)`
|
|
@@ -218,27 +326,39 @@ import { getFindings, getOverview } from "@diegovelasquezweb/a11y-engine";
|
|
|
218
326
|
|
|
219
327
|
const findings = getFindings(payload);
|
|
220
328
|
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
329
|
```
|
|
239
330
|
|
|
240
331
|
Returns: `AuditSummary`
|
|
241
332
|
|
|
333
|
+
**`AuditSummary` shape:**
|
|
334
|
+
|
|
335
|
+
```ts
|
|
336
|
+
{
|
|
337
|
+
score: number, // 0–100. Formula: 100 - (Critical×15) - (Serious×5) - (Moderate×2) - (Minor×0.5)
|
|
338
|
+
label: string, // "Excellent" (90–100) | "Good" (75–89) | "Fair" (55–74) | "Poor" (35–54) | "Critical" (0–34)
|
|
339
|
+
wcagStatus: string, // "Pass" | "Conditional Pass" | "Fail"
|
|
340
|
+
totals: {
|
|
341
|
+
Critical: number,
|
|
342
|
+
Serious: number,
|
|
343
|
+
Moderate: number,
|
|
344
|
+
Minor: number,
|
|
345
|
+
},
|
|
346
|
+
personaGroups: Record<string, { // Keyed by persona ID
|
|
347
|
+
label: string, // "Screen Readers", "Keyboard Only", ...
|
|
348
|
+
count: number, // Findings affecting this persona
|
|
349
|
+
icon: string, // Same as persona ID
|
|
350
|
+
}>,
|
|
351
|
+
quickWins: EnrichedFinding[], // Top 3 Critical/Serious findings with fixCode
|
|
352
|
+
targetUrl: string, // The scanned URL
|
|
353
|
+
detectedStack: {
|
|
354
|
+
framework: string | null, // "nextjs" | "react" | etc.
|
|
355
|
+
cms: string | null, // "wordpress" | "shopify" | etc.
|
|
356
|
+
uiLibraries: string[], // ["radix-ui", "tailwindcss", ...]
|
|
357
|
+
},
|
|
358
|
+
totalFindings: number, // Total enriched findings count
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
242
362
|
---
|
|
243
363
|
|
|
244
364
|
## Output API
|
|
@@ -254,12 +374,18 @@ const { buffer, contentType } = await getPDFReport(payload, {
|
|
|
254
374
|
baseUrl: "https://example.com",
|
|
255
375
|
target: "WCAG 2.2 AA",
|
|
256
376
|
});
|
|
257
|
-
|
|
258
|
-
// In a Next.js API route:
|
|
259
|
-
return new Response(buffer, { headers: { "Content-Type": contentType } });
|
|
260
377
|
```
|
|
261
378
|
|
|
262
|
-
Returns: `Promise<
|
|
379
|
+
Returns: `Promise<PDFReportResult>`
|
|
380
|
+
|
|
381
|
+
**`PDFReportResult` shape:**
|
|
382
|
+
|
|
383
|
+
```ts
|
|
384
|
+
{
|
|
385
|
+
buffer: Buffer, // Raw PDF binary data
|
|
386
|
+
contentType: "application/pdf", // MIME type for response headers
|
|
387
|
+
}
|
|
388
|
+
```
|
|
263
389
|
|
|
264
390
|
---
|
|
265
391
|
|
|
@@ -276,7 +402,16 @@ const { html, contentType } = await getHTMLReport(payload, {
|
|
|
276
402
|
});
|
|
277
403
|
```
|
|
278
404
|
|
|
279
|
-
Returns: `Promise<
|
|
405
|
+
Returns: `Promise<HTMLReportResult>`
|
|
406
|
+
|
|
407
|
+
**`HTMLReportResult` shape:**
|
|
408
|
+
|
|
409
|
+
```ts
|
|
410
|
+
{
|
|
411
|
+
html: string, // Self-contained HTML document string
|
|
412
|
+
contentType: "text/html", // MIME type for response headers
|
|
413
|
+
}
|
|
414
|
+
```
|
|
280
415
|
|
|
281
416
|
---
|
|
282
417
|
|
|
@@ -292,7 +427,16 @@ const { html, contentType } = await getChecklist({
|
|
|
292
427
|
});
|
|
293
428
|
```
|
|
294
429
|
|
|
295
|
-
Returns: `Promise<
|
|
430
|
+
Returns: `Promise<ChecklistResult>`
|
|
431
|
+
|
|
432
|
+
**`ChecklistResult` shape:**
|
|
433
|
+
|
|
434
|
+
```ts
|
|
435
|
+
{
|
|
436
|
+
html: string, // Self-contained HTML with interactive checklist
|
|
437
|
+
contentType: "text/html", // MIME type for response headers
|
|
438
|
+
}
|
|
439
|
+
```
|
|
296
440
|
|
|
297
441
|
---
|
|
298
442
|
|
|
@@ -307,11 +451,18 @@ const { markdown, contentType } = await getRemediationGuide(payload, {
|
|
|
307
451
|
baseUrl: "https://example.com",
|
|
308
452
|
patternFindings: payload.patternFindings ?? null,
|
|
309
453
|
});
|
|
310
|
-
|
|
311
|
-
// Write to disk or return as download
|
|
312
454
|
```
|
|
313
455
|
|
|
314
|
-
Returns: `Promise<
|
|
456
|
+
Returns: `Promise<RemediationGuideResult>`
|
|
457
|
+
|
|
458
|
+
**`RemediationGuideResult` shape:**
|
|
459
|
+
|
|
460
|
+
```ts
|
|
461
|
+
{
|
|
462
|
+
markdown: string, // Full Markdown document with remediation roadmap
|
|
463
|
+
contentType: "text/markdown", // MIME type for response headers
|
|
464
|
+
}
|
|
465
|
+
```
|
|
315
466
|
|
|
316
467
|
---
|
|
317
468
|
|
|
@@ -326,122 +477,181 @@ const result = await getSourcePatterns("./", {
|
|
|
326
477
|
framework: "nextjs", // optional — scopes scan to framework source dirs
|
|
327
478
|
onlyPattern: "placeholder-only-label", // optional — run a single pattern
|
|
328
479
|
});
|
|
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
480
|
```
|
|
349
481
|
|
|
350
482
|
Returns: `Promise<SourcePatternResult>`
|
|
351
483
|
|
|
484
|
+
**`SourcePatternResult` shape:**
|
|
485
|
+
|
|
486
|
+
```ts
|
|
487
|
+
{
|
|
488
|
+
findings: {
|
|
489
|
+
id: string, // "PAT-a1b2c3" — unique pattern finding ID
|
|
490
|
+
pattern_id: string, // Pattern definition ID (e.g. "placeholder-only-label")
|
|
491
|
+
title: string, // Human-readable issue title
|
|
492
|
+
severity: string, // "Critical" | "Serious" | "Moderate" | "Minor"
|
|
493
|
+
status: string, // "confirmed" | "potential"
|
|
494
|
+
file: string, // Relative file path (e.g. "src/components/SearchBar.tsx")
|
|
495
|
+
line: number, // Line number where the pattern was found
|
|
496
|
+
match: string, // The matching source code line
|
|
497
|
+
context: string, // Surrounding code for context
|
|
498
|
+
fix_description: string, // How to fix the pattern
|
|
499
|
+
}[],
|
|
500
|
+
summary: {
|
|
501
|
+
total: number, // Total findings found
|
|
502
|
+
confirmed: number, // Definite accessibility issues
|
|
503
|
+
potential: number, // Likely issues that need manual review
|
|
504
|
+
},
|
|
505
|
+
}
|
|
506
|
+
```
|
|
507
|
+
|
|
352
508
|
---
|
|
353
509
|
|
|
354
510
|
## Knowledge API
|
|
355
511
|
|
|
356
|
-
These functions expose engine-owned content for UIs and agents to render. All accept an optional `{ locale?: string }` option (default: `"en"`).
|
|
357
|
-
|
|
358
512
|
### `getKnowledge(options?)`
|
|
359
513
|
|
|
360
|
-
Returns
|
|
514
|
+
Returns all accessibility knowledge in a single call. Accepts an optional `{ locale?: string }` option (default: `"en"`).
|
|
515
|
+
|
|
516
|
+
This is the **only exported Knowledge API function**. The data it returns covers scanner help, persona profiles, concepts, glossary, docs, conformance levels, WCAG principles, and severity definitions — all in one call.
|
|
361
517
|
|
|
362
518
|
```ts
|
|
363
519
|
import { getKnowledge } from "@diegovelasquezweb/a11y-engine";
|
|
364
520
|
|
|
365
521
|
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
522
|
```
|
|
375
523
|
|
|
376
|
-
Returns
|
|
377
|
-
|
|
378
|
-
---
|
|
524
|
+
**Returns:** `EngineKnowledge`
|
|
379
525
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
Returns scan option descriptions, allowed values, and engine metadata — used to render Advanced Settings UI.
|
|
526
|
+
**`EngineKnowledge` shape:**
|
|
383
527
|
|
|
384
528
|
```ts
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
529
|
+
{
|
|
530
|
+
locale: string, // "en"
|
|
531
|
+
version: string, // "1.0.0"
|
|
532
|
+
|
|
533
|
+
scanner: {
|
|
534
|
+
title: string, // "Scanner Help"
|
|
535
|
+
engines: { // Engine descriptions
|
|
536
|
+
id: string, // "axe" | "cdp" | "pa11y"
|
|
537
|
+
label: string,
|
|
538
|
+
description: string,
|
|
539
|
+
}[],
|
|
540
|
+
options: { // CLI/API option descriptions
|
|
541
|
+
name: string, // "maxRoutes"
|
|
542
|
+
type: string, // "number" | "string" | "boolean"
|
|
543
|
+
default: string | number | boolean,
|
|
544
|
+
description: string,
|
|
545
|
+
values?: string[], // Allowed values if enum-like
|
|
546
|
+
}[],
|
|
547
|
+
},
|
|
548
|
+
|
|
549
|
+
personas: { // Disability persona profiles
|
|
550
|
+
id: string, // "screenReader" | "keyboard" | "vision" | "cognitive"
|
|
551
|
+
icon: string, // Same as id, used for icon lookup
|
|
552
|
+
label: string, // "Screen Readers"
|
|
553
|
+
description: string, // Explanation of the persona
|
|
554
|
+
keywords: string[], // Keywords for matching findings
|
|
555
|
+
mappedRules: string[], // axe rule IDs mapped to this persona
|
|
556
|
+
}[],
|
|
557
|
+
|
|
558
|
+
concepts: Record<string, { // Concept definitions keyed by ID
|
|
559
|
+
title: string,
|
|
560
|
+
body: string,
|
|
561
|
+
context?: string, // When/where this concept applies
|
|
562
|
+
}>,
|
|
563
|
+
|
|
564
|
+
glossary: { // Accessibility term definitions
|
|
565
|
+
term: string,
|
|
566
|
+
definition: string,
|
|
567
|
+
}[],
|
|
568
|
+
|
|
569
|
+
docs: { // Documentation articles
|
|
570
|
+
sections: {
|
|
571
|
+
id: string,
|
|
572
|
+
title: string,
|
|
573
|
+
groups: {
|
|
574
|
+
id: string,
|
|
575
|
+
title: string,
|
|
576
|
+
articles: {
|
|
577
|
+
id: string,
|
|
578
|
+
title: string,
|
|
579
|
+
body: string,
|
|
580
|
+
}[],
|
|
581
|
+
}[],
|
|
582
|
+
}[],
|
|
583
|
+
},
|
|
584
|
+
|
|
585
|
+
conformanceLevels: { // WCAG A/AA/AAA definitions
|
|
586
|
+
level: string, // "A" | "AA" | "AAA"
|
|
587
|
+
label: string,
|
|
588
|
+
description: string,
|
|
589
|
+
axeTags: string[], // ["wcag2a", "wcag21a", "wcag22a"]
|
|
590
|
+
}[],
|
|
591
|
+
|
|
592
|
+
wcagPrinciples: { // The four WCAG principles
|
|
593
|
+
id: string, // "perceivable" | "operable" | "understandable" | "robust"
|
|
594
|
+
label: string,
|
|
595
|
+
description: string,
|
|
596
|
+
criterionPrefix: string, // "1." | "2." | "3." | "4."
|
|
597
|
+
}[],
|
|
598
|
+
|
|
599
|
+
severityLevels: { // Severity definitions
|
|
600
|
+
level: string, // "Critical" | "Serious" | "Moderate" | "Minor"
|
|
601
|
+
label: string,
|
|
602
|
+
description: string,
|
|
603
|
+
order: number, // 1 (Critical) – 4 (Minor)
|
|
604
|
+
}[],
|
|
605
|
+
}
|
|
388
606
|
```
|
|
389
607
|
|
|
390
608
|
---
|
|
391
609
|
|
|
392
|
-
|
|
610
|
+
## Constants
|
|
393
611
|
|
|
394
|
-
|
|
612
|
+
### `VIEWPORT_PRESETS`
|
|
395
613
|
|
|
396
|
-
|
|
397
|
-
const ref = getPersonaReference();
|
|
398
|
-
// ref.personas → [{ id: "screenReader", label: "Screen Readers", icon: "...", description: "..." }, ...]
|
|
399
|
-
```
|
|
400
|
-
|
|
401
|
-
---
|
|
402
|
-
|
|
403
|
-
### `getUiHelp(options?)`
|
|
404
|
-
|
|
405
|
-
Returns shared concept definitions and a glossary of accessibility terms.
|
|
614
|
+
Ready-made viewport dimensions for common device classes. Useful when building scanner UI option pickers.
|
|
406
615
|
|
|
407
616
|
```ts
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
//
|
|
617
|
+
import { VIEWPORT_PRESETS } from "@diegovelasquezweb/a11y-engine";
|
|
618
|
+
|
|
619
|
+
// VIEWPORT_PRESETS:
|
|
620
|
+
// [
|
|
621
|
+
// { label: "Desktop", width: 1280, height: 800 },
|
|
622
|
+
// { label: "Laptop", width: 1440, height: 900 },
|
|
623
|
+
// { label: "Tablet", width: 768, height: 1024 },
|
|
624
|
+
// { label: "Mobile", width: 375, height: 812 },
|
|
625
|
+
// ]
|
|
411
626
|
```
|
|
412
627
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
### `getConformanceLevels(options?)`
|
|
416
|
-
|
|
417
|
-
Returns WCAG conformance level definitions with their corresponding axe-core tag sets.
|
|
418
|
-
|
|
419
|
-
```ts
|
|
420
|
-
const { conformanceLevels } = getConformanceLevels();
|
|
421
|
-
// conformanceLevels[0] → { id: "AA", label: "WCAG 2.2 AA", axeTags: ["wcag2a", "wcag2aa", ...] }
|
|
422
|
-
```
|
|
628
|
+
Type: `ViewportPreset[]` — `{ label: string; width: number; height: number }[]`
|
|
423
629
|
|
|
424
630
|
---
|
|
425
631
|
|
|
426
|
-
### `
|
|
632
|
+
### `DEFAULT_AI_SYSTEM_PROMPT`
|
|
427
633
|
|
|
428
|
-
|
|
634
|
+
The default system prompt passed to Claude for AI enrichment. Exported so consumers can read, log, or extend it when building custom AI workflows.
|
|
429
635
|
|
|
430
636
|
```ts
|
|
431
|
-
|
|
432
|
-
// wcagPrinciples[0] → { id: "perceivable", label: "Perceivable", prefix: "1.", description: "..." }
|
|
433
|
-
```
|
|
637
|
+
import { DEFAULT_AI_SYSTEM_PROMPT } from "@diegovelasquezweb/a11y-engine";
|
|
434
638
|
|
|
435
|
-
|
|
639
|
+
// Override for a specific scan:
|
|
640
|
+
await runAudit({
|
|
641
|
+
baseUrl: "https://example.com",
|
|
642
|
+
ai: {
|
|
643
|
+
enabled: true,
|
|
644
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
645
|
+
systemPrompt: DEFAULT_AI_SYSTEM_PROMPT + "\n\nFocus on Vue 3 Composition API patterns.",
|
|
646
|
+
},
|
|
647
|
+
});
|
|
648
|
+
```
|
|
436
649
|
|
|
437
|
-
|
|
650
|
+
Type: `string`
|
|
438
651
|
|
|
439
|
-
|
|
652
|
+
---
|
|
440
653
|
|
|
441
|
-
|
|
442
|
-
const { severityLevels } = getSeverityLevels();
|
|
443
|
-
// severityLevels[0] → { id: "Critical", label: "Critical", order: 0, description: "..." }
|
|
444
|
-
```
|
|
654
|
+
> **Note on `ai_enriched_findings` fast path**: When `getFindings()` receives a payload that contains `ai_enriched_findings` and no `screenshotUrlBuilder` option is provided, it returns `ai_enriched_findings` directly without re-normalizing the raw `findings` array. If a `screenshotUrlBuilder` is provided, normalization always runs so paths can be rewritten.
|
|
445
655
|
|
|
446
656
|
---
|
|
447
657
|
|
package/docs/architecture.md
CHANGED
|
@@ -119,7 +119,7 @@ Output shape:
|
|
|
119
119
|
|
|
120
120
|
Core artifacts generated by the pipeline:
|
|
121
121
|
|
|
122
|
-
- `progress.json`: step status
|
|
122
|
+
- `progress.json`: step status — `page`, `axe`, `cdp`, `pa11y`, `merge`, `intelligence`, `repo` (remote package.json fetch), `patterns` (source pattern scan), `ai` (Claude enrichment)
|
|
123
123
|
- `a11y-scan-results.json`: merged runtime scan output per route
|
|
124
124
|
- `a11y-findings.json`: enriched findings payload used by reports and API consumers
|
|
125
125
|
|
package/docs/engine-manifest.md
CHANGED
|
@@ -49,6 +49,7 @@ This document is the current technical inventory of the engine package.
|
|
|
49
49
|
| `assets/remediation/code-patterns.mjs` | Source code pattern definitions |
|
|
50
50
|
| `assets/remediation/source-boundaries.mjs` | Framework source boundaries |
|
|
51
51
|
| `assets/remediation/axe-check-maps.mjs` | axe check-to-rule mappings |
|
|
52
|
+
| `assets/knowledge/knowledge.mjs` | Knowledge API data — scanner help, personas, concepts, glossary, docs, conformance levels, WCAG principles, severity definitions |
|
|
52
53
|
| `assets/reporting/compliance-config.mjs` | Compliance scoring configuration |
|
|
53
54
|
| `assets/reporting/wcag-reference.mjs` | WCAG + persona mapping reference |
|
|
54
55
|
| `assets/reporting/manual-checks.mjs` | Manual checklist data |
|
|
@@ -62,6 +63,7 @@ Current files:
|
|
|
62
63
|
- `tests/asset-loader.test.mjs`
|
|
63
64
|
- `tests/audit-summary.test.mjs`
|
|
64
65
|
- `tests/enriched-findings.test.mjs`
|
|
66
|
+
- `tests/knowledge-api.test.mjs`
|
|
65
67
|
- `tests/reports-api.test.mjs`
|
|
66
68
|
- `tests/reports-paths.test.mjs`
|
|
67
69
|
- `tests/run-audit.integration.test.mjs`
|
package/docs/intelligence.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Intelligence & Enrichment
|
|
2
2
|
|
|
3
|
-
**Navigation**: [Home](../README.md) • [Architecture](architecture.md) • [API Reference](api-reference.md) • [CLI Handbook](cli-handbook.md) • [Output Artifacts](outputs.md) • [Engine Manifest](engine-manifest.md) • [
|
|
3
|
+
**Navigation**: [Home](../README.md) • [Architecture](architecture.md) • [Intelligence](intelligence.md) • [API Reference](api-reference.md) • [CLI Handbook](cli-handbook.md) • [Output Artifacts](outputs.md) • [Engine Manifest](engine-manifest.md) • [Testing](testing.md)
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
package/docs/outputs.md
CHANGED
|
@@ -63,6 +63,10 @@ Real-time scan progress written by `src/pipeline/dom-scanner.mjs` as each engine
|
|
|
63
63
|
| `cdp` | CDP | Chrome DevTools Protocol accessibility tree check. `found` = issue count. |
|
|
64
64
|
| `pa11y` | pa11y | HTML CodeSniffer scan. `found` = issue count. |
|
|
65
65
|
| `merge` | — | Cross-engine merge and deduplication. `merged` = final unique count. |
|
|
66
|
+
| `repo` | — | Remote `package.json` fetch via GitHub API for stack detection. Only emitted when `repoUrl` is set. |
|
|
67
|
+
| `intelligence` | — | Analyzer enrichment step (fix intelligence, ownership, scoring). |
|
|
68
|
+
| `patterns` | — | Source code pattern scan (local or remote). Only emitted when `projectDir` or `repoUrl` is set. `total` / `confirmed` / `potential` counts in `extra`. |
|
|
69
|
+
| `ai` | Claude | AI enrichment via Anthropic API. Only emitted when `ai.enabled` is `true` and `ai.apiKey` is set. |
|
|
66
70
|
|
|
67
71
|
### Step statuses
|
|
68
72
|
|
package/docs/testing.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
- **Framework**: Vitest
|
|
16
16
|
- **Command**: `pnpm test`
|
|
17
|
-
- **Current suite**:
|
|
17
|
+
- **Current suite**: 9 files, 38 tests
|
|
18
18
|
|
|
19
19
|
The suite focuses on regression protection for architecture changes, public API contracts, and critical report-generation paths.
|
|
20
20
|
|
|
@@ -32,19 +32,24 @@ The suite focuses on regression protection for architecture changes, public API
|
|
|
32
32
|
- `tests/audit-summary.test.mjs`
|
|
33
33
|
- Verifies canonicalization, normalization, sorting, effort inference, quick wins, and detected stack output.
|
|
34
34
|
|
|
35
|
-
### 3)
|
|
35
|
+
### 3) Knowledge API
|
|
36
|
+
|
|
37
|
+
- `tests/knowledge-api.test.mjs`
|
|
38
|
+
- Verifies `getKnowledge()` returns the full expected shape: scanner, personas, concepts, glossary, docs, conformance levels, WCAG principles, and severity levels.
|
|
39
|
+
|
|
40
|
+
### 4) Report API and import safety
|
|
36
41
|
|
|
37
42
|
- `tests/reports-api.test.mjs`
|
|
38
43
|
- `tests/reports-paths.test.mjs`
|
|
39
44
|
- Verifies report APIs return expected output types and protects against broken relative imports after refactors.
|
|
40
45
|
|
|
41
|
-
###
|
|
46
|
+
### 5) Source-pattern behavior
|
|
42
47
|
|
|
43
48
|
- `tests/source-patterns.test.mjs`
|
|
44
49
|
- `tests/source-scanner-utils.test.mjs`
|
|
45
50
|
- Verifies edge behavior for pattern filtering and source scanner utility functions.
|
|
46
51
|
|
|
47
|
-
###
|
|
52
|
+
### 6) Integration tests (no network)
|
|
48
53
|
|
|
49
54
|
- `tests/run-audit.integration.test.mjs`
|
|
50
55
|
- Mocks scanner/analyzer modules to verify:
|
package/package.json
CHANGED
package/src/cli/audit.mjs
CHANGED
|
@@ -34,6 +34,7 @@ Audit Intelligence:
|
|
|
34
34
|
--axe-tags <csv> Comma-separated axe tags (e.g., wcag2a,wcag2aa).
|
|
35
35
|
--only-rule <id> Only check for this specific rule ID.
|
|
36
36
|
--ignore-findings <csv> Ignore specific rule IDs.
|
|
37
|
+
--include-incomplete Include axe "incomplete" items as findings.
|
|
37
38
|
--exclude-selectors <csv> Exclude CSS selectors from scan.
|
|
38
39
|
|
|
39
40
|
Execution & Emulation:
|
|
@@ -187,6 +188,7 @@ async function main() {
|
|
|
187
188
|
const skipPatterns = argv.includes("--skip-patterns");
|
|
188
189
|
const affectedOnly = argv.includes("--affected-only");
|
|
189
190
|
const ignoreFindings = getArgValue("ignore-findings");
|
|
191
|
+
const includeIncomplete = argv.includes("--include-incomplete");
|
|
190
192
|
const excludeSelectors = getArgValue("exclude-selectors");
|
|
191
193
|
|
|
192
194
|
const waitUntil = getArgValue("wait-until");
|
|
@@ -290,6 +292,7 @@ async function main() {
|
|
|
290
292
|
|
|
291
293
|
const analyzerArgs = [];
|
|
292
294
|
if (ignoreFindings) analyzerArgs.push("--ignore-findings", ignoreFindings);
|
|
295
|
+
if (includeIncomplete) analyzerArgs.push("--include-incomplete");
|
|
293
296
|
const resolvedFramework = framework || detectedFramework;
|
|
294
297
|
if (resolvedFramework) analyzerArgs.push("--framework", resolvedFramework);
|
|
295
298
|
await runScript("../enrichment/analyzer.mjs", analyzerArgs);
|
|
@@ -257,6 +257,7 @@ function printUsage() {
|
|
|
257
257
|
Options:
|
|
258
258
|
--output <path> Output findings JSON path (default: .audit/a11y-findings.json)
|
|
259
259
|
--ignore-findings <csv> Ignore specific rule IDs (overrides config)
|
|
260
|
+
--include-incomplete Include axe "incomplete" items as findings
|
|
260
261
|
-h, --help Show this help
|
|
261
262
|
`);
|
|
262
263
|
}
|
|
@@ -276,10 +277,15 @@ function parseArgs(argv) {
|
|
|
276
277
|
input: getInternalPath("a11y-scan-results.json"),
|
|
277
278
|
output: getInternalPath("a11y-findings.json"),
|
|
278
279
|
ignoreFindings: [],
|
|
280
|
+
includeIncomplete: false,
|
|
279
281
|
};
|
|
280
282
|
|
|
281
283
|
for (let i = 0; i < argv.length; i += 1) {
|
|
282
284
|
const key = argv[i];
|
|
285
|
+
if (key === "--include-incomplete") {
|
|
286
|
+
args.includeIncomplete = true;
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
283
289
|
const value = argv[i + 1];
|
|
284
290
|
if (!key.startsWith("--") || value === undefined) continue;
|
|
285
291
|
|
|
@@ -897,6 +903,7 @@ function buildFindings(inputPayload, cliArgs) {
|
|
|
897
903
|
primary_source_scope: ownership.primarySourceScope,
|
|
898
904
|
search_strategy: ownership.searchStrategy,
|
|
899
905
|
component_hint: extractComponentHint(bestSelector) ?? derivePageHint(route.path),
|
|
906
|
+
needs_verification: !!v._fromIncomplete,
|
|
900
907
|
verification_command: `pnpm a11y --base-url ${route.url} --routes ${route.path} --only-rule ${v.id} --max-routes 1`,
|
|
901
908
|
verification_command_fallback: `node scripts/audit.mjs --base-url ${route.url} --routes ${route.path} --only-rule ${v.id} --max-routes 1`,
|
|
902
909
|
});
|
|
@@ -966,12 +973,33 @@ export function collectIncompleteFindings(routes) {
|
|
|
966
973
|
/**
|
|
967
974
|
* Runs the analyzer programmatically on a scan payload.
|
|
968
975
|
* @param {Object} scanPayload - The raw scan output from dom-scanner ({ routes, base_url, projectContext, ... }).
|
|
969
|
-
* @param {{ ignoreFindings?: string[], framework?: string, output?: string }} [options={}]
|
|
976
|
+
* @param {{ ignoreFindings?: string[], framework?: string, output?: string, includeIncomplete?: boolean }} [options={}]
|
|
970
977
|
* @returns {Object} The enriched findings payload { findings, incomplete_findings, metadata, ... }.
|
|
971
978
|
*/
|
|
972
979
|
export function runAnalyzer(scanPayload, options = {}) {
|
|
973
980
|
if (!scanPayload) throw new Error("Missing scan payload");
|
|
974
981
|
|
|
982
|
+
const sourceRoutes = scanPayload.routes || [];
|
|
983
|
+
const routesForAnalysis = options.includeIncomplete
|
|
984
|
+
? sourceRoutes.map((route) => {
|
|
985
|
+
const incomplete = Array.isArray(route.incomplete) ? route.incomplete : [];
|
|
986
|
+
const violations = Array.isArray(route.violations) ? route.violations : [];
|
|
987
|
+
if (incomplete.length === 0) return route;
|
|
988
|
+
return {
|
|
989
|
+
...route,
|
|
990
|
+
violations: [
|
|
991
|
+
...violations,
|
|
992
|
+
...incomplete.map((item) => ({ ...item, _fromIncomplete: true })),
|
|
993
|
+
],
|
|
994
|
+
};
|
|
995
|
+
})
|
|
996
|
+
: sourceRoutes;
|
|
997
|
+
|
|
998
|
+
const scanPayloadForAnalysis =
|
|
999
|
+
routesForAnalysis === sourceRoutes
|
|
1000
|
+
? scanPayload
|
|
1001
|
+
: { ...scanPayload, routes: routesForAnalysis };
|
|
1002
|
+
|
|
975
1003
|
const args = {
|
|
976
1004
|
input: null,
|
|
977
1005
|
output: options.output || getInternalPath("a11y-findings.json"),
|
|
@@ -980,7 +1008,7 @@ export function runAnalyzer(scanPayload, options = {}) {
|
|
|
980
1008
|
};
|
|
981
1009
|
|
|
982
1010
|
const ignoredRules = new Set(args.ignoreFindings);
|
|
983
|
-
const result = buildFindings(
|
|
1011
|
+
const result = buildFindings(scanPayloadForAnalysis, args);
|
|
984
1012
|
|
|
985
1013
|
if (ignoredRules.size > 0) {
|
|
986
1014
|
const knownIds = new Set(
|
|
@@ -1056,6 +1084,7 @@ function main() {
|
|
|
1056
1084
|
runAnalyzer(payload, {
|
|
1057
1085
|
ignoreFindings: args.ignoreFindings,
|
|
1058
1086
|
framework: args.framework,
|
|
1087
|
+
includeIncomplete: args.includeIncomplete,
|
|
1059
1088
|
output: args.output,
|
|
1060
1089
|
});
|
|
1061
1090
|
}
|
package/src/index.d.mts
CHANGED
|
@@ -51,6 +51,7 @@ export interface Finding {
|
|
|
51
51
|
source_rule_id?: string | null;
|
|
52
52
|
pages_affected?: number | null;
|
|
53
53
|
affected_urls?: string[] | null;
|
|
54
|
+
needs_verification?: boolean;
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
export interface EnrichedFinding {
|
|
@@ -102,6 +103,7 @@ export interface EnrichedFinding {
|
|
|
102
103
|
checkData: Record<string, unknown> | null;
|
|
103
104
|
pagesAffected: number | null;
|
|
104
105
|
affectedUrls: string[] | null;
|
|
106
|
+
needsVerification?: boolean;
|
|
105
107
|
}
|
|
106
108
|
|
|
107
109
|
export interface SeverityTotals {
|
|
@@ -142,6 +144,7 @@ export interface AuditSummary {
|
|
|
142
144
|
export interface ScanPayload {
|
|
143
145
|
findings: Finding[] | Record<string, unknown>[];
|
|
144
146
|
metadata?: Record<string, unknown>;
|
|
147
|
+
incomplete_findings?: unknown[];
|
|
145
148
|
}
|
|
146
149
|
|
|
147
150
|
export interface ReportOptions {
|
|
@@ -397,6 +400,7 @@ export interface RunAuditOptions {
|
|
|
397
400
|
repoUrl?: string;
|
|
398
401
|
githubToken?: string;
|
|
399
402
|
skipPatterns?: boolean;
|
|
403
|
+
includeIncomplete?: boolean;
|
|
400
404
|
screenshotsDir?: string;
|
|
401
405
|
engines?: EngineSelection;
|
|
402
406
|
ai?: AiOptions;
|
|
@@ -449,18 +453,6 @@ export function getSourcePatterns(
|
|
|
449
453
|
options?: SourcePatternOptions
|
|
450
454
|
): Promise<SourcePatternResult>;
|
|
451
455
|
|
|
452
|
-
export function getScannerHelp(options?: KnowledgeOptions): ScannerHelp;
|
|
453
|
-
|
|
454
|
-
export function getPersonaReference(options?: KnowledgeOptions): PersonaReference;
|
|
455
|
-
|
|
456
|
-
export function getUiHelp(options?: KnowledgeOptions): UiHelp;
|
|
457
|
-
|
|
458
|
-
export function getConformanceLevels(options?: KnowledgeOptions): ConformanceLevelsResult;
|
|
459
|
-
|
|
460
|
-
export function getWcagPrinciples(options?: KnowledgeOptions): WcagPrinciplesResult;
|
|
461
|
-
|
|
462
|
-
export function getSeverityLevels(options?: KnowledgeOptions): SeverityLevelsResult;
|
|
463
|
-
|
|
464
456
|
export function getKnowledge(options?: KnowledgeOptions): EngineKnowledge;
|
|
465
457
|
|
|
466
458
|
export const DEFAULT_AI_SYSTEM_PROMPT: string;
|
package/src/index.mjs
CHANGED
|
@@ -161,6 +161,7 @@ function normalizeSingleFinding(item, index, screenshotUrlBuilder) {
|
|
|
161
161
|
check_data: item.check_data && typeof item.check_data === "object" ? item.check_data : null,
|
|
162
162
|
pages_affected: typeof item.pages_affected === "number" ? item.pages_affected : null,
|
|
163
163
|
affected_urls: Array.isArray(item.affected_urls) ? item.affected_urls : null,
|
|
164
|
+
needs_verification: Boolean(item.needs_verification),
|
|
164
165
|
};
|
|
165
166
|
}
|
|
166
167
|
|
|
@@ -247,6 +248,7 @@ export function getFindings(input, options = {}) {
|
|
|
247
248
|
checkData: finding.check_data,
|
|
248
249
|
pagesAffected: finding.pages_affected,
|
|
249
250
|
affectedUrls: finding.affected_urls,
|
|
251
|
+
needsVerification: finding.needs_verification,
|
|
250
252
|
};
|
|
251
253
|
|
|
252
254
|
// Enrich from intelligence if no fix data exists yet
|
|
@@ -379,8 +381,9 @@ function getPersonaGroups(findings) {
|
|
|
379
381
|
* @returns {object} Full audit summary.
|
|
380
382
|
*/
|
|
381
383
|
export function getOverview(findings, payload = null) {
|
|
384
|
+
const scorableFindings = findings.filter((f) => !f.needsVerification);
|
|
382
385
|
const totals = { Critical: 0, Serious: 0, Moderate: 0, Minor: 0 };
|
|
383
|
-
for (const f of
|
|
386
|
+
for (const f of scorableFindings) {
|
|
384
387
|
const severity = f.severity || "";
|
|
385
388
|
if (severity in totals) totals[severity] += 1;
|
|
386
389
|
}
|
|
@@ -434,7 +437,7 @@ export function getOverview(findings, payload = null) {
|
|
|
434
437
|
* @param {{ locale?: string }} [options={}]
|
|
435
438
|
* @returns {{ locale: string, version: string, title: string, engines: object[], options: object[] }}
|
|
436
439
|
*/
|
|
437
|
-
|
|
440
|
+
function getScannerHelp(options = {}) {
|
|
438
441
|
const locale = resolveKnowledgeLocale(options.locale || "en");
|
|
439
442
|
const payload = getKnowledgeData();
|
|
440
443
|
const scanner = payload.locales[locale]?.scanner || { title: "Scanner Help", engines: [], options: [] };
|
|
@@ -455,7 +458,7 @@ export function getScannerHelp(options = {}) {
|
|
|
455
458
|
* @param {{ locale?: string }} [options={}]
|
|
456
459
|
* @returns {{ locale: string, version: string, personas: object[] }}
|
|
457
460
|
*/
|
|
458
|
-
|
|
461
|
+
function getPersonaReference(options = {}) {
|
|
459
462
|
const locale = resolveKnowledgeLocale(options.locale || "en");
|
|
460
463
|
const payload = getKnowledgeData();
|
|
461
464
|
const wcagRef = getWcagReference();
|
|
@@ -492,7 +495,7 @@ export function getPersonaReference(options = {}) {
|
|
|
492
495
|
* @param {{ locale?: string }} [options={}]
|
|
493
496
|
* @returns {{ locale: string, version: string, tooltips: Record<string, object>, glossary: object[] }}
|
|
494
497
|
*/
|
|
495
|
-
|
|
498
|
+
function getConceptsAndGlossary(options = {}) {
|
|
496
499
|
const locale = resolveKnowledgeLocale(options.locale || "en");
|
|
497
500
|
const payload = getKnowledgeData();
|
|
498
501
|
const localePayload = payload.locales[locale] || {};
|
|
@@ -518,7 +521,7 @@ export function getUiHelp(options = {}) {
|
|
|
518
521
|
* @param {{ locale?: string }} [options={}]
|
|
519
522
|
* @returns {{ locale: string, version: string, conformanceLevels: object[] }}
|
|
520
523
|
*/
|
|
521
|
-
|
|
524
|
+
function getConformanceLevels(options = {}) {
|
|
522
525
|
const locale = resolveKnowledgeLocale(options.locale || "en");
|
|
523
526
|
const payload = getKnowledgeData();
|
|
524
527
|
const levels = payload.locales[locale]?.conformanceLevels || [];
|
|
@@ -535,7 +538,7 @@ export function getConformanceLevels(options = {}) {
|
|
|
535
538
|
* @param {{ locale?: string }} [options={}]
|
|
536
539
|
* @returns {{ locale: string, version: string, wcagPrinciples: object[] }}
|
|
537
540
|
*/
|
|
538
|
-
|
|
541
|
+
function getWcagPrinciples(options = {}) {
|
|
539
542
|
const locale = resolveKnowledgeLocale(options.locale || "en");
|
|
540
543
|
const payload = getKnowledgeData();
|
|
541
544
|
const principles = payload.locales[locale]?.wcagPrinciples || [];
|
|
@@ -552,7 +555,7 @@ export function getWcagPrinciples(options = {}) {
|
|
|
552
555
|
* @param {{ locale?: string }} [options={}]
|
|
553
556
|
* @returns {{ locale: string, version: string, severityLevels: object[] }}
|
|
554
557
|
*/
|
|
555
|
-
|
|
558
|
+
function getSeverityLevels(options = {}) {
|
|
556
559
|
const locale = resolveKnowledgeLocale(options.locale || "en");
|
|
557
560
|
const payload = getKnowledgeData();
|
|
558
561
|
const levels = payload.locales[locale]?.severityLevels || [];
|
|
@@ -573,7 +576,7 @@ export const VIEWPORT_PRESETS = [
|
|
|
573
576
|
export function getKnowledge(options = {}) {
|
|
574
577
|
const scanner = getScannerHelp(options);
|
|
575
578
|
const personas = getPersonaReference(options);
|
|
576
|
-
const ui =
|
|
579
|
+
const ui = getConceptsAndGlossary(options);
|
|
577
580
|
const conformance = getConformanceLevels(options);
|
|
578
581
|
const principles = getWcagPrinciples(options);
|
|
579
582
|
const severity = getSeverityLevels(options);
|
|
@@ -622,6 +625,7 @@ export function getKnowledge(options = {}) {
|
|
|
622
625
|
* framework?: string,
|
|
623
626
|
* projectDir?: string,
|
|
624
627
|
* skipPatterns?: boolean,
|
|
628
|
+
* includeIncomplete?: boolean,
|
|
625
629
|
* engines?: { axe?: boolean, cdp?: boolean, pa11y?: boolean },
|
|
626
630
|
* onProgress?: (step: string, status: string, extra?: object) => void,
|
|
627
631
|
* }} options
|
|
@@ -691,6 +695,7 @@ export async function runAudit(options) {
|
|
|
691
695
|
const findingsPayload = runAnalyzer(scanPayload, {
|
|
692
696
|
ignoreFindings: options.ignoreFindings,
|
|
693
697
|
framework: options.framework,
|
|
698
|
+
includeIncomplete: options.includeIncomplete,
|
|
694
699
|
});
|
|
695
700
|
|
|
696
701
|
// Step 3: Source patterns (optional) — works with local projectDir or remote repoUrl
|