@smicolon/ai-kit 0.3.2 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/README.md +73 -40
  2. package/dist/index.js +312 -127
  3. package/package.json +5 -5
  4. package/.claude-plugin/marketplace.json +0 -369
  5. package/packs/architect/CHANGELOG.md +0 -17
  6. package/packs/architect/README.md +0 -58
  7. package/packs/architect/agents/system-architect.md +0 -768
  8. package/packs/architect/commands/diagram-create.md +0 -300
  9. package/packs/better-auth/.mcp.json +0 -14
  10. package/packs/better-auth/CHANGELOG.md +0 -26
  11. package/packs/better-auth/README.md +0 -125
  12. package/packs/better-auth/agents/auth-architect.md +0 -278
  13. package/packs/better-auth/commands/auth-provider-add.md +0 -265
  14. package/packs/better-auth/commands/auth-setup.md +0 -298
  15. package/packs/better-auth/skills/auth-security/SKILL.md +0 -425
  16. package/packs/better-auth/skills/better-auth-patterns/SKILL.md +0 -455
  17. package/packs/dev-loop/CHANGELOG.md +0 -69
  18. package/packs/dev-loop/README.md +0 -155
  19. package/packs/dev-loop/commands/cancel-dev.md +0 -21
  20. package/packs/dev-loop/commands/dev-loop.md +0 -72
  21. package/packs/dev-loop/commands/dev-plan.md +0 -351
  22. package/packs/dev-loop/hooks/hooks.json +0 -15
  23. package/packs/dev-loop/hooks/stop-hook.sh +0 -178
  24. package/packs/dev-loop/scripts/setup-dev-loop.sh +0 -194
  25. package/packs/dev-loop/skills/tdd-planner/SKILL.md +0 -249
  26. package/packs/dev-loop/skills/tdd-planner/references/framework-patterns.md +0 -874
  27. package/packs/dev-loop/skills/tdd-planner/references/good-example.md +0 -260
  28. package/packs/dev-loop/skills/tdd-planner/references/plan-template.md +0 -275
  29. package/packs/django/CHANGELOG.md +0 -39
  30. package/packs/django/README.md +0 -92
  31. package/packs/django/agents/django-architect.md +0 -182
  32. package/packs/django/agents/django-builder.md +0 -250
  33. package/packs/django/agents/django-feature-based.md +0 -420
  34. package/packs/django/agents/django-reviewer.md +0 -253
  35. package/packs/django/agents/django-tester.md +0 -230
  36. package/packs/django/commands/api-endpoint.md +0 -285
  37. package/packs/django/commands/model-create.md +0 -178
  38. package/packs/django/commands/test-generate.md +0 -325
  39. package/packs/django/rules/migrations.md +0 -138
  40. package/packs/django/rules/models.md +0 -167
  41. package/packs/django/rules/serializers.md +0 -126
  42. package/packs/django/rules/services.md +0 -131
  43. package/packs/django/rules/tests.md +0 -140
  44. package/packs/django/rules/views.md +0 -102
  45. package/packs/django/skills/import-convention-enforcer/SKILL.md +0 -226
  46. package/packs/django/skills/import-convention-enforcer/patterns/django-imports.md +0 -343
  47. package/packs/django/skills/migration-safety-checker/SKILL.md +0 -375
  48. package/packs/django/skills/model-entity-validator/SKILL.md +0 -298
  49. package/packs/django/skills/performance-optimizer/SKILL.md +0 -447
  50. package/packs/django/skills/red-phase-verifier/SKILL.md +0 -180
  51. package/packs/django/skills/security-first-validator/SKILL.md +0 -435
  52. package/packs/django/skills/test-coverage-advisor/SKILL.md +0 -394
  53. package/packs/django/skills/test-validity-checker/SKILL.md +0 -194
  54. package/packs/failure-log/CHANGELOG.md +0 -20
  55. package/packs/failure-log/README.md +0 -168
  56. package/packs/failure-log/commands/failure-add.md +0 -106
  57. package/packs/failure-log/commands/failure-list.md +0 -89
  58. package/packs/failure-log/hooks/hooks.json +0 -16
  59. package/packs/failure-log/hooks/scripts/inject-failures.sh +0 -64
  60. package/packs/failure-log/skills/failure-log-manager/SKILL.md +0 -164
  61. package/packs/flutter/CHANGELOG.md +0 -19
  62. package/packs/flutter/README.md +0 -170
  63. package/packs/flutter/agents/flutter-architect.md +0 -166
  64. package/packs/flutter/agents/flutter-builder.md +0 -303
  65. package/packs/flutter/agents/release-manager.md +0 -355
  66. package/packs/flutter/commands/fastlane-setup.md +0 -188
  67. package/packs/flutter/commands/flutter-build.md +0 -90
  68. package/packs/flutter/commands/flutter-deploy.md +0 -133
  69. package/packs/flutter/commands/flutter-test.md +0 -117
  70. package/packs/flutter/commands/signing-setup.md +0 -209
  71. package/packs/flutter/hooks/hooks.json +0 -17
  72. package/packs/flutter/skills/fastlane-knowledge/SKILL.md +0 -193
  73. package/packs/flutter/skills/flutter-architecture/SKILL.md +0 -127
  74. package/packs/flutter/skills/store-publishing/SKILL.md +0 -163
  75. package/packs/hono/CHANGELOG.md +0 -19
  76. package/packs/hono/README.md +0 -143
  77. package/packs/hono/agents/hono-architect.md +0 -240
  78. package/packs/hono/agents/hono-builder.md +0 -285
  79. package/packs/hono/agents/hono-reviewer.md +0 -279
  80. package/packs/hono/agents/hono-tester.md +0 -346
  81. package/packs/hono/commands/middleware-create.md +0 -223
  82. package/packs/hono/commands/project-init.md +0 -306
  83. package/packs/hono/commands/route-create.md +0 -153
  84. package/packs/hono/commands/rpc-client.md +0 -263
  85. package/packs/hono/skills/cloudflare-bindings/SKILL.md +0 -408
  86. package/packs/hono/skills/hono-patterns/SKILL.md +0 -309
  87. package/packs/hono/skills/rpc-typesafe/SKILL.md +0 -388
  88. package/packs/hono/skills/zod-validation/SKILL.md +0 -332
  89. package/packs/nestjs/CHANGELOG.md +0 -29
  90. package/packs/nestjs/README.md +0 -75
  91. package/packs/nestjs/agents/nestjs-architect.md +0 -402
  92. package/packs/nestjs/agents/nestjs-builder.md +0 -301
  93. package/packs/nestjs/agents/nestjs-tester.md +0 -437
  94. package/packs/nestjs/commands/module-create.md +0 -369
  95. package/packs/nestjs/rules/controllers.md +0 -92
  96. package/packs/nestjs/rules/dto.md +0 -124
  97. package/packs/nestjs/rules/entities.md +0 -102
  98. package/packs/nestjs/rules/services.md +0 -106
  99. package/packs/nestjs/skills/barrel-export-manager/SKILL.md +0 -389
  100. package/packs/nestjs/skills/import-convention-enforcer/SKILL.md +0 -365
  101. package/packs/nextjs/CHANGELOG.md +0 -36
  102. package/packs/nextjs/README.md +0 -76
  103. package/packs/nextjs/agents/frontend-tester.md +0 -680
  104. package/packs/nextjs/agents/frontend-visual.md +0 -820
  105. package/packs/nextjs/agents/nextjs-architect.md +0 -331
  106. package/packs/nextjs/agents/nextjs-modular.md +0 -433
  107. package/packs/nextjs/commands/component-create.md +0 -398
  108. package/packs/nextjs/rules/api-routes.md +0 -129
  109. package/packs/nextjs/rules/components.md +0 -106
  110. package/packs/nextjs/rules/hooks.md +0 -132
  111. package/packs/nextjs/skills/accessibility-validator/SKILL.md +0 -445
  112. package/packs/nextjs/skills/import-convention-enforcer/SKILL.md +0 -399
  113. package/packs/nextjs/skills/react-form-validator/SKILL.md +0 -569
  114. package/packs/nuxtjs/CHANGELOG.md +0 -30
  115. package/packs/nuxtjs/README.md +0 -56
  116. package/packs/nuxtjs/agents/frontend-tester.md +0 -680
  117. package/packs/nuxtjs/agents/frontend-visual.md +0 -820
  118. package/packs/nuxtjs/agents/nuxtjs-architect.md +0 -537
  119. package/packs/nuxtjs/commands/component-create.md +0 -223
  120. package/packs/nuxtjs/rules/components.md +0 -101
  121. package/packs/nuxtjs/rules/composables.md +0 -118
  122. package/packs/nuxtjs/rules/server-routes.md +0 -127
  123. package/packs/nuxtjs/skills/accessibility-validator/SKILL.md +0 -183
  124. package/packs/nuxtjs/skills/import-convention-enforcer/SKILL.md +0 -196
  125. package/packs/nuxtjs/skills/veevalidate-form-validator/SKILL.md +0 -190
  126. package/packs/onboard/CHANGELOG.md +0 -22
  127. package/packs/onboard/README.md +0 -103
  128. package/packs/onboard/agents/onboard-guide.md +0 -118
  129. package/packs/onboard/commands/onboard.md +0 -313
  130. package/packs/onboard/skills/onboard-context-provider/SKILL.md +0 -98
  131. package/packs/tanstack-router/CHANGELOG.md +0 -30
  132. package/packs/tanstack-router/README.md +0 -113
  133. package/packs/tanstack-router/agents/tanstack-architect.md +0 -173
  134. package/packs/tanstack-router/agents/tanstack-builder.md +0 -360
  135. package/packs/tanstack-router/agents/tanstack-tester.md +0 -454
  136. package/packs/tanstack-router/commands/form-create.md +0 -313
  137. package/packs/tanstack-router/commands/query-create.md +0 -263
  138. package/packs/tanstack-router/commands/route-create.md +0 -190
  139. package/packs/tanstack-router/commands/table-create.md +0 -413
  140. package/packs/tanstack-router/skills/ai-patterns/SKILL.md +0 -370
  141. package/packs/tanstack-router/skills/db-patterns/SKILL.md +0 -346
  142. package/packs/tanstack-router/skills/devtools-patterns/SKILL.md +0 -415
  143. package/packs/tanstack-router/skills/form-patterns/SKILL.md +0 -425
  144. package/packs/tanstack-router/skills/pacer-patterns/SKILL.md +0 -341
  145. package/packs/tanstack-router/skills/query-patterns/SKILL.md +0 -359
  146. package/packs/tanstack-router/skills/router-patterns/SKILL.md +0 -285
  147. package/packs/tanstack-router/skills/store-patterns/SKILL.md +0 -351
  148. package/packs/tanstack-router/skills/table-patterns/SKILL.md +0 -531
  149. package/packs/tanstack-router/skills/tanstack-conventions/SKILL.md +0 -428
  150. package/packs/tanstack-router/skills/virtual-patterns/SKILL.md +0 -490
  151. package/packs/worktree/CHANGELOG.md +0 -45
  152. package/packs/worktree/README.md +0 -219
  153. package/packs/worktree/commands/wt.md +0 -93
  154. package/packs/worktree/scripts/wt.sh +0 -957
  155. package/packs/worktree/skills/worktree-manager/SKILL.md +0 -113
@@ -1,957 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- # =============================================================================
5
- # wt.sh - Git Worktree Manager
6
- # Naming: ~/code/project/ → ~/code/project--branch-name/
7
- # =============================================================================
8
-
9
- # Colors
10
- RED='\033[0;31m'
11
- GREEN='\033[0;32m'
12
- YELLOW='\033[1;33m'
13
- BLUE='\033[0;34m'
14
- CYAN='\033[0;36m'
15
- NC='\033[0m' # No Color
16
-
17
- # Helpers
18
- info() { echo -e "${BLUE}ℹ${NC} $1"; }
19
- success() { echo -e "${GREEN}✓${NC} $1"; }
20
- warn() { echo -e "${YELLOW}⚠${NC} $1"; }
21
- error() { echo -e "${RED}✗${NC} $1" >&2; }
22
-
23
- # Get repo root and name (finds main worktree, not current worktree)
24
- get_repo_info() {
25
- # First check we're in a git repo
26
- git rev-parse --show-toplevel &>/dev/null || {
27
- error "Not in a git repository"
28
- exit 1
29
- }
30
-
31
- # Get the main worktree (first line of git worktree list)
32
- REPO_ROOT=$(git worktree list --porcelain | grep "^worktree " | head -1 | sed 's/^worktree //')
33
-
34
- if [[ -z "$REPO_ROOT" ]]; then
35
- error "Could not determine main worktree"
36
- exit 1
37
- fi
38
-
39
- REPO_NAME=$(basename "$REPO_ROOT")
40
- REPO_PARENT=$(dirname "$REPO_ROOT")
41
- }
42
-
43
- # Detect package manager
44
- detect_pkg_manager() {
45
- local dir="${1:-$REPO_ROOT}"
46
- if [[ -f "$dir/bun.lockb" ]] || [[ -f "$dir/bun.lock" ]]; then
47
- echo "bun"
48
- elif [[ -f "$dir/pnpm-lock.yaml" ]]; then
49
- echo "pnpm"
50
- elif [[ -f "$dir/yarn.lock" ]]; then
51
- echo "yarn"
52
- elif [[ -f "$dir/package-lock.json" ]] || [[ -f "$dir/package.json" ]]; then
53
- echo "npm"
54
- else
55
- echo ""
56
- fi
57
- }
58
-
59
- # Detect if monorepo
60
- is_monorepo() {
61
- local dir="${1:-$REPO_ROOT}"
62
- [[ -f "$dir/pnpm-workspace.yaml" ]] && return 0
63
- [[ -f "$dir/turbo.json" ]] && return 0
64
- [[ -f "$dir/nx.json" ]] && return 0
65
- [[ -f "$dir/lerna.json" ]] && return 0
66
- if [[ -f "$dir/package.json" ]]; then
67
- grep -q '"workspaces"' "$dir/package.json" 2>/dev/null && return 0
68
- fi
69
- return 1
70
- }
71
-
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() {
173
- local src="$1"
174
- local dst="$2"
175
- local count=0
176
-
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"
181
- ((count++))
182
- done
183
-
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
- [[ -f "$env_file" ]] || return 0
208
- local has_auto=false
209
-
210
- # Check if auto mode is enabled
211
- for line in "${WT_REWRITE_LINES[@]}"; do
212
- [[ "$line" == "auto" ]] && has_auto=true
213
- done
214
-
215
- local tmpfile
216
- tmpfile=$(mktemp)
217
- local changed=false
218
-
219
- while IFS= read -r line || [[ -n "$line" ]]; do
220
- local newline="$line"
221
-
222
- # Template: replace {{BRANCH}} placeholders
223
- if [[ "$line" == *'{{BRANCH}}'* ]]; then
224
- newline="${line//\{\{BRANCH\}\}/$branch_slug}"
225
- changed=true
226
- elif [[ "$has_auto" == true ]]; then
227
- # Auto-detect known keys (only lines with = that aren't comments)
228
- if [[ "$line" =~ ^[[:space:]]*([A-Za-z_][A-Za-z0-9_]*)=(.*) ]]; then
229
- local key="${BASH_REMATCH[1]}"
230
- local val="${BASH_REMATCH[2]}"
231
- # Strip surrounding quotes from val for processing
232
- local raw_val="$val"
233
- raw_val="${raw_val#\"}"
234
- raw_val="${raw_val%\"}"
235
- raw_val="${raw_val#\'}"
236
- raw_val="${raw_val%\'}"
237
-
238
- case "$key" in
239
- DB_NAME|POSTGRES_DB|MYSQL_DATABASE|DATABASE_NAME)
240
- # Suffix the value
241
- newline="${key}=${raw_val}_${branch_slug}"
242
- changed=true
243
- ;;
244
- DATABASE_URL|POSTGRES_URL)
245
- # Parse URL: scheme://user:pass@host:port/dbname?params
246
- # Suffix the database name portion
247
- if [[ "$raw_val" =~ ^(.*://[^/]*/?)([^?]+)(.*) ]]; then
248
- local prefix="${BASH_REMATCH[1]}"
249
- local dbname="${BASH_REMATCH[2]}"
250
- local suffix="${BASH_REMATCH[3]}"
251
- newline="${key}=${prefix}${dbname}_${branch_slug}${suffix}"
252
- changed=true
253
- fi
254
- ;;
255
- COMPOSE_PROJECT_NAME)
256
- newline="${key}=${raw_val}_${branch_slug}"
257
- changed=true
258
- ;;
259
- esac
260
- fi
261
- fi
262
-
263
- printf '%s\n' "$newline"
264
- done < "$env_file" > "$tmpfile"
265
-
266
- if [[ "$changed" == true ]]; then
267
- mv "$tmpfile" "$env_file"
268
- else
269
- rm -f "$tmpfile"
270
- fi
271
-
272
- echo "$changed"
273
- }
274
-
275
- # Find and rewrite all .env* files in the worktree
276
- rewrite_all_env_files() {
277
- local wt_path="$1"
278
- local branch_slug="$2"
279
- local count=0
280
-
281
- # Only proceed if there are rewrite lines configured
282
- [[ ${#WT_REWRITE_LINES[@]} -gt 0 ]] || return 0
283
-
284
- while IFS= read -r -d '' env_file; do
285
- local basename_f
286
- basename_f=$(basename "$env_file")
287
- # Only process .env* files (not .envrc or similar non-env files)
288
- [[ "$basename_f" == .env* ]] || continue
289
- local result
290
- result=$(rewrite_env_file "$env_file" "$branch_slug")
291
- [[ "$result" == "true" ]] && ((count++))
292
- done < <(find "$wt_path" -type f -name '.env*' -not -path '*/node_modules/*' -not -path '*/.git/*' -print0 2>/dev/null)
293
-
294
- echo "$count"
295
- }
296
-
297
- # =============================================================================
298
- # Phase 3: Docker Compose Isolation
299
- # =============================================================================
300
-
301
- # Deterministic port offset from branch name (1-100 range via cksum)
302
- branch_to_port_offset() {
303
- local branch="$1"
304
- local hash
305
- hash=$(printf '%s' "$branch" | cksum | awk '{print $1}')
306
- echo $(( (hash % 100) + 1 ))
307
- }
308
-
309
- # Minimal YAML parser: extract host ports from docker-compose services
310
- # Output format: service_name:host_port:container_port (one per line)
311
- parse_docker_compose_ports() {
312
- local compose_file="$1"
313
- awk '
314
- /^[[:space:]]*[a-zA-Z_][a-zA-Z0-9_-]*:/ && !/^\s*-/ && !/ports:/ && !/image:/ && !/container_name:/ {
315
- # Top-level or nested service name
316
- indent = 0
317
- for (i = 1; i <= length($0); i++) {
318
- if (substr($0, i, 1) == " ") indent++
319
- else break
320
- }
321
- line = $0
322
- gsub(/^[[:space:]]+/, "", line)
323
- gsub(/:.*/, "", line)
324
- if (indent <= 4) current_service = line
325
- }
326
- /ports:/ {
327
- in_ports = 1
328
- next
329
- }
330
- in_ports && /^[[:space:]]*-[[:space:]]*"?[0-9]/ {
331
- line = $0
332
- gsub(/^[[:space:]]*-[[:space:]]*/, "", line)
333
- gsub(/"/, "", line)
334
- gsub(/[[:space:]].*/, "", line)
335
- # line is now like "5432:5432" or "8025:8025"
336
- split(line, parts, ":")
337
- if (length(parts) >= 2) {
338
- print current_service ":" parts[1] ":" parts[2]
339
- }
340
- next
341
- }
342
- in_ports && /^[[:space:]]*[^-[:space:]]/ {
343
- in_ports = 0
344
- }
345
- ' "$compose_file"
346
- }
347
-
348
- # Detect compose file path — checks [docker] file= directive, then auto-detects
349
- detect_compose_file() {
350
- local wt_path="$1"
351
- local compose_file=""
352
-
353
- # Check for file= directive in [docker] config
354
- for line in "${WT_DOCKER_LINES[@]}"; do
355
- if [[ "$line" =~ ^file=(.+) ]]; then
356
- local specified="${BASH_REMATCH[1]}"
357
- specified="${specified#"${specified%%[![:space:]]*}"}" # trim leading
358
- specified="${specified%"${specified##*[![:space:]]}"}" # trim trailing
359
- if [[ -f "$wt_path/$specified" ]]; then
360
- echo "$specified"
361
- return 0
362
- else
363
- warn "Specified compose file not found: $specified"
364
- fi
365
- fi
366
- done
367
-
368
- # Auto-detect in standard locations
369
- local candidates=(
370
- "local.yml"
371
- "docker-compose.local.yml"
372
- "docker-compose.yml"
373
- "docker-compose.yaml"
374
- "compose.yml"
375
- "compose.yaml"
376
- )
377
-
378
- # Check root first
379
- for candidate in "${candidates[@]}"; do
380
- if [[ -f "$wt_path/$candidate" ]]; then
381
- echo "$candidate"
382
- return 0
383
- fi
384
- done
385
-
386
- # Check nested app directories
387
- for subdir in apps services; do
388
- [[ -d "$wt_path/$subdir" ]] || continue
389
- for app_dir in "$wt_path/$subdir"/*/; do
390
- [[ -d "$app_dir" ]] || continue
391
- for candidate in "${candidates[@]}"; do
392
- if [[ -f "$app_dir$candidate" ]]; then
393
- local rel="${app_dir#"$wt_path"/}$candidate"
394
- echo "$rel"
395
- return 0
396
- fi
397
- done
398
- done
399
- done
400
-
401
- return 1
402
- }
403
-
404
- # Generate docker-compose.worktree.yml override with port offsets
405
- generate_docker_compose_override() {
406
- local compose_file="$1" # full path to original compose file
407
- local offset="$2"
408
- local override_dir
409
- override_dir=$(dirname "$compose_file")
410
- local override_file="$override_dir/docker-compose.worktree.yml"
411
-
412
- local ports_data
413
- ports_data=$(parse_docker_compose_ports "$compose_file")
414
-
415
- [[ -z "$ports_data" ]] && return 1
416
-
417
- local tmpfile
418
- tmpfile=$(mktemp)
419
-
420
- cat > "$tmpfile" << 'HEADER'
421
- # Auto-generated by wt — DO NOT EDIT
422
- # Port offsets for worktree isolation
423
- services:
424
- HEADER
425
-
426
- local current_service=""
427
- while IFS=: read -r service host_port container_port; do
428
- [[ -z "$service" ]] && continue
429
- local new_port=$((host_port + offset))
430
-
431
- if [[ "$service" != "$current_service" ]]; then
432
- printf ' %s:\n' "$service" >> "$tmpfile"
433
- printf ' ports:\n' >> "$tmpfile"
434
- current_service="$service"
435
- fi
436
- printf ' - "%d:%s"\n' "$new_port" "$container_port" >> "$tmpfile"
437
- done <<< "$ports_data"
438
-
439
- mv "$tmpfile" "$override_file"
440
- echo "$override_file"
441
- }
442
-
443
- # Orchestrate Docker isolation: detect, offset, generate override, update .env
444
- setup_docker_isolation() {
445
- local wt_path="$1"
446
- local branch="$2"
447
- local branch_slug="$3"
448
-
449
- # Check if docker section has 'auto' or any config
450
- local has_docker=false
451
- for line in "${WT_DOCKER_LINES[@]}"; do
452
- [[ "$line" == "auto" || "$line" =~ ^file= ]] && has_docker=true
453
- done
454
- [[ "$has_docker" == true ]] || return 0
455
-
456
- local compose_rel
457
- compose_rel=$(detect_compose_file "$wt_path") || {
458
- warn "No docker-compose file found, skipping Docker isolation"
459
- return 0
460
- }
461
-
462
- local compose_full="$wt_path/$compose_rel"
463
- local offset
464
- offset=$(branch_to_port_offset "$branch")
465
-
466
- # Check for custom port_offset
467
- for line in "${WT_DOCKER_LINES[@]}"; do
468
- if [[ "$line" =~ ^port_offset=([0-9]+) ]]; then
469
- offset="${BASH_REMATCH[1]}"
470
- fi
471
- done
472
-
473
- info "Docker isolation: $compose_rel (port offset +$offset)"
474
-
475
- local override_file
476
- override_file=$(generate_docker_compose_override "$compose_full" "$offset") || {
477
- warn "No ports found in compose file, skipping port override"
478
- return 0
479
- }
480
-
481
- local override_rel="${override_file#"$wt_path"/}"
482
- success "Generated $override_rel"
483
-
484
- # Set COMPOSE_FILE and COMPOSE_PROJECT_NAME in root .env
485
- local root_env="$wt_path/.env"
486
- local compose_file_val="${compose_rel}:${override_rel}"
487
- local project_name="${REPO_NAME}_${branch_slug}"
488
-
489
- # Append or update COMPOSE_FILE in .env
490
- if [[ -f "$root_env" ]]; then
491
- local tmpfile
492
- tmpfile=$(mktemp)
493
- local found_cf=false
494
- local found_cpn=false
495
- while IFS= read -r line || [[ -n "$line" ]]; do
496
- if [[ "$line" =~ ^COMPOSE_FILE= ]]; then
497
- printf 'COMPOSE_FILE=%s\n' "$compose_file_val"
498
- found_cf=true
499
- elif [[ "$line" =~ ^COMPOSE_PROJECT_NAME= ]]; then
500
- printf 'COMPOSE_PROJECT_NAME=%s\n' "$project_name"
501
- found_cpn=true
502
- else
503
- printf '%s\n' "$line"
504
- fi
505
- done < "$root_env" > "$tmpfile"
506
- [[ "$found_cf" == false ]] && printf 'COMPOSE_FILE=%s\n' "$compose_file_val" >> "$tmpfile"
507
- [[ "$found_cpn" == false ]] && printf 'COMPOSE_PROJECT_NAME=%s\n' "$project_name" >> "$tmpfile"
508
- mv "$tmpfile" "$root_env"
509
- else
510
- printf 'COMPOSE_FILE=%s\n' "$compose_file_val" > "$root_env"
511
- printf 'COMPOSE_PROJECT_NAME=%s\n' "$project_name" >> "$root_env"
512
- fi
513
-
514
- # Print port mapping summary
515
- local ports_data
516
- ports_data=$(parse_docker_compose_ports "$compose_full")
517
- if [[ -n "$ports_data" ]]; then
518
- echo ""
519
- echo -e " ${CYAN}Port mappings:${NC}"
520
- while IFS=: read -r service host_port container_port; do
521
- [[ -z "$service" ]] && continue
522
- local new_port=$((host_port + offset))
523
- echo -e " ${service}: ${host_port} → ${GREEN}${new_port}${NC}"
524
- done <<< "$ports_data"
525
- fi
526
- }
527
-
528
- # =============================================================================
529
- # Phase 4: Database Auto-Creation
530
- # =============================================================================
531
-
532
- # Detect Postgres — checks Docker containers first, then local
533
- detect_postgres() {
534
- # Check Docker containers for postgres
535
- if command -v docker &>/dev/null; then
536
- local pg_container
537
- pg_container=$(docker ps --filter "ancestor=postgres" --filter "status=running" --format '{{.Names}}' 2>/dev/null | head -1)
538
- if [[ -n "$pg_container" ]]; then
539
- echo "docker:$pg_container"
540
- return 0
541
- fi
542
- # Also check by port binding (for custom images)
543
- pg_container=$(docker ps --filter "publish=5432" --filter "status=running" --format '{{.Names}}' 2>/dev/null | head -1)
544
- if [[ -n "$pg_container" ]]; then
545
- echo "docker:$pg_container"
546
- return 0
547
- fi
548
- fi
549
-
550
- # Check local postgres
551
- if command -v pg_isready &>/dev/null && pg_isready -q 2>/dev/null; then
552
- echo "local"
553
- return 0
554
- fi
555
-
556
- return 1
557
- }
558
-
559
- # Create database if it doesn't exist (idempotent)
560
- create_database_if_not_exists() {
561
- local db_name="$1"
562
- local pg_source="$2" # "docker:container_name" or "local"
563
-
564
- local exists_query="SELECT 1 FROM pg_database WHERE datname='$db_name'"
565
- local create_query="CREATE DATABASE \"$db_name\""
566
- local result=""
567
-
568
- if [[ "$pg_source" == local ]]; then
569
- result=$(psql -tAc "$exists_query" postgres 2>/dev/null || true)
570
- if [[ "$result" != "1" ]]; then
571
- psql -c "$create_query" postgres 2>/dev/null && return 0 || return 1
572
- fi
573
- elif [[ "$pg_source" == docker:* ]]; then
574
- local container="${pg_source#docker:}"
575
- result=$(docker exec "$container" psql -U postgres -tAc "$exists_query" postgres 2>/dev/null || true)
576
- if [[ "$result" != "1" ]]; then
577
- docker exec "$container" psql -U postgres -c "$create_query" postgres 2>/dev/null && return 0 || return 1
578
- fi
579
- fi
580
-
581
- return 0 # Already exists
582
- }
583
-
584
- # Read rewritten DB name from .env files and auto-create databases
585
- auto_create_databases() {
586
- local wt_path="$1"
587
- local pg_source
588
-
589
- pg_source=$(detect_postgres) || {
590
- warn "Postgres not running, skipping database auto-creation"
591
- return 0
592
- }
593
-
594
- # Collect unique database names from all .env files
595
- local db_names=()
596
- while IFS= read -r -d '' env_file; do
597
- while IFS= read -r line; do
598
- if [[ "$line" =~ ^(DB_NAME|POSTGRES_DB|MYSQL_DATABASE|DATABASE_NAME)=(.+) ]]; then
599
- local val="${BASH_REMATCH[2]}"
600
- val="${val#\"}" ; val="${val%\"}"
601
- val="${val#\'}" ; val="${val%\'}"
602
- # Only add if not already in array
603
- local found=false
604
- for existing in "${db_names[@]+"${db_names[@]}"}"; do
605
- [[ "$existing" == "$val" ]] && found=true
606
- done
607
- [[ "$found" == false ]] && db_names+=("$val")
608
- elif [[ "$line" =~ ^(DATABASE_URL|POSTGRES_URL)=(.+) ]]; then
609
- local url="${BASH_REMATCH[2]}"
610
- url="${url#\"}" ; url="${url%\"}"
611
- # Extract db name from URL: scheme://user:pass@host:port/dbname
612
- if [[ "$url" =~ ://[^/]*/([^?]+) ]]; then
613
- local val="${BASH_REMATCH[1]}"
614
- local found=false
615
- for existing in "${db_names[@]+"${db_names[@]}"}"; do
616
- [[ "$existing" == "$val" ]] && found=true
617
- done
618
- [[ "$found" == false ]] && db_names+=("$val")
619
- fi
620
- fi
621
- done < "$env_file"
622
- done < <(find "$wt_path" -maxdepth 3 -name '.env*' -not -path '*/node_modules/*' -not -path '*/.git/*' -print0 2>/dev/null)
623
-
624
- for db_name in "${db_names[@]+"${db_names[@]}"}"; do
625
- if create_database_if_not_exists "$db_name" "$pg_source"; then
626
- success "Database ready: $db_name"
627
- else
628
- warn "Could not create database: $db_name"
629
- fi
630
- done
631
- }
632
-
633
- # =============================================================================
634
- # Commands
635
- # =============================================================================
636
-
637
- cmd_create() {
638
- local branch="${1:-}"
639
-
640
- if [[ -z "$branch" ]]; then
641
- error "Usage: wt create <branch-name>"
642
- echo " Creates a new worktree for the given branch"
643
- exit 1
644
- fi
645
-
646
- get_repo_info
647
- local wt_path=$(get_worktree_path "$branch")
648
-
649
- if [[ -d "$wt_path" ]]; then
650
- error "Worktree already exists: $wt_path"
651
- exit 1
652
- fi
653
-
654
- # --- Step 1: Create git worktree ---
655
- info "Creating worktree for branch: $branch"
656
-
657
- if git show-ref --verify --quiet "refs/heads/$branch" 2>/dev/null; then
658
- info "Checking out existing local branch: $branch"
659
- git worktree add "$wt_path" "$branch"
660
- elif git show-ref --verify --quiet "refs/remotes/origin/$branch" 2>/dev/null; then
661
- info "Checking out existing remote branch: origin/$branch"
662
- git worktree add "$wt_path" "$branch"
663
- else
664
- info "Creating new branch: $branch"
665
- git worktree add -b "$branch" "$wt_path"
666
- fi
667
- success "Worktree created at: $wt_path"
668
-
669
- # --- Step 2: Load .worktreeinclude ---
670
- generate_default_worktreeinclude
671
- parse_worktreeinclude
672
-
673
- # --- Step 3: Copy files matching patterns ---
674
- if [[ ${#WT_FILE_PATTERNS[@]} -gt 0 ]]; then
675
- info "Copying files from .worktreeinclude..."
676
- match_files_by_patterns "$REPO_ROOT"
677
- local file_count
678
- file_count=$(copy_matched_files "$REPO_ROOT" "$wt_path")
679
- if [[ "$file_count" -gt 0 ]]; then
680
- success "Copied $file_count file(s)"
681
- else
682
- warn "No matching files found to copy"
683
- fi
684
- fi
685
-
686
- # --- Step 4: Rewrite env vars ---
687
- local branch_slug
688
- branch_slug=$(sanitize_branch_for_suffix "$branch")
689
-
690
- if [[ ${#WT_REWRITE_LINES[@]} -gt 0 ]]; then
691
- info "Rewriting env vars (suffix: _$branch_slug)..."
692
- local rewrite_count
693
- rewrite_count=$(rewrite_all_env_files "$wt_path" "$branch_slug")
694
- if [[ "$rewrite_count" -gt 0 ]]; then
695
- success "Rewrote $rewrite_count .env file(s)"
696
- fi
697
- fi
698
-
699
- # --- Step 5: Docker Compose isolation ---
700
- if [[ ${#WT_DOCKER_LINES[@]} -gt 0 ]]; then
701
- setup_docker_isolation "$wt_path" "$branch" "$branch_slug"
702
- fi
703
-
704
- # --- Step 6: Auto-create database ---
705
- if [[ ${#WT_REWRITE_LINES[@]} -gt 0 ]]; then
706
- auto_create_databases "$wt_path"
707
- fi
708
-
709
- # --- Step 7: Install dependencies ---
710
- local pkg_mgr=$(detect_pkg_manager "$wt_path")
711
- if [[ -n "$pkg_mgr" ]]; then
712
- info "Detected package manager: $pkg_mgr"
713
-
714
- if is_monorepo "$wt_path"; then
715
- info "Monorepo detected, installing at root..."
716
- fi
717
-
718
- info "Running $pkg_mgr install..."
719
- cd "$wt_path"
720
- case "$pkg_mgr" in
721
- bun) bun install ;;
722
- pnpm) pnpm install ;;
723
- yarn) yarn install ;;
724
- npm) npm install ;;
725
- esac
726
- success "Dependencies installed"
727
- fi
728
-
729
- # --- Step 8: Summary ---
730
- echo ""
731
- success "Worktree ready!"
732
- echo ""
733
- echo -e "${GREEN}cd ${wt_path}${NC}"
734
- }
735
-
736
- cmd_list() {
737
- get_repo_info
738
-
739
- info "Worktrees for: $REPO_NAME"
740
- echo ""
741
-
742
- local found=0
743
- while IFS= read -r line; do
744
- local wt_path=$(echo "$line" | awk '{print $1}')
745
- local wt_branch=$(echo "$line" | awk '{print $3}' | sed 's/\[//;s/\]//')
746
-
747
- if [[ "$wt_path" == "$REPO_ROOT" ]]; then
748
- echo -e " ${BLUE}●${NC} $wt_path ${GREEN}(main)${NC}"
749
- else
750
- local branch_display=$(get_branch_from_worktree "$wt_path")
751
- echo -e " ${YELLOW}○${NC} $wt_path ${YELLOW}($branch_display)${NC}"
752
- fi
753
- ((found++))
754
- done < <(git worktree list --porcelain | grep "^worktree " | sed 's/^worktree //')
755
-
756
- if [[ "$found" -eq 0 ]]; then
757
- warn "No worktrees found"
758
- fi
759
- echo ""
760
- }
761
-
762
- cmd_remove() {
763
- local branch="${1:-}"
764
- local delete_branch=false
765
-
766
- if [[ "${2:-}" == "--delete-branch" ]] || [[ "${2:-}" == "-d" ]]; then
767
- delete_branch=true
768
- fi
769
-
770
- if [[ -z "$branch" ]]; then
771
- error "Usage: wt remove <branch-name> [--delete-branch|-d]"
772
- echo " Removes the worktree for the given branch"
773
- echo " --delete-branch, -d: Also delete the git branch"
774
- exit 1
775
- fi
776
-
777
- get_repo_info
778
- local wt_path=$(get_worktree_path "$branch")
779
-
780
- if [[ ! -d "$wt_path" ]]; then
781
- error "Worktree not found: $wt_path"
782
- exit 1
783
- fi
784
-
785
- # Stop Docker containers if compose setup exists
786
- if [[ -f "$wt_path/.env" ]]; then
787
- local compose_file_val=""
788
- while IFS= read -r line; do
789
- [[ "$line" =~ ^COMPOSE_FILE=(.+) ]] && compose_file_val="${BASH_REMATCH[1]}"
790
- done < "$wt_path/.env"
791
-
792
- if [[ -n "$compose_file_val" ]] && command -v docker &>/dev/null; then
793
- info "Stopping Docker containers..."
794
- (cd "$wt_path" && docker compose down 2>/dev/null) || warn "Could not stop containers (may not be running)"
795
- fi
796
- fi
797
-
798
- info "Removing worktree: $wt_path"
799
- git worktree remove "$wt_path" --force
800
- success "Worktree removed"
801
- info "Note: databases are preserved — drop manually if no longer needed"
802
-
803
- if [[ "$delete_branch" == true ]]; then
804
- info "Deleting branch: $branch"
805
- git branch -D "$branch" 2>/dev/null || warn "Branch not found or already deleted"
806
- success "Branch deleted"
807
- fi
808
- }
809
-
810
- cmd_open() {
811
- local branch=""
812
- local editor=""
813
-
814
- while [[ $# -gt 0 ]]; do
815
- case "$1" in
816
- --cursor|-c)
817
- editor="cursor"
818
- shift
819
- ;;
820
- --agy|-a)
821
- editor="agy"
822
- shift
823
- ;;
824
- --code|-v)
825
- editor="code"
826
- shift
827
- ;;
828
- -*)
829
- error "Unknown option: $1"
830
- exit 1
831
- ;;
832
- *)
833
- branch="$1"
834
- shift
835
- ;;
836
- esac
837
- done
838
-
839
- if [[ -z "$branch" ]]; then
840
- error "Usage: wt open <branch-name> [--cursor|-c] [--agy|-a] [--code|-v]"
841
- echo " Opens the worktree in specified editor (auto-detects if not specified)"
842
- exit 1
843
- fi
844
-
845
- get_repo_info
846
- local wt_path=$(get_worktree_path "$branch")
847
-
848
- if [[ ! -d "$wt_path" ]]; then
849
- error "Worktree not found: $wt_path"
850
- echo " Run 'wt create $branch' first"
851
- exit 1
852
- fi
853
-
854
- if [[ -z "$editor" && -n "${WT_EDITOR:-}" ]]; then
855
- editor="$WT_EDITOR"
856
- fi
857
-
858
- if [[ -n "$editor" ]]; then
859
- if ! command -v "$editor" &>/dev/null; then
860
- error "$editor not found in PATH"
861
- exit 1
862
- fi
863
- info "Opening in $editor: $wt_path"
864
- "$editor" "$wt_path"
865
- else
866
- if command -v cursor &>/dev/null; then
867
- info "Opening in Cursor: $wt_path"
868
- cursor "$wt_path"
869
- elif command -v agy &>/dev/null; then
870
- info "Opening in Antigravity: $wt_path"
871
- agy "$wt_path"
872
- elif command -v code &>/dev/null; then
873
- info "Opening in VS Code: $wt_path"
874
- code "$wt_path"
875
- else
876
- error "No supported editor found (cursor, agy, code)"
877
- exit 1
878
- fi
879
- fi
880
- success "Opened!"
881
- }
882
-
883
- cmd_help() {
884
- echo "wt - Git Worktree Manager"
885
- echo ""
886
- echo "Usage: wt <command> [args]"
887
- echo ""
888
- echo "Commands:"
889
- echo " create, c <branch> Create worktree with isolation (env, docker, db)"
890
- echo " list, ls List all worktrees for current repo"
891
- echo " remove, rm <branch> Remove worktree (add -d to delete branch too)"
892
- echo " open, o <branch> Open worktree (flag > WT_EDITOR > auto-detect)"
893
- echo " Options: --cursor|-c, --agy|-a, --code|-v"
894
- echo " help, -h, --help Show this help"
895
- echo ""
896
- echo "Environment:"
897
- echo " WT_EDITOR Default editor (cursor, agy, code)"
898
- echo ""
899
- echo "Isolation (.worktreeinclude):"
900
- echo " Auto-generated on first 'wt create' if missing."
901
- echo " Controls which files to copy and how to isolate services."
902
- echo ""
903
- echo " Sections:"
904
- echo " (top) Glob patterns for files to copy (e.g., .env*)"
905
- echo " [rewrite] 'auto' to suffix DB_NAME, DATABASE_URL, etc."
906
- echo " Use {{BRANCH}} for custom templates"
907
- echo " [docker] 'auto' to generate port-offset override"
908
- echo " 'file=path' for custom compose file"
909
- echo ""
910
- echo "Naming convention:"
911
- echo " ~/code/project/ → main repo"
912
- echo " ~/code/project--feat-auth/ → worktree for feat-auth branch"
913
- echo ""
914
- echo "Examples:"
915
- echo " wt create feature/authentication"
916
- echo " wt c feat-payments"
917
- echo " wt ls"
918
- echo " wt rm feature/authentication"
919
- echo " wt rm feat-payments -d"
920
- echo " wt o feat-payments"
921
- echo " wt o feat-payments --agy"
922
- echo " WT_EDITOR=agy wt o feat-payments"
923
- }
924
-
925
- # =============================================================================
926
- # Main
927
- # =============================================================================
928
-
929
- main() {
930
- local cmd="${1:-help}"
931
- shift || true
932
-
933
- case "$cmd" in
934
- create|c)
935
- cmd_create "$@"
936
- ;;
937
- list|ls)
938
- cmd_list "$@"
939
- ;;
940
- remove|rm)
941
- cmd_remove "$@"
942
- ;;
943
- open|o)
944
- cmd_open "$@"
945
- ;;
946
- help|-h|--help)
947
- cmd_help
948
- ;;
949
- *)
950
- error "Unknown command: $cmd"
951
- cmd_help
952
- exit 1
953
- ;;
954
- esac
955
- }
956
-
957
- main "$@"