@iloom/cli 0.13.0 → 0.13.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.
package/LICENSE CHANGED
@@ -20,7 +20,7 @@ it within your organization. What is prohibited is redistributing or offering
20
20
  access to this software (in original or modified form) as part of something
21
21
  you sell or provide to others.
22
22
 
23
- Change Date: 2030-03-15
23
+ Change Date: 2030-03-16
24
24
  Change License: Apache License 2.0
25
25
 
26
26
  For clarity, on or after the Change Date, the Licensed Work will
package/README.md CHANGED
@@ -880,7 +880,7 @@ License & Name
880
880
 
881
881
  * ❌ You cannot resell iloom itself as a product or SaaS.
882
882
 
883
- * Converts to Apache 2.0 on 2030-03-15.
883
+ * Converts to Apache 2.0 on 2030-03-16.
884
884
 
885
885
 
886
886
  See [LICENSE](https://raw.githubusercontent.com/iloom-ai/iloom-cli/main/LICENSE) for complete terms.
package/dist/README.md CHANGED
@@ -880,7 +880,7 @@ License & Name
880
880
 
881
881
  * ❌ You cannot resell iloom itself as a product or SaaS.
882
882
 
883
- * Converts to Apache 2.0 on 2030-03-15.
883
+ * Converts to Apache 2.0 on 2030-03-16.
884
884
 
885
885
 
886
886
  See [LICENSE](https://raw.githubusercontent.com/iloom-ai/iloom-cli/main/LICENSE) for complete terms.
@@ -352,9 +352,13 @@ To recover:
352
352
  const systemPrompt = `Please help resolve the git rebase conflicts in this repository. Analyze the conflicted files, understand the changes from both branches, fix the conflicts, then run 'git add .' to stage the resolved files, and finally run 'git rebase --continue' to continue the rebase process. Once the issue is resolved, tell the user they can use /exit to continue with the process.`;
353
353
  const prompt = `Help me with this rebase please.`;
354
354
  const rebaseAllowedTools = [
355
+ "Read",
356
+ "Grep",
357
+ "Glob",
355
358
  "Bash(git status:*)",
356
359
  "Bash(git diff:*)",
357
360
  "Bash(git log:*)",
361
+ "Bash(git show:*)",
358
362
  "Bash(git add:*)",
359
363
  "Bash(git rebase:*)",
360
364
  "Bash(GIT_EDITOR=true git rebase:*)"
@@ -364,8 +368,8 @@ To recover:
364
368
  appendSystemPrompt: systemPrompt,
365
369
  addDir: worktreePath,
366
370
  headless: options.jsonStream ? true : false,
371
+ ...options.jsonStream && { permissionMode: "bypassPermissions" },
367
372
  ...options.jsonStream && {
368
- permissionMode: "bypassPermissions",
369
373
  passthroughStdout: true
370
374
  },
371
375
  allowedTools: rebaseAllowedTools,
@@ -539,4 +543,4 @@ export {
539
543
  MergeManager,
540
544
  BuildRunner
541
545
  };
542
- //# sourceMappingURL=chunk-SM3BCHYB.js.map
546
+ //# sourceMappingURL=chunk-EHAITKLS.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/MergeManager.ts","../src/lib/BuildRunner.ts"],"sourcesContent":["import { executeGitCommand, fetchOrigin, findMainWorktreePathWithSettings, findWorktreeForBranch, getMergeTargetBranch, GitCommandError } from '../utils/git.js'\nimport { getLogger } from '../utils/logger-context.js'\nimport { detectClaudeCli, launchClaude } from '../utils/claude.js'\nimport { SettingsManager } from './SettingsManager.js'\nimport { MetadataManager } from './MetadataManager.js'\nimport type { MergeOptions, RebaseOutcome } from '../types/index.js'\n\n/**\n * MergeManager handles Git rebase and fast-forward merge operations\n * Implements fail-fast behavior for conflicts (Phase 1 - no Claude assistance)\n *\n * Ports bash/merge-and-clean.sh lines 781-1090\n */\nexport class MergeManager {\n\tprivate settingsManager: SettingsManager\n\tprivate metadataManager: MetadataManager\n\n\tconstructor(settingsManager?: SettingsManager, metadataManager?: MetadataManager) {\n\t\tthis.settingsManager = settingsManager ?? new SettingsManager()\n\t\tthis.metadataManager = metadataManager ?? new MetadataManager()\n\t}\n\n\t/**\n\t * Get the merge target branch for a loom\n\t * Priority: parent loom metadata > configured main branch > 'main'\n\t * @param worktreePath - Optional path to load settings/metadata from (defaults to process.cwd())\n\t * @private\n\t */\n\tprivate async getMainBranch(worktreePath?: string): Promise<string> {\n\t\t// Delegate to shared utility function\n\t\treturn getMergeTargetBranch(worktreePath ?? process.cwd(), {\n\t\t\tsettingsManager: this.settingsManager,\n\t\t\tmetadataManager: this.metadataManager,\n\t\t})\n\t}\n\n\t/**\n\t * Rebase current branch on main with fail-fast on conflicts\n\t * Ports bash/merge-and-clean.sh lines 781-913\n\t *\n\t * @param worktreePath - Path to the worktree\n\t * @param options - Merge options (dryRun, force)\n\t * @throws Error if main branch doesn't exist, uncommitted changes exist, or conflicts occur\n\t */\n\tasync rebaseOnMain(worktreePath: string, options: MergeOptions = {}): Promise<RebaseOutcome> {\n\t\tconst { dryRun = false, force = false, jsonStream = false } = options\n\n\t\t// Pre-check: abort any in-progress rebase before starting a new one\n\t\tawait this.abortInProgressRebase(worktreePath)\n\n\t\tconst mainBranch = await this.getMainBranch(worktreePath)\n\n\t\t// Determine whether to use remote (origin/) or local branch reference\n\t\t// - Child looms: always use local parent branch (parent may not be pushed)\n\t\t// - PR modes (pr, draft-pr) for non-child: fetch and use origin/{branch}\n\t\t// - Local mode: use local branch (no fetch)\n\t\tconst metadata = await this.metadataManager.readMetadata(worktreePath)\n\t\tconst isChildLoom = !!metadata?.parentLoom\n\t\tconst settings = await this.settingsManager.loadSettings(worktreePath)\n\t\tconst mergeBehaviorMode = settings.mergeBehavior?.mode ?? 'local'\n\t\tconst isPRMode = mergeBehaviorMode === 'pr' || mergeBehaviorMode === 'draft-pr'\n\t\tconst useRemote = isPRMode && !isChildLoom\n\n\t\tlet targetBranch: string\n\t\tif (useRemote) {\n\t\t\t// PR modes (non-child): fetch and use origin/{branch}\n\t\t\tgetLogger().info('Fetching from origin...')\n\t\t\tawait fetchOrigin(worktreePath)\n\t\t\ttargetBranch = `origin/${mainBranch}`\n\t\t} else {\n\t\t\t// Local mode or child loom: use local branch\n\t\t\tgetLogger().info(`Using local branch ${mainBranch} for rebase...`)\n\t\t\ttargetBranch = mainBranch\n\t\t}\n\n\t\tgetLogger().info(`Starting rebase on ${targetBranch}...`)\n\n\t\t// Step 1: Check if branch exists (remote ref for origin/, local ref otherwise)\n\t\tconst refPath = useRemote ? `refs/remotes/${targetBranch}` : `refs/heads/${targetBranch}`\n\t\ttry {\n\t\t\tawait executeGitCommand(['show-ref', '--verify', '--quiet', refPath], {\n\t\t\t\tcwd: worktreePath,\n\t\t\t})\n\t\t} catch {\n\t\t\tif (useRemote) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Remote branch \"${targetBranch}\" does not exist. Cannot rebase.\\n` +\n\t\t\t\t\t\t`Ensure the repository has a \"${mainBranch}\" branch on origin.`\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Local branch \"${targetBranch}\" does not exist. Cannot rebase.\\n` +\n\t\t\t\t\t\t`Ensure the branch exists locally.`\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\t// Step 2: Check for uncommitted changes and create WIP commit if needed\n\t\tconst statusOutput = await executeGitCommand(['status', '--porcelain'], {\n\t\t\tcwd: worktreePath,\n\t\t})\n\n\t\tlet wipCommitHash: string | null = null\n\t\tif (statusOutput.trim()) {\n\t\t\tgetLogger().info('Uncommitted changes detected, creating temporary WIP commit...')\n\t\t\twipCommitHash = await this.createWipCommit(worktreePath)\n\t\t\tgetLogger().debug(`Created WIP commit: ${wipCommitHash}`)\n\t\t}\n\n\t\t// Step 3: Check if rebase is needed by comparing merge-base with target HEAD\n\t\tconst mergeBase = await executeGitCommand(['merge-base', targetBranch, 'HEAD'], {\n\t\t\tcwd: worktreePath,\n\t\t})\n\n\t\tconst targetHead = await executeGitCommand(['rev-parse', targetBranch], {\n\t\t\tcwd: worktreePath,\n\t\t})\n\n\t\tconst mergeBaseTrimmed = mergeBase.trim()\n\t\tconst targetHeadTrimmed = targetHead.trim()\n\n\t\t// If merge-base matches target HEAD, branch is already up to date\n\t\tif (mergeBaseTrimmed === targetHeadTrimmed) {\n\t\t\tgetLogger().success(`Branch is already up to date with ${targetBranch}. No rebase needed.`)\n\t\t\t// Restore WIP commit if created (soft reset to remove temporary commit)\n\t\t\tif (wipCommitHash) {\n\t\t\t\tawait this.restoreWipCommit(worktreePath, wipCommitHash)\n\t\t\t}\n\t\t\treturn { conflictsDetected: false, claudeLaunched: false, conflictsResolved: false }\n\t\t}\n\n\t\t// Step 4: Show commits to be rebased (for informational purposes)\n\t\tconst commitsOutput = await executeGitCommand(['log', '--oneline', `${targetBranch}..HEAD`], {\n\t\t\tcwd: worktreePath,\n\t\t})\n\n\t\tconst commits = commitsOutput.trim()\n\t\tconst commitLines = commits ? commits.split('\\n') : []\n\n\t\tif (commits) {\n\t\t\t// Show commits that will be rebased\n\t\t\tgetLogger().info(`Found ${commitLines.length} commit(s) to rebase:`)\n\t\t\tcommitLines.forEach((commit) => getLogger().info(` ${commit}`))\n\t\t} else {\n\t\t\t// Target has moved forward but branch has no new commits\n\t\t\tgetLogger().info(`${targetBranch} has moved forward. Rebasing to update branch...`)\n\t\t}\n\n\t\t// Step 5: User confirmation (unless force mode or dry-run)\n\t\tif (!force && !dryRun) {\n\t\t\t// TODO: Implement interactive prompt for confirmation\n\t\t\t// For now, proceeding automatically (use --force to skip this message)\n\t\t\tgetLogger().info('Proceeding with rebase... (use --force to skip confirmations)')\n\t\t}\n\n\t\t// Step 6: Execute rebase (unless dry-run)\n\t\tif (dryRun) {\n\t\t\tgetLogger().info(`[DRY RUN] Would execute: git rebase ${targetBranch}`)\n\t\t\tif (commitLines.length > 0) {\n\t\t\t\tgetLogger().info(`[DRY RUN] This would rebase ${commitLines.length} commit(s)`)\n\t\t\t}\n\t\t\treturn { conflictsDetected: false, claudeLaunched: false, conflictsResolved: false }\n\t\t}\n\n\t\t// Execute rebase\n\t\t// Use -c core.hooksPath=/dev/null to disable hooks during rebase\n\t\t// This prevents pre-commit hooks from running when commits are re-applied\n\t\ttry {\n\t\t\tawait executeGitCommand(['-c', 'core.hooksPath=/dev/null', 'rebase', targetBranch], { cwd: worktreePath })\n\t\t\tgetLogger().success('Rebase completed successfully!')\n\n\t\t\t// Restore WIP commit if created\n\t\t\tif (wipCommitHash) {\n\t\t\t\tawait this.restoreWipCommit(worktreePath, wipCommitHash)\n\t\t\t}\n\t\t\treturn { conflictsDetected: false, claudeLaunched: false, conflictsResolved: false }\n\t\t} catch (error) {\n\t\t\t// Detect conflicts\n\t\t\tconst conflictedFiles = await this.detectConflictedFiles(worktreePath)\n\n\t\t\tif (conflictedFiles.length > 0) {\n\t\t\t\t// Try Claude-assisted resolution first\n\t\t\t\tgetLogger().info('Merge conflicts detected, attempting Claude-assisted resolution...')\n\n\t\t\t\tconst resolved = await this.attemptClaudeConflictResolution(\n\t\t\t\t\tworktreePath,\n\t\t\t\t\tconflictedFiles,\n\t\t\t\t\t{ jsonStream }\n\t\t\t\t)\n\n\t\t\t\tif (resolved) {\n\t\t\t\t\tgetLogger().success('Conflicts resolved with Claude assistance, rebase completed')\n\n\t\t\t\t\t// Restore WIP commit if created\n\t\t\t\t\tif (wipCommitHash) {\n\t\t\t\t\t\tawait this.restoreWipCommit(worktreePath, wipCommitHash)\n\t\t\t\t\t}\n\t\t\t\t\treturn { conflictsDetected: true, claudeLaunched: true, conflictsResolved: true }\n\t\t\t\t}\n\n\t\t\t\t// Claude couldn't resolve or not available - fail fast\n\t\t\t\tconst conflictError = this.formatConflictError(conflictedFiles)\n\t\t\t\tthrow new Error(conflictError)\n\t\t\t}\n\n\t\t\t// If not a conflict, re-throw the original error\n\t\t\tthrow new Error(\n\t\t\t\t`Rebase failed: ${error instanceof Error ? error.message : String(error)}\\n` +\n\t\t\t\t\t'Run: git status for more details\\n' +\n\t\t\t\t\t'Or: git rebase --abort to cancel the rebase'\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Validate that fast-forward merge is possible\n\t * Ports bash/merge-and-clean.sh lines 957-968\n\t *\n\t * @param branchName - Name of the branch to merge\n\t * @param mainWorktreePath - Path where main branch is checked out\n\t * @throws Error if fast-forward is not possible\n\t */\n\tasync validateFastForwardPossible(mainBranch: string, branchName: string, mainWorktreePath: string): Promise<void> {\n\n\t\t// Step 1: Get merge-base between main and branch\n\t\tconst mergeBase = await executeGitCommand(['merge-base', mainBranch, branchName], {\n\t\t\tcwd: mainWorktreePath,\n\t\t})\n\n\t\t// Step 2: Get current HEAD of main\n\t\tconst mainHead = await executeGitCommand(['rev-parse', mainBranch], {\n\t\t\tcwd: mainWorktreePath,\n\t\t})\n\n\t\t// Step 3: Compare - they must match for fast-forward\n\t\tconst mergeBaseTrimmed = mergeBase.trim()\n\t\tconst mainHeadTrimmed = mainHead.trim()\n\n\t\tif (mergeBaseTrimmed !== mainHeadTrimmed) {\n\t\t\tthrow new Error(\n\t\t\t\t'Cannot perform fast-forward merge.\\n' +\n\t\t\t\t\t`The ${mainBranch} branch has moved forward since this branch was created.\\n` +\n\t\t\t\t\t`Merge base: ${mergeBaseTrimmed}\\n` +\n\t\t\t\t\t`Main HEAD: ${mainHeadTrimmed}\\n\\n` +\n\t\t\t\t\t'To fix this:\\n' +\n\t\t\t\t\t` 1. Rebase the branch on ${mainBranch}: git rebase ${mainBranch}\\n` +\n\t\t\t\t\t` 2. Or use: il finish to automatically rebase and merge\\n`\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Perform fast-forward only merge\n\t * Ports bash/merge-and-clean.sh lines 938-994\n\t *\n\t * @param branchName - Name of the branch to merge\n\t * @param worktreePath - Path to the worktree\n\t * @param options - Merge options (dryRun, force)\n\t * @throws Error if checkout, validation, or merge fails\n\t */\n\tasync performFastForwardMerge(\n\t\tbranchName: string,\n\t\tworktreePath: string,\n\t\toptions: MergeOptions = {}\n\t): Promise<void> {\n\t\tconst { dryRun = false, force = false } = options\n\n\t\tgetLogger().info('Starting fast-forward merge...')\n\n\t\t// Step 1: Get the merge target branch FIRST\n\t\t// For child looms, this will be the parent branch from metadata\n\t\t// For regular looms, this falls back to settings.mainBranch or 'main'\n\t\tconst mainBranch = await this.getMainBranch(worktreePath)\n\n\t\t// Step 2: Find where the merge target branch is checked out\n\t\t// CRITICAL: We must find the worktree for the MERGE TARGET, not settings.mainBranch\n\t\t// This fixes the child loom bug where we'd find the 'main' worktree instead of the parent branch worktree\n\t\tlet mainWorktreePath: string\n\t\tif (options.repoRoot) {\n\t\t\tmainWorktreePath = options.repoRoot\n\t\t} else {\n\t\t\ttry {\n\t\t\t\t// First try to find worktree with the exact merge target branch checked out\n\t\t\t\tmainWorktreePath = await findWorktreeForBranch(mainBranch, worktreePath)\n\t\t\t} catch {\n\t\t\t\t// Fallback: if no worktree has the branch checked out, use settings-based lookup\n\t\t\t\t// This handles edge cases like bare repos or detached HEAD states\n\t\t\t\tgetLogger().debug(`No worktree found for branch '${mainBranch}', falling back to settings-based lookup`)\n\t\t\t\tmainWorktreePath = await findMainWorktreePathWithSettings(worktreePath, this.settingsManager)\n\t\t\t}\n\t\t}\n\n\t\t// Step 3: No need to checkout - the merge target branch is already checked out in mainWorktreePath\n\t\tgetLogger().debug(`Using ${mainBranch} branch location: ${mainWorktreePath}`)\n\n\t\t// Step 4: Verify we're on the correct branch\n\t\tconst currentBranch = await executeGitCommand(['branch', '--show-current'], {\n\t\t\tcwd: mainWorktreePath,\n\t\t})\n\n\t\tif (currentBranch.trim() !== mainBranch) {\n\t\t\tthrow new Error(\n\t\t\t\t`Expected ${mainBranch} branch but found: ${currentBranch.trim()}\\n` +\n\t\t\t\t\t`At location: ${mainWorktreePath}\\n` +\n\t\t\t\t\t'This indicates the main worktree detection failed.'\n\t\t\t)\n\t\t}\n\n\t\t// Step 5: Validate fast-forward is possible\n\t\tawait this.validateFastForwardPossible(mainBranch, branchName, mainWorktreePath)\n\n\t\t// Step 6: Show commits to be merged\n\t\tconst commitsOutput = await executeGitCommand(['log', '--oneline', `${mainBranch}..${branchName}`], {\n\t\t\tcwd: mainWorktreePath,\n\t\t})\n\n\t\tconst commits = commitsOutput.trim()\n\n\t\t// If no commits, branch has no changes ahead of main\n\t\tif (!commits) {\n\t\t\tgetLogger().success(`Branch has no commits ahead of ${mainBranch}. No merge needed.`)\n\t\t\treturn\n\t\t}\n\n\t\t// Show commits that will be merged\n\t\tconst commitLines = commits.split('\\n')\n\t\tgetLogger().info(`Found ${commitLines.length} commit(s) to merge:`)\n\t\tcommitLines.forEach((commit) => getLogger().info(` ${commit}`))\n\n\t\t// Step 7: User confirmation (unless force mode or dry-run)\n\t\tif (!force && !dryRun) {\n\t\t\t// TODO: Implement interactive prompt for confirmation\n\t\t\t// For now, proceeding automatically (use --force to skip this message)\n\t\t\tgetLogger().info('Proceeding with fast-forward merge... (use --force to skip confirmations)')\n\t\t}\n\n\t\t// Step 8: Execute merge (unless dry-run)\n\t\tif (dryRun) {\n\t\t\tgetLogger().info(`[DRY RUN] Would execute: git merge --ff-only ${branchName}`)\n\t\t\tgetLogger().info(`[DRY RUN] This would merge ${commitLines.length} commit(s)`)\n\t\t\treturn\n\t\t}\n\n\t\t// Execute fast-forward merge\n\t\ttry {\n\t\t\tgetLogger().debug(`Executing fast-forward merge of ${branchName} into ${mainBranch} using cwd: ${mainWorktreePath}...`)\n\t\t\tawait executeGitCommand(['merge', '--ff-only', branchName], { cwd: mainWorktreePath })\n\t\t\tgetLogger().success(`Fast-forward merge completed! Merged ${commitLines.length} commit(s).`)\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`Fast-forward merge failed: ${error instanceof Error ? error.message : String(error)}\\n\\n` +\n\t\t\t\t\t'To recover:\\n' +\n\t\t\t\t\t' 1. Check merge status: git status\\n' +\n\t\t\t\t\t' 2. Abort merge if needed: git merge --abort\\n' +\n\t\t\t\t\t' 3. Verify branch is rebased: git rebase main\\n' +\n\t\t\t\t\t' 4. Try merge again: il finish'\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Helper: Detect conflicted files after failed rebase\n\t * @private\n\t */\n\tprivate async detectConflictedFiles(worktreePath: string): Promise<string[]> {\n\t\ttry {\n\t\t\tconst output = await executeGitCommand(['diff', '--name-only', '--diff-filter=U'], {\n\t\t\t\tcwd: worktreePath,\n\t\t\t})\n\n\t\t\treturn output\n\t\t\t\t.trim()\n\t\t\t\t.split('\\n')\n\t\t\t\t.filter((file) => file.length > 0)\n\t\t} catch {\n\t\t\t// If command fails, return empty array (might not be a conflict)\n\t\t\treturn []\n\t\t}\n\t}\n\n\t/**\n\t * Create a temporary WIP commit to preserve uncommitted changes during rebase\n\t * Stages all changes (tracked, untracked) using git add -A\n\t * Uses --no-verify to skip pre-commit hooks since this is a temporary internal commit\n\t * @param worktreePath - Path to the worktree\n\t * @returns The commit hash of the WIP commit\n\t * @private\n\t */\n\tprivate async createWipCommit(worktreePath: string): Promise<string> {\n\t\t// Stage all changes including untracked files\n\t\tawait executeGitCommand(['add', '-A'], { cwd: worktreePath })\n\n\t\t// Create WIP commit with distinctive message\n\t\t// Use --no-verify to skip pre-commit hooks - this is a temporary internal commit\n\t\tawait executeGitCommand(['commit', '--no-verify', '-m', 'WIP: Auto-stash for rebase'], { cwd: worktreePath })\n\n\t\t// Get and return the commit hash\n\t\tconst hash = await executeGitCommand(['rev-parse', 'HEAD'], { cwd: worktreePath })\n\t\treturn hash.trim()\n\t}\n\n\t/**\n\t * Restore uncommitted changes from WIP commit via soft reset\n\t * Logs warning but does not fail if soft reset fails (changes are safe in commit history)\n\t * @param worktreePath - Path to the worktree\n\t * @param wipCommitHash - Original WIP commit hash for verification logging\n\t * @private\n\t */\n\tprivate async restoreWipCommit(worktreePath: string, wipCommitHash: string): Promise<void> {\n\t\tgetLogger().info('Restoring uncommitted changes from WIP commit...')\n\n\t\ttry {\n\t\t\t// Soft reset to parent - changes become staged\n\t\t\tawait executeGitCommand(['reset', '--soft', 'HEAD~1'], { cwd: worktreePath })\n\n\t\t\t// Unstage files to restore to original working directory state\n\t\t\tawait executeGitCommand(['reset', 'HEAD'], { cwd: worktreePath })\n\n\t\t\tgetLogger().success('Restored uncommitted changes from WIP commit')\n\t\t} catch (error) {\n\t\t\t// Log warning but consider rebase successful - work is not lost\n\t\t\tgetLogger().warn(\n\t\t\t\t`Failed to restore WIP commit (${wipCommitHash}). ` +\n\t\t\t\t\t`Your changes are safe in the commit history. ` +\n\t\t\t\t\t`Manual recovery: git reset --soft HEAD~1`,\n\t\t\t\t{ error: error instanceof Error ? error.message : String(error) }\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Helper: Format conflict error message with manual resolution steps\n\t * @private\n\t */\n\tprivate formatConflictError(conflictedFiles: string[]): string {\n\t\tconst fileList = conflictedFiles.map((file) => ` • ${file}`).join('\\n')\n\n\t\treturn (\n\t\t\t'Rebase failed - merge conflicts detected in:\\n' +\n\t\t\tfileList +\n\t\t\t'\\n\\n' +\n\t\t\t'To resolve manually:\\n' +\n\t\t\t' 1. Fix conflicts in the files above\\n' +\n\t\t\t' 2. Stage resolved files: git add <files>\\n' +\n\t\t\t' 3. Continue rebase: git rebase --continue\\n' +\n\t\t\t' 4. Or abort rebase: git rebase --abort\\n' +\n\t\t\t' 5. Then re-run: il finish <issue-number>'\n\t\t)\n\t}\n\n\t/**\n\t * Attempt to resolve conflicts using Claude\n\t * Ports bash/merge-and-clean.sh lines 839-894\n\t *\n\t * @param worktreePath - Path to the worktree\n\t * @param conflictedFiles - List of files with conflicts\n\t * @returns true if conflicts resolved, false otherwise\n\t * @private\n\t */\n\tprivate async attemptClaudeConflictResolution(\n\t\tworktreePath: string,\n\t\tconflictedFiles: string[],\n\t\toptions: { jsonStream?: boolean } = {}\n\t): Promise<boolean> {\n\t\t// Check if Claude CLI is available\n\t\tconst isClaudeAvailable = await detectClaudeCli()\n\t\tif (!isClaudeAvailable) {\n\t\t\tgetLogger().debug('Claude CLI not available, skipping conflict resolution')\n\t\t\treturn false\n\t\t}\n\n\t\tgetLogger().info(`Launching Claude to resolve conflicts in ${conflictedFiles.length} file(s)...`)\n\n\t\t// Hard-coded prompt matching bash script line 844\n\t\t// No templates, no complexity - just the essential instruction\n\t\tconst systemPrompt =\n\t\t\t`Please help resolve the git rebase conflicts in this repository. ` +\n\t\t\t`Analyze the conflicted files, understand the changes from both branches, ` +\n\t\t\t`fix the conflicts, then run 'git add .' to stage the resolved files, ` +\n\t\t\t`and finally run 'git rebase --continue' to continue the rebase process. ` +\n\t\t\t`Once the issue is resolved, tell the user they can use /exit to continue with the process.`\n\n\t\tconst prompt =\n\t\t\t`Help me with this rebase please.`\n\n\t\t// Git commands to auto-approve during rebase conflict resolution\n\t\t// These are the essential commands Claude needs to analyze and resolve conflicts\n\t\t// Note: git reset and git checkout are intentionally excluded as they can be destructive\n\t\tconst rebaseAllowedTools = [\n\t\t\t'Bash(git status:*)',\n\t\t\t'Bash(git diff:*)',\n\t\t\t'Bash(git log:*)',\n\t\t\t'Bash(git add:*)',\n\t\t\t'Bash(git rebase:*)',\n\t\t\t'Bash(GIT_EDITOR=true git rebase:*)',\n\t\t]\n\n\t\ttry {\n\t\t\t// Launch Claude interactively in current terminal\n\t\t\t// User will interact directly with Claude to resolve conflicts\n\t\t\t// When jsonStream is true, run headless with stdout passthrough for JSONL streaming\n\t\t\tawait launchClaude(prompt, {\n\t\t\t\tappendSystemPrompt: systemPrompt,\n\t\t\t\taddDir: worktreePath,\n\t\t\t\theadless: options.jsonStream ? true : false,\n\t\t\t\t...(options.jsonStream && {\n\t\t\t\t\tpermissionMode: 'bypassPermissions' as const,\n\t\t\t\t\tpassthroughStdout: true,\n\t\t\t\t}),\n\t\t\t\tallowedTools: rebaseAllowedTools,\n\t\t\t\tnoSessionPersistence: true, // Utility operation - no session persistence needed\n\t\t\t})\n\n\t\t\t// After Claude interaction completes, check if conflicts resolved\n\t\t\tconst remainingConflicts = await this.detectConflictedFiles(worktreePath)\n\n\t\t\tif (remainingConflicts.length > 0) {\n\t\t\t\tgetLogger().warn(\n\t\t\t\t\t`Conflicts still exist in ${remainingConflicts.length} file(s) after Claude assistance`\n\t\t\t\t)\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Check if rebase completed or still in progress\n\t\t\tconst rebaseInProgress = await this.isRebaseInProgress(worktreePath)\n\n\t\t\tif (rebaseInProgress) {\n\t\t\t\tgetLogger().warn('Rebase still in progress after Claude assistance')\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\treturn true\n\t\t} catch (error) {\n\t\t\tgetLogger().warn('Claude conflict resolution failed', {\n\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t})\n\t\t\treturn false\n\t\t}\n\t}\n\n\t/**\n\t * Check if a git rebase is currently in progress\n\t * Checks for .git/rebase-merge or .git/rebase-apply directories\n\t * Ports bash script logic from lines 853-856\n\t *\n\t * @param worktreePath - Path to the worktree\n\t * @returns true if rebase in progress, false otherwise\n\t * @private\n\t */\n\tprivate async isRebaseInProgress(worktreePath: string): Promise<boolean> {\n\t\tconst fs = await import('node:fs/promises')\n\t\tconst path = await import('node:path')\n\n\t\t// In git worktrees, .git is a file pointing to the actual git dir.\n\t\t// Use git rev-parse to resolve the real git directory.\n\t\tconst gitDir = (await executeGitCommand(\n\t\t\t['rev-parse', '--absolute-git-dir'],\n\t\t\t{ cwd: worktreePath }\n\t\t)).trim()\n\n\t\tconst rebaseMergePath = path.join(gitDir, 'rebase-merge')\n\t\tconst rebaseApplyPath = path.join(gitDir, 'rebase-apply')\n\n\t\t// Check for rebase-merge directory\n\t\ttry {\n\t\t\tawait fs.access(rebaseMergePath)\n\t\t\treturn true\n\t\t} catch {\n\t\t\t// Directory doesn't exist, continue checking\n\t\t}\n\n\t\t// Check for rebase-apply directory\n\t\ttry {\n\t\t\tawait fs.access(rebaseApplyPath)\n\t\t\treturn true\n\t\t} catch {\n\t\t\t// Directory doesn't exist\n\t\t}\n\n\t\treturn false\n\t}\n\n\t/**\n\t * Abort an in-progress rebase if one is detected\n\t * This handles cases where a previous rebase was interrupted (e.g., terminal closed,\n\t * Claude session ended, user manually stopped) and the worktree is left in a dirty state.\n\t * Since we're about to start a new rebase, the stale rebase state is irrelevant and safe to abort.\n\t *\n\t * @param worktreePath - Path to the worktree\n\t * @private\n\t */\n\tprivate async abortInProgressRebase(worktreePath: string): Promise<void> {\n\t\tconst rebaseInProgress = await this.isRebaseInProgress(worktreePath)\n\n\t\tif (!rebaseInProgress) {\n\t\t\treturn\n\t\t}\n\n\t\tgetLogger().warn('A rebase is already in progress. Aborting the stale rebase before proceeding...')\n\n\t\ttry {\n\t\t\tawait executeGitCommand(['rebase', '--abort'], { cwd: worktreePath })\n\t\t\tgetLogger().info('Stale rebase aborted successfully.')\n\t\t} catch (error) {\n\t\t\t// Handle race condition: rebase may have been resolved between check and abort\n\t\t\tconst errorMsg = error instanceof Error ? error.message : String(error)\n\t\t\tif (errorMsg.includes('No rebase in progress')) {\n\t\t\t\tgetLogger().info('Rebase was already resolved by another process.')\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (error instanceof GitCommandError) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Failed to abort in-progress rebase: ${error.message}\\n` +\n\t\t\t\t\t\t'Manual recovery: run \"git rebase --abort\" in the worktree directory.'\n\t\t\t\t)\n\t\t\t}\n\t\t\tthrow error\n\t\t}\n\t}\n}\n","import { getLogger } from '../utils/logger-context.js'\nimport { detectPackageManager, runScript } from '../utils/package-manager.js'\nimport { getPackageConfig, hasScript } from '../utils/package-json.js'\nimport { ProjectCapabilityDetector } from './ProjectCapabilityDetector.js'\n\nexport interface BuildOptions {\n\tdryRun?: boolean\n}\n\nexport interface BuildResult {\n\tsuccess: boolean\n\tskipped: boolean\n\treason?: string\n\tduration: number\n}\n\n/**\n * BuildRunner handles post-merge build verification for CLI projects\n * Only runs build when project has CLI capabilities (bin field in package.json)\n */\nexport class BuildRunner {\n\tprivate capabilityDetector: ProjectCapabilityDetector\n\n\tconstructor(capabilityDetector?: ProjectCapabilityDetector) {\n\t\tthis.capabilityDetector = capabilityDetector ?? new ProjectCapabilityDetector()\n\t}\n\n\t/**\n\t * Run build verification in the specified directory\n\t * @param buildPath - Path where build should run (typically main worktree path)\n\t * @param options - Build options\n\t */\n\tasync runBuild(buildPath: string, options: BuildOptions = {}): Promise<BuildResult> {\n\t\tconst startTime = Date.now()\n\n\t\ttry {\n\t\t\t// Step 1: Check if build script exists (checks .iloom/package.iloom.json first, then package.json)\n\t\t\tconst pkgJson = await getPackageConfig(buildPath)\n\t\t\tconst hasBuildScript = hasScript(pkgJson, 'build')\n\n\t\t\tif (!hasBuildScript) {\n\t\t\tgetLogger().debug('Skipping build - no build script found')\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tskipped: true,\n\t\t\t\t\treason: 'No build script found in package configuration',\n\t\t\t\t\tduration: Date.now() - startTime,\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\t// Handle missing package.json - skip build for non-Node.js projects without package.iloom.json\n\t\t\tif (error instanceof Error && error.message.includes('package.json not found')) {\n\t\t\tgetLogger().debug('Skipping build - no package configuration found')\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tskipped: true,\n\t\t\t\t\treason: 'No package configuration found in project',\n\t\t\t\t\tduration: Date.now() - startTime,\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Re-throw other errors\n\t\t\tthrow error\n\t\t}\n\n\t\t// Step 2: Check if project has CLI capability (bin field)\n\t\tconst capabilities = await this.capabilityDetector.detectCapabilities(buildPath)\n\t\tconst isCLIProject = capabilities.capabilities.includes('cli')\n\n\t\tif (!isCLIProject) {\n\t\tgetLogger().debug('Skipping build - not a CLI project (no bin field)')\n\t\t\treturn {\n\t\t\t\tsuccess: true,\n\t\t\t\tskipped: true,\n\t\t\t\treason: 'Project is not a CLI project (no bin field in package.json)',\n\t\t\t\tduration: Date.now() - startTime,\n\t\t\t}\n\t\t}\n\n\t\t// Step 3: Detect package manager\n\t\tconst packageManager = await detectPackageManager(buildPath)\n\n\t\t// Step 4: Handle dry-run mode\n\t\tif (options.dryRun) {\n\t\t\tconst command =\n\t\t\t\tpackageManager === 'npm' ? 'npm run build' : `${packageManager} build`\n\t\tgetLogger().info(`[DRY RUN] Would run: ${command}`)\n\t\t\treturn {\n\t\t\t\tsuccess: true,\n\t\t\t\tskipped: false,\n\t\t\t\tduration: Date.now() - startTime,\n\t\t\t}\n\t\t}\n\n\t\t// Step 5: Execute build\n\tgetLogger().info('Running build...')\n\n\t\ttry {\n\t\t\tawait runScript('build', buildPath, [], { quiet: true })\n\t\tgetLogger().success('Build completed successfully')\n\n\t\t\treturn {\n\t\t\t\tsuccess: true,\n\t\t\t\tskipped: false,\n\t\t\t\tduration: Date.now() - startTime,\n\t\t\t}\n\t\t} catch {\n\t\t\t// Step 6: Throw detailed error on failure\n\t\t\tconst runCommand =\n\t\t\t\tpackageManager === 'npm' ? 'npm run build' : `${packageManager} build`\n\n\t\t\tthrow new Error(\n\t\t\t\t`Error: Build failed.\\n` +\n\t\t\t\t\t`Fix build errors before proceeding.\\n\\n` +\n\t\t\t\t\t`Run '${runCommand}' to see detailed errors.`\n\t\t\t)\n\t\t}\n\t}\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/lib/MergeManager.ts","../src/lib/BuildRunner.ts"],"sourcesContent":["import { executeGitCommand, fetchOrigin, findMainWorktreePathWithSettings, findWorktreeForBranch, getMergeTargetBranch, GitCommandError } from '../utils/git.js'\nimport { getLogger } from '../utils/logger-context.js'\nimport { detectClaudeCli, launchClaude } from '../utils/claude.js'\nimport { SettingsManager } from './SettingsManager.js'\nimport { MetadataManager } from './MetadataManager.js'\nimport type { MergeOptions, RebaseOutcome } from '../types/index.js'\n\n/**\n * MergeManager handles Git rebase and fast-forward merge operations\n * Implements fail-fast behavior for conflicts (Phase 1 - no Claude assistance)\n *\n * Ports bash/merge-and-clean.sh lines 781-1090\n */\nexport class MergeManager {\n\tprivate settingsManager: SettingsManager\n\tprivate metadataManager: MetadataManager\n\n\tconstructor(settingsManager?: SettingsManager, metadataManager?: MetadataManager) {\n\t\tthis.settingsManager = settingsManager ?? new SettingsManager()\n\t\tthis.metadataManager = metadataManager ?? new MetadataManager()\n\t}\n\n\t/**\n\t * Get the merge target branch for a loom\n\t * Priority: parent loom metadata > configured main branch > 'main'\n\t * @param worktreePath - Optional path to load settings/metadata from (defaults to process.cwd())\n\t * @private\n\t */\n\tprivate async getMainBranch(worktreePath?: string): Promise<string> {\n\t\t// Delegate to shared utility function\n\t\treturn getMergeTargetBranch(worktreePath ?? process.cwd(), {\n\t\t\tsettingsManager: this.settingsManager,\n\t\t\tmetadataManager: this.metadataManager,\n\t\t})\n\t}\n\n\t/**\n\t * Rebase current branch on main with fail-fast on conflicts\n\t * Ports bash/merge-and-clean.sh lines 781-913\n\t *\n\t * @param worktreePath - Path to the worktree\n\t * @param options - Merge options (dryRun, force)\n\t * @throws Error if main branch doesn't exist, uncommitted changes exist, or conflicts occur\n\t */\n\tasync rebaseOnMain(worktreePath: string, options: MergeOptions = {}): Promise<RebaseOutcome> {\n\t\tconst { dryRun = false, force = false, jsonStream = false } = options\n\n\t\t// Pre-check: abort any in-progress rebase before starting a new one\n\t\tawait this.abortInProgressRebase(worktreePath)\n\n\t\tconst mainBranch = await this.getMainBranch(worktreePath)\n\n\t\t// Determine whether to use remote (origin/) or local branch reference\n\t\t// - Child looms: always use local parent branch (parent may not be pushed)\n\t\t// - PR modes (pr, draft-pr) for non-child: fetch and use origin/{branch}\n\t\t// - Local mode: use local branch (no fetch)\n\t\tconst metadata = await this.metadataManager.readMetadata(worktreePath)\n\t\tconst isChildLoom = !!metadata?.parentLoom\n\t\tconst settings = await this.settingsManager.loadSettings(worktreePath)\n\t\tconst mergeBehaviorMode = settings.mergeBehavior?.mode ?? 'local'\n\t\tconst isPRMode = mergeBehaviorMode === 'pr' || mergeBehaviorMode === 'draft-pr'\n\t\tconst useRemote = isPRMode && !isChildLoom\n\n\t\tlet targetBranch: string\n\t\tif (useRemote) {\n\t\t\t// PR modes (non-child): fetch and use origin/{branch}\n\t\t\tgetLogger().info('Fetching from origin...')\n\t\t\tawait fetchOrigin(worktreePath)\n\t\t\ttargetBranch = `origin/${mainBranch}`\n\t\t} else {\n\t\t\t// Local mode or child loom: use local branch\n\t\t\tgetLogger().info(`Using local branch ${mainBranch} for rebase...`)\n\t\t\ttargetBranch = mainBranch\n\t\t}\n\n\t\tgetLogger().info(`Starting rebase on ${targetBranch}...`)\n\n\t\t// Step 1: Check if branch exists (remote ref for origin/, local ref otherwise)\n\t\tconst refPath = useRemote ? `refs/remotes/${targetBranch}` : `refs/heads/${targetBranch}`\n\t\ttry {\n\t\t\tawait executeGitCommand(['show-ref', '--verify', '--quiet', refPath], {\n\t\t\t\tcwd: worktreePath,\n\t\t\t})\n\t\t} catch {\n\t\t\tif (useRemote) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Remote branch \"${targetBranch}\" does not exist. Cannot rebase.\\n` +\n\t\t\t\t\t\t`Ensure the repository has a \"${mainBranch}\" branch on origin.`\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Local branch \"${targetBranch}\" does not exist. Cannot rebase.\\n` +\n\t\t\t\t\t\t`Ensure the branch exists locally.`\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\t// Step 2: Check for uncommitted changes and create WIP commit if needed\n\t\tconst statusOutput = await executeGitCommand(['status', '--porcelain'], {\n\t\t\tcwd: worktreePath,\n\t\t})\n\n\t\tlet wipCommitHash: string | null = null\n\t\tif (statusOutput.trim()) {\n\t\t\tgetLogger().info('Uncommitted changes detected, creating temporary WIP commit...')\n\t\t\twipCommitHash = await this.createWipCommit(worktreePath)\n\t\t\tgetLogger().debug(`Created WIP commit: ${wipCommitHash}`)\n\t\t}\n\n\t\t// Step 3: Check if rebase is needed by comparing merge-base with target HEAD\n\t\tconst mergeBase = await executeGitCommand(['merge-base', targetBranch, 'HEAD'], {\n\t\t\tcwd: worktreePath,\n\t\t})\n\n\t\tconst targetHead = await executeGitCommand(['rev-parse', targetBranch], {\n\t\t\tcwd: worktreePath,\n\t\t})\n\n\t\tconst mergeBaseTrimmed = mergeBase.trim()\n\t\tconst targetHeadTrimmed = targetHead.trim()\n\n\t\t// If merge-base matches target HEAD, branch is already up to date\n\t\tif (mergeBaseTrimmed === targetHeadTrimmed) {\n\t\t\tgetLogger().success(`Branch is already up to date with ${targetBranch}. No rebase needed.`)\n\t\t\t// Restore WIP commit if created (soft reset to remove temporary commit)\n\t\t\tif (wipCommitHash) {\n\t\t\t\tawait this.restoreWipCommit(worktreePath, wipCommitHash)\n\t\t\t}\n\t\t\treturn { conflictsDetected: false, claudeLaunched: false, conflictsResolved: false }\n\t\t}\n\n\t\t// Step 4: Show commits to be rebased (for informational purposes)\n\t\tconst commitsOutput = await executeGitCommand(['log', '--oneline', `${targetBranch}..HEAD`], {\n\t\t\tcwd: worktreePath,\n\t\t})\n\n\t\tconst commits = commitsOutput.trim()\n\t\tconst commitLines = commits ? commits.split('\\n') : []\n\n\t\tif (commits) {\n\t\t\t// Show commits that will be rebased\n\t\t\tgetLogger().info(`Found ${commitLines.length} commit(s) to rebase:`)\n\t\t\tcommitLines.forEach((commit) => getLogger().info(` ${commit}`))\n\t\t} else {\n\t\t\t// Target has moved forward but branch has no new commits\n\t\t\tgetLogger().info(`${targetBranch} has moved forward. Rebasing to update branch...`)\n\t\t}\n\n\t\t// Step 5: User confirmation (unless force mode or dry-run)\n\t\tif (!force && !dryRun) {\n\t\t\t// TODO: Implement interactive prompt for confirmation\n\t\t\t// For now, proceeding automatically (use --force to skip this message)\n\t\t\tgetLogger().info('Proceeding with rebase... (use --force to skip confirmations)')\n\t\t}\n\n\t\t// Step 6: Execute rebase (unless dry-run)\n\t\tif (dryRun) {\n\t\t\tgetLogger().info(`[DRY RUN] Would execute: git rebase ${targetBranch}`)\n\t\t\tif (commitLines.length > 0) {\n\t\t\t\tgetLogger().info(`[DRY RUN] This would rebase ${commitLines.length} commit(s)`)\n\t\t\t}\n\t\t\treturn { conflictsDetected: false, claudeLaunched: false, conflictsResolved: false }\n\t\t}\n\n\t\t// Execute rebase\n\t\t// Use -c core.hooksPath=/dev/null to disable hooks during rebase\n\t\t// This prevents pre-commit hooks from running when commits are re-applied\n\t\ttry {\n\t\t\tawait executeGitCommand(['-c', 'core.hooksPath=/dev/null', 'rebase', targetBranch], { cwd: worktreePath })\n\t\t\tgetLogger().success('Rebase completed successfully!')\n\n\t\t\t// Restore WIP commit if created\n\t\t\tif (wipCommitHash) {\n\t\t\t\tawait this.restoreWipCommit(worktreePath, wipCommitHash)\n\t\t\t}\n\t\t\treturn { conflictsDetected: false, claudeLaunched: false, conflictsResolved: false }\n\t\t} catch (error) {\n\t\t\t// Detect conflicts\n\t\t\tconst conflictedFiles = await this.detectConflictedFiles(worktreePath)\n\n\t\t\tif (conflictedFiles.length > 0) {\n\t\t\t\t// Try Claude-assisted resolution first\n\t\t\t\tgetLogger().info('Merge conflicts detected, attempting Claude-assisted resolution...')\n\n\t\t\t\tconst resolved = await this.attemptClaudeConflictResolution(\n\t\t\t\t\tworktreePath,\n\t\t\t\t\tconflictedFiles,\n\t\t\t\t\t{ jsonStream }\n\t\t\t\t)\n\n\t\t\t\tif (resolved) {\n\t\t\t\t\tgetLogger().success('Conflicts resolved with Claude assistance, rebase completed')\n\n\t\t\t\t\t// Restore WIP commit if created\n\t\t\t\t\tif (wipCommitHash) {\n\t\t\t\t\t\tawait this.restoreWipCommit(worktreePath, wipCommitHash)\n\t\t\t\t\t}\n\t\t\t\t\treturn { conflictsDetected: true, claudeLaunched: true, conflictsResolved: true }\n\t\t\t\t}\n\n\t\t\t\t// Claude couldn't resolve or not available - fail fast\n\t\t\t\tconst conflictError = this.formatConflictError(conflictedFiles)\n\t\t\t\tthrow new Error(conflictError)\n\t\t\t}\n\n\t\t\t// If not a conflict, re-throw the original error\n\t\t\tthrow new Error(\n\t\t\t\t`Rebase failed: ${error instanceof Error ? error.message : String(error)}\\n` +\n\t\t\t\t\t'Run: git status for more details\\n' +\n\t\t\t\t\t'Or: git rebase --abort to cancel the rebase'\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Validate that fast-forward merge is possible\n\t * Ports bash/merge-and-clean.sh lines 957-968\n\t *\n\t * @param branchName - Name of the branch to merge\n\t * @param mainWorktreePath - Path where main branch is checked out\n\t * @throws Error if fast-forward is not possible\n\t */\n\tasync validateFastForwardPossible(mainBranch: string, branchName: string, mainWorktreePath: string): Promise<void> {\n\n\t\t// Step 1: Get merge-base between main and branch\n\t\tconst mergeBase = await executeGitCommand(['merge-base', mainBranch, branchName], {\n\t\t\tcwd: mainWorktreePath,\n\t\t})\n\n\t\t// Step 2: Get current HEAD of main\n\t\tconst mainHead = await executeGitCommand(['rev-parse', mainBranch], {\n\t\t\tcwd: mainWorktreePath,\n\t\t})\n\n\t\t// Step 3: Compare - they must match for fast-forward\n\t\tconst mergeBaseTrimmed = mergeBase.trim()\n\t\tconst mainHeadTrimmed = mainHead.trim()\n\n\t\tif (mergeBaseTrimmed !== mainHeadTrimmed) {\n\t\t\tthrow new Error(\n\t\t\t\t'Cannot perform fast-forward merge.\\n' +\n\t\t\t\t\t`The ${mainBranch} branch has moved forward since this branch was created.\\n` +\n\t\t\t\t\t`Merge base: ${mergeBaseTrimmed}\\n` +\n\t\t\t\t\t`Main HEAD: ${mainHeadTrimmed}\\n\\n` +\n\t\t\t\t\t'To fix this:\\n' +\n\t\t\t\t\t` 1. Rebase the branch on ${mainBranch}: git rebase ${mainBranch}\\n` +\n\t\t\t\t\t` 2. Or use: il finish to automatically rebase and merge\\n`\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Perform fast-forward only merge\n\t * Ports bash/merge-and-clean.sh lines 938-994\n\t *\n\t * @param branchName - Name of the branch to merge\n\t * @param worktreePath - Path to the worktree\n\t * @param options - Merge options (dryRun, force)\n\t * @throws Error if checkout, validation, or merge fails\n\t */\n\tasync performFastForwardMerge(\n\t\tbranchName: string,\n\t\tworktreePath: string,\n\t\toptions: MergeOptions = {}\n\t): Promise<void> {\n\t\tconst { dryRun = false, force = false } = options\n\n\t\tgetLogger().info('Starting fast-forward merge...')\n\n\t\t// Step 1: Get the merge target branch FIRST\n\t\t// For child looms, this will be the parent branch from metadata\n\t\t// For regular looms, this falls back to settings.mainBranch or 'main'\n\t\tconst mainBranch = await this.getMainBranch(worktreePath)\n\n\t\t// Step 2: Find where the merge target branch is checked out\n\t\t// CRITICAL: We must find the worktree for the MERGE TARGET, not settings.mainBranch\n\t\t// This fixes the child loom bug where we'd find the 'main' worktree instead of the parent branch worktree\n\t\tlet mainWorktreePath: string\n\t\tif (options.repoRoot) {\n\t\t\tmainWorktreePath = options.repoRoot\n\t\t} else {\n\t\t\ttry {\n\t\t\t\t// First try to find worktree with the exact merge target branch checked out\n\t\t\t\tmainWorktreePath = await findWorktreeForBranch(mainBranch, worktreePath)\n\t\t\t} catch {\n\t\t\t\t// Fallback: if no worktree has the branch checked out, use settings-based lookup\n\t\t\t\t// This handles edge cases like bare repos or detached HEAD states\n\t\t\t\tgetLogger().debug(`No worktree found for branch '${mainBranch}', falling back to settings-based lookup`)\n\t\t\t\tmainWorktreePath = await findMainWorktreePathWithSettings(worktreePath, this.settingsManager)\n\t\t\t}\n\t\t}\n\n\t\t// Step 3: No need to checkout - the merge target branch is already checked out in mainWorktreePath\n\t\tgetLogger().debug(`Using ${mainBranch} branch location: ${mainWorktreePath}`)\n\n\t\t// Step 4: Verify we're on the correct branch\n\t\tconst currentBranch = await executeGitCommand(['branch', '--show-current'], {\n\t\t\tcwd: mainWorktreePath,\n\t\t})\n\n\t\tif (currentBranch.trim() !== mainBranch) {\n\t\t\tthrow new Error(\n\t\t\t\t`Expected ${mainBranch} branch but found: ${currentBranch.trim()}\\n` +\n\t\t\t\t\t`At location: ${mainWorktreePath}\\n` +\n\t\t\t\t\t'This indicates the main worktree detection failed.'\n\t\t\t)\n\t\t}\n\n\t\t// Step 5: Validate fast-forward is possible\n\t\tawait this.validateFastForwardPossible(mainBranch, branchName, mainWorktreePath)\n\n\t\t// Step 6: Show commits to be merged\n\t\tconst commitsOutput = await executeGitCommand(['log', '--oneline', `${mainBranch}..${branchName}`], {\n\t\t\tcwd: mainWorktreePath,\n\t\t})\n\n\t\tconst commits = commitsOutput.trim()\n\n\t\t// If no commits, branch has no changes ahead of main\n\t\tif (!commits) {\n\t\t\tgetLogger().success(`Branch has no commits ahead of ${mainBranch}. No merge needed.`)\n\t\t\treturn\n\t\t}\n\n\t\t// Show commits that will be merged\n\t\tconst commitLines = commits.split('\\n')\n\t\tgetLogger().info(`Found ${commitLines.length} commit(s) to merge:`)\n\t\tcommitLines.forEach((commit) => getLogger().info(` ${commit}`))\n\n\t\t// Step 7: User confirmation (unless force mode or dry-run)\n\t\tif (!force && !dryRun) {\n\t\t\t// TODO: Implement interactive prompt for confirmation\n\t\t\t// For now, proceeding automatically (use --force to skip this message)\n\t\t\tgetLogger().info('Proceeding with fast-forward merge... (use --force to skip confirmations)')\n\t\t}\n\n\t\t// Step 8: Execute merge (unless dry-run)\n\t\tif (dryRun) {\n\t\t\tgetLogger().info(`[DRY RUN] Would execute: git merge --ff-only ${branchName}`)\n\t\t\tgetLogger().info(`[DRY RUN] This would merge ${commitLines.length} commit(s)`)\n\t\t\treturn\n\t\t}\n\n\t\t// Execute fast-forward merge\n\t\ttry {\n\t\t\tgetLogger().debug(`Executing fast-forward merge of ${branchName} into ${mainBranch} using cwd: ${mainWorktreePath}...`)\n\t\t\tawait executeGitCommand(['merge', '--ff-only', branchName], { cwd: mainWorktreePath })\n\t\t\tgetLogger().success(`Fast-forward merge completed! Merged ${commitLines.length} commit(s).`)\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`Fast-forward merge failed: ${error instanceof Error ? error.message : String(error)}\\n\\n` +\n\t\t\t\t\t'To recover:\\n' +\n\t\t\t\t\t' 1. Check merge status: git status\\n' +\n\t\t\t\t\t' 2. Abort merge if needed: git merge --abort\\n' +\n\t\t\t\t\t' 3. Verify branch is rebased: git rebase main\\n' +\n\t\t\t\t\t' 4. Try merge again: il finish'\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Helper: Detect conflicted files after failed rebase\n\t * @private\n\t */\n\tprivate async detectConflictedFiles(worktreePath: string): Promise<string[]> {\n\t\ttry {\n\t\t\tconst output = await executeGitCommand(['diff', '--name-only', '--diff-filter=U'], {\n\t\t\t\tcwd: worktreePath,\n\t\t\t})\n\n\t\t\treturn output\n\t\t\t\t.trim()\n\t\t\t\t.split('\\n')\n\t\t\t\t.filter((file) => file.length > 0)\n\t\t} catch {\n\t\t\t// If command fails, return empty array (might not be a conflict)\n\t\t\treturn []\n\t\t}\n\t}\n\n\t/**\n\t * Create a temporary WIP commit to preserve uncommitted changes during rebase\n\t * Stages all changes (tracked, untracked) using git add -A\n\t * Uses --no-verify to skip pre-commit hooks since this is a temporary internal commit\n\t * @param worktreePath - Path to the worktree\n\t * @returns The commit hash of the WIP commit\n\t * @private\n\t */\n\tprivate async createWipCommit(worktreePath: string): Promise<string> {\n\t\t// Stage all changes including untracked files\n\t\tawait executeGitCommand(['add', '-A'], { cwd: worktreePath })\n\n\t\t// Create WIP commit with distinctive message\n\t\t// Use --no-verify to skip pre-commit hooks - this is a temporary internal commit\n\t\tawait executeGitCommand(['commit', '--no-verify', '-m', 'WIP: Auto-stash for rebase'], { cwd: worktreePath })\n\n\t\t// Get and return the commit hash\n\t\tconst hash = await executeGitCommand(['rev-parse', 'HEAD'], { cwd: worktreePath })\n\t\treturn hash.trim()\n\t}\n\n\t/**\n\t * Restore uncommitted changes from WIP commit via soft reset\n\t * Logs warning but does not fail if soft reset fails (changes are safe in commit history)\n\t * @param worktreePath - Path to the worktree\n\t * @param wipCommitHash - Original WIP commit hash for verification logging\n\t * @private\n\t */\n\tprivate async restoreWipCommit(worktreePath: string, wipCommitHash: string): Promise<void> {\n\t\tgetLogger().info('Restoring uncommitted changes from WIP commit...')\n\n\t\ttry {\n\t\t\t// Soft reset to parent - changes become staged\n\t\t\tawait executeGitCommand(['reset', '--soft', 'HEAD~1'], { cwd: worktreePath })\n\n\t\t\t// Unstage files to restore to original working directory state\n\t\t\tawait executeGitCommand(['reset', 'HEAD'], { cwd: worktreePath })\n\n\t\t\tgetLogger().success('Restored uncommitted changes from WIP commit')\n\t\t} catch (error) {\n\t\t\t// Log warning but consider rebase successful - work is not lost\n\t\t\tgetLogger().warn(\n\t\t\t\t`Failed to restore WIP commit (${wipCommitHash}). ` +\n\t\t\t\t\t`Your changes are safe in the commit history. ` +\n\t\t\t\t\t`Manual recovery: git reset --soft HEAD~1`,\n\t\t\t\t{ error: error instanceof Error ? error.message : String(error) }\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Helper: Format conflict error message with manual resolution steps\n\t * @private\n\t */\n\tprivate formatConflictError(conflictedFiles: string[]): string {\n\t\tconst fileList = conflictedFiles.map((file) => ` • ${file}`).join('\\n')\n\n\t\treturn (\n\t\t\t'Rebase failed - merge conflicts detected in:\\n' +\n\t\t\tfileList +\n\t\t\t'\\n\\n' +\n\t\t\t'To resolve manually:\\n' +\n\t\t\t' 1. Fix conflicts in the files above\\n' +\n\t\t\t' 2. Stage resolved files: git add <files>\\n' +\n\t\t\t' 3. Continue rebase: git rebase --continue\\n' +\n\t\t\t' 4. Or abort rebase: git rebase --abort\\n' +\n\t\t\t' 5. Then re-run: il finish <issue-number>'\n\t\t)\n\t}\n\n\t/**\n\t * Attempt to resolve conflicts using Claude\n\t * Ports bash/merge-and-clean.sh lines 839-894\n\t *\n\t * @param worktreePath - Path to the worktree\n\t * @param conflictedFiles - List of files with conflicts\n\t * @returns true if conflicts resolved, false otherwise\n\t * @private\n\t */\n\tprivate async attemptClaudeConflictResolution(\n\t\tworktreePath: string,\n\t\tconflictedFiles: string[],\n\t\toptions: { jsonStream?: boolean } = {}\n\t): Promise<boolean> {\n\t\t// Check if Claude CLI is available\n\t\tconst isClaudeAvailable = await detectClaudeCli()\n\t\tif (!isClaudeAvailable) {\n\t\t\tgetLogger().debug('Claude CLI not available, skipping conflict resolution')\n\t\t\treturn false\n\t\t}\n\n\t\tgetLogger().info(`Launching Claude to resolve conflicts in ${conflictedFiles.length} file(s)...`)\n\n\t\t// Hard-coded prompt matching bash script line 844\n\t\t// No templates, no complexity - just the essential instruction\n\t\tconst systemPrompt =\n\t\t\t`Please help resolve the git rebase conflicts in this repository. ` +\n\t\t\t`Analyze the conflicted files, understand the changes from both branches, ` +\n\t\t\t`fix the conflicts, then run 'git add .' to stage the resolved files, ` +\n\t\t\t`and finally run 'git rebase --continue' to continue the rebase process. ` +\n\t\t\t`Once the issue is resolved, tell the user they can use /exit to continue with the process.`\n\n\t\tconst prompt =\n\t\t\t`Help me with this rebase please.`\n\n\t\t// Tools to auto-approve during rebase conflict resolution\n\t\t// Includes core file tools for reading/editing conflicted files,\n\t\t// plus the essential git commands Claude needs to analyze and resolve conflicts\n\t\t// Note: git reset and git checkout are intentionally excluded as they can be destructive\n\t\tconst rebaseAllowedTools = [\n\t\t\t'Read',\n\t\t\t'Grep',\n\t\t\t'Glob',\n\t\t\t'Bash(git status:*)',\n\t\t\t'Bash(git diff:*)',\n\t\t\t'Bash(git log:*)',\n\t\t\t'Bash(git show:*)',\n\t\t\t'Bash(git add:*)',\n\t\t\t'Bash(git rebase:*)',\n\t\t\t'Bash(GIT_EDITOR=true git rebase:*)',\n\t\t]\n\n\t\ttry {\n\t\t\t// Launch Claude interactively in current terminal\n\t\t\t// User will interact directly with Claude to resolve conflicts\n\t\t\t// When jsonStream is true, run headless with stdout passthrough for JSONL streaming\n\t\t\tawait launchClaude(prompt, {\n\t\t\t\tappendSystemPrompt: systemPrompt,\n\t\t\t\taddDir: worktreePath,\n\t\t\t\theadless: options.jsonStream ? true : false,\n\t\t\t\t...(options.jsonStream && { permissionMode: 'bypassPermissions' as const }),\n\t\t\t\t...(options.jsonStream && {\n\t\t\t\t\tpassthroughStdout: true,\n\t\t\t\t}),\n\t\t\t\tallowedTools: rebaseAllowedTools,\n\t\t\t\tnoSessionPersistence: true, // Utility operation - no session persistence needed\n\t\t\t})\n\n\t\t\t// After Claude interaction completes, check if conflicts resolved\n\t\t\tconst remainingConflicts = await this.detectConflictedFiles(worktreePath)\n\n\t\t\tif (remainingConflicts.length > 0) {\n\t\t\t\tgetLogger().warn(\n\t\t\t\t\t`Conflicts still exist in ${remainingConflicts.length} file(s) after Claude assistance`\n\t\t\t\t)\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Check if rebase completed or still in progress\n\t\t\tconst rebaseInProgress = await this.isRebaseInProgress(worktreePath)\n\n\t\t\tif (rebaseInProgress) {\n\t\t\t\tgetLogger().warn('Rebase still in progress after Claude assistance')\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\treturn true\n\t\t} catch (error) {\n\t\t\tgetLogger().warn('Claude conflict resolution failed', {\n\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t})\n\t\t\treturn false\n\t\t}\n\t}\n\n\t/**\n\t * Check if a git rebase is currently in progress\n\t * Checks for .git/rebase-merge or .git/rebase-apply directories\n\t * Ports bash script logic from lines 853-856\n\t *\n\t * @param worktreePath - Path to the worktree\n\t * @returns true if rebase in progress, false otherwise\n\t * @private\n\t */\n\tprivate async isRebaseInProgress(worktreePath: string): Promise<boolean> {\n\t\tconst fs = await import('node:fs/promises')\n\t\tconst path = await import('node:path')\n\n\t\t// In git worktrees, .git is a file pointing to the actual git dir.\n\t\t// Use git rev-parse to resolve the real git directory.\n\t\tconst gitDir = (await executeGitCommand(\n\t\t\t['rev-parse', '--absolute-git-dir'],\n\t\t\t{ cwd: worktreePath }\n\t\t)).trim()\n\n\t\tconst rebaseMergePath = path.join(gitDir, 'rebase-merge')\n\t\tconst rebaseApplyPath = path.join(gitDir, 'rebase-apply')\n\n\t\t// Check for rebase-merge directory\n\t\ttry {\n\t\t\tawait fs.access(rebaseMergePath)\n\t\t\treturn true\n\t\t} catch {\n\t\t\t// Directory doesn't exist, continue checking\n\t\t}\n\n\t\t// Check for rebase-apply directory\n\t\ttry {\n\t\t\tawait fs.access(rebaseApplyPath)\n\t\t\treturn true\n\t\t} catch {\n\t\t\t// Directory doesn't exist\n\t\t}\n\n\t\treturn false\n\t}\n\n\t/**\n\t * Abort an in-progress rebase if one is detected\n\t * This handles cases where a previous rebase was interrupted (e.g., terminal closed,\n\t * Claude session ended, user manually stopped) and the worktree is left in a dirty state.\n\t * Since we're about to start a new rebase, the stale rebase state is irrelevant and safe to abort.\n\t *\n\t * @param worktreePath - Path to the worktree\n\t * @private\n\t */\n\tprivate async abortInProgressRebase(worktreePath: string): Promise<void> {\n\t\tconst rebaseInProgress = await this.isRebaseInProgress(worktreePath)\n\n\t\tif (!rebaseInProgress) {\n\t\t\treturn\n\t\t}\n\n\t\tgetLogger().warn('A rebase is already in progress. Aborting the stale rebase before proceeding...')\n\n\t\ttry {\n\t\t\tawait executeGitCommand(['rebase', '--abort'], { cwd: worktreePath })\n\t\t\tgetLogger().info('Stale rebase aborted successfully.')\n\t\t} catch (error) {\n\t\t\t// Handle race condition: rebase may have been resolved between check and abort\n\t\t\tconst errorMsg = error instanceof Error ? error.message : String(error)\n\t\t\tif (errorMsg.includes('No rebase in progress')) {\n\t\t\t\tgetLogger().info('Rebase was already resolved by another process.')\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (error instanceof GitCommandError) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Failed to abort in-progress rebase: ${error.message}\\n` +\n\t\t\t\t\t\t'Manual recovery: run \"git rebase --abort\" in the worktree directory.'\n\t\t\t\t)\n\t\t\t}\n\t\t\tthrow error\n\t\t}\n\t}\n}\n","import { getLogger } from '../utils/logger-context.js'\nimport { detectPackageManager, runScript } from '../utils/package-manager.js'\nimport { getPackageConfig, hasScript } from '../utils/package-json.js'\nimport { ProjectCapabilityDetector } from './ProjectCapabilityDetector.js'\n\nexport interface BuildOptions {\n\tdryRun?: boolean\n}\n\nexport interface BuildResult {\n\tsuccess: boolean\n\tskipped: boolean\n\treason?: string\n\tduration: number\n}\n\n/**\n * BuildRunner handles post-merge build verification for CLI projects\n * Only runs build when project has CLI capabilities (bin field in package.json)\n */\nexport class BuildRunner {\n\tprivate capabilityDetector: ProjectCapabilityDetector\n\n\tconstructor(capabilityDetector?: ProjectCapabilityDetector) {\n\t\tthis.capabilityDetector = capabilityDetector ?? new ProjectCapabilityDetector()\n\t}\n\n\t/**\n\t * Run build verification in the specified directory\n\t * @param buildPath - Path where build should run (typically main worktree path)\n\t * @param options - Build options\n\t */\n\tasync runBuild(buildPath: string, options: BuildOptions = {}): Promise<BuildResult> {\n\t\tconst startTime = Date.now()\n\n\t\ttry {\n\t\t\t// Step 1: Check if build script exists (checks .iloom/package.iloom.json first, then package.json)\n\t\t\tconst pkgJson = await getPackageConfig(buildPath)\n\t\t\tconst hasBuildScript = hasScript(pkgJson, 'build')\n\n\t\t\tif (!hasBuildScript) {\n\t\t\tgetLogger().debug('Skipping build - no build script found')\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tskipped: true,\n\t\t\t\t\treason: 'No build script found in package configuration',\n\t\t\t\t\tduration: Date.now() - startTime,\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\t// Handle missing package.json - skip build for non-Node.js projects without package.iloom.json\n\t\t\tif (error instanceof Error && error.message.includes('package.json not found')) {\n\t\t\tgetLogger().debug('Skipping build - no package configuration found')\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tskipped: true,\n\t\t\t\t\treason: 'No package configuration found in project',\n\t\t\t\t\tduration: Date.now() - startTime,\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Re-throw other errors\n\t\t\tthrow error\n\t\t}\n\n\t\t// Step 2: Check if project has CLI capability (bin field)\n\t\tconst capabilities = await this.capabilityDetector.detectCapabilities(buildPath)\n\t\tconst isCLIProject = capabilities.capabilities.includes('cli')\n\n\t\tif (!isCLIProject) {\n\t\tgetLogger().debug('Skipping build - not a CLI project (no bin field)')\n\t\t\treturn {\n\t\t\t\tsuccess: true,\n\t\t\t\tskipped: true,\n\t\t\t\treason: 'Project is not a CLI project (no bin field in package.json)',\n\t\t\t\tduration: Date.now() - startTime,\n\t\t\t}\n\t\t}\n\n\t\t// Step 3: Detect package manager\n\t\tconst packageManager = await detectPackageManager(buildPath)\n\n\t\t// Step 4: Handle dry-run mode\n\t\tif (options.dryRun) {\n\t\t\tconst command =\n\t\t\t\tpackageManager === 'npm' ? 'npm run build' : `${packageManager} build`\n\t\tgetLogger().info(`[DRY RUN] Would run: ${command}`)\n\t\t\treturn {\n\t\t\t\tsuccess: true,\n\t\t\t\tskipped: false,\n\t\t\t\tduration: Date.now() - startTime,\n\t\t\t}\n\t\t}\n\n\t\t// Step 5: Execute build\n\tgetLogger().info('Running build...')\n\n\t\ttry {\n\t\t\tawait runScript('build', buildPath, [], { quiet: true })\n\t\tgetLogger().success('Build completed successfully')\n\n\t\t\treturn {\n\t\t\t\tsuccess: true,\n\t\t\t\tskipped: false,\n\t\t\t\tduration: Date.now() - startTime,\n\t\t\t}\n\t\t} catch {\n\t\t\t// Step 6: Throw detailed error on failure\n\t\t\tconst runCommand =\n\t\t\t\tpackageManager === 'npm' ? 'npm run build' : `${packageManager} build`\n\n\t\t\tthrow new Error(\n\t\t\t\t`Error: Build failed.\\n` +\n\t\t\t\t\t`Fix build errors before proceeding.\\n\\n` +\n\t\t\t\t\t`Run '${runCommand}' to see detailed errors.`\n\t\t\t)\n\t\t}\n\t}\n}\n"],"mappings":"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","names":[]}
@@ -23,6 +23,12 @@ function buildDevServerUrl(port, protocol = "http") {
23
23
  return `${protocol}://localhost:${port}`;
24
24
  }
25
25
  async function buildDevServerCommand(workspacePath) {
26
+ const scripts = await getPackageScripts(workspacePath);
27
+ const devScript = scripts["dev"];
28
+ if ((devScript == null ? void 0 : devScript.source) === "iloom-config") {
29
+ logger.debug(`Dev server command (from iloom config): ${devScript.command}`);
30
+ return devScript.command;
31
+ }
26
32
  const packageManager = await detectPackageManager(workspacePath);
27
33
  let devCommand;
28
34
  switch (packageManager) {
@@ -66,6 +72,26 @@ var DockerDevServerStrategy = class {
66
72
  constructor(_config, utils) {
67
73
  this.utils = utils;
68
74
  }
75
+ /**
76
+ * Force-remove a container and verify it's gone before returning.
77
+ * Handles the known Docker race where `docker rm -f` returns success but the
78
+ * name isn't immediately released, causing subsequent `docker run --name` to
79
+ * fail with "name already in use".
80
+ */
81
+ async ensureContainerRemoved(containerName, maxRetries = 3) {
82
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
83
+ await execa("docker", ["rm", "-f", containerName], { reject: false });
84
+ const check = await execa("docker", ["container", "inspect", containerName], { reject: false });
85
+ if (check.exitCode !== 0) {
86
+ return;
87
+ }
88
+ if (attempt < maxRetries - 1) {
89
+ logger.debug(`Container "${containerName}" still exists after rm -f, retrying (attempt ${attempt + 1}/${maxRetries})...`);
90
+ await new Promise((resolve) => globalThis.setTimeout(resolve, 500));
91
+ }
92
+ }
93
+ logger.warn(`Container "${containerName}" still exists after ${maxRetries} removal attempts`);
94
+ }
69
95
  /**
70
96
  * Resolve the container port using 3-tier fallback:
71
97
  * 1. config.containerPort (explicit)
@@ -150,7 +176,7 @@ var DockerDevServerStrategy = class {
150
176
  const nameId = config.identifier ?? worktreePath;
151
177
  const imageName = this.utils.buildImageName(nameId);
152
178
  const containerName = this.utils.buildContainerName(nameId);
153
- await execa("docker", ["rm", "-f", containerName], { reject: false });
179
+ await this.ensureContainerRemoved(containerName);
154
180
  const args = [
155
181
  "run",
156
182
  "-d",
@@ -207,7 +233,7 @@ var DockerDevServerStrategy = class {
207
233
  const imageName = this.utils.buildImageName(nameId);
208
234
  const containerName = this.utils.buildContainerName(nameId);
209
235
  const { redirectToStderr, onProcessStarted, envOverrides, onOutput } = opts;
210
- await execa("docker", ["rm", "-f", containerName], { reject: false });
236
+ await this.ensureContainerRemoved(containerName);
211
237
  const args = [
212
238
  "run",
213
239
  "--rm",
@@ -728,4 +754,4 @@ export {
728
754
  buildDevServerUrl,
729
755
  DevServerManager
730
756
  };
731
- //# sourceMappingURL=chunk-N6DY47YN.js.map
757
+ //# sourceMappingURL=chunk-P5MNWBLH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/dev-server.ts","../src/lib/DevServerManager.ts","../src/lib/DockerDevServerStrategy.ts","../src/lib/NativeDevServerStrategy.ts"],"sourcesContent":["import { detectPackageManager } from './package-manager.js'\nimport { getPackageScripts } from './package-json.js'\nimport { logger } from './logger.js'\nimport type { Capability } from '../types/loom.js'\n\n/**\n * Build the dev server URL from port and protocol\n */\nexport function buildDevServerUrl(port: number, protocol: 'http' | 'https' = 'http'): string {\n\treturn `${protocol}://localhost:${port}`\n}\n\n/**\n * Build dev server command for workspace\n * Detects package manager and constructs appropriate command\n */\nexport async function buildDevServerCommand(\n\tworkspacePath: string\n): Promise<string> {\n\t// Check for iloom config dev script first (package.iloom.json / package.iloom.local.json)\n\tconst scripts = await getPackageScripts(workspacePath)\n\tconst devScript = scripts['dev']\n\n\tif (devScript?.source === 'iloom-config') {\n\t\tlogger.debug(`Dev server command (from iloom config): ${devScript.command}`)\n\t\treturn devScript.command\n\t}\n\n\tconst packageManager = await detectPackageManager(workspacePath)\n\n\tlet devCommand: string\n\n\tswitch (packageManager) {\n\t\tcase 'pnpm':\n\t\t\tdevCommand = 'pnpm dev'\n\t\t\tbreak\n\t\tcase 'npm':\n\t\t\tdevCommand = 'npm run dev'\n\t\t\tbreak\n\t\tcase 'yarn':\n\t\t\tdevCommand = 'yarn dev'\n\t\t\tbreak\n\t\tdefault:\n\t\t\t// Fallback to npm (handles bun and other package managers)\n\t\t\tlogger.warn(`Unknown or unsupported package manager: ${packageManager}, defaulting to npm`)\n\t\t\tdevCommand = 'npm run dev'\n\t}\n\n\tlogger.debug(`Dev server command: ${devCommand}`)\n\treturn devCommand\n}\n\n/**\n * Build complete dev server launch command for terminal\n * Includes VSCode launch, echo message (only for web projects), and dev server start\n */\nexport async function getDevServerLaunchCommand(\n\tworkspacePath: string,\n\tport?: number,\n\tcapabilities: Capability[] = []\n): Promise<string> {\n\tconst devCommand = await buildDevServerCommand(workspacePath)\n\n\tconst commands: string[] = []\n\n\t// // Open VSCode\n\t// commands.push('code .')\n\n\t// Echo message (only for web projects)\n\tif (capabilities.includes('web')) {\n\t\tif (port !== undefined) {\n\t\t\tcommands.push(`echo 'Starting dev server on PORT=${port}...'`)\n\t\t} else {\n\t\t\tcommands.push(`echo 'Starting dev server...'`)\n\t\t}\n\t}\n\n\t// Start dev server\n\tcommands.push(devCommand)\n\n\treturn commands.join(' && ')\n}\n","import path from 'path'\nimport { ProcessManager } from './process/ProcessManager.js'\nimport { DockerManager, type DockerConfig } from './DockerManager.js'\nimport { DockerDevServerStrategy, type DockerConfig as StrategyDockerConfig, type DockerUtils } from './DockerDevServerStrategy.js'\nimport { NativeDevServerStrategy } from './NativeDevServerStrategy.js'\nimport { logger } from '../utils/logger.js'\n\n/**\n * Default startup timeout in milliseconds (180 seconds)\n * Can be overridden via ILOOM_DEV_SERVER_TIMEOUT environment variable\n */\nconst DEFAULT_STARTUP_TIMEOUT = 180000\n\n/**\n * Bridge DockerManager static methods to the DockerUtils interface\n * expected by DockerDevServerStrategy.\n */\nconst dockerUtils: DockerUtils = {\n\tparseDockerfileExpose: (filePath: string) => DockerManager.parseExposeFromDockerfile(filePath),\n\tinspectImagePorts: (imageName: string) => DockerManager.inspectImagePorts(imageName),\n\tbuildContainerName: (id: string | number) => DockerManager.buildContainerName(id),\n\tbuildImageName: (id: string | number) => DockerManager.buildImageName(id),\n\tassertDockerAvailable: () => DockerManager.assertAvailable(),\n}\n\nfunction getStartupTimeout(): number {\n\tconst envTimeout = process.env.ILOOM_DEV_SERVER_TIMEOUT\n\tif (envTimeout) {\n\t\tconst parsed = parseInt(envTimeout, 10)\n\t\tif (!isNaN(parsed) && parsed > 0) {\n\t\t\treturn parsed\n\t\t}\n\t}\n\treturn DEFAULT_STARTUP_TIMEOUT\n}\n\nexport interface DevServerManagerOptions {\n\t/**\n\t * Maximum time to wait for server to start (in milliseconds)\n\t * Default: 180000 (180 seconds)\n\t * Can be overridden via ILOOM_DEV_SERVER_TIMEOUT environment variable\n\t */\n\tstartupTimeout?: number\n\n\t/**\n\t * Interval between port checks (in milliseconds)\n\t * Default: 1000 (1 second)\n\t */\n\tcheckInterval?: number\n}\n\n// Re-export DockerConfig from DockerManager for backward compatibility\nexport type { DockerConfig } from './DockerManager.js'\n\n/**\n * Convert a DockerConfig (from DockerManager) to a StrategyDockerConfig\n * (for DockerDevServerStrategy).\n */\nfunction toStrategyConfig(config: DockerConfig): StrategyDockerConfig {\n\treturn {\n\t\tdockerFile: config.dockerFile,\n\t\tcontainerPort: config.containerPort,\n\t\tbuildArgs: config.dockerBuildArgs,\n\t\tbuildSecrets: config.dockerBuildSecrets,\n\t\trunArgs: config.dockerRunArgs,\n\t\tidentifier: config.identifier,\n\t\tprotocol: config.protocol,\n\t}\n}\n\n/**\n * DevServerManager handles auto-starting and monitoring dev servers.\n * Used by open/run commands to ensure dev server is running before opening browser.\n *\n * When devServer config is absent OR mode is not 'docker', behavior is identical\n * to the native process-based implementation via NativeDevServerStrategy.\n * When Docker mode is configured, all operations delegate to DockerDevServerStrategy.\n */\nexport class DevServerManager {\n\tprivate readonly processManager: ProcessManager\n\tprivate readonly options: Required<DevServerManagerOptions>\n\tprivate readonly nativeStrategy: NativeDevServerStrategy\n\tprivate runningDockerContainers: Map<number, string> = new Map()\n\n\tconstructor(\n\t\tprocessManager?: ProcessManager,\n\t\toptions: DevServerManagerOptions = {}\n\t) {\n\t\tthis.processManager = processManager ?? new ProcessManager()\n\t\tthis.options = {\n\t\t\tstartupTimeout: options.startupTimeout ?? getStartupTimeout(),\n\t\t\tcheckInterval: options.checkInterval ?? 1000,\n\t\t}\n\t\tthis.nativeStrategy = new NativeDevServerStrategy(\n\t\t\tthis.processManager,\n\t\t\tthis.options.startupTimeout,\n\t\t\tthis.options.checkInterval\n\t\t)\n\t}\n\n\t/**\n\t * Create a DockerDevServerStrategy for the given Docker config.\n\t * The strategy encapsulates all Docker container lifecycle operations.\n\t */\n\tprivate createDockerStrategy(dockerConfig: DockerConfig): DockerDevServerStrategy {\n\t\treturn new DockerDevServerStrategy(toStrategyConfig(dockerConfig), dockerUtils)\n\t}\n\n\t/**\n\t * Ensure dev server is running on the specified port.\n\t * If not running, start it and wait for it to be ready.\n\t *\n\t * @param worktreePath - Path to the worktree\n\t * @param port - Port the server should run on\n\t * @param dockerConfig - Optional Docker configuration for container-based server\n\t * @returns true if server is ready, false if startup failed/timed out\n\t */\n\tasync ensureServerRunning(worktreePath: string, port: number, dockerConfig?: DockerConfig): Promise<boolean> {\n\t\tlogger.debug(`Checking if dev server is running on port ${port}...`)\n\n\t\t// Docker mode: check if container is already running\n\t\tif (dockerConfig) {\n\t\t\tconst strategy = this.createDockerStrategy(dockerConfig)\n\t\t\tconst containerName = dockerUtils.buildContainerName(dockerConfig.identifier)\n\t\t\tconst isRunning = await strategy.isContainerRunning(containerName)\n\t\t\tif (isRunning) {\n\t\t\t\tlogger.debug(`Docker container \"${containerName}\" already running on port ${port}`)\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\tlogger.info(`Docker dev server not running on port ${port}, starting...`)\n\t\t\ttry {\n\t\t\t\tawait this.startDockerServer(worktreePath, port, dockerConfig, strategy)\n\t\t\t\treturn true\n\t\t\t} catch (error) {\n\t\t\t\tlogger.error(\n\t\t\t\t\t`Failed to start Docker dev server: ${error instanceof Error ? error.message : 'Unknown error'}`\n\t\t\t\t)\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\n\t\t// Native mode: check if a process is listening on the port\n\t\tconst existingProcess = await this.processManager.detectDevServer(port)\n\t\tif (existingProcess) {\n\t\t\tlogger.debug(\n\t\t\t\t`Dev server already running on port ${port} (PID: ${existingProcess.pid})`\n\t\t\t)\n\t\t\treturn true\n\t\t}\n\n\t\t// Not running - start it\n\t\tlogger.info(`Dev server not running on port ${port}, starting...`)\n\n\t\ttry {\n\t\t\tawait this.nativeStrategy.startBackground(worktreePath, port)\n\t\t\treturn true\n\t\t} catch (error) {\n\t\t\tlogger.error(\n\t\t\t\t`Failed to start dev server: ${error instanceof Error ? error.message : 'Unknown error'}`\n\t\t\t)\n\t\t\treturn false\n\t\t}\n\t}\n\n\t/**\n\t * Start dev server in Docker container (background) and wait for it to be ready.\n\t * Builds the image, resolves the container port, starts the container detached,\n\t * and polls the host port for readiness.\n\t */\n\tprivate async startDockerServer(\n\t\tworktreePath: string,\n\t\tport: number,\n\t\tdockerConfig: DockerConfig,\n\t\tstrategy: DockerDevServerStrategy\n\t): Promise<void> {\n\t\tconst strategyConfig = toStrategyConfig(dockerConfig)\n\t\tconst imageName = dockerUtils.buildImageName(dockerConfig.identifier)\n\t\tconst dockerfilePath = path.resolve(worktreePath, dockerConfig.dockerFile)\n\n\t\t// Build image\n\t\tawait strategy.buildImage(worktreePath, strategyConfig)\n\n\t\t// Resolve container port (config > image inspect > Dockerfile EXPOSE)\n\t\tconst containerPort = await strategy.resolveContainerPort(\n\t\t\tstrategyConfig,\n\t\t\timageName,\n\t\t\tdockerfilePath\n\t\t)\n\n\t\t// Run container detached\n\t\tconst containerName = await strategy.runContainerDetached(\n\t\t\tworktreePath,\n\t\t\tport,\n\t\t\tcontainerPort,\n\t\t\tstrategyConfig\n\t\t)\n\n\t\t// Track for cleanup\n\t\tthis.runningDockerContainers.set(port, containerName)\n\n\t\t// Wait for server to be ready via TCP probe (Docker proxy listens on host port)\n\t\t// Pass container name for early crash detection\n\t\tlogger.info(`Waiting for Docker dev server to start on port ${port}...`)\n\t\tconst ready = await strategy.waitForReady(\n\t\t\tport,\n\t\t\tthis.options.startupTimeout,\n\t\t\tthis.options.checkInterval,\n\t\t\tcontainerName\n\t\t)\n\n\t\tif (!ready) {\n\t\t\t// Clean up the container if startup failed\n\t\t\tawait strategy.stopContainer(containerName)\n\t\t\tthis.runningDockerContainers.delete(port)\n\t\t\tthrow new Error(\n\t\t\t\t`Docker dev server failed to start within ${this.options.startupTimeout}ms timeout`\n\t\t\t)\n\t\t}\n\n\t\tlogger.success(`Docker dev server started successfully on port ${port}`)\n\t}\n\n\t/**\n\t * Check if a dev server is running on the specified port\n\t *\n\t * @param port - Port to check\n\t * @param dockerConfig - Optional Docker configuration; when provided, checks container status\n\t * @returns true if server is running, false otherwise\n\t */\n\tasync isServerRunning(port: number, dockerConfig?: DockerConfig): Promise<boolean> {\n\t\tif (dockerConfig) {\n\t\t\tconst strategy = this.createDockerStrategy(dockerConfig)\n\t\t\tconst containerName = dockerUtils.buildContainerName(dockerConfig.identifier)\n\t\t\treturn strategy.isContainerRunning(containerName)\n\t\t}\n\t\tconst existingProcess = await this.processManager.detectDevServer(port)\n\t\treturn existingProcess !== null\n\t}\n\n\t/**\n\t * Run dev server in foreground mode (blocking).\n\t * This method blocks until the server is stopped (e.g., via Ctrl+C).\n\t *\n\t * @param worktreePath - Path to the worktree\n\t * @param port - Port the server should run on\n\t * @param redirectToStderr - If true, redirect stdout/stderr to stderr (useful for JSON output)\n\t * @param onProcessStarted - Callback called immediately after process starts with PID\n\t * @returns Process information including PID\n\t */\n\tasync runServerForeground(\n\t\tworktreePath: string,\n\t\tport: number,\n\t\tredirectToStderr = false,\n\t\tonProcessStarted?: (pid?: number) => void,\n\t\tenvOverrides?: Record<string, string>,\n\t\tdockerConfig?: DockerConfig,\n\t\tonOutput?: (data: Buffer) => void\n\t): Promise<{ pid?: number }> {\n\t\t// Docker mode: build image and run container in foreground\n\t\tif (dockerConfig) {\n\t\t\tlogger.debug(`Starting Docker dev server in foreground on port ${port}`)\n\n\t\t\tconst strategy = this.createDockerStrategy(dockerConfig)\n\t\t\tconst strategyConfig = toStrategyConfig(dockerConfig)\n\t\t\tconst imageName = dockerUtils.buildImageName(dockerConfig.identifier)\n\t\t\tconst containerName = dockerUtils.buildContainerName(dockerConfig.identifier)\n\t\t\tconst dockerfilePath = path.resolve(worktreePath, dockerConfig.dockerFile)\n\n\t\t\t// Build image\n\t\t\tawait strategy.buildImage(worktreePath, strategyConfig)\n\n\t\t\t// Resolve container port\n\t\t\tconst containerPort = await strategy.resolveContainerPort(\n\t\t\t\tstrategyConfig,\n\t\t\t\timageName,\n\t\t\t\tdockerfilePath\n\t\t\t)\n\n\t\t\tif (onProcessStarted) {\n\t\t\t\tonProcessStarted(undefined)\n\t\t\t}\n\n\t\t\t// Track container for cleanup\n\t\t\tthis.runningDockerContainers.set(port, containerName)\n\t\t\ttry {\n\t\t\t\t// Run container in foreground (blocks until stopped)\n\t\t\t\t// DockerDevServerStrategy.runContainerForeground handles signal forwarding internally\n\t\t\t\tawait strategy.runContainerForeground(\n\t\t\t\t\tworktreePath,\n\t\t\t\t\tport,\n\t\t\t\t\tcontainerPort,\n\t\t\t\t\tstrategyConfig,\n\t\t\t\t\t{ redirectToStderr, envOverrides, onOutput }\n\t\t\t\t)\n\t\t\t} finally {\n\t\t\t\tthis.runningDockerContainers.delete(port)\n\t\t\t}\n\n\t\t\treturn {}\n\t\t}\n\n\t\t// Native mode: delegate to NativeDevServerStrategy\n\t\treturn this.nativeStrategy.startForeground(worktreePath, port, {\n\t\t\tredirectToStderr,\n\t\t\t...(onProcessStarted !== undefined && { onProcessStarted }),\n\t\t\t...(envOverrides !== undefined && { envOverrides }),\n\t\t\t...(onOutput !== undefined && { onOutput }),\n\t\t})\n\t}\n\n\t/**\n\t * Clean up all running server processes and Docker containers.\n\t * This should be called when the manager is being disposed.\n\t */\n\tasync cleanup(): Promise<void> {\n\t\t// Clean up native process-based servers\n\t\tawait this.nativeStrategy.stopAll()\n\n\t\t// Clean up Docker containers using DockerDevServerStrategy\n\t\tfor (const [port, containerName] of this.runningDockerContainers.entries()) {\n\t\t\ttry {\n\t\t\t\tlogger.debug(`Cleaning up Docker container \"${containerName}\" on port ${port}`)\n\t\t\t\t// Create a minimal strategy just for stopContainer\n\t\t\t\tconst strategy = new DockerDevServerStrategy({}, dockerUtils)\n\t\t\t\tawait strategy.stopContainer(containerName)\n\t\t\t} catch (error) {\n\t\t\t\tlogger.warn(\n\t\t\t\t\t`Failed to stop Docker container \"${containerName}\" on port ${port}: ${error instanceof Error ? error.message : 'Unknown error'}`\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tthis.runningDockerContainers.clear()\n\t}\n}\n","import { execa, type ExecaError } from 'execa'\nimport net from 'net'\nimport { logger } from '../utils/logger.js'\nimport { restoreTerminalState } from '../utils/terminal.js'\nimport { expandAndValidateSecretPaths } from '../utils/docker.js'\n\n/**\n * Docker configuration shape consumed by DockerDevServerStrategy.\n * Matches the DevServerSettings['docker'] shape from the settings schema.\n */\nexport interface DockerConfig {\n\t/** Path to Dockerfile (relative to worktree) */\n\tdockerFile?: string | undefined\n\t/** Port inside the container (auto-detected from image inspect or Dockerfile EXPOSE if not set) */\n\tcontainerPort?: number | undefined\n\t/** Build arguments passed as --build-arg to docker build */\n\tbuildArgs?: Record<string, string> | undefined\n\t/** Secret files to mount during docker build via BuildKit --secret flag */\n\tbuildSecrets?: Record<string, string> | undefined\n\t/** Additional docker run flags */\n\trunArgs?: string[] | undefined\n\t/** Identifier for naming containers/images (issue number, branch name). Falls back to worktreePath if not set. */\n\tidentifier?: string | undefined\n\t/** Protocol for displayed URLs (http or https, default http) */\n\tprotocol?: 'http' | 'https' | undefined\n}\n\n/**\n * Options for runContainerForeground.\n */\nexport interface RunForegroundOptions {\n\t/** If true, redirect stdout/stderr to process.stderr */\n\tredirectToStderr?: boolean | undefined\n\t/** Called immediately after the container starts */\n\tonProcessStarted?: ((pid?: number) => void) | undefined\n\t/** Additional environment variables to forward into the container */\n\tenvOverrides?: Record<string, string> | undefined\n\t/** Callback for server output when using pipe mode (for TUI). When provided, stdio is piped instead of inherited. */\n\tonOutput?: ((data: Buffer) => void) | undefined\n}\n\n/**\n * Utility function contracts from the docker-utils sibling issue.\n * Coded against these shapes so this class compiles without waiting for the sibling merge.\n */\ntype ParseDockerfileExposeFn = (path: string) => Promise<number | null>\ntype InspectImagePortsFn = (name: string) => Promise<number | null>\ntype BuildContainerNameFn = (id: string | number) => string\ntype BuildImageNameFn = (id: string | number) => string\ntype AssertDockerAvailableFn = () => Promise<void>\n\n/**\n * Injected docker utility functions.\n * Default implementations are imported from DockerManager for backward compatibility\n * until the dedicated docker-utils module is merged.\n */\nexport interface DockerUtils {\n\tparseDockerfileExpose: ParseDockerfileExposeFn\n\tinspectImagePorts: InspectImagePortsFn\n\tbuildContainerName: BuildContainerNameFn\n\tbuildImageName: BuildImageNameFn\n\tassertDockerAvailable: AssertDockerAvailableFn\n}\n\n/**\n * Attempt a single TCP connection to localhost:port.\n * Resolves true if the connection succeeds, false otherwise.\n */\nfunction tcpProbe(port: number): Promise<boolean> {\n\treturn new Promise((resolve) => {\n\t\tconst socket = net.createConnection({ port, host: '127.0.0.1' })\n\t\tsocket.once('connect', () => {\n\t\t\tsocket.destroy()\n\t\t\tresolve(true)\n\t\t})\n\t\tsocket.once('error', () => {\n\t\t\tsocket.destroy()\n\t\t\tresolve(false)\n\t\t})\n\t})\n}\n\n/**\n * DockerDevServerStrategy handles the full Docker container lifecycle for a dev server:\n * - Image building\n * - Container running (detached and foreground)\n * - Container stopping\n * - Readiness detection via TCP probe\n * - Port resolution (3-tier: config > image inspect > Dockerfile EXPOSE)\n *\n * This class is the core Docker logic delegated to by DevServerManager.\n * It does NOT modify DevServerManager, ResourceCleanup, or CLI commands.\n */\nexport class DockerDevServerStrategy {\n\tprivate readonly utils: DockerUtils\n\n\tconstructor(_config: DockerConfig, utils: DockerUtils) {\n\t\tthis.utils = utils\n\t}\n\n\t/**\n\t * Force-remove a container and verify it's gone before returning.\n\t * Handles the known Docker race where `docker rm -f` returns success but the\n\t * name isn't immediately released, causing subsequent `docker run --name` to\n\t * fail with \"name already in use\".\n\t */\n\tprivate async ensureContainerRemoved(containerName: string, maxRetries = 3): Promise<void> {\n\t\tfor (let attempt = 0; attempt < maxRetries; attempt++) {\n\t\t\tawait execa('docker', ['rm', '-f', containerName], { reject: false })\n\n\t\t\t// Verify container is gone (inspect exits non-zero when container doesn't exist)\n\t\t\tconst check = await execa('docker', ['container', 'inspect', containerName], { reject: false })\n\t\t\tif (check.exitCode !== 0) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (attempt < maxRetries - 1) {\n\t\t\t\tlogger.debug(`Container \"${containerName}\" still exists after rm -f, retrying (attempt ${attempt + 1}/${maxRetries})...`)\n\t\t\t\tawait new Promise<void>((resolve) => globalThis.setTimeout(resolve, 500))\n\t\t\t}\n\t\t}\n\n\t\tlogger.warn(`Container \"${containerName}\" still exists after ${maxRetries} removal attempts`)\n\t}\n\n\t/**\n\t * Resolve the container port using 3-tier fallback:\n\t * 1. config.containerPort (explicit)\n\t * 2. inspectImagePorts(imageName) (from built image)\n\t * 3. parseDockerfileExpose(dockerfilePath) (from Dockerfile)\n\t *\n\t * Throws a clear error if all three return null.\n\t *\n\t * @param config - Docker config (may override the constructor config)\n\t * @param imageName - Name of the built Docker image\n\t * @param dockerfilePath - Absolute path to the Dockerfile\n\t */\n\tasync resolveContainerPort(\n\t\tconfig: DockerConfig,\n\t\timageName: string,\n\t\tdockerfilePath: string\n\t): Promise<number> {\n\t\tif (config.containerPort !== undefined) {\n\t\t\treturn config.containerPort\n\t\t}\n\n\t\tconst inspectedPort = await this.utils.inspectImagePorts(imageName)\n\t\tif (inspectedPort !== null) {\n\t\t\tlogger.debug(`Auto-detected container port ${inspectedPort} from Docker image inspect`)\n\t\t\treturn inspectedPort\n\t\t}\n\n\t\tconst exposedPort = await this.utils.parseDockerfileExpose(dockerfilePath)\n\t\tif (exposedPort !== null) {\n\t\t\tlogger.debug(`Auto-detected container port ${exposedPort} from Dockerfile EXPOSE directive`)\n\t\t\treturn exposedPort\n\t\t}\n\n\t\tthrow new Error(\n\t\t\t'Cannot determine container port. Set `devServer.docker.containerPort` in settings or add an `EXPOSE` directive to your Dockerfile.'\n\t\t)\n\t}\n\n\t/**\n\t * Build a Docker image for the worktree.\n\t * Build context is always the worktree root directory.\n\t *\n\t * @param worktreePath - Absolute path to the worktree (build context)\n\t * @param config - Docker config with Dockerfile path and build args\n\t */\n\tasync buildImage(worktreePath: string, config: DockerConfig): Promise<void> {\n\t\tconst imageName = this.utils.buildImageName(config.identifier ?? worktreePath)\n\t\tconst dockerfilePath = config.dockerFile ?? './Dockerfile'\n\n\t\tconst args = ['build', '-t', imageName, '-f', dockerfilePath]\n\n\t\tif (config.buildArgs) {\n\t\t\tfor (const [key, value] of Object.entries(config.buildArgs)) {\n\t\t\t\targs.push('--build-arg', `${key}=${value}`)\n\t\t\t}\n\t\t}\n\n\t\t// Mount secret files via BuildKit --secret flags\n\t\tconst expandedSecrets = expandAndValidateSecretPaths(config.buildSecrets, worktreePath)\n\t\tfor (const [id, srcPath] of Object.entries(expandedSecrets)) {\n\t\t\targs.push('--secret', `id=${id},src=${srcPath}`)\n\t\t}\n\n\t\t// Context is always the worktree root\n\t\targs.push('.')\n\n\t\tlogger.info(`Building Docker image \"${imageName}\" from ${dockerfilePath}...`)\n\n\t\tconst execaOptions: { cwd: string; stdio: 'inherit'; env?: Record<string, string> } = {\n\t\t\tcwd: worktreePath,\n\t\t\tstdio: 'inherit',\n\t\t}\n\n\t\t// Enable BuildKit when secrets are being used (required for --secret flag on older Docker versions)\n\t\tif (Object.keys(expandedSecrets).length > 0) {\n\t\t\texecaOptions.env = { ...process.env, DOCKER_BUILDKIT: '1' }\n\t\t}\n\n\t\ttry {\n\t\t\tawait execa('docker', args, execaOptions)\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : 'Unknown error'\n\t\t\tthrow new Error(`Docker build failed for image \"${imageName}\": ${message}`)\n\t\t}\n\n\t\tlogger.success(`Docker image \"${imageName}\" built successfully`)\n\t}\n\n\t/**\n\t * Run a container in detached (background) mode.\n\t * Force-removes any existing container with the same name first.\n\t * Mounts the worktree at /app and adds an anonymous volume for node_modules.\n\t * Forwards PORT and any envOverrides into the container.\n\t *\n\t * @param worktreePath - Absolute path to the worktree (mounted at /app)\n\t * @param hostPort - Port on the host to map\n\t * @param containerPort - Port inside the container\n\t * @param config - Docker config with run args\n\t * @param envOverrides - Additional environment variables to set in the container\n\t * @returns The container name\n\t */\n\tasync runContainerDetached(\n\t\tworktreePath: string,\n\t\thostPort: number,\n\t\tcontainerPort: number,\n\t\tconfig: DockerConfig,\n\t\tenvOverrides?: Record<string, string>\n\t): Promise<string> {\n\t\tconst nameId = config.identifier ?? worktreePath\n\t\tconst imageName = this.utils.buildImageName(nameId)\n\t\tconst containerName = this.utils.buildContainerName(nameId)\n\n\t\t// Force-remove any existing container with same name and verify it's gone\n\t\tawait this.ensureContainerRemoved(containerName)\n\n\t\tconst args = [\n\t\t\t'run', '-d',\n\t\t\t'--name', containerName,\n\t\t\t'-p', `${hostPort}:${containerPort}`,\n\t\t\t// Mount worktree at /app\n\t\t\t'-v', `${worktreePath}:/app`,\n\t\t\t// Anonymous volume for node_modules to prevent host/container conflicts\n\t\t\t'-v', '/app/node_modules',\n\t\t\t// Forward PORT as the container port so the app listens where Docker expects.\n\t\t\t// The -p mapping handles host-to-container translation.\n\t\t\t'-e', `PORT=${containerPort}`,\n\t\t]\n\n\t\t// Forward additional environment variables\n\t\tif (envOverrides) {\n\t\t\tfor (const [key, value] of Object.entries(envOverrides)) {\n\t\t\t\targs.push('-e', `${key}=${value}`)\n\t\t\t}\n\t\t}\n\n\t\t// Additional run flags from config\n\t\tif (config.runArgs) {\n\t\t\targs.push(...config.runArgs)\n\t\t}\n\n\t\targs.push(imageName)\n\n\t\tconst displayProtocol = config.protocol ?? 'http'\n\t\tlogger.info(`Starting Docker container \"${containerName}\" in background (${displayProtocol}://localhost:${hostPort} → container:${containerPort})...`)\n\n\t\ttry {\n\t\t\tawait execa('docker', args)\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : 'Unknown error'\n\t\t\tthrow new Error(`Failed to start Docker container \"${containerName}\": ${message}`)\n\t\t}\n\n\t\tlogger.success(`Docker container \"${containerName}\" started on port ${hostPort}`)\n\t\treturn containerName\n\t}\n\n\t/**\n\t * Run a container in foreground (blocking) mode.\n\t * The container is automatically removed on exit (--rm flag).\n\t * Traps SIGINT and SIGTERM and forwards them to the container via docker stop.\n\t *\n\t * @param worktreePath - Absolute path to the worktree (mounted at /app)\n\t * @param hostPort - Port on the host to map\n\t * @param containerPort - Port inside the container\n\t * @param config - Docker config with run args\n\t * @param opts - Additional options (redirectToStderr, onProcessStarted, envOverrides)\n\t * @returns Object with optional pid (Docker containers don't expose host PID)\n\t */\n\tasync runContainerForeground(\n\t\tworktreePath: string,\n\t\thostPort: number,\n\t\tcontainerPort: number,\n\t\tconfig: DockerConfig,\n\t\topts: RunForegroundOptions = {}\n\t): Promise<{ pid?: number }> {\n\t\tconst nameId = config.identifier ?? worktreePath\n\t\tconst imageName = this.utils.buildImageName(nameId)\n\t\tconst containerName = this.utils.buildContainerName(nameId)\n\t\tconst { redirectToStderr, onProcessStarted, envOverrides, onOutput } = opts\n\n\t\t// Force-remove any existing container with same name (stale from previous ungraceful exit)\n\t\tawait this.ensureContainerRemoved(containerName)\n\n\t\tconst args = [\n\t\t\t'run', '--rm',\n\t\t\t'--name', containerName,\n\t\t\t'-p', `${hostPort}:${containerPort}`,\n\t\t\t// Mount worktree at /app\n\t\t\t'-v', `${worktreePath}:/app`,\n\t\t\t// Anonymous volume for node_modules to prevent host/container conflicts\n\t\t\t'-v', '/app/node_modules',\n\t\t\t// Forward PORT as the container port so the app listens where Docker expects.\n\t\t\t// The -p mapping handles host-to-container translation.\n\t\t\t'-e', `PORT=${containerPort}`,\n\t\t]\n\n\t\t// Forward additional environment variables\n\t\tif (envOverrides) {\n\t\t\tfor (const [key, value] of Object.entries(envOverrides)) {\n\t\t\t\targs.push('-e', `${key}=${value}`)\n\t\t\t}\n\t\t}\n\n\t\t// Additional run flags from config\n\t\tif (config.runArgs) {\n\t\t\targs.push(...config.runArgs)\n\t\t}\n\n\t\targs.push(imageName)\n\n\t\tconst displayProtocol = config.protocol ?? 'http'\n\t\tlogger.info(`Running Docker container \"${containerName}\" in foreground (${displayProtocol}://localhost:${hostPort} → container:${containerPort})...`)\n\n\t\t// Determine stdio based on mode:\n\t\t// - onOutput (TUI pipe mode): stdin ignored (TUI handles it), stdout/stderr piped to callback\n\t\t// - redirectToStderr: stdout/stderr -> process.stderr, stdin inherited\n\t\t// - default: inherit all stdio\n\t\tconst stdio = onOutput\n\t\t\t? (['ignore', 'pipe', 'pipe'] as const)\n\t\t\t: redirectToStderr\n\t\t\t\t? ([process.stdin, process.stderr, process.stderr] as const)\n\t\t\t\t: ('inherit' as const)\n\n\t\t// Signal forwarding: trap SIGINT/SIGTERM and forward to container\n\t\tconst forwardSignal = (): void => {\n\t\t\tlogger.debug(`Stopping container \"${containerName}\"`)\n\t\t\tvoid execa('docker', ['stop', containerName], { reject: false })\n\t\t}\n\n\t\tconst onSigint = (): void => forwardSignal()\n\t\tconst onSigterm = (): void => forwardSignal()\n\n\t\tprocess.on('SIGINT', onSigint)\n\t\tprocess.on('SIGTERM', onSigterm)\n\n\t\tif (onProcessStarted) {\n\t\t\tonProcessStarted(undefined)\n\t\t}\n\n\t\ttry {\n\t\t\tconst dockerProcess = execa('docker', args, { stdio })\n\n\t\t\t// When onOutput is provided, pipe stdout/stderr to the callback\n\t\t\tif (onOutput) {\n\t\t\t\tdockerProcess.stdout?.on('data', onOutput)\n\t\t\t\tdockerProcess.stderr?.on('data', onOutput)\n\t\t\t}\n\n\t\t\tawait dockerProcess\n\t\t} catch (error) {\n\t\t\tconst execaError = error as ExecaError\n\t\t\t// When the user presses Ctrl+C, the signal handler calls `docker stop`,\n\t\t\t// which causes `docker run` to exit with code 143 (128+SIGTERM) or 130\n\t\t\t// (128+SIGINT). Execa may also report the signal name directly. These\n\t\t\t// are all expected shutdown paths and should not surface as errors.\n\t\t\tconst isExpectedShutdown =\n\t\t\t\texecaError.exitCode === 143 ||\n\t\t\t\texecaError.exitCode === 130 ||\n\t\t\t\texecaError.signal === 'SIGTERM' ||\n\t\t\t\texecaError.signal === 'SIGINT'\n\t\t\tif (!isExpectedShutdown) {\n\t\t\t\tthrow error\n\t\t\t}\n\t\t} finally {\n\t\t\t// Clean up signal handlers to avoid leaks\n\t\t\tprocess.removeListener('SIGINT', onSigint)\n\t\t\tprocess.removeListener('SIGTERM', onSigterm)\n\t\t\trestoreTerminalState()\n\t\t}\n\n\t\treturn {}\n\t}\n\n\t/**\n\t * Stop and remove a container by name.\n\t * Uses docker rm -f which handles both running and stopped containers atomically.\n\t * Handles already-stopped containers gracefully (no error thrown).\n\t *\n\t * @param containerName - Name of the container to stop and remove\n\t */\n\tasync stopContainer(containerName: string): Promise<void> {\n\t\tlogger.debug(`Stopping and removing container \"${containerName}\"...`)\n\t\tawait execa('docker', ['rm', '-f', containerName], { reject: false })\n\t\tlogger.debug(`Container \"${containerName}\" stopped and removed`)\n\t}\n\n\t/**\n\t * Check if a named container is currently running.\n\t * Uses exact name matching with anchored regex to avoid partial name matches.\n\t *\n\t * @param containerName - Name of the container to check\n\t * @returns true if the container is running, false otherwise\n\t */\n\tasync isContainerRunning(containerName: string): Promise<boolean> {\n\t\ttry {\n\t\t\tconst result = await execa('docker', [\n\t\t\t\t'ps',\n\t\t\t\t'--filter', `name=^${containerName}$`,\n\t\t\t\t'--format', '{{.Names}}',\n\t\t\t], { reject: false })\n\n\t\t\treturn result.exitCode === 0 && result.stdout.trim() === containerName\n\t\t} catch {\n\t\t\treturn false\n\t\t}\n\t}\n\n\t/**\n\t * Wait for the dev server to be ready by probing the TCP port.\n\t * Uses net.createConnection instead of lsof-based detection since Docker port\n\t * forwarding shows com.docker.backend as the listening process (not the dev server).\n\t * Exits early if the container has stopped (crash detection).\n\t *\n\t * @param port - Host port to probe\n\t * @param timeout - Maximum time to wait in milliseconds\n\t * @param interval - Interval between probes in milliseconds\n\t * @param containerName - Optional container name to monitor for early exit\n\t * @returns true if the port accepts connections within the timeout, false otherwise\n\t */\n\tasync waitForReady(port: number, timeout: number, interval: number, containerName?: string): Promise<boolean> {\n\t\tconst startTime = Date.now()\n\t\tlet attempts = 0\n\n\t\twhile (Date.now() - startTime < timeout) {\n\t\t\tattempts++\n\n\t\t\t// Early exit: if the container has stopped, stop polling\n\t\t\tif (containerName && attempts % 3 === 0) {\n\t\t\t\tconst stillRunning = await this.isContainerRunning(containerName)\n\t\t\t\tif (!stillRunning) {\n\t\t\t\t\tlogger.warn(\n\t\t\t\t\t\t`Docker container \"${containerName}\" exited before becoming ready (after ${attempts} attempts, ${Date.now() - startTime}ms)`\n\t\t\t\t\t)\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst isReady = await tcpProbe(port)\n\t\t\tif (isReady) {\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\tawait new Promise<void>((resolve) => globalThis.setTimeout(resolve, interval))\n\t\t}\n\n\t\treturn false\n\t}\n}\n","import { execa, type ExecaChildProcess, type ExecaError } from 'execa'\nimport { setTimeout } from 'timers/promises'\nimport { ProcessManager } from './process/ProcessManager.js'\nimport { buildDevServerCommand } from '../utils/dev-server.js'\nimport { runScript } from '../utils/package-manager.js'\nimport { getPackageScripts } from '../utils/package-json.js'\nimport { logger } from '../utils/logger.js'\nimport { restoreTerminalState } from '../utils/terminal.js'\nimport type { DevServerStrategy, ForegroundOpts } from './DevServerStrategy.js'\n\n/**\n * NativeDevServerStrategy implements DevServerStrategy for process-based dev servers.\n * This is the default mode — the dev server runs directly on the host as a child process.\n */\nexport class NativeDevServerStrategy implements DevServerStrategy {\n\tprivate readonly processManager: ProcessManager\n\tprivate readonly startupTimeout: number\n\tprivate readonly checkInterval: number\n\tprivate runningServers: Map<number, ExecaChildProcess> = new Map()\n\n\tconstructor(\n\t\tprocessManager: ProcessManager,\n\t\tstartupTimeout: number,\n\t\tcheckInterval: number\n\t) {\n\t\tthis.processManager = processManager\n\t\tthis.startupTimeout = startupTimeout\n\t\tthis.checkInterval = checkInterval\n\t}\n\n\tasync isRunning(port: number): Promise<boolean> {\n\t\tconst process = await this.processManager.detectDevServer(port)\n\t\treturn process !== null\n\t}\n\n\tasync startBackground(\n\t\tworktreePath: string,\n\t\tport: number,\n\t\tenvOverrides?: Record<string, string>\n\t): Promise<void> {\n\t\t// Guard: Check if a dev script exists in package.json or package.iloom.json\n\t\tconst scripts = await getPackageScripts(worktreePath)\n\t\tif (!scripts['dev']) {\n\t\t\tlogger.warn('Skipping auto-start: no \"dev\" script found in package.json or package.iloom.json')\n\t\t\treturn\n\t\t}\n\n\t\t// Build dev server command\n\t\tconst devCommand = await buildDevServerCommand(worktreePath)\n\t\tlogger.debug(`Starting dev server with command: ${devCommand}`)\n\n\t\t// Start server in background\n\t\tconst serverProcess = execa('sh', ['-c', devCommand], {\n\t\t\tcwd: worktreePath,\n\t\t\tenv: {\n\t\t\t\t...process.env,\n\t\t\t\t...envOverrides,\n\t\t\t\tPORT: port.toString(),\n\t\t\t},\n\t\t\t// Important: Don't inherit stdio - server runs in background\n\t\t\tstdio: 'ignore',\n\t\t\t// Detach from parent process so it continues running\n\t\t\tdetached: true,\n\t\t})\n\n\t\t// Store reference to prevent cleanup\n\t\tthis.runningServers.set(port, serverProcess)\n\n\t\t// Remove from map when process exits naturally or crashes\n\t\tserverProcess.on('exit', () => {\n\t\t\tthis.runningServers.delete(port)\n\t\t})\n\n\t\t// Unref so parent can exit\n\t\tserverProcess.unref()\n\n\t\t// Wait for server to be ready (pass process ref for early crash detection)\n\t\tlogger.info(`Waiting for dev server to start on port ${port}...`)\n\t\tconst ready = await this.waitForReady(port, serverProcess)\n\n\t\tif (!ready) {\n\t\t\tthrow new Error(\n\t\t\t\t`Dev server failed to start within ${this.startupTimeout}ms timeout`\n\t\t\t)\n\t\t}\n\n\t\tlogger.success(`Dev server started successfully on port ${port}`)\n\t}\n\n\tasync startForeground(\n\t\tworktreePath: string,\n\t\tport: number,\n\t\topts: ForegroundOpts\n\t): Promise<{ pid?: number }> {\n\t\tconst { redirectToStderr = false, onProcessStarted, envOverrides, onOutput } = opts\n\n\t\tlogger.debug(`Starting dev server in foreground on port ${port}`)\n\n\t\tif (redirectToStderr || onOutput) {\n\t\t\t// For redirectToStderr or onOutput (TUI pipe mode), we need direct execa control for custom stdio\n\t\t\tconst devCommand = await buildDevServerCommand(worktreePath)\n\t\t\tlogger.debug(`Starting dev server with command: ${devCommand}`)\n\n\t\t\t// Determine stdio based on mode:\n\t\t\t// - redirectToStderr: stdout/stderr -> process.stderr, stdin inherited\n\t\t\t// - onOutput (TUI mode): stdin ignored (TUI handles it), stdout/stderr piped to callback\n\t\t\tconst stdio = onOutput\n\t\t\t\t? (['ignore', 'pipe', 'pipe'] as const)\n\t\t\t\t: ([process.stdin, process.stderr, process.stderr] as const)\n\n\t\t\tconst serverProcess = execa('sh', ['-c', devCommand], {\n\t\t\t\tcwd: worktreePath,\n\t\t\t\tenv: {\n\t\t\t\t\t...process.env,\n\t\t\t\t\t...envOverrides,\n\t\t\t\t\tPORT: port.toString(),\n\t\t\t\t},\n\t\t\t\tstdio,\n\t\t\t})\n\n\t\t\t// When onOutput is provided, pipe stdout/stderr to the callback\n\t\t\tif (onOutput) {\n\t\t\t\tserverProcess.stdout?.on('data', onOutput)\n\t\t\t\tserverProcess.stderr?.on('data', onOutput)\n\t\t\t}\n\n\t\t\tconst processInfo: { pid?: number } =\n\t\t\t\tserverProcess.pid !== undefined ? { pid: serverProcess.pid } : {}\n\n\t\t\tif (onProcessStarted) {\n\t\t\t\tonProcessStarted(processInfo.pid)\n\t\t\t}\n\n\t\t\t// Register no-op SIGINT handler to prevent signal-exit from re-raising SIGINT\n\t\t\t// before finally blocks can run, ensuring terminal state is restored on Ctrl+C.\n\t\t\tconst onSigint = (): void => {}\n\t\t\tprocess.on('SIGINT', onSigint)\n\n\t\t\ttry {\n\t\t\t\tawait serverProcess\n\t\t\t} catch (error) {\n\t\t\t\tconst execaError = error as ExecaError\n\t\t\t\t// If killed by SIGINT, the user intentionally cancelled — return silently\n\t\t\t\tif (execaError.signal !== 'SIGINT') {\n\t\t\t\t\tthrow error\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tprocess.removeListener('SIGINT', onSigint)\n\t\t\t\trestoreTerminalState()\n\t\t\t}\n\n\t\t\treturn processInfo\n\t\t}\n\n\t\t// Use runScript for standard foreground mode\n\t\treturn await runScript('dev', worktreePath, [], {\n\t\t\tenv: {\n\t\t\t\t...envOverrides,\n\t\t\t\tPORT: port.toString(),\n\t\t\t},\n\t\t\tforeground: true,\n\t\t\t...(onProcessStarted && { onStart: onProcessStarted }),\n\t\t\t...(onOutput !== undefined ? { onOutput } : {}),\n\t\t\tnoCi: true, // Dev servers should not have CI=true\n\t\t})\n\t}\n\n\tasync stop(port: number): Promise<boolean> {\n\t\tconst serverProcess = this.runningServers.get(port)\n\t\tif (!serverProcess) {\n\t\t\treturn false\n\t\t}\n\n\t\ttry {\n\t\t\t// Kill the entire process group (negative PID) since the server is\n\t\t\t// spawned with detached:true via `sh -c`. Without this, only the\n\t\t\t// shell process receives the signal and the actual dev server\n\t\t\t// (node/vite/next) remains running as an orphan.\n\t\t\tif (serverProcess.pid) {\n\t\t\t\tprocess.kill(-serverProcess.pid, 'SIGTERM')\n\t\t\t} else {\n\t\t\t\tserverProcess.kill()\n\t\t\t}\n\t\t\tthis.runningServers.delete(port)\n\t\t\treturn true\n\t\t} catch (error) {\n\t\t\tlogger.warn(\n\t\t\t\t`Failed to kill server process on port ${port}: ${error instanceof Error ? error.message : 'Unknown error'}`\n\t\t\t)\n\t\t\treturn false\n\t\t}\n\t}\n\n\t/**\n\t * Stop all tracked server processes. Called during cleanup.\n\t */\n\tasync stopAll(): Promise<void> {\n\t\tfor (const [port] of this.runningServers.entries()) {\n\t\t\tawait this.stop(port)\n\t\t}\n\t}\n\n\t/**\n\t * Wait for server to be ready by polling the port.\n\t * Exits early if the spawned process has already exited (crash detection).\n\t * Public so DevServerManager can reuse it for Docker mode readiness checks.\n\t *\n\t * @param port - Port to poll\n\t * @param processRef - Optional spawned process to monitor for early exit\n\t */\n\tasync waitForReady(port: number, processRef?: ExecaChildProcess): Promise<boolean> {\n\t\tconst startTime = Date.now()\n\t\tlet attempts = 0\n\n\t\twhile (Date.now() - startTime < this.startupTimeout) {\n\t\t\tattempts++\n\n\t\t\t// Early exit: if the spawned process has already exited, stop polling\n\t\t\t// Check both null and undefined since exitCode is undefined before the process exits\n\t\t\tif (processRef && processRef.exitCode != null) {\n\t\t\t\tlogger.warn(\n\t\t\t\t\t`Dev server process exited with code ${processRef.exitCode} before becoming ready (after ${attempts} attempts, ${Date.now() - startTime}ms)`\n\t\t\t\t)\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tconst processInfo = await this.processManager.detectDevServer(port)\n\n\t\t\tif (processInfo) {\n\t\t\t\tlogger.debug(\n\t\t\t\t\t`Server detected on port ${port} after ${attempts} attempts (${Date.now() - startTime}ms)`\n\t\t\t\t)\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\tawait setTimeout(this.checkInterval)\n\t\t}\n\n\t\tlogger.warn(\n\t\t\t`Server did not start on port ${port} after ${this.startupTimeout}ms (${attempts} attempts)`\n\t\t)\n\t\treturn false\n\t}\n}\n"],"mappings":"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","names":["execa","process","execa"]}
package/dist/cli.js CHANGED
@@ -25,7 +25,7 @@ import {
25
25
  import {
26
26
  BuildRunner,
27
27
  MergeManager
28
- } from "./chunk-SM3BCHYB.js";
28
+ } from "./chunk-EHAITKLS.js";
29
29
  import {
30
30
  assembleChildrenData
31
31
  } from "./chunk-C2BVNJW5.js";
@@ -1863,6 +1863,16 @@ var migrations = [
1863
1863
  ];
1864
1864
  await ensureGlobalGitignorePatterns(allIloomPatterns);
1865
1865
  }
1866
+ },
1867
+ {
1868
+ version: "0.13.1",
1869
+ description: "Add global gitignore for .env.local and .env.*.local files",
1870
+ migrate: async () => {
1871
+ await ensureGlobalGitignorePatterns([
1872
+ "**/.env.local",
1873
+ "**/.env.*.local"
1874
+ ]);
1875
+ }
1866
1876
  }
1867
1877
  ];
1868
1878
 
@@ -2455,7 +2465,7 @@ program.command("commit").alias("c").description("Commit all uncommitted files w
2455
2465
  program.command("rebase").description("Rebase current branch on main with Claude-assisted conflict resolution").option("-f, --force", "Skip confirmation prompts").option("-n, --dry-run", "Preview actions without executing").option("--json-stream", "Stream JSONL output; runs Claude headless for conflict resolution").action(async (options) => {
2456
2466
  const executeAction = async () => {
2457
2467
  try {
2458
- const { RebaseCommand } = await import("./rebase-CSGQICAP.js");
2468
+ const { RebaseCommand } = await import("./rebase-MLIN572O.js");
2459
2469
  const command = new RebaseCommand();
2460
2470
  const result = await command.execute(options);
2461
2471
  if (options.jsonStream && result) {
@@ -2523,7 +2533,7 @@ program.command("spin").alias("ignite").description("Launch Claude with auto-det
2523
2533
  program.command("open").description("Open workspace in browser or run CLI tool").argument("[identifier]", "Issue number, PR number, or branch name (auto-detected if omitted)").allowUnknownOption().action(async (identifier, _options, command) => {
2524
2534
  try {
2525
2535
  const args = (command == null ? void 0 : command.args) ? command.args.slice(identifier ? 1 : 0) : [];
2526
- const { OpenCommand } = await import("./open-WUTLRI6S.js");
2536
+ const { OpenCommand } = await import("./open-2HL6GV5F.js");
2527
2537
  const cmd = new OpenCommand();
2528
2538
  const input = identifier ? { identifier, args } : { args };
2529
2539
  await cmd.execute(input);
@@ -2535,7 +2545,7 @@ program.command("open").description("Open workspace in browser or run CLI tool")
2535
2545
  program.command("run").description("Run CLI tool or open workspace in browser").argument("[identifier]", "Issue number, PR number, or branch name (auto-detected if omitted)").allowUnknownOption().action(async (identifier, _options, command) => {
2536
2546
  try {
2537
2547
  const args = (command == null ? void 0 : command.args) ? command.args.slice(identifier ? 1 : 0) : [];
2538
- const { RunCommand } = await import("./run-3YL2IXXI.js");
2548
+ const { RunCommand } = await import("./run-CUNRQNZS.js");
2539
2549
  const cmd = new RunCommand();
2540
2550
  const input = identifier ? { identifier, args } : { args };
2541
2551
  await cmd.execute(input);
@@ -2555,7 +2565,7 @@ program.command("vscode").description("Install iloom VS Code extension and open
2555
2565
  });
2556
2566
  program.command("dev-server").alias("dev").description("Start dev server for workspace (foreground)").argument("[identifier]", "Issue number, PR number, or branch name (auto-detected if omitted)").option("--json", "Output as JSON").action(async (identifier, options) => {
2557
2567
  try {
2558
- const { DevServerCommand } = await import("./dev-server-67NPVWUN.js");
2568
+ const { DevServerCommand } = await import("./dev-server-UQKNKU2S.js");
2559
2569
  const cmd = new DevServerCommand();
2560
2570
  await cmd.execute({ identifier, json: options == null ? void 0 : options.json });
2561
2571
  } catch (error) {