skills/ship/SKILL.md
Final mile from merge-ready branch to shipped: squash-merge, archive backlog tickets with trailers, update touched docs, run /reflect, apply outputs. Assumes /deliver or /deliver --polish-only already made the branch ready. Use when: "ship it", "merge and close out", "final mile", "land and reflect", "finish this ticket". Trigger: /ship.
npx skillsauth add phrazzld/spellbook 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.
The final mile. Branch is merge-ready; /ship lands it, archives the
ticket(s), syncs docs, runs /reflect, and threads reflect's outputs back
into the repo. One command from "green" to "shipped and learned from."
/ship has authority within its domain.
Archive, merge, pull, reflect, apply. Escalate only on refuse conditions.Closes-backlog trailers must survive
into the squash commit on master. /groom sweeps master by trailer —
a dropped trailer is a ticket that never closes.harness/reflect-outputs branch for human review. This is a hard
invariant from reflect/SKILL.md./ship assumes
/deliver --polish-only already proved the branch clean. If it wasn't
run, refuse and route the operator back..harness-kit/agents.yaml, verify that /deliver --polish-only or the
documented landability evidence includes two or more roster-member receipts
or an explicit exception before final-mile merge work.Delegation floor applies: probe the roster first; dispatch two or more
providers for substantive work; direct solo only for mechanical, emergency,
user-forbidden, or fewer-than-two-providers cases. See
harnesses/shared/AGENTS.md (Roster).
Local lane guidance: Normally verify upstream roster receipts; if final-mile work surfaces substantive judgment, route back to /deliver --polish-only or dispatch release-risk and closure-state review lanes.
Assert at start; refuse with a clear reason on any miss.
master / main / default protected branch).^(feat|fix|chore|refactor|docs|test|perf)/([0-9]+)-.
The numeric capture is the primary backlog ID being shipped.harnesses/shared/AGENTS.md (Closeout).gh pr view --json mergeable,mergeStateStatus
reports mergeable. A conflicted or blocked PR means /deliver --polish-only isn't done.ship or conditional verdict; or git-native mode has
operator-provided/current-session local gate receipts from /ci or the
repo's documented gate. Do not require a PR or verdict solely to land a
locally verified git-native branch. A dont-ship verdict still blocks.
For Harness Kit and any repo with dagger.json, the documented gate is
dagger call check --source=. on the exact HEAD being landed.git rev-parse HEAD):
exact behavior changed, live repo evidence read, acceptance source,
command/path exercised, repo-fit check, and residual unverified paths.
Refuse stale evidence that names a different SHA or omits the SHA when the
upstream phase could have produced one. When the oracle depends on a fixture, contract, golden
file, screenshot, Gherkin feature, transcript, or equivalent acceptance
artifact, the evidence includes its sha256 hash. If acceptance criteria,
artifact contents, or assertion strength changed, the evidence includes an
explicit Contract-change acknowledgment: line. If .evidence/<branch>/<date>/
is non-empty, /ship carries it into the final commit as QA-Evidence:./trace after merge. If no transcript or trace artifact is available, the
operator provides a Trace-waiver: <reason> line. Raw session logs do not
satisfy this prerequisite..harness-kit/agent-readiness.yaml exists, the branch evidence includes
readiness impact: improved, preserved, or regressed. Regressions must include
a contract-change reference and a valid future-expiring waiver.When .harness-kit/work/ledger.jsonl is available, /ship consumes the latest
completed /deliver record for the closing backlog/branch. It calls
scripts/work-ledger.py append for phase_started at final-mile start,
next_action_changed after merge while trace/reflect remains, blocker_added
on a refuse condition, and phase_completed with status=completed after
trailers, trace handoff, reflect, and follow-up mutations are done.
Primary ID from the branch name regex capture. Then scan branch commits:
git log --format=%B master..HEAD \
| git interpret-trailers --parse --no-divider
Collect every Closes-backlog: and Ships-backlog: value (closing) plus
every Refs-backlog: value (reference-only). Merge with the primary ID.
Prefer backlog_ids_from_range master..HEAD from scripts/lib/backlog.sh
when available.
For each ID in the closing set:
source scripts/lib/backlog.sh
backlog_archive "<id>"
This performs git mv backlog.d/<id>-*.md backlog.d/_done/. Stage the
moves. Idempotent — already-archived IDs exit 0 silently.
If the primary ID has no matching file AND no trailers were found, refuse (see Refuse Conditions): the branch is shipping something with no backlog association.
Inspect the diff to find docs that may have gone stale:
git diff master..HEAD --name-only
If the downstream repo has a drift contract (e.g.
docs/context/DRIFT-WATCHLIST.md), read it and cross-reference the
changed paths. When doc updates are required and not yet present,
dispatch a focused general-purpose subagent with:
Do not invent docs that don't already exist. If the repo has no drift contract, skip this step and note it in the final report.
One commit. Subject: chore(backlog): archive shipped tickets.
Inject every closing ID as a separate trailer — do not hand-format:
msg="chore(backlog): archive shipped tickets"
for id in $CLOSING_IDS; do
msg="$(printf '%s' "$msg" \
| git interpret-trailers \
--if-exists addIfDifferent \
--trailer "Closes-backlog: $id")"
done
if [ -f scripts/lib/evidence.sh ]; then
source scripts/lib/evidence.sh
evidence="$(evidence_trailer "$(evidence_dir)" 2>/dev/null || true)"
if [ -n "$evidence" ]; then
msg="$(printf '%s' "$msg" \
| git interpret-trailers \
--if-exists addIfDifferent \
--trailer "$evidence")"
fi
fi
git commit -m "$msg"
Body stays minimal. The trailers are the contract; prose is optional.
GitHub mode (PR exists, gh available):
Construct a squash body that carries every closing trailer. GitHub's default squash template often drops commit trailers, so pass the body explicitly:
body="$(git log --format=%B master..HEAD \
| git interpret-trailers --parse --no-divider \
| grep -E '^(Closes-backlog|Ships-backlog|Refs-backlog|QA-Evidence):' \
| sort -u)"
gh pr merge --squash --body "$body"
Include a one-line subject summarizing the shipped work above the trailer
block. Match the repo's squash-subject convention (look at recent
git log master --merges).
Git-native mode (no PR, no gh, or no GitHub remote):
dagger call check --source=.
git checkout master
git merge --squash <branch>
git commit -F <constructed-message-file>
Detect mode by: remote URL + gh on PATH + gh pr view exit code.
GitHub mode is preferred when available because it records the merge in
the PR timeline. The Dagger command is explicit here because squash merges do
not invoke Git's pre-merge-commit hook.
git checkout master
git pull --ff-only
git log -1 --format=%B | git interpret-trailers --parse --no-divider
The output must contain Closes-backlog: <id> for every ID in the
closing set. If any are missing, stop and escalate — the squash body
construction dropped them and the fix must happen before /groom next
sweeps. If the shipping branch had a non-empty .evidence/<branch>/<date>/,
the output must also contain QA-Evidence: <path>.
After the merge SHA is known, write or link the final work record. Preferred local form:
python3 skills/trace/scripts/trace_record.py append \
--backlog "<primary-id>" \
--branch "<pre-merge-branch>" \
--commit "<branch-head-before-merge>" \
--reviewer-verdict-ref "<receipt-or-verdict-ref>" \
--qa-ref "<qa-evidence-ref>" \
--demo-ref "<demo-ref-if-any>" \
--transcript-ref "<redacted-transcript-ref>" \
--shipped-ref "master@<merged-sha>"
If no safe transcript exists, use --waiver-reason "<why no transcript was available>". If no local JSONL store is appropriate, the final report must
name another durable trace ref such as a Git note or PR body section. Do not
persist raw session logs.
/reflect cycleBounded scope: the just-shipped work only. Pass as context:
Capture reflect's outputs:
Reflect may propose new tickets, edits to open tickets, or deletions. Apply
them in-tree: add files to backlog.d/, edit existing tickets. Commit to
master:
chore(backlog): apply reflect outputs from shipping <primary-id>
If reflect proposed no backlog mutations, skip this commit.
Reflect's harness proposals never land on master. Create or checkout the branch:
git checkout -B harness/reflect-outputs master
Apply the harness edits there. Commit per-concern (match /yeet commit
discipline). Push:
git push -u origin harness/reflect-outputs
If the branch already exists with prior suggestions, rebase onto master first, then add the new commits. Report the branch name so a human can review.
Return to master before finishing:
git checkout master
Emit a single block covering:
.evidence/<branch>/<date>/ artifacts.harness/reflect-outputs, retro notes, coaching./reflect and final report.scripts/summarize-delegations.py --format text scoped to the closing
backlog ref when receipts exist.Stop and surface to the user instead of shipping:
^(type)/(\d+)- — no primary ID extractable.master / main directly.dont-ship (verdict_check_landable returns 2).dagger.json, dagger call check --source=. has not passed
on the exact HEAD being landed.gh pr checks is red. Do not add a --force flag;
refuse.gh pr view --json mergeable,mergeStateStatus.Trace-waiver: <reason> line.
Operator must provide a transcript/ref source or a waiver reason before
merge.backlog.d/<id>-*.md file AND no closing trailers on
any branch commit — shipping with no backlog association. Operator must
add a ticket or add a marker commit and re-run.Contract-change acknowledgment:
explaining why the contract changed. Route back to /deliver --polish-only..git/MERGE_HEAD,
.git/CHERRY_PICK_HEAD, rebase-* dir).Every ticket closure flows through git trailers. Keys recognized by
scripts/lib/backlog.sh:
Closes-backlog: <id> — closes the ticket (archival intent).Ships-backlog: <id> — synonym for Closes-backlog, closes the ticket.Refs-backlog: <id> — references the ticket without closing it.Example trailer block on a squash merge commit:
feat(lane): add adaptive backoff to dispatcher
Closes-backlog: 029
Closes-backlog: 031
Refs-backlog: 024
IDs are bare numeric strings (029, not BACKLOG-029). Trailers are
injected via git interpret-trailers --trailer, never hand-formatted, to
avoid whitespace and key-casing drift.
| Mode | Detection | Merge command |
|---|---|---|
| GitHub | remote URL + gh on PATH + gh pr view succeeds | gh pr merge --squash --body "<trailers>" |
| Git-native | no PR, no gh, or no GitHub remote | git merge --squash <branch> && git commit -F <msg> |
GitHub mode is preferred when available because the PR timeline records the merge. Behavior is otherwise identical: squash-only, trailer-preserving.
/deliver --polish-only leaves the branch merge-ready.
/ship assumes that work is done; it does not re-run CI, code-review, or
refactor./reflect cycle for retro, backlog mutations, and
harness proposals./flywheel as the landing + reflection stage of each
cycle. /flywheel reads /ship's final report to decide the next
cycle./yeet: /yeet ships the working tree to the remote
(commits + push). /ship ships the branch to master (merge + archive
gh pr merge --squash
with no --body often uses the PR title + description, not commit
trailers. Always pass --body with the trailer block explicitly.Closes-backlog: NNN lines and Co-Authored-By:
splits the block; git interpret-trailers --parse only recognizes
the last block, so downstream backlog_ids_from_commit returns empty.
Use git interpret-trailers --if-exists addIfDifferent --trailer "..."
to inject programmatically — it handles block boundaries correctly./groom
sweeps. One commit on the feature branch; one squash commit on master.backlog.d/<id>-*.md
to move. Trust the trailers; don't fail the archive step on a missing
file, but do note it.reflect/SKILL.md encodes it as an invariant. Harness
edits go to harness/reflect-outputs, full stop. A /reflect run that
writes to master's .claude/, .agents/, AGENTS.md, or CLAUDE.md
is a bug; surface it./ship on an already-shipped branch. The branch is
gone, the PR is closed. Detect and exit early; do not attempt to
re-archive or re-reflect.Closes-backlog: 029 appearing in three
branch commits must squash to one trailer on master, not three. The
interpret-trailers --if-exists addIfDifferent flag handles this;
don't sort-and-paste manually./ship still merges and
reflects. /flywheel decides whether /deploy runs after.Single report, plain text:
/ship complete
Merged: <sha> on master (PR #<n>)
Closed: 029, 031
Referenced: 024
Docs: docs/context/lane-runtime.md (synced)
Reflect: 2 backlog mutations applied, 3 harness proposals on
harness/reflect-outputs, retro in .harness-kit/reflect/<cycle>/
Residual: none
On refuse, emit the reason and the action the operator must take to re-enable shipping.
development
Lightweight evidence-backed retro and catch-up reports for a current repo, branch, PR, backlog slice, or recent agent session. Use when the user asks for a debrief, catch me up, what changed, why it matters, product implications, end-user implications, developer experience implications, current app state, backlog state, workspace state, alternatives considered, or context rebuild after losing the thread. Trigger: /debrief.
testing
Capture agent-session work records as local JSONL audit evidence. Links a backlog/spec, branch, commits, review verdicts, QA/demo evidence, transcript refs, and shipped ref without storing raw private transcripts. Use when: "trace this work", "write work record", "agent session trace", "journal this delivery", "link transcript evidence". Trigger: /trace, /journal.
data-ai
Turn proven agent-session patterns into first-party Harness Kit skills. Use when: "skillify this conversation", "make this into a skill", "generate a skill from current transcript", "extract reusable workflow". Trigger: /skillify.
testing
Run one targeted, read-only architecture or quality critique through a named lens from the shared rubric. Use when: "critique this module", "run an Ousterhout pass", "lens critique", "architecture critique". Trigger: /critique.