Runcastle

Release Notes Writer

MIT0 downloads

by runcastle

v1.0.1

A 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

Disclosures — declared side-effect surface

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=1000
ARG 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"]

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.