Release Notes Writer
MIT↓ 0 downloadsA single Claude Code agent reads the commits since your last tag, groups them into Added/Changed/Fixed/Security by user-facing intent, and writes a clean CHANGELOG.md entry plus a friendly RELEASE_NOTES.md — turning raw git history into release-ready notes in one pass.
Topology
Disclosures
Everything below runs on your machine or inside the sandbox when you use this workflow. Mismatches between these declarations and the actual code block publishing.
Host hooks
Commands executed on YOUR host machine by Sandcastle lifecycle hooks.
None declared.
Sandbox hooks
Commands executed inside the sandbox container.
None declared.
Network access
None. The agent reads the local git history and writes CHANGELOG.md and RELEASE_NOTES.md inside the sandbox; no external hosts are contacted.
Shell expansion
Prompt files contain !`command` blocks — the agent CLI executes these commands at prompt-load time. They are highlighted amber in the prompt files below.
Files
Diff vs the stock Sandcastle 0.12.0 template Dockerfile — green lines were added by the author, red lines were removed from stock.
+# Sandbox image for the Release Notes Writer workflow.+# Node 22 + git (the prompt reads the commit history) + the Claude Code CLI,+# running as a non-root `agent` user.FROM node:22-bookworm-# System dependencies.RUN apt-get update && apt-get install -y --no-install-recommends \git \curl \jq \ca-certificates \&& rm -rf /var/lib/apt/lists/*# Claude Code CLI (the agent runtime).RUN npm install -g @anthropic-ai/claude-code# Non-root agent user. `sandcastle docker build-image` aligns AGENT_UID/GID to# the host user via --build-arg to avoid permission errors on bind mounts.+# node:22-bookworm already ships a "node" user at UID/GID 1000, so we RENAME it+# (the stock Sandcastle template pattern) — groupadd/useradd would collide with+# the existing IDs on a default build.ARG AGENT_UID=1000ARG AGENT_GID=1000-RUN groupadd --gid ${AGENT_GID} agent \- && useradd --uid ${AGENT_UID} --gid ${AGENT_GID} --create-home --shell /bin/bash agent+RUN groupmod -o -g ${AGENT_GID} node \+ && usermod -o -u ${AGENT_UID} -g ${AGENT_GID} -d /home/agent -m -l agent node-USER agent-WORKDIR /workspace+USER ${AGENT_UID}:${AGENT_GID}+WORKDIR /home/agent++# Sandcastle bind-mounts the worktree and sets the working directory at+# container start; the container just needs to stay alive until then.+ENTRYPOINT ["sleep", "infinity"]
Show full Dockerfile (highlighted)
# Sandbox image for the Release Notes Writer workflow.
# Node 22 + git (the prompt reads the commit history) + the Claude Code CLI,
# running as a non-root `agent` user.
FROM node:22-bookworm
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
curl \
jq \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# Claude Code CLI (the agent runtime).
RUN npm install -g @anthropic-ai/claude-code
# Non-root agent user. `sandcastle docker build-image` aligns AGENT_UID/GID to
# the host user via --build-arg to avoid permission errors on bind mounts.
# node:22-bookworm already ships a "node" user at UID/GID 1000, so we RENAME it
# (the stock Sandcastle template pattern) — groupadd/useradd would collide with
# the existing IDs on a default build.
ARG AGENT_UID=1000
ARG AGENT_GID=1000
RUN groupmod -o -g ${AGENT_GID} node \
&& usermod -o -u ${AGENT_UID} -g ${AGENT_GID} -d /home/agent -m -l agent node
USER ${AGENT_UID}:${AGENT_GID}
WORKDIR /home/agent
# Sandcastle bind-mounts the worktree and sets the working directory at
# container start; the container just needs to stay alive until then.
ENTRYPOINT ["sleep", "infinity"]
# Auth for the Claude Code agent.
# Run `claude setup-token` on your host to generate a token, then paste it here
# in your local .sandcastle/.env (never commit the real .env).
CLAUDE_CODE_OAUTH_TOKEN=
import { run, claudeCode } from "@ai-hero/sandcastle";
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
// A single-pass release-notes writer. One Sonnet agent reads the commits since
// the last tag (the prompt expands `git log` inline), groups them into a
// Keep-a-Changelog style breakdown, and writes both CHANGELOG.md and
// RELEASE_NOTES.md. maxIterations: 1 keeps it to one focused pass.
const result = await run({
name: "release-notes-writer",
agent: claudeCode("claude-sonnet-4-6"),
sandbox: docker(),
promptFile: ".sandcastle/prompt.md",
maxIterations: 1,
});
console.log(`Wrote release notes in ${result.commits.length} commit(s) on ${result.branch}.`);
Write the release notes for the next version
You are a release manager. Turn the raw commit history since the last release into clean, human-readable release notes and a changelog entry.
Commits since the last tag
!git log --oneline --no-merges $(git describe --tags --abbrev=0 2>/dev/null)..HEAD
(If the repository has no tags yet, the range is empty above — in that case run
git log --oneline --no-merges yourself to read the full history and treat it as
the first release.)
Your task
Read the commits above and group them by user-facing intent, not by the literal commit prefix. Use these Keep-a-Changelog sections, omitting any that are empty:
- Added — new features and capabilities.
- Changed — changes to existing behaviour.
- Deprecated — soon-to-be-removed features.
- Removed — features taken out.
- Fixed — bug fixes.
- Security — vulnerability fixes.
Translate terse commit subjects into short, benefit-oriented lines a user would understand. Collapse noise — merge follow-up "fix typo" / "address review" commits into the change they belong to, and drop pure chore/CI commits that have no user impact. Do not invent changes that aren't in the log.
Then produce two files:
CHANGELOG.md— prepend a new version section at the top (create the file with a standard Keep-a-Changelog header if it doesn't exist). Use the next sensible semantic version and today's date, e.g.## [1.4.0] - 2025-01-15. Preserve any existing entries below.RELEASE_NOTES.md— a friendlier, standalone announcement for this release: a one-paragraph summary of the highlights up top, then the grouped list, then a short "Upgrade notes" line if any change needs action.
Commit both files with the message docs: release notes for <version>.
README
Release Notes Writer
Cutting a release shouldn't mean squinting at git log and hand-copying commit
subjects into a changelog. This workflow points a single Claude Code agent at
your commit history and gets you release-ready notes in one pass.
What it does
The agent reads every commit since your last tag and groups them by user-facing intent — not by the literal commit prefix — into the Keep-a-Changelog sections: Added, Changed, Deprecated, Removed, Fixed, Security (empty sections are dropped). It rewrites terse subjects into short, benefit-oriented lines, collapses noise (follow-up "fix typo" and "address review" commits fold into the change they belong to), and drops pure chore/CI commits that have no user impact. It never invents changes that aren't in the log. Then it produces two files:
CHANGELOG.md— a new version section prepended at the top (created with a standard header if the file is new), dated, with existing entries preserved.RELEASE_NOTES.md— a friendlier standalone announcement: a highlights paragraph, the grouped list, and an "Upgrade notes" line when action is needed.
How it works
The topology is a single node: summarize commits → notes. main.ts runs one
Sonnet agent with maxIterations: 1 in a Docker sandbox. The prompt seeds the
commit list by expanding git log --oneline --no-merges <last-tag>..HEAD inline
(shell expansion), so the agent starts with exactly the commits in scope. Runs on
Docker or Podman.
Requirements
Set CLAUDE_CODE_OAUTH_TOKEN in .sandcastle/.env (run claude setup-token on
your host). Make sure your repository has its tags available so the agent can
find the previous release (git fetch --tags); with no tags it treats the full
history as the first release. Build the image once with
npx @ai-hero/sandcastle docker build-image, then run it with
npx tsx .sandcastle/main.ts.