Cluster
Plan a topic cluster around a seed keyword for a chosen managed site. Pulls 5-15 SERP-clustered related keywords (per SEO_GUIDE Section 2.4 overlap test), designs a pillar page (broad target) + N cluster pages (narrow targets), specifies the internal-linking graph (pillar ↔ clusters + lateral cluster ↔ cluster), and generates wireframes for every page in one pass. Output lands in `_drafts/cluster-{seed-slug}/` ready for `/blog` or `/service` to fill in content. Use when the user types `/cluster {seed-keyword} {site}` or asks for a topic-cluster plan.From its SKILL.md
npx -y skills add mkreindel/seo-toolkit-template --skill clusterAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
- runs commandsInstructs the agent to run 3 commands, including `scripts/semrush.mjs --keyword="{seed}" --type=related` and 2 more.
SKILL.md
17.7 KB, ~4.4k tokens by cl100k_base, as published. Nobody here has run it
/cluster — Topic cluster planner
Plans a complete topic cluster around a seed keyword: pillar page + N cluster pages + the internal-linking graph + wireframes for every page. Topical authority is built at cluster granularity, not single-post granularity — and this skill is what makes that engineering possible inside the toolkit.
Inputs
Required (asked at start if not given):
- Seed keyword — the broad topical anchor. Examples: "ai consultant" (site-a), "spanish catering houston" (site-b), "endocrinologist houston" (site-c).
- Site — must match a folder under
sites/[name]/. The skill reads itssite-info.md.
Optional (per-run overrides):
--max-clusters=N— caps the cluster count (default 8; valid range 3–15). Lower → faster ship; higher → fuller topical authority but more downstream content to write.--include-bottom-funnel— when set, includes commercial-intent clusters (pricing, vs-competitor, alternative) alongside informational ones. Default: informational-only.- Keyword tool — overrides the site's default (SEMrush, GSC, Ahrefs, manual SERP analysis). Defaults to
site-info.md→Keyword tool.
Workflow
Step 0 — Cron-mode detection (if invoked with --cron)
If the invocation contains --cron, this skill runs in cron mode (no user available). Required behavior:
-
Idempotency check: see this skill's "Routine versioning + idempotency contract" section. If today's output already exists at
_drafts/cluster-{seed-slug}/, exit cleanly withexit: "idempotent-skip". Write one line to the audit log viascripts/lib/audit-log.mjsappendRun({ exit: "idempotent-skip", ... }). -
Escalation contract: any decision that would normally prompt the user (missing required file, voice anti-AI failure, ambiguous SERP cluster, keyword cannibalization) MUST be escalated by writing an item to
sites/{site}/_inbox/viascripts/lib/cron-mode.mjswriteInboxItem(...). After writing, exit cleanly withexit: "escalated". Do NOT useAskUserQuestionin cron mode. -
Defaults: when a choice would normally be asked, default to
site-info.md/goals.mdvalues. If both are silent on the required choice, escalate per (2). -
Audit log: ALWAYS write one line to the audit log on exit — success (
shipped), escalation (escalated), idempotent skip (idempotent-skip), or failure (failed). -
Backoff: at the start of every cron-mode run, call
checkBackoff({ routine })fromscripts/lib/audit-log.mjs. If true, the routine has hit the 3-strike threshold — write_inbox/routine-disabled-{name}.md, runscripts/sync-schedules.mjs --pause-routine={name}, and exit.
Step 1 — Read context (Rule 1)
Load in order:
CLAUDE.md(toolkit root) — especially# Multilingual sitesand# Keyword researchsectionsSEO_GUIDE.md— Section 2.4 (SERP comparison test), Section 3.3 (cluster design), Section 5.1 (internal-linking patterns), Section 2.6 (architecture)on-page-seo.md— Tier 1 reference (each cluster page eventually satisfies all 16)sites/[site]/site-info.md— Languages block + footprint + Service business + Architecture pointersites/[site]/architecture.mdif present (required for service-business sites with >3 commercial keywords)- Resolved
references/— root files + per-site overrides sites/[site]/keywords.csvandsites/[site]/service-keywords.csvsites/[site]/used-keywords.mdsites/[site]/notes.md
If any required file is missing, ask the user before proceeding.
Step 2 — Validate seed + intent
The seed keyword anchors the entire cluster. Validate:
-
Intent classification. Run a quick SERP look at the seed:
- If top 10 is dominated by informational content (definitional posts, how-to guides, "what is X" pages) → informational cluster. Cluster pages mostly become blog posts.
- If top 10 is dominated by commercial pages (service offerings, product pages, comparison/pricing) → commercial cluster. Cluster pages become service pages.
- If mixed → flag and ask user which intent dominates this site's positioning before proceeding.
-
Cannibalization check. If the seed (or close variants) already appears in
used-keywords.mdorkeywords.csvas the primary keyword of an existing/planned page, abort and report. Topic clusters cannot be built around a seed that's already been "spent" on a single page — the existing page must be retargeted as the pillar, OR a different seed picked. -
Architecture-fit check (commercial seeds only). If site is service-business with architecture.md, the seed must already appear in architecture as a pillar candidate, OR the user adds it to architecture before generating. Off-architecture clusters create cannibalization at scale.
Step 3 — SERP-cluster expansion
Goal: surface 5-15 keywords that share enough SERP overlap with the seed to belong on the same cluster.
-
Pull related keywords from the site's chosen keyword tool:
- SEMrush:
scripts/semrush.mjs --keyword="{seed}" --type=related(orphrase_relatedendpoint). Returns 50-200 related terms with volume + KD. - GSC fallback: if SEMrush unavailable, pull GSC top-queries where the site already shows up for variants of the seed (
scripts/keyword-discovery.mjs --site={site} --grep="{seed-stem}"). - Manual fallback: pull PAA (People Also Ask) from Google + autocomplete suggestions.
- SEMrush:
-
SERP-overlap test (per
SEO_GUIDE.mdSection 2.4) on each candidate vs the seed:- Fetch top 10 for both incognito (target region per
site-info.md). - Count shared URLs.
- ≥ 4 shared → cluster member (high confidence)
- 2–3 shared → borderline (keep only if KD is attainable and volume ≥ 100)
- ≤ 1 shared → exclude (belongs on a different page, suggest as future seed)
- Fetch top 10 for both incognito (target region per
-
Attainability filter. Each candidate must satisfy
KD ≤ site_AS + 15(the realism filter from v2 spec Hard Truth 2). Where:site_AS= latest SEMrush Authority Score fromsites/{site}/_baselines/semrush-*.json- Candidates above the threshold are flagged as "stretch" — included only if user explicitly opts in.
-
Volume cap. Cluster members should each have volume ≥ 50/mo (lower than the 100 floor for individual blog posts because clusters create compounding value through linking — even small-volume terms contribute to topical authority).
-
Cap the final list at
--max-clusters(default 8). Rank byvolume × (1 / KD_distance)and take the top N.
Step 4 — Design the cluster shape
For each member of the cluster:
-
Assign role. One of:
- Pillar (broadest seed) — the topical hub. Single page. Title matches the seed. Designed to rank for the broad term + serve as the entry point.
- Cluster page (specific aspect) — N pages. Each covers one narrower angle (definition, how-to, vs-comparison, pricing, use-case, etc.).
- Optional spoke (very narrow long-tail) — only if --max-clusters allows.
-
Assign intent type. Per page:
info— informational, becomes a blog post (handled by/bloglater)comm— commercial, becomes a service page (handled by/servicelater)mixed— hybrid, becomes a blog post that bridges into commercial pages
-
Title / H1 / slug. Per page:
- Title: 50-60 chars, primary keyword near start
- H1: sentence case, contains the primary keyword
- Slug: short, lowercase, hyphenated, contains primary keyword stem
-
Per-language fan-out (multilingual sites): each cluster page gets its EN + ES (or other declared language) variant. Slug is shared; titles/H1s are native rewrites per
voice.[lang].md.
Step 5 — Internal-linking graph design
The link graph is the load-bearing structure that converts N independent pages into a topical cluster.
-
Pillar links to ALL clusters. Every cluster page is linked from the pillar via inline anchor text (3-5 internal links per pillar section, each pointing to a relevant cluster).
-
Every cluster links back to pillar. Each cluster page has at minimum 1 "see the pillar" link in body copy, usually in the introduction or a "for the full overview, see [pillar]" callout.
-
Lateral cluster links based on SERP overlap:
- For each pair of cluster pages, if they share ≥ 4 SERP URLs with each other → bidirectional internal link.
- If they share 2-3 → unidirectional from the higher-volume one to the lower-volume one (lower one borrows authority).
- If they share ≤ 1 → no lateral link.
-
Anchor text variation. Use varied anchor text across all links — never link the same target with the same anchor 3+ times across the cluster. Mix exact-match (the primary keyword), partial-match (variant), and natural-language (descriptive phrase).
-
External links (cluster-wide consistency): each page has 2-3 external links to authoritative sources. Across the cluster, those sources should overlap by 30-60% (signals topical consistency to Google).
Step 6 — Wireframe generation
For the pillar AND each cluster page, generate a wireframe at the path defined in Step 9. Each wireframe follows the standard shape from .claude/skills/wireframe/SKILL.md and CLAUDE.md "Wireframe before content" rule, with cluster-aware additions:
Required wireframe sections (per page):
- Layout zones (top-to-bottom): hero, TOC if 1500+ words, content sections (H2 per section), FAQ, related-cluster (the lateral links), author bio (blog) or NAP (service).
- Heading map (H1 / H2 / H3) — H1 = primary keyword for this page; H2s ordered by what the SERP top 3 use as their structure (overlap > divergence).
- Internal linking pattern slots (per
SEO_GUIDE.mdSection 5.1):- For pillar: patterns 1 (header dropdown), 2 (TOC), 3 (footer), 4 (breadcrumb), 7 (blog→service bridges to relevant cluster pages)
- For cluster: patterns 1, 3, 4, 6 (related-cluster sidebar/end-section), 7 (back to pillar)
- Image plan — hero image + N inline images.
- External link plan — 2-3 authoritative sources by topic.
- CTA placement — where the conversion ask sits.
- Cluster-specific section — explicit list of which other cluster pages this page should link to (the lateral graph from Step 5).
Step 7 — Plan + approval (Rule 2)
Present to user before writing any files:
- Seed + intent classification + dominant SERP shape
- Pillar page proposal: title + slug + intent
- Cluster page list: N rows of (keyword + title + slug + intent + role)
- Internal-link graph: ASCII or matrix view (pillar ↔ each cluster + lateral pairs)
- Language fan-out (multilingual): list of languages × pages = total file count
- Estimated downstream effort: N pages × ~1-2h per page (via
/blogor/service) = total ramp-up time - Architecture impact (commercial clusters): if pillar/clusters need to be added to
architecture.md, list them
Wait for explicit approval. If user wants to reduce N → re-run Step 3-5 with smaller --max-clusters.
Step 8 — Resolve language fan-out (multilingual sites only)
Read site-info.md Languages section.
Multilingual: false→ skip; continue to Step 9 as a single-language cluster.Multilingual: true:- Default coverage = every language declared.
- For each declared language:
- Resolve voice files: root
references/voice.md+ per-sitereferences/voice.[lang].md(REQUIRED). - Cluster slug shared across languages.
- Hreflang cluster generated for every page (en + es + x-default).
- URLs formatted per language URL pattern from site-info.md.
- Resolve voice files: root
- Schema
inLanguageper language draft.
The skill produces 1 cluster × N languages = N×(pillar + clusters) wireframe files total. Each in its own _drafts/cluster-{seed-slug}/{lang}/ subfolder.
Step 9 — Write output
Create the directory tree:
sites/{site}/_drafts/cluster-{seed-slug}/
├── cluster-plan.md # overview + keyword assignments + link graph
├── {lang}/ # per-language (only if multilingual)
│ ├── pillar/
│ │ └── wireframe.md
│ └── clusters/
│ ├── {cluster-1-slug}/
│ │ └── wireframe.md
│ ├── {cluster-2-slug}/
│ │ └── wireframe.md
│ └── ... (N total)
Monolingual sites skip the {lang}/ directory layer:
sites/{site}/_drafts/cluster-{seed-slug}/
├── cluster-plan.md
├── pillar/wireframe.md
└── clusters/{slug}/wireframe.md # N of these
cluster-plan.md contents:
# Cluster plan — {site} — {seed}
**Created:** {YYYY-MM-DD}
**Seed keyword:** {seed}
**Intent classification:** {info | comm | mixed}
**Site AS at plan time:** {N}
**Attainability ceiling (KD ≤ site_AS + 15):** {N+15}
**Languages:** {en, es, ...}
## Pillar page
- **URL:** {site-origin}/{lang-prefix}/{path}/{slug}
- **Primary keyword:** {pillar-keyword}
- **Title:** {pillar-title}
- **H1:** {pillar-h1}
- **Intent:** {info|comm|mixed}
- **Volume:** {volume}/mo
- **KD:** {kd}
- **Wireframe:** ./pillar/wireframe.md (or ./{lang}/pillar/wireframe.md)
## Cluster pages
| # | Keyword | URL | Title | Intent | Vol | KD | Wireframe |
|---|---------|-----|-------|--------|-----|-----|-----------|
| 1 | {kw} | ... | ... | info | ... | ... | ./clusters/{slug}/wireframe.md |
| 2 | ... | ... | ... | ... | ... | ... | ... |
... (N rows)
## Internal-link graph
### Pillar → Clusters (always, 1:N)
- pillar → cluster-1 (anchor: "{anchor-text-1}")
- pillar → cluster-2 (anchor: "{anchor-text-2}")
- ... (N edges)
### Cluster → Pillar (always, N:1)
- cluster-1 → pillar (anchor: "for the full overview…")
- cluster-2 → pillar (anchor: "see the parent guide…")
- ... (N edges)
### Lateral cluster ↔ cluster (per SERP overlap test)
- cluster-1 ↔ cluster-2 (bidirectional; shared SERP URLs: 5)
- cluster-2 → cluster-4 (unidirectional, lower-volume target; shared: 3)
- ... (M edges)
## Next-step build sequence
To ship the cluster, run these commands in order (suggested cadence: 1 per week to let Google crawl/index between):
1. `/blog {site} {pillar-keyword}` ← pillar first; clusters need it as the link target
2. `/blog {site} {cluster-1-keyword}` (or `/service` if intent=comm)
3. `/blog {site} {cluster-2-keyword}` (or `/service`)
... (N more)
Each `/blog` or `/service` run reads `_drafts/cluster-{seed-slug}/clusters/{slug}/wireframe.md` (or the pillar one) for its planning artifact, then generates content following the wireframe + the AI-search optimization rules (P1.5).
## Status
OPEN — awaiting user approval to start the N+1 content runs.
wireframe.md contents (per page): standard wireframe per .claude/skills/wireframe/SKILL.md, with the cluster-specific link slots populated from Step 5's graph.
Step 10 — Update tracker
For each page in the cluster (pillar + N clusters), append a row to sites/{site}/used-keywords.md with status "planned via /cluster — wireframe shipped, content pending."
This prevents future /blog runs from accidentally picking the cluster's keywords as standalone targets. When the content actually ships via /blog or /service, those skills will update the row's status to "shipped."
Step 11 — Report
Print:
- Pillar URL + cluster URLs (N total)
- Cluster plan file path:
sites/{site}/_drafts/cluster-{seed-slug}/cluster-plan.md - Total wireframes generated: N+1 (or (N+1) × num_languages for multilingual)
- Link graph summary: N pillar-to-cluster + N cluster-to-pillar + M lateral edges
- Suggested next-step command:
/blog {site} {pillar-keyword}(start with pillar) - Estimated full cluster shipping timeline: N+1 weeks at 1 post/week cadence
Routine versioning + idempotency contract
This skill participates in cruise-control via --cron mode. When invoked by cron:
-
Stamp
routine_versionin every output produced (cluster-plan.md frontmatter, each wireframe's metadata, audit log line viascripts/lib/audit-log.mjs). Currentroutine_version: 1.0. -
Idempotency key: the cluster slug + seed keyword. If
sites/{site}/_drafts/cluster-{seed-slug}/cluster-plan.mdexists from a prior run with matching seed, exit withidempotent-skip. Do not regenerate (user may have already started content runs against the existing wireframes). -
3-strike contract (per
scripts/lib/audit-log.mjs): three consecutivefailedruns for this skill on a given site → pause automatic invocation, write_inbox/routine-disabled-cluster.md.
Refusal conditions
Refuse and report clearly if:
- Seed keyword already in
used-keywords.mdas a primary target of an existing page (cannibalization risk — must retarget existing page or pick different seed) - Site is service-business with >3 commercial keywords AND
architecture.mdis missing (perSEO_GUIDE.mdSection 2.6) - Site has no
keywords.csvor it's empty (cluster expansion needs candidate pool) - Multilingual site with missing
references/voice.[lang].mdfor any declared language --max-clustersoutside valid range (3-15)- SERP-cluster expansion returns 0 candidates that pass the overlap + attainability filters (in this case, the seed is structurally not a cluster — it's a single-page target; recommend
/blogor/servicedirectly)
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.