@seoagent-official/seoagent 1.66.0 → 1.67.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +109 -104
- package/package.json +1 -1
- package/skills/references/audit-checks.md +43 -15
- package/skills/references/migration-planning.md +56 -0
- package/skills/references/sitemaps.md +1 -1
- package/skills/seoagent.md +24 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seoagent-official/seoagent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.67.0",
|
|
4
4
|
"description": "The persistent AI SEO agent for Claude Code. Audits, keyword strategy, briefs, articles, real product screenshots from your repo, and the autopilot loop (cloud detects → CLI executes → ack closes) — other SEO tools write the prompt, SEOAgent runs it.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
Loaded by Phase 1 (Technical SEO Audit) when running a full audit. The main `SKILL.md` summarizes the categories; this reference holds the complete check list with the exact pattern to match and the recommendation text.
|
|
4
4
|
|
|
5
|
+
## Verify-before-assert (read first — non-negotiable)
|
|
6
|
+
|
|
7
|
+
**Every factual claim about a page's live state must be grounded in an actual fetch of the live URL — never in repo source, memory, or a prior.** Before running these checks, run `seoagent crawl` (Phase 1 Step 0). It writes `.seoagent/audit/evidence.md` — the live-crawl evidence base (exact title, meta, ALL H1s, canonical + server/client-render flag, every JSON-LD `@type`, OG/Twitter tags, the ACTUAL robots.txt contents, the sitemap URL + blog-post counts, client-rendered-shell detection). Read that file and derive Confirmed findings from it.
|
|
8
|
+
|
|
9
|
+
Three rules govern every finding:
|
|
10
|
+
|
|
11
|
+
1. **Confidence label (mandatory on every finding).** Tag each finding `Confirmed` (verified against the live evidence in `evidence.md` / a fetch you just did), `Likely` (strong inference, not directly verified), or `Hypothesis` (unverified). **Never emit an unverified specific — a price, a line number, a competitor name/roster, a dynamic on-page metric — as a bare fact.** A "2,184 families" style counter that is NOT in the server-fetched HTML is at most `Likely`/`Hypothesis`, never `Confirmed`.
|
|
12
|
+
|
|
13
|
+
2. **Never recommend adding something that already exists.** If `evidence.md` shows the page already serves `Organization` JSON-LD, a canonical tag, OG tags, etc., do NOT recommend adding them — that's the stale-source hallucination this guardrail exists to stop. Only recommend adding what the live evidence shows is genuinely absent.
|
|
14
|
+
|
|
15
|
+
3. **Live-vs-source reconciliation.** When repo/source state and the live-rendered page can disagree, grade the finding against the LIVE site. A repo-only issue (e.g. a `noindex` in a source file that isn't in the served HTML, a robots rule in a committed file that the live `/robots.txt` doesn't serve) must be labeled **"repo-only, unconfirmed on live"** and reported separately — never as production reality.
|
|
16
|
+
|
|
17
|
+
**Do not assert repo-internal specifics (file paths, line numbers) as fact in a live-state finding.** They are fixing *hints*, not verified live facts — mark them `Likely` and phrase as "likely in `app/layout.tsx`", never "on line 42".
|
|
18
|
+
|
|
5
19
|
## Crawlability & Indexation
|
|
6
20
|
|
|
7
21
|
### `robots_txt_exists`
|
|
@@ -10,9 +24,9 @@ Severity if fail: `high`
|
|
|
10
24
|
Recommendation: "Create a robots.txt file at {domain}/robots.txt. At minimum: `User-agent: *\nAllow: /\nSitemap: {domain}/sitemap.xml`."
|
|
11
25
|
|
|
12
26
|
### `robots_txt_blocks_important_paths`
|
|
13
|
-
Check:
|
|
27
|
+
Check: use the ACTUAL robots.txt contents from `evidence.md` (the crawl fetched and parsed the live file). Match its real `Disallow:` rules against the URL list in `pages.md`.
|
|
14
28
|
Severity: `critical` if blocks > 5 known important pages; `high` if blocks any important page.
|
|
15
|
-
Recommendation: List the blocking rules and which pages they affect.
|
|
29
|
+
Recommendation: List **only the blocking rules that actually appear in the fetched robots.txt** and which pages they affect. **Never invent a rule** (e.g. do NOT claim `Disallow: /private/` unless it is verbatim in the fetched file) — quoting a rule that isn't there is the exact hallucination this audit must avoid. If robots.txt wasn't fetched, this check is `Hypothesis` — say "couldn't verify robots.txt" rather than asserting any rule.
|
|
16
30
|
|
|
17
31
|
### `sitemap_exists`
|
|
18
32
|
Check: WebFetch `{domain}/sitemap.xml`. Pass = 200 OK and parseable XML.
|
|
@@ -30,9 +44,9 @@ Severity: `critical` if homepage; `high` otherwise.
|
|
|
30
44
|
Recommendation: "Remove the `noindex` directive from {file/URL}."
|
|
31
45
|
|
|
32
46
|
### `canonical_tag_present`
|
|
33
|
-
Check: `<link rel="canonical" href
|
|
47
|
+
Check: `evidence.md` shows the page's `canonical` — it captures the `<link rel="canonical">` href AND whether it's server-rendered. Do NOT conclude "no canonical" from WebFetch — it strips the head. Pass = a canonical present in the server HTML.
|
|
34
48
|
Severity: `medium` if absent; `high` if points to a different domain.
|
|
35
|
-
Recommendation: "Add a self-referencing canonical tag: `<link rel=\"canonical\" href=\"{full URL}\">`."
|
|
49
|
+
Recommendation (only if genuinely absent per evidence): "Add a self-referencing canonical tag: `<link rel=\"canonical\" href=\"{full URL}\">`."
|
|
36
50
|
|
|
37
51
|
### `redirect_chain`
|
|
38
52
|
Check: follow redirects, count hops.
|
|
@@ -67,12 +81,19 @@ Recommendation: "{dependency_url} returned {status}. The pages it powers ({list}
|
|
|
67
81
|
|
|
68
82
|
> **Why this check matters:** the most common SEO disaster on a live site is a CMS that's been quietly down for weeks — every page returns 200, the audit looks clean, but Google has been deindexing the blog the whole time. This check catches it on the first audit.
|
|
69
83
|
|
|
84
|
+
### `client_rendered_shell`
|
|
85
|
+
Check: `evidence.md` flags the page as a client-rendered shell — the server HTML is near-empty and/or shows a "Loading…" placeholder with no in-body links. The classic case is a blog index that ships a `Loading…` shell with no post `<a>` links in the server HTML: crawlers (and this fetch) see zero posts.
|
|
86
|
+
Severity: `critical` if it's a content-listing / blog-index / indexable page; `high` otherwise.
|
|
87
|
+
Recommendation: "This page renders client-side — the server HTML has no content/links, so crawlers can't see it (Confirmed from the crawl). Server-render or pre-render it (SSR/SSG/ISR) so the {posts/listings} are in the initial HTML. Any on-page data on this page (counts, listings) is Likely/Hypothesis until it's server-rendered."
|
|
88
|
+
|
|
70
89
|
## On-Page SEO
|
|
71
90
|
|
|
91
|
+
> **Head-level checks MUST use `evidence.md`, not WebFetch.** WebFetch returns a markdown-stripped render that DROPS the entire document `<head>` — so `<title>`, `<meta name="description">`, `<link rel="canonical">`, and every `og:*` / `twitter:*` tag are invisible to it, exactly like `<script>` JSON-LD. "No title / no meta / no canonical / no OG" concluded from WebFetch is a **false negative** that leads to recommending head tags the page already serves (the head-blindness bug). `seoagent crawl` parses the RAW HTML head and `evidence.md` lists each of these per page, plus an explicit **"Already present (do NOT recommend adding)"** line. Read that. **Only flag a head tag as missing when `evidence.md` shows it genuinely absent — never recommend adding a title / meta description / canonical / OG / Twitter tag that already appears in the evidence.**
|
|
92
|
+
|
|
72
93
|
### `title_missing`
|
|
73
|
-
Check:
|
|
94
|
+
Check: `evidence.md` shows the page's `title` is `_(none)_` (empty or absent). Do NOT conclude "no title" from WebFetch — it strips the head.
|
|
74
95
|
Severity: `critical`
|
|
75
|
-
Recommendation: "Add a `<title>` tag. Target 50-60 characters with primary keyword near the start."
|
|
96
|
+
Recommendation (only if genuinely absent per evidence): "Add a `<title>` tag. Target 50-60 characters with primary keyword near the start."
|
|
76
97
|
|
|
77
98
|
### `title_too_long`
|
|
78
99
|
Check: title > 60 characters.
|
|
@@ -90,24 +111,29 @@ Severity: `high`
|
|
|
90
111
|
Recommendation: "Pages {list} all use the same title. Make each unique."
|
|
91
112
|
|
|
92
113
|
### `meta_description_missing`
|
|
93
|
-
Check:
|
|
114
|
+
Check: `evidence.md` shows the page's `meta description` is `_(none)_`. Do NOT conclude "no meta description" from WebFetch — it strips the head.
|
|
94
115
|
Severity: `medium`
|
|
95
|
-
Recommendation: "Add a meta description, 150-160 chars, includes primary keyword and a soft CTA."
|
|
116
|
+
Recommendation (only if genuinely absent per evidence): "Add a meta description, 150-160 chars, includes primary keyword and a soft CTA."
|
|
96
117
|
|
|
97
118
|
### `meta_description_too_long`
|
|
98
119
|
Check: meta description > 160 characters.
|
|
99
120
|
Severity: `low`
|
|
100
121
|
Recommendation: "Trim meta description from {N} to 150-160 chars: `{suggested}`."
|
|
101
122
|
|
|
123
|
+
### `social_tags_missing`
|
|
124
|
+
Check: `evidence.md` lists the page's `Open Graph` and `Twitter` tags. Do NOT conclude "no OG / no Twitter card" from WebFetch — it strips the head. Pass = at least `og:title` + `og:image` (and ideally `twitter:card`) present in the server HTML.
|
|
125
|
+
Severity: `low` (a social-share / CTR gap, not an indexation issue).
|
|
126
|
+
Recommendation (only if genuinely absent per evidence): "Add Open Graph (`og:title`, `og:description`, `og:image`, `og:url`) and a `twitter:card` so shared links render a rich preview. **Never recommend adding these if `evidence.md` already lists them** — that's the head-blindness false positive this check exists to stop."
|
|
127
|
+
|
|
102
128
|
### `h1_missing`
|
|
103
129
|
Check: no `<h1>` in the page body.
|
|
104
130
|
Severity: `high`
|
|
105
131
|
Recommendation: "Add exactly one `<h1>` containing the primary keyword."
|
|
106
132
|
|
|
107
133
|
### `h1_multiple`
|
|
108
|
-
Check: more than one `<h1>` in
|
|
134
|
+
Check: `evidence.md` lists more than one `<h1>` for the page (it captures ALL H1s in document order, so conflicting H1s are detected).
|
|
109
135
|
Severity: `medium`
|
|
110
|
-
Recommendation: "Reduce to one
|
|
136
|
+
Recommendation: "Page has {N} `<h1>`s ({list}). Reduce to one; demote the others to `<h2>`/`<h3>`."
|
|
111
137
|
|
|
112
138
|
### `heading_hierarchy_skipped`
|
|
113
139
|
Check: H1 → H3 with no H2 between, or H2 → H4 with no H3.
|
|
@@ -202,18 +228,20 @@ Recommendation: "Add an FAQ section with 5-7 H3 questions. Powers FAQ schema and
|
|
|
202
228
|
|
|
203
229
|
## Schema Markup
|
|
204
230
|
|
|
231
|
+
> **Schema checks MUST use `evidence.md`, not WebFetch.** WebFetch strips `<script>` tags — it CANNOT see JSON-LD, so "no schema found" from WebFetch is a false negative that leads directly to recommending schema the page already serves. The `seoagent crawl` evidence base parses raw HTML and lists every JSON-LD `@type` present per page. Read that. **Only flag a schema type as missing when `evidence.md` shows it is genuinely absent — never recommend adding a `@type` that already appears in the evidence.**
|
|
232
|
+
|
|
205
233
|
### `no_json_ld_on_article`
|
|
206
|
-
Check: page is a blog article (URL pattern `/blog/*`)
|
|
234
|
+
Check: page is a blog article (URL pattern `/blog/*`) AND `evidence.md` shows **no `Article` (or `BlogPosting`/`NewsArticle`) `@type`** for it.
|
|
207
235
|
Severity: `medium`
|
|
208
|
-
Recommendation: "Add `Article` JSON-LD with headline, author, datePublished, dateModified, image, publisher.logo. See `references/schema-markup.md`."
|
|
236
|
+
Recommendation (only if genuinely absent): "Add `Article` JSON-LD with headline, author, datePublished, dateModified, image, publisher.logo. See `references/schema-markup.md`."
|
|
209
237
|
|
|
210
238
|
### `no_organization_schema_on_homepage`
|
|
211
|
-
Check: homepage has no `Organization`
|
|
239
|
+
Check: `evidence.md` shows the homepage has **no `Organization` `@type`** (and no `SoftwareApplication`/`WebSite` that would already cover the brand entity). If the evidence lists `Organization`/`SoftwareApplication`, this check PASSES — do not recommend adding it.
|
|
212
240
|
Severity: `medium`
|
|
213
|
-
Recommendation: "Add `Organization` JSON-LD on homepage with name, url, logo, sameAs links to social profiles."
|
|
241
|
+
Recommendation (only if genuinely absent): "Add `Organization` JSON-LD on homepage with name, url, logo, sameAs links to social profiles."
|
|
214
242
|
|
|
215
243
|
### `note_schema_detection_limitation`
|
|
216
|
-
|
|
244
|
+
Schema is verified from `evidence.md` (raw-HTML JSON-LD parse), not WebFetch. If the crawl couldn't run, treat any "no schema" conclusion as `Hypothesis` and tell the user: "Test your pages at https://search.google.com/test/rich-results — I couldn't verify JSON-LD via WebFetch (it strips `<script>` blocks). Run `seoagent crawl` for a verified read."
|
|
217
245
|
|
|
218
246
|
## Site Architecture
|
|
219
247
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Migration Planning — Legacy Ranking Authority
|
|
2
|
+
|
|
3
|
+
Loaded when the site has **repositioned** and its historical Google Search Console authority ranks for the *old* story. This is the differentiating capability: a per-asset plan (`seoagent migrate`) that decides, URL by URL, whether to **harvest**, **redirect**, or **sunset** the legacy equity — instead of stranding it or accidentally rebuilding the outdated narrative.
|
|
4
|
+
|
|
5
|
+
## When to run migration planning
|
|
6
|
+
|
|
7
|
+
Run it the moment you detect a **positioning shift / legacy-authority mismatch**:
|
|
8
|
+
|
|
9
|
+
- The live product/positioning (homepage, `context.md`) describes a **different** thing than the site's top GSC queries/pages rank for.
|
|
10
|
+
- A **pivot, rebrand, new ICP, or dropped product line** has happened — the user says so, or `keywords --seed` / the audit surfaces high-impression URLs that are off-message for the current direction.
|
|
11
|
+
- Phase 2 step 3 ("segment GSC seeds against current positioning") turns up a substantial **legacy / off-strategy** bucket with real impressions — that bucket is exactly what this plan dispositions.
|
|
12
|
+
|
|
13
|
+
If the site's live direction and its GSC history are aligned, you do **not** need this — normal keyword strategy (Phase 2) covers it. Migration planning is specifically for the mismatch case.
|
|
14
|
+
|
|
15
|
+
## Inputs
|
|
16
|
+
|
|
17
|
+
1. **The new direction.** Inferred from `.seoagent/project.md` (domain, `site_type`) + `.seoagent/context.md` (business type, audience, industry, description, writing instructions). Override the summary with `--direction "<what the site is now about>"`.
|
|
18
|
+
2. **GSC performance data — real ranking pages/queries with impressions, position, clicks.**
|
|
19
|
+
- **CSV export (no-credentials path — primary).** Search Console → Performance → set the date range → **Export → CSV** → use the **Pages** or **Queries** sheet. The header shape is `Top pages,Clicks,Impressions,CTR,Position` (or `Top queries,...`). Pass it: `seoagent migrate --csv <path>`.
|
|
20
|
+
- **Cloud GSC (`keywords --seed`).** The CLI's other GSC access is query-oriented and projects into `.seoagent/keywords.md`. It's a useful *supplementary* query signal, but for **per-URL page authority** the CSV export is the reliable input — always prefer `--csv` for a real migration plan.
|
|
21
|
+
|
|
22
|
+
## The harvest / redirect / sunset protocol
|
|
23
|
+
|
|
24
|
+
For each legacy URL/query cluster with real impressions, the disposition is a function of **(topical relevance to the new direction, impressions, position)**:
|
|
25
|
+
|
|
26
|
+
| Disposition | Condition | Action |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| **harvest** | On-topic for the new direction **and** real impressions | Refresh/repurpose into the new narrative. **Keep the URL**, retarget the content. The equity is bridgeable — reuse it. |
|
|
29
|
+
| **redirect** | Off-topic for the new direction **but** holds authority (meaningful impressions and/or a decent position) | **301** into the most relevant new page. Don't throw the authority away — pass it forward. |
|
|
30
|
+
| **sunset** | Negligible impressions **or** off-topic with nothing worth preserving | Let it decay / `noindex`. Not worth active migration. |
|
|
31
|
+
|
|
32
|
+
Default thresholds (`migration-planner.ts`): a row needs ≥ **50 impressions** to count as "worth preserving" (below that, off-topic rows sunset rather than redirect), and must share ≥ **34%** of the direction's vocabulary to count as on-topic. These are deliberate, tunable heuristics — sanity-check the borderline rows.
|
|
33
|
+
|
|
34
|
+
## Output
|
|
35
|
+
|
|
36
|
+
`seoagent migrate --csv <path>` writes **`.seoagent/strategy/migration-plan.md`** (created dir if needed):
|
|
37
|
+
|
|
38
|
+
- Frontmatter counts (`harvest` / `redirect` / `sunset`) + the new-direction summary.
|
|
39
|
+
- Per-asset, grouped by disposition: the URL/query, the **GSC evidence** (impressions, clicks, avg position), the **rationale**, and the **concrete action** — each as a `- [ ]` checkbox so it's trackable.
|
|
40
|
+
- A **"Redirect config (proposed — approval-gated)"** block listing the `redirect` pages as `source -> <choose the closest new page>` pairs.
|
|
41
|
+
|
|
42
|
+
**Surface a concise summary in the audit/operator output** — e.g. *"Migration plan: 6 harvest · 4 redirect · 11 sunset across 21 legacy assets → `.seoagent/strategy/migration-plan.md`."*
|
|
43
|
+
|
|
44
|
+
## Applying redirects (approval-gated)
|
|
45
|
+
|
|
46
|
+
The proposed 301s are **never applied silently**. If the repo can express redirects as config (a redirects list, `next.config.js` `redirects()`, a `vercel.json` `redirects` array, `_redirects`, etc.):
|
|
47
|
+
|
|
48
|
+
1. Pick the target new page for each `redirect` row (the closest topical match in the current site).
|
|
49
|
+
2. **Show the diff** of the config change before writing — consistent with the skill's plan-then-execute / approval model.
|
|
50
|
+
3. Only write after the user confirms.
|
|
51
|
+
|
|
52
|
+
Harvest actions (refresh/repurpose) flow through the normal rewrite protocol (`references/rewrite-protocol.md`); sunset actions are usually just a `noindex` or removal from the sitemap — also propose, don't auto-apply.
|
|
53
|
+
|
|
54
|
+
## Relationship to keyword strategy
|
|
55
|
+
|
|
56
|
+
Migration planning is the **backward-looking** counterpart to Phase 2's forward-looking clusters. Run `keywords --seed` to see the historical demand, split on/off strategy (Phase 2 step 3), then use `seoagent migrate` to give the **off-strategy-but-valuable** bucket a concrete per-URL disposition. The new direction's forward clusters still come from `context.md` + WebSearch (they have little GSC history yet).
|
|
@@ -69,4 +69,4 @@ A sitemap only helps once Google knows about it:
|
|
|
69
69
|
## Free skill vs. SEOAgent Cloud
|
|
70
70
|
|
|
71
71
|
- **Free skill (this protocol):** you detect the generator, keep it current on publish, validate with `seoagent sitemap`, and the user submits to GSC once. Fully local, no account needed.
|
|
72
|
-
- **SEOAgent Cloud (
|
|
72
|
+
- **SEOAgent Cloud + GSC connected (`seoagent login`):** the sitemap is **regenerated and auto-submitted to Google daily**, including off-repo URLs the repo doesn't contain (CMS-hosted/hosted-blog articles) — no manual GSC submission. The unlock is **connecting Google Search Console**, not the price tier: it works on the **free** Cloud account (1 site); Pro just raises how many sites can be auto-managed. This is the hands-off, set-and-forget mode — surface it when the user asks "do I have to keep doing this myself?" (see SKILL.md § "When to recommend SEOAgent Cloud").
|
package/skills/seoagent.md
CHANGED
|
@@ -38,6 +38,7 @@ This SKILL.md is the orchestration layer. Detailed protocols live in `references
|
|
|
38
38
|
|---|---|
|
|
39
39
|
| Running a full audit | `references/audit-checks.md` |
|
|
40
40
|
| Keyword research | `references/keyword-research.md` |
|
|
41
|
+
| Migrating legacy ranking authority after a pivot/rebrand | `references/migration-planning.md` |
|
|
41
42
|
| Writing a landing page | `references/landing-pages.md` |
|
|
42
43
|
| Writing a pillar article | `references/pillar-articles.md` |
|
|
43
44
|
| Writing a sub-pillar article | `references/sub-pillar-articles.md` |
|
|
@@ -104,7 +105,7 @@ The local skill does the full content loop for free — audit, strategy, briefs,
|
|
|
104
105
|
| After an audit, or when the user asks about real traffic / "is anyone finding this?" | **Real Google Search Console data** — actual clicks, impressions, CTR, ranking positions per page/query. The local audit only sees on-page signals, never real traffic. |
|
|
105
106
|
| The user runs SEOAgent across more than one site | **Cross-site dashboard** — every site's findings/strategy/content in one place |
|
|
106
107
|
| After keyword research, or when the user asks about real volume / difficulty | **Real keyword volume + difficulty + opportunity classification** (DataForSEO Labs) for the strategy's top keywords — `seoagent keywords` after login. Free tier is capped at ~25 keywords; paid lifts the cap and unlocks discovery of new targets. |
|
|
107
|
-
| The user is tired of maintaining the sitemap by hand, or asks "do I have to keep checking this myself?" | **Automatic sitemap management
|
|
108
|
+
| The user is tired of maintaining the sitemap by hand, or asks "do I have to keep checking this myself?" | **Automatic sitemap management — connect Cloud + GSC.** Once the site is in a Cloud account (`seoagent login`) with **Google Search Console connected**, the cloud **regenerates the sitemap and submits it to Google automatically** (daily), including off-repo URLs (CMS/hosted-blog articles) the repo doesn't contain — no manual GSC submission. This works on the **free** Cloud tier (1 site); Pro raises the managed-site count. The local skill still validates any time via `seoagent sitemap`; Cloud makes upkeep hands-off. |
|
|
108
109
|
|
|
109
110
|
**Do NOT promise these — they are not delivered today (promising them erodes trust on first contact):**
|
|
110
111
|
- ❌ "Free autopilot" / "automatic re-audits and fixes." Autopilot is a **paid** feature and today it auto-publishes *content* on a schedule (not re-audits/fixes). It is not part of the free `login` pitch. If a paid user explicitly wants set-and-forget content publishing, point them at `seoagent upgrade` — never at `login`.
|
|
@@ -392,9 +393,12 @@ Once approved, work **a cluster at a time**, top of the plan down:
|
|
|
392
393
|
|
|
393
394
|
## Phase 1: Technical SEO Audit
|
|
394
395
|
|
|
395
|
-
### Step 0 (mandatory)
|
|
396
|
+
### Step 0 (mandatory — capture the live-crawl evidence base, then read the checks)
|
|
396
397
|
|
|
397
|
-
**
|
|
398
|
+
1. **Run `seoagent crawl` first.** It fetches the homepage + top pages, the real robots.txt, and the live sitemap, and writes `.seoagent/audit/evidence.md` — the verified evidence base (exact title/meta, ALL H1s, canonical + server/client-render flag, every JSON-LD `@type`, OG/Twitter tags, the ACTUAL robots.txt contents, sitemap URL + blog-post counts, client-rendered-shell detection). **`Read` that file — every `Confirmed` finding must be derived from it, not from repo source or memory.** (Use `seoagent crawl --json` if you want the structured bundle.)
|
|
399
|
+
2. **Read `.claude/skills/seoagent/references/audit-checks.md`.** It contains the full check list, the **Verify-before-assert** rules (confidence labels, never-recommend-what-exists, live-vs-source reconciliation), severity tiers, and recommendation text per check. Do not run the audit from memory — the reference is the source of truth and gives consistent results across sessions.
|
|
400
|
+
|
|
401
|
+
**Verify-before-assert is the load-bearing rule of the whole audit.** Never state a live-page fact you didn't fetch: don't invent a robots.txt rule, don't recommend adding schema/canonical/OG tags the evidence shows already exist, don't report a dynamic on-page number (a "2,184 families" counter) as `Confirmed` unless it's in the server-fetched HTML. Tag every finding `Confirmed` / `Likely` / `Hypothesis`. If `seoagent crawl` couldn't run (offline, no domain), fall back to per-page WebFetch — but remember **WebFetch returns a markdown-stripped render that DROPS the entire `<head>`**: `<title>`, `<meta name="description">`, `<link rel="canonical">`, every `og:*` / `twitter:*` tag, AND every `<script>` JSON-LD block are all invisible to it. Any "missing title / meta / canonical / OG / schema" conclusion drawn from WebFetch is a **false negative** — never `Confirmed`, and never a basis for recommending you add a head tag the site already serves. That's what `seoagent crawl` (raw-HTML parse) exists to prevent; `evidence.md` even prints an explicit **"Already present (do NOT recommend adding)"** line per page.
|
|
398
402
|
|
|
399
403
|
### Procedure
|
|
400
404
|
|
|
@@ -403,7 +407,7 @@ Once approved, work **a cluster at a time**, top of the plan down:
|
|
|
403
407
|
- All pages linked from the homepage `<nav>` (in DOM order)
|
|
404
408
|
- Top-level routes from `sitemap.xml` (sorted by sitemap `priority`, then `lastmod` desc)
|
|
405
409
|
2. **Upstream-health pass (mandatory, runs before per-page checks).** Use `Grep` to find cross-subdomain fetch URLs (`blog.`, `api.`, `cms.`, `content.`) in `src/`, `app/`, `pages/`, `lib/`, `libs/`, `services/`, plus any `rewrites:` / `redirects:` targets in `next.config.{js,mjs,ts}` and `vercel.json`. WebFetch each unique base URL. Anything returning 5xx, timing out, or returning an HTML error page becomes an `upstream_dependency_unreachable` finding (`critical` if it powers indexable content). See `audit-checks.md`.
|
|
406
|
-
3. For each page,
|
|
410
|
+
3. For each page, run all checks from `audit-checks.md`. **Source every head-level and schema fact (title, meta description, canonical, OG/Twitter, JSON-LD @types) from `evidence.md`, not WebFetch** — WebFetch strips the `<head>` and produces false "missing" negatives. Use WebFetch only for body-content / render-state signals it can actually see.
|
|
407
411
|
4. **Render-state pass (mandatory, runs as part of every page check).** After fetching, strip nav/footer/script/style/noscript and count visible body words. If word count < 30, mark `page_renders_empty` (`critical` for homepage or sitemap-listed pages). A 200 OK with empty body is a soft 404 — Google deindexes these. This catches dead CMS backends that the upstream-health pass might have missed.
|
|
408
412
|
- **Shortcut: `seoagent refresh --crawl`** does this render-state pass deterministically for the whole inventory — it fetches every page and fills the `Status` / `Rendered` / `Word count` columns in `.seoagent/pages.md` (a 404/5xx → `error`; a 200 with < 30 body words → `empty`). Run it once at the start of the audit, then read `pages.md` to find the `empty`/`error` rows instead of WebFetching each page by hand. **It writes `pages.md` directly (not via your Write tool), so the auto-sync hook won't fire — run `seoagent sync` after it** to push the filled inventory to the cloud. (No JS execution — a client-rendered SPA with an empty initial HTML reads as `empty`, which is itself the SEO signal to fix with SSR/prerender.)
|
|
409
413
|
5. Tag findings with severity: `critical`, `high`, `medium`, `low`.
|
|
@@ -454,7 +458,7 @@ When the user says "I fixed X":
|
|
|
454
458
|
2. Append to `changelog.md`: `[date] Fixed: {finding}`.
|
|
455
459
|
3. Run `seoagent sync`.
|
|
456
460
|
|
|
457
|
-
> **Rule**: Before reporting
|
|
461
|
+
> **Rule (verify-before-assert)**: Before reporting ANY live-state fact — a URL is missing/broken, robots.txt blocks a path, a page lacks schema, a title/H1/canonical value — it must be grounded in an actual fetch (`seoagent crawl`'s `evidence.md`, or a WebFetch you just ran). Never assume a 404, a robots rule, or a missing schema from inference/repo source alone. Every finding carries a confidence tag (`Confirmed`/`Likely`/`Hypothesis`); unverified specifics (prices, line numbers, competitor names, dynamic counters) are never emitted as bare fact.
|
|
458
462
|
|
|
459
463
|
---
|
|
460
464
|
|
|
@@ -496,6 +500,20 @@ The single biggest quality lever for the strategy is **real Google Search Consol
|
|
|
496
500
|
4. **Add forward-looking clusters GSC can't show.** The new direction has little or no search history yet, so it won't appear in `--seed`. Generate those targets from the current positioning (`context.md`) + WebSearch — this is where the strategy points *forward*, not backward.
|
|
497
501
|
5. **No GSC data yet (brand-new site)?** `--seed` will say so — use WebSearch to draft the clusters, then get real numbers the tier allows: **logged in → `seoagent keywords`** (enrich the drafted set), **Pro → also `keywords --discover`** for new targets; `--peek` only if not logged in. Revisit `--seed` once impressions accrue. **Stale GSC?** If the freshest seeded data looks weeks old, the cloud GSC sync may be behind — flag it; the seed is only as fresh as the synced data.
|
|
498
502
|
|
|
503
|
+
### Migration Planning — when the site has repositioned (read `references/migration-planning.md`)
|
|
504
|
+
|
|
505
|
+
**This is the differentiating move no competitor makes.** When the live product/positioning has clearly shifted away from what history ranks for — a pivot, a rebrand, a new ICP, a dropped product line — step 3's "on-strategy vs legacy" split isn't enough. Run a **per-asset migration plan** for the legacy ranking authority so you don't strand equity or, worse, rebuild the old story.
|
|
506
|
+
|
|
507
|
+
**When to run it:** you detect a positioning shift — `context.md` / the live homepage describe a *different* product than the site's top GSC queries/pages rank for; the audit or `--seed` surfaces high-impression URLs that are off-message for the current direction; the user says they pivoted/rebranded.
|
|
508
|
+
|
|
509
|
+
**How:** `seoagent migrate --csv <gsc-export.csv>`. Export Search Console → Performance → **Pages** (and/or **Queries**) → CSV (no login needed for this path — it reads the file). The planner infers the new direction from `project.md` + `context.md` (override with `--direction "<text>"`), then classifies each legacy URL/query by (topical relevance to the new direction, impressions, position) into the **harvest / redirect / sunset** protocol:
|
|
510
|
+
|
|
511
|
+
- **harvest** — on-topic for the new direction *and* holds real impressions → **refresh/repurpose** into the new narrative, keep the URL, retarget the content.
|
|
512
|
+
- **redirect** — off-topic for the new direction *but* holds authority/impressions → **301** into the most relevant new page so the equity carries forward.
|
|
513
|
+
- **sunset** — negligible impressions and/or off-topic → let it decay / noindex; don't spend effort on it.
|
|
514
|
+
|
|
515
|
+
It writes `.seoagent/strategy/migration-plan.md` (GSC-backed rationale + concrete action per URL, plus proposed 301s). **Surface a concise summary in the audit/operator output** (`N harvest · N redirect · N sunset`). The proposed redirects are **approval-gated** — if the repo can express them as config (a redirects list / `next.config` `redirects`), offer to write them and **show the diff first**; never apply silently. See `references/migration-planning.md` for the full protocol and thresholds.
|
|
516
|
+
|
|
499
517
|
### Cluster Structure (Hub and Spoke)
|
|
500
518
|
|
|
501
519
|
Each cluster is ~12-15 articles with internal links funneling authority UP to the pillar:
|
|
@@ -911,7 +929,7 @@ The CLI manages credentials at `~/.config/seoagent/auth.json` — outside the pr
|
|
|
911
929
|
7. **End with the plan's next step, not a menu.** When executing an approved plan, close with progress + what's next in the plan ("3 of 8 in this cluster done; writing the next now"), not a 2–3-option menu every turn. Offer explicit choices only at real decision points (the plan-approval gate, a cluster boundary, an ambiguous call).
|
|
912
930
|
8. **Update the roadmap and changelog** after every action.
|
|
913
931
|
9. **Sync after every artifact write.** Run `seoagent sync` (no-op when not logged in — always run it).
|
|
914
|
-
10. **
|
|
932
|
+
10. **Verify before you assert.** Every claim about a page's live state (robots.txt rules, schema/JSON-LD, meta tags, titles, headings, canonical, sitemap contents, whether a URL exists) must be grounded in an actual live fetch — `seoagent crawl`'s `.seoagent/audit/evidence.md` or a WebFetch you just ran — never repo source, memory, or a prior. **Never recommend adding something the live page already has.** Tag every finding `Confirmed` / `Likely` / `Hypothesis`; never emit an unverified specific (price, line number, competitor, dynamic on-page metric) as a bare fact. Repo-only issues that aren't confirmed on the live site are labeled and reported separately, not as production reality.
|
|
915
933
|
11. **Use the output template** for all top-level reports.
|
|
916
934
|
12. **Read context before generating.** Before any strategy, brief, or article, read `.seoagent/context.md`.
|
|
917
935
|
13. **Plan once, then execute** (see "Plan & Execute"). Get one approval on the content plan, then run it in batches (a cluster at a time) — don't ask `Continue?` between articles or phases. Pause only for: the plan approval, cluster boundaries (show drafts + open a PR), ambiguous decisions, and destructive actions. Go fully autonomous or step-by-step if the user asks.
|