From just-ship
Automates full git workflow: commit changes, push, create PR, merge to main, switch branch, update ticket status via Board API or Supabase. No questions, invoke with /ship T-N or keywords like 'done'.
npx claudepluginhub yves-s/just-ship --plugin just-shipThis skill uses the workspace's default tool permissions.
Vom uncommitted Code bis zum gemergten PR auf main. **Ein Befehl, keine Unterbrechung.**
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Creates isolated Git worktrees for feature branches with prioritized directory selection, gitignore safety checks, auto project setup for Node/Python/Rust/Go, and baseline verification.
Vom uncommitted Code bis zum gemergten PR auf main. Ein Befehl, keine Unterbrechung.
DU DARFST NICHT STOPPEN ODER FRAGEN. Führe ALLE Schritte 1-8 hintereinander aus. Kein "Soll ich...?", kein "Möchtest du...?", kein "Ich habe committed, soll ich jetzt pushen?". EINFACH ALLES DURCHLAUFEN.
Falls du den Drang hast eine Frage zu stellen: UNTERDRÜCKE IHN und mach einfach den nächsten Schritt.
finishing-a-development-branch aufrufenLies project.json. Bestimme den Pipeline-Modus:
pipeline.workspace_id gesetzt → board-api.sh verwenden:
bash .claude/scripts/board-api.sh patch "tickets/$TICKET_NUMBER" '{"status": "done"}'
Credentials werden intern aufgelöst. pipeline.project_id aus project.json.project_id gesetzt (ohne workspace_id), und project_id hat keine Bindestriche → execute_sql verwenden, Warnung ausgeben: "Kein Board API konfiguriert. Nutze Legacy Supabase MCP. Fuehre /setup-just-ship aus um zu upgraden."workspace_id noch project_id konfiguriert → Pipeline-Schritte überspringenproject_id Format-Check: Falls pipeline.project_id gesetzt ist und KEINE Bindestriche enthält (kurzer alphanumerischer String wie wsmnutkobalfrceavpxs), ist es eine alte Supabase-Projekt-ID. Warnung ausgeben: "pipeline.project_id sieht nach einer alten Supabase-ID aus. Fuehre /setup-just-ship aus um auf Board-UUID zu migrieren."
/shipKRITISCH — dieser Schritt setzt die Variable die ALLE folgenden Board-API-Calls verwenden.
Falls /ship mit Argument aufgerufen wird (z.B. /ship T-385):
T-385 → 385, oder 385 direkt):
TICKET_NUMBER=$(echo "$ARGUMENTS" | grep -oE '[0-9]+' | head -1)
git branch --list "*T-${TICKET_NUMBER}*" "*/${TICKET_NUMBER}-*" | head -1 | xargs
git checkout {branch}
Falls /ship ohne Argument: aktuellen Branch verwenden. Fehler wenn auf main.
In JEDEM Fall — Ticket-Nummer aus Branch-Name extrahieren und als Shell-Variable setzen:
TICKET_NUMBER=$(git branch --show-current | grep -oE 'T-[0-9]+' | head -1 | sed 's/T-//')
if [ -z "$TICKET_NUMBER" ]; then
echo "ERROR: Konnte keine Ticket-Nummer aus Branch-Name extrahieren. Branch: $(git branch --show-current)"
exit 1
fi
echo "TICKET_NUMBER=$TICKET_NUMBER"
ALLE folgenden Bash-Blöcke MÜSSEN $TICKET_NUMBER verwenden. Niemals {N} als Platzhalter in board-api.sh Calls — das wird nicht aufgelöst und erzeugt einen 404.
SOFORT WEITER ZU SCHRITT 1.
git status
Falls uncommitted changes:
git add <betroffene-dateien>
git commit -m "feat(T-{ticket}): {englische Beschreibung}
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
SOFORT WEITER ZU SCHRITT 2.
git push -u origin $(git branch --show-current)
SOFORT WEITER ZU SCHRITT 3.
gh pr view 2>/dev/null || gh pr create --title "feat(T-{ticket}): {Beschreibung}" --body "$(cat <<'EOF'
## Summary
- {Bullet Points}
## Test plan
- {Was wurde getestet}
🤖 Generated with [Claude Code](https://claude.com/claude-code)
EOF
)"
SOFORT WEITER ZU SCHRITT 3a.
PR-URL extrahieren und ins Ticket patchen:
REVIEW_URL=$(gh pr view --json url -q .url 2>/dev/null || echo "")
Board API (bevorzugt):
if [ -n "$REVIEW_URL" ]; then
if bash .claude/scripts/board-api.sh patch "tickets/$TICKET_NUMBER" '{"review_url": "'"$REVIEW_URL"'"}'; then
echo "✓ review_url auf T-$TICKET_NUMBER gesetzt"
else
echo "⚠ review_url Patch fehlgeschlagen für T-$TICKET_NUMBER — weiter ohne Blockade"
fi
fi
Legacy Supabase MCP (Fallback):
if [ -n "$REVIEW_URL" ]; then
mcp__claude_ai_Supabase__execute_sql "UPDATE public.tickets SET review_url = '$REVIEW_URL' WHERE number = $TICKET_NUMBER AND workspace_id = '{pipeline.workspace_id}' RETURNING number, review_url;"
fi
SOFORT WEITER ZU SCHRITT 3b.
Nur ausführen wenn hosting.provider gesetzt ist. Die Scripts prüfen selbst ob ein Hosting-Provider konfiguriert ist und exiten graceful wenn nicht. Bei nicht gesetztem hosting-Feld wird dieser gesamte Schritt übersprungen — kein API-Call, kein Warten.
WICHTIG: Die Preview-URL MUSS eine Deployment-URL sein (z.B. https://<project>-<hash>.vercel.app, https://<store>.myshopify.com/?preview_theme_id=... oder https://<app>.coolify-domain.tld). NIEMALS einen GitHub-Link, PR-URL oder Repository-URL als preview_url setzen. Das preview_url-Feld ist ausschließlich für die live deployete Vorschau.
# Read hosting provider from project.json (supports object and legacy string format)
HOSTING_PROVIDER=$(node -e "
const c = require('./project.json');
const h = c.hosting;
if (typeof h === 'object' && h !== null) {
process.stdout.write(h.provider || '');
} else if (typeof h === 'string') {
process.stdout.write(h);
}
")
if [ "$HOSTING_PROVIDER" = "shopify" ]; then
PREVIEW_URL=$(bash .claude/scripts/shopify-dev.sh start "T-${TICKET_NUMBER}" "${TITLE}")
elif [ "$HOSTING_PROVIDER" = "vercel" ]; then
PREVIEW_URL=$(bash .claude/scripts/get-preview-url.sh 30)
elif [ "$HOSTING_PROVIDER" = "coolify" ]; then
PREVIEW_URL=$(bash .claude/scripts/get-preview-url.sh 60)
else
# No hosting provider configured — skip preview URL entirely
PREVIEW_URL=""
fi
Falls eine URL gefunden wurde ($PREVIEW_URL nicht leer):
Board API:
if [ -n "$PREVIEW_URL" ]; then
if bash .claude/scripts/board-api.sh patch "tickets/$TICKET_NUMBER" '{"preview_url": "'"$PREVIEW_URL"'"}'; then
echo "✓ preview_url auf T-$TICKET_NUMBER gesetzt"
else
echo "⚠ preview_url Patch fehlgeschlagen für T-$TICKET_NUMBER — weiter ohne Blockade"
fi
fi
Legacy Supabase MCP (Fallback):
if [ -n "$PREVIEW_URL" ]; then
mcp__claude_ai_Supabase__execute_sql "UPDATE public.tickets SET preview_url = '$PREVIEW_URL' WHERE number = $TICKET_NUMBER AND workspace_id = '{pipeline.workspace_id}' RETURNING number, preview_url;"
fi
Falls keine URL gefunden: still überspringen, kein Fehler. Das Script exits immer mit Code 0.
SOFORT WEITER ZU SCHRITT 3c.
PID-Tracking: /just-ship-review speichert die Dev-Server-PID in .claude/.dev-server-pid.
if [ -f ".claude/.dev-server-pid" ]; then
PID=$(cat .claude/.dev-server-pid)
kill $PID 2>/dev/null || true
rm -f .claude/.dev-server-pid
fi
Falls PID-Datei nicht existiert aber build.dev_port in project.json konfiguriert:
DEV_PORT=$(node -e "process.stdout.write(String(require('./project.json').build?.dev_port || ''))")
if [ -n "$DEV_PORT" ]; then
lsof -ti :$DEV_PORT | xargs kill 2>/dev/null || true
fi
SOFORT WEITER ZU SCHRITT 4.
gh pr merge --squash --delete-branch
SOFORT WEITER ZU SCHRITT 5.
git checkout main && git pull origin main
# Lokalen Branch aufräumen (Remote wird von --delete-branch gelöscht)
git branch -d {branch} 2>/dev/null || true
SOFORT WEITER ZU SCHRITT 5a.
Bestimme Shopify-Variant und führe den passenden Post-Merge-Step aus:
PLATFORM=$(node -e "process.stdout.write(require('./project.json').stack?.platform || '')" 2>/dev/null || echo "")
VARIANT=$(node -e "process.stdout.write(require('./project.json').stack?.variant || '')" 2>/dev/null || echo "")
App-Variant (variant: "remix"): Extensions und App-Config deployen:
if [ "$PLATFORM" = "shopify" ] && [ "$VARIANT" = "remix" ]; then
bash .claude/scripts/shopify-app-deploy.sh
fi
Das Script:
shopify app deploy --force ausSHOPIFY_CLI_PARTNERS_TOKEN (VPS) oder CLI-Session (lokal)Ausgabe:
✓ shopify — Extensions und App-Config deployed (bei Erfolg)⚠ shopify — App Deploy fehlgeschlagen, manuell deployen (bei Fehler)Theme-Variant (default): Unpublished Preview-Theme löschen:
if [ "$PLATFORM" = "shopify" ] && [ "$VARIANT" != "remix" ]; then
THEME_ID_FILE=".worktrees/T-${TICKET_NUMBER}/.claude/.shopify-theme-id"
[ ! -f "$THEME_ID_FILE" ] && THEME_ID_FILE=".claude/.shopify-theme-id"
SHOPIFY_THEME_ID_FILE="$THEME_ID_FILE" bash .claude/scripts/shopify-preview.sh cleanup
fi
Ausgabe:
✓ shopify — Theme gelöscht (falls Theme-ID vorhanden)SOFORT WEITER ZU SCHRITT 5b.
Prüfe ob ein Worktree für dieses Ticket existiert:
if [ -d ".worktrees/T-$TICKET_NUMBER" ]; then
git worktree remove ".worktrees/T-$TICKET_NUMBER" --force 2>/dev/null || true
fi
Ausgabe: ✓ worktree — .worktrees/T-$TICKET_NUMBER aufgeräumt (falls vorhanden)
Falls kein Worktree existiert: still überspringen.
SOFORT WEITER ZU SCHRITT 5c.
Hinweis: Schritt 3b (Vercel Preview URL) bleibt unverändert. Für Shopify-Projekte returned das Vercel-Script leer, und die Preview-URL wurde bereits während /develop Schritt 9f ins Ticket geschrieben.
Token-Delta-Berechnung und Board-Update läuft deterministisch über ein Script:
bash .claude/scripts/ship-token-tracking.sh $TICKET_NUMBER
Das Script berechnet das Delta zwischen dem Start-Snapshot (geschrieben bei /develop Step 3e) und dem aktuellen Session-Stand, patcht das Board mit den granularen Token-Feldern und räumt den Snapshot auf.
SOFORT WEITER ZU SCHRITT 6.
Board API (bevorzugt): Via Bash curl mit Retry bei Fehler:
SHIP_STATUS_OK=false
for ATTEMPT in 1 2 3; do
if bash .claude/scripts/board-api.sh patch "tickets/$TICKET_NUMBER" '{"status": "done", "summary": "{pr_summary}"}'; then
SHIP_STATUS_OK=true
break
fi
echo "Board-Status-Update fehlgeschlagen (Versuch $ATTEMPT/3), retry in ${ATTEMPT}s..."
sleep $ATTEMPT
done
if [ "$SHIP_STATUS_OK" != "true" ]; then
echo "⚠ Board-Status-Update auf 'done' fehlgeschlagen nach 3 Versuchen. Manuell prüfen: T-$TICKET_NUMBER"
fi
Hinweis: summary wird mitgesendet damit das Board eine Zusammenfassung des abgeschlossenen Tickets anzeigt.
Legacy Supabase MCP (Fallback): Via mcp__claude_ai_Supabase__execute_sql:
UPDATE public.tickets SET status = 'done', summary = '{summary}' WHERE number = $TICKET_NUMBER AND workspace_id = '{pipeline.workspace_id}' RETURNING number, title, status;
SOFORT WEITER ZU SCHRITT 7.
✓ Shipped: feat(T-{ticket}): {Beschreibung}
PR: {url}
Branch: {branch} → deleted
Worktree: .worktrees/T-$TICKET_NUMBER → aufgeräumt (falls vorhanden)
Board: done (falls konfiguriert)
Prüfe ob andere Branches aufgeräumt werden sollten:
git fetch --prune
STALE=$(git branch -v | grep '\[gone\]' | awk '{print $1}')
BEHIND=$(git for-each-ref --format='%(refname:short) %(upstream:track)' refs/heads | grep -v 'main' | while read branch track; do
COUNT=$(git rev-list --count "$branch..main" 2>/dev/null || echo 0)
if [ "$COUNT" -gt 50 ]; then
echo "$branch — $COUNT Commits hinter main"
fi
done)
Falls stale oder weit hinter main (>50 Commits):
Hinweis: Folgende Branches könnten aufgeräumt werden:
{branch-name} — Remote gelöscht
{branch-name} — 73 Commits hinter main
Nur als Hinweis — nicht automatisch löschen.
git pull --rebase origin {branch}, dann nochmal pushengit add -A oder git add .--force push--amend bei Hook-Failurefinishing-a-development-branch aufrufen