@iloom/cli 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +58 -16
  3. package/dist/{BranchNamingService-B5PVRR7F.js → BranchNamingService-FLPUUFOB.js} +2 -2
  4. package/dist/ClaudeContextManager-KE5TBZVZ.js +14 -0
  5. package/dist/ClaudeService-CRSETT3A.js +13 -0
  6. package/dist/{GitHubService-S2OGUTDR.js → GitHubService-O7U4UQ7N.js} +3 -3
  7. package/dist/{LoomLauncher-5LFM4LXB.js → LoomLauncher-NL65LSKP.js} +6 -6
  8. package/dist/{MetadataManager-DFI73J3G.js → MetadataManager-XJ2YB762.js} +2 -2
  9. package/dist/PRManager-2ABCWXHW.js +16 -0
  10. package/dist/{ProjectCapabilityDetector-S5FLNCFI.js → ProjectCapabilityDetector-UZYW32SY.js} +3 -3
  11. package/dist/{PromptTemplateManager-C3DK6XZL.js → PromptTemplateManager-7L3HJQQU.js} +2 -2
  12. package/dist/README.md +58 -16
  13. package/dist/{SettingsManager-35F5RUJH.js → SettingsManager-YU4VYPTW.js} +2 -2
  14. package/dist/agents/iloom-issue-analyze-and-plan.md +42 -17
  15. package/dist/agents/iloom-issue-analyzer.md +14 -14
  16. package/dist/agents/iloom-issue-complexity-evaluator.md +38 -15
  17. package/dist/agents/iloom-issue-enhancer.md +15 -15
  18. package/dist/agents/iloom-issue-implementer.md +44 -15
  19. package/dist/agents/iloom-issue-planner.md +121 -17
  20. package/dist/agents/iloom-issue-reviewer.md +15 -15
  21. package/dist/{build-FJVYP7EV.js → build-O2EJHDEW.js} +9 -9
  22. package/dist/{chunk-ZPSTA5PR.js → chunk-3CDWFEGL.js} +2 -2
  23. package/dist/{chunk-VU3QMIP2.js → chunk-453NC377.js} +91 -15
  24. package/dist/chunk-453NC377.js.map +1 -0
  25. package/dist/{chunk-UQIXZ3BA.js → chunk-5V74K5ZA.js} +2 -2
  26. package/dist/{chunk-7WANFUIK.js → chunk-6TL3BYH6.js} +2 -2
  27. package/dist/{chunk-5TXLVEXT.js → chunk-C3AKFAIR.js} +2 -2
  28. package/dist/{chunk-K7SEEHKO.js → chunk-CNSTXBJ3.js} +7 -419
  29. package/dist/chunk-CNSTXBJ3.js.map +1 -0
  30. package/dist/{chunk-5IWU3HXE.js → chunk-EPPPDVHD.js} +23 -11
  31. package/dist/chunk-EPPPDVHD.js.map +1 -0
  32. package/dist/{chunk-UB4TFAXJ.js → chunk-FEAJR6PN.js} +9 -55
  33. package/dist/chunk-FEAJR6PN.js.map +1 -0
  34. package/dist/{chunk-6YSFTPKW.js → chunk-FM4KBPVA.js} +18 -13
  35. package/dist/chunk-FM4KBPVA.js.map +1 -0
  36. package/dist/{chunk-AEIMYF4P.js → chunk-FP7G7DG3.js} +6 -2
  37. package/dist/chunk-FP7G7DG3.js.map +1 -0
  38. package/dist/{chunk-LT3SGBR7.js → chunk-GCPAZSGV.js} +36 -2
  39. package/dist/{chunk-LT3SGBR7.js.map → chunk-GCPAZSGV.js.map} +1 -1
  40. package/dist/chunk-GJMEKEI5.js +517 -0
  41. package/dist/chunk-GJMEKEI5.js.map +1 -0
  42. package/dist/{chunk-64O2UIWO.js → chunk-GV5X6XUE.js} +4 -4
  43. package/dist/{chunk-7Q66W4OH.js → chunk-HBJITKSZ.js} +37 -1
  44. package/dist/chunk-HBJITKSZ.js.map +1 -0
  45. package/dist/{chunk-7HIRPCKU.js → chunk-HVQNVRAF.js} +2 -2
  46. package/dist/{chunk-BXCPJJYM.js → chunk-ITN64ENQ.js} +1 -1
  47. package/dist/chunk-ITN64ENQ.js.map +1 -0
  48. package/dist/{chunk-6U6VI4SZ.js → chunk-KVS4XGBQ.js} +4 -4
  49. package/dist/{chunk-AXX3QIKK.js → chunk-LLWX3PCW.js} +2 -2
  50. package/dist/{chunk-WIJWIKAN.js → chunk-LQBLDI47.js} +105 -7
  51. package/dist/chunk-LQBLDI47.js.map +1 -0
  52. package/dist/{chunk-SN3Z6EZO.js → chunk-N7FVXZNI.js} +2 -2
  53. package/dist/chunk-NTIZLX42.js +822 -0
  54. package/dist/chunk-NTIZLX42.js.map +1 -0
  55. package/dist/{chunk-PMVWQBWS.js → chunk-S7YMZQUD.js} +31 -45
  56. package/dist/chunk-S7YMZQUD.js.map +1 -0
  57. package/dist/chunk-TIYJEEVO.js +79 -0
  58. package/dist/chunk-TIYJEEVO.js.map +1 -0
  59. package/dist/{chunk-EK3XCAAS.js → chunk-UDRZY65Y.js} +2 -2
  60. package/dist/{chunk-3PT7RKL5.js → chunk-USJSNHGG.js} +2 -2
  61. package/dist/{chunk-CFUWQHCJ.js → chunk-VWGKGNJP.js} +114 -35
  62. package/dist/chunk-VWGKGNJP.js.map +1 -0
  63. package/dist/{chunk-F6WVM437.js → chunk-WFQ5CLTR.js} +6 -3
  64. package/dist/chunk-WFQ5CLTR.js.map +1 -0
  65. package/dist/{chunk-TRQ76ISK.js → chunk-Z6BO53V7.js} +9 -9
  66. package/dist/{chunk-GEXP5IOF.js → chunk-ZA575VLF.js} +21 -8
  67. package/dist/chunk-ZA575VLF.js.map +1 -0
  68. package/dist/{claude-H33OQMXO.js → claude-6H36IBHO.js} +4 -2
  69. package/dist/{cleanup-OU2HFOOG.js → cleanup-ZPOMRSNN.js} +20 -16
  70. package/dist/cleanup-ZPOMRSNN.js.map +1 -0
  71. package/dist/cli.js +511 -959
  72. package/dist/cli.js.map +1 -1
  73. package/dist/commit-6S2RIA2K.js +237 -0
  74. package/dist/commit-6S2RIA2K.js.map +1 -0
  75. package/dist/{compile-ULNO5F7Q.js → compile-LRMAADUT.js} +9 -9
  76. package/dist/{contribute-T7ENST5N.js → contribute-GXKOIA42.js} +99 -31
  77. package/dist/contribute-GXKOIA42.js.map +1 -0
  78. package/dist/{dev-server-4RCDJ5MU.js → dev-server-GREJUEKW.js} +22 -74
  79. package/dist/dev-server-GREJUEKW.js.map +1 -0
  80. package/dist/{feedback-O4Q55SVS.js → feedback-G7G5QCY4.js} +10 -10
  81. package/dist/{git-FVMGBHC2.js → git-ENLT2VNI.js} +6 -4
  82. package/dist/hooks/iloom-hook.js +30 -2
  83. package/dist/{ignite-VHV65WEZ.js → ignite-YUAOJ5PP.js} +20 -20
  84. package/dist/ignite-YUAOJ5PP.js.map +1 -0
  85. package/dist/index.d.ts +71 -27
  86. package/dist/index.js +196 -266
  87. package/dist/index.js.map +1 -1
  88. package/dist/init-XQQMFDM6.js +21 -0
  89. package/dist/{lint-5JMCWE4Y.js → lint-OFVN7FT6.js} +9 -9
  90. package/dist/mcp/issue-management-server.js +359 -13
  91. package/dist/mcp/issue-management-server.js.map +1 -1
  92. package/dist/mcp/recap-server.js +13 -4
  93. package/dist/mcp/recap-server.js.map +1 -1
  94. package/dist/{open-WHVUYGPY.js → open-MCWQAPSZ.js} +25 -76
  95. package/dist/open-MCWQAPSZ.js.map +1 -0
  96. package/dist/{projects-SA76I4TZ.js → projects-PQOTWUII.js} +11 -4
  97. package/dist/projects-PQOTWUII.js.map +1 -0
  98. package/dist/prompts/init-prompt.txt +63 -59
  99. package/dist/prompts/issue-prompt.txt +132 -63
  100. package/dist/prompts/pr-prompt.txt +3 -3
  101. package/dist/prompts/regular-prompt.txt +16 -18
  102. package/dist/prompts/session-summary-prompt.txt +13 -13
  103. package/dist/{rebase-5EY3Q6XP.js → rebase-RKQED567.js} +53 -8
  104. package/dist/rebase-RKQED567.js.map +1 -0
  105. package/dist/{recap-VOOUXOGP.js → recap-ZKGHZCX6.js} +6 -6
  106. package/dist/{run-NCRK5NPR.js → run-CCG24PBC.js} +25 -76
  107. package/dist/run-CCG24PBC.js.map +1 -0
  108. package/dist/schema/settings.schema.json +14 -3
  109. package/dist/{shell-SBLXVOVJ.js → shell-2NNSIU34.js} +6 -6
  110. package/dist/{summary-CVFAMDOJ.js → summary-G6L3VAKK.js} +11 -10
  111. package/dist/{summary-CVFAMDOJ.js.map → summary-G6L3VAKK.js.map} +1 -1
  112. package/dist/{test-3KIVXI6J.js → test-QZDOEUIO.js} +9 -9
  113. package/dist/{test-git-ZB6AGGRW.js → test-git-E2BLXR6M.js} +4 -4
  114. package/dist/{test-prefix-FBGXKMPA.js → test-prefix-A7JGGYAA.js} +4 -4
  115. package/dist/{test-webserver-YVQD42W6.js → test-webserver-NRMGT2HB.js} +29 -8
  116. package/dist/test-webserver-NRMGT2HB.js.map +1 -0
  117. package/package.json +3 -1
  118. package/dist/ClaudeContextManager-6J2EB4QU.js +0 -14
  119. package/dist/ClaudeService-O2PB22GX.js +0 -13
  120. package/dist/PRManager-OCSB2HPT.js +0 -14
  121. package/dist/chunk-5IWU3HXE.js.map +0 -1
  122. package/dist/chunk-6YSFTPKW.js.map +0 -1
  123. package/dist/chunk-7Q66W4OH.js.map +0 -1
  124. package/dist/chunk-AEIMYF4P.js.map +0 -1
  125. package/dist/chunk-BXCPJJYM.js.map +0 -1
  126. package/dist/chunk-CFUWQHCJ.js.map +0 -1
  127. package/dist/chunk-F6WVM437.js.map +0 -1
  128. package/dist/chunk-GEXP5IOF.js.map +0 -1
  129. package/dist/chunk-K7SEEHKO.js.map +0 -1
  130. package/dist/chunk-PMVWQBWS.js.map +0 -1
  131. package/dist/chunk-UB4TFAXJ.js.map +0 -1
  132. package/dist/chunk-VU3QMIP2.js.map +0 -1
  133. package/dist/chunk-W6WVRHJ6.js +0 -251
  134. package/dist/chunk-W6WVRHJ6.js.map +0 -1
  135. package/dist/chunk-WIJWIKAN.js.map +0 -1
  136. package/dist/cleanup-OU2HFOOG.js.map +0 -1
  137. package/dist/contribute-T7ENST5N.js.map +0 -1
  138. package/dist/dev-server-4RCDJ5MU.js.map +0 -1
  139. package/dist/ignite-VHV65WEZ.js.map +0 -1
  140. package/dist/init-HB34Q5FH.js +0 -21
  141. package/dist/open-WHVUYGPY.js.map +0 -1
  142. package/dist/projects-SA76I4TZ.js.map +0 -1
  143. package/dist/rebase-5EY3Q6XP.js.map +0 -1
  144. package/dist/run-NCRK5NPR.js.map +0 -1
  145. package/dist/test-webserver-YVQD42W6.js.map +0 -1
  146. /package/dist/{BranchNamingService-B5PVRR7F.js.map → BranchNamingService-FLPUUFOB.js.map} +0 -0
  147. /package/dist/{ClaudeContextManager-6J2EB4QU.js.map → ClaudeContextManager-KE5TBZVZ.js.map} +0 -0
  148. /package/dist/{ClaudeService-O2PB22GX.js.map → ClaudeService-CRSETT3A.js.map} +0 -0
  149. /package/dist/{GitHubService-S2OGUTDR.js.map → GitHubService-O7U4UQ7N.js.map} +0 -0
  150. /package/dist/{LoomLauncher-5LFM4LXB.js.map → LoomLauncher-NL65LSKP.js.map} +0 -0
  151. /package/dist/{MetadataManager-DFI73J3G.js.map → MetadataManager-XJ2YB762.js.map} +0 -0
  152. /package/dist/{PRManager-OCSB2HPT.js.map → PRManager-2ABCWXHW.js.map} +0 -0
  153. /package/dist/{ProjectCapabilityDetector-S5FLNCFI.js.map → ProjectCapabilityDetector-UZYW32SY.js.map} +0 -0
  154. /package/dist/{PromptTemplateManager-C3DK6XZL.js.map → PromptTemplateManager-7L3HJQQU.js.map} +0 -0
  155. /package/dist/{SettingsManager-35F5RUJH.js.map → SettingsManager-YU4VYPTW.js.map} +0 -0
  156. /package/dist/{build-FJVYP7EV.js.map → build-O2EJHDEW.js.map} +0 -0
  157. /package/dist/{chunk-ZPSTA5PR.js.map → chunk-3CDWFEGL.js.map} +0 -0
  158. /package/dist/{chunk-UQIXZ3BA.js.map → chunk-5V74K5ZA.js.map} +0 -0
  159. /package/dist/{chunk-7WANFUIK.js.map → chunk-6TL3BYH6.js.map} +0 -0
  160. /package/dist/{chunk-5TXLVEXT.js.map → chunk-C3AKFAIR.js.map} +0 -0
  161. /package/dist/{chunk-64O2UIWO.js.map → chunk-GV5X6XUE.js.map} +0 -0
  162. /package/dist/{chunk-7HIRPCKU.js.map → chunk-HVQNVRAF.js.map} +0 -0
  163. /package/dist/{chunk-6U6VI4SZ.js.map → chunk-KVS4XGBQ.js.map} +0 -0
  164. /package/dist/{chunk-AXX3QIKK.js.map → chunk-LLWX3PCW.js.map} +0 -0
  165. /package/dist/{chunk-SN3Z6EZO.js.map → chunk-N7FVXZNI.js.map} +0 -0
  166. /package/dist/{chunk-EK3XCAAS.js.map → chunk-UDRZY65Y.js.map} +0 -0
  167. /package/dist/{chunk-3PT7RKL5.js.map → chunk-USJSNHGG.js.map} +0 -0
  168. /package/dist/{chunk-TRQ76ISK.js.map → chunk-Z6BO53V7.js.map} +0 -0
  169. /package/dist/{claude-H33OQMXO.js.map → claude-6H36IBHO.js.map} +0 -0
  170. /package/dist/{compile-ULNO5F7Q.js.map → compile-LRMAADUT.js.map} +0 -0
  171. /package/dist/{feedback-O4Q55SVS.js.map → feedback-G7G5QCY4.js.map} +0 -0
  172. /package/dist/{git-FVMGBHC2.js.map → git-ENLT2VNI.js.map} +0 -0
  173. /package/dist/{init-HB34Q5FH.js.map → init-XQQMFDM6.js.map} +0 -0
  174. /package/dist/{lint-5JMCWE4Y.js.map → lint-OFVN7FT6.js.map} +0 -0
  175. /package/dist/{recap-VOOUXOGP.js.map → recap-ZKGHZCX6.js.map} +0 -0
  176. /package/dist/{shell-SBLXVOVJ.js.map → shell-2NNSIU34.js.map} +0 -0
  177. /package/dist/{test-3KIVXI6J.js.map → test-QZDOEUIO.js.map} +0 -0
  178. /package/dist/{test-git-ZB6AGGRW.js.map → test-git-E2BLXR6M.js.map} +0 -0
  179. /package/dist/{test-prefix-FBGXKMPA.js.map → test-prefix-A7JGGYAA.js.map} +0 -0
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  SessionSummaryService
4
- } from "./chunk-K7SEEHKO.js";
4
+ } from "./chunk-CNSTXBJ3.js";
5
5
  import "./chunk-NXMDEL3F.js";
