@iloom/cli 0.8.3 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +50 -4
  3. package/dist/{BranchNamingService-AO7BPIUJ.js → BranchNamingService-K6XNWQ6C.js} +2 -2
  4. package/dist/ClaudeContextManager-X2Y72GRL.js +14 -0
  5. package/dist/ClaudeService-7P32TTES.js +13 -0
  6. package/dist/{GitHubService-ACZVNTJE.js → GitHubService-O7T6CFAJ.js} +3 -3
  7. package/dist/{LoomLauncher-NHZMEVTQ.js → LoomLauncher-3I47SUPV.js} +6 -6
  8. package/dist/{ProjectCapabilityDetector-IA56AUE6.js → ProjectCapabilityDetector-N5L7T4IY.js} +3 -3
  9. package/dist/PromptTemplateManager-36YLQRHP.js +11 -0
  10. package/dist/README.md +50 -4
  11. package/dist/{SettingsManager-VCVLL32H.js → SettingsManager-QR7V2IW2.js} +2 -2
  12. package/dist/agents/iloom-artifact-reviewer.md +280 -0
  13. package/dist/agents/iloom-code-reviewer.md +9 -7
  14. package/dist/agents/iloom-issue-analyze-and-plan.md +21 -6
  15. package/dist/agents/iloom-issue-analyzer.md +21 -6
  16. package/dist/agents/iloom-issue-complexity-evaluator.md +21 -6
  17. package/dist/agents/iloom-issue-enhancer.md +21 -6
  18. package/dist/agents/iloom-issue-implementer.md +21 -6
  19. package/dist/agents/iloom-issue-planner.md +21 -6
  20. package/dist/{build-Z3WCIKPD.js → build-IC4CJRMP.js} +8 -8
  21. package/dist/{chunk-TVH67KEO.js → chunk-2HZX6AMR.js} +2 -2
  22. package/dist/{chunk-VZYSM7N7.js → chunk-2JPXGGP4.js} +20 -15
  23. package/dist/chunk-2JPXGGP4.js.map +1 -0
  24. package/dist/{chunk-SC6X5EBG.js → chunk-3P6J4IZZ.js} +3 -3
  25. package/dist/{chunk-HSGZW3ID.js → chunk-4GAJJUYS.js} +3 -3
  26. package/dist/chunk-4GAJJUYS.js.map +1 -0
  27. package/dist/{chunk-RD7I2Q2F.js → chunk-4LKGCFGG.js} +2 -2
  28. package/dist/{chunk-GWONJE3X.js → chunk-4ZIHFUPN.js} +226 -62
  29. package/dist/chunk-4ZIHFUPN.js.map +1 -0
  30. package/dist/{chunk-SSASIBDJ.js → chunk-5LVVQGB3.js} +5 -5
  31. package/dist/{chunk-4BSXZ5YZ.js → chunk-B7U6OKUR.js} +5 -24
  32. package/dist/chunk-B7U6OKUR.js.map +1 -0
  33. package/dist/{chunk-RNBIISBZ.js → chunk-ENGCJIYQ.js} +48 -3
  34. package/dist/chunk-ENGCJIYQ.js.map +1 -0
  35. package/dist/{chunk-IGKPPACU.js → chunk-FO5GGFOV.js} +17 -8
  36. package/dist/chunk-FO5GGFOV.js.map +1 -0
  37. package/dist/{chunk-GDS2HXSW.js → chunk-H6ST2TGP.js} +20 -3
  38. package/dist/chunk-H6ST2TGP.js.map +1 -0
  39. package/dist/{chunk-A7XHHUEV.js → chunk-HZXBHMVM.js} +47 -22
  40. package/dist/chunk-HZXBHMVM.js.map +1 -0
  41. package/dist/{chunk-44Y5IF7P.js → chunk-I3HMNWQQ.js} +9 -8
  42. package/dist/chunk-I3HMNWQQ.js.map +1 -0
  43. package/dist/{chunk-XJHQVOT6.js → chunk-J7FJ6PUT.js} +2 -2
  44. package/dist/chunk-JT5LZRMI.js +302 -0
  45. package/dist/chunk-JT5LZRMI.js.map +1 -0
  46. package/dist/{chunk-Q457PKGH.js → chunk-KAYXR544.js} +2 -2
  47. package/dist/{chunk-XU5A6BWA.js → chunk-MZPRBNYC.js} +4 -4
  48. package/dist/{chunk-XHNACIHO.js → chunk-NTTSUAVM.js} +2 -2
  49. package/dist/{chunk-PLI3JQWT.js → chunk-OAVJR4PM.js} +2 -2
  50. package/dist/{chunk-UDZCTLD6.js → chunk-OK7LUTRW.js} +3 -3
  51. package/dist/{chunk-PBSHQVCT.js → chunk-POU2UMWN.js} +5 -5
  52. package/dist/chunk-QN47QVBX.js +131 -0
  53. package/dist/chunk-QN47QVBX.js.map +1 -0
  54. package/dist/{chunk-3FC3VNEX.js → chunk-RD7OPXZK.js} +34 -8
  55. package/dist/chunk-RD7OPXZK.js.map +1 -0
  56. package/dist/{chunk-O6LECMT6.js → chunk-TGRK3CHF.js} +8 -8
  57. package/dist/{chunk-7GLZVDPQ.js → chunk-TL72BGP6.js} +2 -2
  58. package/dist/{chunk-THS5L54H.js → chunk-TRUMP4DA.js} +26 -2
  59. package/dist/{chunk-THS5L54H.js.map → chunk-TRUMP4DA.js.map} +1 -1
  60. package/dist/{chunk-52MVUK5V.js → chunk-USSL2X4A.js} +2 -2
  61. package/dist/chunk-USSL2X4A.js.map +1 -0
  62. package/dist/{chunk-XPKN3QWY.js → chunk-VOGGLPG5.js} +1 -2
  63. package/dist/{chunk-IWIIOFEB.js → chunk-XFEK2X2D.js} +16 -6
  64. package/dist/chunk-XFEK2X2D.js.map +1 -0
  65. package/dist/{chunk-PVW6JE7E.js → chunk-Y5HSSIK2.js} +2 -2
  66. package/dist/{chunk-4KGRPHM6.js → chunk-Y5O2ALDZ.js} +3 -3
  67. package/dist/{claude-V4HRPR4Z.js → claude-TP2QO3BU.js} +2 -2
  68. package/dist/{cleanup-NWNKWPUY.js → cleanup-D3CSRBBZ.js} +23 -18
  69. package/dist/{cleanup-NWNKWPUY.js.map → cleanup-D3CSRBBZ.js.map} +1 -1
  70. package/dist/cli.js +143 -93
  71. package/dist/cli.js.map +1 -1
  72. package/dist/{commit-534QIRHY.js → commit-IWGT42XN.js} +13 -13
  73. package/dist/{compile-UANHMNTS.js → compile-EOWJORKO.js} +8 -8
  74. package/dist/{contribute-7USRBWRM.js → contribute-WSJTV2RX.js} +4 -4
  75. package/dist/{dev-server-TO7RLYJI.js → dev-server-Q6M62ATG.js} +13 -13
  76. package/dist/{feedback-7ZZI6RC5.js → feedback-QPNDZQRV.js} +14 -14
  77. package/dist/{git-GUNOPP4Q.js → git-W3XUIFTR.js} +5 -3
  78. package/dist/hooks/iloom-hook.js +5 -3
  79. package/dist/{ignite-JBX3BUDE.js → ignite-OPO6EDYT.js} +95 -52
  80. package/dist/ignite-OPO6EDYT.js.map +1 -0
  81. package/dist/index.d.ts +40 -2
  82. package/dist/index.js +52 -15
  83. package/dist/index.js.map +1 -1
  84. package/dist/{chunk-FPNSFP6K.js → init-ALYWKNWG.js} +42 -329
  85. package/dist/init-ALYWKNWG.js.map +1 -0
  86. package/dist/issues-L7TBUPXT.js +116 -0
  87. package/dist/issues-L7TBUPXT.js.map +1 -0
  88. package/dist/{lint-XPODLDVA.js → lint-IHUH45OC.js} +8 -8
  89. package/dist/mcp/issue-management-server.js +3 -2
  90. package/dist/mcp/issue-management-server.js.map +1 -1
  91. package/dist/{open-M2SUR74Y.js → open-KWOV2OFO.js} +15 -15
  92. package/dist/{plan-FB4AOJ2Q.js → plan-BRJBFJHF.js} +60 -28
  93. package/dist/plan-BRJBFJHF.js.map +1 -0
  94. package/dist/{projects-325GEEGJ.js → projects-LH362JZQ.js} +3 -3
  95. package/dist/prompts/init-prompt.txt +9 -1
  96. package/dist/prompts/issue-prompt.txt +310 -0
  97. package/dist/prompts/plan-prompt.txt +4 -6
  98. package/dist/prompts/pr-prompt.txt +79 -0
  99. package/dist/prompts/regular-prompt.txt +205 -0
  100. package/dist/{rebase-4FNRBW3H.js → rebase-AJOJOZUG.js} +9 -9
  101. package/dist/{recap-GSXFEOD6.js → recap-GKJXMDXW.js} +5 -5
  102. package/dist/{run-GZNHRJB2.js → run-QEUVZF7J.js} +15 -15
  103. package/dist/schema/settings.schema.json +9 -1
  104. package/dist/{shell-2SPM3Z5O.js → shell-DAAVG4YN.js} +5 -5
  105. package/dist/{summary-Z4F7YFXE.js → summary-ZKOA35PT.js} +13 -13
  106. package/dist/{test-LBSPYIJW.js → test-5GPWWO3P.js} +8 -8
  107. package/dist/{test-git-ZPSPA2TP.js → test-git-EJUKDB7F.js} +3 -3
  108. package/dist/{test-prefix-6DLB2BHE.js → test-prefix-23TOBUXY.js} +3 -3
  109. package/dist/{test-webserver-XLJ2TZFP.js → test-webserver-CKROHFBQ.js} +5 -5
  110. package/dist/{vscode-LH3VSQ2W.js → vscode-6TOLFCI2.js} +5 -5
  111. package/package.json +2 -2
  112. package/dist/ClaudeContextManager-RDP6CLK6.js +0 -14
  113. package/dist/ClaudeService-FKPOQRA4.js +0 -13
  114. package/dist/PRManager-A63LT3NF.js +0 -16
  115. package/dist/PromptTemplateManager-OUYDHOPI.js +0 -9
  116. package/dist/chunk-3FC3VNEX.js.map +0 -1
  117. package/dist/chunk-44Y5IF7P.js.map +0 -1
  118. package/dist/chunk-4BSXZ5YZ.js.map +0 -1
  119. package/dist/chunk-52MVUK5V.js.map +0 -1
  120. package/dist/chunk-66QOCD5N.js +0 -79
  121. package/dist/chunk-66QOCD5N.js.map +0 -1
  122. package/dist/chunk-A7XHHUEV.js.map +0 -1
  123. package/dist/chunk-FPNSFP6K.js.map +0 -1
  124. package/dist/chunk-GDS2HXSW.js.map +0 -1
  125. package/dist/chunk-GWONJE3X.js.map +0 -1
  126. package/dist/chunk-HSGZW3ID.js.map +0 -1
  127. package/dist/chunk-IGKPPACU.js.map +0 -1
  128. package/dist/chunk-IWIIOFEB.js.map +0 -1
  129. package/dist/chunk-RNBIISBZ.js.map +0 -1
  130. package/dist/chunk-VZYSM7N7.js.map +0 -1
  131. package/dist/git-GUNOPP4Q.js.map +0 -1
  132. package/dist/ignite-JBX3BUDE.js.map +0 -1
  133. package/dist/init-XXDIB2UJ.js +0 -21
  134. package/dist/init-XXDIB2UJ.js.map +0 -1
  135. package/dist/plan-FB4AOJ2Q.js.map +0 -1
  136. /package/dist/{BranchNamingService-AO7BPIUJ.js.map → BranchNamingService-K6XNWQ6C.js.map} +0 -0
  137. /package/dist/{ClaudeContextManager-RDP6CLK6.js.map → ClaudeContextManager-X2Y72GRL.js.map} +0 -0
  138. /package/dist/{ClaudeService-FKPOQRA4.js.map → ClaudeService-7P32TTES.js.map} +0 -0
  139. /package/dist/{GitHubService-ACZVNTJE.js.map → GitHubService-O7T6CFAJ.js.map} +0 -0
  140. /package/dist/{LoomLauncher-NHZMEVTQ.js.map → LoomLauncher-3I47SUPV.js.map} +0 -0
  141. /package/dist/{PRManager-A63LT3NF.js.map → ProjectCapabilityDetector-N5L7T4IY.js.map} +0 -0
  142. /package/dist/{ProjectCapabilityDetector-IA56AUE6.js.map → PromptTemplateManager-36YLQRHP.js.map} +0 -0
  143. /package/dist/{PromptTemplateManager-OUYDHOPI.js.map → SettingsManager-QR7V2IW2.js.map} +0 -0
  144. /package/dist/{build-Z3WCIKPD.js.map → build-IC4CJRMP.js.map} +0 -0
  145. /package/dist/{chunk-TVH67KEO.js.map → chunk-2HZX6AMR.js.map} +0 -0
  146. /package/dist/{chunk-SC6X5EBG.js.map → chunk-3P6J4IZZ.js.map} +0 -0
  147. /package/dist/{chunk-RD7I2Q2F.js.map → chunk-4LKGCFGG.js.map} +0 -0
  148. /package/dist/{chunk-SSASIBDJ.js.map → chunk-5LVVQGB3.js.map} +0 -0
  149. /package/dist/{chunk-XJHQVOT6.js.map → chunk-J7FJ6PUT.js.map} +0 -0
  150. /package/dist/{chunk-Q457PKGH.js.map → chunk-KAYXR544.js.map} +0 -0
  151. /package/dist/{chunk-XU5A6BWA.js.map → chunk-MZPRBNYC.js.map} +0 -0
  152. /package/dist/{chunk-XHNACIHO.js.map → chunk-NTTSUAVM.js.map} +0 -0
  153. /package/dist/{chunk-PLI3JQWT.js.map → chunk-OAVJR4PM.js.map} +0 -0
  154. /package/dist/{chunk-UDZCTLD6.js.map → chunk-OK7LUTRW.js.map} +0 -0
  155. /package/dist/{chunk-PBSHQVCT.js.map → chunk-POU2UMWN.js.map} +0 -0
  156. /package/dist/{chunk-O6LECMT6.js.map → chunk-TGRK3CHF.js.map} +0 -0
  157. /package/dist/{chunk-7GLZVDPQ.js.map → chunk-TL72BGP6.js.map} +0 -0
  158. /package/dist/{chunk-XPKN3QWY.js.map → chunk-VOGGLPG5.js.map} +0 -0
  159. /package/dist/{chunk-PVW6JE7E.js.map → chunk-Y5HSSIK2.js.map} +0 -0
  160. /package/dist/{chunk-4KGRPHM6.js.map → chunk-Y5O2ALDZ.js.map} +0 -0
  161. /package/dist/{SettingsManager-VCVLL32H.js.map → claude-TP2QO3BU.js.map} +0 -0
  162. /package/dist/{commit-534QIRHY.js.map → commit-IWGT42XN.js.map} +0 -0
  163. /package/dist/{compile-UANHMNTS.js.map → compile-EOWJORKO.js.map} +0 -0
  164. /package/dist/{contribute-7USRBWRM.js.map → contribute-WSJTV2RX.js.map} +0 -0
  165. /package/dist/{dev-server-TO7RLYJI.js.map → dev-server-Q6M62ATG.js.map} +0 -0
  166. /package/dist/{feedback-7ZZI6RC5.js.map → feedback-QPNDZQRV.js.map} +0 -0
  167. /package/dist/{claude-V4HRPR4Z.js.map → git-W3XUIFTR.js.map} +0 -0
  168. /package/dist/{lint-XPODLDVA.js.map → lint-IHUH45OC.js.map} +0 -0
  169. /package/dist/{open-M2SUR74Y.js.map → open-KWOV2OFO.js.map} +0 -0
  170. /package/dist/{projects-325GEEGJ.js.map → projects-LH362JZQ.js.map} +0 -0
  171. /package/dist/{rebase-4FNRBW3H.js.map → rebase-AJOJOZUG.js.map} +0 -0
  172. /package/dist/{recap-GSXFEOD6.js.map → recap-GKJXMDXW.js.map} +0 -0
  173. /package/dist/{run-GZNHRJB2.js.map → run-QEUVZF7J.js.map} +0 -0
  174. /package/dist/{shell-2SPM3Z5O.js.map → shell-DAAVG4YN.js.map} +0 -0
  175. /package/dist/{summary-Z4F7YFXE.js.map → summary-ZKOA35PT.js.map} +0 -0
  176. /package/dist/{test-LBSPYIJW.js.map → test-5GPWWO3P.js.map} +0 -0
  177. /package/dist/{test-git-ZPSPA2TP.js.map → test-git-EJUKDB7F.js.map} +0 -0
  178. /package/dist/{test-prefix-6DLB2BHE.js.map → test-prefix-23TOBUXY.js.map} +0 -0
  179. /package/dist/{test-webserver-XLJ2TZFP.js.map → test-webserver-CKROHFBQ.js.map} +0 -0
  180. /package/dist/{vscode-LH3VSQ2W.js.map → vscode-6TOLFCI2.js.map} +0 -0
