Cluster Planner Skill
Turn the flat, ranked keyword queue into a strategy: a few money clusters, each a business-value parent topic tied to a live product, with a keystone article + a supporting ring. This is the layer that makes us publish on a plan instead of one-off keywords. See STRATEGY.md §1.
It is also the engine's anti-"stuck" mechanism: every run it proposes NEW clusters when it finds high-business-value demand, or a live product, that no cluster covers yet. The cluster list is a living config, never a fixed list.
Input
/cluster-planner (no args). Reads:
content-pipeline/0-keywords/clusters.md— the cluster config (the control surface;status: active/planned/archived).content-pipeline/0-keywords/keyword-queue.csv— the vetted, ranked, winnability-scored queue from/keyword-prioritization.brand-config.md→ Products / Features — the live product set (to detect products with no cluster).
Process
Read the active clusters from
clusters.md(skiparchived; treatplannedas not-yet-coverable but track them).Assign each queued keyword to a cluster. Match on relevance to the cluster's
parent_topic+seeds(token/topic overlap; a keyword may only belong to ONE cluster — its best match). A keyword that matches no active cluster →cluster = unclustered.Per cluster, pick the keystone + supporting ring. The keystone is the deep, definitive hub the cluster's internal links point to — and it MUST be winnable (never a hub we can't rank). Pick in this order:
- The cluster's
parent_topicterm is the strategic keystone target. If a member matching the parent_topic is winnable (winnability ≥ 5), it's the activekeystone. - If the parent_topic term is above-ceiling (
winnability ≤ 3— too hard for our DR right now), tag itrole = planned_keystone(the head term we build toward as DR rises —STRATEGY.md§2) and promote the best WINNABLE member as the activekeystone. Among winnable candidates, prefer the highesttraffic_potential(the broadest hub), not the narrowest long-tail — the keystone should be a broad term others link up to. - If no member of the cluster is winnable, set
coverage = keystone_deferredand leave no active keystone — build the supporting ring first; the keystone unlocks when DR rises. - Supporting = every other member (sub-topics, long-tail, intent variants) — each its own article, internally linked up to the keystone.
- Tag every row:
cluster(id) +role(keystone|planned_keystone|supporting). - Winnable-first ordering: within a cluster, order by
winnabilitythenpriority_score, so we build the rankable members before the reach ones (STRATEGY.md§2 — ambition expands as DR grows).
- The cluster's
Coverage check. For each active cluster flag:
no_keystone(zero members in the pool → a seeding gap to fill via Layer 1b),keystone_deferred(members exist but none winnable yet — supporting-only until DR rises),thin(< 3 supporting members), orhealthy. A cluster whose parent_topic is above-ceiling but has a winnable active keystone is stillhealthy(the head term sits asplanned_keystone).Cluster discovery — propose new clusters (the self-extension). Two sources:
- Uncovered demand: cluster the
unclusteredrows by theme; if a theme has ≥ 3 members that are winnable (winnability ≥ 5) AND business-value ≥ 2, propose it as a new cluster candidate (suggestedid,name,parent_topic= highest-traffic_potentialmember,seeds= the theme's terms). - Uncovered products: compare each live product/feature in
brand-config.mdagainst theproductcolumn of active clusters. Any live product with no cluster → propose a cluster for it (so a newly-shipped product gets a blog beachhead even ifclusters.mdlags). Ignorecoming-soon/roadmapproducts (don't propose clusters for things we can't demo yet). - Never auto-edit
clusters.md— write proposals for human/EO review (adding a cluster is a deliberate strategy call).
- Uncovered demand: cluster the
Write outputs:
- Cluster-tag the queue (ATOMIC — PLE-3063 item 4): read the entire
keyword-queue.csv, add/update thecluster+rolevalues for every row in memory, thenWritethe whole file back in one shot. Never useEditto patch individual cells / the role column in place — a linter/format hook can race a partialEditand corrupt or half-tag the queue (that's the "mutated queue role column" bug). One read → mutate the full table → oneWrite. Preserve column order and every other column untouched; no row duplication. content-pipeline/0-keywords/cluster-map.md— per active cluster: keystone (keyword + slug + priority + winnability), supporting count, coverage status. The at-a-glance strategy board.content-pipeline/0-keywords/cluster-proposals.md— the new-cluster candidates from step 5, each with why (members, traffic, BV) and a ready-to-pasteclusters.mdrow. Empty file with "no new clusters proposed" if none.
- Cluster-tag the queue (ATOMIC — PLE-3063 item 4): read the entire
Tell the user / log: one line per active cluster (
<name>: keystone "<kw>" (winnability X, priority Y), N supporting, <coverage>), thenN new cluster(s) proposed(or none).
Output
keyword-queue.csvgainscluster+rolecolumns (every queued row tagged).cluster-map.md— the live cluster board (keystone + ring + coverage per cluster).cluster-proposals.md— new-cluster candidates (the growth feed).
Autonomous behavior (Layer 6 of /keyword-research-pipeline)
- Run AFTER
/keyword-prioritization(it needspriority_score+winnability). - Only cluster rows that reached the queue (BID-PASS, not tool-led, not gap=strong).
- In autonomous mode, skip the user-facing suggestion; emit the one-line-per-cluster summary + the proposal count, and ensure
cluster-map.md+cluster-proposals.mdare written for the publish loop + the operator to read.
Quality checklist
- Every queued keyword has a
cluster(orunclustered) +role. - Every active cluster with ≥1 winnable member has exactly one active
keystone, and that keystone is winnable (winnability ≥ 5) — an above-ceiling keystone is a bug; it should beplanned_keystonewith a winnable member promoted. -
cluster-proposals.mdactually fires when the queue contains a winnable, high-BV theme outside the current clusters (the anti-stuck check — if it never proposes anything across many runs while unclustered winnable rows exist, the discovery step is broken). - No cluster maps to a
coming-soon/roadmapproduct (we can't demo it → not a money cluster yet).
Discipline
- A cluster must be product-led (BV ≥ 2). Don't invent clusters for high-traffic, no-product-fit themes (the GIF trap —
STRATEGY.mdanti-pattern #8); route those tounclusteredand leave them. - The keystone is chosen on winnable traffic potential, not raw volume — a parent topic we can't rank for yet stays a planned keystone until DR supports it (
STRATEGY.md§2).