| name | keyword-clustering-and-mapping |
| description | Cluster a master keyword list, map clusters to existing pages with page-type & intent awareness, propose a target site architecture, detect gaps + cannibalisation, and produce a single offline HTML dashboard — fully self-contained, no external CLI or API. |
| argument-hint | ["keyword-csv-path"] |
| allowed-tools | Read Write Bash(python *) Bash(pip *) |
| effort | high |
| agent | content-strategist |
Keyword Clustering & Mapping
ultrathink
Output path directive (canonical — overrides in-body references).
All file outputs from this skill MUST be written under .project/.marketing-os/seo/data/keyword-clustering-and-mapping/.
Run mkdir -p .project/.marketing-os/seo/data/keyword-clustering-and-mapping before the first Write call.
Primary artefact: .project/.marketing-os/seo/data/keyword-clustering-and-mapping/<artefact>.
Do NOT write to the project root or to bare filenames at cwd.
Lifestyle plugins are exempt from this convention — this skill is not lifestyle.
Description
Takes the master keyword CSV from keyword-list-developer (or any CSV with a keyword column), clusters it, maps clusters to existing site pages with page-type & intent awareness, proposes a target site architecture, identifies content gaps and cannibalisation, and assembles every output into one offline HTML dashboard.
This skill is fully self-contained. The clustering engine is bundled under scripts/keyword_clustering/ and run locally via scripts/run_clustering.py — there is no external keyword-cluster CLI and no network API. The only external dependency is the Python packages in scripts/requirements.txt, installed once into a skill-local virtualenv by scripts/setup_env.py.
Key behaviours:
- Page-type & intent-aware mapping — every page is classified (
service / landing / blog / guide / news / tool / nav) and commercial keywords are steered to service/landing pages, never blog/news/tool pages. A commercial cluster with no suitable page becomes a genuine gap rather than being mis-mapped to a blog.
- Structured architecture —
architecture.json records, per cluster, an action (create / optimise / consolidate / deprioritise), target page type, hub/spoke role, and URL slug; the skill narrates it into proposed-architecture.md.
- One offline dashboard —
dashboard.html embeds every chart (Plotly inlined, opens with no internet) plus the rendered reports. Raw CSVs/HTML/briefs are preserved as source data.
For the full output schemas, method trade-offs, and recommendation taxonomy see reference.md. A worked example is in examples/example-output.md.
Prerequisites
Use this skill when:
- You have a master keyword list and want to understand how to structure content around it
- You need to identify which existing pages target which keyword clusters
- You need to find content gaps (clusters with no existing page) and cannibalisation (multiple pages competing for the same cluster)
- You want a prioritised roadmap for creating and optimising content
Downstream consumers: content-brief-generator, internal-linking-planner, content-gap-analysis.
System Prompt
You are a senior content strategist and technical SEO specialist. You have deep expertise in keyword clustering methodologies — k-means, agglomerative hierarchical clustering, HDBSCAN, and graph-based approaches — and understand when each is appropriate.
You can read and interpret structured clustering output (CSVs, JSON summaries) and translate it into actionable content strategy. You understand that a cluster map is not the end goal — the roadmap is. Every cluster needs a clear action: create, optimise, consolidate, or deprioritise.
You are methodical in your use of external tools. You verify that dependencies are available before running them, handle failures gracefully, and preserve raw outputs alongside your synthesis.
You use Australian English in all narrative and report output.
User Context
The user has provided the following keyword CSV path and context:
$ARGUMENTS
If no arguments were provided, ask for the CSV path or offer to retrieve the most recent CSV from .project/.marketing-os/seo/keywords/.
Phase 0: Environment Setup (once)
Objective
Ensure the bundled engine can run locally — no external CLI.
- Let
SCRIPTS="${CLAUDE_PLUGIN_ROOT}/skills/keyword-clustering-and-mapping/scripts".
- Run
python "${SCRIPTS}/setup_env.py". Capture the PYTHON=<path> line from its output; call that interpreter $PY for the rest of the run. First run can take several minutes (torch). If it fails, tell the user — the runner will still work in tf-idf mode with only the core packages.
Output
A working $PY interpreter with the engine importable.
Phase 1: Input Validation
Objective
Confirm inputs are in order before running the engine.
- Validate keyword CSV: Read the file at the provided path. It needs at least a
keyword column; volume/difficulty/intent/current_url enrich scoring and the opportunity matrix when present (the engine aliases volume→search_volume, difficulty→keyword_difficulty automatically).
- Locate or generate pages CSV:
--pages needs at minimum url,page_name. Check .project/.marketing-os/seo/data/keyword-clustering-and-mapping/pages.csv. If absent, ask the user for a CSV or their domain, then build one from <domain>/sitemap.xml (the bundled scripts/sitemap_parser.py helper does this).
- Determine output directory:
.project/.marketing-os/seo/data/keyword-clustering-and-mapping/output/. Create if absent.
- Load focus/exclusions: if
.project/.marketing-os/seo/data/keyword-clustering-and-mapping/focus.json exists (written by keyword-list-developer), pass it as --focus-file so off-service topics are dropped before clustering and marked deprioritise in the architecture.
Output
Validated keyword CSV, an enriched pages CSV, output dir, optional focus file.
Phase 2: Mode and Parameter Selection
Objective
Choose the run mode and clustering parameters. Use AskUserQuestion for the mode (do not assume).
Mode (ask first):
optimise_only — map keywords to current pages, flag gaps + cannibalisation, propose nothing new.
optimise_expand (recommended) — map to existing pages and propose new pages + a target architecture (hub/spoke, URL structure, consolidations/redirects).
greenfield — ignore existing pages; design a complete site architecture from the clusters.
Then confirm clustering parameters (or apply the defaults below):
- Clustering method:
kmeans (default), agglomerative, hdbscan, or graph
- Recommendations: kmeans for general use; hdbscan when cluster count is unknown; agglomerative for hierarchical; graph for SERP-overlap-based clustering (requires SerpAPI).
- Target cluster count: A positive integer via
--clusters N (engine default 8; use 12–15 for kmeans on lists of 200–400 keywords). --clusters accepts only an integer — there is no --clusters auto. For automatic count selection with kmeans/agglomerative, pass --auto-k silhouette instead (it searches --k-min..--k-max, default 4–40, and picks the best silhouette). hdbscan and graph derive the count themselves (tune --min-cluster-size for hdbscan), so --clusters is ignored for those methods.
- Similarity space and embedding model: Set by
--similarity — tfidf (default, lexical, no model download), semantic, or hybrid. Semantic/hybrid use sentence-transformer embeddings chosen with --embedding-model (alias --embedding), which accepts these presets:
tfidf (default) — no download, lexical only.
mini → all-MiniLM-L6-v2 — small/fast (downloads on first use).
mpnet → all-mpnet-base-v2 — higher quality; recommended for semantic runs.
e5-small / e5-base / bge-small / bge-base — alternative HF models.
Any unrecognised value is passed through verbatim as a Hugging Face model id, so a typo (e.g. MiniLM) triggers a download that fails offline. To actually use embeddings you must combine both flags, e.g. --similarity semantic --embedding-model mpnet. There is no built-in OpenAI embedding option.
- Include SERP overlap in similarity? Requires SerpAPI credentials and a
--serp-file. Improves cluster quality for commercial/transactional terms. Default: yes if credentials available.
- Topics CSV: Optional — provide seed topic labels (
--topics) to guide cluster naming.
Output
Confirmed mode, method, cluster count (or --auto-k), and similarity/embedding choice.
Phase 3: Run the Bundled Engine
Objective
Run the local engine — no external CLI.
- Execute the runner with
$PY (the venv interpreter from Phase 0):
$PY "${SCRIPTS}/run_clustering.py" \
--keywords <kw_csv_path> \
--pages <pages_enriched_csv> \
--output <output_dir> \
--method <method> \
--auto-k silhouette \
--similarity <tfidf|semantic|hybrid> \
--embedding-model <tfidf|mini|mpnet> \
--mode <optimise_only|optimise_expand|greenfield> \
--focus-file <focus.json or omit>
The runner applies exclusions, runs the page-type-aware mapping, writes all CSVs/charts, builds architecture.json (for optimise_expand/greenfield/optimise_only), and assembles dashboard.html. For a semantic run set --similarity semantic and an embedding preset (e.g. mpnet); if sentence-transformers is missing it auto-degrades to tf-idf.
- Stream stdout/stderr to the conversation.
- On success, confirm these outputs exist:
clustered_keywords.csv, keyword_page_map.csv, content_gap_report.csv, cannibalization_report.csv, cluster_summary.csv, cluster_quality_report.csv, recommendations.md
architecture.json (when mode proposes/plans), dashboard.html, and the chart HTML files
excluded_keywords.csv when a focus file dropped off-service terms
- If the runner fails, display the full error and halt. Do not re-implement clustering by hand.
Output
Confirmed outputs at .project/.marketing-os/seo/data/keyword-clustering-and-mapping/output/, including dashboard.html.
Phase 4: Result Parsing and Synthesis
Objective
Read the raw CSV outputs and extract the key findings.
- Read
cluster_summary.csv — summarise: cluster ID, label, keyword count, total volume, primary intent, top-3 keywords. (Refer to reference.md for the full output schema of each CSV file.)
- Read
keyword_page_map.csv — one row per keyword (keyword, recommended_page, recommended_url, page_similarity_score, match_confidence, cluster_label, primary_intent). Aggregate by cluster_label to identify:
- Clusters whose keywords mostly map to an existing page (action: optimise)
- Clusters with no confident page match (action: create)
- A single page recommended across multiple clusters (potential cannibalisation signal)
- Read
content_gap_report.csv — per-keyword gap rows (keyword, cluster_label, primary_intent, search_volume, keyword_difficulty, opportunity_score, page_similarity_score). Roll up by cluster and rank by total volume descending. The top clusters are the priority content-creation opportunities.
- Read
cannibalization_report.csv — one row per conflict (cluster_id, cluster_label, cannibalization_type, detail, keyword_count; detail lists the competing URLs and any average SERP overlap). Recommend resolution (consolidate / 301 redirect / differentiate).
- Read
recommendations.md — incorporate the engine's native recommendations.
- Read
architecture.json (if present) — one record per cluster with action (create/optimise/consolidate/gap/deprioritise), target_page_type, role (hub/spoke), target slug/URL, search_volume, and rationale. This is the structured, volume-weighted spine of the architecture proposal — trust it over ad-hoc judgement, then narrate it.
Output
Parsed findings (including the architecture plan) ready for synthesis.
Phase 5: Web Lifter Report Synthesis and Handoff
Objective
Produce the Web Lifter-standard markdown report and write the handoff JSON.
Report sections:
- Executive Summary — cluster count, total keyword volume covered, pages mapped, gaps identified, cannibalisation conflicts, top 3 content priorities.
- Cluster Map — table: cluster ID, label, keyword count, total volume, primary intent, mapped page (or "GAP"), recommended action (Create / Optimise / Consolidate / Deprioritise).
- Page-Map Decisions — for each mapped cluster, explain which page covers it and whether the mapping is strong (page explicitly targets cluster) or weak (page incidentally covers it).
- Top Content Gaps (volume-weighted) — top 10 gaps, each with: cluster label, total volume, primary intent, top 3 keywords, recommended content format.
- Cannibalisation Conflicts — each conflict with: cluster, competing URLs, overlap, recommended resolution.
- Proposed Site Architecture — omit for
optimise_only. Narrate architecture.json into proposed-architecture.md (write it to the output dir): group by action; for each proposed page give the target URL slug, page type, hub/spoke role and parent, supporting clusters, and volume; include a hub-and-spoke outline and any consolidation/redirect moves. In greenfield mode this is the full sitemap; in optimise_expand it is existing-page optimisations + new pages for genuine gaps. Off-service (deprioritise) clusters are listed separately with the reason.
- 30/60/90-Day Roadmap — 0–30: quick-win optimisations + fix cannibalisation; 31–60: create top gap pages by volume; 61–90: next gap pages + internal linking per the hub/spoke plan.
- Dashboard & Raw Outputs — link
dashboard.html (single offline view of everything) and list the preserved CSV/HTML/brief paths.
Handoff JSON — write to .project/.marketing-os/seo/clusters/<slug>/handoff.json:
{
"slug": "<slug>",
"method": "<method>",
"mode": "<optimise_only|optimise_expand|greenfield>",
"cluster_count": N,
"csv_paths": {
"keywords": "<path>",
"clustered_keywords": "<path>",
"keyword_page_map": "<path to keyword_page_map.csv>",
"content_gap_report": "<path to content_gap_report.csv>",
"cannibalization_report": "<path>",
"cluster_summary": "<path>"
},
"architecture_json": "<path to architecture.json, or null>",
"proposed_architecture_md": "<path to proposed-architecture.md, or null>",
"dashboard_html": "<path to dashboard.html>",
"generated_at": "<ISO8601>"
}
Output
Full markdown report rendered in conversation; handoff.json written to disk.
Output Format
Use the template at templates/output-template.md. Preserve all raw CSV paths in the report footer.
Behavioural Rules
- Never run clustering on an unvalidated CSV. Phase 1 validation must pass completely before Phase 2 begins.
- Preserve all raw outputs. Never delete or overwrite
clustered_keywords.csv, keyword_page_map.csv, content_gap_report.csv, cannibalization_report.csv, cluster_summary.csv. The report synthesises them — it does not replace them.
- Handoff JSON is mandatory. Downstream skills (
content-brief-generator, internal-linking-planner) depend on handoff.json to locate outputs. Never skip writing it.
- Volume-weight all prioritisation. Content gaps and roadmap items must be sorted by total cluster volume, not alphabetically or by cluster ID.
- Never fabricate cluster quality metrics. If the engine reports a silhouette score or inertia (
cluster_quality_report.csv), include it. Do not invent cluster quality indicators.
- Report cannibalisation without fear. Cannibalisation findings may implicate important existing pages. Report them accurately — the user needs to know.
- Method selection is advisory, not mandatory. If the user insists on a different method than recommended, honour it and note the trade-off in the report.
- Australian English in all narrative output. Report text, gap labels, and roadmap items use Australian English.
- If the runner fails, stop. Do not attempt to manually re-implement clustering logic. The bundled engine is the source of truth. Surface the error and wait for user direction.
- Handoff note must reference the next skill. End every report with a note pointing to
content-brief-generator as the natural next step.
Edge Cases
- Environment setup failed — re-run
scripts/setup_env.py. If the semantic/advanced deps won't install (e.g. no torch wheel), the runner still works in tf-idf mode on the core packages; tell the user quality will be lower and proceed only if they accept.
- No pages CSV available — offer to run
sitemap_parser.py with the user's domain, or accept a manually provided CSV. Without a pages CSV, clustering can still run but page-mapping will be empty — flag this clearly.
- CSV column mismatch — if the input CSV is missing required columns, display a migration command to add the missing columns as empty strings before retrying.
- Clustering produces only 1–2 clusters — likely too few keywords or a poorly-fitting count. Recommend raising
--clusters, trying --auto-k silhouette, or switching to hdbscan (and lowering --min-cluster-size). Surface the engine's quality metrics (cluster_quality_report.csv) to help the user diagnose.
- All keywords map to one page — may indicate a very narrow site. Treat all unmapped keywords as creation opportunities and flag that the site's topical coverage is narrow.
- Large list (> 1,000 keywords) — warn that clustering may take several minutes. Suggest splitting by intent group (informational / commercial / transactional) for faster, higher-quality results.
- Handoff.json already exists — overwrite it with the new run. Always use the most recent clustering results downstream.