---
description: Evidence-first SEO/AEO audit discipline — how to diagnose search and answer-engine visibility in this project and what never to recommend
alwaysApply: false
---

# SEO / AEO audit discipline

When asked to audit search visibility, diagnose a traffic drop, or plan SEO
changes, work in this order and keep the evidence rules.

## Non-negotiables

1. **Evidence or silence.** Every finding carries: the observation, where it was
   observed (URL, file:line, report), the value, and the date. No finding you did
   not verify on this site. "Best practice says" is not evidence.
2. **Tag the evidence tier** of every recommendation: CONFIRMED (engine-documented
   or reproduced here) · STUDY (published multi-site data) · FIELD (single case) ·
   HYPOTHESIS. Weights for prioritization: 1.0 / 0.7 / 0.4 / 0.2. A HYPOTHESIS
   never outranks a CONFIRMED blocker.
3. **Diagnose before prescribing.** "Add schema" is not a diagnosis. And never
   ship a FIELD or HYPOTHESIS change sitewide — run it as a controlled test (one
   variable, matched cohorts, 50+ URLs per group, 4+ weeks, control group,
   discard tests that overlap a core update).
4. **Refuse the myth list** in "Never put these in a plan" below. Recommending
   one wastes the client's budget and is a defect, not a nicety.
5. **Never recommend deceptive tactics** (cloaking, fabricated consensus, review
   manipulation, takedown abuse, click spoofing). They belong in an audit only as
   things to detect and defend against.
6. **State what you could not check.** A missing Search Console login is a gap in
   the report, not a silent omission.
7. **Never blend measured with assumed.** When one deliverable carries both — a
   link-building CSV always does — a `source` column separates them, and the
   volume cells of an unmeasured row stay **blank, not zero**. A `0` reads as
   "measured, no demand"; blank reads as "nobody has checked".
8. **Know each instrument's blind spot, and say it in the output.** Rules 1 and 7
   govern what you write; they do not see a tool that blends or omits before you
   look. A static fetch cannot see JSON-LD injected by JavaScript, so "no schema
   found" is a **false finding** on any Yoast/RankMath/AIOSEO site — confirm with
   a rendering check before reporting absence. GA4 with consent-mode modelling
   returns observed and estimated behaviour inside one number: modelling needs
   consent mode plus ~1,000 daily consent-denied events over 7+ days and ~1,000
   daily consenting users on 7 of the last 28 days, it applies when the reporting
   identity is **Blended**, GA4 marks it "Including estimated user data", and the
   BigQuery export carries observed data only. Before an instrument's silence
   becomes a finding, establish it could have seen the thing at all.

## The six instruments, and what each one cannot see

They ship inside the skill's own directory, not in the project you are standing
in — resolve that directory once and run them from it. Non-negotiable 8 is about
these: each states its own blind spot, and an absence one of them reports becomes
a finding only after you have established it could have seen the thing at all.

| script | what it settles | what it cannot see |
|---|---|---|
| `preflight.py` | which sources this audit can actually observe, and which gate a failure hit | Bing, Yandex, analytics, server logs, a crawl export, every MCP tool — a green preflight is not a covered access step |
| `page_audit.py` | per-page canonical traps, robots directives, headings, schema inventory, read budget | JSON-LD the CMS injects with JavaScript; a read cut off by `--max-bytes` publishes no counts at all |
| `url_inspection.py` | Google's own canonical, coverage state and robots verdict — the only source here that earns CONFIRMED | anything, when the index does not answer: those rows support no finding at any tier |
| `gsc_pull.py` | queries, position split, cannibalization, the property's own CTR curve, branded split | the long tail past the API row cap; a decline shallower than the cliff threshold; the branded split without `--brand-terms`, which it refuses to guess |
| `sitemap_audit.py` | declared URLs clustered into the template families the site actually ships | orphans — a sitemap holds no link graph, so that needs a crawl |
| `psi_pull.py` | field (CrUX) and lab (Lighthouse), kept strictly apart | field data where CrUX has none — reported absent, never as a pass, and the lab score never stands in for it |
| `agent_surface.py` | the agent surface: `.well-known` set, markdown negotiation and its `Vary`, `Link` headers, 404 shape, JSON-LD `sameAs`, OpenAPI function-calling readiness, the auth-discovery chain | effect — every probe answers *is it there*, which is not *does it work*; and one url is not a site |

Absent field data, an unanswered inspection and a capped row set are each a gap in
the report — never a zero, never a blank.

## Order of work

These thirteen steps are the working order, not the eleven audit tracks — several
steps serve the same track, and the point is the sequence: blockers before
optimization, cause before prescription. Step 13 is conditional and runs last.

1. **Blockers**: manual action; `noindex` in the pre-render source (it wins even
   when the rendered DOM is clean, and a `meta refresh` + `noindex` combination
   has no defined precedence — use a server-side 301); robots-blocked render
   resources; deindexation events (check for anomalous click *spikes*, not only
   drops, and verify every GSC property variant including www/non-www).
2. **Canonicalization**: self-referencing canonical everywhere; a canonical link
   carrying `media`, `type`, `hreflang` or `lang` is silently discarded by Google
   (framework `data-*` attributes are harmless); edge-rendered HTML must match
   the client version. On multi-locale sites, hreflang annotations must be
   bidirectional, self-referencing, use valid `language`/`language-region`
   codes, carry an `x-default`, and point only at indexable 200 URLs — hreflang
   routes users, it never substitutes for a canonical or for making the locales
   genuinely different.
