- Version
0.1.0 - Purity server-only (shells out to the
gitCLI vianode:child_process, reads/writes files vianode:fs) - Purpose Provision per-task git worktrees, write the runtime-agnostic system-of-record scaffold, and validate the structured
AGENT_HANDOFF.jsonso any runtime, or a human, can pick up a task cold. - Workspace deps none
- External deps
zod^3.25.0
container tier, which this package never provisions. The package exposes a single . entry point (no subpath exports). DB-bound bookkeeping (the workspaces row, the task status drive) lives server-side in apps/web/server/lib/worktrees.ts; this package is purely the git/file mechanism.
Public API
Functions
isolation (./isolation)
| Signature | Contract |
|---|---|
isolationForTask(kind: TaskKind): IsolationLevel | research/review → none; code → worktree; unknown kinds default to worktree (the safe-for-concurrency choice). Never returns container automatically. |
needsWorktree(kind: TaskKind): boolean | True iff isolationForTask(kind) === 'worktree'. |
./scaffold)
| Signature | Contract |
|---|---|
renderTaskMd(input: TaskScaffoldInput): string | Render TASK.md: what/why, acceptance criteria, known gotchas, how-to-work. |
renderProgressMd(input: TaskScaffoldInput): string | Render task-progress.md: the running state log + the clock-in/clock-out ritual. |
renderDecisionsJson(): string | Render DECISIONS.json: an empty structured clawboo/decisions@1 log for the “why” compaction drops. |
renderInitSh(input: TaskScaffoldInput): string | Render the runtime-agnostic init.sh (set -euo pipefail, single-quoted INSTALL_CMD/VERIFY_CMD/START_CMD). |
renderVerificationMd(input: TaskScaffoldInput): string | Render VERIFICATION.md: the evidence template the completion gate reads. |
writeScaffold(worktreePath: string, input: TaskScaffoldInput): Promise<void> | Write all five SoR files into the worktree root + chmod 0755 on init.sh. Does NOT write AGENT_HANDOFF.json (that is the clock-out artifact). |
./handoff)
| Signature | Contract |
|---|---|
writeHandoff(worktreePath: string, input: AgentHandoffInput): Promise<AgentHandoff> | Validate + write AGENT_HANDOFF.json at the worktree root (the clock-out artifact); timestamp defaults to now. |
readHandoff(worktreePath: string): Promise<AgentHandoff | null> | Read + zod-validate the handoff; null if absent (first pickup) or malformed. |
reconstructState(worktreePath: string): Promise<ResumeState> | The clock-in read: rebuild { done, broken, next, … } from AGENT_HANDOFF.json (falling back to task-progress.md) + init.sh, with NO chat/board access. Proof the SoR is runtime-agnostic. |
./lifecycle)
| Signature | Contract |
|---|---|
provisionWorktree(opts: ProvisionOptions): Promise<Worktree> | Branch a fresh worktree on clawboo/task-<id> from a SHA (never the dirty tree), write + commit the SoR scaffold as the baseline. Orphan-resilient pre-clean, branch-collision recovery, verify-both, cleanup-then-one-retry. Returns the scaffold-commit baseCommit. |
resumeWorktree(opts: ResumeOptions): Promise<Worktree> | Re-attach a paused task’s existing branch at its tip, NEVER -b/-B, so prior commits are preserved. Recovers baseCommit as the scaffold commit. Throws if the branch is gone. |
loadWorktree(opts: LoadOptions): Promise<Worktree> | Reconstruct the Worktree handle (path/branch/recovered baseline) for an already-provisioned task, for callers (the board) that persisted only the path/branch. |
pauseWorktree(repoPath: string, worktree: Worktree): Promise<PauseResult> | Commit any uncommitted work, drop the worktree to free disk/process slots, and KEEP the branch. |
commitWorktreeWork(repoPath: string, worktree: Worktree, message?: string): Promise<CommitResult> | Commit uncommitted work and return the resulting HEAD sha WITHOUT dropping the worktree, the critic’s checkpoint before a detached review. No-op (returns HEAD) when clean. |
completeWorktree(repoPath: string, worktree: Worktree): Promise<CompleteResult> | Diff vs baseline excluding the SoR files: empty → auto-cleanup (remove worktree + delete branch + prune); non-empty → retain + return the diff-stat. |
gcWorktrees(opts: GcOptions): Promise<GcResult> | Reap worktrees by age (default 72h) + count (default 25), commit-before-drop (keeps the branch), skip active tasks (re-checked under the per-path mutex), continue-on-failure. Only touches the clawboo worktree root. |
provisionReviewWorktree(opts: ReviewOptions): Promise<Worktree> | Detached, read-only reviewer checkout at a SHA, no branch ⇒ a reviewer structurally cannot push. |
removeReviewWorktree(repoPath: string, worktree: Worktree): Promise<void> | Tear down a review worktree (no branch to delete, it was detached). |
./git, low-level plumbing, exported for the server orchestrator + tests)
| Signature | Contract |
|---|---|
isGitRepo(dir: string): Promise<boolean> | True when dir is inside a git work tree. |
resolveBaseSha(repoPath: string, ref?: string): Promise<string> | Resolve a ref (default HEAD) to a full commit SHA. |
revParse(dir: string, ref?: string): Promise<string> | Resolve the current HEAD commit (default HEAD) of a worktree. |
branchExists(repoPath: string, branch: string): Promise<boolean> | True when a local branch exists. |
branchNameForTask(taskId: string): string | The default branch name clawboo/task-<taskId>. |
worktreeRootFor(repoPath: string, rootDir?: string): string | The clawboo worktree root, default <repoPath>/.clawboo/worktrees. |
diffStat(worktreePath: string, baseCommit: string, opts?: { excludePaths?: string[] }): Promise<DiffStat> | Diff-stat vs the baseline (committed delta + untracked, minus excludePaths); dirty answers “did any real work happen?”. |
worktreeDiff(worktreePath: string, baseCommit: string, opts?: { excludePaths?: string[] }): Promise<string> | The unified diff text vs the baseline, for read-only display. |
isDetached(worktreePath: string): Promise<boolean> | True when HEAD is detached (the read-only reviewer guarantee). |
isWorktreeRegistered(repoPath: string, worktreePath: string): Promise<boolean> | True when worktreePath is registered as a git worktree (compares realpaths). |
Types & interfaces
| Name | Shape / contract |
|---|---|
IsolationLevel | 'none' | 'worktree' | 'container', execution-isolation tier (orthogonal to the security sandbox). |
TaskKind | 'code' | 'research' | 'review' | (string & {}), open set; drives the isolation decision. |
Worktree | { taskId, worktreePath, branch, baseCommit, detached }, a provisioned on-disk world for one task (empty taskId/branch for a detached reviewer). |
DiffStat | { filesChanged, insertions, deletions, dirty }, change summary relative to baseline. |
TaskScaffoldInput | { taskId, title, description?, acceptanceCriteria?, knownGotchas?, commands?: { install?; verify?; start? }, teamName? }, inputs for rendering the SoR scaffold. |
ResumeState | { hasHandoff, done, broken, next, whyBlocked?, commands: { init; verify; start }, warnings, lastRuntime?, nativeSessionId? }, the clock-in reconstruction. |
AgentHandoff | z.infer of agentHandoffSchema: { handoffFrom, runtime, timestamp, completedSubtasks, brokenOrUnverified, nextBestStep, whyBlocked?, commands, evidence, warnings, nativeSessionId?, roomCursor? }. Role-neutral: runtime may be 'human'. |
AgentHandoffInput | Omit<AgentHandoff, 'timestamp'> & { timestamp?: string }, timestamp defaults to now. |
ProvisionOptions | { repoPath, taskId, baseSha?, baseRef?, rootDir?, scaffold: TaskScaffoldInput }. |
ResumeOptions | { repoPath, taskId, rootDir? }. |
LoadOptions | { repoPath, taskId, rootDir? }. |
PauseResult | { committed: boolean; head: string }. |
CommitResult | { head: string; committed: boolean }. |
CompleteResult | { dirty: boolean; diffStat: DiffStat; cleaned: boolean }. |
GcOptions | { repoPath, rootDir?, maxAgeMs?, maxCount?, isActive?: (taskId) => boolean, now? }. |
GcResult | { reaped: string[]; skipped: { taskId; reason }[]; failed: { taskId; error }[] }. |
ReviewOptions | { repoPath, sha, rootDir? }. |
Constants
| Name | Value / contract |
|---|---|
SOR_FILES | { task: 'TASK.md', progress: 'task-progress.md', decisions: 'DECISIONS.json', init: 'init.sh', verification: 'VERIFICATION.md', handoff: 'AGENT_HANDOFF.json' }, canonical SoR filenames at the worktree root. |
agentHandoffSchema | zod object validating AGENT_HANDOFF.json; runtime is any executor id (may be 'human'); array/object fields default. |
Classes
| Name | Contract |
|---|---|
GitError | Error subclass thrown on non-zero git exit; carries readonly stderr: string and readonly args: string[]. |
KeyedMutex | Per-key async mutex (run<T>(key, fn): Promise<T>) serializing ops that share a key (a worktree path); a failed op does NOT poison the chain. |
Used by
apps/web(server):lib/worktrees.ts(the board orchestrator: provision/load/pause/resume/complete/GC, scaffold, handoff round-trip,SOR_FILES,IsolationLevel),lib/executorRunner.ts(claim → worktree → run → verify → handoff),lib/verification/{index,critic,deterministicGate}.ts(commitWorktreeWork,provisionReviewWorktree/removeReviewWorktree,DiffStat/Worktree,SOR_FILES),lib/routines/openclawDispatch.ts(KeyedMutex),api/board.ts(agentHandoffSchema).
Source
Barrel:packages/worktrees/src/index.ts (re-exports ./types, ./isolation, ./scaffold, ./handoff, ./lifecycle, ./git).
See also
- Worktrees & cross-runtime handoff
- Cross-runtime handoff guide
- Executor runner internals
- Verification (builder≠judge)
@clawboo/executor, theRuntimeAdapterwhose runs work inside a worktree@clawboo/governance, the verify gate that reviews a worktree’s diff- Package overview