@iloom/cli 0.10.0 → 0.10.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 (155) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +2 -2
  3. package/dist/{BranchNamingService-ECJHBB67.js → BranchNamingService-25KSZAEM.js} +2 -2
  4. package/dist/ClaudeContextManager-66GR4BGM.js +14 -0
  5. package/dist/ClaudeService-7KM5NA5Z.js +13 -0
  6. package/dist/{LoomLauncher-L64HHS3T.js → LoomLauncher-TDLZSYG2.js} +6 -6
  7. package/dist/{PromptTemplateManager-DULSVRRE.js → PromptTemplateManager-YOE2SIPG.js} +2 -2
  8. package/dist/README.md +2 -2
  9. package/dist/{SettingsManager-BQDQA3FK.js → SettingsManager-FNKCOZMQ.js} +2 -2
  10. package/dist/{build-5GO3XW26.js → build-VHGEMXBA.js} +6 -6
  11. package/dist/chunk-4E7LCFUG.js +24 -0
  12. package/dist/chunk-4E7LCFUG.js.map +1 -0
  13. package/dist/{chunk-MNHZB4Z2.js → chunk-4FGEGQW4.js} +3 -3
  14. package/dist/{chunk-LXLMMXXY.js → chunk-5FJWO4IT.js} +17 -12
  15. package/dist/chunk-5FJWO4IT.js.map +1 -0
  16. package/dist/{chunk-ZHPNZC75.js → chunk-5RPBYK5Q.js} +26 -21
  17. package/dist/chunk-5RPBYK5Q.js.map +1 -0
  18. package/dist/{chunk-WY4QBK43.js → chunk-63QWFWH3.js} +2 -2
  19. package/dist/{chunk-YYAKPQBT.js → chunk-7VHJNVLF.js} +19 -9
  20. package/dist/chunk-7VHJNVLF.js.map +1 -0
  21. package/dist/{chunk-SF2P22EE.js → chunk-C6HNNJIV.js} +2 -2
  22. package/dist/{chunk-5MWV33NN.js → chunk-CVCTIDDK.js} +2 -2
  23. package/dist/{chunk-RYWFS37M.js → chunk-E6KOWMKA.js} +2 -2
  24. package/dist/{chunk-6EU6TCF6.js → chunk-EVPZFV3K.js} +5 -5
  25. package/dist/{chunk-ZEWU5PZK.js → chunk-G5V75JD5.js} +2 -2
  26. package/dist/chunk-GRISNU6G.js +651 -0
  27. package/dist/chunk-GRISNU6G.js.map +1 -0
  28. package/dist/{chunk-VGGST52X.js → chunk-I5T677EA.js} +2 -2
  29. package/dist/{chunk-VECNX6VX.js → chunk-KIK2ZFAL.js} +2 -2
  30. package/dist/{chunk-FB47TIJG.js → chunk-KKV5WH5M.js} +4 -23
  31. package/dist/chunk-KKV5WH5M.js.map +1 -0
  32. package/dist/{chunk-ZW2LKWWE.js → chunk-KVHIAWVT.js} +3 -3
  33. package/dist/{chunk-3D7WQM7I.js → chunk-LLHXQS3C.js} +2 -2
  34. package/dist/{chunk-Y4YZTHZE.js → chunk-LUKXJSRI.js} +2 -2
  35. package/dist/{ignite-CGOV3TD4.js → chunk-OTGH2HRS.js} +105 -71
  36. package/dist/chunk-OTGH2HRS.js.map +1 -0
  37. package/dist/{chunk-J5S7DFYC.js → chunk-QVLPWNE3.js} +2 -2
  38. package/dist/chunk-RJ3VBUFK.js +781 -0
  39. package/dist/chunk-RJ3VBUFK.js.map +1 -0
  40. package/dist/{chunk-SN3SQCFK.js → chunk-S7PZA6IV.js} +4 -4
  41. package/dist/{chunk-UWGVCXRF.js → chunk-SKSYYBCU.js} +23 -1
  42. package/dist/chunk-SKSYYBCU.js.map +1 -0
  43. package/dist/{chunk-JO2LZ6EQ.js → chunk-SWSJWA2S.js} +2 -2
  44. package/dist/{chunk-ONQYPICO.js → chunk-UR5DGNUO.js} +60 -6
  45. package/dist/chunk-UR5DGNUO.js.map +1 -0
  46. package/dist/{chunk-4WJNIR5O.js → chunk-UUEW5KWB.js} +1 -1
  47. package/dist/chunk-UUEW5KWB.js.map +1 -0
  48. package/dist/{chunk-NRSWLOAZ.js → chunk-WXIM2WS7.js} +4 -4
  49. package/dist/{chunk-UD3WJDIV.js → chunk-ZNMPGMHY.js} +11 -774
  50. package/dist/chunk-ZNMPGMHY.js.map +1 -0
  51. package/dist/{claude-P3NQR6IJ.js → claude-7GGEWVEM.js} +2 -2
  52. package/dist/{cleanup-6UCPVMFG.js → cleanup-6PVAC4NI.js} +19 -17
  53. package/dist/{cleanup-6UCPVMFG.js.map → cleanup-6PVAC4NI.js.map} +1 -1
  54. package/dist/cli.js +154 -614
  55. package/dist/cli.js.map +1 -1
  56. package/dist/{commit-L3EPY5QG.js → commit-FZR5XDQG.js} +12 -10
  57. package/dist/commit-FZR5XDQG.js.map +1 -0
  58. package/dist/{compile-ZS4HYRX5.js → compile-7ALJHZ4N.js} +6 -6
  59. package/dist/{contribute-ORDDQGSL.js → contribute-5GKLK3BQ.js} +3 -3
  60. package/dist/{dev-server-FYZ2AQIH.js → dev-server-7SMIB7OF.js} +8 -8
  61. package/dist/{feedback-TMBXSCM5.js → feedback-G2GJFN2F.js} +10 -8
  62. package/dist/{feedback-TMBXSCM5.js.map → feedback-G2GJFN2F.js.map} +1 -1
  63. package/dist/{git-ET64COO3.js → git-GTLKAZRJ.js} +3 -3
  64. package/dist/ignite-H2O5Y5A2.js +34 -0
  65. package/dist/ignite-H2O5Y5A2.js.map +1 -0
  66. package/dist/index.d.ts +113 -18
  67. package/dist/index.js +177 -12
  68. package/dist/index.js.map +1 -1
  69. package/dist/{init-GFQ5W7GK.js → init-32YOKXRL.js} +8 -8
  70. package/dist/{issues-T4ZZSPEG.js → issues-4UUAQ5K6.js} +3 -3
  71. package/dist/{lint-6TQXDZ3T.js → lint-AAN2NZWG.js} +6 -6
  72. package/dist/mcp/harness-server.js +140 -0
  73. package/dist/mcp/harness-server.js.map +1 -0
  74. package/dist/mcp/issue-management-server.js +140 -18
  75. package/dist/mcp/issue-management-server.js.map +1 -1
  76. package/dist/{open-5QZGXQRF.js → open-FXWW3VI4.js} +8 -8
  77. package/dist/{plan-U7ZQWLFY.js → plan-RQ5FPIGF.js} +338 -36
  78. package/dist/plan-RQ5FPIGF.js.map +1 -0
  79. package/dist/prompts/CLAUDE.md +2 -2
  80. package/dist/prompts/init-prompt.txt +102 -27
  81. package/dist/prompts/issue-prompt.txt +46 -0
  82. package/dist/prompts/plan-prompt.txt +59 -19
  83. package/dist/prompts/swarm-orchestrator-prompt.txt +107 -80
  84. package/dist/{rebase-DWIB77KV.js → rebase-6NVLX5V7.js} +17 -8
  85. package/dist/rebase-6NVLX5V7.js.map +1 -0
  86. package/dist/{recap-MX63HAKV.js → recap-OMBOKJST.js} +6 -6
  87. package/dist/{run-O3TFNQFC.js → run-BBXLRIZB.js} +8 -8
  88. package/dist/schema/settings.schema.json +36 -2
  89. package/dist/{shell-G6VC2CYR.js → shell-RF7LTND5.js} +5 -5
  90. package/dist/{summary-FWHAX55O.js → summary-WTQZ7XG2.js} +9 -9
  91. package/dist/{test-F7JNJZYP.js → test-SGO6I5Z7.js} +6 -6
  92. package/dist/{test-git-BTAOIUE2.js → test-git-XM4TM65W.js} +3 -3
  93. package/dist/{test-jira-CHYNV33F.js → test-jira-LDTOYFSD.js} +3 -3
  94. package/dist/{test-prefix-Q6TFSU6F.js → test-prefix-GBO37XCN.js} +3 -3
  95. package/dist/{test-webserver-EONCG7E7.js → test-webserver-NZ3JTVLL.js} +5 -5
  96. package/dist/{vscode-VA5X4P25.js → vscode-6XUGHJKL.js} +5 -5
  97. package/package.json +1 -1
  98. package/dist/ClaudeContextManager-QXX6ZFST.js +0 -14
  99. package/dist/ClaudeService-NJNK2SUH.js +0 -13
  100. package/dist/chunk-4WJNIR5O.js.map +0 -1
  101. package/dist/chunk-FB47TIJG.js.map +0 -1
  102. package/dist/chunk-LXLMMXXY.js.map +0 -1
  103. package/dist/chunk-ONQYPICO.js.map +0 -1
  104. package/dist/chunk-UD3WJDIV.js.map +0 -1
  105. package/dist/chunk-UVD4CZKS.js +0 -101
  106. package/dist/chunk-UVD4CZKS.js.map +0 -1
  107. package/dist/chunk-UWGVCXRF.js.map +0 -1
  108. package/dist/chunk-YYAKPQBT.js.map +0 -1
  109. package/dist/chunk-ZHPNZC75.js.map +0 -1
  110. package/dist/commit-L3EPY5QG.js.map +0 -1
  111. package/dist/ignite-CGOV3TD4.js.map +0 -1
  112. package/dist/plan-U7ZQWLFY.js.map +0 -1
  113. package/dist/rebase-DWIB77KV.js.map +0 -1
  114. /package/dist/{BranchNamingService-ECJHBB67.js.map → BranchNamingService-25KSZAEM.js.map} +0 -0
  115. /package/dist/{ClaudeContextManager-QXX6ZFST.js.map → ClaudeContextManager-66GR4BGM.js.map} +0 -0
  116. /package/dist/{ClaudeService-NJNK2SUH.js.map → ClaudeService-7KM5NA5Z.js.map} +0 -0
  117. /package/dist/{LoomLauncher-L64HHS3T.js.map → LoomLauncher-TDLZSYG2.js.map} +0 -0
  118. /package/dist/{PromptTemplateManager-DULSVRRE.js.map → PromptTemplateManager-YOE2SIPG.js.map} +0 -0
  119. /package/dist/{SettingsManager-BQDQA3FK.js.map → SettingsManager-FNKCOZMQ.js.map} +0 -0
  120. /package/dist/{build-5GO3XW26.js.map → build-VHGEMXBA.js.map} +0 -0
  121. /package/dist/{chunk-MNHZB4Z2.js.map → chunk-4FGEGQW4.js.map} +0 -0
  122. /package/dist/{chunk-WY4QBK43.js.map → chunk-63QWFWH3.js.map} +0 -0
  123. /package/dist/{chunk-SF2P22EE.js.map → chunk-C6HNNJIV.js.map} +0 -0
  124. /package/dist/{chunk-5MWV33NN.js.map → chunk-CVCTIDDK.js.map} +0 -0
  125. /package/dist/{chunk-RYWFS37M.js.map → chunk-E6KOWMKA.js.map} +0 -0
  126. /package/dist/{chunk-6EU6TCF6.js.map → chunk-EVPZFV3K.js.map} +0 -0
  127. /package/dist/{chunk-ZEWU5PZK.js.map → chunk-G5V75JD5.js.map} +0 -0
  128. /package/dist/{chunk-VGGST52X.js.map → chunk-I5T677EA.js.map} +0 -0
  129. /package/dist/{chunk-VECNX6VX.js.map → chunk-KIK2ZFAL.js.map} +0 -0
  130. /package/dist/{chunk-ZW2LKWWE.js.map → chunk-KVHIAWVT.js.map} +0 -0
  131. /package/dist/{chunk-3D7WQM7I.js.map → chunk-LLHXQS3C.js.map} +0 -0
  132. /package/dist/{chunk-Y4YZTHZE.js.map → chunk-LUKXJSRI.js.map} +0 -0
  133. /package/dist/{chunk-J5S7DFYC.js.map → chunk-QVLPWNE3.js.map} +0 -0
  134. /package/dist/{chunk-SN3SQCFK.js.map → chunk-S7PZA6IV.js.map} +0 -0
  135. /package/dist/{chunk-JO2LZ6EQ.js.map → chunk-SWSJWA2S.js.map} +0 -0
  136. /package/dist/{chunk-NRSWLOAZ.js.map → chunk-WXIM2WS7.js.map} +0 -0
  137. /package/dist/{claude-P3NQR6IJ.js.map → claude-7GGEWVEM.js.map} +0 -0
  138. /package/dist/{compile-ZS4HYRX5.js.map → compile-7ALJHZ4N.js.map} +0 -0
  139. /package/dist/{contribute-ORDDQGSL.js.map → contribute-5GKLK3BQ.js.map} +0 -0
  140. /package/dist/{dev-server-FYZ2AQIH.js.map → dev-server-7SMIB7OF.js.map} +0 -0
  141. /package/dist/{git-ET64COO3.js.map → git-GTLKAZRJ.js.map} +0 -0
  142. /package/dist/{init-GFQ5W7GK.js.map → init-32YOKXRL.js.map} +0 -0
  143. /package/dist/{issues-T4ZZSPEG.js.map → issues-4UUAQ5K6.js.map} +0 -0
  144. /package/dist/{lint-6TQXDZ3T.js.map → lint-AAN2NZWG.js.map} +0 -0
  145. /package/dist/{open-5QZGXQRF.js.map → open-FXWW3VI4.js.map} +0 -0
  146. /package/dist/{recap-MX63HAKV.js.map → recap-OMBOKJST.js.map} +0 -0
  147. /package/dist/{run-O3TFNQFC.js.map → run-BBXLRIZB.js.map} +0 -0
  148. /package/dist/{shell-G6VC2CYR.js.map → shell-RF7LTND5.js.map} +0 -0
  149. /package/dist/{summary-FWHAX55O.js.map → summary-WTQZ7XG2.js.map} +0 -0
  150. /package/dist/{test-F7JNJZYP.js.map → test-SGO6I5Z7.js.map} +0 -0
  151. /package/dist/{test-git-BTAOIUE2.js.map → test-git-XM4TM65W.js.map} +0 -0
  152. /package/dist/{test-jira-CHYNV33F.js.map → test-jira-LDTOYFSD.js.map} +0 -0
  153. /package/dist/{test-prefix-Q6TFSU6F.js.map → test-prefix-GBO37XCN.js.map} +0 -0
  154. /package/dist/{test-webserver-EONCG7E7.js.map → test-webserver-NZ3JTVLL.js.map} +0 -0
  155. /package/dist/{vscode-VA5X4P25.js.map → vscode-6XUGHJKL.js.map} +0 -0
