Skip to content

fix(release): take the release body from the CHANGELOG - #255

Merged
oratis merged 1 commit into
mainfrom
fix/release-notes-from-changelog
Aug 9, 2026
Merged

fix(release): take the release body from the CHANGELOG#255
oratis merged 1 commit into
mainfrom
fix/release-notes-from-changelog

Conversation

@oratis

@oratis oratis commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Stacked on #252. Retargets to main when that merges.

Closes the last item on docs/V0.3.0_REPORT.md §8's suggested follow-ups.

The problem

gen-release-notes.ts describes a release by walking its commit range, and with no preceding tag it falls back to the root commit. #250 fixed the shallow clone that made that range come out empty — v0.3.0's release page shipped reading "0 commits." — but left the underlying choice in place: a release described by the verbs its commits happened to start with.

CHANGELOG.md already says what shipped, written for people, grouped by what the changes mean. A list of commit subjects is what you write when nobody wrote anything better.

Change

--version <x.y.z> makes that version's CHANGELOG entry the release body.

The commit walk stays as the fallback and announces itself, in the body and on stderr:

> Generated from the commit log: CHANGELOG.md has no `[0.4.0]` entry.

Notes generated because nobody wrote a changelog entry should not look like notes somebody wrote.

Links are absolutised and pinned at the tag

A release body is not rendered inside the repository, so docs/file-contract.md resolves against nothing and 404s. Every repo-relative link becomes https://github.com/oratis/deepcode/blob/v0.3.1/docs/….

Pinned at the tag, not the default branch — a link in the v0.3.0 notes should keep pointing at the v0.3.0 document after that file moves. Absolute URLs, in-page anchors and mailto: are left alone.

[Unreleased] cannot satisfy the lookup

The pattern requires the exact version. A release that shipped whatever happened to be sitting under "Unreleased" would be lying about its own contents.

Verified against the real thing

npx tsx scripts/gen-release-notes.ts v0.3.0 HEAD --version 0.3.0 --repo oratis/deepcode

renders the actual 0.3.0 entry with working links, and --version 9.9.9 falls through to the commit log with the notice attached.

Also

Passes a scrubbed environment to the git calls, for the reason in #252. Six lines duplicated rather than importing gitSpawnEnv: the github-release job runs this with npx tsx after pnpm install but before any build, so packages/core/dist does not exist. That is the caveat #252 listed as not-covered.

Verification

typecheck, lint, format, docs clean; full suite green through the pre-commit hook. 11 new tests covering section extraction, the Unreleased guard, prose that looks like a heading, empty sections, link rewriting and argument parsing.

🤖 Generated with Claude Code

@oratis

oratis commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

Review — approve, no changes

The argument for taking the body from the CHANGELOG rather than the commit log is the right one, and the fallback behaviour is what makes it safe to merge: it still works, and it announces that it happened. "Notes generated because nobody wrote a changelog entry should not look like notes somebody wrote" is the correct instinct — a silent fallback would make a skipped release step invisible.

Checked the pieces that could bite:

  • changelogEntry is anchored per-line (^## \[…\] with m), so a version mentioned in prose cannot be mistaken for a section, and the version is regex-escaped before interpolation. The empty-section-is-absent rule is right: an empty entry is worse than the commit log, not better.
  • [Unreleased] cannot satisfy a semver lookup. The test that documents changelogEntry(md, 'Unreleased') returning the Unreleased body is honest about what the function does rather than pretending it has a special case, and --version is fed from needs.validate.outputs.version, so it is always x.y.z.
  • absoluteLinks leaves anything with a scheme, #, or // alone, and pinning at the tag rather than the default branch is correct for the reason given — the v0.3.0 notes should keep resolving to v0.3.0's copy of a file after it moves.
  • resolveRepo scrubs the environment, and so does gitLog. Duplicating six lines rather than importing gitSpawnEnv is the right trade for a script the release job runs with npx tsx before anything is built; the comment says so, which is what makes it a decision rather than an oversight. This closes the gap fix(test): stop a fixture from re-initialising the real repository #252 listed as not covered.

One thing to know, not to change

absoluteLinks matches ](target) without looking at what precedes it, so an image — ![alt](docs/x.png) — is rewritten to a /blob/ URL, which renders as a link rather than an image in a release body. There are no images in the CHANGELOG today, so this is latent. If one is ever added, the fix is ?raw=1 or raw.githubusercontent.com for that form.

@oratis oratis mentioned this pull request Aug 9, 2026
7 tasks
@oratis
oratis changed the base branch from fix/git-env-test-isolation to main August 9, 2026 15:38
`gen-release-notes.ts` walked a commit range, and with no preceding tag it fell
back to the root commit. That is how v0.3.0's release page came to read
"0 commits." — #250 fixed the shallow clone that produced the empty range, but
the underlying choice was still to describe a release by its commit subjects.

CHANGELOG.md already says what shipped, written for people, grouped by what the
changes mean rather than by the verb the commit happened to start with. A list
of commit subjects is what you write when nobody wrote anything better.

So `--version` makes that entry the release body. The commit walk remains the
fallback and announces itself, in the body and on stderr: notes generated
because nobody wrote a changelog entry should not look like notes somebody
wrote.

Repo-relative links are rewritten to absolute URLs pinned at the tag. A release
body is not rendered inside the repository, so `docs/file-contract.md` resolves
against nothing and 404s; pinning at the tag rather than the default branch also
keeps a v0.3.0 link pointing at the v0.3.0 document after the file moves.

`[Unreleased]` cannot satisfy the lookup — a release that shipped whatever
happened to be sitting under that heading would be lying about its contents.

Also passes a scrubbed environment to the git calls, for the reason in #252. It
duplicates six lines rather than importing `gitSpawnEnv`, because the release job
runs this with `npx tsx` after install but before any build, so core's `dist/`
does not exist yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@oratis
oratis force-pushed the fix/release-notes-from-changelog branch from 73ffb37 to 8fdd274 Compare August 9, 2026 16:05
@oratis
oratis merged commit 4b02a8c into main Aug 9, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant