@diegovelasquezweb/a11y-engine 0.9.0 → 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 +11 -17
- package/docs/api-reference.md +346 -69
- 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 -0
- package/src/index.mjs +6 -1
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
|
@@ -94,12 +94,18 @@ Computes severity totals, compliance score, WCAG pass/fail status, persona impac
|
|
|
94
94
|
```ts
|
|
95
95
|
const summary = getOverview(findings, payload);
|
|
96
96
|
// summary.score -> 72
|
|
97
|
-
// summary.label -> "Good"
|
|
98
|
-
// summary.wcagStatus -> "Fail"
|
|
97
|
+
// summary.label -> "Good" // "Excellent" | "Good" | "Fair" | "Poor" | "Critical"
|
|
98
|
+
// summary.wcagStatus -> "Fail" // "Pass" | "Conditional Pass" | "Fail"
|
|
99
99
|
// summary.totals -> { Critical: 1, Serious: 3, Moderate: 5, Minor: 2 }
|
|
100
|
-
// summary.personaGroups -> {
|
|
101
|
-
//
|
|
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"
|
|
102
107
|
// summary.detectedStack -> { framework: "nextjs", cms: null, uiLibraries: [] }
|
|
108
|
+
// summary.totalFindings -> 11
|
|
103
109
|
```
|
|
104
110
|
|
|
105
111
|
### Output API
|
|
@@ -136,24 +142,12 @@ See [API Reference](docs/api-reference.md) for the full `EngineKnowledge` shape.
|
|
|
136
142
|
|
|
137
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.
|
|
138
144
|
|
|
139
|
-
## AI enrichment
|
|
140
|
-
|
|
141
|
-
When `ANTHROPIC_API_KEY` is set, the engine runs a post-scan enrichment step that sends Critical and Serious findings to Claude. Claude generates:
|
|
142
|
-
|
|
143
|
-
- A specific fix description referencing the actual selector, colors, and violation data
|
|
144
|
-
- A production-quality code snippet in the correct framework syntax
|
|
145
|
-
- Context-aware suggestions when repo source files are available
|
|
146
|
-
|
|
147
|
-
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`.
|
|
148
|
-
|
|
149
|
-
The system prompt is fully customizable via `options.ai.systemPrompt` (programmatic API) or the `AI_SYSTEM_PROMPT` env var (CLI).
|
|
150
|
-
|
|
151
145
|
## Documentation
|
|
152
146
|
|
|
153
147
|
| Resource | Description |
|
|
154
148
|
| :--- | :--- |
|
|
155
149
|
| [Architecture](docs/architecture.md) | Multi-engine pipeline, merge logic, and execution model |
|
|
156
|
-
| [API Reference](docs/api-reference.md) | Function signatures, options,
|
|
150
|
+
| [API Reference](docs/api-reference.md) | Function signatures, options, return contracts, and exported constants |
|
|
157
151
|
| [CLI Handbook](docs/cli-handbook.md) | Full flag reference and usage examples |
|
|
158
152
|
| [Output Artifacts](docs/outputs.md) | Schema and structure of every generated file |
|
|
159
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,29 +477,34 @@ 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
|
|
@@ -357,6 +513,8 @@ Returns: `Promise<SourcePatternResult>`
|
|
|
357
513
|
|
|
358
514
|
Returns all accessibility knowledge in a single call. Accepts an optional `{ locale?: string }` option (default: `"en"`).
|
|
359
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.
|
|
517
|
+
|
|
360
518
|
```ts
|
|
361
519
|
import { getKnowledge } from "@diegovelasquezweb/a11y-engine";
|
|
362
520
|
|
|
@@ -365,16 +523,135 @@ const knowledge = getKnowledge({ locale: "en" });
|
|
|
365
523
|
|
|
366
524
|
**Returns:** `EngineKnowledge`
|
|
367
525
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
526
|
+
**`EngineKnowledge` shape:**
|
|
527
|
+
|
|
528
|
+
```ts
|
|
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
|
+
}
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
---
|
|
609
|
+
|
|
610
|
+
## Constants
|
|
611
|
+
|
|
612
|
+
### `VIEWPORT_PRESETS`
|
|
613
|
+
|
|
614
|
+
Ready-made viewport dimensions for common device classes. Useful when building scanner UI option pickers.
|
|
615
|
+
|
|
616
|
+
```ts
|
|
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
|
+
// ]
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
Type: `ViewportPreset[]` — `{ label: string; width: number; height: number }[]`
|
|
629
|
+
|
|
630
|
+
---
|
|
631
|
+
|
|
632
|
+
### `DEFAULT_AI_SYSTEM_PROMPT`
|
|
633
|
+
|
|
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.
|
|
635
|
+
|
|
636
|
+
```ts
|
|
637
|
+
import { DEFAULT_AI_SYSTEM_PROMPT } from "@diegovelasquezweb/a11y-engine";
|
|
638
|
+
|
|
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
|
+
```
|
|
649
|
+
|
|
650
|
+
Type: `string`
|
|
651
|
+
|
|
652
|
+
---
|
|
653
|
+
|
|
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.
|
|
378
655
|
|
|
379
656
|
---
|
|
380
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;
|
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
|
}
|
|
@@ -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
|