--- description: Complete a feature branch with a forced-choice menu of outcomes (merge locally / push + open PR / keep as-is / discard). Runs verification before offering options (tests, lint, typecheck per the project's conventions — delegates to `verification.md`). Requires typed confirmation for destructive deletion (no accidental work loss). Handles git worktree cleanup correctly: tears down for merge and discard, preserves for keep and push (where the worktree may still be needed for follow-up review or fixes). References existing rules for commit conventions (`commits.md`), review discipline (`review-code`), and verification (`verification.md`) — this skill is the workflow scaffold, not a re-teach of the underlying standards. Use when implementation is complete and you need to wrap up a branch. Do NOT use for mid-development merges (that's normal git flow), for the wrap-up *of a whole session* (different scope — session-end is narrative + handoff, not branch integration), for creating a new branch (no skill for that — just `git checkout -b`), or when review hasn't happened yet (run `/review-code` first, then this). disable-model-invocation: true --- # /finish-branch Complete a development branch cleanly. Verify, pick one of four outcomes, execute, clean up. ## When to Use - Implementation is done, tests are written, you believe the branch is ready to move forward - You're about to ask "okay, now what?" — this skill exists to stop you asking and start you choosing - You need to integrate, preserve, or discard a branch's work deliberately ## When NOT to Use - Mid-development merges — that's regular git flow, not "finishing" a branch - Full session wrap-up (closing the day's work, recording context for next time) — different scope, not about branch integration - Creating a new branch — just `git checkout -b ` - Before review has happened on a significant change — run `/review-code` first; this skill assumes the work has been judged ready ## Phase 1 — Verify Before offering any option, run the project's verification. Delegate to `verification.md` discipline: - Tests pass (full suite, not just the last one you wrote) - Linter clean (no new warnings) - Type checker clean (no new errors) - Staged diff matches intent (no accidental additions) Commands vary by stack. Use what the project's Makefile, `package.json` scripts, or convention dictates. Examples: - JavaScript / TypeScript: `npm test && npm run lint && npx tsc --noEmit` - Python: `pytest && ruff check . && pyright` - Go: `go test ./... && go vet ./... && golangci-lint run` - Elisp: `make test && make validate-parens && make validate-modules` **If verification fails:** stop. Report the failures with file:line, do not offer the outcome menu. The branch isn't finished — fix the failures and re-invoke `/finish-branch`. **If verification passes:** continue to Phase 2. If any verification step fails and triggers sub-investigations, follow `subagents.md` — dispatch a fresh fix-agent per failure with pasted error context rather than retrying in the orchestrator. ## Phase 2 — Determine Base Branch The four outcomes need to know what the branch is being integrated into. This is the **branch name** — the thing you check out, pull, merge into, and pass to `gh pr create --base`. It is not a merge-base SHA; the merge-base is a separate value computed below. Resolve the base branch name in priority order: 1. **The base of an existing PR.** If the branch already has a PR open, that PR's base is authoritative: ```bash gh pr view --json baseRefName --jq .baseRefName 2>/dev/null ``` 2. **The remote's configured default branch.** When there's no PR, ask the remote what it defaults to and strip the result down to the bare branch name: ```bash git symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null | sed 's@^origin/@@' ``` `refs/remotes/origin/HEAD` is set by `git clone`; if it's missing, refresh it with `git remote set-head origin --auto` and retry. 3. **Ask the user.** If neither resolves: "I couldn't determine the base branch from an open PR or the remote's default. What's the base branch for this work?" Don't guess. Store the answer as `` (the name) and use it everywhere a branch name is needed. **Compute the merge-base separately.** A few steps need the merge-base *commit*, not the branch name — the commit range to integrate, the count of branch-only commits, the discard preview. Derive it from the resolved branch name: ```bash git merge-base HEAD # the commit where this branch diverged ``` Keep the two values distinct: `` is a ref name for checkout/pull/merge/PR; the merge-base SHA is only for computing what's on the branch. Also collect: - Current branch name: `git branch --show-current` - Commit range to integrate: `git log ..HEAD --oneline` - Unpushed commits on the remote: `git log @{u}..HEAD` if the branch tracks a remote, otherwise all commits are local - Whether the current directory is in a git worktree (see Phase 5 for the detection command) These details feed the outcome prompt and the execution phases. ## Phase 3 — Offer the Menu Present exactly these four options. Don't editorialize. Don't add a "recommendation." The user picks. ``` Branch is ready. It has N commits on top of . What would you like to do? 1. Merge locally into (integrate now, delete the branch) 2. Push and open a Pull Request (remote integration flow) 3. Keep as-is (preserve branch and worktree for later) 4. Discard (delete branch and commits — requires confirmation) Pick a number. ``` If the branch already has a remote and the user chose 1 (merge locally), note: "The branch is pushed to `origin/`. After merging locally, do you also want to delete the remote branch?" Ask; don't assume. **Stop and wait for the answer.** Don't guess, don't infer from context, don't proceed to Phase 4 until the user picks. ## Phase 4 — Execute the Chosen Outcome ### Option 1 — Merge Locally **Pre-flight checks — run before touching any branch.** 1. **Worktree must be clean.** A checkout or merge on a dirty tree can fail mid-way or clobber uncommitted work: ```bash git status --porcelain ``` If this prints anything, stop. Surface the dirty files and ask whether to commit, stash (`git stash push -u`), or abort. Don't auto-stash — the user decides what happens to their uncommitted work. 2. **Protected-branch awareness.** If `` is a protected branch (often `main`, `master`, or `develop`, but confirm against the project's convention — a local push to a protected branch may be rejected or violate team policy), say so and confirm the user actually wants a local merge rather than a PR. Direct merges into a protected base are usually a mistake. Now integrate. Check out the base and update it only if it tracks a remote: ```bash git checkout # Update the base from its upstream only if one is configured. if git rev-parse --abbrev-ref --symbolic-full-name @{u} >/dev/null 2>&1; then git pull --ff-only # fast-forward only; a non-ff base means it diverged — surface, don't merge blindly else echo "No upstream for ; skipping pull, merging local state as-is." fi ``` **Integration shape is a team-policy choice, not a hardcoded flag.** Ask which the project uses before integrating: - **Merge commit** — `git merge --no-ff ` preserves the branch point in history (the default where the team keeps merge commits). - **Rebase** — `git rebase ` then fast-forward the base, for a linear history. After rebasing, `git checkout && git merge --ff-only `. Pick per the project's convention; if it's unclear, ask. Don't assume `--no-ff`. **Re-verify the merged result.** Run tests / lint / type check on the merged state — the merge may have integrated cleanly at the text level while breaking semantically. If verification passes: ```bash git branch -d # safe delete (refuses unmerged branches, which this one is merged) ``` If the branch had a remote: - If user confirmed removing the remote: `git push origin --delete ` - Otherwise: leave the remote branch, note that the user should clean it up manually Clean up the worktree (Phase 5). ### Option 2 — Push and Open a Pull Request ```bash git push -u origin # -u sets upstream on first push ``` Open the PR. Use the project's `gh` CLI (install via `deps` target if missing): ```bash gh pr create \ --base \ --title "" \ --body "$(cat <<'EOF' ## Summary ## Test Plan - [ ] EOF )" ``` **Commit message and PR body discipline:** no AI attribution, no "🤖 Generated with" footer, conventional message style — see `commits.md`. If the project has a `.github/pull_request_template.md`, use it instead of the template above. **Do NOT clean up the worktree.** The branch is not yet merged; you may need the worktree for reviewer feedback, fixes, or rebase. (Phase 5 table.) ### Option 3 — Keep As-Is No git state changes. Report: ``` Keeping branch as-is. Worktree preserved at (or "same working directory" if not a worktree). Resume later with `git checkout ` or re-invoke `/finish-branch`. ``` **Do NOT clean up the worktree.** The user explicitly wants to come back. ### Option 4 — Discard **Confirmation gate — required.** Write out what will be permanently lost: ``` Discarding will permanently delete: - Branch: - Commits that exist only on this branch (N commits): - Worktree at (if applicable) - Remote branch origin/ (if it exists) This cannot be undone via `git checkout` — only via the reflog (≤30 days by default). To proceed, type exactly: discard ``` **Wait for the user to type the literal word `discard`.** Anything else — "yes," "y," "confirm," a number — does not qualify. Re-prompt. If confirmed: ```bash git checkout git branch -D # force delete git push origin --delete 2>/dev/null # delete remote if it exists; ignore error if not ``` Clean up the worktree (Phase 5). ## Phase 5 — Worktree Cleanup | Option | Cleanup worktree? | |---|---| | 1. Merge locally | **Yes** | | 2. Push + PR | **No** (may still be needed for review feedback) | | 3. Keep as-is | **No** (user explicitly wants it) | | 4. Discard | **Yes** | **Detecting whether you're in a worktree.** Don't grep `git worktree list` for the branch name — branch names can collide, substring-match, or be detached. Compare the per-worktree git dir against the shared common dir instead; they differ only inside a linked worktree: ```bash test "$(git rev-parse --git-dir)" != "$(git rev-parse --git-common-dir)" \ && echo "in a linked worktree" || echo "in the main working tree" ``` To find the worktree's path for the removal command, read the machine-readable listing rather than parsing columns: ```bash git worktree list --porcelain # `worktree ` / `branch refs/heads/` per block ``` **If cleanup applies:** ```bash git worktree remove # non-destructive if clean ``` If `git worktree remove` refuses (unclean state somehow): surface the reason to the user. Don't force removal without their consent — a dirty worktree may contain work they intended to rescue. **If no cleanup:** done. Report final state. ## Output Short final report, not a celebration: ``` Branch : - Outcome: <1 | 2 | 3 | 4> - - Next: ``` No emojis. No "🎉 all done!" No AI attribution. See `commits.md`. ## Critical Rules **DO:** - Run verification before offering the outcome menu. No exceptions. - Present exactly four options, clearly labeled. The forced choice is the point. - Require the literal word `discard` for Option 4. - Re-verify after a merge (Option 1) — merges can integrate textually while breaking semantically. - Clean up worktrees only for Options 1 and 4. **DON'T:** - Offer options with failing verification — the branch isn't finished. - Editorialize the menu ("you should probably do option 2"). The user picks. - Accept "y" or "yes" for the discard gate. Literal word `discard`. - Clean up worktrees after Option 2 or 3 — the user needs them. - Add AI attribution to commit messages, PR descriptions, or output. ## Common Mistakes This Prevents - **Open-ended "what now?" at the end of work.** Natural but corrosive — the user either has to improvise the workflow or restate their preference each time. The four-option menu ends the improvisation. - **Accidental destructive deletes.** "Discard this branch?" → "y" → 3 days of work gone. The typed-word gate turns one muscle-memory keystroke into a deliberate action. - **Merge-then-oops.** Text-level merge completes; semantic integration is broken; the user didn't notice because they didn't re-run the tests on the merged result. Phase 4 Option 1 re-verifies. - **Worktree amnesia.** Cleaning up a worktree after Option 2 (push + PR) means losing local state just when the reviewer asks for a fix. The cleanup matrix keeps the worktree exactly when it's still needed. - **"Generated by Claude Code" trailing into a PR.** The no-attribution rule from `commits.md` applies here too — the PR body is committed content under the project's identity, not Claude's. ## Integration with Other Skills **Before this skill:** - `/review-code` — run a review on the branch before finishing it for significant changes - `/arch-evaluate` — if the branch touches architecture, audit against `.architecture/brief.md` **After this skill:** - If Option 2 (PR opened): reviewer feedback comes in → address → `/finish-branch` again on updated state - If Option 1 (merged locally): branch is done; if this closes a ticket / ADR, update tracking - If Option 3 (kept): resume later; re-invoke when ready - If Option 4 (discarded): often paired with `/brainstorm` or `/arch-design` to retry the problem differently **Companion rules (not skills) this skill defers to rather than re-teaching:** - Verification discipline → `verification.md` - Commit message conventions + no AI attribution → `commits.md` - Review discipline (for anything pre-merge) → `review-code`