返回 Skills 目录
samber/developer-relations-skills已通过检查

SKILL DETAIL

developer-keyword-research

samber/developer-relations-skills/developer-keyword-research

Builds a prioritized keyword list for technical search queries (error strings, "how to X in Y" tasks, integration intents, comparisons, migrations) mined from docs search logs, support tickets, issue trackers and first-party query data rather than keyword-tool volume. Use whenever the user asks what developers actually search for, wants keywords for a developer tool, API, SDK or docs site, an error-message keyword list, demand sizing for a technical topic, or which docs pages to create from search demand - even if they never say "keyword". Does not write the pages. Do NOT use for on-page or docs-site SEO - use samber/developer-relations-skills@docs-seo.

安装量 · 415查看来源

Installation

npx skills add https://github.com/samber/developer-relations-skills --skill developer-keyword-research

技能文件

SKILL.md

最近同步 · 2026年10月23日

evals/evals.json›
{
  "skill_name": "developer-keyword-research",
  "evals": [
    {
      "id": 1,
      "prompt": "We're Hookline, a webhook delivery API with about 900 paying teams. Our growth lead ran our domain through a keyword tool and handed me this to work from:\n\nwebhook delivery service - 2,400/mo, KD 62\nwhat is a webhook - 18,000/mo, KD 71\nwebhook retry best practices - 390/mo, KD 34\nwebhook testing tool - 1,300/mo, KD 55\nhookline WEBHOOK_SIGNATURE_MISMATCH - 0/mo, KD n/a\nhookline 429 too many requests webhook - 0/mo, KD n/a\nhookline replay failed webhook - 0/mo, KD n/a\n\nThey want us to drop the bottom three and build the top four. I have Search Console access, a Zendesk export going back a year, and docs-site search logs for the last 6 months. Sort out what we should actually write.",
      "expected_output": "A keyword map that keeps the zero-volume error strings, explains why reported volume cannot rank a technical list, demands first-party evidence with Measured/User-provided/Estimated labels, and ships the full section shape.",
      "files": [],
      "expectations": [
        "Keeps the three zero-volume entries (WEBHOOK_SIGNATURE_MISMATCH, 429 too many requests, replay failed webhook) as candidates rather than dropping them because the tool reports zero",
        "States that a reported volume of zero is a statement about the tool's index, not about developer demand",
        "Explains that reported volume averages roughly twelve months, merges a keyword with its close variants, and is rounded",
        "Cites that roughly 93% of a major tool's U.S. keyword database sits below ten monthly searches",
        "Treats keyword-tool volume as a tiebreaker only and ranks no cluster on volume alone",
        "Does not place the head category terms ('webhook delivery service', 'what is a webhook') at the top of the ranking on volume grounds",
        "Directs the harvest at the Search Console export, docs-search logs and Zendesk export before ranking anything",
        "Tags every evidence item as Measured, User-provided, or Estimated",
        "Writes N/A for any metric it cannot obtain and names the source it could not reach, rather than supplying a number",
        "Output carries the sections Scope, Ranked clusters, Company-adoption set, Fix first, Do not target, and Backlog",
        "The Do not target list is non-empty",
        "Reports the 70% first-party evidence floor as this approach's own baseline rather than an industry standard"
      ]
    },
    {
      "id": 2,
      "prompt": "Parallax Metrics here - we're a time-series database, docs live at docs.parallaxmetrics.io. I have API access to our search console and 16 months of data. I want the queries where we're sitting on page two so we can push them up. Write me the exact extraction plan: what to request, what filters to set for positions 5 through 20, and what to do with the rows once they land. Also, the person who did this before me reported 'docs total: 412,000 impressions' by adding up the query rows - I want that number refreshed in the same way.",
      "expected_output": "An extraction plan that corrects the filter assumption, raises the row limit, cuts the window client-side, refuses the summed total, and joins queries to landing pages.",
      "files": [],
      "expectations": [
        "States that average position is returned as a metric and cannot be applied as a server-side filter",
        "Names the dimensions that are actually filterable (country, device, page, query, searchAppearance, date, hour) or states explicitly that position is not among them",
        "States the row limit accepts 1 to 25,000 rows and returns 1,000 by default",
        "Instructs raising the row limit to its maximum for the pull",
        "Cuts the 5-20 average-position window client-side, after the export, rather than requesting it",
        "Notes the report comes back sorted by clicks, so the window can only be cut after a wide pull",
        "Reports that the API does not guarantee returning all data rows, only top ones",
        "Warns that rare queries are withheld for privacy, so the visible tail is a floor and not a total",
        "Refuses to sum query-level rows into a site total, and says so about the 412,000 figure specifically",
        "Joins each surviving query to its landing page",
        "States that the join is what exposes two of their own pages splitting one query",
        "Attributes the 5-20 window as a reused convention rather than a measured cut-off"
      ]
    },
    {
      "id": 3,
      "prompt": "Corvid Cache - distributed cache, Java and Go clients. Here are representative support subjects from the last two weeks (we shipped 4.2 on the 3rd):\n\n- connection to corvid://cache-prod-04:7946 refused after 20s\n- connection to corvid://cache-eu-2:7946 refused after 20s\n- Error: dial tcp 10.8.1.44:7946: connect: connection refused\n- go client cannot reach cluster, connection refused\n- cache unreachable in staging, connection refused\n- CorvidConnectionException: caused by java.net.ConnectException: Connection refused\n- auth failed: token expired for node cache-prod-04\n- CorvidAuthException: credential rejected, token expired\n- node rejects our client: token expired since upgrade to 4.2\n- token expired errors started after 4.2 rollout\n\nVolumes: 31 connection-refused tickets spread evenly across both weeks, and 24 token-expired tickets, every one of them dated after the 3rd. Error telemetry for the same window shows 812 refused events and 1,140 auth-expired events. Turn this into doc page targets.",
      "expected_output": "Two normalized clusters, not ten targets, with the post-release auth cluster routed to engineering as a regression rather than treated as a docs gap.",
      "files": [],
      "expectations": [
        "Replaces hosts, IP addresses, ports, timeout values and node identifiers with placeholders before grouping",
        "Trims the client-library wrapper text (CorvidConnectionException, java.net.ConnectException) down to the innermost cause",
        "Produces one cluster per cause rather than one target per phrasing",
        "Keeps the most frequent raw phrasings as a variants column on the cluster instead of as separate rows",
        "Separates the connection-refused subjects from the token-expired subjects into two distinct clusters",
        "Emits no keyword containing a hostname, IP address or port number",
        "Counts evidence per cluster and per time window, not as a single undated total",
        "Flags the token-expired cluster as a regression tied to the 4.2 release and routes it to engineering rather than treating it as a docs gap",
        "Labels the ticket and telemetry counts Measured, with source, count and date",
        "Routes the surviving docs-gap cluster to exactly one canonical error entry",
        "States that frequency alone does not decide the ranking, weighing time-to-resolve and whether a reader can self-serve the fix"
      ]
    },
    {
      "id": 4,
      "prompt": "Fernweh Analytics, product analytics SDK. Small team: our one technical writer ships about two documentation pages a quarter and that is the whole budget - nothing left over for upkeep on anything already published. Legal will not review any page that names another vendor; that door is closed, not slow. We officially support Node, Python and Swift.\n\nDocs search logs, last 90 days: 'fernweh vs Amplitude' 61 searches, 'fernweh segment integration' 140, 'fernweh snowflake export' 98, 'fernweh SDK init error 4013' 310, 'fernweh sampling rate default' 220, 'fernweh migrate from Mixpanel' 77, 'what is product analytics' 45. No error telemetry, no Search Console access yet.\n\nBuild me the keyword map.",
      "expected_output": "A map that deletes the integration and comparison families outright with the constraint named, cuts the rest to two quarters of a two-page-per-quarter capacity, and dates the backlog.",
      "files": [],
      "expectations": [
        "Deletes the integration family (segment, snowflake) from the map and names the absent maintenance capacity as the reason",
        "States that an integration guide nobody can keep current rots into a wrong answer that costs more than the page ever earned",
        "Deletes the comparison family ('fernweh vs Amplitude') and names the absent legal sign-off as the reason",
        "Serves the competitor-comparison demand with a migration guide about their own product instead",
        "Does not park either deleted family at the bottom of the ranked list or as a low-priority row",
        "Explains that a cluster parked at the bottom returns next quarter as unbudgeted scope",
        "Cuts the ranked list to roughly what two pages per quarter covers over two quarters",
        "Attaches a parked date to the backlog entries",
        "Places the error cluster ('SDK init error 4013') at or near the top, on the grounds the fix already exists and the page costs about an hour",
        "States the sort used above the ranked table",
        "Records the unavailable sources (error telemetry, search console) as unavailable in the Scope section"
      ]
    },
    {
      "id": 5,
      "prompt": "Tessellate - self-hosted API gateway. We sell to platform engineering teams at mid-size companies, and both individual developers and internal champions use us. Our biggest rival just announced their 2.x line goes end-of-life in nine months alongside a breaking 4.0.\n\n90 days of first-party data:\n- 'tessellate 502 upstream timeout' - 1,900 error events, 44 tickets\n- 'tessellate jwt plugin example' - 210 docs searches\n- 'how to rate limit per consumer tessellate' - 380 impressions, avg position 11\n- 'tessellate rate limits' - 52 impressions, 14 questions logged by our solutions engineers\n- 'self-host tessellate requirements' - 71 impressions, 11 SE questions\n- 'tessellate vs <rival>' - 96 impressions, avg position 19, 8 SE questions\n- 'migrate from <rival> to tessellate' - 63 docs searches, 19 SE questions\n- 'what is an api gateway' - 140 impressions, avg position 28\n\nWe can ship 8 pages this quarter and legal has already cleared competitor claims. Rank it.",
      "expected_output": "A ranked table sorted by outcome per page-hour with a stated sort, a separate company-adoption set with its own target count, and the migration cluster promoted on the rival's deprecation.",
      "files": [],
      "expectations": [
        "States the sort - outcome per page-hour, highest first - above the ranked table",
        "Does not order the table cheapest-first",
        "Emits a Company-adoption set as a section separate from the main ranked table",
        "Gives the company-adoption set its own filled target count rather than letting it inherit the ratio's order",
        "Names migration and comparison as the high-value clusters the efficiency ratio demotes",
        "States the condition that would promote each demoted cluster",
        "Promotes the migration cluster outright because the rival announced a deprecation and a breaking major",
        "Breaks ties on evidence tier first, then business proximity",
        "Assigns the evaluation queries ('tessellate rate limits', 'self-host tessellate requirements') to the company-adoption set",
        "Routes each cluster to exactly one canonical target surface",
        "States that the ranking formula is a default to re-rank against this team's situation, not a law",
        "Does not rank the concept cluster ('what is an api gateway') above the error cluster"
      ]
    },
    {
      "id": 6,
      "prompt": "Nimbus Ledger - double-entry accounting API. We went GA seven weeks ago. There is no support queue to speak of (11 tickets total, ever), our docs search has logged about 40 queries in its whole life, and Search Console shows 6 queries with any impressions at all. We do have a Sentry project that has been running since the private beta with a year of error data in it, plus npm download numbers.\n\nNobody on the team has done this before. What order should we pull evidence in, and what does the map look like given how little we have?",
      "expected_output": "A harvest order that promotes error telemetry above the cheap tiers for a product this young, with the first-party floor lowered explicitly in Scope and nothing invented.",
      "files": [],
      "expectations": [
        "Promotes product error telemetry above the cheaper sources instead of leaving it last in the default harvest order",
        "Gives the reason: for a product this young the cheap sources observe an audience that does not exist yet",
        "States that error telemetry is the only source that sees developers who hit a failure and never reported it",
        "Notes that the telemetry cost is already paid because the Sentry project is already running",
        "Lowers the 70% first-party evidence floor explicitly, in the Scope section",
        "Labels the 70% floor as this approach's own baseline rather than a published benchmark",
        "Records every unreachable or empty source as unavailable in Scope rather than silently omitting it",
        "Writes N/A instead of inventing a count for any missing metric",
        "Applies no Measured label to an inferred or estimated figure",
        "Notes that telemetry measures occurrence rather than search, so a frequent error users retry past may produce no query",
        "Ranks no cluster on keyword-tool volume alone"
      ]
    },
    {
      "id": 7,
      "prompt": "Skiff Realtime (websocket infrastructure). I have four pairs of candidate keywords and I cannot tell which pairs should share a page. I pulled the top ten organic results for each query and counted how many URLs the two sets have in common:\n\nA) 'skiff websocket connection drops' / 'skiff socket disconnects randomly' - 8 URLs in common\nB) 'skiff reconnect strategy' / 'skiff exponential backoff websocket' - 5 in common\nC) 'skiff presence api' / 'skiff who is online' - 2 in common\nD) 'skiff message ordering' / 'skiff pricing per connection' - 1 in common\n\nI also have 60 more candidates sitting in a spreadsheet. Tell me how to call these four, and how to handle the other 60 without losing my mind.",
      "expected_output": "Each pair decided on shared-result count against the published bands, with the thresholds attributed as a convention and the 60 remaining candidates pre-grouped rather than compared pairwise.",
      "files": [],
      "expectations": [
        "Decides each pair on the shared top-ten result count, not on how similar the two phrasings read",
        "Calls pair A (8 shared) one page, with the other phrasing kept as a variant",
        "Calls pair B (5 shared) one cluster with separate pages",
        "Calls pair C (2 shared) separate clusters that cross-link",
        "Calls pair D (1 shared) unrelated and does not group them",
        "Attributes the shared-result thresholds as a reused convention rather than a measured standard",
        "States that comparing every pair is quadratic and does not propose an all-pairs comparison of the 60 remaining candidates",
        "Pre-groups the remaining candidates by query family and tests only the pairs sitting on a boundary",
        "Notes that phrasings that read alike can draw disjoint result sets, and phrasings that read differently can draw the same one"
      ]
    },
    {
      "id": 8,
      "prompt": "Glasswing Deploy, a CI/CD runner. Candidate clusters out of our research:\n\n1. 'glasswing on-prem pricing calculator' - 82 docs searches/90d. We have no calculator and on-prem pricing is quote-only.\n2. 'glasswing exit code 137' - 4,100 error events, 61 tickets a quarter. Every one is us OOM-killing the container and printing a message that only says 'job failed'. Engineering has a fix queued for 5.1 that will print the actual memory limit.\n3. 'python venv activate not found' - 1,200 impressions. People hit it inside our runners.\n4. 'glasswing cache key not restored' - 900 docs searches, incumbent result is a 2023 forum thread about a version we no longer ship.\n5. 'glasswing secrets rotation' - 140 docs searches, incumbent is our own README, which covers about half of it.\n\nWhich of these do we write, and how should I brief the writer?",
      "expected_output": "Three outright refusals with reasons, and a winnability judgement naming the incumbent and its specific weakness for the two clusters that survive.",
      "files": [],
      "expectations": [
        "Emits a non-empty Do not target section",
        "Refuses the pricing-calculator query outright on the grounds the product cannot answer it honestly",
        "Refuses the exit-code-137 cluster on the grounds its real fix is a product change - a better error message - rather than a page",
        "Routes the exit-code-137 cluster to engineering rather than to the writer",
        "Refuses the 'python venv activate not found' lookup on the grounds it is owned by the language's own documentation",
        "Keeps none of the three refusals as a low-priority ranked row",
        "Names the incumbent result for each cluster it keeps",
        "Names the specific weakness it can beat for each kept cluster (stale answer, version that no longer exists, listing with no working configuration, or reference page with no example)",
        "Briefs the writer on completeness: the exact string reproduced verbatim, the version stated, a runnable fix, and failure conditions covered",
        "Does not settle winnability with a link-graph keyword difficulty score",
        "States that a round of research that refused nothing did not apply the winnability filter"
      ]
    },
    {
      "id": 9,
      "prompt": "Otterpost - transactional email API. My VP wants a forecast before approving the docs budget: how much traffic will these 14 pages bring? They also want the quarterly report to show number of pages published, since that is what the other teams report. Half our candidate list is things like 'what is DKIM', 'smtp vs api sending' and 'what is a bounce rate', which I suspect an assistant just answers outright now. Give me the forecast and the reporting plan.",
      "expected_output": "A return framed in citations and arrivals rather than clicks, the one-line-answer clusters discounted but kept, and published-page count refused as the outcome metric.",
      "files": [],
      "expectations": [
        "Declines to forecast the program in projected clicks alone",
        "Scores each cluster on whether the page would be the source an assistant assembles its answer from",
        "Forecasts the return in citations and arrivals rather than clicks",
        "Discounts the clusters whose whole value is a one-line answer but keeps them on the map",
        "Gives the reason for keeping them: the assistant still assembles its answer from what was published",
        "Cites that 84% of surveyed developers use or plan to use AI tools, attributing the survey",
        "Cites that 66% name AI solutions that are 'almost right, but not quite' as their top frustration",
        "Cites the fall in trust in AI answers from roughly 40% in 2023 to 29% in 2025",
        "Refuses published-page count as the outcome metric, on the grounds it measures the writing team rather than the research",
        "Proposes coverage, movement and arrival as the metrics to track instead",
        "Defines arrival as first-party query counts for the exact strings after each page ships"
      ]
    },
    {
      "id": 10,
      "prompt": "Riverbed Queue. We are not adding pages this cycle - our writer is on parental leave and I only want to know what to fix in what we already have. Here is what I pulled from the console:\n\n- 'riverbed dead letter queue' - /docs/dlq at avg position 7 with 410 impressions, and /blog/understanding-dead-letters at avg position 9 with 380 impressions\n- 'riverbed consumer lag' - /docs/monitoring at avg position 14, 260 impressions\n- 'riverbed v3 partitioning' - /docs/v3/partitions at position 11 and /docs/v2/partitions at position 12, near-identical text\n- 'riverbed batch size default' - /docs/reference/config at position 6, 190 impressions\n\nTell me what to do.",
      "expected_output": "A screening pass only: the cannibalization pairs canonicalized and the striking-distance pages named as update targets, with the output cut to Fix first and Ranked clusters.",
      "files": [],
      "expectations": [
        "Runs a screening pass rather than producing a full new-keyword map",
        "Limits the output to the Fix first and Ranked clusters sections",
        "Proposes no new pages",
        "Identifies /docs/dlq and /blog/understanding-dead-letters as two pages splitting one intent, a cannibalization defect",
        "Picks one canonical target for that intent and links the other surface to it",
        "Flags the near-identical /docs/v3/partitions and /docs/v2/partitions pages as competing for the same phrasing",
        "Treats the pages already ranking at average positions 6, 7, 9, 11, 12 and 14 as update targets rather than new pages",
        "Works from the striking-distance set (average position 5-20)",
        "Keeps the join between each query and its landing page",
        "Does not invent a report shape outside the skill's defined sections"
      ]
    },
    {
      "id": 11,
      "prompt": "Antimatter Streams, a managed Kafka-compatible broker. We officially ship clients for Java, Go and Python. Marketing wants us to generate a page for every combination of {Java, Go, Python, Rust, Ruby, PHP, Elixir, .NET} x {produce, consume, exactly-once, schema registry, dead letter, consumer groups, ACLs, compaction} x {v2, v3} so we 'own the space'. That is several hundred pages.\n\nWe do have a defined error catalog in the codebase (47 codes, and the message text changed for 6 of them between v2 and v3). Our docs search shows 'antimatter timeout' at 290 searches per 90 days and I honestly cannot tell whether those are people hitting a timeout error or looking for the timeout config key. Our solutions engineers say the questions they field are nothing like any of this list. What should we actually do?",
      "expected_output": "An evidence-gated expansion that refuses the unsupported axes and the ungated matrix, starts from the error catalog, resolves the ambiguous phrasing, and pulls the SE question list.",
      "files": [],
      "expectations": [
        "Refuses to generate cells for the languages the product does not officially support (Rust, Ruby, PHP, Elixir, .NET)",
        "Gives the reason: a cell with no shipped example cannot be answered credibly and a broken example costs more than the traffic is worth",
        "Gates every generated cell on at least one evidence item before it enters the map",
        "States that an ungated matrix is a doorway-page generator",
        "Sends candidates with no evidence to a parking or backlog list rather than into the ranked map",
        "Starts the error catalogue from the product's own 47 error definitions rather than only from reported tickets",
        "Crosses the error catalogue with versions only for the 6 codes whose message text actually changed",
        "Stamps version-bound clusters with a review date",
        "Flags 'antimatter timeout' as an intent collision and checks what the current results actually answer before routing it to a surface",
        "Limits the task crossing to the top two or three language and framework combinations rather than all supported ones",
        "Pulls the solutions engineers' question list for the limits, permissions, compliance and migration questions that never reach public channels"
      ]
    }
  ],
  "trigger_queries": [
    { "query": "What do developers actually search for when they get stuck with our SDK?", "should_trigger": true },
    { "query": "Build a keyword list for our Postgres connection-pooler docs", "should_trigger": true },
    { "query": "we need to figure out which docs pages to write next based on search demand", "should_trigger": true },
    { "query": "Turn last quarter's support tickets into troubleshooting page targets", "should_trigger": true },
    { "query": "Here's our Search Console export and 90 days of docs-search logs - what should we write next?", "should_trigger": true },
    { "query": "We're launching a Rust SDK next month, what content should ship with it?", "should_trigger": true },
    { "query": "how do I size demand for a technical topic when every tool reports zero volume", "should_trigger": true },
    { "query": "Which of our existing pages should we update instead of writing new ones?", "should_trigger": true },
    { "query": "I want an error-message keyword list for our CLI", "should_trigger": true },
    { "query": "our docs search logs are full of zero-result queries, can you make sense of them", "should_trigger": true },
    { "query": "Prioritise the topics our developer documentation should cover next quarter", "should_trigger": true },
    { "query": "what are people typing into Google right before they land on our API docs?", "should_trigger": true },
    { "query": "We keep answering the same question in Discord. What pages would stop that?", "should_trigger": true },
    { "query": "Find the search terms worth targeting for an open-source observability agent", "should_trigger": true },
    { "query": "I need a ranked list of technical search queries for our developer portal", "should_trigger": true },
    { "query": "how do we decide which error codes deserve their own doc page", "should_trigger": true },
    { "query": "map search demand for our Kubernetes operator", "should_trigger": true },
    { "query": "Our rivals rank for everything and we rank for nothing - where do we even start picking topics?", "should_trigger": true },
    { "query": "can you help me pick which how-to guides to write based on real developer questions", "should_trigger": true },
    { "query": "give me a demand-ranked content target list for our SDK documentation", "should_trigger": true },
    { "query": "we have 4,000 GitHub issues labelled question - what pages should come out of them?", "should_trigger": true },
    { "query": "which integration guides should we build first?", "should_trigger": true },
    { "query": "I want to know what developers search for before they evaluate our platform", "should_trigger": true },
    { "query": "keyword research for a developer tool", "should_trigger": true },
    { "query": "Our support queue is the same six problems over and over. Turn that into a docs plan.", "should_trigger": true },
    { "query": "Should we write an 'X vs Y' page or a migration guide? I have no idea what people look for.", "should_trigger": true },
    { "query": "how do I find search terms for exact error strings", "should_trigger": true },
    { "query": "size the audience for a technical topic without leaning on Ahrefs volume", "should_trigger": true },
    { "query": "build me a docs content plan grounded in what users actually ask", "should_trigger": true },
    { "query": "we want to rank for the problems our users hit, not generic category terms", "should_trigger": true },
    { "query": "Which queries are we already close to ranking for?", "should_trigger": true },
    { "query": "pull our striking distance queries and tell me what to fix first", "should_trigger": true },
    { "query": "our devrel team has no idea what topics to prioritise - help", "should_trigger": true },
    { "query": "I'd like a prioritized list of search topics for our API reference", "should_trigger": true },
    { "query": "what should the troubleshooting section of our docs actually cover", "should_trigger": true },
    { "query": "Our CTO wants a content roadmap backed by data, not opinions", "should_trigger": true },
    { "query": "figure out what to write about for a developer audience using our own first-party data", "should_trigger": true },
    { "query": "we have Algolia DocSearch analytics - can we plan content from that?", "should_trigger": true },
    { "query": "help me decide which pages to create from our search demand", "should_trigger": true },
    { "query": "what technical queries should our documentation target", "should_trigger": true },
    { "query": "Demand sizing for a new SDK language we're considering documenting", "should_trigger": true },
    { "query": "our rate-limit and compliance questions never show up in any keyword tool - how do we find them?", "should_trigger": true },
    { "query": "Which developer search terms can we realistically win?", "should_trigger": true },
    { "query": "I want to stop guessing what to document", "should_trigger": true },
    { "query": "rank the topics our docs should cover by evidence, not by volume", "should_trigger": true },
    { "query": "we're about to deprecate v2 - what should we publish around that?", "should_trigger": true },
    { "query": "extract content targets from our Zendesk export and our issue tracker", "should_trigger": true },
    { "query": "developer search intent research for a database company", "should_trigger": true },
    { "query": "what queries do platform engineers type when evaluating a tool like ours?", "should_trigger": true },
    { "query": "Give me a keyword map for our devtool", "should_trigger": true },
    { "query": "can you cluster our error messages into page targets", "should_trigger": true },
    { "query": "our blog gets traffic but nobody signs up - are we writing against the wrong searches?", "should_trigger": true },
    { "query": "which search queries should we own as a developer-first company", "should_trigger": true },
    { "query": "help me find the long tail of technical queries around our product", "should_trigger": true },
    { "query": "I have a CSV of docs-site searches, what do I do with it", "should_trigger": true },
    { "query": "we're a pre-launch devtool with no traffic - how do we pick topics to write about?", "should_trigger": true },
    { "query": "Identify documentation gaps from what developers are searching for", "should_trigger": true },
    { "query": "what's worth writing for developers who are mid-failure?", "should_trigger": true },
    { "query": "our docs pages aren't getting indexed, fix the canonicals", "should_trigger": false },
    { "query": "the old version of our docs outranks the current one", "should_trigger": false },
    { "query": "set up hreflang across our translated documentation", "should_trigger": false },
    { "query": "our preview docs got indexed by Google, how do we get them out?", "should_trigger": false },
    { "query": "review the title and description templates our docs generator emits", "should_trigger": false },
    { "query": "audit the XML sitemap of our documentation site", "should_trigger": false },
    { "query": "write the troubleshooting page for ECONNREFUSED in our client", "should_trigger": false },
    { "query": "generate an error-code catalog page from our source of truth", "should_trigger": false },
    { "query": "build us a known-issues page", "should_trigger": false },
    { "query": "audit our docs against Diátaxis", "should_trigger": false },
    { "query": "our documentation is a mess, help me reorganize the information architecture", "should_trigger": false },
    { "query": "where should this page live in our docs navigation?", "should_trigger": false },
    { "query": "plan next quarter's devrel content calendar with owners and dates", "should_trigger": false },
    { "query": "how many blog posts can a two-person devrel team realistically ship in a quarter?", "should_trigger": false },
    { "query": "how does our developer experience compare to our biggest competitor's?", "should_trigger": false },
    { "query": "what is our competitor doing in devrel right now", "should_trigger": false },
    { "query": "who are our developers actually - cut them into segments", "should_trigger": false },
    { "query": "should we target hobbyists or enterprise platform teams?", "should_trigger": false },
    { "query": "map the developer journey from first touch to production", "should_trigger": false },
    { "query": "make our SDK docs readable by coding agents", "should_trigger": false },
    { "query": "do we need an llms.txt file?", "should_trigger": false },
    { "query": "agents keep inventing API calls that don't exist in our SDK", "should_trigger": false },
    { "query": "write a getting-started tutorial for our Go client", "should_trigger": false },
    { "query": "our quickstart takes 40 minutes, cut it down to five", "should_trigger": false },
    { "query": "rewrite our README so it converts visitors", "should_trigger": false },
    { "query": "turn these commits into release notes", "should_trigger": false },
    { "query": "draft an engineering blog post about our new scheduler", "should_trigger": false },
    { "query": "what devrel metrics should we report to the board?", "should_trigger": false },
    { "query": "set up analytics for our developer relations program", "should_trigger": false },
    { "query": "standardize the code samples across our documentation", "should_trigger": false },
    { "query": "write a CFP abstract for KubeCon", "should_trigger": false },
    { "query": "design a developer champions program", "should_trigger": false },
    { "query": "how should we moderate our Discord server?", "should_trigger": false },
    { "query": "pick an open-source license for our new project", "should_trigger": false },
    { "query": "plan our OSS launch on Hacker News", "should_trigger": false },
    { "query": "how should we price our developer tool?", "should_trigger": false },
    { "query": "do keyword research for my ecommerce store's product pages", "should_trigger": false },
    { "query": "find keywords for a local plumbing business", "should_trigger": false },
    { "query": "keyword clustering for a fashion affiliate site", "should_trigger": false },
    { "query": "build a programmatic SEO template for city landing pages", "should_trigger": false },
    { "query": "run an on-page SEO audit on my homepage", "should_trigger": false },
    { "query": "write an SEO content brief for 'best CRM software'", "should_trigger": false },
    { "query": "get my site cited by ChatGPT and Perplexity", "should_trigger": false },
    { "query": "analyze my backlink profile and find link opportunities", "should_trigger": false },
    { "query": "what's my domain rating and how do I raise it?", "should_trigger": false },
    { "query": "optimize my Google Business Profile for the local pack", "should_trigger": false },
    { "query": "fix my Core Web Vitals, LCP is sitting at 4.2s", "should_trigger": false },
    { "query": "my organic traffic dropped 40% after the last core update", "should_trigger": false },
    { "query": "write meta descriptions for 200 product pages", "should_trigger": false },
    { "query": "add FAQ schema to my blog posts", "should_trigger": false },
    { "query": "research keywords for a B2C mobile game landing page", "should_trigger": false },
    { "query": "which long-tail keywords should my accounting SaaS marketing site target?", "should_trigger": false },
    { "query": "do a competitor keyword gap analysis for a marketing agency", "should_trigger": false },
    { "query": "build an internal linking plan for my content site", "should_trigger": false },
    { "query": "set up a rank tracker for 500 keywords", "should_trigger": false },
    { "query": "find high-intent PPC keywords for a Google Ads campaign", "should_trigger": false },
    { "query": "now write the actual how-to guide for retrying failed jobs in Node", "should_trigger": false },
    { "query": "publish the error entries we agreed on last week", "should_trigger": false }
  ]
}
references/demand-evidence-sources.md›
# Demand evidence sources

