{"_id":"worktree-compose","_rev":"6-9927fef2d2aa7902a628ba834a3b4f88","name":"worktree-compose","dist-tags":{"latest":"1.0.0"},"versions":{"0.1.0":{"name":"worktree-compose","version":"0.1.0","keywords":["git","worktree","docker","compose","isolation","parallel","development","mcp"],"license":"MIT","_id":"worktree-compose@0.1.0","maintainers":[{"name":"levw","email":"abdelraahmanmostafa@gmail.com"}],"bin":{"wtc":"dist/cli.js","worktree-compose":"dist/cli.js"},"dist":{"shasum":"512eeb71031292f83a8fe8a1d75c9ecb482b3a46","tarball":"https://registry.npmjs.org/worktree-compose/-/worktree-compose-0.1.0.tgz","fileCount":48,"integrity":"sha512-mno0JdE1kxkL2zZQj9wFM2Vk+XWw+/IIqLJY1zSnU/lyAtIvvwMQDIbbvhvJWjEhDBd2THDO2KUFhso3FskfuA==","signatures":[{"sig":"MEUCIQD+MUkchz14saRflaBaopoo6+KQlLR2JjKfi/MX0TIjgwIgQeyG7yfC04oYtTnFEm1BC1Z8pC7w4LAyUOEpxLQiy5M=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":61509},"type":"module","engines":{"node":">=18"},"gitHead":"cd0bb6efa2a2598366d0fab3019b581cc9c8c1eb","scripts":{"dev":"tsx src/cli.ts","test":"NODE_OPTIONS='--experimental-vm-modules' jest --coverage","build":"tsc","prepublishOnly":"npm run build"},"_npmUser":{"name":"levw","email":"abdelraahmanmostafa@gmail.com"},"_npmVersion":"10.9.2","description":"Zero-config Docker Compose isolation for git worktrees","directories":{},"_nodeVersion":"22.15.0","dependencies":{"zod":"^3.24.4","yaml":"^2.8.0","chalk":"^5.4.1","commander":"^14.0.0","cli-table3":"^0.6.5","@modelcontextprotocol/sdk":"^1.12.1"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.4","jest":"^29.7.0","ts-jest":"^29.3.4","typescript":"^5.8.3","@types/jest":"^29.5.14","@types/node":"^22.15.21"},"_npmOperationalInternal":{"tmp":"tmp/worktree-compose_0.1.0_1771851495865_0.8841681298209596","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"worktree-compose","version":"0.1.2","keywords":["git","worktree","docker","compose","isolation","parallel","development","mcp"],"license":"MIT","_id":"worktree-compose@0.1.2","maintainers":[{"name":"levw","email":"abdelraahmanmostafa@gmail.com"}],"homepage":"https://github.com/LevwTech/worktree-compose#readme","bugs":{"url":"https://github.com/LevwTech/worktree-compose/issues"},"bin":{"wtc":"dist/cli.js","worktree-compose":"dist/cli.js"},"dist":{"shasum":"ae379075fdc35f1aa67118f3af3773bdfb0747a8","tarball":"https://registry.npmjs.org/worktree-compose/-/worktree-compose-0.1.2.tgz","fileCount":48,"integrity":"sha512-ICgHWogMdVeIzJ3osaEmO7N4xKeyij2vcrFxdDDU/lCx2rQqtzQp4pFHo01mN7ZEu9pm5/pxFj0RiPweMUhMJA==","signatures":[{"sig":"MEUCIQCuPuOA5NZY96LUCrKlv2wS0o0vuhvivVvFPOpPWg3KDwIgJMvGCkfzi6XtWHD9vo+cwpF6+0h8toDpTQYwjVIJ29s=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":50872},"type":"module","engines":{"node":">=18"},"gitHead":"48e328c83bf689a4482d6e011505f663a9746bda","scripts":{"dev":"tsx src/cli.ts","test":"NODE_OPTIONS='--experimental-vm-modules' jest --coverage","build":"tsc","prepublishOnly":"npm run build"},"_npmUser":{"name":"levw","email":"abdelraahmanmostafa@gmail.com"},"repository":{"url":"git+https://github.com/LevwTech/worktree-compose.git","type":"git"},"_npmVersion":"10.9.2","description":"Zero-config Docker Compose isolation for git worktrees","directories":{},"_nodeVersion":"22.15.0","dependencies":{"zod":"^3.24.4","yaml":"^2.8.0","chalk":"^5.4.1","commander":"^14.0.0","cli-table3":"^0.6.5","@modelcontextprotocol/sdk":"^1.12.1"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.4","jest":"^29.7.0","ts-jest":"^29.3.4","typescript":"^5.8.3","@types/jest":"^29.5.14","@types/node":"^22.15.21"},"_npmOperationalInternal":{"tmp":"tmp/worktree-compose_0.1.2_1771852302690_0.8025095032743181","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"worktree-compose","version":"0.1.3","keywords":["git","worktree","docker","compose","isolation","parallel","development","mcp"],"license":"MIT","_id":"worktree-compose@0.1.3","maintainers":[{"name":"levw","email":"abdelraahmanmostafa@gmail.com"}],"homepage":"https://github.com/LevwTech/worktree-compose#readme","bugs":{"url":"https://github.com/LevwTech/worktree-compose/issues"},"bin":{"wtc":"dist/cli.js","worktree-compose":"dist/cli.js"},"dist":{"shasum":"d0c5ff1bea48aefe6612c6d49b4498a6019a915d","tarball":"https://registry.npmjs.org/worktree-compose/-/worktree-compose-0.1.3.tgz","fileCount":49,"integrity":"sha512-se3f2PYZFLd0V6rWbIR3sJpLEwCngRdLIluEHQIiuEfE+V1MdLGmh+iyYXCp61Gz0SblXIyGyVetp36GBoUmWw==","signatures":[{"sig":"MEUCIFveiv7FN0ehLdOfkZSWGUzTMKtd4jphE5sJtoTUHgCCAiEA6/O3eAsIPKjHbEeeZGgan2i73BEAVLWwhQu9HOPeBXw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":52073},"type":"module","engines":{"node":">=18"},"gitHead":"0fa7a8f98c4b2ef85eb2f3ef6f5523214102e8ac","scripts":{"dev":"tsx src/cli.ts","test":"NODE_OPTIONS='--experimental-vm-modules' jest --coverage","build":"tsc","prepublishOnly":"npm run build"},"_npmUser":{"name":"levw","email":"abdelraahmanmostafa@gmail.com"},"repository":{"url":"git+https://github.com/LevwTech/worktree-compose.git","type":"git"},"_npmVersion":"10.9.2","description":"Zero-config Docker Compose isolation for git worktrees","directories":{},"_nodeVersion":"22.15.0","dependencies":{"zod":"^3.24.4","yaml":"^2.8.0","chalk":"^5.4.1","commander":"^14.0.0","cli-table3":"^0.6.5","@modelcontextprotocol/sdk":"^1.12.1"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.4","jest":"^29.7.0","ts-jest":"^29.3.4","typescript":"^5.8.3","@types/jest":"^29.5.14","@types/node":"^22.15.21"},"_npmOperationalInternal":{"tmp":"tmp/worktree-compose_0.1.3_1771856832816_0.670303059378953","host":"s3://npm-registry-packages-npm-production"}},"0.1.4":{"name":"worktree-compose","version":"0.1.4","keywords":["git","worktree","docker","compose","isolation","parallel","development","mcp"],"license":"MIT","_id":"worktree-compose@0.1.4","maintainers":[{"name":"levw","email":"abdelraahmanmostafa@gmail.com"}],"homepage":"https://github.com/LevwTech/worktree-compose#readme","bugs":{"url":"https://github.com/LevwTech/worktree-compose/issues"},"bin":{"wtc":"dist/cli.js","worktree-compose":"dist/cli.js"},"dist":{"shasum":"effdb64c149bdbfbeb7a98aded6185fceb63161d","tarball":"https://registry.npmjs.org/worktree-compose/-/worktree-compose-0.1.4.tgz","fileCount":49,"integrity":"sha512-bRkYW2eGxDIxRbl3hb4qLIGGqJjWHYt0L/TjLZ+9pK9U2PGRReN+HIoLOSBBq7rcnTqsQdQU/r6nfzMwX95U6g==","signatures":[{"sig":"MEUCIG56otNErZIZ82OI7ZsDwHBs6h3FAYc7/USoXHukkKznAiEA7awPCQb/6fPB+gaJVAPGi5NgkiRbix0E0Tni1W1vuPY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":52083},"type":"module","engines":{"node":">=18"},"gitHead":"bd1818154463276b63b9983f54ef1122f0d52239","scripts":{"dev":"tsx src/cli.ts","test":"NODE_OPTIONS='--experimental-vm-modules' jest --coverage","build":"tsc","prepublishOnly":"npm run build"},"_npmUser":{"name":"levw","email":"abdelraahmanmostafa@gmail.com"},"repository":{"url":"git+https://github.com/LevwTech/worktree-compose.git","type":"git"},"_npmVersion":"10.9.2","description":"Zero-config Docker Compose isolation for git worktrees","directories":{},"_nodeVersion":"22.15.0","dependencies":{"zod":"^3.24.4","yaml":"^2.8.0","chalk":"^5.4.1","commander":"^14.0.0","cli-table3":"^0.6.5","@modelcontextprotocol/sdk":"^1.12.1"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.4","jest":"^29.7.0","ts-jest":"^29.3.4","typescript":"^5.8.3","@types/jest":"^29.5.14","@types/node":"^22.15.21"},"_npmOperationalInternal":{"tmp":"tmp/worktree-compose_0.1.4_1771862932519_0.5698627442711974","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"worktree-compose","version":"0.2.0","keywords":["git","worktree","docker","compose","isolation","parallel","development","mcp"],"license":"MIT","_id":"worktree-compose@0.2.0","maintainers":[{"name":"levw","email":"abdelraahmanmostafa@gmail.com"}],"homepage":"https://github.com/mostafasudo/worktree-compose#readme","bugs":{"url":"https://github.com/mostafasudo/worktree-compose/issues"},"bin":{"wtc":"dist/cli.js","worktree-compose":"dist/cli.js"},"dist":{"shasum":"a58d16144467816253bd7b2e0325849b0ff7d422","tarball":"https://registry.npmjs.org/worktree-compose/-/worktree-compose-0.2.0.tgz","fileCount":49,"integrity":"sha512-Tt/4cTOWf4rkfbKJ1B/H9f6Wmt8Cs3H4EmODmHIr++jqW1F0ajp4/dtpRxJ1REEo48HxARnc9JhOgUUcKFwSBw==","signatures":[{"sig":"MEYCIQCPx1rVKW/aWp6C/cH4WZMyIynljQQqVtceYS70tgVQDAIhAJ+Ba5PmdnRwY3k+GUA4X8a9/ShX8KuBYqZTeBg+uET3","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":68116},"type":"module","_from":"file:worktree-compose-0.2.0.tgz","engines":{"node":">=18"},"scripts":{"dev":"tsx src/cli.ts","test":"NODE_OPTIONS='--experimental-vm-modules' jest --coverage","build":"tsc","release":"set -a && . ./.env && set +a && pnpm publish --no-git-checks"},"_npmUser":{"name":"levw","email":"abdelraahmanmostafa@gmail.com"},"_resolved":"/private/var/folders/t7/7td0y11n219fy3g87clfnv_r0000gn/T/b08c8467da931124a0f62616bcff2bc9/worktree-compose-0.2.0.tgz","_integrity":"sha512-Tt/4cTOWf4rkfbKJ1B/H9f6Wmt8Cs3H4EmODmHIr++jqW1F0ajp4/dtpRxJ1REEo48HxARnc9JhOgUUcKFwSBw==","repository":{"url":"git+https://github.com/mostafasudo/worktree-compose.git","type":"git"},"_npmVersion":"10.9.2","description":"Zero-config Docker Compose isolation for git worktrees","directories":{},"_nodeVersion":"22.15.0","dependencies":{"zod":"^3.24.4","yaml":"^2.8.0","chalk":"^5.4.1","commander":"^14.0.0","cli-table3":"^0.6.5","@modelcontextprotocol/sdk":"^1.12.1"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.4","jest":"^29.7.0","ts-jest":"^29.3.4","typescript":"^5.8.3","@types/jest":"^29.5.14","@types/node":"^22.15.21"},"_npmOperationalInternal":{"tmp":"tmp/worktree-compose_0.2.0_1782469884385_0.15923115862641857","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"_id":"worktree-compose@1.0.0","bin":{"wtc":"dist/cli.js","worktree-compose":"dist/cli.js"},"bugs":{"url":"https://github.com/mostafasudo/worktree-compose/issues"},"dist":{"shasum":"e8a9ebc39510be25b88e7a567457e19d97e0164b","tarball":"https://registry.npmjs.org/worktree-compose/-/worktree-compose-1.0.0.tgz","fileCount":101,"integrity":"sha512-xQSXSHCm5nBbZnzkBR86F1/Cmue59XQoeAuLh5SNe4xscWuY8/TeT+UvIVAU2VgAG/uBJ0Q/AQd7PnOPkjYqJQ==","signatures":[{"sig":"MEQCIERg/eEK6pcBjiciybJ1AsktIwi8T2luTzeCxww0Xvg2AiA5PuknmGrLcGKOjPn3ohA2vjwb5xu5MSlW1wIG7XuJCA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQC9dP9We0yX7hCtqvuVqafZ7jF/PQM+bcoq+1VuzKsxTAIgRaRLBGpzkixQz1AlKNmQOX0GQAqXdVjIksu6a2ExzlM="}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/worktree-compose@1.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":374291},"name":"worktree-compose","type":"module","engines":{"node":">=20"},"gitHead":"1aad0b05cbb465fb284b1e3637ee11e0c9d592a5","license":"MIT","scripts":{"dev":"tsx src/cli.ts","test":"NODE_OPTIONS='--experimental-vm-modules' jest --coverage","build":"tsc","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"version":"1.0.0","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"32d016b7-19d0-4bc8-be57-1a15cd59c314"}},"homepage":"https://www.worktree-compose.com","keywords":["git","worktree","docker","compose","isolation","parallel","development","mcp","podman"],"repository":{"url":"git+https://github.com/mostafasudo/worktree-compose.git","type":"git"},"_npmVersion":"11.21.1","description":"Zero-config Docker Compose isolation for git worktrees","directories":{},"maintainers":[{"name":"levw","email":"abdelraahmanmostafa@gmail.com"}],"_nodeVersion":"24.21.0","dependencies":{"zod":"^3.25.0","yaml":"^2.8.0","chalk":"^5.4.1","commander":"^14.0.0","cli-table3":"^0.6.5","@modelcontextprotocol/sdk":"^1.26.0"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.4","jest":"^29.7.0","ts-jest":"^29.3.4","typescript":"^5.8.3","@types/jest":"^29.5.14","@types/node":"^22.15.21"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/worktree-compose_1.0.0_1792828705036_0.811600480953572"}}},"time":{"created":"2026-02-23T12:58:15.865Z","modified":"2026-10-24T07:58:25.527Z","0.1.0":"2026-02-23T12:58:16.017Z","0.1.2":"2026-02-23T13:11:42.844Z","0.1.3":"2026-02-23T14:27:12.961Z","0.1.4":"2026-02-23T16:08:52.657Z","0.2.0":"2026-06-26T10:31:24.580Z","1.0.0":"2026-10-24T07:58:25.140Z"},"bugs":{"url":"https://github.com/mostafasudo/worktree-compose/issues"},"license":"MIT","homepage":"https://www.worktree-compose.com","keywords":["git","worktree","docker","compose","isolation","parallel","development","mcp","podman"],"repository":{"url":"git+https://github.com/mostafasudo/worktree-compose.git","type":"git"},"description":"Zero-config Docker Compose isolation for git worktrees","maintainers":[{"name":"levw","email":"abdelraahmanmostafa@gmail.com"}],"readme":"<div align=\"center\">\n\n<img src=\"https://raw.githubusercontent.com/mostafasudo/worktree-compose/main/worktree-compose.jpg\" alt=\"worktree-compose logo\" width=\"140\" />\n\n# worktree-compose · `wtc`\n\n### Zero-config Docker Compose isolation for git worktrees\n\nEvery worktree gets its own ports, database, cache, and containers — **automatically**.\n\n[![npm version](https://img.shields.io/npm/v/worktree-compose?label=npm&color=cb3837&logo=npm)](https://www.npmjs.com/package/worktree-compose)\n[![downloads](https://img.shields.io/npm/dm/worktree-compose?color=cb3837)](https://www.npmjs.com/package/worktree-compose)\n[![node](https://img.shields.io/node/v/worktree-compose?color=339933&logo=node.js&logoColor=white)](https://nodejs.org)\n[![license](https://img.shields.io/npm/l/worktree-compose?color=blue)](https://github.com/mostafasudo/worktree-compose/blob/main/LICENSE)\n[![DevHunt](https://img.shields.io/badge/DevHunt-%231%20Product%20of%20the%20Week-ff6154)](https://devhunt.org/tool/worktree-compose)\n\n**[worktree-compose.com](https://www.worktree-compose.com)** · [Changelog](https://github.com/mostafasudo/worktree-compose/blob/main/CHANGELOG.md) · [Agent skill](https://github.com/mostafasudo/worktree-compose/blob/main/SKILL.md) · [GitHub](https://github.com/mostafasudo/worktree-compose)\n\n<img src=\"https://raw.githubusercontent.com/mostafasudo/worktree-compose/main/wtc-explainer.gif\" alt=\"worktree-compose demo — isolated Docker stacks per git worktree\" width=\"820\" />\n\n<em>The full <code>wtc</code> workflow: an isolated Docker stack per worktree, four AI agents building in parallel, compared live on their own ports — then promote the winner and clean up. <a href=\"https://www.worktree-compose.com/#demo\">Watch the video</a>.</em>\n\n</div>\n\n```bash\nnpm install -g worktree-compose\n```\n\n```\nwtc list\n\n┌───────┬──────────────┬────────┬────────────────────────┬────────────────────────────────────────────────────────────┐\n│ Index │ Branch       │ Status │ URL                    │ Ports                                                      │\n├───────┼──────────────┼────────┼────────────────────────┼────────────────────────────────────────────────────────────┤\n│ -     │ main         │ -      │ -                      │ postgres:5434 backend:8000 frontend:5173                   │\n├───────┼──────────────┼────────┼────────────────────────┼────────────────────────────────────────────────────────────┤\n│ 1     │ feature-auth │ up     │ http://localhost:25174 │ POSTGRES_PORT=25435 BACKEND_PORT=28001 FRONTEND_PORT=25174 │\n├───────┼──────────────┼────────┼────────────────────────┼────────────────────────────────────────────────────────────┤\n│ 2     │ fix-billing  │ exited │ http://localhost:25175 │ POSTGRES_PORT=25436 BACKEND_PORT=28002 FRONTEND_PORT=25175 │\n└───────┴──────────────┴────────┴────────────────────────┴────────────────────────────────────────────────────────────┘\n```\n\n> **Type `wtc`, not `npx wtc`.** An unrelated npm package is named `wtc`, so without the global install `npx wtc` downloads and runs that package instead. For a one-off run without installing, use `npx worktree-compose <command>` (for example `npx worktree-compose list`).\n\n## Contents\n\n- [Why wtc?](#why-wtc)\n- [How is this different from `docker compose -p`?](#how-is-this-different-from-docker-compose--p)\n- [Quick Start](#quick-start)\n- [Usage](#usage)\n- [Selecting worktrees](#selecting-worktrees)\n- [Preparing your compose file](#preparing-your-compose-file)\n- [Monorepos & custom env files](#monorepos--custom-env-files)\n- [How It Works](#how-it-works) — [stable identity](#stable-worktree-identity), [ports](#port-allocation), [project names](#container-isolation-and-compose_project_name), [file sync](#file-sync), [env files](#env-files)\n- [Commands](#commands) — [what each one does to your data](#what-each-command-does-to-your-data)\n- [Configuration (Optional)](#configuration-optional)\n- [Docker, Podman, OrbStack, Colima](#docker-podman-orbstack-colima)\n- [MCP Server](#mcp-server)\n- [Agent Skill](#agent-skill)\n- [Full Example](#full-example)\n- [Upgrading from 0.2](#upgrading-from-02)\n- [Requirements](#requirements)\n- [Troubleshooting](#troubleshooting)\n- [Contributing](#contributing)\n- [License](#license)\n\n## Why wtc?\n\nSpin up multiple developers or AI agents on the same repo — each in its own [git worktree](https://git-scm.com/docs/git-worktree) — and they immediately fight over a single Docker Compose setup: port conflicts, a shared database, a shared cache, colliding containers. You can't run two stacks side by side.\n\n`wtc` reads your existing compose file and turns every worktree into a fully isolated, side-by-side environment — unique host ports, separate containers, networks, and volumes — with **zero configuration**.\n\n- ⚡ **Zero config** — reads your existing `docker-compose.yml` / `compose.yaml`, no new files required\n- 🔌 **Automatic ports** — unique, collision-free host ports per worktree\n- 📦 **Full isolation** — separate containers, networks, and volumes for each stack\n- 📌 **Stable identity** — a worktree keeps its index, ports, and project name; removing one never renumbers the others\n- 🧪 **True side-by-side** — run `N` stacks at once and compare them in the browser\n- 🔄 **One-command promote** — pull a worktree's changes back into your branch\n- 🐳 **Docker or Podman** — Docker Compose v2, Podman, OrbStack, Colima\n- 🩺 **`wtc doctor`** — finds what would break isolation before it bites\n- 🤖 **Built-in MCP server + agent skill** — let AI agents start, stop, and manage their own stacks\n\n<table>\n<tr><th>Without <code>wtc</code></th><th>With <code>wtc</code></th></tr>\n<tr><td>\n\n```bash\ngit worktree add ../app-feat feat\n# hand-pick non-colliding host ports\n# edit ../app-feat/.env per service\n# copy compose file + Dockerfiles over\ndocker compose -p app-feat up -d --build\n# ...remember to tear it all down later\n```\n\n</td><td>\n\n```bash\ngit worktree add ../app-feat feat\nwtc start\n\n# ...and when you're done:\nwtc clean\n```\n\n</td></tr>\n</table>\n\n## How is this different from `docker compose -p`?\n\n`docker compose -p` only namespaces the project. You'd still hand-pick free host ports, hand-edit each worktree's `.env`, copy infra files in, remember which project belongs to which worktree, and tear everything down yourself. `wtc` builds on that same project-name isolation and automates the rest: collision-free **port allocation**, a **stable index and project name** per worktree, per-worktree **env file injection** (including `COMPOSE_PROJECT_NAME`, so plain `docker compose` in the worktree hits the right stack), infra-file **sync**, one-command **`promote`**, safe **cleanup**, and a built-in **MCP server** so AI agents can drive it all themselves.\n\n## Quick Start\n\n```bash\n# 1. Install once, globally\nnpm install -g worktree-compose\n\n# 2. Create a worktree for some parallel work\ngit worktree add -b feature ../myapp-feature\n\n# 3. Boot an isolated stack for it (\".\" = the worktree you're in)\ncd ../myapp-feature\nwtc start .\n\n# 4. See every stack, its status, URL, and ports\nwtc list\n\n# 5. Something off? Ask the doctor\nwtc doctor\n```\n\n`wtc` works from the main checkout or from any worktree: it always reads the compose file, config, and env files from the main checkout.\n\n## Usage\n\n```bash\nwtc start                     # start every worktree's stack\nwtc start .                   # just the worktree you're in\nwtc start 1 fix-billing       # by index, branch, directory name, or path\nwtc start --no-build          # skip the image rebuild\nwtc start --env .env.development --workspace server   # monorepo env files\n\nwtc list                      # status, URL, and ports (alias: wtc ls; --json for scripts)\nwtc stop 1                    # stop the containers; data is kept and start resumes them\nwtc restart .                 # re-sync files and env, recreate what changed\n\nwtc promote fix-billing       # copy a worktree's changes into the current checkout\n\nwtc prune                     # tear down stacks of worktrees you already deleted\nwtc clean --dry-run           # preview the full cleanup\nwtc clean                     # tear down every stack (deletes volumes), remove worktrees that hold no work\n\nwtc doctor                    # check git, engine, ports, isolation, orphans\nwtc skill --install           # install the agent skill\nwtc mcp                       # MCP server for AI agents (stdio)\n```\n\n## Selecting worktrees\n\n`start`, `stop`, and `restart` take any number of worktrees (none means all); `promote` takes exactly one. Each one can be given as:\n\n| Selector | Example | Matches |\n|----------|---------|---------|\n| index | `2` | the worktree's stable index (the `Index` column of `wtc list`) |\n| branch | `fix-billing` | the branch checked out in the worktree |\n| directory name | `myapp-fix-billing` | the basename of the worktree's directory |\n| path | `../myapp-fix-billing` | a path, resolved against the current directory (symlinks such as macOS `/tmp` are followed) |\n| `.` | `.` | the worktree that contains the current directory |\n\n- A name that matches several worktrees (say, two directories with the same basename in different places) is an error listing them; use the index instead.\n- A number is an index, unless it also names a *different* worktree (a branch or directory called `2`). Then `wtc` refuses and asks for the branch, the directory name, or a path — the message suggests one that works from where you are (e.g. `../2`).\n- Through MCP, a JSON number is always an index; a string follows the rules above.\n\n## Preparing your compose file\n\n`wtc` looks for `compose.yaml`, `compose.yml`, `docker-compose.yaml`, or `docker-compose.yml` (in that order) at the root of the main checkout.\n\nFor `wtc` to isolate a service's port, the host port must use the `${VAR:-default}` pattern, with an upper-case variable name:\n\n```yaml\n# wtc CAN isolate this\nports:\n  - \"${BACKEND_PORT:-8000}:8000\"\n\n# wtc CANNOT isolate this (hardcoded)\nports:\n  - \"8080:8080\"\n```\n\nIf `wtc` finds hardcoded ports, it warns you and suggests the fix:\n\n```\n⚠ Service \"nginx\" uses a raw port mapping (8080:80). To enable port isolation, change it to: \"${NGINX_PORT:-8080}:80\"\n```\n\n### Supported port formats\n\n```yaml\n# Standard\n- \"${BACKEND_PORT:-8000}:8000\"\n\n# Same var for host and container\n- \"${FRONTEND_PORT:-5173}:${FRONTEND_PORT:-5173}\"\n\n# IP-bound\n- \"127.0.0.1:${API_PORT:-3000}:3000\"\n\n# With protocol\n- \"${BACKEND_PORT:-8000}:8000/tcp\"\n\n# Multiple ports per service\n- \"${BACKEND_PORT:-8000}:8000\"\n- \"${DEBUG_PORT:-9229}:9229\"\n\n# Long-form syntax\n- target: 8000\n  published: \"${BACKEND_PORT:-8000}\"\n  protocol: tcp\n```\n\n### Settings that break isolation\n\n`wtc start` and `wtc doctor` warn about compose settings that make worktree stacks collide or share data (`wtc list` only prints how many there are):\n\n- **`container_name: db`** — every stack would use the same container name, so the second one fails to start. Remove it, or make it per-stack: `container_name: ${COMPOSE_PROJECT_NAME:-myapp}-db`.\n- **Top-level volumes or networks with a fixed `name:` or `external: true`** — every stack shares them. A `name:` that contains `${COMPOSE_PROJECT_NAME}` is per-stack and fine.\n- **Hardcoded host ports** — see above.\n- **Cross-worktree port collisions** — see [`portStride`](#clustered-default-ports-and-portstride).\n\n### Included compose files\n\nIf your compose file pulls in others via the top-level [`include:`](https://docs.docker.com/reference/compose-file/include/) directive, `wtc` follows them recursively — services defined in included files get the same port isolation as top-level ones, and the included files are copied into each worktree alongside the main one.\n\n```yaml\ninclude:\n  - ./db/compose.yml\n  - path: ./services/api/compose.yml\n```\n\nBuild contexts in an included file resolve relative to that file's own directory, just as Docker Compose does. (Included files that live outside the repo still have their ports isolated, but aren't copied into the worktree.)\n\n## Monorepos & custom env files\n\nIn a monorepo, local env files usually live next to each app (`apps/server/.env.development`) instead of at the repo root, and the filename isn't always `.env`. Use `envFile` plus workspace selectors to keep every worktree's env files consistent:\n\n```bash\n# Copy + inject apps/server/.env.development and apps/homepage/.env.development\n# into the worktree, in addition to the root env file\nwtc start --env .env.development --workspace server --workspace homepage\n```\n\nOr make it the project default in `.wtcrc.json`:\n\n```json\n{\n  \"$schema\": \"https://unpkg.com/worktree-compose/wtc.schema.json\",\n  \"envFile\": \".env.development\",\n  \"workspaces\": [\"server\", \"homepage\"],\n  \"workspaceEnvOverrides\": {\n    \"server\": {\n      \"DATABASE_URL\": \"postgresql://postgres:postgres@localhost:${POSTGRES_PORT}/postgres\"\n    }\n  }\n}\n```\n\nHow workspace selection works:\n\n- Workspaces come from `package.json#workspaces` (npm/bun/yarn) or `pnpm-workspace.yaml#packages`. Only directories with a `package.json` count. In a repo that declares no workspaces, `--workspace server` looks for a `package.json` in `server`, `apps/server`, `packages/server`, or `libs/server`.\n- `--workspace` matches by package **name**, directory name, or repo-relative path, and may be repeated. On the command line it replaces the configured `workspaces` for that run; `--env` likewise replaces `envFile`.\n- The **repo root is always processed** — docker compose interpolates `${POSTGRES_PORT:-5434}` from the root env file, so skipping it would break port isolation. Only the root env file gets `COMPOSE_PROJECT_NAME`.\n- Every target gets the allocated port variables. A selected workspace with no env file (and no `.env.example`) is skipped with a warning.\n- `workspaceEnvOverrides` keys match a workspace by package name, then by directory name (only when no other workspace shares it), then by repo-relative path.\n- `restart` checks `--env` and `--workspace` before stopping anything, so a typo never leaves stacks stopped.\n\n> **Why `--env`, not `--env-file`?** Node.js intercepts `--env-file` in its own argv parsing — even after the script path — so the flag never reaches `wtc`. `--env` is the safe spelling; the config key stays `envFile`. It must be a bare filename such as `.env.development`.\n\n## How It Works\n\n### Stable worktree identity\n\nThe first time `wtc` acts on a worktree (`start`, `stop`, …), it records the worktree's **index** (which decides its ports) and its **compose project name** in `.git/wtc/state.json`: inside the git directory every worktree shares, never committed. From then on they don't change. `wtc list`, `wtc doctor`, and dry runs only read: they show the identity a new worktree will get without recording it.\n\n- **Removing a worktree never renumbers the others.** (0.2 derived the index from the position in `git worktree list`, so removing worktree 1 silently moved worktree 2 onto new ports and a new project, orphaning its running stack — [#9](https://github.com/mostafasudo/worktree-compose/issues/9).)\n- **A new worktree gets the lowest index no record holds.**\n- **Renaming the branch keeps the project name**, and **`git worktree move` keeps the index, ports, and project**: `wtc` keeps an id in the worktree's git admin directory (`.git/worktrees/<name>/wtc-id`), which a move keeps and `git worktree remove` deletes. Restart a moved worktree so bind mounts follow the new path.\n- **A worktree you delete becomes an orphan.** After `git worktree remove` or `rm -rf`, `wtc list` shows it as `(removed)`. Its index stays reserved, because its stack may still be running on those ports, until `wtc prune` (or `wtc clean`) tears the stack down and forgets it.\n- **A worktree git still lists is never an orphan**, even when `wtc` can't start it: a [locked](https://git-scm.com/docs/git-worktree#_commands) worktree whose directory is missing (an unmounted drive) shows as `(locked, missing on disk)` and keeps its index, record, and stack until it is back; `prune` and `clean` leave it alone.\n\n```\ngit worktree remove ../myapp-spike-cache\ngit worktree add -b docs ../myapp-docs\nwtc list\n\n┌───────┬───────────────────────┬────────┬────────────────────────┬────────────────────────────────────────────────────────────┐\n│ Index │ Branch                │ Status │ URL                    │ Ports                                                      │\n├───────┼───────────────────────┼────────┼────────────────────────┼────────────────────────────────────────────────────────────┤\n│ -     │ main                  │ -      │ -                      │ postgres:5434 backend:8000 frontend:5173                   │\n├───────┼───────────────────────┼────────┼────────────────────────┼────────────────────────────────────────────────────────────┤\n│ 1     │ feature-auth          │ up     │ http://localhost:25174 │ POSTGRES_PORT=25435 BACKEND_PORT=28001 FRONTEND_PORT=25174 │\n├───────┼───────────────────────┼────────┼────────────────────────┼────────────────────────────────────────────────────────────┤\n│ 2     │ fix-billing           │ exited │ http://localhost:25175 │ POSTGRES_PORT=25436 BACKEND_PORT=28002 FRONTEND_PORT=25175 │\n├───────┼───────────────────────┼────────┼────────────────────────┼────────────────────────────────────────────────────────────┤\n│ 3     │ spike-cache (removed) │ up     │ -                      │ POSTGRES_PORT=25437 BACKEND_PORT=28003 FRONTEND_PORT=25176 │\n├───────┼───────────────────────┼────────┼────────────────────────┼────────────────────────────────────────────────────────────┤\n│ 4     │ docs                  │ down   │ http://localhost:25177 │ POSTGRES_PORT=25438 BACKEND_PORT=28004 FRONTEND_PORT=25177 │\n└───────┴───────────────────────┴────────┴────────────────────────┴────────────────────────────────────────────────────────────┘\n(removed) = the worktree is gone but its stack/record remains. Run 'wtc prune' to tear it down.\n```\n\n**Coming from 0.2?** On the first run, `wtc` gives existing worktrees the same positional indices 0.2 used, and a worktree whose 0.2 stack is still running adopts that stack's index and project (matched by the directory compose ran in). Running stacks are picked up, not duplicated. If `state.json` is ever lost or unreadable, it is rebuilt the same way (an unreadable file is first moved aside as `state.json.bak-<time>`).\n\n### Port Allocation\n\nEach worktree gets unique ports from its index: `20000 + default_port + (worktree_index × portStride)`\n\n`portStride` defaults to `1`, so by default the table below holds:\n\n| Service  | Main (default) | Worktree 1 | Worktree 2 | Worktree 3 |\n|----------|---------------|------------|------------|------------|\n| postgres | 5434          | 25435      | 25436      | 25437      |\n| backend  | 8000          | 28001      | 28002      | 28003      |\n| frontend | 5173          | 25174      | 25175      | 25176      |\n\nThe table is illustrative — every value follows the formula above. (If an allocation would exceed `65535`, `wtc` falls back to `default_port + 100 × index`.)\n\n#### Clustered default ports and `portStride`\n\nWith the default stride of `1`, two worktrees collide on a port whenever a pair of services has default host ports whose difference equals the worktrees' index difference. Projects with **clustered** default ports (e.g. mocks on `8081`–`8086`) therefore collide even between adjacent worktrees, and the second `docker compose up` fails to bind.\n\nWhen `wtc` detects such a collision across the indices in use (live worktrees and orphans) it warns, naming the colliding ports and the recommended stride. Set `portStride` in your config (`.wtcrc.json` or `package.json#wtc`) to spread each worktree's ports far enough apart:\n\n```json\n{ \"portStride\": 100 }\n```\n\nA stride of `100` gives every worktree a 100-port band, which is collision-free unless some pair of default ports differs by an exact multiple of the stride. The recommended value in the warning is the smallest stride that is collision-free over a generous worktree horizon. The canonical `docker compose up` in the main checkout is unaffected — only the worktrees' ports change.\n\n### Container isolation and `COMPOSE_PROJECT_NAME`\n\nEach worktree's stack runs as the compose project `{repo}-wt-{index}-{branch}`, lowercased with anything other than `a-z`, `0-9`, and `-` turned into `-` — for example `myapp-wt-1-feature-auth`. Compose names containers, networks, and volumes after the project, so nothing is shared. `{repo}` is the main checkout's directory name (for a bare repository, the repository's own name), so it is the same from every worktree.\n\n`wtc` passes `-p <project>` on every compose call, exports `COMPOSE_PROJECT_NAME` and the allocated ports into compose's environment, and writes `COMPOSE_PROJECT_NAME=<project>` into the worktree's root env file. With the default `.env`, a plain `docker compose ps` / `logs` / `exec` (or a Makefile) run inside the worktree therefore reaches the same stack on the same ports.\n\nCompose only reads a file named `.env` on its own. With a custom `envFile` such as `.env.development`, pass it explicitly (`docker compose --env-file .env.development ps`) or pass `-p <project>` (the `project` field of `wtc list --json`).\n\n`${COMPOSE_PROJECT_NAME}` is also available in `envOverrides` and `workspaceEnvOverrides` values, e.g. `\"QUEUE_PREFIX\": \"${COMPOSE_PROJECT_NAME}-jobs\"`.\n\n### File Sync\n\nBefore every start, `wtc` copies infrastructure files from the main checkout into the worktree, so every worktree runs the latest Docker setup:\n\n- the compose file, plus every file it pulls in via `include:` (inside the repo);\n- every Dockerfile a service's `build` points to (remote build contexts and `dockerfile_inline` have none);\n- the extra paths listed in [`sync`](#sync). Entries must stay inside the repository; absolute or `../` entries are skipped with a warning.\n\nSync copies over what is at those paths and removes nothing else: files that only the worktree has stay. Symlinks are recreated as symlinks, never followed. `wtc` never writes through a symlink it finds at a destination (it replaces the link) and skips any destination whose directory resolves outside the worktree. Where main has a symlink but the worktree has a real directory, the directory is left alone with a warning.\n\n### Env Files\n\nOn every `start` and `restart`, for the root and each selected workspace:\n\n1. **Copy fresh from main:** `<envFile>` (default `.env`), falling back to `.env.example`. The root gets an empty file if neither exists; a workspace with neither is skipped.\n2. **Inject:** `COMPOSE_PROJECT_NAME` (root only), every allocated port variable, then `envOverrides` (root) or `workspaceEnvOverrides` (that workspace). **A key that already exists is rewritten in place** — comments, ordering, and all other lines are preserved. Keys that don't exist are appended in a marked block, which is replaced on every run and never accumulates.\n\nWith `\"envOverrides\": { \"DATABASE_URL\": \"postgresql://app:app@localhost:${POSTGRES_PORT}/app\" }` and a main `.env` of\n\n```bash\nPOSTGRES_PORT=5434\nDATABASE_URL=postgresql://app:app@localhost:5434/app\n```\n\nworktree 1 gets:\n\n```bash\nPOSTGRES_PORT=25435\nDATABASE_URL=postgresql://app:app@localhost:25435/app\n\n# --- wtc port overrides ---\nCOMPOSE_PROJECT_NAME=myapp-wt-1-feature-auth\nBACKEND_PORT=28001\nFRONTEND_PORT=25174\n# --- end wtc ---\n```\n\nTwo consequences of copying fresh every time:\n\n- **Edits made only in a worktree's env file are lost on the next start.** Put lasting values in main's env file, or in `envOverrides` / `workspaceEnvOverrides`.\n- **Only the keys above are written.** A value with a port inside it, like `DATABASE_URL` above, keeps main's port unless you add an override for it.\n\nIf a worktree's env file is a symlink (for example to main's `.env`), `wtc` replaces the link with a regular copy and never writes through it.\n\nThe allocated ports are also exported into the environment of the compose process `wtc` launches. Compose resolves `${VAR:-default}` from the process environment, `--env-file`, and the project `.env` — never from a service's `env_file:`, and never from a custom filename like `.env.development`. Exporting the ports keeps published host ports correct when the project uses a custom `envFile`.\n\n### Repository layouts\n\nBesides a plain clone, `wtc` handles bare repositories with worktrees (the worktree on the repository's default branch acts as the main checkout and is never started, synced into, or removed), submodules, and `--separate-git-dir` checkouts. Locked worktrees work normally; a locked worktree whose directory is missing (an unmounted drive) is skipped with a warning, and its stack and index are kept.\n\nTwo layouts don't tell from every worktree which checkout is the main one: a bare repository whose default-branch worktree is mid-rebase, mid-bisect, or on another branch for now, and a `--separate-git-dir` repository seen from a linked worktree. `wtc` remembers the main checkout (in `.git/wtc/state.json`) once it has run there or found it, and uses that. When it can't tell at all, it warns, uses the current checkout's compose and config files for `list`, `doctor`, `stop`, `promote`, and `prune`, records nothing, and refuses `start`, `restart`, and `clean` — they would sync files into, start, or remove checkouts based on the wrong one. The error says how to fix it: check the default branch out in your main worktree, or run `wtc start` from the main checkout.\n\n## Commands\n\n### `wtc start`\n\n```\nwtc start [worktrees...] [--env <name>] [--workspace <name>]... [--no-build]\n```\n\nFor each selected worktree (all when none are given): sync infrastructure files, copy and inject env files, then run `compose -p <project> up -d --build`. Stops at the first failure; on success it prints `wtc list`. A worktree whose directory is missing is refused (see `wtc prune`). The main checkout's own stack is not managed by `wtc`: run `docker compose` there as usual.\n\n| Flag | |\n|------|-|\n| `--env <name>` | Env filename to copy and inject for this run (default: `envFile`, else `.env`) |\n| `--workspace <name>` | Workspace to also inject env into; repeatable (see [Monorepos](#monorepos--custom-env-files)) |\n| `--no-build` | Run `compose up -d` without `--build` |\n\n### `wtc stop`\n\n```\nwtc stop [worktrees...]\n```\n\nRuns `compose stop`: containers are stopped, **not removed**, and all volumes are kept — including the anonymous ones images like postgres and redis create — so `wtc start` resumes the same containers with their data. `wtc list` shows a stopped stack as `exited`. One failure doesn't stop the rest; the exit code is 1 if any stack failed to stop.\n\n### `wtc restart`\n\n```\nwtc restart [worktrees...] [--env <name>] [--workspace <name>]... [--no-build]\n```\n\n`compose stop`, then everything `start` does, with `up -d --build --remove-orphans`: unchanged services start again, changed ones are recreated (keeping their volumes), and containers of services you removed from the compose file go away. Use it after migrations, Dockerfile changes, or config updates. Env options and selectors are validated before anything is stopped.\n\n### `wtc list`\n\n```\nwtc list [--json]        # alias: wtc ls\n```\n\nEvery worktree with its index, branch, status, URL, and ports, plus a row for main's default ports. Status is `up`, `partial` (with running/total, e.g. `partial 2/3`), `exited` (containers stopped), `down` (no containers), or `?` (no reachable engine). One-off `docker compose run` containers are not counted. Removed worktrees whose record remains are shown as `(removed)`; worktrees git still lists but `wtc` can't start as `(locked, missing on disk)` or `(now the main checkout)`. Read-only: it never writes the registry.\n\n`--json` prints an array of `{ index, branch, path, project, status, running, total, url, ports, orphan, unavailable }` to stdout (`unavailable` is `\"missing\"`, `\"main\"`, or `null`); warnings go to stderr. The URL is the first service whose name contains `frontend`, `web`, `app`, or `ui`, else the last isolated port.\n\n### `wtc promote`\n\n```\nwtc promote <worktree> [--force]\n```\n\nCopies a worktree's changes into the checkout you run it from (usually main) as uncommitted changes: everything it changed since it branched — committed, staged, unstaged, and untracked — with deleted files deleted and renames applied as delete + add. Compose files and local env files (`.env`, `.env.development`, … — committed templates like `.env.example` still promote) are skipped, because `wtc` rewrites them per worktree. Symlinks are copied as symlinks; directories such as submodules are skipped with a warning. Works without Docker.\n\nIt refuses, before writing anything:\n\n- **conflicts** — a file it would touch has uncommitted or untracked changes in the target (unless the target already has identical content). Commit or stash first.\n- **reverts** — a file it would touch was also changed (committed) on the target branch since the worktree branched. Promote copies whole files, so this would silently undo those commits. Merge the target branch into the worktree first (the error prints the command), or pass `--force` to overwrite them anyway.\n- **unsafe paths** — a path that escapes the repository or passes through a symlinked directory on either side.\n- promoting a worktree into itself.\n\n### `wtc clean`\n\n```\nwtc clean [--dry-run] [--force] [--keep-worktrees] [--sweep]\n```\n\nEnds the session for every worktree:\n\n1. **Tears down every stack `wtc` knows** — all live worktrees and all orphans — with `compose down -v --remove-orphans`. **This deletes their volumes**, including the stacks of worktrees that step 2 keeps and with `--keep-worktrees`. To stop everything but keep the data, use `wtc stop`.\n2. **Removes worktrees that hold no work of their own** (skipped entirely with `--keep-worktrees`). Never removed: the main checkout and the worktree you run `clean` from. Kept unless you pass `--force`:\n   - uncommitted changes, tracked or untracked — other than the files `wtc` itself wrote there (the env files it injects, and compose/Dockerfile/`sync` copies that still match main);\n   - a detached HEAD with commits no branch, tag, or remote contains;\n   - submodules with commits not pushed to any remote;\n   - locked worktrees.\n\n   Each kept worktree is listed with the reason.\n3. **Sweeps leftover containers, networks, and volumes, one at a time — only of stacks this checkout owns:** projects in its registry, or `<repo>-wt-<n>-…` projects whose containers ran from the main checkout, one of its worktrees, or a recorded worktree path. Stacks that are merely *named* like this repo's (another clone with the same directory name) are listed and never removed. Ownership is judged from containers: volumes and networks of a project with **no container left** carry no working directory, so they are listed, never removed — see [leftovers](#troubleshooting).\n4. Runs `git worktree prune`, and forgets the records of removed worktrees and torn-down orphans. Worktrees that stay keep their index.\n\n| Flag | |\n|------|-|\n| `--dry-run` | Print exactly what would be stopped, removed, and swept; change nothing (not even `wtc`'s registry) |\n| `--force` | Also remove worktrees with uncommitted changes, unreachable commits, unpushed submodule commits, and locked ones |\n| `--keep-worktrees` | Tear down the stacks (deleting their volumes), keep every worktree |\n| `--sweep` | Also remove `<repo>-wt-<n>-…` stacks whose working directory no longer exists — e.g. left behind by wtc 0.2 — judged from their containers: volumes or networks with no container left are listed, never removed. Never a stack whose directory exists |\n\n### `wtc prune`\n\n```\nwtc prune [--dry-run]\n```\n\nTears down (`compose down -v --remove-orphans`, deleting volumes) and forgets **orphans only**: stacks of worktrees you deleted with `git worktree remove` or `rm -rf`. Live worktrees are not touched — nor is any worktree git still lists, such as a locked one on an unmounted drive; each orphan is re-checked right before teardown. Also runs `git worktree prune` when git still lists worktrees whose directory is gone.\n\n### `wtc doctor`\n\n```\nwtc doctor [--json]\n```\n\nA ✔ / ⚠ / ✖ checklist with hints: git version; repository and main-checkout detection; compose file found and parsed (services, included files); config parsed (`.wtcrc.json` or `package.json#wtc`); compose engine and version (when it doesn't work, whether `WTC_COMPOSE_COMMAND`, `composeCommand`, or auto-detection chose it); daemon reachable; isolatable vs. hardcoded ports; [isolation warnings](#settings-that-break-isolation); cross-worktree port collisions; missing worktrees; orphaned stacks; each worktree's stack state; and allocated ports already taken by something else for worktrees whose stack isn't running. Read-only. Exits 1 if any check fails.\n\n### `wtc skill`\n\n```\nwtc skill [--install] [--dir <path>]\n```\n\nPrints the bundled [agent skill](#agent-skill). `--install` writes it to `~/.claude/skills/worktree-compose/SKILL.md`; `--dir <path>` installs it to `<path>/worktree-compose/SKILL.md` instead.\n\n### `wtc mcp`\n\nStarts the [MCP server](#mcp-server) on stdio.\n\n### What each command does to your data\n\n| Command | Containers | Volumes (your data) | Worktrees |\n|---------|-----------|---------------------|-----------|\n| `wtc stop` | stopped, kept | kept | kept |\n| `wtc restart` | stopped, then started; changed services recreated | kept | kept |\n| `wtc prune` | removed (deleted worktrees' stacks only) | **deleted** (those stacks only) | — |\n| `wtc clean` | removed (every stack) | **deleted** (every stack) | removed unless they hold work; never main or the current one |\n| `wtc clean --keep-worktrees` | removed (every stack) | **deleted** (every stack) | kept |\n\nExit codes: any error exits 1. `stop`, `clean`, and `prune` also exit 1 when a stack, removal, or sweep failed (worktrees kept on purpose don't count); `doctor` exits 1 when a check fails.\n\n## Configuration (Optional)\n\n`wtc` works zero-config. For project-specific needs, create `.wtcrc.json` at the root of the main checkout:\n\n```json\n{\n  \"$schema\": \"https://unpkg.com/worktree-compose/wtc.schema.json\",\n  \"sync\": [\".generated/prisma-client\", \"local-certs/\"],\n  \"envFile\": \".env.development\",\n  \"workspaces\": [\"server\", \"homepage\"],\n  \"envOverrides\": {\n    \"VITE_API_URL\": \"http://localhost:${BACKEND_PORT}\"\n  },\n  \"workspaceEnvOverrides\": {\n    \"server\": {\n      \"DATABASE_URL\": \"postgresql://postgres:postgres@localhost:${POSTGRES_PORT}/postgres\"\n    }\n  },\n  \"portStride\": 100,\n  \"composeCommand\": \"podman compose\"\n}\n```\n\nOr use a `\"wtc\"` key in `package.json`. (`.wtcrc.json` takes precedence.) The `$schema` line gives editors completion and validation for every key; `wtc` ignores it.\n\n### `sync`\n\nExtra files/directories to copy from main into each worktree on start. Use for gitignored or generated files that Docker needs but aren't committed — like generated clients, local certificates, or build artifacts. Paths are repo-relative; entries that are absolute or leave the repository are skipped with a warning.\n\n### `envFile`\n\nEnv filename to copy from main and inject overrides into, for the root and every selected workspace. Defaults to `.env`. Must be a bare filename. Per-run override: `--env`.\n\n### `workspaces`\n\nMonorepo workspaces that also get their env file copied and injected, in addition to the root. Accepts package names, directory names, or repo-relative paths. Omitted → root only. Per-run override: `--workspace`. See [Monorepos & custom env files](#monorepos--custom-env-files).\n\n### `envOverrides`\n\nAdditional env vars injected into the **root** env file. Values interpolate `${VAR}` with the allocated port variables and `${COMPOSE_PROJECT_NAME}`. Existing keys are rewritten in place; new keys are appended in the override block.\n\n### `workspaceEnvOverrides`\n\nPer-workspace overrides, keyed by workspace package name (a unique directory name or the repo-relative path also work). Applied only to that workspace's env file, with the same interpolation and in-place rewrite behavior as `envOverrides`.\n\n### `portStride`\n\nPort spacing between consecutive worktree indices (default `1`, must be a positive integer). Allocated port = `20000 + default_port + worktree_index × portStride`. Raise it (e.g. `100`) when your project exposes clustered default ports that would otherwise collide across worktrees — see [Port Allocation](#port-allocation). `wtc` warns and recommends a value when it detects a collision.\n\n### `composeCommand`\n\nWhich compose tool to use instead of auto-detection: exactly one of `\"docker compose\"`, `\"podman compose\"`, `\"docker-compose\"`, or `\"podman-compose\"` (a string, or an array such as `[\"podman\", \"compose\"]`). Repository config is limited to these four so a cloned repository can't make `wtc` run an arbitrary program; anything custom goes in the `WTC_COMPOSE_COMMAND` environment variable, which takes precedence. See [Docker, Podman, OrbStack, Colima](#docker-podman-orbstack-colima).\n\n## Docker, Podman, OrbStack, Colima\n\n`wtc` picks the compose command in this order:\n\n1. **`WTC_COMPOSE_COMMAND`** environment variable — any command, split on whitespace: `WTC_COMPOSE_COMMAND=\"podman compose\"`, an absolute path, or global flags such as `WTC_COMPOSE_COMMAND=\"docker --context colima compose\"`.\n2. **`composeCommand`** in `.wtcrc.json` / `package.json#wtc` — one of the four standard commands below.\n3. **Auto-detection**, in order: `docker compose` (Compose v2) → `podman compose` → `docker-compose` (standalone, including legacy v1) → `podman-compose`. When more than one is installed, the first whose daemon answers wins, so a quit Docker Desktop doesn't shadow a running Podman machine.\n\n| Engine | How |\n|--------|-----|\n| Docker Desktop / Docker Engine | `docker compose` (auto-detected) |\n| OrbStack, Colima | through the `docker` CLI and its current context (auto-detected); for another context, `WTC_COMPOSE_COMMAND=\"docker --context <name> compose\"` |\n| Podman | `podman compose` or `podman-compose` (auto-detected); on macOS and Windows, start its VM with `podman machine start` |\n| Anything else (e.g. `nerdctl compose`) | `WTC_COMPOSE_COMMAND` |\n\nStatus checks, teardown, and cleanup use the same CLI and global flags as compose, so they always talk to the same daemon. `wtc doctor` shows which engine was picked and its version, and whether its daemon is reachable. `wtc promote` doesn't need an engine at all.\n\n## MCP Server\n\nBuilt-in [MCP](https://modelcontextprotocol.io/) server, so AI agents can manage their stacks themselves. It works on the git repository of the directory it starts in — your agent's project. The setups below use the global install.\n\n### Setup\n\n**Claude Code** — for every project:\n\n```bash\nclaude mcp add --scope user wtc -- wtc mcp\n```\n\n(Without `--scope user`, Claude Code registers it for the current project only.) Or share it with your team through a project `.mcp.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"wtc\": { \"command\": \"wtc\", \"args\": [\"mcp\"] }\n  }\n}\n```\n\n**Codex** — `~/.codex/config.toml`:\n\n```toml\n[mcp_servers.wtc]\ncommand = \"wtc\"\nargs = [\"mcp\"]\n```\n\n**Cursor** — `.cursor/mcp.json`, with the same JSON as `.mcp.json` above.\n\n**Without a global install**, use `npx` with the package name (never `npx wtc`):\n\n```json\n{ \"command\": \"npx\", \"args\": [\"-y\", \"worktree-compose\", \"mcp\"] }\n```\n\nAfter upgrading `wtc`, restart your MCP client so it runs the new version.\n\n### Tools\n\n| Tool | Parameters | What it does | Safety |\n|------|-----------|--------------|--------|\n| `wtc_start` | `indices?: (number \\| string)[]`, `envFile?: string`, `workspaces?: string[]`, `build?: boolean` (default `true`) | Same as `wtc start` | not destructive |\n| `wtc_stop` | `indices?: (number \\| string)[]` | Same as `wtc stop` (`compose stop`) | not destructive: containers and volumes are kept |\n| `wtc_restart` | same as `wtc_start` | Same as `wtc restart` | not destructive |\n| `wtc_list` | — | First content item is the JSON array of `wtc list --json`; warnings follow as separate items | read-only: never writes the registry |\n| `wtc_promote` | `index: number \\| string`, `force?: boolean` | Same as `wtc promote`, into the checkout the server runs in | **destructive**: overwrites and deletes files there |\n| `wtc_clean` | `force?`, `keepWorktrees?`, `dryRun?`, `sweep?` (all `boolean`) | Same as `wtc clean` | **destructive**: deletes volumes, removes worktrees |\n| `wtc_prune` | `dryRun?: boolean` | Same as `wtc prune` | **destructive**: deletes removed worktrees' volumes |\n| `wtc_doctor` | — | Same checks as `wtc doctor`, as JSON | read-only |\n\n`indices` / `index` take [selectors](#selecting-worktrees): a JSON number is always an index; a string can be an index, branch, directory name, path, or `.`. Omit `indices` for all worktrees.\n\n- Tools that change stacks or files (start, stop, restart, promote, clean, prune) run one at a time, so a client retry can't launch a second `compose up` for the same stack.\n- Cancelling a request stops the compose command it started; a request cancelled while it waits its turn does not run at all. Requests with a `progressToken` get progress notifications while compose runs.\n- Every tool returns its progress lines. Failures come back as `isError: true` with the reason (including the tail of compose's error output) — also when a teardown or removal inside `wtc_stop`, `wtc_clean`, or `wtc_prune` failed.\n- Tool annotations mark `wtc_list` and `wtc_doctor` read-only and `wtc_promote`, `wtc_clean`, and `wtc_prune` destructive, so clients can ask before running them.\n- stdout carries only JSON-RPC; all logging goes to stderr.\n\n## Agent Skill\n\n`wtc` ships an [agent skill](https://docs.claude.com/en/docs/claude-code/skills) ([`SKILL.md`](https://github.com/mostafasudo/worktree-compose/blob/main/SKILL.md)) that teaches AI coding agents the worktree workflow: commands and selectors, stop vs. clean, ports, env files, config, and the MCP tools. Install the copy that matches your installed version:\n\n```bash\nwtc skill --install              # ~/.claude/skills/worktree-compose/SKILL.md\nwtc skill --dir ~/my-agent/skills # <dir>/worktree-compose/SKILL.md\nwtc skill                        # print it\n```\n\nRe-run `wtc skill --install` after upgrading `wtc`.\n\n## Full Example\n\n```bash\ncd myapp\nnpm install -g worktree-compose\n\ngit worktree add -b agent-1-auth ../myapp-agent-1\ngit worktree add -b agent-2-auth ../myapp-agent-2\n\nwtc start\n# Worktree 1: BACKEND_PORT=28001 FRONTEND_PORT=25174\n# Worktree 2: BACKEND_PORT=28002 FRONTEND_PORT=25175\n\n# Compare side by side\n# http://localhost:25174  (agent 1)\n# http://localhost:25175  (agent 2)\n\nwtc promote agent-1-auth\ngit add -A && git commit -m \"feat: auth from agent 1\"\n\nwtc clean --dry-run   # preview\nwtc clean             # worktrees with uncommitted work are kept; --force discards them\n```\n\n## Upgrading from 0.2\n\n1.0 changes a few behaviors; the [changelog](https://github.com/mostafasudo/worktree-compose/blob/main/CHANGELOG.md#upgrading-from-02) has the full list.\n\n- **Node.js 20+.**\n- **Install globally and type `wtc`** — without an install, `npx wtc` runs an unrelated package. Update MCP configs to `\"command\": \"wtc\", \"args\": [\"mcp\"]`.\n- **Indices are stable.** Your existing stacks keep their names on the first run; after that, removing a worktree never shifts the others. Deleted worktrees show as `(removed)` until `wtc prune`.\n- **`wtc stop` keeps containers** (`compose stop` instead of `down`); `start` resumes them. Only `clean` and `prune` remove stacks and volumes.\n- **`wtc clean` keeps worktrees that hold work** (uncommitted changes, unreachable commits, unpushed submodule commits, locked) unless you pass `--force`, and only removes Docker resources of stacks it owns. Stale 0.2 stacks that still have containers: `wtc clean --sweep`. 0.2's `wtc stop` kept named volumes, so a worktree deleted after `wtc stop` can leave volumes with no container; `wtc clean` lists those but never removes them. Find them with `docker volume ls --filter label=com.docker.compose.project=<project>` and remove them with `docker volume rm`.\n- **Env keys are rewritten in place**, and the root env file now gets `COMPOSE_PROJECT_NAME`.\n- **`wtc promote`** also aborts when the target branch changed the same files since the worktree branched (`--force` overrides), and untracked files in the target now count as conflicts.\n\n## Requirements\n\n- **Node.js** >= 20\n- **Git** 2.17 or newer, with worktree support (2.31+ recommended)\n- **Docker** with Compose v2 (`docker compose`), or **Podman** (`podman compose` / `podman-compose`) — see [engines](#docker-podman-orbstack-colima)\n- A compose file with `${VAR:-default}` host ports\n\n## Troubleshooting\n\nStart with **`wtc doctor`**: it checks the engine, the daemon, ports, isolation settings, missing worktrees, and orphaned stacks, and says what to do.\n\n**`npx wtc` does something unexpected** — when worktree-compose isn't installed, it downloads and runs an unrelated npm package named `wtc`. Install globally (`npm install -g worktree-compose`) and type `wtc`, or use `npx worktree-compose <command>`.\n\n**\"No compose file found\"** — `wtc` reads the compose file from the root of the main checkout, not the current directory: `compose.yaml`, `compose.yml`, `docker-compose.yaml`, or `docker-compose.yml`.\n\n**\"No extra worktrees found\"** — Create worktrees first: `git worktree add -b my-branch ../my-feature`\n\n**Ports not changing** — Use `${VAR:-default}` with an upper-case variable for host ports, not hardcoded numbers. `wtc doctor` lists which ports are isolatable.\n\n**\"Bind for 0.0.0.0:25435 failed: port is already allocated\"** — Something else listens on that port. `wtc doctor` reports allocated ports that are already taken; stop the other process, or raise `portStride`. If you run `docker compose` yourself with a custom `envFile`, pass `--env-file <envFile>` (Compose only reads `.env` on its own) or export the port (e.g. `POSTGRES_PORT=25435 docker compose up`).\n\n**A stack shows as `(removed)`** — its worktree was deleted but its stack or record remains. `wtc prune` tears it down (and frees its index); preview with `wtc prune --dry-run`.\n\n**Leftover containers, networks, or volumes** — `wtc clean --dry-run` shows what `wtc clean` would remove. Leftovers of stacks whose worktree directory no longer exists (e.g. from wtc 0.2) are listed, and removed with `wtc clean --sweep` as long as the stack still has a container (that is where the working directory is recorded). Volumes or networks with no container left, and resources merely *named* like this repo's stacks, are listed and never removed by `wtc`. Inspect one project exactly before removing anything yourself: `docker ps -a --filter label=com.docker.compose.project=<project>` shows nothing for volume-only leftovers, so also run `docker volume ls --filter label=com.docker.compose.project=<project>` and `docker network ls --filter label=com.docker.compose.project=<project>`.\n\n**\"Refusing to start worktree stacks: cannot locate the main checkout\"** (also `restart`, `clean`) — the repository layout doesn't say which checkout is the main one (see [repository layouts](#repository-layouts)). In a bare repository, check the default branch out in your main worktree again (finish the rebase or bisect), or add a worktree for it; with `--separate-git-dir`, run it from the main checkout (after one `wtc start` there, linked worktrees find it too).\n\n**`wtc clean` kept a worktree** — it prints why (uncommitted changes, unreachable commits, unpushed submodule commits, locked, or the worktree you're in). Commit or push the work, or pass `--force` to discard it.\n\n**\"Timed out waiting for the wtc state lock\"** — another `wtc` command (or MCP call) is updating `.git/wtc/state.json`; retry when it finishes. If no `wtc` process is running at all, delete the lock file the message names.\n\n**\"…was written by a newer wtc (state version N); upgrade wtc.\"** — an older `wtc` (often an MCP server started by a cached install) met a registry written by a newer one. Upgrade it (`npm install -g worktree-compose@latest`) and restart your MCP client. The older version leaves the file untouched.\n\n**Wrong engine, or the daemon is down** — `wtc doctor` shows which engine was picked and whether its daemon answers. Start the daemon, or set `WTC_COMPOSE_COMMAND` (e.g. `\"podman compose\"`).\n\n## Contributing\n\nIssues and pull requests are welcome. Found a bug or have an idea? [Open an issue](https://github.com/mostafasudo/worktree-compose/issues) — and if `wtc` saves you time, a ⭐ on the [repo](https://github.com/mostafasudo/worktree-compose) helps others find it. Docs and demos live at [worktree-compose.com](https://www.worktree-compose.com).\n\n## License\n\n[MIT](https://github.com/mostafasudo/worktree-compose/blob/main/LICENSE)\n","readmeFilename":"README.md"}