3. **Indexation economics**: "Discovered – not indexed" is a crawl-priority
   problem (internal links from most-crawled pages, priority sitemap, clean
   server signals); "Crawled – not indexed" is the opposite bucket and discovery
   signals will not fix it — but its *cause* is contested (quality rejection vs
   authority deficit), so do not assert one in a report; the discriminating test
   holds content constant and adds links from strong nodes to one cohort only.
   Robots wildcards match substrings — `Disallow: /*?` takes out every
   parameterized URL behind it. Robots-*blocked*-but-indexed URLs are indexed
   strings with no content processed and dilute nothing; a mass of *crawlable*
   thin pages does.
4. **Architecture**: money pages within 1–2 clicks; kill equity leaks (tag
   archives, calendars, author bios, internal search); descriptive internal
   anchors; find orphans by diffing a crawl against the sitemap.
5. **Intent and cannibalization**: match the page type the top-10 rewards; >70%
   SERP overlap between two of your pages means one intent cluster — merge with a
   301, not a canonical.
6. **Content value**: does the page offer something, complete the task on-site,
   carry proprietary data, hold a tight focus? Original quantitative data is the
   one lever that moves information gain.
7. **Extractability / AEO**: answer in the first 100 words, one claim per
   sentence, facts in plain HTML (a JS-gated price makes engines cite an
   aggregator instead), 4–10 H2–H4 subheads, factual `alt` text, and keep
   navigation out of the top of the *source order* — an answer engine's first
   read is roughly 5,700 characters and every link marker spends it.
8. **Entity consensus**: the homepage H1 is the reference; align every
   third-party profile to it; brand as the grammatical subject of extractable
   insights; `Organization` schema with `sameAs`.
9. **Experience**: LCP triage order is TTFB → clear the LCP path →
   `fetchpriority="high"` → defer JS; image formats last. Native `<dialog>`
   instead of scroll-lock + blur overlays for INP.
10. **Decline attribution**: date-align the curve against the Google update
    timeline before naming a cause (2026: Jun spam 24–26 Jun · May core 21 May–2
    Jun · Mar core 27 Mar–8 Apr · Mar spam 24–25 Mar · Discover core from 5 Feb;
    2025: Dec core 11–29 Dec · Aug spam 26 Aug–21 Sep · Jun core 30 Jun–17 Jul ·
    Mar core 13–27 Mar). Ranking shifts often precede the announcement by 7–10
    days and continue after "complete"; spam-filter losses do not recover at the
    next core update; half of documented GSC outages coincided with rollouts.
    Source: searchenginejournal.com/google-algorithm-history/ and
    status.search.google.com.
11. **Post-click**: does the money template carry the elements that close
    (crawlable pricing, comparison naming real alternatives, implementation
    detail, proof with numbers)? Are calls, offline conversions and AI referrals
    tracked at all, or is last-click hiding whole channels?
12. **Measurement**: per engine, per persona; mentions and recommendations, not
    citations alone; cross-check GSC against analytics, logs and an independent
    tracker before calling anything an algorithmic hit.
13. **Agent surface** — only if the site sells something an agent could buy, call
    or automate; skip it for a content site. Start with the check that is cheapest
    and most often missed: **read the SERVER-RENDERED root and list which
    conventional entry points it links to** (`/api`, `/register`, `/about`,
    `/contact`). On a client-rendered site those links are added at hydration, work
    in every browser, and are absent from the document a crawler or an agent reads.
    A `301` to the homepage is not a page, however green a redirect-following
    scanner reports it. Then ship the parts that are true whether or not agents
    ever arrive: a real 404 status for unknown paths (a `200` app shell tells every
    consumer that every path exists), a unique `operationId` and
    a description on every OpenAPI operation (that is the function schema an LLM
    generates), rate-limit headers with `Retry-After`, and a `401` carrying
    `WWW-Authenticate: Bearer resource_metadata="…"` pointing at RFC 9728
    protected-resource metadata on the host that serves the API. Everything else —
    the `.well-known` set, `/auth.md`, markdown twins — is a draft spec or a vendor
    convention: **presence is confirmed by one request, effect is a hypothesis**,
    so it belongs in the Experiments bucket until the server log shows agent
    traffic. A third-party "agent-readiness score" is a checklist generator, never
    a target, and where it contradicts the refuted list below, the refuted list
    wins.

## Never put these in a plan

The fourteen asked for most often, out of 33 refuted claims the full skill
carries (install it for the counter-evidence and the working alternative to
each):

`llms.txt` as a ranking or citation lever · Markdown mirrors of HTML pages ·
"chunk your content" · rewriting text "for AI" · schema volume as an AI-citation
lever · FAQPage markup for rich results (retired) · AMP for ranking advantage ·
date bumps as freshness · "publish more pages" · disavowing on a third-party
toxicity score · a single AI-visibility score as a KPI · self-promotional "best
[category]" listicles · scaled AI content · `Disallow`-ing tracking-parameter
URLs to protect crawl budget.

The first two have one narrow non-myth use, and confusing it with the myth is how
the myth returns: `llms.txt` and markdown twins do not help a page get **found** —
that is the refuted claim, and it stays refuted — but they can let an agent that
**already arrived** read your canonical facts for fewer tokens. That is a serving
decision, not a ranking one. It is only honest when the markdown is generated from
the same source as the HTML, `Vary: Accept` is set wherever negotiation is on (or a
CDN hands the cached HTML to an agent asking for markdown), and any advertised
`rel="alternate"` actually resolves to markdown. A full `.md` twin of every page is
the refuted version.

## Deliverables

`docs/seo/audit-<date>.md` (Issue · Impact · Evidence · Cause · Fix · Effort ·
Tier · Verification) and `docs/seo/plan-<date>.md` (exact target, change, why,
expected effect and horizon, verification, rollback). Group the plan into
Blockers → Leaks → Gains → Experiments and end with one next action.
