@iloom/cli 0.3.4 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -3
- package/dist/{BranchNamingService-A77VI6AI.js → BranchNamingService-GCCWB3LK.js} +4 -3
- package/dist/ClaudeContextManager-DK77227F.js +16 -0
- package/dist/ClaudeService-W3SA7HVG.js +15 -0
- package/dist/GitHubService-RPM27GWD.js +12 -0
- package/dist/{LoomLauncher-ZV3ZZIBA.js → LoomLauncher-S3YGJRJQ.js} +43 -27
- package/dist/LoomLauncher-S3YGJRJQ.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-5Q3NDNNV.js → chunk-2W2FBL5G.js} +153 -6
- package/dist/chunk-2W2FBL5G.js.map +1 -0
- package/dist/{chunk-OXAM2WVC.js → chunk-55TB3FSG.js} +21 -1
- package/dist/chunk-55TB3FSG.js.map +1 -0
- 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-2MAIX45J.js → chunk-BIIQHEXJ.js} +104 -43
- package/dist/chunk-BIIQHEXJ.js.map +1 -0
- package/dist/{chunk-UAN4A3YU.js → chunk-G6CIIJLT.js} +11 -11
- package/dist/{chunk-DLHA5VQ3.js → chunk-HD5SUKI2.js} +36 -179
- package/dist/chunk-HD5SUKI2.js.map +1 -0
- package/dist/{chunk-2IJEMXOB.js → chunk-IARWMDAX.js} +427 -428
- package/dist/chunk-IARWMDAX.js.map +1 -0
- package/dist/chunk-IJ7IGJT3.js +192 -0
- package/dist/chunk-IJ7IGJT3.js.map +1 -0
- package/dist/{chunk-2CXREBLZ.js → chunk-JC5HXN75.js} +8 -6
- package/dist/chunk-JC5HXN75.js.map +1 -0
- 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-4XIDC3NF.js → chunk-MD6HA5IK.js} +2 -2
- 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-PGPI5LR4.js → chunk-POI7KLBH.js} +7 -21
- package/dist/chunk-POI7KLBH.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-SUOXY5WJ.js → chunk-QIUJPPJQ.js} +5 -5
- package/dist/chunk-QIUJPPJQ.js.map +1 -0
- package/dist/{chunk-ZM3CFL5L.js → chunk-QRBOPFAA.js} +3 -3
- package/dist/{chunk-OYF4VIFI.js → chunk-RUC7OULH.js} +147 -22
- package/dist/chunk-RUC7OULH.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-TMZAVPGF.js +667 -0
- package/dist/chunk-TMZAVPGF.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-AKUJXDNW.js → chunk-UPUAQYAW.js} +3 -3
- 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-RW54ZMBM.js → chunk-VAYGNQTE.js} +2 -2
- package/dist/{chunk-ZT3YZB4K.js → chunk-VBFDVGAE.js} +12 -12
- package/dist/chunk-VBFDVGAE.js.map +1 -0
- package/dist/{chunk-IFB4Z76W.js → chunk-VTXCGKV5.js} +13 -12
- package/dist/chunk-VTXCGKV5.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/{chunk-UJL4HI2R.js → chunk-Z5NXYJIG.js} +20 -2
- package/dist/chunk-Z5NXYJIG.js.map +1 -0
- package/dist/{claude-W52VKI6L.js → claude-ACVXNB6N.js} +8 -5
- package/dist/{cleanup-H4VXU3C3.js → cleanup-KDLVTT7M.js} +133 -122
- package/dist/cleanup-KDLVTT7M.js.map +1 -0
- package/dist/cli.js +953 -430
- package/dist/cli.js.map +1 -1
- package/dist/{color-F7RU6B6Z.js → color-ZPIIUADB.js} +3 -3
- package/dist/{contribute-Y7IQV5QY.js → contribute-HY372S6F.js} +8 -6
- package/dist/{contribute-Y7IQV5QY.js.map → contribute-HY372S6F.js.map} +1 -1
- package/dist/dev-server-JCJGQ3PV.js +298 -0
- package/dist/dev-server-JCJGQ3PV.js.map +1 -0
- package/dist/{feedback-XTUCKJNT.js → feedback-7PVBQNLJ.js} +13 -12
- package/dist/{feedback-XTUCKJNT.js.map → feedback-7PVBQNLJ.js.map} +1 -1
- package/dist/{git-IYA53VIC.js → git-4BVOOOOV.js} +16 -4
- package/dist/hooks/iloom-hook.js +258 -0
- package/dist/{ignite-T74RYXCA.js → ignite-3B264M7K.js} +245 -39
- package/dist/ignite-3B264M7K.js.map +1 -0
- package/dist/index.d.ts +461 -124
- package/dist/index.js +743 -210
- package/dist/index.js.map +1 -1
- package/dist/init-LBA6NUK2.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-OGCV32Z4.js} +15 -13
- package/dist/{open-UMXANW5S.js.map → open-OGCV32Z4.js.map} +1 -1
- package/dist/projects-P55273AB.js +73 -0
- package/dist/projects-P55273AB.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-4T5FQHNH.js} +11 -9
- package/dist/{rebase-VJ2VKR6R.js.map → rebase-4T5FQHNH.js.map} +1 -1
- package/dist/{remote-VUNCQZ6J.js → remote-73TZ2ADI.js} +3 -3
- package/dist/{run-MJYY4PUT.js → run-HNOP6WE2.js} +15 -13
- package/dist/{run-MJYY4PUT.js.map → run-HNOP6WE2.js.map} +1 -1
- package/dist/schema/settings.schema.json +49 -0
- package/dist/shell-DE3HKJSM.js +240 -0
- package/dist/shell-DE3HKJSM.js.map +1 -0
- package/dist/summary-GDT7DTRI.js +244 -0
- package/dist/summary-GDT7DTRI.js.map +1 -0
- package/dist/{test-git-IT5EWQ5C.js → test-git-YMAE57UP.js} +6 -4
- package/dist/{test-git-IT5EWQ5C.js.map → test-git-YMAE57UP.js.map} +1 -1
- package/dist/{test-prefix-NPWDPUUH.js → test-prefix-YCKL6CMT.js} +6 -4
- package/dist/{test-prefix-NPWDPUUH.js.map → test-prefix-YCKL6CMT.js.map} +1 -1
- package/dist/{test-tabs-PRMRSHKI.js → test-tabs-3SCJWRKT.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-UJL4HI2R.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-GCCWB3LK.js.map} +0 -0
- /package/dist/{ClaudeContextManager-BN7RE5ZQ.js.map → ClaudeContextManager-DK77227F.js.map} +0 -0
- /package/dist/{ClaudeService-DLYLJUPA.js.map → ClaudeService-W3SA7HVG.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-UAN4A3YU.js.map → chunk-G6CIIJLT.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-AKUJXDNW.js.map → chunk-UPUAQYAW.js.map} +0 -0
- /package/dist/{chunk-OSCLCMDG.js.map → chunk-UYWAESOT.js.map} +0 -0
- /package/dist/{chunk-RW54ZMBM.js.map → chunk-VAYGNQTE.js.map} +0 -0
- /package/dist/{chunk-CFFQ2Z7A.js.map → chunk-WUQQNE63.js.map} +0 -0
- /package/dist/{claude-W52VKI6L.js.map → claude-ACVXNB6N.js.map} +0 -0
- /package/dist/{color-F7RU6B6Z.js.map → color-ZPIIUADB.js.map} +0 -0
- /package/dist/{git-IYA53VIC.js.map → git-4BVOOOOV.js.map} +0 -0
- /package/dist/{init-4FHTAM3F.js.map → init-LBA6NUK2.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-3SCJWRKT.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-2W2FBL5G.js";
|
|
27
|
+
import {
|
|
28
|
+
MetadataManager
|
|
29
|
+
} from "./chunk-IJ7IGJT3.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,201 +37,33 @@ 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-RUC7OULH.js";
|
|
33
44
|
import {
|
|
34
45
|
findEnvFileForDatabaseUrl,
|
|
35
46
|
formatEnvLine,
|
|
36
47
|
hasVariableInAnyEnvFile,
|
|
48
|
+
isNoEnvFilesFoundError,
|
|
37
49
|
loadEnvIntoProcess,
|
|
38
50
|
parseEnvFile,
|
|
39
51
|
validateEnvVariable
|
|
40
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-Z5NXYJIG.js";
|
|
53
|
+
import {
|
|
54
|
+
getLogger
|
|
55
|
+
} from "./chunk-6UIGZD2N.js";
|
|
41
56
|
import {
|
|
42
|
-
createLogger,
|
|
43
57
|
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
|
-
};
|
|
58
|
+
} from "./chunk-UYVWLISQ.js";
|
|
219
59
|
|
|
220
60
|
// src/lib/LoomManager.ts
|
|
221
|
-
import
|
|
222
|
-
import
|
|
61
|
+
import path2 from "path";
|
|
62
|
+
import fs2 from "fs-extra";
|
|
223
63
|
|
|
224
64
|
// src/lib/VSCodeIntegration.ts
|
|
225
|
-
import
|
|
226
|
-
import
|
|
65
|
+
import fs from "fs-extra";
|
|
66
|
+
import path from "path";
|
|
227
67
|
import { parse, modify, applyEdits } from "jsonc-parser";
|
|
228
68
|
var VSCodeIntegration = class {
|
|
229
69
|
/**
|
|
@@ -233,10 +73,10 @@ var VSCodeIntegration = class {
|
|
|
233
73
|
* @param hexColor - Hex color string (e.g., "#dcebf8")
|
|
234
74
|
*/
|
|
235
75
|
async setTitleBarColor(workspacePath, hexColor) {
|
|
236
|
-
const vscodeDir =
|
|
237
|
-
const settingsPath =
|
|
76
|
+
const vscodeDir = path.join(workspacePath, ".vscode");
|
|
77
|
+
const settingsPath = path.join(vscodeDir, "settings.json");
|
|
238
78
|
try {
|
|
239
|
-
await
|
|
79
|
+
await fs.ensureDir(vscodeDir);
|
|
240
80
|
const settings = await this.readSettings(settingsPath);
|
|
241
81
|
const updatedSettings = this.mergeColorSettings(settings, hexColor);
|
|
242
82
|
await this.writeSettings(settingsPath, updatedSettings);
|
|
@@ -256,10 +96,10 @@ var VSCodeIntegration = class {
|
|
|
256
96
|
*/
|
|
257
97
|
async readSettings(settingsPath) {
|
|
258
98
|
try {
|
|
259
|
-
if (!await
|
|
99
|
+
if (!await fs.pathExists(settingsPath)) {
|
|
260
100
|
return {};
|
|
261
101
|
}
|
|
262
|
-
const content = await
|
|
102
|
+
const content = await fs.readFile(settingsPath, "utf8");
|
|
263
103
|
const errors = [];
|
|
264
104
|
const settings = parse(content, errors, { allowTrailingComma: true });
|
|
265
105
|
if (errors.length > 0) {
|
|
@@ -283,8 +123,8 @@ var VSCodeIntegration = class {
|
|
|
283
123
|
async writeSettings(settingsPath, settings) {
|
|
284
124
|
try {
|
|
285
125
|
let content;
|
|
286
|
-
if (await
|
|
287
|
-
const existingContent = await
|
|
126
|
+
if (await fs.pathExists(settingsPath)) {
|
|
127
|
+
const existingContent = await fs.readFile(settingsPath, "utf8");
|
|
288
128
|
if (existingContent.includes("//") || existingContent.includes("/*")) {
|
|
289
129
|
content = await this.modifyWithCommentsPreserved(existingContent, settings);
|
|
290
130
|
} else {
|
|
@@ -294,8 +134,8 @@ var VSCodeIntegration = class {
|
|
|
294
134
|
content = JSON.stringify(settings, null, 2) + "\n";
|
|
295
135
|
}
|
|
296
136
|
const tempPath = `${settingsPath}.tmp`;
|
|
297
|
-
await
|
|
298
|
-
await
|
|
137
|
+
await fs.writeFile(tempPath, content, "utf8");
|
|
138
|
+
await fs.rename(tempPath, settingsPath);
|
|
299
139
|
} catch (error) {
|
|
300
140
|
throw new Error(
|
|
301
141
|
`Failed to write settings.json: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
@@ -373,7 +213,7 @@ var LoomManager = class {
|
|
|
373
213
|
return null;
|
|
374
214
|
}
|
|
375
215
|
try {
|
|
376
|
-
const envFilePath =
|
|
216
|
+
const envFilePath = path2.join(loomPath, ".env");
|
|
377
217
|
const settings = await this.settings.loadSettings();
|
|
378
218
|
const databaseUrlVarName = ((_b = (_a = settings.capabilities) == null ? void 0 : _a.database) == null ? void 0 : _b.databaseUrlEnvVarName) ?? "DATABASE_URL";
|
|
379
219
|
const connectionString = await this.environment.getEnvVariable(envFilePath, databaseUrlVarName);
|
|
@@ -382,7 +222,7 @@ var LoomManager = class {
|
|
|
382
222
|
}
|
|
383
223
|
return await this.database.getBranchNameFromConnectionString(connectionString, loomPath);
|
|
384
224
|
} catch (error) {
|
|
385
|
-
|
|
225
|
+
getLogger().debug(`Could not get database branch for loom at ${loomPath}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
386
226
|
return null;
|
|
387
227
|
}
|
|
388
228
|
}
|
|
@@ -392,28 +232,29 @@ var LoomManager = class {
|
|
|
392
232
|
* NEW: Checks for existing worktrees and reuses them if found
|
|
393
233
|
*/
|
|
394
234
|
async createIloom(input) {
|
|
395
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m
|
|
396
|
-
|
|
235
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
236
|
+
getLogger().info("Fetching issue data...");
|
|
397
237
|
const issueData = await this.fetchIssueData(input);
|
|
398
238
|
if (input.type === "issue" || input.type === "pr" || input.type === "branch") {
|
|
399
|
-
|
|
239
|
+
getLogger().info("Checking for existing worktree...");
|
|
400
240
|
const existing = await this.findExistingIloom(input, issueData);
|
|
401
241
|
if (existing) {
|
|
402
|
-
|
|
242
|
+
getLogger().success(`Found existing worktree, reusing: ${existing.path}`);
|
|
403
243
|
return await this.reuseIloom(existing, input, issueData);
|
|
404
244
|
}
|
|
405
|
-
|
|
245
|
+
getLogger().info("No existing worktree found, creating new one...");
|
|
406
246
|
}
|
|
407
|
-
|
|
247
|
+
getLogger().info("Preparing branch name...");
|
|
408
248
|
const branchName = await this.prepareBranchName(input, issueData);
|
|
409
|
-
|
|
249
|
+
getLogger().info("Creating git worktree...");
|
|
410
250
|
const worktreePath = await this.createWorktreeOnly(input, branchName);
|
|
411
251
|
this.loadMainEnvFile();
|
|
412
252
|
const { capabilities, binEntries } = await this.capabilityDetector.detectCapabilities(worktreePath);
|
|
413
253
|
await this.copyEnvironmentFiles(worktreePath);
|
|
414
|
-
await this.copyIloomSettings(worktreePath
|
|
254
|
+
await this.copyIloomSettings(worktreePath);
|
|
255
|
+
await this.copyClaudeSettings(worktreePath);
|
|
415
256
|
const settingsData = await this.settings.loadSettings();
|
|
416
|
-
const basePort = ((
|
|
257
|
+
const basePort = ((_b = (_a = settingsData.capabilities) == null ? void 0 : _a.web) == null ? void 0 : _b.basePort) ?? 3e3;
|
|
417
258
|
let port = basePort;
|
|
418
259
|
if (capabilities.includes("web")) {
|
|
419
260
|
port = await this.setupPortForWeb(worktreePath, input, basePort);
|
|
@@ -421,10 +262,10 @@ var LoomManager = class {
|
|
|
421
262
|
try {
|
|
422
263
|
await installDependencies(worktreePath, true, true);
|
|
423
264
|
} catch (error) {
|
|
424
|
-
|
|
265
|
+
getLogger().warn(`Failed to install dependencies: ${error instanceof Error ? error.message : "Unknown error"}`, error);
|
|
425
266
|
}
|
|
426
267
|
let databaseBranch = void 0;
|
|
427
|
-
if (this.database && !((
|
|
268
|
+
if (this.database && !((_c = input.options) == null ? void 0 : _c.skipDatabase)) {
|
|
428
269
|
try {
|
|
429
270
|
const connectionString = await this.database.createBranchIfConfigured(
|
|
430
271
|
branchName,
|
|
@@ -432,7 +273,7 @@ var LoomManager = class {
|
|
|
432
273
|
// workspace path - checks all dotenv-flow files
|
|
433
274
|
void 0,
|
|
434
275
|
// cwd
|
|
435
|
-
(
|
|
276
|
+
(_d = input.parentLoom) == null ? void 0 : _d.databaseBranch
|
|
436
277
|
// fromBranch - use parent's database branch for child looms
|
|
437
278
|
);
|
|
438
279
|
if (connectionString) {
|
|
@@ -441,26 +282,26 @@ var LoomManager = class {
|
|
|
441
282
|
worktreePath,
|
|
442
283
|
varName,
|
|
443
284
|
isFileTrackedByGit,
|
|
444
|
-
async (p) =>
|
|
285
|
+
async (p) => fs2.pathExists(p),
|
|
445
286
|
async (p, v) => this.environment.getEnvVariable(p, v)
|
|
446
287
|
);
|
|
447
288
|
await this.environment.setEnvVar(
|
|
448
|
-
|
|
289
|
+
path2.join(worktreePath, targetFile),
|
|
449
290
|
varName,
|
|
450
291
|
connectionString
|
|
451
292
|
);
|
|
452
|
-
|
|
293
|
+
getLogger().success("Database branch configured");
|
|
453
294
|
databaseBranch = branchName;
|
|
454
295
|
}
|
|
455
296
|
} catch (error) {
|
|
456
|
-
|
|
297
|
+
getLogger().error(
|
|
457
298
|
`Failed to setup database branch: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
458
299
|
);
|
|
459
300
|
throw error;
|
|
460
301
|
}
|
|
461
302
|
}
|
|
462
303
|
let cliSymlinks = void 0;
|
|
463
|
-
if (capabilities.includes("cli")) {
|
|
304
|
+
if (capabilities.includes("cli") && input.type !== "branch") {
|
|
464
305
|
try {
|
|
465
306
|
cliSymlinks = await this.cliIsolation.setupCLIIsolation(
|
|
466
307
|
worktreePath,
|
|
@@ -468,7 +309,7 @@ var LoomManager = class {
|
|
|
468
309
|
binEntries
|
|
469
310
|
);
|
|
470
311
|
} catch (error) {
|
|
471
|
-
|
|
312
|
+
getLogger().warn(
|
|
472
313
|
`Failed to setup CLI isolation: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
473
314
|
error
|
|
474
315
|
);
|
|
@@ -477,38 +318,38 @@ var LoomManager = class {
|
|
|
477
318
|
const allMetadata = await this.metadataManager.listAllMetadata();
|
|
478
319
|
const usedHexColors = allMetadata.filter((metadata) => metadata.colorHex !== null).map((metadata) => metadata.colorHex);
|
|
479
320
|
const colorData = selectDistinctColor(branchName, usedHexColors);
|
|
480
|
-
|
|
321
|
+
getLogger().debug(`Selected color ${colorData.hex} for branch ${branchName} (${usedHexColors.length} colors in use globally)`);
|
|
481
322
|
try {
|
|
482
323
|
await this.applyColorSynchronization(worktreePath, branchName, colorData, settingsData, input.options);
|
|
483
324
|
} catch (error) {
|
|
484
|
-
|
|
325
|
+
getLogger().warn(
|
|
485
326
|
`Failed to apply color synchronization: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
486
327
|
error
|
|
487
328
|
);
|
|
488
329
|
}
|
|
489
330
|
if (input.type === "issue") {
|
|
490
331
|
try {
|
|
491
|
-
|
|
332
|
+
getLogger().info("Moving issue to In Progress...");
|
|
492
333
|
if (this.issueTracker.moveIssueToInProgress) {
|
|
493
334
|
await this.issueTracker.moveIssueToInProgress(input.identifier);
|
|
494
335
|
}
|
|
495
336
|
} catch (error) {
|
|
496
|
-
|
|
337
|
+
getLogger().warn(
|
|
497
338
|
`Failed to move issue to In Progress: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
498
339
|
error
|
|
499
340
|
);
|
|
500
341
|
}
|
|
501
342
|
}
|
|
502
|
-
const enableClaude = ((
|
|
503
|
-
const enableCode = ((
|
|
504
|
-
const enableDevServer = ((
|
|
505
|
-
const enableTerminal = ((
|
|
506
|
-
const oneShot = ((
|
|
507
|
-
const setArguments = (
|
|
508
|
-
const executablePath = (
|
|
343
|
+
const enableClaude = ((_e = input.options) == null ? void 0 : _e.enableClaude) !== false;
|
|
344
|
+
const enableCode = ((_f = input.options) == null ? void 0 : _f.enableCode) !== false;
|
|
345
|
+
const enableDevServer = ((_g = input.options) == null ? void 0 : _g.enableDevServer) !== false;
|
|
346
|
+
const enableTerminal = ((_h = input.options) == null ? void 0 : _h.enableTerminal) ?? false;
|
|
347
|
+
const oneShot = ((_i = input.options) == null ? void 0 : _i.oneShot) ?? "default";
|
|
348
|
+
const setArguments = (_j = input.options) == null ? void 0 : _j.setArguments;
|
|
349
|
+
const executablePath = (_k = input.options) == null ? void 0 : _k.executablePath;
|
|
509
350
|
if (enableClaude || enableCode || enableDevServer || enableTerminal) {
|
|
510
|
-
const { LoomLauncher } = await import("./LoomLauncher-
|
|
511
|
-
const { ClaudeContextManager } = await import("./ClaudeContextManager-
|
|
351
|
+
const { LoomLauncher } = await import("./LoomLauncher-S3YGJRJQ.js");
|
|
352
|
+
const { ClaudeContextManager } = await import("./ClaudeContextManager-DK77227F.js");
|
|
512
353
|
const claudeContext = new ClaudeContextManager(void 0, void 0, this.settings);
|
|
513
354
|
const launcher = new LoomLauncher(claudeContext, this.settings);
|
|
514
355
|
await launcher.launchLoom({
|
|
@@ -527,13 +368,14 @@ var LoomManager = class {
|
|
|
527
368
|
...setArguments && { setArguments },
|
|
528
369
|
...executablePath && { executablePath },
|
|
529
370
|
sourceEnvOnStart: settingsData.sourceEnvOnStart ?? false,
|
|
530
|
-
colorTerminal: ((
|
|
371
|
+
colorTerminal: ((_l = input.options) == null ? void 0 : _l.colorTerminal) ?? ((_m = settingsData.colors) == null ? void 0 : _m.terminal) ?? true,
|
|
531
372
|
colorHex: colorData.hex
|
|
532
373
|
});
|
|
533
374
|
}
|
|
534
375
|
const description = (issueData == null ? void 0 : issueData.title) ?? branchName;
|
|
535
376
|
const issue_numbers = input.type === "issue" ? [String(input.identifier)] : [];
|
|
536
377
|
const pr_numbers = input.type === "pr" ? [String(input.identifier)] : [];
|
|
378
|
+
const sessionId = generateDeterministicSessionId(worktreePath);
|
|
537
379
|
const metadataInput = {
|
|
538
380
|
description,
|
|
539
381
|
branchName,
|
|
@@ -542,7 +384,10 @@ var LoomManager = class {
|
|
|
542
384
|
issue_numbers,
|
|
543
385
|
pr_numbers,
|
|
544
386
|
issueTracker: this.issueTracker.providerName,
|
|
545
|
-
colorHex: colorData.hex
|
|
387
|
+
colorHex: colorData.hex,
|
|
388
|
+
sessionId,
|
|
389
|
+
projectPath: this.gitWorktree.workingDirectory,
|
|
390
|
+
...input.parentLoom && { parentLoom: input.parentLoom }
|
|
546
391
|
};
|
|
547
392
|
await this.metadataManager.writeMetadata(worktreePath, metadataInput);
|
|
548
393
|
const loom = {
|
|
@@ -568,7 +413,7 @@ var LoomManager = class {
|
|
|
568
413
|
}
|
|
569
414
|
}
|
|
570
415
|
};
|
|
571
|
-
|
|
416
|
+
getLogger().success(`Created loom: ${loom.id} at ${loom.path}`);
|
|
572
417
|
return loom;
|
|
573
418
|
}
|
|
574
419
|
/**
|
|
@@ -616,7 +461,7 @@ var LoomManager = class {
|
|
|
616
461
|
const pattern = `${sanitizedBranchName}-looms/`;
|
|
617
462
|
return worktrees.filter((wt) => wt.path.includes(pattern));
|
|
618
463
|
} catch (error) {
|
|
619
|
-
|
|
464
|
+
getLogger().debug(`Failed to find child looms: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
620
465
|
return [];
|
|
621
466
|
}
|
|
622
467
|
}
|
|
@@ -630,7 +475,7 @@ var LoomManager = class {
|
|
|
630
475
|
async checkAndWarnChildLooms(branchName) {
|
|
631
476
|
let targetBranch = branchName;
|
|
632
477
|
if (!targetBranch) {
|
|
633
|
-
const { getCurrentBranch } = await import("./git-
|
|
478
|
+
const { getCurrentBranch } = await import("./git-4BVOOOOV.js");
|
|
634
479
|
targetBranch = await getCurrentBranch();
|
|
635
480
|
}
|
|
636
481
|
if (!targetBranch) {
|
|
@@ -638,19 +483,19 @@ var LoomManager = class {
|
|
|
638
483
|
}
|
|
639
484
|
const childLooms = await this.findChildLooms(targetBranch);
|
|
640
485
|
if (childLooms.length > 0) {
|
|
641
|
-
|
|
486
|
+
getLogger().warn(`Found ${childLooms.length} child loom(s) that should be finished first:`);
|
|
642
487
|
for (const child of childLooms) {
|
|
643
|
-
|
|
488
|
+
getLogger().warn(` - ${child.path}`);
|
|
644
489
|
}
|
|
645
|
-
|
|
646
|
-
|
|
490
|
+
getLogger().warn("");
|
|
491
|
+
getLogger().warn("To finish child looms:");
|
|
647
492
|
for (const child of childLooms) {
|
|
648
493
|
const prMatch = child.branch.match(/_pr_(\d+)/);
|
|
649
494
|
const issueId = extractIssueNumber(child.branch);
|
|
650
495
|
const childIdentifier = prMatch ? prMatch[1] : issueId ?? child.branch;
|
|
651
|
-
|
|
496
|
+
getLogger().warn(` il finish ${childIdentifier}`);
|
|
652
497
|
}
|
|
653
|
-
|
|
498
|
+
getLogger().warn("");
|
|
654
499
|
return true;
|
|
655
500
|
}
|
|
656
501
|
return false;
|
|
@@ -696,14 +541,14 @@ var LoomManager = class {
|
|
|
696
541
|
*/
|
|
697
542
|
async createWorktreeOnly(input, branchName) {
|
|
698
543
|
var _a;
|
|
699
|
-
|
|
544
|
+
getLogger().info("Ensuring repository has initial commit...");
|
|
700
545
|
await ensureRepositoryHasCommits(this.gitWorktree.workingDirectory);
|
|
701
546
|
const settingsData = await this.settings.loadSettings();
|
|
702
547
|
let worktreePrefix = settingsData.worktreePrefix;
|
|
703
548
|
if (input.parentLoom) {
|
|
704
549
|
const sanitizedBranchName = input.parentLoom.branchName.replace(/\//g, "-").replace(/[^a-zA-Z0-9-_]/g, "-");
|
|
705
550
|
worktreePrefix = `${sanitizedBranchName}-looms/`;
|
|
706
|
-
|
|
551
|
+
getLogger().info(`Creating child loom with prefix: ${worktreePrefix}`);
|
|
707
552
|
}
|
|
708
553
|
const pathOptions = input.type === "pr" ? { isPR: true, prNumber: input.identifier } : {};
|
|
709
554
|
if (worktreePrefix !== void 0) {
|
|
@@ -715,10 +560,10 @@ var LoomManager = class {
|
|
|
715
560
|
pathOptions
|
|
716
561
|
);
|
|
717
562
|
if (input.type === "pr") {
|
|
718
|
-
|
|
563
|
+
getLogger().info("Fetching all remote branches...");
|
|
719
564
|
try {
|
|
720
565
|
await executeGitCommand(["fetch", "origin"], { cwd: this.gitWorktree.workingDirectory });
|
|
721
|
-
|
|
566
|
+
getLogger().success("Successfully fetched from remote");
|
|
722
567
|
} catch (error) {
|
|
723
568
|
throw new Error(
|
|
724
569
|
`Failed to fetch from remote: ${error instanceof Error ? error.message : "Unknown error"}. Make sure you have access to the repository.`
|
|
@@ -740,13 +585,13 @@ var LoomManager = class {
|
|
|
740
585
|
...baseBranch && { baseBranch }
|
|
741
586
|
});
|
|
742
587
|
if (input.type === "pr" && !branchExistedLocally) {
|
|
743
|
-
|
|
588
|
+
getLogger().info("Resetting new PR branch to match remote exactly...");
|
|
744
589
|
try {
|
|
745
590
|
await executeGitCommand(["reset", "--hard", `origin/${branchName}`], { cwd: worktreePath });
|
|
746
591
|
await executeGitCommand(["branch", "--set-upstream-to", `origin/${branchName}`], { cwd: worktreePath });
|
|
747
|
-
|
|
592
|
+
getLogger().success("Successfully reset to match remote");
|
|
748
593
|
} catch (error) {
|
|
749
|
-
|
|
594
|
+
getLogger().warn(`Failed to reset to match remote: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
750
595
|
}
|
|
751
596
|
}
|
|
752
597
|
return worktreePath;
|
|
@@ -768,23 +613,23 @@ var LoomManager = class {
|
|
|
768
613
|
];
|
|
769
614
|
for (const pattern of envFilePatterns) {
|
|
770
615
|
try {
|
|
771
|
-
const mainEnvPath =
|
|
772
|
-
const worktreeEnvPath =
|
|
773
|
-
if (!await
|
|
616
|
+
const mainEnvPath = path2.join(mainWorkspacePath, pattern);
|
|
617
|
+
const worktreeEnvPath = path2.join(worktreePath, pattern);
|
|
618
|
+
if (!await fs2.pathExists(mainEnvPath)) {
|
|
774
619
|
continue;
|
|
775
620
|
}
|
|
776
621
|
if (await isFileTrackedByGit(pattern, mainWorkspacePath)) {
|
|
777
|
-
|
|
622
|
+
getLogger().debug(`Skipping ${pattern} (tracked by git, already in worktree)`);
|
|
778
623
|
continue;
|
|
779
624
|
}
|
|
780
|
-
if (await
|
|
781
|
-
|
|
625
|
+
if (await fs2.pathExists(worktreeEnvPath)) {
|
|
626
|
+
getLogger().warn(`${pattern} already exists in worktree, skipping copy`);
|
|
782
627
|
continue;
|
|
783
628
|
}
|
|
784
629
|
await this.environment.copyIfExists(mainEnvPath, worktreeEnvPath);
|
|
785
|
-
|
|
630
|
+
getLogger().debug(`Copied ${pattern} to worktree`);
|
|
786
631
|
} catch (error) {
|
|
787
|
-
|
|
632
|
+
getLogger().warn(`Warning: Failed to copy ${pattern}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
788
633
|
}
|
|
789
634
|
}
|
|
790
635
|
}
|
|
@@ -792,35 +637,41 @@ var LoomManager = class {
|
|
|
792
637
|
* Copy iloom configuration (settings.local.json) from main repo to worktree
|
|
793
638
|
* Always called regardless of project capabilities
|
|
794
639
|
* @param worktreePath Path to the worktree
|
|
795
|
-
* @param parentBranchName Optional parent branch name for child looms (sets mainBranch)
|
|
796
640
|
*/
|
|
797
|
-
async copyIloomSettings(worktreePath
|
|
798
|
-
const mainSettingsLocalPath =
|
|
641
|
+
async copyIloomSettings(worktreePath) {
|
|
642
|
+
const mainSettingsLocalPath = path2.join(process.cwd(), ".iloom", "settings.local.json");
|
|
799
643
|
try {
|
|
800
|
-
const worktreeIloomDir =
|
|
801
|
-
await
|
|
802
|
-
const worktreeSettingsLocalPath =
|
|
803
|
-
if (await
|
|
804
|
-
|
|
644
|
+
const worktreeIloomDir = path2.join(worktreePath, ".iloom");
|
|
645
|
+
await fs2.ensureDir(worktreeIloomDir);
|
|
646
|
+
const worktreeSettingsLocalPath = path2.join(worktreeIloomDir, "settings.local.json");
|
|
647
|
+
if (await fs2.pathExists(worktreeSettingsLocalPath)) {
|
|
648
|
+
getLogger().warn("settings.local.json already exists in worktree, skipping copy");
|
|
805
649
|
} else {
|
|
806
650
|
await this.environment.copyIfExists(mainSettingsLocalPath, worktreeSettingsLocalPath);
|
|
807
651
|
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
652
|
+
} catch (error) {
|
|
653
|
+
getLogger().warn(`Warning: Failed to copy settings.local.json: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Copy Claude settings (settings.local.json) from main repo to worktree
|
|
658
|
+
* Always called regardless of project capabilities
|
|
659
|
+
* Follows the same pattern as copyIloomSettings()
|
|
660
|
+
* @param worktreePath Path to the worktree
|
|
661
|
+
*/
|
|
662
|
+
async copyClaudeSettings(worktreePath) {
|
|
663
|
+
const mainClaudeSettingsPath = path2.join(process.cwd(), ".claude", "settings.local.json");
|
|
664
|
+
try {
|
|
665
|
+
const worktreeClaudeDir = path2.join(worktreePath, ".claude");
|
|
666
|
+
await fs2.ensureDir(worktreeClaudeDir);
|
|
667
|
+
const worktreeClaudeSettingsPath = path2.join(worktreeClaudeDir, "settings.local.json");
|
|
668
|
+
if (await fs2.pathExists(worktreeClaudeSettingsPath)) {
|
|
669
|
+
getLogger().debug(".claude/settings.local.json already exists in worktree, skipping copy");
|
|
670
|
+
} else {
|
|
671
|
+
await this.environment.copyIfExists(mainClaudeSettingsPath, worktreeClaudeSettingsPath);
|
|
821
672
|
}
|
|
822
673
|
} catch (error) {
|
|
823
|
-
|
|
674
|
+
getLogger().warn(`Warning: Failed to copy .claude/settings.local.json: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
824
675
|
}
|
|
825
676
|
}
|
|
826
677
|
/**
|
|
@@ -828,7 +679,7 @@ var LoomManager = class {
|
|
|
828
679
|
* Only called when project has web capabilities
|
|
829
680
|
*/
|
|
830
681
|
async setupPortForWeb(worktreePath, input, basePort) {
|
|
831
|
-
const envFilePath =
|
|
682
|
+
const envFilePath = path2.join(worktreePath, ".env.local");
|
|
832
683
|
const options = { basePort };
|
|
833
684
|
if (input.type === "issue") {
|
|
834
685
|
options.issueNumber = input.identifier;
|
|
@@ -848,11 +699,15 @@ var LoomManager = class {
|
|
|
848
699
|
loadMainEnvFile() {
|
|
849
700
|
const result = loadEnvIntoProcess({ path: process.cwd() });
|
|
850
701
|
if (result.error) {
|
|
851
|
-
|
|
702
|
+
if (isNoEnvFilesFoundError(result.error)) {
|
|
703
|
+
getLogger().debug("No .env files found (this is normal for projects without environment files)");
|
|
704
|
+
} else {
|
|
705
|
+
getLogger().warn(`Warning: Could not load .env files: ${result.error.message}`);
|
|
706
|
+
}
|
|
852
707
|
} else {
|
|
853
|
-
|
|
708
|
+
getLogger().info("Loaded environment variables using dotenv-flow");
|
|
854
709
|
if (result.parsed && Object.keys(result.parsed).length > 0) {
|
|
855
|
-
|
|
710
|
+
getLogger().debug(`Loaded ${Object.keys(result.parsed).length} environment variables`);
|
|
856
711
|
}
|
|
857
712
|
}
|
|
858
713
|
}
|
|
@@ -871,8 +726,12 @@ var LoomManager = class {
|
|
|
871
726
|
var _a, _b;
|
|
872
727
|
const settingsData = await this.settings.loadSettings();
|
|
873
728
|
const basePort = ((_b = (_a = settingsData.capabilities) == null ? void 0 : _a.web) == null ? void 0 : _b.basePort) ?? 3e3;
|
|
874
|
-
if (input.type === "issue"
|
|
875
|
-
|
|
729
|
+
if (input.type === "issue") {
|
|
730
|
+
if (typeof input.identifier === "number") {
|
|
731
|
+
return this.environment.calculatePort({ basePort, issueNumber: input.identifier });
|
|
732
|
+
} else if (typeof input.identifier === "string") {
|
|
733
|
+
return this.environment.calculatePort({ basePort, branchName: input.identifier });
|
|
734
|
+
}
|
|
876
735
|
}
|
|
877
736
|
if (input.type === "pr" && typeof input.identifier === "number") {
|
|
878
737
|
return this.environment.calculatePort({ basePort, prNumber: input.identifier });
|
|
@@ -880,7 +739,7 @@ var LoomManager = class {
|
|
|
880
739
|
if (input.type === "branch" && typeof input.identifier === "string") {
|
|
881
740
|
return this.environment.calculatePort({ basePort, branchName: input.identifier });
|
|
882
741
|
}
|
|
883
|
-
throw new Error(`Unknown input type: ${input.type}`);
|
|
742
|
+
throw new Error(`Unknown input type: ${input.type} with identifier type: ${typeof input.identifier}`);
|
|
884
743
|
}
|
|
885
744
|
/**
|
|
886
745
|
* Apply color synchronization to both VSCode and terminal
|
|
@@ -898,34 +757,56 @@ var LoomManager = class {
|
|
|
898
757
|
const colorVscode = (options == null ? void 0 : options.colorVscode) ?? ((_a = settings.colors) == null ? void 0 : _a.vscode) ?? false;
|
|
899
758
|
const colorTerminal = (options == null ? void 0 : options.colorTerminal) ?? ((_b = settings.colors) == null ? void 0 : _b.terminal) ?? true;
|
|
900
759
|
if (!colorVscode && !colorTerminal) {
|
|
901
|
-
|
|
760
|
+
getLogger().debug("Color synchronization disabled for both VSCode and terminal");
|
|
902
761
|
return;
|
|
903
762
|
}
|
|
904
763
|
if (colorVscode) {
|
|
905
764
|
const vscode = new VSCodeIntegration();
|
|
906
765
|
await vscode.setTitleBarColor(worktreePath, colorData.hex);
|
|
907
|
-
|
|
766
|
+
getLogger().info(`Applied VSCode title bar color: ${colorData.hex} for branch: ${branchName}`);
|
|
908
767
|
} else {
|
|
909
|
-
|
|
768
|
+
getLogger().debug("VSCode color sync disabled (default: false for safety)");
|
|
910
769
|
}
|
|
911
770
|
}
|
|
912
771
|
/**
|
|
913
772
|
* Map worktrees to loom objects
|
|
914
|
-
*
|
|
915
|
-
* Now reads metadata from MetadataManager (spec section 3.2)
|
|
773
|
+
* Reads loom metadata from MetadataManager with branch name parsing as fallback
|
|
916
774
|
*/
|
|
917
775
|
async mapWorktreesToLooms(worktrees) {
|
|
918
776
|
return await Promise.all(worktrees.map(async (wt) => {
|
|
777
|
+
var _a, _b;
|
|
778
|
+
const loomMetadata = await this.metadataManager.readMetadata(wt.path);
|
|
919
779
|
let type = "branch";
|
|
920
780
|
let identifier = wt.branch;
|
|
921
|
-
if (
|
|
922
|
-
type =
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
781
|
+
if (loomMetadata == null ? void 0 : loomMetadata.issueType) {
|
|
782
|
+
type = loomMetadata.issueType;
|
|
783
|
+
if (type === "issue" && ((_a = loomMetadata.issue_numbers) == null ? void 0 : _a[0])) {
|
|
784
|
+
const issueId = loomMetadata.issue_numbers[0];
|
|
785
|
+
const numericId = parseInt(issueId, 10);
|
|
786
|
+
identifier = isNaN(numericId) ? issueId : numericId;
|
|
787
|
+
} else if (type === "pr" && ((_b = loomMetadata.pr_numbers) == null ? void 0 : _b[0])) {
|
|
788
|
+
const prId = loomMetadata.pr_numbers[0];
|
|
789
|
+
identifier = parseInt(prId, 10);
|
|
790
|
+
} else if (type === "branch") {
|
|
791
|
+
identifier = wt.branch;
|
|
792
|
+
}
|
|
793
|
+
} else {
|
|
794
|
+
const prNumber = extractPRNumber(wt.branch);
|
|
795
|
+
if (prNumber !== null) {
|
|
796
|
+
type = "pr";
|
|
797
|
+
identifier = prNumber;
|
|
798
|
+
} else {
|
|
799
|
+
const issueNumber = extractIssueNumber(wt.branch);
|
|
800
|
+
if (issueNumber !== null) {
|
|
801
|
+
type = "issue";
|
|
802
|
+
const numericId = parseInt(issueNumber, 10);
|
|
803
|
+
identifier = isNaN(numericId) ? issueNumber : numericId;
|
|
804
|
+
} else {
|
|
805
|
+
type = "branch";
|
|
806
|
+
identifier = wt.branch;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
927
809
|
}
|
|
928
|
-
const loomMetadata = await this.metadataManager.readMetadata(wt.path);
|
|
929
810
|
return {
|
|
930
811
|
id: `${type}-${identifier}`,
|
|
931
812
|
path: wt.path,
|
|
@@ -969,41 +850,42 @@ var LoomManager = class {
|
|
|
969
850
|
const { capabilities, binEntries } = await this.capabilityDetector.detectCapabilities(worktreePath);
|
|
970
851
|
await this.copyEnvironmentFiles(worktreePath);
|
|
971
852
|
await this.copyIloomSettings(worktreePath);
|
|
853
|
+
await this.copyClaudeSettings(worktreePath);
|
|
972
854
|
const settingsData = await this.settings.loadSettings();
|
|
973
855
|
const basePort = ((_b = (_a = settingsData.capabilities) == null ? void 0 : _a.web) == null ? void 0 : _b.basePort) ?? 3e3;
|
|
974
856
|
let port = basePort;
|
|
975
857
|
if (capabilities.includes("web")) {
|
|
976
858
|
port = await this.setupPortForWeb(worktreePath, input, basePort);
|
|
977
859
|
}
|
|
978
|
-
|
|
860
|
+
getLogger().info("Database branch assumed to be already configured for existing worktree");
|
|
979
861
|
const databaseBranch = void 0;
|
|
980
862
|
const existingMetadata = await this.metadataManager.readMetadata(worktreePath);
|
|
981
863
|
let colorHex;
|
|
982
864
|
if (existingMetadata == null ? void 0 : existingMetadata.colorHex) {
|
|
983
865
|
colorHex = existingMetadata.colorHex;
|
|
984
|
-
|
|
866
|
+
getLogger().debug(`Reusing stored color ${colorHex} for branch ${branchName}`);
|
|
985
867
|
} else {
|
|
986
868
|
const colorData = generateColorFromBranchName(branchName);
|
|
987
869
|
colorHex = colorData.hex;
|
|
988
|
-
|
|
870
|
+
getLogger().debug(`No stored color, using hash-based color ${colorHex} for branch ${branchName}`);
|
|
989
871
|
}
|
|
990
872
|
try {
|
|
991
873
|
const colorData = { hex: colorHex, rgb: hexToRgb(colorHex), index: 0 };
|
|
992
874
|
await this.applyColorSynchronization(worktreePath, branchName, colorData, settingsData, input.options);
|
|
993
875
|
} catch (error) {
|
|
994
|
-
|
|
876
|
+
getLogger().warn(
|
|
995
877
|
`Failed to apply color synchronization: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
996
878
|
error
|
|
997
879
|
);
|
|
998
880
|
}
|
|
999
881
|
if (input.type === "issue") {
|
|
1000
882
|
try {
|
|
1001
|
-
|
|
883
|
+
getLogger().info("Moving issue to In Progress...");
|
|
1002
884
|
if (this.issueTracker.moveIssueToInProgress) {
|
|
1003
885
|
await this.issueTracker.moveIssueToInProgress(input.identifier);
|
|
1004
886
|
}
|
|
1005
887
|
} catch (error) {
|
|
1006
|
-
|
|
888
|
+
getLogger().warn(
|
|
1007
889
|
`Failed to move issue to In Progress: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1008
890
|
error
|
|
1009
891
|
);
|
|
@@ -1017,9 +899,9 @@ var LoomManager = class {
|
|
|
1017
899
|
const setArguments = (_h = input.options) == null ? void 0 : _h.setArguments;
|
|
1018
900
|
const executablePath = (_i = input.options) == null ? void 0 : _i.executablePath;
|
|
1019
901
|
if (enableClaude || enableCode || enableDevServer || enableTerminal) {
|
|
1020
|
-
|
|
1021
|
-
const { LoomLauncher } = await import("./LoomLauncher-
|
|
1022
|
-
const { ClaudeContextManager } = await import("./ClaudeContextManager-
|
|
902
|
+
getLogger().info("Launching workspace components...");
|
|
903
|
+
const { LoomLauncher } = await import("./LoomLauncher-S3YGJRJQ.js");
|
|
904
|
+
const { ClaudeContextManager } = await import("./ClaudeContextManager-DK77227F.js");
|
|
1023
905
|
const claudeContext = new ClaudeContextManager(void 0, void 0, this.settings);
|
|
1024
906
|
const launcher = new LoomLauncher(claudeContext, this.settings);
|
|
1025
907
|
await launcher.launchLoom({
|
|
@@ -1046,6 +928,7 @@ var LoomManager = class {
|
|
|
1046
928
|
if (!existingMetadata) {
|
|
1047
929
|
const issue_numbers = input.type === "issue" ? [String(input.identifier)] : [];
|
|
1048
930
|
const pr_numbers = input.type === "pr" ? [String(input.identifier)] : [];
|
|
931
|
+
const sessionId = generateDeterministicSessionId(worktreePath);
|
|
1049
932
|
const metadataInput = {
|
|
1050
933
|
description,
|
|
1051
934
|
branchName,
|
|
@@ -1054,7 +937,10 @@ var LoomManager = class {
|
|
|
1054
937
|
issue_numbers,
|
|
1055
938
|
pr_numbers,
|
|
1056
939
|
issueTracker: this.issueTracker.providerName,
|
|
1057
|
-
colorHex
|
|
940
|
+
colorHex,
|
|
941
|
+
sessionId,
|
|
942
|
+
projectPath: this.gitWorktree.workingDirectory,
|
|
943
|
+
...input.parentLoom && { parentLoom: input.parentLoom }
|
|
1058
944
|
};
|
|
1059
945
|
await this.metadataManager.writeMetadata(worktreePath, metadataInput);
|
|
1060
946
|
}
|
|
@@ -1081,14 +967,13 @@ var LoomManager = class {
|
|
|
1081
967
|
}
|
|
1082
968
|
}
|
|
1083
969
|
};
|
|
1084
|
-
|
|
970
|
+
getLogger().success(`Reused existing loom: ${loom.id} at ${loom.path}`);
|
|
1085
971
|
return loom;
|
|
1086
972
|
}
|
|
1087
973
|
};
|
|
1088
974
|
|
|
1089
975
|
// src/lib/EnvironmentManager.ts
|
|
1090
|
-
import
|
|
1091
|
-
var logger2 = createLogger({ prefix: "\u{1F4DD}" });
|
|
976
|
+
import fs3 from "fs-extra";
|
|
1092
977
|
var EnvironmentManager = class {
|
|
1093
978
|
constructor() {
|
|
1094
979
|
this.backupSuffix = ".backup";
|
|
@@ -1103,15 +988,15 @@ var EnvironmentManager = class {
|
|
|
1103
988
|
if (!validation.valid) {
|
|
1104
989
|
throw new Error(validation.error ?? "Invalid variable name");
|
|
1105
990
|
}
|
|
1106
|
-
const fileExists = await
|
|
991
|
+
const fileExists = await fs3.pathExists(filePath);
|
|
1107
992
|
if (!fileExists) {
|
|
1108
|
-
|
|
993
|
+
getLogger().info(`Creating ${filePath} with ${key}...`);
|
|
1109
994
|
const content = formatEnvLine(key, value);
|
|
1110
|
-
await
|
|
1111
|
-
|
|
995
|
+
await fs3.writeFile(filePath, content, "utf8");
|
|
996
|
+
getLogger().success(`${filePath} created with ${key}`);
|
|
1112
997
|
return;
|
|
1113
998
|
}
|
|
1114
|
-
const existingContent = await
|
|
999
|
+
const existingContent = await fs3.readFile(filePath, "utf8");
|
|
1115
1000
|
const envMap = parseEnvFile(existingContent);
|
|
1116
1001
|
let backupPath;
|
|
1117
1002
|
if (backup) {
|
|
@@ -1140,15 +1025,15 @@ var EnvironmentManager = class {
|
|
|
1140
1025
|
newLines.push(line);
|
|
1141
1026
|
}
|
|
1142
1027
|
if (!variableUpdated) {
|
|
1143
|
-
|
|
1028
|
+
getLogger().info(`Adding ${key} to ${filePath}...`);
|
|
1144
1029
|
newLines.push(formatEnvLine(key, value));
|
|
1145
|
-
|
|
1030
|
+
getLogger().success(`${key} added successfully`);
|
|
1146
1031
|
} else {
|
|
1147
|
-
|
|
1148
|
-
|
|
1032
|
+
getLogger().info(`Updating ${key} in ${filePath}...`);
|
|
1033
|
+
getLogger().success(`${key} updated successfully`);
|
|
1149
1034
|
}
|
|
1150
1035
|
const newContent = newLines.join("\n");
|
|
1151
|
-
await
|
|
1036
|
+
await fs3.writeFile(filePath, newContent, "utf8");
|
|
1152
1037
|
return backupPath;
|
|
1153
1038
|
}
|
|
1154
1039
|
/**
|
|
@@ -1156,10 +1041,10 @@ var EnvironmentManager = class {
|
|
|
1156
1041
|
*/
|
|
1157
1042
|
async readEnvFile(filePath) {
|
|
1158
1043
|
try {
|
|
1159
|
-
const content = await
|
|
1044
|
+
const content = await fs3.readFile(filePath, "utf8");
|
|
1160
1045
|
return parseEnvFile(content);
|
|
1161
1046
|
} catch (error) {
|
|
1162
|
-
|
|
1047
|
+
getLogger().debug(
|
|
1163
1048
|
`Could not read env file ${filePath}: ${error instanceof Error ? error.message : String(error)}`
|
|
1164
1049
|
);
|
|
1165
1050
|
return /* @__PURE__ */ new Map();
|
|
@@ -1179,13 +1064,13 @@ var EnvironmentManager = class {
|
|
|
1179
1064
|
* @private
|
|
1180
1065
|
*/
|
|
1181
1066
|
async copyIfExists(source, destination) {
|
|
1182
|
-
const sourceExists = await
|
|
1067
|
+
const sourceExists = await fs3.pathExists(source);
|
|
1183
1068
|
if (!sourceExists) {
|
|
1184
|
-
|
|
1069
|
+
getLogger().debug(`Source file ${source} does not exist, skipping copy`);
|
|
1185
1070
|
return;
|
|
1186
1071
|
}
|
|
1187
|
-
await
|
|
1188
|
-
|
|
1072
|
+
await fs3.copy(source, destination, { overwrite: false });
|
|
1073
|
+
getLogger().success(`Copied ${source} to ${destination}`);
|
|
1189
1074
|
}
|
|
1190
1075
|
/**
|
|
1191
1076
|
* Calculate unique port for workspace
|
|
@@ -1245,7 +1130,7 @@ var EnvironmentManager = class {
|
|
|
1245
1130
|
*/
|
|
1246
1131
|
async validateEnvFile(filePath) {
|
|
1247
1132
|
try {
|
|
1248
|
-
const content = await
|
|
1133
|
+
const content = await fs3.readFile(filePath, "utf8");
|
|
1249
1134
|
const envMap = parseEnvFile(content);
|
|
1250
1135
|
const errors = [];
|
|
1251
1136
|
for (const [key, value] of envMap.entries()) {
|
|
@@ -1273,19 +1158,19 @@ var EnvironmentManager = class {
|
|
|
1273
1158
|
async createBackup(filePath) {
|
|
1274
1159
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1275
1160
|
const backupPath = `${filePath}${this.backupSuffix}-${timestamp}`;
|
|
1276
|
-
await
|
|
1277
|
-
|
|
1161
|
+
await fs3.copy(filePath, backupPath);
|
|
1162
|
+
getLogger().debug(`Created backup at ${backupPath}`);
|
|
1278
1163
|
return backupPath;
|
|
1279
1164
|
}
|
|
1280
1165
|
};
|
|
1281
1166
|
|
|
1282
1167
|
// src/lib/CLIIsolationManager.ts
|
|
1283
|
-
import
|
|
1284
|
-
import
|
|
1285
|
-
import
|
|
1168
|
+
import fs4 from "fs-extra";
|
|
1169
|
+
import path3 from "path";
|
|
1170
|
+
import os from "os";
|
|
1286
1171
|
var CLIIsolationManager = class {
|
|
1287
1172
|
constructor() {
|
|
1288
|
-
this.iloomBinDir =
|
|
1173
|
+
this.iloomBinDir = path3.join(os.homedir(), ".iloom", "bin");
|
|
1289
1174
|
}
|
|
1290
1175
|
/**
|
|
1291
1176
|
* Setup CLI isolation for a worktree
|
|
@@ -1300,7 +1185,7 @@ var CLIIsolationManager = class {
|
|
|
1300
1185
|
async setupCLIIsolation(worktreePath, identifier, binEntries) {
|
|
1301
1186
|
await this.buildProject(worktreePath);
|
|
1302
1187
|
await this.verifyBinTargets(worktreePath, binEntries);
|
|
1303
|
-
await
|
|
1188
|
+
await fs4.ensureDir(this.iloomBinDir);
|
|
1304
1189
|
const symlinkNames = await this.createVersionedSymlinks(
|
|
1305
1190
|
worktreePath,
|
|
1306
1191
|
identifier,
|
|
@@ -1316,12 +1201,12 @@ var CLIIsolationManager = class {
|
|
|
1316
1201
|
async buildProject(worktreePath) {
|
|
1317
1202
|
const pkgJson = await readPackageJson(worktreePath);
|
|
1318
1203
|
if (!hasScript(pkgJson, "build")) {
|
|
1319
|
-
|
|
1204
|
+
getLogger().warn("No build script found in package.json - skipping build");
|
|
1320
1205
|
return;
|
|
1321
1206
|
}
|
|
1322
|
-
|
|
1207
|
+
getLogger().info("Building CLI tool...");
|
|
1323
1208
|
await runScript("build", worktreePath, [], { quiet: true });
|
|
1324
|
-
|
|
1209
|
+
getLogger().success("Build completed");
|
|
1325
1210
|
}
|
|
1326
1211
|
/**
|
|
1327
1212
|
* Verify bin targets exist and are executable
|
|
@@ -1330,13 +1215,13 @@ var CLIIsolationManager = class {
|
|
|
1330
1215
|
*/
|
|
1331
1216
|
async verifyBinTargets(worktreePath, binEntries) {
|
|
1332
1217
|
for (const binPath of Object.values(binEntries)) {
|
|
1333
|
-
const targetPath =
|
|
1334
|
-
const exists = await
|
|
1218
|
+
const targetPath = path3.resolve(worktreePath, binPath);
|
|
1219
|
+
const exists = await fs4.pathExists(targetPath);
|
|
1335
1220
|
if (!exists) {
|
|
1336
1221
|
throw new Error(`Bin target does not exist: ${targetPath}`);
|
|
1337
1222
|
}
|
|
1338
1223
|
try {
|
|
1339
|
-
await
|
|
1224
|
+
await fs4.access(targetPath, fs4.constants.X_OK);
|
|
1340
1225
|
} catch {
|
|
1341
1226
|
}
|
|
1342
1227
|
}
|
|
@@ -1352,10 +1237,10 @@ var CLIIsolationManager = class {
|
|
|
1352
1237
|
const symlinkNames = [];
|
|
1353
1238
|
for (const [binName, binPath] of Object.entries(binEntries)) {
|
|
1354
1239
|
const versionedName = `${binName}-${identifier}`;
|
|
1355
|
-
const targetPath =
|
|
1356
|
-
const symlinkPath =
|
|
1357
|
-
await
|
|
1358
|
-
|
|
1240
|
+
const targetPath = path3.resolve(worktreePath, binPath);
|
|
1241
|
+
const symlinkPath = path3.join(this.iloomBinDir, versionedName);
|
|
1242
|
+
await fs4.symlink(targetPath, symlinkPath);
|
|
1243
|
+
getLogger().success(`CLI available: ${versionedName}`);
|
|
1359
1244
|
symlinkNames.push(versionedName);
|
|
1360
1245
|
}
|
|
1361
1246
|
return symlinkNames;
|
|
@@ -1370,10 +1255,10 @@ var CLIIsolationManager = class {
|
|
|
1370
1255
|
}
|
|
1371
1256
|
const shell = this.detectShell();
|
|
1372
1257
|
const rcFile = this.getShellRcFile(shell);
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1258
|
+
getLogger().warn("\n\u26A0\uFE0F One-time PATH setup required:");
|
|
1259
|
+
getLogger().warn(` Add to ${rcFile}:`);
|
|
1260
|
+
getLogger().warn(` export PATH="$HOME/.iloom/bin:$PATH"`);
|
|
1261
|
+
getLogger().warn(` Then run: source ${rcFile}
|
|
1377
1262
|
`);
|
|
1378
1263
|
}
|
|
1379
1264
|
/**
|
|
@@ -1407,12 +1292,12 @@ var CLIIsolationManager = class {
|
|
|
1407
1292
|
async cleanupVersionedExecutables(identifier) {
|
|
1408
1293
|
const removed = [];
|
|
1409
1294
|
try {
|
|
1410
|
-
const files = await
|
|
1295
|
+
const files = await fs4.readdir(this.iloomBinDir);
|
|
1411
1296
|
for (const file of files) {
|
|
1412
1297
|
if (this.matchesIdentifier(file, identifier)) {
|
|
1413
|
-
const symlinkPath =
|
|
1298
|
+
const symlinkPath = path3.join(this.iloomBinDir, file);
|
|
1414
1299
|
try {
|
|
1415
|
-
await
|
|
1300
|
+
await fs4.unlink(symlinkPath);
|
|
1416
1301
|
removed.push(file);
|
|
1417
1302
|
} catch (error) {
|
|
1418
1303
|
const isEnoent = error && typeof error === "object" && "code" in error && error.code === "ENOENT";
|
|
@@ -1420,7 +1305,7 @@ var CLIIsolationManager = class {
|
|
|
1420
1305
|
removed.push(file);
|
|
1421
1306
|
continue;
|
|
1422
1307
|
}
|
|
1423
|
-
|
|
1308
|
+
getLogger().warn(
|
|
1424
1309
|
`Failed to remove symlink ${file}: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1425
1310
|
);
|
|
1426
1311
|
}
|
|
@@ -1429,13 +1314,13 @@ var CLIIsolationManager = class {
|
|
|
1429
1314
|
} catch (error) {
|
|
1430
1315
|
const isEnoent = error && typeof error === "object" && "code" in error && error.code === "ENOENT";
|
|
1431
1316
|
if (isEnoent) {
|
|
1432
|
-
|
|
1317
|
+
getLogger().warn("No CLI executables directory found - nothing to cleanup");
|
|
1433
1318
|
return [];
|
|
1434
1319
|
}
|
|
1435
1320
|
throw error;
|
|
1436
1321
|
}
|
|
1437
1322
|
if (removed.length > 0) {
|
|
1438
|
-
|
|
1323
|
+
getLogger().success(`Removed CLI executables: ${removed.join(", ")}`);
|
|
1439
1324
|
}
|
|
1440
1325
|
return removed;
|
|
1441
1326
|
}
|
|
@@ -1448,15 +1333,15 @@ var CLIIsolationManager = class {
|
|
|
1448
1333
|
async findOrphanedSymlinks() {
|
|
1449
1334
|
const orphaned = [];
|
|
1450
1335
|
try {
|
|
1451
|
-
const files = await
|
|
1336
|
+
const files = await fs4.readdir(this.iloomBinDir);
|
|
1452
1337
|
for (const file of files) {
|
|
1453
|
-
const symlinkPath =
|
|
1338
|
+
const symlinkPath = path3.join(this.iloomBinDir, file);
|
|
1454
1339
|
try {
|
|
1455
|
-
const stats = await
|
|
1340
|
+
const stats = await fs4.lstat(symlinkPath);
|
|
1456
1341
|
if (stats.isSymbolicLink()) {
|
|
1457
|
-
const target = await
|
|
1342
|
+
const target = await fs4.readlink(symlinkPath);
|
|
1458
1343
|
try {
|
|
1459
|
-
await
|
|
1344
|
+
await fs4.access(target);
|
|
1460
1345
|
} catch {
|
|
1461
1346
|
orphaned.push({
|
|
1462
1347
|
name: file,
|
|
@@ -1466,7 +1351,7 @@ var CLIIsolationManager = class {
|
|
|
1466
1351
|
}
|
|
1467
1352
|
}
|
|
1468
1353
|
} catch (error) {
|
|
1469
|
-
|
|
1354
|
+
getLogger().warn(
|
|
1470
1355
|
`Failed to check symlink ${file}: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1471
1356
|
);
|
|
1472
1357
|
}
|
|
@@ -1491,11 +1376,11 @@ var CLIIsolationManager = class {
|
|
|
1491
1376
|
let removedCount = 0;
|
|
1492
1377
|
for (const symlink of orphaned) {
|
|
1493
1378
|
try {
|
|
1494
|
-
await
|
|
1379
|
+
await fs4.unlink(symlink.path);
|
|
1495
1380
|
removedCount++;
|
|
1496
|
-
|
|
1381
|
+
getLogger().success(`Removed orphaned symlink: ${symlink.name}`);
|
|
1497
1382
|
} catch (error) {
|
|
1498
|
-
|
|
1383
|
+
getLogger().warn(
|
|
1499
1384
|
`Failed to remove orphaned symlink ${symlink.name}: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1500
1385
|
);
|
|
1501
1386
|
}
|
|
@@ -1517,17 +1402,16 @@ var CLIIsolationManager = class {
|
|
|
1517
1402
|
};
|
|
1518
1403
|
|
|
1519
1404
|
// src/lib/DatabaseManager.ts
|
|
1520
|
-
import
|
|
1521
|
-
var logger3 = createLogger({ prefix: "\u{1F5C2}\uFE0F" });
|
|
1405
|
+
import fs5 from "fs-extra";
|
|
1522
1406
|
var DatabaseManager = class {
|
|
1523
1407
|
constructor(provider, environment, databaseUrlEnvVarName = "DATABASE_URL") {
|
|
1524
1408
|
this.provider = provider;
|
|
1525
1409
|
this.environment = environment;
|
|
1526
1410
|
this.databaseUrlEnvVarName = databaseUrlEnvVarName;
|
|
1527
1411
|
if (databaseUrlEnvVarName !== "DATABASE_URL") {
|
|
1528
|
-
|
|
1412
|
+
getLogger().debug(`DatabaseManager configured with custom variable: ${databaseUrlEnvVarName}`);
|
|
1529
1413
|
} else {
|
|
1530
|
-
|
|
1414
|
+
getLogger().debug("DatabaseManager using default variable: DATABASE_URL");
|
|
1531
1415
|
}
|
|
1532
1416
|
}
|
|
1533
1417
|
/**
|
|
@@ -1544,12 +1428,12 @@ var DatabaseManager = class {
|
|
|
1544
1428
|
*/
|
|
1545
1429
|
async shouldUseDatabaseBranching(workspacePath) {
|
|
1546
1430
|
if (!this.provider.isConfigured()) {
|
|
1547
|
-
|
|
1431
|
+
getLogger().debug("Skipping database branching: Database provider not configured");
|
|
1548
1432
|
return false;
|
|
1549
1433
|
}
|
|
1550
1434
|
const hasDatabaseUrl = await this.hasDatabaseUrlInEnv(workspacePath);
|
|
1551
1435
|
if (!hasDatabaseUrl) {
|
|
1552
|
-
|
|
1436
|
+
getLogger().debug(
|
|
1553
1437
|
"Skipping database branching: configured database URL variable not found in any env file"
|
|
1554
1438
|
);
|
|
1555
1439
|
return false;
|
|
@@ -1570,28 +1454,28 @@ var DatabaseManager = class {
|
|
|
1570
1454
|
return null;
|
|
1571
1455
|
}
|
|
1572
1456
|
if (!await this.provider.isCliAvailable()) {
|
|
1573
|
-
|
|
1574
|
-
|
|
1457
|
+
getLogger().warn("Skipping database branch creation: Neon CLI not available");
|
|
1458
|
+
getLogger().warn("Install with: npm install -g neonctl");
|
|
1575
1459
|
return null;
|
|
1576
1460
|
}
|
|
1577
1461
|
try {
|
|
1578
1462
|
const isAuth = await this.provider.isAuthenticated(cwd);
|
|
1579
1463
|
if (!isAuth) {
|
|
1580
|
-
|
|
1581
|
-
|
|
1464
|
+
getLogger().warn("Skipping database branch creation: Not authenticated with Neon CLI");
|
|
1465
|
+
getLogger().warn("Run: neon auth");
|
|
1582
1466
|
return null;
|
|
1583
1467
|
}
|
|
1584
1468
|
} catch (error) {
|
|
1585
1469
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1586
|
-
|
|
1470
|
+
getLogger().error(`Database authentication check failed: ${errorMessage}`);
|
|
1587
1471
|
throw error;
|
|
1588
1472
|
}
|
|
1589
1473
|
try {
|
|
1590
1474
|
const connectionString = await this.provider.createBranch(branchName, fromBranch, cwd);
|
|
1591
|
-
|
|
1475
|
+
getLogger().success(`Database branch ready: ${this.provider.sanitizeBranchName(branchName)}`);
|
|
1592
1476
|
return connectionString;
|
|
1593
1477
|
} catch (error) {
|
|
1594
|
-
|
|
1478
|
+
getLogger().error(
|
|
1595
1479
|
`Failed to create database branch: ${error instanceof Error ? error.message : String(error)}`
|
|
1596
1480
|
);
|
|
1597
1481
|
throw error;
|
|
@@ -1617,7 +1501,7 @@ var DatabaseManager = class {
|
|
|
1617
1501
|
};
|
|
1618
1502
|
}
|
|
1619
1503
|
if (!this.provider.isConfigured()) {
|
|
1620
|
-
|
|
1504
|
+
getLogger().debug("Skipping database branch deletion: Database provider not configured");
|
|
1621
1505
|
return {
|
|
1622
1506
|
success: true,
|
|
1623
1507
|
deleted: false,
|
|
@@ -1626,7 +1510,7 @@ var DatabaseManager = class {
|
|
|
1626
1510
|
};
|
|
1627
1511
|
}
|
|
1628
1512
|
if (!await this.provider.isCliAvailable()) {
|
|
1629
|
-
|
|
1513
|
+
getLogger().info("Skipping database branch deletion: CLI tool not available");
|
|
1630
1514
|
return {
|
|
1631
1515
|
success: false,
|
|
1632
1516
|
deleted: false,
|
|
@@ -1638,7 +1522,7 @@ var DatabaseManager = class {
|
|
|
1638
1522
|
try {
|
|
1639
1523
|
const isAuth = await this.provider.isAuthenticated(cwd);
|
|
1640
1524
|
if (!isAuth) {
|
|
1641
|
-
|
|
1525
|
+
getLogger().warn("Skipping database branch deletion: Not authenticated with DB Provider");
|
|
1642
1526
|
return {
|
|
1643
1527
|
success: false,
|
|
1644
1528
|
deleted: false,
|
|
@@ -1649,7 +1533,7 @@ var DatabaseManager = class {
|
|
|
1649
1533
|
}
|
|
1650
1534
|
} catch (error) {
|
|
1651
1535
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1652
|
-
|
|
1536
|
+
getLogger().error(`Database authentication check failed: ${errorMessage}`);
|
|
1653
1537
|
return {
|
|
1654
1538
|
success: false,
|
|
1655
1539
|
deleted: false,
|
|
@@ -1662,7 +1546,7 @@ var DatabaseManager = class {
|
|
|
1662
1546
|
const result = await this.provider.deleteBranch(branchName, isPreview, cwd);
|
|
1663
1547
|
return result;
|
|
1664
1548
|
} catch (error) {
|
|
1665
|
-
|
|
1549
|
+
getLogger().warn(
|
|
1666
1550
|
`Unexpected error in database deletion: ${error instanceof Error ? error.message : String(error)}`
|
|
1667
1551
|
);
|
|
1668
1552
|
return {
|
|
@@ -1683,13 +1567,13 @@ var DatabaseManager = class {
|
|
|
1683
1567
|
*/
|
|
1684
1568
|
async getBranchNameFromConnectionString(connectionString, cwd) {
|
|
1685
1569
|
if (!this.provider.isConfigured()) {
|
|
1686
|
-
|
|
1570
|
+
getLogger().debug("Provider not configured, skipping reverse lookup");
|
|
1687
1571
|
return null;
|
|
1688
1572
|
}
|
|
1689
1573
|
if ("getBranchNameFromConnectionString" in this.provider && typeof this.provider.getBranchNameFromConnectionString === "function") {
|
|
1690
1574
|
return this.provider.getBranchNameFromConnectionString(connectionString, cwd);
|
|
1691
1575
|
}
|
|
1692
|
-
|
|
1576
|
+
getLogger().debug("Provider does not support reverse lookup");
|
|
1693
1577
|
return null;
|
|
1694
1578
|
}
|
|
1695
1579
|
/**
|
|
@@ -1700,26 +1584,26 @@ var DatabaseManager = class {
|
|
|
1700
1584
|
async hasDatabaseUrlInEnv(workspacePath) {
|
|
1701
1585
|
try {
|
|
1702
1586
|
if (this.databaseUrlEnvVarName !== "DATABASE_URL") {
|
|
1703
|
-
|
|
1587
|
+
getLogger().debug(`Looking for custom database URL variable: ${this.databaseUrlEnvVarName}`);
|
|
1704
1588
|
} else {
|
|
1705
|
-
|
|
1589
|
+
getLogger().debug("Looking for default database URL variable: DATABASE_URL");
|
|
1706
1590
|
}
|
|
1707
1591
|
const hasConfiguredVar = await hasVariableInAnyEnvFile(
|
|
1708
1592
|
workspacePath,
|
|
1709
1593
|
this.databaseUrlEnvVarName,
|
|
1710
|
-
async (p) =>
|
|
1594
|
+
async (p) => fs5.pathExists(p),
|
|
1711
1595
|
async (p, v) => this.environment.getEnvVariable(p, v)
|
|
1712
1596
|
);
|
|
1713
1597
|
if (hasConfiguredVar) {
|
|
1714
1598
|
if (this.databaseUrlEnvVarName !== "DATABASE_URL") {
|
|
1715
|
-
|
|
1599
|
+
getLogger().debug(`\u2705 Found custom database URL variable: ${this.databaseUrlEnvVarName}`);
|
|
1716
1600
|
} else {
|
|
1717
|
-
|
|
1601
|
+
getLogger().debug(`\u2705 Found default database URL variable: DATABASE_URL`);
|
|
1718
1602
|
}
|
|
1719
1603
|
return true;
|
|
1720
1604
|
}
|
|
1721
1605
|
if (this.databaseUrlEnvVarName !== "DATABASE_URL") {
|
|
1722
|
-
|
|
1606
|
+
getLogger().debug(`\u274C Custom database URL variable '${this.databaseUrlEnvVarName}' not found in any env file`);
|
|
1723
1607
|
throw new Error(
|
|
1724
1608
|
`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
1609
|
);
|
|
@@ -1727,13 +1611,13 @@ var DatabaseManager = class {
|
|
|
1727
1611
|
const hasDefaultVar = await hasVariableInAnyEnvFile(
|
|
1728
1612
|
workspacePath,
|
|
1729
1613
|
"DATABASE_URL",
|
|
1730
|
-
async (p) =>
|
|
1614
|
+
async (p) => fs5.pathExists(p),
|
|
1731
1615
|
async (p, v) => this.environment.getEnvVariable(p, v)
|
|
1732
1616
|
);
|
|
1733
1617
|
if (hasDefaultVar) {
|
|
1734
|
-
|
|
1618
|
+
getLogger().debug("\u2705 Found fallback DATABASE_URL variable");
|
|
1735
1619
|
} else {
|
|
1736
|
-
|
|
1620
|
+
getLogger().debug("\u274C No DATABASE_URL variable found in any env file");
|
|
1737
1621
|
}
|
|
1738
1622
|
return hasDefaultVar;
|
|
1739
1623
|
} catch (error) {
|
|
@@ -1746,7 +1630,7 @@ var DatabaseManager = class {
|
|
|
1746
1630
|
};
|
|
1747
1631
|
|
|
1748
1632
|
// src/lib/ResourceCleanup.ts
|
|
1749
|
-
import
|
|
1633
|
+
import path4 from "path";
|
|
1750
1634
|
var ResourceCleanup = class {
|
|
1751
1635
|
constructor(gitWorktree, processManager, database, cliIsolation, settingsManager) {
|
|
1752
1636
|
this.gitWorktree = gitWorktree;
|
|
@@ -1768,7 +1652,7 @@ var ResourceCleanup = class {
|
|
|
1768
1652
|
const operations = [];
|
|
1769
1653
|
const errors = [];
|
|
1770
1654
|
const displayIdentifier = parsed.branchName ?? ((_a = parsed.number) == null ? void 0 : _a.toString()) ?? parsed.originalInput;
|
|
1771
|
-
|
|
1655
|
+
getLogger().info(`Starting cleanup for: ${displayIdentifier}`);
|
|
1772
1656
|
const number = parsed.number;
|
|
1773
1657
|
if (number !== void 0) {
|
|
1774
1658
|
const port = this.processManager.calculatePort(number);
|
|
@@ -1814,7 +1698,7 @@ var ResourceCleanup = class {
|
|
|
1814
1698
|
if (!worktree) {
|
|
1815
1699
|
throw new Error(`No worktree found for identifier: ${displayIdentifier}`);
|
|
1816
1700
|
}
|
|
1817
|
-
|
|
1701
|
+
getLogger().debug(`Found worktree: path="${worktree.path}", branch="${worktree.branch}"`);
|
|
1818
1702
|
} catch (error) {
|
|
1819
1703
|
const err = error instanceof Error ? error : new Error("Unknown error");
|
|
1820
1704
|
errors.push(err);
|
|
@@ -1827,7 +1711,9 @@ var ResourceCleanup = class {
|
|
|
1827
1711
|
};
|
|
1828
1712
|
}
|
|
1829
1713
|
if (!options.force) {
|
|
1830
|
-
const
|
|
1714
|
+
const shouldCheckMergeSafety = options.checkMergeSafety ?? options.deleteBranch === true;
|
|
1715
|
+
const shouldCheckRemoteBranch = options.checkRemoteBranch ?? false;
|
|
1716
|
+
const safety = await this.validateWorktreeSafety(worktree, parsed.originalInput, shouldCheckMergeSafety, shouldCheckRemoteBranch);
|
|
1831
1717
|
if (!safety.isSafe) {
|
|
1832
1718
|
const blockerMessage = safety.blockers.join("\n\n");
|
|
1833
1719
|
throw new Error(`Cannot cleanup:
|
|
@@ -1836,18 +1722,18 @@ ${blockerMessage}`);
|
|
|
1836
1722
|
}
|
|
1837
1723
|
if (safety.warnings.length > 0) {
|
|
1838
1724
|
safety.warnings.forEach((warning) => {
|
|
1839
|
-
|
|
1725
|
+
getLogger().warn(warning);
|
|
1840
1726
|
});
|
|
1841
1727
|
}
|
|
1842
1728
|
}
|
|
1843
1729
|
let databaseConfig = null;
|
|
1844
1730
|
if (!options.keepDatabase && worktree) {
|
|
1845
|
-
const envFilePath =
|
|
1731
|
+
const envFilePath = path4.join(worktree.path, ".env");
|
|
1846
1732
|
try {
|
|
1847
1733
|
const shouldCleanup = this.database ? await this.database.shouldUseDatabaseBranching(envFilePath) : false;
|
|
1848
1734
|
databaseConfig = { shouldCleanup, envFilePath };
|
|
1849
1735
|
} catch (error) {
|
|
1850
|
-
|
|
1736
|
+
getLogger().warn(
|
|
1851
1737
|
`Failed to read database config from ${envFilePath}, skipping database cleanup: ${error instanceof Error ? error.message : String(error)}`
|
|
1852
1738
|
);
|
|
1853
1739
|
databaseConfig = { shouldCleanup: false, envFilePath };
|
|
@@ -1858,11 +1744,25 @@ ${blockerMessage}`);
|
|
|
1858
1744
|
try {
|
|
1859
1745
|
mainWorktreePath = await findMainWorktreePathWithSettings(worktree.path, this.settingsManager);
|
|
1860
1746
|
} catch (error) {
|
|
1861
|
-
|
|
1747
|
+
getLogger().warn(
|
|
1862
1748
|
`Failed to find main worktree path: ${error instanceof Error ? error.message : String(error)}`
|
|
1863
1749
|
);
|
|
1864
1750
|
}
|
|
1865
1751
|
}
|
|
1752
|
+
let mergeTargetBranch = null;
|
|
1753
|
+
if (options.deleteBranch && worktree && !options.dryRun) {
|
|
1754
|
+
try {
|
|
1755
|
+
mergeTargetBranch = await getMergeTargetBranch(worktree.path, {
|
|
1756
|
+
settingsManager: this.settingsManager,
|
|
1757
|
+
metadataManager: this.metadataManager
|
|
1758
|
+
});
|
|
1759
|
+
getLogger().debug(`Pre-fetched merge target branch: ${mergeTargetBranch}`);
|
|
1760
|
+
} catch (error) {
|
|
1761
|
+
getLogger().warn(
|
|
1762
|
+
`Failed to pre-fetch merge target branch: ${error instanceof Error ? error.message : String(error)}`
|
|
1763
|
+
);
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1866
1766
|
if (options.dryRun) {
|
|
1867
1767
|
operations.push({
|
|
1868
1768
|
type: "worktree",
|
|
@@ -1886,7 +1786,7 @@ ${blockerMessage}`);
|
|
|
1886
1786
|
message: `Worktree removed: ${worktree.path}`
|
|
1887
1787
|
});
|
|
1888
1788
|
await this.metadataManager.deleteMetadata(worktree.path);
|
|
1889
|
-
|
|
1789
|
+
getLogger().debug(`Metadata file cleanup attempted for: ${worktree.path}`);
|
|
1890
1790
|
} catch (error) {
|
|
1891
1791
|
const err = error instanceof Error ? error : new Error("Unknown error");
|
|
1892
1792
|
errors.push(err);
|
|
@@ -1907,7 +1807,12 @@ ${blockerMessage}`);
|
|
|
1907
1807
|
});
|
|
1908
1808
|
} else {
|
|
1909
1809
|
try {
|
|
1910
|
-
const branchOptions = {
|
|
1810
|
+
const branchOptions = {
|
|
1811
|
+
dryRun: false
|
|
1812
|
+
};
|
|
1813
|
+
if (mergeTargetBranch !== null) {
|
|
1814
|
+
branchOptions.mergeTargetBranch = mergeTargetBranch;
|
|
1815
|
+
}
|
|
1911
1816
|
if (options.force !== void 0) {
|
|
1912
1817
|
branchOptions.force = options.force;
|
|
1913
1818
|
}
|
|
@@ -1948,7 +1853,7 @@ ${blockerMessage}`);
|
|
|
1948
1853
|
} catch (error) {
|
|
1949
1854
|
const err = error instanceof Error ? error : new Error("Unknown error");
|
|
1950
1855
|
errors.push(err);
|
|
1951
|
-
|
|
1856
|
+
getLogger().warn(
|
|
1952
1857
|
`CLI symlink cleanup failed: ${err.message}`
|
|
1953
1858
|
);
|
|
1954
1859
|
operations.push({
|
|
@@ -1978,7 +1883,7 @@ ${blockerMessage}`);
|
|
|
1978
1883
|
mainWorktreePath ?? void 0
|
|
1979
1884
|
);
|
|
1980
1885
|
if (deletionResult.deleted) {
|
|
1981
|
-
|
|
1886
|
+
getLogger().info(`Database branch deleted: ${worktree.branch}`);
|
|
1982
1887
|
operations.push({
|
|
1983
1888
|
type: "database",
|
|
1984
1889
|
success: true,
|
|
@@ -1986,7 +1891,7 @@ ${blockerMessage}`);
|
|
|
1986
1891
|
deleted: true
|
|
1987
1892
|
});
|
|
1988
1893
|
} else if (deletionResult.notFound) {
|
|
1989
|
-
|
|
1894
|
+
getLogger().debug(`No database branch found for: ${worktree.branch}`);
|
|
1990
1895
|
operations.push({
|
|
1991
1896
|
type: "database",
|
|
1992
1897
|
success: true,
|
|
@@ -1994,7 +1899,7 @@ ${blockerMessage}`);
|
|
|
1994
1899
|
deleted: false
|
|
1995
1900
|
});
|
|
1996
1901
|
} else if (deletionResult.userDeclined) {
|
|
1997
|
-
|
|
1902
|
+
getLogger().info("Preview database deletion declined by user");
|
|
1998
1903
|
operations.push({
|
|
1999
1904
|
type: "database",
|
|
2000
1905
|
success: true,
|
|
@@ -2004,7 +1909,7 @@ ${blockerMessage}`);
|
|
|
2004
1909
|
} else if (!deletionResult.success) {
|
|
2005
1910
|
const errorMsg = deletionResult.error ?? "Unknown error";
|
|
2006
1911
|
errors.push(new Error(errorMsg));
|
|
2007
|
-
|
|
1912
|
+
getLogger().warn(`Database cleanup failed: ${errorMsg}`);
|
|
2008
1913
|
operations.push({
|
|
2009
1914
|
type: "database",
|
|
2010
1915
|
success: false,
|
|
@@ -2015,7 +1920,7 @@ ${blockerMessage}`);
|
|
|
2015
1920
|
});
|
|
2016
1921
|
} else {
|
|
2017
1922
|
errors.push(new Error("Database cleanup in an unknown state"));
|
|
2018
|
-
|
|
1923
|
+
getLogger().warn("Database deletion returned unexpected result state");
|
|
2019
1924
|
operations.push({
|
|
2020
1925
|
type: "database",
|
|
2021
1926
|
success: false,
|
|
@@ -2025,7 +1930,7 @@ ${blockerMessage}`);
|
|
|
2025
1930
|
}
|
|
2026
1931
|
} catch (error) {
|
|
2027
1932
|
errors.push(error instanceof Error ? error : new Error(String(error)));
|
|
2028
|
-
|
|
1933
|
+
getLogger().warn(
|
|
2029
1934
|
`Unexpected database cleanup exception: ${error instanceof Error ? error.message : String(error)}`
|
|
2030
1935
|
);
|
|
2031
1936
|
operations.push({
|
|
@@ -2072,19 +1977,19 @@ ${blockerMessage}`);
|
|
|
2072
1977
|
* Terminate dev server on specified port
|
|
2073
1978
|
*/
|
|
2074
1979
|
async terminateDevServer(port) {
|
|
2075
|
-
|
|
1980
|
+
getLogger().debug(`Checking for dev server on port ${port}`);
|
|
2076
1981
|
const processInfo = await this.processManager.detectDevServer(port);
|
|
2077
1982
|
if (!processInfo) {
|
|
2078
|
-
|
|
1983
|
+
getLogger().debug(`No process found on port ${port}`);
|
|
2079
1984
|
return false;
|
|
2080
1985
|
}
|
|
2081
1986
|
if (!processInfo.isDevServer) {
|
|
2082
|
-
|
|
1987
|
+
getLogger().warn(
|
|
2083
1988
|
`Process on port ${port} (${processInfo.name}) doesn't appear to be a dev server, skipping`
|
|
2084
1989
|
);
|
|
2085
1990
|
return false;
|
|
2086
1991
|
}
|
|
2087
|
-
|
|
1992
|
+
getLogger().info(`Terminating dev server: ${processInfo.name} (PID: ${processInfo.pid})`);
|
|
2088
1993
|
await this.processManager.terminateProcess(processInfo.pid);
|
|
2089
1994
|
const isFree = await this.processManager.verifyPortFree(port);
|
|
2090
1995
|
if (!isFree) {
|
|
@@ -2104,23 +2009,75 @@ ${blockerMessage}`);
|
|
|
2104
2009
|
if (protectedBranches.includes(branchName)) {
|
|
2105
2010
|
throw new Error(`Cannot delete protected branch: ${branchName}`);
|
|
2106
2011
|
}
|
|
2012
|
+
const workingDir = cwd ?? await findMainWorktreePathWithSettings(void 0, this.settingsManager);
|
|
2013
|
+
try {
|
|
2014
|
+
await executeGitCommand(["rev-parse", "--verify", `refs/heads/${branchName}`], {
|
|
2015
|
+
cwd: workingDir
|
|
2016
|
+
});
|
|
2017
|
+
} catch {
|
|
2018
|
+
getLogger().debug(`Branch ${branchName} does not exist, skipping deletion`);
|
|
2019
|
+
return true;
|
|
2020
|
+
}
|
|
2107
2021
|
if (options.dryRun) {
|
|
2108
|
-
|
|
2022
|
+
getLogger().info(`[DRY RUN] Would delete branch: ${branchName}`);
|
|
2109
2023
|
return true;
|
|
2110
2024
|
}
|
|
2111
2025
|
try {
|
|
2112
|
-
let
|
|
2113
|
-
|
|
2026
|
+
let deleteFlag = "-d";
|
|
2027
|
+
let deleteCwd = workingDir;
|
|
2028
|
+
if (options.force) {
|
|
2029
|
+
deleteFlag = "-D";
|
|
2030
|
+
} else if (options.mergeTargetBranch) {
|
|
2031
|
+
const mergeTarget = options.mergeTargetBranch;
|
|
2032
|
+
try {
|
|
2033
|
+
const targetWorktreePath = await findWorktreeForBranch(mergeTarget, workingDir);
|
|
2034
|
+
getLogger().debug(`Running branch delete from worktree where '${mergeTarget}' is checked out: ${targetWorktreePath}`);
|
|
2035
|
+
deleteCwd = targetWorktreePath;
|
|
2036
|
+
} catch {
|
|
2037
|
+
getLogger().debug(`Could not find worktree for branch '${mergeTarget}', falling back to merge check`);
|
|
2038
|
+
const isMerged = await isBranchMergedIntoMain(branchName, mergeTarget, workingDir);
|
|
2039
|
+
if (isMerged) {
|
|
2040
|
+
getLogger().debug(`Branch '${branchName}' verified merged into '${mergeTarget}', using force delete`);
|
|
2041
|
+
deleteFlag = "-D";
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
} else if (options.worktreePath) {
|
|
2045
|
+
getLogger().warn("deleteBranch called with worktreePath but no mergeTargetBranch - this may fail if worktree was deleted");
|
|
2046
|
+
try {
|
|
2047
|
+
const mergeTarget = await getMergeTargetBranch(options.worktreePath, {
|
|
2048
|
+
settingsManager: this.settingsManager,
|
|
2049
|
+
metadataManager: this.metadataManager
|
|
2050
|
+
});
|
|
2051
|
+
try {
|
|
2052
|
+
const targetWorktreePath = await findWorktreeForBranch(mergeTarget, workingDir);
|
|
2053
|
+
getLogger().debug(`Running branch delete from worktree where '${mergeTarget}' is checked out: ${targetWorktreePath}`);
|
|
2054
|
+
deleteCwd = targetWorktreePath;
|
|
2055
|
+
} catch {
|
|
2056
|
+
getLogger().debug(`Could not find worktree for branch '${mergeTarget}', falling back to merge check`);
|
|
2057
|
+
const isMerged = await isBranchMergedIntoMain(branchName, mergeTarget, workingDir);
|
|
2058
|
+
if (isMerged) {
|
|
2059
|
+
getLogger().debug(`Branch '${branchName}' verified merged into '${mergeTarget}', using force delete`);
|
|
2060
|
+
deleteFlag = "-D";
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
} catch (error) {
|
|
2064
|
+
getLogger().debug(`Could not read merge target from worktreePath: ${error instanceof Error ? error.message : String(error)}`);
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2114
2067
|
await executeGitCommand(["branch", deleteFlag, branchName], {
|
|
2115
|
-
cwd:
|
|
2068
|
+
cwd: deleteCwd
|
|
2116
2069
|
});
|
|
2117
|
-
|
|
2070
|
+
getLogger().info(`Branch deleted: ${branchName}`);
|
|
2118
2071
|
return true;
|
|
2119
2072
|
} catch (error) {
|
|
2073
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2074
|
+
if (errorMessage.includes("not found") || errorMessage.includes("does not exist")) {
|
|
2075
|
+
getLogger().debug(`Branch ${branchName} already deleted`);
|
|
2076
|
+
return true;
|
|
2077
|
+
}
|
|
2120
2078
|
if (options.force) {
|
|
2121
2079
|
throw error;
|
|
2122
2080
|
}
|
|
2123
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2124
2081
|
if (errorMessage.includes("not fully merged")) {
|
|
2125
2082
|
throw new Error(
|
|
2126
2083
|
`Cannot delete unmerged branch '${branchName}'. Use --force to delete anyway.`
|
|
@@ -2143,17 +2100,17 @@ ${blockerMessage}`);
|
|
|
2143
2100
|
*/
|
|
2144
2101
|
async cleanupDatabase(branchName, worktreePath) {
|
|
2145
2102
|
if (!this.database) {
|
|
2146
|
-
|
|
2103
|
+
getLogger().debug("Database manager not available, skipping database cleanup");
|
|
2147
2104
|
return false;
|
|
2148
2105
|
}
|
|
2149
2106
|
try {
|
|
2150
|
-
const envFilePath =
|
|
2107
|
+
const envFilePath = path4.join(worktreePath, ".env");
|
|
2151
2108
|
const shouldCleanup = await this.database.shouldUseDatabaseBranching(envFilePath);
|
|
2152
2109
|
let cwd;
|
|
2153
2110
|
try {
|
|
2154
2111
|
cwd = await findMainWorktreePathWithSettings(worktreePath, this.settingsManager);
|
|
2155
2112
|
} catch (error) {
|
|
2156
|
-
|
|
2113
|
+
getLogger().debug(
|
|
2157
2114
|
`Could not find main worktree path, using current directory: ${error instanceof Error ? error.message : String(error)}`
|
|
2158
2115
|
);
|
|
2159
2116
|
}
|
|
@@ -2165,23 +2122,23 @@ ${blockerMessage}`);
|
|
|
2165
2122
|
cwd
|
|
2166
2123
|
);
|
|
2167
2124
|
if (result.deleted) {
|
|
2168
|
-
|
|
2125
|
+
getLogger().info(`Database branch deleted: ${branchName}`);
|
|
2169
2126
|
return true;
|
|
2170
2127
|
} else if (result.notFound) {
|
|
2171
|
-
|
|
2128
|
+
getLogger().debug(`No database branch found for: ${branchName}`);
|
|
2172
2129
|
return false;
|
|
2173
2130
|
} else if (result.userDeclined) {
|
|
2174
|
-
|
|
2131
|
+
getLogger().info("Preview database deletion declined by user");
|
|
2175
2132
|
return false;
|
|
2176
2133
|
} else if (!result.success) {
|
|
2177
|
-
|
|
2134
|
+
getLogger().warn(`Database cleanup failed: ${result.error ?? "Unknown error"}`);
|
|
2178
2135
|
return false;
|
|
2179
2136
|
} else {
|
|
2180
|
-
|
|
2137
|
+
getLogger().debug("Database deletion returned unexpected result");
|
|
2181
2138
|
return false;
|
|
2182
2139
|
}
|
|
2183
2140
|
} catch (error) {
|
|
2184
|
-
|
|
2141
|
+
getLogger().warn(
|
|
2185
2142
|
`Unexpected database cleanup error: ${error instanceof Error ? error.message : String(error)}`
|
|
2186
2143
|
);
|
|
2187
2144
|
return false;
|
|
@@ -2202,8 +2159,13 @@ ${blockerMessage}`);
|
|
|
2202
2159
|
/**
|
|
2203
2160
|
* Validate worktree safety given a worktree object
|
|
2204
2161
|
* Private method used internally when worktree is already known
|
|
2162
|
+
*
|
|
2163
|
+
* @param worktree - The worktree to validate
|
|
2164
|
+
* @param identifier - The original identifier used (for error messages)
|
|
2165
|
+
* @param checkBranchMerge - Whether to check if branch is merged into main (for branch deletion)
|
|
2166
|
+
* @param checkRemoteBranch - Whether to check if branch exists on remote (for GitHub-PR mode)
|
|
2205
2167
|
*/
|
|
2206
|
-
async validateWorktreeSafety(worktree, identifier) {
|
|
2168
|
+
async validateWorktreeSafety(worktree, identifier, checkBranchMerge = false, checkRemoteBranch = false) {
|
|
2207
2169
|
const warnings = [];
|
|
2208
2170
|
const blockers = [];
|
|
2209
2171
|
const isMain = await this.gitWorktree.isMainWorktree(worktree, this.settingsManager);
|
|
@@ -2220,6 +2182,44 @@ Please resolve before cleanup - you have some options:
|
|
|
2220
2182
|
\u2022 Force cleanup: il cleanup ${identifier} --force (WARNING: will discard changes)`;
|
|
2221
2183
|
blockers.push(blockerMessage);
|
|
2222
2184
|
}
|
|
2185
|
+
if ((checkBranchMerge || checkRemoteBranch) && worktree.branch) {
|
|
2186
|
+
const mainBranch = await getMergeTargetBranch(worktree.path, {
|
|
2187
|
+
settingsManager: this.settingsManager,
|
|
2188
|
+
metadataManager: this.metadataManager
|
|
2189
|
+
});
|
|
2190
|
+
const remoteStatus = await checkRemoteBranchStatus(worktree.branch, worktree.path);
|
|
2191
|
+
if (remoteStatus.networkError) {
|
|
2192
|
+
const blockerMessage = `Cannot verify remote branch status due to network error.
|
|
2193
|
+
|
|
2194
|
+
Error: ${remoteStatus.errorMessage ?? "Unknown network error"}
|
|
2195
|
+
|
|
2196
|
+
Unable to determine if branch '${worktree.branch}' is safely backed up.
|
|
2197
|
+
Use --force to proceed without verification.`;
|
|
2198
|
+
blockers.push(blockerMessage);
|
|
2199
|
+
} else if (remoteStatus.exists && remoteStatus.localAhead) {
|
|
2200
|
+
const blockerMessage = `Branch '${worktree.branch}' has unpushed commits that would be lost.
|
|
2201
|
+
The remote branch exists but your local branch is ahead.
|
|
2202
|
+
|
|
2203
|
+
Please resolve before cleanup:
|
|
2204
|
+
\u2022 Push your commits: git push origin ${worktree.branch}
|
|
2205
|
+
\u2022 Force cleanup: il cleanup ${identifier} --force (WARNING: will lose commits)`;
|
|
2206
|
+
blockers.push(blockerMessage);
|
|
2207
|
+
} else if (remoteStatus.exists && !remoteStatus.localAhead) {
|
|
2208
|
+
} else if (!remoteStatus.exists) {
|
|
2209
|
+
const isMerged = await isBranchMergedIntoMain(worktree.branch, mainBranch, worktree.path);
|
|
2210
|
+
if (isMerged) {
|
|
2211
|
+
} else {
|
|
2212
|
+
const blockerMessage = `Branch '${worktree.branch}' has not been pushed to remote and is not merged into '${mainBranch}'.
|
|
2213
|
+
Deleting this branch would result in data loss.
|
|
2214
|
+
|
|
2215
|
+
Please resolve before cleanup - you have some options:
|
|
2216
|
+
\u2022 Push to remote: git push -u origin ${worktree.branch}
|
|
2217
|
+
\u2022 Merge to ${mainBranch}: git checkout ${mainBranch} && git merge ${worktree.branch}
|
|
2218
|
+
\u2022 Force cleanup: il cleanup ${identifier} --force (WARNING: will lose commits)`;
|
|
2219
|
+
blockers.push(blockerMessage);
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
2223
|
return {
|
|
2224
2224
|
isSafe: blockers.length === 0,
|
|
2225
2225
|
warnings,
|
|
@@ -2282,11 +2282,10 @@ Please resolve before cleanup - you have some options:
|
|
|
2282
2282
|
};
|
|
2283
2283
|
|
|
2284
2284
|
export {
|
|
2285
|
-
MetadataManager,
|
|
2286
2285
|
LoomManager,
|
|
2287
2286
|
EnvironmentManager,
|
|
2288
2287
|
CLIIsolationManager,
|
|
2289
2288
|
DatabaseManager,
|
|
2290
2289
|
ResourceCleanup
|
|
2291
2290
|
};
|
|
2292
|
-
//# sourceMappingURL=chunk-
|
|
2291
|
+
//# sourceMappingURL=chunk-IARWMDAX.js.map
|