Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.4 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Commands
npm install # install dependencies
npm run typecheck # tsc --noEmit (type check without build)
npm run build # compile TypeScript → dist/ (also copies schema.sql)
npm run dev # start daemon on http://127.0.0.1:4517
npm test # run all tests (node --test via tsx)
tsx --test test/<file>.test.ts # run a single test file
Worker processes can be run against TypeScript source directly (for dev/testing) via:
MAESTRO_WORKER_CMD="tsx src/executor/worker.ts" npm run dev
Architecture
Maestro is a local-first daemon that manages tasks across multiple git projects. It drives tasks through a complexity-based approval pipeline and auto-executes approved tasks via headless Claude Code agents.
Process Model (critical to understand)
The system is split into two strictly separated process roles:
daemon (src/daemon/) — the only DB writer. Responsible for:
- Serving REST + WebSocket API (
src/api/) - Running the orchestrator loop (tick =
ingest → reap → claim) - Spawning one worker OS process per task
- Ingesting worker output from
outbox.ndjsoninto SQLite - Reaping dead workers, managing retries/back-off, state transitions
worker (src/executor/worker.ts + src/executor/pipeline.ts) — a dumb executor that never touches the DB. Each task gets its own node dist/executor/worker.js <runId>. It reads only runs/<runId>/job.json and appends progress/results to runs/<runId>/outbox.ndjson. The daemon ingests this file idempotently via a lastSeq cursor.
Communication between daemon and worker is file + signal only (job.json, outbox.ndjson, heartbeat, SIGTERM). This makes execution survive daemon restarts.
Key Source Modules
| Path | Purpose |
|---|---|
src/model/ |
Types (types.ts), state machine (status.ts), complexity levels (complexity.ts), score scheduling algorithm (scoring.ts) |
src/store/ |
SQLite access layer (store.ts), schema (schema.sql), state-machine guards, mappers |
src/daemon/orchestrator.ts |
Score-based task scheduling loop: ingest → reap → claim |
src/daemon/ingest.ts |
Reads worker outbox.ndjson, writes DB events, triggers WS push |
src/executor/protocol.ts |
File protocol definitions + heartbeat/liveness helpers |
src/executor/pipeline.ts |
Worker execution pipeline: worktree setup → agent run → verify → dual review → output |
src/executor/cc.ts |
Claude Agent SDK wrapper for headless CC runs |
src/executor/reviewer.ts |
Code review + security audit agent runs |
src/executor/merge.ts |
--no-ff auto-merge logic on exec_review accept |
src/api/server.ts |
Fastify REST + WebSocket server |
src/mcp/ |
MCP server tools exposed to Claude Code sessions |
src/sync/ |
One-way sync from legacy todo.json files |
app/ |
Active console (the dashboard users actually use) — React SPA on :4519 (vite dev; proxies /api+/ws to :4517). Data & wiring live here: src/api.js, src/app.jsx, src/adapt.js. |
design/ui_kits/console/ |
Active console UI — React UI components, the single source of truth for the console; app/ loads them via vite. Frontend UI changes go here; data/actions go in app/src/. Strings go through i18n.js (5 langs). |
web/ |
⚠️ Legacy / deprecated — old vanilla-JS dashboard served directly by the daemon. Superseded by the app/ + design/ui_kits/console/ React console. Do NOT add new features, config entries, or UI here — build them in app/ + design/ui_kits/console/. |
Frontend rule of thumb: the live console is
design/ui_kits/console/(UI) +app/src/(data/wiring) on:4519.web/is legacy — never target it for new work.
Task Lifecycle
init → [Hard: analyzing → plan_review] → [Medium: speccing → spec_review] → ready
→ queued → executing → exec_review → done
- Hard: planner agent produces analysis + task decomposition; requires
plan_reviewapproval - Medium: agent writes a spec (what + why); requires
spec_reviewapproval - Easy: agent writes operations record; goes directly to
ready - All tasks with code changes pass through
exec_review(dual review: code-review + security audit) acceptatexec_reviewtriggers automatic--no-ffmerge;merge:falseskips merge
Score Scheduling
score = P(self) + Σ P(completed deps) + Σ P(tasks blocked by self)
P0=3, P1=2, P2=1. Higher score → picked first. No preemption.
Model Tier Assignment
Built-in default for every role × every complexity is claude-opus-4-8 (DEFAULT_MODEL in src/executor/models.ts). Models are configurable per project via projects.models (JSON).
Resolution order (resolveModel, highest → lowest):
project.models[role]— per-project per-scenario config. Value is either a model id string (applies to all complexities) or{easy,medium,hard}(per-complexity). Applies to all roles incl. reviewer/conflict.- legacy
project.model— single override, executor/planner only (NOT reviewer/conflict, preserving self-review/conflict-resolution independence). - env vars —
MAESTRO_MODEL_{EASY,MEDIUM,HARD}(executor),MAESTRO_MODEL_REVIEW_*(reviewer),MAESTRO_MODEL_PLAN_*(planner). DEFAULT_MODEL=claude-opus-4-8.
Example projects.models:
{"executor":{"easy":"claude-sonnet-4-6","hard":"claude-opus-4-8"},"reviewer":"claude-fable-5"}
Model fallback chain on unavailability: opus-4-8 → sonnet-4-6 → fable-5 (MODEL_FALLBACK_CHAIN). Note: claude-fable-5 is no longer a built-in default (it is unavailable in some environments); configure it explicitly per project if desired.
Data Directory
Default ~/.maestro/ — contains SQLite DB, runs/<runId>/ directories (job.json, outbox.ndjson, heartbeat, sandbox.sb, transcripts), and git worktrees.
Key Env Vars
| Var | Default | Note |
|---|---|---|
MAESTRO_PORT |
4517 |
daemon listen port |
MAESTRO_DATA_DIR |
~/.maestro |
SQLite + worktree root |
MAESTRO_ORCH_INTERVAL |
15 |
orchestrator tick seconds; 0 disables |
MAESTRO_WORKER_CMD |
node dist/executor/worker.js |
override for tsx dev |
MAESTRO_NOTIFY |
on | set 0 to disable macOS notifications |
MAESTRO_SANDBOX |
off | set on for OS-level write fence + ulimits on workers |