@iloom/cli 0.10.0 → 0.10.2

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 (157) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +2 -2
  3. package/dist/{BranchNamingService-ECJHBB67.js → BranchNamingService-4OP6LOH6.js} +2 -2
  4. package/dist/ClaudeContextManager-ZKTUVQB2.js +14 -0
  5. package/dist/ClaudeService-TRWOYQ6O.js +13 -0
  6. package/dist/{LoomLauncher-L64HHS3T.js → LoomLauncher-FRECYMXS.js} +6 -6
  7. package/dist/{PromptTemplateManager-DULSVRRE.js → PromptTemplateManager-YOE2SIPG.js} +2 -2
  8. package/dist/README.md +2 -2
  9. package/dist/{SettingsManager-BQDQA3FK.js → SettingsManager-FNKCOZMQ.js} +2 -2
  10. package/dist/{build-5GO3XW26.js → build-VHGEMXBA.js} +6 -6
  11. package/dist/{chunk-ZW2LKWWE.js → chunk-2VEWSM34.js} +3 -3
  12. package/dist/{chunk-LXLMMXXY.js → chunk-2YZCWAVZ.js} +17 -12
  13. package/dist/chunk-2YZCWAVZ.js.map +1 -0
  14. package/dist/{chunk-UD3WJDIV.js → chunk-3F27M7ZD.js} +11 -774
  15. package/dist/chunk-3F27M7ZD.js.map +1 -0
  16. package/dist/chunk-4E7LCFUG.js +24 -0
  17. package/dist/chunk-4E7LCFUG.js.map +1 -0
  18. package/dist/{chunk-MNHZB4Z2.js → chunk-4FGEGQW4.js} +3 -3
  19. package/dist/{chunk-WY4QBK43.js → chunk-63QWFWH3.js} +2 -2
  20. package/dist/{chunk-YYAKPQBT.js → chunk-7VHJNVLF.js} +19 -9
  21. package/dist/chunk-7VHJNVLF.js.map +1 -0
  22. package/dist/chunk-BFHDVFSK.js +651 -0
  23. package/dist/chunk-BFHDVFSK.js.map +1 -0
  24. package/dist/{chunk-FB47TIJG.js → chunk-BFLMCE2U.js} +4 -23
  25. package/dist/chunk-BFLMCE2U.js.map +1 -0
  26. package/dist/{chunk-SN3SQCFK.js → chunk-BU53XIGY.js} +4 -4
  27. package/dist/{chunk-SF2P22EE.js → chunk-C6HNNJIV.js} +2 -2
  28. package/dist/{chunk-5MWV33NN.js → chunk-CVCTIDDK.js} +2 -2
  29. package/dist/{chunk-ZEWU5PZK.js → chunk-G5V75JD5.js} +2 -2
  30. package/dist/{chunk-ZHPNZC75.js → chunk-HYGUPUV5.js} +26 -21
  31. package/dist/chunk-HYGUPUV5.js.map +1 -0
  32. package/dist/{chunk-VGGST52X.js → chunk-I5T677EA.js} +2 -2
  33. package/dist/{chunk-VECNX6VX.js → chunk-KIK2ZFAL.js} +2 -2
  34. package/dist/{chunk-3D7WQM7I.js → chunk-LLHXQS3C.js} +2 -2
  35. package/dist/{chunk-Y4YZTHZE.js → chunk-LUKXJSRI.js} +2 -2
  36. package/dist/{chunk-ONQYPICO.js → chunk-PZ5WSR5Z.js} +63 -9
  37. package/dist/chunk-PZ5WSR5Z.js.map +1 -0
  38. package/dist/{chunk-J5S7DFYC.js → chunk-QFTDZ5E3.js} +2 -2
  39. package/dist/chunk-RJ3VBUFK.js +781 -0
  40. package/dist/chunk-RJ3VBUFK.js.map +1 -0
  41. package/dist/{chunk-UWGVCXRF.js → chunk-SKSYYBCU.js} +23 -1
  42. package/dist/chunk-SKSYYBCU.js.map +1 -0
  43. package/dist/{chunk-JO2LZ6EQ.js → chunk-SWSJWA2S.js} +2 -2
  44. package/dist/{chunk-4WJNIR5O.js → chunk-UUEW5KWB.js} +1 -1
  45. package/dist/chunk-UUEW5KWB.js.map +1 -0
  46. package/dist/{chunk-6EU6TCF6.js → chunk-V3SVMFDQ.js} +5 -5
  47. package/dist/{chunk-NRSWLOAZ.js → chunk-WXIM2WS7.js} +4 -4
  48. package/dist/{chunk-RYWFS37M.js → chunk-XE4BDRZD.js} +2 -2
  49. package/dist/{ignite-CGOV3TD4.js → chunk-ZGM2FE2R.js} +105 -73
  50. package/dist/chunk-ZGM2FE2R.js.map +1 -0
  51. package/dist/{claude-P3NQR6IJ.js → claude-LN7OWVNI.js} +2 -2
  52. package/dist/{cleanup-6UCPVMFG.js → cleanup-4ZM2AJDC.js} +19 -17
  53. package/dist/{cleanup-6UCPVMFG.js.map → cleanup-4ZM2AJDC.js.map} +1 -1
  54. package/dist/cli.js +167 -614
  55. package/dist/cli.js.map +1 -1
  56. package/dist/{commit-L3EPY5QG.js → commit-4CFLXRZ3.js} +12 -10
  57. package/dist/commit-4CFLXRZ3.js.map +1 -0
  58. package/dist/{compile-ZS4HYRX5.js → compile-7ALJHZ4N.js} +6 -6
  59. package/dist/{contribute-ORDDQGSL.js → contribute-5GKLK3BQ.js} +3 -3
  60. package/dist/{dev-server-FYZ2AQIH.js → dev-server-7SMIB7OF.js} +8 -8
  61. package/dist/{feedback-TMBXSCM5.js → feedback-EZWF5CAL.js} +10 -8
  62. package/dist/{feedback-TMBXSCM5.js.map → feedback-EZWF5CAL.js.map} +1 -1
  63. package/dist/{git-ET64COO3.js → git-GTLKAZRJ.js} +3 -3
  64. package/dist/ignite-MQETGFNA.js +34 -0
  65. package/dist/ignite-MQETGFNA.js.map +1 -0
  66. package/dist/index.d.ts +113 -18
  67. package/dist/index.js +180 -15
  68. package/dist/index.js.map +1 -1
  69. package/dist/{init-GFQ5W7GK.js → init-ZB2RITW6.js} +8 -8
  70. package/dist/install-deps-RLSGSHH7.js +43 -0
  71. package/dist/install-deps-RLSGSHH7.js.map +1 -0
  72. package/dist/{issues-T4ZZSPEG.js → issues-4UUAQ5K6.js} +3 -3
  73. package/dist/{lint-6TQXDZ3T.js → lint-AAN2NZWG.js} +6 -6
  74. package/dist/mcp/harness-server.js +140 -0
  75. package/dist/mcp/harness-server.js.map +1 -0
  76. package/dist/mcp/issue-management-server.js +140 -18
  77. package/dist/mcp/issue-management-server.js.map +1 -1
  78. package/dist/{open-5QZGXQRF.js → open-FXWW3VI4.js} +8 -8
  79. package/dist/{plan-U7ZQWLFY.js → plan-D3KSN5MU.js} +338 -36
  80. package/dist/plan-D3KSN5MU.js.map +1 -0
  81. package/dist/prompts/CLAUDE.md +2 -2
  82. package/dist/prompts/init-prompt.txt +102 -27
  83. package/dist/prompts/issue-prompt.txt +46 -0
  84. package/dist/prompts/plan-prompt.txt +59 -19
  85. package/dist/prompts/swarm-orchestrator-prompt.txt +121 -80
  86. package/dist/{rebase-DWIB77KV.js → rebase-62FDLIH4.js} +17 -8
  87. package/dist/rebase-62FDLIH4.js.map +1 -0
  88. package/dist/{recap-MX63HAKV.js → recap-OMBOKJST.js} +6 -6
  89. package/dist/{run-O3TFNQFC.js → run-BBXLRIZB.js} +8 -8
  90. package/dist/schema/settings.schema.json +36 -2
  91. package/dist/{shell-G6VC2CYR.js → shell-RF7LTND5.js} +5 -5
  92. package/dist/{summary-FWHAX55O.js → summary-YZI25KW4.js} +9 -9
  93. package/dist/{test-F7JNJZYP.js → test-SGO6I5Z7.js} +6 -6
  94. package/dist/{test-git-BTAOIUE2.js → test-git-XM4TM65W.js} +3 -3
  95. package/dist/{test-jira-CHYNV33F.js → test-jira-LDTOYFSD.js} +3 -3
  96. package/dist/{test-prefix-Q6TFSU6F.js → test-prefix-GBO37XCN.js} +3 -3
  97. package/dist/{test-webserver-EONCG7E7.js → test-webserver-NZ3JTVLL.js} +5 -5
  98. package/dist/{vscode-VA5X4P25.js → vscode-6XUGHJKL.js} +5 -5
  99. package/package.json +1 -1
  100. package/dist/ClaudeContextManager-QXX6ZFST.js +0 -14
  101. package/dist/ClaudeService-NJNK2SUH.js +0 -13
  102. package/dist/chunk-4WJNIR5O.js.map +0 -1
  103. package/dist/chunk-FB47TIJG.js.map +0 -1
  104. package/dist/chunk-LXLMMXXY.js.map +0 -1
  105. package/dist/chunk-ONQYPICO.js.map +0 -1
  106. package/dist/chunk-UD3WJDIV.js.map +0 -1
  107. package/dist/chunk-UVD4CZKS.js +0 -101
  108. package/dist/chunk-UVD4CZKS.js.map +0 -1
  109. package/dist/chunk-UWGVCXRF.js.map +0 -1
  110. package/dist/chunk-YYAKPQBT.js.map +0 -1
  111. package/dist/chunk-ZHPNZC75.js.map +0 -1
  112. package/dist/commit-L3EPY5QG.js.map +0 -1
  113. package/dist/ignite-CGOV3TD4.js.map +0 -1
  114. package/dist/plan-U7ZQWLFY.js.map +0 -1
  115. package/dist/rebase-DWIB77KV.js.map +0 -1
  116. /package/dist/{BranchNamingService-ECJHBB67.js.map → BranchNamingService-4OP6LOH6.js.map} +0 -0
  117. /package/dist/{ClaudeContextManager-QXX6ZFST.js.map → ClaudeContextManager-ZKTUVQB2.js.map} +0 -0
  118. /package/dist/{ClaudeService-NJNK2SUH.js.map → ClaudeService-TRWOYQ6O.js.map} +0 -0
  119. /package/dist/{LoomLauncher-L64HHS3T.js.map → LoomLauncher-FRECYMXS.js.map} +0 -0
  120. /package/dist/{PromptTemplateManager-DULSVRRE.js.map → PromptTemplateManager-YOE2SIPG.js.map} +0 -0
  121. /package/dist/{SettingsManager-BQDQA3FK.js.map → SettingsManager-FNKCOZMQ.js.map} +0 -0
  122. /package/dist/{build-5GO3XW26.js.map → build-VHGEMXBA.js.map} +0 -0
  123. /package/dist/{chunk-ZW2LKWWE.js.map → chunk-2VEWSM34.js.map} +0 -0
  124. /package/dist/{chunk-MNHZB4Z2.js.map → chunk-4FGEGQW4.js.map} +0 -0
  125. /package/dist/{chunk-WY4QBK43.js.map → chunk-63QWFWH3.js.map} +0 -0
  126. /package/dist/{chunk-SN3SQCFK.js.map → chunk-BU53XIGY.js.map} +0 -0
  127. /package/dist/{chunk-SF2P22EE.js.map → chunk-C6HNNJIV.js.map} +0 -0
  128. /package/dist/{chunk-5MWV33NN.js.map → chunk-CVCTIDDK.js.map} +0 -0
  129. /package/dist/{chunk-ZEWU5PZK.js.map → chunk-G5V75JD5.js.map} +0 -0
  130. /package/dist/{chunk-VGGST52X.js.map → chunk-I5T677EA.js.map} +0 -0
  131. /package/dist/{chunk-VECNX6VX.js.map → chunk-KIK2ZFAL.js.map} +0 -0
  132. /package/dist/{chunk-3D7WQM7I.js.map → chunk-LLHXQS3C.js.map} +0 -0
  133. /package/dist/{chunk-Y4YZTHZE.js.map → chunk-LUKXJSRI.js.map} +0 -0
  134. /package/dist/{chunk-J5S7DFYC.js.map → chunk-QFTDZ5E3.js.map} +0 -0
  135. /package/dist/{chunk-JO2LZ6EQ.js.map → chunk-SWSJWA2S.js.map} +0 -0
  136. /package/dist/{chunk-6EU6TCF6.js.map → chunk-V3SVMFDQ.js.map} +0 -0
  137. /package/dist/{chunk-NRSWLOAZ.js.map → chunk-WXIM2WS7.js.map} +0 -0
  138. /package/dist/{chunk-RYWFS37M.js.map → chunk-XE4BDRZD.js.map} +0 -0
  139. /package/dist/{claude-P3NQR6IJ.js.map → claude-LN7OWVNI.js.map} +0 -0
  140. /package/dist/{compile-ZS4HYRX5.js.map → compile-7ALJHZ4N.js.map} +0 -0
  141. /package/dist/{contribute-ORDDQGSL.js.map → contribute-5GKLK3BQ.js.map} +0 -0
  142. /package/dist/{dev-server-FYZ2AQIH.js.map → dev-server-7SMIB7OF.js.map} +0 -0
  143. /package/dist/{git-ET64COO3.js.map → git-GTLKAZRJ.js.map} +0 -0
  144. /package/dist/{init-GFQ5W7GK.js.map → init-ZB2RITW6.js.map} +0 -0
  145. /package/dist/{issues-T4ZZSPEG.js.map → issues-4UUAQ5K6.js.map} +0 -0
  146. /package/dist/{lint-6TQXDZ3T.js.map → lint-AAN2NZWG.js.map} +0 -0
  147. /package/dist/{open-5QZGXQRF.js.map → open-FXWW3VI4.js.map} +0 -0
  148. /package/dist/{recap-MX63HAKV.js.map → recap-OMBOKJST.js.map} +0 -0
  149. /package/dist/{run-O3TFNQFC.js.map → run-BBXLRIZB.js.map} +0 -0
  150. /package/dist/{shell-G6VC2CYR.js.map → shell-RF7LTND5.js.map} +0 -0
  151. /package/dist/{summary-FWHAX55O.js.map → summary-YZI25KW4.js.map} +0 -0
  152. /package/dist/{test-F7JNJZYP.js.map → test-SGO6I5Z7.js.map} +0 -0
  153. /package/dist/{test-git-BTAOIUE2.js.map → test-git-XM4TM65W.js.map} +0 -0
  154. /package/dist/{test-jira-CHYNV33F.js.map → test-jira-LDTOYFSD.js.map} +0 -0
  155. /package/dist/{test-prefix-Q6TFSU6F.js.map → test-prefix-GBO37XCN.js.map} +0 -0
  156. /package/dist/{test-webserver-EONCG7E7.js.map → test-webserver-NZ3JTVLL.js.map} +0 -0
  157. /package/dist/{vscode-VA5X4P25.js.map → vscode-6XUGHJKL.js.map} +0 -0
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  IssueManagementProviderFactory
4
- } from "./chunk-JO2LZ6EQ.js";
4
+ } from "./chunk-SWSJWA2S.js";
5
5
  import "./chunk-4232AHNQ.js";
