# SEO Audit — Executable Handover & Runbook

**Playbook release: `2026-11-06.1`** — [verify the live release](https://seo-audit-site.vercel.app/handover/release.json).
**What this file is.** This is a *stateful runbook* — both the instructions for the audit and its running record. It is meant to be opened by a **local, agentic coding tool** (Claude Code, Cursor, or OpenAI Codex) that will actually run the SEO audit — collecting data, running tools, and writing the report — pausing to ask you (the human) only when it hits something it genuinely can't do alone.

> **Will this run in your setup? (read this first)** You need a **coding tool that runs on your own machine** — **Claude Code, Cursor, or OpenAI Codex** (per-tool setup in §1.1). It does **not** run inside a browser chat tab (claude.ai, chatgpt.com) or an app-builder (Lovable): those can't run the local crawl or drive your logged-in browser. **No coding tool installed, and don't want one?** Use the in-browser **SEO Audit Report Builder** on the website instead — it needs nothing installed.

It is **not** the reference manual. The full rationale — *why* each step matters, the data-maturity gating, the report-design philosophy, confidence calibration — lives in **`seo-audit-methodology.md`** in this same folder. This file is the **how + where-are-we**. Consult the methodology for the *why*.

The trick that makes this work: **this file carries the audit's state.** At the start of each session the assistant reads the Progress Ledger to learn where things stand, does the next piece of work, and writes its progress *back into the Ledger* before it stops. That's what lets a long audit survive a context reset — or resume the next day — without re-explaining anything.

**Diagnosis and action are deliberately separate documents, generated by one runbook.** This runbook first audits the site and finishes `<client-slug>-seo-audit-YYYY-MM-DD.md`; only after the report passes QA does it generate `<client-slug>-seo-action-runbook-YYYY-MM-DD.md` beside the report. The action document carries every recommendation, explains how the AI can help, requires current best-practice research, and manages recurring implementation and measurement. The audit never changes the site, and generating the action document never implies permission to begin implementation.

### Deliverable naming convention

Never use generic `REPORT.md`, `ACTION-RUNBOOK.md`, or `GROWTH-PLAN.md` names for new work. Use `<client-slug>-<document-purpose>-YYYY-MM-DD.md`, with the date set to the run date. Existing generic files may be read for compatibility, but every new audit and re-audit must use the dated client-specific names.

---

## 0. How to operate this runbook (read this first, every session)

> ### 🎯 PRIME DIRECTIVE #1 — finish the report before generating the action runbook; never implement during the audit.
> The delivery package contains two client-specific documents: `REPORT_FILE` and `ACTION_FILE` (resolved using the naming convention above). Until `REPORT_FILE` exists and passes the QA gate (§5.4), the report is the **only** document you work on—finishing it always takes priority. After QA passes, generate `ACTION_FILE` from the finished report using §5.5. Do not start any recommended action.
> - **Do NOT implement fixes, edit the user's code/site, "set things up", deploy, or change external accounts during the audit.** The audit produces `REPORT_FILE`, then `ACTION_FILE`, plus evidence files under `./audit-data/`. If you find yourself changing the site instead of documenting work, stop and return to the report.
> - **Recommendations are written, not performed.** "Set up GA4", "add Product schema", "fix the 404 bug" all go *into the report* as prioritized actions. You write them down; you do **not** do them now. Performing actions live is exactly what derails the audit and stops the report getting written.
> - **Don't get pulled into a side conversation.** If the human chats or asks something mid-audit, answer in a sentence or two, then immediately return to the next Ledger item. Politely steer back: "I'll note that — let me keep going on the report."
> - **A missing setup is a finding, not a dead end.** If something isn't set up (GA4, conversion tracking, GSC), don't skip it and don't fix it live — record it as a **Tier 1 / Week 1** recommendation in the report with beginner-friendly setup steps (see §3 fallbacks and §5).

> ### ⚠️ PRIME DIRECTIVE #2 — update this file as you go, not at the end.
> **This file is your only memory.** You *will* lose context — a context compaction, a fresh session, or picking this back up another day. Anything not written into the Ledger is gone, and the next session starts blind.
> - **A step is not finished until its data is saved *and* its Ledger row is updated.** Treat the file-write as the final action *of* the step, not a chore for later.
> - **Never complete two steps in a row without writing back.** No batching updates to the end.
> - **After each update, say so in chat** (e.g. "Ledger: item 4 → ✅") so the human can see the state is current.
> - **If you ever notice you're several steps ahead of the Ledger, stop and reconcile it before doing anything else.**
> If the human ever has to remind you to update this file, the runbook has failed — don't make them.

If you are the AI assistant driving this audit, follow this loop:

0. **At every session start, verify the release and complete skill installation using §1, including for an audit already in progress.** Record the file path, release, checker result, and selected skill paths in §2. Never substitute old registered skills for the published variants.

0a. **If the Ledger header (§2) is still unset, this is a fresh audit — do the Kickoff & Preflight (§1.4–§1.5) first.** Verify the required tools are installed (hand the human install commands for any that aren't), then ask the kickoff questions in one message. Start available collection once the client / site / stage and Squirrel Scan are confirmed; use the missing-browser fallback in §1.4 for authenticated/rendered evidence.
1. **Read the Progress Ledger (§2).** It is the single source of truth for what's collected, what's pending, and what's blocked waiting on the human. Do not re-do work already marked ✅.
2. **Read the "Blockers / waiting on user" queue (§2.3).** If the human has just answered or supplied a file that unblocks an item, action that first.
3. **Pick the next ⏳ / ⬜ item** in phase order (Phase 1 → 2 → 3). Don't start Phase 2 skills until Phase 1 data is collected; don't start Phase 3 synthesis until the skills are run. (See methodology Phase 2 / Phase 3 gating.)
4. **Attempt it using the method in §3–§5.** Most data collection is done by driving the browser with Playwright against the human's already-logged-in session, or by running a local CLI.
5. **On success:** save the data to the audit-data folder (§1.3), then **immediately** update the Ledger row to ✅ with the file location and a one-line note — *before* you touch the next item (see Prime directive). Echo it in chat.
6. **On a blocker** (login wall, 2FA, a judgment call only the human can make, a file you can't reach): do **not** guess or fabricate. Set the Ledger row to 🙋, add a clear request to the Blockers queue using a Fallback Prompt (§6), tell the human in chat, and move on to the next item you *can* do. Come back when they've answered.
7. **Before you end the session:** complete the End-of-session Checkpoint (§7) — write your state back into this file. The next session resumes from exactly here.

**Seven standing rules** (do not violate):
- **Never call Ahrefs without per-client permission — connected is not authorised.** The Ahrefs MCP is billed to **one specific client's account**, but it is connected machine-wide, so it is reachable from every project on this machine. Its credits are somebody's money. **Before the first Ahrefs call of any audit**, ask (**F-AHREFS**, §6) and record the answer in the Ledger header. If the answer is no, or you haven't asked yet, use the free route (Playwright/GSC/Bing) and mark the Ahrefs enrichment ⏭️ with the reason. Never "just check" an endpoint to see if it works — a call is a charge. This applies to every Ahrefs endpoint: GSC history (§3.1), backlinks (§3.15), volumes, difficulty, and competitor gap (§1.1c).
- **Finish the report, generate the action runbook, but don't perform the work.** Capture every fix and missing setup in `REPORT.md`; after it passes QA, compile the recommendations into `ACTION-RUNBOOK.md`. Never edit the user's site/code, deploy, or run setup steps during the audit (Prime Directive #1).
- **Keep the Ledger live.** Update it after *every* item, not in a batch at the end — see the Prime directive. A stale Ledger loses work on any context reset and forces you to re-explain everything next session.
- **Never guess competitor names.** Ask the human and wait. A comparison page built on a wrong competitor is worse than none. (§3.8)
- **Research, don't assert; verify before you claim.** No content or keyword recommendation without real demand data (or an explicit "hypothesis — no volume pulled" label). Don't present derived numbers as platform totals, don't turn tiny counts into scary percentages, and check "missing / broken / fixed" against the live page before writing it. Use "consistent with" for patterns you can't confirm; don't cite unofficial thresholds as Google specs. (Full rules: §5.1 claims discipline.)
- **Trend before snapshot.** A 16-month aggregate hides a collapse that started in month 12 — the totals look healthy while the site is actively bleeding. Collect the monthly series (§3.1, §3.3) and run the Trend & Anomaly Scan (§5.1 step 1) before writing any verdict on a top page or query. "Why did traffic drop?" is the question every client eventually asks — the report must answer it before they ask it.
- **Squirrel Scan is mandatory before running any skills.** Without it, skill outputs are generic. (§3.5)

---

## 1. Setup (do once per machine, then check at session start)

### Install the playbook skills

These five skills are public on [GitHub](https://github.com/BigChrisCooke/seo-playbook-skills) and install through the [skills CLI](https://skills.sh/docs):

```bash
npx skills add BigChrisCooke/seo-playbook-skills --skill remove-ai-slop-without-removing-ctas-and-good-marketing-copy trend-scan claim-check ai-seo-better-than-corey-haines schema-markup-better-than-corey-haines
```

Select your agent in the installer (Claude Code, Cursor, or Codex). Installation is project-local by default; add `--global` for all projects. Requires Node.js/npm for installation and Python 3.10+ for the helpers; no pip packages or API keys are needed for these three scripts. Restart the agent session if the skills are not yet visible.

- `remove-ai-slop-without-removing-ctas-and-good-marketing-copy`: the conditional AI-written-page review, with its conversion guard and fallback prose rubric included.
- `trend-scan`: traffic direction and anomaly checks from exported CSVs before synthesis.
- `claim-check`: flag stale or undated claims before report delivery; the agent then verifies the primary sources.

### Skill names and existing audit files

Use the published skills below. Older audits keep their existing evidence filenames; these filenames are storage locations, not skill commands.

| Invoke | Source | Save audit evidence as |
| --- | --- | --- |
| `/ai-seo-better-than-corey-haines` | [GitHub](https://github.com/BigChrisCooke/seo-playbook-skills/tree/main/skills/ai-seo-better-than-corey-haines) · [skills.sh](https://skills.sh/BigChrisCooke/seo-playbook-skills/ai-seo-better-than-corey-haines) | `skills/ai-seo.md` |
| `/schema-markup-better-than-corey-haines` | [GitHub](https://github.com/BigChrisCooke/seo-playbook-skills/tree/main/skills/schema-markup-better-than-corey-haines) · [skills.sh](https://skills.sh/BigChrisCooke/seo-playbook-skills/schema-markup-better-than-corey-haines) | `skills/schema-markup.md` |
| `/competitors` | [GitHub](https://github.com/coreyhaines31/marketingskills/tree/main/skills/competitors) | `skills/competitor-alternatives.md` |

Corey's comparison-page skill is now named `competitors`. Install it and the other two upstream dependencies separately:

```bash
npx skills add coreyhaines31/marketingskills --skill programmatic-seo site-architecture competitors
```

If using the standalone runbook without our optional orchestrator, also install `seo-audit` from that upstream repository. If using our orchestrator, it supplies that checklist itself; do not replace it with the upstream skill of the same name.

Before analysis, resolve each required skill by its published name. If one is missing, link its source above and provide the install command; mark it blocked until installed. Do not silently use an older local `ai-seo` or `schema-markup` copy. If an upstream skill cross-references those older names, route AI visibility and structured-data work through the published variants above. Use the runbook's audit-only scope and verification rules throughout.

### Verify this release and the complete installation

**Playbook release: `2026-11-06.1`.** The current [release manifest](https://seo-audit-site.vercel.app/handover/release.json) lists SHA-256 hashes and byte counts for the clean downloads. At session start, read this file from disk and report its absolute path and release. Compare with the live manifest; do not infer freshness from a download timestamp or file size. A working copy's hash will change when its Ledger is edited. Preserve that Ledger when upgrading instructions, and record the new release only after applying them.

For Claude Code, run these commands **from the audit project folder**. They install complete packages into that project. For Codex or Cursor, replace `claude-code` with `codex` or `cursor`.

```bash
npx --yes skills add BigChrisCooke/seo-playbook-skills --skill claim-check trend-scan remove-ai-slop-without-removing-ctas-and-good-marketing-copy ai-seo-better-than-corey-haines schema-markup-better-than-corey-haines --agent claude-code --copy --yes
npx --yes skills add coreyhaines31/marketingskills --skill programmatic-seo site-architecture competitors --agent claude-code --copy --yes
```

For the standalone runbook, also run `npx --yes skills add coreyhaines31/marketingskills --skill seo-audit --agent claude-code --copy --yes`. Skip that command if using our optional orchestrator, which supplies the checklist under the same name.

Download [check-skills.py](https://seo-audit-site.vercel.app/skill-pack/check-skills.py) into the audit project, then run:

```bash
python check-skills.py --project . --standalone
```

Omit `--standalone` when using our optional orchestrator. The checker performs no installs or network requests. It checks all five public packages against the published file hashes, verifies the three upstream entrypoints and their referenced resources, and runs the bundled Python helpers with `--help`. Missing scripts, changed package contents, or conflicting installed copies are reported with paths. `--skills-dir PATH` restricts checking to an explicitly selected skills directory; only use it after confirming that the agent will load that directory.

Save its JSON output as `audit-data/<client>/skill-preflight.json`. Record the release, selected skill paths, hashes, results, and fresh-session confirmation in the Ledger. A successful disk check does **not** prove that a running agent has refreshed its registered skill instructions. After installing or changing skills, checkpoint and start a fresh agent session, then confirm the selected paths before invoking them. Do not report an update as applied merely because a command ran.

The old editor name `edit-conversion-safe-blogs` maps to `remove-ai-slop-without-removing-ctas-and-good-marketing-copy`; use the published name. Use the two published analysis variants instead of old local `schema`, `schema-markup`, or `ai-seo` copies. `schema-detector` is not required: the published schema variant plus the two validators cover that step.

**Check delivery dependencies at the start:** a missing `claim-check` package or script blocks the delivery QA gate. Record it immediately, install the complete package, and verify it before marking delivery ready. Independent data collection may continue while it is blocked. Manual trend analysis must be labelled manual; it does not establish that `trend-scan` is installed. The conditional editor still requires its complete package when AI-written pages are in scope.

**SXO and drift setup:** a skill entrypoint alone is insufficient. If using `seo-sxo`, add `--with-sxo` to the checker; if capturing or comparing with `seo-drift`, add `--with-drift`. Use both flags when running both, and pass `--seo-bundle "<bundle>"` with the complete upstream repository root. The checker verifies the selected skill, the shared `fetch_page.py`, `parse_html.py`, and `render_page.py` files, and (for drift) `drift_baseline.py` and `drift_compare.py`. It checks managed runtime and Chromium readiness, then starts the scripts with `--help` in that runtime. It does not fetch pages or prove that a baseline was saved.

Obtain the complete shared script bundle from [the upstream repository](https://github.com/AgriciDaniel/claude-seo), following its current installation instructions, and record the source revision. Do not assume a skill-only CLI install includes repository-level scripts. Keep `requirements.txt` at the **repository root**, alongside `scripts/`; copying only `scripts/` breaks managed setup. Install the selected skill's complete folder (including references) for your agent and resolve its commands to this same bundle.

Replace `<bundle>` below with the real repository path. These Python entrypoint commands work in PowerShell and POSIX shells; the upstream Bash launcher `<bundle>/scripts/claude-seo setup` and `doctor --json` are alternatives when Bash is available:

```bash
python "<bundle>/scripts/runtime.py" setup
python "<bundle>/scripts/runtime.py" doctor --json
python check-skills.py --project . --standalone --with-sxo --with-drift --seo-bundle "<bundle>"
```

Omit `--standalone` when using our orchestrator, and omit the optional flag for any step not being run. Require **both** `ready: true` and `browser_ready: true`; a zero doctor exit code alone can still mean Chromium is missing. Use `python "<bundle>/scripts/runtime.py" run <script.py> ...` for subsequent commands so they use the managed environment, rather than the system Python. Then verify an actual rendered fetch on a target page (`run render_page.py <url> --mode always --json`): check its status, error, `mode_used`, and content. Preflight success does not substitute for this evidence.

If setup is incomplete, record the affected step as unavailable and continue independent audit work. A manual SERP/page-type review is a separately labelled fallback; for CSR/hybrid/unknown pages it needs rendered evidence, not just response HTML. Even a static page requires an explicit response-versus-rendered comparison before treating response HTML as sufficient. A hand-built JSON snapshot is manual evidence, **not** a completed `/seo-drift` baseline; retain the setup action for the next cycle.

For a manual install, copy each whole skill directory, including its scripts and reference files. These skills do not depend on a particular user's home folder. Browse the [skills.sh collection](https://skills.sh/BigChrisCooke/seo-playbook-skills) or [installation guide](https://github.com/BigChrisCooke/seo-playbook-skills#install).

### 1.1 Tools you'll need

Run this in any **local, agentic coding tool** — **Claude Code, Cursor, or OpenAI Codex**. All three run terminal commands, edit files, and support the Playwright browser tool over MCP. Pick one; everything from §2 onward is identical. You'll need:

- **A browser tool (Playwright)** — lets the assistant drive a real browser to read on-screen GSC/GA4 tables and trigger exports against the human's logged-in Google session. Neither Claude Code nor Cursor has a built-in browser that does this, so **Playwright is the standard choice for both** — it reads the page's *accessibility tree*, which is exactly what the table-reading steps in §3 rely on. Set it up per your surface in §1.1a / §1.1b below. *(Token-saving option: Microsoft's standalone **Playwright CLI** does the same job at roughly a quarter of the tokens of the MCP — useful because GSC/GA4 tables are large. If it's installed, prefer driving the browser through it; otherwise the MCP is fine.)*
- **Squirrel Scan CLI** — `squirrel audit --help` should work. Mandatory. If missing, install from **https://squirrelscan.com/download** (mac/linux: a `curl … | sh` one-liner; Windows: a native installer). It is **not** on npm — `npx @andreasgrafen/squirrel` will 404. Repo for reference: https://github.com/squirrelscan/squirrelscan
- **SXO/drift runtime** (only for steps being run): use the complete shared script bundle and managed `setup` / `doctor --json` commands in §1. Do not install a hand-picked subset of Python packages.

> The assistant **cannot add an MCP server to itself mid-session** (any of them). If the browser tool isn't connected, give the human the setup steps below and ask them to reload, then continue.

#### 1.1a Setup — Claude Code
- **Install:** https://code.claude.com/docs (or `npm install -g @anthropic-ai/claude-code`). Run it in a terminal, or inside VS Code / JetBrains via the extension — same tool.
- **Add Playwright:**
  ```
  claude mcp add playwright npx @playwright/mcp@latest
  ```
  First run downloads a browser; if prompted, also `npx playwright install chrome`. Docs: https://github.com/microsoft/playwright-mcp
- **Begin:** open Claude Code in your working folder and say *"Follow seo-audit-runbook.md and begin the audit."*

#### 1.1b Setup — Cursor
- **Install** the Cursor app: https://cursor.com . Its Agent runs terminal commands and MCP tools (with approval), so it can do everything this audit needs.
- **Add Playwright** by creating **`.cursor/mcp.json`** in your working folder (or `~/.cursor/mcp.json` for all projects):
  ```json
  {
    "mcpServers": {
      "playwright": { "command": "npx", "args": ["@playwright/mcp@latest"] }
    }
  }
  ```
  Reload Cursor, then confirm `playwright` shows under **Settings → MCP** as Available. First run downloads a browser; if prompted, `npx playwright install chrome`.
- **Begin:** open the folder, open the Agent (Cmd/Ctrl+I), and say *"Follow seo-audit-runbook.md and begin the audit."*
- **Recommended (keeps the agent on track — not a hook, just persistent context):** drop an **`AGENTS.md`** in the working folder with these rules. Cursor injects it into *every* agent request, so the agent doesn't drift off-task or forget the rules between turns:
  ```
  # SEO audit — rules for this folder
  - Follow seo-audit-runbook.md. Read its §2 Ledger first; keep it updated as you work.
  - The ONLY deliverable is REPORT_FILE. Do not fix anything, edit code, or create other
    files during the audit — write every fix and missing setup as a recommendation in the report.
  - Don't get pulled into side chats. Answer briefly, then return to the next Ledger item.
  - Ask the kickoff questions (competitors, target persona) and wait for answers before starting.
  - The audit isn't done until REPORT_FILE exists and passes the §5.4 QA gate.
  ```
  (A `.cursor/rules/seo-audit.mdc` with `alwaysApply: true` and the same content works identically.) This is the most reliable way to stop the agent wandering — strongly recommended on Cursor.

#### 1.1c Setup — OpenAI Codex
- **Install** the Codex CLI: `npm install -g @openai/codex` (docs: https://developers.openai.com/codex). It runs locally and executes terminal commands (with approval), so it can do everything this audit needs.
- **Add Playwright:**
  ```
  codex mcp add playwright npx "@playwright/mcp@latest"
  ```
  (Or add it under `[mcp_servers]` in `~/.codex/config.toml`.) Start Codex and run `/mcp` to confirm `playwright` is connected. First run downloads a browser; if prompted, `npx playwright install chrome`.
- **Begin:** open Codex in your working folder and say *"Follow seo-audit-runbook.md and begin the audit."*

### 1.2 Login state
GSC and GA4 require the human to be logged into the right Google account in the browser Playwright controls. **Do not handle the human's password or 2FA.** When a login wall appears, pause and ask the human to log in in the open browser window, then continue. (§6, prompt F-LOGIN.)

**CAPTCHAs and "unusual traffic" pages work the same way.** If any site in the browser shows a CAPTCHA, a reCAPTCHA, Google's "sorry / unusual traffic" page or an HTTP 429 challenge, **stop and ask the human to solve it in the open browser window** (§6, prompt F-CAPTCHA), then carry on from where you stopped. Do not give up on the step, switch to a manual fallback, or hand the work back as a to-do list without asking first. Do not try to solve or get round a CAPTCHA yourself. Slow down afterwards (one request at a time, a few seconds apart).

**Downloads are allowed.** When a tool offers an export (Search Console, GA4, Google Ads, validators), download the file and save it in the data folder (§1.3) rather than reading rows off the screen. Note where the browser saved it and move or copy it into the data folder. If the download can't be triggered under automation, ask the human to click it (§6, prompt F-FILE).

### 1.3 Where collected data is stored
Create and use this folder (relative to the working repo, or wherever the human prefers — record the path in the Ledger):

```
./audit-data/<client-slug>/
  site-profile.md            stack, scale, rendering, markets, site-type modules + change/deploy path
  gsc-risk-freshness.md      Manual Actions, Security Issues, 24-hour preliminary view + incident check
  gsc-performance.csv        gsc-queries + pages export (or a notes file if read on-screen)
  gsc-indexing.md            the four "not indexed" counts + URL lists
  gsc-search-appearance.md   enhancement status + Search Appearance performance baseline
  ga4-landing-pages.csv      landing pages + conversions
  ga4-channels.md            direct/organic sessions + engagement, property name
  ga4-conversion-paths.md    blog/content → product / demo / trial ranked lists (§3.3)
  psi-<page>.json            raw PageSpeed Insights API response per page
  squirrel-report.txt        Squirrel Scan llm-format output
  manual-audits.md           per-page view-source notes, including Open Graph share metadata
  js-render-parity.md        conditional response HTML vs rendered DOM vs Google-view comparison
  serp/<keyword>.md          SERP composition + page-type classification per keyword
  competitors.md             confirmed competitor names (human-supplied)
  robots-sitemap.md          robots.txt contents + sitemap structure
  schema-validation.md       Rich Results + schema.org validator output, 3 URLs × 2 tools
  ai-search-opportunities.md fixed prompt sample, observed searches/citations, gaps + validation
  ai-search-benchmark.md     repeatable monthly AI-search visibility baseline
  internal-search-check.md   conditional indexability + harmless reflected-input check
  change-events.md           dated site/deploy/content/analytics/Google events used for causal context
  template-root-causes.md    finding groups mapped to shared template/component/plugin fixes
  content-inventory.md       evidence-based protect/refresh/merge/prune decision register
  crawl-stats.md             conditional GSC Crawl Stats baseline + crawl-constraint verdict
  server-log-analysis.md     conditional Googlebot log evidence for large/constrained sites only
  backlink-baseline.md       GSC sample + Bing where available (Ahrefs only if authorised 🔒); risk/opportunity, never auto-disavow
  serp-history-panel.md      fixed query/competitor/page-type sample for repeatable cycle comparison
  implementation-adapter.md  conditional CMS/framework/version mapping + build/deploy/live QA
  content-briefs/<id>.md     evidence-backed brief for each approved new/refresh/consolidation action
  skills/<skill>.md          each skill's saved output
  <client-slug>-seo-audit-YYYY-MM-DD.md             final synthesized report (Phase 3 output)
  <client-slug>-seo-action-runbook-YYYY-MM-DD.md    client-specific recurring execution document
```

Record the chosen `<client-slug>` and absolute path in the Ledger header so the next session finds it.

### Missing browser or PageSpeed quota: continue with explicit limits

Playwright is one browser option, not a global audit gate. Test whichever browser capability the agent actually has; if none can access authenticated tools, request GSC/GA4 CSV exports through F-FILE and continue public crawl, source, sitemap, and supplied-file analysis. Record browser-only/rendered checks as unavailable until evidence arrives. Do not infer a rendering pass from raw HTML. Tool unavailability is a limitation of this audit environment, not evidence that the client's website is broken.

For PageSpeed HTTP 429, record the response and URL/strategy, check for an already configured `PAGESPEED_API_KEY` or `GOOGLE_API_KEY` without displaying either value. Prefer the dedicated PageSpeed key; if absent or unusable, try the existing Google key once. Pass the selected value as the API key and verify the response: its environment-variable name does not guarantee that PageSpeed is enabled, permitted by key restrictions, or within quota. Redact keys from commands, URLs, logs, and saved evidence. Do not repeatedly call the same anonymous endpoint. A new key is optional and does not guarantee quota. Try a human-supplied PageSpeed report or local Lighthouse when Chrome and the runtime are available; neither requires Playwright. Keep Lighthouse lab measurements separate from field Core Web Vitals. If no route is available, record Performance as **not measured** and continue the audit. Ask once for the missing evidence; do not wait indefinitely or create cloud accounts during the audit.

A report may be delivered with these explicitly documented evidence gaps once the applicable analysis and claim-check QA are complete. Mark affected collection rows unavailable with their reason and collection action; do not mark them passed. With missing performance or other category evidence, show measured categories but **withhold the overall 0–100 Health Score** until its inputs exist. Do not substitute zero, invent a score, or silently redistribute weights. This evidence-gap rule takes precedence over instructions elsewhere to display a total score or wait for every collection row.

Sources: [PageSpeed API setup](https://developers.google.com/speed/docs/insights/v5/get-started) and [Lighthouse execution options](https://developer.chrome.com/docs/lighthouse/overview).

### 1.4 Preflight check (run at the start of every session)
First perform the release and complete-package checks in §1. Local installation of the audit dependencies in the audit project is setup for this workflow; it does not authorize changes to the audited site or external accounts.

Before collecting anything, confirm the toolchain and hand the human install commands for whatever's missing (use **F-TOOL**):
- **Browser tool available?** Test an available browser capability. If none works, record it and use manual exports; Playwright installation is an option, not a requirement to continue independent work.
- **`squirrel audit --help`** runs?
- *(Only if running SXO/drift)* Run the §1 checker with `--with-sxo` and/or `--with-drift`, plus `--seo-bundle "<bundle>"`. Require managed runtime and Chromium readiness, script startup, and an actual rendered fetch. If incomplete, record that step as unavailable; continue independent work.

Squirrel Scan remains the crawl prerequisite. A missing browser does not block independent Phase 1 work: use the explicit fallback above, request exports once, and record unavailable evidence. PageSpeed may also be unavailable; use the quota fallback without blocking the remaining audit.

### 1.5 Kickoff questions (ask all of these up front, in one message)
Ask these at the very start — so the human can answer while you work, and so the human-only blockers surface early instead of mid-flow. **Ask them in plain, everyday English — the person answering may not be technical.** Don't use words like "slug", "property", "persona", "TOFU", or "post-migration" *in the question itself*. Each item below gives you **what to actually say** (in quotes) and **what you need it for** (so you know how to use the answer). Work out the technical bits yourself from their plain answers — don't make them do it.

**Before you ask, do a quick check for prior audits.** List `./audit-data/` and look for sibling `seo-audit-runbook-*.md` files in the working folder. If you find any, ask the human first — in plain English — *"I can see you've already audited [site A]. Is the new site related to it — e.g. the same company's blog, documentation, a regional version, a sister brand, or a domain move?"* If yes, **pre-fill** the new run's persona, confirmed competitors, and any TOFU benchmark from the related audit's Ledger header and `competitors.md`, then in your kickoff message **confirm each pre-filled value** rather than re-asking blind ("I've carried over your customer description and competitors from the [A] audit — keep, edit, or replace?"). If they say no relation, run the kickoff clean. If no prior audits exist, skip this step.

**Build the Site Profile before Phase 1.** Inspect the live site and available repository/configuration first; do not make the human answer facts you can establish yourself. Save `site-profile.md` with: approximate indexable URL count; CMS/framework/hosting; response mode (static/SSR/CSR/hybrid/unknown); source/CMS access; deploy path; target countries/languages; ecommerce/local/publisher/image/video/internal-search flags; server-log availability; connected SEO tools; and the modules activated below. Ask one short follow-up only for material unknowns. The profile is a router, not a finding:

- **JavaScript-dependent or uncertain rendering** → run the §3.6 response-versus-rendered module.
- **Structured-data types present or recommended** → run the §3.10 Search Appearance outcome baseline.
- **Public internal search** → run §3.12.
- **Existing content or any refresh/merge/prune recommendation** → run the §3.13 content lifecycle decision protocol.
- **Roughly ≥1,000 URLs or direct evidence of crawl constraint** → inspect §3.14 Crawl Stats and request server logs only if that evidence warrants them; small healthy sites mark log analysis N/A.
- **GSC available** → create the §3.15 Links sample baseline; enrich with Bing when available, and with Ahrefs **only when the Ledger header records it as authorised for this client** (🔒 §0).
- **Confirmed competitors plus validated target queries** → create the fixed §3.16 SERP history panel.
- **Source/CMS access plus an identifiable stack and an implementable recommendation** → activate the §3.17 CMS/framework implementation adapter; no access/action means reasoned N/A.
- **Multiple languages/regions, ecommerce, physical/service locations, media-heavy publishing, or ≥1,000 URLs** → record the applicable Wave 3 conditional pack; do not force generic checks into the core audit.
- **No applicable condition** → mark that module N/A with the reason rather than manufacturing a section.

1. **Say:** *"What's the link to your website?"*
   **You need:** the site URL. Derive the client name and a short `<client-slug>` (lowercase, hyphenated, e.g. `acme-co`) from the domain yourself — **don't ask the human for a "slug"** or a "client name" separately; the link is enough.
2. **Say:** *"Roughly how long have you had this website — did you just launch it, recently rebuild or move it to a new address, have it a while and still growing, or has it been well-established for years?"*
   **You need:** the site stage (new / post-migration / establishing / mature). This gates what's worth running (see methodology *Data Maturity*; e.g. hold `/seo-audit` on a brand-new site with no GSC history).
3. **Say:** *"Who are your main competitors? Share 2–3 names or links to businesses you'd compare yourself to."*
   **You need:** confirmed competitor names. **Human-only — never guess or infer from search results.** This unblocks `/competitors`. Asked now, not later. *(If pre-filled from a related audit, ask the human to confirm or edit instead.)*
4. **Say:** *"Do you use Google Analytics on this site? If so, what name shows up for it in your Analytics account?"* (If they're not sure, that's fine — move on.)
   **You need:** the GA4 property name, so you can check it actually matches the site (§3.3). If they don't have Analytics or don't know, that's not a blocker — it becomes a "set this up" recommendation in the report (§3.3).
5. **(Usually don't ask.)** Where to save the files you collect. Default to `./audit-data/<client-slug>/` silently. Only ask — in plain terms, e.g. *"Is it OK if I save my working files in a folder called `audit-data` here?"* — if you can't write there or the human cares where it goes.
6. **Say:** *"Who are you trying to reach with your website? Describe your ideal customer — their job or role, their industry, and what they're trying to get done."*
   **You need:** the target persona(s). **Do not guess this.** The Phase 3 content recommendations depend on it; if the human isn't sure, help them articulate it, but get their explicit confirmation before relying on it. *(If pre-filled from a related audit, ask the human to confirm or edit — related sites often share customers but not always; a company's docs site serves engineers even if the marketing site serves buyers.)*
7. **Say:** *"Have there been any big website changes during the period we're reviewing—such as a redesign, domain move, CMS change, large content release, analytics change, or major deployment? Approximate dates are fine."*
   **You need:** the initial entries for `change-events.md`. Also inspect repository/deployment history and later add relevant Google Search incidents or ranking updates. An unanswered question is not a blocker; record `unknown` and do not invent a timeline.
8. **Say:** *"Have you used AI to write any of your pages?"*
   **You need:** a yes/no answer and, when the answer is yes, the URLs or source files for the AI-written pages. This activates the conditional `/remove-ai-slop-without-removing-ctas-and-good-marketing-copy` review in Phase 2. If the human knows AI was used but cannot identify every page, record that limitation and build a representative sample from the highest-traffic page, highest-converting page, and each materially different content template. Do not use an AI detector to guess authorship.
9. **Say (only if the Ahrefs MCP is connected):** *"I can see an Ahrefs connection on this machine. It bills to a specific client's account, so I won't touch it without a yes — **do you want me to use Ahrefs for this audit?** If not, I'll use the free route and the audit still completes; I just won't be able to quote market search volumes."*
   **You need:** an explicit yes or no, written into the Ledger header (**Ahrefs authorised for THIS client?**). **Unset means no.** Ask this at kickoff even though it's rarely needed until §3.1/§3.15 — the whole point is that it is answered *before* anything is tempted to call an endpoint mid-flow. If the answer is no, mark the Ahrefs enrichment on Ledger item 15 as ⏭️ now, so no later step re-litigates it. If the human is unsure who pays, treat that as a no and move on; you can always ask again later, but you cannot un-spend a credit.

**As soon as the first four answers are in, open Google's tools with the available browser, or request manual exports if no browser is available. Continue independent data collection.** This is the biggest time-saver in the audit: Google login is interactive (the user has to do it), so surfacing it now means they're logging in while you're crawling — not sitting idle watching you run Squirrel Scan.

- **Google Search Console** — open for any site that isn't brand-new with zero history. Navigate to `https://search.google.com/search-console/` and tell the user: *"I've opened Google Search Console in a browser — please log in if you haven't already, then select the property for [site URL]. Just say 'logged in' when you're there and I'll start reading the data."* Set items 1–2 in the Ledger to 🙋 and add to §2.3: *"Waiting on GSC login — blocks items 1 and 2."*
- **Google Analytics** — if Q4 answer confirms they have GA4 (i.e., not "no" / "don't have it" / "not sure"). Navigate to `https://analytics.google.com/` (open in a new tab in the same browser) and say: *"I've also opened Google Analytics — please navigate to the [property name] property and let me know when you're in."* Set item 3 to 🙋 and add to §2.3: *"Waiting on GA4 login — blocks item 3."*

**Then keep working in the background.** Don't wait for the login confirmation before proceeding. Ask questions 5–8 in the same follow-up message (or inline with the login request), run the Preflight (§1.4), kick off Squirrel Scan (§3.5), fetch PSI data (§3.4), grab robots.txt + sitemap (§3.9), and run manual page audits (§3.6) — none of these need a Google login. Come back to GSC (§3.1–§3.2) and GA4 (§3.3) the moment the user says they're in.

**This is a hard gate, not a formality.** Do not start collecting data until the human has actually answered — especially **#3 competitors** and **#6 persona**. These are not optional, and you must not silently proceed without them (skipping them is what produces a generic, low-value content section). Later, when you reach the content recommendations (§5.1 thin-site guard), you must *also* ask **what good top-of-funnel content looks like for that persona** — their real questions, the terms they'd search — before writing TOFU recommendations. Asking the persona at kickoff and *not* following up on TOFU at synthesis is a common miss — don't make it.

Record the answers in the Ledger header (§2) immediately, including the AI-written-pages answer, save `site-profile.md`, and start `change-events.md`. If this audit is related to a prior one, also record the relationship in the header (e.g. *"Related to: acme — parent company, same customer and competitors"*) so the next session knows where to look for shared context. Then begin the collection loop.

### 1.6 Auditing more than one site (multi-site setup)
This file is **both** the instructions and the running state for **one** audit — the Ledger header in §2 has one client, one site URL, one session log, one blockers queue. If you have **two or more sites**, do **not** reuse the same file — the Ledgers would clobber each other and you'd quickly lose track of which row referred to which site.

**Related vs unrelated matters for what carries over, not for file layout.** Either way, each site gets its own runbook copy + its own data folder (below). The difference is what gets pre-filled at kickoff: if the new site is related to one you've already audited (same company's blog/docs, sister brand, regional version, domain move), the agent should carry over the customer description, confirmed competitors, and any TOFU benchmark from the related audit, then ask you to confirm — see the pre-check at the top of §1.5. If unrelated, kickoff runs clean. Even when context is shared, don't share a Ledger across related sites — each site's data, indexing, and 90-day plan are still its own.

**One runbook copy per site:**

1. **Duplicate the file**, renaming the copy with a short site label — e.g. `seo-audit-runbook-acme.md`, `seo-audit-runbook-globex.md`. Keep them side by side in the same working folder. (Leave the original `seo-audit-runbook.md` as the pristine template you copy from.)
2. **In each copy**, clear the Ledger header (§2) back to a blank state and fill it in fresh on first run via Kickoff (§1.5). The session log (§2.5) and blockers queue (§2.3) should also start empty.
3. **Each site gets its own data folder** — `./audit-data/<site-label>/` — so the collected files stay cleanly separated as long as each runbook points at its own folder.
4. **Tell the agent which runbook to follow** at the start of every session — e.g. *"Follow seo-audit-runbook-acme.md and continue the audit."* The agent will read that file's §2 to pick up where it left off. Don't switch mid-session; finish or checkpoint (§7) the current site first.
5. **If you update the instructions** (§0, §1, §3–§7) in one copy, the others won't get the change automatically. Either propagate the edit by hand, or — easier — keep edits in the pristine template and re-copy only the §1–§7 instruction blocks across, leaving each site's §2 Ledger untouched.

The tradeoff: a small amount of duplication if the runbook instructions evolve, in exchange for never having to disentangle two sites' state from a single Ledger. The duplication cost is low because the instructions are stable once you've run a couple of audits; the disentanglement cost (if you tried to share one file) is high and error-prone.

---

## 2. PROGRESS LEDGER  ⟵ read first, update as you go

> **Client:** _(unset — ask human)_   **Site URL:** _(unset)_   **Data folder:** _(unset)_
> **Site stage** (new / post-migration / establishing / mature — gates what's worth running, see methodology Data Maturity): _(unset)_
> **Target persona(s):** _(unset — ask human, don't guess; see §1.5 #6)_
> **AI-written pages:** _(unset — ask human; see §1.5 #8. Record no, or yes + identified URLs/files and coverage limitations)_
> **Site profile / active conditional modules:** _(unset — see `site-profile.md`)_
> **Ahrefs authorised for THIS client?** _(unset — ask before any Ahrefs call; see §0 standing rules + F-AHREFS. Record `yes — approved by {who} on {date}`, or `no — use free route`. Unset means **not authorised**.)_
> **Runbook path / playbook release applied:** _(unset — read from disk; current template 2026-11-06.1)_
> **Skill preflight evidence:** _(unset — skill-preflight.json; selected paths + hashes + missing files)_
> **Agent session after installation:** _(unset — confirm a fresh session selected the verified skill paths)_
> **Last updated:** _(unset)_

Status key: ✅ done · ⏳ in progress · ⬜ not started · 🙋 blocked (needs human) · ⏭️ skipped (reason noted)

### 2.1 Phase 1 — Data Collection
| # | Item | Status | Location / note |
|---|---|---|---|
| 1 | GSC safety + freshness + Performance (**Manual Actions / Security Issues / preliminary 24h / status incident check**, then 16mo queries/pages + monthly trend) (§3.1) | ⬜ | |
| 2 | GSC Indexing — counts + URLs + **per-bucket diagnosis** (§3.2) | ⬜ | |
| 3 | GA4 (landing pages, channels, property check, **conversion paths blog→product/demo/trial — 16mo range, not GA4's default 7d**) | ⬜ | |
| 4 | PageSpeed Insights (homepage + top pages, mobile + desktop) | ⬜ | |
| 5 | Squirrel Scan crawl | ⬜ | |
| 6 | Manual page audits (5 pages, Open Graph) + conditional response/render parity | ⬜ | §3.6 — parity N/A only with reason |
| 7 | SERP captures + page-type classification | ⬜ | |
| 8 | Competitor names (human-confirmed) | ⬜ | |
| 9 | robots.txt + sitemap | ⬜ | |
| 10 | Schema validation + GSC enhancement/Search Appearance outcome baseline | ⬜ | §3.10 — F-ANSWER fallback if blocked |
| 11 | AI-search opportunity + citation sample (bounded; reuse the confirmed persona, no extra kickoff questions) | ⬜ | §3.11 — mark ⏭️ with a reason only when AI-assisted discovery is genuinely irrelevant |
| 12 | Internal-site-search indexability + reflected-input check (conditional) | ⬜ | §3.12 — mark ⏭️ N/A if the site has no internal search |
| 13 | Evidence-based content refresh / merge / prune register | ⬜ | §3.13 — no decision from age/traffic alone |
| 14 | Crawl Stats + conditional server-log analysis | ⬜ | §3.14 — logs only for large or genuinely crawl-constrained sites |
| 15 | Backlink baseline | ⬜ | §3.15 — GSC sample + Bing when available; **Ahrefs only if authorised in header (🔒)**; never auto-disavow |
| 16 | Fixed historical SERP panel | ⬜ | §3.16 — same queries/locale/device/page types each cycle |
| 17 | CMS/framework implementation adapter (conditional) | ⬜ | §3.17 — exact stack/version/files/build/deploy/live checks, or reasoned N/A |
| 18 | On-page baseline for the next cycle (`/seo-drift`) | ⬜ | §3.18 — produces no finding today; that is the point. Skipping it leaves the next audit with nothing to compare against |

### 2.2 Phase 2 — Skills (run Squirrel Scan first; run /seo-audit last)
| Skill | Status | Output saved to |
|---|---|---|
| /programmatic-seo | ⬜ | |
| /ai-seo-better-than-corey-haines | ⬜ | |
| /site-architecture | ⬜ | |
| /schema-markup-better-than-corey-haines | ⬜ | |
| /competitors | ⬜ | (needs Phase 1 Ledger item 8 confirmed) |
| /remove-ai-slop-without-removing-ctas-and-good-marketing-copy | ⬜ | conditional: run when kickoff §1.5 #8 = yes; otherwise mark ⏭️ with “AI not used” |
| /seo-audit | ⬜ | (run last) |

### 2.3 Blockers / waiting on user
> _(empty — add 🙋 items here with the exact ask and which Ledger row they unblock)_

### 2.4 Phase 3 — Synthesis
| Item | Status | Note |
|---|---|---|
| **Trend & Anomaly Scan** (§5.1 step 1) | ⬜ | direction (site + top pages/queries), trend breaks diagnosed, concentration risk, GSC alerts answered |
| **Change/event timeline reconciled** | ⬜ | deployments/content/analytics + Google incidents; causal claims calibrated |
| Collect all skill outputs into staging | ⬜ | |
| Deduplicate findings | ⬜ | |
| Cross-reference each rec against raw data | ⬜ | |
| **Group findings by root cause/template** | ⬜ | `template-root-causes.md`; one shared fix beats page-by-page repetition |
| **Content decisions reconciled** | ⬜ | every affected URL has a supported keep/refresh/consolidate/etc. decision; destructive routes gated |
| **Evidence-backed briefs compiled** | ⬜ | `content-briefs/`; one per approved new/refresh/consolidation recommendation |
| Assign tiers / impact / effort | ⬜ | |
| Build Conversion Architecture view (blog→product/demo/trial) | ⬜ | §5.1 step / §5.2 item — from `ga4-conversion-paths.md` |
| Write 90-day plan (before detailed recs) | ⬜ | |
| Answer the Acquisition Channel Questions (§5.7) | ⬜ | do near the END — needs all data sources in; feeds §5.2 item 14 |
| Write executive + leadership summary | ⬜ | reframing finding first (§5.3) |
| Assemble REPORT_FILE per template (§5.2) | ⬜ | |
| `/claim-check REPORT_FILE` run and every flagged line resolved | ⬜ | §5.4 — checked and dated, corrected, or cut |
| **Pre-delivery QA gate (§5.4) passed** | ⬜ | audit isn't done until this is ✅ |
| **Generate / reconcile ACTION-RUNBOOK.md (§5.5)** | ⬜ | only after REPORT QA; preserve prior action history on re-audits |

### 2.5 Session log (append one line per session)
> _(e.g. "2026-05-31 · Claude Code · collected items 4,5,9; item 1 blocked on GSC login → see Blockers")_

---

## 3. Phase 1 — Data Collection (executable steps)

For each item: **Goal → Method → Success criteria → Store → Fallback.** Prefer reading the rendered table via a Playwright accessibility snapshot (often easier than fighting export dialogs); fall back to triggering the CSV export and reading the downloaded file.

> **⚠️ Date range — always pull the longest window available (16 months for GSC, 16+ months for GA4 if retention allows).** A common Playwright trap: GSC defaults to a 3-month view and GA4 to **the last 7 days** — if you snapshot without explicitly changing the range first, you'll capture a tiny, recent slice and miss seasonality, trends, and most of the long-tail. **Before reading any GSC or GA4 table, set the date range to the maximum first, wait for the table to re-render, *then* take the snapshot.** If a longer window genuinely isn't available (new property, retention limit), record the actual range used in the file so downstream analysis isn't built on an assumed 16 months. Always state the exact range on every metric (claims discipline, §5.1).

### 3.1 GSC Performance
- **Goal:** a safety/freshness sweep followed by two historical views, **all mandatory when GSC is available**:
  1. **Search risk status:** Manual Actions and Security Issues, recorded as clear / affected / inaccessible. These are authoritative Google reports, unlike speculative crawler header warnings; an affected result is a Critical blocker and must be handled before ordinary optimization.
  2. **The preliminary 24-hour view:** hourly clicks and impressions compared with the previous 24 hours, explicitly labelled preliminary. Use it as an early-warning signal, never as finalized evidence or a sole reason to change/rollback the site.
  3. **The 16-month aggregate** — queries table + pages table (clicks, impressions, CTR, position), plus the aggregate totals + actual date range used. 16 months is the max GSC retains — always ask for the full window unless the property is younger than that.
  4. **The monthly trend series** — clicks / impressions / CTR / position **by month** for (a) the site total, (b) the top ~10 pages, (c) the top ~10 queries. The aggregate answers *how big*; the series answers *which direction* — and only the direction catches a live decline. A 16-month sum looks healthy even when the last five months fell off a cliff, and a 16-month average position blends a December #3 with a June #7 into a fine-looking 5.5. **Do not treat the aggregate as a substitute for the series.**

  Also capture any **alert / recommendation cards GSC itself displays** (e.g. *"Impressions down 58% for query X"*). Google flags drops for free — read them and record them verbatim.
- **Method:** Playwright → open the correct Search Console property. If a login wall appears → **F-LOGIN**.
  - **Risk first:** open **Security & Manual Actions → Manual actions**, then **Security issues**. Record the headline status and any issue type/sample URLs; do not browse a reported malware URL directly. If affected, preserve the Google evidence, flag the human, and route remediation/review as Red work in `ACTION-RUNBOOK.md`.
  - **Freshness next:** open Performance → Search results → **24 hours**, select hourly granularity, and compare with the previous 24 hours where available. Record the dotted/preliminary period and absolute clicks/impressions; check [Google's Search Status Dashboard](https://status.search.google.com/) plus [Search Console data anomalies](https://support.google.com/webmasters/answer/6211453) for overlapping incidents. Add relevant dated events to `change-events.md`. Do not diagnose seasonality or causality from two partial days.
  - **History after that:** open the Date picker and select "Last 16 months" — do not skip this step. GSC defaults to a shorter view; a snapshot taken before the picker is changed will miss most of the data. Wait for the table to re-render with the new range (the metrics totals should update), then enable the Queries tab and read the table; switch to the Pages tab and read it. (Or use the Export button and read the downloaded CSV — same rule: change the range *before* exporting.)
  - **For the monthly series:** the full-export ZIP's "Dates" CSV gives the daily site series (roll it up to months); for per-page/per-query movement use the date picker's **Compare** tab (last 6 months vs previous 6 months, and last 3 months vs same 3 months last year for seasonal sites) and read the delta columns. Either route is fine as long as the by-month movement of the site and the top pages/queries ends up in the file.
  - **Shortcut — 🔒 GATED, ask first:** if the **Ahrefs MCP** is connected *and* the Ledger header records Ahrefs as authorised for this client, its `gsc-performance-history`, `gsc-pages-history`, and `gsc-keyword-history` endpoints return the monthly series directly with **no login wall** — this also unblocks the whole item when the human can't do the Playwright login. **If the Ledger header is unset or says no, do not call these endpoints.** Ahrefs is connected machine-wide but billed to one client's account (§0 standing rules); emit **F-AHREFS**, set this row 🙋, and take the Playwright route meanwhile. A blocked login is not a reason to skip the gate — it is a reason to ask both questions at once. (Keywords Explorer volumes are *market* data — never a substitute for the client's own click history.)
- **Success:** Manual Actions and Security Issues recorded; preliminary 24-hour view labelled correctly and checked against Google-wide incidents; ≥ top 10–25 queries and pages captured with all five metrics, totals, **the exact date range used**, the by-month series, and GSC alert cards recorded (or "none shown"). If a shorter range was used because the property is newer, record that.
- **Store:** `gsc-risk-freshness.md` + `gsc-performance.csv` (or `.md` if read on-screen) + `gsc-trend.md` (the monthly series + alert cards — §5.1 step 1 reads this file). Append relevant incidents/updates to `change-events.md`.
- **Fallback:** **F-FILE** — request the 16-month exports plus screenshots/text of Manual Actions, Security Issues, and the 24-hour chart. If only the long export is supplied, mark risk/freshness evidence unavailable rather than assuming clear.
- **Primary guidance:** [Manual Actions](https://support.google.com/webmasters/answer/9044175), [Security Issues](https://support.google.com/webmasters/answer/9044101), and [preliminary/hourly Performance data](https://support.google.com/webmasters/answer/7576553).

### 3.2 GSC Indexing
- **Goal:** counts + URL lists **+ a concrete diagnosis of *why* each bucket is sitting there.** The counts alone are noise; the diagnosis is the value.
- **Method:**
  1. Playwright → GSC → Pages / Indexing report → open each "Not indexed" reason → read the count and export the URL list where available. Buckets to capture: Crawled–currently not indexed; Discovered–currently not indexed; Duplicate–Google chose different canonical; Page with redirect; Alternate page with proper canonical tag; Excluded by 'noindex' tag; Blocked by robots.txt; Soft 404; 404/Not found.
  2. **Then diagnose.** For each bucket with a non-trivial count, pick **up to 5 representative URLs** and run them through GSC's **URL Inspection** tool (Playwright → paste URL → read the live test + crawl history). Capture: last crawl date, Google-selected canonical vs declared canonical, rendered HTML differences, any indexability blockers (noindex, robots, redirect chain, soft 404). Cross-reference each URL against the Squirrel Scan report (§3.5) for the same path — Squirrel often surfaces the underlying code-level cause (missing canonical, JS-only content, thin body, parameter explosion).
  3. **Pattern-spot.** Write 1–2 sentences per bucket explaining the dominant cause (e.g. "Crawled–not indexed is dominated by paginated `/blog/page/N/` URLs with duplicate titles" or "Discovered–not indexed is mostly parameter URLs from faceted nav — crawl-budget waste"). A bucket with no diagnosis = work not done.
  4. **Choose the post-fix discovery route by eligible URL count.** Count only URLs that should genuinely be indexed after their causes are fixed—not redirects, duplicates, `noindex` pages, alternate canonicals, search/filter pages, or thin pages. If **1–10 eligible URLs** remain, the report may recommend individual URL Inspection requests for the highest-priority pages. If **more than 10** remain, the report must recommend an XML sitemap containing all eligible canonical URLs with truthful `<lastmod>` values; reserve individual requests for only the small highest-priority set. Carry the implementation steps into the generated `ACTION-RUNBOOK.md` using §5.5.
- **Success:** all bucket counts captured; URL lists saved if exportable; **a per-bucket cause statement written**, each grounded in URL Inspection output or a Squirrel finding; eligible URLs counted; and the report’s discovery recommendation follows the `1–10` versus `>10` route above.
- **Store:** `gsc-indexing.md` — structure as: bucket → count → sample URLs inspected → diagnosis → recommended fix lane (content / canonical / technical / leave alone).
- **Fallback:** **F-FILE** for the URL exports. If URL Inspection is rate-limited, capture as many as you can and note the limit in the file.

### 3.3 GA4
- **Goal:** property-name sanity check (does it match the site?), landing-pages report (sessions + conversions), Direct vs Organic sessions + Direct engagement rate (for the embedded-software Direct anomaly check — see methodology), **and conversion-architecture data: which content pages actually feed product / demo / trial conversions.**
- **Method:**
  1. **Set the date range to the longest available window FIRST — target 16 months, or the property's full history if shorter.** GA4 reports default to **the last 7 days**, which is useless for an audit. Open the date picker in the top-right of the report and pick a custom range running back 16 months (or until the property's start date). Wait for the chart and table to re-render before snapshotting. Record the actual range used.
  2. Playwright → GA4 → Reports → Engagement → Landing page; with the date range set, read the table.
  3. Acquisition → Traffic acquisition for channel sessions + engagement rate (same long range). Note the property name shown in the property switcher. **Also capture the monthly Organic Search sessions series over the same window** (the report chart, set to monthly granularity) — it feeds the Trend & Anomaly Scan (§5.1 step 1) and is the cross-check (or stand-in) when GSC is blocked.
  4. **Conversion architecture** (feeds the Conversion Architecture report section — §5.1 / §5.2). In Explore → **Path exploration**, **set the date range to the same long window** (Explore reports also default to 30 days — change it explicitly), then set the starting point to a blog URL pattern (e.g. `/blog/`) and the ending point to the product / pricing page — capture which blog pages drive product-page visits. Then in **Landing page → Conversions**, segment by the key conversion events (demo request, trial start, signup) and capture which blog/content pages convert. If Explore is unavailable, the **Pages and screens** report filtered to `/blog/` with conversions as a secondary metric works as a fallback.
- **Success:** **actual date range recorded** (16 months target; less if retention/property age caps it); landing pages with sessions/conversions; direct & organic sessions; direct engagement rate; property name confirmed; **a ranked list of blog/content pages by (a) product-page visits driven, (b) demo requests generated, (c) trial starts generated** — or an explicit note that GA4 isn't tracking the conversion events needed to answer this (which itself is a finding).
- **Store:** `ga4-landing-pages.csv` + `ga4-channels.md` + `ga4-conversion-paths.md` (the three ranked lists).
- **Fallback:** **F-FILE** for the landing-pages export; **F-ANSWER** "Is the GA4 property name the one tracking the main site?" if ambiguous. **F-ANSWER** "Which GA4 events count as conversions for this site (demo, trial, signup, contact-form)?" if no conversion events are configured — that gap is itself a Tier 1 finding.
- **If GA4 isn't set up at all** (no property, no data): this is **not** something to skip, and **not** something you set up live. It becomes one of the **highest-priority Week 1 actions in the report** — you can't improve what you can't measure. Write a Tier 1 recommendation **"Set up Google Analytics 4"** with beginner-friendly steps (create a GA4 property at analytics.google.com → add the measurement ID / tag to the site → mark key events like form submits, signups, or marketplace clicks as conversions), and note that the next audit (in ~2 weeks) will have real conversion data to work with. Same pattern for any missing measurement (no GSC, no conversion tracking): record "set it up" as a top action, don't perform it. Then carry on with the rest of the audit using whatever data *does* exist.

### 3.4 PageSpeed Insights  *(API preferred; quota failures do not block the audit)*
- **Goal:** Performance score + Core Web Vitals (LCP, TBT, CLS, FCP) + the third-party summary, for the homepage on **both mobile and desktop**, plus the top 3–5 landing pages (mobile is enough for those).
- **Method (preferred — the API returns the whole report, so there are no report sections to expand and no Playwright needed):** call the public endpoint once per strategy (an unkeyed call can work, but availability is not guaranteed):
  - `https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=<PAGE_URL>&strategy=mobile`
  - `https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=<PAGE_URL>&strategy=desktop`
  The JSON *is* the full report — read `lighthouseResult.categories.performance.score` and the `audits` for LCP/TBT/CLS/FCP, and scan `audits['third-party-summary']` for CPU time and any off-domain font/CDN hosts (the methodology flags staging-CDN font regressions here). **Do not try to scrape `pagespeed.web.dev` with Playwright** — the rendered score gauge and the collapsible "Diagnostics" sections are unreliable to read; the API gives you all of it cleanly.
- **Success:** mobile **and** desktop score + CWV captured for the homepage; mobile for the top pages.
- **Store:** `psi-<page>-<strategy>.json` (raw) + a one-line summary in the Ledger.
- **Fallback:** follow the missing-browser/PageSpeed-quota rule in §1.4. Record HTTP 429 and strategy; use an existing configured key without exposing it, a human-supplied PageSpeed report, or local Lighthouse if available. If none is available, record `not measured`, continue, and withhold the overall Health Score. Never treat absent performance evidence as a score of zero.


### 3.5 Squirrel Scan  *(MANDATORY — run before any skills)*
- **Goal:** code-level crawl across 18 categories: structured-data errors, broken/internal-nofollow links, accessibility, the actual URLs behind GSC's "crawled–not indexed". (Squirrel also reports security headers — **do not surface these as SEO findings**; see the Security findings rule in §5.2 item 10 and the claims-discipline note in §5.1.)
- **Method:** run the CLI (confirm flags with `squirrel audit --help` first):
  `squirrel audit https://<domain> --max-pages <page-limit> --coverage full --format llm --output ./audit-data/<client>/squirrel-report.txt`
- **Coverage:** Replace `<page-limit>` with a numeric limit chosen from the sitemap URL count and expected crawl scope, leaving room for redirects and non-sitemap URLs. Discovered URLs can exceed sitemap counts; there is no universal safe multiplier. If the crawl reports that it capped, or reaches the configured limit without confirming completion, increase the limit and re-run. If scope or resources require stopping, label it a **partial crawl**. Record sitemap count, discovered/audited counts, limit, exclusions, and completion status alongside the output. Do not present a capped score as a full-site score or compare scores from different crawl scopes without explaining the difference.
- **Success:** report file written; category scores + top failures readable; crawl completion or partial scope explicitly recorded.
- **Store:** `squirrel-report.txt`.
- **Fallback:** if the CLI isn't installed → **F-ANSWER** with the install/restore note from the methodology; do not proceed to skills without this.

### 3.6 Manual page audits
- **Goal:** for homepage, top commercial/product page, top organic landing page, pricing page, one blog post — capture H1, title tag, meta description, schema types present, page type, and the Open Graph share fields `og:title`, `og:description`, and `og:image`; establish whether JavaScript changes search-critical elements.
- **Method:** Playwright → navigate each → read `<head>` + H1 from both the HTTP response/page source and the rendered DOM. For Open Graph, verify that all three fields exist, describe the same page rather than a stale template, and that `og:image` resolves successfully as an absolute HTTPS URL. Where practical, open one representative share preview in a platform or preview tool; platforms commonly prefer Open Graph descriptions over the standard meta description.
  - **Render-parity router:** on one representative page, compare response HTML with the rendered DOM. If search-critical elements are added, removed, duplicated, or changed—or `site-profile.md` says CSR/hybrid/unknown—expand to up to three representative templates.
  - Compare: status/final URL, title, meta description, canonical, meta robots, H1, primary visible content, crawlable `<a href>` internal links, and JSON-LD. Record response value → rendered value → material difference.
  - For any material difference, corroborate one representative URL using URL Inspection's crawled/rendered HTML or the Rich Results Test. Browser rendering alone shows what a browser can render, not proof of what Google indexed.
  - If the representative response/render comparison is materially identical and the site is static/SSR, mark the expanded module **N/A — no material JS dependency observed**, with the tested URL.
- **Success:** 5 pages captured (fewer if the site is smaller — note why), an Open Graph verdict for each, and either a completed response/render/Google-view comparison or an evidence-backed N/A. Treat Open Graph as share-preview quality, **not a Google ranking factor**.
- **Store:** `manual-audits.md` + `js-render-parity.md`.
- **Primary guidance:** [Google JavaScript SEO basics](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics).

### 3.7 SERP captures + page-type classification
- **Goal:** for the top 3–5 commercial keywords, the top-10 results classified by page type, dominant-type pattern, AI-Overview presence, whether the client appears and with what page type (the SXO mismatch signal). **Capture visible publish/updated dates on the top results** — a SERP that has turned over to fresher content on a term the client is sliding on is a content-decay signal for §5.1 step 1.
- **Method:** Playwright incognito search per keyword **or** WebSearch. **Note the limitation** (methodology): WebSearch returns ~6–8 results, no PAA/featured-snippet shape — report confidence as "strong direction," not a precise %.
- **Success:** per-keyword page-type table + verdict (aligned / mismatch).
- **Store:** `serp/<keyword>.md`.
- **Fallback:** **F-ANSWER** for the page-type judgment calls if genuinely ambiguous (methodology says these calls are consultant value — surface them rather than guessing).

### 3.8 Competitor names  *(human-only — never guess)*
- **Goal:** confirmed competitor names.
- **Method:** you should have asked at kickoff (§1.5). If still unanswered, ask now and wait. Never infer from the SERP and proceed.
- **Store:** `competitors.md`.
- **Fallback:** **F-ANSWER** "Who are the confirmed competitors? (I won't guess — a comparison page built on the wrong competitor is worse than none.)" Mark item 8 🙋 until answered; this also gates `/competitors`.

### 3.9 robots.txt + sitemap
- **Goal:** robots.txt contents + sitemap structure (and any missing sitemap types).
- **Method:** fetch `https://<domain>/robots.txt` and `https://<domain>/sitemap_index.xml` (or `/sitemap.xml`).
- **Store:** `robots-sitemap.md`.

### 3.10 Schema validation — Rich Results Test + Schema.org validator  *(Playwright-driven; F-ANSWER fallback if blocked)*
- **Goal:** ground the schema-markup analysis (§4) in **Google's own validator** plus the **stricter schema.org validator**, then separate technical validity from actual Search appearance. A valid block can make a page eligible; it does not prove Google displays a rich result.
- **Sample:** **3 URLs** representative of the site's main page types:
  1. **Homepage**
  2. **One product / feature / pricing page** (or the closest equivalent for this site)
  3. **One blog post / article** (skip if no blog yet — note it)
  Pick the 3 URLs yourself from the site map; only ask the human if the choice is genuinely ambiguous.
- **Method (Playwright — preferred):** drive each validator directly. For a 3-URL × 2-validator sample these usually run without intervention, but both validators sit behind Google's abuse check and can show a CAPTCHA or a "sorry" (429) page, especially over a VPN or shared network. If that happens, use **F-CAPTCHA** (§1.2): ask the human to solve it, then continue.
  0. **Outcome baseline first:** in Search Console, record applicable Enhancements/rich-result report totals and Performance → Search appearance rows for the longest comparable window. Capture `appearance type | valid/invalid where reported | impressions | clicks | CTR | date range | affected/example URLs`. If no relevant appearance is available, record `none observed`; do not turn zero appearance into proof that valid markup is broken.
  1. **Schema.org Validator** — `https://validator.schema.org/` (no login; can still show Google's CAPTCHA → F-CAPTCHA).
     For each URL: navigate, paste it into the "Fetch URL" field, click **Run test**, wait for the results panel to render, and snapshot it. Capture: detected types, any errors, any warnings.
  2. **Rich Results Test** — `https://search.google.com/test/rich-results`.
     For each URL: navigate, paste into the URL field, click **Test URL**, wait for results (this can take 15–30s — wait for the "Results" panel to appear, not just the spinner to stop). Snapshot it. Capture: detected items, errors, warnings. **If a reCAPTCHA challenge or "sorry" page appears** (it can happen after a few runs), pause and use **F-CAPTCHA**: ask the human to solve it in the open window, then carry on with the remaining URLs.
  3. **Use `/schema-markup-better-than-corey-haines` to review coverage on the same 3 URLs.** Feed it both validator outputs and visible-page evidence. Ask for missing or mismatched entities and a per-URL JSON summary with evidence and dated eligibility sources. This uses the public package already installed in §1; no `schema-detector` installation or private patch is needed. Keep schema.org validity, Google rich-result eligibility, and observed display separate. Save this evidence in `schema-validation.md` for Phase 2 reuse.
  4. **Never add a property to silence a warning.** `WebSite` without `SearchAction` gets flagged, but that property tells Google the site has its own search box to send people into. If the site hasn't got one, leave it out and write down why. A checker going quiet is not the goal; the report being true is.
  5. After capturing all three, note any **delta** per URL — items Google recognised that schema.org rejected, or vice versa, or that were missing altogether.
- **Success:** 6 result sets captured (3 URLs × 2 validators) with detected types + errors/warnings; per-URL delta noted; Search Appearance/enhancement baseline recorded or explicitly unavailable.
- **Store:** `schema-validation.md` — one block per URL with both validators' outputs side-by-side, plus a short "delta" note per URL; `gsc-search-appearance.md` for the outcome baseline.
- **Fallback:** first **F-CAPTCHA** for any CAPTCHA. Use **F-ANSWER** only if Playwright is unavailable, or if the human says they can't help with the CAPTCHA right now. Then hand the human these instructions:
  > For each of the URLs I couldn't validate, please do both and paste the results back:
  >
  > **A. Rich Results Test** — https://search.google.com/test/rich-results → paste URL → click "Test URL" → paste back detected items + errors/warnings.
  >
  > **B. Schema.org Validator** — https://validator.schema.org/ → same URL → paste back detected types + errors/warnings.
- **Downstream:** feed this into `/schema-markup-better-than-corey-haines` (§4) — the skill should cite specific validator errors per URL, not abstract "your schema is malformed" findings.
- **Retired types — never carry these into §4.** Both validators still pass `FAQPage` and `HowTo`, because both are valid schema.org vocabulary. Neither earns a rich result any more: Google removed `HowTo` in September 2023 and retired `FAQPage` for all sites on 7 May 2026 (verified against Search Central, 4 September 2026). If a validator reports them as detected items, record them as present-but-inert; do not write either into the report as an opportunity.
- **Measurement downstream:** when a schema action ships, the generated action runbook must revalidate live immediately, then compare the same Search Appearance type at 2/4/8 weeks. Keep `valid`, `eligible`, and `shown` as three separate states; never promise display.
- **Primary guidance:** [Google's structured-data guidelines](https://developers.google.com/search/docs/appearance/structured-data/sd-policies) and [Search Console Search appearance dimensions](https://support.google.com/webmasters/answer/17011259).

### 3.11 AI-search opportunity + citation sample  *(bounded; no new kickoff questions)*
- **Goal:** find how the confirmed target persona's search journey expands in conversational AI, which live searches those systems actually perform, which domains they cite, and where the client has a validated on-site or third-party visibility opportunity.
- **Inputs:** reuse the confirmed persona from §1.5, 5–10 commercially relevant seed topics already supported by GSC/keyword research, the live SERPs from §3.7, and current site coverage. Do **not** ask the human another onboarding questionnaire.
- **Method:**
  1. Turn each validated seed into a small set of natural-language prompts using the confirmed persona/context, then map the most likely useful follow-up questions. These generated prompts are candidate research inputs—not proof that people ask them.
  2. Select a stable, bounded benchmark of **20–50 prompts** across the funnel. For each, record the exact platform/model or surface, date, locale, persona/context, and prompt. Test through the current supported browser/API route after checking its primary documentation.
  3. Record only observable behaviour: whether live web search/grounding occurred; the actual search/fan-out queries if the platform exposes them; brand mention; client citation; cited URLs/domains; and answer position/context. If grounding or generated searches are not exposed, write `unknown`—never infer them.
  4. Map the observations back to existing pages, missing topic clusters, and repeatedly cited third-party sources. Distinguish **on-site work** (improve or create a useful page) from **off-site work** (earn an accurate review, listing, expert contribution, or citation on a source the answers already trust).
  5. Validate any proposed content topic with the demand rules in §5.1 before recommending it. Cluster related prompts by user task and intent; never create one page per prompt or fan-out query.
- **Success:** a reproducible prompt set and a table `seed | persona/context | prompt/follow-up | platform/date/locale | web search observed? | exposed search queries | brand mentioned? | client cited? | cited domains | existing coverage | validated action/hypothesis`. Every claimed opportunity has either independent demand evidence or the word `hypothesis`.
- **Store:** detailed observations in `ai-search-opportunities.md`; copy the stable prompt panel and configuration into `ai-search-benchmark.md` so the same panel can be rerun monthly.
- **Fallback / skip:** if a platform blocks automated access, use a small manual browser sample and state the limitation. If AI-assisted discovery is genuinely irrelevant to the client, mark Ledger item 11 ⏭️ with the business reason; do not manufacture a GEO section to fill the report.
- **Research basis to re-check before use:** [Mark Williams-Cook's AI-platform keyword-research process](https://athenseo.com/knowledge-hub/keyword-research-for-ai-platforms/) and [Google's current guidance for generative AI features](https://developers.google.com/search/docs/fundamentals/ai-optimization-guide). Platform behaviour changes; the current primary documentation wins.

### 3.12 Internal-site-search indexability + reflected-input safety  *(conditional; harmless test only)*
- **When it applies:** first establish whether the site has a public internal-search results page. If it does not, mark Ledger item 12 **⏭️ N/A — no internal site search** and stop. Do not manufacture a finding.
- **Goal:** detect indexable search-result URLs that can create crawl/index bloat or let arbitrary search text appear in Google under the brand, and detect unsafe reflected-input handling that needs developer/security review.
- **Method:**
  1. Open one ordinary search URL using a unique plain-text marker such as `audit-search-check`.
  2. Open a second search URL using the URL-encoded equivalent of the harmless HTML-like marker `<b>audit-search-check</b>`. Do **not** use scripts, event handlers, external links, or exploit payloads; this SEO audit is not a penetration test.
  3. For both URLs capture: HTTP status, final URL, canonical, meta robots, `X-Robots-Tag`, sitemap inclusion, and whether ordinary internal links expose search-result URLs.
  4. Inspect both page source and the rendered DOM, including `<title>`, H1, and visible body. Record whether the marker is absent, displayed as literal text/escaped markup, or interpreted as an actual HTML element.
  5. If Search Console access is available, check a representative search URL with URL Inspection and look for indexed search-result URLs/query patterns. Treat this as corroboration; the live directives and rendering are still required evidence.
- **Severity and recommendation rules:**
  - **No internal search:** N/A; no report finding.
  - **Search results are `noindex` and input is escaped/rendered as text:** pass or low observation; normally no recommendation.
  - **Search results are indexable but input is escaped:** **Medium**. Recommend a crawlable `noindex` directive, removal from XML sitemaps, and avoiding indexable internal links to generated result URLs. Do not block the URLs in `robots.txt` before Google can crawl and see `noindex`; a robots block alone can leave a URL listed without a snippet.
  - **Harmless markup is interpreted as HTML or other untrusted input is inserted unsafely:** **High** and route to a developer/security owner. Describe it as **unsafe reflected HTML/input handling**, not “confirmed XSS,” because this bounded check does not attempt code execution. Recommend context-appropriate output encoding/escaping, framework auto-escaping, and safe DOM sinks, followed by a proper security review.
- **Success:** item 12 is explicitly N/A or `internal-search-check.md` contains both URLs, directives, source/rendered observations, evidence, calibrated severity, and a report/action recommendation only when warranted.
- **Store:** `internal-search-check.md`.
- **Research basis to re-check before acting:** [Google's `noindex` guidance](https://developers.google.com/search/docs/crawling-indexing/block-indexing), [OWASP Cross Site Scripting Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html), and [the internal-search abuse case that prompted this check](https://markwilliamscook.com/problems-with-indexable-internal-search-pages/). Current primary Google and OWASP guidance wins.

### 3.13 Evidence-based content refresh / merge / prune protocol
- **Goal:** replace the blunt “old page + traffic loss = refresh or delete” rule with a recurring decision register grounded in search, conversion, link, user, and business evidence.
- **Method:**
  1. Build the canonical URL inventory from sitemap(s), crawl, CMS/repository, GSC Pages, and GA4 landing pages. Record page type/template, status/canonical/indexability, sitemap inclusion, last **substantive** edit where verifiable, recent/prior/year-over-year GSC metrics, GA4 sessions/conversions, internal/external-link evidence when available, target persona/task, unique first-party value, business/legal/support value, and known overlap/successor.
  2. Use finalized GSC data to compare a recent stable period with the previous equivalent period and, for seasonal sites, the same period last year. Record absolute and relative movement. A threshold such as `>30% click loss` only nominates a review; age, word count, low traffic, or one percentage never decides it.
  3. Diagnose the movement: **Demand decline · Competitiveness/content loss · SERP/CTR change · Technical/indexing · Tracking/data anomaly · Seasonal · Volatile/too little data · Unexplained**. Check Google Trends, current SERP type/features/freshness, `change-events.md`, query→Pages in GSC, conversions, links/referrals, and protected business value.
  4. Assign one provisional decision: **Protect/keep · Refresh · Merge/consolidate · Differentiate · Redirect · Noindex · Remove (404/410) · Investigate**. A refresh needs a fixable content/intent gap. Merge only substantially overlapping user tasks; use a permanent redirect only when the target is a meaningful replacement. If content is gone with no suitable replacement, use `404/410`. Do not change a date merely to appear fresh.
  5. Every destructive route is **Red / Human decision**. Before approval list inbound/internal links, conversions, navigation/support/legal obligations, replacement URL (or explicit none), redirect/canonical/sitemap changes, rollback/recovery limits, and post-change monitoring.
- **Success:** all canonical URLs are inventoried or sampling limits are explicit; each reviewed URL has a supported provisional decision, confidence, and next check; no automatic refresh/merge/prune action comes from age or traffic alone.
- **Store:** `content-inventory.md`.
- **Primary guidance:** [people-first content self-assessment](https://developers.google.com/search/docs/fundamentals/creating-helpful-content), [debugging Search traffic drops](https://developers.google.com/search/docs/monitor-debug/debugging-search-traffic-drops), [Search Console comparisons](https://support.google.com/webmasters/answer/17011165), [canonical consolidation](https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls), and [404/410 versus replacement handling](https://developers.google.com/search/docs/crawling-indexing/troubleshoot-crawling-errors).

### 3.14 Crawl Stats + conditional server-log analysis
- **Goal:** identify genuine crawling constraints without burdening small, healthy sites with specialist log work they do not need.
- **Router:** open GSC Crawl Stats when GSC is available and record the headline baseline. For sites below roughly **1,000 URLs** with healthy host status, normal discovery/indexing, and no evidence of crawl constraint, mark server-log analysis **N/A — no crawl-level problem evidenced**. Escalate to logs only for a large/rapidly changing site, material `Discovered – currently not indexed` backlog, parameter/facet/infinite URL space, migration, host/response anomalies, important pages not being recrawled, or another direct crawl signal. Google's current advanced crawl-budget guidance is narrower still (primarily very large or rapidly changing sites), so size is a router—not a finding.
- **Method:**
  1. In Crawl Stats record the exact 90-day range, total crawl requests, total download size, average response time, host status, response-code distribution, file types, crawl purpose, Googlebot types, and material spikes/drops with example URLs where the report exposes them.
  2. Reconcile anomalies with deployments, outages, migrations, robots/sitemap changes, URL parameters, 5xx/429 responses, and `change-events.md`. A change in crawl volume alone is not proof of a budget problem.
  3. If the router justifies logs, request a bounded representative export with timestamp/timezone, request URL/query string, status, bytes, response time, user agent, referrer if recorded, and source IP. Minimize/secure the data and follow the site's retention/privacy rules.
  4. Verify Google crawler traffic using reverse/forward DNS or Google's published crawler IP ranges; a `Googlebot` user-agent string can be spoofed. Analyze important-vs-waste patterns, status/latency, recrawl lag, orphan/facet/parameter load, and mobile/desktop crawler mix. Recommend a change only when logs corroborate the problem.
- **Success:** `crawl-stats.md` contains the baseline, incident reconciliation, and explicit `logs required / logs N/A` verdict. When logs apply, `server-log-analysis.md` states period/coverage, verified crawler method, limitations, affected patterns, representative URLs, and evidence-backed action.
- **Store:** `crawl-stats.md`; conditional `server-log-analysis.md`.
- **Primary guidance:** [Crawl Stats report](https://support.google.com/webmasters/answer/9679690), [current crawl-budget guidance](https://developers.google.com/crawling/docs/crawl-budget), and [verifying Google crawler requests](https://developers.google.com/crawling/docs/crawlers-fetchers/verify-google-requests).

### 3.15 Backlink baseline + disavow safety
- **Goal:** preserve a repeatable sample of link authority/risk and find defensible link opportunities without treating a sampled report as a complete index or turning suspicious-looking links into an automatic disavow task.
- **Method:**
  1. Export GSC Links: top linked pages (external), top linking sites, top linking text, and internal links. Record the property, export date, row limits, and that GSC provides a sample—not every known link.
  2. When available, enrich with Bing Webmaster data, and with Ahrefs **only if the Ledger header records Ahrefs as authorised for this client** (🔒 §0 standing rules — if unset, emit **F-AHREFS** and proceed Bing-only rather than calling it). Keep each provider's counts separate; do not combine incompatible indexes into a fake total. Record referring domains/pages, target URL, first/last seen where supported, follow status if supported, topical/context relevance, and obvious lost/new movements.
  3. Classify observations: **Protect/monitor valuable link · reclaim broken/redirected target · unlinked mention/outreach opportunity · irrelevant/spam sample/no action · investigate known link scheme/manual-action risk**. Verify material source and target pages live before recommending work.
  4. **Never automatically recommend, create, or upload a disavow file.** Google says most sites do not need the tool and incorrect use can harm Search performance. Consider it only with a considerable pattern of artificial/paid/low-quality links **and** a related Manual Action or credible risk from link schemes, after expert human review and attempted removals. This is always Red / exact owner approval.
- **Success:** the baseline names sources, dates, coverage limits, valuable targets/referring sources, reclaim/opportunity candidates, and any verified risk; differences between providers are not presented as errors; no toxicity score alone creates a disavow action.
- **Store:** `backlink-baseline.md`.
- **Primary guidance:** [GSC Links report](https://support.google.com/webmasters/answer/9049606) and [Google's disavow guidance](https://support.google.com/webmasters/answer/2648487).

### 3.16 Fixed historical SERP panel
- **Goal:** convert one-off SERP screenshots into a small repeatable panel that shows page-type, feature, landing-URL, competitor, and new-entrant change between audit cycles.
- **Method:**
  1. Freeze 5–10 validated commercial query clusters, locale, device, collection method, the client's actual landing URL, confirmed competitors, and the page-type taxonomy. Record the baseline date; do not change the panel just because a query moved.
  2. Per query record top results/domain/page type, client URL and observed position/range, AI Overview/featured snippet/PAA/other material features, visible dates, and meaningful new entrants. Keep personalized/local volatility and incomplete result capture explicit.
  3. On later cycles run the same configuration and produce a delta: entrants/exits, page-type consensus, feature-owner changes, client landing-URL switches, and confirmed competitor movement. Research the live pages before attributing a cause; a changed rank, sitemap `<lastmod>`, or content hash is a prompt to inspect, not proof of strategy or causality.
  4. Use public evidence only, respect access controls and reasonable request rates, and never copy competitor text or fabricate their strategy.
- **When a page looks fine and still won't rank, use `/seo-sxo <url> <keyword>` only after its optional script/runtime checks in §1 pass.** If unavailable, record a manual SERP/page-type review and its evidence limitations instead; do not claim the skill ran. Not on every page — it's a diagnosis, not a survey, and running it across a whole site wastes hours. Use it on the specific case where the page is well written, technically sound, targets the right term, and is still nowhere.
  It reads the top 10 results for that term backwards and works out what *kind* of page Google is actually rewarding, then compares yours against it. Its own thresholds: if more than 60% of the top 10 are one page type, that's a strong consensus and being a different type is likely your problem; 40–60% is mixed; under 40% means the SERP is fragmented and page type probably isn't what's holding you back.
  This is the cause the rest of the runbook can't see. A comparison page that's perfect in every technical respect will not rank for a term where Google shows ten product pages, and no amount of on-page work fixes that — the fix is a different page, or a different target term. Record the verdict either way, including "page type is fine, the problem is elsewhere", so the next cycle doesn't re-check it.
- **Success:** the panel is fixed and reproducible; competitors are human-confirmed; observed changes are separated from interpretations; any action links back to the client's content decision or brief.
- **Store:** `serp-history-panel.md`.

### 3.17 CMS/framework implementation adapter — conditional
- **Goal:** translate approved recommendations into the smallest correct shared implementation surface for the client's actual stack, with version-aware research, rollback, and repeatable build/deploy/live verification.
- **Activate when:** source or CMS access exists, the stack can be identified, and at least one report recommendation may be implemented. Otherwise record `N/A — no implementation access/action`; knowing a CMS name alone is not enough.
- **General method:**
  1. Inspect the repository/CMS before advising. Record the detected framework/CMS and installed version/major, lockfile or platform evidence, rendering mode, hosting adapter, build/test commands, deployment path, and relevant shared config/layout/component/template/content fields.
  2. Research current primary framework/CMS and host guidance for the installed major. Record `question | primary source | source/update date | version fit | decision` in `implementation-adapter.md`. Do not rely on remembered paths, plugins, flags, or APIs.
  3. Map each implementable root cause to one exact shared surface, affected URL pattern/count, representative affected URL(s), one unaffected regression sample, proposed diff, acceptance criteria, and shared rollback. If the surface cannot be located, keep the action Researching—do not create page-by-page busywork.
  4. Verify in four distinct states: source/config review; local checks and production build; built response/output inspection; authorized deploy followed by live response/rendered checks. Record each state separately.
  5. Re-run the bounded adapter checks after relevant deployments and framework/plugin/theme upgrades. Never fold a dependency/platform upgrade into an SEO fix without separate evidence, scope, approval, and rollback.
- **Initial Astro adapter:**
  1. Inspect `package.json` and the lockfile, `astro.config.*`, `src/pages/`, shared layouts/head components, content collections or data source, `@astrojs/sitemap`, host config, scripts, and deployment configuration. Use the files actually present; do not assume these names when the project differs.
  2. Check `site`, `base`, trailing-slash policy, output mode, hosting adapter, and integrations as one URL-generation/deployment system. Confirm the intended production origin in generated sitemap and canonical/Open Graph/schema URLs.
  3. Inventory file-based routes. For static output, verify each intended dynamic route is deliberately emitted through `getStaticPaths()` or the installed version's equivalent content pipeline. For on-demand routes, verify server output and a compatible host adapter. Test intended 404, redirect, canonical, and indexability behavior.
  4. Locate the true shared head/layout surface. Map page-specific title, description, canonical, robots, Open Graph, Twitter, and JSON-LD fields through props/frontmatter/content data; prevent a shared fallback from silently making many pages identical.
  5. Check the sitemap integration against the canonical/indexable route set. Exclude non-indexable routes. Add per-URL `<lastmod>` only from a reliable substantive-edit source—never the build, deployment, or audit timestamp for unchanged pages.
  6. Run the declared production build. Inspect at least one affected and one unaffected generated HTML file plus sitemap output, then deploy only with authorization and repeat status/final URL, canonical, robots, title/description, H1, structured data, Open Graph, crawlable links, and sitemap checks live.
- **Success:** every mapped action names the exact shared source and installed version, cites current primary guidance, passes affected/unaffected local and built-output checks, preserves a rollback, and distinguishes built/deployed/live verified states. The adapter produces no generic “install an SEO plugin” advice.
- **Store:** `implementation-adapter.md`.
- **Astro primary guidance:** [configuration](https://docs.astro.build/en/reference/configuration-reference/), [routing](https://docs.astro.build/en/guides/routing/), [integrations](https://docs.astro.build/en/guides/integrations/), and [`@astrojs/sitemap`](https://docs.astro.build/en/guides/integrations-guide/sitemap/).

---

### 3.18 On-page baseline for the next cycle — `/seo-drift`
- **Goal:** capture what the important pages look like *today*, so the next audit can show exactly what changed instead of arguing from memory. This is the cheapest thing in Phase 3 and the only one whose whole value is in the future.
- **Why it's here and not in Phase 5:** a comparison needs two points. On a first audit there is nothing to compare against, so all you can do — and should do — is set the first point. Do not skip it because it produces no finding today; that is exactly why it gets skipped and exactly why the next cycle has nothing to work with.
- **Method:** run `/seo-drift` to baseline the same URLs you used for the SERP panel (§3.16) plus the homepage and the top 5 pages by clicks. It records 13 things per page — title, meta description, canonical, robots directives, the H1/H2/H3 lists, JSON-LD schema, Open Graph tags, Core Web Vitals, HTTP status, and content hashes — into a local SQLite file at `~/.cache/claude-seo/drift/baselines.db`.
- **Setup and verification:** Use the complete shared script bundle and setup checks above before capture (`--with-drift --seo-bundle "<bundle>"`). Run baseline and compare through that same managed runtime. A minimum verification is:

```bash
python "<bundle>/scripts/runtime.py" run drift_baseline.py <url> --skip-cwv
python "<bundle>/scripts/runtime.py" run drift_compare.py <url> --baseline-id <saved-id> --skip-cwv
```

Replace `<saved-id>` with the returned baseline ID. `--skip-cwv` is for this verification or unavailable performance evidence; it does not measure Core Web Vitals. Record any omitted performance evidence and preserve separately collected performance results. Require successful capture and comparison, a persisted baseline row in the reported SQLite database, and a comparison referencing that ID. Inspect the captured status/title/headings/canonical/schema against the page evidence; a successful exit with empty or wrong fields is not a valid baseline. Record the absolute database path, URL set, timestamp, baseline IDs, source revision, comparison result, and field coverage. Raw-response-only captures must be labelled as such; they cannot establish rendered coverage for JS-dependent pages. Preserve this initial snapshot, and add a separate post-fix baseline after implementation. If capture or comparison fails, mark drift unavailable with the repair action; do not silently substitute manual JSON and mark the step complete.

- **Success:** baseline stored, the URL list written into the report appendix so the next cycle uses the same one, and the capture date recorded.
- **Do not put the comparison on a schedule with email alerts.** Its 17 rules will produce 10–30 findings a month on a normal site, most of them either intentional changes or noise. Automating that trains the client to ignore the channel, which is worse than not having it. The value is a person running the comparison and surfacing the two or three things that actually matter. Borrow the diff, keep the judgement.

## 4. Phase 2 — Skills

Run all six core skills against the collected data (not in the abstract — paste the relevant Squirrel/GSC/GA4 specifics into each skill prompt so outputs cite real URLs and numbers), plus the conditional AI-written-page review below when kickoff §1.5 #8 is yes. **Run `/seo-audit` last. Run `/competitors` only after Phase 1 Ledger item 8 (competitor names) is confirmed.** Save each output under `skills/<skill>.md` (or the directory specified below), using the three evidence filename mappings in the installation section and tick §2.2. See methodology Phase 2 for what each skill adds and the prompts to use. Do not start Phase 3 until all six core skills are ✅ or explicitly ⏭️ with a reason, and the conditional `/remove-ai-slop-without-removing-ctas-and-good-marketing-copy` row is either ✅ or correctly marked ⏭️.

### 4.1 Conditional AI-written-page review

When kickoff §1.5 #8 is **yes**, invoke the installed `/remove-ai-slop-without-removing-ctas-and-good-marketing-copy` skill (install command in the setup section) as part of this audit:

1. Read that skill's `SKILL.md` and follow its complete workflow, including every referenced rubric/evaluation it requires. If the skill is unavailable, use **F-TOOL** and leave its Ledger row blocked; do not silently substitute an AI detector or a generic rewrite prompt.
2. Run it on local audit copies only—never edit the live site, CMS, or repository source during the audit. Preserve each page's visible headings, links, CTAs, offers, positioning, proof, and capability claims in the working copy so the skill's conversion contract and integrity checks are meaningful.
3. Review every page the human identifies. If they cannot identify the complete set, use the representative sample recorded at kickoff and state the coverage limitation. Store original extracts, working edits, conversion-contract snapshots, verification results, and a concise findings summary under `audit-data/<client>/skills/remove-ai-slop-without-removing-ctas-and-good-marketing-copy/`.
4. Save the summary as `skills/remove-ai-slop-without-removing-ctas-and-good-marketing-copy.md`. Separate recurring editorial risks from page-specific examples, cite the affected URLs, and feed only evidence-backed findings and recommendations into Phase 3. The working edits are audit evidence, not authorization to publish them.

When kickoff §1.5 #8 is **no**, mark the Ledger row ⏭️ with `AI not used per kickoff answer`. Do not run the skill. An unanswered or ambiguous kickoff answer must remain 🙋 until the human resolves it.

---

## 5. Phase 3 — Synthesis & Report

The methodology's Report Design Philosophy is the authority on the *why*; the rules below are the enforceable *what*. Don't just "write a report" — produce the specific shape below and pass the QA gate (§5.4) before declaring done. **Write the whole report in plain, beginner-friendly English** — many readers run this on their own and aren't technical. Explain any jargon in a few words the first time it appears, and never assume the reader already knows how to do a step.

### 5.1 Synthesis process (do in this order)
1. **Trend & Anomaly Scan (mandatory — do this before reading any skill output).** From `gsc-trend.md` (§3.1) and the GA4 monthly series (§3.3), answer four questions **with numbers**:

   **Run `/trend-scan` first.** It answers all four questions from the CSV files you exported, and it always prints the flattering long-period total next to the recent trend so the two cannot be separated. Every page or search term it flags arrives marked **NEEDS AN ANSWER** — working out why each one dropped is still your job, using the list below. Doing this by hand instead is fine. Skipping it is not: this whole step exists because a healthy-looking twelve-month total once hid a five-month slide.

   - **Direction:** are site-wide organic clicks rising, flat, or falling across the window? State it plainly, month by month — "clicks have fallen every month since January" beats any aggregate.
   - **Trend breaks:** which top-10 pages/queries lost **>30% of clicks** or **>2 average positions** between recent periods (or vs the same period last year, for seasonal sites)? For each one, diagnose the cause — content decay / fresher competitors above it (check the publish dates captured in §3.7), a SERP-feature change (AI Overview arrival), a CTR/title problem, a technical cause, or **the page being the wrong type for what that SERP now rewards** (§3.16 — use verified SXO or the labelled manual SERP/page-type fallback in §1) — or write "unexplained" and name the data that would settle it. **A flagged mover with no diagnosis = work not done** (same rule as the §3.2 indexing buckets).
   - **Concentration:** what share of organic clicks does the #1 page/query hold? Above ~25–30%, name it as a portfolio risk and add a diversification action — a site living off one volatile informational page is one algorithm update away from a cliff.
   - **Google's own flags:** answer every GSC alert/recommendation card captured in §3.1.

   **Reconcile the change journal before diagnosing a mover.** Compare `change-events.md` with the trend break: deployments, migrations, template/plugin releases, content edits, analytics changes, incidents, Manual Actions/Security Issues, Google Search Status events, and documented data anomalies. Separate preliminary 24-hour data from finalized historical data. A matching date is evidence of correlation, not proof of cause: write **"consistent with"** unless a rollback/reproduction, log evidence, or another controlled check confirms the mechanism. Never auto-rollback a search change merely because rankings fall inside seven days; rollback immediately for a failed technical acceptance test or a pre-agreed business guardrail, otherwise keep the change journal intact and measure at 2/4/8 weeks with an unaffected comparison group where possible.

   **A live decline outranks everything else you found.** It becomes the reframing finding the Executive Summary opens with (§5.3) and it re-orders the 90-day plan — do not lead with opportunity framing while the site is bleeding. And its verdicts override snapshot impressions: a page that *looks* like the crown asset in the aggregate ("huge impressions, position ~5") may be a **decaying** asset in the series (position 3 → 7, CTR collapsed) — the series wins. If the series couldn't be collected at all, write "no trend analysis was possible — [what was missing]" into the Executive Summary; never let the window aggregate stand in for a trend.
2. Collect all skill outputs into one staging doc.
3. Deduplicate (multiple skills surface the same finding — keep the most detailed version, strip the rest).
4. **Group findings by shared root cause before creating recommendations.** Build `template-root-causes.md` with `root-cause ID | evidence/finding IDs | shared template/component/plugin/config | URL pattern | affected count | representative URL | proposed single fix | regression sample | confidence`. One shared mechanism becomes one recommendation and one action, even when hundreds of URLs are affected. Use representative URLs for proof and a regression sample that includes an unaffected page. Only keep page-by-page actions when the evidence shows the fault is genuinely URL-specific.
5. **Reconcile the Content Decision Register.** Join `content-inventory.md` with conversion, query→Pages, backlink, business value, seasonality, historical SERP, and technical evidence. Every content recommendation must resolve to one decision—protect, refresh, merge, differentiate, redirect, noindex, remove, build, or investigate—with confidence and approval risk. Destructive routes remain Red/Human decision.
6. **Compile an evidence-backed brief for every proposed new, refresh, or merge action.** Save `content-briefs/<id>.md` using the standard below. A content recommendation without a populated brief is not ready for the 90-day plan.
7. Cross-reference every root-cause and content recommendation against the raw data; **drop any that don't connect to a specific data point** ("build comparison pages" is generic; "build `/compare/x-vs-y/` because Y holds position 1–2 and the client is at 17" is defensible).
8. Assign tier / impact (1–5) / effort (t-shirt size) to each.
9. **Build the Conversion Architecture view** (from `ga4-conversion-paths.md`, §3.3): three ranked lists — blog/content pages by (a) product-page visits driven, (b) demo requests generated, (c) trial starts generated — plus the architecture finding (where top traffic and top conversion diverge; pages missing a product CTA; tracking gaps). This becomes a dedicated report section (§5.2). If GA4 wasn't available, state that the analysis can't be done until tracking is in place and put "set up GA4 conversion events" into Tier 1.
10. **Build the AI-search Opportunity view** from §3.11 when applicable: fixed benchmark, observed brand/citation baseline, recurring cited domains, on-site gaps, off-site opportunities, and limitations. Treat model output as volatile directional evidence, not a deterministic ranking report.
11. **Write the 90-day plan first** — it forces real prioritization. If an item isn't in the plan, question why it's in the main report at all.
12. **Research and answer the Acquisition Channel Questions (§5.7)** — deliberately late in the order, because every question synthesizes across multiple sources. Answer each one citing the collected data, or name the gap (with its Tier 1 action) where the data doesn't exist yet. These become the report's closing Q&A section (§5.2 item 14).
13. *Then* write the summaries and assemble `REPORT_FILE` in the structure below.

> **Thin-site guard — recommend new content, not just tweaks to the pages that exist.** On a site with few pages, it's tempting to make every recommendation about optimising the handful of pages already there. Resist that — a small site's biggest SEO problem is usually *missing* content, not imperfect existing content. For the site's core topic and the **target persona** (kickoff §1.5 #6), recommend building content across the funnel, each piece mapped to the persona and to its funnel stage:
> - **Top of funnel (TOFU)** — awareness/educational content the persona searches for *before* they know this product exists (problem-aware, not brand-aware).
> - **Middle of funnel (MOFU)** — comparison, how-to, and evaluation content for someone weighing options.
> - **Bottom of funnel (BOFU)** — product / pricing / integration / alternative pages for someone ready to choose.
>
> **Ask, don't guess.** Before writing these recommendations, confirm with the human: (a) the **target persona** if it's still unset, and (b) **what genuinely good top-of-funnel content looks like for that persona** — the real questions they ask, the terms they'd actually search, the formats they trust. TOFU invented without the persona's reality produces generic content that won't rank or convert. Use **F-ANSWER** for both if unconfirmed.

> **Research, don't assert — keyword data is MANDATORY before any content recommendation.** Do not recommend a single page, cluster, or content piece without demand data behind it. Pull real search volume and difficulty for every head term you propose. If you genuinely can't get the data, **label the recommendation a hypothesis** ("untested — no volume data pulled") — never assert "high volume," "low/zero competition," "100× larger audience," or "real search volume" without a number to point at. Those phrases are the tell that you're guessing.
> - **Free tools (use them — don't guess):** **Google Autocomplete** (`https://suggestqueries.google.com/complete/search?client=firefox&q=<term>` — a suggestion = real recurring demand; nothing back = nobody searches it; run it first as the demand/no-demand filter), **Google Trends** (direction + seasonality + rising terms), **Google Keyword Planner** (banded monthly volumes, free, no ad spend needed), **Bing Webmaster → Keyword Research** (free volume estimates), the **live SERP** (the only honest difficulty read — see *who* ranks: Reddit/Quora/AlternativeTo/forums → a new site can compete; high-DA listicles → the play is "get listed on them," not "outrank them"), and the **client's own GSC query data** (strongest first-party signal — queries already earning impressions are proven demand; mine this first).
> - **Method:** seed 5–10 terms → autocomplete-expand each → keep only variants the product can actually satisfy → band by volume → grade difficulty from the live SERP → output a short table `query | demand signal | difficulty | product fit | verdict`. If a term has no autocomplete suggestion *and* no GSC impressions, it is not a target — say so and move on. Separate **audience size** from **audience fit**: a big audience that won't convert is worse than a small one that will.
> - **AI-search guardrail:** persona-enriched prompts, conversation follow-ups, grounding predictions, observed fan-out queries, and AI citations can reveal language, context, and distribution opportunities, but they are **not keyword volume**. Label generated prompts as hypotheses until independently validated. Group related questions into the smallest set of genuinely useful pages; never recommend a separate page for every prompt/query variation. Google says ordinary SEO fundamentals apply to its AI features and warns against scaled pages made primarily to target query or fan-out variations.
> - **If the client has no keyword-volume source set up, add "Set up Google Keyword Planner" as a Week 1 / Tier 1 action in the report** — same pattern as the GA4 setup recommendation (§3.3). It's free (create a Google Ads account; no ad spend required) and it's the most authoritative free source of real search volumes. Spell out the beginner steps: create/sign in to Google Ads → Tools → Keyword Planner → "Discover new keywords" / "Get search volume." Without a volume source, content bets stay hypotheses — so getting this set up is a prerequisite for the *next* audit cycle's content plan.
> - **Optional — paid tools (only if the human already has one):** **Ahrefs** has an *official MCP server* — connect it for in-audit search-volume, difficulty, backlink, and competitor-gap data. **🔒 Per-client permission required before any call:** the MCP is connected machine-wide, so it is reachable from every project, but it bills to one specific client's account. Ask (**F-AHREFS**) and record the answer in the Ledger header before using it on a new client. Treat "it's connected" as availability, never as authorisation. **Surfer SEO** (content optimization + SERP/keyword research) and **Mangools / KWFinder** (budget-friendly volume + difficulty) — pull volume and difficulty from their dashboard or API and feed the numbers in. None of these are required — the free stack above grounds every recommendation — but a paid tool sharpens volume and difficulty calls. Never make the audit *depend* on one.

> **Evidence-backed content brief standard — new, refresh, and consolidation actions all use it.** Each `content-briefs/<id>.md` must state:
> - action ID and decision (`new / refresh / merge`), existing/source/destination URLs, and the existing URL(s) Google currently shows for the query cluster;
> - confirmed persona, user task, funnel stage, business/conversion goal, CTA, and why this action beats protect/leave-alone;
> - target query cluster with demand source, number/band, market, and date; actual GSC queries where available;
> - live SERP intent/page-type evidence, competitor/SERP changes, and what an existing page already covers;
> - the **unique first-party contribution**—experience, product data, examples, research, opinion, media, proof, or customer evidence—which prevents a commodity rewrite;
> - proposed outline and questions to answer (not a copied competitor outline or arbitrary word count), internal links in/out, supported schema, sources to verify, approvals/facts still needed, acceptance criteria, baseline, and 2/4/8-week measurement.
>
> An AI may research and draft from an approved brief, but must not invent product claims, customer evidence, author experience, statistics, or competitor facts. Do not publish one page per keyword/prompt variation; consolidate by user task. Re-check [Google's people-first guidance](https://developers.google.com/search/docs/fundamentals/creating-helpful-content) and [generative-AI content guidance](https://developers.google.com/search/docs/fundamentals/using-gen-ai-content) before drafting.

> **Claims discipline — every number and label must survive a skeptical, technical client.** (This is what makes or breaks trust in the whole report.)
> - **Derived ≠ total.** Quote the platform's own headline number for the *exact date range you name* (and state the range every time). Never sum per-page impressions and present the result as the site total — if you must derive a figure, label it derived and show the working.
> - **Tiny numbers stay absolute.** Below ~50 events, lead with absolutes, not percentages — "8 → 3 clicks/day," not "down 69%." Match adjectives to the real number: don't call 8 visitors in a month "rapid growth."
> - **Verify against the live site before asserting.** Open the page before you write "missing," "broken," or "fixed." Don't claim something's absent (an `llms.txt`, internal links, a docs index) without checking it isn't already there. A partial crawl is not ground truth — if Squirrel Scan reports an incomplete crawl (e.g. behind Cloudflare), caveat every score derived from it, and never report a grade jump as fact when two crawls covered different pages.
> - **Don't raise security carelessly.** On a static site, missing CSP/HSTS/X-Frame headers are near-irrelevant — putting "Security" in front of stakeholders raises alarm and erodes trust in everything else. Only flag a security issue that's genuinely exploitable for *that* architecture; if it's truly minor, leave it out.
> - **Don't overweight one-off events.** Separate a durable asset (a post that still ranks, a real backlink) from a transient blip (a traffic spike that's long gone and irrelevant by report time). Report the asset; drop the event.
> - **Right-size the report to the data you have.** Length is not value — you're paid for prioritisation and impact, not word count. Thin data → short report. If the honest answer is "do these three things and re-measure in a month," that *is* the report; don't inflate molehills into mountains to fill pages.

### 5.2 Required report structure (`REPORT_FILE`, in this order)
1. **Title line + friendly opener** — `# <Client> SEO Audit & Prioritized Recommendations` · prepared date · scope · data sources. Immediately under it, add one warm, plain-English line for non-technical readers, e.g.: *"New to SEO? Don't worry — every action in this report is something you can do yourself or hand to a developer. If you get stuck on any of it, just ask your AI assistant to walk you through it step by step."*

   Then, still near the top (right after the opener, before the SEO Health Score), add a short **"Get a second opinion"** callout. Keep it **AI-agnostic** — don't name a specific assistant or tool; the reader may be using any AI for both the audit and the sanity check. Use wording close to this:
   > **Get a second opinion (optional, but recommended).** This report was written by an AI assistant working from your audit data. For a sanity check, paste the whole report into a *different* AI assistant and ask it to critique the findings, priorities, and anything it thinks is missing or overstated. Then bring that feedback *back* to the assistant that wrote this report and tell it: *"Here's feedback from another AI on your report — but you have more data and context, so feel free to push back and hold your ground when you disagree."* That last line matters: the assistant that ran the audit has seen the raw data the second one hasn't, so you want it to defend the findings the evidence supports — not just agree with every critique.
2. **SEO Health Score** — a single 0–100 score + per-category table, on page 1 right after the opener. See **§5.6** for how to compute and render it.
3. **Leadership Summary** — 60-second entry point: ≤3 decisions required this week, the single biggest commercial opportunity (1 sentence), the core strategic observation, what success looks like at 90 days. NOT a findings list.
4. **Executive Summary** — see §5.3. This is the make-or-break section.
5. **90-Day Execution Plan** — *near the top, not the end.* Four groups: **Week 1** / **Month 1** / **This Quarter** / **Deprioritized for now**. Use **actions, not findings** ("Fix the pagination 404 bug", not "404s were found"). The Deprioritized group is mandatory — name what you're deliberately *not* doing and why. End **This Quarter** with an action to **trial a keyword-research tool — Ahrefs, Surfer SEO, or Mangools** — so the next cycle's content plan is backed by real volume and difficulty data rather than free-tool estimates.
6. **Top Revenue Opportunities** — ≤5-row table: priority / opportunity / why it's the top lever / where in report.
7. **Key Findings** — performance & technical / indexing / traffic & conversion / **content lifecycle decisions** / backlink baseline / historical SERP environment / **AI-search visibility when §3.11 applies** / **internal-search risk only when §3.12 produces a finding**. Include GSC Manual Action/Security status, material response-vs-rendered differences, Crawl Stats only when it produces a useful verdict, and Search Appearance outcomes where applicable—without padding the section when checks passed. The content block summarizes protect/refresh/merge/differentiate/build/prune decisions and their diagnosed causes; detailed URL rows stay in the evidence/appendix. **Open the traffic & conversion block with a "Traffic Trajectory" sub-section** (from §5.1 step 1): a small by-month table of site organic clicks, then a winners/losers table of the top pages/queries by click delta with the diagnosis for each loser. Say the direction in plain words before any opportunity framing. Include the **SXO** block here (~20 lines per URL: SERP table + verdict + headline gap + "what this reframes") if SERP work was done. Keep the AI-search block short: benchmark coverage, observed brand/citation baseline, recurring cited domains, validated on-site/off-site actions, and limitations—not a fabricated universal "AI rank." Keep an indexable-search finding in Indexing; mention unsafe reflected input separately only when the harmless test produced direct evidence, with the §3.12 calibration.
8. **Conversion Architecture** — traffic flow from content into the funnel. Answer these three explicitly, citing `ga4-conversion-paths.md`:
   - **Which blog/content pages drive product-page visits?** (ranked list)
   - **Which blog/content pages generate demo requests?**
   - **Which blog/content pages generate trial starts (or signups, or the equivalent primary conversion event)?**

   Then write the architecture finding: are the top-trafficked pages also the top-converting ones, or is there a disconnect (high traffic / no conversion paths out)? Identify pages that should link more aggressively to product (or where a product CTA is missing entirely). If GA4 isn't tracking the needed conversion events, that gap goes in Tier 1 — you can't optimize what you can't measure. If GA4 wasn't available at all, state that and recommend the tracking setup as the first thing to fix.
9. **Additional Findings from Skills** — one short section per skill cluster; drop anything not specific to this client.
10. **Security findings — keep light, or omit entirely.** SEO audits routinely over-flag security and **Squirrel is noisy here** — it has flagged a LinkedIn share button as a credential leak, and "missing" CSP / X-Frame-Options headers are frequently false positives. SEO is not our security specialization; surfacing speculative security issues worries clients for no reason and damages our credibility. **Default: omit this section.** Only include something if you've manually verified it *and* it actually affects crawling/indexing (e.g. an HTTPS setup breaking the crawl). When included, the disclaimer must read close to verbatim: *"This is not a security audit tool and often raises false flags — check to verify before acting."* Never put security findings in Tier 1.
11. **What NOT to Do** — client-specific anti-patterns.
12. **Prioritized Recommendations** — each item once: `#N. Title / Impact / Effort / Issue / Actions / Verification`, grouped Tier 1 → 2 → 3 → Defer. (No "owner" field — this tool doesn't know the client's team or resources, so don't invent one.)
13. **Programmatic SEO** — launch-order table only; full playbooks go to an appendix. **Column headers must be exactly: `Month | Playbook | URL pattern | First batch | Quality bar`.** The Month column holds plain numbers — `1`, `2`, `3`, … — *not* "Wave 1 (Month 1)" or "Wave 1" or any other label. Multiple playbooks can share a month (e.g. two rows both showing `2`). For deferred items, write `Defer` in the Month column rather than a number.
14. **Acquisition Channel Q&A** — the closing strategic section, framed by the goal **"turn this website into a predictable acquisition channel."** Answer every question in **§5.7** explicitly, as question-and-answer pairs, drawing on *all* collected sources (GSC, GA4, PSI, Squirrel, SERPs, skill outputs). Each answer cites its data or names the gap that blocks it (with the matching Tier 1 action). The section ends with the three §5.7 deliverables: the **website improvement roadmap**, the **lead magnet plan**, and the **content priorities for the next 6 months**.
15. **Verification & Tracking** — open this section by stating plainly that **SEO compounds over time: this audit is iteration 1, not a one-off.** Tell the reader to use the locally generated action runbook to ship approved batches, then **re-run this audit every ~2 weeks.** Be honest about the horizon — **ranking and traffic gains usually take 4–8 weeks** as Google re-evaluates — but explicitly make the case that re-running every two weeks is *worth it anyway*: each cycle ships the next batch of fixes and content so improvements start compounding sooner (instead of the site sitting idle for two months), and it catches regressions before they fester. Don't wait for one round to fully mature before starting the next — stack the cycles. Then: the baseline snapshot (`/seo drift baseline` after Tier 1 ships) + persistent change journal + monthly content-decision/decay/query-ownership/internal-link review + fixed competitor/SERP comparison + the **SEO Health Score re-run tracker** (§5.6). For schema actions include the same-appearance GSC 2/4/8-week outcome check. Include the "don't put drift on a cron with email alerts" note.
16. **From Audit to Action** — link to **[`ACTION-RUNBOOK.md`](./ACTION-RUNBOOK.md)** in the same client data folder. Explain that this audit generated it specifically from this report: it classifies every recommendation by how the AI can help, identifies research required before acting, proposes the first batch, implements only after approval, verifies live changes, measures at 2/4/8 weeks, and continues over time. Include this exact start command in a code block: `Follow ACTION-RUNBOOK.md and show me the Action Capability Map and recommended first batch. Research anything that may have changed. Do not change anything until I approve a batch.`
17. **Appendices** — data sources, detailed pSEO playbooks, and any comprehensive findings lists. **The appendix is for noise, not overflow:** if a section wouldn't change a decision-maker's choices, it belongs here, not the main body.

### 5.3 Executive Summary rules (the highest-leverage section)
- **Open with the 1–2 findings that reframe how the client thinks about their situation** — not "we found 50 issues." The reframing finding goes in paragraph one, never paragraph three.
- **A live decline is always the reframing finding.** If the Trend & Anomaly Scan (§5.1 step 1) shows organic traffic materially down and still falling, the summary opens with that — the drop, its diagnosed cause, and the fix — never with an opportunity pitch that ignores the bleed.
- State **what's working** in one short paragraph first (credibility; signals this isn't an attack).
- Name the findings that **determine the priority order**.
- Close with a **realistic 90-day expectation, with honest hedges** (Google re-evaluation takes 4–8 weeks; AI-Overview behaviour is volatile; some items depend on third parties).
- **Avoid:** starting with methodology/scope, listing all findings as bullets, false precision ("+30–40% traffic"), burying the lede.

### 5.4 Pre-delivery QA gate (must pass before marking the audit ✅)
Check each — if any fails, fix before delivering:
- [ ] Executive Summary opens with a **reframing finding**, not a contents list or methodology.
- [ ] One paragraph on **what's working** is present.
- [ ] **90-Day Plan appears near the top** (after the summaries), grouped by window, with a **Deprioritized group**.
- [ ] Every recommendation is **grounded in a specific data point** from the collected files (no generic advice).
- [ ] Each recommendation appears **once** (no duplicate matrix + detail).
- [ ] **Confidence calibrated** — "consistent with" for patterns you can't confirm; no unofficial thresholds cited as Google specs; schema claims softened (Google may still extract partial data).
- [ ] Implementation detail / exploratory frameworks / full playbooks are in **appendices**, not the main body.
- [ ] **For a thin site:** recommendations include **new TOFU/MOFU/BOFU content mapped to the confirmed target persona** — not only tweaks to the existing pages. The persona and "what good TOFU looks like" were **confirmed with the human, not guessed**.
- [ ] **GSC Indexing buckets are diagnosed, not just counted** (§3.2) — each non-trivial bucket has a stated cause and a fix lane. A list of counts with no "why" is a fail.
- [ ] **Indexing recommendations are count-aware** (§3.2 / §5.5 action-runbook requirements) — when more than 10 eligible canonical URLs need discovery or recrawl, the report recommends a sitemap with accurate `<lastmod>` values rather than trying to submit every URL individually. Ineligible exclusions are fixed or left out, not submitted.
- [ ] **The Site Profile exists and the conditional router was applied** (§1.4): active modules are named and every skipped conditional module has a reasoned N/A, not a silent omission.
- [ ] **GSC safety/freshness was completed or explicitly unavailable** (§3.1): Manual Actions, Security Issues, the preliminary 24-hour view, and overlapping Google incidents/anomalies are recorded. A crawler's noisy header warning neither substitutes for nor cancels an authoritative GSC security report.
- [ ] **Traffic trajectory is stated from the monthly series, not the window aggregate** (§5.1 step 1): the report says plainly whether organic clicks are rising, flat, or falling; every top page/query that lost >30% of clicks or >2 positions is diagnosed (or explicitly marked unexplained); GSC's own alert cards are answered. If the series couldn't be collected, the Executive Summary says "no trend analysis was possible." An aggregate presented as if it were trend-checked is a fail.
- [ ] **The change timeline was reconciled before causal claims** (§5.1): preliminary data is labelled, dated site/analytics/Google events were checked, correlation is described as "consistent with" unless independently confirmed, and no ranking-only seven-day auto-rollback is prescribed.
- [ ] **Every fact with a shelf life in `REPORT.md` has been checked, and dated** — run the verified `claim-check` package on `REPORT_FILE` and save the scan plus source-check notes. If its skill registration is unavailable, explicitly read its verified `SKILL.md` and execute its bundled `scripts/claim_check.py` by absolute path; that is use of the same package, not a manual substitute. If the package or script is missing, this gate remains blocked. Work the flagged list. Prices, limits, version numbers, anything switched off, and any claim about what a search engine will show. For each one: confirm it against the source and add the date you checked, correct it, or delete it. **Never date something you didn't actually go and look at.** A statistic with no link, or a named source that isn't linked, either gets its link or gets cut — an unsourced number in a client deliverable is all downside.
- [ ] **No retired schema type is recommended anywhere in the report** (§3.10) — `HowTo` (Google stopped showing these September 2023) and `FAQPage` (stopped for every site 7 May 2026). Both still pass every validator, so passing validation is not evidence. If either appears in the report as an opportunity rather than as "present but doing nothing", that's a fail.
- [ ] **Shared root causes are grouped before prioritization** (§5.1): `template-root-causes.md` maps findings to a template/component/plugin/config fix, affected pattern/count, representative proof, and regression sample. The report does not create one recommendation per affected URL for a shared bug.
- [ ] **Content decisions use the full lifecycle register** (§3.13): protect/refresh/merge/differentiate/redirect/noindex/remove/investigate verdicts cite comparable search trends, conversion, backlinks, user/business value, overlap, SERP, and technical evidence. No destructive action is based on age, word count, or low traffic alone; changing the date alone is never a refresh.
- [ ] **Crawl analysis is proportionate** (§3.14): Crawl Stats has an explicit healthy/problem verdict, and server logs are requested only for a large/rapidly changing or genuinely crawl-constrained site. Small healthy sites are marked log-analysis N/A rather than given crawl-budget theatre.
- [ ] **The backlink baseline names its coverage limits and forbids automatic disavow** (§3.15): GSC/Ahrefs/Bing indexes remain separate, material sources/targets were verified live, and no toxicity score or suspicious sample produces a disavow action without Google's strict criteria and exact human approval.
- [ ] **The fixed historical SERP panel exists or is reasoned N/A** (§3.16): only human-confirmed competitors are watched, the query/locale/device/page-type sample is reproducible, and observed changes are separated from causal/strategy interpretation.
- [ ] **The CMS/framework adapter exists or is reasoned N/A** (§3.17): an active adapter records the installed version, current primary sources, exact shared implementation surface, build/deploy path, affected/unaffected tests, and rollback.
- [ ] **Every new/refresh/consolidation recommendation has a populated evidence brief** (§5.1): persona/task, demand source/date/market, current owner, SERP intent, unique first-party contribution, CTA, internal links, supported schema, approvals, acceptance, and measurement are present. No invented product/customer/competitor facts.
- [ ] **Concentration risk is stated** — the #1 page's share of organic clicks is named; above ~25–30% there is a diversification action in the plan.
- [ ] **No "protect / leave alone / don't rewrite" verdict on a page whose recent trend wasn't checked** — "it has huge impressions so it works" is a claims-discipline violation unless the monthly series confirms the page is actually stable.
- [ ] **Conversion Architecture section is present** (§5.2 item 8) and answers all three: which content pages drive product-page visits, demo requests, trial starts. Or, if GA4 conversion tracking wasn't in place, that gap is named in Tier 1.
- [ ] **Acquisition Channel Q&A is present near the end** (§5.2 item 14 / §5.7): every topic and focus question answered as an explicit Q&A pair with cited data — or the gap named with its Tier 1 action — and the section closes with all three deliverables: website improvement roadmap, lead magnet plan, 6-month content priorities. No question silently skipped.
- [ ] **Schema findings cite Rich Results Test and/or schema.org validator output** for the 3 sampled URLs (§3.10) — not just Squirrel's static parse. (Or the F-ANSWER is still open and the limitation is noted.)
- [ ] **Schema outcome reporting separates valid, eligible, and shown** (§3.10): applicable Enhancement and Search Appearance baselines are recorded (or unavailable/none observed), with a same-appearance 2/4/8-week measurement plan and no promise of rich-result display.
- [ ] **Open Graph share metadata was checked on every manually audited page** (§3.6): `og:title`, `og:description`, and a resolvable `og:image`, with issues framed as share-preview quality rather than a ranking factor.
- [ ] **Response-vs-rendered HTML was checked or evidence-backed N/A** (§3.6): material JavaScript differences are corroborated with a Google view on a representative URL and carried into template-level acceptance criteria.
- [ ] **AI-search research is evidence-disciplined** (§3.11): fixed sample/config recorded; generated prompts are labelled hypotheses; unexposed grounding/searches are `unknown`; no page-per-prompt recommendations; and any skipped sample has a stated reason.
- [ ] **Internal search was checked or explicitly marked N/A** (§3.12): an ordinary query and harmless HTML-like marker were tested; indexability and escaping were recorded; crawlable `noindex` is recommended for indexable result pages; unsafe reflection is escalated without claiming a confirmed exploit.
- [ ] Opens with the warm, plain-English "**if you get stuck, just ask your AI assistant**" line, and the whole report is written so a non-technical reader can follow it (jargon explained, steps spelled out).
- [ ] **Page 1 carries a 0–100 SEO Health Score** + per-category table (§5.6), with the honest calibration caveats (AI-assessed, not a Google ranking factor; not comparable across sites; you don't manually score anything).
- [ ] **Missing setups are captured as Tier 1 actions** (e.g. "Set up GA4", and "Set up Google Keyword Planner" if the client has no search-volume source), with beginner steps — not skipped, and not done live during the audit.
- [ ] **No site work was performed during the audit** — no fixes, deployments, setup steps, or external-account changes; only `REPORT.md`, audit evidence, and—after this QA gate—`ACTION-RUNBOOK.md` are allowed.
- [ ] **The AI-writing branch is resolved:** kickoff §1.5 #8 is recorded; a “yes” has a completed `/remove-ai-slop-without-removing-ctas-and-good-marketing-copy` review with conversion-integrity verification and findings integrated into the report, while a “no” is explicitly marked ⏭️. No live/source page was edited during the audit.
- [ ] **Every content/keyword recommendation has real demand data behind it** (volume + difficulty from the free tools or GSC), or is explicitly labelled a hypothesis. No "high volume / low competition / 100× audience" claims without a number.
- [ ] **No spurious numbers:** no derived figure presented as a platform total; tiny counts reported as absolutes not percentages; adjectives match the real numbers; the named date range is on every metric.
- [ ] **Site facts verified live:** every "missing / broken / fixed" was checked against the actual page; incomplete crawls are caveated, not reported as fact.
- [ ] **Security raised only if genuinely exploitable** for this architecture (no boilerplate-header alarms on a static site).
- [ ] **Report is right-sized to the data** — thin data produced a short report, not padding; no narrative built on a one-off blip.
- [ ] Main report is readable in ~40 minutes and leaves the reader with a clear action plan.
- [ ] Report tells the reader **SEO is iterative** — ship everything, then **re-run every ~2 weeks** — and makes the case it's worth it *even though* ranking gains take 4–8 weeks (cycles compound sooner; regressions caught early).
- [ ] Report contains the **From Audit to Action** handoff, links to `./ACTION-RUNBOOK.md`, and includes the exact start command from §5.2 item 16.

Tick §2.4 as you go; the audit isn't done until §5.4 passes.

### 5.5 Generate the local `ACTION_FILE` — after report QA, before delivery

Once §5.4 passes, generate `ACTION_FILE` in the **same folder as `REPORT_FILE`**. This is a compiled, client-specific operating document—not a copy of this audit runbook and not a generic blank template.

**On the first audit:** create `ACTION_FILE` using the resolved dated filename.

**On a later audit:** if it already exists, reconcile it rather than overwriting it. Preserve approvals, completed actions, evidence, measurements, blockers, rollback notes, and session history. Add the new audit date/cycle, map new recommendations, mark genuinely superseded items with a reason, and retain unfinished work whose evidence is still valid.

The generated document must:

1. Name the client, site, audit date, source `REPORT.md`, data folder, current site stage, next re-audit date, the Site Profile summary, and active conditional modules.
2. Copy **every recommendation ID** from the report into an **Action Capability Map** with one label:
   - **Direct** — AI can make a bounded, reversible change in available code/CMS and test it.
   - **Draft** — AI can prepare copy, content, schema, redirects, navigation, analytics definitions, or a proposed diff, but needs approval before applying.
   - **Guide** — AI can walk the human through Search Console, GA4, DNS, CMS, or another account; the human handles login, passwords, and 2FA.
   - **Human decision** — pricing, legal, brand, deletion/merger, ownership, or business judgment.
   - **Research first** — current Google/platform/framework guidance or a missing fact must be checked before proposing work.
3. Recommend the first batch of no more than five actions, grounded in the report’s priorities and dependencies.
4. Include an **Action Ledger** with: action ID, report recommendation, root-cause/template group, capability, risk, target URLs/files/system, acceptance criteria, status, evidence, rollback, owner, and next check date.
5. Use states: Proposed → Approved → Researching → In progress → Local verified → Deployed → Live verified → Measuring → Complete; plus Blocked, Deferred, and Rolled back.
6. Require fresh primary-source research for search-sensitive, API, structured-data, analytics, framework/CMS, legal, or unfamiliar actions. Store `research question | source URL | source updated date | guidance | implementation decision`. Before repeating manual work at scale, explicitly research a supported bulk mechanism, API, feed, sitemap, import/export, template-level fix, or automation route.
7. Require baseline → scoped proposal → approval gate → implementation → local verification → authorized deployment → live verification → 2/4/8-week measurement → learning/next batch.
8. Classify risk:
   - **Green:** bounded and reversible; may run within an approved batch.
   - **Amber:** bounded copy/navigation/canonical/URL/schema/performance proposals that do not remove or merge content; show research and proposed diff first.
   - **Red:** destructive, business, legal, ownership, DNS, analytics definitions, bulk publishing, and content-lifecycle merge/prune/`noindex`/redirect/removal decisions; exact human authorization required.
9. Define cadence: after every deployment; weekly light checks; action batch roughly every two weeks; monthly trends/content lifecycle/indexing/conversion/backlink/fixed-SERP review **plus the fixed AI-search prompt/citation benchmark when applicable**; conditional Crawl Stats/log review while a documented crawl question is open; quarterly or post-major-change full audit.
10. Preserve unrelated user work, cap batches at five, distinguish local/deployed/live/measured states, and never promise indexing, rankings, rich results, or revenue.
11. When a report identifies an Open Graph issue, include it as a small, reversible action with acceptance criteria for `og:title`, `og:description`, a resolvable absolute-HTTPS `og:image`, and one tested share preview. Keep it separate from title/meta-description SEO claims.

**Mandatory template-level remediation and regression section inside the generated file:**

- Start from `template-root-causes.md`. Prefer one bounded change to the shared template, component, plugin, CMS field, or configuration over repeating the same edit on every affected URL.
- For each shared fix, record the URL pattern and affected count, representative failing URL(s), exact source of the shared behaviour, proposed diff, and why it addresses the evidence. If the shared source cannot be located, keep the action in Researching rather than improvising page-level edits.
- Test representative affected pages before and after, plus at least one unaffected page/template. For JavaScript-dependent changes, verify the HTTP response, rendered DOM, and a Google view where material. A browser-only pass is not enough evidence of Google visibility.
- Define rollback at the same shared level and preserve the pre-change snapshot. Use page-by-page work only when the root-cause table shows the pages are truly isolated.

**Conditional CMS/framework adapter workflow inside the generated file when §3.17 is active:**

- Carry forward the detected stack/version, rendering mode, hosting adapter, build/deploy commands, primary-source research, and action-to-shared-surface map from `implementation-adapter.md`.
- Replace generic implementation language with the exact file, component, template, CMS field, plugin setting, or configuration used by this client. Keep unknown mappings in Researching.
- Keep source changed, local verified, production build passed, built output verified, deployed, and live verified as separate ledger evidence. A build or preview is not proof of production.
- Preserve unrelated work, use the project's own validation commands, and require a representative affected plus unaffected regression test. Treat dependency/framework/plugin upgrades as separately scoped actions.
- For Astro, verify the production origin/config and route generation, shared head/layout fields, canonical/indexable sitemap membership, truthful source-backed `<lastmod>`, generated HTML, and the live site. Re-check current official Astro documentation for the installed major before changing code.

**Mandatory change journal and measurement discipline inside the generated file:**

- Carry `change-events.md` forward across audit cycles. Log each approved action/deployment with its action ID, timestamp, scope, target metric, acceptance result, and next 2/4/8-week check; also add material content/analytics changes and relevant Google Search incidents/data anomalies.
- Keep the preliminary 24-hour GSC view separate from finalized reporting. It can trigger investigation, not a causal verdict, ranking-only rollback, or stakeholder claim.
- Compare the target URL/template/query with its own baseline and, where possible, an unaffected comparison group. Describe a dated before/after relationship as **"consistent with"** unless rollback/reproduction, logs, or another controlled check confirms cause.
- Roll back immediately for a failed technical acceptance criterion or a pre-agreed business guardrail. Do not auto-rollback solely because rankings fall within seven days; Search recrawling/re-evaluation can take longer, so preserve the journal and use the scheduled 2/4/8-week reviews.

**Conditional Search Appearance outcome loop inside the generated file when a schema action exists:**

- Re-run the live Rich Results Test and schema.org validator immediately after deployment. Record technical validity separately from Google's eligibility verdict.
- At 2/4/8 weeks, compare the same applicable GSC Enhancement report and Search Appearance type, date basis, URLs, impressions, clicks, and CTR against `gsc-search-appearance.md`. Do not switch appearance types or windows to manufacture a lift.
- Report `valid`, `eligible`, and `shown` as distinct states. A valid result can remain unshown; no action may promise rich-result display.

**Mandatory content lifecycle + brief workflow inside the generated file:**

- Carry `content-inventory.md` forward as the decision register. On each monthly review update finalized comparable GSC/GA4 periods, the fixed SERP panel, backlink evidence, substantive content/deploy events, and the provisional decision; do not reset protected/history fields.
- Treat `>30% loss`, age, word count, and low traffic as review triggers only. Diagnose demand, seasonality, SERP/CTR, technical/indexing, tracking, competitor, conversion, link, support/legal, and user value before choosing Protect/Refresh/Merge/Differentiate/Redirect/Noindex/Remove/Investigate.
- Every new, refresh, or merge action must link to a populated `content-briefs/<id>.md`. The AI may research and prepare a proposed diff/draft, but must show the unique first-party contribution and obtain approval before applying or publishing. Missing product facts, experience, customer proof, statistics, and competitor claims remain explicit questions—never invented filler.
- Merge/prune/noindex/redirect actions are Red. Before approval snapshot/export the content, list inbound/internal links and conversions, verify the replacement serves the same user task, show redirect/canonical/sitemap/internal-link changes, state what is recoverable, and define live plus 2/4/8-week checks. Never bulk-prune from a traffic threshold.
- After publishing, verify status/canonical/indexability, response/rendered content, links, schema where applicable, sitemap/`lastmod`, analytics event/CTA, and the intended GSC query→Pages relationship. Add the action/deploy to `change-events.md`; measure the same page/query/market, not a convenient substitute.

**Conditional crawl workflow inside the generated file:**

- Carry the Crawl Stats baseline and `logs required / logs N/A` verdict forward. Small healthy sites do not get a log-analysis task simply to make the runbook look comprehensive.
- If logs are warranted, specify the bounded date range and minimum fields, privacy/retention handling, Google crawler verification method, hypotheses, patterns/URLs to test, and acceptance criteria before requesting data.
- Do not recommend crawl controls from crawl-volume movement alone. Require corroboration such as host/5xx/429 trouble, material discovered backlog, important-URL recrawl lag, or verified waste from facets/parameters/duplicates. Re-check Crawl Stats and affected patterns after any approved change.

**Mandatory backlink baseline + disavow safety workflow inside the generated file:**

- Preserve separate dated GSC, Ahrefs, and Bing baselines and their coverage limits. Compare like with like; never add provider indexes into a fake “total backlinks” metric or present sample churn as a confirmed lost link without live verification.
- Prioritize protecting valuable links, reclaiming useful broken/redirected targets, and evidence-based mention/outreach opportunities. External outreach remains Draft/Human-owned; the AI may research and draft but must not impersonate the client or send without approval.
- Never automatically generate, recommend, or upload a disavow file. A disavow action is Red and enters the ledger only when Google's criteria are plausibly met—a considerable artificial link pattern plus a related Manual Action or credible link-scheme risk—after expert human review, source verification, and removal attempts.

**Mandatory historical SERP panel workflow inside the generated file:**

- Copy the fixed queries, locale, device, collection method, page-type vocabulary, client landing URLs, and confirmed competitors from `serp-history-panel.md`. Re-run the same panel monthly; changes to the panel require a dated reason and preserve the old series.
- Record observed entrants/exits, page-type consensus, SERP-feature/owner changes, client landing-URL switches, and confirmed competitor movement. Separate observations from explanations; inspect live pages before attributing intent, freshness, content, or strategy.
- Feed a verified change into an existing content decision/brief when relevant. Do not create an action merely because a competitor moved, and never copy their text.

**Mandatory indexing playbook inside the generated file:**

- Qualify URLs first: `200`, crawlable, no `noindex`, intended canonical, not redirect/duplicate/soft-404/facet, useful unique content, relevant internal link, correct sitemap.
- **1–10 eligible URLs:** keep them in the sitemap; individual Search Console requests are optional for the most important/time-sensitive pages.
- **Fast Playwright workflow for 1–10:** ask the human to log into the correct Search Console account in the Playwright-controlled browser; confirm the property; open a **separate tab for every eligible URL** (maximum 10); stage all URL Inspection tabs before waiting for individual checks; work through the prepared tabs and record already indexed / accepted / blocked / failed / reauthentication / quota. Tabs save navigation time but never bypass quotas. Stop on challenges, wrong property, reauthentication, UI changes, or quota.
- **More than 10 eligible URLs:** do not click “Request indexing” for every page. Generate/update an XML sitemap containing all eligible canonical URLs and truthful `<lastmod>` values; keep its URL stable; submit it once in Search Console; reference it in `robots.txt`; reserve individual requests for only the small highest-priority set.
- Use a page’s real significant-change time for `<lastmod>`—never today/audit/build time for unchanged pages. One sitemap supports up to 50,000 URLs or 50 MB uncompressed.
- Record that URL Inspection API is read-only for indexed status and Google’s Indexing API is limited to eligible `JobPosting` and livestream `BroadcastEvent` pages.

**Conditional internal-search playbook inside the generated file when §3.12 produces a finding:**

- Map an **indexable-search-results** recommendation as **Amber** because `noindex`, canonicals, sitemaps, templates, and crawl paths can affect many URLs. Map **unsafe reflected input** as **Red / Human decision** for developer or security ownership; the AI may research, reproduce with the same harmless marker, and draft a fix, but must not run exploit payloads or declare XSS from this audit.
- For indexable result pages, propose a crawlable `noindex` directive in the page template or HTTP header, remove generated search URLs from XML sitemaps, and avoid exposing unlimited search-result URLs through crawlable internal links. Do not use `robots.txt` as the removal mechanism and do not block crawling until Google can observe `noindex`.
- For reflected input, use context-appropriate output encoding and safe framework/DOM APIs so the query is rendered as text rather than executable markup. Treat generic “sanitize it” advice as incomplete; name the output context and the exact template/component/sink to change.
- Acceptance criteria: ordinary search still works; both test URLs return the intended status; the harmless `<b>audit-search-check</b>` marker appears only as literal text or is omitted; no live element is created from it; `noindex` is present in source/headers and visible to crawlers; search URLs are absent from sitemaps; and a representative URL is rechecked in Search Console after recrawl.
- At the next monthly indexing review, sample the same URL pattern and compare indexed counts. Never promise immediate removal, and obtain explicit approval before any sitewide template or header change.

**Mandatory AI-search playbook inside the generated file when §3.11 applies:**

- Copy the audit's fixed 20–50 prompt benchmark into the action runbook; do not regenerate a new panel every month. Preserve persona/context, platform/model or surface, locale, and prompt wording so changes are interpretable.
- Rerun it monthly and record brand mentions, client citations, cited URLs/domains, observable grounding/search behaviour, and exposed search queries. Report directional change and sample counts—not a universal "AI ranking" or share-of-voice claim.
- Research current primary platform/API documentation before each automated run because models, grounding behaviour, returned fields, and access rules change.
- Split opportunities into **on-site** and **off-site** lanes. A repeatedly cited review, directory, publication, forum, or community source may make accurate inclusion/outreach the better action than publishing another client-owned page.
- Treat generated prompts, predicted grounding, and follow-up questions as hypotheses until supported by independent demand or customer evidence. Consolidate related questions by user task; never bulk-publish one page per prompt or fan-out query.
- Google requires no special AI file or schema for AI Overviews/AI Mode. Do not score or promise visibility from `llms.txt`, AI-specific markup, or any other unverified technical shortcut.

**Conditional bulk-action playbooks inside the generated file:**

- **Existing-content intent gaps:** when the report identifies valuable pages to improve, crawl the selected pages and compare their visible content with relevant People Also Ask/conversational questions. Keep only unanswered questions that fit the page's existing user task and have independent demand/first-party support. Draft a consolidated improvement, show the evidence and diff, obtain approval, then measure the page—never stuff every related question into it.
- **Migration / bulk redirect mapping:** when URLs are changing at scale, build a deterministic inventory of old and proposed URLs/content, use semantic embeddings/similarity only to produce candidate matches with confidence scores, and human-review low-confidence matches plus every commercially important URL. Before deployment verify intent/content equivalence, target `200`/indexability/canonical, no loops or chains, no many-to-one mapping without a documented consolidation decision, and an explicit unmatched-URL queue. This is an Amber `Draft` action; a generative model must not invent or deploy redirect targets autonomously.
- **Method references:** [intent-gap analysis using page content and related questions](https://searchengineland.com/improving-content-quality-scale-ai-442546) and [semantic candidate matching for redirect maps](https://searchengineland.com/site-migrations-ai-powered-redirect-mapping-437793). Treat these as starting methods; current primary search/platform documentation and the client's evidence determine implementation.

**Generation QA—do not deliver until all pass:**

- [ ] `ACTION-RUNBOOK.md` exists beside `REPORT.md`.
- [ ] The report’s `./ACTION-RUNBOOK.md` link resolves.
- [ ] Every active report recommendation appears exactly once in the Capability Map and Ledger.
- [ ] The first batch is client-specific and contains no more than five actions.
- [ ] Research questions and acceptance criteria are populated, not left as generic placeholders.
- [ ] The Site Profile and active/N/A conditional modules are carried into the generated runbook.
- [ ] Every shared finding maps to one root-cause/template action with representative affected and unaffected regression checks.
- [ ] The persistent change journal, preliminary-data guard, causal language, and 2/4/8-week measurement rules are present.
- [ ] The content decision register, evidence-brief gate, unique first-party contribution, Red merge/prune controls, and post-publish verification loop are present.
- [ ] Crawl Stats is carried forward with a reasoned logs-required/N/A verdict; any log request includes scope, privacy, crawler verification, hypotheses, and acceptance criteria.
- [ ] GSC/Ahrefs/Bing backlink baselines remain separate and the no-auto-disavow rule plus Google's strict escalation criteria are explicit.
- [ ] The fixed historical SERP panel preserves query/locale/device/page-type configuration and separates observed changes from interpretations.
- [ ] If §3.17 is active, the action runbook carries the versioned CMS/framework adapter, exact shared implementation surfaces, current primary sources, affected/unaffected tests, build/deploy/live evidence states, and rollback.
- [ ] The indexing route matches the eligible URL count and includes the Playwright small-batch workflow.
- [ ] Every JavaScript-dependent action verifies response HTML, rendered DOM, and a Google view where material.
- [ ] Every schema action includes the validity → eligibility → Search Appearance outcome loop.
- [ ] If §3.11 applies, the fixed AI-search benchmark, monthly comparison table, platform-research step, and on-site/off-site lanes are present.
- [ ] If §3.12 produced a finding, the action runbook separates the Amber indexability change from the Red unsafe-input review and includes the harmless-marker acceptance test.
- [ ] Any content-gap or migration-scale work uses the conditional bulk playbook and its human/approval gates.
- [ ] Every Open Graph action is separately framed and has a real share-preview acceptance test.
- [ ] Existing action history was preserved on a re-audit.
- [ ] No implementation or external change was performed.

After both files pass their gates, deliver them together and proactively offer the handoff without starting work:

> “Your audit package is complete: `REPORT.md` explains what matters, and the locally generated `ACTION-RUNBOOK.md` turns every recommendation into recurring, research-backed work. I can show you its Action Capability Map and recommended first batch now. I won’t change anything until you approve a batch.”

### 5.6 SEO Health Score (page 1, right after the opener)
Apply the missing-evidence rule in §1.4 first: withhold the overall score when a required category is unmeasured. With complete inputs, open the report with a single **0–100 SEO Health Score** and a per-category table. It gives an instant baseline and makes the 2-week re-run compelling ("we went from 62 to 68") — so it must be **reproducible**: an unchanged site should re-score within a point or two. **Read three categories straight off the tools** (no judgement): **Technical** = Squirrel Scan overall 0–100; **Performance** = mean PSI *mobile* score across the pages tested; **Schema** = validation pass-rate (valid blocks ÷ total). **Score the other four** (Content Coverage, On-Page, AI Visibility, Programmatic) against the rubric bands in methodology *SEO Health Score*, and **show your working** — one line per category citing the evidence and the band it hits. Apply the weights, sum, round:

| Category | Weight | What you're scoring |
|---|---|---|
| Content Coverage | 22% | TOFU/MOFU/BOFU funnel coverage for the target persona; thin-site penalty for low page count; are the high-intent pages (comparisons, integrations, alternatives) present? |
| Technical SEO | 20% | Squirrel Scan categories aggregated — crawlability, indexability, security headers, URL structure, mobile, links (its overall is already 0–100). |
| On-Page SEO | 18% | H1/title/meta completeness on key pages; heading hierarchy; keyword→page mapping; internal linking (spoke-to-hub, orphans). |
| Performance | 12% | PSI across top 3–5 pages (mobile); LCP/TBT/CLS; third-party bloat. |
| Schema Markup | 10% | Validation pass rate; missing required types per page type; @graph correctness. |
| AI Visibility | 10% | Fixed prompt benchmark coverage; observed brand mentions and client citations; recurring third-party cited domains; indexed, useful supporting pages; AI Overview/AI Mode appearance for validated target queries. Do not award points merely for `llms.txt` or "AI-specific" markup. |
| Programmatic SEO | 8% | Coverage of the five B2B SaaS playbooks (integrations, comparisons, personas, glossary, templates) and cluster launch-readiness. |

**Severity tiers:** Critical 0–39 · High 40–59 · Medium 60–79 · Low 80–100.

**Not applicable?** If a category genuinely doesn't apply (e.g. Programmatic SEO for a 5-page brochure site, or AI Visibility before any query data), mark it **N/A and renormalise the remaining weights to 100** — don't score it 0 and drag the total down; note what you excluded. If the Squirrel Scan crawl was incomplete (e.g. Cloudflare), label the Technical score *provisional*.

**Render it on page 1 like this**, then two sentences naming the two highest-impact levers (lowest score × highest weight, fixable soon):
```
## SEO Health Score: 62/100 (Medium)

| Category          | Score | Weight |
|-------------------|-------|--------|
| Content Coverage  | 45    | 22%    |
| Technical SEO     | 71    | 20%    |
| …                 | …     | …      |
```

**Calibration — be honest (this is a claims-discipline issue too, see §5.1):**
- It's a **consultant's assessment (anchored to the tools), not a Google ranking factor.** 80 doesn't mean you'll rank #1 — it means well-optimised against this methodology's criteria.
- **Not comparable across sites.** A 62 for a 5-page SaaS and a 62 for a 500-page publisher mean different things — compare a site only against its own history.
- **Don't chase the number.** The category breakdown tells the real story; the score just opens the conversation.

**Re-run tracker** — carry the score forward in the Verification & Tracking section so each cycle shows the delta (this is the single best retention hook — it turns a one-off doc into a recurring health check):

| Date | Score | Tier | Delta |
|---|---|---|---|
| 2026-06-01 | 62 | Medium | — (baseline) |
| 2026-06-15 | 68 | Medium | +6 |

**Reading the delta:** the tool-anchored categories (Technical, Performance, Schema — 42% of the score) move only when the underlying numbers do, so their deltas are real. Treat an overall swing of **±3 points or less as noise** unless it traces to a specific shipped change.

### 5.7 Acquisition Channel Questions (research at the END — answer in the report's closing Q&A)

**The standing goal these questions serve: turn the website into a predictable acquisition channel.** These are synthesis questions, not collection items — none of them is answerable from a single data source. Do **not** try to answer them early or piecemeal: research them as one of the *last* analysis steps of Phase 3 (§5.1), once GSC, GA4, PSI, Squirrel Scan, SERP captures, and the skill outputs are all in, then write the answers into the report's **Acquisition Channel Q&A** section (§5.2 item 14) as explicit question-and-answer pairs.

**Answer every question below.** Each answer must cite the specific data behind it (claims discipline §5.1 applies in full). If a question genuinely can't be answered yet — e.g. no GA4 conversion tracking — the honest answer *is* the gap: name what's missing, point to the Tier 1 setup action that fixes it, and label any forward-looking part a hypothesis. Never skip a question silently.

**Topic questions:**
1. **Website structure review** — does the page hierarchy, navigation, and internal linking support the buyer's journey from first visit to conversion, or are there structural blockers (orphan pages, dead ends, missing hub pages)? *(from `/site-architecture`, the Squirrel crawl, manual page audits)*
2. **Conversion optimization** — where do visitors actually leak out of the funnel? Which high-traffic pages have weak or missing CTAs, and what's the cheapest fix per leak? *(from GA4 landing pages + conversion paths + engagement, manual audits)*
3. **Product pages** — are they findable, ranking, and converting? What do the SERPs that matter expect a product page to look like that ours doesn't? *(from GSC pages, SERP captures + page-type classification, manual audits)*
4. **Landing pages** — which pages earn traffic and which earn conversions, and where do those two lists diverge? *(from GA4 landing pages vs `ga4-conversion-paths.md`)*
5. **Lead magnets** — what does the site offer today between "read a blog post" and "book a demo / start a trial"? (Often the honest answer is *nothing* — that's the finding.) Which lead magnets would fit the confirmed persona, and where should each live? *(from the persona, conversion-path data, competitor pages)*
6. **SEO opportunities** — what are the top unexploited demand pockets: queries with impressions but poor position, SERPs the site could realistically win, and gaps competitors rank for? *(from GSC queries, SERP captures, keyword demand data per §5.1)*
7. **Content strategy** — given all of the above, what should be written next and why — mapped to persona and funnel stage (TOFU/MOFU/BOFU per the thin-site guard)?

**Focus questions (answer these two with particular care — they're the spine of the section):**
- **What content attracts buyers vs. just traffic?** Rank top content twice — by traffic and by conversions driven (the Conversion Architecture data, §5.2 item 8) — and name which pages/topics/formats attract *buyers* and which only attract *readers*. The divergence list is the answer.
- **How does (and should) blog → lead magnet → demo/trial connect?** Map the path that exists today against the path that should exist: which posts should feed which lead magnet, and which lead magnet feeds which conversion event. Where the chain is broken or missing a link, say exactly where.

**Three deliverables close the Q&A section** (these are the section's output, not optional extras):
- **Website improvement roadmap** — the structure / UX / conversion changes, sequenced. May reference the 90-day plan for months 1–3 and extend directionally beyond it; don't duplicate the plan, point into it.
- **Lead magnet plan** — which magnets to build, in what order, the persona need each serves, which pages/posts offer it, and which conversion event it feeds.
- **Content priorities for the next 6 months** — months 1–3 lifted from the 90-day plan; months 4–6 directional, each priority keyed to demand data (or labelled hypothesis per §5.1).

---

## 6. Fallback prompt library

When blocked, emit one of these verbatim-ish, add it to §2.3, and set the Ledger row to 🙋. Keep each ask to a single clear action.

- **F-LOGIN:** "I've hit a Google login wall for {GSC|GA4}. Please log into the correct account in the browser window I have open, then tell me 'logged in' and I'll continue."
- **F-CAPTCHA:** "{site} is showing a CAPTCHA / 'unusual traffic' check in the browser window I have open (tab: {tab}). Could you solve it there and tell me 'done'? I'll carry on from where I stopped, and go slower afterwards."
- **F-FILE:** "I couldn't pull {data} from the UI. Please export it ({where}) and drop the file in `./audit-data/<client>/` — then tell me the filename."
- **F-ANSWER:** "I need a decision I shouldn't make for you: {question}. (I won't guess.)"
- **F-TOOL:** "The {tool} isn't available on this machine ({what failed}). Here's how to install/restore it: {note}. Tell me when it's ready."
- **F-AHREFS:** "The Ahrefs MCP is connected on this machine, but it bills to a specific client's Ahrefs account and I won't spend their credits without a yes. **Do you want me to use Ahrefs for this audit ({client})?** It would save the Google login step and give me {volume / difficulty / backlink / competitor-gap} data. If you'd rather not, say no and I'll use the free route (Playwright against GSC, plus Bing Webmaster where available) — the audit still completes, I just can't quote market search volumes."

---

## 7. End-of-session checkpoint (do before ending any session)

This is the **final reconciliation**, not the only time you write to the file — if you've followed the Prime directive (§0) you've been updating after every item all along, so this is mostly a last check. If the Ledger is far behind reality here, that's a sign you drifted mid-session; fix the habit next session.

1. Save every artifact you collected to the data folder (§1.3).
2. Update each touched **Ledger** row: status + location + one-line note.
3. Update the Ledger header: client, site URL, data-folder path, site stage, **Last updated**.
4. Move any new blockers into **§2.3** with a clear ask (use §6 wording).
5. Append one line to the **§2.5 session log** summarizing what you did and what's next.
6. In chat, give the human a 2-line status: "Done this session: … / Waiting on you: …".

That's the whole checkpoint. The next session — a fresh Claude Code run, or you the next day — opens this file, reads §2, and picks up exactly where you left off.

---

## Appendix: Optional enforcement hook (Claude Code power users only)

*Everything above works with no extra setup. This appendix is opt-in and **Claude Code only** — the desktop app doesn't support hooks. Skip it if you're not running in Claude Code.*

The in-file Prime Directive (§0) makes the model *want* to keep the Ledger current, but only a **Stop hook** can make the harness *enforce* it. This one blocks Claude from ending its turn if the runbook hasn't been touched recently, nudging it to reconcile the Ledger first. It's self-limiting — once the file is updated, the next stop is allowed, so there's no infinite loop.

**1. Save this as `.claude/hooks/handover-stop-check.mjs`** in your audit working folder:

```js
#!/usr/bin/env node
// Stop hook: nudge Claude to keep the handover Ledger current before finishing.
// Blocks stop (exit 2) only if the runbook hasn't been modified recently — once
// Claude updates it, mtime is fresh and the next stop is allowed (no loop).
import { statSync } from 'node:fs';

const RUNBOOK = 'seo-audit-runbook.md'; // path to your WORKING copy, relative to project root
const STALE_MINUTES = 10;               // tune to taste

try {
  const ageMin = (Date.now() - statSync(RUNBOOK).mtimeMs) / 60000;
  if (ageMin > STALE_MINUTES) {
    console.error(
      `Before stopping: update the handover Ledger (§2) in ${RUNBOOK} to reflect ` +
      `this session — statuses, file locations, blockers, and the session log — then stop.`
    );
    process.exit(2); // blocks the stop; this message is fed back to Claude
  }
} catch {
  // Runbook not found in this directory — don't block.
}
process.exit(0);
```

**2. Add this to `.claude/settings.json`** in the same folder (project-scoped, so it only fires during audits — not in your other projects):

```json
{
  "hooks": {
    "Stop": [
      { "hooks": [ { "type": "command", "command": "node .claude/hooks/handover-stop-check.mjs" } ] }
    ]
  }
}
```

**Notes & caveats:**
- Hooks run from the project's working directory, so keep your working copy of the runbook at the project root (or adjust `RUNBOOK`).
- This is a **reminder, not a correctness check** — it only knows whether the file was *touched* recently, not whether you updated the *right* rows. The QA still relies on the model following §0.
- Tune `STALE_MINUTES`; raise it if it nags during long read-only stretches.
- To turn it off: delete the `Stop` entry, or set `"disableAllHooks": true` in settings.
- This is config, not client data — never commit the `./audit-data/` folder.

---

*Companion to `seo-audit-methodology.md` (the reference manual). This runbook is safe to commit to your public repo — it contains no client data; all collected data lives in the local `./audit-data/` folder, which should be git-ignored.*
