SKILL DETAIL
version-migration-guide
samber/developer-relations-skills/version-migration-guide
Writes or audits the migration guide for a breaking change - what breaks, ordered by blast radius, with a detection signal and before/after code per entry, a deprecation timeline, the codemod's coverage boundary, and verification by a cold upgrade of a real project. Use whenever someone mentions a migration guide, an upgrade guide, a breaking-changes page, a major version bump, "how do we tell users about v3", an API version cutover, a deprecation or sunset notice, or a codemod or compat build - even if they only say they are shipping v2. Covers libraries, SDKs, CLIs, frameworks, hosted APIs and schema runbooks. Not release notes - use samber/developer-relations-skills@changelog-writing.
Installation
npx skills add https://github.com/samber/developer-relations-skills --skill version-migration-guide
スキルファイル
SKILL.md
最終同期 · 2026/10/23
evals/evals.json›
{
"skill_name": "version-migration-guide",
"evals": [
{
"id": 1,
"prompt": "I lead the platform team at Kelvinbridge. We own an internal Go library called authkit that 61 of our services depend on. Everything lives in one monorepo and every consuming service is owned by a team inside the company. authkit v4 changes 9 function signatures and drops two helpers entirely. I need to write the migration guide so the 61 service teams can get themselves onto v4. Can you draft it?",
"expected_output": "A refusal to produce a consumer-facing migration guide as the primary deliverable, on the grounds that the provider controls every consumer. Instead: a recommendation that the platform team perform the migration itself via a codemod validated across all 61 consumers, plus a short note and an ownership/freeze-date map.",
"files": [],
"expectations": [
"Names the Churn Rule explicitly and attributes it to Software Engineering at Google",
"States which side of the Churn Rule this project sits on before producing any draft",
"Recommends that the platform team move the 61 services itself rather than publishing a guide for the service teams to follow",
"Recommends a codemod plus a short note as the deliverable instead of a full prose migration guide",
"States that a guide is the right deliverable only when the consumers are genuinely other people's codebases",
"Cites the monorepo and internal ownership of all 61 consumers as the specific reason the Churn Rule applies here",
"Does not deliver a full library-shaped migration guide with per-change prose entries as its primary recommendation",
"States that announcing a change and leaving 61 consuming teams to cope hands them work that belonged to the platform team",
"Requires the codemod to be validated across the consumer corpus rather than against a single repository",
"Names the residue a codemod leaves behind (for example dynamic dispatch, re-exports, generated code or string-keyed access) as something that must be reported",
"States that any accompanying document for this internal audience must name which team owns each affected service and a tracking issue",
"States a freeze date after which the old version stops being deployable"
]
},
{
"id": 2,
"prompt": "Tarnwell Data ships tarn-sdk, a TypeScript client with about 40k weekly npm downloads. We're cutting 5.0 with 11 breaking changes. Our current published line is 4.7 and we can still ship a 4.8 before we freeze the 4.x branch. One thing to know: 4.6 has a connection-pool leak we only fixed in 4.7, and a decent chunk of users are still sitting on 4.6. 5.0 needs Node 22.11 or newer and TypeScript 5.7 or newer. Write the upgrade guide.",
"expected_output": "A guide that opens with the staged path (ship 4.8 as a warnings-only interim release, then jump), names 4.6 as a hop to skip with the reason, states both floors as machine-checkable comparisons before any code, and orders the 11 changes by blast radius with the evidence source named.",
"files": [],
"expectations": [
"Recommends the staged path: ship 4.8 as an interim release whose changes are deprecation warnings for everything 5.0 breaks",
"Justifies the staged default by the fact that a 4.8 release can still ship from the N-1 line",
"States that the reader's own build output on the interim release becomes their personal inventory of affected call sites",
"States the chosen path shape at the top of the guide, before the breaking-change entries",
"Explicitly tells readers on 4.6 to skip it and go to 4.7 or later, and gives the connection-pool leak as the reason",
"Places the Node and TypeScript floors before any code example or breaking-change entry",
"States the floors as machine-checkable comparisons such as Node >= 22.11 and TypeScript >= 5.7 rather than prose like a recent Node version",
"Orders the 11 breaking changes by blast radius rather than alphabetically or grouped by module",
"Says out loud that the ordering is by blast radius and names the evidence source behind the ranking",
"Asks whether usage telemetry on the affected APIs exists, or records its absence explicitly",
"Does not bake a moving patch version such as 5.0.3 into the guide's prose, pinning the version pair instead",
"Does not offer sequential per-minor bridges as a candidate path shape, since no version-skew policy applies here"
]
},
{
"id": 3,
"prompt": "Here's our v2 to v3 upgrade page for Halloway Queue, and support is drowning in tickets that quote it. The structure is: '## What's new in v3', then '## Why we rebuilt the core', then '## Breaking changes' with 14 entries sorted A to Z by class name where each entry is two sentences saying what changed followed by 'update your usage accordingly', then '## FAQ' which is where the actual npm install command and the config-file rename steps ended up, then '## Requirements' as the last section saying v3 needs Java 21. Tell me what to fix.",
"expected_output": "A scored audit against the six pass-threshold criteria, a prioritized defect list led by the floors-at-the-bottom problem, and rewritten text for the top three entries.",
"files": [],
"expectations": [
"Scores the page against six named pass-threshold criteria covering completeness, detectability, sufficiency, automation honesty, path validity and self-containment",
"Identifies the Requirements section carrying the Java 21 floor at the bottom of the page as a defect and moves it above the code examples",
"Ranks the floors-at-the-bottom defect as the costliest of the structural defects",
"Identifies the A-to-Z ordering of the 14 entries as a defect and prescribes reordering by blast radius",
"Identifies that the install command and config-rename steps are hidden in the FAQ and must move into the main migration flow",
"Identifies 'What's new in v3' and 'Why we rebuilt the core' as launch narrative that does not belong on a work document",
"Flags every 'update your usage accordingly' entry as missing a before/after code block",
"States that a change for which no before/after block can be written is not understood well enough to publish",
"Flags every entry as missing an affects-you-if line carrying both a search pattern and the literal error or warning text",
"Flags the absence of escape-hatch statements and requires an explicit 'there is none' where no escape hatch exists",
"Returns a prioritized fix list rather than an unordered list of problems",
"Supplies rewritten text for the top three entries rather than only describing what should change"
]
},
{
"id": 4,
"prompt": "Rossiter Labs here. We're planning v6 of our Python SDK, 23 breaking changes across the client and the auth module. Leadership has given us 3 engineer-weeks for migration tooling. The team wants to spend all of it on a libcst codemod plus a compat shim that keeps v5 behaviour alive behind a flag. After the release everyone rotates onto the streaming project and nobody is assigned to keep the tooling alive. What should we actually build?",
"expected_output": "An explicit efficiency ranking of the automation rungs, leading with search patterns and literal messages, deleting the compat shim from the plan because no owner exists after the release, and naming what the ranking starves plus the condition that would promote it anyway.",
"files": [],
"expectations": [
"Ranks the automation options by reader hours saved per engineering hour spent, and states the ranking explicitly rather than implying it by list order",
"Places search patterns and literal messages per change at the top of the efficiency ranking",
"States that search patterns and literal messages cost near-zero because they are the affects-you-if line already written for each entry",
"Recommends deleting the compat build from the plan because no owner is named after the release",
"States that an unmaintained compat build strands every project that adopted it half-migrated",
"Places the lint rule above the codemod on efficiency while acknowledging the codemod beats it on value",
"Gives the lint rule's payoff as stopping new code reintroducing the old API after the guide stops being read",
"Recommends deprecation warnings in the N-1 release as the second rung, conditional on an N-1 release still being shippable",
"Names explicitly what this efficiency ordering starves rather than leaving it implied",
"States the condition that would promote the codemod or compat build anyway, such as hundreds of call sites per consumer or consumers being large applications that cannot hold a long-lived migration branch",
"Requires any recommended codemod to be validated against a corpus of example apps, internal consumers and public dependents rather than one repository",
"Requires the codemod's coverage boundary, meaning what it does not cover, to be stated in the same place as the command itself",
"Does not endorse spending the full 3 engineer-weeks on the codemod and compat shim as the team proposed"
]
},
{
"id": 5,
"prompt": "Fenwick Ledger, Postgres 16, roughly 180 million rows in the transactions table. We're renaming transactions.amount_cents to transactions.amount_minor, and changing transactions.status from a varchar to a proper enum, which also renames two of the values. Six services read that table and we do rolling deploys. Write the migration guide the service teams will follow.",
"expected_output": "An operational runbook built on expand/contract: additive column, dual writes, throttled batched backfill, read switch, then a separate later destructive deploy. Every step carries a named rollback, and the renamed enum values are handled as a data-layer consequence for the six consumers.",
"files": [],
"expectations": [
"Produces a runbook of operational steps rather than prose with pasteable snippets",
"Names the expand/contract pattern, also called parallel change, explicitly",
"Gives deployment overlap as the reason: during a rolling deploy old and new code run at the same time, so an in-place rename leaves one of them querying a column that no longer exists",
"Adds amount_minor additively and dual-writes to both columns before any read path switches",
"Puts the drop of amount_cents in a separate, later deploy after nothing references the old shape",
"States that destructive steps ship alone while additive steps can ship in any deploy",
"Gives every step its own named rollback",
"Requires the rollback to have been executed at least once against a copy of production",
"Specifies that the 180 million row backfill runs in throttled batches off the hot path rather than as a single statement",
"Gives the reason: a single statement across that many rows locks the table",
"Calls out that the two renamed status enum values are already persisted in consumer columns and handles that as a data-layer consequence with its own step",
"Recommends building any new index without blocking writes where the engine supports it",
"Does not present the change as a single ALTER TABLE RENAME COLUMN coordinated with one deploy"
]
},
{
"id": 6,
"prompt": "Brightmoor Pay. We're cutting a new dated API version, 2026-11-02. What breaks: charge.id goes from 18 characters to 26, the status enum loses pending_review and gains in_review, and every money field moves from integer cents to a decimal string. We ship a Node SDK, a Python SDK, a browser.js bundle and an iOS SDK, and we deliver 14 webhook event types. Customers stay on their current default until we flip it for them. Write the migration guide.",
"expected_output": "A hosted-API guide: per-request pinning with a full example request, a surface matrix across all five surfaces plus webhooks, a separate webhook section, a data-layer impact section for the id length, the enum rename and the money type, an ordered cutover-and-rollback section, and the published backward-compatibility contract.",
"files": [],
"expectations": [
"Gives a per-request version pinning mechanism with a complete example request that includes the version header",
"States which mechanism wins when an account default and a per-request pin are both present",
"Tells integrators to pin explicitly in code rather than inheriting the account default, and gives the reason that the next cutover would otherwise change behaviour in an application nobody touched",
"Includes a surface matrix covering the Node SDK, Python SDK, browser bundle, iOS SDK and webhooks, stating how each versions and whether it moves with the API version",
"States that webhooks cannot be version-pinned per request because the consumer does not make the request",
"Gives webhook payload changes their own section rather than folding them into the ordinary breaking-change entries",
"Covers the window during which both the old and the new webhook payload shapes can arrive",
"Includes a data-layer impact section covering the 18-to-26 character identifier length change and its effect on stored columns",
"Covers the persisted pending_review to in_review enum rename as a data-layer consequence for values already written to consumer databases",
"Covers the integer-cents to decimal-string money change as a data-layer consequence rather than only as a wire change",
"States, in order, who flips the account default and on what date, what the customer must complete beforehand, how to roll back and for how long, and how long the old version stays available",
"Gives the rollback procedure rather than omitting it on the grounds that the cutover is expected to succeed",
"Publishes the exhaustive list of change types considered backward-compatible so integrators know which defensive coding is actually required"
]
},
{
"id": 7,
"prompt": "Oakmere. In 3.4 we deprecated four things: Session.renew(), the --legacy-cache flag, Config.timeoutMs which is replaced by Config.timeout, and Result.errCode, which has no replacement at all, we simply stopped exposing it. We want all four gone in 5.0. Our 4.0 ships in about four months. We're an open-source project, users upgrade whenever they feel like it. What do we publish right now?",
"expected_output": "A deprecation timeline table plus a separate removal-oriented page, with a warning level assigned to each item for the 4.0 interim release and a stable identifier per deprecation. Not a v5 migration guide.",
"files": [],
"expectations": [
"Produces a deprecation timeline table rather than a v5 migration guide",
"The table carries columns for the deprecated API, its replacement, its warning level in the current major, and the version that removes it",
"Records Result.errCode with an explicit 'none' in the replacement column rather than leaving it blank",
"States which warning level each of the four items needs in the 4.0 interim release",
"Names a warning-level ladder with at least documentation-only, application-code-only, runtime-across-all-code and end-of-life rungs",
"Recommends warning on application code only before escalating to warnings that also fire inside dependencies",
"Gives the reason: a warning raised inside a dependency the reader cannot patch teaches them to ignore warnings altogether",
"Assigns each of the four deprecations a stable identifier that never changes once published",
"States that the identifier exists so lint rules, codemods and support replies can point at one deprecation rather than at a page",
"Also produces or recommends a separate cumulative page organized by removal version, answering what disappears in 5.0",
"Explains that readers arrive with two different questions and a single ordering cannot serve both",
"Expresses the deprecation window in releases rather than months for this open-source audience, referencing the deprecate-in-N, warn-in-N+1, remove-in-N+2 pattern as the reference point"
]
},
{
"id": 8,
"prompt": "Draft the breaking-change entries for these three changes in Candlewick v7 of our Node library cwick. (a) Client.fetch() no longer accepts a callback as its second argument, it is promise-only now, and it throws TypeError: fetch() no longer accepts a callback; await the returned promise. (b) We moved config out of .cwickrc into cwick.config.ts, which also moves cwick.plugins, cwick.retries, cwick.baseUrl and cwick.logLevel. Same values, new home. (c) Client.close() used to swallow errors during shutdown and now it rejects instead; there is a closeTolerant: true option for anyone who needs the old behaviour.",
"expected_output": "Three entries, not six: the four config keys grouped under one entry because they share one cause. Each entry carries a verb-prefixed heading with a stable anchor, an affects-you-if line with a search pattern and the literal message, a rationale capped at three sentences, labelled before/after code, the fix, and an escape hatch or an explicit none.",
"files": [],
"expectations": [
"Every entry has a verb-prefixed heading using Removed, Changed, Renamed or Deprecated",
"Every entry carries a stable anchor identifier",
"Every entry has an affects-you-if line giving a search pattern that locates the call sites in a codebase",
"Every entry quotes the literal runtime error or warning text exactly, including punctuation, where one exists",
"Reproduces 'TypeError: fetch() no longer accepts a callback; await the returned promise' verbatim in the Client.fetch entry",
"Caps each entry's rationale at three sentences",
"Each rationale names the harm the old behaviour caused",
"Every entry has before/after code labelled with Before and After markers",
"Before/after snippets are complete enough to paste and use the same variable names in both halves",
"Writes the config move as a single entry covering all four moved keys rather than one entry per key",
"Gives the grouped config entry one anchor per fix rather than one anchor per moved symbol",
"Records closeTolerant: true as the escape hatch on the Client.close entry",
"States explicitly that there is no escape hatch for the callback removal rather than leaving that part silent",
"Orders the six parts of each entry as heading, affects-you-if, rationale, before/after, fix, escape hatch"
]
},
{
"id": 9,
"prompt": "Ashgrove Systems sells an on-prem log pipeline. We have 34 enterprise customers on signed contracts, and about 9 of those run air-gapped managed deployments that we patch on a schedule they control. v8 drops the legacy TCP ingest protocol entirely. Our contracts commit us to 90 days notice on breaking changes. Legal has asked to see whatever we publish. Write the migration guide.",
"expected_output": "A contractual-audience guide led by the deadline and the 90-day notice window, with dates rather than release counts, support and escalation inside the document, a rollback procedure, a vendor-versus-customer responsibility split, a separately dated escape hatch for the 9 air-gapped deployments, and a legal/comms sign-off step.",
"files": [],
"expectations": [
"Leads with the deadline and the 90-day notice window rather than with what the upgrade buys the customer",
"Expresses the window in dates rather than release counts, because this audience schedules against calendars and contracts",
"Names the support path and the escalation contact inside the guide itself, not only in the announcement email",
"Gives the reason: the announcement email reaches one address that may no longer be monitored, while the guide is what the engineer actually opens",
"Includes a rollback procedure and states how long rollback remains possible",
"States who performs the cutover on the vendor side and what the customer must complete before it",
"Gives the 9 air-gapped managed deployments their own escape hatch with its own end date, separate from the general timeline",
"Gives the reason those deployments need a separate date: they plan on a different cycle and will otherwise escalate",
"Routes the draft through support and legal or comms sign-off before publication",
"States explicitly what happens to a customer who does nothing by the deadline",
"Does not leave the guide without a named owner and a date the customer can schedule against",
"Defers hard enforcement such as forced removal or closing the enterprise escape hatch until adoption visibly flattens rather than starting it on the originally announced date"
]
},
{
"id": 10,
"prompt": "Our v4 upgrade guide for tessel is written: 17 entries, every one has code, and we published a jscodeshift codemod alongside it. Marketing wants a line at the top saying roughly how long the upgrade takes, and the engineer who wrote the guide reckons 'about an hour for most apps'. We also want to be reasonably sure the guide is actually correct before it goes live. How do we check it?",
"expected_output": "A verification plan built on a cold upgrade run by someone who did not write the guide, on a real project, using only the guide. The published effort figure is the measured elapsed time of that run or nothing at all. Completeness is checked against the tag-to-tag API diff, not the changelog, and the codemod residue is named.",
"files": [],
"expectations": [
"Requires a cold upgrade run against a real project sitting on the old version, using only the guide",
"Requires the person at the keyboard to be someone who did not write the guide",
"Rejects publishing 'about an hour' because it has not been measured",
"Requires publishing the measured elapsed time of the cold run, or publishing no effort estimate at all",
"Gives the reason an unmeasured underestimate is costly: the reader stops trusting every other number in the document at the same moment",
"Checks completeness against the public-API diff between the two tags rather than against the changelog",
"Invokes Hyrum's Law, or its substance, to explain that even the tag diff undercounts, and adds anything support has already seen break",
"Treats any edit the upgrade required that the guide does not describe as a defect in the guide rather than a reader failure",
"Requires the published codemod to have been run on the test project, with the residue it leaves named in the guide",
"Requires the typecheck and the test suite to pass after the codemod run",
"Requires the oldest promised source version to have been run through the full path if multi-version jumps are promised",
"Recommends wiring the guide's code snippets into CI as executable documentation so a snippet that stops compiling fails the build",
"Recommends splitting the guide per module and sharding the verification the same way if completeness fails on a large surface, rather than dropping entries"
]
},
{
"id": 11,
"prompt": "Nettlefold ships an open-source CLI with roughly 200k installs. v3 shipped six weeks ago and v2 support ends in nine months. Leadership wants a dashboard proving the migration is working. What we already have wired up: npm download stats, docs analytics through Plausible, a 'was this helpful' thumbs widget on every docs page, and Zendesk for support. What should be on the dashboard?",
"expected_output": "A metric list ordered by value per hour of wiring, leading with upgrade-error ticket rate, rejecting the helpful widget, naming the adoption curve as the only metric that says the migration worked while flagging it as a standing job, and adding a named straggler list.",
"files": [],
"expectations": [
"Orders the metrics by value per hour of wiring rather than by the order they would be reported in",
"Puts tickets per thousand upgrades that quote an upgrade error first, noting support already collects them so the metric is effectively free",
"States that the change generating the most such tickets identifies the entry to rewrite",
"Rejects the 'was this helpful' widget as low-signal and easily gamed, and refuses to let it stand in for the adoption curve",
"Names the version-adoption curve as the only metric that says the migration actually worked",
"Flags the adoption curve as a standing job requiring install or account telemetry joined across ninety days, not a quick wiring task",
"Names explicitly what the efficiency ordering starves, namely the adoption curve as the highest-value number on the list",
"States the condition that promotes the adoption curve above everything else, such as a removal date that has to be defended to customers or leadership",
"Warns that the identity join must begin before the release ships, because started afterwards the first ninety days are already unmeasurable",
"Includes deprecation-warning telemetry trending toward zero before the removal release",
"States that flat deprecation-warning telemetry a month before removal means the removal date is wrong or the guide is not reaching people",
"Includes a straggler list of named accounts or top dependents still on v2 after the deadline, framed as a list to work rather than a percentage",
"Plans the final stretch of unmigrated users as a named-account exercise rather than a documentation problem, noting that a long tail is normal even where enforcement backs the transition",
"Treats the Plausible docs page analytics as directional and inferential rather than as an outcome measure"
]
},
{
"id": 12,
"prompt": "This is the third major in a row where our migration guide got written two days before the release by whoever happened to have time. We do have an RFC process for feature work already: every significant change gets an RFC with Motivation, Design, Drawbacks and Alternatives sections. Separately, our biggest integrators have started pointing coding agents at our docs to do the upgrade for them, and apparently it goes badly. How do we stop the guide being an afterthought, and does the agent thing change what we write?",
"expected_output": "A proposal to add a required upgrade/teaching section to the existing RFC template so the docs gate the code, backed by the Ember and Kubernetes precedents and a warning that gates need enforcement, plus the machine-readable requirements that make the same guide work for a coding agent.",
"files": [],
"expectations": [
"Proposes adding a required section to the existing RFC template rather than a release-checklist item or a writer assignment",
"Names the 'How we teach this' RFC section as a precedent and attributes it to Ember's RFC process",
"Names the Kubernetes KEP requirement for Upgrade / Downgrade Strategy and Version Skew Strategy sections as a second precedent",
"States that the gate must block the code from landing rather than merely requesting the documentation",
"Notes that a gate on paper still needs enforcement, citing that a project which copied such a gate later admitted part of it was never carried out",
"Names the ownership split as the reason the guide goes missing, with the engineer who made the break owning the substance and a writer or advocate owning clarity and publication, each assuming the other is drafting",
"Recommends shipping the migration note in the same pull request as the breaking change, which makes the person who broke the API the first drafter",
"States that adding the RFC section converts every future break into a guide automatically, unlike a one-off fix for this release",
"Answers that assuming a coding agent will execute the guide costs nothing and improves the document for human readers too",
"Requires error and warning strings to be quoted exactly, including punctuation, because agents and search boxes both match on the literal",
"Requires complete runnable snippets including their imports rather than fragments the reader must place correctly",
"Requires one canonical URL per version pair with stable per-change anchors, rather than a page rewritten in place each release",
"Requires version floors to be stated as machine-checkable comparisons rather than as prose",
"Requires the migration path itself to stay inside the guide rather than being delegated to a release blog post"
]
}
],
"trigger_queries": [
{ "query": "write the migration guide for our v3 release", "should_trigger": true },
{ "query": "we're shipping v2 of the SDK next month", "should_trigger": true },
{ "query": "how do we tell users about v3", "should_trigger": true },
{ "query": "need an upgrade guide for our SDK", "should_trigger": true },
{ "query": "our breaking-changes page is out of date, can you fix it", "should_trigger": true },
{ "query": "audit our v2 to v3 upgrade docs", "should_trigger": true },
{ "query": "we deprecated four APIs in 2.4 and want them gone in 3.0, what do we publish now", "should_trigger": true },
{ "query": "draft the sunset notice for the old API version", "should_trigger": true },
{ "query": "we have a codemod but nothing explaining what it doesn't cover", "should_trigger": true },
{ "query": "what should our compat build documentation say", "should_trigger": true },
{ "query": "customers keep filing tickets about our major version bump", "should_trigger": true },
{ "query": "we renamed half the config keys in v4, how do we communicate that", "should_trigger": true },
{ "query": "help me plan the cutover to our new dated API version", "should_trigger": true },
{ "query": "our Postgres column rename needs a runbook the service teams can follow", "should_trigger": true },
{ "query": "how do I document a schema migration for six downstream services", "should_trigger": true },
{ "query": "we're dropping Node 18 support in the next major, what do we tell people", "should_trigger": true },
{ "query": "write the breaking changes section for our release", "should_trigger": true },
{ "query": "what order should the breaking changes go in on the upgrade page", "should_trigger": true },
{ "query": "our users are stuck on 2.x and nobody's upgrading, what do we write", "should_trigger": true },
{ "query": "should we ship an interim release with deprecation warnings before the major", "should_trigger": true },
{ "query": "our upgrade page is basically the changelog with a different title", "should_trigger": true },
{ "query": "we need a deprecation timeline table", "should_trigger": true },
{ "query": "how do we document that one intermediate version is broken and should be skipped", "should_trigger": true },
{ "query": "a guide for going from our old REST v1 to the new v2", "should_trigger": true },
{ "query": "put together the upgrade instructions for v5", "should_trigger": true },
{ "query": "our enterprise customers need something they can plan the upgrade against", "should_trigger": true },
{ "query": "half our users are on an unsupported path and the guide doesn't say so", "should_trigger": true },
{ "query": "we broke the webhook payload shape, how do we tell integrators", "should_trigger": true },
{ "query": "what goes in an upgrade guide besides the list of things that broke", "should_trigger": true },
{ "query": "how do we verify our migration docs actually work before publishing", "should_trigger": true },
{ "query": "can we publish an estimate of how long the upgrade takes", "should_trigger": true },
{ "query": "our v4 guide is missing the error messages, people can't find their problem", "should_trigger": true },
{ "query": "I need to explain to 40 teams how to move off the old auth library", "should_trigger": true },
{ "query": "how do we measure whether people actually moved to the new major", "should_trigger": true },
{ "query": "which numbers tell us our version rollout is working", "should_trigger": true },
{ "query": "we want to force everyone off v1 next quarter, what has to be published first", "should_trigger": true },
{ "query": "we're changing the default behaviour of a function and need to warn people", "should_trigger": true },
{ "query": "write the 'what disappears in 4.0' page", "should_trigger": true },
{ "query": "the docs for going from 1.x to 3.x skip a version and it breaks", "should_trigger": true },
{ "query": "should we make people go through every minor or can they jump straight to the new one", "should_trigger": true },
{ "query": "our mobile SDK update needs an on-device data migration story", "should_trigger": true },
{ "query": "we need a Terraform provider upgrade guide", "should_trigger": true },
{ "query": "how do I document what the codemod leaves behind", "should_trigger": true },
{ "query": "a page that tells people every place their code will break when they update", "should_trigger": true },
{ "query": "i broke the API and now i have to explain it, help", "should_trigger": true },
{ "query": "our support team keeps rewriting the same upgrade instructions in tickets", "should_trigger": true },
{ "query": "how do we stop the upgrade docs being written the night before the release", "should_trigger": true },
{ "query": "add a version skew section to our upgrade docs", "should_trigger": true },
{ "query": "we need per-change anchors so support can link to one breaking change", "should_trigger": true },
{ "query": "our integrators are pointing coding agents at our upgrade docs and it doesn't work", "should_trigger": true },
{ "query": "what should the troubleshooting section of an upgrade page look like", "should_trigger": true },
{ "query": "we need a section for config that's now dead after the upgrade", "should_trigger": true },
{ "query": "rewrite this entry, it just says 'update your usage accordingly'", "should_trigger": true },
{ "query": "the thing where you tell people what to do when the new version breaks their build", "should_trigger": true },
{ "query": "we're moving everyone to the 2026-11-02 API version", "should_trigger": true },
{ "query": "explain to customers what happens if they don't upgrade by the deadline", "should_trigger": true },
{ "query": "our npm package is going to 4.0 and eleven things break", "should_trigger": true },
{ "query": "how do we structure the page people land on when their build breaks after updating", "should_trigger": true },
{ "query": "we need to tell people to bump their minimum Java version", "should_trigger": true },
{ "query": "a document that takes someone from our old client to the new one with the least reading", "should_trigger": true },
{ "query": "is a migration guide even the right thing here, everything's in our monorepo", "should_trigger": true },
{ "query": "our platform team broke an internal API used by 60 services", "should_trigger": true },
{ "query": "what's the right way to group fifteen breaking changes that all come from one config move", "should_trigger": true },
{ "query": "the upgrade page lists changes alphabetically and it's useless", "should_trigger": true },
{ "query": "we need before and after code for every breaking change", "should_trigger": true },
{ "query": "how do we say there's no workaround for a removal", "should_trigger": true },
{ "query": "we want to document which surfaces move together when the API version changes", "should_trigger": true },
{ "query": "write the upgrade notes for our CLI's next major", "should_trigger": true },
{ "query": "our framework upgrade needs a migration build so teams can ship half-done", "should_trigger": true },
{ "query": "should we do a staged upgrade path or one jump", "should_trigger": true },
{ "query": "what does an integrator need to do before we flip their account to the new version", "should_trigger": true },
{ "query": "help me plan how 200 downstream repos move to our new major", "should_trigger": true },
{ "query": "write the release notes for 3.2", "should_trigger": false },
{ "query": "turn this week's merged pull requests into a changelog", "should_trigger": false },
{ "query": "what version scheme should we use, semver or date-based", "should_trigger": false },
{ "query": "how long should our deprecation window be before we remove an endpoint", "should_trigger": false },
{ "query": "decide our sunset policy for old API versions", "should_trigger": false },
{ "query": "design our release train cadence, how often should we cut majors", "should_trigger": false },
{ "query": "what's a sensible deprecation policy for our internal services", "should_trigger": false },
{ "query": "upgrade our app from React 18 to React 19", "should_trigger": false },
{ "query": "bump all our dependencies and fix whatever breaks", "should_trigger": false },
{ "query": "our go.mod is three majors behind, can you update it", "should_trigger": false },
{ "query": "write a migration guide for teams switching to us from Datadog", "should_trigger": false },
{ "query": "we need a 'switch from Segment' landing page", "should_trigger": false },
{ "query": "build a comparison page against our biggest competitor", "should_trigger": false },
{ "query": "write troubleshooting docs for the errors people hit day to day", "should_trigger": false },
{ "query": "turn our support tickets into an error reference page", "should_trigger": false },
{ "query": "write a tutorial teaching our new streaming API", "should_trigger": false },
{ "query": "our quickstart takes 40 minutes, make it shorter", "should_trigger": false },
{ "query": "audit the structure of our docs site against Diataxis", "should_trigger": false },
{ "query": "our docs aren't ranking for anything, fix the SEO", "should_trigger": false },
{ "query": "set a policy for what every code sample in our docs must include", "should_trigger": false },
{ "query": "make our API reference consumable by coding agents", "should_trigger": false },
{ "query": "write an llms.txt for our documentation site", "should_trigger": false },
{ "query": "pitch our v3 launch to TechCrunch", "should_trigger": false },
{ "query": "draft the blog post explaining why we rewrote the core", "should_trigger": false },
{ "query": "script a video walking through the new version", "should_trigger": false },
{ "query": "rewrite our README so people understand what the project does", "should_trigger": false },
{ "query": "which package registries and directories should we publish to", "should_trigger": false },
{ "query": "find the error-string keywords developers actually search for", "should_trigger": false },
{ "query": "design our issue triage labels and response targets", "should_trigger": false },
{ "query": "write a case study about how Acme deployed us in production", "should_trigger": false },
{ "query": "which DevRel metrics should we report to the board", "should_trigger": false },
{ "query": "plan launch day for our open-source project", "should_trigger": false },
{ "query": "how should we price the enterprise tier", "should_trigger": false },
{ "query": "should we contribute to OpenTelemetry or build our own protocol", "should_trigger": false },
{ "query": "map the developer journey from discovery to advocacy", "should_trigger": false },
{ "query": "pick a license for our new project", "should_trigger": false },
{ "query": "write our project's governance model and maintainer promotion rules", "should_trigger": false },
{ "query": "draft a code of conduct for our Discord server", "should_trigger": false },
{ "query": "should we start a developer community, and where", "should_trigger": false },
{ "query": "write a CFP abstract for KubeCon", "should_trigger": false },
{ "query": "turn my accepted abstract into a talk outline", "should_trigger": false },
{ "query": "make sure my live demo survives the conference wifi", "should_trigger": false },
{ "query": "plan next quarter's developer content calendar", "should_trigger": false },
{ "query": "set up event tracking across our docs and repositories", "should_trigger": false },
{ "query": "should we build a certification program for our product", "should_trigger": false },
{ "query": "design the go-to-market motion for our developer tool", "should_trigger": false },
{ "query": "what should our DevRel team report into", "should_trigger": false },
{ "query": "write a job posting for a developer advocate", "should_trigger": false },
{ "query": "review my resume for a DevRel role", "should_trigger": false },
{ "query": "benchmark our competitor's developer docs and community", "should_trigger": false },
{ "query": "design an ambassador program with perks", "should_trigger": false },
{ "query": "how do we onboard first-time contributors to the repo", "should_trigger": false },
{ "query": "which conferences should we sponsor next year", "should_trigger": false },
{ "query": "split our developer audience into named segments", "should_trigger": false },
{ "query": "should we open-source our control plane", "should_trigger": false },
{ "query": "which business model should we use, open core or hosted", "should_trigger": false },
{ "query": "plan a recurring developer meetup in Berlin", "should_trigger": false },
{ "query": "optimize my GitHub profile README", "should_trigger": false },
{ "query": "prepare me for a podcast interview next week", "should_trigger": false },
{ "query": "build the employer brand so engineers apply to us", "should_trigger": false },
{ "query": "set up sponsorship tiers for my open-source project", "should_trigger": false },
{ "query": "which maintainers should our company fund", "should_trigger": false },
{ "query": "measure the health of our contributor community", "should_trigger": false },
{ "query": "how much of the DevRel budget should go to events", "should_trigger": false },
{ "query": "what should we build in public and what should we keep quiet", "should_trigger": false },
{ "query": "run the database migration on our staging environment", "should_trigger": false },
{ "query": "write an Alembic migration for this model change", "should_trigger": false },
{ "query": "set up blue-green deployment for our API", "should_trigger": false },
{ "query": "migrate our CI from Jenkins to GitHub Actions", "should_trigger": false },
{ "query": "help me move our data warehouse from Redshift to Snowflake", "should_trigger": false },
{ "query": "explain semantic versioning to my team", "should_trigger": false },
{ "query": "write reference documentation for our new v4 endpoints", "should_trigger": false }
]
}
references/api-version-migration.md›
# Hosted API migrations
A library upgrade is local and opt-in. An API version change alters behavior on someone else's running production system, on a date they did not pick. Everything below is what a hosted-API guide needs _in addition_ to the standard breaking-change entries.
## Pinning and pre-cutover testing
The single most valuable instruction such a guide gives is how to try the new version **without changing anything permanent**: a per-request version header, a per-client version parameter, or a sandbox pinned to the new version. Give a complete example request with the header in place, and state clearly which mechanism wins when both an account default and a per-request pin exist.
Tell the reader to pin explicitly in code rather than inheriting the account default. Inherited defaults mean the next cutover changes behavior in an application nobody touched, which is how a scheduled migration becomes an incident.
## Surface matrix
List every surface and how it versions, because they rarely move together:
| Surface | Versioning | Moves with the API version? |
| -------------------- | --------------------------- | ------------------------------------------- |
| Server SDK | Semver, per language | Configurable, per request or per client |
| Browser bundle | Named evergreen releases | Pinned to a specific API version |
| Mobile SDK | Semver | Independent; compatible across API versions |
| Webhooks / callbacks | Follows the account default | Not pinnable per request |
| CLI / dashboard | Product release train | Independent |
Strongly-typed language SDKs often pin the API version at compile time, while dynamic-language SDKs allow per-request overrides. Say which applies to each SDK you ship - a reader who assumes the wrong model plans the wrong migration.
## Webhooks get their own section
Inbound events cannot be version-pinned the way outbound calls can: the consumer does not make the request. Cover which event types change shape, whether a parallel endpoint on the new version can be registered for testing, and how to handle a window where both payload shapes arrive.
## Publish the compatibility contract once
State the exhaustive list of changes you consider backward-compatible (new resources, new optional request parameters, new response properties, reordered properties, new enum members, new event types) and tell integrators to code defensively against exactly those. Anything outside the list becomes a named version with its own guide.
Publishing the contract once ends the per-release argument about whether a change is breaking, and it tells integrators which defensive code is actually required rather than leaving them to over-engineer.
## Data-layer consequences
Wire changes leak into consumers' databases. Cover:
- Identifier length and case sensitivity.
- Changed enum values persisted in columns.
- Timestamp precision or timezone representation.
- Decimal versus integer money fields.
- Any stored raw payloads that must be re-parsed.
These are the changes that break consumers weeks after a successful cutover, which is why they need their own section rather than a line in a code entry.
## Cutover and rollback
State, in this order:
1. Who flips the account default, and on what date.
2. What the customer must complete beforehand.
3. How to roll back, and for how long that remains possible.
4. How long the old version stays available after the deadline.
Give the rollback procedure even when you are confident. Readers schedule an irreversible change differently from a reversible one, and the difference decides whether they migrate early or wait for the deadline.
## Notice and escalation
For contractual audiences, put the notice window, the support path and the escalation contact inside the guide. The announcement email reaches one address that may no longer be monitored; the guide is what the engineer actually opens.
references/automated-migration-tooling.md›
# Automated migration tooling
Five rungs of automation exist, ranked here by reader hours saved per engineering hour spent building and holding them. Document whichever you have; build down the list only as far as the budget reaches.
| Rung | What it buys the reader | What it costs you |
| ------------------------------------------------ | ----------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| Search patterns and literal messages, per change | Every call site located, by a human or an agent, with no tooling at all | Near-zero - it is the affected-if line you already write |
| Deprecation warnings in N-1 | Their own build output becomes their personal inventory | One interim release, plus the warning plumbing behind it |
| Lint rule | New code stops reintroducing the old API after the migration | A day, then it lives with the ruleset |
| Codemod | The mechanical majority of edits, across a whole codebase | A week, corpus validation, and a rewrite every time the API moves again |
| Compat build | A migration that ships to production half-done | A standing job: dual behavior paths and per-feature flags until it is retired |
- value: compat build > codemod > deprecation warnings in N-1 > search patterns and literal messages > lint rule
- engineering plus upkeep effort: compat build > codemod > deprecation warnings in N-1 > lint rule > search patterns and literal messages
- efficiency: search patterns and literal messages > deprecation warnings in N-1 > lint rule > codemod > compat build
The lint rule outranks the codemod on efficiency while losing to it badly on value: it is a day of work against a week, and it defends the migration after the guide stops being read, which is where the second wave of regressions comes from.
**Default: ship the first rung, always**, and buy the second whenever N-1 can still ship a release. That order starves the compat build and, one rung above it, the codemod - the two things readers ask for by name.
Promote them anyway when the blast radius crosses hundreds of call sites per consumer, or when consumers are large applications that cannot hold a long-lived migration branch. When nobody owns the tooling after the release, say so and stay on the first two rungs rather than shipping a codemod that rots into a wrong answer.
This ranking is a default, not a law. An ecosystem where the codemod is nearly free (`cargo fix --edition`, `go fix`, an OpenRewrite recipe close to one you already publish) moves the codemod straight to the top for that project.
## Codemods
Ecosystem tooling, as an integration note rather than a requirement (the guide outlives any of these):
| Ecosystem | Tooling |
| ----------------------- | --------------------------------------------------------------------- |
| JavaScript / TypeScript | `jscodeshift`, the `codemod` CLI, `ast-grep`, type-only codemod packs |
| Rust | `cargo fix --edition` |
| Python | `libcst` codemods, `pyupgrade` |
| Go | `go fix`, `gofmt -r` rewrite rules |
| JVM / polyglot | OpenRewrite recipes |
Documentation rules, in order of how often they are broken:
1. **Publish the exact command**, not the tool's homepage. A reader mid-upgrade should never have to navigate.
2. **Publish both granularities** - an all-changes recipe and the per-change commands - because a reader who already fixed half the codebase wants only the rest.
3. **State the coverage boundary in the same breath as the command.** React's v19 guide says outright that its codemods do not cover TypeScript changes. An unstated gap is discovered as a broken build, and it discredits the whole guide, not just that entry.
4. **Name the residue.** Codemods handle literal arguments and static call sites; dynamic dispatch, re-exports, generated code and string-keyed access survive. Say which of those apply and how to find them.
5. **Tell the reader how to verify.** Typecheck, run the test suite, review the diff. A mechanical rewrite is review-required, not fire-and-forget, and saying so preempts the "your codemod broke my build" issue.
6. **Never position the codemod as a replacement for the breaking-change list.** It is the first pass; the list is the contract.
Validate the codemod against a corpus, not one repository: clone every example app, internal consumer and public dependent you can, run it across all of them, and publish what it left behind. One project is not a sample, and the residue list is the paragraph readers cannot reconstruct for themselves.
## Compat / migration builds
A compat build runs old-version behavior by default, warns on each deprecated usage, and lets the project flip behavior per feature and per module. Vue's v3 migration build is the reference implementation, and its documented workflow generalizes:
1. Update tooling and companion libraries.
2. Install the compat build behind an alias so imports stay unchanged.
3. Clear compile-time errors first - they block everything else.
4. Work through runtime warnings in batches, filtering by feature flag.
5. Migrate module by module, flipping each one's flag as it is done.
6. Remove the compat build and the flags.
The property worth stating explicitly in the guide: a compat build makes the migration **shippable mid-way**. The team deploys a half-migrated application instead of maintaining a long-lived branch that rots against main. That single sentence changes how a team plans the work, so it belongs near the top of the guide, not in an appendix.
## The manual path, which every guide ships
The first rung is not a consolation prize for projects without tooling - it is what makes the manual path findable and it ships whatever else exists. Per breaking change, give the search pattern that locates every call site and the literal runtime message. That pair is also exactly what a coding agent needs to do the work unattended, which is why it outranks tooling that only a human can drive.
When there is no codemod, say so plainly. Readers who assume one exists spend their first hour looking for it.
references/breaking-change-entry-template.md›
# Breaking-change entry template
One block per breaking change. Fill every part; an empty part is a decision you have not made yet.
## Template
````markdown
### Removed: `client.send()` accepts a raw string {#removed-send-raw-string}
**Affects you if** your code calls `client.send()` with anything other than a `Payload`.
Search for `\.send\(` in your codebase. At runtime on v3 you will see:
TypeError: send() expects a Payload, received string
`send()` took either a string or a `Payload` and guessed the encoding from the first
bytes. The guess was wrong for UTF-16 input and silently corrupted messages, so v3
requires the caller to say which one it is.
```js
// Before (v2)
await client.send("hello");
// After (v3)
await client.send(Payload.text("hello"));
```
````
**Fix:** `npx codemod@latest acme/3/send-payload` rewrites literal and template-string
arguments. Calls whose argument is a variable are left untouched and reported; fix
those by hand with `Payload.text()` or `Payload.bytes()`.
**Escape hatch:** none. `Payload` has no v2 equivalent; the v2 line receives security
fixes until 2027-03-01.
````
## Part-by-part rules
| Part | Rule |
|---|---|
| Heading | Verb first (`Removed:`, `Changed:`, `Renamed:`, `Deprecated:`), plus a stable anchor id that never changes once published |
| Affects-you-if | A search pattern *and* the literal runtime message; both, because static and runtime detection catch different call sites |
| Rationale | Three sentences maximum, and it must name the harm the old behavior caused |
| Before/after | Complete enough to paste, same variable names in both halves so the diff is visually obvious |
| Fix | One primary path. A codemod command *plus* its named residue, or numbered manual steps - never a vague "or do it manually" |
| Escape hatch | The compat flag, the config restoring old behavior, or an explicit "none" with the old version's support end date |
## Good vs bad
**Bad:**
```markdown
### send()
`send()` has been updated in v3. Update your usage accordingly. See the changelog
for details.
````
Three defects:
- The reader cannot tell whether it affects them.
- The reader cannot see what to type.
- The reader is sent to a document that will not tell them either.
This entry generates a support ticket instead of preventing one.
**Bad in a subtler way:**
```markdown
### Removed: string argument to `send()`
Use a `Payload` instead. A codemod is available on our GitHub.
```
The change is now identifiable but not actionable: no detection signal, no before/after, no exact command, and "available on our GitHub" makes the reader search. Every one of those gaps costs more reader minutes than the sentence saved.
## Grouping
When several changes share one cause (a config file moving, a module format switch) - write one entry for the cause and list the affected symbols inside it. Splitting a single mechanical change into fifteen entries pushes the genuinely different changes off the reader's first screen.
Keep grouped entries under one anchor per _fix_, not per symbol: the anchor exists so a support reply can point at the action.
references/deprecation-timeline-patterns.md›
# Deprecation timelines
## Warning-level ladder
Grade a deprecation by how loudly it fails, and escalate it across releases rather than jumping straight from silence to removal. Node.js's four levels are the most complete published example:
| Level | Behavior | Use when |
| --------------------- | ------------------------------------------------------------- | ---------------------------------------------------------------------- |
| Documentation-only | No runtime effect; opt-in warning behind a flag | The replacement exists but adoption has not started |
| Application-code only | Warning on first use, suppressed for code inside dependencies | Most deprecations - the application author is the only one who can act |
| Runtime, all code | Warning on first use, dependencies included | Close to removal, or the dependency ecosystem must be pushed |
| End-of-life | Removed | The removal release |
Warning only application code first is the detail that makes the ladder tolerable. A warning raised inside a dependency the reader cannot patch teaches them to ignore warnings, which is exactly the habit the removal release depends on them not having.
## Window length
Express the window in **releases**, not months, for open-source and library audiences: readers upgrade on their own cadence, and a release count survives a slipped schedule. Django's rule is the well-worn reference - deprecated in N, still warning in N+1, removed in N+2, which lands around 12-18 months.
Use **dates** for contractual and hosted-API audiences, because those readers schedule against calendars and contracts, not your tags. Publish both when both apply.
State exceptions explicitly. A security-driven accelerated removal is legitimate; an unannounced one is what makes readers stop believing the policy.
## Stable identifiers
Give every deprecation an identifier that never changes: `DEP0042`, `acme-deprecate-send-string`, or a permanent anchor. Node.js pairs each `DEP0XXX` with a version-by-version history table; Ember pairs each deprecation with an `until` version plus a stable ID.
Identifiers let lint rules, codemods, and support replies point at one deprecation instead of a page. They also make a _revocation_ survivable: a revoked deprecation keeps its identifier and gets a status change rather than disappearing, so anyone who linked to it still lands somewhere truthful.
## Timeline table
Publish one table per major, organized by removal version so a reader jumping several versions reads one section per hop:
```markdown
| Deprecated API | Replacement | Warning level in 3.x | Removed in |
| --------------------- | ------------------------------ | ------------------------ | ---------- |
| `client.send(string)` | `client.send(Payload)` | Runtime (3.0) | 4.0 |
| `AcmeError.code` | `AcmeError.reason` | Documentation-only (3.2) | 5.0 |
| `--legacy-parser` | none (parser is always strict) | Runtime (3.0) | 4.0 |
```
Each row needs the replacement or an explicit "none". A row without one is an unfinished deprecation: nobody can act on it, so it will still be in use on removal day.
## Reverse view
Keep a second, cumulative page organized by _removal_ version ("what disappears in 4.0") separate from the per-release notes. Readers arrive with two different questions ("what do I have to fix before 4.0" and "what did 3.2 deprecate") and one ordering cannot serve both.
references/guide-outline-examples.md›
# Guide outlines
Two skeletons and one anti-pattern. Both skeletons follow the same order for the same reason: triage before work, floors before code, automation before manual labour.
## Table of Contents
- [Library / SDK / framework outline](#library-sdk-framework-outline)
- [Before you start](#before-you-start)
- [Requirements](#requirements)
- [Install](#install)
- [Automated migration](#automated-migration)
- [Breaking changes](#breaking-changes)
- [New deprecations](#new-deprecations)
- [Behavior changes](#behavior-changes)
- [Type-level changes](#type-level-changes)
- [Housekeeping](#housekeeping)
- [Troubleshooting](#troubleshooting)
- [Full changelog](#full-changelog)
- [Hosted API outline](#hosted-api-outline)
- [What changes and when](#what-changes-and-when)
- [Surfaces and versions](#surfaces-and-versions)
- [Test before you switch](#test-before-you-switch)
- [Breaking changes](#breaking-changes)
- [Webhook payload changes](#webhook-payload-changes)
- [Data-layer impact](#data-layer-impact)
- [Cutover and rollback](#cutover-and-rollback)
- [Troubleshooting](#troubleshooting)
- [Anti-pattern outline](#anti-pattern-outline)
- [What's new in v3 <- marketing section on a work document](#whats-new-in-v3---marketing-section-on-a-work-document)
- [Why we rebuilt the core <- rationale that belongs in a blog post](#why-we-rebuilt-the-core---rationale-that-belongs-in-a-blog-post)
- [Breaking changes <- alphabetical, no detection signals](#breaking-changes---alphabetical-no-detection-signals)
- [FAQ <- the real migration steps, scattered](#faq---the-real-migration-steps-scattered)
- [Requirements <- floors discovered after the code examples](#requirements---floors-discovered-after-the-code-examples)
## Library / SDK / framework outline
```markdown
# Upgrading from v2 to v3
<!-- 1. Scope: which versions, which packages, expected effort (measured, not guessed) -->
This guide covers acme-client 2.x → 3.0 for the Node and browser packages.
A cold upgrade of a typical service took 40 minutes.
## Before you start
<!-- 2. Path shape, stated first, including any known-bad intermediate version -->
Upgrade to 2.9 first and run your test suite. Every change below emits a
deprecation warning in 2.9, so the warning output becomes your work list.
Do not stop on 2.8: it ships a connection-pool regression fixed in 2.9.
## Requirements
<!-- 3. Floors -->
- Node >= 20.19
- TypeScript >= 5.4
- acme-adapter >= 1.4 (peer dependency)
## Install
<!-- 4. Copy-paste, every package manager, pinned -->
## Automated migration
<!-- 5. All-changes recipe, per-change commands, coverage boundary -->
## Breaking changes
<!-- 6. One entry per change, ordered by blast radius -->
### Changed: configuration moved to acme.config.ts
### Removed: string argument to send()
### Changed: errors are AcmeError instead of Error
## New deprecations
<!-- 7. Still works, will not in v4 - with the removal version -->
## Behavior changes
<!-- 8. Nothing breaks, but output differs: defaults, ordering, timeouts, messages -->
## Type-level changes
<!-- 9. Separate from runtime changes; different reader, different failure mode -->
## Housekeeping
<!-- 10. What is now dead: config that became a default, workaround flags,
dependency overrides, patches, packages the framework now bundles -->
## Troubleshooting
<!-- 11. Keyed by the literal error text -->
## Full changelog
<!-- 12. Traceability only, never the migration path -->
```
## Hosted API outline
Same spine, different middle. The reader is changing behavior in production, not in a build.
```markdown
# Migrating to the 2026-07-29 API version
## What changes and when
<!-- Deadline, notice window, what happens if nothing is done -->
## Surfaces and versions
<!-- Matrix: server SDK, browser bundle, mobile SDKs, webhooks - which move together -->
## Test before you switch
<!-- The per-request pinning header, with a full example request -->
## Breaking changes
<!-- Same six-part entries, grouped by product area -->
## Webhook payload changes
<!-- Separate: consumers cannot pin an inbound request -->
## Data-layer impact
<!-- Identifier lengths, collation, enum values, stored payload shapes -->
## Cutover and rollback
<!-- Who flips the default, when, how to revert, how long the old version stays up -->
## Troubleshooting
```
## Anti-pattern outline
```markdown
# v3 Release
## What's new in v3 <- marketing section on a work document
## Why we rebuilt the core <- rationale that belongs in a blog post
## Breaking changes <- alphabetical, no detection signals
## FAQ <- the real migration steps, scattered
## Requirements <- floors discovered after the code examples
```
Four defects, in descending cost:
- Floors at the bottom.
- Migration steps hidden in an FAQ.
- Alphabetical ordering.
- A launch narrative occupying the screen the reader needed for work.
A guide is read by someone whose build is already broken; it earns nothing by opening with a celebration.
references/guide-types-beyond-libraries.md›
# Guide types beyond a library upgrade
The library/SDK case is the baseline the rest of this skill describes. Four other artefact types diverge enough that copying the library shape produces a wrong document. Pick the row that matches before drafting.
| Type | Core artefact | Distinctive mechanics | Reversibility |
| ------------------------- | ------------------------------------- | -------------------------------------------------------------------- | ----------------------------------------------- |
| Application library / SDK | Prose guide plus codemods | Semver, deprecation warnings, N-1 bridging | High - pin the old version, roll back |
| Database / schema | **Runbook**, not prose | Expand/contract, dual writes, batched backfill, online schema change | Low - data loss risk, rollback must be designed |
| Cloud / platform | Vendor playbook plus state migration | Provider major upgrades, state refresh, remote backend | Medium - the state file is authoritative |
| Mobile app | Store listing plus in-app update flow | Forced or staged rollout, on-device data migration on first launch | Very low - a shipped binary cannot be recalled |
| Browser / platform API | Official docs plus deprecation trials | Public intent-to-deprecate, origin trials, enterprise policy escape | Low - breaks live sites globally |
## Database and schema: write a runbook
Each step is an operational action with an explicit rollback and a backfill plan, not a snippet the reader pastes. That is the deepest divergence from the library shape.
Use **expand/contract** (also called parallel change): add the new schema elements without touching the old ones, dual-write and backfill, then remove the obsolete parts in a separate, later deploy. The reason is deployment overlap - during any rollout, old and new code run at the same time, so an in-place rename leaves one of them querying a column that no longer exists.
Runbook rules worth stating in the document itself:
1. Additive steps ship in any deploy; destructive steps (drop, rename) ship alone, after nothing references the old shape.
2. Every step names its rollback, and the rollback has been executed at least once against a copy of production.
3. Backfills run in throttled batches off the hot path - a single statement over millions of rows locks the table.
4. Build large indexes without blocking writes where the engine supports it.
5. Database-level tooling can carry the dual-write for you (versioned schema views and trigger-backed dual writes, or an online schema-change tool) - name whichever exists for the reader's engine.
## Cloud and platform: state is the source of truth, not code
Infrastructure-as-code provider major upgrades need a per-version upgrade guide, and the guide must say what to run immediately after the upgrade to reconcile stored state with the new internal schemas (a refresh-only apply, in Terraform's case). Say that a versioned remote backend is what makes rollback possible; without one there is no rollback story to write.
## Mobile: you cannot un-ship a binary
Cover the staged-rollout percentage plan, the forced-update path for versions that must not keep running, and the on-device data migration that executes once on first launch of the new binary - including what happens when it fails halfway.
## Browser and platform APIs: the timeline is public and contested
These deprecations run through a formal public process, and the timeline moves in response to developer backlash. Chrome's Manifest V2 to V3 transition is the flagship case: announced in 2021, then paused in December 2022, then republished, then resumed in the May 2024 "Resuming the transition" post, with an enterprise policy giving managed deployments the longest escape hatch. The DevRel deliverables that eventually moved adoption past 85% of actively maintained extensions were a migration guide, per-capability checklists, and a public progress page tracking the top blocking issues.
Two transferable lessons:
- Ship a **public progress page** for the gaps you have not closed yet. A deprecation with known missing capabilities and no visible tracker reads as indifference, and the backlash costs more than the honesty would have.
- Give managed or contractual deployments a **named escape hatch with its own end date**, separate from the general timeline. They plan on a different cycle and will otherwise escalate.
## Language runtime and OS: the hardest case
Python 2 to 3 is the reference: a semantic break across an entire ecosystem, an announcement-to-sunset window of over a decade (2008 announcement, 2015 target, sunset January 1 2020), dedicated tooling, and a hard publicly committed date that is what finally forced completion. If your break is of this class, the guide is one input among many - the date and the ecosystem tooling do most of the work.
Attribution for the figures above:
- The 85% Manifest V3 number and the transition dates come from Google's own Chromium and Chrome-for-Developers posts (May 2024).
- The Python sunset date comes from the Python Software Foundation.
- Expand/contract and online schema-change tooling are documented by the Postgres and MySQL tool projects that implement them.
references/sources.md›
# Sources behind this skill's claims
Every named practice, figure and threshold in this skill traces to one of the entries below. Anything not traceable here is labelled in place as a self-set baseline. Cite these when a colleague asks why the guide is shaped this way - an argument from a published policy ends faster than an argument from taste.
## Guide anatomy and per-change entries
- **React 19 upgrade guide** (react.dev, 2024) - section order (installing, codemods, breaking changes, new deprecations, notable changes, TypeScript changes, changelog), the "upgrade to React 18.3 first to help identify any issues" staged-path instruction, the all-changes codemod recipe, and the explicit statement that the recipe "does not include the TypeScript changes".
- **Prisma v7 upgrade material** - priority tags per change group so a reader triages before reading, a version-to-version comparison table as the scanning surface, runtime and language floors stated before any code change, troubleshooting keyed by error text, and routing an unsupported path out (stay on the old major) instead of half-supporting it.
- **Stripe upgrade documentation** - date-based named versions, the published exhaustive list of changes considered backward-compatible, per-request version pinning for testing before the account default moves, and the separate versioning tracks for server SDK, browser bundle, mobile SDK and webhooks.
## Deprecation policy
- **Django deprecation timeline** - deprecated in N, still working in N+1, removed in N+2 (roughly 12-18 months); the timeline document organised by removal version; security items labelled as an accelerated timeframe rather than silently accelerated.
- **Node.js deprecation levels** - the four-rung ladder (documentation-only, application, runtime, end-of-life), the `DEP0XXX` stable identifiers with per-version history, and the rule that deprecations can be revoked without the identifier being reused.
- **Ember deprecation guides** - every deprecation carries an `until` version and a stable ID so lint rules and tooling can reference one deprecation rather than a document URL.
- **PEP 387** (Python backwards-compatibility policy) - a documented deprecation process with warnings and a removal target before a break lands.
## Ownership and internal gates
- **Ember RFC process** - originated the "How we teach this" required RFC section; the Recommended stage requires the feature to be integrated into the guides and the API documentation polished before the RFC completes.
- **Rust RFC 1636** ("document_all_features") - copied that gate explicitly: stabilization must not proceed until the How We Teach This requirements are fulfilled. A later Rust internals thread admits the changelog half "has not ever been done" - evidence that docs gates need enforcement, not just wording.
- **Kubernetes KEP template** - required `Upgrade / Downgrade Strategy` and `Version Skew Strategy` sections plus a Production Readiness Review; the enhancements freeze removes an incomplete KEP from the release.
- **Technical Writer HQ** - migration guides described as content senior writers inherit, because they are high-risk, cross-team, and "can break production if misunderstood".
## Support windows and version skew
- **Kubernetes version-skew policy** - three most recent minor releases maintained; documented skew limits between components force a staged upgrade order and forbid direct multi-minor jumps. AKS documents the fallout for clusters that violate skew.
- **Node.js release policy** - even-numbered majors historically got 18 months Active LTS plus 12 months Maintenance; from v27 every major becomes LTS after a Current phase. Published as a machine-readable schedule file the tooling ecosystem reads.
- **PostgreSQL versioning policy** - each major supported for five years after initial release, with cross-major upgrade tooling that can bridge several majors in one hop.
- **endoflife.date** - community-run aggregator of EOL dates with an API, consumed by CI and compliance checks.
## Validation
- **`cargo test --doc` and `mdbook test`** (Rust) - code blocks in docs and books compiled and run by the build.
- **Software Engineering at Google, chapter 22** - the Rosie platform "takes a large change and shards it based upon project boundaries and ownership rules into changes that can be submitted atomically"; Chromium's own large-scale-change workflow is the comparable public case.
## Named laws and methods
- **The Churn Rule** - Software Engineering at Google, chapter 1: "infrastructure teams must do the work to move their internal users to new versions themselves or do the update in place, in backward-compatible fashion. This policy, which we've called the 'Churn Rule', scales better." Google adopted it in 2012 after finding that announcing a delete date and pushing migration work onto customers fails as the dependency graph grows.
- **Hyrum's Law** (hyrumslaw.com) - "With a sufficient number of users of an API, it does not matter what you promise in the contract: all observable behaviors of your system will be depended on by somebody."
- **Diátaxis** - the four-mode documentation compass; a migration guide is a how-to (goal-oriented, assumes competence, teaches nothing).
- **Expand/contract**, also called parallel change - the additive-then-destructive schema migration pattern implemented by Postgres and MySQL online schema-change tooling.
## Adoption figures
- **Chrome Manifest V3** - Google blog, May 2024: over 85% of actively maintained extensions on V3, after the December 2022 pause and the republished timeline. A 93% figure attributed to a Google spokesperson in 2025 press coverage is a press quote rather than a Google publication - quote the 85% instead.
- **Kubernetes in production** - Datadog Security Labs, data as of October 2025: 78% of hosts on mainstream-supported versions, 19% extended, 3% unsupported.
- **React** - State of React 2024 and 2025 (Devographics): React 18.x around 78% of version-question respondents in 2024; React 19 around 48% versus React 18 around 41% in 2025.
- **Python 2 sunset** - Python Software Foundation: January 1 2020, with 2.7.18 released April 2020, after the original 2015 target was extended once.
references/validation-and-adoption-metrics.md›
# Validating the guide, and measuring whether it worked
Two separate questions. Validation happens before publish and asks "is this document correct?". Measurement happens after and asks "did people actually move?".
## Validation layer 1 - make the guide's own commands executable in CI
The strongest check is mechanical: the snippets in the guide are compiled and run by the build. Rust is the reference implementation: `cargo test --doc` compiles every code block in doc comments into a doctest binary, and `mdbook test` extends the same treatment to book-length prose.
A broken migration snippet then fails the build the way a broken function would. Equivalents exist elsewhere: Go example tests, `pytest --doctest-modules`, and Markdown-testing crates and packages that run fenced blocks out of a README.
The rationale is anti-rot: if the code changes, the docs break; if the docs break, the build fails. A migration guide is the document that rots fastest, because it is written against a version that is still moving when it is published.
This catches a broken command. It does not catch a broken narrative - an entry that compiles fine but sends the reader down the wrong path.
## Validation layer 2 - the cold walkthrough
Have one person who did not write the guide follow it end to end, with no author hand-holding, on a real project. A support engineer, a new hire, or a design-partner customer all work. This is what surfaces the assumed step, the missing prerequisite, and the command that only works from the repository root.
Measure the elapsed time of that run and publish it as the expected effort. Never publish an effort estimate you have not measured: an underestimate is the fastest way to lose a reader mid-upgrade, because they stop trusting every other number in the document at the same moment.
Private betas and early-access cohorts are the same check run with real consumers instead of one reader: treat the mechanism as proven and the specific ritual as yours to design. Multiple API vendors document this as a named pre-GA validation stage rather than an invented step - Auth0/Okta, Klaviyo, Samsara and GitHub each publish a staged beta/early-access/GA lifecycle explicitly framed around gathering feedback and validating changes before general availability, with breaking-change protections applying only once GA is reached.
## Validation layer 3 - run the codemod against a corpus, not one repository
If the upgrade is mechanizable, the codemod is the thing under test, and one test project is not a sample. Google's large-scale change process is the industrial version: the Rosie platform shards a master change along project and ownership boundaries into pieces that are tested, reviewed and submitted independently.
At that scale, tens of thousands of commits come from the tooling rather than from people. Chromium runs a comparable documented workflow for changes touching many owner directories.
Scale it down honestly: run the codemod across every example app, internal consumer and public dependent you can clone, and record what it left behind. That residue list is the most valuable paragraph in the automation section, because it is the part readers cannot discover until their build breaks.
## Measurement - the version-adoption curve is the primary metric
Everything else is a proxy. Read the curve off package-registry downloads, runtime telemetry, or a version question in an ecosystem survey. Published reference points, useful as calibration for what a healthy curve looks like:
| Case | What was measured | Figure | Source and date |
| -------------------------------- | --------------------------------------------- | -------------------------------------------- | ---------------------------------------------- |
| Chrome Manifest V3 | Actively maintained extensions on V3 | over 85% | Google blog, May 2024 |
| Chrome Manifest V3 (independent) | New uploads on V3 after the pause | roughly nine in ten | arXiv 2404.08310, April 2024 |
| Kubernetes in production | Hosts on supported versions | 78% mainstream, 19% extended, 3% unsupported | Datadog Security Labs, data as of October 2025 |
| React | Daily-driver version among survey respondents | React 18.x about 78% | State of React 2024 |
| React | Post-19 split | React 19 about 48%, React 18 about 41% | State of React 2025 |
Two readings to take from these, not one:
1. A long tail is normal. Even a transition with vendor enforcement behind it sat at 85%, not 100%. Plan the last 15% as a named-account exercise, not as a docs problem.
2. Hard enforcement (brownouts, forced removal, closing the escape hatch) belongs after the curve visibly flattens, not on the date you first announced. Both the Chrome and Kubernetes figures above describe curves that took years to flatten.
## Secondary signals worth tagging to one upgrade
Worth tagging to one upgrade:
- Support-ticket and forum-question volume quoting an upgrade error.
- Docs-page analytics on the guide itself (pageviews, drop-off point, exit rate).
- In-page search queries, which name the entry you failed to write.
- Issue-tracker counts on a migration label, including duplicates.
- Deprecation-warning telemetry trending toward zero before the removal release.
Abandonment shows up as version pinning, rollbacks, and half-finished migrations sitting on branches.
The "was this helpful" widget is low-signal and easily gamed - do not let it stand in for the adoption curve. Page analytics connect to migration outcomes only by inference, so treat those numbers as directional.
Machine-readable support windows (a community EOL aggregator, or a project's own published schedule file) are what downstream CI and compliance checks consume to flag an unsupported version. Publishing yours in that form does more for the long tail than another announcement post.
SKILL.md›
---
name: version-migration-guide
description: Writes or audits the migration guide for a breaking change - what breaks, ordered by blast radius, with a detection signal and before/after code per entry, a deprecation timeline, the codemod's coverage boundary, and verification by a cold upgrade of a real project. Use whenever someone mentions a migration guide, an upgrade guide, a breaking-changes page, a major version bump, "how do we tell users about v3", an API version cutover, a deprecation or sunset notice, or a codemod or compat build - even if they only say they are shipping v2. Covers libraries, SDKs, CLIs, frameworks, hosted APIs and schema runbooks. Not release notes - use samber/developer-relations-skills@changelog-writing.
license: MIT
metadata:
author: Samuel Berthe
version: "1.0.3"
---
# Version Migration Guide
You are a developer-documentation specialist. You produce one artefact: the document that carries a working project from version N to version N+1 with the least reading, the fewest surprises, and no trips into the source code.
A migration guide is a how-to in the Diátaxis sense - goal-oriented, assumes competence, teaches nothing. The changelog says what happened; the guide says what the reader must now do, in the order they must do it. Every sentence that does not change what the reader types is overhead.
## Scope check
Confirm the task before starting. Route elsewhere when:
- The user wants the per-release "what shipped" list → changelog skill.
- The user is deciding the version scheme, the deprecation window length, or the sunset policy → API versioning-policy skill. This skill documents a change that has already been decided.
- The user wants the upgrade performed in their own codebase → that is a consumer-side task, not this skill. This skill writes the document that such a task follows.
- The user wants error pages keyed by message text for everyday failures → samber/developer-relations-skills@developer-troubleshooting-docs. Upgrade-specific errors stay here.
- The reader needs to learn the new version's concepts → tutorial skill.
See the References section for the exact skill identifiers.
## First decide whether a guide is the right deliverable
Sometimes it is not, and two named laws settle it.
- **The Churn Rule** (_Software Engineering at Google_, ch. 1; adopted at Google in 2012 because announcing a removal date and leaving consumers to cope stops scaling as the dependency graph grows): "infrastructure teams must do the work to move their internal users to new versions themselves or do the update in place, in backward-compatible fashion." Where you control the consumers (internal services, a monorepo, a platform you operate), do the migration for them: a codemod plus a short note, not a guide. Write a guide only when the consumers are genuinely other people's codebases.
- **Hyrum's Law** (hyrumslaw.com): "With a sufficient number of users of an API, it does not matter what you promise in the contract: all observable behaviors of your system will be depended on by somebody." Your documented contract is not the blast radius: undocumented behavior, timing, error text and bug-compatibility are all load-bearing for someone, so an inventory built only from the changelog is always short.
State which side of the Churn Rule you are on at the top of your working notes. A guide written for a population you could have migrated yourself hands them work that was yours.
## Pick the guide type
Default to the library/SDK shape this skill describes. Schema, cloud, mobile and browser/platform migrations diverge enough that copying that shape produces the wrong document - a schema migration is a runbook of operational steps with per-step rollbacks, not prose with snippets. Read [./references/guide-types-beyond-libraries.md](./references/guide-types-beyond-libraries.md) whenever the migration is not a plain library or SDK upgrade.
## Interview
Ask one at a time, multiple-choice where you can. Stop as soon as you know the version pair, the surfaces and the audience - do not run the whole list mechanically.
**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 being migrated: a library/SDK, a framework, a CLI, a hosted API, a database schema, a mobile app, or several surfaces that version independently?
2. Which exact version pair does this guide cover, and must it also support multi-version jumps (N-3 → N) or only the single hop?
3. Who upgrades: open-source users choosing their own moment, paying customers under a notice window, or internal teams under a mandate?
4. Is the breaking set already frozen, or is inventorying it part of this work?
5. What automation exists: a codemod, a compat/migration build, a lint rule, a version-pinning header - or nothing?
6. Did version N-1 already emit deprecation warnings for these changes, or does N break cold?
7. Do you have usage telemetry on the deprecated APIs - who is affected and how many?
8. Where will the guide live, and what must it link to (changelog, release notes, support channel, status page)?
9. What date must the guide publish by, relative to the release? A date inside the release rules out an interim warnings release and forces the single hop.
10. Is this one major, or the shape every future major reuses? Compounding promotes the compat build, the RFC gate and stable per-change anchors; a one-off deletes all three.
11. What engineering hours exist for migration tooling, and who maintains it after the release? No named owner deletes the compat build and the codemod from the plan before you cost them.
Ask question 7 even when you expect a no. Telemetry is what turns "order by blast radius" from a guess into a measurement, and its absence is itself worth stating in the guide's plan.
The path-shape and automation rankings below are defaults, not laws. Re-rank both against the answers above and against what the project already owns:
- An interim release already scheduled makes the staged path free.
- An existing codemod corpus makes the codemod free.
Either one moves that option to the top regardless of the default order.
## Workflow
1. **Freeze the scope.** Write the version pair and the surface list at the top of your working notes and treat them as fixed. A guide that quietly grows to cover three version pairs helps nobody through any one of them.
2. **Inventory what breaks.** Pull from every source, not just the changelog: the public-API diff between the two tags, `Removed`/`Changed` changelog entries, the deprecation warnings N-1 emits, the migration commits in your own example apps and internal consumers, and support tickets from early adopters. Classify each item as a removal, a rename or signature change, a behavior or default change, a requirement floor, or a wire/format change.
3. **Size the blast radius.** Rank changes by how many readers hit them, using telemetry, then dependent-repo searches, then the maintainers' estimate. This ranking becomes the guide's order - never alphabetical, never per-module.
4. **Choose the path shape** and state it before anything else in the guide. Three shapes compete; the fourth is imposed. Ranked by reader hours saved per engineering hour you spend building the runway:
- _Staged_ - upgrade to the last minor of N-1 first so deprecation warnings surface at every affected call site, then jump. React's v19 guide is the reference wording: upgrade to 18.3 first, because it is identical to 18.2 apart from warnings for what v19 changes. Costs one interim warnings-only release, and turns every reader's build output into their own personal inventory. **Default whenever N-1 can still ship.**
- _Single hop_ - no runway, near-zero cost to you, and the whole breaking set lands on the reader at once. Take it when the set fits in one sitting, or when N-1 is already frozen and the staged path is no longer purchasable.
- _Incremental with a compat build_ - the project runs half-migrated and ships to production mid-way. A standing job: dual behavior paths, per-feature flags and warnings you maintain until the build is retired.
- _Sequential bridges_ - not ranked against the other three, because a version-skew policy imposes it rather than you choosing it (Kubernetes maintains the three most recent minors and constrains component skew). When such a policy applies, the guide is a chain of N→N+1 hops and the other three shapes are off the table; say so in the first screen. When no policy applies, delete this shape from the menu instead of offering it.
- value: incremental with a compat build > staged > single hop
- engineering plus upkeep effort: incremental with a compat build > staged > single hop
- efficiency: staged > single hop > incremental with a compat build
That order starves the compat build - the shape that saves readers the most work and costs you the most to hold. Promote it above the default when consumers are large applications that cannot park a migration branch against a moving main, which is why frameworks build one and libraries do not. Where the interview named no owner for it after the release, delete it rather than listing it as an option: a compat build nobody maintains strands every project that adopted it half-migrated.
5. **Name any hop that is known-bad.** If a specific intermediate version regresses, say "skip it, go directly to X" with the reason. Readers otherwise follow the sequential rule into a version you know is broken.
6. **Write the floors first.** Runtime and language versions, peer dependencies, database or infrastructure minimums, build-tool requirements. A reader who fails a floor never reaches your code examples, so a floor buried at step 9 wastes their whole session.
7. **Write one entry per breaking change** using the six-part block below. Work down the blast-radius ranking so the reader can stop early and still have handled the changes most likely to affect them.
8. **Publish the deprecation timeline** as a table: what is deprecated now, what warning level it emits, and which version removes it. See [./references/deprecation-timeline-patterns.md](./references/deprecation-timeline-patterns.md).
9. **Document the automation and its boundary.** Give the exact command, the "run everything" recipe, and an explicit statement of what it does _not_ cover. When automation still has to be _built_, rank it by reader hours saved per engineering hour: `search patterns and literal messages > deprecation warnings in N-1 > lint rule > codemod > compat build`. **Default: ship the search patterns and the literal messages**, which you already write as each entry's affected-if line, so they cost nothing extra and unblock both humans and coding agents. See [./references/automated-migration-tooling.md](./references/automated-migration-tooling.md) for what each rung buys and what it starves.
10. **Add a housekeeping section.** After the upgrade works, tell the reader what is now dead: config options that became defaults, workaround flags and dependency-resolution overrides, patches, and packages the framework now bundles. Nobody deletes these on their own, and they become the next upgrade's mystery failures.
11. **Add a troubleshooting section keyed by error text.** Readers arrive mid-upgrade by pasting an error into a search box; the section must match on the string they paste, not on your name for the subsystem.
12. **Verify by cold upgrade run** against the pass threshold below. Fix and rerun until it passes.
13. **Instrument and record.** Define the adoption and deflection metrics (KPIs below). If your environment has persistent memory, store the version pair, the path shape, the deprecation windows and the surface matrix - the next major reuses all four.
## Entry anatomy
Give every breaking change the same six parts, in this order. The six-part shape and its three-sentence rationale cap are this skill's own conventions, not a published template; the order matches how a reader triages: _does this hit me_ → _why_ → _what does it look like_ → _what do I type_.
1. **Verb-prefixed heading with a stable anchor** - `Removed:`, `Changed:`, `Renamed:`, `Deprecated:`. The anchor is a permanent identifier, because lint rules, codemods and support replies link to individual changes, not to the page.
2. **The affected-if line** - the detection signal. Give the search pattern that finds it in a codebase _and_ the exact warning or error text the reader will see. A reader who cannot tell whether a change applies to them reads all of it, slowly.
3. **What changed and why**, in three sentences at most. The why earns compliance; the fourth sentence loses it.
4. **Before/after code**, labeled `// Before` and `// After`, complete enough to paste.
5. **The fix**: the codemod command, or numbered manual steps. Never both a command and a vague "or update manually".
6. **The escape hatch**: the compat flag, the config that restores old behavior, or an explicit "there is none". Saying there is none is information; silence is not.
See [./references/breaking-change-entry-template.md](./references/breaking-change-entry-template.md) for the fill-in template and two worked negative examples alongside the good one.
## Who writes it, and the gate that forces it to exist
The engineer who made the breaking change owns the technical substance; a writer or advocate owns clarity, structure and publication. The guide goes missing in that split - each owner assumes the other is drafting. In docs-as-code projects the migration note ships in the same pull request as the break, which makes the person who broke the API the first drafter by construction.
The stronger fix is a **governance gate that requires the docs before the code can land**. Three projects run one:
- Ember's RFC template requires a "How we teach this" section, and its Recommended stage requires the feature to be integrated into the guides before the RFC completes.
- Rust RFC 1636 copied Ember's gate: stabilization must not proceed until the How We Teach This requirements are fulfilled.
- Kubernetes KEPs require `Upgrade / Downgrade Strategy` and `Version Skew Strategy` sections, and an incomplete KEP is removed from the release at the enhancements freeze.
If the project has an RFC, KEP or design-doc process, propose the section rather than the guide - it converts every future break into a guide automatically. A gate on paper still needs enforcement: a Rust internals thread admits the changelog half of RFC 1636 was never actually done.
## Audience split
The entries themselves are identical for every audience - same detection signals, same before/after, same escape hatches. Three things change.
- **Open-source / self-serve readers** upgrade when they choose to, so the guide competes with doing nothing.
- Open with what the upgrade buys them, in two lines.
- Express the runway in releases rather than dates, and say plainly which version still receives security fixes, and until when.
- Failure mode: a guide that assumes the reader has already decided to upgrade.
- **Commercial / contractual readers** upgrade because a date forces them.
- Lead with the deadline and the notice window.
- Name the support and escalation path inside the guide, not only in the announcement email.
- Give a rollback procedure, and say who performs the cutover on the vendor side and what the customer must do before it.
- Give managed or enterprise deployments their own escape hatch with its own end date, and route the draft through support and legal/comms sign-off before publish.
- Failure mode: a technically perfect guide with no date and no named owner, which enterprise readers cannot schedule against.
- **Internal / mandated readers** need the ownership map more than the motivation.
- State which team owns each affected service, the tracking issue, and the freeze date after which the old version stops being deployable.
- Re-read the Churn Rule before writing this one.
## Surfaces that version independently
A library upgrade is local and opt-in. A hosted API upgrade changes behavior on someone else's running production system, so the guide must add coordination a library guide never needs:
- The pinning mechanism for testing the new version per request, before any default moves.
- A compatibility matrix across server SDK, browser bundle, mobile SDK and webhook payloads.
- The data-layer consequences of changed identifiers or enums.
- The cutover-and-rollback procedure.
When you ship a family of packages that must move together, add the version-consistency check: every package in the family on one version, and any companion library on its documented compatible version. A half-upgraded family produces errors that look like your breaking changes but are not.
Read [./references/api-version-migration.md](./references/api-version-migration.md) when any part of the migration crosses a network boundary you control.
## Machine-readable requirements
Assume a coding agent, not only a human, will execute this guide. That assumption costs nothing and improves it for humans too. This section covers this one page; making the whole documentation set agent-consumable is samber/developer-relations-skills@coding-agent-docs-optimization's job.
- Quote error and warning strings exactly, including punctuation. Agents and search boxes both match on the literal.
- Give complete, runnable snippets with their imports - never a fragment the reader must place correctly.
- Keep one canonical URL per version pair, with stable per-change anchors. A guide that moves each release breaks every link support ever pasted.
- State version floors as machine-checkable comparisons (`Node >= 20.19`), not prose ("a recent Node version").
- Keep the migration path inside the guide. "See the release blog post for context" is fine; "see the blog post for the steps" strands anyone reading programmatically.
- Never bake the moving patch version into prose. Pin the version pair the guide covers and let the changelog carry the current release.
## Invocation examples
**Drafting from scratch.** "We're releasing v3 of our Node client next month, 14 breaking changes, no codemod yet. Write the migration guide." → Run the interview (stop after questions 1-5), then produce: the inventory table classified by change type, the blast-radius ranking with its evidence source, the recommended path shape, and the full guide draft in the library outline. Flag the missing codemod as a decision, not a gap to paper over.
**Auditing an existing guide.** "Here's our v2→v3 upgrade page, support keeps getting tickets about it." → Score the page against the six pass-threshold criteria, list every entry missing a detection signal or before/after block, name the ordering defect if there is one, and hand back a prioritized fix list with the rewritten text for the top three entries.
**Mid-deprecation.** "We deprecated four APIs in 2.4 and want to remove them in 3.0 - what do we publish now?" → Produce the deprecation timeline table plus the removal-oriented page ("what disappears in 3.0"), not a migration guide yet, and state which warning level each item needs in the interim release.
Expected output shape, in every case:
```
1. Assumptions and open questions (what you inferred, what only the user can answer)
2. Inventory table: change | type | blast radius | evidence
3. Recommended path shape, with the reason in one line
4. The guide itself, in the outline from references/guide-outline-examples.md
5. Verification plan: the cold-run project, who runs it, what gets measured
6. KPI list with where each number comes from
```
Deliver sections 1-3 for validation before writing section 4 when the breaking set is large or contested. Rewriting fourteen entries because the ordering was wrong costs more than one round-trip.
## Pass threshold
Verify by running a cold upgrade: take a real project on version N (an example app, an internal consumer, or a community project) and upgrade it using only the guide, with someone who did not write the guide at the keyboard. It ships when all six hold. These six are this skill's own bar, not an industry standard; they are absolute because each failure is silent for the author and expensive for the reader.
1. **Completeness.** Every removed or behavior-changed public symbol in the tag-to-tag diff has an entry. Diff the guide against the API diff; do not trust the changelog as the inventory. Hyrum's Law means even that diff undercounts, so add anything support has already seen break.
2. **Detectability.** Every entry carries a search pattern and the exact error or warning text.
3. **Sufficiency.** The upgrade completes with no edit the guide does not describe. Any undocumented edit is a defect, not a reader failure.
4. **Automation honesty.** Every codemod claimed has been run on the test project, and the residue it leaves is named in the guide. Typecheck and test suite pass afterwards.
5. **Path validity.** If multi-hop jumps are promised, at least the oldest promised version has been run through the full path.
6. **Self-containment.** The reader never opens the source, the changelog or the issue tracker to know what to do next.
Wire the guide's commands into CI where your ecosystem supports executable documentation (Rust's `cargo test --doc` and `mdbook test` are the reference implementation), so a snippet that stops compiling fails the build instead of rotting quietly. Measure the elapsed time of the cold run and publish it as the expected effort; never claim a duration you have not measured. See [./references/validation-and-adoption-metrics.md](./references/validation-and-adoption-metrics.md).
When completeness fails on a large surface, split the guide per module rather than dropping entries, and shard the verification the same way. Google's Rosie platform is the industrial precedent: it shards one large change along ownership boundaries so each piece is tested, reviewed and submitted independently (_Software Engineering at Google_, ch. 22). A partial guide silently teaches readers that the guide is not authoritative.
## KPIs
Instrument in this order - value per hour of wiring, not the order they get reported in.
- **Tickets per thousand upgrades** that quote an upgrade error, and which change generates most of them. Free: support already collects them. That change's entry is the one to rewrite.
- **Deprecation-warning telemetry** trending toward zero before the removal release. An hour of aggregation on plumbing the staged path already required. If it is flat a month out, the removal date is wrong or the guide is not reaching people.
- **Codemod run rate** versus manual migration, where the tooling reports it - free where it does, unavailable where it does not.
- **Guide behavior**: in-page search for error strings and exits to the support channel, both signals of a missing or unfindable entry. One hour of docs analytics.
- **Adoption curve**: share of active installs or accounts on N+1 at 30/60/90 days, compared with the previous major's curve - the only metric that says the migration worked, and a standing job since it needs install or account telemetry joined across ninety days. A long tail is normal, even for transitions with strong platform enforcement behind them; plan the last ~15% as named accounts to work directly, not as a docs problem. See [./references/validation-and-adoption-metrics.md](./references/validation-and-adoption-metrics.md) for the published curves this skill's 15% cut-off is calibrated against.
- **Stragglers**: named accounts or top dependents still on N after the deadline - the list your support and success teams work, not a percentage.
The adoption curve and the straggler list tie on effort and are bought together: the same identity join produces both, the curve being the aggregate and the list its tail, so neither exists before that join does. Codemod run rate and guide behavior tie on effort for a different reason - each is an hour of reading a system someone else already runs, and each answers a question about one artefact rather than about the migration.
That order starves the adoption curve, the highest-value number on the page. Promote it above everything when a removal date has to be defended to customers or to leadership, and start the join before the release ships: begun afterwards, the first ninety days are already unmeasurable.
Start hard enforcement (brownouts, forced removal, closing the enterprise escape hatch) only once the curve visibly flattens. Published transitions of this kind consistently take years, not months, to get there - see the reference above for the curves.
## Failure modes
| Symptom | Fix |
| --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| Guide is the changelog with a new title | Rewrite each item as an action the reader takes; delete anything that changes nothing for them |
| Changes ordered by module or alphabetically | Reorder by blast radius; the change everyone hits goes first |
| "Update your usage accordingly" with no code | Add the before/after block; if you cannot write one, the change is not understood well enough to publish |
| Reader cannot tell whether a change applies | Add the affected-if line with a search pattern and the literal warning text |
| Deprecation removed without ever warning at runtime | Escalate the warning level in an interim release before removal; if it is too late, say so and extend support for the old version |
| Multi-version jump unsupported but not mentioned | State the supported hops; give the ordered path for older versions or route them to sequential upgrades |
| Guide rewritten in place each release | One canonical page per version pair, kept online with stable anchors |
| Enterprise reader has no date to plan against | Publish the deadline, the notice window and the rollback story |
| Guide names the vendor tooling in every step | Describe the capability; keep tool names in one integration note that can be updated alone |
| Effort estimate published without a measured run | Run the cold upgrade and publish that number, or publish none |
| Schema change written as prose with snippets | Rewrite as a runbook with per-step rollbacks; see the guide-types reference |
| Unsupported path half-supported | Route those readers out explicitly ("stay on 2.x, here is why") instead of leaving them in the main flow |
## References
- See [./references/breaking-change-entry-template.md](./references/breaking-change-entry-template.md) for the per-change block template and good/bad worked pairs.
- See [./references/guide-outline-examples.md](./references/guide-outline-examples.md) for the full page skeletons (library/SDK and hosted API) plus a negative outline.
- See [./references/deprecation-timeline-patterns.md](./references/deprecation-timeline-patterns.md) for warning-level ladders, release-count windows and the timeline table format.
- See [./references/automated-migration-tooling.md](./references/automated-migration-tooling.md) for codemod ecosystems, compat builds and coverage-boundary wording.
- See [./references/api-version-migration.md](./references/api-version-migration.md) for hosted-API specifics: pinning, surface matrices, webhooks, cutover and rollback.
- See [./references/guide-types-beyond-libraries.md](./references/guide-types-beyond-libraries.md) for schema runbooks, cloud/platform, mobile and browser-API migrations.
- See [./references/validation-and-adoption-metrics.md](./references/validation-and-adoption-metrics.md) for executable-docs CI, cold walkthroughs, codemod corpus testing and adoption benchmarks.
- See [./references/sources.md](./references/sources.md) for the published policies, figures and named methods behind every claim above.
- samber/developer-relations-skills@developer-tutorial (when the reader must learn the new version's concepts, not just port their code)
- samber/developer-relations-skills@tech-press-relations (press handling for a major version with breaking changes)
- samber/developer-relations-skills@oss-release-policy (the deprecation window, support end dates and breaking-change definition this guide applies)
- samber/developer-relations-skills@developer-docs-structure-audit (where the guide belongs in the docs site)
- samber/developer-relations-skills@docs-versioning-strategy (whether the old version's docs stay live, frozen or archived once the guide ships)
- samber/developer-platform-skills@api-versioning-policy (choosing the version scheme, deprecation windows and sunset timelines)