release-notes
Maintainer-only. Translate a version's GAIA CHANGELOG entries into plain-language public release notes for the marketing site (gaiareact.com). Writes a release-data `.ts` file under `../website/src/pa
SKILL.md.claude/skills/release-notes/SKILL.md
GitHubrelease-notes
Translate one version's CHANGELOG entries into adopter-facing release notes. The CHANGELOG is written for contributors (terse, internal mechanics, issue numbers); adopters read the website and care what GAIA now does for their project.
Contents: Pre-flight: Worktree check; Inputs; Output (a): the release data file; Output (b): the editorial-decisions report; The translation contract; Worked exemplars; Workflow; See.
It pairs with /gaia-release but runs independently: point it at the [Unreleased] block while cutting a release, or at any historical ## [x.y.z] block to backfill the website.
It never edits CHANGELOG.md; it only reads it.
Pre-flight: Worktree check
This skill writes into the sibling website checkout, resolved relative to the main checkout. If invoked from a linked worktree, reject hard: gaia_refuse_if_worktree (.gaia/scripts/main-only-lib.sh) asks the shared resolver which tree this is and refuses out loud, naming the main checkout, when the answer is a worktree.
Detection (run this first, before anything else):
. .gaia/scripts/main-only-lib.sh
gaia_refuse_if_worktree "/release-notes" || exit 1
Inputs
Invocation carries a target version, e.g. release-notes 1.4.0 (no leading v). Resolve which CHANGELOG block to translate, and where the date comes from, by that version:
- Graduated / historical:
CHANGELOG.mdcontains## [<version>] - <YYYY-MM-DD>. Translate that block. Takeversionanddateverbatim from the header. The release already happened on that date; the shell clock is irrelevant. - Live cut: the version isn't graduated yet; its entries live under
## [Unreleased]. Translate that block.versionis the argument (the maintainer is cutting it now).dateis today from the shell clock (date +%F).
If no version was supplied and an [Unreleased] block exists, ask the maintainer which version is being cut, the version label has to come from a human, not a guess.
The date is never yours to invent
Whether historical or live, the date comes from the CHANGELOG header or the shell, never from your own notion of "today." Models routinely misdate by months; a wrong date silently ships a wrong timeline to every visitor. For a live cut, run date +%F and use exactly that.
Output (a): the release data file
Write to ../website/src/pages/changelog/releases/<version>.ts (version with no leading v; mkdir -p the directory if it's missing). The changelog page auto-discovers every file via import.meta.glob('../releases/*.ts') and sorts by version, so dropping the file in is all that's needed, there's no index or import list to update.
The schema lives in ../website/src/pages/changelog/types.ts (the Release type), read it as the source of truth before writing.
Match the file form of the existing release files, don't trust this skill's literal shape. The website's convention drifts (export style and key order have both changed). Before writing, open the newest releases/*.ts and mirror it exactly: types.ts gives you the fields, a live sibling file gives you the serialization. Today that form is a bare default export with keys in alphabetical order (the formatter sorts them). Omit any optional key with no content:
export default {
added: ['...'],
date: '2026-06-02',
fixed: ['...'],
headline: 'A short title or one plain sentence.',
improved: ['...'],
version: '1.4.0',
};
added→ "New" (capabilities that didn't exist before).improved→ "Improved" (changed/enhanced behavior).fixed→ "Fixed".headline, required for any bucketed release (one or moreadded/improved/fixeditems). A short title or one plain sentence, leading with the release's reason to exist. The buckets render with no title of their own, so a bucketed card with noheadlineships titleless. A grab-bag with no single story still gets aheadline: use a descriptive umbrella line naming the two or three biggest items ("Built-in a11y testing, dead-code detection, and stories as tests"), never nothing. The only way to omitheadlineis to let asummarycarry the lead instead (see below). For a newly-shipped capability, carry the rule 1nowinto the headline too, "GAIA apps now ship with a CSP", not "ship with".summaryvs buckets, mutually exclusive. The renderer showssummaryas a single paragraph instead of theadded/improved/fixedlists. The choice is driven by how many distinct adopter-facing items survive the cut, not by the release's age or whether it's a backfill. Use buckets whenever one or more itemizable adopter-facing changes remain; each lands as its own bullet under New/Improved/Fixed. Reservesummaryfor: (1) an internal-only release where every change was dropped as maintainer plumbing, a one-line "no adopter-facing changes" note (e.g. "Internal release-tooling fixes. No change to how GAIA apps behave."); (2) a genuine one-sentence story; or (3) a broad inaugural / overview release where a narrative reads better than a long list (the1.0.0entry). Never set both.- Bullet strings are markdown-lite: inline code in backticks, links as
[text](url)(rendered as a new-tab link). Entry-reference links are optional and are built from the trailing(#N)the entry carries:https://github.com/gaia-react/gaia/issues/Nfor the issue an entry closes,https://github.com/gaia-react/gaia/pull/Nfor an entry carrying a pull-request number. Include one only when a reader would genuinely follow it for detail, never as decoration. The exemplars below omit them.
Output (b): the editorial-decisions report
After writing the file, print a report to the terminal (don't write a file, this is a human gate the maintainer reads before accepting the notes). It makes your editorial judgment auditable. Use this structure:
## Release notes, v<version> (<date>)
Wrote ../website/src/pages/changelog/releases/<version>.ts
**Dropped** (with reason)
- <changelog line>, <why: pure-internal / housekeeping / docs-only>
**Consolidated**
- <N changelog lines about X> → one <bucket> item
**Needs a human ruling** (ambiguous actor, see rule 6)
- <line>, feature or housekeeping? <why it's ambiguous>
Omit a section if it's empty. Never drop something silently: if it didn't make the notes, it appears under Dropped or Needs a human ruling.
The translation contract
This is the substance of the skill, the rules that turn a contributor's changelog into an adopter's release notes.
- Restate at capability/impact altitude, with a concrete subject. Name the component the adopter touches, CI, Code Review Audit,
/update-gaia, the quality gate. "what changed and what it means for me," not "which function moved." The scaffolded React app is GAIA apps (apps built from the template); effects on the developer's own code or files are your … (your utility classes, yourpackage.json). So "the app ships with a CSP" becomes "GAIA apps now ship with a CSP." - Be more specific, not less. Plain language is not vague language. One dense technical line often splits into two clear ones. Resist the urge to compress meaning out.
- Every bullet names what changed and its observable result. "Optimized" says optimized what, with what result; a bullet a reader can't act on or verify is noise, so cut or rewrite it. Frame the lead descriptively ("Maintenance release", "Security release"), never by semver tier, which the version number already conveys.
- Drop changes with no adopter relevance, judge by relevance, not residence. The test is whether an adopter building their own product would notice or benefit, not whether the changed file physically ships to their machine. A hook, guard, or workflow can ship to every adopter and still be maintainer-only in effect. Drop: ADR reframes, leak-check tweaks, internal status stamping, test-harness changes, docs-only edits, and fixes to GAIA's own development and release plumbing, sibling-repo push handling, release lockstep, contributor-only guard edge cases. The adopter's product behaves identically with or without them. Trap: "strip surrounding quotes from literal
git -Ccaptures so a quoted sibling-repo push is recognized as foreign" ships inside an adopter hook, yet only fires when you push across GAIA's own sibling repos during a release, drop it. (Contrast: a change to the Code Review Audit an adopter runs before every merge is adopter-facing, residence aside, an adopter who runs it sees the benefit, so keep it.) - Drop maintainer housekeeping on GAIA's own repo/wiki. Promoting memories, reorganizing the wiki, dated audit artifacts, version-stamp bumps. These change GAIA's own state, not what GAIA does for an adopter.
- Flag ambiguous actors, don't guess. Imperative/passive changelog voice often hides the subject. "promote machine-local feedback memories into shared wiki" reads like a feature ("GAIA now promotes memories") but was the maintainer running
/gaia-cleanseon GAIA's own wiki, housekeeping, drop it. When a line could mean "GAIA now does X" (keep) or "I did X to the repo" (drop) and the voice won't tell you which, put it under Needs a human ruling rather than picking. Guessing wrong either invents a feature or buries a real one. - Consolidate scattered lines about one subject. Four Code-Review-Audit lines describing one coherent change become one item. The adopter wants the story, not the commit-by-commit trail.
- Keep breaking changes and migrations, plainly framed, with a pointer. Plain does not mean painless. State the breakage in adopter terms, then point to the exact steps: "See the full changelog for exact steps" (or link the entry). Dropping the scary part to sound friendly is the one unforgivable edit.
- House style: no em-dashes, present tense. Use colons, commas, periods. The raw CHANGELOG violates both freely (em-dashes everywhere, "was changed from"), so never pipe a line through verbatim, always rewrite.
- Bucket mapping.
### Added→added("New").### Changed→improved("Improved").### Fixed→fixed("Fixed").### Removed→improved("Improved"): the website has no "Removed" bucket, and a removal that survives the cut reads to an adopter as a cleanup or simplification, but see rule 11, most convention-anchored removals are reframed or dropped, not bucketed verbatim. A**BREAKING:**or Migration sub-bullet stays in whatever bucket its parent lives in (usuallyimproved). - A
/update-gaia-automated cleanup is not a manual step. When a### Removed/### Changedentry carries the adopter-action convention anchor (an Action required: line and/or a literalpnpmcommand, seeCHANGELOG.md) and the action reduces to a single mechanical command/update-gaiasurfaces for the adopter (apnpm remove/pnpm install-style cleanup), do not present it as a chore the reader must perform. Either reframe at impact altitude, naming the cleanup as a benefit and noting the tooling handles it ("GAIA dropped the vestigialreact-router-domshim;/update-gaiacleans it up for you"), or drop it as no adopter-behavior-change. Log the decision in Output (b): a drop goes under Dropped with the reason "agent-automated by/update-gaia"; a reframe is visible in the notes themselves. Never a silent cut. Guardrail (does not erode rule 8): a genuine breaking change or hand-migration, anything the agent cannot do for the adopter (editing their code, resolving a conflict patch, a real API break), is still kept, plainly framed, with a pointer to the exact steps. The anchor is the discriminator: a one-command cleanup is automatable (reframe/drop); an Action required: that demands human code changes is a migration (keep). When you cannot tell which from the entry, route it to Needs a human ruling, never guess.
Worked exemplars
These are the calibration. Study how dense, internal-sounding changelog blocks collapse into a few adopter-meaningful bullets, and what gets cut. They show the editorial result and house style.
1.3.5, seven Added, five Changed, four Fixed → two / three / one
export default {
added: [
'Dependency supply-chain hardening: new package versions are quarantined for 7 days before GAIA installs them, and downgrades are blocked, defending against a compromised fresh release.',
'GAIA apps now ship with a Content-Security-Policy that restricts which scripts can run, hardening them against script injection.',
],
date: '2026-06-02',
fixed: [
"`Form/Chain` merges class names with `twMerge`, so your utility classes override the component's defaults instead of conflicting with them.",
],
headline: 'Faster, cheaper CI and a hardened dependency supply chain.',
improved: [
'Code Review Audit on CI is now opt-in and installs on demand. When it runs, it only audits changes since the last green run instead of the whole codebase, so CI is faster and cheaper.',
'React Router v8 future flags are enabled so your app is ready for the v8 upgrade early. One flag (`v8_passThroughRequests`) changes how loaders and actions receive the request: if you have customized `app/root.tsx`, a small migration is needed. See the full changelog for exact steps.',
'Kickoff prompts print to the terminal instead of silently copying to your clipboard.',
],
version: '1.3.5',
};
Report for 1.3.5:
**Dropped** (with reason)
- stamp GAIA-Audit status on out-of-scope skips, internal status stamping, no adopter effect
- clarify the gate owns formatting; make PR merge marker-first, internal workflow mechanics
- document useDebounce return semantics, docs-only
- detect orphaned wiki drift in preflight via suggested_base, internal release tooling
- recover the un-evaluated window on sync re-anchor, internal wiki-sync mechanics
- gaia-release updates softwareVersion, maintainer-only release tooling
**Consolidated**
- four Code Review Audit / CI lines (install on demand, honor both tokens, gate on since-last-green delta, incremental scope, opt-in) → one "Improved" item
- two pnpm supply-chain lines (minimumReleaseAge quarantine, no-downgrade trustPolicy) → one "New" item
1.4.0, note the housekeeping drop (rules 5/6)
export default {
date: '2026-06-02',
fixed: [
'`/update-gaia` no longer re-adds files you intentionally deleted.',
'`/update-gaia` only raises conflicts for files that actually changed in the release, instead of flooding you with spurious patches.',
'Wiki entries take their dates from your system clock instead of the model, so timestamps are always correct.',
],
headline: '`/update-gaia` stops fighting you over your own files.',
version: '1.4.0',
};
Report for 1.4.0:
**Dropped** (with reason)
- promote machine-local feedback memories into shared wiki, reads like a feature but was the maintainer running /gaia-cleanse, a content action on GAIA's own wiki, not adopter-visible behavior (rule 5/6)
When summary fits, internal-only or one-line releases
A release with no distinct adopter-facing items left after the cut, an internal-only patch where everything was dropped as maintainer plumbing, or a genuinely one-sentence story, collapses to a single summary paragraph. This is about item count, not age: a backfilled old release with two real adopter changes still earns buckets. The shape, for an internal-only patch like 1.1.1:
export default {
date: '2026-05-11',
headline: 'GAIA release fixes',
summary:
'Fix to the gaia-release maintainer reference, plus follow-ups from live init runs.',
version: '1.1.1',
};
Workflow
- Resolve the version → block → date (Inputs above). For a live cut, run
date +%Fnow. - Read
../website/src/pages/changelog/types.tsfor the currentReleasefields and the newestreleases/*.tsfor the current file form (export style + key order). Then read the version's block fromCHANGELOG.md. Classify every line: keep, drop (rule 4/5), consolidate-with-siblings (rule 7), reframe-or-drop a/update-gaia-automated cleanup (rule 11), or ambiguous (rule 6). - Translate the kept lines through the contract. Group by bucket (rule 10). Merge consolidations.
- Draft the
headlineper Output (a), or asummaryin place of buckets when one of Output (a)'s threesummarycases applies. - Assemble the
.tsin the exact form of the file you read in step 2 (today: bare default export, keys alphabetical). Omit empty optional keys. Sweep for em-dashes and for bullets with no observable result before writing (rule 3/9). mkdir -p ../website/src/pages/changelog/releasesand write<version>.ts. No index to update, the page globs the directory.- Print the editorial-decisions report. Every changelog line that didn't survive appears under Dropped or Needs a human ruling, no silent cuts.
See
.claude/commands/gaia-release.md, the release process this feeds: Step 14 invokes this skill for the changelog page entry and locksteps the other website version sites.CHANGELOG.md, the source.