006 / 024 · Dev · v0.17.1 · MIT
ship
ship takes a change from your working tree to a merged pull request: branch off, run the project's own tests, commit, open the pull request, wait for CI, merge, then clean up the worktree. Which steps ran, which were skipped, and why, all go in the report.
- Arguments
Each optional.
[branch-name][--dry-run][--no-test][--draft][--base=<branch>][--bump=<version|major|minor|patch>][--merge-strategy=<squash|merge|rebase>]
- When it fires
The prompts CI scores this skill against, on every release.
Fires on
- Ship it.
- 开个 PR 合了
- Get this merged once CI is green.
Stays out of
- Tag a release and publish the package.
- Just pull the latest main.
- 邮件写好了,帮我发出去
- 这样发出去会不会太冲,帮我改客气点
The preflight and the execution plan
Step 0 is a preflight. It reads the repository's state — which branch you are on, whether the tree is clean, whether there are unpushed commits, whether this branch already has an open pull request — and prints an execution plan marking every later step RUN or SKIP. The rest of the run follows that plan exactly: a step marked SKIP does not run, and no step outside the plan is added along the way. --dry-run prints the plan and stops.
The reason for the plan is that "ship" means different things in different states: uncommitted changes on the base branch, an open pull request on a feature branch, and a clean tree whose local base carries commits the remote does not are three different paths. Settling the state first means nothing downstream has to guess.
A clean tree on the base branch with nothing unpushed and no open pull request exits at step 0 and reports that there is nothing to ship. One case looks like that and is not: somebody committed to main by hand, so the local base carries commits the remote does not. That branches off and carries them rather than reporting no work.
Where the test, lint and version commands come from
The test command, the lint command and the version bumper are read from wherever the project declares them — a script in package.json, a justfile target, a Makefile rule, and the CI configuration. Matching stops at the first hit; a project that declares none skips that step, and the report says skipped rather than passed. A failing test is retried at most twice before the run stops and asks.
The version bumper is the exception: found or not, it runs only when --bump is passed. Whether a change is a release is the author's call rather than a property of the diff. Detected and not asked for is reported as skipped.
The credential scan before the commit
A scan runs before anything is staged. It reads the content of the change rather than file names, and it covers untracked files. Untracked files have to be read separately because git diff never mentions them, and a freshly created config.local.json holding a live token is exactly that case.
A hit stops the run and asks. It does not decide alone: fixtures and documentation legitimately hold credential-shaped strings, so what comes back is the file, the line and a question.
Staging is by explicit path, never git add -A. Untracked files it cannot classify are collected into a single prompt and asked about in one round trip.
Waiting for CI
"No checks reported" means two different things minutes apart. A pull request opened seconds ago reports none because GitHub has not registered the workflow run yet, and that looks identical to a repository with no CI at all. Merging on the second reading ships without the checks the project wrote.
So the preflight has already established whether the repository declares a workflow. Where one is declared, the run re-polls for up to a minute before concluding; if nothing registers, the report says CI skipped with the number of workflows declared. Where none is, step 5 is skipped outright.
Merge states that need a person — a missing required review, a failing non-required check, a conflict with the base — stop and ask. Conflicts are never resolved automatically.
What it does not do
It does not tag a release, publish a package, or deploy; those are separate work that follows a merge. It does not write a commit message without pushing it. It does not force-push the base branch, does not resolve merge conflicts for you, and does not remove the worktree the run is standing in — that one is reported as kept, because the deletion cannot be carried out from inside it.
Also in Dev
6 more skills · one installsync
/dev:syncsync does one thing: it lays out the difference between your local repository and its remote, and performs only the part of that which is unambiguously safe.
cleanup
/dev:cleanupAfter a change ships, the leftovers stay: the local branch, its copy on the remote, the worktree it was built in, and sometimes a directory holding nothing but ignored files. cleanup removes those, and keeps and reports anything whose history it cannot account for.
retitle
/dev:retitleA conversation list gets hard to use not because the names are ugly but because they carry nothing you can sort on.
steward
/dev:stewardsync, ship and cleanup each work in the repository you are standing in. steward is the one that runs that loop across every repository you have actually been working in — it finds them, sweeps them, and hands back a single report of what is waiting on you: which branches are ready to merge, which pull requests are blocked and on what, which branches were written and never opened, and which worktrees a live session is still sitting in.
reunite
/dev:reunitesessions answers the moment you sign in with your other account and the sidebar is suddenly almost empty.
handoff
/dev:handoffhandoff keeps readable conversation history available in Claude Code and Codex on the same machine.