@iloom/cli 0.1.19 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/README.md +290 -30
  2. package/dist/BranchNamingService-3OQPRSWT.js +13 -0
  3. package/dist/ClaudeContextManager-MUQSDY2E.js +13 -0
  4. package/dist/ClaudeService-HG4VQ7AW.js +12 -0
  5. package/dist/GitHubService-EBOETDIW.js +11 -0
  6. package/dist/{LoomLauncher-UMMLPIZO.js → LoomLauncher-FLEMBCSQ.js} +64 -33
  7. package/dist/LoomLauncher-FLEMBCSQ.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 +290 -30
  11. package/dist/{SettingsManager-SKLUVE3K.js → SettingsManager-WHHFGSL7.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-3KATJIKO.js +55 -0
  21. package/dist/chunk-3KATJIKO.js.map +1 -0
  22. package/dist/{chunk-JXQXSC45.js → chunk-3RUPPQRG.js} +1 -18
  23. package/dist/chunk-3RUPPQRG.js.map +1 -0
  24. package/dist/{chunk-PR7FKQBG.js → chunk-47KSHUCR.js} +3 -3
  25. package/dist/chunk-47KSHUCR.js.map +1 -0
  26. package/dist/{chunk-IO4WFTL2.js → chunk-4HHRTA7Q.js} +3 -3
  27. package/dist/{chunk-IO4WFTL2.js.map → chunk-4HHRTA7Q.js.map} +1 -1
  28. package/dist/chunk-5EF7Z346.js +1987 -0
  29. package/dist/chunk-5EF7Z346.js.map +1 -0
  30. package/dist/{chunk-VVH3ANF2.js → chunk-AWOFAD5O.js} +12 -12
  31. package/dist/chunk-AWOFAD5O.js.map +1 -0
  32. package/dist/{chunk-DEPYQRRB.js → chunk-C5QCTEQK.js} +2 -2
  33. package/dist/{chunk-CWR2SANQ.js → chunk-EBISESAP.js} +1 -1
  34. package/dist/{chunk-ELFT36PV.js → chunk-FIAT22G7.js} +4 -16
  35. package/dist/chunk-FIAT22G7.js.map +1 -0
  36. package/dist/{chunk-ZWXJBSUW.js → chunk-G2IEYOLQ.js} +11 -38
  37. package/dist/chunk-G2IEYOLQ.js.map +1 -0
  38. package/dist/{chunk-ZMNQBJUI.js → chunk-IP7SMKIF.js} +61 -22
  39. package/dist/chunk-IP7SMKIF.js.map +1 -0
  40. package/dist/{chunk-JNKJ7NJV.js → chunk-JKXJ7BGL.js} +6 -2
  41. package/dist/{chunk-JNKJ7NJV.js.map → chunk-JKXJ7BGL.js.map} +1 -1
  42. package/dist/{chunk-LAPY6NAE.js → chunk-JQFO7QQN.js} +68 -12
  43. package/dist/{chunk-LAPY6NAE.js.map → chunk-JQFO7QQN.js.map} +1 -1
  44. package/dist/{SettingsMigrationManager-MTQIMI54.js → chunk-KLBYVHPK.js} +3 -2
  45. package/dist/{chunk-KOCQAD2E.js → chunk-MAVL6PJF.js} +26 -3
  46. package/dist/chunk-MAVL6PJF.js.map +1 -0
  47. package/dist/{chunk-USVVV3FP.js → chunk-MKWYLDFK.js} +5 -5
  48. package/dist/chunk-ML3NRPNB.js +396 -0
  49. package/dist/chunk-ML3NRPNB.js.map +1 -0
  50. package/dist/{chunk-FXV24OYZ.js → chunk-PA6Q6AWM.js} +24 -4
  51. package/dist/chunk-PA6Q6AWM.js.map +1 -0
  52. package/dist/chunk-RO26VS3W.js +444 -0
  53. package/dist/chunk-RO26VS3W.js.map +1 -0
  54. package/dist/{chunk-PV3GAXQO.js → chunk-VAYCCUXW.js} +72 -2
  55. package/dist/{chunk-PV3GAXQO.js.map → chunk-VAYCCUXW.js.map} +1 -1
  56. package/dist/{chunk-SPYPLHMK.js → chunk-VU3QMIP2.js} +34 -2
  57. package/dist/chunk-VU3QMIP2.js.map +1 -0
  58. package/dist/{chunk-PVAVNJKS.js → chunk-WEN5C5DM.js} +10 -1
  59. package/dist/chunk-WEN5C5DM.js.map +1 -0
  60. package/dist/{chunk-PXZBAC2M.js → chunk-XXV3UFZL.js} +4 -4
  61. package/dist/{chunk-PXZBAC2M.js.map → chunk-XXV3UFZL.js.map} +1 -1
  62. package/dist/{chunk-RSRO7564.js → chunk-ZE74H5BR.js} +28 -3
  63. package/dist/chunk-ZE74H5BR.js.map +1 -0
  64. package/dist/{chunk-GZP4UGGM.js → chunk-ZM3CFL5L.js} +2 -2
  65. package/dist/{chunk-BLCTGFZN.js → chunk-ZT3YZB4K.js} +3 -4
  66. package/dist/chunk-ZT3YZB4K.js.map +1 -0
  67. package/dist/{claude-7LUVDZZ4.js → claude-GOP6PFC7.js} +2 -2
  68. package/dist/{cleanup-ZHROIBSQ.js → cleanup-7RWLBSLE.js} +86 -25
  69. package/dist/cleanup-7RWLBSLE.js.map +1 -0
  70. package/dist/cli.js +2513 -64
  71. package/dist/cli.js.map +1 -1
  72. package/dist/{contribute-3MQJ3XAQ.js → contribute-BS2L4FZR.js} +9 -6
  73. package/dist/{contribute-3MQJ3XAQ.js.map → contribute-BS2L4FZR.js.map} +1 -1
  74. package/dist/{feedback-ZOUCCHN4.js → feedback-N4ECWIPF.js} +15 -14
  75. package/dist/{feedback-ZOUCCHN4.js.map → feedback-N4ECWIPF.js.map} +1 -1
  76. package/dist/{git-OUYMVYJX.js → git-TDXKRTXM.js} +4 -2
  77. package/dist/{ignite-HICLZEYU.js → ignite-VM64QO3J.js} +32 -27
  78. package/dist/ignite-VM64QO3J.js.map +1 -0
  79. package/dist/index.d.ts +379 -45
  80. package/dist/index.js +1241 -448
  81. package/dist/index.js.map +1 -1
  82. package/dist/{init-UMKNHNV5.js → init-G3T64SC4.js} +104 -40
  83. package/dist/init-G3T64SC4.js.map +1 -0
  84. package/dist/mcp/issue-management-server.js +934 -0
  85. package/dist/mcp/issue-management-server.js.map +1 -0
  86. package/dist/{neon-helpers-ZVIRPKCI.js → neon-helpers-WPUACUVC.js} +3 -3
  87. package/dist/{open-ETZUFSE4.js → open-KXDXEKRZ.js} +39 -36
  88. package/dist/open-KXDXEKRZ.js.map +1 -0
  89. package/dist/{prompt-ANTQWHUF.js → prompt-7INJ7YRU.js} +4 -2
  90. package/dist/prompt-7INJ7YRU.js.map +1 -0
  91. package/dist/prompts/init-prompt.txt +563 -91
  92. package/dist/prompts/issue-prompt.txt +27 -27
  93. package/dist/{rebase-KBWFDZCN.js → rebase-Q7GMM7EI.js} +6 -6
  94. package/dist/{remote-GJEZWRCC.js → remote-VUNCQZ6J.js} +5 -2
  95. package/dist/remote-VUNCQZ6J.js.map +1 -0
  96. package/dist/{run-4SVQ3WEU.js → run-PAWJJCSX.js} +39 -36
  97. package/dist/run-PAWJJCSX.js.map +1 -0
  98. package/dist/schema/settings.schema.json +74 -0
  99. package/dist/{terminal-3D6TUAKJ.js → terminal-BIRBZ4AZ.js} +2 -2
  100. package/dist/terminal-BIRBZ4AZ.js.map +1 -0
  101. package/dist/{test-git-MKZATGZN.js → test-git-3WDLNQCA.js} +3 -3
  102. package/dist/{test-prefix-ZNLWDI3K.js → test-prefix-EVGAWAJW.js} +3 -3
  103. package/dist/{test-tabs-JRKY3QMM.js → test-tabs-RXDBZ6J7.js} +2 -2
  104. package/dist/{test-webserver-M2I3EV4J.js → test-webserver-DAHONWCS.js} +4 -4
  105. package/dist/test-webserver-DAHONWCS.js.map +1 -0
  106. package/package.json +2 -1
  107. package/dist/ClaudeContextManager-JKR4WGNU.js +0 -13
  108. package/dist/ClaudeService-55DQGB7T.js +0 -12
  109. package/dist/GitHubService-LWP4GKGH.js +0 -11
  110. package/dist/LoomLauncher-UMMLPIZO.js.map +0 -1
  111. package/dist/add-issue-X56V3XPB.js +0 -69
  112. package/dist/add-issue-X56V3XPB.js.map +0 -1
  113. package/dist/chunk-BLCTGFZN.js.map +0 -1
  114. package/dist/chunk-ELFT36PV.js.map +0 -1
  115. package/dist/chunk-FXV24OYZ.js.map +0 -1
  116. package/dist/chunk-H4E4THUZ.js +0 -55
  117. package/dist/chunk-H4E4THUZ.js.map +0 -1
  118. package/dist/chunk-H5LDRGVK.js +0 -642
  119. package/dist/chunk-H5LDRGVK.js.map +0 -1
  120. package/dist/chunk-JXQXSC45.js.map +0 -1
  121. package/dist/chunk-KOCQAD2E.js.map +0 -1
  122. package/dist/chunk-PR7FKQBG.js.map +0 -1
  123. package/dist/chunk-PVAVNJKS.js.map +0 -1
  124. package/dist/chunk-Q2KYPAH2.js +0 -545
  125. package/dist/chunk-Q2KYPAH2.js.map +0 -1
  126. package/dist/chunk-RSRO7564.js.map +0 -1
  127. package/dist/chunk-SPYPLHMK.js.map +0 -1
  128. package/dist/chunk-VCMMAFXQ.js +0 -54
  129. package/dist/chunk-VCMMAFXQ.js.map +0 -1
  130. package/dist/chunk-VVH3ANF2.js.map +0 -1
  131. package/dist/chunk-VYQLLHZ7.js +0 -239
  132. package/dist/chunk-VYQLLHZ7.js.map +0 -1
  133. package/dist/chunk-ZMNQBJUI.js.map +0 -1
  134. package/dist/chunk-ZWXJBSUW.js.map +0 -1
  135. package/dist/cleanup-ZHROIBSQ.js.map +0 -1
  136. package/dist/enhance-VGWUX474.js +0 -176
  137. package/dist/enhance-VGWUX474.js.map +0 -1
  138. package/dist/finish-QJSK6Z7J.js +0 -1355
  139. package/dist/finish-QJSK6Z7J.js.map +0 -1
  140. package/dist/ignite-HICLZEYU.js.map +0 -1
  141. package/dist/init-UMKNHNV5.js.map +0 -1
  142. package/dist/mcp/chunk-6SDFJ42P.js +0 -62
  143. package/dist/mcp/chunk-6SDFJ42P.js.map +0 -1
  144. package/dist/mcp/claude-YHHHLSXH.js +0 -249
  145. package/dist/mcp/claude-YHHHLSXH.js.map +0 -1
  146. package/dist/mcp/color-QS5BFCNN.js +0 -168
  147. package/dist/mcp/color-QS5BFCNN.js.map +0 -1
  148. package/dist/mcp/github-comment-server.js +0 -168
  149. package/dist/mcp/github-comment-server.js.map +0 -1
  150. package/dist/mcp/terminal-SDCMDVD7.js +0 -202
  151. package/dist/mcp/terminal-SDCMDVD7.js.map +0 -1
  152. package/dist/open-ETZUFSE4.js.map +0 -1
  153. package/dist/run-4SVQ3WEU.js.map +0 -1
  154. package/dist/start-CT2ZEFP2.js +0 -983
  155. package/dist/start-CT2ZEFP2.js.map +0 -1
  156. package/dist/test-webserver-M2I3EV4J.js.map +0 -1
  157. /package/dist/{ClaudeContextManager-JKR4WGNU.js.map → BranchNamingService-3OQPRSWT.js.map} +0 -0
  158. /package/dist/{ClaudeService-55DQGB7T.js.map → ClaudeContextManager-MUQSDY2E.js.map} +0 -0
  159. /package/dist/{GitHubService-LWP4GKGH.js.map → ClaudeService-HG4VQ7AW.js.map} +0 -0
  160. /package/dist/{PromptTemplateManager-WII75TKH.js.map → GitHubService-EBOETDIW.js.map} +0 -0
  161. /package/dist/{SettingsManager-SKLUVE3K.js.map → ProjectCapabilityDetector-34LU7JJ4.js.map} +0 -0
  162. /package/dist/{claude-7LUVDZZ4.js.map → PromptTemplateManager-A52RUAMS.js.map} +0 -0
  163. /package/dist/{git-OUYMVYJX.js.map → SettingsManager-WHHFGSL7.js.map} +0 -0
  164. /package/dist/{neon-helpers-ZVIRPKCI.js.map → SettingsMigrationManager-AGIIIPDQ.js.map} +0 -0
  165. /package/dist/{chunk-DEPYQRRB.js.map → chunk-C5QCTEQK.js.map} +0 -0
  166. /package/dist/{chunk-CWR2SANQ.js.map → chunk-EBISESAP.js.map} +0 -0
  167. /package/dist/{SettingsMigrationManager-MTQIMI54.js.map → chunk-KLBYVHPK.js.map} +0 -0
  168. /package/dist/{chunk-USVVV3FP.js.map → chunk-MKWYLDFK.js.map} +0 -0
  169. /package/dist/{chunk-GZP4UGGM.js.map → chunk-ZM3CFL5L.js.map} +0 -0
  170. /package/dist/{prompt-ANTQWHUF.js.map → claude-GOP6PFC7.js.map} +0 -0
  171. /package/dist/{remote-GJEZWRCC.js.map → git-TDXKRTXM.js.map} +0 -0
  172. /package/dist/{terminal-3D6TUAKJ.js.map → neon-helpers-WPUACUVC.js.map} +0 -0
  173. /package/dist/{rebase-KBWFDZCN.js.map → rebase-Q7GMM7EI.js.map} +0 -0
  174. /package/dist/{test-git-MKZATGZN.js.map → test-git-3WDLNQCA.js.map} +0 -0
  175. /package/dist/{test-prefix-ZNLWDI3K.js.map → test-prefix-EVGAWAJW.js.map} +0 -0
  176. /package/dist/{test-tabs-JRKY3QMM.js.map → test-tabs-RXDBZ6J7.js.map} +0 -0
@@ -1,7 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- ClaudeBranchNameStrategy,
4
- SimpleBranchNameStrategy,
5
3
  createIssue,
6
4
  executeGhCommand,
7
5
  fetchGhIssue,
@@ -11,10 +9,10 @@ import {
11
9
  fetchProjectList,
12
10
  hasProjectScope,
13
11
  updateProjectItemField
14
- } from "./chunk-JXQXSC45.js";
12
+ } from "./chunk-3RUPPQRG.js";
15
13
  import {
16
14
  promptConfirmation
17
- } from "./chunk-JNKJ7NJV.js";
15
+ } from "./chunk-JKXJ7BGL.js";
18
16
  import {
19
17
  logger
20
18
  } from "./chunk-GEHQXLEI.js";
