# Codev.md - Development Environment Setup
<!-- Complete setup guide: prerequisites, installation, configuration, troubleshooting -->
<!-- Get a new developer from zero to running the application in under 30 minutes -->
<!-- Last updated: 2026-07-27 -->

## How to Use This Document

Three rules keep a setup document useful instead of decorative:

1. **The 30-minute bar is the success criterion.** Hand this file to someone who has never seen the repository and time them. If they are not running the application in 30 minutes, the document has a bug - fix the document, not the person.
2. **"Ask the tech lead for the API key" is a bottleneck, not documentation.** Every credential below names where it lives and who grants it. If a step still requires a conversation, that step is unfinished.
3. **Pin versions precisely, but never in prose.** A prerequisite of "Node.js" is meaningless and a major-version range is barely better - an exact version, pinned in a file that both humans and CI read, is a prerequisite. Pin it in `.nvmrc` and `package.json` `engines`, then have this document teach the reader how to *read* the pin. A version number typed into a paragraph is stale the first time you upgrade; a command that reads the pin never is.

## Prerequisites

### System Requirements
- **OS**: macOS 13+ (Ventura), Ubuntu 22.04+, or Windows 11 with WSL2
- **RAM**: 16GB minimum (services + IDE + browser consume ~10GB during development)
- **Disk**: 20GB free space (node_modules, Docker images, database data)
- **Network**: VPN access required for staging/production databases (request from IT)

### Required Software

Install these in order. Each step assumes the previous one is complete.

#### 1. Node.js (via a version manager)

The exact version this project requires is pinned in `.nvmrc`, and the acceptable range is enforced by `engines.node` in `package.json`. Do not install a version named in this paragraph - install the one the repository asks for.

```bash
# Install a Node version manager (nvm, fnm, asdf, or volta - pick one and stay on it).
# Follow the installer's own current instructions; do not copy a pinned installer URL
# into this file, because it will rot.

# From the repository root, let the repo choose the version:
nvm install          # reads .nvmrc
nvm use              # reads .nvmrc
nvm alias default "$(cat .nvmrc)"

# Verify against the pin rather than against a number written here:
cat .nvmrc                                          # the version this repo requires
node --version                                      # must match .nvmrc
node -p "require('./package.json').engines.node"    # the range CI enforces
```

`.nvmrc` should track a Node release line that is still in active LTS support - check the Node.js release schedule when you bump it, rather than trusting whatever this document said the last time it was edited.

#### 2. Package Manager

The package manager and its version are pinned in the `packageManager` field of `package.json`. Corepack reads that field and activates the matching version, so there is no globally installed copy to drift out of sync.

```bash
corepack enable
corepack install    # installs exactly the version named in package.json

# Verify against the pin:
node -p "require('./package.json').packageManager"
pnpm --version      # must match the version above
```

#### 3. Docker and Docker Compose
```bash
# macOS: Install Docker Desktop from https://docker.com/products/docker-desktop
# Linux:
sudo apt-get update
sudo apt-get install docker.io docker-compose-v2
sudo usermod -aG docker $USER  # Log out and back in after this

docker --version          # any Docker Engine release still receiving security updates
docker compose version    # must be Compose v2 - the `docker compose` subcommand,
                          # not the standalone `docker-compose` binary
```

#### 4. Git
```bash
git --version  # any release still receiving security updates

# Configure git (use your work email)
git config --global user.name "Your Name"
git config --global user.email "you@company.com"
git config --global pull.rebase true
git config --global init.defaultBranch main
```

#### 5. PostgreSQL Client (for debugging, not the server)

Match the client major version to the server major version pinned in `docker-compose.yml`. A client older than the server will fail on newer `pg_dump` formats.

```bash
# Read the server version the project actually runs:
grep -A1 'image: postgres' docker-compose.yml

# macOS:
brew install libpq
brew link --force libpq

# Ubuntu (substitute the major version you just read):
sudo apt-get install postgresql-client-<major>

psql --version  # must match the server major version in docker-compose.yml
```

## Reproducible and Agent-Ready Environments

