@iloom/cli 0.10.0 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +2 -2
- package/dist/{BranchNamingService-ECJHBB67.js → BranchNamingService-4OP6LOH6.js} +2 -2
- package/dist/ClaudeContextManager-ZKTUVQB2.js +14 -0
- package/dist/ClaudeService-TRWOYQ6O.js +13 -0
- package/dist/{LoomLauncher-L64HHS3T.js → LoomLauncher-FRECYMXS.js} +6 -6
- package/dist/{PromptTemplateManager-DULSVRRE.js → PromptTemplateManager-YOE2SIPG.js} +2 -2
- package/dist/README.md +2 -2
- package/dist/{SettingsManager-BQDQA3FK.js → SettingsManager-FNKCOZMQ.js} +2 -2
- package/dist/{build-5GO3XW26.js → build-VHGEMXBA.js} +6 -6
- package/dist/{chunk-ZW2LKWWE.js → chunk-2VEWSM34.js} +3 -3
- package/dist/{chunk-LXLMMXXY.js → chunk-2YZCWAVZ.js} +17 -12
- package/dist/chunk-2YZCWAVZ.js.map +1 -0
- package/dist/{chunk-UD3WJDIV.js → chunk-3F27M7ZD.js} +11 -774
- package/dist/chunk-3F27M7ZD.js.map +1 -0
- package/dist/chunk-4E7LCFUG.js +24 -0
- package/dist/chunk-4E7LCFUG.js.map +1 -0
- package/dist/{chunk-MNHZB4Z2.js → chunk-4FGEGQW4.js} +3 -3
- package/dist/{chunk-WY4QBK43.js → chunk-63QWFWH3.js} +2 -2
- package/dist/{chunk-YYAKPQBT.js → chunk-7VHJNVLF.js} +19 -9
- package/dist/chunk-7VHJNVLF.js.map +1 -0
- package/dist/chunk-BFHDVFSK.js +651 -0
- package/dist/chunk-BFHDVFSK.js.map +1 -0
- package/dist/{chunk-FB47TIJG.js → chunk-BFLMCE2U.js} +4 -23
- package/dist/chunk-BFLMCE2U.js.map +1 -0
- package/dist/{chunk-SN3SQCFK.js → chunk-BU53XIGY.js} +4 -4
- package/dist/{chunk-SF2P22EE.js → chunk-C6HNNJIV.js} +2 -2
- package/dist/{chunk-5MWV33NN.js → chunk-CVCTIDDK.js} +2 -2
- package/dist/{chunk-ZEWU5PZK.js → chunk-G5V75JD5.js} +2 -2
- package/dist/{chunk-ZHPNZC75.js → chunk-HYGUPUV5.js} +26 -21
- package/dist/chunk-HYGUPUV5.js.map +1 -0
- package/dist/{chunk-VGGST52X.js → chunk-I5T677EA.js} +2 -2
- package/dist/{chunk-VECNX6VX.js → chunk-KIK2ZFAL.js} +2 -2
- package/dist/{chunk-3D7WQM7I.js → chunk-LLHXQS3C.js} +2 -2
- package/dist/{chunk-Y4YZTHZE.js → chunk-LUKXJSRI.js} +2 -2
- package/dist/{chunk-ONQYPICO.js → chunk-PZ5WSR5Z.js} +63 -9
- package/dist/chunk-PZ5WSR5Z.js.map +1 -0
- package/dist/{chunk-J5S7DFYC.js → chunk-QFTDZ5E3.js} +2 -2
- package/dist/chunk-RJ3VBUFK.js +781 -0
- package/dist/chunk-RJ3VBUFK.js.map +1 -0
- package/dist/{chunk-UWGVCXRF.js → chunk-SKSYYBCU.js} +23 -1
- package/dist/chunk-SKSYYBCU.js.map +1 -0
- package/dist/{chunk-JO2LZ6EQ.js → chunk-SWSJWA2S.js} +2 -2
- package/dist/{chunk-4WJNIR5O.js → chunk-UUEW5KWB.js} +1 -1
- package/dist/chunk-UUEW5KWB.js.map +1 -0
- package/dist/{chunk-6EU6TCF6.js → chunk-V3SVMFDQ.js} +5 -5
- package/dist/{chunk-NRSWLOAZ.js → chunk-WXIM2WS7.js} +4 -4
- package/dist/{chunk-RYWFS37M.js → chunk-XE4BDRZD.js} +2 -2
- package/dist/{ignite-CGOV3TD4.js → chunk-ZGM2FE2R.js} +105 -73
- package/dist/chunk-ZGM2FE2R.js.map +1 -0
- package/dist/{claude-P3NQR6IJ.js → claude-LN7OWVNI.js} +2 -2
- package/dist/{cleanup-6UCPVMFG.js → cleanup-4ZM2AJDC.js} +19 -17
- package/dist/{cleanup-6UCPVMFG.js.map → cleanup-4ZM2AJDC.js.map} +1 -1
- package/dist/cli.js +167 -614
- package/dist/cli.js.map +1 -1
- package/dist/{commit-L3EPY5QG.js → commit-4CFLXRZ3.js} +12 -10
- package/dist/commit-4CFLXRZ3.js.map +1 -0
- package/dist/{compile-ZS4HYRX5.js → compile-7ALJHZ4N.js} +6 -6
- package/dist/{contribute-ORDDQGSL.js → contribute-5GKLK3BQ.js} +3 -3
- package/dist/{dev-server-FYZ2AQIH.js → dev-server-7SMIB7OF.js} +8 -8
- package/dist/{feedback-TMBXSCM5.js → feedback-EZWF5CAL.js} +10 -8
- package/dist/{feedback-TMBXSCM5.js.map → feedback-EZWF5CAL.js.map} +1 -1
- package/dist/{git-ET64COO3.js → git-GTLKAZRJ.js} +3 -3
- package/dist/ignite-MQETGFNA.js +34 -0
- package/dist/ignite-MQETGFNA.js.map +1 -0
- package/dist/index.d.ts +113 -18
- package/dist/index.js +180 -15
- package/dist/index.js.map +1 -1
- package/dist/{init-GFQ5W7GK.js → init-ZB2RITW6.js} +8 -8
- package/dist/install-deps-RLSGSHH7.js +43 -0
- package/dist/install-deps-RLSGSHH7.js.map +1 -0
- package/dist/{issues-T4ZZSPEG.js → issues-4UUAQ5K6.js} +3 -3
- package/dist/{lint-6TQXDZ3T.js → lint-AAN2NZWG.js} +6 -6
- package/dist/mcp/harness-server.js +140 -0
- package/dist/mcp/harness-server.js.map +1 -0
- package/dist/mcp/issue-management-server.js +140 -18
- package/dist/mcp/issue-management-server.js.map +1 -1
- package/dist/{open-5QZGXQRF.js → open-FXWW3VI4.js} +8 -8
- package/dist/{plan-U7ZQWLFY.js → plan-D3KSN5MU.js} +338 -36
- package/dist/plan-D3KSN5MU.js.map +1 -0
- package/dist/prompts/CLAUDE.md +2 -2
- package/dist/prompts/init-prompt.txt +102 -27
- package/dist/prompts/issue-prompt.txt +46 -0
- package/dist/prompts/plan-prompt.txt +59 -19
- package/dist/prompts/swarm-orchestrator-prompt.txt +121 -80
- package/dist/{rebase-DWIB77KV.js → rebase-62FDLIH4.js} +17 -8
- package/dist/rebase-62FDLIH4.js.map +1 -0
- package/dist/{recap-MX63HAKV.js → recap-OMBOKJST.js} +6 -6
- package/dist/{run-O3TFNQFC.js → run-BBXLRIZB.js} +8 -8
- package/dist/schema/settings.schema.json +36 -2
- package/dist/{shell-G6VC2CYR.js → shell-RF7LTND5.js} +5 -5
- package/dist/{summary-FWHAX55O.js → summary-YZI25KW4.js} +9 -9
- package/dist/{test-F7JNJZYP.js → test-SGO6I5Z7.js} +6 -6
- package/dist/{test-git-BTAOIUE2.js → test-git-XM4TM65W.js} +3 -3
- package/dist/{test-jira-CHYNV33F.js → test-jira-LDTOYFSD.js} +3 -3
- package/dist/{test-prefix-Q6TFSU6F.js → test-prefix-GBO37XCN.js} +3 -3
- package/dist/{test-webserver-EONCG7E7.js → test-webserver-NZ3JTVLL.js} +5 -5
- package/dist/{vscode-VA5X4P25.js → vscode-6XUGHJKL.js} +5 -5
- package/package.json +1 -1
- package/dist/ClaudeContextManager-QXX6ZFST.js +0 -14
- package/dist/ClaudeService-NJNK2SUH.js +0 -13
- package/dist/chunk-4WJNIR5O.js.map +0 -1
- package/dist/chunk-FB47TIJG.js.map +0 -1
- package/dist/chunk-LXLMMXXY.js.map +0 -1
- package/dist/chunk-ONQYPICO.js.map +0 -1
- package/dist/chunk-UD3WJDIV.js.map +0 -1
- package/dist/chunk-UVD4CZKS.js +0 -101
- package/dist/chunk-UVD4CZKS.js.map +0 -1
- package/dist/chunk-UWGVCXRF.js.map +0 -1
- package/dist/chunk-YYAKPQBT.js.map +0 -1
- package/dist/chunk-ZHPNZC75.js.map +0 -1
- package/dist/commit-L3EPY5QG.js.map +0 -1
- package/dist/ignite-CGOV3TD4.js.map +0 -1
- package/dist/plan-U7ZQWLFY.js.map +0 -1
- package/dist/rebase-DWIB77KV.js.map +0 -1
- /package/dist/{BranchNamingService-ECJHBB67.js.map → BranchNamingService-4OP6LOH6.js.map} +0 -0
- /package/dist/{ClaudeContextManager-QXX6ZFST.js.map → ClaudeContextManager-ZKTUVQB2.js.map} +0 -0
- /package/dist/{ClaudeService-NJNK2SUH.js.map → ClaudeService-TRWOYQ6O.js.map} +0 -0
- /package/dist/{LoomLauncher-L64HHS3T.js.map → LoomLauncher-FRECYMXS.js.map} +0 -0
- /package/dist/{PromptTemplateManager-DULSVRRE.js.map → PromptTemplateManager-YOE2SIPG.js.map} +0 -0
- /package/dist/{SettingsManager-BQDQA3FK.js.map → SettingsManager-FNKCOZMQ.js.map} +0 -0
- /package/dist/{build-5GO3XW26.js.map → build-VHGEMXBA.js.map} +0 -0
- /package/dist/{chunk-ZW2LKWWE.js.map → chunk-2VEWSM34.js.map} +0 -0
- /package/dist/{chunk-MNHZB4Z2.js.map → chunk-4FGEGQW4.js.map} +0 -0
- /package/dist/{chunk-WY4QBK43.js.map → chunk-63QWFWH3.js.map} +0 -0
- /package/dist/{chunk-SN3SQCFK.js.map → chunk-BU53XIGY.js.map} +0 -0
- /package/dist/{chunk-SF2P22EE.js.map → chunk-C6HNNJIV.js.map} +0 -0
- /package/dist/{chunk-5MWV33NN.js.map → chunk-CVCTIDDK.js.map} +0 -0
- /package/dist/{chunk-ZEWU5PZK.js.map → chunk-G5V75JD5.js.map} +0 -0
- /package/dist/{chunk-VGGST52X.js.map → chunk-I5T677EA.js.map} +0 -0
- /package/dist/{chunk-VECNX6VX.js.map → chunk-KIK2ZFAL.js.map} +0 -0
- /package/dist/{chunk-3D7WQM7I.js.map → chunk-LLHXQS3C.js.map} +0 -0
- /package/dist/{chunk-Y4YZTHZE.js.map → chunk-LUKXJSRI.js.map} +0 -0
- /package/dist/{chunk-J5S7DFYC.js.map → chunk-QFTDZ5E3.js.map} +0 -0
- /package/dist/{chunk-JO2LZ6EQ.js.map → chunk-SWSJWA2S.js.map} +0 -0
- /package/dist/{chunk-6EU6TCF6.js.map → chunk-V3SVMFDQ.js.map} +0 -0
- /package/dist/{chunk-NRSWLOAZ.js.map → chunk-WXIM2WS7.js.map} +0 -0
- /package/dist/{chunk-RYWFS37M.js.map → chunk-XE4BDRZD.js.map} +0 -0
- /package/dist/{claude-P3NQR6IJ.js.map → claude-LN7OWVNI.js.map} +0 -0
- /package/dist/{compile-ZS4HYRX5.js.map → compile-7ALJHZ4N.js.map} +0 -0
- /package/dist/{contribute-ORDDQGSL.js.map → contribute-5GKLK3BQ.js.map} +0 -0
- /package/dist/{dev-server-FYZ2AQIH.js.map → dev-server-7SMIB7OF.js.map} +0 -0
- /package/dist/{git-ET64COO3.js.map → git-GTLKAZRJ.js.map} +0 -0
- /package/dist/{init-GFQ5W7GK.js.map → init-ZB2RITW6.js.map} +0 -0
- /package/dist/{issues-T4ZZSPEG.js.map → issues-4UUAQ5K6.js.map} +0 -0
- /package/dist/{lint-6TQXDZ3T.js.map → lint-AAN2NZWG.js.map} +0 -0
- /package/dist/{open-5QZGXQRF.js.map → open-FXWW3VI4.js.map} +0 -0
- /package/dist/{recap-MX63HAKV.js.map → recap-OMBOKJST.js.map} +0 -0
- /package/dist/{run-O3TFNQFC.js.map → run-BBXLRIZB.js.map} +0 -0
- /package/dist/{shell-G6VC2CYR.js.map → shell-RF7LTND5.js.map} +0 -0
- /package/dist/{summary-FWHAX55O.js.map → summary-YZI25KW4.js.map} +0 -0
- /package/dist/{test-F7JNJZYP.js.map → test-SGO6I5Z7.js.map} +0 -0
- /package/dist/{test-git-BTAOIUE2.js.map → test-git-XM4TM65W.js.map} +0 -0
- /package/dist/{test-jira-CHYNV33F.js.map → test-jira-LDTOYFSD.js.map} +0 -0
- /package/dist/{test-prefix-Q6TFSU6F.js.map → test-prefix-GBO37XCN.js.map} +0 -0
- /package/dist/{test-webserver-EONCG7E7.js.map → test-webserver-NZ3JTVLL.js.map} +0 -0
- /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
|