Files
Codewalkers/docs/git-process-logging.md
Lukas May 771cd71c1e feat: Validate default branch exists in repo when setting project defaultBranch
registerProject and updateProject now verify via remoteBranchExists that the
specified branch actually exists in the cloned repository before saving.
2026-02-10 11:46:00 +01:00

4.5 KiB

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/<alias>/.

Merge Flow

  1. Check out target branch
  2. git merge <source> --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/<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 wrapper
  • ensureProjectClone(project, workspaceRoot) — Idempotent: checks if clone exists, clones if not
  • getProjectCloneDir(name, id) — Canonical path: repos/<sanitized-name>-<id>/

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

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

import { createLogger } from './logging/index.js';
const writer = createLogger(processId, eventBus);
await writer.open();
await writer.writeStdout('output data');
await writer.close();