@iloom/cli 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/README.md +8 -6
  2. package/dist/{BranchNamingService-OMWKUYMM.js → BranchNamingService-A77VI6AI.js} +2 -2
  3. package/dist/ClaudeContextManager-BN7RE5ZQ.js +15 -0
  4. package/dist/ClaudeService-DLYLJUPA.js +14 -0
  5. package/dist/{GitHubService-EBOETDIW.js → GitHubService-FZHHBOFG.js} +3 -3
  6. package/dist/{LoomLauncher-JF7JZMTZ.js → LoomLauncher-ZV3ZZIBA.js} +40 -26
  7. package/dist/LoomLauncher-ZV3ZZIBA.js.map +1 -0
  8. package/dist/{PromptTemplateManager-A52RUAMS.js → PromptTemplateManager-6HH3PVXV.js} +2 -2
  9. package/dist/README.md +8 -6
  10. package/dist/{SettingsManager-ZCWJ56WP.js → SettingsManager-I2LRCW2A.js} +2 -2
  11. package/dist/{SettingsMigrationManager-AGIIIPDQ.js → SettingsMigrationManager-TJ7UWZG5.js} +3 -3
  12. package/dist/agents/iloom-issue-complexity-evaluator.md +18 -3
  13. package/dist/agents/iloom-issue-enhancer.md +1 -1
  14. package/dist/{chunk-TSKY3JI7.js → chunk-2CXREBLZ.js} +2 -2
  15. package/dist/{chunk-HBYZH6GD.js → chunk-2IJEMXOB.js} +431 -128
  16. package/dist/chunk-2IJEMXOB.js.map +1 -0
  17. package/dist/{chunk-IXKLYTWO.js → chunk-2MAIX45J.js} +8 -8
  18. package/dist/{chunk-4BGK7T6X.js → chunk-5Q3NDNNV.js} +48 -8
  19. package/dist/chunk-5Q3NDNNV.js.map +1 -0
  20. package/dist/{chunk-JQFO7QQN.js → chunk-5VK4NRSF.js} +3 -3
  21. package/dist/{chunk-JQFO7QQN.js.map → chunk-5VK4NRSF.js.map} +1 -1
  22. package/dist/{chunk-XPKDPZ5D.js → chunk-AKUJXDNW.js} +2 -2
  23. package/dist/{chunk-O5OH5MRX.js → chunk-CDZERT7Z.js} +23 -11
  24. package/dist/chunk-CDZERT7Z.js.map +1 -0
  25. package/dist/{chunk-JKXJ7BGL.js → chunk-CE26YH2U.js} +42 -3
  26. package/dist/chunk-CE26YH2U.js.map +1 -0
  27. package/dist/{chunk-ZZZWQGTS.js → chunk-CFFQ2Z7A.js} +74 -75
  28. package/dist/chunk-CFFQ2Z7A.js.map +1 -0
  29. package/dist/{chunk-RO26VS3W.js → chunk-DLHA5VQ3.js} +174 -5
  30. package/dist/chunk-DLHA5VQ3.js.map +1 -0
  31. package/dist/{chunk-ZBQVSHVT.js → chunk-IFB4Z76W.js} +35 -10
  32. package/dist/chunk-IFB4Z76W.js.map +1 -0
  33. package/dist/{chunk-G2IEYOLQ.js → chunk-M7JJCX53.js} +17 -2
  34. package/dist/chunk-M7JJCX53.js.map +1 -0
  35. package/dist/{chunk-KLBYVHPK.js → chunk-OSCLCMDG.js} +2 -2
  36. package/dist/chunk-OXAM2WVC.js +68 -0
  37. package/dist/chunk-OXAM2WVC.js.map +1 -0
  38. package/dist/{chunk-ZWFBBPJI.js → chunk-OYF4VIFI.js} +5 -3
  39. package/dist/chunk-OYF4VIFI.js.map +1 -0
  40. package/dist/{chunk-U5QDY7ZD.js → chunk-PGPI5LR4.js} +8 -8
  41. package/dist/{chunk-WEN5C5DM.js → chunk-RIEO2WML.js} +4 -1
  42. package/dist/chunk-RIEO2WML.js.map +1 -0
  43. package/dist/{chunk-ZE74H5BR.js → chunk-RW54ZMBM.js} +26 -20
  44. package/dist/chunk-RW54ZMBM.js.map +1 -0
  45. package/dist/{chunk-INW24J2W.js → chunk-SUOXY5WJ.js} +2 -2
  46. package/dist/{init-L55Q73H4.js → chunk-UAN4A3YU.js} +345 -45
  47. package/dist/chunk-UAN4A3YU.js.map +1 -0
  48. package/dist/{chunk-IP7SMKIF.js → chunk-UJL4HI2R.js} +59 -60
  49. package/dist/chunk-UJL4HI2R.js.map +1 -0
  50. package/dist/{claude-LUZ35IMK.js → claude-W52VKI6L.js} +4 -2
  51. package/dist/{cleanup-3MONU4PU.js → cleanup-H4VXU3C3.js} +19 -17
  52. package/dist/{cleanup-3MONU4PU.js.map → cleanup-H4VXU3C3.js.map} +1 -1
  53. package/dist/cli.js +347 -114
  54. package/dist/cli.js.map +1 -1
  55. package/dist/{color-ZVALX37U.js → color-F7RU6B6Z.js} +10 -4
  56. package/dist/{contribute-UWJAGIG7.js → contribute-Y7IQV5QY.js} +4 -3
  57. package/dist/{contribute-UWJAGIG7.js.map → contribute-Y7IQV5QY.js.map} +1 -1
  58. package/dist/{feedback-W3BXTGIM.js → feedback-XTUCKJNT.js} +16 -12
  59. package/dist/{feedback-W3BXTGIM.js.map → feedback-XTUCKJNT.js.map} +1 -1
  60. package/dist/{git-34Z6QVDS.js → git-IYA53VIC.js} +9 -2
  61. package/dist/{ignite-KVJEFXNO.js → ignite-T74RYXCA.js} +25 -75
  62. package/dist/ignite-T74RYXCA.js.map +1 -0
  63. package/dist/index.d.ts +71 -14
  64. package/dist/index.js +407 -377
  65. package/dist/index.js.map +1 -1
  66. package/dist/init-4FHTAM3F.js +19 -0
  67. package/dist/mcp/issue-management-server.js +8 -1
  68. package/dist/mcp/issue-management-server.js.map +1 -1
  69. package/dist/{neon-helpers-WPUACUVC.js → neon-helpers-77PBPGJ5.js} +3 -3
  70. package/dist/{open-LNRZL3UU.js → open-UMXANW5S.js} +27 -14
  71. package/dist/open-UMXANW5S.js.map +1 -0
  72. package/dist/{prompt-7INJ7YRU.js → prompt-QALMYTVC.js} +4 -2
  73. package/dist/prompts/init-prompt.txt +89 -9
  74. package/dist/prompts/issue-prompt.txt +18 -11
  75. package/dist/{rebase-C4WNCVGM.js → rebase-VJ2VKR6R.js} +15 -13
  76. package/dist/rebase-VJ2VKR6R.js.map +1 -0
  77. package/dist/{run-IOGNIOYN.js → run-MJYY4PUT.js} +27 -14
  78. package/dist/run-MJYY4PUT.js.map +1 -0
  79. package/dist/schema/settings.schema.json +22 -4
  80. package/dist/{test-git-J7I5MFYH.js → test-git-IT5EWQ5C.js} +5 -5
  81. package/dist/{test-prefix-ZCONBCBX.js → test-prefix-NPWDPUUH.js} +5 -5
  82. package/dist/{test-tabs-RXDBZ6J7.js → test-tabs-PRMRSHKI.js} +3 -2
  83. package/dist/{test-tabs-RXDBZ6J7.js.map → test-tabs-PRMRSHKI.js.map} +1 -1
  84. package/package.json +2 -1
  85. package/dist/ClaudeContextManager-3VXA6UPR.js +0 -13
  86. package/dist/ClaudeService-6CPK43N4.js +0 -12
  87. package/dist/LoomLauncher-JF7JZMTZ.js.map +0 -1
  88. package/dist/chunk-4BGK7T6X.js.map +0 -1
  89. package/dist/chunk-4E4LD3QR.js +0 -302
  90. package/dist/chunk-4E4LD3QR.js.map +0 -1
  91. package/dist/chunk-G2IEYOLQ.js.map +0 -1
  92. package/dist/chunk-HBYZH6GD.js.map +0 -1
  93. package/dist/chunk-IP7SMKIF.js.map +0 -1
  94. package/dist/chunk-JKXJ7BGL.js.map +0 -1
  95. package/dist/chunk-O5OH5MRX.js.map +0 -1
  96. package/dist/chunk-RO26VS3W.js.map +0 -1
  97. package/dist/chunk-WEN5C5DM.js.map +0 -1
  98. package/dist/chunk-ZBQVSHVT.js.map +0 -1
  99. package/dist/chunk-ZE74H5BR.js.map +0 -1
  100. package/dist/chunk-ZWFBBPJI.js.map +0 -1
  101. package/dist/chunk-ZZZWQGTS.js.map +0 -1
  102. package/dist/ignite-KVJEFXNO.js.map +0 -1
  103. package/dist/init-L55Q73H4.js.map +0 -1
  104. package/dist/open-LNRZL3UU.js.map +0 -1
  105. package/dist/rebase-C4WNCVGM.js.map +0 -1
  106. package/dist/run-IOGNIOYN.js.map +0 -1
  107. package/dist/terminal-BIRBZ4AZ.js +0 -16
  108. /package/dist/{BranchNamingService-OMWKUYMM.js.map → BranchNamingService-A77VI6AI.js.map} +0 -0
  109. /package/dist/{ClaudeContextManager-3VXA6UPR.js.map → ClaudeContextManager-BN7RE5ZQ.js.map} +0 -0
  110. /package/dist/{ClaudeService-6CPK43N4.js.map → ClaudeService-DLYLJUPA.js.map} +0 -0
  111. /package/dist/{GitHubService-EBOETDIW.js.map → GitHubService-FZHHBOFG.js.map} +0 -0
  112. /package/dist/{PromptTemplateManager-A52RUAMS.js.map → PromptTemplateManager-6HH3PVXV.js.map} +0 -0
  113. /package/dist/{SettingsManager-ZCWJ56WP.js.map → SettingsManager-I2LRCW2A.js.map} +0 -0
  114. /package/dist/{SettingsMigrationManager-AGIIIPDQ.js.map → SettingsMigrationManager-TJ7UWZG5.js.map} +0 -0
  115. /package/dist/{chunk-TSKY3JI7.js.map → chunk-2CXREBLZ.js.map} +0 -0
  116. /package/dist/{chunk-IXKLYTWO.js.map → chunk-2MAIX45J.js.map} +0 -0
  117. /package/dist/{chunk-XPKDPZ5D.js.map → chunk-AKUJXDNW.js.map} +0 -0
  118. /package/dist/{chunk-KLBYVHPK.js.map → chunk-OSCLCMDG.js.map} +0 -0
  119. /package/dist/{chunk-U5QDY7ZD.js.map → chunk-PGPI5LR4.js.map} +0 -0
  120. /package/dist/{chunk-INW24J2W.js.map → chunk-SUOXY5WJ.js.map} +0 -0
  121. /package/dist/{claude-LUZ35IMK.js.map → claude-W52VKI6L.js.map} +0 -0
  122. /package/dist/{color-ZVALX37U.js.map → color-F7RU6B6Z.js.map} +0 -0
  123. /package/dist/{git-34Z6QVDS.js.map → git-IYA53VIC.js.map} +0 -0
  124. /package/dist/{neon-helpers-WPUACUVC.js.map → init-4FHTAM3F.js.map} +0 -0
  125. /package/dist/{prompt-7INJ7YRU.js.map → neon-helpers-77PBPGJ5.js.map} +0 -0
  126. /package/dist/{terminal-BIRBZ4AZ.js.map → prompt-QALMYTVC.js.map} +0 -0
  127. /package/dist/{test-git-J7I5MFYH.js.map → test-git-IT5EWQ5C.js.map} +0 -0
  128. /package/dist/{test-prefix-ZCONBCBX.js.map → test-prefix-NPWDPUUH.js.map} +0 -0