@@ -32,35 +30,29 @@ var GitHubError = class extends Error {
32
30
  // src/lib/GitHubService.ts
33
31
  var GitHubService = class {
34
32
  constructor(options) {
35
- if (options == null ? void 0 : options.branchNameStrategy) {
36
- this.defaultBranchNameStrategy = options.branchNameStrategy;
37
- } else if ((options == null ? void 0 : options.useClaude) !== false) {
38
- this.defaultBranchNameStrategy = new ClaudeBranchNameStrategy(
39
- options == null ? void 0 : options.claudeModel
40
- );
41
- } else {
42
- this.defaultBranchNameStrategy = new SimpleBranchNameStrategy();
43
- }
33
+ // IssueTracker interface implementation
34
+ this.providerName = "github";
35
+ this.supportsPullRequests = true;
44
36
  this.prompter = (options == null ? void 0 : options.prompter) ?? promptConfirmation;
45
37
  }
46
- // Input detection
38
+ // Input detection - IssueTracker interface implementation
47
39
  async detectInputType(input, repo) {
48
40
  const numberMatch = input.match(/^#?(\d+)$/);
49
41
  if (!(numberMatch == null ? void 0 : numberMatch[1])) {
50
- return { type: "unknown", number: null, rawInput: input };
42
+ return { type: "unknown", identifier: null, rawInput: input };
51
43
  }
52
44
  const number = parseInt(numberMatch[1], 10);
53
45
  logger.debug("Checking if input is a PR", { number });
54
46
  const pr = await this.isValidPR(number, repo);
55
47
  if (pr) {
56
- return { type: "pr", number, rawInput: input };
48
+ return { type: "pr", identifier: number.toString(), rawInput: input };
57
49
  }
58
50
  logger.debug("Checking if input is an issue", { number });
59
51
  const issue = await this.isValidIssue(number, repo);
60
52
  if (issue) {
61
- return { type: "issue", number, rawInput: input };
53
+ return { type: "issue", identifier: number.toString(), rawInput: input };
62
54
  }
63
- return { type: "unknown", number: null, rawInput: input };
55
+ return { type: "unknown", identifier: null, rawInput: input };
64
56
  }
65
57
  // Issue fetching with validation
66
58
  async fetchIssue(issueNumber, repo) {
@@ -154,17 +146,6 @@ var GitHubService = class {
154
146
  }
155
147
  }
156
148
  }
157
- // Branch name generation using strategy pattern
158
- async generateBranchName(options) {
159
- const { issueNumber, title, strategy } = options;
160
- const nameStrategy = strategy ?? this.defaultBranchNameStrategy;
161
- logger.debug("Generating branch name", {
162
- issueNumber,
163
- title,
164
- strategy: nameStrategy.constructor.name
165
- });
166
- return nameStrategy.generate(issueNumber, title);
167
- }
168
149
  // Issue creation
169
150
  async createIssue(title, body, repository, labels) {
170
151
  return createIssue(title, body, { repo: repository, labels });
@@ -299,17 +280,9 @@ State: ${entity.state}`;
299
280
  async promptUserConfirmation(message) {
300
281
  return this.prompter(message);
301
282
  }
302
- // Allow setting strategy at runtime for specific operations
303
- setDefaultBranchNameStrategy(strategy) {
304
- this.defaultBranchNameStrategy = strategy;
305
- }
306
- // Get current strategy for testing
307
- getBranchNameStrategy() {
308
- return this.defaultBranchNameStrategy;
309
- }
310
283
  };
311
284
 
312
285
  export {
313
286
  GitHubService
314
287
  };
315
- //# sourceMappingURL=chunk-ZWXJBSUW.js.map
288
+ //# sourceMappingURL=chunk-G2IEYOLQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types/github.ts","../src/lib/GitHubService.ts"],"sourcesContent":["// Core GitHub response types\nexport interface GitHubIssue {\n\tnumber: number\n\ttitle: string\n\tbody: string\n\tstate: 'OPEN' | 'CLOSED' // GitHub GraphQL format\n\tlabels: { name: string }[]\n\tassignees: { login: string }[]\n\turl: string\n\tcreatedAt: string\n\tupdatedAt: string\n}\n\n// Pull Request types\nexport interface GitHubPullRequest {\n\tnumber: number\n\ttitle: string\n\tbody: string\n\tstate: 'OPEN' | 'CLOSED' | 'MERGED'\n\theadRefName: string // source branch\n\tbaseRefName: string // target branch\n\turl: string\n\tisDraft: boolean\n\tmergeable: 'CONFLICTING' | 'MERGEABLE' | 'UNKNOWN'\n\tcreatedAt: string\n\tupdatedAt: string\n}\n\n// GitHub Projects types\nexport interface GitHubProject {\n\tnumber: number\n\tid: string\n\tname: string\n\tfields: ProjectField[]\n}\n\nexport interface ProjectField {\n\tid: string\n\tname: string\n\tdataType: 'SINGLE_SELECT' | 'TEXT' | 'NUMBER' | 'DATE'\n\toptions?: ProjectFieldOption[]\n}\n\nexport interface ProjectFieldOption {\n\tid: string\n\tname: string\n}\n\nexport interface ProjectItem {\n\tid: string\n\tcontent: {\n\t\ttype: 'Issue' | 'PullRequest' | 'DraftIssue'\n\t\tnumber: number\n\t}\n\tfieldValues: Record<string, unknown>\n}\n\n// Command result types\nexport interface GitHubCommandResult<T = unknown> {\n\tsuccess: boolean\n\tdata?: T\n\terror?: string\n\trateLimitRemaining?: number\n\trateLimitReset?: Date\n}\n\nexport interface GitHubAuthStatus {\n\thasAuth: boolean\n\tscopes: string[]\n\tusername?: string\n}\n\n// Input detection types\nexport interface GitHubInputDetection {\n\ttype: 'issue' | 'pr' | 'unknown'\n\tnumber: number | null\n\trawInput: string\n}\n\n// Context and error types\nexport interface GitHubContext {\n\tissue?: GitHubIssue\n\tpullRequest?: GitHubPullRequest\n\tformattedContext: string\n}\n\nexport enum GitHubErrorCode {\n\tNOT_FOUND = 'NOT_FOUND',\n\tUNAUTHORIZED = 'UNAUTHORIZED',\n\tRATE_LIMITED = 'RATE_LIMITED',\n\tNETWORK_ERROR = 'NETWORK_ERROR',\n\tINVALID_STATE = 'INVALID_STATE',\n\tMISSING_SCOPE = 'MISSING_SCOPE',\n}\n\nexport class GitHubError extends Error {\n\tconstructor(\n\t\tpublic code: GitHubErrorCode,\n\t\tmessage: string,\n\t\tpublic details?: unknown\n\t) {\n\t\tsuper(message)\n\t\tthis.name = 'GitHubError'\n\t}\n}\n","import type { Issue, PullRequest, IssueTrackerInputDetection } from '../types/index.js'\nimport type {\n\tGitHubIssue,\n\tGitHubPullRequest,\n\tGitHubProject,\n\tProjectItem,\n\tProjectField,\n} from '../types/github.js'\nimport { GitHubError, GitHubErrorCode } from '../types/github.js'\nimport {\n\texecuteGhCommand,\n\thasProjectScope,\n\tfetchGhIssue,\n\tfetchGhPR,\n\tfetchProjectList,\n\tfetchProjectItems,\n\tfetchProjectFields,\n\tupdateProjectItemField,\n\tcreateIssue,\n} from '../utils/github.js'\nimport { logger } from '../utils/logger.js'\nimport { promptConfirmation } from '../utils/prompt.js'\nimport type { IssueTracker } from './IssueTracker.js'\n\nexport class GitHubService implements IssueTracker {\n\t// IssueTracker interface implementation\n\treadonly providerName = 'github'\n\treadonly supportsPullRequests = true\n\tprivate prompter: (message: string) => Promise<boolean>\n\n\tconstructor(options?: {\n\t\tprompter?: (message: string) => Promise<boolean>\n\t}) {\n\t\t// Set up prompter (use provided or default to promptConfirmation)\n\t\tthis.prompter = options?.prompter ?? promptConfirmation\n\t}\n\n\t// Input detection - IssueTracker interface implementation\n\tpublic async detectInputType(input: string, repo?: string): Promise<IssueTrackerInputDetection> {\n\t\t// Pattern: #123 or just 123\n\t\tconst numberMatch = input.match(/^#?(\\d+)$/)\n\n\t\tif (!numberMatch?.[1]) {\n\t\t\treturn { type: 'unknown', identifier: null, rawInput: input }\n\t\t}\n\n\t\tconst number = parseInt(numberMatch[1], 10)\n\n\t\t// Try PR first (based on bash script logic at lines 500-533)\n\t\tlogger.debug('Checking if input is a PR', { number })\n\t\tconst pr = await this.isValidPR(number, repo)\n\t\tif (pr) {\n\t\t\treturn { type: 'pr', identifier: number.toString(), rawInput: input }\n\t\t}\n\n\t\t// Try issue next (lines 536-575 in bash)\n\t\tlogger.debug('Checking if input is an issue', { number })\n\t\tconst issue = await this.isValidIssue(number, repo)\n\t\tif (issue) {\n\t\t\treturn { type: 'issue', identifier: number.toString(), rawInput: input }\n\t\t}\n\n\t\t// Neither PR nor issue found\n\t\treturn { type: 'unknown', identifier: null, rawInput: input }\n\t}\n\n\t// Issue fetching with validation\n\tpublic async fetchIssue(issueNumber: number, repo?: string): Promise<Issue> {\n\t\ttry {\n\t\t\treturn await this.fetchIssueInternal(issueNumber, repo)\n\t\t} catch (error) {\n\t\t\t// Only throw NOT_FOUND for actual \"not found\" errors\n\t\t\tif (error instanceof Error && 'stderr' in error && (error as {stderr?: string}).stderr?.includes('Could not resolve')) {\n\t\t\t\tthrow new GitHubError(\n\t\t\t\t\tGitHubErrorCode.NOT_FOUND,\n\t\t\t\t\t`Issue #${issueNumber} not found`,\n\t\t\t\t\terror\n\t\t\t\t)\n\t\t\t}\n\t\t\t// Re-throw all other errors unchanged\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t// Silent issue validation (for detection phase)\n\tpublic async isValidIssue(issueNumber: number, repo?: string): Promise<Issue | false> {\n\t\ttry {\n\t\t\treturn await this.fetchIssueInternal(issueNumber, repo)\n\t\t} catch (error) {\n\t\t\t// Silently return false for \"not found\" errors\n\t\t\tif (error instanceof Error && 'stderr' in error && (error as {stderr?: string}).stderr?.includes('Could not resolve')) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\t// Re-throw unexpected errors\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t// Internal issue fetching logic (shared by fetchIssue and isValidIssue)\n\tprivate async fetchIssueInternal(issueNumber: number, repo?: string): Promise<Issue> {\n\t\tconst ghIssue = await fetchGhIssue(issueNumber, repo)\n\t\treturn this.mapGitHubIssueToIssue(ghIssue)\n\t}\n\n\tpublic async validateIssueState(issue: Issue): Promise<void> {\n\t\tif (issue.state === 'closed') {\n\t\t\tconst response = await this.promptUserConfirmation(\n\t\t\t\t`Issue #${issue.number} is closed. Continue anyway?`\n\t\t\t)\n\t\t\tif (!response) {\n\t\t\t\tthrow new GitHubError(\n\t\t\t\t\tGitHubErrorCode.INVALID_STATE,\n\t\t\t\t\t'User cancelled due to closed issue'\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\t// PR fetching with validation\n\tpublic async fetchPR(prNumber: number, repo?: string): Promise<PullRequest> {\n\t\ttry {\n\t\t\treturn await this.fetchPRInternal(prNumber, repo)\n\t\t} catch (error) {\n\t\t\t// Only throw NOT_FOUND for actual \"not found\" errors\n\t\t\tif (error instanceof Error && 'stderr' in error && (error as {stderr?: string}).stderr?.includes('Could not resolve')) {\n\t\t\t\tthrow new GitHubError(\n\t\t\t\t\tGitHubErrorCode.NOT_FOUND,\n\t\t\t\t\t`PR #${prNumber} not found`,\n\t\t\t\t\terror\n\t\t\t\t)\n\t\t\t}\n\t\t\t// Re-throw all other errors unchanged\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t// Silent PR validation (for detection phase)\n\tpublic async isValidPR(prNumber: number, repo?: string): Promise<PullRequest | false> {\n\t\ttry {\n\t\t\treturn await this.fetchPRInternal(prNumber, repo)\n\t\t} catch (error) {\n\t\t\t// Silently return false for \"not found\" errors\n\t\t\tif (error instanceof Error && 'stderr' in error && (error as {stderr?: string}).stderr?.includes('Could not resolve')) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\t// Re-throw unexpected errors\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t// Internal PR fetching logic (shared by fetchPR and isValidPR)\n\tprivate async fetchPRInternal(prNumber: number, repo?: string): Promise<PullRequest> {\n\t\tconst ghPR = await fetchGhPR(prNumber, repo)\n\t\treturn this.mapGitHubPRToPullRequest(ghPR)\n\t}\n\n\tpublic async validatePRState(pr: PullRequest): Promise<void> {\n\t\tif (pr.state === 'closed' || pr.state === 'merged') {\n\t\t\tconst response = await this.promptUserConfirmation(\n\t\t\t\t`PR #${pr.number} is ${pr.state}. Continue anyway?`\n\t\t\t)\n\t\t\tif (!response) {\n\t\t\t\tthrow new GitHubError(\n\t\t\t\t\tGitHubErrorCode.INVALID_STATE,\n\t\t\t\t\t`User cancelled due to ${pr.state} PR`\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Issue creation\n\tpublic async createIssue(\n\t\ttitle: string,\n\t\tbody: string,\n\t\trepository?: string,\n\t\tlabels?: string[]\n\t): Promise<{ number: string | number; url: string }> {\n\t\t// logger.info('Creating GitHub issue', { title })\n\t\treturn createIssue(title, body, { repo: repository, labels })\n\t}\n\n\tpublic async getIssueUrl(issueNumber: number, repo?: string): Promise<string> {\n\t\tlogger.debug('Fetching issue URL', { issueNumber, repo })\n\t\tconst issue = await fetchGhIssue(issueNumber, repo)\n\t\treturn issue.url\n\t}\n\n\t// GitHub Projects integration\n\tpublic async moveIssueToInProgress(issueNumber: number): Promise<void> {\n\t\t// Based on bash script lines 374-463\n\t\tlogger.info('Moving issue to In Progress in GitHub Projects', {\n\t\t\tissueNumber,\n\t\t})\n\n\t\t// Check for project scope\n\t\tif (!(await hasProjectScope())) {\n\t\t\tlogger.warn('Missing project scope in GitHub CLI auth')\n\t\t\tthrow new GitHubError(\n\t\t\t\tGitHubErrorCode.MISSING_SCOPE,\n\t\t\t\t'GitHub CLI lacks project scope. Run: gh auth refresh -s project'\n\t\t\t)\n\t\t}\n\n\t\t// Get repository info\n\t\tlet owner: string\n\t\ttry {\n\t\t\tconst repoInfo = await executeGhCommand<{\n\t\t\t\towner: { login: string }\n\t\t\t\tname: string\n\t\t\t}>(['repo', 'view', '--json', 'owner,name'])\n\t\t\towner = repoInfo.owner.login\n\t\t} catch (error) {\n\t\t\tlogger.warn('Could not determine repository info', { error })\n\t\t\treturn\n\t\t}\n\n\t\t// List all projects\n\t\tlet projects: GitHubProject[]\n\t\ttry {\n\t\t\tprojects = await fetchProjectList(owner)\n\t\t} catch (error) {\n\t\t\tlogger.warn('Could not fetch projects', { owner, error })\n\t\t\treturn\n\t\t}\n\n\t\tif (!projects.length) {\n\t\t\tlogger.warn('No projects found', { owner })\n\t\t\treturn\n\t\t}\n\n\t\t// Process each project (lines 404-460 in bash)\n\t\tfor (const project of projects) {\n\t\t\tawait this.updateIssueStatusInProject(project, issueNumber, owner)\n\t\t}\n\t}\n\n\tprivate async updateIssueStatusInProject(\n\t\tproject: GitHubProject,\n\t\tissueNumber: number,\n\t\towner: string\n\t): Promise<void> {\n\t\t// Check if issue is in project\n\t\tlet items: ProjectItem[]\n\t\ttry {\n\t\t\titems = await fetchProjectItems(project.number, owner)\n\t\t} catch (error) {\n\t\t\tlogger.debug('Could not fetch project items', { project: project.number, error })\n\t\t\treturn\n\t\t}\n\n\t\t// Find issue item\n\t\tconst item = items.find(\n\t\t\t(i: ProjectItem) =>\n\t\t\t\ti.content.type === 'Issue' && i.content.number === issueNumber\n\t\t)\n\n\t\tif (!item) {\n\t\t\tlogger.debug('Issue not found in project', {\n\t\t\t\tissueNumber,\n\t\t\t\tprojectNumber: project.number,\n\t\t\t})\n\t\t\treturn\n\t\t}\n\n\t\t// Fetch project fields separately (like bash script does)\n\t\tlet fieldsData: { fields: ProjectField[] }\n\t\ttry {\n\t\t\tfieldsData = await fetchProjectFields(project.number, owner)\n\t\t} catch (error) {\n\t\t\tlogger.debug('Could not fetch project fields', { project: project.number, error })\n\t\t\treturn\n\t\t}\n\n\t\t// Find Status field and In Progress option\n\t\tconst statusField = fieldsData.fields.find((f) => f.name === 'Status')\n\t\tif (!statusField) {\n\t\t\tlogger.debug('No Status field found in project', { projectNumber: project.number })\n\t\t\treturn\n\t\t}\n\n\t\tconst inProgressOption = statusField.options?.find(\n\t\t\t(o: { id: string; name: string }) => o.name === 'In Progress' || o.name === 'In progress'\n\t\t)\n\n\t\tif (!inProgressOption) {\n\t\t\tlogger.debug('No In Progress option found in Status field', { projectNumber: project.number })\n\t\t\treturn\n\t\t}\n\n\t\t// Update status\n\t\ttry {\n\t\t\tawait updateProjectItemField(\n\t\t\t\titem.id,\n\t\t\t\tproject.id,\n\t\t\t\tstatusField.id,\n\t\t\t\tinProgressOption.id\n\t\t\t)\n\n\t\t\tlogger.info('Updated issue status in project', {\n\t\t\t\tissueNumber,\n\t\t\t\tprojectNumber: project.number,\n\t\t\t})\n\t\t} catch (error) {\n\t\t\tlogger.debug('Could not update project item', { item: item.id, error })\n\t\t}\n\t}\n\n\t// Utility methods\n\tpublic extractContext(entity: Issue | PullRequest): string {\n\t\tif ('branch' in entity) {\n\t\t\t// It's a PullRequest\n\t\t\treturn `Pull Request #${entity.number}: ${entity.title}\\nBranch: ${entity.branch}\\nState: ${entity.state}`\n\t\t} else {\n\t\t\t// It's an Issue\n\t\t\treturn `GitHub Issue #${entity.number}: ${entity.title}\\nState: ${entity.state}`\n\t\t}\n\t}\n\n\tprivate mapGitHubIssueToIssue(ghIssue: GitHubIssue): Issue {\n\t\treturn {\n\t\t\tnumber: ghIssue.number,\n\t\t\ttitle: ghIssue.title,\n\t\t\tbody: ghIssue.body,\n\t\t\tstate: ghIssue.state.toLowerCase() as 'open' | 'closed',\n\t\t\tlabels: ghIssue.labels.map((l) => l.name),\n\t\t\tassignees: ghIssue.assignees.map((a) => a.login),\n\t\t\turl: ghIssue.url,\n\t\t}\n\t}\n\n\tprivate mapGitHubPRToPullRequest(ghPR: GitHubPullRequest): PullRequest {\n\t\treturn {\n\t\t\tnumber: ghPR.number,\n\t\t\ttitle: ghPR.title,\n\t\t\tbody: ghPR.body,\n\t\t\tstate: ghPR.state.toLowerCase() as 'open' | 'closed' | 'merged',\n\t\t\tbranch: ghPR.headRefName,\n\t\t\tbaseBranch: ghPR.baseRefName,\n\t\t\turl: ghPR.url,\n\t\t\tisDraft: ghPR.isDraft,\n\t\t}\n\t}\n\n\tprivate async promptUserConfirmation(message: string): Promise<boolean> {\n\t\treturn this.prompter(message)\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA+FO,IAAM,cAAN,cAA0B,MAAM;AAAA,EACtC,YACQ,MACP,SACO,SACN;AACD,UAAM,OAAO;AAJN;AAEA;AAGP,SAAK,OAAO;AAAA,EACb;AACD;;;AChFO,IAAM,gBAAN,MAA4C;AAAA,EAMlD,YAAY,SAET;AANH;AAAA,SAAS,eAAe;AACxB,SAAS,uBAAuB;AAO/B,SAAK,YAAW,mCAAS,aAAY;AAAA,EACtC;AAAA;AAAA,EAGA,MAAa,gBAAgB,OAAe,MAAoD;AAE/F,UAAM,cAAc,MAAM,MAAM,WAAW;AAE3C,QAAI,EAAC,2CAAc,KAAI;AACtB,aAAO,EAAE,MAAM,WAAW,YAAY,MAAM,UAAU,MAAM;AAAA,IAC7D;AAEA,UAAM,SAAS,SAAS,YAAY,CAAC,GAAG,EAAE;AAG1C,WAAO,MAAM,6BAA6B,EAAE,OAAO,CAAC;AACpD,UAAM,KAAK,MAAM,KAAK,UAAU,QAAQ,IAAI;AAC5C,QAAI,IAAI;AACP,aAAO,EAAE,MAAM,MAAM,YAAY,OAAO,SAAS,GAAG,UAAU,MAAM;AAAA,IACrE;AAGA,WAAO,MAAM,iCAAiC,EAAE,OAAO,CAAC;AACxD,UAAM,QAAQ,MAAM,KAAK,aAAa,QAAQ,IAAI;AAClD,QAAI,OAAO;AACV,aAAO,EAAE,MAAM,SAAS,YAAY,OAAO,SAAS,GAAG,UAAU,MAAM;AAAA,IACxE;AAGA,WAAO,EAAE,MAAM,WAAW,YAAY,MAAM,UAAU,MAAM;AAAA,EAC7D;AAAA;AAAA,EAGA,MAAa,WAAW,aAAqB,MAA+B;AAnE7E;AAoEE,QAAI;AACH,aAAO,MAAM,KAAK,mBAAmB,aAAa,IAAI;AAAA,IACvD,SAAS,OAAO;AAEf,UAAI,iBAAiB,SAAS,YAAY,WAAU,WAA4B,WAA5B,mBAAoC,SAAS,uBAAsB;AACtH,cAAM,IAAI;AAAA;AAAA,UAET,UAAU,WAAW;AAAA,UACrB;AAAA,QACD;AAAA,MACD;AAEA,YAAM;AAAA,IACP;AAAA,EACD;AAAA;AAAA,EAGA,MAAa,aAAa,aAAqB,MAAuC;AArFvF;AAsFE,QAAI;AACH,aAAO,MAAM,KAAK,mBAAmB,aAAa,IAAI;AAAA,IACvD,SAAS,OAAO;AAEf,UAAI,iBAAiB,SAAS,YAAY,WAAU,WAA4B,WAA5B,mBAAoC,SAAS,uBAAsB;AACtH,eAAO;AAAA,MACR;AAEA,YAAM;AAAA,IACP;AAAA,EACD;AAAA;AAAA,EAGA,MAAc,mBAAmB,aAAqB,MAA+B;AACpF,UAAM,UAAU,MAAM,aAAa,aAAa,IAAI;AACpD,WAAO,KAAK,sBAAsB,OAAO;AAAA,EAC1C;AAAA,EAEA,MAAa,mBAAmB,OAA6B;AAC5D,QAAI,MAAM,UAAU,UAAU;AAC7B,YAAM,WAAW,MAAM,KAAK;AAAA,QAC3B,UAAU,MAAM,MAAM;AAAA,MACvB;AACA,UAAI,CAAC,UAAU;AACd,cAAM,IAAI;AAAA;AAAA,UAET;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAAA;AAAA,EAGA,MAAa,QAAQ,UAAkB,MAAqC;AAvH7E;AAwHE,QAAI;AACH,aAAO,MAAM,KAAK,gBAAgB,UAAU,IAAI;AAAA,IACjD,SAAS,OAAO;AAEf,UAAI,iBAAiB,SAAS,YAAY,WAAU,WAA4B,WAA5B,mBAAoC,SAAS,uBAAsB;AACtH,cAAM,IAAI;AAAA;AAAA,UAET,OAAO,QAAQ;AAAA,UACf;AAAA,QACD;AAAA,MACD;AAEA,YAAM;AAAA,IACP;AAAA,EACD;AAAA;AAAA,EAGA,MAAa,UAAU,UAAkB,MAA6C;AAzIvF;AA0IE,QAAI;AACH,aAAO,MAAM,KAAK,gBAAgB,UAAU,IAAI;AAAA,IACjD,SAAS,OAAO;AAEf,UAAI,iBAAiB,SAAS,YAAY,WAAU,WAA4B,WAA5B,mBAAoC,SAAS,uBAAsB;AACtH,eAAO;AAAA,MACR;AAEA,YAAM;AAAA,IACP;AAAA,EACD;AAAA;AAAA,EAGA,MAAc,gBAAgB,UAAkB,MAAqC;AACpF,UAAM,OAAO,MAAM,UAAU,UAAU,IAAI;AAC3C,WAAO,KAAK,yBAAyB,IAAI;AAAA,EAC1C;AAAA,EAEA,MAAa,gBAAgB,IAAgC;AAC5D,QAAI,GAAG,UAAU,YAAY,GAAG,UAAU,UAAU;AACnD,YAAM,WAAW,MAAM,KAAK;AAAA,QAC3B,OAAO,GAAG,MAAM,OAAO,GAAG,KAAK;AAAA,MAChC;AACA,UAAI,CAAC,UAAU;AACd,cAAM,IAAI;AAAA;AAAA,UAET,yBAAyB,GAAG,KAAK;AAAA,QAClC;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAAA;AAAA,EAGA,MAAa,YACZ,OACA,MACA,YACA,QACoD;AAEpD,WAAO,YAAY,OAAO,MAAM,EAAE,MAAM,YAAY,OAAO,CAAC;AAAA,EAC7D;AAAA,EAEA,MAAa,YAAY,aAAqB,MAAgC;AAC7E,WAAO,MAAM,sBAAsB,EAAE,aAAa,KAAK,CAAC;AACxD,UAAM,QAAQ,MAAM,aAAa,aAAa,IAAI;AAClD,WAAO,MAAM;AAAA,EACd;AAAA;AAAA,EAGA,MAAa,sBAAsB,aAAoC;AAEtE,WAAO,KAAK,kDAAkD;AAAA,MAC7D;AAAA,IACD,CAAC;AAGD,QAAI,CAAE,MAAM,gBAAgB,GAAI;AAC/B,aAAO,KAAK,0CAA0C;AACtD,YAAM,IAAI;AAAA;AAAA,QAET;AAAA,MACD;AAAA,IACD;AAGA,QAAI;AACJ,QAAI;AACH,YAAM,WAAW,MAAM,iBAGpB,CAAC,QAAQ,QAAQ,UAAU,YAAY,CAAC;AAC3C,cAAQ,SAAS,MAAM;AAAA,IACxB,SAAS,OAAO;AACf,aAAO,KAAK,uCAAuC,EAAE,MAAM,CAAC;AAC5D;AAAA,IACD;AAGA,QAAI;AACJ,QAAI;AACH,iBAAW,MAAM,iBAAiB,KAAK;AAAA,IACxC,SAAS,OAAO;AACf,aAAO,KAAK,4BAA4B,EAAE,OAAO,MAAM,CAAC;AACxD;AAAA,IACD;AAEA,QAAI,CAAC,SAAS,QAAQ;AACrB,aAAO,KAAK,qBAAqB,EAAE,MAAM,CAAC;AAC1C;AAAA,IACD;AAGA,eAAW,WAAW,UAAU;AAC/B,YAAM,KAAK,2BAA2B,SAAS,aAAa,KAAK;AAAA,IAClE;AAAA,EACD;AAAA,EAEA,MAAc,2BACb,SACA,aACA,OACgB;AAhPlB;AAkPE,QAAI;AACJ,QAAI;AACH,cAAQ,MAAM,kBAAkB,QAAQ,QAAQ,KAAK;AAAA,IACtD,SAAS,OAAO;AACf,aAAO,MAAM,iCAAiC,EAAE,SAAS,QAAQ,QAAQ,MAAM,CAAC;AAChF;AAAA,IACD;AAGA,UAAM,OAAO,MAAM;AAAA,MAClB,CAAC,MACA,EAAE,QAAQ,SAAS,WAAW,EAAE,QAAQ,WAAW;AAAA,IACrD;AAEA,QAAI,CAAC,MAAM;AACV,aAAO,MAAM,8BAA8B;AAAA,QAC1C;AAAA,QACA,eAAe,QAAQ;AAAA,MACxB,CAAC;AACD;AAAA,IACD;AAGA,QAAI;AACJ,QAAI;AACH,mBAAa,MAAM,mBAAmB,QAAQ,QAAQ,KAAK;AAAA,IAC5D,SAAS,OAAO;AACf,aAAO,MAAM,kCAAkC,EAAE,SAAS,QAAQ,QAAQ,MAAM,CAAC;AACjF;AAAA,IACD;AAGA,UAAM,cAAc,WAAW,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,QAAQ;AACrE,QAAI,CAAC,aAAa;AACjB,aAAO,MAAM,oCAAoC,EAAE,eAAe,QAAQ,OAAO,CAAC;AAClF;AAAA,IACD;AAEA,UAAM,oBAAmB,iBAAY,YAAZ,mBAAqB;AAAA,MAC7C,CAAC,MAAoC,EAAE,SAAS,iBAAiB,EAAE,SAAS;AAAA;AAG7E,QAAI,CAAC,kBAAkB;AACtB,aAAO,MAAM,+CAA+C,EAAE,eAAe,QAAQ,OAAO,CAAC;AAC7F;AAAA,IACD;AAGA,QAAI;AACH,YAAM;AAAA,QACL,KAAK;AAAA,QACL,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,iBAAiB;AAAA,MAClB;AAEA,aAAO,KAAK,mCAAmC;AAAA,QAC9C;AAAA,QACA,eAAe,QAAQ;AAAA,MACxB,CAAC;AAAA,IACF,SAAS,OAAO;AACf,aAAO,MAAM,iCAAiC,EAAE,MAAM,KAAK,IAAI,MAAM,CAAC;AAAA,IACvE;AAAA,EACD;AAAA;AAAA,EAGO,eAAe,QAAqC;AAC1D,QAAI,YAAY,QAAQ;AAEvB,aAAO,iBAAiB,OAAO,MAAM,KAAK,OAAO,KAAK;AAAA,UAAa,OAAO,MAAM;AAAA,SAAY,OAAO,KAAK;AAAA,IACzG,OAAO;AAEN,aAAO,iBAAiB,OAAO,MAAM,KAAK,OAAO,KAAK;AAAA,SAAY,OAAO,KAAK;AAAA,IAC/E;AAAA,EACD;AAAA,EAEQ,sBAAsB,SAA6B;AAC1D,WAAO;AAAA,MACN,QAAQ,QAAQ;AAAA,MAChB,OAAO,QAAQ;AAAA,MACf,MAAM,QAAQ;AAAA,MACd,OAAO,QAAQ,MAAM,YAAY;AAAA,MACjC,QAAQ,QAAQ,OAAO,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,MACxC,WAAW,QAAQ,UAAU,IAAI,CAAC,MAAM,EAAE,KAAK;AAAA,MAC/C,KAAK,QAAQ;AAAA,IACd;AAAA,EACD;AAAA,EAEQ,yBAAyB,MAAsC;AACtE,WAAO;AAAA,MACN,QAAQ,KAAK;AAAA,MACb,OAAO,KAAK;AAAA,MACZ,MAAM,KAAK;AAAA,MACX,OAAO,KAAK,MAAM,YAAY;AAAA,MAC9B,QAAQ,KAAK;AAAA,MACb,YAAY,KAAK;AAAA,MACjB,KAAK,KAAK;AAAA,MACV,SAAS,KAAK;AAAA,IACf;AAAA,EACD;AAAA,EAEA,MAAc,uBAAuB,SAAmC;AACvE,WAAO,KAAK,SAAS,OAAO;AAAA,EAC7B;AACD;","names":[]}
@@ -117,28 +117,67 @@ function loadEnvIntoProcess(options) {
117
117
  return returnValue;
118
118
  }
119
119
 
120
- // src/utils/port.ts
121
- import { createHash } from "crypto";
122
- function generatePortOffsetFromBranchName(branchName) {
123
- if (!branchName || branchName.trim().length === 0) {
124
- throw new Error("Branch name cannot be empty");
120
+ // src/utils/IdentifierParser.ts
121
+ var IdentifierParser = class {
122
+ constructor(gitWorktreeManager) {
123
+ this.gitWorktreeManager = gitWorktreeManager;
125
124
  }
126
- const hash = createHash("sha256").update(branchName).digest("hex");
127
- const hashPrefix = hash.slice(0, 8);
128
- const hashAsInt = parseInt(hashPrefix, 16);
129
- const portOffset = hashAsInt % 999 + 1;
130
- return portOffset;
131
- }
132
- function calculatePortForBranch(branchName, basePort = 3e3) {
133
- const offset = generatePortOffsetFromBranchName(branchName);
134
- const port = basePort + offset;
135
- if (port > 65535) {
136
- throw new Error(
137
- `Calculated port ${port} exceeds maximum (65535). Use a lower base port (current: ${basePort}).`
138
- );
125
+ /**
126
+ * Parse identifier using pattern-based detection on existing worktrees.
127
+ * Does NOT make GitHub API calls - only checks local worktree patterns.
128
+ *
129
+ * @param identifier - The identifier to parse (e.g., "42", "#66", "ENG-123", "my-branch")
130
+ * @returns ParsedInput with type, number/branchName, and originalInput
131
+ * @throws Error if no matching worktree is found
132
+ */
133
+ async parseForPatternDetection(identifier) {
134
+ const cleanId = identifier.replace(/^#/, "").trim();
135
+ const originalInput = identifier;
136
+ const numericMatch = cleanId.match(/^(\d+)$/);
137
+ if (numericMatch == null ? void 0 : numericMatch[1]) {
138
+ const number = parseInt(numericMatch[1], 10);
139
+ const prWorktree = await this.gitWorktreeManager.findWorktreeForPR(number, "");
140
+ if (prWorktree) {
141
+ return {
142
+ type: "pr",
143
+ number,
144
+ originalInput
145
+ };
146
+ }
147
+ const issueWorktree = await this.gitWorktreeManager.findWorktreeForIssue(number);
148
+ if (issueWorktree) {
149
+ return {
150
+ type: "issue",
151
+ number,
152
+ originalInput
153
+ };
154
+ }
155
+ throw new Error(`No worktree found for identifier: ${identifier}`);
156
+ }
157
+ const alphanumericMatch = cleanId.match(/^([A-Za-z]+-\d+)$/);
158
+ if (alphanumericMatch == null ? void 0 : alphanumericMatch[1]) {
159
+ const alphanumericId = alphanumericMatch[1];
160
+ const issueWorktree = await this.gitWorktreeManager.findWorktreeForIssue(alphanumericId);
161
+ if (issueWorktree) {
162
+ return {
163
+ type: "issue",
164
+ number: alphanumericId,
165
+ originalInput
166
+ };
167
+ }
168
+ throw new Error(`No worktree found for identifier: ${identifier}`);
169
+ }
170
+ const branchWorktree = await this.gitWorktreeManager.findWorktreeForBranch(cleanId);
171
+ if (branchWorktree) {
172
+ return {
173
+ type: "branch",
174
+ branchName: cleanId,
175
+ originalInput
176
+ };
177
+ }
178
+ throw new Error(`No worktree found for identifier: ${identifier}`);
139
179
  }
140
- return port;
141
- }
180
+ };
142
181
 
143
182
  export {
144
183
  parseEnvFile,
@@ -146,6 +185,6 @@ export {
146
185
  validateEnvVariable,
147
186
  extractPort,
148
187
  loadEnvIntoProcess,
149
- calculatePortForBranch
188
+ IdentifierParser
150
189
  };
151
- //# sourceMappingURL=chunk-ZMNQBJUI.js.map
190
+ //# sourceMappingURL=chunk-IP7SMKIF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/env.ts","../src/utils/IdentifierParser.ts"],"sourcesContent":["import dotenvFlow, { type DotenvFlowConfigOptions } from 'dotenv-flow'\nimport { logger } from './logger.js'\n\n/**\n * Parse .env file content into key-value map\n * Handles comments, empty lines, quoted/unquoted values, multiline values\n */\nexport function parseEnvFile(content: string): Map<string, string> {\n const envMap = new Map<string, string>()\n const lines = content.split('\\n')\n\n for (const line of lines) {\n const trimmedLine = line.trim()\n\n // Skip empty lines and comments\n if (!trimmedLine || trimmedLine.startsWith('#')) {\n continue\n }\n\n // Remove 'export ' prefix if present\n const cleanLine = trimmedLine.startsWith('export ')\n ? trimmedLine.substring(7)\n : trimmedLine\n\n // Find the first equals sign\n const equalsIndex = cleanLine.indexOf('=')\n if (equalsIndex === -1) {\n continue\n }\n\n const key = cleanLine.substring(0, equalsIndex).trim()\n let value = cleanLine.substring(equalsIndex + 1)\n\n // Handle quoted values\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.substring(1, value.length - 1)\n // Unescape quotes\n value = value.replace(/\\\\\"/g, '\"').replace(/\\\\'/g, \"'\")\n // Unescape newlines\n value = value.replace(/\\\\n/g, '\\n')\n }\n\n if (key) {\n envMap.set(key, value)\n }\n }\n\n return envMap\n}\n\n/**\n * Format environment variable as line for .env file\n * Always quotes values and escapes internal quotes\n */\nexport function formatEnvLine(key: string, value: string): string {\n // Escape quotes and newlines in the value\n const escapedValue = value\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\r/g, '\\\\r')\n\n return `${key}=\"${escapedValue}\"`\n}\n\n/**\n * Validate environment variable name and value\n */\nexport function validateEnvVariable(\n key: string,\n _value?: string\n): { valid: boolean; error?: string } {\n if (!key || key.length === 0) {\n return {\n valid: false,\n error: 'Environment variable key cannot be empty',\n }\n }\n\n if (!isValidEnvKey(key)) {\n return {\n valid: false,\n error: `Invalid environment variable name: ${key}. Must start with a letter or underscore and contain only letters, numbers, and underscores.`,\n }\n }\n\n // Values can be any string, including empty\n return { valid: true }\n}\n\n/**\n * Normalize line endings for cross-platform compatibility\n */\nexport function normalizeLineEndings(content: string): string {\n return content.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n')\n}\n\n/**\n * Extract port from .env file if present\n */\nexport function extractPort(envContent: Map<string, string>): number | null {\n const portValue = envContent.get('PORT')\n if (!portValue) {\n return null\n }\n\n const port = parseInt(portValue, 10)\n if (isNaN(port)) {\n return null\n }\n\n return port\n}\n\n/**\n * Check if environment variable key is valid\n */\nexport function isValidEnvKey(key: string): boolean {\n if (!key || key.length === 0) {\n return false\n }\n\n // Must start with letter or underscore, followed by letters, numbers, or underscores\n const validKeyRegex = /^[A-Za-z_][A-Za-z0-9_]*$/\n return validKeyRegex.test(key)\n}\n\n/**\n * Load environment variables using dotenv-flow\n * Supports environment-specific files (.env.development, .env.production, etc.)\n * and local overrides (.env.local, .env.development.local)\n */\nexport function loadEnvIntoProcess(options?: {\n path?: string\n nodeEnv?: string\n defaultNodeEnv?: string\n}): { parsed?: Record<string, string>; error?: Error } {\n logger.debug('Loading environment variables with dotenv-flow', {\n options: {\n path: options?.path ?? 'current working directory',\n nodeEnv: options?.nodeEnv ?? 'not specified',\n defaultNodeEnv: options?.defaultNodeEnv ?? 'development (default)'\n }\n })\n\n const configOptions: Partial<DotenvFlowConfigOptions> = {\n silent: true, // Don't throw errors if .env files are missing\n }\n\n // Only add defined values to avoid TypeScript strict type issues\n if (options?.path !== undefined) {\n configOptions.path = options.path\n logger.debug(`Using custom path: ${options.path}`)\n }\n if (options?.nodeEnv !== undefined) {\n configOptions.node_env = options.nodeEnv\n logger.debug(`Using NODE_ENV: ${options.nodeEnv}`)\n }\n if (options?.defaultNodeEnv !== undefined) {\n configOptions.default_node_env = options.defaultNodeEnv\n logger.debug(`Using default NODE_ENV: ${options.defaultNodeEnv}`)\n } else {\n configOptions.default_node_env = 'development'\n logger.debug('Using default NODE_ENV: development')\n }\n\n logger.debug('dotenv-flow config options:', configOptions)\n\n const result = dotenvFlow.config(configOptions)\n\n const returnValue: { parsed?: Record<string, string>; error?: Error } = {}\n\n if (result.parsed) {\n returnValue.parsed = result.parsed as Record<string, string>\n const variableCount = Object.keys(result.parsed).length\n logger.debug(`Successfully loaded ${variableCount} environment variables`)\n } else {\n logger.debug('No environment variables were parsed')\n }\n\n if (result.error) {\n returnValue.error = result.error\n logger.debug('dotenv-flow returned an error', {\n error: result.error.message,\n name: result.error.name\n })\n } else {\n logger.debug('dotenv-flow completed without errors')\n }\n\n return returnValue\n}\n\n/**\n * Load environment variables for a specific workspace\n * Automatically determines environment based on NODE_ENV or defaults to development\n */\nexport function loadWorkspaceEnv(workspacePath: string): {\n parsed?: Record<string, string>\n error?: Error\n} {\n const nodeEnv = process.env.NODE_ENV ?? 'development'\n\n logger.debug('Loading workspace environment variables', {\n workspacePath,\n detectedNodeEnv: nodeEnv,\n processNodeEnv: process.env.NODE_ENV ?? 'not set'\n })\n\n return loadEnvIntoProcess({\n path: workspacePath,\n nodeEnv: nodeEnv,\n defaultNodeEnv: 'development'\n })\n}\n","import type { ParsedInput } from '../commands/start.js'\nimport type { GitWorktreeManager } from '../lib/GitWorktreeManager.js'\n\n/**\n * IdentifierParser provides consistent identifier parsing across commands\n * using pattern-based detection without GitHub API calls.\n *\n * Detection Strategy:\n * 1. For numeric input (e.g., \"42\", \"#66\"):\n * - Check for PR worktree first (_pr_N pattern in path)\n * - Then check for issue worktree (issue-N pattern in branch)\n * 2. For non-numeric input:\n * - Treat as branch name and verify worktree exists\n *\n * This ensures:\n * - No unnecessary GitHub API calls\n * - Consistent behavior across finish/cleanup commands\n * - PR detection takes priority over issue detection\n */\nexport class IdentifierParser {\n\tconstructor(private gitWorktreeManager: GitWorktreeManager) {}\n\n\t/**\n\t * Parse identifier using pattern-based detection on existing worktrees.\n\t * Does NOT make GitHub API calls - only checks local worktree patterns.\n\t *\n\t * @param identifier - The identifier to parse (e.g., \"42\", \"#66\", \"ENG-123\", \"my-branch\")\n\t * @returns ParsedInput with type, number/branchName, and originalInput\n\t * @throws Error if no matching worktree is found\n\t */\n\tasync parseForPatternDetection(identifier: string): Promise<ParsedInput> {\n\t\t// Remove # prefix if present and trim whitespace\n\t\tconst cleanId = identifier.replace(/^#/, '').trim()\n\t\tconst originalInput = identifier\n\n\t\t// Check if input is numeric (GitHub-style issue/PR numbers)\n\t\tconst numericMatch = cleanId.match(/^(\\d+)$/)\n\n\t\tif (numericMatch?.[1]) {\n\t\t\tconst number = parseInt(numericMatch[1], 10)\n\n\t\t\t// Priority 1: Check for PR worktree (_pr_N pattern)\n\t\t\t// Pass empty string for branch name since we don't know it yet\n\t\t\tconst prWorktree = await this.gitWorktreeManager.findWorktreeForPR(number, '')\n\t\t\tif (prWorktree) {\n\t\t\t\treturn {\n\t\t\t\t\ttype: 'pr',\n\t\t\t\t\tnumber,\n\t\t\t\t\toriginalInput,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Priority 2: Check for issue worktree (issue-N pattern)\n\t\t\tconst issueWorktree = await this.gitWorktreeManager.findWorktreeForIssue(number)\n\t\t\tif (issueWorktree) {\n\t\t\t\treturn {\n\t\t\t\t\ttype: 'issue',\n\t\t\t\t\tnumber,\n\t\t\t\t\toriginalInput,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// No matching worktree found for numeric input\n\t\t\tthrow new Error(`No worktree found for identifier: ${identifier}`)\n\t\t}\n\n\t\t// Check if input is alphanumeric issue identifier (Linear/Jira-style: ABC-123, ENG-42)\n\t\tconst alphanumericMatch = cleanId.match(/^([A-Za-z]+-\\d+)$/)\n\n\t\tif (alphanumericMatch?.[1]) {\n\t\t\tconst alphanumericId = alphanumericMatch[1]\n\n\t\t\t// Check for issue worktree with alphanumeric identifier\n\t\t\tconst issueWorktree = await this.gitWorktreeManager.findWorktreeForIssue(alphanumericId)\n\t\t\tif (issueWorktree) {\n\t\t\t\treturn {\n\t\t\t\t\ttype: 'issue',\n\t\t\t\t\tnumber: alphanumericId,\n\t\t\t\t\toriginalInput,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// No matching worktree found for alphanumeric identifier\n\t\t\tthrow new Error(`No worktree found for identifier: ${identifier}`)\n\t\t}\n\n\t\t// Non-numeric/non-alphanumeric input: treat as branch name\n\t\tconst branchWorktree = await this.gitWorktreeManager.findWorktreeForBranch(cleanId)\n\t\tif (branchWorktree) {\n\t\t\treturn {\n\t\t\t\ttype: 'branch',\n\t\t\t\tbranchName: cleanId,\n\t\t\t\toriginalInput,\n\t\t\t}\n\t\t}\n\n\t\t// No matching worktree found for branch name\n\t\tthrow new Error(`No worktree found for identifier: ${identifier}`)\n\t}\n}\n"],"mappings":"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","names":[]}
@@ -69,10 +69,14 @@ async function waitForKeypress(message = "Press any key to continue...") {
69
69
  });
70
70
  });
71
71
  }
72
+ function isInteractiveEnvironment() {
73
+ return process.stdin.isTTY === true && process.env.CI !== "true";
74
+ }
72
75
 
73
76
  export {
74
77
  promptConfirmation,
75
78
  promptInput,
76
- waitForKeypress
79
+ waitForKeypress,
80
+ isInteractiveEnvironment
77
81
  };
78
- //# sourceMappingURL=chunk-JNKJ7NJV.js.map
82
+ //# sourceMappingURL=chunk-JKXJ7BGL.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/prompt.ts"],"sourcesContent":["import * as readline from 'node:readline'\nimport { logger } from './logger.js'\n\n/**\n * Prompt user for confirmation (yes/no)\n * @param message The question to ask the user\n * @param defaultValue Default value if user just presses enter (default: false)\n * @returns Promise<boolean> - true if user confirms, false otherwise\n */\nexport async function promptConfirmation(\n\tmessage: string,\n\tdefaultValue = false\n): Promise<boolean> {\n\tconst rl = readline.createInterface({\n\t\tinput: process.stdin,\n\t\toutput: process.stdout,\n\t})\n\n\tconst suffix = defaultValue ? '[Y/n]' : '[y/N]'\n\tconst fullMessage = `${message} ${suffix}: `\n\n\treturn new Promise((resolve) => {\n\t\trl.question(fullMessage, (answer) => {\n\t\t\trl.close()\n\n\t\t\tconst normalized = answer.trim().toLowerCase()\n\n\t\t\tif (normalized === '') {\n\t\t\t\tresolve(defaultValue)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (normalized === 'y' || normalized === 'yes') {\n\t\t\t\tresolve(true)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (normalized === 'n' || normalized === 'no') {\n\t\t\t\tresolve(false)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Invalid input, use default\n\t\t\tlogger.warn('Invalid input, using default value', {\n\t\t\t\tinput: answer,\n\t\t\t\tdefaultValue,\n\t\t\t})\n\t\t\tresolve(defaultValue)\n\t\t})\n\t})\n}\n\n/**\n * Prompt user for text input\n * @param message The prompt message\n * @param defaultValue Optional default value\n * @returns Promise<string> - the user's input\n */\nexport async function promptInput(\n\tmessage: string,\n\tdefaultValue?: string\n): Promise<string> {\n\tconst rl = readline.createInterface({\n\t\tinput: process.stdin,\n\t\toutput: process.stdout,\n\t})\n\n\tconst suffix = defaultValue ? ` [${defaultValue}]` : ''\n\tconst fullMessage = `${message}${suffix}: `\n\n\treturn new Promise((resolve) => {\n\t\trl.question(fullMessage, (answer) => {\n\t\t\trl.close()\n\n\t\t\tconst trimmed = answer.trim()\n\n\t\t\tif (trimmed === '' && defaultValue !== undefined) {\n\t\t\t\tresolve(defaultValue)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresolve(trimmed)\n\t\t})\n\t})\n}\n\n/**\n * Wait for the user to press any key\n * @param message Optional message to display (default: \"Press any key to continue...\")\n * @returns Promise<string> - resolves with the key that was pressed\n */\nexport async function waitForKeypress(\n\tmessage = 'Press any key to continue...'\n): Promise<string> {\n\t// Display message first\n\tprocess.stdout.write(message)\n\n\treturn new Promise((resolve) => {\n\t\t// Enable raw mode to capture single keypresses\n\t\tprocess.stdin.setRawMode(true)\n\t\tprocess.stdin.resume()\n\n\t\t// Listen for single data event\n\t\tprocess.stdin.once('data', (chunk: Buffer) => {\n\t\t\t// Restore normal mode\n\t\t\tprocess.stdin.setRawMode(false)\n\t\t\tprocess.stdin.pause()\n\n\t\t\t// Add newline after keypress for clean output\n\t\t\tprocess.stdout.write('\\n')\n\n\t\t\t// Convert buffer to string and return the key\n\t\t\tconst key = chunk.toString('utf8')\n\t\t\tresolve(key)\n\t\t})\n\t})\n}\n"],"mappings":";;;;;;AAAA,YAAY,cAAc;AAS1B,eAAsB,mBACrB,SACA,eAAe,OACI;AACnB,QAAM,KAAc,yBAAgB;AAAA,IACnC,OAAO,QAAQ;AAAA,IACf,QAAQ,QAAQ;AAAA,EACjB,CAAC;AAED,QAAM,SAAS,eAAe,UAAU;AACxC,QAAM,cAAc,GAAG,OAAO,IAAI,MAAM;AAExC,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC/B,OAAG,SAAS,aAAa,CAAC,WAAW;AACpC,SAAG,MAAM;AAET,YAAM,aAAa,OAAO,KAAK,EAAE,YAAY;AAE7C,UAAI,eAAe,IAAI;AACtB,gBAAQ,YAAY;AACpB;AAAA,MACD;AAEA,UAAI,eAAe,OAAO,eAAe,OAAO;AAC/C,gBAAQ,IAAI;AACZ;AAAA,MACD;AAEA,UAAI,eAAe,OAAO,eAAe,MAAM;AAC9C,gBAAQ,KAAK;AACb;AAAA,MACD;AAGA,aAAO,KAAK,sCAAsC;AAAA,QACjD,OAAO;AAAA,QACP;AAAA,MACD,CAAC;AACD,cAAQ,YAAY;AAAA,IACrB,CAAC;AAAA,EACF,CAAC;AACF;AAQA,eAAsB,YACrB,SACA,cACkB;AAClB,QAAM,KAAc,yBAAgB;AAAA,IACnC,OAAO,QAAQ;AAAA,IACf,QAAQ,QAAQ;AAAA,EACjB,CAAC;AAED,QAAM,SAAS,eAAe,KAAK,YAAY,MAAM;AACrD,QAAM,cAAc,GAAG,OAAO,GAAG,MAAM;AAEvC,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC/B,OAAG,SAAS,aAAa,CAAC,WAAW;AACpC,SAAG,MAAM;AAET,YAAM,UAAU,OAAO,KAAK;AAE5B,UAAI,YAAY,MAAM,iBAAiB,QAAW;AACjD,gBAAQ,YAAY;AACpB;AAAA,MACD;AAEA,cAAQ,OAAO;AAAA,IAChB,CAAC;AAAA,EACF,CAAC;AACF;AAOA,eAAsB,gBACrB,UAAU,gCACQ;AAElB,UAAQ,OAAO,MAAM,OAAO;AAE5B,SAAO,IAAI,QAAQ,CAAC,YAAY;AAE/B,YAAQ,MAAM,WAAW,IAAI;AAC7B,YAAQ,MAAM,OAAO;AAGrB,YAAQ,MAAM,KAAK,QAAQ,CAAC,UAAkB;AAE7C,cAAQ,MAAM,WAAW,KAAK;AAC9B,cAAQ,MAAM,MAAM;AAGpB,cAAQ,OAAO,MAAM,IAAI;AAGzB,YAAM,MAAM,MAAM,SAAS,MAAM;AACjC,cAAQ,GAAG;AAAA,IACZ,CAAC;AAAA,EACF,CAAC;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/utils/prompt.ts"],"sourcesContent":["import * as readline from 'node:readline'\nimport { logger } from './logger.js'\n\n/**\n * Prompt user for confirmation (yes/no)\n * @param message The question to ask the user\n * @param defaultValue Default value if user just presses enter (default: false)\n * @returns Promise<boolean> - true if user confirms, false otherwise\n */\nexport async function promptConfirmation(\n\tmessage: string,\n\tdefaultValue = false\n): Promise<boolean> {\n\tconst rl = readline.createInterface({\n\t\tinput: process.stdin,\n\t\toutput: process.stdout,\n\t})\n\n\tconst suffix = defaultValue ? '[Y/n]' : '[y/N]'\n\tconst fullMessage = `${message} ${suffix}: `\n\n\treturn new Promise((resolve) => {\n\t\trl.question(fullMessage, (answer) => {\n\t\t\trl.close()\n\n\t\t\tconst normalized = answer.trim().toLowerCase()\n\n\t\t\tif (normalized === '') {\n\t\t\t\tresolve(defaultValue)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (normalized === 'y' || normalized === 'yes') {\n\t\t\t\tresolve(true)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (normalized === 'n' || normalized === 'no') {\n\t\t\t\tresolve(false)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Invalid input, use default\n\t\t\tlogger.warn('Invalid input, using default value', {\n\t\t\t\tinput: answer,\n\t\t\t\tdefaultValue,\n\t\t\t})\n\t\t\tresolve(defaultValue)\n\t\t})\n\t})\n}\n\n/**\n * Prompt user for text input\n * @param message The prompt message\n * @param defaultValue Optional default value\n * @returns Promise<string> - the user's input\n */\nexport async function promptInput(\n\tmessage: string,\n\tdefaultValue?: string\n): Promise<string> {\n\tconst rl = readline.createInterface({\n\t\tinput: process.stdin,\n\t\toutput: process.stdout,\n\t})\n\n\tconst suffix = defaultValue ? ` [${defaultValue}]` : ''\n\tconst fullMessage = `${message}${suffix}: `\n\n\treturn new Promise((resolve) => {\n\t\trl.question(fullMessage, (answer) => {\n\t\t\trl.close()\n\n\t\t\tconst trimmed = answer.trim()\n\n\t\t\tif (trimmed === '' && defaultValue !== undefined) {\n\t\t\t\tresolve(defaultValue)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresolve(trimmed)\n\t\t})\n\t})\n}\n\n/**\n * Wait for the user to press any key\n * @param message Optional message to display (default: \"Press any key to continue...\")\n * @returns Promise<string> - resolves with the key that was pressed\n */\nexport async function waitForKeypress(\n\tmessage = 'Press any key to continue...'\n): Promise<string> {\n\t// Display message first\n\tprocess.stdout.write(message)\n\n\treturn new Promise((resolve) => {\n\t\t// Enable raw mode to capture single keypresses\n\t\tprocess.stdin.setRawMode(true)\n\t\tprocess.stdin.resume()\n\n\t\t// Listen for single data event\n\t\tprocess.stdin.once('data', (chunk: Buffer) => {\n\t\t\t// Restore normal mode\n\t\t\tprocess.stdin.setRawMode(false)\n\t\t\tprocess.stdin.pause()\n\n\t\t\t// Add newline after keypress for clean output\n\t\t\tprocess.stdout.write('\\n')\n\n\t\t\t// Convert buffer to string and return the key\n\t\t\tconst key = chunk.toString('utf8')\n\t\t\tresolve(key)\n\t\t})\n\t})\n}\n\n/**\n * Check if running in an interactive environment\n * Returns false if CI environment or no TTY\n */\nexport function isInteractiveEnvironment(): boolean {\n\treturn process.stdin.isTTY === true && process.env.CI !== 'true'\n}\n"],"mappings":";;;;;;AAAA,YAAY,cAAc;AAS1B,eAAsB,mBACrB,SACA,eAAe,OACI;AACnB,QAAM,KAAc,yBAAgB;AAAA,IACnC,OAAO,QAAQ;AAAA,IACf,QAAQ,QAAQ;AAAA,EACjB,CAAC;AAED,QAAM,SAAS,eAAe,UAAU;AACxC,QAAM,cAAc,GAAG,OAAO,IAAI,MAAM;AAExC,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC/B,OAAG,SAAS,aAAa,CAAC,WAAW;AACpC,SAAG,MAAM;AAET,YAAM,aAAa,OAAO,KAAK,EAAE,YAAY;AAE7C,UAAI,eAAe,IAAI;AACtB,gBAAQ,YAAY;AACpB;AAAA,MACD;AAEA,UAAI,eAAe,OAAO,eAAe,OAAO;AAC/C,gBAAQ,IAAI;AACZ;AAAA,MACD;AAEA,UAAI,eAAe,OAAO,eAAe,MAAM;AAC9C,gBAAQ,KAAK;AACb;AAAA,MACD;AAGA,aAAO,KAAK,sCAAsC;AAAA,QACjD,OAAO;AAAA,QACP;AAAA,MACD,CAAC;AACD,cAAQ,YAAY;AAAA,IACrB,CAAC;AAAA,EACF,CAAC;AACF;AAQA,eAAsB,YACrB,SACA,cACkB;AAClB,QAAM,KAAc,yBAAgB;AAAA,IACnC,OAAO,QAAQ;AAAA,IACf,QAAQ,QAAQ;AAAA,EACjB,CAAC;AAED,QAAM,SAAS,eAAe,KAAK,YAAY,MAAM;AACrD,QAAM,cAAc,GAAG,OAAO,GAAG,MAAM;AAEvC,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC/B,OAAG,SAAS,aAAa,CAAC,WAAW;AACpC,SAAG,MAAM;AAET,YAAM,UAAU,OAAO,KAAK;AAE5B,UAAI,YAAY,MAAM,iBAAiB,QAAW;AACjD,gBAAQ,YAAY;AACpB;AAAA,MACD;AAEA,cAAQ,OAAO;AAAA,IAChB,CAAC;AAAA,EACF,CAAC;AACF;AAOA,eAAsB,gBACrB,UAAU,gCACQ;AAElB,UAAQ,OAAO,MAAM,OAAO;AAE5B,SAAO,IAAI,QAAQ,CAAC,YAAY;AAE/B,YAAQ,MAAM,WAAW,IAAI;AAC7B,YAAQ,MAAM,OAAO;AAGrB,YAAQ,MAAM,KAAK,QAAQ,CAAC,UAAkB;AAE7C,cAAQ,MAAM,WAAW,KAAK;AAC9B,cAAQ,MAAM,MAAM;AAGpB,cAAQ,OAAO,MAAM,IAAI;AAGzB,YAAM,MAAM,MAAM,SAAS,MAAM;AACjC,cAAQ,GAAG;AAAA,IACZ,CAAC;AAAA,EACF,CAAC;AACF;AAMO,SAAS,2BAAoC;AACnD,SAAO,QAAQ,MAAM,UAAU,QAAQ,QAAQ,IAAI,OAAO;AAC3D;","names":[]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  promptConfirmation
4
- } from "./chunk-JNKJ7NJV.js";
4
+ } from "./chunk-JKXJ7BGL.js";
5
5
  import {
6
6
  createLogger
7
7
  } from "./chunk-GEHQXLEI.js";
@@ -215,9 +215,29 @@ var NeonProvider = class {
215
215
  }
216
216
  return null;
217
217
  }
