Skip to content

statica-ai/statica

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

400 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Contributing Guide

This guide documents the local development workflow for contributors working on the Statica codebase.

It covers:

  • first-time setup
  • day-to-day development in the main checkout
  • isolated worktree development
  • the shared PostgreSQL model
  • testing and verification
  • full-stack isolated testing (backend + frontend + daemon from source)
  • troubleshooting and destructive reset options

Development Model

Local development uses one shared PostgreSQL container and one database per checkout.

  • the main checkout usually uses .env and POSTGRES_DB=statica
  • each Git worktree uses its own .env.worktree
  • every checkout connects to the same PostgreSQL host: localhost:5432
  • isolation happens at the database level, not by starting a separate Docker Compose project
  • backend and frontend ports are still unique per worktree

This keeps Docker simple while still isolating schema and data.

Prerequisites

  • Node.js v20+
  • pnpm v10.28+
  • Go v1.26+
  • Docker

Important Rules

  • The main checkout should use .env.
  • A worktree should use .env.worktree.
  • Do not copy .env into a worktree directory.

Why:

  • the current command flow prefers .env over .env.worktree
  • if a worktree contains .env, it can accidentally point back to the main database

Environment Files

Main Checkout

Create .env once:

cp .env.example .env

By default, .env points to:

POSTGRES_DB=statica
POSTGRES_PORT=5432
DATABASE_URL=postgres://statica:statica@localhost:5432/statica?sslmode=disable
PORT=8080
FRONTEND_PORT=3000

Worktree

Generate .env.worktree from inside the worktree:

make worktree-env

That generates values like:

POSTGRES_DB=statica_my_feature_702
POSTGRES_PORT=5432
PORT=18782
FRONTEND_PORT=13702
DATABASE_URL=postgres://statica:statica@localhost:5432/statica_my_feature_702?sslmode=disable

Notes:

  • POSTGRES_DB is unique per worktree
  • POSTGRES_PORT stays fixed at 5432
  • backend and frontend ports are derived from the worktree path hash
  • make worktree-env refuses to overwrite an existing .env.worktree

To regenerate a worktree env file:

FORCE=1 make worktree-env

First-Time Setup

Quick Start (recommended)

From any checkout (main or worktree):

make dev

This single command:

  • auto-detects whether you're in a main checkout or a worktree
  • creates the appropriate env file (.env or .env.worktree) if it doesn't exist
  • checks that prerequisites (Node.js, pnpm, Go, Docker) are installed
  • installs JavaScript dependencies
  • ensures the shared PostgreSQL container is running
  • creates the application database if it does not exist
  • runs all migrations
  • starts both backend and frontend

Explicit Setup (advanced)

If you prefer separate control over setup and startup:

Main Checkout

cp .env.example .env
make setup-main
make start-main

Stop:

make stop-main

Worktree

make worktree-env
make setup-worktree
make start-worktree

Stop:

make stop-worktree

Recommended Daily Workflow

Main Checkout

Use the main checkout when you want a stable local environment for main.

make start-main
make stop-main
make check-main

Feature Worktree

Use a worktree when you want isolated data and separate app ports.

git worktree add ../statica-feature -b feat/my-change main
cd ../statica-feature
make dev

After that, day-to-day commands are:

make dev              # start (re-runs setup if needed, idempotent)
make stop-worktree    # stop
make check-worktree   # verify

Running Main and Worktree at the Same Time

This is a first-class workflow.

Example:

  • main checkout
    • database: statica
    • backend: 8080
    • frontend: 3000
  • worktree checkout
    • database: statica_my_feature_702
    • backend: generated worktree port such as 18782
    • frontend: generated worktree port such as 13702

Both checkouts use:

  • the same PostgreSQL container
  • the same PostgreSQL port: 5432

But they do not share application data, because each uses a different database.

Command Reference

Shared Infrastructure

Start the shared PostgreSQL container:

make db-up

Stop the shared PostgreSQL container:

make db-down

Important:

  • make db-down stops the container but keeps the Docker volume
  • your local databases are preserved

App Lifecycle

Main checkout:

make setup-main
make start-main
make stop-main
make check-main

Worktree:

make worktree-env
make setup-worktree
make start-worktree
make stop-worktree
make check-worktree

Generic targets for the current checkout:

make setup
make start
make stop
make check
make dev
make test
make migrate-up
make migrate-down