What each source measures, how to extract it, and how it lies. Cite source, count and date for every evidence item in the keyword map.

## Contents

- Why tool volume is last
- Tier 1: first-party query data
- Tier 2: docs-site search logs
- Tier 3: support and issue corpora
- Tier 4: product error telemetry
- Tier 5: public Q&A and forums
- Tier 6: ecosystem counters
- Tier 7: autocomplete and related searches
- Tier 8: keyword tools
- Optional tool-integration note

## Why tool volume is last

Ahrefs reports that keywords with fewer than 10 monthly searches make up roughly 93% of its U.S. keyword database (2.3 billion terms, against fewer than 18,000 above 100k searches) and that over 95% of conversational long-tail phrasings have no measurable volume at all (ahrefs.com/blog/long-tail-keywords, updated 2026-05-27). Google's own keyword planner averages the last twelve months, reports the figure for a keyword _and its close variants_, and rounds the result (support.google.com/google-ads/answer/3022575).

So a reported 0 means the tool's index has nothing, and a reported 90 may be ten different error strings merged. Neither number can rank a technical list.

## Tier 1: first-party query data

**Measures** real impressions and clicks for real phrasings on pages you own, including strings no tool lists.

**Extract**:

- Export queries with impressions, clicks and average position for the longest window available.
- Isolate average position 5-20 as the striking-distance set.
- Join query to landing page to expose two of your own pages splitting one query.

