@iloom/cli 0.12.2 → 0.13.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 (165) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +1 -1
  3. package/dist/{ClaudeContextManager-IENAE2CP.js → ClaudeContextManager-ZH6LEA5I.js} +5 -5
  4. package/dist/{ClaudeService-YIJCZUUB.js → ClaudeService-YR66WXZN.js} +4 -4
  5. package/dist/{IssueTrackerFactory-2OI7YIN6.js → IssueTrackerFactory-O2ZBA666.js} +3 -3
  6. package/dist/{LoomLauncher-3TSFW7QP.js → LoomLauncher-V54ENBEF.js} +5 -5
  7. package/dist/{MetadataManager-V4LSJ2PB.js → MetadataManager-HHE6LQF2.js} +2 -2
  8. package/dist/{PromptTemplateManager-I75WKXM4.js → PromptTemplateManager-4RFELNYY.js} +2 -2
  9. package/dist/README.md +1 -1
  10. package/dist/{SettingsManager-BMQCAXPP.js → SettingsManager-SLSYEYDZ.js} +4 -2
  11. package/dist/agents/iloom-artifact-reviewer.md +1 -0
  12. package/dist/agents/iloom-code-reviewer.md +21 -0
  13. package/dist/agents/iloom-issue-analyze-and-plan.md +30 -12
  14. package/dist/agents/iloom-issue-analyzer.md +32 -7
  15. package/dist/agents/iloom-issue-complexity-evaluator.md +32 -12
  16. package/dist/agents/iloom-issue-implementer.md +31 -12
  17. package/dist/agents/iloom-issue-planner.md +30 -12
  18. package/dist/agents/iloom-wave-verifier.md +126 -5
  19. package/dist/{build-PKHPE34Y.js → build-ZTGWDHWU.js} +8 -8
  20. package/dist/{chunk-GWJWECZB.js → chunk-55NTREIU.js} +25 -23
  21. package/dist/chunk-55NTREIU.js.map +1 -0
  22. package/dist/{chunk-AYLC633W.js → chunk-7TN5VW4I.js} +65 -7
  23. package/dist/chunk-7TN5VW4I.js.map +1 -0
  24. package/dist/{chunk-PMB6TYV4.js → chunk-C2BVNJW5.js} +2 -2
  25. package/dist/{chunk-QFDM23CO.js → chunk-E5OM25WK.js} +3 -3
  26. package/dist/{chunk-BFF27W3S.js → chunk-ERMEYFT6.js} +7 -2
  27. package/dist/chunk-ERMEYFT6.js.map +1 -0
  28. package/dist/{chunk-653XBU3L.js → chunk-F5NKWLMQ.js} +21 -15
  29. package/dist/chunk-F5NKWLMQ.js.map +1 -0
  30. package/dist/{chunk-H2M62IKR.js → chunk-G2DGDCDP.js} +15 -185
  31. package/dist/chunk-G2DGDCDP.js.map +1 -0
  32. package/dist/{chunk-35CBWAJL.js → chunk-GPBX2BY2.js} +2 -2
  33. package/dist/{chunk-ZM2AYHMO.js → chunk-GQDVH6FA.js} +2 -2
  34. package/dist/{chunk-GDDRANOZ.js → chunk-HWDQRW3O.js} +3 -3
  35. package/dist/chunk-HWDQRW3O.js.map +1 -0
  36. package/dist/{chunk-M3FBM4T3.js → chunk-J5JOJPK3.js} +2 -2
  37. package/dist/{chunk-UMZ2CIDW.js → chunk-KCAWSZUO.js} +11 -10
  38. package/dist/chunk-KCAWSZUO.js.map +1 -0
  39. package/dist/{chunk-OPQC4OWM.js → chunk-KGOBNC5A.js} +4 -4
  40. package/dist/{chunk-V5IYLWRA.js → chunk-LNY2Y32V.js} +2 -2
  41. package/dist/{chunk-7RCUWU3I.js → chunk-MRPIDNZU.js} +1 -1
  42. package/dist/chunk-MRPIDNZU.js.map +1 -0
  43. package/dist/{chunk-YWNF5755.js → chunk-N6DY47YN.js} +80 -45
  44. package/dist/chunk-N6DY47YN.js.map +1 -0
  45. package/dist/{chunk-VIQOQ463.js → chunk-OLJ54WGW.js} +15 -10
  46. package/dist/chunk-OLJ54WGW.js.map +1 -0
  47. package/dist/{chunk-6RINI7OL.js → chunk-PPQ5LV7U.js} +2 -2
  48. package/dist/{chunk-CTHB7345.js → chunk-PS6K2AOV.js} +5 -5
  49. package/dist/{chunk-Y2MVSEJK.js → chunk-QNRXRSKC.js} +6 -3
  50. package/dist/chunk-QNRXRSKC.js.map +1 -0
  51. package/dist/{chunk-EQIII6GI.js → chunk-SM3BCHYB.js} +5 -5
  52. package/dist/{chunk-OKB2NEDQ.js → chunk-T4KFKKEB.js} +7 -7
  53. package/dist/{chunk-VA6CWUAE.js → chunk-T4NESGYB.js} +3 -3
  54. package/dist/{chunk-VZ55H5X6.js → chunk-TJDKGKQV.js} +2 -2
  55. package/dist/{chunk-KCZSUJUR.js → chunk-UXBVDD7U.js} +6 -6
  56. package/dist/{chunk-QF2DROQR.js → chunk-WYDLOQYO.js} +2 -2
  57. package/dist/{chunk-4JZEQBWV.js → chunk-XIVLGWUX.js} +3 -1
  58. package/dist/chunk-XIVLGWUX.js.map +1 -0
  59. package/dist/{chunk-R7DGN73N.js → chunk-ZEFTWM5Z.js} +2 -2
  60. package/dist/{cleanup-QTZU46OP.js → cleanup-BCVY7PEF.js} +18 -18
  61. package/dist/cli.js +94 -69
  62. package/dist/cli.js.map +1 -1
  63. package/dist/{commit-P3OOQ3JN.js → commit-L5JNBU4U.js} +8 -8
  64. package/dist/{compile-IOXQTCP3.js → compile-GPJOHXH4.js} +8 -8
  65. package/dist/{contribute-EHWLYOMZ.js → contribute-QEGCI4PS.js} +4 -4
  66. package/dist/{dev-server-BLJ7NPKG.js → dev-server-67NPVWUN.js} +247 -31
  67. package/dist/dev-server-67NPVWUN.js.map +1 -0
  68. package/dist/{feedback-RFWGB4I5.js → feedback-2LWXKLQZ.js} +4 -4
  69. package/dist/{git-I3PO6FY7.js → git-IS7AV3ED.js} +4 -4
  70. package/dist/hooks/iloom-hook.js +40 -2
  71. package/dist/{ignite-6XT5BKSF.js → ignite-VQDJQ37S.js} +12 -14
  72. package/dist/index.d.ts +69 -37
  73. package/dist/index.js +23 -15
  74. package/dist/index.js.map +1 -1
  75. package/dist/{init-6FUPISD7.js → init-7SDJUAEZ.js} +8 -8
  76. package/dist/{install-deps-BMRONT2L.js → install-deps-NGSFDNUW.js} +8 -8
  77. package/dist/{issues-SUFQJY6O.js → issues-4HQKEUP7.js} +5 -5
  78. package/dist/{lint-PRD2DO5K.js → lint-C5FOVRXY.js} +8 -8
  79. package/dist/mcp/issue-management-server.js +19 -14
  80. package/dist/mcp/issue-management-server.js.map +1 -1
  81. package/dist/{open-NVFYA5IM.js → open-WUTLRI6S.js} +19 -15
  82. package/dist/open-WUTLRI6S.js.map +1 -0
  83. package/dist/{plan-7M5WB7VT.js → plan-GC3HF73T.js} +70 -39
  84. package/dist/plan-GC3HF73T.js.map +1 -0
  85. package/dist/{projects-L5AHUBGA.js → projects-3F6T3KZL.js} +2 -2
  86. package/dist/prompts/init-prompt.txt +40 -12
  87. package/dist/prompts/issue-prompt.txt +3 -0
  88. package/dist/prompts/plan-prompt.txt +67 -11
  89. package/dist/prompts/swarm-orchestrator-prompt.txt +25 -12
  90. package/dist/{rebase-5B7WQQAY.js → rebase-CSGQICAP.js} +7 -7
  91. package/dist/{recap-7PSTJ3MF.js → recap-CKGKFDJL.js} +7 -7
  92. package/dist/{run-6ZCDG7MX.js → run-3YL2IXXI.js} +19 -15
  93. package/dist/run-3YL2IXXI.js.map +1 -0
  94. package/dist/schema/settings.schema.json +35 -7
  95. package/dist/{shell-MX3WRDQN.js → shell-M2YYPNGV.js} +6 -6
  96. package/dist/{summary-WQ7QOAIQ.js → summary-XR4CBJEG.js} +9 -9
  97. package/dist/{test-LAIG62XM.js → test-ESDAHEVE.js} +8 -8
  98. package/dist/{test-git-PYJOYSED.js → test-git-KWPLHYSI.js} +4 -4
  99. package/dist/{test-jira-SM7IU5HW.js → test-jira-6NK7UHSV.js} +3 -3
  100. package/dist/{test-prefix-HIRZBXTM.js → test-prefix-VVODGHXP.js} +4 -4
  101. package/dist/{test-webserver-43PVP2JL.js → test-webserver-AHXKC6H4.js} +6 -6
  102. package/dist/{vscode-VZCCFQRY.js → vscode-OY7HOVRO.js} +6 -6
  103. package/package.json +1 -1
  104. package/dist/chunk-4JZEQBWV.js.map +0 -1
  105. package/dist/chunk-653XBU3L.js.map +0 -1
  106. package/dist/chunk-7RCUWU3I.js.map +0 -1
  107. package/dist/chunk-AYLC633W.js.map +0 -1
  108. package/dist/chunk-BFF27W3S.js.map +0 -1
  109. package/dist/chunk-GDDRANOZ.js.map +0 -1
  110. package/dist/chunk-GWJWECZB.js.map +0 -1
  111. package/dist/chunk-H2M62IKR.js.map +0 -1
  112. package/dist/chunk-UMZ2CIDW.js.map +0 -1
  113. package/dist/chunk-VIQOQ463.js.map +0 -1
  114. package/dist/chunk-Y2MVSEJK.js.map +0 -1
  115. package/dist/chunk-YWNF5755.js.map +0 -1
  116. package/dist/dev-server-BLJ7NPKG.js.map +0 -1
  117. package/dist/open-NVFYA5IM.js.map +0 -1
  118. package/dist/plan-7M5WB7VT.js.map +0 -1
  119. package/dist/run-6ZCDG7MX.js.map +0 -1
  120. /package/dist/{ClaudeContextManager-IENAE2CP.js.map → ClaudeContextManager-ZH6LEA5I.js.map} +0 -0
  121. /package/dist/{ClaudeService-YIJCZUUB.js.map → ClaudeService-YR66WXZN.js.map} +0 -0
  122. /package/dist/{IssueTrackerFactory-2OI7YIN6.js.map → IssueTrackerFactory-O2ZBA666.js.map} +0 -0
  123. /package/dist/{LoomLauncher-3TSFW7QP.js.map → LoomLauncher-V54ENBEF.js.map} +0 -0
  124. /package/dist/{MetadataManager-V4LSJ2PB.js.map → MetadataManager-HHE6LQF2.js.map} +0 -0
  125. /package/dist/{PromptTemplateManager-I75WKXM4.js.map → PromptTemplateManager-4RFELNYY.js.map} +0 -0
  126. /package/dist/{SettingsManager-BMQCAXPP.js.map → SettingsManager-SLSYEYDZ.js.map} +0 -0
  127. /package/dist/{build-PKHPE34Y.js.map → build-ZTGWDHWU.js.map} +0 -0
  128. /package/dist/{chunk-PMB6TYV4.js.map → chunk-C2BVNJW5.js.map} +0 -0
  129. /package/dist/{chunk-QFDM23CO.js.map → chunk-E5OM25WK.js.map} +0 -0
  130. /package/dist/{chunk-35CBWAJL.js.map → chunk-GPBX2BY2.js.map} +0 -0
  131. /package/dist/{chunk-ZM2AYHMO.js.map → chunk-GQDVH6FA.js.map} +0 -0
  132. /package/dist/{chunk-M3FBM4T3.js.map → chunk-J5JOJPK3.js.map} +0 -0
  133. /package/dist/{chunk-OPQC4OWM.js.map → chunk-KGOBNC5A.js.map} +0 -0
  134. /package/dist/{chunk-V5IYLWRA.js.map → chunk-LNY2Y32V.js.map} +0 -0
  135. /package/dist/{chunk-6RINI7OL.js.map → chunk-PPQ5LV7U.js.map} +0 -0
  136. /package/dist/{chunk-CTHB7345.js.map → chunk-PS6K2AOV.js.map} +0 -0
  137. /package/dist/{chunk-EQIII6GI.js.map → chunk-SM3BCHYB.js.map} +0 -0
  138. /package/dist/{chunk-OKB2NEDQ.js.map → chunk-T4KFKKEB.js.map} +0 -0
  139. /package/dist/{chunk-VA6CWUAE.js.map → chunk-T4NESGYB.js.map} +0 -0
  140. /package/dist/{chunk-VZ55H5X6.js.map → chunk-TJDKGKQV.js.map} +0 -0
  141. /package/dist/{chunk-KCZSUJUR.js.map → chunk-UXBVDD7U.js.map} +0 -0
  142. /package/dist/{chunk-QF2DROQR.js.map → chunk-WYDLOQYO.js.map} +0 -0
  143. /package/dist/{chunk-R7DGN73N.js.map → chunk-ZEFTWM5Z.js.map} +0 -0
  144. /package/dist/{cleanup-QTZU46OP.js.map → cleanup-BCVY7PEF.js.map} +0 -0
  145. /package/dist/{commit-P3OOQ3JN.js.map → commit-L5JNBU4U.js.map} +0 -0
  146. /package/dist/{compile-IOXQTCP3.js.map → compile-GPJOHXH4.js.map} +0 -0
  147. /package/dist/{contribute-EHWLYOMZ.js.map → contribute-QEGCI4PS.js.map} +0 -0
  148. /package/dist/{feedback-RFWGB4I5.js.map → feedback-2LWXKLQZ.js.map} +0 -0
  149. /package/dist/{git-I3PO6FY7.js.map → git-IS7AV3ED.js.map} +0 -0
  150. /package/dist/{ignite-6XT5BKSF.js.map → ignite-VQDJQ37S.js.map} +0 -0
  151. /package/dist/{init-6FUPISD7.js.map → init-7SDJUAEZ.js.map} +0 -0
  152. /package/dist/{install-deps-BMRONT2L.js.map → install-deps-NGSFDNUW.js.map} +0 -0
  153. /package/dist/{issues-SUFQJY6O.js.map → issues-4HQKEUP7.js.map} +0 -0
  154. /package/dist/{lint-PRD2DO5K.js.map → lint-C5FOVRXY.js.map} +0 -0
  155. /package/dist/{projects-L5AHUBGA.js.map → projects-3F6T3KZL.js.map} +0 -0
  156. /package/dist/{rebase-5B7WQQAY.js.map → rebase-CSGQICAP.js.map} +0 -0
  157. /package/dist/{recap-7PSTJ3MF.js.map → recap-CKGKFDJL.js.map} +0 -0
  158. /package/dist/{shell-MX3WRDQN.js.map → shell-M2YYPNGV.js.map} +0 -0
  159. /package/dist/{summary-WQ7QOAIQ.js.map → summary-XR4CBJEG.js.map} +0 -0
  160. /package/dist/{test-LAIG62XM.js.map → test-ESDAHEVE.js.map} +0 -0
  161. /package/dist/{test-git-PYJOYSED.js.map → test-git-KWPLHYSI.js.map} +0 -0
  162. /package/dist/{test-jira-SM7IU5HW.js.map → test-jira-6NK7UHSV.js.map} +0 -0
  163. /package/dist/{test-prefix-HIRZBXTM.js.map → test-prefix-VVODGHXP.js.map} +0 -0
  164. /package/dist/{test-webserver-43PVP2JL.js.map → test-webserver-AHXKC6H4.js.map} +0 -0
  165. /package/dist/{vscode-VZCCFQRY.js.map → vscode-OY7HOVRO.js.map} +0 -0
@@ -1,19 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  IdentifierParser
4
- } from "./chunk-ZM2AYHMO.js";
4
+ } from "./chunk-GQDVH6FA.js";
5
5
  import {
6
6
  GitWorktreeManager
7
- } from "./chunk-6RINI7OL.js";
7
+ } from "./chunk-PPQ5LV7U.js";
8
8
  import {
9
9
  extractIssueNumber
10
- } from "./chunk-OPQC4OWM.js";
10
+ } from "./chunk-KGOBNC5A.js";
11
11
  import {
12
12
  SettingsManager
13
- } from "./chunk-653XBU3L.js";
13
+ } from "./chunk-F5NKWLMQ.js";
14
14
  import {
15
15
  MetadataManager
16
- } from "./chunk-4JZEQBWV.js";
16
+ } from "./chunk-XIVLGWUX.js";
17
17
  import "./chunk-FTYWGQFM.js";
18
18
  import {
19
19
  getDotenvFlowFiles,
@@ -242,4 +242,4 @@ var ShellCommand = class {
242
242
  export {
243
243
  ShellCommand
244
244
  };
245
- //# sourceMappingURL=shell-MX3WRDQN.js.map
245
+ //# sourceMappingURL=shell-M2YYPNGV.js.map
@@ -1,18 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  SessionSummaryService
4
- } from "./chunk-KCZSUJUR.js";
4
+ } from "./chunk-UXBVDD7U.js";
5
5
  import "./chunk-NXMDEL3F.js";
6
6
  import {
7
7
  PRManager
8
- } from "./chunk-M3FBM4T3.js";
9
- import "./chunk-R7DGN73N.js";
8
+ } from "./chunk-J5JOJPK3.js";
9
+ import "./chunk-ZEFTWM5Z.js";
10
10
  import "./chunk-4232AHNQ.js";
11
11
  import {
12
12
  GitWorktreeManager
13
- } from "./chunk-6RINI7OL.js";
13
+ } from "./chunk-PPQ5LV7U.js";
14
14
  import "./chunk-WEBMMJKL.js";
15
- import "./chunk-7RCUWU3I.js";
15
+ import "./chunk-MRPIDNZU.js";
16
16
  import "./chunk-DMSL5BAP.js";
17
17
  import "./chunk-KV4NU3RP.js";
18
18
  import "./chunk-NPVA65KS.js";
@@ -22,13 +22,13 @@ import "./chunk-BZ7KTXPB.js";
22
22
  import "./chunk-DDHWZNGL.js";
