@diegovelasquezweb/a11y-engine 0.8.4 → 0.9.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/README.md +16 -19
- package/docs/api-reference.md +13 -80
- package/package.json +1 -1
- package/src/ai/claude.mjs +1 -1
- package/src/ai/enrich.mjs +1 -1
- package/src/index.d.mts +0 -12
- package/src/index.mjs +7 -38
- package/src/pipeline/dom-scanner.mjs +2 -7
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# a11y Engine
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@diegovelasquezweb/a11y-engine)
|
|
4
4
|
|
|
@@ -15,6 +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
19
|
|
|
19
20
|
## Installation
|
|
20
21
|
|
|
@@ -40,12 +41,6 @@ import {
|
|
|
40
41
|
getChecklist,
|
|
41
42
|
getRemediationGuide,
|
|
42
43
|
getSourcePatterns,
|
|
43
|
-
getScannerHelp,
|
|
44
|
-
getPersonaReference,
|
|
45
|
-
getUiHelp,
|
|
46
|
-
getConformanceLevels,
|
|
47
|
-
getWcagPrinciples,
|
|
48
|
-
getSeverityLevels,
|
|
49
44
|
getKnowledge,
|
|
50
45
|
} from "@diegovelasquezweb/a11y-engine";
|
|
51
46
|
```
|
|
@@ -121,19 +116,21 @@ These functions render final artifacts from scan payload data.
|
|
|
121
116
|
|
|
122
117
|
### Knowledge API
|
|
123
118
|
|
|
124
|
-
|
|
119
|
+
Returns all accessibility knowledge in a single call: scanner help, persona profiles, concepts, glossary, documentation, conformance levels, WCAG principles, and severity definitions.
|
|
125
120
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
121
|
+
```ts
|
|
122
|
+
const knowledge = getKnowledge({ locale: "en" });
|
|
123
|
+
// knowledge.scanner → engine and option descriptions
|
|
124
|
+
// knowledge.personas → persona labels and descriptions
|
|
125
|
+
// knowledge.concepts → concept definitions
|
|
126
|
+
// knowledge.glossary → accessibility glossary
|
|
127
|
+
// knowledge.docs → documentation articles
|
|
128
|
+
// knowledge.conformanceLevels → A/AA/AAA with axe tag mappings
|
|
129
|
+
// knowledge.wcagPrinciples → the four WCAG principles
|
|
130
|
+
// knowledge.severityLevels → Critical/Serious/Moderate/Minor definitions
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
See [API Reference](docs/api-reference.md) for the full `EngineKnowledge` shape.
|
|
137
134
|
|
|
138
135
|
## CLI
|
|
139
136
|
|
package/docs/api-reference.md
CHANGED
|
@@ -353,95 +353,28 @@ Returns: `Promise<SourcePatternResult>`
|
|
|
353
353
|
|
|
354
354
|
## Knowledge API
|
|
355
355
|
|
|
356
|
-
These functions expose engine-owned content for UIs and agents to render. All accept an optional `{ locale?: string }` option (default: `"en"`).
|
|
357
|
-
|
|
358
356
|
### `getKnowledge(options?)`
|
|
359
357
|
|
|
360
|
-
Returns
|
|
358
|
+
Returns all accessibility knowledge in a single call. Accepts an optional `{ locale?: string }` option (default: `"en"`).
|
|
361
359
|
|
|
362
360
|
```ts
|
|
363
361
|
import { getKnowledge } from "@diegovelasquezweb/a11y-engine";
|
|
364
362
|
|
|
365
363
|
const knowledge = getKnowledge({ locale: "en" });
|
|
366
|
-
|
|
367
|
-
// knowledge.scanner → scan options help and engine descriptions
|
|
368
|
-
// knowledge.personas → persona labels, icons, descriptions
|
|
369
|
-
// knowledge.concepts → UI concept definitions
|
|
370
|
-
// knowledge.glossary → accessibility glossary
|
|
371
|
-
// knowledge.conformanceLevels → WCAG A/AA/AAA definitions with axe tags
|
|
372
|
-
// knowledge.wcagPrinciples → the 4 WCAG principles
|
|
373
|
-
// knowledge.severityLevels → Critical/Serious/Moderate/Minor definitions
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
Returns: `EngineKnowledge`
|
|
377
|
-
|
|
378
|
-
---
|
|
379
|
-
|
|
380
|
-
### `getScannerHelp(options?)`
|
|
381
|
-
|
|
382
|
-
Returns scan option descriptions, allowed values, and engine metadata — used to render Advanced Settings UI.
|
|
383
|
-
|
|
384
|
-
```ts
|
|
385
|
-
const help = getScannerHelp();
|
|
386
|
-
// help.engines → [{ id: "axe", label: "axe-core", description: "..." }, ...]
|
|
387
|
-
// help.options → [{ id: "maxRoutes", label: "Max Routes", type: "number", ... }, ...]
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
---
|
|
391
|
-
|
|
392
|
-
### `getPersonaReference(options?)`
|
|
393
|
-
|
|
394
|
-
Returns persona labels, descriptions, and disability group definitions.
|
|
395
|
-
|
|
396
|
-
```ts
|
|
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.
|
|
406
|
-
|
|
407
|
-
```ts
|
|
408
|
-
const ui = getUiHelp();
|
|
409
|
-
// ui.concepts → { wcag: "...", aria: "...", ... }
|
|
410
|
-
// ui.glossary → [{ term: "ARIA", definition: "..." }, ...]
|
|
411
|
-
```
|
|
412
|
-
|
|
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
|
-
```
|
|
423
|
-
|
|
424
|
-
---
|
|
425
|
-
|
|
426
|
-
### `getWcagPrinciples(options?)`
|
|
427
|
-
|
|
428
|
-
Returns the four WCAG principles (Perceivable, Operable, Understandable, Robust) with criterion prefix patterns.
|
|
429
|
-
|
|
430
|
-
```ts
|
|
431
|
-
const { wcagPrinciples } = getWcagPrinciples();
|
|
432
|
-
// wcagPrinciples[0] → { id: "perceivable", label: "Perceivable", prefix: "1.", description: "..." }
|
|
433
364
|
```
|
|
434
365
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
366
|
+
**Returns:** `EngineKnowledge`
|
|
367
|
+
|
|
368
|
+
| Field | Type | Description |
|
|
369
|
+
| :--- | :--- | :--- |
|
|
370
|
+
| `scanner` | `{ title, engines, options }` | Scan option descriptions, allowed values, and engine metadata |
|
|
371
|
+
| `personas` | `PersonaReferenceItem[]` | Persona labels, icons, descriptions, and mapped rules |
|
|
372
|
+
| `concepts` | `Record<string, ConceptEntry>` | Concept definitions with title, body, and context |
|
|
373
|
+
| `glossary` | `GlossaryEntry[]` | Accessibility term definitions |
|
|
374
|
+
| `docs` | `KnowledgeDocs` | Documentation articles organized by section and group |
|
|
375
|
+
| `conformanceLevels` | `ConformanceLevel[]` | WCAG A/AA/AAA definitions with axe-core tag mappings |
|
|
376
|
+
| `wcagPrinciples` | `WcagPrinciple[]` | The four WCAG principles with criterion prefix patterns |
|
|
377
|
+
| `severityLevels` | `SeverityLevel[]` | Critical/Serious/Moderate/Minor definitions with ordering |
|
|
445
378
|
|
|
446
379
|
---
|
|
447
380
|
|
package/package.json
CHANGED
package/src/ai/claude.mjs
CHANGED
|
@@ -236,7 +236,7 @@ async function fetchSourceFilesForFindings(findings, repoUrl, githubToken) {
|
|
|
236
236
|
try {
|
|
237
237
|
const content = await fetchRepoFile(repoUrl, filePath, githubToken);
|
|
238
238
|
if (content) sourceFiles[filePath] = content;
|
|
239
|
-
} catch {
|
|
239
|
+
} catch { }
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
242
|
|
package/src/ai/enrich.mjs
CHANGED
|
@@ -74,6 +74,6 @@ async function main() {
|
|
|
74
74
|
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
75
75
|
main().catch((err) => {
|
|
76
76
|
log.warn(`AI enrichment failed (non-fatal): ${err.message}`);
|
|
77
|
-
process.exit(0);
|
|
77
|
+
process.exit(0);
|
|
78
78
|
});
|
|
79
79
|
}
|
package/src/index.d.mts
CHANGED
|
@@ -449,18 +449,6 @@ export function getSourcePatterns(
|
|
|
449
449
|
options?: SourcePatternOptions
|
|
450
450
|
): Promise<SourcePatternResult>;
|
|
451
451
|
|
|
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
452
|
export function getKnowledge(options?: KnowledgeOptions): EngineKnowledge;
|
|
465
453
|
|
|
466
454
|
export const DEFAULT_AI_SYSTEM_PROMPT: string;
|
package/src/index.mjs
CHANGED
|
@@ -7,9 +7,7 @@
|
|
|
7
7
|
import { ASSET_PATHS, loadAssetJson } from "./core/asset-loader.mjs";
|
|
8
8
|
export { DEFAULT_AI_SYSTEM_PROMPT } from "./ai/claude.mjs";
|
|
9
9
|
|
|
10
|
-
// ---------------------------------------------------------------------------
|
|
11
10
|
// Lazy-loaded asset cache
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
11
|
|
|
14
12
|
let _intelligence = null;
|
|
15
13
|
let _pa11yConfig = null;
|
|
@@ -59,9 +57,7 @@ function resolveKnowledgeLocale(locale = "en") {
|
|
|
59
57
|
return "en";
|
|
60
58
|
}
|
|
61
59
|
|
|
62
|
-
// ---------------------------------------------------------------------------
|
|
63
60
|
// Pa11y rule canonicalization (internal)
|
|
64
|
-
// ---------------------------------------------------------------------------
|
|
65
61
|
|
|
66
62
|
function normalizePa11yToken(value) {
|
|
67
63
|
return value
|
|
@@ -98,9 +94,7 @@ function mapPa11yRuleToCanonical(ruleId, sourceRuleId = null, checkData = null)
|
|
|
98
94
|
return ruleId;
|
|
99
95
|
}
|
|
100
96
|
|
|
101
|
-
// ---------------------------------------------------------------------------
|
|
102
97
|
// Raw finding normalization (internal)
|
|
103
|
-
// ---------------------------------------------------------------------------
|
|
104
98
|
|
|
105
99
|
const SEVERITY_ORDER = { Critical: 1, Serious: 2, Moderate: 3, Minor: 4 };
|
|
106
100
|
|
|
@@ -170,9 +164,7 @@ function normalizeSingleFinding(item, index, screenshotUrlBuilder) {
|
|
|
170
164
|
};
|
|
171
165
|
}
|
|
172
166
|
|
|
173
|
-
// ---------------------------------------------------------------------------
|
|
174
167
|
// Finding enrichment
|
|
175
|
-
// ---------------------------------------------------------------------------
|
|
176
168
|
|
|
177
169
|
/**
|
|
178
170
|
* Normalizes and enriches raw findings with intelligence data.
|
|
@@ -189,19 +181,16 @@ export function getFindings(input, options = {}) {
|
|
|
189
181
|
const { screenshotUrlBuilder = null } = options;
|
|
190
182
|
const rules = getIntelligence().rules || {};
|
|
191
183
|
|
|
192
|
-
// If AI enrichment ran, return those findings directly (already normalized + enriched)
|
|
193
184
|
if (input?.ai_enriched_findings?.length > 0 && !screenshotUrlBuilder) {
|
|
194
185
|
return input.ai_enriched_findings;
|
|
195
186
|
}
|
|
196
187
|
|
|
197
188
|
const rawFindings = input?.findings || [];
|
|
198
189
|
|
|
199
|
-
// Normalize raw findings
|
|
200
190
|
const normalized = rawFindings.map((item, index) =>
|
|
201
191
|
normalizeSingleFinding(item, index, screenshotUrlBuilder)
|
|
202
192
|
);
|
|
203
193
|
|
|
204
|
-
// Enrich with intelligence and output camelCase-only findings
|
|
205
194
|
const enriched = normalized.map((finding) => {
|
|
206
195
|
const canonical = mapPa11yRuleToCanonical(
|
|
207
196
|
finding.rule_id,
|
|
@@ -209,7 +198,6 @@ export function getFindings(input, options = {}) {
|
|
|
209
198
|
finding.check_data,
|
|
210
199
|
);
|
|
211
200
|
|
|
212
|
-
// Build camelCase-only enriched finding
|
|
213
201
|
const enrichedFinding = {
|
|
214
202
|
id: finding.id,
|
|
215
203
|
ruleId: canonical,
|
|
@@ -281,7 +269,6 @@ export function getFindings(input, options = {}) {
|
|
|
281
269
|
return enrichedFinding;
|
|
282
270
|
});
|
|
283
271
|
|
|
284
|
-
// Sort by severity then by ID
|
|
285
272
|
enriched.sort((a, b) => {
|
|
286
273
|
const sa = SEVERITY_ORDER[a.severity] ?? 99;
|
|
287
274
|
const sb = SEVERITY_ORDER[b.severity] ?? 99;
|
|
@@ -292,9 +279,7 @@ export function getFindings(input, options = {}) {
|
|
|
292
279
|
return enriched;
|
|
293
280
|
}
|
|
294
281
|
|
|
295
|
-
// ---------------------------------------------------------------------------
|
|
296
282
|
// Score computation (internal)
|
|
297
|
-
// ---------------------------------------------------------------------------
|
|
298
283
|
|
|
299
284
|
function getComplianceScore(totals) {
|
|
300
285
|
const config = getComplianceConfig();
|
|
@@ -325,9 +310,7 @@ function getComplianceScore(totals) {
|
|
|
325
310
|
return { score, label, wcagStatus };
|
|
326
311
|
}
|
|
327
312
|
|
|
328
|
-
// ---------------------------------------------------------------------------
|
|
329
313
|
// Persona grouping (internal)
|
|
330
|
-
// ---------------------------------------------------------------------------
|
|
331
314
|
|
|
332
315
|
function getPersonaGroups(findings) {
|
|
333
316
|
const ref = getWcagReference();
|
|
@@ -384,9 +367,7 @@ function getPersonaGroups(findings) {
|
|
|
384
367
|
return groups;
|
|
385
368
|
}
|
|
386
369
|
|
|
387
|
-
// ---------------------------------------------------------------------------
|
|
388
370
|
// Audit summary
|
|
389
|
-
// ---------------------------------------------------------------------------
|
|
390
371
|
|
|
391
372
|
/**
|
|
392
373
|
* Computes a complete audit summary from enriched findings: severity totals,
|
|
@@ -444,9 +425,7 @@ export function getOverview(findings, payload = null) {
|
|
|
444
425
|
};
|
|
445
426
|
}
|
|
446
427
|
|
|
447
|
-
// ---------------------------------------------------------------------------
|
|
448
428
|
// Knowledge APIs
|
|
449
|
-
// ---------------------------------------------------------------------------
|
|
450
429
|
|
|
451
430
|
/**
|
|
452
431
|
* Returns scanner-facing help metadata including engine descriptions,
|
|
@@ -455,7 +434,7 @@ export function getOverview(findings, payload = null) {
|
|
|
455
434
|
* @param {{ locale?: string }} [options={}]
|
|
456
435
|
* @returns {{ locale: string, version: string, title: string, engines: object[], options: object[] }}
|
|
457
436
|
*/
|
|
458
|
-
|
|
437
|
+
function getScannerHelp(options = {}) {
|
|
459
438
|
const locale = resolveKnowledgeLocale(options.locale || "en");
|
|
460
439
|
const payload = getKnowledgeData();
|
|
461
440
|
const scanner = payload.locales[locale]?.scanner || { title: "Scanner Help", engines: [], options: [] };
|
|
@@ -476,7 +455,7 @@ export function getScannerHelp(options = {}) {
|
|
|
476
455
|
* @param {{ locale?: string }} [options={}]
|
|
477
456
|
* @returns {{ locale: string, version: string, personas: object[] }}
|
|
478
457
|
*/
|
|
479
|
-
|
|
458
|
+
function getPersonaReference(options = {}) {
|
|
480
459
|
const locale = resolveKnowledgeLocale(options.locale || "en");
|
|
481
460
|
const payload = getKnowledgeData();
|
|
482
461
|
const wcagRef = getWcagReference();
|
|
@@ -513,7 +492,7 @@ export function getPersonaReference(options = {}) {
|
|
|
513
492
|
* @param {{ locale?: string }} [options={}]
|
|
514
493
|
* @returns {{ locale: string, version: string, tooltips: Record<string, object>, glossary: object[] }}
|
|
515
494
|
*/
|
|
516
|
-
|
|
495
|
+
function getConceptsAndGlossary(options = {}) {
|
|
517
496
|
const locale = resolveKnowledgeLocale(options.locale || "en");
|
|
518
497
|
const payload = getKnowledgeData();
|
|
519
498
|
const localePayload = payload.locales[locale] || {};
|
|
@@ -539,7 +518,7 @@ export function getUiHelp(options = {}) {
|
|
|
539
518
|
* @param {{ locale?: string }} [options={}]
|
|
540
519
|
* @returns {{ locale: string, version: string, conformanceLevels: object[] }}
|
|
541
520
|
*/
|
|
542
|
-
|
|
521
|
+
function getConformanceLevels(options = {}) {
|
|
543
522
|
const locale = resolveKnowledgeLocale(options.locale || "en");
|
|
544
523
|
const payload = getKnowledgeData();
|
|
545
524
|
const levels = payload.locales[locale]?.conformanceLevels || [];
|
|
@@ -556,7 +535,7 @@ export function getConformanceLevels(options = {}) {
|
|
|
556
535
|
* @param {{ locale?: string }} [options={}]
|
|
557
536
|
* @returns {{ locale: string, version: string, wcagPrinciples: object[] }}
|
|
558
537
|
*/
|
|
559
|
-
|
|
538
|
+
function getWcagPrinciples(options = {}) {
|
|
560
539
|
const locale = resolveKnowledgeLocale(options.locale || "en");
|
|
561
540
|
const payload = getKnowledgeData();
|
|
562
541
|
const principles = payload.locales[locale]?.wcagPrinciples || [];
|
|
@@ -573,7 +552,7 @@ export function getWcagPrinciples(options = {}) {
|
|
|
573
552
|
* @param {{ locale?: string }} [options={}]
|
|
574
553
|
* @returns {{ locale: string, version: string, severityLevels: object[] }}
|
|
575
554
|
*/
|
|
576
|
-
|
|
555
|
+
function getSeverityLevels(options = {}) {
|
|
577
556
|
const locale = resolveKnowledgeLocale(options.locale || "en");
|
|
578
557
|
const payload = getKnowledgeData();
|
|
579
558
|
const levels = payload.locales[locale]?.severityLevels || [];
|
|
@@ -594,7 +573,7 @@ export const VIEWPORT_PRESETS = [
|
|
|
594
573
|
export function getKnowledge(options = {}) {
|
|
595
574
|
const scanner = getScannerHelp(options);
|
|
596
575
|
const personas = getPersonaReference(options);
|
|
597
|
-
const ui =
|
|
576
|
+
const ui = getConceptsAndGlossary(options);
|
|
598
577
|
const conformance = getConformanceLevels(options);
|
|
599
578
|
const principles = getWcagPrinciples(options);
|
|
600
579
|
const severity = getSeverityLevels(options);
|
|
@@ -619,9 +598,7 @@ export function getKnowledge(options = {}) {
|
|
|
619
598
|
};
|
|
620
599
|
}
|
|
621
600
|
|
|
622
|
-
// ---------------------------------------------------------------------------
|
|
623
601
|
// Full audit pipeline
|
|
624
|
-
// ---------------------------------------------------------------------------
|
|
625
602
|
|
|
626
603
|
/**
|
|
627
604
|
* Runs a complete accessibility audit: crawl + scan (axe + CDP + pa11y) + analyze.
|
|
@@ -826,9 +803,7 @@ export async function runAudit(options) {
|
|
|
826
803
|
return findingsPayload;
|
|
827
804
|
}
|
|
828
805
|
|
|
829
|
-
// ---------------------------------------------------------------------------
|
|
830
806
|
// Report generation
|
|
831
|
-
// ---------------------------------------------------------------------------
|
|
832
807
|
|
|
833
808
|
import {
|
|
834
809
|
normalizeFindings as normalizeForReports,
|
|
@@ -1011,9 +986,7 @@ export async function getChecklist(options = {}) {
|
|
|
1011
986
|
};
|
|
1012
987
|
}
|
|
1013
988
|
|
|
1014
|
-
// ---------------------------------------------------------------------------
|
|
1015
989
|
// HTML Report
|
|
1016
|
-
// ---------------------------------------------------------------------------
|
|
1017
990
|
|
|
1018
991
|
/**
|
|
1019
992
|
* Generates an interactive HTML audit dashboard from raw scan findings.
|
|
@@ -1143,9 +1116,7 @@ export async function getHTMLReport(payload, options = {}) {
|
|
|
1143
1116
|
};
|
|
1144
1117
|
}
|
|
1145
1118
|
|
|
1146
|
-
// ---------------------------------------------------------------------------
|
|
1147
1119
|
// Remediation Guide (Markdown)
|
|
1148
|
-
// ---------------------------------------------------------------------------
|
|
1149
1120
|
|
|
1150
1121
|
/**
|
|
1151
1122
|
* Generates a Markdown remediation guide from raw scan findings.
|
|
@@ -1171,9 +1142,7 @@ export async function getRemediationGuide(payload, options = {}) {
|
|
|
1171
1142
|
};
|
|
1172
1143
|
}
|
|
1173
1144
|
|
|
1174
|
-
// ---------------------------------------------------------------------------
|
|
1175
1145
|
// Source Pattern Scanner
|
|
1176
|
-
// ---------------------------------------------------------------------------
|
|
1177
1146
|
|
|
1178
1147
|
/**
|
|
1179
1148
|
* Scans a project's source code for accessibility patterns not detectable by axe-core.
|
|
@@ -935,17 +935,12 @@ function mergeViolations(axeViolations, cdpViolations, pa11yViolations) {
|
|
|
935
935
|
}
|
|
936
936
|
}
|
|
937
937
|
|
|
938
|
-
// Step 3: pa11y findings —
|
|
938
|
+
// Step 3: pa11y findings — only skip if same rule + same target already exists
|
|
939
939
|
for (const v of pa11yViolations) {
|
|
940
940
|
const target = v.nodes?.[0]?.target?.[0] || "";
|
|
941
941
|
const key = `${v.id}::${target}`;
|
|
942
942
|
|
|
943
|
-
|
|
944
|
-
const isAxeEquivDuplicate = v.id && seenRuleTargets.has(v.id) && target && seenRuleTargets.get(v.id).has(target);
|
|
945
|
-
// Also check if any existing finding covers this exact target (broader dedup)
|
|
946
|
-
const selectorCovered = target && [...seen].some((k) => k.endsWith(`::${target}`));
|
|
947
|
-
|
|
948
|
-
if (!seen.has(key) && !isAxeEquivDuplicate && (!selectorCovered || !target)) {
|
|
943
|
+
if (!seen.has(key)) {
|
|
949
944
|
seen.add(key);
|
|
950
945
|
if (!seenRuleTargets.has(v.id)) seenRuleTargets.set(v.id, new Set());
|
|
951
946
|
seenRuleTargets.get(v.id).add(target);
|