@smicolon/ai-kit 0.2.1 → 0.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smicolon/ai-kit",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "AI coding tool pack manager for Smicolon standards",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "worktree",
3
- "version": "0.1.0",
4
- "description": "Git worktree manager for parallel development with automatic env copying and dependency installation",
3
+ "version": "0.2.0",
4
+ "description": "Git worktree manager for parallel development with env isolation, Docker port offsets, and database auto-creation",
5
5
  "author": {
6
6
  "name": "Smicolon",
7
7
  "email": "dev@smicolon.com",
@@ -1,12 +1,33 @@
1
1
  # Changelog
2
2
 
3
- All notable changes to smi-worktree will be documented in this file.
3
+ All notable changes to worktree will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.2.0] - 2026-02-15
8
+
9
+ ### Added
10
+
11
+ - `.worktreeinclude` config file — gitignore-style patterns for files to copy
12
+ - Auto-generates `.worktreeinclude` with sensible defaults on first `wt create`
13
+ - `[rewrite]` section — auto-suffixes DB_NAME, DATABASE_URL, COMPOSE_PROJECT_NAME per branch
14
+ - `{{BRANCH}}` template support for custom env var rewriting
15
+ - `[docker]` section — generates `docker-compose.worktree.yml` with port offsets
16
+ - Deterministic port offset via branch name hash (1-100 range)
17
+ - Custom compose file path via `file=` directive (supports monorepo nested paths)
18
+ - Auto-creates Postgres databases (Docker containers or local)
19
+ - `docker compose down` on `wt remove` before removing worktree
20
+ - Port mapping summary in create output
21
+
7
22
  ### Changed
8
- - Renamed from `smi-worktree` to `worktree` as part of ai-kit migration
9
- - Moved from `plugins/smi-worktree/` to `packs/worktree/`
23
+
24
+ - `wt create` now uses `.worktreeinclude` patterns instead of hardcoded `.env*` copying
25
+ - `wt remove` stops Docker containers and notes that databases are preserved
26
+ - `wt help` documents `.worktreeinclude` sections
27
+
28
+ ### Removed
29
+
30
+ - Hardcoded `copy_env_files()` function (replaced by `.worktreeinclude` pattern matching)
10
31
 
11
32
  ## [0.1.0] - 2026-01-17
12
33
 
@@ -1,11 +1,14 @@
1
1
  # worktree
2
2
 
3
- Git worktree manager for parallel development with automatic environment setup.
3
+ Git worktree manager for parallel development with automatic environment isolation.
4
4
 
5
5
  ## Features
6
6
 
7
7
  - **Sibling naming**: `project--branch-name/` convention
8
- - **Auto env copy**: Copies `.env*` from root and nested directories
8
+ - **`.worktreeinclude`**: Configurable file copying (replaces hardcoded `.env*`)
9
+ - **Env rewriting**: Auto-suffixes DB names, URLs, and compose project names per branch
10
+ - **Docker isolation**: Port-offset overrides so worktrees don't conflict
11
+ - **Database auto-creation**: Creates Postgres databases for rewritten DB names
9
12
  - **Package manager detection**: bun → pnpm → yarn → npm
10
13
  - **Monorepo aware**: Detects workspaces, turbo, nx, lerna
11
14
  - **Editor integration**: Open in Cursor, Antigravity, or VS Code
@@ -16,6 +19,98 @@ Git worktree manager for parallel development with automatic environment setup.
16
19
  /plugin install worktree
