2026-01-18 04:50:46 +01:00
# CLAUDE.md
2026-01-18 07:31:19 +01:00
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
2026-01-18 04:50:46 +01:00
2026-01-31 23:52:44 +01:00
## Quick Reference
| Task | Command |
|------|---------|
| Dev server | `npx tsx src/index.ts web` |
| Type check | `tsc --noEmit` |
2026-02-27 17:54:59 +01:00
| Lint | `npm run lint` (fix: `npm run lint:fix` ) |
| Format | `npm run format` (check: `npm run format:check` ) |
2026-01-31 23:52:44 +01:00
| Single test | `npx vitest run test/<file>.test.ts` |
2026-02-26 16:44:34 +01:00
| Production | `npm run build && systemctl --user restart codeman-web` |
2026-01-31 23:52:44 +01:00
2026-02-12 10:29:57 +01:00
## CRITICAL: Session Safety
2026-01-21 04:44:19 +01:00
2026-02-26 16:44:34 +01:00
**You may be running inside a Codeman-managed tmux session.** Before killing ANY tmux or Claude process:
2026-01-21 04:44:19 +01:00
2026-02-28 01:35:37 +01:00
1. Check: `echo $CODEMAN_MUX` - if `1` , you're in a managed session
2026-02-12 14:41:27 +01:00
2. **NEVER** run `tmux kill-session` , `pkill tmux` , or `pkill claude` without confirming
2026-02-12 10:29:57 +01:00
3. Use the web UI or `./scripts/tmux-manager.sh` instead of direct kill commands
2026-01-21 04:44:19 +01:00
2026-02-15 06:02:19 +01:00
## CRITICAL: Always Test Before Deploying
**NEVER COM without verifying your changes actually work.** For every fix:
1. **Backend changes** : Hit the API endpoint with `curl` and verify the response
2. **Frontend changes** : Use Playwright to load the page and assert the UI renders correctly. Use `waitUntil: 'domcontentloaded'` (not `networkidle` — SSE keeps the connection open). Wait 3-4s for polling/async data to populate, then check element visibility, text content, and CSS values
3. **Only after verification passes** , proceed with COM
The production server caches static files for 1 hour (`maxAge: '1h'` in `server.ts` ). After deploying frontend changes, users may need a hard refresh (Ctrl+Shift+R) to see updates.
2026-01-27 23:15:25 +01:00
## COM Shorthand (Deployment)
2026-01-25 18:09:25 +01:00
2026-02-27 10:58:10 +01:00
Uses [Semantic Versioning ](https://semver.org/ ) (`MAJOR.MINOR.PATCH` ) via `@changesets/cli` .
2026-01-26 21:27:08 +01:00
2026-02-27 10:58:10 +01:00
When user says "COM":
1. **Determine bump type** : `COM` = patch (default), `COM minor` = minor, `COM major` = major
2. **Create a changeset file** (no interactive prompts). Write a `.md` file in `.changeset/` with a random filename:
```bash
cat > .changeset/$(openssl rand -hex 4).md << 'CHANGESET'
---
2026-02-28 02:28:31 +01:00
"aicodeman": patch
2026-02-27 10:58:10 +01:00
---
Description of changes
CHANGESET
` ``
Replace ` patch` with ` minor` or ` major` as needed. Include ` "xterm-zerolag-input": patch` on a separate line if that package changed too.
3. **Consume the changeset**: ` npm run version-packages` (bumps versions in ` package.json` files and updates ` CHANGELOG.md`)
4. **Sync CLAUDE.md version**: Update the ` **Version**` line below to match the new version from ` package.json`
5. **Commit and deploy**: ` git add -A && git commit -m "chore: version packages" && git push && npm run build && systemctl --user restart codeman-web`
2026-02-28 02:02:04 +01:00
**Version**: 0.2.8 (must match ` package.json`)
2026-01-25 18:09:25 +01:00
2026-01-18 04:50:46 +01:00
## Project Overview
2026-01-18 05:17:06 +01:00
2026-02-26 16:44:34 +01:00
Codeman is a Claude Code session manager with web interface and autonomous Ralph Loop. Spawns Claude CLI via PTY, streams via SSE, supports respawn cycling for 24+ hour autonomous runs.
2026-01-18 04:50:46 +01:00
2026-02-27 00:34:38 +01:00
**Tech Stack**: TypeScript (ES2022/NodeNext, strict mode), Node.js, Fastify, node-pty, xterm.js. Supports both Claude Code and OpenCode AI CLIs via pluggable CLI resolvers.
2026-01-28 00:45:37 +01:00
2026-01-31 07:51:29 +01:00
**TypeScript Strictness** (see ` tsconfig.json`): ` noUnusedLocals`, ` noUnusedParameters`, ` noImplicitReturns`, ` noImplicitOverride`, ` noFallthroughCasesInSwitch`, ` allowUnreachableCode: false`, ` allowUnusedLabels: false`. Note: ` src/tui` is excluded from compilation (legacy/deprecated code path).
2026-01-27 00:41:00 +01:00
2026-02-12 14:41:27 +01:00
**Requirements**: Node.js 18+, Claude CLI, tmux
2026-01-20 08:56:15 +01:00
2026-01-18 07:31:19 +01:00
## Commands
2026-01-18 05:17:06 +01:00
2026-02-27 13:16:23 +01:00
**Note**: ` npm run dev` starts the web server (equivalent to ` npx tsx src/index.ts web`).
2026-01-25 04:57:09 +01:00
2026-01-29 16:13:18 +01:00
**Default port**: ` 3000` (web UI at ` http://localhost:3000`)
2026-01-18 05:17:06 +01:00
` ``bash
2026-01-30 17:20:53 +01:00
# Setup
npm install # Install dependencies
2026-01-27 23:15:25 +01:00
# Development
npx tsx src/index.ts web # Dev server (RECOMMENDED)
2026-01-29 11:47:24 +01:00
npx tsx src/index.ts web --https # With TLS (only needed for remote access)
2026-01-28 00:45:37 +01:00
npm run typecheck # Type check
2026-01-30 17:20:53 +01:00
tsc --noEmit --watch # Continuous type checking
2026-02-27 17:54:59 +01:00
npm run lint # ESLint
npm run lint:fix # ESLint with auto-fix
npm run format # Prettier format
npm run format:check # Prettier check only
2026-01-18 05:17:06 +01:00
2026-02-27 17:54:59 +01:00
# Testing (see "Testing" section for CRITICAL safety warnings)
2026-02-19 21:38:01 +01:00
npx vitest run test/<file>.test.ts # Single file (SAFE)
2026-01-28 04:04:40 +01:00
npx vitest run -t "pattern" # Tests matching name
2026-01-28 00:45:37 +01:00
npm run test:coverage # With coverage report
2026-01-25 04:57:09 +01:00
2026-01-27 23:15:25 +01:00
# Production
2026-02-28 01:35:37 +01:00
npm run build # esbuild via scripts/build.mjs (not tsc)
npm run start # node dist/index.js (production)
2026-02-26 16:44:34 +01:00
systemctl --user restart codeman-web
journalctl --user -u codeman-web -f
2026-01-21 23:55:16 +01:00
` ``
2026-02-28 01:35:37 +01:00
**CI**: ` .github/workflows/ci.yml` runs ` typecheck`, ` lint`, and ` format:check` on push to master. Tests are intentionally excluded from CI (they spawn tmux).
2026-01-31 23:52:44 +01:00
## Common Gotchas
2026-02-18 12:51:35 +01:00
- **Single-line prompts only** — ` writeViaMux()` sends text and Enter separately; multi-line breaks Ink
2026-02-28 01:35:37 +01:00
- **Don't kill tmux sessions blindly** — Check ` $CODEMAN_MUX` first; you might be inside one
2026-02-19 08:49:36 +01:00
- **Global regex ` lastIndex` sharing** — ` ANSI_ESCAPE_PATTERN_FULL/SIMPLE` have ` g` flag; use ` createAnsiPatternFull/Simple()` factory functions for fresh instances in loops
- **DEC 2026 sync blocks** — Never discard incomplete sync blocks (START without END); buffer up to 50ms then flush. See ` app.js:extractSyncSegments()`
- **Terminal writes during buffer load** — Live SSE writes are queued while ` _isLoadingBuffer` is true to prevent interleaving with historical data
- **Local echo prompt scanning** — Does NOT use ` buffer.cursorY` (Ink moves it); scans buffer bottom-up for visible ` >` prompt marker
2026-01-31 23:52:44 +01:00
## Import Conventions
2026-02-11 17:29:53 +01:00
- **Utilities**: Import from ` ./utils` (re-exports all): ` import { LRUMap, stripAnsi } from './utils'`
2026-01-31 23:52:44 +01:00
- **Types**: Use type imports: ` import type { SessionState } from './types'`
2026-02-11 17:29:53 +01:00
- **Config**: Import from specific files: ` import { MAX_TERMINAL_BUFFER_SIZE } from './config/buffer-limits'`
2026-01-31 23:52:44 +01:00
2026-01-18 07:31:19 +01:00
## Architecture
2026-01-18 05:17:06 +01:00
2026-01-27 23:15:25 +01:00
### Core Files
2026-01-19 16:45:32 +01:00
2026-01-25 18:09:25 +01:00
| File | Purpose |
|------|---------|
2026-02-28 01:35:37 +01:00
| ` src/index.ts` | CLI entry point: global error recovery, uncaught exception guard, ` MAX_CONSECUTIVE_ERRORS` auto-restart |
2026-01-27 23:15:25 +01:00
| ` src/session.ts` | PTY wrapper: ` runPrompt()`, ` startInteractive()`, ` startShell()` |
2026-02-12 10:29:57 +01:00
| ` src/mux-interface.ts` | ` TerminalMultiplexer` interface + ` MuxSession` type |
2026-02-18 12:55:16 +01:00
| ` src/mux-factory.ts` | Create tmux multiplexer instance |
2026-02-12 14:41:27 +01:00
| ` src/tmux-manager.ts` | tmux session management |
2026-01-27 23:15:25 +01:00
| ` src/session-manager.ts` | Session lifecycle, cleanup |
2026-02-26 16:44:34 +01:00
| ` src/state-store.ts` | State persistence to ` ~/.codeman/state.json` |
2026-01-27 23:15:25 +01:00
| ` src/respawn-controller.ts` | State machine for autonomous cycling |
| ` src/ralph-tracker.ts` | Detects ` <promise>PHRASE</promise>`, todos |
2026-01-31 14:32:20 +01:00
| ` src/ralph-loop.ts` | Autonomous task execution loop (polls queue, assigns tasks) |
| ` src/ralph-config.ts` | Parses ` .claude/ralph-loop.local.md` plugin config |
| ` src/task.ts` | Task model for prompt execution |
| ` src/task-queue.ts` | Priority queue for tasks with dependencies |
| ` src/task-tracker.ts` | Background task tracker for subagent detection |
2026-01-28 05:34:25 +01:00
| ` src/subagent-watcher.ts` | Monitors Claude Code's Task tool (background agents) |
2026-02-12 10:29:57 +01:00
| ` src/team-watcher.ts` | Polls ` ~/.claude/teams/` for agent team activity; matches teams to sessions via ` leadSessionId` |
2026-01-28 05:34:25 +01:00
| ` src/run-summary.ts` | Timeline events for "what happened while away" |
2026-02-12 10:29:57 +01:00
| ` src/ai-checker-base.ts` | Base class for AI-powered checkers (shared by idle + plan checkers) |
| ` src/ai-idle-checker.ts` | AI-powered idle detection |
2026-01-31 14:32:20 +01:00
| ` src/ai-plan-checker.ts` | AI-powered plan completion checker |
2026-01-30 17:20:53 +01:00
| ` src/bash-tool-parser.ts` | Parses Claude's bash tool invocations from output |
| ` src/transcript-watcher.ts` | Watches Claude's transcript files for changes |
| ` src/hooks-config.ts` | Manages ` .claude/settings.local.json` hook configuration |
2026-02-27 08:28:30 +01:00
| ` src/push-store.ts` | VAPID key auto-gen + push subscription CRUD for Web Push |
2026-02-26 16:44:34 +01:00
| ` src/session-lifecycle-log.ts` | Append-only JSONL audit log at ` ~/.codeman/session-lifecycle.jsonl` |
2026-01-30 17:20:53 +01:00
| ` src/image-watcher.ts` | Watches for image file creation (screenshots, etc.) |
2026-01-31 14:32:20 +01:00
| ` src/file-stream-manager.ts` | Manages ` tail -f` processes for live log viewing |
2026-01-30 13:28:30 +01:00
| ` src/plan-orchestrator.ts` | Multi-agent plan generation with research and planning phases |
2026-02-12 10:29:57 +01:00
| ` src/prompts/index.ts` | Barrel export for all agent prompts |
2026-02-19 14:52:39 +01:00
| ` src/prompts/*.ts` | Agent prompts (research-agent, planner) |
2026-01-31 23:52:44 +01:00
| ` src/templates/claude-md.ts` | CLAUDE.md generation for new cases |
2026-02-27 17:54:59 +01:00
| ` src/tunnel-manager.ts` | Manages cloudflared child process for Cloudflare tunnel remote access |
2026-01-31 14:32:20 +01:00
| ` src/cli.ts` | Command-line interface handlers |
2026-02-28 02:22:20 +01:00
| ` src/web/server.ts` | Fastify REST API + SSE at ` /api/events` (~280 routes) |
2026-02-19 08:49:36 +01:00
| ` src/web/schemas.ts` | Zod v4 validation schemas with path/env security allowlists |
2026-02-27 15:29:46 +01:00
| ` src/web/public/app.js` | Frontend: xterm.js, tab management, subagent windows, mobile support (~15K lines) |
2026-02-27 00:34:38 +01:00
| ` src/types.ts` | All TypeScript interfaces (~70 type/interface/enum defs, ~1450 lines) |
2026-01-19 19:56:37 +01:00
2026-02-19 08:49:36 +01:00
**Large files** (>50KB): ` app.js`, ` ralph-tracker.ts`, ` respawn-controller.ts`, ` session.ts`, ` subagent-watcher.ts` — these contain complex state machines; read ` docs/respawn-state-machine.md` before modifying.
2026-02-02 04:49:15 +01:00
2026-02-26 16:04:00 +01:00
### Local Packages
| Package | Purpose |
|---------|---------|
| ` packages/xterm-zerolag-input/` | Instant keystroke feedback overlay for xterm.js — eliminates perceived input latency over high-RTT connections. Source of truth for ` LocalEchoOverlay`; a copy is embedded in ` app.js`. Build: ` npm run build` (tsup). |
2026-01-30 17:20:53 +01:00
### Config Files (` src/config/`)
| File | Purpose |
|------|---------|
| ` buffer-limits.ts` | Terminal/text buffer size limits |
| ` map-limits.ts` | Global limits for Maps, sessions, watchers |
2026-02-28 01:35:37 +01:00
### Utilities (` src/utils/`)
Re-exported via ` src/utils/index.ts`. Key exports:
| File | Exports |
|------|---------|
| ` cleanup-manager.ts` | ` CleanupManager` — centralized disposal for timers, intervals, watchers, listeners, streams |
| ` lru-map.ts` | ` LRUMap` — bounded cache with eviction |
| ` stale-expiration-map.ts` | ` StaleExpirationMap` — TTL-based map with automatic cleanup |
| ` regex-patterns.ts` | ` ANSI_ESCAPE_PATTERN_FULL/SIMPLE`, ` createAnsiPatternFull/Simple()`, ` stripAnsi`, ` TOKEN_PATTERN`, ` SPINNER_PATTERN` |
| ` buffer-accumulator.ts` | ` BufferAccumulator` — batches rapid writes into single flushes |
| ` claude-cli-resolver.ts` | ` findClaudeDir`, ` getAugmentedPath` — resolves Claude CLI paths |
2026-02-28 02:22:20 +01:00
| ` opencode-cli-resolver.ts` | ` resolveOpenCodeDir`, ` isOpenCodeAvailable` — OpenCode CLI support |
2026-02-28 01:35:37 +01:00
| ` string-similarity.ts` | ` stringSimilarity`, ` fuzzyPhraseMatch`, ` todoContentHash` |
| ` token-validation.ts` | ` validateTokenCounts`, ` validateTokensAndCost` |
| ` nice-wrapper.ts` | ` wrapWithNice` — wraps commands with ` nice`/` ionice` for lower priority |
| ` type-safety.ts` | ` assertNever` — exhaustive switch/case guard |
2026-01-18 07:38:19 +01:00
### Data Flow
2026-01-27 23:15:25 +01:00
1. Session spawns ` claude --dangerously-skip-permissions` via node-pty
2. PTY output buffered, ANSI stripped, parsed for JSON messages
3. WebServer broadcasts to SSE clients at ` /api/events`
2026-02-26 16:44:34 +01:00
4. State persists to ` ~/.codeman/state.json` via StateStore
2026-01-18 07:31:19 +01:00
2026-01-27 23:15:25 +01:00
### Key Patterns
2026-01-19 18:34:43 +01:00
2026-02-18 12:51:35 +01:00
**Input to sessions**: Use ` session.writeViaMux()` for programmatic input (respawn, auto-compact). Uses tmux ` send-keys -l` (literal text) + ` send-keys Enter`. All prompts must be single-line.
2026-02-12 10:29:57 +01:00
2026-02-18 12:51:35 +01:00
**Terminal multiplexer**: ` TerminalMultiplexer` interface (` src/mux-interface.ts`) abstracts the backend. ` createMultiplexer()` from ` src/mux-factory.ts` creates the tmux backend.
2026-01-19 18:34:43 +01:00
2026-01-27 23:15:25 +01:00
**Idle detection**: Multi-layer (completion message → AI check → output silence → token stability). See ` docs/respawn-state-machine.md`.
2026-01-22 21:36:43 +01:00
2026-01-27 23:15:25 +01:00
**Token tracking**: Interactive mode parses status line ("123.4k tokens"), estimates 60/40 input/output split.
2026-01-23 10:20:37 +01:00
2026-02-19 08:49:36 +01:00
**Hook events**: Claude Code hooks trigger notifications via ` /api/hook-event`. Key events: ` permission_prompt` (tool approval needed), ` elicitation_dialog` (Claude asking question), ` idle_prompt` (waiting for input), ` stop` (response complete), ` teammate_idle` (Agent Teams), ` task_completed` (Agent Teams). See ` src/hooks-config.ts`.
2026-01-23 10:20:37 +01:00
2026-02-27 08:28:30 +01:00
**Web Push**: Layer 5 of the notification system. Service worker (` sw.js`) receives push events and shows OS-level notifications even when the browser tab is closed. VAPID keys auto-generated on first use and persisted to ` ~/.codeman/push-keys.json`. Per-subscription per-event preferences stored in ` ~/.codeman/push-subscriptions.json`. Expired subscriptions (410/404) auto-cleaned. Requires HTTPS or localhost. iOS requires PWA installed to home screen. See ` src/push-store.ts`.
2026-02-12 10:29:57 +01:00
**Agent Teams (experimental)**: ` TeamWatcher` polls ` ~/.claude/teams/` for team configs and matches teams to sessions via ` leadSessionId`. Teammates are in-process threads (not separate OS processes) and appear as standard subagents. RespawnController checks ` TeamWatcher.hasActiveTeammates()` before triggering respawn. Enable via ` CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` env var in ` settings.local.json`. See ` agent-teams/` for full docs.
2026-02-19 08:49:36 +01:00
**Circuit breaker**: Prevents respawn thrashing when Claude is stuck. States: ` CLOSED` (normal) → ` HALF_OPEN` (testing) → ` OPEN` (blocked). Tracks consecutive no-progress, same-error-repeated, and tests-failing-too-long. Reset via API at ` /api/sessions/:id/ralph-circuit-breaker/reset`.
**Respawn cycle metrics & health scoring**: ` RespawnCycleMetrics` tracks per-cycle outcomes (success, stuck_recovery, blocked, error). ` RalphLoopHealthScore` computes 0-100 health with component scores (cycleSuccess, circuitBreaker, iterationProgress, aiChecker, stuckRecovery). Available via respawn status API.
**Subagent-session correlation**: Session parses Task tool output via ` BashToolParser` → ` SubagentWatcher` discovers new agent → calls ` session.findTaskDescriptionNear()` to match description for window title.
2026-02-27 00:34:38 +01:00
### Frontend Files
| File | Purpose |
|------|---------|
| ` src/web/public/index.html` | HTML entry point with inline critical CSS and async vendor loading |
2026-02-27 15:29:46 +01:00
| ` src/web/public/app.js` | Core UI: xterm.js, tab management, subagent windows, mobile support (~15K lines) |
2026-02-27 17:54:59 +01:00
| ` src/web/public/ralph-wizard.js` | Ralph Loop wizard UI extracted from app.js (~1K lines) |
2026-02-27 00:34:38 +01:00
| ` src/web/public/styles.css` | Main styling (dark theme, layout, components) |
| ` src/web/public/mobile.css` | Responsive overrides for screens <1024px (loaded conditionally via ` media` attribute) |
| ` src/web/public/upload.html` | Screenshot upload page served at ` /upload.html` |
2026-02-27 08:28:30 +01:00
| ` src/web/public/sw.js` | Service worker for Web Push notifications |
| ` src/web/public/manifest.json` | Minimal PWA manifest (required for push on Android) |
2026-02-27 00:34:38 +01:00
| ` src/web/public/vendor/` | Self-hosted xterm.js + addons (eliminates CDN latency) |
2026-02-19 08:49:36 +01:00
### Frontend Architecture (` app.js`)
2026-02-27 15:29:46 +01:00
The frontend is a single ~15K-line vanilla JS file with these key systems:
2026-02-19 08:49:36 +01:00
| System | Key Classes/Functions | Purpose |
|--------|----------------------|---------|
| **Terminal rendering** | ` batchTerminalWrite()`, ` flushPendingWrites()`, ` chunkedTerminalWrite()` | 60fps batched writes with DEC 2026 sync |
| **Local echo overlay** | ` LocalEchoOverlay` class | DOM overlay for instant mobile keystroke feedback |
| **Mobile support** | ` MobileDetection`, ` KeyboardHandler`, ` SwipeHandler`, ` KeyboardAccessoryBar` | Touch input, viewport adaptation, swipe navigation |
| **Subagent windows** | ` openSubagentWindow()`, ` closeSubagentWindow()`, ` updateConnectionLines()` | Floating terminal windows with parent connection lines |
2026-02-27 08:28:30 +01:00
| **Notifications** | ` NotificationManager` class | 5-layer: in-app drawer, tab flash, browser API, web push, audio beep |
2026-02-19 08:49:36 +01:00
| **SSE connection** | ` connectSSE()`, ` addListener()` | EventSource with exponential backoff (1-30s), offline queue (64KB) |
| **Settings** | ` openAppSettings()`, ` apply*Visibility()` | Server-backed + localStorage persistence |
| **Focus management** | ` FocusTrap` class | Modal keyboard navigation with focus restore |
**Z-index layers**: subagent windows (1000), plan agents (1100), log viewers (2000), image popups (3000), local echo overlay (7).
2026-02-20 04:30:02 +01:00
**Built-in respawn presets**: ` solo-work` (3s idle, 60min), ` subagent-workflow` (45s idle, 240min), ` team-lead` (90s idle, 480min), ` ralph-todo` (8s idle, 480min, works through @fix_plan.md tasks), ` overnight-autonomous` (10s idle, 480min, full reset).
2026-02-19 08:49:36 +01:00
**Keyboard shortcuts**: Escape (close panels), Ctrl+? (help), Ctrl+Enter (quick start), Ctrl+W (kill session), Ctrl+Tab (next session), Ctrl+K (kill all), Ctrl+L (clear), Ctrl+Shift+R (restore size), Ctrl/Cmd +/- (font size).
### Security
2026-02-26 16:44:34 +01:00
- **HTTP Basic Auth**: Optional via ` CODEMAN_USERNAME`/` CODEMAN_PASSWORD` env vars
2026-02-27 00:34:38 +01:00
- **Session cookies**: After Basic Auth, a 24h session cookie (` codeman_session`) is issued so credentials aren't re-sent on every request. Active sessions auto-extend. SSE works via same-origin cookie (` EventSource` can't send custom headers).
- **Rate limiting**: 10 failed auth attempts per IP triggers 429 rejection (15-minute decay window). Manual ` StaleExpirationMap` counter — no ` @fastify/rate -limit` needed.
- **Hook bypass**: ` /api/hook-event` POST is exempt from auth — Claude Code hooks curl this from localhost and can't present credentials. Safe: validated by ` HookEventSchema`, only triggers broadcasts.
2026-02-19 08:49:36 +01:00
- **CORS**: Restricted to localhost only
- **Security headers**: X-Content-Type-Options, X-Frame-Options, CSP; HSTS if HTTPS
- **Path validation** (` schemas.ts`): Strict allowlist regex, no shell metacharacters, no traversal, must be absolute
2026-02-26 16:44:34 +01:00
- **Env var allowlist**: Only ` CLAUDE_CODE_*` prefixes allowed; blocks ` PATH`, ` LD_PRELOAD`, ` NODE_OPTIONS`, ` CODEMAN_*` keys
2026-02-19 08:49:36 +01:00
- **File streaming TOCTOU protection**: ` FileStreamManager` calls ` realpathSync()` twice (at validation and before spawn) to catch symlink swaps
### SSE Event Categories
~80+ event types broadcast via ` broadcast()`. Key categories:
| Category | Events | Purpose |
|----------|--------|---------|
| Session | ` session:created/updated/deleted/working/idle/exit/error/completion` | Lifecycle |
| Terminal | ` session:terminal`, ` session:clearTerminal`, ` session:needsRefresh` | Output streaming |
| Respawn | ` respawn:stateChanged/cycleStarted/blocked/aiCheck*/planCheck*/timer*` | Respawn state machine |
| Subagent | ` subagent:discovered/updated/completed/tool_call/progress` | Background agents |
| Ralph | ` session:ralphLoopUpdate/ralphTodoUpdate/ralphCompletionDetected` | Ralph tracking |
| Hooks | ` hook:{eventName}` (dynamic) | Claude Code hook events |
| Plan | ` plan:started/progress/completed/cancelled/subagent` | Plan orchestration |
| Mux | ` mux:created/killed/died/statsUpdated` | tmux process monitor |
| Image | ` image:detected` | Screenshot detection |
### API Route Categories
2026-02-28 02:22:20 +01:00
~280 route handlers in ` server.ts:buildServer()`. Key groups:
2026-02-19 08:49:36 +01:00
| Group | Prefix | Count | Key endpoints |
|-------|--------|-------|---------------|
| Sessions | ` /api/sessions` | ~20 | CRUD, input, resize, interactive, shell |
| Respawn | ` /api/sessions/:id/respawn` | 5 | start, stop, enable, config |
| Ralph | ` /api/sessions/:id/ralph-*` | 6 | state, status, config, circuit-breaker |
| Plan | ` /api/sessions/:id/plan/*` | 5 | task CRUD, checkpoint, history, rollback |
| Subagents | ` /api/subagents` | 7 | list, transcript, kill, cleanup |
| Cases | ` /api/cases` | 5 | CRUD, link, fix-plan |
| Scheduled | ` /api/scheduled` | 4 | CRUD for scheduled runs |
2026-02-27 08:28:30 +01:00
| Push | ` /api/push` | 4 | VAPID key, subscribe, update prefs, unsubscribe |
2026-02-19 08:49:36 +01:00
| System | ` /api/status`, ` /api/stats`, ` /api/config`, ` /api/settings` | 8 | App state, config |
| Files | ` /api/sessions/:id/file*`, ` tail-file` | 5 | Browser, preview, raw, tail stream |
| Mux | ` /api/mux-sessions` | 4 | tmux management, stats |
2026-01-27 23:15:25 +01:00
## Adding Features
2026-01-19 13:41:27 +01:00
2026-02-19 08:49:36 +01:00
- **API endpoint**: Types in ` types.ts`, route in ` server.ts:buildServer()`, use ` createErrorResponse()`. Validate request bodies with Zod schemas in ` schemas.ts`.
- **SSE event**: Emit via ` broadcast()`, handle in ` app.js` SSE listener section (search ` addListener(`)
2026-01-27 23:15:25 +01:00
- **Session setting**: Add to ` SessionState` in ` types.ts`, include in ` session.toState()`, call ` persistSessionState()`
2026-02-19 08:49:36 +01:00
- **Hook event**: Add to ` HookEventType` in ` types.ts`, add hook command in ` hooks-config.ts:generateHooksConfig()`, update ` HookEventSchema` in ` schemas.ts`
- **Mobile feature**: Add to relevant mobile singleton (` KeyboardHandler`, ` KeyboardAccessoryBar`, etc.), test with ` MobileDetection.isMobile()` guard
- **New test**: Pick unique port (search ` const PORT =`), add port comment to test file header. Tests use ports 3150+.
2026-01-21 09:54:42 +01:00
2026-02-19 08:49:36 +01:00
**Validation**: Uses Zod v4 for request validation. Define schemas in ` schemas.ts` and use ` .parse()` or ` .safeParse()`. Note: Zod v4 has different API from v3 (e.g., ` z.object()` options changed, error formatting differs).
2026-01-30 17:20:53 +01:00
2026-01-20 08:56:15 +01:00
## State Files
| File | Purpose |
|------|---------|
2026-02-26 16:44:34 +01:00
| ` ~/.codeman/state.json` | Sessions, settings, tokens, respawn config |
| ` ~/.codeman/mux-sessions.json` | Tmux session metadata for recovery |
| ` ~/.codeman/settings.json` | User preferences |
2026-02-27 08:28:30 +01:00
| ` ~/.codeman/push-keys.json` | VAPID key pair for Web Push (auto-generated) |
| ` ~/.codeman/push-subscriptions.json` | Registered push notification subscriptions |
2026-01-22 20:49:09 +01:00
2026-01-30 17:20:53 +01:00
## Default Settings
2026-02-04 07:24:32 +01:00
UI defaults are set in ` src/web/public/app.js` using ` ??` fallbacks. To change defaults, edit ` openAppSettings()` and ` apply*Visibility()` functions.
2026-01-30 17:20:53 +01:00
2026-02-04 07:24:32 +01:00
**Key defaults:** Most panels hidden (monitor, subagents shown), notifications enabled (audio disabled), subagent tracking on, Ralph tracking off.
2026-01-30 17:20:53 +01:00
2026-01-27 23:15:25 +01:00
## Testing
2026-01-20 08:56:15 +01:00
2026-02-26 16:44:34 +01:00
**CRITICAL: You are running inside a Codeman-managed tmux session.** Never run ` npx vitest run` (full suite) — it spawns/kills tmux sessions and will crash your own session. Instead:
2026-01-21 19:49:44 +01:00
2026-02-13 08:04:20 +01:00
` ``bash
# Safe: run individual test files
npx vitest run test/<specific-file>.test.ts
# Safe: run tests matching a pattern
npx vitest run -t "pattern"
2026-02-26 16:44:34 +01:00
# DANGEROUS from inside Codeman — will kill your tmux session:
2026-02-13 08:04:20 +01:00
# npx vitest run ← DON'T DO THIS
` ``
**Ports**: Unit tests pick unique ports manually. Search ` const PORT =` before adding new tests.
**Config**: Vitest with ` globals: true`, ` fileParallelism: false`. Unit timeout 30s.
2026-01-22 22:43:09 +01:00
2026-02-12 14:41:27 +01:00
**Safety**: ` test/setup.ts` snapshots pre-existing tmux sessions at load time and never kills them. Only sessions registered via ` registerTestTmuxSession()` get cleaned up.
2026-01-21 19:49:44 +01:00
2026-02-12 14:41:27 +01:00
**Respawn tests**: Use MockSession from ` test/respawn-test-utils.ts` to avoid spawning real Claude processes.
2026-01-21 04:27:09 +01:00
2026-02-19 14:52:39 +01:00
**Mobile tests**: Separate Playwright-based suite in ` mobile-test/` with 135 device profiles. Run via ` npx vitest run --config mobile-test/vitest.config.ts`. See ` mobile-test/README.md`.
2026-02-16 12:10:44 +01:00
## Screenshots ("sc")
2026-02-26 16:44:34 +01:00
When the user says "check the sc", "screenshot", or "sc", they mean uploaded screenshots from their mobile device. Screenshots are saved to ` ~/.codeman/screenshots/` and uploaded via ` /upload.html` on the Codeman web UI. To view them, use the Read tool on the image files:
2026-02-16 12:10:44 +01:00
` ``bash
2026-02-26 16:44:34 +01:00
ls ~/.codeman/screenshots/ # List uploaded screenshots
2026-02-16 12:10:44 +01:00
# Then use Read tool on individual files — Claude Code can view images natively
` ``
API: ` GET /api/screenshots` (list), ` GET /api/screenshots/:name` (serve), ` POST /api/screenshots` (upload multipart/form-data). Source: ` src/web/public/upload.html`.
2026-01-27 23:15:25 +01:00
## Debugging
2026-01-26 11:06:35 +01:00
2026-01-20 08:56:15 +01:00
` ``bash
2026-02-12 10:29:57 +01:00
tmux list-sessions # List tmux sessions
tmux attach-session -t <name> # Attach (Ctrl+B D to detach)
2026-01-27 23:15:25 +01:00
curl localhost:3000/api/sessions # Check sessions
curl localhost:3000/api/status | jq # Full app state
2026-02-26 16:44:34 +01:00
cat ~/.codeman/state.json | jq # View persisted state
2026-01-31 07:58:12 +01:00
curl localhost:3000/api/subagents # List background agents
curl localhost:3000/api/sessions/:id/run-summary | jq # Session timeline
2026-01-20 08:56:15 +01:00
` ``
2026-02-04 07:24:32 +01:00
## Troubleshooting
| Problem | Check | Fix |
|---------|-------|-----|
2026-02-12 10:29:57 +01:00
| Session won't start | ` tmux list-sessions` for orphans | Kill orphaned sessions, check Claude CLI installed |
2026-02-04 07:24:32 +01:00
| Port 3000 in use | ` lsof -i :3000` | Kill conflicting process or use ` --port` flag |
| SSE not connecting | Browser console for errors | Check CORS, ensure server running |
| Respawn not triggering | Session settings → Respawn enabled? | Enable respawn, check idle timeout config |
| Terminal blank on tab switch | Network tab for ` /api/sessions/:id/buffer` | Check session exists, restart server |
2026-02-12 14:41:27 +01:00
| Tests failing on session limits | ` tmux list-sessions \| wc -l` | Clean up: ` tmux list-sessions \| grep test \| awk -F: '{print $1}' \| xargs -I{} tmux kill-session -t {}` |
2026-02-26 16:44:34 +01:00
| State not persisting | ` cat ~/.codeman/state.json` | Check file permissions, disk space |
2026-02-04 07:24:32 +01:00
2026-01-27 23:15:25 +01:00
## Performance Constraints
2026-01-27 17:46:43 +01:00
2026-01-27 23:15:25 +01:00
The app must stay fast with 20 sessions and 50 agent windows:
- 60fps terminal (16ms batching + ` requestAnimationFrame`)
- Auto-trimming buffers (2MB terminal max)
- Debounced state persistence (500ms)
2026-02-19 08:49:36 +01:00
- SSE adaptive batching: 16ms (normal), 32ms (moderate), 50ms (rapid); immediate flush at 32KB
- SSE backpressure handling: skip writes to backpressured clients, recover via ` session:needsRefresh` on drain
- Cached endpoints: ` /api/sessions` and ` /api/status` use 1s TTL caches to avoid expensive serialization
- Frontend buffer loads: 128KB chunks via ` requestAnimationFrame` to prevent UI jank
2026-01-27 17:46:43 +01:00
2026-02-02 04:49:15 +01:00
## Terminal Anti-Flicker System
2026-02-26 16:44:34 +01:00
Claude Code uses Ink (React for terminals), which redraws the screen on every state change. Codeman implements a 6-layer anti-flicker pipeline for smooth 60fps output:
2026-02-02 04:49:15 +01:00
` ``
2026-02-04 07:24:32 +01:00
PTY Output → Server Batching (16-50ms) → DEC 2026 Wrap → SSE → Client rAF → xterm.js
2026-02-02 04:49:15 +01:00
` ``
2026-02-04 07:24:32 +01:00
**Key functions:** ` server.ts:batchTerminalData()`, ` server.ts:flushTerminalBatches()`, ` app.js:batchTerminalWrite()`, ` app.js:extractSyncSegments()`
2026-02-02 04:49:15 +01:00
2026-02-04 07:24:32 +01:00
**Typical latency:** 16-32ms. Optional per-session flicker filter adds ~50ms for problematic terminals.
2026-02-02 04:49:15 +01:00
2026-02-04 07:24:32 +01:00
See ` docs/terminal-anti-flicker.md` for full implementation details (adaptive batching, DEC 2026 markers, edge cases).
2026-02-02 04:49:15 +01:00
2026-01-28 05:34:25 +01:00
## Resource Limits
2026-01-27 18:16:19 +01:00
2026-01-28 05:34:25 +01:00
Limits are centralized in ` src/config/buffer-limits.ts` and ` src/config/map-limits.ts`.
**Buffer limits** (per session):
2026-01-27 23:15:25 +01:00
| Buffer | Max | Trim To |
|--------|-----|---------|
| Terminal | 2MB | 1.5MB |
| Text output | 1MB | 768KB |
| Messages | 1000 | 800 |
2026-01-27 18:16:19 +01:00
2026-01-28 05:34:25 +01:00
**Map limits** (global):
| Resource | Max |
|----------|-----|
| Tracked agents | 500 |
| Concurrent sessions | 50 |
| SSE clients total | 100 |
| File watchers | 500 |
Use ` LRUMap` for bounded caches with eviction, ` StaleExpirationMap` for TTL-based cleanup.
2026-01-27 23:15:25 +01:00
## Where to Find More Information
2026-01-27 18:16:19 +01:00
2026-01-27 23:15:25 +01:00
| Topic | Location |
|-------|----------|
| **Respawn state machine** | ` docs/respawn-state-machine.md` |
| **Ralph Loop guide** | ` docs/ralph-wiggum-guide.md` |
| **Claude Code hooks** | ` docs/claude-code-hooks-reference.md` |
2026-02-04 07:24:32 +01:00
| **Terminal anti-flicker** | ` docs/terminal-anti-flicker.md` |
2026-02-27 17:54:59 +01:00
| **Agent Teams (experimental)** | ` agent-teams/README.md`, ` agent-teams/design.md` |
| **API routes** | ` src/web/server.ts:buildServer()` or README.md |
2026-01-27 23:15:25 +01:00
| **SSE events** | Search ` broadcast(` in ` server.ts` |
2026-02-18 10:23:10 +01:00
| **Session statuses** | ` SessionStatus` type in ` src/types.ts` |
2026-01-27 23:15:25 +01:00
| **Error codes** | ` createErrorResponse()` in ` src/types.ts` |
| **Test utilities** | ` test/respawn-test-utils.ts` |
2026-02-19 14:52:39 +01:00
| **Mobile test suite** | ` mobile-test/README.md` |
2026-02-28 01:35:37 +01:00
| **OpenCode integration** | ` docs/opencode-integration.md` |
| **Local echo overlay** | ` docs/local-echo-overlay-plan.md` |
| **Performance investigation** | ` docs/performance-investigation-report.md` |
| **First-load optimization** | ` docs/first-load-optimization-plan.md`, ` docs/perf-audit-first-load.md` |
| **Dead code audit** | ` docs/cleanup-findings.md` |
| **TypeScript improvements** | ` docs/typescript-improvement-suggestions.md` |
| **Browser testing** | ` docs/browser-testing-guide.md` |
| **Mobile testing report** | ` docs/mobile-testing-report.md` |
| **Voice input** | ` docs/voice-input-plan.md` |
| **Improvement roadmaps** | ` docs/respawn-improvement-plan.md`, ` docs/ralph-improvement-plan.md`, ` docs/plan-improvement-roadmap.md` |
| **Background keystroke forwarding** | ` docs/background-keystroke-forwarding-merged-plan.md` |
| **Run summary** | ` docs/run-summary-plan.md` |
2026-02-27 17:54:59 +01:00
2026-02-28 01:35:37 +01:00
Additional design docs and investigation reports are in the ` docs/` directory.
2026-01-28 04:04:40 +01:00
2026-01-29 12:59:42 +01:00
## Scripts
2026-01-28 07:07:18 +01:00
2026-01-29 12:59:42 +01:00
| Script | Purpose |
|--------|---------|
2026-02-12 10:29:57 +01:00
| ` scripts/tmux-manager.sh` | Safe tmux session management (use instead of direct kill commands) |
2026-01-29 12:59:42 +01:00
| ` scripts/monitor-respawn.sh` | Monitor respawn state machine in real-time |
2026-02-12 10:29:57 +01:00
| ` scripts/watch-subagents.ts` | Real-time subagent transcript watcher (list, follow by session/agent ID) |
2026-02-26 16:44:34 +01:00
| ` scripts/codeman-web.service` | systemd service file for production deployment |
2026-02-28 01:35:37 +01:00
| ` scripts/codeman-tunnel.service` | systemd service file for persistent Cloudflare tunnel |
| ` scripts/tunnel.sh` | Start/stop/check Cloudflare quick tunnel (` ./scripts/tunnel.sh start\|stop\|url`) |
| ` scripts/build.mjs` | esbuild-based production build (called by ` npm run build`) |
2026-02-27 17:54:59 +01:00
| ` scripts/postinstall.js` | npm postinstall hook for setup |
Additional scripts in ` scripts/` for screenshots, demos, Ralph wizards, and browser testing.
2026-01-28 07:07:18 +01:00
2026-01-30 17:20:53 +01:00
## Memory Leak Prevention
2026-01-30 11:30:51 +01:00
2026-01-30 17:20:53 +01:00
Frontend runs long (24+ hour sessions); all Maps/timers must be cleaned up.
2026-01-30 11:30:51 +01:00
2026-01-30 13:28:30 +01:00
### Cleanup Patterns
When adding new event listeners or timers:
1. Store handler references for later removal
2. Add cleanup to appropriate ` stop()` or ` cleanup*()` method
3. For singleton watchers, store refs in class properties and remove in server ` stop()`
2026-01-30 14:07:27 +01:00
2026-02-19 08:49:36 +01:00
**Backend**: Clear Maps in ` stop()`, null promise callbacks on error, remove watcher listeners on shutdown. Use ` CleanupManager` for centralized disposal — supports timers, intervals, watchers, listeners, streams. Guard async callbacks with ` if (this.cleanup.isStopped) return`.
2026-01-30 17:20:53 +01:00
2026-02-19 08:49:36 +01:00
**Frontend**: Store drag/resize handlers on elements, clean up in ` close*()` functions. SSE reconnect calls ` handleInit()` which resets state. SSE listeners are tracked in an array and removed on reconnect to prevent accumulation.
2026-01-30 17:20:53 +01:00
Run ` npx vitest run test/memory-leak-prevention.test.ts` to verify patterns.
2026-01-31 14:32:20 +01:00
## Common Workflows
2026-02-26 16:44:34 +01:00
**Investigating a bug**: Start dev server (` npx tsx src/index.ts web`), reproduce in browser, check terminal output and ` ~/.codeman/state.json` for clues.
2026-01-31 14:32:20 +01:00
**Adding a new API endpoint**: Define types in ` types.ts`, add route in ` server.ts:buildServer()`, broadcast SSE events if needed, handle in ` app.js:handleSSEEvent()`.
**Modifying respawn behavior**: Study ` docs/respawn-state-machine.md` first. The state machine is in ` respawn-controller.ts`. Use MockSession from ` test/respawn-test-utils.ts` for testing.
2026-02-19 08:49:36 +01:00
**Modifying mobile behavior**: Mobile singletons (` MobileDetection`, ` KeyboardHandler`, ` SwipeHandler`, ` KeyboardAccessoryBar`) all have ` init()`/` cleanup()` lifecycle. KeyboardHandler uses ` visualViewport` API for iOS keyboard detection (100px threshold for address bar drift). All mobile handlers are re-initialized after SSE reconnect to prevent stale closures.
**Adding a file watcher**: Use ` ImageWatcher` as a template pattern — chokidar with ` awaitWriteFinish`, burst throttling (max 20/10s), debouncing (200ms), and auto-ignore of ` node_modules/.git/dist/`.
2026-02-27 00:34:38 +01:00
## Tunnel Setup (Remote Access)
Access Codeman from mobile/remote devices via Cloudflare quick tunnel.
` ``
Browser → Cloudflare Edge (HTTPS) → cloudflared → localhost:3000
` ``
**Prerequisites**: ` cloudflared` installed (` cloudflared --version`), ` CODEMAN_PASSWORD` set in environment.
### Quick Start
` ``bash
# Via CLI
./scripts/tunnel.sh start # Start tunnel, prints public URL
./scripts/tunnel.sh url # Show current URL
./scripts/tunnel.sh stop # Stop tunnel
# Via web UI: Settings → Tunnel → Toggle On
` ``
### systemd Service (Persistent)
` ``bash
# Install and enable
cp scripts/codeman-tunnel.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now codeman-tunnel
# Check logs
journalctl --user -u codeman-tunnel -f
` ``
### Auth Flow
1. First request → browser shows Basic Auth prompt (username: ` admin` or ` CODEMAN_USERNAME`)
2. On success → server issues ` codeman_session` HttpOnly cookie (24h TTL, auto-extends on activity)
3. Subsequent requests → cookie authenticates silently (no more prompts)
4. SSE works automatically — ` EventSource` sends same-origin cookies
5. 10 failed attempts per IP → 429 rate limit (15-minute decay)
### Security Requirements
- **Always set ` CODEMAN_PASSWORD`** before exposing via tunnel — without it, anyone with the URL has full access
- Session cookies are ` Secure` when using ` --https` flag; through Cloudflare tunnel without ` --https`, cookies are non-Secure but traffic is still encrypted end-to-end via Cloudflare
- ` /api/hook-event` bypasses auth (localhost-only Claude Code hooks need unauthenticated access)