23
23
  import {
24
24
  extractIssueNumber
25
- } from "./chunk-OPQC4OWM.js";
25
+ } from "./chunk-KGOBNC5A.js";
26
26
  import {
27
27
  SettingsManager
28
- } from "./chunk-653XBU3L.js";
28
+ } from "./chunk-F5NKWLMQ.js";
29
29
  import {
30
30
  MetadataManager
31
- } from "./chunk-4JZEQBWV.js";
31
+ } from "./chunk-XIVLGWUX.js";
32
32
  import {
33
33
  getLogger
34
34
  } from "./chunk-FTYWGQFM.js";
@@ -284,4 +284,4 @@ Please provide an issue number, PR number, or branch name.`
284
284
  export {
285
285
  SummaryCommand
286
286
  };
287
- //# sourceMappingURL=summary-WQ7QOAIQ.js.map
287
+ //# sourceMappingURL=summary-XR4CBJEG.js.map
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ScriptCommandBase
4
- } from "./chunk-CTHB7345.js";
5
- import "./chunk-ZM2AYHMO.js";
6
- import "./chunk-VIQOQ463.js";
7
- import "./chunk-6RINI7OL.js";
4
+ } from "./chunk-PS6K2AOV.js";
5
+ import "./chunk-GQDVH6FA.js";
6
+ import "./chunk-OLJ54WGW.js";
7
+ import "./chunk-PPQ5LV7U.js";
8
8
  import "./chunk-K3QGG4O2.js";
9
- import "./chunk-OPQC4OWM.js";
10
- import "./chunk-653XBU3L.js";
11
- import "./chunk-4JZEQBWV.js";
9
+ import "./chunk-KGOBNC5A.js";
10
+ import "./chunk-F5NKWLMQ.js";
11
+ import "./chunk-XIVLGWUX.js";
12
12
  import "./chunk-FTYWGQFM.js";
13
13
  import "./chunk-VRPPI6GU.js";
14
14
 
@@ -24,4 +24,4 @@ var TestCommand = class extends ScriptCommandBase {
24
24
  export {
25
25
  TestCommand
26
26
  };
27
- //# sourceMappingURL=test-LAIG62XM.js.map
27
+ //# sourceMappingURL=test-ESDAHEVE.js.map
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  findMainWorktreePath
4
- } from "./chunk-OPQC4OWM.js";
4
+ } from "./chunk-KGOBNC5A.js";
5
5
  import {
6
6
  SettingsManager
7
- } from "./chunk-653XBU3L.js";
8
- import "./chunk-4JZEQBWV.js";
7
+ } from "./chunk-F5NKWLMQ.js";
8
+ import "./chunk-XIVLGWUX.js";
9
9
  import "./chunk-FTYWGQFM.js";
10
10
  import {
11
11
  logger
@@ -51,4 +51,4 @@ var TestGitCommand = class {
51
51
  export {
52
52
  TestGitCommand
53
53
  };
54
- //# sourceMappingURL=test-git-PYJOYSED.js.map
54
+ //# sourceMappingURL=test-git-KWPLHYSI.js.map
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  IssueManagementProviderFactory
4
- } from "./chunk-R7DGN73N.js";
4
+ } from "./chunk-ZEFTWM5Z.js";
5
5
  import "./chunk-4232AHNQ.js";
6
6
  import "./chunk-DMSL5BAP.js";
7
7
  import "./chunk-KV4NU3RP.js";
8
8
  import "./chunk-NPVA65KS.js";
9
9
  import {
10
10
  SettingsManager
11
- } from "./chunk-653XBU3L.js";
11
+ } from "./chunk-F5NKWLMQ.js";
12
12
  import "./chunk-FTYWGQFM.js";
13
13
  import {
14
14
  logger
@@ -93,4 +93,4 @@ var TestJiraCommand = class {
93
93
  export {
94
94
  TestJiraCommand
95
95
  };
96
- //# sourceMappingURL=test-jira-SM7IU5HW.js.map
96
+ //# sourceMappingURL=test-jira-6NK7UHSV.js.map
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  generateWorktreePath
4
- } from "./chunk-OPQC4OWM.js";
4
+ } from "./chunk-KGOBNC5A.js";
5
5
  import {
6
6
  SettingsManager
7
- } from "./chunk-653XBU3L.js";
8
- import "./chunk-4JZEQBWV.js";
7
+ } from "./chunk-F5NKWLMQ.js";
8
+ import "./chunk-XIVLGWUX.js";
9
9
  import "./chunk-FTYWGQFM.js";
10
10
  import {
11
11
  logger
@@ -67,4 +67,4 @@ var TestPrefixCommand = class {
67
67
  export {
68
68
  TestPrefixCommand
69
69
  };
70
- //# sourceMappingURL=test-prefix-HIRZBXTM.js.map
70
+ //# sourceMappingURL=test-prefix-VVODGHXP.js.map
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ProcessManager
4
- } from "./chunk-V5IYLWRA.js";
5
- import "./chunk-BFF27W3S.js";
6
- import "./chunk-OPQC4OWM.js";
4
+ } from "./chunk-LNY2Y32V.js";
5
+ import "./chunk-ERMEYFT6.js";
6
+ import "./chunk-KGOBNC5A.js";
7
7
  import {
8
8
  SettingsManager
9
- } from "./chunk-653XBU3L.js";
10
- import "./chunk-4JZEQBWV.js";
9
+ } from "./chunk-F5NKWLMQ.js";
10
+ import "./chunk-XIVLGWUX.js";
11
11
  import "./chunk-FTYWGQFM.js";
12
12
  import {
13
13
  logger
@@ -81,4 +81,4 @@ var TestWebserverCommand = class {
81
81
  export {
82
82
  TestWebserverCommand
83
83
  };
84
- //# sourceMappingURL=test-webserver-43PVP2JL.js.map
84
+ //# sourceMappingURL=test-webserver-AHXKC6H4.js.map
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  IdentifierParser
4
- } from "./chunk-ZM2AYHMO.js";
4
+ } from "./chunk-GQDVH6FA.js";
5
5
  import {
6
6
  GitWorktreeManager
7
- } from "./chunk-6RINI7OL.js";
7
+ } from "./chunk-PPQ5LV7U.js";
8
8
  import {
9
9
  getInstallHint,
10
10
  isIdeAvailable
@@ -14,9 +14,9 @@ import {
14
14
  } from "./chunk-NPVA65KS.js";
15
15
  import {
16
16
  extractIssueNumber
17
- } from "./chunk-OPQC4OWM.js";
18
- import "./chunk-653XBU3L.js";
19
- import "./chunk-4JZEQBWV.js";
17
+ } from "./chunk-KGOBNC5A.js";
18
+ import "./chunk-F5NKWLMQ.js";
19
+ import "./chunk-XIVLGWUX.js";
20
20
  import "./chunk-FTYWGQFM.js";
21
21
  import {
22
22
  logger
@@ -161,4 +161,4 @@ var VSCodeCommand = class {
161
161
  export {
162
162
  VSCodeCommand
163
163
  };
164
- //# sourceMappingURL=vscode-VZCCFQRY.js.map
164
+ //# sourceMappingURL=vscode-OY7HOVRO.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iloom/cli",
3
- "version": "0.12.2",
3
+ "version": "0.13.0",
4
4
  "description": "Control plane for maintaining alignment between you and Claude Code as you work across multiple issues using isolated environments, visible context, and multi-agent workflows to scale understanding, not just output",
5
5
  "keywords": [
6
6
  "ai",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/MetadataManager.ts"],"sourcesContent":["import path from 'path'\nimport os from 'os'\nimport fs from 'fs-extra'\nimport { getLogger } from '../utils/logger-context.js'\nimport type { ProjectCapability } from '../types/loom.js'\nimport type { ComplexityOverride, OneShotMode } from '../types/index.js'\n\nexport type SwarmState = 'pending' | 'in_progress' | 'code_review' | 'done' | 'failed'\n\n/**\n * Schema for metadata JSON file\n * Stored in ~/.config/iloom-ai/looms/\n */\nexport interface MetadataFile {\n description: string\n created_at?: string\n version: number\n // Additional metadata fields (v2)\n branchName?: string\n worktreePath?: string\n issueType?: 'branch' | 'issue' | 'pr' | 'epic'\n issueKey?: string // Canonical, properly-cased issue key (e.g., \"PROJ-123\")\n issue_numbers?: string[]\n pr_numbers?: string[]\n issueTracker?: string\n colorHex?: string // Stored hex color (e.g., \"#dcebff\") - robust against palette changes\n sessionId?: string // Claude Code session ID for resume support\n projectPath?: string // Main worktree path (project root) - enables project identification\n issueUrls?: Record<string, string> // Map of issue ID to URL in the issue tracker\n prUrls?: Record<string, string> // Map of PR number to URL in the issue tracker\n draftPrNumber?: number // Draft PR number if draft-pr mode was used\n oneShot?: OneShotMode // One-shot automation mode stored during loom creation\n complexity?: ComplexityOverride // Complexity override stored during loom creation\n capabilities?: ProjectCapability[] // Detected project capabilities\n state?: SwarmState // Swarm mode lifecycle state\n childIssueNumbers?: string[] // Child issue numbers for epic looms\n parentLoom?: {\n type: 'issue' | 'pr' | 'branch' | 'epic'\n identifier: string | number\n branchName: string\n worktreePath: string\n databaseBranch?: string\n }\n // Epic/swarm child issue data (populated during spin setup)\n childIssues?: Array<{\n number: string // Prefixed: \"#123\" for GitHub, \"ENG-123\" for Linear\n title: string\n body: string\n url: string\n }>\n dependencyMap?: Record<string, string[]> // issueNumber -> array of blocking issueNumbers\n mcpConfigPath?: string // Path to per-loom MCP config file (for swarm claude -p commands)\n swarmTeamName?: string // Unique team name for swarm orchestrator (persisted for re-runs)\n}\n\n/**\n * Input for writing metadata (all fields except version and created_at)\n * Note: issueTracker is required because every loom should have an associated\n * issue tracker provider (defaults to 'github' via IssueTrackerFactory)\n */\nexport interface WriteMetadataInput {\n description: string\n branchName: string\n worktreePath: string\n issueType: 'branch' | 'issue' | 'pr' | 'epic'\n issueKey?: string // Canonical, properly-cased issue key (e.g., \"PROJ-123\")\n issue_numbers: string[]\n pr_numbers: string[]\n issueTracker: string\n colorHex: string // Hex color (e.g., \"#dcebff\") - robust against palette changes\n sessionId: string // Claude Code session ID for resume support (required for new looms)\n projectPath: string // Main worktree path (project root) - required for new looms\n issueUrls: Record<string, string> // Map of issue ID to URL in the issue tracker\n prUrls: Record<string, string> // Map of PR number to URL in the issue tracker\n draftPrNumber?: number // Draft PR number for draft-pr mode\n oneShot?: OneShotMode // One-shot automation mode to persist\n complexity?: ComplexityOverride // Complexity override to persist\n capabilities: ProjectCapability[] // Detected project capabilities (required for new looms)\n state?: SwarmState // Swarm mode lifecycle state\n childIssueNumbers?: string[] // Child issue numbers for epic looms\n parentLoom?: {\n type: 'issue' | 'pr' | 'branch' | 'epic'\n identifier: string | number\n branchName: string\n worktreePath: string\n databaseBranch?: string\n }\n // Epic/swarm child issue data (populated during spin setup)\n childIssues?: Array<{\n number: string // Prefixed: \"#123\" for GitHub, \"ENG-123\" for Linear\n title: string\n body: string\n url: string\n }>\n dependencyMap?: Record<string, string[]> // issueNumber -> array of blocking issueNumbers\n mcpConfigPath?: string // Path to per-loom MCP config file (for swarm claude -p commands)\n swarmTeamName?: string // Unique team name for swarm orchestrator (persisted for re-runs)\n}\n\n/**\n * Result of reading metadata for a worktree\n */\nexport interface LoomMetadata {\n status?: 'active' | 'finished'\n finishedAt?: string | null\n description: string\n created_at: string | null\n branchName: string | null\n worktreePath: string | null\n issueType: 'branch' | 'issue' | 'pr' | 'epic' | null\n issueKey: string | null // Canonical, properly-cased issue key (e.g., \"PROJ-123\")\n issue_numbers: string[]\n pr_numbers: string[]\n issueTracker: string | null\n colorHex: string | null // Hex color (e.g., \"#dcebff\") - robust against palette changes\n sessionId: string | null // Claude Code session ID (null for legacy looms)\n projectPath: string | null // Main worktree path (null for legacy looms)\n issueUrls: Record<string, string> // Map of issue ID to URL ({} for legacy looms)\n prUrls: Record<string, string> // Map of PR number to URL ({} for legacy looms)\n draftPrNumber: number | null // Draft PR number (null if not draft mode)\n oneShot: OneShotMode | null // One-shot mode (null for legacy looms)\n complexity: ComplexityOverride | null // Complexity override (null when not overridden)\n capabilities: ProjectCapability[] // Detected project capabilities (empty for legacy looms)\n state: SwarmState | null // Swarm mode lifecycle state (null for non-swarm looms)\n childIssueNumbers: string[] // Child issue numbers for epic looms (empty for non-epic looms)\n parentLoom: {\n type: 'issue' | 'pr' | 'branch' | 'epic'\n identifier: string | number\n branchName: string\n worktreePath: string\n databaseBranch?: string\n } | null\n // Epic/swarm child issue data (empty arrays/objects for non-epic looms)\n childIssues: Array<{\n number: string\n title: string\n body: string\n url: string\n }>\n dependencyMap: Record<string, string[]>\n mcpConfigPath: string | null // Path to per-loom MCP config file (null for non-swarm looms)\n swarmTeamName: string | null // Unique team name for swarm orchestrator (null for non-swarm looms)\n}\n\n/**\n * MetadataManager: Manage loom metadata persistence\n *\n * Stores loom metadata in ~/.config/iloom-ai/looms/ directory.\n * Each worktree gets a JSON file named by slugifying its absolute path.\n *\n * Per spec section 2.2:\n * - Filename derived from worktree absolute path\n * - Path separators replaced with double underscores\n * - Non-alphanumeric chars (except _ and -) replaced with hyphens\n */\nexport class MetadataManager {\n private readonly loomsDir: string\n private readonly finishedDir: string\n\n constructor() {\n this.loomsDir = path.join(os.homedir(), '.config', 'iloom-ai', 'looms')\n this.finishedDir = path.join(this.loomsDir, 'finished')\n }\n\n /**\n * Convert MetadataFile to LoomMetadata with default values for optional fields\n */\n private toMetadata(data: MetadataFile): LoomMetadata {\n return {\n description: data.description,\n created_at: data.created_at ?? null,\n branchName: data.branchName ?? null,\n worktreePath: data.worktreePath ?? null,\n issueType: data.issueType ?? null,\n issueKey: data.issueKey ?? null,\n issue_numbers: data.issue_numbers ?? [],\n pr_numbers: data.pr_numbers ?? [],\n issueTracker: data.issueTracker ?? null,\n colorHex: data.colorHex ?? null,\n sessionId: data.sessionId ?? null,\n projectPath: data.projectPath ?? null,\n issueUrls: data.issueUrls ?? {},\n prUrls: data.prUrls ?? {},\n draftPrNumber: data.draftPrNumber ?? null,\n oneShot: data.oneShot ?? null,\n complexity: data.complexity ?? null,\n capabilities: data.capabilities ?? [],\n state: data.state ?? null,\n childIssueNumbers: data.childIssueNumbers ?? [],\n parentLoom: data.parentLoom ?? null,\n childIssues: data.childIssues ?? [],\n dependencyMap: data.dependencyMap ?? {},\n mcpConfigPath: data.mcpConfigPath ?? null,\n swarmTeamName: data.swarmTeamName ?? null,\n }\n }\n\n /**\n * Convert worktree path to filename slug per spec section 2.2\n *\n * Algorithm:\n * 1. Trim trailing slashes\n * 2. Replace all path separators (/ or \\) with __ (double underscore)\n * 3. Replace any other non-alphanumeric characters (except _ and -) with -\n * 4. Append .json\n *\n * Example:\n * - Worktree: /Users/jane/dev/repo\n * - Filename: _Users__jane__dev__repo.json\n */\n slugifyPath(worktreePath: string): string {\n // 1. Trim trailing slashes\n let slug = worktreePath.replace(/[/\\\\]+$/, '')\n\n // 2. Replace path separators with triple underscores\n slug = slug.replace(/[/\\\\]/g, '___')\n\n // 3. Replace non-alphanumeric chars (except _ and -) with hyphens\n slug = slug.replace(/[^a-zA-Z0-9_-]/g, '-')\n\n // 4. Append .json\n return `${slug}.json`\n }\n\n /**\n * Get the full path to the metadata file for a worktree\n */\n private getFilePath(worktreePath: string): string {\n const filename = this.slugifyPath(worktreePath)\n return path.join(this.loomsDir, filename)\n }\n\n /**\n * Get the full path to the metadata file for a worktree (public API)\n * Used by other services that need to reference the metadata file location\n * (e.g., MCP servers that need to read loom context)\n */\n getMetadataFilePath(worktreePath: string): string {\n return this.getFilePath(worktreePath)\n }\n\n /**\n * Write metadata for a worktree (spec section 3.1)\n *\n * @param worktreePath - Absolute path to the worktree (used for file naming)\n * @param input - Metadata to write (description plus additional fields)\n */\n async writeMetadata(worktreePath: string, input: WriteMetadataInput): Promise<void> {\n try {\n // 1. Ensure looms directory exists\n await fs.ensureDir(this.loomsDir, { mode: 0o755 })\n\n // 2. Create JSON content\n const content: MetadataFile = {\n description: input.description,\n created_at: new Date().toISOString(),\n version: 1,\n branchName: input.branchName,\n worktreePath: input.worktreePath,\n issueType: input.issueType,\n ...(input.issueKey && { issueKey: input.issueKey }),\n issue_numbers: input.issue_numbers,\n pr_numbers: input.pr_numbers,\n issueTracker: input.issueTracker,\n colorHex: input.colorHex,\n sessionId: input.sessionId,\n projectPath: input.projectPath,\n issueUrls: input.issueUrls,\n prUrls: input.prUrls,\n capabilities: input.capabilities,\n ...(input.draftPrNumber && { draftPrNumber: input.draftPrNumber }),\n ...(input.oneShot && { oneShot: input.oneShot }),\n ...(input.complexity && { complexity: input.complexity }),\n ...(input.state && { state: input.state }),\n ...(input.childIssueNumbers && input.childIssueNumbers.length > 0 && { childIssueNumbers: input.childIssueNumbers }),\n ...(input.parentLoom && { parentLoom: input.parentLoom }),\n ...(input.childIssues && input.childIssues.length > 0 && { childIssues: input.childIssues }),\n ...(input.dependencyMap && Object.keys(input.dependencyMap).length > 0 && { dependencyMap: input.dependencyMap }),\n ...(input.mcpConfigPath && { mcpConfigPath: input.mcpConfigPath }),\n }\n\n // 3. Write to slugified filename\n const filePath = this.getFilePath(worktreePath)\n await fs.writeFile(filePath, JSON.stringify(content, null, 2), { mode: 0o644 })\n\n getLogger().debug(`Metadata written for worktree: ${worktreePath}`)\n } catch (error) {\n // Log warning but don't throw - metadata is supplementary\n getLogger().warn(\n `Failed to write metadata for worktree: ${error instanceof Error ? error.message : String(error)}`\n )\n }\n }\n\n /**\n * Read metadata for a worktree (spec section 3.2)\n *\n * @param worktreePath - Absolute path to the worktree\n * @returns The metadata object with all fields, or null if not found/invalid\n */\n async readMetadata(worktreePath: string): Promise<LoomMetadata | null> {\n try {\n const filePath = this.getFilePath(worktreePath)\n\n // Check if file exists\n if (!(await fs.pathExists(filePath))) {\n return null\n }\n\n // Read and parse JSON\n const content = await fs.readFile(filePath, 'utf8')\n const data: MetadataFile = JSON.parse(content)\n\n if (!data.description) {\n return null\n }\n\n return this.toMetadata(data)\n } catch (error) {\n // Return null on any error (graceful degradation per spec)\n getLogger().debug(\n `Could not read metadata for worktree ${worktreePath}: ${error instanceof Error ? error.message : String(error)}`\n )\n return null\n }\n }\n\n /**\n * List all stored loom metadata files\n *\n * Returns an array of LoomMetadata objects for all valid metadata files\n * in the looms directory. Invalid or unreadable files are skipped.\n *\n * @returns Array of LoomMetadata objects from all stored files\n */\n async listAllMetadata(): Promise<LoomMetadata[]> {\n const results: LoomMetadata[] = []\n\n try {\n // Check if looms directory exists\n if (!(await fs.pathExists(this.loomsDir))) {\n return results\n }\n\n // Read all files in looms directory\n const files = await fs.readdir(this.loomsDir)\n\n // Filter to only .json files and read each\n for (const file of files) {\n if (!file.endsWith('.json')) {\n continue\n }\n\n try {\n const filePath = path.join(this.loomsDir, file)\n const content = await fs.readFile(filePath, 'utf8')\n const data: MetadataFile = JSON.parse(content)\n\n // Skip files without required description field\n if (!data.description) {\n continue\n }\n\n results.push(this.toMetadata(data))\n } catch (error) {\n // Skip individual files that fail to parse (graceful degradation)\n getLogger().debug(\n `Skipping metadata file ${file}: ${error instanceof Error ? error.message : String(error)}`\n )\n }\n }\n } catch (error) {\n // Log error but return empty array (graceful degradation)\n getLogger().debug(\n `Could not list metadata files: ${error instanceof Error ? error.message : String(error)}`\n )\n }\n\n return results\n }\n\n /**\n * Update existing metadata for a worktree by merging new fields\n *\n * Reads the existing metadata file, merges the provided updates,\n * and writes back. Only provided fields are overwritten.\n *\n * @param worktreePath - Absolute path to the worktree\n * @param updates - Partial metadata fields to merge\n */\n async updateMetadata(worktreePath: string, updates: Partial<MetadataFile>): Promise<void> {\n try {\n const filePath = this.getFilePath(worktreePath)\n\n // Check if file exists\n if (!(await fs.pathExists(filePath))) {\n getLogger().warn(`No metadata file to update for worktree: ${worktreePath}`)\n return\n }\n\n // Read existing data\n const content = await fs.readFile(filePath, 'utf8')\n const data: MetadataFile = JSON.parse(content)\n\n // Merge updates\n const merged = { ...data, ...updates }\n\n // Write back\n await fs.writeFile(filePath, JSON.stringify(merged, null, 2), { mode: 0o644 })\n\n getLogger().debug(`Metadata updated for worktree: ${worktreePath}`)\n } catch (error) {\n getLogger().warn(\n `Failed to update metadata for worktree: ${error instanceof Error ? error.message : String(error)}`\n )\n throw error\n }\n }\n\n /**\n * Delete metadata for a worktree (spec section 3.3)\n *\n * Idempotent: silently succeeds if file doesn't exist\n * Non-fatal: logs warning on permission errors but doesn't throw\n *\n * @param worktreePath - Absolute path to the worktree\n */\n async deleteMetadata(worktreePath: string): Promise<void> {\n try {\n const filePath = this.getFilePath(worktreePath)\n\n // Check if file exists - silently return if not\n if (!(await fs.pathExists(filePath))) {\n getLogger().debug(`No metadata file to delete for worktree: ${worktreePath}`)\n return\n }\n\n // Delete the file\n await fs.unlink(filePath)\n getLogger().debug(`Metadata deleted for worktree: ${worktreePath}`)\n } catch (error) {\n // Log warning on permission error but don't throw (per spec section 3.3)\n getLogger().warn(\n `Failed to delete metadata for worktree: ${error instanceof Error ? error.message : String(error)}`\n )\n }\n }\n\n /**\n * Archive metadata for a finished worktree\n *\n * Moves the metadata file to the finished/ subdirectory and adds\n * status: 'finished' and finishedAt timestamp fields.\n *\n * Idempotent: silently succeeds if source file doesn't exist\n * Non-fatal: logs warning on errors but doesn't throw\n *\n * @param worktreePath - Absolute path to the worktree\n */\n async archiveMetadata(worktreePath: string): Promise<void> {\n try {\n const filename = this.slugifyPath(worktreePath)\n const sourcePath = path.join(this.loomsDir, filename)\n\n // Check if source file exists - silently return if not (idempotent)\n if (!(await fs.pathExists(sourcePath))) {\n getLogger().debug(`No metadata file to archive for worktree: ${worktreePath}`)\n return\n }\n\n // Read existing metadata\n const content = await fs.readFile(sourcePath, 'utf8')\n const data: MetadataFile = JSON.parse(content)\n\n // Add finished status and timestamp\n const finishedData = {\n ...data,\n status: 'finished' as const,\n finishedAt: new Date().toISOString(),\n }\n\n // Ensure finished directory exists\n await fs.ensureDir(this.finishedDir, { mode: 0o755 })\n\n // Write to finished subdirectory\n const destPath = path.join(this.finishedDir, filename)\n await fs.writeFile(destPath, JSON.stringify(finishedData, null, 2), { mode: 0o644 })\n\n // Delete original file\n await fs.unlink(sourcePath)\n\n getLogger().debug(`Metadata archived for worktree: ${worktreePath}`)\n } catch (error) {\n // Log warning but don't throw - archiving is supplementary\n getLogger().warn(\n `Failed to archive metadata for worktree: ${error instanceof Error ? error.message : String(error)}`\n )\n }\n }\n\n /**\n * List all finished loom metadata files\n *\n * Returns an array of LoomMetadata objects for all finished looms\n * in the finished/ subdirectory, sorted by finishedAt in descending order\n * (most recently finished first).\n *\n * @returns Array of LoomMetadata objects from finished files, sorted by finishedAt desc\n */\n async listFinishedMetadata(): Promise<LoomMetadata[]> {\n const results: LoomMetadata[] = []\n\n try {\n // Check if finished directory exists\n if (!(await fs.pathExists(this.finishedDir))) {\n return results\n }\n\n // Read all files in finished directory\n const files = await fs.readdir(this.finishedDir)\n\n // Filter to only .json files and read each\n for (const file of files) {\n if (!file.endsWith('.json')) {\n continue\n }\n\n try {\n const filePath = path.join(this.finishedDir, file)\n const content = await fs.readFile(filePath, 'utf8')\n const data = JSON.parse(content) as MetadataFile & { status?: string; finishedAt?: string }\n\n // Skip files without required description field\n if (!data.description) {\n continue\n }\n\n const metadata = this.toMetadata(data)\n // Add finished-specific fields\n metadata.status = (data.status as 'active' | 'finished') ?? 'finished'\n metadata.finishedAt = data.finishedAt ?? null\n\n results.push(metadata)\n } catch (error) {\n // Skip individual files that fail to parse (graceful degradation)\n getLogger().warn(\n `Skipping finished metadata file ${file}: ${error instanceof Error ? error.message : String(error)}`\n )\n }\n }\n\n // Sort by finishedAt descending (most recently finished first)\n results.sort((a, b) => {\n const aTime = a.finishedAt ? new Date(a.finishedAt).getTime() : 0\n const bTime = b.finishedAt ? new Date(b.finishedAt).getTime() : 0\n return bTime - aTime\n })\n } catch (error) {\n // Log error but return empty array (graceful degradation)\n getLogger().warn(\n `Could not list finished metadata files: ${error instanceof Error ? error.message : String(error)}`\n )\n }\n\n return results\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/SettingsManager.ts"],"sourcesContent":["import { readFile } from 'fs/promises'\nimport path from 'path'\nimport os from 'os'\nimport { z } from 'zod'\nimport deepmerge from 'deepmerge'\nimport { logger } from '../utils/logger.js'\n\n// Merge mode: canonical values + legacy aliases accepted at parse time\nexport const mergeModeValues = ['local', 'pr', 'draft-pr', 'github-pr', 'github-draft-pr', 'bitbucket-pr'] as const\nexport type MergeMode = 'local' | 'pr' | 'draft-pr'\nconst mergeModeTransform = (val: string): MergeMode => {\n\tconst map: Record<string, MergeMode> = { 'github-pr': 'pr', 'github-draft-pr': 'draft-pr', 'bitbucket-pr': 'pr' }\n\treturn (map[val] ?? val) as MergeMode\n}\n\n/**\n * Zod schema for base agent settings (without nested agents)\n */\nexport const BaseAgentSettingsSchema = z.object({\n\tmodel: z\n\t\t.enum(['sonnet', 'opus', 'haiku'])\n\t\t.optional()\n\t\t.describe('Claude model shorthand: sonnet, opus, or haiku'),\n\tswarmModel: z\n\t\t.enum(['sonnet', 'opus', 'haiku'])\n\t\t.optional()\n\t\t.describe('Model to use for this agent in swarm mode. Overrides the base model when running inside swarm workers.'),\n\tenabled: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe('Whether this agent is enabled. Defaults to true.'),\n\tproviders: z\n\t\t.record(\n\t\t\tz.enum(['claude', 'gemini', 'codex']),\n\t\t\tz.string()\n\t\t)\n\t\t.optional()\n\t\t.describe('Map of review providers to model names. Keys: claude, gemini, codex. Values: model name strings (e.g., \"sonnet\", \"gemini-3-pro-preview\", \"gpt-5.2-codex\")'),\n\treview: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe('Whether artifacts from this agent should be reviewed before posting (defaults to false)'),\n\tswarmReview: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe('Whether artifacts from this agent should be reviewed in swarm mode. Defaults to false if not set (review is off in swarm mode for speed and cost unless explicitly enabled).'),\n})\n\n/**\n * Zod schema for agent settings.\n */\nexport const AgentSettingsSchema = BaseAgentSettingsSchema\n\n/**\n * Zod schema for spin agent settings with default model\n * Used for the spin orchestrator configuration\n */\nexport const SpinAgentSettingsSchema = z.object({\n\tmodel: z\n\t\t.enum(['sonnet', 'opus', 'haiku'])\n\t\t.default('opus')\n\t\t.describe('Claude model shorthand for spin orchestrator'),\n\tswarmModel: z\n\t\t.enum(['sonnet', 'opus', 'haiku'])\n\t\t.optional()\n\t\t.describe('Model for the spin orchestrator when running in swarm mode. Overrides spin.model for swarm workflows.'),\n\tpostSwarmReview: z\n\t\t.boolean()\n\t\t.default(true)\n\t\t.describe('Run a full code review after swarm completion, auto-fixing issues with confidence 80+. Defaults to true.'),\n})\n\n/**\n * Zod schema for plan command settings with default model\n * Used for the plan command configuration\n */\nexport const PlanCommandSettingsSchema = z.object({\n\tmodel: z\n\t\t.enum(['sonnet', 'opus', 'haiku'])\n\t\t.default('opus')\n\t\t.describe('Claude model shorthand for plan command'),\n\tplanner: z\n\t\t.enum(['claude', 'gemini', 'codex'])\n\t\t.default('claude')\n\t\t.describe('AI provider for creating the plan'),\n\treviewer: z\n\t\t.enum(['claude', 'gemini', 'codex', 'none'])\n\t\t.default('none')\n\t\t.describe('AI provider for reviewing the plan (none to skip review)'),\n\twaveVerification: z\n\t\t.boolean()\n\t\t.default(true)\n\t\t.describe('When enabled, the planner generates verification child issues between dependency waves to catch integration issues early.'),\n})\n\n/**\n * Zod schema for summary settings with default model\n * Used for session summary generation configuration\n */\nexport const SummarySettingsSchema = z.object({\n\tmodel: z\n\t\t.enum(['sonnet', 'opus', 'haiku'])\n\t\t.default('sonnet')\n\t\t.describe('Claude model shorthand for session summary generation'),\n})\n\n/**\n * Zod schema for workflow permission configuration\n */\nexport const WorkflowPermissionSchema = z.object({\n\tpermissionMode: z\n\t\t.enum(['plan', 'acceptEdits', 'bypassPermissions', 'default'])\n\t\t.optional()\n\t\t.describe('Permission mode for Claude CLI in this workflow type'),\n\tnoVerify: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe('Skip pre-commit hooks (--no-verify) when committing during commit and finish workflows'),\n\tstartIde: z\n\t\t.boolean()\n\t\t.default(true)\n\t\t.describe('Launch IDE (code) when starting this workflow type'),\n\tstartDevServer: z\n\t\t.boolean()\n\t\t.default(true)\n\t\t.describe('Launch development server when starting this workflow type'),\n\tstartAiAgent: z\n\t\t.boolean()\n\t\t.default(true)\n\t\t.describe('Launch Claude Code agent when starting this workflow type'),\n\tstartTerminal: z\n\t\t.boolean()\n\t\t.default(false)\n\t\t.describe('Launch terminal window without dev server when starting this workflow type'),\n\tgenerateSummary: z\n\t\t.boolean()\n\t\t.default(true)\n\t\t.describe('Generate and post Claude session summary when finishing this workflow type'),\n})\n\n/**\n * Non-defaulting variant for pre-merge validation\n * This prevents Zod from polluting partial settings with default values before merge\n */\nexport const WorkflowPermissionSchemaNoDefaults = z.object({\n\tpermissionMode: z\n\t\t.enum(['plan', 'acceptEdits', 'bypassPermissions', 'default'])\n\t\t.optional()\n\t\t.describe('Permission mode for Claude CLI in this workflow type'),\n\tnoVerify: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe('Skip pre-commit hooks (--no-verify) when committing during commit and finish workflows'),\n\tstartIde: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe('Launch IDE (code) when starting this workflow type'),\n\tstartDevServer: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe('Launch development server when starting this workflow type'),\n\tstartAiAgent: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe('Launch Claude Code agent when starting this workflow type'),\n\tstartTerminal: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe('Launch terminal window without dev server when starting this workflow type'),\n\tgenerateSummary: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe('Generate and post Claude session summary when finishing this workflow type'),\n})\n\n/**\n * Zod schema for workflows settings\n */\nexport const WorkflowsSettingsSchema = z\n\t.object({\n\t\tissue: WorkflowPermissionSchema.optional(),\n\t\tpr: WorkflowPermissionSchema.optional(),\n\t\tregular: WorkflowPermissionSchema.optional(),\n\t})\n\t.optional()\n\n/**\n * Non-defaulting variant for pre-merge validation\n */\nexport const WorkflowsSettingsSchemaNoDefaults = z\n\t.object({\n\t\tissue: WorkflowPermissionSchemaNoDefaults.optional(),\n\t\tpr: WorkflowPermissionSchemaNoDefaults.optional(),\n\t\tregular: WorkflowPermissionSchemaNoDefaults.optional(),\n\t})\n\t.optional()\n\n/**\n * Zod schema for capabilities settings\n */\nexport const CapabilitiesSettingsSchema = z\n\t.object({\n\t\tweb: z\n\t\t\t.object({\n\t\t\t\tbasePort: z\n\t\t\t\t\t.number()\n\t\t\t\t\t.min(1, 'Base port must be >= 1')\n\t\t\t\t\t.max(65535, 'Base port must be <= 65535')\n\t\t\t\t\t.optional()\n\t\t\t\t\t.describe('Base port for web workspace port calculations (default: 3000)'),\n\t\t\t\tdevServer: z\n\t\t\t\t\t.enum(['process', 'docker'])\n\t\t\t\t\t.default('process')\n\t\t\t\t\t.describe('Dev server mode: \"process\" runs natively, \"docker\" runs inside a Docker container with port mapping'),\n\t\t\t\tdockerFile: z\n\t\t\t\t\t.string()\n\t\t\t\t\t.default('./Dockerfile')\n\t\t\t\t\t.describe('Path to Dockerfile relative to worktree root (only used when devServer is \"docker\")'),\n\t\t\t\tcontainerPort: z\n\t\t\t\t\t.number()\n\t\t\t\t\t.min(1, 'Container port must be >= 1')\n\t\t\t\t\t.max(65535, 'Container port must be <= 65535')\n\t\t\t\t\t.optional()\n\t\t\t\t\t.describe('Port the app runs on inside the Docker container (auto-detected from EXPOSE directive if not set)'),\n\t\t\t\tdockerBuildArgs: z\n\t\t\t\t\t.record(z.string())\n\t\t\t\t\t.optional()\n\t\t\t\t\t.describe('Build arguments to pass to docker build (e.g., {\"NODE_ENV\": \"development\"})'),\n\t\t\t\tdockerRunArgs: z\n\t\t\t\t\t.array(z.string())\n\t\t\t\t\t.optional()\n\t\t\t\t\t.describe('Additional arguments for docker run (e.g., [\"-v\", \"./src:/app/src\"] for volume mounts)'),\n\t\t\t})\n\t\t\t.optional()\n\t\t\t.describe('Web dev server settings. To declare a project as a web project, add \"web\" to the capabilities array in .iloom/package.iloom.json or .iloom/package.iloom.local.json.'),\n\t\tdatabase: z\n\t\t\t.object({\n\t\t\t\tdatabaseUrlEnvVarName: z\n\t\t\t\t\t.string()\n\t\t\t\t\t.min(1, 'Database URL variable name cannot be empty')\n\t\t\t\t\t.regex(/^[A-Z_][A-Z0-9_]*$/, 'Must be valid env var name (uppercase, underscores)')\n\t\t\t\t\t.optional()\n\t\t\t\t\t.default('DATABASE_URL')\n\t\t\t\t\t.describe('Name of environment variable for database connection URL'),\n\t\t\t})\n\t\t\t.optional(),\n\t})\n\t.optional()\n\n/**\n * Non-defaulting variant for pre-merge validation\n */\nexport const CapabilitiesSettingsSchemaNoDefaults = z\n\t.object({\n\t\tweb: z\n\t\t\t.object({\n\t\t\t\tbasePort: z\n\t\t\t\t\t.number()\n\t\t\t\t\t.min(1, 'Base port must be >= 1')\n\t\t\t\t\t.max(65535, 'Base port must be <= 65535')\n\t\t\t\t\t.optional()\n\t\t\t\t\t.describe('Base port for web workspace port calculations (default: 3000)'),\n\t\t\t\tdevServer: z\n\t\t\t\t\t.enum(['process', 'docker'])\n\t\t\t\t\t.optional()\n\t\t\t\t\t.describe('Dev server mode: \"process\" runs natively, \"docker\" runs inside a Docker container with port mapping'),\n\t\t\t\tdockerFile: z\n\t\t\t\t\t.string()\n\t\t\t\t\t.optional()\n\t\t\t\t\t.describe('Path to Dockerfile relative to worktree root (only used when devServer is \"docker\")'),\n\t\t\t\tcontainerPort: z\n\t\t\t\t\t.number()\n\t\t\t\t\t.min(1, 'Container port must be >= 1')\n\t\t\t\t\t.max(65535, 'Container port must be <= 65535')\n\t\t\t\t\t.optional()\n\t\t\t\t\t.describe('Port the app runs on inside the Docker container (auto-detected from EXPOSE directive if not set)'),\n\t\t\t\tdockerBuildArgs: z\n\t\t\t\t\t.record(z.string())\n\t\t\t\t\t.optional()\n\t\t\t\t\t.describe('Build arguments to pass to docker build (e.g., {\"NODE_ENV\": \"development\"})'),\n\t\t\t\tdockerRunArgs: z\n\t\t\t\t\t.array(z.string())\n\t\t\t\t\t.optional()\n\t\t\t\t\t.describe('Additional arguments for docker run (e.g., [\"-v\", \"./src:/app/src\"] for volume mounts)'),\n\t\t\t})\n\t\t\t.optional()\n\t\t\t.describe('Web dev server settings. To declare a project as a web project, add \"web\" to the capabilities array in .iloom/package.iloom.json or .iloom/package.iloom.local.json.'),\n\t\tdatabase: z\n\t\t\t.object({\n\t\t\t\tdatabaseUrlEnvVarName: z\n\t\t\t\t\t.string()\n\t\t\t\t\t.min(1, 'Database URL variable name cannot be empty')\n\t\t\t\t\t.regex(/^[A-Z_][A-Z0-9_]*$/, 'Must be valid env var name (uppercase, underscores)')\n\t\t\t\t\t.optional()\n\t\t\t\t\t.describe('Name of environment variable for database connection URL'),\n\t\t\t})\n\t\t\t.optional(),\n\t})\n\t.optional()\n\n/**\n * Zod schema for Docker dev server settings\n */\nexport const DevServerSettingsSchema = z.object({\n\tmode: z\n\t\t.enum(['docker'])\n\t\t.default('docker')\n\t\t.describe('Dev server mode. Currently only \"docker\" is supported.'),\n\tdocker: z\n\t\t.object({\n\t\t\tdockerFile: z\n\t\t\t\t.string()\n\t\t\t\t.default('./Dockerfile')\n\t\t\t\t.refine(\n\t\t\t\t\t(val) => {\n\t\t\t\t\t\t// Must be a relative path (no leading slash)\n\t\t\t\t\t\tif (path.isAbsolute(val)) return false\n\t\t\t\t\t\t// Must not traverse outside the project root\n\t\t\t\t\t\tconst normalized = path.normalize(val)\n\t\t\t\t\t\tif (normalized.startsWith('..')) return false\n\t\t\t\t\t\treturn true\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t'dockerFile must be a relative path that does not traverse outside the project root (no leading \"/\" and no \"../\" escaping)',\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t\t.describe('Path to Dockerfile relative to worktree root'),\n\t\t\tcontainerPort: z\n\t\t\t\t.number()\n\t\t\t\t.min(1, 'Container port must be >= 1')\n\t\t\t\t.max(65535, 'Container port must be <= 65535')\n\t\t\t\t.optional()\n\t\t\t\t.describe('Port the app runs on inside the Docker container (auto-detected from EXPOSE directive if not set)'),\n\t\t\tbuildArgs: z\n\t\t\t\t.record(z.string(), z.string())\n\t\t\t\t.optional()\n\t\t\t\t.describe('Build arguments to pass to docker build'),\n\t\t\trunArgs: z\n\t\t\t\t.array(z.string())\n\t\t\t\t.optional()\n\t\t\t\t.describe('Additional arguments for docker run'),\n\t\t})\n\t\t.optional(),\n})\n\n/**\n * Non-defaulting variant of DevServerSettingsSchema for pre-merge validation\n * This prevents Zod from polluting partial settings with default values before merge\n */\nexport const DevServerSettingsSchemaNoDefaults = z.object({\n\tmode: z\n\t\t.enum(['docker'])\n\t\t.optional()\n\t\t.describe('Dev server mode. Currently only \"docker\" is supported.'),\n\tdocker: z\n\t\t.object({\n\t\t\tdockerFile: z\n\t\t\t\t.string()\n\t\t\t\t.optional()\n\t\t\t\t.refine(\n\t\t\t\t\t(val) => {\n\t\t\t\t\t\tif (val === undefined) return true\n\t\t\t\t\t\tif (path.isAbsolute(val)) return false\n\t\t\t\t\t\tconst normalized = path.normalize(val)\n\t\t\t\t\t\tif (normalized.startsWith('..')) return false\n\t\t\t\t\t\treturn true\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t'dockerFile must be a relative path that does not traverse outside the project root (no leading \"/\" and no \"../\" escaping)',\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t\t.describe('Path to Dockerfile relative to worktree root'),\n\t\t\tcontainerPort: z\n\t\t\t\t.number()\n\t\t\t\t.min(1, 'Container port must be >= 1')\n\t\t\t\t.max(65535, 'Container port must be <= 65535')\n\t\t\t\t.optional()\n\t\t\t\t.describe('Port the app runs on inside the Docker container (auto-detected from EXPOSE directive if not set)'),\n\t\t\tbuildArgs: z\n\t\t\t\t.record(z.string(), z.string())\n\t\t\t\t.optional()\n\t\t\t\t.describe('Build arguments to pass to docker build'),\n\t\t\trunArgs: z\n\t\t\t\t.array(z.string())\n\t\t\t\t.optional()\n\t\t\t\t.describe('Additional arguments for docker run'),\n\t\t})\n\t\t.optional(),\n})\n\n/**\n * Zod schema for Neon database provider settings\n */\nexport const NeonSettingsSchema = z.object({\n\tprojectId: z\n\t\t.string()\n\t\t.min(1)\n\t\t.regex(/^[a-zA-Z0-9-]+$/, 'Neon project ID must contain only letters, numbers, and hyphens')\n\t\t.describe('Neon project ID found in your project URL (e.g., \"fantastic-fox-3566354\")'),\n\tparentBranch: z\n\t\t.string()\n\t\t.min(1)\n\t\t.describe('Branch from which new database branches are created'),\n})\n\n/**\n * Zod schema for database provider settings\n */\nexport const DatabaseProvidersSettingsSchema = z\n\t.object({\n\t\tneon: NeonSettingsSchema.optional().describe(\n\t\t\t'Neon database configuration. Requires Neon CLI installed and authenticated for database branching.',\n\t\t),\n\t})\n\t.optional()\n\n/**\n * Zod schema for iloom settings\n */\nexport const IloomSettingsSchema = z.object({\n\tmainBranch: z\n\t\t.string()\n\t\t.min(1, \"Settings 'mainBranch' cannot be empty\")\n\t\t.optional()\n\t\t.describe('Name of the main/primary branch for the repository'),\n\tsourceEnvOnStart: z\n\t\t.boolean()\n\t\t.default(false)\n\t\t.describe(\n\t\t\t'Source all dotenv-flow files (.env, .env.local, .env.development, .env.development.local) when launching terminal processes (Claude, dev server, terminal). ' +\n\t\t\t\t'Files are sourced in precedence order so later files override earlier ones. ' +\n\t\t\t\t'NOTE TO AI AGENTS: Before making changes to settings files, YOU MUST OFFER to check .env file compatibility. ' +\n\t\t\t\t'WARNING: Unquoted values in .env files may break sourcing in bash/zsh shells. ' +\n\t\t\t\t'Before enabling, verify ALL your .env.* files do not contain unquoted special characters ' +\n\t\t\t\t'(e.g., database URLs with ?, &, or other shell metacharacters). ' +\n\t\t\t\t'Shell compatibility issues may cause processes to fail or behave unexpectedly.',\n\t\t),\n\tworktreePrefix: z\n\t\t.string()\n\t\t.optional()\n\t\t.refine(\n\t\t\t(val) => {\n\t\t\t\tif (val === undefined) return true // undefined = use default calculation\n\t\t\t\tif (val === '') return true // empty string = no prefix mode\n\n\t\t\t\t// Allowlist: only alphanumeric, hyphens, underscores, and forward slashes\n\t\t\t\tconst allowedChars = /^[a-zA-Z0-9\\-_/]+$/\n\t\t\t\tif (!allowedChars.test(val)) return false\n\n\t\t\t\t// Reject if only special characters (no alphanumeric content)\n\t\t\t\tif (/^[-_/]+$/.test(val)) return false\n\n\t\t\t\t// Check each segment (split by /) contains at least one alphanumeric character\n\t\t\t\tconst segments = val.split('/')\n\t\t\t\tfor (const segment of segments) {\n\t\t\t\t\tif (segment && /^[-_]+$/.test(segment)) {\n\t\t\t\t\t\t// Segment exists but contains only hyphens/underscores\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn true\n\t\t\t},\n\t\t\t{\n\t\t\t\tmessage:\n\t\t\t\t\t\"worktreePrefix contains invalid characters. Only alphanumeric characters, hyphens (-), underscores (_), and forward slashes (/) are allowed. Use forward slashes for nested directories.\",\n\t\t\t},\n\t\t)\n\t\t.describe(\n\t\t\t'Prefix for worktree directories. Empty string disables prefix. Defaults to <repo-name>-looms if not set.',\n\t\t),\n\tprotectedBranches: z\n\t\t.array(z.string().min(1, 'Protected branch name cannot be empty'))\n\t\t.optional()\n\t\t.describe('List of branches that cannot be deleted (defaults to [mainBranch, \"main\", \"master\", \"develop\"])'),\n\tcopyGitIgnoredPatterns: z\n\t\t.array(z.string().min(1, 'Pattern cannot be empty'))\n\t\t.optional()\n\t\t.describe('Glob patterns for gitignored files to copy to looms (e.g., [\"*.db\", \"data/*.sqlite\"]). Great for local dbs and large test data files that are too big to commit to git. Note: .env (dotenv-flow) files, iloom\\'s and claude\\'s local settings are automatically copied and do not need to be specified here.'),\n\tworkflows: WorkflowsSettingsSchema.describe('Per-workflow-type permission configurations'),\n\tagents: z\n\t\t.record(z.string(), AgentSettingsSchema)\n\t\t.optional()\n\t\t.nullable()\n\t\t.describe(\n\t\t\t'Per-agent configuration overrides. Available agents: ' +\n\t\t\t\t'iloom-issue-analyzer (analyzes issues), ' +\n\t\t\t\t'iloom-issue-planner (creates implementation plans), ' +\n\t\t\t\t'iloom-issue-analyze-and-plan (combined analysis and planning), ' +\n\t\t\t\t'iloom-issue-complexity-evaluator (evaluates complexity), ' +\n\t\t\t\t'iloom-issue-enhancer (enhances issue descriptions), ' +\n\t\t\t\t'iloom-issue-implementer (implements code changes), ' +\n\t\t\t\t'iloom-code-reviewer (reviews code changes against requirements), ' +\n\t\t\t\t'iloom-artifact-reviewer (reviews artifacts before posting), ' +\n\t\t\t\t'iloom-swarm-worker (swarm worker agent, dynamically generated). ' +\n\t\t\t\t'Use swarmModel on any agent to override its model in swarm mode.',\n\t\t),\n\tspin: SpinAgentSettingsSchema.optional().describe(\n\t\t'Spin orchestrator configuration. Model defaults to opus when not configured.',\n\t),\n\tplan: PlanCommandSettingsSchema.optional().describe(\n\t\t'Plan command configuration. Model defaults to opus, planner to claude, reviewer to none when not configured.',\n\t),\n\tsummary: SummarySettingsSchema.optional().describe(\n\t\t'Session summary generation configuration. Model defaults to sonnet when not configured.',\n\t),\n\tcapabilities: CapabilitiesSettingsSchema.describe('Project capability configurations'),\n\tdevServer: DevServerSettingsSchema.optional().describe('Docker-based dev server configuration'),\n\tdatabaseProviders: DatabaseProvidersSettingsSchema.describe('Database provider configurations'),\n\tissueManagement: z\n\t\t.object({\n\t\t\t// SYNC: If this default changes, update displayDefaultsBox() in src/utils/first-run-setup.ts\n\t\t\tprovider: z.enum(['github', 'linear', 'jira']).optional().default('github').describe('Issue tracker provider (github, linear, jira)'),\n\t\t\tgithub: z\n\t\t\t\t.object({\n\t\t\t\t\tremote: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.min(1, 'Remote name cannot be empty')\n\t\t\t\t\t\t.describe('Git remote name to use for GitHub operations'),\n\t\t\t\t})\n\t\t\t\t.optional(),\n\t\t\tlinear: z\n\t\t\t\t.object({\n\t\t\t\t\tteamId: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.min(1, 'Team ID cannot be empty')\n\t\t\t\t\t\t.describe('Linear team identifier (e.g., \"ENG\", \"PLAT\")'),\n\t\t\t\t\tbranchFormat: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.describe('Branch naming template for Linear issues'),\n\t\t\t\t\tapiToken: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.describe('Linear API token (lin_api_...). SECURITY: Store in settings.local.json only, never commit to source control.'),\n\t\t\t\t})\n\t\t\t\t.optional(),\n\t\t\tjira: z\n\t\t\t\t.object({\n\t\t\t\t\thost: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.min(1, 'Jira host cannot be empty')\n\t\t\t\t\t\t.describe('Jira instance URL (e.g., \"https://yourcompany.atlassian.net\")'),\n\t\t\t\t\tusername: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.min(1, 'Jira username/email cannot be empty')\n\t\t\t\t\t\t.describe('Jira username or email address'),\n\t\t\t\t\tapiToken: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.describe('Jira API token. SECURITY: Store in settings.local.json only, never commit to source control. Generate at: https://id.atlassian.com/manage-profile/security/api-tokens'),\n\t\t\t\t\tprojectKey: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.min(1, 'Project key cannot be empty')\n\t\t\t\t\t\t.describe('Jira project key (e.g., \"PROJ\", \"ENG\")'),\n\t\t\t\t\tboardId: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.describe('Jira board ID for sprint/workflow operations (optional)'),\n\t\t\t\t\ttransitionMappings: z\n\t\t\t\t\t\t.record(z.string(), z.string())\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.describe('Map iloom states to Jira transition names (e.g., {\"In Review\": \"Start Review\"})'),\n\t\t\t\t\tdefaultIssueType: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.min(1)\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.default('Task')\n\t\t\t\t\t\t.describe('Default Jira issue type name for creating issues (e.g., \"Task\", \"Story\", \"Bug\")'),\n\t\t\t\t\tdefaultSubtaskType: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.min(1)\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.default('Subtask')\n\t\t\t\t\t\t.describe('Default Jira issue type name for creating subtasks/child issues (e.g., \"Subtask\", \"Sub-task\")'),\n\t\t\t\t\tdoneStatuses: z\n\t\t\t\t\t\t.array(z.string())\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.default(['Done'])\n\t\t\t\t\t\t.describe('Status names to exclude from issue lists (e.g., [\"Done\", \"Closed\", \"Verify\"])'),\n\t\t\t\t})\n\t\t\t\t.optional(),\n\t\t})\n\t\t.optional()\n\t\t.describe('Issue management configuration'),\n\tversionControl: z\n\t\t.object({\n\t\t\tprovider: z.enum(['github', 'bitbucket']).optional().default('github').describe('Version control provider (github, bitbucket)'),\n\t\t\tbitbucket: z\n\t\t\t\t.object({\n\t\t\t\t\tusername: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.min(1, 'BitBucket username cannot be empty')\n\t\t\t\t\t\t.describe('BitBucket username'),\n\t\t\t\t\tapiToken: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.describe('BitBucket API token. SECURITY: Store in settings.local.json only, never commit to source control. Generate at: https://bitbucket.org/account/settings/app-passwords/ (Note: App passwords deprecated Sep 2025, use API tokens)'),\n\t\t\t\t\tworkspace: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.describe('BitBucket workspace (optional, auto-detected from git remote if not provided)'),\n\t\t\t\t\trepoSlug: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.describe('BitBucket repository slug (optional, auto-detected from git remote if not provided)'),\n\t\t\t\treviewers: z\n\t\t\t\t\t.array(z.string().describe('Reviewer username'))\n\t\t\t\t\t.optional()\n\t\t\t\t\t.describe('List of usernames to add as PR reviewers. Usernames are resolved to Bitbucket account IDs at PR creation time.'),\n\t\t\t\t})\n\t\t\t\t.optional(),\n\t\t})\n\t\t.optional()\n\t\t.describe('Version control provider configuration'),\n\tmergeBehavior: z\n\t\t.object({\n\t\t\t// SYNC: If this default changes, update displayDefaultsBox() in src/utils/first-run-setup.ts\n\t\t\tmode: z.enum(mergeModeValues).default('local').transform(mergeModeTransform),\n\t\t\tremote: z.string().optional(),\n\t\t\tautoCommitPush: z\n\t\t\t\t.boolean()\n\t\t\t\t.optional()\n\t\t\t\t.describe(\n\t\t\t\t\t'Auto-commit and push after code review in draft PR mode. Defaults to true when mode is draft-pr.'\n\t\t\t\t),\n\t\t\tprTitlePrefix: z.boolean().default(false).optional().describe('Prefix PR titles with the issue number (e.g., \"QLH-123: Title\"). Default: false'),\n\t\t\topenBrowserOnFinish: z\n\t\t\t\t.boolean()\n\t\t\t\t.default(true)\n\t\t\t\t.describe(\n\t\t\t\t\t'Open the PR in the default browser after finishing in pr or draft-pr mode. Use --no-browser flag to override.'\n\t\t\t\t),\n\t\t})\n\t\t.optional()\n\t\t.describe('Merge behavior configuration: local (merge locally), pr (create PR), or draft-pr (create draft PR at start, mark ready on finish)'),\n\tide: z\n\t\t.object({\n\t\t\t// SYNC: If this default changes, update displayDefaultsBox() in src/utils/first-run-setup.ts\n\t\t\ttype: z\n\t\t\t\t.enum(['vscode', 'cursor', 'webstorm', 'sublime', 'intellij', 'windsurf', 'antigravity'])\n\t\t\t\t.default('vscode')\n\t\t\t\t.describe(\n\t\t\t\t\t'IDE to launch when starting a loom. Options: vscode (Visual Studio Code), cursor (Cursor AI editor), ' +\n\t\t\t\t\t\t'webstorm (JetBrains WebStorm), sublime (Sublime Text), intellij (JetBrains IntelliJ IDEA), ' +\n\t\t\t\t\t\t'windsurf (Windsurf editor), antigravity (Antigravity IDE).'\n\t\t\t\t),\n\t\t})\n\t\t.optional()\n\t\t.describe(\n\t\t\t'IDE configuration for workspace launches. Controls which editor opens when you start a loom. ' +\n\t\t\t\t'Supports VSCode, Cursor, WebStorm, Sublime Text, IntelliJ, Windsurf, and Antigravity. ' +\n\t\t\t\t'Note: Color synchronization (title bar colors) only works with VSCode-compatible editors (vscode, cursor, windsurf, antigravity).'\n\t\t),\n\tcolors: z\n\t\t.object({\n\t\t\tterminal: z\n\t\t\t\t.boolean()\n\t\t\t\t.default(true)\n\t\t\t\t.describe('Apply terminal background colors based on branch name (macOS only)'),\n\t\t\tvscode: z\n\t\t\t\t.boolean()\n\t\t\t\t.default(false)\n\t\t\t\t.describe(\n\t\t\t\t\t'Apply VSCode/Cursor title bar colors based on branch name. ' +\n\t\t\t\t\t\t'Note: This modifies .vscode/settings.json which may be in source control. ' +\n\t\t\t\t\t\t'Default is false for safety; enable via init or explicitly if .vscode is gitignored.'\n\t\t\t\t),\n\t\t})\n\t\t.optional()\n\t\t.describe('Color synchronization settings for workspace identification'),\n\tattribution: z\n\t\t.enum(['off', 'upstreamOnly', 'on'])\n\t\t.default('upstreamOnly')\n\t\t.describe(\n\t\t\t'Controls when iloom attribution appears in session summaries. ' +\n\t\t\t\t'\"off\" - never show attribution. ' +\n\t\t\t\t'\"upstreamOnly\" - only show for contributions to external repositories (e.g., open source). ' +\n\t\t\t\t'\"on\" - always show attribution.'\n\t\t),\n\tgit: z\n\t\t.object({\n\t\t\tcommitTimeout: z\n\t\t\t\t.number()\n\t\t\t\t.min(1000, 'Commit timeout must be at least 1000ms')\n\t\t\t\t.max(600000, 'Commit timeout cannot exceed 600000ms (10 minutes)')\n\t\t\t\t.default(60000)\n\t\t\t\t.describe('Timeout in milliseconds for git commit operations. Increase for long-running pre-commit hooks.'),\n\t\t})\n\t\t.default({ }) // ensures the object always exists and uses default for the inner properties\n\t\t.describe('Git operation settings'),\n})\n\n/**\n * Non-defaulting variant for pre-merge validation\n * This prevents Zod from polluting partial settings with default values before merge\n */\nexport const IloomSettingsSchemaNoDefaults = z.object({\n\tmainBranch: z\n\t\t.string()\n\t\t.min(1, \"Settings 'mainBranch' cannot be empty\")\n\t\t.optional()\n\t\t.describe('Name of the main/primary branch for the repository'),\n\tsourceEnvOnStart: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe(\n\t\t\t'Source all dotenv-flow files (.env, .env.local, .env.development, .env.development.local) when launching terminal processes (Claude, dev server, terminal). ' +\n\t\t\t\t'Files are sourced in precedence order so later files override earlier ones. ' +\n\t\t\t\t'NOTE TO AI AGENTS: Before making changes to settings files, YOU MUST OFFER to check .env compatibility. ' +\n\t\t\t\t'WARNING: Unquoted values in .env files may break sourcing in bash/zsh shells. ' +\n\t\t\t\t'Before enabling, verify ALL your .env.* files do not contain unquoted special characters ' +\n\t\t\t\t'(e.g., database URLs with ?, &, or other shell metacharacters). ' +\n\t\t\t\t'Shell compatibility issues may cause processes to fail or behave unexpectedly.',\n\t\t),\n\tworktreePrefix: z\n\t\t.string()\n\t\t.optional()\n\t\t.refine(\n\t\t\t(val) => {\n\t\t\t\tif (val === undefined) return true // undefined = use default calculation\n\t\t\t\tif (val === '') return true // empty string = no prefix mode\n\n\t\t\t\t// Allowlist: only alphanumeric, hyphens, underscores, and forward slashes\n\t\t\t\tconst allowedChars = /^[a-zA-Z0-9\\-_/]+$/\n\t\t\t\tif (!allowedChars.test(val)) return false\n\n\t\t\t\t// Reject if only special characters (no alphanumeric content)\n\t\t\t\tif (/^[-_/]+$/.test(val)) return false\n\n\t\t\t\t// Check each segment (split by /) contains at least one alphanumeric character\n\t\t\t\tconst segments = val.split('/')\n\t\t\t\tfor (const segment of segments) {\n\t\t\t\t\tif (segment && /^[-_]+$/.test(segment)) {\n\t\t\t\t\t\t// Segment exists but contains only hyphens/underscores\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn true\n\t\t\t},\n\t\t\t{\n\t\t\t\tmessage:\n\t\t\t\t\t\"worktreePrefix contains invalid characters. Only alphanumeric characters, hyphens (-), underscores (_), and forward slashes (/) are allowed. Use forward slashes for nested directories.\",\n\t\t\t},\n\t\t)\n\t\t.describe(\n\t\t\t'Prefix for worktree directories. Empty string disables prefix. Defaults to <repo-name>-looms if not set.',\n\t\t),\n\tprotectedBranches: z\n\t\t.array(z.string().min(1, 'Protected branch name cannot be empty'))\n\t\t.optional()\n\t\t.describe('List of branches that cannot be deleted (defaults to [mainBranch, \"main\", \"master\", \"develop\"])'),\n\tcopyGitIgnoredPatterns: z\n\t\t.array(z.string().min(1, 'Pattern cannot be empty'))\n\t\t.optional()\n\t\t.describe('Glob patterns for gitignored files to copy to looms (e.g., [\"*.db\", \"data/*.sqlite\"]). Great for local dbs and large test data files that are too big to commit to git. Note: .env (dotenv-flow) files, iloom\\'s and claude\\'s local settings are automatically copied and do not need to be specified here.'),\n\tworkflows: WorkflowsSettingsSchemaNoDefaults.describe('Per-workflow-type permission configurations'),\n\tagents: z\n\t\t.record(z.string(), AgentSettingsSchema)\n\t\t.optional()\n\t\t.nullable()\n\t\t.describe(\n\t\t\t'Per-agent configuration overrides. Available agents: ' +\n\t\t\t\t'iloom-issue-analyzer (analyzes issues), ' +\n\t\t\t\t'iloom-issue-planner (creates implementation plans), ' +\n\t\t\t\t'iloom-issue-analyze-and-plan (combined analysis and planning), ' +\n\t\t\t\t'iloom-issue-complexity-evaluator (evaluates complexity), ' +\n\t\t\t\t'iloom-issue-enhancer (enhances issue descriptions), ' +\n\t\t\t\t'iloom-issue-implementer (implements code changes), ' +\n\t\t\t\t'iloom-code-reviewer (reviews code changes against requirements), ' +\n\t\t\t\t'iloom-artifact-reviewer (reviews artifacts before posting), ' +\n\t\t\t\t'iloom-swarm-worker (swarm worker agent, dynamically generated). ' +\n\t\t\t\t'Use swarmModel on any agent to override its model in swarm mode.',\n\t\t),\n\tspin: z\n\t\t.object({\n\t\t\tmodel: z.enum(['sonnet', 'opus', 'haiku']).optional(),\n\t\t\tswarmModel: z.enum(['sonnet', 'opus', 'haiku']).optional(),\n\t\t\tpostSwarmReview: z.boolean().optional(),\n\t\t})\n\t\t.optional()\n\t\t.describe('Spin orchestrator configuration'),\n\tplan: z\n\t\t.object({\n\t\t\tmodel: z.enum(['sonnet', 'opus', 'haiku']).optional(),\n\t\t\tplanner: z.enum(['claude', 'gemini', 'codex']).optional(),\n\t\t\treviewer: z.enum(['claude', 'gemini', 'codex', 'none']).optional(),\n\t\t\twaveVerification: z.boolean().optional(),\n\t\t})\n\t\t.optional()\n\t\t.describe('Plan command configuration'),\n\tsummary: z\n\t\t.object({\n\t\t\tmodel: z.enum(['sonnet', 'opus', 'haiku']).optional(),\n\t\t})\n\t\t.optional()\n\t\t.describe('Session summary generation configuration'),\n\tcapabilities: CapabilitiesSettingsSchemaNoDefaults.describe('Project capability configurations'),\n\tdevServer: DevServerSettingsSchemaNoDefaults.optional().describe('Docker-based dev server configuration'),\n\tdatabaseProviders: DatabaseProvidersSettingsSchema.describe('Database provider configurations'),\n\tissueManagement: z\n\t\t.object({\n\t\t\tprovider: z.enum(['github', 'linear', 'jira']).optional().describe('Issue tracker provider (github, linear, jira)'),\n\t\t\tgithub: z\n\t\t\t\t.object({\n\t\t\t\t\tremote: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.min(1, 'Remote name cannot be empty')\n\t\t\t\t\t\t.describe('Git remote name to use for GitHub operations'),\n\t\t\t\t})\n\t\t\t\t.optional(),\n\t\t\tlinear: z\n\t\t\t\t.object({\n\t\t\t\t\tteamId: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.min(1, 'Team ID cannot be empty')\n\t\t\t\t\t\t.describe('Linear team identifier (e.g., \"ENG\", \"PLAT\")'),\n\t\t\t\t\tbranchFormat: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.describe('Branch naming template for Linear issues'),\n\t\t\t\t\tapiToken: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.describe('Linear API token (lin_api_...). SECURITY: Store in settings.local.json only, never commit to source control.'),\n\t\t\t\t})\n\t\t\t\t.optional(),\n\t\t\tjira: z\n\t\t\t\t.object({\n\t\t\t\t\thost: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.min(1, 'Jira host cannot be empty')\n\t\t\t\t\t\t.describe('Jira instance URL (e.g., \"https://yourcompany.atlassian.net\")'),\n\t\t\t\t\tusername: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.min(1, 'Jira username/email cannot be empty')\n\t\t\t\t\t\t.describe('Jira username or email address'),\n\t\t\t\t\tapiToken: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.describe('Jira API token. SECURITY: Store in settings.local.json only, never commit to source control. Generate at: https://id.atlassian.com/manage-profile/security/api-tokens'),\n\t\t\t\t\tprojectKey: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.min(1, 'Project key cannot be empty')\n\t\t\t\t\t\t.describe('Jira project key (e.g., \"PROJ\", \"ENG\")'),\n\t\t\t\t\tboardId: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.describe('Jira board ID for sprint/workflow operations (optional)'),\n\t\t\t\t\ttransitionMappings: z\n\t\t\t\t\t\t.record(z.string(), z.string())\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.describe('Map iloom states to Jira transition names (e.g., {\"In Review\": \"Start Review\"})'),\n\t\t\t\t\tdefaultIssueType: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.min(1)\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.describe('Default Jira issue type name for creating issues (e.g., \"Task\", \"Story\", \"Bug\")'),\n\t\t\t\t\tdefaultSubtaskType: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.min(1)\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.describe('Default Jira issue type name for creating subtasks/child issues (e.g., \"Subtask\", \"Sub-task\")'),\n\t\t\t\t\tdoneStatuses: z\n\t\t\t\t\t\t.array(z.string())\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.default(['Done'])\n\t\t\t\t\t\t.describe('Status names to exclude from issue lists (e.g., [\"Done\", \"Closed\", \"Verify\"])'),\n\t\t\t\t})\n\t\t\t\t.optional(),\n\t\t})\n\t\t.optional()\n\t\t.describe('Issue management configuration'),\n\tversionControl: z\n\t\t.object({\n\t\t\tprovider: z.enum(['github', 'bitbucket']).optional().describe('Version control provider (github, bitbucket)'),\n\t\t\tbitbucket: z\n\t\t\t\t.object({\n\t\t\t\t\tusername: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.min(1, 'BitBucket username cannot be empty')\n\t\t\t\t\t\t.describe('BitBucket username'),\n\t\t\t\t\tapiToken: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.describe('BitBucket API token. SECURITY: Store in settings.local.json only, never commit to source control. Generate at: https://bitbucket.org/account/settings/app-passwords/ (Note: App passwords deprecated Sep 2025, use API tokens)'),\n\t\t\t\t\tworkspace: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.describe('BitBucket workspace (optional, auto-detected from git remote if not provided)'),\n\t\t\t\t\trepoSlug: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.describe('BitBucket repository slug (optional, auto-detected from git remote if not provided)'),\n\t\t\t\treviewers: z\n\t\t\t\t\t.array(z.string().describe('Reviewer username'))\n\t\t\t\t\t.optional()\n\t\t\t\t\t.describe('List of usernames to add as PR reviewers. Usernames are resolved to Bitbucket account IDs at PR creation time.'),\n\t\t\t\t})\n\t\t\t\t.optional(),\n\t\t})\n\t\t.optional()\n\t\t.describe('Version control provider configuration'),\n\tmergeBehavior: z\n\t\t.object({\n\t\t\tmode: z.enum(mergeModeValues).transform(mergeModeTransform).optional(),\n\t\t\tremote: z.string().optional(),\n\t\t\tautoCommitPush: z\n\t\t\t\t.boolean()\n\t\t\t\t.optional()\n\t\t\t\t.describe(\n\t\t\t\t\t'Auto-commit and push after code review in draft PR mode. Defaults to true when mode is draft-pr.'\n\t\t\t\t),\n\t\t\tprTitlePrefix: z.boolean().optional(),\n\t\t\topenBrowserOnFinish: z\n\t\t\t\t.boolean()\n\t\t\t\t.optional()\n\t\t\t\t.describe(\n\t\t\t\t\t'Open the PR in the default browser after finishing in pr or draft-pr mode. Use --no-browser flag to override.'\n\t\t\t\t),\n\t\t})\n\t\t.optional()\n\t\t.describe('Merge behavior configuration: local (merge locally), pr (create PR), or draft-pr (create draft PR at start, mark ready on finish)'),\n\tide: z\n\t\t.object({\n\t\t\ttype: z\n\t\t\t\t.enum(['vscode', 'cursor', 'webstorm', 'sublime', 'intellij', 'windsurf', 'antigravity'])\n\t\t\t\t.optional()\n\t\t\t\t.describe(\n\t\t\t\t\t'IDE to launch when starting a loom. Options: vscode (Visual Studio Code), cursor (Cursor AI editor), ' +\n\t\t\t\t\t\t'webstorm (JetBrains WebStorm), sublime (Sublime Text), intellij (JetBrains IntelliJ IDEA), ' +\n\t\t\t\t\t\t'windsurf (Windsurf editor), antigravity (Antigravity IDE).'\n\t\t\t\t),\n\t\t})\n\t\t.optional()\n\t\t.describe(\n\t\t\t'IDE configuration for workspace launches. Controls which editor opens when you start a loom. ' +\n\t\t\t\t'Supports VSCode, Cursor, WebStorm, Sublime Text, IntelliJ, Windsurf, and Antigravity. ' +\n\t\t\t\t'Note: Color synchronization (title bar colors) only works with VSCode-compatible editors (vscode, cursor, windsurf, antigravity).'\n\t\t),\n\tcolors: z\n\t\t.object({\n\t\t\tterminal: z\n\t\t\t\t.boolean()\n\t\t\t\t.optional()\n\t\t\t\t.describe('Apply terminal background colors based on branch name (macOS only)'),\n\t\t\tvscode: z\n\t\t\t\t.boolean()\n\t\t\t\t.optional()\n\t\t\t\t.describe(\n\t\t\t\t\t'Apply VSCode/Cursor title bar colors based on branch name. ' +\n\t\t\t\t\t\t'Note: This modifies .vscode/settings.json which may be in source control.'\n\t\t\t\t),\n\t\t})\n\t\t.optional()\n\t\t.describe('Color synchronization settings for workspace identification'),\n\tattribution: z\n\t\t.enum(['off', 'upstreamOnly', 'on'])\n\t\t.optional()\n\t\t.describe(\n\t\t\t'Controls when iloom attribution appears in session summaries. ' +\n\t\t\t\t'\"off\" - never show attribution. ' +\n\t\t\t\t'\"upstreamOnly\" - only show for contributions to external repositories (e.g., open source). ' +\n\t\t\t\t'\"on\" - always show attribution.'\n\t\t),\n\tgit: z\n\t\t.object({\n\t\t\tcommitTimeout: z\n\t\t\t\t.number()\n\t\t\t\t.min(1000, 'Commit timeout must be at least 1000ms')\n\t\t\t\t.max(600000, 'Commit timeout cannot exceed 600000ms (10 minutes)')\n\t\t\t\t.optional()\n\t\t\t\t.describe('Timeout in milliseconds for git commit operations. Increase for long-running pre-commit hooks.'),\n\t\t})\n\t\t.optional()\n\t\t.describe('Git operation settings'),\n})\n\n/**\n * TypeScript type for dev server settings derived from Zod schema\n */\nexport type DevServerSettings = z.infer<typeof DevServerSettingsSchema>\n\n/**\n * TypeScript type for Neon settings derived from Zod schema\n */\nexport type NeonSettings = z.infer<typeof NeonSettingsSchema>\n\n/**\n * TypeScript type for database providers settings derived from Zod schema\n */\nexport type DatabaseProvidersSettings = z.infer<typeof DatabaseProvidersSettingsSchema>\n\n/**\n * TypeScript type for agent settings derived from Zod schema\n */\nexport type AgentSettings = z.infer<typeof AgentSettingsSchema>\n\n/**\n * TypeScript type for spin agent settings derived from Zod schema\n */\nexport type SpinAgentSettings = z.infer<typeof SpinAgentSettingsSchema>\n\n/**\n * TypeScript type for plan command settings derived from Zod schema\n */\nexport type PlanCommandSettings = z.infer<typeof PlanCommandSettingsSchema>\n\n/**\n * TypeScript type for summary settings derived from Zod schema\n */\nexport type SummarySettings = z.infer<typeof SummarySettingsSchema>\n\n/**\n * TypeScript type for workflow permission configuration derived from Zod schema\n */\nexport type WorkflowPermission = z.infer<typeof WorkflowPermissionSchema>\n\n/**\n * TypeScript type for workflows settings derived from Zod schema\n */\nexport type WorkflowsSettings = z.infer<typeof WorkflowsSettingsSchema>\n\n/**\n * TypeScript type for capabilities settings derived from Zod schema\n */\nexport type CapabilitiesSettings = z.infer<typeof CapabilitiesSettingsSchema>\n\n/**\n * TypeScript type for IDE settings derived from Zod schema\n */\nexport type IdeSettings = z.infer<typeof IloomSettingsSchema>['ide']\n\n/**\n * TypeScript type for iloom settings derived from Zod schema\n */\nexport type IloomSettings = z.infer<typeof IloomSettingsSchema>\n\n/**\n * TypeScript input type for iloom settings (before Zod defaults are applied)\n * Used for validation where partial/input objects need to be accepted\n */\nexport type IloomSettingsInput = z.input<typeof IloomSettingsSchema>\n\n/**\n * Recursively redact sensitive fields (tokens, secrets, passwords) from an object.\n * Returns a deep copy with sensitive string values replaced by '[REDACTED]'.\n */\nexport function redactSensitiveFields(obj: unknown): unknown {\n\tif (obj === null || obj === undefined) return obj\n\tif (typeof obj !== 'object') return obj\n\tif (Array.isArray(obj)) return obj.map(redactSensitiveFields)\n\n\tconst sensitiveKeys = ['apitoken', 'token', 'secret', 'password', 'credential']\n\tconst result: Record<string, unknown> = {}\n\tfor (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n\t\tconst lowerKey = key.toLowerCase()\n\t\tif (sensitiveKeys.some(s => lowerKey.includes(s)) && typeof value === 'string') {\n\t\t\tresult[key] = '[REDACTED]'\n\t\t} else if (typeof value === 'object' && value !== null) {\n\t\t\tresult[key] = redactSensitiveFields(value)\n\t\t} else {\n\t\t\tresult[key] = value\n\t\t}\n\t}\n\treturn result\n}\n\n/**\n * Manages project-level settings from .iloom/settings.json\n */\nexport class SettingsManager {\n\t/**\n\t * Load settings from global, project, and local sources with proper precedence\n\t * Merge hierarchy (lowest to highest priority):\n\t * 1. Global settings (~/.config/iloom-ai/settings.json)\n\t * 2. Project settings (<PROJECT_ROOT>/.iloom/settings.json)\n\t * 3. Local settings (<PROJECT_ROOT>/.iloom/settings.local.json)\n\t * 4. CLI overrides (--set flags)\n\t * Returns empty object if all files don't exist (not an error)\n\t */\n\tasync loadSettings(\n\t\tprojectRoot?: string,\n\t\tcliOverrides?: Partial<IloomSettings>,\n\t): Promise<IloomSettings> {\n\t\tconst root = this.getProjectRoot(projectRoot)\n\n\t\t// Load global settings (lowest priority)\n\t\tconst globalSettings = await this.loadGlobalSettingsFile()\n\t\tconst globalSettingsPath = this.getGlobalSettingsPath()\n\t\tlogger.debug(`🌍 Global settings from ${globalSettingsPath}:`, JSON.stringify(redactSensitiveFields(globalSettings), null, 2))\n\n\t\t// Load base settings from settings.json\n\t\tconst baseSettings = await this.loadSettingsFile(root, 'settings.json')\n\t\tconst baseSettingsPath = path.join(root, '.iloom', 'settings.json')\n\t\tlogger.debug(`📄 Base settings from ${baseSettingsPath}:`, JSON.stringify(redactSensitiveFields(baseSettings), null, 2))\n\n\t\t// Load local overrides from settings.local.json\n\t\tconst localSettings = await this.loadSettingsFile(root, 'settings.local.json')\n\t\tconst localSettingsPath = path.join(root, '.iloom', 'settings.local.json')\n\t\tlogger.debug(`📄 Local settings from ${localSettingsPath}:`, JSON.stringify(redactSensitiveFields(localSettings), null, 2))\n\n\t\t// Deep merge with priority: cliOverrides > localSettings > baseSettings > globalSettings\n\t\tlet merged = this.mergeSettings(this.mergeSettings(globalSettings, baseSettings), localSettings)\n\t\tlogger.debug('🔄 After merging global + base + local settings:', JSON.stringify(redactSensitiveFields(merged), null, 2))\n\n\t\tif (cliOverrides && Object.keys(cliOverrides).length > 0) {\n\t\t\tlogger.debug('⚙️ CLI overrides to apply:', JSON.stringify(redactSensitiveFields(cliOverrides), null, 2))\n\t\t\tmerged = this.mergeSettings(merged, cliOverrides)\n\t\t\tlogger.debug('🔄 After applying CLI overrides:', JSON.stringify(redactSensitiveFields(merged), null, 2))\n\t\t}\n\n\t\t// Validate merged result\n\t\ttry {\n\t\t\tconst finalSettings = IloomSettingsSchema.parse(merged)\n\n\t\t\t// Debug: Log final merged configuration\n\t\t\tthis.logFinalConfiguration(finalSettings)\n\n\t\t\treturn finalSettings\n\t\t} catch (error) {\n\t\t\t// Show all Zod validation errors\n\t\t\tif (error instanceof z.ZodError) {\n\t\t\t\tconst errorMsg = this.formatAllZodErrors(error, '<merged settings>')\n\t\t\t\t// Enhance error message if CLI overrides were applied\n\t\t\t\tif (cliOverrides && Object.keys(cliOverrides).length > 0) {\n\t\t\t\t\tthrow new Error(`${errorMsg.message}\\n\\nNote: CLI overrides were applied. Check your --set arguments.`)\n\t\t\t\t}\n\t\t\t\tthrow errorMsg\n\t\t\t}\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t/**\n\t * Log the final merged configuration for debugging\n\t */\n\tprivate logFinalConfiguration(settings: IloomSettings): void {\n\t\tlogger.debug('📋 Final merged configuration:', JSON.stringify(redactSensitiveFields(settings), null, 2))\n\t}\n\n\t/**\n\t * Load and parse a single settings file\n\t * Returns empty object if file doesn't exist (not an error)\n\t * Uses non-defaulting schema to prevent polluting partial settings with defaults before merge\n\t */\n\tprivate async loadSettingsFile(\n\t\tprojectRoot: string,\n\t\tfilename: string,\n\t): Promise<z.infer<typeof IloomSettingsSchemaNoDefaults>> {\n\t\tconst settingsPath = path.join(projectRoot, '.iloom', filename)\n\n\t\ttry {\n\t\t\tconst content = await readFile(settingsPath, 'utf-8')\n\t\t\tlet parsed: unknown\n\n\t\t\ttry {\n\t\t\t\tparsed = JSON.parse(content)\n\t\t\t} catch (error) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Failed to parse settings file at ${settingsPath}: ${error instanceof Error ? error.message : 'Invalid JSON'}`,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\t// Basic type checking - ensure it's an object, but don't validate schema completeness\n\t\t\t// Individual files may be incomplete (e.g., Linear config split between files)\n\t\t\t// Final validation will happen on the merged result in loadSettings()\n\t\t\tif (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Settings validation failed at ${filename}:\\n - root: Expected object, received ${typeof parsed}`\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn parsed as z.infer<typeof IloomSettingsSchemaNoDefaults>\n\t\t} catch (error) {\n\t\t\t// File not found is not an error - return empty settings\n\t\t\tif ((error as { code?: string }).code === 'ENOENT') {\n\t\t\t\tlogger.debug(`No settings file found at ${settingsPath}, using defaults`)\n\t\t\t\treturn {}\n\t\t\t}\n\n\t\t\t// Re-throw parsing errors\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t/**\n\t * Deep merge two settings objects with priority to override\n\t * Uses deepmerge library with array replacement strategy\n\t */\n\tprivate mergeSettings(\n\t\tbase: Partial<IloomSettings> | z.infer<typeof IloomSettingsSchemaNoDefaults>,\n\t\toverride: Partial<IloomSettings> | z.infer<typeof IloomSettingsSchemaNoDefaults>,\n\t): IloomSettings {\n\t\t// Use deepmerge with array replacement (not concatenation)\n\t\t// Type assertion is safe because the merged result will be validated with IloomSettingsSchema\n\t\t// which applies all the defaults after merging\n\t\treturn deepmerge(base as Record<string, unknown>, override as Record<string, unknown>, {\n\t\t\t// Replace arrays instead of concatenating them\n\t\t\tarrayMerge: (_destinationArray, sourceArray) => sourceArray,\n\t\t}) as IloomSettings\n\t}\n\n\t/**\n\t * Format all Zod validation errors into a single error message\n\t */\n\tprivate formatAllZodErrors(error: z.ZodError, settingsPath: string): Error {\n\t\tconst errorMessages = error.issues.map(issue => {\n\t\t\tconst path = issue.path.length > 0 ? issue.path.join('.') : 'root'\n\t\t\treturn ` - ${path}: ${issue.message}`\n\t\t})\n\n\t\treturn new Error(\n\t\t\t`Settings validation failed at ${settingsPath}:\\n${errorMessages.join('\\n')}`,\n\t\t)\n\t}\n\n\t/**\n\t * Validate settings structure and model names using Zod schema\n\t * This method is kept for testing purposes but uses Zod internally\n\t * @internal - Only used in tests via bracket notation\n\t */\n\t// @ts-expect-error - Used in tests via bracket notation, TypeScript can't detect this usage\n\tprivate validateSettings(settings: IloomSettingsInput): void {\n\t\ttry {\n\t\t\tIloomSettingsSchema.parse(settings)\n\t\t} catch (error) {\n\t\t\tif (error instanceof z.ZodError) {\n\t\t\t\tthrow this.formatAllZodErrors(error, '<validation>')\n\t\t\t}\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t/**\n\t * Get project root (defaults to process.cwd())\n\t */\n\tprivate getProjectRoot(projectRoot?: string): string {\n\t\treturn projectRoot ?? process.cwd()\n\t}\n\n\t/**\n\t * Get global config directory path (~/.config/iloom-ai)\n\t */\n\tprivate getGlobalConfigDir(): string {\n\t\treturn path.join(os.homedir(), '.config', 'iloom-ai')\n\t}\n\n\t/**\n\t * Get global settings file path (~/.config/iloom-ai/settings.json)\n\t */\n\tprivate getGlobalSettingsPath(): string {\n\t\treturn path.join(this.getGlobalConfigDir(), 'settings.json')\n\t}\n\n\t/**\n\t * Load and parse global settings file\n\t * Returns empty object if file doesn't exist (not an error)\n\t * Warns but returns empty object on validation/parse errors (graceful degradation)\n\t */\n\tprivate async loadGlobalSettingsFile(): Promise<z.infer<typeof IloomSettingsSchemaNoDefaults>> {\n\t\tconst settingsPath = this.getGlobalSettingsPath()\n\n\t\ttry {\n\t\t\tconst content = await readFile(settingsPath, 'utf-8')\n\t\t\tlet parsed: unknown\n\n\t\t\ttry {\n\t\t\t\tparsed = JSON.parse(content)\n\t\t\t} catch (error) {\n\t\t\t\tlogger.warn(\n\t\t\t\t\t`Failed to parse global settings file at ${settingsPath}: ${error instanceof Error ? error.message : 'Invalid JSON'}. Ignoring global settings.`,\n\t\t\t\t)\n\t\t\t\treturn {}\n\t\t\t}\n\n\t\t\t// Validate with non-defaulting schema\n\t\t\ttry {\n\t\t\t\tconst validated = IloomSettingsSchemaNoDefaults.strict().parse(parsed)\n\t\t\t\treturn validated\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof z.ZodError) {\n\t\t\t\t\tconst errorMsg = this.formatAllZodErrors(error, 'global settings')\n\t\t\t\t\tlogger.warn(`${errorMsg.message}. Ignoring global settings.`)\n\t\t\t\t} else {\n\t\t\t\t\tlogger.warn(`Validation error in global settings: ${error instanceof Error ? error.message : 'Unknown error'}. Ignoring global settings.`)\n\t\t\t\t}\n\t\t\t\treturn {}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\t// File not found is not an error - return empty settings\n\t\t\tif ((error as { code?: string }).code === 'ENOENT') {\n\t\t\t\tlogger.debug(`No global settings file found at ${settingsPath}`)\n\t\t\t\treturn {}\n\t\t\t}\n\n\t\t\t// Other file system errors - warn and continue\n\t\t\tlogger.warn(`Error reading global settings file at ${settingsPath}: ${error instanceof Error ? error.message : 'Unknown error'}. Ignoring global settings.`)\n\t\t\treturn {}\n\t\t}\n\t}\n\n\t/**\n\t * Get effective protected branches list with mainBranch always included\n\t *\n\t * This method provides a single source of truth for protected branches logic:\n\t * 1. Use configured protectedBranches if provided\n\t * 2. Otherwise use defaults: [mainBranch, 'main', 'master', 'develop']\n\t * 3. ALWAYS ensure mainBranch is included even if user configured custom list\n\t *\n\t * @param projectRoot - Optional project root directory (defaults to process.cwd())\n\t * @returns Array of protected branch names with mainBranch guaranteed to be included\n\t */\n\tasync getProtectedBranches(projectRoot?: string): Promise<string[]> {\n\t\tconst settings = await this.loadSettings(projectRoot)\n\t\t// SYNC: If this default changes, update displayDefaultsBox() in src/utils/first-run-setup.ts\n\t\tconst mainBranch = settings.mainBranch ?? 'main'\n\n\t\t// Build protected branches list:\n\t\t// 1. Use configured protectedBranches if provided\n\t\t// 2. Otherwise use defaults: [mainBranch, 'main', 'master', 'develop']\n\t\t// 3. ALWAYS ensure mainBranch is included even if user configured custom list\n\t\tlet protectedBranches: string[]\n\t\tif (settings.protectedBranches) {\n\t\t\t// Use configured list but ensure mainBranch is always included\n\t\t\tprotectedBranches = settings.protectedBranches.includes(mainBranch)\n\t\t\t\t? settings.protectedBranches\n\t\t\t\t: [mainBranch, ...settings.protectedBranches]\n\t\t} else {\n\t\t\t// Use defaults with current mainBranch\n\t\t\tprotectedBranches = [mainBranch, 'main', 'master', 'develop']\n\t\t}\n\n\t\treturn protectedBranches\n\t}\n\n\t/**\n\t * Get the spin orchestrator model with default applied\n\t * Default is defined in SpinAgentSettingsSchema\n\t *\n\t * @param settings - Pre-loaded settings object\n\t * @returns Model shorthand ('opus', 'sonnet', or 'haiku')\n\t */\n\tgetSpinModel(settings?: IloomSettings, mode?: 'swarm'): 'sonnet' | 'opus' | 'haiku' {\n\t\tif (mode === 'swarm') {\n\t\t\tif (settings?.spin?.swarmModel) {\n\t\t\t\treturn settings.spin.swarmModel\n\t\t\t}\n\t\t\t// Default to opus for swarm orchestrator (\"Balanced\" mode)\n\t\t\treturn 'opus'\n\t\t}\n\t\treturn settings?.spin?.model ?? SpinAgentSettingsSchema.parse({}).model\n\t}\n\n\t/**\n\t * Get the plan command model with default applied\n\t * Default is defined in PlanCommandSettingsSchema\n\t *\n\t * @param settings - Pre-loaded settings object\n\t * @returns Model shorthand ('opus', 'sonnet', or 'haiku')\n\t */\n\tgetPlanModel(settings?: IloomSettings): 'sonnet' | 'opus' | 'haiku' {\n\t\treturn settings?.plan?.model ?? PlanCommandSettingsSchema.parse({}).model\n\t}\n\n\t/**\n\t * Get the plan command planner with default applied\n\t * Default is 'claude'\n\t *\n\t * @param settings - Pre-loaded settings object\n\t * @returns Planner provider ('claude', 'gemini', or 'codex')\n\t */\n\tgetPlanPlanner(settings?: IloomSettings): 'claude' | 'gemini' | 'codex' {\n\t\treturn settings?.plan?.planner ?? 'claude'\n\t}\n\n\t/**\n\t * Get the plan command reviewer with default applied\n\t * Default is 'none' (no review step)\n\t *\n\t * @param settings - Pre-loaded settings object\n\t * @returns Reviewer provider ('claude', 'gemini', 'codex', or 'none')\n\t */\n\tgetPlanReviewer(settings?: IloomSettings): 'claude' | 'gemini' | 'codex' | 'none' {\n\t\treturn settings?.plan?.reviewer ?? 'none'\n\t}\n\n\t/**\n\t * Get the plan command waveVerification setting with default applied\n\t * Default is true (verification tasks are generated)\n\t *\n\t * @param settings - Pre-loaded settings object\n\t * @returns Whether wave verification is enabled\n\t */\n\tgetPlanWaveVerification(settings?: IloomSettings): boolean {\n\t\treturn settings?.plan?.waveVerification !== false\n\t}\n\n\t/**\n\t * Get the session summary model with default applied\n\t * Default is defined in SummarySettingsSchema\n\t *\n\t * @param settings - Pre-loaded settings object\n\t * @returns Model shorthand ('opus', 'sonnet', or 'haiku')\n\t */\n\tgetSummaryModel(settings?: IloomSettings): 'sonnet' | 'opus' | 'haiku' {\n\t\treturn settings?.summary?.model ?? SummarySettingsSchema.parse({}).model\n\t}\n}\n"],"mappings":"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","names":["path"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/PromptTemplateManager.ts"],"sourcesContent":["import { readFile } from 'fs/promises'\nimport { accessSync } from 'fs'\nimport path from 'path'\nimport { fileURLToPath } from 'url'\nimport Handlebars from 'handlebars'\nimport { logger } from '../utils/logger.js'\nimport type { AgentSettings } from './SettingsManager.js'\n\n// Register raw helper to handle content with curly braces (e.g., JSON)\n// Usage: {{{{raw}}}}{{VARIABLE}}{{{{/raw}}}}\n// This outputs the variable content as-is without Handlebars parsing its curly braces\nHandlebars.registerHelper('raw', function (this: unknown, options: Handlebars.HelperOptions) {\n\treturn options.fn(this)\n})\n\nexport interface TemplateVariables {\n\tISSUE_NUMBER?: string | number\n\tPR_NUMBER?: number\n\tISSUE_TITLE?: string\n\tPR_TITLE?: string\n\tWORKSPACE_PATH?: string\n\tPORT?: number\n\tONE_SHOT_MODE?: boolean\n\tINTERACTIVE_MODE?: boolean\n\tSETTINGS_SCHEMA?: string\n\tSETTINGS_GLOBAL_JSON?: string\n\tSETTINGS_JSON?: string\n\tSETTINGS_LOCAL_JSON?: string\n\tSHELL_TYPE?: string\n\tSHELL_CONFIG_PATH?: string\n\tSHELL_CONFIG_CONTENT?: string\n\tREMOTES_INFO?: string\n\tMULTIPLE_REMOTES?: string\n\tSINGLE_REMOTE?: string\n\tSINGLE_REMOTE_NAME?: string\n\tSINGLE_REMOTE_URL?: string\n\tNO_REMOTES?: string\n\tREADME_CONTENT?: string\n\tSETTINGS_SCHEMA_CONTENT?: string\n\tFIRST_TIME_USER?: boolean\n\tVSCODE_SETTINGS_GITIGNORED?: string\n\t// Session summary template variables\n\tSESSION_CONTEXT?: string // Session ID for Claude to reference its conversation\n\tBRANCH_NAME?: string // Branch being finished\n\tLOOM_TYPE?: string // 'issue' or 'pr'\n\tCOMPACT_SUMMARIES?: string // Extracted compact summaries from session transcript\n\tRECAP_DATA?: string // Formatted recap data (goal, complexity, entries, artifacts)\n\t// Draft PR mode variables - mutually exclusive with standard issue mode\n\tDRAFT_PR_NUMBER?: number // PR number for draft PR workflow\n\tDRAFT_PR_URL?: string // Full URL of the draft PR (e.g., https://github.com/owner/repo/pull/123)\n\tDRAFT_PR_MODE?: boolean // True when using draft-pr merge mode\n\tAUTO_COMMIT_PUSH?: boolean // True when auto-commit/push is enabled for draft PR mode\n\tSTANDARD_ISSUE_MODE?: boolean // True when using standard issue commenting (not draft PR)\n\tSTANDARD_BRANCH_MODE?: boolean // True when using standard branch mode (not draft PR)\n\t// Direct prompt mode - agent enhances raw text without issue context or MCP tools\n\tDIRECT_PROMPT_MODE?: boolean\n\t// VS Code environment detection\n\tIS_VSCODE_MODE?: boolean // True when ILOOM_VSCODE=1 environment variable is set\n\t// Multi-language support variables - mutually exclusive\n\tHAS_PACKAGE_JSON?: boolean // True when project has package.json\n\tNO_PACKAGE_JSON?: boolean // True when project does not have package.json (non-Node.js projects)\n\t// Review agent configuration variables (code reviewer)\n\tREVIEW_ENABLED?: boolean // True if review is enabled (defaults to true)\n\tREVIEW_CLAUDE_MODEL?: string // Claude model if configured (defaults to 'sonnet')\n\tREVIEW_GEMINI_MODEL?: string // Gemini model if configured\n\tREVIEW_CODEX_MODEL?: string // Codex model if configured\n\tHAS_REVIEW_CLAUDE?: boolean // True if claude provider configured (defaults to true)\n\tHAS_REVIEW_GEMINI?: boolean // True if gemini provider configured\n\tHAS_REVIEW_CODEX?: boolean // True if codex provider configured\n\t// Artifact reviewer configuration variables\n\tARTIFACT_REVIEW_ENABLED?: boolean // True if artifact review is enabled (defaults to true)\n\tARTIFACT_REVIEW_CLAUDE_MODEL?: string // Claude model if configured (defaults to 'sonnet')\n\tARTIFACT_REVIEW_GEMINI_MODEL?: string // Gemini model if configured\n\tARTIFACT_REVIEW_CODEX_MODEL?: string // Codex model if configured\n\tHAS_ARTIFACT_REVIEW_CLAUDE?: boolean // True if claude provider configured (defaults to true)\n\tHAS_ARTIFACT_REVIEW_GEMINI?: boolean // True if gemini provider configured\n\tHAS_ARTIFACT_REVIEW_CODEX?: boolean // True if codex provider configured\n\t// Per-agent review flags (whether artifacts should be reviewed before posting)\n\tENHANCER_REVIEW_ENABLED?: boolean // True if enhancer artifacts should be reviewed\n\tANALYZER_REVIEW_ENABLED?: boolean // True if analyzer artifacts should be reviewed\n\tPLANNER_REVIEW_ENABLED?: boolean // True if planner artifacts should be reviewed\n\tANALYZE_AND_PLAN_REVIEW_ENABLED?: boolean // True if analyze-and-plan artifacts should be reviewed\n\tIMPLEMENTER_REVIEW_ENABLED?: boolean // True if implementer artifacts should be reviewed\n\tCOMPLEXITY_REVIEW_ENABLED?: boolean // True if complexity evaluator artifacts should be reviewed\n\tCOMPLEXITY_OVERRIDE?: 'trivial' | 'simple' | 'complex' // Complexity override from CLI flag or loom metadata\n\t// Planning mode variables - mutually exclusive\n\tEXISTING_ISSUE_MODE?: boolean // True when decomposing an existing issue (il plan 42)\n\tFRESH_PLANNING_MODE?: boolean // True when starting fresh planning session (il plan \"feature idea\")\n\t// Issue context for decomposition mode\n\tPARENT_ISSUE_NUMBER?: string | undefined // Issue number being decomposed\n\tPARENT_ISSUE_TITLE?: string | undefined // Title of issue being decomposed\n\tPARENT_ISSUE_BODY?: string | undefined // Body of issue being decomposed\n\t// Existing children and dependencies context for decomposition mode\n\tPARENT_ISSUE_CHILDREN?: string | undefined // Formatted list of existing child issues (if any)\n\tPARENT_ISSUE_DEPENDENCIES?: string | undefined // Formatted list of existing dependencies (if any)\n\t// Multi-AI provider support for plan command\n\tPLANNER?: 'claude' | 'gemini' | 'codex'\n\tREVIEWER?: 'claude' | 'gemini' | 'codex' | 'none'\n\tUSE_CLAUDE_PLANNER?: boolean\n\tUSE_GEMINI_PLANNER?: boolean\n\tUSE_CODEX_PLANNER?: boolean\n\tUSE_CLAUDE_REVIEWER?: boolean\n\tUSE_GEMINI_REVIEWER?: boolean\n\tUSE_CODEX_REVIEWER?: boolean\n\tHAS_REVIEWER?: boolean\n\t// Git remote configuration\n\tGIT_REMOTE?: string // Remote name for push (defaults to 'origin')\n\t// Swarm orchestrator variables\n\tEPIC_ISSUE_NUMBER?: string | number\n\tEPIC_WORKTREE_PATH?: string\n\tEPIC_METADATA_PATH?: string // Path to the epic's metadata JSON file\n\tCHILD_ISSUES?: string // JSON stringified array of child issues with worktree paths\n\tDEPENDENCY_MAP?: string // JSON stringified dependency map\n\tSWARM_MODE?: boolean // True when rendering agents in swarm mode\n\tWAVE_VERIFICATION?: boolean // True when wave verification is enabled (planner generates verification child issues)\n\tAUTO_SWARM_MODE?: boolean // True when plan command launched with --auto-swarm flag\n\tNO_CLEANUP?: boolean // True when child loom cleanup should be skipped (e.g., manual cleanup later)\n\tPOST_SWARM_REVIEW?: boolean // True when post-swarm code review is enabled (defaults to true)\n\tISSUE_PREFIX?: string // \"#\" for GitHub, \"\" for Linear/Jira — used in commit message templates\n\tSWARM_TEAM_NAME?: string // Unique team name for swarm orchestrator (project-epic-timestamp)\n}\n\n/**\n * Build review-related template variables from settings.\n * Used by both the ignite command (for prompt templates) and AgentManager (for agent prompts).\n */\nexport function buildReviewTemplateVariables(isSwarmMode: boolean, agents?: Record<string, AgentSettings> | null): Partial<TemplateVariables> {\n\tconst variables: Partial<TemplateVariables> = {}\n\n\t// Code reviewer configuration\n\tconst reviewerSettings = agents?.['iloom-code-reviewer']\n\tconst reviewEnabled = reviewerSettings?.enabled !== false // Default to true\n\tvariables.REVIEW_ENABLED = reviewEnabled\n\n\tif (reviewEnabled) {\n\t\tconst providers = reviewerSettings?.providers ?? {}\n\t\tconst hasAnyProvider = Object.keys(providers).length > 0\n\n\t\tconst claudeModel = providers.claude ?? (hasAnyProvider ? undefined : 'sonnet')\n\t\tif (claudeModel) {\n\t\t\tvariables.REVIEW_CLAUDE_MODEL = claudeModel\n\t\t}\n\t\tif (providers.gemini) {\n\t\t\tvariables.REVIEW_GEMINI_MODEL = providers.gemini\n\t\t}\n\t\tif (providers.codex) {\n\t\t\tvariables.REVIEW_CODEX_MODEL = providers.codex\n\t\t}\n\t\tvariables.HAS_REVIEW_CLAUDE = !!claudeModel\n\t\tvariables.HAS_REVIEW_GEMINI = !!providers.gemini\n\t\tvariables.HAS_REVIEW_CODEX = !!providers.codex\n\t}\n\n\t// Artifact reviewer configuration\n\tconst artifactReviewerSettings = agents?.['iloom-artifact-reviewer']\n\tconst artifactReviewEnabled = artifactReviewerSettings?.enabled !== false // Default to true\n\tvariables.ARTIFACT_REVIEW_ENABLED = artifactReviewEnabled\n\n\tif (artifactReviewEnabled) {\n\t\tconst artifactProviders = artifactReviewerSettings?.providers ?? {}\n\t\tconst hasAnyArtifactProvider = Object.keys(artifactProviders).length > 0\n\n\t\tconst artifactClaudeModel = artifactProviders.claude ?? (hasAnyArtifactProvider ? undefined : 'sonnet')\n\t\tif (artifactClaudeModel) {\n\t\t\tvariables.ARTIFACT_REVIEW_CLAUDE_MODEL = artifactClaudeModel\n\t\t}\n\t\tif (artifactProviders.gemini) {\n\t\t\tvariables.ARTIFACT_REVIEW_GEMINI_MODEL = artifactProviders.gemini\n\t\t}\n\t\tif (artifactProviders.codex) {\n\t\t\tvariables.ARTIFACT_REVIEW_CODEX_MODEL = artifactProviders.codex\n\t\t}\n\t\tvariables.HAS_ARTIFACT_REVIEW_CLAUDE = !!artifactClaudeModel\n\t\tvariables.HAS_ARTIFACT_REVIEW_GEMINI = !!artifactProviders.gemini\n\t\tvariables.HAS_ARTIFACT_REVIEW_CODEX = !!artifactProviders.codex\n\t}\n\n\t// Per-agent review flags (defaults to false for each)\n\t// In swarm mode, swarmReview is used directly (defaults to false if not set)\n\tconst resolveReview = (agent: AgentSettings | undefined): boolean => {\n\t\tif (isSwarmMode) {\n\t\t\treturn agent?.swarmReview === true\n\t\t}\n\t\treturn agent?.review === true\n\t}\n\tvariables.ENHANCER_REVIEW_ENABLED = resolveReview(agents?.['iloom-issue-enhancer'])\n\tvariables.ANALYZER_REVIEW_ENABLED = resolveReview(agents?.['iloom-issue-analyzer'])\n\tvariables.PLANNER_REVIEW_ENABLED = resolveReview(agents?.['iloom-issue-planner'])\n\tvariables.ANALYZE_AND_PLAN_REVIEW_ENABLED = resolveReview(agents?.['iloom-issue-analyze-and-plan'])\n\tvariables.IMPLEMENTER_REVIEW_ENABLED = resolveReview(agents?.['iloom-issue-implementer'])\n\tvariables.COMPLEXITY_REVIEW_ENABLED = resolveReview(agents?.['iloom-issue-complexity-evaluator'])\n\n\treturn variables\n}\n\nexport class PromptTemplateManager {\n\tprivate templateDir: string\n\n\tconstructor(templateDir?: string) {\n\t\tif (templateDir) {\n\t\t\tthis.templateDir = templateDir\n\t\t} else {\n\t\t\t// Find templates relative to the package installation\n\t\t\t// When running from dist/, templates are copied to dist/prompts/\n\t\t\tconst currentFileUrl = import.meta.url\n\t\t\tconst currentFilePath = fileURLToPath(currentFileUrl)\n\t\t\tconst distDir = path.dirname(currentFilePath) // dist directory (may be chunked file location)\n\n\t\t\t// Walk up to find the dist directory (in case of chunked files)\n\t\t\tlet templateDir = path.join(distDir, 'prompts')\n\t\t\tlet currentDir = distDir\n\n\t\t\t// Try to find the prompts directory by walking up\n\t\t\twhile (currentDir !== path.dirname(currentDir)) {\n\t\t\t\tconst candidatePath = path.join(currentDir, 'prompts')\n\t\t\t\ttry {\n\t\t\t\t\t// Check if this directory exists (sync check for constructor)\n\t\t\t\t\taccessSync(candidatePath)\n\t\t\t\t\ttemplateDir = candidatePath\n\t\t\t\t\tbreak\n\t\t\t\t} catch {\n\t\t\t\t\tcurrentDir = path.dirname(currentDir)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.templateDir = templateDir\n\t\t\tlogger.debug('PromptTemplateManager initialized', {\n\t\t\t\tcurrentFilePath,\n\t\t\t\tdistDir,\n\t\t\t\ttemplateDir: this.templateDir\n\t\t\t})\n\t\t}\n\t}\n\n\t/**\n\t * Load a template file by name\n\t */\n\tasync loadTemplate(templateName: 'issue' | 'pr' | 'regular' | 'init' | 'session-summary' | 'plan' | 'swarm-orchestrator'): Promise<string> {\n\t\tconst templatePath = path.join(this.templateDir, `${templateName}-prompt.txt`)\n\n\t\tlogger.debug('Loading template', {\n\t\t\ttemplateName,\n\t\t\ttemplateDir: this.templateDir,\n\t\t\ttemplatePath\n\t\t})\n\n\t\ttry {\n\t\t\treturn await readFile(templatePath, 'utf-8')\n\t\t} catch (error) {\n\t\t\tlogger.error('Failed to load template', { templateName, templatePath, error })\n\t\t\tthrow new Error(`Template not found: ${templatePath}`)\n\t\t}\n\t}\n\n\t/**\n\t * Substitute variables in a template string using Handlebars\n\t */\n\tsubstituteVariables(template: string, variables: TemplateVariables): string {\n\t\tconst compiled = Handlebars.compile(template, { noEscape: true })\n\t\treturn compiled(variables)\n\t}\n\n\t/**\n\t * Get a fully processed prompt for a workflow type\n\t */\n\tasync getPrompt(\n\t\ttype: 'issue' | 'pr' | 'regular' | 'init' | 'session-summary' | 'plan' | 'swarm-orchestrator',\n\t\tvariables: TemplateVariables\n\t): Promise<string> {\n\t\tconst template = await this.loadTemplate(type)\n\t\treturn this.substituteVariables(template, variables)\n\t}\n}\n"],"mappings":"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","names":["templateDir"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/DockerManager.ts","../src/utils/docker.ts"],"sourcesContent":["import { execa } from 'execa'\nimport { logger } from '../utils/logger.js'\nimport { restoreTerminalState } from '../utils/terminal.js'\nimport {\n\tisDockerInstalled,\n\tisDockerRunning,\n\tassertDockerAvailable,\n\tparseDockerfileExpose,\n\tinspectImagePorts,\n\tsanitizeContainerName,\n\tbuildContainerName,\n\tbuildImageName,\n} from '../utils/docker.js'\n\n/**\n * Configuration for Docker-based dev server mode.\n * When provided to DevServerManager methods, the server runs inside a Docker container\n * with port mapping instead of as a local process.\n */\nexport interface DockerConfig {\n\t/** Path to Dockerfile (relative to worktree) */\n\tdockerFile: string\n\t/** Port inside the container (auto-detected from image inspect or Dockerfile EXPOSE if not set) */\n\tcontainerPort?: number | undefined\n\t/** Build arguments passed as --build-arg to docker build */\n\tdockerBuildArgs?: Record<string, string> | undefined\n\t/** Additional docker run flags (e.g., volume mounts) */\n\tdockerRunArgs?: string[] | undefined\n\t/** Identifier for container naming (issue number, branch name) */\n\tidentifier: string\n}\n\n/**\n * Web settings shape accepted by buildDockerConfigFromSettings.\n * Matches the capabilities.web section of IloomSettings.\n * Uses `| undefined` for optional properties to satisfy exactOptionalPropertyTypes.\n */\ninterface WebSettings {\n\tdevServer?: 'process' | 'docker' | undefined\n\tdockerFile?: string | undefined\n\tcontainerPort?: number | undefined\n\tdockerBuildArgs?: Record<string, string> | undefined\n\tdockerRunArgs?: string[] | undefined\n}\n\n/**\n * DockerManager encapsulates all Docker CLI interactions.\n * Used by DevServerManager and ResourceCleanup for Docker-based dev server operations.\n *\n * All methods are static since no instance state is needed -- Docker CLI is stateless.\n */\nexport class DockerManager {\n\t/**\n\t * Check if Docker CLI is available and daemon is running.\n\t * @returns true if Docker is ready, false otherwise\n\t */\n\tstatic async isAvailable(): Promise<boolean> {\n\t\tconst installed = await isDockerInstalled()\n\t\tif (!installed) return false\n\t\treturn isDockerRunning()\n\t}\n\n\t/**\n\t * Assert that Docker is available, throwing a clear error if not.\n\t * Call this early in any workflow that requires Docker.\n\t */\n\tstatic async assertAvailable(): Promise<void> {\n\t\treturn assertDockerAvailable()\n\t}\n\n\t/**\n\t * Build a Docker image.\n\t *\n\t * @param cwd - Working directory (worktree path)\n\t * @param imageName - Image tag name\n\t * @param dockerFile - Path to Dockerfile (relative to cwd)\n\t * @param buildArgs - Optional build arguments passed as --build-arg\n\t */\n\tstatic async buildImage(\n\t\tcwd: string,\n\t\timageName: string,\n\t\tdockerFile: string,\n\t\tbuildArgs?: Record<string, string>\n\t): Promise<void> {\n\t\tconst args = ['build', '-t', imageName, '-f', dockerFile]\n\n\t\tif (buildArgs) {\n\t\t\tfor (const [key, value] of Object.entries(buildArgs)) {\n\t\t\t\targs.push('--build-arg', `${key}=${value}`)\n\t\t\t}\n\t\t}\n\n\t\t// Context directory is always cwd\n\t\targs.push('.')\n\n\t\tlogger.info(`Building Docker image \"${imageName}\" from ${dockerFile}...`)\n\n\t\ttry {\n\t\t\tawait execa('docker', args, {\n\t\t\t\tcwd,\n\t\t\t\tstdio: 'inherit',\n\t\t\t})\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : 'Unknown error'\n\t\t\tthrow new Error(`Docker build failed for image \"${imageName}\": ${message}`)\n\t\t}\n\n\t\tlogger.success(`Docker image \"${imageName}\" built successfully`)\n\t}\n\n\t/**\n\t * Run a container in detached mode (background).\n\t * If a container with the same name already exists, it is force-removed first.\n\t *\n\t * @param imageName - Image to run\n\t * @param containerName - Container name (must be sanitized)\n\t * @param hostPort - Port on the host to map\n\t * @param containerPort - Port inside the container\n\t * @param additionalArgs - Additional docker run flags (e.g., volume mounts)\n\t * @returns Container ID\n\t */\n\tstatic async runDetached(\n\t\timageName: string,\n\t\tcontainerName: string,\n\t\thostPort: number,\n\t\tcontainerPort: number,\n\t\tadditionalArgs?: string[]\n\t): Promise<string> {\n\t\t// Force-remove any existing container with same name to avoid name collision\n\t\tawait DockerManager.forceRemoveContainer(containerName)\n\n\t\tconst args = [\n\t\t\t'run', '-d',\n\t\t\t'--name', containerName,\n\t\t\t'-p', `${hostPort}:${containerPort}`,\n\t\t]\n\n\t\tif (additionalArgs) {\n\t\t\targs.push(...additionalArgs)\n\t\t}\n\n\t\targs.push(imageName)\n\n\t\tlogger.info(`Starting Docker container \"${containerName}\" (${hostPort}:${containerPort})...`)\n\n\t\ttry {\n\t\t\tconst result = await execa('docker', args)\n\t\t\tconst containerId = result.stdout.trim()\n\t\t\tlogger.success(`Docker container \"${containerName}\" started (ID: ${containerId.substring(0, 12)})`)\n\t\t\treturn containerId\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : 'Unknown error'\n\t\t\tthrow new Error(`Failed to start Docker container \"${containerName}\": ${message}`)\n\t\t}\n\t}\n\n\t/**\n\t * Run a container in foreground mode (attached, blocking).\n\t * The container is automatically removed on exit (--rm flag).\n\t * Stdout/stderr are streamed to the terminal.\n\t *\n\t * Signal forwarding: Captures SIGINT/SIGTERM on the host process and\n\t * forwards them to the container via `docker kill --signal` for graceful\n\t * shutdown of the framework running inside the container.\n\t *\n\t * @param imageName - Image to run\n\t * @param containerName - Container name (must be sanitized)\n\t * @param hostPort - Port on the host to map\n\t * @param containerPort - Port inside the container\n\t * @param additionalArgs - Additional docker run flags\n\t * @param redirectToStderr - If true, redirect stdout/stderr to stderr\n\t */\n\tstatic async runForeground(\n\t\timageName: string,\n\t\tcontainerName: string,\n\t\thostPort: number,\n\t\tcontainerPort: number,\n\t\tadditionalArgs?: string[],\n\t\tredirectToStderr?: boolean\n\t): Promise<void> {\n\t\t// Force-remove any existing container with same name to avoid name collision\n\t\tawait DockerManager.forceRemoveContainer(containerName)\n\n\t\tconst args = [\n\t\t\t'run',\n\t\t\t'--name', containerName,\n\t\t\t'--rm',\n\t\t\t'-p', `${hostPort}:${containerPort}`,\n\t\t]\n\n\t\tif (additionalArgs) {\n\t\t\targs.push(...additionalArgs)\n\t\t}\n\n\t\targs.push(imageName)\n\n\t\tlogger.info(`Running Docker container \"${containerName}\" in foreground (${hostPort}:${containerPort})...`)\n\n\t\tconst stdio = redirectToStderr\n\t\t\t? [process.stdin, process.stderr, process.stderr] as const\n\t\t\t: 'inherit' as const\n\n\t\t// Set up signal forwarding to gracefully shut down the container\n\t\tconst forwardSignal = (signal: string): void => {\n\t\t\tlogger.debug(`Forwarding ${signal} to container \"${containerName}\"`)\n\t\t\tvoid execa('docker', ['kill', `--signal=${signal}`, containerName], { reject: false })\n\t\t}\n\n\t\tconst onSigint = (): void => forwardSignal('SIGINT')\n\t\tconst onSigterm = (): void => forwardSignal('SIGTERM')\n\n\t\tprocess.on('SIGINT', onSigint)\n\t\tprocess.on('SIGTERM', onSigterm)\n\n\t\ttry {\n\t\t\tawait execa('docker', args, { stdio })\n\t\t} finally {\n\t\t\t// Clean up signal handlers to avoid leaks\n\t\t\tprocess.removeListener('SIGINT', onSigint)\n\t\t\tprocess.removeListener('SIGTERM', onSigterm)\n\t\t\trestoreTerminalState()\n\t\t}\n\t}\n\n\t/**\n\t * Stop and remove a container by name using atomic force-remove.\n\t * No-op if the container doesn't exist or is already removed.\n\t * Uses `docker rm -f` which handles both running and stopped containers\n\t * atomically, eliminating race conditions between stop and remove.\n\t *\n\t * @param containerName - Name of the container to remove\n\t * @returns true if a container was removed, false if no container was found\n\t */\n\tstatic async stopAndRemoveContainer(containerName: string): Promise<boolean> {\n\t\tconst isRunning = await DockerManager.isContainerRunning(containerName)\n\n\t\tif (!isRunning) {\n\t\t\tlogger.debug(`No running container found with name \"${containerName}\"`)\n\t\t\t// Still try to remove in case container exists but is stopped\n\t\t\tawait execa('docker', ['rm', '-f', containerName], { reject: false })\n\t\t\treturn false\n\t\t}\n\n\t\tlogger.info(`Removing Docker container \"${containerName}\"...`)\n\n\t\t// Atomic force-remove: handles running and stopped containers\n\t\tawait execa('docker', ['rm', '-f', containerName], { reject: false })\n\n\t\tlogger.success(`Docker container \"${containerName}\" removed`)\n\t\treturn true\n\t}\n\n\t/**\n\t * Check if a named container is currently running.\n\t *\n\t * @param containerName - Name of the container to check\n\t * @returns true if the container is running, false otherwise\n\t */\n\tstatic async isContainerRunning(containerName: string): Promise<boolean> {\n\t\ttry {\n\t\t\tconst result = await execa('docker', [\n\t\t\t\t'ps',\n\t\t\t\t'--filter', `name=^${containerName}$`,\n\t\t\t\t'--format', '{{.Names}}',\n\t\t\t], { reject: false })\n\n\t\t\treturn result.exitCode === 0 && result.stdout.trim() === containerName\n\t\t} catch {\n\t\t\treturn false\n\t\t}\n\t}\n\n\t/**\n\t * Parse the EXPOSE directive from a Dockerfile.\n\t * Returns the last exposed port number (handles multi-stage builds correctly),\n\t * or null if none found.\n\t *\n\t * @param dockerfilePath - Absolute path to the Dockerfile\n\t * @returns Last exposed port number, or null if no EXPOSE directive found\n\t */\n\tstatic async parseExposeFromDockerfile(\n\t\tdockerfilePath: string\n\t): Promise<number | null> {\n\t\treturn parseDockerfileExpose(dockerfilePath)\n\t}\n\n\t/**\n\t * Sanitize a string for use as a Docker container name.\n\t *\n\t * @param name - Raw name to sanitize\n\t * @returns Sanitized container name\n\t */\n\tstatic sanitizeContainerName(name: string): string {\n\t\treturn sanitizeContainerName(name)\n\t}\n\n\t/**\n\t * Build the standard container name for an iloom dev server.\n\t *\n\t * @param identifier - Issue number, branch name, or other identifier\n\t * @returns Sanitized container name in the format \"iloom-dev-{identifier}\"\n\t */\n\tstatic buildContainerName(identifier: string | number): string {\n\t\treturn buildContainerName(identifier)\n\t}\n\n\t/**\n\t * Build the standard image name for an iloom dev server.\n\t *\n\t * @param identifier - Issue number, branch name, or other identifier\n\t * @returns Sanitized image name\n\t */\n\tstatic buildImageName(identifier: string | number): string {\n\t\treturn buildImageName(identifier)\n\t}\n\n\t/**\n\t * Detect exposed ports from a built Docker image using `docker image inspect`.\n\t *\n\t * @param imageName - Name of the built Docker image to inspect\n\t * @returns First exposed port number, or null if none found\n\t */\n\tstatic async inspectImagePorts(imageName: string): Promise<number | null> {\n\t\treturn inspectImagePorts(imageName)\n\t}\n\n\t/**\n\t * Resolve the container port from config, image inspection, or Dockerfile EXPOSE directive.\n\t * Priority: config > docker image inspect > Dockerfile regex\n\t * Throws a clear error if no source provides a port.\n\t *\n\t * @param configPort - Port from settings (containerPort field)\n\t * @param dockerfilePath - Absolute path to the Dockerfile\n\t * @param imageName - Optional built image name for inspection (preferred over Dockerfile regex)\n\t * @returns Resolved container port number\n\t */\n\tstatic async resolveContainerPort(\n\t\tconfigPort: number | undefined,\n\t\tdockerfilePath: string,\n\t\timageName?: string\n\t): Promise<number> {\n\t\tif (configPort !== undefined) {\n\t\t\treturn configPort\n\t\t}\n\n\t\t// Try image inspection first (most accurate - handles multi-stage builds, inherited images)\n\t\tif (imageName) {\n\t\t\tconst inspectedPort = await DockerManager.inspectImagePorts(imageName)\n\t\t\tif (inspectedPort !== null) {\n\t\t\t\tlogger.debug(`Auto-detected container port ${inspectedPort} from Docker image inspect`)\n\t\t\t\treturn inspectedPort\n\t\t\t}\n\t\t}\n\n\t\t// Fall back to Dockerfile regex\n\t\tconst exposedPort = await DockerManager.parseExposeFromDockerfile(dockerfilePath)\n\t\tif (exposedPort !== null) {\n\t\t\tlogger.debug(`Auto-detected container port ${exposedPort} from Dockerfile EXPOSE directive`)\n\t\t\treturn exposedPort\n\t\t}\n\n\t\tthrow new Error(\n\t\t\t`Cannot determine container port. No \"containerPort\" configured in settings and no EXPOSE directive found in ${dockerfilePath}.\\n` +\n\t\t\t\t'Either add an EXPOSE directive to your Dockerfile or set \"containerPort\" in capabilities.web settings.'\n\t\t)\n\t}\n\n\t/**\n\t * Force-remove a container by name (no-op if it doesn't exist).\n\t * Used internally before running a new container to avoid name collisions.\n\t */\n\tprivate static async forceRemoveContainer(containerName: string): Promise<void> {\n\t\tawait execa('docker', ['rm', '-f', containerName], { reject: false })\n\t}\n\n\t/**\n\t * Build a DockerConfig from iloom web settings and an identifier.\n\t * Centralizes the Docker config extraction logic used by dev-server, open, and run commands.\n\t *\n\t * @param webSettings - The capabilities.web section from IloomSettings\n\t * @param identifier - Identifier for container naming (issue number, branch name, etc.)\n\t * @returns DockerConfig if Docker mode is enabled, undefined otherwise\n\t */\n\tstatic buildDockerConfigFromSettings(\n\t\twebSettings: WebSettings | undefined,\n\t\tidentifier: string\n\t): DockerConfig | undefined {\n\t\tif (!webSettings || webSettings.devServer !== 'docker') {\n\t\t\treturn undefined\n\t\t}\n\n\t\treturn {\n\t\t\tdockerFile: webSettings.dockerFile ?? './Dockerfile',\n\t\t\tcontainerPort: webSettings.containerPort,\n\t\t\tdockerBuildArgs: webSettings.dockerBuildArgs,\n\t\t\tdockerRunArgs: webSettings.dockerRunArgs,\n\t\t\tidentifier,\n\t\t}\n\t}\n}\n","import { execa } from 'execa'\nimport { readFile } from 'fs/promises'\n\n/**\n * Maximum length for Docker container names.\n * Docker allows up to 63 characters for container names.\n */\nconst MAX_CONTAINER_NAME_LENGTH = 63\n\n/**\n * Check if Docker CLI is installed.\n * Runs `docker --version` — only checks CLI presence, not daemon status.\n * @returns true if Docker CLI is installed, false otherwise\n */\nexport async function isDockerInstalled(): Promise<boolean> {\n\ttry {\n\t\tconst result = await execa('docker', ['--version'], { reject: false })\n\t\treturn result.exitCode === 0\n\t} catch {\n\t\treturn false\n\t}\n}\n\n/**\n * Check if the Docker daemon is running.\n * Runs `docker info` — requires the daemon to be running.\n * @returns true if Docker daemon is running, false otherwise\n */\nexport async function isDockerRunning(): Promise<boolean> {\n\ttry {\n\t\tconst result = await execa('docker', ['info'], { reject: false })\n\t\treturn result.exitCode === 0\n\t} catch {\n\t\treturn false\n\t}\n}\n\n/**\n * Assert that Docker is installed and the daemon is running.\n * Throws a clear, actionable error if either check fails.\n */\nexport async function assertDockerAvailable(): Promise<void> {\n\tconst installed = await isDockerInstalled()\n\tif (!installed) {\n\t\tthrow new Error(\n\t\t\t'Docker is not installed. Please install Docker to use this feature.\\n' +\n\t\t\t\t'Install Docker: https://docs.docker.com/get-docker/'\n\t\t)\n\t}\n\n\tconst running = await isDockerRunning()\n\tif (!running) {\n\t\tthrow new Error(\n\t\t\t'Docker daemon is not running. Please start Docker and try again.\\n' +\n\t\t\t\t'On macOS/Windows: Open the Docker Desktop application.\\n' +\n\t\t\t\t'On Linux: Run `sudo systemctl start docker`'\n\t\t)\n\t}\n}\n\n/**\n * Parse the EXPOSE directive from a Dockerfile.\n * Returns the LAST exposed port number (handles multi-stage builds correctly),\n * or null if none found.\n *\n * Handles formats like:\n * - EXPOSE 4200\n * - EXPOSE 4200/tcp\n * - EXPOSE 8080/udp\n * - Ignores comment lines (# EXPOSE ...)\n *\n * @param dockerfilePath - Absolute path to the Dockerfile\n * @returns Last exposed port number, or null if no valid EXPOSE directive found\n */\nexport async function parseDockerfileExpose(\n\tdockerfilePath: string\n): Promise<number | null> {\n\ttry {\n\t\tconst content = await readFile(dockerfilePath, 'utf-8')\n\t\tconst lines = content.split('\\n')\n\t\tlet lastPort: number | null = null\n\n\t\tfor (const line of lines) {\n\t\t\tconst trimmed = line.trim()\n\t\t\t// Skip comment lines\n\t\t\tif (trimmed.startsWith('#')) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tconst match = /^EXPOSE\\s+(\\d+)/i.exec(trimmed)\n\t\t\tif (match?.[1]) {\n\t\t\t\tconst port = parseInt(match[1], 10)\n\t\t\t\tif (!isNaN(port) && port >= 1 && port <= 65535) {\n\t\t\t\t\tlastPort = port\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn lastPort\n\t} catch {\n\t\treturn null\n\t}\n}\n\n/**\n * Detect exposed ports from a built Docker image using `docker image inspect`.\n * Returns the first exposed port number, or null if none found.\n *\n * @param imageName - Name of the built Docker image to inspect\n * @returns First exposed port number, or null if none found\n */\nexport async function inspectImagePorts(imageName: string): Promise<number | null> {\n\ttry {\n\t\tconst result = await execa('docker', [\n\t\t\t'image', 'inspect', imageName,\n\t\t\t'--format', '{{json .Config.ExposedPorts}}',\n\t\t], { reject: false })\n\n\t\tif (result.exitCode !== 0 || !result.stdout.trim()) {\n\t\t\treturn null\n\t\t}\n\n\t\tconst output = result.stdout.trim()\n\t\t// Output format: {\"4200/tcp\":{},\"8080/tcp\":{}} or null\n\t\tif (output === 'null' || output === '<nil>' || output === '{}') {\n\t\t\treturn null\n\t\t}\n\n\t\tconst parsed: unknown = JSON.parse(output)\n\t\tif (typeof parsed !== 'object' || parsed === null) {\n\t\t\treturn null\n\t\t}\n\n\t\t// Get first key (e.g., \"4200/tcp\") and extract port number\n\t\tconst firstKey = Object.keys(parsed)[0]\n\t\tif (!firstKey) {\n\t\t\treturn null\n\t\t}\n\n\t\tconst portMatch = /^(\\d+)/.exec(firstKey)\n\t\tif (portMatch?.[1]) {\n\t\t\tconst port = parseInt(portMatch[1], 10)\n\t\t\tif (!isNaN(port) && port >= 1 && port <= 65535) {\n\t\t\t\treturn port\n\t\t\t}\n\t\t}\n\n\t\treturn null\n\t} catch {\n\t\treturn null\n\t}\n}\n\n/**\n * Sanitize a string for use as a Docker container name.\n *\n * Docker container names must match: [a-zA-Z0-9][a-zA-Z0-9_.-]\n * - Replace slashes, spaces, and other invalid characters with hyphens\n * - Ensure the name starts with an alphanumeric character\n * - Truncate to a maximum of 63 characters\n *\n * @param name - Raw name to sanitize\n * @returns Sanitized container name\n */\nexport function sanitizeContainerName(name: string | number): string {\n\tlet sanitized = String(name)\n\n\t// Replace any character that isn't alphanumeric, underscore, dot, or hyphen with a hyphen\n\tsanitized = sanitized.replace(/[^a-zA-Z0-9_.-]/g, '-')\n\n\t// Collapse consecutive hyphens into one\n\tsanitized = sanitized.replace(/-{2,}/g, '-')\n\n\t// Remove leading non-alphanumeric characters (Docker requires starting with [a-zA-Z0-9])\n\tsanitized = sanitized.replace(/^[^a-zA-Z0-9]+/, '')\n\n\t// Remove trailing hyphens\n\tsanitized = sanitized.replace(/-+$/, '')\n\n\t// Truncate to max length\n\tif (sanitized.length > MAX_CONTAINER_NAME_LENGTH) {\n\t\tsanitized = sanitized.substring(0, MAX_CONTAINER_NAME_LENGTH)\n\t\t// Remove trailing hyphen if truncation created one\n\t\tsanitized = sanitized.replace(/-+$/, '')\n\t}\n\n\t// Fallback if everything was stripped\n\tif (sanitized.length === 0) {\n\t\tsanitized = 'iloom-container'\n\t}\n\n\treturn sanitized\n}\n\n/**\n * Build the standard container name for an iloom dev server.\n *\n * @param identifier - Issue number, branch name, or other identifier\n * @returns Sanitized container name in the format \"iloom-dev-{identifier}\"\n */\nexport function buildContainerName(identifier: string | number): string {\n\treturn sanitizeContainerName(`iloom-dev-${identifier}`)\n}\n\n/**\n * Build the standard image name for an iloom dev server.\n *\n * @param identifier - Issue number, branch name, or other identifier\n * @returns Sanitized image name in the format \"iloom-dev-{identifier}\"\n */\nexport function buildImageName(identifier: string | number): string {\n\t// Docker image tags must be lowercase\n\treturn sanitizeContainerName(`iloom-dev-${identifier}`).toLowerCase()\n}\n"],"mappings":"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","names":["execa","execa"]}