---
name: seo-machine
description: "Build an organic-traffic operating system for any site or app: a multi-phase, resumable engine that ships programmatic landing pages (alternatives, comparisons, use-cases, playbooks) on top of real keyword research. Use when the user says 'SEO machine', 'build organic traffic', 'rank on Google', 'we need traffic', 'alternatives pages', 'comparison pages', '/for/ pages', 'programmatic SEO', or 'build an SEO engine'. Distinct from `seo-audit` (one-off diagnostic) and `seo-content` (single-article writer): this skill owns research, planning, page generation, internal linking, off-page checklist, and the persistent phase tracker across the whole run. Resumable — every invocation continues the last run."
category: seo
tier: nice-to-have
reads:
  - brand/keyword-plan.md
  - brand/positioning.md
  - brand/competitors.md
  - brand/audience.md
  - brand/stack.md
writes:
  - docs/seo-machine.md
  - .seo/brand.md
  - .seo/link-inventory.md
  - .seo/config.json
triggers:
  - seo machine
  - build organic traffic
  - rank in google
  - alternatives pages
  - comparison pages
  - programmatic seo playbook
  - we need traffic
---

# SEO Machine

End-to-end engine for building organic search traffic. Ships dozens of programmatic landing pages — alternatives, comparisons, use-cases, playbooks — backed by real keyword research, with a persistent roadmap the user resumes across sessions.

