@iloom/cli 0.3.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -3
- package/dist/{BranchNamingService-A77VI6AI.js → BranchNamingService-TOM2KAUT.js} +4 -3
- package/dist/ClaudeContextManager-VEGJTS5E.js +16 -0
- package/dist/ClaudeService-ICSHJMQ5.js +15 -0
- package/dist/GitHubService-RPM27GWD.js +12 -0
- package/dist/{LoomLauncher-ZV3ZZIBA.js → LoomLauncher-SJBZFZXE.js} +25 -22
- package/dist/LoomLauncher-SJBZFZXE.js.map +1 -0
- package/dist/PromptTemplateManager-2TDZAUC6.js +9 -0
- package/dist/README.md +13 -3
- package/dist/{SettingsManager-I2LRCW2A.js → SettingsManager-FJFU6JJD.js} +7 -3
- package/dist/SettingsMigrationManager-EH3J2TCN.js +10 -0
- package/dist/{chunk-UJL4HI2R.js → chunk-3NFBZRPR.js} +2 -2
- package/dist/chunk-6UIGZD2N.js +20 -0
- package/dist/chunk-6UIGZD2N.js.map +1 -0
- package/dist/{chunk-RIEO2WML.js → chunk-74VMN2KC.js} +26 -2
- package/dist/chunk-74VMN2KC.js.map +1 -0
- package/dist/{chunk-OYF4VIFI.js → chunk-75B2HZZ5.js} +147 -22
- package/dist/chunk-75B2HZZ5.js.map +1 -0
- package/dist/{chunk-PGPI5LR4.js → chunk-ADDNFQJ4.js} +7 -21
- package/dist/chunk-ADDNFQJ4.js.map +1 -0
- package/dist/{chunk-AKUJXDNW.js → chunk-F4J6KEL6.js} +3 -3
- package/dist/{chunk-DLHA5VQ3.js → chunk-HD5SUKI2.js} +36 -179
- package/dist/chunk-HD5SUKI2.js.map +1 -0
- package/dist/chunk-HHDSIE72.js +667 -0
- package/dist/chunk-HHDSIE72.js.map +1 -0
- package/dist/{chunk-OXAM2WVC.js → chunk-HVGQP44L.js} +21 -1
- package/dist/chunk-HVGQP44L.js.map +1 -0
- package/dist/{chunk-RW54ZMBM.js → chunk-JJUPY5MM.js} +2 -2
- package/dist/{chunk-UAN4A3YU.js → chunk-KM3W7YQX.js} +11 -11
- package/dist/{chunk-3RUPPQRG.js → chunk-KO2FOMHL.js} +43 -2
- package/dist/{chunk-3RUPPQRG.js.map → chunk-KO2FOMHL.js.map} +1 -1
- package/dist/{chunk-2MAIX45J.js → chunk-LTNDJMTH.js} +104 -43
- package/dist/chunk-LTNDJMTH.js.map +1 -0
- package/dist/{chunk-2CXREBLZ.js → chunk-M5XUCTTJ.js} +8 -6
- package/dist/chunk-M5XUCTTJ.js.map +1 -0
- package/dist/{chunk-4XIDC3NF.js → chunk-MD6HA5IK.js} +2 -2
- package/dist/chunk-MLS5FAV7.js +189 -0
- package/dist/chunk-MLS5FAV7.js.map +1 -0
- package/dist/{chunk-2IJEMXOB.js → chunk-NFVFVYAP.js} +419 -427
- package/dist/chunk-NFVFVYAP.js.map +1 -0
- package/dist/{chunk-OC4H6HJD.js → chunk-O7WHXLCB.js} +2 -2
- package/dist/{chunk-M7JJCX53.js → chunk-OEGECBFS.js} +20 -20
- package/dist/chunk-OEGECBFS.js.map +1 -0
- package/dist/{chunk-MKWYLDFK.js → chunk-OF7BNW4D.js} +43 -3
- package/dist/chunk-OF7BNW4D.js.map +1 -0
- package/dist/{chunk-SUOXY5WJ.js → chunk-P2WZIDF3.js} +5 -5
- package/dist/chunk-P2WZIDF3.js.map +1 -0
- package/dist/{chunk-PA6Q6AWM.js → chunk-PSFVTBM7.js} +2 -2
- package/dist/chunk-QHA67Q7A.js +281 -0
- package/dist/chunk-QHA67Q7A.js.map +1 -0
- package/dist/{chunk-ZM3CFL5L.js → chunk-QRBOPFAA.js} +3 -3
- package/dist/{chunk-IFB4Z76W.js → chunk-S44CHE3G.js} +13 -12
- package/dist/chunk-S44CHE3G.js.map +1 -0
- package/dist/{chunk-CE26YH2U.js → chunk-SJ2GZ6RF.js} +48 -50
- package/dist/chunk-SJ2GZ6RF.js.map +1 -0
- package/dist/{chunk-SSCQCCJ7.js → chunk-THF25ICZ.js} +2 -2
- package/dist/{chunk-5Q3NDNNV.js → chunk-TR5MC2U6.js} +153 -6
- package/dist/chunk-TR5MC2U6.js.map +1 -0
- package/dist/{chunk-5VK4NRSF.js → chunk-UNXRACJ7.js} +35 -36
- package/dist/chunk-UNXRACJ7.js.map +1 -0
- package/dist/{chunk-GEHQXLEI.js → chunk-UYVWLISQ.js} +18 -35
- package/dist/chunk-UYVWLISQ.js.map +1 -0
- package/dist/{chunk-OSCLCMDG.js → chunk-UYWAESOT.js} +3 -3
- package/dist/{chunk-ZT3YZB4K.js → chunk-VBFDVGAE.js} +12 -12
- package/dist/chunk-VBFDVGAE.js.map +1 -0
- package/dist/{chunk-CDZERT7Z.js → chunk-VWNS6DH5.js} +48 -4
- package/dist/chunk-VWNS6DH5.js.map +1 -0
- package/dist/{chunk-CFFQ2Z7A.js → chunk-WUQQNE63.js} +2 -2
- package/dist/{claude-W52VKI6L.js → claude-X7EBJRB2.js} +8 -5
- package/dist/{cleanup-H4VXU3C3.js → cleanup-7QVPYBJJ.js} +133 -122
- package/dist/cleanup-7QVPYBJJ.js.map +1 -0
- package/dist/cli.js +901 -425
- package/dist/cli.js.map +1 -1
- package/dist/{color-F7RU6B6Z.js → color-ZPIIUADB.js} +3 -3
- package/dist/{contribute-Y7IQV5QY.js → contribute-RZYCYUDX.js} +8 -6
- package/dist/{contribute-Y7IQV5QY.js.map → contribute-RZYCYUDX.js.map} +1 -1
- package/dist/dev-server-LOY7YWCP.js +298 -0
- package/dist/dev-server-LOY7YWCP.js.map +1 -0
- package/dist/{feedback-XTUCKJNT.js → feedback-562KPG5U.js} +13 -12
- package/dist/{feedback-XTUCKJNT.js.map → feedback-562KPG5U.js.map} +1 -1
- package/dist/{git-IYA53VIC.js → git-OXJACVAU.js} +16 -4
- package/dist/hooks/iloom-hook.js +258 -0
- package/dist/{ignite-T74RYXCA.js → ignite-VSIPGKKG.js} +245 -39
- package/dist/ignite-VSIPGKKG.js.map +1 -0
- package/dist/index.d.ts +459 -124
- package/dist/index.js +740 -210
- package/dist/index.js.map +1 -1
- package/dist/init-SCR2LQ4A.js +21 -0
- package/dist/{installation-detector-VARGFFRZ.js → installation-detector-6R6YOFVZ.js} +3 -3
- package/dist/mcp/issue-management-server.js +2 -1
- package/dist/mcp/issue-management-server.js.map +1 -1
- package/dist/neon-helpers-L5CXQ5CT.js +11 -0
- package/dist/{open-UMXANW5S.js → open-CX7HUE26.js} +12 -10
- package/dist/{open-UMXANW5S.js.map → open-CX7HUE26.js.map} +1 -1
- package/dist/projects-6DTNDVLH.js +73 -0
- package/dist/projects-6DTNDVLH.js.map +1 -0
- package/dist/{prompt-QALMYTVC.js → prompt-A7GGRHSY.js} +3 -3
- package/dist/prompts/init-prompt.txt +49 -0
- package/dist/prompts/issue-prompt.txt +110 -8
- package/dist/prompts/regular-prompt.txt +463 -14
- package/dist/prompts/session-summary-prompt.txt +82 -0
- package/dist/{rebase-VJ2VKR6R.js → rebase-55URTXZC.js} +11 -9
- package/dist/{rebase-VJ2VKR6R.js.map → rebase-55URTXZC.js.map} +1 -1
- package/dist/{remote-VUNCQZ6J.js → remote-73TZ2ADI.js} +3 -3
- package/dist/{run-MJYY4PUT.js → run-DP2U2CA2.js} +12 -10
- package/dist/{run-MJYY4PUT.js.map → run-DP2U2CA2.js.map} +1 -1
- package/dist/schema/settings.schema.json +49 -0
- package/dist/summary-J3CJSM7L.js +244 -0
- package/dist/summary-J3CJSM7L.js.map +1 -0
- package/dist/{test-git-IT5EWQ5C.js → test-git-QLAIBJLX.js} +6 -4
- package/dist/{test-git-IT5EWQ5C.js.map → test-git-QLAIBJLX.js.map} +1 -1
- package/dist/{test-prefix-NPWDPUUH.js → test-prefix-6YM2ZOON.js} +6 -4
- package/dist/{test-prefix-NPWDPUUH.js.map → test-prefix-6YM2ZOON.js.map} +1 -1
- package/dist/{test-tabs-PRMRSHKI.js → test-tabs-JGO3VOXJ.js} +4 -4
- package/dist/{test-webserver-DAHONWCS.js → test-webserver-VPNLAFZ3.js} +2 -2
- package/dist/{update-4TDDUR5K.js → update-LETF5ASC.js} +4 -4
- package/dist/{update-notifier-QEX3CJHA.js → update-notifier-H55ZK7NU.js} +3 -3
- package/package.json +6 -6
- package/dist/ClaudeContextManager-BN7RE5ZQ.js +0 -15
- package/dist/ClaudeService-DLYLJUPA.js +0 -14
- package/dist/GitHubService-FZHHBOFG.js +0 -11
- package/dist/LoomLauncher-ZV3ZZIBA.js.map +0 -1
- package/dist/PromptTemplateManager-6HH3PVXV.js +0 -9
- package/dist/SettingsMigrationManager-TJ7UWZG5.js +0 -10
- package/dist/chunk-2CXREBLZ.js.map +0 -1
- package/dist/chunk-2IJEMXOB.js.map +0 -1
- package/dist/chunk-2MAIX45J.js.map +0 -1
- package/dist/chunk-5Q3NDNNV.js.map +0 -1
- package/dist/chunk-5VK4NRSF.js.map +0 -1
- package/dist/chunk-CDZERT7Z.js.map +0 -1
- package/dist/chunk-CE26YH2U.js.map +0 -1
- package/dist/chunk-DLHA5VQ3.js.map +0 -1
- package/dist/chunk-GEHQXLEI.js.map +0 -1
- package/dist/chunk-IFB4Z76W.js.map +0 -1
- package/dist/chunk-M7JJCX53.js.map +0 -1
- package/dist/chunk-MKWYLDFK.js.map +0 -1
- package/dist/chunk-OXAM2WVC.js.map +0 -1
- package/dist/chunk-OYF4VIFI.js.map +0 -1
- package/dist/chunk-PGPI5LR4.js.map +0 -1
- package/dist/chunk-RIEO2WML.js.map +0 -1
- package/dist/chunk-SUOXY5WJ.js.map +0 -1
- package/dist/chunk-ZT3YZB4K.js.map +0 -1
- package/dist/cleanup-H4VXU3C3.js.map +0 -1
- package/dist/ignite-T74RYXCA.js.map +0 -1
- package/dist/init-4FHTAM3F.js +0 -19
- package/dist/logger-MKYH4UDV.js +0 -12
- package/dist/neon-helpers-77PBPGJ5.js +0 -10
- package/dist/update-notifier-QEX3CJHA.js.map +0 -1
- /package/dist/{BranchNamingService-A77VI6AI.js.map → BranchNamingService-TOM2KAUT.js.map} +0 -0
- /package/dist/{ClaudeContextManager-BN7RE5ZQ.js.map → ClaudeContextManager-VEGJTS5E.js.map} +0 -0
- /package/dist/{ClaudeService-DLYLJUPA.js.map → ClaudeService-ICSHJMQ5.js.map} +0 -0
- /package/dist/{GitHubService-FZHHBOFG.js.map → GitHubService-RPM27GWD.js.map} +0 -0
- /package/dist/{PromptTemplateManager-6HH3PVXV.js.map → PromptTemplateManager-2TDZAUC6.js.map} +0 -0
- /package/dist/{SettingsManager-I2LRCW2A.js.map → SettingsManager-FJFU6JJD.js.map} +0 -0
- /package/dist/{SettingsMigrationManager-TJ7UWZG5.js.map → SettingsMigrationManager-EH3J2TCN.js.map} +0 -0
- /package/dist/{chunk-UJL4HI2R.js.map → chunk-3NFBZRPR.js.map} +0 -0
- /package/dist/{chunk-AKUJXDNW.js.map → chunk-F4J6KEL6.js.map} +0 -0
- /package/dist/{chunk-RW54ZMBM.js.map → chunk-JJUPY5MM.js.map} +0 -0
- /package/dist/{chunk-UAN4A3YU.js.map → chunk-KM3W7YQX.js.map} +0 -0
- /package/dist/{chunk-4XIDC3NF.js.map → chunk-MD6HA5IK.js.map} +0 -0
- /package/dist/{chunk-OC4H6HJD.js.map → chunk-O7WHXLCB.js.map} +0 -0
- /package/dist/{chunk-PA6Q6AWM.js.map → chunk-PSFVTBM7.js.map} +0 -0
- /package/dist/{chunk-ZM3CFL5L.js.map → chunk-QRBOPFAA.js.map} +0 -0
- /package/dist/{chunk-SSCQCCJ7.js.map → chunk-THF25ICZ.js.map} +0 -0
- /package/dist/{chunk-OSCLCMDG.js.map → chunk-UYWAESOT.js.map} +0 -0
- /package/dist/{chunk-CFFQ2Z7A.js.map → chunk-WUQQNE63.js.map} +0 -0
- /package/dist/{claude-W52VKI6L.js.map → claude-X7EBJRB2.js.map} +0 -0
- /package/dist/{color-F7RU6B6Z.js.map → color-ZPIIUADB.js.map} +0 -0
- /package/dist/{git-IYA53VIC.js.map → git-OXJACVAU.js.map} +0 -0
- /package/dist/{init-4FHTAM3F.js.map → init-SCR2LQ4A.js.map} +0 -0
- /package/dist/{installation-detector-VARGFFRZ.js.map → installation-detector-6R6YOFVZ.js.map} +0 -0
- /package/dist/{logger-MKYH4UDV.js.map → neon-helpers-L5CXQ5CT.js.map} +0 -0
- /package/dist/{neon-helpers-77PBPGJ5.js.map → prompt-A7GGRHSY.js.map} +0 -0
- /package/dist/{prompt-QALMYTVC.js.map → remote-73TZ2ADI.js.map} +0 -0
- /package/dist/{test-tabs-PRMRSHKI.js.map → test-tabs-JGO3VOXJ.js.map} +0 -0
- /package/dist/{test-webserver-DAHONWCS.js.map → test-webserver-VPNLAFZ3.js.map} +0 -0
- /package/dist/{update-4TDDUR5K.js.map → update-LETF5ASC.js.map} +0 -0
- /package/dist/{remote-VUNCQZ6J.js.map → update-notifier-H55ZK7NU.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/MetadataManager.ts","../src/lib/LoomManager.ts","../src/lib/VSCodeIntegration.ts","../src/lib/EnvironmentManager.ts","../src/lib/CLIIsolationManager.ts","../src/lib/DatabaseManager.ts","../src/lib/ResourceCleanup.ts"],"sourcesContent":["import path from 'path'\nimport os from 'os'\nimport fs from 'fs-extra'\nimport { logger } from '../utils/logger.js'\n\n/**\n * Schema for metadata JSON file\n * Stored in ~/.config/iloom-ai/looms/\n */\nexport interface MetadataFile {\n description: string\n created_at?: string\n version: number\n // Additional metadata fields (v2)\n branchName?: string\n worktreePath?: string\n issueType?: 'branch' | 'issue' | 'pr'\n issue_numbers?: string[]\n pr_numbers?: string[]\n issueTracker?: string\n colorHex?: string // Stored hex color (e.g., \"#dcebff\") - robust against palette changes\n}\n\n/**\n * Input for writing metadata (all fields except version and created_at)\n * Note: issueTracker is required because every loom should have an associated\n * issue tracker provider (defaults to 'github' via IssueTrackerFactory)\n */\nexport interface WriteMetadataInput {\n description: string\n branchName: string\n worktreePath: string\n issueType: 'branch' | 'issue' | 'pr'\n issue_numbers: string[]\n pr_numbers: string[]\n issueTracker: string\n colorHex: string // Hex color (e.g., \"#dcebff\") - robust against palette changes\n}\n\n/**\n * Result of reading metadata for a worktree\n */\nexport interface LoomMetadata {\n description: string\n created_at: string | null\n branchName: string | null\n worktreePath: string | null\n issueType: 'branch' | 'issue' | 'pr' | null\n issue_numbers: string[]\n pr_numbers: string[]\n issueTracker: string | null\n colorHex: string | null // Hex color (e.g., \"#dcebff\") - robust against palette changes\n}\n\n/**\n * MetadataManager: Manage loom metadata persistence\n *\n * Stores loom metadata in ~/.config/iloom-ai/looms/ directory.\n * Each worktree gets a JSON file named by slugifying its absolute path.\n *\n * Per spec section 2.2:\n * - Filename derived from worktree absolute path\n * - Path separators replaced with double underscores\n * - Non-alphanumeric chars (except _ and -) replaced with hyphens\n */\nexport class MetadataManager {\n private readonly loomsDir: string\n\n constructor() {\n this.loomsDir = path.join(os.homedir(), '.config', 'iloom-ai', 'looms')\n }\n\n /**\n * Convert worktree path to filename slug per spec section 2.2\n *\n * Algorithm:\n * 1. Trim trailing slashes\n * 2. Replace all path separators (/ or \\) with __ (double underscore)\n * 3. Replace any other non-alphanumeric characters (except _ and -) with -\n * 4. Append .json\n *\n * Example:\n * - Worktree: /Users/jane/dev/repo\n * - Filename: _Users__jane__dev__repo.json\n */\n slugifyPath(worktreePath: string): string {\n // 1. Trim trailing slashes\n let slug = worktreePath.replace(/[/\\\\]+$/, '')\n\n // 2. Replace path separators with double underscores\n slug = slug.replace(/[/\\\\]/g, '___')\n\n // 3. Replace non-alphanumeric chars (except _ and -) with hyphens\n slug = slug.replace(/[^a-zA-Z0-9_-]/g, '-')\n\n // 4. Append .json\n return `${slug}.json`\n }\n\n /**\n * Get the full path to the metadata file for a worktree\n */\n private getFilePath(worktreePath: string): string {\n const filename = this.slugifyPath(worktreePath)\n return path.join(this.loomsDir, filename)\n }\n\n /**\n * Write metadata for a worktree (spec section 3.1)\n *\n * @param worktreePath - Absolute path to the worktree (used for file naming)\n * @param input - Metadata to write (description plus additional fields)\n */\n async writeMetadata(worktreePath: string, input: WriteMetadataInput): Promise<void> {\n try {\n // 1. Ensure looms directory exists\n await fs.ensureDir(this.loomsDir, { mode: 0o755 })\n\n // 2. Create JSON content\n const content: MetadataFile = {\n description: input.description,\n created_at: new Date().toISOString(),\n version: 1,\n branchName: input.branchName,\n worktreePath: input.worktreePath,\n issueType: input.issueType,\n issue_numbers: input.issue_numbers,\n pr_numbers: input.pr_numbers,\n issueTracker: input.issueTracker,\n colorHex: input.colorHex,\n }\n\n // 3. Write to slugified filename\n const filePath = this.getFilePath(worktreePath)\n await fs.writeFile(filePath, JSON.stringify(content, null, 2), { mode: 0o644 })\n\n logger.debug(`Metadata written for worktree: ${worktreePath}`)\n } catch (error) {\n // Log warning but don't throw - metadata is supplementary\n logger.warn(\n `Failed to write metadata for worktree: ${error instanceof Error ? error.message : String(error)}`\n )\n }\n }\n\n /**\n * Read metadata for a worktree (spec section 3.2)\n *\n * @param worktreePath - Absolute path to the worktree\n * @returns The metadata object with all fields, or null if not found/invalid\n */\n async readMetadata(worktreePath: string): Promise<LoomMetadata | null> {\n try {\n const filePath = this.getFilePath(worktreePath)\n\n // Check if file exists\n if (!(await fs.pathExists(filePath))) {\n return null\n }\n\n // Read and parse JSON\n const content = await fs.readFile(filePath, 'utf8')\n const data: MetadataFile = JSON.parse(content)\n\n if (!data.description) {\n return null\n }\n\n return {\n description: data.description,\n created_at: data.created_at ?? null,\n branchName: data.branchName ?? null,\n worktreePath: data.worktreePath ?? null,\n issueType: data.issueType ?? null,\n issue_numbers: data.issue_numbers ?? [],\n pr_numbers: data.pr_numbers ?? [],\n issueTracker: data.issueTracker ?? null,\n colorHex: data.colorHex ?? null,\n }\n } catch (error) {\n // Return null on any error (graceful degradation per spec)\n logger.debug(\n `Could not read metadata for worktree ${worktreePath}: ${error instanceof Error ? error.message : String(error)}`\n )\n return null\n }\n }\n\n /**\n * List all stored loom metadata files\n *\n * Returns an array of LoomMetadata objects for all valid metadata files\n * in the looms directory. Invalid or unreadable files are skipped.\n *\n * @returns Array of LoomMetadata objects from all stored files\n */\n async listAllMetadata(): Promise<LoomMetadata[]> {\n const results: LoomMetadata[] = []\n\n try {\n // Check if looms directory exists\n if (!(await fs.pathExists(this.loomsDir))) {\n return results\n }\n\n // Read all files in looms directory\n const files = await fs.readdir(this.loomsDir)\n\n // Filter to only .json files and read each\n for (const file of files) {\n if (!file.endsWith('.json')) {\n continue\n }\n\n try {\n const filePath = path.join(this.loomsDir, file)\n const content = await fs.readFile(filePath, 'utf8')\n const data: MetadataFile = JSON.parse(content)\n\n // Skip files without required description field\n if (!data.description) {\n continue\n }\n\n results.push({\n description: data.description,\n created_at: data.created_at ?? null,\n branchName: data.branchName ?? null,\n worktreePath: data.worktreePath ?? null,\n issueType: data.issueType ?? null,\n issue_numbers: data.issue_numbers ?? [],\n pr_numbers: data.pr_numbers ?? [],\n issueTracker: data.issueTracker ?? null,\n colorHex: data.colorHex ?? null,\n })\n } catch (error) {\n // Skip individual files that fail to parse (graceful degradation)\n logger.debug(\n `Skipping metadata file ${file}: ${error instanceof Error ? error.message : String(error)}`\n )\n }\n }\n } catch (error) {\n // Log error but return empty array (graceful degradation)\n logger.debug(\n `Could not list metadata files: ${error instanceof Error ? error.message : String(error)}`\n )\n }\n\n return results\n }\n\n /**\n * Delete metadata for a worktree (spec section 3.3)\n *\n * Idempotent: silently succeeds if file doesn't exist\n * Non-fatal: logs warning on permission errors but doesn't throw\n *\n * @param worktreePath - Absolute path to the worktree\n */\n async deleteMetadata(worktreePath: string): Promise<void> {\n try {\n const filePath = this.getFilePath(worktreePath)\n\n // Check if file exists - silently return if not\n if (!(await fs.pathExists(filePath))) {\n logger.debug(`No metadata file to delete for worktree: ${worktreePath}`)\n return\n }\n\n // Delete the file\n await fs.unlink(filePath)\n logger.debug(`Metadata deleted for worktree: ${worktreePath}`)\n } catch (error) {\n // Log warning on permission error but don't throw (per spec section 3.3)\n logger.warn(\n `Failed to delete metadata for worktree: ${error instanceof Error ? error.message : String(error)}`\n )\n }\n }\n}\n","import path from 'path'\nimport fs from 'fs-extra'\nimport { GitWorktreeManager } from './GitWorktreeManager.js'\nimport type { IssueTracker } from './IssueTracker.js'\nimport type { BranchNamingService } from './BranchNamingService.js'\nimport { EnvironmentManager } from './EnvironmentManager.js'\nimport { ClaudeContextManager } from './ClaudeContextManager.js'\nimport { ProjectCapabilityDetector } from './ProjectCapabilityDetector.js'\nimport { CLIIsolationManager } from './CLIIsolationManager.js'\nimport { VSCodeIntegration } from './VSCodeIntegration.js'\nimport { SettingsManager } from './SettingsManager.js'\nimport { MetadataManager, type WriteMetadataInput } from './MetadataManager.js'\nimport { branchExists, executeGitCommand, ensureRepositoryHasCommits, extractIssueNumber, isFileTrackedByGit } from '../utils/git.js'\nimport { installDependencies } from '../utils/package-manager.js'\nimport { generateColorFromBranchName, selectDistinctColor, hexToRgb, type ColorData } from '../utils/color.js'\nimport { DatabaseManager } from './DatabaseManager.js'\nimport { loadEnvIntoProcess, findEnvFileForDatabaseUrl } from '../utils/env.js'\nimport type { Loom, CreateLoomInput } from '../types/loom.js'\nimport type { GitWorktree } from '../types/worktree.js'\nimport type { Issue, PullRequest } from '../types/index.js'\nimport { logger } from '../utils/logger.js'\n\n/**\n * LoomManager orchestrates the creation and management of looms (isolated workspaces)\n * Bridges the gap between input validation and workspace operations\n */\nexport class LoomManager {\n private metadataManager: MetadataManager\n\n constructor(\n private gitWorktree: GitWorktreeManager,\n private issueTracker: IssueTracker,\n private branchNaming: BranchNamingService,\n private environment: EnvironmentManager,\n _claude: ClaudeContextManager, // Not stored - kept for DI compatibility, LoomLauncher creates its own\n private capabilityDetector: ProjectCapabilityDetector,\n private cliIsolation: CLIIsolationManager,\n private settings: SettingsManager,\n private database?: DatabaseManager\n ) {\n this.metadataManager = new MetadataManager()\n }\n\n /**\n * Get database branch name for a loom by reading its .env file\n * Returns null if database is not configured or branch cannot be determined\n *\n * @param loomPath - Path to the loom worktree\n */\n async getDatabaseBranchForLoom(loomPath: string): Promise<string | null> {\n if (!this.database) {\n return null\n }\n\n try {\n const envFilePath = path.join(loomPath, '.env')\n const settings = await this.settings.loadSettings()\n const databaseUrlVarName = settings.capabilities?.database?.databaseUrlEnvVarName ?? 'DATABASE_URL'\n\n // Get database connection string from loom's .env file\n const connectionString = await this.environment.getEnvVariable(envFilePath, databaseUrlVarName)\n\n if (!connectionString) {\n return null\n }\n\n return await this.database.getBranchNameFromConnectionString(connectionString, loomPath)\n } catch (error) {\n logger.debug(`Could not get database branch for loom at ${loomPath}: ${error instanceof Error ? error.message : 'Unknown error'}`)\n return null\n }\n }\n\n /**\n * Create a new loom (isolated workspace)\n * Orchestrates worktree creation, environment setup, and Claude context generation\n * NEW: Checks for existing worktrees and reuses them if found\n */\n async createIloom(input: CreateLoomInput): Promise<Loom> {\n // 1. Fetch issue/PR data if needed\n logger.info('Fetching issue data...')\n const issueData = await this.fetchIssueData(input)\n\n // NEW: Check for existing worktree BEFORE generating branch name (for efficiency)\n if (input.type === 'issue' || input.type === 'pr' || input.type === 'branch') {\n logger.info('Checking for existing worktree...')\n const existing = await this.findExistingIloom(input, issueData)\n if (existing) {\n logger.success(`Found existing worktree, reusing: ${existing.path}`)\n return await this.reuseIloom(existing, input, issueData)\n }\n logger.info('No existing worktree found, creating new one...')\n }\n\n // 2. Generate or validate branch name\n logger.info('Preparing branch name...')\n const branchName = await this.prepareBranchName(input, issueData)\n\n // 3. Create git worktree (WITHOUT dependency installation)\n logger.info('Creating git worktree...')\n const worktreePath = await this.createWorktreeOnly(input, branchName)\n\n // 4. Load main .env variables into process.env (like bash script lines 336-339)\n this.loadMainEnvFile()\n\n // 5. Detect project capabilities\n const { capabilities, binEntries } = await this.capabilityDetector.detectCapabilities(worktreePath)\n\n // 6. Copy environment files (.env) - ALWAYS done regardless of capabilities\n await this.copyEnvironmentFiles(worktreePath)\n\n // 7. Copy Loom settings (settings.local.json) - ALWAYS done regardless of capabilities\n // Pass parent branch name if this is a child loom\n await this.copyIloomSettings(worktreePath, input.parentLoom?.branchName)\n\n // 8. Setup PORT environment variable - ONLY for web projects\n // Load base port from settings\n const settingsData = await this.settings.loadSettings()\n const basePort = settingsData.capabilities?.web?.basePort ?? 3000\n\n let port = basePort // default\n if (capabilities.includes('web')) {\n port = await this.setupPortForWeb(worktreePath, input, basePort)\n }\n\n // 9. Install dependencies AFTER environment setup (like bash script line 757-769)\n try {\n await installDependencies(worktreePath, true, true)\n } catch (error) {\n // Log warning but don't fail - matches bash script behavior\n logger.warn(`Failed to install dependencies: ${error instanceof Error ? error.message : 'Unknown error'}`, error)\n }\n\n // 10. Setup database branch if configured\n let databaseBranch: string | undefined = undefined\n if (this.database && !input.options?.skipDatabase) {\n try {\n const connectionString = await this.database.createBranchIfConfigured(\n branchName,\n worktreePath, // workspace path - checks all dotenv-flow files\n undefined, // cwd\n input.parentLoom?.databaseBranch // fromBranch - use parent's database branch for child looms\n )\n\n if (connectionString) {\n const varName = this.database.getConfiguredVariableName()\n const targetFile = await findEnvFileForDatabaseUrl(\n worktreePath,\n varName,\n isFileTrackedByGit,\n async (p) => fs.pathExists(p),\n async (p, v) => this.environment.getEnvVariable(p, v)\n )\n await this.environment.setEnvVar(\n path.join(worktreePath, targetFile),\n varName,\n connectionString\n )\n logger.success('Database branch configured')\n databaseBranch = branchName\n }\n } catch (error) {\n logger.error(\n `Failed to setup database branch: ${error instanceof Error ? error.message : 'Unknown error'}`\n )\n throw error // Database creation failures are fatal\n }\n }\n\n // 10. Setup CLI isolation if project has CLI capability\n let cliSymlinks: string[] | undefined = undefined\n if (capabilities.includes('cli')) {\n try {\n cliSymlinks = await this.cliIsolation.setupCLIIsolation(\n worktreePath,\n input.identifier,\n binEntries\n )\n } catch (error) {\n // Log warning but don't fail - matches dependency installation behavior\n logger.warn(\n `Failed to setup CLI isolation: ${error instanceof Error ? error.message : 'Unknown error'}`,\n error\n )\n }\n }\n\n // 11. Select color with collision avoidance\n // Get hex colors in use from ALL stored looms across all projects (global collision detection)\n // This prevents color reuse across different repositories for the same user\n const allMetadata = await this.metadataManager.listAllMetadata()\n const usedHexColors: string[] = allMetadata\n .filter((metadata) => metadata.colorHex !== null)\n .map((metadata) => metadata.colorHex as string)\n\n // Select distinct color using hex-based comparison\n const colorData = selectDistinctColor(branchName, usedHexColors)\n logger.debug(`Selected color ${colorData.hex} for branch ${branchName} (${usedHexColors.length} colors in use globally)`)\n\n // Apply color synchronization (terminal and VSCode) based on settings\n try {\n await this.applyColorSynchronization(worktreePath, branchName, colorData, settingsData, input.options)\n } catch (error) {\n // Log warning but don't fail - colors are cosmetic\n logger.warn(\n `Failed to apply color synchronization: ${error instanceof Error ? error.message : 'Unknown error'}`,\n error\n )\n }\n\n // NEW: Move issue to In Progress (for new worktrees)\n if (input.type === 'issue') {\n try {\n logger.info('Moving issue to In Progress...')\n // Check if provider supports this optional method\n if (this.issueTracker.moveIssueToInProgress) {\n await this.issueTracker.moveIssueToInProgress(input.identifier as number)\n }\n } catch (error) {\n // Warn but don't fail - matches bash script behavior\n logger.warn(\n `Failed to move issue to In Progress: ${error instanceof Error ? error.message : 'Unknown error'}`,\n error\n )\n }\n }\n\n // 11.5. Launch workspace components based on individual flags\n const enableClaude = input.options?.enableClaude !== false\n const enableCode = input.options?.enableCode !== false\n const enableDevServer = input.options?.enableDevServer !== false\n const enableTerminal = input.options?.enableTerminal ?? false\n const oneShot = input.options?.oneShot ?? 'default'\n const setArguments = input.options?.setArguments\n const executablePath = input.options?.executablePath\n\n // Only launch if at least one component is enabled\n if (enableClaude || enableCode || enableDevServer || enableTerminal) {\n const { LoomLauncher } = await import('./LoomLauncher.js')\n const { ClaudeContextManager } = await import('./ClaudeContextManager.js')\n\n // Create ClaudeContextManager with shared SettingsManager to ensure CLI overrides work\n const claudeContext = new ClaudeContextManager(undefined, undefined, this.settings)\n const launcher = new LoomLauncher(claudeContext, this.settings)\n\n await launcher.launchLoom({\n enableClaude,\n enableCode,\n enableDevServer,\n enableTerminal,\n worktreePath,\n branchName,\n port,\n capabilities,\n workflowType: input.type === 'branch' ? 'regular' : input.type,\n identifier: input.identifier,\n ...(issueData?.title && { title: issueData.title }),\n oneShot,\n ...(setArguments && { setArguments }),\n ...(executablePath && { executablePath }),\n sourceEnvOnStart: settingsData.sourceEnvOnStart ?? false,\n colorTerminal: input.options?.colorTerminal ?? settingsData.colors?.terminal ?? true,\n colorHex: colorData.hex,\n })\n }\n\n // 12. Write loom metadata (spec section 3.1)\n // Derive description from issue/PR title or branch name\n const description = issueData?.title ?? branchName\n\n // Build issue/pr numbers arrays based on type\n const issue_numbers: string[] = input.type === 'issue' ? [String(input.identifier)] : []\n const pr_numbers: string[] = input.type === 'pr' ? [String(input.identifier)] : []\n\n const metadataInput: WriteMetadataInput = {\n description,\n branchName,\n worktreePath,\n issueType: input.type,\n issue_numbers,\n pr_numbers,\n issueTracker: this.issueTracker.providerName,\n colorHex: colorData.hex,\n }\n await this.metadataManager.writeMetadata(worktreePath, metadataInput)\n\n // 13. Create and return loom metadata\n const loom: Loom = {\n id: this.generateLoomId(input),\n path: worktreePath,\n branch: branchName,\n type: input.type,\n identifier: input.identifier,\n port,\n description,\n createdAt: new Date(),\n lastAccessed: new Date(),\n ...(databaseBranch !== undefined && { databaseBranch }),\n ...(capabilities.length > 0 && { capabilities }),\n ...(Object.keys(binEntries).length > 0 && { binEntries }),\n ...(cliSymlinks && cliSymlinks.length > 0 && { cliSymlinks }),\n ...(issueData !== null && {\n issueData: {\n title: issueData.title,\n body: issueData.body,\n url: issueData.url,\n state: issueData.state,\n },\n }),\n }\n\n logger.success(`Created loom: ${loom.id} at ${loom.path}`)\n return loom\n }\n\n /**\n * Finish a loom (merge work and cleanup)\n * Not yet implemented - see Issue #7\n */\n async finishIloom(_identifier: string): Promise<void> {\n throw new Error('Not implemented - see Issue #7')\n }\n\n\n /**\n * List all active looms\n */\n async listLooms(): Promise<Loom[]> {\n const worktrees = await this.gitWorktree.listWorktrees()\n if (!worktrees) {\n return []\n }\n return await this.mapWorktreesToLooms(worktrees)\n }\n\n /**\n * Find a specific loom by identifier\n * Case-insensitive matching for Linear IDs (MARK-1 vs mark-1)\n */\n async findIloom(identifier: string): Promise<Loom | null> {\n const looms = await this.listLooms()\n const lowerIdentifier = identifier.toLowerCase()\n return (\n looms.find(\n h =>\n h.id.toLowerCase() === lowerIdentifier ||\n h.identifier.toString().toLowerCase() === lowerIdentifier ||\n h.branch.toLowerCase() === lowerIdentifier\n ) ?? null\n )\n }\n\n /**\n * Find child looms for a given parent loom\n * Child looms are worktrees created with the parent loom as their base\n *\n * @param parentBranchName - The parent loom's branch name\n * @returns Array of child loom worktrees\n */\n async findChildLooms(parentBranchName: string): Promise<GitWorktree[]> {\n try {\n const worktrees = await this.gitWorktree.listWorktrees()\n if (!worktrees) {\n return []\n }\n\n // Sanitize parent branch name the same way as in createWorktreeOnly (lines 361-363)\n const sanitizedBranchName = parentBranchName\n .replace(/\\//g, '-')\n .replace(/[^a-zA-Z0-9-_]/g, '-')\n\n // Child looms are in directory: {sanitizedBranchName}-looms/\n const pattern = `${sanitizedBranchName}-looms/`\n\n return worktrees.filter(wt => wt.path.includes(pattern))\n } catch (error) {\n logger.debug(`Failed to find child looms: ${error instanceof Error ? error.message : 'Unknown error'}`)\n return []\n }\n }\n\n /**\n * Check for child looms and warn user if any exist\n * This is useful before finishing or cleaning up a parent loom\n *\n * @param branchName - Optional branch name to check. If not provided, uses current branch.\n * @returns true if child looms were found, false otherwise\n */\n async checkAndWarnChildLooms(branchName?: string): Promise<boolean> {\n // Use provided branch name or get current branch\n let targetBranch: string | null | undefined = branchName\n if (!targetBranch) {\n const { getCurrentBranch } = await import('../utils/git.js')\n targetBranch = await getCurrentBranch()\n }\n\n // Skip if not on a branch\n if (!targetBranch) {\n return false\n }\n\n const childLooms = await this.findChildLooms(targetBranch)\n if (childLooms.length > 0) {\n logger.warn(`Found ${childLooms.length} child loom(s) that should be finished first:`)\n for (const child of childLooms) {\n logger.warn(` - ${child.path}`)\n }\n logger.warn('')\n logger.warn('To finish child looms:')\n for (const child of childLooms) {\n // Extract identifier from child branch for finish command\n // Check PR first since PR branches often contain issue numbers too\n const prMatch = child.branch.match(/_pr_(\\d+)/)\n const issueId = extractIssueNumber(child.branch)\n\n const childIdentifier = prMatch\n ? prMatch[1] // PR: use number\n : issueId ?? child.branch // Issue: use extracted ID (alphanumeric or numeric), or branch name\n\n logger.warn(` il finish ${childIdentifier}`)\n }\n logger.warn('')\n return true\n }\n\n return false\n }\n\n /**\n * Fetch issue/PR data based on input type\n */\n private async fetchIssueData(\n input: CreateLoomInput\n ): Promise<Issue | PullRequest | null> {\n if (input.type === 'issue') {\n return await this.issueTracker.fetchIssue(input.identifier as number)\n } else if (input.type === 'pr') {\n // Check if provider supports PRs before calling\n if (!this.issueTracker.supportsPullRequests || !this.issueTracker.fetchPR) {\n throw new Error('Issue tracker does not support pull requests')\n }\n return await this.issueTracker.fetchPR(input.identifier as number)\n }\n return null\n }\n\n /**\n * Prepare branch name based on input type and issue/PR data\n */\n private async prepareBranchName(\n input: CreateLoomInput,\n issueData: Issue | PullRequest | null\n ): Promise<string> {\n if (input.type === 'branch') {\n return input.identifier as string\n }\n\n if (input.type === 'pr' && issueData && 'branch' in issueData) {\n return issueData.branch\n }\n\n if (input.type === 'issue' && issueData) {\n // Use BranchNamingService for AI-powered branch name generation\n const branchName = await this.branchNaming.generateBranchName({\n issueNumber: input.identifier as number,\n title: issueData.title,\n })\n return branchName\n }\n\n // Fallback for edge cases\n if (input.type === 'pr') {\n return `pr-${input.identifier}`\n }\n\n throw new Error(`Unable to determine branch name for input type: ${input.type}`)\n }\n\n /**\n * Create worktree for the loom (without dependency installation)\n */\n private async createWorktreeOnly(\n input: CreateLoomInput,\n branchName: string\n ): Promise<string> {\n // Ensure repository has at least one commit (needed for worktree creation)\n // This handles the case where the repo is completely empty (post git init, pre-first commit)\n logger.info('Ensuring repository has initial commit...')\n await ensureRepositoryHasCommits(this.gitWorktree.workingDirectory)\n\n // Load worktree prefix from settings\n const settingsData = await this.settings.loadSettings()\n let worktreePrefix = settingsData.worktreePrefix\n\n // If this is a child loom, compute dynamic prefix based on parent\n if (input.parentLoom) {\n // Sanitize branch name for directory use\n const sanitizedBranchName = input.parentLoom.branchName\n .replace(/\\//g, '-')\n .replace(/[^a-zA-Z0-9-_]/g, '-')\n worktreePrefix = `${sanitizedBranchName}-looms/`\n logger.info(`Creating child loom with prefix: ${worktreePrefix}`)\n }\n\n // Build options object, only including prefix if it's defined\n const pathOptions: { isPR?: boolean; prNumber?: number; prefix?: string } =\n input.type === 'pr'\n ? { isPR: true, prNumber: input.identifier as number }\n : {}\n\n if (worktreePrefix !== undefined) {\n pathOptions.prefix = worktreePrefix\n }\n\n const worktreePath = this.gitWorktree.generateWorktreePath(\n branchName,\n undefined,\n pathOptions\n )\n\n // Fetch all remote branches to ensure we have latest refs (especially for PRs)\n // Ports: bash script lines 667-674\n if (input.type === 'pr') {\n logger.info('Fetching all remote branches...')\n try {\n await executeGitCommand(['fetch', 'origin'], { cwd: this.gitWorktree.workingDirectory })\n logger.success('Successfully fetched from remote')\n } catch (error) {\n throw new Error(\n `Failed to fetch from remote: ${error instanceof Error ? error.message : 'Unknown error'}. ` +\n `Make sure you have access to the repository.`\n )\n }\n }\n\n // Check if branch exists locally (used for different purposes depending on type)\n const branchExistedLocally = await branchExists(branchName)\n\n // For non-PRs, throw error if branch exists\n // For PRs, we'll use this to determine if we need to reset later\n if (input.type !== 'pr' && branchExistedLocally) {\n throw new Error(\n `Cannot create worktree: branch '${branchName}' already exists. ` +\n `Use 'git branch -D ${branchName}' to delete it first if needed.`\n )\n }\n\n // Determine base branch: use parent's branch for child looms, otherwise use explicit baseBranch or default (main)\n const baseBranch = input.parentLoom?.branchName ?? input.baseBranch\n\n await this.gitWorktree.createWorktree({\n path: worktreePath,\n branch: branchName,\n createBranch: input.type !== 'pr', // PRs use existing branches\n ...(baseBranch && { baseBranch }),\n })\n\n // Reset PR branch to match remote exactly (if we created a new local branch)\n // Ports: bash script lines 689-713\n if (input.type === 'pr' && !branchExistedLocally) {\n logger.info('Resetting new PR branch to match remote exactly...')\n try {\n await executeGitCommand(['reset', '--hard', `origin/${branchName}`], { cwd: worktreePath })\n await executeGitCommand(['branch', '--set-upstream-to', `origin/${branchName}`], { cwd: worktreePath })\n logger.success('Successfully reset to match remote')\n } catch (error) {\n logger.warn(`Failed to reset to match remote: ${error instanceof Error ? error.message : 'Unknown error'}`)\n }\n }\n\n return worktreePath\n }\n\n /**\n * Copy user application environment files (.env) from main repo to worktree\n * Copies all dotenv-flow patterns: .env, .env.local, .env.{NODE_ENV}, .env.{NODE_ENV}.local\n * Only copies files that exist and are NOT tracked by git (tracked files exist via worktree)\n * Always called regardless of project capabilities\n */\n private async copyEnvironmentFiles(worktreePath: string): Promise<void> {\n const mainWorkspacePath = this.gitWorktree.workingDirectory\n const nodeEnv = process.env.DOTENV_FLOW_NODE_ENV ?? 'development'\n\n // Define all dotenv-flow patterns to copy\n const envFilePatterns = [\n '.env',\n '.env.local',\n `.env.${nodeEnv}`,\n `.env.${nodeEnv}.local`\n ]\n\n for (const pattern of envFilePatterns) {\n try {\n const mainEnvPath = path.join(mainWorkspacePath, pattern)\n const worktreeEnvPath = path.join(worktreePath, pattern)\n\n // Skip if file doesn't exist in main workspace\n if (!(await fs.pathExists(mainEnvPath))) {\n continue\n }\n\n // Skip if file is tracked by git (it will exist in worktree via git)\n if (await isFileTrackedByGit(pattern, mainWorkspacePath)) {\n logger.debug(`Skipping ${pattern} (tracked by git, already in worktree)`)\n continue\n }\n\n // Skip if file already exists in worktree\n if (await fs.pathExists(worktreeEnvPath)) {\n logger.warn(`${pattern} already exists in worktree, skipping copy`)\n continue\n }\n\n // Copy the untracked env file\n await this.environment.copyIfExists(mainEnvPath, worktreeEnvPath)\n logger.debug(`Copied ${pattern} to worktree`)\n } catch (error) {\n // Handle gracefully if individual file fails to copy\n logger.warn(`Warning: Failed to copy ${pattern}: ${error instanceof Error ? error.message : 'Unknown error'}`)\n }\n }\n }\n\n /**\n * Copy iloom configuration (settings.local.json) from main repo to worktree\n * Always called regardless of project capabilities\n * @param worktreePath Path to the worktree\n * @param parentBranchName Optional parent branch name for child looms (sets mainBranch)\n */\n private async copyIloomSettings(worktreePath: string, parentBranchName?: string): Promise<void> {\n const mainSettingsLocalPath = path.join(process.cwd(), '.iloom', 'settings.local.json')\n\n try {\n const worktreeIloomDir = path.join(worktreePath, '.iloom')\n\n // Ensure .iloom directory exists in worktree\n await fs.ensureDir(worktreeIloomDir)\n\n const worktreeSettingsLocalPath = path.join(worktreeIloomDir, 'settings.local.json')\n\n // Check if settings.local.json already exists in worktree\n if (await fs.pathExists(worktreeSettingsLocalPath)) {\n logger.warn('settings.local.json already exists in worktree, skipping copy')\n } else {\n await this.environment.copyIfExists(mainSettingsLocalPath, worktreeSettingsLocalPath)\n }\n\n // If this is a child loom, update mainBranch setting\n if (parentBranchName) {\n let existingSettings = {}\n\n try {\n const content = await fs.readFile(worktreeSettingsLocalPath, 'utf8')\n existingSettings = JSON.parse(content)\n } catch {\n // File doesn't exist or invalid, start fresh\n }\n\n const updatedSettings = {\n ...existingSettings,\n mainBranch: parentBranchName,\n }\n\n await fs.writeFile(worktreeSettingsLocalPath, JSON.stringify(updatedSettings, null, 2))\n logger.info(`Set mainBranch to ${parentBranchName} for child loom`)\n }\n } catch (error) {\n logger.warn(`Warning: Failed to copy settings.local.json: ${error instanceof Error ? error.message : 'Unknown error'}`)\n }\n }\n\n /**\n * Setup PORT environment variable for web projects\n * Only called when project has web capabilities\n */\n private async setupPortForWeb(\n worktreePath: string,\n input: CreateLoomInput,\n basePort: number\n ): Promise<number> {\n const envFilePath = path.join(worktreePath, '.env.local')\n\n // Calculate port based on input type\n const options: { basePort: number; issueNumber?: number; prNumber?: number; branchName?: string } = { basePort }\n\n if (input.type === 'issue') {\n options.issueNumber = input.identifier as number\n } else if (input.type === 'pr') {\n options.prNumber = input.identifier as number\n } else if (input.type === 'branch') {\n options.branchName = input.identifier as string\n }\n\n const port = this.environment.calculatePort(options)\n\n await this.environment.setEnvVar(envFilePath, 'PORT', String(port))\n return port\n }\n\n /**\n * Load environment variables from main .env file into process.env\n * Uses dotenv-flow to handle various .env file patterns\n */\n private loadMainEnvFile(): void {\n const result = loadEnvIntoProcess({ path: process.cwd() })\n\n if (result.error) {\n // Handle gracefully if .env files don't exist\n logger.warn(`Warning: Could not load .env files: ${result.error.message}`)\n } else {\n logger.info('Loaded environment variables using dotenv-flow')\n if (result.parsed && Object.keys(result.parsed).length > 0) {\n logger.debug(`Loaded ${Object.keys(result.parsed).length} environment variables`)\n }\n }\n }\n\n /**\n * Generate a unique loom ID\n */\n private generateLoomId(input: CreateLoomInput): string {\n const prefix = input.type\n return `${prefix}-${input.identifier}`\n }\n\n /**\n * Calculate port for the loom\n * Base port: configurable via settings.capabilities.web.basePort (default 3000) + issue/PR number (or deterministic hash for branches)\n */\n private async calculatePort(input: CreateLoomInput): Promise<number> {\n // Load base port from settings\n const settingsData = await this.settings.loadSettings()\n const basePort = settingsData.capabilities?.web?.basePort ?? 3000\n\n if (input.type === 'issue' && typeof input.identifier === 'number') {\n return this.environment.calculatePort({ basePort, issueNumber: input.identifier })\n }\n\n if (input.type === 'pr' && typeof input.identifier === 'number') {\n return this.environment.calculatePort({ basePort, prNumber: input.identifier })\n }\n\n if (input.type === 'branch' && typeof input.identifier === 'string') {\n // Use deterministic hash for branch-based ports\n return this.environment.calculatePort({ basePort, branchName: input.identifier })\n }\n\n // Fallback: basePort only (shouldn't reach here with valid input)\n throw new Error(`Unknown input type: ${input.type}`)\n }\n\n\n /**\n * Apply color synchronization to both VSCode and terminal\n * Colors are cosmetic - errors are logged but don't block workflow\n * Respects colors settings for independent control\n *\n * DEFAULTS:\n * - terminal: true (always safe, only affects macOS Terminal.app)\n * - vscode: false (safe default, prevents unexpected file modifications)\n *\n * @param colorData - Pre-computed color data (from collision avoidance)\n */\n private async applyColorSynchronization(\n worktreePath: string,\n branchName: string,\n colorData: ColorData,\n settings: import('./SettingsManager.js').IloomSettings,\n options?: CreateLoomInput['options']\n ): Promise<void> {\n // Determine color settings: options override settings, settings override defaults\n // Note: vscode defaults to FALSE for safety\n const colorVscode = options?.colorVscode ?? settings.colors?.vscode ?? false\n const colorTerminal = options?.colorTerminal ?? settings.colors?.terminal ?? true\n\n if (!colorVscode && !colorTerminal) {\n logger.debug('Color synchronization disabled for both VSCode and terminal')\n return\n }\n\n // Apply VSCode title bar color if enabled (default: disabled for safety)\n if (colorVscode) {\n const vscode = new VSCodeIntegration()\n await vscode.setTitleBarColor(worktreePath, colorData.hex)\n logger.info(`Applied VSCode title bar color: ${colorData.hex} for branch: ${branchName}`)\n } else {\n logger.debug('VSCode color sync disabled (default: false for safety)')\n }\n\n // Note: Terminal color is applied during window creation in LoomLauncher\n // The colorTerminal setting is passed through to launch options\n }\n\n /**\n * Map worktrees to loom objects\n * This is a simplified conversion - in production we'd store loom metadata\n * Now reads metadata from MetadataManager (spec section 3.2)\n */\n private async mapWorktreesToLooms(worktrees: GitWorktree[]): Promise<Loom[]> {\n return await Promise.all(worktrees.map(async (wt) => {\n // Extract identifier from branch name\n let type: 'issue' | 'pr' | 'branch' = 'branch'\n let identifier: string | number = wt.branch\n\n if (wt.branch.startsWith('issue-')) {\n type = 'issue'\n identifier = parseInt(wt.branch.replace('issue-', ''), 10)\n } else if (wt.branch.startsWith('pr-')) {\n type = 'pr'\n identifier = parseInt(wt.branch.replace('pr-', ''), 10)\n }\n\n // Read metadata from persistent storage\n const loomMetadata = await this.metadataManager.readMetadata(wt.path)\n\n return {\n id: `${type}-${identifier}`,\n path: wt.path,\n branch: wt.branch,\n type,\n identifier,\n port: await this.calculatePort({ type, identifier, originalInput: '' }),\n ...(loomMetadata?.description && { description: loomMetadata.description }),\n createdAt: new Date(),\n lastAccessed: new Date(),\n }\n }))\n }\n\n /**\n * NEW: Find existing loom for the given input\n * Checks for worktrees matching the issue/PR identifier\n */\n private async findExistingIloom(\n input: CreateLoomInput,\n issueData: Issue | PullRequest | null\n ): Promise<GitWorktree | null> {\n if (input.type === 'issue') {\n return await this.gitWorktree.findWorktreeForIssue(input.identifier as number)\n } else if (input.type === 'pr' && issueData && 'branch' in issueData) {\n return await this.gitWorktree.findWorktreeForPR(\n input.identifier as number,\n issueData.branch\n )\n } else if (input.type === 'branch') {\n return await this.gitWorktree.findWorktreeForBranch(input.identifier as string)\n }\n return null\n }\n\n /**\n * NEW: Reuse an existing loom\n * Includes environment setup and database branching for existing worktrees\n * Ports: handle_existing_worktree() from bash script lines 168-215\n */\n private async reuseIloom(\n worktree: GitWorktree,\n input: CreateLoomInput,\n issueData: Issue | PullRequest | null\n ): Promise<Loom> {\n const worktreePath = worktree.path\n const branchName = worktree.branch\n\n // 1. Load main .env variables into process.env\n this.loadMainEnvFile()\n\n // 2. Detect capabilities (quick, no installation)\n const { capabilities, binEntries } = await this.capabilityDetector.detectCapabilities(worktreePath)\n\n // 3. Defensively copy .env and settings.local.json if missing\n await this.copyEnvironmentFiles(worktreePath)\n await this.copyIloomSettings(worktreePath)\n\n // 4. Setup PORT for web projects (ensure it's set even if .env existed)\n // Load base port from settings\n const settingsData = await this.settings.loadSettings()\n const basePort = settingsData.capabilities?.web?.basePort ?? 3000\n\n let port = basePort\n if (capabilities.includes('web')) {\n port = await this.setupPortForWeb(worktreePath, input, basePort)\n }\n\n // 5. Skip database branch creation for existing worktrees\n // The database branch should have been created when the worktree was first created\n // Matches bash script behavior: handle_existing_worktree() skips all setup\n logger.info('Database branch assumed to be already configured for existing worktree')\n const databaseBranch: string | undefined = undefined\n\n // 5.5. Read existing metadata to get colorHex (for reusing stored color)\n const existingMetadata = await this.metadataManager.readMetadata(worktreePath)\n\n // Determine colorHex for launch\n let colorHex: string\n if (existingMetadata?.colorHex) {\n // Use stored hex color (already migrated from colorIndex if needed in readMetadata)\n colorHex = existingMetadata.colorHex\n logger.debug(`Reusing stored color ${colorHex} for branch ${branchName}`)\n } else {\n // No metadata - fall back to hash-based\n const colorData = generateColorFromBranchName(branchName)\n colorHex = colorData.hex\n logger.debug(`No stored color, using hash-based color ${colorHex} for branch ${branchName}`)\n }\n\n // Apply color synchronization (VSCode colors for reused looms)\n // Mirrors createIloom() behavior at lines 205-214\n try {\n const colorData: ColorData = { hex: colorHex, rgb: hexToRgb(colorHex), index: 0 }\n await this.applyColorSynchronization(worktreePath, branchName, colorData, settingsData, input.options)\n } catch (error) {\n // Log warning but don't fail - colors are cosmetic\n logger.warn(\n `Failed to apply color synchronization: ${error instanceof Error ? error.message : 'Unknown error'}`,\n error\n )\n }\n\n // 6. Move issue to In Progress (for reused worktrees too)\n if (input.type === 'issue') {\n try {\n logger.info('Moving issue to In Progress...')\n // Check if provider supports this optional method\n if (this.issueTracker.moveIssueToInProgress) {\n await this.issueTracker.moveIssueToInProgress(input.identifier as number)\n }\n } catch (error) {\n logger.warn(\n `Failed to move issue to In Progress: ${error instanceof Error ? error.message : 'Unknown error'}`,\n error\n )\n }\n }\n\n // 7. Launch components (same as new worktree)\n const enableClaude = input.options?.enableClaude !== false\n const enableCode = input.options?.enableCode !== false\n const enableDevServer = input.options?.enableDevServer !== false\n const enableTerminal = input.options?.enableTerminal ?? false\n const oneShot = input.options?.oneShot ?? 'default'\n const setArguments = input.options?.setArguments\n const executablePath = input.options?.executablePath\n\n if (enableClaude || enableCode || enableDevServer || enableTerminal) {\n logger.info('Launching workspace components...')\n const { LoomLauncher } = await import('./LoomLauncher.js')\n const { ClaudeContextManager } = await import('./ClaudeContextManager.js')\n\n // Create ClaudeContextManager with shared SettingsManager to ensure CLI overrides work\n const claudeContext = new ClaudeContextManager(undefined, undefined, this.settings)\n const launcher = new LoomLauncher(claudeContext, this.settings)\n\n await launcher.launchLoom({\n enableClaude,\n enableCode,\n enableDevServer,\n enableTerminal,\n worktreePath,\n branchName,\n port,\n capabilities,\n workflowType: input.type === 'branch' ? 'regular' : input.type,\n identifier: input.identifier,\n ...(issueData?.title && { title: issueData.title }),\n oneShot,\n ...(setArguments && { setArguments }),\n ...(executablePath && { executablePath }),\n sourceEnvOnStart: settingsData.sourceEnvOnStart ?? false,\n colorTerminal: input.options?.colorTerminal ?? settingsData.colors?.terminal ?? true,\n colorHex,\n })\n }\n\n // 8. Write loom metadata if missing (spec section 3.1)\n // For reused looms, only write if metadata file doesn't exist\n const description = existingMetadata?.description ?? issueData?.title ?? branchName\n if (!existingMetadata) {\n // Build issue/pr numbers arrays based on type\n const issue_numbers: string[] = input.type === 'issue' ? [String(input.identifier)] : []\n const pr_numbers: string[] = input.type === 'pr' ? [String(input.identifier)] : []\n\n const metadataInput: WriteMetadataInput = {\n description,\n branchName,\n worktreePath,\n issueType: input.type,\n issue_numbers,\n pr_numbers,\n issueTracker: this.issueTracker.providerName,\n colorHex,\n }\n await this.metadataManager.writeMetadata(worktreePath, metadataInput)\n }\n\n // 9. Return loom metadata\n const loom: Loom = {\n id: this.generateLoomId(input),\n path: worktreePath,\n branch: branchName,\n type: input.type,\n identifier: input.identifier,\n port,\n description,\n createdAt: new Date(), // We don't have actual creation date, use now\n lastAccessed: new Date(),\n ...(databaseBranch !== undefined && { databaseBranch }),\n ...(capabilities.length > 0 && { capabilities }),\n ...(Object.keys(binEntries).length > 0 && { binEntries }),\n ...(issueData !== null && {\n issueData: {\n title: issueData.title,\n body: issueData.body,\n url: issueData.url,\n state: issueData.state,\n },\n }),\n }\n\n logger.success(`Reused existing loom: ${loom.id} at ${loom.path}`)\n return loom\n }\n}\n","import fs from 'fs-extra'\nimport path from 'path'\nimport { parse, modify, applyEdits } from 'jsonc-parser'\nimport { logger } from '../utils/logger.js'\nimport {\n\thexToRgb,\n\trgbToHex,\n\tlightenColor,\n\tcalculateForegroundColor,\n} from '../utils/color.js'\n\n/**\n * VSCode settings structure\n */\ninterface VSCodeSettings {\n\t'workbench.colorCustomizations'?: {\n\t\t// Title Bar\n\t\t'titleBar.activeBackground'?: string\n\t\t'titleBar.inactiveBackground'?: string\n\t\t'titleBar.activeForeground'?: string\n\t\t'titleBar.inactiveForeground'?: string\n\t\t// Status Bar\n\t\t'statusBar.background'?: string\n\t\t'statusBar.foreground'?: string\n\t\t'statusBarItem.hoverBackground'?: string\n\t\t'statusBarItem.remoteBackground'?: string\n\t\t'statusBarItem.remoteForeground'?: string\n\t\t// UI Accents\n\t\t'sash.hoverBorder'?: string\n\t\t'commandCenter.border'?: string\n\t\t[key: string]: string | undefined\n\t}\n\t[key: string]: unknown\n}\n\n/**\n * Manages VSCode settings.json manipulation for workspace color synchronization\n */\nexport class VSCodeIntegration {\n\t/**\n\t * Set VSCode title bar color for a workspace\n\t *\n\t * @param workspacePath - Path to workspace directory\n\t * @param hexColor - Hex color string (e.g., \"#dcebf8\")\n\t */\n\tasync setTitleBarColor(workspacePath: string, hexColor: string): Promise<void> {\n\t\tconst vscodeDir = path.join(workspacePath, '.vscode')\n\t\tconst settingsPath = path.join(vscodeDir, 'settings.json')\n\n\t\ttry {\n\t\t\t// Ensure .vscode directory exists\n\t\t\tawait fs.ensureDir(vscodeDir)\n\n\t\t\t// Read existing settings (or create empty object)\n\t\t\tconst settings = await this.readSettings(settingsPath)\n\n\t\t\t// Merge color settings\n\t\t\tconst updatedSettings = this.mergeColorSettings(settings, hexColor)\n\n\t\t\t// Write settings atomically\n\t\t\tawait this.writeSettings(settingsPath, updatedSettings)\n\n\t\t\tlogger.debug(`Set VSCode title bar color to ${hexColor} for ${workspacePath}`)\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to set VSCode title bar color: ${error instanceof Error ? error.message : 'Unknown error'}`\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Read VSCode settings from file\n\t * Supports JSONC (JSON with Comments)\n\t *\n\t * @param settingsPath - Path to settings.json file\n\t * @returns Parsed settings object\n\t */\n\tprivate async readSettings(settingsPath: string): Promise<VSCodeSettings> {\n\t\ttry {\n\t\t\t// Check if file exists\n\t\t\tif (!(await fs.pathExists(settingsPath))) {\n\t\t\t\treturn {}\n\t\t\t}\n\n\t\t\t// Read file content\n\t\t\tconst content = await fs.readFile(settingsPath, 'utf8')\n\n\t\t\t// Parse JSONC (handles comments)\n\t\t\tconst errors: import('jsonc-parser').ParseError[] = []\n\t\t\tconst settings = parse(content, errors, { allowTrailingComma: true })\n\n\t\t\t// Check for parse errors\n\t\t\tif (errors.length > 0) {\n\t\t\t\tconst firstError = errors[0]\n\t\t\t\tthrow new Error(`Invalid JSON: ${firstError ? firstError.error : 'Unknown parse error'}`)\n\t\t\t}\n\n\t\t\treturn settings ?? {}\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to parse settings.json: ${error instanceof Error ? error.message : 'Unknown error'}`\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Write VSCode settings to file atomically\n\t * Preserves comments if present (using JSONC parser)\n\t *\n\t * @param settingsPath - Path to settings.json file\n\t * @param settings - Settings object to write\n\t */\n\tprivate async writeSettings(\n\t\tsettingsPath: string,\n\t\tsettings: VSCodeSettings\n\t): Promise<void> {\n\t\ttry {\n\t\t\tlet content: string\n\n\t\t\t// Check if file exists with comments\n\t\t\tif (await fs.pathExists(settingsPath)) {\n\t\t\t\tconst existingContent = await fs.readFile(settingsPath, 'utf8')\n\n\t\t\t\t// Try to preserve comments by using jsonc-parser's modify function\n\t\t\t\tif (existingContent.includes('//') || existingContent.includes('/*')) {\n\t\t\t\t\t// File has comments - use JSONC modify to preserve them\n\t\t\t\t\tcontent = await this.modifyWithCommentsPreserved(existingContent, settings)\n\t\t\t\t} else {\n\t\t\t\t\t// No comments - use standard JSON.stringify\n\t\t\t\t\tcontent = JSON.stringify(settings, null, 2) + '\\n'\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// New file - use standard JSON.stringify\n\t\t\t\tcontent = JSON.stringify(settings, null, 2) + '\\n'\n\t\t\t}\n\n\t\t\t// Write atomically using temp file + rename\n\t\t\tconst tempPath = `${settingsPath}.tmp`\n\t\t\tawait fs.writeFile(tempPath, content, 'utf8')\n\t\t\tawait fs.rename(tempPath, settingsPath)\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to write settings.json: ${error instanceof Error ? error.message : 'Unknown error'}`\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Modify JSONC content while preserving comments\n\t *\n\t * @param existingContent - Original JSONC content\n\t * @param newSettings - New settings to apply\n\t * @returns Modified JSONC content with comments preserved\n\t */\n\tprivate async modifyWithCommentsPreserved(\n\t\texistingContent: string,\n\t\tnewSettings: VSCodeSettings\n\t): Promise<string> {\n\t\tlet modifiedContent = existingContent\n\n\t\t// Apply each setting modification\n\t\tfor (const [key, value] of Object.entries(newSettings)) {\n\t\t\tconst edits = modify(modifiedContent, [key], value, {})\n\t\t\tmodifiedContent = applyEdits(modifiedContent, edits)\n\t\t}\n\n\t\treturn modifiedContent\n\t}\n\n\t/**\n\t * Merge color settings into existing settings object\n\t *\n\t * @param existing - Existing settings object\n\t * @param hexColor - Hex color to apply (subtle palette color)\n\t * @returns Updated settings object with color merged\n\t */\n\tprivate mergeColorSettings(existing: VSCodeSettings, hexColor: string): VSCodeSettings {\n\t\t// Clone existing settings\n\t\tconst updated: VSCodeSettings = { ...existing }\n\n\t\t// Initialize workbench.colorCustomizations if needed\n\t\tupdated['workbench.colorCustomizations'] ??= {}\n\n\t\tconst colors = updated['workbench.colorCustomizations']\n\n\t\t// Convert hex to RGB for manipulation\n\t\tconst baseRgb = hexToRgb(hexColor)\n\n\t\t// Calculate foreground color based on background luminance\n\t\tconst foreground = calculateForegroundColor(baseRgb)\n\t\tconst foregroundTransparent = foreground.replace('#', '#') + '99' // Add 60% opacity\n\n\t\t// Create lighter variant for hover states\n\t\tconst lighterRgb = lightenColor(baseRgb, 0.05) // 5% lighter\n\t\tconst lighterHex = rgbToHex(lighterRgb.r, lighterRgb.g, lighterRgb.b)\n\n\t\t// Title Bar - subtle top indicator\n\t\tcolors['titleBar.activeBackground'] = hexColor\n\t\tcolors['titleBar.inactiveBackground'] = hexColor + '99' // Semi-transparent when unfocused\n\t\tcolors['titleBar.activeForeground'] = foreground\n\t\tcolors['titleBar.inactiveForeground'] = foregroundTransparent\n\n\t\t// Status Bar - constant visibility at bottom\n\t\tcolors['statusBar.background'] = hexColor\n\t\tcolors['statusBar.foreground'] = foreground\n\t\tcolors['statusBarItem.hoverBackground'] = lighterHex\n\t\tcolors['statusBarItem.remoteBackground'] = hexColor // When connected to remote\n\t\tcolors['statusBarItem.remoteForeground'] = foreground\n\n\t\t// UI Accents - subtle hints\n\t\tcolors['sash.hoverBorder'] = hexColor // Resize borders\n\t\tcolors['commandCenter.border'] = foregroundTransparent // Search box border\n\n\t\treturn updated\n\t}\n}\n","import fs from 'fs-extra'\nimport { createLogger } from '../utils/logger.js'\nimport type {\n PortAssignmentOptions,\n} from '../types/environment.js'\nimport {\n parseEnvFile,\n formatEnvLine,\n validateEnvVariable,\n} from '../utils/env.js'\nimport { calculatePortForBranch } from '../utils/port.js'\n\nconst logger = createLogger({ prefix: '📝' })\n\nexport class EnvironmentManager {\n private readonly backupSuffix: string = '.backup'\n\n /**\n * Set or update an environment variable in a .env file\n * Ports functionality from bash/utils/env-utils.sh:setEnvVar()\n * @returns The backup path if a backup was created\n */\n async setEnvVar(\n filePath: string,\n key: string,\n value: string,\n backup: boolean = false\n ): Promise<string | void> {\n // Validate variable name\n const validation = validateEnvVariable(key, value)\n if (!validation.valid) {\n throw new Error(validation.error ?? 'Invalid variable name')\n }\n\n const fileExists = await fs.pathExists(filePath)\n\n if (!fileExists) {\n // File doesn't exist, create it\n logger.info(`Creating ${filePath} with ${key}...`)\n const content = formatEnvLine(key, value)\n await fs.writeFile(filePath, content, 'utf8')\n logger.success(`${filePath} created with ${key}`)\n return\n }\n\n // File exists, read and parse it\n const existingContent = await fs.readFile(filePath, 'utf8')\n const envMap = parseEnvFile(existingContent)\n\n // Create backup if requested\n let backupPath: string | undefined\n if (backup) {\n backupPath = await this.createBackup(filePath)\n }\n\n // Update or add the variable\n envMap.set(key, value)\n\n // Rebuild the file content, preserving comments and empty lines\n const lines = existingContent.split('\\n')\n const newLines: string[] = []\n let variableUpdated = false\n\n for (const line of lines) {\n const trimmedLine = line.trim()\n\n // Preserve comments and empty lines\n if (!trimmedLine || trimmedLine.startsWith('#')) {\n newLines.push(line)\n continue\n }\n\n // Remove 'export ' prefix if present\n const cleanLine = trimmedLine.startsWith('export ')\n ? trimmedLine.substring(7)\n : trimmedLine\n\n // Check if this line contains our variable\n const equalsIndex = cleanLine.indexOf('=')\n if (equalsIndex !== -1) {\n const lineKey = cleanLine.substring(0, equalsIndex).trim()\n if (lineKey === key) {\n // Replace this line with the new value\n newLines.push(formatEnvLine(key, value))\n variableUpdated = true\n continue\n }\n }\n\n // Keep other lines as-is\n newLines.push(line)\n }\n\n // If variable wasn't in the file, add it at the end\n if (!variableUpdated) {\n logger.info(`Adding ${key} to ${filePath}...`)\n newLines.push(formatEnvLine(key, value))\n logger.success(`${key} added successfully`)\n } else {\n logger.info(`Updating ${key} in ${filePath}...`)\n logger.success(`${key} updated successfully`)\n }\n\n // Write the updated content\n const newContent = newLines.join('\\n')\n await fs.writeFile(filePath, newContent, 'utf8')\n\n return backupPath\n }\n\n /**\n * Read and parse a .env file\n */\n async readEnvFile(filePath: string): Promise<Map<string, string>> {\n try {\n const content = await fs.readFile(filePath, 'utf8')\n return parseEnvFile(content)\n } catch (error) {\n // If file doesn't exist or can't be read, return empty map\n logger.debug(\n `Could not read env file ${filePath}: ${error instanceof Error ? error.message : String(error)}`\n )\n return new Map()\n }\n }\n\n /**\n * Get a specific environment variable from a .env file\n * Returns null if file doesn't exist or variable is not found\n */\n async getEnvVariable(filePath: string, variableName: string): Promise<string | null> {\n const envVars = await this.readEnvFile(filePath)\n return envVars.get(variableName) ?? null\n }\n\n /**\n * Generic file copy helper that only copies if source exists\n * Does not throw if source file doesn't exist - just logs and returns\n * @private\n */\n async copyIfExists(\n source: string,\n destination: string\n ): Promise<void> {\n const sourceExists = await fs.pathExists(source)\n if (!sourceExists) {\n logger.debug(`Source file ${source} does not exist, skipping copy`)\n return\n }\n\n await fs.copy(source, destination, { overwrite: false })\n logger.success(`Copied ${source} to ${destination}`)\n }\n\n /**\n * Calculate unique port for workspace\n * Implements:\n * - Issue/PR: 3000 + issue/PR number\n * - Branch: 3000 + deterministic hash offset (1-999)\n */\n calculatePort(options: PortAssignmentOptions): number {\n const basePort = options.basePort ?? 3000\n\n // Priority: issueNumber > prNumber > branchName > basePort only\n if (options.issueNumber !== undefined) {\n // Try to parse as number for backward compatibility\n const numericIssue = typeof options.issueNumber === 'number'\n ? options.issueNumber\n : parseInt(String(options.issueNumber), 10)\n\n if (!isNaN(numericIssue) && String(numericIssue) === String(options.issueNumber)) {\n // Purely numeric issue ID - use arithmetic port calculation\n const port = basePort + numericIssue\n // Validate port range\n if (port > 65535) {\n throw new Error(\n `Calculated port ${port} exceeds maximum (65535). Use a lower base port or issue number.`\n )\n }\n return port\n }\n // Alphanumeric ID - use hash-based calculation\n return calculatePortForBranch(String(options.issueNumber), basePort)\n }\n\n if (options.prNumber !== undefined) {\n const port = basePort + options.prNumber\n // Validate port range\n if (port > 65535) {\n throw new Error(\n `Calculated port ${port} exceeds maximum (65535). Use a lower base port or PR number.`\n )\n }\n return port\n }\n\n if (options.branchName !== undefined) {\n // Use deterministic hash for branch-based workspaces\n return calculatePortForBranch(options.branchName, basePort)\n }\n\n // Fallback: basePort only (no offset)\n return basePort\n }\n\n /**\n * Set port environment variable for workspace\n */\n async setPortForWorkspace(\n envFilePath: string,\n issueNumber?: string | number,\n prNumber?: number,\n branchName?: string\n ): Promise<number> {\n const options: PortAssignmentOptions = {}\n if (issueNumber !== undefined) {\n options.issueNumber = issueNumber\n }\n if (prNumber !== undefined) {\n options.prNumber = prNumber\n }\n if (branchName !== undefined) {\n options.branchName = branchName\n }\n const port = this.calculatePort(options)\n await this.setEnvVar(envFilePath, 'PORT', String(port))\n return port\n }\n\n /**\n * Validate environment configuration\n */\n async validateEnvFile(\n filePath: string\n ): Promise<{ valid: boolean; errors: string[] }> {\n try {\n const content = await fs.readFile(filePath, 'utf8')\n const envMap = parseEnvFile(content)\n const errors: string[] = []\n\n // Validate each variable name\n for (const [key, value] of envMap.entries()) {\n const validation = validateEnvVariable(key, value)\n if (!validation.valid) {\n errors.push(`${key}: ${validation.error}`)\n }\n }\n\n return {\n valid: errors.length === 0,\n errors,\n }\n } catch (error) {\n return {\n valid: false,\n errors: [\n `Failed to read or parse file: ${error instanceof Error ? error.message : String(error)}`,\n ],\n }\n }\n }\n\n /**\n * Create backup of existing file\n */\n private async createBackup(filePath: string): Promise<string> {\n const timestamp = new Date().toISOString().replace(/[:.]/g, '-')\n const backupPath = `${filePath}${this.backupSuffix}-${timestamp}`\n await fs.copy(filePath, backupPath)\n logger.debug(`Created backup at ${backupPath}`)\n return backupPath\n }\n}\n","import fs from 'fs-extra'\nimport path from 'path'\nimport os from 'os'\nimport { runScript } from '../utils/package-manager.js'\nimport { readPackageJson, hasScript } from '../utils/package-json.js'\nimport { logger } from '../utils/logger.js'\n\nexport class CLIIsolationManager {\n private readonly iloomBinDir: string\n\n constructor() {\n this.iloomBinDir = path.join(os.homedir(), '.iloom', 'bin')\n }\n\n /**\n * Setup CLI isolation for a worktree\n * - Build the project\n * - Create versioned symlinks\n * - Check PATH configuration\n * @param worktreePath Path to the worktree\n * @param identifier Issue/PR number or branch identifier\n * @param binEntries Bin entries from package.json\n * @returns Array of created symlink names\n */\n async setupCLIIsolation(\n worktreePath: string,\n identifier: string | number,\n binEntries: Record<string, string>\n ): Promise<string[]> {\n // 1. Build the project\n await this.buildProject(worktreePath)\n\n // 2. Verify bin targets exist and are executable\n await this.verifyBinTargets(worktreePath, binEntries)\n\n // 3. Create ~/.iloom/bin if needed\n await fs.ensureDir(this.iloomBinDir)\n\n // 4. Create versioned symlinks\n const symlinkNames = await this.createVersionedSymlinks(\n worktreePath,\n identifier,\n binEntries\n )\n\n // 5. Check PATH and provide instructions if needed\n await this.ensureIloomBinInPath()\n\n return symlinkNames\n }\n\n /**\n * Build the project using package.json build script\n * @param worktreePath Path to the worktree\n */\n private async buildProject(worktreePath: string): Promise<void> {\n const pkgJson = await readPackageJson(worktreePath)\n\n if (!hasScript(pkgJson, 'build')) {\n logger.warn('No build script found in package.json - skipping build')\n return\n }\n\n logger.info('Building CLI tool...')\n await runScript('build', worktreePath, [], { quiet: true })\n logger.success('Build completed')\n }\n\n /**\n * Verify bin targets exist and are executable\n * @param worktreePath Path to the worktree\n * @param binEntries Bin entries from package.json\n */\n private async verifyBinTargets(\n worktreePath: string,\n binEntries: Record<string, string>\n ): Promise<void> {\n for (const binPath of Object.values(binEntries)) {\n const targetPath = path.resolve(worktreePath, binPath)\n\n // Check if file exists\n const exists = await fs.pathExists(targetPath)\n if (!exists) {\n throw new Error(`Bin target does not exist: ${targetPath}`)\n }\n\n // Check if file is executable\n try {\n await fs.access(targetPath, fs.constants.X_OK)\n } catch {\n // File is not executable, but that's okay - symlink will work anyway\n // The shebang in the file will determine how it's executed\n }\n }\n }\n\n /**\n * Create versioned symlinks in ~/.iloom/bin\n * @param worktreePath Path to the worktree\n * @param identifier Issue/PR number or branch identifier\n * @param binEntries Bin entries from package.json\n * @returns Array of created symlink names\n */\n private async createVersionedSymlinks(\n worktreePath: string,\n identifier: string | number,\n binEntries: Record<string, string>\n ): Promise<string[]> {\n const symlinkNames: string[] = []\n\n for (const [binName, binPath] of Object.entries(binEntries)) {\n const versionedName = `${binName}-${identifier}`\n const targetPath = path.resolve(worktreePath, binPath)\n const symlinkPath = path.join(this.iloomBinDir, versionedName)\n\n // Create symlink\n await fs.symlink(targetPath, symlinkPath)\n\n logger.success(`CLI available: ${versionedName}`)\n symlinkNames.push(versionedName)\n }\n\n return symlinkNames\n }\n\n /**\n * Check if ~/.iloom/bin is in PATH and provide setup instructions\n */\n private async ensureIloomBinInPath(): Promise<void> {\n const currentPath = process.env.PATH ?? ''\n if (currentPath.includes('.iloom/bin')) {\n return // Already configured\n }\n\n // Detect shell and RC file\n const shell = this.detectShell()\n const rcFile = this.getShellRcFile(shell)\n\n // Print setup instructions\n logger.warn('\\n⚠️ One-time PATH setup required:')\n logger.warn(` Add to ${rcFile}:`)\n logger.warn(` export PATH=\"$HOME/.iloom/bin:$PATH\"`)\n logger.warn(` Then run: source ${rcFile}\\n`)\n }\n\n /**\n * Detect current shell\n * @returns Shell name (zsh, bash, fish, etc.)\n */\n private detectShell(): string {\n const shell = process.env.SHELL ?? ''\n return shell.split('/').pop() ?? 'bash'\n }\n\n /**\n * Get RC file path for shell\n * @param shell Shell name\n * @returns RC file path\n */\n private getShellRcFile(shell: string): string {\n const rcFiles: Record<string, string> = {\n zsh: '~/.zshrc',\n bash: '~/.bashrc',\n fish: '~/.config/fish/config.fish'\n }\n return rcFiles[shell] ?? '~/.bashrc'\n }\n\n /**\n * Cleanup versioned CLI executables for a specific identifier\n * Removes all symlinks matching the pattern: {binName}-{identifier}\n *\n * @param identifier - Issue/PR number or branch identifier\n * @returns Array of removed symlink names\n */\n async cleanupVersionedExecutables(identifier: string | number): Promise<string[]> {\n const removed: string[] = []\n\n try {\n const files = await fs.readdir(this.iloomBinDir)\n\n for (const file of files) {\n if (this.matchesIdentifier(file, identifier)) {\n const symlinkPath = path.join(this.iloomBinDir, file)\n\n try {\n await fs.unlink(symlinkPath)\n removed.push(file)\n } catch (error) {\n // Silently skip if symlink already gone (ENOENT)\n const isEnoent = error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT'\n if (isEnoent) {\n removed.push(file)\n continue\n }\n\n // Log warning for other errors but continue cleanup\n logger.warn(\n `Failed to remove symlink ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`\n )\n }\n }\n }\n } catch (error) {\n // Handle missing bin directory gracefully\n const isEnoent = error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT'\n if (isEnoent) {\n logger.warn('No CLI executables directory found - nothing to cleanup')\n return []\n }\n\n // Re-throw unexpected errors\n throw error\n }\n\n if (removed.length > 0) {\n logger.success(`Removed CLI executables: ${removed.join(', ')}`)\n }\n\n return removed\n }\n\n /**\n * Find orphaned symlinks in ~/.iloom/bin\n * Returns symlinks that point to non-existent targets\n *\n * @returns Array of orphaned symlink information\n */\n async findOrphanedSymlinks(): Promise<Array<{ name: string; path: string; brokenTarget: string }>> {\n const orphaned: Array<{ name: string; path: string; brokenTarget: string }> = []\n\n try {\n const files = await fs.readdir(this.iloomBinDir)\n\n for (const file of files) {\n const symlinkPath = path.join(this.iloomBinDir, file)\n\n try {\n const stats = await fs.lstat(symlinkPath)\n\n if (stats.isSymbolicLink()) {\n const target = await fs.readlink(symlinkPath)\n\n // Check if target exists\n try {\n await fs.access(target)\n } catch {\n // Target doesn't exist - this is an orphaned symlink\n orphaned.push({\n name: file,\n path: symlinkPath,\n brokenTarget: target\n })\n }\n }\n } catch (error) {\n // Skip files we can't read\n logger.warn(\n `Failed to check symlink ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`\n )\n }\n }\n } catch (error) {\n // Handle missing bin directory\n const isEnoent = error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT'\n if (isEnoent) {\n return []\n }\n\n // Re-throw unexpected errors\n throw error\n }\n\n return orphaned\n }\n\n /**\n * Cleanup all orphaned symlinks\n * Removes symlinks that point to non-existent targets\n *\n * @returns Number of symlinks removed\n */\n async cleanupOrphanedSymlinks(): Promise<number> {\n const orphaned = await this.findOrphanedSymlinks()\n let removedCount = 0\n\n for (const symlink of orphaned) {\n try {\n await fs.unlink(symlink.path)\n removedCount++\n logger.success(`Removed orphaned symlink: ${symlink.name}`)\n } catch (error) {\n logger.warn(\n `Failed to remove orphaned symlink ${symlink.name}: ${error instanceof Error ? error.message : 'Unknown error'}`\n )\n }\n }\n\n return removedCount\n }\n\n /**\n * Check if a filename matches the versioned pattern for an identifier\n * Pattern: {binName}-{identifier}\n *\n * @param fileName - Name of the file to check\n * @param identifier - Issue/PR number or branch identifier\n * @returns True if the filename matches the pattern\n */\n private matchesIdentifier(fileName: string, identifier: string | number): boolean {\n const suffix = `-${identifier}`\n return fileName.endsWith(suffix)\n }\n}\n","import type { DatabaseProvider } from '../types/index.js'\nimport { EnvironmentManager } from './EnvironmentManager.js'\nimport { createLogger } from '../utils/logger.js'\nimport { hasVariableInAnyEnvFile } from '../utils/env.js'\nimport fs from 'fs-extra'\n\nconst logger = createLogger({ prefix: '🗂️' })\n\n/**\n * Database Manager - orchestrates database operations with conditional execution\n * Ports functionality from bash scripts with guard conditions:\n * 1. Database provider must be properly configured (provider.isConfigured())\n * 2. The worktree's .env file must contain the configured database URL variable (default: DATABASE_URL)\n *\n * This ensures database branching only occurs for projects that actually use databases\n */\nexport class DatabaseManager {\n constructor(\n private provider: DatabaseProvider,\n private environment: EnvironmentManager,\n private databaseUrlEnvVarName: string = 'DATABASE_URL'\n ) {\n // Debug: Show which database URL variable name is configured\n if (databaseUrlEnvVarName !== 'DATABASE_URL') {\n logger.debug(`🔧 DatabaseManager configured with custom variable: ${databaseUrlEnvVarName}`)\n } else {\n logger.debug('🔧 DatabaseManager using default variable: DATABASE_URL')\n }\n }\n\n /**\n * Get the configured database URL environment variable name\n */\n getConfiguredVariableName(): string {\n return this.databaseUrlEnvVarName\n }\n\n /**\n * Check if database branching should be used\n * Requires BOTH conditions:\n * 1. Database provider is properly configured (checked via provider.isConfigured())\n * 2. Any dotenv-flow file contains the configured database URL variable\n */\n async shouldUseDatabaseBranching(workspacePath: string): Promise<boolean> {\n // Check for provider configuration\n if (!this.provider.isConfigured()) {\n logger.debug('Skipping database branching: Database provider not configured')\n return false\n }\n\n // Check if any dotenv-flow file has the configured database URL variable\n const hasDatabaseUrl = await this.hasDatabaseUrlInEnv(workspacePath)\n if (!hasDatabaseUrl) {\n logger.debug(\n 'Skipping database branching: configured database URL variable not found in any env file'\n )\n return false\n }\n\n return true\n }\n\n /**\n * Create database branch only if configured\n * Returns connection string if branch was created, null if skipped\n *\n * @param branchName - Name of the branch to create\n * @param workspacePath - Path to workspace for configuration checks (checks all dotenv-flow files)\n * @param cwd - Optional working directory to run commands from\n * @param fromBranch - Optional parent branch to create from (for child looms)\n */\n async createBranchIfConfigured(\n branchName: string,\n workspacePath: string,\n cwd?: string,\n fromBranch?: string\n ): Promise<string | null> {\n // Guard condition: check if database branching should be used\n if (!(await this.shouldUseDatabaseBranching(workspacePath))) {\n return null\n }\n\n // Check CLI availability and authentication\n if (!(await this.provider.isCliAvailable())) {\n logger.warn('Skipping database branch creation: Neon CLI not available')\n logger.warn('Install with: npm install -g neonctl')\n return null\n }\n\n try {\n const isAuth = await this.provider.isAuthenticated(cwd)\n if (!isAuth) {\n logger.warn('Skipping database branch creation: Not authenticated with Neon CLI')\n logger.warn('Run: neon auth')\n return null\n }\n } catch (error) {\n // Authentication check failed with an unexpected error - surface it\n const errorMessage = error instanceof Error ? error.message : String(error)\n logger.error(`Database authentication check failed: ${errorMessage}`)\n throw error\n }\n\n try {\n // Create the branch (which checks for preview first)\n // Pass fromBranch if provided (for child looms), otherwise undefined (uses configured parent)\n const connectionString = await this.provider.createBranch(branchName, fromBranch, cwd)\n logger.success(`Database branch ready: ${this.provider.sanitizeBranchName(branchName)}`)\n return connectionString\n } catch (error) {\n logger.error(\n `Failed to create database branch: ${error instanceof Error ? error.message : String(error)}`\n )\n throw error\n }\n }\n\n /**\n * Delete database branch only if configured\n * Returns result object indicating what happened\n *\n * @param branchName - Name of the branch to delete\n * @param shouldCleanup - Boolean indicating if database cleanup should be performed (pre-fetched config)\n * @param isPreview - Whether this is a preview database branch\n * @param cwd - Optional working directory to run commands from (prevents issues with deleted directories)\n */\n async deleteBranchIfConfigured(\n branchName: string,\n shouldCleanup: boolean,\n isPreview: boolean = false,\n cwd?: string\n ): Promise<import('../types/index.js').DatabaseDeletionResult> {\n // If shouldCleanup is explicitly false, skip immediately\n if (shouldCleanup === false) {\n return {\n success: true,\n deleted: false,\n notFound: true, // Treat \"not configured\" as \"nothing to delete\"\n branchName\n }\n }\n\n // If shouldCleanup is explicitly true, validate provider configuration\n if (!this.provider.isConfigured()) {\n logger.debug('Skipping database branch deletion: Database provider not configured')\n return {\n success: true,\n deleted: false,\n notFound: true,\n branchName\n }\n }\n\n // Check CLI availability and authentication\n if (!(await this.provider.isCliAvailable())) {\n logger.info('Skipping database branch deletion: CLI tool not available')\n return {\n success: false,\n deleted: false,\n notFound: true,\n error: \"CLI tool not available\",\n branchName\n }\n }\n\n try {\n const isAuth = await this.provider.isAuthenticated(cwd)\n if (!isAuth) {\n logger.warn('Skipping database branch deletion: Not authenticated with DB Provider')\n return {\n success: false,\n deleted: false,\n notFound: false,\n error: \"Not authenticated with DB Provider\",\n branchName\n }\n }\n } catch (error) {\n // Authentication check failed with an unexpected error - surface it\n const errorMessage = error instanceof Error ? error.message : String(error)\n logger.error(`Database authentication check failed: ${errorMessage}`)\n return {\n success: false,\n deleted: false,\n notFound: false,\n error: `Authentication check failed: ${errorMessage}`,\n branchName\n }\n }\n\n try {\n // Call provider and return its result directly\n const result = await this.provider.deleteBranch(branchName, isPreview, cwd)\n return result\n } catch (error) {\n // Unexpected error (shouldn't happen since provider returns result object)\n logger.warn(\n `Unexpected error in database deletion: ${error instanceof Error ? error.message : String(error)}`\n )\n return {\n success: false,\n deleted: false,\n notFound: false,\n error: error instanceof Error ? error.message : String(error),\n branchName\n }\n }\n }\n\n /**\n * Get database branch name from connection string (reverse lookup)\n * Returns branch name if provider supports reverse lookup, null otherwise\n *\n * @param connectionString - Database connection string\n * @param cwd - Optional working directory to run commands from\n */\n async getBranchNameFromConnectionString(connectionString: string, cwd?: string): Promise<string | null> {\n // Check if provider supports reverse lookup (duck typing)\n if (!this.provider.isConfigured()) {\n logger.debug('Provider not configured, skipping reverse lookup')\n return null\n }\n\n if ('getBranchNameFromConnectionString' in this.provider &&\n typeof this.provider.getBranchNameFromConnectionString === 'function') {\n return this.provider.getBranchNameFromConnectionString(connectionString, cwd)\n }\n\n logger.debug('Provider does not support reverse lookup')\n return null\n }\n\n /**\n * Check if any dotenv-flow file has the configured database URL variable\n * CRITICAL: If user explicitly configured a custom variable name (not default),\n * throw an error if it's missing from all env files\n */\n private async hasDatabaseUrlInEnv(workspacePath: string): Promise<boolean> {\n try {\n // Debug: Show what we're looking for\n if (this.databaseUrlEnvVarName !== 'DATABASE_URL') {\n logger.debug(`Looking for custom database URL variable: ${this.databaseUrlEnvVarName}`)\n } else {\n logger.debug('Looking for default database URL variable: DATABASE_URL')\n }\n\n // Check all dotenv-flow files for the configured variable\n const hasConfiguredVar = await hasVariableInAnyEnvFile(\n workspacePath,\n this.databaseUrlEnvVarName,\n async (p) => fs.pathExists(p),\n async (p, v) => this.environment.getEnvVariable(p, v)\n )\n\n if (hasConfiguredVar) {\n if (this.databaseUrlEnvVarName !== 'DATABASE_URL') {\n logger.debug(`✅ Found custom database URL variable: ${this.databaseUrlEnvVarName}`)\n } else {\n logger.debug(`✅ Found default database URL variable: DATABASE_URL`)\n }\n return true\n }\n\n // If user explicitly configured a custom variable name (not the default)\n // and it's missing, throw an error\n if (this.databaseUrlEnvVarName !== 'DATABASE_URL') {\n logger.debug(`❌ Custom database URL variable '${this.databaseUrlEnvVarName}' not found in any env file`)\n throw new Error(\n `Configured database URL environment variable '${this.databaseUrlEnvVarName}' not found in any dotenv-flow file. ` +\n `Please add it to an .env file or update your iloom configuration.`\n )\n }\n\n // Fall back to DATABASE_URL when using default configuration\n const hasDefaultVar = await hasVariableInAnyEnvFile(\n workspacePath,\n 'DATABASE_URL',\n async (p) => fs.pathExists(p),\n async (p, v) => this.environment.getEnvVariable(p, v)\n )\n\n if (hasDefaultVar) {\n logger.debug('✅ Found fallback DATABASE_URL variable')\n } else {\n logger.debug('❌ No DATABASE_URL variable found in any env file')\n }\n return hasDefaultVar\n } catch (error) {\n // Re-throw configuration errors\n if (error instanceof Error && error.message.includes('not found in')) {\n throw error\n }\n // Return false for other errors\n return false\n }\n }\n}\n","import path from 'path'\nimport { GitWorktreeManager } from './GitWorktreeManager.js'\nimport { DatabaseManager } from './DatabaseManager.js'\nimport { ProcessManager } from './process/ProcessManager.js'\nimport { CLIIsolationManager } from './CLIIsolationManager.js'\nimport { SettingsManager } from './SettingsManager.js'\nimport { MetadataManager } from './MetadataManager.js'\nimport { logger } from '../utils/logger.js'\nimport { hasUncommittedChanges, executeGitCommand, findMainWorktreePathWithSettings, extractIssueNumber } from '../utils/git.js'\n\nimport type {\n\tResourceCleanupOptions,\n\tCleanupResult,\n\tOperationResult,\n\tSafetyCheck,\n\tBranchDeleteOptions,\n} from '../types/cleanup.js'\nimport type { GitWorktree } from '../types/worktree.js'\nimport type { ParsedInput } from '../commands/start.js'\n\n/**\n * Manages resource cleanup for worktrees\n * Provides shared cleanup functionality for finish and cleanup commands\n */\nexport class ResourceCleanup {\n\tprivate settingsManager: SettingsManager\n\tprivate metadataManager: MetadataManager\n\n\tconstructor(\n\t\tprivate gitWorktree: GitWorktreeManager,\n\t\tprivate processManager: ProcessManager,\n\t\tprivate database?: DatabaseManager,\n\t\tprivate cliIsolation?: CLIIsolationManager,\n\t\tsettingsManager?: SettingsManager\n\t) {\n\t\tthis.settingsManager = settingsManager ?? new SettingsManager()\n\t\tthis.metadataManager = new MetadataManager()\n\t}\n\n\t/**\n\t * Cleanup a worktree and associated resources\n\t * Main orchestration method\n\t *\n\t * @param parsed - ParsedInput from IdentifierParser with type information\n\t * @param options - Cleanup options\n\t */\n\tasync cleanupWorktree(\n\t\tparsed: ParsedInput,\n\t\toptions: ResourceCleanupOptions = {}\n\t): Promise<CleanupResult> {\n\t\tconst operations: OperationResult[] = []\n\t\tconst errors: Error[] = []\n\n\t\tconst displayIdentifier = parsed.branchName ?? parsed.number?.toString() ?? parsed.originalInput\n\t\tlogger.info(`Starting cleanup for: ${displayIdentifier}`)\n\n\t\t// Extract number from ParsedInput for port calculation\n\t\tconst number = parsed.number\n\n\t\t// Step 1: Terminate dev server if applicable\n\t\tif (number !== undefined) {\n\t\t\tconst port = this.processManager.calculatePort(number)\n\n\t\t\tif (options.dryRun) {\n\t\t\t\toperations.push({\n\t\t\t\t\ttype: 'dev-server',\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tmessage: `[DRY RUN] Would check for dev server on port ${port}`,\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\ttry {\n\t\t\t\t\tconst terminated = await this.terminateDevServer(port)\n\t\t\t\t\toperations.push({\n\t\t\t\t\t\ttype: 'dev-server',\n\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\tmessage: terminated\n\t\t\t\t\t\t\t? `Dev server on port ${port} terminated`\n\t\t\t\t\t\t\t: `No dev server running on port ${port}`,\n\t\t\t\t\t})\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst err = error instanceof Error ? error : new Error('Unknown error')\n\t\t\t\t\terrors.push(err)\n\t\t\t\t\toperations.push({\n\t\t\t\t\t\ttype: 'dev-server',\n\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\tmessage: `Failed to terminate dev server`,\n\t\t\t\t\t\terror: err.message,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Step 2: Find worktree using specific methods based on type\n\t\tlet worktree: GitWorktree | null = null\n\t\ttry {\n\t\t\t// Use specific finding methods based on parsed type for precision\n\t\t\tif (parsed.type === 'pr' && parsed.number !== undefined) {\n\t\t\t\t// For PRs, ensure the number is numeric (PRs are always numeric per GitHub)\n\t\t\t\tconst prNumber = typeof parsed.number === 'number' ? parsed.number : Number(parsed.number)\n\t\t\t\tif (isNaN(prNumber) || !isFinite(prNumber)) {\n\t\t\t\t\tthrow new Error(`Invalid PR number: ${parsed.number}. PR numbers must be numeric.`)\n\t\t\t\t}\n\t\t\t\t// For PRs, pass empty string for branchName since we're detecting from path pattern\n\t\t\t\tworktree = await this.gitWorktree.findWorktreeForPR(prNumber, '')\n\t\t\t} else if (parsed.type === 'issue' && parsed.number !== undefined) {\n\t\t\t\tworktree = await this.gitWorktree.findWorktreeForIssue(parsed.number)\n\t\t\t} else if (parsed.type === 'branch' && parsed.branchName) {\n\t\t\t\tworktree = await this.gitWorktree.findWorktreeForBranch(parsed.branchName)\n\t\t\t}\n\n\t\t\tif (!worktree) {\n\t\t\t\tthrow new Error(`No worktree found for identifier: ${displayIdentifier}`)\n\t\t\t}\n\n\t\t\tlogger.debug(`Found worktree: path=\"${worktree.path}\", branch=\"${worktree.branch}\"`)\n\t\t} catch (error) {\n\t\t\tconst err = error instanceof Error ? error : new Error('Unknown error')\n\t\t\terrors.push(err)\n\n\t\t\treturn {\n\t\t\t\tidentifier: displayIdentifier,\n\t\t\t\tsuccess: false,\n\t\t\t\toperations,\n\t\t\t\terrors,\n\t\t\t\trollbackRequired: false,\n\t\t\t}\n\t\t}\n\n\t\t// Step 2.5: Validate safety before proceeding with cleanup (unless force flag is set)\n\t\tif (!options.force) {\n\t\t\tconst safety = await this.validateWorktreeSafety(worktree, parsed.originalInput)\n\n\t\t\tif (!safety.isSafe) {\n\t\t\t\t// Format blocker messages for error output\n\t\t\t\tconst blockerMessage = safety.blockers.join('\\n\\n')\n\t\t\t\tthrow new Error(`Cannot cleanup:\\n\\n${blockerMessage}`)\n\t\t\t}\n\n\t\t\t// Log warnings if any\n\t\t\tif (safety.warnings.length > 0) {\n\t\t\t\tsafety.warnings.forEach(warning => {\n\t\t\t\t\tlogger.warn(warning)\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\t// Step 3: Pre-fetch database configuration before worktree removal\n\t\t// This config is used AFTER worktree deletion when env file no longer exists\n\t\tlet databaseConfig: { shouldCleanup: boolean; envFilePath: string } | null = null\n\t\tif (!options.keepDatabase && worktree) {\n\t\t\tconst envFilePath = path.join(worktree.path, '.env')\n\t\t\ttry {\n\t\t\t\t// Pre-check if database cleanup should happen by reading .env file now\n\t\t\t\tconst shouldCleanup = this.database\n\t\t\t\t\t? await this.database.shouldUseDatabaseBranching(envFilePath)\n\t\t\t\t\t: false\n\t\t\t\tdatabaseConfig = { shouldCleanup, envFilePath }\n\t\t\t} catch (error) {\n\t\t\t\t// If we can't read the config, we'll skip database cleanup\n\t\t\t\tlogger.warn(\n\t\t\t\t\t`Failed to read database config from ${envFilePath}, skipping database cleanup: ${\n\t\t\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t\t\t}`\n\t\t\t\t)\n\t\t\t\tdatabaseConfig = { shouldCleanup: false, envFilePath }\n\t\t\t}\n\t\t}\n\n\t\t// Step 3.5: Find main worktree path before deletion (needed for branch and database operations)\n\t\tlet mainWorktreePath: string | null = null\n\t\tif (!options.dryRun) {\n\t\t\ttry {\n\t\t\t\tmainWorktreePath = await findMainWorktreePathWithSettings(worktree.path, this.settingsManager)\n\t\t\t} catch (error) {\n\t\t\t\tlogger.warn(\n\t\t\t\t\t`Failed to find main worktree path: ${error instanceof Error ? error.message : String(error)}`\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\t// Step 4: Remove worktree\n\t\tif (options.dryRun) {\n\t\t\toperations.push({\n\t\t\t\ttype: 'worktree',\n\t\t\t\tsuccess: true,\n\t\t\t\tmessage: `[DRY RUN] Would remove worktree: ${worktree.path}`,\n\t\t\t})\n\t\t} else {\n\t\t\ttry {\n\t\t\t\tconst worktreeOptions: { force?: boolean; removeDirectory: true; removeBranch: false } =\n\t\t\t\t\t{\n\t\t\t\t\t\tremoveDirectory: true,\n\t\t\t\t\t\tremoveBranch: false, // Handle branch separately\n\t\t\t\t\t}\n\t\t\t\tif (options.force !== undefined) {\n\t\t\t\t\tworktreeOptions.force = options.force\n\t\t\t\t}\n\t\t\t\tawait this.gitWorktree.removeWorktree(worktree.path, worktreeOptions)\n\n\t\t\t\toperations.push({\n\t\t\t\t\ttype: 'worktree',\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tmessage: `Worktree removed: ${worktree.path}`,\n\t\t\t\t})\n\n\t\t\t\t// Step 4.5: Delete metadata file after worktree removal (spec section 3.3)\n\t\t\t\t// This is idempotent - silently succeeds if file doesn't exist\n\t\t\t\tawait this.metadataManager.deleteMetadata(worktree.path)\n\t\t\t\tlogger.debug(`Metadata file cleanup attempted for: ${worktree.path}`)\n\t\t\t} catch (error) {\n\t\t\t\tconst err = error instanceof Error ? error : new Error('Unknown error')\n\t\t\t\terrors.push(err)\n\t\t\t\toperations.push({\n\t\t\t\t\ttype: 'worktree',\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\tmessage: `Failed to remove worktree`,\n\t\t\t\t\terror: err.message,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\t// Step 5: Delete branch if requested\n\t\tif (options.deleteBranch && worktree) {\n\t\t\tif (options.dryRun) {\n\t\t\t\toperations.push({\n\t\t\t\t\ttype: 'branch',\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tmessage: `[DRY RUN] Would delete branch: ${worktree.branch}`,\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\ttry {\n\t\t\t\t\tconst branchOptions: BranchDeleteOptions = { dryRun: false }\n\t\t\t\t\tif (options.force !== undefined) {\n\t\t\t\t\t\tbranchOptions.force = options.force\n\t\t\t\t\t}\n\t\t\t\t\t// Pass main worktree path to ensure we can execute git commands\n\t\t\t\t\tawait this.deleteBranch(worktree.branch, branchOptions, mainWorktreePath ?? undefined)\n\n\t\t\t\t\toperations.push({\n\t\t\t\t\t\ttype: 'branch',\n\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\tmessage: `Branch deleted: ${worktree.branch}`,\n\t\t\t\t\t})\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst err = error instanceof Error ? error : new Error('Unknown error')\n\t\t\t\t\terrors.push(err)\n\t\t\t\t\toperations.push({\n\t\t\t\t\t\ttype: 'branch',\n\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\tmessage: `Failed to delete branch`,\n\t\t\t\t\t\terror: err.message,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Step 5.5: Cleanup CLI symlinks if CLI isolation is available\n\t\t// Derive identifier from parsed input (number for issue/PR, branchName for branch)\n\t\tconst cliIdentifier = parsed.number ?? parsed.branchName\n\t\tif (this.cliIsolation && cliIdentifier !== undefined) {\n\t\t\tif (options.dryRun) {\n\t\t\t\toperations.push({\n\t\t\t\t\ttype: 'cli-symlinks',\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tmessage: `[DRY RUN] Would cleanup CLI symlinks for: ${cliIdentifier}`,\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\ttry {\n\t\t\t\t\tconst removed = await this.cliIsolation.cleanupVersionedExecutables(cliIdentifier)\n\t\t\t\t\toperations.push({\n\t\t\t\t\t\ttype: 'cli-symlinks',\n\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\tmessage: removed.length > 0\n\t\t\t\t\t\t\t? `CLI symlinks removed: ${removed.length}`\n\t\t\t\t\t\t\t: 'No CLI symlinks to cleanup',\n\t\t\t\t\t})\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Log warning but don't fail\n\t\t\t\t\tconst err = error instanceof Error ? error : new Error('Unknown error')\n\t\t\t\t\terrors.push(err)\n\t\t\t\t\tlogger.warn(\n\t\t\t\t\t\t`CLI symlink cleanup failed: ${err.message}`\n\t\t\t\t\t)\n\t\t\t\t\toperations.push({\n\t\t\t\t\t\ttype: 'cli-symlinks',\n\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\tmessage: 'CLI symlink cleanup failed (non-fatal)',\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Step 6: Cleanup database after worktree and branch removal (using pre-read config)\n\t\tif (databaseConfig && worktree) {\n\t\t\tif (options.dryRun) {\n\t\t\t\toperations.push({\n\t\t\t\t\ttype: 'database',\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tmessage: `[DRY RUN] Would cleanup database branch for: ${worktree.branch}`,\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\ttry {\n\t\t\t\t\tif (databaseConfig.shouldCleanup && this.database) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t// Call database deletion with pre-fetched shouldCleanup value and main worktree path\n\t\t\t\t\t\t\t// This avoids reading the already-deleted env file and running commands from deleted directories\n\t\t\t\t\t\t\tconst deletionResult = await this.database.deleteBranchIfConfigured(\n\t\t\t\t\t\t\t\tworktree.branch,\n\t\t\t\t\t\t\t\tdatabaseConfig.shouldCleanup,\n\t\t\t\t\t\t\t\tfalse, // isPreview\n\t\t\t\t\t\t\t\tmainWorktreePath ?? undefined\n\t\t\t\t\t\t\t)\n\n\t\t\t\t\t\t\t// Create operation result based on what actually happened\n\t\t\t\t\t\t\tif (deletionResult.deleted) {\n\t\t\t\t\t\t\t\t// Branch was actually deleted\n\t\t\t\t\t\t\t\tlogger.info(`Database branch deleted: ${worktree.branch}`)\n\t\t\t\t\t\t\t\toperations.push({\n\t\t\t\t\t\t\t\t\ttype: 'database',\n\t\t\t\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\t\t\t\tmessage: `Database branch deleted`,\n\t\t\t\t\t\t\t\t\tdeleted: true,\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t} else if (deletionResult.notFound) {\n\t\t\t\t\t\t\t\t// Branch didn't exist - not an error, just nothing to delete\n\t\t\t\t\t\t\t\tlogger.debug(`No database branch found for: ${worktree.branch}`)\n\t\t\t\t\t\t\t\toperations.push({\n\t\t\t\t\t\t\t\t\ttype: 'database',\n\t\t\t\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\t\t\t\tmessage: `No database branch found (skipped)`,\n\t\t\t\t\t\t\t\t\tdeleted: false,\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t} else if (deletionResult.userDeclined) {\n\t\t\t\t\t\t\t\t// User declined preview database deletion\n\t\t\t\t\t\t\t\tlogger.info('Preview database deletion declined by user')\n\t\t\t\t\t\t\t\toperations.push({\n\t\t\t\t\t\t\t\t\ttype: 'database',\n\t\t\t\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\t\t\t\tmessage: `Database cleanup skipped (user declined)`,\n\t\t\t\t\t\t\t\t\tdeleted: false,\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t} else if (!deletionResult.success) {\n\t\t\t\t\t\t\t\t// Deletion failed with error\n\t\t\t\t\t\t\t\tconst errorMsg = deletionResult.error ?? 'Unknown error'\n\t\t\t\t\t\t\t\terrors.push(new Error(errorMsg))\n\t\t\t\t\t\t\t\tlogger.warn(`Database cleanup failed: ${errorMsg}`)\n\t\t\t\t\t\t\t\toperations.push({\n\t\t\t\t\t\t\t\t\ttype: 'database',\n\t\t\t\t\t\t\t\t\tsuccess: false, // Non-fatal, but report error\n\t\t\t\t\t\t\t\t\tmessage: `Database cleanup failed`,\n\t\t\t\t\t\t\t\t\terror: errorMsg,\n\t\t\t\t\t\t\t\t\tdeleted: false,\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// Unexpected state - log for debugging\n\t\t\t\t\t\t\t\terrors.push(new Error('Database cleanup in an unknown state'))\n\t\t\t\t\t\t\t\tlogger.warn('Database deletion returned unexpected result state')\n\t\t\t\t\t\t\t\toperations.push({\n\t\t\t\t\t\t\t\t\ttype: 'database',\n\t\t\t\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\t\t\t\tmessage: `Database cleanup in an unknown state`,\n\t\t\t\t\t\t\t\t\tdeleted: false,\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t// Unexpected exception (shouldn't happen with result object pattern)\n\t\t\t\t\t\t\terrors.push(error instanceof Error ? error : new Error(String(error)))\n\t\t\t\t\t\t\tlogger.warn(\n\t\t\t\t\t\t\t\t`Unexpected database cleanup exception: ${error instanceof Error ? error.message : String(error)}`\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\toperations.push({\n\t\t\t\t\t\t\t\ttype: 'database',\n\t\t\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\t\t\tmessage: `Database cleanup failed`,\n\t\t\t\t\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t\t\t\tdeleted: false,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Database manager not available or not configured\n\t\t\t\t\t\toperations.push({\n\t\t\t\t\t\t\ttype: 'database',\n\t\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\t\tmessage: `Database cleanup skipped (not available)`,\n\t\t\t\t\t\t\tdeleted: false,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// This catch block is for any unexpected errors in the outer logic\n\t\t\t\t\tconst err = error instanceof Error ? error : new Error('Unknown error')\n\t\t\t\t\terrors.push(err)\n\t\t\t\t\toperations.push({\n\t\t\t\t\t\ttype: 'database',\n\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\tmessage: `Database cleanup failed`,\n\t\t\t\t\t\terror: err.message,\n\t\t\t\t\t\tdeleted: false,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Calculate overall success\n\t\tconst success = errors.length === 0\n\n\t\treturn {\n\t\t\tidentifier: displayIdentifier,\n\t\t\tbranchName: worktree?.branch,\n\t\t\tsuccess,\n\t\t\toperations,\n\t\t\terrors,\n\t\t\trollbackRequired: false, // Cleanup operations are generally not reversible\n\t\t}\n\t}\n\n\t/**\n\t * Terminate dev server on specified port\n\t */\n\tasync terminateDevServer(port: number): Promise<boolean> {\n\t\tlogger.debug(`Checking for dev server on port ${port}`)\n\n\t\tconst processInfo = await this.processManager.detectDevServer(port)\n\n\t\tif (!processInfo) {\n\t\t\tlogger.debug(`No process found on port ${port}`)\n\t\t\treturn false\n\t\t}\n\n\t\tif (!processInfo.isDevServer) {\n\t\t\tlogger.warn(\n\t\t\t\t`Process on port ${port} (${processInfo.name}) doesn't appear to be a dev server, skipping`\n\t\t\t)\n\t\t\treturn false\n\t\t}\n\n\t\tlogger.info(`Terminating dev server: ${processInfo.name} (PID: ${processInfo.pid})`)\n\n\t\tawait this.processManager.terminateProcess(processInfo.pid)\n\n\t\t// Verify termination\n\t\tconst isFree = await this.processManager.verifyPortFree(port)\n\t\tif (!isFree) {\n\t\t\tthrow new Error(`Dev server may still be running on port ${port}`)\n\t\t}\n\n\t\treturn true\n\t}\n\n\t/**\n\t * Delete a Git branch with safety checks\n\t *\n\t * @param branchName - Name of the branch to delete\n\t * @param options - Delete options (force, dryRun)\n\t * @param cwd - Working directory to execute git command from (defaults to finding main worktree)\n\t */\n\tasync deleteBranch(\n\t\tbranchName: string,\n\t\toptions: BranchDeleteOptions = {},\n\t\tcwd?: string\n\t): Promise<boolean> {\n\t\t// Get protected branches list from centralized method\n\t\tconst protectedBranches = await this.settingsManager.getProtectedBranches(cwd)\n\n\t\t// Check for protected branches\n\t\tif (protectedBranches.includes(branchName)) {\n\t\t\tthrow new Error(`Cannot delete protected branch: ${branchName}`)\n\t\t}\n\n\t\tif (options.dryRun) {\n\t\t\tlogger.info(`[DRY RUN] Would delete branch: ${branchName}`)\n\t\t\treturn true\n\t\t}\n\n\t\t// Use GitWorktreeManager's removeWorktree with removeBranch option\n\t\t// Or execute git branch -D directly via executeGitCommand\n\t\ttry {\n\t\t\t// Use provided cwd, or find main worktree path as fallback\n\t\t\t// This ensures we're not running git commands from a deleted directory\n\t\t\tlet workingDir = cwd ?? await findMainWorktreePathWithSettings(undefined, this.settingsManager)\t\t\t\n\n\t\t\t// Use safe delete (-d) unless force is specified\n\t\t\tconst deleteFlag = options.force ? '-D' : '-d'\n\t\t\tawait executeGitCommand(['branch', deleteFlag, branchName], {\n\t\t\t\tcwd: workingDir\n\t\t\t})\n\n\t\t\tlogger.info(`Branch deleted: ${branchName}`)\n\t\t\treturn true\n\t\t} catch (error) {\n\t\t\tif (options.force) {\n\t\t\t\tthrow error\n\t\t\t}\n\n\t\t\t// For safe delete failures, check if it's actually an unmerged branch error\n\t\t\t// and provide helpful message only in that case, otherwise show the real error\n\t\t\tconst errorMessage = error instanceof Error ? error.message : String(error)\n\n\t\t\t// Git error for unmerged branch typically contains \"not fully merged\"\n\t\t\tif (errorMessage.includes('not fully merged')) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Cannot delete unmerged branch '${branchName}'. Use --force to delete anyway.`\n\t\t\t\t)\n\t\t\t}\n\n\t\t\t// For other errors (like branch doesn't exist), show the actual git error\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t/**\n\t * Cleanup database branch\n\t * Gracefully handles missing DatabaseManager\n\t *\n\t * @deprecated This method is deprecated and should not be used for post-deletion cleanup.\n\t * Use the pre-fetch mechanism in cleanupWorktree() instead.\n\t * This method will fail if called after worktree deletion because\n\t * it attempts to read the .env file which has been deleted.\n\t *\n\t * @param branchName - Name of the branch to delete\n\t * @param worktreePath - Path to worktree (must still exist with .env file)\n\t */\n\tasync cleanupDatabase(branchName: string, worktreePath: string): Promise<boolean> {\n\t\tif (!this.database) {\n\t\t\tlogger.debug('Database manager not available, skipping database cleanup')\n\t\t\treturn false\n\t\t}\n\n\t\ttry {\n\t\t\t// Pre-fetch configuration before deletion\n\t\t\tconst envFilePath = path.join(worktreePath, '.env')\n\t\t\tconst shouldCleanup = await this.database.shouldUseDatabaseBranching(envFilePath)\n\n\t\t\t// Find main worktree path to avoid running commands from potentially deleted directories\n\t\t\tlet cwd: string | undefined\n\t\t\ttry {\n\t\t\t\tcwd = await findMainWorktreePathWithSettings(worktreePath, this.settingsManager)\n\t\t\t} catch (error) {\n\t\t\t\t// If we can't find main worktree, commands will run from current directory\n\t\t\t\tlogger.debug(\n\t\t\t\t\t`Could not find main worktree path, using current directory: ${error instanceof Error ? error.message : String(error)}`\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tconst result = await this.database.deleteBranchIfConfigured(\n\t\t\t\tbranchName,\n\t\t\t\tshouldCleanup,\n\t\t\t\tfalse, // isPreview\n\t\t\t\tcwd\n\t\t\t)\n\n\t\t\t// Only return true if deletion actually occurred\n\t\t\tif (result.deleted) {\n\t\t\t\tlogger.info(`Database branch deleted: ${branchName}`)\n\t\t\t\treturn true\n\t\t\t} else if (result.notFound) {\n\t\t\t\tlogger.debug(`No database branch found for: ${branchName}`)\n\t\t\t\treturn false\n\t\t\t} else if (result.userDeclined) {\n\t\t\t\tlogger.info('Preview database deletion declined by user')\n\t\t\t\treturn false\n\t\t\t} else if (!result.success) {\n\t\t\t\tlogger.warn(`Database cleanup failed: ${result.error ?? 'Unknown error'}`)\n\t\t\t\treturn false\n\t\t\t} else {\n\t\t\t\t// Unexpected state\n\t\t\t\tlogger.debug('Database deletion returned unexpected result')\n\t\t\t\treturn false\n\t\t\t}\n\t\t} catch (error) {\n\t\t\t// Unexpected exception\n\t\t\tlogger.warn(\n\t\t\t\t`Unexpected database cleanup error: ${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 * Cleanup multiple worktrees\n\t */\n\tasync cleanupMultipleWorktrees(\n\t\tidentifiers: string[],\n\t\toptions: ResourceCleanupOptions = {}\n\t): Promise<CleanupResult[]> {\n\t\tconst results: CleanupResult[] = []\n\n\t\tfor (const identifier of identifiers) {\n\t\t\t// Parse the identifier to get ParsedInput format\n\t\t\tconst parsed = this.parseIdentifier(identifier)\n\t\t\tconst result = await this.cleanupWorktree(parsed, options)\n\t\t\tresults.push(result)\n\t\t}\n\n\t\treturn results\n\t}\n\n\t/**\n\t * Validate worktree safety given a worktree object\n\t * Private method used internally when worktree is already known\n\t */\n\tprivate async validateWorktreeSafety(\n\t\tworktree: GitWorktree,\n\t\tidentifier: string\n\t): Promise<SafetyCheck> {\n\t\tconst warnings: string[] = []\n\t\tconst blockers: string[] = []\n\n\t\t// Check if main worktree\n\t\tconst isMain = await this.gitWorktree.isMainWorktree(worktree, this.settingsManager)\n\t\tif (isMain) {\n\t\t\tblockers.push(`Cannot cleanup main worktree: \"${worktree.branch}\" @ \"${worktree.path}\"`)\n\t\t}\n\n\t\t// Check for uncommitted changes\n\t\tconst hasChanges = await hasUncommittedChanges(worktree.path)\n\t\tif (hasChanges) {\n\t\t\t// Create simple blocker message with actionable guidance\n\t\t\tconst blockerMessage =\n\t\t\t\t`Worktree has uncommitted changes.\\n\\n` +\n\t\t\t\t`Please resolve before cleanup - you have some options:\\n` +\n\t\t\t\t` • Commit changes: cd ${worktree.path} && git commit -am \"message\"\\n` +\n\t\t\t\t` • Stash changes: cd ${worktree.path} && git stash\\n` +\n\t\t\t\t` • Force cleanup: il cleanup ${identifier} --force (WARNING: will discard changes)`\n\n\t\t\tblockers.push(blockerMessage)\n\t\t}\n\n\t\treturn {\n\t\t\tisSafe: blockers.length === 0,\n\t\t\twarnings,\n\t\t\tblockers,\n\t\t}\n\t}\n\n\t/**\n\t * Validate cleanup safety\n\t */\n\tasync validateCleanupSafety(identifier: string): Promise<SafetyCheck> {\n\t\tconst warnings: string[] = []\n\t\tconst blockers: string[] = []\n\n\t\t// Find worktree\n\t\tconst worktrees = await this.gitWorktree.findWorktreesByIdentifier(identifier)\n\n\t\tif (worktrees.length === 0) {\n\t\t\tblockers.push(`No worktree found for: ${identifier}`)\n\t\t\treturn { isSafe: false, warnings, blockers }\n\t\t}\n\n\t\tconst worktree = worktrees[0]\n\t\tif (!worktree) {\n\t\t\tblockers.push(`No worktree found for: ${identifier}`)\n\t\t\treturn { isSafe: false, warnings, blockers }\n\t\t}\n\n\t\t// Delegate to private method that validates the worktree\n\t\treturn await this.validateWorktreeSafety(worktree, identifier)\n\t}\n\n\t/**\n\t * Parse identifier to determine type and extract number\n\t * Helper method for port calculation\n\t */\n\tprivate parseIdentifier(identifier: string): ParsedInput {\n\t\t// Check for issue pattern\n\t\tconst issueId = extractIssueNumber(identifier)\n\t\tif (issueId !== null) {\n\t\t\treturn {\n\t\t\t\ttype: 'issue',\n\t\t\t\tnumber: issueId,\n\t\t\t\toriginalInput: identifier\n\t\t\t}\n\t\t}\n\n\t\t// Check for PR pattern\n\t\tconst prMatch = identifier.match(/(?:pr|PR)[/-](\\d+)/)\n\t\tif (prMatch?.[1]) {\n\t\t\treturn {\n\t\t\t\ttype: 'pr',\n\t\t\t\tnumber: parseInt(prMatch[1], 10),\n\t\t\t\toriginalInput: identifier\n\t\t\t}\n\t\t}\n\n\t\t// Check for numeric identifier\n\t\tconst numericMatch = identifier.match(/^#?(\\d+)$/)\n\t\tif (numericMatch?.[1]) {\n\t\t\t// Assume issue for numeric identifiers\n\t\t\treturn {\n\t\t\t\ttype: 'issue',\n\t\t\t\tnumber: parseInt(numericMatch[1], 10),\n\t\t\t\toriginalInput: identifier\n\t\t\t}\n\t\t}\n\n\t\t// Treat as branch name\n\t\treturn {\n\t\t\ttype: 'branch',\n\t\t\tbranchName: identifier,\n\t\t\toriginalInput: identifier\n\t\t}\n\t}\n}\n"],"mappings":"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","names":["path","fs","fs","path","path","fs","path","fs","fs","logger","fs","fs","path","os","path","os","fs","fs","logger","path","path"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/MergeManager.ts"],"sourcesContent":["import { executeGitCommand, findMainWorktreePathWithSettings } from '../utils/git.js'\nimport { logger } from '../utils/logger.js'\nimport { detectClaudeCli, launchClaude } from '../utils/claude.js'\nimport { SettingsManager } from './SettingsManager.js'\nimport type { MergeOptions } 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\n\tconstructor(settingsManager?: SettingsManager) {\n\t\tthis.settingsManager = settingsManager ?? new SettingsManager()\n\t}\n\n\t/**\n\t * Get the main branch name from settings (defaults to 'main')\n\t * @param worktreePath - Optional path to load settings from (defaults to process.cwd())\n\t * @private\n\t */\n\tprivate async getMainBranch(worktreePath?: string): Promise<string> {\n\t\tconst settings = await this.settingsManager.loadSettings(worktreePath)\n\t\treturn settings.mainBranch ?? 'main'\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<void> {\n\t\tconst { dryRun = false, force = false } = options\n\t\tconst mainBranch = await this.getMainBranch(worktreePath)\n\n\t\tlogger.info(`Starting rebase on ${mainBranch} branch...`)\n\n\t\t// Step 1: Check if main branch exists\n\t\ttry {\n\t\t\tawait executeGitCommand(['show-ref', '--verify', '--quiet', `refs/heads/${mainBranch}`], {\n\t\t\t\tcwd: worktreePath,\n\t\t\t})\n\t\t} catch {\n\t\t\tthrow new Error(\n\t\t\t\t`Main branch \"${mainBranch}\" does not exist. Cannot rebase.\\n` +\n\t\t\t\t\t`Ensure the repository has a \"${mainBranch}\" branch or create it first.`\n\t\t\t)\n\t\t}\n\n\t\t// Step 2: Check for uncommitted changes (defensive check)\n\t\tconst statusOutput = await executeGitCommand(['status', '--porcelain'], {\n\t\t\tcwd: worktreePath,\n\t\t})\n\n\t\tif (statusOutput.trim()) {\n\t\t\tthrow new Error(\n\t\t\t\t'Uncommitted changes detected. Please commit or stash changes before rebasing.\\n' +\n\t\t\t\t\t'Run: git status to see uncommitted changes\\n' +\n\t\t\t\t\t'Or: il finish will automatically commit them for you'\n\t\t\t)\n\t\t}\n\n\t\t// Step 3: Check if rebase is needed by comparing merge-base with main HEAD\n\t\tconst mergeBase = await executeGitCommand(['merge-base', mainBranch, 'HEAD'], {\n\t\t\tcwd: worktreePath,\n\t\t})\n\n\t\tconst mainHead = await executeGitCommand(['rev-parse', mainBranch], {\n\t\t\tcwd: worktreePath,\n\t\t})\n\n\t\tconst mergeBaseTrimmed = mergeBase.trim()\n\t\tconst mainHeadTrimmed = mainHead.trim()\n\n\t\t// If merge-base matches main HEAD, branch is already up to date\n\t\tif (mergeBaseTrimmed === mainHeadTrimmed) {\n\t\t\tlogger.success(`Branch is already up to date with ${mainBranch}. No rebase needed.`)\n\t\t\treturn\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', `${mainBranch}..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\tlogger.info(`Found ${commitLines.length} commit(s) to rebase:`)\n\t\t\tcommitLines.forEach((commit) => logger.info(` ${commit}`))\n\t\t} else {\n\t\t\t// Main has moved forward but branch has no new commits\n\t\t\tlogger.info(`${mainBranch} branch 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\tlogger.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\tlogger.info(`[DRY RUN] Would execute: git rebase ${mainBranch}`)\n\t\t\tif (commitLines.length > 0) {\n\t\t\t\tlogger.info(`[DRY RUN] This would rebase ${commitLines.length} commit(s)`)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\t// Execute rebase\n\t\ttry {\n\t\t\tawait executeGitCommand(['rebase', mainBranch], { cwd: worktreePath })\n\t\t\tlogger.success('Rebase completed successfully!')\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\tlogger.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)\n\n\t\t\t\tif (resolved) {\n\t\t\t\t\tlogger.success('Conflicts resolved with Claude assistance, rebase completed')\n\t\t\t\t\treturn // Continue with successful rebase\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\tlogger.info('Starting fast-forward merge...')\n\n\t\t// Step 1: Find where main branch is checked out\n\t\t// This copies the bash script approach: find main worktree, run commands from there\n\t\tconst mainWorktreePath = options.repoRoot ??\n\t\t\tawait findMainWorktreePathWithSettings(worktreePath, this.settingsManager)\n\n\t\t// Load mainBranch from settings in the worktree being finished (not cwd)\n\t\t// This ensures we use the correct settings when finishing a child loom from parent\n\t\tconst mainBranch = await this.getMainBranch(worktreePath)\n\n\t\t// Step 3: No need to checkout main - it's already checked out in mainWorktreePath\n\t\tlogger.debug(`Using ${mainBranch} branch location: ${mainWorktreePath}`)\n\n\t\t// Step 4: Verify on main 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 is already merged\n\t\tif (!commits) {\n\t\t\tlogger.success(`Branch is already merged into ${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\tlogger.info(`Found ${commitLines.length} commit(s) to merge:`)\n\t\tcommitLines.forEach((commit) => logger.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\tlogger.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\tlogger.info(`[DRY RUN] Would execute: git merge --ff-only ${branchName}`)\n\t\t\tlogger.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\tlogger.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\tlogger.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 * 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): Promise<boolean> {\n\t\t// Check if Claude CLI is available\n\t\tconst isClaudeAvailable = await detectClaudeCli()\n\t\tif (!isClaudeAvailable) {\n\t\t\tlogger.debug('Claude CLI not available, skipping conflict resolution')\n\t\t\treturn false\n\t\t}\n\n\t\tlogger.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 prompt =\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`Handle the entire workflow for me.`\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\tawait launchClaude(prompt, {\n\t\t\t\taddDir: worktreePath,\n\t\t\t\theadless: false, // Interactive - runs in current terminal with stdio: inherit\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\tlogger.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\tlogger.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\tlogger.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\tconst rebaseMergePath = path.join(worktreePath, '.git', 'rebase-merge')\n\t\tconst rebaseApplyPath = path.join(worktreePath, '.git', '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"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/git.ts"],"sourcesContent":["import path from 'path'\nimport { execa, type ExecaError } from 'execa'\nimport { type GitWorktree } from '../types/worktree.js'\nimport { SettingsManager, type SettingsManager as SettingsManagerType } from '../lib/SettingsManager.js'\nimport { logger } from './logger.js'\n\n/**\n * Execute a Git command and return the stdout result\n * Throws an error if the command fails\n */\nexport async function executeGitCommand(\n args: string[],\n options?: { cwd?: string; timeout?: number; stdio?: 'inherit' | 'pipe' }\n): Promise<string> {\n try {\n const result = await execa('git', args, {\n cwd: options?.cwd ?? process.cwd(),\n timeout: options?.timeout ?? 30000,\n encoding: 'utf8',\n stdio: options?.stdio ?? 'pipe',\n verbose: logger.isDebugEnabled(),\n })\n\n return result.stdout\n } catch (error) {\n const execaError = error as ExecaError\n const stderr = execaError.stderr ?? execaError.message ?? 'Unknown Git error'\n throw new Error(`Git command failed: ${stderr}`)\n }\n}\n\n/**\n * Parse git worktree list output into structured data\n * @param output - The output from git worktree list --porcelain\n * @param defaultBranch - Default branch name to use for bare repositories (defaults to 'main')\n */\nexport function parseWorktreeList(output: string, defaultBranch?: string): GitWorktree[] {\n const worktrees: GitWorktree[] = []\n const lines = output.trim().split('\\n')\n\n let i = 0\n while (i < lines.length) {\n const pathLine = lines[i]\n if (!pathLine?.startsWith('worktree ')) {\n i++\n continue\n }\n\n // Parse path line: \"worktree /path/to/worktree\"\n const pathMatch = pathLine.match(/^worktree (.+)$/)\n if (!pathMatch) {\n i++\n continue\n }\n\n let branch = ''\n let commit = ''\n let detached = false\n let bare = false\n let locked = false\n let lockReason: string | undefined\n\n // Process subsequent lines for this worktree\n i++\n while (i < lines.length && !lines[i]?.startsWith('worktree ')) {\n const line = lines[i]?.trim()\n if (!line) {\n i++\n continue\n }\n\n if (line === 'bare') {\n bare = true\n branch = defaultBranch ?? 'main' // Default assumption for bare repo\n } else if (line === 'detached') {\n detached = true\n branch = 'HEAD'\n } else if (line.startsWith('locked')) {\n locked = true\n const lockMatch = line.match(/^locked (.+)$/)\n lockReason = lockMatch?.[1]\n branch = branch || 'unknown'\n } else if (line.startsWith('HEAD ')) {\n // Parse commit line: \"HEAD abc123def456...\"\n const commitMatch = line.match(/^HEAD ([a-f0-9]+)/)\n if (commitMatch) {\n commit = commitMatch[1] ?? ''\n }\n } else if (line.startsWith('branch ')) {\n // Parse branch line: \"branch refs/heads/feature-branch\"\n const branchMatch = line.match(/^branch refs\\/heads\\/(.+)$/)\n branch = branchMatch?.[1] ?? line.replace('branch ', '')\n }\n\n i++\n }\n\n const worktree: GitWorktree = {\n path: pathMatch[1] ?? '',\n branch,\n commit,\n bare,\n detached,\n locked,\n }\n\n if (lockReason !== undefined) {\n worktree.lockReason = lockReason\n }\n\n worktrees.push(worktree)\n }\n\n return worktrees\n}\n\n/**\n * Check if a branch name follows PR naming patterns\n */\nexport function isPRBranch(branchName: string): boolean {\n const prPatterns = [\n /^pr\\/\\d+/i, // pr/123, pr/123-feature-name\n /^pull\\/\\d+/i, // pull/123\n /^\\d+[-_]/, // 123-feature-name, 123_feature_name\n /^feature\\/pr[-_]?\\d+/i, // feature/pr123, feature/pr-123\n /^hotfix\\/pr[-_]?\\d+/i, // hotfix/pr123\n ]\n\n return prPatterns.some(pattern => pattern.test(branchName))\n}\n\n/**\n * Extract PR number from branch name\n */\nexport function extractPRNumber(branchName: string): number | null {\n const patterns = [\n /^pr\\/(\\d+)/i, // pr/123\n /^pull\\/(\\d+)/i, // pull/123\n /^(\\d+)[-_]/, // 123-feature-name\n /^feature\\/pr[-_]?(\\d+)/i, // feature/pr123\n /^hotfix\\/pr[-_]?(\\d+)/i, // hotfix/pr123\n /pr[-_]?(\\d+)/i, // anywhere with pr123 or pr-123\n ]\n\n for (const pattern of patterns) {\n const match = branchName.match(pattern)\n if (match?.[1]) {\n const num = parseInt(match[1], 10)\n if (!isNaN(num)) return num\n }\n }\n\n return null\n}\n\n/**\n * Extract issue number from branch name\n * Supports both new format (issue-{issueId}__{slug}) and old format (issue-{number}-{slug})\n * @returns string issue ID (alphanumeric) or null if not found\n */\nexport function extractIssueNumber(branchName: string): string | null {\n // Priority 1: New format - issue-{issueId}__ (alphanumeric ID with double underscore)\n const newFormatPattern = /issue-([^_]+)__/i\n const newMatch = branchName.match(newFormatPattern)\n if (newMatch?.[1]) return newMatch[1]\n\n // Priority 2: Old format - issue-{number}- or issue-{number}$ (numeric only, dash or end)\n const oldFormatPattern = /issue-(\\d+)(?:-|$)/i\n const oldMatch = branchName.match(oldFormatPattern)\n if (oldMatch?.[1]) return oldMatch[1]\n\n // Priority 3: Alphanumeric ID at end (either format without description)\n const alphanumericEndPattern = /issue-([^_\\s/]+)$/i\n const alphanumericMatch = branchName.match(alphanumericEndPattern)\n if (alphanumericMatch?.[1]) return alphanumericMatch[1]\n\n // Priority 4: Legacy patterns (issue_N, leading number)\n const legacyPatterns = [\n /issue_(\\d+)/i, // issue_42\n /^(\\d+)-/, // 42-feature-name\n ]\n for (const pattern of legacyPatterns) {\n const match = branchName.match(pattern)\n if (match?.[1]) return match[1]\n }\n\n return null\n}\n\n/**\n * Check if a path follows worktree naming patterns\n */\nexport function isWorktreePath(path: string): boolean {\n const worktreePatterns = [\n /\\/worktrees?\\//i, // Contains /worktree/ or /worktrees/\n /\\/workspace[-_]?\\d+/i, // workspace123, workspace-123\n /\\/issue[-_]?\\d+/i, // issue123, issue-123\n /\\/pr[-_]?\\d+/i, // pr123, pr-123\n /-worktree$/i, // ends with -worktree\n /\\.worktree$/i, // ends with .worktree\n ]\n\n return worktreePatterns.some(pattern => pattern.test(path))\n}\n\n/**\n * Generate a worktree path based on branch name and root directory\n * For PRs, adds _pr_<PR_NUM> suffix to distinguish from issue branches\n */\nexport function generateWorktreePath(\n branchName: string,\n rootDir: string = process.cwd(),\n options?: { isPR?: boolean; prNumber?: number; prefix?: string }\n): string {\n // Replace slashes with dashes (matches bash line 593)\n let sanitized = branchName.replace(/\\//g, '-')\n\n // Add PR suffix if this is a PR (matches bash lines 595-597)\n if (options?.isPR && options?.prNumber) {\n sanitized = `${sanitized}_pr_${options.prNumber}`\n }\n\n const parentDir = path.dirname(rootDir)\n\n // Handle prefix logic\n let prefix: string\n\n if (options?.prefix === undefined) {\n // No prefix in options - calculate default: <basename>-looms\n const mainFolderName = path.basename(rootDir)\n prefix = mainFolderName ? `${mainFolderName}-looms/` : 'looms/'\n } else if (options.prefix === '') {\n // Empty string = no prefix mode\n prefix = ''\n } else {\n // Custom prefix provided\n prefix = options.prefix\n\n // Check if prefix contains forward slashes (nested directory structure)\n const hasNestedPath = prefix.includes('/')\n\n if (hasNestedPath) {\n // Check if it ends with a separator character (dash, underscore, or slash)\n const endsWithSeparator = /[-_/]$/.test(prefix)\n\n if (!endsWithSeparator) {\n // Has nested path but no trailing separator: auto-append hyphen\n // Example: \"temp/looms\" becomes \"temp/looms-\"\n prefix = `${prefix}-`\n }\n // If it already ends with -, _, or /, keep as-is\n } else {\n // Single-level prefix: auto-append separator if it doesn't end with one\n const endsWithSeparator = /[-_]$/.test(prefix)\n if (!endsWithSeparator) {\n prefix = `${prefix}-`\n }\n }\n }\n\n // Apply prefix (or not, if empty)\n if (prefix === '') {\n return path.join(parentDir, sanitized)\n } else if (prefix.endsWith('/')) {\n // Forward slash = nested directory, use path.join for proper handling\n return path.join(parentDir, prefix, sanitized)\n } else if (prefix.includes('/')) {\n // Contains slash but doesn't end with slash = nested with separator (e.g., \"looms/myprefix-\")\n // Split and handle: last part is prefix with separator, rest is directory path\n const lastSlashIndex = prefix.lastIndexOf('/')\n const dirPath = prefix.substring(0, lastSlashIndex)\n const prefixWithSeparator = prefix.substring(lastSlashIndex + 1)\n return path.join(parentDir, dirPath, `${prefixWithSeparator}${sanitized}`)\n } else {\n // Dash/underscore separator = single directory name\n return path.join(parentDir, `${prefix}${sanitized}`)\n }\n}\n\n/**\n * Validate that a directory is a valid Git repository\n */\nexport async function isValidGitRepo(path: string): Promise<boolean> {\n try {\n await executeGitCommand(['rev-parse', '--git-dir'], { cwd: path })\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Get the current branch name for a repository\n */\nexport async function getCurrentBranch(path: string = process.cwd()): Promise<string | null> {\n try {\n const result = await executeGitCommand(['branch', '--show-current'], { cwd: path })\n return result.trim()\n } catch {\n return null\n }\n}\n\n/**\n * Check if a branch exists (local or remote)\n */\nexport async function branchExists(\n branchName: string,\n path: string = process.cwd(),\n includeRemote = true\n): Promise<boolean> {\n try {\n // Check local branches\n const localResult = await executeGitCommand(['branch', '--list', branchName], { cwd: path })\n if (localResult.trim()) {\n return true\n }\n\n // Check remote branches if requested\n if (includeRemote) {\n const remoteResult = await executeGitCommand(['branch', '-r', '--list', `*/${branchName}`], {\n cwd: path,\n })\n if (remoteResult.trim()) {\n return true\n }\n }\n\n return false\n } catch {\n return false\n }\n}\n\n/**\n * Get the root directory of the current worktree\n * Returns the worktree root when in a linked worktree, or main repo root when in main worktree\n */\nexport async function getWorktreeRoot(path: string = process.cwd()): Promise<string | null> {\n try {\n const result = await executeGitCommand(['rev-parse', '--show-toplevel'], { cwd: path })\n return result.trim()\n } catch {\n return null\n }\n}\n\n/**\n * Get the main repository root directory\n * Returns the main repo root even when called from a linked worktree\n */\nexport async function getRepoRoot(path: string = process.cwd()): Promise<string | null> {\n try {\n // Get the common git directory (shared by all worktrees)\n const gitCommonDir = await executeGitCommand(\n ['rev-parse', '--path-format=absolute', '--git-common-dir'],\n { cwd: path }\n )\n const trimmedPath = gitCommonDir.trim()\n\n // Handle linked worktree: /path/to/repo/.git/worktrees/worktree-name -> /path/to/repo\n // Handle main worktree: /path/to/repo/.git -> /path/to/repo\n const repoRoot = trimmedPath\n .replace(/\\/\\.git\\/worktrees\\/[^/]+$/, '') // Remove /.git/worktrees/name suffix\n .replace(/\\/\\.git$/, '') // Remove /.git suffix\n\n return repoRoot\n } catch(error) {\n logger.warn(`Failed to determine repo root from git-common-dir: ${path}`, error instanceof Error ? error.message : String(error))\n return null\n }\n}\n\n/**\n * Find the worktree path where main branch is checked out\n * Copies bash script approach: parse git worktree list to find main\n */\nexport async function findMainWorktreePath(\n path: string = process.cwd(),\n options?: { mainBranch?: string }\n): Promise<string> {\n try {\n const output = await executeGitCommand(['worktree', 'list', '--porcelain'], { cwd: path })\n const worktrees = parseWorktreeList(output, options?.mainBranch)\n\n // Guard: empty worktree list\n if (worktrees.length === 0) {\n throw new Error('No worktrees found in repository')\n }\n\n // Tier 1: Check for specified mainBranch in options\n if (options?.mainBranch) {\n const specified = worktrees.find(wt => wt.branch === options.mainBranch)\n if (!specified?.path) {\n throw new Error(\n `No worktree found with branch '${options.mainBranch}' (specified in settings). Available worktrees: ${worktrees.map(wt => `${wt.path} (${wt.branch})`).join(', ')}`\n )\n }\n return specified.path\n }\n\n // Tier 2: Look for \"main\" branch\n const mainBranch = worktrees.find(wt => wt.branch === 'main')\n if (mainBranch?.path) {\n return mainBranch.path\n }\n\n // Tier 3: Use first worktree (primary worktree)\n const firstWorktree = worktrees[0]\n if (!firstWorktree?.path) {\n throw new Error('Failed to determine primary worktree path')\n }\n return firstWorktree.path\n } catch (error) {\n if (\n error instanceof Error &&\n (error.message.includes('No worktree found with branch') ||\n error.message.includes('No worktrees found') ||\n error.message.includes('Failed to determine primary worktree'))\n ) {\n // Re-throw our specific errors\n throw error\n }\n throw new Error(`Failed to find main worktree: ${error instanceof Error ? error.message : String(error)}`)\n }\n}\n\n/**\n * Find main worktree path with automatic settings loading\n *\n * This is a convenience wrapper that:\n * 1. Loads project settings from .iloom/settings.json\n * 2. Extracts mainBranch configuration if present\n * 3. Calls findMainWorktreePath with appropriate options\n *\n * @param path - Path to search from (defaults to process.cwd())\n * @param settingsManager - Optional SettingsManager instance (for DI/testing)\n * @returns Path to main worktree\n * @throws Error if main worktree cannot be found\n */\nexport async function findMainWorktreePathWithSettings(\n path?: string,\n settingsManager?: SettingsManagerType\n): Promise<string> {\n // Lazy load SettingsManager to avoid circular dependencies\n settingsManager ??= new SettingsManager()\n\n const settings = await settingsManager.loadSettings(path)\n const findOptions = settings.mainBranch ? { mainBranch: settings.mainBranch } : undefined\n return findMainWorktreePath(path, findOptions)\n}\n\n/**\n * Check if there are uncommitted changes in a repository\n */\nexport async function hasUncommittedChanges(path: string = process.cwd()): Promise<boolean> {\n try {\n const result = await executeGitCommand(['status', '--porcelain'], { cwd: path })\n return result.trim().length > 0\n } catch {\n return false\n }\n}\n\n/**\n * Get the default branch name for a repository\n */\nexport async function getDefaultBranch(path: string = process.cwd()): Promise<string> {\n try {\n // Try to get from remote\n const remoteResult = await executeGitCommand(['symbolic-ref', 'refs/remotes/origin/HEAD'], {\n cwd: path,\n })\n const match = remoteResult.match(/refs\\/remotes\\/origin\\/(.+)/)\n if (match) return match[1] ?? 'main'\n\n // Fallback to common default branch names\n const commonDefaults = ['main', 'master', 'develop']\n for (const branch of commonDefaults) {\n if (await branchExists(branch, path)) {\n return branch\n }\n }\n\n return 'main' // Final fallback\n } catch {\n return 'main'\n }\n}\n\n/**\n * Find all branches related to a GitHub issue or PR number\n * Matches patterns like:\n * - Issue patterns: issue-25, issue/25, 25-feature, feat-25, feat/issue-25\n * - PR patterns: pr/25, pull/25, pr-25, feature/pr-25\n *\n * Based on bash cleanup-worktree.sh find_issue_branches() (lines 133-154)\n *\n * @param issueNumber - The issue or PR number to search for\n * @param path - Working directory to search from (defaults to process.cwd())\n * @param settingsManager - Optional SettingsManager instance (for DI/testing)\n */\nexport async function findAllBranchesForIssue(\n issueNumber: string | number,\n path: string = process.cwd(),\n settingsManager?: SettingsManagerType\n): Promise<string[]> {\n // Lazy load SettingsManager to avoid circular dependencies\n if (!settingsManager) {\n const { SettingsManager: SM } = await import('../lib/SettingsManager.js')\n settingsManager = new SM()\n }\n\n // Get protected branches list from centralized method\n const protectedBranches = await settingsManager.getProtectedBranches(path)\n\n // Get all branches (local and remote)\n const output = await executeGitCommand(['branch', '-a'], { cwd: path })\n\n const branches: string[] = []\n const lines = output.split('\\n').filter(Boolean)\n\n for (const line of lines) {\n // Skip remotes/origin/HEAD pointer\n if (line.includes('remotes/origin/HEAD')) {\n continue\n }\n\n // Clean the branch name:\n // 1. Remove git status markers (* + spaces at start)\n let cleanBranch = line.replace(/^[*+ ]+/, '')\n\n // 2. Remove 'origin/' prefix if present\n cleanBranch = cleanBranch.replace(/^origin\\//, '')\n\n // 3. Remove 'remotes/origin/' prefix if present\n cleanBranch = cleanBranch.replace(/^remotes\\/origin\\//, '')\n\n // 4. Trim any remaining whitespace\n cleanBranch = cleanBranch.trim()\n\n // Skip protected branches\n if (protectedBranches.includes(cleanBranch)) {\n continue\n }\n\n // Check if branch contains issue number with strict word boundary pattern\n // The issue number must NOT be:\n // - Part of a larger number (preceded or followed by a digit)\n // - After an unknown word (like \"tissue-25\")\n // The issue number CAN be:\n // - At start: \"25-feature\"\n // - After known prefix + separator: \"issue-25\", \"feat-25\", \"fix-25\", \"pr-25\"\n // - After just a separator with no prefix: test_25 (separator at start)\n\n // First check: not part of a larger number\n const notPartOfNumber = new RegExp(`(?<!\\\\d)${issueNumber}(?!\\\\d)`)\n if (!notPartOfNumber.test(cleanBranch)) {\n continue\n }\n\n // Second check: if preceded by letters, validate they're known issue-related prefixes\n // This prevents \"tissue-25\" but allows \"issue-25\", \"feat-25\", etc.\n const beforeNumber = cleanBranch.substring(0, cleanBranch.indexOf(String(issueNumber)))\n\n if (beforeNumber) {\n // Extract the last word (letters) before the number\n const lastWord = beforeNumber.match(/([a-zA-Z]+)[-_/\\s]*$/)\n if (lastWord?.[1]) {\n const word = lastWord[1].toLowerCase()\n // Known prefixes for issue-related branches\n const knownPrefixes = [\n 'issue', 'issues',\n 'feat', 'feature', 'features',\n 'fix', 'fixes', 'bugfix', 'hotfix',\n 'pr', 'pull',\n 'test', 'tests',\n 'chore',\n 'docs',\n 'refactor',\n 'perf',\n 'style',\n 'ci',\n 'build',\n 'revert'\n ]\n\n // If we found a word and it's NOT in the known list, skip this branch\n if (!knownPrefixes.includes(word)) {\n continue\n }\n }\n }\n\n // Passed all checks - add to results\n if (!branches.includes(cleanBranch)) {\n branches.push(cleanBranch)\n }\n }\n\n return branches\n}\n\n/**\n * Check if a repository is empty (has no commits yet)\n * @param path - Repository path to check (defaults to process.cwd())\n * @returns true if repository has no commits, false otherwise\n */\nexport async function isEmptyRepository(path: string = process.cwd()): Promise<boolean> {\n try {\n await executeGitCommand(['rev-parse', '--verify', 'HEAD'], { cwd: path })\n return false // HEAD exists, repo has commits\n } catch {\n return true // HEAD doesn't exist, repo is empty\n }\n}\n\n/**\n * Ensure repository has at least one commit\n * Creates an initial empty commit if repository is empty\n * @param path - Repository path (defaults to process.cwd())\n */\nexport async function ensureRepositoryHasCommits(path: string = process.cwd()): Promise<void> {\n const isEmpty = await isEmptyRepository(path)\n if (isEmpty) {\n await executeGitCommand(['commit', '--no-verify', '--allow-empty', '-m', 'Initial commit'], { cwd: path })\n }\n}\n\n/**\n * Push a branch to remote repository\n * Used for PR workflow to push changes to remote without merging locally\n *\n * @param branchName - The branch name to push\n * @param worktreePath - The worktree path where the branch is checked out\n * @param options - Push options\n * @throws Error if push fails\n */\nexport async function pushBranchToRemote(\n branchName: string,\n worktreePath: string,\n options?: { dryRun?: boolean }\n): Promise<void> {\n if (options?.dryRun) {\n // In dry-run mode, just log what would be done\n return\n }\n\n try {\n // Execute: git push origin <branch-name>\n // This matches the bash script behavior (merge-and-clean.sh line 359)\n await executeGitCommand(['push', 'origin', branchName], {\n cwd: worktreePath,\n timeout: 120000, // 120 second timeout for push operations\n })\n } catch (error) {\n // Provide helpful error message based on common push failures\n const errorMessage = error instanceof Error ? error.message : String(error)\n\n // Check for common error patterns\n if (errorMessage.includes('failed to push') || errorMessage.includes('rejected')) {\n throw new Error(\n `Failed to push changes to origin/${branchName}\\n\\n` +\n ` Possible causes:\\n` +\n ` • Remote branch was deleted\\n` +\n ` • Push was rejected (non-fast-forward)\\n` +\n ` • Network connectivity issues\\n\\n` +\n ` To retry: il finish --pr <number>\\n` +\n ` To force push: git push origin ${branchName} --force`\n )\n }\n\n if (errorMessage.includes('Could not resolve host') || errorMessage.includes('network')) {\n throw new Error(\n `Failed to push changes to origin/${branchName}: Network connectivity issues\\n\\n` +\n ` Check your internet connection and try again.`\n )\n }\n\n if (errorMessage.includes('No such remote')) {\n throw new Error(\n `Failed to push changes: Remote 'origin' not found\\n\\n` +\n ` Configure remote: git remote add origin <url>`\n )\n }\n\n // For other errors, re-throw with original message\n throw new Error(`Failed to push to remote: ${errorMessage}`)\n }\n}\n\n/**\n * Check if a file is tracked by git\n * Uses git ls-files to check if file is in the index\n * @param filePath - Absolute or relative path to the file\n * @param cwd - Working directory (defaults to process.cwd())\n * @returns true if file is tracked, false otherwise\n */\nexport async function isFileTrackedByGit(\n filePath: string,\n cwd: string = process.cwd()\n): Promise<boolean> {\n try {\n const result = await executeGitCommand(\n ['ls-files', '--error-unmatch', filePath],\n { cwd }\n )\n return result.trim().length > 0\n } catch (error) {\n // Only return false if it's the specific \"pathspec did not match\" error\n const errorMessage = error instanceof Error ? error.message : String(error)\n if (errorMessage.includes('pathspec') && errorMessage.includes('did not match')) {\n return false\n }\n // Re-throw other errors\n throw error\n }\n}\n\n/**\n * Check if a file is gitignored\n * Uses `git check-ignore` which handles nested gitignore files and global patterns\n *\n * @param filePath - Path to file to check (relative to repo root)\n * @param cwd - Working directory (defaults to process.cwd())\n * @returns true if file IS ignored, false if NOT ignored or on error\n */\nexport async function isFileGitignored(\n filePath: string,\n cwd: string = process.cwd()\n): Promise<boolean> {\n try {\n await executeGitCommand(['check-ignore', '-q', filePath], { cwd })\n return true // Exit 0 = file IS ignored\n } catch {\n return false // Exit 1 = NOT ignored (or error)\n }\n}\n"],"mappings":"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","names":["path"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/providers/NeonProvider.ts","../src/utils/neon-helpers.ts"],"sourcesContent":["import { execa, type ExecaError } from 'execa'\nimport type { DatabaseProvider } from '../../types/index.js'\nimport { createLogger } from '../../utils/logger.js'\nimport { promptConfirmation } from '../../utils/prompt.js'\n\nconst logger = createLogger({ prefix: '🗂️' })\n\ninterface NeonBranch {\n name: string\n id: string\n [key: string]: unknown\n}\n\nexport interface NeonConfig {\n projectId: string\n parentBranch: string\n}\n\n/**\n * Validate Neon configuration\n * Checks that required configuration values are present\n */\nexport function validateNeonConfig(config: {\n projectId?: string\n parentBranch?: string\n}): { valid: boolean; error?: string } {\n if (!config.projectId) {\n return {\n valid: false,\n error: 'Neon projectId is required. Configure in .iloom/settings.json under databaseProviders.neon',\n }\n }\n\n if (!config.parentBranch) {\n return {\n valid: false,\n error: 'Neon parentBranch is required. Configure in .iloom/settings.json under databaseProviders.neon',\n }\n }\n\n // Basic validation for project ID format (should start with appropriate prefix)\n if (!/^[a-zA-Z0-9-]+$/.test(config.projectId)) {\n return {\n valid: false,\n error: 'Neon projectId contains invalid characters',\n }\n }\n\n return { valid: true }\n}\n\n/**\n * Neon database provider implementation\n * Ports functionality from bash/utils/neon-utils.sh\n */\nexport class NeonProvider implements DatabaseProvider {\n private _isConfigured: boolean = false\n\n constructor(private config: NeonConfig) {\n logger.debug('NeonProvider initialized with config:', {\n projectId: config.projectId,\n parentBranch: config.parentBranch,\n hasProjectId: !!config.projectId,\n hasParentBranch: !!config.parentBranch,\n })\n\n // Validate config but don't throw - just mark as not configured\n // This allows the provider to be instantiated even when Neon is not being used\n const validation = validateNeonConfig(config)\n if (!validation.valid) {\n logger.debug(`NeonProvider not configured: ${validation.error}`)\n logger.debug('Neon database branching will not be used')\n this._isConfigured = false\n } else {\n this._isConfigured = true\n }\n }\n\n /**\n * Check if provider is properly configured\n * Returns true if projectId and parentBranch are valid in settings\n */\n isConfigured(): boolean {\n return this._isConfigured\n }\n\n /**\n * Execute a Neon CLI command and return stdout\n * Throws an error if the command fails\n *\n * @param args - Command arguments to pass to neon CLI\n * @param cwd - Optional working directory to run the command from (defaults to current directory)\n */\n private async executeNeonCommand(args: string[], cwd?: string): Promise<string> {\n // Check if provider is properly configured\n if (!this._isConfigured) {\n throw new Error('NeonProvider is not configured. Check databaseProviders.neon configuration in .iloom/settings.json')\n }\n\n // Log the exact command being executed for debugging\n const command = `neon ${args.join(' ')}`\n logger.debug(`Executing Neon CLI command: ${command}`)\n logger.debug(`Project ID being used: ${this.config.projectId}`)\n if (cwd) {\n logger.debug(`Working directory: ${cwd}`)\n }\n\n const result = await execa('neon', args, {\n timeout: 30000,\n encoding: 'utf8',\n stdio: 'pipe',\n ...(cwd && { cwd }),\n })\n return result.stdout\n }\n\n /**\n * Check if neon CLI is available\n * Ports: check_neon_cli() from bash/utils/neon-utils.sh:18-23\n */\n async isCliAvailable(): Promise<boolean> {\n try {\n await execa('command', ['-v', 'neon'], {\n timeout: 5000,\n shell: true,\n })\n return true\n } catch {\n return false\n }\n }\n\n /**\n * Check if user is authenticated with Neon CLI\n * Ports: check_neon_auth() from bash/utils/neon-utils.sh:25-36\n *\n * @param cwd - Optional working directory to run the command from (prevents issues with deleted directories)\n * @throws Error if authentication check fails for reasons other than not being authenticated\n */\n async isAuthenticated(cwd?: string): Promise<boolean> {\n const cliAvailable = await this.isCliAvailable()\n if (!cliAvailable) {\n return false\n }\n\n try {\n await execa('neon', ['me'], {\n timeout: 10000,\n stdio: 'pipe',\n ...(cwd && { cwd }),\n })\n return true\n } catch (error) {\n const execaError = error as ExecaError\n const stderr = execaError.stderr?.trim() ?? ''\n\n // Check for authentication failure patterns (should return false, not throw)\n const isAuthError =\n stderr.toLowerCase().includes('not authenticated') ||\n stderr.toLowerCase().includes('not logged in') ||\n stderr.toLowerCase().includes('authentication required') ||\n stderr.toLowerCase().includes('login required')\n\n if (isAuthError) {\n return false\n }\n\n // For any other error, let it bubble up\n throw error\n }\n }\n\n /**\n * Sanitize branch name for Neon (replace slashes with underscores)\n * Ports: sanitize_neon_branch_name() from bash/utils/neon-utils.sh:11-15\n */\n sanitizeBranchName(branchName: string): string {\n return branchName.replace(/\\//g, '_')\n }\n\n /**\n * Extract endpoint ID from Neon connection string\n * Pattern matches: ep-abc-123 or ep-abc-123-pooler\n * Returns: ep-abc-123 (without -pooler suffix)\n * Used by: get_neon_branch_name() from bash/utils/neon-utils.sh:294\n */\n private extractEndpointId(connectionString: string): string | null {\n // First, extract the full host part between @ and first dot\n // Examples:\n // @ep-abc123.us-east-1.neon.tech -> ep-abc123\n // @ep-abc123-pooler.us-east-1.neon.tech -> ep-abc123-pooler\n const hostMatch = connectionString.match(/@(ep-[a-z0-9-]+)\\./)\n if (!hostMatch?.[1]) {\n return null\n }\n\n const fullEndpoint = hostMatch[1]\n // Remove -pooler suffix if present\n return fullEndpoint.replace(/-pooler$/, '')\n }\n\n /**\n * List all branches in the Neon project\n * Ports: list_neon_branches() from bash/utils/neon-utils.sh:63-74\n *\n * @param cwd - Optional working directory to run commands from\n */\n async listBranches(cwd?: string): Promise<string[]> {\n const output = await this.executeNeonCommand([\n 'branches',\n 'list',\n '--project-id',\n this.config.projectId,\n '--output',\n 'json',\n ], cwd)\n\n const branches: NeonBranch[] = JSON.parse(output)\n return branches.map(branch => branch.name)\n }\n\n /**\n * Check if a branch exists\n * Ports: check_neon_branch_exists() from bash/utils/neon-utils.sh:38-61\n *\n * @param name - Branch name to check\n * @param cwd - Optional working directory to run commands from\n */\n async branchExists(name: string, cwd?: string): Promise<boolean> {\n const branches = await this.listBranches(cwd)\n return branches.includes(name)\n }\n\n /**\n * Get connection string for a specific branch\n * Ports: get_neon_connection_string() from bash/utils/neon-utils.sh:76-90\n *\n * @param branch - Branch name to get connection string for\n * @param cwd - Optional working directory to run commands from\n */\n async getConnectionString(branch: string, cwd?: string): Promise<string> {\n const connectionString = await this.executeNeonCommand([\n 'connection-string',\n '--branch',\n branch,\n '--project-id',\n this.config.projectId,\n ], cwd)\n return connectionString.trim()\n }\n\n /**\n * Find Vercel preview database branch\n * Checks for both patterns: preview/<branch> and preview_<sanitized-branch>\n * Ports: find_preview_database_branch() from bash/utils/neon-utils.sh:92-124\n *\n * @param branchName - Branch name to find preview for\n * @param cwd - Optional working directory to run commands from\n */\n async findPreviewBranch(branchName: string, cwd?: string): Promise<string | null> {\n // Check for exact preview branch match with slash pattern\n const slashPattern = `preview/${branchName}`\n if (await this.branchExists(slashPattern, cwd)) {\n logger.info(`Found Vercel preview database: ${slashPattern}`)\n return slashPattern\n }\n\n // Check for underscore pattern variation\n const sanitized = this.sanitizeBranchName(branchName)\n const underscorePattern = `preview_${sanitized}`\n if (await this.branchExists(underscorePattern, cwd)) {\n logger.info(`Found Vercel preview database: ${underscorePattern}`)\n return underscorePattern\n }\n\n return null\n }\n\n /**\n * Remove expiration date from a Neon branch\n * Used when parent branches need to have child branches created\n * Neon limitation: \"Branches with an expiration date cannot have child branches\"\n *\n * @param branchName - Name of the branch to remove expiration from\n * @param cwd - Optional working directory to run commands from\n */\n private async removeExpiration(branchName: string, cwd?: string): Promise<void> {\n logger.info(`Removing expiration date from branch: ${branchName}`)\n await this.executeNeonCommand([\n 'branches',\n 'set-expiration',\n branchName,\n '--project-id',\n this.config.projectId,\n ], cwd)\n logger.success(`Expiration date removed from ${branchName}`)\n }\n\n /**\n * Create a new database branch\n * ALWAYS checks for Vercel preview database first\n * Handles Neon limitation where parent branches with expiration dates cannot have children\n * Returns connection string for the branch\n * Ports: create_neon_database_branch() from bash/utils/neon-utils.sh:126-187\n *\n * @param name - Name for the new branch\n * @param fromBranch - Parent branch to create from (defaults to config.parentBranch)\n * @param cwd - Optional working directory to run commands from\n */\n async createBranch(name: string, fromBranch?: string, cwd?: string): Promise<string> {\n // Always check for existing Vercel preview database first (lines 149-158)\n const previewBranch = await this.findPreviewBranch(name, cwd)\n if (previewBranch) {\n const connectionString = await this.getConnectionString(previewBranch, cwd)\n logger.success(`Using existing Vercel preview database: ${previewBranch}`)\n return connectionString\n }\n\n // Sanitize branch name for Neon (replace slashes with underscores)\n const sanitizedName = this.sanitizeBranchName(name)\n const parentBranch = fromBranch ?? this.config.parentBranch\n\n logger.info('Creating Neon database branch...')\n logger.info(` Parent branch: ${parentBranch}`)\n logger.info(` New branch: ${sanitizedName}`)\n\n try {\n // Create the database branch\n await this.executeNeonCommand([\n 'branches',\n 'create',\n '--name',\n sanitizedName,\n '--parent',\n parentBranch,\n '--project-id',\n this.config.projectId,\n ], cwd)\n } catch (error) {\n // Check if error is about parent branch having an expiration date\n const errorMessage = error instanceof Error ? error.message : String(error)\n if (errorMessage.toLowerCase().includes('expiration') &&\n errorMessage.toLowerCase().includes('child')) {\n logger.warn('Parent branch has an expiration date - removing it to allow child branch creation')\n\n // Remove expiration from parent branch\n await this.removeExpiration(parentBranch, cwd)\n\n // Retry branch creation\n logger.info('Retrying database branch creation...')\n await this.executeNeonCommand([\n 'branches',\n 'create',\n '--name',\n sanitizedName,\n '--parent',\n parentBranch,\n '--project-id',\n this.config.projectId,\n ], cwd)\n } else {\n // Re-throw if it's a different error\n throw error\n }\n }\n\n logger.success('Database branch created successfully')\n\n // Get the connection string for the new branch\n logger.info('Getting connection string for new database branch...')\n const connectionString = await this.getConnectionString(sanitizedName, cwd)\n\n return connectionString\n }\n\n /**\n * Delete a database branch\n * Includes preview database protection with user confirmation\n * Ports: delete_neon_database_branch() from bash/utils/neon-utils.sh:204-259\n *\n * @param name - Name of the branch to delete\n * @param isPreview - Whether this is a preview database branch\n * @param cwd - Optional working directory to run commands from (prevents issues with deleted directories)\n */\n async deleteBranch(name: string, isPreview: boolean = false, cwd?: string): Promise<import('../../types/index.js').DatabaseDeletionResult> {\n // Sanitize branch name for Neon\n const sanitizedName = this.sanitizeBranchName(name)\n\n // For preview contexts, check for preview databases first\n if (isPreview) {\n const previewBranch = await this.findPreviewBranch(name, cwd)\n if (previewBranch) {\n logger.warn(`Found Vercel preview database: ${previewBranch}`)\n logger.warn('Preview databases are managed by Vercel and will be cleaned up automatically')\n logger.warn('Manual deletion may interfere with Vercel\\'s preview deployments')\n\n const confirmed = await promptConfirmation(\n 'Delete preview database anyway?',\n false\n )\n\n if (confirmed) {\n // User confirmed - delete preview branch\n try {\n logger.info(`Deleting Vercel preview database: ${previewBranch}`)\n await this.executeNeonCommand([\n 'branches',\n 'delete',\n previewBranch,\n '--project-id',\n this.config.projectId,\n ], cwd)\n logger.success('Preview database deleted successfully')\n return {\n success: true,\n deleted: true,\n notFound: false,\n branchName: previewBranch\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n logger.error(`Failed to delete preview database: ${errorMessage}`)\n return {\n success: false,\n deleted: false,\n notFound: false,\n error: errorMessage,\n branchName: previewBranch\n }\n }\n } else {\n // User declined deletion\n logger.info('Skipping preview database deletion')\n return {\n success: true,\n deleted: false,\n notFound: false,\n userDeclined: true,\n branchName: previewBranch\n }\n }\n }\n // If no preview database found, fall through to check regular branch\n }\n\n // Check for regular branch\n logger.info(`Checking for Neon database branch: ${sanitizedName}`)\n\n try {\n const exists = await this.branchExists(sanitizedName, cwd)\n\n if (!exists) {\n logger.info(`No database branch found for '${name}'`)\n return {\n success: true,\n deleted: false,\n notFound: true,\n branchName: sanitizedName\n }\n }\n\n // Branch exists - delete it\n logger.info(`Deleting Neon database branch: ${sanitizedName}`)\n await this.executeNeonCommand([\n 'branches',\n 'delete',\n sanitizedName,\n '--project-id',\n this.config.projectId,\n ], cwd)\n logger.success('Database branch deleted successfully')\n\n return {\n success: true,\n deleted: true,\n notFound: false,\n branchName: sanitizedName\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n logger.error(`Failed to delete database branch: ${errorMessage}`)\n return {\n success: false,\n deleted: false,\n notFound: false,\n error: errorMessage,\n branchName: sanitizedName\n }\n }\n }\n\n /**\n * Get branch name from endpoint ID (reverse lookup)\n * Searches all branches to find one with matching endpoint\n * Ports: get_neon_branch_name() from bash/utils/neon-utils.sh:262-308\n *\n * @param endpointId - Endpoint ID to search for\n * @param cwd - Optional working directory to run commands from\n */\n async getBranchNameFromEndpoint(endpointId: string, cwd?: string): Promise<string | null> {\n const branches = await this.listBranches(cwd)\n\n for (const branch of branches) {\n try {\n const connectionString = await this.getConnectionString(branch, cwd)\n const branchEndpointId = this.extractEndpointId(connectionString)\n\n if (branchEndpointId === endpointId) {\n return branch\n }\n } catch {\n // Skip branches that fail to get connection string\n continue\n }\n }\n\n return null\n }\n\n /**\n * Get branch name from a connection string (reverse lookup)\n * Extracts endpoint ID from connection string and finds matching branch\n *\n * @param connectionString - Neon connection string (e.g., postgres://...@ep-abc-123.region.neon.tech/...)\n * @param cwd - Optional working directory to run commands from\n */\n async getBranchNameFromConnectionString(connectionString: string, cwd?: string): Promise<string | null> {\n const endpointId = this.extractEndpointId(connectionString)\n if (!endpointId) {\n logger.debug('Could not extract endpoint ID from connection string')\n return null\n }\n return this.getBranchNameFromEndpoint(endpointId, cwd)\n }\n}\n","import { NeonProvider } from '../lib/providers/NeonProvider.js'\nimport type { IloomSettings } from '../lib/SettingsManager.js'\n\n/**\n * Create NeonProvider from settings configuration\n * Returns provider with isConfigured() = false if neon settings missing\n */\nexport function createNeonProviderFromSettings(settings: IloomSettings): NeonProvider {\n\tconst neonConfig = settings.databaseProviders?.neon\n\n\treturn new NeonProvider({\n\t\tprojectId: neonConfig?.projectId ?? '',\n\t\tparentBranch: neonConfig?.parentBranch ?? '',\n\t})\n}\n"],"mappings":"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","names":["connectionString"]}
|