**Secrets are vault-native — no `.env` on disk (#2655).** The org forbids *using* `.env` files, not just
committing them. A repo **without** `.env.example` stages vault-native: `mmi-cli stage --apply` resolves the
declared dev runtime secrets (registry `requiredRuntimeSecrets[dev]`) from the vault and injects them straight
into the compose **process environment**, which Compose interpolates the file with — no secret ever touches
disk. A repo that still ships `.env.example` keeps the legacy `.env` bootstrap for back-compat until its
compose migrates to passthrough; new repos should ship **no** `.env.example`.

**Runtime env reaches the container through the generated passthrough (#6345).** Interpolation is not
delivery: a container receives only what its own `environment:` declares. So `/stage` mirrors the box deploy —
it generates `tmp/stage/runtime-env.override.yml` (gitignored, **key names only**, values stay in the process
env) passing every runtime key through to each service the compose marks `labels: {"mmi.runtime-env": "true"}`,
and loads it beside `docker-compose.yml` for the whole stage. Mark the app service with that label; a repo
that hand-declares its variables needs nothing. Unmarked sidecars (db, cache) keep getting only what they
interpolate themselves. Never wrap an individual `docker compose` call in `mmi-cli vault secrets use` to work
around a missing value (#6344) — that value is scoped to the one child process it wraps, so the next `docker
compose logs`/`down` fails the same `required variable … is missing a value`. Declare the key in registry
`requiredRuntimeSecrets[dev]` and let `/stage` carry it across the whole lifecycle.

**Build-time secrets (#5381).** When META declares `requiredBuildSecrets` (e.g. Katip's
`NODE_AUTH_TOKEN=@github-packages-token` for `@mutmutco/example-ui` on GitHub Packages), `/stage` injects those
BuildKit ids into the **`docker compose build`** process env only — not into the running container. Central
deploy mints the packages sentinel from Actions `GITHUB_TOKEN`; a laptop cannot. Resolution order: existing
process env → stageless project vault secret under the BuildKit id → existing GitHub sign-in
(`GH_TOKEN`, `GITHUB_TOKEN`, or `gh auth token`). Package read access is still required and is enforced
by GitHub Packages during the build. Cloud containers whose GH_TOKEN is a proxy placeholder set MMI_GH_PAT to a fine-grained PAT (resource owner mutmutco, Organization → Members: read); mmi-cli prefers it. If every source is missing, `/stage` fails before the build.
Setup: [`docs/Guides/tenant-runtime.md`](../../docs/Guides/tenant-runtime.md) § Local `/stage`. Never commit
or print the PAT.

## Notes

- `/stage` is local — no AWS, no deploy, no board or version effect. The one cloud exception is
  `--live`: an on-demand **dev** stage of your branch for **`tenant-container`** repos only, without
  a stage IP restriction (above). Preflight `deployModel` before offering it; `registry-publish`
  and other models have no live stage.
- Remote rc/live environments move only through `/rcand`, `/release`, and `/hotfix`.
- Everything is gitignored; `/stage` never produces a tracked change.
- **Playwright MCP output goes to `tmp/`**, never the repo root: if you drive the Playwright MCP server,
  pass `--output-dir tmp/playwright-mcp` (or point its output there). `.playwright-mcp/` is kept gitignored
  by the org `.gitignore` managed block (`mmi-cli doctor`) as a safety net, so a stray default capture is
  never tracked.
- `tools[]` is declarative for now; stage hardening starts/stops the main configured process. Repos with
  extra local services should encode them behind `stage.up` until a tool runner is added.
- **Stale Docker bundle:** the registry-derived Docker Compose default builds with `docker compose build
  --no-cache` before `up`. If the browser still serves an old bundle, run `mmi-cli stage stop --apply`, rerun
  `/stage`, then inspect `tmp/stage/state.json` identity, container image labels, and compose build inputs.
- **Stale `.env`:** when `.env` already exists from a prior `/stage` run, `stage run` does **not** refresh it
  from an updated `.env.example` — it warns on stderr and keeps the stale file. Delete `.env` (or merge in the
  new keys by hand) when `.env.example` changes, then re-run `/stage`.



## Detail moved from SKILL.md (verbatim)

The stage recipe is the **registry-derived default** for plumbing-free product repos: a `tenant-container`
repo that ships `docker-compose.yml` and has a Hub registry `portRange` needs **no committed config**.
`mmi-cli stage` derives a default compose stage — run `docker compose build --no-cache`, run `docker compose
up -d`, choose a free port from the registry range, and report the local URL (e.g. `http://127.0.0.1:3700/`).
The commands are shell-aware and `--apply` runs them cross-shell. Board, deploy, secret, and project registry
facts all live in the Hub registry — no repo-local control-plane file.

The JSON reports `source` (`derived` / `local` / `none`) and, when derived, the local `url`. `source: none`
means neither a usable local recipe nor a derivable default exists — the message names the missing fact
(deployModel, `docker-compose.yml`, or registry `portRange`; `.env.example` is **not** required — #2655).
On a `deployModel: vercel` repo the receipt also carries `preview` — the current branch's latest
Vercel **Preview** deployment (`state`, `url`) read from the GitHub Deployments the Vercel GitHub app
posts — and the text output ends with a `vercel preview (<branch>)` line. Report that URL as the branch
preview alongside the local stage; no `preview.deployment` means the branch was not pushed yet or the
Vercel GitHub app is not connected. There is no box stage on a vercel repo: `--live` refuses.
When the gap is a missing Hub registry `portRange`, the JSON receipt also carries `recovery` with
`mmi-cli stage port-range <owner/repo>` (#5539) — assign the block, then re-run `/stage`. That gap does not
mean the repo's Hub registry/org project setup is missing; it means local stage derivation cannot pick a
collision-safe port until META.portRange exists.

A stage is **per worktree**. The CLI force-stops only the previous stage **in this worktree**, runs
`stage.build`, starts `stage.up`, records `tmp/stage/state.json`, picks a free port from the registry
range (skipping ports reserved by sibling worktrees), and polls `stage.healthUrl` when configured.
Recipes run in Bash, including Git Bash on Windows. Use Bash syntax for build, up and teardown;
legacy cmd `set VAR=value` and PowerShell `$env:VAR` recipes must be converted to Bash or Node scripts.
Windows requires Git Bash or an absolute `SHELL` path to its `bash.exe`; there is no cmd fallback.
Use `--port` to pin a port when needed:

```bash
mmi-cli stage run --apply
mmi-cli stage run --apply --port 5180
```

A local stage is bound to the worktree that started it. Concurrent worktrees on one machine each keep
their own stage on separate ports — you do not need to stop one before starting another. For `jervaise`, continuity
keys on branch; use a distinct North Star slug per parallel grind or feature. Stage JSON/state records
the starting `cwd` plus git branch/commit when available.

Stop the stage when the work is done, before switching context, or before replacing it with another stage.
If the user clearly wants the preview to stay up, leave it running and report that. The next `/stage` also
stops the previous recorded stage before starting, so a stale server does not linger between runs. For the
registry-derived Docker Compose default, stop also runs the recorded compose teardown (`docker compose down`)
from the original stage working directory. Stop re-fetches the declared dev runtime secrets so that `down`
can interpolate the same file (`${POSTGRES_PASSWORD:?}`, `${PORT:?}`) — secrets never come from the state
file (#6351). A vault-native repo with no `.env` gets `PORT` defaulted to the bound stage port.
