---
name: "near-miss-seo"
description: "Find \"near miss\" Google Search Console queries (average position 5-20) for existing pages, group them by real search intent, and return the section headings that would close the gap — so pages that already rank get pushed higher instead of new content being written from scratch. Use whenever the user mentions near-miss keywords, striking distance, positions 5-20, GSC query gaps, \"rank an existing page higher\", optimizing or refreshing a live page, keyword gap analysis on a service page, or hands over a Search Console Queries/Pages CSV export. Also trigger on \"what headings should I add to this page\", \"why isn't this page ranking better\", or content-refresh requests for pages that already get impressions."
---

# Near-Miss SEO (Striking-Distance Gap Analysis)

Take pages that already rank in Google positions 5-20, find the searches they only half answer, and return the exact section headings that would close the gap. Optimizing an existing page that already earns impressions beats publishing new content and hoping.

## Core principle

If a page sits at position 5-20 for a query, Google already believes the page is relevant — just not the most relevant. The fix is almost never "more words." It is answering a question the page currently skips, in a section a skimmer can find.

## Step 1 — Scope the job

Ask (batch into one AskUserQuestion where possible) unless already provided:

- Which site / domain.
- Which pages to work on — usually the money pages: top 3-5 service or product pages. If the user says "whatever's best," pick by impressions.
- Country/market and whether they care about local intent (city + service).
- Whether they want headings only, or headings plus drafted copy (default: headings + gap analysis).

Do not proceed on more than 5 pages at once. Depth beats breadth here.

## Step 2 — Get the data

Try automated first, fall back to CSV. Never block on the CSV if a live source is available.

**Path A — live pull (preferred).** Check the available tools for Search Console-backed tools whose names end in:

- `gsc-pages` — page-level impressions/clicks/position. Use to pick pages and confirm they matter.
- `gsc-keywords` — query-level rows. This is the main dataset. Filter to the target page URL.
- `gsc-performance-by-position` / `gsc-ctr-by-position` — sanity-check the distribution and expected CTR lift.
- `gsc-page-history` / `gsc-keyword-history` — check whether a query is trending up or decaying.

If the tool schemas aren't loaded, load them with ToolSearch (`gsc keywords pages` style query) before calling. Use a 3-month window (the last 90 days) unless the user asks otherwise; shorter windows are noisy, longer windows hide recent movement. If the tools exist but no project is configured for the domain, say so plainly and switch to Path B rather than guessing.

**Path B — CSV export.** Give the user these instructions verbatim:

> 1. Open Google Search Console → **Performance** → **Search results**.
> 2. Set the date range to **Last 3 months**.
> 3. Turn on **Average position** (and Average CTR) at the top so those columns appear.
> 4. Add a filter: **Page** → *URLs containing* → paste the page path.
> 5. Click the **QUERIES** tab.
> 6. **Export** → *Download CSV* (or Google Sheets).
> 7. Send me that CSV plus the page — either the live URL or a paste of its full copy.

Accept the export in any shape: GSC's own CSV, an Excel workbook, a pasted table, or a Looker Studio export. Column names vary by locale (`Top queries` / `Query` / `Search query`; `Position` / `Average position`). Detect columns by fuzzy match, not exact string.

**Also get the page itself.** Fetch the live URL (web_fetch, or the Chrome tools if the page is JS-rendered and the fetch comes back empty). Read the actual headings, body copy, FAQ, and any tables. Without the page you can only guess at what's missing — do not skip this.

## Step 3 — Build the near-miss set

Filter query rows to:

- Average position **5.0 - 20.0** inclusive. This is the striking-distance band. Position 1-4 pages are already winning; past 20 usually needs a different page, not a new section.
- Impressions above a floor. Default floor: 10 impressions over 90 days, or the 60th percentile of the filtered set, whichever is higher. Single-impression queries are noise.
- Drop brand queries (company name, misspellings of it, "brand + login"), unless the user says otherwise.
- Drop queries whose intent belongs to a different page entirely (a plumbing page picking up "hvac repair"). Note these separately as **wrong-page queries** — they're a signal the site needs a new page, which is a different deliverable.

Run the filtering and math in code (pandas via bash), not by eye. Report the counts: total queries, in-band queries, surviving the floor.

Flag two special buckets while you're in there:

- **CTR underperformers** — position 5-10 but CTR well below what that position typically earns. That's a title/meta problem, not a content problem.
- **High-impression, position 15-20** — biggest upside, hardest lift.

## Step 4 — Cluster by what the person actually wants