@@ -2,30 +2,31 @@
2
2
  import {
3
3
  detectPackageManager,
4
4
  runScript
5
- } from "./chunk-RD7I2Q2F.js";
5
+ } from "./chunk-4LKGCFGG.js";
6
6
  import {
7
7
  ProjectCapabilityDetector
8
- } from "./chunk-7GLZVDPQ.js";
8
+ } from "./chunk-TL72BGP6.js";
9
9
  import {
10
10
  getPackageConfig,
11
11
  hasScript
12
- } from "./chunk-XPKN3QWY.js";
12
+ } from "./chunk-VOGGLPG5.js";
13
13
  import {
14
14
  executeGitCommand,
15
+ fetchOrigin,
15
16
  findMainWorktreePathWithSettings,
16
17
  findWorktreeForBranch,
17
18
  getMergeTargetBranch
18
- } from "./chunk-GDS2HXSW.js";
19
+ } from "./chunk-H6ST2TGP.js";
19
20
  import {
20
21
  SettingsManager
21
- } from "./chunk-IWIIOFEB.js";
22
+ } from "./chunk-XFEK2X2D.js";
22
23
  import {
23
24
  MetadataManager
24
25
  } from "./chunk-KBEIQP4G.js";
25
26
  import {
26
27
  detectClaudeCli,
27
28
  launchClaude
28
- } from "./chunk-IGKPPACU.js";
29
+ } from "./chunk-FO5GGFOV.js";
29
30
  import {
30
31
  getLogger
31
32
  } from "./chunk-6MLEBAYZ.js";