218
+ /**
219
+ * Remove expiration date from a Neon branch
220
+ * Used when parent branches need to have child branches created
221
+ * Neon limitation: "Branches with an expiration date cannot have child branches"
222
+ *
223
+ * @param branchName - Name of the branch to remove expiration from
224
+ * @param cwd - Optional working directory to run commands from
225
+ */
226
+ async removeExpiration(branchName, cwd) {
227
+ logger.info(`Removing expiration date from branch: ${branchName}`);
228
+ await this.executeNeonCommand([
229
+ "branches",
230
+ "set-expiration",
231
+ branchName,
232
+ "--project-id",
233
+ this.config.projectId
234
+ ], cwd);
235
+ logger.success(`Expiration date removed from ${branchName}`);
236
+ }
218
237
  /**
219
238
  * Create a new database branch
220
239
  * ALWAYS checks for Vercel preview database first
240
+ * Handles Neon limitation where parent branches with expiration dates cannot have children
221
241
  * Returns connection string for the branch
222
242
  * Ports: create_neon_database_branch() from bash/utils/neon-utils.sh:126-187
223
243
  *
@@ -237,16 +257,37 @@ var NeonProvider = class {
237
257
  logger.info("Creating Neon database branch...");
238
258
  logger.info(` Source branch: ${parentBranch}`);
239
259
  logger.info(` New branch: ${sanitizedName}`);
240
- await this.executeNeonCommand([
241
- "branches",
242
- "create",
243
- "--name",
244
- sanitizedName,
245
- "--parent",
246
- parentBranch,
247
- "--project-id",
248
- this.config.projectId
249
- ], cwd);
260
+ try {
261
+ await this.executeNeonCommand([
262
+ "branches",
263
+ "create",
264
+ "--name",
265
+ sanitizedName,
266
+ "--parent",
267
+ parentBranch,
268
+ "--project-id",
269
+ this.config.projectId
270
+ ], cwd);
271
+ } catch (error) {
272
+ const errorMessage = error instanceof Error ? error.message : String(error);
273
+ if (errorMessage.toLowerCase().includes("expiration") && errorMessage.toLowerCase().includes("child")) {
274
+ logger.warn("Parent branch has an expiration date - removing it to allow child branch creation");
275
+ await this.removeExpiration(parentBranch, cwd);
276
+ logger.info("Retrying database branch creation...");
277
+ await this.executeNeonCommand([
278
+ "branches",
279
+ "create",
280
+ "--name",
281
+ sanitizedName,
282
+ "--parent",
283
+ parentBranch,
284
+ "--project-id",
285
+ this.config.projectId
286
+ ], cwd);
287
+ } else {
288
+ throw error;
289
+ }
290
+ }
250
291
  logger.success("Database branch created successfully");
251
292
  logger.info("Getting connection string for new database branch...");
252
293
  const connectionString = await this.getConnectionString(sanitizedName, cwd);
@@ -375,6 +416,21 @@ var NeonProvider = class {
375
416
  }
376
417
  return null;
377
418
  }
419
+ /**
420
+ * Get branch name from a connection string (reverse lookup)
421
+ * Extracts endpoint ID from connection string and finds matching branch
422
+ *
423
+ * @param connectionString - Neon connection string (e.g., postgres://...@ep-abc-123.region.neon.tech/...)
424
+ * @param cwd - Optional working directory to run commands from
425
+ */
426
+ async getBranchNameFromConnectionString(connectionString, cwd) {
427
+ const endpointId = this.extractEndpointId(connectionString);
428
+ if (!endpointId) {
429
+ logger.debug("Could not extract endpoint ID from connection string");
430
+ return null;
431
+ }
432
+ return this.getBranchNameFromEndpoint(endpointId, cwd);
433
+ }
378
434
  };
