plugins-copilot/git-tools/skills/ship/SKILL.md
Use this skill whenever the user asks to take in-flight work through the full merge lifecycle. Triggers on shorthand prompts like "branch/commit/pr/watch/master/pull", "pr/watch/master/pull", "watch/master/pull", "push+pr then watch and master/pull", "branch/pr/merge/master/pull", "send it", "ship it", "merge it", or terse single words "watch", "merge", "pull" when the context implies finishing a PR. Drives staging → commit → push → PR create → CI watch → merge wait → checkout master → pull, using native host tooling (gh on GitHub, host-native equivalent on Gitea) for every call, and cleans up the worktree if one was used. Skips steps that are already complete instead of redoing them.
npx skillsauth add st0nefish/claude-toolkit shipInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Purpose. Take whatever in-progress work exists and drive it through the canonical lifecycle: stage → commit → push → PR → watch CI → wait for merge → return to default branch → pull → clean up the worktree if one was used. Skip any step that's already done.
Use native host tooling for every GitHub/Gitea call. On GitHub repos, prefer gh. On Gitea or other hosts, use the equivalent host-native CLI or API tooling. See the sibling git-cli skill for the full command reference.
Trigger on any shorthand that means "finish this PR":
branch/commit/pr/watch/master/pull (and reorderings)pr/watch/master/pullwatch/master/pullpush+pr, then watch and master/pullbranch/pr/merge/master/pullsend it, ship it, merge it, let's merge thatwatch, merge, pull when the surrounding context already involves a PRIf the user explicitly invokes /git-tools:ship, use this same procedure.
Run these steps in order. Detect and skip any step that is already complete; do not redo work.
git status --porcelain=v1 -b
git rev-parse --abbrev-ref HEAD
Establish: dirty working tree? on default branch or feature branch? upstream set? Determine the default branch via git symbolic-ref --quiet --short refs/remotes/origin/HEAD | sed 's|^origin/||'.
Also detect whether this checkout is a linked git worktree — this gates the cleanup in step 7:
git_dir=$(git rev-parse --git-dir)
git_common_dir=$(git rev-parse --git-common-dir)
# Linked worktree iff the two differ. Normalize with realpath first — on macOS
# /tmp is a symlink to /private/tmp, so a raw string compare can falsely differ.
if [ "$(realpath "$git_dir")" != "$(realpath "$git_common_dir")" ]; then
echo "linked worktree" # step 7 cleans it up after a confirmed merge
else
echo "main checkout" # step 7 keeps its current behavior
fi
This read is non-destructive; it only records context for later.
If there are uncommitted changes:
git status --short + git diff --stat).git add -A or git add . (avoids accidentally including secrets or generated files).feat: add tea CLI classifier), optional body paragraph for larger changes, optional bullet list of specific changes. Match the repo's existing commit-log style — check git log --oneline -20 first.Skip this step if the working tree is clean.
If on the default branch (master/main), create a feature branch with a conventional prefix (feat-, fix-, chore-, docs-, refactor-, test-) before pushing:
git checkout -b <prefix>-<short-description>
Skip if already on a non-default branch.
git push -u origin HEAD
-u is needed only on first push; if upstream is already tracked, plain git push is fine. If the push fails because the remote has new commits, do not force-push — investigate first (someone else may have pushed; git pull --rebase, resolve, retry).
Check first (on GitHub):
gh pr list --head "$(git rev-parse --abbrev-ref HEAD)" --state open --json number,url
On Gitea or other hosts, use the equivalent host-native PR listing command.
If no PR exists for the current branch, create one (on GitHub):
gh pr create --title "..." --head "$(git rev-parse --abbrev-ref HEAD)" --body-file - <<'EOF'
## Summary
...
## Test plan
...
EOF
Pull a concise title from the most recent commit message; pull the body from the commit body plus a short test plan.
Poll the run associated with the current branch until it passes, fails, or the PR merges. On GitHub:
gh run list --branch "$(git rev-parse --abbrev-ref HEAD)" --limit 1 --json databaseId,status,conclusion,url
gh run watch <run-id> --exit-status
On Gitea or other hosts, use the host-native CI listing/watching command. Time-box polling (e.g. 600s overall, 30s interval) and surface the failure or timeout clearly. Output should include status (pass|fail|closed|timeout|no-workflow), url, duration, and on fail the failing job names with logs.
On failure: stop the orchestrator and surface the failure to the user. Do not push fixes silently — let them decide.
If the repo has an auto-merge bot (this repo's st0nefish-ci enables auto-merge on PR open), poll the PR until merged:
gh pr view <number> --json state,mergedAt
Stop when mergedAt is non-null (status: merged) or state becomes CLOSED without merge. Time-box this poll (e.g. 300s overall, 15s interval). On Gitea, use the host-native PR view command.
Skip this step if the repo does not auto-merge. Never call gh pr merge to merge manually unless the user explicitly asks — that bypasses CI gating and the auto-merge workflow.
Always determine the default branch first:
default=$(git symbolic-ref --quiet --short refs/remotes/origin/HEAD | sed 's|^origin/||')
Case A — main checkout (step 0 reported main checkout): unchanged behavior.
git checkout "$default"
git pull
Case B — linked worktree (step 0 reported linked worktree): only proceed after the branch has actually merged (step 6 confirmed the merge). Worktree teardown is destructive and hard to reverse — never run this block on an unmerged branch.
Capture identifiers while still inside the linked worktree (these are per-worktree):
linked_wt=$(git rev-parse --show-toplevel)
feature_branch=$(git rev-parse --abbrev-ref HEAD) # literal "HEAD" if detached
main_wt=$(git worktree list --porcelain | awk 'NR==1 && /^worktree /{sub(/^worktree /,""); print}')
Cleanliness gate. If the worktree has uncommitted or untracked changes, do not remove it automatically:
git -C "$linked_wt" status --porcelain
git worktree remove --force) before doing anything destructive.Move into the main worktree and refresh — you cannot reliably remove the worktree you are standing in (its working directory becomes invalid):
cd "$main_wt"
git checkout "$default"
git pull
Remove the merged worktree and prune stale metadata:
git worktree remove "$linked_wt" # add --force ONLY after explicit user approval when dirty
git worktree prune
Delete the now-merged local branch (skip when feature_branch is HEAD, i.e. detached):
git branch -d "$feature_branch"
-d is the safe form — it refuses if git doesn't see the branch as fully merged. A failure usually means a squash merge (the branch tip is not an ancestor of the merge commit). Tell the user and ask before force-deleting:
git branch -D "$feature_branch" # only after the user confirms
Returning to the default branch is the step most often forgotten — always run it after merge (in either case) unless the user explicitly says otherwise.
Each step probes state first and skips when there's nothing to do. Re-running the orchestrator after a partial run should pick up from wherever it left off — e.g. if the PR is already open, jump to step 5; if CI is already passing, jump to step 6; if the PR is already merged, jump to step 7.
The step-7 worktree cleanup is gated twice: it runs only when step 0 detected a linked worktree and the branch has merged. In the main checkout it is skipped entirely (current behavior preserved), and re-running after the worktree is already removed is a no-op — the worktree detection short-circuits.
tea directly on Gitea — use the host-native CLI through its own canonical entry point or the API.--force-with-lease).gh pr merge / tea pr merge to manually merge unless the user asked — the auto-merge bot handles this.git worktree remove the worktree you are standing in — cd into the main worktree first, then remove.--force-remove a worktree with uncommitted or untracked changes without explicit user approval.git branch -D (force-delete) without confirming first — a git branch -d refusal usually signals a squash merge, not work that's safe to discard blindly.git refuses with already used by worktree at ...).After the lifecycle completes, give the user a one-line summary: PR number + URL, CI status, final state (merged/blocked), and that the workspace is back on the default branch.
development
Start work from your description — explore the codebase and plan
data-ai
Multi-phase, multi-agent feature workflow: spec → plan → refine → divide → execute → review. Invoke when the user escalates a session-start/session-issue flow to orchestration, or asks to run a non-trivial feature (multiple files, design ambiguity, cross-cutting concerns, correctness-critical paths) through the full multi-agent workflow. For small fixes, prefer session-start.
tools
Browse open issues, pick one, and start work on it
tools
Review, clean up, and open a PR to finalize the work