6
6
  import {
7
7
  SettingsManager
8
- } from "./chunk-YYAKPQBT.js";
8
+ } from "./chunk-7VHJNVLF.js";
9
9
  import "./chunk-HEXKPKCK.js";
10
10
  import "./chunk-VG45TUYK.js";
11
11
  import "./chunk-6MLEBAYZ.js";
@@ -93,4 +93,4 @@ var TestJiraCommand = class {
93
93
  export {
94
94
  TestJiraCommand
95
95
  };
96
- //# sourceMappingURL=test-jira-CHYNV33F.js.map
96
+ //# sourceMappingURL=test-jira-LDTOYFSD.js.map
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  generateWorktreePath
4
- } from "./chunk-MNHZB4Z2.js";
4
+ } from "./chunk-4FGEGQW4.js";
5
5
  import {
6
6
  SettingsManager
7
- } from "./chunk-YYAKPQBT.js";
7
+ } from "./chunk-7VHJNVLF.js";
8
8
  import "./chunk-KB64WNBZ.js";
9
9
  import "./chunk-6MLEBAYZ.js";
10
10
  import {
@@ -67,4 +67,4 @@ var TestPrefixCommand = class {
67
67
  export {
68
68
  TestPrefixCommand
69
69
  };
70
- //# sourceMappingURL=test-prefix-Q6TFSU6F.js.map
70
+ //# sourceMappingURL=test-prefix-GBO37XCN.js.map
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ProcessManager
4
- } from "./chunk-ZEWU5PZK.js";
5
- import "./chunk-3D7WQM7I.js";
6
- import "./chunk-MNHZB4Z2.js";
4
+ } from "./chunk-G5V75JD5.js";
5
+ import "./chunk-LLHXQS3C.js";
6
+ import "./chunk-4FGEGQW4.js";
7
7
  import {
8
8
  SettingsManager
9
- } from "./chunk-YYAKPQBT.js";
9
+ } from "./chunk-7VHJNVLF.js";
10
10
  import "./chunk-KB64WNBZ.js";
11
11
  import "./chunk-6MLEBAYZ.js";
12
12
  import {
@@ -81,4 +81,4 @@ var TestWebserverCommand = class {
81
81
  export {
82
82
  TestWebserverCommand
83
83
  };
84
- //# sourceMappingURL=test-webserver-EONCG7E7.js.map
84
+ //# sourceMappingURL=test-webserver-NZ3JTVLL.js.map
@@ -1,18 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  IdentifierParser
4
- } from "./chunk-WY4QBK43.js";
4
+ } from "./chunk-63QWFWH3.js";
5
5
  import {
6
6
  GitWorktreeManager
7
- } from "./chunk-VGGST52X.js";
7
+ } from "./chunk-I5T677EA.js";
8
8
  import {
9
9
  getInstallHint,
10
10
  isIdeAvailable
11
11
  } from "./chunk-O7VL5N6S.js";
12
12
  import {
13
13
  extractIssueNumber
14
- } from "./chunk-MNHZB4Z2.js";
15
- import "./chunk-YYAKPQBT.js";
14
+ } from "./chunk-4FGEGQW4.js";
15
+ import "./chunk-7VHJNVLF.js";
16
16
  import "./chunk-KB64WNBZ.js";
17
17
  import "./chunk-6MLEBAYZ.js";
