@diegovelasquezweb/a11y-engine 0.11.32 → 0.11.33

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.32",
3
+ "version": "0.11.33",
4
4
  "description": "WCAG 2.2 accessibility audit engine — scanner, analyzer, and report builders",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -167,15 +167,56 @@ function buildPatternAiInput({ finding, candidate }) {
167
167
  };
168
168
  }
169
169
 
170
+ // Layout file name patterns for page-level fixes (e.g. bypass/skip-link).
171
+ // Ordered by priority: more specific names first.
172
+ const LAYOUT_FILE_PATTERNS = [
173
+ /^app[\/\\]layout\.[jt]sx?$/i,
174
+ /^src[\/\\]app[\/\\]layout\.[jt]sx?$/i,
175
+ /^pages[\/\\]_app\.[jt]sx?$/i,
176
+ /^pages[\/\\]_document\.[jt]sx?$/i,
177
+ /^src[\/\\]pages[\/\\]_app\.[jt]sx?$/i,
178
+ /^src[\/\\]pages[\/\\]_document\.[jt]sx?$/i,
179
+ /layouts[\/\\]default\.vue$/i,
180
+ /layouts[\/\\][^\/\\]+\.vue$/i,
181
+ /\+layout\.svelte$/i,
182
+ /app\.component\.html$/i,
183
+ /layouts[\/\\][^\/\\]+\.astro$/i,
184
+ /layout[\/\\]theme\.liquid$/i,
185
+ ];
186
+
187
+ function getLayoutCandidates(projectDir, files) {
188
+ const byPattern = [];
189
+ for (const { abs, rel, content } of files) {
190
+ const normalRel = rel.replace(/\\/g, "/");
191
+ for (let i = 0; i < LAYOUT_FILE_PATTERNS.length; i++) {
192
+ if (LAYOUT_FILE_PATTERNS[i].test(normalRel)) {
193
+ byPattern.push({ abs, rel, content, score: LAYOUT_FILE_PATTERNS.length - i });
194
+ break;
195
+ }
196
+ }
197
+ }
198
+ return byPattern.sort((a, b) => b.score - a.score).slice(0, MAX_CANDIDATE_FILES);
199
+ }
200
+
170
201
  function getCandidateFiles(projectDir, finding) {
171
- const files = listFilesRecursive(projectDir);
202
+ const allFiles = listFilesRecursive(projectDir).map((abs) => {
203
+ const content = fs.readFileSync(abs, "utf8");
204
+ const rel = path.relative(projectDir, abs);
205
+ return { abs, rel, content };
206
+ });
207
+
172
208
  const tokens = selectorTokens(finding.selector);
173
- const ranked = files
174
- .map((abs) => {
175
- const content = fs.readFileSync(abs, "utf8");
176
- const rel = path.relative(projectDir, abs);
177
- return { abs, rel, content, score: scoreFile(rel, content, tokens) };
178
- })
209
+
210
+ if (tokens.length === 0) {
211
+ // Selector has no useful tokens (e.g. "html", "body") — target layout files directly.
212
+ const layoutCandidates = getLayoutCandidates(projectDir, allFiles);
213
+ if (layoutCandidates.length > 0) return layoutCandidates;
214
+ // Fallback: return all files with equal weight so Claude gets the full picture.
215
+ return allFiles.slice(0, MAX_CANDIDATE_FILES).map((f) => ({ ...f, score: 1 }));
216
+ }
217
+
218
+ const ranked = allFiles
219
+ .map((f) => ({ ...f, score: scoreFile(f.rel, f.content, tokens) }))
179
220
  .filter((item) => item.score > 0)
180
221
  .sort((a, b) => b.score - a.score)
181
222
  .slice(0, MAX_CANDIDATE_FILES);
@@ -216,11 +257,19 @@ function groupFindingsByFile(domFindings, projectDir) {
216
257
 
217
258
  for (const finding of domFindings) {
218
259
  const tokens = selectorTokens(finding.selector);
219
- const ranked = allFiles
220
- .map((f) => ({ ...f, score: scoreFile(f.rel, f.content, tokens) }))
221
- .filter((f) => f.score > 0)
222
- .sort((a, b) => b.score - a.score)
223
- .slice(0, MAX_CANDIDATE_FILES);
260
+ let ranked;
261
+ if (tokens.length === 0) {
262
+ const layoutCandidates = getLayoutCandidates(projectDir, allFiles);
263
+ ranked = layoutCandidates.length > 0
264
+ ? layoutCandidates
265
+ : allFiles.slice(0, MAX_CANDIDATE_FILES).map((f) => ({ ...f, score: 1 }));
266
+ } else {
267
+ ranked = allFiles
268
+ .map((f) => ({ ...f, score: scoreFile(f.rel, f.content, tokens) }))
269
+ .filter((f) => f.score > 0)
270
+ .sort((a, b) => b.score - a.score)
271
+ .slice(0, MAX_CANDIDATE_FILES);
272
+ }
224
273
 
225
274
  const key = ranked.length > 0 ? ranked[0].rel : `__no_candidates_${finding.id}`;
226
275
  if (!initialGroups.has(key)) {