<objective>
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)).
</objective>
<inputs>
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 | NEEDS_ATTENTION | BLOCKED as final line for orchestrator consumption | | /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.
</inputs>
<workflow>
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:
``bash # 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):
``bash [ -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: 5000 ` Spawn Agent(subagent_type="general-purpose") — expand $REPO_ROOT, $RANGE, $GATHER_FILE to literal values (REPO_ROOT and GATHER_FILE in Shared setup; RANGE in Gather changes) before spawning: `text 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:
``bash STATUS=$(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 ` Pass $GATHER_FILE` path 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 ;;
*)