Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Local development OVERRIDES — optional.
#
# You normally do NOT need a .env file: `pnpm dev` bakes in the deterministic
# localhost wiring (see scripts/dev-serve.sh). Copy this file to `.env` ONLY to
# override a default or to supply a secret. Values set here take precedence.

# --- Secrets (optional; enable AI features) ---
# OPENAI_API_KEY=

# --- Override examples (uncomment + edit only if needed) ---
# Point the apps at a different Postgres/Redis (e.g. the proprietary stack on 5433/6380):
# DATABASE_URL=postgres://postgres:postgres@localhost:5432/packmind
# REDIS_URI=redis://localhost:6379

# Frontend → api/mcp proxy targets (defaults: localhost:3000 / localhost:3001):
# API_HOSTNAME=localhost
# API_PORT=3000
# MCP_HOSTNAME=localhost
# MCP_PORT=3001
24 changes: 22 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,28 @@ This is an Nx monorepo containing applications and reusable packages.

## Local Development Environment

Local development uses Docker Compose to run all services (API, frontend, database, Redis, mcp-Server, Postgresq).
This starts the entire development environmentDocker Compose automatically provisions PostgreSQL and Redis - no manual setup required.
Apps (api, frontend, mcp-server) run **natively on the host**; only the stateful
infra (PostgreSQL, Redis) runs in Docker via `docker-compose.dev.yml`. This gives
real file-watch events and fast HMR — no Nx daemon socket, no file-watch polling.

Per-developer install (minimal): Docker + [`mise`](https://mise.jdx.dev) (pins
Node + pnpm from `mise.toml`).

```bash
mise install && pnpm install # one-time
pnpm dev # daily: infra up → migrations → serve all 3 apps native
```

`pnpm dev` bakes in the deterministic localhost wiring (see `scripts/dev-serve.sh`);
an optional `.env` overrides it (see `.env.example`). Other scripts: `dev:infra`,
`dev:infra:down`, `dev:reset` (wipe volumes), `dev:setup`, `migrate`.

Apps serve at: frontend `:4200`, api `:3000`, mcp-server `:3001`.

> Legacy full-in-container stack (`docker-compose.yml`,
> `PACKMIND_EDITION=oss docker compose --profile dev up -d`) remains during the
> transition as a fallback. Do not run it alongside `docker-compose.dev.yml`
> (port conflicts). `docker-local.sh` + `dockerfile/` still build prod-like images.
## Working with Nx
Comment thread
greptile-apps[bot] marked this conversation as resolved.

The following commands apply for both NX apps and packages (use `./node_modules/.bin/nx show projects` to list actual apps and packages.)
Expand Down
36 changes: 29 additions & 7 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,43 @@

## Starting the stack:

You will need node 24.15.0 and docker to start the development stack. This repo
uses **pnpm 11** (pinned via `packageManager` in `package.json`); enable it through
corepack — no global install needed. pnpm settings (overrides, `allowBuilds`,
hoisting) live in `pnpm-workspace.yaml`, not `.npmrc` or the `package.json` `pnpm`
field, as required since pnpm 11:
The apps (api, frontend, mcp-server) run **natively on your host**; only Postgres
and Redis run in Docker. This gives real file-watch events and fast HMR. You need
**Docker** and **Node 24.15.0 + pnpm 11**.

Recommended — pin the toolchain with [`mise`](https://mise.jdx.dev) (reads
`mise.toml`), then two commands:

```shell
mise install # installs the pinned Node + pnpm (via corepack)
pnpm install
pnpm dev # Postgres + Redis up → run migrations → serve all 3 apps
```

Without mise, provide Node/pnpm yourself (corepack — no global install needed):

```shell
nvm use
corepack enable
pnpm install --frozen-lockfile
PACKMIND_EDITION=oss node scripts/select-tsconfig.mjs
docker compose --profile=dev up
pnpm dev
```