Conversion-first (alternatives + comparisons before blogs). Publish-early (a thin page indexed today beats a perfect page indexed in three weeks). Stack-native (writes into the user's framework — Next.js, Astro, Rails+Inertia, or a portable markdown fallback).

## Two modes, auto-detected

| Mode | When | What it produces |
|---|---|---|
| **Initialize** | `docs/seo-machine.md` does not exist | Stack detection → brand context → keyword research → tech audit → roadmap + link inventory |
| **Resume** | `docs/seo-machine.md` exists | Reads the tracker, picks the next pending phase (or one the user names), executes end-to-end through the quality gates |

Detection rule on every invocation: check `docs/seo-machine.md` at repo root (or the path recorded in `.seo/config.json`). No file → Initialize. File exists → Resume. The user does not need to remember which mode they're in.

## Publishing Order (the load-bearing decision rule)

The roadmap orders patterns by **conversion intent first, traffic intent second**. This is the single most important decision the skill makes — it's why the alternatives + comparison pages ship before the playbooks + blog posts.

| Order | Pattern | Why | Cost |
|---|---|---|---|
| 1 | `/alternatives/[competitor]` (Pattern A) | Highest conversion intent — the reader is already in market | ~4 hours per page |
| 2 | `/compare/[a]-vs-[b]` (Pattern D) | Be the third option in any two-vendor comparison | ~3 hours per page |
| 3 | `/for/[use-case]` and `/for/[audience]` (Pattern B/C) | JTBD without competitor name in query — high TP/volume ratio | ~4-5 hours per page |
| 4 | `/playbooks/[topic]` (Pattern E) | Authority + AI-citation surface area + inbound link draw | ~2-3 days per page |
| 5 | `/blog/...` (out of scope here) | Lowest conversion intent; defer until ~10 indexed pages exist | — |

**Why this order matters:** alternatives pages convert 5–20× blog posts at 1/10 the search volume. Shipping one alternatives page is worth ten generic blog posts on the same topic. The roadmap should reflect this — Phase 0 (tech audit) → Phases 1–N (alternatives, then comparisons, then use-cases) → late phases (playbooks) → never (blogs, unless explicitly requested by the user).

Striking-distance boosts (pages already ranking pos 5–20 in GSC) jump the queue when they exist — fastest wins per hour of work.

---

## On Activation

1. Load brand context where present: `brand/keyword-plan.md`, `positioning.md`, `competitors.md`, `audience.md`, `stack.md`. Missing files are fine.
2. Surface what loaded:

## Backend Selection

1. **OpenSEO configured**: validate KD/competition BEFORE generating pages (`get_keyword_metrics`, `get_serp_results`) — programmatic SEO on unvalidated targets manufactures pages nobody can rank for. Consume `marketing/seo/clusters/*.md` from `openseo-keyword-clustering` when present.
2. **OpenSEO absent**: proceed with playbook validation (Exa + manual checks); mark competition estimates `unknown` in phase docs.
   ```
   Brand context loaded:
   ├── Keyword Plan    ✓/✗  (seeds the roadmap; ✗ → generate during Initialize)
   ├── Positioning     ✓/✗  (drives /compare and /alternatives angles)
   ├── Competitors     ✓/✗  (target list for /alternatives pages)
   ├── Audience        ✓/✗  (intent buckets for /for/<persona> pages)
   └── Stack           ✓/✗  (selects references/stacks/<framework>.md)
   ```
3. Progressive enhancement: with zero brand files the skill still works — it derives signal from the repo (`CLAUDE.md`, `README.md`, `package.json`, design tokens, existing marketing pages) during Initialize Step 2. With full brand context it skips re-discovery and goes straight to research + roadmap.

---

## Hard prerequisites

| # | Check | Failure mode |
|---|---|---|
| 1 | `git rev-parse --is-inside-work-tree` succeeds | Stop. Tell the user this skill writes a persistent roadmap; initialize git or run from a repo. |
| 2 | A stack is detectable — one of `package.json`, `Gemfile`, `composer.json`, `requirements.txt`, `astro.config.*`, `next.config.*`, `nuxt.config.*`, `gatsby-config.*`, `_config.yml`, `config.toml`, `pyproject.toml` | Ask the user what stack they're on before continuing. |
| 3 | Research backend resolved (see below) | Never refuse to run — fall through to manual mode. |

**Research backend resolution order** (record the choice in `.seo/config.json` under `research_backend`):

1. **Exa-stack** (mktg-native, default). Probe `mcp__exa__web_search_advanced_exa` with a minimal query. On success, use the full Exa research stack — Exa MCP + Firecrawl (SERP scrape) + `gh` (OSS competitor signal) + `/last30days` skill (Reddit/X/HN aggregation) + `mktg-x` (auth-walled Twitter) + the mktg-native `company-research` / `people-research` skills. The 7 Exa-native recipes in `references/exa-recipes.md` replace the Ahrefs cookbook entirely; the cross-API compound recipes in `references/api-stack-recipes.md` add capabilities Ahrefs alone can't do (pain-point clustering, OSS competitor teardown, outreach prospect discovery, newcomer surveillance).
2. **Manual mode** — Exa-stack unavailable. Follow `references/manual-research.md` (free Google + Search Console + paste-from-UI fallback).

> **Ahrefs as a footnote:** if `mcp__ahrefs__subscription-info-limits-and-usage` returns data AND the user wants numeric volume/KD/TP precision, layer Ahrefs on top of the Exa-stack rather than replacing it. The "Ahrefs appendix" at the bottom of `references/exa-recipes.md` documents which Ahrefs queries upgrade each Exa-native recipe with precise numbers. Ahrefs MCP is intentionally NOT in mktg's chained-in ecosystem table — it's a paid escalation, not a default dependency.

---

## Initialize mode

Goal: end the run with a written `docs/seo-machine.md` containing keyword research, a phase tracker grouped by pattern, and technical-audit findings — plus `.seo/brand.md` and `.seo/link-inventory.md`.

### Step 1 — Detect stack + frontend convention

Read `references/stacks/detection.md`. Identify framework family, routing convention (file-based vs controller-based), component language, and existing marketing pages (`git ls-files | grep -iE 'marketing|landing|pages/(home|about|pricing)'`). Resolve ambiguity with `AskUserQuestion`. Persist to `.seo/config.json`.

### Step 2 — Detect brand + product context

Read every signal first, propose `.seo/brand.md`, then ask only about gaps. Signals: `CLAUDE.md`, `README.md`, `package.json` / `Gemfile.lock`, `tailwind.config.*` or design-token CSS, `app/views/marketing/*` or `pages/index.*`, `pricing` page.

If `brand/voice-profile.md` and `brand/positioning.md` already exist (mktg-native project), read them and skip 80% of the questions — those files already say who the buyer is and what the brand sounds like.

Otherwise use a single `AskUserQuestion` (3–4 questions max) to fill gaps. Required to know:

- **Product one-liner** (≤20 words)
- **Primary persona** (e.g. "B2B SaaS founder", "indie agency owner")
- **3–7 direct competitors** by name
- **Brand voice tags** (e.g. "honest, technical, no-jargon")
- **Free tier?** (drives the "is [brand] free" keyword strategy)
- **Anti-positioning** — what the product does NOT do (used in honest comparison sections)

Write to `.seo/brand.md` using `assets/brand-template.md` as the skeleton.

### Step 3 — Keyword research (Exa-native stack)

Follow `references/exa-recipes.md` — 7 Exa-native recipes that replace the Ahrefs cookbook:

| Recipe | Purpose | Primary API |
|---|---|---|
| A. Domain baseline | DR estimate from indexed-page count + brand mentions + GitHub stars (OSS) | Exa `company_research_exa` + `web_search_advanced_exa` + `gh` |
| B. Competitor reverse-lookup | What competitors rank for | Exa `web_search_advanced_exa` `site:<competitor>` + AI summary |
| C. Use-case sweep | `/for/` and `/playbooks/` topic discovery | Exa `deep_search_exa` + Firecrawl autocomplete + `/last30days` |
| D. Comparison volume | Validate demand for `/compare/[a]-vs-[b]` before writing | Exa `web_search_advanced_exa` + Firecrawl SERP scrape |
| E. SERP saturation | Replaces Ahrefs KD with SERP-composition-derived signal | Exa search + per-result `company_research_exa` |
| F. Backlink prospecting | Referring-domain proxy via Exa | Exa search + DR proxy (see `off-page-seo` skill) |
| G. Content gap | Their indexed keywords − ours, ranked by DR-cap fit | Recipes B + own-site Exa lookup |

Then pull cross-API compound recipes from `references/api-stack-recipes.md` for moves Ahrefs alone can't do — pain-point cluster discovery (`/last30days` + `mktg-x` + Exa deep search), OSS competitor teardown (`gh` + DeepWiki + Exa), outreach prospect discovery (Exa Websets + people-research), newcomer surveillance (Exa Websets cron).

When Exa is unavailable, fall back to `references/manual-research.md` (paste-from-UI workflow). When the user has paid Ahrefs, layer it on top per the appendix in `exa-recipes.md` — don't replace.

Cache the raw output to `.seo/keyword-research.json`. Curate the decision-ready summary into the **Keyword Research Appendix** section of `docs/seo-machine.md`.

### Step 4 — Technical foundations audit

Run `scripts/tech_audit.py` (sitemap, robots, meta-tag uniqueness, schema). Whatever it finds becomes **Phase 0** in the roadmap. Day-0 crawl shapes Google's understanding of the site for months — retroactive fixes are harder.

### Step 5 — Generate the roadmap

Fill `assets/roadmap-template.md` with:

- **Site facts** (domain, DR, stack, brand colors, fonts) from steps 1–2
- **Reference data** — paths to controllers / page files this skill will edit
- **Keyword Research Appendix** — curated, grouped by pattern
- **Phase Status Tracker** — auto-populated:
  - Phase 0: technical foundations fixes
  - Phase 1+: one row per page candidate
  - Group by pattern. Within a pattern, order by `traffic_potential` desc → `volume` desc → `KD` asc
  - Striking-distance boosts near the front (fastest wins on existing sites)
  - Off-page checklist (directories + outreach) tail-end, one phase per category

Write to the configured path (default `docs/seo-machine.md`). Print the path back and recommend a human review pass before Phase 0.

### Step 6 — Generate `.seo/link-inventory.md`

Every phase reads this file to pick internal links. Use `assets/link-inventory-template.md`. Pre-populate from existing routes (features, tools, pricing, blog posts). Each phase appends to it as new pages ship.

### Step 7 — Hand off

```
✓ Initialize complete

  Roadmap:        docs/seo-machine.md
  Brand context:  .seo/brand.md
  Keyword cache:  .seo/keyword-research.json
  Link inventory: .seo/link-inventory.md
  Config:         .seo/config.json

Next: review docs/seo-machine.md, then run me again to execute Phase 0.
```

Do not auto-execute Phase 0. Pattern priorities and competitor lists are decisions worth a human pass.

---

## Resume mode

Goal: pick the next phase from the tracker, execute end-to-end with quality gates, hand back so the user can commit/PR.

### Step 1 — Read state

Load `docs/seo-machine.md`, `.seo/brand.md`, `.seo/link-inventory.md`, `.seo/config.json`. Find the **Phase Status Tracker**, identify the next `pending` phase with the lowest number, print it back along with the two phases that follow.

### Step 2 — Confirm scope

Single `AskUserQuestion`:
- "Continue with Phase N: [title]?"
- Options: "Yes, start Phase N" / "Pick a different phase" / "Re-audit (refresh research)" / "Just show the tracker"

"Different" → list pending phases. "Re-audit" → loop back into Initialize steps 3–5. "Show" → print tracker and stop.

### Step 3 — Execute

Pattern → reference mapping:

| Phase type | Reference |
|---|---|
| Phase 0 — technical foundations | `references/technical-audit.md` |
| Pattern A — `/alternatives/[competitor]` | `references/patterns/alternatives.md` |
| Pattern B/C — `/for/[use-case]` or `/for/[audience]` | `references/patterns/use-case.md` |
| Pattern D — `/compare/[a]-vs-[b]` | `references/patterns/compare.md` |
| Pattern E — `/playbooks/[topic]` long-form | `references/patterns/playbooks.md` |
| Striking-distance boost | `references/striking-distance.md` |
| Off-page checklist phase | `references/off-page.md` |
| Internal-link spine audit | `references/quality-bars.md` (link-audit section) |

For page-generating phases, the flow is always:

1. **Re-research** — current competitor pricing, feature changes. Don't trust 60-day-old cached data on commercial-intent terms.
2. **Generate the page payload** — output format follows the stack (`references/stacks/<framework>.md`) or `references/stacks/markdown-fallback.md`.
3. **Verify** against the quality bar (word count, internal links, schema, honesty section on alts). Run `scripts/word_count.py` and `scripts/link_audit.py`. If a check fails, fix it — don't ship under-spec work.
4. **Update `.seo/link-inventory.md`** with the new page.
5. **Update the tracker row** in `docs/seo-machine.md` — status `completed`, PR ref or commit SHA. Same edit batch as the page work so reviewers see both in one diff.

### Step 4 — Hand off (do NOT auto-commit)

```
✓ Phase N complete: [title]

Files changed:
  app/controllers/marketing_controller.rb  (added entry)
  app/frontend/pages/Alternatives/Show.tsx (no change — uses existing layout)
  docs/seo-machine.md                      (tracker updated)
  .seo/link-inventory.md                   (new page registered)

Quality gates:
  ✓ Word count: 712 / 600 min
  ✓ Internal links: 2 alts, 1 feature, 1 tool
  ✓ FAQ JSON-LD attached
  ✓ Honesty section present (3 rows)

Suggested commit: "SEO Phase N: ship /alternatives/[slug]"

Next phase pending: Phase N+1 — [title]
```

Open a PR only if the user has expressed they want that cadence. Otherwise let them drive git.

---

## Interactive principles

| Rule | Why |
|---|---|
| Ask before you guess on positioning calls | The user's gut on which competitor or use-case to ship next often beats the data |
| Show the numbers, not the conclusion | "vol 400, KD 12, TP 1,800" builds more trust than "the data is good" |
| Be honest about what won't work | KD 80 at DR 8 = doomed. Say so, offer the closest winnable alternative |
| One-sentence theory max per phase | The user doesn't need a recap of SEO theory every run |
| Surface tradeoffs, not opinions | "5 thin alternatives or 2 deep ones first?" — state the tradeoff, ask |

---

## Adjacent skills (route here vs there)

| Skill | When | When NOT |
|---|---|---|
| `seo-audit` | One-off diagnostic on an existing site | Forward execution — use this skill |
| `seo-content` | Writing a single rankable article | A whole sprint — use this skill |
| `competitor-alternatives` | One `/alternatives/<x>` page, ad-hoc | Pattern A as part of a sprint — use this skill |
| `keyword-research` | Producing `brand/keyword-plan.md` | This skill consumes that artifact — chain them |
| `ai-seo` | LLM-citation optimization for a few pages | Run after this skill ships ~10 indexed pages |

---

## Anti-patterns

| Don't | Why |
|---|---|
| Target head terms when DR is low | "Social media management tool" at DR 8 is wasted work. KD ≤ DR+10 while DR is low. |
| Ship pages with no inbound internal links | A new page nobody links to is an island. ≥2 inbound links from existing pages, same phase. |
| Ship `/alternatives` pages without an "honesty" section | Three honest tradeoffs (where the competitor wins) is non-negotiable. Brand signal AND Google quality signal. |
| Skip schema markup | FAQPage JSON-LD captures snippets. Article for playbooks. SoftwareApplication for homepage + use-case. Quality gate fails without them. |
| Auto-commit | Always show diff summary; let the user commit (local conventions, hooks, branch naming). |
| Restart Initialize when partial state exists | If `.seo/config.json` exists without the roadmap, prompt before wiping — they may have aborted mid-run. |

---

## File map

| File | Purpose |
|---|---|
| `references/methodology.md` | Why-this-works theory + lessons from real phases |
| `references/exa-recipes.md` | 7 Exa-native research recipes (replaces the old Ahrefs cookbook). Ahrefs appendix at the bottom for paid-precision escalation. |
| `references/api-stack-recipes.md` | Cross-API compound recipes — pain-point mining, OSS competitor teardown, outreach prospect discovery, newcomer surveillance. Uses Exa + Firecrawl + gh + mktg-x + DeepWiki + last30days. |
| `references/manual-research.md` | L0 fallback when no MCP available |
| `references/patterns/alternatives.md` | Pattern A spec, data shape, quality bar, example |
| `references/patterns/use-case.md` | Pattern B+C spec |
| `references/patterns/compare.md` | Pattern D spec |
| `references/patterns/playbooks.md` | Pattern E spec (2,500-word bar) |
| `references/stacks/detection.md` | Stack-detection signal table |
| `references/stacks/rails-inertia.md` | Rails + Inertia adapter (reference implementation) |
| `references/stacks/nextjs.md` | Next.js App Router adapter |
| `references/stacks/astro.md` | Astro content-collection adapter |
| `references/stacks/markdown-fallback.md` | Universal markdown output format |
| `references/technical-audit.md` | Phase 0 recipes (sitemap, robots, meta, schema) |
| `references/striking-distance.md` | GSC pos 5–20 audit + boost recipe |
| `references/off-page.md` | Backlink checklist + Ahrefs referring-domains research |
| `references/quality-bars.md` | Verification spec per pattern |
| `scripts/word_count.py` | Strip markup → word count |
| `scripts/link_audit.py` | Verify internal-link minimums per page |
| `scripts/tech_audit.py` | Sitemap.xml + robots.txt + meta-tag scanner |
| `assets/roadmap-template.md` | `docs/seo-machine.md` skeleton |
| `assets/brand-template.md` | `.seo/brand.md` skeleton |
| `assets/link-inventory-template.md` | `.seo/link-inventory.md` skeleton |

---

## Outputs

The skill writes to **project paths**, not `brand/`. This is why `writes:` in `skills-manifest.json` is `[]` — the manifest declares only `brand/*.md` writes, and seo-machine's outputs live in the user's repo, not in brand memory.

| Output | Mode | Purpose |
|---|---|---|
| `docs/seo-machine.md` | Initialize (write), Resume (update phase row) | Persistent sprint roadmap — Phase Status Tracker + Reference Data + Keyword Research Appendix. Single source of truth across sessions. |
| `.seo/brand.md` | Initialize | Product context derived from repo + user answers (one-liner, persona, competitors, voice tags, anti-positioning). |
| `.seo/link-inventory.md` | Initialize (create), Resume (append per phase) | Every internal-link target the skill can use. Each phase appends new pages. |
| `.seo/keyword-research.json` | Initialize | Cached raw output from Ahrefs / Exa / manual research. Read by every phase, refreshed every 30 days. |
| `.seo/config.json` | Initialize | Stack info, research backend choice, project IDs. Persists machine-readable state. |
| Phase-specific page files | Resume (per phase) | Stack-native page files (e.g. `app/controllers/marketing_controller.rb` + `app/frontend/pages/Alternatives/Show.tsx` for Rails+Inertia; `app/[pattern]/[slug]/page.tsx` for Next.js App Router; etc.). Paths follow `references/stacks/<framework>.md`. |

This follows the **Long-Arc Sprint Persistence Pattern** documented in `AGENTS.md` — `docs/seo-machine.md` is the canonical doc that survives session interruption.

## /cmo integration

`/cmo` routes here under **Playbook #9 — SEO Authority Build, Path B (programmatic sprint)**. On a cold project /cmo first spawns `mktg-brand-researcher`, `mktg-audience-researcher`, `mktg-competitive-scanner` in parallel (foundation), runs `keyword-research` to populate `brand/keyword-plan.md`, then hands off here. After every phase, /cmo spawns `mktg-content-reviewer` + `mktg-seo-analyst` in one message to score the page batch before the user commits.

See `skills/cmo/rules/playbooks.md` and `skills/cmo/rules/sub-agents.md` for the full handoff contract.
