---
description: Production-readiness check for the current branch — branch freshness, typecheck, lint, build, schema/migration drift, migration safety, env coverage, and recent LS comms. Stack-agnostic: detects the project's toolchain (Node/Python/Go/Rust/Ruby/JVM/.NET/PHP) and migrations tooling at runtime, runs the detected command per check, and skips with a reason where a stack has no equivalent. Read-only; reports a go/no-go punch list. Does NOT deploy.
---

# /kit:deploy-check

Audit the current branch for production readiness. Bundles every signal a human would manually check before clicking "deploy" or merging to a deploy-tracked branch (default: `master`/`main`). Reports a punch list with ✓ / ⚠ / ✗ per check and a single go/no-go verdict.

This skill ships with `launch-kit init` into **any** project, so it makes **no stack assumptions**. Preflight step 5 detects the toolchain and the migrations tooling once; every check below runs the *detected* command, and any check with no equivalent on this stack is **skipped with a visible reason** rather than failing or running a tool the repo doesn't have.

Parse `$ARGUMENTS`:
- (empty) — full check on the current branch vs the default branch.
- **--target=&lt;branch&gt;** — compare against a different deploy-tracked branch.
- **--skip=&lt;check,…&gt;** — comma-separated checks to skip (e.g. `--skip=lint,build`). Use sparingly; surface skipped checks in the report.
- **--quick** — skip the build (the slowest check). Still runs the rest.
- **--json** — emit a machine-readable report (per check: ok, detail, durationMs).

Examples:
- `/kit:deploy-check`
- `/kit:deploy-check --quick`
- `/kit:deploy-check --skip=lint`
- `/kit:deploy-check --target=staging --json`

## Preflight

1. Confirm cwd is a git repo (`git rev-parse --is-inside-work-tree`).
2. Resolve the target branch — `--target=` if given, else default branch (`git symbolic-ref refs/remotes/origin/HEAD --short` → strip `origin/`; fall back to `master`/`main`).
3. Print the scope upfront: `"deploy-check: <current-branch> → <target-branch>"`.
4. Stash check — if `git status --porcelain` shows uncommitted changes, surface a `⚠ uncommitted changes in working tree` row at the top. Don't refuse; some workflows want uncommitted work included.
5. **Detect the project stack** (this is what makes the skill portable — resolve once, reuse below). Identify the project family from its manifest, then resolve the per-check command. **Always prefer an explicit script/target the repo defines** (a `package.json` script, a `Makefile` target like `build`/`lint`/`test`, a `justfile` recipe) over the language default — a defined script encodes the project's real intent.

   | Family | Marker | typecheck | lint | build | env-ref grep |
   |---|---|---|---|---|---|
   | Node | `package.json` (PM from lockfile: pnpm/yarn/bun/npm; `RUN=<pm> run`) | `<RUN> typecheck` else `npx tsc --noEmit` **only if `tsconfig.json`**; else skip | `<RUN> lint` if defined | `<RUN> build` if defined | `process\.env\.[A-Z_]+` in `*.ts,*.tsx,*.js,*.mjs,*.cjs` |
   | Python | `pyproject.toml`/`requirements.txt`/`setup.py` | `mypy`/`pyright` if configured, else skip | `ruff check`/`flake8` if configured | build script if defined (often none) | `os\.environ\[`, `os\.getenv\(`, `getenv\(` in `*.py` |
   | Go | `go.mod` | `go vet ./...` | `golangci-lint run` if present | `go build ./...` | `os\.Getenv\(` in `*.go` |
   | Rust | `Cargo.toml` | `cargo check` | `cargo clippy` | `cargo build --release` | `env::var\(`, `std::env::var` in `*.rs` |
   | Ruby | `Gemfile` | `srb tc` if sorbet, else skip | `rubocop` if present | build script if defined | `ENV\[` in `*.rb` |
   | JVM | `pom.xml`/`build.gradle(.kts)` | (compile = build) | checkstyle/ktlint if configured | `mvn -q package` / `gradle build` | `System\.getenv\(` in `*.java,*.kt` |
   | .NET | `*.csproj`/`*.sln` | (compile = build) | `dotnet format --verify-no-changes` if present | `dotnet build -c Release` | `Environment\.GetEnvironmentVariable\(` in `*.cs` |
   | PHP | `composer.json` | `phpstan`/`psalm` if present | `php-cs-fixer`/`phpcs` if present | build script if defined | `getenv\(`, `\$_ENV\[` in `*.php` |
   | unknown | — | skip | skip | skip (look for a `Makefile`/`justfile` `build` target first) | skip env_coverage |

   Also resolve, reusing the same detection `/kit:ship` uses:
   - **`MIGRATIONS_DIR`** — `prisma/migrations/` · `supabase/migrations/` · `drizzle/` (or a dir with `drizzle/meta/`) · `db/migrate/` (Rails) · `alembic/versions/` · a Django app's `*/migrations/` · `src/main/resources/db/migration/` (Flyway) · a knex/TypeORM/Sequelize dir, or **`none`**. A config-declared `migrationsPath` wins.
   - **`MIGRATION_TOOL`** — prisma / supabase / drizzle / rails / alembic / django / flyway / knex / typeorm / sequelize / `none`. Drives whether a *drift* command exists (check 5).
   - **`ENV_EXAMPLE`** — `.env.testing` if it exists, else `.env.example`, else `none`.

   Record the resolved command set and print a one-line `stack: <family> · pm=<pm> · migrations=<tool|none>` so the user can see what was detected.