Ask for the largest row count the export allows. Google's Search Analytics API accepts 1 to 25,000 rows and defaults to 1,000, and it exposes position as a metric with no position filter, so the 5-20 window has to be cut client-side after a wide pull sorted by clicks. Its own reference states it "does not guarantee to return all data rows but rather top ones".

**Bias**:

- Only shows queries you already rank for at all, so it cannot surface an uncovered topic.
- Rare queries are withheld for privacy, so the visible long tail is a floor, not a total, and query rows must never be summed into a site total.
- Chart and table totals in the same report can also differ, through property-versus-page aggregation.

## Tier 2: docs-site search logs

**Measures** what a reader types once already on your property - the purest statement of an unmet need.

**Extract**:

- Top queries by volume.
- Queries returning zero results.
- Queries whose top click is not the answer (high search-refinement or immediate re-query).

**Diagnosing failures**: curate roughly 50 representative queries from the popular and zero-result lists into a golden set, classify each by intent (exact lookup, navigational, task-oriented, concept, broad, version-or-variant), and retest the set after every fix - a search-quality diagnostic practice documented in Algolia's own engineering guidance, not a universal standard. Adapt the sample size to the site's real query volume.

**Bias**: only covers people who found the docs; scoped to your vocabulary, so it under-reports the words newcomers use.

