/update
Pull direction: bumps this workspace's scripts to match the latest hub template. Opposite of /push.
> /plugin marketplace add anshss/shiploop> /plugin install shiploop@shiploop
Command definition
update.mddescription: Pull direction: bumps this workspace's scripts to match the latest hub template. Opposite of /push.
allowed-tools: Bash, Read
/shiploop:update
The **pull direction** of the harness-code update channel — `git pull` for harness code. Reconciles this workspace against the latest installed hub, the ongoing-maintenance path after `/shiploop:setup` has scaffolded once. `scaffold.sh` is the machinery; this command wraps it with reachability resolution + safety guards + verification.
Procedure
1. **Locate the hub** (same priority as `/setup`, see "Locate the plugin" there). 2. **Verify preconditions** — meta-repo workspace, harness paths not dirty, no governor running. 3. **Cheap version + diff check** (writes nothing). 4. **Component-by-component bump** via `scaffold.sh --component <name> --yes` — PRESERVES `scripts/lib/workspace.sh` (never overwritten without explicit ask). 5. **Run no-auth verifiers** — `config-check.sh` + `bash -n` sweep + stale-relocations warning. The bump itself also **purges retired files**: every writer run applies `templates/lib/purge.txt`, so files an older shiploop installed and no longer ships are deleted from this workspace. Report what went, since a purge can remove something the operator was still opening by habit. 6. **Update `.harness-version` stamp** (scaffold does this every run). 7. **Report** a concise per-component `in-sync | bumped | skipped` summary.
Phase 0 — Locate the hub
Resolve `HUB` in priority order: 1. `${CLAUDE_PLUGIN_ROOT}` (plugin run) 2. `${GOVERN_UPSTREAM_HARNESS_DIR}` from `scripts/lib/workspace.sh` (operator's local fork clone) 3. `~/.claude/skills/shiploop/` (legacy clone-into-skills) 4. Glob `~/.claude/plugins/**/shiploop/VERSION` (plugin-cache install)
If none resolve, STOP and print:
Cannot locate the shiploop hub.
Options:
- Install as a plugin (recommended):
/plugin marketplace add anshss/shiploop
/plugin install shiploop@shiploop
- Point at a local clone by exporting one of:
CLAUDE_PLUGIN_ROOT=/path/to/shiploop (env)
GOVERN_UPSTREAM_HARNESS_DIR=/path/to/shiploop (workspace.sh)`SCAFFOLD=$HUB/scaffold.sh`. Confirm `bash "$SCAFFOLD" --version` prints a version and `$HUB/templates` is a directory — either check failing means the hub is unresolvable; print the same guidance.
Phase 0.5 — Hub freshness probe (network, best-effort)
`/update` is otherwise a no-network LOCAL reconcile — it compares this workspace against the hub clone on disk, which can itself be behind GitHub (observed: a clone 1 commit behind a merged PR would report "already at hub VERSION" while both are stale). Only when `$HUB` is a git clone (`git -C "$HUB" rev-parse --git-dir` succeeds), do a best-effort upstream check that degrades gracefully with no network and never blocks the update:
if git -C "$HUB" rev-parse --git-dir >/dev/null 2>&1; then
if git -C "$HUB" fetch -q origin 2>/dev/null; then
behind="$(git -C "$HUB" rev-list --count HEAD..origin/main 2>/dev/null || echo 0)"
if [ "${behind:-0}" -gt 0 ]; then
echo "⚠ hub clone is $behind commit(s) BEHIND origin/main ($HUB)."
echo " This local reconcile can only pull what the clone already has."
echo " Refresh it first (recommended): git -C \"$HUB\" pull --ff-only origin main"
fi
else
echo "── /update: offline or no 'origin' remote — skipping hub freshness probe (local reconcile only) ──"
fi
fiIf behind, WARN with the count and OFFER to pull `--ff-only` — do NOT auto-pull (operator may be pinned intentionally). If declined, proceed against the clone as-is and note in Phase 5 that the hub itself may be stale. Offline / non-git hub → skip silently.
Phase 1 — Workspace preconditions
Must be a meta-repo workspace (`scripts/lib/workspace.sh` exists) — else STOP, tell operator to run `/shiploop:setup` first.
**Branch guard.** Root must be on its default branch (`main`) — same rationale as setup.md Phase 0.5. Not on it → STOP, offer `git switch` first.
**Dirty-tree guard.** `git status --porcelain` on harness-owned paths (`scripts`, `.githooks`, `governor`, `.claude/settings.json`, `.claude/commands`, `package.json`, `.gitignore`) must be clean — else STOP and print the paths; the command overwrites mechanism scripts and would clobber uncommitted changes.
Phase 2 — Version + diff check (no writes)
HUB_V="$(bash "$SCAFFOLD" --version)"
STAMP_V="$(awk 'NF && $0 !~ /^#/ {print $1; exit}' scripts/lib/.harness-version 2>/dev/null || echo unknown)"
echo "hub: $HUB_V"
echo "stamp: $STAMP_V"
bash "$SCAFFOLD" --workspace-dir "$(pwd)" --diff-only # exit 3 = drift, 0 = cleanExit 0 + stamps match → print `── /update: workspace is up to date (hub=$HUB_V) ──` and stop. Else proceed to Phase 3 with the `behind` components as the bump plan.
Phase 3 — Component-by-component bump
Safe to refresh without an interview — these only read `workspace.sh`. Must cover every component `--diff-only` tracks, or an untracked one loops "behind" forever:
for c in core-scripts worktrees govern githooks commands agents workflows; do
bash "$SCAFFOLD" --workspace-dir "$(pwd)" --component "$c" --yes
done
bash "$SCAFFOLD" --workspace-dir "$(pwd)" --component config-merge --yes
`config-merge` is the **merge tier** — the components that converge an EXISTING workspace's config. None are byte-comparable, and until this landed none were reachable from `/update` at all, so a hub release that added an npm entrypoint or a `GOVERN_*` knob reached only brand-new installs. It bundles `seeds`, `readme`, `gitignore`, `package-json-merge`, `workspace-sh-merge`, `settings-merge`. Every one is additive and idempotent: a second run is a byte-level no-op. What each does:
| Component | Converges | Never does | |---|---|---| | `package-json-merge` | adds harness script keys the hub ships and this `package.json` lacks (`govern:validations`, `govern:dry-run`, …) plus `dev:<repo>`
Read more
description: Pull direction: bumps this workspace's scripts to match the latest hub template. Opposite of /push. allowed-tools: Bash, Read
/shiploop:update
The **pull direction** of the harness-code update channel — `git pull` for harness code. Reconciles this workspace against the latest installed hub, the ongoing-maintenance path after `/shiploop:setup` has scaffolded once. `scaffold.sh` is the machinery; this command wraps it with reachability resolution + safety guards + verification.
Procedure
1. **Locate the hub** (same priority as `/setup`, see "Locate the plugin" there). 2. **Verify preconditions** — meta-repo workspace, harness paths not dirty, no governor running. 3. **Cheap version + diff check** (writes nothing). 4. **Component-by-component bump** via `scaffold.sh --component <name> --yes` — PRESERVES `scripts/lib/workspace.sh` (never overwritten without explicit ask). 5. **Run no-auth verifiers** — `config-check.sh` + `bash -n` sweep + stale-relocations warning. The bump itself also **purges retired files**: every writer run applies `templates/lib/purge.txt`, so files an older shiploop installed and no longer ships are deleted from this workspace. Report what went, since a purge can remove something the operator was still opening by habit. 6. **Update `.harness-version` stamp** (scaffold does this every run). 7. **Report** a concise per-component `in-sync | bumped | skipped` summary.
Phase 0 — Locate the hub
Resolve `HUB` in priority order: 1. `${CLAUDE_PLUGIN_ROOT}` (plugin run) 2. `${GOVERN_UPSTREAM_HARNESS_DIR}` from `scripts/lib/workspace.sh` (operator's local fork clone) 3. `~/.claude/skills/shiploop/` (legacy clone-into-skills) 4. Glob `~/.claude/plugins/**/shiploop/VERSION` (plugin-cache install)
If none resolve, STOP and print:
Cannot locate the shiploop hub.
Options:
- Install as a plugin (recommended):
/plugin marketplace add anshss/shiploop
/plugin install shiploop@shiploop
- Point at a local clone by exporting one of:
CLAUDE_PLUGIN_ROOT=/path/to/shiploop (env)
GOVERN_UPSTREAM_HARNESS_DIR=/path/to/shiploop (workspace.sh)`SCAFFOLD=$HUB/scaffold.sh`. Confirm `bash "$SCAFFOLD" --version` prints a version and `$HUB/templates` is a directory — either check failing means the hub is unresolvable; print the same guidance.
Phase 0.5 — Hub freshness probe (network, best-effort)
`/update` is otherwise a no-network LOCAL reconcile — it compares this workspace against the hub clone on disk, which can itself be behind GitHub (observed: a clone 1 commit behind a merged PR would report "already at hub VERSION" while both are stale). Only when `$HUB` is a git clone (`git -C "$HUB" rev-parse --git-dir` succeeds), do a best-effort upstream check that degrades gracefully with no network and never blocks the update:
if git -C "$HUB" rev-parse --git-dir >/dev/null 2>&1; then
if git -C "$HUB" fetch -q origin 2>/dev/null; then
behind="$(git -C "$HUB" rev-list --count HEAD..origin/main 2>/dev/null || echo 0)"
if [ "${behind:-0}" -gt 0 ]; then
echo "⚠ hub clone is $behind commit(s) BEHIND origin/main ($HUB)."
echo " This local reconcile can only pull what the clone already has."
echo " Refresh it first (recommended): git -C \"$HUB\" pull --ff-only origin main"
fi
else
echo "── /update: offline or no 'origin' remote — skipping hub freshness probe (local reconcile only) ──"
fi
fiIf behind, WARN with the count and OFFER to pull `--ff-only` — do NOT auto-pull (operator may be pinned intentionally). If declined, proceed against the clone as-is and note in Phase 5 that the hub itself may be stale. Offline / non-git hub → skip silently.
Phase 1 — Workspace preconditions
Must be a meta-repo workspace (`scripts/lib/workspace.sh` exists) — else STOP, tell operator to run `/shiploop:setup` first.
**Branch guard.** Root must be on its default branch (`main`) — same rationale as setup.md Phase 0.5. Not on it → STOP, offer `git switch` first.
**Dirty-tree guard.** `git status --porcelain` on harness-owned paths (`scripts`, `.githooks`, `governor`, `.claude/settings.json`, `.claude/commands`, `package.json`, `.gitignore`) must be clean — else STOP and print the paths; the command overwrites mechanism scripts and would clobber uncommitted changes.
Phase 2 — Version + diff check (no writes)
HUB_V="$(bash "$SCAFFOLD" --version)"
STAMP_V="$(awk 'NF && $0 !~ /^#/ {print $1; exit}' scripts/lib/.harness-version 2>/dev/null || echo unknown)"
echo "hub: $HUB_V"
echo "stamp: $STAMP_V"
bash "$SCAFFOLD" --workspace-dir "$(pwd)" --diff-only # exit 3 = drift, 0 = cleanExit 0 + stamps match → print `── /update: workspace is up to date (hub=$HUB_V) ──` and stop. Else proceed to Phase 3 with the `behind` components as the bump plan.
Phase 3 — Component-by-component bump
Safe to refresh without an interview — these only read `workspace.sh`. Must cover every component `--diff-only` tracks, or an untracked one loops "behind" forever:
for c in core-scripts worktrees govern githooks commands agents workflows; do bash "$SCAFFOLD" --workspace-dir "$(pwd)" --component "$c" --yes done bash "$SCAFFOLD" --workspace-dir "$(pwd)" --component config-merge --yes
`config-merge` is the **merge tier** — the components that converge an EXISTING workspace's config. None are byte-comparable, and until this landed none were reachable from `/update` at all, so a hub release that added an npm entrypoint or a `GOVERN_*` knob reached only brand-new installs. It bundles `seeds`, `readme`, `gitignore`, `package-json-merge`, `workspace-sh-merge`, `settings-merge`. Every one is additive and idempotent: a second run is a byte-level no-op. What each does:
| Component | Converges | Never does | |---|---|---| | `package-json-merge` | adds harness script keys the hub ships and this `package.json` lacks (`govern:validations`, `govern:dry-run`, …) plus `dev:<repo>`
Other commands on shiploop.
More commands from other plugins.
build
CommandImplement tasks incrementally — build, test, verify, commit. Add "auto" to run the whole plan in one approved pass.

