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.
Files changed (48) hide show
  1. package/README.md +618 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +75 -0
  4. package/dist/commands/clean.d.ts +1 -0
  5. package/dist/commands/clean.js +49 -0
  6. package/dist/commands/list.d.ts +1 -0
  7. package/dist/commands/list.js +71 -0
  8. package/dist/commands/promote.d.ts +1 -0
  9. package/dist/commands/promote.js +35 -0
  10. package/dist/commands/restart.d.ts +1 -0
  11. package/dist/commands/restart.js +6 -0
  12. package/dist/commands/start.d.ts +1 -0
  13. package/dist/commands/start.js +37 -0
  14. package/dist/commands/stop.d.ts +1 -0
  15. package/dist/commands/stop.js +24 -0
  16. package/dist/compose/detect.d.ts +3 -0
  17. package/dist/compose/detect.js +25 -0
  18. package/dist/compose/parse.d.ts +2 -0
  19. package/dist/compose/parse.js +55 -0
  20. package/dist/compose/types.d.ts +13 -0
  21. package/dist/compose/types.js +1 -0
  22. package/dist/config.d.ts +5 -0
  23. package/dist/config.js +16 -0
  24. package/dist/context.d.ts +14 -0
  25. package/dist/context.js +29 -0
  26. package/dist/git/promote.d.ts +4 -0
  27. package/dist/git/promote.js +58 -0
  28. package/dist/git/worktree.d.ts +10 -0
  29. package/dist/git/worktree.js +40 -0
  30. package/dist/mcp/server.d.ts +1 -0
  31. package/dist/mcp/server.js +132 -0
  32. package/dist/ports/allocate.d.ts +3 -0
  33. package/dist/ports/allocate.js +29 -0
  34. package/dist/ports/extract.d.ts +4 -0
  35. package/dist/ports/extract.js +92 -0
  36. package/dist/ports/types.d.ts +13 -0
  37. package/dist/ports/types.js +1 -0
  38. package/dist/sync/env.d.ts +5 -0
  39. package/dist/sync/env.js +58 -0
  40. package/dist/sync/files.d.ts +2 -0
  41. package/dist/sync/files.js +59 -0
  42. package/dist/utils/exec.d.ts +4 -0
  43. package/dist/utils/exec.js +23 -0
  44. package/dist/utils/log.d.ts +5 -0
  45. package/dist/utils/log.js +16 -0
  46. package/dist/utils/sanitize.d.ts +2 -0
  47. package/dist/utils/sanitize.js +11 -0
  48. 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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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;