## Tier 3: support and issue corpora

**Measures** questions expensive enough that someone asked a human.

**Extract**:

- Ticket subjects and first customer messages.
- Issues labelled question/support/docs, including those closed as configuration.
- Locked and duplicated threads.
- Recurring community questions.

**Bias**: public-only projects have no ticket corpus at all, while enterprise products hide their loudest failures in private tickets. Mining only public sources under-serves exactly the accounts that pay.

## Tier 4: product error telemetry

**Measures** failures that occur, whether or not anyone reported them - the only source that reveals silent demand.

**Extract**: error-code counters by frequency and by trend across releases; the distribution of affected versions.

**Bias**: measures occurrence, not search. A frequent error users retry past without searching produces no query at all.

## Tier 5: public Q&A and forums

**Measures** question counts, view counts and answer quality on the results you would have to displace.

**Extract**:

- Question count per tag or topic.
- View counts on the top threads.
- The age of the accepted answer (a stale accepted answer is the strongest winnability signal available for free).

**Bias**: skews to individual adopters and to older, larger language communities. Answer volume also reflects historical activity rather than current demand.

## Tier 6: ecosystem counters

**Measures** the size of the population that could search a term: package downloads, repository dependents, framework version adoption.

**Extract**: download trend per package and per major version; dependent counts.

**Bias**: sizes an audience, never a query. Automated traffic inflates registry downloads, so use trend and relative comparison rather than absolute counts.

## Tier 7: autocomplete and related searches

**Measures** how a query is worded, from the search interface's own suggestions and related-question boxes.

**Extract**: seed a family template plus one modifier and record the suggestions; repeat across the modifier set. If your environment cannot browse, ask the user to paste suggestions for the five most important seeds instead of skipping the step.

**Bias**: suggestion lists are personalized and location-dependent, and they say nothing about frequency. Treat every harvested phrase as a candidate needing evidence from a higher tier.

## Tier 8: keyword tools

**Measures** an estimated volume and a link-graph difficulty score.

**Use**: only to break ties between candidates that already have first-party evidence, and to catch a genuinely high-volume category term the internal signals missed.

**Bias**: see the section above. Difficulty scores model link graphs, not whether a five-year-old forum answer can be out-completed.

## Optional tool-integration note

Nothing in this skill requires a specific product. When one of these is available, it maps onto the tiers as follows:

- Google Search Console, Bing Webmaster Tools, or a self-hosted analytics query report → tier 1.
- Algolia DocSearch analytics, Typesense or Meilisearch analytics, or the docs generator's built-in search log → tier 2.
- Zendesk, Intercom, Freshdesk exports and the GitHub/GitLab issue APIs → tier 3.
- Sentry, Datadog or the product's own metrics backend → tier 4.
- Stack Exchange API, Discourse search, Reddit and Discord archives → tier 5.
- npm, PyPI, crates.io, Packagist, Maven Central, Docker Hub statistics and the GitHub dependents graph → tier 6.
- Ahrefs, Semrush, Moz, Google Keyword Planner → tier 8.

Record which of these you actually used, with the export date, in the map's Scope section. A reader cannot judge a keyword map without knowing which tiers were reachable.
references/keyword-map-example.md›
# Keyword map: worked example and counter-example

The example below is illustrative, built around a fictional managed job-queue product ("Quebase") with Node.js, Python and Go clients. Numbers are placeholders for the shape of the evidence, not benchmarks to reuse.

## Contents

- Positive example
- Counter-example that fails the threshold
- Why the difference matters
- Error normalization: right and wrong

## Positive example

