ReapWorktreesDeps
Defined in: src/tab-reaper.ts:215
Reviewer/critic disposable checkouts ({basename}-review-{tag}, created by
worktree.ts:createDetached) whose teardown was missed — a crash, a shepherd
restart that cleared in-memory review tracking, or a foreign-era basename whose
repo is no longer configured — accumulate as dead dirs under .shepherd-worktrees.
This is a disk-driven sweep that reaps them; it COMPLEMENTS plan-gate’s
gcStaleReviewWorktrees (which is store-driven and only knows plan_gate
spawns it still tracks) — it does not replace it. Runs SYNC (a boot + hourly
maintenance pass, not on the typing hot path), consistent with that sibling.
Tag-shape match, basename-agnostic (guard d). Selection keys off the reviewer
TAG SHAPE — a name ending in -review-(<8hex> | <uuid>-<8hex>) — NOT the basename.
This is deliberate: a worktree minted under a now-defunct basename (tank-review-…,
flowagent-review-…, pulse-review-…) whose repo is no longer configured would be
invisible to any basename- or repo-scoped filter, yet is exactly the kind of orphan
that strands forever. Matching the tag suffix alone catches those. The flip side
(guards below) is that a USER prompt slugging to review-* yields {basename}-review-*
too — so a hex-shaped suffix could in principle alias real user work; (d)‘s strict
hex/uuid shape plus the session-path spare (e) keep that from being reaped.
Full spare/reap coverage matrix (an unowned candidate is reaped only if it survives every spare below):
- pre-
inflightbegin() window — each reviewer service’sbegin()doescreateDetached(mints the-review-<tag>dir on disk) and only LATERinflight.setthenrecordReviewerSpawn. In that window the dir exists, matches the tag shape, is NOT inprotectedPaths, has NOreviewer_spawnsrow, and hosts no liveclaudeyet — a mid-begin checkout. Covered by the directory-age guard: a candidate whose dir is younger thangraceMs(or that can’t be stat’d → fail-closed) is spared. Checked BEFORE thescanAliveprobe so a not-yet-running spawn is held by age alone. - owned in memory (
protectedPaths) — paths a reviewer service currently holds. Spared REGARDLESS of age or/procliveness. This is the #631 regression guard: a re-adopted plan-gate orphan has a DEAD reviewerclaudeAND an OLD uncompletedreviewer_spawnsrow, yettick()still needs its worktree — age/proc heuristics alone would wrongly reap it. The caller unions the three reviewer services’inflightWorktrees()into this set. - live store session (
sessionWorktreePaths, guard e) — any path backing a live user session is spared even if its name happens to match the tag shape. - live
claudeunder the dir (scanAlive) — one cheap/procpass; a candidate hosting a liveclaudeis spared (sparedLive). - recent uncompleted spawn (the
graceMsgrace) — areviewer_spawnsrow withcompletedAt == nullwhosespawnedAtis withingraceMs. INVARIANT:recordReviewerSpawnruns AFTERinflight.set, so a row NEVER precedes in-memory tracking — the grace therefore does NOT cover the pre-inflightwindow (the age guard does). It spares a recently-spawned reviewer whose path is not (yet/any longer) ininflight, e.g. across a restart before re-adoption, or a review/critic spawn that isn’t re-adopted. - old + ownerless — survives every spare above → reaped.
Too-young/unstattable and live-session spares are counted under sparedOwned.
Fully dependency-injected (no direct fs/proc/store calls) so it is unit-testable
without a real filesystem, /proc, or store. The real wiring into index.ts is a
separate task.
Properties
Section titled “Properties”dirMtime
Section titled “dirMtime”dirMtime: (
path) =>number|null
Defined in: src/tab-reaper.ts:238
Dir mtime in epoch-ms, or null if it can’t be stat’d (→ fail-closed spare). Injected
so the function stays I/O-free + unit-testable; the caller wraps statSync(p).mtimeMs.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”number | null
graceMs
Section titled “graceMs”graceMs:
number
Defined in: src/tab-reaper.ts:235
Grace window for a recent uncompleted spawn (spare if spawnedAt > now()-graceMs).
Also the dir-age threshold: a candidate dir younger than graceMs is spared.
listDir
Section titled “listDir”listDir: (
parent) =>string[]
Defined in: src/tab-reaper.ts:219
Entry NAMES under a parent (default in caller = readdirSync); [] if unreadable.
Parameters
Section titled “Parameters”parent
Section titled “parent”string
Returns
Section titled “Returns”string[]
listReviewerSpawns
Section titled “listReviewerSpawns”listReviewerSpawns: () =>
object[]
Defined in: src/tab-reaper.ts:227
Append-only reviewer-spawn rows (subset of ReviewerSpawnRow fields).
Returns
Section titled “Returns”object[]
now: () =>
number
Defined in: src/tab-reaper.ts:232
Returns
Section titled “Returns”number
parents
Section titled “parents”parents:
string[]
Defined in: src/tab-reaper.ts:217
Distinct .shepherd-worktrees dirs to sweep.
protectedPaths
Section titled “protectedPaths”protectedPaths:
Set<string>
Defined in: src/tab-reaper.ts:221
In-memory reviewer-owned paths — spare regardless of age/proc (#631 guard).
remove
Section titled “remove”remove: (
worktreePath) =>void
Defined in: src/tab-reaper.ts:240
Worktree removal wrapper (worktree.remove).
Parameters
Section titled “Parameters”worktreePath
Section titled “worktreePath”string
Returns
Section titled “Returns”void
scanAlive
Section titled “scanAlive”scanAlive: (
paths) =>Map<string,boolean>
Defined in: src/tab-reaper.ts:225
One-pass /proc liveness probe (scanClaudeAliveByWorktree).
Parameters
Section titled “Parameters”string[]
Returns
Section titled “Returns”Map<string, boolean>
sessionWorktreePaths
Section titled “sessionWorktreePaths”sessionWorktreePaths:
Set<string>
Defined in: src/tab-reaper.ts:223
Live store session worktreePaths — spare (user-work guard e).