This is the part that carries the value, and it is a judgment call, not a string-similarity job. Group the near-miss queries by the underlying job the searcher is trying to do — not by shared words. "how much does X cost", "X price", and "is X worth it" belong together; "emergency X near me" is a different person entirely.

Useful intent buckets for service and product pages:

- **Cost / pricing** — ranges, what drives price up, financing.
- **Timing / urgency** — how long it takes, same-day, after-hours, seasonal.
- **Eligibility / fit** — does this apply to my situation, my house, my size, my case.
- **Process** — what actually happens, step by step, what I need to prepare.
- **Comparison** — X vs Y, repair vs replace, DIY vs hire.
- **Trust / risk** — licensed, insured, warranty, what if it goes wrong, reviews.
- **Local** — city, neighborhood, "near me", service-area edges.
- **Troubleshooting / symptom** — the problem in the searcher's own words, before they know the solution's name.

For each cluster record: the member queries, total impressions, best and average position, and — after reading the page — a verdict of **missing**, **buried**, or **thin**.

- *Missing* — page never addresses it. Add a section.
- *Buried* — addressed in a mid-paragraph clause with no heading. Promote it to a heading.
- *Thin* — has a heading but one generic sentence. Expand with specifics.

## Step 5 — Write the heading map

For each page, output headings that are:

- Phrased the way the searcher phrases it, not the way the industry does. If people search "why is my AC blowing warm air," the heading is not "Refrigerant Charge Diagnostics."
- Natural as a heading, not a keyword string. `Repair or Replace: How to Decide` beats `AC Repair vs AC Replacement Cost Comparison Guide`.
- Placed. Say where it goes: after which existing section, and why that order matches how the reader's questions arrive.
- Scoped. One or two sentences on what the section must contain to actually answer the query — the specific number, range, timeline, or condition. A heading with vague filler under it does nothing.
- Ranked. Order the recommendations by impressions × position gap, so the user knows what to do first if they only do three things.

Cap it. 4-8 new sections per page. A page with 20 bolted-on H2s reads like a content farm and dilutes the original topic.

Also flag, separately from the headings:

- **Title/meta rewrites** for the CTR underperformers — those need no new content at all and often move fastest.
- **Internal link opportunities** — other pages on the site that should point at this one with the near-miss phrasing as anchor.
- **New page candidates** — the wrong-page queries from Step 3, with a one-line note on what page would own them.

## Step 6 — Deliver

Default deliverable: a Markdown file per page in the outputs folder, named `near-miss-<page-slug>.md`, containing:

1. **Snapshot** — page URL, date range, total impressions, clicks, near-miss query count.
2. **Priority actions** — the top 3, one line each.
3. **Intent clusters** — table per cluster: queries, impressions, position range, verdict.
4. **Recommended headings** — the heading, where it goes, what it must answer, which queries it targets.
5. **Title/meta rewrites** — current vs proposed, with reasoning.
6. **Internal links and new page candidates.**
7. **Appendix** — the full filtered near-miss query table.

Present the files with present_files. Keep the chat summary to a few lines: what you found, the single highest-upside fix.

If the user asked for drafted copy too, write the sections after the heading map is agreed — and use the `humanize-content` skill for the drafting so the new sections don't read as AI filler on an otherwise human page. For a spreadsheet deliverable instead, use the `xlsx` skill; for a client-facing document, `docx`.

## Guardrails

- **Never invent data.** If you don't have GSC rows, say so and ask for them. Do not estimate positions or impressions from general knowledge — those numbers are site-specific and unguessable.
- **Don't promise rankings.** Say "closes the gap on these queries," not "will rank #1." Position movement depends on competitors, links, and Google's own updates.
- **Respect the page's purpose.** A service page that converts should not be turned into a 4,000-word blog post. If the near-miss queries are informational and the page is transactional, the right answer may be a linked supporting article, not a new section — say that.
- **One page's data at a time.** Do not pool queries across pages when clustering; the page context is what makes the "half answers" verdict meaningful.
- **Sanity-check the position column.** GSC average position is an average across impressions; a query averaging 8.0 may be bouncing between 3 and 15. Where `gsc-keyword-history` is available, check volatility before calling something a stable near miss.

## Verification before delivering

- Re-read the page and confirm every "missing" heading is genuinely absent. Recommending a section that already exists destroys trust instantly.
- Confirm every recommended heading traces back to at least one real query in the filtered set.
- Confirm the impression and position figures in the deliverable match the source data (spot-check three rows against the CSV or tool output).
- Confirm no brand query survived into the recommendations.

