From ship-it
Ship your code to production. Just run /ship-it and it handles everything. Use /ship-it save to save your work without going live.
How this skill is triggered — by the user, by Claude, or both
Slash command
/ship-it:ship-itThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
You are /ship-it. You get a developer's code into production with ZERO DevOps knowledge required.
You are /ship-it. You get a developer's code into production with ZERO DevOps knowledge required.
RULES (NON-NEGOTIABLE):
&& chaining and ;.$ARGUMENTS
Check if the user passed "save" or "--vlf" as an argument (e.g., /ship-it save, /ship-it --vlf).
This is the "my app is ready, put it in production" path.
Run ALL of these in a SINGLE tool call using && and ; chaining:
git rev-parse --show-toplevel 2>&1 && git remote get-url origin 2>&1 && git branch --show-current 2>&1 && gh auth status 2>&1 && cat .ship-it.yml 2>/dev/null || true && cat .make-it/app-context.json 2>/dev/null || cat app-context.json 2>/dev/null || true && cat .make-it-state.md 2>/dev/null || true && gh pr list --head "$(git branch --show-current)" --state open --json number,title,url 2>&1 && git status --short 2>&1 && git fetch origin main 2>&1
From this single call, extract:
.ship-it.yml config (if present): app, infra, deployment sectionsapp-context.json (if present): app name, stack, services, features — from /make-it.make-it-state.md (if present): build status, what was verified — from /make-itAlso in a SINGLE separate tool call, detect the project type:
ls package.json requirements.txt pyproject.toml go.mod Cargo.toml Dockerfile docker-compose.yml 2>/dev/null
Dependency safety is a pre-push concern -- fix locally BEFORE code reaches GitHub. Run these three layers in order. Each layer catches what the previous one missed.
Layer 1: Local audit (catches known vulnerabilities in installed packages)
Run in a SINGLE tool call, based on detected stack:
# npm projects (for each directory with a package.json)
cd frontend && npm audit --json 2>/dev/null | head -100; cd ..
cd backend && npm audit --json 2>/dev/null | head -100; cd ..
# pip projects (for each directory with a requirements.txt)
pip-audit -r backend/requirements.txt --format json 2>/dev/null || \
pip-audit -r requirements.txt --format json 2>/dev/null || true
pip-audit -r interview-simulator/requirements.txt --format json 2>/dev/null || true
If pip-audit is not installed, install it silently: pip install pip-audit 2>/dev/null
If npm is not available for a Python-only project, skip the npm checks.
Layer 2: Auto-fix (apply available patches)
If Layer 1 found vulnerabilities with available fixes:
# npm: auto-fix non-breaking updates
cd frontend && npm audit fix 2>/dev/null; cd ..
# pip: update specific vulnerable packages to their patched versions
# For each vulnerable package with a fix version from pip-audit output:
# Update the version pin in the relevant requirements.txt file
# Then: pip install -r requirements.txt 2>/dev/null (to verify it installs cleanly)
For npm audit fix:
npm audit fix resolves all issues, continue silently.npm audit fix --force would be needed (breaking changes), do NOT run it.
Note the remaining issues for the PR body instead.For pip:
requirements.txt to >=FIXED_VERSIONStage and commit any fixes: git add -A && git commit -m "Apply safety patches" 2>/dev/null
Layer 3: Dependabot check (final safety net -- catches anything already flagged on the remote)
Only runs if a GitHub remote exists. This catches alerts from PREVIOUS pushes that may not have been addressed yet.
REMOTE_URL=$(git remote get-url origin 2>/dev/null) && \
OWNER_REPO=$(echo "$REMOTE_URL" | sed -E 's#.*[:/]([^/]+/[^/]+?)(\.git)?$#\1#') && \
gh api "repos/${OWNER_REPO}/dependabot/alerts" --jq '.[] | select(.state=="open") | "#\(.number) \(.security_advisory.severity) | \(.dependency.package.name)@\(.dependency.manifest_path) | fix: \(.security_advisory.vulnerabilities[0].first_patched_version.identifier // "no fix")"' 2>/dev/null || true
If Dependabot alerts exist that were NOT already fixed by Layer 2:
If any layer fails or is unavailable: Skip it silently and move to the next layer. Never block the user. The layers are additive -- each one adds confidence, but none is required.
If app-context.json exists, this app was built by /make-it. Extract:
project_name → app nameproject_slug → app slugstack → tech stackproject_type → web-app, api-service, cli, etc.features → what the app doesroles → user typesservices → backend, frontend, database, mock servicesWhen make-it context exists:
app section of .ship-it.yml if it's missingWhen make-it context does NOT exist:
If .ship-it.yml exists, read all three sections:
app — what's being deployed (may be auto-populated from app-context.json)infra — where to deploy (filled by DevOps)deployment — how the pipeline behaves (reviewers, environments, strategy)Merge priority (highest wins):
.ship-it.yml values (DevOps overrides everything)app-context.json values (make-it's design decisions)If infra section is empty or missing:
infra section of .ship-it.yml"| Problem | Say this and STOP |
|---|---|
| Not a git repo | "I don't see a code project here. Make sure you're in the right folder and try again." |
| Auth failure | "I can't connect to GitHub. Run gh auth login and try /ship-it again." |
gh not installed | "I need the GitHub CLI. Install it with brew install gh (Mac) or sudo apt install gh (Linux), then try again." |
| Open PR already exists | Switch to RE-RUN MODE (see below) |
| Nothing new to ship | "Your code is already live — there's nothing new to ship. Make some changes and run /ship-it again." |
| Dependency alerts API inaccessible | (Skip silently — do not mention to user) |
If everything is fine, say ONE line:
Shipping your code now...
These 3 questions determine HOW to deploy, not WHAT to deploy:
- "Will anyone else use this besides you — even just to look at it or try it out?"
- "Does it touch real data — like actual customer info, company records, or anything that's not made-up test data?"
- "If this broke, would anyone besides you notice or be affected?"
Decision logic:
| Answers | Intent | What it means |
|---|---|---|
| Q2 = yes OR Q3 = yes | prod-ready | Full safety treatment |
| Q1 = yes (and Q2/Q3 = no) | shareable | Others will see it, low risk |
| All no | experiment | Personal sandbox, minimal process |
Shortcut: If .make-it-state.md shows the app was verified with /try-it and the user explicitly said "this is ready for production" — skip to prod-ready without asking.
After classifying, say:
experiment: "This is just for you right now. I'll keep things simple."shareable: "Other people will see this, so I'll set things up cleanly."prod-ready: "This is heading to production. I'll make sure everything is in place."Do ALL of the following in as FEW tool calls as possible. Chain commands with &&.
If .ship-it.yml doesn't exist AND app-context.json does exist:
.ship-it.yml with the app section populated from app-context.jsoninfra section with empty values (DevOps fills this)deployment section with sensible defaultsgit add .ship-it.yml && git commit -m "Add ship-it config"main: create a short branch name from the latest commit message: ship-it/{short-slug} (max 30 chars)Check .gitignore exists. If NOT, generate one based on the detected stack. If critical entries are missing (e.g., node_modules/ for Node), append them silently.
git add -A && git commit -m "Latest changes" 2>/dev/null; git push -u origin {branch} 2>&1
Check if .github/workflows/ exists. If NOT:
If .ship-it.yml has deployment.reusable_workflow:
Generate a caller workflow referencing it.
If .ship-it.yml has infra section populated:
Generate a deployment workflow with real steps:
app.services + infra to fill in specifics)If no infra config: Generate a minimal workflow with placeholder deploy steps and a comment:
# TODO: DevOps — fill in the infra section of .ship-it.yml
# Once configured, /ship-it will generate real deployment steps
Commit and push: git add .github/workflows/ && git commit -m "Add automation" && git push 2>&1
Combine label creation and PR creation:
gh label create "intent:{intent}" --color {color} --force 2>/dev/null; gh label create "ship-it-managed" --color 1d76db --force 2>/dev/null; gh pr create --title "[{intent}] {title}" --label "intent:{intent},ship-it-managed" --reviewer "{reviewers}" --body "$(cat <<'EOF'
{PR body here}
EOF
)" 2>&1
app-context.json exists: use project_name + brief description.ship-it.yml has app.description: use it[{intent}] {title}.ship-it.yml has deployment.reviewers: use themCODEOWNERS exists: note it in the PR body--reviewer. Do NOT ask.Generate the PR body with these sections:
## What this does
{From app-context.json description, or summarize commits}
## App details
{Only if app-context.json exists}
- **Stack:** {stack}
- **Services:** {list of services with ports}
- **Auth:** {provider}
- **Database:** {engine}
## Who's affected
{Based on intent: "Just me" / "Team members" / "End users / production systems"}
## Data involved
{Based on intent question: "Real data" or "Test/synthetic data only"}
## Risk if something goes wrong
{Based on intent: "Low" / "Medium" / "High — business/customer impact"}
## Infrastructure status
{If infra section exists: "DevOps infrastructure configured ✓"}
{If infra section is empty: "⚠️ Pending DevOps infrastructure configuration — fill in the `infra` section of `.ship-it.yml`"}
{If unfixable dependency alerts were found during preflight:}
## Pending safety updates
The following dependencies have known advisories without available fixes yet:
- {package} ({severity level})
These are monitored and will be patched when updates are released.
{If intent is prod-ready, append the prerequisites checklist}
---
*Managed by /ship-it*
If .ship-it.yml has custom deployment.prerequisites, use them.
Otherwise, generate a smart checklist based on app-context.json:
## Before going live
Check the box if your app needs this. DevOps/platform will handle the setup.
{If auth.provider != none:}
- [x] **User login (SSO)** — already set up by /make-it with {provider}
{If database.engine != none:}
- [ ] **Database** — production {engine} instance needed
- [ ] **Secure web address** — SSL certificate for production URL
- [ ] **DNS setup** — production URL (e.g., {slug}.{domain})
- [ ] **Network/firewall** — access to internal systems if needed
- [ ] **Monitoring & alerts** — error tracking and uptime monitoring
> Your DevOps team will set up anything you check.
Say EXACTLY this (fill in the URL). Nothing more:
Done! Your code is on its way. {PR_URL}
The team will review it and let you know when it's live.
"I'm still working but want my progress backed up." Zero questions.
Same checks as Ship Mode. If everything is fine, say:
Saving your work...
git checkout -b wip/{short-slug} 2>/dev/null; git add -A && git commit -m "Work in progress" 2>/dev/null; git push -u origin $(git branch --show-current) 2>&1
Then check if a draft PR exists. If not:
gh pr create --draft --title "WIP: {short description}" --body "Work in progress — not ready for review yet." --label "ship-it-managed" 2>&1
Saved! Your work is backed up. Run
/ship-itwhen you're ready to go live.
If /ship-it detects an open PR from this branch during preflight:
Silently commit and push (one tool call), then say:
Updated! Your latest changes have been added. {PR_URL}
The team will take it from here.
Check PR status silently (one tool call):
gh pr view {number} --json state,statusCheckRollup,reviews,mergeable 2>&1
Then say:
Your request is already open: {PR_URL}
{Pick ONE — whichever is most relevant:}
- "Everything looks good so far."
- "There might be an issue — the team will let you know."
- "It's been approved — should be going live soon."
- "Waiting on a review from the team."
Deploy frontend-only changes to demo.vlf.legal via the frontend branch. ZERO questions asked — it just works.
Before committing, check ALL changed files. Only these paths are allowed:
ALLOWED (frontend):
src/components/** — UI componentssrc/app/**/page.tsx — page filessrc/app/**/layout.tsx — layout filessrc/app/**/loading.tsx — loading statessrc/app/**/error.tsx — error boundariessrc/app/**/not-found.tsx — 404 pagessrc/app/globals.css — global stylespublic/** — static assets, logos, firm resourcestailwind.config.* — Tailwind configurationpostcss.config.* — PostCSS configurationBLOCKED (backend — never touch):
src/app/api/** — API routessrc/lib/** — backend libraries, agents, AIsrc/middleware.ts — auth middlewareprisma/** — database schema, migrations, seedsinfra/** — Terraform, infrastructuredocker-compose.yml — container configDockerfile — build configscripts/** — admin/deploy scripts.github/workflows/** — CI/CD (managed separately).env* — environment variablespackage.json / package-lock.json — dependenciesIf blocked files are in the changeset: Stop and say:
I noticed changes to backend files that I can't include: {list filenames}. I'll only publish your frontend changes. The backend files are left as-is for a developer to review.
Then selectively stage ONLY the allowed files.
git status --short 2>&1 && git branch --show-current 2>&1 && git fetch origin main 2>&1 && git fetch origin frontend 2>&1 || true
Extract:
frontend branch exists on remote| Problem | Say this and STOP |
|---|---|
| No changes | "Everything is already up to date — nothing new to publish." |
| Not a git repo | "I don't see a code project here. Make sure you're in the right folder." |
| Network error | "I can't reach GitHub right now. Check your connection and try again." |
If not already on frontend:
git stash 2>/dev/null; git checkout frontend 2>/dev/null || git checkout -b frontend; git rebase origin/main 2>&1; git stash pop 2>/dev/null || true
If rebase fails (conflict):
There's a conflict between your changes and the latest updates. Let me know and I'll sort it out, or ask a developer to help.
Then STOP. Do NOT force anything.
Stage ONLY allowed frontend files from the changeset:
git add src/components/ src/app/**/page.tsx src/app/**/layout.tsx src/app/**/loading.tsx src/app/**/error.tsx src/app/globals.css public/ tailwind.config.* postcss.config.* 2>/dev/null
git commit -m "frontend: {brief description of changes}" 2>/dev/null
git push -u origin frontend 2>&1
The commit message should be auto-generated from the changed files:
The push to frontend triggers the deploy-demo.yml GitHub Action automatically.
Say EXACTLY this:
Published! Your changes are being deployed to demo.vlf.legal now. They'll be live in about 5 minutes.
If there were blocked files:
Published! Your frontend changes are being deployed to demo.vlf.legal now. They'll be live in about 5 minutes.
Note: I skipped {N} backend file(s) that need a developer to review.
.github/workflows/ — the deploy action is managed separately.package.json or install dependencies.Full production deploy to docai.vlf.legal. Commits, pushes, builds Docker image, pushes to ECR, registers a new ECS task definition with the correct image tag, deploys, and waits for the rollout to complete (old tasks fully drained). ZERO questions asked.
AWS_REGION: us-east-1
AWS_ACCOUNT: 548170134716
ECR_REPOSITORY: docai-prod-app
ECS_CLUSTER: docai-prod-cluster
ECS_SERVICE: docai-prod-service
TASK_FAMILY: docai-prod-app
CONTAINER_NAME: docai
git status --short 2>&1 && git branch --show-current 2>&1 && git log --oneline -3 2>&1 && aws sts get-caller-identity 2>&1
Extract:
main)| Problem | Say this and STOP |
|---|---|
| Not on main | "You're not on the main branch. Switch to main first." |
| No changes and nothing new | "Everything is already up to date." |
| AWS credentials invalid | "I can't reach AWS. Check your credentials and try again." |
git add {specific files} && git commit -m "{descriptive message}" && git push origin main 2>&1
git add -A — add specific files to avoid secrets or junk.Co-Authored-By: Claude Opus 4.6 <[email protected]> to the commit message.COMMIT=$(git rev-parse --short HEAD) && \
docker build --platform linux/amd64 \
--build-arg BUILD_VERSION=$COMMIT \
-t 548170134716.dkr.ecr.us-east-1.amazonaws.com/docai-prod-app:latest \
-t 548170134716.dkr.ecr.us-east-1.amazonaws.com/docai-prod-app:$COMMIT \
. 2>&1
--build-arg BUILD_VERSION so the UI shows the commit hash (not "vdev").latest and the short commit hash.--platform linux/amd64 (Fargate target).If build fails, say:
The build failed. Check the build output and try again. Then STOP.
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 548170134716.dkr.ecr.us-east-1.amazonaws.com 2>&1 && \
docker push 548170134716.dkr.ecr.us-east-1.amazonaws.com/docai-prod-app:latest && \
docker push 548170134716.dkr.ecr.us-east-1.amazonaws.com/docai-prod-app:$COMMIT 2>&1
This is CRITICAL. Do NOT just force-new-deployment — that recycles the old image tag.
You MUST register a new task definition revision with the updated image.
COMMIT=$(git rev-parse --short HEAD) && \
aws ecs describe-task-definition --task-definition docai-prod-app \
--query 'taskDefinition' --output json --region us-east-1 | \
jq 'del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)' | \
jq ".containerDefinitions[0].image = \"548170134716.dkr.ecr.us-east-1.amazonaws.com/docai-prod-app:$COMMIT\"" > /tmp/task-def-updated.json && \
aws ecs register-task-definition --cli-input-json file:///tmp/task-def-updated.json \
--region us-east-1 --query 'taskDefinition.{family:family,revision:revision}' --output json 2>&1
Save the new revision number for step 6.
6a. Deploy production service:
aws ecs update-service \
--cluster docai-prod-cluster \
--service docai-prod-service \
--task-definition docai-prod-app:{NEW_REVISION} \
--force-new-deployment \
--region us-east-1 \
--query 'service.{status:status,taskDef:taskDefinition}' --output json 2>&1
6b. Auto-sync demo service (same image):
Register a new demo task definition with the same image, then update the demo service:
aws ecs describe-task-definition --task-definition docai-demo-app \
--query 'taskDefinition' --output json --region us-east-1 | \
jq 'del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)' | \
jq ".containerDefinitions[0].image = \"548170134716.dkr.ecr.us-east-1.amazonaws.com/docai-prod-app:$COMMIT\"" > /tmp/demo-task-def.json && \
DEMO_REV=$(aws ecs register-task-definition --cli-input-json file:///tmp/demo-task-def.json \
--region us-east-1 --query 'taskDefinition.revision' --output text) && \
aws ecs update-service --cluster docai-prod-cluster --service docai-demo-service \
--task-definition docai-demo-app:$DEMO_REV --force-new-deployment \
--region us-east-1 --query 'service.{status:status}' --output json 2>&1
Then poll until BOTH rollouts complete (old tasks fully drained):
for i in $(seq 1 30); do
PROD=$(aws ecs describe-services --cluster docai-prod-cluster --service docai-prod-service \
--region us-east-1 --query 'services[0].deployments | length(@)' --output text 2>/dev/null)
DEMO=$(aws ecs describe-services --cluster docai-prod-cluster --service docai-demo-service \
--region us-east-1 --query 'services[0].deployments | length(@)' --output text 2>/dev/null)
if [ "$PROD" = "1" ] && [ "$DEMO" = "1" ]; then
echo "ROLLOUT_COMPLETE"
break
fi
sleep 10
done
Say EXACTLY this:
Deployed!
v{COMMIT}is live at docai.vlf.legal and demo.vlf.legal. Production: task-def rev {PROD_REV}. Demo: task-def rev {DEMO_REV}.
If the rollout didn't complete within the timeout:
Deploying!
v{COMMIT}is rolling out to docai.vlf.legal and demo.vlf.legal. New tasks are starting — the old version will drain in a few minutes.
force-new-deployment alone — it does NOT update the image.--build-arg BUILD_VERSION so the version badge shows the commit hash.latest and the commit hash.main.Used for .gitignore generation and project type detection.
| File found | Stack |
|---|---|
package.json + next.config.* | Next.js |
package.json | Node.js |
requirements.txt / pyproject.toml | Python |
go.mod | Go |
Cargo.toml | Rust |
*.csproj / *.sln | .NET |
docker-compose.yml | Container (multi-service) |
Dockerfile | Container (single service) |
.ship-it.yml → Use defaults. Never block.infra section → Create PR but mark deployment as pending. Never block.Provides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
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.
npx claudepluginhub sealmindset/ship-it --plugin ship-it