```markdown
# Keyword map: Quebase - Q3 docs and content targets

## Scope

Axes: languages Node.js / Python / Go; runtimes v3 (current) and v2 (deprecated 2026-12);
deployment self-hosted and cloud. Adoption mode: both, with a separate company-adoption set.
Owned surfaces: docs site, engineering blog, repository, changelog.
Sources used: search-console export 2026-08-20 (16 months); docs search log 2026-08-20 (90 days);
support export 2026-08-19; issue tracker; error telemetry.
Sources unavailable: community Discord archive (no export access) - tier 5 marked partial.

## Ranked clusters

Sorted by outcome per page-hour, highest first. The two error entries lead because the fix is already written in the tickets; the migration guide sits at P2 despite being the largest single outcome on the map, because it costs a week of borrowed engineering time (that demotion is the ranking working, and it reverses the quarter a rival ships a breaking major).

| Cluster                     | Primary query                         | Variants                                                     | Family      | Evidence                                                                   | Incumbent                                    | Target surface                   | Existing URL or new         | Priority |
| --------------------------- | ------------------------------------- | ------------------------------------------------------------ | ----------- | -------------------------------------------------------------------------- | -------------------------------------------- | -------------------------------- | --------------------------- | -------- |
| Job stuck in `pending`      | quebase job stuck pending             | "jobs not processing", "worker idle pending"                 | Error       | 214 docs searches / 90d (2026-08-20); 38 tickets; 1.9k error events        | none (no page exists)                        | Error entry                      | new                         | P1       |
| Visibility-timeout exceeded | `VisibilityTimeoutExceeded`           | "error 409 visibility", "job retried twice"                  | Error       | 6.1k error events / 30d; 22 tickets; 0 impressions                         | forum thread from 2024, answer predates v3   | Error entry                      | new                         | P1       |
| Retry with backoff in Node  | how to retry failed jobs quebase node | "exponential backoff quebase", "retry policy nodejs"         | Task        | 340 impressions, avg position 12 (striking distance); 11 community threads | our own blog post, v2 syntax                 | How-to guide                     | update /docs/guides/retries | P1       |
| Quebase with Django         | quebase django integration            | "celery to quebase django", "django background jobs quebase" | Integration | 190 impressions, avg position 17; 14 tickets                               | competitor's tutorial                        | Integration guide                | new                         | P2       |
| v2 to v3 upgrade            | upgrade quebase v2 to v3              | "quebase v3 breaking changes"                                | Migration   | 96 docs searches / 90d; 27 issues                                          | our changelog only                           | Migration guide (review 2026-12) | new                         | P2       |
| Idempotency semantics       | are quebase jobs exactly once         | "at least once vs exactly once queue"                        | Concept     | 58 impressions, avg position 24; 9 tickets                                 | encyclopedic article, no product consequence | Explanation page                 | new                         | P3       |

## Company-adoption set

| Cluster                    | Primary query          | Family     | Evidence                                            | Incumbent                | Target surface    | Priority |
| -------------------------- | ---------------------- | ---------- | --------------------------------------------------- | ------------------------ | ----------------- | -------- |
| Throughput and rate limits | quebase rate limits    | Evaluation | 12 sales questions logged / quarter; 41 impressions | pricing page, no numbers | Limits page       | P1       |
| Self-hosting requirements  | self-host quebase      | Evaluation | 9 sales questions; 63 impressions, avg position 9   | our README, incomplete   | Trust/limits page | P1       |
| Quebase vs Sidekiq Pro     | quebase vs sidekiq pro | Comparison | 5 sales questions; 88 impressions, avg position 19  | competitor's own page    | Comparison page   | P2       |

## Fix first

- `/docs/guides/retries` and `/blog/retry-patterns` both rank for "quebase retry policy" - canonicalize on the guide, link from the post.
- v2 and v3 copies of the concurrency page are near-duplicates competing for the same phrasing.

## Do not target

- "quebase pricing calculator" - no such feature; cannot answer honestly.
- `ECONNRESET during shutdown` - 4.2k events, but the fix is a graceful-shutdown change shipping in v3.4; routed to engineering, not to docs.
- "python asyncio tutorial" - owned by the language's own documentation.

## Backlog (parked 2026-08-26)

- Kubernetes operator install path - evidence exists (31 docs searches) but no supported operator yet.
- Go generics migration - 12 issues, below the P3 evidence floor this quarter.
```

## Counter-example that fails the threshold

```markdown
| Keyword                   | Volume | Difficulty | Priority |
| ------------------------- | ------ | ---------- | -------- |
| job queue                 | 14,000 | 71         | P1       |
| background jobs           | 8,100  | 64         | P1       |
| message queue software    | 3,600  | 58         | P2       |
| quebase job stuck pending | 0      | 3          | drop     |
| VisibilityTimeoutExceeded | 0      | -          | drop     |
```

Four defects, one per line of the threshold:

- Evidence is a single tool's estimate, unattributed and undated, so nothing can be audited.
- Zero first-party evidence, so the list would look identical for any competitor.
- Ranked on volume alone, which is exactly why the two highest-frequency real failures were dropped.
- No target surface, no cannibalization pass, no refusals (nothing tells a writer which page to open).

The head terms it promotes are category words that a job-queue startup cannot win and that no developer types while blocked.

## Why the difference matters

The first map is auditable: a reviewer can re-pull each count and check the date. It also routes to docs, not only to the blog, which is where most technical demand belongs. The second map is portable to any product in the category (which is the clearest sign no research happened).

## Error normalization: right and wrong

Six raw ticket subjects from the same week:

```
connection to postgres://prod-db-7:5432 refused after 30s
connection to postgres://staging-db:5432 refused after 30s
Error: connect ECONNREFUSED 10.4.2.19:5432
worker cannot reach database, connect ECONNREFUSED
db connection refused in production
psycopg2.OperationalError: connection refused
```

**Wrong - one target per phrasing:**

| Keyword                                      | Evidence |
| -------------------------------------------- | -------- |
| connection to postgres prod-db-7 refused     | 1 ticket |
| connection to postgres staging-db refused    | 1 ticket |
| ECONNREFUSED 10.4.2.19                       | 1 ticket |
| worker cannot reach database                 | 1 ticket |
| db connection refused in production          | 1 ticket |
| psycopg2 OperationalError connection refused | 1 ticket |

Six thin pages compete with each other, each carrying evidence too weak to rank anything, and two of the "keywords" contain a host and an IP nobody else will ever search.

**Right - one cluster per cause:**

| Cluster                     | Primary query                             | Variants                                                                                                       | Evidence                                                                 |
| --------------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| Database connection refused | `ECONNREFUSED` connecting to the database | "connection refused after 30s", "psycopg2 OperationalError connection refused", "worker cannot reach database" | 6 tickets / 7d (2026-08-20, Measured); 812 error events / 30d (Measured) |

One page, evidence strong enough to rank, and the variants column keeps the exact strings people paste. Normalization did the work:

- Hosts, IPs, ports and timeouts became placeholders.
- The client-library wrapper text was trimmed to the innermost cause.
- The two most common raw phrasings were kept as variants.

Two checks before you accept a merge like this:

- Confirm the cause really is one (the same message from a firewall rule and from a wrong password are two pages, however identical the string).
- Confirm the cluster is a docs gap rather than a regression: six tickets in one week, all after a release, is a signal for engineering first.
references/published-findings.md›
# Published findings and self-set baselines

Every number and named convention this skill quotes, with its source. Read this before repeating a figure to a user, and quote its source with the figure.

## Contents

- Sourced figures
- Reused conventions, not standards
- Baselines this skill sets itself
- Keeping the figures current

## Sourced figures

| Claim                                                                         | Figure                                                                                                                                                | Source                                                                                             |
| ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| Most keywords sit below any usable volume                                     | ~93% of the U.S. keyword database has fewer than 10 monthly searches (2.3 billion terms, against fewer than 18,000 above 100k)                        | ahrefs.com/blog/long-tail-keywords                                                                 |
| Conversational phrasings are mostly unmeasured                                | over 95% have no measurable volume                                                                                                                    | same page                                                                                          |
| Reported volume merges distinct queries                                       | Keyword Planner averages the last 12 months, reports a keyword _and its close variants_, and rounds                                                   | support.google.com/google-ads/answer/3022575                                                       |
| A query export is truncated by default                                        | Search Analytics `rowLimit` accepts 1–25,000, defaults to 1,000                                                                                       | developers.google.com/webmaster-tools/v1/searchanalytics/query                                     |
| Position cannot be filtered server-side                                       | position is a returned metric; the filterable dimensions are country, device, page, query, searchAppearance, date, hour                               | same reference                                                                                     |
| Not every row comes back                                                      | "does not guarantee to return all data rows but rather top ones"                                                                                      | same reference                                                                                     |
| Totals do not reconcile                                                       | chart and table totals differ through property-vs-page aggregation                                                                                    | support.google.com/webmasters/answer/7576553                                                       |
| Assistant use is near-universal among developers                              | 84% of respondents use or plan to use AI tools                                                                                                        | Stack Overflow Developer Survey 2025, AI section (49,000+ respondents)                             |
| Wrong-but-plausible answers are the top complaint                             | 66% name "AI solutions that are almost right, but not quite"                                                                                          | same survey                                                                                        |
| Distrusted answers send developers back to humans                             | 75.3%                                                                                                                                                 | same survey                                                                                        |
| Trust in AI answers is falling even as usage rises                            | 29% of 2025 respondents trust AI, down from roughly 40% in 2023                                                                                       | Stack Overflow Blog, "Mind the gap: Closing the AI trust gap for developers," published 2026-02-18 |
| Assistants are displacing the search step itself, not just search results     | "Searching for development-related information on the internet" ranks second among tasks developers delegate to AI, after writing boilerplate code    | JetBrains State of Developer Ecosystem 2025 survey (24,534 respondents, fielded April-June 2025)   |
| A page still earns its keep without the click                                 | "Fewer visits, but the same level of influence for the website… all of them rely on the information that they crawl from your website" - Rand Fishkin | sparktoro.com, "Does Your Website Still Matter in the Zero-Click Era?", published 2026-08-14       |
| Capturing knowledge during the fix, then restructuring it for the next reader | the solve loop and evolve loop, with the issue/environment/cause/resolution article shape                                                             | Knowledge-Centered Service, Consortium for Service Innovation                                      |
| Markup no longer buys SERP real estate for troubleshooting pages              | FAQ rich results stopped appearing on 7 May 2026; documentation removed June 2026                                                                     | Google Search Central FAQ structured-data page                                                     |

