@diegovelasquezweb/a11y-engine 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/assets/knowledge/knowledge.mjs +253 -0
- package/docs/api-reference.md +31 -1
- package/docs/architecture.md +1 -1
- package/docs/cli-handbook.md +1 -1
- package/docs/engine-manifest.md +1 -1
- package/docs/intelligence.md +209 -0
- package/docs/outputs.md +1 -1
- package/docs/testing.md +1 -1
- package/package.json +1 -1
- package/src/core/asset-loader.mjs +7 -0
- package/src/enrichment/analyzer.mjs +1 -1
- package/src/index.d.mts +113 -0
- package/src/index.mjs +129 -0
- package/src/reports/html.mjs +9 -9
package/README.md
CHANGED
|
@@ -37,6 +37,10 @@ import {
|
|
|
37
37
|
getChecklist,
|
|
38
38
|
getRemediationGuide,
|
|
39
39
|
getSourcePatterns,
|
|
40
|
+
getScannerHelp,
|
|
41
|
+
getPersonaReference,
|
|
42
|
+
getUiHelp,
|
|
43
|
+
getKnowledge,
|
|
40
44
|
} from "@diegovelasquezweb/a11y-engine";
|
|
41
45
|
```
|
|
42
46
|
|
|
@@ -113,6 +117,18 @@ These functions render final artifacts from scan payload data.
|
|
|
113
117
|
| `getRemediationGuide(payload, options?)` | `{ markdown, contentType }` | Markdown remediation guide |
|
|
114
118
|
| `getSourcePatterns(projectDir, options?)` | `{ findings, summary }` | Source code pattern analysis |
|
|
115
119
|
|
|
120
|
+
### Knowledge API
|
|
121
|
+
|
|
122
|
+
These functions expose scanner help content, persona explanations, and UI copy
|
|
123
|
+
so frontends or agents can render tooltips and guidance from engine-owned data.
|
|
124
|
+
|
|
125
|
+
| Function | Returns | Description |
|
|
126
|
+
| :--- | :--- | :--- |
|
|
127
|
+
| `getScannerHelp(options?)` | `{ locale, version, title, engines, options }` | Scanner option and engine help metadata |
|
|
128
|
+
| `getPersonaReference(options?)` | `{ locale, version, personas }` | Persona labels, descriptions, and mapping hints |
|
|
129
|
+
| `getUiHelp(options?)` | `{ locale, version, tooltips, glossary }` | Shared tooltip copy and glossary entries |
|
|
130
|
+
| `getKnowledge(options?)` | `{ locale, version, scanner, personas, tooltips, glossary }` | Full documentation pack for UI or agent flows |
|
|
131
|
+
|
|
116
132
|
See [API Reference](docs/api-reference.md) for exact options and return types.
|
|
117
133
|
|
|
118
134
|
## Optional CLI
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
version: "1.0.0",
|
|
3
|
+
locales: {
|
|
4
|
+
en: {
|
|
5
|
+
scanner: {
|
|
6
|
+
title: "Scanner Help",
|
|
7
|
+
engines: [
|
|
8
|
+
{
|
|
9
|
+
id: "axe",
|
|
10
|
+
label: "axe-core",
|
|
11
|
+
description: "Primary WCAG rule engine for rendered DOM violations.",
|
|
12
|
+
defaultEnabled: true,
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: "cdp",
|
|
16
|
+
label: "CDP",
|
|
17
|
+
description: "Chrome accessibility tree checks for name/role/focus gaps.",
|
|
18
|
+
defaultEnabled: true,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: "pa11y",
|
|
22
|
+
label: "pa11y",
|
|
23
|
+
description: "HTML CodeSniffer checks to complement axe coverage.",
|
|
24
|
+
defaultEnabled: true,
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
options: [
|
|
28
|
+
{
|
|
29
|
+
id: "maxRoutes",
|
|
30
|
+
label: "Max routes",
|
|
31
|
+
description: "Limits how many same-origin pages are scanned.",
|
|
32
|
+
defaultValue: 10,
|
|
33
|
+
type: "number",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: "crawlDepth",
|
|
37
|
+
label: "Crawl depth",
|
|
38
|
+
description: "Controls how many link levels are explored from the start page.",
|
|
39
|
+
defaultValue: 2,
|
|
40
|
+
type: "number",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "waitUntil",
|
|
44
|
+
label: "Load strategy",
|
|
45
|
+
description: "Navigation readiness event before scanning each page.",
|
|
46
|
+
defaultValue: "domcontentloaded",
|
|
47
|
+
type: "enum",
|
|
48
|
+
allowedValues: ["domcontentloaded", "load", "networkidle"],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: "timeoutMs",
|
|
52
|
+
label: "Timeout",
|
|
53
|
+
description: "Maximum navigation wait time per route in milliseconds.",
|
|
54
|
+
defaultValue: 30000,
|
|
55
|
+
type: "number",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: "viewport",
|
|
59
|
+
label: "Viewport",
|
|
60
|
+
description: "Browser viewport used during scan emulation.",
|
|
61
|
+
defaultValue: "1280x800",
|
|
62
|
+
type: "text",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: "colorScheme",
|
|
66
|
+
label: "Color scheme",
|
|
67
|
+
description: "Emulated light or dark color-scheme preference.",
|
|
68
|
+
defaultValue: "light",
|
|
69
|
+
type: "enum",
|
|
70
|
+
allowedValues: ["light", "dark"],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: "axeTags",
|
|
74
|
+
label: "Conformance tags",
|
|
75
|
+
description: "WCAG tag filters sent to axe-core.",
|
|
76
|
+
defaultValue: ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22a", "wcag22aa"],
|
|
77
|
+
type: "string[]",
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
personas: {
|
|
82
|
+
screenReader: {
|
|
83
|
+
label: "Screen Reader Users",
|
|
84
|
+
description: "People who rely on spoken output and semantic structure.",
|
|
85
|
+
},
|
|
86
|
+
keyboard: {
|
|
87
|
+
label: "Keyboard-Only Users",
|
|
88
|
+
description: "People who navigate and operate controls using the keyboard only.",
|
|
89
|
+
},
|
|
90
|
+
vision: {
|
|
91
|
+
label: "Low Vision Users",
|
|
92
|
+
description: "People affected by low contrast, scaling, and visual clarity issues.",
|
|
93
|
+
},
|
|
94
|
+
cognitive: {
|
|
95
|
+
label: "Cognitive & Learning Users",
|
|
96
|
+
description: "People who benefit from predictable behavior and clear instructions.",
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
tooltips: {
|
|
100
|
+
scoreGauge: {
|
|
101
|
+
title: "Compliance Score",
|
|
102
|
+
body: "Weighted score from severity totals. It is a prioritization signal, not legal certification.",
|
|
103
|
+
},
|
|
104
|
+
wcagStatus: {
|
|
105
|
+
title: "WCAG Status",
|
|
106
|
+
body: "Pass = no issues. Conditional Pass = only Moderate/Minor. Fail = any Critical/Serious remaining.",
|
|
107
|
+
},
|
|
108
|
+
severityCards: {
|
|
109
|
+
title: "Severity Breakdown",
|
|
110
|
+
body: "Issue counts grouped by user impact and task completion risk.",
|
|
111
|
+
},
|
|
112
|
+
personaImpact: {
|
|
113
|
+
title: "Persona Impact",
|
|
114
|
+
body: "Shows which user groups are most affected by current findings.",
|
|
115
|
+
},
|
|
116
|
+
quickWins: {
|
|
117
|
+
title: "Quick Wins",
|
|
118
|
+
body: "Top Critical/Serious findings that already include concrete fix code.",
|
|
119
|
+
},
|
|
120
|
+
findingsFilter: {
|
|
121
|
+
title: "Findings Filter",
|
|
122
|
+
body: "Filter findings by severity or WCAG principle to focus remediation.",
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
docs: {
|
|
126
|
+
sections: [
|
|
127
|
+
{
|
|
128
|
+
id: "understanding-wcag",
|
|
129
|
+
heading: "Understanding WCAG",
|
|
130
|
+
groups: [
|
|
131
|
+
{
|
|
132
|
+
id: "wcag-versions",
|
|
133
|
+
label: "WCAG Versions",
|
|
134
|
+
articles: [
|
|
135
|
+
{
|
|
136
|
+
id: "wcag-2-0",
|
|
137
|
+
title: "WCAG 2.0",
|
|
138
|
+
tag: "2008",
|
|
139
|
+
tagVariant: "neutral",
|
|
140
|
+
summary: "The original W3C recommendation that established the foundation for web accessibility.",
|
|
141
|
+
body: "Introduced the four principles (Perceivable, Operable, Understandable, Robust) and three conformance levels (A, AA, AAA). Covers core requirements like text alternatives, keyboard access, color contrast, and form labels. Still widely referenced in legal frameworks worldwide.",
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
id: "wcag-2-1",
|
|
145
|
+
title: "WCAG 2.1",
|
|
146
|
+
tag: "2018",
|
|
147
|
+
tagVariant: "info",
|
|
148
|
+
summary: "Extended 2.0 with 17 new success criteria for mobile, low vision, and cognitive disabilities.",
|
|
149
|
+
body: "Added criteria for touch targets (2.5.5), text spacing (1.4.12), content reflow (1.4.10), orientation (1.3.4), and input purpose (1.3.5). Required by the European Accessibility Act (EAA) and referenced in updated ADA guidance. All 2.0 criteria remain \u2014 2.1 is a superset.",
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
id: "wcag-2-2",
|
|
153
|
+
title: "WCAG 2.2",
|
|
154
|
+
tag: "2023",
|
|
155
|
+
tagVariant: "violet",
|
|
156
|
+
summary: "The latest version, adding 9 new criteria focused on cognitive accessibility and consistent help.",
|
|
157
|
+
body: "Key additions include consistent help (3.2.6), accessible authentication (3.3.8), dragging movements (2.5.7), and focus appearance (2.4.11/2.4.12). Removed criterion 4.1.1 (Parsing) as it\u2019s now handled by modern browsers. Supersedes both 2.0 and 2.1 \u2014 all prior criteria are included.",
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
id: "conformance-levels",
|
|
163
|
+
label: "Conformance Levels",
|
|
164
|
+
articles: [
|
|
165
|
+
{
|
|
166
|
+
id: "level-a",
|
|
167
|
+
title: "Level A",
|
|
168
|
+
tag: "Minimum",
|
|
169
|
+
tagVariant: "warning",
|
|
170
|
+
summary: "The baseline: essential requirements that remove the most severe barriers.",
|
|
171
|
+
body: "Covers fundamentals like non-text content alternatives (1.1.1), keyboard operability (2.1.1), page titles (2.4.2), and language of the page (3.1.1). Failing Level A means some users cannot access the content at all. Every site should meet Level A at minimum.",
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
id: "level-aa",
|
|
175
|
+
title: "Level AA",
|
|
176
|
+
tag: "Standard",
|
|
177
|
+
tagVariant: "success",
|
|
178
|
+
summary: "The recommended target for most websites \u2014 required by most accessibility laws.",
|
|
179
|
+
body: "Includes all Level A criteria plus requirements for color contrast (1.4.3 \u2014 4.5:1 ratio), resize text (1.4.4), focus visible (2.4.7), error suggestion (3.3.3), and consistent navigation (3.2.3). Referenced by ADA, Section 508, EN 301 549, and EAA. This is the standard the scanner defaults to.",
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
id: "level-aaa",
|
|
183
|
+
title: "Level AAA",
|
|
184
|
+
tag: "Enhanced",
|
|
185
|
+
tagVariant: "purple",
|
|
186
|
+
summary: "The highest conformance level \u2014 not required but beneficial for specialized audiences.",
|
|
187
|
+
body: "Adds stricter contrast (1.4.6 \u2014 7:1 ratio), sign language for audio (1.2.6), extended audio description (1.2.7), and reading level (3.1.5). Full AAA conformance is not recommended as a general policy because some criteria cannot be satisfied for all content types. Useful for targeted sections like education or government services.",
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
id: "how-it-works",
|
|
195
|
+
heading: "How It Works",
|
|
196
|
+
articles: [
|
|
197
|
+
{
|
|
198
|
+
id: "load-render",
|
|
199
|
+
title: "Load & Render",
|
|
200
|
+
icon: "globe",
|
|
201
|
+
summary: "The scanner loads your URL in a real browser (Chromium) and waits for full render.",
|
|
202
|
+
body: "A headless Chromium instance navigates to the target URL, executes JavaScript, waits for network idle, and captures the fully rendered DOM. This ensures dynamic content (SPAs, lazy-loaded elements) is included in the analysis.",
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
id: "multi-engine-scan",
|
|
206
|
+
title: "Multi-Engine Scan",
|
|
207
|
+
icon: "cpu",
|
|
208
|
+
summary: "Multiple engines (axe-core, CDP, pa11y) run in parallel for broader coverage.",
|
|
209
|
+
body: "Each engine uses different detection techniques: axe-core runs DOM-based rule checks, CDP inspects accessibility tree properties via Chrome DevTools Protocol, and pa11y validates rendered HTML against WCAG criteria. Combined, they catch issues that any single engine would miss.",
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
id: "merge-deduplicate",
|
|
213
|
+
title: "Merge & Deduplicate",
|
|
214
|
+
icon: "git-merge",
|
|
215
|
+
summary: "Findings from all engines are normalized, deduplicated, and scored by severity.",
|
|
216
|
+
body: "Results are merged by selector + rule ID to eliminate duplicates. Each finding is mapped to its WCAG criterion, assigned a severity level (Critical/Serious/Moderate/Minor), and enriched with persona impact data showing which disability groups are affected.",
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
id: "ai-enrichment",
|
|
220
|
+
title: "AI Enrichment",
|
|
221
|
+
icon: "sparkles",
|
|
222
|
+
summary: "Each issue gets code fixes, MDN references, and framework-specific guidance.",
|
|
223
|
+
body: "The intelligence layer generates actionable fix descriptions, ready-to-use code snippets, links to relevant MDN documentation, and effort estimates. Quick Wins are identified as high-impact issues with low-effort fixes for immediate remediation.",
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
},
|
|
227
|
+
],
|
|
228
|
+
},
|
|
229
|
+
glossary: [
|
|
230
|
+
{
|
|
231
|
+
term: "Critical",
|
|
232
|
+
definition: "Blocks key user tasks with no practical workaround.",
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
term: "Serious",
|
|
236
|
+
definition: "Major barrier with difficult workaround or significant friction.",
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
term: "Moderate",
|
|
240
|
+
definition: "Usability degradation that still allows task completion.",
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
term: "Minor",
|
|
244
|
+
definition: "Lower-impact issue that still reduces quality and consistency.",
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
term: "Conditional Pass",
|
|
248
|
+
definition: "No Critical/Serious findings remain, but Moderate/Minor issues still exist.",
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
};
|
package/docs/api-reference.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# API Reference
|
|
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) • [Testing](testing.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
|
|
|
@@ -102,6 +102,36 @@ Returns: `Promise<RemediationGuide>` (`{ markdown, contentType }`)
|
|
|
102
102
|
|
|
103
103
|
Returns: `Promise<SourcePatternResult>`
|
|
104
104
|
|
|
105
|
+
## Knowledge API
|
|
106
|
+
|
|
107
|
+
### `getScannerHelp(options?)`
|
|
108
|
+
|
|
109
|
+
- `options`: `KnowledgeOptions`
|
|
110
|
+
- `locale?: string`
|
|
111
|
+
|
|
112
|
+
Returns: `ScannerHelp` (`{ locale, version, title, engines, options }`)
|
|
113
|
+
|
|
114
|
+
### `getPersonaReference(options?)`
|
|
115
|
+
|
|
116
|
+
- `options`: `KnowledgeOptions`
|
|
117
|
+
- `locale?: string`
|
|
118
|
+
|
|
119
|
+
Returns: `PersonaReference` (`{ locale, version, personas }`)
|
|
120
|
+
|
|
121
|
+
### `getUiHelp(options?)`
|
|
122
|
+
|
|
123
|
+
- `options`: `KnowledgeOptions`
|
|
124
|
+
- `locale?: string`
|
|
125
|
+
|
|
126
|
+
Returns: `UiHelp` (`{ locale, version, tooltips, glossary }`)
|
|
127
|
+
|
|
128
|
+
### `getKnowledge(options?)`
|
|
129
|
+
|
|
130
|
+
- `options`: `KnowledgeOptions`
|
|
131
|
+
- `locale?: string`
|
|
132
|
+
|
|
133
|
+
Returns: `EngineKnowledge` (`{ locale, version, scanner, personas, tooltips, glossary }`)
|
|
134
|
+
|
|
105
135
|
---
|
|
106
136
|
|
|
107
137
|
Canonical type source: `src/index.d.mts`
|
package/docs/architecture.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Engine Architecture
|
|
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) • [Testing](testing.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/cli-handbook.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# CLI Handbook
|
|
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) • [Testing](testing.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/engine-manifest.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Engine Manifest
|
|
2
2
|
|
|
3
|
-
**Navigation**: [Home](../README.md) • [Architecture](architecture.md) • [CLI Handbook](cli-handbook.md) • [Output Artifacts](outputs.md) • [Engine Manifest](engine-manifest.md) • [Testing](testing.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
|
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# Intelligence & Enrichment
|
|
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) • [Intelligence](intelligence.md) • [Testing](testing.md)
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Overview](#overview)
|
|
10
|
+
- [Enrichment Pipeline](#enrichment-pipeline)
|
|
11
|
+
- [Intelligence Database](#intelligence-database)
|
|
12
|
+
- [Finding Enrichment](#finding-enrichment)
|
|
13
|
+
- [False Positive Filtering](#false-positive-filtering)
|
|
14
|
+
- [Cross-Page Deduplication](#cross-page-deduplication)
|
|
15
|
+
- [Ownership Classification](#ownership-classification)
|
|
16
|
+
- [Persona Mapping](#persona-mapping)
|
|
17
|
+
- [Scoring & Compliance](#scoring--compliance)
|
|
18
|
+
- [Source Code Patterns](#source-code-patterns)
|
|
19
|
+
- [Assets Reference](#assets-reference)
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Overview
|
|
24
|
+
|
|
25
|
+
After the runtime engines (axe-core, CDP, pa11y) produce raw violations, the analyzer transforms them into enriched findings. This is the step that turns a list of rule IDs and selectors into actionable output with fix code, framework guidance, ownership hints, persona impact, and compliance scoring.
|
|
26
|
+
|
|
27
|
+
The enrichment runs in `src/enrichment/analyzer.mjs` and is invoked by `runAudit` (programmatic) or `audit.mjs` (CLI).
|
|
28
|
+
|
|
29
|
+
## Enrichment Pipeline
|
|
30
|
+
|
|
31
|
+
```mermaid
|
|
32
|
+
flowchart TD
|
|
33
|
+
RAW[Raw violations from dom-scanner] --> BUILD[buildFindings]
|
|
34
|
+
BUILD --> IGNORE[Apply ignoreFindings filter]
|
|
35
|
+
IGNORE --> FP[Filter false positives]
|
|
36
|
+
FP --> DEDUP[Cross-page deduplication]
|
|
37
|
+
DEDUP --> META[Compute metadata]
|
|
38
|
+
|
|
39
|
+
subgraph META[Metadata Computation]
|
|
40
|
+
OA[Overall assessment]
|
|
41
|
+
PC[Passed WCAG criteria]
|
|
42
|
+
OOS[Out of scope routes]
|
|
43
|
+
REC[Recommendations]
|
|
44
|
+
TM[Testing methodology]
|
|
45
|
+
INC[Incomplete findings]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
DEDUP --> OUTPUT[Enriched payload]
|
|
49
|
+
META --> OUTPUT
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Intelligence Database
|
|
53
|
+
|
|
54
|
+
The engine ships a bundled intelligence database at `assets/remediation/intelligence.mjs`. This contains per-rule entries keyed by axe rule ID (e.g. `color-contrast`, `image-alt`).
|
|
55
|
+
|
|
56
|
+
Each rule entry can include:
|
|
57
|
+
|
|
58
|
+
| Field | Purpose |
|
|
59
|
+
| :--- | :--- |
|
|
60
|
+
| `fix.description` | Plain-language explanation of how to fix |
|
|
61
|
+
| `fix.code` | Ready-to-apply code snippet |
|
|
62
|
+
| `category` | Grouping label (e.g. "Color & Contrast", "Forms") |
|
|
63
|
+
| `related_rules` | Other rule IDs commonly seen together |
|
|
64
|
+
| `managed_by_libraries` | UI libraries that handle this rule (e.g. `["radix-ui"]`) |
|
|
65
|
+
| `false_positive_risk` | Known false positive patterns for this rule |
|
|
66
|
+
| `fix_difficulty_notes` | Edge cases and pitfalls |
|
|
67
|
+
| `framework_notes` | Framework-specific fix guidance (keyed by framework ID) |
|
|
68
|
+
| `cms_notes` | CMS-specific fix guidance |
|
|
69
|
+
| `preferred_relationship_checks` | axe check IDs to prioritize for relationship hints |
|
|
70
|
+
| `guardrails` / `guardrails_overrides` | Must/must-not/verify constraints for automated fixes |
|
|
71
|
+
|
|
72
|
+
When a framework is detected (e.g. `nextjs`), only the relevant framework notes are included in the output. React-based frameworks (`nextjs`, `gatsby`) resolve to `react` notes.
|
|
73
|
+
|
|
74
|
+
## Finding Enrichment
|
|
75
|
+
|
|
76
|
+
For each raw violation, `buildFindings` produces an enriched finding by:
|
|
77
|
+
|
|
78
|
+
1. **Selector ranking** — scores all affected selectors by specificity (IDs > data attributes > ARIA > class names), penalizing Tailwind utility classes, and picks the most stable one as `primary_selector`.
|
|
79
|
+
|
|
80
|
+
2. **WCAG mapping** — maps axe tags to human-readable WCAG labels (e.g. "WCAG 2.1 AA") and resolves the criterion ID (e.g. `1.4.3`) from the WCAG reference database.
|
|
81
|
+
|
|
82
|
+
3. **Classification** — tags each finding as `A`, `AA`, `AAA`, or `Best Practice` based on its axe tags.
|
|
83
|
+
|
|
84
|
+
4. **Intelligence lookup** — matches the rule ID against the intelligence database to pull fix description, fix code, category, related rules, guardrails, and difficulty notes.
|
|
85
|
+
|
|
86
|
+
5. **ARIA role detection** — extracts explicit roles from HTML or infers implicit roles from tag names (e.g. `<nav>` → `navigation`), then links to APG pattern documentation.
|
|
87
|
+
|
|
88
|
+
6. **Code language detection** — analyzes fix code snippets to determine language (`html`, `jsx`, `css`) for syntax highlighting.
|
|
89
|
+
|
|
90
|
+
7. **Failure analysis** — extracts the primary failure mode, relationship hints (e.g. "label is not associated with input"), failure check messages, and related DOM context from axe node data.
|
|
91
|
+
|
|
92
|
+
8. **Component hint** — extracts a semantic component name from the selector (skipping Tailwind utilities) or derives a page hint from the route path.
|
|
93
|
+
|
|
94
|
+
9. **Impacted users** — looks up which disability groups are affected, first by rule ID, then by axe tag fallback.
|
|
95
|
+
|
|
96
|
+
10. **File search pattern** — resolves framework-specific glob patterns (from source boundaries) so developers know where to search for the source file.
|
|
97
|
+
|
|
98
|
+
11. **Stable ID** — generates a deterministic hash (`A11Y-xxxxxx`) from rule ID + URL + selector so findings are stable across scans.
|
|
99
|
+
|
|
100
|
+
## False Positive Filtering
|
|
101
|
+
|
|
102
|
+
After enrichment, confirmed false positives are removed. Currently covers:
|
|
103
|
+
|
|
104
|
+
- `color-contrast` violations where the evidence HTML contains CSS gradients (`linear-gradient`, `radial-gradient`) — axe cannot compute contrast against gradients.
|
|
105
|
+
- `color-contrast` violations where the element is hidden (`visibility: hidden`, `display: none`).
|
|
106
|
+
|
|
107
|
+
The count of removed false positives is stored in `metadata.fpFiltered`.
|
|
108
|
+
|
|
109
|
+
## Cross-Page Deduplication
|
|
110
|
+
|
|
111
|
+
When scanning multiple routes, the same component often produces identical violations on every page. The deduplicator groups findings by `rule_id` + normalized `primary_selector` and collapses them into a single representative finding with:
|
|
112
|
+
|
|
113
|
+
- `pages_affected` — number of pages where this violation appears
|
|
114
|
+
- `affected_urls` — list of all affected URLs
|
|
115
|
+
- `total_instances` — sum of DOM element instances across all pages
|
|
116
|
+
|
|
117
|
+
Selectors like `html`, `body`, and `:root` are never grouped (they're too generic).
|
|
118
|
+
|
|
119
|
+
## Ownership Classification
|
|
120
|
+
|
|
121
|
+
Each finding is classified by whether the violation lives in the project's editable source or outside it:
|
|
122
|
+
|
|
123
|
+
| Status | Meaning |
|
|
124
|
+
| :--- | :--- |
|
|
125
|
+
| `primary` | The finding should be fixed in the project source tree |
|
|
126
|
+
| `outside_primary_source` | The element comes from a WordPress plugin, cross-origin iframe, or external embed |
|
|
127
|
+
| `unknown` | Source scope could not be determined (no `projectDir` or framework boundaries) |
|
|
128
|
+
|
|
129
|
+
This drives the `search_strategy` field: `direct_source_patch` for primary findings, `verify_ownership_before_search` for outside/unknown.
|
|
130
|
+
|
|
131
|
+
## Persona Mapping
|
|
132
|
+
|
|
133
|
+
The engine maps each finding to disability groups using three layers:
|
|
134
|
+
|
|
135
|
+
1. **Rule-based** — the WCAG reference database (`assets/reporting/wcag-reference.mjs`) contains a `personaMapping` object that maps persona keys to lists of axe rule IDs. If a finding's `ruleId` matches, it's counted for that persona.
|
|
136
|
+
|
|
137
|
+
2. **Criterion-based** — if no rule match is found, the engine checks if the finding's WCAG criterion ID is associated with a persona through the rule-to-criterion map.
|
|
138
|
+
|
|
139
|
+
3. **Keyword fallback** — if neither rule nor criterion matches, the `impactedUsers` text is searched for persona keywords (e.g. "blind", "keyboard", "cognitive").
|
|
140
|
+
|
|
141
|
+
A single finding can match multiple personas. The persona configuration (`personaConfig`) defines the labels and icons:
|
|
142
|
+
|
|
143
|
+
| Key | Label |
|
|
144
|
+
| :--- | :--- |
|
|
145
|
+
| `screenReader` | Screen Readers |
|
|
146
|
+
| `keyboard` | Keyboard Only |
|
|
147
|
+
| `vision` | Color/Low Vision |
|
|
148
|
+
| `cognitive` | Cognitive/Motor |
|
|
149
|
+
|
|
150
|
+
## Scoring & Compliance
|
|
151
|
+
|
|
152
|
+
The compliance score is computed from severity totals using weights defined in `assets/reporting/compliance-config.mjs`:
|
|
153
|
+
|
|
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`).
|
|
157
|
+
4. **WCAG status** — `Pass` (no findings), `Conditional Pass` (only Moderate/Minor), or `Fail` (any Critical/Serious).
|
|
158
|
+
|
|
159
|
+
The `overallAssessment` in metadata follows the same logic for the formal compliance verdict.
|
|
160
|
+
|
|
161
|
+
Additionally, `passedCriteria` lists all WCAG criterion IDs that had explicit axe passes and no active violations — used in reports to show what the site does well.
|
|
162
|
+
|
|
163
|
+
## Source Code Patterns
|
|
164
|
+
|
|
165
|
+
The source scanner (`src/source-patterns/source-scanner.mjs`) detects accessibility issues that runtime engines cannot see because they exist only in source code.
|
|
166
|
+
|
|
167
|
+
### How it works
|
|
168
|
+
|
|
169
|
+
1. Loads pattern definitions from `assets/remediation/code-patterns.mjs`. Each pattern has:
|
|
170
|
+
- `id` — unique identifier (e.g. `no-aria-hidden-on-focusable`)
|
|
171
|
+
- `regex` — the pattern to search for
|
|
172
|
+
- `globs` — file extensions to scan (e.g. `**/*.tsx`)
|
|
173
|
+
- `title`, `severity`, `wcag`, `wcag_criterion`, `wcag_level`
|
|
174
|
+
- `fix_description` — how to fix matches
|
|
175
|
+
- `requires_manual_verification` — whether context analysis is needed
|
|
176
|
+
- `context_reject_regex` — regex that, if found near the match, means it's not a violation
|
|
177
|
+
- `context_window` — lines of context to check (default 5)
|
|
178
|
+
|
|
179
|
+
2. Resolves scan directories using framework source boundaries (`assets/remediation/source-boundaries.mjs`). For example, Next.js projects scope to `src/`, `app/`, `pages/`, `components/`.
|
|
180
|
+
|
|
181
|
+
3. Walks the file tree, skipping `node_modules`, `.git`, `dist`, build outputs, and minified files.
|
|
182
|
+
|
|
183
|
+
4. For each file matching the pattern's globs, runs the regex line by line. When a match is found:
|
|
184
|
+
- Captures 3 lines of context above and below
|
|
185
|
+
- Runs context validation if `requires_manual_verification` is set — checks if `context_reject_regex` appears nearby. If it does, the finding is marked `potential` instead of `confirmed`.
|
|
186
|
+
- Generates a stable ID (`PAT-xxxxxx`)
|
|
187
|
+
|
|
188
|
+
5. Output includes a summary with `total`, `confirmed`, and `potential` counts.
|
|
189
|
+
|
|
190
|
+
### Integration with the audit pipeline
|
|
191
|
+
|
|
192
|
+
When `runAudit` is called with `projectDir` and without `skipPatterns`:
|
|
193
|
+
|
|
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.
|
|
198
|
+
|
|
199
|
+
## Assets Reference
|
|
200
|
+
|
|
201
|
+
| Asset | Used by | Purpose |
|
|
202
|
+
| :--- | :--- | :--- |
|
|
203
|
+
| `remediation/intelligence.mjs` | analyzer | Per-rule fix descriptions, code, guardrails, framework notes |
|
|
204
|
+
| `remediation/axe-check-maps.mjs` | analyzer | Failure mode and relationship hint mappings from axe check IDs |
|
|
205
|
+
| `remediation/guardrails.mjs` | analyzer | Shared guardrail constraints for safe automated fixes |
|
|
206
|
+
| `remediation/source-boundaries.mjs` | analyzer, source-scanner | Framework-specific source directory scoping |
|
|
207
|
+
| `remediation/code-patterns.mjs` | source-scanner | Regex pattern definitions for source code scanning |
|
|
208
|
+
| `reporting/wcag-reference.mjs` | analyzer, reports | WCAG criterion map, persona mapping, impacted users, APG patterns, MDN links |
|
|
209
|
+
| `reporting/compliance-config.mjs` | analyzer, reports | Severity weights, score thresholds, impact mapping |
|
package/docs/outputs.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Output Artifacts
|
|
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) • [Testing](testing.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/testing.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Testing Strategy
|
|
2
2
|
|
|
3
|
-
**Navigation**: [Home](../README.md) • [Architecture](architecture.md) • [CLI Handbook](cli-handbook.md) • [Output Artifacts](outputs.md) • [Engine Manifest](engine-manifest.md) • [Testing](testing.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/package.json
CHANGED
|
@@ -17,6 +17,7 @@ import sourceBoundaries from "../../assets/remediation/source-boundaries.mjs";
|
|
|
17
17
|
import complianceConfig from "../../assets/reporting/compliance-config.mjs";
|
|
18
18
|
import manualChecks from "../../assets/reporting/manual-checks.mjs";
|
|
19
19
|
import wcagReference from "../../assets/reporting/wcag-reference.mjs";
|
|
20
|
+
import knowledgeData from "../../assets/knowledge/knowledge.mjs";
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Pre-loaded asset map. Each value is the parsed JSON object, ready to use.
|
|
@@ -46,6 +47,9 @@ export const ASSETS = {
|
|
|
46
47
|
wcagReference,
|
|
47
48
|
manualChecks,
|
|
48
49
|
},
|
|
50
|
+
knowledge: {
|
|
51
|
+
knowledge: knowledgeData,
|
|
52
|
+
},
|
|
49
53
|
};
|
|
50
54
|
|
|
51
55
|
/**
|
|
@@ -78,6 +82,9 @@ export const ASSET_PATHS = {
|
|
|
78
82
|
wcagReference: "reporting.wcagReference",
|
|
79
83
|
manualChecks: "reporting.manualChecks",
|
|
80
84
|
},
|
|
85
|
+
knowledge: {
|
|
86
|
+
knowledge: "knowledge.knowledge",
|
|
87
|
+
},
|
|
81
88
|
};
|
|
82
89
|
|
|
83
90
|
/**
|
|
@@ -832,7 +832,7 @@ function buildFindings(inputPayload, cliArgs) {
|
|
|
832
832
|
|
|
833
833
|
findings.push({
|
|
834
834
|
id: "",
|
|
835
|
-
rule_id: v.id,
|
|
835
|
+
rule_id: v.source === "cdp" ? v.id.replace(/^cdp-/, "") : v.id,
|
|
836
836
|
source_rule_id: v.source_rule_id || null,
|
|
837
837
|
source: v.source || "axe",
|
|
838
838
|
title: v.help,
|
package/src/index.d.mts
CHANGED
|
@@ -209,6 +209,111 @@ export interface SourcePatternOptions {
|
|
|
209
209
|
onlyPattern?: string;
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
// Knowledge APIs
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
|
|
216
|
+
export interface ScannerEngineHelp {
|
|
217
|
+
id: "axe" | "cdp" | "pa11y" | string;
|
|
218
|
+
label: string;
|
|
219
|
+
description: string;
|
|
220
|
+
defaultEnabled: boolean;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export interface ScannerOptionHelp {
|
|
224
|
+
id: string;
|
|
225
|
+
label: string;
|
|
226
|
+
description: string;
|
|
227
|
+
defaultValue: unknown;
|
|
228
|
+
type: string;
|
|
229
|
+
allowedValues?: unknown[];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export interface ScannerHelp {
|
|
233
|
+
locale: string;
|
|
234
|
+
version: string;
|
|
235
|
+
title: string;
|
|
236
|
+
engines: ScannerEngineHelp[];
|
|
237
|
+
options: ScannerOptionHelp[];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export interface PersonaReferenceItem {
|
|
241
|
+
id: string;
|
|
242
|
+
icon: string;
|
|
243
|
+
label: string;
|
|
244
|
+
description: string;
|
|
245
|
+
keywords: string[];
|
|
246
|
+
mappedRules: string[];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export interface PersonaReference {
|
|
250
|
+
locale: string;
|
|
251
|
+
version: string;
|
|
252
|
+
personas: PersonaReferenceItem[];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export interface UiTooltip {
|
|
256
|
+
title: string;
|
|
257
|
+
body: string;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export interface GlossaryEntry {
|
|
261
|
+
term: string;
|
|
262
|
+
definition: string;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export interface DocArticle {
|
|
266
|
+
id: string;
|
|
267
|
+
title: string;
|
|
268
|
+
icon?: string;
|
|
269
|
+
tag?: string;
|
|
270
|
+
tagVariant?: string;
|
|
271
|
+
summary: string;
|
|
272
|
+
body: string;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export interface DocGroup {
|
|
276
|
+
id: string;
|
|
277
|
+
label: string;
|
|
278
|
+
articles: DocArticle[];
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export interface DocSection {
|
|
282
|
+
id: string;
|
|
283
|
+
heading: string;
|
|
284
|
+
articles?: DocArticle[];
|
|
285
|
+
groups?: DocGroup[];
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export interface KnowledgeDocs {
|
|
289
|
+
sections: DocSection[];
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export interface UiHelp {
|
|
293
|
+
locale: string;
|
|
294
|
+
version: string;
|
|
295
|
+
tooltips: Record<string, UiTooltip>;
|
|
296
|
+
glossary: GlossaryEntry[];
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export interface EngineKnowledge {
|
|
300
|
+
locale: string;
|
|
301
|
+
version: string;
|
|
302
|
+
scanner: {
|
|
303
|
+
title: string;
|
|
304
|
+
engines: ScannerEngineHelp[];
|
|
305
|
+
options: ScannerOptionHelp[];
|
|
306
|
+
};
|
|
307
|
+
personas: PersonaReferenceItem[];
|
|
308
|
+
tooltips: Record<string, UiTooltip>;
|
|
309
|
+
glossary: GlossaryEntry[];
|
|
310
|
+
docs: KnowledgeDocs;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export interface KnowledgeOptions {
|
|
314
|
+
locale?: string;
|
|
315
|
+
}
|
|
316
|
+
|
|
212
317
|
// ---------------------------------------------------------------------------
|
|
213
318
|
// Engine selection
|
|
214
319
|
// ---------------------------------------------------------------------------
|
|
@@ -293,3 +398,11 @@ export function getSourcePatterns(
|
|
|
293
398
|
projectDir: string,
|
|
294
399
|
options?: SourcePatternOptions
|
|
295
400
|
): Promise<SourcePatternResult>;
|
|
401
|
+
|
|
402
|
+
export function getScannerHelp(options?: KnowledgeOptions): ScannerHelp;
|
|
403
|
+
|
|
404
|
+
export function getPersonaReference(options?: KnowledgeOptions): PersonaReference;
|
|
405
|
+
|
|
406
|
+
export function getUiHelp(options?: KnowledgeOptions): UiHelp;
|
|
407
|
+
|
|
408
|
+
export function getKnowledge(options?: KnowledgeOptions): EngineKnowledge;
|
package/src/index.mjs
CHANGED
|
@@ -14,6 +14,7 @@ let _intelligence = null;
|
|
|
14
14
|
let _pa11yConfig = null;
|
|
15
15
|
let _complianceConfig = null;
|
|
16
16
|
let _wcagReference = null;
|
|
17
|
+
let _knowledge = null;
|
|
17
18
|
|
|
18
19
|
function getIntelligence() {
|
|
19
20
|
if (!_intelligence) _intelligence = loadAssetJson(ASSET_PATHS.remediation.intelligence, "intelligence.json");
|
|
@@ -41,6 +42,22 @@ function getWcagReference() {
|
|
|
41
42
|
return _wcagReference;
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
function getKnowledgeData() {
|
|
46
|
+
if (!_knowledge) _knowledge = loadAssetJson(ASSET_PATHS.knowledge.knowledge, "knowledge.json");
|
|
47
|
+
return _knowledge;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function clone(value) {
|
|
51
|
+
return JSON.parse(JSON.stringify(value));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function resolveKnowledgeLocale(locale = "en") {
|
|
55
|
+
const payload = getKnowledgeData();
|
|
56
|
+
const locales = payload.locales || {};
|
|
57
|
+
if (locale && locales[locale]) return locale;
|
|
58
|
+
return "en";
|
|
59
|
+
}
|
|
60
|
+
|
|
44
61
|
// ---------------------------------------------------------------------------
|
|
45
62
|
// Pa11y rule canonicalization (internal)
|
|
46
63
|
// ---------------------------------------------------------------------------
|
|
@@ -421,6 +438,118 @@ export function getOverview(findings, payload = null) {
|
|
|
421
438
|
};
|
|
422
439
|
}
|
|
423
440
|
|
|
441
|
+
// ---------------------------------------------------------------------------
|
|
442
|
+
// Knowledge APIs
|
|
443
|
+
// ---------------------------------------------------------------------------
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Returns scanner-facing help metadata including engine descriptions,
|
|
447
|
+
* advanced option hints, and defaults.
|
|
448
|
+
*
|
|
449
|
+
* @param {{ locale?: string }} [options={}]
|
|
450
|
+
* @returns {{ locale: string, version: string, title: string, engines: object[], options: object[] }}
|
|
451
|
+
*/
|
|
452
|
+
export function getScannerHelp(options = {}) {
|
|
453
|
+
const locale = resolveKnowledgeLocale(options.locale || "en");
|
|
454
|
+
const payload = getKnowledgeData();
|
|
455
|
+
const scanner = payload.locales[locale]?.scanner || { title: "Scanner Help", engines: [], options: [] };
|
|
456
|
+
|
|
457
|
+
return {
|
|
458
|
+
locale,
|
|
459
|
+
version: payload.version || "1.0.0",
|
|
460
|
+
title: scanner.title,
|
|
461
|
+
engines: clone(scanner.engines || []),
|
|
462
|
+
options: clone(scanner.options || []),
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Returns persona explanations with labels, descriptions, and the WCAG rule/
|
|
468
|
+
* keyword mappings used for impact grouping.
|
|
469
|
+
*
|
|
470
|
+
* @param {{ locale?: string }} [options={}]
|
|
471
|
+
* @returns {{ locale: string, version: string, personas: object[] }}
|
|
472
|
+
*/
|
|
473
|
+
export function getPersonaReference(options = {}) {
|
|
474
|
+
const locale = resolveKnowledgeLocale(options.locale || "en");
|
|
475
|
+
const payload = getKnowledgeData();
|
|
476
|
+
const wcagRef = getWcagReference();
|
|
477
|
+
|
|
478
|
+
const copyMap = payload.locales[locale]?.personas || {};
|
|
479
|
+
const personaConfig = wcagRef.personaConfig || {};
|
|
480
|
+
const personaMapping = wcagRef.personaMapping || {};
|
|
481
|
+
|
|
482
|
+
const personas = Object.keys(copyMap).map((id) => {
|
|
483
|
+
const copy = copyMap[id] || {};
|
|
484
|
+
const config = personaConfig[id] || {};
|
|
485
|
+
const mapping = personaMapping[id] || {};
|
|
486
|
+
|
|
487
|
+
return {
|
|
488
|
+
id,
|
|
489
|
+
icon: id,
|
|
490
|
+
label: copy.label || config.label || id,
|
|
491
|
+
description: copy.description || "",
|
|
492
|
+
keywords: Array.isArray(mapping.keywords) ? clone(mapping.keywords) : [],
|
|
493
|
+
mappedRules: Array.isArray(mapping.rules) ? clone(mapping.rules) : [],
|
|
494
|
+
};
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
return {
|
|
498
|
+
locale,
|
|
499
|
+
version: payload.version || "1.0.0",
|
|
500
|
+
personas,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Returns UI tooltip copy and glossary terms for scanner cards and labels.
|
|
506
|
+
*
|
|
507
|
+
* @param {{ locale?: string }} [options={}]
|
|
508
|
+
* @returns {{ locale: string, version: string, tooltips: Record<string, object>, glossary: object[] }}
|
|
509
|
+
*/
|
|
510
|
+
export function getUiHelp(options = {}) {
|
|
511
|
+
const locale = resolveKnowledgeLocale(options.locale || "en");
|
|
512
|
+
const payload = getKnowledgeData();
|
|
513
|
+
const localePayload = payload.locales[locale] || {};
|
|
514
|
+
|
|
515
|
+
return {
|
|
516
|
+
locale,
|
|
517
|
+
version: payload.version || "1.0.0",
|
|
518
|
+
tooltips: clone(localePayload.tooltips || {}),
|
|
519
|
+
glossary: clone(localePayload.glossary || []),
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Returns the full documentation package that frontends or agents can render
|
|
525
|
+
* as help content next to findings and scores.
|
|
526
|
+
*
|
|
527
|
+
* @param {{ locale?: string }} [options={}]
|
|
528
|
+
* @returns {{ locale: string, version: string, scanner: object, personas: object[], tooltips: Record<string, object>, glossary: object[] }}
|
|
529
|
+
*/
|
|
530
|
+
export function getKnowledge(options = {}) {
|
|
531
|
+
const scanner = getScannerHelp(options);
|
|
532
|
+
const personas = getPersonaReference(options);
|
|
533
|
+
const ui = getUiHelp(options);
|
|
534
|
+
|
|
535
|
+
const payload = getKnowledgeData();
|
|
536
|
+
const docs = clone(payload.locales[scanner.locale]?.docs ?? { sections: [] });
|
|
537
|
+
|
|
538
|
+
return {
|
|
539
|
+
locale: scanner.locale,
|
|
540
|
+
version: scanner.version,
|
|
541
|
+
scanner: {
|
|
542
|
+
title: scanner.title,
|
|
543
|
+
engines: scanner.engines,
|
|
544
|
+
options: scanner.options,
|
|
545
|
+
},
|
|
546
|
+
personas: personas.personas,
|
|
547
|
+
tooltips: ui.tooltips,
|
|
548
|
+
glossary: ui.glossary,
|
|
549
|
+
docs,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
424
553
|
// ---------------------------------------------------------------------------
|
|
425
554
|
// Full audit pipeline
|
|
426
555
|
// ---------------------------------------------------------------------------
|
package/src/reports/html.mjs
CHANGED
|
@@ -285,7 +285,7 @@ function buildHtml(args, findings, metadata = {}) {
|
|
|
285
285
|
</div>
|
|
286
286
|
</div>
|
|
287
287
|
<h3 class="text-xl font-bold text-slate-900 mb-1">${label} Compliance</h3>
|
|
288
|
-
<p class="text-xs font-medium text-slate-500 max-w-
|
|
288
|
+
<p class="text-xs font-medium text-slate-500 max-w-50 leading-snug">Automated testing coverage based on ${escapeHtml(args.target)} technical checks.</p>
|
|
289
289
|
</div>
|
|
290
290
|
|
|
291
291
|
<div class="md:col-span-7 grid grid-cols-2 gap-4">
|
|
@@ -334,13 +334,13 @@ function buildHtml(args, findings, metadata = {}) {
|
|
|
334
334
|
<div class="group">
|
|
335
335
|
<div class="flex items-center justify-between mb-2">
|
|
336
336
|
<div class="flex items-center gap-3">
|
|
337
|
-
<div class="w-8 h-8 rounded-lg bg-slate-50 flex items-center justify-center text-slate-600 group-hover:bg-
|
|
337
|
+
<div class="w-8 h-8 rounded-lg bg-slate-50 flex items-center justify-center text-slate-600 group-hover:bg-(--primary-light) group-hover:text-(--primary) transition-colors">${p.icon}</div>
|
|
338
338
|
<span class="text-sm font-bold text-slate-700">${p.label}</span>
|
|
339
339
|
</div>
|
|
340
340
|
<span class="text-xs font-black text-slate-900">${p.count} issues</span>
|
|
341
341
|
</div>
|
|
342
342
|
<div class="w-full bg-slate-100 rounded-full h-1.5 overflow-hidden">
|
|
343
|
-
<div class="bg-
|
|
343
|
+
<div class="bg-(--primary) h-full rounded-full transition-all duration-500" style="width: ${findings.length > 0 ? (p.count / findings.length) * 100 : 0}%"></div>
|
|
344
344
|
</div>
|
|
345
345
|
</div>`,
|
|
346
346
|
)
|
|
@@ -355,14 +355,14 @@ function buildHtml(args, findings, metadata = {}) {
|
|
|
355
355
|
? `
|
|
356
356
|
<div class="premium-card rounded-2xl bg-slate-900 p-6 mb-12 relative overflow-hidden">
|
|
357
357
|
<div class="absolute -right-4 -bottom-4 opacity-10">
|
|
358
|
-
<svg class="w-32 h-32 text-
|
|
358
|
+
<svg class="w-32 h-32 text-(--primary)" fill="currentColor" viewBox="0 0 24 24"><path d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
|
|
359
359
|
</div>
|
|
360
360
|
<div class="relative z-10">
|
|
361
361
|
<div class="flex items-center gap-3 mb-4">
|
|
362
|
-
<span class="px-2 py-0.5 rounded bg-
|
|
362
|
+
<span class="px-2 py-0.5 rounded bg-(--primary) text-[10px] font-black text-white uppercase tracking-tighter">AI Analysis</span>
|
|
363
363
|
<h3 class="text-xl font-bold text-white">Recommended Quick Wins</h3>
|
|
364
364
|
</div>
|
|
365
|
-
<p class="text-xs text-
|
|
365
|
+
<p class="text-xs text-(--primary)/80 mb-6 -mt-2 leading-relaxed italic">High-priority issues with ready-to-use code fixes for immediate remediation.</p>
|
|
366
366
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
367
367
|
${quickWins
|
|
368
368
|
.map(
|
|
@@ -393,7 +393,7 @@ function buildHtml(args, findings, metadata = {}) {
|
|
|
393
393
|
<div class="flex items-center gap-4">
|
|
394
394
|
<h3 class="text-xl font-extrabold text-slate-900 tracking-tight">Findings <span class="text-slate-600 font-bold ml-1">${findings.length}</span></h3>
|
|
395
395
|
<div class="flex gap-1 bg-white border border-slate-200 rounded-xl p-1 shadow-sm">
|
|
396
|
-
<button onclick="setView('severity')" id="view-severity" class="view-btn px-4 py-1.5 rounded-lg text-xs font-bold uppercase tracking-widest bg-
|
|
396
|
+
<button onclick="setView('severity')" id="view-severity" class="view-btn px-4 py-1.5 rounded-lg text-xs font-bold uppercase tracking-widest bg-(--primary-light) text-(--primary) transition-all">By Severity</button>
|
|
397
397
|
${
|
|
398
398
|
Object.keys(_pageCounts).length > 1
|
|
399
399
|
? `<button onclick="setView('page')" id="view-page" class="view-btn px-4 py-1.5 rounded-lg text-xs font-bold uppercase tracking-widest text-slate-500 hover:text-slate-700 transition-all">By Page</button>`
|
|
@@ -407,7 +407,7 @@ function buildHtml(args, findings, metadata = {}) {
|
|
|
407
407
|
<!-- Row 2: Search & Filter Select -->
|
|
408
408
|
<div class="flex items-center gap-4 w-full">
|
|
409
409
|
<div class="relative flex-1">
|
|
410
|
-
<input type="text" id="search-input" oninput="handleSearch(this.value)" placeholder="Search violations..." class="w-full pl-11 pr-4 py-3 bg-white border border-slate-200 rounded-2xl text-sm font-medium focus:outline-none focus:ring-4 focus:ring-
|
|
410
|
+
<input type="text" id="search-input" oninput="handleSearch(this.value)" placeholder="Search violations..." class="w-full pl-11 pr-4 py-3 bg-white border border-slate-200 rounded-2xl text-sm font-medium focus:outline-none focus:ring-4 focus:ring-(--primary)/10 focus:border-(--primary) transition-all shadow-sm">
|
|
411
411
|
<svg class="absolute left-4 top-3.5 w-5 h-5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
412
412
|
</div>
|
|
413
413
|
|
|
@@ -444,7 +444,7 @@ function buildHtml(args, findings, metadata = {}) {
|
|
|
444
444
|
</div>
|
|
445
445
|
|
|
446
446
|
<footer class="mt-10 py-6 border-t border-slate-200 text-center">
|
|
447
|
-
<p class="text-slate-600 text-sm font-medium">Generated by <a href="https://github.com/diegovelasquezweb/a11y" target="_blank" class="text-slate-700 hover:text-
|
|
447
|
+
<p class="text-slate-600 text-sm font-medium">Generated by <a href="https://github.com/diegovelasquezweb/a11y" target="_blank" class="text-slate-700 hover:text-(--primary) font-semibold transition-colors">a11y</a> • <span class="text-slate-700">${escapeHtml(args.target)}</span></p>
|
|
448
448
|
</footer>
|
|
449
449
|
</main>
|
|
450
450
|
|