$npx -y skills add gaia-react/gaia --skill release-notesMaintainer-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/pages/changelog/releases/ plus an editorial-decisions report for human review. Us
| 1 | # release-notes |
| 2 | |
| 3 | Translate one version's CHANGELOG entries into adopter-facing release notes. The CHANGELOG is written for GAIA's own contributors: terse, imperative, full of internal mechanics and PR numbers. Adopters read the website. They don't care that an ADR was reframed or a memory was promoted; they care what GAIA now does for _their_ project. This skill is the translation layer between the two audiences. |
| 4 | |
| 5 | **Maintainer-only.** Adopters never release GAIA, so this skill ships nowhere, it's excluded from the distribution tarball by `.gaia/release-exclude` (category 1), the same as `/gaia-release`. It pairs with `/gaia-release` but runs independently: you can point it at the `[Unreleased]` block while cutting a release, or at any historical `## [x.y.z]` block to backfill the website. |
| 6 | |
| 7 | **It never edits `CHANGELOG.md`.** The changelog stays technical and precise, that's its job. This skill only _reads_ it. |
| 8 | |
| 9 | ## Inputs |
| 10 | |
| 11 | 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: |
| 12 | |
| 13 | - **Graduated / historical**: `CHANGELOG.md` contains `## [<version>], <YYYY-MM-DD>`. Translate that block. Take `version` and `date` **verbatim from the header**. The release already happened on that date; the shell clock is irrelevant. |
| 14 | - **Live cut**: the version isn't graduated yet; its entries live under `## [Unreleased]`. Translate that block. `version` is the argument (the maintainer is cutting it now). `date` is **today from the shell clock** (`date +%F`). |
| 15 | |
| 16 | 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. |
| 17 | |
| 18 | ### The date is never yours to invent |
| 19 | |
| 20 | 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. This is the same discipline GAIA enforces for wiki playbook dates. |
| 21 | |
| 22 | ## Output (a): the release data file |
| 23 | |
| 24 | 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. |
| 25 | |
| 26 | **The schema lives in `../website/src/pages/changelog/types.ts` (the `Release` type), read it as the source of truth before writing.** As of this writing it is: |
| 27 | |
| 28 | ```ts |
| 29 | export type Release = { |
| 30 | added?: string[]; // "New" |
| 31 | date: string; // ISO yyyy-mm-dd |
| 32 | fixed?: string[]; // "Fixed" |
| 33 | headline?: string; |
| 34 | improved?: string[]; // "Improved" |
| 35 | summary?: string; // freeform fallback for legacy/coarse entries |
| 36 | version: string; // semver, no leading 'v' |
| 37 | }; |
| 38 | ``` |
| 39 | |
| 40 | **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: |
| 41 | |
| 42 | ```ts |
| 43 | export default { |
| 44 | added: ['...'], |
| 45 | date: '2026-06-02', |
| 46 | fixed: ['...'], |
| 47 | headline: 'A short title or one plain sentence.', |
| 48 | improved: ['...'], |
| 49 | version: '1.4.0', |
| 50 | }; |
| 51 | ``` |
| 52 | |
| 53 | - `added` → "New" (capabilities that didn't exist before). `improved` → "Improved" (changed/enhanced behavior). `fixed` → "Fixed". |
| 54 | - `headline`, **required for any bucketed release** (one or more `added`/`improved`/`fixed` items). 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 no `headline` ships titleless (this is exactly how `1.1.0` lost its title). A grab-bag with no single story |