Release notes, curated for humans. relnote reads the commits since your last tag and asks Claude Code — your existing subscription, no API keys — to turn them into release notes end users actually want to read.
Self-bootstrapped: the v0.2.0 release notes were written by relnote itself — then reviewed by a human, as intended.
Not a changelog generator — a curator. It judges each commit by its subject and body, keeps only what users will notice, merges related commits into single entries, translates internal jargon into user value, and lists everything it left out (with reasons) so reviewers can audit its judgment.
brew install agiletalk/tap/relnoteOr build from source:
git clone https://github.com/agiletalk/relnote.git
cd relnote && swift build -c release
cp .build/release/relnote /usr/local/bin/git, plus one of the following (checked in this order by --executor auto):
| Executor | Needs | Quality | Cost |
|---|---|---|---|
claude |
Claude Code CLI, signed in — uses your existing Claude subscription, no API keys | best | subscription usage |
apple |
macOS 26+ with Apple Intelligence — Apple's on-device model, nothing to install | good for small releases; noticeably below Claude | free, offline |
--no-llm |
nothing | deterministic Conventional Commits grouping, no curation | free, offline |
The on-device model has a small (~4k token) context window and is strongest
in English — for large ranges or nuanced curation, prefer claude.
relnote # notes for last tag → HEAD, markdown to stdout
relnote --lang ko # Korean notes
relnote --from v1.2.0 --to v1.3.0
relnote --format json | jq . # structured output
relnote --no-llm # deterministic, offline
relnote -C ~/code/my-app > RELEASE_NOTES.mdIf HEAD is exactly at your latest tag (you just cut a release), relnote
automatically steps back one tag — so bare relnote right after tagging
gives you the notes for that release, not an empty range.
| Flag | Default | Description |
|---|---|---|
--from <ref> |
latest tag | Start of the commit range (exclusive) |
--to <ref> |
HEAD |
End of the commit range (inclusive) |
--version-name <name> |
tag at --to, or Unreleased |
Version shown in the notes |
--format <md|json> |
md |
Output format |
--lang <en|ko> |
en |
Notes language |
--no-llm |
off | Skip the LLM; group conventional commits offline |
--executor <auto|claude|apple> |
auto |
auto = claude if installed, else the Apple on-device model |
--model <model> |
claude's default | Passed through to claude --model (claude executor only) |
-C <dir> |
. |
Run as if started in <dir> |
--max-commits <n> |
200 |
Cap on commits fed to curation |
--timeout <s> |
300 |
Timeout for the claude invocation |
--verbose |
off | Diagnostics on stderr (range resolution, cost, session id) |
Notes go to stdout; progress and warnings go to stderr, so piping and redirection stay clean.
- Range — finds the latest tag reachable from
--to(git describe) and usestag..HEAD. No tags? Falls back to recent history with a warning. - Parse — reads the log with field-separator formatting, classifies each
commit against Conventional Commits (type, scope,
!/BREAKING CHANGE:). - Curate — sends subjects and bodies to
claude -pwith a JSON schema (--json-schema), which returns validated structured output: oneherochange, up to 6highlights, and anexcludedlist with reasons.versionanddateare injected from git, never generated. - Render — markdown (or raw JSON with
--format json).
{
"version": "v2.10.0",
"date": "2026-07-13",
"hero": { "title": "…", "description": "…" },
"highlights": [{ "title": "…", "description": "…" }],
"excluded": [{ "commit": "…", "reason": "…" }]
}- Slack Block Kit renderer + webhook delivery
relnote publish— update GitHub Release bodies in place- More
--executorbackends: Codex CLI, GitHub Copilot CLI, opencode (subscription-based, like claude), plus bring-your-own-key APIs and Ollama (local) - Linux support
MIT © Chanju Jeon
