@iloom/cli 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/README.md +234 -667
  2. package/dist/BranchNamingService-OMWKUYMM.js +13 -0
  3. package/dist/ClaudeContextManager-3VXA6UPR.js +13 -0
  4. package/dist/ClaudeService-6CPK43N4.js +12 -0
  5. package/dist/GitHubService-EBOETDIW.js +11 -0
  6. package/dist/{LoomLauncher-CTSWJL35.js → LoomLauncher-JF7JZMTZ.js} +63 -32
  7. package/dist/LoomLauncher-JF7JZMTZ.js.map +1 -0
  8. package/dist/ProjectCapabilityDetector-34LU7JJ4.js +9 -0
  9. package/dist/{PromptTemplateManager-WII75TKH.js → PromptTemplateManager-A52RUAMS.js} +2 -2
  10. package/dist/README.md +234 -667
  11. package/dist/{SettingsManager-XOYCLH3D.js → SettingsManager-ZCWJ56WP.js} +12 -4
  12. package/dist/SettingsMigrationManager-AGIIIPDQ.js +10 -0
  13. package/dist/agents/iloom-issue-analyze-and-plan.md +125 -35
  14. package/dist/agents/iloom-issue-analyzer.md +284 -32
  15. package/dist/agents/iloom-issue-complexity-evaluator.md +40 -21
  16. package/dist/agents/iloom-issue-enhancer.md +69 -48
  17. package/dist/agents/iloom-issue-implementer.md +36 -25
  18. package/dist/agents/iloom-issue-planner.md +35 -24
  19. package/dist/agents/iloom-issue-reviewer.md +62 -9
  20. package/dist/{chunk-SWCRXDZC.js → chunk-3RUPPQRG.js} +1 -18
  21. package/dist/chunk-3RUPPQRG.js.map +1 -0
  22. package/dist/{chunk-HBVFXN7R.js → chunk-4BGK7T6X.js} +26 -3
  23. package/dist/chunk-4BGK7T6X.js.map +1 -0
  24. package/dist/{chunk-6LEQW46Y.js → chunk-4E4LD3QR.js} +72 -2
  25. package/dist/{chunk-6LEQW46Y.js.map → chunk-4E4LD3QR.js.map} +1 -1
  26. package/dist/{chunk-CWR2SANQ.js → chunk-EBISESAP.js} +1 -1
  27. package/dist/{chunk-TS6DL67T.js → chunk-G2IEYOLQ.js} +11 -38
  28. package/dist/chunk-G2IEYOLQ.js.map +1 -0
  29. package/dist/chunk-HBYZH6GD.js +1989 -0
  30. package/dist/chunk-HBYZH6GD.js.map +1 -0
  31. package/dist/chunk-INW24J2W.js +55 -0
  32. package/dist/chunk-INW24J2W.js.map +1 -0
  33. package/dist/{chunk-ZMNQBJUI.js → chunk-IP7SMKIF.js} +61 -22
  34. package/dist/chunk-IP7SMKIF.js.map +1 -0
  35. package/dist/{chunk-4IV6W4U5.js → chunk-IXKLYTWO.js} +12 -12
  36. package/dist/chunk-IXKLYTWO.js.map +1 -0
  37. package/dist/{chunk-JNKJ7NJV.js → chunk-JKXJ7BGL.js} +6 -2
  38. package/dist/{chunk-JNKJ7NJV.js.map → chunk-JKXJ7BGL.js.map} +1 -1
  39. package/dist/{chunk-LAPY6NAE.js → chunk-JQFO7QQN.js} +68 -12
  40. package/dist/{chunk-LAPY6NAE.js.map → chunk-JQFO7QQN.js.map} +1 -1
  41. package/dist/{SettingsMigrationManager-MTQIMI54.js → chunk-KLBYVHPK.js} +3 -2
  42. package/dist/{chunk-USVVV3FP.js → chunk-MKWYLDFK.js} +5 -5
  43. package/dist/chunk-O5OH5MRX.js +396 -0
  44. package/dist/chunk-O5OH5MRX.js.map +1 -0
  45. package/dist/{chunk-DJUGYNQE.js → chunk-PA6Q6AWM.js} +16 -3
  46. package/dist/chunk-PA6Q6AWM.js.map +1 -0
  47. package/dist/chunk-RO26VS3W.js +444 -0
  48. package/dist/chunk-RO26VS3W.js.map +1 -0
  49. package/dist/{chunk-VETG35MF.js → chunk-TSKY3JI7.js} +3 -3
  50. package/dist/{chunk-VETG35MF.js.map → chunk-TSKY3JI7.js.map} +1 -1
  51. package/dist/{chunk-LHP6ROUM.js → chunk-U5QDY7ZD.js} +4 -16
  52. package/dist/chunk-U5QDY7ZD.js.map +1 -0
  53. package/dist/{chunk-SPYPLHMK.js → chunk-VU3QMIP2.js} +34 -2
  54. package/dist/chunk-VU3QMIP2.js.map +1 -0
  55. package/dist/{chunk-PVAVNJKS.js → chunk-WEN5C5DM.js} +10 -1
  56. package/dist/chunk-WEN5C5DM.js.map +1 -0
  57. package/dist/{chunk-2PLUQT6J.js → chunk-XPKDPZ5D.js} +2 -2
  58. package/dist/{chunk-RF2YI2XJ.js → chunk-ZBQVSHVT.js} +5 -5
  59. package/dist/chunk-ZBQVSHVT.js.map +1 -0
  60. package/dist/{chunk-GZP4UGGM.js → chunk-ZM3CFL5L.js} +2 -2
  61. package/dist/{chunk-BLCTGFZN.js → chunk-ZT3YZB4K.js} +3 -4
  62. package/dist/chunk-ZT3YZB4K.js.map +1 -0
  63. package/dist/{chunk-MFU53H6J.js → chunk-ZWFBBPJI.js} +6 -6
  64. package/dist/{chunk-MFU53H6J.js.map → chunk-ZWFBBPJI.js.map} +1 -1
  65. package/dist/{claude-ZIWDG4XG.js → claude-LUZ35IMK.js} +2 -2
  66. package/dist/{cleanup-FEIVZSIV.js → cleanup-3MONU4PU.js} +88 -27
  67. package/dist/cleanup-3MONU4PU.js.map +1 -0
  68. package/dist/cli.js +2511 -62
  69. package/dist/cli.js.map +1 -1
  70. package/dist/{contribute-EMZKCAC6.js → contribute-UWJAGIG7.js} +6 -6
  71. package/dist/{feedback-LFNMQBAZ.js → feedback-W3BXTGIM.js} +15 -14
  72. package/dist/{feedback-LFNMQBAZ.js.map → feedback-W3BXTGIM.js.map} +1 -1
  73. package/dist/{git-WC6HZLOT.js → git-34Z6QVDS.js} +4 -2
  74. package/dist/{ignite-MQWVJEAB.js → ignite-KVJEFXNO.js} +32 -27
  75. package/dist/ignite-KVJEFXNO.js.map +1 -0
  76. package/dist/index.d.ts +359 -45
  77. package/dist/index.js +1267 -503
  78. package/dist/index.js.map +1 -1
  79. package/dist/{init-GJDYN2IK.js → init-L55Q73H4.js} +104 -40
  80. package/dist/init-L55Q73H4.js.map +1 -0
  81. package/dist/mcp/issue-management-server.js +934 -0
  82. package/dist/mcp/issue-management-server.js.map +1 -0
  83. package/dist/{neon-helpers-ZVIRPKCI.js → neon-helpers-WPUACUVC.js} +3 -3
  84. package/dist/neon-helpers-WPUACUVC.js.map +1 -0
  85. package/dist/{open-NXSN7XOC.js → open-LNRZL3UU.js} +39 -36
  86. package/dist/open-LNRZL3UU.js.map +1 -0
  87. package/dist/{prompt-ANTQWHUF.js → prompt-7INJ7YRU.js} +4 -2
  88. package/dist/prompt-7INJ7YRU.js.map +1 -0
  89. package/dist/prompts/init-prompt.txt +541 -98
  90. package/dist/prompts/issue-prompt.txt +27 -27
  91. package/dist/{rebase-DUNFOJVS.js → rebase-C4WNCVGM.js} +6 -6
  92. package/dist/{remote-ZCXJVVNW.js → remote-VUNCQZ6J.js} +3 -2
  93. package/dist/remote-VUNCQZ6J.js.map +1 -0
  94. package/dist/{run-O7ZK7CKA.js → run-IOGNIOYN.js} +39 -36
  95. package/dist/run-IOGNIOYN.js.map +1 -0
  96. package/dist/schema/settings.schema.json +59 -3
  97. package/dist/{test-git-T76HOTIA.js → test-git-J7I5MFYH.js} +3 -3
  98. package/dist/{test-prefix-6HJUVQMH.js → test-prefix-ZCONBCBX.js} +3 -3
  99. package/dist/{test-webserver-M2I3EV4J.js → test-webserver-DAHONWCS.js} +4 -4
  100. package/dist/test-webserver-DAHONWCS.js.map +1 -0
  101. package/package.json +3 -2
  102. package/dist/ClaudeContextManager-LVCYRM6Q.js +0 -13
  103. package/dist/ClaudeService-WVTWB3DK.js +0 -12
  104. package/dist/GitHubService-7E2S5NNZ.js +0 -11
  105. package/dist/LoomLauncher-CTSWJL35.js.map +0 -1
  106. package/dist/add-issue-OBI325W7.js +0 -69
  107. package/dist/add-issue-OBI325W7.js.map +0 -1
  108. package/dist/chunk-4IV6W4U5.js.map +0 -1
  109. package/dist/chunk-BLCTGFZN.js.map +0 -1
  110. package/dist/chunk-CVLAZRNB.js +0 -54
  111. package/dist/chunk-CVLAZRNB.js.map +0 -1
  112. package/dist/chunk-DJUGYNQE.js.map +0 -1
  113. package/dist/chunk-H4E4THUZ.js +0 -55
  114. package/dist/chunk-H4E4THUZ.js.map +0 -1
  115. package/dist/chunk-H5LDRGVK.js +0 -642
  116. package/dist/chunk-H5LDRGVK.js.map +0 -1
  117. package/dist/chunk-HBVFXN7R.js.map +0 -1
  118. package/dist/chunk-LHP6ROUM.js.map +0 -1
  119. package/dist/chunk-PVAVNJKS.js.map +0 -1
  120. package/dist/chunk-RF2YI2XJ.js.map +0 -1
  121. package/dist/chunk-SPYPLHMK.js.map +0 -1
  122. package/dist/chunk-SWCRXDZC.js.map +0 -1
  123. package/dist/chunk-SYOSCMIT.js +0 -545
  124. package/dist/chunk-SYOSCMIT.js.map +0 -1
  125. package/dist/chunk-T3KEIB4D.js +0 -243
  126. package/dist/chunk-T3KEIB4D.js.map +0 -1
  127. package/dist/chunk-TS6DL67T.js.map +0 -1
  128. package/dist/chunk-ZMNQBJUI.js.map +0 -1
  129. package/dist/cleanup-FEIVZSIV.js.map +0 -1
  130. package/dist/enhance-MNA4ZGXW.js +0 -176
  131. package/dist/enhance-MNA4ZGXW.js.map +0 -1
  132. package/dist/finish-TX5CJICB.js +0 -1749
  133. package/dist/finish-TX5CJICB.js.map +0 -1
  134. package/dist/ignite-MQWVJEAB.js.map +0 -1
  135. package/dist/init-GJDYN2IK.js.map +0 -1
  136. package/dist/mcp/chunk-6SDFJ42P.js +0 -62
  137. package/dist/mcp/chunk-6SDFJ42P.js.map +0 -1
  138. package/dist/mcp/claude-NDFOCQQQ.js +0 -249
  139. package/dist/mcp/claude-NDFOCQQQ.js.map +0 -1
  140. package/dist/mcp/color-QS5BFCNN.js +0 -168
  141. package/dist/mcp/color-QS5BFCNN.js.map +0 -1
  142. package/dist/mcp/github-comment-server.js +0 -168
  143. package/dist/mcp/github-comment-server.js.map +0 -1
  144. package/dist/mcp/terminal-OMNRFWB3.js +0 -227
  145. package/dist/mcp/terminal-OMNRFWB3.js.map +0 -1
  146. package/dist/open-NXSN7XOC.js.map +0 -1
  147. package/dist/run-O7ZK7CKA.js.map +0 -1
  148. package/dist/start-73I5W7WW.js +0 -983
  149. package/dist/start-73I5W7WW.js.map +0 -1
  150. package/dist/test-webserver-M2I3EV4J.js.map +0 -1
  151. /package/dist/{ClaudeContextManager-LVCYRM6Q.js.map → BranchNamingService-OMWKUYMM.js.map} +0 -0
  152. /package/dist/{ClaudeService-WVTWB3DK.js.map → ClaudeContextManager-3VXA6UPR.js.map} +0 -0
  153. /package/dist/{GitHubService-7E2S5NNZ.js.map → ClaudeService-6CPK43N4.js.map} +0 -0
  154. /package/dist/{PromptTemplateManager-WII75TKH.js.map → GitHubService-EBOETDIW.js.map} +0 -0
  155. /package/dist/{SettingsManager-XOYCLH3D.js.map → ProjectCapabilityDetector-34LU7JJ4.js.map} +0 -0
  156. /package/dist/{claude-ZIWDG4XG.js.map → PromptTemplateManager-A52RUAMS.js.map} +0 -0
  157. /package/dist/{git-WC6HZLOT.js.map → SettingsManager-ZCWJ56WP.js.map} +0 -0
  158. /package/dist/{neon-helpers-ZVIRPKCI.js.map → SettingsMigrationManager-AGIIIPDQ.js.map} +0 -0
  159. /package/dist/{chunk-CWR2SANQ.js.map → chunk-EBISESAP.js.map} +0 -0
  160. /package/dist/{SettingsMigrationManager-MTQIMI54.js.map → chunk-KLBYVHPK.js.map} +0 -0
  161. /package/dist/{chunk-USVVV3FP.js.map → chunk-MKWYLDFK.js.map} +0 -0
  162. /package/dist/{chunk-2PLUQT6J.js.map → chunk-XPKDPZ5D.js.map} +0 -0
  163. /package/dist/{chunk-GZP4UGGM.js.map → chunk-ZM3CFL5L.js.map} +0 -0
  164. /package/dist/{prompt-ANTQWHUF.js.map → claude-LUZ35IMK.js.map} +0 -0
  165. /package/dist/{contribute-EMZKCAC6.js.map → contribute-UWJAGIG7.js.map} +0 -0
  166. /package/dist/{remote-ZCXJVVNW.js.map → git-34Z6QVDS.js.map} +0 -0
  167. /package/dist/{rebase-DUNFOJVS.js.map → rebase-C4WNCVGM.js.map} +0 -0
  168. /package/dist/{test-git-T76HOTIA.js.map → test-git-J7I5MFYH.js.map} +0 -0
  169. /package/dist/{test-prefix-6HJUVQMH.js.map → test-prefix-ZCONBCBX.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/EnvironmentManager.ts","../src/lib/CLIIsolationManager.ts","../src/lib/DatabaseManager.ts"],"sourcesContent":["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 * 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 const port = basePort + options.issueNumber\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\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?: 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)\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'\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. .env file contains the configured database URL variable\n */\n async shouldUseDatabaseBranching(envFilePath: 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 .env has the configured database URL variable\n const hasDatabaseUrl = await this.hasDatabaseUrlInEnv(envFilePath)\n if (!hasDatabaseUrl) {\n logger.debug(\n 'Skipping database branching: configured database URL variable not found in .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 envFilePath - Path to .env file for configuration checks\n * @param cwd - Optional working directory to run commands from\n */\n async createBranchIfConfigured(\n branchName: string,\n envFilePath: string,\n cwd?: string\n ): Promise<string | null> {\n // Guard condition: check if database branching should be used\n if (!(await this.shouldUseDatabaseBranching(envFilePath))) {\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 const connectionString = await this.provider.createBranch(branchName, undefined, 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 * Check if .env 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 .env\n */\n private async hasDatabaseUrlInEnv(envFilePath: string): Promise<boolean> {\n try {\n const envMap = await this.environment.readEnvFile(envFilePath)\n\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 configured variable first\n if (envMap.has(this.databaseUrlEnvVarName)) {\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 .env file`)\n throw new Error(\n `Configured database URL environment variable '${this.databaseUrlEnvVarName}' not found in .env file. ` +\n `Please add it to your .env file or update your iloom configuration.`\n )\n }\n\n // Fall back to DATABASE_URL when using default configuration\n const hasDefaultVar = envMap.has('DATABASE_URL')\n if (hasDefaultVar) {\n logger.debug('✅ Found fallback DATABASE_URL variable')\n } else {\n logger.debug('❌ No DATABASE_URL variable found in .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 .env')) {\n throw error\n }\n // Return false for file read errors\n return false\n }\n }\n}\n"],"mappings":"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","names":["logger","fs","fs","logger"]}
@@ -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 type { SettingsManager } 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 * 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 repository root directory\n */\nexport async function getRepoRoot(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 * 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?: SettingsManager\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 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: number,\n path: string = process.cwd(),\n settingsManager?: SettingsManager\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"],"mappings":"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","names":["path"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/ClaudeService.ts"],"sourcesContent":["import { detectClaudeCli, launchClaude, launchClaudeInNewTerminalWindow, ClaudeCliOptions, generateBranchName } from '../utils/claude.js'\nimport { PromptTemplateManager, TemplateVariables } from './PromptTemplateManager.js'\nimport { SettingsManager, IloomSettings } from './SettingsManager.js'\nimport { logger } from '../utils/logger.js'\n\nexport interface ClaudeWorkflowOptions {\n\ttype: 'issue' | 'pr' | 'regular'\n\tissueNumber?: number\n\tprNumber?: number\n\ttitle?: string\n\tworkspacePath: string\n\tport?: number\n\theadless?: boolean\n\tbranchName?: string\n\toneShot?: import('../types/index.js').OneShotMode\n\tsetArguments?: string[] // Raw --set arguments to forward\n\texecutablePath?: string // Executable path to use for spin command\n}\n\nexport class ClaudeService {\n\tprivate templateManager: PromptTemplateManager\n\tprivate settingsManager: SettingsManager\n\tprivate settings?: IloomSettings\n\n\tconstructor(templateManager?: PromptTemplateManager, settingsManager?: SettingsManager) {\n\t\tthis.templateManager = templateManager ?? new PromptTemplateManager()\n\t\tthis.settingsManager = settingsManager ?? new SettingsManager()\n\t}\n\n\t/**\n\t * Check if Claude CLI is available\n\t */\n\tasync isAvailable(): Promise<boolean> {\n\t\treturn detectClaudeCli()\n\t}\n\n\t/**\n\t * Get the appropriate model for a workflow type\n\t */\n\tprivate getModelForWorkflow(type: 'issue' | 'pr' | 'regular'): string | undefined {\n\t\t// Issue workflows use claude-sonnet-4-20250514\n\t\tif (type === 'issue') {\n\t\t\treturn 'claude-sonnet-4-20250514'\n\t\t}\n\t\t// For PR and regular workflows, use Claude's default model\n\t\treturn undefined\n\t}\n\n\t/**\n\t * Get the appropriate permission mode for a workflow type\n\t */\n\tprivate getPermissionModeForWorkflow(\n\t\ttype: 'issue' | 'pr' | 'regular'\n\t): ClaudeCliOptions['permissionMode'] {\n\t\t// Check settings for configured permission mode\n\t\tif (this.settings?.workflows) {\n\t\t\tconst workflowConfig =\n\t\t\t\ttype === 'issue'\n\t\t\t\t\t? this.settings.workflows.issue\n\t\t\t\t\t: type === 'pr'\n\t\t\t\t\t\t? this.settings.workflows.pr\n\t\t\t\t\t\t: this.settings.workflows.regular\n\n\t\t\tif (workflowConfig?.permissionMode) {\n\t\t\t\treturn workflowConfig.permissionMode\n\t\t\t}\n\t\t}\n\n\t\t// Fall back to current defaults\n\t\tif (type === 'issue') {\n\t\t\treturn 'acceptEdits'\n\t\t}\n\t\t// For PR and regular workflows, use default permissions\n\t\treturn 'default'\n\t}\n\n\t/**\n\t * Launch Claude for a specific workflow\n\t */\n\tasync launchForWorkflow(options: ClaudeWorkflowOptions): Promise<string | void> {\n\t\tconst { type, issueNumber, prNumber, title, workspacePath, port, headless = false, branchName, oneShot = 'default', setArguments, executablePath } = options\n\n\t\ttry {\n\t\t\t// Load settings if not already cached\n\t\t\t// Settings are pre-validated at CLI startup, so no error handling needed here\n\t\t\tthis.settings ??= await this.settingsManager.loadSettings()\n\n\t\t\t// Build template variables\n\t\t\tconst variables: TemplateVariables = {\n\t\t\t\tWORKSPACE_PATH: workspacePath,\n\t\t\t}\n\n\t\t\tif (issueNumber !== undefined) {\n\t\t\t\tvariables.ISSUE_NUMBER = issueNumber\n\t\t\t}\n\n\t\t\tif (prNumber !== undefined) {\n\t\t\t\tvariables.PR_NUMBER = prNumber\n\t\t\t}\n\n\t\t\tif (title !== undefined) {\n\t\t\t\tif (type === 'issue') {\n\t\t\t\t\tvariables.ISSUE_TITLE = title\n\t\t\t\t} else if (type === 'pr') {\n\t\t\t\t\tvariables.PR_TITLE = title\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (port !== undefined) {\n\t\t\t\tvariables.PORT = port\n\t\t\t}\n\n\t\t\t// Get the prompt from template manager\n\t\t\tconst prompt = await this.templateManager.getPrompt(type, variables)\n\n\t\t\t// Determine model and permission mode\n\t\t\tconst model = this.getModelForWorkflow(type)\n\t\t\tconst permissionMode = this.getPermissionModeForWorkflow(type)\n\n\t\t\t// Display warning if bypassPermissions mode is used\n\t\t\tif (permissionMode === 'bypassPermissions') {\n\t\t\t\tlogger.warn(\n\t\t\t\t\t'⚠️ WARNING: Using bypassPermissions mode - Claude will execute all tool calls without confirmation. ' +\n\t\t\t\t\t\t'This can be dangerous. Use with caution.'\n\t\t\t\t)\n\t\t\t}\n\n\t\t\t// Build Claude CLI options\n\t\t\tconst claudeOptions: ClaudeCliOptions = {\n\t\t\t\taddDir: workspacePath,\n\t\t\t\theadless,\n\t\t\t}\n\n\t\t\t// Add optional model if present\n\t\t\tif (model !== undefined) {\n\t\t\t\tclaudeOptions.model = model\n\t\t\t}\n\n\t\t\t// Add permission mode if not default\n\t\t\tif (permissionMode !== undefined && permissionMode !== 'default') {\n\t\t\t\tclaudeOptions.permissionMode = permissionMode\n\t\t\t}\n\n\t\t\t// Add optional branch name for terminal coloring\n\t\t\tif (branchName !== undefined) {\n\t\t\t\tclaudeOptions.branchName = branchName\n\t\t\t}\n\n\t\t\t// Add optional port for terminal window export\n\t\t\tif (port !== undefined) {\n\t\t\t\tclaudeOptions.port = port\n\t\t\t}\n\n\t\t\t// Add optional setArguments for forwarding\n\t\t\tif (setArguments !== undefined) {\n\t\t\t\tclaudeOptions.setArguments = setArguments\n\t\t\t}\n\n\t\t\t// Add optional executablePath for spin command\n\t\t\tif (executablePath !== undefined) {\n\t\t\t\tclaudeOptions.executablePath = executablePath\n\t\t\t}\n\n\t\t\tlogger.debug('Launching Claude for workflow', {\n\t\t\t\ttype,\n\t\t\t\tmodel,\n\t\t\t\tpermissionMode,\n\t\t\t\theadless,\n\t\t\t\tworkspacePath,\n\t\t\t})\n\n\t\t\t// Launch Claude\n\t\t\tif (headless) {\n\t\t\t\t// Headless mode: use simple launchClaude\n\t\t\t\treturn await launchClaude(prompt, claudeOptions)\n\t\t\t} else {\n\t\t\t\t// Interactive workflow mode: use terminal window launcher\n\t\t\t\t// This is the \"end of il start\" behavior\n\t\t\t\tif (!claudeOptions.addDir) {\n\t\t\t\t\tthrow new Error('workspacePath required for interactive workflow launch')\n\t\t\t\t}\n\n\t\t\t\treturn await launchClaudeInNewTerminalWindow(prompt, {\n\t\t\t\t\t...claudeOptions,\n\t\t\t\t\tworkspacePath: claudeOptions.addDir,\n\t\t\t\t\toneShot,\n\t\t\t\t})\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tlogger.error('Failed to launch Claude for workflow', { error, options })\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t/**\n\t * Generate branch name with Claude, with fallback on failure\n\t */\n\tasync generateBranchNameWithFallback(issueTitle: string, issueNumber: number): Promise<string> {\n\t\ttry {\n\t\t\treturn await generateBranchName(issueTitle, issueNumber)\n\t\t} catch (error) {\n\t\t\tlogger.warn('Claude branch name generation failed, using fallback', { error })\n\t\t\treturn `feat/issue-${issueNumber}`\n\t\t}\n\t}\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/PromptTemplateManager.ts"],"sourcesContent":["import { readFile } from 'fs/promises'\nimport { accessSync } from 'fs'\nimport path from 'path'\nimport { fileURLToPath } from 'url'\nimport { logger } from '../utils/logger.js'\n\nexport interface TemplateVariables {\n\tISSUE_NUMBER?: number\n\tPR_NUMBER?: number\n\tISSUE_TITLE?: string\n\tPR_TITLE?: string\n\tWORKSPACE_PATH?: string\n\tPORT?: number\n\tONE_SHOT_MODE?: boolean\n\tSETTINGS_SCHEMA?: string\n\tSETTINGS_JSON?: string\n\tSETTINGS_LOCAL_JSON?: string\n\tSHELL_TYPE?: string\n\tSHELL_CONFIG_PATH?: string\n\tSHELL_CONFIG_CONTENT?: string\n\tREMOTES_INFO?: string\n\tMULTIPLE_REMOTES?: string\n\tSINGLE_REMOTE?: string\n\tSINGLE_REMOTE_NAME?: string\n\tSINGLE_REMOTE_URL?: string\n\tNO_REMOTES?: string\n\tREADME_CONTENT?: string\n\tSETTINGS_SCHEMA_CONTENT?: string\n\tFIRST_TIME_USER?: boolean\n}\n\nexport class PromptTemplateManager {\n\tprivate templateDir: string\n\n\tconstructor(templateDir?: string) {\n\t\tif (templateDir) {\n\t\t\tthis.templateDir = templateDir\n\t\t} else {\n\t\t\t// Find templates relative to the package installation\n\t\t\t// When running from dist/, templates are copied to dist/prompts/\n\t\t\tconst currentFileUrl = import.meta.url\n\t\t\tconst currentFilePath = fileURLToPath(currentFileUrl)\n\t\t\tconst distDir = path.dirname(currentFilePath) // dist directory (may be chunked file location)\n\n\t\t\t// Walk up to find the dist directory (in case of chunked files)\n\t\t\tlet templateDir = path.join(distDir, 'prompts')\n\t\t\tlet currentDir = distDir\n\n\t\t\t// Try to find the prompts directory by walking up\n\t\t\twhile (currentDir !== path.dirname(currentDir)) {\n\t\t\t\tconst candidatePath = path.join(currentDir, 'prompts')\n\t\t\t\ttry {\n\t\t\t\t\t// Check if this directory exists (sync check for constructor)\n\t\t\t\t\taccessSync(candidatePath)\n\t\t\t\t\ttemplateDir = candidatePath\n\t\t\t\t\tbreak\n\t\t\t\t} catch {\n\t\t\t\t\tcurrentDir = path.dirname(currentDir)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.templateDir = templateDir\n\t\t\tlogger.debug('PromptTemplateManager initialized', {\n\t\t\t\tcurrentFilePath,\n\t\t\t\tdistDir,\n\t\t\t\ttemplateDir: this.templateDir\n\t\t\t})\n\t\t}\n\t}\n\n\t/**\n\t * Load a template file by name\n\t */\n\tasync loadTemplate(templateName: 'issue' | 'pr' | 'regular' | 'init'): Promise<string> {\n\t\tconst templatePath = path.join(this.templateDir, `${templateName}-prompt.txt`)\n\n\t\tlogger.debug('Loading template', {\n\t\t\ttemplateName,\n\t\t\ttemplateDir: this.templateDir,\n\t\t\ttemplatePath\n\t\t})\n\n\t\ttry {\n\t\t\treturn await readFile(templatePath, 'utf-8')\n\t\t} catch (error) {\n\t\t\tlogger.error('Failed to load template', { templateName, templatePath, error })\n\t\t\tthrow new Error(`Template not found: ${templatePath}`)\n\t\t}\n\t}\n\n\t/**\n\t * Substitute variables in a template string\n\t */\n\tsubstituteVariables(template: string, variables: TemplateVariables): string {\n\t\tlet result = template\n\n\t\t// Process conditional sections first\n\t\tresult = this.processConditionalSections(result, variables)\n\n\t\t// Replace each variable if it exists\n\t\tif (variables.ISSUE_NUMBER !== undefined) {\n\t\t\tresult = result.replace(/ISSUE_NUMBER/g, String(variables.ISSUE_NUMBER))\n\t\t}\n\n\t\tif (variables.PR_NUMBER !== undefined) {\n\t\t\tresult = result.replace(/PR_NUMBER/g, String(variables.PR_NUMBER))\n\t\t}\n\n\t\tif (variables.ISSUE_TITLE !== undefined) {\n\t\t\tresult = result.replace(/ISSUE_TITLE/g, variables.ISSUE_TITLE)\n\t\t}\n\n\t\tif (variables.PR_TITLE !== undefined) {\n\t\t\tresult = result.replace(/PR_TITLE/g, variables.PR_TITLE)\n\t\t}\n\n\t\tif (variables.WORKSPACE_PATH !== undefined) {\n\t\t\tresult = result.replace(/WORKSPACE_PATH/g, variables.WORKSPACE_PATH)\n\t\t}\n\n\t\tif (variables.PORT !== undefined) {\n\t\t\tresult = result.replace(/PORT/g, String(variables.PORT))\n\t\t}\n\n\t\tif (variables.SETTINGS_SCHEMA !== undefined) {\n\t\t\tresult = result.replace(/SETTINGS_SCHEMA/g, variables.SETTINGS_SCHEMA)\n\t\t}\n\n\t\tif (variables.SETTINGS_JSON !== undefined) {\n\t\t\tresult = result.replace(/SETTINGS_JSON/g, variables.SETTINGS_JSON)\n\t\t}\n\n\t\tif (variables.SETTINGS_LOCAL_JSON !== undefined) {\n\t\t\tresult = result.replace(/SETTINGS_LOCAL_JSON/g, variables.SETTINGS_LOCAL_JSON)\n\t\t}\n\n\t\tif (variables.SHELL_TYPE !== undefined) {\n\t\t\tresult = result.replace(/SHELL_TYPE/g, variables.SHELL_TYPE)\n\t\t}\n\n\t\tif (variables.SHELL_CONFIG_PATH !== undefined) {\n\t\t\tresult = result.replace(/SHELL_CONFIG_PATH/g, variables.SHELL_CONFIG_PATH)\n\t\t}\n\n\t\tif (variables.SHELL_CONFIG_CONTENT !== undefined) {\n\t\t\tresult = result.replace(/SHELL_CONFIG_CONTENT/g, variables.SHELL_CONFIG_CONTENT)\n\t\t}\n\n\t\tif (variables.REMOTES_INFO !== undefined) {\n\t\t\tresult = result.replace(/REMOTES_INFO/g, variables.REMOTES_INFO)\n\t\t}\n\n\t\tif (variables.MULTIPLE_REMOTES !== undefined) {\n\t\t\tresult = result.replace(/MULTIPLE_REMOTES/g, variables.MULTIPLE_REMOTES)\n\t\t}\n\n\t\tif (variables.SINGLE_REMOTE !== undefined) {\n\t\t\tresult = result.replace(/SINGLE_REMOTE/g, variables.SINGLE_REMOTE)\n\t\t}\n\n\t\tif (variables.SINGLE_REMOTE_NAME !== undefined) {\n\t\t\tresult = result.replace(/SINGLE_REMOTE_NAME/g, variables.SINGLE_REMOTE_NAME)\n\t\t}\n\n\t\tif (variables.SINGLE_REMOTE_URL !== undefined) {\n\t\t\tresult = result.replace(/SINGLE_REMOTE_URL/g, variables.SINGLE_REMOTE_URL)\n\t\t}\n\n\t\tif (variables.NO_REMOTES !== undefined) {\n\t\t\tresult = result.replace(/NO_REMOTES/g, variables.NO_REMOTES)\n\t\t}\n\n\t\tif (variables.README_CONTENT !== undefined) {\n\t\t\tresult = result.replace(/README_CONTENT/g, variables.README_CONTENT)\n\t\t}\n\n\t\tif (variables.SETTINGS_SCHEMA_CONTENT !== undefined) {\n\t\t\tresult = result.replace(/SETTINGS_SCHEMA_CONTENT/g, variables.SETTINGS_SCHEMA_CONTENT)\n\t\t}\n\n\t\treturn result\n\t}\n\n\t/**\n\t * Process conditional sections in template\n\t * Format: {{#IF ONE_SHOT_MODE}}content{{/IF ONE_SHOT_MODE}}\n\t *\n\t * Note: /s flag allows . to match newlines\n\t */\n\tprivate processConditionalSections(template: string, variables: TemplateVariables): string {\n\t\tlet result = template\n\n\t\t// Process ONE_SHOT_MODE conditionals\n\t\tconst oneShotRegex = /\\{\\{#IF ONE_SHOT_MODE\\}\\}(.*?)\\{\\{\\/IF ONE_SHOT_MODE\\}\\}/gs\n\n\t\tif (variables.ONE_SHOT_MODE === true) {\n\t\t\t// Include the content, remove the conditional markers\n\t\t\tresult = result.replace(oneShotRegex, '$1')\n\t\t} else {\n\t\t\t// Remove the entire conditional block\n\t\t\tresult = result.replace(oneShotRegex, '')\n\t\t}\n\n\t\t// Process SETTINGS_JSON conditionals\n\t\tconst settingsJsonRegex = /\\{\\{#IF SETTINGS_JSON\\}\\}(.*?)\\{\\{\\/IF SETTINGS_JSON\\}\\}/gs\n\n\t\tif (variables.SETTINGS_JSON !== undefined && variables.SETTINGS_JSON !== '') {\n\t\t\t// Include the content, remove the conditional markers\n\t\t\tresult = result.replace(settingsJsonRegex, '$1')\n\t\t} else {\n\t\t\t// Remove the entire conditional block\n\t\t\tresult = result.replace(settingsJsonRegex, '')\n\t\t}\n\n\t\t// Process SETTINGS_LOCAL_JSON conditionals\n\t\tconst settingsLocalJsonRegex = /\\{\\{#IF SETTINGS_LOCAL_JSON\\}\\}(.*?)\\{\\{\\/IF SETTINGS_LOCAL_JSON\\}\\}/gs\n\n\t\tif (variables.SETTINGS_LOCAL_JSON !== undefined && variables.SETTINGS_LOCAL_JSON !== '') {\n\t\t\t// Include the content, remove the conditional markers\n\t\t\tresult = result.replace(settingsLocalJsonRegex, '$1')\n\t\t} else {\n\t\t\t// Remove the entire conditional block\n\t\t\tresult = result.replace(settingsLocalJsonRegex, '')\n\t\t}\n\n\t\t// Process MULTIPLE_REMOTES conditionals\n\t\tconst multipleRemotesRegex = /\\{\\{#IF MULTIPLE_REMOTES\\}\\}(.*?)\\{\\{\\/IF MULTIPLE_REMOTES\\}\\}/gs\n\n\t\tif (variables.MULTIPLE_REMOTES !== undefined && variables.MULTIPLE_REMOTES !== '') {\n\t\t\t// Include the content, remove the conditional markers\n\t\t\tresult = result.replace(multipleRemotesRegex, '$1')\n\t\t} else {\n\t\t\t// Remove the entire conditional block\n\t\t\tresult = result.replace(multipleRemotesRegex, '')\n\t\t}\n\n\t\t// Process SINGLE_REMOTE conditionals\n\t\tconst singleRemoteRegex = /\\{\\{#IF SINGLE_REMOTE\\}\\}(.*?)\\{\\{\\/IF SINGLE_REMOTE\\}\\}/gs\n\n\t\tif (variables.SINGLE_REMOTE !== undefined && variables.SINGLE_REMOTE !== '') {\n\t\t\t// Include the content, remove the conditional markers\n\t\t\tresult = result.replace(singleRemoteRegex, '$1')\n\t\t} else {\n\t\t\t// Remove the entire conditional block\n\t\t\tresult = result.replace(singleRemoteRegex, '')\n\t\t}\n\n\t\t// Process NO_REMOTES conditionals\n\t\tconst noRemotesRegex = /\\{\\{#IF NO_REMOTES\\}\\}(.*?)\\{\\{\\/IF NO_REMOTES\\}\\}/gs\n\n\t\tif (variables.NO_REMOTES !== undefined && variables.NO_REMOTES !== '') {\n\t\t\t// Include the content, remove the conditional markers\n\t\t\tresult = result.replace(noRemotesRegex, '$1')\n\t\t} else {\n\t\t\t// Remove the entire conditional block\n\t\t\tresult = result.replace(noRemotesRegex, '')\n\t\t}\n\n\t\t// Process FIRST_TIME_USER conditionals\n\t\tconst firstTimeUserRegex = /\\{\\{#IF FIRST_TIME_USER\\}\\}(.*?)\\{\\{\\/IF FIRST_TIME_USER\\}\\}/gs\n\n\t\tif (variables.FIRST_TIME_USER === true) {\n\t\t\t// Include the content, remove the conditional markers\n\t\t\tresult = result.replace(firstTimeUserRegex, '$1')\n\t\t} else {\n\t\t\t// Remove the entire conditional block\n\t\t\tresult = result.replace(firstTimeUserRegex, '')\n\t\t}\n\n\t\treturn result\n\t}\n\n\t/**\n\t * Get a fully processed prompt for a workflow type\n\t */\n\tasync getPrompt(\n\t\ttype: 'issue' | 'pr' | 'regular' | 'init',\n\t\tvariables: TemplateVariables\n\t): Promise<string> {\n\t\tconst template = await this.loadTemplate(type)\n\t\treturn this.substituteVariables(template, variables)\n\t}\n}\n"],"mappings":"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","names":["templateDir"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/IssueEnhancementService.ts"],"sourcesContent":["import type { GitHubService } from './GitHubService.js'\nimport type { AgentManager } from './AgentManager.js'\nimport type { SettingsManager } from './SettingsManager.js'\nimport { launchClaude } from '../utils/claude.js'\nimport { openBrowser } from '../utils/browser.js'\nimport { waitForKeypress } from '../utils/prompt.js'\nimport { logger } from '../utils/logger.js'\n\n/**\n * Service for enhancing and creating GitHub issues with AI assistance.\n * Extracts reusable issue enhancement logic from StartCommand.\n */\nexport class IssueEnhancementService {\n\tconstructor(\n\t\tprivate gitHubService: GitHubService,\n\t\tprivate agentManager: AgentManager,\n\t\tprivate settingsManager: SettingsManager\n\t) {}\n\n\t/**\n\t * Validates that a description meets minimum requirements.\n\t * Requirements: >30 characters AND >2 spaces\n\t */\n\tpublic validateDescription(description: string): boolean {\n\t\tconst trimmedDescription = description.trim()\n\t\tconst spaceCount = (trimmedDescription.match(/ /g) ?? []).length\n\n\t\treturn trimmedDescription.length > 30 && spaceCount > 2\n\t}\n\n\t/**\n\t * Enhances a description using Claude AI in headless mode.\n\t * Falls back to original description if enhancement fails.\n\t */\n\tpublic async enhanceDescription(description: string): Promise<string> {\n\t\ttry {\n\t\t\tlogger.info('Enhancing description with Claude AI. This may take a moment...')\n\n\t\t\t// Load agent configurations\n\t\t\tconst settings = await this.settingsManager.loadSettings()\n\t\t\tconst loadedAgents = await this.agentManager.loadAgents(settings)\n\t\t\tconst agents = this.agentManager.formatForCli(loadedAgents)\n\n\t\t\t// Call Claude in headless mode with issue enhancer agent\n\t\t\tconst prompt = `@agent-iloom-issue-enhancer\n\nTASK: Enhance the following issue description for GitHub.\n\nINPUT:\n${description}\n\nOUTPUT REQUIREMENTS:\n- Return ONLY the enhanced description markdown text\n- NO meta-commentary (no \"Here is...\", \"The enhanced...\", \"I have...\", etc)\n- NO code block markers (\\`\\`\\`)\n- NO conversational framing or acknowledgments\n- NO explanations of your work\n- Start your response immediately with the enhanced content\n\nYour response should be the raw markdown that will become the GitHub issue body.`\n\n\t\t\tconst enhanced = await launchClaude(prompt, {\n\t\t\t\theadless: true,\n\t\t\t\tmodel: 'sonnet',\n\t\t\t\tagents,\n\t\t\t})\n\n\t\t\tif (enhanced && typeof enhanced === 'string') {\n\t\t\t\tlogger.success('Description enhanced successfully')\n\t\t\t\treturn enhanced\n\t\t\t}\n\n\t\t\t// Fallback to original description\n\t\t\tlogger.warn('Claude enhancement returned empty result, using original description')\n\t\t\treturn description\n\t\t} catch (error) {\n\t\t\tlogger.warn(`Failed to enhance description: ${error instanceof Error ? error.message : 'Unknown error'}`)\n\t\t\treturn description\n\t\t}\n\t}\n\n\t/**\n\t * Creates a GitHub issue with title and enhanced body.\n\t * @param originalDescription - Used as the issue title\n\t * @param enhancedDescription - Used as the issue body\n\t * @param repository - Optional repository override (format: \"owner/repo\")\n\t * @param labels - Optional array of label names to add to the issue\n\t * @returns Issue number and URL\n\t */\n\tpublic async createEnhancedIssue(\n\t\toriginalDescription: string,\n\t\tenhancedDescription: string,\n\t\trepository?: string,\n\t\tlabels?: string[]\n\t): Promise<{ number: number; url: string }> {\n\t\tlogger.info('Creating GitHub issue from description...')\n\n\t\tconst result = await this.gitHubService.createIssue(\n\t\t\toriginalDescription, // Use original description as title\n\t\t\tenhancedDescription, // Use enhanced description as body\n\t\t\trepository,\n\t\t\tlabels\n\t\t)\n\n\t\treturn result\n\t}\n\n\t/**\n\t * Waits for user keypress and opens issue in browser for review.\n\t * @param issueNumber - Issue number to open for review\n\t * @param confirm - If true, wait for additional keypress after opening browser before returning\n\t * @param repository - Optional repository to fetch issue from (format: \"owner/repo\")\n\t */\n\tpublic async waitForReviewAndOpen(issueNumber: number, confirm = false, repository?: string): Promise<void> {\n\t\t// Check if running in CI environment\n\t\tconst isCI = process.env.CI === 'true'\n\n\t\tif (isCI) {\n\t\t\t// In CI: Skip all interactive operations\n\t\t\tlogger.info(`Running in CI environment - skipping interactive prompts for issue #${issueNumber}`)\n\t\t\treturn\n\t\t}\n\n\t\t// Get issue URL\n\t\tconst issueUrl = await this.gitHubService.getIssueUrl(issueNumber, repository)\n\n\t\t// Display message and wait for first keypress\n\t\tconst message = `Created issue #${issueNumber}.\nReview and edit the issue in your browser if needed.\nPress any key to open issue for editing...`\n\t\tawait waitForKeypress(message)\n\n\t\t// Open issue in browser\n\t\tawait openBrowser(issueUrl)\n\n\t\t// If confirmation required, wait for second keypress\n\t\tif (confirm) {\n\t\t\tawait waitForKeypress('Press any key to continue with loom creation...')\n\t\t}\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;AAYO,IAAM,0BAAN,MAA8B;AAAA,EACpC,YACS,eACA,cACA,iBACP;AAHO;AACA;AACA;AAAA,EACN;AAAA;AAAA;AAAA;AAAA;AAAA,EAMI,oBAAoB,aAA8B;AACxD,UAAM,qBAAqB,YAAY,KAAK;AAC5C,UAAM,cAAc,mBAAmB,MAAM,IAAI,KAAK,CAAC,GAAG;AAE1D,WAAO,mBAAmB,SAAS,MAAM,aAAa;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAa,mBAAmB,aAAsC;AACrE,QAAI;AACH,aAAO,KAAK,iEAAiE;AAG7E,YAAM,WAAW,MAAM,KAAK,gBAAgB,aAAa;AACzD,YAAM,eAAe,MAAM,KAAK,aAAa,WAAW,QAAQ;AAChE,YAAM,SAAS,KAAK,aAAa,aAAa,YAAY;AAG1D,YAAM,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,EAKhB,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYV,YAAM,WAAW,MAAM,aAAa,QAAQ;AAAA,QAC3C,UAAU;AAAA,QACV,OAAO;AAAA,QACP;AAAA,MACD,CAAC;AAED,UAAI,YAAY,OAAO,aAAa,UAAU;AAC7C,eAAO,QAAQ,mCAAmC;AAClD,eAAO;AAAA,MACR;AAGA,aAAO,KAAK,sEAAsE;AAClF,aAAO;AAAA,IACR,SAAS,OAAO;AACf,aAAO,KAAK,kCAAkC,iBAAiB,QAAQ,MAAM,UAAU,eAAe,EAAE;AACxG,aAAO;AAAA,IACR;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAa,oBACZ,qBACA,qBACA,YACA,QAC2C;AAC3C,WAAO,KAAK,2CAA2C;AAEvD,UAAM,SAAS,MAAM,KAAK,cAAc;AAAA,MACvC;AAAA;AAAA,MACA;AAAA;AAAA,MACA;AAAA,MACA;AAAA,IACD;AAEA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAa,qBAAqB,aAAqB,UAAU,OAAO,YAAoC;AAE3G,UAAM,OAAO,QAAQ,IAAI,OAAO;AAEhC,QAAI,MAAM;AAET,aAAO,KAAK,uEAAuE,WAAW,EAAE;AAChG;AAAA,IACD;AAGA,UAAM,WAAW,MAAM,KAAK,cAAc,YAAY,aAAa,UAAU;AAG7E,UAAM,UAAU,kBAAkB,WAAW;AAAA;AAAA;AAG7C,UAAM,gBAAgB,OAAO;AAG7B,UAAM,YAAY,QAAQ;AAG1B,QAAI,SAAS;AACZ,YAAM,gBAAgB,iDAAiD;AAAA,IACxE;AAAA,EACD;AACD;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/process/ProcessManager.ts"],"sourcesContent":["import { execa } from 'execa'\nimport { setTimeout } from 'timers/promises'\nimport type { ProcessInfo, Platform } from '../../types/process.js'\n\n/**\n * Manages process detection and termination across platforms\n * Ports dev server termination logic from bash/merge-and-clean.sh lines 1092-1148\n */\nexport class ProcessManager {\n\tprivate readonly platform: Platform\n\n\tconstructor() {\n\t\tthis.platform = this.detectPlatform()\n\t}\n\n\t/**\n\t * Detect current platform\n\t */\n\tprivate detectPlatform(): Platform {\n\t\tswitch (process.platform) {\n\t\t\tcase 'darwin':\n\t\t\t\treturn 'darwin'\n\t\t\tcase 'linux':\n\t\t\t\treturn 'linux'\n\t\t\tcase 'win32':\n\t\t\t\treturn 'win32'\n\t\t\tdefault:\n\t\t\t\treturn 'unsupported'\n\t\t}\n\t}\n\n\t/**\n\t * Detect if a dev server is running on the specified port\n\t * Ports logic from merge-and-clean.sh lines 1107-1123\n\t */\n\tasync detectDevServer(port: number): Promise<ProcessInfo | null> {\n\t\tif (this.platform === 'unsupported') {\n\t\t\tthrow new Error('Process detection not supported on this platform')\n\t\t}\n\n\t\t// Use platform-specific detection\n\t\tif (this.platform === 'win32') {\n\t\t\treturn await this.detectOnPortWindows(port)\n\t\t} else {\n\t\t\treturn await this.detectOnPortUnix(port)\n\t\t}\n\t}\n\n\t/**\n\t * Unix/macOS implementation using lsof\n\t * Ports bash lines 1107-1123\n\t */\n\tprivate async detectOnPortUnix(port: number): Promise<ProcessInfo | null> {\n\t\ttry {\n\t\t\t// Run lsof to find process listening on port (LISTEN only)\n\t\t\tconst result = await execa('lsof', ['-i', `:${port}`, '-P'], {\n\t\t\t\treject: false,\n\t\t\t})\n\n\t\t\t// Filter for LISTEN state only\n\t\t\tconst lines = result.stdout.split('\\n').filter(line => line.includes('LISTEN'))\n\n\t\t\tif (lines.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\t// Parse first LISTEN line\n\t\t\tconst firstLine = lines[0]\n\t\t\tif (!firstLine) return null\n\n\t\t\tconst parts = firstLine.split(/\\s+/)\n\t\t\tif (parts.length < 2) return null\n\n\t\t\tconst processName = parts[0] ?? ''\n\t\t\tconst pid = parseInt(parts[1] ?? '', 10)\n\n\t\t\tif (isNaN(pid)) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\t// Get full command line using ps\n\t\t\tconst psResult = await execa('ps', ['-p', pid.toString(), '-o', 'command='], {\n\t\t\t\treject: false,\n\t\t\t})\n\t\t\tconst fullCommand = psResult.stdout.trim()\n\n\t\t\t// Validate if this is a dev server\n\t\t\tconst isDevServer = this.isDevServerProcess(processName, fullCommand)\n\n\t\t\treturn {\n\t\t\t\tpid,\n\t\t\t\tname: processName,\n\t\t\t\tcommand: fullCommand,\n\t\t\t\tport,\n\t\t\t\tisDevServer,\n\t\t\t}\n\t\t} catch {\n\t\t\t// If lsof fails, assume no process on port\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/**\n\t * Windows implementation using netstat and tasklist\n\t */\n\tprivate async detectOnPortWindows(port: number): Promise<ProcessInfo | null> {\n\t\ttry {\n\t\t\t// Use netstat to find PID listening on port\n\t\t\tconst result = await execa('netstat', ['-ano'], { reject: false })\n\t\t\tconst lines = result.stdout.split('\\n')\n\n\t\t\t// Find line with our port and LISTENING state\n\t\t\tconst portLine = lines.find(\n\t\t\t\tline => line.includes(`:${port}`) && line.includes('LISTENING')\n\t\t\t)\n\n\t\t\tif (!portLine) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\t// Extract PID (last column)\n\t\t\tconst parts = portLine.trim().split(/\\s+/)\n\t\t\tconst lastPart = parts[parts.length - 1]\n\t\t\tif (!lastPart) return null\n\n\t\t\tconst pid = parseInt(lastPart, 10)\n\n\t\t\tif (isNaN(pid)) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\t// Get process info using tasklist\n\t\t\tconst taskResult = await execa(\n\t\t\t\t'tasklist',\n\t\t\t\t['/FI', `PID eq ${pid}`, '/FO', 'CSV'],\n\t\t\t\t{\n\t\t\t\t\treject: false,\n\t\t\t\t}\n\t\t\t)\n\n\t\t\t// Parse CSV output\n\t\t\tconst lines2 = taskResult.stdout.split('\\n')\n\t\t\tif (lines2.length < 2) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst secondLine = lines2[1]\n\t\t\tif (!secondLine) return null\n\n\t\t\tconst parts2 = secondLine.split(',')\n\t\t\tconst processName = (parts2[0] ?? '').replace(/\"/g, '')\n\n\t\t\t// TODO: Get full command line on Windows (more complex)\n\t\t\tconst fullCommand = processName\n\n\t\t\tconst isDevServer = this.isDevServerProcess(processName, fullCommand)\n\n\t\t\treturn {\n\t\t\t\tpid,\n\t\t\t\tname: processName,\n\t\t\t\tcommand: fullCommand,\n\t\t\t\tport,\n\t\t\t\tisDevServer,\n\t\t\t}\n\t\t} catch {\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/**\n\t * Validate if process is a dev server\n\t * Ports logic from merge-and-clean.sh lines 1121-1123\n\t */\n\tprivate isDevServerProcess(processName: string, command: string): boolean {\n\t\t// Check process name patterns\n\t\tconst devServerNames = /^(node|npm|pnpm|yarn|next|next-server|vite|webpack|dev-server)$/i\n\t\tif (devServerNames.test(processName)) {\n\t\t\t// Additional validation via command line\n\t\t\tconst devServerCommands =\n\t\t\t\t/(next dev|next-server|npm.*dev|pnpm.*dev|yarn.*dev|vite|webpack.*serve|turbo.*dev|dev.*server)/i\n\t\t\treturn devServerCommands.test(command)\n\t\t}\n\n\t\t// Check command line alone\n\t\tconst devServerCommands =\n\t\t\t/(next dev|next-server|npm.*dev|pnpm.*dev|yarn.*dev|vite|webpack.*serve|turbo.*dev|dev.*server)/i\n\t\treturn devServerCommands.test(command)\n\t}\n\n\t/**\n\t * Terminate a process by PID\n\t * Ports logic from merge-and-clean.sh lines 1126-1139\n\t */\n\tasync terminateProcess(pid: number): Promise<boolean> {\n\t\ttry {\n\t\t\tif (this.platform === 'win32') {\n\t\t\t\t// Windows: use taskkill\n\t\t\t\tawait execa('taskkill', ['/PID', pid.toString(), '/F'], { reject: true })\n\t\t\t} else {\n\t\t\t\t// Unix/macOS: use kill -9\n\t\t\t\tprocess.kill(pid, 'SIGKILL')\n\t\t\t}\n\n\t\t\t// Wait briefly for process to die\n\t\t\tawait setTimeout(1000)\n\n\t\t\treturn true\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to terminate process ${pid}: ${error instanceof Error ? error.message : 'Unknown error'}`\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Verify that a port is free\n\t * Ports verification logic from merge-and-clean.sh lines 1135-1139\n\t */\n\tasync verifyPortFree(port: number): Promise<boolean> {\n\t\tconst processInfo = await this.detectDevServer(port)\n\t\treturn processInfo === null\n\t}\n\n\t/**\n\t * Calculate dev server port from issue/PR number\n\t * Ports logic from merge-and-clean.sh lines 1093-1098\n\t */\n\tcalculatePort(number: number): number {\n\t\treturn 3000 + number\n\t}\n}\n"],"mappings":"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","names":["devServerCommands"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/utils/github.ts"],"sourcesContent":["import { execa } from 'execa'\nimport type {\n\tGitHubIssue,\n\tGitHubPullRequest,\n\tGitHubProject,\n\tGitHubAuthStatus,\n\tBranchNameStrategy,\n\tProjectItem,\n\tProjectField,\n} from '../types/github.js'\nimport { logger } from './logger.js'\n\n// Core GitHub CLI execution wrapper\nexport async function executeGhCommand<T = unknown>(\n\targs: string[],\n\toptions?: { cwd?: string; timeout?: number }\n): Promise<T> {\n\tconst result = await execa('gh', args, {\n\t\tcwd: options?.cwd ?? process.cwd(),\n\t\ttimeout: options?.timeout ?? 30000,\n\t\tencoding: 'utf8',\n\t})\n\n\t// Parse JSON output if --json flag, --format json, or --jq was used\n\tconst isJson =\n\t\targs.includes('--json') ||\n\t\targs.includes('--jq') ||\n\t\t(args.includes('--format') && args[args.indexOf('--format') + 1] === 'json')\n\tconst data = isJson ? JSON.parse(result.stdout) : result.stdout\n\n\treturn data as T\n}\n\n// Authentication checking\nexport async function checkGhAuth(): Promise<GitHubAuthStatus> {\n\ttry {\n\t\tconst output = await executeGhCommand<string>(['auth', 'status'])\n\n\t\t// Parse auth status output - handle both old and new formats\n\t\t// Old format: \"Logged in to github.com as username\"\n\t\t// New format: \"✓ Logged in to github.com account username (keyring)\"\n\n\t\t// Split output into lines to find the active account\n\t\tconst lines = output.split('\\n')\n\t\tlet username: string | undefined\n\t\tlet scopes: string[] = []\n\n\t\t// Find the active account (look for \"Active account: true\" or first account if none marked)\n\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\tconst line = lines[i]\n\n\t\t\t// Match new format: \"✓ Logged in to github.com account username\"\n\t\t\tconst newFormatMatch = line?.match(/Logged in to github\\.com account ([^\\s(]+)/)\n\t\t\tif (newFormatMatch) {\n\t\t\t\tconst accountName = newFormatMatch[1]\n\n\t\t\t\t// Check if this is the active account\n\t\t\t\tconst nextFewLines = lines.slice(i + 1, i + 5).join('\\n')\n\t\t\t\tconst isActive = nextFewLines.includes('Active account: true')\n\n\t\t\t\t// If this is the active account, or we haven't found one yet and there's no \"Active account\" marker\n\t\t\t\tif (isActive || (!username && !output.includes('Active account:'))) {\n\t\t\t\t\tusername = accountName\n\n\t\t\t\t\t// Find scopes for this account\n\t\t\t\t\tconst scopeMatch = nextFewLines.match(/Token scopes: (.+)/)\n\t\t\t\t\tif (scopeMatch?.[1]) {\n\t\t\t\t\t\tscopes = scopeMatch[1].split(', ').map(scope => scope.replace(/^'|'$/g, ''))\n\t\t\t\t\t}\n\n\t\t\t\t\t// If this is the active account, we're done\n\t\t\t\t\tif (isActive) break\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Fallback: match old format\n\t\t\tif (!username) {\n\t\t\t\tconst oldFormatMatch = line?.match(/Logged in to github\\.com as ([^\\s]+)/)\n\t\t\t\tif (oldFormatMatch) {\n\t\t\t\t\tusername = oldFormatMatch[1]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// If scopes not yet extracted, try the old \"Token scopes\" format\n\t\tif (scopes.length === 0) {\n\t\t\tconst scopeMatch = output.match(/Token scopes: (.+)/)\n\t\t\tscopes = scopeMatch?.[1]?.split(', ').map(scope => scope.replace(/^'|'$/g, '')) ?? []\n\t\t}\n\n\t\treturn {\n\t\t\thasAuth: true,\n\t\t\tscopes,\n\t\t\t...(username && { username }),\n\t\t}\n\t} catch (error) {\n\t\t// Only return \"no auth\" for specific authentication errors\n\t\tif (error instanceof Error && 'stderr' in error && (error as {stderr?: string}).stderr?.includes('You are not logged into any GitHub hosts')) {\n\t\t\treturn { hasAuth: false, scopes: [] }\n\t\t}\n\t\t// Re-throw unexpected errors\n\t\tthrow error\n\t}\n}\n\nexport async function hasProjectScope(): Promise<boolean> {\n\tconst auth = await checkGhAuth()\n\treturn auth.scopes.includes('project')\n}\n\n// Issue fetching\nexport async function fetchGhIssue(\n\tissueNumber: number,\n\trepo?: string\n): Promise<GitHubIssue> {\n\tlogger.debug('Fetching GitHub issue', { issueNumber, repo })\n\n\tconst args = [\n\t\t'issue',\n\t\t'view',\n\t\tString(issueNumber),\n\t\t'--json',\n\t\t'number,title,body,state,labels,assignees,url,createdAt,updatedAt',\n\t]\n\n\tif (repo) {\n\t\targs.push('--repo', repo)\n\t}\n\n\treturn executeGhCommand<GitHubIssue>(args)\n}\n\n// PR fetching\nexport async function fetchGhPR(\n\tprNumber: number,\n\trepo?: string\n): Promise<GitHubPullRequest> {\n\tlogger.debug('Fetching GitHub PR', { prNumber, repo })\n\n\tconst args = [\n\t\t'pr',\n\t\t'view',\n\t\tString(prNumber),\n\t\t'--json',\n\t\t'number,title,body,state,headRefName,baseRefName,url,isDraft,mergeable,createdAt,updatedAt',\n\t]\n\n\tif (repo) {\n\t\targs.push('--repo', repo)\n\t}\n\n\treturn executeGhCommand<GitHubPullRequest>(args)\n}\n\n// Project operations\nexport async function fetchProjectList(\n\towner: string\n): Promise<GitHubProject[]> {\n\tconst result = await executeGhCommand<{ projects: GitHubProject[] }>([\n\t\t'project',\n\t\t'list',\n\t\t'--owner',\n\t\towner,\n\t\t'--limit',\n\t\t'100',\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result?.projects ?? []\n}\n\nexport async function fetchProjectItems(\n\tprojectNumber: number,\n\towner: string\n): Promise<ProjectItem[]> {\n\tconst result = await executeGhCommand<{ items: ProjectItem[] }>([\n\t\t'project',\n\t\t'item-list',\n\t\tString(projectNumber),\n\t\t'--owner',\n\t\towner,\n\t\t'--limit',\n\t\t'10000',\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result?.items ?? []\n}\n\nexport async function fetchProjectFields(\n\tprojectNumber: number,\n\towner: string\n): Promise<{ fields: ProjectField[] }> {\n\tconst result = await executeGhCommand<{ fields: ProjectField[] }>([\n\t\t'project',\n\t\t'field-list',\n\t\tString(projectNumber),\n\t\t'--owner',\n\t\towner,\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result ?? { fields: [] }\n}\n\nexport async function updateProjectItemField(\n\titemId: string,\n\tprojectId: string,\n\tfieldId: string,\n\toptionId: string\n): Promise<void> {\n\tawait executeGhCommand([\n\t\t'project',\n\t\t'item-edit',\n\t\t'--id',\n\t\titemId,\n\t\t'--project-id',\n\t\tprojectId,\n\t\t'--field-id',\n\t\tfieldId,\n\t\t'--single-select-option-id',\n\t\toptionId,\n\t\t'--format',\n\t\t'json',\n\t])\n}\n\n// Branch name generation strategies\nexport class SimpleBranchNameStrategy implements BranchNameStrategy {\n\tasync generate(issueNumber: number, title: string): Promise<string> {\n\t\t// Create a simple slug from the title\n\t\tconst slug = title\n\t\t\t.toLowerCase()\n\t\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t\t.replace(/^-|-$/g, '')\n\t\t\t.substring(0, 20) // Keep it short for the simple strategy\n\n\t\treturn `feat/issue-${issueNumber}-${slug}`\n\t}\n}\n\nexport class ClaudeBranchNameStrategy implements BranchNameStrategy {\n\tconstructor(private claudeModel = 'haiku') {}\n\n\tasync generate(issueNumber: number, title: string): Promise<string> {\n\t\t// Import dynamically to avoid circular dependency\n\t\tconst { generateBranchName } = await import('../utils/claude.js')\n\n\t\t// Delegate to the shared implementation\n\t\treturn generateBranchName(title, issueNumber, this.claudeModel)\n\t}\n}\n\n// Template-based strategy for custom patterns\nexport class TemplateBranchNameStrategy implements BranchNameStrategy {\n\tconstructor(private template = '{prefix}/issue-{number}-{slug}') {}\n\n\tasync generate(issueNumber: number, title: string): Promise<string> {\n\t\t// Determine prefix based on title\n\t\tconst prefix = this.determinePrefix(title)\n\n\t\t// Create slug from title\n\t\tconst slug = title\n\t\t\t.toLowerCase()\n\t\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t\t.replace(/^-|-$/g, '')\n\t\t\t.substring(0, 30)\n\n\t\treturn this.template\n\t\t\t.replace('{prefix}', prefix)\n\t\t\t.replace('{number}', String(issueNumber))\n\t\t\t.replace('{slug}', slug)\n\t}\n\n\tprivate determinePrefix(title: string): string {\n\t\tconst lowerTitle = title.toLowerCase()\n\t\tif (lowerTitle.includes('fix') || lowerTitle.includes('bug')) return 'fix'\n\t\tif (lowerTitle.includes('doc')) return 'docs'\n\t\tif (lowerTitle.includes('test')) return 'test'\n\t\tif (lowerTitle.includes('refactor')) return 'refactor'\n\t\treturn 'feat'\n\t}\n}\n\n// GitHub Issue Operations\n\ninterface IssueCreateResponse {\n\tnumber: number\n\turl: string\n}\n\n/**\n * Create a new GitHub issue\n * @param title - The issue title\n * @param body - The issue body (markdown supported)\n * @param options - Optional configuration\n * @param options.repo - Repository in format \"owner/repo\" (uses current repo if not provided)\n * @param options.labels - Array of label names to add to the issue\n * @returns Issue metadata including number and URL\n */\nexport async function createIssue(\n\ttitle: string,\n\tbody: string,\n\toptions?: { repo?: string | undefined; labels?: string[] | undefined }\n): Promise<IssueCreateResponse> {\n\tconst { repo, labels } = options ?? {}\n\n\tlogger.debug('Creating GitHub issue', { title, repo, labels })\n\n\tconst args = [\n\t\t'issue',\n\t\t'create',\n\t\t'--title',\n\t\ttitle,\n\t\t'--body',\n\t\tbody,\n\t]\n\n\t// Add repo if provided\n\tif (repo) {\n\t\targs.splice(2, 0, '--repo', repo)\n\t}\n\n\t// Add labels if provided\n\tif (labels && labels.length > 0) {\n\t\targs.push('--label', labels.join(','))\n\t}\n\n\tconst execaOptions: { timeout: number; encoding: 'utf8'; cwd?: string } = {\n\t\ttimeout: 30000,\n\t\tencoding: 'utf8',\n\t}\n\n\tif (!repo) {\n\t\texecaOptions.cwd = process.cwd()\n\t}\n\n\tconst result = await execa('gh', args, execaOptions)\n\n\t// Parse the URL from the output (format: \"https://github.com/owner/repo/issues/123\")\n\tconst urlMatch = result.stdout.trim().match(/https:\\/\\/github\\.com\\/[^/]+\\/[^/]+\\/issues\\/(\\d+)/)\n\tif (!urlMatch?.[1]) {\n\t\tthrow new Error(`Failed to parse issue URL from gh output: ${result.stdout}`)\n\t}\n\n\tconst issueNumber = parseInt(urlMatch[1], 10)\n\tconst issueUrl = urlMatch[0]\n\n\treturn {\n\t\tnumber: issueNumber,\n\t\turl: issueUrl,\n\t}\n}\n\n/**\n * @deprecated Use createIssue with options.repo instead\n * Create a new GitHub issue in a specific repository\n * @param title - Issue title\n * @param body - Issue body (markdown)\n * @param repository - Repository in format \"owner/repo\"\n * @param labels - Optional array of label names to add to the issue\n * @returns Issue number and URL\n */\nexport async function createIssueInRepo(\n\ttitle: string,\n\tbody: string,\n\trepository: string,\n\tlabels?: string[]\n): Promise<IssueCreateResponse> {\n\treturn createIssue(title, body, { repo: repository, labels })\n}\n\n// GitHub Comment Operations\n\ninterface CommentResponse {\n\tid: number\n\turl: string\n\tcreated_at?: string\n\tupdated_at?: string\n}\n\ninterface RepoInfo {\n\towner: string\n\tname: string\n}\n\n/**\n * Create a comment on a GitHub issue\n * @param issueNumber - The issue number\n * @param body - The comment body (markdown supported)\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Comment metadata including ID and URL\n */\nexport async function createIssueComment(\n\tissueNumber: number,\n\tbody: string,\n\trepo?: string\n): Promise<CommentResponse> {\n\tlogger.debug('Creating issue comment', { issueNumber, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${issueNumber}/comments`\n\t\t: `repos/:owner/:repo/issues/${issueNumber}/comments`\n\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\tapiPath,\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, created_at: .created_at}',\n\t])\n}\n\n/**\n * Update an existing GitHub comment\n * @param commentId - The comment ID\n * @param body - The updated comment body (markdown supported)\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Updated comment metadata\n */\nexport async function updateIssueComment(\n\tcommentId: number,\n\tbody: string,\n\trepo?: string\n): Promise<CommentResponse> {\n\tlogger.debug('Updating issue comment', { commentId, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/comments/${commentId}`\n\t\t: `repos/:owner/:repo/issues/comments/${commentId}`\n\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\tapiPath,\n\t\t'-X',\n\t\t'PATCH',\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, updated_at: .updated_at}',\n\t])\n}\n\n/**\n * Create a comment on a GitHub pull request\n * Note: PR comments use the same endpoint as issue comments\n * @param prNumber - The PR number\n * @param body - The comment body (markdown supported)\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Comment metadata including ID and URL\n */\nexport async function createPRComment(\n\tprNumber: number,\n\tbody: string,\n\trepo?: string\n): Promise<CommentResponse> {\n\tlogger.debug('Creating PR comment', { prNumber, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${prNumber}/comments`\n\t\t: `repos/:owner/:repo/issues/${prNumber}/comments`\n\n\t// PR comments use the issues endpoint\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\tapiPath,\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, created_at: .created_at}',\n\t])\n}\n\n/**\n * Get repository owner and name from current directory\n * @returns Repository owner and name\n */\nexport async function getRepoInfo(): Promise<RepoInfo> {\n\tlogger.debug('Fetching repository info')\n\n\tconst result = await executeGhCommand<{ owner: { login: string }; name: string }>([\n\t\t'repo',\n\t\t'view',\n\t\t'--json',\n\t\t'owner,name',\n\t])\n\n\treturn {\n\t\towner: result.owner.login,\n\t\tname: result.name,\n\t}\n}"],"mappings":"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","names":[]}