worktree-compose 0.1.0
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.
- package/README.md +618 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +75 -0
- package/dist/commands/clean.d.ts +1 -0
- package/dist/commands/clean.js +49 -0
- package/dist/commands/list.d.ts +1 -0
- package/dist/commands/list.js +71 -0
- package/dist/commands/promote.d.ts +1 -0
- package/dist/commands/promote.js +35 -0
- package/dist/commands/restart.d.ts +1 -0
- package/dist/commands/restart.js +6 -0
- package/dist/commands/start.d.ts +1 -0
- package/dist/commands/start.js +37 -0
- package/dist/commands/stop.d.ts +1 -0
- package/dist/commands/stop.js +24 -0
- package/dist/compose/detect.d.ts +3 -0
- package/dist/compose/detect.js +25 -0
- package/dist/compose/parse.d.ts +2 -0
- package/dist/compose/parse.js +55 -0
- package/dist/compose/types.d.ts +13 -0
- package/dist/compose/types.js +1 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +16 -0
- package/dist/context.d.ts +14 -0
- package/dist/context.js +29 -0
- package/dist/git/promote.d.ts +4 -0
- package/dist/git/promote.js +58 -0
- package/dist/git/worktree.d.ts +10 -0
- package/dist/git/worktree.js +40 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +132 -0
- package/dist/ports/allocate.d.ts +3 -0
- package/dist/ports/allocate.js +29 -0
- package/dist/ports/extract.d.ts +4 -0
- package/dist/ports/extract.js +92 -0
- package/dist/ports/types.d.ts +13 -0
- package/dist/ports/types.js +1 -0
- package/dist/sync/env.d.ts +5 -0
- package/dist/sync/env.js +58 -0
- package/dist/sync/files.d.ts +2 -0
- package/dist/sync/files.js +59 -0
- package/dist/utils/exec.d.ts +4 -0
- package/dist/utils/exec.js +23 -0
- package/dist/utils/log.d.ts +5 -0
- package/dist/utils/log.js +16 -0
- package/dist/utils/sanitize.d.ts +2 -0
- package/dist/utils/sanitize.js +11 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
# worktree-compose (wtc)
|
|
2
|
+
|
|
3
|
+
Zero-config Docker Compose isolation for git worktrees.
|
|
4
|
+
|
|
5
|
+
## The Problem
|
|
6
|
+
|
|
7
|
+
You're running multiple AI agents (or developers) in parallel on the same repo, each in its own [git worktree](https://git-scm.com/docs/git-worktree). Without isolation, they all share the same Docker Compose setup — same Postgres, same Redis, same backend, same frontend. This means:
|
|
8
|
+
|
|
9
|
+
- **Port conflicts** — two stacks can't both bind to port 8000
|
|
10
|
+
- **Shared database** — agents overwrite each other's data
|
|
11
|
+
- **Shared cache** — one agent's Redis state leaks into another's
|
|
12
|
+
- **Container collisions** — `docker compose up` in one worktree kills the other's containers
|
|
13
|
+
- **No comparison** — you can't open two frontends side by side to compare agent outputs
|
|
14
|
+
|
|
15
|
+
You need each worktree to have its own fully isolated stack: its own database, its own cache, its own ports, its own containers. But setting this up manually for every worktree is tedious and error-prone.
|
|
16
|
+
|
|
17
|
+
## The Solution
|
|
18
|
+
|
|
19
|
+
`wtc` gives every git worktree its own Docker Compose stack automatically.
|
|
20
|
+
|
|
21
|
+
It reads your `docker-compose.yml`, finds every service that exposes a port, assigns unique ports per worktree, injects them into each worktree's `.env`, and starts isolated containers. No configuration needed.
|
|
22
|
+
|
|
23
|
+
Each worktree gets:
|
|
24
|
+
- **Its own ports** — no collisions
|
|
25
|
+
- **Its own database** — no shared state
|
|
26
|
+
- **Its own cache** — no leaking
|
|
27
|
+
- **Its own containers** — independent lifecycles
|
|
28
|
+
- **Its own URL** — open them side by side and compare
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install -D worktree-compose
|
|
34
|
+
# or
|
|
35
|
+
pnpm add -D worktree-compose
|
|
36
|
+
# or
|
|
37
|
+
yarn add -D worktree-compose
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
This gives you the `wtc` command in your project.
|
|
41
|
+
|
|
42
|
+
## Quick Start
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# 1. Create worktrees
|
|
46
|
+
git worktree add ../feature-1 feature-1
|
|
47
|
+
git worktree add ../feature-2 feature-2
|
|
48
|
+
|
|
49
|
+
# 2. Start isolated stacks for all worktrees
|
|
50
|
+
npx wtc start
|
|
51
|
+
|
|
52
|
+
# 3. See what's running
|
|
53
|
+
npx wtc list
|
|
54
|
+
|
|
55
|
+
# 4. Open each frontend in your browser and compare
|
|
56
|
+
|
|
57
|
+
# 5. Pick the best one and pull its changes into main
|
|
58
|
+
npx wtc promote 1
|
|
59
|
+
|
|
60
|
+
# 6. Clean up everything
|
|
61
|
+
npx wtc clean
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## How It Works
|
|
65
|
+
|
|
66
|
+
### 1. Auto-Detection
|
|
67
|
+
|
|
68
|
+
`wtc` finds your compose file in the repo root, checking these names in order (matching Docker Compose's own precedence):
|
|
69
|
+
|
|
70
|
+
1. `compose.yaml`
|
|
71
|
+
2. `compose.yml`
|
|
72
|
+
3. `docker-compose.yaml`
|
|
73
|
+
4. `docker-compose.yml`
|
|
74
|
+
|
|
75
|
+
### 2. Port Parsing
|
|
76
|
+
|
|
77
|
+
It parses the YAML and scans every service's `ports:` array. For each port using the `${VAR:-default}` pattern, it extracts the env var name and default value.
|
|
78
|
+
|
|
79
|
+
**Your compose file:**
|
|
80
|
+
|
|
81
|
+
```yaml
|
|
82
|
+
services:
|
|
83
|
+
postgres:
|
|
84
|
+
image: postgres:15
|
|
85
|
+
ports:
|
|
86
|
+
- "${POSTGRES_PORT:-5434}:5432"
|
|
87
|
+
|
|
88
|
+
redis:
|
|
89
|
+
image: redis:7-alpine
|
|
90
|
+
ports:
|
|
91
|
+
- "${REDIS_PORT:-6380}:6379"
|
|
92
|
+
|
|
93
|
+
backend:
|
|
94
|
+
build:
|
|
95
|
+
context: ./backend
|
|
96
|
+
dockerfile: Dockerfile.dev
|
|
97
|
+
ports:
|
|
98
|
+
- "${BACKEND_PORT:-8000}:8000"
|
|
99
|
+
|
|
100
|
+
worker:
|
|
101
|
+
build:
|
|
102
|
+
context: ./backend
|
|
103
|
+
dockerfile: Dockerfile.dev
|
|
104
|
+
# No ports — this service is ignored by wtc
|
|
105
|
+
|
|
106
|
+
frontend:
|
|
107
|
+
build:
|
|
108
|
+
context: ./frontend
|
|
109
|
+
dockerfile: Dockerfile.dev
|
|
110
|
+
ports:
|
|
111
|
+
- "${FRONTEND_PORT:-5173}:${FRONTEND_PORT:-5173}"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`wtc` detects 4 overridable ports (postgres, redis, backend, frontend) and ignores the worker (no port mapping).
|
|
115
|
+
|
|
116
|
+
### 3. Port Allocation
|
|
117
|
+
|
|
118
|
+
Each worktree N gets unique ports using the formula:
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
port = 20000 + default_port + worktree_index
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
| Service | Main (default) | Worktree 1 | Worktree 2 | Worktree 3 |
|
|
125
|
+
|------------|---------------|------------|------------|------------|
|
|
126
|
+
| postgres | 5434 | 25435 | 25436 | 25437 |
|
|
127
|
+
| redis | 6380 | 26381 | 26382 | 26383 |
|
|
128
|
+
| backend | 8000 | 28001 | 28002 | 28003 |
|
|
129
|
+
| frontend | 5173 | 25174 | 25175 | 25176 |
|
|
130
|
+
|
|
131
|
+
The `20000` offset ensures worktree ports never collide with the main worktree's defaults or common system ports.
|
|
132
|
+
|
|
133
|
+
If a computed port exceeds 65535 (e.g. a service with a very high default port), wtc falls back to `default + 100 * N`. If even that overflows, it errors with a clear message.
|
|
134
|
+
|
|
135
|
+
### 4. Container Isolation
|
|
136
|
+
|
|
137
|
+
Each worktree gets its own `COMPOSE_PROJECT_NAME` following the pattern:
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
{repo-name}-wt-{index}-{branch}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
For example: `myapp-wt-1-feature-auth`, `myapp-wt-2-fix-billing`.
|
|
144
|
+
|
|
145
|
+
This means each worktree has completely separate:
|
|
146
|
+
- **Containers** — `myapp-wt-1-feature-auth-postgres-1`, etc.
|
|
147
|
+
- **Networks** — `myapp-wt-1-feature-auth_default`
|
|
148
|
+
- **Volumes** — `myapp-wt-1-feature-auth_pg-data`
|
|
149
|
+
|
|
150
|
+
Nothing is shared between worktrees.
|
|
151
|
+
|
|
152
|
+
### 5. File Sync
|
|
153
|
+
|
|
154
|
+
Before starting a worktree's stack, wtc copies infrastructure files from main into the worktree:
|
|
155
|
+
|
|
156
|
+
- The compose file itself
|
|
157
|
+
- Every Dockerfile referenced in `build.dockerfile` fields
|
|
158
|
+
- `.env` (or `.env.example` if `.env` doesn't exist)
|
|
159
|
+
|
|
160
|
+
This ensures the worktree always has the latest Docker setup, even if the worktree's branch doesn't have recent infrastructure changes.
|
|
161
|
+
|
|
162
|
+
### 6. Env Injection
|
|
163
|
+
|
|
164
|
+
After copying `.env`, wtc appends a clearly delimited block with the allocated port overrides:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
# existing .env content stays untouched...
|
|
168
|
+
OPENAI_API_KEY=sk-...
|
|
169
|
+
DATABASE_URL=...
|
|
170
|
+
|
|
171
|
+
# --- wtc port overrides ---
|
|
172
|
+
POSTGRES_PORT=25435
|
|
173
|
+
REDIS_PORT=26381
|
|
174
|
+
BACKEND_PORT=28001
|
|
175
|
+
FRONTEND_PORT=25174
|
|
176
|
+
# --- end wtc ---
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
This is **idempotent** — running `wtc start` again strips the old block and writes a fresh one.
|
|
180
|
+
|
|
181
|
+
## Commands
|
|
182
|
+
|
|
183
|
+
### `wtc start [indices...]`
|
|
184
|
+
|
|
185
|
+
Start Docker Compose stacks for worktrees.
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
npx wtc start # start ALL worktrees
|
|
189
|
+
npx wtc start 1 # start worktree 1 only
|
|
190
|
+
npx wtc start 1 2 3 # start worktrees 1, 2, and 3
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**What it does for each worktree:**
|
|
194
|
+
|
|
195
|
+
1. Syncs the compose file and Dockerfiles from main
|
|
196
|
+
2. Copies `.env` and injects port overrides
|
|
197
|
+
3. Runs `docker compose up -d --build` with the worktree's unique project name
|
|
198
|
+
|
|
199
|
+
**Output:**
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
=== Worktree 1: feature-auth ===
|
|
203
|
+
ℹ Path: /Users/you/myapp-feature-auth
|
|
204
|
+
ℹ Project: myapp-wt-1-feature-auth
|
|
205
|
+
ℹ Ports: POSTGRES_PORT=25435 REDIS_PORT=26381 BACKEND_PORT=28001 FRONTEND_PORT=25174
|
|
206
|
+
✔ Synced infrastructure files
|
|
207
|
+
✔ Injected port overrides into .env
|
|
208
|
+
[+] Running 5/5
|
|
209
|
+
✔ Container myapp-wt-1-feature-auth-postgres-1 Started
|
|
210
|
+
✔ Container myapp-wt-1-feature-auth-redis-1 Started
|
|
211
|
+
✔ Container myapp-wt-1-feature-auth-backend-1 Started
|
|
212
|
+
✔ Container myapp-wt-1-feature-auth-frontend-1 Started
|
|
213
|
+
✔ Worktree 1 started
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
After starting, it prints a table of all worktrees with their URLs.
|
|
217
|
+
|
|
218
|
+
### `wtc stop [indices...]`
|
|
219
|
+
|
|
220
|
+
Stop Docker Compose stacks for worktrees.
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
npx wtc stop # stop ALL worktrees
|
|
224
|
+
npx wtc stop 1 # stop worktree 1 only
|
|
225
|
+
npx wtc stop 1 2 # stop worktrees 1 and 2
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Runs `docker compose down` for each target worktree. Data in named volumes is preserved — only containers and networks are removed.
|
|
229
|
+
|
|
230
|
+
### `wtc restart [indices...]`
|
|
231
|
+
|
|
232
|
+
Stop and then start worktrees. This is a full restart: re-syncs files, re-injects env vars, and rebuilds containers.
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
npx wtc restart # restart ALL worktrees
|
|
236
|
+
npx wtc restart 1 # restart worktree 1 only
|
|
237
|
+
npx wtc restart 1 2 # restart worktrees 1 and 2
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**When to use restart:**
|
|
241
|
+
- An agent wrote a database migration that needs to run on startup
|
|
242
|
+
- Dockerfiles were changed and containers need rebuilding
|
|
243
|
+
- The compose file was modified
|
|
244
|
+
- `.env` values in main changed and need to be re-synced
|
|
245
|
+
|
|
246
|
+
### `wtc list` / `wtc ls`
|
|
247
|
+
|
|
248
|
+
Show all worktrees with their branch, status (up/down), URLs, and port assignments.
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
npx wtc list
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**Output:**
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
┌───────┬───────────────┬────────┬────────────────────────┬─────────────────────────────────────────────────────────┐
|
|
258
|
+
│ Index │ Branch │ Status │ URL │ Ports │
|
|
259
|
+
├───────┼───────────────┼────────┼────────────────────────┼─────────────────────────────────────────────────────────┤
|
|
260
|
+
│ - │ main │ - │ - │ postgres:5434 redis:6380 backend:8000 frontend:5173 │
|
|
261
|
+
├───────┼───────────────┼────────┼────────────────────────┼─────────────────────────────────────────────────────────┤
|
|
262
|
+
│ 1 │ feature-auth │ up │ http://localhost:25174 │ postgres:25435 redis:26381 backend:28001 frontend:25174 │
|
|
263
|
+
├───────┼───────────────┼────────┼────────────────────────┼─────────────────────────────────────────────────────────┤
|
|
264
|
+
│ 2 │ fix-billing │ down │ http://localhost:25175 │ postgres:25436 redis:26382 backend:28002 frontend:25175 │
|
|
265
|
+
└───────┴───────────────┴────────┴────────────────────────┴─────────────────────────────────────────────────────────┘
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
- **Index** — the number you pass to other commands (`wtc start 1`, `wtc promote 2`)
|
|
269
|
+
- **Branch** — the git branch checked out in that worktree
|
|
270
|
+
- **Status** — whether Docker containers are currently running
|
|
271
|
+
- **URL** — the frontend URL (auto-detected from services named `frontend`, `web`, `app`, or `ui`)
|
|
272
|
+
- **Ports** — all allocated ports for that worktree
|
|
273
|
+
|
|
274
|
+
### `wtc promote <index>`
|
|
275
|
+
|
|
276
|
+
Copy all changed files from a worktree into your current branch as uncommitted changes.
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
npx wtc promote 1
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**What it does:**
|
|
283
|
+
|
|
284
|
+
1. Finds the divergence point between your current branch and the worktree's branch (`git merge-base`)
|
|
285
|
+
2. Collects all files that changed in the worktree (committed, uncommitted, and untracked)
|
|
286
|
+
3. **Safety check** — aborts if any of those files have uncommitted changes in your current branch (prevents overwriting your work)
|
|
287
|
+
4. Copies changed files into your repo (or deletes files that were removed in the worktree)
|
|
288
|
+
5. Leaves everything as uncommitted changes so you can review before committing
|
|
289
|
+
|
|
290
|
+
**What it excludes automatically:**
|
|
291
|
+
- `.env` — this was injected by wtc, not authored by the agent
|
|
292
|
+
- `docker-compose.yml` / `compose.yml` — synced from main, not a real change
|
|
293
|
+
|
|
294
|
+
**Output:**
|
|
295
|
+
|
|
296
|
+
```
|
|
297
|
+
ℹ Promoting worktree 1 (feature-auth) into main
|
|
298
|
+
✔ Promoted 12 file(s). Changes are uncommitted in main.
|
|
299
|
+
src/auth/login.ts
|
|
300
|
+
src/auth/session.ts
|
|
301
|
+
src/middleware/auth.ts
|
|
302
|
+
...
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
**If there's a conflict:**
|
|
306
|
+
|
|
307
|
+
```
|
|
308
|
+
✖ Abort: the following files have uncommitted changes and would be overwritten:
|
|
309
|
+
src/auth/login.ts
|
|
310
|
+
|
|
311
|
+
Commit or stash your local changes first, then re-run promote.
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### `wtc clean`
|
|
315
|
+
|
|
316
|
+
Stop all worktree containers, remove all worktrees (except your current one), and prune stale Docker resources.
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
npx wtc clean
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
**What it does:**
|
|
323
|
+
|
|
324
|
+
1. For each non-main worktree:
|
|
325
|
+
- Stops its Docker Compose stack (`docker compose down`)
|
|
326
|
+
- Removes the git worktree (`git worktree remove --force`)
|
|
327
|
+
2. Prunes stale git worktree references (`git worktree prune`)
|
|
328
|
+
3. Removes any orphaned Docker containers, networks, and volumes matching the `*-wt-*` pattern
|
|
329
|
+
|
|
330
|
+
**Output:**
|
|
331
|
+
|
|
332
|
+
```
|
|
333
|
+
ℹ Stopping containers for myapp-wt-1-feature-auth...
|
|
334
|
+
ℹ Removing worktree: /Users/you/myapp-feature-auth
|
|
335
|
+
ℹ Stopping containers for myapp-wt-2-fix-billing...
|
|
336
|
+
ℹ Removing worktree: /Users/you/myapp-fix-billing
|
|
337
|
+
✔ Cleanup complete.
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### `wtc mcp`
|
|
341
|
+
|
|
342
|
+
Start the MCP (Model Context Protocol) server. This is not meant to be run manually — it's used by AI agents. See the [MCP Server](#mcp-server) section below.
|
|
343
|
+
|
|
344
|
+
## Preparing Your docker-compose.yml
|
|
345
|
+
|
|
346
|
+
For `wtc` to isolate a service's port, the host port must use the `${VAR:-default}` pattern:
|
|
347
|
+
|
|
348
|
+
```yaml
|
|
349
|
+
# wtc CAN isolate this (env var pattern)
|
|
350
|
+
ports:
|
|
351
|
+
- "${BACKEND_PORT:-8000}:8000"
|
|
352
|
+
|
|
353
|
+
# wtc CANNOT isolate this (hardcoded number)
|
|
354
|
+
ports:
|
|
355
|
+
- "8080:8080"
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
If `wtc` finds hardcoded ports, it warns you and suggests the fix:
|
|
359
|
+
|
|
360
|
+
```
|
|
361
|
+
⚠ Service "nginx" uses a raw port mapping (8080:80).
|
|
362
|
+
To enable port isolation, change it to: "${NGINX_PORT:-8080}:80"
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Supported port formats
|
|
366
|
+
|
|
367
|
+
`wtc` handles all common Docker Compose port syntaxes:
|
|
368
|
+
|
|
369
|
+
```yaml
|
|
370
|
+
# Standard — most common
|
|
371
|
+
- "${BACKEND_PORT:-8000}:8000"
|
|
372
|
+
|
|
373
|
+
# Same var for host and container (when the app reads the port from env)
|
|
374
|
+
- "${FRONTEND_PORT:-5173}:${FRONTEND_PORT:-5173}"
|
|
375
|
+
|
|
376
|
+
# IP-bound
|
|
377
|
+
- "127.0.0.1:${API_PORT:-3000}:3000"
|
|
378
|
+
|
|
379
|
+
# With protocol
|
|
380
|
+
- "${BACKEND_PORT:-8000}:8000/tcp"
|
|
381
|
+
|
|
382
|
+
# Multiple ports per service
|
|
383
|
+
- "${BACKEND_PORT:-8000}:8000"
|
|
384
|
+
- "${DEBUG_PORT:-9229}:9229"
|
|
385
|
+
|
|
386
|
+
# Long-form syntax
|
|
387
|
+
- target: 8000
|
|
388
|
+
published: "${BACKEND_PORT:-8000}"
|
|
389
|
+
protocol: tcp
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
## Configuration (Optional)
|
|
393
|
+
|
|
394
|
+
`wtc` works zero-config out of the box. For project-specific needs, create a `.wtcrc.json` in your repo root:
|
|
395
|
+
|
|
396
|
+
```json
|
|
397
|
+
{
|
|
398
|
+
"sync": ["backend/alembic", "backend/alembic.ini"],
|
|
399
|
+
"envOverrides": {
|
|
400
|
+
"VITE_API_URL": "http://localhost:${BACKEND_PORT}"
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
Or add a `"wtc"` key in your `package.json`:
|
|
406
|
+
|
|
407
|
+
```json
|
|
408
|
+
{
|
|
409
|
+
"wtc": {
|
|
410
|
+
"sync": ["backend/alembic"],
|
|
411
|
+
"envOverrides": {
|
|
412
|
+
"VITE_API_URL": "http://localhost:${BACKEND_PORT}"
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
Config is looked for in this order:
|
|
419
|
+
1. `.wtcrc.json` in repo root
|
|
420
|
+
2. `"wtc"` key in `package.json`
|
|
421
|
+
3. No config (zero-config defaults)
|
|
422
|
+
|
|
423
|
+
### `sync`
|
|
424
|
+
|
|
425
|
+
Extra files or directories to copy from main into each worktree on every start.
|
|
426
|
+
|
|
427
|
+
**Why this exists:** Git worktrees check out a branch's files, but the branch may not have the latest infrastructure files (migration configs, seed scripts, etc.). This setting ensures every worktree gets fresh copies of these files from main.
|
|
428
|
+
|
|
429
|
+
**Example:** Your backend uses Alembic for database migrations. The migration files live in `backend/alembic/` and the config in `backend/alembic.ini`. Without syncing these, a worktree on an older branch would have outdated migration files.
|
|
430
|
+
|
|
431
|
+
```json
|
|
432
|
+
{
|
|
433
|
+
"sync": [
|
|
434
|
+
"backend/alembic",
|
|
435
|
+
"backend/alembic.ini"
|
|
436
|
+
]
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
Both files and directories are supported. Directories are copied recursively.
|
|
441
|
+
|
|
442
|
+
### `envOverrides`
|
|
443
|
+
|
|
444
|
+
Additional environment variables to inject into each worktree's `.env`. Supports `${VAR}` interpolation with the allocated port values.
|
|
445
|
+
|
|
446
|
+
**Why this exists:** Some env vars depend on allocated ports. For example, a frontend might need `VITE_API_URL` pointing to the backend's allocated port. Since `wtc` assigns different backend ports per worktree, this URL must be derived dynamically.
|
|
447
|
+
|
|
448
|
+
```json
|
|
449
|
+
{
|
|
450
|
+
"envOverrides": {
|
|
451
|
+
"VITE_API_URL": "http://localhost:${BACKEND_PORT}",
|
|
452
|
+
"VITE_WS_URL": "ws://localhost:${BACKEND_PORT}/ws"
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
With worktree 1 getting `BACKEND_PORT=28001`, this produces:
|
|
458
|
+
|
|
459
|
+
```bash
|
|
460
|
+
VITE_API_URL=http://localhost:28001
|
|
461
|
+
VITE_WS_URL=ws://localhost:28001/ws
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
You can reference any env var that `wtc` allocates (any `${VAR:-default}` port pattern found in your compose file).
|
|
465
|
+
|
|
466
|
+
## MCP Server
|
|
467
|
+
|
|
468
|
+
`wtc` ships a built-in [MCP](https://modelcontextprotocol.io/) server so AI agents can manage their worktree's Docker stack programmatically — without shelling out to the CLI.
|
|
469
|
+
|
|
470
|
+
### Why
|
|
471
|
+
|
|
472
|
+
When an AI agent is working in a worktree and makes changes that need a container restart (like adding a database migration), it needs a way to restart its stack. The MCP server exposes all `wtc` commands as tools that agents can call directly.
|
|
473
|
+
|
|
474
|
+
### Setup
|
|
475
|
+
|
|
476
|
+
The MCP server uses stdio transport. Configure it in your agent's MCP settings:
|
|
477
|
+
|
|
478
|
+
**Claude Code** (`.claude/settings.json` or `.claude/settings.local.json`):
|
|
479
|
+
|
|
480
|
+
```json
|
|
481
|
+
{
|
|
482
|
+
"mcpServers": {
|
|
483
|
+
"wtc": {
|
|
484
|
+
"command": "npx",
|
|
485
|
+
"args": ["wtc", "mcp"]
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
**Codex:**
|
|
492
|
+
|
|
493
|
+
```json
|
|
494
|
+
{
|
|
495
|
+
"servers": {
|
|
496
|
+
"wtc": {
|
|
497
|
+
"command": "npx",
|
|
498
|
+
"args": ["wtc", "mcp"]
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
### Available Tools
|
|
505
|
+
|
|
506
|
+
| Tool | Parameters | Description |
|
|
507
|
+
|------|-----------|-------------|
|
|
508
|
+
| `wtc_start` | `indices?: number[]` | Start worktree stacks. Pass specific indices or omit for all. |
|
|
509
|
+
| `wtc_stop` | `indices?: number[]` | Stop worktree stacks. Pass specific indices or omit for all. |
|
|
510
|
+
| `wtc_restart` | `indices?: number[]` | Restart worktree stacks. Use after DB migrations, Dockerfile changes, or config updates that require fresh containers. |
|
|
511
|
+
| `wtc_list` | none | List all worktrees with branch, status (up/down), ports, and URLs. Returns structured JSON. |
|
|
512
|
+
| `wtc_promote` | `index: number` | Copy changed files from a worktree into the current branch as uncommitted changes. |
|
|
513
|
+
| `wtc_clean` | none | Stop all containers, remove all worktrees, prune stale Docker resources. |
|
|
514
|
+
|
|
515
|
+
### Agent Workflow Example
|
|
516
|
+
|
|
517
|
+
1. **Human** creates worktrees and runs `npx wtc start`
|
|
518
|
+
2. **Agents** are spawned in separate worktrees, each with its own isolated stack
|
|
519
|
+
3. An agent writes a database migration, then calls `wtc_restart` with its worktree index — containers restart, the migration runs on startup
|
|
520
|
+
4. The agent verifies the migration worked by hitting its own backend URL
|
|
521
|
+
5. **Human** opens each worktree's frontend URL, compares the results side by side
|
|
522
|
+
6. **Human** runs `npx wtc promote 1` to pull the best agent's changes into main
|
|
523
|
+
7. **Human** runs `npx wtc clean` to tear everything down
|
|
524
|
+
|
|
525
|
+
## Full Example Walkthrough
|
|
526
|
+
|
|
527
|
+
Here's a complete end-to-end example with a typical web app:
|
|
528
|
+
|
|
529
|
+
```bash
|
|
530
|
+
# You have a repo with docker-compose.yml containing postgres, redis, backend, frontend
|
|
531
|
+
cd myapp
|
|
532
|
+
|
|
533
|
+
# Install wtc
|
|
534
|
+
pnpm add -D worktree-compose
|
|
535
|
+
|
|
536
|
+
# Create branches for two AI agents to work on
|
|
537
|
+
git branch agent-1-auth
|
|
538
|
+
git branch agent-2-auth
|
|
539
|
+
|
|
540
|
+
# Create worktrees
|
|
541
|
+
git worktree add ../myapp-agent-1 agent-1-auth
|
|
542
|
+
git worktree add ../myapp-agent-2 agent-2-auth
|
|
543
|
+
|
|
544
|
+
# Start both stacks
|
|
545
|
+
npx wtc start
|
|
546
|
+
|
|
547
|
+
# Output shows:
|
|
548
|
+
# Worktree 1 (agent-1-auth): backend:28001 frontend:25174
|
|
549
|
+
# Worktree 2 (agent-2-auth): backend:28002 frontend:25175
|
|
550
|
+
|
|
551
|
+
# Point each agent at its worktree directory
|
|
552
|
+
# Agent 1 works in ../myapp-agent-1
|
|
553
|
+
# Agent 2 works in ../myapp-agent-2
|
|
554
|
+
|
|
555
|
+
# Check status anytime
|
|
556
|
+
npx wtc list
|
|
557
|
+
|
|
558
|
+
# Compare frontends side by side
|
|
559
|
+
# http://localhost:25174 (agent 1)
|
|
560
|
+
# http://localhost:25175 (agent 2)
|
|
561
|
+
|
|
562
|
+
# Agent 1 did a better job — promote its changes
|
|
563
|
+
npx wtc promote 1
|
|
564
|
+
|
|
565
|
+
# Review the changes
|
|
566
|
+
git diff
|
|
567
|
+
|
|
568
|
+
# Commit if happy
|
|
569
|
+
git add -A && git commit -m "feat: add auth (from agent 1)"
|
|
570
|
+
|
|
571
|
+
# Clean up
|
|
572
|
+
npx wtc clean
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
## Requirements
|
|
576
|
+
|
|
577
|
+
- **Node.js** >= 18
|
|
578
|
+
- **Git** with worktree support (any modern version)
|
|
579
|
+
- **Docker** with Compose v2 (`docker compose`, not the legacy `docker-compose`)
|
|
580
|
+
- A `docker-compose.yml` with `${VAR:-default}` port patterns for any service you want isolated
|
|
581
|
+
|
|
582
|
+
## Troubleshooting
|
|
583
|
+
|
|
584
|
+
### "No compose file found"
|
|
585
|
+
|
|
586
|
+
`wtc` looks for compose files in the git repo root (found via `git rev-parse --show-toplevel`), not the current directory. Make sure your compose file is in the repo root and named one of: `compose.yaml`, `compose.yml`, `docker-compose.yaml`, `docker-compose.yml`.
|
|
587
|
+
|
|
588
|
+
### "No extra worktrees found"
|
|
589
|
+
|
|
590
|
+
You need to create git worktrees before using `wtc`. Create them with:
|
|
591
|
+
|
|
592
|
+
```bash
|
|
593
|
+
git worktree add ../my-feature my-feature-branch
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
### Ports not changing
|
|
597
|
+
|
|
598
|
+
Make sure your compose file uses `${VAR:-default}` for host ports, not hardcoded numbers. `wtc` can only override ports that use env var patterns.
|
|
599
|
+
|
|
600
|
+
### "Docker daemon not running"
|
|
601
|
+
|
|
602
|
+
Start Docker Desktop (or the Docker daemon) before running `wtc start`.
|
|
603
|
+
|
|
604
|
+
### Promote fails with "Not a valid object name detached"
|
|
605
|
+
|
|
606
|
+
This happened in older versions when a worktree was in detached HEAD state. Update to the latest version — this is now handled correctly.
|
|
607
|
+
|
|
608
|
+
### Stale containers after manual cleanup
|
|
609
|
+
|
|
610
|
+
If you manually removed worktrees without running `wtc clean`, orphaned containers may remain. Run `wtc clean` to remove them, or manually:
|
|
611
|
+
|
|
612
|
+
```bash
|
|
613
|
+
docker ps -a --filter "name=-wt-" -q | xargs docker rm -f
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
## License
|
|
617
|
+
|
|
618
|
+
MIT
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { startCommand } from "./commands/start.js";
|
|
4
|
+
import { stopCommand } from "./commands/stop.js";
|
|
5
|
+
import { restartCommand } from "./commands/restart.js";
|
|
6
|
+
import { listCommand } from "./commands/list.js";
|
|
7
|
+
import { promoteCommand } from "./commands/promote.js";
|
|
8
|
+
import { cleanCommand } from "./commands/clean.js";
|
|
9
|
+
import { startMcpServer } from "./mcp/server.js";
|
|
10
|
+
import { error } from "./utils/log.js";
|
|
11
|
+
const program = new Command();
|
|
12
|
+
program
|
|
13
|
+
.name("wtc")
|
|
14
|
+
.description("Zero-config Docker Compose isolation for git worktrees")
|
|
15
|
+
.version("0.1.0");
|
|
16
|
+
program
|
|
17
|
+
.command("start")
|
|
18
|
+
.description("Start Docker Compose stacks for worktrees")
|
|
19
|
+
.argument("[indices...]", "Worktree indices to start (omit for all)")
|
|
20
|
+
.action((indices) => {
|
|
21
|
+
wrap(() => startCommand(indices.map(Number)));
|
|
22
|
+
});
|
|
23
|
+
program
|
|
24
|
+
.command("stop")
|
|
25
|
+
.description("Stop Docker Compose stacks for worktrees")
|
|
26
|
+
.argument("[indices...]", "Worktree indices to stop (omit for all)")
|
|
27
|
+
.action((indices) => {
|
|
28
|
+
wrap(() => stopCommand(indices.map(Number)));
|
|
29
|
+
});
|
|
30
|
+
program
|
|
31
|
+
.command("restart")
|
|
32
|
+
.description("Restart Docker Compose stacks for worktrees")
|
|
33
|
+
.argument("[indices...]", "Worktree indices to restart (omit for all)")
|
|
34
|
+
.action((indices) => {
|
|
35
|
+
wrap(() => restartCommand(indices.map(Number)));
|
|
36
|
+
});
|
|
37
|
+
program
|
|
38
|
+
.command("list")
|
|
39
|
+
.alias("ls")
|
|
40
|
+
.description("List all worktrees with ports and URLs")
|
|
41
|
+
.action(() => {
|
|
42
|
+
wrap(() => listCommand());
|
|
43
|
+
});
|
|
44
|
+
program
|
|
45
|
+
.command("promote")
|
|
46
|
+
.description("Copy changed files from a worktree into the current branch")
|
|
47
|
+
.argument("<index>", "Worktree index to promote")
|
|
48
|
+
.action((index) => {
|
|
49
|
+
wrap(() => promoteCommand(Number(index)));
|
|
50
|
+
});
|
|
51
|
+
program
|
|
52
|
+
.command("clean")
|
|
53
|
+
.description("Stop all containers, remove all worktrees, prune everything")
|
|
54
|
+
.action(() => {
|
|
55
|
+
wrap(() => cleanCommand());
|
|
56
|
+
});
|
|
57
|
+
program
|
|
58
|
+
.command("mcp")
|
|
59
|
+
.description("Start the MCP server (stdio transport)")
|
|
60
|
+
.action(() => {
|
|
61
|
+
startMcpServer().catch((err) => {
|
|
62
|
+
error(String(err));
|
|
63
|
+
process.exit(1);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
function wrap(fn) {
|
|
67
|
+
try {
|
|
68
|
+
fn();
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
error(err instanceof Error ? err.message : String(err));
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
program.parse();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function cleanCommand(): void;
|