setup ยท diff

git:20261012.d086513 to git:20261013.7233f74

4 added, 4 removed. Audit B to B.

---
name: setup
description: Interactive first-time project setup that fills missing env values, starts required services, runs migrations and seeds, and verifies the app starts.
disable-model-invocation: true
argument-hint: "/setup"
allowed-tools: "Read, Write, Edit, Bash, Grep, Glob, AskUserQuestion"
sensitive: true
---
## Overview
First-time project setup that detects the stack, installs dependencies, configures the environment, starts required services, runs database migrations and seeds, and verifies the application starts. Interactive: prompts for missing non-secret environment values in chat, hands secret values to a script the user runs, and confirms before starting services.
## When to use
- When cloning a new project and setting it up for the first time.
- When onboarding to a project and the README setup steps are unclear or incomplete.
- When returning to a project after a long time and the environment may be stale.
- When a teammate asks "how do I get this running?"
Neighbouring work has its own skill:
- For managing production infrastructure, use `/infra`.
- For deploying to staging or production, use `/deploy`.
- For generating a README, use `/readme`.
- For updating dependencies, use `/audit deps`.
## Arguments
This skill takes no arguments. Run `/setup` and it walks through everything interactively.
## Steps
1. **Detect the project type.** Read the root directory for manifest files. Check for these in order:
- `package.json`: Node.js project. Check for `pnpm-lock.yaml`, `yarn.lock`, or `package-lock.json` to determine the package manager.
- `go.mod`: Go project.
- `Cargo.toml`: Rust project.
- `pyproject.toml` or `requirements.txt`: Python project.
- `Makefile`: check for language-specific targets.
- `docker-compose.yml` or `compose.yml`: containerized services.
- If multiple manifest files exist, report all detected project types.
- 2. **Check prerequisites.** For each detected project type, verify the required runtime is installed. If `mise` is available (`which mise`) and the project has a `.mise.toml`, `.tool-versions`, `.node-version`, `.nvmrc`, `.python-version`, `.ruby-version`, or `.terraform-version` file, run `mise install` once to install every pinned runtime, then `mise current` to verify resolution. After that, the per-runtime checks below confirm the right version is active:
+ 2. **Check prerequisites.** For each detected project type, verify the required runtime is installed. If `which mise` finds `mise` and the project has a `.mise.toml`, `.tool-versions`, `.node-version`, `.nvmrc`, `.python-version`, `.ruby-version`, or `.terraform-version` file, run `mise install` once to install every pinned runtime, then `mise current` to verify resolution. After that, the per-runtime checks below confirm the right version is active:
- Node.js: `node --version`. Compare against `.node-version`, `.nvmrc`, `.tool-versions`, `.mise.toml`, or the `engines` field in `package.json`.
- Go: `go version`. Compare against `.tool-versions` or `.mise.toml`.
- Rust: `rustc --version` and `cargo --version`. Compare against `rust-toolchain.toml`.
- Python: `python3 --version`. Compare against `.python-version`, `.tool-versions`, `.mise.toml`, or `pyproject.toml` requires-python.
- Docker: `docker --version` and `docker compose version`.
- If a prerequisite is missing and `mise` cannot resolve it, report it and stop. Do not install runtimes manually.
3. **Install dependencies.**
- Node.js with pnpm: `pnpm install`.
- Node.js with yarn: `yarn install`.
- Node.js with npm: `npm ci`.
- Go: `go mod download`.
- Rust: `cargo build`.
- - Python with pip: `pip install -r requirements.txt` or `pip install -e .`.
+ - Python with `uv.lock` or a `pyproject.toml`: `uv sync`, per [`rules/lang/python.md`](../../rules/lang/python.md). Never a bare `pip install`.
+ - Python with only `requirements.txt`: `uv venv` then `uv pip install -r requirements.txt`.
- Python with poetry: `poetry install`.
- Report the result: success or failure with the error output.
4. **Environment setup.** Check for `.env.example` or `.env.template` in the project root; the commands below name `.env.example`, so substitute the template file when that is the one present. Never read `.env` values into the conversation; work with key names only.
- If `.env` does not exist, create it from the example with owner-only permissions: `(umask 077 && cp .env.example .env)`.
- List the keys that are missing, empty, or still equal to the example value. This prints names, never values:
```bash
awk -F= 'NR == FNR && /^[A-Za-z_][A-Za-z0-9_]*=/ { example[$1] = substr($0, length($1) + 2); next } NR == FNR { next } $1 in example { present[$1] = 1; value = substr($0, length($1) + 2); if (value == "" || value == example[$1]) { print $1 } } END { for (key in example) { if (!(key in present)) { print key } } }' .env.example .env
```
- Classify each listed key. A key is secret-shaped when its name contains `KEY`, `SECRET`, `TOKEN`, `PASSWORD`, `PASS`, `PWD`, `PRIVATE`, `CREDENTIAL`, `AUTH`, `SALT`, or `DSN`, or when it holds a connection string that can carry credentials, such as `DATABASE_URL` or `REDIS_URL`. When unsure, treat the key as secret-shaped.
- For each non-secret key, ask in chat: show the variable name, the description from comments in `.env.example` if available, and the default value if one exists. Accept the default if the user presses enter, and write the answer to `.env`.
- For the secret-shaped keys, generate a wizard script so the values never pass through the conversation. Write it to the project workspace as `setup-env.sh`, resolving the root with [`hooks/_lib/project_workspace.py`](../../hooks/_lib/project_workspace.py) the same way [`/checkpoint`](../checkpoint/SKILL.md) does, or to the scratchpad directory outside a git repository. Fill `KEYS` with the secret-shaped names and leave the rest of the template unchanged:
```bash
#!/usr/bin/env bash
set -euo pipefail
ENV_FILE="${ENV_FILE:-.env}"
MAX_ATTEMPTS=3
KEYS=(DATABASE_URL STRIPE_SECRET_KEY)
has_value() {
[[ -f "$ENV_FILE" ]] && grep -qE "^${1}=.+" "$ENV_FILE"
}
write_env() {
local key="$1" value="$2" tmp sq="'\\''"
[[ -e "$ENV_FILE" ]] || (umask 077 && : >"$ENV_FILE")
tmp=$(umask 077 && mktemp)
grep -vE "^${key}=" "$ENV_FILE" >"$tmp" || true
printf "%s='%s'\n" "$key" "${value//\'/$sq}" >>"$tmp"
cat "$tmp" >"$ENV_FILE"
rm -f "$tmp"
}
ask_secret() {
local key="$1" value attempt rc
for ((attempt = 1; attempt <= MAX_ATTEMPTS; attempt++)); do
value=""
rc=0
if has_value "$key"; then
printf '%s [Enter keeps the saved value]: ' "$key"
else
printf '%s: ' "$key"
fi
IFS= read -rs value || rc=$?
printf '\n'
if [[ -z "$value" ]] && has_value "$key"; then
printf 'Kept %s.\n' "$key"
return 0
fi
if [[ -n "$value" ]]; then
write_env "$key" "$value"
printf 'Saved %s.\n' "$key"
return 0
fi
if ((rc != 0)); then
printf 'Input closed before %s was entered.\n' "$key" >&2
return 1
fi
printf '%s is required.\n' "$key" >&2
done
return 1
}
main() {
local key missing=""
for key in "${KEYS[@]}"; do
ask_secret "$key" || missing="$missing $key"
done
if [[ -n "$missing" ]]; then
printf 'Still missing:%s\n' "$missing" >&2
return 1
fi
printf 'Every value is saved in %s.\n' "$ENV_FILE"
}
main "$@"
```
- - Check the script with `bash -n` and `shellcheck`, then tell the user to run it from the project root with `! bash <path>` at the prompt, or in a separate terminal. Input is hidden with `read -rs`, a new `.env` is created under `umask 077`, a re-run offers each saved value as the Enter default without showing it, a closed input stream ends the loop instead of spinning, and no value is ever echoed.
+ - Check the script with `bash -n` and `shellcheck`, then tell the user to run `bash <path>` from the project root in their own terminal window. Never suggest the `!` prefix: a `!` command gets no terminal input, so `read -rs` hits end of file and every key is reported missing. Input is hidden with `read -rs`, a new `.env` is created under `umask 077`, a re-run offers each saved value as the Enter default without showing it, a closed input stream ends the loop instead of spinning, and no value is ever echoed.
- After the user reports the script finished, rerun the key-name listing above to confirm every key now has a value. The script holds key names only, so it may stay in the workspace for a later re-run.
- Never overwrite values the user has already set in `.env`.
5. **Detect required services.** Scan these sources for service dependencies:
- `docker-compose.yml` or `compose.yml`: list all services defined.
- `.env.example`: look for connection strings or host variables that imply services: `DATABASE_URL`, `REDIS_URL`, `MONGODB_URI`, `ELASTICSEARCH_URL`, `RABBITMQ_URL`, `KAFKA_BROKERS`.
- For each detected service, check if it is already running:
- PostgreSQL: `pg_isready -h localhost` or check the port.
- Redis: `redis-cli ping`.
- MongoDB: `mongosh --eval "db.runCommand({ping:1})"` or check the port.
- Docker services: `docker compose ps`.
6. **Offer to start services.** For each service that is not running:
- If a docker-compose file defines the service, offer to start it: `docker compose up -d <service>`.
- Wait for health checks to pass before proceeding. Use `docker compose ps` to verify the service reports as healthy.
- If the service is not in docker-compose, inform the user that the service needs to be started manually and provide the typical connection details from the env vars.
7. **Run database setup.** Detect the ORM or migration tool:
- Prisma: `npx prisma migrate dev` or `npx prisma db push` depending on the project stage. Then `npx prisma db seed` if a seed script exists.
- Knex: `npx knex migrate:latest` then `npx knex seed:run` if seeds exist.
- TypeORM: `npx typeorm migration:run`.
- Django: `python manage.py migrate` then `python manage.py loaddata` if fixtures exist.
- SQLAlchemy with Alembic: `alembic upgrade head`.
- If no ORM is detected but `DATABASE_URL` exists, inform the user that manual database setup may be needed.
8. **Verify the application starts.** Detect the start command:
- Node.js: check `package.json` scripts for `dev`, `start:dev`, or `start`.
- Go: `go run .` or check a Makefile target.
- Python: check for a `main.py`, `app.py`, or framework-specific entry point.
- Start the application. Wait up to 15 seconds for a health check endpoint or for the process to stabilize.
- If the app starts and responds to a health check, report success.
- If the app crashes, capture the error output and report it with diagnostic suggestions.
- Stop the application after verification. This is a setup check, not a long-running process.
9. **Present the setup report.**
```
## Setup Complete
**Project:** <detected type and framework>
**Runtime:** <version>
**Package manager:** <name and version>
### Dependencies
Installed successfully.
### Environment
- .env created from .env.example
- N variables configured
### Services
| Service | Status |
|---------|--------|
| PostgreSQL | running on localhost:5432 |
| Redis | running on localhost:6379 |
### Database
- Migrations applied: N
- Seed data loaded: yes/no
### Application
Started successfully on http://localhost:3000
### Next steps
- Run `pnpm dev` to start development
- Run `pnpm test` to verify tests pass
```
## Rules
- Never overwrite an existing `.env` file. Merge missing values only.
- Never read, print, or ask in chat for a secret-shaped value. The wizard script collects it in the user's terminal, per [`rules/security.md`](../../rules/security.md).
- Always check if services are already running before attempting to start them. Starting a second instance on the same port causes errors.
- Always verify the application actually starts. "Dependencies installed" is not "setup complete."
- Never install runtimes or system-level tools automatically. Report what is missing and let the user decide how to install.
- When a step fails, report the error clearly and continue with the remaining steps where possible. A failed database migration should not prevent reporting what else was configured.
- Prefer `pnpm` for JavaScript and TypeScript projects. Only use `npm` or `yarn` when the lockfile indicates otherwise.
- When docker-compose starts a service, wait for its health check before running migrations. A started container is not a ready service.
## Common Rationalizations
- "Pasting the API key in chat is faster than running a script": a value in chat is in the transcript and counts as disclosed. The wizard script reads it with `read -rs` in the user's terminal.
- "The key name looks harmless, so ask for it in chat": the classification in step 4 decides, and an unsure call counts as secret-shaped.
- "Dependencies installed, so setup is done": setup is done when the app starts and answers its health check in step 8.
- "The container is up, so run the migrations": `docker compose ps` must report the service healthy first.
- "The runtime is missing, so install it quickly": runtimes come from `mise` or from the user. Report the gap and let the user choose.
## Red Flags
- Stop and switch to the wizard script when the next message would ask for, print, or echo a secret-shaped value.
- Stop and merge only missing keys when the next write would replace a value already set in `.env`.
- Stop and report the missing prerequisite when `mise` cannot resolve a pinned runtime.
- Stop and report the crash output with diagnostic suggestions when the app exits during step 8.
## Verification
- The key-name listing from step 4 prints nothing after the wizard script finishes.
- No secret-shaped value appears anywhere in the conversation.
- Every detected service reports healthy or running before migrations ran.
- The app started, answered a health check or stayed up for the 15-second window, and was stopped afterward.
- The setup report lists every step, with the error output for any step that failed.
## Related skills
- `/onboard` - Understand the codebase structure and architecture.
- `/infra` - Manage infrastructure for local development.
- `/test` - Run the suite, coverage, and lint once setup is complete.
- - `/test` - Run the test suite to verify everything works.