--- name: ship description: Ship a Hamster Studio brief. Merge base, implement in parallel waves, test, review, create bisectable commits, and optionally PR. Use when the user wants to execute or ship a brief. --- # Ship Brief Executes a Hamster Studio brief. **Execution-only**: the plan — parent tasks, subtasks, context — was generated upstream in Hamster Studio and synced into `.hamster/`. This skill schedules those existing tasks into parallel waves and executes them. It never generates, splits, or replans tasks on its own initiative — but it doesn't execute blindly either. Executors adapt to mechanical drift (documented as deviations) and escalate genuine plan defects as **PLAN_ISSUE** instead of implementing something known to be wrong; the orchestrator (and for scope changes, the user) decides how to proceed. Project skills ARE loaded during execution: task-executors read `.claude/skills/hamster-project-context/` (generated by `hamster sync`), relevant project skills, and `.hamster/` blueprints/methods. Execution-only restricts *planning*, not *context*. **Argument**: "$ARGUMENTS" --- ## Setup Run prerequisites, account discovery, and live sync in ONE bash call: ```bash errors="" which hamster >/dev/null 2>&1 || errors="${errors}hamster CLI not found. Install from https://tryhamster.com\n" [ -d ".hamster" ] || errors="${errors}.hamster/ directory not found. Run 'hamster sync' first.\n" which gh >/dev/null 2>&1 || errors="${errors}gh CLI not found. Install from https://cli.github.com\n" dirty=$(git status --porcelain 2>/dev/null | head -5) [ -n "$dirty" ] && errors="${errors}Uncommitted changes:\n${dirty}\n" if [ -n "$errors" ]; then printf "PREREQ_FAIL:\n$errors"; exit 1; fi account=$(ls -d .hamster/*/ 2>/dev/null | head -1 | xargs basename) if pgrep -f "hamster sync --watch" >/dev/null 2>&1; then echo "PREREQ_OK account=${account} sync_pid=existing" else hamster sync --watch > /dev/null 2>&1 & echo "PREREQ_OK account=${account} sync_pid=$!" fi ``` - `PREREQ_FAIL` → show errors and stop (for uncommitted changes only: ask whether to proceed or stash) - `sync_pid=existing` → a watcher is already running (an interrupted session, or the user's own — `pgrep` matches machine-wide, so it may belong to another repo). Reuse it; do NOT kill it at completion - Numeric `sync_pid` → remember the literal number. Each Bash call is a fresh shell, so `$sync_pid` does not survive to later calls — at Completion, substitute it literally (e.g. `kill 12345`) --- ## Brief Selection > Referenced by `/hamster:plan` ("run the Brief Selection and Scheduling sections") — keep this section's name and behavior stable. ### If argument provided: Extract a slug from URL/UUID/slug and verify in one call: ```bash arg="$ARGUMENTS"; arg="${arg%/}" if echo "$arg" | grep -qE '^https?://'; then identifier=$(echo "$arg" | sed -E 's|^https?://[^/]+/home/[^/]+/briefs/([^/]+)(/tasks)?$|\1|') else identifier="$arg"; fi if echo "$identifier" | grep -qE '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'; then slug="" for brief_dir in .hamster/${account}/briefs/*/; do bf="${brief_dir}brief.md"; [ -f "$bf" ] || continue eid=$(awk -F'"' '/^---$/{n++; next} n==1 && /^entity_id:/ { print $2; exit }' "$bf") [ "$eid" = "$identifier" ] && { slug=$(basename "$brief_dir"); break; } done else slug="$identifier"; fi if [ -f ".hamster/${account}/briefs/${slug}/brief.md" ]; then echo "FOUND: $slug" else echo "NOT_FOUND: $identifier"; ls -d .hamster/${account}/briefs/*${slug}*/ 2>/dev/null | head -5; fi ``` If `NOT_FOUND`, suggest the partial matches shown. ### If no argument: List actionable briefs and let the user pick via AskUserQuestion: ```bash briefs_dir=".hamster/${account}/briefs" for brief_dir in "${briefs_dir}"/*/; do [ -d "$brief_dir" ] || continue slug=$(basename "$brief_dir"); brief_file="${brief_dir}brief.md"; tasks_dir="${brief_dir}tasks" [ -f "$brief_file" ] && [ -d "$tasks_dir" ] || continue brief_status=$(awk -F'"' '/^---$/{n++; next} n==1 && /^status:/ { print $2; exit }' "$brief_file") case "$brief_status" in aligned|delivering|refining) ;; *) continue ;; esac total=$(ls "$tasks_dir"/*.md 2>/dev/null | wc -l | tr -d ' '); [ "$total" -eq 0 ] && continue done_count=$(grep -l '^status: "done"' "$tasks_dir"/*.md 2>/dev/null | wc -l | tr -d ' ') title=$(awk -F'"' '/^---$/{n++; next} n==1 && /^title:/ { print $2; exit }' "$brief_file") echo "${brief_status}|${slug}|${title}|${done_count}/${total}" done | sort -t'|' -k1,1 ``` --- ## Scheduling (inline — no planner agent) > Referenced by `/hamster:plan` and `/hamster:resume` — keep this section's name and behavior stable. The plan already exists; this step only organizes it into waves. Parse all task frontmatter in one call: ```bash tasks_dir=".hamster/${account}/briefs/${slug}/tasks" for f in "$tasks_dir"/*.md; do [ -f "$f" ] || continue awk -F'"' -v file="$f" ' /^---$/ { n++; next } n == 1 && /^display_id:/ { did = $2 } n == 1 && /^entity_id:/ { eid = $2 } n == 1 && /^parent_task_id:/ { pid = $2 } n == 1 && /^title:/ { t = $2 } n == 1 && /^status:/ { s = $2 } n == 2 { print did "|" eid "|" pid "|" t "|" s "|" file; exit } ' "$f" done | sort -t'|' -k1,1 ``` From this output (format `HAM-123|entity-uuid|parent-uuid|Title|status|path`): 1. **Build the tree**: rows with empty `parent_task_id` are parents; rows whose `parent_task_id` matches a parent's `entity_id` are its subtasks. A parent with no subtasks is standalone. 2. **Filter**: skip parents whose entire subtree is `done`. Parents with `in_progress` tasks go in the earliest wave. 3. **Detect overlap** between remaining parents. Extract concrete mentions (file paths, PascalCase components, module names) from parent task bodies in one call: ```bash for f in {parent-task-files}; do echo "== $(basename "$f")" grep -ohE '[A-Za-z0-9_./-]+\.[a-z]{2,4}|[A-Z][a-z]+[A-Z][A-Za-z]+' "$f" | sort -u | head -20 done ``` Two parents sharing 2+ concrete mentions → conflict → serialize into different waves. Judgment call: titles clearly touching the same feature area also conflict. Do NOT search the codebase for this — task text only. 4. **Group greedily into waves**: Wave 1 = all mutually non-conflicting parents; conflicting parents fall to later waves. Show the user a compact schedule and confirm ONCE with AskUserQuestion ("Execute this schedule?" / "Modify" / "Cancel"): ``` {brief title} — {n} parents, {m} subtasks remaining ({d} done) Wave 1 (parallel): HAM-100 {title}, HAM-300 {title} Wave 2: HAM-200 {title} (conflicts with HAM-100: both touch auth/UserService) ``` --- ## Branch + Merge Base One call: ```bash lowest_id=$(ls "$tasks_dir"/*.md 2>/dev/null | xargs -I{} basename {} | grep -oE 'ham-[0-9]+' | sed 's/ham-//' | sort -n | head -1) branch="feature/ham-${lowest_id}-${slug}" git checkout -b "$branch" default_branch=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo "main") git fetch origin "$default_branch" && git merge "origin/$default_branch" --no-edit echo "branch=$branch base=$default_branch" ``` Merge conflict → **STOP**, report conflicts, do NOT auto-resolve. --- ## Execution Loop > Referenced by `/hamster:resume` (re-enters this loop at the resume wave) — keep this section's name and behavior stable. Same applies to Completion below. For each wave, in order: ### 1. Parallel Execution Launch ALL **task-executor** agents for this wave in a SINGLE message (parallel Agent calls). Each receives: parent display ID, subtask display IDs in order, brief slug, account slug, and a 2-3 sentence brief context summary. Executors load project skills and discover codebase context themselves — do not pre-chew context for them. Wait for all to complete; collect each executor's file list, deviations, and any PLAN_ISSUE. **Handling PLAN_ISSUE** (executor found a defect in the plan and skipped that task): 1. Verify the claim yourself — read the cited code/task; executors can be wrong too 2. **Local fix, same scope** (stale assumption with an obvious correct implementation): re-launch that task-executor with the corrected instruction; note it in the wave report 3. **Scope, API, or user-visible behavior change — or the task is obsolete** (already implemented, feature removed): AskUserQuestion with the executor's recommendation as the lead option ("Apply recommended alternative" / "Implement as originally written" / "Skip this task"). Never silently drop or rewrite a task 4. Record the resolution; surface all plan issues and deviations in the final report and PR body so they flow back into Hamster Studio Tier 1/2 deviations (documented adaptations with unchanged outcome) need no action here — the wave reviewer judges them. ### 2. Validate + Test (once per wave) First, run the repo's formatter in write mode — discover it from the repo's own scripts and tooling config, and pick the variant that fixes files, not the `--check` one. Formatting is a separate CI gate from lint in many repos; skipping it ships red PRs even when lint passes. Formatter rewrites are part of the wave: they land in each parent's staged files at commit time. Then checks and tests: ```bash # Detect tooling; run checks then tests if [ -f "package.json" ]; then pm=$(command -v pnpm >/dev/null && echo pnpm || (command -v yarn >/dev/null && echo yarn || echo npm)) $pm run typecheck 2>/dev/null; $pm run lint 2>/dev/null; $pm test 2>/dev/null elif [ -f "Cargo.toml" ]; then cargo check && cargo clippy 2>/dev/null && cargo test elif [ -f "go.mod" ]; then go build ./... && go vet ./... && go test ./... elif [ -f "Makefile" ]; then make check 2>/dev/null; make test 2>/dev/null fi ``` - Validation errors → fix directly with Edit (type errors, imports); re-launch a task-executor only for substantive failures - Test failures → STOP, report, ask user: fix or skip ### 3. Wave Review **Fast path**: if the wave diff is small (< ~150 changed lines) AND touches no sensitive areas, review the diff yourself inline against project conventions — no agent needed. Sensitive areas: auth, payments, migrations, security, CI workflows (`.github/workflows/`), env/secret config files, public API type definitions, and new dependencies (additions to package manifests — version bumps alone don't count). Otherwise launch ONE **wave-reviewer** agent with: wave number, parent IDs, per-parent file lists, brief context. It returns per-parent PASS/NEEDS_FIXES verdicts and applies simplifications for passing parents. **NEEDS_FIXES handling** (per parent): small issues (1-3 files) → fix directly with Edit; larger → re-launch task-executor with the issue list. Max 2 review rounds, then report to user. ### 4. Commit Wave (bisectable, per parent, sequential) For each parent: stage ONLY that parent's files (plus its simplifications). Split into logical commits when changes span concerns (infra → types → logic → UI → tests); a cohesive change gets one commit: ```bash git add {specific files} git diff --cached --name-only # verify staging git commit -m "feat(ham-{id}): {concise description} - {key change} Task: HAM-{id} Brief: {slug}" ``` - **NEVER** `git add .` / `git add -A`; never stage `.env*`, `.hamster/.state.json`, keys/secrets - Pre-commit hook fails → fix the issue, new commit; never `--no-verify` - Simplification changes commit as `refactor(ham-{id}): simplify post-review` ### 5. Progress Report ``` Wave {n} complete: HAM-{id} ✓ ({c} commits), HAM-{id} ✓ ({c} commits) Remaining: {n} waves, {n} parents ``` Non-interactive by default — only stop for: merge conflicts, test failures, critical review findings after 2 rounds, agent failures, or two executors having modified the same file (report and ask). --- ## Completion Stop sync, then final validation. Only kill the watcher if YOU started it (numeric `sync_pid` from Setup) — substitute the literal PID; if `sync_pid=existing`, leave it running: ```bash kill {literal-sync-pid} 2>/dev/null # re-run the wave validation block above for a final full check ``` If the final formatter pass leaves a diff (e.g. from post-review edits), commit it before the PR: `git add -u && git commit -m "style: apply repository formatter"`. **PR** — Ask the user ("Create a PR?" yes/later). If yes, inline (no agent): ```bash git push -u origin HEAD gh pr create --base "$default_branch" --title "{brief title, <70 chars}" --body "$(cat <<'EOF' ## Summary {1-3 sentences} ## Tasks - [x] HAM-123: {title} - [x] HAM-124: {title} ## Changes {grouped by area} ## Plan Feedback {deviations and plan issues encountered + how resolved — omit section if none} Brief: {slug} EOF )" ``` Then update brief status and report: ```bash hamster brief status ${slug} delivering ``` ``` Brief shipped: {title} Branch: {branch} | PR: {url or skipped} Tasks: {n}/{total} | Waves: {n} | Commits: {n} Plan feedback: {n deviations, n plan issues — or "none"} ``` --- ## Error Recovery | Error | Recovery | |-------|----------| | Prereq failure | Stop with instructions (uncommitted changes: ask stash/proceed) | | Brief not found | Show partial matches, ask user | | Auth expired | `hamster auth login`; continue without status updates if it fails | | Merge conflict (base or between executors) | Stop, report, never auto-resolve | | Test gate fails | Stop, report, ask fix or skip | | NEEDS_FIXES after 2 rounds | Report to user, ask skip or manual fix | | Agent fails | Report, ask retry or skip | | Executor returns PLAN_ISSUE | Verify, then: local fix → corrected re-launch; scope change → ask user | | Task already done | Skip it | ## Notes - One commit set per parent (subtasks are never committed individually) - Interrupted? `/hamster:resume` reconstructs state from git + task statuses - Single PR per brief by default; >15 tasks → ask about splitting