bobs-workshop 0.1.4

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 (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +252 -0
  3. package/bin/bobs-mcp.js +130 -0
  4. package/dist/api/taskLogger.js +106 -0
  5. package/dist/api/taskLogger.js.map +1 -0
  6. package/dist/cli/checker.js +401 -0
  7. package/dist/cli/checker.js.map +1 -0
  8. package/dist/cli/cleanup.js +131 -0
  9. package/dist/cli/cleanup.js.map +1 -0
  10. package/dist/cli/debug.js +157 -0
  11. package/dist/cli/debug.js.map +1 -0
  12. package/dist/cli/health.js +97 -0
  13. package/dist/cli/health.js.map +1 -0
  14. package/dist/cli/setup.js +81 -0
  15. package/dist/cli/setup.js.map +1 -0
  16. package/dist/cli/workshop.js +42 -0
  17. package/dist/cli/workshop.js.map +1 -0
  18. package/dist/dashboard/server.js +1206 -0
  19. package/dist/dashboard/server.js.map +1 -0
  20. package/dist/index.js +757 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/prompts/architect.js +157 -0
  23. package/dist/prompts/architect.js.map +1 -0
  24. package/dist/prompts/debugger.js +201 -0
  25. package/dist/prompts/debugger.js.map +1 -0
  26. package/dist/prompts/engineer.js +171 -0
  27. package/dist/prompts/engineer.js.map +1 -0
  28. package/dist/prompts/orchestrator.js +225 -0
  29. package/dist/prompts/orchestrator.js.map +1 -0
  30. package/dist/prompts/reviewer.js +199 -0
  31. package/dist/prompts/reviewer.js.map +1 -0
  32. package/dist/services/activitySummarizer.js +353 -0
  33. package/dist/services/activitySummarizer.js.map +1 -0
  34. package/dist/services/changeValidator.js +396 -0
  35. package/dist/services/changeValidator.js.map +1 -0
  36. package/dist/services/claudeOrchestrator.js +343 -0
  37. package/dist/services/claudeOrchestrator.js.map +1 -0
  38. package/dist/services/fileMonitor.js +250 -0
  39. package/dist/services/fileMonitor.js.map +1 -0
  40. package/dist/services/implementationSummarizer.js +306 -0
  41. package/dist/services/implementationSummarizer.js.map +1 -0
  42. package/dist/services/liveMonitor.js +315 -0
  43. package/dist/services/liveMonitor.js.map +1 -0
  44. package/dist/services/mcpAuditLogger.js +104 -0
  45. package/dist/services/mcpAuditLogger.js.map +1 -0
  46. package/dist/services/mcpLogger.js +223 -0
  47. package/dist/services/mcpLogger.js.map +1 -0
  48. package/dist/services/tmuxManager.js +541 -0
  49. package/dist/services/tmuxManager.js.map +1 -0
  50. package/dist/tools/approvalTools.js +244 -0
  51. package/dist/tools/approvalTools.js.map +1 -0
  52. package/dist/tools/autoDebugger.js +147 -0
  53. package/dist/tools/autoDebugger.js.map +1 -0
  54. package/dist/tools/cleanupService.js +221 -0
  55. package/dist/tools/cleanupService.js.map +1 -0
  56. package/dist/tools/dashboardTools.js +359 -0
  57. package/dist/tools/dashboardTools.js.map +1 -0
  58. package/dist/tools/developmentNudges.js +336 -0
  59. package/dist/tools/developmentNudges.js.map +1 -0
  60. package/dist/tools/gitTools.js +741 -0
  61. package/dist/tools/gitTools.js.map +1 -0
  62. package/dist/tools/orchestratorTools.js +765 -0
  63. package/dist/tools/orchestratorTools.js.map +1 -0
  64. package/dist/tools/searchTools.js +788 -0
  65. package/dist/tools/searchTools.js.map +1 -0
  66. package/dist/tools/specTools.js +350 -0
  67. package/dist/tools/specTools.js.map +1 -0
  68. package/dist/tools/tmuxTools.js +100 -0
  69. package/dist/tools/tmuxTools.js.map +1 -0
  70. package/dist/tools/workRecorder.js +215 -0
  71. package/dist/tools/workRecorder.js.map +1 -0
  72. package/dist/tools/worktreeTools.js +705 -0
  73. package/dist/tools/worktreeTools.js.map +1 -0
  74. package/dist/utils/__tests__/integration.test.js +57 -0
  75. package/dist/utils/__tests__/integration.test.js.map +1 -0
  76. package/dist/utils/__tests__/serverDetection.test.js +151 -0
  77. package/dist/utils/__tests__/serverDetection.test.js.map +1 -0
  78. package/dist/utils/errorHandling.js +336 -0
  79. package/dist/utils/errorHandling.js.map +1 -0
  80. package/dist/utils/processManager.js +172 -0
  81. package/dist/utils/processManager.js.map +1 -0
  82. package/dist/utils/reliability.js +263 -0
  83. package/dist/utils/reliability.js.map +1 -0
  84. package/dist/utils/responseFormatter.js +250 -0
  85. package/dist/utils/responseFormatter.js.map +1 -0
  86. package/dist/utils/serverDetection.js +133 -0
  87. package/dist/utils/serverDetection.js.map +1 -0
  88. package/dist/utils/specMigration.js +105 -0
  89. package/dist/utils/specMigration.js.map +1 -0
  90. package/dist/validation/schemas.js +299 -0
  91. package/dist/validation/schemas.js.map +1 -0
  92. package/package.json +79 -0
  93. package/scripts/init-workspace.js +63 -0
  94. package/scripts/install-search-tools.js +116 -0
@@ -0,0 +1,705 @@
1
+ // src/tools/worktreeTools.ts
2
+ import simpleGit from "simple-git";
3
+ import { z } from "zod";
4
+ import { spawn } from "child_process";
5
+ import path from "path";
6
+ const git = simpleGit();
7
+ export const WorktreeCreateInput = z.object({
8
+ spec_id: z.string(),
9
+ branch: z.string(),
10
+ base: z.string().optional().default("main")
11
+ });
12
+ export const WorktreeCreateOutput = z.object({
13
+ worktree_id: z.string(),
14
+ branch: z.string(),
15
+ path: z.string(),
16
+ status: z.string()
17
+ });
18
+ export const WorktreeStatusInput = z.object({
19
+ worktree_id: z.string()
20
+ });
21
+ export const WorktreeStatusOutput = z.object({
22
+ branch: z.string(),
23
+ dirty: z.boolean(),
24
+ ahead: z.number(),
25
+ behind: z.number(),
26
+ changed_files: z.array(z.string())
27
+ });
28
+ export const WorktreeMergeInput = z.object({
29
+ worktree_id: z.string()
30
+ });
31
+ export const WorktreeRemoveInput = z.object({
32
+ worktree_id: z.string(),
33
+ spec_id: z.string().optional()
34
+ });
35
+ export const WorktreeListOutput = z.object({
36
+ committed: z.array(z.string()),
37
+ dirty: z.array(z.string()),
38
+ clean: z.array(z.string())
39
+ });
40
+ export const WorktreeStartServerInput = z.object({
41
+ worktree_id: z.string(),
42
+ command: z.string().default("npm run dev"),
43
+ port: z.number().optional()
44
+ });
45
+ export const WorktreeStartServerOutput = z.object({
46
+ status: z.string(),
47
+ url: z.string().optional(),
48
+ pid: z.number()
49
+ });
50
+ export const WorktreeStopServerInput = z.object({
51
+ pid: z.number()
52
+ });
53
+ export const WorktreeStopServerOutput = z.object({
54
+ status: z.string()
55
+ });
56
+ // Store running server processes
57
+ const runningServers = new Map();
58
+ export async function worktreeCreateHandler(input) {
59
+ const validated = WorktreeCreateInput.parse(input);
60
+ const branchName = validated.branch || `feature/${validated.spec_id}`;
61
+ try {
62
+ console.log(`🔧 Creating actual git worktree for branch: ${branchName}`);
63
+ // Create the worktree directory path
64
+ const worktreePath = path.resolve(process.cwd(), `.bob/worktrees/${branchName}`);
65
+ // Ensure the worktrees directory exists
66
+ const fs = await import('fs-extra');
67
+ await fs.ensureDir(path.dirname(worktreePath));
68
+ // FIXED: Actually create a git worktree (not just a branch)
69
+ const { exec } = await import('child_process');
70
+ const { promisify } = await import('util');
71
+ const execAsync = promisify(exec);
72
+ // Create the worktree with a new branch
73
+ console.log(`Creating worktree at: ${worktreePath}`);
74
+ await execAsync(`git worktree add ${worktreePath} -b ${branchName} ${validated.base}`);
75
+ // Install dependencies in the new worktree
76
+ console.log(`Installing dependencies in worktree: ${worktreePath}`);
77
+ await installDependenciesInWorktree(worktreePath);
78
+ // Update SPEC with worktree creation and metadata
79
+ if (validated.spec_id) {
80
+ const { specUpdateHandler } = await import("./specTools.js");
81
+ await specUpdateHandler({
82
+ spec_id: validated.spec_id,
83
+ worktree: {
84
+ branch: branchName,
85
+ path: worktreePath,
86
+ status: "active",
87
+ created_at: new Date().toISOString()
88
+ },
89
+ execution_log: {
90
+ timestamp: new Date().toISOString(),
91
+ engineer: "system",
92
+ action: "worktree_created",
93
+ task_id: "system-worktree-creation",
94
+ files_changed: [],
95
+ commit_hash: "pending",
96
+ note: `Created actual git worktree at ${worktreePath} with branch ${branchName} and installed dependencies`
97
+ }
98
+ });
99
+ }
100
+ console.log(`✅ Worktree created successfully at: ${worktreePath}`);
101
+ return {
102
+ worktree_id: branchName,
103
+ branch: branchName,
104
+ path: worktreePath,
105
+ status: "created"
106
+ };
107
+ }
108
+ catch (error) {
109
+ throw new Error(`Failed to create worktree: ${error instanceof Error ? error.message : String(error)}`);
110
+ }
111
+ }
112
+ // Helper function to install dependencies in a worktree
113
+ async function installDependenciesInWorktree(worktreePath) {
114
+ try {
115
+ const { exec } = await import('child_process');
116
+ const { promisify } = await import('util');
117
+ const execAsync = promisify(exec);
118
+ const fs = await import('fs-extra');
119
+ // Check if package.json exists in the worktree
120
+ const packageJsonPath = path.join(worktreePath, 'package.json');
121
+ if (await fs.pathExists(packageJsonPath)) {
122
+ console.log(`Installing npm dependencies in ${worktreePath}...`);
123
+ // Install dependencies (use npm ci for faster, reliable installs)
124
+ try {
125
+ await execAsync('npm ci', { cwd: worktreePath });
126
+ console.log(`✅ Dependencies installed successfully in ${worktreePath}`);
127
+ }
128
+ catch (ciError) {
129
+ // Fallback to npm install if npm ci fails
130
+ console.log(`npm ci failed, falling back to npm install...`);
131
+ await execAsync('npm install', { cwd: worktreePath });
132
+ console.log(`✅ Dependencies installed successfully in ${worktreePath}`);
133
+ }
134
+ }
135
+ else {
136
+ console.log(`No package.json found in ${worktreePath}, skipping dependency installation`);
137
+ }
138
+ // Check for other package managers
139
+ const yarnLockPath = path.join(worktreePath, 'yarn.lock');
140
+ const pnpmLockPath = path.join(worktreePath, 'pnpm-lock.yaml');
141
+ if (await fs.pathExists(yarnLockPath)) {
142
+ console.log(`Installing yarn dependencies in ${worktreePath}...`);
143
+ await execAsync('yarn install --frozen-lockfile', { cwd: worktreePath });
144
+ console.log(`✅ Yarn dependencies installed successfully in ${worktreePath}`);
145
+ }
146
+ else if (await fs.pathExists(pnpmLockPath)) {
147
+ console.log(`Installing pnpm dependencies in ${worktreePath}...`);
148
+ await execAsync('pnpm install --frozen-lockfile', { cwd: worktreePath });
149
+ console.log(`✅ PNPM dependencies installed successfully in ${worktreePath}`);
150
+ }
151
+ }
152
+ catch (error) {
153
+ console.warn(`Warning: Failed to install dependencies in worktree: ${error}`);
154
+ // Don't throw - dependency installation failure shouldn't break worktree creation
155
+ }
156
+ }
157
+ export async function worktreeStatusHandler(input) {
158
+ const validated = WorktreeStatusInput.parse(input);
159
+ try {
160
+ const status = await git.status();
161
+ return {
162
+ branch: validated.worktree_id,
163
+ dirty: !status.isClean(),
164
+ ahead: 0, // simplified for now
165
+ behind: 0, // simplified for now
166
+ changed_files: [...status.modified, ...status.created, ...status.deleted]
167
+ };
168
+ }
169
+ catch (error) {
170
+ throw new Error(`Failed to get worktree status: ${error instanceof Error ? error.message : String(error)}`);
171
+ }
172
+ }
173
+ export async function worktreeMergeHandler(input) {
174
+ const validated = WorktreeMergeInput.parse(input);
175
+ try {
176
+ // Safety checks before merging
177
+ const currentBranch = await git.branch();
178
+ const status = await git.status();
179
+ // Ensure we're not on the branch being merged
180
+ if (currentBranch.current === validated.worktree_id) {
181
+ throw new Error(`Cannot merge while on branch ${validated.worktree_id}. Switch to main first.`);
182
+ }
183
+ // Check if main branch is clean
184
+ if (!status.isClean()) {
185
+ throw new Error("Main branch has uncommitted changes. Please commit or stash them before merging.");
186
+ }
187
+ // Check if branch exists
188
+ const branches = Object.keys(currentBranch.branches);
189
+ if (!branches.includes(validated.worktree_id)) {
190
+ throw new Error(`Branch ${validated.worktree_id} does not exist`);
191
+ }
192
+ // Switch to main and pull latest (if remote exists)
193
+ await git.checkout("main");
194
+ try {
195
+ // Try to pull latest main (ignore errors if no remote)
196
+ await git.pull("origin", "main");
197
+ }
198
+ catch (pullError) {
199
+ console.warn("Could not pull latest main (no remote or network issue):", pullError);
200
+ }
201
+ // Check for merge conflicts by doing a dry run
202
+ try {
203
+ await git.raw(['merge', '--no-commit', '--no-ff', validated.worktree_id]);
204
+ // If successful, abort the merge to complete it properly
205
+ await git.merge(['--abort']);
206
+ }
207
+ catch (dryRunError) {
208
+ throw new Error(`Merge would result in conflicts: ${dryRunError instanceof Error ? dryRunError.message : String(dryRunError)}`);
209
+ }
210
+ // Perform the actual merge
211
+ await git.merge([validated.worktree_id, '--no-ff']);
212
+ return {
213
+ status: "merged",
214
+ commit_hash: await git.revparse('HEAD'),
215
+ note: `Successfully merged ${validated.worktree_id} into main`
216
+ };
217
+ }
218
+ catch (error) {
219
+ // Ensure we're back on main if merge failed
220
+ try {
221
+ const currentBranch = await git.branch();
222
+ if (currentBranch.current !== "main") {
223
+ await git.checkout("main");
224
+ }
225
+ }
226
+ catch (cleanupError) {
227
+ console.warn("Failed to cleanup after merge error:", cleanupError);
228
+ }
229
+ throw new Error(`Failed to merge worktree: ${error instanceof Error ? error.message : String(error)}`);
230
+ }
231
+ }
232
+ export async function worktreeRemoveHandler(input) {
233
+ const validated = WorktreeRemoveInput.parse(input);
234
+ try {
235
+ console.log(`🔧 Removing worktree: ${validated.worktree_id}`);
236
+ // FIXED: Actually remove the git worktree, not just the branch
237
+ const { exec } = await import('child_process');
238
+ const { promisify } = await import('util');
239
+ const execAsync = promisify(exec);
240
+ const fs = await import('fs-extra');
241
+ const worktreePath = path.resolve(process.cwd(), `.bob/worktrees/${validated.worktree_id}`);
242
+ // Check if worktree exists
243
+ if (await fs.pathExists(worktreePath)) {
244
+ console.log(`Removing worktree directory: ${worktreePath}`);
245
+ // Remove the worktree using git command
246
+ await execAsync(`git worktree remove ${worktreePath} --force`);
247
+ console.log(`✅ Worktree removed: ${worktreePath}`);
248
+ }
249
+ else {
250
+ console.log(`Worktree directory not found: ${worktreePath}`);
251
+ }
252
+ // Switch to main branch in main repository
253
+ await git.checkout("main");
254
+ // Delete the local branch
255
+ try {
256
+ await git.deleteLocalBranch(validated.worktree_id);
257
+ console.log(`✅ Branch deleted: ${validated.worktree_id}`);
258
+ }
259
+ catch (branchError) {
260
+ console.log(`Warning: Could not delete branch ${validated.worktree_id}:`, branchError);
261
+ // Don't fail the whole operation if branch deletion fails
262
+ }
263
+ // Update SPEC with worktree removal
264
+ if (validated.spec_id) {
265
+ try {
266
+ const { specUpdateHandler } = await import("./specTools.js");
267
+ await specUpdateHandler({
268
+ spec_id: validated.spec_id,
269
+ worktree: {
270
+ status: "removed",
271
+ removed_at: new Date().toISOString()
272
+ }
273
+ });
274
+ }
275
+ catch (specError) {
276
+ console.warn(`Warning: Could not update SPEC ${validated.spec_id}:`, specError);
277
+ // Don't fail the whole operation if SPEC update fails
278
+ }
279
+ }
280
+ return {
281
+ status: "removed"
282
+ };
283
+ }
284
+ catch (error) {
285
+ throw new Error(`Failed to remove worktree: ${error instanceof Error ? error.message : String(error)}`);
286
+ }
287
+ }
288
+ export async function worktreeListHandler() {
289
+ try {
290
+ const branchInfo = await git.branch();
291
+ const status = await git.status();
292
+ // Filter out remote branches and main branch - only show local feature branches
293
+ const branches = Object.keys(branchInfo.branches).filter(name => name !== "main" &&
294
+ !name.startsWith("remotes/") &&
295
+ !name.includes("origin/"));
296
+ const committed = [];
297
+ const dirty = [];
298
+ const clean = [];
299
+ for (const branch of branches) {
300
+ const branchData = {
301
+ name: branch
302
+ };
303
+ if (branch === branchInfo.current && !status.isClean()) {
304
+ dirty.push(branchData);
305
+ }
306
+ else {
307
+ // simplified branch categorization for now
308
+ clean.push(branchData);
309
+ }
310
+ }
311
+ return {
312
+ committed,
313
+ dirty,
314
+ clean
315
+ };
316
+ }
317
+ catch (error) {
318
+ throw new Error(`Failed to list worktrees: ${error instanceof Error ? error.message : String(error)}`);
319
+ }
320
+ }
321
+ export async function worktreeStartServerHandler(input) {
322
+ const validated = WorktreeStartServerInput.parse(input);
323
+ try {
324
+ // FIXED: Find the actual worktree directory instead of just switching branches
325
+ const worktreePath = path.resolve(process.cwd(), `.bob/worktrees/${validated.worktree_id}`);
326
+ // Check if worktree exists
327
+ const fs = await import('fs-extra');
328
+ if (!await fs.pathExists(worktreePath)) {
329
+ throw new Error(`Worktree not found at ${worktreePath}. Please create the worktree first.`);
330
+ }
331
+ console.log(`Starting server in worktree: ${worktreePath}`);
332
+ // Parse command parts
333
+ const commandParts = validated.command.split(" ");
334
+ const command = commandParts[0];
335
+ const args = commandParts.slice(1);
336
+ // Spawn the server process in the worktree directory
337
+ const serverProcess = spawn(command, args, {
338
+ cwd: worktreePath, // FIXED: Run in the actual worktree directory
339
+ stdio: 'pipe',
340
+ detached: false,
341
+ env: { ...process.env, PORT: validated.port?.toString() || process.env.PORT }
342
+ });
343
+ if (!serverProcess.pid) {
344
+ throw new Error("Failed to start server process");
345
+ }
346
+ // Store the process for later management
347
+ runningServers.set(serverProcess.pid, serverProcess);
348
+ // Log server output for debugging
349
+ serverProcess.stdout?.on('data', (data) => {
350
+ console.log(`[SERVER ${serverProcess.pid}] ${data.toString()}`);
351
+ });
352
+ serverProcess.stderr?.on('data', (data) => {
353
+ console.log(`[SERVER ${serverProcess.pid}] ERROR: ${data.toString()}`);
354
+ });
355
+ // Determine URL if port is known
356
+ let url;
357
+ if (validated.port) {
358
+ url = `http://localhost:${validated.port}`;
359
+ }
360
+ else if (validated.command.includes("dev") || validated.command.includes("start")) {
361
+ // Common development server ports
362
+ url = "http://localhost:3000";
363
+ }
364
+ // Update SPEC with server start event
365
+ const { specUpdateHandler } = await import("./specTools.js");
366
+ await specUpdateHandler({
367
+ spec_id: validated.worktree_id.replace("feature/", ""),
368
+ execution_log: {
369
+ timestamp: new Date().toISOString(),
370
+ engineer: "system",
371
+ action: "server_started",
372
+ task_id: "system-server-start",
373
+ files_changed: [],
374
+ commit_hash: "pending",
375
+ note: `Started server in worktree ${worktreePath}: ${validated.command} (PID: ${serverProcess.pid})`
376
+ }
377
+ });
378
+ console.log(`✅ Server started successfully in worktree (PID: ${serverProcess.pid})`);
379
+ return {
380
+ status: "running",
381
+ url,
382
+ pid: serverProcess.pid
383
+ };
384
+ }
385
+ catch (error) {
386
+ throw new Error(`Failed to start server: ${error instanceof Error ? error.message : String(error)}`);
387
+ }
388
+ }
389
+ export async function worktreeStopServerHandler(input) {
390
+ const validated = WorktreeStopServerInput.parse(input);
391
+ try {
392
+ const serverProcess = runningServers.get(validated.pid);
393
+ if (!serverProcess) {
394
+ throw new Error(`No running server found with PID ${validated.pid}`);
395
+ }
396
+ // Kill the process
397
+ serverProcess.kill('SIGTERM');
398
+ // Remove from tracking
399
+ runningServers.delete(validated.pid);
400
+ return {
401
+ status: "stopped"
402
+ };
403
+ }
404
+ catch (error) {
405
+ throw new Error(`Failed to stop server: ${error instanceof Error ? error.message : String(error)}`);
406
+ }
407
+ }
408
+ // ============================================================================
409
+ // NEW MCP TOOL: Comprehensive Worktree Debug & Maintenance
410
+ // ============================================================================
411
+ export const WorktreeDebugInput = z.object({
412
+ action: z.enum(["repair", "cleanup", "validate", "status"]).describe("Debug action to perform"),
413
+ spec_id: z.string().optional().describe("Optional SPEC ID to focus on"),
414
+ max_age_days: z.number().optional().default(7).describe("Max age in days for cleanup action")
415
+ });
416
+ export const WorktreeDebugOutput = z.object({
417
+ action: z.string(),
418
+ success: z.boolean(),
419
+ report: z.string(),
420
+ actions_taken: z.array(z.string()),
421
+ recommendations: z.array(z.string()).optional(),
422
+ worktrees_found: z.number().optional(),
423
+ worktrees_cleaned: z.number().optional(),
424
+ issues_found: z.array(z.string()).optional()
425
+ });
426
+ /**
427
+ * bob.worktree.debug - Comprehensive worktree maintenance tool
428
+ * Handles repair, cleanup, validation, and status reporting
429
+ */
430
+ export async function worktreeDebugHandler(input) {
431
+ const validated = WorktreeDebugInput.parse(input);
432
+ try {
433
+ console.log(`bob.worktree.debug called with action: ${validated.action}`);
434
+ switch (validated.action) {
435
+ case "status":
436
+ return await debugStatus(validated.spec_id);
437
+ case "validate":
438
+ return await debugValidate(validated.spec_id);
439
+ case "repair":
440
+ return await debugRepair(validated.spec_id);
441
+ case "cleanup":
442
+ return await debugCleanup(validated.max_age_days);
443
+ default:
444
+ throw new Error(`Unknown debug action: ${validated.action}`);
445
+ }
446
+ }
447
+ catch (error) {
448
+ return {
449
+ action: validated.action,
450
+ success: false,
451
+ report: `Debug action failed: ${error instanceof Error ? error.message : String(error)}`,
452
+ actions_taken: [],
453
+ recommendations: ["Check logs for detailed error information"],
454
+ issues_found: [String(error)]
455
+ };
456
+ }
457
+ }
458
+ // Helper: Get detailed status of all worktrees
459
+ async function debugStatus(specId) {
460
+ const { exec } = await import('child_process');
461
+ const { promisify } = await import('util');
462
+ const execAsync = promisify(exec);
463
+ const fs = await import('fs-extra');
464
+ const actionsTaken = [];
465
+ const issuesFound = [];
466
+ // Get git worktrees
467
+ const { stdout } = await execAsync('git worktree list --porcelain');
468
+ const worktrees = stdout.trim().split('\n\n').filter(w => w.length > 0);
469
+ const worktreeCount = worktrees.length - 1; // Exclude main repo
470
+ actionsTaken.push(`Found ${worktreeCount} worktrees (excluding main repo)`);
471
+ // Parse worktrees
472
+ for (const worktreeInfo of worktrees) {
473
+ const lines = worktreeInfo.split('\n');
474
+ const worktreePath = lines[0].replace('worktree ', '');
475
+ if (worktreePath === process.cwd())
476
+ continue; // Skip main
477
+ const branchLine = lines.find(l => l.startsWith('branch '));
478
+ const branch = branchLine ? branchLine.replace('branch refs/heads/', '') : 'unknown';
479
+ // Check if directory exists
480
+ const exists = await fs.pathExists(worktreePath);
481
+ if (!exists) {
482
+ issuesFound.push(`Worktree registered but directory missing: ${worktreePath} (${branch})`);
483
+ }
484
+ else {
485
+ actionsTaken.push(`✓ ${branch} → ${worktreePath}`);
486
+ }
487
+ }
488
+ // Check SPEC metadata if specId provided
489
+ if (specId) {
490
+ const { specGetHandler } = await import("./specTools.js");
491
+ try {
492
+ const spec = await specGetHandler({ spec_id: specId });
493
+ if (spec.worktree) {
494
+ actionsTaken.push(`SPEC ${specId} worktree metadata: ${spec.worktree.status} | ${spec.worktree.branch} → ${spec.worktree.path}`);
495
+ if (spec.worktree.path && !await fs.pathExists(spec.worktree.path)) {
496
+ issuesFound.push(`SPEC worktree path does not exist: ${spec.worktree.path}`);
497
+ }
498
+ }
499
+ else {
500
+ actionsTaken.push(`SPEC ${specId} has no worktree metadata`);
501
+ }
502
+ }
503
+ catch (error) {
504
+ issuesFound.push(`Could not load SPEC ${specId}: ${error}`);
505
+ }
506
+ }
507
+ return {
508
+ action: "status",
509
+ success: true,
510
+ report: `Status report: ${worktreeCount} worktrees found, ${issuesFound.length} issues detected`,
511
+ actions_taken: actionsTaken,
512
+ recommendations: issuesFound.length > 0 ? ["Run 'repair' action to fix issues"] : undefined,
513
+ worktrees_found: worktreeCount,
514
+ issues_found: issuesFound.length > 0 ? issuesFound : undefined
515
+ };
516
+ }
517
+ // Helper: Validate worktree exists before operations
518
+ async function debugValidate(specId) {
519
+ if (!specId) {
520
+ return {
521
+ action: "validate",
522
+ success: false,
523
+ report: "Validation requires a spec_id parameter",
524
+ actions_taken: [],
525
+ recommendations: ["Provide spec_id to validate worktree"]
526
+ };
527
+ }
528
+ const { specGetHandler } = await import("./specTools.js");
529
+ const fs = await import('fs-extra');
530
+ const actionsTaken = [];
531
+ const issuesFound = [];
532
+ try {
533
+ const spec = await specGetHandler({ spec_id: specId });
534
+ if (!spec.worktree || !spec.worktree.branch || !spec.worktree.path) {
535
+ issuesFound.push("SPEC missing worktree metadata");
536
+ return {
537
+ action: "validate",
538
+ success: false,
539
+ report: "SPEC has incomplete worktree metadata",
540
+ actions_taken: [],
541
+ issues_found: issuesFound,
542
+ recommendations: ["Check if worktree was created properly", "Run workflow.start to create worktree"]
543
+ };
544
+ }
545
+ actionsTaken.push(`SPEC worktree: ${spec.worktree.branch} → ${spec.worktree.path}`);
546
+ actionsTaken.push(`Status: ${spec.worktree.status}`);
547
+ // Validate directory exists
548
+ if (!await fs.pathExists(spec.worktree.path)) {
549
+ issuesFound.push(`Worktree directory does not exist: ${spec.worktree.path}`);
550
+ }
551
+ else {
552
+ actionsTaken.push("✓ Worktree directory exists");
553
+ }
554
+ // Validate it's in git worktree list
555
+ const { exec } = await import('child_process');
556
+ const { promisify } = await import('util');
557
+ const execAsync = promisify(exec);
558
+ const { stdout } = await execAsync('git worktree list --porcelain');
559
+ if (!stdout.includes(spec.worktree.path)) {
560
+ issuesFound.push(`Worktree not registered in git: ${spec.worktree.path}`);
561
+ }
562
+ else {
563
+ actionsTaken.push("✓ Worktree registered in git");
564
+ }
565
+ return {
566
+ action: "validate",
567
+ success: issuesFound.length === 0,
568
+ report: issuesFound.length === 0
569
+ ? `Worktree for ${specId} is valid and ready`
570
+ : `Worktree for ${specId} has ${issuesFound.length} issues`,
571
+ actions_taken: actionsTaken,
572
+ issues_found: issuesFound.length > 0 ? issuesFound : undefined,
573
+ recommendations: issuesFound.length > 0 ? ["Run 'repair' action to fix issues"] : undefined
574
+ };
575
+ }
576
+ catch (error) {
577
+ return {
578
+ action: "validate",
579
+ success: false,
580
+ report: `Validation failed: ${error}`,
581
+ actions_taken: actionsTaken,
582
+ issues_found: [String(error)]
583
+ };
584
+ }
585
+ }
586
+ // Helper: Repair worktree inconsistencies
587
+ async function debugRepair(specId) {
588
+ const { exec } = await import('child_process');
589
+ const { promisify } = await import('util');
590
+ const execAsync = promisify(exec);
591
+ const fs = await import('fs-extra');
592
+ const actionsTaken = [];
593
+ const issuesFound = [];
594
+ // Get git worktrees
595
+ const { stdout } = await execAsync('git worktree list --porcelain');
596
+ const worktrees = stdout.trim().split('\n\n');
597
+ // Check for orphaned worktrees (registered but directory missing)
598
+ for (const worktreeInfo of worktrees) {
599
+ const lines = worktreeInfo.split('\n');
600
+ const worktreePath = lines[0].replace('worktree ', '');
601
+ if (worktreePath === process.cwd())
602
+ continue;
603
+ if (!await fs.pathExists(worktreePath)) {
604
+ issuesFound.push(`Orphaned worktree: ${worktreePath}`);
605
+ try {
606
+ await execAsync(`git worktree remove ${worktreePath} --force`);
607
+ actionsTaken.push(`✓ Removed orphaned worktree: ${worktreePath}`);
608
+ }
609
+ catch (error) {
610
+ actionsTaken.push(`✗ Failed to remove ${worktreePath}: ${error}`);
611
+ }
612
+ }
613
+ }
614
+ // If specId provided, sync SPEC metadata with reality
615
+ if (specId) {
616
+ const { specGetHandler, specUpdateHandler } = await import("./specTools.js");
617
+ try {
618
+ const spec = await specGetHandler({ spec_id: specId });
619
+ if (spec.worktree && spec.worktree.path) {
620
+ const exists = await fs.pathExists(spec.worktree.path);
621
+ const inGit = stdout.includes(spec.worktree.path);
622
+ if (!exists && !inGit && spec.worktree.status !== "removed") {
623
+ // Worktree is gone but SPEC thinks it's active
624
+ await specUpdateHandler({
625
+ spec_id: specId,
626
+ worktree: {
627
+ status: "removed",
628
+ removed_at: new Date().toISOString()
629
+ }
630
+ });
631
+ actionsTaken.push(`✓ Updated SPEC ${specId} worktree status to 'removed'`);
632
+ }
633
+ }
634
+ }
635
+ catch (error) {
636
+ actionsTaken.push(`Could not update SPEC ${specId}: ${error}`);
637
+ }
638
+ }
639
+ // Prune stale git worktree references
640
+ try {
641
+ await execAsync('git worktree prune');
642
+ actionsTaken.push("✓ Pruned stale git worktree references");
643
+ }
644
+ catch (error) {
645
+ actionsTaken.push(`✗ Failed to prune: ${error}`);
646
+ }
647
+ return {
648
+ action: "repair",
649
+ success: true,
650
+ report: `Repair completed: ${actionsTaken.length} actions taken, ${issuesFound.length} issues found`,
651
+ actions_taken: actionsTaken,
652
+ issues_found: issuesFound.length > 0 ? issuesFound : undefined,
653
+ recommendations: issuesFound.length > 0 ? ["Some issues remain - manual intervention may be required"] : undefined
654
+ };
655
+ }
656
+ // Helper: Cleanup stale worktrees
657
+ async function debugCleanup(maxAgeDays) {
658
+ const { exec } = await import('child_process');
659
+ const { promisify } = await import('util');
660
+ const execAsync = promisify(exec);
661
+ const fs = await import('fs-extra');
662
+ const actionsTaken = [];
663
+ let cleanedCount = 0;
664
+ const maxAgeMs = maxAgeDays * 24 * 60 * 60 * 1000;
665
+ const now = Date.now();
666
+ // Get all worktrees
667
+ const { stdout } = await execAsync('git worktree list --porcelain');
668
+ const worktrees = stdout.trim().split('\n\n');
669
+ for (const worktreeInfo of worktrees) {
670
+ const lines = worktreeInfo.split('\n');
671
+ const worktreePath = lines[0].replace('worktree ', '');
672
+ if (worktreePath === process.cwd())
673
+ continue;
674
+ const branchLine = lines.find(l => l.startsWith('branch '));
675
+ const branch = branchLine ? branchLine.replace('branch refs/heads/', '') : 'unknown';
676
+ if (!await fs.pathExists(worktreePath)) {
677
+ actionsTaken.push(`Skipping missing worktree: ${branch}`);
678
+ continue;
679
+ }
680
+ // Check last modification time
681
+ try {
682
+ const stats = await fs.stat(worktreePath);
683
+ const ageMs = now - stats.mtimeMs;
684
+ if (ageMs > maxAgeMs) {
685
+ actionsTaken.push(`Stale worktree found: ${branch} (${Math.floor(ageMs / (24 * 60 * 60 * 1000))} days old)`);
686
+ // Remove worktree
687
+ await execAsync(`git worktree remove ${worktreePath} --force`);
688
+ cleanedCount++;
689
+ actionsTaken.push(`✓ Removed stale worktree: ${branch}`);
690
+ }
691
+ }
692
+ catch (error) {
693
+ actionsTaken.push(`✗ Failed to process ${branch}: ${error}`);
694
+ }
695
+ }
696
+ return {
697
+ action: "cleanup",
698
+ success: true,
699
+ report: `Cleanup completed: ${cleanedCount} stale worktrees removed`,
700
+ actions_taken: actionsTaken,
701
+ worktrees_cleaned: cleanedCount,
702
+ recommendations: cleanedCount > 0 ? ["Review removed worktrees to ensure no important work was lost"] : undefined
703
+ };
704
+ }
705
+ //# sourceMappingURL=worktreeTools.js.map