@iloom/cli 0.3.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -3
- package/dist/{BranchNamingService-A77VI6AI.js → BranchNamingService-TOM2KAUT.js} +4 -3
- package/dist/ClaudeContextManager-VEGJTS5E.js +16 -0
- package/dist/ClaudeService-ICSHJMQ5.js +15 -0
- package/dist/GitHubService-RPM27GWD.js +12 -0
- package/dist/{LoomLauncher-ZV3ZZIBA.js → LoomLauncher-SJBZFZXE.js} +25 -22
- package/dist/LoomLauncher-SJBZFZXE.js.map +1 -0
- package/dist/PromptTemplateManager-2TDZAUC6.js +9 -0
- package/dist/README.md +13 -3
- package/dist/{SettingsManager-I2LRCW2A.js → SettingsManager-FJFU6JJD.js} +7 -3
- package/dist/SettingsMigrationManager-EH3J2TCN.js +10 -0
- package/dist/{chunk-UJL4HI2R.js → chunk-3NFBZRPR.js} +2 -2
- package/dist/chunk-6UIGZD2N.js +20 -0
- package/dist/chunk-6UIGZD2N.js.map +1 -0
- package/dist/{chunk-RIEO2WML.js → chunk-74VMN2KC.js} +26 -2
- package/dist/chunk-74VMN2KC.js.map +1 -0
- package/dist/{chunk-OYF4VIFI.js → chunk-75B2HZZ5.js} +147 -22
- package/dist/chunk-75B2HZZ5.js.map +1 -0
- package/dist/{chunk-PGPI5LR4.js → chunk-ADDNFQJ4.js} +7 -21
- package/dist/chunk-ADDNFQJ4.js.map +1 -0
- package/dist/{chunk-AKUJXDNW.js → chunk-F4J6KEL6.js} +3 -3
- package/dist/{chunk-DLHA5VQ3.js → chunk-HD5SUKI2.js} +36 -179
- package/dist/chunk-HD5SUKI2.js.map +1 -0
- package/dist/chunk-HHDSIE72.js +667 -0
- package/dist/chunk-HHDSIE72.js.map +1 -0
- package/dist/{chunk-OXAM2WVC.js → chunk-HVGQP44L.js} +21 -1
- package/dist/chunk-HVGQP44L.js.map +1 -0
- package/dist/{chunk-RW54ZMBM.js → chunk-JJUPY5MM.js} +2 -2
- package/dist/{chunk-UAN4A3YU.js → chunk-KM3W7YQX.js} +11 -11
- package/dist/{chunk-3RUPPQRG.js → chunk-KO2FOMHL.js} +43 -2
- package/dist/{chunk-3RUPPQRG.js.map → chunk-KO2FOMHL.js.map} +1 -1
- package/dist/{chunk-2MAIX45J.js → chunk-LTNDJMTH.js} +104 -43
- package/dist/chunk-LTNDJMTH.js.map +1 -0
- package/dist/{chunk-2CXREBLZ.js → chunk-M5XUCTTJ.js} +8 -6
- package/dist/chunk-M5XUCTTJ.js.map +1 -0
- package/dist/{chunk-4XIDC3NF.js → chunk-MD6HA5IK.js} +2 -2
- package/dist/chunk-MLS5FAV7.js +189 -0
- package/dist/chunk-MLS5FAV7.js.map +1 -0
- package/dist/{chunk-2IJEMXOB.js → chunk-NFVFVYAP.js} +419 -427
- package/dist/chunk-NFVFVYAP.js.map +1 -0
- package/dist/{chunk-OC4H6HJD.js → chunk-O7WHXLCB.js} +2 -2
- package/dist/{chunk-M7JJCX53.js → chunk-OEGECBFS.js} +20 -20
- package/dist/chunk-OEGECBFS.js.map +1 -0
- package/dist/{chunk-MKWYLDFK.js → chunk-OF7BNW4D.js} +43 -3
- package/dist/chunk-OF7BNW4D.js.map +1 -0
- package/dist/{chunk-SUOXY5WJ.js → chunk-P2WZIDF3.js} +5 -5
- package/dist/chunk-P2WZIDF3.js.map +1 -0
- package/dist/{chunk-PA6Q6AWM.js → chunk-PSFVTBM7.js} +2 -2
- package/dist/chunk-QHA67Q7A.js +281 -0
- package/dist/chunk-QHA67Q7A.js.map +1 -0
- package/dist/{chunk-ZM3CFL5L.js → chunk-QRBOPFAA.js} +3 -3
- package/dist/{chunk-IFB4Z76W.js → chunk-S44CHE3G.js} +13 -12
- package/dist/chunk-S44CHE3G.js.map +1 -0
- package/dist/{chunk-CE26YH2U.js → chunk-SJ2GZ6RF.js} +48 -50
- package/dist/chunk-SJ2GZ6RF.js.map +1 -0
- package/dist/{chunk-SSCQCCJ7.js → chunk-THF25ICZ.js} +2 -2
- package/dist/{chunk-5Q3NDNNV.js → chunk-TR5MC2U6.js} +153 -6
- package/dist/chunk-TR5MC2U6.js.map +1 -0
- package/dist/{chunk-5VK4NRSF.js → chunk-UNXRACJ7.js} +35 -36
- package/dist/chunk-UNXRACJ7.js.map +1 -0
- package/dist/{chunk-GEHQXLEI.js → chunk-UYVWLISQ.js} +18 -35
- package/dist/chunk-UYVWLISQ.js.map +1 -0
- package/dist/{chunk-OSCLCMDG.js → chunk-UYWAESOT.js} +3 -3
- package/dist/{chunk-ZT3YZB4K.js → chunk-VBFDVGAE.js} +12 -12
- package/dist/chunk-VBFDVGAE.js.map +1 -0
- package/dist/{chunk-CDZERT7Z.js → chunk-VWNS6DH5.js} +48 -4
- package/dist/chunk-VWNS6DH5.js.map +1 -0
- package/dist/{chunk-CFFQ2Z7A.js → chunk-WUQQNE63.js} +2 -2
- package/dist/{claude-W52VKI6L.js → claude-X7EBJRB2.js} +8 -5
- package/dist/{cleanup-H4VXU3C3.js → cleanup-7QVPYBJJ.js} +133 -122
- package/dist/cleanup-7QVPYBJJ.js.map +1 -0
- package/dist/cli.js +901 -425
- package/dist/cli.js.map +1 -1
- package/dist/{color-F7RU6B6Z.js → color-ZPIIUADB.js} +3 -3
- package/dist/{contribute-Y7IQV5QY.js → contribute-RZYCYUDX.js} +8 -6
- package/dist/{contribute-Y7IQV5QY.js.map → contribute-RZYCYUDX.js.map} +1 -1
- package/dist/dev-server-LOY7YWCP.js +298 -0
- package/dist/dev-server-LOY7YWCP.js.map +1 -0
- package/dist/{feedback-XTUCKJNT.js → feedback-562KPG5U.js} +13 -12
- package/dist/{feedback-XTUCKJNT.js.map → feedback-562KPG5U.js.map} +1 -1
- package/dist/{git-IYA53VIC.js → git-OXJACVAU.js} +16 -4
- package/dist/hooks/iloom-hook.js +258 -0
- package/dist/{ignite-T74RYXCA.js → ignite-VSIPGKKG.js} +245 -39
- package/dist/ignite-VSIPGKKG.js.map +1 -0
- package/dist/index.d.ts +459 -124
- package/dist/index.js +740 -210
- package/dist/index.js.map +1 -1
- package/dist/init-SCR2LQ4A.js +21 -0
- package/dist/{installation-detector-VARGFFRZ.js → installation-detector-6R6YOFVZ.js} +3 -3
- package/dist/mcp/issue-management-server.js +2 -1
- package/dist/mcp/issue-management-server.js.map +1 -1
- package/dist/neon-helpers-L5CXQ5CT.js +11 -0
- package/dist/{open-UMXANW5S.js → open-CX7HUE26.js} +12 -10
- package/dist/{open-UMXANW5S.js.map → open-CX7HUE26.js.map} +1 -1
- package/dist/projects-6DTNDVLH.js +73 -0
- package/dist/projects-6DTNDVLH.js.map +1 -0
- package/dist/{prompt-QALMYTVC.js → prompt-A7GGRHSY.js} +3 -3
- package/dist/prompts/init-prompt.txt +49 -0
- package/dist/prompts/issue-prompt.txt +110 -8
- package/dist/prompts/regular-prompt.txt +90 -0
- package/dist/prompts/session-summary-prompt.txt +82 -0
- package/dist/{rebase-VJ2VKR6R.js → rebase-55URTXZC.js} +11 -9
- package/dist/{rebase-VJ2VKR6R.js.map → rebase-55URTXZC.js.map} +1 -1
- package/dist/{remote-VUNCQZ6J.js → remote-73TZ2ADI.js} +3 -3
- package/dist/{run-MJYY4PUT.js → run-DP2U2CA2.js} +12 -10
- package/dist/{run-MJYY4PUT.js.map → run-DP2U2CA2.js.map} +1 -1
- package/dist/schema/settings.schema.json +49 -0
- package/dist/summary-J3CJSM7L.js +244 -0
- package/dist/summary-J3CJSM7L.js.map +1 -0
- package/dist/{test-git-IT5EWQ5C.js → test-git-QLAIBJLX.js} +6 -4
- package/dist/{test-git-IT5EWQ5C.js.map → test-git-QLAIBJLX.js.map} +1 -1
- package/dist/{test-prefix-NPWDPUUH.js → test-prefix-6YM2ZOON.js} +6 -4
- package/dist/{test-prefix-NPWDPUUH.js.map → test-prefix-6YM2ZOON.js.map} +1 -1
- package/dist/{test-tabs-PRMRSHKI.js → test-tabs-JGO3VOXJ.js} +4 -4
- package/dist/{test-webserver-DAHONWCS.js → test-webserver-VPNLAFZ3.js} +2 -2
- package/dist/{update-4TDDUR5K.js → update-LETF5ASC.js} +4 -4
- package/dist/{update-notifier-QEX3CJHA.js → update-notifier-H55ZK7NU.js} +3 -3
- package/package.json +6 -6
- package/dist/ClaudeContextManager-BN7RE5ZQ.js +0 -15
- package/dist/ClaudeService-DLYLJUPA.js +0 -14
- package/dist/GitHubService-FZHHBOFG.js +0 -11
- package/dist/LoomLauncher-ZV3ZZIBA.js.map +0 -1
- package/dist/PromptTemplateManager-6HH3PVXV.js +0 -9
- package/dist/SettingsMigrationManager-TJ7UWZG5.js +0 -10
- package/dist/chunk-2CXREBLZ.js.map +0 -1
- package/dist/chunk-2IJEMXOB.js.map +0 -1
- package/dist/chunk-2MAIX45J.js.map +0 -1
- package/dist/chunk-5Q3NDNNV.js.map +0 -1
- package/dist/chunk-5VK4NRSF.js.map +0 -1
- package/dist/chunk-CDZERT7Z.js.map +0 -1
- package/dist/chunk-CE26YH2U.js.map +0 -1
- package/dist/chunk-DLHA5VQ3.js.map +0 -1
- package/dist/chunk-GEHQXLEI.js.map +0 -1
- package/dist/chunk-IFB4Z76W.js.map +0 -1
- package/dist/chunk-M7JJCX53.js.map +0 -1
- package/dist/chunk-MKWYLDFK.js.map +0 -1
- package/dist/chunk-OXAM2WVC.js.map +0 -1
- package/dist/chunk-OYF4VIFI.js.map +0 -1
- package/dist/chunk-PGPI5LR4.js.map +0 -1
- package/dist/chunk-RIEO2WML.js.map +0 -1
- package/dist/chunk-SUOXY5WJ.js.map +0 -1
- package/dist/chunk-ZT3YZB4K.js.map +0 -1
- package/dist/cleanup-H4VXU3C3.js.map +0 -1
- package/dist/ignite-T74RYXCA.js.map +0 -1
- package/dist/init-4FHTAM3F.js +0 -19
- package/dist/logger-MKYH4UDV.js +0 -12
- package/dist/neon-helpers-77PBPGJ5.js +0 -10
- package/dist/update-notifier-QEX3CJHA.js.map +0 -1
- /package/dist/{BranchNamingService-A77VI6AI.js.map → BranchNamingService-TOM2KAUT.js.map} +0 -0
- /package/dist/{ClaudeContextManager-BN7RE5ZQ.js.map → ClaudeContextManager-VEGJTS5E.js.map} +0 -0
- /package/dist/{ClaudeService-DLYLJUPA.js.map → ClaudeService-ICSHJMQ5.js.map} +0 -0
- /package/dist/{GitHubService-FZHHBOFG.js.map → GitHubService-RPM27GWD.js.map} +0 -0
- /package/dist/{PromptTemplateManager-6HH3PVXV.js.map → PromptTemplateManager-2TDZAUC6.js.map} +0 -0
- /package/dist/{SettingsManager-I2LRCW2A.js.map → SettingsManager-FJFU6JJD.js.map} +0 -0
- /package/dist/{SettingsMigrationManager-TJ7UWZG5.js.map → SettingsMigrationManager-EH3J2TCN.js.map} +0 -0
- /package/dist/{chunk-UJL4HI2R.js.map → chunk-3NFBZRPR.js.map} +0 -0
- /package/dist/{chunk-AKUJXDNW.js.map → chunk-F4J6KEL6.js.map} +0 -0
- /package/dist/{chunk-RW54ZMBM.js.map → chunk-JJUPY5MM.js.map} +0 -0
- /package/dist/{chunk-UAN4A3YU.js.map → chunk-KM3W7YQX.js.map} +0 -0
- /package/dist/{chunk-4XIDC3NF.js.map → chunk-MD6HA5IK.js.map} +0 -0
- /package/dist/{chunk-OC4H6HJD.js.map → chunk-O7WHXLCB.js.map} +0 -0
- /package/dist/{chunk-PA6Q6AWM.js.map → chunk-PSFVTBM7.js.map} +0 -0
- /package/dist/{chunk-ZM3CFL5L.js.map → chunk-QRBOPFAA.js.map} +0 -0
- /package/dist/{chunk-SSCQCCJ7.js.map → chunk-THF25ICZ.js.map} +0 -0
- /package/dist/{chunk-OSCLCMDG.js.map → chunk-UYWAESOT.js.map} +0 -0
- /package/dist/{chunk-CFFQ2Z7A.js.map → chunk-WUQQNE63.js.map} +0 -0
- /package/dist/{claude-W52VKI6L.js.map → claude-X7EBJRB2.js.map} +0 -0
- /package/dist/{color-F7RU6B6Z.js.map → color-ZPIIUADB.js.map} +0 -0
- /package/dist/{git-IYA53VIC.js.map → git-OXJACVAU.js.map} +0 -0
- /package/dist/{init-4FHTAM3F.js.map → init-SCR2LQ4A.js.map} +0 -0
- /package/dist/{installation-detector-VARGFFRZ.js.map → installation-detector-6R6YOFVZ.js.map} +0 -0
- /package/dist/{logger-MKYH4UDV.js.map → neon-helpers-L5CXQ5CT.js.map} +0 -0
- /package/dist/{neon-helpers-77PBPGJ5.js.map → prompt-A7GGRHSY.js.map} +0 -0
- /package/dist/{prompt-QALMYTVC.js.map → remote-73TZ2ADI.js.map} +0 -0
- /package/dist/{test-tabs-PRMRSHKI.js.map → test-tabs-JGO3VOXJ.js.map} +0 -0
- /package/dist/{test-webserver-DAHONWCS.js.map → test-webserver-VPNLAFZ3.js.map} +0 -0
- /package/dist/{update-4TDDUR5K.js.map → update-LETF5ASC.js.map} +0 -0
- /package/dist/{remote-VUNCQZ6J.js.map → update-notifier-H55ZK7NU.js.map} +0 -0
|
@@ -5,23 +5,31 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
installDependencies,
|
|
7
7
|
runScript
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-VBFDVGAE.js";
|
|
9
9
|
import {
|
|
10
10
|
hasScript,
|
|
11
11
|
readPackageJson
|
|
12
12
|
} from "./chunk-2ZPFJQ3B.js";
|
|
13
13
|
import {
|
|
14
14
|
branchExists,
|
|
15
|
+
checkRemoteBranchStatus,
|
|
15
16
|
ensureRepositoryHasCommits,
|
|
16
17
|
executeGitCommand,
|
|
17
18
|
extractIssueNumber,
|
|
19
|
+
extractPRNumber,
|
|
18
20
|
findMainWorktreePathWithSettings,
|
|
21
|
+
findWorktreeForBranch,
|
|
22
|
+
getMergeTargetBranch,
|
|
19
23
|
hasUncommittedChanges,
|
|
24
|
+
isBranchMergedIntoMain,
|
|
20
25
|
isFileTrackedByGit
|
|
21
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-TR5MC2U6.js";
|
|
27
|
+
import {
|
|
28
|
+
MetadataManager
|
|
29
|
+
} from "./chunk-MLS5FAV7.js";
|
|
22
30
|
import {
|
|
23
31
|
SettingsManager
|
|
24
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-VWNS6DH5.js";
|
|
25
33
|
import {
|
|
26
34
|
calculateForegroundColor,
|
|
27
35
|
generateColorFromBranchName,
|
|
@@ -29,7 +37,10 @@ import {
|
|
|
29
37
|
lightenColor,
|
|
30
38
|
rgbToHex,
|
|
31
39
|
selectDistinctColor
|
|
32
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-WUQQNE63.js";
|
|
41
|
+
import {
|
|
42
|
+
generateDeterministicSessionId
|
|
43
|
+
} from "./chunk-75B2HZZ5.js";
|
|
33
44
|
import {
|
|
34
45
|
findEnvFileForDatabaseUrl,
|
|
35
46
|
formatEnvLine,
|
|
@@ -37,193 +48,21 @@ import {
|
|
|
37
48
|
loadEnvIntoProcess,
|
|
38
49
|
parseEnvFile,
|
|
39
50
|
validateEnvVariable
|
|
40
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-3NFBZRPR.js";
|
|
52
|
+
import {
|
|
53
|
+
getLogger
|
|
54
|
+
} from "./chunk-6UIGZD2N.js";
|
|
41
55
|
import {
|
|
42
|
-
createLogger,
|
|
43
56
|
logger
|
|
44
|
-
} from "./chunk-
|
|
45
|
-
|
|
46
|
-
// src/lib/MetadataManager.ts
|
|
47
|
-
import path from "path";
|
|
48
|
-
import os from "os";
|
|
49
|
-
import fs from "fs-extra";
|
|
50
|
-
var MetadataManager = class {
|
|
51
|
-
constructor() {
|
|
52
|
-
this.loomsDir = path.join(os.homedir(), ".config", "iloom-ai", "looms");
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Convert worktree path to filename slug per spec section 2.2
|
|
56
|
-
*
|
|
57
|
-
* Algorithm:
|
|
58
|
-
* 1. Trim trailing slashes
|
|
59
|
-
* 2. Replace all path separators (/ or \) with __ (double underscore)
|
|
60
|
-
* 3. Replace any other non-alphanumeric characters (except _ and -) with -
|
|
61
|
-
* 4. Append .json
|
|
62
|
-
*
|
|
63
|
-
* Example:
|
|
64
|
-
* - Worktree: /Users/jane/dev/repo
|
|
65
|
-
* - Filename: _Users__jane__dev__repo.json
|
|
66
|
-
*/
|
|
67
|
-
slugifyPath(worktreePath) {
|
|
68
|
-
let slug = worktreePath.replace(/[/\\]+$/, "");
|
|
69
|
-
slug = slug.replace(/[/\\]/g, "___");
|
|
70
|
-
slug = slug.replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
71
|
-
return `${slug}.json`;
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Get the full path to the metadata file for a worktree
|
|
75
|
-
*/
|
|
76
|
-
getFilePath(worktreePath) {
|
|
77
|
-
const filename = this.slugifyPath(worktreePath);
|
|
78
|
-
return path.join(this.loomsDir, filename);
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Write metadata for a worktree (spec section 3.1)
|
|
82
|
-
*
|
|
83
|
-
* @param worktreePath - Absolute path to the worktree (used for file naming)
|
|
84
|
-
* @param input - Metadata to write (description plus additional fields)
|
|
85
|
-
*/
|
|
86
|
-
async writeMetadata(worktreePath, input) {
|
|
87
|
-
try {
|
|
88
|
-
await fs.ensureDir(this.loomsDir, { mode: 493 });
|
|
89
|
-
const content = {
|
|
90
|
-
description: input.description,
|
|
91
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
92
|
-
version: 1,
|
|
93
|
-
branchName: input.branchName,
|
|
94
|
-
worktreePath: input.worktreePath,
|
|
95
|
-
issueType: input.issueType,
|
|
96
|
-
issue_numbers: input.issue_numbers,
|
|
97
|
-
pr_numbers: input.pr_numbers,
|
|
98
|
-
issueTracker: input.issueTracker,
|
|
99
|
-
colorHex: input.colorHex
|
|
100
|
-
};
|
|
101
|
-
const filePath = this.getFilePath(worktreePath);
|
|
102
|
-
await fs.writeFile(filePath, JSON.stringify(content, null, 2), { mode: 420 });
|
|
103
|
-
logger.debug(`Metadata written for worktree: ${worktreePath}`);
|
|
104
|
-
} catch (error) {
|
|
105
|
-
logger.warn(
|
|
106
|
-
`Failed to write metadata for worktree: ${error instanceof Error ? error.message : String(error)}`
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Read metadata for a worktree (spec section 3.2)
|
|
112
|
-
*
|
|
113
|
-
* @param worktreePath - Absolute path to the worktree
|
|
114
|
-
* @returns The metadata object with all fields, or null if not found/invalid
|
|
115
|
-
*/
|
|
116
|
-
async readMetadata(worktreePath) {
|
|
117
|
-
try {
|
|
118
|
-
const filePath = this.getFilePath(worktreePath);
|
|
119
|
-
if (!await fs.pathExists(filePath)) {
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
const content = await fs.readFile(filePath, "utf8");
|
|
123
|
-
const data = JSON.parse(content);
|
|
124
|
-
if (!data.description) {
|
|
125
|
-
return null;
|
|
126
|
-
}
|
|
127
|
-
return {
|
|
128
|
-
description: data.description,
|
|
129
|
-
created_at: data.created_at ?? null,
|
|
130
|
-
branchName: data.branchName ?? null,
|
|
131
|
-
worktreePath: data.worktreePath ?? null,
|
|
132
|
-
issueType: data.issueType ?? null,
|
|
133
|
-
issue_numbers: data.issue_numbers ?? [],
|
|
134
|
-
pr_numbers: data.pr_numbers ?? [],
|
|
135
|
-
issueTracker: data.issueTracker ?? null,
|
|
136
|
-
colorHex: data.colorHex ?? null
|
|
137
|
-
};
|
|
138
|
-
} catch (error) {
|
|
139
|
-
logger.debug(
|
|
140
|
-
`Could not read metadata for worktree ${worktreePath}: ${error instanceof Error ? error.message : String(error)}`
|
|
141
|
-
);
|
|
142
|
-
return null;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* List all stored loom metadata files
|
|
147
|
-
*
|
|
148
|
-
* Returns an array of LoomMetadata objects for all valid metadata files
|
|
149
|
-
* in the looms directory. Invalid or unreadable files are skipped.
|
|
150
|
-
*
|
|
151
|
-
* @returns Array of LoomMetadata objects from all stored files
|
|
152
|
-
*/
|
|
153
|
-
async listAllMetadata() {
|
|
154
|
-
const results = [];
|
|
155
|
-
try {
|
|
156
|
-
if (!await fs.pathExists(this.loomsDir)) {
|
|
157
|
-
return results;
|
|
158
|
-
}
|
|
159
|
-
const files = await fs.readdir(this.loomsDir);
|
|
160
|
-
for (const file of files) {
|
|
161
|
-
if (!file.endsWith(".json")) {
|
|
162
|
-
continue;
|
|
163
|
-
}
|
|
164
|
-
try {
|
|
165
|
-
const filePath = path.join(this.loomsDir, file);
|
|
166
|
-
const content = await fs.readFile(filePath, "utf8");
|
|
167
|
-
const data = JSON.parse(content);
|
|
168
|
-
if (!data.description) {
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
171
|
-
results.push({
|
|
172
|
-
description: data.description,
|
|
173
|
-
created_at: data.created_at ?? null,
|
|
174
|
-
branchName: data.branchName ?? null,
|
|
175
|
-
worktreePath: data.worktreePath ?? null,
|
|
176
|
-
issueType: data.issueType ?? null,
|
|
177
|
-
issue_numbers: data.issue_numbers ?? [],
|
|
178
|
-
pr_numbers: data.pr_numbers ?? [],
|
|
179
|
-
issueTracker: data.issueTracker ?? null,
|
|
180
|
-
colorHex: data.colorHex ?? null
|
|
181
|
-
});
|
|
182
|
-
} catch (error) {
|
|
183
|
-
logger.debug(
|
|
184
|
-
`Skipping metadata file ${file}: ${error instanceof Error ? error.message : String(error)}`
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
} catch (error) {
|
|
189
|
-
logger.debug(
|
|
190
|
-
`Could not list metadata files: ${error instanceof Error ? error.message : String(error)}`
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
return results;
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* Delete metadata for a worktree (spec section 3.3)
|
|
197
|
-
*
|
|
198
|
-
* Idempotent: silently succeeds if file doesn't exist
|
|
199
|
-
* Non-fatal: logs warning on permission errors but doesn't throw
|
|
200
|
-
*
|
|
201
|
-
* @param worktreePath - Absolute path to the worktree
|
|
202
|
-
*/
|
|
203
|
-
async deleteMetadata(worktreePath) {
|
|
204
|
-
try {
|
|
205
|
-
const filePath = this.getFilePath(worktreePath);
|
|
206
|
-
if (!await fs.pathExists(filePath)) {
|
|
207
|
-
logger.debug(`No metadata file to delete for worktree: ${worktreePath}`);
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
await fs.unlink(filePath);
|
|
211
|
-
logger.debug(`Metadata deleted for worktree: ${worktreePath}`);
|
|
212
|
-
} catch (error) {
|
|
213
|
-
logger.warn(
|
|
214
|
-
`Failed to delete metadata for worktree: ${error instanceof Error ? error.message : String(error)}`
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
};
|
|
57
|
+
} from "./chunk-UYVWLISQ.js";
|
|
219
58
|
|
|
220
59
|
// src/lib/LoomManager.ts
|
|
221
|
-
import
|
|
222
|
-
import
|
|
60
|
+
import path2 from "path";
|
|
61
|
+
import fs2 from "fs-extra";
|
|
223
62
|
|
|
224
63
|
// src/lib/VSCodeIntegration.ts
|
|
225
|
-
import
|
|
226
|
-
import
|
|
64
|
+
import fs from "fs-extra";
|
|
65
|
+
import path from "path";
|
|
227
66
|
import { parse, modify, applyEdits } from "jsonc-parser";
|
|
228
67
|
var VSCodeIntegration = class {
|
|
229
68
|
/**
|
|
@@ -233,10 +72,10 @@ var VSCodeIntegration = class {
|
|
|
233
72
|
* @param hexColor - Hex color string (e.g., "#dcebf8")
|
|
234
73
|
*/
|
|
235
74
|
async setTitleBarColor(workspacePath, hexColor) {
|
|
236
|
-
const vscodeDir =
|
|
237
|
-
const settingsPath =
|
|
75
|
+
const vscodeDir = path.join(workspacePath, ".vscode");
|
|
76
|
+
const settingsPath = path.join(vscodeDir, "settings.json");
|
|
238
77
|
try {
|
|
239
|
-
await
|
|
78
|
+
await fs.ensureDir(vscodeDir);
|
|
240
79
|
const settings = await this.readSettings(settingsPath);
|
|
241
80
|
const updatedSettings = this.mergeColorSettings(settings, hexColor);
|
|
242
81
|
await this.writeSettings(settingsPath, updatedSettings);
|
|
@@ -256,10 +95,10 @@ var VSCodeIntegration = class {
|
|
|
256
95
|
*/
|
|
257
96
|
async readSettings(settingsPath) {
|
|
258
97
|
try {
|
|
259
|
-
if (!await
|
|
98
|
+
if (!await fs.pathExists(settingsPath)) {
|
|
260
99
|
return {};
|
|
261
100
|
}
|
|
262
|
-
const content = await
|
|
101
|
+
const content = await fs.readFile(settingsPath, "utf8");
|
|
263
102
|
const errors = [];
|
|
264
103
|
const settings = parse(content, errors, { allowTrailingComma: true });
|
|
265
104
|
if (errors.length > 0) {
|
|
@@ -283,8 +122,8 @@ var VSCodeIntegration = class {
|
|
|
283
122
|
async writeSettings(settingsPath, settings) {
|
|
284
123
|
try {
|
|
285
124
|
let content;
|
|
286
|
-
if (await
|
|
287
|
-
const existingContent = await
|
|
125
|
+
if (await fs.pathExists(settingsPath)) {
|
|
126
|
+
const existingContent = await fs.readFile(settingsPath, "utf8");
|
|
288
127
|
if (existingContent.includes("//") || existingContent.includes("/*")) {
|
|
289
128
|
content = await this.modifyWithCommentsPreserved(existingContent, settings);
|
|
290
129
|
} else {
|
|
@@ -294,8 +133,8 @@ var VSCodeIntegration = class {
|
|
|
294
133
|
content = JSON.stringify(settings, null, 2) + "\n";
|
|
295
134
|
}
|
|
296
135
|
const tempPath = `${settingsPath}.tmp`;
|
|
297
|
-
await
|
|
298
|
-
await
|
|
136
|
+
await fs.writeFile(tempPath, content, "utf8");
|
|
137
|
+
await fs.rename(tempPath, settingsPath);
|
|
299
138
|
} catch (error) {
|
|
300
139
|
throw new Error(
|
|
301
140
|
`Failed to write settings.json: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
@@ -373,7 +212,7 @@ var LoomManager = class {
|
|
|
373
212
|
return null;
|
|
374
213
|
}
|
|
375
214
|
try {
|
|
376
|
-
const envFilePath =
|
|
215
|
+
const envFilePath = path2.join(loomPath, ".env");
|
|
377
216
|
const settings = await this.settings.loadSettings();
|
|
378
217
|
const databaseUrlVarName = ((_b = (_a = settings.capabilities) == null ? void 0 : _a.database) == null ? void 0 : _b.databaseUrlEnvVarName) ?? "DATABASE_URL";
|
|
379
218
|
const connectionString = await this.environment.getEnvVariable(envFilePath, databaseUrlVarName);
|
|
@@ -382,7 +221,7 @@ var LoomManager = class {
|
|
|
382
221
|
}
|
|
383
222
|
return await this.database.getBranchNameFromConnectionString(connectionString, loomPath);
|
|
384
223
|
} catch (error) {
|
|
385
|
-
|
|
224
|
+
getLogger().debug(`Could not get database branch for loom at ${loomPath}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
386
225
|
return null;
|
|
387
226
|
}
|
|
388
227
|
}
|
|
@@ -392,28 +231,29 @@ var LoomManager = class {
|
|
|
392
231
|
* NEW: Checks for existing worktrees and reuses them if found
|
|
393
232
|
*/
|
|
394
233
|
async createIloom(input) {
|
|
395
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m
|
|
396
|
-
|
|
234
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
235
|
+
getLogger().info("Fetching issue data...");
|
|
397
236
|
const issueData = await this.fetchIssueData(input);
|
|
398
237
|
if (input.type === "issue" || input.type === "pr" || input.type === "branch") {
|
|
399
|
-
|
|
238
|
+
getLogger().info("Checking for existing worktree...");
|
|
400
239
|
const existing = await this.findExistingIloom(input, issueData);
|
|
401
240
|
if (existing) {
|
|
402
|
-
|
|
241
|
+
getLogger().success(`Found existing worktree, reusing: ${existing.path}`);
|
|
403
242
|
return await this.reuseIloom(existing, input, issueData);
|
|
404
243
|
}
|
|
405
|
-
|
|
244
|
+
getLogger().info("No existing worktree found, creating new one...");
|
|
406
245
|
}
|
|
407
|
-
|
|
246
|
+
getLogger().info("Preparing branch name...");
|
|
408
247
|
const branchName = await this.prepareBranchName(input, issueData);
|
|
409
|
-
|
|
248
|
+
getLogger().info("Creating git worktree...");
|
|
410
249
|
const worktreePath = await this.createWorktreeOnly(input, branchName);
|
|
411
250
|
this.loadMainEnvFile();
|
|
412
251
|
const { capabilities, binEntries } = await this.capabilityDetector.detectCapabilities(worktreePath);
|
|
413
252
|
await this.copyEnvironmentFiles(worktreePath);
|
|
414
|
-
await this.copyIloomSettings(worktreePath
|
|
253
|
+
await this.copyIloomSettings(worktreePath);
|
|
254
|
+
await this.copyClaudeSettings(worktreePath);
|
|
415
255
|
const settingsData = await this.settings.loadSettings();
|
|
416
|
-
const basePort = ((
|
|
256
|
+
const basePort = ((_b = (_a = settingsData.capabilities) == null ? void 0 : _a.web) == null ? void 0 : _b.basePort) ?? 3e3;
|
|
417
257
|
let port = basePort;
|
|
418
258
|
if (capabilities.includes("web")) {
|
|
419
259
|
port = await this.setupPortForWeb(worktreePath, input, basePort);
|
|
@@ -421,10 +261,10 @@ var LoomManager = class {
|
|
|
421
261
|
try {
|
|
422
262
|
await installDependencies(worktreePath, true, true);
|
|
423
263
|
} catch (error) {
|
|
424
|
-
|
|
264
|
+
getLogger().warn(`Failed to install dependencies: ${error instanceof Error ? error.message : "Unknown error"}`, error);
|
|
425
265
|
}
|
|
426
266
|
let databaseBranch = void 0;
|
|
427
|
-
if (this.database && !((
|
|
267
|
+
if (this.database && !((_c = input.options) == null ? void 0 : _c.skipDatabase)) {
|
|
428
268
|
try {
|
|
429
269
|
const connectionString = await this.database.createBranchIfConfigured(
|
|
430
270
|
branchName,
|
|
@@ -432,7 +272,7 @@ var LoomManager = class {
|
|
|
432
272
|
// workspace path - checks all dotenv-flow files
|
|
433
273
|
void 0,
|
|
434
274
|
// cwd
|
|
435
|
-
(
|
|
275
|
+
(_d = input.parentLoom) == null ? void 0 : _d.databaseBranch
|
|
436
276
|
// fromBranch - use parent's database branch for child looms
|
|
437
277
|
);
|
|
438
278
|
if (connectionString) {
|
|
@@ -441,19 +281,19 @@ var LoomManager = class {
|
|
|
441
281
|
worktreePath,
|
|
442
282
|
varName,
|
|
443
283
|
isFileTrackedByGit,
|
|
444
|
-
async (p) =>
|
|
284
|
+
async (p) => fs2.pathExists(p),
|
|
445
285
|
async (p, v) => this.environment.getEnvVariable(p, v)
|
|
446
286
|
);
|
|
447
287
|
await this.environment.setEnvVar(
|
|
448
|
-
|
|
288
|
+
path2.join(worktreePath, targetFile),
|
|
449
289
|
varName,
|
|
450
290
|
connectionString
|
|
451
291
|
);
|
|
452
|
-
|
|
292
|
+
getLogger().success("Database branch configured");
|
|
453
293
|
databaseBranch = branchName;
|
|
454
294
|
}
|
|
455
295
|
} catch (error) {
|
|
456
|
-
|
|
296
|
+
getLogger().error(
|
|
457
297
|
`Failed to setup database branch: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
458
298
|
);
|
|
459
299
|
throw error;
|
|
@@ -468,7 +308,7 @@ var LoomManager = class {
|
|
|
468
308
|
binEntries
|
|
469
309
|
);
|
|
470
310
|
} catch (error) {
|
|
471
|
-
|
|
311
|
+
getLogger().warn(
|
|
472
312
|
`Failed to setup CLI isolation: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
473
313
|
error
|
|
474
314
|
);
|
|
@@ -477,38 +317,38 @@ var LoomManager = class {
|
|
|
477
317
|
const allMetadata = await this.metadataManager.listAllMetadata();
|
|
478
318
|
const usedHexColors = allMetadata.filter((metadata) => metadata.colorHex !== null).map((metadata) => metadata.colorHex);
|
|
479
319
|
const colorData = selectDistinctColor(branchName, usedHexColors);
|
|
480
|
-
|
|
320
|
+
getLogger().debug(`Selected color ${colorData.hex} for branch ${branchName} (${usedHexColors.length} colors in use globally)`);
|
|
481
321
|
try {
|
|
482
322
|
await this.applyColorSynchronization(worktreePath, branchName, colorData, settingsData, input.options);
|
|
483
323
|
} catch (error) {
|
|
484
|
-
|
|
324
|
+
getLogger().warn(
|
|
485
325
|
`Failed to apply color synchronization: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
486
326
|
error
|
|
487
327
|
);
|
|
488
328
|
}
|
|
489
329
|
if (input.type === "issue") {
|
|
490
330
|
try {
|
|
491
|
-
|
|
331
|
+
getLogger().info("Moving issue to In Progress...");
|
|
492
332
|
if (this.issueTracker.moveIssueToInProgress) {
|
|
493
333
|
await this.issueTracker.moveIssueToInProgress(input.identifier);
|
|
494
334
|
}
|
|
495
335
|
} catch (error) {
|
|
496
|
-
|
|
336
|
+
getLogger().warn(
|
|
497
337
|
`Failed to move issue to In Progress: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
498
338
|
error
|
|
499
339
|
);
|
|
500
340
|
}
|
|
501
341
|
}
|
|
502
|
-
const enableClaude = ((
|
|
503
|
-
const enableCode = ((
|
|
504
|
-
const enableDevServer = ((
|
|
505
|
-
const enableTerminal = ((
|
|
506
|
-
const oneShot = ((
|
|
507
|
-
const setArguments = (
|
|
508
|
-
const executablePath = (
|
|
342
|
+
const enableClaude = ((_e = input.options) == null ? void 0 : _e.enableClaude) !== false;
|
|
343
|
+
const enableCode = ((_f = input.options) == null ? void 0 : _f.enableCode) !== false;
|
|
344
|
+
const enableDevServer = ((_g = input.options) == null ? void 0 : _g.enableDevServer) !== false;
|
|
345
|
+
const enableTerminal = ((_h = input.options) == null ? void 0 : _h.enableTerminal) ?? false;
|
|
346
|
+
const oneShot = ((_i = input.options) == null ? void 0 : _i.oneShot) ?? "default";
|
|
347
|
+
const setArguments = (_j = input.options) == null ? void 0 : _j.setArguments;
|
|
348
|
+
const executablePath = (_k = input.options) == null ? void 0 : _k.executablePath;
|
|
509
349
|
if (enableClaude || enableCode || enableDevServer || enableTerminal) {
|
|
510
|
-
const { LoomLauncher } = await import("./LoomLauncher-
|
|
511
|
-
const { ClaudeContextManager } = await import("./ClaudeContextManager-
|
|
350
|
+
const { LoomLauncher } = await import("./LoomLauncher-SJBZFZXE.js");
|
|
351
|
+
const { ClaudeContextManager } = await import("./ClaudeContextManager-VEGJTS5E.js");
|
|
512
352
|
const claudeContext = new ClaudeContextManager(void 0, void 0, this.settings);
|
|
513
353
|
const launcher = new LoomLauncher(claudeContext, this.settings);
|
|
514
354
|
await launcher.launchLoom({
|
|
@@ -527,13 +367,14 @@ var LoomManager = class {
|
|
|
527
367
|
...setArguments && { setArguments },
|
|
528
368
|
...executablePath && { executablePath },
|
|
529
369
|
sourceEnvOnStart: settingsData.sourceEnvOnStart ?? false,
|
|
530
|
-
colorTerminal: ((
|
|
370
|
+
colorTerminal: ((_l = input.options) == null ? void 0 : _l.colorTerminal) ?? ((_m = settingsData.colors) == null ? void 0 : _m.terminal) ?? true,
|
|
531
371
|
colorHex: colorData.hex
|
|
532
372
|
});
|
|
533
373
|
}
|
|
534
374
|
const description = (issueData == null ? void 0 : issueData.title) ?? branchName;
|
|
535
375
|
const issue_numbers = input.type === "issue" ? [String(input.identifier)] : [];
|
|
536
376
|
const pr_numbers = input.type === "pr" ? [String(input.identifier)] : [];
|
|
377
|
+
const sessionId = generateDeterministicSessionId(worktreePath);
|
|
537
378
|
const metadataInput = {
|
|
538
379
|
description,
|
|
539
380
|
branchName,
|
|
@@ -542,7 +383,9 @@ var LoomManager = class {
|
|
|
542
383
|
issue_numbers,
|
|
543
384
|
pr_numbers,
|
|
544
385
|
issueTracker: this.issueTracker.providerName,
|
|
545
|
-
colorHex: colorData.hex
|
|
386
|
+
colorHex: colorData.hex,
|
|
387
|
+
sessionId,
|
|
388
|
+
...input.parentLoom && { parentLoom: input.parentLoom }
|
|
546
389
|
};
|
|
547
390
|
await this.metadataManager.writeMetadata(worktreePath, metadataInput);
|
|
548
391
|
const loom = {
|
|
@@ -568,7 +411,7 @@ var LoomManager = class {
|
|
|
568
411
|
}
|
|
569
412
|
}
|
|
570
413
|
};
|
|
571
|
-
|
|
414
|
+
getLogger().success(`Created loom: ${loom.id} at ${loom.path}`);
|
|
572
415
|
return loom;
|
|
573
416
|
}
|
|
574
417
|
/**
|
|
@@ -616,7 +459,7 @@ var LoomManager = class {
|
|
|
616
459
|
const pattern = `${sanitizedBranchName}-looms/`;
|
|
617
460
|
return worktrees.filter((wt) => wt.path.includes(pattern));
|
|
618
461
|
} catch (error) {
|
|
619
|
-
|
|
462
|
+
getLogger().debug(`Failed to find child looms: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
620
463
|
return [];
|
|
621
464
|
}
|
|
622
465
|
}
|
|
@@ -630,7 +473,7 @@ var LoomManager = class {
|
|
|
630
473
|
async checkAndWarnChildLooms(branchName) {
|
|
631
474
|
let targetBranch = branchName;
|
|
632
475
|
if (!targetBranch) {
|
|
633
|
-
const { getCurrentBranch } = await import("./git-
|
|
476
|
+
const { getCurrentBranch } = await import("./git-OXJACVAU.js");
|
|
634
477
|
targetBranch = await getCurrentBranch();
|
|
635
478
|
}
|
|
636
479
|
if (!targetBranch) {
|
|
@@ -638,19 +481,19 @@ var LoomManager = class {
|
|
|
638
481
|
}
|
|
639
482
|
const childLooms = await this.findChildLooms(targetBranch);
|
|
640
483
|
if (childLooms.length > 0) {
|
|
641
|
-
|
|
484
|
+
getLogger().warn(`Found ${childLooms.length} child loom(s) that should be finished first:`);
|
|
642
485
|
for (const child of childLooms) {
|
|
643
|
-
|
|
486
|
+
getLogger().warn(` - ${child.path}`);
|
|
644
487
|
}
|
|
645
|
-
|
|
646
|
-
|
|
488
|
+
getLogger().warn("");
|
|
489
|
+
getLogger().warn("To finish child looms:");
|
|
647
490
|
for (const child of childLooms) {
|
|
648
491
|
const prMatch = child.branch.match(/_pr_(\d+)/);
|
|
649
492
|
const issueId = extractIssueNumber(child.branch);
|
|
650
493
|
const childIdentifier = prMatch ? prMatch[1] : issueId ?? child.branch;
|
|
651
|
-
|
|
494
|
+
getLogger().warn(` il finish ${childIdentifier}`);
|
|
652
495
|
}
|
|
653
|
-
|
|
496
|
+
getLogger().warn("");
|
|
654
497
|
return true;
|
|
655
498
|
}
|
|
656
499
|
return false;
|
|
@@ -696,14 +539,14 @@ var LoomManager = class {
|
|
|
696
539
|
*/
|
|
697
540
|
async createWorktreeOnly(input, branchName) {
|
|
698
541
|
var _a;
|
|
699
|
-
|
|
542
|
+
getLogger().info("Ensuring repository has initial commit...");
|
|
700
543
|
await ensureRepositoryHasCommits(this.gitWorktree.workingDirectory);
|
|
701
544
|
const settingsData = await this.settings.loadSettings();
|
|
702
545
|
let worktreePrefix = settingsData.worktreePrefix;
|
|
703
546
|
if (input.parentLoom) {
|
|
704
547
|
const sanitizedBranchName = input.parentLoom.branchName.replace(/\//g, "-").replace(/[^a-zA-Z0-9-_]/g, "-");
|
|
705
548
|
worktreePrefix = `${sanitizedBranchName}-looms/`;
|
|
706
|
-
|
|
549
|
+
getLogger().info(`Creating child loom with prefix: ${worktreePrefix}`);
|
|
707
550
|
}
|
|
708
551
|
const pathOptions = input.type === "pr" ? { isPR: true, prNumber: input.identifier } : {};
|
|
709
552
|
if (worktreePrefix !== void 0) {
|
|
@@ -715,10 +558,10 @@ var LoomManager = class {
|
|
|
715
558
|
pathOptions
|
|
716
559
|
);
|
|
717
560
|
if (input.type === "pr") {
|
|
718
|
-
|
|
561
|
+
getLogger().info("Fetching all remote branches...");
|
|
719
562
|
try {
|
|
720
563
|
await executeGitCommand(["fetch", "origin"], { cwd: this.gitWorktree.workingDirectory });
|
|
721
|
-
|
|
564
|
+
getLogger().success("Successfully fetched from remote");
|
|
722
565
|
} catch (error) {
|
|
723
566
|
throw new Error(
|
|
724
567
|
`Failed to fetch from remote: ${error instanceof Error ? error.message : "Unknown error"}. Make sure you have access to the repository.`
|
|
@@ -740,13 +583,13 @@ var LoomManager = class {
|
|
|
740
583
|
...baseBranch && { baseBranch }
|
|
741
584
|
});
|
|
742
585
|
if (input.type === "pr" && !branchExistedLocally) {
|
|
743
|
-
|
|
586
|
+
getLogger().info("Resetting new PR branch to match remote exactly...");
|
|
744
587
|
try {
|
|
745
588
|
await executeGitCommand(["reset", "--hard", `origin/${branchName}`], { cwd: worktreePath });
|
|
746
589
|
await executeGitCommand(["branch", "--set-upstream-to", `origin/${branchName}`], { cwd: worktreePath });
|
|
747
|
-
|
|
590
|
+
getLogger().success("Successfully reset to match remote");
|
|
748
591
|
} catch (error) {
|
|
749
|
-
|
|
592
|
+
getLogger().warn(`Failed to reset to match remote: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
750
593
|
}
|
|
751
594
|
}
|
|
752
595
|
return worktreePath;
|
|
@@ -768,23 +611,23 @@ var LoomManager = class {
|
|
|
768
611
|
];
|
|
769
612
|
for (const pattern of envFilePatterns) {
|
|
770
613
|
try {
|
|
771
|
-
const mainEnvPath =
|
|
772
|
-
const worktreeEnvPath =
|
|
773
|
-
if (!await
|
|
614
|
+
const mainEnvPath = path2.join(mainWorkspacePath, pattern);
|
|
615
|
+
const worktreeEnvPath = path2.join(worktreePath, pattern);
|
|
616
|
+
if (!await fs2.pathExists(mainEnvPath)) {
|
|
774
617
|
continue;
|
|
775
618
|
}
|
|
776
619
|
if (await isFileTrackedByGit(pattern, mainWorkspacePath)) {
|
|
777
|
-
|
|
620
|
+
getLogger().debug(`Skipping ${pattern} (tracked by git, already in worktree)`);
|
|
778
621
|
continue;
|
|
779
622
|
}
|
|
780
|
-
if (await
|
|
781
|
-
|
|
623
|
+
if (await fs2.pathExists(worktreeEnvPath)) {
|
|
624
|
+
getLogger().warn(`${pattern} already exists in worktree, skipping copy`);
|
|
782
625
|
continue;
|
|
783
626
|
}
|
|
784
627
|
await this.environment.copyIfExists(mainEnvPath, worktreeEnvPath);
|
|
785
|
-
|
|
628
|
+
getLogger().debug(`Copied ${pattern} to worktree`);
|
|
786
629
|
} catch (error) {
|
|
787
|
-
|
|
630
|
+
getLogger().warn(`Warning: Failed to copy ${pattern}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
788
631
|
}
|
|
789
632
|
}
|
|
790
633
|
}
|
|
@@ -792,35 +635,41 @@ var LoomManager = class {
|
|
|
792
635
|
* Copy iloom configuration (settings.local.json) from main repo to worktree
|
|
793
636
|
* Always called regardless of project capabilities
|
|
794
637
|
* @param worktreePath Path to the worktree
|
|
795
|
-
* @param parentBranchName Optional parent branch name for child looms (sets mainBranch)
|
|
796
638
|
*/
|
|
797
|
-
async copyIloomSettings(worktreePath
|
|
798
|
-
const mainSettingsLocalPath =
|
|
639
|
+
async copyIloomSettings(worktreePath) {
|
|
640
|
+
const mainSettingsLocalPath = path2.join(process.cwd(), ".iloom", "settings.local.json");
|
|
799
641
|
try {
|
|
800
|
-
const worktreeIloomDir =
|
|
801
|
-
await
|
|
802
|
-
const worktreeSettingsLocalPath =
|
|
803
|
-
if (await
|
|
804
|
-
|
|
642
|
+
const worktreeIloomDir = path2.join(worktreePath, ".iloom");
|
|
643
|
+
await fs2.ensureDir(worktreeIloomDir);
|
|
644
|
+
const worktreeSettingsLocalPath = path2.join(worktreeIloomDir, "settings.local.json");
|
|
645
|
+
if (await fs2.pathExists(worktreeSettingsLocalPath)) {
|
|
646
|
+
getLogger().warn("settings.local.json already exists in worktree, skipping copy");
|
|
805
647
|
} else {
|
|
806
648
|
await this.environment.copyIfExists(mainSettingsLocalPath, worktreeSettingsLocalPath);
|
|
807
649
|
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
650
|
+
} catch (error) {
|
|
651
|
+
getLogger().warn(`Warning: Failed to copy settings.local.json: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Copy Claude settings (settings.local.json) from main repo to worktree
|
|
656
|
+
* Always called regardless of project capabilities
|
|
657
|
+
* Follows the same pattern as copyIloomSettings()
|
|
658
|
+
* @param worktreePath Path to the worktree
|
|
659
|
+
*/
|
|
660
|
+
async copyClaudeSettings(worktreePath) {
|
|
661
|
+
const mainClaudeSettingsPath = path2.join(process.cwd(), ".claude", "settings.local.json");
|
|
662
|
+
try {
|
|
663
|
+
const worktreeClaudeDir = path2.join(worktreePath, ".claude");
|
|
664
|
+
await fs2.ensureDir(worktreeClaudeDir);
|
|
665
|
+
const worktreeClaudeSettingsPath = path2.join(worktreeClaudeDir, "settings.local.json");
|
|
666
|
+
if (await fs2.pathExists(worktreeClaudeSettingsPath)) {
|
|
667
|
+
getLogger().debug(".claude/settings.local.json already exists in worktree, skipping copy");
|
|
668
|
+
} else {
|
|
669
|
+
await this.environment.copyIfExists(mainClaudeSettingsPath, worktreeClaudeSettingsPath);
|
|
821
670
|
}
|
|
822
671
|
} catch (error) {
|
|
823
|
-
|
|
672
|
+
getLogger().warn(`Warning: Failed to copy .claude/settings.local.json: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
824
673
|
}
|
|
825
674
|
}
|
|
826
675
|
/**
|
|
@@ -828,7 +677,7 @@ var LoomManager = class {
|
|
|
828
677
|
* Only called when project has web capabilities
|
|
829
678
|
*/
|
|
830
679
|
async setupPortForWeb(worktreePath, input, basePort) {
|
|
831
|
-
const envFilePath =
|
|
680
|
+
const envFilePath = path2.join(worktreePath, ".env.local");
|
|
832
681
|
const options = { basePort };
|
|
833
682
|
if (input.type === "issue") {
|
|
834
683
|
options.issueNumber = input.identifier;
|
|
@@ -848,11 +697,11 @@ var LoomManager = class {
|
|
|
848
697
|
loadMainEnvFile() {
|
|
849
698
|
const result = loadEnvIntoProcess({ path: process.cwd() });
|
|
850
699
|
if (result.error) {
|
|
851
|
-
|
|
700
|
+
getLogger().warn(`Warning: Could not load .env files: ${result.error.message}`);
|
|
852
701
|
} else {
|
|
853
|
-
|
|
702
|
+
getLogger().info("Loaded environment variables using dotenv-flow");
|
|
854
703
|
if (result.parsed && Object.keys(result.parsed).length > 0) {
|
|
855
|
-
|
|
704
|
+
getLogger().debug(`Loaded ${Object.keys(result.parsed).length} environment variables`);
|
|
856
705
|
}
|
|
857
706
|
}
|
|
858
707
|
}
|
|
@@ -871,8 +720,12 @@ var LoomManager = class {
|
|
|
871
720
|
var _a, _b;
|
|
872
721
|
const settingsData = await this.settings.loadSettings();
|
|
873
722
|
const basePort = ((_b = (_a = settingsData.capabilities) == null ? void 0 : _a.web) == null ? void 0 : _b.basePort) ?? 3e3;
|
|
874
|
-
if (input.type === "issue"
|
|
875
|
-
|
|
723
|
+
if (input.type === "issue") {
|
|
724
|
+
if (typeof input.identifier === "number") {
|
|
725
|
+
return this.environment.calculatePort({ basePort, issueNumber: input.identifier });
|
|
726
|
+
} else if (typeof input.identifier === "string") {
|
|
727
|
+
return this.environment.calculatePort({ basePort, branchName: input.identifier });
|
|
728
|
+
}
|
|
876
729
|
}
|
|
877
730
|
if (input.type === "pr" && typeof input.identifier === "number") {
|
|
878
731
|
return this.environment.calculatePort({ basePort, prNumber: input.identifier });
|
|
@@ -880,7 +733,7 @@ var LoomManager = class {
|
|
|
880
733
|
if (input.type === "branch" && typeof input.identifier === "string") {
|
|
881
734
|
return this.environment.calculatePort({ basePort, branchName: input.identifier });
|
|
882
735
|
}
|
|
883
|
-
throw new Error(`Unknown input type: ${input.type}`);
|
|
736
|
+
throw new Error(`Unknown input type: ${input.type} with identifier type: ${typeof input.identifier}`);
|
|
884
737
|
}
|
|
885
738
|
/**
|
|
886
739
|
* Apply color synchronization to both VSCode and terminal
|
|
@@ -898,34 +751,56 @@ var LoomManager = class {
|
|
|
898
751
|
const colorVscode = (options == null ? void 0 : options.colorVscode) ?? ((_a = settings.colors) == null ? void 0 : _a.vscode) ?? false;
|
|
899
752
|
const colorTerminal = (options == null ? void 0 : options.colorTerminal) ?? ((_b = settings.colors) == null ? void 0 : _b.terminal) ?? true;
|
|
900
753
|
if (!colorVscode && !colorTerminal) {
|
|
901
|
-
|
|
754
|
+
getLogger().debug("Color synchronization disabled for both VSCode and terminal");
|
|
902
755
|
return;
|
|
903
756
|
}
|
|
904
757
|
if (colorVscode) {
|
|
905
758
|
const vscode = new VSCodeIntegration();
|
|
906
759
|
await vscode.setTitleBarColor(worktreePath, colorData.hex);
|
|
907
|
-
|
|
760
|
+
getLogger().info(`Applied VSCode title bar color: ${colorData.hex} for branch: ${branchName}`);
|
|
908
761
|
} else {
|
|
909
|
-
|
|
762
|
+
getLogger().debug("VSCode color sync disabled (default: false for safety)");
|
|
910
763
|
}
|
|
911
764
|
}
|
|
912
765
|
/**
|
|
913
766
|
* Map worktrees to loom objects
|
|
914
|
-
*
|
|
915
|
-
* Now reads metadata from MetadataManager (spec section 3.2)
|
|
767
|
+
* Reads loom metadata from MetadataManager with branch name parsing as fallback
|
|
916
768
|
*/
|
|
917
769
|
async mapWorktreesToLooms(worktrees) {
|
|
918
770
|
return await Promise.all(worktrees.map(async (wt) => {
|
|
771
|
+
var _a, _b;
|
|
772
|
+
const loomMetadata = await this.metadataManager.readMetadata(wt.path);
|
|
919
773
|
let type = "branch";
|
|
920
774
|
let identifier = wt.branch;
|
|
921
|
-
if (
|
|
922
|
-
type =
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
775
|
+
if (loomMetadata == null ? void 0 : loomMetadata.issueType) {
|
|
776
|
+
type = loomMetadata.issueType;
|
|
777
|
+
if (type === "issue" && ((_a = loomMetadata.issue_numbers) == null ? void 0 : _a[0])) {
|
|
778
|
+
const issueId = loomMetadata.issue_numbers[0];
|
|
779
|
+
const numericId = parseInt(issueId, 10);
|
|
780
|
+
identifier = isNaN(numericId) ? issueId : numericId;
|
|
781
|
+
} else if (type === "pr" && ((_b = loomMetadata.pr_numbers) == null ? void 0 : _b[0])) {
|
|
782
|
+
const prId = loomMetadata.pr_numbers[0];
|
|
783
|
+
identifier = parseInt(prId, 10);
|
|
784
|
+
} else if (type === "branch") {
|
|
785
|
+
identifier = wt.branch;
|
|
786
|
+
}
|
|
787
|
+
} else {
|
|
788
|
+
const prNumber = extractPRNumber(wt.branch);
|
|
789
|
+
if (prNumber !== null) {
|
|
790
|
+
type = "pr";
|
|
791
|
+
identifier = prNumber;
|
|
792
|
+
} else {
|
|
793
|
+
const issueNumber = extractIssueNumber(wt.branch);
|
|
794
|
+
if (issueNumber !== null) {
|
|
795
|
+
type = "issue";
|
|
796
|
+
const numericId = parseInt(issueNumber, 10);
|
|
797
|
+
identifier = isNaN(numericId) ? issueNumber : numericId;
|
|
798
|
+
} else {
|
|
799
|
+
type = "branch";
|
|
800
|
+
identifier = wt.branch;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
927
803
|
}
|
|
928
|
-
const loomMetadata = await this.metadataManager.readMetadata(wt.path);
|
|
929
804
|
return {
|
|
930
805
|
id: `${type}-${identifier}`,
|
|
931
806
|
path: wt.path,
|
|
@@ -969,41 +844,42 @@ var LoomManager = class {
|
|
|
969
844
|
const { capabilities, binEntries } = await this.capabilityDetector.detectCapabilities(worktreePath);
|
|
970
845
|
await this.copyEnvironmentFiles(worktreePath);
|
|
971
846
|
await this.copyIloomSettings(worktreePath);
|
|
847
|
+
await this.copyClaudeSettings(worktreePath);
|
|
972
848
|
const settingsData = await this.settings.loadSettings();
|
|
973
849
|
const basePort = ((_b = (_a = settingsData.capabilities) == null ? void 0 : _a.web) == null ? void 0 : _b.basePort) ?? 3e3;
|
|
974
850
|
let port = basePort;
|
|
975
851
|
if (capabilities.includes("web")) {
|
|
976
852
|
port = await this.setupPortForWeb(worktreePath, input, basePort);
|
|
977
853
|
}
|
|
978
|
-
|
|
854
|
+
getLogger().info("Database branch assumed to be already configured for existing worktree");
|
|
979
855
|
const databaseBranch = void 0;
|
|
980
856
|
const existingMetadata = await this.metadataManager.readMetadata(worktreePath);
|
|
981
857
|
let colorHex;
|
|
982
858
|
if (existingMetadata == null ? void 0 : existingMetadata.colorHex) {
|
|
983
859
|
colorHex = existingMetadata.colorHex;
|
|
984
|
-
|
|
860
|
+
getLogger().debug(`Reusing stored color ${colorHex} for branch ${branchName}`);
|
|
985
861
|
} else {
|
|
986
862
|
const colorData = generateColorFromBranchName(branchName);
|
|
987
863
|
colorHex = colorData.hex;
|
|
988
|
-
|
|
864
|
+
getLogger().debug(`No stored color, using hash-based color ${colorHex} for branch ${branchName}`);
|
|
989
865
|
}
|
|
990
866
|
try {
|
|
991
867
|
const colorData = { hex: colorHex, rgb: hexToRgb(colorHex), index: 0 };
|
|
992
868
|
await this.applyColorSynchronization(worktreePath, branchName, colorData, settingsData, input.options);
|
|
993
869
|
} catch (error) {
|
|
994
|
-
|
|
870
|
+
getLogger().warn(
|
|
995
871
|
`Failed to apply color synchronization: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
996
872
|
error
|
|
997
873
|
);
|
|
998
874
|
}
|
|
999
875
|
if (input.type === "issue") {
|
|
1000
876
|
try {
|
|
1001
|
-
|
|
877
|
+
getLogger().info("Moving issue to In Progress...");
|
|
1002
878
|
if (this.issueTracker.moveIssueToInProgress) {
|
|
1003
879
|
await this.issueTracker.moveIssueToInProgress(input.identifier);
|
|
1004
880
|
}
|
|
1005
881
|
} catch (error) {
|
|
1006
|
-
|
|
882
|
+
getLogger().warn(
|
|
1007
883
|
`Failed to move issue to In Progress: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1008
884
|
error
|
|
1009
885
|
);
|
|
@@ -1017,9 +893,9 @@ var LoomManager = class {
|
|
|
1017
893
|
const setArguments = (_h = input.options) == null ? void 0 : _h.setArguments;
|
|
1018
894
|
const executablePath = (_i = input.options) == null ? void 0 : _i.executablePath;
|
|
1019
895
|
if (enableClaude || enableCode || enableDevServer || enableTerminal) {
|
|
1020
|
-
|
|
1021
|
-
const { LoomLauncher } = await import("./LoomLauncher-
|
|
1022
|
-
const { ClaudeContextManager } = await import("./ClaudeContextManager-
|
|
896
|
+
getLogger().info("Launching workspace components...");
|
|
897
|
+
const { LoomLauncher } = await import("./LoomLauncher-SJBZFZXE.js");
|
|
898
|
+
const { ClaudeContextManager } = await import("./ClaudeContextManager-VEGJTS5E.js");
|
|
1023
899
|
const claudeContext = new ClaudeContextManager(void 0, void 0, this.settings);
|
|
1024
900
|
const launcher = new LoomLauncher(claudeContext, this.settings);
|
|
1025
901
|
await launcher.launchLoom({
|
|
@@ -1046,6 +922,7 @@ var LoomManager = class {
|
|
|
1046
922
|
if (!existingMetadata) {
|
|
1047
923
|
const issue_numbers = input.type === "issue" ? [String(input.identifier)] : [];
|
|
1048
924
|
const pr_numbers = input.type === "pr" ? [String(input.identifier)] : [];
|
|
925
|
+
const sessionId = generateDeterministicSessionId(worktreePath);
|
|
1049
926
|
const metadataInput = {
|
|
1050
927
|
description,
|
|
1051
928
|
branchName,
|
|
@@ -1054,7 +931,9 @@ var LoomManager = class {
|
|
|
1054
931
|
issue_numbers,
|
|
1055
932
|
pr_numbers,
|
|
1056
933
|
issueTracker: this.issueTracker.providerName,
|
|
1057
|
-
colorHex
|
|
934
|
+
colorHex,
|
|
935
|
+
sessionId,
|
|
936
|
+
...input.parentLoom && { parentLoom: input.parentLoom }
|
|
1058
937
|
};
|
|
1059
938
|
await this.metadataManager.writeMetadata(worktreePath, metadataInput);
|
|
1060
939
|
}
|
|
@@ -1081,14 +960,13 @@ var LoomManager = class {
|
|
|
1081
960
|
}
|
|
1082
961
|
}
|
|
1083
962
|
};
|
|
1084
|
-
|
|
963
|
+
getLogger().success(`Reused existing loom: ${loom.id} at ${loom.path}`);
|
|
1085
964
|
return loom;
|
|
1086
965
|
}
|
|
1087
966
|
};
|
|
1088
967
|
|
|
1089
968
|
// src/lib/EnvironmentManager.ts
|
|
1090
|
-
import
|
|
1091
|
-
var logger2 = createLogger({ prefix: "\u{1F4DD}" });
|
|
969
|
+
import fs3 from "fs-extra";
|
|
1092
970
|
var EnvironmentManager = class {
|
|
1093
971
|
constructor() {
|
|
1094
972
|
this.backupSuffix = ".backup";
|
|
@@ -1103,15 +981,15 @@ var EnvironmentManager = class {
|
|
|
1103
981
|
if (!validation.valid) {
|
|
1104
982
|
throw new Error(validation.error ?? "Invalid variable name");
|
|
1105
983
|
}
|
|
1106
|
-
const fileExists = await
|
|
984
|
+
const fileExists = await fs3.pathExists(filePath);
|
|
1107
985
|
if (!fileExists) {
|
|
1108
|
-
|
|
986
|
+
getLogger().info(`Creating ${filePath} with ${key}...`);
|
|
1109
987
|
const content = formatEnvLine(key, value);
|
|
1110
|
-
await
|
|
1111
|
-
|
|
988
|
+
await fs3.writeFile(filePath, content, "utf8");
|
|
989
|
+
getLogger().success(`${filePath} created with ${key}`);
|
|
1112
990
|
return;
|
|
1113
991
|
}
|
|
1114
|
-
const existingContent = await
|
|
992
|
+
const existingContent = await fs3.readFile(filePath, "utf8");
|
|
1115
993
|
const envMap = parseEnvFile(existingContent);
|
|
1116
994
|
let backupPath;
|
|
1117
995
|
if (backup) {
|
|
@@ -1140,15 +1018,15 @@ var EnvironmentManager = class {
|
|
|
1140
1018
|
newLines.push(line);
|
|
1141
1019
|
}
|
|
1142
1020
|
if (!variableUpdated) {
|
|
1143
|
-
|
|
1021
|
+
getLogger().info(`Adding ${key} to ${filePath}...`);
|
|
1144
1022
|
newLines.push(formatEnvLine(key, value));
|
|
1145
|
-
|
|
1023
|
+
getLogger().success(`${key} added successfully`);
|
|
1146
1024
|
} else {
|
|
1147
|
-
|
|
1148
|
-
|
|
1025
|
+
getLogger().info(`Updating ${key} in ${filePath}...`);
|
|
1026
|
+
getLogger().success(`${key} updated successfully`);
|
|
1149
1027
|
}
|
|
1150
1028
|
const newContent = newLines.join("\n");
|
|
1151
|
-
await
|
|
1029
|
+
await fs3.writeFile(filePath, newContent, "utf8");
|
|
1152
1030
|
return backupPath;
|
|
1153
1031
|
}
|
|
1154
1032
|
/**
|
|
@@ -1156,10 +1034,10 @@ var EnvironmentManager = class {
|
|
|
1156
1034
|
*/
|
|
1157
1035
|
async readEnvFile(filePath) {
|
|
1158
1036
|
try {
|
|
1159
|
-
const content = await
|
|
1037
|
+
const content = await fs3.readFile(filePath, "utf8");
|
|
1160
1038
|
return parseEnvFile(content);
|
|
1161
1039
|
} catch (error) {
|
|
1162
|
-
|
|
1040
|
+
getLogger().debug(
|
|
1163
1041
|
`Could not read env file ${filePath}: ${error instanceof Error ? error.message : String(error)}`
|
|
1164
1042
|
);
|
|
1165
1043
|
return /* @__PURE__ */ new Map();
|
|
@@ -1179,13 +1057,13 @@ var EnvironmentManager = class {
|
|
|
1179
1057
|
* @private
|
|
1180
1058
|
*/
|
|
1181
1059
|
async copyIfExists(source, destination) {
|
|
1182
|
-
const sourceExists = await
|
|
1060
|
+
const sourceExists = await fs3.pathExists(source);
|
|
1183
1061
|
if (!sourceExists) {
|
|
1184
|
-
|
|
1062
|
+
getLogger().debug(`Source file ${source} does not exist, skipping copy`);
|
|
1185
1063
|
return;
|
|
1186
1064
|
}
|
|
1187
|
-
await
|
|
1188
|
-
|
|
1065
|
+
await fs3.copy(source, destination, { overwrite: false });
|
|
1066
|
+
getLogger().success(`Copied ${source} to ${destination}`);
|
|
1189
1067
|
}
|
|
1190
1068
|
/**
|
|
1191
1069
|
* Calculate unique port for workspace
|
|
@@ -1245,7 +1123,7 @@ var EnvironmentManager = class {
|
|
|
1245
1123
|
*/
|
|
1246
1124
|
async validateEnvFile(filePath) {
|
|
1247
1125
|
try {
|
|
1248
|
-
const content = await
|
|
1126
|
+
const content = await fs3.readFile(filePath, "utf8");
|
|
1249
1127
|
const envMap = parseEnvFile(content);
|
|
1250
1128
|
const errors = [];
|
|
1251
1129
|
for (const [key, value] of envMap.entries()) {
|
|
@@ -1273,19 +1151,19 @@ var EnvironmentManager = class {
|
|
|
1273
1151
|
async createBackup(filePath) {
|
|
1274
1152
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1275
1153
|
const backupPath = `${filePath}${this.backupSuffix}-${timestamp}`;
|
|
1276
|
-
await
|
|
1277
|
-
|
|
1154
|
+
await fs3.copy(filePath, backupPath);
|
|
1155
|
+
getLogger().debug(`Created backup at ${backupPath}`);
|
|
1278
1156
|
return backupPath;
|
|
1279
1157
|
}
|
|
1280
1158
|
};
|
|
1281
1159
|
|
|
1282
1160
|
// src/lib/CLIIsolationManager.ts
|
|
1283
|
-
import
|
|
1284
|
-
import
|
|
1285
|
-
import
|
|
1161
|
+
import fs4 from "fs-extra";
|
|
1162
|
+
import path3 from "path";
|
|
1163
|
+
import os from "os";
|
|
1286
1164
|
var CLIIsolationManager = class {
|
|
1287
1165
|
constructor() {
|
|
1288
|
-
this.iloomBinDir =
|
|
1166
|
+
this.iloomBinDir = path3.join(os.homedir(), ".iloom", "bin");
|
|
1289
1167
|
}
|
|
1290
1168
|
/**
|
|
1291
1169
|
* Setup CLI isolation for a worktree
|
|
@@ -1300,7 +1178,7 @@ var CLIIsolationManager = class {
|
|
|
1300
1178
|
async setupCLIIsolation(worktreePath, identifier, binEntries) {
|
|
1301
1179
|
await this.buildProject(worktreePath);
|
|
1302
1180
|
await this.verifyBinTargets(worktreePath, binEntries);
|
|
1303
|
-
await
|
|
1181
|
+
await fs4.ensureDir(this.iloomBinDir);
|
|
1304
1182
|
const symlinkNames = await this.createVersionedSymlinks(
|
|
1305
1183
|
worktreePath,
|
|
1306
1184
|
identifier,
|
|
@@ -1316,12 +1194,12 @@ var CLIIsolationManager = class {
|
|
|
1316
1194
|
async buildProject(worktreePath) {
|
|
1317
1195
|
const pkgJson = await readPackageJson(worktreePath);
|
|
1318
1196
|
if (!hasScript(pkgJson, "build")) {
|
|
1319
|
-
|
|
1197
|
+
getLogger().warn("No build script found in package.json - skipping build");
|
|
1320
1198
|
return;
|
|
1321
1199
|
}
|
|
1322
|
-
|
|
1200
|
+
getLogger().info("Building CLI tool...");
|
|
1323
1201
|
await runScript("build", worktreePath, [], { quiet: true });
|
|
1324
|
-
|
|
1202
|
+
getLogger().success("Build completed");
|
|
1325
1203
|
}
|
|
1326
1204
|
/**
|
|
1327
1205
|
* Verify bin targets exist and are executable
|
|
@@ -1330,13 +1208,13 @@ var CLIIsolationManager = class {
|
|
|
1330
1208
|
*/
|
|
1331
1209
|
async verifyBinTargets(worktreePath, binEntries) {
|
|
1332
1210
|
for (const binPath of Object.values(binEntries)) {
|
|
1333
|
-
const targetPath =
|
|
1334
|
-
const exists = await
|
|
1211
|
+
const targetPath = path3.resolve(worktreePath, binPath);
|
|
1212
|
+
const exists = await fs4.pathExists(targetPath);
|
|
1335
1213
|
if (!exists) {
|
|
1336
1214
|
throw new Error(`Bin target does not exist: ${targetPath}`);
|
|
1337
1215
|
}
|
|
1338
1216
|
try {
|
|
1339
|
-
await
|
|
1217
|
+
await fs4.access(targetPath, fs4.constants.X_OK);
|
|
1340
1218
|
} catch {
|
|
1341
1219
|
}
|
|
1342
1220
|
}
|
|
@@ -1352,10 +1230,10 @@ var CLIIsolationManager = class {
|
|
|
1352
1230
|
const symlinkNames = [];
|
|
1353
1231
|
for (const [binName, binPath] of Object.entries(binEntries)) {
|
|
1354
1232
|
const versionedName = `${binName}-${identifier}`;
|
|
1355
|
-
const targetPath =
|
|
1356
|
-
const symlinkPath =
|
|
1357
|
-
await
|
|
1358
|
-
|
|
1233
|
+
const targetPath = path3.resolve(worktreePath, binPath);
|
|
1234
|
+
const symlinkPath = path3.join(this.iloomBinDir, versionedName);
|
|
1235
|
+
await fs4.symlink(targetPath, symlinkPath);
|
|
1236
|
+
getLogger().success(`CLI available: ${versionedName}`);
|
|
1359
1237
|
symlinkNames.push(versionedName);
|
|
1360
1238
|
}
|
|
1361
1239
|
return symlinkNames;
|
|
@@ -1370,10 +1248,10 @@ var CLIIsolationManager = class {
|
|
|
1370
1248
|
}
|
|
1371
1249
|
const shell = this.detectShell();
|
|
1372
1250
|
const rcFile = this.getShellRcFile(shell);
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1251
|
+
getLogger().warn("\n\u26A0\uFE0F One-time PATH setup required:");
|
|
1252
|
+
getLogger().warn(` Add to ${rcFile}:`);
|
|
1253
|
+
getLogger().warn(` export PATH="$HOME/.iloom/bin:$PATH"`);
|
|
1254
|
+
getLogger().warn(` Then run: source ${rcFile}
|
|
1377
1255
|
`);
|
|
1378
1256
|
}
|
|
1379
1257
|
/**
|
|
@@ -1407,12 +1285,12 @@ var CLIIsolationManager = class {
|
|
|
1407
1285
|
async cleanupVersionedExecutables(identifier) {
|
|
1408
1286
|
const removed = [];
|
|
1409
1287
|
try {
|
|
1410
|
-
const files = await
|
|
1288
|
+
const files = await fs4.readdir(this.iloomBinDir);
|
|
1411
1289
|
for (const file of files) {
|
|
1412
1290
|
if (this.matchesIdentifier(file, identifier)) {
|
|
1413
|
-
const symlinkPath =
|
|
1291
|
+
const symlinkPath = path3.join(this.iloomBinDir, file);
|
|
1414
1292
|
try {
|
|
1415
|
-
await
|
|
1293
|
+
await fs4.unlink(symlinkPath);
|
|
1416
1294
|
removed.push(file);
|
|
1417
1295
|
} catch (error) {
|
|
1418
1296
|
const isEnoent = error && typeof error === "object" && "code" in error && error.code === "ENOENT";
|
|
@@ -1420,7 +1298,7 @@ var CLIIsolationManager = class {
|
|
|
1420
1298
|
removed.push(file);
|
|
1421
1299
|
continue;
|
|
1422
1300
|
}
|
|
1423
|
-
|
|
1301
|
+
getLogger().warn(
|
|
1424
1302
|
`Failed to remove symlink ${file}: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1425
1303
|
);
|
|
1426
1304
|
}
|
|
@@ -1429,13 +1307,13 @@ var CLIIsolationManager = class {
|
|
|
1429
1307
|
} catch (error) {
|
|
1430
1308
|
const isEnoent = error && typeof error === "object" && "code" in error && error.code === "ENOENT";
|
|
1431
1309
|
if (isEnoent) {
|
|
1432
|
-
|
|
1310
|
+
getLogger().warn("No CLI executables directory found - nothing to cleanup");
|
|
1433
1311
|
return [];
|
|
1434
1312
|
}
|
|
1435
1313
|
throw error;
|
|
1436
1314
|
}
|
|
1437
1315
|
if (removed.length > 0) {
|
|
1438
|
-
|
|
1316
|
+
getLogger().success(`Removed CLI executables: ${removed.join(", ")}`);
|
|
1439
1317
|
}
|
|
1440
1318
|
return removed;
|
|
1441
1319
|
}
|
|
@@ -1448,15 +1326,15 @@ var CLIIsolationManager = class {
|
|
|
1448
1326
|
async findOrphanedSymlinks() {
|
|
1449
1327
|
const orphaned = [];
|
|
1450
1328
|
try {
|
|
1451
|
-
const files = await
|
|
1329
|
+
const files = await fs4.readdir(this.iloomBinDir);
|
|
1452
1330
|
for (const file of files) {
|
|
1453
|
-
const symlinkPath =
|
|
1331
|
+
const symlinkPath = path3.join(this.iloomBinDir, file);
|
|
1454
1332
|
try {
|
|
1455
|
-
const stats = await
|
|
1333
|
+
const stats = await fs4.lstat(symlinkPath);
|
|
1456
1334
|
if (stats.isSymbolicLink()) {
|
|
1457
|
-
const target = await
|
|
1335
|
+
const target = await fs4.readlink(symlinkPath);
|
|
1458
1336
|
try {
|
|
1459
|
-
await
|
|
1337
|
+
await fs4.access(target);
|
|
1460
1338
|
} catch {
|
|
1461
1339
|
orphaned.push({
|
|
1462
1340
|
name: file,
|
|
@@ -1466,7 +1344,7 @@ var CLIIsolationManager = class {
|
|
|
1466
1344
|
}
|
|
1467
1345
|
}
|
|
1468
1346
|
} catch (error) {
|
|
1469
|
-
|
|
1347
|
+
getLogger().warn(
|
|
1470
1348
|
`Failed to check symlink ${file}: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1471
1349
|
);
|
|
1472
1350
|
}
|
|
@@ -1491,11 +1369,11 @@ var CLIIsolationManager = class {
|
|
|
1491
1369
|
let removedCount = 0;
|
|
1492
1370
|
for (const symlink of orphaned) {
|
|
1493
1371
|
try {
|
|
1494
|
-
await
|
|
1372
|
+
await fs4.unlink(symlink.path);
|
|
1495
1373
|
removedCount++;
|
|
1496
|
-
|
|
1374
|
+
getLogger().success(`Removed orphaned symlink: ${symlink.name}`);
|
|
1497
1375
|
} catch (error) {
|
|
1498
|
-
|
|
1376
|
+
getLogger().warn(
|
|
1499
1377
|
`Failed to remove orphaned symlink ${symlink.name}: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1500
1378
|
);
|
|
1501
1379
|
}
|
|
@@ -1517,17 +1395,16 @@ var CLIIsolationManager = class {
|
|
|
1517
1395
|
};
|
|
1518
1396
|
|
|
1519
1397
|
// src/lib/DatabaseManager.ts
|
|
1520
|
-
import
|
|
1521
|
-
var logger3 = createLogger({ prefix: "\u{1F5C2}\uFE0F" });
|
|
1398
|
+
import fs5 from "fs-extra";
|
|
1522
1399
|
var DatabaseManager = class {
|
|
1523
1400
|
constructor(provider, environment, databaseUrlEnvVarName = "DATABASE_URL") {
|
|
1524
1401
|
this.provider = provider;
|
|
1525
1402
|
this.environment = environment;
|
|
1526
1403
|
this.databaseUrlEnvVarName = databaseUrlEnvVarName;
|
|
1527
1404
|
if (databaseUrlEnvVarName !== "DATABASE_URL") {
|
|
1528
|
-
|
|
1405
|
+
getLogger().debug(`DatabaseManager configured with custom variable: ${databaseUrlEnvVarName}`);
|
|
1529
1406
|
} else {
|
|
1530
|
-
|
|
1407
|
+
getLogger().debug("DatabaseManager using default variable: DATABASE_URL");
|
|
1531
1408
|
}
|
|
1532
1409
|
}
|
|
1533
1410
|
/**
|
|
@@ -1544,12 +1421,12 @@ var DatabaseManager = class {
|
|
|
1544
1421
|
*/
|
|
1545
1422
|
async shouldUseDatabaseBranching(workspacePath) {
|
|
1546
1423
|
if (!this.provider.isConfigured()) {
|
|
1547
|
-
|
|
1424
|
+
getLogger().debug("Skipping database branching: Database provider not configured");
|
|
1548
1425
|
return false;
|
|
1549
1426
|
}
|
|
1550
1427
|
const hasDatabaseUrl = await this.hasDatabaseUrlInEnv(workspacePath);
|
|
1551
1428
|
if (!hasDatabaseUrl) {
|
|
1552
|
-
|
|
1429
|
+
getLogger().debug(
|
|
1553
1430
|
"Skipping database branching: configured database URL variable not found in any env file"
|
|
1554
1431
|
);
|
|
1555
1432
|
return false;
|
|
@@ -1570,28 +1447,28 @@ var DatabaseManager = class {
|
|
|
1570
1447
|
return null;
|
|
1571
1448
|
}
|
|
1572
1449
|
if (!await this.provider.isCliAvailable()) {
|
|
1573
|
-
|
|
1574
|
-
|
|
1450
|
+
getLogger().warn("Skipping database branch creation: Neon CLI not available");
|
|
1451
|
+
getLogger().warn("Install with: npm install -g neonctl");
|
|
1575
1452
|
return null;
|
|
1576
1453
|
}
|
|
1577
1454
|
try {
|
|
1578
1455
|
const isAuth = await this.provider.isAuthenticated(cwd);
|
|
1579
1456
|
if (!isAuth) {
|
|
1580
|
-
|
|
1581
|
-
|
|
1457
|
+
getLogger().warn("Skipping database branch creation: Not authenticated with Neon CLI");
|
|
1458
|
+
getLogger().warn("Run: neon auth");
|
|
1582
1459
|
return null;
|
|
1583
1460
|
}
|
|
1584
1461
|
} catch (error) {
|
|
1585
1462
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1586
|
-
|
|
1463
|
+
getLogger().error(`Database authentication check failed: ${errorMessage}`);
|
|
1587
1464
|
throw error;
|
|
1588
1465
|
}
|
|
1589
1466
|
try {
|
|
1590
1467
|
const connectionString = await this.provider.createBranch(branchName, fromBranch, cwd);
|
|
1591
|
-
|
|
1468
|
+
getLogger().success(`Database branch ready: ${this.provider.sanitizeBranchName(branchName)}`);
|
|
1592
1469
|
return connectionString;
|
|
1593
1470
|
} catch (error) {
|
|
1594
|
-
|
|
1471
|
+
getLogger().error(
|
|
1595
1472
|
`Failed to create database branch: ${error instanceof Error ? error.message : String(error)}`
|
|
1596
1473
|
);
|
|
1597
1474
|
throw error;
|
|
@@ -1617,7 +1494,7 @@ var DatabaseManager = class {
|
|
|
1617
1494
|
};
|
|
1618
1495
|
}
|
|
1619
1496
|
if (!this.provider.isConfigured()) {
|
|
1620
|
-
|
|
1497
|
+
getLogger().debug("Skipping database branch deletion: Database provider not configured");
|
|
1621
1498
|
return {
|
|
1622
1499
|
success: true,
|
|
1623
1500
|
deleted: false,
|
|
@@ -1626,7 +1503,7 @@ var DatabaseManager = class {
|
|
|
1626
1503
|
};
|
|
1627
1504
|
}
|
|
1628
1505
|
if (!await this.provider.isCliAvailable()) {
|
|
1629
|
-
|
|
1506
|
+
getLogger().info("Skipping database branch deletion: CLI tool not available");
|
|
1630
1507
|
return {
|
|
1631
1508
|
success: false,
|
|
1632
1509
|
deleted: false,
|
|
@@ -1638,7 +1515,7 @@ var DatabaseManager = class {
|
|
|
1638
1515
|
try {
|
|
1639
1516
|
const isAuth = await this.provider.isAuthenticated(cwd);
|
|
1640
1517
|
if (!isAuth) {
|
|
1641
|
-
|
|
1518
|
+
getLogger().warn("Skipping database branch deletion: Not authenticated with DB Provider");
|
|
1642
1519
|
return {
|
|
1643
1520
|
success: false,
|
|
1644
1521
|
deleted: false,
|
|
@@ -1649,7 +1526,7 @@ var DatabaseManager = class {
|
|
|
1649
1526
|
}
|
|
1650
1527
|
} catch (error) {
|
|
1651
1528
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1652
|
-
|
|
1529
|
+
getLogger().error(`Database authentication check failed: ${errorMessage}`);
|
|
1653
1530
|
return {
|
|
1654
1531
|
success: false,
|
|
1655
1532
|
deleted: false,
|
|
@@ -1662,7 +1539,7 @@ var DatabaseManager = class {
|
|
|
1662
1539
|
const result = await this.provider.deleteBranch(branchName, isPreview, cwd);
|
|
1663
1540
|
return result;
|
|
1664
1541
|
} catch (error) {
|
|
1665
|
-
|
|
1542
|
+
getLogger().warn(
|
|
1666
1543
|
`Unexpected error in database deletion: ${error instanceof Error ? error.message : String(error)}`
|
|
1667
1544
|
);
|
|
1668
1545
|
return {
|
|
@@ -1683,13 +1560,13 @@ var DatabaseManager = class {
|
|
|
1683
1560
|
*/
|
|
1684
1561
|
async getBranchNameFromConnectionString(connectionString, cwd) {
|
|
1685
1562
|
if (!this.provider.isConfigured()) {
|
|
1686
|
-
|
|
1563
|
+
getLogger().debug("Provider not configured, skipping reverse lookup");
|
|
1687
1564
|
return null;
|
|
1688
1565
|
}
|
|
1689
1566
|
if ("getBranchNameFromConnectionString" in this.provider && typeof this.provider.getBranchNameFromConnectionString === "function") {
|
|
1690
1567
|
return this.provider.getBranchNameFromConnectionString(connectionString, cwd);
|
|
1691
1568
|
}
|
|
1692
|
-
|
|
1569
|
+
getLogger().debug("Provider does not support reverse lookup");
|
|
1693
1570
|
return null;
|
|
1694
1571
|
}
|
|
1695
1572
|
/**
|
|
@@ -1700,26 +1577,26 @@ var DatabaseManager = class {
|
|
|
1700
1577
|
async hasDatabaseUrlInEnv(workspacePath) {
|
|
1701
1578
|
try {
|
|
1702
1579
|
if (this.databaseUrlEnvVarName !== "DATABASE_URL") {
|
|
1703
|
-
|
|
1580
|
+
getLogger().debug(`Looking for custom database URL variable: ${this.databaseUrlEnvVarName}`);
|
|
1704
1581
|
} else {
|
|
1705
|
-
|
|
1582
|
+
getLogger().debug("Looking for default database URL variable: DATABASE_URL");
|
|
1706
1583
|
}
|
|
1707
1584
|
const hasConfiguredVar = await hasVariableInAnyEnvFile(
|
|
1708
1585
|
workspacePath,
|
|
1709
1586
|
this.databaseUrlEnvVarName,
|
|
1710
|
-
async (p) =>
|
|
1587
|
+
async (p) => fs5.pathExists(p),
|
|
1711
1588
|
async (p, v) => this.environment.getEnvVariable(p, v)
|
|
1712
1589
|
);
|
|
1713
1590
|
if (hasConfiguredVar) {
|
|
1714
1591
|
if (this.databaseUrlEnvVarName !== "DATABASE_URL") {
|
|
1715
|
-
|
|
1592
|
+
getLogger().debug(`\u2705 Found custom database URL variable: ${this.databaseUrlEnvVarName}`);
|
|
1716
1593
|
} else {
|
|
1717
|
-
|
|
1594
|
+
getLogger().debug(`\u2705 Found default database URL variable: DATABASE_URL`);
|
|
1718
1595
|
}
|
|
1719
1596
|
return true;
|
|
1720
1597
|
}
|
|
1721
1598
|
if (this.databaseUrlEnvVarName !== "DATABASE_URL") {
|
|
1722
|
-
|
|
1599
|
+
getLogger().debug(`\u274C Custom database URL variable '${this.databaseUrlEnvVarName}' not found in any env file`);
|
|
1723
1600
|
throw new Error(
|
|
1724
1601
|
`Configured database URL environment variable '${this.databaseUrlEnvVarName}' not found in any dotenv-flow file. Please add it to an .env file or update your iloom configuration.`
|
|
1725
1602
|
);
|
|
@@ -1727,13 +1604,13 @@ var DatabaseManager = class {
|
|
|
1727
1604
|
const hasDefaultVar = await hasVariableInAnyEnvFile(
|
|
1728
1605
|
workspacePath,
|
|
1729
1606
|
"DATABASE_URL",
|
|
1730
|
-
async (p) =>
|
|
1607
|
+
async (p) => fs5.pathExists(p),
|
|
1731
1608
|
async (p, v) => this.environment.getEnvVariable(p, v)
|
|
1732
1609
|
);
|
|
1733
1610
|
if (hasDefaultVar) {
|
|
1734
|
-
|
|
1611
|
+
getLogger().debug("\u2705 Found fallback DATABASE_URL variable");
|
|
1735
1612
|
} else {
|
|
1736
|
-
|
|
1613
|
+
getLogger().debug("\u274C No DATABASE_URL variable found in any env file");
|
|
1737
1614
|
}
|
|
1738
1615
|
return hasDefaultVar;
|
|
1739
1616
|
} catch (error) {
|
|
@@ -1746,7 +1623,7 @@ var DatabaseManager = class {
|
|
|
1746
1623
|
};
|
|
1747
1624
|
|
|
1748
1625
|
// src/lib/ResourceCleanup.ts
|
|
1749
|
-
import
|
|
1626
|
+
import path4 from "path";
|
|
1750
1627
|
var ResourceCleanup = class {
|
|
1751
1628
|
constructor(gitWorktree, processManager, database, cliIsolation, settingsManager) {
|
|
1752
1629
|
this.gitWorktree = gitWorktree;
|
|
@@ -1768,7 +1645,7 @@ var ResourceCleanup = class {
|
|
|
1768
1645
|
const operations = [];
|
|
1769
1646
|
const errors = [];
|
|
1770
1647
|
const displayIdentifier = parsed.branchName ?? ((_a = parsed.number) == null ? void 0 : _a.toString()) ?? parsed.originalInput;
|
|
1771
|
-
|
|
1648
|
+
getLogger().info(`Starting cleanup for: ${displayIdentifier}`);
|
|
1772
1649
|
const number = parsed.number;
|
|
1773
1650
|
if (number !== void 0) {
|
|
1774
1651
|
const port = this.processManager.calculatePort(number);
|
|
@@ -1814,7 +1691,7 @@ var ResourceCleanup = class {
|
|
|
1814
1691
|
if (!worktree) {
|
|
1815
1692
|
throw new Error(`No worktree found for identifier: ${displayIdentifier}`);
|
|
1816
1693
|
}
|
|
1817
|
-
|
|
1694
|
+
getLogger().debug(`Found worktree: path="${worktree.path}", branch="${worktree.branch}"`);
|
|
1818
1695
|
} catch (error) {
|
|
1819
1696
|
const err = error instanceof Error ? error : new Error("Unknown error");
|
|
1820
1697
|
errors.push(err);
|
|
@@ -1827,7 +1704,9 @@ var ResourceCleanup = class {
|
|
|
1827
1704
|
};
|
|
1828
1705
|
}
|
|
1829
1706
|
if (!options.force) {
|
|
1830
|
-
const
|
|
1707
|
+
const shouldCheckMergeSafety = options.checkMergeSafety ?? options.deleteBranch === true;
|
|
1708
|
+
const shouldCheckRemoteBranch = options.checkRemoteBranch ?? false;
|
|
1709
|
+
const safety = await this.validateWorktreeSafety(worktree, parsed.originalInput, shouldCheckMergeSafety, shouldCheckRemoteBranch);
|
|
1831
1710
|
if (!safety.isSafe) {
|
|
1832
1711
|
const blockerMessage = safety.blockers.join("\n\n");
|
|
1833
1712
|
throw new Error(`Cannot cleanup:
|
|
@@ -1836,18 +1715,18 @@ ${blockerMessage}`);
|
|
|
1836
1715
|
}
|
|
1837
1716
|
if (safety.warnings.length > 0) {
|
|
1838
1717
|
safety.warnings.forEach((warning) => {
|
|
1839
|
-
|
|
1718
|
+
getLogger().warn(warning);
|
|
1840
1719
|
});
|
|
1841
1720
|
}
|
|
1842
1721
|
}
|
|
1843
1722
|
let databaseConfig = null;
|
|
1844
1723
|
if (!options.keepDatabase && worktree) {
|
|
1845
|
-
const envFilePath =
|
|
1724
|
+
const envFilePath = path4.join(worktree.path, ".env");
|
|
1846
1725
|
try {
|
|
1847
1726
|
const shouldCleanup = this.database ? await this.database.shouldUseDatabaseBranching(envFilePath) : false;
|
|
1848
1727
|
databaseConfig = { shouldCleanup, envFilePath };
|
|
1849
1728
|
} catch (error) {
|
|
1850
|
-
|
|
1729
|
+
getLogger().warn(
|
|
1851
1730
|
`Failed to read database config from ${envFilePath}, skipping database cleanup: ${error instanceof Error ? error.message : String(error)}`
|
|
1852
1731
|
);
|
|
1853
1732
|
databaseConfig = { shouldCleanup: false, envFilePath };
|
|
@@ -1858,11 +1737,25 @@ ${blockerMessage}`);
|
|
|
1858
1737
|
try {
|
|
1859
1738
|
mainWorktreePath = await findMainWorktreePathWithSettings(worktree.path, this.settingsManager);
|
|
1860
1739
|
} catch (error) {
|
|
1861
|
-
|
|
1740
|
+
getLogger().warn(
|
|
1862
1741
|
`Failed to find main worktree path: ${error instanceof Error ? error.message : String(error)}`
|
|
1863
1742
|
);
|
|
1864
1743
|
}
|
|
1865
1744
|
}
|
|
1745
|
+
let mergeTargetBranch = null;
|
|
1746
|
+
if (options.deleteBranch && worktree && !options.dryRun) {
|
|
1747
|
+
try {
|
|
1748
|
+
mergeTargetBranch = await getMergeTargetBranch(worktree.path, {
|
|
1749
|
+
settingsManager: this.settingsManager,
|
|
1750
|
+
metadataManager: this.metadataManager
|
|
1751
|
+
});
|
|
1752
|
+
getLogger().debug(`Pre-fetched merge target branch: ${mergeTargetBranch}`);
|
|
1753
|
+
} catch (error) {
|
|
1754
|
+
getLogger().warn(
|
|
1755
|
+
`Failed to pre-fetch merge target branch: ${error instanceof Error ? error.message : String(error)}`
|
|
1756
|
+
);
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1866
1759
|
if (options.dryRun) {
|
|
1867
1760
|
operations.push({
|
|
1868
1761
|
type: "worktree",
|
|
@@ -1886,7 +1779,7 @@ ${blockerMessage}`);
|
|
|
1886
1779
|
message: `Worktree removed: ${worktree.path}`
|
|
1887
1780
|
});
|
|
1888
1781
|
await this.metadataManager.deleteMetadata(worktree.path);
|
|
1889
|
-
|
|
1782
|
+
getLogger().debug(`Metadata file cleanup attempted for: ${worktree.path}`);
|
|
1890
1783
|
} catch (error) {
|
|
1891
1784
|
const err = error instanceof Error ? error : new Error("Unknown error");
|
|
1892
1785
|
errors.push(err);
|
|
@@ -1907,7 +1800,12 @@ ${blockerMessage}`);
|
|
|
1907
1800
|
});
|
|
1908
1801
|
} else {
|
|
1909
1802
|
try {
|
|
1910
|
-
const branchOptions = {
|
|
1803
|
+
const branchOptions = {
|
|
1804
|
+
dryRun: false
|
|
1805
|
+
};
|
|
1806
|
+
if (mergeTargetBranch !== null) {
|
|
1807
|
+
branchOptions.mergeTargetBranch = mergeTargetBranch;
|
|
1808
|
+
}
|
|
1911
1809
|
if (options.force !== void 0) {
|
|
1912
1810
|
branchOptions.force = options.force;
|
|
1913
1811
|
}
|
|
@@ -1948,7 +1846,7 @@ ${blockerMessage}`);
|
|
|
1948
1846
|
} catch (error) {
|
|
1949
1847
|
const err = error instanceof Error ? error : new Error("Unknown error");
|
|
1950
1848
|
errors.push(err);
|
|
1951
|
-
|
|
1849
|
+
getLogger().warn(
|
|
1952
1850
|
`CLI symlink cleanup failed: ${err.message}`
|
|
1953
1851
|
);
|
|
1954
1852
|
operations.push({
|
|
@@ -1978,7 +1876,7 @@ ${blockerMessage}`);
|
|
|
1978
1876
|
mainWorktreePath ?? void 0
|
|
1979
1877
|
);
|
|
1980
1878
|
if (deletionResult.deleted) {
|
|
1981
|
-
|
|
1879
|
+
getLogger().info(`Database branch deleted: ${worktree.branch}`);
|
|
1982
1880
|
operations.push({
|
|
1983
1881
|
type: "database",
|
|
1984
1882
|
success: true,
|
|
@@ -1986,7 +1884,7 @@ ${blockerMessage}`);
|
|
|
1986
1884
|
deleted: true
|
|
1987
1885
|
});
|
|
1988
1886
|
} else if (deletionResult.notFound) {
|
|
1989
|
-
|
|
1887
|
+
getLogger().debug(`No database branch found for: ${worktree.branch}`);
|
|
1990
1888
|
operations.push({
|
|
1991
1889
|
type: "database",
|
|
1992
1890
|
success: true,
|
|
@@ -1994,7 +1892,7 @@ ${blockerMessage}`);
|
|
|
1994
1892
|
deleted: false
|
|
1995
1893
|
});
|
|
1996
1894
|
} else if (deletionResult.userDeclined) {
|
|
1997
|
-
|
|
1895
|
+
getLogger().info("Preview database deletion declined by user");
|
|
1998
1896
|
operations.push({
|
|
1999
1897
|
type: "database",
|
|
2000
1898
|
success: true,
|
|
@@ -2004,7 +1902,7 @@ ${blockerMessage}`);
|
|
|
2004
1902
|
} else if (!deletionResult.success) {
|
|
2005
1903
|
const errorMsg = deletionResult.error ?? "Unknown error";
|
|
2006
1904
|
errors.push(new Error(errorMsg));
|
|
2007
|
-
|
|
1905
|
+
getLogger().warn(`Database cleanup failed: ${errorMsg}`);
|
|
2008
1906
|
operations.push({
|
|
2009
1907
|
type: "database",
|
|
2010
1908
|
success: false,
|
|
@@ -2015,7 +1913,7 @@ ${blockerMessage}`);
|
|
|
2015
1913
|
});
|
|
2016
1914
|
} else {
|
|
2017
1915
|
errors.push(new Error("Database cleanup in an unknown state"));
|
|
2018
|
-
|
|
1916
|
+
getLogger().warn("Database deletion returned unexpected result state");
|
|
2019
1917
|
operations.push({
|
|
2020
1918
|
type: "database",
|
|
2021
1919
|
success: false,
|
|
@@ -2025,7 +1923,7 @@ ${blockerMessage}`);
|
|
|
2025
1923
|
}
|
|
2026
1924
|
} catch (error) {
|
|
2027
1925
|
errors.push(error instanceof Error ? error : new Error(String(error)));
|
|
2028
|
-
|
|
1926
|
+
getLogger().warn(
|
|
2029
1927
|
`Unexpected database cleanup exception: ${error instanceof Error ? error.message : String(error)}`
|
|
2030
1928
|
);
|
|
2031
1929
|
operations.push({
|
|
@@ -2072,19 +1970,19 @@ ${blockerMessage}`);
|
|
|
2072
1970
|
* Terminate dev server on specified port
|
|
2073
1971
|
*/
|
|
2074
1972
|
async terminateDevServer(port) {
|
|
2075
|
-
|
|
1973
|
+
getLogger().debug(`Checking for dev server on port ${port}`);
|
|
2076
1974
|
const processInfo = await this.processManager.detectDevServer(port);
|
|
2077
1975
|
if (!processInfo) {
|
|
2078
|
-
|
|
1976
|
+
getLogger().debug(`No process found on port ${port}`);
|
|
2079
1977
|
return false;
|
|
2080
1978
|
}
|
|
2081
1979
|
if (!processInfo.isDevServer) {
|
|
2082
|
-
|
|
1980
|
+
getLogger().warn(
|
|
2083
1981
|
`Process on port ${port} (${processInfo.name}) doesn't appear to be a dev server, skipping`
|
|
2084
1982
|
);
|
|
2085
1983
|
return false;
|
|
2086
1984
|
}
|
|
2087
|
-
|
|
1985
|
+
getLogger().info(`Terminating dev server: ${processInfo.name} (PID: ${processInfo.pid})`);
|
|
2088
1986
|
await this.processManager.terminateProcess(processInfo.pid);
|
|
2089
1987
|
const isFree = await this.processManager.verifyPortFree(port);
|
|
2090
1988
|
if (!isFree) {
|
|
@@ -2104,23 +2002,75 @@ ${blockerMessage}`);
|
|
|
2104
2002
|
if (protectedBranches.includes(branchName)) {
|
|
2105
2003
|
throw new Error(`Cannot delete protected branch: ${branchName}`);
|
|
2106
2004
|
}
|
|
2005
|
+
const workingDir = cwd ?? await findMainWorktreePathWithSettings(void 0, this.settingsManager);
|
|
2006
|
+
try {
|
|
2007
|
+
await executeGitCommand(["rev-parse", "--verify", `refs/heads/${branchName}`], {
|
|
2008
|
+
cwd: workingDir
|
|
2009
|
+
});
|
|
2010
|
+
} catch {
|
|
2011
|
+
getLogger().debug(`Branch ${branchName} does not exist, skipping deletion`);
|
|
2012
|
+
return true;
|
|
2013
|
+
}
|
|
2107
2014
|
if (options.dryRun) {
|
|
2108
|
-
|
|
2015
|
+
getLogger().info(`[DRY RUN] Would delete branch: ${branchName}`);
|
|
2109
2016
|
return true;
|
|
2110
2017
|
}
|
|
2111
2018
|
try {
|
|
2112
|
-
let
|
|
2113
|
-
|
|
2019
|
+
let deleteFlag = "-d";
|
|
2020
|
+
let deleteCwd = workingDir;
|
|
2021
|
+
if (options.force) {
|
|
2022
|
+
deleteFlag = "-D";
|
|
2023
|
+
} else if (options.mergeTargetBranch) {
|
|
2024
|
+
const mergeTarget = options.mergeTargetBranch;
|
|
2025
|
+
try {
|
|
2026
|
+
const targetWorktreePath = await findWorktreeForBranch(mergeTarget, workingDir);
|
|
2027
|
+
getLogger().debug(`Running branch delete from worktree where '${mergeTarget}' is checked out: ${targetWorktreePath}`);
|
|
2028
|
+
deleteCwd = targetWorktreePath;
|
|
2029
|
+
} catch {
|
|
2030
|
+
getLogger().debug(`Could not find worktree for branch '${mergeTarget}', falling back to merge check`);
|
|
2031
|
+
const isMerged = await isBranchMergedIntoMain(branchName, mergeTarget, workingDir);
|
|
2032
|
+
if (isMerged) {
|
|
2033
|
+
getLogger().debug(`Branch '${branchName}' verified merged into '${mergeTarget}', using force delete`);
|
|
2034
|
+
deleteFlag = "-D";
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
} else if (options.worktreePath) {
|
|
2038
|
+
getLogger().warn("deleteBranch called with worktreePath but no mergeTargetBranch - this may fail if worktree was deleted");
|
|
2039
|
+
try {
|
|
2040
|
+
const mergeTarget = await getMergeTargetBranch(options.worktreePath, {
|
|
2041
|
+
settingsManager: this.settingsManager,
|
|
2042
|
+
metadataManager: this.metadataManager
|
|
2043
|
+
});
|
|
2044
|
+
try {
|
|
2045
|
+
const targetWorktreePath = await findWorktreeForBranch(mergeTarget, workingDir);
|
|
2046
|
+
getLogger().debug(`Running branch delete from worktree where '${mergeTarget}' is checked out: ${targetWorktreePath}`);
|
|
2047
|
+
deleteCwd = targetWorktreePath;
|
|
2048
|
+
} catch {
|
|
2049
|
+
getLogger().debug(`Could not find worktree for branch '${mergeTarget}', falling back to merge check`);
|
|
2050
|
+
const isMerged = await isBranchMergedIntoMain(branchName, mergeTarget, workingDir);
|
|
2051
|
+
if (isMerged) {
|
|
2052
|
+
getLogger().debug(`Branch '${branchName}' verified merged into '${mergeTarget}', using force delete`);
|
|
2053
|
+
deleteFlag = "-D";
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
} catch (error) {
|
|
2057
|
+
getLogger().debug(`Could not read merge target from worktreePath: ${error instanceof Error ? error.message : String(error)}`);
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2114
2060
|
await executeGitCommand(["branch", deleteFlag, branchName], {
|
|
2115
|
-
cwd:
|
|
2061
|
+
cwd: deleteCwd
|
|
2116
2062
|
});
|
|
2117
|
-
|
|
2063
|
+
getLogger().info(`Branch deleted: ${branchName}`);
|
|
2118
2064
|
return true;
|
|
2119
2065
|
} catch (error) {
|
|
2066
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2067
|
+
if (errorMessage.includes("not found") || errorMessage.includes("does not exist")) {
|
|
2068
|
+
getLogger().debug(`Branch ${branchName} already deleted`);
|
|
2069
|
+
return true;
|
|
2070
|
+
}
|
|
2120
2071
|
if (options.force) {
|
|
2121
2072
|
throw error;
|
|
2122
2073
|
}
|
|
2123
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2124
2074
|
if (errorMessage.includes("not fully merged")) {
|
|
2125
2075
|
throw new Error(
|
|
2126
2076
|
`Cannot delete unmerged branch '${branchName}'. Use --force to delete anyway.`
|
|
@@ -2143,17 +2093,17 @@ ${blockerMessage}`);
|
|
|
2143
2093
|
*/
|
|
2144
2094
|
async cleanupDatabase(branchName, worktreePath) {
|
|
2145
2095
|
if (!this.database) {
|
|
2146
|
-
|
|
2096
|
+
getLogger().debug("Database manager not available, skipping database cleanup");
|
|
2147
2097
|
return false;
|
|
2148
2098
|
}
|
|
2149
2099
|
try {
|
|
2150
|
-
const envFilePath =
|
|
2100
|
+
const envFilePath = path4.join(worktreePath, ".env");
|
|
2151
2101
|
const shouldCleanup = await this.database.shouldUseDatabaseBranching(envFilePath);
|
|
2152
2102
|
let cwd;
|
|
2153
2103
|
try {
|
|
2154
2104
|
cwd = await findMainWorktreePathWithSettings(worktreePath, this.settingsManager);
|
|
2155
2105
|
} catch (error) {
|
|
2156
|
-
|
|
2106
|
+
getLogger().debug(
|
|
2157
2107
|
`Could not find main worktree path, using current directory: ${error instanceof Error ? error.message : String(error)}`
|
|
2158
2108
|
);
|
|
2159
2109
|
}
|
|
@@ -2165,23 +2115,23 @@ ${blockerMessage}`);
|
|
|
2165
2115
|
cwd
|
|
2166
2116
|
);
|
|
2167
2117
|
if (result.deleted) {
|
|
2168
|
-
|
|
2118
|
+
getLogger().info(`Database branch deleted: ${branchName}`);
|
|
2169
2119
|
return true;
|
|
2170
2120
|
} else if (result.notFound) {
|
|
2171
|
-
|
|
2121
|
+
getLogger().debug(`No database branch found for: ${branchName}`);
|
|
2172
2122
|
return false;
|
|
2173
2123
|
} else if (result.userDeclined) {
|
|
2174
|
-
|
|
2124
|
+
getLogger().info("Preview database deletion declined by user");
|
|
2175
2125
|
return false;
|
|
2176
2126
|
} else if (!result.success) {
|
|
2177
|
-
|
|
2127
|
+
getLogger().warn(`Database cleanup failed: ${result.error ?? "Unknown error"}`);
|
|
2178
2128
|
return false;
|
|
2179
2129
|
} else {
|
|
2180
|
-
|
|
2130
|
+
getLogger().debug("Database deletion returned unexpected result");
|
|
2181
2131
|
return false;
|
|
2182
2132
|
}
|
|
2183
2133
|
} catch (error) {
|
|
2184
|
-
|
|
2134
|
+
getLogger().warn(
|
|
2185
2135
|
`Unexpected database cleanup error: ${error instanceof Error ? error.message : String(error)}`
|
|
2186
2136
|
);
|
|
2187
2137
|
return false;
|
|
@@ -2202,8 +2152,13 @@ ${blockerMessage}`);
|
|
|
2202
2152
|
/**
|
|
2203
2153
|
* Validate worktree safety given a worktree object
|
|
2204
2154
|
* Private method used internally when worktree is already known
|
|
2155
|
+
*
|
|
2156
|
+
* @param worktree - The worktree to validate
|
|
2157
|
+
* @param identifier - The original identifier used (for error messages)
|
|
2158
|
+
* @param checkBranchMerge - Whether to check if branch is merged into main (for branch deletion)
|
|
2159
|
+
* @param checkRemoteBranch - Whether to check if branch exists on remote (for GitHub-PR mode)
|
|
2205
2160
|
*/
|
|
2206
|
-
async validateWorktreeSafety(worktree, identifier) {
|
|
2161
|
+
async validateWorktreeSafety(worktree, identifier, checkBranchMerge = false, checkRemoteBranch = false) {
|
|
2207
2162
|
const warnings = [];
|
|
2208
2163
|
const blockers = [];
|
|
2209
2164
|
const isMain = await this.gitWorktree.isMainWorktree(worktree, this.settingsManager);
|
|
@@ -2220,6 +2175,44 @@ Please resolve before cleanup - you have some options:
|
|
|
2220
2175
|
\u2022 Force cleanup: il cleanup ${identifier} --force (WARNING: will discard changes)`;
|
|
2221
2176
|
blockers.push(blockerMessage);
|
|
2222
2177
|
}
|
|
2178
|
+
if ((checkBranchMerge || checkRemoteBranch) && worktree.branch) {
|
|
2179
|
+
const mainBranch = await getMergeTargetBranch(worktree.path, {
|
|
2180
|
+
settingsManager: this.settingsManager,
|
|
2181
|
+
metadataManager: this.metadataManager
|
|
2182
|
+
});
|
|
2183
|
+
const remoteStatus = await checkRemoteBranchStatus(worktree.branch, worktree.path);
|
|
2184
|
+
if (remoteStatus.networkError) {
|
|
2185
|
+
const blockerMessage = `Cannot verify remote branch status due to network error.
|
|
2186
|
+
|
|
2187
|
+
Error: ${remoteStatus.errorMessage ?? "Unknown network error"}
|
|
2188
|
+
|
|
2189
|
+
Unable to determine if branch '${worktree.branch}' is safely backed up.
|
|
2190
|
+
Use --force to proceed without verification.`;
|
|
2191
|
+
blockers.push(blockerMessage);
|
|
2192
|
+
} else if (remoteStatus.exists && remoteStatus.localAhead) {
|
|
2193
|
+
const blockerMessage = `Branch '${worktree.branch}' has unpushed commits that would be lost.
|
|
2194
|
+
The remote branch exists but your local branch is ahead.
|
|
2195
|
+
|
|
2196
|
+
Please resolve before cleanup:
|
|
2197
|
+
\u2022 Push your commits: git push origin ${worktree.branch}
|
|
2198
|
+
\u2022 Force cleanup: il cleanup ${identifier} --force (WARNING: will lose commits)`;
|
|
2199
|
+
blockers.push(blockerMessage);
|
|
2200
|
+
} else if (remoteStatus.exists && !remoteStatus.localAhead) {
|
|
2201
|
+
} else if (!remoteStatus.exists) {
|
|
2202
|
+
const isMerged = await isBranchMergedIntoMain(worktree.branch, mainBranch, worktree.path);
|
|
2203
|
+
if (isMerged) {
|
|
2204
|
+
} else {
|
|
2205
|
+
const blockerMessage = `Branch '${worktree.branch}' has not been pushed to remote and is not merged into '${mainBranch}'.
|
|
2206
|
+
Deleting this branch would result in data loss.
|
|
2207
|
+
|
|
2208
|
+
Please resolve before cleanup - you have some options:
|
|
2209
|
+
\u2022 Push to remote: git push -u origin ${worktree.branch}
|
|
2210
|
+
\u2022 Merge to ${mainBranch}: git checkout ${mainBranch} && git merge ${worktree.branch}
|
|
2211
|
+
\u2022 Force cleanup: il cleanup ${identifier} --force (WARNING: will lose commits)`;
|
|
2212
|
+
blockers.push(blockerMessage);
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2223
2216
|
return {
|
|
2224
2217
|
isSafe: blockers.length === 0,
|
|
2225
2218
|
warnings,
|
|
@@ -2282,11 +2275,10 @@ Please resolve before cleanup - you have some options:
|
|
|
2282
2275
|
};
|
|
2283
2276
|
|
|
2284
2277
|
export {
|
|
2285
|
-
MetadataManager,
|
|
2286
2278
|
LoomManager,
|
|
2287
2279
|
EnvironmentManager,
|
|
2288
2280
|
CLIIsolationManager,
|
|
2289
2281
|
DatabaseManager,
|
|
2290
2282
|
ResourceCleanup
|
|
2291
2283
|
};
|
|
2292
|
-
//# sourceMappingURL=chunk-
|
|
2284
|
+
//# sourceMappingURL=chunk-NFVFVYAP.js.map
|