skills/ship/SKILL.md
End-to-end branch delivery: commit (no AI attribution) → push → open a pull request → ensure a Board work item exists (create one per task, assigned to the configured user, if none) and link it → after merge, clean up branch and worktree. Auto-detects the platform from the remote — Azure Repos + Boards (azure-devops-node-api SDK; OAuth Bearer push fallback via `az`) or GitHub (Octokit; `gh` for auth). Scripts are TypeScript, run via `bun`. Use whenever asked to "ship", "ship it", "ship this branch", "open a PR", "push and open a PR", "raise a PR", "deliver this", "send this for review", or "create a PR and link the work item" — and when a direct push to main is blocked and the change needs to go through a PR instead. Also covers snapshotting the current state as an annotated git tag — use whenever asked to "save the current state", "tag this", "snapshot the state", "create a checkpoint", "tag a release point", or "mark where we are" (a git-tag checkpoint, distinct from PR labels/tags). Also opens a just-created (or any) PR in a chosen browser account profile — use whenever asked to "open the PR in my browser", "open it in Edge/Chrome", or "open the PR as <account>".
npx skillsauth add julianobarbosa/claude-code-skills 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.
ship takes a finished branch the last mile: commit it cleanly, push it (working around Azure
DevOps auth when needed), open a pull request, link it to its work item, and — once it's merged —
tear down the branch and worktree. It auto-detects whether you're on Azure Repos or GitHub
and follows the matching path, so the same command works at your organization and in a github.com repo.
It is deliberately narrow. It does not merge locally, run pipelines, or manage backlogs — it hands a reviewable PR to the platform and cleans up after the merge.
ship — the PR-based delivery flow: push → PR → (merge happens via the platform) → cleanup.commit — just stage + commit + push the current branch, no PR.merge — merge a branch into main locally (fast-forward) and clean up. Use this when there's
no PR gate; use ship when changes must go through review/policy.azure-devops — the deep REST/MCP toolbox (WIQL, batch updates, pipelines, comment threads).
ship calls only the thin slice it needs; reach for azure-devops for anything richer.Run bun scripts/ship-detect.ts first — it prints the platform, the Azure org/project/repo (if
any), the branch, and an inferred work-item id. Everything below branches on that.
One-time setup: the scripts depend on
azure-devops-node-apiand@octokit/rest. Runbun installin the skill dir (~/.claude/skills/ship/) once before first use.
git status and git log --oneline @{u}.. 2>/dev/null.ship-detect.ts. If it says unknown (e.g. a custom SSH host alias),
set SHIP_PLATFORM=azure or SHIP_PLATFORM=github for the session.Stage only files for this task and commit. Never add AI attribution — no
Generated with Claude, no Co-Authored-By: Claude. Commits are authored by the human; tooling
provenance does not belong in git history (this repo's commit-msg hook strips trailers as a
backstop, but don't rely on it — don't write them in the first place). If pre-commit hooks fail on
unrelated issues, --no-verify is acceptable; if they flag your change, fix it.
main/master → you can't open a PR from main into itself. Create a branch and move the
commit onto it before pushing:
git branch feature/<slug> && git reset --hard @{u} && git checkout feature/<slug>
(Only do this when the commits aren't yet pushed to main. If unsure, stop and ask.)bun scripts/ship-push.ts # pushes current branch, sets upstream
On Azure DevOps, if the normal push fails on auth, this automatically mints an az OAuth token and
retries with a Bearer header — the fallback for when neither SSH nor an HTTPS credential helper is
available. See references/azure-devops.md for the mechanics.
Invariant — never ask the user for the work item id. Resolving → verifying → (if missing) creating + assigning + linking the work item is fully automatic. A PR must never be surfaced as ready without a linked Board work item. If you're about to ask "what's the work-item id?", stop — that question is the exact failure this skill exists to prevent. The id comes from
--work-item, the branch name, or a freshly created item; it never comes from a question.Code review does not own the work item. Because ship guarantees a linked item at PR-open, the review step — human reviewers or the
code-review/bmad-code-reviewskills — must not re-prompt for or block on a work item. That concern is already satisfied upstream.
Draft a real description — copy assets/pr-template.md to a temp file, fill Summary / Changes /
Verification, and keep the AB#<id> line so the work item links.
bun scripts/ship-pr.ts --title "<title>" --body-file /tmp/pr-body.md \
--work-item <id> --transition Resolved \
--reviewer [email protected] --required-reviewer [email protected] --tag needs-review
createPullRequest)
or Octokit (pulls.create).createPullRequest call. On GitHub the
create API accepts none of those, so labels + reviewers are attached in follow-up calls — same
result, just unavoidable there.--reviewer <upn|username> adds an optional (non-blocking) reviewer;
--required-reviewer marks it required on Azure (blocks completion). Both are comma-separated
and/or repeatable. On Azure a UPN/email is resolved to an identity; an unresolvable name is warned
and skipped (reviewer_skipped=…), never blocking the PR. GitHub has no per-PR "required"
reviewer (that's branch protection), so required ones are just requested. The script prints
reviewer_added=<name> required=<bool> for each.
When neither flag is given, $SHIP_ADO_DEFAULT_REVIEWER (if set) is added as an optional
reviewer — a PR with no reviewer record leaves no trace that anyone was asked. Unset = no
reviewer, the prior behaviour.--work-item (or the branch name), then verifies it actually exists via the SDK
(getWorkItem). The branch-name parse is only a heuristic — a branch like 1234-foo can carry a
number that isn't a real item, which is why existence is checked, not assumed.$SHIP_ADO_ASSIGNEE (default [email protected]; override with --assignee).
Pass --task "<title>" once per task (repeatable), or --tasks-file <file> (one title per line),
to create one item each. With no --task, a single item is created from the PR --title. Type
defaults to Task (--work-item-type "User Story"|Bug). Disable creation with
--no-create-work-item to link only a pre-existing id.
work_item_created=<id> per created item and work_items=<ids> for the set —
surface the new ids to the user (don't fabricate them; they come from the SDK response).--transition (Azure only) moves each linked/created item after the PR opens. State names
are process-specific (Agile: Resolved; Scrum: Committed; Basic: Doing) — verify the valid next
state first; see references/azure-devops.md. Omit --transition to leave the board untouched.
(Created items start in the type's initial state, e.g. New/To Do.)pr_url=…; surface it to the user.Azure DevOps calls them tags; GitHub calls them labels — same idea: a small, visible signal that helps reviewers triage and helps the team organize PRs. Microsoft's guidance is that tags "communicate extra information to reviewers, such as that the PR is still a work in progress, or is a hotfix for an upcoming release" (Add tags to a pull request).
A PR is never opened bare. With no --tag, ship-pr.ts derives a type label
from the branch prefix (feat/… → feat, fix/|hotfix/|bugfix/ → fix,
docs/ → docs, chore/ → chore, refactor/ → refactor) and prints a
NOTE. An unrecognised prefix gets a WARNING, not a failure — no platform has a
"require a label" policy, so this is the only enforcement point, and a hard error
here would break repos with other conventions.
Apply tags at PR-open time with --tag (comma-separated and/or repeatable):
bun scripts/ship-pr.ts --title "<title>" --body-file /tmp/pr-body.md \
--work-item <id> --transition Resolved --tag "hotfix,do-not-merge"
…or manage tags on an already-open PR with ship-tag.ts (the id is pr_id= on Azure / pr_number=
on GitHub, both printed by ship-pr.ts):
bun scripts/ship-tag.ts <pr-id> --add "needs-review" # add
bun scripts/ship-tag.ts <pr-id> --remove "do-not-merge" # remove
bun scripts/ship-tag.ts <pr-id> --list # show current tags
Tags are free-form on both platforms (Azure creates the tag definition on first use; GitHub
auto-creates a missing label). For a recommended, consistent tag set — do-not-merge, work-in-progress,
hotfix, plus type/area conventions — see the Recommended tags section of
references/azure-devops.md / references/github.md.
Review actually happens on the PR page, and which signed-in account you open it under matters
when you juggle several (work vs. personal vs. cloud) — the wrong profile shows the wrong
permissions, or no access at all. So after the PR opens, always surface the pr_url, then ask
whether to open it in the browser. Opening is opt-in, never automatic (a browser window is a visible
side effect; the user asked to be prompted).
ship-open.ts resolves an email → browser profile by reading the browser's own Local State
(the same account map its profile switcher shows), then launches that profile at the URL. It works
on WSL (→ Windows Edge/Chrome) and macOS (→ Chrome/Edge). Nothing is baked in — the map is
discovered live each run, the email is an argument, and only your default email is remembered, in
~/.config/ship/open.json (outside this skill).
Discover the accounts, then ask the user which one with AskUserQuestion (only worth prompting when there's more than one; offer each account plus a "don't open" choice):
bun scripts/ship-open.ts --list-profiles # prints browser= + one `profile=<dir>\t<email>\t<name>` per account
Open the PR under the chosen account (add --dry-run first if you want to confirm the resolved
profile without a window):
bun scripts/ship-open.ts "<pr_url>" --profile <email> # opens; prints opened_url= / profile_dir=
bun scripts/ship-open.ts "<pr_url>" --profile <email> --dry-run # resolve only, no window
Remember the pick as the default so future ships offer it with one keypress:
bun scripts/ship-open.ts --set-default <email> [--browser edge|chrome]
An unknown or omitted email falls back to the browser's Default profile (with a note on stderr) rather than guessing wrong. This step is independent of the merge/cleanup below — opening the PR to review it doesn't advance or block anything.
Do this only once the PR is actually merged (don't delete a branch with an open PR). Mirror the
merge skill's cleanup, and ask before deleting:
cd to the main repo dir first, then
git worktree remove <path> — you can't delete a branch from inside its own worktree.git branch -d <branch> (lowercase -d refuses unmerged branches — that refusal is the safety
net; only escalate to -D if the user explicitly confirms).git push origin --delete <branch> (ignore failure if it was local-only).Independent of the PR flow — reach for this any time you want a return-to point: after a clean
merge to main, before a risky migration, or just to mark "everything works here." It creates an
annotated git tag on HEAD (the working tree is irrelevant — a tag names a commit).
bun scripts/ship-snapshot.ts \
-m "what changed / why you're saving here" \
-m "verification status"
The script auto-fills what's easy to get wrong by hand:
v<YYYY.MM.DD-HHMM> (minute-granular so repeated snapshots in a day don't
collide). Pass --daily for a once-a-day v<YYYY.MM.DD>, or --name <tag> for an exact name.Snapshot <tag> — <branch> @ <shorthash> — so the
tag is self-describing even with no -m. Each -m you add becomes its own paragraph (passed via
an arg array, so real newlines/punctuation survive — unlike a \n inside a single shell string).Tags are local until pushed (shared/visible — confirm first, like opening a PR). Add --push to
push to origin; on Azure it falls back to the az OAuth Bearer header the same way ship-push.ts
does. The script prints tag=, commit=, branch=, pushed=.
| Script | Does |
|--------|------|
| bun scripts/ship-detect.ts [remote] | Print platform + Azure coordinates + branch + inferred work item |
| bun scripts/ship-push.ts [-r remote] [-b branch] | Push + set upstream; Azure OAuth Bearer fallback on auth failure |
| bun scripts/ship-pr.ts --title … [opts] | Open PR on the detected platform; ensure/create + link work item(s) (assigned to the configured user); optional Board transition; optional --tag; optional --reviewer/--required-reviewer — work item + tags + reviewers packed into one create call on Azure |
| bun scripts/ship-tag.ts <pr-id> [--add\|--remove "t1,t2"] [--list] | Add / remove / list PR tags (Azure) or labels (GitHub) on an existing PR |
| bun scripts/ship-open.ts <url> [--profile <email>] [--dry-run] | Open a PR/URL in a chosen browser account profile (email→profile via the browser's Local State); WSL→Edge/Chrome, macOS→Chrome/Edge; also --list-profiles, --set-default |
| bun scripts/ship-snapshot.ts [-m "para"]… [--daily] [--name <tag>] [--push] | Save current state as an annotated git tag (date-named, self-describing subject); optional push with Azure OAuth fallback |
Scripts are TypeScript run via bun; shared helpers live in scripts/ship-lib.ts. Run bun install
in the skill dir once (installs azure-devops-node-api + @octokit/rest). Git/push stays a
subprocess call (it's a git operation); only the ADO/GitHub REST surfaces use the SDKs. Platform
detail lives in references/azure-devops.md and references/github.md — read the one matching the
detected platform. The PR description starts from assets/pr-template.md.
pulls.create, ADO createPullRequest) require the branch to be
pushed first — keep step 3 before step 4.ship-detect.ts returning unknown is almost always a custom SSH host alias — set SHIP_PLATFORM.
(A dev.azure.com-<alias> SSH host is detected as azure but its org/project/repo won't parse —
same limitation the shell version had; pass coordinates/--work-item explicitly if needed.)Cannot find module 'azure-devops-node-api', run bun install in
the skill dir.AZURE_DEVOPS_EXT_PAT is set but under-scoped (e.g. Code-only, missing Work Items / Pull
Request write), ship-pr.ts auto-detects the 401/403 and retries with the az OAuth bearer
token — no need to unset the PAT manually. The fallback requires az login as an org member.az repos pr create / gh pr create and then bolt the work item
on afterward — that path skips the auto-create+link invariant and forces a manual "what's the id?"
round-trip with the user. Use ship-pr.ts so the work item is guaranteed at PR-open.ship-open.ts matches --profile <email> against the browser's signed-in account emails, so a
profile with no account attached is only reachable as the Default fallback, not by email. It
auto-picks the installed browser (chrome preferred if both) and needs no deps or network — just the
browser's local Local State file. If it can't find that file, the browser isn't installed where
expected; pass --browser or check the path it printed.--tag, ship-tag.ts) — a
triage signal on the PR. (2) Work items (step 4) — the traceability link. (3) Git tags
(ship-snapshot.ts) — a commit checkpoint in history. ship-tag.ts takes a PR id;
ship-snapshot.ts takes no positional arg and tags HEAD. Same word, three concepts.createPullRequestLabel returns the existing definition rather
than erroring, and GitHub dedupes; parseTags also drops blank/duplicate names from the CSV.ship-tag.ts needs the PR id — Azure uses the numeric pr_id, GitHub uses the pr_number
(both emitted by ship-pr.ts). On GitHub a tag that names a non-existent label is auto-created.testing
Brief description of what this skill does. Include specific triggers - when should Claude use this skill? Example triggers, file types, or keywords that indicate this skill applies.
tools
Manage and troubleshoot PATH configuration in zsh. Use when adding tools to PATH (bun, nvm, Python venv, cargo, go), diagnosing "command not found" errors, validating PATH entries, or organizing shell configuration in .zshrc and .zshrc.local files.
tools
Zabbix monitoring system automation via API and Python. Use when: (1) Managing hosts, templates, items, triggers, or host groups, (2) Automating monitoring configuration, (3) Sending data via Zabbix trapper/sender, (4) Querying historical data or events, (5) Bulk operations on Zabbix objects, (6) Maintenance window management, (7) User/permission management
development
Operate YouTube Music via natural language. Search songs, artists, albums, playlists, lyrics, charts, recommendations, and control playback. Browse personal library, manage playlists, rate tracks, and inspect account info. Use this skill whenever the user asks about YouTube Music, wants to play music, manage playlists, search by song or artist name, inspect lyrics, or control playback.