6
6
  import {
7
7
  CLIIsolationManager,
@@ -9,30 +9,28 @@ import {
9
9
  EnvironmentManager,
10
10
  LoomManager,
11
11
  ResourceCleanup
12
- } from "./chunk-PMVWQBWS.js";
12
+ } from "./chunk-S7YMZQUD.js";
13
+ import {
14
+ BuildRunner,
15
+ MergeManager
16
+ } from "./chunk-LQBLDI47.js";
13
17
  import {
14
18
  IssueTrackerFactory,
15
19
  generateIssueManagementMcpConfig
16
- } from "./chunk-6YSFTPKW.js";
17
- import "./chunk-7Q66W4OH.js";
20
+ } from "./chunk-FM4KBPVA.js";
18
21
  import {
19
22
  ProcessManager
20
- } from "./chunk-VU3QMIP2.js";
21
- import {
22
- detectPackageManager,
23
- installDependencies,
24
- runScript
25
- } from "./chunk-AXX3QIKK.js";
23
+ } from "./chunk-453NC377.js";
26
24
  import {
27
25
  IdentifierParser
28
- } from "./chunk-UQIXZ3BA.js";
26
+ } from "./chunk-5V74K5ZA.js";
29
27
  import {
30
28
  createNeonProviderFromSettings
31
29
  } from "./chunk-7LSSNB7Y.js";
32
30
  import {
33
31
  InitCommand,
34
32
  ShellCompletion
35
- } from "./chunk-UB4TFAXJ.js";
33
+ } from "./chunk-FEAJR6PN.js";
36
34
  import {
37
35
  FirstRunManager
38
36
  } from "./chunk-Q7POFB5Q.js";
@@ -40,29 +38,35 @@ import "./chunk-F2PWIRV4.js";
40
38
  import {
41
39
  IssueEnhancementService,
42
40
  capitalizeFirstLetter
43
- } from "./chunk-7HIRPCKU.js";
41
+ } from "./chunk-HVQNVRAF.js";
44
42
  import {
45
43
  ProjectCapabilityDetector
46
- } from "./chunk-ZPSTA5PR.js";
47
- import {
48
- getPackageConfig,
49
- hasScript
50
- } from "./chunk-BXCPJJYM.js";
44
+ } from "./chunk-3CDWFEGL.js";
51
45
  import {
52
46
  AgentManager
53
- } from "./chunk-SN3Z6EZO.js";
47
+ } from "./chunk-N7FVXZNI.js";
54
48
  import {
55
- MergeManager
56
- } from "./chunk-WIJWIKAN.js";
49
+ CommitManager,
50
+ UserAbortedCommitError,
51
+ ValidationRunner
52
+ } from "./chunk-NTIZLX42.js";
53
+ import {
54
+ installDependencies
55
+ } from "./chunk-LLWX3PCW.js";
57
56
  import {
58
57
  GitWorktreeManager
59
- } from "./chunk-EK3XCAAS.js";
58
+ } from "./chunk-UDRZY65Y.js";
59
+ import "./chunk-ITN64ENQ.js";
60
60
  import {
61
61
  PRManager
62
- } from "./chunk-5IWU3HXE.js";
62
+ } from "./chunk-EPPPDVHD.js";
63
63
  import {
64
64
  openBrowser
65
65
  } from "./chunk-YETJNRQM.js";
66
+ import {
67
+ IssueManagementProviderFactory
68
+ } from "./chunk-GJMEKEI5.js";
69
+ import "./chunk-HBJITKSZ.js";
66
70
  import {
67
71
  getConfiguredRepoFromSettings,
68
72
  hasMultipleRemotes
@@ -74,16 +78,17 @@ import {
74
78
  } from "./chunk-O7VL5N6S.js";
75
79
  import {
76
80
  ClaudeContextManager
77
- } from "./chunk-7WANFUIK.js";
78
- import "./chunk-6U6VI4SZ.js";
79
- import "./chunk-W6WVRHJ6.js";
81
+ } from "./chunk-6TL3BYH6.js";
82
+ import "./chunk-KVS4XGBQ.js";
83
+ import "./chunk-TIYJEEVO.js";
80
84
  import {
81
85
  extractSettingsOverrides
82
86
  } from "./chunk-GYCR2LOU.js";
83
87
  import {
84
88
  DefaultBranchNamingService
85
- } from "./chunk-5TXLVEXT.js";
89
+ } from "./chunk-C3AKFAIR.js";
86
90
  import {
91
+ GitCommandError,
87
92
  executeGitCommand,
88
93
  extractIssueNumber,
89
94
  findMainWorktreePathWithSettings,
@@ -91,30 +96,29 @@ import {
91
96
  getMergeTargetBranch,
92
97
  getRepoRoot,
93
98
  isPlaceholderCommit,
99
+ isValidGitRepo,
94
100
  pushBranchToRemote,
95
101
  removePlaceholderCommitFromHead,
96
102
  removePlaceholderCommitFromHistory
97
- } from "./chunk-GEXP5IOF.js";
103
+ } from "./chunk-ZA575VLF.js";
98
104
  import {
99
105
  SettingsManager
100
- } from "./chunk-F6WVM437.js";
106
+ } from "./chunk-WFQ5CLTR.js";
101
107
  import {
102
108
  MetadataManager
103
- } from "./chunk-CFUWQHCJ.js";
109
+ } from "./chunk-VWGKGNJP.js";
104
110
  import {
105
111
  GitHubService
106
- } from "./chunk-3PT7RKL5.js";
107
- import "./chunk-LT3SGBR7.js";
112
+ } from "./chunk-USJSNHGG.js";
113
+ import "./chunk-GCPAZSGV.js";
108
114
  import {
109
- promptCommitAction,
110
115
  promptConfirmation,
111
116
  waitForKeypress
112
117
  } from "./chunk-ZX3GTM7O.js";
113
118
  import "./chunk-433MOLAU.js";
114
119
  import {
115
- detectClaudeCli,
116
120
  launchClaude
117
- } from "./chunk-AEIMYF4P.js";
121
+ } from "./chunk-FP7G7DG3.js";
118
122
  import {
119
123
  getLogger,
120
124
  withLogger
@@ -687,7 +691,8 @@ var EnhanceCommand = class {
687
691
  "mcp__issue_management__get_issue",
688
692
  "mcp__issue_management__get_comment",
689
693
  "mcp__issue_management__create_comment",
690
- "mcp__issue_management__update_comment"
694
+ "mcp__issue_management__update_comment",
695
+ "mcp__issue_management__create_issue"
691
696
  ];
692
697
  disallowedTools = ["Bash(gh api:*)"];
693
698
  getLogger().debug("Configured tool filtering for issue workflow", { allowedTools, disallowedTools });
@@ -815,860 +820,6 @@ IMPORTANT: When you create your analysis comment, tag @${author} in the "Questio
815
820
  }
816
821
  };
817
822
 