@@ -1,10 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- formatEnvLine,
4
- loadEnvIntoProcess,
5
- parseEnvFile,
6
- validateEnvVariable
7
- } from "./chunk-IP7SMKIF.js";
8
2
  import {
9
3
  calculatePortForBranch
10
4
  } from "./chunk-VU3QMIP2.js";
@@ -16,36 +10,220 @@ import {
16
10
  hasScript,
17
11
  readPackageJson
18
12
  } from "./chunk-2ZPFJQ3B.js";
19
- import {
20
- SettingsManager
21
- } from "./chunk-O5OH5MRX.js";
22
13
  import {
23
14
  branchExists,
24
15
  ensureRepositoryHasCommits,
25
16
  executeGitCommand,
26
17
  extractIssueNumber,
27
18
  findMainWorktreePathWithSettings,
28
- hasUncommittedChanges
29
- } from "./chunk-4BGK7T6X.js";
19
+ hasUncommittedChanges,
20
+ isFileTrackedByGit
21
+ } from "./chunk-5Q3NDNNV.js";
22
+ import {
23
+ SettingsManager
24
+ } from "./chunk-CDZERT7Z.js";
30
25
  import {
31
26
  calculateForegroundColor,
32
27
  generateColorFromBranchName,
33
28
  hexToRgb,
34
29
  lightenColor,
35
- rgbToHex
36
- } from "./chunk-ZZZWQGTS.js";
30
+ rgbToHex,
31
+ selectDistinctColor
32
+ } from "./chunk-CFFQ2Z7A.js";
33
+ import {
34
+ findEnvFileForDatabaseUrl,
35
+ formatEnvLine,
36
+ hasVariableInAnyEnvFile,
37
+ loadEnvIntoProcess,
38
+ parseEnvFile,
39
+ validateEnvVariable
40
+ } from "./chunk-UJL4HI2R.js";
37
41
  import {
38
42
  createLogger,
39
43
  logger
40
44
  } from "./chunk-GEHQXLEI.js";
41
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
+ };
219
+
42
220
  // src/lib/LoomManager.ts
