History

Drop release-notes archiving — single rolling next.md (#493)

Other CLIs in this space don't archive release notes per-version into the repo: - opencode generates GitHub Releases bodies from git log + AI at release time; nothing committed. - codex uses git-cliff against conventional commits; root CHANGELOG.md redirects to GitHub Releases. - openclaw appends to a single CHANGELOG.md with a `## Unreleased` section (no rename ritual). We were the outlier with the rename-`next.md`-to-`v<version>.md` post-release ritual. It was easy to forget and didn't add value: historical release bodies are already preserved on GitHub Releases (durable, indexed, linkable).

Rhys Sullivan committed 6 months ago

Commit 4d0709b1 parent d8be39b3 files · +29 −29Browse files

Unified · Split

3 files
  1. .skills/cli-release/SKILL.md +12 −10
  2. RELEASING.md +13 −12
  3. apps/cli/src/release.ts +4 −7
.skills/cli-release/SKILL.md+12−10
@@ -37,10 +37,11 @@ The owner doesn't want GitHub's auto-generated "PR title by @user" list. Release · 36 unmodified lines
37**`apps/cli/release-notes/next.md` is the canonical user-facing changelog.** Per-package workspace `CHANGELOG.md` files were removed in late 2026 — they were empty stubs and changesets does not regenerate them under `changelog: false`. Don't create them.37**`apps/cli/release-notes/next.md` is the canonical user-facing changelog.** Per-package workspace `CHANGELOG.md` files were removed in late 2026 — they were empty stubs and changesets does not regenerate them under `changelog: false`. Don't create them.
38 38
39### How it's wired39### How it's wired
40`apps/cli/src/release.ts:198` picks the notes file in this order:40`apps/cli/src/release.ts` reads `apps/cli/release-notes/next.md` and uses
411. `apps/cli/release-notes/v<version>.md` (archived per release)41its contents as the GitHub Release body. If the file is missing or empty,
422. `apps/cli/release-notes/next.md` (rolling draft)42falls back to `gh release create --generate-notes`. There's no
433. Fall back to `gh release create --generate-notes`43per-version archive in the repo — historical release bodies live on
44GitHub Releases (durable, indexed, linkable).
44 45
45### Writing conventions46### Writing conventions
46Structure release-notes files as:47Structure release-notes files as:
@@ -83,12 +84,13 @@ Do not `Thanks` maintainers, bots, or the repo owner — the lint script rejects · 36 unmodified lines
83- The `.changeset/*.md` body can be a one-liner pointing at the release-notes section it expands; users read the GitHub release body, not the changeset.84- The `.changeset/*.md` body can be a one-liner pointing at the release-notes section it expands; users read the GitHub release body, not the changeset.
84- Frontmatter is `"executor": patch` (or `minor`/`major` if owner says so).85- Frontmatter is `"executor": patch` (or `minor`/`major` if owner says so).
85 86
86### Post-release archival (manual)87### Starting a new release cycle
87After a release publishes, rename `apps/cli/release-notes/next.md` → `apps/cli/release-notes/v<version>.md` and commit on `main` (or in a small follow-up PR). This:88There's no post-release rename step. When you start work on the next
88- Preserves the historical notes file-by-version (matches `release.ts`'s preferred lookup path).89release, replace the existing `next.md` content with new entries — the
89- Resets `next.md` absence so the next cycle starts from a blank file — no chance of last release's content leaking into the next one.90previous cycle's content is already preserved on the matching `vX.Y.Z`
90 91GitHub Release page, so overwriting locally is safe. If `next.md` content
91`release.ts` falls back to `next.md` if `v<version>.md` is missing, so skipping the rename won't break a publish; it just leaves stale content in `next.md` for the next release.92looks stale (i.e. mentions features already shipped), that's the signal
93to clear it.
92 94
93## Beta release flow95## Beta release flow
94 96
RELEASING.md+13−12
@@ -54,18 +54,19 @@ To pack the `@executor-js/*` library packages without publishing: · 53 unmodified lines
54 54
55## Release notes55## Release notes
56 56
57User-facing release notes live in `apps/cli/release-notes/`:57User-facing release notes live at `apps/cli/release-notes/next.md` —
58 58one rolling file. **This is the single source of truth users see.** Edit
59- `next.md` — rolling draft for the next release. **This is the single59it whenever you ship a user-visible change.
60 source of truth users see.** Edit it whenever you ship a user-visible60
61 change.61`apps/cli/src/release.ts` reads `next.md` and uses its contents as the
62- `v<version>.md` — archived per-release snapshots. After a release62GitHub Release body. If the file is missing or empty it falls back to
63 publishes, rename `next.md` to `v<version>.md` so the next cycle starts63`gh release create --generate-notes` (auto-generated from PR titles).
64 blank.64
65 65There's no per-version archive in the repo — historical release bodies
66`apps/cli/src/release.ts` reads `v<tag>.md` first, falls back to66live on GitHub Releases (durable, indexed, linkable). When you start a
67`next.md`, and only invokes `gh release create --generate-notes` if both67new release cycle, replace the existing `next.md` content with your new
68are absent.68entries; the previous cycle's content is already preserved on the
69matching `vX.Y.Z` release page.
69 70
70### Authoring rules71### Authoring rules
71 72
apps/cli/src/release.ts+4−7
@@ -195,13 +195,10 @@ const syncGitHubRelease = async (input: { · 194 unmodified lines
195 return;195 return;
196 }196 }
197 197
198 const versionedNotes = resolve(cliRoot, "release-notes", `${input.tag}.md`);198 // Single rolling release-notes file. Historical release bodies live on
199 const fallbackNotes = resolve(cliRoot, "release-notes", "next.md");199 // GitHub Releases — we don't archive per-version copies in the repo.
200 const notesFile = existsSync(versionedNotes)200 const notesPath = resolve(cliRoot, "release-notes", "next.md");
201 ? versionedNotes201 const notesFile = existsSync(notesPath) ? notesPath : null;
202 : existsSync(fallbackNotes)
203 ? fallbackNotes
204 : null;
205 202
206 const args = [203 const args = [
207 "release",204 "release",