AuditForge AI: Building a Five-Dimension Website Auditor with Gemini
2026-07-02 · 4 min read
The Problem with Existing Audit Tools
Developers use Google Lighthouse for performance, Screaming Frog for SEO, AccessibilityInsights for WCAG, and security scanners for vulnerabilities. No single tool covers all four — and none of them assess the newest dimension: AI crawler visibility.
As AI-powered search answers (ChatGPT, Perplexity, Google AI Overviews) become dominant, websites need to be discoverable not just to Googlebot, but to AI answer engines. This requires different metadata, different content structure, and explicit llms.txt files.
AuditForge AI covers all five dimensions in one tool.
The Five Audit Dimensions
1. SEO Audit
async function auditSEO(url: string, html: string): Promise<SEOAuditResult> {
const dom = parseHTML(html);
return {
titleTag: {
present: !!dom.querySelector("title"),
length: dom.querySelector("title")?.textContent?.length ?? 0,
optimal: between(dom.querySelector("title")?.textContent?.length ?? 0, 50, 60),
},
metaDescription: {
present: !!dom.querySelector('meta[name="description"]'),
length: dom.querySelector('meta[name="description"]')?.getAttribute("content")?.length ?? 0,
},
h1Count: dom.querySelectorAll("h1").length,
headingHierarchy: checkHeadingHierarchy(dom),
canonicalUrl: dom.querySelector('link[rel="canonical"]')?.getAttribute("href"),
openGraph: checkOpenGraph(dom),
structuredData: extractStructuredData(dom),
};
}
2. Performance Audit (Lighthouse API)
async function auditPerformance(url: string): Promise<PerformanceAuditResult> {
const { lhr } = await lighthouse(url, {
onlyCategories: ["performance"],
output: "json",
});
return {
performanceScore: lhr.categories.performance.score! * 100,
largestContentfulPaint: lhr.audits["largest-contentful-paint"].numericValue,
totalBlockingTime: lhr.audits["total-blocking-time"].numericValue,
cumulativeLayoutShift: lhr.audits["cumulative-layout-shift"].numericValue,
firstContentfulPaint: lhr.audits["first-contentful-paint"].numericValue,
speedIndex: lhr.audits["speed-index"].numericValue,
opportunities: extractOpportunities(lhr),
};
}
3. Accessibility Audit
async function auditAccessibility(url: string): Promise<AccessibilityAuditResult> {
const { lhr } = await lighthouse(url, {
onlyCategories: ["accessibility"],
output: "json",
});
return {
accessibilityScore: lhr.categories.accessibility.score! * 100,
violations: lhr.audits,
wcagLevel: determineWCAGLevel(lhr),
};
}
4. Security Audit
async function auditSecurity(url: string, headers: Headers): Promise<SecurityAuditResult> {
return {
https: url.startsWith("https://"),
hsts: headers.has("strict-transport-security"),
contentSecurityPolicy: headers.has("content-security-policy"),
xFrameOptions: headers.has("x-frame-options"),
xContentTypeOptions: headers.get("x-content-type-options") === "nosniff",
referrerPolicy: headers.has("referrer-policy"),
permissionsPolicy: headers.has("permissions-policy"),
mixedContent: await detectMixedContent(url),
};
}
5. AI Visibility Audit
This is the novel dimension. AI answer engines use different discovery mechanisms than Googlebot:
async function auditAIVisibility(url: string, html: string): Promise<AIVisibilityResult> {
const baseUrl = new URL(url).origin;
// Check for llms.txt
const llmsTxtResponse = await fetch(`${baseUrl}/llms.txt`).catch(() => null);
const hasLlmsTxt = llmsTxtResponse?.ok ?? false;
// Check for AI-readable structured data
const dom = parseHTML(html);
const structuredData = extractStructuredData(dom);
const hasArticleSchema = structuredData.some(s => s["@type"] === "Article");
const hasFAQSchema = structuredData.some(s => s["@type"] === "FAQPage");
const hasPersonSchema = structuredData.some(s => s["@type"] === "Person");
// Check content density (thin content is ignored by AI engines)
const wordCount = extractMainContent(dom).split(/\s+/).length;
// Check robots.txt for AI crawler rules
const robotsTxt = await fetch(`${baseUrl}/robots.txt`).then(r => r.text()).catch(() => "");
const blocksGPTBot = robotsTxt.includes("User-agent: GPTBot") && robotsTxt.includes("Disallow: /");
const blocksClaudeBot = robotsTxt.includes("User-agent: ClaudeBot") && robotsTxt.includes("Disallow: /");
return {
hasLlmsTxt,
hasArticleSchema,
hasFAQSchema,
hasPersonSchema,
wordCount,
blocksGPTBot,
blocksClaudeBot,
aiVisibilityScore: computeAIScore({ hasLlmsTxt, hasArticleSchema, wordCount, blocksGPTBot }),
};
}
The llms.txt standard is emerging — it's a machine-readable file telling AI crawlers what content they can use and how. It's the robots.txt for AI engines.
Gemini AI Recommendations
Raw audit data is overwhelming. Gemini translates it into a prioritized action list:
async function generateRecommendations(
auditResults: FullAuditResult,
): Promise<AIRecommendations> {
const prompt = `
You are a web performance and SEO expert. Analyze these audit results and create a prioritized action list.
Audit Results:
SEO Score: ${auditResults.seo.overallScore}/100
Performance Score: ${auditResults.performance.performanceScore}/100
Accessibility Score: ${auditResults.accessibility.accessibilityScore}/100
Security Issues: ${auditResults.security.issueCount}
AI Visibility Score: ${auditResults.aiVisibility.aiVisibilityScore}/100
Key Issues Found:
${JSON.stringify(auditResults.topIssues, null, 2)}
Provide:
1. Top 3 critical fixes (highest impact, lowest effort)
2. Top 3 medium-priority improvements
3. AI visibility quick wins (if llms.txt missing, structured data gaps)
4. One-paragraph plain-language summary for a non-technical stakeholder
Return as JSON: { critical: [], medium: [], aiQuickWins: [], summary: string }
`;
const response = await gemini.generateContent({
contents: [{ role: "user", parts: [{ text: prompt }] }],
generationConfig: { responseMimeType: "application/json" },
});
return JSON.parse(response.response.text());
}
The AI Visibility Check: Why It Matters
When I started building AuditForge AI, the AI visibility check didn't exist as a formal category anywhere. I built it because I'd seen firsthand (building the portfolio's llms.txt) how much it matters for discoverability.
The check revealed: most websites I audited were actively blocking GPTBot and ClaudeBot in their robots.txt — often because a developer added a blanket "block all bots" rule without realizing it blocked AI crawlers too.