Move src/ → apps/server/ and packages/web/ → apps/web/ to adopt standard monorepo conventions (apps/ for runnable apps, packages/ for reusable libraries). Update all config files, shared package imports, test fixtures, and documentation to reflect new paths. Key fixes: - Update workspace config to ["apps/*", "packages/*"] - Update tsconfig.json rootDir/include for apps/server/ - Add apps/web/** to vitest exclude list - Update drizzle.config.ts schema path - Fix ensure-schema.ts migration path detection (3 levels up in dev, 2 levels up in dist) - Fix tests/integration/cli-server.test.ts import paths - Update packages/shared imports to apps/server/ paths - Update all docs/ files with new paths
4.5 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>) |
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();