@diegovelasquezweb/a11y-engine 0.11.51 → 0.11.53

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.11.51",
3
+ "version": "0.11.53",
4
4
  "description": "WCAG 2.2 accessibility audit engine — scanner, analyzer, and report builders",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -465,16 +465,32 @@ function detectStylingSystem(aiInput) {
465
465
  return "inline";
466
466
  }
467
467
 
468
+ export function buildStyleInstruction(stylingSystem) {
469
+ if (stylingSystem === "tailwind") {
470
+ return (
471
+ "This project uses Tailwind CSS. Apply style fixes as Tailwind utility classes in the HTML/JSX file — do not write raw CSS. " +
472
+ "For inline style attributes that suppress focus (e.g. style=\"outline: none\"): remove outline: none from the style attribute and add focus-visible: Tailwind utility classes (e.g. focus-visible:outline-2 focus-visible:outline-offset-2) directly on the element instead."
473
+ );
474
+ }
475
+ if (stylingSystem === "css") {
476
+ return (
477
+ "CSS/SCSS files are available in the files array. Prefer fixing visual issues (touch targets, color contrast) in the CSS/SCSS file using proper selectors. " +
478
+ "For violations in a CSS/SCSS file: add or update the rule there directly. " +
479
+ "For violations in an inline HTML style attribute (e.g. style=\"outline: none\"): make TWO changes — " +
480
+ "(1) remove outline: none / outline: 0 from the HTML style attribute, " +
481
+ "(2) add a :focus-visible rule in the CSS/SCSS file targeting the element."
482
+ );
483
+ }
484
+ return (
485
+ "No CSS file was provided. Remove outline: none or outline: 0 from the style attribute entirely and rely on the browser default focus indicator. " +
486
+ "Do not add new CSS files."
487
+ );
488
+ }
489
+
468
490
  async function callClaudeForPatch({ apiKey, model, aiInput, remediationPath }) {
469
491
  const remediationContext = extractRemediationContext(remediationPath);
470
492
  const stylingSystem = detectStylingSystem(aiInput);
471
-
472
- const styleInstruction =
473
- stylingSystem === "tailwind"
474
- ? "This project uses Tailwind CSS. Apply style fixes as Tailwind utility classes in the HTML/JSX file — do not write raw CSS."
475
- : stylingSystem === "css"
476
- ? "CSS/SCSS files are available in the files array. Prefer fixing visual issues (touch targets, color contrast, focus outlines) in the CSS/SCSS file using proper selectors rather than inline styles."
477
- : "No CSS file was provided. Apply style fixes using inline style attributes in the HTML file.";
493
+ const styleInstruction = buildStyleInstruction(stylingSystem);
478
494
 
479
495
  const system = [
480
496
  "You are an accessibility fix engine.",
@@ -696,19 +712,36 @@ export async function applyFindingFix(input) {
696
712
 
697
713
  const validation = validateAiPatchOutput(patchOutput, projectDir, candidateSet);
698
714
  if (!validation.ok) {
699
- // When Claude returns no changes, it may be because a prior fix (e.g. the DOM
700
- // batch) already resolved this issue. Verify by checking if the pattern's
701
- // context_reject_regex now matches the surroundingLines of the target element.
715
+ // When Claude returns no changes OR a no-op patch (search===replace), it may be
716
+ // because a prior fix (e.g. the DOM batch) already resolved this issue.
717
+ // Verify by checking if the pattern's context_reject_regex now matches the
718
+ // current file content around the target element.
702
719
  // If it does, the element is already accessible — count as resolved.
703
- if (validation.reason === "AI patch output has no changes") {
720
+ const isNoChanges = validation.reason === "AI patch output has no changes";
721
+ const isNoop = validation.reason.startsWith("AI generated a no-op patch for ");
722
+ if (isNoChanges || isNoop) {
704
723
  const patternId = finding.pattern_id || finding.patternId || "";
705
724
  const patternDef = (ASSETS.remediation.codePatterns?.patterns || [])
706
725
  .find((p) => p.id === patternId);
707
726
  const rejectRegex = patternDef?.context_reject_regex;
708
727
  if (rejectRegex) {
709
- const context = [aiInput.finding.surroundingLines, aiInput.finding.matchLine]
710
- .filter(Boolean)
711
- .join("\n");
728
+ // For no-op patches, read the CURRENT file content — the DOM batch may have
729
+ // already resolved this finding by modifying the file after the scan.
730
+ let context;
731
+ if (isNoop) {
732
+ try {
733
+ const currentContent = fs.readFileSync(candidate.abs, "utf8");
734
+ const fileLines = currentContent.split("\n");
735
+ const lineIdx = Math.max(0, (aiInput.finding.line || 1) - 1);
736
+ const start = Math.max(0, lineIdx - 4);
737
+ const end = Math.min(fileLines.length, lineIdx + 5);
738
+ context = fileLines.slice(start, end).join("\n");
739
+ } catch {
740
+ context = [aiInput.finding.surroundingLines, aiInput.finding.matchLine].filter(Boolean).join("\n");
741
+ }
742
+ } else {
743
+ context = [aiInput.finding.surroundingLines, aiInput.finding.matchLine].filter(Boolean).join("\n");
744
+ }
712
745
  try {
713
746
  if (new RegExp(rejectRegex, "i").test(context)) {
714
747
  return buildResult({