Two claims in this skill are documented behaviour without a figure attached:

- Search Console withholds rare queries for privacy.
- Query-level rows must not be summed into site totals.

State them as behaviour, never as a quantified loss.

## Reused conventions, not standards

These are widely reused keyword-research conventions. They are usable defaults rather than measured findings - attribute them that way.

- **Shared-top-10 merge thresholds** (7-10 one page, 4-6 one cluster, 2-3 cross-link, 0-1 unrelated): a widely reused convention, not a measured cut-off.
- **Striking distance = average position 5-20**, filtered client-side because the API sorts by clicks: a widely reused convention for identifying queries in striking distance below ranking. The window is not a measured cut-off.
- **Measured / User-provided / Estimated labelling**, with `N/A` for anything unavailable: a classification system applied across research workflows to distinguish evidence quality.
- **Weighted difficulty scores** (top-10 authority 25%, page authority 20%, content bar 20%, backlinks 20%, stability 15%): a link-graph model. Listed here so you recognise it, not to adopt it - four of its five inputs model a link graph, and technical results are usually held by a stale community answer that loses to completeness rather than to authority.
- **Golden-set query diagnosis** (roughly 50 representative queries drawn from popular and zero-result searches, classified by intent, retested after every fix): a search-quality diagnostic practice documented in Algolia's own engineering guidance for content-heavy sites, not a universal standard for every docs search tool.

## Baselines this skill sets itself

Say "our baseline" when you report these, and adjust them to the product's data situation rather than defending them as standards:

- **70% of ranked clusters must draw top evidence from tiers 1-4.** Raise it for a product with rich first-party data; lower it explicitly, in the Scope section, for a pre-launch product that has almost none.
- **The eight-tier ordering of demand sources.** The tiers reflect how directly each source observes a developer asking, and the ordering is a judgement rather than a measured ranking.
- **"A question asked three times is a missing page; asked thirty times it is a docs or product defect."** A working rule of thumb from support-signal practice rather than a measured threshold.
- **Rank order: evidence tier, then business proximity, then winnability, then maintenance cost.** A judgement about what matters for a devtool, not a scoring formula to defend.

## Keeping the figures current

Re-read a row against its source before quoting it in a map: survey waves, help-page wording and API limits all move. If the source has changed, update the row and the number in `SKILL.md` together; a figure that no longer matches its source is worse than no figure, because it looks verified.
references/query-pattern-library.md›
# Query pattern library

Templates, modifiers and expansion recipes for generating technical keyword candidates. Every generated candidate is still a candidate - it enters the map only once it carries demand evidence.

## Contents

- Modifier vocabulary
- Family templates
- Expansion recipes
- Error-string normalization
- Adoption-mode split
- Expansion traps

## Modifier vocabulary

A real developer query is a family template plus one or more modifiers. Use only the values that are true for the product.

| Modifier class    | Example values                                                                     |
| ----------------- | ---------------------------------------------------------------------------------- |
| Language          | the languages you ship a client or example for                                     |
| Framework         | the web/app frameworks your users actually run                                     |
| Runtime / version | major versions still supported, plus the one being deprecated                      |
| Platform          | operating system, container, serverless, managed platform                          |
| Deployment mode   | local, self-hosted, cloud, air-gapped                                              |
| Stage             | development, staging, production, CI                                               |
| Qualifier         | `example`, `not working`, `slow`, `without <dependency>`, `best practice`, `limit` |

## Family templates

**Error string.** The message verbatim, the numeric or symbolic code, a distinctive stack frame, and the message with the variable part stripped. Developers paste; they do not paraphrase.

**Task how-to.** `how to <task> in <language>`, `<product> <task> <framework>`, `<task> with <product> example`, `<product> <task> without <dependency>`.

**Integration intent.** `<product> <other product> integration`, `connect <product> to <other product>`, `<product> with <other product>`, `send <data> from <product> to <other product>`. The expansion set is your integration catalogue, not a generic vendor list.

**Comparison.** `<product> vs <competitor>`, `<competitor> alternative`, `alternatives to <competitor>`, `<product> or <competitor> for <use case>`, `<open-source thing> vs <hosted thing>`.

**Concept.** `what is <concept>`, `<concept> explained`, `why <constraint>`, `<concept> vs <adjacent concept>`.

**Reference lookup.** `<library> <symbol>`, `<api> <endpoint> example`, `<config key> default`, `<cli command> flags`.

**Migration.** `migrate from <competitor> to <product>`, `upgrade to <version>`, `<feature> deprecated`, `<old api> replacement`.

**Evaluation.** `<product> rate limits`, `<product> pricing`, `self-host <product>`, `<product> SOC 2`, `<product> data residency`, `<product> SLA`.

## Expansion recipes

1. **Error catalogue × supported versions.** Start from the product's own error definitions, not from tickets (this covers failures nobody reported yet). Cross with versions only where the message text actually changed.
2. **Task inventory × language × framework.** Build the task inventory from what the quickstart and how-to sections already promise, plus the tasks support keeps explaining. Cross with the top two or three language/framework combinations, not all of them; a cell with no shipped example cannot be answered credibly.
3. **Integration catalogue × direction.** Each integration produces at least two intents (pulling data in and pushing data out) and they are different pages when the setup differs.
4. **Competitor set × use case.** Only competitors a real evaluator names. Crossing with a use case is what makes a comparison page defensible instead of generic.
5. **Public API surface × "example".** Reference lookups are mostly owned by upstream docs; the winnable cell is the one where the official page shows a signature but no working example.

## Error-string normalization

Cluster before counting, or one cause becomes ten targets.

1. Replace absolute paths, UUIDs, hashes, timestamps, port numbers, quoted identifiers and stack line numbers with placeholders.
2. Trim the framework's wrapper text and keep the innermost cause.
3. Group by the normalized string; keep the two or three most frequent raw phrasings as variants, because those are what people paste.
4. Count per cluster and per time window. A cluster that appeared after the last release is a regression signal to route to engineering, not a docs gap.
5. Rank surviving clusters by frequency × time-to-resolve × whether a reader can self-serve the fix. A frequent error only support can fix belongs in the product, not in a page.

## Adoption-mode split

|                      | Individual adoption                  | Company adoption                           |
| -------------------- | ------------------------------------ | ------------------------------------------ |
| Dominant families    | error, task, concept, reference      | evaluation, comparison, migration          |
| Wording              | first person, mid-task, error-pasted | formal, checklist-shaped, risk-shaped      |
| Searcher             | the user, not the buyer              | a champion assembling an internal case     |
| Observed volume      | high                                 | very low                                   |
| Value per query      | low                                  | high                                       |
| Best evidence source | docs search, issues, community       | sales and solution-engineer question lists |

Give each mode its own target count. A single ranked list always resolves in favour of the individual set.

## Expansion traps

- Generating cells for technologies you do not officially support: the page cannot be kept working, and a broken example costs more credibility than the traffic is worth.
- Treating every phrasing variant as a separate target instead of a variant column on one cluster.
- Letting a version number into a keyword without a review date attached.
- Expanding comparison queries against competitors nobody evaluates you against, which produces pages that only advertise the competitor.
- Assuming one intent per phrasing. `<product> timeout` is an error for one reader and a configuration lookup for another; check what the current results answer before routing it.
SKILL.md›
---
name: developer-keyword-research
description: Builds a prioritized keyword list for technical search queries (error strings, "how to X in Y" tasks, integration intents, comparisons, migrations) mined from docs search logs, support tickets, issue trackers and first-party query data rather than keyword-tool volume. Use whenever the user asks what developers actually search for, wants keywords for a developer tool, API, SDK or docs site, an error-message keyword list, demand sizing for a technical topic, or which docs pages to create from search demand - even if they never say "keyword". Does not write the pages. Do NOT use for on-page or docs-site SEO - use samber/developer-relations-skills@docs-seo.
license: MIT
metadata:
  author: Samuel Berthe
  version: "1.0.1"
---

# Developer Keyword Research

You are a technical search-demand analyst for a developer-facing product. You turn scattered evidence of what developers ask into a ranked, evidence-backed keyword map that other people can write against.

Technical queries break the standard keyword workflow. Ahrefs puts roughly 93% of its U.S. keyword database below ten monthly searches, and Google's Keyword Planner reports one figure for a keyword _and its close variants_, averaged over twelve months and rounded.

Almost every exact error string and version-specific phrasing lands in that band, so a volume-ranked list sorts the entire technical long tail to the bottom. Rank on observed evidence instead, and use tool volume only as a tiebreaker.

Every figure this skill quotes is traceable: [./references/published-findings.md](./references/published-findings.md) gives each one's source, and lists separately the few baselines this skill sets itself. Quote a number from there rather than inventing one, and never present a self-set baseline as an industry standard.

## Interview

Ask these one at a time, multiple-choice where possible, and stop as soon as you can act. Skip any question the conversation already answered.

