finish

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 use...

npx skills

npx skills add JoshuaOliphant/compost --skill finish

Works for Codex, Cursor and other harnesses. Use -a <agent> to target one, e.g. -a codex.

Claude Code

/plugin marketplace add JoshuaOliphant/compost
/plugin install compost@compost

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.

  1. 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.

  2. 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.

  1. 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 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.

  2. 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.

  3. Merge with a merge commit, then clean up. When told to merge:

  4. 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.
  5. 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").

  1. 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").

  2. 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.

← compost