379
435
 
380
436
  // src/utils/neon-helpers.ts
@@ -390,4 +446,4 @@ function createNeonProviderFromSettings(settings) {
390
446
  export {
391
447
  createNeonProviderFromSettings
392
448
  };
393
- //# sourceMappingURL=chunk-LAPY6NAE.js.map
449
+ //# sourceMappingURL=chunk-JQFO7QQN.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/providers/NeonProvider.ts","../src/utils/neon-helpers.ts"],"sourcesContent":["import { execa, type ExecaError } from 'execa'\nimport type { DatabaseProvider } from '../../types/index.js'\nimport { createLogger } from '../../utils/logger.js'\nimport { promptConfirmation } from '../../utils/prompt.js'\n\nconst logger = createLogger({ prefix: '🗂️' })\n\ninterface NeonBranch {\n name: string\n id: string\n [key: string]: unknown\n}\n\nexport interface NeonConfig {\n projectId: string\n parentBranch: string\n}\n\n/**\n * Validate Neon configuration\n * Checks that required configuration values are present\n */\nexport function validateNeonConfig(config: {\n projectId?: string\n parentBranch?: string\n}): { valid: boolean; error?: string } {\n if (!config.projectId) {\n return {\n valid: false,\n error: 'Neon projectId is required. Configure in .iloom/settings.json under databaseProviders.neon',\n }\n }\n\n if (!config.parentBranch) {\n return {\n valid: false,\n error: 'Neon parentBranch is required. Configure in .iloom/settings.json under databaseProviders.neon',\n }\n }\n\n // Basic validation for project ID format (should start with appropriate prefix)\n if (!/^[a-zA-Z0-9-]+$/.test(config.projectId)) {\n return {\n valid: false,\n error: 'Neon projectId contains invalid characters',\n }\n }\n\n return { valid: true }\n}\n\n/**\n * Neon database provider implementation\n * Ports functionality from bash/utils/neon-utils.sh\n */\nexport class NeonProvider implements DatabaseProvider {\n private _isConfigured: boolean = false\n\n constructor(private config: NeonConfig) {\n logger.debug('NeonProvider initialized with config:', {\n projectId: config.projectId,\n parentBranch: config.parentBranch,\n hasProjectId: !!config.projectId,\n hasParentBranch: !!config.parentBranch,\n })\n\n // Validate config but don't throw - just mark as not configured\n // This allows the provider to be instantiated even when Neon is not being used\n const validation = validateNeonConfig(config)\n if (!validation.valid) {\n logger.debug(`NeonProvider not configured: ${validation.error}`)\n logger.debug('Neon database branching will not be used')\n this._isConfigured = false\n } else {\n this._isConfigured = true\n }\n }\n\n /**\n * Check if provider is properly configured\n * Returns true if projectId and parentBranch are valid in settings\n */\n isConfigured(): boolean {\n return this._isConfigured\n }\n\n /**\n * Execute a Neon CLI command and return stdout\n * Throws an error if the command fails\n *\n * @param args - Command arguments to pass to neon CLI\n * @param cwd - Optional working directory to run the command from (defaults to current directory)\n */\n private async executeNeonCommand(args: string[], cwd?: string): Promise<string> {\n // Check if provider is properly configured\n if (!this._isConfigured) {\n throw new Error('NeonProvider is not configured. Check databaseProviders.neon configuration in .iloom/settings.json')\n }\n\n // Log the exact command being executed for debugging\n const command = `neon ${args.join(' ')}`\n logger.debug(`Executing Neon CLI command: ${command}`)\n logger.debug(`Project ID being used: ${this.config.projectId}`)\n if (cwd) {\n logger.debug(`Working directory: ${cwd}`)\n }\n\n const result = await execa('neon', args, {\n timeout: 30000,\n encoding: 'utf8',\n stdio: 'pipe',\n ...(cwd && { cwd }),\n })\n return result.stdout\n }\n\n /**\n * Check if neon CLI is available\n * Ports: check_neon_cli() from bash/utils/neon-utils.sh:18-23\n */\n async isCliAvailable(): Promise<boolean> {\n try {\n await execa('command', ['-v', 'neon'], {\n timeout: 5000,\n shell: true,\n })\n return true\n } catch {\n return false\n }\n }\n\n /**\n * Check if user is authenticated with Neon CLI\n * Ports: check_neon_auth() from bash/utils/neon-utils.sh:25-36\n *\n * @param cwd - Optional working directory to run the command from (prevents issues with deleted directories)\n * @throws Error if authentication check fails for reasons other than not being authenticated\n */\n async isAuthenticated(cwd?: string): Promise<boolean> {\n const cliAvailable = await this.isCliAvailable()\n if (!cliAvailable) {\n return false\n }\n\n try {\n await execa('neon', ['me'], {\n timeout: 10000,\n stdio: 'pipe',\n ...(cwd && { cwd }),\n })\n return true\n } catch (error) {\n const execaError = error as ExecaError\n const stderr = execaError.stderr?.trim() ?? ''\n\n // Check for authentication failure patterns (should return false, not throw)\n const isAuthError =\n stderr.toLowerCase().includes('not authenticated') ||\n stderr.toLowerCase().includes('not logged in') ||\n stderr.toLowerCase().includes('authentication required') ||\n stderr.toLowerCase().includes('login required')\n\n if (isAuthError) {\n return false\n }\n\n // For any other error, let it bubble up\n throw error\n }\n }\n\n /**\n * Sanitize branch name for Neon (replace slashes with underscores)\n * Ports: sanitize_neon_branch_name() from bash/utils/neon-utils.sh:11-15\n */\n sanitizeBranchName(branchName: string): string {\n return branchName.replace(/\\//g, '_')\n }\n\n /**\n * Extract endpoint ID from Neon connection string\n * Pattern matches: ep-abc-123 or ep-abc-123-pooler\n * Returns: ep-abc-123 (without -pooler suffix)\n * Used by: get_neon_branch_name() from bash/utils/neon-utils.sh:294\n */\n private extractEndpointId(connectionString: string): string | null {\n // First, extract the full host part between @ and first dot\n // Examples:\n // @ep-abc123.us-east-1.neon.tech -> ep-abc123\n // @ep-abc123-pooler.us-east-1.neon.tech -> ep-abc123-pooler\n const hostMatch = connectionString.match(/@(ep-[a-z0-9-]+)\\./)\n if (!hostMatch?.[1]) {\n return null\n }\n\n const fullEndpoint = hostMatch[1]\n // Remove -pooler suffix if present\n return fullEndpoint.replace(/-pooler$/, '')\n }\n\n /**\n * List all branches in the Neon project\n * Ports: list_neon_branches() from bash/utils/neon-utils.sh:63-74\n *\n * @param cwd - Optional working directory to run commands from\n */\n async listBranches(cwd?: string): Promise<string[]> {\n const output = await this.executeNeonCommand([\n 'branches',\n 'list',\n '--project-id',\n this.config.projectId,\n '--output',\n 'json',\n ], cwd)\n\n const branches: NeonBranch[] = JSON.parse(output)\n return branches.map(branch => branch.name)\n }\n\n /**\n * Check if a branch exists\n * Ports: check_neon_branch_exists() from bash/utils/neon-utils.sh:38-61\n *\n * @param name - Branch name to check\n * @param cwd - Optional working directory to run commands from\n */\n async branchExists(name: string, cwd?: string): Promise<boolean> {\n const branches = await this.listBranches(cwd)\n return branches.includes(name)\n }\n\n /**\n * Get connection string for a specific branch\n * Ports: get_neon_connection_string() from bash/utils/neon-utils.sh:76-90\n *\n * @param branch - Branch name to get connection string for\n * @param cwd - Optional working directory to run commands from\n */\n async getConnectionString(branch: string, cwd?: string): Promise<string> {\n const connectionString = await this.executeNeonCommand([\n 'connection-string',\n '--branch',\n branch,\n '--project-id',\n this.config.projectId,\n ], cwd)\n return connectionString.trim()\n }\n\n /**\n * Find Vercel preview database branch\n * Checks for both patterns: preview/<branch> and preview_<sanitized-branch>\n * Ports: find_preview_database_branch() from bash/utils/neon-utils.sh:92-124\n *\n * @param branchName - Branch name to find preview for\n * @param cwd - Optional working directory to run commands from\n */\n async findPreviewBranch(branchName: string, cwd?: string): Promise<string | null> {\n // Check for exact preview branch match with slash pattern\n const slashPattern = `preview/${branchName}`\n if (await this.branchExists(slashPattern, cwd)) {\n logger.info(`Found Vercel preview database: ${slashPattern}`)\n return slashPattern\n }\n\n // Check for underscore pattern variation\n const sanitized = this.sanitizeBranchName(branchName)\n const underscorePattern = `preview_${sanitized}`\n if (await this.branchExists(underscorePattern, cwd)) {\n logger.info(`Found Vercel preview database: ${underscorePattern}`)\n return underscorePattern\n }\n\n return null\n }\n\n /**\n * Create a new database branch\n * ALWAYS checks for Vercel preview database first\n * Returns connection string for the branch\n * Ports: create_neon_database_branch() from bash/utils/neon-utils.sh:126-187\n *\n * @param name - Name for the new branch\n * @param fromBranch - Parent branch to create from (defaults to config.parentBranch)\n * @param cwd - Optional working directory to run commands from\n */\n async createBranch(name: string, fromBranch?: string, cwd?: string): Promise<string> {\n // Always check for existing Vercel preview database first (lines 149-158)\n const previewBranch = await this.findPreviewBranch(name, cwd)\n if (previewBranch) {\n const connectionString = await this.getConnectionString(previewBranch, cwd)\n logger.success(`Using existing Vercel preview database: ${previewBranch}`)\n return connectionString\n }\n\n // Sanitize branch name for Neon (replace slashes with underscores)\n const sanitizedName = this.sanitizeBranchName(name)\n const parentBranch = fromBranch ?? this.config.parentBranch\n\n logger.info('Creating Neon database branch...')\n logger.info(` Source branch: ${parentBranch}`)\n logger.info(` New branch: ${sanitizedName}`)\n\n // Create the database branch\n await this.executeNeonCommand([\n 'branches',\n 'create',\n '--name',\n sanitizedName,\n '--parent',\n parentBranch,\n '--project-id',\n this.config.projectId,\n ], cwd)\n\n logger.success('Database branch created successfully')\n\n // Get the connection string for the new branch\n logger.info('Getting connection string for new database branch...')\n const connectionString = await this.getConnectionString(sanitizedName, cwd)\n\n return connectionString\n }\n\n /**\n * Delete a database branch\n * Includes preview database protection with user confirmation\n * Ports: delete_neon_database_branch() from bash/utils/neon-utils.sh:204-259\n *\n * @param name - Name of the branch to delete\n * @param isPreview - Whether this is a preview database branch\n * @param cwd - Optional working directory to run commands from (prevents issues with deleted directories)\n */\n async deleteBranch(name: string, isPreview: boolean = false, cwd?: string): Promise<import('../../types/index.js').DatabaseDeletionResult> {\n // Sanitize branch name for Neon\n const sanitizedName = this.sanitizeBranchName(name)\n\n // For preview contexts, check for preview databases first\n if (isPreview) {\n const previewBranch = await this.findPreviewBranch(name, cwd)\n if (previewBranch) {\n logger.warn(`Found Vercel preview database: ${previewBranch}`)\n logger.warn('Preview databases are managed by Vercel and will be cleaned up automatically')\n logger.warn('Manual deletion may interfere with Vercel\\'s preview deployments')\n\n const confirmed = await promptConfirmation(\n 'Delete preview database anyway?',\n false\n )\n\n if (confirmed) {\n // User confirmed - delete preview branch\n try {\n logger.info(`Deleting Vercel preview database: ${previewBranch}`)\n await this.executeNeonCommand([\n 'branches',\n 'delete',\n previewBranch,\n '--project-id',\n this.config.projectId,\n ], cwd)\n logger.success('Preview database deleted successfully')\n return {\n success: true,\n deleted: true,\n notFound: false,\n branchName: previewBranch\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n logger.error(`Failed to delete preview database: ${errorMessage}`)\n return {\n success: false,\n deleted: false,\n notFound: false,\n error: errorMessage,\n branchName: previewBranch\n }\n }\n } else {\n // User declined deletion\n logger.info('Skipping preview database deletion')\n return {\n success: true,\n deleted: false,\n notFound: false,\n userDeclined: true,\n branchName: previewBranch\n }\n }\n }\n // If no preview database found, fall through to check regular branch\n }\n\n // Check for regular branch\n logger.info(`Checking for Neon database branch: ${sanitizedName}`)\n\n try {\n const exists = await this.branchExists(sanitizedName, cwd)\n\n if (!exists) {\n logger.info(`No database branch found for '${name}'`)\n return {\n success: true,\n deleted: false,\n notFound: true,\n branchName: sanitizedName\n }\n }\n\n // Branch exists - delete it\n logger.info(`Deleting Neon database branch: ${sanitizedName}`)\n await this.executeNeonCommand([\n 'branches',\n 'delete',\n sanitizedName,\n '--project-id',\n this.config.projectId,\n ], cwd)\n logger.success('Database branch deleted successfully')\n\n return {\n success: true,\n deleted: true,\n notFound: false,\n branchName: sanitizedName\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n logger.error(`Failed to delete database branch: ${errorMessage}`)\n return {\n success: false,\n deleted: false,\n notFound: false,\n error: errorMessage,\n branchName: sanitizedName\n }\n }\n }\n\n /**\n * Get branch name from endpoint ID (reverse lookup)\n * Searches all branches to find one with matching endpoint\n * Ports: get_neon_branch_name() from bash/utils/neon-utils.sh:262-308\n *\n * @param endpointId - Endpoint ID to search for\n * @param cwd - Optional working directory to run commands from\n */\n async getBranchNameFromEndpoint(endpointId: string, cwd?: string): Promise<string | null> {\n const branches = await this.listBranches(cwd)\n\n for (const branch of branches) {\n try {\n const connectionString = await this.getConnectionString(branch, cwd)\n const branchEndpointId = this.extractEndpointId(connectionString)\n\n if (branchEndpointId === endpointId) {\n return branch\n }\n } catch {\n // Skip branches that fail to get connection string\n continue\n }\n }\n\n return null\n }\n}\n","import { NeonProvider } from '../lib/providers/NeonProvider.js'\nimport type { IloomSettings } from '../lib/SettingsManager.js'\n\n/**\n * Create NeonProvider from settings configuration\n * Returns provider with isConfigured() = false if neon settings missing\n */\nexport function createNeonProviderFromSettings(settings: IloomSettings): NeonProvider {\n\tconst neonConfig = settings.databaseProviders?.neon\n\n\treturn new NeonProvider({\n\t\tprojectId: neonConfig?.projectId ?? '',\n\t\tparentBranch: neonConfig?.parentBranch ?? '',\n\t})\n}\n"],"mappings":"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","names":["connectionString"]}
1
+ {"version":3,"sources":["../src/lib/providers/NeonProvider.ts","../src/utils/neon-helpers.ts"],"sourcesContent":["import { execa, type ExecaError } from 'execa'\nimport type { DatabaseProvider } from '../../types/index.js'\nimport { createLogger } from '../../utils/logger.js'\nimport { promptConfirmation } from '../../utils/prompt.js'\n\nconst logger = createLogger({ prefix: '🗂️' })\n\ninterface NeonBranch {\n name: string\n id: string\n [key: string]: unknown\n}\n\nexport interface NeonConfig {\n projectId: string\n parentBranch: string\n}\n\n/**\n * Validate Neon configuration\n * Checks that required configuration values are present\n */\nexport function validateNeonConfig(config: {\n projectId?: string\n parentBranch?: string\n}): { valid: boolean; error?: string } {\n if (!config.projectId) {\n return {\n valid: false,\n error: 'Neon projectId is required. Configure in .iloom/settings.json under databaseProviders.neon',\n }\n }\n\n if (!config.parentBranch) {\n return {\n valid: false,\n error: 'Neon parentBranch is required. Configure in .iloom/settings.json under databaseProviders.neon',\n }\n }\n\n // Basic validation for project ID format (should start with appropriate prefix)\n if (!/^[a-zA-Z0-9-]+$/.test(config.projectId)) {\n return {\n valid: false,\n error: 'Neon projectId contains invalid characters',\n }\n }\n\n return { valid: true }\n}\n\n/**\n * Neon database provider implementation\n * Ports functionality from bash/utils/neon-utils.sh\n */\nexport class NeonProvider implements DatabaseProvider {\n private _isConfigured: boolean = false\n\n constructor(private config: NeonConfig) {\n logger.debug('NeonProvider initialized with config:', {\n projectId: config.projectId,\n parentBranch: config.parentBranch,\n hasProjectId: !!config.projectId,\n hasParentBranch: !!config.parentBranch,\n })\n\n // Validate config but don't throw - just mark as not configured\n // This allows the provider to be instantiated even when Neon is not being used\n const validation = validateNeonConfig(config)\n if (!validation.valid) {\n logger.debug(`NeonProvider not configured: ${validation.error}`)\n logger.debug('Neon database branching will not be used')\n this._isConfigured = false\n } else {\n this._isConfigured = true\n }\n }\n\n /**\n * Check if provider is properly configured\n * Returns true if projectId and parentBranch are valid in settings\n */\n isConfigured(): boolean {\n return this._isConfigured\n }\n\n /**\n * Execute a Neon CLI command and return stdout\n * Throws an error if the command fails\n *\n * @param args - Command arguments to pass to neon CLI\n * @param cwd - Optional working directory to run the command from (defaults to current directory)\n */\n private async executeNeonCommand(args: string[], cwd?: string): Promise<string> {\n // Check if provider is properly configured\n if (!this._isConfigured) {\n throw new Error('NeonProvider is not configured. Check databaseProviders.neon configuration in .iloom/settings.json')\n }\n\n // Log the exact command being executed for debugging\n const command = `neon ${args.join(' ')}`\n logger.debug(`Executing Neon CLI command: ${command}`)\n logger.debug(`Project ID being used: ${this.config.projectId}`)\n if (cwd) {\n logger.debug(`Working directory: ${cwd}`)\n }\n\n const result = await execa('neon', args, {\n timeout: 30000,\n encoding: 'utf8',\n stdio: 'pipe',\n ...(cwd && { cwd }),\n })\n return result.stdout\n }\n\n /**\n * Check if neon CLI is available\n * Ports: check_neon_cli() from bash/utils/neon-utils.sh:18-23\n */\n async isCliAvailable(): Promise<boolean> {\n try {\n await execa('command', ['-v', 'neon'], {\n timeout: 5000,\n shell: true,\n })\n return true\n } catch {\n return false\n }\n }\n\n /**\n * Check if user is authenticated with Neon CLI\n * Ports: check_neon_auth() from bash/utils/neon-utils.sh:25-36\n *\n * @param cwd - Optional working directory to run the command from (prevents issues with deleted directories)\n * @throws Error if authentication check fails for reasons other than not being authenticated\n */\n async isAuthenticated(cwd?: string): Promise<boolean> {\n const cliAvailable = await this.isCliAvailable()\n if (!cliAvailable) {\n return false\n }\n\n try {\n await execa('neon', ['me'], {\n timeout: 10000,\n stdio: 'pipe',\n ...(cwd && { cwd }),\n })\n return true\n } catch (error) {\n const execaError = error as ExecaError\n const stderr = execaError.stderr?.trim() ?? ''\n\n // Check for authentication failure patterns (should return false, not throw)\n const isAuthError =\n stderr.toLowerCase().includes('not authenticated') ||\n stderr.toLowerCase().includes('not logged in') ||\n stderr.toLowerCase().includes('authentication required') ||\n stderr.toLowerCase().includes('login required')\n\n if (isAuthError) {\n return false\n }\n\n // For any other error, let it bubble up\n throw error\n }\n }\n\n /**\n * Sanitize branch name for Neon (replace slashes with underscores)\n * Ports: sanitize_neon_branch_name() from bash/utils/neon-utils.sh:11-15\n */\n sanitizeBranchName(branchName: string): string {\n return branchName.replace(/\\//g, '_')\n }\n\n /**\n * Extract endpoint ID from Neon connection string\n * Pattern matches: ep-abc-123 or ep-abc-123-pooler\n * Returns: ep-abc-123 (without -pooler suffix)\n * Used by: get_neon_branch_name() from bash/utils/neon-utils.sh:294\n */\n private extractEndpointId(connectionString: string): string | null {\n // First, extract the full host part between @ and first dot\n // Examples:\n // @ep-abc123.us-east-1.neon.tech -> ep-abc123\n // @ep-abc123-pooler.us-east-1.neon.tech -> ep-abc123-pooler\n const hostMatch = connectionString.match(/@(ep-[a-z0-9-]+)\\./)\n if (!hostMatch?.[1]) {\n return null\n }\n\n const fullEndpoint = hostMatch[1]\n // Remove -pooler suffix if present\n return fullEndpoint.replace(/-pooler$/, '')\n }\n\n /**\n * List all branches in the Neon project\n * Ports: list_neon_branches() from bash/utils/neon-utils.sh:63-74\n *\n * @param cwd - Optional working directory to run commands from\n */\n async listBranches(cwd?: string): Promise<string[]> {\n const output = await this.executeNeonCommand([\n 'branches',\n 'list',\n '--project-id',\n this.config.projectId,\n '--output',\n 'json',\n ], cwd)\n\n const branches: NeonBranch[] = JSON.parse(output)\n return branches.map(branch => branch.name)\n }\n\n /**\n * Check if a branch exists\n * Ports: check_neon_branch_exists() from bash/utils/neon-utils.sh:38-61\n *\n * @param name - Branch name to check\n * @param cwd - Optional working directory to run commands from\n */\n async branchExists(name: string, cwd?: string): Promise<boolean> {\n const branches = await this.listBranches(cwd)\n return branches.includes(name)\n }\n\n /**\n * Get connection string for a specific branch\n * Ports: get_neon_connection_string() from bash/utils/neon-utils.sh:76-90\n *\n * @param branch - Branch name to get connection string for\n * @param cwd - Optional working directory to run commands from\n */\n async getConnectionString(branch: string, cwd?: string): Promise<string> {\n const connectionString = await this.executeNeonCommand([\n 'connection-string',\n '--branch',\n branch,\n '--project-id',\n this.config.projectId,\n ], cwd)\n return connectionString.trim()\n }\n\n /**\n * Find Vercel preview database branch\n * Checks for both patterns: preview/<branch> and preview_<sanitized-branch>\n * Ports: find_preview_database_branch() from bash/utils/neon-utils.sh:92-124\n *\n * @param branchName - Branch name to find preview for\n * @param cwd - Optional working directory to run commands from\n */\n async findPreviewBranch(branchName: string, cwd?: string): Promise<string | null> {\n // Check for exact preview branch match with slash pattern\n const slashPattern = `preview/${branchName}`\n if (await this.branchExists(slashPattern, cwd)) {\n logger.info(`Found Vercel preview database: ${slashPattern}`)\n return slashPattern\n }\n\n // Check for underscore pattern variation\n const sanitized = this.sanitizeBranchName(branchName)\n const underscorePattern = `preview_${sanitized}`\n if (await this.branchExists(underscorePattern, cwd)) {\n logger.info(`Found Vercel preview database: ${underscorePattern}`)\n return underscorePattern\n }\n\n return null\n }\n\n /**\n * Remove expiration date from a Neon branch\n * Used when parent branches need to have child branches created\n * Neon limitation: \"Branches with an expiration date cannot have child branches\"\n *\n * @param branchName - Name of the branch to remove expiration from\n * @param cwd - Optional working directory to run commands from\n */\n private async removeExpiration(branchName: string, cwd?: string): Promise<void> {\n logger.info(`Removing expiration date from branch: ${branchName}`)\n await this.executeNeonCommand([\n 'branches',\n 'set-expiration',\n branchName,\n '--project-id',\n this.config.projectId,\n ], cwd)\n logger.success(`Expiration date removed from ${branchName}`)\n }\n\n /**\n * Create a new database branch\n * ALWAYS checks for Vercel preview database first\n * Handles Neon limitation where parent branches with expiration dates cannot have children\n * Returns connection string for the branch\n * Ports: create_neon_database_branch() from bash/utils/neon-utils.sh:126-187\n *\n * @param name - Name for the new branch\n * @param fromBranch - Parent branch to create from (defaults to config.parentBranch)\n * @param cwd - Optional working directory to run commands from\n */\n async createBranch(name: string, fromBranch?: string, cwd?: string): Promise<string> {\n // Always check for existing Vercel preview database first (lines 149-158)\n const previewBranch = await this.findPreviewBranch(name, cwd)\n if (previewBranch) {\n const connectionString = await this.getConnectionString(previewBranch, cwd)\n logger.success(`Using existing Vercel preview database: ${previewBranch}`)\n return connectionString\n }\n\n // Sanitize branch name for Neon (replace slashes with underscores)\n const sanitizedName = this.sanitizeBranchName(name)\n const parentBranch = fromBranch ?? this.config.parentBranch\n\n logger.info('Creating Neon database branch...')\n logger.info(` Source branch: ${parentBranch}`)\n logger.info(` New branch: ${sanitizedName}`)\n\n try {\n // Create the database branch\n await this.executeNeonCommand([\n 'branches',\n 'create',\n '--name',\n sanitizedName,\n '--parent',\n parentBranch,\n '--project-id',\n this.config.projectId,\n ], cwd)\n } catch (error) {\n // Check if error is about parent branch having an expiration date\n const errorMessage = error instanceof Error ? error.message : String(error)\n if (errorMessage.toLowerCase().includes('expiration') &&\n errorMessage.toLowerCase().includes('child')) {\n logger.warn('Parent branch has an expiration date - removing it to allow child branch creation')\n\n // Remove expiration from parent branch\n await this.removeExpiration(parentBranch, cwd)\n\n // Retry branch creation\n logger.info('Retrying database branch creation...')\n await this.executeNeonCommand([\n 'branches',\n 'create',\n '--name',\n sanitizedName,\n '--parent',\n parentBranch,\n '--project-id',\n this.config.projectId,\n ], cwd)\n } else {\n // Re-throw if it's a different error\n throw error\n }\n }\n\n logger.success('Database branch created successfully')\n\n // Get the connection string for the new branch\n logger.info('Getting connection string for new database branch...')\n const connectionString = await this.getConnectionString(sanitizedName, cwd)\n\n return connectionString\n }\n\n /**\n * Delete a database branch\n * Includes preview database protection with user confirmation\n * Ports: delete_neon_database_branch() from bash/utils/neon-utils.sh:204-259\n *\n * @param name - Name of the branch to delete\n * @param isPreview - Whether this is a preview database branch\n * @param cwd - Optional working directory to run commands from (prevents issues with deleted directories)\n */\n async deleteBranch(name: string, isPreview: boolean = false, cwd?: string): Promise<import('../../types/index.js').DatabaseDeletionResult> {\n // Sanitize branch name for Neon\n const sanitizedName = this.sanitizeBranchName(name)\n\n // For preview contexts, check for preview databases first\n if (isPreview) {\n const previewBranch = await this.findPreviewBranch(name, cwd)\n if (previewBranch) {\n logger.warn(`Found Vercel preview database: ${previewBranch}`)\n logger.warn('Preview databases are managed by Vercel and will be cleaned up automatically')\n logger.warn('Manual deletion may interfere with Vercel\\'s preview deployments')\n\n const confirmed = await promptConfirmation(\n 'Delete preview database anyway?',\n false\n )\n\n if (confirmed) {\n // User confirmed - delete preview branch\n try {\n logger.info(`Deleting Vercel preview database: ${previewBranch}`)\n await this.executeNeonCommand([\n 'branches',\n 'delete',\n previewBranch,\n '--project-id',\n this.config.projectId,\n ], cwd)\n logger.success('Preview database deleted successfully')\n return {\n success: true,\n deleted: true,\n notFound: false,\n branchName: previewBranch\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n logger.error(`Failed to delete preview database: ${errorMessage}`)\n return {\n success: false,\n deleted: false,\n notFound: false,\n error: errorMessage,\n branchName: previewBranch\n }\n }\n } else {\n // User declined deletion\n logger.info('Skipping preview database deletion')\n return {\n success: true,\n deleted: false,\n notFound: false,\n userDeclined: true,\n branchName: previewBranch\n }\n }\n }\n // If no preview database found, fall through to check regular branch\n }\n\n // Check for regular branch\n logger.info(`Checking for Neon database branch: ${sanitizedName}`)\n\n try {\n const exists = await this.branchExists(sanitizedName, cwd)\n\n if (!exists) {\n logger.info(`No database branch found for '${name}'`)\n return {\n success: true,\n deleted: false,\n notFound: true,\n branchName: sanitizedName\n }\n }\n\n // Branch exists - delete it\n logger.info(`Deleting Neon database branch: ${sanitizedName}`)\n await this.executeNeonCommand([\n 'branches',\n 'delete',\n sanitizedName,\n '--project-id',\n this.config.projectId,\n ], cwd)\n logger.success('Database branch deleted successfully')\n\n return {\n success: true,\n deleted: true,\n notFound: false,\n branchName: sanitizedName\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n logger.error(`Failed to delete database branch: ${errorMessage}`)\n return {\n success: false,\n deleted: false,\n notFound: false,\n error: errorMessage,\n branchName: sanitizedName\n }\n }\n }\n\n /**\n * Get branch name from endpoint ID (reverse lookup)\n * Searches all branches to find one with matching endpoint\n * Ports: get_neon_branch_name() from bash/utils/neon-utils.sh:262-308\n *\n * @param endpointId - Endpoint ID to search for\n * @param cwd - Optional working directory to run commands from\n */\n async getBranchNameFromEndpoint(endpointId: string, cwd?: string): Promise<string | null> {\n const branches = await this.listBranches(cwd)\n\n for (const branch of branches) {\n try {\n const connectionString = await this.getConnectionString(branch, cwd)\n const branchEndpointId = this.extractEndpointId(connectionString)\n\n if (branchEndpointId === endpointId) {\n return branch\n }\n } catch {\n // Skip branches that fail to get connection string\n continue\n }\n }\n\n return null\n }\n\n /**\n * Get branch name from a connection string (reverse lookup)\n * Extracts endpoint ID from connection string and finds matching branch\n *\n * @param connectionString - Neon connection string (e.g., postgres://...@ep-abc-123.region.neon.tech/...)\n * @param cwd - Optional working directory to run commands from\n */\n async getBranchNameFromConnectionString(connectionString: string, cwd?: string): Promise<string | null> {\n const endpointId = this.extractEndpointId(connectionString)\n if (!endpointId) {\n logger.debug('Could not extract endpoint ID from connection string')\n return null\n }\n return this.getBranchNameFromEndpoint(endpointId, cwd)\n }\n}\n","import { NeonProvider } from '../lib/providers/NeonProvider.js'\nimport type { IloomSettings } from '../lib/SettingsManager.js'\n\n/**\n * Create NeonProvider from settings configuration\n * Returns provider with isConfigured() = false if neon settings missing\n */\nexport function createNeonProviderFromSettings(settings: IloomSettings): NeonProvider {\n\tconst neonConfig = settings.databaseProviders?.neon\n\n\treturn new NeonProvider({\n\t\tprojectId: neonConfig?.projectId ?? '',\n\t\tparentBranch: neonConfig?.parentBranch ?? '',\n\t})\n}\n"],"mappings":"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","names":["connectionString"]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  promptConfirmation
4
- } from "./chunk-JNKJ7NJV.js";
4
+ } from "./chunk-JKXJ7BGL.js";
5
5
  import {
6
6
  logger
7
7
  } from "./chunk-GEHQXLEI.js";
