| name | geo-audit |
| description | GEO (Generative Engine Optimization) readiness audit. Scans source code for AI citation signals across 12 dimensions: AI crawler access, schema graph connectivity, llms.txt, SSR rendering, freshness, chunkability, canonicalization, sitemap, BLUF structure, heading quality, citation signals, and anti-patterns. Produces tiered report (A/B/C/D) with evidence-backed findings and JSON output for geo-fix consumption. |
| tools | ["Read","Edit","Write","Grep","Glob","Bash","Agent"] |
| codesift_tools | {"always":["analyze_project","index_status","index_folder","index_file","plan_turn","get_file_tree","get_file_outline","search_text","search_patterns","search_symbols","audit_scan"],"by_stack":{"typescript":["get_type_info"],"javascript":[],"python":["python_audit","analyze_async_correctness"],"php":["php_project_audit","php_security_scan","resolve_php_namespace"],"kotlin":["analyze_sealed_hierarchy","find_extension_functions","trace_flow_chain","trace_suspend_chain","trace_compose_tree","analyze_compose_recomposition","trace_hilt_graph","trace_room_schema","analyze_kmp_declarations","extract_kotlin_serialization_contract"],"nestjs":["nest_audit"],"nextjs":["framework_audit","nextjs_route_map","nextjs_metadata_audit"],"astro":["astro_audit","astro_actions_audit","astro_hydration_audit","astro_image_audit"],"hono":["analyze_hono_app","audit_hono_security"],"express":[],"fastify":[],"react":["react_quickstart","analyze_hooks","analyze_renders"],"django":["analyze_django_settings","effective_django_view_security","taint_trace"],"fastapi":["trace_fastapi_depends","get_pydantic_models"],"flask":["find_framework_wiring"],"jest":[],"yii":["resolve_php_service"],"prisma":["analyze_prisma_schema"],"drizzle":[],"sql":["sql_audit"],"postgres":["migration_lint"]}} |
zuvo:geo-audit -- GEO Readiness Audit
Deep code-level audit for Generative Engine Optimization. Examines source code, templates, and config files across 12 dimensions to determine how well a site is structured for AI citation by Google AI Overviews, ChatGPT Browse, Perplexity, and Gemini.
Scope: GEO readiness assessment, AI citation signal audit, schema graph analysis, content chunkability evaluation, AI crawler policy review.
Out of scope: Traditional SEO (zuvo:seo-audit), code quality (zuvo:code-audit), security vulnerabilities (zuvo:security-audit), live URL crawling beyond --live-url flag.
Mandatory File Loading
Read these files before any work begins:
../../shared/includes/codesift-setup.md -- CodeSift discovery and tool selection
../../shared/includes/env-compat.md -- Agent dispatch and environment adaptation
../../shared/includes/seo-bot-registry.md -- Canonical AI/search bot taxonomy and live-probe scope
../../shared/includes/seo-page-profile-registry.md -- Profile-aware thresholds and downgrades
../../shared/includes/geo-check-registry.md -- Canonical check slugs for G1-G12
../../shared/includes/geo-fix-registry.md -- Fix type IDs, safety tiers, framework templates
../../shared/includes/audit-output-schema.md -- JSON output contract
../../shared/includes/backlog-protocol.md -- Backlog persistence contract
../../shared/includes/run-logger.md -- Run logging contract
../../shared/includes/retrospective.md -- Retrospective protocol
Print the checklist:
CORE FILES LOADED:
1. codesift-setup.md -- [READ | MISSING -> STOP]
2. env-compat.md -- [READ | MISSING -> STOP]
3. seo-bot-registry.md -- [READ | MISSING -> STOP]
4. seo-page-profile-registry.md -- [READ | MISSING -> STOP]
5. geo-check-registry.md -- [READ | MISSING -> STOP]
6. geo-fix-registry.md -- [READ | MISSING -> STOP]
7. audit-output-schema.md -- [READ | MISSING -> STOP]
8. backlog-protocol.md -- [READ | MISSING -> STOP]
9. run-logger.md -- [READ | MISSING -> STOP]
10. retrospective.md -- [READ | MISSING -> STOP]
If any file is missing, STOP.
Environment Compatibility
Read ../../shared/includes/env-compat.md for agent dispatch patterns, path resolution, and progress tracking across Claude Code, Codex, and Cursor.
MANDATORY TOOL CALLS — GEO Audit Validity Gate
INVALID if any tool below is skipped. "DEFERRED", "N/A" NOT valid reasons.
| Tool | Trigger | Skip allowed? |
|---|
get_file_tree | Always | NO — locate llms.txt, robots.txt, sitemap.xml |
search_text | Always | NO — AI crawler directives, BLUF markers, JSON-LD |
search_patterns | Always | NO — GEO anti-patterns (paywalled SSR, weak headings) |
audit_scan | Always | NO — compound check |
nextjs_metadata_audit | Next.js detected | NO when Next.js |
Forbidden: nextjs_metadata_audit: skipped (when Next), codesift: unavailable (when deferred), retrospective: skipped — REJECTED.
POSTAMBLE: report on disk → retro appended → ~/.zuvo/append-runlog exit 0. Every GEO finding needs path/to/file.ext:LINE (verify-audit gate).
Mandatory-tools-acknowledgment: I will run get_file_tree + search_text + search_patterns + audit_scan + nextjs_metadata_audit (when Next.js) + framework tools for this GEO audit. Every finding will cite a `path/to/file.ext:LINE` resolving in the current tree.
Use the deterministic preload helper FIRST. Run ~/.zuvo/compute-preload geo-audit "$PWD". Math gate enforced.
CodeSift Integration
Read ../../shared/includes/codesift-setup.md for the full initialization sequence.
Summary: Run the CodeSift setup from codesift-setup.md at skill start. Use CodeSift for schema discovery, bot policy search, and template analysis when available. Fall back to Grep/Read/Glob/Bash if unavailable.
Safety Gates (NON-NEGOTIABLE)
GATE 1 -- Read-Only Audit
This audit is read-only against source code.
Allowed write targets:
zuvo/audits/ for the report file (.md and .json)
memory/backlog.md only when --persist-backlog is explicitly enabled
FORBIDDEN:
- Writing to any source/production file
- Modifying application code (suggest fixes in report, do not apply)
- Installing packages or modifying dependencies
GATE 2 -- Live Audit Consent
If --live-url is provided, follow the live probe consent protocol from env-compat.md. Code-only mode is the default and requires no consent.
Phase 0: Parse $ARGUMENTS and Detection
0.1 Arguments
| Argument | Behavior |
|---|
[path] | Scope to directory or file (default: .) |
--profile | Override auto-detection: blog, docs, ecommerce, marketing, app-shell |
--cms / --no-cms | Override CMS detection |
--live-url URL | Enable live checks (G1 HTTP status, content negotiation) |
--lang LANG | Anti-pattern language: en (default), pl |
--persist-backlog | Persist findings to memory/backlog.md |
Default: code-only audit of current directory, auto-detect profile and CMS.
0.2 Stack Detection
Detect the web framework from project config files:
ASTRO=$(find . -maxdepth 3 -name "astro.config.*" 2>/dev/null | wc -l)
NEXT=$(find . -maxdepth 3 -name "next.config.*" 2>/dev/null | wc -l)
HUGO=$(find . -maxdepth 2 \( -name "hugo.toml" -o -name "hugo.yaml" \) 2>/dev/null | wc -l)
NUXT=$(find . -maxdepth 3 -name "nuxt.config.*" 2>/dev/null | wc -l)
SVELTEKIT=$(find . -maxdepth 3 -name "svelte.config.*" 2>/dev/null | wc -l)
GATSBY=$(find . -maxdepth 3 -name "gatsby-config.*" 2>/dev/null | wc -l)
WP=$(find . -maxdepth 3 -name "wp-config.php" 2>/dev/null | wc -l)
DOCUSAURUS=$(find . -maxdepth 3 -name "docusaurus.config.*" 2>/dev/null | wc -l)
VITEPRESS=$(find . -maxdepth 3 -path "*/.vitepress/config.*" 2>/dev/null | wc -l)
REMIX=$(find . -maxdepth 3 -name "remix.config.*" 2>/dev/null | wc -l)
Also detect:
SEO/GEO files:
robots.txt -- check public/robots.txt, static/robots.txt, or framework route (src/pages/robots.txt.ts, app/robots.ts)
sitemap -- check public/sitemap*.xml, framework config (@astrojs/sitemap in config, app/sitemap.ts), or generated output
llms.txt -- check public/llms.txt, static/llms.txt
llms-full.txt -- check public/llms-full.txt, static/llms-full.txt
Deploy platform:
vercel.json or .vercel/ -> Vercel
netlify.toml or _headers + _redirects -> Netlify
wrangler.toml or _worker.js -> Cloudflare
.htaccess -> Apache
nginx.conf or /etc/nginx/ refs -> Nginx
- None detected -> Unknown
Store results:
DETECTED_STACK = [astro | nextjs | hugo | nuxt | sveltekit | gatsby | wordpress | docusaurus | vitepress | remix | html]
DETECTED_PLATFORM = [vercel | netlify | cloudflare | apache | nginx | unknown]
0.3 Profile Auto-Detection
Detect the site profile from codebase signals. Override with --profile flag.
Detection heuristics:
- blog: markdown/MDX files in
content/, src/content/, posts/, or blog route patterns
- docs:
docs/ directory, Docusaurus/Starlight/VitePress config, or docs route patterns
- ecommerce: product/cart/checkout components, Shopify/Snipcart/Stripe product config
- marketing: no content directory, few pages, landing page patterns (fallback default)
- app-shell: SPA framework with no content routes, dashboard patterns
Fallback: marketing (safest default -- fewest content quality expectations).
Store result:
DETECTED_PROFILE = [blog | docs | ecommerce | marketing | app-shell]
0.4 CMS Auto-Detection
Detect CMS from code signals. Override with --cms or --no-cms flag.
Detection signals:
- WordPress:
wp-config.php, wp-content/, PHP WordPress function calls
- Contentful:
@contentful/rich-text-* imports, contentful SDK usage
- Sanity:
@sanity/client imports, sanity.config.*
- Strapi:
@strapi/* imports, Strapi API calls
- Prismic:
@prismicio/* imports
Headless CMS heuristic: GraphQL queries to known CMS endpoints (*.contentful.com, *.sanity.io, *.prismic.io, *.strapi.*). Generic GraphQL usage (analytics, feature flags) does NOT trigger CMS detection.
When CMS detected:
- G9-G12 ->
INSUFFICIENT DATA
- Executive summary shows:
SCOPE NOTICE: "CMS-backed site detected ([type]). Content quality dimensions (G9-G12) cannot be assessed from source code. Use --live-url for partial live verification."
This is a first-class status in the report header, not a footnote.
Store result:
CMS_DETECTED = [wordpress | contentful | sanity | strapi | prismic | none]
0.5 seo-audit Import Protocol
Import overlapping findings from the most recent seo-audit JSON output to avoid re-auditing what seo-audit already checked.
- Scan
zuvo/audits/ for seo-audit-*.json files
- If found, select the file with the lexicographically greatest filename (ISO date + optional
-N suffix ensures correct ordering)
- Extract findings with
layer: geo from dimensions D3, D5, D9, D10. Also extract critical_gates.CG5 status from the JSON root object (CG5 is a gate value, not a dimension finding -- handled separately).
- Map to geo-audit dimensions:
- D5
robots-ai-policy -> G1
- D5
llms-txt-present -> G3
- D3 JSON-LD checks -> G2
- D3 CG5 (SSR) -> G4
- D9
heading-structure -> G10
- D9
answer-first -> G9
- D10
freshness -> G5
- D10
semantic-html -> G6
- D10
eeat-signals -> G2/G5
- Tag imported findings
[IMPORTED:seo-audit] and use the seo-audit's evidence
- If the seo-audit JSON is >48h old: emit warning "seo-audit import is stale (>48h old). Results may not reflect current codebase state."
- If no
seo-audit-*.json found: "No seo-audit JSON found. Running all checks from scratch."
Dependency direction constraint: geo-audit MAY read seo-audit output. seo-audit MUST NOT read geo-audit output. This prevents circular dependency between the two skills.
Print:
Stack: [framework] | Platform: [platform] | Profile: [profile] | CMS: [detected or none]
GEO files: robots.txt [found/MISSING] | sitemap [found/MISSING] | llms.txt [found/MISSING] | llms-full.txt [found/MISSING]
seo-audit import: [filename and date | "none -- running all checks from scratch"]
Phase 1: Parallel Agent Dispatch
Dispatch 3 agents in parallel. Each agent evaluates its assigned dimensions independently and returns raw check statuses only.
Dimension Grouping
| Group | Agent | File | Dimensions |
|---|
| A (Crawl & Access) | Crawl & Access Agent | agents/geo-crawl-access.md | G1, G7, G8 |
| B (Schema & Render) | Schema & Render Agent | agents/geo-schema-render.md | G2, G4, G5 |
| C (Content Signals) | Content Signals Agent | agents/geo-content-signals.md | G3, G6, G9, G10, G11, G12 |
Agent Dispatch
Refer to ../../shared/includes/env-compat.md for dispatch patterns per environment.
Claude Code: Use the Agent tool to run all three in parallel:
Agent 1: GEO Crawl & Access (Group A)
model: "sonnet"
type: "Explore"
instructions: read agents/geo-crawl-access.md
input: detected_stack, detected_platform, detected_profile, cms_detected,
imported_findings (G1/G7/G8 subset), file_paths, codesift_repo, lang
Agent 2: GEO Schema & Render (Group B)
model: "sonnet"
type: "Explore"
instructions: read agents/geo-schema-render.md
input: detected_stack, detected_profile, cms_detected,
imported_findings (G2/G4/G5 subset), file_paths, codesift_repo, lang
Agent 3: GEO Content Signals (Group C)
model: "sonnet"
type: "Explore"
instructions: read agents/geo-content-signals.md
input: detected_stack, detected_profile, cms_detected,
imported_findings (G3/G6/G9-G12 subset), file_paths, codesift_repo, lang
Codex: Define TOML agents per env-compat.md patterns. Each agent runs in read-only sandbox.
Cursor: No agent dispatch. Execute each agent's analysis sequentially yourself, maintaining identical output format.
If native agent dispatch is unavailable, run the three agent analyses sequentially yourself, preserve the same report sections, and note the fallback mode in the final audit header.
Waiting for Results
Collect all 3 agent reports before proceeding to Phase 2. Pass to each agent: detected_stack, detected_platform, detected_profile, cms_detected, imported_findings (subset for that agent's dimensions), file_paths, codesift_repo, lang.
If an agent fails or times out:
- Retry once with same inputs
- If retry fails: log the error, proceed with results from successful agents, mark the failed agent's dimensions as
INSUFFICIENT DATA
Agent vs main scoring boundary: Agents return raw check statuses (PASS/PARTIAL/FAIL/INSUFFICIENT DATA) per check. The main agent calculates all numeric scores in Phase 3. Agents do NOT calculate dimension scores themselves.
Dimension Constraints (normative -- agents MUST follow)
Enforcement Model (normative)
Read ../../shared/includes/geo-check-registry.md as the single source of truth for owner_agent, layer, enforcement, and evidence_mode.
blocking: can produce overall FAIL or PROVISIONAL
scored: affects dimension and overall scores, but cannot alone flip the overall result
advisory: prioritized and reported, but excluded from pass/fail logic
Only blocking checks may produce overall FAIL or PROVISIONAL. Heuristic, advisory, or content-inaccessible findings must never create a blocking result without direct evidence.
For fix_type identifiers and safety classifications, agents MUST use ../../shared/includes/geo-fix-registry.md as the canonical source.
G1 -- AI Crawler Access:
- Use
../../shared/includes/seo-bot-registry.md for canonical bot taxonomy
- Training vs retrieval bot distinction is explicit: blocking GPTBot (training) while allowing ChatGPT-User (retrieval) is scored as PASS, not FAIL
- When Cloudflare/WAF/CDN is detected AND
--live-url is not provided, G1 robots.txt checks are capped at PARTIAL with note: "WAF detected -- robots.txt PASS cannot be confirmed without live verification"
- WAF detection: scan for
_headers, wrangler.toml, Cloudflare Pages config, Vercel vercel.json firewall rules
G2 -- Schema Graph:
- Attribute richness matters: generic minimally-populated schema underperforms no schema (41.6% vs 59.8% citation rate per Growth Marshal research)
- Check required + recommended fields per type (Organization, Article, Person, FAQPage, WebSite)
@graph pattern preferred over scattered inline schemas
- Wikidata/Wikipedia
sameAs links for entity disambiguation
G4 -- SSR & Rendering:
- FAIL patterns:
useEffect + JSON-LD injection (Next.js), client:load island containing schema (Astro), document.head.appendChild with schema script
- PASS patterns: inline
<script type="application/ld+json"> in layout/page component rendered server-side
G9-G12 -- Content Dimensions (advisory only):
- Never produce blocking gate failures
- On CMS-detected sites:
INSUFFICIENT DATA
- All heuristics are regex-based and deterministic, not LLM-subjective
CodeSift Query Patterns (when available)
Use these specific queries for GEO-relevant searches:
search_text(repo, "application/ld+json", file_pattern="*.{astro,tsx,html,php}")
search_text(repo, "@id", file_pattern="*.{astro,tsx,html,json}")
search_text(repo, "sameAs", file_pattern="*.{astro,tsx,html,json}")
search_text(repo, "canonical", file_pattern="*.{astro,tsx,html,php}")
search_text(repo, "robots", file_pattern="*.{txt,ts,js,toml,yaml}")
search_text(repo, "sitemap", file_pattern="*.{ts,js,mjs,toml,yaml}")
search_text(repo, "llms.txt")
search_text(repo, "dateModified", file_pattern="*.{astro,tsx,html,json}")
search_text(repo, "datePublished", file_pattern="*.{astro,tsx,html,json}")
search_text(repo, "client:load", file_pattern="*.astro")
search_text(repo, "use client", file_pattern="*.{tsx,jsx}")
Phase 2: Merge & Assign IDs
After all agents complete:
2.1 Concatenate Findings
Merge findings arrays from all 3 agents into a single ordered list.
2.2 Assign Stable IDs
Assign stable finding IDs using format {dimension}-{check_slug} (e.g., G2-schema-id-disconnected, G1-retrieval-bots-blocked). These IDs are deterministic across runs for the same codebase.
Also assign display-order numbers (F1, F2, ...) for human-readable reports. The stable ID is used for --finding filtering in geo-fix.
2.3 Conflict Resolution (imported vs own checks)
If an imported seo-audit finding conflicts with geo-audit's own deeper check, geo-audit's result takes precedence. Note in the finding's confidence_reason field:
"Overrides imported seo-audit finding [id] which reported [status]."
geo-audit's checks are deeper (e.g., @id graph connectivity goes beyond D3's basic schema presence check), so geo-audit's own result is authoritative.
2.4 Missing Dimensions
If any dimension is entirely missing (agent failed): mark all its checks as INSUFFICIENT DATA in scoring.
Phase 3: Scoring
3.1 Check Status -> Numeric Value
| Status | Value | Notes |
|---|
| PASS | 1.0 | Evidence confirms check passes |
| PARTIAL | 0.5 | Partially satisfied or minor issues |
| FAIL | 0.0 | Evidence confirms check fails |
| INSUFFICIENT DATA | excluded | Not counted in denominator |
3.2 Dimension Scores
Per dimension: score = (sum of check values / count of non-excluded checks) * 100
N/A rules per dimension (exclude from overall score):
| Dimension | N/A when |
|---|
| G9 (BLUF) | No content corpus (no markdown/blog/pages directories) |
| G10 (Heading Structure) | No content corpus |
| G11 (Citation Signals) | docs or ecommerce profile |
| G12 (Anti-patterns) | No content corpus |
3.3 Critical Gates
All 4 gates MUST have explicit status:
GCG1: Retrieval bots not blocked in robots.txt -- from G1
GCG2: Schema has @id field -- from G2
GCG3: JSON-LD is SSR-rendered (not client-only) -- from G4
GCG4: Canonical tags present in layout -- from G7
Critical gate statuses:
PASS -- evidence confirms gate is satisfied
FAIL -- evidence confirms gate is not satisfied
INSUFFICIENT DATA -- static analysis is inconclusive and no live verification is available
Scoring rules:
- Any blocking critical gate =
FAIL -> overall result = FAIL regardless of score
- Any blocking critical gate =
INSUFFICIENT DATA -> overall result = PROVISIONAL until live/source verification is completed
PROVISIONAL does not block CI gates (it is not a FAIL) but flags incomplete assurance
- Only
blocking checks and critical gates control FAIL vs PROVISIONAL. scored and advisory findings may lower scores, but they never override the overall result on their own.
GCG2 detail: Schema present WITHOUT @id = G2 dimension PARTIAL score but GCG2 FAIL. Schema absent entirely = both G2 FAIL and GCG2 FAIL.
3.4 Weighted Overall Score
Dimension weights (sum to 100%):
G1: 15% G2: 18% G3: 8% G4: 12%
G5: 10% G6: 7% G7: 10% G8: 5%
G9: 5% G10: 4% G11: 3% G12: 3%
Active weights = sum of weights where dimension is NOT N/A
Overall = sum(dimension_score * weight) / active_weights * 100
3.5 Tier Assignment
A (>= 85): GEO-ready. Strong AI citation signals across all dimensions.
B (70-84): Good foundation. Optimization opportunities identified.
C (50-69): Significant gaps. Prioritized fixes needed for AI visibility.
D (< 50): Major issues. Site unlikely to be cited by AI systems.
Result overrides:
- Any blocking critical gate = FAIL -> result = "FAIL" (regardless of tier)
- Any blocking critical gate = INSUFFICIENT DATA -> result = "PROVISIONAL"
Tier is always calculated from score: A/B/C/D.
3.6 3D Priority Calculation
For each finding:
SEO Impact: HIGH(3) / MEDIUM(2) / LOW(1)
Business Impact: HIGH(3) / MEDIUM(2) / LOW(1)
Fix Effort: EASY(1) / MEDIUM(2) / HARD(3)
Priority = (SEO_impact * 0.4) + (Business_impact * 0.4) + ((4 - Effort) * 0.2)
Range: 1.0 - 3.0
Quick Win = Priority >= 2.0 AND Effort = EASY
Assignment rubric (to ensure consistent scoring):
| Factor | 3 (HIGH) | 2 (MEDIUM) | 1 (LOW) |
|---|
| SEO Impact | Blocks AI citation, schema graph, or crawlability (GCG fail) | Degrades citation signals or content discoverability | Cosmetic or minor optimization |
| Business Impact | Affects homepage, landing pages, or money pages | Affects secondary pages or non-revenue content | Affects low-traffic or internal pages |
| Fix Effort | 1-2 files, config change or additive insert | 3-5 files, template modification, testing needed | 6+ files, architecture change, or content creation |
Phase 4: Validation
Before generating the report, verify:
- Count Consistency: Total checks = sum of all dimension checks. No check counted twice.
- Score Math: Recalculate overall from dimension scores * weights. Must match within 0.1.
- Critical Gate Completeness: All 4 gates have explicit PASS/FAIL/INSUFFICIENT DATA with evidence.
- Evidence Completeness: Every FAIL finding has file:line or INSUFFICIENT DATA note.
- Finding ID Uniqueness: All stable IDs (
{dimension}-{check_slug}) are unique. No duplicates.
- Display ID Sequence: F-IDs are sequential (F1, F2, ...) with no gaps or duplicates.
- JSON Schema Compliance: Output matches
audit-output-schema.md v1.1 with "skill": "geo-audit".
- Dimension Coverage: All 12 dimensions (G1-G12) have at least one check result (even if INSUFFICIENT DATA or N/A).
Fix any discrepancies before presenting to user.
Phase 4b: Adversarial Review on Audit Report (MANDATORY -- do NOT skip)
After the audit report is generated, run cross-model validation to catch score inflation and gate inconsistency. Runs on ALL audits.
adversarial-review --mode audit --files "zuvo/audits/geo-audit-[date].md"
If adversarial-review is not in PATH: ~/.claude/plugins/cache/zuvo-marketplace/zuvo/*/scripts/adversarial-review.sh
Wait for complete output. Then:
- CRITICAL (FAIL gate not in verdict, severity mismatch, score inflation) -> fix in report before delivery
- WARNING (N/A abuse, skipped check, INSUFFICIENT DATA overuse) -> append to Known Gaps section
- INFO -> ignore
Phase 5: Report
5.1 Executive Summary
GEO AUDIT -- [project name]
----
GEO Score: [N]/100 -- Tier [A/B/C/D]
Result: [PASS / FAIL / PROVISIONAL]
Profile: [detected or overridden profile]
CMS: [detected type or "none"]
----
Critical Gates: [N PASS] / [N FAIL] / [N INSUFFICIENT DATA]
Health scale: HEALTHY (80+), NEEDS ATTENTION (60-79), AT RISK (40-59), CRITICAL (<40).
Include scope notices (CMS, WAF advisories) immediately after the summary header, not buried in findings.
If seo-audit was imported: Imported [N] findings from seo-audit ([date]). [M] overridden by deeper geo-audit checks.
5.2 Dimension Score Table
All 12 dimensions with score, weight, weighted contribution, check count, and status:
| Dimension | Score | Weight | Weighted | Checks | Status |
|-----------|-------|--------|----------|--------|--------|
| G1: AI Crawler Access | 85 | 15% | 12.8 | 4 | PASS |
| G2: Schema Graph | 50 | 18% | 9.0 | 6 | PARTIAL |
| ... | ... | ... | ... | ... | ... |
5.3 Critical Gates
All 4 gates with explicit PASS/FAIL/INSUFFICIENT DATA and evidence:
GCG1: Retrieval bots not blocked -- [PASS/FAIL/INSUFFICIENT DATA] -- [evidence]
GCG2: Schema has @id -- [PASS/FAIL/INSUFFICIENT DATA] -- [evidence]
GCG3: JSON-LD is SSR-rendered -- [PASS/FAIL/INSUFFICIENT DATA] -- [evidence]
GCG4: Canonical tags present -- [PASS/FAIL/INSUFFICIENT DATA] -- [evidence]
5.4 Findings Detail
Every finding in the execution plan uses this structure:
[F-ID] [Dimension] [Severity] [Confidence]
Issue: [one-line description]
Evidence: [file:line or "code-only inference"]
Why it matters: [GEO/citation impact in one sentence]
Fix: [actionable instruction]
Priority: [N.N] (SEO=[1-3] x Biz=[1-3] x Effort=[1-3])
Enforcement: [blocking | scored | advisory]
Layer: [technical-geo | content-geo]
ETA: [minutes or "n/a"]
Confidence scale:
HIGH = direct source evidence (file:line confirms the finding)
MEDIUM = inferred from config or indirect signals
LOW = heuristic or absence-based (e.g., file not found)
See also `../../shared/includes/geo-fix-registry.md` for the canonical confidence definitions.
Findings sorted by:
- Quick Wins first (Priority >= 2.0 AND Effort = EASY)
- Then by priority descending
5.5 Fix Coverage Summary
Count of findings by fix category:
Fixable (SAFE): [N] findings -- auto-applied by geo-fix
Fixable (MODERATE): [N] findings -- applied with validation by geo-fix --auto
Fixable (DANGEROUS): [N] findings -- requires confirmation per fix
Out of Scope: [N] findings -- content scaffolds only, no auto-fix
Advisory: [N] findings -- no fix template
5.6 Next-Action Routing
| Audit Result | Proposed Action | Why |
|---|
| Any GCG = FAIL | Fix critical gate first | GCG failures block all AI citation optimization |
| GEO Score < 50 | Run zuvo:geo-fix for quick wins | Technical fixes have highest ROI |
| G2 < 50 (Schema) | Add/fix JSON-LD with @id connectivity | Schema graph is the strongest GEO signal |
| G1 < 50 (Crawlers) | Fix robots.txt AI bot policy | No access = no citation |
| G3 < 50 (llms.txt) | Generate llms.txt | Low effort, future-proofing |
| Tier A (>= 85) | Periodic re-audit or add --live-url for verification | Maintain and measure |
5.7 Save Report
mkdir -p audit-results
Save to: zuvo/audits/geo-audit-YYYY-MM-DD.md — at the project root (zuvo/ resolves via git rev-parse --show-toplevel; override $ZUVO_OUTPUT_DIR. See ../../shared/includes/report-output-location.md).
Auto-increment if a report for today already exists: geo-audit-YYYY-MM-DD-2.md, geo-audit-YYYY-MM-DD-3.md, etc.
5.8 JSON Output
Before generating JSON, read ../../shared/includes/audit-output-schema.md for the schema contract. For fix_type values and safety classifications, reference ../../shared/includes/geo-fix-registry.md.
After saving the markdown report, also save structured JSON findings for downstream consumption by zuvo:geo-fix and CI pipelines.
File: zuvo/audits/geo-audit-YYYY-MM-DD.json
Auto-increment with -N suffix if same-day file exists (same convention as .md).
Schema: See ../../shared/includes/audit-output-schema.md for the full schema definition.
Serialize from Phase 3 scoring results. Top-level structure:
{
"version": "1.1",
"skill": "geo-audit",
"timestamp": "[ISO 8601]",
"project": "[absolute path]",
"args": "[arguments]",
"stack": "[detected stack]",
"profile": "[detected/overridden profile]",
"cms_detected": "[CMS type or null]",
"seo_audit_imported": "[seo-audit date or null]",
"result": "[PASS | FAIL | PROVISIONAL]",
"score": {
"overall": "[0-100]",
"tier": "[A/B/C/D]",
"dimensions": {
"G1": { "score": "[0-100]", "weight": 15, "checks": "[N]", "excluded": "[N]" }
}
},
"critical_gates": [
{ "id": "GCG1", "name": "Retrieval bots not blocked", "dimension": "G1", "status": "[PASS|FAIL|INSUFFICIENT DATA]", "evidence": "..." }
],
"findings": [
{
"id": "G2-schema-id-disconnected", "display_id": "F3", "dimension": "G2",
"check": "schema-id-disconnected", "status": "FAIL", "severity": "HIGH",
"enforcement": "scored", "layer": "technical-geo",
"seo_impact": 3, "business_impact": 3, "effort": 2, "priority": 2.8,
"confidence_reason": "...", "evidence": "src/layouts/BaseLayout.astro:42",
"file": "src/layouts/BaseLayout.astro", "line": 42,
"fix_type": "schema-id-link", "fix_safety": "MODERATE",
"fix_params": { "file": "src/layouts/BaseLayout.astro" },
"eta_minutes": 20, "imported_from": null
}
],
"scope_notices": [],
"advisories": [],
"summary": {
"findings_count": { "total": 0, "critical": 0, "high": 0, "medium": 0, "low": 0 },
"quick_wins": 0,
"fixable": { "safe": 0, "moderate": 0, "dangerous": 0, "out_of_scope": 0, "no_template": 0 }
}
}
All 12 dimensions (G1-G12) must appear in score.dimensions with their respective weights. All 4 critical gates must appear in critical_gates array.
Nullability: findings[].fix_type, findings[].fix_safety, and findings[].fix_params are nullable. Set to null for findings that have no auto-fix template (content quality, advisory checks, etc.). Consumers MUST check for null before using these fields.
The findings[].fix_type, findings[].fix_safety, and findings[].fix_params fields enable zuvo:geo-fix to apply automated fixes without re-scanning the codebase.
Extension fields (tolerated under v1.1 unknown-key rules): profile, cms_detected, seo_audit_imported, scope_notices, advisories.
CI-Parseable Summary:
GEO-AUDIT-RESULT: PASS|FAIL|PROVISIONAL score=NN tier=X critical=none|GCG-N
Phase 6: Backlog & Next Steps
Backlog Persistence (optional)
Activated with --persist-backlog flag.
Follow ../../shared/includes/backlog-protocol.md for the full persistence contract.
Emit entries to memory/backlog.md for findings that meet at least one condition:
- Priority >= 2.0
- Any Critical Gate = FAIL
Fingerprint format: {file}|{dimension}|{check} (e.g., public/robots.txt|G1|retrieval-bots-blocked).
Same format used by geo-fix for backlog updates. Deduplicate against existing entries.
Next-Action Suggestion
After backlog persistence, suggest the next skill to run:
Suggested next step: zuvo:geo-fix
[N] fixable findings detected ([M] SAFE, [K] MODERATE).
Run `zuvo:geo-fix` to apply automated fixes, or `zuvo:geo-fix --dry-run` to preview.
GEO-AUDIT COMPLETE
Overall: [N]/100 -- Tier [A/B/C/D] | Result: [PASS/FAIL/PROVISIONAL]
Profile: [profile] | CMS: [type or none]
Critical gates: [N PASS] / [N FAIL] / [N INSUFFICIENT DATA]
Findings: [N critical] / [N total] | Quick wins: [N]
Validity Gate (REQUIRED — print BEFORE Run line, AFTER retro append + append-runlog)
VALIDITY GATE
required_tool_calls:
get_file_tree: [<N> | NOT_CALLED]
search_text: [<N> | NOT_CALLED]
search_patterns: [<N> | NOT_CALLED]
audit_scan: [<N> | NOT_CALLED]
nextjs_metadata_audit: [<N> | not_required | NOT_CALLED]
postamble:
retros_log_appended: [yes(bytes_added=N) | NOT_APPENDED]
retros_md_appended: [yes(entry_count=N) | NOT_APPENDED]
verify_audit_pass: [yes(<verified>/<total>) | NOT_RUN | REJECTED]
gate_status: [PASS | FAIL]
If gate_status = FAIL → VERDICT = INCOMPLETE. Append the Run line via the retro-gated wrapper (NOT direct >> runs.log):
printf '%b\n' "$RUN_LINE" | ~/.zuvo/append-runlog
Run: geo-audit - -dimensions
Retrospective (REQUIRED)
Follow the retrospective protocol from retrospective.md.
Gate check → structured questions → TSV emit → markdown append.
If gate check skips: print "RETRO: skipped (trivial session)" and proceed.
After printing this block, append the Run: line value (without the Run: prefix) to the log file path resolved per run-logger.md.
VERDICT: PASS (0 critical findings), WARN (1-3 critical), FAIL (4+ critical).