---
description: End-to-end ship flow — runs /kit:deploy-check, prints the exact commit list, authors a shipping-log Brief (doc/shipping-logs/*.md) that joins the push payload, then pushes the named branch only after explicit confirmation. The Brief publishes itself to the LS Comm Hub via file-content-sync when the push lands — no separate post, no local log file. Stack-agnostic: detects the project's migrations tooling and deploy platform at runtime rather than assuming any one stack. Never calls a host's deploy CLI directly — pushing to a deploy-tracked branch lets the host's git integration deploy.
---

# /kit:ship

Compose `/kit:deploy-check` + shipping-log Brief + `git push` into one gated workflow. The skill is the human checklist for "I'm shipping now" — same checks a careful human would run, encoded so they cannot be skipped or reordered.

This skill ships with `launch-kit init` into **any** project — Node, Python, Ruby, Go, Rust, a static site, a library — so it makes **no stack assumptions**. It detects the migrations tooling and deploy platform at runtime (Preflight step 7) and adapts; anything it can't detect, it degrades to a safe, platform-neutral path rather than referencing a tool the repo doesn't have.

The shipping log is a **Brief**: a markdown doc at `doc/shipping-logs/<date>-<target>-<sha>.md`, committed as part of the ship itself. If the project syncs its docs to LaunchSecure (the `shipping-logs` doc category mapped to the discussion handler — the same file-content-sync mechanism the LS server uses for any `doc/**` category), the moment the push lands the doc materializes as a Comm Hub comment under the "Shipping Logs" header — team-visible, branch-aware, no separate publish step. If the project is not wired to LaunchSecure, the Brief is still a durable in-repo record of every ship. The repo records what shipped; the Comm Hub renders it when present. One artifact, one or two surfaces.

Hard rule from project memory: **never push commits the user didn't explicitly name**, and **pushing to a deploy-tracked branch may auto-deploy** — so this skill always asks per-action before pushing, even when invoked with arguments. There is no `--yes-i-mean-it` flag.

Parse `$ARGUMENTS`:

- (empty) — full flow on the current branch → its upstream (resolved via `git rev-parse --abbrev-ref @{u}` or, missing that, `origin/<current-branch>`).
- **--to=&lt;branch&gt;** — push to a different remote branch (e.g. `--to=staging`). The skill never creates a new remote branch silently — if the target doesn't exist on origin, surface that and ask.
- **--dry-run** — run deploy-check + build the shipping-log Brief preview, but do NOT write the doc, do NOT commit, do NOT push. Useful for "what would shipping look like right now?".
- **--note=&lt;text&gt;** — free-form human note rendered as the Brief's `**Note**:` line. Useful for hotfix context, rollout caveats, or named reviewer.
- **--skip-check=&lt;list&gt;** — forwarded verbatim to `/kit:deploy-check --skip=<list>`. Surface skipped checks in the final report; do not let `--skip-check=build` slip through silently.
- **--quick** — forwarded to deploy-check (skips the slow `build` step). Refuse if the target is a deploy-tracked branch (`master` or whatever the repo's default is) — for prod, build must run.

Examples:

- `/kit:ship` → check, author Brief, confirm, push current branch (including the Brief) to its upstream.
- `/kit:ship --dry-run` → check + preview the Brief only; no writes, no push.
- `/kit:ship --to=master` from `implementation` → cross-branch merge-and-deploy flow (see "Cross-branch ship" below).
- `/kit:ship --note="hotfix for feedback widget 500s"` → Brief carries the note.

## Preflight

1. `git rev-parse --is-inside-work-tree` — abort if not a git repo.
2. Resolve current branch and the **deploy-target branch**:
   - If `--to=` given, use it.
   - Else `git rev-parse --abbrev-ref @{u}` (the branch's tracked upstream).
   - Else fall back to `origin/<current>` and warn that no upstream is set.
3. Resolve the **deploy-tracked branch list** for "is this a prod push?" gating:
   - Read `git symbolic-ref refs/remotes/origin/HEAD --short` (strip `origin/`) → that's the repo's default; treat as deploy-tracked.
   - Always treat `master` and `main` as deploy-tracked if they exist on origin.
   - If the resolved target is in this list, set `IS_PROD_PUSH=true`. Used to escalate confirmations and refuse `--quick` / `--skip-check=build`.
4. Print the scope upfront — the user should never wonder what's about to happen:
   ```
   ship: <current-branch> → <target-branch>   (prod: yes|no)
   ```
5. Stash check — if `git status --porcelain` shows uncommitted changes, refuse with `✗ working tree not clean — commit, stash, or discard before shipping`. Shipping with dirty state is never the right move; the deploy-check skill is lenient about it because it's read-only, this skill is not.
6. Confirm the shipping-logs category exists — `doc/shipping-logs/` directory present OR `shipping-logs` listed in the project's docCategories config (e.g. a `.launch-secure.config` / launch-kit config). If neither, warn: `⚠ shipping-logs doc category not registered — the Brief is still written to the repo, but won't sync to the Comm Hub` and continue.
7. **Detect the project stack** (this is what makes the skill portable — do it once here, reuse in steps 3 and 5). Probe the working tree; do not assume:
   - **`MIGRATIONS_DIR`** — the first of these that exists, else `none`:
     `prisma/migrations/` (Prisma) · `supabase/migrations/` (Supabase) · `drizzle/` or a dir containing `drizzle/meta/` (Drizzle) · `db/migrate/` (Rails/ActiveRecord) · `migrations/` with `alembic.ini` nearby or `alembic/versions/` (Alembic) · `*/migrations/` under a Django app · `src/main/resources/db/migration/` (Flyway) · a knex/TypeORM/Sequelize migrations dir named in the repo's ORM config. If a launch-kit/project config declares an explicit `migrationsPath`, that wins over detection. If none match, `MIGRATIONS_DIR=none` → the migration gate (step 3) is skipped entirely. Non-DB projects (libraries, CLIs, static sites) legitimately have no migrations.
   - **`BACKUP_STORY`** — detect what protects a migration: a backup script (`scripts/*backup*`, `scripts/migrate-with-backup.*`) and/or a CI workflow (`.github/workflows/*backup*`, `*migrat*`, or the equivalent under `.gitlab-ci.yml` / other CI). Record whatever exists, or `none`.
   - **`DEPLOY_PLATFORM`** — the first marker that matches, else `unknown`:
     `vercel.json` / `.vercel/` → `vercel` · `netlify.toml` → `netlify` · `fly.toml` → `fly` · `render.yaml` → `render` · `railway.json`/`railway.toml` → `railway` · an app-deploying workflow under `.github/workflows/` → `github-actions` · a `Dockerfile` with no platform marker → `container` · none → `unknown`. Drives the post-push guidance in step 5; never hardcode a single host.

## Step 1 — Gate (run /kit:deploy-check)

Invoke `/kit:deploy-check` against the resolved target. Forward `--quick` / `--skip-check=` / `--target=`. Do NOT re-implement any check here — single source of truth lives in `deploy-check/SKILL.md`, which does its own stack detection (typecheck/build/migration-drift adapt to the detected toolchain).

Verdict handling:

- **NO-GO** → abort the ship. Print the failing checks verbatim from deploy-check. Tell the user what to fix. Do not offer to "force ship".
- **GO WITH CAVEATS** → surface the warnings, then ask:
  > "deploy-check returned GO WITH CAVEATS (<N> warnings listed above). Continue with ship? Reply `yes` to continue, `cancel` to abort."
- **GO** → continue silently to step 2.

Hard rules:

- If `IS_PROD_PUSH=true` and the user passed `--quick` or `--skip-check=build`, refuse before running deploy-check: `✗ --quick / --skip-check=build are not allowed for prod pushes. Re-run without them.`
- If deploy-check reports a `migration_safety` row (it only does when the project has migrations) and it is anything other than ✓, treat as NO-GO regardless of overall verdict. Migration corruption is why the project carries a migration-safety discipline — this skill enforces it when migrations exist, and is silent about it when they don't.

## Step 2 — Show the exact push payload

Print, verbatim, what's about to be pushed. This is the user's last chance to recognise an unexpected commit (per project memory: `push X` never includes other local commits).

```
About to push <current-branch> → origin/<target-branch>:

  <sha>  <subject>     <author-relative-time>
  <sha>  <subject>     <author-relative-time>
  …

<N> commits, <M> files changed (<+adds>/<-dels>)
(+ 1 shipping-log Brief commit, authored in step 4)
```

Generated from:

- `git log --pretty=format:'  %h  %s   (%ar, %an)' origin/<target>..HEAD`
- `git diff --shortstat origin/<target>...HEAD`

If the list is empty → abort: `nothing to push — local branch is at or behind origin/<target>`.

If any commit author is not the current `git config user.email`, surface a `⚠ <N> commits authored by someone else (<emails>)` line — common in shared branches but worth flagging once.

## Step 3 — Migration backup gate (only when MIGRATIONS_DIR ≠ none)

If `MIGRATIONS_DIR=none` (detected in Preflight step 7), **skip this step entirely** — there's nothing to gate.

Otherwise, if `git diff origin/<target>...HEAD --name-only -- <MIGRATIONS_DIR>` returns any files, surface:

```
⚠ This push includes <N> new migration(s):
  - <MIGRATIONS_DIR>/<file>
  …

Backup story:
  <BACKUP_STORY, described concretely — e.g.:>
  - CI: <detected workflow> runs a DB dump server-side once the migration lands on <target-branch>.
  - Local: if you applied locally, you used <detected backup script> per the repo's migration docs.
  <or, if BACKUP_STORY=none:>
  - ⚠ No automated pre-migration backup detected in this repo. Take a manual dump before this lands,
    or add a backup step to CI. Recovery is impossible without one.
```

Then ask:

> "Migration push detected. Type `understood` to continue, or `cancel` to abort."

This is a deliberate friction point — typing a word is more honest than `y`. Do not accept `yes` here; require `understood`.

## Step 4 — Author the shipping-log Brief

Build the Brief BEFORE pushing so it ships inside the same payload — the record and the code land atomically, and (when synced) file-content-sync publishes both on the same webhook delivery.

If `--dry-run`, build and show the Brief in a fenced block but do NOT write the file and do NOT commit.

### Path & naming

```
doc/shipping-logs/<YYYY-MM-DD>-<target-branch>-<shortsha>.md
```

- `<YYYY-MM-DD>` — ship date (local date).
- `<target-branch>` — slugified target (e.g. `master`, `implementation`).
- `<shortsha>` — short sha of the current HEAD (pre-Brief-commit). Keeps same-day ships to the same target from colliding, and makes the filename greppable back to the commit range.

One ship = one new file. Never append to or rewrite a previous ship's Brief — corrections happen in a new `## Update <ISO-date>` section added to the SAME ship's Brief.

### Brief format

Plain markdown, no HTML, no emojis (project comms convention). Frontmatter carries only the title — the discussion handler manages its own sync fields.

```markdown
---
title: Ship <YYYY-MM-DD> — <branch> → <target>: <one-line summary>
---

**Shipped**: <ISO timestamp>
**Branch**: <branch> → <target>
**Prod push**: yes | no
**Commits**: <N> (<oldest-sha>..<newest-sha>)
**Deploy check**: <verdict>
**Note**: <--note value — omit this line entirely when absent>

## Summary

<One paragraph: what this ship is, why it went out, what a teammate needs to know.>

## What shipped

### <theme scope, e.g. db-providers>
- <outcome bullet — short product-facing sentence, NOT a raw commit subject>
- <outcome bullet>

### <next theme>
- …

## Migrations

<List each migration path + its backup story, or "None.">

## Deploy check

<verdict>
- <warning lines from the deploy-check report, verbatim>

## Commits

- `<shortsha>` <subject>
- `<shortsha>` <subject>
- …
```

Field sources:

- **Theme grouping** — group commits by conventional-commit scope (same default rule as `/kit:standup` step 3). Outcomes are short product-facing sentences. If commits lack scopes and there are ≥20 changed files across ≥5 dirs, escalate to launch-chart module grouping; otherwise bucket into a `misc` theme.
- **Commits / stats** — `git log --pretty=format:'%h%x09%s' origin/<target>..HEAD` + `git diff --shortstat origin/<target>...HEAD`.
- **Migrations** — if `MIGRATIONS_DIR ≠ none`: `git diff --name-only origin/<target>...HEAD -- <MIGRATIONS_DIR>` (exactly what step 3 saw); else `None.`
- **Deploy check** — verdict + warnings from step 1's report.

### Preview + confirm

Show the full Brief in a fenced block, then ask:

> "Write this Brief to `doc/shipping-logs/<filename>` and commit it? Reply `yes` to write + commit, `edit` to tweak (summary, themes, note), `skip` to ship without a log, or `cancel` to abort the ship."

- `yes` / `y` → write the file, then `git add doc/shipping-logs/<filename> && git commit -m "docs(shipping-logs): ship <YYYY-MM-DD> — <target> (<one-line summary>)"`. The Brief commit is now the payload's tip.
- `edit` → ask what to change, redraft, re-show, re-ask.
- `skip` → no file, no commit. The ship proceeds unlogged — say so plainly: `shipping without a log entry; there will be no record of this ship.`
- `cancel` → abort the entire ship. Nothing was written, nothing was pushed.

## Step 5 — Push confirmation

Ask **exactly**:

- If `IS_PROD_PUSH=false`:
  > "Push <N> commits to origin/<target-branch>? Reply `yes` to push, `cancel` to abort."
- If `IS_PROD_PUSH=true`:
  > "Push <N> commits to origin/<target-branch>. This is a deploy-tracked branch — <if DEPLOY_PLATFORM≠unknown: '<platform> will deploy automatically'; else: 'whatever deploy pipeline is wired to this branch will pick it up'>. Reply `ship it` to push, `cancel` to abort."

`<N>` includes the Brief commit from step 4 (when not skipped). Accept exactly `yes` / `ship it` (case-insensitive). Anything else → treat as cancel. **Never** accept silence, `ok`, `sure`, free-form approval. If `--dry-run`, skip this step entirely.

On confirm: `git push origin <current-branch>:<target-branch>`. Stream stdout. If the push fails (non-fast-forward, hook reject, auth) — surface the error verbatim, do not retry, do not `--force`.

If the push fails AND a Brief commit was created in step 4, leave it in place — it's a normal local commit; the user resolves the push problem and re-runs `/kit:ship` (step 2 will show it as part of the payload).

After a successful push:

- The Brief is now live in the repo. If the project is wired to LaunchSecure, file-content-sync picks it up from the webhook delivery and materializes the Comm Hub comment under the "Shipping Logs" category (branch-aware: a ship to a non-default branch shows with a branch pill until it reaches the default branch).
- If `IS_PROD_PUSH=true`, surface deploy guidance **for the detected platform** (`DEPLOY_PLATFORM`), never a hardcoded host:

  | DEPLOY_PLATFORM | Post-push line |
  |---|---|
  | `vercel` | `Vercel's git integration will deploy the latest commit on <target>. Check: vercel ls --prod. Do NOT run vercel deploy from here — it bypasses the git→Vercel flow and double-deploys.` |
  | `netlify` | `Netlify's git integration will build the latest commit on <target>. Check the Netlify dashboard / netlify status. Do NOT run netlify deploy --prod from here.` |
  | `fly` | `If a deploy GitHub Action is wired, it will run flyctl deploy on push. Otherwise a maintainer runs it. Check: fly status.` |
  | `render` / `railway` | `<platform>'s git integration will deploy the latest commit on <target>. Check the <platform> dashboard.` |
  | `github-actions` | `The deploy workflow under .github/workflows/ will run on push to <target>. Check: gh run list --branch <target>.` |
  | `container` / `unknown` | `Push complete. This repo has no detected managed-deploy integration — whatever CI/CD or manual process is wired to <target> takes over from here. This skill does not deploy.` |

  ```
  Pushed.
  <platform-specific line from the table above>

  Shipping log written: doc/shipping-logs/<filename><if synced: → Comm Hub "Shipping Logs">
  ```

## Cross-branch ship (--to=master from a feature branch)

When the user runs `/kit:ship --to=master` from a non-master branch, the flow is **push the current branch's commits onto master**, which is a fast-forward / merge, not a normal push. Handle this carefully:

1. After deploy-check passes, before step 2, verify the current branch is ahead of `origin/master` (`git rev-list --left-right --count origin/master...HEAD` → left=behind, right=ahead). If behind, abort: `✗ <current> is behind origin/master by <N>; rebase or merge master first`.
2. The push command becomes `git push origin <current-branch>:master`. Make sure step 2's "About to push" preamble shows this asymmetric refspec so the user sees it.
3. On non-fast-forward reject from origin, do NOT suggest `--force`. Surface and stop.
4. The Brief's `**Branch**:` line shows the asymmetric flow (`<current> → master`) and its filename uses `master` as the target slug.

## Constraints

- **Never auto-commit and auto-push in one breath.** Always two explicit confirmations (step 4 Brief commit, step 5 push). The `--dry-run` flag exists instead of bundling.
- **Never call a host's deploy CLI** (`vercel deploy`, `netlify deploy`, `flyctl deploy`, etc.). Managed deploys come from the host's git integration on push to a deploy-tracked branch. Calling the CLI double-deploys and bypasses the gate. The one exception is a project whose `DEPLOY_PLATFORM` is genuinely CLI-only with no git integration — and even then, this skill stops at the push and leaves the deploy to the user.
- **Never `git push --force` / `--force-with-lease`.** If a push fails, the user investigates.
- **Never `git push <branch>` without an explicit refspec.** Always `<local-branch>:<remote-branch>` so the destination is unambiguous in the printed command (and in shell history).
- **Never bypass migration_safety when migrations exist.** A ✓ on the deploy-check migration_safety row is the gate; anything else is NO-GO. When the project has no migrations, there is no such row and nothing to bypass.
- **No stack assumptions.** Everything stack-specific (migrations tooling, deploy host, backup story) is resolved by detection in Preflight step 7. If detection comes up empty, degrade to the neutral path — never reference a tool, path, or command the repo doesn't have.
- **Single source of truth for checks.** Do not duplicate `/kit:deploy-check` logic inline; invoke it.
- **One ship, one Brief.** Never edit, delete, or rewrite a previous ship's Brief. Corrections go in an `## Update <ISO-date>` section appended to that ship's own Brief in a follow-up commit.
- **Log only what actually ships.** The Brief is committed before the push, but it only publishes when the push lands — if the user cancels at step 5, tell them the Brief commit is still local and will ride along with the next ship (or can be dropped by them; never drop it yourself).
- **Honest about deploy state.** This skill ends when the push completes. It does NOT wait for a build to finish, does NOT verify the prod URL is up, does NOT roll back. Point the user at the detected platform's status command for deploy-monitoring.
- **Dry-run is truly dry.** `--dry-run` must NOT write any file, must NOT commit, must NOT push. The preview is shown in chat only.
- **Respect doc/ ownership rules.** Only ever write under `doc/shipping-logs/`. Never touch `doc/ideas/` (personal scratch) or other categories from this skill.

## Idempotency

Re-running `/kit:ship` after a successful run:

- Step 2 will show "nothing to push" if the previous push landed cleanly — the natural no-op. No new Brief results.
- Each successful ship is a separate file in `doc/shipping-logs/`. There is no per-day or per-branch coalescing — the Comm Hub renders them as separate entries. If a hotfix ships 10 minutes after a feature, both Briefs exist independently.
- A Brief committed but not pushed (cancelled at step 5) is picked up by the next run: step 2 shows it in the payload, step 4 detects an existing un-pushed Brief for this range and offers to reuse or rewrite it instead of authoring a duplicate.

## Reading the log

The shipping history lives in the repo, and (when synced) in the Comm Hub:

```
# repo side — every ship, newest first
ls -t doc/shipping-logs/

# one ship's full record
cat doc/shipping-logs/<file>.md
```

Comm Hub side (when the project is LaunchSecure-wired): the "Shipping Logs" category header in the LS sidebar lists every published ship Brief with the BookOpen icon. Filter chips (Published / In Development) follow the branch-aware brief tiers.

## Notes for the assistant

- The user has explicit project-memory rules against unauthorised pushes and dumb reverts. Treat every confirmation as load-bearing — don't paraphrase the prompt wording, don't accept ambiguous approval.
- "Ship" is the user's verb for "I want this in production." If the target isn't actually deploy-tracked, the skill should still work but should say so clearly in the scope line (step 5 phrasing changes).
- The shipping-log Brief is the durable artifact and (when synced) the published record at once — there is no separate "publish" step and no local NDJSON file. If you find `.launchsecure/ship-log.ndjson` in a repo, it's from a pre-Brief version of this skill; leave it alone (don't migrate, don't delete) and mention it once.
- **Portability is the contract.** This skill is installed by `launch-kit init` into projects on stacks you can't predict. Anything you hardcode that isn't in *this* repo is a bug for the next repo. When in doubt, detect or degrade — never assume.
- This skill assumes `/kit:deploy-check` exists. If missing (older launch-kit install), surface that as a preflight error pointing to `npx @launchsecure/launch-kit init`.