@@ -0,0 +1,781 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ archiveRecap
4
+ } from "./chunk-LUKXJSRI.js";
5
+ import {
6
+ calculatePortFromIdentifier
7
+ } from "./chunk-LLHXQS3C.js";
8
+ import {
9
+ checkRemoteBranchStatus,
10
+ executeGitCommand,
11
+ extractIssueNumber,
12
+ findMainWorktreePathWithSettings,
13
+ findWorktreeForBranch,
14
+ getMergeTargetBranch,
15
+ hasUncommittedChanges,
16
+ isBranchMergedIntoMain
17
+ } from "./chunk-4FGEGQW4.js";
18
+ import {
19
+ SettingsManager
20
+ } from "./chunk-7VHJNVLF.js";
21
+ import {
22
+ MetadataManager
23
+ } from "./chunk-KB64WNBZ.js";
24
+ import {
25
+ getLogger
26
+ } from "./chunk-6MLEBAYZ.js";
27
+
28
+ // src/lib/ResourceCleanup.ts
29
+ import path from "path";
30
+ var ResourceCleanup = class {
31
+ constructor(gitWorktree, processManager, database, cliIsolation, settingsManager) {
32
+ this.gitWorktree = gitWorktree;
33
+ this.processManager = processManager;
34
+ this.database = database;
35
+ this.cliIsolation = cliIsolation;
36
+ this.settingsManager = settingsManager ?? new SettingsManager();
37
+ this.metadataManager = new MetadataManager();
38
+ }
39
+ /**
40
+ * Cleanup a worktree and associated resources
41
+ * Main orchestration method
42
+ *
43
+ * @param parsed - ParsedInput from IdentifierParser with type information
44
+ * @param options - Cleanup options
45
+ */
46
+ async cleanupWorktree(parsed, options = {}) {
47
+ var _a, _b, _c;
48
+ const operations = [];
49
+ const errors = [];
50
+ const displayIdentifier = parsed.branchName ?? ((_a = parsed.number) == null ? void 0 : _a.toString()) ?? parsed.originalInput;
51
+ getLogger().info(`Starting cleanup for: ${displayIdentifier}`);
52
+ const number = parsed.number;
53
+ if (number !== void 0) {
54
+ const settings = await this.settingsManager.loadSettings();
55
+ const basePort = ((_c = (_b = settings == null ? void 0 : settings.capabilities) == null ? void 0 : _b.web) == null ? void 0 : _c.basePort) ?? 3e3;
56
+ const port = calculatePortFromIdentifier(number, basePort);
57
+ if (options.dryRun) {
58
+ operations.push({
59
+ type: "dev-server",
60
+ success: true,
61
+ message: `[DRY RUN] Would check for dev server on port ${port}`
62
+ });
63
+ } else {
64
+ try {
65
+ const terminated = await this.terminateDevServer(port);
66
+ operations.push({
67
+ type: "dev-server",
68
+ success: true,
69
+ message: terminated ? `Dev server on port ${port} terminated` : `No dev server running on port ${port}`
70
+ });
71
+ } catch (error) {
72
+ const err = error instanceof Error ? error : new Error("Unknown error");
73
+ errors.push(err);
74
+ operations.push({
75
+ type: "dev-server",
76
+ success: false,
77
+ message: `Failed to terminate dev server`,
78
+ error: err.message
79
+ });
80
+ }
81
+ }
82
+ }
83
+ let worktree = null;
84
+ try {
85
+ if (options.worktree) {
86
+ worktree = {
87
+ path: options.worktree.path,
88
+ branch: options.worktree.branch,
89
+ commit: "",
90
+ bare: false,
91
+ detached: false,
92
+ locked: false
93
+ };
94
+ getLogger().debug(`Using pre-resolved worktree: path="${worktree.path}", branch="${worktree.branch}"`);
95
+ } else {
96
+ if (parsed.type === "pr" && parsed.number !== void 0) {
97
+ const prNumber = typeof parsed.number === "number" ? parsed.number : Number(parsed.number);
98
+ if (isNaN(prNumber) || !isFinite(prNumber)) {
99
+ throw new Error(`Invalid PR number: ${parsed.number}. PR numbers must be numeric.`);
100
+ }
101
+ worktree = await this.gitWorktree.findWorktreeForPR(prNumber, "");
102
+ } else if (parsed.type === "issue" && parsed.number !== void 0) {
103
+ worktree = await this.gitWorktree.findWorktreeForIssue(parsed.number);
104
+ } else if (parsed.type === "branch" && parsed.branchName) {
105
+ worktree = await this.gitWorktree.findWorktreeForBranch(parsed.branchName);
106
+ }
107
+ }
108
+ if (!worktree) {
109
+ throw new Error(`No worktree found for identifier: ${displayIdentifier}`);
110
+ }
111
+ getLogger().debug(`Found worktree: path="${worktree.path}", branch="${worktree.branch}"`);
112
+ } catch (error) {
113
+ const err = error instanceof Error ? error : new Error("Unknown error");
114
+ errors.push(err);
115
+ return {
116
+ identifier: displayIdentifier,
117
+ success: false,
118
+ operations,
119
+ errors,
120
+ rollbackRequired: false
121
+ };
122
+ }
123
+ let safetyCheckPassed = false;
124
+ if (!options.force) {
125
+ const shouldCheckMergeSafety = options.checkMergeSafety ?? options.deleteBranch === true;
126
+ const shouldCheckRemoteBranch = options.checkRemoteBranch ?? false;
127
+ const safety = await this.validateWorktreeSafety(worktree, parsed.originalInput, shouldCheckMergeSafety, shouldCheckRemoteBranch);
128
+ if (!safety.isSafe) {
129
+ const blockerMessage = safety.blockers.join("\n\n");
130
+ throw new Error(`Cannot cleanup:
131
+
132
+ ${blockerMessage}`);
133
+ }
134
+ safetyCheckPassed = true;
135
+ if (safety.warnings.length > 0) {
136
+ safety.warnings.forEach((warning) => {
137
+ getLogger().warn(warning);
138
+ });
139
+ }
140
+ }
141
+ let databaseConfig = null;
142
+ if (!options.keepDatabase && worktree) {
143
+ const envFilePath = path.join(worktree.path, ".env");
144
+ try {
145
+ const shouldCleanup = this.database ? await this.database.shouldUseDatabaseBranching(envFilePath) : false;
146
+ databaseConfig = { shouldCleanup, envFilePath };
147
+ } catch (error) {
148
+ getLogger().warn(
149
+ `Failed to read database config from ${envFilePath}, skipping database cleanup: ${error instanceof Error ? error.message : String(error)}`
150
+ );
151
+ databaseConfig = { shouldCleanup: false, envFilePath };
152
+ }
153
+ }
154
+ let mainWorktreePath = null;
155
+ if (!options.dryRun) {
156
+ try {
157
+ mainWorktreePath = await findMainWorktreePathWithSettings(worktree.path, this.settingsManager);
158
+ } catch (error) {
159
+ getLogger().warn(
160
+ `Failed to find main worktree path: ${error instanceof Error ? error.message : String(error)}`
161
+ );
162
+ }
163
+ }
164
+ let mergeTargetBranch = null;
165
+ if (options.deleteBranch && worktree && !options.dryRun) {
166
+ try {
167
+ mergeTargetBranch = await getMergeTargetBranch(worktree.path, {
168
+ settingsManager: this.settingsManager,
169
+ metadataManager: this.metadataManager
170
+ });
171
+ getLogger().debug(`Pre-fetched merge target branch: ${mergeTargetBranch}`);
172
+ } catch (error) {
173
+ getLogger().warn(
174
+ `Failed to pre-fetch merge target branch: ${error instanceof Error ? error.message : String(error)}`
175
+ );
176
+ }
177
+ }
178
+ if (options.dryRun) {
179
+ operations.push({
180
+ type: "worktree",
181
+ success: true,
182
+ message: `[DRY RUN] Would remove worktree: ${worktree.path}`
183
+ });
184
+ } else {
185
+ try {
186
+ const worktreeOptions = {
187
+ removeDirectory: true,
188
+ removeBranch: false
189
+ // Handle branch separately
190
+ };
191
+ if (options.force !== void 0) {
192
+ worktreeOptions.force = options.force;
193
+ }
194
+ await this.gitWorktree.removeWorktree(worktree.path, worktreeOptions);
195
+ operations.push({
196
+ type: "worktree",
197
+ success: true,
198
+ message: `Worktree removed: ${worktree.path}`
199
+ });
200
+ } catch (error) {
201
+ const err = error instanceof Error ? error : new Error("Unknown error");
202
+ errors.push(err);
203
+ operations.push({
204
+ type: "worktree",
205
+ success: false,
206
+ message: `Failed to remove worktree`,
207
+ error: err.message
208
+ });
209
+ }
210
+ }
211
+ if (worktree) {
212
+ if (options.dryRun) {
213
+ operations.push({
214
+ type: "recap",
215
+ success: true,
216
+ message: `[DRY RUN] Would archive recap file for: ${worktree.path}`
217
+ });
218
+ } else {
219
+ try {
220
+ await archiveRecap(worktree.path);
221
+ operations.push({
222
+ type: "recap",
223
+ success: true,
224
+ message: `Recap file archived`
225
+ });
226
+ } catch (error) {
227
+ const err = error instanceof Error ? error : new Error("Unknown error");
228
+ getLogger().warn(`Recap archival failed: ${err.message}`);
229
+ operations.push({
230
+ type: "recap",
231
+ success: false,
232
+ message: "Recap archival failed (non-fatal)",
233
+ error: err.message
234
+ });
235
+ }
236
+ }
237
+ }
238
+ if (options.deleteBranch && worktree) {
239
+ if (options.dryRun) {
240
+ operations.push({
241
+ type: "branch",
242
+ success: true,
243
+ message: `[DRY RUN] Would delete branch: ${worktree.branch}`
244
+ });
245
+ } else {
246
+ try {
247
+ const branchOptions = {
248
+ dryRun: false,
249
+ safetyVerified: safetyCheckPassed
250
+ };
251
+ if (mergeTargetBranch !== null) {
252
+ branchOptions.mergeTargetBranch = mergeTargetBranch;
253
+ }
254
+ if (options.force !== void 0) {
255
+ branchOptions.force = options.force;
256
+ }
257
+ await this.deleteBranch(worktree.branch, branchOptions, mainWorktreePath ?? void 0);
258
+ operations.push({
259
+ type: "branch",
260
+ success: true,
261
+ message: `Branch deleted: ${worktree.branch}`
262
+ });
263
+ } catch (error) {
264
+ const err = error instanceof Error ? error : new Error("Unknown error");
265
+ errors.push(err);
266
+ operations.push({
267
+ type: "branch",
268
+ success: false,
269
+ message: `Failed to delete branch`,
270
+ error: err.message
271
+ });
272
+ }
273
+ }
274
+ }
275
+ const cliIdentifier = parsed.number ?? parsed.branchName;
276
+ if (this.cliIsolation && cliIdentifier !== void 0) {
277
+ if (options.dryRun) {
278
+ operations.push({
279
+ type: "cli-symlinks",
280
+ success: true,
281
+ message: `[DRY RUN] Would cleanup CLI symlinks for: ${cliIdentifier}`
282
+ });
283
+ } else {
284
+ try {
285
+ const removed = await this.cliIsolation.cleanupVersionedExecutables(cliIdentifier);
286
+ operations.push({
287
+ type: "cli-symlinks",
288
+ success: true,
289
+ message: removed.length > 0 ? `CLI symlinks removed: ${removed.length}` : "No CLI symlinks to cleanup"
290
+ });
291
+ } catch (error) {
292
+ const err = error instanceof Error ? error : new Error("Unknown error");
293
+ errors.push(err);
294
+ getLogger().warn(
295
+ `CLI symlink cleanup failed: ${err.message}`
296
+ );
297
+ operations.push({
298
+ type: "cli-symlinks",
299
+ success: false,
300
+ message: "CLI symlink cleanup failed (non-fatal)"
301
+ });
302
+ }
303
+ }
304
+ }
305
+ if (databaseConfig && worktree) {
306
+ if (options.dryRun) {
307
+ operations.push({
308
+ type: "database",
309
+ success: true,
310
+ message: `[DRY RUN] Would cleanup database branch for: ${worktree.branch}`
311
+ });
312
+ } else {
313
+ try {
314
+ if (databaseConfig.shouldCleanup && this.database) {
315
+ try {
316
+ const deletionResult = await this.database.deleteBranchIfConfigured(
317
+ worktree.branch,
318
+ databaseConfig.shouldCleanup,
319
+ false,
320
+ // isPreview
321
+ mainWorktreePath ?? void 0
322
+ );
323
+ if (deletionResult.deleted) {
324
+ getLogger().info(`Database branch deleted: ${worktree.branch}`);
325
+ operations.push({
326
+ type: "database",
327
+ success: true,
328
+ message: `Database branch deleted`,
329
+ deleted: true
330
+ });
331
+ } else if (deletionResult.notFound) {
332
+ getLogger().debug(`No database branch found for: ${worktree.branch}`);
333
+ operations.push({
334
+ type: "database",
335
+ success: true,
336
+ message: `No database branch found (skipped)`,
337
+ deleted: false
338
+ });
339
+ } else if (deletionResult.userDeclined) {
340
+ getLogger().info("Preview database deletion declined by user");
341
+ operations.push({
342
+ type: "database",
343
+ success: true,
344
+ message: `Database cleanup skipped (user declined)`,
345
+ deleted: false
346
+ });
347
+ } else if (!deletionResult.success) {
348
+ const errorMsg = deletionResult.error ?? "Unknown error";
349
+ errors.push(new Error(errorMsg));
350
+ getLogger().warn(`Database cleanup failed: ${errorMsg}`);
351
+ operations.push({
352
+ type: "database",
353
+ success: false,
354
+ // Non-fatal, but report error
355
+ message: `Database cleanup failed`,
356
+ error: errorMsg,
357
+ deleted: false
358
+ });
359
+ } else {
360
+ errors.push(new Error("Database cleanup in an unknown state"));
361
+ getLogger().warn("Database deletion returned unexpected result state");
362
+ operations.push({
363
+ type: "database",
364
+ success: false,
365
+ message: `Database cleanup in an unknown state`,
366
+ deleted: false
367
+ });
368
+ }
369
+ } catch (error) {
370
+ errors.push(error instanceof Error ? error : new Error(String(error)));
371
+ getLogger().warn(
372
+ `Unexpected database cleanup exception: ${error instanceof Error ? error.message : String(error)}`
373
+ );
374
+ operations.push({
375
+ type: "database",
376
+ success: false,
377
+ message: `Database cleanup failed`,
378
+ error: error instanceof Error ? error.message : String(error),
379
+ deleted: false
380
+ });
381
+ }
382
+ } else {
383
+ operations.push({
384
+ type: "database",
385
+ success: true,
386
+ message: `Database cleanup skipped (not available)`,
387
+ deleted: false
388
+ });
389
+ }
390
+ } catch (error) {
391
+ const err = error instanceof Error ? error : new Error("Unknown error");
392
+ errors.push(err);
393
+ operations.push({
394
+ type: "database",
395
+ success: false,
396
+ message: `Database cleanup failed`,
397
+ error: err.message,
398
+ deleted: false
399
+ });
400
+ }
401
+ }
402
+ }
403
+ if (worktree) {
404
+ if (options.dryRun) {
405
+ const action = options.archive ? "archive" : "delete";
406
+ operations.push({
407
+ type: "metadata",
408
+ success: true,
409
+ message: `[DRY RUN] Would ${action} metadata for worktree: ${worktree.path}`
410
+ });
411
+ } else if (options.archive) {
412
+ try {
413
+ await this.metadataManager.archiveMetadata(worktree.path);
414
+ getLogger().info(`Metadata archived for worktree: ${worktree.path}`);
415
+ operations.push({
416
+ type: "metadata",
417
+ success: true,
418
+ message: "Metadata archived"
419
+ });
420
+ } catch (error) {
421
+ const err = error instanceof Error ? error : new Error(String(error));
422
+ errors.push(err);
423
+ getLogger().warn(`Metadata archival failed: ${err.message}`);
424
+ operations.push({
425
+ type: "metadata",
426
+ success: false,
427
+ message: "Metadata archival failed (non-fatal)",
428
+ error: err.message
429
+ });
430
+ }
431
+ } else {
432
+ try {
433
+ await this.metadataManager.deleteMetadata(worktree.path);
434
+ getLogger().info(`Metadata deleted for worktree: ${worktree.path}`);
435
+ operations.push({
436
+ type: "metadata",
437
+ success: true,
438
+ message: "Metadata deleted"
439
+ });
440
+ } catch (error) {
441
+ const err = error instanceof Error ? error : new Error(String(error));
442
+ errors.push(err);
443
+ getLogger().warn(`Metadata deletion failed: ${err.message}`);
444
+ operations.push({
445
+ type: "metadata",
446
+ success: false,
447
+ message: "Metadata deletion failed (non-fatal)",
448
+ error: err.message
449
+ });
450
+ }
451
+ }
452
+ }
453
+ const success = errors.length === 0;
454
+ return {
455
+ identifier: displayIdentifier,
456
+ branchName: worktree == null ? void 0 : worktree.branch,
457
+ success,
458
+ operations,
459
+ errors,
460
+ rollbackRequired: false
461
+ // Cleanup operations are generally not reversible
462
+ };
463
+ }
464
+ /**
465
+ * Terminate dev server on specified port
466
+ */
467
+ async terminateDevServer(port) {
468
+ getLogger().debug(`Checking for dev server on port ${port}`);
469
+ const processInfo = await this.processManager.detectDevServer(port);
470
+ if (!processInfo) {
471
+ getLogger().debug(`No process found on port ${port}`);
472
+ return false;
473
+ }
474
+ if (!processInfo.isDevServer) {
475
+ getLogger().warn(
476
+ `Process on port ${port} (${processInfo.name}) doesn't appear to be a dev server, skipping`
477
+ );
478
+ return false;
479
+ }
480
+ getLogger().info(`Terminating dev server: ${processInfo.name} (PID: ${processInfo.pid})`);
481
+ await this.processManager.terminateProcess(processInfo.pid);
482
+ const isFree = await this.processManager.verifyPortFree(port);
483
+ if (!isFree) {
484
+ throw new Error(`Dev server may still be running on port ${port}`);
485
+ }
486
+ return true;
487
+ }
488
+ /**
489
+ * Delete a Git branch with safety checks
490
+ *
491
+ * @param branchName - Name of the branch to delete
492
+ * @param options - Delete options (force, dryRun)
493
+ * @param cwd - Working directory to execute git command from (defaults to finding main worktree)
494
+ */
495
+ async deleteBranch(branchName, options = {}, cwd) {
496
+ const protectedBranches = await this.settingsManager.getProtectedBranches(cwd);
497
+ if (protectedBranches.includes(branchName)) {
498
+ throw new Error(`Cannot delete protected branch: ${branchName}`);
499
+ }
500
+ const workingDir = cwd ?? await findMainWorktreePathWithSettings(void 0, this.settingsManager);
501
+ try {
502
+ await executeGitCommand(["rev-parse", "--verify", `refs/heads/${branchName}`], {
503
+ cwd: workingDir
504
+ });
505
+ } catch {
506
+ getLogger().debug(`Branch ${branchName} does not exist, skipping deletion`);
507
+ return true;
508
+ }
509
+ if (options.dryRun) {
510
+ getLogger().info(`[DRY RUN] Would delete branch: ${branchName}`);
511
+ return true;
512
+ }
513
+ let deleteCwd = workingDir;
514
+ try {
515
+ let deleteFlag = "-d";
516
+ if (options.force) {
517
+ deleteFlag = "-D";
518
+ } else if (options.mergeTargetBranch) {
519
+ const mergeTarget = options.mergeTargetBranch;
520
+ try {
521
+ const targetWorktreePath = await findWorktreeForBranch(mergeTarget, workingDir);
522
+ getLogger().debug(`Running branch delete from worktree where '${mergeTarget}' is checked out: ${targetWorktreePath}`);
523
+ deleteCwd = targetWorktreePath;
524
+ } catch {
525
+ getLogger().debug(`Could not find worktree for branch '${mergeTarget}', falling back to merge check`);
526
+ const isMerged = await isBranchMergedIntoMain(branchName, mergeTarget, workingDir);
527
+ if (isMerged) {
528
+ getLogger().debug(`Branch '${branchName}' verified merged into '${mergeTarget}', using force delete`);
529
+ deleteFlag = "-D";
530
+ }
531
+ }
532
+ } else if (options.worktreePath) {
533
+ getLogger().warn("deleteBranch called with worktreePath but no mergeTargetBranch - this may fail if worktree was deleted");
534
+ try {
535
+ const mergeTarget = await getMergeTargetBranch(options.worktreePath, {
536
+ settingsManager: this.settingsManager,
537
+ metadataManager: this.metadataManager
538
+ });
539
+ try {
540
+ const targetWorktreePath = await findWorktreeForBranch(mergeTarget, workingDir);
541
+ getLogger().debug(`Running branch delete from worktree where '${mergeTarget}' is checked out: ${targetWorktreePath}`);
542
+ deleteCwd = targetWorktreePath;
543
+ } catch {
544
+ getLogger().debug(`Could not find worktree for branch '${mergeTarget}', falling back to merge check`);
545
+ const isMerged = await isBranchMergedIntoMain(branchName, mergeTarget, workingDir);
546
+ if (isMerged) {
547
+ getLogger().debug(`Branch '${branchName}' verified merged into '${mergeTarget}', using force delete`);
548
+ deleteFlag = "-D";
549
+ }
550
+ }
551
+ } catch (error) {
552
+ getLogger().debug(`Could not read merge target from worktreePath: ${error instanceof Error ? error.message : String(error)}`);
553
+ }
554
+ }
555
+ await executeGitCommand(["branch", deleteFlag, branchName], {
556
+ cwd: deleteCwd
557
+ });
558
+ getLogger().info(`Branch deleted: ${branchName}`);
559
+ return true;
560
+ } catch (error) {
561
+ const errorMessage = error instanceof Error ? error.message : String(error);
562
+ if (errorMessage.includes("not found") || errorMessage.includes("does not exist")) {
563
+ getLogger().debug(`Branch ${branchName} already deleted`);
564
+ return true;
565
+ }
566
+ if (options.force) {
567
+ throw error;
568
+ }
569
+ if (errorMessage.includes("not fully merged")) {
570
+ if (options.safetyVerified) {
571
+ getLogger().info(`Branch '${branchName}' not merged into HEAD but safety verified - using force delete`);
572
+ await executeGitCommand(["branch", "-D", branchName], { cwd: deleteCwd });
573
+ getLogger().info(`Branch deleted: ${branchName}`);
574
+ return true;
575
+ }
576
+ throw new Error(
577
+ `Cannot delete unmerged branch '${branchName}'. Use --force to delete anyway.`
578
+ );
579
+ }
580
+ throw error;
581
+ }
582
+ }
583
+ /**
584
+ * Cleanup database branch
585
+ * Gracefully handles missing DatabaseManager
586
+ *
587
+ * @deprecated This method is deprecated and should not be used for post-deletion cleanup.
588
+ * Use the pre-fetch mechanism in cleanupWorktree() instead.
589
+ * This method will fail if called after worktree deletion because
590
+ * it attempts to read the .env file which has been deleted.
591
+ *
592
+ * @param branchName - Name of the branch to delete
593
+ * @param worktreePath - Path to worktree (must still exist with .env file)
594
+ */
595
+ async cleanupDatabase(branchName, worktreePath) {
596
+ if (!this.database) {
597
+ getLogger().debug("Database manager not available, skipping database cleanup");
598
+ return false;
599
+ }
600
+ try {
601
+ const envFilePath = path.join(worktreePath, ".env");
602
+ const shouldCleanup = await this.database.shouldUseDatabaseBranching(envFilePath);
603
+ let cwd;
604
+ try {
605
+ cwd = await findMainWorktreePathWithSettings(worktreePath, this.settingsManager);
606
+ } catch (error) {
607
+ getLogger().debug(
608
+ `Could not find main worktree path, using current directory: ${error instanceof Error ? error.message : String(error)}`
609
+ );
610
+ }
611
+ const result = await this.database.deleteBranchIfConfigured(
612
+ branchName,
613
+ shouldCleanup,
614
+ false,
615
+ // isPreview
616
+ cwd
617
+ );
618
+ if (result.deleted) {
619
+ getLogger().info(`Database branch deleted: ${branchName}`);
620
+ return true;
621
+ } else if (result.notFound) {
622
+ getLogger().debug(`No database branch found for: ${branchName}`);
623
+ return false;
624
+ } else if (result.userDeclined) {
625
+ getLogger().info("Preview database deletion declined by user");
626
+ return false;
627
+ } else if (!result.success) {
628
+ getLogger().warn(`Database cleanup failed: ${result.error ?? "Unknown error"}`);
629
+ return false;
630
+ } else {
631
+ getLogger().debug("Database deletion returned unexpected result");
632
+ return false;
633
+ }
634
+ } catch (error) {
635
+ getLogger().warn(
636
+ `Unexpected database cleanup error: ${error instanceof Error ? error.message : String(error)}`
637
+ );
638
+ return false;
639
+ }
640
+ }
641
+ /**
642
+ * Cleanup multiple worktrees
643
+ */
644
+ async cleanupMultipleWorktrees(identifiers, options = {}) {
645
+ const results = [];
646
+ for (const identifier of identifiers) {
647
+ const parsed = this.parseIdentifier(identifier);
648
+ const result = await this.cleanupWorktree(parsed, options);
649
+ results.push(result);
650
+ }
651
+ return results;
652
+ }
653
+ /**
654
+ * Validate worktree safety given a worktree object
655
+ * Private method used internally when worktree is already known
656
+ *
657
+ * @param worktree - The worktree to validate
658
+ * @param identifier - The original identifier used (for error messages)
659
+ * @param checkBranchMerge - Whether to check if branch is merged into main (for branch deletion)
660
+ * @param checkRemoteBranch - Whether to check if branch exists on remote (for GitHub-PR mode)
661
+ */
662
+ async validateWorktreeSafety(worktree, identifier, checkBranchMerge = false, checkRemoteBranch = false) {
663
+ const warnings = [];
664
+ const blockers = [];
665
+ const isMain = await this.gitWorktree.isMainWorktree(worktree, this.settingsManager);
666
+ if (isMain) {
667
+ blockers.push(`Cannot cleanup main worktree: "${worktree.branch}" @ "${worktree.path}"`);
668
+ }
669
+ const hasChanges = await hasUncommittedChanges(worktree.path);
670
+ if (hasChanges) {
671
+ const blockerMessage = `Worktree has uncommitted changes.
672
+
673
+ Please resolve before cleanup - you have some options:
674
+ \u2022 Commit changes: cd ${worktree.path} && git commit -am "message"
675
+ \u2022 Stash changes: cd ${worktree.path} && git stash
676
+ \u2022 Force cleanup: il cleanup ${identifier} --force (WARNING: will discard changes)`;
677
+ blockers.push(blockerMessage);
678
+ }
679
+ if ((checkBranchMerge || checkRemoteBranch) && worktree.branch) {
680
+ const mainBranch = await getMergeTargetBranch(worktree.path, {
681
+ settingsManager: this.settingsManager,
682
+ metadataManager: this.metadataManager
683
+ });
684
+ const remoteStatus = await checkRemoteBranchStatus(worktree.branch, worktree.path);
685
+ if (remoteStatus.networkError) {
686
+ const blockerMessage = `Cannot verify remote branch status due to network error.
687
+
688
+ Error: ${remoteStatus.errorMessage ?? "Unknown network error"}
689
+
690
+ Unable to determine if branch '${worktree.branch}' is safely backed up.
691
+ Use --force to proceed without verification.`;
692
+ blockers.push(blockerMessage);
693
+ } else if (remoteStatus.exists && remoteStatus.localAhead) {
694
+ const blockerMessage = `Branch '${worktree.branch}' has unpushed commits that would be lost.
695
+ The remote branch exists but your local branch is ahead.
696
+
697
+ Please resolve before cleanup:
698
+ \u2022 Push your commits: git push origin ${worktree.branch}
699
+ \u2022 Force cleanup: il cleanup ${identifier} --force (WARNING: will lose commits)`;
700
+ blockers.push(blockerMessage);
701
+ } else if (remoteStatus.exists && !remoteStatus.localAhead) {
702
+ } else if (!remoteStatus.exists) {
703
+ const isMerged = await isBranchMergedIntoMain(worktree.branch, mainBranch, worktree.path);
704
+ if (isMerged) {
705
+ } else {
706
+ const blockerMessage = `Branch '${worktree.branch}' has not been pushed to remote and is not merged into '${mainBranch}'.
707
+ Deleting this branch would result in data loss.
708
+
709
+ Please resolve before cleanup - you have some options:
710
+ \u2022 Push to remote: git push -u origin ${worktree.branch}
711
+ \u2022 Merge to ${mainBranch}: git checkout ${mainBranch} && git merge ${worktree.branch}
712
+ \u2022 Force cleanup: il cleanup ${identifier} --force (WARNING: will lose commits)`;
713
+ blockers.push(blockerMessage);
714
+ }
715
+ }
716
+ }
717
+ return {
718
+ isSafe: blockers.length === 0,
719
+ warnings,
720
+ blockers
721
+ };
722
+ }
723
+ /**
724
+ * Validate cleanup safety
725
+ */
726
+ async validateCleanupSafety(identifier) {
727
+ const warnings = [];
728
+ const blockers = [];
729
+ const worktrees = await this.gitWorktree.findWorktreesByIdentifier(identifier);
730
+ if (worktrees.length === 0) {
731
+ blockers.push(`No worktree found for: ${identifier}`);
732
+ return { isSafe: false, warnings, blockers };
733
+ }
734
+ const worktree = worktrees[0];
735
+ if (!worktree) {
736
+ blockers.push(`No worktree found for: ${identifier}`);
737
+ return { isSafe: false, warnings, blockers };
738
+ }
739
+ return await this.validateWorktreeSafety(worktree, identifier);
740
+ }
741
+ /**
742
+ * Parse identifier to determine type and extract number
743
+ * Helper method for port calculation
744
+ */
745
+ parseIdentifier(identifier) {
746
+ const issueId = extractIssueNumber(identifier);
747
+ if (issueId !== null) {
748
+ return {
749
+ type: "issue",
750
+ number: issueId,
751
+ originalInput: identifier
752
+ };
753
+ }
754
+ const prMatch = identifier.match(/(?:pr|PR)[/-](\d+)/);
755
+ if (prMatch == null ? void 0 : prMatch[1]) {
756
+ return {
757
+ type: "pr",
758
+ number: parseInt(prMatch[1], 10),
759
+ originalInput: identifier
760
+ };
761
+ }
762
+ const numericMatch = identifier.match(/^#?(\d+)$/);
763
+ if (numericMatch == null ? void 0 : numericMatch[1]) {
764
+ return {
765
+ type: "issue",
766
+ number: parseInt(numericMatch[1], 10),
767
+ originalInput: identifier
768
+ };
769
+ }
770
+ return {
771
+ type: "branch",
772
+ branchName: identifier,
773
+ originalInput: identifier
774
+ };
775
+ }
776
+ };
777
+
778
+ export {
779
+ ResourceCleanup
780
+ };
781
+ //# sourceMappingURL=chunk-RJ3VBUFK.js.map