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
- Load brand context where present:
brand/keyword-plan.md,positioning.md,competitors.md,audience.md,stack.md. Missing files are fine. - Surface what loaded:
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) - 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):
- Exa-stack (mktg-native, default). Probe
mcp__exa__web_search_advanced_exawith a minimal query. On success, use the full Exa research stack — Exa MCP + Firecrawl (SERP scrape) +gh(OSS competitor signal) +/last30daysskill (Reddit/X/HN aggregation) +mktg-x(auth-walled Twitter) + the mktg-nativecompany-research/people-researchskills. The 7 Exa-native recipes inreferences/exa-recipes.mdreplace the Ahrefs cookbook entirely; the cross-API compound recipes inreferences/api-stack-recipes.mdadd capabilities Ahrefs alone can't do (pain-point clustering, OSS competitor teardown, outreach prospect discovery, newcomer surveillance). - 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-usagereturns 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 ofreferences/exa-recipes.mddocuments 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_resea |