818
- // src/lib/ValidationRunner.ts
819
- var ValidationRunner = class {
820
- constructor() {
821
- }
822
- /**
823
- * Run all validations in sequence: typecheck → lint → test
824
- * Fails fast on first error
825
- */
826
- async runValidations(worktreePath, options = {}) {
827
- const startTime = Date.now();
828
- const steps = [];
829
- if (!options.skipTypecheck) {
830
- const typecheckResult = await this.runTypecheck(
831
- worktreePath,
832
- options.dryRun ?? false
833
- );
834
- steps.push(typecheckResult);
835
- if (!typecheckResult.passed && !typecheckResult.skipped) {
836
- return {
837
- success: false,
838
- steps,
839
- totalDuration: Date.now() - startTime
840
- };
841
- }
842
- }
843
- if (!options.skipLint) {
844
- const lintResult = await this.runLint(worktreePath, options.dryRun ?? false);
845
- steps.push(lintResult);
846
- if (!lintResult.passed && !lintResult.skipped) {
847
- return { success: false, steps, totalDuration: Date.now() - startTime };
848
- }
849
- }
850
- if (!options.skipTests) {
851
- const testResult = await this.runTests(
852
- worktreePath,
853
- options.dryRun ?? false
854
- );
855
- steps.push(testResult);
856
- if (!testResult.passed && !testResult.skipped) {
857
- return { success: false, steps, totalDuration: Date.now() - startTime };
858
- }
859
- }
860
- return { success: true, steps, totalDuration: Date.now() - startTime };
861
- }
862
- /**
863
- * Run typecheck validation
864
- * Prefers 'compile' script over 'typecheck' if both exist
865
- */
866
- async runTypecheck(worktreePath, dryRun) {
867
- const stepStartTime = Date.now();
868
- let scriptToRun = null;
869
- try {
870
- const pkgJson = await getPackageConfig(worktreePath);
871
- const hasCompileScript = hasScript(pkgJson, "compile");
872
- const hasTypecheckScript = hasScript(pkgJson, "typecheck");
873
- if (hasCompileScript) {
874
- scriptToRun = "compile";
875
- } else if (hasTypecheckScript) {
876
- scriptToRun = "typecheck";
877
- }
878
- if (!scriptToRun) {
879
- getLogger().debug("Skipping typecheck - no compile or typecheck script found");
880
- return {
881
- step: "typecheck",
882
- passed: true,
883
- skipped: true,
884
- duration: Date.now() - stepStartTime
885
- };
886
- }
887
- } catch (error) {
888
- if (error instanceof Error && error.message.includes("package.json not found")) {
889
- getLogger().debug("Skipping typecheck - no package.json found (non-Node.js project)");
890
- return {
891
- step: "typecheck",
892
- passed: true,
893
- skipped: true,
894
- duration: Date.now() - stepStartTime
895
- };
896
- }
897
- throw error;
898
- }
899
- const packageManager = await detectPackageManager(worktreePath);
900
- if (dryRun) {
901
- const command = packageManager === "npm" ? `npm run ${scriptToRun}` : `${packageManager} ${scriptToRun}`;
902
- getLogger().info(`[DRY RUN] Would run: ${command}`);
903
- return {
904
- step: scriptToRun,
905
- passed: true,
906
- skipped: false,
907
- duration: Date.now() - stepStartTime
908
- };
909
- }
910
- getLogger().info(`Running ${scriptToRun}...`);
911
- try {
912
- await runScript(scriptToRun, worktreePath, [], { quiet: true });
913
- getLogger().success(`${scriptToRun.charAt(0).toUpperCase() + scriptToRun.slice(1)} passed`);
914
- return {
915
- step: scriptToRun,
916
- passed: true,
917
- skipped: false,
918
- duration: Date.now() - stepStartTime
919
- };
920
- } catch {
921
- const fixed = await this.attemptClaudeFix(
922
- scriptToRun,
923
- worktreePath,
924
- packageManager
925
- );
926
- if (fixed) {
927
- return {
928
- step: scriptToRun,
929
- passed: true,
930
- skipped: false,
931
- duration: Date.now() - stepStartTime
932
- };
933
- }
934
- const runCommand = packageManager === "npm" ? `npm run ${scriptToRun}` : `${packageManager} ${scriptToRun}`;
935
- const stepLabel = scriptToRun.charAt(0).toUpperCase() + scriptToRun.slice(1);
936
- throw new Error(
937
- `Error: ${stepLabel} failed.
938
- Fix type errors before merging.
939
-
940
- Run '${runCommand}' to see detailed errors.`
941
- );
942
- }
943
- }
944
- /**
945
- * Run lint validation
946
- */
947
- async runLint(worktreePath, dryRun) {
948
- const stepStartTime = Date.now();
949
- try {
950
- const pkgJson = await getPackageConfig(worktreePath);
951
- const hasLintScript = hasScript(pkgJson, "lint");
952
- if (!hasLintScript) {
953
- getLogger().debug("Skipping lint - no lint script found");
954
- return {
955
- step: "lint",
956
- passed: true,
957
- skipped: true,
958
- duration: Date.now() - stepStartTime
959
- };
960
- }
961
- } catch (error) {
962
- if (error instanceof Error && error.message.includes("package.json not found")) {
963
- getLogger().debug("Skipping lint - no package.json found (non-Node.js project)");
964
- return {
965
- step: "lint",
966
- passed: true,
967
- skipped: true,
968
- duration: Date.now() - stepStartTime
969
- };
970
- }
971
- throw error;
972
- }
973
- const packageManager = await detectPackageManager(worktreePath);
974
- if (dryRun) {
975
- const command = packageManager === "npm" ? "npm run lint" : `${packageManager} lint`;
976
- getLogger().info(`[DRY RUN] Would run: ${command}`);
977
- return {
978
- step: "lint",
979
- passed: true,
980
- skipped: false,
981
- duration: Date.now() - stepStartTime
982
- };
983
- }
984
- getLogger().info("Running lint...");
985
- try {
986
- await runScript("lint", worktreePath, [], { quiet: true });
987
- getLogger().success("Linting passed");
988
- return {
989
- step: "lint",
990
- passed: true,
991
- skipped: false,
992
- duration: Date.now() - stepStartTime
993
- };
994
- } catch {
995
- const fixed = await this.attemptClaudeFix(
996
- "lint",
997
- worktreePath,
998
- packageManager
999
- );
1000
- if (fixed) {
1001
- return {
1002
- step: "lint",
1003
- passed: true,
1004
- skipped: false,
1005
- duration: Date.now() - stepStartTime
1006
- };
1007
- }
1008
- const runCommand = packageManager === "npm" ? "npm run lint" : `${packageManager} lint`;
1009
- throw new Error(
1010
- `Error: Linting failed.
1011
- Fix linting errors before merging.
1012
-
1013
- Run '${runCommand}' to see detailed errors.`
1014
- );
1015
- }
1016
- }
1017
- /**
1018
- * Run test validation
1019
- */
1020
- async runTests(worktreePath, dryRun) {
1021
- const stepStartTime = Date.now();
1022
- try {
1023
- const pkgJson = await getPackageConfig(worktreePath);
1024
- const hasTestScript = hasScript(pkgJson, "test");
1025
- if (!hasTestScript) {
1026
- getLogger().debug("Skipping tests - no test script found");
1027
- return {
1028
- step: "test",
1029
- passed: true,
1030
- skipped: true,
1031
- duration: Date.now() - stepStartTime
1032
- };
1033
- }
1034
- } catch (error) {
1035
- if (error instanceof Error && error.message.includes("package.json not found")) {
1036
- getLogger().debug("Skipping tests - no package.json found (non-Node.js project)");
1037
- return {
1038
- step: "test",
1039
- passed: true,
1040
- skipped: true,
1041
- duration: Date.now() - stepStartTime
1042
- };
1043
- }
1044
- throw error;
1045
- }
1046
- const packageManager = await detectPackageManager(worktreePath);
1047
- if (dryRun) {
1048
- const command = packageManager === "npm" ? "npm run test" : `${packageManager} test`;
1049
- getLogger().info(`[DRY RUN] Would run: ${command}`);
1050
- return {
1051
- step: "test",
1052
- passed: true,
1053
- skipped: false,
1054
- duration: Date.now() - stepStartTime
1055
- };
1056
- }
1057
- getLogger().info("Running tests...");
1058
- try {
1059
- await runScript("test", worktreePath, [], { quiet: true });
1060
- getLogger().success("Tests passed");
1061
- return {
1062
- step: "test",
1063
- passed: true,
1064
- skipped: false,
1065
- duration: Date.now() - stepStartTime
1066
- };
1067
- } catch {
1068
- const fixed = await this.attemptClaudeFix(
1069
- "test",
1070
- worktreePath,
1071
- packageManager
1072
- );
1073
- if (fixed) {
1074
- return {
1075
- step: "test",
1076
- passed: true,
1077
- skipped: false,
1078
- duration: Date.now() - stepStartTime
1079
- };
1080
- }
1081
- const runCommand = packageManager === "npm" ? "npm run test" : `${packageManager} test`;
1082
- throw new Error(
1083
- `Error: Tests failed.
1084
- Fix test failures before merging.
1085
-
1086
- Run '${runCommand}' to see detailed errors.`
1087
- );
1088
- }
1089
- }
1090
- /**
1091
- * Attempt to fix validation errors using Claude
1092
- * Pattern based on MergeManager.attemptClaudeConflictResolution
1093
- *
1094
- * @param validationType - Type of validation that failed ('compile' | 'typecheck' | 'lint' | 'test')
1095
- * @param worktreePath - Path to the worktree
1096
- * @param packageManager - Detected package manager
1097
- * @returns true if Claude fixed the issue, false otherwise
1098
- */
1099
- async attemptClaudeFix(validationType, worktreePath, packageManager) {
1100
- const isClaudeAvailable = await detectClaudeCli();
1101
- if (!isClaudeAvailable) {
1102
- getLogger().debug("Claude CLI not available, skipping auto-fix");
1103
- return false;
1104
- }
1105
- const validationCommand = this.getValidationCommand(validationType, packageManager);
1106
- const prompt = this.getClaudePrompt(validationType, validationCommand);
1107
- const validationTypeCapitalized = validationType.charAt(0).toUpperCase() + validationType.slice(1);
1108
- getLogger().info(`Launching Claude to help fix ${validationTypeCapitalized} errors...`);
1109
- try {
1110
- await launchClaude(prompt, {
1111
- addDir: worktreePath,
1112
- headless: false,
1113
- // Interactive mode
1114
- permissionMode: "acceptEdits",
1115
- // Auto-accept edits
1116
- model: "sonnet"
1117
- // Use Sonnet model
1118
- });
1119
- getLogger().info(`Re-running ${validationTypeCapitalized} after Claude's fixes...`);
1120
- try {
1121
- await runScript(validationType, worktreePath, [], { quiet: true });
1122
- getLogger().success(`${validationTypeCapitalized} passed after Claude auto-fix`);
1123
- return true;
1124
- } catch {
1125
- getLogger().warn(`${validationTypeCapitalized} still failing after Claude's help`);
1126
- return false;
1127
- }
1128
- } catch (error) {
1129
- getLogger().warn("Claude auto-fix failed", {
1130
- error: error instanceof Error ? error.message : String(error)
1131
- });
1132
- return false;
1133
- }
1134
- }
1135
- /**
1136
- * Get validation command string for prompts
1137
- * Uses il commands for multi-language project support
1138
- */
1139
- getValidationCommand(validationType, _packageManager) {
1140
- return `il ${validationType}`;
1141
- }
1142
- /**
1143
- * Get Claude prompt for specific validation type
1144
- * Matches bash script prompts exactly
1145
- */
1146
- getClaudePrompt(validationType, validationCommand) {
1147
- switch (validationType) {
1148
- case "compile":
1149
- case "typecheck":
1150
- return `There are TypeScript errors in this codebase. Please analyze the ${validationType} output, identify all type errors, and fix them. Run '${validationCommand}' to see the errors, then make the necessary code changes to resolve all type issues. When you are done, tell the user to quit using /exit to continue the validation process.`;
1151
- case "lint":
1152
- return `There are ESLint errors in this codebase. Please analyze the linting output, identify all linting issues, and fix them. Run '${validationCommand}' to see the errors, then make the necessary code changes to resolve all linting issues. Focus on code quality, consistency, and following the project's linting rules. When you are done, tell the user to quit using /exit to continue the validation process.`;
1153
- case "test":
1154
- return `There are unit test failures in this codebase. Please analyze the test output to understand what's failing, then fix the issues. This might involve updating test code, fixing bugs in the source code, or updating tests to match new behavior. Run '${validationCommand}' to see the detailed test failures, then make the necessary changes to get all tests passing. When you are done, tell the user to quit using /exit to continue the validation process.`;
1155
- }
1156
- }
1157
- };
1158
-
1159
- // src/utils/vscode.ts
1160
- import { execa } from "execa";
1161
- function isRunningInVSCode() {
1162
- return process.env.TERM_PROGRAM === "vscode";
1163
- }
1164
- function isRunningInCursor() {
1165
- return !!process.env.CURSOR_TRACE_ID;
1166
- }
1167
- function isRunningInAntigravity() {
1168
- return !!process.env.ANTIGRAVITY_CLI_ALIAS;
1169
- }
1170
- async function isVSCodeAvailable() {
1171
- try {
1172
- await execa("command", ["-v", "code"], {
1173
- shell: true,
1174
- timeout: 5e3
1175
- });
1176
- return true;
1177
- } catch (error) {
1178
- logger.debug("VSCode CLI not available", { error });
1179
- return false;
1180
- }
1181
- }
1182
- async function isCursorAvailable() {
1183
- try {
1184
- await execa("command", ["-v", "cursor"], {
1185
- shell: true,
1186
- timeout: 5e3
1187
- });
1188
- return true;
1189
- } catch (error) {
1190
- logger.debug("Cursor CLI not available", { error });
1191
- return false;
1192
- }
1193
- }
1194
- async function isAntigravityAvailable() {
1195
- try {
1196
- await execa("command", ["-v", "agy"], {
1197
- shell: true,
1198
- timeout: 5e3
1199
- });
1200
- return true;
1201
- } catch (error) {
1202
- logger.debug("Antigravity CLI not available", { error });
1203
- return false;
1204
- }
1205
- }
1206
-
1207
- // src/types/index.ts
1208
- var UserAbortedCommitError = class extends Error {
1209
- constructor(message = "User aborted the commit") {
1210
- super(message);
1211
- this.name = "UserAbortedCommitError";
1212
- }
1213
- };
1214
-
1215
- // src/lib/CommitManager.ts
1216
- import { writeFile, readFile as readFile2, unlink } from "fs/promises";
1217
- import { join } from "path";
1218
- import { execa as execa2 } from "execa";
1219
- var CommitManager = class {
1220
- constructor() {
1221
- }
1222
- /**
1223
- * Detect uncommitted changes in a worktree
1224
- * Parses git status --porcelain output into structured GitStatus
1225
- */
1226
- async detectUncommittedChanges(worktreePath) {
1227
- const porcelainOutput = await executeGitCommand(["status", "--porcelain"], {
1228
- cwd: worktreePath
1229
- });
1230
- const { stagedFiles, unstagedFiles } = this.parseGitStatus(porcelainOutput);
1231
- const currentBranch = await executeGitCommand(["branch", "--show-current"], {
1232
- cwd: worktreePath
1233
- });
1234
- return {
1235
- hasUncommittedChanges: stagedFiles.length > 0 || unstagedFiles.length > 0,
1236
- unstagedFiles,
1237
- stagedFiles,
1238
- currentBranch: currentBranch.trim(),
1239
- // Defer these to future enhancement
1240
- isAheadOfRemote: false,
1241
- isBehindRemote: false
1242
- };
1243
- }
1244
- /**
1245
- * Stage all changes and commit with Claude-generated or simple message
1246
- * Tries Claude first, falls back to simple message if Claude unavailable or fails
1247
- */
1248
- async commitChanges(worktreePath, options) {
1249
- if (options.dryRun) {
1250
- getLogger().info("[DRY RUN] Would run: git add -A");
1251
- getLogger().info("[DRY RUN] Would generate commit message with Claude (if available)");
1252
- const fallbackMessage = this.generateFallbackMessage(options);
1253
- const verifyFlag = options.skipVerify ? " --no-verify" : "";
1254
- getLogger().info(`[DRY RUN] Would commit with message${verifyFlag}: ${fallbackMessage}`);
1255
- return;
1256
- }
1257
- await executeGitCommand(["add", "-A"], { cwd: worktreePath });
1258
- let message = null;
1259
- if (!options.message) {
1260
- try {
1261
- message = await this.generateClaudeCommitMessage(worktreePath, options.issueNumber);
1262
- } catch (error) {
1263
- getLogger().debug("Claude commit message generation failed, using fallback", { error });
1264
- }
1265
- }
1266
- message ??= this.generateFallbackMessage(options);
1267
- if (options.skipVerify) {
1268
- getLogger().warn("Skipping pre-commit hooks (--no-verify configured in settings)");
1269
- }
1270
- try {
1271
- if (options.noReview || options.message) {
1272
- const commitArgs = ["commit", "-m", message];
1273
- if (options.skipVerify) {
1274
- commitArgs.push("--no-verify");
1275
- }
1276
- await executeGitCommand(commitArgs, { cwd: worktreePath });
1277
- } else {
1278
- const action = await promptCommitAction(message);
1279
- if (action === "abort") {
1280
- throw new UserAbortedCommitError();
1281
- }
1282
- if (action === "accept") {
1283
- const commitArgs = ["commit", "-m", message];
1284
- if (options.skipVerify) {
1285
- commitArgs.push("--no-verify");
1286
- }
1287
- await executeGitCommand(commitArgs, { cwd: worktreePath });
1288
- } else {
1289
- getLogger().info("Opening editor for commit message review...");
1290
- if (isRunningInAntigravity() && await isAntigravityAvailable()) {
1291
- await this.commitWithExternalEditor(worktreePath, message, options, "agy", "Antigravity");
1292
- } else if (isRunningInCursor() && await isCursorAvailable()) {
1293
- await this.commitWithExternalEditor(worktreePath, message, options, "cursor", "Cursor");
1294
- } else if (isRunningInVSCode() && await isVSCodeAvailable()) {
1295
- await this.commitWithExternalEditor(worktreePath, message, options, "code", "VSCode");
1296
- } else {
1297
- const commitArgs = ["commit", "-e", "-m", message];
1298
- if (options.skipVerify) {
1299
- commitArgs.push("--no-verify");
1300
- }
1301
- await executeGitCommand(commitArgs, {
1302
- cwd: worktreePath,
1303
- stdio: "inherit",
1304
- timeout: 3e5
1305
- // 5 minutes for interactive editing
1306
- });
1307
- }
1308
- }
1309
- }
1310
- } catch (error) {
1311
- if (error instanceof UserAbortedCommitError) {
1312
- throw error;
1313
- }
1314
- if (error instanceof Error && error.message.includes("nothing to commit")) {
1315
- getLogger().info("No changes to commit");
1316
- return;
1317
- }
1318
- throw error;
1319
- }
1320
- }
1321
- /**
1322
- * Commit with external editor CLI (VSCode, Cursor, Antigravity, etc.)
1323
- * Handles file creation, editing, and commit to ensure the file opens
1324
- * in the current editor window (preserves IPC context)
1325
- */
1326
- async commitWithExternalEditor(worktreePath, message, options, cliCommand, editorName) {
1327
- const commitMsgPath = join(worktreePath, ".COMMIT_EDITMSG");
1328
- const initialContent = `${message}
1329
-
1330
- # Please enter the commit message for your changes. Lines starting
1331
- # with '#' will be ignored, and an empty message aborts the commit.
1332
- #
1333
- # Save and close the file to complete the commit.
1334
- `;
1335
- await writeFile(commitMsgPath, initialContent, "utf-8");
1336
- try {
1337
- getLogger().debug(`Opening commit message in ${editorName}: ${commitMsgPath}`);
1338
- await execa2(cliCommand, ["--wait", commitMsgPath], {
1339
- cwd: worktreePath,
1340
- stdio: "inherit"
1341
- });
1342
- const editedContent = await readFile2(commitMsgPath, "utf-8");
1343
- const finalMessage = editedContent.split("\n").filter((line) => !line.startsWith("#")).join("\n").trim();
1344
- if (!finalMessage) {
1345
- throw new UserAbortedCommitError();
1346
- }
1347
- const commitArgs = ["commit", "-F", commitMsgPath];
1348
- if (options.skipVerify) {
1349
- commitArgs.push("--no-verify");
1350
- }
1351
- await writeFile(commitMsgPath, finalMessage, "utf-8");
1352
- await executeGitCommand(commitArgs, { cwd: worktreePath });
1353
- } finally {
1354
- try {
1355
- await unlink(commitMsgPath);
1356
- } catch {
1357
- }
1358
- }
1359
- }
1360
- /**
1361
- * Generate simple fallback commit message when Claude unavailable
1362
- * Used as fallback for Claude-powered commit messages
1363
- */
1364
- generateFallbackMessage(options) {
1365
- if (options.message) {
1366
- return options.message;
1367
- }
1368
- if (options.issueNumber) {
1369
- return `WIP: Auto-commit for issue #${options.issueNumber}
1370
-
1371
- Fixes #${options.issueNumber}`;
1372
- } else {
1373
- return "WIP: Auto-commit uncommitted changes";
1374
- }
1375
- }
1376
- /**
1377
- * Parse git status --porcelain output
1378
- * Format: "XY filename" where X=index, Y=worktree
1379
- * Examples:
1380
- * "M file.ts" - staged modification
1381
- * " M file.ts" - unstaged modification
1382
- * "MM file.ts" - both staged and unstaged
1383
- * "?? file.ts" - untracked
1384
- */
1385
- parseGitStatus(porcelainOutput) {
1386
- const stagedFiles = [];
1387
- const unstagedFiles = [];
1388
- if (!porcelainOutput.trim()) {
1389
- return { stagedFiles, unstagedFiles };
1390
- }
1391
- const lines = porcelainOutput.split("\n").filter((line) => line.trim());
1392
- for (const line of lines) {
1393
- if (line.length < 3) continue;
1394
- const indexStatus = line[0];
1395
- const worktreeStatus = line[1];
1396
- const filename = line.substring(3);
1397
- if (indexStatus !== " " && indexStatus !== "?") {
1398
- stagedFiles.push(filename);
1399
- }
1400
- if (worktreeStatus !== " " || line.startsWith("??")) {
1401
- unstagedFiles.push(filename);
1402
- }
1403
- }
1404
- return { stagedFiles, unstagedFiles };
1405
- }
1406
- /**
1407
- * Generate commit message using Claude Code
1408
- * Claude examines the git repository directly via --add-dir option
1409
- * Returns null if Claude unavailable or fails validation
1410
- */
1411
- async generateClaudeCommitMessage(worktreePath, issueNumber) {
1412
- const startTime = Date.now();
1413
- getLogger().info("Starting Claude commit message generation...", {
1414
- worktreePath: worktreePath.split("/").pop(),
1415
- // Just show the folder name for privacy
1416
- issueNumber
1417
- });
1418
- getLogger().debug("Checking Claude CLI availability...");
1419
- const isClaudeAvailable = await detectClaudeCli();
1420
- if (!isClaudeAvailable) {
1421
- getLogger().info("Claude CLI not available, skipping Claude commit message generation");
1422
- return null;
1423
- }
1424
- getLogger().debug("Claude CLI is available");
1425
- getLogger().debug("Building commit message prompt...");
1426
- const prompt = this.buildCommitMessagePrompt(issueNumber);
1427
- getLogger().debug("Prompt built", { promptLength: prompt.length });
1428
- getLogger().debug("Claude prompt content:", {
1429
- prompt,
1430
- truncatedPreview: prompt.substring(0, 500) + (prompt.length > 500 ? "...[truncated]" : "")
1431
- });
1432
- try {
1433
- getLogger().info("Calling Claude CLI for commit message generation...");
1434
- const claudeStartTime = Date.now();
1435
- const claudeOptions = {
1436
- headless: true,
1437
- addDir: worktreePath,
1438
- model: "claude-haiku-4-5-20251001",
1439
- // Fast, cost-effective model
1440
- timeout: 12e4,
1441
- // 120 second timeout
1442
- appendSystemPrompt: "Output only the requested content. Never include preamble, analysis, or meta-commentary. Your response is used verbatim."
1443
- };
1444
- getLogger().debug("Claude CLI call parameters:", {
1445
- options: claudeOptions,
1446
- worktreePathForAnalysis: worktreePath,
1447
- addDirContents: "Will include entire worktree directory for analysis"
1448
- });
1449
- const result = await launchClaude(prompt, claudeOptions);
1450
- const claudeDuration = Date.now() - claudeStartTime;
1451
- getLogger().debug("Claude API call completed", { duration: `${claudeDuration}ms` });
1452
- if (typeof result !== "string") {
1453
- getLogger().warn("Claude returned non-string result", { resultType: typeof result });
1454
- return null;
1455
- }
1456
- getLogger().debug("Raw Claude output received", {
1457
- outputLength: result.length,
1458
- preview: result.substring(0, 200) + (result.length > 200 ? "..." : "")
1459
- });
1460
- getLogger().debug("Sanitizing Claude output...");
1461
- const sanitized = this.sanitizeClaudeOutput(result);
1462
- getLogger().debug("Output sanitized", {
1463
- originalLength: result.length,
1464
- sanitizedLength: sanitized.length,
1465
- sanitized: sanitized.substring(0, 200) + (sanitized.length > 200 ? "..." : "")
1466
- });
1467
- if (!sanitized) {
1468
- getLogger().warn("Claude returned empty message after sanitization");
1469
- return null;
1470
- }
1471
- let finalMessage = sanitized;
1472
- if (issueNumber) {
1473
- if (!finalMessage.includes(`Fixes #${issueNumber}`)) {
1474
- finalMessage = `${finalMessage}
1475
-
1476
- Fixes #${issueNumber}`;
1477
- getLogger().debug(`Added "Fixes #${issueNumber}" trailer to commit message`);
1478
- } else {
1479
- getLogger().debug(`"Fixes #${issueNumber}" already present in commit message`);
1480
- }
1481
- }
1482
- const totalDuration = Date.now() - startTime;
1483
- getLogger().info("Claude commit message generated successfully", {
1484
- message: finalMessage,
1485
- totalDuration: `${totalDuration}ms`,
1486
- claudeApiDuration: `${claudeDuration}ms`
1487
- });
1488
- return finalMessage;
1489
- } catch (error) {
1490
- const totalDuration = Date.now() - startTime;
1491
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
1492
- if (errorMessage.includes("timed out") || errorMessage.includes("timeout")) {
1493
- getLogger().warn("Claude commit message generation timed out after 45 seconds", {
1494
- totalDuration: `${totalDuration}ms`,
1495
- worktreePath: worktreePath.split("/").pop()
1496
- });
1497
- } else {
1498
- getLogger().warn("Failed to generate commit message with Claude", {
1499
- error: errorMessage,
1500
- totalDuration: `${totalDuration}ms`,
1501
- worktreePath: worktreePath.split("/").pop()
1502
- });
1503
- }
1504
- return null;
1505
- }
1506
- }
1507
- /**
1508
- * Build structured XML prompt for commit message generation
1509
- * Uses XML format for clear task definition and output expectations
1510
- */
1511
- buildCommitMessagePrompt(issueNumber) {
1512
- const issueContext = issueNumber ? `
1513
- <IssueContext>
1514
- This commit is associated with GitHub issue #${issueNumber}.
1515
- If the changes appear to resolve the issue, include "Fixes #${issueNumber}" at the end of the first line of commit message.
1516
- </IssueContext>` : "";
1517
- return `<Task>
1518
- You are a software engineer writing a commit message for this repository.
1519
- Examine the staged changes in the git repository and generate a concise, meaningful commit message.
1520
- </Task>
1521
-
1522
- <Requirements>
1523
- <Format>The first line must be a brief summary of the changes made as a full sentence. If it references an issue, include "Fixes #N" at the end of this line.
1524
-
1525
- Add 2 newlines, then add a bullet-point form description of the changes made, each change on a new line.</Format>
1526
- <Mood>Use imperative mood (e.g., "Add feature" not "Added feature")</Mood>
1527
- <Focus>Be specific about what was changed and why</Focus>
1528
- <Conciseness>Keep message under 72 characters for subject line when possible</Conciseness>
1529
- <NoMeta>CRITICAL: Do NOT include ANY explanatory text, analysis, or meta-commentary. Output ONLY the raw commit message.</NoMeta>
1530
- <Examples>
1531
- Good: "Add user authentication with JWT tokens. Fixes #42
1532
-
1533
- - Implement login and registration endpoints
1534
- - Secure routes with JWT middleware
1535
- - Update user model to store hashed passwords"
1536
- Good: "Fix navigation bug in sidebar menu."
1537
- Bad: "Based on the changes, I'll create: Add user authentication"
1538
- Bad: "Looking at the files, this commit should be: Fix navigation bug"
1539
- </Examples>
1540
- ${issueContext}
1541
- </Requirements>
1542
-
1543
- <Output>
1544
- IMPORTANT: Your entire response will be used directly as the git commit message.
1545
- Do not include any explanatory text before or after the commit message.
1546
- Start your response immediately with the commit message text.
1547
- </Output>`;
1548
- }
1549
- /**
1550
- * Sanitize Claude output to remove meta-commentary and clean formatting
1551
- * Handles cases where Claude includes explanatory text despite instructions
1552
- */
1553
- sanitizeClaudeOutput(rawOutput) {
1554
- let cleaned = rawOutput.trim();
1555
- const metaPatterns = [
1556
- /^.*?based on.*?changes.*?:/i,
1557
- /^.*?looking at.*?files.*?:/i,
1558
- /^.*?examining.*?:/i,
1559
- /^.*?analyzing.*?:/i,
1560
- /^.*?i'll.*?generate.*?:/i,
1561
- /^.*?let me.*?:/i,
1562
- /^.*?the commit message.*?should be.*?:/i,
1563
- /^.*?here.*?is.*?commit.*?message.*?:/i
1564
- ];
1565
- for (const pattern of metaPatterns) {
1566
- cleaned = cleaned.replace(pattern, "").trim();
1567
- }
1568
- if (cleaned.includes(":")) {
1569
- const colonIndex = cleaned.indexOf(":");
1570
- const beforeColon = cleaned.substring(0, colonIndex).trim().toLowerCase();
1571
- const metaIndicators = [
1572
- "here is the commit message",
1573
- "commit message",
1574
- "here is",
1575
- "the message should be",
1576
- "i suggest",
1577
- "my suggestion"
1578
- ];
1579
- const isMetaCommentary = metaIndicators.some((indicator) => beforeColon.includes(indicator));
1580
- if (isMetaCommentary) {
1581
- const afterColon = cleaned.substring(colonIndex + 1).trim();
1582
- if (afterColon && afterColon.length > 10) {
1583
- cleaned = afterColon;
1584
- }
1585
- }
1586
- }
1587
- if (cleaned.startsWith('"') && cleaned.endsWith('"') || cleaned.startsWith("'") && cleaned.endsWith("'")) {
1588
- cleaned = cleaned.slice(1, -1).trim();
1589
- }
1590
- return cleaned;
1591
- }
1592
- };
1593
-
1594
- // src/lib/BuildRunner.ts
1595
- var BuildRunner = class {
1596
- constructor(capabilityDetector) {
1597
- this.capabilityDetector = capabilityDetector ?? new ProjectCapabilityDetector();
1598
- }
1599
- /**
1600
- * Run build verification in the specified directory
1601
- * @param buildPath - Path where build should run (typically main worktree path)
1602
- * @param options - Build options
1603
- */
1604
- async runBuild(buildPath, options = {}) {
1605
- const startTime = Date.now();
1606
- try {
1607
- const pkgJson = await getPackageConfig(buildPath);
1608
- const hasBuildScript = hasScript(pkgJson, "build");
1609
- if (!hasBuildScript) {
1610
- getLogger().debug("Skipping build - no build script found");
1611
- return {
1612
- success: true,
1613
- skipped: true,
1614
- reason: "No build script found in package configuration",
1615
- duration: Date.now() - startTime
1616
- };
1617
- }
1618
- } catch (error) {
1619
- if (error instanceof Error && error.message.includes("package.json not found")) {
1620
- getLogger().debug("Skipping build - no package configuration found");
1621
- return {
1622
- success: true,
1623
- skipped: true,
1624
- reason: "No package configuration found in project",
1625
- duration: Date.now() - startTime
1626
- };
1627
- }
1628
- throw error;
1629
- }
1630
- const capabilities = await this.capabilityDetector.detectCapabilities(buildPath);
1631
- const isCLIProject = capabilities.capabilities.includes("cli");
1632
- if (!isCLIProject) {
1633
- getLogger().debug("Skipping build - not a CLI project (no bin field)");
1634
- return {
1635
- success: true,
1636
- skipped: true,
1637
- reason: "Project is not a CLI project (no bin field in package.json)",
1638
- duration: Date.now() - startTime
1639
- };
1640
- }
1641
- const packageManager = await detectPackageManager(buildPath);
1642
- if (options.dryRun) {
1643
- const command = packageManager === "npm" ? "npm run build" : `${packageManager} build`;
1644
- getLogger().info(`[DRY RUN] Would run: ${command}`);
1645
- return {
1646
- success: true,
1647
- skipped: false,
1648
- duration: Date.now() - startTime
1649
- };
1650
- }
1651
- getLogger().info("Running build...");
1652
- try {
1653
- await runScript("build", buildPath, [], { quiet: true });
1654
- getLogger().success("Build completed successfully");
1655
- return {
1656
- success: true,
1657
- skipped: false,
1658
- duration: Date.now() - startTime
1659
- };
1660
- } catch {
1661
- const runCommand = packageManager === "npm" ? "npm run build" : `${packageManager} build`;
1662
- throw new Error(
1663
- `Error: Build failed.
1664
- Fix build errors before proceeding.
1665
-
1666
- Run '${runCommand}' to see detailed errors.`
1667
- );
1668
- }
1669
- }
1670
- };
1671
-
1672
823
  // src/commands/finish.ts
