@diegovelasquezweb/a11y-engine 0.7.8 → 0.8.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegovelasquezweb/a11y-engine",
3
- "version": "0.7.8",
3
+ "version": "0.8.0",
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
@@ -13,7 +13,7 @@
13
13
  */
14
14
 
15
15
  const ANTHROPIC_API = "https://api.anthropic.com/v1/messages";
16
- const DEFAULT_MODEL = "claude-sonnet-4-5";
16
+ const DEFAULT_MODEL = "claude-haiku-4-5-20251001";
17
17
  const MAX_AI_FINDINGS = 20; // cap to control cost
18
18
 
19
19
  /**
@@ -35,17 +35,18 @@ function buildSystemPrompt(context) {
35
35
 
36
36
  return `You are an expert web accessibility engineer specializing in WCAG 2.2 AA remediation.
37
37
 
38
- Your task is to improve the fix guidance for accessibility findings from an automated scan.
38
+ Your task is to provide a developer-friendly AI hint for each accessibility finding something MORE USEFUL than the generic automated fix already provided.
39
39
  ${stackInfo ? `\nProject context:\n${stackInfo}` : ""}
40
- For each finding you receive, provide:
41
- 1. A clear, specific fix description (1-2 sentences, actionable, no jargon)
42
- 2. Ready-to-use fix code in the correct language for the stack${context.hasSourceCode ? "\n3. The exact line/component to change if you can identify it from the source code" : ""}
40
+ For each finding, provide:
41
+ 1. fixDescription: A 2-3 sentence explanation that goes BEYOND the generic fix. Explain WHY this matters for real users, WHAT specifically to look for in the codebase, and HOW to verify the fix works. Be specific to the selector and actual violation data provided.
42
+ 2. fixCode: A ready-to-use, production-quality code snippet in the correct syntax for the stack. Do NOT copy the existing fix code write a BETTER, more complete example that a developer can use directly.
43
43
 
44
44
  Rules:
45
- - Keep fix code minimal and focused only the changed element, not the whole file
46
- - Use the detected framework syntax (JSX for React/Next.js, template syntax for Vue, etc.)
47
- - Do not change component logic, only accessibility attributes
48
- - If the fix requires multiple changes, show the most important one
45
+ - Your fixDescription must add new insight not present in currentFix don't paraphrase it
46
+ - Your fixCode must be different and more complete than currentCode
47
+ - Use framework-specific syntax (JSX/TSX for React/Next.js, SFC for Vue, etc.)
48
+ - Reference the actual selector or element from the finding when possible
49
+ - If the violation data contains specific values (colors, ratios, labels), use them in your response
49
50
  - Respond in JSON only — no markdown, no explanation outside the JSON structure`;
50
51
  }
51
52
 
package/src/ai/enrich.mjs CHANGED
@@ -52,7 +52,14 @@ async function main() {
52
52
  f.fixDescription !== original.fixDescription ||
53
53
  f.fixCode !== original.fixCode
54
54
  );
55
- return wasImproved ? { ...f, aiEnhanced: true } : f;
55
+ if (!wasImproved) return f;
56
+ return {
57
+ ...original,
58
+ aiEnhanced: true,
59
+ ai_fix_description: f.fixDescription || null,
60
+ ai_fix_code: f.fixCode || null,
61
+ ai_fix_code_lang: f.fixCodeLang || null,
62
+ };
56
63
  });
57
64
 
58
65
  writeJson(findingsPath, { ...payload, findings: enrichedWithFlag });
@@ -100,7 +100,7 @@ export function buildIssueCard(finding) {
100
100
  <div class="space-y-2">
101
101
  ${stackNotes.map(({ key, note, style }) => `
102
102
  <div class="flex gap-2 items-start">
103
- <span class="flex-shrink-0 px-2 py-0.5 rounded text-[10px] font-bold border ${style} uppercase tracking-wider mt-0.5">${escapeHtml(key)}</span>
103
+ <span class="shrink-0 px-2 py-0.5 rounded text-[10px] font-bold border ${style} uppercase tracking-wider mt-0.5">${escapeHtml(key)}</span>
104
104
  <p class="text-[12px] text-slate-600 leading-relaxed">${escapeHtml(note)}</p>
105
105
  </div>`).join("")}
106
106
  </div>
@@ -135,7 +135,7 @@ export function buildIssueCard(finding) {
135
135
  </div>`;
136
136
 
137
137
  const fixPanelHtml = `
138
- <div class="bg-gradient-to-br from-indigo-50 to-white border border-indigo-100/80 rounded-xl p-5 relative overflow-hidden shadow-sm">
138
+ <div class="bg-linear-to-br from-indigo-50 to-white border border-indigo-100/80 rounded-xl p-5 relative overflow-hidden shadow-sm">
139
139
  <div class="absolute top-0 right-0 p-4 opacity-[0.03] transform translate-x-4 -translate-y-4 pointer-events-none">
140
140
  <svg class="w-32 h-32 text-indigo-900" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z" clip-rule="evenodd"></path></svg>
141
141
  </div>
@@ -188,7 +188,7 @@ export function buildIssueCard(finding) {
188
188
  Visual Evidence
189
189
  </h4>
190
190
  <div class="bg-slate-50/50 p-2 rounded-xl border border-slate-200/60 inline-block shadow-sm">
191
- <img src="${escapeHtml(finding.screenshotPath)}" alt="Screenshot of ${escapeHtml(finding.title)}" class="rounded-lg border border-slate-200 shadow-sm max-h-[360px] w-auto object-contain bg-white" loading="lazy">
191
+ <img src="${escapeHtml(finding.screenshotPath)}" alt="Screenshot of ${escapeHtml(finding.title)}" class="rounded-lg border border-slate-200 shadow-sm max-h-90 w-auto object-contain bg-white" loading="lazy">
192
192
  </div>
193
193
  </div>`
194
194
  : "";
@@ -251,7 +251,7 @@ export function buildIssueCard(finding) {
251
251
  return `
252
252
  <article class="issue-card bg-white/90 backdrop-blur-xl rounded-2xl border ${borderClass} shadow-sm transition-all duration-300 hover:shadow-lg hover:-translate-y-0.5 mb-8 overflow-hidden group" data-severity="${finding.severity}" data-rule-id="${escapeHtml(finding.ruleId)}" data-wcag="${escapeHtml(finding.wcag)}" data-collapsed="true" id="${escapeHtml(finding.id)}">
253
253
  <button
254
- class="card-header w-full text-left p-5 md:p-6 bg-gradient-to-r from-white to-slate-50/80 cursor-pointer select-none relative focus:outline-none focus:ring-4 focus:ring-indigo-500/20"
254
+ class="card-header w-full text-left p-5 md:p-6 bg-linear-to-r from-white to-slate-50/80 cursor-pointer select-none relative focus:outline-none focus:ring-4 focus:ring-indigo-500/20"
255
255
  onclick="toggleCard(this)"
256
256
  aria-expanded="false"
257
257
  aria-controls="body-${escapeHtml(finding.id)}"
@@ -270,15 +270,15 @@ export function buildIssueCard(finding) {
270
270
  <div class="flex flex-wrap gap-x-4 gap-y-2 text-[13px] text-slate-600 font-medium">
271
271
  <div class="flex items-center gap-1.5 bg-slate-50/50 px-2 py-1 rounded-md border border-slate-100">
272
272
  <svg class="w-3.5 h-3.5 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"></path></svg>
273
- <span class="truncate max-w-[200px] md:max-w-md transition-colors searchable-field issue-url">${escapeHtml(finding.url)}</span>
273
+ <span class="truncate max-w-50 md:max-w-md transition-colors searchable-field issue-url">${escapeHtml(finding.url)}</span>
274
274
  </div>
275
275
  <div class="flex items-center gap-1.5 min-w-0 bg-slate-50/50 px-2 py-1 rounded-md border border-slate-100">
276
- <svg class="w-3.5 h-3.5 text-slate-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"></path></svg>
276
+ <svg class="w-3.5 h-3.5 text-slate-500 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"></path></svg>
277
277
  <code class="text-[12px] text-slate-800 font-mono truncate min-w-0 flex-1 searchable-field issue-selector">${escapeHtml(finding.selector)}</code>
278
278
  </div>
279
279
  </div>
280
280
  </div>
281
- <div class="bg-white p-1.5 rounded-full border border-slate-200 shadow-sm group-hover:bg-slate-50 transition-colors mt-1 flex-shrink-0">
281
+ <div class="bg-white p-1.5 rounded-full border border-slate-200 shadow-sm group-hover:bg-slate-50 transition-colors mt-1 shrink-0">
282
282
  <svg class="card-chevron w-5 h-5 text-slate-500 transition-transform duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7"></path></svg>
283
283
  </div>
284
284
  </div>
@@ -323,7 +323,7 @@ export function buildManualCheckCard(check) {
323
323
 
324
324
  const conditionalNote = check.conditional
325
325
  ? `<div class="mb-5 flex items-start gap-2.5 bg-amber-50 border border-amber-200 rounded-xl px-4 py-3">
326
- <svg class="w-4 h-4 text-amber-500 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
326
+ <svg class="w-4 h-4 text-amber-500 mt-0.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
327
327
  <p class="text-[12px] text-amber-800 font-medium leading-relaxed">${escapeHtml(check.conditional)}</p>
328
328
  </div>`
329
329
  : "";
@@ -346,7 +346,7 @@ export function buildManualCheckCard(check) {
346
346
 
347
347
  return `
348
348
  <article class="manual-card bg-white/90 backdrop-blur-xl rounded-2xl border border-amber-200 hover:border-amber-300 shadow-sm transition-all duration-300 hover:shadow-lg hover:-translate-y-0.5 mb-4 overflow-hidden group" id="${id}" data-criterion="${escapeHtml(check.criterion)}" data-level="${escapeHtml(check.level)}" data-state="" data-collapsed="true">
349
- <div class="manual-header flex items-stretch bg-gradient-to-r from-amber-50/60 to-white">
349
+ <div class="manual-header flex items-stretch bg-linear-to-r from-amber-50/60 to-white">
350
350
  <button
351
351
  class="card-header flex-1 text-left p-5 md:p-6 cursor-pointer select-none relative focus:outline-none focus:ring-4 focus:ring-amber-500/20"
352
352
  onclick="toggleCard(this)"
@@ -362,12 +362,12 @@ export function buildManualCheckCard(check) {
362
362
  </div>
363
363
  <h3 class="text-base font-extrabold text-slate-900 group-hover:text-amber-900 transition-colors">${escapeHtml(check.title)}</h3>
364
364
  </div>
365
- <div class="bg-white p-1.5 rounded-full border border-amber-200 shadow-sm group-hover:bg-amber-50 transition-colors flex-shrink-0">
365
+ <div class="bg-white p-1.5 rounded-full border border-amber-200 shadow-sm group-hover:bg-amber-50 transition-colors shrink-0">
366
366
  <svg class="card-chevron w-5 h-5 text-amber-500 transition-transform duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7"></path></svg>
367
367
  </div>
368
368
  </div>
369
369
  </button>
370
- <div class="flex items-center gap-2 px-4 border-l border-amber-100 flex-shrink-0">
370
+ <div class="flex items-center gap-2 px-4 border-l border-amber-100 shrink-0">
371
371
  <button class="manual-verified-btn px-3 py-1.5 rounded-full text-xs font-bold border border-slate-200 bg-white text-slate-500 hover:border-emerald-300 hover:text-emerald-700 hover:bg-emerald-50 transition-colors whitespace-nowrap" onclick="setManualState('${escapeHtml(check.criterion)}', 'verified')">✓ Verified</button>
372
372
  <button class="manual-na-btn px-3 py-1.5 rounded-full text-xs font-bold border border-slate-200 bg-white text-slate-500 hover:border-slate-400 hover:text-slate-600 hover:bg-slate-50 transition-colors whitespace-nowrap" onclick="setManualState('${escapeHtml(check.criterion)}', 'na')">N/A</button>
373
373
  </div>