Skip to content
Back to skills

Geo Audit

ASecurity

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.

  • 8 stars
  • 0 votes
  • 0 copies
  • 7 views
  • Added May 28, 2026
developmentjavascripttypescriptpythongojavaphpkotlinshellbashsql

Works with

  • claude code
  • cursor
  • cli
  • api

Security analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned September 21, 2026

npx -y skills add greglas75/zuvo --skill geo-audit --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Geo Audit?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Geo Audit
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/greglas75-geo-audit/badge)](https://www.skillsdirectory.com/skills/greglas75-geo-audit)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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."
category: Structure/SEO/GEO
tools:
  - Read
  - Edit
  - Write
  - Grep
  - Glob
  - Bash
  - Agent
codesift_tools:
  always:
    - analyze_project
    - index_status
    - index_folder
    - index_file
    - plan_turn
    - get_file_tree            # find llms.txt, robots.txt, sitemap.xml, rss
    - get_file_outline
    - search_text              # AI crawler directives, BLUF markers, JSON-LD
    - search_patterns          # GEO anti-patterns (paywalled SSR, weak headings)
    - 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:

1. `../../shared/includes/codesift-setup.md` -- CodeSift discovery and tool selection
2. `../../shared/includes/env-compat.md` -- Agent dispatch and environment adaptation
3. `../../shared/includes/seo-bot-registry.md` -- Canonical AI/search bot taxonomy and live-probe scope
4. `../../shared/includes/seo-page-profile-registry.md` -- Profile-aware thresholds and downgrades
5. `../../shared/includes/geo-check-registry.md` -- Canonical check slugs for G1-G12
6. `../../shared/includes/geo-fix-registry.md` -- Fix type IDs, safety tiers, framework templates
7. `../../shared/includes/audit-output-schema.md` -- JSON output contract
8. `../../shared/includes/backlog-protocol.md` -- Backlog persistence contract
9. `../../shared/includes/run-logger.md` -- Run logging contract
10. `../../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 all supported platforms.

## 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:

```bash
# Portable web framework detection (BSD/GNU compatible)
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.

1. Scan `zuvo/audits/` for `seo-audit-*.json` files
2. If found, select the file with the lexicographically greatest filename (ISO date + optional `-N` suffix ensures correct ordering)
3. 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).
4. 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
5. Tag imported findings `[IMPORTED:seo-audit]` and use the seo-audit's evidence
6. If the seo-audit JSON is >48h old: emit warning "seo-audit import is stale (>48h old). Results may not reflect current codebase state."
7. 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 follows `../../shared/includes/execution-policy.md` through env-compat. Reuse existing
authorization within that policy; session restrictions take precedence. Run each required gate
and report its actual independence or an unmet requirement.

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
```

<!-- PLATFORM:CODEX -->
**Codex:** Define TOML agents per env-compat.md patterns. Each agent runs in read-only sandbox.
<!-- /PLATFORM:CODEX -->

<!-- PLATFORM:CURSOR -->
**Cursor:** No agent dispatch. Execute each agent's analysis sequentially yourself, maintaining identical output format.
<!-- /PLATFORM:CURSOR -->

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:

1. **Count Consistency:** Total checks = sum of all dimension checks. No check counted twice.
2. **Score Math:** Recalculate overall from dimension scores * weights. Must match within 0.1.
3. **Critical Gate Completeness:** All 4 gates have explicit PASS/FAIL/INSUFFICIENT DATA with evidence.
4. **Evidence Completeness:** Every FAIL finding has file:line or INSUFFICIENT DATA note.
5. **Finding ID Uniqueness:** All stable IDs (`{dimension}-{check_slug}`) are unique. No duplicates.
6. **Display ID Sequence:** F-IDs are sequential (F1, F2, ...) with no gaps or duplicates.
7. **JSON Schema Compliance:** Output matches `audit-output-schema.md` v1.1 with `"skill": "geo-audit"`.
8. **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.

Point `--files` at the report you just wrote — a literal `[date]` placeholder matches nothing and burns the pass.

```bash
# The report auto-increments (-2.md, -3.md) on same-day reruns — take the newest match.
REPORT=$(ls -t zuvo/audits/geo-audit-$(date +%F)*.md 2>/dev/null | head -1)
[ -n "$REPORT" ] && ~/.zuvo/adversarial-review --mode audit --files "$REPORT" \
  || echo "adversarial: report not found — write it first"
```

If `adversarial-review` is not in PATH: `~/.zuvo/adversarial-review` (stable; the versioned cache path breaks after any release)

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:
1. Quick Wins first (Priority >= 2.0 AND Effort = EASY)
2. 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

```bash
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:

```json
{
  "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`):

```bash
printf '%b\n' "$RUN_LINE" | ~/.zuvo/append-runlog
```

Run: <ISO-8601-Z>	geo-audit	<project>	<N-critical>	<N-total>	<VERDICT>	-	<N>-dimensions	<NOTES>	<BRANCH>	<SHA7>	<INCLUDES>	<TIER>


### 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).

Files in this skill

  • SKILL.md33.3 KB
  • agents/geo-content-signals.md23.2 KB
  • agents/geo-crawl-access.md14 KB
  • agents/geo-schema-render.md13.8 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.