1673
824
  import path3 from "path";
1674
825
  var FinishCommand = class {
@@ -1709,7 +860,7 @@ var FinishCommand = class {
1709
860
  const neonProvider = createNeonProviderFromSettings(settings);
1710
861
  const databaseManager = new DatabaseManager(neonProvider, environmentManager, databaseUrlEnvVarName);
1711
862
  const cliIsolationManager = new CLIIsolationManager();
1712
- const { DefaultBranchNamingService: DefaultBranchNamingService2 } = await import("./BranchNamingService-B5PVRR7F.js");
863
+ const { DefaultBranchNamingService: DefaultBranchNamingService2 } = await import("./BranchNamingService-FLPUUFOB.js");
1713
864
  this.loomManager ??= new LoomManager(
1714
865
  this.gitWorktreeManager,
1715
866
  this.issueTracker,
@@ -1768,6 +919,7 @@ var FinishCommand = class {
1768
919
  */
1769
920
  async execute(input) {
1770
921
  var _a, _b, _c, _d;
922
+ process.env.ILOOM = "1";
1771
923
  const isJsonMode = input.options.json === true;
1772
924
  const result = {
1773
925
  success: false,
@@ -2049,7 +1201,7 @@ var FinishCommand = class {
2049
1201
  * This is the workflow: rebase → validate → commit → merge → cleanup
2050
1202
  */
2051
1203
  async executeIssueWorkflow(parsed, options, worktree, result) {
2052
- var _a, _b, _c;
1204
+ var _a, _b, _c, _d;
2053
1205
  getLogger().info("Rebasing branch on main...");
2054
1206
  const mergeOptions = {
2055
1207
  dryRun: options.dryRun ?? false,
@@ -2094,9 +1246,12 @@ var FinishCommand = class {
2094
1246
  getLogger().info("Validation passed, auto-committing uncommitted changes...");
2095
1247
  const settings2 = await this.settingsManager.loadSettings(worktree.path);
2096
1248
  const skipVerify = ((_b = (_a = settings2.workflows) == null ? void 0 : _a.issue) == null ? void 0 : _b.noVerify) ?? false;
1249
+ const providerType = ((_c = settings2.issueManagement) == null ? void 0 : _c.provider) ?? "github";
1250
+ const issuePrefix = IssueManagementProviderFactory.create(providerType).issuePrefix;
2097
1251
  const commitOptions = {
2098
1252
  dryRun: options.dryRun ?? false,
2099
- skipVerify
1253
+ skipVerify,
1254
+ issuePrefix
2100
1255
  };
2101
1256
  if (parsed.type === "issue" && parsed.number) {
2102
1257
  commitOptions.issueNumber = parsed.number;
@@ -2142,9 +1297,9 @@ var FinishCommand = class {
2142
1297
  `The 'github-draft-pr' merge mode requires a GitHub-compatible issue tracker. Your provider (${this.issueTracker.providerName}) does not support pull requests.`
2143
1298
  );
2144
1299
  }
2145
- const { MetadataManager: MetadataManager2 } = await import("./MetadataManager-DFI73J3G.js");
2146
- const metadataManager = new MetadataManager2();
2147
- const metadata = await metadataManager.readMetadata(worktree.path);
1300
+ const { MetadataManager: MetadataManager3 } = await import("./MetadataManager-XJ2YB762.js");
1301
+ const metadataManager2 = new MetadataManager3();
1302
+ const metadata = await metadataManager2.readMetadata(worktree.path);
2148
1303
  getLogger().debug(`Draft PR mode: worktree=${worktree.path}, draftPrNumber=${(metadata == null ? void 0 : metadata.draftPrNumber) ?? "none"}`);
2149
1304
  if (!(metadata == null ? void 0 : metadata.draftPrNumber)) {
2150
1305
  getLogger().warn("No draft PR found in metadata, creating new PR...");
@@ -2209,7 +1364,7 @@ var FinishCommand = class {
2209
1364
  } else {
2210
1365
  getLogger().info(`[DRY RUN] Would mark PR #${metadata.draftPrNumber} as ready for review`);
2211
1366
  }
2212
- const prUrl = (_c = metadata.prUrls) == null ? void 0 : _c[String(metadata.draftPrNumber)];
1367
+ const prUrl = (_d = metadata.prUrls) == null ? void 0 : _d[String(metadata.draftPrNumber)];
2213
1368
  if (prUrl) {
2214
1369
  result.prUrl = prUrl;
2215
1370
  }
@@ -2243,7 +1398,17 @@ var FinishCommand = class {
2243
1398
  getLogger().debug("Skipping build verification (--skip-build flag provided)");
2244
1399
  }
2245
1400
  await this.generateSessionSummaryIfConfigured(parsed, worktree, options);
2246
- await this.performPostMergeCleanup(parsed, options, worktree, result);
1401
+ const { MetadataManager: MetadataManager2 } = await import("./MetadataManager-XJ2YB762.js");
1402
+ const metadataManager = new MetadataManager2();
1403
+ if (!options.dryRun) {
1404
+ await metadataManager.archiveMetadata(worktree.path);
1405
+ }
1406
+ if (options.cleanup === false) {
1407
+ getLogger().info("Worktree kept active (--no-cleanup flag)");
1408
+ getLogger().info(`To cleanup later: il cleanup ${parsed.originalInput}`);
1409
+ } else {
1410
+ await this.performPostMergeCleanup(parsed, options, worktree, result);
1411
+ }
2247
1412
  }
2248
1413
  /**
2249
1414
  * Execute workflow for Pull Requests
@@ -2252,7 +1417,7 @@ var FinishCommand = class {
2252
1417
  * - CLOSED/MERGED: Skip to cleanup
2253
1418
  */
2254
1419
  async executePRWorkflow(parsed, options, worktree, pr, result) {
2255
- var _a, _b;
1420
+ var _a, _b, _c;
2256
1421
  if (pr.state === "closed" || pr.state === "merged") {
2257
1422
  getLogger().info(`PR #${parsed.number} is ${pr.state.toUpperCase()} - skipping to cleanup`);
2258
1423
  const gitStatus = await this.commitManager.detectUncommittedChanges(worktree.path);
@@ -2262,6 +1427,11 @@ var FinishCommand = class {
2262
1427
  "Cannot cleanup PR with uncommitted changes. Commit or stash changes, then run again with --force to cleanup anyway."
2263
1428
  );
2264
1429
  }
1430
+ const { MetadataManager: MetadataManager2 } = await import("./MetadataManager-XJ2YB762.js");
1431
+ const metadataManager = new MetadataManager2();
1432
+ if (!options.dryRun) {
1433
+ await metadataManager.archiveMetadata(worktree.path);
1434
+ }
2265
1435
  await this.performPRCleanup(parsed, options, worktree, pr.state, result);
2266
1436
  getLogger().success(`PR #${parsed.number} cleanup completed`);
2267
1437
  result.operations.push({
@@ -2284,10 +1454,13 @@ var FinishCommand = class {
2284
1454
  getLogger().info("Committing uncommitted changes...");
2285
1455
  const settings = await this.settingsManager.loadSettings(worktree.path);
2286
1456
  const skipVerify = ((_b = (_a = settings.workflows) == null ? void 0 : _a.pr) == null ? void 0 : _b.noVerify) ?? false;
1457
+ const providerType = ((_c = settings.issueManagement) == null ? void 0 : _c.provider) ?? "github";
1458
+ const issuePrefix = IssueManagementProviderFactory.create(providerType).issuePrefix;
2287
1459
  try {
2288
1460
  await this.commitManager.commitChanges(worktree.path, {
2289
1461
  dryRun: false,
2290
- skipVerify
1462
+ skipVerify,
1463
+ issuePrefix
2291
1464
  // Do NOT pass issueNumber for PRs - no "Fixes #" trailer needed
2292
1465
  });
2293
1466
  getLogger().success("Changes committed");
@@ -2386,6 +1559,11 @@ var FinishCommand = class {
2386
1559
  }
2387
1560
  finishResult.prUrl = prResult.url;
2388
1561
  await this.generateSessionSummaryIfConfigured(parsed, worktree, options, prResult.number);
1562
+ const { MetadataManager: MetadataManager2 } = await import("./MetadataManager-XJ2YB762.js");
1563
+ const metadataManager = new MetadataManager2();
1564
+ if (!options.dryRun) {
1565
+ await metadataManager.archiveMetadata(worktree.path);
1566
+ }
2389
1567
  await this.handlePRCleanupPrompt(parsed, options, worktree, finishResult);
2390
1568
  }
2391
1569
  }
@@ -2740,7 +1918,7 @@ var FinishCommand = class {
2740
1918
  // src/utils/package-info.ts
2741
1919
  import { readFileSync } from "fs";
2742
1920
  import { fileURLToPath } from "url";
2743
- import { dirname, join as join2 } from "path";
1921
+ import { dirname, join } from "path";
2744
1922
  function getPackageInfo(scriptPath) {
2745
1923
  try {
2746
1924
  let basePath;
@@ -2751,7 +1929,7 @@ function getPackageInfo(scriptPath) {
2751
1929
  basePath = __filename2;
2752
1930
  }
2753
1931
  const __dirname = dirname(basePath);
2754
- const packageJsonPath = join2(__dirname, "..", "package.json");
1932
+ const packageJsonPath = join(__dirname, "..", "package.json");
2755
1933
  const packageJsonContent = readFileSync(packageJsonPath, "utf8");
2756
1934
  const packageJson2 = JSON.parse(packageJsonContent);
2757
1935
  return packageJson2;
@@ -2778,12 +1956,12 @@ function determineLoomType(worktree) {
2778
1956
  }
2779
1957
  return "branch";
2780
1958
  }
2781
- function extractPRNumbers(path4) {
2782
- if (!path4) {
1959
+ function extractPRNumbers(path6) {
1960
+ if (!path6) {
2783
1961
  return [];
2784
1962
  }
2785
1963
  const prPathPattern = /_pr_(\d+)$/;
2786
- const match = path4.match(prPathPattern);
1964
+ const match = path6.match(prPathPattern);
2787
1965
  if (match == null ? void 0 : match[1]) {
2788
1966
  return [match[1]];
2789
1967
  }
@@ -2830,12 +2008,199 @@ function formatLoomForJson(worktree, mainWorktreePath, metadata) {
2830
2008
  colorHex: (metadata == null ? void 0 : metadata.colorHex) ?? null,
2831
2009
  projectPath: (metadata == null ? void 0 : metadata.projectPath) ?? null,
2832
2010
  issueUrls: (metadata == null ? void 0 : metadata.issueUrls) ?? {},
2833
- prUrls: (metadata == null ? void 0 : metadata.prUrls) ?? {}
2011
+ prUrls: (metadata == null ? void 0 : metadata.prUrls) ?? {},
2012
+ capabilities: (metadata == null ? void 0 : metadata.capabilities) ?? []
2834
2013
  };
2835
2014
  }
2836
2015
  function formatLoomsForJson(worktrees, mainWorktreePath, metadata) {
2837
2016
  return worktrees.map((wt) => formatLoomForJson(wt, mainWorktreePath, metadata == null ? void 0 : metadata.get(wt.path)));
2838
2017
  }
2018
+ function formatFinishedLoomForJson(metadata) {
2019
+ const loomType = metadata.issueType ?? "branch";
2020
+ return {
2021
+ name: metadata.branchName ?? metadata.worktreePath ?? "unknown",
2022
+ worktreePath: null,
2023
+ // Finished looms no longer have a worktree
2024
+ branch: metadata.branchName,
2025
+ type: loomType,
2026
+ issue_numbers: metadata.issue_numbers,
2027
+ pr_numbers: metadata.pr_numbers,
2028
+ isMainWorktree: false,
2029
+ // Finished looms are never the main worktree
2030
+ description: metadata.description ?? null,
2031
+ created_at: metadata.created_at ?? null,
2032
+ issueTracker: metadata.issueTracker ?? null,
2033
+ colorHex: metadata.colorHex ?? null,
2034
+ projectPath: metadata.projectPath ?? null,
2035
+ issueUrls: metadata.issueUrls ?? {},
2036
+ prUrls: metadata.prUrls ?? {},
2037
+ capabilities: metadata.capabilities ?? [],
2038
+ status: metadata.status ?? "finished",
2039
+ finishedAt: metadata.finishedAt ?? null
2040
+ };
2041
+ }
2042
+
2043
+ // src/cli.ts
2044
+ import fs3 from "fs-extra";
2045
+
2046
+ // src/lib/VersionMigrationManager.ts
2047
+ import fs2 from "fs-extra";
2048
+ import path5 from "path";
2049
+ import os2 from "os";
2050
+
2051
+ // src/migrations/index.ts
2052
+ import fs from "fs-extra";
2053
+ import path4 from "path";
2054
+ import os from "os";
2055
+ var migrations = [
2056
+ // v0.6.0 is the baseline - no migrations needed
2057
+ {
2058
+ version: "0.6.1",
2059
+ description: "Add global gitignore for .iloom/settings.local.json",
2060
+ migrate: async () => {
2061
+ const globalIgnorePath = path4.join(os.homedir(), ".config", "git", "ignore");
2062
+ const pattern = "**/.iloom/settings.local.json";
2063
+ await fs.ensureDir(path4.dirname(globalIgnorePath));
2064
+ let content = "";
2065
+ try {
2066
+ content = await fs.readFile(globalIgnorePath, "utf-8");
2067
+ } catch {
2068
+ }
2069
+ if (content.includes(pattern)) {
2070
+ return;
2071
+ }
2072
+ const separator = content.endsWith("\n") || content === "" ? "" : "\n";
2073
+ const newContent = content + separator + "\n# Added by iloom CLI\n" + pattern + "\n";
2074
+ await fs.writeFile(globalIgnorePath, newContent, "utf-8");
2075
+ }
2076
+ }
2077
+ ];
2078
+
2079
+ // src/lib/VersionMigrationManager.ts
2080
+ var VersionMigrationManager = class {
2081
+ constructor() {
2082
+ this.DEFAULT_VERSION = "0.6.0";
2083
+ this.VERSION_OVERRIDE_ENV = "ILOOM_VERSION_OVERRIDE";
2084
+ }
2085
+ // Return path to migration state file
2086
+ getMigrationStatePath() {
2087
+ return path5.join(os2.homedir(), ".config", "iloom-ai", "migration-state.json");
2088
+ }
2089
+ // Get effective version, respecting ILOOM_VERSION_OVERRIDE env var
2090
+ // packageVersion is the version from package.json passed by caller
2091
+ getEffectiveVersion(packageVersion) {
2092
+ const override = process.env[this.VERSION_OVERRIDE_ENV];
2093
+ if (override && override.trim() !== "") {
2094
+ logger.debug(`[VersionMigrationManager] Using version override: ${override} (package.json: ${packageVersion})`);
2095
+ return override.trim();
2096
+ }
2097
+ return packageVersion;
2098
+ }
2099
+ // Load full migration state from file
2100
+ // Returns state with DEFAULT_VERSION if file missing/invalid
2101
+ async loadFullMigrationState() {
2102
+ const statePath = this.getMigrationStatePath();
2103
+ try {
2104
+ const content = await fs2.readFile(statePath, "utf-8");
2105
+ const state = JSON.parse(content);
2106
+ if (typeof state.lastMigratedVersion === "string") {
2107
+ return state;
2108
+ }
2109
+ } catch {
2110
+ logger.debug(`[VersionMigrationManager] Migration state not found, using default version: ${this.DEFAULT_VERSION}`);
2111
+ }
2112
+ return {
2113
+ lastMigratedVersion: this.DEFAULT_VERSION,
2114
+ migratedAt: (/* @__PURE__ */ new Date()).toISOString()
2115
+ };
2116
+ }
2117
+ // Load last migrated version from state file
2118
+ // Returns DEFAULT_VERSION if file missing/invalid
2119
+ async loadMigrationState() {
2120
+ const state = await this.loadFullMigrationState();
2121
+ return state.lastMigratedVersion;
2122
+ }
2123
+ // Save current version to state file, preserving and appending to existing failures
2124
+ async saveMigrationState(version, newFailures = []) {
2125
+ const statePath = this.getMigrationStatePath();
2126
+ try {
2127
+ await fs2.ensureDir(path5.dirname(statePath));
2128
+ const existingState = await this.loadFullMigrationState();
2129
+ const existingFailures = existingState.failedMigrations ?? [];
2130
+ const allFailures = [...existingFailures];
2131
+ for (const newFailure of newFailures) {
2132
+ const existingIndex = allFailures.findIndex((f) => f.version === newFailure.version);
2133
+ if (existingIndex >= 0) {
2134
+ allFailures[existingIndex] = newFailure;
2135
+ } else {
2136
+ allFailures.push(newFailure);
2137
+ }
2138
+ }
2139
+ const state = {
2140
+ lastMigratedVersion: version,
2141
+ migratedAt: (/* @__PURE__ */ new Date()).toISOString(),
2142
+ ...allFailures.length > 0 && { failedMigrations: allFailures }
2143
+ };
2144
+ await fs2.writeFile(statePath, JSON.stringify(state, null, 2), "utf-8");
2145
+ } catch (error) {
2146
+ logger.warn(`[VersionMigrationManager] Failed to save migration state: ${error}`);
2147
+ }
2148
+ }
2149
+ // Compare semver versions: returns negative if v1 < v2, positive if v1 > v2, 0 if equal
2150
+ // Pattern from update-notifier.ts:190-221
2151
+ compareVersions(v1, v2) {
2152
+ try {
2153
+ const parts1 = v1.split(".").map((p) => parseInt(p, 10));
2154
+ const parts2 = v2.split(".").map((p) => parseInt(p, 10));
2155
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
2156
+ const p1 = parts1[i] ?? 0;
2157
+ const p2 = parts2[i] ?? 0;
2158
+ if (p1 !== p2) return p1 - p2;
2159
+ }
2160
+ return 0;
2161
+ } catch {
2162
+ return 0;
2163
+ }
2164
+ }
2165
+ // Get migrations that need to run (version > lastMigrated, version <= current)
2166
+ getPendingMigrations(lastMigratedVersion, currentVersion) {
2167
+ return migrations.filter(
2168
+ (m) => this.compareVersions(m.version, lastMigratedVersion) > 0 && this.compareVersions(m.version, currentVersion) <= 0
2169
+ ).sort((a, b) => this.compareVersions(a.version, b.version));
2170
+ }
2171
+ // Main entry point - run any pending migrations
2172
+ // packageVersion is the version from package.json; may be overridden by ILOOM_VERSION_OVERRIDE
2173
+ async runMigrationsIfNeeded(packageVersion) {
2174
+ const currentVersion = this.getEffectiveVersion(packageVersion);
2175
+ const lastMigratedVersion = await this.loadMigrationState();
2176
+ if (this.compareVersions(lastMigratedVersion, currentVersion) >= 0) {
2177
+ return;
2178
+ }
2179
+ const pending = this.getPendingMigrations(lastMigratedVersion, currentVersion);
2180
+ if (pending.length === 0) {
2181
+ logger.debug(`[VersionMigrationManager] No migrations to run, updating state to ${currentVersion}`);
2182
+ await this.saveMigrationState(currentVersion);
2183
+ return;
2184
+ }
2185
+ const failedMigrations = [];
2186
+ for (const migration of pending) {
2187
+ logger.debug(`[VersionMigrationManager] Running migration to ${migration.version}: ${migration.description}`);
2188
+ try {
2189
+ await migration.migrate();
2190
+ logger.debug(`[VersionMigrationManager] Migration to ${migration.version} completed`);
2191
+ } catch (error) {
2192
+ logger.warn(`[VersionMigrationManager] Migration to ${migration.version} failed: ${error}`);
2193
+ failedMigrations.push({
2194
+ version: migration.version,
2195
+ description: migration.description,
2196
+ failedAt: (/* @__PURE__ */ new Date()).toISOString(),
2197
+ error: error instanceof Error ? error.message : String(error)
2198
+ });
2199
+ }
2200
+ }
2201
+ await this.saveMigrationState(currentVersion, failedMigrations);
2202
+ }
2203
+ };
2839
2204
 
2840
2205
  // src/cli.ts
2841
2206
  var __filename = fileURLToPath2(import.meta.url);
@@ -2868,6 +2233,12 @@ program.name("iloom").description(packageJson.description).version(packageJson.v
2868
2233
  } catch (error) {
2869
2234
  logger.debug(`Settings migration failed: ${error instanceof Error ? error.message : "Unknown"}`);
2870
2235
  }
2236
+ try {
2237
+ const versionMigrationManager = new VersionMigrationManager();
2238
+ await versionMigrationManager.runMigrationsIfNeeded(packageJson.version);
2239
+ } catch (error) {
2240
+ logger.warn(`Version migration failed: ${error instanceof Error ? error.message : "Unknown"}`);
2241
+ }
2871
2242
  await validateSettingsForCommand(actionCommand);
2872
2243
  await validateGhCliForCommand(actionCommand);
2873
2244
  await validateIdeForStartCommand(thisCommand);
@@ -2997,14 +2368,14 @@ async function autoLaunchInitForMultipleRemotes() {
2997
2368
  await waitForKeypress2("Press any key to start configuration...");
2998
2369
  logger.info("");
2999
2370
  try {
3000
- const { InitCommand: InitCommand2 } = await import("./init-HB34Q5FH.js");
2371
+ const { InitCommand: InitCommand2 } = await import("./init-XQQMFDM6.js");
3001
2372
  const initCommand = new InitCommand2();
3002
2373
  const customInitialMessage = "Help me configure which git remote iloom should use for GitHub operations. I have multiple remotes and need to select the correct one.";
3003
2374
  await initCommand.execute(customInitialMessage);
3004
2375
  logger.info("");
3005
2376
  logger.info("Configuration complete! Continuing with your original command...");
3006
2377
  logger.info("");
3007
- const { SettingsManager: SettingsManager2 } = await import("./SettingsManager-35F5RUJH.js");
2378
+ const { SettingsManager: SettingsManager2 } = await import("./SettingsManager-YU4VYPTW.js");
3008
2379
  const settingsManager = new SettingsManager2();
3009
2380
  const settings = await settingsManager.loadSettings();
3010
2381
  const { hasMultipleRemotes: hasMultipleRemotes2 } = await import("./remote-IJAMOEAP.js");
@@ -3098,7 +2469,7 @@ program.command("add-issue").alias("a").description("Create and enhance GitHub i
3098
2469
  });
3099
2470
  program.command("feedback").alias("f").description("Submit feedback/bug report to iloom-cli repository").argument("<description>", "Feedback title (>30 chars, >2 spaces; or any non-empty text when --body provided)").option("--body <text>", "Body text for feedback (added after diagnostics)").action(async (description, options) => {
3100
2471
  try {
3101
- const { FeedbackCommand } = await import("./feedback-O4Q55SVS.js");
2472
+ const { FeedbackCommand } = await import("./feedback-G7G5QCY4.js");
3102
2473
  const command = new FeedbackCommand();
3103
2474
  const feedbackOptions = {};
3104
2475
  if (options.body !== void 0) {
@@ -3148,7 +2519,7 @@ program.command("enhance").description("Apply enhancement agent to existing GitH
3148
2519
  await executeAction();
3149
2520
  }
3150
2521
  });
3151
- program.command("finish").alias("dn").description("Merge work and cleanup workspace").argument("[identifier]", "Issue number, PR number, or branch name (auto-detected if omitted)").option("-f, --force", "Skip confirmation prompts").option("-n, --dry-run", "Preview actions without executing").option("--pr <number>", "Treat input as PR number", parseFloat).option("--skip-build", "Skip post-merge build verification").option("--no-browser", "Skip opening PR in browser (github-pr mode only)").option("--cleanup", "Clean up worktree after PR creation (github-pr mode only)").option("--no-cleanup", "Keep worktree after PR creation (github-pr mode only)").option("--json", "Output result as JSON").action(async (identifier, options) => {
2522
+ program.command("finish").alias("dn").description("Merge work and cleanup workspace").argument("[identifier]", "Issue number, PR number, or branch name (auto-detected if omitted)").option("-f, --force", "Skip confirmation prompts").option("-n, --dry-run", "Preview actions without executing").option("--pr <number>", "Treat input as PR number", parseFloat).option("--skip-build", "Skip post-merge build verification").option("--no-browser", "Skip opening PR in browser (github-pr mode only)").option("--cleanup", "Clean up worktree after finishing (default in local mode)").option("--no-cleanup", "Keep worktree after finishing").option("--json", "Output result as JSON").action(async (identifier, options) => {
3152
2523
  const executeAction = async () => {
3153
2524
  try {
3154
2525
  const settingsManager = new SettingsManager();
@@ -3179,9 +2550,45 @@ program.command("finish").alias("dn").description("Merge work and cleanup worksp
3179
2550
  await executeAction();
3180
2551
  }
3181
2552
  });
2553
+ program.command("commit").alias("c").description("Commit all uncommitted files with issue reference").option("-m, --message <text>", "Custom commit message (skip Claude generation)").option("--fixes", 'Use "Fixes #N" trailer instead of "Refs #N" (closes issue)').option("--no-review", "Skip commit message review prompt").option("--json", "Output result as JSON (implies --no-review)").option("--wip-commit", "Quick WIP commit: skip validations and pre-commit hooks").action(async (options) => {
2554
+ const executeAction = async () => {
2555
+ try {
2556
+ const { CommitCommand } = await import("./commit-6S2RIA2K.js");
2557
+ const command = new CommitCommand();
2558
+ const noReview = options.review === false || options.json === true;
2559
+ const result = await command.execute({
2560
+ message: options.message,
2561
+ fixes: options.fixes ?? false,
2562
+ noReview,
2563
+ json: options.json ?? false,
2564
+ wipCommit: options.wipCommit ?? false
2565
+ });
2566
+ if (options.json && result) {
2567
+ console.log(JSON.stringify(result, null, 2));
2568
+ }
2569
+ process.exit(0);
2570
+ } catch (error) {
2571
+ if (error instanceof UserAbortedCommitError) {
2572
+ process.exit(130);
2573
+ }
2574
+ if (options.json) {
2575
+ console.log(JSON.stringify({ success: false, error: error instanceof Error ? error.message : "Unknown error" }, null, 2));
2576
+ } else {
2577
+ logger.error(`Commit failed: ${error instanceof Error ? error.message : "Unknown error"}`);
2578
+ }
2579
+ process.exit(1);
2580
+ }
2581
+ };
2582
+ if (options.json) {
2583
+ const jsonLogger = createStderrLogger();
2584
+ await withLogger(jsonLogger, executeAction);
2585
+ } else {
2586
+ await executeAction();
2587
+ }
2588
+ });
3182
2589
  program.command("rebase").description("Rebase current branch on main with Claude-assisted conflict resolution").option("-f, --force", "Skip confirmation prompts").option("-n, --dry-run", "Preview actions without executing").action(async (options) => {
3183
2590
  try {
3184
- const { RebaseCommand } = await import("./rebase-5EY3Q6XP.js");
2591
+ const { RebaseCommand } = await import("./rebase-RKQED567.js");
3185
2592
  const command = new RebaseCommand();
3186
2593
  await command.execute(options);
3187
2594
  } catch (error) {
@@ -3193,7 +2600,7 @@ program.command("spin").alias("ignite").description("Launch Claude with auto-det
3193
2600
  new Option("--one-shot <mode>", "One-shot automation mode").choices(["default", "noReview", "bypassPermissions"]).default("default")
3194
2601
  ).action(async (options) => {
3195
2602
  try {
3196
- const { IgniteCommand } = await import("./ignite-VHV65WEZ.js");
2603
+ const { IgniteCommand } = await import("./ignite-YUAOJ5PP.js");
3197
2604
  const command = new IgniteCommand();
3198
2605
  await command.execute(options.oneShot ?? "default");
3199
2606
  } catch (error) {
@@ -3204,7 +2611,7 @@ program.command("spin").alias("ignite").description("Launch Claude with auto-det
3204
2611
  program.command("open").description("Open workspace in browser or run CLI tool").argument("[identifier]", "Issue number, PR number, or branch name (auto-detected if omitted)").allowUnknownOption().action(async (identifier, _options, command) => {
3205
2612
  try {
3206
2613
  const args = (command == null ? void 0 : command.args) ? command.args.slice(identifier ? 1 : 0) : [];
3207
- const { OpenCommand } = await import("./open-WHVUYGPY.js");
2614
+ const { OpenCommand } = await import("./open-MCWQAPSZ.js");
3208
2615
  const cmd = new OpenCommand();
3209
2616
  const input = identifier ? { identifier, args } : { args };
3210
2617
  await cmd.execute(input);
@@ -3216,7 +2623,7 @@ program.command("open").description("Open workspace in browser or run CLI tool")
3216
2623
  program.command("run").description("Run CLI tool or open workspace in browser").argument("[identifier]", "Issue number, PR number, or branch name (auto-detected if omitted)").allowUnknownOption().action(async (identifier, _options, command) => {
3217
2624
  try {
3218
2625
  const args = (command == null ? void 0 : command.args) ? command.args.slice(identifier ? 1 : 0) : [];
3219
- const { RunCommand } = await import("./run-NCRK5NPR.js");
2626
+ const { RunCommand } = await import("./run-CCG24PBC.js");
3220
2627
  const cmd = new RunCommand();
3221
2628
  const input = identifier ? { identifier, args } : { args };
3222
2629
  await cmd.execute(input);
@@ -3227,7 +2634,7 @@ program.command("run").description("Run CLI tool or open workspace in browser").
3227
2634
  });
3228
2635
  program.command("dev-server").alias("dev").description("Start dev server for workspace (foreground)").argument("[identifier]", "Issue number, PR number, or branch name (auto-detected if omitted)").option("--json", "Output as JSON").action(async (identifier, options) => {
3229
2636
  try {
3230
- const { DevServerCommand } = await import("./dev-server-4RCDJ5MU.js");
2637
+ const { DevServerCommand } = await import("./dev-server-GREJUEKW.js");
3231
2638
  const cmd = new DevServerCommand();
3232
2639
  await cmd.execute({ identifier, json: options == null ? void 0 : options.json });
3233
2640
  } catch (error) {
@@ -3237,7 +2644,7 @@ program.command("dev-server").alias("dev").description("Start dev server for wor
3237
2644
  });
3238
2645
  program.command("shell").alias("terminal").description("Open interactive shell with workspace environment").argument("[identifier]", "Issue number, PR number, or branch name (auto-detected if omitted)").action(async (identifier) => {
3239
2646
  try {
3240
- const { ShellCommand } = await import("./shell-SBLXVOVJ.js");
2647
+ const { ShellCommand } = await import("./shell-2NNSIU34.js");
3241
2648
  const cmd = new ShellCommand();
3242
2649
  await cmd.execute({ identifier });
3243
2650
  } catch (error) {
@@ -3247,7 +2654,7 @@ program.command("shell").alias("terminal").description("Open interactive shell w
3247
2654
  });
3248
2655
  program.command("build").description("Run the build script").argument("[identifier]", "Issue number, PR number, or branch name (auto-detected if omitted)").action(async (identifier) => {
3249
2656
  try {
3250
- const { BuildCommand } = await import("./build-FJVYP7EV.js");
2657
+ const { BuildCommand } = await import("./build-O2EJHDEW.js");
3251
2658
  const cmd = new BuildCommand();
3252
2659
  await cmd.execute(identifier ? { identifier } : {});
3253
2660
  } catch (error) {
@@ -3257,7 +2664,7 @@ program.command("build").description("Run the build script").argument("[identifi
3257
2664
  });
3258
2665
  program.command("lint").description("Run the lint script").argument("[identifier]", "Issue number, PR number, or branch name (auto-detected if omitted)").action(async (identifier) => {
3259
2666
  try {
3260
- const { LintCommand } = await import("./lint-5JMCWE4Y.js");
2667
+ const { LintCommand } = await import("./lint-OFVN7FT6.js");
3261
2668
  const cmd = new LintCommand();
3262
2669
  await cmd.execute(identifier ? { identifier } : {});
3263
2670
  } catch (error) {
@@ -3267,7 +2674,7 @@ program.command("lint").description("Run the lint script").argument("[identifier
3267
2674
  });
3268
2675
  program.command("test").description("Run the test script").argument("[identifier]", "Issue number, PR number, or branch name (auto-detected if omitted)").action(async (identifier) => {
3269
2676
  try {
3270
- const { TestCommand } = await import("./test-3KIVXI6J.js");
2677
+ const { TestCommand } = await import("./test-QZDOEUIO.js");
3271
2678
  const cmd = new TestCommand();
3272
2679
  await cmd.execute(identifier ? { identifier } : {});
3273
2680
  } catch (error) {
@@ -3277,7 +2684,7 @@ program.command("test").description("Run the test script").argument("[identifier
3277
2684
  });
3278
2685
  program.command("compile").alias("typecheck").description("Run the compile or typecheck script (prefers compile if both exist)").argument("[identifier]", "Issue number, PR number, or branch name (auto-detected if omitted)").action(async (identifier) => {
3279
2686
  try {
3280
- const { CompileCommand } = await import("./compile-ULNO5F7Q.js");
2687
+ const { CompileCommand } = await import("./compile-LRMAADUT.js");
3281
2688
  const cmd = new CompileCommand();
3282
2689
  await cmd.execute(identifier ? { identifier } : {});
3283
2690
  } catch (error) {
@@ -3285,10 +2692,10 @@ program.command("compile").alias("typecheck").description("Run the compile or ty
3285
2692
  process.exit(1);
3286
2693
  }
3287
2694
  });
3288
- program.command("cleanup").alias("remove").alias("clean").description("Remove workspaces").argument("[identifier]", "Branch name or issue number to cleanup (auto-detected)").option("-l, --list", "List all worktrees").option("-a, --all", "Remove all worktrees (interactive confirmation)").option("-i, --issue <number>", "Cleanup by issue number", parseInt).option("-f, --force", "Skip confirmations and force removal").option("--dry-run", "Show what would be done without doing it").option("--json", "Output result as JSON").action(async (identifier, options) => {
2695
+ program.command("cleanup").alias("remove").alias("clean").description("Remove workspaces").argument("[identifier]", "Branch name or issue number to cleanup (auto-detected)").option("-l, --list", "List all worktrees").option("-a, --all", "Remove all worktrees (interactive confirmation)").option("-i, --issue <number>", "Cleanup by issue number", parseInt).option("-f, --force", "Skip confirmations and force removal").option("--dry-run", "Show what would be done without doing it").option("--json", "Output result as JSON").option("--defer <ms>", "Wait specified milliseconds before cleanup", parseInt).action(async (identifier, options) => {
3289
2696
  const executeAction = async () => {
3290
2697
  try {
3291
- const { CleanupCommand } = await import("./cleanup-OU2HFOOG.js");
2698
+ const { CleanupCommand } = await import("./cleanup-ZPOMRSNN.js");
3292
2699
  const command = new CleanupCommand();
3293
2700
  const input = {
3294
2701
  options: options ?? {}
@@ -3317,15 +2724,78 @@ program.command("cleanup").alias("remove").alias("clean").description("Remove wo
3317
2724
  await executeAction();
3318
2725
  }
3319
2726
  });
3320
- program.command("list").description("Show active workspaces").option("--json", "Output as JSON").action(async (options) => {
2727
+ program.command("list").description("Show active workspaces").option("--json", "Output as JSON").option("--finished", "Show only finished looms (sorted by finish time, latest first)").option("--all", "Show both active and finished looms").option("--global", "Show looms from all projects (default: current project only)").action(async (options) => {
3321
2728
  try {
3322
2729
  const manager = new GitWorktreeManager();
3323
2730
  const metadataManager = new MetadataManager();
3324
- const worktrees = await manager.listWorktrees({ porcelain: true });
2731
+ let currentProjectPath = null;
2732
+ if (!options.global) {
2733
+ try {
2734
+ currentProjectPath = await findMainWorktreePathWithSettings();
2735
+ } catch (error) {
2736
+ if (error instanceof GitCommandError && (error.exitCode === 128 || /fatal: not a git repository/i.test(error.stderr))) {
2737
+ currentProjectPath = null;
2738
+ } else if (error instanceof Error && error.message.includes("Invalid settings")) {
2739
+ currentProjectPath = null;
2740
+ } else {
2741
+ throw error;
2742
+ }
2743
+ }
2744
+ }
2745
+ const showActive = !options.finished;
2746
+ const showFinished = Boolean(options.finished) || Boolean(options.all);
2747
+ let worktrees = [];
3325
2748
  const metadata = /* @__PURE__ */ new Map();
3326
- for (const worktree of worktrees) {
3327
- const loomMetadata = await metadataManager.readMetadata(worktree.path);
3328
- metadata.set(worktree.path, loomMetadata);
2749
+ let globalActiveLooms = [];
2750
+ if (showActive) {
2751
+ if (options.global) {
2752
+ const allMetadata = await metadataManager.listAllMetadata();
2753
+ for (const loom of allMetadata) {
2754
+ if (!loom.worktreePath) {
2755
+ continue;
2756
+ }
2757
+ const pathExists = await fs3.pathExists(loom.worktreePath);
2758
+ if (!pathExists) {
2759
+ continue;
2760
+ }
2761
+ const isValid = await isValidGitRepo(loom.worktreePath);
2762
+ if (!isValid) {
2763
+ continue;
2764
+ }
2765
+ globalActiveLooms.push(loom);
2766
+ metadata.set(loom.worktreePath, loom);
2767
+ }
2768
+ } else {
2769
+ try {
2770
+ worktrees = await manager.listWorktrees({ porcelain: true });
2771
+ for (const worktree of worktrees) {
2772
+ const loomMetadata = await metadataManager.readMetadata(worktree.path);
2773
+ metadata.set(worktree.path, loomMetadata);
2774
+ }
2775
+ } catch (error) {
2776
+ if (error instanceof GitCommandError && (error.exitCode === 128 || /fatal: not a git repository/i.test(error.stderr))) {
2777
+ worktrees = [];
2778
+ } else {
2779
+ throw error;
2780
+ }
2781
+ }
2782
+ }
2783
+ }
2784
+ let finishedLooms = [];
2785
+ if (showFinished) {
2786
+ finishedLooms = await metadataManager.listFinishedMetadata();
2787
+ }
2788
+ let filteredWorktrees = worktrees;
2789
+ let filteredGlobalActiveLooms = globalActiveLooms;
2790
+ let filteredFinishedLooms = finishedLooms;
2791
+ if (currentProjectPath) {
2792
+ filteredWorktrees = worktrees.filter((wt) => {
2793
+ const loomMetadata = metadata.get(wt.path);
2794
+ return (loomMetadata == null ? void 0 : loomMetadata.projectPath) == null || (loomMetadata == null ? void 0 : loomMetadata.projectPath) === currentProjectPath;
2795
+ });
2796
+ filteredFinishedLooms = finishedLooms.filter(
2797
+ (loom) => loom.projectPath == null || loom.projectPath === currentProjectPath
2798
+ );
3329
2799
  }
3330
2800
  if (options.json) {
3331
2801
  let mainWorktreePath;
@@ -3333,26 +2803,108 @@ program.command("list").description("Show active workspaces").option("--json", "
3333
2803
  mainWorktreePath = await findMainWorktreePathWithSettings();
3334
2804
  } catch {
3335
2805
  }
3336
- console.log(JSON.stringify(formatLoomsForJson(worktrees, mainWorktreePath, metadata), null, 2));
2806
+ let activeJson = [];
2807
+ if (showActive) {
2808
+ if (options.global) {
2809
+ activeJson = globalActiveLooms.map((loom) => ({
2810
+ name: loom.branchName ?? loom.worktreePath ?? "unknown",
2811
+ worktreePath: loom.worktreePath,
2812
+ branch: loom.branchName,
2813
+ type: loom.issueType ?? "branch",
2814
+ issue_numbers: loom.issue_numbers,
2815
+ pr_numbers: loom.pr_numbers,
2816
+ isMainWorktree: false,
2817
+ // Global looms from other projects are never the main worktree
2818
+ description: loom.description ?? null,
2819
+ created_at: loom.created_at ?? null,
2820
+ issueTracker: loom.issueTracker ?? null,
2821
+ colorHex: loom.colorHex ?? null,
2822
+ projectPath: loom.projectPath ?? null,
2823
+ issueUrls: loom.issueUrls ?? {},
2824
+ prUrls: loom.prUrls ?? {},
2825
+ status: "active",
2826
+ finishedAt: null
2827
+ }));
2828
+ } else {
2829
+ activeJson = formatLoomsForJson(worktrees, mainWorktreePath, metadata).map((loom) => ({
2830
+ ...loom,
2831
+ status: "active",
2832
+ finishedAt: null
2833
+ }));
2834
+ }
2835
+ }
2836
+ if (currentProjectPath && !options.global) {
2837
+ activeJson = activeJson.filter(
2838
+ (loom) => loom.projectPath == null || loom.projectPath === currentProjectPath
2839
+ );
2840
+ }
2841
+ let finishedJson = finishedLooms.map(formatFinishedLoomForJson);
2842
+ if (currentProjectPath) {
2843
+ finishedJson = finishedJson.filter(
2844
+ (loom) => loom.projectPath == null || loom.projectPath === currentProjectPath
2845
+ );
2846
+ }
2847
+ const allLooms = [...activeJson, ...finishedJson];
2848
+ console.log(JSON.stringify(allLooms, null, 2));
3337
2849
  return;
3338
2850
  }
3339
- if (worktrees.length === 0) {
3340
- logger.info("No worktrees found");
2851
+ const hasActive = options.global ? filteredGlobalActiveLooms.length > 0 : filteredWorktrees.length > 0;
2852
+ const hasFinished = filteredFinishedLooms.length > 0;
2853
+ if (!hasActive && !hasFinished) {
2854
+ if (options.finished) {
2855
+ logger.info("No finished looms found");
2856
+ } else if (options.all) {
2857
+ logger.info("No looms found");
2858
+ } else {
2859
+ logger.info("No worktrees found");
2860
+ }
3341
2861
  return;
3342
2862
  }
3343
- logger.info("Active workspaces:");
3344
- for (const worktree of worktrees) {
3345
- const formatted = manager.formatWorktree(worktree);
3346
- const loomMetadata = metadata.get(worktree.path);
3347
- logger.info(` ${formatted.title}`);
3348
- if (loomMetadata == null ? void 0 : loomMetadata.description) {
3349
- logger.info(` Description: ${loomMetadata.description}`);
2863
+ if (showActive && hasActive) {
2864
+ logger.info("Active workspaces:");
2865
+ if (options.global) {
2866
+ for (const loom of filteredGlobalActiveLooms) {
2867
+ logger.info(` ${loom.branchName ?? "unknown"}`);
2868
+ if (loom.description) {
2869
+ logger.info(` Description: ${loom.description}`);
2870
+ }
2871
+ if (loom.worktreePath) {
2872
+ logger.info(` Path: ${loom.worktreePath}`);
2873
+ }
2874
+ if (loom.projectPath) {
2875
+ logger.info(` Project: ${loom.projectPath}`);
2876
+ }
2877
+ }
2878
+ } else {
2879
+ for (const worktree of filteredWorktrees) {
2880
+ const formatted = manager.formatWorktree(worktree);
2881
+ const loomMetadata = metadata.get(worktree.path);
2882
+ logger.info(` ${formatted.title}`);
2883
+ if (loomMetadata == null ? void 0 : loomMetadata.description) {
2884
+ logger.info(` Description: ${loomMetadata.description}`);
2885
+ }
2886
+ logger.info(` Path: ${formatted.path}`);
2887
+ logger.info(` Commit: ${formatted.commit}`);
2888
+ }
2889
+ }
2890
+ }
2891
+ if (showFinished && hasFinished) {
2892
+ if (showActive && hasActive) {
2893
+ logger.info("");
2894
+ }
2895
+ logger.info("Finished looms:");
2896
+ for (const loom of filteredFinishedLooms) {
2897
+ logger.info(` ${loom.branchName ?? "unknown"}`);
2898
+ if (loom.description) {
2899
+ logger.info(` Description: ${loom.description}`);
2900
+ }
2901
+ if (loom.finishedAt) {
2902
+ logger.info(` Finished: ${new Date(loom.finishedAt).toLocaleString()}`);
2903
+ }
3350
2904
  }
3351
- logger.info(` Path: ${formatted.path}`);
3352
- logger.info(` Commit: ${formatted.commit}`);
3353
2905
  }
3354
2906
  } catch (error) {
3355
- if (error instanceof Error && error.message.includes("not a git repository")) {
2907
+ if (error instanceof GitCommandError && (error.exitCode === 128 || /fatal: not a git repository/i.test(error.stderr))) {
3356
2908
  if (options.json) {
3357
2909
  console.log("[]");
3358
2910
  } else {
@@ -3366,7 +2918,7 @@ program.command("list").description("Show active workspaces").option("--json", "
3366
2918
  });
3367
2919
  program.command("projects").description("List configured iloom projects").option("--json", "Output as JSON (default behavior)").action(async (options) => {
3368
2920
  try {
3369
- const { ProjectsCommand } = await import("./projects-SA76I4TZ.js");
2921
+ const { ProjectsCommand } = await import("./projects-PQOTWUII.js");
3370
2922
  const command = new ProjectsCommand();
3371
2923
  const result = await command.execute(options);
3372
2924
  console.log(JSON.stringify(result, null, 2));
@@ -3377,7 +2929,7 @@ program.command("projects").description("List configured iloom projects").option
3377
2929
  });
3378
2930
  program.command("init").alias("config").description("Initialize iloom configuration").argument("[prompt]", 'Custom initial message to send to Claude (defaults to "Help me configure iloom settings.")').action(async (prompt) => {
3379
2931
  try {
3380
- const { InitCommand: InitCommand2 } = await import("./init-HB34Q5FH.js");
2932
+ const { InitCommand: InitCommand2 } = await import("./init-XQQMFDM6.js");
3381
2933
  const command = new InitCommand2();
3382
2934
  const trimmedPrompt = prompt == null ? void 0 : prompt.trim();
3383
2935
  const customPrompt = trimmedPrompt && trimmedPrompt.length > 0 ? trimmedPrompt : void 0;
@@ -3387,11 +2939,11 @@ program.command("init").alias("config").description("Initialize iloom configurat
3387
2939
  process.exit(1);
3388
2940
  }
3389
2941
  });
3390
- program.command("contribute").description("Set up local development environment for contributing to iloom").action(async () => {
2942
+ program.command("contribute").description("Set up local development environment for contributing to a GitHub project").argument("[repository]", "GitHub repository (owner/repo, github.com/owner/repo, or full URL). Defaults to iloom-ai/iloom-cli").action(async (repository) => {
3391
2943
  try {
3392
- const { ContributeCommand } = await import("./contribute-T7ENST5N.js");
2944
+ const { ContributeCommand } = await import("./contribute-GXKOIA42.js");
3393
2945
  const command = new ContributeCommand();
3394
- await command.execute();
2946
+ await command.execute(repository);
3395
2947
  } catch (error) {
3396
2948
  logger.error(`Failed to set up contributor environment: ${error instanceof Error ? error.message : "Unknown error"}`);
3397
2949
  process.exit(1);
@@ -3409,8 +2961,8 @@ program.command("update").description("Update iloom-cli to the latest version").
3409
2961
  });
3410
2962
  program.command("test-github").description("Test GitHub integration (Issue #3)").argument("<identifier>", "Issue number or PR number").option("--no-claude", "Skip Claude for branch name generation").action(async (identifier, options) => {
3411
2963
  try {
3412
- const { GitHubService: GitHubService2 } = await import("./GitHubService-S2OGUTDR.js");
3413
- const { DefaultBranchNamingService: DefaultBranchNamingService2 } = await import("./BranchNamingService-B5PVRR7F.js");
2964
+ const { GitHubService: GitHubService2 } = await import("./GitHubService-O7U4UQ7N.js");
2965
+ const { DefaultBranchNamingService: DefaultBranchNamingService2 } = await import("./BranchNamingService-FLPUUFOB.js");
3414
2966
  logger.info("Testing GitHub Integration\n");
3415
2967
  const service = new GitHubService2();
3416
2968
  const branchNaming = new DefaultBranchNamingService2({ useClaude: options.claude !== false });
@@ -3468,14 +3020,14 @@ program.command("test-github").description("Test GitHub integration (Issue #3)")
3468
3020
  });
3469
3021
  program.command("test-claude").description("Test Claude integration (Issue #10)").option("--detect", "Test Claude CLI detection").option("--version", "Get Claude CLI version").option("--branch <title>", "Test branch name generation with given title").option("--issue <number>", "Issue number for branch generation", "123").option("--launch <prompt>", "Launch Claude with a prompt (headless)").option("--interactive", "Launch Claude interactively (requires --launch)").option("--template <name>", "Test template loading").action(async (options) => {
3470
3022
  try {
3471
- const { detectClaudeCli: detectClaudeCli2, getClaudeVersion, generateBranchName, launchClaude: launchClaude2 } = await import("./claude-H33OQMXO.js");
3472
- const { PromptTemplateManager } = await import("./PromptTemplateManager-C3DK6XZL.js");
3473
- const { ClaudeService } = await import("./ClaudeService-O2PB22GX.js");
3474
- const { ClaudeContextManager: ClaudeContextManager2 } = await import("./ClaudeContextManager-6J2EB4QU.js");
3023
+ const { detectClaudeCli, getClaudeVersion, generateBranchName, launchClaude: launchClaude2 } = await import("./claude-6H36IBHO.js");
3024
+ const { PromptTemplateManager } = await import("./PromptTemplateManager-7L3HJQQU.js");
3025
+ const { ClaudeService } = await import("./ClaudeService-CRSETT3A.js");
3026
+ const { ClaudeContextManager: ClaudeContextManager2 } = await import("./ClaudeContextManager-KE5TBZVZ.js");
3475
3027
  logger.info("Testing Claude Integration\n");
3476
3028
  if (options.detect) {
3477
3029
  logger.info("Detecting Claude CLI...");
3478
- const isAvailable = await detectClaudeCli2();
3030
+ const isAvailable = await detectClaudeCli();
3479
3031
  if (isAvailable) {
3480
3032
  logger.success(" Claude CLI is available");
3481
3033
  } else {
@@ -3531,7 +3083,7 @@ program.command("test-claude").description("Test Claude integration (Issue #10)"
3531
3083
  if (!options.detect && !options.version && !options.branch && !options.launch && !options.template) {
3532
3084
  logger.info("Running full Claude integration test suite...\n");
3533
3085
  logger.info("1. Testing Claude CLI detection...");
3534
- const isAvailable = await detectClaudeCli2();
3086
+ const isAvailable = await detectClaudeCli();
3535
3087
  if (isAvailable) {
3536
3088
  logger.success(" Claude CLI is available");
3537
3089
  } else {
@@ -3606,7 +3158,7 @@ program.command("test-claude").description("Test Claude integration (Issue #10)"
3606
3158
  });
3607
3159
  program.command("test-webserver").description("Test if a web server is running on a workspace port").argument("<issue-number>", "Issue number (port will be calculated as 3000 + issue number)", parseInt).option("--kill", "Kill the web server if detected").action(async (issueNumber, options) => {
3608
3160
  try {
3609
- const { TestWebserverCommand } = await import("./test-webserver-YVQD42W6.js");
3161
+ const { TestWebserverCommand } = await import("./test-webserver-NRMGT2HB.js");
3610
3162
  const command = new TestWebserverCommand();
3611
3163
  await command.execute({ issueNumber, options });
3612
3164
  } catch (error) {
@@ -3619,7 +3171,7 @@ program.command("test-webserver").description("Test if a web server is running o
3619
3171
  });
3620
3172
  program.command("test-git").description("Test Git integration - findMainWorktreePath() function (reads .iloom/settings.json)").action(async () => {
3621
3173
  try {
3622
- const { TestGitCommand } = await import("./test-git-ZB6AGGRW.js");
3174
+ const { TestGitCommand } = await import("./test-git-E2BLXR6M.js");
3623
3175
  const command = new TestGitCommand();
3624
3176
  await command.execute();
3625
3177
  } catch (error) {
@@ -3645,7 +3197,7 @@ program.command("test-tabs").description("Test iTerm2 dual tab functionality - o
3645
3197
  });
3646
3198
  program.command("test-prefix").description("Test worktree prefix configuration - preview worktree paths (reads .iloom/settings.json)").action(async () => {
3647
3199
  try {
3648
- const { TestPrefixCommand } = await import("./test-prefix-FBGXKMPA.js");
3200
+ const { TestPrefixCommand } = await import("./test-prefix-A7JGGYAA.js");
3649
3201
  const command = new TestPrefixCommand();
3650
3202
  await command.execute();
3651
3203
  } catch (error) {
@@ -3659,7 +3211,7 @@ program.command("test-prefix").description("Test worktree prefix configuration -
3659
3211
  program.command("summary").description("Generate Claude session summary for a loom").argument("[identifier]", "Issue number, PR number (pr/123), or branch name (auto-detected if omitted)").option("--with-comment", "Post summary as a comment to the issue/PR").option("--json", "Output result as JSON").action(async (identifier, options) => {
3660
3212
  const executeAction = async () => {
3661
3213
  try {
3662
- const { SummaryCommand } = await import("./summary-CVFAMDOJ.js");
3214
+ const { SummaryCommand } = await import("./summary-G6L3VAKK.js");
3663
3215
  const command = new SummaryCommand();
3664
3216
  const result = await command.execute({ identifier, options });
3665
3217
  if (options.json && result) {
@@ -3688,7 +3240,7 @@ program.command("summary").description("Generate Claude session summary for a lo
3688
3240
  program.command("recap").description("Get recap for a loom (defaults to current directory)").argument("[identifier]", "Issue number, PR number (pr/123), or branch name (auto-detected if omitted)").option("--json", "Output as JSON with filePath for file watching").action(async (identifier, options) => {
3689
3241
  const executeAction = async () => {
3690
3242
  try {
3691
- const { RecapCommand } = await import("./recap-VOOUXOGP.js");
3243
+ const { RecapCommand } = await import("./recap-ZKGHZCX6.js");
3692
3244
  const command = new RecapCommand();
3693
3245
  const result = await command.execute({ identifier, json: options.json });
3694
3246
  if (options.json && result) {
@@ -3717,7 +3269,7 @@ program.command("recap").description("Get recap for a loom (defaults to current
3717
3269
  program.command("test-neon").description("Test Neon integration and debug configuration").action(async () => {
3718
3270
  var _a;
3719
3271
  try {
3720
- const { SettingsManager: SettingsManager2 } = await import("./SettingsManager-35F5RUJH.js");
3272
+ const { SettingsManager: SettingsManager2 } = await import("./SettingsManager-YU4VYPTW.js");
3721
3273
  const { createNeonProviderFromSettings: createNeonProviderFromSettings2 } = await import("./neon-helpers-3KBC4A3Y.js");
3722
3274
  logger.info("Testing Neon Integration\n");
3723
3275
  logger.info("1. Settings Configuration:");