17
20
  ```
18
21
 
22
+ ## Quick Start
23
+
24
+ ```bash
25
+ /wt create feature/auth
26
+ # → Creates worktree at ~/code/project--feature-auth/
27
+ # → Copies files per .worktreeinclude
28
+ # → Rewrites DB_NAME, DATABASE_URL with branch suffix
29
+ # → Generates docker-compose.worktree.yml with offset ports
30
+ # → Creates database in running Postgres
31
+ # → Installs dependencies
32
+ ```
33
+
34
+ ## `.worktreeinclude`
35
+
36
+ Auto-generated on first `wt create` if missing. Commit this file so your team shares the same config.
37
+
38
+ ```ini
39
+ # .worktreeinclude — Files to copy to new worktrees
40
+ .env*
41
+
42
+ # Monorepo (uncomment as needed)
43
+ # apps/*/.env*
44
+ # packages/*/.env*
45
+ # services/*/.env*
46
+
47
+ [rewrite]
48
+ auto
49
+
50
+ [docker]
51
+ auto
52
+ # file=apps/backend/docker-compose.local.yml
53
+ # port_offset=10
54
+ ```
55
+
56
+ ### File Patterns (top section)
57
+
58
+ Gitignore-style glob patterns for untracked files to copy from main worktree:
59
+
60
+ ```
61
+ .env*
62
+ apps/*/.env*
63
+ config/local.yml
64
+ ```
65
+
66
+ ### `[rewrite]` — Env Var Isolation
67
+
68
+ **`auto` mode** detects and suffixes known keys with a branch slug:
69
+
70
+ | Key | Example | Result |
71
+ |-----|---------|--------|
72
+ | `DB_NAME` | `myapp` | `myapp_feature_auth` |
73
+ | `POSTGRES_DB` | `myapp` | `myapp_feature_auth` |
74
+ | `DATABASE_URL` | `postgres://...host/myapp` | `postgres://...host/myapp_feature_auth` |
75
+ | `COMPOSE_PROJECT_NAME` | `myapp` | `myapp_feature_auth` |
76
+
77
+ **Template mode** uses `{{BRANCH}}` for custom vars:
78
+
79
+ ```bash
80
+ MY_CUSTOM_DB=app_{{BRANCH}}
81
+ REDIS_PREFIX={{BRANCH}}_
82
+ ```
83
+
84
+ Both modes work together — template takes precedence over auto.
85
+
86
+ Branch slug: `feature/auth-v2` → `feature_auth_v2` (lowercase, special chars → `_`, max 30 chars).
87
+
88
+ ### `[docker]` — Docker Compose Isolation
89
+
90
+ Generates a `docker-compose.worktree.yml` override with port offsets:
91
+
92
+ - **`auto`**: Auto-detects compose file (`local.yml` → `docker-compose.local.yml` → `docker-compose.yml` → etc.)
93
+ - **`file=path`**: Specify compose file path (supports nested monorepo paths)
94
+ - **`port_offset=N`**: Override the auto-calculated offset
95
+
96
+ ```ini
97
+ [docker]
98
+ auto
99
+ file=apps/backend/docker-compose.local.yml
100
+ ```
101
+
102
+ **How it works:**
103
+
104
+ 1. Parses ports from your compose file
105
+ 2. Calculates a deterministic offset from branch name (1-100 via `cksum`)
106
+ 3. Generates `docker-compose.worktree.yml` next to the original
107
+ 4. Sets `COMPOSE_FILE=original.yml:docker-compose.worktree.yml` in `.env`
108
+ 5. Sets `COMPOSE_PROJECT_NAME` for container/volume/network isolation
109
+
110
+ After setup, `docker compose up -d` in the worktree automatically picks up both files.
111
+
112
+ **Auto-creates databases** in running Postgres containers (Docker or local). Idempotent — safe to run multiple times.
113
+
19
114
  ## Usage
20
115
 
21
116
  ### Create Worktree
@@ -26,7 +121,10 @@ Git worktree manager for parallel development with automatic environment setup.
26
121
  ```
27
122
 
28
123
  Creates worktree at `~/code/project--feature-authentication/` with:
29
- - All `.env*` files copied
124
+ - Files copied per `.worktreeinclude`
125
+ - Env vars rewritten with branch suffix
126
+ - Docker port offsets applied
127
+ - Database created
30
128
  - Dependencies installed
31
129
 
32
130
  ### List Worktrees
@@ -43,6 +141,8 @@ Creates worktree at `~/code/project--feature-authentication/` with:
43
141
  /wt rm feat-payments -d # also delete branch
44
142
  ```
45
143
 
144
+ Stops Docker containers before removing. Databases are preserved (drop manually if no longer needed).
145
+
46
146
  ### Open in Editor
47
147
 
48
148
  ```bash
@@ -73,12 +173,6 @@ Priority: flag > `WT_EDITOR` > auto-detect (Cursor → Antigravity → VS Code)
73
173
 
74
174
  ## Auto-Setup Details
75
175
 
76
- ### Environment Files
77
-
78
- Copies from:
79
- - Root: `.env`, `.env.local`, `.env.development`, etc.
80
- - Nested: `apps/*/.env*`, `packages/*/.env*`, `services/*/.env*`
81
-
82
176
  ### Package Manager Detection
83
177
 
84
178
  Checks in order:
@@ -96,6 +190,19 @@ Detects via:
96
190
  - `lerna.json`
97
191
  - `"workspaces"` in `package.json`
98
192
 
193
+ ### Compose File Detection Order
194
+
195
+ When `[docker] auto` is set and no `file=` specified:
196
+
197
+ 1. `local.yml`
198
+ 2. `docker-compose.local.yml`
199
+ 3. `docker-compose.yml`
200
+ 4. `docker-compose.yaml`
201
+ 5. `compose.yml`
202
+ 6. `compose.yaml`
203
+
204
+ Also searches `apps/*/` and `services/*/` subdirectories.
205
+
99
206
  ## Skill Triggers
100
207
 
101
208
  The worktree-manager skill auto-activates when you mention:
@@ -104,6 +211,8 @@ The worktree-manager skill auto-activates when you mention:
104
211
  - "feature branch setup"
105
212
  - "work on multiple branches"
106
213
  - "separate workspace for branch"
214
+ - "docker port conflict" or "database isolation"
215
+ - "worktreeinclude" or "env isolation"
107
216
 
108
217
  ## License
109
218
 
@@ -1,13 +1,13 @@
1
1
  ---
2
2
  name: wt
3
- description: Git worktree manager - create, list, remove, and open worktrees with automatic env copying and dependency installation
3
+ description: Git worktree manager - create, list, remove, and open worktrees with env isolation, Docker port offsets, and database auto-creation
4
4
  argument-hint: '<command> [branch] [options]'
5
5
  allowed-tools: ["Bash(${CLAUDE_PLUGIN_ROOT}/scripts/wt.sh:*)"]
6
6
  ---
7
7
 
8
8
  # Git Worktree Manager
9
9
 
10
- Manage git worktrees with automatic setup for parallel development.
10
+ Manage git worktrees with automatic isolation for parallel development.
11
11
 
12
12
  ## Usage
13
13
 
@@ -19,9 +19,9 @@ Manage git worktrees with automatic setup for parallel development.
19
19
 
20
20
  | Command | Alias | Description |
21
21
  |---------|-------|-------------|
22
- | `create <branch>` | `c` | Create worktree, copy .env files, install deps |
22
+ | `create <branch>` | `c` | Create worktree with isolation (env, docker, db) |
23
23
  | `list` | `ls` | List all worktrees for current repo |
24
- | `remove <branch>` | `rm` | Remove worktree (add `-d` to delete branch) |
24
+ | `remove <branch>` | `rm` | Remove worktree (stops Docker, add `-d` to delete branch) |
25
25
  | `open <branch> [--editor]` | `o` | Open worktree (--cursor\|-c, --agy\|-a, --code\|-v) |
26
26
 
27
27
  ## Instructions
@@ -35,7 +35,7 @@ Run the worktree manager script:
35
35
  ## Examples
36
36
 
37
37
  ```bash
38
- # Create worktree for new feature
38
+ # Create worktree for new feature (with full isolation)
39
39
  /wt create feature/authentication
40
40
 
41
41
  # Short form
@@ -44,7 +44,7 @@ Run the worktree manager script:
44
44
  # List all worktrees
45
45
  /wt ls
46
46
 
47
- # Remove worktree
47
+ # Remove worktree (stops Docker containers first)
48
48
  /wt rm feature/authentication
49
49
 
50
50
  # Remove worktree AND delete branch
@@ -67,7 +67,27 @@ Run the worktree manager script:
67
67
 
68
68
  ## Auto-Setup on Create
69
69
 
70
- 1. Copies all `.env*` files from root
71
- 2. Copies nested `.env*` from `apps/*/`, `packages/*/`, `services/*/`
72
- 3. Detects package manager (bun pnpm yarn → npm)
73
- 4. Runs install at root (monorepo-aware)
70
+ 1. Loads `.worktreeinclude` (generates default if missing)
71
+ 2. Copies files matching glob patterns
72
+ 3. Rewrites env vars (DB_NAME, DATABASE_URL, etc.) with branch suffix
73
+ 4. Generates `docker-compose.worktree.yml` with port offsets
74
+ 5. Auto-creates database in running Postgres
75
+ 6. Detects package manager (bun → pnpm → yarn → npm)
76
+ 7. Runs install at root (monorepo-aware)
77
+
78
+ ## `.worktreeinclude` Format
79
+
80
+ ```ini
81
+ # File patterns to copy
82
+ .env*
83
+ apps/*/.env*
84
+
85
+ [rewrite]
86
+ auto # suffix DB_NAME, DATABASE_URL, etc.
87
+ # MY_DB=app_{{BRANCH}} # template mode
88
+
89
+ [docker]
90
+ auto # auto-detect compose file
91
+ # file=apps/backend/docker-compose.local.yml
92
+ # port_offset=10
93
+ ```
@@ -11,6 +11,7 @@ RED='\033[0;31m'
11
11
  GREEN='\033[0;32m'
12
12
  YELLOW='\033[1;33m'