The numbered steps below install everything onto your own machine. That path works, but it is the slowest to reproduce and the easiest to get subtly wrong - and a coding agent cannot follow it at all without inheriting whatever drift already exists on the host.

Prefer a containerized environment. The goal is that a human and an agent get byte-identical toolchains, so "works on my machine" stops being a category of bug.

### Devcontainer (preferred path)

The repository ships `.devcontainer/devcontainer.json`, which pins the base image, the toolchain versions, the extensions, and the post-create commands. Anything that supports the devcontainer specification can open it - editors, CLIs, and hosted development environments alike.

```bash
# Build and enter the container (CLI path; your editor's "Reopen in Container" does the same)
devcontainer up --workspace-folder .
devcontainer exec --workspace-folder . -- pnpm test
```

What belongs in the devcontainer definition:

- The base image, pinned by digest, not by a floating tag
- Every tool the numbered setup steps install, so the container *is* the setup script
- The `postCreateCommand` that installs dependencies, runs migrations, and seeds data
- `forwardPorts` for every port listed in the Service Dependencies section

When the setup steps below and the devcontainer definition disagree, the devcontainer wins and the document is wrong. Fix the document.

### Remote Development Machines

Long-running or unattended agent work should not run on a laptop. Move it to a remote development machine - a cloud workspace or a dedicated VM that runs the same devcontainer image.

Why it matters beyond convenience:

- **Blast radius.** An agent that deletes a directory deletes it on a disposable machine, not on the host holding your SSH keys and your other clients' repositories.
- **Credential separation.** The remote machine holds only the scoped credentials this project needs. Your personal credentials never enter the environment an agent can read.
- **Auditability.** Shell history, network egress, and file changes on a shared remote machine can be logged centrally. The same activity on a laptop is invisible.
- **Continuity.** A task that takes an hour survives you closing your lid.

Document for your own project: how to request a machine, how long they live, and what gets wiped on teardown.

### Network Boundary and Egress Allowlist

The development environment should not be able to reach arbitrary hosts. Restrict outbound traffic to an allowlist, and treat any addition to it as a reviewed change.

| Destination | Why it is needed | Allowed from |
|-------------|------------------|--------------|
| Package registry | Dependency installation | All environments |
| Source host (git remote) | Clone, fetch, push | All environments |
| Model provider API | AI features and agent inference | All environments |
| Secrets manager | Credential retrieval at startup | All environments |
| Error and telemetry sink | Local debugging parity | Dev and staging |
| Payment provider sandbox | Billing flows against test keys | Dev and staging |
| Production database or API | - | **Never from a development environment** |

Two properties make an allowlist worth the effort. It stops a compromised or hallucinated dependency from exfiltrating your source tree or your `.env` file. And it turns "the agent tried to call something unexpected" from an invisible event into a logged, blocked one that a human reviews.

Keep the allowlist in version control next to the devcontainer definition so a diff shows exactly when a destination was added and by whom.

## Project Setup

### Step 1: Clone and Install
```bash
# Clone the repository
git clone git@github.com:acme-corp/atlas.git
cd atlas

# Install dependencies (pnpm, not npm)
pnpm install

# This also runs the postinstall script which:
# - Generates Prisma client
# - Sets up git hooks via Husky
# - Validates .nvmrc matches your Node version
```

### Step 2: Start Infrastructure Services
```bash
# Start PostgreSQL, Redis, and MinIO (S3-compatible storage) via Docker
docker compose up -d

# Verify all services are healthy
docker compose ps
# Should show 3 services with status "healthy"

# Expected ports:
# PostgreSQL: 5432
# Redis:      6379
# MinIO:      9000 (API) / 9001 (Console)
```

### Step 3: Configure Environment Variables

```bash
# Copy the example environment file
cp .env.example .env
```

Edit `.env` with the following values. Secrets marked with [1PASSWORD] should be retrieved from the "Atlas Dev" vault in 1Password.