@@ -140,7 +140,8 @@ var SettingsMigrationManager = class {
140
140
  }
141
141
  }
142
142
  };
143
+
143
144
  export {
144
145
  SettingsMigrationManager
145
146
  };
146
- //# sourceMappingURL=SettingsMigrationManager-MTQIMI54.js.map
147
+ //# sourceMappingURL=chunk-KLBYVHPK.js.map
@@ -127,6 +127,28 @@ function extractPRNumber(branchName) {
127
127
  }
128
128
  return null;
129
129
  }
130
+ function extractIssueNumber(branchName) {
131
+ const newFormatPattern = /issue-([^_]+)__/i;
132
+ const newMatch = branchName.match(newFormatPattern);
133
+ if (newMatch == null ? void 0 : newMatch[1]) return newMatch[1];
134
+ const oldFormatPattern = /issue-(\d+)(?:-|$)/i;
135
+ const oldMatch = branchName.match(oldFormatPattern);
136
+ if (oldMatch == null ? void 0 : oldMatch[1]) return oldMatch[1];
137
+ const alphanumericEndPattern = /issue-([^_\s/]+)$/i;
138
+ const alphanumericMatch = branchName.match(alphanumericEndPattern);
139
+ if (alphanumericMatch == null ? void 0 : alphanumericMatch[1]) return alphanumericMatch[1];
140
+ const legacyPatterns = [
141
+ /issue_(\d+)/i,
142
+ // issue_42
143
+ /^(\d+)-/
144
+ // 42-feature-name
145
+ ];
146
+ for (const pattern of legacyPatterns) {
147
+ const match = branchName.match(pattern);
148
+ if (match == null ? void 0 : match[1]) return match[1];
149
+ }
150
+ return null;
151
+ }
130
152
  function isWorktreePath(path2) {
131
153
  const worktreePatterns = [
132
154
  /\/worktrees?\//i,
@@ -261,7 +283,7 @@ async function findMainWorktreePath(path2 = process.cwd(), options) {
261
283
  }
262
284
  async function findMainWorktreePathWithSettings(path2, settingsManager) {
263
285
  if (!settingsManager) {
264
- const { SettingsManager: SM } = await import("./SettingsManager-SKLUVE3K.js");
286
+ const { SettingsManager: SM } = await import("./SettingsManager-WHHFGSL7.js");
265
287
  settingsManager = new SM();
266
288
  }
267
289
  const settings = await settingsManager.loadSettings(path2);
@@ -296,7 +318,7 @@ async function getDefaultBranch(path2 = process.cwd()) {
296
318
  }
297
319
  async function findAllBranchesForIssue(issueNumber, path2 = process.cwd(), settingsManager) {
298
320
  if (!settingsManager) {
299
- const { SettingsManager: SM } = await import("./SettingsManager-SKLUVE3K.js");
321
+ const { SettingsManager: SM } = await import("./SettingsManager-WHHFGSL7.js");
300
322
  settingsManager = new SM();
301
323
  }
302
324
  const protectedBranches = await settingsManager.getProtectedBranches(path2);
@@ -419,6 +441,7 @@ export {
419
441
  parseWorktreeList,
420
442
  isPRBranch,
421
443
  extractPRNumber,
444
+ extractIssueNumber,
422
445
  isWorktreePath,
423
446
  generateWorktreePath,
424
447
  isValidGitRepo,
@@ -434,4 +457,4 @@ export {
434
457
  ensureRepositoryHasCommits,
435
458
  pushBranchToRemote
436
459
  };
437
- //# sourceMappingURL=chunk-KOCQAD2E.js.map
460
+ //# sourceMappingURL=chunk-MAVL6PJF.js.map