ship Skill
<!-- ship-merge-deploy-protocol:start -->
Merge → deploy protocol (load-bearing — especially Grok Build)
You own merge through production verification — never ask the operator to monitor.
- Phase 7: poll PR merge to
MERGED(auto-merge queue, BEHIND sync, required-check failure exit). - After merge: poll release/deploy workflows on the merge commit to
completed+success. - Step 3.8: invoke
/postmerge <PR-number>(Grok) orsoleur:postmerge(Claude) before Step 4 cleanup. - FORBIDDEN: Ending the session at merge, at a red release run you did not investigate, or with "want me to watch CI?"
- Harness polling:
plugins/soleur/lib/harness.ts→pollInstructions()— Claude uses Monitor tool; Grok uses AwaitShell (patternforMERGED,BEHIND detected,auto-sync.*pushed,postmerge verification complete) or blocking Shell withblock_until_ms. - BEHIND stop-and-sync: When
mergeStateStatusisBEHIND, stop CI-only polling and resync before continuing. Grok/ad-hoc polls:bash plugins/soleur/scripts/sync-pr-behind.sh <PR>from the feature worktree. Canonical spec:plugins/soleur/lib/pr-merge-poll.ts.
See workflow-fidelity.ts (SHIP_MERGE_DEPLOY_SENTINEL, POST_MERGE_VERIFICATION_SKILLS) and wg-after-a-pr-merges-to-main-verify-all. <!-- ship-merge-deploy-protocol:end -->
Purpose: Enforce the full feature lifecycle before creating a PR, preventing missed steps like forgotten /compound runs and uncommitted artifacts. Version bumping is handled by CI at merge time via semver labels.
CRITICAL: No command substitution. Never use $() in Bash commands. When a step says "get value X, then use it in command Y", run them as two separate Bash tool calls -- first get the value, then use it literally in the next call. This avoids Claude Code's security prompt for command substitution.
Headless Mode Detection
If $ARGUMENTS contains --headless, set HEADLESS_MODE=true. Strip --headless from $ARGUMENTS before processing remaining args.
When HEADLESS_MODE=true:
- Phase 2: auto-invoke
skill: soleur:compound --headless(forward flag, no user prompt) - Phase 4: if test files are missing, continue without writing (CI gate catches this)
- Phase 6: auto-accept generated PR title/body without user confirmation
- Phase 7: if CI is flaky or unrelated check fails, abort pipeline (do not ask whether to proceed)
- All failure conditions: abort with clear error message, do not prompt
Phase 0: Context Detection
Detect the current environment:
# Determine current branch and worktree
git rev-parse --abbrev-ref HEAD
git worktree list
pwd
Branch safety check (defense-in-depth): If the branch from the command above is main or master, abort immediately with: "Error: ship cannot run on main/master. Checkout a feature branch first." This is defense-in-depth alongside PreToolUse hooks -- it fires even if hooks are unavailable (e.g., in CI).
Trailer-parse verification gate (defense-in-depth for [hr-always-read-a-file-before-editing-it]). For every commit on this branch since origin/main, parse any Key: value-shaped lines in the body and confirm git interpret-trailers recognises each as a trailer. The modal failure is a blank line between an Allowlist-Widened-By:/Reviewed-by:/Signed-off-by: line and Co-Authored-By:, which silently demotes the upstream trailer into body prose and breaks downstream consumers parsing via git log --format='%(trailers:key=NAME,valueonly)':
# Scan the WHOLE body, but only for KNOWN trailer keys.
#
# Two wrong ways to scope this, both tried:
# * Any `^Word: ` anywhere — flags ordinary prose ("Suites: 18/18",
# "Note: …", "Runbook: …"), so the gate fails on almost every
# well-written commit message. A gate that cannot pass gets routinely
# ignored, which is worse than no gate: it trains the reader to walk
# past red. Measured on PR #6727: 6 hits, all prose, while every
# intended trailer parsed fine.
# * Final paragraph only — VACUOUS for the exact class this gate exists
# to catch. The modal failure is a blank line BEFORE `Co-Authored-By:`,
# which leaves the orphaned trailer in the second-to-last paragraph
# while the final paragraph stays a clean parseable block. Verified
# vacuous against a synthetic demotion.
#
# The real signal is neither position nor shape: it is whether the key is
# one a downstream consumer actually reads. Prose keys are not. Extend this
# list when a new machine-read trailer is introduced.
KNOWN_TRAILER_KEYS='Co-Authored-By|Signed-off-by|Allowlist-Widened-By|Reviewed-by|Reviewed-By-Soleur|Reviewed-Coverage|Acked-by|Tested-by|Cc'
RC=0
for sha in $(git rev-list origin/main..HEAD); do
BODY=$(git log -1 --format=%B "$sha")
declare -a CANDIDATES=()
while IFS= read -r line; do
[[ "$line" =~ ^(${KNOWN_TRAILER_KEYS}):[[:space:]] ]] && CANDIDATES+=("${BASH_REMATCH[1]}")
done <<< "$BODY"
for key in "${CANDIDATES[@]}"; do
val=$(git log -1 --format="%(trailers:key=${key},valueonly)" "$sha")
if [[ -z "$val" ]]; then
echo "[FAIL] ${sha:0:8}: '${key}:' is in the body but does not parse as a trailer." >&2
echo " Fix: git rebase -i, reword the commit to make the final paragraph a contiguous Key: value block." >&2
RC=1
fi
done
done
exit $RC
If the gate fails, do NOT proceed — reword the offending commit(s) via git rebase -i (or git commit --amend if the failing commit is HEAD AND has not been pushed) so the final paragraph is a pure contiguous Key: value block. See knowledge-base/project/learnings/2026-05-16-git-trailer-parser-requires-contiguous-key-value-block.md and PR #4106.
Load project conventions:
if [[ -f "CLAUDE.md" ]]; then
cat CLAUDE.md
fi
Identify the base branch (main/master) for comparison:
git remote show origin | grep 'HEAD branch'
Phase 1: Validate Artifact Trail
Check that feature artifacts exist and are committed. Look for files related to the current feature branch name:
Get the current branch name:
git rev-parse --abbrev-ref HEAD
Extract the feature name from the result by stripping the feat-, feature/, fix-, or fix/ prefix. Then search for related artifacts using the Glob and Bash tools:
- Brainstorms: glob
knowledge-base/project/brainstorms/FEATURE - Specs: check
knowledge-base/project/specs/feat-FEATURE/spec.md - Plans: glob
knowledge-base/project/plans/FEATURE - Uncommitted files:
git status --porcelain knowledge-base/
If artifacts exist but are not committed: Stage and commit them.
If no artifacts exist: Note this in the checklist but do not block. Not all features go through the full brainstorm/plan cycle.
Phase 1.5: Review Evidence Gate
Check for evidence that /review ran on the current branch. This is defense-in-depth -- /one-shot already enforces review ordering, but direct /ship invocations bypass it.
Step 1: Check for review artifacts (legacy).
Search for todo files tagged as code-review findings that this branch introduced — refresh origin/main first, since the scoping is only as accurate as the cached ref:
if ! git fetch origin main >/dev/null 2>&1; then
echo "origin/main is stale — Signals 1-2 are unreliable; use Signal 3 only" >&2
else
git log origin/main..HEAD -G'code-review' --name-only --format= -- todos/ 2>/dev/null \
| sort -u | while read -r f; do
git show "HEAD:$f" 2>/dev/null | grep -q "code-review" && echo "$f"
done | head -1
fi
This was a repo-global grep -rl "code-review" todos/, which made the signal structurally unfailable (#6724): todos/ is a tracked directory on main, so a single long-lived review todo anywhere in it satisfied Step 1 for every branch forever — including branches where review never ran.
Three details are load-bearing, each closing a narrower version of the same vacuity (all