```bash
# Database (Docker Compose defaults - no changes needed for local dev)
DATABASE_URL="postgresql://atlas:atlas_dev_password@localhost:5432/atlas_dev"

# Redis (Docker Compose defaults)
REDIS_URL="redis://localhost:6379"

# Object Storage (MinIO - Docker Compose defaults)
S3_ENDPOINT="http://localhost:9000"
S3_ACCESS_KEY="minioadmin"
S3_SECRET_KEY="minioadmin"
S3_BUCKET="atlas-uploads"

# Authentication
AUTH_SECRET="generate-locally-see-command-below"
GOOGLE_CLIENT_ID="[1PASSWORD: Atlas Dev > Google OAuth > Client ID]"
GOOGLE_CLIENT_SECRET="[1PASSWORD: Atlas Dev > Google OAuth > Client Secret]"

# Email (development uses local mailpit - no config needed)
SMTP_HOST="localhost"
SMTP_PORT="1025"

# External Services
STRIPE_SECRET_KEY="[1PASSWORD: Atlas Dev > Stripe > Test Secret Key]"
STRIPE_WEBHOOK_SECRET="[1PASSWORD: Atlas Dev > Stripe > Webhook Secret]"
AI_PROVIDER_API_KEY="[1PASSWORD: Atlas Dev > AI Provider > API Key]"

# Application
NODE_ENV="development"
PORT="3000"
LOG_LEVEL="debug"
```

```bash
# Generate the AUTH_SECRET value
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# Paste the output as the AUTH_SECRET value in .env
```

### Step 4: Initialize the Database
```bash
# Run all migrations to create tables
pnpm db:migrate

# Seed development data (creates test users, sample data)
pnpm db:seed

# Verify the database is set up correctly
pnpm db:studio
# Opens Prisma Studio at http://localhost:5555
# You should see tables with seeded data
```

### Step 5: Start the Development Server
```bash
# Start the application in development mode (hot reload enabled)
pnpm dev

# Application: http://localhost:3000
# API docs:    http://localhost:3000/api/docs
# Email inbox: http://localhost:8025 (Mailpit - catches all dev emails)
```

### Step 6: Verify Everything Works
```bash
# Run the test suite to confirm your setup is correct
pnpm test
# Expect: 0 failures and 0 skipped. The runner prints the total; if your total is
# lower than CI's on the same commit, tests are being skipped and your setup is
# incomplete - do not move on.

# Run a quick smoke test
pnpm test:smoke

# If all tests pass, your environment is ready
```

### Test Accounts (from seed data)
| Email | Password | Role |
|-------|----------|------|
| admin@example.com | password123 | Admin |
| user@example.com | password123 | User |
| viewer@example.com | password123 | Viewer |

## Service Dependencies

### Service Architecture
```
                    +----------------+
                    |   Next.js App  |
                    |   (port 3000)  |
                    +-------+--------+
                            |
              +-------------+-------------+
              |             |             |
      +-------v---+  +-----v-----+  +----v------+
      | PostgreSQL |  |   Redis   |  |   MinIO   |
      | (port 5432)|  |(port 6379)|  |(port 9000)|
      +------------+  +-----------+  +-----------+
```

### PostgreSQL (Primary Database)
- **Purpose**: All application data - users, documents, teams, billing
- **Version**: pinned by the `postgres` image tag in `docker-compose.yml` - that file is the source of truth, not this line
- **Local access**: `psql postgresql://atlas:atlas_dev_password@localhost:5432/atlas_dev`
- **GUI**: Prisma Studio (`pnpm db:studio`) or pgAdmin/DBeaver

### Redis (Cache and Queues)
- **Purpose**: Session store, rate limiting, background job queue (BullMQ)
- **Version**: pinned by the `redis` image tag in `docker-compose.yml`
- **Local access**: `redis-cli` or RedisInsight
- **Note**: Flushing Redis in dev is safe - sessions will just expire and jobs will re-enqueue

### MinIO (Object Storage)
- **Purpose**: File uploads, document attachments, profile images
- **Console**: http://localhost:9001 (login: minioadmin/minioadmin)
- **Note**: S3-compatible API. Production uses AWS S3. Code is identical.

## MCP Servers This Repository Expects