18
18
  import {
@@ -161,4 +161,4 @@ var VSCodeCommand = class {
161
161
  export {
162
162
  VSCodeCommand
163
163
  };
164
- //# sourceMappingURL=vscode-VA5X4P25.js.map
164
+ //# sourceMappingURL=vscode-6XUGHJKL.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iloom/cli",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
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,14 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- ClaudeContextManager
4
- } from "./chunk-RYWFS37M.js";
5
- import "./chunk-SN3SQCFK.js";
6
- import "./chunk-ONQYPICO.js";
7
- import "./chunk-4WJNIR5O.js";
8
- import "./chunk-YYAKPQBT.js";
9
- import "./chunk-6MLEBAYZ.js";
10
- import "./chunk-VT4PDUYT.js";
11
- export {
12
- ClaudeContextManager
13
- };
14
- //# sourceMappingURL=ClaudeContextManager-QXX6ZFST.js.map
@@ -1,13 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- ClaudeService
4
- } from "./chunk-SN3SQCFK.js";
5
- import "./chunk-ONQYPICO.js";
6
- import "./chunk-4WJNIR5O.js";
7
- import "./chunk-YYAKPQBT.js";
8
- import "./chunk-6MLEBAYZ.js";
9
- import "./chunk-VT4PDUYT.js";
10
- export {
11
- ClaudeService
12
- };
13
- //# sourceMappingURL=ClaudeService-NJNK2SUH.js.map
@@ -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 github-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\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\tSWARM_AGENT_METADATA?: string // JSON string mapping agent names to { model, tools } for claude -p commands\n\tNO_CLEANUP?: boolean // True when child loom cleanup should be skipped (e.g., manual cleanup later)\n\tISSUE_PREFIX?: string // \"#\" for GitHub, \"\" for Linear/Jira — used in commit message templates\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(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\tvariables.ENHANCER_REVIEW_ENABLED = agents?.['iloom-issue-enhancer']?.review === true\n\tvariables.ANALYZER_REVIEW_ENABLED = agents?.['iloom-issue-analyzer']?.review === true\n\tvariables.PLANNER_REVIEW_ENABLED = agents?.['iloom-issue-planner']?.review === true\n\tvariables.ANALYZE_AND_PLAN_REVIEW_ENABLED = agents?.['iloom-issue-analyze-and-plan']?.review === true\n\tvariables.IMPLEMENTER_REVIEW_ENABLED = agents?.['iloom-issue-implementer']?.review === true\n\tvariables.COMPLEXITY_REVIEW_ENABLED = agents?.['iloom-issue-complexity-evaluator']?.review === true\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/IssueEnhancementService.ts","../src/utils/text.ts"],"sourcesContent":["import type { IssueTracker } from './IssueTracker.js'\nimport type { AgentManager } from './AgentManager.js'\nimport type { SettingsManager } from './SettingsManager.js'\nimport type { TemplateVariables } from './PromptTemplateManager.js'\nimport { launchClaude } from '../utils/claude.js'\nimport { openBrowser } from '../utils/browser.js'\nimport { waitForKeypress } from '../utils/prompt.js'\nimport { getLogger } from '../utils/logger-context.js'\nimport { generateIssueManagementMcpConfig } from '../utils/mcp.js'\n\n/**\n * Options for enhancing an existing issue\n */\nexport interface EnhanceExistingIssueOptions {\n\t/** GitHub username of issue author for tagging in questions */\n\tauthor?: string\n\t/** Repository in \"owner/repo\" format */\n\trepo?: string\n}\n\n/**\n * Result of enhancing an existing issue\n */\nexport interface EnhanceExistingIssueResult {\n\t/** Whether the issue was enhanced */\n\tenhanced: boolean\n\t/** URL of the comment if enhancement occurred */\n\turl?: string\n}\n\n/**\n * Service for enhancing and creating issues with AI assistance.\n * Extracts reusable issue enhancement logic from StartCommand.\n */\nexport class IssueEnhancementService {\n\tconstructor(\n\t\tprivate issueTrackerService: IssueTracker,\n\t\tprivate agentManager: AgentManager,\n\t\tprivate settingsManager: SettingsManager\n\t) {\n\t\t// No-op - logger now uses AsyncLocalStorage context\n\t}\n\n\t/**\n\t * Expose issue tracker for provider checks\n\t */\n\tpublic get issueTracker(): IssueTracker {\n\t\treturn this.issueTrackerService\n\t}\n\n\t/**\n\t * Validates that a description meets minimum requirements.\n\t *\n\t * When hasBody is false (default): Strict validation - >30 characters AND >2 spaces\n\t * When hasBody is true: Relaxed validation - only requires non-empty description\n\t *\n\t * @param description - The description text to validate\n\t * @param hasBody - If true, skip strict validation (only require non-empty)\n\t */\n\tpublic validateDescription(description: string, hasBody = false): boolean {\n\t\tconst trimmedDescription = description.trim()\n\n\t\t// When --body is provided, only require non-empty description\n\t\tif (hasBody) {\n\t\t\treturn trimmedDescription.length > 0\n\t\t}\n\n\t\t// Standard validation: >30 chars AND >2 spaces\n\t\tconst spaceCount = (trimmedDescription.match(/ /g) ?? []).length\n\t\treturn trimmedDescription.length > 30 && spaceCount > 2\n\t}\n\n\t/**\n\t * Enhances a description using Claude Code in headless mode.\n\t * Falls back to original description if enhancement fails.\n\t */\n\tpublic async enhanceDescription(description: string): Promise<string> {\n\t\ttry {\n\t\t\tgetLogger().info('Enhancing description with Claude Code. This may take a moment...')\n\n\t\t\t// Load only the enhancer agent with template variables so Handlebars expressions resolve\n\t\t\tconst settings = await this.settingsManager.loadSettings()\n\t\t\tconst templateVariables: TemplateVariables = {\n\t\t\t\tSTANDARD_ISSUE_MODE: true,\n\t\t\t\tDIRECT_PROMPT_MODE: true,\n\t\t\t}\n\t\t\tconst loadedAgents = await this.agentManager.loadAgents(\n\t\t\t\tsettings,\n\t\t\t\ttemplateVariables,\n\t\t\t\t['iloom-issue-enhancer.md']\n\t\t\t)\n\t\t\tconst agents = this.agentManager.formatForCli(loadedAgents)\n\n\t\t\t// Call Claude in headless mode with issue enhancer agent\n\t\t\tconst prompt = `@agent-iloom-issue-enhancer\n\nTASK: Enhance the following issue description for the issue tracker.\n\nINPUT:\n${description}\n\nOUTPUT REQUIREMENTS:\n- Return ONLY the enhanced description markdown text\n- Use GitHub-Flavored Markdown syntax ONLY\n- NEVER use Jira Wiki format (e.g., {code}, h1., *bold*, {quote}, [link|url])\n- NO meta-commentary (no \"Here is...\", \"The enhanced...\", \"I have...\", etc)\n- NO code block markers (\\`\\`\\`)\n- NO conversational framing or acknowledgments\n- NO explanations of your work\n- Start your response immediately with the enhanced content\n\nYour response should be the raw markdown that will become the issue body.`\n\n\t\t\tconst enhanced = await launchClaude(prompt, {\n\t\t\t\theadless: true,\n\t\t\t\tmodel: 'sonnet',\n\t\t\t\tagents,\n\t\t\t\tnoSessionPersistence: true, // Utility operation - don't persist session\n\t\t\t})\n\n\t\t\tif (enhanced && typeof enhanced === 'string') {\n\t\t\t\tgetLogger().success('Description enhanced successfully')\n\t\t\t\treturn enhanced\n\t\t\t}\n\n\t\t\t// Fallback to original description\n\t\t\tgetLogger().warn('Claude enhancement returned empty result, using original description')\n\t\t\treturn description\n\t\t} catch (error) {\n\t\t\tgetLogger().warn(`Failed to enhance description: ${error instanceof Error ? error.message : 'Unknown error'}`)\n\t\t\treturn description\n\t\t}\n\t}\n\n\t/**\n\t * Creates a GitHub issue with title and enhanced body.\n\t * @param originalDescription - Used as the issue title\n\t * @param enhancedDescription - Used as the issue body\n\t * @param repository - Optional repository override (format: \"owner/repo\")\n\t * @param labels - Optional array of label names to add to the issue\n\t * @returns Issue number and URL\n\t */\n\tpublic async createEnhancedIssue(\n\t\toriginalDescription: string,\n\t\tenhancedDescription: string,\n\t\trepository?: string,\n\t\tlabels?: string[]\n\t): Promise<{ number: string | number; url: string }> {\n\t\tgetLogger().info('Creating issue from description...')\n\n\t\tconst result = await this.issueTrackerService.createIssue(\n\t\t\toriginalDescription, // Use original description as title\n\t\t\tenhancedDescription, // Use enhanced description as body\n\t\t\trepository,\n\t\t\tlabels\n\t\t)\n\n\t\treturn result\n\t}\n\n\t/**\n\t * Waits for user keypress and opens issue in browser for review.\n\t * @param issueNumber - Issue number to open for review\n\t * @param confirm - If true, wait for additional keypress after opening browser before returning\n\t * @param repository - Optional repository to fetch issue from (format: \"owner/repo\")\n\t */\n\tpublic async waitForReviewAndOpen(issueNumber: string | number, confirm = false, repository?: string): Promise<void> {\n\t\t// Check if running in non-interactive environment (CI or no TTY)\n\t\tconst isCI = process.env.CI === 'true'\n\t\tconst isNonInteractive = isCI || !process.stdin.isTTY\n\n\t\tif (isNonInteractive) {\n\t\t\t// In non-interactive environment: Skip all interactive operations\n\t\t\tgetLogger().info(`Running in non-interactive environment - skipping interactive prompts for issue #${issueNumber}`)\n\t\t\treturn\n\t\t}\n\n\t\t// Get issue URL\n\t\tconst issueUrl = await this.issueTrackerService.getIssueUrl(issueNumber, repository)\n\n\t\t// Display message and wait for first keypress\n\t\tconst message = `Created issue #${issueNumber}.\nReview and edit the issue in your browser if needed.\nPress any key to open issue for editing...`\n\t\tawait waitForKeypress(message)\n\n\t\t// Open issue in browser\n\t\tawait openBrowser(issueUrl)\n\n\t\t// If confirmation required, wait for second keypress\n\t\tif (confirm) {\n\t\t\tawait waitForKeypress('Press any key to continue with loom creation...')\n\t\t}\n\t}\n\n\t/**\n\t * Enhances an existing issue using the issue enhancer agent.\n\t * This method encapsulates the Claude invocation, MCP config generation,\n\t * and response parsing for enhancing existing issues.\n\t *\n\t * @param issueNumber - The issue number to enhance\n\t * @param options - Optional enhancement options (author, repo)\n\t * @returns Result indicating whether enhancement occurred and the comment URL if so\n\t */\n\tpublic async enhanceExistingIssue(\n\t\tissueNumber: string | number,\n\t\toptions?: EnhanceExistingIssueOptions\n\t): Promise<EnhanceExistingIssueResult> {\n\t\tconst { author, repo } = options ?? {}\n\n\t\t// Load only the enhancer agent with template variables so Handlebars expressions resolve\n\t\tconst settings = await this.settingsManager.loadSettings()\n\t\tconst templateVariables: TemplateVariables = {\n\t\t\tISSUE_NUMBER: issueNumber,\n\t\t\tSTANDARD_ISSUE_MODE: true,\n\t\t}\n\t\tconst loadedAgents = await this.agentManager.loadAgents(\n\t\t\tsettings,\n\t\t\ttemplateVariables,\n\t\t\t['iloom-issue-enhancer.md']\n\t\t)\n\t\tconst agents = this.agentManager.formatForCli(loadedAgents)\n\n\t\t// Generate MCP config and tool filtering for issue management\n\t\tlet mcpConfig: Record<string, unknown>[] | undefined\n\t\tlet allowedTools: string[] | undefined\n\t\tlet disallowedTools: string[] | undefined\n\n\t\ttry {\n\t\t\tconst provider = this.issueTrackerService.providerName as 'github' | 'linear'\n\t\t\tmcpConfig = await generateIssueManagementMcpConfig('issue', repo, provider, settings)\n\t\t\tgetLogger().debug('Generated MCP configuration for issue management:', { mcpConfig })\n\n\t\t\t// Configure tool filtering for issue workflows\n\t\t\tallowedTools = [\n\t\t\t\t'mcp__issue_management__get_issue',\n\t\t\t\t'mcp__issue_management__get_comment',\n\t\t\t\t'mcp__issue_management__create_comment',\n\t\t\t\t'mcp__issue_management__update_comment',\n\t\t\t\t'mcp__issue_management__create_issue',\n\t\t\t]\n\t\t\tdisallowedTools = ['Bash(gh api:*)']\n\n\t\t\tgetLogger().debug('Configured tool filtering for issue workflow', { allowedTools, disallowedTools })\n\t\t} catch (error) {\n\t\t\t// Log warning but continue without MCP\n\t\t\tgetLogger().warn(`Failed to generate MCP config: ${error instanceof Error ? error.message : 'Unknown error'}`)\n\t\t}\n\n\t\t// Construct prompt for the orchestrating Claude instance\n\t\tconst prompt = this.constructEnhancerPrompt(issueNumber, author)\n\n\t\t// Invoke Claude CLI with enhancer agent\n\t\tconst response = await launchClaude(prompt, {\n\t\t\theadless: true,\n\t\t\tmodel: 'sonnet',\n\t\t\tagents,\n\t\t\tnoSessionPersistence: true, // Headless operation - no session persistence needed\n\t\t\t...(mcpConfig && { mcpConfig }),\n\t\t\t...(allowedTools && { allowedTools }),\n\t\t\t...(disallowedTools && { disallowedTools }),\n\t\t})\n\n\t\t// Parse response to determine outcome\n\t\treturn this.parseEnhancerResponse(response)\n\t}\n\n\t/**\n\t * Construct the prompt for the orchestrating Claude instance.\n\t * This prompt is very clear about expected output format to ensure reliable parsing.\n\t */\n\tprivate constructEnhancerPrompt(issueNumber: string | number, author?: string): string {\n\t\tconst authorInstruction = author\n\t\t\t? `\\nIMPORTANT: When you create your analysis comment, tag @${author} in the \"Questions for Reporter\" section if you have questions.\\n`\n\t\t\t: ''\n\n\t\treturn `Execute @agent-iloom-issue-enhancer ${issueNumber}${authorInstruction}\n\n## OUTPUT REQUIREMENTS\n* If the issue was not enhanced, return ONLY: \"No enhancement needed\"\n* If the issue WAS enhanced, return ONLY: <FULL URL OF THE COMMENT INCLUDING COMMENT ID>\n* If you encounter permission/authentication/access errors, return ONLY: \"Permission denied: <specific error description>\"\n* IMPORTANT: Return ONLY one of the above - DO NOT include commentary such as \"I created a comment at <URL>\" or \"I examined the issue and found no enhancement was necessary\"\n* CONTEXT: Your output is going to be parsed programmatically, so adherence to the output requirements is CRITICAL.`\n\t}\n\n\t/**\n\t * Parse the response from the enhancer agent.\n\t * Returns either { enhanced: false } or { enhanced: true, url: \"...\" }\n\t * Throws specific errors for permission issues.\n\t */\n\tprivate parseEnhancerResponse(response: string | void): EnhanceExistingIssueResult {\n\t\t// Handle empty or void response\n\t\tif (!response || typeof response !== 'string') {\n\t\t\tthrow new Error('No response from enhancer agent')\n\t\t}\n\n\t\tconst trimmed = response.trim()\n\n\t\tgetLogger().debug(`RESPONSE FROM ENHANCER AGENT: '${trimmed}'`)\n\n\t\t// Check for permission denied errors (case-insensitive)\n\t\tif (trimmed.toLowerCase().startsWith('permission denied:')) {\n\t\t\tconst errorMessage = trimmed.substring('permission denied:'.length).trim()\n\t\t\tthrow new Error(`Permission denied: ${errorMessage}`)\n\t\t}\n\n\t\t// Check for \"No enhancement needed\" (case-insensitive)\n\t\tif (trimmed.toLowerCase().includes('no enhancement needed')) {\n\t\t\treturn { enhanced: false }\n\t\t}\n\n\t\t// Check if response looks like a GitHub comment URL\n\t\tconst urlPattern = /https:\\/\\/github\\.com\\/[^/]+\\/[^/]+\\/issues\\/\\d+#issuecomment-\\d+/\n\t\tconst match = trimmed.match(urlPattern)\n\n\t\tif (match) {\n\t\t\treturn { enhanced: true, url: match[0] }\n\t\t}\n\n\t\t// Unexpected response format\n\t\tthrow new Error(`Unexpected response from enhancer agent: ${trimmed}`)\n\t}\n}\n","/**\n * Capitalizes the first letter of a string.\n *\n * Override behavior: If the string starts with a space, it signals the user\n * wants to opt-out of auto-capitalization. In this case, the leading space\n * is stripped and the first letter is NOT capitalized.\n *\n * @param str - The string to process\n * @returns The processed string with first letter capitalized (or original if override)\n */\nexport function capitalizeFirstLetter(str: string): string {\n\t// Handle empty or whitespace-only strings\n\tif (!str || str.length === 0) {\n\t\treturn str\n\t}\n\n\t// Check for space-prefix override: strip leading space and return as-is\n\tif (str.startsWith(' ')) {\n\t\treturn str.slice(1)\n\t}\n\n\t// Find the first character that could be capitalized (a letter)\n\tconst firstChar = str.charAt(0)\n\n\t// If first character is a letter (including unicode), capitalize it\n\t// Check if toUpperCase() produces a different result (indicates it's a letter with case)\n\tconst upperChar = firstChar.toUpperCase()\n\tif (upperChar !== firstChar.toLowerCase() || /\\p{L}/u.test(firstChar)) {\n\t\t// Only capitalize if it actually changes (avoids issues with non-cased scripts)\n\t\tif (upperChar !== firstChar) {\n\t\t\treturn upperChar + str.slice(1)\n\t\t}\n\t}\n\n\t// Non-letter first character or no case transformation available: return unchanged\n\treturn str\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/MergeManager.ts","../src/lib/BuildRunner.ts"],"sourcesContent":["import { executeGitCommand, fetchOrigin, findMainWorktreePathWithSettings, findWorktreeForBranch, getMergeTargetBranch, GitCommandError } from '../utils/git.js'\nimport { getLogger } from '../utils/logger-context.js'\nimport { detectClaudeCli, launchClaude } from '../utils/claude.js'\nimport { SettingsManager } from './SettingsManager.js'\nimport { MetadataManager } from './MetadataManager.js'\nimport type { MergeOptions } from '../types/index.js'\n\n/**\n * MergeManager handles Git rebase and fast-forward merge operations\n * Implements fail-fast behavior for conflicts (Phase 1 - no Claude assistance)\n *\n * Ports bash/merge-and-clean.sh lines 781-1090\n */\nexport class MergeManager {\n\tprivate settingsManager: SettingsManager\n\tprivate metadataManager: MetadataManager\n\n\tconstructor(settingsManager?: SettingsManager, metadataManager?: MetadataManager) {\n\t\tthis.settingsManager = settingsManager ?? new SettingsManager()\n\t\tthis.metadataManager = metadataManager ?? new MetadataManager()\n\t}\n\n\t/**\n\t * Get the merge target branch for a loom\n\t * Priority: parent loom metadata > configured main branch > 'main'\n\t * @param worktreePath - Optional path to load settings/metadata from (defaults to process.cwd())\n\t * @private\n\t */\n\tprivate async getMainBranch(worktreePath?: string): Promise<string> {\n\t\t// Delegate to shared utility function\n\t\treturn getMergeTargetBranch(worktreePath ?? process.cwd(), {\n\t\t\tsettingsManager: this.settingsManager,\n\t\t\tmetadataManager: this.metadataManager,\n\t\t})\n\t}\n\n\t/**\n\t * Rebase current branch on main with fail-fast on conflicts\n\t * Ports bash/merge-and-clean.sh lines 781-913\n\t *\n\t * @param worktreePath - Path to the worktree\n\t * @param options - Merge options (dryRun, force)\n\t * @throws Error if main branch doesn't exist, uncommitted changes exist, or conflicts occur\n\t */\n\tasync rebaseOnMain(worktreePath: string, options: MergeOptions = {}): Promise<void> {\n\t\tconst { dryRun = false, force = false } = options\n\n\t\t// Pre-check: abort any in-progress rebase before starting a new one\n\t\tawait this.abortInProgressRebase(worktreePath)\n\n\t\tconst mainBranch = await this.getMainBranch(worktreePath)\n\n\t\t// Determine whether to use remote (origin/) or local branch reference\n\t\t// - Child looms: always use local parent branch (parent may not be pushed)\n\t\t// - PR modes (github-pr, github-draft-pr) for non-child: fetch and use origin/{branch}\n\t\t// - Local mode: use local branch (no fetch)\n\t\tconst metadata = await this.metadataManager.readMetadata(worktreePath)\n\t\tconst isChildLoom = !!metadata?.parentLoom\n\t\tconst settings = await this.settingsManager.loadSettings(worktreePath)\n\t\tconst mergeBehaviorMode = settings.mergeBehavior?.mode ?? 'local'\n\t\tconst isPRMode = mergeBehaviorMode === 'github-pr' || mergeBehaviorMode === 'github-draft-pr'\n\t\tconst useRemote = isPRMode && !isChildLoom\n\n\t\tlet targetBranch: string\n\t\tif (useRemote) {\n\t\t\t// PR modes (non-child): fetch and use origin/{branch}\n\t\t\tgetLogger().info('Fetching from origin...')\n\t\t\tawait fetchOrigin(worktreePath)\n\t\t\ttargetBranch = `origin/${mainBranch}`\n\t\t} else {\n\t\t\t// Local mode or child loom: use local branch\n\t\t\tgetLogger().info(`Using local branch ${mainBranch} for rebase...`)\n\t\t\ttargetBranch = mainBranch\n\t\t}\n\n\t\tgetLogger().info(`Starting rebase on ${targetBranch}...`)\n\n\t\t// Step 1: Check if branch exists (remote ref for origin/, local ref otherwise)\n\t\tconst refPath = useRemote ? `refs/remotes/${targetBranch}` : `refs/heads/${targetBranch}`\n\t\ttry {\n\t\t\tawait executeGitCommand(['show-ref', '--verify', '--quiet', refPath], {\n\t\t\t\tcwd: worktreePath,\n\t\t\t})\n\t\t} catch {\n\t\t\tif (useRemote) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Remote branch \"${targetBranch}\" does not exist. Cannot rebase.\\n` +\n\t\t\t\t\t\t`Ensure the repository has a \"${mainBranch}\" branch on origin.`\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Local branch \"${targetBranch}\" does not exist. Cannot rebase.\\n` +\n\t\t\t\t\t\t`Ensure the branch exists locally.`\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\t// Step 2: Check for uncommitted changes and create WIP commit if needed\n\t\tconst statusOutput = await executeGitCommand(['status', '--porcelain'], {\n\t\t\tcwd: worktreePath,\n\t\t})\n\n\t\tlet wipCommitHash: string | null = null\n\t\tif (statusOutput.trim()) {\n\t\t\tgetLogger().info('Uncommitted changes detected, creating temporary WIP commit...')\n\t\t\twipCommitHash = await this.createWipCommit(worktreePath)\n\t\t\tgetLogger().debug(`Created WIP commit: ${wipCommitHash}`)\n\t\t}\n\n\t\t// Step 3: Check if rebase is needed by comparing merge-base with target HEAD\n\t\tconst mergeBase = await executeGitCommand(['merge-base', targetBranch, 'HEAD'], {\n\t\t\tcwd: worktreePath,\n\t\t})\n\n\t\tconst targetHead = await executeGitCommand(['rev-parse', targetBranch], {\n\t\t\tcwd: worktreePath,\n\t\t})\n\n\t\tconst mergeBaseTrimmed = mergeBase.trim()\n\t\tconst targetHeadTrimmed = targetHead.trim()\n\n\t\t// If merge-base matches target HEAD, branch is already up to date\n\t\tif (mergeBaseTrimmed === targetHeadTrimmed) {\n\t\t\tgetLogger().success(`Branch is already up to date with ${targetBranch}. No rebase needed.`)\n\t\t\t// Restore WIP commit if created (soft reset to remove temporary commit)\n\t\t\tif (wipCommitHash) {\n\t\t\t\tawait this.restoreWipCommit(worktreePath, wipCommitHash)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\t// Step 4: Show commits to be rebased (for informational purposes)\n\t\tconst commitsOutput = await executeGitCommand(['log', '--oneline', `${targetBranch}..HEAD`], {\n\t\t\tcwd: worktreePath,\n\t\t})\n\n\t\tconst commits = commitsOutput.trim()\n\t\tconst commitLines = commits ? commits.split('\\n') : []\n\n\t\tif (commits) {\n\t\t\t// Show commits that will be rebased\n\t\t\tgetLogger().info(`Found ${commitLines.length} commit(s) to rebase:`)\n\t\t\tcommitLines.forEach((commit) => getLogger().info(` ${commit}`))\n\t\t} else {\n\t\t\t// Target has moved forward but branch has no new commits\n\t\t\tgetLogger().info(`${targetBranch} has moved forward. Rebasing to update branch...`)\n\t\t}\n\n\t\t// Step 5: User confirmation (unless force mode or dry-run)\n\t\tif (!force && !dryRun) {\n\t\t\t// TODO: Implement interactive prompt for confirmation\n\t\t\t// For now, proceeding automatically (use --force to skip this message)\n\t\t\tgetLogger().info('Proceeding with rebase... (use --force to skip confirmations)')\n\t\t}\n\n\t\t// Step 6: Execute rebase (unless dry-run)\n\t\tif (dryRun) {\n\t\t\tgetLogger().info(`[DRY RUN] Would execute: git rebase ${targetBranch}`)\n\t\t\tif (commitLines.length > 0) {\n\t\t\t\tgetLogger().info(`[DRY RUN] This would rebase ${commitLines.length} commit(s)`)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\t// Execute rebase\n\t\t// Use -c core.hooksPath=/dev/null to disable hooks during rebase\n\t\t// This prevents pre-commit hooks from running when commits are re-applied\n\t\ttry {\n\t\t\tawait executeGitCommand(['-c', 'core.hooksPath=/dev/null', 'rebase', targetBranch], { cwd: worktreePath })\n\t\t\tgetLogger().success('Rebase completed successfully!')\n\n\t\t\t// Restore WIP commit if created\n\t\t\tif (wipCommitHash) {\n\t\t\t\tawait this.restoreWipCommit(worktreePath, wipCommitHash)\n\t\t\t}\n\t\t} catch (error) {\n\t\t\t// Detect conflicts\n\t\t\tconst conflictedFiles = await this.detectConflictedFiles(worktreePath)\n\n\t\t\tif (conflictedFiles.length > 0) {\n\t\t\t\t// Try Claude-assisted resolution first\n\t\t\t\tgetLogger().info('Merge conflicts detected, attempting Claude-assisted resolution...')\n\n\t\t\t\tconst resolved = await this.attemptClaudeConflictResolution(\n\t\t\t\t\tworktreePath,\n\t\t\t\t\tconflictedFiles\n\t\t\t\t)\n\n\t\t\t\tif (resolved) {\n\t\t\t\t\tgetLogger().success('Conflicts resolved with Claude assistance, rebase completed')\n\n\t\t\t\t\t// Restore WIP commit if created\n\t\t\t\t\tif (wipCommitHash) {\n\t\t\t\t\t\tawait this.restoreWipCommit(worktreePath, wipCommitHash)\n\t\t\t\t\t}\n\t\t\t\t\treturn // Continue with successful rebase\n\t\t\t\t}\n\n\t\t\t\t// Claude couldn't resolve or not available - fail fast\n\t\t\t\tconst conflictError = this.formatConflictError(conflictedFiles)\n\t\t\t\tthrow new Error(conflictError)\n\t\t\t}\n\n\t\t\t// If not a conflict, re-throw the original error\n\t\t\tthrow new Error(\n\t\t\t\t`Rebase failed: ${error instanceof Error ? error.message : String(error)}\\n` +\n\t\t\t\t\t'Run: git status for more details\\n' +\n\t\t\t\t\t'Or: git rebase --abort to cancel the rebase'\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Validate that fast-forward merge is possible\n\t * Ports bash/merge-and-clean.sh lines 957-968\n\t *\n\t * @param branchName - Name of the branch to merge\n\t * @param mainWorktreePath - Path where main branch is checked out\n\t * @throws Error if fast-forward is not possible\n\t */\n\tasync validateFastForwardPossible(mainBranch: string, branchName: string, mainWorktreePath: string): Promise<void> {\n\n\t\t// Step 1: Get merge-base between main and branch\n\t\tconst mergeBase = await executeGitCommand(['merge-base', mainBranch, branchName], {\n\t\t\tcwd: mainWorktreePath,\n\t\t})\n\n\t\t// Step 2: Get current HEAD of main\n\t\tconst mainHead = await executeGitCommand(['rev-parse', mainBranch], {\n\t\t\tcwd: mainWorktreePath,\n\t\t})\n\n\t\t// Step 3: Compare - they must match for fast-forward\n\t\tconst mergeBaseTrimmed = mergeBase.trim()\n\t\tconst mainHeadTrimmed = mainHead.trim()\n\n\t\tif (mergeBaseTrimmed !== mainHeadTrimmed) {\n\t\t\tthrow new Error(\n\t\t\t\t'Cannot perform fast-forward merge.\\n' +\n\t\t\t\t\t`The ${mainBranch} branch has moved forward since this branch was created.\\n` +\n\t\t\t\t\t`Merge base: ${mergeBaseTrimmed}\\n` +\n\t\t\t\t\t`Main HEAD: ${mainHeadTrimmed}\\n\\n` +\n\t\t\t\t\t'To fix this:\\n' +\n\t\t\t\t\t` 1. Rebase the branch on ${mainBranch}: git rebase ${mainBranch}\\n` +\n\t\t\t\t\t` 2. Or use: il finish to automatically rebase and merge\\n`\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Perform fast-forward only merge\n\t * Ports bash/merge-and-clean.sh lines 938-994\n\t *\n\t * @param branchName - Name of the branch to merge\n\t * @param worktreePath - Path to the worktree\n\t * @param options - Merge options (dryRun, force)\n\t * @throws Error if checkout, validation, or merge fails\n\t */\n\tasync performFastForwardMerge(\n\t\tbranchName: string,\n\t\tworktreePath: string,\n\t\toptions: MergeOptions = {}\n\t): Promise<void> {\n\t\tconst { dryRun = false, force = false } = options\n\n\t\tgetLogger().info('Starting fast-forward merge...')\n\n\t\t// Step 1: Get the merge target branch FIRST\n\t\t// For child looms, this will be the parent branch from metadata\n\t\t// For regular looms, this falls back to settings.mainBranch or 'main'\n\t\tconst mainBranch = await this.getMainBranch(worktreePath)\n\n\t\t// Step 2: Find where the merge target branch is checked out\n\t\t// CRITICAL: We must find the worktree for the MERGE TARGET, not settings.mainBranch\n\t\t// This fixes the child loom bug where we'd find the 'main' worktree instead of the parent branch worktree\n\t\tlet mainWorktreePath: string\n\t\tif (options.repoRoot) {\n\t\t\tmainWorktreePath = options.repoRoot\n\t\t} else {\n\t\t\ttry {\n\t\t\t\t// First try to find worktree with the exact merge target branch checked out\n\t\t\t\tmainWorktreePath = await findWorktreeForBranch(mainBranch, worktreePath)\n\t\t\t} catch {\n\t\t\t\t// Fallback: if no worktree has the branch checked out, use settings-based lookup\n\t\t\t\t// This handles edge cases like bare repos or detached HEAD states\n\t\t\t\tgetLogger().debug(`No worktree found for branch '${mainBranch}', falling back to settings-based lookup`)\n\t\t\t\tmainWorktreePath = await findMainWorktreePathWithSettings(worktreePath, this.settingsManager)\n\t\t\t}\n\t\t}\n\n\t\t// Step 3: No need to checkout - the merge target branch is already checked out in mainWorktreePath\n\t\tgetLogger().debug(`Using ${mainBranch} branch location: ${mainWorktreePath}`)\n\n\t\t// Step 4: Verify we're on the correct branch\n\t\tconst currentBranch = await executeGitCommand(['branch', '--show-current'], {\n\t\t\tcwd: mainWorktreePath,\n\t\t})\n\n\t\tif (currentBranch.trim() !== mainBranch) {\n\t\t\tthrow new Error(\n\t\t\t\t`Expected ${mainBranch} branch but found: ${currentBranch.trim()}\\n` +\n\t\t\t\t\t`At location: ${mainWorktreePath}\\n` +\n\t\t\t\t\t'This indicates the main worktree detection failed.'\n\t\t\t)\n\t\t}\n\n\t\t// Step 5: Validate fast-forward is possible\n\t\tawait this.validateFastForwardPossible(mainBranch, branchName, mainWorktreePath)\n\n\t\t// Step 6: Show commits to be merged\n\t\tconst commitsOutput = await executeGitCommand(['log', '--oneline', `${mainBranch}..${branchName}`], {\n\t\t\tcwd: mainWorktreePath,\n\t\t})\n\n\t\tconst commits = commitsOutput.trim()\n\n\t\t// If no commits, branch has no changes ahead of main\n\t\tif (!commits) {\n\t\t\tgetLogger().success(`Branch has no commits ahead of ${mainBranch}. No merge needed.`)\n\t\t\treturn\n\t\t}\n\n\t\t// Show commits that will be merged\n\t\tconst commitLines = commits.split('\\n')\n\t\tgetLogger().info(`Found ${commitLines.length} commit(s) to merge:`)\n\t\tcommitLines.forEach((commit) => getLogger().info(` ${commit}`))\n\n\t\t// Step 7: User confirmation (unless force mode or dry-run)\n\t\tif (!force && !dryRun) {\n\t\t\t// TODO: Implement interactive prompt for confirmation\n\t\t\t// For now, proceeding automatically (use --force to skip this message)\n\t\t\tgetLogger().info('Proceeding with fast-forward merge... (use --force to skip confirmations)')\n\t\t}\n\n\t\t// Step 8: Execute merge (unless dry-run)\n\t\tif (dryRun) {\n\t\t\tgetLogger().info(`[DRY RUN] Would execute: git merge --ff-only ${branchName}`)\n\t\t\tgetLogger().info(`[DRY RUN] This would merge ${commitLines.length} commit(s)`)\n\t\t\treturn\n\t\t}\n\n\t\t// Execute fast-forward merge\n\t\ttry {\n\t\t\tgetLogger().debug(`Executing fast-forward merge of ${branchName} into ${mainBranch} using cwd: ${mainWorktreePath}...`)\n\t\t\tawait executeGitCommand(['merge', '--ff-only', branchName], { cwd: mainWorktreePath })\n\t\t\tgetLogger().success(`Fast-forward merge completed! Merged ${commitLines.length} commit(s).`)\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`Fast-forward merge failed: ${error instanceof Error ? error.message : String(error)}\\n\\n` +\n\t\t\t\t\t'To recover:\\n' +\n\t\t\t\t\t' 1. Check merge status: git status\\n' +\n\t\t\t\t\t' 2. Abort merge if needed: git merge --abort\\n' +\n\t\t\t\t\t' 3. Verify branch is rebased: git rebase main\\n' +\n\t\t\t\t\t' 4. Try merge again: il finish'\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Helper: Detect conflicted files after failed rebase\n\t * @private\n\t */\n\tprivate async detectConflictedFiles(worktreePath: string): Promise<string[]> {\n\t\ttry {\n\t\t\tconst output = await executeGitCommand(['diff', '--name-only', '--diff-filter=U'], {\n\t\t\t\tcwd: worktreePath,\n\t\t\t})\n\n\t\t\treturn output\n\t\t\t\t.trim()\n\t\t\t\t.split('\\n')\n\t\t\t\t.filter((file) => file.length > 0)\n\t\t} catch {\n\t\t\t// If command fails, return empty array (might not be a conflict)\n\t\t\treturn []\n\t\t}\n\t}\n\n\t/**\n\t * Create a temporary WIP commit to preserve uncommitted changes during rebase\n\t * Stages all changes (tracked, untracked) using git add -A\n\t * Uses --no-verify to skip pre-commit hooks since this is a temporary internal commit\n\t * @param worktreePath - Path to the worktree\n\t * @returns The commit hash of the WIP commit\n\t * @private\n\t */\n\tprivate async createWipCommit(worktreePath: string): Promise<string> {\n\t\t// Stage all changes including untracked files\n\t\tawait executeGitCommand(['add', '-A'], { cwd: worktreePath })\n\n\t\t// Create WIP commit with distinctive message\n\t\t// Use --no-verify to skip pre-commit hooks - this is a temporary internal commit\n\t\tawait executeGitCommand(['commit', '--no-verify', '-m', 'WIP: Auto-stash for rebase'], { cwd: worktreePath })\n\n\t\t// Get and return the commit hash\n\t\tconst hash = await executeGitCommand(['rev-parse', 'HEAD'], { cwd: worktreePath })\n\t\treturn hash.trim()\n\t}\n\n\t/**\n\t * Restore uncommitted changes from WIP commit via soft reset\n\t * Logs warning but does not fail if soft reset fails (changes are safe in commit history)\n\t * @param worktreePath - Path to the worktree\n\t * @param wipCommitHash - Original WIP commit hash for verification logging\n\t * @private\n\t */\n\tprivate async restoreWipCommit(worktreePath: string, wipCommitHash: string): Promise<void> {\n\t\tgetLogger().info('Restoring uncommitted changes from WIP commit...')\n\n\t\ttry {\n\t\t\t// Soft reset to parent - changes become staged\n\t\t\tawait executeGitCommand(['reset', '--soft', 'HEAD~1'], { cwd: worktreePath })\n\n\t\t\t// Unstage files to restore to original working directory state\n\t\t\tawait executeGitCommand(['reset', 'HEAD'], { cwd: worktreePath })\n\n\t\t\tgetLogger().success('Restored uncommitted changes from WIP commit')\n\t\t} catch (error) {\n\t\t\t// Log warning but consider rebase successful - work is not lost\n\t\t\tgetLogger().warn(\n\t\t\t\t`Failed to restore WIP commit (${wipCommitHash}). ` +\n\t\t\t\t\t`Your changes are safe in the commit history. ` +\n\t\t\t\t\t`Manual recovery: git reset --soft HEAD~1`,\n\t\t\t\t{ error: error instanceof Error ? error.message : String(error) }\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Helper: Format conflict error message with manual resolution steps\n\t * @private\n\t */\n\tprivate formatConflictError(conflictedFiles: string[]): string {\n\t\tconst fileList = conflictedFiles.map((file) => ` • ${file}`).join('\\n')\n\n\t\treturn (\n\t\t\t'Rebase failed - merge conflicts detected in:\\n' +\n\t\t\tfileList +\n\t\t\t'\\n\\n' +\n\t\t\t'To resolve manually:\\n' +\n\t\t\t' 1. Fix conflicts in the files above\\n' +\n\t\t\t' 2. Stage resolved files: git add <files>\\n' +\n\t\t\t' 3. Continue rebase: git rebase --continue\\n' +\n\t\t\t' 4. Or abort rebase: git rebase --abort\\n' +\n\t\t\t' 5. Then re-run: il finish <issue-number>'\n\t\t)\n\t}\n\n\t/**\n\t * Attempt to resolve conflicts using Claude\n\t * Ports bash/merge-and-clean.sh lines 839-894\n\t *\n\t * @param worktreePath - Path to the worktree\n\t * @param conflictedFiles - List of files with conflicts\n\t * @returns true if conflicts resolved, false otherwise\n\t * @private\n\t */\n\tprivate async attemptClaudeConflictResolution(\n\t\tworktreePath: string,\n\t\tconflictedFiles: string[]\n\t): Promise<boolean> {\n\t\t// Check if Claude CLI is available\n\t\tconst isClaudeAvailable = await detectClaudeCli()\n\t\tif (!isClaudeAvailable) {\n\t\t\tgetLogger().debug('Claude CLI not available, skipping conflict resolution')\n\t\t\treturn false\n\t\t}\n\n\t\tgetLogger().info(`Launching Claude to resolve conflicts in ${conflictedFiles.length} file(s)...`)\n\n\t\t// Hard-coded prompt matching bash script line 844\n\t\t// No templates, no complexity - just the essential instruction\n\t\tconst systemPrompt =\n\t\t\t`Please help resolve the git rebase conflicts in this repository. ` +\n\t\t\t`Analyze the conflicted files, understand the changes from both branches, ` +\n\t\t\t`fix the conflicts, then run 'git add .' to stage the resolved files, ` +\n\t\t\t`and finally run 'git rebase --continue' to continue the rebase process. ` +\n\t\t\t`Once the issue is resolved, tell the user they can use /exit to continue with the process.`\n\n\t\tconst prompt =\n\t\t\t`Help me with this rebase please.`\n\n\t\t// Git commands to auto-approve during rebase conflict resolution\n\t\t// These are the essential commands Claude needs to analyze and resolve conflicts\n\t\t// Note: git reset and git checkout are intentionally excluded as they can be destructive\n\t\tconst rebaseAllowedTools = [\n\t\t\t'Bash(git status:*)',\n\t\t\t'Bash(git diff:*)',\n\t\t\t'Bash(git log:*)',\n\t\t\t'Bash(git add:*)',\n\t\t\t'Bash(git rebase:*)',\n\t\t\t'Bash(GIT_EDITOR=true git rebase:*)',\n\t\t]\n\n\t\ttry {\n\t\t\t// Launch Claude interactively in current terminal\n\t\t\t// User will interact directly with Claude to resolve conflicts\n\t\t\tawait launchClaude(prompt, {\n\t\t\t\tappendSystemPrompt: systemPrompt,\n\t\t\t\taddDir: worktreePath,\n\t\t\t\theadless: false, // Interactive - runs in current terminal with stdio: inherit\n\t\t\t\tallowedTools: rebaseAllowedTools,\n\t\t\t\tnoSessionPersistence: true, // Utility operation - no session persistence needed\n\t\t\t})\n\n\t\t\t// After Claude interaction completes, check if conflicts resolved\n\t\t\tconst remainingConflicts = await this.detectConflictedFiles(worktreePath)\n\n\t\t\tif (remainingConflicts.length > 0) {\n\t\t\t\tgetLogger().warn(\n\t\t\t\t\t`Conflicts still exist in ${remainingConflicts.length} file(s) after Claude assistance`\n\t\t\t\t)\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Check if rebase completed or still in progress\n\t\t\tconst rebaseInProgress = await this.isRebaseInProgress(worktreePath)\n\n\t\t\tif (rebaseInProgress) {\n\t\t\t\tgetLogger().warn('Rebase still in progress after Claude assistance')\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\treturn true\n\t\t} catch (error) {\n\t\t\tgetLogger().warn('Claude conflict resolution failed', {\n\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t})\n\t\t\treturn false\n\t\t}\n\t}\n\n\t/**\n\t * Check if a git rebase is currently in progress\n\t * Checks for .git/rebase-merge or .git/rebase-apply directories\n\t * Ports bash script logic from lines 853-856\n\t *\n\t * @param worktreePath - Path to the worktree\n\t * @returns true if rebase in progress, false otherwise\n\t * @private\n\t */\n\tprivate async isRebaseInProgress(worktreePath: string): Promise<boolean> {\n\t\tconst fs = await import('node:fs/promises')\n\t\tconst path = await import('node:path')\n\n\t\t// In git worktrees, .git is a file pointing to the actual git dir.\n\t\t// Use git rev-parse to resolve the real git directory.\n\t\tconst gitDir = (await executeGitCommand(\n\t\t\t['rev-parse', '--absolute-git-dir'],\n\t\t\t{ cwd: worktreePath }\n\t\t)).trim()\n\n\t\tconst rebaseMergePath = path.join(gitDir, 'rebase-merge')\n\t\tconst rebaseApplyPath = path.join(gitDir, 'rebase-apply')\n\n\t\t// Check for rebase-merge directory\n\t\ttry {\n\t\t\tawait fs.access(rebaseMergePath)\n\t\t\treturn true\n\t\t} catch {\n\t\t\t// Directory doesn't exist, continue checking\n\t\t}\n\n\t\t// Check for rebase-apply directory\n\t\ttry {\n\t\t\tawait fs.access(rebaseApplyPath)\n\t\t\treturn true\n\t\t} catch {\n\t\t\t// Directory doesn't exist\n\t\t}\n\n\t\treturn false\n\t}\n\n\t/**\n\t * Abort an in-progress rebase if one is detected\n\t * This handles cases where a previous rebase was interrupted (e.g., terminal closed,\n\t * Claude session ended, user manually stopped) and the worktree is left in a dirty state.\n\t * Since we're about to start a new rebase, the stale rebase state is irrelevant and safe to abort.\n\t *\n\t * @param worktreePath - Path to the worktree\n\t * @private\n\t */\n\tprivate async abortInProgressRebase(worktreePath: string): Promise<void> {\n\t\tconst rebaseInProgress = await this.isRebaseInProgress(worktreePath)\n\n\t\tif (!rebaseInProgress) {\n\t\t\treturn\n\t\t}\n\n\t\tgetLogger().warn('A rebase is already in progress. Aborting the stale rebase before proceeding...')\n\n\t\ttry {\n\t\t\tawait executeGitCommand(['rebase', '--abort'], { cwd: worktreePath })\n\t\t\tgetLogger().info('Stale rebase aborted successfully.')\n\t\t} catch (error) {\n\t\t\t// Handle race condition: rebase may have been resolved between check and abort\n\t\t\tconst errorMsg = error instanceof Error ? error.message : String(error)\n\t\t\tif (errorMsg.includes('No rebase in progress')) {\n\t\t\t\tgetLogger().info('Rebase was already resolved by another process.')\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (error instanceof GitCommandError) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Failed to abort in-progress rebase: ${error.message}\\n` +\n\t\t\t\t\t\t'Manual recovery: run \"git rebase --abort\" in the worktree directory.'\n\t\t\t\t)\n\t\t\t}\n\t\t\tthrow error\n\t\t}\n\t}\n}\n","import { getLogger } from '../utils/logger-context.js'\nimport { detectPackageManager, runScript } from '../utils/package-manager.js'\nimport { getPackageConfig, hasScript } from '../utils/package-json.js'\nimport { ProjectCapabilityDetector } from './ProjectCapabilityDetector.js'\n\nexport interface BuildOptions {\n\tdryRun?: boolean\n}\n\nexport interface BuildResult {\n\tsuccess: boolean\n\tskipped: boolean\n\treason?: string\n\tduration: number\n}\n\n/**\n * BuildRunner handles post-merge build verification for CLI projects\n * Only runs build when project has CLI capabilities (bin field in package.json)\n */\nexport class BuildRunner {\n\tprivate capabilityDetector: ProjectCapabilityDetector\n\n\tconstructor(capabilityDetector?: ProjectCapabilityDetector) {\n\t\tthis.capabilityDetector = capabilityDetector ?? new ProjectCapabilityDetector()\n\t}\n\n\t/**\n\t * Run build verification in the specified directory\n\t * @param buildPath - Path where build should run (typically main worktree path)\n\t * @param options - Build options\n\t */\n\tasync runBuild(buildPath: string, options: BuildOptions = {}): Promise<BuildResult> {\n\t\tconst startTime = Date.now()\n\n\t\ttry {\n\t\t\t// Step 1: Check if build script exists (checks .iloom/package.iloom.json first, then package.json)\n\t\t\tconst pkgJson = await getPackageConfig(buildPath)\n\t\t\tconst hasBuildScript = hasScript(pkgJson, 'build')\n\n\t\t\tif (!hasBuildScript) {\n\t\t\tgetLogger().debug('Skipping build - no build script found')\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tskipped: true,\n\t\t\t\t\treason: 'No build script found in package configuration',\n\t\t\t\t\tduration: Date.now() - startTime,\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\t// Handle missing package.json - skip build for non-Node.js projects without package.iloom.json\n\t\t\tif (error instanceof Error && error.message.includes('package.json not found')) {\n\t\t\tgetLogger().debug('Skipping build - no package configuration found')\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tskipped: true,\n\t\t\t\t\treason: 'No package configuration found in project',\n\t\t\t\t\tduration: Date.now() - startTime,\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Re-throw other errors\n\t\t\tthrow error\n\t\t}\n\n\t\t// Step 2: Check if project has CLI capability (bin field)\n\t\tconst capabilities = await this.capabilityDetector.detectCapabilities(buildPath)\n\t\tconst isCLIProject = capabilities.capabilities.includes('cli')\n\n\t\tif (!isCLIProject) {\n\t\tgetLogger().debug('Skipping build - not a CLI project (no bin field)')\n\t\t\treturn {\n\t\t\t\tsuccess: true,\n\t\t\t\tskipped: true,\n\t\t\t\treason: 'Project is not a CLI project (no bin field in package.json)',\n\t\t\t\tduration: Date.now() - startTime,\n\t\t\t}\n\t\t}\n\n\t\t// Step 3: Detect package manager\n\t\tconst packageManager = await detectPackageManager(buildPath)\n\n\t\t// Step 4: Handle dry-run mode\n\t\tif (options.dryRun) {\n\t\t\tconst command =\n\t\t\t\tpackageManager === 'npm' ? 'npm run build' : `${packageManager} build`\n\t\tgetLogger().info(`[DRY RUN] Would run: ${command}`)\n\t\t\treturn {\n\t\t\t\tsuccess: true,\n\t\t\t\tskipped: false,\n\t\t\t\tduration: Date.now() - startTime,\n\t\t\t}\n\t\t}\n\n\t\t// Step 5: Execute build\n\tgetLogger().info('Running build...')\n\n\t\ttry {\n\t\t\tawait runScript('build', buildPath, [], { quiet: true })\n\t\tgetLogger().success('Build completed successfully')\n\n\t\t\treturn {\n\t\t\t\tsuccess: true,\n\t\t\t\tskipped: false,\n\t\t\t\tduration: Date.now() - startTime,\n\t\t\t}\n\t\t} catch {\n\t\t\t// Step 6: Throw detailed error on failure\n\t\t\tconst runCommand =\n\t\t\t\tpackageManager === 'npm' ? 'npm run build' : `${packageManager} build`\n\n\t\t\tthrow new Error(\n\t\t\t\t`Error: Build failed.\\n` +\n\t\t\t\t\t`Fix build errors before proceeding.\\n\\n` +\n\t\t\t\t\t`Run '${runCommand}' to see detailed errors.`\n\t\t\t)\n\t\t}\n\t}\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/utils/claude.ts"],"sourcesContent":["import { execa } from 'execa'\nimport { existsSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { createHash, randomUUID } from 'node:crypto'\nimport { logger } from './logger.js'\nimport { getLogger } from './logger-context.js'\nimport { openTerminalWindow } from './terminal.js'\n\n/**\n * Generate a deterministic UUID v5 from a worktree path\n * Uses SHA1 hash with URL namespace to create a consistent session ID\n * that can be used to resume Claude Code sessions\n */\nexport function generateDeterministicSessionId(worktreePath: string): string {\n\t// UUID v5 namespace for URLs (RFC 4122)\n\tconst URL_NAMESPACE = '6ba7b811-9dad-11d1-80b4-00c04fd430c8'\n\n\t// Create SHA1 hash of namespace + path\n\tconst hash = createHash('sha1')\n\n\t// Convert namespace UUID to bytes\n\tconst namespaceBytes = Buffer.from(URL_NAMESPACE.replace(/-/g, ''), 'hex')\n\thash.update(namespaceBytes)\n\thash.update(worktreePath)\n\n\tconst digest = hash.digest()\n\n\t// Format as UUID v5:\n\t// - Set version (bits 12-15 of time_hi_and_version) to 5\n\t// - Set variant (bits 6-7 of clock_seq_hi_and_reserved) to binary 10\n\tconst bytes = Array.from(digest.subarray(0, 16))\n\n\t// Set version to 5 (byte 6, high nibble)\n\tconst byte6 = bytes[6] ?? 0\n\tbytes[6] = (byte6 & 0x0f) | 0x50\n\n\t// Set variant to RFC 4122 (byte 8, high 2 bits = 10)\n\tconst byte8 = bytes[8] ?? 0\n\tbytes[8] = (byte8 & 0x3f) | 0x80\n\n\t// Format as UUID string\n\tconst hex = Buffer.from(bytes).toString('hex')\n\treturn `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`\n}\n\n/**\n * Generate a random UUID v4 for session ID\n * Uses crypto.randomUUID() for cryptographically secure random UUID generation\n * Used to create unique session IDs for each loom, enabling fresh Claude sessions\n */\nexport function generateRandomSessionId(): string {\n\treturn randomUUID()\n}\n\nexport interface ClaudeCliOptions {\n\tmodel?: string\n\tpermissionMode?: 'plan' | 'acceptEdits' | 'bypassPermissions' | 'default'\n\taddDir?: string\n\theadless?: boolean\n\tbranchName?: string // Optional branch name for terminal coloring\n\tport?: number // Optional port for terminal window export\n\ttimeout?: number // Timeout in milliseconds\n\tappendSystemPrompt?: string // System instructions to append to system prompt\n\tmcpConfig?: Record<string, unknown>[] // Array of MCP server configurations\n\tallowedTools?: string[] // Tools to allow via --allowed-tools flag\n\tdisallowedTools?: string[] // Tools to disallow via --disallowed-tools flag\n\tagents?: Record<string, unknown> // Agent configurations for --agents flag\n\toneShot?: import('../types/index.js').OneShotMode // One-shot automation mode\n\tsetArguments?: string[] // Raw --set arguments to forward (e.g., ['workflows.issue.startIde=false'])\n\texecutablePath?: string // Executable path to use for spin command (e.g., 'il', 'il-125', or '/path/to/dist/cli.js')\n\tsessionId?: string // Session ID for Claude Code resume support (must be valid UUID)\n\tnoSessionPersistence?: boolean // Prevent session data from being saved to disk (for utility operations)\n\toutputFormat?: 'json' | 'stream-json' | 'text' // Output format for Claude CLI (headless mode)\n\tverbose?: boolean // Enable verbose output (headless mode) - defaults to true when headless\n\tjsonMode?: 'json' | 'stream' // JSON output mode: 'json' for final object, 'stream' for real-time JSONL\n\tenv?: Record<string, string> // Additional environment variables to pass to the Claude process\n}\n\n/**\n * Detect if Claude CLI is available on the system\n */\nexport async function detectClaudeCli(): Promise<boolean> {\n\ttry {\n\t\t// Use 'command -v' for cross-platform compatibility (works on macOS/Linux)\n\t\tawait execa('command', ['-v', 'claude'], {\n\t\t\tshell: true,\n\t\t\ttimeout: 5000,\n\t\t})\n\t\treturn true\n\t} catch (error) {\n\t\t// Claude CLI not found\n\t\tlogger.debug('Claude CLI not available', { error })\n\t\treturn false\n\t}\n}\n\n/**\n * Get Claude CLI version\n */\nexport async function getClaudeVersion(): Promise<string | null> {\n\ttry {\n\t\tconst result = await execa('claude', ['--version'], {\n\t\t\ttimeout: 5000,\n\t\t})\n\t\treturn result.stdout.trim()\n\t} catch (error) {\n\t\tlogger.warn('Failed to get Claude version', { error })\n\t\treturn null\n\t}\n}\n\n/**\n * Parse JSON stream output and extract result from last JSON object with type:\"result\"\n */\nfunction parseJsonStreamOutput(output: string): string {\n\ttry {\n\t\t// Split by newlines and filter out empty lines\n\t\tconst lines = output.split('\\n').filter(line => line.trim())\n\n\t\t// Find the last valid JSON object with type:\"result\"\n\t\tlet lastResult = ''\n\t\tfor (const line of lines) {\n\t\t\ttry {\n\t\t\t\tconst jsonObj = JSON.parse(line)\n\t\t\t\tif (jsonObj && typeof jsonObj === 'object' && jsonObj.type === 'result' && 'result' in jsonObj) {\n\t\t\t\t\tlastResult = jsonObj.result\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Skip invalid JSON lines\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\treturn lastResult || output // Fallback to original output if no valid result found\n\t} catch {\n\t\t// If parsing fails completely, return original output\n\t\treturn output\n\t}\n}\n\n/**\n * Launch Claude CLI with specified options\n * In headless mode, returns stdout. In interactive mode, returns void.\n */\nexport async function launchClaude(\n\tprompt: string,\n\toptions: ClaudeCliOptions = {}\n): Promise<string | void> {\n\tconst { model, permissionMode, addDir, headless = false, appendSystemPrompt, mcpConfig, allowedTools, disallowedTools, agents, sessionId, noSessionPersistence, outputFormat, verbose, jsonMode, env: extraEnv } = options\n\tconst log = getLogger()\n\n\t// Build command arguments\n\tconst args: string[] = []\n\n\tif (headless) {\n\t\targs.push('-p')\n\n\t\t// Use user-provided outputFormat or default to stream-json for progress tracking\n\t\tconst effectiveOutputFormat = outputFormat ?? 'stream-json'\n\t\targs.push('--output-format', effectiveOutputFormat)\n\n\t\t// Use user-provided verbose setting or default to true\n\t\tif (verbose !== false) {\n\t\t\targs.push('--verbose')\n\t\t}\n\t}\n\n\tif (model) {\n\t\targs.push('--model', model)\n\t}\n\n\tif (permissionMode && permissionMode !== 'default') {\n\t\targs.push('--permission-mode', permissionMode)\n\t}\n\n\tif (addDir) {\n\t\targs.push('--add-dir', addDir)\n\t}\n\n\targs.push('--add-dir', '/tmp') //TODO: Won't work on Windows\n\n\t// Add --append-system-prompt flag if provided\n\tif (appendSystemPrompt) {\n\t\targs.push('--append-system-prompt', appendSystemPrompt)\n\t}\n\n\t// Add --mcp-config flags for each MCP server configuration\n\tif (mcpConfig && mcpConfig.length > 0) {\n\t\tfor (const config of mcpConfig) {\n\t\t\targs.push('--mcp-config', JSON.stringify(config))\n\t\t}\n\t}\n\n\t// Add --allowed-tools flags if provided\n\tif (allowedTools && allowedTools.length > 0) {\n\t\targs.push('--allowed-tools', ...allowedTools)\n\t}\n\n\t// Add --disallowed-tools flags if provided\n\tif (disallowedTools && disallowedTools.length > 0) {\n\t\targs.push('--disallowed-tools', ...disallowedTools)\n\t}\n\n\t// Add --agents flag if provided\n\tif (agents) {\n\t\targs.push('--agents', JSON.stringify(agents))\n\t}\n\n\t// Add --session-id flag if provided (enables Claude Code session resume)\n\tif (sessionId) {\n\t\targs.push('--session-id', sessionId)\n\t}\n\n\t// Add --no-session-persistence flag if requested (for utility operations that don't need session persistence)\n\t// Note: --no-session-persistence can only be used with --print mode (-p), which is only added in headless mode\n\tif (noSessionPersistence && headless) {\n\t\targs.push('--no-session-persistence')\n\t}\n\n\t// Set CLAUDECODE=0 to prevent Claude from detecting it's running inside Claude Code\n\tconst claudeEnv = { ...process.env, CLAUDECODE: '0' }\n\n\ttry {\n\t\tif (headless) {\n\t\t\t// Headless mode: capture and return output\n\t\t\tconst isDebugMode = logger.isDebugEnabled()\n\n\t\t\t// Set up execa options based on debug mode\n\t\t\tconst execaOptions = {\n\t\t\t\tinput: prompt,\n\t\t\t\ttimeout: 0, // Disable timeout for long responses\n\t\t\t\t...(addDir && { cwd: addDir }), // Run Claude in the worktree directory\n\t\t\t\tverbose: isDebugMode,\n\t\t\t\tenv: { ...claudeEnv, ...extraEnv }, // CLAUDECODE=0 + any extra env vars\n\t\t\t\t...(isDebugMode && { stdio: ['pipe', 'pipe', 'pipe'] as const }), // Enable streaming in debug mode\n\t\t\t}\n\n\t\t\tconst subprocess = execa('claude', args, execaOptions)\n\n\t\t\t// Check if JSON streaming format is enabled (always true in headless mode)\n\t\t\tconst isJsonStreamFormat = args.includes('--output-format') && args.includes('stream-json')\n\n\t\t\t// Handle real-time streaming (enabled for progress tracking)\n\t\t\tlet outputBuffer = ''\n\t\t\tlet isStreaming = false\n\t\t\tlet isFirstProgress = true\n\t\t\tif (subprocess.stdout && typeof subprocess.stdout.on === 'function') {\n\t\t\t\tisStreaming = true\n\t\t\t\tsubprocess.stdout.on('data', (chunk: Buffer) => {\n\t\t\t\t\tconst text = chunk.toString()\n\t\t\t\t\toutputBuffer += text\n\n\t\t\t\t\tif (jsonMode === 'stream') {\n\t\t\t\t\t\t// --json-stream: Output raw JSONL to stdout immediately\n\t\t\t\t\t\tprocess.stdout.write(text)\n\t\t\t\t\t} else if (jsonMode === 'json') {\n\t\t\t\t\t\t// --json: Suppress all progress output (will return final JSON)\n\t\t\t\t\t\t// Do nothing - just accumulate in buffer\n\t\t\t\t\t} else if (isDebugMode) {\n\t\t\t\t\t\tlog.stdout.write(text) // Full JSON streaming in debug mode\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Progress dots in non-debug mode with robot emoji prefix\n\t\t\t\t\t\tif (isFirstProgress) {\n\t\t\t\t\t\t\tlog.stdout.write('🤖 .')\n\t\t\t\t\t\t\tisFirstProgress = false\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tlog.stdout.write('.')\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tconst result = await subprocess\n\n\t\t\t// Return streamed output if we were streaming, otherwise use result.stdout\n\t\t\tif (isStreaming) {\n\t\t\t\tconst rawOutput = outputBuffer.trim()\n\n\t\t\t\t// Clean up progress dots with newline in non-debug mode (skip for json modes)\n\t\t\t\tif (!isDebugMode && !jsonMode) {\n\t\t\t\t\tlog.stdout.write('\\n')\n\t\t\t\t}\n\n\t\t\t\treturn isJsonStreamFormat ? parseJsonStreamOutput(rawOutput) : rawOutput\n\t\t\t} else {\n\t\t\t\t// Fallback for mocked tests or when streaming not available\n\t\t\t\tif (isDebugMode) {\n\t\t\t\t\t// In debug mode, write to stdout even if not streaming (old behavior for tests)\n\t\t\t\t\tlog.stdout.write(result.stdout)\n\t\t\t\t\tif (result.stdout && !result.stdout.endsWith('\\n')) {\n\t\t\t\t\t\tlog.stdout.write('\\n')\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// In non-debug mode, show a single progress dot even without streaming (for tests)\n\t\t\t\t\tlog.stdout.write('🤖 .')\n\t\t\t\t\tlog.stdout.write('\\n')\n\t\t\t\t}\n\t\t\t\tconst rawOutput = result.stdout.trim()\n\t\t\t\treturn isJsonStreamFormat ? parseJsonStreamOutput(rawOutput) : rawOutput\n\t\t\t}\n\t\t} else {\n\t\t\t// Simple interactive mode: run Claude in current terminal with stdio inherit\n\t\t\t// Used for conflict resolution, error fixing, etc.\n\t\t\t// This is the simple approach: claude -- \"prompt\"\n\n\t\t\t// First attempt: capture stderr to detect session ID conflicts\n\t\t\t// stdin/stdout inherit for interactivity, stderr captured for error detection\n\t\t\ttry {\n\t\t\t\tawait execa('claude', [...args, '--', prompt], {\n\t\t\t\t\t...(addDir && { cwd: addDir }),\n\t\t\t\t\tstdio: ['inherit', 'inherit', 'pipe'], // Capture stderr to detect session conflicts\n\t\t\t\t\ttimeout: 0, // Disable timeout\n\t\t\t\t\tverbose: logger.isDebugEnabled(),\n\t\t\t\t\tenv: { ...claudeEnv, ...extraEnv }, // CLAUDECODE=0 + any extra env vars\n\t\t\t\t})\n\t\t\t\treturn\n\t\t\t} catch (interactiveError) {\n\t\t\t\tconst interactiveExecaError = interactiveError as { stderr?: string; message?: string }\n\t\t\t\tconst interactiveErrorMessage = interactiveExecaError.stderr ?? interactiveExecaError.message ?? ''\n\n\t\t\t\t// Check for session ID conflict\n\t\t\t\tconst sessionMatch = interactiveErrorMessage.match(/Session ID ([0-9a-f-]+) is already in use/i)\n\t\t\t\tconst conflictSessionId = sessionMatch?.[1]\n\t\t\t\tif (sessionMatch && sessionId && conflictSessionId) {\n\t\t\t\t\tlog.debug(`Session ID ${conflictSessionId} already in use, retrying with --resume`)\n\n\t\t\t\t\t// Rebuild args with --resume instead of --session-id\n\t\t\t\t\tconst resumeArgs = args.filter((arg, idx) => {\n\t\t\t\t\t\tif (arg === '--session-id') return false\n\t\t\t\t\t\tif (idx > 0 && args[idx - 1] === '--session-id') return false\n\t\t\t\t\t\treturn true\n\t\t\t\t\t})\n\t\t\t\t\tresumeArgs.push('--resume', conflictSessionId)\n\n\t\t\t\t\t// Retry with full stdio inherit for proper interactive experience\n\t\t\t\t\t// Note: When using --resume, we omit the prompt since the session already has context\n\t\t\t\t\tawait execa('claude', resumeArgs, {\n\t\t\t\t\t\t...(addDir && { cwd: addDir }),\n\t\t\t\t\t\tstdio: 'inherit',\n\t\t\t\t\t\ttimeout: 0,\n\t\t\t\t\t\tverbose: logger.isDebugEnabled(),\n\t\t\t\t\t\tenv: claudeEnv,\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// Not a session conflict, re-throw\n\t\t\t\tthrow interactiveError\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\t// Check for specific Claude CLI errors\n\t\tconst execaError = error as {\n\t\t\tstderr?: string\n\t\t\tmessage?: string\n\t\t\texitCode?: number\n\t\t}\n\n\t\tconst errorMessage = execaError.stderr ?? execaError.message ?? 'Unknown Claude CLI error'\n\n\t\t// Check for \"Session ID ... is already in use\" error and retry with --resume\n\t\tconst sessionInUseMatch = errorMessage.match(/Session ID ([0-9a-f-]+) is already in use/i)\n\t\tconst extractedSessionId = sessionInUseMatch?.[1]\n\t\tif (sessionInUseMatch && sessionId && extractedSessionId) {\n\t\t\tlog.debug(`Session ID ${extractedSessionId} already in use, retrying with --resume`)\n\n\t\t\t// Rebuild args with --resume instead of --session-id\n\t\t\tconst resumeArgs = args.filter((arg, idx) => {\n\t\t\t\t// Filter out --session-id and its value\n\t\t\t\tif (arg === '--session-id') return false\n\t\t\t\tif (idx > 0 && args[idx - 1] === '--session-id') return false\n\t\t\t\treturn true\n\t\t\t})\n\t\t\tresumeArgs.push('--resume', extractedSessionId)\n\n\t\t\ttry {\n\t\t\t\tif (headless) {\n\t\t\t\t\tconst isDebugMode = logger.isDebugEnabled()\n\t\t\t\t\t// Note: In headless mode, we still need to pass the prompt even with --resume\n\t\t\t\t\t// because there's no interactive input mechanism\n\t\t\t\t\tconst execaOptions = {\n\t\t\t\t\t\tinput: prompt,\n\t\t\t\t\t\ttimeout: 0,\n\t\t\t\t\t\t...(addDir && { cwd: addDir }),\n\t\t\t\t\t\tverbose: isDebugMode,\n\t\t\t\t\t\tenv: claudeEnv,\n\t\t\t\t\t\t...(isDebugMode && { stdio: ['pipe', 'pipe', 'pipe'] as const }),\n\t\t\t\t\t}\n\n\t\t\t\t\tconst subprocess = execa('claude', resumeArgs, execaOptions)\n\t\t\t\t\tconst isJsonStreamFormat = resumeArgs.includes('--output-format') && resumeArgs.includes('stream-json')\n\n\t\t\t\t\tlet outputBuffer = ''\n\t\t\t\t\tlet isStreaming = false\n\t\t\t\t\tlet isFirstProgress = true\n\t\t\t\t\tif (subprocess.stdout && typeof subprocess.stdout.on === 'function') {\n\t\t\t\t\t\tisStreaming = true\n\t\t\t\t\t\tsubprocess.stdout.on('data', (chunk: Buffer) => {\n\t\t\t\t\t\t\tconst text = chunk.toString()\n\t\t\t\t\t\t\toutputBuffer += text\n\t\t\t\t\t\t\tif (jsonMode === 'stream') {\n\t\t\t\t\t\t\t\tprocess.stdout.write(text)\n\t\t\t\t\t\t\t} else if (jsonMode === 'json') {\n\t\t\t\t\t\t\t\t// Suppress progress output for json mode\n\t\t\t\t\t\t\t} else if (isDebugMode) {\n\t\t\t\t\t\t\t\tlog.stdout.write(text)\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (isFirstProgress) {\n\t\t\t\t\t\t\t\t\tlog.stdout.write('🤖 .')\n\t\t\t\t\t\t\t\t\tisFirstProgress = false\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tlog.stdout.write('.')\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\n\t\t\t\t\tconst result = await subprocess\n\n\t\t\t\t\tif (isStreaming) {\n\t\t\t\t\t\tconst rawOutput = outputBuffer.trim()\n\t\t\t\t\t\tif (!isDebugMode && !jsonMode) {\n\t\t\t\t\t\t\tlog.stdout.write('\\n')\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn isJsonStreamFormat ? parseJsonStreamOutput(rawOutput) : rawOutput\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (isDebugMode) {\n\t\t\t\t\t\t\tlog.stdout.write(result.stdout)\n\t\t\t\t\t\t\tif (result.stdout && !result.stdout.endsWith('\\n')) {\n\t\t\t\t\t\t\t\tlog.stdout.write('\\n')\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tlog.stdout.write('🤖 .')\n\t\t\t\t\t\t\tlog.stdout.write('\\n')\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst rawOutput = result.stdout.trim()\n\t\t\t\t\t\treturn isJsonStreamFormat ? parseJsonStreamOutput(rawOutput) : rawOutput\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Note: When using --resume, we omit the prompt since the session already has context\n\t\t\t\t\tawait execa('claude', resumeArgs, {\n\t\t\t\t\t\t...(addDir && { cwd: addDir }),\n\t\t\t\t\t\tstdio: 'inherit',\n\t\t\t\t\t\ttimeout: 0,\n\t\t\t\t\t\tverbose: logger.isDebugEnabled(),\n\t\t\t\t\t\tenv: claudeEnv,\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} catch (retryError) {\n\t\t\t\tconst retryExecaError = retryError as { stderr?: string; message?: string }\n\t\t\t\tconst retryErrorMessage = retryExecaError.stderr ?? retryExecaError.message ?? 'Unknown Claude CLI error'\n\t\t\t\tthrow new Error(`Claude CLI error: ${retryErrorMessage}`)\n\t\t\t}\n\t\t}\n\n\t\t// Re-throw with more context\n\t\tthrow new Error(`Claude CLI error: ${errorMessage}`)\n\t}\n}\n\n/**\n * Launch Claude in a new terminal window with rich context\n * This is specifically for \"end of il start\" workflow\n * Ports the terminal window opening, coloring, and .env sourcing behavior\n */\nexport async function launchClaudeInNewTerminalWindow(\n\t_prompt: string,\n\toptions: ClaudeCliOptions & {\n\t\tworkspacePath: string // Required for terminal window launch\n\t}\n): Promise<void> {\n\tconst { workspacePath, branchName, oneShot = 'default', port, setArguments, executablePath } = options\n\n\t// Verify required parameter\n\tif (!workspacePath) {\n\t\tthrow new Error('workspacePath is required for terminal window launch')\n\t}\n\n\t// Build launch command with optional --one-shot flag\n\t// Use provided executable path or fallback to 'il'\n\tconst executable = executablePath ?? 'iloom'\n\tlet launchCommand = `${executable} spin`\n\tif (oneShot !== 'default') {\n\t\tlaunchCommand += ` --one-shot=${oneShot}`\n\t}\n\n\t// Append --set arguments if provided\n\tif (setArguments && setArguments.length > 0) {\n\t\tfor (const setArg of setArguments) {\n\t\t\tlaunchCommand += ` --set ${setArg}`\n\t\t}\n\t}\n\n\t// Apply terminal background color if branch name available\n\tlet backgroundColor: { r: number; g: number; b: number } | undefined\n\tif (branchName) {\n\t\ttry {\n\t\t\tconst { generateColorFromBranchName } = await import('./color.js')\n\t\t\tconst colorData = generateColorFromBranchName(branchName)\n\t\t\tbackgroundColor = colorData.rgb\n\t\t} catch (error) {\n\t\t\tlogger.warn(\n\t\t\t\t`Failed to generate terminal color: ${error instanceof Error ? error.message : 'Unknown error'}`\n\t\t\t)\n\t\t}\n\t}\n\n\t// Check if .env file exists in workspace\n\tconst hasEnvFile = existsSync(join(workspacePath, '.env'))\n\n\t// Open new terminal window with Claude\n\tawait openTerminalWindow({\n\t\tworkspacePath,\n\t\tcommand: launchCommand,\n\t\t...(backgroundColor && { backgroundColor }),\n\t\tincludeEnvSetup: hasEnvFile, // source .env only if it exists\n\t\t...(port !== undefined && { port, includePortExport: true }),\n\t})\n}\n\n/**\n * Generate a branch name using Claude with fallback\n * This matches the implementation that was working in ClaudeBranchNameStrategy\n */\nexport async function generateBranchName(\n\tissueTitle: string,\n\tissueNumber: string | number,\n\tmodel: string = 'haiku'\n): Promise<string> {\n\ttry {\n\t\t// Check if Claude CLI is available\n\t\tconst isAvailable = await detectClaudeCli()\n\t\tif (!isAvailable) {\n\t\t\tlogger.warn('Claude CLI not available, using fallback branch name')\n\t\t\treturn `feat/issue-${issueNumber}`\n\t\t}\n\n\t\tlogger.debug('Generating branch name with Claude', { issueNumber, issueTitle })\n\n\t\t// Use the proven prompt format from ClaudeBranchNameStrategy\n\t\tconst prompt = `<Task>\nGenerate a git branch name for the following issue:\n<Issue>\n<IssueNumber>${issueNumber}</IssueNumber>\n<IssueTitle>${issueTitle}</IssueTitle>\n</Issue>\n\n<Requirements>\n<IssueNumber>Must use this exact issue number: ${issueNumber}</IssueNumber>\n<Format>Format must be: {prefix}/issue-${issueNumber}__{description}</Format>\n<Prefix>Prefix must be one of: feat, fix, docs, refactor, test, chore</Prefix>\n<MaxLength>Maximum 50 characters total</MaxLength>\n<Characters>Only lowercase letters, numbers, and hyphens allowed</Characters>\n<Output>Reply with ONLY the branch name, nothing else</Output>\n</Requirements>\n</Task>`\n\n\t\tlogger.debug('Sending prompt to Claude', { prompt })\n\n\t\tconst result = (await launchClaude(prompt, {\n\t\t\tmodel,\n\t\t\theadless: true,\n\t\t\tnoSessionPersistence: true, // Utility operation - don't persist session\n\t\t})) as string\n\n\t\t// Normalize to lowercase for consistency (Linear IDs are uppercase but branches should be lowercase)\n\t\tconst branchName = result.trim().toLowerCase()\n\t\tlogger.debug('Claude returned branch name', { branchName, issueNumber })\n\n\t\t// Validate generated name using same validation as ClaudeBranchNameStrategy\n\t\tif (!branchName || !isValidBranchName(branchName, issueNumber)) {\n\t\t\tlogger.warn('Invalid branch name from Claude, using fallback', { branchName })\n\t\t\treturn `feat/issue-${issueNumber}`.toLowerCase()\n\t\t}\n\n\t\treturn branchName\n\t} catch (error) {\n\t\tlogger.warn('Failed to generate branch name with Claude', { error })\n\t\treturn `feat/issue-${issueNumber}`.toLowerCase()\n\t}\n}\n\n/**\n * Validate branch name format\n * Check format: {prefix}/issue-{number}__{description}\n * Uses case-insensitive matching for issue number (Linear uses uppercase like MARK-1)\n */\nfunction isValidBranchName(name: string, issueNumber: string | number): boolean {\n\tconst pattern = new RegExp(`^(feat|fix|docs|refactor|test|chore)/issue-${issueNumber}__[a-z0-9-]+$`, 'i')\n\treturn pattern.test(name) && name.length <= 50\n}\n"],"mappings":";;;;;;;;;;AAAA,SAAS,aAAa;AACtB,SAAS,kBAAkB;AAC3B,SAAS,YAAY;AACrB,SAAS,YAAY,kBAAkB;AAUhC,SAAS,+BAA+B,cAA8B;AAE5E,QAAM,gBAAgB;AAGtB,QAAM,OAAO,WAAW,MAAM;AAG9B,QAAM,iBAAiB,OAAO,KAAK,cAAc,QAAQ,MAAM,EAAE,GAAG,KAAK;AACzE,OAAK,OAAO,cAAc;AAC1B,OAAK,OAAO,YAAY;AAExB,QAAM,SAAS,KAAK,OAAO;AAK3B,QAAM,QAAQ,MAAM,KAAK,OAAO,SAAS,GAAG,EAAE,CAAC;AAG/C,QAAM,QAAQ,MAAM,CAAC,KAAK;AAC1B,QAAM,CAAC,IAAK,QAAQ,KAAQ;AAG5B,QAAM,QAAQ,MAAM,CAAC,KAAK;AAC1B,QAAM,CAAC,IAAK,QAAQ,KAAQ;AAG5B,QAAM,MAAM,OAAO,KAAK,KAAK,EAAE,SAAS,KAAK;AAC7C,SAAO,GAAG,IAAI,MAAM,GAAG,CAAC,CAAC,IAAI,IAAI,MAAM,GAAG,EAAE,CAAC,IAAI,IAAI,MAAM,IAAI,EAAE,CAAC,IAAI,IAAI,MAAM,IAAI,EAAE,CAAC,IAAI,IAAI,MAAM,IAAI,EAAE,CAAC;AAC7G;AAOO,SAAS,0BAAkC;AACjD,SAAO,WAAW;AACnB;AA6BA,eAAsB,kBAAoC;AACzD,MAAI;AAEH,UAAM,MAAM,WAAW,CAAC,MAAM,QAAQ,GAAG;AAAA,MACxC,OAAO;AAAA,MACP,SAAS;AAAA,IACV,CAAC;AACD,WAAO;AAAA,EACR,SAAS,OAAO;AAEf,WAAO,MAAM,4BAA4B,EAAE,MAAM,CAAC;AAClD,WAAO;AAAA,EACR;AACD;AAKA,eAAsB,mBAA2C;AAChE,MAAI;AACH,UAAM,SAAS,MAAM,MAAM,UAAU,CAAC,WAAW,GAAG;AAAA,MACnD,SAAS;AAAA,IACV,CAAC;AACD,WAAO,OAAO,OAAO,KAAK;AAAA,EAC3B,SAAS,OAAO;AACf,WAAO,KAAK,gCAAgC,EAAE,MAAM,CAAC;AACrD,WAAO;AAAA,EACR;AACD;AAKA,SAAS,sBAAsB,QAAwB;AACtD,MAAI;AAEH,UAAM,QAAQ,OAAO,MAAM,IAAI,EAAE,OAAO,UAAQ,KAAK,KAAK,CAAC;AAG3D,QAAI,aAAa;AACjB,eAAW,QAAQ,OAAO;AACzB,UAAI;AACH,cAAM,UAAU,KAAK,MAAM,IAAI;AAC/B,YAAI,WAAW,OAAO,YAAY,YAAY,QAAQ,SAAS,YAAY,YAAY,SAAS;AAC/F,uBAAa,QAAQ;AAAA,QACtB;AAAA,MACD,QAAQ;AAEP;AAAA,MACD;AAAA,IACD;AAEA,WAAO,cAAc;AAAA,EACtB,QAAQ;AAEP,WAAO;AAAA,EACR;AACD;AAMA,eAAsB,aACrB,QACA,UAA4B,CAAC,GACJ;AACzB,QAAM,EAAE,OAAO,gBAAgB,QAAQ,WAAW,OAAO,oBAAoB,WAAW,cAAc,iBAAiB,QAAQ,WAAW,sBAAsB,cAAc,SAAS,UAAU,KAAK,SAAS,IAAI;AACnN,QAAM,MAAM,UAAU;AAGtB,QAAM,OAAiB,CAAC;AAExB,MAAI,UAAU;AACb,SAAK,KAAK,IAAI;AAGd,UAAM,wBAAwB,gBAAgB;AAC9C,SAAK,KAAK,mBAAmB,qBAAqB;AAGlD,QAAI,YAAY,OAAO;AACtB,WAAK,KAAK,WAAW;AAAA,IACtB;AAAA,EACD;AAEA,MAAI,OAAO;AACV,SAAK,KAAK,WAAW,KAAK;AAAA,EAC3B;AAEA,MAAI,kBAAkB,mBAAmB,WAAW;AACnD,SAAK,KAAK,qBAAqB,cAAc;AAAA,EAC9C;AAEA,MAAI,QAAQ;AACX,SAAK,KAAK,aAAa,MAAM;AAAA,EAC9B;AAEA,OAAK,KAAK,aAAa,MAAM;AAG7B,MAAI,oBAAoB;AACvB,SAAK,KAAK,0BAA0B,kBAAkB;AAAA,EACvD;AAGA,MAAI,aAAa,UAAU,SAAS,GAAG;AACtC,eAAW,UAAU,WAAW;AAC/B,WAAK,KAAK,gBAAgB,KAAK,UAAU,MAAM,CAAC;AAAA,IACjD;AAAA,EACD;AAGA,MAAI,gBAAgB,aAAa,SAAS,GAAG;AAC5C,SAAK,KAAK,mBAAmB,GAAG,YAAY;AAAA,EAC7C;AAGA,MAAI,mBAAmB,gBAAgB,SAAS,GAAG;AAClD,SAAK,KAAK,sBAAsB,GAAG,eAAe;AAAA,EACnD;AAGA,MAAI,QAAQ;AACX,SAAK,KAAK,YAAY,KAAK,UAAU,MAAM,CAAC;AAAA,EAC7C;AAGA,MAAI,WAAW;AACd,SAAK,KAAK,gBAAgB,SAAS;AAAA,EACpC;AAIA,MAAI,wBAAwB,UAAU;AACrC,SAAK,KAAK,0BAA0B;AAAA,EACrC;AAGA,QAAM,YAAY,EAAE,GAAG,QAAQ,KAAK,YAAY,IAAI;AAEpD,MAAI;AACH,QAAI,UAAU;AAEb,YAAM,cAAc,OAAO,eAAe;AAG1C,YAAM,eAAe;AAAA,QACpB,OAAO;AAAA,QACP,SAAS;AAAA;AAAA,QACT,GAAI,UAAU,EAAE,KAAK,OAAO;AAAA;AAAA,QAC5B,SAAS;AAAA,QACT,KAAK,EAAE,GAAG,WAAW,GAAG,SAAS;AAAA;AAAA,QACjC,GAAI,eAAe,EAAE,OAAO,CAAC,QAAQ,QAAQ,MAAM,EAAW;AAAA;AAAA,MAC/D;AAEA,YAAM,aAAa,MAAM,UAAU,MAAM,YAAY;AAGrD,YAAM,qBAAqB,KAAK,SAAS,iBAAiB,KAAK,KAAK,SAAS,aAAa;AAG1F,UAAI,eAAe;AACnB,UAAI,cAAc;AAClB,UAAI,kBAAkB;AACtB,UAAI,WAAW,UAAU,OAAO,WAAW,OAAO,OAAO,YAAY;AACpE,sBAAc;AACd,mBAAW,OAAO,GAAG,QAAQ,CAAC,UAAkB;AAC/C,gBAAM,OAAO,MAAM,SAAS;AAC5B,0BAAgB;AAEhB,cAAI,aAAa,UAAU;AAE1B,oBAAQ,OAAO,MAAM,IAAI;AAAA,UAC1B,WAAW,aAAa,QAAQ;AAAA,UAGhC,WAAW,aAAa;AACvB,gBAAI,OAAO,MAAM,IAAI;AAAA,UACtB,OAAO;AAEN,gBAAI,iBAAiB;AACpB,kBAAI,OAAO,MAAM,aAAM;AACvB,gCAAkB;AAAA,YACnB,OAAO;AACN,kBAAI,OAAO,MAAM,GAAG;AAAA,YACrB;AAAA,UACD;AAAA,QACD,CAAC;AAAA,MACF;AAEA,YAAM,SAAS,MAAM;AAGrB,UAAI,aAAa;AAChB,cAAM,YAAY,aAAa,KAAK;AAGpC,YAAI,CAAC,eAAe,CAAC,UAAU;AAC9B,cAAI,OAAO,MAAM,IAAI;AAAA,QACtB;AAEA,eAAO,qBAAqB,sBAAsB,SAAS,IAAI;AAAA,MAChE,OAAO;AAEN,YAAI,aAAa;AAEhB,cAAI,OAAO,MAAM,OAAO,MAAM;AAC9B,cAAI,OAAO,UAAU,CAAC,OAAO,OAAO,SAAS,IAAI,GAAG;AACnD,gBAAI,OAAO,MAAM,IAAI;AAAA,UACtB;AAAA,QACD,OAAO;AAEN,cAAI,OAAO,MAAM,aAAM;AACvB,cAAI,OAAO,MAAM,IAAI;AAAA,QACtB;AACA,cAAM,YAAY,OAAO,OAAO,KAAK;AACrC,eAAO,qBAAqB,sBAAsB,SAAS,IAAI;AAAA,MAChE;AAAA,IACD,OAAO;AAON,UAAI;AACH,cAAM,MAAM,UAAU,CAAC,GAAG,MAAM,MAAM,MAAM,GAAG;AAAA,UAC9C,GAAI,UAAU,EAAE,KAAK,OAAO;AAAA,UAC5B,OAAO,CAAC,WAAW,WAAW,MAAM;AAAA;AAAA,UACpC,SAAS;AAAA;AAAA,UACT,SAAS,OAAO,eAAe;AAAA,UAC/B,KAAK,EAAE,GAAG,WAAW,GAAG,SAAS;AAAA;AAAA,QAClC,CAAC;AACD;AAAA,MACD,SAAS,kBAAkB;AAC1B,cAAM,wBAAwB;AAC9B,cAAM,0BAA0B,sBAAsB,UAAU,sBAAsB,WAAW;AAGjG,cAAM,eAAe,wBAAwB,MAAM,4CAA4C;AAC/F,cAAM,oBAAoB,6CAAe;AACzC,YAAI,gBAAgB,aAAa,mBAAmB;AACnD,cAAI,MAAM,cAAc,iBAAiB,yCAAyC;AAGlF,gBAAM,aAAa,KAAK,OAAO,CAAC,KAAK,QAAQ;AAC5C,gBAAI,QAAQ,eAAgB,QAAO;AACnC,gBAAI,MAAM,KAAK,KAAK,MAAM,CAAC,MAAM,eAAgB,QAAO;AACxD,mBAAO;AAAA,UACR,CAAC;AACD,qBAAW,KAAK,YAAY,iBAAiB;AAI7C,gBAAM,MAAM,UAAU,YAAY;AAAA,YACjC,GAAI,UAAU,EAAE,KAAK,OAAO;AAAA,YAC5B,OAAO;AAAA,YACP,SAAS;AAAA,YACT,SAAS,OAAO,eAAe;AAAA,YAC/B,KAAK;AAAA,UACN,CAAC;AACD;AAAA,QACD;AAGA,cAAM;AAAA,MACP;AAAA,IACD;AAAA,EACD,SAAS,OAAO;AAEf,UAAM,aAAa;AAMnB,UAAM,eAAe,WAAW,UAAU,WAAW,WAAW;AAGhE,UAAM,oBAAoB,aAAa,MAAM,4CAA4C;AACzF,UAAM,qBAAqB,uDAAoB;AAC/C,QAAI,qBAAqB,aAAa,oBAAoB;AACzD,UAAI,MAAM,cAAc,kBAAkB,yCAAyC;AAGnF,YAAM,aAAa,KAAK,OAAO,CAAC,KAAK,QAAQ;AAE5C,YAAI,QAAQ,eAAgB,QAAO;AACnC,YAAI,MAAM,KAAK,KAAK,MAAM,CAAC,MAAM,eAAgB,QAAO;AACxD,eAAO;AAAA,MACR,CAAC;AACD,iBAAW,KAAK,YAAY,kBAAkB;AAE9C,UAAI;AACH,YAAI,UAAU;AACb,gBAAM,cAAc,OAAO,eAAe;AAG1C,gBAAM,eAAe;AAAA,YACpB,OAAO;AAAA,YACP,SAAS;AAAA,YACT,GAAI,UAAU,EAAE,KAAK,OAAO;AAAA,YAC5B,SAAS;AAAA,YACT,KAAK;AAAA,YACL,GAAI,eAAe,EAAE,OAAO,CAAC,QAAQ,QAAQ,MAAM,EAAW;AAAA,UAC/D;AAEA,gBAAM,aAAa,MAAM,UAAU,YAAY,YAAY;AAC3D,gBAAM,qBAAqB,WAAW,SAAS,iBAAiB,KAAK,WAAW,SAAS,aAAa;AAEtG,cAAI,eAAe;AACnB,cAAI,cAAc;AAClB,cAAI,kBAAkB;AACtB,cAAI,WAAW,UAAU,OAAO,WAAW,OAAO,OAAO,YAAY;AACpE,0BAAc;AACd,uBAAW,OAAO,GAAG,QAAQ,CAAC,UAAkB;AAC/C,oBAAM,OAAO,MAAM,SAAS;AAC5B,8BAAgB;AAChB,kBAAI,aAAa,UAAU;AAC1B,wBAAQ,OAAO,MAAM,IAAI;AAAA,cAC1B,WAAW,aAAa,QAAQ;AAAA,cAEhC,WAAW,aAAa;AACvB,oBAAI,OAAO,MAAM,IAAI;AAAA,cACtB,OAAO;AACN,oBAAI,iBAAiB;AACpB,sBAAI,OAAO,MAAM,aAAM;AACvB,oCAAkB;AAAA,gBACnB,OAAO;AACN,sBAAI,OAAO,MAAM,GAAG;AAAA,gBACrB;AAAA,cACD;AAAA,YACD,CAAC;AAAA,UACF;AAEA,gBAAM,SAAS,MAAM;AAErB,cAAI,aAAa;AAChB,kBAAM,YAAY,aAAa,KAAK;AACpC,gBAAI,CAAC,eAAe,CAAC,UAAU;AAC9B,kBAAI,OAAO,MAAM,IAAI;AAAA,YACtB;AACA,mBAAO,qBAAqB,sBAAsB,SAAS,IAAI;AAAA,UAChE,OAAO;AACN,gBAAI,aAAa;AAChB,kBAAI,OAAO,MAAM,OAAO,MAAM;AAC9B,kBAAI,OAAO,UAAU,CAAC,OAAO,OAAO,SAAS,IAAI,GAAG;AACnD,oBAAI,OAAO,MAAM,IAAI;AAAA,cACtB;AAAA,YACD,OAAO;AACN,kBAAI,OAAO,MAAM,aAAM;AACvB,kBAAI,OAAO,MAAM,IAAI;AAAA,YACtB;AACA,kBAAM,YAAY,OAAO,OAAO,KAAK;AACrC,mBAAO,qBAAqB,sBAAsB,SAAS,IAAI;AAAA,UAChE;AAAA,QACD,OAAO;AAEN,gBAAM,MAAM,UAAU,YAAY;AAAA,YACjC,GAAI,UAAU,EAAE,KAAK,OAAO;AAAA,YAC5B,OAAO;AAAA,YACP,SAAS;AAAA,YACT,SAAS,OAAO,eAAe;AAAA,YAC/B,KAAK;AAAA,UACN,CAAC;AACD;AAAA,QACD;AAAA,MACD,SAAS,YAAY;AACpB,cAAM,kBAAkB;AACxB,cAAM,oBAAoB,gBAAgB,UAAU,gBAAgB,WAAW;AAC/E,cAAM,IAAI,MAAM,qBAAqB,iBAAiB,EAAE;AAAA,MACzD;AAAA,IACD;AAGA,UAAM,IAAI,MAAM,qBAAqB,YAAY,EAAE;AAAA,EACpD;AACD;AAOA,eAAsB,gCACrB,SACA,SAGgB;AAChB,QAAM,EAAE,eAAe,YAAY,UAAU,WAAW,MAAM,cAAc,eAAe,IAAI;AAG/F,MAAI,CAAC,eAAe;AACnB,UAAM,IAAI,MAAM,sDAAsD;AAAA,EACvE;AAIA,QAAM,aAAa,kBAAkB;AACrC,MAAI,gBAAgB,GAAG,UAAU;AACjC,MAAI,YAAY,WAAW;AAC1B,qBAAiB,eAAe,OAAO;AAAA,EACxC;AAGA,MAAI,gBAAgB,aAAa,SAAS,GAAG;AAC5C,eAAW,UAAU,cAAc;AAClC,uBAAiB,UAAU,MAAM;AAAA,IAClC;AAAA,EACD;AAGA,MAAI;AACJ,MAAI,YAAY;AACf,QAAI;AACH,YAAM,EAAE,4BAA4B,IAAI,MAAM,OAAO,qBAAY;AACjE,YAAM,YAAY,4BAA4B,UAAU;AACxD,wBAAkB,UAAU;AAAA,IAC7B,SAAS,OAAO;AACf,aAAO;AAAA,QACN,sCAAsC,iBAAiB,QAAQ,MAAM,UAAU,eAAe;AAAA,MAC/F;AAAA,IACD;AAAA,EACD;AAGA,QAAM,aAAa,WAAW,KAAK,eAAe,MAAM,CAAC;AAGzD,QAAM,mBAAmB;AAAA,IACxB;AAAA,IACA,SAAS;AAAA,IACT,GAAI,mBAAmB,EAAE,gBAAgB;AAAA,IACzC,iBAAiB;AAAA;AAAA,IACjB,GAAI,SAAS,UAAa,EAAE,MAAM,mBAAmB,KAAK;AAAA,EAC3D,CAAC;AACF;AAMA,eAAsB,mBACrB,YACA,aACA,QAAgB,SACE;AAClB,MAAI;AAEH,UAAM,cAAc,MAAM,gBAAgB;AAC1C,QAAI,CAAC,aAAa;AACjB,aAAO,KAAK,sDAAsD;AAClE,aAAO,cAAc,WAAW;AAAA,IACjC;AAEA,WAAO,MAAM,sCAAsC,EAAE,aAAa,WAAW,CAAC;AAG9E,UAAM,SAAS;AAAA;AAAA;AAAA,eAGF,WAAW;AAAA,cACZ,UAAU;AAAA;AAAA;AAAA;AAAA,iDAIyB,WAAW;AAAA,yCACnB,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQlD,WAAO,MAAM,4BAA4B,EAAE,OAAO,CAAC;AAEnD,UAAM,SAAU,MAAM,aAAa,QAAQ;AAAA,MAC1C;AAAA,MACA,UAAU;AAAA,MACV,sBAAsB;AAAA;AAAA,IACvB,CAAC;AAGD,UAAM,aAAa,OAAO,KAAK,EAAE,YAAY;AAC7C,WAAO,MAAM,+BAA+B,EAAE,YAAY,YAAY,CAAC;AAGvE,QAAI,CAAC,cAAc,CAAC,kBAAkB,YAAY,WAAW,GAAG;AAC/D,aAAO,KAAK,mDAAmD,EAAE,WAAW,CAAC;AAC7E,aAAO,cAAc,WAAW,GAAG,YAAY;AAAA,IAChD;AAEA,WAAO;AAAA,EACR,SAAS,OAAO;AACf,WAAO,KAAK,8CAA8C,EAAE,MAAM,CAAC;AACnE,WAAO,cAAc,WAAW,GAAG,YAAY;AAAA,EAChD;AACD;AAOA,SAAS,kBAAkB,MAAc,aAAuC;AAC/E,QAAM,UAAU,IAAI,OAAO,8CAA8C,WAAW,iBAAiB,GAAG;AACxG,SAAO,QAAQ,KAAK,IAAI,KAAK,KAAK,UAAU;AAC7C;","names":[]}