@@ -57,18 +58,42 @@ var MergeManager = class {
57
58
  * @throws Error if main branch doesn't exist, uncommitted changes exist, or conflicts occur
58
59
  */
59
60
  async rebaseOnMain(worktreePath, options = {}) {
61
+ var _a;
60
62
  const { dryRun = false, force = false } = options;
61
63
  const mainBranch = await this.getMainBranch(worktreePath);
62
- getLogger().info(`Starting rebase on ${mainBranch} branch...`);
64
+ const metadata = await this.metadataManager.readMetadata(worktreePath);
65
+ const isChildLoom = !!(metadata == null ? void 0 : metadata.parentLoom);
66
+ const settings = await this.settingsManager.loadSettings(worktreePath);
67
+ const mergeBehaviorMode = ((_a = settings.mergeBehavior) == null ? void 0 : _a.mode) ?? "local";
68
+ const isPRMode = mergeBehaviorMode === "github-pr" || mergeBehaviorMode === "github-draft-pr";
69
+ const useRemote = isPRMode && !isChildLoom;
70
+ let targetBranch;
71
+ if (useRemote) {
72
+ getLogger().info("Fetching from origin...");
73
+ await fetchOrigin(worktreePath);
74
+ targetBranch = `origin/${mainBranch}`;
75
+ } else {
76
+ getLogger().info(`Using local branch ${mainBranch} for rebase...`);
77
+ targetBranch = mainBranch;
78
+ }
79
+ getLogger().info(`Starting rebase on ${targetBranch}...`);
80
+ const refPath = useRemote ? `refs/remotes/${targetBranch}` : `refs/heads/${targetBranch}`;
63
81
  try {
64
- await executeGitCommand(["show-ref", "--verify", "--quiet", `refs/heads/${mainBranch}`], {
82
+ await executeGitCommand(["show-ref", "--verify", "--quiet", refPath], {
65
83
  cwd: worktreePath
66
84
  });
67
85
  } catch {
68
- throw new Error(
69
- `Main branch "${mainBranch}" does not exist. Cannot rebase.
70
- Ensure the repository has a "${mainBranch}" branch or create it first.`
71
- );
86
+ if (useRemote) {
87
+ throw new Error(
88
+ `Remote branch "${targetBranch}" does not exist. Cannot rebase.
89
+ Ensure the repository has a "${mainBranch}" branch on origin.`
90
+ );
91
+ } else {
92
+ throw new Error(
93
+ `Local branch "${targetBranch}" does not exist. Cannot rebase.
94
+ Ensure the branch exists locally.`
95
+ );
96
+ }
72
97
  }
73
98
  const statusOutput = await executeGitCommand(["status", "--porcelain"], {
74
99
  cwd: worktreePath
@@ -79,22 +104,22 @@ Ensure the repository has a "${mainBranch}" branch or create it first.`
79
104
  wipCommitHash = await this.createWipCommit(worktreePath);
80
105
  getLogger().debug(`Created WIP commit: ${wipCommitHash}`);
81
106
  }
82
- const mergeBase = await executeGitCommand(["merge-base", mainBranch, "HEAD"], {
107
+ const mergeBase = await executeGitCommand(["merge-base", targetBranch, "HEAD"], {
83
108
  cwd: worktreePath
84
109
  });
85
- const mainHead = await executeGitCommand(["rev-parse", mainBranch], {
110
+ const targetHead = await executeGitCommand(["rev-parse", targetBranch], {
86
111
  cwd: worktreePath
87
112
  });
88
113
  const mergeBaseTrimmed = mergeBase.trim();
89
- const mainHeadTrimmed = mainHead.trim();
90
- if (mergeBaseTrimmed === mainHeadTrimmed) {
91
- getLogger().success(`Branch is already up to date with ${mainBranch}. No rebase needed.`);
114
+ const targetHeadTrimmed = targetHead.trim();
115
+ if (mergeBaseTrimmed === targetHeadTrimmed) {
116
+ getLogger().success(`Branch is already up to date with ${targetBranch}. No rebase needed.`);
92
117
  if (wipCommitHash) {
93
118
  await this.restoreWipCommit(worktreePath, wipCommitHash);
94
119
  }
95
120
  return;
96
121
  }
97
- const commitsOutput = await executeGitCommand(["log", "--oneline", `${mainBranch}..HEAD`], {
122
+ const commitsOutput = await executeGitCommand(["log", "--oneline", `${targetBranch}..HEAD`], {
98
123
  cwd: worktreePath
99
124
  });
100
125
  const commits = commitsOutput.trim();
@@ -103,20 +128,20 @@ Ensure the repository has a "${mainBranch}" branch or create it first.`
103
128
  getLogger().info(`Found ${commitLines.length} commit(s) to rebase:`);
104
129
  commitLines.forEach((commit) => getLogger().info(` ${commit}`));
105
130
  } else {
106
- getLogger().info(`${mainBranch} branch has moved forward. Rebasing to update branch...`);
131
+ getLogger().info(`${targetBranch} has moved forward. Rebasing to update branch...`);
107
132
  }
108
133
  if (!force && !dryRun) {
109
134
  getLogger().info("Proceeding with rebase... (use --force to skip confirmations)");
110
135
  }
111
136
  if (dryRun) {
112
- getLogger().info(`[DRY RUN] Would execute: git rebase ${mainBranch}`);
137
+ getLogger().info(`[DRY RUN] Would execute: git rebase ${targetBranch}`);
113
138
  if (commitLines.length > 0) {
114
139
  getLogger().info(`[DRY RUN] This would rebase ${commitLines.length} commit(s)`);
115
140
  }
116
141
  return;
117
142
  }
118
143
  try {
119
- await executeGitCommand(["-c", "core.hooksPath=/dev/null", "rebase", mainBranch], { cwd: worktreePath });
144
+ await executeGitCommand(["-c", "core.hooksPath=/dev/null", "rebase", targetBranch], { cwd: worktreePath });
120
145
  getLogger().success("Rebase completed successfully!");
121
146
  if (wipCommitHash) {
122
147
  await this.restoreWipCommit(worktreePath, wipCommitHash);
@@ -469,4 +494,4 @@ export {
469
494
  MergeManager,
470
495
  BuildRunner
471
496
  };
472
- //# sourceMappingURL=chunk-A7XHHUEV.js.map
497
+ //# sourceMappingURL=chunk-HZXBHMVM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/MergeManager.ts","../src/lib/BuildRunner.ts"],"sourcesContent":["import { executeGitCommand, fetchOrigin, findMainWorktreePathWithSettings, findWorktreeForBranch, getMergeTargetBranch } from '../utils/git.js'\nimport { getLogger } from '../utils/logger-context.js'\nimport { detectClaudeCli, launchClaude } from '../utils/claude.js'\nimport { SettingsManager } from './SettingsManager.js'\nimport { MetadataManager } from './MetadataManager.js'\nimport type { MergeOptions } from '../types/index.js'\n\n/**\n * MergeManager handles Git rebase and fast-forward merge operations\n * Implements fail-fast behavior for conflicts (Phase 1 - no Claude assistance)\n *\n * Ports bash/merge-and-clean.sh lines 781-1090\n */\nexport class MergeManager {\n\tprivate settingsManager: SettingsManager\n\tprivate metadataManager: MetadataManager\n\n\tconstructor(settingsManager?: SettingsManager, metadataManager?: MetadataManager) {\n\t\tthis.settingsManager = settingsManager ?? new SettingsManager()\n\t\tthis.metadataManager = metadataManager ?? new MetadataManager()\n\t}\n\n\t/**\n\t * Get the merge target branch for a loom\n\t * Priority: parent loom metadata > configured main branch > 'main'\n\t * @param worktreePath - Optional path to load settings/metadata from (defaults to process.cwd())\n\t * @private\n\t */\n\tprivate async getMainBranch(worktreePath?: string): Promise<string> {\n\t\t// Delegate to shared utility function\n\t\treturn getMergeTargetBranch(worktreePath ?? process.cwd(), {\n\t\t\tsettingsManager: this.settingsManager,\n\t\t\tmetadataManager: this.metadataManager,\n\t\t})\n\t}\n\n\t/**\n\t * Rebase current branch on main with fail-fast on conflicts\n\t * Ports bash/merge-and-clean.sh lines 781-913\n\t *\n\t * @param worktreePath - Path to the worktree\n\t * @param options - Merge options (dryRun, force)\n\t * @throws Error if main branch doesn't exist, uncommitted changes exist, or conflicts occur\n\t */\n\tasync rebaseOnMain(worktreePath: string, options: MergeOptions = {}): Promise<void> {\n\t\tconst { dryRun = false, force = false } = options\n\t\tconst mainBranch = await this.getMainBranch(worktreePath)\n\n\t\t// Determine whether to use remote (origin/) or local branch reference\n\t\t// - Child looms: always use local parent branch (parent may not be pushed)\n\t\t// - PR modes (github-pr, github-draft-pr) for non-child: fetch and use origin/{branch}\n\t\t// - Local mode: use local branch (no fetch)\n\t\tconst metadata = await this.metadataManager.readMetadata(worktreePath)\n\t\tconst isChildLoom = !!metadata?.parentLoom\n\t\tconst settings = await this.settingsManager.loadSettings(worktreePath)\n\t\tconst mergeBehaviorMode = settings.mergeBehavior?.mode ?? 'local'\n\t\tconst isPRMode = mergeBehaviorMode === 'github-pr' || mergeBehaviorMode === 'github-draft-pr'\n\t\tconst useRemote = isPRMode && !isChildLoom\n\n\t\tlet targetBranch: string\n\t\tif (useRemote) {\n\t\t\t// PR modes (non-child): fetch and use origin/{branch}\n\t\t\tgetLogger().info('Fetching from origin...')\n\t\t\tawait fetchOrigin(worktreePath)\n\t\t\ttargetBranch = `origin/${mainBranch}`\n\t\t} else {\n\t\t\t// Local mode or child loom: use local branch\n\t\t\tgetLogger().info(`Using local branch ${mainBranch} for rebase...`)\n\t\t\ttargetBranch = mainBranch\n\t\t}\n\n\t\tgetLogger().info(`Starting rebase on ${targetBranch}...`)\n\n\t\t// Step 1: Check if branch exists (remote ref for origin/, local ref otherwise)\n\t\tconst refPath = useRemote ? `refs/remotes/${targetBranch}` : `refs/heads/${targetBranch}`\n\t\ttry {\n\t\t\tawait executeGitCommand(['show-ref', '--verify', '--quiet', refPath], {\n\t\t\t\tcwd: worktreePath,\n\t\t\t})\n\t\t} catch {\n\t\t\tif (useRemote) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Remote branch \"${targetBranch}\" does not exist. Cannot rebase.\\n` +\n\t\t\t\t\t\t`Ensure the repository has a \"${mainBranch}\" branch on origin.`\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Local branch \"${targetBranch}\" does not exist. Cannot rebase.\\n` +\n\t\t\t\t\t\t`Ensure the branch exists locally.`\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\t// Step 2: Check for uncommitted changes and create WIP commit if needed\n\t\tconst statusOutput = await executeGitCommand(['status', '--porcelain'], {\n\t\t\tcwd: worktreePath,\n\t\t})\n\n\t\tlet wipCommitHash: string | null = null\n\t\tif (statusOutput.trim()) {\n\t\t\tgetLogger().info('Uncommitted changes detected, creating temporary WIP commit...')\n\t\t\twipCommitHash = await this.createWipCommit(worktreePath)\n\t\t\tgetLogger().debug(`Created WIP commit: ${wipCommitHash}`)\n\t\t}\n\n\t\t// Step 3: Check if rebase is needed by comparing merge-base with target HEAD\n\t\tconst mergeBase = await executeGitCommand(['merge-base', targetBranch, 'HEAD'], {\n\t\t\tcwd: worktreePath,\n\t\t})\n\n\t\tconst targetHead = await executeGitCommand(['rev-parse', targetBranch], {\n\t\t\tcwd: worktreePath,\n\t\t})\n\n\t\tconst mergeBaseTrimmed = mergeBase.trim()\n\t\tconst targetHeadTrimmed = targetHead.trim()\n\n\t\t// If merge-base matches target HEAD, branch is already up to date\n\t\tif (mergeBaseTrimmed === targetHeadTrimmed) {\n\t\t\tgetLogger().success(`Branch is already up to date with ${targetBranch}. No rebase needed.`)\n\t\t\t// Restore WIP commit if created (soft reset to remove temporary commit)\n\t\t\tif (wipCommitHash) {\n\t\t\t\tawait this.restoreWipCommit(worktreePath, wipCommitHash)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\t// Step 4: Show commits to be rebased (for informational purposes)\n\t\tconst commitsOutput = await executeGitCommand(['log', '--oneline', `${targetBranch}..HEAD`], {\n\t\t\tcwd: worktreePath,\n\t\t})\n\n\t\tconst commits = commitsOutput.trim()\n\t\tconst commitLines = commits ? commits.split('\\n') : []\n\n\t\tif (commits) {\n\t\t\t// Show commits that will be rebased\n\t\t\tgetLogger().info(`Found ${commitLines.length} commit(s) to rebase:`)\n\t\t\tcommitLines.forEach((commit) => getLogger().info(` ${commit}`))\n\t\t} else {\n\t\t\t// Target has moved forward but branch has no new commits\n\t\t\tgetLogger().info(`${targetBranch} has moved forward. Rebasing to update branch...`)\n\t\t}\n\n\t\t// Step 5: User confirmation (unless force mode or dry-run)\n\t\tif (!force && !dryRun) {\n\t\t\t// TODO: Implement interactive prompt for confirmation\n\t\t\t// For now, proceeding automatically (use --force to skip this message)\n\t\t\tgetLogger().info('Proceeding with rebase... (use --force to skip confirmations)')\n\t\t}\n\n\t\t// Step 6: Execute rebase (unless dry-run)\n\t\tif (dryRun) {\n\t\t\tgetLogger().info(`[DRY RUN] Would execute: git rebase ${targetBranch}`)\n\t\t\tif (commitLines.length > 0) {\n\t\t\t\tgetLogger().info(`[DRY RUN] This would rebase ${commitLines.length} commit(s)`)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\t// Execute rebase\n\t\t// Use -c core.hooksPath=/dev/null to disable hooks during rebase\n\t\t// This prevents pre-commit hooks from running when commits are re-applied\n\t\ttry {\n\t\t\tawait executeGitCommand(['-c', 'core.hooksPath=/dev/null', 'rebase', targetBranch], { cwd: worktreePath })\n\t\t\tgetLogger().success('Rebase completed successfully!')\n\n\t\t\t// Restore WIP commit if created\n\t\t\tif (wipCommitHash) {\n\t\t\t\tawait this.restoreWipCommit(worktreePath, wipCommitHash)\n\t\t\t}\n\t\t} catch (error) {\n\t\t\t// Detect conflicts\n\t\t\tconst conflictedFiles = await this.detectConflictedFiles(worktreePath)\n\n\t\t\tif (conflictedFiles.length > 0) {\n\t\t\t\t// Try Claude-assisted resolution first\n\t\t\t\tgetLogger().info('Merge conflicts detected, attempting Claude-assisted resolution...')\n\n\t\t\t\tconst resolved = await this.attemptClaudeConflictResolution(\n\t\t\t\t\tworktreePath,\n\t\t\t\t\tconflictedFiles\n\t\t\t\t)\n\n\t\t\t\tif (resolved) {\n\t\t\t\t\tgetLogger().success('Conflicts resolved with Claude assistance, rebase completed')\n\n\t\t\t\t\t// Restore WIP commit if created\n\t\t\t\t\tif (wipCommitHash) {\n\t\t\t\t\t\tawait this.restoreWipCommit(worktreePath, wipCommitHash)\n\t\t\t\t\t}\n\t\t\t\t\treturn // Continue with successful rebase\n\t\t\t\t}\n\n\t\t\t\t// Claude couldn't resolve or not available - fail fast\n\t\t\t\tconst conflictError = this.formatConflictError(conflictedFiles)\n\t\t\t\tthrow new Error(conflictError)\n\t\t\t}\n\n\t\t\t// If not a conflict, re-throw the original error\n\t\t\tthrow new Error(\n\t\t\t\t`Rebase failed: ${error instanceof Error ? error.message : String(error)}\\n` +\n\t\t\t\t\t'Run: git status for more details\\n' +\n\t\t\t\t\t'Or: git rebase --abort to cancel the rebase'\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Validate that fast-forward merge is possible\n\t * Ports bash/merge-and-clean.sh lines 957-968\n\t *\n\t * @param branchName - Name of the branch to merge\n\t * @param mainWorktreePath - Path where main branch is checked out\n\t * @throws Error if fast-forward is not possible\n\t */\n\tasync validateFastForwardPossible(mainBranch: string, branchName: string, mainWorktreePath: string): Promise<void> {\n\n\t\t// Step 1: Get merge-base between main and branch\n\t\tconst mergeBase = await executeGitCommand(['merge-base', mainBranch, branchName], {\n\t\t\tcwd: mainWorktreePath,\n\t\t})\n\n\t\t// Step 2: Get current HEAD of main\n\t\tconst mainHead = await executeGitCommand(['rev-parse', mainBranch], {\n\t\t\tcwd: mainWorktreePath,\n\t\t})\n\n\t\t// Step 3: Compare - they must match for fast-forward\n\t\tconst mergeBaseTrimmed = mergeBase.trim()\n\t\tconst mainHeadTrimmed = mainHead.trim()\n\n\t\tif (mergeBaseTrimmed !== mainHeadTrimmed) {\n\t\t\tthrow new Error(\n\t\t\t\t'Cannot perform fast-forward merge.\\n' +\n\t\t\t\t\t`The ${mainBranch} branch has moved forward since this branch was created.\\n` +\n\t\t\t\t\t`Merge base: ${mergeBaseTrimmed}\\n` +\n\t\t\t\t\t`Main HEAD: ${mainHeadTrimmed}\\n\\n` +\n\t\t\t\t\t'To fix this:\\n' +\n\t\t\t\t\t` 1. Rebase the branch on ${mainBranch}: git rebase ${mainBranch}\\n` +\n\t\t\t\t\t` 2. Or use: il finish to automatically rebase and merge\\n`\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Perform fast-forward only merge\n\t * Ports bash/merge-and-clean.sh lines 938-994\n\t *\n\t * @param branchName - Name of the branch to merge\n\t * @param worktreePath - Path to the worktree\n\t * @param options - Merge options (dryRun, force)\n\t * @throws Error if checkout, validation, or merge fails\n\t */\n\tasync performFastForwardMerge(\n\t\tbranchName: string,\n\t\tworktreePath: string,\n\t\toptions: MergeOptions = {}\n\t): Promise<void> {\n\t\tconst { dryRun = false, force = false } = options\n\n\t\tgetLogger().info('Starting fast-forward merge...')\n\n\t\t// Step 1: Get the merge target branch FIRST\n\t\t// For child looms, this will be the parent branch from metadata\n\t\t// For regular looms, this falls back to settings.mainBranch or 'main'\n\t\tconst mainBranch = await this.getMainBranch(worktreePath)\n\n\t\t// Step 2: Find where the merge target branch is checked out\n\t\t// CRITICAL: We must find the worktree for the MERGE TARGET, not settings.mainBranch\n\t\t// This fixes the child loom bug where we'd find the 'main' worktree instead of the parent branch worktree\n\t\tlet mainWorktreePath: string\n\t\tif (options.repoRoot) {\n\t\t\tmainWorktreePath = options.repoRoot\n\t\t} else {\n\t\t\ttry {\n\t\t\t\t// First try to find worktree with the exact merge target branch checked out\n\t\t\t\tmainWorktreePath = await findWorktreeForBranch(mainBranch, worktreePath)\n\t\t\t} catch {\n\t\t\t\t// Fallback: if no worktree has the branch checked out, use settings-based lookup\n\t\t\t\t// This handles edge cases like bare repos or detached HEAD states\n\t\t\t\tgetLogger().debug(`No worktree found for branch '${mainBranch}', falling back to settings-based lookup`)\n\t\t\t\tmainWorktreePath = await findMainWorktreePathWithSettings(worktreePath, this.settingsManager)\n\t\t\t}\n\t\t}\n\n\t\t// Step 3: No need to checkout - the merge target branch is already checked out in mainWorktreePath\n\t\tgetLogger().debug(`Using ${mainBranch} branch location: ${mainWorktreePath}`)\n\n\t\t// Step 4: Verify we're on the correct branch\n\t\tconst currentBranch = await executeGitCommand(['branch', '--show-current'], {\n\t\t\tcwd: mainWorktreePath,\n\t\t})\n\n\t\tif (currentBranch.trim() !== mainBranch) {\n\t\t\tthrow new Error(\n\t\t\t\t`Expected ${mainBranch} branch but found: ${currentBranch.trim()}\\n` +\n\t\t\t\t\t`At location: ${mainWorktreePath}\\n` +\n\t\t\t\t\t'This indicates the main worktree detection failed.'\n\t\t\t)\n\t\t}\n\n\t\t// Step 5: Validate fast-forward is possible\n\t\tawait this.validateFastForwardPossible(mainBranch, branchName, mainWorktreePath)\n\n\t\t// Step 6: Show commits to be merged\n\t\tconst commitsOutput = await executeGitCommand(['log', '--oneline', `${mainBranch}..${branchName}`], {\n\t\t\tcwd: mainWorktreePath,\n\t\t})\n\n\t\tconst commits = commitsOutput.trim()\n\n\t\t// If no commits, branch has no changes ahead of main\n\t\tif (!commits) {\n\t\t\tgetLogger().success(`Branch has no commits ahead of ${mainBranch}. No merge needed.`)\n\t\t\treturn\n\t\t}\n\n\t\t// Show commits that will be merged\n\t\tconst commitLines = commits.split('\\n')\n\t\tgetLogger().info(`Found ${commitLines.length} commit(s) to merge:`)\n\t\tcommitLines.forEach((commit) => getLogger().info(` ${commit}`))\n\n\t\t// Step 7: User confirmation (unless force mode or dry-run)\n\t\tif (!force && !dryRun) {\n\t\t\t// TODO: Implement interactive prompt for confirmation\n\t\t\t// For now, proceeding automatically (use --force to skip this message)\n\t\t\tgetLogger().info('Proceeding with fast-forward merge... (use --force to skip confirmations)')\n\t\t}\n\n\t\t// Step 8: Execute merge (unless dry-run)\n\t\tif (dryRun) {\n\t\t\tgetLogger().info(`[DRY RUN] Would execute: git merge --ff-only ${branchName}`)\n\t\t\tgetLogger().info(`[DRY RUN] This would merge ${commitLines.length} commit(s)`)\n\t\t\treturn\n\t\t}\n\n\t\t// Execute fast-forward merge\n\t\ttry {\n\t\t\tgetLogger().debug(`Executing fast-forward merge of ${branchName} into ${mainBranch} using cwd: ${mainWorktreePath}...`)\n\t\t\tawait executeGitCommand(['merge', '--ff-only', branchName], { cwd: mainWorktreePath })\n\t\t\tgetLogger().success(`Fast-forward merge completed! Merged ${commitLines.length} commit(s).`)\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`Fast-forward merge failed: ${error instanceof Error ? error.message : String(error)}\\n\\n` +\n\t\t\t\t\t'To recover:\\n' +\n\t\t\t\t\t' 1. Check merge status: git status\\n' +\n\t\t\t\t\t' 2. Abort merge if needed: git merge --abort\\n' +\n\t\t\t\t\t' 3. Verify branch is rebased: git rebase main\\n' +\n\t\t\t\t\t' 4. Try merge again: il finish'\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Helper: Detect conflicted files after failed rebase\n\t * @private\n\t */\n\tprivate async detectConflictedFiles(worktreePath: string): Promise<string[]> {\n\t\ttry {\n\t\t\tconst output = await executeGitCommand(['diff', '--name-only', '--diff-filter=U'], {\n\t\t\t\tcwd: worktreePath,\n\t\t\t})\n\n\t\t\treturn output\n\t\t\t\t.trim()\n\t\t\t\t.split('\\n')\n\t\t\t\t.filter((file) => file.length > 0)\n\t\t} catch {\n\t\t\t// If command fails, return empty array (might not be a conflict)\n\t\t\treturn []\n\t\t}\n\t}\n\n\t/**\n\t * Create a temporary WIP commit to preserve uncommitted changes during rebase\n\t * Stages all changes (tracked, untracked) using git add -A\n\t * Uses --no-verify to skip pre-commit hooks since this is a temporary internal commit\n\t * @param worktreePath - Path to the worktree\n\t * @returns The commit hash of the WIP commit\n\t * @private\n\t */\n\tprivate async createWipCommit(worktreePath: string): Promise<string> {\n\t\t// Stage all changes including untracked files\n\t\tawait executeGitCommand(['add', '-A'], { cwd: worktreePath })\n\n\t\t// Create WIP commit with distinctive message\n\t\t// Use --no-verify to skip pre-commit hooks - this is a temporary internal commit\n\t\tawait executeGitCommand(['commit', '--no-verify', '-m', 'WIP: Auto-stash for rebase'], { cwd: worktreePath })\n\n\t\t// Get and return the commit hash\n\t\tconst hash = await executeGitCommand(['rev-parse', 'HEAD'], { cwd: worktreePath })\n\t\treturn hash.trim()\n\t}\n\n\t/**\n\t * Restore uncommitted changes from WIP commit via soft reset\n\t * Logs warning but does not fail if soft reset fails (changes are safe in commit history)\n\t * @param worktreePath - Path to the worktree\n\t * @param wipCommitHash - Original WIP commit hash for verification logging\n\t * @private\n\t */\n\tprivate async restoreWipCommit(worktreePath: string, wipCommitHash: string): Promise<void> {\n\t\tgetLogger().info('Restoring uncommitted changes from WIP commit...')\n\n\t\ttry {\n\t\t\t// Soft reset to parent - changes become staged\n\t\t\tawait executeGitCommand(['reset', '--soft', 'HEAD~1'], { cwd: worktreePath })\n\n\t\t\t// Unstage files to restore to original working directory state\n\t\t\tawait executeGitCommand(['reset', 'HEAD'], { cwd: worktreePath })\n\n\t\t\tgetLogger().success('Restored uncommitted changes from WIP commit')\n\t\t} catch (error) {\n\t\t\t// Log warning but consider rebase successful - work is not lost\n\t\t\tgetLogger().warn(\n\t\t\t\t`Failed to restore WIP commit (${wipCommitHash}). ` +\n\t\t\t\t\t`Your changes are safe in the commit history. ` +\n\t\t\t\t\t`Manual recovery: git reset --soft HEAD~1`,\n\t\t\t\t{ error: error instanceof Error ? error.message : String(error) }\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Helper: Format conflict error message with manual resolution steps\n\t * @private\n\t */\n\tprivate formatConflictError(conflictedFiles: string[]): string {\n\t\tconst fileList = conflictedFiles.map((file) => ` • ${file}`).join('\\n')\n\n\t\treturn (\n\t\t\t'Rebase failed - merge conflicts detected in:\\n' +\n\t\t\tfileList +\n\t\t\t'\\n\\n' +\n\t\t\t'To resolve manually:\\n' +\n\t\t\t' 1. Fix conflicts in the files above\\n' +\n\t\t\t' 2. Stage resolved files: git add <files>\\n' +\n\t\t\t' 3. Continue rebase: git rebase --continue\\n' +\n\t\t\t' 4. Or abort rebase: git rebase --abort\\n' +\n\t\t\t' 5. Then re-run: il finish <issue-number>'\n\t\t)\n\t}\n\n\t/**\n\t * Attempt to resolve conflicts using Claude\n\t * Ports bash/merge-and-clean.sh lines 839-894\n\t *\n\t * @param worktreePath - Path to the worktree\n\t * @param conflictedFiles - List of files with conflicts\n\t * @returns true if conflicts resolved, false otherwise\n\t * @private\n\t */\n\tprivate async attemptClaudeConflictResolution(\n\t\tworktreePath: string,\n\t\tconflictedFiles: string[]\n\t): Promise<boolean> {\n\t\t// Check if Claude CLI is available\n\t\tconst isClaudeAvailable = await detectClaudeCli()\n\t\tif (!isClaudeAvailable) {\n\t\t\tgetLogger().debug('Claude CLI not available, skipping conflict resolution')\n\t\t\treturn false\n\t\t}\n\n\t\tgetLogger().info(`Launching Claude to resolve conflicts in ${conflictedFiles.length} file(s)...`)\n\n\t\t// Hard-coded prompt matching bash script line 844\n\t\t// No templates, no complexity - just the essential instruction\n\t\tconst systemPrompt =\n\t\t\t`Please help resolve the git rebase conflicts in this repository. ` +\n\t\t\t`Analyze the conflicted files, understand the changes from both branches, ` +\n\t\t\t`fix the conflicts, then run 'git add .' to stage the resolved files, ` +\n\t\t\t`and finally run 'git rebase --continue' to continue the rebase process. ` +\n\t\t\t`Once the issue is resolved, tell the user they can use /exit to continue with the process.`\n\n\t\tconst prompt =\n\t\t\t`Help me with this rebase please.`\n\n\t\t// Git commands to auto-approve during rebase conflict resolution\n\t\t// These are the essential commands Claude needs to analyze and resolve conflicts\n\t\t// Note: git reset and git checkout are intentionally excluded as they can be destructive\n\t\tconst rebaseAllowedTools = [\n\t\t\t'Bash(git status:*)',\n\t\t\t'Bash(git diff:*)',\n\t\t\t'Bash(git log:*)',\n\t\t\t'Bash(git add:*)',\n\t\t\t'Bash(git rebase:*)',\n\t\t]\n\n\t\ttry {\n\t\t\t// Launch Claude interactively in current terminal\n\t\t\t// User will interact directly with Claude to resolve conflicts\n\t\t\tawait launchClaude(prompt, {\n\t\t\t\tappendSystemPrompt: systemPrompt,\n\t\t\t\taddDir: worktreePath,\n\t\t\t\theadless: false, // Interactive - runs in current terminal with stdio: inherit\n\t\t\t\tallowedTools: rebaseAllowedTools,\n\t\t\t\tnoSessionPersistence: true, // Utility operation - no session persistence needed\n\t\t\t})\n\n\t\t\t// After Claude interaction completes, check if conflicts resolved\n\t\t\tconst remainingConflicts = await this.detectConflictedFiles(worktreePath)\n\n\t\t\tif (remainingConflicts.length > 0) {\n\t\t\t\tgetLogger().warn(\n\t\t\t\t\t`Conflicts still exist in ${remainingConflicts.length} file(s) after Claude assistance`\n\t\t\t\t)\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Check if rebase completed or still in progress\n\t\t\tconst rebaseInProgress = await this.isRebaseInProgress(worktreePath)\n\n\t\t\tif (rebaseInProgress) {\n\t\t\t\tgetLogger().warn('Rebase still in progress after Claude assistance')\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\treturn true\n\t\t} catch (error) {\n\t\t\tgetLogger().warn('Claude conflict resolution failed', {\n\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t})\n\t\t\treturn false\n\t\t}\n\t}\n\n\t/**\n\t * Check if a git rebase is currently in progress\n\t * Checks for .git/rebase-merge or .git/rebase-apply directories\n\t * Ports bash script logic from lines 853-856\n\t *\n\t * @param worktreePath - Path to the worktree\n\t * @returns true if rebase in progress, false otherwise\n\t * @private\n\t */\n\tprivate async isRebaseInProgress(worktreePath: string): Promise<boolean> {\n\t\tconst fs = await import('node:fs/promises')\n\t\tconst path = await import('node:path')\n\n\t\tconst rebaseMergePath = path.join(worktreePath, '.git', 'rebase-merge')\n\t\tconst rebaseApplyPath = path.join(worktreePath, '.git', 'rebase-apply')\n\n\t\t// Check for rebase-merge directory\n\t\ttry {\n\t\t\tawait fs.access(rebaseMergePath)\n\t\t\treturn true\n\t\t} catch {\n\t\t\t// Directory doesn't exist, continue checking\n\t\t}\n\n\t\t// Check for rebase-apply directory\n\t\ttry {\n\t\t\tawait fs.access(rebaseApplyPath)\n\t\t\treturn true\n\t\t} catch {\n\t\t\t// Directory doesn't exist\n\t\t}\n\n\t\treturn false\n\t}\n}\n","import { getLogger } from '../utils/logger-context.js'\nimport { detectPackageManager, runScript } from '../utils/package-manager.js'\nimport { getPackageConfig, hasScript } from '../utils/package-json.js'\nimport { ProjectCapabilityDetector } from './ProjectCapabilityDetector.js'\n\nexport interface BuildOptions {\n\tdryRun?: boolean\n}\n\nexport interface BuildResult {\n\tsuccess: boolean\n\tskipped: boolean\n\treason?: string\n\tduration: number\n}\n\n/**\n * BuildRunner handles post-merge build verification for CLI projects\n * Only runs build when project has CLI capabilities (bin field in package.json)\n */\nexport class BuildRunner {\n\tprivate capabilityDetector: ProjectCapabilityDetector\n\n\tconstructor(capabilityDetector?: ProjectCapabilityDetector) {\n\t\tthis.capabilityDetector = capabilityDetector ?? new ProjectCapabilityDetector()\n\t}\n\n\t/**\n\t * Run build verification in the specified directory\n\t * @param buildPath - Path where build should run (typically main worktree path)\n\t * @param options - Build options\n\t */\n\tasync runBuild(buildPath: string, options: BuildOptions = {}): Promise<BuildResult> {\n\t\tconst startTime = Date.now()\n\n\t\ttry {\n\t\t\t// Step 1: Check if build script exists (checks .iloom/package.iloom.json first, then package.json)\n\t\t\tconst pkgJson = await getPackageConfig(buildPath)\n\t\t\tconst hasBuildScript = hasScript(pkgJson, 'build')\n\n\t\t\tif (!hasBuildScript) {\n\t\t\tgetLogger().debug('Skipping build - no build script found')\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tskipped: true,\n\t\t\t\t\treason: 'No build script found in package configuration',\n\t\t\t\t\tduration: Date.now() - startTime,\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\t// Handle missing package.json - skip build for non-Node.js projects without package.iloom.json\n\t\t\tif (error instanceof Error && error.message.includes('package.json not found')) {\n\t\t\tgetLogger().debug('Skipping build - no package configuration found')\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tskipped: true,\n\t\t\t\t\treason: 'No package configuration found in project',\n\t\t\t\t\tduration: Date.now() - startTime,\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Re-throw other errors\n\t\t\tthrow error\n\t\t}\n\n\t\t// Step 2: Check if project has CLI capability (bin field)\n\t\tconst capabilities = await this.capabilityDetector.detectCapabilities(buildPath)\n\t\tconst isCLIProject = capabilities.capabilities.includes('cli')\n\n\t\tif (!isCLIProject) {\n\t\tgetLogger().debug('Skipping build - not a CLI project (no bin field)')\n\t\t\treturn {\n\t\t\t\tsuccess: true,\n\t\t\t\tskipped: true,\n\t\t\t\treason: 'Project is not a CLI project (no bin field in package.json)',\n\t\t\t\tduration: Date.now() - startTime,\n\t\t\t}\n\t\t}\n\n\t\t// Step 3: Detect package manager\n\t\tconst packageManager = await detectPackageManager(buildPath)\n\n\t\t// Step 4: Handle dry-run mode\n\t\tif (options.dryRun) {\n\t\t\tconst command =\n\t\t\t\tpackageManager === 'npm' ? 'npm run build' : `${packageManager} build`\n\t\tgetLogger().info(`[DRY RUN] Would run: ${command}`)\n\t\t\treturn {\n\t\t\t\tsuccess: true,\n\t\t\t\tskipped: false,\n\t\t\t\tduration: Date.now() - startTime,\n\t\t\t}\n\t\t}\n\n\t\t// Step 5: Execute build\n\tgetLogger().info('Running build...')\n\n\t\ttry {\n\t\t\tawait runScript('build', buildPath, [], { quiet: true })\n\t\tgetLogger().success('Build completed successfully')\n\n\t\t\treturn {\n\t\t\t\tsuccess: true,\n\t\t\t\tskipped: false,\n\t\t\t\tduration: Date.now() - startTime,\n\t\t\t}\n\t\t} catch {\n\t\t\t// Step 6: Throw detailed error on failure\n\t\t\tconst runCommand =\n\t\t\t\tpackageManager === 'npm' ? 'npm run build' : `${packageManager} build`\n\n\t\t\tthrow new Error(\n\t\t\t\t`Error: Build failed.\\n` +\n\t\t\t\t\t`Fix build errors before proceeding.\\n\\n` +\n\t\t\t\t\t`Run '${runCommand}' to see detailed errors.`\n\t\t\t)\n\t\t}\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAaO,IAAM,eAAN,MAAmB;AAAA,EAIzB,YAAY,iBAAmC,iBAAmC;AACjF,SAAK,kBAAkB,mBAAmB,IAAI,gBAAgB;AAC9D,SAAK,kBAAkB,mBAAmB,IAAI,gBAAgB;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,cAAc,cAAwC;AAEnE,WAAO,qBAAqB,gBAAgB,QAAQ,IAAI,GAAG;AAAA,MAC1D,iBAAiB,KAAK;AAAA,MACtB,iBAAiB,KAAK;AAAA,IACvB,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,aAAa,cAAsB,UAAwB,CAAC,GAAkB;AA5CrF;AA6CE,UAAM,EAAE,SAAS,OAAO,QAAQ,MAAM,IAAI;AAC1C,UAAM,aAAa,MAAM,KAAK,cAAc,YAAY;AAMxD,UAAM,WAAW,MAAM,KAAK,gBAAgB,aAAa,YAAY;AACrE,UAAM,cAAc,CAAC,EAAC,qCAAU;AAChC,UAAM,WAAW,MAAM,KAAK,gBAAgB,aAAa,YAAY;AACrE,UAAM,sBAAoB,cAAS,kBAAT,mBAAwB,SAAQ;AAC1D,UAAM,WAAW,sBAAsB,eAAe,sBAAsB;AAC5E,UAAM,YAAY,YAAY,CAAC;AAE/B,QAAI;AACJ,QAAI,WAAW;AAEd,gBAAU,EAAE,KAAK,yBAAyB;AAC1C,YAAM,YAAY,YAAY;AAC9B,qBAAe,UAAU,UAAU;AAAA,IACpC,OAAO;AAEN,gBAAU,EAAE,KAAK,sBAAsB,UAAU,gBAAgB;AACjE,qBAAe;AAAA,IAChB;AAEA,cAAU,EAAE,KAAK,sBAAsB,YAAY,KAAK;AAGxD,UAAM,UAAU,YAAY,gBAAgB,YAAY,KAAK,cAAc,YAAY;AACvF,QAAI;AACH,YAAM,kBAAkB,CAAC,YAAY,YAAY,WAAW,OAAO,GAAG;AAAA,QACrE,KAAK;AAAA,MACN,CAAC;AAAA,IACF,QAAQ;AACP,UAAI,WAAW;AACd,cAAM,IAAI;AAAA,UACT,kBAAkB,YAAY;AAAA,+BACG,UAAU;AAAA,QAC5C;AAAA,MACD,OAAO;AACN,cAAM,IAAI;AAAA,UACT,iBAAiB,YAAY;AAAA;AAAA,QAE9B;AAAA,MACD;AAAA,IACD;AAGA,UAAM,eAAe,MAAM,kBAAkB,CAAC,UAAU,aAAa,GAAG;AAAA,MACvE,KAAK;AAAA,IACN,CAAC;AAED,QAAI,gBAA+B;AACnC,QAAI,aAAa,KAAK,GAAG;AACxB,gBAAU,EAAE,KAAK,gEAAgE;AACjF,sBAAgB,MAAM,KAAK,gBAAgB,YAAY;AACvD,gBAAU,EAAE,MAAM,uBAAuB,aAAa,EAAE;AAAA,IACzD;AAGA,UAAM,YAAY,MAAM,kBAAkB,CAAC,cAAc,cAAc,MAAM,GAAG;AAAA,MAC/E,KAAK;AAAA,IACN,CAAC;AAED,UAAM,aAAa,MAAM,kBAAkB,CAAC,aAAa,YAAY,GAAG;AAAA,MACvE,KAAK;AAAA,IACN,CAAC;AAED,UAAM,mBAAmB,UAAU,KAAK;AACxC,UAAM,oBAAoB,WAAW,KAAK;AAG1C,QAAI,qBAAqB,mBAAmB;AAC3C,gBAAU,EAAE,QAAQ,qCAAqC,YAAY,qBAAqB;AAE1F,UAAI,eAAe;AAClB,cAAM,KAAK,iBAAiB,cAAc,aAAa;AAAA,MACxD;AACA;AAAA,IACD;AAGA,UAAM,gBAAgB,MAAM,kBAAkB,CAAC,OAAO,aAAa,GAAG,YAAY,QAAQ,GAAG;AAAA,MAC5F,KAAK;AAAA,IACN,CAAC;AAED,UAAM,UAAU,cAAc,KAAK;AACnC,UAAM,cAAc,UAAU,QAAQ,MAAM,IAAI,IAAI,CAAC;AAErD,QAAI,SAAS;AAEZ,gBAAU,EAAE,KAAK,SAAS,YAAY,MAAM,uBAAuB;AACnE,kBAAY,QAAQ,CAAC,WAAW,UAAU,EAAE,KAAK,KAAK,MAAM,EAAE,CAAC;AAAA,IAChE,OAAO;AAEN,gBAAU,EAAE,KAAK,GAAG,YAAY,kDAAkD;AAAA,IACnF;AAGA,QAAI,CAAC,SAAS,CAAC,QAAQ;AAGtB,gBAAU,EAAE,KAAK,+DAA+D;AAAA,IACjF;AAGA,QAAI,QAAQ;AACX,gBAAU,EAAE,KAAK,uCAAuC,YAAY,EAAE;AACtE,UAAI,YAAY,SAAS,GAAG;AAC3B,kBAAU,EAAE,KAAK,+BAA+B,YAAY,MAAM,YAAY;AAAA,MAC/E;AACA;AAAA,IACD;AAKA,QAAI;AACH,YAAM,kBAAkB,CAAC,MAAM,4BAA4B,UAAU,YAAY,GAAG,EAAE,KAAK,aAAa,CAAC;AACzG,gBAAU,EAAE,QAAQ,gCAAgC;AAGpD,UAAI,eAAe;AAClB,cAAM,KAAK,iBAAiB,cAAc,aAAa;AAAA,MACxD;AAAA,IACD,SAAS,OAAO;AAEf,YAAM,kBAAkB,MAAM,KAAK,sBAAsB,YAAY;AAErE,UAAI,gBAAgB,SAAS,GAAG;AAE/B,kBAAU,EAAE,KAAK,oEAAoE;AAErF,cAAM,WAAW,MAAM,KAAK;AAAA,UAC3B;AAAA,UACA;AAAA,QACD;AAEA,YAAI,UAAU;AACb,oBAAU,EAAE,QAAQ,6DAA6D;AAGjF,cAAI,eAAe;AAClB,kBAAM,KAAK,iBAAiB,cAAc,aAAa;AAAA,UACxD;AACA;AAAA,QACD;AAGA,cAAM,gBAAgB,KAAK,oBAAoB,eAAe;AAC9D,cAAM,IAAI,MAAM,aAAa;AAAA,MAC9B;AAGA,YAAM,IAAI;AAAA,QACT,kBAAkB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA;AAAA;AAAA,MAGzE;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,4BAA4B,YAAoB,YAAoB,kBAAyC;AAGlH,UAAM,YAAY,MAAM,kBAAkB,CAAC,cAAc,YAAY,UAAU,GAAG;AAAA,MACjF,KAAK;AAAA,IACN,CAAC;AAGD,UAAM,WAAW,MAAM,kBAAkB,CAAC,aAAa,UAAU,GAAG;AAAA,MACnE,KAAK;AAAA,IACN,CAAC;AAGD,UAAM,mBAAmB,UAAU,KAAK;AACxC,UAAM,kBAAkB,SAAS,KAAK;AAEtC,QAAI,qBAAqB,iBAAiB;AACzC,YAAM,IAAI;AAAA,QACT;AAAA,MACQ,UAAU;AAAA,cACF,gBAAgB;AAAA,cAChB,eAAe;AAAA;AAAA;AAAA,4BAED,UAAU,gBAAgB,UAAU;AAAA;AAAA;AAAA,MAEnE;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,wBACL,YACA,cACA,UAAwB,CAAC,GACT;AAChB,UAAM,EAAE,SAAS,OAAO,QAAQ,MAAM,IAAI;AAE1C,cAAU,EAAE,KAAK,gCAAgC;AAKjD,UAAM,aAAa,MAAM,KAAK,cAAc,YAAY;AAKxD,QAAI;AACJ,QAAI,QAAQ,UAAU;AACrB,yBAAmB,QAAQ;AAAA,IAC5B,OAAO;AACN,UAAI;AAEH,2BAAmB,MAAM,sBAAsB,YAAY,YAAY;AAAA,MACxE,QAAQ;AAGP,kBAAU,EAAE,MAAM,iCAAiC,UAAU,0CAA0C;AACvG,2BAAmB,MAAM,iCAAiC,cAAc,KAAK,eAAe;AAAA,MAC7F;AAAA,IACD;AAGA,cAAU,EAAE,MAAM,SAAS,UAAU,qBAAqB,gBAAgB,EAAE;AAG5E,UAAM,gBAAgB,MAAM,kBAAkB,CAAC,UAAU,gBAAgB,GAAG;AAAA,MAC3E,KAAK;AAAA,IACN,CAAC;AAED,QAAI,cAAc,KAAK,MAAM,YAAY;AACxC,YAAM,IAAI;AAAA,QACT,YAAY,UAAU,sBAAsB,cAAc,KAAK,CAAC;AAAA,eAC/C,gBAAgB;AAAA;AAAA,MAElC;AAAA,IACD;AAGA,UAAM,KAAK,4BAA4B,YAAY,YAAY,gBAAgB;AAG/E,UAAM,gBAAgB,MAAM,kBAAkB,CAAC,OAAO,aAAa,GAAG,UAAU,KAAK,UAAU,EAAE,GAAG;AAAA,MACnG,KAAK;AAAA,IACN,CAAC;AAED,UAAM,UAAU,cAAc,KAAK;AAGnC,QAAI,CAAC,SAAS;AACb,gBAAU,EAAE,QAAQ,kCAAkC,UAAU,oBAAoB;AACpF;AAAA,IACD;AAGA,UAAM,cAAc,QAAQ,MAAM,IAAI;AACtC,cAAU,EAAE,KAAK,SAAS,YAAY,MAAM,sBAAsB;AAClE,gBAAY,QAAQ,CAAC,WAAW,UAAU,EAAE,KAAK,KAAK,MAAM,EAAE,CAAC;AAG/D,QAAI,CAAC,SAAS,CAAC,QAAQ;AAGtB,gBAAU,EAAE,KAAK,2EAA2E;AAAA,IAC7F;AAGA,QAAI,QAAQ;AACX,gBAAU,EAAE,KAAK,gDAAgD,UAAU,EAAE;AAC7E,gBAAU,EAAE,KAAK,8BAA8B,YAAY,MAAM,YAAY;AAC7E;AAAA,IACD;AAGA,QAAI;AACH,gBAAU,EAAE,MAAM,mCAAmC,UAAU,SAAS,UAAU,eAAe,gBAAgB,KAAK;AACtH,YAAM,kBAAkB,CAAC,SAAS,aAAa,UAAU,GAAG,EAAE,KAAK,iBAAiB,CAAC;AACrF,gBAAU,EAAE,QAAQ,wCAAwC,YAAY,MAAM,aAAa;AAAA,IAC5F,SAAS,OAAO;AACf,YAAM,IAAI;AAAA,QACT,8BAA8B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMrF;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,sBAAsB,cAAyC;AAC5E,QAAI;AACH,YAAM,SAAS,MAAM,kBAAkB,CAAC,QAAQ,eAAe,iBAAiB,GAAG;AAAA,QAClF,KAAK;AAAA,MACN,CAAC;AAED,aAAO,OACL,KAAK,EACL,MAAM,IAAI,EACV,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC;AAAA,IACnC,QAAQ;AAEP,aAAO,CAAC;AAAA,IACT;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAc,gBAAgB,cAAuC;AAEpE,UAAM,kBAAkB,CAAC,OAAO,IAAI,GAAG,EAAE,KAAK,aAAa,CAAC;AAI5D,UAAM,kBAAkB,CAAC,UAAU,eAAe,MAAM,4BAA4B,GAAG,EAAE,KAAK,aAAa,CAAC;AAG5G,UAAM,OAAO,MAAM,kBAAkB,CAAC,aAAa,MAAM,GAAG,EAAE,KAAK,aAAa,CAAC;AACjF,WAAO,KAAK,KAAK;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,iBAAiB,cAAsB,eAAsC;AAC1F,cAAU,EAAE,KAAK,kDAAkD;AAEnE,QAAI;AAEH,YAAM,kBAAkB,CAAC,SAAS,UAAU,QAAQ,GAAG,EAAE,KAAK,aAAa,CAAC;AAG5E,YAAM,kBAAkB,CAAC,SAAS,MAAM,GAAG,EAAE,KAAK,aAAa,CAAC;AAEhE,gBAAU,EAAE,QAAQ,8CAA8C;AAAA,IACnE,SAAS,OAAO;AAEf,gBAAU,EAAE;AAAA,QACX,iCAAiC,aAAa;AAAA,QAG9C,EAAE,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE;AAAA,MACjE;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,oBAAoB,iBAAmC;AAC9D,UAAM,WAAW,gBAAgB,IAAI,CAAC,SAAS,YAAO,IAAI,EAAE,EAAE,KAAK,IAAI;AAEvE,WACC,mDACA,WACA;AAAA,EAQF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAc,gCACb,cACA,iBACmB;AAEnB,UAAM,oBAAoB,MAAM,gBAAgB;AAChD,QAAI,CAAC,mBAAmB;AACvB,gBAAU,EAAE,MAAM,wDAAwD;AAC1E,aAAO;AAAA,IACR;AAEA,cAAU,EAAE,KAAK,4CAA4C,gBAAgB,MAAM,aAAa;AAIhG,UAAM,eACL;AAMD,UAAM,SACL;AAKD,UAAM,qBAAqB;AAAA,MAC1B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD;AAEA,QAAI;AAGH,YAAM,aAAa,QAAQ;AAAA,QAC1B,oBAAoB;AAAA,QACpB,QAAQ;AAAA,QACR,UAAU;AAAA;AAAA,QACV,cAAc;AAAA,QACd,sBAAsB;AAAA;AAAA,MACvB,CAAC;AAGD,YAAM,qBAAqB,MAAM,KAAK,sBAAsB,YAAY;AAExE,UAAI,mBAAmB,SAAS,GAAG;AAClC,kBAAU,EAAE;AAAA,UACX,4BAA4B,mBAAmB,MAAM;AAAA,QACtD;AACA,eAAO;AAAA,MACR;AAGA,YAAM,mBAAmB,MAAM,KAAK,mBAAmB,YAAY;AAEnE,UAAI,kBAAkB;AACrB,kBAAU,EAAE,KAAK,kDAAkD;AACnE,eAAO;AAAA,MACR;AAEA,aAAO;AAAA,IACR,SAAS,OAAO;AACf,gBAAU,EAAE,KAAK,qCAAqC;AAAA,QACrD,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,MAC7D,CAAC;AACD,aAAO;AAAA,IACR;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAc,mBAAmB,cAAwC;AACxE,UAAM,KAAK,MAAM,OAAO,aAAkB;AAC1C,UAAM,OAAO,MAAM,OAAO,MAAW;AAErC,UAAM,kBAAkB,KAAK,KAAK,cAAc,QAAQ,cAAc;AACtE,UAAM,kBAAkB,KAAK,KAAK,cAAc,QAAQ,cAAc;AAGtE,QAAI;AACH,YAAM,GAAG,OAAO,eAAe;AAC/B,aAAO;AAAA,IACR,QAAQ;AAAA,IAER;AAGA,QAAI;AACH,YAAM,GAAG,OAAO,eAAe;AAC/B,aAAO;AAAA,IACR,QAAQ;AAAA,IAER;AAEA,WAAO;AAAA,EACR;AACD;;;AC7hBO,IAAM,cAAN,MAAkB;AAAA,EAGxB,YAAY,oBAAgD;AAC3D,SAAK,qBAAqB,sBAAsB,IAAI,0BAA0B;AAAA,EAC/E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,SAAS,WAAmB,UAAwB,CAAC,GAAyB;AACnF,UAAM,YAAY,KAAK,IAAI;AAE3B,QAAI;AAEH,YAAM,UAAU,MAAM,iBAAiB,SAAS;AAChD,YAAM,iBAAiB,UAAU,SAAS,OAAO;AAEjD,UAAI,CAAC,gBAAgB;AACrB,kBAAU,EAAE,MAAM,wCAAwC;AACzD,eAAO;AAAA,UACN,SAAS;AAAA,UACT,SAAS;AAAA,UACT,QAAQ;AAAA,UACR,UAAU,KAAK,IAAI,IAAI;AAAA,QACxB;AAAA,MACD;AAAA,IACD,SAAS,OAAO;AAEf,UAAI,iBAAiB,SAAS,MAAM,QAAQ,SAAS,wBAAwB,GAAG;AAChF,kBAAU,EAAE,MAAM,iDAAiD;AAClE,eAAO;AAAA,UACN,SAAS;AAAA,UACT,SAAS;AAAA,UACT,QAAQ;AAAA,UACR,UAAU,KAAK,IAAI,IAAI;AAAA,QACxB;AAAA,MACD;AAEA,YAAM;AAAA,IACP;AAGA,UAAM,eAAe,MAAM,KAAK,mBAAmB,mBAAmB,SAAS;AAC/E,UAAM,eAAe,aAAa,aAAa,SAAS,KAAK;AAE7D,QAAI,CAAC,cAAc;AACnB,gBAAU,EAAE,MAAM,mDAAmD;AACpE,aAAO;AAAA,QACN,SAAS;AAAA,QACT,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,UAAU,KAAK,IAAI,IAAI;AAAA,MACxB;AAAA,IACD;AAGA,UAAM,iBAAiB,MAAM,qBAAqB,SAAS;AAG3D,QAAI,QAAQ,QAAQ;AACnB,YAAM,UACL,mBAAmB,QAAQ,kBAAkB,GAAG,cAAc;AAChE,gBAAU,EAAE,KAAK,wBAAwB,OAAO,EAAE;AACjD,aAAO;AAAA,QACN,SAAS;AAAA,QACT,SAAS;AAAA,QACT,UAAU,KAAK,IAAI,IAAI;AAAA,MACxB;AAAA,IACD;AAGD,cAAU,EAAE,KAAK,kBAAkB;AAElC,QAAI;AACH,YAAM,UAAU,SAAS,WAAW,CAAC,GAAG,EAAE,OAAO,KAAK,CAAC;AACxD,gBAAU,EAAE,QAAQ,8BAA8B;AAEjD,aAAO;AAAA,QACN,SAAS;AAAA,QACT,SAAS;AAAA,QACT,UAAU,KAAK,IAAI,IAAI;AAAA,MACxB;AAAA,IACD,QAAQ;AAEP,YAAM,aACL,mBAAmB,QAAQ,kBAAkB,GAAG,cAAc;AAE/D,YAAM,IAAI;AAAA,QACT;AAAA;AAAA;AAAA,OAES,UAAU;AAAA,MACpB;AAAA,IACD;AAAA,EACD;AACD;","names":[]}
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- IssueManagementProviderFactory
4
- } from "./chunk-UDZCTLD6.js";
5
2
  import {
6
3
  openBrowser
7
4
  } from "./chunk-YETJNRQM.js";
5
+ import {
6
+ IssueManagementProviderFactory
7
+ } from "./chunk-OK7LUTRW.js";
8
8
  import {
9
9
  getConfiguredRepoFromSettings,
10
10
  getEffectivePRTargetRemote,
@@ -12,11 +12,11 @@ import {
12
12
  } from "./chunk-FXDYIV3K.js";
13
13
  import {
14
14
  executeGhCommand
15
- } from "./chunk-THS5L54H.js";
15
+ } from "./chunk-TRUMP4DA.js";
16
16
  import {
17
17
  detectClaudeCli,
18
18
  launchClaude
19
- } from "./chunk-IGKPPACU.js";
19
+ } from "./chunk-FO5GGFOV.js";
20
20
  import {
21
21
  getLogger
22
22
  } from "./chunk-6MLEBAYZ.js";
@@ -298,10 +298,11 @@ Then retry: il finish`
298
298
  * @param branchName - Branch to create PR from (used as --head)
299
299
  * @param title - PR title
300
300
  * @param body - PR body
301
+ * @param baseBranch - Base branch to target (used as --base)
301
302
  * @param cwd - Working directory
302
303
  * @returns PR URL and number
303
304
  */
304
- async createDraftPR(branchName, title, body, cwd) {
305
+ async createDraftPR(branchName, title, body, baseBranch, cwd) {
305
306
  try {
306
307
  const targetRemote = await getEffectivePRTargetRemote(this.settings, cwd);
307
308
  let headValue = branchName;
@@ -313,7 +314,7 @@ Then retry: il finish`
313
314
  getLogger().debug(`Fork workflow detected, using head: ${headValue}`);
314
315
  }
315
316
  }
316
- const args = ["pr", "create", "--head", headValue, "--title", title, "--body", body, "--draft"];
317
+ const args = ["pr", "create", "--head", headValue, "--title", title, "--body", body, "--base", baseBranch, "--draft"];
317
318
  if (targetRemote !== "origin") {
318
319
  const repo = await getConfiguredRepoFromSettings(this.settings, cwd);
319
320
  args.push("--repo", repo);
@@ -358,4 +359,4 @@ Then retry: il start`
358
359
  export {
359
360
  PRManager
360
361
  };
361
- //# sourceMappingURL=chunk-44Y5IF7P.js.map
362
+ //# sourceMappingURL=chunk-I3HMNWQQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/PRManager.ts"],"sourcesContent":["import { executeGhCommand } from '../utils/github.js'\nimport { launchClaude, detectClaudeCli } from '../utils/claude.js'\nimport { getEffectivePRTargetRemote, getConfiguredRepoFromSettings, parseGitRemotes } from '../utils/remote.js'\nimport { openBrowser } from '../utils/browser.js'\nimport { getLogger } from '../utils/logger-context.js'\nimport type { IloomSettings } from './SettingsManager.js'\nimport { IssueManagementProviderFactory } from '../mcp/IssueManagementProviderFactory.js'\n\ninterface ExistingPR {\n\tnumber: number\n\turl: string\n}\n\ninterface PRCreationResult {\n\turl: string\n\tnumber: number\n\twasExisting: boolean\n}\n\nexport class PRManager {\n\tconstructor(private settings: IloomSettings) {\n\t\t// Uses getLogger() for all logging operations\n\t}\n\n\t/**\n\t * Get the issue prefix from the configured provider\n\t */\n\tpublic get issuePrefix(): string {\n\t\tconst providerType = this.settings.issueManagement?.provider ?? 'github'\n\t\tconst provider = IssueManagementProviderFactory.create(providerType)\n\t\treturn provider.issuePrefix\n\t}\n\n\t/**\n\t * Check if a PR already exists for the given branch\n\t * @param branchName - Branch to check\n\t * @param cwd - Working directory\n\t * @returns Existing PR info or null if none found\n\t */\n\tasync checkForExistingPR(branchName: string, cwd?: string): Promise<ExistingPR | null> {\n\t\ttry {\n\t\t\tconst prList = await executeGhCommand<Array<{ number: number; url: string }>>(\n\t\t\t\t['pr', 'list', '--head', branchName, '--state', 'open', '--json', 'number,url'],\n\t\t\t\tcwd ? { cwd } : undefined\n\t\t\t)\n\n\t\t\tif (prList.length > 0) {\n\t\t\t\treturn prList[0] ?? null // Return first match\n\t\t\t}\n\n\t\t\treturn null\n\t\t} catch (error) {\n\t\tgetLogger().debug('Error checking for existing PR', { error })\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/**\n\t * Generate PR body using Claude if available, otherwise use simple template\n\t * @param issueNumber - Issue number to include in body\n\t * @param worktreePath - Path to worktree for context\n\t * @returns PR body markdown\n\t */\n\tasync generatePRBody(issueNumber: string | number | undefined, worktreePath: string): Promise<string> {\n\t\t// Try Claude first for rich body generation\n\t\tconst hasClaudeCli = await detectClaudeCli()\n\n\t\tif (hasClaudeCli) {\n\t\t\ttry {\n\t\t\t\tconst prompt = this.buildPRBodyPrompt(issueNumber)\n\n\t\t\t\tconst body = await launchClaude(prompt, {\n\t\t\t\t\theadless: true,\n\t\t\t\t\taddDir: worktreePath,\n\t\t\t\t\ttimeout: 30000,\n\t\t\t\t\tnoSessionPersistence: true, // Utility operation - don't persist session\n\t\t\t\t})\n\n\t\t\t\tif (body && typeof body === 'string' && body.trim()) {\n\t\t\t\t\tconst sanitized = this.sanitizeClaudeOutput(body)\n\t\t\t\t\tif (sanitized) {\n\t\t\t\t\t\treturn sanitized\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\tgetLogger().debug('Claude PR body generation failed, using template', { error })\n\t\t\t}\n\t\t}\n\n\t\t// Fallback to simple template\n\t\tlet body = 'This PR contains changes from the iloom workflow.\\n\\n'\n\n\t\tif (issueNumber) {\n\t\t\tbody += `Fixes ${this.issuePrefix}${issueNumber}`\n\t\t}\n\n\t\treturn body\n\t}\n\n\t/**\n\t * Build structured XML prompt for PR body generation\n\t * Uses XML format for clear task definition and output expectations\n\t */\n\tprivate buildPRBodyPrompt(issueNumber?: string | number): string {\n\t\tconst issueContext = issueNumber\n\t\t\t? `\\n<IssueContext>\nThis PR is associated with issue ${this.issuePrefix}${issueNumber}.\nInclude \"Fixes ${this.issuePrefix}${issueNumber}\" at the end of the body on its own line.\n</IssueContext>`\n\t\t\t: ''\n\n\t\tconst examplePrefix = this.issuePrefix || '' // Use empty string for Linear examples\n\t\treturn `<Task>\nYou are a software engineer writing a pull request body for this repository.\nExamine the changes in the git repository and generate a concise, professional PR description.\n</Task>\n\n<Requirements>\n<Format>Write 2-3 sentences summarizing what was changed and why.${issueNumber ? `\\n\\nEnd with \"Fixes ${this.issuePrefix}${issueNumber}\" on its own line.` : ''}</Format>\n<Tone>Professional and concise</Tone>\n<Focus>Summarize the changes and their purpose</Focus>\n<NoMeta>CRITICAL: Do NOT include ANY explanatory text, analysis, or meta-commentary. Output ONLY the raw PR body text.</NoMeta>\n<Examples>\nGood: \"Add user authentication with JWT tokens to secure the API endpoints. This includes login and registration endpoints with proper password hashing.\n\nFixes ${examplePrefix}42\"\nGood: \"Fix navigation bug in sidebar menu that caused incorrect highlighting on nested routes.\"\nBad: \"Here's the PR body:\\n\\n---\\n\\nAdd user authentication...\"\nBad: \"Based on the changes, I'll write: Fix navigation bug...\"\n</Examples>\n${issueContext}\n</Requirements>\n\n<Output>\nIMPORTANT: Your entire response will be used directly as the GitHub pull request body.\nDo not include any explanatory text, headers, or separators before or after the body.\nStart your response immediately with the PR body text.\n</Output>`\n\t}\n\n\t/**\n\t * Sanitize Claude output to remove meta-commentary and clean formatting\n\t * Handles cases where Claude includes explanatory text despite instructions\n\t */\n\tprivate sanitizeClaudeOutput(rawOutput: string): string {\n\t\tlet cleaned = rawOutput.trim()\n\n\t\t// Remove common meta-commentary patterns (case-insensitive)\n\t\tconst metaPatterns = [\n\t\t\t/^.*?based on.*?changes.*?:/i,\n\t\t\t/^.*?looking at.*?files.*?:/i,\n\t\t\t/^.*?examining.*?:/i,\n\t\t\t/^.*?analyzing.*?:/i,\n\t\t\t/^.*?i'll.*?generate.*?:/i,\n\t\t\t/^.*?let me.*?:/i,\n\t\t\t/^.*?here.*?is.*?(?:the\\s+)?(?:pr|pull request).*?body.*?:/i,\n\t\t\t/^.*?here's.*?(?:the\\s+)?(?:pr|pull request).*?body.*?:/i,\n\t\t]\n\n\t\tfor (const pattern of metaPatterns) {\n\t\t\tcleaned = cleaned.replace(pattern, '').trim()\n\t\t}\n\n\t\t// Remove leading separator lines (---, ===, etc.)\n\t\tcleaned = cleaned.replace(/^[-=]{3,}\\s*/m, '').trim()\n\n\t\t// Extract content after separators only if it looks like meta-commentary\n\t\tif (cleaned.includes(':')) {\n\t\t\tconst colonIndex = cleaned.indexOf(':')\n\t\t\tconst beforeColon = cleaned.substring(0, colonIndex).trim().toLowerCase()\n\n\t\t\t// Only split if the text before colon looks like meta-commentary\n\t\t\tconst metaIndicators = [\n\t\t\t\t'here is the pr body',\n\t\t\t\t'here is the pull request body',\n\t\t\t\t'pr body',\n\t\t\t\t'pull request body',\n\t\t\t\t'here is',\n\t\t\t\t\"here's\",\n\t\t\t\t'the body should be',\n\t\t\t\t'i suggest',\n\t\t\t\t'my suggestion'\n\t\t\t]\n\n\t\t\tconst isMetaCommentary = metaIndicators.some(indicator => beforeColon.includes(indicator))\n\n\t\t\tif (isMetaCommentary) {\n\t\t\t\tconst afterColon = cleaned.substring(colonIndex + 1).trim()\n\t\t\t\t// Remove leading separator after colon\n\t\t\t\tconst afterSeparator = afterColon.replace(/^[-=]{3,}\\s*/m, '').trim()\n\t\t\t\tif (afterSeparator && afterSeparator.length > 10) {\n\t\t\t\t\tcleaned = afterSeparator\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Remove quotes if the entire message is wrapped in them\n\t\tif ((cleaned.startsWith('\"') && cleaned.endsWith('\"')) ||\n\t\t\t(cleaned.startsWith(\"'\") && cleaned.endsWith(\"'\"))) {\n\t\t\tcleaned = cleaned.slice(1, -1).trim()\n\t\t}\n\n\t\treturn cleaned\n\t}\n\n\t/**\n\t * Create a GitHub PR for the branch\n\t * @param branchName - Branch to create PR from (used as --head)\n\t * @param title - PR title\n\t * @param body - PR body\n\t * @param baseBranch - Base branch to target (usually main/master)\n\t * @param cwd - Working directory\n\t * @returns PR URL\n\t */\n\tasync createPR(\n\t\tbranchName: string,\n\t\ttitle: string,\n\t\tbody: string,\n\t\tbaseBranch: string,\n\t\tcwd?: string\n\t): Promise<string> {\n\t\ttry {\n\t\t\t// Get the target remote for the PR\n\t\t\tconst targetRemote = await getEffectivePRTargetRemote(this.settings, cwd)\n\n\t\t\t// Determine the correct --head value\n\t\t\t// For fork workflows (target != origin), we need \"username:branch\" format\n\t\t\t// See: https://github.com/cli/cli/issues/2691\n\t\t\tlet headValue = branchName\n\n\t\t\tif (targetRemote !== 'origin') {\n\t\t\t\t// Fork workflow: need to specify the head as \"owner:branch\"\n\t\t\t\t// Get the owner of the origin remote (where we pushed the branch)\n\t\t\t\tconst remotes = await parseGitRemotes(cwd)\n\t\t\t\tconst originRemote = remotes.find(r => r.name === 'origin')\n\n\t\t\t\tif (originRemote) {\n\t\t\t\t\theadValue = `${originRemote.owner}:${branchName}`\n\t\t\t\tgetLogger().debug(`Fork workflow detected, using head: ${headValue}`)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Build gh pr create command\n\t\t\t// Note: gh pr create returns a plain URL string, not JSON\n\t\t\tconst args = ['pr', 'create', '--head', headValue, '--title', title, '--body', body, '--base', baseBranch]\n\n\t\t\t// If target remote is not 'origin', we need to specify the repo\n\t\t\tif (targetRemote !== 'origin') {\n\t\t\t\tconst repo = await getConfiguredRepoFromSettings(this.settings, cwd)\n\t\t\t\targs.push('--repo', repo)\n\t\t\t}\n\n\t\t\t// gh pr create returns the PR URL as plain text (not JSON)\n\t\t\tconst result = await executeGhCommand<string>(args, cwd ? { cwd } : undefined)\n\n\t\t\t// Result is a string URL like \"https://github.com/owner/repo/pull/123\"\n\t\t\tconst url = typeof result === 'string' ? result.trim() : String(result).trim()\n\n\t\t\tif (!url.includes('github.com') || !url.includes('/pull/')) {\n\t\t\t\tthrow new Error(`Unexpected response from gh pr create: ${url}`)\n\t\t\t}\n\n\t\t\treturn url\n\t\t} catch (error) {\n\t\t\tconst errorMessage = error instanceof Error ? error.message : String(error)\n\n\t\t\t// Provide helpful error message for common GraphQL errors\n\t\t\tif (errorMessage.includes(\"Head sha can't be blank\") || errorMessage.includes(\"No commits between\")) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Failed to create pull request: ${errorMessage}\\n\\n` +\n\t\t\t\t\t`This error typically occurs when:\\n` +\n\t\t\t\t\t` - The branch was not fully pushed to the remote\\n` +\n\t\t\t\t\t` - There's a race condition between push and PR creation\\n` +\n\t\t\t\t\t` - The branch has no commits ahead of the base branch\\n\\n` +\n\t\t\t\t\t`Try running: git push -u origin ${branchName}\\n` +\n\t\t\t\t\t`Then retry: il finish`\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tthrow new Error(`Failed to create pull request: ${errorMessage}`)\n\t\t}\n\t}\n\n\t/**\n\t * Open PR URL in browser\n\t * @param url - PR URL to open\n\t */\n\tasync openPRInBrowser(url: string): Promise<void> {\n\t\ttry {\n\t\t\tawait openBrowser(url)\n\t\tgetLogger().debug('Opened PR in browser', { url })\n\t\t} catch (error) {\n\t\t\t// Don't fail the whole operation if browser opening fails\n\t\tgetLogger().warn('Failed to open PR in browser', { error })\n\t\t}\n\t}\n\n\t/**\n\t * Complete PR workflow: check for existing, create if needed, optionally open in browser\n\t * @param branchName - Branch to create PR from\n\t * @param title - PR title\n\t * @param issueNumber - Optional issue number for body generation\n\t * @param baseBranch - Base branch to target\n\t * @param worktreePath - Path to worktree\n\t * @param openInBrowser - Whether to open PR in browser\n\t * @returns PR creation result\n\t */\n\tasync createOrOpenPR(\n\t\tbranchName: string,\n\t\ttitle: string,\n\t\tissueNumber: string | number | undefined,\n\t\tbaseBranch: string,\n\t\tworktreePath: string,\n\t\topenInBrowser: boolean\n\t): Promise<PRCreationResult> {\n\t\t// Check for existing PR\n\t\tconst existingPR = await this.checkForExistingPR(branchName, worktreePath)\n\n\t\tif (existingPR) {\n\t\tgetLogger().info(`Pull request already exists: ${existingPR.url}`)\n\n\t\t\tif (openInBrowser) {\n\t\t\t\tawait this.openPRInBrowser(existingPR.url)\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\turl: existingPR.url,\n\t\t\t\tnumber: existingPR.number,\n\t\t\t\twasExisting: true,\n\t\t\t}\n\t\t}\n\n\t\t// Generate PR body\n\t\tconst body = await this.generatePRBody(issueNumber, worktreePath)\n\n\t\t// Create new PR\n\tgetLogger().info('Creating pull request...')\n\t\tconst url = await this.createPR(branchName, title, body, baseBranch, worktreePath)\n\n\t\t// Extract PR number from URL\n\t\tconst prNumber = this.extractPRNumberFromUrl(url)\n\n\t\tif (openInBrowser) {\n\t\t\tawait this.openPRInBrowser(url)\n\t\t}\n\n\t\treturn {\n\t\t\turl,\n\t\t\tnumber: prNumber,\n\t\t\twasExisting: false,\n\t\t}\n\t}\n\n\t/**\n\t * Extract PR number from GitHub PR URL\n\t * @param url - PR URL (e.g., https://github.com/owner/repo/pull/123)\n\t * @returns PR number\n\t */\n\tprivate extractPRNumberFromUrl(url: string): number {\n\t\tconst match = url.match(/\\/pull\\/(\\d+)/)\n\t\tif (match?.[1]) {\n\t\t\treturn parseInt(match[1], 10)\n\t\t}\n\t\tthrow new Error(`Could not extract PR number from URL: ${url}`)\n\t}\n\n\t/**\n\t * Create a draft PR for the branch\n\t * Used by github-draft-pr mode during il start\n\t * @param branchName - Branch to create PR from (used as --head)\n\t * @param title - PR title\n\t * @param body - PR body\n\t * @param baseBranch - Base branch to target (used as --base)\n\t * @param cwd - Working directory\n\t * @returns PR URL and number\n\t */\n\tasync createDraftPR(\n\t\tbranchName: string,\n\t\ttitle: string,\n\t\tbody: string,\n\t\tbaseBranch: string,\n\t\tcwd?: string\n\t): Promise<{ url: string; number: number }> {\n\t\ttry {\n\t\t\t// Get the target remote for the PR\n\t\t\tconst targetRemote = await getEffectivePRTargetRemote(this.settings, cwd)\n\n\t\t\t// Determine the correct --head value\n\t\t\t// For fork workflows (target != origin), we need \"username:branch\" format\n\t\t\tlet headValue = branchName\n\n\t\t\tif (targetRemote !== 'origin') {\n\t\t\t\t// Fork workflow: need to specify the head as \"owner:branch\"\n\t\t\t\tconst remotes = await parseGitRemotes(cwd)\n\t\t\t\tconst originRemote = remotes.find(r => r.name === 'origin')\n\n\t\t\t\tif (originRemote) {\n\t\t\t\t\theadValue = `${originRemote.owner}:${branchName}`\n\t\t\t\t\tgetLogger().debug(`Fork workflow detected, using head: ${headValue}`)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Build gh pr create command with --draft flag\n\t\t\tconst args = ['pr', 'create', '--head', headValue, '--title', title, '--body', body, '--base', baseBranch, '--draft']\n\n\t\t\t// If target remote is not 'origin', we need to specify the repo\n\t\t\tif (targetRemote !== 'origin') {\n\t\t\t\tconst repo = await getConfiguredRepoFromSettings(this.settings, cwd)\n\t\t\t\targs.push('--repo', repo)\n\t\t\t}\n\n\t\t\t// gh pr create returns the PR URL as plain text (not JSON)\n\t\t\tconst result = await executeGhCommand<string>(args, cwd ? { cwd } : undefined)\n\n\t\t\t// Result is a string URL like \"https://github.com/owner/repo/pull/123\"\n\t\t\tconst url = typeof result === 'string' ? result.trim() : String(result).trim()\n\n\t\t\tif (!url.includes('github.com') || !url.includes('/pull/')) {\n\t\t\t\tthrow new Error(`Unexpected response from gh pr create --draft: ${url}`)\n\t\t\t}\n\n\t\t\tconst number = this.extractPRNumberFromUrl(url)\n\n\t\t\treturn { url, number }\n\t\t} catch (error) {\n\t\t\tconst errorMessage = error instanceof Error ? error.message : String(error)\n\n\t\t\t// Provide helpful error message for common errors\n\t\t\tif (errorMessage.includes(\"Head sha can't be blank\") || errorMessage.includes(\"No commits between\")) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Failed to create draft pull request: ${errorMessage}\\n\\n` +\n\t\t\t\t\t`This error typically occurs when:\\n` +\n\t\t\t\t\t` - The branch was not fully pushed to the remote\\n` +\n\t\t\t\t\t` - The branch has no commits ahead of the base branch\\n\\n` +\n\t\t\t\t\t`Try running: git push -u origin ${branchName}\\n` +\n\t\t\t\t\t`Then retry: il start`\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tthrow new Error(`Failed to create draft pull request: ${errorMessage}`)\n\t\t}\n\t}\n\n\t/**\n\t * Mark a draft PR as ready for review\n\t * Used by github-draft-pr mode during il finish\n\t * @param prNumber - PR number to mark ready\n\t * @param cwd - Working directory\n\t */\n\tasync markPRReady(prNumber: number, cwd?: string): Promise<void> {\n\t\tconst args = ['pr', 'ready', String(prNumber)]\n\t\tawait executeGhCommand(args, cwd ? { cwd } : undefined)\n\t\tgetLogger().info(`Marked PR #${prNumber} as ready for review`)\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAmBO,IAAM,YAAN,MAAgB;AAAA,EACtB,YAAoB,UAAyB;AAAzB;AAAA,EAEpB;AAAA;AAAA;AAAA;AAAA,EAKA,IAAW,cAAsB;AA3BlC;AA4BE,UAAM,iBAAe,UAAK,SAAS,oBAAd,mBAA+B,aAAY;AAChE,UAAM,WAAW,+BAA+B,OAAO,YAAY;AACnE,WAAO,SAAS;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,mBAAmB,YAAoB,KAA0C;AACtF,QAAI;AACH,YAAM,SAAS,MAAM;AAAA,QACpB,CAAC,MAAM,QAAQ,UAAU,YAAY,WAAW,QAAQ,UAAU,YAAY;AAAA,QAC9E,MAAM,EAAE,IAAI,IAAI;AAAA,MACjB;AAEA,UAAI,OAAO,SAAS,GAAG;AACtB,eAAO,OAAO,CAAC,KAAK;AAAA,MACrB;AAEA,aAAO;AAAA,IACR,SAAS,OAAO;AAChB,gBAAU,EAAE,MAAM,kCAAkC,EAAE,MAAM,CAAC;AAC5D,aAAO;AAAA,IACR;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,eAAe,aAA0C,cAAuC;AAErG,UAAM,eAAe,MAAM,gBAAgB;AAE3C,QAAI,cAAc;AACjB,UAAI;AACH,cAAM,SAAS,KAAK,kBAAkB,WAAW;AAEjD,cAAMA,QAAO,MAAM,aAAa,QAAQ;AAAA,UACvC,UAAU;AAAA,UACV,QAAQ;AAAA,UACR,SAAS;AAAA,UACT,sBAAsB;AAAA;AAAA,QACvB,CAAC;AAED,YAAIA,SAAQ,OAAOA,UAAS,YAAYA,MAAK,KAAK,GAAG;AACpD,gBAAM,YAAY,KAAK,qBAAqBA,KAAI;AAChD,cAAI,WAAW;AACd,mBAAO;AAAA,UACR;AAAA,QACD;AAAA,MACD,SAAS,OAAO;AAChB,kBAAU,EAAE,MAAM,oDAAoD,EAAE,MAAM,CAAC;AAAA,MAC/E;AAAA,IACD;AAGA,QAAI,OAAO;AAEX,QAAI,aAAa;AAChB,cAAQ,SAAS,KAAK,WAAW,GAAG,WAAW;AAAA,IAChD;AAEA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,kBAAkB,aAAuC;AAChE,UAAM,eAAe,cAClB;AAAA;AAAA,mCAC8B,KAAK,WAAW,GAAG,WAAW;AAAA,iBAChD,KAAK,WAAW,GAAG,WAAW;AAAA,mBAE1C;AAEH,UAAM,gBAAgB,KAAK,eAAe;AAC1C,WAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mEAM0D,cAAc;AAAA;AAAA,kBAAuB,KAAK,WAAW,GAAG,WAAW,uBAAuB,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAOvJ,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAKnB,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQb;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,qBAAqB,WAA2B;AACvD,QAAI,UAAU,UAAU,KAAK;AAG7B,UAAM,eAAe;AAAA,MACpB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD;AAEA,eAAW,WAAW,cAAc;AACnC,gBAAU,QAAQ,QAAQ,SAAS,EAAE,EAAE,KAAK;AAAA,IAC7C;AAGA,cAAU,QAAQ,QAAQ,iBAAiB,EAAE,EAAE,KAAK;AAGpD,QAAI,QAAQ,SAAS,GAAG,GAAG;AAC1B,YAAM,aAAa,QAAQ,QAAQ,GAAG;AACtC,YAAM,cAAc,QAAQ,UAAU,GAAG,UAAU,EAAE,KAAK,EAAE,YAAY;AAGxE,YAAM,iBAAiB;AAAA,QACtB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACD;AAEA,YAAM,mBAAmB,eAAe,KAAK,eAAa,YAAY,SAAS,SAAS,CAAC;AAEzF,UAAI,kBAAkB;AACrB,cAAM,aAAa,QAAQ,UAAU,aAAa,CAAC,EAAE,KAAK;AAE1D,cAAM,iBAAiB,WAAW,QAAQ,iBAAiB,EAAE,EAAE,KAAK;AACpE,YAAI,kBAAkB,eAAe,SAAS,IAAI;AACjD,oBAAU;AAAA,QACX;AAAA,MACD;AAAA,IACD;AAGA,QAAK,QAAQ,WAAW,GAAG,KAAK,QAAQ,SAAS,GAAG,KAClD,QAAQ,WAAW,GAAG,KAAK,QAAQ,SAAS,GAAG,GAAI;AACpD,gBAAU,QAAQ,MAAM,GAAG,EAAE,EAAE,KAAK;AAAA,IACrC;AAEA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,SACL,YACA,OACA,MACA,YACA,KACkB;AAClB,QAAI;AAEH,YAAM,eAAe,MAAM,2BAA2B,KAAK,UAAU,GAAG;AAKxE,UAAI,YAAY;AAEhB,UAAI,iBAAiB,UAAU;AAG9B,cAAM,UAAU,MAAM,gBAAgB,GAAG;AACzC,cAAM,eAAe,QAAQ,KAAK,OAAK,EAAE,SAAS,QAAQ;AAE1D,YAAI,cAAc;AACjB,sBAAY,GAAG,aAAa,KAAK,IAAI,UAAU;AAChD,oBAAU,EAAE,MAAM,uCAAuC,SAAS,EAAE;AAAA,QACpE;AAAA,MACD;AAIA,YAAM,OAAO,CAAC,MAAM,UAAU,UAAU,WAAW,WAAW,OAAO,UAAU,MAAM,UAAU,UAAU;AAGzG,UAAI,iBAAiB,UAAU;AAC9B,cAAM,OAAO,MAAM,8BAA8B,KAAK,UAAU,GAAG;AACnE,aAAK,KAAK,UAAU,IAAI;AAAA,MACzB;AAGA,YAAM,SAAS,MAAM,iBAAyB,MAAM,MAAM,EAAE,IAAI,IAAI,MAAS;AAG7E,YAAM,MAAM,OAAO,WAAW,WAAW,OAAO,KAAK,IAAI,OAAO,MAAM,EAAE,KAAK;AAE7E,UAAI,CAAC,IAAI,SAAS,YAAY,KAAK,CAAC,IAAI,SAAS,QAAQ,GAAG;AAC3D,cAAM,IAAI,MAAM,0CAA0C,GAAG,EAAE;AAAA,MAChE;AAEA,aAAO;AAAA,IACR,SAAS,OAAO;AACf,YAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAG1E,UAAI,aAAa,SAAS,yBAAyB,KAAK,aAAa,SAAS,oBAAoB,GAAG;AACpG,cAAM,IAAI;AAAA,UACT,kCAAkC,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kCAKX,UAAU;AAAA;AAAA,QAE9C;AAAA,MACD;AAEA,YAAM,IAAI,MAAM,kCAAkC,YAAY,EAAE;AAAA,IACjE;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,gBAAgB,KAA4B;AACjD,QAAI;AACH,YAAM,YAAY,GAAG;AACtB,gBAAU,EAAE,MAAM,wBAAwB,EAAE,IAAI,CAAC;AAAA,IACjD,SAAS,OAAO;AAEhB,gBAAU,EAAE,KAAK,gCAAgC,EAAE,MAAM,CAAC;AAAA,IAC1D;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,eACL,YACA,OACA,aACA,YACA,cACA,eAC4B;AAE5B,UAAM,aAAa,MAAM,KAAK,mBAAmB,YAAY,YAAY;AAEzE,QAAI,YAAY;AAChB,gBAAU,EAAE,KAAK,gCAAgC,WAAW,GAAG,EAAE;AAEhE,UAAI,eAAe;AAClB,cAAM,KAAK,gBAAgB,WAAW,GAAG;AAAA,MAC1C;AAEA,aAAO;AAAA,QACN,KAAK,WAAW;AAAA,QAChB,QAAQ,WAAW;AAAA,QACnB,aAAa;AAAA,MACd;AAAA,IACD;AAGA,UAAM,OAAO,MAAM,KAAK,eAAe,aAAa,YAAY;AAGjE,cAAU,EAAE,KAAK,0BAA0B;AAC1C,UAAM,MAAM,MAAM,KAAK,SAAS,YAAY,OAAO,MAAM,YAAY,YAAY;AAGjF,UAAM,WAAW,KAAK,uBAAuB,GAAG;AAEhD,QAAI,eAAe;AAClB,YAAM,KAAK,gBAAgB,GAAG;AAAA,IAC/B;AAEA,WAAO;AAAA,MACN;AAAA,MACA,QAAQ;AAAA,MACR,aAAa;AAAA,IACd;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,uBAAuB,KAAqB;AACnD,UAAM,QAAQ,IAAI,MAAM,eAAe;AACvC,QAAI,+BAAQ,IAAI;AACf,aAAO,SAAS,MAAM,CAAC,GAAG,EAAE;AAAA,IAC7B;AACA,UAAM,IAAI,MAAM,yCAAyC,GAAG,EAAE;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,cACL,YACA,OACA,MACA,YACA,KAC2C;AAC3C,QAAI;AAEH,YAAM,eAAe,MAAM,2BAA2B,KAAK,UAAU,GAAG;AAIxE,UAAI,YAAY;AAEhB,UAAI,iBAAiB,UAAU;AAE9B,cAAM,UAAU,MAAM,gBAAgB,GAAG;AACzC,cAAM,eAAe,QAAQ,KAAK,OAAK,EAAE,SAAS,QAAQ;AAE1D,YAAI,cAAc;AACjB,sBAAY,GAAG,aAAa,KAAK,IAAI,UAAU;AAC/C,oBAAU,EAAE,MAAM,uCAAuC,SAAS,EAAE;AAAA,QACrE;AAAA,MACD;AAGA,YAAM,OAAO,CAAC,MAAM,UAAU,UAAU,WAAW,WAAW,OAAO,UAAU,MAAM,UAAU,YAAY,SAAS;AAGpH,UAAI,iBAAiB,UAAU;AAC9B,cAAM,OAAO,MAAM,8BAA8B,KAAK,UAAU,GAAG;AACnE,aAAK,KAAK,UAAU,IAAI;AAAA,MACzB;AAGA,YAAM,SAAS,MAAM,iBAAyB,MAAM,MAAM,EAAE,IAAI,IAAI,MAAS;AAG7E,YAAM,MAAM,OAAO,WAAW,WAAW,OAAO,KAAK,IAAI,OAAO,MAAM,EAAE,KAAK;AAE7E,UAAI,CAAC,IAAI,SAAS,YAAY,KAAK,CAAC,IAAI,SAAS,QAAQ,GAAG;AAC3D,cAAM,IAAI,MAAM,kDAAkD,GAAG,EAAE;AAAA,MACxE;AAEA,YAAM,SAAS,KAAK,uBAAuB,GAAG;AAE9C,aAAO,EAAE,KAAK,OAAO;AAAA,IACtB,SAAS,OAAO;AACf,YAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAG1E,UAAI,aAAa,SAAS,yBAAyB,KAAK,aAAa,SAAS,oBAAoB,GAAG;AACpG,cAAM,IAAI;AAAA,UACT,wCAAwC,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kCAIjB,UAAU;AAAA;AAAA,QAE9C;AAAA,MACD;AAEA,YAAM,IAAI,MAAM,wCAAwC,YAAY,EAAE;AAAA,IACvE;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,YAAY,UAAkB,KAA6B;AAChE,UAAM,OAAO,CAAC,MAAM,SAAS,OAAO,QAAQ,CAAC;AAC7C,UAAM,iBAAiB,MAAM,MAAM,EAAE,IAAI,IAAI,MAAS;AACtD,cAAU,EAAE,KAAK,cAAc,QAAQ,sBAAsB;AAAA,EAC9D;AACD;","names":["body"]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getRepoInfo
4
- } from "./chunk-THS5L54H.js";
4
+ } from "./chunk-TRUMP4DA.js";
5
5
  import {
6
6
  logger
7
7
  } from "./chunk-VT4PDUYT.js";
@@ -118,4 +118,4 @@ export {
118
118
  generateIssueManagementMcpConfig,
119
119
  generateRecapMcpConfig
120
120
  };
121
- //# sourceMappingURL=chunk-XJHQVOT6.js.map
121
+ //# sourceMappingURL=chunk-J7FJ6PUT.js.map
@@ -0,0 +1,302 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ GitWorktreeManager
4
+ } from "./chunk-4GAJJUYS.js";
5
+ import {
6
+ logger
7
+ } from "./chunk-VT4PDUYT.js";
8
+
9
+ // src/lib/ShellCompletion.ts
10
+ import omelette from "omelette";
11
+ import { readFile } from "fs/promises";
12
+ import { existsSync } from "fs";
13
+ import path from "path";
14
+ import os from "os";
15
+ var ShellCompletion = class {
16
+ constructor(commandName) {
17
+ // omelette instance - no types available
18
+ this.COMPLETION_TIMEOUT = 1e3;
19
+ this.commandName = commandName ?? this.detectCommandName();
20
+ this.completion = omelette("iloom|il <command> <arg>");
21
+ this.setupHandlers();
22
+ }
23
+ detectCommandName() {
24
+ const scriptPath = process.argv[1] ?? "il";
25
+ const baseName = scriptPath.split("/").pop() ?? "il";
26
+ return baseName.replace(/\.js$/, "");
27
+ }
28
+ setupHandlers() {
29
+ this.completion.on("command", ({ reply }) => {
30
+ reply([
31
+ "start",
32
+ "finish",
33
+ "spin",
34
+ "ignite",
35
+ "open",
36
+ "run",
37
+ "cleanup",
38
+ "list",
39
+ "init"
40
+ // Intentionally exclude test-* commands from autocomplete
41
+ ]);
42
+ });
43
+ this.completion.on("arg", async ({ line, reply }) => {
44
+ if (line.includes("cleanup")) {
45
+ const suggestions = await this.getBranchSuggestionsWithTimeout();
46
+ reply(suggestions);
47
+ } else {
48
+ reply([]);
49
+ }
50
+ });
51
+ }
52
+ /**
53
+ * Get branch suggestions with timeout to prevent blocking
54
+ */
55
+ async getBranchSuggestionsWithTimeout() {
56
+ try {
57
+ return await Promise.race([
58
+ this.getBranchSuggestions(),
59
+ this.timeout(this.COMPLETION_TIMEOUT, [])
60
+ ]);
61
+ } catch (error) {
62
+ logger.debug(`Autocomplete branch suggestions failed: ${error}`);
63
+ return [];
64
+ }
65
+ }
66
+ async timeout(ms, defaultValue) {
67
+ return new Promise((resolve) => {
68
+ setTimeout(() => resolve(defaultValue), ms);
69
+ });
70
+ }
71
+ async getBranchSuggestions() {
72
+ try {
73
+ const manager = new GitWorktreeManager();
74
+ const worktrees = await manager.listWorktrees({ porcelain: true });
75
+ const repoInfo = await manager.getRepoInfo();
76
+ const repoRoot = repoInfo.root;
77
+ const currentBranch = repoInfo.currentBranch;
78
+ return worktrees.filter((wt) => wt.path !== repoRoot).filter((wt) => wt.branch !== currentBranch).map((wt) => wt.branch);
79
+ } catch (error) {
80
+ logger.debug(`Failed to get branch suggestions: ${error}`);
81
+ return [];
82
+ }
83
+ }
84
+ /**
85
+ * Initialize completion - must be called before program.parseAsync()
86
+ */
87
+ init() {
88
+ this.completion.init();
89
+ }
90
+ /**
91
+ * Detect user's current shell
92
+ */
93
+ detectShell() {
94
+ const shell = process.env.SHELL ?? "";
95
+ if (shell.includes("bash")) return "bash";
96
+ if (shell.includes("zsh")) return "zsh";
97
+ if (shell.includes("fish")) return "fish";
98
+ return "unknown";
99
+ }
100
+ /**
101
+ * Get completion script for a specific shell
102
+ */
103
+ getCompletionScript(shell) {
104
+ switch (shell) {
105
+ case "bash":
106
+ return this.completion.setupShellInitFile("bash");
107
+ case "zsh":
108
+ return this.completion.setupShellInitFile("zsh");
109
+ case "fish":
110
+ return this.completion.setupShellInitFile("fish");
111
+ default:
112
+ throw new Error(`Unsupported shell type: ${shell}`);
113
+ }
114
+ }
115
+ /**
116
+ * Get setup instructions for manual installation
117
+ */
118
+ getSetupInstructions(shell) {
119
+ const binaryName = this.commandName;
120
+ switch (shell) {
121
+ case "bash":
122
+ return `
123
+ Add the following to your ~/.bashrc or ~/.bash_profile:
124
+
125
+ eval "$(${binaryName} --completion)"
126
+
127
+ Then reload your shell:
128
+
129
+ source ~/.bashrc
130
+ `;
131
+ case "zsh":
132
+ return `
133
+ Add the following to your ~/.zshrc:
134
+
135
+ eval "$(${binaryName} --completion)"
136
+
137
+ Then reload your shell:
138
+
139
+ source ~/.zshrc
140
+ `;
141
+ case "fish":
142
+ return `
143
+ Add the following to your ~/.config/fish/config.fish:
144
+
145
+ ${binaryName} --completion | source
146
+
147
+ Then reload your shell:
148
+
149
+ source ~/.config/fish/config.fish
150
+ `;
151
+ default:
152
+ return `
153
+ Shell autocomplete is supported for bash, zsh, and fish.
154
+ Your current shell (${shell}) may not be supported.
155
+
156
+ Please consult your shell's documentation for setting up custom completions.
157
+ `;
158
+ }
159
+ }
160
+ /**
161
+ * Generate completion script and print to stdout
162
+ * Used by: il --completion
163
+ */
164
+ printCompletionScript(shell) {
165
+ const detectedShell = shell ?? this.detectShell();
166
+ if (detectedShell === "unknown") {
167
+ logger.error("Could not detect shell type. Please specify --shell bash|zsh|fish");
168
+ process.exit(1);
169
+ }
170
+ try {
171
+ const script = this.getCompletionScript(detectedShell);
172
+ console.log(script);
173
+ } catch (error) {
174
+ logger.error(`Failed to generate completion script: ${error}`);
175
+ process.exit(1);
176
+ }
177
+ }
178
+ /**
179
+ * Get the shell configuration file path for the given shell type
180
+ */
181
+ getShellConfigPath(shell) {
182
+ const homeDir = os.homedir();
183
+ switch (shell) {
184
+ case "bash": {
185
+ const bashrcPath = path.join(homeDir, ".bashrc");
186
+ const bashProfilePath = path.join(homeDir, ".bash_profile");
187
+ if (existsSync(bashrcPath)) {
188
+ return bashrcPath;
189
+ } else if (existsSync(bashProfilePath)) {
190
+ return bashProfilePath;
191
+ }
192
+ return bashrcPath;
193
+ }
194
+ case "zsh":
195
+ return path.join(homeDir, ".zshrc");
196
+ case "fish":
197
+ return path.join(homeDir, ".config", "fish", "config.fish");
198
+ default:
199
+ return null;
200
+ }
201
+ }
202
+ /**
203
+ * Read the shell configuration file contents
204
+ */
205
+ async readShellConfig(shell) {
206
+ const configPath = this.getShellConfigPath(shell);
207
+ if (!configPath) {
208
+ return null;
209
+ }
210
+ try {
211
+ let content = "";
212
+ if (existsSync(configPath)) {
213
+ content = await readFile(configPath, "utf-8");
214
+ }
215
+ return {
216
+ path: configPath,
217
+ content
218
+ };
219
+ } catch (error) {
220
+ logger.debug(`Failed to read shell config file ${configPath}: ${error}`);
221
+ return {
222
+ path: configPath,
223
+ content: ""
224
+ };
225
+ }
226
+ }
227
+ /**
228
+ * Grep for completion-related content in shell configuration file
229
+ * Returns only lines containing '--completion' with 2 lines of context before and after
230
+ * Properly handles overlapping matches
231
+ */
232
+ async grepCompletionConfig(shell) {
233
+ const configPath = this.getShellConfigPath(shell);
234
+ if (!configPath) {
235
+ return null;
236
+ }
237
+ try {
238
+ let content = "";
239
+ if (existsSync(configPath)) {
240
+ const fullContent = await readFile(configPath, "utf-8");
241
+ const lines = fullContent.split(/\r?\n/);
242
+ const matchingIndices = [];
243
+ lines.forEach((line, index) => {
244
+ if (line.includes("--completion")) {
245
+ matchingIndices.push(index);
246
+ }
247
+ });
248
+ if (matchingIndices.length === 0) {
249
+ content = "";
250
+ } else {
251
+ const ranges = [];
252
+ matchingIndices.forEach((matchIndex) => {
253
+ const start = Math.max(0, matchIndex - 2);
254
+ const end = Math.min(lines.length - 1, matchIndex + 2);
255
+ ranges.push({ start, end });
256
+ });
257
+ const mergedRanges = this.mergeOverlappingRanges(ranges);
258
+ const resultSections = mergedRanges.map(
259
+ (range) => lines.slice(range.start, range.end + 1).join("\n")
260
+ );
261
+ content = resultSections.join("\n--\n");
262
+ }
263
+ }
264
+ return {
265
+ path: configPath,
266
+ content
267
+ };
268
+ } catch (error) {
269
+ logger.debug(`Failed to grep shell config file ${configPath}: ${error}`);
270
+ return {
271
+ path: configPath,
272
+ content: ""
273
+ };
274
+ }
275
+ }
276
+ /**
277
+ * Merge overlapping ranges to avoid duplicate lines
278
+ */
279
+ mergeOverlappingRanges(ranges) {
280
+ if (ranges.length === 0) return [];
281
+ const sorted = [...ranges].sort((a, b) => a.start - b.start);
282
+ const firstRange = sorted[0];
283
+ if (!firstRange) return [];
284
+ const merged = [firstRange];
285
+ for (let i = 1; i < sorted.length; i++) {
286
+ const current = sorted[i];
287
+ const last = merged[merged.length - 1];
288
+ if (!current || !last) continue;
289
+ if (current.start <= last.end + 1) {
290
+ last.end = Math.max(last.end, current.end);
291
+ } else {
292
+ merged.push(current);
293
+ }
294
+ }
295
+ return merged;
296
+ }
297
+ };
298
+
299
+ export {
300
+ ShellCompletion
301
+ };
302
+ //# sourceMappingURL=chunk-JT5LZRMI.js.map