finishing-a-development-branch
Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup
| Model | Source | Category |
|---|---|---|
| sonnet | core | Git |
Overview
Section titled “Overview”Guide completion of development work by presenting clear options and handling chosen workflow.
Core principle: Verify tests → Present options → Execute choice → Clean up.
First action — before anything else:
touch /tmp/.armadillo-merge-skill-activeThis flag authorizes merge operations. Without it, the PreToolUse hook blocks merging. The flag is single-use — consumed when the merge REST call runs.
Mandatory Announcement — FIRST OUTPUT before anything else:
┏━ 🚀 f
<details><summary><strong>Full Reference</strong></summary>
# Finishing a Development Branch
## Overview
Guide completion of development work by presenting clear options and handling chosen workflow.
**Core principle:** Verify tests → Present options → Execute choice → Clean up.
**First action — before anything else:**```bashtouch /tmp/.armadillo-merge-skill-activeThis flag authorizes merge operations. Without it, the PreToolUse hook blocks merging. The flag is single-use — consumed when the merge REST call runs.
Mandatory Announcement — FIRST OUTPUT before anything else:
┏━ 🚀 finishing-a-development-branch ━━━━━━━━━━━━┓┃ [one-line description of what branch/work] ┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛No exceptions. Box frame first, then work.
The Process
Section titled “The Process”Step 1: Verify Tests
Section titled “Step 1: Verify Tests”Before presenting options, verify tests pass. Use run_in_background: true. Poll with TaskOutput.
Step 1a: Discover test command. Check in order:
package.jsonscripts →npm testCargo.toml→cargo testpyproject.toml/setup.cfg/pytest.ini→pytestgo.mod→go test ./...Makefilewith test target →make test- If none found, check for test files (
*.test.*,*_test.*,tests/directory)
Step 1b: Run ALL discovered test suites. Projects may have multiple test layers (e.g., npm test for unit tests AND .claude/tests/ for skill tests). Run every discoverable test suite — do not pick one and skip the rest.
# Example: run both unit tests and integration testsnpm test.claude/tests/explicit-skill-requests/run-all.shIf some tests require external services (API calls, live databases, etc.) that are unavailable, still run them — report the results and note which failures are environment-related vs. code-related. Do NOT skip tests because they “probably” fail or “seem” unrelated.
Step 1c: Check branch age.
BRANCH_DATE=$(git log -1 --format=%ci HEAD)# Calculate days old (macOS-compatible)DAYS_OLD=$(( ( $(date +%s) - $(date -j -f "%Y-%m-%d %H:%M:%S %z" "$BRANCH_DATE" +%s) ) / 86400 ))| Age | Action |
|---|---|
| ≤ 7 days | Continue normally |
| 7-14 days | ⚠ Branch is <N> days old. Consider rebasing onto main before proceeding. |
| > 14 days | ◆ Branch has drifted significantly from main (<N> days). Strongly recommend rebasing before merge. |
If tests fail:
Tests failing (<N> failures). Must fix before completing:
[Show failures]
Cannot proceed with merge/PR until tests pass.Stop. Don’t proceed to Step 2.
If all tests pass: Continue to Step 2.
If tests have pre-existing failures (confirmed via git stash && run tests && git stash pop or checking failures exist on base branch too): Note the pre-existing failures explicitly, confirm no new failures were introduced, then continue to Step 2.
Step 1.5: Auto-Generate Changelog (Armadillo Repo Only)
Section titled “Step 1.5: Auto-Generate Changelog (Armadillo Repo Only)”This step only applies when working in the armadillo repo. Detect by checking:
node -e "console.log(JSON.parse(require('fs').readFileSync('package.json','utf-8')).name)"If the output is armadillo, proceed. Otherwise skip to Step 2.
Step 1.5a: Identify changed skills.
# Get the base branch merge-baseBASE=$(git merge-base HEAD main 2>/dev/null || git merge-base HEAD master 2>/dev/null)
# List changed skill filesgit diff --name-only $BASE..HEAD -- .claude/skills/ skills.jsonIf no skill files changed, skip to Step 2.
Step 1.5b: Analyze changes and generate entries.
For each changed skill:
- Read the diff:
git diff $BASE..HEAD -- <file> - Determine the change type:
- New file not in base →
added - Modified existing file →
improved - File deleted →
removed - Bug fix (check commit messages for “fix”) →
fixed
- New file not in base →
- Write a one-line
summarydescribing what changed - Write an optional
detailsfield if the change is substantial - Set
breaking: trueif the change could break existing user customizations
Step 1.5c: Read current CHANGELOG.json and append entries.
- Read
CHANGELOG.jsonfrom repo root - Read current version from
package.json - If the version key already exists in the changelog, append new entries to its
changesarray (don’t duplicate — check if a similar entry already exists by matchingskill+files) - If the version key doesn’t exist, create it with today’s date
- Write the updated
CHANGELOG.json - Stage it:
git add CHANGELOG.json
Step 1.5d: Commit the changelog update.
git add CHANGELOG.jsongit commit -m "docs: update CHANGELOG.json for <version>"Important: This commit happens before Step 2 (base branch determination) and Step 3 (presenting options). The changelog should be part of the branch’s commits before a PR is created.
Step 2: Determine Base Branch
Section titled “Step 2: Determine Base Branch”# Try common base branchesgit merge-base HEAD main 2>/dev/null || git merge-base HEAD master 2>/dev/nullOr ask: “This branch split from main - is that correct?”
Step 2.5: Integration Branch Detection
Section titled “Step 2.5: Integration Branch Detection”Check if an integration branch exists that this feature should target:
git branch -r | grep 'origin/integrate/' | head -5If integration branches exist, check for file overlap:
# Files changed on this branchgit diff main...HEAD --name-only > /tmp/my-files.txt# Files changed on integration branchgit diff main...origin/integrate/<name> --name-only > /tmp/integrate-files.txt# Overlapcomm -12 <(sort /tmp/my-files.txt) <(sort /tmp/integrate-files.txt)If overlap detected:
ℹ Integration branch `integrate/<name>` exists with overlapping files. Consider merging into the integration branch instead of main.
▸ Target main or integrate/<name>?Step 3: Present Options
Section titled “Step 3: Present Options”Present exactly these 4 options:
Implementation complete. What would you like to do?
1. Push and create a Pull Request2. Push and create a Draft PR (WIP — not ready for review)3. Keep the branch as-is (I'll handle it later)4. Discard this work
Which option?Don’t add explanation - keep options concise.
Step 4: Execute Choice
Section titled “Step 4: Execute Choice”Option 1: Push and Create PR
Section titled “Option 1: Push and Create PR”Use the writing-prs skill to build the PR description. Invoke it:
Skill tool → skill: "writing-prs"The skill handles:
- Conventional commits title format
- Hybrid PR template (Why, Changes, conditional Review Guide, Test Plan, Links)
env -u GITHUB_TOKENprefix on allgh apicommands- REST API calls (not GraphQL)
If the writing-prs skill is unavailable, push and create manually via REST:
env -u GITHUB_TOKEN git push -u origin <feature-branch>
# Detect repo slugSLUG=$(git remote get-url origin | sed 's|.*github.com[:/]||;s|\.git$||')
# Create PR via RESTBODY=$(cat <<'EOF'## Why<motivation>
## Changes▪ <change 1>▪ <change 2>
## Test plan- [ ] <test command>- [ ] <manual verification step>
Generated with [Claude Code](https://claude.com/claude-code)EOF)PR_NUM=$(env -u GITHUB_TOKEN gh api "repos/${SLUG}/pulls" \ --method POST \ --field title="<type>(<scope>): <description>" \ --field head="<feature-branch>" \ --field base="main" \ --field body="$BODY" \ --jq '.number')echo "Created PR #${PR_NUM}"Merge via REST:
env -u GITHUB_TOKEN gh api "repos/${SLUG}/pulls/${PR_NUM}/merge" \ --method PUT \ --field merge_method=squash \ --field commit_title="<type>(<scope>): <description> (#${PR_NUM})"Poll for merge completion (if using auto-merge via branch protection):
for i in $(seq 1 30); do STATE=$(env -u GITHUB_TOKEN gh api "repos/${SLUG}/pulls/${PR_NUM}" --jq '.state' 2>/dev/null) MERGED=$(env -u GITHUB_TOKEN gh api "repos/${SLUG}/pulls/${PR_NUM}" --jq '.merged' 2>/dev/null) if [ "$MERGED" = "true" ]; then break; fi sleep 10doneAfter merge — local cleanup:
Pre-flight check before switching to main:
# Check if main worktree has uncommitted changesMAIN_STATUS=$(git status --porcelain 2>/dev/null)If main is clean ($MAIN_STATUS is empty):
git checkout mainenv -u GITHUB_TOKEN git pullgit fetch --prunegit branch -D <feature-branch>If main is dirty ($MAIN_STATUS is non-empty):
# Worktree remove and branch delete work from any branch — no checkout neededgit branch -D <feature-branch>Report: ⚠ Main has uncommitted changes — worktree cleaned, pull skipped. Run 'git pull' on main when ready.
Then: Cleanup worktree (Step 5)
Option 2: Push and Create Draft PR
Section titled “Option 2: Push and Create Draft PR”Same as Option 1 but with --field draft=true:
env -u GITHUB_TOKEN git push -u origin <feature-branch>
SLUG=$(git remote get-url origin | sed 's|.*github.com[:/]||;s|\.git$||')BODY=$(cat <<'EOF'## Why<motivation>
## Changes▪ <change 1>▪ <change 2>
## StatusWork in progress — not ready for review
Generated with [Claude Code](https://claude.com/claude-code)EOF)PR_NUM=$(env -u GITHUB_TOKEN gh api "repos/${SLUG}/pulls" \ --method POST \ --field title="<type>(<scope>): <description>" \ --field head="<feature-branch>" \ --field base="main" \ --field body="$BODY" \ --field draft=true \ --jq '.number')echo "Created draft PR #${PR_NUM}"Report: “Draft PR #N created. Convert to ready when done.”
Don’t cleanup worktree — WIP means user will come back to it.
Option 3: Keep As-Is
Section titled “Option 3: Keep As-Is”Report: “Keeping branch
Don’t cleanup worktree.
Option 4: Discard
Section titled “Option 4: Discard”Confirm first:
This will permanently delete:- Branch <name>- All commits: <commit-list>- Worktree at <path>
Type 'discard' to confirm.Wait for exact confirmation.
If confirmed:
git checkout <base-branch>git branch -D <feature-branch>Then: Cleanup worktree (Step 5)
Step 5: Cleanup Worktree
Section titled “Step 5: Cleanup Worktree”For Options 1 and 4:
Remove the worktree (works from any branch — no checkout needed):
git worktree remove <worktree-path> --force 2>/dev/null || rm -rf <worktree-path>git worktree pruneNote: If main had dirty state and the checkout was skipped in Step 4, worktree removal still works — it doesn’t require being on main. The branch was already deleted in Step 4.
For Options 2 and 3: Keep worktree.
Quick Reference
Section titled “Quick Reference”| Option | Merge | Push | Keep Worktree | Cleanup Branch |
|---|---|---|---|---|
| 1. Create PR | auto (CI gate) | ✓ | until merged | ✓ (after merge) |
| 2. Draft PR | - | ✓ | ✓ (WIP) | - |
| 3. Keep as-is | - | - | ✓ | - |
| 4. Discard | - | - | - | ✓ (force) |
Common Mistakes
Section titled “Common Mistakes”Skipping test verification
- Problem: Merge broken code, create failing PR
- Fix: Always verify tests before offering options
Rationalizing skipping tests
- Problem: “Tests need external services” or “failures look pre-existing” used as excuse to not run them at all
- Fix: Always run tests. If they need external services, run them anyway and report results. If failures are pre-existing, prove it by checking the base branch. Never skip.
Open-ended questions
- Problem: “What should I do next?” → ambiguous
- Fix: Present exactly 4 structured options
Automatic worktree cleanup
- Problem: Remove worktree when might need it (Options 2 or 3)
- Fix: Only cleanup for Options 1 and 4
No confirmation for discard
- Problem: Accidentally delete work
- Fix: Require typed “discard” confirmation
Red Flags
Section titled “Red Flags”Never:
- Proceed with failing tests
- Merge without verifying tests on result
- Delete work without confirmation
- Force-push without explicit request
- Push directly to
main— always go through a PR - Start implementation on main/master branch without explicit user consent
Always:
- Verify tests before offering options
- Present exactly 4 options
- Get typed confirmation for Option 4
- Clean up worktree for Options 1 & 4 only
Integration
Section titled “Integration”Called by:
- subagent-driven-development (Step 7) - After all tasks complete
- executing-plans (Step 5) - After all batches complete
Pairs with:
- worktree - Cleans up worktree created by that skill
Auto-changelog:
- When in the armadillo repo, auto-generates
CHANGELOG.jsonentries before creating PR - Detects repo by checking
package.jsonname isarmadillo - Only runs when
.claude/skills/orskills.jsonfiles changed