@diegovelasquezweb/a11y-engine 0.8.4 → 0.8.5

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 CHANGED
@@ -1,4 +1,4 @@
1
- # @diegovelasquezweb/a11y-engine
1
+ # a11y Engine
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/@diegovelasquezweb/a11y-engine)](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 so frontends and agents can render guidance from engine-owned data |
18
19
 
19
20
  ## Installation
20
21
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegovelasquezweb/a11y-engine",
3
- "version": "0.8.4",
3
+ "version": "0.8.5",
4
4
  "description": "WCAG 2.2 accessibility audit engine — scanner, analyzer, and report builders",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/ai/claude.mjs CHANGED
@@ -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 { /* non-fatal */ }
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); // non-fatal — never block the pipeline
77
+ process.exit(0);
78
78
  });
79
79
  }
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,
@@ -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 — check via canonical rule ID (axe-equivalent) + selector
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
- // If pa11y was mapped to an axe rule ID, check if that rule already covers this target
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);