---
name: finish
description: Integrates reviewed work by testing it against the branch it will merge into, opening or updating the pull request, and merging or discarding only on the user's word. Use when the user says "finish this", "wrap it up", "open the PR", "update the PR", "ready to merge", "merge it", "clean up the worktree", or "throw this branch away".
---

# Finish

Review is addressed and the branch is ready to leave its worktree. This skill proves the work
still holds once it meets the branch it merges into, writes the pull request so a reviewer can
audit every decision made without them, and cleans up after the merge. Merging into the default
branch is the user's decision, and so is throwing work away. Everything before that point is
yours to do without asking.

## Steps

1. **Confirm the branch is ready.** A `compost:review` outcome is recorded for every issue in
   the work: its step 6 comment on the issue, or its result in this session for work with no
   issue. If one is missing, stop and run `compost:review` now, whatever the size of the diff.
   A skipped review is a gap to close here, never a line under Risk, and so is one recorded as
   incomplete on an axis. Every `compost:review`
   finding is fixed or answered on the issue with a `file:line` reason. `compost:verify` ran on
   the current HEAD. If commits landed after its last run, run it again first.

2. **Record where you are.** Capture these now, because cleanup runs from another directory:

   ```bash
   worktree_path=$(git rev-parse --show-toplevel)
   branch=$(git branch --show-current)          # empty on a detached HEAD
   main_root=$(git worktree list --porcelain | sed -n '1s/^worktree //p')  # the main checkout
   ```

   A detached HEAD means the harness manages this workspace. Push it as a named branch
   (`git push origin HEAD:refs/heads/<issue>-<slug>`) and leave the workspace in place.

