- Add BranchManager.listCommits() and diffCommit() for commit-level navigation - Add getPhaseReviewCommits and getCommitDiff tRPC procedures - New ReviewHeader: consolidated toolbar with phase selector pills, branch info, stats, integrated preview controls, and approve/reject actions - New CommitNav: horizontal commit strip with "All changes" + individual commits, each showing hash, message, and change stats - Slim down ReviewSidebar: file list only with dimming for out-of-scope files when viewing a single commit - ReviewTab orchestrates all pieces in a single bordered card layout
4.7 KiB
Git, Process, and Logging Modules
Three infrastructure modules supporting agent execution.
Git Module (apps/server/git/)
Manages git worktrees for isolated agent workspaces.
Architecture
- Port:
WorktreeManagerinterface - Adapter:
SimpleGitWorktreeManagerusing 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/<alias>/.
Merge Flow
- Check out target branch
git merge <source> --no-edit- On success: emit
worktree:merged - On conflict:
git merge --abort, emitworktree:conflictwith conflicting file list - Restore original branch
BranchManager (apps/server/git/branch-manager.ts)
- Port:
BranchManagerinterface - Adapter:
SimpleGitBranchManagerusing 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/<branch>) |
listCommits(repoPath, base, head) |
List commits head has that base doesn't (with stats) |
diffCommit(repoPath, commitHash) |
Get unified diff for a single commit |
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 wrapperensureProjectClone(project, workspaceRoot)— Idempotent: checks if clone exists, clones if notgetProjectCloneDir(name, id)— Canonical path:repos/<sanitized-name>-<id>/
Events Emitted
worktree:created, worktree:removed, worktree:merged, worktree:conflict
Process Module (apps/server/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
execawithdetached: 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 (apps/server/logger/)
Structured logging via pino.
Usage
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 (apps/server/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 WriteStreamswriteStdout(data)/writeStderr(data)— prefix each line with[YYYY-MM-DD HH:mm:ss.SSS]- Handles backpressure (waits for drain event)
- Emits
log:entryevent via EventBus
Factory
import { createLogger } from './logging/index.js';
const writer = createLogger(processId, eventBus);
await writer.open();
await writer.writeStdout('output data');
await writer.close();