**Memory: Store a few info about the current task or project context. When memory lives in a file, use `devrel-context.md`; if a different memory system is in use, rely on that instead. Separate task info in different sections. Remove finished tasks. Add a date to a task; no date for general project context. Some interview responses may differ between 2 tasks.**
1. What is the product, and what does a developer do with it in the first hour?
2. Which surfaces do you own that can rank - docs site, blog, repository, changelog, community forum?
3. Which of these can you actually pull data from: first-party search-query report, docs-site search logs, support tickets, issue tracker, community archive, product error telemetry?
4. Are you chasing individual adoption (a developer solves a problem alone) or company adoption (a champion builds an internal case), or both?
5. Which languages, frameworks, runtimes and deployment modes do you officially support? These are the expansion axes.
6. Who currently answers these queries - the technology's own docs, a public Q&A site, a competitor, nobody?
7. How many pages per quarter can the team actually ship _and keep current_? That ceiling is the cut line in step 9, and a low one deletes the standing-maintenance families (integration and migration guides) from this map rather than parking them.
8. By what date must the first pages be live, and is a launch, SDK or migration already committed to that date? A date inside the month promotes the error and reference families, which ship in hours; a planning cycle further out opens migration and comparison, which need engineering and legal time.
9. One-off win or a compounding asset? A one-off promotes error entries and reference examples, which answer a query and then sit still; a compounding mandate promotes the how-to set and the company-adoption families, worth little in week one and most of the map's value in year two.

If a data source is unavailable, say so in the output and downgrade that evidence tier rather than substituting a guess.

## Workflow

1. **Frame the scope.** Write down the product's surface list, its supported-technology axes, and the adoption mode(s) from the interview. Everything downstream expands from these; an axis you cannot support honestly must not enter the matrix.
2. **Screen what you already own.** For each candidate topic, check which of your own surfaces already earns impressions for it. Treat an existing page that ranks as an update target, not a new page. Treat two of your pages splitting one intent as a cannibalization defect to fix before adding a third.
3. **Harvest first-party demand, in pull order.** Work the sources in the efficiency order under "Demand evidence" - evidence per hour of pulling, which is not the same as the strength order the tiers are numbered by. Pull the strongest sources available:
   - Query reports (isolate average position 5-20 - the striking-distance set below).
   - Docs-site searches with zero results or a wrong top click.
   - Support-ticket subjects.
   - Issues labelled question/support.
   - Repeated community threads.
   - Product error-code counters.

   Capture each item while the resolver's own wording is in front of you, as Knowledge-Centered Service's solve loop prescribes - a question reconstructed a week later loses the exact string that made it searchable.

4. **Normalize and cluster error strings.** Replace paths, UUIDs, numbers and quoted values with placeholders, then group. One cause is one cluster, however many phrasings it has. Ten tickets, one cluster, one page. When two survivors might still be one page or two, settle it on result overlap, not wording - run the merge test below.
5. **Expand by pattern.** Cross the query families with your real supported axes to generate candidate phrasings. Generate freely; the evidence gate comes next. See [./references/query-pattern-library.md](./references/query-pattern-library.md) for the families, modifier vocabulary and expansion recipes.
6. **Attach evidence to every candidate.** Give each keyword at least one evidence item - source, count, date - and tag every metric:
   - **Measured** - from an export, a log or an API.
   - **User-provided** - the user stated it.
   - **Estimated** - you inferred it.

   Write `N/A` for an unavailable metric and name the source you could not reach. A candidate with no evidence goes to the parking list, not the map.

   Never let an estimate travel as a measurement: a reviewer who cannot tell them apart has to redo the whole pull to trust any line of the map. See [./references/demand-evidence-sources.md](./references/demand-evidence-sources.md) for what each source measures and how it is biased.

7. **Judge winnability per cluster.** Name who holds the result today and what specifically beats them. Refuse the three unwinnable categories below instead of scoring them low.
8. **Route each cluster to one surface** - one canonical target per cluster, other surfaces link to it:
   - Error entry.
   - How-to.
   - Tutorial.
   - Reference example.
   - Comparison page.
   - Migration guide.
   - Integration guide.
   - Trust/limits page.
9. **Score, rank, and cut.** Rank by outcome per page-hour: evidence tier, business proximity and winnability multiplied together as the value, divided by what the page costs to write and to keep current. Lead with the highest ratio, never with the cheapest cluster (cheapest-first is a different order and answers a different question). Break a tie on evidence tier, then on business proximity.

   That formula is this skill's own default, not a law: it shifts with context and with who writes the pages, so re-rank it against what you already know about this team (an in-house expert on one framework, a support lead who will hand you normalized tickets, a comparison page legal already cleared).

   Cut the list to what the team's stated shipping capacity covers in two quarters, put the rest in a dated backlog, and delete outright - with the constraint named - anything the stated constraints rule out, since a cluster parked at the bottom returns next quarter as unbudgeted scope.

10. **Ship the keyword map** in the output shape below, then run the pass threshold. Iterate until every check passes.

If your harness has persistent memory, store the scope frame (axes, adoption mode, owned surfaces), the do-not-target list and the reasons behind it. The next run then re-scores instead of re-litigating.

## Pulling the striking-distance set

Queries already at average position 5-20 carry proven demand and an unwon ranking - work them before any net-new discovery. The 5-20 window is a convention shared by shipped SEO skills, not a measured cut-off; widen or narrow it against your own data. If you can query a search console, extract the set in one pass:

1. Request the query dimension over the longest window available, with the row limit raised to its maximum (these exports truncate by default). The documented case is Google's Search Analytics API: it accepts 1 to 25,000 rows, returns 1,000 unless asked for more, and states it "does not guarantee to return all data rows" (API reference). Expect an equivalent cap from any other console.
2. Cut to the 5-20 average-position window client-side. Position comes back as a metric, never as a filter, and the report sorts by clicks - the window cannot be requested, only cut after a wide pull.
3. Join each surviving query to its landing page. The join exposes two of your own pages splitting one query.

If you cannot reach a console, ask the user for a CSV export of the same report and state in the output which window it covers. Treat the visible tail as a floor (rare queries are withheld for privacy) and never sum query rows into site totals.

## The merge test

Two candidate keywords belong on one page when the same results already answer both. Judge on results, not wording: two phrasings that read alike often draw disjoint result sets, and two that read differently often draw the same one. Where you can inspect live results, count how many URLs the two top-ten organic result sets share:

| Shared results | Decision                                   |
| -------------- | ------------------------------------------ |
| 7-10           | one page, other phrasing becomes a variant |
| 4-6            | one cluster, separate pages                |
| 2-3            | separate clusters, cross-link them         |
| 0-1            | unrelated, do not group                    |

Attribute these cut points as what they are: a convention published by shipped clustering skills, not a measured standard.

Comparing every pair is quadratic. Pre-group by family first, and test only the pairs that sit on a boundary.

## Query families

Full templates, modifiers and expansion recipes live in the reference file. The families, ordered by outcome per page-hour (the order to expand and to ship in, not the order they are easiest to generate):

| Family             | Example shape                               | Target surface              | Cost to write and keep current                                | What the page buys                                                                   |
| ------------------ | ------------------------------------------- | --------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| Error string       | verbatim message or code                    | error entry                 | an hour; the fix already exists in the ticket                 | catches a developer mid-failure, the highest-intent moment on the map                |
| Reference lookup   | `<library> <symbol> example`                | reference page with example | an hour; transcription from the API surface                   | closes the gap upstream docs leave when they never show an example                   |
| Task how-to        | `how to <task> in <framework>`              | how-to guide                | a day, plus a re-test per framework release                   | the compounding middle of the map: one page serves every future arrival on that task |
| Evaluation         | `X rate limits`, `self-host X`, `X pricing` | trust/limits page           | an hour to write, then a sign-off                             | unblocks a champion's internal case; the cheapest page that touches a purchase       |
| Integration intent | `<product> with <other product>`            | integration guide           | a day, then a standing job (the other product keeps changing) | reaches developers already committed to a stack you plug into                        |
| Migration          | `migrate from X to Y`, `X deprecated`       | migration guide             | a week, most of it engineering time you have to borrow        | wins a switch outright, the largest single outcome available here                    |
| Comparison         | `X vs Y`, `X alternative`                   | comparison page             | a day plus a review of every claim about a rival              | meets an evaluator at the decision, and ages the moment the rival ships              |
| Concept            | `what is X`, `why X`                        | explanation page            | a day                                                         | weakest rung: a one-line answer an assistant gives without sending anyone            |

- efficiency (outcome per page-hour - the default expansion order): error string > reference lookup > task how-to > evaluation > integration intent > migration > comparison > concept
- effort (heaviest first): migration > comparison > integration intent > task how-to > concept > evaluation == reference lookup > error string
- value (largest outcome first): migration > comparison > integration intent > error string > task how-to > evaluation > reference lookup > concept
- compliance cost (heaviest review first): comparison > evaluation > migration > error string == reference lookup == task how-to == integration intent == concept

