treebox 1.0.0__tar.gz → 1.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. {treebox-1.0.0 → treebox-1.0.2}/.agents/skills/treebox/SKILL.md +9 -8
  2. {treebox-1.0.0 → treebox-1.0.2}/.agents/skills/treebox-e2e/SKILL.md +1 -1
  3. {treebox-1.0.0 → treebox-1.0.2}/.agents/skills/treebox-e2e/references/scenarios.md +36 -5
  4. treebox-1.0.2/CHANGELOG.md +84 -0
  5. {treebox-1.0.0 → treebox-1.0.2}/CLAUDE.md +24 -7
  6. {treebox-1.0.0 → treebox-1.0.2}/CONTRIBUTING.md +22 -3
  7. {treebox-1.0.0 → treebox-1.0.2}/PKG-INFO +14 -9
  8. {treebox-1.0.0 → treebox-1.0.2}/README.md +13 -8
  9. {treebox-1.0.0 → treebox-1.0.2}/docs/configuration.md +25 -5
  10. {treebox-1.0.0 → treebox-1.0.2}/docs/how-it-works.md +22 -3
  11. {treebox-1.0.0 → treebox-1.0.2}/docs/index.md +2 -1
  12. {treebox-1.0.0 → treebox-1.0.2}/docs/install.md +12 -12
  13. {treebox-1.0.0 → treebox-1.0.2}/docs/usage.md +101 -27
  14. {treebox-1.0.0 → treebox-1.0.2}/scripts/golden-diff.sh +71 -17
  15. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/assets.py +92 -3
  16. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/cli.py +155 -24
  17. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/config.py +41 -10
  18. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/ecosystems.py +6 -3
  19. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/git.py +56 -15
  20. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/models.py +15 -4
  21. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/output.py +173 -68
  22. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/provision.py +154 -92
  23. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/resolve.py +6 -1
  24. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/runners/__init__.py +7 -6
  25. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/runners/base.py +50 -3
  26. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/runners/docker.py +167 -48
  27. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/runners/host.py +12 -2
  28. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/state.py +17 -1
  29. treebox-1.0.2/tests/golden/README.md +21 -0
  30. treebox-1.0.2/tests/golden/doctor-host-no-env.txt +18 -0
  31. treebox-1.0.2/tests/golden/list.txt +16 -0
  32. {treebox-1.0.0 → treebox-1.0.2}/tests/test_integration.py +667 -4
  33. {treebox-1.0.0 → treebox-1.0.2}/tests/test_units.py +888 -24
  34. treebox-1.0.0/CHANGELOG.md +0 -39
  35. treebox-1.0.0/tests/golden/README.md +0 -19
  36. {treebox-1.0.0 → treebox-1.0.2}/.agents/skills/treebox-e2e/fixture.sh +0 -0
  37. {treebox-1.0.0 → treebox-1.0.2}/.github/workflows/autofix.yml +0 -0
  38. {treebox-1.0.0 → treebox-1.0.2}/.github/workflows/ci.yml +0 -0
  39. {treebox-1.0.0 → treebox-1.0.2}/.github/workflows/claude.yml +0 -0
  40. {treebox-1.0.0 → treebox-1.0.2}/.github/workflows/docs.yml +0 -0
  41. {treebox-1.0.0 → treebox-1.0.2}/.github/workflows/release.yml +0 -0
  42. {treebox-1.0.0 → treebox-1.0.2}/.gitignore +0 -0
  43. {treebox-1.0.0 → treebox-1.0.2}/.pre-commit-config.yaml +0 -0
  44. {treebox-1.0.0 → treebox-1.0.2}/AGENTS.md +0 -0
  45. {treebox-1.0.0 → treebox-1.0.2}/LICENSE +0 -0
  46. {treebox-1.0.0 → treebox-1.0.2}/ROADMAP.md +0 -0
  47. {treebox-1.0.0 → treebox-1.0.2}/assets/treebox-demo.gif +0 -0
  48. {treebox-1.0.0 → treebox-1.0.2}/assets/treebox-logo.png +0 -0
  49. {treebox-1.0.0 → treebox-1.0.2}/docs/agents.md +0 -0
  50. {treebox-1.0.0 → treebox-1.0.2}/docs/assets/treebox-lifecycle.gif +0 -0
  51. {treebox-1.0.0 → treebox-1.0.2}/docs/assets/treebox-logo.png +0 -0
  52. {treebox-1.0.0 → treebox-1.0.2}/docs/javascripts/treebox.js +0 -0
  53. {treebox-1.0.0 → treebox-1.0.2}/docs/stylesheets/extra.css +0 -0
  54. {treebox-1.0.0 → treebox-1.0.2}/hooks/copy_page.py +0 -0
  55. {treebox-1.0.0 → treebox-1.0.2}/install.sh +0 -0
  56. {treebox-1.0.0 → treebox-1.0.2}/mkdocs.yml +0 -0
  57. {treebox-1.0.0 → treebox-1.0.2}/pyproject.toml +0 -0
  58. {treebox-1.0.0 → treebox-1.0.2}/scripts/validate.sh +0 -0
  59. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/__init__.py +0 -0
  60. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/assets/container/Dockerfile +0 -0
  61. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/assets/container/allowed-domains.sh +0 -0
  62. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/assets/container/container.json +0 -0
  63. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/assets/container/firewall.json +0 -0
  64. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/assets/container/init-firewall.sh +0 -0
  65. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/assets/container/post-create.sh +0 -0
  66. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/assets/pre-push +0 -0
  67. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/forge.py +0 -0
  68. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/harnesses.py +0 -0
  69. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/locking.py +0 -0
  70. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/names.py +0 -0
  71. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/py.typed +0 -0
  72. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/status.py +0 -0
  73. {treebox-1.0.0 → treebox-1.0.2}/src/treebox/system.py +0 -0
  74. {treebox-1.0.0 → treebox-1.0.2}/tests/conftest.py +0 -0
  75. {treebox-1.0.0 → treebox-1.0.2}/tests/golden/create-docker-json.txt +0 -0
  76. {treebox-1.0.0 → treebox-1.0.2}/tests/golden/create-docker-print.txt +0 -0
  77. {treebox-1.0.0 → treebox-1.0.2}/tests/golden/create-dryrun-docker-json.txt +0 -0
  78. {treebox-1.0.0 → treebox-1.0.2}/tests/golden/create-dryrun-docker.txt +0 -0
  79. {treebox-1.0.0 → treebox-1.0.2}/tests/golden/create-dryrun-host-json.txt +0 -0
  80. {treebox-1.0.0 → treebox-1.0.2}/tests/golden/create-dryrun-host.txt +0 -0
  81. {treebox-1.0.0 → treebox-1.0.2}/tests/golden/create-host-json.txt +0 -0
  82. {treebox-1.0.0 → treebox-1.0.2}/tests/golden/create-host-print.txt +0 -0
  83. {treebox-1.0.0 → treebox-1.0.2}/tests/golden/create-slash-json.txt +0 -0
  84. {treebox-1.0.0 → treebox-1.0.2}/tests/golden/doctor-docker-json.txt +0 -0
  85. {treebox-1.0.0 → treebox-1.0.2}/tests/golden/doctor-docker.txt +0 -0
  86. {treebox-1.0.0 → treebox-1.0.2}/tests/golden/doctor-host-json.txt +0 -0
  87. {treebox-1.0.0 → treebox-1.0.2}/tests/golden/doctor-host.txt +0 -0
  88. {treebox-1.0.0 → treebox-1.0.2}/tests/golden/doctor-no-login.txt +0 -0
  89. {treebox-1.0.0 → treebox-1.0.2}/tests/golden/enter-docker-json.txt +0 -0
  90. {treebox-1.0.0 → treebox-1.0.2}/tests/golden/enter-docker-print.txt +0 -0
  91. {treebox-1.0.0 → treebox-1.0.2}/tests/golden/enter-host-json.txt +0 -0
  92. {treebox-1.0.0 → treebox-1.0.2}/tests/golden/enter-host-print.txt +0 -0
  93. {treebox-1.0.0 → treebox-1.0.2}/tests/test_contract.py +0 -0
  94. {treebox-1.0.0 → treebox-1.0.2}/uv.lock +0 -0
