Prepare release communication from changes. Output adapts to audience — user-facing notes, CHANGELOG entry, internal summary, migration guide.
NOT for ecosystem impact without release (use oss:analyse (requires oss plugin)). NOT for contributor communication or post-release announcements (use oss:shepherd (requires oss plugin)). NOT for retrospective analysis (audit checks forward readiness only — historical review → oss:analyse (requires oss plugin)).
Mode comes first; range or flags follow:
| Invocation | Arguments | Writes to disk |
|---|---|---|
/release [notes] [range] | optional range (default: last-tag..HEAD); use v1->v2 for explicit range | DRAFT.md |
/release notes [range] --changelog | optional range + flag | DRAFT.md + prepends CHANGELOG.md |
/release notes [range] --summary | optional range + flag | DRAFT.md + .temp/output-release-summary-<branch>-<date>.md |
/release notes [range] --migration | optional range + flag | DRAFT.md + .temp/output-release-migration-<branch>-<date>.md |
/release notes [range] --changelog --summary --migration | all flags | All four outputs |
/release prepare <version> | version to stamp, e.g. v1.3.0 | All artifacts in releases/<version>/: DRAFT.md + CHANGELOG.md + SUMMARY.md + MIGRATION.md + demo.py |
/release audit [version] | optional target version | Terminal readiness report; emits `verdict: READY |
/release demo [range] | optional range (default: last-tag..HEAD) | releases/<version>/demo.py or .temp/release-demo-<branch>-<date>.py |
Range notation: v1->v2 (e.g. v1.2->v2.0) — converted internally to git range. No mode → defaults to notes. Flags add outputs alongside notes. prepare = full pipeline — runs audit first, then all artifacts; use when cutting release, not drafting.
Task hygiene: Call TaskList; triage found tasks (completed / deleted / in_progress).
Task tracking — create ALL tasks upfront, execute sequentially; mark completed as each phase finishes. After mode detection, mark inapplicable tasks deleted:
demomode: mark deleted — Classify each change, Audit changelog, Extract contributors, Draft migration guide, Draft executive summary, Write release draft- bug-fix-only release (no 🚀 Added items): mark deleted — Generate release demo
Tasks:
- Gather changes (git log + find common base tag)
- Explore codebase (changed files, impl detail)
- Validate docs alignment
- Classify each change
- Audit changelog
- Extract contributors
- Identify highlights
- Draft migration guide
- Generate release demo (feature releases only)
- Draft executive summary
- Write release draft
Sequential enforcement: never begin phase until prior marked completed. One phase at time. On failure (empty range, git error, demo fail), stop and report — no downstream phases.
Delegation strategy
Gather + explore + validate produce large git/PR output bloating main context. In prepare and audit modes, delegate to subagent via file-based handoff (CLAUDE.md §2):
- Pre-compute gather file path and create dir:
# BRANCH and DATE defined in Shared setup block below — see next section GATHER_FILE=".temp/release-gather-$BRANCH-$DATE.md" mkdir -p .temp # timeout: 5000 - Assert variables before spawning (prevent un-expanded variable names passed literally to agent):
Spawn[ -n "$GATHER_FILE" ] && [ -n "$REPO_ROOT" ] && [ -n "$RANGE" ] || { echo "Error: GATHER_FILE, REPO_ROOT, or RANGE is empty — verify Shared setup and Gather changes completed"; exit 1; } # timeout: 5000Agent(subagent_type="general-purpose")— expand$REPO_ROOT,$RANGE,$GATHER_FILEto literal values (REPO_ROOT and GATHER_FILE in Shared setup; RANGE in Gather changes) before spawning:Agent(subagent_type="general-purpose", prompt="Working directory: <REPO_ROOT>. Run all git commands from that directory (use: git -C <REPO_ROOT> <cmd> or cd <REPO_ROOT> first). For git range <RANGE>: Run gather phase: git log, git diff --stat, gh pr list. Run classify phase on all commits and PR data. Run explore phase: top 3–5 most significant changed files (read actual diffs). Write full findings — commit list, classified change table, diff excerpts — to <GATHER_FILE> using the Write tool. Return ONLY: {\"status\":\"done\",\"file\":\"<GATHER_FILE>\",\"changes\":N,\"breaking\":N,\"confidence\":0.N}") - Validate envelope and pass file path downstream — every "abort" below is a hard
exit 1, not a prose continuation:
PassSTATUS=$(echo "$ENVELOPE" | jq -r '.status' 2>/dev/null) GATHER_FILE=$(echo "$ENVELOPE" | jq -r '.file' 2>/dev/null) BREAKING=$(echo "$ENVELOPE" | jq -r '.breaking // 0' 2>/dev/null) # default 0 — never skip migration guide on missing field if [ "$STATUS" != "done" ] || [ -z "$GATHER_FILE" ] || [ "$GATHER_FILE" = "null" ] || [ ! -f "$GATHER_FILE" ]; then echo "Error: delegation validation failed — status=$STATUS, file=$GATHER_FILE" >&2 exit 1 fi$GATHER_FILEpath to artifact phase — do NOT read gather file into main context; artifact agent reads it directly.
notes and demo modes: skip delegation — single-pass; run gather/explore/validate inline. Size guard: before inline gather, estimate commit count with git rev-list --count ${RANGE:-${LAST_TAG:-HEAD~20}..HEAD} 2>/dev/null (reuses LAST_TAG derived in Shared setup when available; falls back to HEAD~20 only when LAST_TAG unset). If count exceeds 50, delegate gather to general-purpose subagent same as prepare mode — inline gather with >50 commits causes substantial context flood. Define GATHER_FILE before spawning (mirrors prepare-mode step 1) so the envelope-validation block above can resolve the path:
GATHER_FILE=".temp/release-gather-$BRANCH-$DATE.md"
mkdir -p .temp # timeout: 5000
Mode Detection
Parse $ARGUMENTS by first token:
read FIRST REST <<<"$ARGUMENTS"
# Range-first detection: if FIRST looks like a range (contains -> or ..),
# force notes mode and reframe args so the shared flag-parse loop runs over the
# whole tail (REST). Without this, "/release v1->v2 --changelog" falls to the
# default route which assigns RANGE="$ARGUMENTS" verbatim — leaving --changelog
# embedded inside the range string and the flag silently ignored.
# Also check full ARGUMENTS for spaced-arrow form "v1 -> v2" (FIRST alone would be "v1", missing the "->")
if [[ "$FIRST" == *"->"* ]] || [[ "$FIRST" == *".."* ]] || [[ "$ARGUMENTS" == *"->"* ]] || [[ "$ARGUMENTS" == *".."* ]]; then
MODE="notes"
REST="$ARGUMENTS" # reuse full ARGUMENTS so flag loop discovers the complete range and all flags
FIRST="notes"
fi
| First token | Mode | Routing |
|---|---|---|
prepare | prepare | Run Shared setup block first, then skip to Mode: prepare (skip other mode sections only — Shared setup is mandatory prerequisite) |
audit | audit | Run Shared setup block first, then skip to Mode: audit (skip other mode sections only — Shared setup is mandatory prerequisite) |
demo | demo | Run Shared setup block first, then skip to Mode: demo (skip other mode sections only — Shared setup is mandatory prerequisite) |
notes | notes | Parse flags and range from $REST; run all phases |
| (bare range — handled above by range-first detection) | notes | Falls through to notes route after FIRST is rewritten |
| (none) | notes | RANGE="", no flags; run all phases |
After matching notes, parse flags from $REST:
DO_CHANGELOG=false; DO_SUMMARY=false; DO_MIGRATION=false; RANGE=""
for arg in $REST; do
case "$arg" in
--changelog) DO_CHANGELOG=true ;;
--summary) DO_SUMMARY=true ;;
--migration) DO_MIGRATION=true ;;
*)