These generic targets require a valid env file in the current directory.

How Database Creation Works

Database creation is automatic.

The following commands all ensure the target database exists before they continue:

  • make setup
  • make start
  • make dev
  • make test
  • make migrate-up
  • make migrate-down
  • make check

That logic lives in scripts/ensure-postgres.sh.

Testing

Run all local checks:

make check-main

Or from a worktree:

make check-worktree

This runs:

  1. TypeScript typecheck
  2. TypeScript unit tests
  3. Go tests
  4. Playwright E2E tests

Notes:

  • Go tests create their own fixture data
  • E2E tests create their own workspace and issue fixtures
  • the check flow starts backend/frontend only if they are not already running

Local Codex Daemon

Run the local daemon:

make daemon

The daemon authenticates using the CLI's stored token (statica login). It registers runtimes for all watched workspaces from the CLI config.

Full-Stack Isolated Testing

This section covers running the complete stack (backend, frontend, daemon) from source in a fully isolated environment. Useful for testing end-to-end changes that span multiple components, or for automated CI/AI workflows that need zero human intervention.

Why Not Just make daemon?

make daemon uses the system-installed CLI's stored token and connects to whatever server is configured in ~/.statica/config.json. That's fine for day-to-day development against a shared server, but for fully isolated testing you need:

  • a local backend and frontend (from source)
  • a local daemon (from source) with its own profile
  • automated authentication (no browser login)
  • no interference with your production CLI config

Dynamic Profile Naming

Each worktree must use a unique daemon profile to avoid collisions when multiple features run in parallel.

The profile name is derived from the worktree directory using the same slug + hash pattern as scripts/init-worktree-env.sh:

WORKTREE_DIR="$(basename "$PWD")"
SLUG="$(printf '%s' "$WORKTREE_DIR" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/_/g; s/__*/_/g; s/^_//; s/_$//')"
HASH="$(printf '%s' "$PWD" | cksum | awk '{print $1}')"
OFFSET=$((HASH % 1000))
PROFILE="dev-${SLUG}-${OFFSET}"

Example: worktree at ../statica-feat-auth produces profile dev-statica_feat_auth-347, matching that worktree's port and database allocation.

Start the Isolated Environment

Run all steps from the worktree root (where the Makefile is).

1. Start backend, frontend, and database

make dev

Wait for the backend to be healthy:

PORT=$(grep '^PORT=' .env.worktree 2>/dev/null || grep '^PORT=' .env | head -1 | cut -d= -f2)
PORT=${PORT:-8080}
SERVER="http://localhost:${PORT}"

for i in $(seq 1 30); do
  curl -sf "$SERVER/health" > /dev/null 2>&1 && break
  sleep 2
done

2. Create a test user and token (automated auth)

In non-production environments the verification code is fixed at 888888:

curl -s -X POST "$SERVER/auth/send-code" \
  -H "Content-Type: application/json" \
  -d '{"email": "dev@localhost"}'

JWT=$(curl -s -X POST "$SERVER/auth/verify-code" \
  -H "Content-Type: application/json" \
  -d '{"email": "dev@localhost", "code": "888888"}' | jq -r '.token')

