/finalize Command
Post-deploy documentation + housekeeping
/finalize — Complete Documentation & Cleanup After Deployment
Purpose Finalize docs, roadmap, and housekeeping after a successful production deployment.
Dependencies
- A finished prod deploy (
/ship→deploy-prod), withworkflow-state.yamlcontaining:feature.title,feature.slugdeployment.production.urldeployment.production.run_id(for linking logs)version(MAJOR.MINOR.PATCH)
Execution model
- Idempotent: safe to re-run; completed tasks are skipped
- Deterministic: no prompts, no editors
- Tracked: every step mirrored in TodoWrite
MENTAL MODEL
You are a post-deployment finalizer that updates documentation and housekeeping with zero manual intervention.
Philosophy: Deployment is not done until the docs are updated, roadmap is current, and branches are cleaned. Finalize automates this paperwork using industry standards.
Standards:
- Changelog: Keep a Changelog format
- Versioning: Semantic Versioning (SemVer)
- Badges: Shields.io static badges
- GitHub: Official gh CLI and REST API
Token efficiency: No prompts, no editors, pure automation. TodoWrite tracks every step for resumability.
PRECONDITIONS
# Required CLIs
for c in gh jq yq git; do
command -v "$c" >/dev/null || { echo "❌ missing: $c"; exit 1; }
done
# Auth must be valid
gh auth status >/dev/null || { echo "❌ gh not authenticated"; exit 1; }
Reference: gh auth status verifies CLI authentication before any API use (GitHub CLI Manual)
STEP 0 — Initialize & Create Todo List (MANDATORY)
CRITICAL: Create TodoWrite list IMMEDIATELY to track all finalization tasks.
TodoWrite({
todos: [
{ content: "Load feature context and version info", status: "completed", activeForm: "Loading context" },
{ content: "Update CHANGELOG.md", status: "pending", activeForm: "Updating CHANGELOG.md" },
{ content: "Update README.md (version badge + features)", status: "pending", activeForm: "Updating README.md" },
{ content: "Generate user docs (help article)", status: "pending", activeForm: "Generating help article" },
{ content: "Update API docs (if endpoints changed)", status: "pending", activeForm: "Updating API docs" },
{ content: "Close current milestone", status: "pending", activeForm: "Closing milestone" },
{ content: "Create next milestone", status: "pending", activeForm: "Creating next milestone" },
{ content: "Update roadmap issue to 'shipped'", status: "pending", activeForm: "Updating roadmap issue" },
{ content: "Update GitHub Release with production info", status: "pending", activeForm: "Updating GitHub Release" },
{ content: "Commit & push documentation changes", status: "pending", activeForm: "Committing documentation" },
{ content: "Cleanup feature branch (safe)", status: "pending", activeForm: "Cleaning up branch" }
]
})
Load context:
FEATURE_DIR="$(ls -td specs/*/ | head -1)"
STATE="${FEATURE_DIR%/}/workflow-state.yaml"
TITLE="$(yq -r '.feature.title' "$STATE")"
SLUG="$(yq -r '.feature.slug' "$STATE")"
PROD_URL="$(yq -r '.deployment.production.url // ""' "$STATE")"
VERSION="$(yq -r '.version' "$STATE")" # e.g., 1.3.0
STEP 1 — CHANGELOG.md (Keep a Changelog + SemVer)
Standard: Follow Keep a Changelog and Semantic Versioning
DATE="$(date +%F)"
CHANGELOG="CHANGELOG.md"
# Create if missing
[[ -f $CHANGELOG ]] || cat > $CHANGELOG <<'EOF'
# Changelog
All notable changes to this project will be documented in this file.
This format follows [Keep a Changelog](https://keepachangelog.com/)
and this project adheres to [Semantic Versioning](https://semver.org/).
EOF
# Collect notable commits since last tag or last version line
LAST_TAG="$(git describe --tags --abbrev=0 2>/dev/null || true)"
RANGE="${LAST_TAG:+${LAST_TAG}..HEAD}"
ADDED="$(git log --pretty='- %s' --grep='^feat:' $RANGE)"
FIXED="$(git log --pretty='- %s' --grep='^fix:' $RANGE)"
CHANGED="$(git log --pretty='- %s' --grep='^refactor:' $RANGE)"
SECURITY="$(git log --pretty='- %s' --grep='^security:' $RANGE)"
# Prepend new section
TMP="$(mktemp)"
{
echo "## [v${VERSION}] - ${DATE}"
[[ -n "$ADDED" ]] && { echo ""; echo "### Added"; echo "$ADDED"; }
[[ -n "$FIXED" ]] && { echo ""; echo "### Fixed"; echo "$FIXED"; }
[[ -n "$CHANGED" ]] && { echo ""; echo "### Changed"; echo "$CHANGED"; }
[[ -n "$SECURITY" ]] && { echo ""; echo "### Security"; echo "$SECURITY"; }
echo ""
cat "$CHANGELOG"
} > "$TMP" && mv "$TMP" "$CHANGELOG"
After completion:
- Update TodoWrite: Mark "Update CHANGELOG.md" as
completed - Mark "Update README.md" as
in_progress
STEP 2 — README.md (Version Badge + Features)
Standard: Use Shields.io static badges
README="README.md"
[[ -f $README ]] || touch $README
# Ensure a version badge exists (idempotent replace or append)
grep -q 'img.shields.io/badge/version-' "$README" \
&& sed -i 's#img.shields.io/badge/version-[^)]*#img.shields.io/badge/version-v'"$VERSION"'-blue#g' "$README" \
|| sed -i '1i \n' "$README"
# Append feature line (once)
LINE=" - 🎉 **${TITLE}** — shipped in v${VERSION}"
grep -qF "$LINE" "$README" || {
# Ensure Features section exists
grep -q "^## Features" "$README" || printf "\n## Features\n" >> "$README"
# Add feature below Features heading
sed -i "/^## Features/a $LINE" "$README"
}
After completion:
- Update TodoWrite: Mark "Update README.md" as
completed - Mark "Generate user docs" as
in_progress
STEP 3 — User Docs (Help Article)
DOC_DIR="docs/help/features"
mkdir -p "$DOC_DIR"
DOC_FILE="${DOC_DIR}/${SLUG}.md"
cat > "$DOC_FILE" <<EOF
# ${TITLE}
**Version**: v${VERSION}
**Released**: ${DATE}
## Overview
Short summary (from spec.md).
## How to Use
Step-by-step (from user stories).
## Features
- Pulled from acceptance criteria.
## Screenshots
<!-- Add or link assets -->
## Troubleshooting
Common issues and resolutions.
EOF
# Ensure index exists and link entry
INDEX="docs/help/README.md"
mkdir -p "$(dirname "$INDEX")"
grep -q "features/${SLUG}.md" "$INDEX" 2>/dev/null || {
[[ -f "$INDEX" ]] || echo "# Help Documentation" > "$INDEX"
printf "\n## Features\n\n- [%s](features/%s.md) — v%s\n" "$TITLE" "$SLUG" "$VERSION" >> "$INDEX"
}
After completion:
- Update TodoWrite: Mark "Generate user docs" as
completed - Mark "Update API docs" as
in_progress
STEP 4 — API Docs (Conditional)
# Heuristic: if spec/plan mentioned API changes, update docs/API_ENDPOINTS.md
if rg -n "API|endpoint|route" "$FEATURE_DIR/spec.md" "$FEATURE_DIR/plan.md" >/dev/null 2>&1; then
APIDOC="docs/API_ENDPOINTS.md"
mkdir -p "$(dirname "$APIDOC")"
[[ -f "$APIDOC" ]] || echo "# API Endpoints" > "$APIDOC"
# Append/update a minimal endpoint block (ensure no duplicates)
BLOCK="### ${TITLE} (v${VERSION})"
grep -qF "$BLOCK" "$APIDOC" || cat >> "$APIDOC" <<EOF
${BLOCK}
- **Method**: [GET|POST|PUT|DELETE]
- **Path**: /api/[endpoint]
- **Auth**: [Required|Optional]
- **Request**: [Body schema]
- **Response**: [Response schema]
EOF
else
echo "ℹ️ No API changes detected, skipping API documentation"
fi
After completion:
- Update TodoWrite: Mark "Update API docs" as
completed(even if skipped) - Mark "Close current milestone" as
in_progress
STEP 5 — Milestones (Close Current, Create Next)
Reference: Uses GitHub REST milestones API via gh api
# Close current (best-effort; non-blocking if absent)
CUR_MINOR="$(echo "$VERSION" | awk -F. '{print $1"."$2}')"
CUR_MS_JSON="$(gh api repos/:owner/:repo/milestones --jq '.[] | select(.title | test("^v?'$CUR_MINOR'\\.x$"))' 2>/dev/null || true)"
if [[ -n "$CUR_MS_JSON" ]]; then
CUR_MS_NUM="$(jq -r '.number' <<<"$CUR_MS_JSON")"
echo "Closing milestone #${CUR_MS_NUM} (v${CUR_MINOR}.x)"
gh api -X PATCH "repos/:owner/:repo/milestones/$CUR_MS_NUM" -f state=closed >/dev/null 2>&1 || true
else
echo "ℹ️ No milestone found for v${CUR_MINOR}.x"
fi
# Create next minor (e.g., 1.3.0 -> 1.4.0)
NEXT_MINOR="$(echo "$VERSION" | awk -F. '{printf "%d.%d.0", $1, $2+1}')"
DUE_ON="$(date -u -d '+14 days' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v+14d +%Y-%m-%dT%H:%M:%SZ)"
echo "Creating next milestone: v${NEXT_MINOR} (due: ${DUE_ON})"
gh api repos/:owner/:repo/milestones \
-f title="v$NEXT_MINOR" \
-f due_on="$DUE_ON" \
>/dev/null 2>&1 || echo "ℹ️ Milestone v${NEXT_MINOR} already exists or creation failed"
After completion:
- Update TodoWrite: Mark "Close current milestone" and "Create next milestone" as
completed - Mark "Update roadmap issue" as
in_progress
STEP 6 — Roadmap Issue → Shipped
Reference: Uses gh issue commands
ISSUE_JSON="$(gh issue list --label 'type:feature' --search "slug: ${SLUG}" --json number --limit 1 2>/dev/null || true)"
NUM="$(jq -r '.[0].number // empty' <<<"$ISSUE_JSON")"
if [[ -n "$NUM" ]]; then
echo "Updating roadmap issue #${NUM} to 'shipped'"
gh issue edit "$NUM" \
--add-label "status:shipped" \
--remove-label "status:in-progress" \
>/dev/null 2>&1 || true
gh issue comment "$NUM" --body "$(cat <<TXT
🚀 Shipped in v${VERSION} on ${DATE}
**Production**: ${PROD_URL:-N/A}
See [release notes](https://github.com/:owner/:repo/releases/tag/v${VERSION})
TXT
)" >/dev/null 2>&1 || true
else
echo "ℹ️ No roadmap issue found for slug: ${SLUG}"
fi
After completion:
- Update TodoWrite: Mark "Update roadmap issue" as
completed - Mark "Update GitHub Release" as
in_progress
STEP 7 — Update GitHub Release with Production Info
Reference: Uses gh release commands
# Check if release exists for this version
RELEASE_TAG="v${VERSION}"
if gh release view "$RELEASE_TAG" >/dev/null 2>&1; then
echo "Updating GitHub Release: ${RELEASE_TAG}"
# Get existing release body
EXISTING_BODY="$(gh release view "$RELEASE_TAG" --json body -q .body)"
# Prepare production deployment footer
PROD_FOOTER="$(cat <<EOF
---
## 🚀 Production Deployment
**Status**: ✅ Deployed
**URL**: ${PROD_URL:-N/A}
**Date**: ${DATE}
**Feature**: ${TITLE}
### Deployment Info
- **Version**: v${VERSION}
- **Run ID**: $(yq -r '.deployment.production.run_id // "N/A"' "$STATE")
- **Deploy Logs**: [View logs](https://github.com/:owner/:repo/actions/runs/$(yq -r '.deployment.production.run_id // ""' "$STATE"))
### Documentation
- **CHANGELOG**: [View changes](https://github.com/:owner/:repo/blob/main/CHANGELOG.md#v${VERSION//.})
- **Help Article**: [docs/help/features/${SLUG}.md](https://github.com/:owner/:repo/blob/main/docs/help/features/${SLUG}.md)
🎉 Feature fully deployed and documented!
EOF
)"
# Check if release already has production info (idempotent)
if echo "$EXISTING_BODY" | grep -q "## 🚀 Production Deployment"; then
echo "ℹ️ GitHub Release already contains production deployment info"
else
# Append production info to existing release notes
NEW_BODY="${EXISTING_BODY}${PROD_FOOTER}"
gh release edit "$RELEASE_TAG" --notes "$NEW_BODY" >/dev/null 2>&1 \
&& echo "✅ Updated GitHub Release with production deployment info" \
|| echo "⚠️ Failed to update GitHub Release (non-blocking)"
fi
else
echo "ℹ️ No GitHub Release found for ${RELEASE_TAG} (may not be using release tags)"
fi
After completion:
- Update TodoWrite: Mark "Update GitHub Release" as
completed - Mark "Commit & push documentation" as
in_progress
STEP 8 — Commit & Push Docs
git add CHANGELOG.md README.md docs/ 2>/dev/null || true
if git diff --cached --quiet; then
echo "ℹ️ No documentation changes to commit"
else
git commit -m "docs: finalize v${VERSION} documentation
- Update CHANGELOG.md with v${VERSION} section
- Update README.md version badge and features list
- Add help article for ${TITLE}
- Update API documentation (conditional)
- Update GitHub Release with production deployment info
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>" || true
git push || echo "⚠️ Push failed (may need to pull first)"
fi
After completion:
- Update TodoWrite: Mark "Commit & push documentation" as
completed - Mark "Cleanup feature branch" as
in_progress
STEP 9 — Branch Cleanup (Safe)
# Only delete if merged and not checked out
FEATURE_BRANCH="$(yq -r '.workflow.git.feature_branch // ""' "$STATE")"
if [[ -n "$FEATURE_BRANCH" ]]; then
CURRENT="$(git branch --show-current)"
# Switch to main if on feature branch
if [[ "$CURRENT" == "$FEATURE_BRANCH" ]]; then
git checkout -q main 2>/dev/null || git checkout -q master 2>/dev/null || true
fi
# Only delete if fully merged (safe)
if git branch --merged | grep -q " ${FEATURE_BRANCH}$"; then
echo "Deleting merged branch: ${FEATURE_BRANCH}"
git branch -d "$FEATURE_BRANCH" 2>/dev/null || true
git push origin --delete "$FEATURE_BRANCH" >/dev/null 2>&1 || true
else
echo "ℹ️ Branch ${FEATURE_BRANCH} not fully merged, skipping deletion"
fi
else
echo "ℹ️ No feature branch to clean up"
fi
After completion:
- Update TodoWrite: Mark "Cleanup feature branch" as
completed
STEP 10 — Summary
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📚 Finalization Complete
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Version: v${VERSION}
Feature: ${SLUG}
Date: ${DATE}
### Files Updated
- ✅ CHANGELOG.md (Keep a Changelog format)
- ✅ README.md (Shields.io badge + features)
- ✅ docs/help/features/${SLUG}.md (help article)
- ✅ docs/API_ENDPOINTS.md (conditional)
### GitHub
- ✅ Closed milestone: v${CUR_MINOR}.x
- ✅ Created next milestone: v${NEXT_MINOR} (due: [date])
- ✅ Updated roadmap issue to "shipped" (if found)
- ✅ Updated GitHub Release with production deployment info
### Git
- ✅ Committed documentation changes
- ✅ Pushed to main
- ✅ Cleaned up feature branch (if merged)
### Next Steps
1. Review documentation accuracy
2. Announce release (social media, blog, email)
3. Monitor user feedback and error logs
4. Plan next feature from roadmap
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🎉 Full workflow complete: /feature → /ship → /finalize ✅
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Deployment logs: gh run view $(yq -r '.deployment.production.run_id // "N/A"' "$STATE") --log
ERROR RECOVERY
Idempotency: Re-running /finalize is safe. Completed tasks are skipped based on TodoWrite state.
If any step fails:
- Update TodoWrite: Keep failed task as
in_progress - Display specific error with fix instructions
- User runs
/finalizeagain to resume
Example error handling:
# If git push fails
if ! git push; then
echo "❌ Git push failed"
echo ""
echo "Likely cause: Need to pull changes first"
echo "Fix: git pull --rebase && /finalize"
exit 1
fi
GitHub API safety:
- All
ghcommands use|| trueto avoid blocking entire workflow - Optional steps (milestones, roadmap) are logged but don't fail finalization
- Rate limiting is handled gracefully with warnings
Branch cleanup safety:
- Uses
-d(safe delete) not-D(force delete) - Only deletes if
git branch --mergedconfirms merge - Never deletes if unmerged changes exist
CONSTRAINTS
TodoWrite discipline:
- Create list at Step 0 (before any work)
- Update after EVERY step completion
- Mark as
completedonly if operation succeeded - Mark as
completed(with note) if optional step skipped
Standards compliance:
- CHANGELOG: Keep a Changelog
- Versioning: Semantic Versioning
- Badges: Shields.io
- GitHub: Official gh CLI and REST API
No prompts:
- All operations are deterministic
- No editors (vi, nano, etc.)
- No user input required
- Safe to run in CI/CD
Idempotency:
- Safe to re-run multiple times
- Completed tasks are skipped
- Duplicate prevention (grep -q checks)
- Best-effort operations with graceful degradation
NOTES
Workflow dispatch: If you later auto-trigger releases via GitHub Actions, the target workflow must declare on: workflow_dispatch. Use gh workflow run and gh run watch to dispatch and monitor (gh workflow run).
Milestone discovery: Assumes milestones are named vX.Y.x (e.g., v1.2.x). Adjust regex in Step 5 if using different naming.
Date format: Uses ISO-8601 (YYYY-MM-DD) for CHANGELOG dates per Keep a Changelog standard.
Artifact linking: Production deployment logs can be viewed via gh run view <run-id> --log using the run_id from workflow-state.yaml.