## Checks

Run independent checks (typecheck/lint/build) in PARALLEL. Each returns `{ name, status: "ok"|"warn"|"fail"|"skip", detail: <one-line>, artifact?: <path|url> }`. **A check with no detected command is `skip` with a reason — never `fail`.**

### 1. branch_freshness

```
git fetch --quiet origin
git rev-list --left-right --count <target>...HEAD
```
- ✓ `0 commits behind target` → fresh
- ⚠ `1–10 behind`
- ✗ `>10 behind` → suggest `git merge origin/<target>` or rebase

### 2. typecheck

Run `TYPECHECK_CMD` from Preflight. If none for this stack, `⚠ typecheck skipped — no typecheck configured for <family>`.
- ✓ `0 errors`
- ✗ `<N> errors` (sample first 3 `file:line:message`)

### 3. lint

Run `LINT_CMD`. If none defined, `⚠ lint skipped — no lint script/linter detected`.
- ✓ `0 errors` · ⚠ `<N> warnings` · ✗ `<N> errors`

### 4. build

Run `BUILD_CMD` (the project's real build, so the check matches CI). Skip if `--quick` or `--skip=build`. If no build command exists for this stack (common for libraries / scripting projects), `⚠ build skipped — no build step detected`.
- ✓ `built in <durationMs>ms` · ✗ `build failed` (last 20 lines of stderr as artifact)

**DB-safety guard (only when the build touches a DB).** If the detected `BUILD_CMD` runs migrations as part of the build (e.g. a Node build script that chains `prisma migrate deploy`, or any build that applies `MIGRATION_TOOL` migrations — detect by scanning the build script / target), then a prod DB connection string must NOT be used. If the relevant connection env (`DATABASE_URL`, `DB_URL`, etc.) looks production-y (matches `neon.tech`, `rds.amazonaws.com`, `supabase.co`, a `vercel.app` proxy host, etc.), refuse the build step: `⚠ build skipped — build applies migrations and DATABASE_URL looks production-y (<host>); re-run with a dev DB or pass --skip=build`. If the build does NOT touch a DB (most stacks), skip this guard.

### 5. schema_drift (only when MIGRATION_TOOL supports it)

Schema-vs-migrations consistency, using the tool's own drift command:
- **prisma** — `npx prisma migrate diff --from-schema-datasource prisma/schema.prisma --to-migrations prisma/migrations --shadow-database-url <DEV_SHADOW_URL>`. The diff should be EMPTY (this project hand-authors migrations — see CLAUDE.md). Non-empty = a model change without a migration.
- **drizzle** — `npx drizzle-kit check` (reports schema/migration mismatch).
- **rails** — `bin/rails db:abort_if_pending_migrations` against a dev DB (pending = drift).
- **alembic/django** — `alembic check` / `python manage.py makemigrations --check --dry-run` (a would-be-generated migration = drift).
- **other / none** — `⚠ schema_drift skipped — no drift command for <tool>` (or skip silently when `MIGRATION_TOOL=none`).

- ✓ `schema matches migrations` · ✗ `<N> drift detected` (surface the diff as an artifact; suggest authoring a migration).
- If a drift command needs a shadow/dev DB and none is available: `⚠ schema_drift skipped — no shadow/dev DB URL`.

### 6. migration_safety (only when MIGRATIONS_DIR ≠ none)

If `MIGRATIONS_DIR=none`, skip entirely. Otherwise scan every migration added since divergence:

```
git diff --name-only <target>...HEAD -- <MIGRATIONS_DIR>
```

For **SQL-based** migrations (prisma/supabase/flyway/rails-SQL/alembic-SQL), scan the SQL for risk patterns (per the project's migration-safety discipline):
- ✓ no destructive ops (`DROP COLUMN`, `DROP TABLE`, `ALTER COLUMN ... DROP`, `DELETE FROM` without a backup-matching `WHERE`).
- ⚠ destructive ops present BUT a sidecar `CREATE TABLE _backup_*` AND an orphan-check (`RAISE EXCEPTION ... orphans remain`) are present in the same file.
- ✗ destructive ops without backup OR without orphan-check OR expand+contract mixed in one migration.

For **non-SQL** migration DSLs (Rails Ruby DSL, Django/Alembic Python ops, Drizzle TS), best-effort scan for the tool's destructive ops (`remove_column`, `drop_table`, `op.drop_column`, `.dropColumn`, etc.) and note `partial coverage — non-SQL migration, pattern scan only`.

Each failure cites the exact migration filename + line numbers.

### 7. env_coverage (only when ENV_EXAMPLE ≠ none and an env-ref pattern exists)

Compare documented keys in `ENV_EXAMPLE` against env references in new code since divergence, using the stack's `env-ref grep`:

```
git diff <target>...HEAD -- <stack source globs> | grep -oE '<ENV_REF_PATTERN>'
```
- ✓ every referenced env var is documented in `ENV_EXAMPLE`
- ⚠ `<N> new env vars used but missing from <ENV_EXAMPLE>` (list them)
- env docs are advisory — this is never a hard ✗. If `ENV_EXAMPLE=none`, `⚠ env_coverage skipped — no env example file`.

### 8. recent_comms (LS Comm Hub)

Pull the last 10 comments tagged `release` or `daily_update` via `mcp__launch-secure__communication_read`; surface `"<N> release/standup comments in last 7 days — latest: <title> (<author>, <age>)"`. Informational. Skip with `⚠ launch-secure MCP not wired` if unavailable.

### 9. recall_health

`mcp__launch-recall__recall_status` — confirms the shadow-git backup is alive so a rollback path exists. Informational.
- ✓ `recall alive — last snap <X ago>` · ⚠ `recall dead — restart before deploy`. Skip if the MCP isn't wired.

## Report

Render a punch list (plain text, monospace; not a markdown table):

```
deploy-check: implementation → master
stack: node · pm=npm · migrations=prisma

  ✓ branch_freshness       0 commits behind master
  ✓ typecheck              0 errors (3.2s)
  ⚠ lint                   12 warnings (no errors)
  ✓ build                  built in 47s
  ✓ schema_drift           schema matches migrations
  ✗ migration_safety       1 destructive op without backup
                           → prisma/migrations/2026_05_25_drop_legacy/migration.sql:14 (DROP COLUMN, no _backup_* sidecar)
  ⚠ env_coverage           2 new env vars missing from .env.example: FEED_BACKEND_URL, FEED_TIMEOUT_MS
  ✓ recent_comms           1 release comment in last 7 days — "channels rollout" (prajyot, 2d ago)
  ✓ recall_health          alive — last snap 4m ago

verdict: NO-GO — 1 hard fail (migration_safety).
next: author a backup table + orphan check in the listed migration, then re-run /kit:deploy-check.
```

Verdict rules:
- Any ✗ → NO-GO with the failing check(s) listed.
- All ✓ + ⚠/skip only → GO WITH CAVEATS, list the warnings + skips.
- All ✓ → GO.
- **Skips never fail the gate** — but if every code-quality check (typecheck+lint+build) skipped, add a `⚠ note: no typecheck/lint/build ran for this stack — gate is weak` so a green verdict isn't mistaken for thorough.

## Constraints

- **Read-only.** Never deploy, never push, never auto-fix.
- **No stack assumptions.** Every tool-specific command is resolved by detection in Preflight step 5. If detection comes up empty for a check, skip it with a reason — never run a tool the repo doesn't have, never hard-fail for its absence.
- **Parallel checks.** Run independent checks concurrently — sequential bloats the wait past usefulness.
- **No silent skips.** Every skipped check appears in the report with a reason.
- **DB-safe.** The build DB-safety guard only triggers when the detected build actually applies migrations; when it does, refuse a production-looking connection string.
- **Honest about scope.** This check is what a human would do manually; it is NOT a guarantee. Surface: `"This check doesn't run e2e tests, doesn't validate runtime config, doesn't sniff for secrets in commits — those are separate skills."`
- **launch-chart is optional context only** (e.g. blast radius of a migration), never part of the gate. The gate is git + the detected toolchain + the migrations tool + MCP-LS.