@@ -26,11 +26,11 @@ Provisioning (fetch → resolve branch → worktree add → copy submodules →
26
26
  ## Command reference
27
27
 
28
28
  - `create [NAME]` — provision and launch. Flags: `--base <branch>` `--checkout <existing-branch>` `--isolation host|docker` `--harness claude|codex` `--template <name>` `--cold` `--no-fetch` `--firewall/--no-firewall` `--dry-run` `--print` `--json` `--repo <path>` `--root <dir>` `--quiet` `--verbose`.
29
- - `enter REF [-- AGENT_ARGS…]` — re-launch in an existing worktree; refreshes `.env`, reuses recorded isolation/firewall/harness/template defaults, and re-syncs deps only if the lockfile changed. Flags: `--isolation` `--harness` `--template <name>` `--cold` `--print` `--json` `--repo` `--root` `--quiet` `--verbose`.
30
- - `list` (alias `ls`) table of worktrees (branch · isolation · deps freshness · `.env` presence · directory). Flags: `--repo` `--root` `--json`.
29
+ - `enter REF [-- AGENT_ARGS…]` — re-launch in an existing worktree; refreshes `.env`, reuses recorded isolation/firewall/harness/template defaults, and re-syncs deps only if the lockfile changed (an interrupted setup is finished rather than skipped). Flags: `--isolation` `--harness` `--template <name>` `--cold` `--print` `--json` `--repo` `--root` `--quiet` `--verbose`.
30
+ - `list` (alias `ls`) - table of worktrees (branch · recorded isolation · deps freshness · `.env` presence), with a root footer that combines with each stable name to identify its directory. Missing legacy state is shown honestly as `unknown`; `--json` includes each full `path`. Flags: `--repo` `--root` `--json`.
31
31
  - `teardown [REF...]` (alias `rm`) — remove selected worktrees; caches are left intact. Flags: `--force` `--delete-branch` `--remove-volumes` `--skip-container` `--json` `--isolation` `--repo` `--root` `--quiet` `--verbose`.
32
32
  - `template init NAME` — scaffold an operator-owned sandbox template into `$TREEBOX_HOME/templates/<name>` (copies the full required file set from the default). Flags: `--from <template>` `--force` `--json`. Then edit its `Dockerfile`/`container.json` and select with `--template <name>`.
33
- - `template list` (alias `ls`) — table of templates (name · source · status) with the configured default flagged; when `default` still resolves to the bundled image, also highlights what that image ships. Flags: `--json`.
33
+ - `template list` (alias `ls`) — table of templates (name · source · firewall · status; status flags missing required files and malformed JSON, firewall shows whether the template can serve `--firewall`) with the configured default flagged; when `default` still resolves to the bundled image, also highlights what that image ships. Flags: `--json`.
34
34
  - `template path [NAME]` — print a template's resolved directory (for scripting: `cd "$(treebox template path <name>)"`).
35
35
  - `doctor` — check git, login credentials, UID/GID, and isolation deps. Flags: `--isolation` `--repo` `--json`.
36
36
  - `version` / `--version` / `-V` — print the version.
@@ -54,7 +54,7 @@ treebox teardown fix-auth --force --delete-branch
54
54
  Provision without launching the agent (for scripting / over SSH / when you only need the worktree prepared):
55
55
 
56
56
  ```bash
57
- treebox create fix-auth --dry-run # print the git/setup commands it would run; change nothing
57
+ treebox create fix-auth --dry-run # print the git/setup commands it would run; change nothing (fails like a real create on conflicts)
58
58
  treebox create fix-auth --print # prints the launch command (self-contained: carries the worktree dir, runnable from anywhere), no launch
59
59
  treebox create fix-auth --json # JSON {schemaVersion, name, worktree_path, branch, base, entry_command, created}
60
60
  treebox list --json # JSON {schemaVersion, worktrees:[…]}
@@ -70,7 +70,7 @@ Branch on the **exit code**, not the text:
70
70
  | `0` | success |
71
71
  | `1` | runtime failure, missing runner dependency, or failed doctor hard check |
72
72
  | `2` | usage — bad name/branch/config/ref or option |
73
- | `3` | not-found — the worktree the command needs doesn't exist |
73
+ | `3` | not-found — the worktree, branch, or template the command needs doesn't exist |
74
74
  | `4` | auth/permission — `origin` fetch failed |
75
75
  | `5` | conflict — already exists / worktree lock held / uncommitted changes |
76
76
 
@@ -84,10 +84,11 @@ With `--json`, a failure prints **one object on stderr**: `{"schemaVersion":1,"e
84
84
  - Every worktree gets a pre-push guard that blocks pushing any `treebox/*` ref: generated placeholder branches must be renamed (`git branch -m <type>/<short-name>`) before push; explicitly named branches push as-is.
85
85
  - `create` launches the agent (Claude by default) after provisioning and exits with the agent process's exit code. Use `--harness codex`, or `--print` / `--json` / `--dry-run` to avoid launching.
86
86
  - Re-running `create` on a fully provisioned same-name worktree is a `SLUG_CONFLICT` (exit `5`), not an idempotent enter. Use `enter` to re-launch. If the dir exists but setup never finished (a prior run died mid-provision), `create` resumes setup instead of launching into a half-built tree.
87
+ - `--dry-run` enforces the same read-only preconditions as a real `create` with the same exit codes and JSON errors (`BRANCH_EXISTS`, `SLUG_CONFLICT`, `NOT_FOUND` for a missing `--checkout`/base branch, `BRANCH_IN_USE`, `TEMPLATE_NOT_FOUND` for a docker `--template` that doesn't resolve, `TEMPLATE_INVALID` for one that resolves but whose `container.json`, or `firewall.json` with `--firewall`, is broken), so it doubles as a safe preflight; the half-provisioned same-name case previews resuming setup instead of a fresh `worktree add`. Verdicts reflect the refs already available locally (a dry run never fetches); run `git fetch origin` first when exact parity with create's fetch is required. Nothing changes on disk or in git either way.
87
88
  - **Freshness is enforced.** `create` *requires* a successful `git fetch origin` and branches from the freshly-fetched `origin/<base>` (preferred over a stale local branch). If the fetch fails it exits `4` and does **not** fall back to stale refs. In a terminal, git/ssh will prompt for credentials (no pre-loaded ssh-agent needed); headless/`--json` runs need working non-interactive auth. Pass `--no-fetch` only when the user explicitly accepts possibly-stale local refs (e.g. offline). A repo with no `origin` skips the fetch. Run `doctor` to check origin reachability up front.
88
- - `enter` recomputes the dependency-manifest hash and re-syncs only when deps changed; it always refreshes `.env`. It reuses the worktree's recorded isolation/firewall/harness/template defaults, so config changes do not silently change an existing worktree. A mismatched explicit `--isolation` is a conflict; explicit `--harness` and `--template` are per-session overrides. Pass extra agent args after `--`. `--cold` bypasses the shared cache.
89
+ - `enter` recomputes the dependency-manifest hash and re-syncs only when deps changed; if a prior run died before setup completed, it finishes setup even when the hash is unchanged. It always refreshes `.env`. It reuses the worktree's recorded isolation/firewall/harness/template defaults, so config changes do not silently change an existing worktree. A mismatched explicit `--isolation` is a conflict; explicit `--harness` and `--template` are per-session overrides. Pass extra agent args after `--`. `--cold` bypasses the shared cache. For a docker worktree whose container stopped (host reboot, manual `docker stop`), the container is restarted and the firewall re-applied before the agent launches - and before a `--print`/`--json` command is emitted, so the emitted command is never dead on replay.
89
90
  - `--cold` is the escape hatch for a corrupted cache: from-source dependency resolution.
90
- - `teardown` confirms interactively. Without a TTY (scripts/agents) or with `--json` it refuses unless `--force` (exit `5`); `--force` is also required to remove a worktree with uncommitted changes. `--json` prints a record of what was removed to stdout. `--skip-container` leaves containers/images alone; cleanup failures are best-effort (`container: "failed"`) and do not undo worktree removal. With host isolation there are no containers/volumes to remove.
91
+ - `teardown` confirms interactively. Without a TTY (scripts/agents) or with `--json` it refuses unless `--force` (exit `5`); `--force` is also required to remove a worktree with uncommitted changes. A corrupt worktree (its `.git` pointer file missing) is never misreported as `DIRTY_WORKTREE` from the main checkout's uncommitted changes: it takes the normal confirmation path (`NEEDS_CONFIRMATION` under `--json`/non-TTY; `--force` removes the directory and git's stale registration without touching the main checkout; container cleanup still follows the recorded isolation, recovered from git's own registration rather than the missing pointer). `--json` prints a record of what was removed to stdout. `--skip-container` leaves containers/images alone; cleanup failures are best-effort (a failed container/image removal is `container: "failed"`, every remaining cleanup step is still attempted) and do not undo worktree removal. A failed volume removal alone reports `volumes_removed: false` without marking the container `failed`. If Docker is unavailable, the worktree is still removed and the record reports `container: "skipped"` with `volumes_removed: false`; `volumes_removed` is `true` only when docker volumes were actually removed. With host isolation there are no containers/volumes to remove (`volumes_removed` is always `false`).
91
92
  - Only one `create`/`enter`/`teardown` may operate on a given worktree name at a time (a per-name lock, exit `5` if held). Wait rather than retrying immediately.
92
93
  - Output: a compact spinner + green checkmarks with right-aligned timings on stderr, degrading gracefully off-TTY and under `NO_COLOR` (no color/animation). `--verbose` streams raw command output; `--quiet` suppresses progress. `--json` success payloads and `--print` commands write to stdout; human `--dry-run` writes its plan to stderr, while `--dry-run --json` writes JSON to stdout.
93
94
  - The container firewall is off by default; pass `--firewall` to merge in the generated outbound allowlist. `--firewall`/`--no-firewall` is a tri-state override — an explicit flag wins in either direction and absence falls through to the `firewall` config default (so `--no-firewall` opts a single run out of a `firewall = true` config). The created-time choice is recorded, so `enter` reuses it rather than re-resolving the config.
@@ -95,6 +96,6 @@ With `--json`, a failure prints **one object on stderr**: `{"schemaVersion":1,"e
95
96
  ## Notes
96
97
 
97
98
  - Authentication is **subscription-login only**: a one-time `claude` / `codex` login on the host (`~/.claude`, `~/.codex`) is reused everywhere. `doctor` verifies those dirs. `ANTHROPIC_API_KEY` is never used.
98
- - Defaults (`isolation`, `harness`, `base`, `root`, `env_file`, `firewall`, caches, custom setup hook) are configurable via a user-level TOML at `$TREEBOX_CONFIG`, else `$TREEBOX_HOME/config.toml` (default `~/.treebox/config.toml`). There is no repo-level config (a repo you don't trust must not run host commands).
99
+ - Defaults (`isolation`, `harness`, `base`, `root`, `env_file`, `firewall`, caches, custom setup hook) are configurable via a user-level TOML at `$TREEBOX_CONFIG`, else `$TREEBOX_HOME/config.toml` (default `~/.treebox/config.toml`). There is no repo-level config (a repo you don't trust must not run host commands). Path-valued settings (`root`, `env_file`, `caches` entries, the `TREEBOX_*` env vars) and the `--repo`/`--root` flags expand a leading `~`; plain relative `root`/`env_file` values stay repo-relative.
99
100
  - The sandbox container is always defined by the operator template, never by anything in the target repo. Templates are named (`--template <name>`, default `default`): resolved from `$TREEBOX_TEMPLATE_DIR`, then `$TREEBOX_HOME/templates/<name>/` (default `~/.treebox/templates/<name>/`), then the bundled default (only for `default`). An unknown name is a loud error, not a fallback. The rendered config + build context live *outside* the mounted worktree (`<root>/.containers/<branch>/`) and are never mounted in, so the boxed agent can't read or edit its own cage.
100
101
  - Shared package caches live on the host and are reused across worktrees (and bind-mounted into containers), so a fresh worktree sets up at near warm-tree speed.
@@ -99,7 +99,7 @@ scenario you add.
99
99
  e2e_cleanup # reaps treebox-e2e-* docker containers/images, then removes $SBX
100
100
  ```
101
101
 
102
- `e2e_cleanup` is one pass: it reaps every `treebox-e2e-*` docker artifact — so H5's
102
+ `e2e_cleanup` is one pass: it reaps every `treebox-e2e-*` docker artifact — so H6's
103
103
  intentional `--skip-container` leftover and any containers from a crashed run go
104
104
  too — and then removes the sandbox dir. If a run died before you could call it,
105
105
  `e2e_reap_docker` alone clears the docker side; it only ever touches
@@ -42,7 +42,10 @@ Conventions used below:
42
42
  no literal to match; a `0.7.1.dev12`-style value is normal off a tag.)
43
43
  - [ ] **A2 doctor human** — `tb doctor --repo "$REPO"`
44
44
  Expect: exit 0 when git + at least one harness login are healthy; check
45
- rows on stderr are readable off-TTY (no raw ANSI garbage when piped).
45
+ rows on stdout are readable off-TTY (no raw ANSI garbage when piped).
46
+ Against a repo with no `.env`, the `.env` row is a muted `·` note ending
47
+ in `· optional` (still showing the configured path), never a red `✗`,
48
+ and the exit code stays 0.
46
49
  - [ ] **A3 doctor json** — `tb doctor --repo "$REPO" --json`
47
50
  Expect: exit 0; JSON on stdout with `ok`, `isolation`, `checks[]`,
48
51
  `advisories`; `ok` true iff every hard check passed.
@@ -94,6 +97,16 @@ Conventions used below:
94
97
  on **stderr**, stdout empty. JSON mode: object on **stdout** with
95
98
  `"dry_run": true` and a `commands` array. Both: exit 0 and
96
99
  `$ROOT/plan-x` does not exist.
100
+ - [ ] **B10b --dry-run preflights like a real create** -
101
+ `tb create fix-auth --repo "$REPO" --root "$SBX/wts2" --dry-run --json`
102
+ (branch `fix-auth` exists from B1)
103
+ Expect: exit 5; JSON error `BRANCH_EXISTS` on stderr, stdout empty;
104
+ `$SBX/wts2/fix-auth` not created and `git -C "$REPO" worktree list`
105
+ unchanged - a dry run fails with the same error a real create would,
106
+ never printing a plan a real run refuses. Same parity for
107
+ `SLUG_CONFLICT` (existing dir), `NOT_FOUND` (missing `--checkout`/base
108
+ branch, exit 3), and `BRANCH_IN_USE` (`--checkout` of a checked-out
109
+ branch).
97
110
  - [ ] **B11 --print** — `tb create print-x --repo "$REPO" --root "$ROOT" --print`
98
111
  Expect: exit 0; stdout is exactly one runnable launch command that carries
99
112
  the worktree dir (self-contained — no reliance on cwd) and the harness
@@ -149,8 +162,10 @@ Prereq: `fix-auth` worktree from B1 exists.
149
162
 
150
163
  - [ ] **D1 human table** — `tb list --repo "$REPO" --root "$ROOT"`
151
164
  Expect: exit 0; one row per worktree with name, live branch, isolation,
152
- deps freshness, `.env` presence; renamed branches (C4) show the new
153
- branch under the stable name.
165
+ deps freshness, `.env` presence, plus one worktree-root footer; renamed
166
+ branches (C4) show the new branch under the stable name. A registration
167
+ without readable Treebox state shows isolation as `unknown`, never the
168
+ current config default.
154
169
  - [ ] **D2 json shape** — `tb list --repo "$REPO" --root "$ROOT" --json`
155
170
  Expect: exit 0; `worktrees[]` where each entry has `name`, `branch`,
156
171
  `unnamed`, `missing`, `path`, `base`, `isolation`, `harness`, `deps`
@@ -183,6 +198,15 @@ Prereq: `fix-auth` worktree from B1 exists.
183
198
  - [ ] **E6 teardown resolves original name after rename** — covered by tearing
184
199
  down `fix-auth` (E1–E3) *after* C4 renamed its branch: the original name
185
200
  must still resolve.
201
+ - [ ] **E7 corrupt worktree is not "dirty"** - create a throwaway `e2e-corrupt`,
202
+ remove its pointer file (`rm "$ROOT/e2e-corrupt/.git"`), dirty the main
203
+ checkout (`touch "$REPO/main-dirt.txt"`), then teardown without `--force`
204
+ (`--json`)
205
+ Expect: exit 5 with `NEEDS_CONFIRMATION`, *not* `DIRTY_WORKTREE` (the main
206
+ checkout's dirt must not be attributed to the corrupt tree); then with
207
+ `--force --json`: exit 0, directory gone, registration pruned from
208
+ `git -C "$REPO" worktree list`, and `$REPO/main-dirt.txt` untouched
209
+ (clean it up afterwards).
186
210
 
187
211
  ## F. template
188
212
 
@@ -227,7 +251,7 @@ Each writes `$TREEBOX_CONFIG`, runs, then restores the empty file (`: > "$TREEBO
227
251
 
228
252
  **Name every worktree here with the `e2e-` prefix** (see the docker naming marker
229
253
  in the preamble): the container/image become `treebox-e2e-*`, so `e2e_cleanup` /
230
- `e2e_reap_docker` remove them even when a scenario (H5's `--skip-container`) leaves
254
+ `e2e_reap_docker` remove them even when a scenario (H6's `--skip-container`) leaves
231
255
  one behind on purpose.
232
256
 
233
257
  - [ ] **H1 docker create** — `tb create e2e-dockered --repo "$REPO" --root "$ROOT" --isolation docker --print`
@@ -255,7 +279,14 @@ one behind on purpose.
255
279
  `--isolation` flag
256
280
  Expect: exit 0; still docker (recorded at create), even if the config
257
281
  default says host.
258
- - [ ] **H5 docker teardown** — `tb teardown e2e-dockered … --force --json`; then
282
+ - [ ] **H5 stopped container revived on enter** — `docker stop` the
283
+ `treebox-e2e-dockered-*` container, then `tb enter e2e-dockered … --print`
284
+ Expect: exit 0; the container is running again (`docker ps`) *before* the
285
+ command is printed, so the emitted `docker exec …` replays cleanly — a
286
+ stopped sandbox never yields a dead entry command. With a `--firewall`
287
+ worktree, egress lockdown is re-applied on the restart (iptables rules
288
+ don't survive a container restart).
289
+ - [ ] **H6 docker teardown** — `tb teardown e2e-dockered … --force --json`; then
259
290
  `--remove-volumes` on a fresh `e2e-dockered2`; then `--skip-container` on a
260
291
  fresh `e2e-dockered3`
261
292
  Expect: exit 0 each; `--remove-volumes` reports `volumes_removed: true`;
@@ -0,0 +1,84 @@
1
+ # Changelog
2
+
3
+ All notable changes to treebox will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [1.0.1] - 2026-07-10
11
+
12
+ ### Fixed
13
+
14
+ - Path-valued configuration now expands a leading `~` consistently for
15
+ `root`, `env_file`, cache paths, `TREEBOX_*` environment variables, and the
16
+ `--repo` / `--root` CLI flags. Relative `root` and `env_file` values remain
17
+ repository-relative (#6).
18
+
19
+ - `create --dry-run` now enforces the same read-only preconditions as a real
20
+ `create` - `BRANCH_EXISTS` for a name whose branch already exists,
21
+ `SLUG_CONFLICT` for an occupied worktree directory, `NOT_FOUND` (exit 3) for
22
+ a missing `--checkout` or base branch, `BRANCH_IN_USE` for a `--checkout`
23
+ branch already backing another worktree - failing with the same exit codes
24
+ and JSON errors instead of printing a plan a real run would refuse (#4).
25
+ A half-provisioned same-name worktree previews finishing setup, mirroring
26
+ real `create`; a dry run still changes nothing on disk or in git.
27
+
28
+ - `teardown` no longer misreports a corrupt worktree (a registered directory
29
+ whose `.git` pointer file is missing) as `DIRTY_WORKTREE` when the *main*
30
+ checkout has uncommitted changes: git linkage is verified before the
31
+ dirtiness check, so the corrupt tree takes the normal confirmation path
32
+ instead (`NEEDS_CONFIRMATION` under `--json` / non-TTY), and `--force`
33
+ removes the directory and git's stale registration without touching the
34
+ main checkout's files (#3). Container cleanup survives the corruption too:
35
+ the recorded isolation and template are recovered through git's own worktree
36
+ registration rather than the missing `.git` pointer, so a corrupt docker
37
+ worktree is still torn down with the runner it was created with.
38
+
39
+ - `doctor` no longer renders a missing (optional) `.env` as a red `✗` failure
40
+ row before concluding all-good: the row is now a muted `·` note marked
41
+ `optional`, still showing the configured path (#5). Exit codes and the
42
+ `--json` payload are unchanged.
43
+
44
+ - `treebox enter` now finishes an interrupted setup: when a prior run died
45
+ before setup completed, `enter` re-runs setup (reporting "setup never
46
+ completed") instead of skipping it as up-to-date because the lockfile hash
47
+ is unchanged, and records the worktree as provisioned once it succeeds (#7).
48
+
49
+ - `teardown --json` now reports what runner cleanup actually did: when Docker
50
+ is unavailable the worktree is still removed but the record says
51
+ `container: "skipped"` with `volumes_removed: false` (even under
52
+ `--remove-volumes`), and `volumes_removed` is `true` only when docker
53
+ volumes were really removed - never on host isolation (#2).
54
+
55
+ ## [1.0.0] - 2026-07-06
56
+
57
+ First stable release. treebox is Apache-2.0 licensed and ready for production
58
+ use: isolated, ready-to-run git worktrees for AI coding agents, run host-native
59
+ or inside a docker sandbox.
60
+
61
+ ### Added
62
+
63
+ - `treebox template init|list|path` — scaffold and inspect operator-owned docker
64
+ sandbox templates from any install (`uv tool` / pipx included), so customizing
65
+ a sandbox no longer means hand-copying the shipped template directory or
66
+ reaching into the package internals (#155).
67
+
68
+ ### Changed
69
+
70
+ - **Breaking:** `treebox create NAME` now uses `NAME` directly as the branch
71
+ name, created fresh from `origin/<base>` — no more `treebox/NAME`
72
+ placeholder or forced rename for explicitly named work (#153). Names may
73
+ contain slashes (`feature/auth`); the directory flattens them to `--`
74
+ (`feature--auth`). Scripts that expected the guarded `treebox/<NAME>`
75
+ placeholder get a directly pushable `NAME` branch instead. The `treebox/`
76
+ prefix is rejected as a name (`INVALID_NAME`), and naming a branch that
77
+ already exists locally or on origin is a new `BRANCH_EXISTS` conflict
78
+ (exit 5) pointing at `--checkout`.
79
+ - The pre-push guard is now installed in **every** worktree (including
80
+ explicit names and `--checkout`): pushing any `treebox/*` ref is always
81
+ blocked, so generated placeholder branches must still be renamed before
82
+ push. Nameless `create` behavior is unchanged.
83
+ - `enter REF` for a branch that exists but has no worktree now hints
84
+ `treebox create --checkout REF` instead of the generic not-found advice.
@@ -78,7 +78,10 @@ Module map:
78
78
  `get_runner` and `VALID_ISOLATION` derive from it. Doctor-facing vocabulary
79
79
  (preflight detail, whether a login is a hard gate) lives in `RunnerFacts`,
80
80
  not in the run methods; teardown options (docker's `remove_volumes`) arrive
81
- at the runner's constructor, never through the protocol.
81
+ at the runner's constructor, never through the protocol. Docker image and
82
+ volume ownership lives in the runner-owned
83
+ `.containers/<worktree>/treebox-resources.json` manifest outside the
84
+ sandbox mount, never in sandbox-writable worktree state.
82
85
  - **`cli.py`** (Typer) is the entry point: `create [NAME] / enter <ref> /
83
86
  list / teardown <ref>... / template <init|list|path> / doctor / version`
84
87
  (`ls`/`rm` are hidden aliases of list/teardown, `template ls` of
@@ -89,7 +92,9 @@ Module map:
89
92
  name → live branch → unique substring (`resolve.py`); ambiguity exits 2.
90
93
  `create` and `enter` share `_run_session` for runner preflight, the
91
94
  per-name lock, provision error classification, and the final
92
- `--json`/`--print`/launch fork. `_reconcile_with_state` folds recorded
95
+ `--json`/`--print`/launch fork (the emit fork calls `runner.prepare_entry`
96
+ so the printed `entry_command` is never dead on replay).
97
+ `_reconcile_with_state` folds recorded
93
98
  creation-time choices into existing-worktree sessions before the runner and
94
99
  harness objects are resolved: recorded isolation conflicts with a mismatched
95
100
  explicit `--isolation`, while recorded firewall/harness/template protect an
@@ -105,15 +110,23 @@ Module map:
105
110
  - **`state.py`** stores per-worktree state (lockfile hash + provisioning
106
111
  choices) inside the worktree's private git dir (`.git/worktrees/<id>/`), so it
107
112
  never appears in `git status` and is pruned with the worktree. The lockfile
108
- hash is what lets `enter` re-sync only when deps changed; the recorded
109
- choices are what let `enter`/`teardown` recover the worktree's created-time
110
- isolation, firewall, harness, and template defaults.
113
+ hash is what lets `enter` re-sync only when deps changed (the recorded
114
+ `provisioned` flag makes `enter` finish an interrupted setup even when the
115
+ hash matches); the recorded choices are what let `enter`/`teardown` recover
116
+ the worktree's created-time isolation, firewall, harness, and template
117
+ defaults. This state is sandbox-writable and therefore never authorizes
118
+ destructive host cleanup. `teardown` reads the non-destructive choices
119
+ through the repo's own worktree
120
+ registration (`load_registered`) rather than the worktree's `.git` pointer,
121
+ so a corrupt tree's recorded choices still drive container cleanup.
111
122
  - **`models.py`** holds the `Worktree` value object and the name-as-identity
112
123
  rule: the *name* is the directory leaf and lock key, never renamed; the
113
124
  *branch* is a mutable attribute read live from git (the agent renames it
114
125
  with `git branch -m`). Branch-shaped inputs (`create feature/auth`,
115
126
  `create --checkout feature/auth`) derive the name by flattening slashes to
116
- `--` (`feature--auth`); generated names come from `names.py`.
127
+ `--` (`feature--auth`); generated names come from `names.py`. Also home to
128
+ `expand_user`, the tolerant `~`-expansion helper every path-consuming module
129
+ shares (an unresolvable `~user` stays literal instead of raising).
117
130
  - **`config.py`** is **user-level TOML only** (`$TREEBOX_CONFIG`, else
118
131
  `$TREEBOX_HOME/config.toml`, default `~/.treebox/config.toml`), never read
119
132
  from the target repo — a repo-level config could run arbitrary host commands.
@@ -170,9 +183,13 @@ What a sandboxed backend must **guarantee** (the security invariants):
170
183
  never exposed to the sandbox (they hold host-executed config).
171
184
  - The sandbox-defining config is rendered outside the mount, so a boxed
172
185
  agent cannot edit the definition of its own box.
186
+ - Destructive cleanup trusts only runner-owned metadata outside the mount;
187
+ sandbox-writable worktree state never records Docker volume ownership.
173
188
  - The shared `.git/hooks` is presented read-only (host git executes it).
174
189
  - Egress lockdown, when enabled, exists before any workspace-derived code
175
- runs (firewall-before-post-create).
190
+ runs (firewall-before-post-create), and is re-established when
191
+ `prepare_entry` restarts a stopped sandbox (iptables rules don't survive
192
+ a container restart).
176
193
  - Only user-level treebox config is ever read — never the target repo's.
177
194
 
178
195
  What a backend may **assume** (host locality):
@@ -53,9 +53,9 @@ test against a throwaway local remote:
53
53
  ./scripts/validate.sh
54
54
  ```
55
55
 
56
- For issue #141 behavior-preserving refactors, `scripts/golden-diff.sh` is the
57
- focused snapshot-only gate. Refresh snapshots only after the issue explicitly
58
- accepts a behavior change.
56
+ For behavior-preserving refactors, `scripts/golden-diff.sh` is the focused
57
+ snapshot-only gate. Refresh snapshots only after you've deliberately accepted a
58
+ behavior change.
59
59
 
60
60
  ```bash
61
61
  scripts/golden-diff.sh
@@ -67,6 +67,25 @@ For docs-only changes, this is enough:
67
67
  uv run --extra docs mkdocs build --strict
68
68
  ```
69
69
 
70
+ ## Commit messages
71
+
72
+ treebox uses [Conventional Commits](https://www.conventionalcommits.org/):
73
+ `type(scope): summary`, imperative and lowercase. Common types are `feat`,
74
+ `fix`, `docs`, `refactor`, `test`, and `chore`.
75
+
76
+ ```
77
+ fix: copy .env before the runner setup step
78
+ feat(runners): add a podman isolation backend
79
+ docs: document the template command
80
+ ```
81
+
82
+ **Breaking changes** take a `!` (`feat!:`, `fix!:`) or a `BREAKING CHANGE:`
83
+ footer. treebox's public contract is its CLI surface, its `--json` payloads
84
+ (which carry a `schemaVersion`), and its exit codes: if you change that
85
+ observable output, bump `SCHEMA_VERSION` when the JSON shape changes and
86
+ regenerate the golden snapshots with `scripts/golden-diff.sh --update`. Note
87
+ user-facing changes under `[Unreleased]` in `CHANGELOG.md`.
88
+
70
89
  ## Boundaries
71
90
 
72
91
  Please preserve the basics: fresh refs by default, no trust in target-repo sandbox config, and stable CLI output for scripts.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: treebox
3
- Version: 1.0.0
3
+ Version: 1.0.2
4
4
  Summary: Isolated, ready-to-run git worktrees for AI coding agents — host-native or docker-sandboxed.
5
5
  Project-URL: Homepage, https://github.com/Seth-Peters/treebox
6
6
  Project-URL: Documentation, https://seth-peters.github.io/treebox/
@@ -49,6 +49,7 @@ Description-Content-Type: text/markdown
49
49
  <a href="https://github.com/Seth-Peters/treebox/actions/workflows/ci.yml"><img src="https://github.com/Seth-Peters/treebox/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
50
50
  <a href="https://seth-peters.github.io/treebox/"><img src="https://img.shields.io/badge/docs-seth--peters.github.io%2Ftreebox-2f6f4f" alt="Documentation"></a>
51
51
  <img src="https://img.shields.io/badge/python-3.11%2B-3776ab" alt="Python 3.11+">
52
+ <img src="https://img.shields.io/badge/platform-macOS%20%7C%20Linux-2f6f4f" alt="Platform: macOS and Linux">
52
53
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-2f6f4f" alt="Apache 2.0 License"></a>
53
54
  <a href="https://www.linkedin.com/in/seth-peters/"><img src="https://img.shields.io/badge/LinkedIn-Seth%20Peters-0a66c2?logo=linkedin&logoColor=white" alt="Seth Peters on LinkedIn"></a>
54
55
  </p>
@@ -111,8 +112,8 @@ The core worktree lifecycle is five commands (`template` scaffolds docker
111
112
  sandbox templates and `version` prints the version alongside them).
112
113
 
113
114
  **Check the host.** `doctor` verifies exactly what `create` will need — git,
114
- agent logins, `.env`, credentials for the required fetch — and prints the fix
115
- for anything missing:
115
+ agent logins, the (optional) `.env`, credentials for the required fetch — and
116
+ prints the fix for anything missing:
116
117
 
117
118
  ```bash
118
119
  treebox doctor
@@ -150,16 +151,19 @@ so push the base first if its latest commits only exist locally.
150
151
  the worktree was created with; an explicit `--harness` overrides it for that
151
152
  session only, without changing what's recorded on disk. The ref is the name,
152
153
  the *current* branch (renames are followed live), or a unique substring of
153
- either. Dependencies re-sync only if the lockfile changed since last time:
154
+ either. Dependencies re-sync only if the lockfile changed since last time; a
155
+ setup that never completed (a prior run died mid-provision) is finished rather
156
+ than skipped:
154
157
 
155
158
  ```bash
156
159
  treebox enter fix-auth --harness claude
157
160
  treebox enter fix-auth --harness codex -- --resume # args after -- go to the agent
158
161
  ```
159
162
 
160
- **List.** See what exists, what each worktree was last doing, and what has
161
- gone stale sorted by recency, with `treebox/*` placeholders flagged
162
- `⚠ unnamed`:
163
+ **List.** See what exists, each worktree's recorded isolation, what it was
164
+ last doing, and what has gone stale. Results are sorted by recency, with
165
+ `treebox/*` placeholders flagged `⚠ unnamed`; the root footer plus each stable
166
+ name identifies the directory without a wide path column:
163
167
 
164
168
  ```bash
165
169
  treebox list
@@ -179,7 +183,8 @@ treebox teardown # pick interactively
179
183
  treebox is built to be scripted, including by agents: the worktree commands and
180
184
  `doctor` take `--json` (data to stdout, diagnostics to stderr, a schema that
181
185
  only gains fields within a version), `--dry-run` prints the exact commands
182
- without running them, and exit codes are stable (`0` ok · `1` runtime/doctor
186
+ without running them (and fails with the same errors a real run would, so it
187
+ doubles as a preflight), and exit codes are stable (`0` ok · `1` runtime/doctor
183
188
  blocked · `2` usage · `3` not found · `4` auth · `5` conflict). Full
184
189
  reference in the [usage guide](https://seth-peters.github.io/treebox/usage/).
185
190
 
@@ -251,7 +256,7 @@ one per stack:
251
256
 
252
257
  ```bash
253
258
  treebox template init node # copies from the built-in default
254
- treebox template list # names, source, status, default + what the default bundles (`ls` works too)
259
+ treebox template list # names, source, firewall, status, default + what the default bundles (`ls` works too)
255
260
  treebox template path node # where it lives: cd "$(treebox template path node)"
256
261
  ```
257
262
 
@@ -12,6 +12,7 @@
12
12
  <a href="https://github.com/Seth-Peters/treebox/actions/workflows/ci.yml"><img src="https://github.com/Seth-Peters/treebox/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
13
13
  <a href="https://seth-peters.github.io/treebox/"><img src="https://img.shields.io/badge/docs-seth--peters.github.io%2Ftreebox-2f6f4f" alt="Documentation"></a>
14
14
  <img src="https://img.shields.io/badge/python-3.11%2B-3776ab" alt="Python 3.11+">
15
+ <img src="https://img.shields.io/badge/platform-macOS%20%7C%20Linux-2f6f4f" alt="Platform: macOS and Linux">
15
16
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-2f6f4f" alt="Apache 2.0 License"></a>
16
17
  <a href="https://www.linkedin.com/in/seth-peters/"><img src="https://img.shields.io/badge/LinkedIn-Seth%20Peters-0a66c2?logo=linkedin&logoColor=white" alt="Seth Peters on LinkedIn"></a>
17
18
  </p>
@@ -74,8 +75,8 @@ The core worktree lifecycle is five commands (`template` scaffolds docker
74
75
  sandbox templates and `version` prints the version alongside them).
75
76
 
76
77
  **Check the host.** `doctor` verifies exactly what `create` will need — git,
77
- agent logins, `.env`, credentials for the required fetch — and prints the fix
78
- for anything missing:
78
+ agent logins, the (optional) `.env`, credentials for the required fetch — and
79
+ prints the fix for anything missing:
79
80
 
80
81
  ```bash
81
82
  treebox doctor
@@ -113,16 +114,19 @@ so push the base first if its latest commits only exist locally.
113
114
  the worktree was created with; an explicit `--harness` overrides it for that
114
115
  session only, without changing what's recorded on disk. The ref is the name,
115
116
  the *current* branch (renames are followed live), or a unique substring of
116
- either. Dependencies re-sync only if the lockfile changed since last time:
117
+ either. Dependencies re-sync only if the lockfile changed since last time; a
118
+ setup that never completed (a prior run died mid-provision) is finished rather
119
+ than skipped:
117
120
 
118
121
  ```bash
119
122
  treebox enter fix-auth --harness claude
120
123
  treebox enter fix-auth --harness codex -- --resume # args after -- go to the agent
121
124
  ```
122
125
 
123
- **List.** See what exists, what each worktree was last doing, and what has
124
- gone stale sorted by recency, with `treebox/*` placeholders flagged
125
- `⚠ unnamed`:
126
+ **List.** See what exists, each worktree's recorded isolation, what it was
127
+ last doing, and what has gone stale. Results are sorted by recency, with
128
+ `treebox/*` placeholders flagged `⚠ unnamed`; the root footer plus each stable
129
+ name identifies the directory without a wide path column:
126
130
 
127
131
  ```bash
128
132
  treebox list
@@ -142,7 +146,8 @@ treebox teardown # pick interactively
142
146
  treebox is built to be scripted, including by agents: the worktree commands and
143
147
  `doctor` take `--json` (data to stdout, diagnostics to stderr, a schema that
144
148
  only gains fields within a version), `--dry-run` prints the exact commands
145
- without running them, and exit codes are stable (`0` ok · `1` runtime/doctor
149
+ without running them (and fails with the same errors a real run would, so it
150
+ doubles as a preflight), and exit codes are stable (`0` ok · `1` runtime/doctor
146
151
  blocked · `2` usage · `3` not found · `4` auth · `5` conflict). Full
147
152
  reference in the [usage guide](https://seth-peters.github.io/treebox/usage/).
148
153
 
@@ -214,7 +219,7 @@ one per stack:
214
219
 
215
220
  ```bash
216
221
  treebox template init node # copies from the built-in default
217
- treebox template list # names, source, status, default + what the default bundles (`ls` works too)
222
+ treebox template list # names, source, firewall, status, default + what the default bundles (`ls` works too)
218
223
  treebox template path node # where it lives: cd "$(treebox template path node)"
219
224
  ```
220
225
 
@@ -38,6 +38,18 @@ uv = "/mnt/fast/cache/uv"
38
38
  npm = "/mnt/fast/cache/npm"
39
39
  ```
40
40
 
41
+ Every key is optional, but the vocabulary is closed: an unknown top-level key
42
+ (a typo like `isolatoin`) is a loud error (exit `2`, with a did-you-mean
43
+ suggestion), never silently ignored - a typo'd `isolation` or `firewall` would
44
+ otherwise drop your security posture without warning. Only the subkeys inside
45
+ `[caches]` are open-ended (ecosystem cache names).
46
+
47
+ Path-valued keys understand a leading `~`: `root`, `env_file`, and every
48
+ `caches` entry expand `~/…` to your home directory before use, as do quoted
49
+ `--root '~/trees'` and `--repo '~/proj'` on the command line. Ordinary
50
+ relative paths keep their meaning: `root` and `env_file` stay relative to the
51
+ repo.
52
+
41
53
  For a new `create`, precedence is what you'd expect:
42
54
 
43
55
  ```text
@@ -48,16 +60,21 @@ For an existing worktree, `enter` and `teardown` also read the worktree's
48
60
  recorded creation-time state. Recorded isolation wins over the config default,
49
61
  and a conflicting explicit `--isolation` exits `5`; `enter` always reuses the
50
62
  recorded firewall, while recorded harness and template beat config defaults
51
- unless `--harness` or `--template` is passed. `teardown` uses the recorded
52
- template for docker volume cleanup because it has no `--template` flag.
63
+ unless `--harness` or `--template` is passed. `teardown` removes the docker
64
+ volumes recorded in the runner-owned
65
+ `<worktree-root>/.containers/<worktree>/treebox-resources.json` manifest at create time.
66
+ Worktree state under `.git` is sandbox-writable and never authorizes volume
67
+ deletion. Older worktrees without a manifest, or worktrees whose
68
+ `<worktree-root>/.containers/<worktree>` directory was manually deleted, skip volume removal
69
+ with a warning instead of guessing.
53
70
 
54
71
  | Key | Default | What it controls |
55
72
  | ---------- | -------------------- | --------------------------------------------------------- |
56
73
  | `isolation`| `host` | Where agents run: the worktree shell, or a docker sandbox. |
57
74
  | `harness` | `claude` | Which agent `create` launches by default; `enter` reuses the harness the worktree was created with unless `--harness` overrides it. |
58
75
  | `base` | `main` | Base branch for new branches (resolved as `origin/<base>`). |
59
- | `root` | `.treebox/worktrees` | Where worktree directories are created, relative to the repo. |
60
- | `env_file` | `.env` | The secrets file copied into every new worktree. |
76
+ | `root` | `.treebox/worktrees` | Where worktree directories are created: repo-relative, absolute, or `~`-prefixed. |
77
+ | `env_file` | `.env` | The secrets file copied into every new worktree; repo-relative unless absolute or `~`-prefixed. |
61
78
  | `firewall` | `false` | Restrict container egress (docker isolation). |
62
79
  | `template` | `default` | Which operator template defines the sandbox. |
63
80
  | `setup_hook` | *(auto-detect)* | Your own setup commands instead of the detected package manager's. |
@@ -101,7 +118,7 @@ It always writes the full required file set, so a later `create` never fails
101
118
  with "*file* not found in template dir". Inspect what you have any time:
102
119
 
103
120
  ```bash
104
- treebox template list # names, source, status, default + what the default bundles (`ls` works too)
121
+ treebox template list # names, source, firewall, status, default + what the default bundles (`ls` works too)
105
122
  treebox template path node # where it lives: cd "$(treebox template path node)"
106
123
  ```
107
124
 
@@ -170,6 +187,9 @@ container config in the target repo itself is deliberately ignored — see
170
187
  | `TREEBOX_TEMPLATE_DIR` | Explicit template dir; wins for any `--template` name. |
171
188
  | `XDG_CACHE_HOME` | Standard XDG base for the shared package caches treebox mounts. |
172
189
 
190
+ All four expand a leading `~` to your home directory, like the path-valued
191
+ config keys above.
192
+
173
193
  Secrets stay in files: treebox copies your repo's `.env` (or the configured
174
194
  `env_file`) into each worktree and mounts it into containers. Host isolation
175
195
  uses your live subscription login; docker isolation mounts scoped copies of the
@@ -72,16 +72,33 @@ files (lockfiles) at setup time and stores the hash in the worktree's private
72
72
  git dir (`.git/worktrees/<id>/`). That state never appears in `git status`,
73
73
  is pruned together with the worktree, and is what lets `enter` re-sync
74
74
  dependencies only when they actually changed — and `list` show `fresh` /
75
- `stale` at a glance.
75
+ `stale` at a glance. It also records whether setup ever completed: `enter`
76
+ finishes an interrupted setup instead of skipping it as unchanged.
76
77
 
77
78
  The same private state records the worktree's creation-time choices. For an
78
79
  existing worktree, `enter` and `teardown` recover the recorded isolation and
79
80
  template instead of drifting to today's config defaults; `enter` also reuses the
80
81
  recorded firewall, and it reuses the recorded harness unless a per-session
81
- harness override is passed.
82
+ harness override is passed. That state is sandbox-writable because the Docker
83
+ runner mounts the git common directory, so it never authorizes destructive
84
+ host cleanup. Before first Docker setup, the runner writes exact per-workspace
85
+ image and volume ownership to
86
+ `<worktree-root>/.containers/<worktree>/treebox-resources.json`, outside every
87
+ sandbox mount. `teardown --remove-volumes` validates and trusts only that
88
+ manifest, so it can still remove volumes when the container and user template
89
+ are both gone.
90
+ `teardown` reads that record through the repo's own worktree registration
91
+ rather than the worktree's `.git` pointer, so the recorded choices survive
92
+ even a corrupt worktree whose pointer file is gone.
82
93
  An explicit `--isolation` that disagrees with the recorded mode is a conflict,
83
94
  not an override.
84
95
 
96
+ Older worktrees without a resource manifest remain tear-downable, but volume
97
+ removal is skipped with a warning. The same safe behavior applies if an
98
+ operator manually deletes the `.containers/<worktree>` directory: treebox
99
+ does not guess ownership from volume names, current templates, container
100
+ mounts, or sandbox-writable state.
101
+
85
102
  ## Warmth lives in the cache, not the tree
86
103
 
87
104
  treebox detects the package manager from the repo — **uv, npm, pnpm, go, or
@@ -130,7 +147,9 @@ treebox assumes the caller is often another program:
130
147
  without parsing prose.
131
148
  - **`--json` with a `schemaVersion`** that only gains fields within a version
132
149
  (git-porcelain discipline), plus `--print` and `--dry-run` for scripts that
133
- want the commands, not the side effects.
150
+ want the commands, not the side effects. An emitted entry command is
151
+ entry-ready: a stopped docker sandbox is restarted (firewall re-applied)
152
+ before the command is printed, never dead on replay.
134
153
  - **Per-worktree locking**, held by `create`, `enter`, and `teardown` alike,
135
154
  so racing operations on one name — two `create fix-auth` calls, or a
136
155
  `teardown fix-auth` against a concurrent provision — conflict cleanly (`5`)
@@ -59,7 +59,8 @@ plain SSH.
59
59
  ---
60
60
 
61
61
  Installs hardlink from shared caches (`~/.cache/uv`, the pnpm store, …).
62
- `enter` re-syncs only when the lockfile changed.
62
+ `enter` re-syncs only when the lockfile changed (an interrupted setup
63
+ is finished rather than skipped).
63
64
 
64
65
  - :material-cube-outline:{ .lg .middle } **The sandbox config lives outside the box**
65
66