Imported from wilsonfaustino/ai-tools (
skills/ship/SKILL.md). Install upstream withnpx skills add wilsonfaustino/ai-tools --skill ship. Copyright stays with the author.
Ship
Push current branch to remote and create/update a GitHub PR with intent-driven descriptions.
Flags
| Flag | Effect |
|---|---|
--draft |
Create PR as draft, no prompt |
--ready |
Create PR as ready, no prompt |
--base <branch> |
Override detected base branch |
If neither --draft nor --ready is passed, ask the user: "Open as draft or ready?"
Pre-flight Checks
Run these in parallel:
gh auth status
git rev-parse --abbrev-ref HEAD
git status --porcelain
gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name'
Resolve base branch: if --base flag provided, use that value. Otherwise use the result of gh repo view.
Then run (now that base is known):
git log <base>..HEAD --oneline
Hard blocks (refuse to proceed)
- Branch IS the base branch (e.g.,
main,master,develop) - No commits ahead of
<base> - Detached HEAD
gh auth statusfails
Soft blocks (ask user)
- Uncommitted changes: offer commit, stash, or abort
- No upstream: push with
-uafter confirmation - PR already merged/closed: warn and ask
Never do
git push --forceor--force-with-leasegit reset,git clean,git checkout .gh pr close,gh pr merge- Delete any branch
- Use em-dashes in generated text
Push
# First push (no upstream)
git push -u origin <branch>
# Subsequent
git push
Detect Existing PR
gh pr list --head <branch> --state open --json number,url
If a PR exists, proceed to the Update Existing PR section after gathering context.
PR Title
Derive from branch name:
feat/phase-c2-user-migration -> feat(phase-c2): user migration
fix/jwt-expiration-edge-case -> fix(jwt-expiration): edge case
docs/update-readme -> docs(update): readme
chore/cleanup-deps -> chore(cleanup): deps
Rules:
- Split on first
/to gettypeandslug - Split slug on first hyphen that follows a word boundary after a logical scope: scope is the leading identifier (may contain hyphens if multi-part like
phase-c2), rest = description (hyphens to spaces). Use the examples above as reference. - No
/prefix: use full branch name as title - Cap at 70 characters
- Single-word result: ask the user
PR Body
Gather context
Run in parallel:
git log <base>..HEAD --pretty=format:"%s%n%b"
git diff <base>...HEAD --stat
gh label list --json name --jq '.[].name'
for f in .github/PULL_REQUEST_TEMPLATE.md .github/pull_request_template.md PULL_REQUEST_TEMPLATE.md .github/PULL_REQUEST_TEMPLATE/default.md; do [ -f "$f" ] && echo "$f" && break; done
gh extension list 2>/dev/null | grep -q gh-stack && gh stack view --json 2>/dev/null || true
The label list is used in the Label Validation step below. If the template check finds a file, use it as the body structure and fill each section. The last command probes stack membership (see Stack Detection).
Default format (no template)
## Summary
<2-4 sentences: WHY these changes exist, not WHAT files changed>
## Changes
<bulleted list from commits, grouped by area>
## Notes
<breaking changes, migration steps, env vars -- omit section if empty>
Intent-first principle: synthesize the PURPOSE from commits and diff.
- Bad: "Updated auth.routes.ts, added migration script, modified user schema"
- Good: "Migrates user data from CloudFlow to GRACE, syncing existing users with their entitlements so ADA can authenticate against the new backend"
Stack Detection
Only run on new PR creation. Skip entirely on Update Existing PR flow.
Detect stacked PR membership in this order. First hit wins:
-
gh-stack extension: if
gh stack view --jsonreturned data in Gather context, parse it for the chain containing the current branch. -
Base chain: if
<base>is NOT the repo default branch, query open PRs targeting it and check if<base>is itself the head of another open PR:gh pr list --head <base> --state open --json number,title,headRefName,baseRefNameIf a match exists, walk the chain upward (repeat with each parent's base) and downward (
gh pr list --base <current-head>) until both ends terminate.
If neither method yields a chain, skip the Stack section.
Stack prompt
If a chain of 2+ PRs is detected, prompt:
Stacked PR detected (N PRs in chain). Append Stack section to PR body? (y/n)
If no, skip. If yes, build the section.
Stack section format
## Stack
This is part of a N-PR stack:
- **#<num> (this PR)** -- <pr title>
- #<num> -- <pr title>
- #<num> -- <pr title>
Placement: if the PR template already contains a ## Stack heading, fill it in place. Otherwise append to the very bottom of the body (after all template-rendered sections, after Notes, after everything).
Ordering: use the base-chain walk result, ordered from oldest ancestor (closest to default branch) to newest tip. For gh-stack JSON, follow the same root-to-tip order it returns.
Current PR marker: bold the current PR's line with (this PR).
Two-step create+edit (current PR number does not exist at creation time):
- Create PR without Stack section.
- Capture returned PR number.
- Build Stack section with real number.
gh pr edit <new-number> --body "<body + Stack section>".
Label Mapping
| Branch prefix | Label |
|---|---|
feat/ |
enhancement |
fix/ |
bug |
docs/ |
documentation |
refactor/ |
refactor |
chore/ |
chore |
Label Validation
After deriving the label from branch prefix, check it against the prefetched label list from the Gather context step.
- Label exists in repo: use it in
gh pr create/gh pr edit - Label missing: prompt the user:
Label
enhancementdoesn't exist in this repo. Create it? (y/n)
If yes:
gh label create "<label>" --description "" --color "ededed"
Then use the label normally. If no, skip the label silently.
Create PR
Determine draft status:
--draftflag: pass--draft--readyflag: no--draft- No flag: ask "Open as draft or ready?" and use the answer
# Ready PR
gh pr create --title "<title>" --body "<body>" --base "<base>" --assignee @me --label "<labels>"
# Draft PR
gh pr create --title "<title>" --body "<body>" --base "<base>" --assignee @me --label "<labels>" --draft
Update Existing PR
When an open PR is detected, fetch the current body:
gh pr view <number> --json body --jq '.body'
Detect new commits
Compare the push result: if git push reported "Everything up-to-date", there are no new commits. Otherwise, new commits were pushed.
If new commits were pushed
Show a body update prompt:
PR # exists. Body update options:
- Overwrite -- regenerate body from all commits
- Append -- add a "Latest changes" section with new commit summaries
- Skip -- leave body as-is
| Choice | Body action | Title/Labels action |
|---|---|---|
| Overwrite | Full regeneration, same format as new PR | Update if derived values differ |
| Append | Append section below to existing body | Update if derived values differ |
| Skip | No change | Update if derived values differ |
Append format:
## Latest changes (YYYY-MM-DD)
- <new commit summaries only>
If no new commits
Skip body prompt. Only update title/labels if derived values differ from current.
Draft behavior on updates
- Existing draft PR +
/ship(no flag): do NOT convert to ready. That requires explicitgh pr ready. - Existing non-draft PR +
/ship --draft: ignore flag. Cannot un-ready via edit. --draftonly applies on creation.
Edit command
# Title/labels only
gh pr edit <number> --title "<title>" --add-label "<labels>"
# With body overwrite
gh pr edit <number> --title "<title>" --add-label "<labels>" --body "<body>"
# With body append
gh pr edit <number> --title "<title>" --add-label "<labels>" --body "<existing body + appended section>"
Flow Summary
- Pre-flight checks in parallel (auth + branch + clean state + base branch detection)
- Resolve base branch (
--baseflag or detected default) - Check commits ahead of
<base>(hard block if none) - Handle soft blocks (single decision point)
- Push
- Detect existing PR
- Gather context in parallel (commits + diff stat + prefetch labels + template check + stack probe)
- Generate title + body + labels
- Validate labels, offer creation if missing
- If existing PR with new commits: prompt overwrite/append/skip
- If new PR and no
--draft/--readyflag: ask "Open as draft or ready?" - Single
gh pr create(with or without--draft) orgh pr edit - If new PR and stack detected: prompt for Stack section, then
gh pr editto append with real PR number
Target: 7-9 tool calls for a clean branch (10-12 with stack detected).
