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.
- {treebox-1.0.0 → treebox-1.0.2}/.agents/skills/treebox/SKILL.md +9 -8
- {treebox-1.0.0 → treebox-1.0.2}/.agents/skills/treebox-e2e/SKILL.md +1 -1
- {treebox-1.0.0 → treebox-1.0.2}/.agents/skills/treebox-e2e/references/scenarios.md +36 -5
- treebox-1.0.2/CHANGELOG.md +84 -0
- {treebox-1.0.0 → treebox-1.0.2}/CLAUDE.md +24 -7
- {treebox-1.0.0 → treebox-1.0.2}/CONTRIBUTING.md +22 -3
- {treebox-1.0.0 → treebox-1.0.2}/PKG-INFO +14 -9
- {treebox-1.0.0 → treebox-1.0.2}/README.md +13 -8
- {treebox-1.0.0 → treebox-1.0.2}/docs/configuration.md +25 -5
- {treebox-1.0.0 → treebox-1.0.2}/docs/how-it-works.md +22 -3
- {treebox-1.0.0 → treebox-1.0.2}/docs/index.md +2 -1
- {treebox-1.0.0 → treebox-1.0.2}/docs/install.md +12 -12
- {treebox-1.0.0 → treebox-1.0.2}/docs/usage.md +101 -27
- {treebox-1.0.0 → treebox-1.0.2}/scripts/golden-diff.sh +71 -17
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/assets.py +92 -3
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/cli.py +155 -24
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/config.py +41 -10
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/ecosystems.py +6 -3
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/git.py +56 -15
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/models.py +15 -4
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/output.py +173 -68
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/provision.py +154 -92
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/resolve.py +6 -1
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/runners/__init__.py +7 -6
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/runners/base.py +50 -3
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/runners/docker.py +167 -48
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/runners/host.py +12 -2
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/state.py +17 -1
- treebox-1.0.2/tests/golden/README.md +21 -0
- treebox-1.0.2/tests/golden/doctor-host-no-env.txt +18 -0
- treebox-1.0.2/tests/golden/list.txt +16 -0
- {treebox-1.0.0 → treebox-1.0.2}/tests/test_integration.py +667 -4
- {treebox-1.0.0 → treebox-1.0.2}/tests/test_units.py +888 -24
- treebox-1.0.0/CHANGELOG.md +0 -39
- treebox-1.0.0/tests/golden/README.md +0 -19
- {treebox-1.0.0 → treebox-1.0.2}/.agents/skills/treebox-e2e/fixture.sh +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/.github/workflows/autofix.yml +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/.github/workflows/ci.yml +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/.github/workflows/claude.yml +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/.github/workflows/docs.yml +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/.github/workflows/release.yml +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/.gitignore +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/.pre-commit-config.yaml +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/AGENTS.md +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/LICENSE +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/ROADMAP.md +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/assets/treebox-demo.gif +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/assets/treebox-logo.png +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/docs/agents.md +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/docs/assets/treebox-lifecycle.gif +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/docs/assets/treebox-logo.png +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/docs/javascripts/treebox.js +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/docs/stylesheets/extra.css +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/hooks/copy_page.py +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/install.sh +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/mkdocs.yml +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/pyproject.toml +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/scripts/validate.sh +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/__init__.py +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/assets/container/Dockerfile +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/assets/container/allowed-domains.sh +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/assets/container/container.json +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/assets/container/firewall.json +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/assets/container/init-firewall.sh +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/assets/container/post-create.sh +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/assets/pre-push +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/forge.py +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/harnesses.py +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/locking.py +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/names.py +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/py.typed +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/status.py +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/src/treebox/system.py +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/tests/conftest.py +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/tests/golden/create-docker-json.txt +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/tests/golden/create-docker-print.txt +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/tests/golden/create-dryrun-docker-json.txt +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/tests/golden/create-dryrun-docker.txt +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/tests/golden/create-dryrun-host-json.txt +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/tests/golden/create-dryrun-host.txt +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/tests/golden/create-host-json.txt +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/tests/golden/create-host-print.txt +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/tests/golden/create-slash-json.txt +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/tests/golden/doctor-docker-json.txt +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/tests/golden/doctor-docker.txt +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/tests/golden/doctor-host-json.txt +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/tests/golden/doctor-host.txt +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/tests/golden/doctor-no-login.txt +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/tests/golden/enter-docker-json.txt +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/tests/golden/enter-docker-print.txt +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/tests/golden/enter-host-json.txt +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/tests/golden/enter-host-print.txt +0 -0
- {treebox-1.0.0 → treebox-1.0.2}/tests/test_contract.py +0 -0
- {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`)
|
|
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"
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
109
|
-
|
|
110
|
-
|
|
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
|
|
57
|
-
|
|
58
|
-
|
|
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.
|
|
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
|
|
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,
|
|
161
|
-
gone stale
|
|
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
|
|
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
|
|
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,
|
|
124
|
-
gone stale
|
|
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
|
|
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`
|
|
52
|
-
|
|
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
|
|
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
|
|