Model Context Protocol (MCP) servers are how a coding agent reaches tools and live data - the database, the issue tracker, the error reporter. They are a service dependency like PostgreSQL or Redis, and they belong in this document for the same reason: an undocumented dependency is a setup failure waiting to happen, and an over-privileged one is an incident waiting to happen.

Configuration lives in the repository so every developer and every agent gets the same set. Credentials never do - they come from the environment or the secrets manager.

| Server | What the agent uses it for | Scope | Credential source |
|--------|---------------------------|-------|-------------------|
| Database (schema introspection) | Read table structure, indexes, and applied migrations | **Read-only**, dev database only | `MCP_DB_URL` from 1Password |
| Filesystem | Read and edit files inside the workspace | Read/write, workspace root only | None |
| Git / source host | Read diffs, open pull requests, comment | Read/write on branches; **never force-push to the default branch** | Fine-grained token, repo-scoped |
| Issue tracker | Read tickets, post status comments | Read/write on this project only | Project-scoped token |
| Error reporting | Read stack traces while debugging | **Read-only** | Read token |
| Documentation search | Look up internal architecture docs | **Read-only** | SSO |

### Rules for Adding a Server

1. **Default to read-only.** Grant write scope only where a workflow genuinely requires it, and name that workflow in the table above. Read-only is the difference between an agent that reports a problem and an agent that causes one.
2. **Scope to this project.** A token that can reach every repository in the organization is not a project dependency, it is an organizational risk.
3. **Never point a development-environment server at production data.** Use a seeded copy. This is the same rule as the egress allowlist, applied to tools instead of hosts.
4. **Pin the server version** the same way you pin every other dependency, and review upgrades. An MCP server is code you are handing your credentials to.
5. **Record what each one is for.** A server nobody can justify should be removed, not tolerated.
6. **Rotate on offboarding.** MCP credentials are credentials. They belong in the same rotation list as everything else in Secrets Management below.

## Common Development Commands

```bash
# Application
pnpm dev                    # Start dev server with hot reload
pnpm build                  # Production build
pnpm start                  # Start production build locally
pnpm lint                   # Run ESLint
pnpm type-check             # TypeScript compiler check

# Database
pnpm db:migrate             # Apply pending migrations
pnpm db:migrate:create      # Create a new migration
pnpm db:seed                # Seed development data
pnpm db:reset               # Drop all tables, re-migrate, re-seed
pnpm db:studio              # Open Prisma Studio GUI

# Testing
pnpm test                   # Run all unit tests
pnpm test:watch             # Run tests in watch mode
pnpm test:integration       # Integration tests (needs running services)
pnpm test:e2e               # Playwright end-to-end tests
pnpm test:coverage          # Generate coverage report

# Docker
docker compose up -d        # Start all services
docker compose down         # Stop all services
docker compose logs -f      # Tail logs from all services
docker compose down -v      # Stop and delete all data (fresh start)

# Code Generation
pnpm generate:component     # Scaffold a new React component
pnpm generate:route         # Scaffold a new API route
pnpm generate:migration     # Create a new database migration
```

## What an Agent May Run Without Approval

Every command above falls into one of three tiers. Publishing the tiers is what makes unattended agent work safe: without them, a team either approves every command (and gets no benefit) or approves everything (and eventually loses data). Tier the risk once, write it down, and let the boundary do the work.

### Tier 1: Autonomous - no approval needed

Reversible, local, and cheap. An agent may run these unattended, in a loop, as often as it likes.

```bash
pnpm lint                   # Read-only analysis
pnpm type-check             # Read-only analysis
pnpm test                   # Local, isolated, no external calls
pnpm test:watch
pnpm build                  # Writes only to the build output directory
pnpm dev                    # Local server
pnpm db:migrate             # Local dev database only
pnpm db:seed                # Local dev database only
pnpm db:reset               # Destroys only local seeded data
docker compose up -d
docker compose logs -f
git add / git commit / git checkout -b     # Local history, recoverable
```

The common thread: worst case, a human runs `pnpm db:reset` or deletes a branch and everything is back.

