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_KEYor fileservice/private-key.pem, and optionalGH_APP_INSTALLATION_ID. - Admin token for the web app: set
ADMIN_API_TOKEN(used by web build args) andVITE_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:
Development workflow¶
- Type checking:
npm run type-check - Lint:
npm run lint(auto-fix withnpm run lint:fix) - Format check:
npm run format:check(write withnpm run format) - Unit tests:
npm run test:unit - Integration tests:
npm run test:integration - For DB-backed tests, set
DATABASE_URL_TESTto a reachable Postgres instance. - Full/coverage:
npm run testornpm 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.tsexercises server startup, health probes, signal-driven shutdown, and failure reporting paths.src/__tests__/integration/app.test.tsvalidates Fastify wiring: middleware (request IDs, rate limits), route registration, Sentry hooks, and error handling.- Helpers added for lifecycle-heavy tests:
src/__tests__/helpers/mockSentry.tsfor Sentry initialization and capture spiessrc/__tests__/helpers/mockProcess.tsfor signal/exit interceptionsrc/__tests__/helpers/mockDb.tsandmockRedis.tsmodule 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/gittinkereror individualPGHOST/PGPORT/PGUSER/PGPASSWORD/PGDATABASE. - Optional pool tuning:
PGPOOL_MAX(default 10). - Migrations auto-run and create
runs,run_metrics,paused_repos; addsuser_id/installation_idcolumns. - Redis
- Set
REDIS_URL=redis://localhost:6379orREDIS_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; optionalSENTRY_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_TOKENSfor cost estimates. - APIs:
GET /api/analytics/usage?group_by=repo,user,day&from=...&to=...andGET /api/analytics/export(CSV). - Web UI
- Set
VITE_SERVICE_URL(defaults tohttp://localhost:3000). - Install deps in
/web, thennpm run dev -- --hostornpm run build. - Service
- Install deps in
/service. - Start with
npm run dev(hot reload) ornpm 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.tsfor 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
~/.sshand/usr/local/bin/codex). - Operational behaviors (pauses, retries, rate limits) are documented in operations.md.