karthik.dev
Back to blogSEO

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.

GitHub: github.com/karthikrshet/auditforge-ai