Skip to content

Development Guide

This guide covers local setup, day-to-day workflows, testing, Docker Compose, debugging, and how the codebase is organized.

Prerequisites

  • Node.js 24 (service enforces >=24 <25). Use a version manager (e.g., nvm install 24 && nvm use 24).
  • PostgreSQL 16+ reachable at DATABASE_URL (placeholder: postgres://USER:PASSWORD@localhost:5432/gittinkerer).
  • Redis 7+ reachable at REDIS_URL (placeholder: redis://localhost:6379).
  • Codex CLI installed and on PATH (host: /usr/local/bin/codex). Authenticate it before running the runner.
  • GitHub App creds in service/.env (see .env.example): GH_APP_ID, GH_APP_PRIVATE_KEY or file service/private-key.pem, and optional GH_APP_INSTALLATION_ID.
  • Admin token for the web app: set ADMIN_API_TOKEN (used by web build args) and VITE_ADMIN_API_TOKEN (frontend runtime).

Local setup

cd service
npm install
# copy and fill env
cp ../.env.example .env
# point DB/Redis to local instances or Docker ports

To run the API locally with hot reload:

npm run dev

Development workflow

  • Type checking: npm run type-check
  • Lint: npm run lint (auto-fix with npm run lint:fix)
  • Format check: npm run format:check (write with npm run format)
  • Unit tests: npm run test:unit
  • Integration tests: npm run test:integration
  • For DB-backed tests, set DATABASE_URL_TEST to a reachable Postgres instance.
  • Full/coverage: npm run test or npm run test:coverage
  • UI test runner: npm run test:ui

Test architecture

graph TD
    subgraph "Unit Tests (Fast, Isolated)"
        UT1[Domain Models
        Run, RepoRef, Instruction]
        UT2[Use Cases
        createRun, startRun]
        UT3[Utilities
        Zod schemas, helpers]
    end

    subgraph "Integration Tests (Real Infra)"
        IT1[Database Repos
        runsRepo, metricsRepo]
        IT2[Redis Operations
        cache, rateLimit]
        IT3[API Endpoints
        POST /api/runs, webhooks]
    end

    subgraph "Test Helpers & Mocks"
        TH1[Mock Database
        PoolClient, Pool]
        TH2[Mock Redis
        RedisClient]
        TH3[Mock GitHub API
        fetch, webhooks]
        TH4[Mock Process
        spawnGittinkerer]
        TH5[Test Factories
        createTestRun, createTestPayload]
    end

    UT2 --> TH1
    UT2 --> TH2
    UT2 --> TH3
    UT2 --> TH4
    UT2 --> TH5

    IT3 --> TH1
    IT3 --> TH2
    IT3 --> TH4

    IT1 -.->|Test DB| DB[(Test PostgreSQL)]
    IT2 -.->|Test Redis| RD[(Test Redis)]
graph TD
    A[Test Suite] --> B[Redis Tests]
    A --> C[Filesystem Tests]
    A --> D[Process Tests]

    B --> B1[index.test.ts\\nConnection & Lifecycle]
    B --> B2[rateLimit.test.ts\\nEnhanced Error Paths]
    B --> B3[cache.test.ts\\nEnhanced Error Paths]
    B --> B4[integration.test.ts\\nFull Lifecycle]

    C --> C1[artifacts.test.ts\\nAll Functions]
    C --> C2[artifacts.snapshot.test.ts\\nFile Formats]

    D --> D1[spawnGittinkerer.test.ts\\nCLI Spawning]
    D --> D2[workspacePaths.test.ts\\nPath Utils]

    B1 -.->|uses| M1[mockRedis.ts]
    B2 -.->|uses| M1
    B3 -.->|uses| M1
    C1 -.->|uses| M2[mockFs.ts]
    C2 -.->|uses| M2
    D1 -.->|uses| M3[mockProcess.ts]

    style B1 fill:#e1f5ff
    style B2 fill:#e1f5ff
    style B3 fill:#e1f5ff
    style B4 fill:#e1f5ff
    style C1 fill:#fff4e1
    style C2 fill:#fff4e1
    style D1 fill:#f0ffe1
    style D2 fill:#f0ffe1

Key patterns: - Dependency injection: use cases accept collaborators as arguments. - Factories: helpers build domain objects, payloads, and DB rows with sane defaults. - Mock isolation: unit tests mock external services; integration uses real infra or dedicated test containers. - Snapshot testing: apply to complex responses where helpful.

Helper implementations live in service/src/__tests__/helpers/ (e.g., factories.ts, mockDb.ts, mockRedis.ts, mockProcess.ts, mockGithub.ts, mockReply.ts, mockSentry.ts).

Integration Coverage

  • src/__tests__/integration/server.test.ts exercises server startup, health probes, signal-driven shutdown, and failure reporting paths.
  • src/__tests__/integration/app.test.ts validates Fastify wiring: middleware (request IDs, rate limits), route registration, Sentry hooks, and error handling.
  • Helpers added for lifecycle-heavy tests:
  • src/__tests__/helpers/mockSentry.ts for Sentry initialization and capture spies
  • src/__tests__/helpers/mockProcess.ts for signal/exit interception
  • src/__tests__/helpers/mockDb.ts and mockRedis.ts module factories to short-circuit bootstrap work

Code organization (service)

  • domain/: Pure entities and validation (run lifecycle, repo refs, instructions).
  • usecases/: Application services orchestrating domain rules and side effects (e.g., createRun, startRun).
  • infra/: Adapters for Postgres, Redis, filesystem artifacts, process spawning, Sentry, GitHub App auth.
  • controllers/: HTTP handlers wiring Fastify routes to use cases and adapters.
  • routes/, middleware/, utils/, types/: API surface, cross-cutting helpers, and shared typings.

Imports use TS path aliases (see tsconfig.json) to keep module boundaries clear.

Docker Compose workflow

What it provides: - Postgres 16-alpine with default creds (gittinkerer/gittinkerer). - Redis 7-alpine. - Service container (builds from repo) mounting host SSH keys, Codex binary, and the repo for runner access. - Web container (Svelte) built against the service URL; requires ADMIN_API_TOKEN/VITE_ADMIN_API_TOKEN placeholders.

Quick start: 1. Ensure Docker is running. 2. Copy .env.example to service/.env and fill required values (GitHub auth, optional Sentry). 3. Export ADMIN_API_TOKEN/VITE_ADMIN_API_TOKEN, DATABASE_URL, and REDIS_URL as shown below. 4. Run docker compose up --build. - Services: Postgres (postgres:5432), Redis (redis:6379), service API (service:3000), web UI (web:5173). - VITE_SERVICE_URL in the web container points to http://service:3000; host access via http://localhost:5173. 5. Migrations run automatically on service start.

Run it:

# from repo root
export ADMIN_API_TOKEN=changeme
export VITE_ADMIN_API_TOKEN=$ADMIN_API_TOKEN
export DATABASE_URL=postgres://gittinkerer:gittinkerer@localhost:5432/gittinkerer
export REDIS_URL=redis://localhost:6379
export GH_APP_ID=...
export GH_APP_PRIVATE_KEY="$(cat /path/to/private-key.pem)"

docker compose up --build

Service ports: 3000 (API), 5173 (web). Postgres and Redis are exposed on 5432/6379.

Manual setup (without Compose)

  • PostgreSQL
  • Set DATABASE_URL=postgres://user:pass@host:5432/gittinkerer or individual PGHOST/PGPORT/PGUSER/PGPASSWORD/PGDATABASE.
  • Optional pool tuning: PGPOOL_MAX (default 10).
  • Migrations auto-run and create runs, run_metrics, paused_repos; adds user_id/installation_id columns.
  • Redis
  • Set REDIS_URL=redis://localhost:6379 or REDIS_HOST/REDIS_PORT.
  • Optional: REPO_RATE_LIMIT_MAX_REQUESTS (default 5), REPO_RATE_LIMIT_WINDOW_SECONDS (default 60), RUN_STATUS_CACHE_TTL_SECONDS (default 86400).
  • Sentry observability
  • Set SENTRY_DSN; optional SENTRY_ENVIRONMENT, SENTRY_TRACES_SAMPLE_RATE (0–1).
  • Captures service errors (migrations, Redis init, GitHub auth, run spawn, request crashes) and bash failures with context (run_id, repo, stage, user/installation).
  • Analytics & billing
  • Set COST_PER_1K_TOKENS for cost estimates.
  • APIs: GET /api/analytics/usage?group_by=repo,user,day&from=...&to=... and GET /api/analytics/export (CSV).
  • Web UI
  • Set VITE_SERVICE_URL (defaults to http://localhost:3000).
  • Install deps in /web, then npm run dev -- --host or npm run build.
  • Service
  • Install deps in /service.
  • Start with npm run dev (hot reload) or npm start (built). Requires Node 24.x.

Service dependencies (flowchart)

flowchart LR
    PG[(Postgres 16)] --> SVC[Service]
    RD[(Redis 7)] --> SVC
    SVC --> WEB[Web UI]
    SVC --> Runner["CLI Runner (Codex + git)"]
    Runner --> Repo[(Mounted repo + SSH)]

Debugging with tmux (observing the runner)

To watch the spawned bash runner without blocking the API:

# inside the container or host where runner executes
session=debug_$(date +%s)
tmux new-session -d -s "$session" "bash /path/to/bin/gittinkerer run --payload-file <path> --timestamp <ts> --artifacts-dir <dir>"
echo "Process in tmux session: $session"
# attach later (read-only)
tmux attach-session -t "$session" -r

Container tips: - Ensure tmux is installed in the container. - Mount a tmux socket for host attachment (compose example): - Add tmpfs: - /tmp/tmux and run tmux with -S /tmp/tmux/default. - From host: tmux -S /tmp/tmux-<container-id>/default attach -t <session>.

Testing patterns and helpers

  • Use dependency injection to pass mocks/stubs into use cases and infra.
  • Prefer factories from service/src/__tests__/helpers/factories.ts for domain objects, payloads, and DB rows.
  • Mocks:
  • mockDb.ts: captures queries, returns queued rows/results.
  • mockRedis.ts: TTL/incr behavior with controllable time.
  • mockProcess.ts: simulates process spawning, signals, and payload file lifecycle.
  • mockGithub.ts / mockReply.ts: stub external HTTP interactions.
  • mockSentry.ts: captures telemetry calls without network.
  • Arrange-Act-Assert: keep tests structured; isolate side effects; prefer explicit expectations over broad snapshots.

Reference commands

  • Start service (hot reload): npm run dev
  • Start built server: npm run build && npm start
  • Format code: npm run format
  • Clean test cache (Vitest): npx vitest --clearCache

Notes

  • Keep secrets in .env; never commit them.
  • DB/Redis credentials in this doc are placeholders; update to match your environment.
  • The runner expects SSH keys and Codex CLI accessible in the environment (Compose mounts ~/.ssh and /usr/local/bin/codex).
  • Operational behaviors (pauses, retries, rate limits) are documented in operations.md.