# Git, Process, and Logging Modules Three infrastructure modules supporting agent execution. ## Git Module (`src/git/`) Manages git worktrees for isolated agent workspaces. ### Architecture - **Port**: `WorktreeManager` interface - **Adapter**: `SimpleGitWorktreeManager` using simple-git library ### WorktreeManager Methods | Method | Purpose | |--------|---------| | `create(id, branch, baseBranch?)` | Create worktree with new branch (default base: 'main') | | `remove(id)` | Clean up worktree directory | | `list()` | All worktrees including main | | `get(id)` | Specific worktree by ID | | `diff(id)` | Changed files vs HEAD | | `merge(id, targetBranch)` | Merge worktree branch into target | ### Worktree Storage Worktrees stored in `.cw-worktrees/` subdirectory of the repo. Each agent gets a worktree at `.cw-worktrees/agent//`. ### Merge Flow 1. Check out target branch 2. `git merge --no-edit` 3. On success: emit `worktree:merged` 4. On conflict: `git merge --abort`, emit `worktree:conflict` with conflicting file list 5. Restore original branch ### BranchManager (`src/git/branch-manager.ts`) - **Port**: `BranchManager` interface - **Adapter**: `SimpleGitBranchManager` using simple-git | Method | Purpose | |--------|---------| | `ensureBranch(repoPath, branch, baseBranch)` | Create branch from base if it doesn't exist (idempotent) | | `mergeBranch(repoPath, source, target)` | Merge via ephemeral worktree, returns conflict info | | `diffBranches(repoPath, base, head)` | Three-dot diff between branches | | `deleteBranch(repoPath, branch)` | Delete local branch (no-op if missing) | | `branchExists(repoPath, branch)` | Check local branches | | `remoteBranchExists(repoPath, branch)` | Check remote tracking branches (`origin/`) | `remoteBranchExists` is used by `registerProject` and `updateProject` to validate that a project's default branch actually exists in the cloned repository before saving. ### Project Clones - `cloneProject(url, destPath)` — Simple git clone wrapper - `ensureProjectClone(project, workspaceRoot)` — Idempotent: checks if clone exists, clones if not - `getProjectCloneDir(name, id)` — Canonical path: `repos/-/` ### Events Emitted `worktree:created`, `worktree:removed`, `worktree:merged`, `worktree:conflict` --- ## Process Module (`src/process/`) Spawns, tracks, and controls child processes. ### Classes **ProcessRegistry** — In-memory metadata store: - `register(info)`, `unregister(id)`, `get(id)`, `getAll()`, `getByPid(pid)`, `updateStatus(id, status)` **ProcessManager** — Lifecycle management: | Method | Purpose | |--------|---------| | `spawn(options)` | Spawn detached process (survives parent exit) | | `stop(id)` | SIGTERM → wait 5s → SIGKILL | | `stopAll()` | Stop all running processes in parallel | | `restart(id)` | Stop + re-spawn with same options | | `isRunning(id)` | Check with `process.kill(pid, 0)` | ### Spawn Details - Uses `execa` with `detached: true`, `stdio: 'ignore'` - Calls `subprocess.unref()` so parent can exit - Exit handler updates registry and emits events ### Events Emitted `process:spawned`, `process:stopped`, `process:crashed` --- ## Logger Module (`src/logger/`) Structured logging via **pino**. ### Usage ```typescript import { createModuleLogger } from './logger/index.js'; const log = createModuleLogger('my-module'); log.info({ key: 'value' }, 'message'); ``` ### Configuration | Env Var | Effect | |---------|--------| | `CW_LOG_LEVEL` | Override log level | | `CW_LOG_PRETTY` | Set to `'1'` for human-readable output | | `NODE_ENV=development` | Default to 'debug' level | ### Output - Default: JSON to stderr (fd 2) - Pretty mode: pino-pretty to stdout with colors and timestamps --- ## Logging Module (`src/logging/`) File-based per-process output capture (separate from pino). ### Classes **LogManager** — Directory management: - Base dir: `~/.cw/logs/` - Structure: `{processId}/stdout.log`, `{processId}/stderr.log` - `cleanOldLogs(retainDays)` — removes old directories by mtime **ProcessLogWriter** — File I/O with timestamps: - `open()` — create directories and append-mode WriteStreams - `writeStdout(data)` / `writeStderr(data)` — prefix each line with `[YYYY-MM-DD HH:mm:ss.SSS]` - Handles backpressure (waits for drain event) - Emits `log:entry` event via EventBus ### Factory ```typescript import { createLogger } from './logging/index.js'; const writer = createLogger(processId, eventBus); await writer.open(); await writer.writeStdout('output data'); await writer.close(); ```