@diegovelasquezweb/a11y-engine 0.7.3 → 0.7.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/package.json +1 -1
- package/src/ai/enrich.mjs +64 -0
- package/src/cli/audit.mjs +33 -2
- package/src/reports/renderers/html.mjs +1 -9
package/package.json
CHANGED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @file enrich.mjs
|
|
4
|
+
* @description CLI script that reads a11y-findings.json, enriches Critical/Serious
|
|
5
|
+
* findings using the Claude AI module, and writes the result back.
|
|
6
|
+
* Runs as a child process from audit.mjs after the analyzer step.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { log, getInternalPath, writeJson } from "../core/utils.mjs";
|
|
11
|
+
import { enrichWithAI } from "./claude.mjs";
|
|
12
|
+
|
|
13
|
+
async function main() {
|
|
14
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
15
|
+
if (!apiKey) {
|
|
16
|
+
log.warn("No ANTHROPIC_API_KEY found — skipping AI enrichment.");
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const findingsPath = getInternalPath("a11y-findings.json");
|
|
21
|
+
|
|
22
|
+
let payload;
|
|
23
|
+
try {
|
|
24
|
+
const { readFileSync } = await import("node:fs");
|
|
25
|
+
payload = JSON.parse(readFileSync(findingsPath, "utf-8"));
|
|
26
|
+
} catch (err) {
|
|
27
|
+
log.warn(`Could not read findings file — skipping AI enrichment: ${err.message}`);
|
|
28
|
+
process.exit(0);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const findings = payload.findings ?? [];
|
|
32
|
+
if (findings.length === 0) {
|
|
33
|
+
log.info("No findings to enrich.");
|
|
34
|
+
process.exit(0);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const stack = payload.metadata?.projectContext ?? {};
|
|
38
|
+
const repoUrl = process.env.A11Y_REPO_URL || payload.metadata?.repoUrl || null;
|
|
39
|
+
const githubToken = process.env.GH_TOKEN || undefined;
|
|
40
|
+
|
|
41
|
+
log.info(`AI enrichment: processing up to 20 Critical/Serious findings...`);
|
|
42
|
+
|
|
43
|
+
const enriched = await enrichWithAI(findings, { stack, repoUrl }, {
|
|
44
|
+
enabled: true,
|
|
45
|
+
apiKey,
|
|
46
|
+
githubToken,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
writeJson(findingsPath, { ...payload, findings: enriched });
|
|
50
|
+
|
|
51
|
+
const improved = enriched.filter((f, i) =>
|
|
52
|
+
f.fixDescription !== findings[i]?.fixDescription ||
|
|
53
|
+
f.fixCode !== findings[i]?.fixCode
|
|
54
|
+
).length;
|
|
55
|
+
|
|
56
|
+
log.success(`AI enrichment complete. ${improved} finding(s) improved.`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
60
|
+
main().catch((err) => {
|
|
61
|
+
log.warn(`AI enrichment failed (non-fatal): ${err.message}`);
|
|
62
|
+
process.exit(0); // non-fatal — never block the pipeline
|
|
63
|
+
});
|
|
64
|
+
}
|
package/src/cli/audit.mjs
CHANGED
|
@@ -143,6 +143,28 @@ async function main() {
|
|
|
143
143
|
const repoUrl = getArgValue("repo-url");
|
|
144
144
|
const githubToken = getArgValue("github-token");
|
|
145
145
|
|
|
146
|
+
let remotePackageJson = null;
|
|
147
|
+
let detectedFramework = null;
|
|
148
|
+
if (repoUrl) {
|
|
149
|
+
try {
|
|
150
|
+
const { fetchPackageJson } = await import("../core/github-api.mjs");
|
|
151
|
+
remotePackageJson = await fetchPackageJson(repoUrl, githubToken || undefined);
|
|
152
|
+
if (remotePackageJson) {
|
|
153
|
+
const { detectProjectContext } = await import("../pipeline/dom-scanner.mjs");
|
|
154
|
+
const ctx = detectProjectContext(null, remotePackageJson);
|
|
155
|
+
if (ctx.framework) {
|
|
156
|
+
detectedFramework = ctx.framework;
|
|
157
|
+
log.info(`Detected framework from repository: ${ctx.framework}`);
|
|
158
|
+
}
|
|
159
|
+
if (ctx.uiLibraries.length) {
|
|
160
|
+
log.info(`Detected UI libraries: ${ctx.uiLibraries.join(", ")}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
} catch (err) {
|
|
164
|
+
log.warn(`Could not fetch package.json from repo: ${err.message}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
146
168
|
const sessionFile = getInternalPath("a11y-session.json");
|
|
147
169
|
let projectDir = getArgValue("project-dir");
|
|
148
170
|
if (projectDir) {
|
|
@@ -268,9 +290,18 @@ async function main() {
|
|
|
268
290
|
|
|
269
291
|
const analyzerArgs = [];
|
|
270
292
|
if (ignoreFindings) analyzerArgs.push("--ignore-findings", ignoreFindings);
|
|
271
|
-
|
|
293
|
+
const resolvedFramework = framework || detectedFramework;
|
|
294
|
+
if (resolvedFramework) analyzerArgs.push("--framework", resolvedFramework);
|
|
272
295
|
await runScript("../enrichment/analyzer.mjs", analyzerArgs);
|
|
273
296
|
|
|
297
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
298
|
+
await runScript("../ai/enrich.mjs", [], {
|
|
299
|
+
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
|
|
300
|
+
...(repoUrl ? { A11Y_REPO_URL: repoUrl } : {}),
|
|
301
|
+
...(githubToken ? { GH_TOKEN: githubToken } : {}),
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
274
305
|
if ((projectDir || repoUrl) && !skipPatterns) {
|
|
275
306
|
const patternArgs = [];
|
|
276
307
|
if (projectDir) {
|
|
@@ -279,7 +310,7 @@ async function main() {
|
|
|
279
310
|
patternArgs.push("--repo-url", repoUrl);
|
|
280
311
|
if (githubToken) patternArgs.push("--github-token", githubToken);
|
|
281
312
|
}
|
|
282
|
-
let resolvedFramework = framework;
|
|
313
|
+
let resolvedFramework = framework || detectedFramework;
|
|
283
314
|
if (!resolvedFramework) {
|
|
284
315
|
try {
|
|
285
316
|
const findings = JSON.parse(fs.readFileSync(getInternalPath("a11y-findings.json"), "utf-8"));
|
|
@@ -107,15 +107,7 @@ export function buildIssueCard(finding) {
|
|
|
107
107
|
</div>`
|
|
108
108
|
: "";
|
|
109
109
|
|
|
110
|
-
const implNotesHtml =
|
|
111
|
-
? `<div class="mt-4 pt-3 border-t border-indigo-100/50">
|
|
112
|
-
<h4 class="text-[10px] font-black text-amber-700 uppercase tracking-widest mb-2 flex items-center gap-1.5">
|
|
113
|
-
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg>
|
|
114
|
-
Implementation Notes
|
|
115
|
-
</h4>
|
|
116
|
-
<p class="text-[12px] text-amber-900/80 leading-relaxed bg-amber-50/60 border border-amber-100/60 rounded-lg p-3">${escapeHtml(finding.fixDifficultyNotes)}</p>
|
|
117
|
-
</div>`
|
|
118
|
-
: "";
|
|
110
|
+
const implNotesHtml = "";
|
|
119
111
|
|
|
120
112
|
const problemPanelHtml = `
|
|
121
113
|
<div class="grid grid-cols-1 gap-6">
|