The app should be available at [http://localhost:4200](http://localhost:4200)
(api on `:3000`, mcp-server on `:3001`).

`pnpm dev` bakes in the local connection wiring; create a `.env` (see
`.env.example`) only to override a default or supply a secret. Useful scripts:
`pnpm dev:infra` (infra only), `pnpm dev:infra:down`, `pnpm dev:reset` (wipe data
volumes), `pnpm migrate`.

pnpm settings (overrides, `allowBuilds`, hoisting) live in `pnpm-workspace.yaml`,
not `.npmrc` or the `package.json` `pnpm` field, as required since pnpm 11.

> **Legacy stack (transitional):** the old full-in-container setup
> (`PACKMIND_EDITION=oss docker compose --profile=dev up`) still works as a
> fallback but is being retired. Do not run it at the same time as `pnpm dev` /
> `docker-compose.dev.yml` — they share ports 5432/6379.

## Migrating an existing checkout from npm to pnpm

Expand Down
140 changes: 140 additions & 0 deletions LOCAL_DEV_REVAMP_PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# Local Dev Environment Revamp — Migration Plan

**Status:** Plan only. No files changed yet.
**Goal:** Two commands to a working stack, minimal per-developer install, fast HMR, works on Linux + macOS.
**Date:** 2026-06-17

---

## 1. Problem statement

The current `docker-compose.yml` runs the **watched Node apps** (frontend, api, mcp-server) _inside containers_ over a **bind-mounted source tree** (`.:/packmind`) plus a named `node_modules` volume. This combination is the root of a year of friction:

- **File-system events don't cross the container/VM boundary** (especially on macOS). To compensate, the setup forces polling everywhere: `NX_WATCHER: polling`, `CHOKIDAR_USEPOLLING=1`, `WATCHPACK_POLLING=true`, polling intervals of 200–1000ms. Polling = laggy HMR + constant CPU burn.
- **`node_modules` lives in a named volume** (`dev-node_modules`), invisible to the host, prone to drift, and requiring a dedicated `install-dependencies` container on every boot.
- **The Nx daemon runs in its own container** sharing a socket via the `dev-nx-sock` volume. It's fragile: every app service carries a "Daemon OK / Daemon NOT reachable → fall back" branch that exists _only_ because the design is brittle.

**Insight:** Docker is excellent for **stateful infra** (Postgres, Redis) and bad for **hot-reloading Node processes**. We currently use it for both. Splitting the two removes ~80% of the pain regardless of which toolchain manager we pick.

---

## 2. Chosen approach — Hybrid (infra in Docker, apps native) + `mise`

- **Infra (Postgres, Redis) stays in Docker.** Already pinned by image tag, stateful, Docker's sweet spot. Optionally keep pgAdmin.
- **Apps run natively on the host** via `nx run-many` → real fs events, fast HMR, no daemon/socket/polling.
- **Toolchain pinned by [`mise`](https://mise.jdx.dev)** reading `.nvmrc` (Node 24.15.0) + `package.json` `packageManager` (pnpm 11.5.0 via corepack). Per-dev install = Docker (usually already present) + `mise` (one curl/brew). That is the "minimal install."

### Why this over Nix/devenv

Nix/devenv gives maximal reproducibility (system libs, exact Postgres binary, hermetic CI) but costs team ramp-up. For a pure-JS stack whose infra is already pinned by Docker image tags, mise delivers ~95% of the reproducibility at ~5% of the cost. Nix remains the better choice **only if** hermetic, system-level reproducibility is ranked above onboarding simplicity. See Appendix A for the devenv variant if that priority changes.

### Target developer experience

One-time:

```bash
mise install # installs pinned Node + pnpm
pnpm install
```

Daily:

```bash
pnpm dev # infra up (pg+redis) → migrations → nx run-many serve (api, frontend, mcp)
```

---

## 3. Configuration delta: Docker service-names → localhost

Today apps reach each other by Docker **service name**. Running natively, every reference becomes `localhost`. This is the single most error-prone part of the migration.

| Variable | Today (in-container) | Native value |
| ------------------------- | ----------------------------------------------------- | ------------------------------------------------------ |
| `DATABASE_URL` | `postgres://postgres:postgres@postgres:5432/packmind` | `postgres://postgres:postgres@localhost:5432/packmind` |
| `REDIS_URI` | `redis://redis:6379` | `redis://localhost:6379` |
| `API_HOSTNAME` (frontend) | `backend` | `localhost` |
| `MCP_HOSTNAME` (frontend) | `mcp-server` | `localhost` |
| `API_PORT` / `MCP_PORT` | `3000` / `3001` | unchanged |
| `APP_WEB_URL` | `http://localhost:4200` | unchanged |
| `COOKIE_SECURE` | `false` | unchanged |
| `JS_PLAYGROUND_PATH` | `packages/linter/js-playground-local` | unchanged |

These belong in a committed `.env.example` (and a gitignored `.env`, sourced by the existing `withEnv` script pattern already in `package.json`).

Note: `packages/migrations/datasource.ts` **already** points at `localhost:5432` with `postgres/postgres/packmind` — so native migrations need no new datasource; use `datasource.ts`, not `datasourceDocker.ts`.

---

## 4. Required pre-serve steps (don't lose these)

The current compose entrypoints do two things beyond `nx serve` that must be preserved as explicit setup steps:

1. **tsconfig selection** — `node scripts/select-tsconfig.mjs` (reads `PACKMIND_EDITION`, must be `oss`). Run once after install / on edition change.
2. **JS playground copy** — api and mcp-server copy `packages/linter/js-playground` → `packages/linter/js-playground-local` if absent. Fold into a `predev` / setup script.

---

## 5. Files to add / change

**Add:**

- `mise.toml` — pins `node = "24.15.0"`, enables corepack/pnpm; optional `[env]` to auto-load `.env`.
- `docker-compose.dev.yml` — **infra only**: `postgres` (17-alpine), `redis` (7.2.4), optional `pgadmin`. Named volumes for data. No app services, no nx-daemon, no install-deps, no socket volume, no polling env.
- `.env.example` — the native env values from §3.
- Root `package.json` scripts:
- `dev:infra` → `docker compose -f docker-compose.dev.yml up -d`
- `dev:setup` → `node scripts/select-tsconfig.mjs && <js-playground copy>`
- `migrate` → `cd packages/migrations && pnpm typeorm migration:run -d datasource.ts`
- `dev` → `pnpm dev:infra && pnpm migrate && nx run-many -t serve -p api frontend mcp-server` (serve targets: `api:serve:development`, `frontend:dev`, `mcp-server:serve:development` — confirm config names during build).
- `CONTRIBUTING.md` (or README dev section) — the two-command flow + troubleshooting.

**Change:**

- `CLAUDE.md` — update the "Local Development Environment" section to describe the hybrid flow.
- Memory note: the recorded start command (`PACKMIND_EDITION=oss docker compose --profile dev up -d`) will be superseded by `pnpm dev`.

**Keep, untouched (transition safety):**

- `docker-compose.yml` (full in-container stack) — leave during transition; delete only after the team validates the hybrid flow.
- `docker-local.sh` + `dockerfile/` — still used for prod-like image builds. Out of scope.

**Eventually delete (phase 4, after validation):** from `docker-compose.yml` — `nx-daemon`, `install-dependencies`, `run-migrations`, `frontend`, `backend`, `mcp-server`, `nginx` services; the `dev-nx-sock`, `dev-node_modules`, `dev-dist`, `dev-tmp`, `dev-corepack-cache`, `dev-pnpm-store` volumes; all `CHOKIDAR_*` / `WATCHPACK_*` / `NX_WATCHER*` / `NX_DAEMON*` env. ~250 lines.

---

## 6. Phased rollout (each phase = its own commit)

1. **Add infra-only compose + mise + .env.example.** Devs can run `docker compose -f docker-compose.dev.yml up -d` and serve apps manually. Old compose still present. _Validation:_ infra healthy, apps serve natively against it.
2. **Add `pnpm dev` orchestration + setup scripts.** Collapse to two commands. _Validation:_ clean clone → `mise install && pnpm install` → `pnpm dev` → frontend at :4200, api at :3000, mcp at :3001, login works.
3. **Docs + CLAUDE.md update.** Team switches over. Gather feedback for ~1 week.
4. **Remove app/daemon services from `docker-compose.yml`** (or delete the file if `docker-local.sh` fully covers prod-like testing). Drop dead env/volumes.

---

## 7. Risks & mitigations

- **Process orchestration / logs.** `nx run-many -t serve` interleaves logs; Ctrl-C should stop all. If output is messy, add a lightweight runner (`mprocs` / `concurrently`) — but try plain `nx run-many` first (zero extra dep).
- **Migration ordering.** `pnpm dev` must run migrations _after_ Postgres is healthy. Add a readiness wait (`pg_isready` loop) or `docker compose ... up -d --wait` (Compose v2 `--wait` blocks until healthy).
- **Port conflicts** with the old full stack. Don't run both compose files at once; `docker-compose.dev.yml` reuses 5432/6379, so `down` the old stack first.
- **macOS native Postgres/Redis not needed** — they stay in Docker, so no host DB install. Bind-mount perf problem is gone because _source_ is no longer mounted into a container.
- **`frontend:dev` vs `frontend:serve` target name.** Compose uses `frontend:dev`; project.json shows `serve`. Verify the exact runnable target when wiring `pnpm dev`.
- **CI parity.** CI currently sets `CI=...` to disable the daemon. CI is unaffected (it builds images / runs e2e via the existing paths). Confirm e2e (`run-e2e-tests` profile) still works against either stack.

---

## 8. Rollback

All Phase 1–3 changes are **additive** (new files + new scripts). If the hybrid flow misbehaves, developers fall back to the untouched `docker-compose.yml` immediately. No destructive change occurs until Phase 4, which is gated on team sign-off.

---

## Appendix A — devenv.sh (Nix) variant

If hermetic reproducibility later outranks onboarding simplicity, swap `mise` + `docker-compose.dev.yml` for a single `devenv.nix`:

- `languages.javascript` + `pnpm` (pinned), `services.postgres` (initial DB `packmind`), `services.redis` — all **native processes**, no Docker daemon at all.
- `processes.{api,frontend,mcp}` with `process-compose` `depends_on` to order migrations first.
- Per-dev install: **Nix only**. Commands: `nix develop` (or `direnv allow`) then `devenv up`.

Trade-off: strongest reproducibility (system libs + exact Postgres binary, identical in CI) at the cost of team Nix ramp-up. Everything else in this plan (the localhost env delta, pre-serve steps, phased rollout, rollback) applies unchanged.
1 change: 0 additions & 1 deletion apps/api/nodemon.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
"watch": ["dist/apps/api"],
"ext": "js,json",
"exec": "node dist/apps/api/main.js",
"legacyWatch": true,
"delay": "200ms",
"signal": "SIGTERM",
"ignore": ["dist/apps/api/**/*.map"],
Expand Down
2 changes: 1 addition & 1 deletion apps/api/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"parallel": true,
"commands": [
"nx run api:build:development --skip-nx-cache",
"sh -lc 'mkdir -p dist/apps/api; while [ ! -f dist/apps/api/main.js ]; do sleep 0.2; done; npx nodemon --config apps/api/nodemon.json --legacy-watch --polling-interval 500'"
"sh -lc 'mkdir -p dist/apps/api; while [ ! -f dist/apps/api/main.js ]; do sleep 0.2; done; npx nodemon --config apps/api/nodemon.json ${WATCH_POLLING:+--legacy-watch --polling-interval 500}'"
]
},
"configurations": {
Expand Down
71 changes: 71 additions & 0 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Local development infra ONLY — Postgres + Redis (+ optional pgAdmin).
# The apps (api, frontend, mcp-server) run natively on the host via `pnpm dev`,
# NOT in containers. This avoids bind-mount fs-event loss, file-watch polling,
# and the shared Nx-daemon socket — the sources of the old in-container setup's pain.
#
# Start: docker compose -f docker-compose.dev.yml up -d
# With UI: docker compose -f docker-compose.dev.yml --profile tools up -d
# Stop: docker compose -f docker-compose.dev.yml down
# Wipe: docker compose -f docker-compose.dev.yml down -v
#
# NOTE: reuses ports 5432/6379. Do not run this alongside the legacy
# docker-compose.yml dev stack — `down` the old one first.
#
# Dedicated Compose project name isolates containers/volumes/network from the
# legacy stack (which defaults to project "packmind"), so the two never collide.
name: packmind-dev

services:
postgres:
image: postgres:17-alpine
container_name: packmind-dev-postgres
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: packmind
ports:
- '5432:5432'
volumes:
- packmind-dev-postgres:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U postgres']
interval: 2s
timeout: 2s
retries: 15
start_period: 2s

redis:
image: redis:7.2.4
container_name: packmind-dev-redis
ports:
- '6379:6379'
volumes:
- packmind-dev-redis:/data
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 2s
timeout: 2s
retries: 15
start_period: 1s

pgadmin:
profiles:
- tools
image: dpage/pgadmin4:latest

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The pgadmin service uses the unpinned latest tag while postgres and redis are both version-pinned. On a fresh pull, different developers could get different pgAdmin versions, breaking the consistency goal this PR sets out to achieve. Pinning to a specific version avoids unexpected breakage when pgAdmin ships a breaking UI or config-schema change.

Suggested change
image: dpage/pgadmin4:latest
image: dpage/pgadmin4:9.4

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

container_name: packmind-dev-pgadmin
environment:
PGADMIN_DEFAULT_EMAIL: admin@pgadmin.com
PGADMIN_DEFAULT_PASSWORD: password
PGADMIN_LISTEN_PORT: 80
ports:
- '2345:80'
volumes:
- packmind-dev-pgadmin:/var/lib/pgadmin
depends_on:
postgres:
condition: service_healthy

volumes:
packmind-dev-postgres:
packmind-dev-redis:
packmind-dev-pgadmin:
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ services:
CHOKIDAR_USEPOLLING: '1'
CHOKIDAR_INTERVAL: '200'
WATCHPACK_POLLING: 'true'
WATCH_POLLING: '1'
JS_PLAYGROUND_PATH: 'packages/linter/js-playground-local'
APP_WEB_URL: ${APP_WEB_URL:-http://localhost:4200}

Expand Down
9 changes: 9 additions & 0 deletions mise.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Per-developer toolchain, pinned. Install once with `mise install`.
# Node version mirrors .nvmrc; pnpm is provisioned via corepack to match
# package.json "packageManager" exactly (no version drift vs the lockfile).
[tools]
node = { version = "24.15.0", postinstall = "corepack enable && corepack prepare pnpm@11.5.0 --activate" }

[env]
# Default edition for local development (override per-shell if needed).
PACKMIND_EDITION = "oss"
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
"packageManager": "pnpm@11.5.0",
"scripts": {
"build": "NODE_OPTIONS='--max-old-space-size=16384' nx run-many -t build",
"dev": "pnpm dev:infra && pnpm dev:setup && pnpm migrate && bash scripts/dev-serve.sh",
"dev:infra": "docker compose -f docker-compose.dev.yml up -d --wait",
"dev:infra:down": "docker compose -f docker-compose.dev.yml down",
"dev:reset": "docker compose -f docker-compose.dev.yml down -v",
"dev:setup": "node scripts/prepare-local-dev.mjs",
"migrate": "cd packages/migrations && pnpm typeorm migration:run --dataSource=datasource.ts --transaction each",
"chakra:typegen": "pnpm --filter ./packages/ui exec chakra typegen ./src/lib/theme/theme.ts",
"e2e": "PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=false pnpm exec playwright test --config apps/e2e-tests/playwright.config.ts",
"husky": "husky",
Expand Down
Loading
Loading