43
- import path2 from "path";
44
- import fs2 from "fs-extra";
221
+ import path3 from "path";
222
+ import fs3 from "fs-extra";
45
223
 
46
224
  // src/lib/VSCodeIntegration.ts
47
- import fs from "fs-extra";
48
- import path from "path";
225
+ import fs2 from "fs-extra";
226
+ import path2 from "path";
49
227
  import { parse, modify, applyEdits } from "jsonc-parser";
50
228
  var VSCodeIntegration = class {
51
229
  /**
@@ -55,10 +233,10 @@ var VSCodeIntegration = class {
55
233
  * @param hexColor - Hex color string (e.g., "#dcebf8")
56
234
  */
57
235
  async setTitleBarColor(workspacePath, hexColor) {
58
- const vscodeDir = path.join(workspacePath, ".vscode");
59
- const settingsPath = path.join(vscodeDir, "settings.json");
236
+ const vscodeDir = path2.join(workspacePath, ".vscode");
237
+ const settingsPath = path2.join(vscodeDir, "settings.json");
60
238
  try {
61
- await fs.ensureDir(vscodeDir);
239
+ await fs2.ensureDir(vscodeDir);
62
240
  const settings = await this.readSettings(settingsPath);
63
241
  const updatedSettings = this.mergeColorSettings(settings, hexColor);
64
242
  await this.writeSettings(settingsPath, updatedSettings);
@@ -78,10 +256,10 @@ var VSCodeIntegration = class {
78
256
  */
79
257
  async readSettings(settingsPath) {
80
258
  try {
81
- if (!await fs.pathExists(settingsPath)) {
259
+ if (!await fs2.pathExists(settingsPath)) {
82
260
  return {};
83
261
  }
84
- const content = await fs.readFile(settingsPath, "utf8");
262
+ const content = await fs2.readFile(settingsPath, "utf8");
85
263
  const errors = [];
86
264
  const settings = parse(content, errors, { allowTrailingComma: true });
87
265
  if (errors.length > 0) {
@@ -105,8 +283,8 @@ var VSCodeIntegration = class {
105
283
  async writeSettings(settingsPath, settings) {
106
284
  try {
107
285
  let content;
108
- if (await fs.pathExists(settingsPath)) {
109
- const existingContent = await fs.readFile(settingsPath, "utf8");
286
+ if (await fs2.pathExists(settingsPath)) {
287
+ const existingContent = await fs2.readFile(settingsPath, "utf8");
110
288
  if (existingContent.includes("//") || existingContent.includes("/*")) {
111
289
  content = await this.modifyWithCommentsPreserved(existingContent, settings);
112
290
  } else {
@@ -116,8 +294,8 @@ var VSCodeIntegration = class {
116
294
  content = JSON.stringify(settings, null, 2) + "\n";
117
295
  }
118
296
  const tempPath = `${settingsPath}.tmp`;
119
- await fs.writeFile(tempPath, content, "utf8");
120
- await fs.rename(tempPath, settingsPath);
297
+ await fs2.writeFile(tempPath, content, "utf8");
298
+ await fs2.rename(tempPath, settingsPath);
121
299
  } catch (error) {
122
300
  throw new Error(
123
301
  `Failed to write settings.json: ${error instanceof Error ? error.message : "Unknown error"}`
@@ -181,6 +359,7 @@ var LoomManager = class {
181
359
  this.cliIsolation = cliIsolation;
182
360
  this.settings = settings;
183
361
  this.database = database;
362
+ this.metadataManager = new MetadataManager();
184
363
  }
185
364
  /**
186
365
  * Get database branch name for a loom by reading its .env file
@@ -194,7 +373,7 @@ var LoomManager = class {
194
373
  return null;
195
374
  }
196
375
  try {
197
- const envFilePath = path2.join(loomPath, ".env");
376
+ const envFilePath = path3.join(loomPath, ".env");
198
377
  const settings = await this.settings.loadSettings();
199
378
  const databaseUrlVarName = ((_b = (_a = settings.capabilities) == null ? void 0 : _a.database) == null ? void 0 : _b.databaseUrlEnvVarName) ?? "DATABASE_URL";
200
379
  const connectionString = await this.environment.getEnvVariable(envFilePath, databaseUrlVarName);
@@ -213,7 +392,7 @@ var LoomManager = class {
213
392
  * NEW: Checks for existing worktrees and reuses them if found
214
393
  */
215
394
  async createIloom(input) {
216
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
395
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
217
396
  logger.info("Fetching issue data...");
218
397
  const issueData = await this.fetchIssueData(input);
219
398
  if (input.type === "issue" || input.type === "pr" || input.type === "branch") {
@@ -249,16 +428,25 @@ var LoomManager = class {
249
428
  try {
250
429
  const connectionString = await this.database.createBranchIfConfigured(
251
430
  branchName,
252
- path2.join(worktreePath, ".env"),
431
+ worktreePath,
432
+ // workspace path - checks all dotenv-flow files
253
433
  void 0,
254
434
  // cwd
255
435
  (_e = input.parentLoom) == null ? void 0 : _e.databaseBranch
256
436
  // fromBranch - use parent's database branch for child looms
257
437
  );
258
438
  if (connectionString) {
439
+ const varName = this.database.getConfiguredVariableName();
440
+ const targetFile = await findEnvFileForDatabaseUrl(
441
+ worktreePath,
442
+ varName,
443
+ isFileTrackedByGit,
444
+ async (p) => fs3.pathExists(p),
445
+ async (p, v) => this.environment.getEnvVariable(p, v)
446
+ );
259
447
  await this.environment.setEnvVar(
260
- path2.join(worktreePath, ".env"),
261
- this.database.getConfiguredVariableName(),
448
+ path3.join(worktreePath, targetFile),
449
+ varName,
262
450
  connectionString
263
451
  );
264
452
  logger.success("Database branch configured");
@@ -286,15 +474,17 @@ var LoomManager = class {
286
474
  );
287
475
  }
288
476
  }
289
- if (!((_f = input.options) == null ? void 0 : _f.skipColorSync)) {
290
- try {
291
- await this.applyColorSynchronization(worktreePath, branchName);
292
- } catch (error) {
293
- logger.warn(
294
- `Failed to apply color synchronization: ${error instanceof Error ? error.message : "Unknown error"}`,
295
- error
296
- );
297
- }
477
+ const allMetadata = await this.metadataManager.listAllMetadata();
478
+ const usedHexColors = allMetadata.filter((metadata) => metadata.colorHex !== null).map((metadata) => metadata.colorHex);
479
+ const colorData = selectDistinctColor(branchName, usedHexColors);
480
+ logger.debug(`Selected color ${colorData.hex} for branch ${branchName} (${usedHexColors.length} colors in use globally)`);
481
+ try {
482
+ await this.applyColorSynchronization(worktreePath, branchName, colorData, settingsData, input.options);
483
+ } catch (error) {
484
+ logger.warn(
485
+ `Failed to apply color synchronization: ${error instanceof Error ? error.message : "Unknown error"}`,
486
+ error
487
+ );
298
488
  }
299
489
  if (input.type === "issue") {
300
490
  try {
@@ -309,16 +499,16 @@ var LoomManager = class {
309
499
  );
310
500
  }
311
501
  }
312
- const enableClaude = ((_g = input.options) == null ? void 0 : _g.enableClaude) !== false;
313
- const enableCode = ((_h = input.options) == null ? void 0 : _h.enableCode) !== false;
314
- const enableDevServer = ((_i = input.options) == null ? void 0 : _i.enableDevServer) !== false;
315
- const enableTerminal = ((_j = input.options) == null ? void 0 : _j.enableTerminal) ?? false;
316
- const oneShot = ((_k = input.options) == null ? void 0 : _k.oneShot) ?? "default";
317
- const setArguments = (_l = input.options) == null ? void 0 : _l.setArguments;
318
- const executablePath = (_m = input.options) == null ? void 0 : _m.executablePath;
502
+ const enableClaude = ((_f = input.options) == null ? void 0 : _f.enableClaude) !== false;
503
+ const enableCode = ((_g = input.options) == null ? void 0 : _g.enableCode) !== false;
504
+ const enableDevServer = ((_h = input.options) == null ? void 0 : _h.enableDevServer) !== false;
505
+ const enableTerminal = ((_i = input.options) == null ? void 0 : _i.enableTerminal) ?? false;
506
+ const oneShot = ((_j = input.options) == null ? void 0 : _j.oneShot) ?? "default";
507
+ const setArguments = (_k = input.options) == null ? void 0 : _k.setArguments;
508
+ const executablePath = (_l = input.options) == null ? void 0 : _l.executablePath;
319
509
  if (enableClaude || enableCode || enableDevServer || enableTerminal) {
320
- const { LoomLauncher } = await import("./LoomLauncher-JF7JZMTZ.js");
321
- const { ClaudeContextManager } = await import("./ClaudeContextManager-3VXA6UPR.js");
510
+ const { LoomLauncher } = await import("./LoomLauncher-ZV3ZZIBA.js");
511
+ const { ClaudeContextManager } = await import("./ClaudeContextManager-BN7RE5ZQ.js");
322
512
  const claudeContext = new ClaudeContextManager(void 0, void 0, this.settings);
323
513
  const launcher = new LoomLauncher(claudeContext, this.settings);
324
514
  await launcher.launchLoom({
@@ -336,9 +526,25 @@ var LoomManager = class {
336
526
  oneShot,
337
527
  ...setArguments && { setArguments },
338
528
  ...executablePath && { executablePath },
339
- sourceEnvOnStart: settingsData.sourceEnvOnStart ?? false
529
+ sourceEnvOnStart: settingsData.sourceEnvOnStart ?? false,
530
+ colorTerminal: ((_m = input.options) == null ? void 0 : _m.colorTerminal) ?? ((_n = settingsData.colors) == null ? void 0 : _n.terminal) ?? true,
531
+ colorHex: colorData.hex
340
532
  });
341
533
  }
534
+ const description = (issueData == null ? void 0 : issueData.title) ?? branchName;
535
+ const issue_numbers = input.type === "issue" ? [String(input.identifier)] : [];
536
+ const pr_numbers = input.type === "pr" ? [String(input.identifier)] : [];
537
+ const metadataInput = {
538
+ description,
539
+ branchName,
540
+ worktreePath,
541
+ issueType: input.type,
542
+ issue_numbers,
543
+ pr_numbers,
544
+ issueTracker: this.issueTracker.providerName,
545
+ colorHex: colorData.hex
546
+ };
547
+ await this.metadataManager.writeMetadata(worktreePath, metadataInput);
342
548
  const loom = {
343
549
  id: this.generateLoomId(input),
344
550
  path: worktreePath,
@@ -346,6 +552,7 @@ var LoomManager = class {
346
552
  type: input.type,
347
553
  identifier: input.identifier,
348
554
  port,
555
+ description,
349
556
  createdAt: /* @__PURE__ */ new Date(),
350
557
  lastAccessed: /* @__PURE__ */ new Date(),
351
558
  ...databaseBranch !== void 0 && { databaseBranch },
@@ -376,6 +583,9 @@ var LoomManager = class {
376
583
  */
377
584
  async listLooms() {
378
585
  const worktrees = await this.gitWorktree.listWorktrees();
586
+ if (!worktrees) {
587
+ return [];
588
+ }
379
589
  return await this.mapWorktreesToLooms(worktrees);
380
590
  }
381
591
  /**
@@ -420,7 +630,7 @@ var LoomManager = class {
420
630
  async checkAndWarnChildLooms(branchName) {
421
631
  let targetBranch = branchName;
422
632
  if (!targetBranch) {
423
- const { getCurrentBranch } = await import("./git-34Z6QVDS.js");
633
+ const { getCurrentBranch } = await import("./git-IYA53VIC.js");
424
634
  targetBranch = await getCurrentBranch();
425
635
  }
426
636
  if (!targetBranch) {
@@ -543,19 +753,39 @@ var LoomManager = class {
543
753
  }
544
754
  /**
545
755
  * Copy user application environment files (.env) from main repo to worktree
756
+ * Copies all dotenv-flow patterns: .env, .env.local, .env.{NODE_ENV}, .env.{NODE_ENV}.local
757
+ * Only copies files that exist and are NOT tracked by git (tracked files exist via worktree)
546
758
  * Always called regardless of project capabilities
547
759
  */
548
760
  async copyEnvironmentFiles(worktreePath) {
549
- const envFilePath = path2.join(worktreePath, ".env");
550
- try {
551
- const mainEnvPath = path2.join(process.cwd(), ".env");
552
- if (await fs2.pathExists(envFilePath)) {
553
- logger.warn(".env file already exists in worktree, skipping copy");
554
- } else {
555
- await this.environment.copyIfExists(mainEnvPath, envFilePath);
761
+ const mainWorkspacePath = this.gitWorktree.workingDirectory;
762
+ const nodeEnv = process.env.DOTENV_FLOW_NODE_ENV ?? "development";
763
+ const envFilePatterns = [
764
+ ".env",
765
+ ".env.local",
766
+ `.env.${nodeEnv}`,
767
+ `.env.${nodeEnv}.local`
768
+ ];
769
+ for (const pattern of envFilePatterns) {
770
+ try {
771
+ const mainEnvPath = path3.join(mainWorkspacePath, pattern);
772
+ const worktreeEnvPath = path3.join(worktreePath, pattern);
773
+ if (!await fs3.pathExists(mainEnvPath)) {
774
+ continue;
775
+ }
776
+ if (await isFileTrackedByGit(pattern, mainWorkspacePath)) {
777
+ logger.debug(`Skipping ${pattern} (tracked by git, already in worktree)`);
778
+ continue;
779
+ }
780
+ if (await fs3.pathExists(worktreeEnvPath)) {
781
+ logger.warn(`${pattern} already exists in worktree, skipping copy`);
782
+ continue;
783
+ }
784
+ await this.environment.copyIfExists(mainEnvPath, worktreeEnvPath);
785
+ logger.debug(`Copied ${pattern} to worktree`);
786
+ } catch (error) {
787
+ logger.warn(`Warning: Failed to copy ${pattern}: ${error instanceof Error ? error.message : "Unknown error"}`);
556
788
  }
557
- } catch (error) {
558
- logger.warn(`Warning: Failed to copy main .env file: ${error instanceof Error ? error.message : "Unknown error"}`);
559
789
  }
560
790
  }
561
791
  /**
@@ -565,12 +795,12 @@ var LoomManager = class {
565
795
  * @param parentBranchName Optional parent branch name for child looms (sets mainBranch)
566
796
  */
567
797
  async copyIloomSettings(worktreePath, parentBranchName) {
568
- const mainSettingsLocalPath = path2.join(process.cwd(), ".iloom", "settings.local.json");
798
+ const mainSettingsLocalPath = path3.join(process.cwd(), ".iloom", "settings.local.json");
569
799
  try {
570
- const worktreeIloomDir = path2.join(worktreePath, ".iloom");
571
- await fs2.ensureDir(worktreeIloomDir);
572
- const worktreeSettingsLocalPath = path2.join(worktreeIloomDir, "settings.local.json");
573
- if (await fs2.pathExists(worktreeSettingsLocalPath)) {
800
+ const worktreeIloomDir = path3.join(worktreePath, ".iloom");
801
+ await fs3.ensureDir(worktreeIloomDir);
802
+ const worktreeSettingsLocalPath = path3.join(worktreeIloomDir, "settings.local.json");
803
+ if (await fs3.pathExists(worktreeSettingsLocalPath)) {
574
804
  logger.warn("settings.local.json already exists in worktree, skipping copy");
575
805
  } else {
576
806
  await this.environment.copyIfExists(mainSettingsLocalPath, worktreeSettingsLocalPath);
@@ -578,7 +808,7 @@ var LoomManager = class {
578
808
  if (parentBranchName) {
579
809
  let existingSettings = {};
580
810
  try {
581
- const content = await fs2.readFile(worktreeSettingsLocalPath, "utf8");
811
+ const content = await fs3.readFile(worktreeSettingsLocalPath, "utf8");
582
812
  existingSettings = JSON.parse(content);
583
813
  } catch {
584
814
  }
@@ -586,7 +816,7 @@ var LoomManager = class {
586
816
  ...existingSettings,
587
817
  mainBranch: parentBranchName
588
818
  };
589
- await fs2.writeFile(worktreeSettingsLocalPath, JSON.stringify(updatedSettings, null, 2));
819
+ await fs3.writeFile(worktreeSettingsLocalPath, JSON.stringify(updatedSettings, null, 2));
590
820
  logger.info(`Set mainBranch to ${parentBranchName} for child loom`);
591
821
  }
592
822
  } catch (error) {
@@ -598,7 +828,7 @@ var LoomManager = class {
598
828
  * Only called when project has web capabilities
599
829
  */
600
830
  async setupPortForWeb(worktreePath, input, basePort) {
601
- const envFilePath = path2.join(worktreePath, ".env");
831
+ const envFilePath = path3.join(worktreePath, ".env.local");
602
832
  const options = { basePort };
603
833
  if (input.type === "issue") {
604
834
  options.issueNumber = input.identifier;
@@ -655,16 +885,34 @@ var LoomManager = class {
655
885
  /**
656
886
  * Apply color synchronization to both VSCode and terminal
657
887
  * Colors are cosmetic - errors are logged but don't block workflow
888
+ * Respects colors settings for independent control
889
+ *
890
+ * DEFAULTS:
891
+ * - terminal: true (always safe, only affects macOS Terminal.app)
892
+ * - vscode: false (safe default, prevents unexpected file modifications)
893
+ *
894
+ * @param colorData - Pre-computed color data (from collision avoidance)
658
895
  */
659
- async applyColorSynchronization(worktreePath, branchName) {
660
- const colorData = generateColorFromBranchName(branchName);
661
- const vscode = new VSCodeIntegration();
662
- await vscode.setTitleBarColor(worktreePath, colorData.hex);
663
- logger.info(`Applied VSCode title bar color: ${colorData.hex} for branch: ${branchName}`);
896
+ async applyColorSynchronization(worktreePath, branchName, colorData, settings, options) {
897
+ var _a, _b;
898
+ const colorVscode = (options == null ? void 0 : options.colorVscode) ?? ((_a = settings.colors) == null ? void 0 : _a.vscode) ?? false;
899
+ const colorTerminal = (options == null ? void 0 : options.colorTerminal) ?? ((_b = settings.colors) == null ? void 0 : _b.terminal) ?? true;
900
+ if (!colorVscode && !colorTerminal) {
901
+ logger.debug("Color synchronization disabled for both VSCode and terminal");
902
+ return;
903
+ }
904
+ if (colorVscode) {
905
+ const vscode = new VSCodeIntegration();
906
+ await vscode.setTitleBarColor(worktreePath, colorData.hex);
907
+ logger.info(`Applied VSCode title bar color: ${colorData.hex} for branch: ${branchName}`);
908
+ } else {
909
+ logger.debug("VSCode color sync disabled (default: false for safety)");
910
+ }
664
911
  }
665
912
  /**
666
913
  * Map worktrees to loom objects
667
914
  * This is a simplified conversion - in production we'd store loom metadata
915
+ * Now reads metadata from MetadataManager (spec section 3.2)
668
916
  */
669
917
  async mapWorktreesToLooms(worktrees) {
670
918
  return await Promise.all(worktrees.map(async (wt) => {
@@ -677,6 +925,7 @@ var LoomManager = class {
677
925
  type = "pr";
678
926
  identifier = parseInt(wt.branch.replace("pr-", ""), 10);
679
927
  }
928
+ const loomMetadata = await this.metadataManager.readMetadata(wt.path);
680
929
  return {
681
930
  id: `${type}-${identifier}`,
682
931
  path: wt.path,
@@ -684,6 +933,7 @@ var LoomManager = class {
684
933
  type,
685
934
  identifier,
686
935
  port: await this.calculatePort({ type, identifier, originalInput: "" }),
936
+ ...(loomMetadata == null ? void 0 : loomMetadata.description) && { description: loomMetadata.description },
687
937
  createdAt: /* @__PURE__ */ new Date(),
688
938
  lastAccessed: /* @__PURE__ */ new Date()
689
939
  };
@@ -712,7 +962,7 @@ var LoomManager = class {
712
962
  * Ports: handle_existing_worktree() from bash script lines 168-215
713
963
  */
714
964
  async reuseIloom(worktree, input, issueData) {
715
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
965
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
716
966
  const worktreePath = worktree.path;
717
967
  const branchName = worktree.branch;
718
968
  this.loadMainEnvFile();
@@ -727,6 +977,25 @@ var LoomManager = class {
727
977
  }
728
978
  logger.info("Database branch assumed to be already configured for existing worktree");
729
979
  const databaseBranch = void 0;
980
+ const existingMetadata = await this.metadataManager.readMetadata(worktreePath);
981
+ let colorHex;
982
+ if (existingMetadata == null ? void 0 : existingMetadata.colorHex) {
983
+ colorHex = existingMetadata.colorHex;
984
+ logger.debug(`Reusing stored color ${colorHex} for branch ${branchName}`);
985
+ } else {
986
+ const colorData = generateColorFromBranchName(branchName);
987
+ colorHex = colorData.hex;
988
+ logger.debug(`No stored color, using hash-based color ${colorHex} for branch ${branchName}`);
989
+ }
990
+ try {
991
+ const colorData = { hex: colorHex, rgb: hexToRgb(colorHex), index: 0 };
992
+ await this.applyColorSynchronization(worktreePath, branchName, colorData, settingsData, input.options);
993
+ } catch (error) {
994
+ logger.warn(
995
+ `Failed to apply color synchronization: ${error instanceof Error ? error.message : "Unknown error"}`,
996
+ error
997
+ );
998
+ }
730
999
  if (input.type === "issue") {
731
1000
  try {
732
1001
  logger.info("Moving issue to In Progress...");
@@ -749,8 +1018,8 @@ var LoomManager = class {
749
1018
  const executablePath = (_i = input.options) == null ? void 0 : _i.executablePath;
750
1019
  if (enableClaude || enableCode || enableDevServer || enableTerminal) {
751
1020
  logger.info("Launching workspace components...");
752
- const { LoomLauncher } = await import("./LoomLauncher-JF7JZMTZ.js");
753
- const { ClaudeContextManager } = await import("./ClaudeContextManager-3VXA6UPR.js");
1021
+ const { LoomLauncher } = await import("./LoomLauncher-ZV3ZZIBA.js");
1022
+ const { ClaudeContextManager } = await import("./ClaudeContextManager-BN7RE5ZQ.js");
754
1023
  const claudeContext = new ClaudeContextManager(void 0, void 0, this.settings);
755
1024
  const launcher = new LoomLauncher(claudeContext, this.settings);
756
1025
  await launcher.launchLoom({
@@ -768,9 +1037,27 @@ var LoomManager = class {
768
1037
  oneShot,
769
1038
  ...setArguments && { setArguments },
770
1039
  ...executablePath && { executablePath },
771
- sourceEnvOnStart: settingsData.sourceEnvOnStart ?? false
1040
+ sourceEnvOnStart: settingsData.sourceEnvOnStart ?? false,
1041
+ colorTerminal: ((_j = input.options) == null ? void 0 : _j.colorTerminal) ?? ((_k = settingsData.colors) == null ? void 0 : _k.terminal) ?? true,
1042
+ colorHex
772
1043
  });
773
1044
  }
1045
+ const description = (existingMetadata == null ? void 0 : existingMetadata.description) ?? (issueData == null ? void 0 : issueData.title) ?? branchName;
1046
+ if (!existingMetadata) {
1047
+ const issue_numbers = input.type === "issue" ? [String(input.identifier)] : [];
1048
+ const pr_numbers = input.type === "pr" ? [String(input.identifier)] : [];
1049
+ const metadataInput = {
1050
+ description,
1051
+ branchName,
1052
+ worktreePath,
1053
+ issueType: input.type,
1054
+ issue_numbers,
1055
+ pr_numbers,
1056
+ issueTracker: this.issueTracker.providerName,
1057
+ colorHex
1058
+ };
1059
+ await this.metadataManager.writeMetadata(worktreePath, metadataInput);
1060
+ }
774
1061
  const loom = {
775
1062
  id: this.generateLoomId(input),
776
1063
  path: worktreePath,
@@ -778,6 +1065,7 @@ var LoomManager = class {
778
1065
  type: input.type,
779
1066
  identifier: input.identifier,
780
1067
  port,
1068
+ description,
781
1069
  createdAt: /* @__PURE__ */ new Date(),
782
1070
  // We don't have actual creation date, use now
783
1071
  lastAccessed: /* @__PURE__ */ new Date(),
@@ -799,7 +1087,7 @@ var LoomManager = class {
799
1087
  };
800
1088
 
801
1089
  // src/lib/EnvironmentManager.ts
802
- import fs3 from "fs-extra";
1090
+ import fs4 from "fs-extra";
803
1091
  var logger2 = createLogger({ prefix: "\u{1F4DD}" });
804
1092
  var EnvironmentManager = class {
805
1093
  constructor() {
@@ -815,15 +1103,15 @@ var EnvironmentManager = class {
815
1103
  if (!validation.valid) {
816
1104
  throw new Error(validation.error ?? "Invalid variable name");
817
1105
  }
818
- const fileExists = await fs3.pathExists(filePath);
1106
+ const fileExists = await fs4.pathExists(filePath);
819
1107
  if (!fileExists) {
820
1108
  logger2.info(`Creating ${filePath} with ${key}...`);
821
1109
  const content = formatEnvLine(key, value);
822
- await fs3.writeFile(filePath, content, "utf8");
1110
+ await fs4.writeFile(filePath, content, "utf8");
823
1111
  logger2.success(`${filePath} created with ${key}`);
824
1112
  return;
825
1113
  }
826
- const existingContent = await fs3.readFile(filePath, "utf8");
1114
+ const existingContent = await fs4.readFile(filePath, "utf8");
827
1115
  const envMap = parseEnvFile(existingContent);
828
1116
  let backupPath;
829
1117
  if (backup) {
@@ -860,7 +1148,7 @@ var EnvironmentManager = class {
860
1148
  logger2.success(`${key} updated successfully`);
861
1149
  }
862
1150
  const newContent = newLines.join("\n");
863
- await fs3.writeFile(filePath, newContent, "utf8");
1151
+ await fs4.writeFile(filePath, newContent, "utf8");
864
1152
  return backupPath;
865
1153
  }
866
1154
  /**
@@ -868,7 +1156,7 @@ var EnvironmentManager = class {
868
1156
  */
869
1157
  async readEnvFile(filePath) {
870
1158
  try {
871
- const content = await fs3.readFile(filePath, "utf8");
1159
+ const content = await fs4.readFile(filePath, "utf8");
872
1160
  return parseEnvFile(content);
873
1161
  } catch (error) {
874
1162
  logger2.debug(
@@ -891,12 +1179,12 @@ var EnvironmentManager = class {
891
1179
  * @private
892
1180
  */
893
1181
  async copyIfExists(source, destination) {
894
- const sourceExists = await fs3.pathExists(source);
1182
+ const sourceExists = await fs4.pathExists(source);
895
1183
  if (!sourceExists) {
896
1184
  logger2.debug(`Source file ${source} does not exist, skipping copy`);
897
1185
  return;
898
1186
  }
899
- await fs3.copy(source, destination, { overwrite: false });
1187
+ await fs4.copy(source, destination, { overwrite: false });
900
1188
  logger2.success(`Copied ${source} to ${destination}`);
901
1189
  }
902
1190
  /**
@@ -957,7 +1245,7 @@ var EnvironmentManager = class {
957
1245
  */
958
1246
  async validateEnvFile(filePath) {
959
1247
  try {
960
- const content = await fs3.readFile(filePath, "utf8");
1248
+ const content = await fs4.readFile(filePath, "utf8");
961
1249
  const envMap = parseEnvFile(content);
962
1250
  const errors = [];
963
1251
  for (const [key, value] of envMap.entries()) {
@@ -985,19 +1273,19 @@ var EnvironmentManager = class {
985
1273
  async createBackup(filePath) {
986
1274
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
987
1275
  const backupPath = `${filePath}${this.backupSuffix}-${timestamp}`;
988
- await fs3.copy(filePath, backupPath);
1276
+ await fs4.copy(filePath, backupPath);
989
1277
  logger2.debug(`Created backup at ${backupPath}`);
990
1278
  return backupPath;
991
1279
  }
992
1280
  };
993
1281
 
994
1282
  // src/lib/CLIIsolationManager.ts
995
- import fs4 from "fs-extra";
996
- import path3 from "path";
997
- import os from "os";
1283
+ import fs5 from "fs-extra";
1284
+ import path4 from "path";
1285
+ import os2 from "os";
998
1286
  var CLIIsolationManager = class {
999
1287
  constructor() {
1000
- this.iloomBinDir = path3.join(os.homedir(), ".iloom", "bin");
1288
+ this.iloomBinDir = path4.join(os2.homedir(), ".iloom", "bin");
1001
1289
  }
1002
1290
  /**
1003
1291
  * Setup CLI isolation for a worktree
@@ -1012,7 +1300,7 @@ var CLIIsolationManager = class {
1012
1300
  async setupCLIIsolation(worktreePath, identifier, binEntries) {
1013
1301
  await this.buildProject(worktreePath);
1014
1302
  await this.verifyBinTargets(worktreePath, binEntries);
1015
- await fs4.ensureDir(this.iloomBinDir);
1303
+ await fs5.ensureDir(this.iloomBinDir);
1016
1304
  const symlinkNames = await this.createVersionedSymlinks(
1017
1305
  worktreePath,
1018
1306
  identifier,
@@ -1042,13 +1330,13 @@ var CLIIsolationManager = class {
1042
1330
  */
1043
1331
  async verifyBinTargets(worktreePath, binEntries) {
1044
1332
  for (const binPath of Object.values(binEntries)) {
1045
- const targetPath = path3.resolve(worktreePath, binPath);
1046
- const exists = await fs4.pathExists(targetPath);
1333
+ const targetPath = path4.resolve(worktreePath, binPath);
1334
+ const exists = await fs5.pathExists(targetPath);
1047
1335
  if (!exists) {
1048
1336
  throw new Error(`Bin target does not exist: ${targetPath}`);
1049
1337
  }
1050
1338
  try {
1051
- await fs4.access(targetPath, fs4.constants.X_OK);
1339
+ await fs5.access(targetPath, fs5.constants.X_OK);
1052
1340
  } catch {
1053
1341
  }
1054
1342
  }
@@ -1064,9 +1352,9 @@ var CLIIsolationManager = class {
1064
1352
  const symlinkNames = [];
1065
1353
  for (const [binName, binPath] of Object.entries(binEntries)) {
1066
1354
  const versionedName = `${binName}-${identifier}`;
1067
- const targetPath = path3.resolve(worktreePath, binPath);
1068
- const symlinkPath = path3.join(this.iloomBinDir, versionedName);
1069
- await fs4.symlink(targetPath, symlinkPath);
1355
+ const targetPath = path4.resolve(worktreePath, binPath);
1356
+ const symlinkPath = path4.join(this.iloomBinDir, versionedName);
1357
+ await fs5.symlink(targetPath, symlinkPath);
1070
1358
  logger.success(`CLI available: ${versionedName}`);
1071
1359
  symlinkNames.push(versionedName);
1072
1360
  }
@@ -1119,12 +1407,12 @@ var CLIIsolationManager = class {
1119
1407
  async cleanupVersionedExecutables(identifier) {
1120
1408
  const removed = [];
1121
1409
  try {
1122
- const files = await fs4.readdir(this.iloomBinDir);
1410
+ const files = await fs5.readdir(this.iloomBinDir);
1123
1411
  for (const file of files) {
1124
1412
  if (this.matchesIdentifier(file, identifier)) {
1125
- const symlinkPath = path3.join(this.iloomBinDir, file);
1413
+ const symlinkPath = path4.join(this.iloomBinDir, file);
1126
1414
  try {
1127
- await fs4.unlink(symlinkPath);
1415
+ await fs5.unlink(symlinkPath);
1128
1416
  removed.push(file);
1129
1417
  } catch (error) {
1130
1418
  const isEnoent = error && typeof error === "object" && "code" in error && error.code === "ENOENT";
@@ -1160,15 +1448,15 @@ var CLIIsolationManager = class {
1160
1448
  async findOrphanedSymlinks() {
1161
1449
  const orphaned = [];
1162
1450
  try {
1163
- const files = await fs4.readdir(this.iloomBinDir);
1451
+ const files = await fs5.readdir(this.iloomBinDir);
1164
1452
  for (const file of files) {
1165
- const symlinkPath = path3.join(this.iloomBinDir, file);
1453
+ const symlinkPath = path4.join(this.iloomBinDir, file);
1166
1454
  try {
1167
- const stats = await fs4.lstat(symlinkPath);
1455
+ const stats = await fs5.lstat(symlinkPath);
1168
1456
  if (stats.isSymbolicLink()) {
1169
- const target = await fs4.readlink(symlinkPath);
1457
+ const target = await fs5.readlink(symlinkPath);
1170
1458
  try {
1171
- await fs4.access(target);
1459
+ await fs5.access(target);
1172
1460
  } catch {
1173
1461
  orphaned.push({
1174
1462
  name: file,
@@ -1203,7 +1491,7 @@ var CLIIsolationManager = class {
1203
1491
  let removedCount = 0;
1204
1492
  for (const symlink of orphaned) {
1205
1493
  try {
1206
- await fs4.unlink(symlink.path);
1494
+ await fs5.unlink(symlink.path);
1207
1495
  removedCount++;
1208
1496
  logger.success(`Removed orphaned symlink: ${symlink.name}`);
1209
1497
  } catch (error) {
@@ -1229,6 +1517,7 @@ var CLIIsolationManager = class {
1229
1517
  };
1230
1518
 
1231
1519
  // src/lib/DatabaseManager.ts
1520
+ import fs6 from "fs-extra";
1232
1521
  var logger3 = createLogger({ prefix: "\u{1F5C2}\uFE0F" });
1233
1522
  var DatabaseManager = class {
1234
1523
  constructor(provider, environment, databaseUrlEnvVarName = "DATABASE_URL") {
@@ -1251,17 +1540,17 @@ var DatabaseManager = class {
1251
1540
  * Check if database branching should be used
1252
1541
  * Requires BOTH conditions:
1253
1542
  * 1. Database provider is properly configured (checked via provider.isConfigured())
1254
- * 2. .env file contains the configured database URL variable
1543
+ * 2. Any dotenv-flow file contains the configured database URL variable
1255
1544
  */
1256
- async shouldUseDatabaseBranching(envFilePath) {
1545
+ async shouldUseDatabaseBranching(workspacePath) {
1257
1546
  if (!this.provider.isConfigured()) {
1258
1547
  logger3.debug("Skipping database branching: Database provider not configured");
1259
1548
  return false;
1260
1549
  }
1261
- const hasDatabaseUrl = await this.hasDatabaseUrlInEnv(envFilePath);
1550
+ const hasDatabaseUrl = await this.hasDatabaseUrlInEnv(workspacePath);
1262
1551
  if (!hasDatabaseUrl) {
1263
1552
  logger3.debug(
1264
- "Skipping database branching: configured database URL variable not found in .env file"
1553
+ "Skipping database branching: configured database URL variable not found in any env file"
1265
1554
  );
1266
1555
  return false;
1267
1556
  }
@@ -1272,12 +1561,12 @@ var DatabaseManager = class {
1272
1561
  * Returns connection string if branch was created, null if skipped
1273
1562
  *
1274
1563
  * @param branchName - Name of the branch to create
1275
- * @param envFilePath - Path to .env file for configuration checks
1564
+ * @param workspacePath - Path to workspace for configuration checks (checks all dotenv-flow files)
1276
1565
  * @param cwd - Optional working directory to run commands from
1277
1566
  * @param fromBranch - Optional parent branch to create from (for child looms)
1278
1567
  */
1279
- async createBranchIfConfigured(branchName, envFilePath, cwd, fromBranch) {
1280
- if (!await this.shouldUseDatabaseBranching(envFilePath)) {
1568
+ async createBranchIfConfigured(branchName, workspacePath, cwd, fromBranch) {
1569
+ if (!await this.shouldUseDatabaseBranching(workspacePath)) {
1281
1570
  return null;
1282
1571
  }
1283
1572
  if (!await this.provider.isCliAvailable()) {
@@ -1404,19 +1693,24 @@ var DatabaseManager = class {
1404
1693
  return null;
1405
1694
  }
1406
1695
  /**
1407
- * Check if .env has the configured database URL variable
1696
+ * Check if any dotenv-flow file has the configured database URL variable
1408
1697
  * CRITICAL: If user explicitly configured a custom variable name (not default),
1409
- * throw an error if it's missing from .env
1698
+ * throw an error if it's missing from all env files
1410
1699
  */
1411
- async hasDatabaseUrlInEnv(envFilePath) {
1700
+ async hasDatabaseUrlInEnv(workspacePath) {
1412
1701
  try {
1413
- const envMap = await this.environment.readEnvFile(envFilePath);
1414
1702
  if (this.databaseUrlEnvVarName !== "DATABASE_URL") {
1415
1703
  logger3.debug(`Looking for custom database URL variable: ${this.databaseUrlEnvVarName}`);
1416
1704
  } else {
1417
1705
  logger3.debug("Looking for default database URL variable: DATABASE_URL");
1418
1706
  }
1419
- if (envMap.has(this.databaseUrlEnvVarName)) {
1707
+ const hasConfiguredVar = await hasVariableInAnyEnvFile(
1708
+ workspacePath,
1709
+ this.databaseUrlEnvVarName,
1710
+ async (p) => fs6.pathExists(p),
1711
+ async (p, v) => this.environment.getEnvVariable(p, v)
1712
+ );
1713
+ if (hasConfiguredVar) {
1420
1714
  if (this.databaseUrlEnvVarName !== "DATABASE_URL") {
1421
1715
  logger3.debug(`\u2705 Found custom database URL variable: ${this.databaseUrlEnvVarName}`);
1422
1716
  } else {
@@ -1425,20 +1719,25 @@ var DatabaseManager = class {
1425
1719
  return true;
1426
1720
  }
1427
1721
  if (this.databaseUrlEnvVarName !== "DATABASE_URL") {
1428
- logger3.debug(`\u274C Custom database URL variable '${this.databaseUrlEnvVarName}' not found in .env file`);
1722
+ logger3.debug(`\u274C Custom database URL variable '${this.databaseUrlEnvVarName}' not found in any env file`);
1429
1723
  throw new Error(
1430
- `Configured database URL environment variable '${this.databaseUrlEnvVarName}' not found in .env file. Please add it to your .env file or update your iloom configuration.`
1724
+ `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.`
1431
1725
  );
1432
1726
  }
1433
- const hasDefaultVar = envMap.has("DATABASE_URL");
1727
+ const hasDefaultVar = await hasVariableInAnyEnvFile(
1728
+ workspacePath,
1729
+ "DATABASE_URL",
1730
+ async (p) => fs6.pathExists(p),
1731
+ async (p, v) => this.environment.getEnvVariable(p, v)
1732
+ );
1434
1733
  if (hasDefaultVar) {
1435
1734
  logger3.debug("\u2705 Found fallback DATABASE_URL variable");
1436
1735
  } else {
1437
- logger3.debug("\u274C No DATABASE_URL variable found in .env file");
1736
+ logger3.debug("\u274C No DATABASE_URL variable found in any env file");
1438
1737
  }
1439
1738
  return hasDefaultVar;
1440
1739
  } catch (error) {
1441
- if (error instanceof Error && error.message.includes("not found in .env")) {
1740
+ if (error instanceof Error && error.message.includes("not found in")) {
1442
1741
  throw error;
1443
1742
  }
1444
1743
  return false;
@@ -1447,7 +1746,7 @@ var DatabaseManager = class {
1447
1746
  };
1448
1747
 
1449
1748
  // src/lib/ResourceCleanup.ts
1450
- import path4 from "path";
1749
+ import path5 from "path";
1451
1750
  var ResourceCleanup = class {
1452
1751
  constructor(gitWorktree, processManager, database, cliIsolation, settingsManager) {
1453
1752
  this.gitWorktree = gitWorktree;
@@ -1455,6 +1754,7 @@ var ResourceCleanup = class {
1455
1754
  this.database = database;
1456
1755
  this.cliIsolation = cliIsolation;
1457
1756
  this.settingsManager = settingsManager ?? new SettingsManager();
1757
+ this.metadataManager = new MetadataManager();
1458
1758
  }
1459
1759
  /**
1460
1760
  * Cleanup a worktree and associated resources
@@ -1542,7 +1842,7 @@ ${blockerMessage}`);
1542
1842
  }
1543
1843
  let databaseConfig = null;
1544
1844
  if (!options.keepDatabase && worktree) {
1545
- const envFilePath = path4.join(worktree.path, ".env");
1845
+ const envFilePath = path5.join(worktree.path, ".env");
1546
1846
  try {
1547
1847
  const shouldCleanup = this.database ? await this.database.shouldUseDatabaseBranching(envFilePath) : false;
1548
1848
  databaseConfig = { shouldCleanup, envFilePath };
@@ -1585,6 +1885,8 @@ ${blockerMessage}`);
1585
1885
  success: true,
1586
1886
  message: `Worktree removed: ${worktree.path}`
1587
1887
  });
1888
+ await this.metadataManager.deleteMetadata(worktree.path);
1889
+ logger.debug(`Metadata file cleanup attempted for: ${worktree.path}`);
1588
1890
  } catch (error) {
1589
1891
  const err = error instanceof Error ? error : new Error("Unknown error");
1590
1892
  errors.push(err);
@@ -1845,7 +2147,7 @@ ${blockerMessage}`);
1845
2147
  return false;
1846
2148
  }
1847
2149
  try {
1848
- const envFilePath = path4.join(worktreePath, ".env");
2150
+ const envFilePath = path5.join(worktreePath, ".env");
1849
2151
  const shouldCleanup = await this.database.shouldUseDatabaseBranching(envFilePath);
1850
2152
  let cwd;
1851
2153
  try {
@@ -1980,10 +2282,11 @@ Please resolve before cleanup - you have some options:
1980
2282
  };
1981
2283
 
1982
2284
  export {
2285
+ MetadataManager,
1983
2286
  LoomManager,
1984
2287
  EnvironmentManager,
1985
2288
  CLIIsolationManager,
1986
2289
  DatabaseManager,
1987
2290
  ResourceCleanup
1988
2291
  };
1989
- //# sourceMappingURL=chunk-HBYZH6GD.js.map
2292
+ //# sourceMappingURL=chunk-2IJEMXOB.js.map