@diegovelasquezweb/a11y-engine 0.11.28 → 0.11.29

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.28",
3
+ "version": "0.11.29",
4
4
  "description": "WCAG 2.2 accessibility audit engine — scanner, analyzer, and report builders",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -316,16 +316,22 @@ function parseJsonBlock(text) {
316
316
  }
317
317
 
318
318
  async function callClaudeForPatch({ apiKey, model, aiInput }) {
319
+ const isMulti = Array.isArray(aiInput.findings);
319
320
  const system = [
320
321
  "You are an accessibility fix engine.",
321
322
  "Return JSON only.",
322
323
  "Generate deterministic text replacements for provided files.",
323
- "Use finding.fixDescription and execution.constraints.must as guidance for what to fix and how.",
324
+ "Use finding.fixDescription and constraints.must as guidance for what to fix and how.",
324
325
  "For insertions (new element that does not yet exist in the file), use the nearest existing parent element as the search anchor. The replace value must include that anchor plus the new content.",
325
326
  "Do not create files. Do not modify paths outside provided filePath values.",
327
+ isMulti
328
+ ? "You are fixing MULTIPLE findings at once. Generate at least one change per finding. Set findingId on each change to the exact finding ID it addresses."
329
+ : "",
326
330
  "Schema:",
327
- "{\"changes\":[{\"filePath\":\"...\",\"search\":\"...\",\"replace\":\"...\"}],\"verifyRule\":\"...\",\"verifyRoute\":\"...\",\"notes\":\"...\"}",
328
- ].join("\n");
331
+ isMulti
332
+ ? "{\"changes\":[{\"findingId\":\"...(required: exact finding id)\",\"filePath\":\"...\",\"search\":\"...\",\"replace\":\"...\"}],\"notes\":\"...\"}"
333
+ : "{\"changes\":[{\"filePath\":\"...\",\"search\":\"...\",\"replace\":\"...\"}],\"notes\":\"...\"}",
334
+ ].filter(Boolean).join("\n");
329
335
 
330
336
  const userMessage = JSON.stringify(aiInput, null, 2);
331
337
 
@@ -388,6 +394,7 @@ function validateAiPatchOutput(output, projectDir, fileSet) {
388
394
  function applyChanges(projectDir, changes) {
389
395
  const changedFiles = [];
390
396
  const patchParts = [];
397
+ const succeededFindingIds = new Set();
391
398
 
392
399
  for (const change of changes) {
393
400
  const rel = change.filePath;
@@ -401,6 +408,9 @@ function applyChanges(projectDir, changes) {
401
408
  fs.writeFileSync(abs, updated, "utf8");
402
409
  changedFiles.push(rel);
403
410
  patchParts.push(`--- ${rel}\n+++ ${rel}\n@@\n-${change.search}\n+${change.replace}`);
411
+ if (typeof change.findingId === "string" && change.findingId.trim()) {
412
+ succeededFindingIds.add(change.findingId.trim());
413
+ }
404
414
  }
405
415
 
406
416
  if (changedFiles.length === 0) return { ok: false, reason: "No effective changes were applied" };
@@ -409,6 +419,7 @@ function applyChanges(projectDir, changes) {
409
419
  ok: true,
410
420
  changedFiles: [...new Set(changedFiles)],
411
421
  patch: patchParts.join("\n"),
422
+ succeededFindingIds,
412
423
  };
413
424
  }
414
425
 
@@ -661,8 +672,8 @@ export async function applyFindingFix(input) {
661
672
  message: "Patch applied successfully.",
662
673
  changedFiles: applied.changedFiles,
663
674
  patch: applied.patch,
664
- verifyRule: patchOutput.verifyRule || execution.verify.ruleId,
665
- verifyRoute: patchOutput.verifyRoute || execution.verify.route,
675
+ verifyRule: execution.verify.ruleId,
676
+ verifyRoute: execution.verify.route,
666
677
  findingTitle: finding.title || "",
667
678
  branchSlug: slugify(`${findingId}-${ruleId}`),
668
679
  usage: claudeUsage,
@@ -867,19 +878,30 @@ export async function applyFindingsFix(input) {
867
878
  const perInput = Math.round(claudeUsage.input_tokens / n);
868
879
  const perOutput = Math.round(claudeUsage.output_tokens / n);
869
880
 
881
+ // If Claude tagged changes with findingId, use those for per-finding success tracking.
882
+ // If no findingId tags were emitted, fall back to group-level success for all findings.
883
+ const hasFindingIdTracking = applied.succeededFindingIds.size > 0;
884
+
870
885
  for (const finding of withRules) {
871
886
  const ruleId = typeof finding.rule_id === "string" ? finding.rule_id.trim() : "";
872
887
  const intelligenceRule = intelligenceRules[ruleId] || {};
873
888
  const execution = buildExecution(ruleId, intelligenceRule, finding);
889
+
890
+ const findingApplied = hasFindingIdTracking
891
+ ? applied.succeededFindingIds.has(finding.id)
892
+ : true;
893
+
874
894
  resultMap.set(
875
895
  finding.id,
876
896
  makeResult(finding.id, {
877
- applied: true,
878
- reason: "",
879
- message: "Patch applied successfully.",
897
+ applied: findingApplied,
898
+ reason: findingApplied ? "" : FIX_ERROR_CODES.PATCH_APPLY_FAILED,
899
+ message: findingApplied
900
+ ? "Patch applied successfully."
901
+ : "The change for this finding could not be applied (search block not found).",
880
902
  changedFiles: applied.changedFiles,
881
903
  patch: applied.patch,
882
- verifyRule: patchOutput.verifyRule || execution.verify.ruleId,
904
+ verifyRule: execution.verify.ruleId,
883
905
  verifyRoute: execution.verify.route,
884
906
  findingTitle: finding.title || "",
885
907
  branchSlug: slugify(`${finding.id}-${ruleId}`),
@@ -605,8 +605,11 @@ async function analyzeRoute(
605
605
  const builder = new AxeBuilder({ page });
606
606
 
607
607
  if (onlyRule) {
608
- log.info(`Targeted Audit: Only checking rule "${onlyRule}"`);
609
- builder.withRules([onlyRule]);
608
+ const rules = onlyRule.includes(",")
609
+ ? onlyRule.split(",").map((r) => r.trim()).filter(Boolean)
610
+ : [onlyRule];
611
+ log.info(`Targeted Audit: Only checking rules "${rules.join(", ")}"`);
612
+ builder.withRules(rules);
610
613
  } else {
611
614
  const tagsToUse = axeTags || AXE_TAGS;
612
615
  builder.withTags(tagsToUse);