### Tier 2: Human approval required

Reversible only with effort, or visible to someone outside the loop.

- Pushing to a shared branch, or opening a pull request that triggers CI on shared runners
- Installing or upgrading a dependency (supply-chain surface, and it changes the lockfile everyone shares)
- Adding a destination to the egress allowlist, or adding an MCP server
- Editing CI configuration, deployment workflows, or anything under `.github/`
- Any command against staging
- Writing to a third-party sandbox that other developers share
- Deleting a file the agent did not create in this session

### Tier 3: Never - not with approval

An agent does not run these. If a task appears to require one, the task is wrong or the tooling is missing a safe equivalent.

- Anything touching production data: production `DATABASE_URL`, production API tokens, production object storage
- Any `DELETE`, `UPDATE`, `DROP`, or `TRUNCATE` outside a local dev database
- Anything that spends money: provisioning infrastructure, sending real email or SMS, charging a live payment key
- `git push --force` to a default or release branch, history rewrites, tag deletion
- Rotating, revoking, or printing production secrets
- Anything irreversible with no rollback path documented in this file

### Making the Boundary Enforceable

A list in a document is a convention. These make it a control:

- **Withhold the credentials.** A development environment that has no production connection string cannot touch production, whatever any prompt says. This is the strongest control available and it is free.
- **Keep the egress allowlist narrow** so Tier 3 destinations are unreachable, not merely discouraged.
- **Run deterministic scanners in CI** - secret scanning, dependency audit, license check, migration linting. A scanner rejects a bad change the same way every time; a reviewer does not.
- **Require review by risk tier, not by author.** A dependency bump and a migration deserve different scrutiny regardless of whether a human or an agent wrote them.
- **Ship an audit trail.** Log which commands ran, which MCP servers were called, and which egress attempts were blocked, then forward those events to wherever your organization already watches security logs. An agent action nobody can reconstruct is an agent action nobody can review.
- **Use specialized review agents as a pre-filter, not a gate.** They are good at catching the mechanical problems before a human looks. They do not replace the human on a Tier 2 change.

## IDE Setup

### VS Code (Recommended)

#### Required Extensions
Install via the Extensions panel or command line:
```bash
code --install-extension dbaeumer.vscode-eslint
code --install-extension esbenp.prettier-vscode
code --install-extension bradlc.vscode-tailwindcss
code --install-extension Prisma.prisma
code --install-extension usernamehw.errorlens
code --install-extension eamodio.gitlens
```

#### Workspace Settings
The project includes `.vscode/settings.json` with team-standard settings. Key settings:
```json
{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
  },
  "typescript.tsdk": "node_modules/typescript/lib",
  "tailwindCSS.experimental.classRegex": [
    ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
  ]
}
```

#### Debugging Configuration
The project includes `.vscode/launch.json` with pre-configured debug profiles:
- **Next.js: Server** - Debug server-side code with breakpoints
- **Next.js: Client** - Debug client-side code in Chrome
- **Jest: Current File** - Debug the currently open test file

### JetBrains (WebStorm)
- Enable ESLint and Prettier integration in Preferences > Languages & Frameworks
- Set Node interpreter to the version-manager-managed Node
- Import the `.editorconfig` from the project root

### Agent Instructions (`AGENTS.md`)

Editor choice is personal; agent instructions are not. The repository root holds an `AGENTS.md` - plain Markdown, no frontmatter, no schema - that tells any coding agent how this project builds, tests, and formats itself. It is read natively by a wide range of agent CLIs and editor integrations, so one file covers the whole team instead of one config per tool.

Precedence is nearest-file-wins: an `AGENTS.md` inside `packages/api/` overrides the root file for work in that directory. Use that to keep the root file short and put package-specific rules where they apply.

What belongs in it, and what does not:

- **Belongs**: the exact build/test/lint commands, the version-pin locations described above, the risk tiers from the previous section, project conventions a newcomer would get wrong.
- **Does not belong**: anything secret, anything already enforced by a linter or CI (say it once, in the tool), and prose about why the architecture is good.

Update `AGENTS.md` in the same pull request that changes the commands it documents - the same rule this document lives under.