PAT=$(curl -s -X POST "$SERVER/api/tokens" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{"name": "auto-dev", "expires_in_days": 365}' | jq -r '.token')

3. Create a workspace

WS=$(curl -s -X POST "$SERVER/api/workspaces" \
  -H "Authorization: Bearer $PAT" \
  -H "Content-Type: application/json" \
  -d '{"name": "Dev", "slug": "dev"}' | jq -r '.id')

4. Compute profile name and write CLI config

# Compute profile (see Dynamic Profile Naming above)
WORKTREE_DIR="$(basename "$PWD")"
SLUG="$(printf '%s' "$WORKTREE_DIR" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/_/g; s/__*/_/g; s/^_//; s/_$//')"
HASH="$(printf '%s' "$PWD" | cksum | awk '{print $1}')"
OFFSET=$((HASH % 1000))
PROFILE="dev-${SLUG}-${OFFSET}"

FRONTEND_PORT=$(grep '^FRONTEND_PORT=' .env.worktree 2>/dev/null || grep '^FRONTEND_PORT=' .env | head -1 | cut -d= -f2)
FRONTEND_PORT=${FRONTEND_PORT:-3000}

CONFIG_DIR="$HOME/.statica/profiles/$PROFILE"
mkdir -p "$CONFIG_DIR"

cat > "$CONFIG_DIR/config.json" << EOF
{
  "server_url": "$SERVER",
  "app_url": "http://localhost:${FRONTEND_PORT}",
  "token": "$PAT",
  "workspace_id": "$WS",
  "watched_workspaces": [{"id": "$WS", "name": "Dev"}]
}
EOF

5. Start the daemon from source

make cli ARGS="daemon start --profile $PROFILE"

The daemon runs from the current worktree's Go source, connecting to the local backend. Agent-executed statica commands automatically use the same binary (the daemon prepends its own directory to PATH).

Stop the Isolated Environment

# Compute profile (same formula)
PROFILE="dev-$(printf '%s' "$(basename "$PWD")" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/_/g; s/__*/_/g; s/^_//; s/_$//')-$(( $(printf '%s' "$PWD" | cksum | awk '{print $1}') % 1000 ))"

# 1. Stop daemon
make cli ARGS="daemon stop --profile $PROFILE"

# 2. Stop backend + frontend
make stop            # main checkout
make stop-worktree   # worktree checkout

# 3. (Optional) Stop shared PostgreSQL
make db-down

# 4. (Optional) Clean build artifacts
make clean

# 5. (Optional) Remove profile config
rm -rf "$HOME/.statica/profiles/$PROFILE"

Desktop App Local Testing

To test the Electron desktop app against a local backend:

# After backend is running (make dev)
pnpm dev:desktop

This automatically:

  1. Compiles the statica CLI from server/cmd/statica into apps/desktop/resources/bin/statica
  2. Creates an isolated profile named desktop-localhost-<PORT>
  3. Starts and manages its own daemon instance
  4. Connects to the local backend

Login in the Desktop UI with dev@localhost and code 888888.

If the backend runs on a non-default port (worktree), create apps/desktop/.env.development.local:

VITE_API_URL=http://localhost:<backend-port>
VITE_WS_URL=ws://localhost:<backend-port>/ws

Isolation Guarantee

Nothing in this flow touches the system-installed statica or the default ~/.statica/config.json:

Resource System / Production Local Dev (per-worktree)
Config ~/.statica/config.json ~/.statica/profiles/dev-<slug>-<hash>/config.json
Daemon PID ~/.statica/daemon.pid ~/.statica/profiles/dev-<slug>-<hash>/daemon.pid
Health port 19514 19514 + 1 + (name_hash % 1000)
Workspaces dir ~/statica_workspaces/ ~/statica_workspaces_dev-<slug>-<hash>/
Database remote / production local Docker: statica_<slug>_<hash>
Desktop profile desktop-api.statica.ai desktop-localhost-<port>

Multiple worktrees can run simultaneously without conflict.

Troubleshooting

Missing Env File

If you see:

Missing env file: .env

or:

Missing env file: .env.worktree

then create the expected env file first.

Main checkout:

cp .env.example .env

Worktree:

make worktree-env

Check Which Database a Checkout Uses

Inspect the env file:

cat .env
cat .env.worktree

Look for:

  • POSTGRES_DB
  • DATABASE_URL
  • PORT
  • FRONTEND_PORT

List All Local Databases in Shared PostgreSQL

docker compose exec -T postgres psql -U statica -d postgres -At -c "select datname from pg_database order by datname;"

Worktree Is Accidentally Using the Main Database

Check whether the worktree contains .env.

It should not.

The safe worktree setup is:

make worktree-env
make setup-worktree
make start-worktree

App Stops but PostgreSQL Keeps Running

That is expected.

  • make stop
  • make stop-main
  • make stop-worktree

only stop backend/frontend processes.

To stop the shared PostgreSQL container:

make db-down

Destructive Reset

If you want to stop PostgreSQL and keep your local databases:

make db-down

If you want to wipe all local PostgreSQL data for this repo:

docker compose down -v

Warning:

  • this deletes the shared Docker volume
  • this deletes the main database and every worktree database in that volume
  • after that you must run make setup-main or make setup-worktree again

Typical Flows

Stable Main Environment

make dev

Feature Worktree

git worktree add ../statica-feature -b feat/my-change main
cd ../statica-feature
make dev

Return to a Previously Configured Worktree

cd ../statica-feature
make start-worktree

Validate Before Pushing

Main checkout:

make check-main

Worktree:

make check-worktree

About

No description, website, or topics provided.

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors