@tokenbooks/wt 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +395 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +71 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/commands/doctor.d.ts +7 -0
  7. package/dist/commands/doctor.js +153 -0
  8. package/dist/commands/doctor.js.map +1 -0
  9. package/dist/commands/list.d.ts +6 -0
  10. package/dist/commands/list.js +15 -0
  11. package/dist/commands/list.js.map +1 -0
  12. package/dist/commands/new.d.ts +8 -0
  13. package/dist/commands/new.js +160 -0
  14. package/dist/commands/new.js.map +1 -0
  15. package/dist/commands/remove.d.ts +7 -0
  16. package/dist/commands/remove.js +133 -0
  17. package/dist/commands/remove.js.map +1 -0
  18. package/dist/commands/setup.d.ts +10 -0
  19. package/dist/commands/setup.js +157 -0
  20. package/dist/commands/setup.js.map +1 -0
  21. package/dist/core/database.d.ts +11 -0
  22. package/dist/core/database.js +69 -0
  23. package/dist/core/database.js.map +1 -0
  24. package/dist/core/env-patcher.d.ts +11 -0
  25. package/dist/core/env-patcher.js +133 -0
  26. package/dist/core/env-patcher.js.map +1 -0
  27. package/dist/core/git.d.ts +19 -0
  28. package/dist/core/git.js +102 -0
  29. package/dist/core/git.js.map +1 -0
  30. package/dist/core/registry.d.ts +20 -0
  31. package/dist/core/registry.js +103 -0
  32. package/dist/core/registry.js.map +1 -0
  33. package/dist/core/slot-allocator.d.ts +17 -0
  34. package/dist/core/slot-allocator.js +37 -0
  35. package/dist/core/slot-allocator.js.map +1 -0
  36. package/dist/output.d.ts +11 -0
  37. package/dist/output.js +91 -0
  38. package/dist/output.js.map +1 -0
  39. package/dist/schemas/config.schema.d.ts +57 -0
  40. package/dist/schemas/config.schema.js +33 -0
  41. package/dist/schemas/config.schema.js.map +1 -0
  42. package/dist/schemas/registry.schema.d.ts +22 -0
  43. package/dist/schemas/registry.schema.js +19 -0
  44. package/dist/schemas/registry.schema.js.map +1 -0
  45. package/dist/types.d.ts +26 -0
  46. package/dist/types.js +3 -0
  47. package/dist/types.js.map +1 -0
  48. package/package.json +44 -0
  49. package/skills/wt/SKILL.md +211 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ledgertech Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,395 @@