3. **Name the merge target.** It is the branch this work forked from. Take it from the issue: a
   parent issue may name an integration branch for its sub-issues. Otherwise it is the default
   branch (the tracker doc's **Default branch** command; with none, `git symbolic-ref --short
   refs/remotes/origin/HEAD` minus its `origin/`). State it in the PR.

4. **Run the suite on the actual merge result.** A green branch proves only the branch. Start
   with the cheap check:

   ```bash
   git fetch origin
   git merge-base --is-ancestor origin/<target> HEAD
   ```

   Exit 0 means the target hasn't moved past your branch, so the merge result is HEAD's own
   tree and `compost:verify`'s run on HEAD is the merge-result run. Exit 1 means the target
   moved: merge it into your branch (`git merge --no-ff --no-edit origin/<target>`), resolve any
   conflict, and go back through `compost:verify`; the branch then holds the merge result. This needs no second
   worktree and no `git -C`, which a worktree-isolated session refuses. Do not rebase a branch
   you have already pushed. A red merge result stops here and goes to `compost:diagnose`. Any
   other exit is an error (a wrong target name, a failed fetch): fix it before going on.

   Where your branch must not take the target's commits yet, build the merge in a throwaway
   worktree instead: `git worktree add --detach "$main_root/.worktrees/merge-check-<issue>"
   origin/<target>`, `cd` into it, `git merge --no-ff --no-edit <head>` (your branch's
   `git rev-parse HEAD`, taken before the `cd`, so a detached HEAD works too), install
   dependencies, run the full suite and the recorded gates, then `cd` back and
   `git worktree remove` it. A worktree-isolated session may refuse to work outside its own
   worktree; then take the merge-into-the-branch path above.

5. **Open or update the pull request.** Push the branch and open a draft PR against the target,
   or update the open one. The body has five sections: Goal, Decisions, Changes, Validation,
   Risk. Decisions summarizes the rulings posted as issue comments. Validation carries
   `compost:verify`'s evidence and the merge-result run. Risk calls the change a one-way or
   two-way door with its blast radius, and names whatever is unproven, deferred, or excluded
   from coverage. End the body with a `Closes` line for the issue, or for
   the parent and every sub-issue (`Closes #12, closes #13, closes #14`), keeping any the PR
   already has; leave out `ready-for-human` sub-issues, and the parent while any is open. Use the
   [PR body template](references/pr-body.md) and the **Pull requests** commands in
   `docs/agents/issue-tracker.md` (create as a draft, edit the body). Use those, not the forge CLI's
   porcelain: `gh pr create` goes through GraphQL, which Claude Code cloud sessions block. With a
   local tracker, post the same body on the issue file instead.

6. **Stop for the merge decision when the target is the default branch.** Report the PR link,
   the merge-result run, and three options: merge, keep the branch open for more feedback, or
   discard. Wait for the answer. A merge into an integration branch that is not the default
   branch does not need to wait; go on to step 7.

7. **Merge with a merge commit, then clean up.** When told to merge:
   - With an open PR, mark it ready (the forge refuses to merge a draft), then merge through the
     forge with a merge commit, using the tracker doc's **Ready for review** and **Merge** commands (in a cloud session, the
     cloud variant of ready).
     That is the forge's `--no-ff`. Never squash or rebase unless the user asks for it.
   - Without a PR, build the merge commit in this worktree on a detached HEAD, so the main
     checkout is never touched and no `git -C` is needed:

     ```bash
     git status --porcelain -uall    # must be empty: commit or ask about anything listed
     git fetch origin
     git switch --detach origin/<target>
     git merge --no-ff -m "Merge branch '$branch' into <target>" "$branch"
     ```

     If the merge stops on conflicts, the target moved after step 4: `git merge --abort`,
     `git switch "$branch"`, and go back to step 4. Otherwise run the suite on the result. If it
     is red, `git switch "$branch"` and stop with the branch and worktree in place and nothing
     pushed. If it is green, `git push origin HEAD:<target>`. A rejected push also means the
     target moved: `git switch "$branch"` and go back to step 4. Then close the issue, or the
     parent, the way the tracker says (for a local tracker, `Status: closed` and a closing note
     naming the merge commit).

     With no remote there is nothing to push to, and the target branch has to move in the
     checkout that has it. That needs a session that is not worktree-isolated: `cd
     "$main_root"`, `git switch <target>`, `git merge --no-ff "$branch"`, then run the suite
     there.

   Then clean up. Fetch, and check `git merge-base --is-ancestor "$branch" origin/<target>` (or
   `<target>` with no remote): any exit but 0 means the branch isn't merged, so stop and say so.
   On 0, remove the worktree first, since git won't delete a branch a worktree has checked out.
   Use the harness's exit tool (such as `ExitWorktree` with remove) if it created the worktree;
   otherwise `cd "$main_root"` and `git worktree remove "$worktree_path"`. If removal is refused
   because of uncommitted files, list them (`git status --porcelain -uall` inside the worktree)
   and ask the user whether to commit, move, or delete them. Never add `--force` on your own.
   Then delete the branch with `git branch -D "$branch"` (the ancestry check already proved it
   merged, which `-d` can't see when the local target is stale) and delete the remote branch (`git push origin --delete "$branch"`).

8. **Discard only on request, and only after the user types "discard".** When the user asks to
   throw the work away, show what will be lost: the branch, the commits
   (`git log --oneline <target>..$branch`), the worktree path, and any open PR. Ask them to type
   `discard`. A paraphrase such as "yeah, get rid of it" is not the confirmation. Once the word
   arrives, close the PR (the tracker doc's **Close without merging**), remove the worktree as in step 7, delete the branch with
   `git branch -D "$branch"`, and delete the remote branch (`git push origin --delete "$branch"`).

9. **Capture what was learned.** If the work turned up a gotcha, a non-obvious root cause, or a
   verdict reached from measurement, and the compound-knowledge plugin is installed, use
   `compound-knowledge:compound-capture` to record it. When no `compost:implement` run worked this
   parent, tick the issue in its parent's checklist; otherwise implement already did.

## Next moves

- `compost:implement` when the parent issue still has unblocked issues.
- `compost:build` when review or the user asks for changes on the open PR.
- `compost:diagnose` when the merge result is red.
- `compost:pause` when stopping with the PR open and work still in flight.
- `compost:retro` once the PR is merged, to file what in the environment would have made the run go better.
