warmtree 0.1.0__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 (38) hide show
  1. warmtree-0.1.0/.gitattributes +1 -0
  2. warmtree-0.1.0/.github/workflows/ci.yml +23 -0
  3. warmtree-0.1.0/.github/workflows/publish.yml +39 -0
  4. warmtree-0.1.0/.gitignore +9 -0
  5. warmtree-0.1.0/.python-version +1 -0
  6. warmtree-0.1.0/CLAUDE.md +25 -0
  7. warmtree-0.1.0/DESIGN.md +143 -0
  8. warmtree-0.1.0/IDEAS.md +11 -0
  9. warmtree-0.1.0/LICENSE +21 -0
  10. warmtree-0.1.0/PKG-INFO +279 -0
  11. warmtree-0.1.0/README.md +259 -0
  12. warmtree-0.1.0/docs/demo.gif +0 -0
  13. warmtree-0.1.0/pyproject.toml +46 -0
  14. warmtree-0.1.0/tests/conftest.py +28 -0
  15. warmtree-0.1.0/tests/test_cli.py +106 -0
  16. warmtree-0.1.0/tests/test_cli_lifecycle.py +174 -0
  17. warmtree-0.1.0/tests/test_cli_refresh.py +76 -0
  18. warmtree-0.1.0/tests/test_config.py +103 -0
  19. warmtree-0.1.0/tests/test_git.py +62 -0
  20. warmtree-0.1.0/tests/test_lifecycle.py +253 -0
  21. warmtree-0.1.0/tests/test_lock.py +43 -0
  22. warmtree-0.1.0/tests/test_pool.py +79 -0
  23. warmtree-0.1.0/tests/test_refresh.py +184 -0
  24. warmtree-0.1.0/tests/test_size.py +179 -0
  25. warmtree-0.1.0/tests/test_skill.py +172 -0
  26. warmtree-0.1.0/tests/test_state_concurrency.py +48 -0
  27. warmtree-0.1.0/tests/test_warm.py +102 -0
  28. warmtree-0.1.0/uv.lock +108 -0
  29. warmtree-0.1.0/warmtree/__init__.py +3 -0
  30. warmtree-0.1.0/warmtree/__main__.py +5 -0
  31. warmtree-0.1.0/warmtree/cli.py +344 -0
  32. warmtree-0.1.0/warmtree/config.py +142 -0
  33. warmtree-0.1.0/warmtree/git.py +152 -0
  34. warmtree-0.1.0/warmtree/lock.py +59 -0
  35. warmtree-0.1.0/warmtree/pool.py +365 -0
  36. warmtree-0.1.0/warmtree/skill.py +93 -0
  37. warmtree-0.1.0/warmtree/skills/warmtree/SKILL.md +116 -0
  38. warmtree-0.1.0/warmtree/warm.py +104 -0