## Troubleshooting

### Port Conflicts
```bash
# Check what is using a port
# macOS/Linux:
lsof -i :3000
# Windows (PowerShell):
netstat -ano | findstr :3000

# Kill process on port
# macOS/Linux:
kill -9 $(lsof -t -i :3000)
# Windows:
taskkill /PID [PID] /F

# Or change the port in .env:
PORT=3001
```

### Docker Issues
```bash
# Services will not start
docker compose down -v    # Remove volumes and try fresh
docker compose up -d

# Out of disk space
docker system prune -a    # Remove unused images and containers
docker volume prune       # Remove unused volumes

# Permission denied (Linux)
sudo usermod -aG docker $USER
# Log out and back in
```

### Database Issues
```bash
# Cannot connect to database
docker compose ps         # Is postgres service running?
docker compose logs db    # Check postgres logs for errors

# Migration fails
pnpm db:migrate:status    # Check which migrations have been applied
pnpm db:reset             # Nuclear option - drops everything and starts fresh

# Prisma client out of sync with schema
pnpm prisma generate      # Regenerate the Prisma client
```

### Node/pnpm Issues
```bash
# Wrong Node version
nvm use                   # Reads .nvmrc and switches to correct version

# Dependency resolution errors
rm -rf node_modules
pnpm install --force      # Force reinstall all dependencies

# pnpm lockfile conflicts after merge
git checkout --theirs pnpm-lock.yaml
pnpm install
git add pnpm-lock.yaml
```

## Environment Variables Reference

| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `DATABASE_URL` | Yes | - | PostgreSQL connection string |
| `REDIS_URL` | Yes | - | Redis connection string |
| `S3_ENDPOINT` | Yes | - | S3/MinIO endpoint URL |
| `S3_ACCESS_KEY` | Yes | - | S3/MinIO access key |
| `S3_SECRET_KEY` | Yes | - | S3/MinIO secret key |
| `S3_BUCKET` | Yes | - | S3/MinIO bucket name |
| `AUTH_SECRET` | Yes | - | NextAuth session encryption secret |
| `GOOGLE_CLIENT_ID` | Yes | - | Google OAuth client ID |
| `GOOGLE_CLIENT_SECRET` | Yes | - | Google OAuth client secret |
| `STRIPE_SECRET_KEY` | No | - | Stripe API key (payments disabled without) |
| `AI_PROVIDER_API_KEY` | No | - | Model provider API key (AI features disabled without) |
| `MCP_DB_URL` | No | - | Read-only connection string for the schema-introspection MCP server |
| `PORT` | No | 3000 | Application port |
| `NODE_ENV` | No | development | Environment mode |
| `LOG_LEVEL` | No | info | Logging verbosity (debug/info/warn/error) |

## Secrets Management

### Development Secrets
- Store in `.env` (gitignored - never committed)
- Get values from 1Password vault "Atlas Dev"
- Never share secrets over Slack or email - use 1Password sharing

### Generating Local Secrets
```bash
# Generate a random secret key (for AUTH_SECRET, etc.)
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

# Generate a UUID
node -e "console.log(require('crypto').randomUUID())"
```

### Production Secrets
- Managed via [AWS Systems Manager Parameter Store / Vault / etc.]
- Rotated quarterly by the platform team
- Access requests go through [process/tool]
- Never present in a development environment, a devcontainer, or any MCP server configuration - see the risk tiers above

## Keeping This Document Current

- **Update setup docs in the same pull request as the change.** A new service dependency, a changed port, a new required credential, a new MCP server, or an added egress destination all make this file wrong the moment they merge. Reviewers should block a PR that changes setup without changing this file.
- **Re-time the 30-minute bar** whenever someone new joins. It is the only claim here that can be measured, so measure it.
- **Never write a version number in prose.** Point at `.nvmrc`, `package.json`, or `docker-compose.yml` and show the command that reads it. Every literal version in a paragraph is a future support ticket.
- **Delete steps that no longer apply.** A setup document that has only ever grown is a setup document nobody finishes.