Two ties, both real. Evaluation and reference pages are equal in effort because neither is written so much as transcribed (one from the platform's own limits, one from the API surface). The five-way compliance tie is every family that makes no claim about anyone else's product and quotes no customer: there is nothing for anyone to review.

Individual adoption dominates the error, how-to, concept and reference families; company adoption dominates evaluation, comparison, migration and integration intent, typed by a champion checking stack fit rather than the eventual signer.

What this efficiency order starves is exactly that company-adoption set: migration and comparison sit highest on value and highest on effort, so a ratio ranking demotes them every quarter and the map serves traffic without ever reaching a purchase decision. The standing counter-measure is the map's separate company-adoption target count - fill it regardless of where the ratio put those rows. Promote migration to the top outright when a rival ships a deprecation or a breaking major, which is the one window where a switch is cheap to win and closes within a quarter.

Delete rather than demote. No shipping capacity to keep an integration guide current deletes the integration family from this map - the page rots into a wrong answer, which costs more than the page ever earned. No sign-off available for claims about a competitor deletes the comparison family, and the demand is served with a migration guide about your own product instead.

## Demand evidence

The tiers are numbered by evidence strength - how directly each source observes a developer asking. That is the value axis, and it is not the order to pull them in, because they differ by a factor of a hundred in what they cost to extract.

1. First-party query data, especially position 5-20 - an hour with console access, near-zero if the user already exported it.
2. Docs-site search logs, especially zero-result queries - an hour where the logging exists, a week of engineering where it does not.
3. Support tickets and issue-tracker questions - a day of reading and normalizing, and the richest source per hour in any product old enough to have a queue.
4. Product error telemetry - a week, most of it borrowed from whoever owns the store. The only source that sees the failures nobody bothered to report.
5. Public Q&A and forum question counts - an hour; other people's audience, so phrasing and frequency but no ownership.
6. Ecosystem counters (registry downloads, dependents) - near-zero; sizes the population, not the query.
7. Autocomplete and related-search harvests - near-zero; shows phrasing, not frequency.
8. Keyword-tool volume and difficulty - near-zero; tiebreaker only.

- efficiency (evidence per hour of pulling - the default harvest order): tier 1 > tier 2 > tier 3 > tier 5 > tier 7 > tier 8 > tier 6 > tier 4
- effort (heaviest first): tier 4 > tier 3 > tier 2 > tier 1 == tier 5 > tier 6 == tier 7 == tier 8
- value: the tier numbering above, strongest first
- compliance cost (heaviest review first): tier 3 > tier 4 > tier 2 > tier 5 > tier 1 == tier 6 == tier 7 == tier 8

Two ties worth defending. Tiers 1 and 5 are each one export against a documented interface, capped by the same row limits and readable the same afternoon. The four-way compliance tie is every source carrying no customer content: a query string, a download count, an autocomplete suggestion and a tool's index name that nobody owns and that quotes no one.

What this harvest order starves is product error telemetry: strongest evidence on the list, and last on efficiency because it needs a week of an engineer nobody has spare. Promote it above everything when the map is error-family-heavy, or when the product is too young for a support queue to have produced volume. In both cases the cheap tiers observe an audience that does not exist yet, and telemetry is the only source that sees the silent majority who hit a failure and never filed anything.

Delete rather than demote. No access to a source at all deletes that tier from this run - record it as unavailable in the Scope section and lower the 70% floor explicitly, rather than leaving it on the list where the next reader reads its absence as an oversight. Customer content you cannot scrub or quote deletes tier 3 the same way.

This ordering is this skill's own baseline, and both orders shift with context and with who does the pulling. Re-rank against what you already know about this team (a support lead who already normalizes ticket subjects moves tier 3 to the front; a product with an existing telemetry dashboard has paid tier 4's cost already).

Two rules keep the map honest:

- Never drop a keyword because a tool reports zero volume: tools average over months, merge close variants and round, so zero is a statement about the tool's index.
- Never invent a number: write `N/A` when a source returns nothing, and mark a tier unavailable when you could not access it at all.

## Winnability

For each cluster, name the incumbent result and the specific weakness you can beat:

- A stale answer.
- A version that no longer exists.
- A listing with no working configuration.
- A reference page that never shows an example.

Completeness wins technical queries:

- Exact string reproduced verbatim.
- Version stated.
- Runnable fix.
- Failure conditions covered.

Refuse three categories outright, and list the refusals in the output:

- Queries your product cannot honestly answer.
- Queries whose real fix is a product change or a better error message, not a page.
- Reference lookups owned by the technology's own documentation.

Assistants changed what a click forecast is worth: a page can now be cited without being clicked. Score a cluster on whether your page would be the source an answer is assembled from, not on projected traffic, and forecast the return in citations and arrivals rather than clicks alone.

The simple end of the list loses clicks first. Stack Overflow's 2025 Developer Survey (49,000+ respondents) has 84% of respondents using or planning to use AI tools, and 66% naming "AI solutions that are almost right, but not quite" as their top frustration; trust in those answers is falling even as usage rises, from roughly 40% in 2023 to 29% in 2025 (so one-line answers lose their clicks while verification, edge cases and exact version behaviour hold their value). JetBrains' State of Developer Ecosystem 2025 survey (24,534 respondents, fielded April-June 2025) ranks "searching for development-related information on the internet" as the second most common task developers delegate to AI tools, after writing boilerplate code - assistants are displacing the search step itself, not just its results.

Discount clusters whose entire value is a one-line answer, but keep them on the map: the assistant still assembles its answer from what you published. "Fewer visits, but the same level of influence," as Rand Fishkin puts it (SparkToro, published 2026-08-14).

## Invocation examples

Whatever the phrasing, produce the same artifact: one Markdown keyword map. When the request is narrower than a full map, cut sections - never invent a different shape.

- "Build a keyword list for our Postgres connection-pooler docs" - full run, both adoption modes, interview first.
- "Here's our search-console export and 90 days of docs-search logs, find what we should write next" - data already supplied, so skip the source questions and go straight to harvesting and clustering.
- "Turn last quarter's support tickets into troubleshooting page targets" - error family only; normalize, cluster, rank, and route every cluster to an error entry.
- "We're launching a Rust SDK next month, what should ship with it?" - expansion bounded by one axis, and the map serves a committed launch rather than a quarter's capacity.
- "Which of our existing pages should we update instead of writing new ones?" - screening pass only (step 2 plus the striking-distance set), output limited to the Fix first and Ranked clusters sections.

## Output

Produce a Markdown report with these sections:

```
# Keyword map: <product / scope>

## Scope
Axes, adoption mode(s), owned surfaces, sources available and unavailable.

## Ranked clusters
Sorted by outcome per page-hour, highest first - state that sort above the table,
with the high-value clusters the ratio demoted and what would promote each.
| Cluster | Primary query | Variants | Family | Evidence (source, count, date) | Incumbent | Target surface | Existing URL or "new" | Priority |

## Company-adoption set
Same table, kept separate so neither volume nor the efficiency ratio can bury it.

## Fix first
Cannibalization and update-instead-of-create cases found in step 2.

## Do not target
Cluster, reason (cannot answer / product fix / owned by upstream docs).

## Backlog
Candidates with evidence but beyond this quarter's capacity, with the date they were parked.
```

A worked example, plus a negative example of a map that fails the threshold, is in [./references/keyword-map-example.md](./references/keyword-map-example.md).

## Pass threshold

The map is finished when all six hold. Iterate until they do, and state the result explicitly.

1. Every ranked keyword carries at least one evidence item with a source, a count, a date and a Measured/User-provided/Estimated label.
2. At least 70% of ranked clusters draw their top evidence from tiers 1-4 (first-party). Below that, the map is guesswork dressed as research - go back to step 3.
3. No cluster is ranked on keyword-tool volume alone.
4. Every cluster has exactly one canonical target surface, and no two clusters target the same page with different intents.
5. The do-not-target list is non-empty. A round of research that refused nothing did not apply the winnability filter.
6. The ranked table states its sort, names the high-value clusters the ratio demoted with the condition that promotes each, and the company-adoption set has its own filled target count rather than inheriting the ratio's order.

The 70% floor is this skill's own baseline, not a published benchmark. Report it as such, and raise it when the product has rich first-party data or lower it (explicitly, in the Scope section) when a pre-launch product has almost none.

## Measuring the list afterwards

Track coverage, movement and arrival:

- Coverage - share of ranked clusters with a live target.
- Movement - striking-distance queries that crossed into the top results.
- Arrival - first-party query counts for the exact strings after each page ships.

Do not report published-page count as the outcome - it measures the writing team, not the research.

## Failure modes

- **Matrix explosion.** Crossing every template with every modifier yields thousands of cells nobody searches. Gate every cell on evidence; an ungated matrix is a doorway-page generator.
- **One page per ticket.** Skipping error normalization multiplies one cause into ten thin pages that compete with each other.
- **Version rot.** A keyword tied to a version silently expires. Stamp version-bound clusters with a review date.
- **Intent collision.** The same phrasing can be an error or a configuration lookup. Check what the current results actually answer before assigning a surface.
- **Blog as dumping ground.** Routing a docs-shaped query to the blog buries the answer where nobody in the product will link it.
- **Sales-visible questions missing.** Limits, permissions, compliance and migration questions rarely reach public channels; ask the people who answer them.

## References

- [./references/query-pattern-library.md](./references/query-pattern-library.md) - query families, modifier vocabulary, expansion recipes, error-string normalization.
- [./references/demand-evidence-sources.md](./references/demand-evidence-sources.md) - per-source extraction guidance, biases, and an optional tool-integration note.
- [./references/keyword-map-example.md](./references/keyword-map-example.md) - a worked keyword map and a failing counter-example.
- [./references/published-findings.md](./references/published-findings.md) - every figure this skill quotes, with its source, and the baselines it sets itself.

Hands off to, and complements:

- `samber/developer-relations-skills@developer-troubleshooting-docs` - writes the error entries this map routes to.
- `samber/developer-relations-skills@docs-seo` - on-page and technical SEO once the target pages exist.
- `samber/developer-relations-skills@developer-docs-structure-audit` - structural gaps in the docs set, independent of search demand.
- `samber/developer-relations-skills@devrel-content-calendar` - slots the resulting topics into a quarter.
- `samber/developer-relations-skills@developer-segmentation` - names the audience segments the adoption-mode split assumes.