1
+ # wt — Git Worktree Environment Isolation
2
+
3
+ A CLI tool that gives each git worktree its own Postgres database, Redis database index, ports, and `.env` files. Prevents worktrees from corrupting each other's data.
4
+
5
+ ## The Problem
6
+
7
+ When you use `git worktree add` for parallel development, all worktrees share the same database, Redis instance, and ports. This means:
8
+
9
+ - Schema migrations in one worktree break another
10
+ - BullMQ/Redis queues collide across worktrees
11
+ - Two dev servers can't run simultaneously on the same port
12
+ - `.env` files point to the same resources everywhere
13
+
14
+ `wt` solves this by assigning each worktree an isolated **slot** (1–15) that determines its database name, Redis DB index, and port range.
15
+
16
+ ## How It Works
17
+
18
+ Each worktree gets a numbered slot. The slot determines everything:
19
+
20
+ | Resource | Formula | Slot 0 (main) | Slot 1 | Slot 2 | Slot 3 |
21
+ |----------|---------|:-:|:-:|:-:|:-:|
22
+ | Database | `{baseName}_wt{slot}` | `mydb` | `mydb_wt1` | `mydb_wt2` | `mydb_wt3` |
23
+ | Redis DB | `slot` | `/0` | `/1` | `/2` | `/3` |
24
+ | Ports | `slot * stride + defaultPort` | 3000, 3001 | 3100, 3101 | 3200, 3201 | 3300, 3301 |
25
+
26
+ - **Database**: Created via `CREATE DATABASE ... TEMPLATE` (fast filesystem copy, not dump/restore)
27
+ - **Redis**: Uses a different DB index per worktree (Redis supports 0–15)
28
+ - **Ports**: Offset by `portStride` (default 100) per slot
29
+ - **Env files**: Copied from main worktree and patched with the slot's values
30
+
31
+ ## Quick Start
32
+
33
+ ### 1. Install
34
+
35
+ **Global** (available in all repos):
36
+
37
+ ```bash
38
+ pnpm add -g @tokenbooks/wt
39
+ ```
40
+
41
+ **Per-project** (recommended for teams — version-locked in package.json):
42
+
43
+ ```bash
44
+ pnpm add -D @tokenbooks/wt
45
+ ```
46
+
47
+ ### 2. Create `wt.config.json`
48
+
49
+ Create this file in your repository root and commit it. See [Configuration Reference](#configuration-reference) for full details.
50
+
51
+ ```json
52
+ {
53
+ "baseDatabaseName": "myapp",
54
+ "baseWorktreePath": ".worktrees",
55
+ "portStride": 100,
56
+ "maxSlots": 15,
57
+ "services": [
58
+ { "name": "web", "defaultPort": 3000 },
59
+ { "name": "api", "defaultPort": 4000 }
60
+ ],
61
+ "envFiles": [
62
+ {
63
+ "source": ".env",
64
+ "patches": [
65
+ { "var": "DATABASE_URL", "type": "database" }
66
+ ]
67
+ },
68
+ {
69
+ "source": "backend/.env",
70
+ "patches": [
71
+ { "var": "DATABASE_URL", "type": "database" },
72
+ { "var": "REDIS_URL", "type": "redis" },
73
+ { "var": "PORT", "type": "port", "service": "api" }
74
+ ]
75
+ },
76
+ {
77
+ "source": "frontend/.env",
78
+ "patches": [
79
+ { "var": "PORT", "type": "port", "service": "web" },
80
+ { "var": "API_URL", "type": "url", "service": "api" }
81
+ ]
82
+ }
83
+ ],
84
+ "postSetup": ["npm install"],
85
+ "autoInstall": true
86
+ }
87
+ ```
88
+
89
+ ### 3. Add `.worktree-registry.json` to `.gitignore`
90
+
91
+ ```bash
92
+ echo ".worktree-registry.json" >> .gitignore
93
+ ```
94
+
95
+ ### 4. Use it
96
+
97
+ ```bash
98
+ # Create a worktree with full isolation
99
+ wt new feat/my-feature
100
+
101
+ # List all worktree allocations
102
+ wt list
103
+
104
+ # Check health
105
+ wt doctor
106
+
107
+ # Clean up
108
+ wt remove feat-my-feature
109
+ ```
110
+
111
+ ### 5. Claude Code skill (optional)
112
+
113
+ The package ships with a `/wt` skill for [Claude Code](https://docs.anthropic.com/en/docs/claude-code). To enable it, symlink from your project:
114
+
115
+ ```bash
116
+ mkdir -p .claude/skills
117
+ ln -s ../../node_modules/@tokenbooks/wt/skills/wt/SKILL.md .claude/skills/wt.md
118
+ ```
119
+
120
+ Then use `/wt init`, `/wt new feat/foo`, `/wt doctor`, etc. inside Claude Code.
121
+
122
+ ## Commands
123
+
124
+ ### `wt new <branch> [--slot N] [--no-install] [--json]`
125
+
126
+ Creates a new git worktree and sets up its isolated environment:
127
+
128
+ 1. Allocates the next available slot (or uses `--slot N`)
129
+ 2. Runs `git worktree add .worktrees/<slug> -b <branch>`
130
+ 3. Creates a new Postgres database from the main DB as template
131
+ 4. Copies all configured `.env` files, patching each with slot-specific values
132
+ 5. Runs `postSetup` commands (unless `--no-install`)
133
+
134
+ ### `wt setup [path] [--no-install] [--json]`
135
+
136
+ Sets up an existing worktree that was created manually or by another tool. Useful when:
137
+
138
+ - You ran `git worktree add` directly
139
+ - A worktree's env files need regenerating
140
+ - Called automatically by the `post-checkout` hook
141
+
142
+ If the worktree already has a slot allocation, it reuses it.
143
+
144
+ ### `wt remove <path-or-slot> [--keep-db] [--json]`
145
+
146
+ Removes a worktree and cleans up its resources:
147
+
148
+ 1. Drops the worktree's Postgres database (unless `--keep-db`)
149
+ 2. Runs `git worktree remove`
150
+ 3. Removes the allocation from the registry
151
+
152
+ Accepts either a path (`.worktrees/feat-my-feature`) or a slot number (`3`).
153
+
154
+ ### `wt list [--json]`
155
+
156
+ Shows all worktree allocations with their slot, branch, database, Redis DB, ports, and status (ok/stale).
157
+
158
+ ### `wt doctor [--fix] [--json]`
159
+
160
+ Runs diagnostics:
161
+
162
+ - **Stale entries**: Registry points to a path that no longer exists
163
+ - **Missing databases**: Allocated DB doesn't exist in Postgres
164
+ - **Missing env files**: Expected env files not found in worktree
165
+ - **Orphaned databases**: `{baseName}_wt*` databases not in the registry
166
+
167
+ Use `--fix` to auto-repair stale entries and drop orphaned databases.
168
+
169
+ ### JSON output
170
+
171
+ All commands support `--json` for machine-readable output:
172
+
173
+ ```json
174
+ {
175
+ "success": true,
176
+ "data": { ... }
177
+ }
178
+ ```
179
+
180
+ Or on error:
181
+
182
+ ```json
183
+ {
184
+ "success": false,
185
+ "error": { "code": "NO_SLOTS", "message": "All 15 slots are occupied." }
186
+ }
187
+ ```
188
+
189
+ ## Configuration Reference
190
+
191
+ ### `wt.config.json`
192
+
193
+ This file lives in your repository root and is committed to version control.
194
+
195
+ ```typescript
196
+ {
197
+ // Required: name of your main Postgres database
198
+ "baseDatabaseName": string,
199
+
200
+ // Directory for worktrees, relative to repo root (default: ".worktrees")
201
+ "baseWorktreePath": string,
202
+
203
+ // Port offset per slot (default: 100)
204
+ "portStride": number,
205
+
206
+ // Maximum number of concurrent worktrees (default: 15, max: 15)
207
+ "maxSlots": number,
208
+
209
+ // Services that need port allocation
210
+ "services": [
211
+ { "name": string, "defaultPort": number }
212
+ ],
213
+
214
+ // Env files to copy and patch for each worktree
215
+ "envFiles": [
216
+ {
217
+ "source": string, // Path relative to repo root
218
+ "patches": [
219
+ {
220
+ "var": string, // Env var name to patch
221
+ "type": string, // "database" | "redis" | "port" | "url"
222
+ "service": string // Required for "port" and "url" types
223
+ }
224
+ ]
225
+ }
226
+ ],
227
+
228
+ // Commands to run in the worktree after env setup (default: [])
229
+ "postSetup": string[],
230
+
231
+ // Whether to run postSetup automatically (default: true)
232
+ "autoInstall": boolean
233
+ }
234
+ ```
235
+
236
+ ### Patch Types
237
+
238
+ | Type | What it patches | Input | Output (slot 3) |
239
+ |------|----------------|-------|------------------|
240
+ | `database` | Replaces DB name in a Postgres URL | `postgresql://u:p@host:5432/myapp?schema=public` | `postgresql://u:p@host:5432/myapp_wt3?schema=public` |
241
+ | `redis` | Replaces or appends DB index in a Redis URL | `redis://:pass@host:6379/0` | `redis://:pass@host:6379/3` |
242
+ | `port` | Replaces the entire value with the allocated port | `4000` | `4300` |
243
+ | `url` | Replaces the port number inside a URL | `http://localhost:4000/api` | `http://localhost:4300/api` |
244
+
245
+ The `port` and `url` types require a `service` field that matches a name in `services`.
246
+
247
+ ### `.worktree-registry.json`
248
+
249
+ Auto-managed file at the repo root. **Add to `.gitignore`** — it's machine-local.
250
+
251
+ ```json
252
+ {
253
+ "version": 1,
254
+ "allocations": {
255
+ "1": {
256
+ "worktreePath": "/absolute/path/to/.worktrees/feat-auth",
257
+ "branchName": "feat/auth",
258
+ "dbName": "myapp_wt1",
259
+ "redisDb": 1,
260
+ "ports": { "web": 3100, "api": 4100 },
261
+ "createdAt": "2026-02-17T14:30:00Z"
262
+ }
263
+ }
264
+ }
265
+ ```
266
+
267
+ ## Git Hook: Automatic Setup
268
+
269
+ Add a `post-checkout` hook so `wt setup` runs automatically when switching branches inside a worktree. With [Husky](https://typicode.github.io/husky/):
270
+
271
+ Create `.husky/post-checkout`:
272
+
273
+ ```bash
274
+ #!/bin/bash
275
+
276
+ prev_head="$1"
277
+ new_head="$2"
278
+ is_branch="$3"
279
+
280
+ # Only run on branch checkout (not file checkout)
281
+ [ "$is_branch" = "0" ] && exit 0
282
+
283
+ # Detect if we're in a worktree (not main)
284
+ git_common=$(git rev-parse --git-common-dir 2>/dev/null)
285
+ git_dir=$(git rev-parse --git-dir 2>/dev/null)
286
+ [ "$git_common" = "$git_dir" ] && exit 0
287
+
288
+ # Resolve main worktree path
289
+ main_worktree=$(cd "$git_common/.." && pwd -P)
290
+
291
+ # Find wt: local install first, then global
292
+ wt_bin="$main_worktree/node_modules/.bin/wt"
293
+ if [ ! -f "$wt_bin" ]; then
294
+ wt_bin=$(command -v wt 2>/dev/null || true)
295
+ fi
296
+
297
+ if [ -z "$wt_bin" ]; then
298
+ echo "Warning: wt CLI not found. Install globally (pnpm add -g @tokenbooks/wt) or locally (pnpm add -D @tokenbooks/wt)."
299
+ exit 0
300
+ fi
301
+
302
+ echo "Setting up worktree environment..."
303
+ "$wt_bin" setup "$(pwd -P)" --json 2>/dev/null && echo "Worktree ready!" || {
304
+ echo "Warning: Auto-setup failed. Run 'wt setup' manually."
305
+ exit 0
306
+ }
307
+ ```
308
+
309
+ The hook checks for a local install first (`node_modules/.bin/wt`), then falls back to a global `wt` on PATH.
310
+
311
+ ## Setup Guide for LLM Agents
312
+
313
+ If you are an LLM agent setting up `wt` for a repository, follow these steps:
314
+
315
+ ### Step 1: Discover the project structure
316
+
317
+ Identify these from the repository:
318
+
319
+ - **Database URL format**: Search `.env` files for `DATABASE_URL`. Extract the database name (the path segment after the port, before `?`).
320
+ - **Redis URL format**: Search for `REDIS_URL`. Note whether it ends with `/0` or has no DB index.
321
+ - **Services and ports**: Find all dev server commands and their default ports. Check `package.json` scripts, `docker-compose.yml`, and framework configs.
322
+ - **Env files**: List all `.env` files (not `.env.example`). These are the files that need patching.
323
+
324
+ ### Step 2: Map env vars to patch types
325
+
326
+ For each `.env` file, identify which variables need patching:
327
+
328
+ | If the variable contains... | Use patch type |
329
+ |----------------------------|----------------|
330
+ | A Postgres connection URL (`postgresql://...`) | `database` |
331
+ | A Redis connection URL (`redis://...`) | `redis` |
332
+ | Just a port number (`3000`) | `port` + service name |
333
+ | A URL with a port (`http://localhost:3000/...`) | `url` + service name |
334
+
335
+ Variables that don't match any pattern (API keys, secrets, feature flags) should NOT be patched.
336
+
337
+ ### Step 3: Build `wt.config.json`
338
+
339
+ Using the discovered information, construct the config:
340
+
341
+ ```
342
+ 1. baseDatabaseName = the DB name from the main DATABASE_URL
343
+ 2. services = each dev server as { name, defaultPort }
344
+ 3. envFiles = each .env file with its patches
345
+ 4. postSetup = the install command for the package manager (npm install, pnpm install, etc.)
346
+ ```
347
+
348
+ Validate that:
349
+ - Every `port` and `url` patch has a `service` that exists in `services`
350
+ - The `portStride` (default 100) doesn't cause port collisions with other local services
351
+ - `maxSlots * portStride` doesn't push ports into reserved ranges (e.g., above 65535)
352
+
353
+ ### Step 4: Install and test
354
+
355
+ ```bash
356
+ # Install wt
357
+ pnpm add -D @tokenbooks/wt
358
+
359
+ # Add to .gitignore
360
+ echo ".worktree-registry.json" >> .gitignore
361
+
362
+ # Verify
363
+ wt list # Should show "No worktree allocations found."
364
+ wt doctor # Should show "All checks passed."
365
+
366
+ # Smoke test (creates a real worktree + database)
367
+ wt new test/wt-smoke --no-install
368
+ wt list # Should show the new allocation
369
+ wt remove test-wt-smoke
370
+ wt list # Should be empty again
371
+ ```
372
+
373
+ ### Step 5: Add convenience scripts (optional)
374
+
375
+ ```json
376
+ {
377
+ "scripts": {
378
+ "wt": "wt",
379
+ "wt:new": "wt new",
380
+ "wt:list": "wt list",
381
+ "wt:doctor": "wt doctor"
382
+ }
383
+ }
384
+ ```
385
+
386
+ ## Requirements
387
+
388
+ - Node.js >= 20.19.0
389
+ - PostgreSQL (running, accessible via `DATABASE_URL` in root `.env`)
390
+ - Redis (if using `redis` patch type)
391
+ - Git (for worktree operations)
392
+
393
+ ## License
394
+
395
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const commander_1 = require("commander");
5
+ const new_1 = require("./commands/new");
6
+ const setup_1 = require("./commands/setup");
7
+ const remove_1 = require("./commands/remove");
8
+ const list_1 = require("./commands/list");
9
+ const doctor_1 = require("./commands/doctor");
10
+ const git_1 = require("./core/git");
11
+ const program = new commander_1.Command();
12
+ program
13
+ .name('wt')
14
+ .description('Git worktree environment isolation CLI')
15
+ .version('0.1.0');
16
+ program
17
+ .command('new')
18
+ .description('Create a new worktree with isolated environment')
19
+ .argument('<branch>', 'Branch name to create or checkout')
20
+ .option('--slot <n>', 'Force a specific slot number')
21
+ .option('--no-install', 'Skip post-setup commands')
22
+ .option('--json', 'Output as JSON', false)
23
+ .action(async (branch, opts) => {
24
+ await (0, new_1.newCommand)(branch, {
25
+ json: opts.json,
26
+ install: opts.install,
27
+ slot: opts.slot,
28
+ });
29
+ });
30
+ program
31
+ .command('setup')
32
+ .description('Set up environment for an existing worktree')
33
+ .argument('[path]', 'Worktree path (default: current directory)')
34
+ .option('--no-install', 'Skip post-setup commands')
35
+ .option('--json', 'Output as JSON', false)
36
+ .action(async (targetPath, opts) => {
37
+ await (0, setup_1.setupCommand)(targetPath, {
38
+ json: opts.json,
39
+ install: opts.install,
40
+ });
41
+ });
42
+ program
43
+ .command('remove')
44
+ .description('Remove a worktree and clean up its resources')
45
+ .argument('<path-or-slot>', 'Worktree path or slot number')
46
+ .option('--keep-db', 'Keep the database (do not drop)', false)
47
+ .option('--json', 'Output as JSON', false)
48
+ .action(async (pathOrSlot, opts) => {
49
+ await (0, remove_1.removeCommand)(pathOrSlot, {
50
+ json: opts.json,
51
+ keepDb: opts.keepDb,
52
+ });
53
+ });
54
+ program
55
+ .command('list')
56
+ .description('List all worktree allocations')
57
+ .option('--json', 'Output as JSON', false)
58
+ .action((opts) => {
59
+ const repoRoot = (0, git_1.getMainWorktreePath)();
60
+ (0, list_1.listCommand)(repoRoot, { json: opts.json });
61
+ });
62
+ program
63
+ .command('doctor')
64
+ .description('Diagnose and fix worktree environment issues')
65
+ .option('--fix', 'Auto-repair stale entries and orphaned databases', false)
66
+ .option('--json', 'Output as JSON', false)
67
+ .action(async (opts) => {
68
+ await (0, doctor_1.doctorCommand)({ json: opts.json, fix: opts.fix });
69
+ });
70
+ program.parse();
71
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ interface DoctorOptions {
2
+ readonly json: boolean;
3
+ readonly fix: boolean;
4
+ }
5
+ /** Run diagnostics on the worktree registry and databases */
6
+ export declare function doctorCommand(options: DoctorOptions): Promise<void>;
7
+ export {};
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.doctorCommand = doctorCommand;
37
+ const fs = __importStar(require("node:fs"));
38
+ const path = __importStar(require("node:path"));
39
+ const registry_1 = require("../core/registry");
40
+ const database_1 = require("../core/database");
41
+ const git_1 = require("../core/git");
42
+ const setup_1 = require("./setup");
43
+ const output_1 = require("../output");
44
+ /** Read DATABASE_URL from the main worktree's .env file */
45
+ function readDatabaseUrl(mainRoot) {
46
+ const envPath = path.join(mainRoot, '.env');
47
+ const content = fs.readFileSync(envPath, 'utf-8');
48
+ const match = content.match(/^DATABASE_URL=["']?([^"'\n]+)/m);
49
+ if (!match?.[1]) {
50
+ throw new Error('DATABASE_URL not found in .env');
51
+ }
52
+ return match[1];
53
+ }
54
+ /** Run diagnostics on the worktree registry and databases */
55
+ async function doctorCommand(options) {
56
+ try {
57
+ const mainRoot = (0, git_1.getMainWorktreePath)();
58
+ const config = (0, setup_1.loadConfig)(mainRoot);
59
+ let registry = (0, registry_1.readRegistry)(mainRoot);
60
+ const databaseUrl = readDatabaseUrl(mainRoot);
61
+ const issues = [];
62
+ // Check each allocation
63
+ for (const [slotStr, allocation] of Object.entries(registry.allocations)) {
64
+ const slot = Number(slotStr);
65
+ // Check if worktree path exists
66
+ if (!fs.existsSync(allocation.worktreePath)) {
67
+ const issue = {
68
+ type: 'stale_entry',
69
+ slot,
70
+ detail: `Path does not exist: ${allocation.worktreePath}`,
71
+ fixed: options.fix,
72
+ };
73
+ issues.push(issue);
74
+ if (options.fix) {
75
+ registry = (0, registry_1.removeAllocation)(registry, slot);
76
+ }
77
+ }
78
+ // Check if database exists
79
+ const dbOk = await (0, database_1.databaseExists)(databaseUrl, allocation.dbName);
80
+ if (!dbOk) {
81
+ issues.push({
82
+ type: 'missing_db',
83
+ slot,
84
+ detail: `Database does not exist: ${allocation.dbName}`,
85
+ });
86
+ }
87
+ // Check if env files exist in the worktree
88
+ if (fs.existsSync(allocation.worktreePath)) {
89
+ for (const envFile of config.envFiles) {
90
+ const envPath = path.join(allocation.worktreePath, envFile.source);
91
+ if (!fs.existsSync(envPath)) {
92
+ issues.push({
93
+ type: 'missing_env',
94
+ slot,
95
+ detail: `Missing env file: ${envFile.source}`,
96
+ });
97
+ }
98
+ }
99
+ }
100
+ }
101
+ // Detect orphaned databases
102
+ const pattern = `${config.baseDatabaseName}_wt%`;
103
+ const allDbs = await (0, database_1.listDatabasesByPattern)(databaseUrl, pattern);
104
+ const registeredDbNames = new Set(Object.values(registry.allocations).map((a) => a.dbName));
105
+ for (const dbName of allDbs) {
106
+ if (!registeredDbNames.has(dbName)) {
107
+ const issue = {
108
+ type: 'orphaned_db',
109
+ detail: `Orphaned database: ${dbName}`,
110
+ fixed: options.fix,
111
+ };
112
+ issues.push(issue);
113
+ if (options.fix) {
114
+ await (0, database_1.dropDatabase)(databaseUrl, dbName, config.baseDatabaseName);
115
+ }
116
+ }
117
+ }
118
+ // Save registry if fixes were applied
119
+ if (options.fix) {
120
+ (0, registry_1.writeRegistry)(mainRoot, registry);
121
+ }
122
+ // Output
123
+ const result = { issues, totalIssues: issues.length, fixed: options.fix };
124
+ if (options.json) {
125
+ console.log((0, output_1.formatJson)((0, output_1.success)(result)));
126
+ return;
127
+ }
128
+ if (issues.length === 0) {
129
+ console.log('All checks passed. No issues found.');
130
+ return;
131
+ }
132
+ console.log(`Found ${issues.length} issue(s):\n`);
133
+ for (const issue of issues) {
134
+ const prefix = issue.fixed ? '[FIXED]' : '[ISSUE]';
135
+ const slotInfo = issue.slot !== undefined ? ` (slot ${issue.slot})` : '';
136
+ console.log(` ${prefix} ${issue.type}${slotInfo}: ${issue.detail}`);
137
+ }
138
+ if (!options.fix && issues.some((i) => i.type === 'stale_entry' || i.type === 'orphaned_db')) {
139
+ console.log('\nRun with --fix to auto-repair stale entries and orphaned databases.');
140
+ }
141
+ }
142
+ catch (err) {
143
+ const message = err instanceof Error ? err.message : String(err);
144
+ if (options.json) {
145
+ console.log((0, output_1.formatJson)((0, output_1.error)('DOCTOR_FAILED', message)));
146
+ }
147
+ else {
148
+ console.error(`Doctor failed: ${message}`);
149
+ }
150
+ process.exitCode = 1;
151
+ }
152
+ }
153
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,sCA2GC;AA3ID,4CAA8B;AAC9B,gDAAkC;AAClC,+CAAiF;AACjF,+CAAwF;AACxF,qCAAkD;AAClD,mCAAqC;AACrC,sCAAuD;AAcvD,2DAA2D;AAC3D,SAAS,eAAe,CAAC,QAAgB;IACvC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAClD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,gCAAgC,CAAC,CAAC;IAC9D,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,6DAA6D;AACtD,KAAK,UAAU,aAAa,CAAC,OAAsB;IACxD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,IAAA,yBAAmB,GAAE,CAAC;QACvC,MAAM,MAAM,GAAG,IAAA,kBAAU,EAAC,QAAQ,CAAC,CAAC;QACpC,IAAI,QAAQ,GAAG,IAAA,uBAAY,EAAC,QAAQ,CAAC,CAAC;QACtC,MAAM,WAAW,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAsB,EAAE,CAAC;QAErC,wBAAwB;QACxB,KAAK,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YACzE,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;YAE7B,gCAAgC;YAChC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;gBAC5C,MAAM,KAAK,GAAoB;oBAC7B,IAAI,EAAE,aAAa;oBACnB,IAAI;oBACJ,MAAM,EAAE,wBAAwB,UAAU,CAAC,YAAY,EAAE;oBACzD,KAAK,EAAE,OAAO,CAAC,GAAG;iBACnB,CAAC;gBACF,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACnB,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;oBAChB,QAAQ,GAAG,IAAA,2BAAgB,EAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;gBAC9C,CAAC;YACH,CAAC;YAED,2BAA2B;YAC3B,MAAM,IAAI,GAAG,MAAM,IAAA,yBAAc,EAAC,WAAW,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;YAClE,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,MAAM,CAAC,IAAI,CAAC;oBACV,IAAI,EAAE,YAAY;oBAClB,IAAI;oBACJ,MAAM,EAAE,4BAA4B,UAAU,CAAC,MAAM,EAAE;iBACxD,CAAC,CAAC;YACL,CAAC;YAED,2CAA2C;YAC3C,IAAI,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;gBAC3C,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;oBACtC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;oBACnE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;wBAC5B,MAAM,CAAC,IAAI,CAAC;4BACV,IAAI,EAAE,aAAa;4BACnB,IAAI;4BACJ,MAAM,EAAE,qBAAqB,OAAO,CAAC,MAAM,EAAE;yBAC9C,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAED,4BAA4B;QAC5B,MAAM,OAAO,GAAG,GAAG,MAAM,CAAC,gBAAgB,MAAM,CAAC;QACjD,MAAM,MAAM,GAAG,MAAM,IAAA,iCAAsB,EAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QAClE,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAC/B,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CACzD,CAAC;QACF,KAAK,MAAM,MAAM,IAAI,MAAM,EAAE,CAAC;YAC5B,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;gBACnC,MAAM,KAAK,GAAoB;oBAC7B,IAAI,EAAE,aAAa;oBACnB,MAAM,EAAE,sBAAsB,MAAM,EAAE;oBACtC,KAAK,EAAE,OAAO,CAAC,GAAG;iBACnB,CAAC;gBACF,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACnB,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;oBAChB,MAAM,IAAA,uBAAY,EAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,gBAAgB,CAAC,CAAC;gBACnE,CAAC;YACH,CAAC;QACH,CAAC;QAED,sCAAsC;QACtC,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;YAChB,IAAA,wBAAa,EAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACpC,CAAC;QAED,SAAS;QACT,MAAM,MAAM,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC;QAC1E,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YACjB,OAAO,CAAC,GAAG,CAAC,IAAA,mBAAU,EAAC,IAAA,gBAAO,EAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YACzC,OAAO;QACT,CAAC;QAED,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,OAAO,CAAC,GAAG,CAAC,qCAAqC,CAAC,CAAC;YACnD,OAAO;QACT,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,SAAS,MAAM,CAAC,MAAM,cAAc,CAAC,CAAC;QAClD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;YACnD,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,UAAU,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACzE,OAAO,CAAC,GAAG,CAAC,KAAK,MAAM,IAAI,KAAK,CAAC,IAAI,GAAG,QAAQ,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QACvE,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,aAAa,IAAI,CAAC,CAAC,IAAI,KAAK,aAAa,CAAC,EAAE,CAAC;YAC7F,OAAO,CAAC,GAAG,CAAC,uEAAuE,CAAC,CAAC;QACvF,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YACjB,OAAO,CAAC,GAAG,CAAC,IAAA,mBAAU,EAAC,IAAA,cAAK,EAAC,eAAe,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;QAC3D,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,kBAAkB,OAAO,EAAE,CAAC,CAAC;QAC7C,CAAC;QACD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC;AACH,CAAC"}
@@ -0,0 +1,6 @@
1
+ interface ListOptions {
2
+ readonly json: boolean;
3
+ }
4
+ /** List all worktree allocations */
5
+ export declare function listCommand(repoRoot: string, options: ListOptions): void;
6
+ export {};