bob-reap
bob-reap — Working-Tree Residue Reaper
Section titled “bob-reap — Working-Tree Residue Reaper”What
bob-reapmay delete, what it can never delete, and how a project declares its reapable surfaces (#1664). (Back to reference index.)
Runtime tooling leaves regenerable residue in a project tree: swarm scratch,
stale warp-drive state, dev-server logs and pidfiles, build output, aging QA
media. bob-reap keeps the tree uncluttered without ever touching durable
episodic data or authored files. Per the
Prime Directive, the reapable surface is
declared in exactly one place (the manifest), and the reaper is idempotent and
observable (dry-run report by default).
bob-reap [project-root] [options] # dry-run report — deletes nothingbob-reap --apply # actually deletebob-reap --apply --owner warp-drive,dev-up,qa # only those owners' surfaces| Option | Effect |
|---|---|
--apply |
Perform deletion (default is a dry-run report: path, age, why). |
--owner <t>[,<t>...] |
Restrict to surfaces owned by these tools. One or more of warp-drive, swarm, dev-up, docs-site, dash, qa, misc. |
--manifest <path> |
Explicit manifest path (default <root>/reap.json). |
--json |
Machine-readable report. |
Exit codes follow the CLI conventions established by the swarm hardening work:
0 clean (including “no manifest — nothing declared”), 1 a deletion failed,
2 config or usage error — bad manifest, unknown owner, or a keep-list
refusal. A missing reap.json is a clean no-op unless the path was passed
explicitly via --manifest, which fails loudly.
The manifest (reap.json)
Section titled “The manifest (reap.json)”Reapable surfaces are declared in reap.json at the project root, validated
against schemas/reap.schema.json (wired into make check via
check-schemas.js). No path list lives in the reaper code. Each surface:
{ "name": "playwright-media", "glob": ".playwright-mcp/", "owner": "qa", "ttl_days": 7, "reason": "QA browser session media — useful for about a week, then clutter"}Four glob forms are supported: a directory (x/ — the directory and all
contents), an exact file path (dev.json), a root-level glob (*.png —
top-level files only), and a recursive basename (**/.DS_Store — anywhere
outside .git). Anything else is refused as a config error.
ttl_days is an age floor: a candidate is reaped only when its mtime is older
than that many days (0 / absent = no age requirement).
Safety model
Section titled “Safety model”In order of authority:
- Keep-list (hard floor).
.claude/.decisions.jsonl,.claude/session-ledger.jsonl,.claude/loop-notes.json, anddocs/audits/**are hardcoded in the reaper, enforced independently of manifest content. A manifest entry whose scope definitely covers a keep-list path (a directory or exact-file glob) is refused — reported, exit2, never honored. A recursive or root-glob entry that only incidentally matches a keep path (a.DS_Storeinsidedocs/audits/) has that file skipped by a per-file backstop while the rest of the surface reaps normally. - Tracked-file protection. A git-tracked file is never deleted, even when a manifest glob matches it. A directory retaining a tracked file is not pruned.
- Liveness guards. A surface is skipped — with a stated reason — while
its owning tool is active: warp-drive state in a non-terminal phase (an
unreadable state file is conservatively treated as live), an in-flight
swarm run (
.swarm/run.jsonwith launched-but-not-done items), or a live.dev-server.pidprocess. Live session state is never deleted. This guards against the past incident where tooling resolving the wrong cwd wiped a live warp-drive state file. - TTL. Candidates younger than the surface’s
ttl_daysare skipped.
gitignore alignment
Section titled “gitignore alignment”Every manifest glob is expected to be git-ignored — reapable residue should
never be committable. Each run probes every surface with git check-ignore
and reports [drift] for any glob that is not ignored. Drift is report-only
(the tracked-file protection above is what actually prevents damage).
Session-end integration
Section titled “Session-end integration”- warp-drive: the
session_endingphase runsbob-reap --apply --owner warp-drive,dev-up,qa— the ending session’s own surfaces. Its own state file is still live at that point and is skipped by the liveness guard; it is removed by thesession_endedtransition itself. - swarm: the run composition ends with a
reapstep (bob-reap --apply --owner swarm) aftersummary, dropping the run’s.swarm/scratch once every branch is done.
Both integrations are non-blocking: a missing manifest is a clean no-op, and
an exit 2 (manifest needs attention) is noted in the session summary rather
than retried.
What is deliberately out of scope
Section titled “What is deliberately out of scope”Git stashes (human-gated by standing convention), .codex/ and AGENTS.md
(tool-experiment artifacts, human decision), provisions/ports.json drift
(pending commit work), and docs/audits/ (durable record — keep-listed).
The spurious root dev.json (#1481) is a reap target as an interim
mitigation; the root-cause fix stays with that issue.
make test-bob-reap runs tests/test-bob-reap.js — manifest validation,
keep-list refusal and backstop, liveness-guard skips, TTL logic, tracked-file
protection, owner filtering, and drift reporting, over throwaway git fixtures.