Lands finished Git worktrees or jj workspaces, publishes, proves remote equality, and cleans up.
Resources
1Install
npx skillscat add edmundmiller/dotfiles/done Install via the SkillsCat registry.
Here's a thinking process: 1. **Analyze the Request:** - **Goal:** Write a 2-3 sentence plain-text summary in English. - **Subject:** An AI agent skill for a public software directory. - **Source Material:** A SKILL.md excerpt describing a "done" skill that handles Git worktrees/jj workspaces, publishing, proving remote equality, and cleanup.
Done
Close the current repository task completely. Preserve unrelated work.
Completion invariant
A task is done only when:
- Task changes are shaped into reviewable, green Git commits or jj changes.
- The task revision is an ancestor of the repository's actual default branch/bookmark.
- When a writable remote exists, the authoritative remote default tip equals the local default tip.
- After proof, remove a task worktree/workspace and feature branch/bookmark. Use the owning launcher for its active worktree; never delete the agent's original directory directly.
Commit/change shaping, integration, publication, proof, and cleanup are separate states. Never report done from a clean feature workspace alone.
Select the backend
Run jj root --ignore-working-copy. If it succeeds, use the jj path. Otherwise use Git. Never initialize jj during closeout.
Honor an explicit PR, local-only, no-push, squash, merge, or fast-forward request. Honor repository rules that require review or forbid direct pushes. Otherwise, Default to direct landing on the actual default branch/bookmark and publish it. A bare done authorizes this closeout; do not ask PR versus direct merge again.
Discover the remote and default destination from live state. Prefer remote symbolic HEAD, hosting metadata, or jj's trunk()/tracked bookmarks. Fall back to main or master only when the ref exists. Do not assume the remote is origin.
Git closeout
Dirty default checkout integration
Unrelated dirt in the default branch's existing checkout is not itself a
blocker after task changes are committed. Preserve it and first try the normal
safe Git path:
- Fetch the remote and create a clean temporary integration worktree at the
remote default tip. - Rebase or cherry-pick only the explicit task commits there. Never absorb
other commits merely because they are ancestors of the task tip. - Run
git -C "$default_checkout" merge --ff-only "$integration_tip".
Git will refuse before overwriting overlapping tracked or untracked work. - When it succeeds, push the default branch without force, fetch again, and
prove local/remote equality.
Report Blocked: only when Git refuses the fast-forward because the task
overlaps dirty paths, or when the default histories cannot be reconciled
without changing user-owned work. Include Git's refusal as evidence. Never move
that checkout to a preservation branch, create a preservation branch to free
the default branch, stash, reset, commit its unrelated dirt, or update its
checked-out branch ref directly. A bare done does not authorize changing the
branch meaning of another checkout.
Unrelated dirt in a non-default task worktree does not block landing its already
committed task revision through a clean default checkout. Preserve the dirt and
defer that worktree's cleanup.
Snapshot. Before any
cd, recordactive_directory=$(pwd -P)and never recompute it. Record root, path, branch/detached state, worktrees, task tip, default branch, remotes, status, and ahead/behind counts. Identify unrelated files.Commit task work. Split distinct intents. Leave unrelated dirt unstaged. Run focused checks.
Refresh. Fetch the chosen remote. Reconcile destination local-only commits and remote tip. Rebase the explicit task commits when safe; rerun checks after changed content or commit IDs.
Integrate. Prefer a fast-forward on the default branch. Use its clean checkout directly, or use a temporary integration worktree and then attempt
merge --ff-onlyin a dirty default checkout as described above. Never reset, overwrite, or blindly stash unrelated dirt.Publish. Push the default branch, not merely the feature branch. Do not bypass hooks.
Prove. After a final fetch, run:
bash "${HOME}/.agents/skills/done/scripts/verify-landing.sh" \ "$integration_tip" "$default_branch" "$remote"If the installed verifier is unavailable, require both
git merge-base --is-ancestor "$integration_tip" "$default_branch"and equality betweengit rev-parse "$default_branch"andgit ls-remote "$remote" "refs/heads/$default_branch".Clean up last. Recheck tracked/untracked files. Remove no dirty worktree.
Do not remove the worktree before the cleanup verifier passes. Before
removing any candidate worktree, require:if bash "${HOME}/.agents/skills/done/scripts/verify-workspace-cleanup.sh" \ "$active_directory" "$candidate_worktree" then git worktree remove "$candidate_worktree" fiNever delete a candidate that contains
active_directory, even aftercd
elsewhere.cd-ing out first only leaves the returning shell in a deleted
CWD. Route a recognized launcher-owned current worktree through Launcher
teardown after completing any receipt. Otherwise report cleanup deferred,
not failed. Delete a feature branch only after Git proves containment and no
preserved worktree still checks it out.
jj closeout
@ is workspace-local. Other workspaces share the repository and operation log, not the same working-copy commit. Never use repository-wide jj undo or jj op restore as routine recovery while other agents may be active.
Snapshot. Before any
cd, recordactive_directory=$(pwd -P)and never recompute it. Recordjj root,jj workspace root,jj workspace list,jj status,jj op log -n 1,jj git remote list, andjj log -r '@-::@ | trunk()'. Identify unrelated files, the task's stable change ID, and each cleanup candidate's workspace name and physical root separately.Shape task work. Resolve conflicts, describe each meaningful change, run focused checks, then create an empty successor with
jj new. Record the completed task change ID explicitly; do not blindly assume every@-belongs to this task.Refresh. Run
jj git fetch --remote "$remote". Determine the actual default bookmark and tracked remote bookmark. If the remote advanced, rebase only the task's explicit change range onto the fresh remote destination and rerun checks.Integrate. Move the local default bookmark to the rebased task tip only after proving the intended task range. Do not move unrelated bookmarks.
jj bookmark set "$default_bookmark" -r "$task_change_id"Publish. Push that bookmark through jj. Raw
git pushandjj_vcs align_pushbypass this proof contract.jj git push --remote "$remote" --bookmark "$default_bookmark" jj git fetch --remote "$remote"Prove. Use the stable task change ID; sign-on-push may rewrite its commit ID.
bash "${HOME}/.agents/skills/done/scripts/verify-jj-landing.sh" \ "$task_change_id" "$default_bookmark" "$remote"The verifier requires no task conflict, task containment by the local default bookmark, and equality among local, tracked-remote, and authoritative Git remote tips.
Clean up last. Confirm the task workspace is empty and contains no unrelated files. Before forgetting or removing a workspace, require:
bash "${HOME}/.agents/skills/done/scripts/verify-workspace-cleanup.sh" \ "$active_directory" "$candidate_workspace_path"Forget by workspace name and remove only the corresponding physical path
after the verifier succeeds. Never remove the workspace containingactive_directory, even aftercdelsewhere. Route a recognized
launcher-owned current workspace through Launcher teardown after
completing any receipt. Otherwise preserve it and report cleanup deferred.
Preserve other workspaces and bookmarks.
Receipt
If agent-start produced a receipt, finish it only after proof. Record the proved local and authoritative remote tips:
hey agent-complete "$receipt" \
--revision "$task_revision" \
--local-tip "$local_tip" \
--remote-tip "$remote_tip"A mismatch records false_done and fails. Do not edit the receipt to turn it green.
Launcher teardown
Launcher teardown applies only after landing proof, receipt completion, and a
fresh check that the recorded task checkout has no tracked, untracked, or
conflicted files. Prepare the final report first. Do not manually remove the
active directory.
Herdr
When HERDR_ENV=1, invoke the teardown script with the recorded task root as
the last tool action:
bash "${HOME}/.agents/skills/done/scripts/teardown-herdr-worktree.sh" \
"$task_root"The script requires HERDR_WORKSPACE_ID, proves the active directory and clean
Git root match the recorded task root, parses Herdr's structured worktree
provenance, and executes native worktree removal without --force. It never
closes a normal or parent Herdr workspace. Treat any refusal as deferred cleanup
and report its exact reason.
Codex Desktop
When CODEX_THREAD_ID is set, first confirm the recorded task root is the
calling thread's Codex-owned worktree under${CODEX_HOME:-$HOME/.codex}/worktrees/. Do not archive a same-directory or
local-checkout thread merely because the variable exists.
Use the current-thread set_thread_archived tool with { "archived": true },
omitting threadId and hostId. The background archive lets Codex Desktop own
thread and worktree cleanup. Never run git worktree remove against the active
Codex worktree. If the tool is unavailable or ownership cannot be proved,
report cleanup deferred and tell the user to archive the thread in Codex
Desktop.
Final report
Lead with the user-visible result in plain language: what changed, whether it works, and whether the user needs to act. For a bare done after an earlier completion report, say explicitly that no additional product change was made; only closeout was re-verified.
Follow with one compact proof line: Landed on <destination>; remote verified; <checks> passed. Include hashes, local/remote tip equality, worktree, branch, receipt, and unrelated-file details only when the user asks or they require user action. Otherwise say only that unrelated work was preserved. Never lead with backend, branch cleanup, raw hashes, or other repository mechanics.
If any landing invariant is unproved, lead with Blocked: or Local only: and give the exact next action.