13
13
  BLUE='\033[0;34m'
14
+ CYAN='\033[0;36m'
14
15
  NC='\033[0m' # No Color
15
16
 
16
17
  # Helpers
@@ -58,66 +59,574 @@ detect_pkg_manager() {
58
59
  # Detect if monorepo
59
60
  is_monorepo() {
60
61
  local dir="${1:-$REPO_ROOT}"
61
- # Check for monorepo indicators
62
62
  [[ -f "$dir/pnpm-workspace.yaml" ]] && return 0
63
63
  [[ -f "$dir/turbo.json" ]] && return 0
64
64
  [[ -f "$dir/nx.json" ]] && return 0
65
65
  [[ -f "$dir/lerna.json" ]] && return 0
66
- # Check for workspaces in package.json
67
66
  if [[ -f "$dir/package.json" ]]; then
68
67
  grep -q '"workspaces"' "$dir/package.json" 2>/dev/null && return 0
69
68
  fi
70
69
  return 1
71
70
  }
72
71
 
73
- # Copy .env files from source to destination
74
- copy_env_files() {
72
+ # Get worktree path from branch name
73
+ get_worktree_path() {
74
+ local branch="$1"
75
+ local safe_branch="${branch//\//-}"
76
+ echo "${REPO_PARENT}/${REPO_NAME}--${safe_branch}"
77
+ }
78
+
79
+ # Strip main repo name prefix from worktree basename
80
+ get_branch_from_worktree() {
81
+ local wt_path="$1"
82
+ local wt_name=$(basename "$wt_path")
83
+ echo "${wt_name#${REPO_NAME}--}"
84
+ }
85
+
86
+ # =============================================================================
87
+ # Phase 1: .worktreeinclude Parsing & File Copying
88
+ # =============================================================================
89
+
90
+ # Generate default .worktreeinclude if it doesn't exist
91
+ generate_default_worktreeinclude() {
92
+ local target="$REPO_ROOT/.worktreeinclude"
93
+ [[ -f "$target" ]] && return 0
94
+
95
+ cat > "$target" << 'TEMPLATE'
96
+ # .worktreeinclude — Files to copy to new worktrees
97
+ # Commit this file so your team shares the same config
98
+ .env*
99
+
100
+ # Monorepo (uncomment as needed)
101
+ # apps/*/.env*
102
+ # packages/*/.env*
103
+ # services/*/.env*
104
+
105
+ [rewrite]
106
+ auto
107
+
108
+ [docker]
109
+ auto
110
+ # file=apps/backend/docker-compose.local.yml
111
+ # port_offset=10
112
+ TEMPLATE
113
+ info "Generated .worktreeinclude (commit this file)"
114
+ }
115
+
116
+ # Parse .worktreeinclude into 3 arrays: file patterns, rewrite lines, docker lines
117
+ parse_worktreeinclude() {
118
+ local include_file="$REPO_ROOT/.worktreeinclude"
119
+ WT_FILE_PATTERNS=()
120
+ WT_REWRITE_LINES=()
121
+ WT_DOCKER_LINES=()
122
+
123
+ [[ -f "$include_file" ]] || return 0
124
+
125
+ local current_section="files"
126
+ while IFS= read -r line || [[ -n "$line" ]]; do
127
+ # Strip trailing whitespace
128
+ line="${line%"${line##*[![:space:]]}"}"
129
+ # Skip empty lines and comments
130
+ [[ -z "$line" ]] && continue
131
+ [[ "$line" == \#* ]] && continue
132
+
133
+ # Detect section headers
134
+ if [[ "$line" == "[rewrite]" ]]; then
135
+ current_section="rewrite"
136
+ continue
137
+ elif [[ "$line" == "[docker]" ]]; then
138
+ current_section="docker"
139
+ continue
140
+ fi
141
+
142
+ case "$current_section" in
143
+ files) WT_FILE_PATTERNS+=("$line") ;;
144
+ rewrite) WT_REWRITE_LINES+=("$line") ;;
145
+ docker) WT_DOCKER_LINES+=("$line") ;;
146
+ esac
147
+ done < "$include_file"
148
+ }
149
+
150
+ # Expand glob patterns against main worktree, return matched files (relative paths)
151
+ match_files_by_patterns() {
152
+ local src="$1"
153
+ MATCHED_FILES=()
154
+
155
+ for pattern in "${WT_FILE_PATTERNS[@]}"; do
156
+ # Use subshell with nullglob to safely expand globs
157
+ while IFS= read -r -d '' file; do
158
+ local rel="${file#"$src"/}"
159
+ MATCHED_FILES+=("$rel")
160
+ done < <(
161
+ cd "$src"
162
+ shopt -s nullglob
163
+ shopt -s globstar 2>/dev/null || true # bash 4+ only, needed for **
164
+ for f in $pattern; do
165
+ [[ -f "$f" ]] && printf '%s\0' "$src/$f"
166
+ done
167
+ )
168
+ done
169
+ }
170
+
171
+ # Copy matched files preserving directory structure
172
+ copy_matched_files() {
75
173
  local src="$1"
76
174
  local dst="$2"
77
175
  local count=0
78
176
 
79
- # Copy root .env* files
80
- for envfile in "$src"/.env*; do
81
- [[ -f "$envfile" ]] || continue
82
- local filename=$(basename "$envfile")
83
- cp "$envfile" "$dst/$filename"
177
+ for rel in "${MATCHED_FILES[@]+"${MATCHED_FILES[@]}"}"; do
178
+ local dir=$(dirname "$rel")
179
+ [[ "$dir" != "." ]] && mkdir -p "$dst/$dir"
180
+ cp "$src/$rel" "$dst/$rel"
84
181
  ((count++))
85
182
  done
86
183
 
87
- # Copy nested .env* files from common monorepo directories
88
- for subdir in apps packages services libs modules; do
89
- [[ -d "$src/$subdir" ]] || continue
90
- for app_dir in "$src/$subdir"/*/; do
184
+ echo "$count"
185
+ }
186
+
187
+ # =============================================================================
188
+ # Phase 2: Env Var Rewriting
189
+ # =============================================================================
190
+
191
+ # Branch name → safe slug for suffixing (lowercase, special chars → _, max 30)
192
+ sanitize_branch_for_suffix() {
193
+ local branch="$1"
194
+ local slug
195
+ slug=$(printf '%s' "$branch" | tr '[:upper:]' '[:lower:]' | tr -c '[:alnum:]' '_')
196
+ # Trim leading/trailing underscores
197
+ slug="${slug#_}"
198
+ slug="${slug%_}"
199
+ # Truncate to 30 chars
200
+ printf '%s' "${slug:0:30}"
201
+ }
202
+
203
+ # Rewrite a single .env file — auto-detect known keys + template {{BRANCH}}
204
+ rewrite_env_file() {
205
+ local env_file="$1"
206
+ local branch_slug="$2"
207
+ local has_auto=false
208
+
209
+ # Check if auto mode is enabled
210
+ for line in "${WT_REWRITE_LINES[@]}"; do
211
+ [[ "$line" == "auto" ]] && has_auto=true
212
+ done
213
+
214
+ local tmpfile
215
+ tmpfile=$(mktemp)
216
+ local changed=false
217
+
218
+ while IFS= read -r line || [[ -n "$line" ]]; do
219
+ local newline="$line"
220
+
221
+ # Template: replace {{BRANCH}} placeholders
222
+ if [[ "$line" == *'{{BRANCH}}'* ]]; then
223
+ newline="${line//\{\{BRANCH\}\}/$branch_slug}"
224
+ changed=true
225
+ elif [[ "$has_auto" == true ]]; then
226
+ # Auto-detect known keys (only lines with = that aren't comments)
227
+ if [[ "$line" =~ ^[[:space:]]*([A-Za-z_][A-Za-z0-9_]*)=(.*) ]]; then
228
+ local key="${BASH_REMATCH[1]}"
229
+ local val="${BASH_REMATCH[2]}"
230
+ # Strip surrounding quotes from val for processing
231
+ local raw_val="$val"
232
+ raw_val="${raw_val#\"}"
233
+ raw_val="${raw_val%\"}"
234
+ raw_val="${raw_val#\'}"
235
+ raw_val="${raw_val%\'}"
236
+
237
+ case "$key" in
238
+ DB_NAME|POSTGRES_DB|MYSQL_DATABASE|DATABASE_NAME)
239
+ # Suffix the value
240
+ newline="${key}=${raw_val}_${branch_slug}"
241
+ changed=true
242
+ ;;
243
+ DATABASE_URL|POSTGRES_URL)
244
+ # Parse URL: scheme://user:pass@host:port/dbname?params
245
+ # Suffix the database name portion
246
+ if [[ "$raw_val" =~ ^(.*://[^/]*/?)([^?]+)(.*) ]]; then
247
+ local prefix="${BASH_REMATCH[1]}"
248
+ local dbname="${BASH_REMATCH[2]}"
249
+ local suffix="${BASH_REMATCH[3]}"
250
+ newline="${key}=${prefix}${dbname}_${branch_slug}${suffix}"
251
+ changed=true
252
+ fi
253
+ ;;
254
+ COMPOSE_PROJECT_NAME)
255
+ newline="${key}=${raw_val}_${branch_slug}"
256
+ changed=true
257
+ ;;
258
+ esac
259
+ fi
260
+ fi
261
+
262
+ printf '%s\n' "$newline"
263
+ done < "$env_file" > "$tmpfile"
264
+
265
+ if [[ "$changed" == true ]]; then
266
+ mv "$tmpfile" "$env_file"
267
+ else
268
+ rm -f "$tmpfile"
269
+ fi
270
+
271
+ echo "$changed"
272
+ }
273
+
274
+ # Find and rewrite all .env* files in the worktree
275
+ rewrite_all_env_files() {
276
+ local wt_path="$1"
277
+ local branch_slug="$2"
278
+ local count=0
279
+
280
+ # Only proceed if there are rewrite lines configured
281
+ [[ ${#WT_REWRITE_LINES[@]} -gt 0 ]] || return 0
282
+
283
+ while IFS= read -r -d '' env_file; do
284
+ local basename_f
285
+ basename_f=$(basename "$env_file")
286
+ # Only process .env* files (not .envrc or similar non-env files)
287
+ [[ "$basename_f" == .env* ]] || continue
288
+ local result
289
+ result=$(rewrite_env_file "$env_file" "$branch_slug")
290
+ [[ "$result" == "true" ]] && ((count++))
291
+ done < <(find "$wt_path" -name '.env*' -not -path '*/node_modules/*' -not -path '*/.git/*' -print0 2>/dev/null)
292
+
293
+ echo "$count"
294
+ }
295
+
296
+ # =============================================================================
297
+ # Phase 3: Docker Compose Isolation
298
+ # =============================================================================
299
+
300
+ # Deterministic port offset from branch name (1-100 range via cksum)
301
+ branch_to_port_offset() {
302
+ local branch="$1"
303
+ local hash
304
+ hash=$(printf '%s' "$branch" | cksum | awk '{print $1}')
305
+ echo $(( (hash % 100) + 1 ))
306
+ }
307
+
308
+ # Minimal YAML parser: extract host ports from docker-compose services
309
+ # Output format: service_name:host_port:container_port (one per line)
310
+ parse_docker_compose_ports() {
311
+ local compose_file="$1"
312
+ awk '
313
+ /^[[:space:]]*[a-zA-Z_][a-zA-Z0-9_-]*:/ && !/^\s*-/ && !/ports:/ && !/image:/ && !/container_name:/ {
314
+ # Top-level or nested service name
315
+ indent = 0
316
+ for (i = 1; i <= length($0); i++) {
317
+ if (substr($0, i, 1) == " ") indent++
318
+ else break
319
+ }
320
+ line = $0
321
+ gsub(/^[[:space:]]+/, "", line)
322
+ gsub(/:.*/, "", line)
323
+ if (indent <= 4) current_service = line
324
+ }
325
+ /ports:/ {
326
+ in_ports = 1
327
+ next
328
+ }
329
+ in_ports && /^[[:space:]]*-[[:space:]]*"?[0-9]/ {
330
+ line = $0
331
+ gsub(/^[[:space:]]*-[[:space:]]*/, "", line)
332
+ gsub(/"/, "", line)
333
+ gsub(/[[:space:]].*/, "", line)
334
+ # line is now like "5432:5432" or "8025:8025"
335
+ split(line, parts, ":")
336
+ if (length(parts) >= 2) {
337
+ print current_service ":" parts[1] ":" parts[2]
338
+ }
339
+ next
340
+ }
341
+ in_ports && /^[[:space:]]*[^-[:space:]]/ {
342
+ in_ports = 0
343
+ }
344
+ ' "$compose_file"
345
+ }
346
+
347
+ # Detect compose file path — checks [docker] file= directive, then auto-detects
348
+ detect_compose_file() {
349
+ local wt_path="$1"
350
+ local compose_file=""
351
+
352
+ # Check for file= directive in [docker] config
353
+ for line in "${WT_DOCKER_LINES[@]}"; do
354
+ if [[ "$line" =~ ^file=(.+) ]]; then
355
+ local specified="${BASH_REMATCH[1]}"
356
+ specified="${specified#"${specified%%[![:space:]]*}"}" # trim leading
357
+ specified="${specified%"${specified##*[![:space:]]}"}" # trim trailing
358
+ if [[ -f "$wt_path/$specified" ]]; then
359
+ echo "$specified"
360
+ return 0
361
+ else
362
+ warn "Specified compose file not found: $specified"
363
+ fi
364
+ fi
365
+ done
366
+
367
+ # Auto-detect in standard locations
368
+ local candidates=(
369
+ "local.yml"
370
+ "docker-compose.local.yml"
371
+ "docker-compose.yml"
372
+ "docker-compose.yaml"
373
+ "compose.yml"
374
+ "compose.yaml"
375
+ )
376
+
377
+ # Check root first
378
+ for candidate in "${candidates[@]}"; do
379
+ if [[ -f "$wt_path/$candidate" ]]; then
380
+ echo "$candidate"
381
+ return 0
382
+ fi
383
+ done
384
+
385
+ # Check nested app directories
386
+ for subdir in apps services; do
387
+ [[ -d "$wt_path/$subdir" ]] || continue
388
+ for app_dir in "$wt_path/$subdir"/*/; do
91
389
  [[ -d "$app_dir" ]] || continue
92
- local app_name=$(basename "$app_dir")
93
- for envfile in "$app_dir".env*; do
94
- [[ -f "$envfile" ]] || continue
95
- local filename=$(basename "$envfile")
96
- # Ensure target directory exists
97
- mkdir -p "$dst/$subdir/$app_name"
98
- cp "$envfile" "$dst/$subdir/$app_name/$filename"
99
- ((count++))
390
+ for candidate in "${candidates[@]}"; do
391
+ if [[ -f "$app_dir$candidate" ]]; then
392
+ local rel="${app_dir#"$wt_path"/}$candidate"
393
+ echo "$rel"
394
+ return 0
395
+ fi
100
396
  done
101
397
  done
102
398
  done
103
399
 
104
- echo "$count"
400
+ return 1
105
401
  }
106
402
 
107
- # Get worktree path from branch name
108
- get_worktree_path() {
109
- local branch="$1"
110
- # Replace slashes with dashes for branch names like feature/auth
111
- local safe_branch="${branch//\//-}"
112
- echo "${REPO_PARENT}/${REPO_NAME}--${safe_branch}"
403
+ # Generate docker-compose.worktree.yml override with port offsets
404
+ generate_docker_compose_override() {
405
+ local compose_file="$1" # full path to original compose file
406
+ local offset="$2"
407
+ local override_dir
408
+ override_dir=$(dirname "$compose_file")
409
+ local override_file="$override_dir/docker-compose.worktree.yml"
410
+
411
+ local ports_data
412
+ ports_data=$(parse_docker_compose_ports "$compose_file")
413
+
414
+ [[ -z "$ports_data" ]] && return 1
415
+
416
+ local tmpfile
417
+ tmpfile=$(mktemp)
418
+
419
+ cat > "$tmpfile" << 'HEADER'
420
+ # Auto-generated by wt — DO NOT EDIT
421
+ # Port offsets for worktree isolation
422
+ services:
423
+ HEADER
424
+
425
+ local current_service=""
426
+ while IFS=: read -r service host_port container_port; do
427
+ [[ -z "$service" ]] && continue
428
+ local new_port=$((host_port + offset))
429
+
430
+ if [[ "$service" != "$current_service" ]]; then
431
+ printf ' %s:\n' "$service" >> "$tmpfile"
432
+ printf ' ports:\n' >> "$tmpfile"
433
+ current_service="$service"
434
+ fi
435
+ printf ' - "%d:%s"\n' "$new_port" "$container_port" >> "$tmpfile"
436
+ done <<< "$ports_data"
437
+
438
+ mv "$tmpfile" "$override_file"
439
+ echo "$override_file"
113
440
  }
114
441
 
115
- # Strip main repo name prefix from worktree basename
116
- get_branch_from_worktree() {
442
+ # Orchestrate Docker isolation: detect, offset, generate override, update .env
443
+ setup_docker_isolation() {
117
444
  local wt_path="$1"
118
- local wt_name=$(basename "$wt_path")
119
- # Remove the repo name prefix and --
120
- echo "${wt_name#${REPO_NAME}--}"
445
+ local branch="$2"
446
+ local branch_slug="$3"
447
+
448
+ # Check if docker section has 'auto' or any config
449
+ local has_docker=false
450
+ for line in "${WT_DOCKER_LINES[@]}"; do
451
+ [[ "$line" == "auto" || "$line" =~ ^file= ]] && has_docker=true
452
+ done
453
+ [[ "$has_docker" == true ]] || return 0
454
+
455
+ local compose_rel
456
+ compose_rel=$(detect_compose_file "$wt_path") || {
457
+ warn "No docker-compose file found, skipping Docker isolation"
458
+ return 0
459
+ }
460
+
461
+ local compose_full="$wt_path/$compose_rel"
462
+ local offset
463
+ offset=$(branch_to_port_offset "$branch")
464
+
465
+ # Check for custom port_offset
466
+ for line in "${WT_DOCKER_LINES[@]}"; do
467
+ if [[ "$line" =~ ^port_offset=([0-9]+) ]]; then
468
+ offset="${BASH_REMATCH[1]}"
469
+ fi
470
+ done
471
+
472
+ info "Docker isolation: $compose_rel (port offset +$offset)"
473
+
474
+ local override_file
475
+ override_file=$(generate_docker_compose_override "$compose_full" "$offset") || {
476
+ warn "No ports found in compose file, skipping port override"
477
+ return 0
478
+ }
479
+
480
+ local override_rel="${override_file#"$wt_path"/}"
481
+ success "Generated $override_rel"
482
+
483
+ # Set COMPOSE_FILE and COMPOSE_PROJECT_NAME in root .env
484
+ local root_env="$wt_path/.env"
485
+ local compose_file_val="${compose_rel}:${override_rel}"
486
+ local project_name="${REPO_NAME}_${branch_slug}"
487
+
488
+ # Append or update COMPOSE_FILE in .env
489
+ if [[ -f "$root_env" ]]; then
490
+ local tmpfile
491
+ tmpfile=$(mktemp)
492
+ local found_cf=false
493
+ local found_cpn=false
494
+ while IFS= read -r line || [[ -n "$line" ]]; do
495
+ if [[ "$line" =~ ^COMPOSE_FILE= ]]; then
496
+ printf 'COMPOSE_FILE=%s\n' "$compose_file_val"
497
+ found_cf=true
498
+ elif [[ "$line" =~ ^COMPOSE_PROJECT_NAME= ]]; then
499
+ printf 'COMPOSE_PROJECT_NAME=%s\n' "$project_name"
500
+ found_cpn=true
501
+ else
502
+ printf '%s\n' "$line"
503
+ fi
504
+ done < "$root_env" > "$tmpfile"
505
+ [[ "$found_cf" == false ]] && printf 'COMPOSE_FILE=%s\n' "$compose_file_val" >> "$tmpfile"
506
+ [[ "$found_cpn" == false ]] && printf 'COMPOSE_PROJECT_NAME=%s\n' "$project_name" >> "$tmpfile"
507
+ mv "$tmpfile" "$root_env"
508
+ else
509
+ printf 'COMPOSE_FILE=%s\n' "$compose_file_val" > "$root_env"
510
+ printf 'COMPOSE_PROJECT_NAME=%s\n' "$project_name" >> "$root_env"
511
+ fi
512
+
513
+ # Print port mapping summary
514
+ local ports_data
515
+ ports_data=$(parse_docker_compose_ports "$compose_full")
516
+ if [[ -n "$ports_data" ]]; then
517
+ echo ""
518
+ echo -e " ${CYAN}Port mappings:${NC}"
519
+ while IFS=: read -r service host_port container_port; do
520
+ [[ -z "$service" ]] && continue
521
+ local new_port=$((host_port + offset))
522
+ echo -e " ${service}: ${host_port} → ${GREEN}${new_port}${NC}"
523
+ done <<< "$ports_data"
524
+ fi
525
+ }
526
+
527
+ # =============================================================================
528
+ # Phase 4: Database Auto-Creation
529
+ # =============================================================================
530
+
531
+ # Detect Postgres — checks Docker containers first, then local
532
+ detect_postgres() {
533
+ # Check Docker containers for postgres
534
+ if command -v docker &>/dev/null; then
535
+ local pg_container
536
+ pg_container=$(docker ps --filter "ancestor=postgres" --filter "status=running" --format '{{.Names}}' 2>/dev/null | head -1)
537
+ if [[ -n "$pg_container" ]]; then
538
+ echo "docker:$pg_container"
539
+ return 0
540
+ fi
541
+ # Also check by port binding (for custom images)
542
+ pg_container=$(docker ps --filter "publish=5432" --filter "status=running" --format '{{.Names}}' 2>/dev/null | head -1)
543
+ if [[ -n "$pg_container" ]]; then
544
+ echo "docker:$pg_container"
545
+ return 0
546
+ fi
547
+ fi
548
+
549
+ # Check local postgres
550
+ if command -v pg_isready &>/dev/null && pg_isready -q 2>/dev/null; then
551
+ echo "local"
552
+ return 0
553
+ fi
554
+
555
+ return 1
556
+ }
557
+
558
+ # Create database if it doesn't exist (idempotent)
559
+ create_database_if_not_exists() {
560
+ local db_name="$1"
561
+ local pg_source="$2" # "docker:container_name" or "local"
562
+
563
+ local exists_query="SELECT 1 FROM pg_database WHERE datname='$db_name'"
564
+ local create_query="CREATE DATABASE \"$db_name\""
565
+ local result=""
566
+
567
+ if [[ "$pg_source" == local ]]; then
568
+ result=$(psql -tAc "$exists_query" postgres 2>/dev/null || true)
569
+ if [[ "$result" != "1" ]]; then
570
+ psql -c "$create_query" postgres 2>/dev/null && return 0 || return 1
571
+ fi
572
+ elif [[ "$pg_source" == docker:* ]]; then
573
+ local container="${pg_source#docker:}"
574
+ result=$(docker exec "$container" psql -U postgres -tAc "$exists_query" postgres 2>/dev/null || true)
575
+ if [[ "$result" != "1" ]]; then
576
+ docker exec "$container" psql -U postgres -c "$create_query" postgres 2>/dev/null && return 0 || return 1
577
+ fi
578
+ fi
579
+
580
+ return 0 # Already exists
581
+ }
582
+
583
+ # Read rewritten DB name from .env files and auto-create databases
584
+ auto_create_databases() {
585
+ local wt_path="$1"
586
+ local pg_source
587
+
588
+ pg_source=$(detect_postgres) || {
589
+ warn "Postgres not running, skipping database auto-creation"
590
+ return 0
591
+ }
592
+
593
+ # Collect unique database names from all .env files
594
+ local db_names=()
595
+ while IFS= read -r -d '' env_file; do
596
+ while IFS= read -r line; do
597
+ if [[ "$line" =~ ^(DB_NAME|POSTGRES_DB|MYSQL_DATABASE|DATABASE_NAME)=(.+) ]]; then
598
+ local val="${BASH_REMATCH[2]}"
599
+ val="${val#\"}" ; val="${val%\"}"
600
+ val="${val#\'}" ; val="${val%\'}"
601
+ # Only add if not already in array
602
+ local found=false
603
+ for existing in "${db_names[@]+"${db_names[@]}"}"; do
604
+ [[ "$existing" == "$val" ]] && found=true
605
+ done
606
+ [[ "$found" == false ]] && db_names+=("$val")
607
+ elif [[ "$line" =~ ^(DATABASE_URL|POSTGRES_URL)=(.+) ]]; then
608
+ local url="${BASH_REMATCH[2]}"
609
+ url="${url#\"}" ; url="${url%\"}"
610
+ # Extract db name from URL: scheme://user:pass@host:port/dbname
611
+ if [[ "$url" =~ ://[^/]*/([^?]+) ]]; then
612
+ local val="${BASH_REMATCH[1]}"
613
+ local found=false
614
+ for existing in "${db_names[@]+"${db_names[@]}"}"; do
615
+ [[ "$existing" == "$val" ]] && found=true
616
+ done
617
+ [[ "$found" == false ]] && db_names+=("$val")
618
+ fi
619
+ fi
620
+ done < "$env_file"
621
+ done < <(find "$wt_path" -maxdepth 3 -name '.env*' -not -path '*/node_modules/*' -not -path '*/.git/*' -print0 2>/dev/null)
622
+
623
+ for db_name in "${db_names[@]+"${db_names[@]}"}"; do
624
+ if create_database_if_not_exists "$db_name" "$pg_source"; then
625
+ success "Database ready: $db_name"
626
+ else
627
+ warn "Could not create database: $db_name"
628
+ fi
629
+ done
121
630
  }
122
631
 
123
632
  # =============================================================================
@@ -141,9 +650,9 @@ cmd_create() {
141
650
  exit 1
142
651
  fi
143
652
 
653
+ # --- Step 1: Create git worktree ---
144
654
  info "Creating worktree for branch: $branch"
145
655
 
146
- # Check if branch exists (local or remote)
147
656
  if git show-ref --verify --quiet "refs/heads/$branch" 2>/dev/null; then
148
657
  info "Checking out existing local branch: $branch"
149
658
  git worktree add "$wt_path" "$branch"
@@ -156,16 +665,47 @@ cmd_create() {
156
665
  fi
157
666
  success "Worktree created at: $wt_path"
158
667
 
159
- # Copy .env files
160
- info "Copying .env files..."
161
- local env_count=$(copy_env_files "$REPO_ROOT" "$wt_path")
162
- if [[ "$env_count" -gt 0 ]]; then
163
- success "Copied $env_count .env file(s)"
164
- else
165
- warn "No .env files found to copy"
668
+ # --- Step 2: Load .worktreeinclude ---
669
+ generate_default_worktreeinclude
670
+ parse_worktreeinclude
671
+
672
+ # --- Step 3: Copy files matching patterns ---
673
+ if [[ ${#WT_FILE_PATTERNS[@]} -gt 0 ]]; then
674
+ info "Copying files from .worktreeinclude..."
675
+ match_files_by_patterns "$REPO_ROOT"
676
+ local file_count
677
+ file_count=$(copy_matched_files "$REPO_ROOT" "$wt_path")
678
+ if [[ "$file_count" -gt 0 ]]; then
679
+ success "Copied $file_count file(s)"
680
+ else
681
+ warn "No matching files found to copy"
682
+ fi
166
683
  fi
167
684
 
168
- # Detect and run package manager install
685
+ # --- Step 4: Rewrite env vars ---
686
+ local branch_slug
687
+ branch_slug=$(sanitize_branch_for_suffix "$branch")
688
+
689
+ if [[ ${#WT_REWRITE_LINES[@]} -gt 0 ]]; then
690
+ info "Rewriting env vars (suffix: _$branch_slug)..."
691
+ local rewrite_count
692
+ rewrite_count=$(rewrite_all_env_files "$wt_path" "$branch_slug")
693
+ if [[ "$rewrite_count" -gt 0 ]]; then
694
+ success "Rewrote $rewrite_count .env file(s)"
695
+ fi
696
+ fi
697
+
698
+ # --- Step 5: Docker Compose isolation ---
699
+ if [[ ${#WT_DOCKER_LINES[@]} -gt 0 ]]; then
700
+ setup_docker_isolation "$wt_path" "$branch" "$branch_slug"
701
+ fi
702
+
703
+ # --- Step 6: Auto-create database ---
704
+ if [[ ${#WT_REWRITE_LINES[@]} -gt 0 ]]; then
705
+ auto_create_databases "$wt_path"
706
+ fi
707
+
708
+ # --- Step 7: Install dependencies ---
169
709
  local pkg_mgr=$(detect_pkg_manager "$wt_path")
170
710
  if [[ -n "$pkg_mgr" ]]; then
171
711
  info "Detected package manager: $pkg_mgr"
@@ -185,6 +725,7 @@ cmd_create() {
185
725
  success "Dependencies installed"
186
726
  fi
187
727
 
728
+ # --- Step 8: Summary ---
188
729
  echo ""
189
730
  success "Worktree ready!"
190
731
  echo ""
@@ -197,10 +738,8 @@ cmd_list() {
197
738
  info "Worktrees for: $REPO_NAME"
198
739
  echo ""
199
740
 
200
- # Get all worktrees
201
741
  local found=0
202
742
  while IFS= read -r line; do
203
- # Parse worktree output (format: "path HEAD branch")
204
743
  local wt_path=$(echo "$line" | awk '{print $1}')
205
744
  local wt_branch=$(echo "$line" | awk '{print $3}' | sed 's/\[//;s/\]//')
206
745
 
@@ -223,7 +762,6 @@ cmd_remove() {
223
762
  local branch="${1:-}"
224
763
  local delete_branch=false
225
764
 
226
- # Check for --delete-branch flag
227
765
  if [[ "${2:-}" == "--delete-branch" ]] || [[ "${2:-}" == "-d" ]]; then
228
766
  delete_branch=true
229
767
  fi
@@ -243,9 +781,23 @@ cmd_remove() {
243
781
  exit 1
244
782
  fi
245
783
 
784
+ # Stop Docker containers if compose setup exists
785
+ if [[ -f "$wt_path/.env" ]]; then
786
+ local compose_file_val=""
787
+ while IFS= read -r line; do
788
+ [[ "$line" =~ ^COMPOSE_FILE=(.+) ]] && compose_file_val="${BASH_REMATCH[1]}"
789
+ done < "$wt_path/.env"
790
+
791
+ if [[ -n "$compose_file_val" ]] && command -v docker &>/dev/null; then
792
+ info "Stopping Docker containers..."
793
+ (cd "$wt_path" && docker compose down 2>/dev/null) || warn "Could not stop containers (may not be running)"
794
+ fi
795
+ fi
796
+
246
797
  info "Removing worktree: $wt_path"
247
798
  git worktree remove "$wt_path" --force
248
799
  success "Worktree removed"
800
+ info "Note: databases are preserved — drop manually if no longer needed"
249
801
 
250
802
  if [[ "$delete_branch" == true ]]; then
251
803
  info "Deleting branch: $branch"
@@ -258,7 +810,6 @@ cmd_open() {
258
810
  local branch=""
259
811
  local editor=""
260
812
 
261
- # Parse arguments
262
813
  while [[ $# -gt 0 ]]; do
263
814
  case "$1" in
264
815
  --cursor|-c)
@@ -299,7 +850,6 @@ cmd_open() {
299
850
  exit 1
300
851
  fi
301
852
 
302
- # Priority: flag > WT_EDITOR env > auto-detect
303
853
  if [[ -z "$editor" && -n "${WT_EDITOR:-}" ]]; then
304
854
  editor="$WT_EDITOR"
305
855
  fi
@@ -312,7 +862,6 @@ cmd_open() {
312
862
  info "Opening in $editor: $wt_path"
313
863
  "$editor" "$wt_path"
314
864
  else
315
- # Auto-detect: Cursor → Antigravity → VS Code
316
865
  if command -v cursor &>/dev/null; then
317
866
  info "Opening in Cursor: $wt_path"
318
867
  cursor "$wt_path"
@@ -336,7 +885,7 @@ cmd_help() {
336
885
  echo "Usage: wt <command> [args]"
337
886
  echo ""
338
887
  echo "Commands:"
339
- echo " create, c <branch> Create worktree, copy .env files, install deps"
888
+ echo " create, c <branch> Create worktree with isolation (env, docker, db)"
340
889
  echo " list, ls List all worktrees for current repo"
341
890
  echo " remove, rm <branch> Remove worktree (add -d to delete branch too)"
342
891
  echo " open, o <branch> Open worktree (flag > WT_EDITOR > auto-detect)"
@@ -346,6 +895,17 @@ cmd_help() {
346
895
  echo "Environment:"
347
896
  echo " WT_EDITOR Default editor (cursor, agy, code)"
348
897
  echo ""
898
+ echo "Isolation (.worktreeinclude):"
899
+ echo " Auto-generated on first 'wt create' if missing."
900
+ echo " Controls which files to copy and how to isolate services."
901
+ echo ""
902
+ echo " Sections:"
903
+ echo " (top) Glob patterns for files to copy (e.g., .env*)"
904
+ echo " [rewrite] 'auto' to suffix DB_NAME, DATABASE_URL, etc."
905
+ echo " Use {{BRANCH}} for custom templates"
906
+ echo " [docker] 'auto' to generate port-offset override"
907
+ echo " 'file=path' for custom compose file"
908
+ echo ""
349
909
  echo "Naming convention:"
350
910
  echo " ~/code/project/ → main repo"
351
911
  echo " ~/code/project--feat-auth/ → worktree for feat-auth branch"
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  name: worktree-manager
3
- description: This skill should be used when the user mentions "worktree", "wt", "new branch workspace", "parallel development", "feature branch setup", "work on multiple branches", "separate workspace for branch", or wants to manage git worktrees for parallel feature development.
4
- version: 1.0.0
3
+ description: This skill should be used when the user mentions "worktree", "wt", "new branch workspace", "parallel development", "feature branch setup", "work on multiple branches", "separate workspace for branch", "docker port conflict", "database isolation", "worktreeinclude", "env isolation", or wants to manage git worktrees for parallel feature development.
4
+ version: 2.0.0
5
5
  ---
6
6
 
7
7
  # Git Worktree Manager Skill
8
8
 
9
- Manages git worktrees for parallel development with automatic environment setup.
9
+ Manages git worktrees for parallel development with automatic environment isolation.
10
10
 
11
11
  ## Activation Triggers
12
12
 
@@ -17,14 +17,17 @@ This skill activates when the user:
17
17
  - Needs a "separate workspace for a feature"
18
18
  - Asks about "parallel development" setup
19
19
  - Wants to "set up a new branch workspace"
20
+ - Has "docker port conflicts" between branches
21
+ - Needs "database isolation" for parallel work
22
+ - Asks about ".worktreeinclude" or "env isolation"
20
23
 
21
24
  ## Commands Reference
22
25
 
23
26
  | Command | Alias | Description |
24
27
  |---------|-------|-------------|
25
- | `/wt create <branch>` | `/wt c` | Create worktree with auto-setup |
28
+ | `/wt create <branch>` | `/wt c` | Create worktree with full isolation |
26
29
  | `/wt list` | `/wt ls` | Show all worktrees |
27
- | `/wt remove <branch>` | `/wt rm` | Remove worktree |
30
+ | `/wt remove <branch>` | `/wt rm` | Remove worktree (stops Docker first) |
28
31
  | `/wt open <branch> [--editor]` | `/wt o` | Open (--cursor\|-c, --agy\|-a, --code\|-v) |
29
32
 
30
33
  ## Naming Convention
@@ -36,23 +39,55 @@ Worktrees are created as siblings with `--` separator:
36
39
  ~/[PARENT_DIRECTORY]/[REPO_NAME]--[BRANCH_NAME]/ # worktree for [BRANCH_NAME]
37
40
  ```
38
41
 
42
+ ## Three-Layer Isolation
43
+
44
+ ### Layer 1: `.worktreeinclude` — File Selection
45
+
46
+ Gitignore-style file at repo root controlling which untracked files to copy. Auto-generated with sensible defaults on first `wt create`.
47
+
48
+ ```ini
49
+ .env*
50
+ # apps/*/.env*
51
+
52
+ [rewrite]
53
+ auto
54
+
55
+ [docker]
56
+ auto
57
+ ```
58
+
59
+ ### Layer 2: `[rewrite]` — Env Var Isolation
60
+
61
+ - **`auto`**: Suffixes DB_NAME, POSTGRES_DB, DATABASE_URL, COMPOSE_PROJECT_NAME with branch slug
62
+ - **`{{BRANCH}}`**: Template placeholder for custom vars
63
+
64
+ ### Layer 3: `[docker]` — Docker Compose Override
65
+
66
+ - Generates `docker-compose.worktree.yml` with port offsets
67
+ - Sets `COMPOSE_FILE` and `COMPOSE_PROJECT_NAME` in `.env`
68
+ - Auto-creates databases in running Postgres
69
+
39
70
  ## Auto-Setup Features
40
71
 
41
72
  When creating a worktree, the following happens automatically:
42
73
 
43
74
  1. **Branch handling**: Creates new branch or checks out existing (local/remote)
44
- 2. **Env files**: Copies all `.env*` from root and nested directories
45
- 3. **Package manager**: Detects bun/pnpm/yarn/npm from lockfiles
46
- 4. **Dependencies**: Runs install at root (monorepo-aware)
75
+ 2. **File copying**: Copies files matching `.worktreeinclude` patterns
76
+ 3. **Env rewriting**: Suffixes database names and URLs with branch slug
77
+ 4. **Docker isolation**: Generates port-offset override, sets compose config
78
+ 5. **Database creation**: Creates database in running Postgres (idempotent)
79
+ 6. **Package manager**: Detects bun/pnpm/yarn/npm from lockfiles
80
+ 7. **Dependencies**: Runs install at root (monorepo-aware)
47
81
 
48
82
  ## Behavioral Expectations
49
83
 
50
84
  When user asks about worktrees or parallel development:
51
85
 
52
86
  1. Suggest using `/wt create <branch>` for new worktrees
53
- 2. Explain the naming convention if they seem unfamiliar
54
- 3. Mention the auto-setup features (env copying, dep install)
87
+ 2. Explain the three-layer isolation if they have Docker/database conflicts
88
+ 3. Mention `.worktreeinclude` for customizing which files are copied
55
89
  4. Show the `cd` command output for easy navigation
90
+ 5. Explain that `wt remove` stops Docker containers but preserves databases
56
91
 
57
92
  ## Example Interactions
58
93
 
@@ -60,9 +95,19 @@ When user asks about worktrees or parallel development:
60
95
 
61
96
  **Response**: Use `/wt create feature/auth` to create a parallel workspace. This will:
62
97
  - Create `project--feature-auth/` as sibling directory
63
- - Copy your `.env` files
98
+ - Copy your `.env` files and rewrite DB names with `_feature_auth` suffix
99
+ - Generate Docker port offsets so both worktrees can run simultaneously
64
100
  - Install dependencies
65
101
 
66
- **User**: "Show me my worktrees"
102
+ **User**: "My worktrees are conflicting on port 5432"
103
+
104
+ **Response**: The `.worktreeinclude` file's `[docker]` section handles this. With `auto` enabled, `wt create` generates a `docker-compose.worktree.yml` override that offsets all ports. Each worktree gets a deterministic offset based on the branch name. Run `docker compose config` to verify the merged ports.
67
105
 
68
- **Response**: Use `/wt ls` to list all worktrees for the current repo.
106
+ **User**: "How do I customize which files are copied to worktrees?"
107
+
108
+ **Response**: Edit `.worktreeinclude` in your repo root. It uses gitignore-style patterns:
109
+ ```
110
+ .env*
111
+ config/local.yml
112
+ apps/*/.env*
113
+ ```