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).
Unified · Split
| @@ -37,10 +37,11 @@ The owner doesn't want GitHub's auto-generated "PR title by @user" list. Release · 36 unmodified lines | ||
| 37 | 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 | 39 | ### How it's wired |
| 40 | −`apps/cli/src/release.ts:198` picks the notes file in this order: | |
| 41 | −1. `apps/cli/release-notes/v<version>.md` (archived per release) | |
| 42 | −2. `apps/cli/release-notes/next.md` (rolling draft) | |
| 43 | −3. Fall back to `gh release create --generate-notes` | |
| 40 | +`apps/cli/src/release.ts` reads `apps/cli/release-notes/next.md` and uses | |
| 41 | +its contents as the GitHub Release body. If the file is missing or empty, | |
| 42 | +falls back to `gh release create --generate-notes`. There's no | |
| 43 | +per-version archive in the repo — historical release bodies live on | |
| 44 | +GitHub Releases (durable, indexed, linkable). | |
| 44 | 45 | |
| 45 | 46 | ### Writing conventions |
| 46 | 47 | Structure 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 | 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 | 85 | - Frontmatter is `"executor": patch` (or `minor`/`major` if owner says so). |
| 85 | 86 | |
| 86 | −### Post-release archival (manual) | |
| 87 | −After 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: | |
| 88 | −- Preserves the historical notes file-by-version (matches `release.ts`'s preferred lookup path). | |
| 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. | |
| 90 | − | |
| 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. | |
| 87 | +### Starting a new release cycle | |
| 88 | +There's no post-release rename step. When you start work on the next | |
| 89 | +release, replace the existing `next.md` content with new entries — the | |
| 90 | +previous cycle's content is already preserved on the matching `vX.Y.Z` | |
| 91 | +GitHub Release page, so overwriting locally is safe. If `next.md` content | |
| 92 | +looks stale (i.e. mentions features already shipped), that's the signal | |
| 93 | +to clear it. | |
| 92 | 94 | |
| 93 | 95 | ## Beta release flow |
| 94 | 96 | |
| @@ -54,18 +54,19 @@ To pack the `@executor-js/*` library packages without publishing: · 53 unmodified lines | ||
| 54 | 54 | |
| 55 | 55 | ## Release notes |
| 56 | 56 | |
| 57 | −User-facing release notes live in `apps/cli/release-notes/`: | |
| 58 | − | |
| 59 | −- `next.md` — rolling draft for the next release. **This is the single | |
| 60 | − source of truth users see.** Edit it whenever you ship a user-visible | |
| 61 | − change. | |
| 62 | −- `v<version>.md` — archived per-release snapshots. After a release | |
| 63 | − publishes, rename `next.md` to `v<version>.md` so the next cycle starts | |
| 64 | − blank. | |
| 65 | − | |
| 66 | −`apps/cli/src/release.ts` reads `v<tag>.md` first, falls back to | |
| 67 | −`next.md`, and only invokes `gh release create --generate-notes` if both | |
| 68 | −are absent. | |
| 57 | +User-facing release notes live at `apps/cli/release-notes/next.md` — | |
| 58 | +one rolling file. **This is the single source of truth users see.** Edit | |
| 59 | +it whenever you ship a user-visible change. | |
| 60 | + | |
| 61 | +`apps/cli/src/release.ts` reads `next.md` and uses its contents as the | |
| 62 | +GitHub Release body. If the file is missing or empty it falls back to | |
| 63 | +`gh release create --generate-notes` (auto-generated from PR titles). | |
| 64 | + | |
| 65 | +There's no per-version archive in the repo — historical release bodies | |
| 66 | +live on GitHub Releases (durable, indexed, linkable). When you start a | |
| 67 | +new release cycle, replace the existing `next.md` content with your new | |
| 68 | +entries; the previous cycle's content is already preserved on the | |
| 69 | +matching `vX.Y.Z` release page. | |
| 69 | 70 | |
| 70 | 71 | ### Authoring rules |
| 71 | 72 | |
| @@ -195,13 +195,10 @@ const syncGitHubRelease = async (input: { · 194 unmodified lines | ||
| 195 | 195 | return; |
| 196 | 196 | } |
| 197 | 197 | |
| 198 | − const versionedNotes = resolve(cliRoot, "release-notes", `${input.tag}.md`); | |
| 199 | − const fallbackNotes = resolve(cliRoot, "release-notes", "next.md"); | |
| 200 | − const notesFile = existsSync(versionedNotes) | |
| 201 | − ? versionedNotes | |
| 202 | − : existsSync(fallbackNotes) | |
| 203 | − ? fallbackNotes | |
| 204 | − : null; | |
| 198 | + // Single rolling release-notes file. Historical release bodies live on | |
| 199 | + // GitHub Releases — we don't archive per-version copies in the repo. | |
| 200 | + const notesPath = resolve(cliRoot, "release-notes", "next.md"); | |
| 201 | + const notesFile = existsSync(notesPath) ? notesPath : null; | |
| 205 | 202 | |
| 206 | 203 | const args = [ |
| 207 | 204 | "release", |