Docs
Know whether it is safe to ship right now
RepoOps reads one repository's checkout, rolls the security scan, the config scan, the working-tree git state and a build-readiness look into one verdict, then prints the deploy and custom-domain steps for the stack it detected. It executes nothing and changes nothing.
For: the engineer about to deploy a repository, and the owner who wants the same verdict in front of every push and every pull request
What it does, and why it helps
The preflight is four checks over the repository in front of you. Security reuses the guardrails scan (a committed secret, a tracked .env, an all-interfaces bind, open CORS). Config reuses the env doctor (a variable the code reads that nothing declares, a variable missing from .env.example, a hard-coded localhost URL). Git state is git status and the ahead and behind count against the tracked branch. Build readiness looks for a build or start script, a committed lockfile and an installed node_modules, and runs none of them. Each check lands as Pass, Heads-up or Blocked with a detail line and, when it is not a pass, a How to fix block. The worst status is the verdict: Ready to ship, Caution or Blocked.
Three things block. A critical or high guardrails finding. A high env-doctor finding. An uncommitted change to a protected path (.env files, payments, billing, auth, phi, migrations, the repository brain). Everything else is a heads-up: a dirty tree, a branch behind its base, a missing lockfile, a scan that could not run. Below the checklist the tab names the stack it detected from package.json and the root config files, then prints the matching deploy runbook (Vercel for the static-host class, Fly.io or Render for a long-running Node server, a generic path when nothing is recognised) and a custom-domain runbook for the same host. The commands are printed for you to run; RepoOps never runs them.
The same function is a command line, bin/ship-check.mjs, which exits 1 on a blocked verdict, and a reusable GitHub workflow that runs it as a ship-check status on every pull request. Whether that status is required is a branch-protection setting only a repository admin can flip.
The pain. The first time anyone reads the tree for a deploy is when the host's build fails, or when it succeeds and a variable is undefined in production. The checks that would have caught it exist, on three different tabs, and nobody opens all three at once.
The point of view. Read the checkout before the host does. A verdict over file names, variable names and git state is cheap enough to run before every push, so it should be one call with one answer, and the answer should carry its fix.
What gets easier. Deciding to deploy. One badge, four checks, each miss with its fix text, then the deploy and domain steps for your stack on the same page.
When it helps. Before a deploy, before a push, and on every pull request once the reusable workflow is in the repository. It fits a single repository with a package.json at the root and a fetched remote; a static site with only an index.html gets the static-host runbook.
Its limits. It grades names and git state, not behaviour: it does not run your tests or your build, and a ready verdict means none of these four checks fired, not that the host is configured. It does not know what is set on Vercel or Fly. The stack list is short by design, and an unrecognised stack gets the generic runbook. The catches panel at the bottom records what the Review path caught; it does not stop a merge, and the only module that fails a pull request for a recurring mistake is the separate review-check.
Understand it in 30 seconds
Read the narration
- 0:00 You are about to deploy.
- 0:01 Nobody has read the tree yet.
- 0:06 RepoOps reads the checkout before the host.
- 0:09 Four checks, one verdict, and nothing is executed.
- 0:13 A variable the code reads and nothing declares.
- 0:16 The verdict is blocked, and the card names the exact fix.
- 0:23 Declare it, re-run, ready to ship.
- 0:26 The same check can gate a push.
Synthetic example. Read the guide
Where to find it
Where to find it
- Desktop:
localhost:4000, then Remediation in the sidebar, then Ship Assistant under All tools, in the Approvals and workflows group. - Hosted: desktop only.
- Keyboard: ⌘ K, then type “Ship Assistant”.
When to use it
A first deploy of a Next.js app
Situation. The app runs locally. The tree is committed and pushed, the guardrails and env-doctor scans are clean, and package-lock.json is tracked. Nobody has deployed it yet and nobody knows which host to pick.
What you do. Open Remediation, then Ship Assistant under All tools, in the Approvals and workflows group. Press Re-run preflight so the verdict is fresh, read the four checks, then follow the Deploy runbook from the top.
What you see. The badge reads Ready to ship and the headline reads You're clear to ship. Four checks read Pass: No security footguns exposed, Config is prod-ready, Working tree clean vs origin/main, Build/run looks ready. Detected stack reads Next.js. Deploy runbook shows Vercel with five numbered steps (Install the Vercel CLI, Log in, Deploy a preview, Set your env vars, Ship to production) and Custom-domain runbook shows the five domain steps.
What it establishes. You know which host class the repository fits and what to type, in order. Ready to ship means these four checks passed on this checkout at that time; it says nothing about the host's environment variables until you set them in step four.
A push stopped by a variable nothing declares
Situation. The pre-push hook is installed for the repository. A new route reads process.env.STRIPE_WEBHOOK_SECRET and the variable is in no .env file. The developer pushes.
What you do. Read the hook's output: the verdict line, the blocking check with its detail and the fix. Declare the variable (add it to .env.example and set it on the host), then push again. For one urgent push with a known false positive, REPOOPS_SHIP_CHECK_OFF=1 skips the check and prints that it was skipped.
What you see. The push is rejected with the ship-check verdict BLOCKED, the stack, and the blocking check counting one prod-breaking config gap with its fix text. On the tab the same check reads Blocked and the badge reads Blocked with the headline Not safe to ship yet.
What it establishes. The gap is declared before the host ever builds the branch. The hook only reads the working tree; it does not know whether the host has the value, so the deploy runbook's env step still applies.
Before you start
- Supported versions
- RepoOps desktop v0.3.1, the release this guide was read against. The command line and the reusable workflow ship with the same package; the workflow defaults to Node 20 on the runner.
- Where it runs
- Local: Remediation, then Ship Assistant under All tools, in the Approvals and workflows group (the palette verb Open Ship Assistant opens it too), the CLI, and the pre-push hook. Hosted: /team/ship-assistant, under All tools on Remediation, is a desktop-only stub that explains why and links the download; no verdict is published to the team.
- Permissions
- None beyond reading the checkout. The tab resolves the repository from the iframe it is opened in; the CLI runs as you in the current directory or the path you pass. Marking ship-check as a required status is a GitHub branch-protection change and needs a repository admin.
- Connections
- git on the machine and a fetched remote: the behind and ahead counts compare HEAD with <remote>/<branch> from repos.config.json (defaults origin and main). No API key, no token, no network call from the preflight itself.
- Plan
- No plan gate; the pricing capability map has no Ship Assistant row. The reusable workflow runs on your own GitHub Actions minutes.
Configure it
- Open the tab and read the verdict.
Ship Assistant is under Moved here on Remediation, and the palette verb Open Ship Assistant opens it. The first paint is the last verdict from the tab cache, keyed per repository; press Re-run preflight for a fresh scan. Below the badge, the Preflight checklist lists the four checks with Pass, Heads-up or Blocked and a How to fix block on every miss.
- Point it at the right base branch.
The behind and ahead counts compare HEAD with <remote>/<branch>. Both come from the repository's entry in repos.config.json: remote defaults to origin and branch to main. A repository that ships from another branch needs branch set, or every check will read behind.
- Run it from the command line.
node bin/ship-check.mjs runs the same preflight against the current directory (or --repo <path>). Ready exits 0 with one line; caution exits 0 and prints the warnings; blocked exits 1 and prints every blocking check with its fix. --strict makes caution exit 1 too, --json prints the raw result, --quiet prints only on failure. Exit 2 means the scan could not run at all.
- Put it in front of a push.
The desktop installer carries an opt-in pre-push hook that runs the packaged ship-check. It refuses to overwrite a hook it did not write, and it lets the push through with a printed notice when node is not on PATH or the check file is missing. A hook that says Push allowed is telling you it did not run.
- Make it a status on every pull request.
Copy docs/templates/repoops-merge-gate.yml into the repository as .github/workflows/repoops-merge-gate.yml. It calls the reusable merge-gate workflow, which runs two jobs, ship-check and review-check. Open one pull request so GitHub registers both contexts, then add them as required checks in branch protection. Until that toggle is on, both run and report but do not block the merge button.
- Read the catches panel for what it is.
Accountability catches lists prevention events the Review path recorded when a diff would have reintroduced a defect an active lesson guards, one row per lesson with a count and the last date. The panel says it in its own words: it records the catch, it does not stop the merge. Empty reads No pre-merge catches recorded yet, which is the honest state.
| Setting | Where | A sensible choice | Why it matters |
|---|---|---|---|
fresh=1 | GET /api/ship-assistant, the Re-run preflight button | Press it before you decide | Without it the route returns the per-repository cached verdict and reuses the Security and Config tab scans; with it the scan re-reads the tree and bypasses both caches. |
branch and remote | the repository's entry in repos.config.json | the branch you deploy from (default main on origin) | The ahead and behind counts, and the Working tree clean vs line, compare against this ref. |
--strict | bin/ship-check.mjs, and the strict input of the reusable workflow (default false) | off until the heads-ups are down to ones you accept | Strict makes a caution verdict exit 1, so a dirty tree or a missing lockfile stops the push or fails the status. |
--repo <path> | bin/ship-check.mjs | unset when you run it from the repository root | Overrides the repository root; the default is the current directory. |
--json and --quiet | bin/ship-check.mjs | --json in CI when you scrape the result, --quiet in a hook | --json prints the whole result object; --quiet prints only on failure. |
REPOOPS_SHIP_CHECK_OFF | the shell environment of the push or the CI job | unset; 1 for one push with a known false positive | Exactly 1 skips the check and prints that it was skipped. It is an escape hatch, not a setting to leave on. |
REPOOPS_SHIP_CHECK | the shell environment, read by the installed pre-push hook | unset | Points the hook at another copy of ship-check.mjs; the installer bakes the packaged path in at install time. |
node-version | the reusable workflow's input | 20 (the default) | The Node the runner installs before it runs ship-check and review-check. |
What you should see
The clean case
Configuration. A committed, pushed tree on the tracked branch, clean guardrails and env-doctor scans, a build or start script, a tracked lockfile, node_modules present, a recognised stack.
Expect. Badge Ready to ship, headline You're clear to ship, and the sub-line that every preflight check passed. Four Pass rows. Detected stack names the framework and why. Deploy runbook and Custom-domain runbook follow.
Verify. node bin/ship-check.mjs exits 0 and prints READY with the stack label and 0 warning(s). The tab's badge reads Ready to ship, with the stack's deploy runbook below it.
Heads-ups only
Configuration. The same tree with uncommitted files, or a branch behind its base, or no lockfile, or a scan that did not run.
Expect. Badge Caution. The rows that fired read Heads-up with a How to fix block; the rest read Pass. A scan that could not run reads Security scan unavailable or Config scan unavailable, and a tree without git reads Git state unavailable.
Verify. ship-check exits 0 and prints CAUTION with the warning count; with --strict it exits 1 and lists the warnings under strict mode. The Today line reads Preflight: caution with the count of heads-ups.
Blocked
Configuration. A critical or high guardrails finding, a high env-doctor finding, or an uncommitted change under .env*, payments, billing, auth, phi, migrations or .claude/brain.
Expect. Badge Blocked, headline Not safe to ship yet, the sub-line counting blockers and heads-ups. Each blocked row carries its fix: the Security guardrails tab for an exposure, the Config & env doctor tab for a config gap, the File integrity tab for a protected path.
Verify. ship-check exits 1, prints BLOCKED and push blocked, then each blocking check with its fix, then the REPOOPS_SHIP_CHECK_OFF override line. The pull-request status ship-check fails. The Today line reads Preflight: BLOCKED.
The repository could not be read
Configuration. A repository path that is missing or not a git work tree.
Expect. The tab shows the banner Couldn't run preflight with the reason (not a git repository (sync the repo first), or repo path not found). No checklist, no runbook.
Verify. ship-check exits 2 with the same reason on stderr. The Today panel falls back to the plain git lines or Git state unavailable for this repo.
Data and cost
- What is captured
- The preflight writes nothing. It reads git status and the ahead and behind count, package.json, the root config files, the lockfile and node_modules presence, and the guardrails and env-doctor results. The catches panel reads the repository brain's lessons.jsonl and prevention-events.jsonl, at most 500 rows of each. The result sits in the server's per-repository memory cache (64 entries, no expiry, replaced by a fresh run) and in the browser's tab cache under the key ship-assistant:<repo>.
- Who can see it
- Local only. No publisher reads the ship cache, the hosted /team/ship-assistant page is a stub, and the CLI prints to your terminal or your CI log. What a CI log shows is whatever your workflow prints: the check titles and fix text, plus the whole result under --json.
- How long it is kept
- Nothing is stored by this feature. The memory cache lives until the server restarts or a fresh run replaces it; the browser cache lives in the tab's IndexedDB until the next run. The prevention ledger the catches panel reads is the Review path's file, and no control here shortens it.
- What leaves the machine
- None from the preflight. A guardrails evidence snippet is masked by the scanner's redact before it reaches the response, so a secret-shaped match shows a hint, not the body. The runbook commands are text for you to run.
- What it costs
- No model call and no metered spend; the work is git and file reads. The first-run intake caps the same call at 8 seconds and shows not detected past it, which is the only budget the code holds for it.
When the result differs
| Symptom | Likely cause | Next action |
|---|---|---|
| The verdict does not match what I committed a minute ago. | The tab painted its cached verdict, and the route served the per-repository cache. | Press Re-run preflight. The button forces fresh=1, which bypasses the ship cache and the Security and Config tab caches. |
| Banner: Couldn't run preflight: not a git repository (sync the repo first). | The tracked repository's path is not a git work tree, or the mirror has not synced. | Sync the repository, then re-run. ship-check exits 2 for the same reason. |
| A row reads Security scan unavailable or Config scan unavailable. | The guardrails or env-doctor scan threw, so its check is unknown and counted as a heads-up. | Open the Security guardrails or Config & env doctor tab, run the scan there, then re-run preflight. |
| Every run reads commits behind origin/main and the repository ships from another branch. | The base ref is <remote>/<branch> from repos.config.json, defaults origin and main. | Set branch (and remote if needed) on the repository's entry, restart, re-run. |
| Blocked for a protected-path change I meant to make. | An uncommitted file matches .env*, payments, billing, auth, phi, migrations or .claude/brain. | Review it on the File integrity tab and commit it deliberately, or revert it. The block is on the uncommitted state, not the path itself. |
| The hook prints Push allowed and the push goes through unchecked. | node is not on PATH for the hook's shell, or the packaged ship-check.mjs is not at the baked path. | Put node on PATH for git's shell, or reinstall RepoOps so the hook's path resolves; REPOOPS_SHIP_CHECK can point it at another copy. |
| ship-check and review-check show on the pull request but a red one still merges. | Neither is a required status yet. | Add both as required checks in branch protection after they have reported at least once; the runbook docs/runbooks/merge-gate.md has the gh one-liner. |
| Accountability catches reads No pre-merge catches recorded yet. | The Review path has recorded no prevention event for this repository. | Nothing to fix. The panel is honest-empty; it fills only when a diff review matched an active lesson. |
| The hosted dashboard shows only a stub for Ship Assistant. | By design: the checks read a local checkout, and no verdict is published. | Use the desktop app, the CLI, or the pull-request status. |
- Disable
- Nothing runs unless the tab, GET /api/today, the CLI or the workflow asks. Remove the pre-push hook to stop the push gate; remove the caller workflow and un-require the two checks to stop the pull-request gate.
- Roll back
- Not applicable. The preflight changes nothing in the repository or on any host, so there is nothing to roll back; the runbook steps you ran by hand are yours to reverse with the host's own tools.
- Revoke access
- No credential is created or used. The reusable workflow runs with contents: read on your own Actions runner.
- Delete
- Not provided for the catches ledger. The panel reads the repository brain's prevention-events.jsonl and no route here deletes from it. The verdict itself is not stored; a restart clears the server cache and the browser cache is replaced by the next run.
Related tasks
Maintenance evidence
- Feature id
ship-assistant(spine leafship-assistant)- Owner
- Vibe-coder mission control program (the Ship Assistant row in features.md) and the merge gate, T5 (docs/runbooks/merge-gate.md). Guide: LDG-0717.
- Supported product version
- RepoOps v0.3.1
- Last verified
- 2026-09-15, read against origin/main at 52366bb6d; labels read from the served tab source public/ship-assistant.html, the Today panel strings from lib/today.mjs, the CLI output from bin/ship-check.mjs, and the hook template from installer/lib/git-hooks.mjs. Not run on a live instance in this slice.
- Example fixtures
- No fixture file; the inputs are inline in lib/ship-assistant.test.mjs (detectStack, runbookFor, buildPreflight for ready, caution, blocked and degrade, shipSummary, buildAccountabilityGate), lib/today.test.mjs (the ship-readiness panel id and the ship blocker ranking), lib/workflow-runs.test.mjs (the Ship preflight seed) and installer/lib/git-hooks.test.mjs (the hook install and refusal).
- Source references
lib/ship-assistant.mjs,lib/routes/ship-assistant.mjs,lib/today.mjs,lib/lessons.mjs,lib/mirror.mjs,bin/ship-check.mjs,installer/lib/git-hooks.mjs,.github/workflows/merge-gate.yml,docs/runbooks/merge-gate.md,public/ship-assistant.html,website/app/team/(home)/ship-assistant/page.tsx- Documentation review
- Independent review requested on the slice pull request; not yet recorded.
- Video review
- Narrated story rendered and published 2026-09-26 (render 554e8fd22323, LDG-1014) with the breadcrumb Remediation, which lists the feature under Moved here, checked against main at 7aab4cd82 with LDG-1014 part 1. Six frames, the captions and the transcript were reviewed by the authoring agent, not an independent reviewer; the audio was not listened to by a person. Narration is the provisional Windows voice until LDG-0721.
Last updated