# Overstory Project-agnostic swarm system for Claude Code agent orchestration. Overstory turns a single Claude Code session into a multi-agent team by spawning worker agents in isolated git worktrees, coordinating them through a custom SQLite mail system, and merging their work back with tiered conflict resolution. New projects ship with Claude workers headless by default — `ov serve`'s web UI is the primary operator surface, and `tmux attach` is the opt-in escape hatch for live steering. **Your Claude Code session IS the orchestrator.** There is no separate daemon. CLAUDE.md + hooks + the `ov` CLI provide everything. ## Tech Stack - **Runtime:** Bun (runs TypeScript directly, no build step) - **Language:** TypeScript with strict mode (`noUncheckedIndexedAccess`, no `any`) - **Linting:** Biome (formatter + linter in one tool) - **Runtime dependencies:** `chalk` (v5, ESM-only color output), `@os-eco/mulch-cli` (programmatic API for record/search/query). Core I/O uses Bun built-in APIs (`bun:sqlite`, `Bun.spawn`, `Bun.file`, etc.) - **CLI framework:** Commander.js (typed options, subcommands, auto-generated help) - **Dev dependencies:** `@types/bun`, `typescript`, `@biomejs/biome` - **External CLIs (not npm deps):** `bd` (beads) or `sd` (seeds) for issue tracking, `mulch` for expertise, `git`, `tmux` ## Architecture ### Orchestrator Model When you open Claude Code in a project with `.overstory/` initialized: 1. `SessionStart` hook runs `ov prime` (loads config, recent activity, mulch expertise) 2. `UserPromptSubmit` hook runs `ov mail check --inject` (surfaces new messages from agents) 3. You use the `ov` CLI via Bash tool to spawn agents, check status, merge work ### Agent Definitions: Library Base + Dynamic Overlay Each agent gets two instruction layers: - **Layer 1 (Base):** Reusable `.md` files in `agents/` defining the HOW (workflow, constraints, capabilities) - **Layer 2 (Overlay):** Per-task `CLAUDE.md` generated by `ov sling`, written to worktree, defining the WHAT (task ID, file scope, spec path, branch name) The orchestrator (or a team lead) only passes WHAT. The base definition already has HOW. ### Hierarchical Delegation ``` Orchestrator (your Claude Code session) --> Team Lead (Claude Code in tmux, can spawn sub-workers) --> Specialist Workers (Claude Code in tmux, leaf nodes) ``` Depth limit is configurable (default 2). Prevents runaway spawning. ### Runtime Modes: headless vs tmux (Claude Code) Claude Code agents can run in two modes. **Headless is the shipped default for new projects** (`ov init` writes `runtime.claudeHeadlessByDefault: true`); tmux is the escape hatch for live attach. Legacy projects on upgrade keep tmux until they edit their config — the resolver fallback at `src/commands/sling.ts:499` is unchanged. | Mode | Spawn path | I/O | Visibility | When to use | |------|------------|-----|------------|-------------| | **headless** (default, new projects) | `src/agents/turn-runner.ts` -> `Bun.spawn` per turn (spawn-per-turn for task-scoped workers); `src/worktree/process.ts` -> `Bun.spawn` for long-lived headless capabilities (coordinator/orchestrator/monitor) | NDJSON stream-json on stdout (per-turn log dir for workers; single log file for long-lived) | `ov serve` web UI (primary), `ov logs --agent `, `ov feed` | Default. UI-driven swarms, CI environments, containers without tmux/DBus, structured per-tool-call event fidelity. | | **tmux** (opt-in escape hatch) | `src/worktree/tmux.ts` -> `tmux new-session` | Pane content (capture-pane) | `tmux attach` from operator shell | Operator wants to attach and watch / steer a single agent mid-session. Legacy projects pre-default-flip. | Under headless mode, **task-scoped workers (builder, scout, reviewer, merger, lead) use the spawn-per-turn engine** (overstory-2cf9 / Phase 3). There is no long-lived process between turns: each user turn (initial dispatch, mail batch, nudge) spawns a fresh claude with `--resume ` via `src/agents/turn-runner.ts`, writes the turn to a real stdin pipe, drains stream-json into `events.db`, and exits on EOF. The runner observes the agent's terminal mail (`worker_done` for builder/scout/reviewer/lead; `merged`/`merge_failed` for merger) and transitions the session to `completed`. Persistent capabilities (coordinator, orchestrator, monitor) keep their long-lived process and continue to use `src/worktree/process.ts`. Selection is per-spawn: - `ov sling --no-headless ` — force tmux for this spawn (override the new default). - `ov sling --headless ` — force headless (override a project still set to tmux). - Otherwise, the project default applies: `runtime.claudeHeadlessByDefault` in `.overstory/config.yaml` controls the default (`true` for new projects). When the field is absent — the case for legacy projects upgrading from earlier overstory versions — the resolver falls back to tmux. The flag is a no-op for runtimes that statically declare `headless: true` (e.g. Sapling). Passing `--headless` with a runtime that has no `buildDirectSpawn` (Codex, Pi, Cursor) is rejected with a `ValidationError`. Both modes write to the same `EventStore` and `SessionStore`, so `ov status`, `ov dashboard`, `ov inspect`, and `ov feed` work identically across both. ### Messaging: Custom SQLite Mail Purpose-built messaging via `bun:sqlite` in `.overstory/mail.db`. WAL mode for concurrent access from multiple agents. ~1-5ms per query. Independent of beads (which is too slow for high-frequency polling). ## Directory Structure ``` overstory/ # This repo (the overstory tool itself) src/ index.ts # CLI entry point (Commander.js program, 38 commands) types.ts # ALL shared types and interfaces config.ts # Config loader + defaults + validation errors.ts # Custom error types (extend OverstoryError) json.ts # Standardized JSON envelope helpers (jsonOutput/jsonError) commands/ # One file per CLI subcommand agents.ts # ov agents (discover) init.ts # ov init sling.ts # ov sling (spawn worker) prime.ts # ov prime status.ts # ov status dashboard.ts # ov dashboard (live TUI) inspect.ts # ov inspect (deep agent view) coordinator.ts # ov coordinator start/stop/status/send/ask/output/check-complete supervisor.ts # ov supervisor start/stop/status [DEPRECATED] hooks.ts # ov hooks install/uninstall/status mail.ts # ov mail send/check/list/read/reply/purge nudge.ts # ov nudge (tmux text nudge) merge.ts # ov merge spec.ts # ov spec write group.ts # ov group create/status/add/remove/list clean.ts # ov clean (nuclear cleanup) doctor.ts # ov doctor (health checks) worktree.ts # ov worktree list/clean log.ts # ov log (hook target) logs.ts # ov logs (NDJSON log query) feed.ts # ov feed (unified event stream) watch.ts # ov watch (watchdog) monitor.ts # ov monitor start/stop/status (Tier 2) trace.ts # ov trace (event timeline) errors.ts # ov errors (aggregated error view) replay.ts # ov replay (multi-agent replay) run.ts # ov run list/show/complete stop.ts # ov stop (terminate agent) costs.ts # ov costs (token/cost analysis) metrics.ts # ov metrics ecosystem.ts # ov ecosystem (os-eco tool dashboard) update.ts # ov update (refresh managed files) upgrade.ts # ov upgrade (npm version upgrades) discover.ts # ov discover (brownfield codebase discovery) orchestrator.ts # ov orchestrator (multi-repo coordination) completions.ts # ov --completions (shell completions) serve.ts # ov serve (HTTP + WebSocket surface for the UI) serve/ # REST handlers (rest.ts), WebSocket broadcaster (ws.ts), static SPA fallback (static.ts) canopy/ client.ts # Canopy client (prompt rendering, listing, emission) agents/ # Agent lifecycle management manifest.ts # Agent registry (load + query capabilities) overlay.ts # Dynamic CLAUDE.md overlay generator identity.ts # Persistent agent identity (CVs) hooks-deployer.ts # Deploy hooks config to worktree copilot-hooks-deployer.ts # Deploy hooks config to Copilot worktrees guard-rules.ts # Shared guard constants (tool lists, bash patterns) lifecycle.ts # Session handoff (checkpoint/resume/complete) checkpoint.ts # Session checkpoint save/load/clear mail-poll-detect.ts # Bash mail-poll pattern detector (runtime backstop) scope-detect.ts # Soft FILE_SCOPE violation detection (builder/merger) worktree/ manager.ts # Create/list/cleanup git worktrees via Bun.spawn tmux.ts # Tmux session management via Bun.spawn process.ts # Headless subprocess management (non-tmux runtimes) sessions/ store.ts # SQLite SessionStore + RunStore (agent lifecycle, runs) compat.ts # Migration bridge from sessions.json to sessions.db events/ store.ts # SQLite EventStore (tool events, timelines, errors) tool-filter.ts # Smart arg filtering for event storage tailer.ts # NDJSON event tailer for headless agent stdout logs insights/ analyzer.ts # Session insight analyzer for auto-expertise quality-gates.ts # Run quality gates at session-end -> success/partial/failure tracker/ types.ts # TrackerClient interface, TrackerIssue, TrackerBackend factory.ts # createTrackerClient(), resolveBackend(), trackerCliName() beads.ts # Beads (bd) backend adapter seeds.ts # Seeds (sd) backend adapter mail/ store.ts # SQLite mail storage (bun:sqlite, WAL mode) client.ts # Mail operations (send/check/list/read/reply) broadcast.ts # Group address resolution (@all, @builders, etc.) runtimes/ types.ts # AgentRuntime interface + supporting types (incl. RuntimeConnection, RpcProcessHandle) registry.ts # Runtime registry (getRuntime() factory) claude.ts # Claude Code runtime adapter pi.ts # Pi runtime adapter (Mario Zechner's Pi coding agent) pi-guards.ts # Pi guard extension generator (.pi/extensions/) copilot.ts # GitHub Copilot runtime adapter codex.ts # OpenAI Codex runtime adapter (headless, OS-level sandbox) gemini.ts # Gemini CLI runtime adapter (Google's gemini coding agent) sapling.ts # Sapling runtime adapter (headless coding agent) opencode.ts # OpenCode runtime adapter (SST OpenCode coding agent) cursor.ts # Cursor CLI runtime adapter (Cursor's `agent` binary) aider.ts # Aider runtime adapter (Paul Gauthier's AI pair programmer) goose.ts # Goose runtime adapter (Block's AI developer agent) amp.ts # Amp runtime adapter (Sourcegraph's AI coding agent) connections.ts # Module-level RuntimeConnection registry for RPC agents mulch/ client.ts # mulch client (programmatic API for record/search/query, CLI wrapper for rest) merge/ queue.ts # FIFO merge queue resolver.ts # Tiered conflict resolution (4 tiers) lock.ts # Sentinel-file lock (prevents concurrent ov merge against same target) predict.ts # Side-effect-free conflict prediction for ov merge --dry-run watchdog/ daemon.ts # Tier 0: mechanical process monitoring triage.ts # Tier 1: AI-assisted failure classification health.ts # Health check definitions + state machine logging/ logger.ts # Multi-format logger (human + NDJSON) sanitizer.ts # Secret redaction reporter.ts # Console reporter (ANSI colors) color.ts # Central color control (NO_COLOR, --quiet) theme.ts # Canonical visual theme (state colors, event labels, separators) format.ts # Shared formatting (duration, timestamps, agent colors) metrics/ store.ts # SQLite metrics storage summary.ts # Metrics reporting pricing.ts # Runtime-agnostic pricing + cost estimation transcript.ts # Claude Code transcript JSONL parser doctor/ # Modular health check system *.ts # 13 check categories (see `ov doctor --help`) utils/ bin.ts # Resolve overstory binary for re-launch fs.ts # Filesystem cleanup (SQLite wipe, JSON reset, directory clear) pid.ts # PID file read/write/remove time.ts # Parse relative time formats (1h, 30m, 2d, 10s) version.ts # Version detection (current, npm registry, CLI tools) agents/ # Base agent definitions (the HOW) scout.md # Read-only exploration (leaf, depth 2) builder.md # Implementation (leaf, depth 2) reviewer.md # Read-only validation (leaf, depth 2) merger.md # Branch merge specialist (leaf, depth 2) lead.md # Team lead (can spawn sub-workers, depth 1) supervisor.md # Per-project supervisor (can spawn, depth 1) [DEPRECATED] coordinator.md # Top-level orchestrator (spawns leads only, depth 0) orchestrator.md # Multi-repo coordinator of coordinators (no worktree) monitor.md # Tier 2 continuous fleet patrol (no worktree) templates/ CLAUDE.md.tmpl # Template for orchestrator CLAUDE.md overlay.md.tmpl # Template for per-worker overlay hooks.json.tmpl # Template for settings.local.json copilot-hooks.json.tmpl # Template for Copilot hooks config # Tests colocated: src/config.test.ts, src/mail/store.test.ts, etc. ``` ### What `ov init` creates in a target project ``` target-project/ .overstory/ config.yaml # Project configuration config.local.yaml # Machine-specific overrides (gitignored) agent-manifest.json # Agent registry hooks.json # Central hooks config current-run.txt # Active run ID session-branch.txt # Branch at session start (merge target default) README.md # Contributor-facing directory explanation merge-queue.db # FIFO merge queue (SQLite, WAL mode) agents/{name}/ # Agent state + identity identity.yaml # Persistent agent CV checkpoint.json # Session checkpoint for recovery worktrees/{agent-name}/ # Git worktrees (gitignored) specs/{task-id}.md # Task specifications logs/{agent-name}/{ts}/ # Agent logs (gitignored) mail.db # SQLite mail (gitignored, WAL mode) sessions.db # SQLite sessions + runs (gitignored, WAL mode) events.db # SQLite events/timelines (gitignored, WAL mode) metrics.db # SQLite metrics (gitignored, WAL mode) ``` ## Coding Conventions ### Formatting - **Tab indentation** (enforced by Biome) - **100 character line width** (enforced by Biome) - Biome handles import organization automatically ### TypeScript - Strict mode with `noUncheckedIndexedAccess` -- always handle possible `undefined` from indexing - `noExplicitAny` is an error -- use `unknown` and narrow, or define proper types - `useConst` is enforced -- use `const` unless reassignment is needed - `noNonNullAssertion` is a warning -- avoid `!` postfix, check for null/undefined instead - All shared types and interfaces go in `src/types.ts` - All error types go in `src/errors.ts` and must extend `OverstoryError` base class ### Dependencies - **Minimal runtime dependencies.** Only `chalk` (color output), `commander` (CLI framework), and `@os-eco/mulch-cli` (programmatic expertise API) are allowed as runtime deps. - Use Bun built-in APIs: `bun:sqlite` for databases, `Bun.spawn` for subprocesses, `Bun.file` for file I/O, `Bun.write` for writes - External tools (`bd`, `mulch`, `git`, `tmux`) are invoked as subprocesses via `Bun.spawn`, never as npm imports - Dev dependencies are limited to types and tooling ### File Organization - Each CLI command gets its own file in `src/commands/` - Each subsystem gets its own directory under `src/` (agents, worktree, beads, mail, etc.) - Base agent definitions (`.md` files) live in `agents/` at the repo root - Templates live in `templates/` at the repo root - Tests are colocated with source files (e.g., `src/config.test.ts`, `src/mail/store.test.ts`) ### Subprocess Execution All external commands run through `Bun.spawn`. Capture stdout/stderr, check exit codes, throw typed errors on failure. ```typescript const proc = Bun.spawn(["git", "worktree", "add", path, "-b", branch], { cwd: repoRoot, stdout: "pipe", stderr: "pipe", }); const exitCode = await proc.exited; if (exitCode !== 0) { const stderr = await new Response(proc.stderr).text(); throw new WorktreeError(`Failed to create worktree: ${stderr}`); } ``` ### SQLite All SQLite uses `bun:sqlite` (synchronous API). Always enable WAL mode and busy timeout for concurrent access: ```typescript import { Database } from "bun:sqlite"; const db = new Database(dbPath); db.exec("PRAGMA journal_mode=WAL"); db.exec("PRAGMA busy_timeout=5000"); ``` ## CLI Command Reference ### Core Workflow ``` ov init Initialize .overstory/ and bootstrap os-eco ecosystem tools --yes, -y Skip interactive prompts --name Set project name (default: auto-detect) --tools Comma-separated list of tools to bootstrap (default: mulch,seeds,canopy) --skip-mulch Skip mulch bootstrap --skip-seeds Skip seeds bootstrap --skip-canopy Skip canopy bootstrap --skip-onboard Skip CLAUDE.md onboarding step for ecosystem tools --json JSON output ov sling Spawn a worker agent --capability builder | scout | reviewer | lead | merger --name Unique agent name (auto-generated if omitted) --spec Path to task spec file --files Exclusive file scope (comma-separated) --siblings Parallel sibling agent names (renders rebase-before-merge_ready guidance) --parent Parent (for hierarchy tracking) --depth Current hierarchy depth (default: 0) --skip-scout Skip scout phase (passed to lead overlay) --skip-review Skip review phase for lead agents --max-agents Max children per lead (overrides config) --dispatch-max-agents Per-lead max agents ceiling (injected into overlay) --skip-task-check Skip task existence validation --no-scout-check Suppress scout-before-build warning --force-hierarchy Bypass hierarchy validation (debugging only) --runtime Runtime adapter (default: config or claude) --base-branch Base branch for worktree creation (default: current HEAD) --profile Named profile for canopy prompt overlay --headless Force headless (non-tmux) spawn for runtimes with buildDirectSpawn (Claude Code) --no-headless Force tmux spawn (overrides runtime.claudeHeadlessByDefault) --json JSON output ov discover Discover a brownfield codebase via coordinator-driven scout swarm --skip Skip categories (comma-separated: architecture,dependencies,testing,apis,config,implicit) --name Coordinator agent name (default: discover-coordinator) --task-id Task ID (unused — kept for backward compatibility) --attach / --no-attach Control tmux attach (default: attach on TTY) --watchdog Auto-start watchdog daemon with coordinator --json JSON output ov stop Terminate a running agent --clean-worktree Remove the agent's worktree (best-effort) --json JSON output ov prime Load context for orchestrator/agent --agent Per-agent priming --compact Less context (for PreCompact hook) ov spec write Write a spec file to .overstory/specs/ --body Spec content (or pipe via stdin) --agent Agent attribution ov update Refresh .overstory/ managed files from installed package --agents Only refresh agent definitions --manifest Only refresh agent-manifest.json --hooks Only refresh hooks.json --dry-run Show what would change without writing --json JSON output Global flags (available on all commands): --json JSON output --verbose Verbose output --quiet, -q Suppress non-error output --timing Print command execution time --project Target project root (overrides auto-detection) ``` ### Coordination Agents ``` ov coordinator Persistent coordinator agent start Start coordinator (spawns Claude Code at root) --attach / --no-attach Control tmux attach (default: attach on TTY) --watchdog Auto-start watchdog daemon --monitor Auto-start Tier 2 monitor agent --profile Named profile for canopy prompt overlay stop Stop coordinator (kills tmux session) status Show coordinator state send Fire-and-forget message to coordinator (mail + auto-nudge) --subject Message subject (required) ask Synchronous request/response to coordinator --subject Message subject (required) --timeout Reply timeout (default: 120) output Show recent coordinator output (tmux pane content) --lines Number of lines to capture (default: 100) check-complete Evaluate exit triggers, return completion status --json JSON output ov orchestrator Multi-repo coordinator of coordinators start Start orchestrator (spawns Claude Code at root) --attach / --no-attach Control tmux attach (default: attach on TTY) --watchdog Auto-start watchdog daemon --profile Named profile for canopy prompt overlay stop Stop orchestrator (kills tmux session) status Show orchestrator state send Fire-and-forget message to orchestrator (mail + auto-nudge) --subject Message subject (required) ask Synchronous request/response to orchestrator --subject Message subject (required) --timeout Reply timeout (default: 120) output Show recent orchestrator output (tmux pane content) --lines Number of lines to capture (default: 100) --json JSON output ov supervisor [DEPRECATED] Per-project supervisor agent start Start supervisor --task Task ID (required) --name Unique name (required) --parent Parent agent (default: coordinator) --depth Hierarchy depth (default: 1) stop --name Stop supervisor status [--name ] Show supervisor(s) state --json JSON output ``` ### Messaging ``` ov mail send Send a message --to --subject --body --from Sender name --type Semantic: status|question|result|error Protocol: worker_done|merge_ready|merged| merge_failed|escalation|health_check| dispatch|assign --priority --payload Structured JSON payload --json JSON output ov mail check Check inbox (unread messages) --agent --inject --json ov mail list List messages with filters --from --to --unread --json ov mail read Mark message as read ov mail reply --body Reply in same thread ov mail purge Delete old messages --all | --days | --agent ov nudge [message] Send a text nudge to an agent via tmux --from Sender name (default: orchestrator) --force Skip debounce check --json JSON output ``` ### Merge ``` ov merge Merge agent branches into canonical --branch Specific branch --all All completed branches --into Target branch (default: session-branch.txt > canonicalBranch) --dry-run Check for conflicts only (predicts resolution tier + conflict files) --json JSON output ``` ### Task Groups ``` ov group Batch coordination create '' [id2...] Create a new task group status [group-id-or-name] Show progress for one or all groups add [id2...] Add issues to a group remove [...] Remove issues from a group list List all groups (summary) --json --skip-validation JSON output / skip beads checks ``` ### Observability ``` ov status Show all active agents, worktrees, state --json --verbose JSON output / extra per-agent detail --all Show all runs (default: current run only) ov dashboard Live TUI dashboard for agent monitoring --interval Poll interval (default: 2000, min: 500) --all Show all runs (default: current run only) ov inspect Deep inspection of a single agent --follow Poll and refresh continuously --interval Polling interval (default: 3000) --limit Recent tool calls to show (default: 20) --no-tmux Skip tmux capture-pane --json JSON output ov trace Chronological event timeline for agent or task --since --until Time range filter (ISO 8601) --limit Max events (default: 100) --json JSON output ov errors Aggregated error view across agents --agent --run Filter by agent or run --since --until Time range filter (ISO 8601) --limit Max errors (default: 100) --json JSON output ov replay Interleaved chronological replay across agents --run --agent Filter by run or agent (repeatable) --since --until Time range filter (ISO 8601) --limit Max events (default: 200) --json JSON output ov run [sub] Manage runs (coordinator session groupings) (default) Show current run status list [--last ] List recent runs (default: 10) show Show run details (agents, duration) complete Mark current run as completed --json JSON output ov feed [options] Unified real-time event stream across agents --follow, -f Continuously poll for new events --interval Polling interval (default: 2000) --agent --run Filter by agent or run --since Start time (ISO 8601) --limit Max initial events --json JSON output ov logs [options] Query NDJSON logs across agents --agent Filter by agent --level Filter by log level (debug|info|warn|error) --since --until Time range filter (ISO 8601 or relative) --limit Max entries --follow Tail logs in real time --json JSON output ov costs Token/cost analysis and breakdown --live Show real-time token usage for active agents --self Show cost for the current orchestrator session --agent --run Filter by agent or run --bead Show cost breakdown for a specific task/bead --by-capability Group by capability with subtotals --last Recent sessions (default: 20) --json JSON output ov metrics Show session metrics --last --json ``` ### Infrastructure ``` ov hooks Manage orchestrator hooks install Install hooks to .claude/settings.local.json --force Overwrite existing hooks uninstall Remove hooks status Check if hooks are installed --json JSON output ov worktree list List worktrees with status ov worktree clean Remove completed worktrees --completed Only finished agents --all Force remove all --force Delete even if branches are unmerged ov log Log a hook event (called by hooks) --agent Events: tool-start, tool-end, session-end ov watch Start watchdog daemon (Tier 0) --interval --background ov monitor Manage Tier 2 monitor agent start Start monitor (spawns Claude Code at root) stop Stop monitor (kills tmux session) status Show monitor state ov doctor Run health checks on overstory setup --category Run one category only --fix Auto-fix fixable issues --verbose Show passing checks too --json JSON output Categories: dependencies, config, structure, databases, consistency, agents, merge, logs, version, ecosystem, providers, watchdog, serve ov ecosystem Show os-eco tool versions and health --json JSON output ov upgrade Upgrade overstory to latest npm version --check Compare versions without installing --all Upgrade all 4 ecosystem tools --json JSON output ov clean Wipe runtime state (nuclear cleanup) --agent Targeted cleanup of a single agent --all Wipe everything --mail --sessions --metrics Individual DB cleanup --logs --worktrees --branches Individual resource cleanup --agents --specs Individual state cleanup --json JSON output ov serve HTTP + WebSocket surface for the web UI --port Port (default: 7321) --host Bind host (default: 127.0.0.1) --json JSON output Routes: /healthz, /api/runs, /api/agents, /api/events, /api/mail, /ws (per-run/per-agent/mail rooms), SPA fallback to ui/dist ``` ## Testing - **Framework:** `bun test` (built-in, Jest-compatible API) - **Test location:** Tests colocated with source files (e.g., `src/config.test.ts`, `src/mail/store.test.ts`) - **Naming:** `{module}.test.ts` matching the source file name - **Run tests:** `bun test` - **Run single test:** `bun test src/config.test.ts` ### Philosophy: Never mock what you can use for real Prefer real implementations over mocks. Mocks are a last resort, not a default. **Use real implementations for:** - **Filesystem:** Use temp directories (`mkdtemp`) for file I/O tests - **SQLite:** Use temp files or `:memory:` databases for `bun:sqlite` tests - **Git:** Use real git repos in temp directories for worktree/merge tests - **CLI tools:** Use real `bd` CLI for beads-client tests (when available) **Only mock when the real thing has unacceptable side effects:** - **tmux:** Real tmux operations interfere with developer sessions and are fragile in CI - **External AI services:** API calls with real costs and latency - **Network requests:** Flaky in CI, may incur costs When mocking is truly necessary, document WHY in a comment at the top of the test file. See mulch record `mx-56558b` for background on why `mock.module()` should be avoided (it leaks across test files). ### Test Helpers Shared test utilities live in `src/test-helpers.ts`: - `createTempGitRepo()` -- Initialize a real git repo in a temp dir with initial commit - `cleanupTempDir()` -- Remove temp directories - `commitFile()` -- Add and commit a file to a test repo ## Tool Integration ### Task Tracker (bd/sd) -- Issue Tracking ```bash bd ready # Find available work (beads backend) sd ready # Find available work (seeds backend) bd show # View issue details bd update --status in_progress # Claim work bd close --reason "summary" # Complete work bd sync # Sync with git ``` Issues are tracked by the configured task tracker backend (beads or seeds). Overstory wraps the tracker CLI via `src/tracker/` with a pluggable backend system — `resolveBackend()` auto-detects which tracker is available. ### mulch -- Structured Expertise ```bash mulch prime [domain] # Output priming prompt mulch status # Show domain statistics mulch record # Record expertise mulch search [query] # Search across domains ``` Expertise records live in `.mulch/`. Overstory wraps `mulch` via `src/mulch/client.ts`. ## Quality Gates Run all three before committing: ```bash bun test # Tests pass biome check . # Linting + formatting clean tsc --noEmit # Type checking passes ``` Or use the package.json scripts: ```bash bun run test # bun test bun run lint # biome check . bun run typecheck # tsc --noEmit ``` ## Session Completion Protocol When ending a work session, you MUST: 1. File issues for remaining work (`bd create`) 2. Run quality gates (if code changed): `bun test && biome check . && tsc --noEmit` 3. Update issue status: close finished work, update in-progress items 4. Push to remote (MANDATORY): ```bash git pull --rebase bd sync git push git status # MUST show "up to date with origin" ``` 5. Verify all changes are committed AND pushed 6. Hand off context for the next session Work is NOT complete until `git push` succeeds. ## Project Expertise (Mulch) This project uses [Mulch](https://github.com/jayminwest/mulch) for structured expertise management. **At the start of every session**, run: ```bash ml prime ``` Injects project-specific conventions, patterns, decisions, failures, references, and guides into your context. Run `ml prime --files src/foo.ts` before editing a file to load only records relevant to that path (per-file framing, classification age, and confirmation scores included). For monolith projects where dumping every record wastes context, set `prime.default_mode: manifest` in `.mulch/mulch.config.yaml` (or pass `--manifest`) to emit a quick reference + domain index. Agents then scope-load with `ml prime ` or `ml prime --files `. **Before completing your task**, record insights worth preserving — conventions discovered, patterns applied, failures encountered, or decisions made: ```bash ml record --type --description "..." ``` Evidence auto-populates from git (current commit + changed files). Link explicitly with `--evidence-seeds ` / `--evidence-gh ` / `--evidence-linear ` / `--evidence-bead `, `--evidence-commit `, or `--relates-to `. Upserts of named records merge outcomes instead of replacing them; validation failures print a copy-paste retry hint with missing fields pre-filled. Run `ml status` for domain health, `ml doctor` to check record integrity (add `--fix` to strip broken file anchors), `ml --help` for the full command list. Write commands use file locking and atomic writes, so multiple agents can record concurrently. Expertise survives `git worktree` cleanup — `.mulch/` resolves to the main repo. ### Before You Finish 1. Discover what to record (shows changed files and suggests domains): ```bash ml learn ``` 2. Store insights from this work session: ```bash ml record --type --description "..." ``` 3. Validate and commit: ```bash ml sync ``` ## Issue Tracking (Seeds) This project uses [Seeds](https://github.com/jayminwest/seeds) for git-native issue tracking. **At the start of every session**, run: ``` sd prime ``` This injects session context: rules, command reference, and workflows. **Quick reference:** - `sd ready` — Find unblocked work - `sd create --title "..." --type task --priority 2` — Create issue - `sd update --status in_progress` — Claim work - `sd close ` — Complete work - `sd dep add ` — Add dependency between issues - `sd sync` — Sync with git (run before pushing) ### Before You Finish 1. Close completed issues: `sd close ` 2. File issues for remaining work: `sd create --title "..."` 3. Sync and push: `sd sync && git push` ## Prompt Management (Canopy) This project uses [Canopy](https://github.com/jayminwest/canopy) for git-native prompt management. **At the start of every session**, run: ``` cn prime ``` This injects prompt workflow context: commands, conventions, and common workflows. **Quick reference:** - `cn list` — List all prompts - `cn render ` — View rendered prompt (resolves inheritance) - `cn emit --all` — Render prompts to files - `cn update ` — Update a prompt (creates new version) - `cn sync` — Stage and commit .canopy/ changes **Do not manually edit emitted files.** Use `cn update` to modify prompts, then `cn emit` to regenerate.