1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
|
---
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 <name>`
- 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 `<base-branch>` (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 <base-branch> HEAD # the commit where this branch diverged
```
Keep the two values distinct: `<base-branch>` 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 <base-branch>..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 <branch-name> is ready. It has N commits on top of <base-branch>.
What would you like to do?
1. Merge locally into <base-branch> (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/<branch-name>`. 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 `<base-branch>` 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 <base-branch>
# 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 <base-branch>; 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 <branch-name>` preserves the branch point in history (the default where the team keeps merge commits).
- **Rebase** — `git rebase <base-branch> <branch-name>` then fast-forward the base, for a linear history. After rebasing, `git checkout <base-branch> && git merge --ff-only <branch-name>`.
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 <branch-name> # 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 <branch-name>`
- 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 <branch-name> # -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 <base-branch> \
--title "<subject from the most recent commit, or user-provided>" \
--body "$(cat <<'EOF'
## Summary
<two or three bullets summarizing what changed, pulled from the commit range>
## Test Plan
- [ ] <steps the reviewer should take to verify>
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 <branch-name> as-is.
Worktree preserved at <worktree-path> (or "same working directory" if not a worktree).
Resume later with `git checkout <branch-name>` 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: <branch-name>
- Commits that exist only on this branch (N commits):
<list, abbreviated if very long>
- Worktree at <worktree-path> (if applicable)
- Remote branch origin/<branch-name> (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 <base-branch>
git branch -D <branch-name> # force delete
git push origin --delete <branch-name> 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 <path>` / `branch refs/heads/<name>` per block
```
**If cleanup applies:**
```bash
git worktree remove <worktree-path> # 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 <branch-name>:
- Outcome: <1 | 2 | 3 | 4>
- <specific state change, e.g. "merged into main; branch deleted; worktree removed">
- Next: <what the user would do next — e.g. "await PR review", "resume work", "start a new branch">
```
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`
|