@@ -0,0 +1 @@
1
+ * text=auto eol=lf
@@ -0,0 +1,23 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ strategy:
11
+ fail-fast: false
12
+ matrix:
13
+ os: [ubuntu-latest, windows-latest]
14
+ runs-on: ${{ matrix.os }}
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: astral-sh/setup-uv@v6
18
+ with:
19
+ python-version: "3.12"
20
+ - run: uv sync
21
+ - run: uv run ruff check .
22
+ - run: uv run ruff format --check .
23
+ - run: uv run pytest -q
@@ -0,0 +1,39 @@
1
+ name: Publish
2
+
3
+ # Publishes to PyPI when a version tag is pushed, using trusted publishing.
4
+ # No API token is stored anywhere; PyPI trusts this workflow's OIDC identity.
5
+ # One-time setup on pypi.org: add a trusted publisher for
6
+ # owner dcolliervb23, repository warmtree, workflow publish.yml, environment pypi.
7
+
8
+ on:
9
+ push:
10
+ tags: ["v*"]
11
+
12
+ jobs:
13
+ build:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: astral-sh/setup-uv@v6
18
+ with:
19
+ python-version: "3.12"
20
+ - name: Tag must match the package version
21
+ run: test "v$(uv version --short)" = "$GITHUB_REF_NAME"
22
+ - run: uv build
23
+ - uses: actions/upload-artifact@v4
24
+ with:
25
+ name: dist
26
+ path: dist/
27
+
28
+ publish:
29
+ needs: build
30
+ runs-on: ubuntu-latest
31
+ environment: pypi
32
+ permissions:
33
+ id-token: write
34
+ steps:
35
+ - uses: actions/download-artifact@v4
36
+ with:
37
+ name: dist
38
+ path: dist/
39
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,9 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+ .warmtree/
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,25 @@
1
+ # warmtree — rules for AI sessions in this repo
2
+
3
+ Read DESIGN.md first. It is the scope. Do not expand it; append ideas to IDEAS.md.
4
+ The scope is the pool only. Do not add port allocation, merge helpers, or
5
+ general cleanup; the design explains why.
6
+
7
+ ## Working rules
8
+ - Prefer boring, readable Python over clever Python. Every line should be one
9
+ a reviewer can follow without explanation.
10
+ - The maintainer writes the tests and the README. Draft them if asked, then
11
+ stop and wait for review before committing.
12
+ - Small commits, one step each. Conventional messages (`feat:`, `test:`, `docs:`).
13
+ Plain messages: no co-author trailers, no session links.
14
+
15
+ ## Publishing hygiene
16
+ - `git config user.email dcolliervb23@users.noreply.github.com` in this repo. Never a personal email.
17
+ - No secrets. PyPI publishing is GitHub Actions trusted publishing only.
18
+ - No local machine paths in any committed file.
19
+ - Assume every commit is public.
20
+
21
+ ## Stack
22
+ - Python 3.12, standard library only at runtime (`dependencies = []`).
23
+ - Dev tools: `uv`, `ruff`, `pytest`. Tests create real temporary git repos.
24
+ - Run tests with `uv run pytest -q`. Lint with `uv run ruff check .`.
25
+ - Must work on Windows, Linux, and WSL. CI runs on ubuntu-latest and windows-latest.
@@ -0,0 +1,143 @@
1
+ # warmtree design
2
+
3
+ > `git worktree add` is fast. Everything after it is slow: dependency install,
4
+ > build caches, untracked env files. warmtree keeps a few worktrees already
5
+ > checked out, fast-forwarded, and warmed, so `warmtree take <branch>` hands
6
+ > you a ready workspace in under a second. Built for parallel AI coding agents.
7
+
8
+ Language: Python 3.12, standard library only.
9
+
10
+ ## Positioning
11
+
12
+ The worktree-tooling space is crowded. worktrunk (Rust) already does on-create
13
+ hooks, copies ignored files, allocates ports, and cleans up merged worktrees.
14
+ Several smaller tools cover env copying and ports on their own. **No tool
15
+ pre-creates warm worktrees.** The pool is the entire contribution, and the
16
+ scope is held to it on purpose.
17
+
18
+ warmtree owns the **create** and **release** steps. Everything in between is
19
+ ordinary git, so worktrunk, portree, global git hooks, and editors all work on
20
+ a pooled worktree unchanged. The README documents two workflows: standalone,
21
+ and warmtree-for-create plus worktrunk-for-everything-else.
22
+
23
+ ## Rules
24
+
25
+ - Standard library only. No runtime dependencies. `uvx warmtree` must work
26
+ on a machine with nothing but Python and git.
27
+ - Stack-agnostic. warmtree never knows what npm or dotnet are. Projects
28
+ declare their own warm commands.
29
+ - Boring, readable Python. Every line should be explainable to a reviewer.
30
+ - Tests run against real temporary git repos. Nothing about git is mocked.
31
+
32
+ ## v1 scope
33
+
34
+ ### Config: `.warmtree.toml` at the repo root (parsed with `tomllib`)
35
+
36
+ ```toml
37
+ [pool]
38
+ size = 3 # slots to keep ready
39
+ base = "main" # branch slots are parked on
40
+ dir = "../.warmtree/app" # where slots live; default is ../.warmtree/<repo name>
41
+ lockfiles = ["package-lock.json", "uv.lock"] # re-warm only when these change
42
+
43
+ [warm]
44
+ run = ["npm ci"] # executed in the slot at fill/refresh time
45
+ copy = [".env", ".env.local"] # untracked files copied from the main worktree
46
+ env = true # write WARMTREE_SLOT=<n> into copied env files
47
+ ```
48
+
49
+ Every key has a default; an empty file means "pool of 2 on the default
50
+ branch, fast-forward only."
51
+
52
+ ### Commands
53
+ | Command | Behavior |
54
+ |---|---|
55
+ | `warmtree init` | Write a starter config. Detects common lockfiles only to pre-fill `lockfiles`; never guesses `run`. Installs the agent skill for any coding agent the repo shows signs of. |
56
+ | `warmtree skill [--tool T] [--force]` | Install or refresh the agent skill in detected or named tool folders. |
57
+ | `warmtree fill` | Create missing slots with `git worktree add --detach`, run `copy`, run `run`, record lockfile hashes. |
58
+ | `warmtree take <branch> [--from <ref>]` | Claim the oldest ready slot: create or check out `<branch>` inside it, print its path, mark it taken. Refill the pool afterward (`--no-refill` to skip; `--refill-background` spawns a detached process). If no slot is ready, fall back to a cold create and say so. |
59
+ | `warmtree release <branch>` | Reset the slot to `base`, discard the working tree (`--keep-branch` keeps the branch ref), return the slot to ready. Refuses if the tree is dirty unless `--force`. |
60
+ | `warmtree refresh` | Fast-forward every ready slot to `base`; if any lockfile hash changed, re-run `run` and `copy` in that slot. Meant for a nightly scheduled task or cron. |
61
+ | `warmtree status` | Table of slots: path, state (ready/taken/warming/stale), branch, age, last warm. `--json` for agents. |
62
+ | `warmtree size [N]` | Show configured size and counts by state. With `N`, write the size to the config and grow or shrink the pool to match; shrinking removes waiting slots only. |
63
+ | `warmtree which` | Name the slot the current directory is inside, so an agent can tell whether it already has a workspace. |
64
+ | `warmtree remove [SLOT...] [--all]` | Delete slots and their worktree registrations. |
65
+
66
+ ### State
67
+ - `state.json` inside the pool dir: slot list with state, branch,
68
+ created/warmed timestamps, lockfile hashes. One file, rewritten atomically.
69
+ - Concurrency: `take` holds an OS file lock (`msvcrt` on Windows, `fcntl`
70
+ elsewhere) for the claim. Two agents calling `take` at once get two
71
+ different slots.
72
+
73
+ ### Agent integration
74
+ - `warmtree/skills/warmtree/SKILL.md` in the open Agent Skills format (read by
75
+ Claude Code, GitHub Copilot, Codex CLI, Cursor), shipped inside the package.
76
+ `warmtree init` copies it into the skills folder of every tool the repo
77
+ shows signs of using (`CLAUDE.md`, `copilot-instructions.md`, `AGENTS.md`,
78
+ `.cursor/`); `warmtree skill` does the same on demand and never overwrites
79
+ an edited copy without `--force`. The rule: when an isolated workspace is needed,
80
+ run `warmtree take <branch>` and `cd` into the printed path instead of
81
+ `git worktree add`; when done and merged, `warmtree release <branch>`.
82
+ Before that, `warmtree which` tells the agent whether it is already in a
83
+ slot, and `warmtree size` tells it whether enough slots are ready for the
84
+ parallel work it is about to start, and grows the pool if not.
85
+ - `AGENTS.md` paragraph in the README for projects that use that convention.
86
+ - Note in the README that Claude Code's native worktree feature is not
87
+ intercepted; the skill is the integration.
88
+
89
+ ### Out (v1)
90
+ - Port allocation beyond the `WARMTREE_SLOT` variable. Subdomain routing.
91
+ Merging, PR creation, branch listing. Any GUI or TUI. Daemon mode. Shared
92
+ dependency stores. Windows junction tricks for `node_modules`.
93
+
94
+ ## Architecture
95
+
96
+ ```
97
+ warmtree/
98
+ __init__.py
99
+ cli.py argparse entry point, one function per command
100
+ config.py load/validate .warmtree.toml, defaults
101
+ git.py thin subprocess wrapper: worktree add/remove, fetch, ff, status
102
+ pool.py slot lifecycle: fill, take, release, refresh, state persistence
103
+ lock.py cross-platform file lock
104
+ warm.py run commands, copy files, hash lockfiles, inject WARMTREE_SLOT
105
+ skill.py detect coding-agent folders, install the bundled SKILL.md
106
+ skills/warmtree/SKILL.md
107
+ tests/ pytest against real temporary git repos (no mocks of git)
108
+ ```
109
+
110
+ Key decisions and the reasoning behind each:
111
+ - **Slots are detached worktrees parked on `base`.** A branch is only
112
+ created at `take`, so a slot is never "on" a branch anyone else is using,
113
+ and git's one-branch-per-worktree rule never bites the pool.
114
+ - **Reset on release, do not delete.** Deleting and recreating throws away
115
+ the warm state; resetting keeps `node_modules` and friends intact. Lockfile
116
+ hashes decide whether a re-warm is needed.
117
+ - **Fallback to cold create.** An empty pool is a slow path, never an error.
118
+ Agents should never fail because the pool drained.
119
+ - **State in one JSON file with a lock, not SQLite.** Dozens of slots at
120
+ most; simplest thing that is correct under concurrent `take`.
121
+ - **Standard library only.** The tool has to run before the project's own
122
+ dependencies exist; that is the whole point.
123
+
124
+ ## Build order
125
+
126
+ | # | Deliverable | Done when |
127
+ |---|---|---|
128
+ | 1 | config, git wrapper, `init`, `fill`, `status`, tests on temp repos, CI on ubuntu + windows | `fill` creates N detached slots in a temp repo; CI green on both OSes |
129
+ | 2 | `take` with file lock and refill, `release` with reset, `remove`, fallback path | Two concurrent `take` calls get two slots (test); `release` returns a slot to ready |
130
+ | 3 | `warm`: run commands, copy files, lockfile hashing, `refresh`, `WARMTREE_SLOT` | Changing a lockfile makes `refresh` re-run `run` in every slot, unchanged lockfile skips |
131
+ | 4 | SKILL.md, AGENTS.md, README with GIF and worktrunk workflow, trusted publishing to PyPI, tag v0.1.0 | `uvx warmtree status` works on a clean machine; Claude Code takes a slot via the skill |
132
+
133
+ ## Acceptance criteria (v0.1.0)
134
+ - 25+ tests against real git repos, green in CI on Ubuntu and Windows.
135
+ - `take` returns a ready slot in under 1 second on a repo whose `npm ci` takes minutes (measured in the README).
136
+ - Concurrency test in step 2 passes.
137
+ - Zero runtime dependencies; `pyproject.toml` `dependencies = []`.
138
+ - PyPI release published from GitHub Actions via trusted publishing; no API token stored locally.
139
+
140
+ ## Before publishing
141
+ Use it on a large real repo with `size = 2` for a week before the first
142
+ release. Every papercut found goes in IDEAS.md; only bugs are fixed before
143
+ v0.1.0.
@@ -0,0 +1,11 @@
1
+ # Ideas
2
+
3
+ Things that came up while building warmtree and are out of scope for v1.
4
+ Append here instead of expanding DESIGN.md.
5
+
6
+ - **Stream warm output.** `run` output is captured and shown only on failure
7
+ so `take` keeps stdout clean. A `--verbose` flag on `fill` and `refresh`
8
+ could stream it to stderr for a first-time `npm ci` that takes minutes.
9
+ - **`refresh --fetch`.** `refresh` moves slots to the local base tip. It could
10
+ run `git fetch` first, or accept `base = "origin/main"`, which already works
11
+ because the base is resolved with `rev-parse`.
warmtree-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dan Collier
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,279 @@
1
+ Metadata-Version: 2.5
2
+ Name: warmtree
3
+ Version: 0.1.0
4
+ Summary: A pool of pre-warmed git worktrees for parallel AI coding agents.
5
+ Project-URL: Homepage, https://github.com/dcolliervb23/warmtree
6
+ Author: Dan Collier
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Keywords: agents,developer-tools,git,worktree
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Version Control :: Git
18
+ Requires-Python: >=3.12
19
+ Description-Content-Type: text/markdown
20
+
21
+ # warmtree
22
+
23
+ A pool of pre-warmed git worktrees for parallel AI coding agents.
24
+
25
+ `git worktree add` is fast. Everything after it is slow: installing
26
+ dependencies, rebuilding caches, copying the `.env` files git does not track.
27
+ On a large repo that cold start is minutes, and with several agents working in
28
+ parallel you pay it several times a day. warmtree keeps a few worktrees already
29
+ checked out, warmed, and ready, so `warmtree take <branch>` hands you a
30
+ workspace in under a second.
31
+
32
+ ![warmtree demo](docs/demo.gif)
33
+
34
+ Measured on a private TypeScript app (457 packages, 664 MB of node_modules):
35
+ a cold `git worktree add` + `npm ci` took 6.6 s even with a fast machine and
36
+ network; `warmtree take` handed over a warm slot in 0.15 s.
37
+
38
+ warmtree owns the **create** and **release** steps only. Everything in between
39
+ is ordinary git, so your editor, hooks, and other worktree tools work on a
40
+ pooled worktree unchanged.
41
+
42
+ ## Status
43
+
44
+ Early. Stable enough to dogfood, and dogfooded daily on a large private repo.
45
+
46
+ ## Install
47
+
48
+ Requires Python 3.12+ and git. No other runtime dependencies.
49
+
50
+ From a clone, for dogfooding while the code is still changing:
51
+
52
+ ```sh
53
+ uv tool install --editable .
54
+ ```
55
+
56
+ Or straight from GitHub:
57
+
58
+ ```sh
59
+ uv tool install git+https://github.com/dcolliervb23/warmtree
60
+ ```
61
+
62
+ Or from PyPI: `uvx warmtree` works on a machine with nothing but Python
63
+ and git.
64
+
65
+ ## Quick start
66
+
67
+ Run these from inside the repo you want to pool.
68
+
69
+ ```sh
70
+ warmtree init # writes .warmtree.toml with defaults
71
+ # edit .warmtree.toml: add your install command to [warm] run
72
+ warmtree fill # creates and warms the slots, two by default
73
+ warmtree status # see them
74
+ ```
75
+
76
+ Take a slot when you need an isolated workspace, work in it, then release it:
77
+
78
+ ```sh
79
+ cd "$(warmtree take feature/login)" # bash / zsh
80
+ # ... commit, push, open a PR ...
81
+ warmtree release feature/login
82
+ ```
83
+
84
+ PowerShell:
85
+
86
+ ```powershell
87
+ cd (warmtree take feature/login)
88
+ warmtree release feature/login
89
+ ```
90
+
91
+ `take` prints only the slot path on stdout. Everything else it says goes to
92
+ stderr, so the `cd` idiom works.
93
+
94
+ Keep slots current with a nightly `warmtree refresh` from cron or Task
95
+ Scheduler, after whatever pulls your base branch.
96
+
97
+ ## Commands
98
+
99
+ | Command | What it does |
100
+ |---|---|
101
+ | `warmtree init [--force] [--no-skill]` | Write a starter `.warmtree.toml`. Pre-fills `lockfiles` from what it finds in the repo. Never guesses your install command. Installs the agent skill for any coding agent it detects. |
102
+ | `warmtree skill [--tool T] [--force]` | Install or refresh the agent skill in the tool folders this repo uses, or in the ones named with `--tool`. Never overwrites an edited copy without `--force`. |
103
+ | `warmtree fill` | Create slots until `size` are ready. Each slot is a worktree with a detached HEAD at the base branch, with `copy` files copied in and `run` commands executed. |
104
+ | `warmtree take <branch> [--from REF]` | Claim the oldest ready slot. Creates `<branch>` there (from `REF` or the base branch) or checks it out if it already exists. Prints the path, then refills the pool. |
105
+ | `warmtree take ... --no-refill` | Skip the refill. |
106
+ | `warmtree take ... --refill-background` | Refill in a detached process and return immediately. |
107
+ | `warmtree release <branch> [--keep-branch] [--force]` | Park the slot back on the base branch and mark it ready. Refuses a dirty tree unless `--force`. Deletes the branch if it is merged; an unmerged branch is always kept. |
108
+ | `warmtree refresh` | Move every waiting slot to the current base commit and re-copy files. Re-runs `run` only in slots whose lockfile hashes changed or whose last warm failed. Skips taken slots. |
109
+ | `warmtree size [N]` | Show the configured size and a count of slots by state. With `N`, write the new size to `.warmtree.toml` and grow or shrink the pool to match. Shrinking removes ready slots only. |
110
+ | `warmtree which` | Name the slot the current directory is inside, as `slot-N <state> <branch>`. Exit 1 if not in a slot. |
111
+ | `warmtree remove [SLOT...] [--all] [--force]` | Delete slots and their worktree registrations. Taken slots need `--force`. |
112
+ | `warmtree status [--json]` | Table of slots: name, state, branch, age, last warm, path. `--json` for scripts and agents. |
113
+
114
+ Slot states:
115
+
116
+ - `ready`: parked on base, warm, free to take.
117
+ - `taken`: a branch is checked out and someone is working in it.
118
+ - `warming`: `run` commands are executing right now, in `fill` or `refresh`.
119
+ - `stale`: the last warm failed. `refresh` retries it. `take` never hands out a
120
+ stale slot.
121
+
122
+ If no slot is ready, `take` falls back to a normal `git worktree add`, tells
123
+ you on stderr, and the new worktree joins the pool as a taken slot. An empty
124
+ pool is a slow path, never an error.
125
+
126
+ ## Configuration
127
+
128
+ `.warmtree.toml` at the repo root. Every key has a default; an empty file or
129
+ no file at all means a pool of two on the repo's default branch with nothing
130
+ to warm.
131
+
132
+ ```toml
133
+ [pool]
134
+ size = 2 # slots to keep ready; taken slots do not count
135
+ # base = "main" # branch slots park on; default: the repo's default branch
136
+ # dir = "../.warmtree/app" # where slots live; default: ../.warmtree/<repo name>
137
+ lockfiles = ["package-lock.json", "uv.lock"] # re-warm only when one of these changes
138
+
139
+ [warm]
140
+ run = ["npm ci"] # commands run inside a slot at fill and refresh time
141
+ copy = [".env", ".env.local"] # untracked files copied from the main worktree
142
+ env = true # write WARMTREE_SLOT=<n> into the copied env files
143
+ ```
144
+
145
+ Details worth knowing:
146
+
147
+ - `run` commands go through the shell, in order, inside the slot, with
148
+ `WARMTREE_SLOT=<n>` in the environment. Output is captured and shown only
149
+ when a command fails. warmtree has no idea what npm or dotnet are; you do.
150
+ - `copy` paths are relative to the repo root. Files missing from the main
151
+ worktree are skipped. With `env = true`, copied files named like `.env`,
152
+ `.env.local`, or `app.env` get a `WARMTREE_SLOT=<n>` line appended, so your
153
+ project can derive a per-slot port or database name from it.
154
+ - `lockfiles` are hashed inside the slot after each warm. `refresh` re-runs
155
+ `run` only when a hash differs.
156
+ - `warmtree size N` edits the `size` line in place. Your comments and other
157
+ keys are left alone.
158
+ - Unknown keys are errors, so a typo never silently disables warming.
159
+
160
+ ## Using it with coding agents
161
+
162
+ warmtree ships an [Agent Skill](https://agentskills.io): a short set of
163
+ instructions an agent loads when the task calls for an isolated workspace. It
164
+ tells the agent to check whether it is already in a slot (`warmtree which`),
165
+ check capacity before fanning out (`warmtree size`), take a slot instead of
166
+ running `git worktree add`, and release it when the branch is merged.
167
+
168
+ `warmtree init` installs it automatically wherever it sees signs of a coding
169
+ agent in the repo, and prints each path it wrote:
170
+
171
+ | Found in the repo | Skill installed at |
172
+ |---|---|
173
+ | `CLAUDE.md` or `.claude/` | `.claude/skills/warmtree/SKILL.md` (Claude Code) |
174
+ | `.github/copilot-instructions.md` | `.github/skills/warmtree/SKILL.md` (GitHub Copilot) |
175
+ | `AGENTS.md` or `.agents/` | `.agents/skills/warmtree/SKILL.md` (Codex and others) |
176
+ | `.cursor/` | `.cursor/skills/warmtree/SKILL.md` (Cursor) |
177
+
178
+ Commit those folders so every agent on the project gets the skill. To install
179
+ for a tool that was not detected, or to refresh the copies after upgrading
180
+ warmtree:
181
+
182
+ ```sh
183
+ warmtree skill --tool claude # claude, copilot, codex, or cursor; repeatable
184
+ warmtree skill # re-detect and refresh
185
+ warmtree skill --force # replace a copy you edited by hand
186
+ ```
187
+
188
+ A copy you have edited is never overwritten without `--force`. Pass
189
+ `--no-skill` to `init` to skip all of this.
190
+
191
+ If your project uses an `AGENTS.md` instead, this paragraph is enough:
192
+
193
+ > This repo has a warmtree pool. When you need an isolated workspace, run
194
+ > `warmtree take <branch>` and `cd` into the printed path instead of
195
+ > `git worktree add`. Run `warmtree which` first to see if you are already in
196
+ > a slot, and `warmtree size` to check how many are ready before starting
197
+ > parallel work. When the branch is merged, run `warmtree release <branch>`.
198
+
199
+ Claude Code's built-in worktree feature is not intercepted. The skill is the
200
+ integration.
201
+
202
+ ## With worktrunk
203
+
204
+ [worktrunk](https://github.com/max-sixty/worktrunk) covers the rest of the
205
+ worktree lifecycle: hooks, port allocation, cleanup of merged branches. The two
206
+ fit together because warmtree only touches create and release.
207
+
208
+ - Use `warmtree take` instead of `wt switch --create` to get a warm checkout.
209
+ - Inside the slot, worktrunk's commands work as they do in any worktree.
210
+ - When the branch is merged, run `warmtree release <branch>` rather than
211
+ worktrunk's remove, so the slot goes back to the pool instead of being
212
+ deleted.
213
+
214
+ ## How it works
215
+
216
+ - **Slots are detached worktrees parked on the base branch.** A branch is only
217
+ created at `take`, so a slot is never on a branch someone else is using and
218
+ git's one-branch-per-worktree rule never bites the pool.
219
+ - **Release resets, it does not delete.** `release` checks out the base
220
+ branch detached and runs `git clean -fd`, which removes untracked files but
221
+ keeps ignored ones. `node_modules`, `.venv`, and friends survive, so the slot
222
+ is still warm for the next take.
223
+ - **Warming never holds the lock.** A slot is marked `warming` under the pool
224
+ lock, the slow commands run with the lock released, and the result is
225
+ written under the lock again. A three-minute `npm ci` in one slot never
226
+ blocks `take` on another.
227
+ - **State is one JSON file with a file lock.** `state.json` lives in the pool
228
+ directory and is rewritten atomically. `take` holds an OS file lock while it
229
+ picks a slot, so two agents calling `take` at the same moment get two
230
+ different slots.
231
+ - **Standard library only.** warmtree has to run before your project's own
232
+ dependencies exist. That is the whole point.
233
+
234
+ The pool directory defaults to a sibling of your repo named after it, for
235
+ example `~/dev/.warmtree/myapp/slot-1`, so repos that share a parent folder
236
+ never share a pool.
237
+
238
+ ## Development
239
+
240
+ ```sh
241
+ uv sync # creates .venv with Python 3.12, pytest, ruff
242
+ uv run pytest -q # the whole suite, about a minute
243
+ uv run ruff check .
244
+ uv run ruff format --check .
245
+ ```
246
+
247
+ Those three commands are exactly what CI runs on Ubuntu and Windows. Tests
248
+ create real temporary git repos; nothing about git is mocked, so `git` must
249
+ be on your PATH.
250
+
251
+ Useful variations:
252
+
253
+ ```sh
254
+ uv run pytest -q tests/test_lifecycle.py # one file
255
+ uv run pytest -q -k concurrent # tests whose name matches
256
+ uv run pytest -x # stop at the first failure
257
+ uv run pytest -v # show every test name
258
+ uv run ruff format . # fix formatting instead of checking
259
+ ```
260
+
261
+ ### Releasing
262
+
263
+ Releases go to PyPI through GitHub Actions trusted publishing; no API token is
264
+ stored anywhere. One-time setup on pypi.org: add a trusted publisher for owner
265
+ `dcolliervb23`, repository `warmtree`, workflow `publish.yml`, environment
266
+ `pypi`. Then:
267
+
268
+ ```sh
269
+ uv version 0.1.0 # sets the version in pyproject.toml
270
+ git commit -am "chore: release 0.1.0"
271
+ git tag v0.1.0
272
+ git push && git push --tags
273
+ ```
274
+
275
+ The workflow refuses to publish if the tag and the package version disagree.
276
+
277
+ ## License
278
+
279
+ MIT