@iloom/cli 0.9.2 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +160 -41
  3. package/dist/{BranchNamingService-K6XNWQ6C.js → BranchNamingService-25KSZAEM.js} +2 -2
  4. package/dist/ClaudeContextManager-66GR4BGM.js +14 -0
  5. package/dist/ClaudeService-7KM5NA5Z.js +13 -0
  6. package/dist/{GitHubService-TGWJN4V4.js → GitHubService-MEHKHUQP.js} +4 -4
  7. package/dist/IssueTrackerFactory-NG53YX5S.js +14 -0
  8. package/dist/{LoomLauncher-73NXL2CL.js → LoomLauncher-TDLZSYG2.js} +9 -9
  9. package/dist/{MetadataManager-W3C54UYT.js → MetadataManager-5QZSTKNN.js} +2 -2
  10. package/dist/{ProjectCapabilityDetector-N5L7T4IY.js → ProjectCapabilityDetector-5KSYUTBJ.js} +3 -3
  11. package/dist/{PromptTemplateManager-36YLQRHP.js → PromptTemplateManager-YOE2SIPG.js} +2 -2
  12. package/dist/README.md +160 -41
  13. package/dist/{SettingsManager-AW3JTJHD.js → SettingsManager-FNKCOZMQ.js} +4 -2
  14. package/dist/agents/iloom-artifact-reviewer.md +11 -0
  15. package/dist/agents/iloom-code-reviewer.md +14 -0
  16. package/dist/agents/iloom-issue-analyze-and-plan.md +55 -12
  17. package/dist/agents/iloom-issue-analyzer.md +49 -6
  18. package/dist/agents/iloom-issue-complexity-evaluator.md +47 -6
  19. package/dist/agents/iloom-issue-enhancer.md +86 -7
  20. package/dist/agents/iloom-issue-implementer.md +48 -7
  21. package/dist/agents/iloom-issue-planner.md +115 -62
  22. package/dist/{build-THZI572G.js → build-VHGEMXBA.js} +9 -9
  23. package/dist/chunk-4232AHNQ.js +35 -0
  24. package/dist/chunk-4232AHNQ.js.map +1 -0
  25. package/dist/chunk-4E7LCFUG.js +24 -0
  26. package/dist/chunk-4E7LCFUG.js.map +1 -0
  27. package/dist/{chunk-AR5QKYNE.js → chunk-4FGEGQW4.js} +4 -4
  28. package/dist/{chunk-R4YWBGY6.js → chunk-5FJWO4IT.js} +67 -22
  29. package/dist/chunk-5FJWO4IT.js.map +1 -0
  30. package/dist/{chunk-VPTAX5TR.js → chunk-5RPBYK5Q.js} +35 -30
  31. package/dist/chunk-5RPBYK5Q.js.map +1 -0
  32. package/dist/{chunk-YKFCCV6S.js → chunk-63QWFWH3.js} +7 -7
  33. package/dist/chunk-63QWFWH3.js.map +1 -0
  34. package/dist/{chunk-RI2YL6TK.js → chunk-7VHJNVLF.js} +80 -23
  35. package/dist/chunk-7VHJNVLF.js.map +1 -0
  36. package/dist/{chunk-B7U6OKUR.js → chunk-C6HNNJIV.js} +11 -3
  37. package/dist/chunk-C6HNNJIV.js.map +1 -0
  38. package/dist/{chunk-A7NJF73J.js → chunk-CVCTIDDK.js} +4 -4
  39. package/dist/{chunk-Z2TWEXR7.js → chunk-E6KOWMKA.js} +6 -6
  40. package/dist/chunk-E6KOWMKA.js.map +1 -0
  41. package/dist/{chunk-3I4ONZRT.js → chunk-EVPZFV3K.js} +10 -10
  42. package/dist/chunk-EVPZFV3K.js.map +1 -0
  43. package/dist/{chunk-IZIYLYPK.js → chunk-G5V75JD5.js} +2 -2
  44. package/dist/chunk-GRISNU6G.js +651 -0
  45. package/dist/chunk-GRISNU6G.js.map +1 -0
  46. package/dist/chunk-HEXKPKCK.js +1396 -0
  47. package/dist/chunk-HEXKPKCK.js.map +1 -0
  48. package/dist/{chunk-TC7APDKU.js → chunk-I5T677EA.js} +2 -2
  49. package/dist/{chunk-KBEIQP4G.js → chunk-KB64WNBZ.js} +43 -3
  50. package/dist/chunk-KB64WNBZ.js.map +1 -0
  51. package/dist/{chunk-NWMORW3U.js → chunk-KIK2ZFAL.js} +2 -2
  52. package/dist/{chunk-CWRI4JC3.js → chunk-KKV5WH5M.js} +30 -31
  53. package/dist/chunk-KKV5WH5M.js.map +1 -0
  54. package/dist/{chunk-DGG2VY7B.js → chunk-KVHIAWVT.js} +9 -9
  55. package/dist/chunk-KVHIAWVT.js.map +1 -0
  56. package/dist/{chunk-OFDN5NKS.js → chunk-KXDRI47U.js} +69 -12
  57. package/dist/chunk-KXDRI47U.js.map +1 -0
  58. package/dist/{chunk-NUACL52E.js → chunk-LLHXQS3C.js} +2 -2
  59. package/dist/chunk-LUKXJSRI.js +73 -0
  60. package/dist/chunk-LUKXJSRI.js.map +1 -0
  61. package/dist/{chunk-TL72BGP6.js → chunk-MORRVYPT.js} +2 -2
  62. package/dist/chunk-OTGH2HRS.js +1427 -0
  63. package/dist/chunk-OTGH2HRS.js.map +1 -0
  64. package/dist/{chunk-7ZEHSSUP.js → chunk-P4O6EH46.js} +4 -4
  65. package/dist/{chunk-KAYXR544.js → chunk-QVLPWNE3.js} +2 -2
  66. package/dist/chunk-QZWEJVWV.js +207 -0
  67. package/dist/chunk-QZWEJVWV.js.map +1 -0
  68. package/dist/chunk-RJ3VBUFK.js +781 -0
  69. package/dist/chunk-RJ3VBUFK.js.map +1 -0
  70. package/dist/chunk-RSYT7MVI.js +202 -0
  71. package/dist/chunk-RSYT7MVI.js.map +1 -0
  72. package/dist/{chunk-6IIL5M2L.js → chunk-S7PZA6IV.js} +10 -8
  73. package/dist/{chunk-6IIL5M2L.js.map → chunk-S7PZA6IV.js.map} +1 -1
  74. package/dist/chunk-SKSYYBCU.js +229 -0
  75. package/dist/chunk-SKSYYBCU.js.map +1 -0
  76. package/dist/{chunk-ULSWCPQG.js → chunk-SWSJWA2S.js} +476 -5
  77. package/dist/chunk-SWSJWA2S.js.map +1 -0
  78. package/dist/{chunk-KXGQYLFZ.js → chunk-UKBAJ2QQ.js} +61 -7
  79. package/dist/chunk-UKBAJ2QQ.js.map +1 -0
  80. package/dist/{chunk-FO5GGFOV.js → chunk-UR5DGNUO.js} +71 -9
  81. package/dist/chunk-UR5DGNUO.js.map +1 -0
  82. package/dist/{chunk-QN47QVBX.js → chunk-UUEW5KWB.js} +1 -1
  83. package/dist/chunk-UUEW5KWB.js.map +1 -0
  84. package/dist/{chunk-4CO6KG5S.js → chunk-VG45TUYK.js} +53 -7
  85. package/dist/{chunk-4CO6KG5S.js.map → chunk-VG45TUYK.js.map} +1 -1
  86. package/dist/{chunk-4LKGCFGG.js → chunk-WWKOVDWC.js} +2 -2
  87. package/dist/{chunk-KJTVU3HZ.js → chunk-WXIM2WS7.js} +8 -8
  88. package/dist/chunk-WXIM2WS7.js.map +1 -0
  89. package/dist/{chunk-VOGGLPG5.js → chunk-YQ57ORTV.js} +14 -1
  90. package/dist/chunk-YQ57ORTV.js.map +1 -0
  91. package/dist/{chunk-SOSQILHO.js → chunk-ZNMPGMHY.js} +44 -797
  92. package/dist/chunk-ZNMPGMHY.js.map +1 -0
  93. package/dist/{claude-TP2QO3BU.js → claude-7GGEWVEM.js} +2 -2
  94. package/dist/{cleanup-PJRIFFU4.js → cleanup-6PVAC4NI.js} +85 -34
  95. package/dist/cleanup-6PVAC4NI.js.map +1 -0
  96. package/dist/cli.js +630 -801
  97. package/dist/cli.js.map +1 -1
  98. package/dist/{commit-IVP3M4HG.js → commit-FZR5XDQG.js} +26 -23
  99. package/dist/commit-FZR5XDQG.js.map +1 -0
  100. package/dist/{compile-R2J65HBQ.js → compile-7ALJHZ4N.js} +9 -9
  101. package/dist/{contribute-VDZXHK5Y.js → contribute-5GKLK3BQ.js} +14 -6
  102. package/dist/contribute-5GKLK3BQ.js.map +1 -0
  103. package/dist/{dev-server-7F622OEO.js → dev-server-7SMIB7OF.js} +29 -15
  104. package/dist/dev-server-7SMIB7OF.js.map +1 -0
  105. package/dist/{feedback-E7VET7CL.js → feedback-G2GJFN2F.js} +18 -16
  106. package/dist/{feedback-E7VET7CL.js.map → feedback-G2GJFN2F.js.map} +1 -1
  107. package/dist/{git-2QDQ2X2S.js → git-GTLKAZRJ.js} +4 -4
  108. package/dist/hooks/iloom-hook.js +15 -0
  109. package/dist/ignite-H2O5Y5A2.js +34 -0
  110. package/dist/ignite-H2O5Y5A2.js.map +1 -0
  111. package/dist/index.d.ts +482 -58
  112. package/dist/index.js +1340 -44
  113. package/dist/index.js.map +1 -1
  114. package/dist/{init-676DHF6R.js → init-32YOKXRL.js} +57 -21
  115. package/dist/init-32YOKXRL.js.map +1 -0
  116. package/dist/{issues-PJSOLOBJ.js → issues-4UUAQ5K6.js} +61 -20
  117. package/dist/issues-4UUAQ5K6.js.map +1 -0
  118. package/dist/{lint-CJM7BAIM.js → lint-AAN2NZWG.js} +9 -9
  119. package/dist/mcp/harness-server.js +140 -0
  120. package/dist/mcp/harness-server.js.map +1 -0
  121. package/dist/mcp/issue-management-server.js +2599 -262
  122. package/dist/mcp/issue-management-server.js.map +1 -1
  123. package/dist/mcp/recap-server.js +144 -21
  124. package/dist/mcp/recap-server.js.map +1 -1
  125. package/dist/{neon-helpers-VVFFTLXE.js → neon-helpers-CQN2PB4S.js} +3 -3
  126. package/dist/neon-helpers-CQN2PB4S.js.map +1 -0
  127. package/dist/{open-544H7JF5.js → open-FXWW3VI4.js} +15 -15
  128. package/dist/open-FXWW3VI4.js.map +1 -0
  129. package/dist/{plan-Q7ELXDLC.js → plan-RQ5FPIGF.js} +358 -40
  130. package/dist/plan-RQ5FPIGF.js.map +1 -0
  131. package/dist/{projects-LH362JZQ.js → projects-2UOXFLNZ.js} +4 -4
  132. package/dist/prompts/CLAUDE.md +62 -0
  133. package/dist/prompts/init-prompt.txt +430 -34
  134. package/dist/prompts/issue-prompt.txt +473 -54
  135. package/dist/prompts/plan-prompt.txt +140 -19
  136. package/dist/prompts/pr-prompt.txt +44 -1
  137. package/dist/prompts/regular-prompt.txt +42 -1
  138. package/dist/prompts/session-summary-prompt.txt +14 -0
  139. package/dist/prompts/swarm-orchestrator-prompt.txt +464 -0
  140. package/dist/{rebase-YND35CIE.js → rebase-6NVLX5V7.js} +21 -12
  141. package/dist/rebase-6NVLX5V7.js.map +1 -0
  142. package/dist/{recap-3W7COH7D.js → recap-OMBOKJST.js} +47 -19
  143. package/dist/recap-OMBOKJST.js.map +1 -0
  144. package/dist/{run-QUXJKDQQ.js → run-BBXLRIZB.js} +15 -15
  145. package/dist/run-BBXLRIZB.js.map +1 -0
  146. package/dist/schema/package-iloom.schema.json +58 -0
  147. package/dist/schema/settings.schema.json +149 -15
  148. package/dist/{shell-QGECBLST.js → shell-RF7LTND5.js} +14 -7
  149. package/dist/shell-RF7LTND5.js.map +1 -0
  150. package/dist/{summary-G2T4452H.js → summary-WTQZ7XG2.js} +27 -25
  151. package/dist/summary-WTQZ7XG2.js.map +1 -0
  152. package/dist/{test-EA5NQFDC.js → test-SGO6I5Z7.js} +9 -9
  153. package/dist/{test-git-M7LSLEFL.js → test-git-XM4TM65W.js} +4 -4
  154. package/dist/test-jira-LDTOYFSD.js +96 -0
  155. package/dist/test-jira-LDTOYFSD.js.map +1 -0
  156. package/dist/{test-prefix-64NAAUON.js → test-prefix-GBO37XCN.js} +4 -4
  157. package/dist/{test-webserver-OK6Z5FJM.js → test-webserver-NZ3JTVLL.js} +6 -6
  158. package/dist/{vscode-AR5NNXXI.js → vscode-6XUGHJKL.js} +7 -7
  159. package/package.json +5 -1
  160. package/dist/ClaudeContextManager-HR5JQKAI.js +0 -14
  161. package/dist/ClaudeService-TK7FMC2X.js +0 -13
  162. package/dist/chunk-3I4ONZRT.js.map +0 -1
  163. package/dist/chunk-B7U6OKUR.js.map +0 -1
  164. package/dist/chunk-CWRI4JC3.js.map +0 -1
  165. package/dist/chunk-DGG2VY7B.js.map +0 -1
  166. package/dist/chunk-FJDRTVJX.js +0 -520
  167. package/dist/chunk-FJDRTVJX.js.map +0 -1
  168. package/dist/chunk-FO5GGFOV.js.map +0 -1
  169. package/dist/chunk-KBEIQP4G.js.map +0 -1
  170. package/dist/chunk-KJTVU3HZ.js.map +0 -1
  171. package/dist/chunk-KXGQYLFZ.js.map +0 -1
  172. package/dist/chunk-OFDN5NKS.js.map +0 -1
  173. package/dist/chunk-QN47QVBX.js.map +0 -1
  174. package/dist/chunk-R4YWBGY6.js.map +0 -1
  175. package/dist/chunk-RI2YL6TK.js.map +0 -1
  176. package/dist/chunk-SOSQILHO.js.map +0 -1
  177. package/dist/chunk-ULSWCPQG.js.map +0 -1
  178. package/dist/chunk-VOGGLPG5.js.map +0 -1
  179. package/dist/chunk-VPTAX5TR.js.map +0 -1
  180. package/dist/chunk-W6DP5RVR.js +0 -101
  181. package/dist/chunk-W6DP5RVR.js.map +0 -1
  182. package/dist/chunk-WHI5KEOX.js +0 -121
  183. package/dist/chunk-WHI5KEOX.js.map +0 -1
  184. package/dist/chunk-YKFCCV6S.js.map +0 -1
  185. package/dist/chunk-Z2TWEXR7.js.map +0 -1
  186. package/dist/cleanup-PJRIFFU4.js.map +0 -1
  187. package/dist/commit-IVP3M4HG.js.map +0 -1
  188. package/dist/contribute-VDZXHK5Y.js.map +0 -1
  189. package/dist/dev-server-7F622OEO.js.map +0 -1
  190. package/dist/ignite-IW35CDBD.js +0 -784
  191. package/dist/ignite-IW35CDBD.js.map +0 -1
  192. package/dist/init-676DHF6R.js.map +0 -1
  193. package/dist/issues-PJSOLOBJ.js.map +0 -1
  194. package/dist/open-544H7JF5.js.map +0 -1
  195. package/dist/plan-Q7ELXDLC.js.map +0 -1
  196. package/dist/rebase-YND35CIE.js.map +0 -1
  197. package/dist/recap-3W7COH7D.js.map +0 -1
  198. package/dist/run-QUXJKDQQ.js.map +0 -1
  199. package/dist/shell-QGECBLST.js.map +0 -1
  200. package/dist/summary-G2T4452H.js.map +0 -1
  201. /package/dist/{BranchNamingService-K6XNWQ6C.js.map → BranchNamingService-25KSZAEM.js.map} +0 -0
  202. /package/dist/{ClaudeContextManager-HR5JQKAI.js.map → ClaudeContextManager-66GR4BGM.js.map} +0 -0
  203. /package/dist/{ClaudeService-TK7FMC2X.js.map → ClaudeService-7KM5NA5Z.js.map} +0 -0
  204. /package/dist/{GitHubService-TGWJN4V4.js.map → GitHubService-MEHKHUQP.js.map} +0 -0
  205. /package/dist/{MetadataManager-W3C54UYT.js.map → IssueTrackerFactory-NG53YX5S.js.map} +0 -0
  206. /package/dist/{LoomLauncher-73NXL2CL.js.map → LoomLauncher-TDLZSYG2.js.map} +0 -0
  207. /package/dist/{ProjectCapabilityDetector-N5L7T4IY.js.map → MetadataManager-5QZSTKNN.js.map} +0 -0
  208. /package/dist/{PromptTemplateManager-36YLQRHP.js.map → ProjectCapabilityDetector-5KSYUTBJ.js.map} +0 -0
  209. /package/dist/{SettingsManager-AW3JTJHD.js.map → PromptTemplateManager-YOE2SIPG.js.map} +0 -0
  210. /package/dist/{claude-TP2QO3BU.js.map → SettingsManager-FNKCOZMQ.js.map} +0 -0
  211. /package/dist/{build-THZI572G.js.map → build-VHGEMXBA.js.map} +0 -0
  212. /package/dist/{chunk-AR5QKYNE.js.map → chunk-4FGEGQW4.js.map} +0 -0
  213. /package/dist/{chunk-A7NJF73J.js.map → chunk-CVCTIDDK.js.map} +0 -0
  214. /package/dist/{chunk-IZIYLYPK.js.map → chunk-G5V75JD5.js.map} +0 -0
  215. /package/dist/{chunk-TC7APDKU.js.map → chunk-I5T677EA.js.map} +0 -0
  216. /package/dist/{chunk-NWMORW3U.js.map → chunk-KIK2ZFAL.js.map} +0 -0
  217. /package/dist/{chunk-NUACL52E.js.map → chunk-LLHXQS3C.js.map} +0 -0
  218. /package/dist/{chunk-TL72BGP6.js.map → chunk-MORRVYPT.js.map} +0 -0
  219. /package/dist/{chunk-7ZEHSSUP.js.map → chunk-P4O6EH46.js.map} +0 -0
  220. /package/dist/{chunk-KAYXR544.js.map → chunk-QVLPWNE3.js.map} +0 -0
  221. /package/dist/{chunk-4LKGCFGG.js.map → chunk-WWKOVDWC.js.map} +0 -0
  222. /package/dist/{git-2QDQ2X2S.js.map → claude-7GGEWVEM.js.map} +0 -0
  223. /package/dist/{compile-R2J65HBQ.js.map → compile-7ALJHZ4N.js.map} +0 -0
  224. /package/dist/{neon-helpers-VVFFTLXE.js.map → git-GTLKAZRJ.js.map} +0 -0
  225. /package/dist/{lint-CJM7BAIM.js.map → lint-AAN2NZWG.js.map} +0 -0
  226. /package/dist/{projects-LH362JZQ.js.map → projects-2UOXFLNZ.js.map} +0 -0
  227. /package/dist/{test-EA5NQFDC.js.map → test-SGO6I5Z7.js.map} +0 -0
  228. /package/dist/{test-git-M7LSLEFL.js.map → test-git-XM4TM65W.js.map} +0 -0
  229. /package/dist/{test-prefix-64NAAUON.js.map → test-prefix-GBO37XCN.js.map} +0 -0
  230. /package/dist/{test-webserver-OK6Z5FJM.js.map → test-webserver-NZ3JTVLL.js.map} +0 -0
  231. /package/dist/{vscode-AR5NNXXI.js.map → vscode-6XUGHJKL.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/image-processor.ts","../src/mcp/GitHubIssueManagementProvider.ts","../src/utils/linear-markup-converter.ts","../src/mcp/LinearIssueManagementProvider.ts","../src/mcp/JiraIssueManagementProvider.ts","../src/mcp/IssueManagementProviderFactory.ts"],"sourcesContent":["/* global fetch, AbortController, setTimeout, clearTimeout */\nimport { tmpdir } from 'node:os'\nimport { join, extname } from 'node:path'\nimport { existsSync, mkdirSync, createWriteStream, unlinkSync } from 'node:fs'\nimport { pipeline } from 'node:stream/promises'\nimport { Readable } from 'node:stream'\nimport { createHash } from 'node:crypto'\nimport { execa } from 'execa'\nimport { logger } from './logger.js'\nimport type { IssueProvider } from '../mcp/types.js'\n\n/**\n * Represents a matched image in markdown content\n */\nexport interface ImageMatch {\n fullMatch: string\n url: string\n isMarkdown: boolean // true for ![](url), false for <img>\n}\n\n/**\n * Supported image extensions\n */\nconst SUPPORTED_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg']\n\n/**\n * Maximum allowed image size (10MB)\n */\nconst MAX_IMAGE_SIZE = 10 * 1024 * 1024\n\n/**\n * Request timeout in milliseconds (30 seconds)\n */\nconst REQUEST_TIMEOUT_MS = 30000\n\n/**\n * Cache directory path for downloaded images\n */\nexport const CACHE_DIR = join(tmpdir(), 'iloom-images')\n\n/**\n * Cached GitHub auth token (module-level to avoid repeated `gh auth token` calls)\n */\nlet cachedGitHubToken: string | undefined\n\n/**\n * Extract all image URLs from markdown content\n * Handles both ![alt](url) and <img src=\"url\"> formats\n *\n * @param content - Markdown content to parse\n * @returns Array of image matches with full match string and URL\n */\nexport function extractMarkdownImageUrls(content: string): ImageMatch[] {\n if (!content) {\n return []\n }\n\n const matches: ImageMatch[] = []\n\n // Regex for markdown images: ![alt](url)\n // Captures the entire match and the URL separately\n // Handles parentheses in URLs by matching balanced parens\n // The URL part matches: non-paren chars OR (balanced paren group)*, followed by non-paren/non-space chars\n const markdownRegex = /!\\[([^\\]]*)\\]\\(((?:[^()\\s]|\\((?:[^()\\s]|\\([^()]*\\))*\\))+)\\)/g\n let match: RegExpExecArray | null\n\n while ((match = markdownRegex.exec(content)) !== null) {\n const url = match[2]\n if (url) {\n matches.push({\n fullMatch: match[0],\n url,\n isMarkdown: true\n })\n }\n }\n\n // Regex for HTML img tags: <img ... src=\"url\" ...>\n // Handles both double and single quotes, and self-closing tags\n const htmlImgRegex = /<img\\s+[^>]*src=[\"']([^\"']+)[\"'][^>]*\\/?>/gi\n\n while ((match = htmlImgRegex.exec(content)) !== null) {\n const url = match[1]\n if (url) {\n matches.push({\n fullMatch: match[0],\n url,\n isMarkdown: false\n })\n }\n }\n\n return matches\n}\n\n/**\n * Check if URL requires authentication to download\n * - Linear: uploads.linear.app\n * - GitHub: private-user-images.githubusercontent.com\n *\n * @param url - Image URL to check\n * @returns true if URL requires authentication\n */\nexport function isAuthenticatedImageUrl(url: string): boolean {\n try {\n const parsedUrl = new URL(url)\n const hostname = parsedUrl.hostname.toLowerCase()\n\n // Linear uploads require authentication\n if (hostname === 'uploads.linear.app') {\n return true\n }\n\n // GitHub private user images require authentication\n if (hostname === 'private-user-images.githubusercontent.com') {\n return true\n }\n\n // GitHub user-attachments (uploaded images in issues/PRs) require authentication\n if (hostname === 'github.com' && parsedUrl.pathname.startsWith('/user-attachments/assets/')) {\n return true\n }\n\n return false\n } catch {\n // Invalid URL - treat as not authenticated\n return false\n }\n}\n\n/**\n * Get extension from URL pathname\n *\n * @param url - URL to extract extension from\n * @returns Extension including dot (e.g., '.png') or null if not found\n */\nfunction getExtensionFromUrl(url: string): string | null {\n try {\n const parsedUrl = new URL(url)\n const pathname = parsedUrl.pathname\n const ext = extname(pathname).toLowerCase()\n\n if (SUPPORTED_EXTENSIONS.includes(ext)) {\n return ext\n }\n return null\n } catch {\n return null\n }\n}\n\n/**\n * Generate cache key from URL\n * For GitHub URLs, strips JWT query params to ensure consistent caching\n * Returns hash + original extension\n *\n * @param url - Image URL to generate cache key for\n * @returns Cache key (hash + extension)\n */\nexport function getCacheKey(url: string): string {\n const parsedUrl = new URL(url)\n\n // For GitHub private images, remove jwt query param to get stable cache key\n // The jwt changes each fetch but the base URL is the same for the same image\n if (parsedUrl.hostname === 'private-user-images.githubusercontent.com') {\n parsedUrl.searchParams.delete('jwt')\n }\n\n // Get URL without volatile params for hashing\n const stableUrl = parsedUrl.toString()\n\n // Generate SHA256 hash of the stable URL (first 16 chars for brevity)\n const hash = createHash('sha256').update(stableUrl).digest('hex').slice(0, 16)\n\n // Extract extension from URL pathname, default to .png\n const ext = getExtensionFromUrl(url) ?? '.png'\n\n return `${hash}${ext}`\n}\n\n/**\n * Check if image is already cached\n * Returns file path if exists, undefined otherwise\n *\n * @param url - Image URL to check cache for\n * @returns Cached file path or undefined\n */\nexport function getCachedImagePath(url: string): string | undefined {\n const cacheKey = getCacheKey(url)\n const cachedPath = join(CACHE_DIR, cacheKey)\n\n if (existsSync(cachedPath)) {\n return cachedPath\n }\n return undefined\n}\n\n/**\n * Get authentication token for the given provider\n *\n * @param provider - Provider type ('github' or 'linear')\n * @returns Authentication token or undefined\n */\nasync function getAuthToken(provider: IssueProvider): Promise<string | undefined> {\n if (provider === 'github') {\n // Return cached token if available\n if (cachedGitHubToken !== undefined) {\n return cachedGitHubToken\n }\n\n try {\n // Execute `gh auth token` to get GitHub token\n const result = await execa('gh', ['auth', 'token'])\n cachedGitHubToken = result.stdout.trim()\n return cachedGitHubToken\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n logger.warn(`Failed to get GitHub auth token via gh CLI: ${message}`)\n return undefined\n }\n }\n\n if (provider === 'linear') {\n // Linear token from environment variable\n return process.env.LINEAR_API_TOKEN\n }\n\n return undefined\n}\n\n/**\n * Clear the cached GitHub auth token (for testing purposes)\n */\nexport function clearCachedGitHubToken(): void {\n cachedGitHubToken = undefined\n}\n\n/**\n * Download image from URL and stream it directly to a file\n *\n * @param url - Image URL to download\n * @param destPath - Destination file path\n * @param authHeader - Optional Authorization header value\n * @throws Error if download fails, times out, or exceeds size limit\n */\nexport async function downloadAndSaveImage(\n url: string,\n destPath: string,\n authHeader?: string\n): Promise<void> {\n const headers: Record<string, string> = {}\n if (authHeader) {\n headers['Authorization'] = authHeader\n }\n\n // Set up abort controller for timeout\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS)\n\n try {\n const response = await fetch(url, { headers, signal: controller.signal })\n\n if (!response.ok) {\n throw new Error(`Failed to download image: ${response.status} ${response.statusText}`)\n }\n\n // Check Content-Length header if available\n const contentLength = response.headers.get('Content-Length')\n if (contentLength && parseInt(contentLength, 10) > MAX_IMAGE_SIZE) {\n throw new Error(`Image too large: ${contentLength} bytes exceeds ${MAX_IMAGE_SIZE} byte limit`)\n }\n\n if (!response.body) {\n throw new Error('Response body is null')\n }\n\n // Convert ReadableStream to Node.js Readable\n const reader = response.body.getReader()\n let bytesWritten = 0\n\n const nodeReadable = new Readable({\n async read(): Promise<void> {\n try {\n const { done, value } = await reader.read()\n if (done) {\n this.push(null)\n return\n }\n\n bytesWritten += value.byteLength\n if (bytesWritten > MAX_IMAGE_SIZE) {\n reader.cancel()\n this.destroy(new Error(`Image too large: ${bytesWritten} bytes exceeds ${MAX_IMAGE_SIZE} byte limit`))\n return\n }\n\n this.push(Buffer.from(value))\n } catch (err) {\n this.destroy(err instanceof Error ? err : new Error(String(err)))\n }\n }\n })\n\n // Ensure cache directory exists\n if (!existsSync(CACHE_DIR)) {\n mkdirSync(CACHE_DIR, { recursive: true })\n }\n\n // Stream to file\n const writeStream = createWriteStream(destPath)\n\n try {\n await pipeline(nodeReadable, writeStream)\n } catch (pipelineError) {\n // Clean up partial file on error\n try {\n if (existsSync(destPath)) {\n unlinkSync(destPath)\n }\n } catch {\n // Ignore cleanup errors\n }\n throw pipelineError\n }\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n throw new Error(`Image download timed out after ${REQUEST_TIMEOUT_MS}ms`)\n }\n throw error\n } finally {\n clearTimeout(timeoutId)\n }\n}\n\n/**\n * Get the destination path for caching an image\n *\n * @param url - Original image URL (used to generate cache key)\n * @returns Local file path where image should be saved\n */\nexport function getCacheDestPath(url: string): string {\n // Ensure cache directory exists\n if (!existsSync(CACHE_DIR)) {\n mkdirSync(CACHE_DIR, { recursive: true })\n }\n\n // Generate cache key from URL\n const cacheKey = getCacheKey(url)\n return join(CACHE_DIR, cacheKey)\n}\n\n/**\n * Rewrite image URLs in markdown content\n *\n * @param content - Original markdown content\n * @param urlMap - Map of original URLs to local file paths\n * @returns Content with URLs replaced\n */\nexport function rewriteMarkdownUrls(\n content: string,\n urlMap: Map<string, string>\n): string {\n let result = content\n\n for (const [originalUrl, localPath] of urlMap) {\n // Escape special regex characters in the URL\n const escapedUrl = originalUrl.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n const urlRegex = new RegExp(escapedUrl, 'g')\n result = result.replace(urlRegex, localPath)\n }\n\n return result\n}\n\n/**\n * Main entry point: process all images in markdown content\n * Downloads authenticated images (with caching), saves locally, rewrites URLs\n *\n * @param content - Markdown content to process\n * @param provider - Image provider for authentication ('github' or 'linear')\n * @returns Content with authenticated image URLs replaced with local file paths\n */\nexport async function processMarkdownImages(\n content: string,\n provider: IssueProvider\n): Promise<string> {\n // Early return if empty\n if (!content) {\n return ''\n }\n\n // Extract all image URLs\n const images = extractMarkdownImageUrls(content)\n if (images.length === 0) {\n return content\n }\n\n // Filter to only authenticated URLs\n const authImages = images.filter(img => isAuthenticatedImageUrl(img.url))\n if (authImages.length === 0) {\n return content\n }\n\n // Get auth token for provider\n const authToken = await getAuthToken(provider)\n\n // Deduplicate URLs (same image might appear multiple times)\n const uniqueUrls = [...new Set(authImages.map(img => img.url))]\n\n // Build URL map - process all unique URLs in parallel\n const urlMap = new Map<string, string>()\n\n // Download/cache images in parallel\n const downloadPromises = uniqueUrls.map(async (url) => {\n try {\n // Check cache first\n const cachedPath = getCachedImagePath(url)\n if (cachedPath) {\n logger.debug(`Using cached image: ${cachedPath}`)\n return { url, localPath: cachedPath }\n }\n\n // Cache miss - download and stream directly to file\n logger.debug(`Downloading image: ${url}`)\n const destPath = getCacheDestPath(url)\n await downloadAndSaveImage(\n url,\n destPath,\n authToken ? `Bearer ${authToken}` : undefined\n )\n return { url, localPath: destPath }\n } catch (error) {\n // Graceful degradation - log warning, return null to keep original URL\n const message = error instanceof Error ? error.message : String(error)\n logger.warn(`Failed to download image ${url}: ${message}`)\n return null\n }\n })\n\n const results = await Promise.all(downloadPromises)\n\n // Build URL map from results\n for (const result of results) {\n if (result !== null) {\n urlMap.set(result.url, result.localPath)\n }\n }\n\n // Rewrite and return\n return rewriteMarkdownUrls(content, urlMap)\n}\n","/**\n * GitHub implementation of Issue Management Provider\n * Uses GitHub CLI for all operations\n * Normalizes GitHub-specific fields (login) to provider-agnostic core fields (id, displayName)\n */\n\nimport type {\n\tIssueManagementProvider,\n\tGetIssueInput,\n\tGetPRInput,\n\tGetReviewCommentsInput,\n\tGetCommentInput,\n\tCreateCommentInput,\n\tUpdateCommentInput,\n\tCreateIssueInput,\n\tCreateChildIssueInput,\n\tCreateDependencyInput,\n\tGetDependenciesInput,\n\tRemoveDependencyInput,\n\tGetChildIssuesInput,\n\tCloseIssueInput,\n\tReopenIssueInput,\n\tEditIssueInput,\n\tCreateIssueResult,\n\tIssueResult,\n\tPRResult,\n\tReviewCommentResult,\n\tCommentDetailResult,\n\tCommentResult,\n\tDependenciesResult,\n\tChildIssueResult,\n\tFlexibleAuthor,\n} from './types.js'\nimport {\n\texecuteGhCommand,\n\tcreateIssueComment,\n\tupdateIssueComment,\n\tcreatePRComment,\n\tcreateIssue,\n\tgetIssueNodeId,\n\taddSubIssue,\n\tgetIssueDatabaseId,\n\tgetIssueDependencies,\n\tcreateIssueDependency,\n\tremoveIssueDependency,\n\tgetSubIssues,\n\tcloseGhIssue,\n\treopenGhIssue,\n\teditGhIssue,\n} from '../utils/github.js'\nimport { processMarkdownImages } from '../utils/image-processor.js'\n\n/**\n * GitHub-specific author structure from API\n */\ninterface GitHubAuthor {\n\tlogin: string\n\tid?: number\n\tavatarUrl?: string\n\turl?: string\n}\n\n/**\n * Normalize GitHub author to FlexibleAuthor format\n */\nfunction normalizeAuthor(author: GitHubAuthor | null | undefined): FlexibleAuthor | null {\n\tif (!author) return null\n\n\treturn {\n\t\tid: author.id ? String(author.id) : author.login,\n\t\tdisplayName: author.login, // GitHub uses login as primary identifier\n\t\tlogin: author.login, // Preserve original GitHub field\n\t\t...(author.avatarUrl && { avatarUrl: author.avatarUrl }),\n\t\t...(author.url && { url: author.url }),\n\t}\n}\n\n/**\n * Extract numeric comment ID from GitHub comment URL\n * URL format: https://github.com/owner/repo/issues/123#issuecomment-3615239386\n */\nexport function extractNumericIdFromUrl(url: string): string {\n\tconst match = url.match(/#issuecomment-(\\d+)$/)\n\tif (!match?.[1]) {\n\t\tthrow new Error(`Cannot extract comment ID from URL: ${url}`)\n\t}\n\treturn match[1]\n}\n\n/**\n * GitHub-specific implementation of IssueManagementProvider\n */\nexport class GitHubIssueManagementProvider implements IssueManagementProvider {\n\treadonly providerName = 'github'\n\treadonly issuePrefix = '#'\n\n\t/**\n\t * Fetch issue details using gh CLI\n\t * Normalizes GitHub-specific fields to provider-agnostic format\n\t */\n\tasync getIssue(input: GetIssueInput): Promise<IssueResult> {\n\t\tconst { number, includeComments = true, repo } = input\n\n\t\t// Convert string ID to number for GitHub CLI\n\t\tconst issueNumber = parseInt(number, 10)\n\t\tif (isNaN(issueNumber)) {\n\t\t\tthrow new Error(`Invalid GitHub issue number: ${number}. GitHub issue IDs must be numeric.`)\n\t\t}\n\n\t\t// Build fields list based on whether we need comments\n\t\tconst fields = includeComments\n\t\t\t? 'body,title,comments,labels,assignees,milestone,author,state,number,url'\n\t\t\t: 'body,title,labels,assignees,milestone,author,state,number,url'\n\n\t\t// Use gh issue view to fetch issue details\n\t\tinterface GitHubIssueResponse {\n\t\t\tnumber: number\n\t\t\ttitle: string\n\t\t\tbody: string\n\t\t\tstate: string\n\t\t\turl: string\n\t\t\tauthor?: GitHubAuthor\n\t\t\tlabels?: Array<{ name: string; color?: string; description?: string }>\n\t\t\tassignees?: Array<GitHubAuthor>\n\t\t\tmilestone?: { title: string; number?: number; state?: string }\n\t\t\tcomments?: Array<{\n\t\t\t\tid: number\n\t\t\t\tauthor: GitHubAuthor\n\t\t\t\tbody: string\n\t\t\t\tcreatedAt: string\n\t\t\t\tupdatedAt?: string\n\t\t\t\turl: string\n\t\t\t}>\n\t\t}\n\n\t\tconst args = [\n\t\t\t'issue',\n\t\t\t'view',\n\t\t\tString(issueNumber),\n\t\t\t'--json',\n\t\t\tfields,\n\t\t]\n\n\t\t// Add --repo flag if repo is provided (gh CLI handles both owner/repo and URL formats)\n\t\tif (repo) {\n\t\t\targs.push('--repo', repo)\n\t\t}\n\n\t\tconst raw = await executeGhCommand<GitHubIssueResponse>(args)\n\n\t\t// Normalize to IssueResult with core fields + passthrough\n\t\tconst result: IssueResult = {\n\t\t\t// Core fields\n\t\t\tid: String(raw.number),\n\t\t\ttitle: raw.title,\n\t\t\tbody: raw.body,\n\t\t\tstate: raw.state,\n\t\t\turl: raw.url,\n\t\t\tprovider: 'github',\n\n\t\t\t// Normalized author\n\t\t\tauthor: normalizeAuthor(raw.author),\n\n\t\t\t// Optional flexible fields\n\t\t\t...(raw.assignees && {\n\t\t\t\tassignees: raw.assignees.map(a => normalizeAuthor(a)).filter((a): a is FlexibleAuthor => a !== null),\n\t\t\t}),\n\t\t\t...(raw.labels && {\n\t\t\t\tlabels: raw.labels,\n\t\t\t}),\n\n\t\t\t// GitHub-specific passthrough fields\n\t\t\t...(raw.milestone && {\n\t\t\t\tmilestone: raw.milestone,\n\t\t\t}),\n\t\t}\n\n\t\t// Handle comments with normalized authors\n\t\t// Use extractNumericIdFromUrl to get REST API-compatible numeric IDs from comment URLs\n\t\t// (GitHub CLI returns GraphQL node IDs in the id field, but REST API expects numeric IDs)\n\t\tif (raw.comments !== undefined) {\n\t\t\tresult.comments = raw.comments.map(comment => ({\n\t\t\t\tid: extractNumericIdFromUrl(comment.url),\n\t\t\t\tbody: comment.body,\n\t\t\t\tcreatedAt: comment.createdAt,\n\t\t\t\tauthor: normalizeAuthor(comment.author),\n\t\t\t\t...(comment.updatedAt && { updatedAt: comment.updatedAt }),\n\t\t\t}))\n\t\t}\n\n\t\t// Process authenticated images in body and comments\n\t\tresult.body = await processMarkdownImages(result.body, 'github')\n\t\tif (result.comments) {\n\t\t\tfor (const comment of result.comments) {\n\t\t\t\tcomment.body = await processMarkdownImages(comment.body, 'github')\n\t\t\t}\n\t\t}\n\n\t\treturn result\n\t}\n\n\t/**\n\t * Fetch pull request details using gh CLI\n\t * Normalizes GitHub-specific fields to provider-agnostic format\n\t */\n\tasync getPR(input: GetPRInput): Promise<PRResult> {\n\t\tconst { number, includeComments = true, repo } = input\n\n\t\t// Convert string ID to number for GitHub CLI\n\t\tconst prNumber = parseInt(number, 10)\n\t\tif (isNaN(prNumber)) {\n\t\t\tthrow new Error(`Invalid GitHub PR number: ${number}. GitHub PR IDs must be numeric.`)\n\t\t}\n\n\t\t// Build fields list based on whether we need comments\n\t\tconst baseFields = 'number,title,body,state,url,author,headRefName,baseRefName,files,commits'\n\t\tconst fields = includeComments\n\t\t\t? `${baseFields},comments`\n\t\t\t: baseFields\n\n\t\t// GitHub PR response structure\n\t\tinterface GitHubPRResponse {\n\t\t\tnumber: number\n\t\t\ttitle: string\n\t\t\tbody: string\n\t\t\tstate: string\n\t\t\turl: string\n\t\t\tauthor?: GitHubAuthor\n\t\t\theadRefName: string\n\t\t\tbaseRefName: string\n\t\t\tfiles?: Array<{\n\t\t\t\tpath: string\n\t\t\t\tadditions: number\n\t\t\t\tdeletions: number\n\t\t\t}>\n\t\t\tcommits?: Array<{\n\t\t\t\toid: string\n\t\t\t\tmessageHeadline: string\n\t\t\t\tauthors: Array<{ name: string; email: string }>\n\t\t\t}>\n\t\t\tcomments?: Array<{\n\t\t\t\tid: number\n\t\t\t\tauthor: GitHubAuthor\n\t\t\t\tbody: string\n\t\t\t\tcreatedAt: string\n\t\t\t\tupdatedAt?: string\n\t\t\t\turl: string\n\t\t\t}>\n\t\t}\n\n\t\tconst args = [\n\t\t\t'pr',\n\t\t\t'view',\n\t\t\tString(prNumber),\n\t\t\t'--json',\n\t\t\tfields,\n\t\t]\n\n\t\t// Add --repo flag if repo is provided\n\t\tif (repo) {\n\t\t\targs.push('--repo', repo)\n\t\t}\n\n\t\tconst raw = await executeGhCommand<GitHubPRResponse>(args)\n\n\t\t// Normalize to PRResult with core fields + passthrough\n\t\tconst result: PRResult = {\n\t\t\t// Core fields\n\t\t\tid: String(raw.number),\n\t\t\tnumber: raw.number,\n\t\t\ttitle: raw.title,\n\t\t\tbody: raw.body,\n\t\t\tstate: raw.state,\n\t\t\turl: raw.url,\n\n\t\t\t// Normalized author\n\t\t\tauthor: normalizeAuthor(raw.author),\n\n\t\t\t// PR-specific fields\n\t\t\theadRefName: raw.headRefName,\n\t\t\tbaseRefName: raw.baseRefName,\n\n\t\t\t// Optional files\n\t\t\t...(raw.files && {\n\t\t\t\tfiles: raw.files,\n\t\t\t}),\n\n\t\t\t// Optional commits - normalize author\n\t\t\t...(raw.commits && {\n\t\t\t\tcommits: raw.commits.map(commit => ({\n\t\t\t\t\toid: commit.oid,\n\t\t\t\t\tmessageHeadline: commit.messageHeadline,\n\t\t\t\t\tauthor: commit.authors?.[0]\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\tid: commit.authors[0].email,\n\t\t\t\t\t\t\tdisplayName: commit.authors[0].name,\n\t\t\t\t\t\t\tname: commit.authors[0].name,\n\t\t\t\t\t\t\temail: commit.authors[0].email,\n\t\t\t\t\t\t}\n\t\t\t\t\t\t: null,\n\t\t\t\t})),\n\t\t\t}),\n\t\t}\n\n\t\t// Handle comments with normalized authors\n\t\t// Use extractNumericIdFromUrl to get REST API-compatible numeric IDs from comment URLs\n\t\tif (raw.comments !== undefined) {\n\t\t\tresult.comments = raw.comments.map(comment => ({\n\t\t\t\tid: extractNumericIdFromUrl(comment.url),\n\t\t\t\tbody: comment.body,\n\t\t\t\tcreatedAt: comment.createdAt,\n\t\t\t\tauthor: normalizeAuthor(comment.author),\n\t\t\t\t...(comment.updatedAt && { updatedAt: comment.updatedAt }),\n\t\t\t}))\n\t\t}\n\n\t\t// Process authenticated images in body and comments\n\t\tresult.body = await processMarkdownImages(result.body, 'github')\n\t\tif (result.comments) {\n\t\t\tfor (const comment of result.comments) {\n\t\t\t\tcomment.body = await processMarkdownImages(comment.body, 'github')\n\t\t\t}\n\t\t}\n\n\t\treturn result\n\t}\n\n\t/**\n\t * Fetch PR review comments (inline code comments on specific files/lines)\n\t * Uses gh api with --paginate to handle PRs with many review comments\n\t * Optionally filters by review ID\n\t */\n\tasync getReviewComments(input: GetReviewCommentsInput): Promise<ReviewCommentResult[]> {\n\t\tconst { number, reviewId, repo } = input\n\n\t\t// Convert string ID to number for GitHub API\n\t\tconst prNumber = parseInt(number, 10)\n\t\tif (isNaN(prNumber)) {\n\t\t\tthrow new Error(`Invalid GitHub PR number: ${number}. GitHub PR IDs must be numeric.`)\n\t\t}\n\n\t\t// Validate reviewId early to avoid unnecessary API call\n\t\tlet numericReviewId: number | undefined\n\t\tif (reviewId) {\n\t\t\tnumericReviewId = parseInt(reviewId, 10)\n\t\t\tif (isNaN(numericReviewId)) {\n\t\t\t\tthrow new Error(`Invalid review ID: ${reviewId}. Review IDs must be numeric.`)\n\t\t\t}\n\t\t}\n\n\t\t// GitHub API response structure for review comments\n\t\tinterface GitHubReviewComment {\n\t\t\tid: number\n\t\t\tbody: string\n\t\t\tpath: string\n\t\t\tline: number | null\n\t\t\tside: string | null\n\t\t\tuser: GitHubAuthor | null\n\t\t\tcreated_at: string\n\t\t\tupdated_at: string | null\n\t\t\tin_reply_to_id: number | null\n\t\t\tpull_request_review_id: number | null\n\t\t}\n\n\t\t// Use explicit repo path if provided, otherwise use :owner/:repo placeholder\n\t\tconst apiPath = repo\n\t\t\t? `repos/${repo}/pulls/${prNumber}/comments`\n\t\t\t: `repos/:owner/:repo/pulls/${prNumber}/comments`\n\n\t\tconst args = [\n\t\t\t'api',\n\t\t\tapiPath,\n\t\t\t'--paginate',\n\t\t\t'--jq',\n\t\t\t'[.[] | {id: .id, body: .body, path: .path, line: .line, side: .side, user: .user, created_at: .created_at, updated_at: .updated_at, in_reply_to_id: .in_reply_to_id, pull_request_review_id: .pull_request_review_id}]',\n\t\t]\n\n\t\tconst raw = await executeGhCommand<GitHubReviewComment[]>(args)\n\n\t\t// Filter by reviewId if provided (already validated above)\n\t\tlet comments = raw\n\t\tif (numericReviewId !== undefined) {\n\t\t\tcomments = comments.filter(c => c.pull_request_review_id === numericReviewId)\n\t\t}\n\n\t\t// Normalize and process each comment\n\t\tconst results: ReviewCommentResult[] = []\n\t\tfor (const comment of comments) {\n\t\t\tconst processedBody = await processMarkdownImages(comment.body, 'github')\n\t\t\tresults.push({\n\t\t\t\tid: String(comment.id),\n\t\t\t\tbody: processedBody,\n\t\t\t\tpath: comment.path,\n\t\t\t\tline: comment.line,\n\t\t\t\tside: comment.side,\n\t\t\t\tauthor: normalizeAuthor(comment.user),\n\t\t\t\tcreatedAt: comment.created_at,\n\t\t\t\tupdatedAt: comment.updated_at ?? null,\n\t\t\t\tinReplyToId: comment.in_reply_to_id ? String(comment.in_reply_to_id) : null,\n\t\t\t\tpullRequestReviewId: comment.pull_request_review_id,\n\t\t\t})\n\t\t}\n\n\t\treturn results\n\t}\n\n\t/**\n\t * Fetch a specific comment by ID using gh API\n\t * Normalizes author to FlexibleAuthor format\n\t */\n\tasync getComment(input: GetCommentInput): Promise<CommentDetailResult> {\n\t\tconst { commentId, repo } = input\n\t\t// Note: GitHub doesn't need the issue number parameter - comment IDs are globally unique\n\t\t// But we accept it for interface compatibility with other providers\n\n\t\t// Convert string ID to number for GitHub API\n\t\tconst numericCommentId = parseInt(commentId, 10)\n\t\tif (isNaN(numericCommentId)) {\n\t\t\tthrow new Error(`Invalid GitHub comment ID: ${commentId}. GitHub comment IDs must be numeric.`)\n\t\t}\n\n\t\t// GitHub API response structure\n\t\tinterface GitHubCommentResponse {\n\t\t\tid: number\n\t\t\tbody: string\n\t\t\tuser: GitHubAuthor\n\t\t\tcreated_at: string\n\t\t\tupdated_at?: string\n\t\t\thtml_url?: string\n\t\t\treactions?: Record<string, unknown>\n\t\t}\n\n\t\t// Use explicit repo path if provided, otherwise use :owner/:repo placeholder\n\t\tconst apiPath = repo\n\t\t\t? `repos/${repo}/issues/comments/${numericCommentId}`\n\t\t\t: `repos/:owner/:repo/issues/comments/${numericCommentId}`\n\n\t\t// Use gh api to fetch specific comment\n\t\tconst raw = await executeGhCommand<GitHubCommentResponse>([\n\t\t\t'api',\n\t\t\tapiPath,\n\t\t\t'--jq',\n\t\t\t'{id: .id, body: .body, user: .user, created_at: .created_at, updated_at: .updated_at, html_url: .html_url, reactions: .reactions}',\n\t\t])\n\n\t\t// Process authenticated images in comment body\n\t\tconst processedBody = await processMarkdownImages(raw.body, 'github')\n\n\t\t// Normalize to CommentDetailResult\n\t\treturn {\n\t\t\tid: String(raw.id),\n\t\t\tbody: processedBody,\n\t\t\tauthor: normalizeAuthor(raw.user),\n\t\t\tcreated_at: raw.created_at,\n\t\t\t...(raw.updated_at && { updated_at: raw.updated_at }),\n\t\t\t// Passthrough GitHub-specific fields\n\t\t\t...(raw.html_url && { html_url: raw.html_url }),\n\t\t\t...(raw.reactions && { reactions: raw.reactions }),\n\t\t}\n\t}\n\n\t/**\n\t * Create a new comment on an issue or PR\n\t */\n\tasync createComment(input: CreateCommentInput): Promise<CommentResult> {\n\t\tconst { number, body, type } = input\n\n\t\t// Convert string ID to number for GitHub utilities\n\t\tconst numericId = parseInt(number, 10)\n\t\tif (isNaN(numericId)) {\n\t\t\tthrow new Error(`Invalid GitHub ${type} number: ${number}. GitHub IDs must be numeric.`)\n\t\t}\n\n\t\t// Delegate to existing GitHub utilities\n\t\tconst result =\n\t\t\ttype === 'issue'\n\t\t\t\t? await createIssueComment(numericId, body)\n\t\t\t\t: await createPRComment(numericId, body)\n\n\t\t// Convert numeric ID to string for the interface\n\t\treturn {\n\t\t\t...result,\n\t\t\tid: String(result.id),\n\t\t}\n\t}\n\n\t/**\n\t * Update an existing comment\n\t */\n\tasync updateComment(input: UpdateCommentInput): Promise<CommentResult> {\n\t\tconst { commentId, body } = input\n\t\t// Note: GitHub doesn't need the issue number parameter - comment IDs are globally unique\n\t\t// But we accept it for interface compatibility with other providers\n\n\t\t// Convert string ID to number for GitHub utility\n\t\tconst numericCommentId = parseInt(commentId, 10)\n\t\tif (isNaN(numericCommentId)) {\n\t\t\tthrow new Error(`Invalid GitHub comment ID: ${commentId}. GitHub comment IDs must be numeric.`)\n\t\t}\n\n\t\t// Delegate to existing GitHub utility\n\t\tconst result = await updateIssueComment(numericCommentId, body)\n\n\t\t// Convert numeric ID to string for the interface\n\t\treturn {\n\t\t\t...result,\n\t\t\tid: String(result.id),\n\t\t}\n\t}\n\n\t/**\n\t * Create a new issue\n\t */\n\tasync createIssue(input: CreateIssueInput): Promise<CreateIssueResult> {\n\t\tconst { title, body, labels, repo } = input\n\t\t// teamKey is ignored for GitHub\n\n\t\tconst result = await createIssue(title, body, { labels, repo })\n\n\t\t// Ensure number is numeric\n\t\tconst issueNumber = typeof result.number === 'number'\n\t\t\t? result.number\n\t\t\t: parseInt(String(result.number), 10)\n\n\t\treturn {\n\t\t\tid: String(issueNumber),\n\t\t\turl: result.url,\n\t\t\tnumber: issueNumber,\n\t\t}\n\t}\n\n\t/**\n\t * Create a child issue linked to a parent issue\n\t * GitHub requires two-step process: create issue, then link via GraphQL\n\t */\n\tasync createChildIssue(input: CreateChildIssueInput): Promise<CreateIssueResult> {\n\t\tconst { parentId, title, body, labels, repo } = input\n\t\t// teamKey is ignored for GitHub\n\n\t\t// Convert parent identifier to number\n\t\tconst parentNumber = parseInt(parentId, 10)\n\t\tif (isNaN(parentNumber)) {\n\t\t\tthrow new Error(`Invalid GitHub parent issue number: ${parentId}. GitHub issue IDs must be numeric.`)\n\t\t}\n\n\t\t// Step 1: Get parent issue's GraphQL node ID\n\t\tconst parentNodeId = await getIssueNodeId(parentNumber, repo)\n\n\t\t// Step 2: Create the child issue\n\t\tconst childResult = await createIssue(title, body, { labels, repo })\n\t\tconst childNumber = typeof childResult.number === 'number'\n\t\t\t? childResult.number\n\t\t\t: parseInt(String(childResult.number), 10)\n\n\t\t// Step 3: Get child issue's GraphQL node ID\n\t\tconst childNodeId = await getIssueNodeId(childNumber, repo)\n\n\t\t// Step 4: Link child to parent via GraphQL mutation\n\t\tawait addSubIssue(parentNodeId, childNodeId)\n\n\t\treturn {\n\t\t\tid: String(childNumber),\n\t\t\turl: childResult.url,\n\t\t\tnumber: childNumber,\n\t\t}\n\t}\n\n\t/**\n\t * Create a blocking dependency between two issues (A blocks B)\n\t * Uses GitHub's sub-issues API: blocking issue becomes parent, blocked issue becomes sub-issue\n\t */\n\tasync createDependency(input: CreateDependencyInput): Promise<void> {\n\t\tconst { blockingIssue, blockedIssue, repo } = input\n\n\t\t// Convert string IDs to numbers\n\t\tconst blockingNumber = parseInt(blockingIssue, 10)\n\t\tif (isNaN(blockingNumber)) {\n\t\t\tthrow new Error(`Invalid GitHub issue number: ${blockingIssue}. GitHub issue IDs must be numeric.`)\n\t\t}\n\n\t\tconst blockedNumber = parseInt(blockedIssue, 10)\n\t\tif (isNaN(blockedNumber)) {\n\t\t\tthrow new Error(`Invalid GitHub issue number: ${blockedIssue}. GitHub issue IDs must be numeric.`)\n\t\t}\n\n\t\t// Get the database ID of the blocking issue\n\t\t// GitHub API: POST /issues/{blocked_issue_number}/dependencies/blocked_by with body issue_id={blocking_database_id}\n\t\tconst blockingDatabaseId = await getIssueDatabaseId(blockingNumber, repo)\n\n\t\t// Create the dependency: path uses blocked issue number, body uses blocking issue DB ID\n\t\tawait createIssueDependency(blockedNumber, blockingDatabaseId, repo)\n\t}\n\n\t/**\n\t * Get dependencies for an issue\n\t */\n\tasync getDependencies(input: GetDependenciesInput): Promise<DependenciesResult> {\n\t\tconst { number, direction, repo } = input\n\n\t\tconst issueNumber = parseInt(number, 10)\n\t\tif (isNaN(issueNumber)) {\n\t\t\tthrow new Error(`Invalid GitHub issue number: ${number}. GitHub issue IDs must be numeric.`)\n\t\t}\n\n\t\tconst result: DependenciesResult = {\n\t\t\tblocking: [],\n\t\t\tblockedBy: [],\n\t\t}\n\n\t\t// Fetch dependencies based on direction\n\t\tif (direction === 'blocking' || direction === 'both') {\n\t\t\tresult.blocking = await getIssueDependencies(issueNumber, 'blocking', repo)\n\t\t}\n\n\t\tif (direction === 'blocked_by' || direction === 'both') {\n\t\t\tresult.blockedBy = await getIssueDependencies(issueNumber, 'blocked_by', repo)\n\t\t}\n\n\t\treturn result\n\t}\n\n\t/**\n\t * Remove a blocking dependency between two issues (A blocks B)\n\t * Uses GitHub's sub-issues API: blocking issue is parent, blocked issue is sub-issue\n\t */\n\tasync removeDependency(input: RemoveDependencyInput): Promise<void> {\n\t\tconst { blockingIssue, blockedIssue, repo } = input\n\n\t\t// Convert string IDs to numbers\n\t\tconst blockingNumber = parseInt(blockingIssue, 10)\n\t\tif (isNaN(blockingNumber)) {\n\t\t\tthrow new Error(`Invalid GitHub issue number: ${blockingIssue}. GitHub issue IDs must be numeric.`)\n\t\t}\n\n\t\tconst blockedNumber = parseInt(blockedIssue, 10)\n\t\tif (isNaN(blockedNumber)) {\n\t\t\tthrow new Error(`Invalid GitHub issue number: ${blockedIssue}. GitHub issue IDs must be numeric.`)\n\t\t}\n\n\t\t// Get the database ID of the blocking issue\n\t\t// GitHub API: DELETE /issues/{blocked_issue_number}/dependencies/blocked_by with body issue_id={blocking_database_id}\n\t\tconst blockingDatabaseId = await getIssueDatabaseId(blockingNumber, repo)\n\n\t\t// Remove the dependency: path uses blocked issue number, body uses blocking issue DB ID\n\t\tawait removeIssueDependency(blockedNumber, blockingDatabaseId, repo)\n\t}\n\n\t/**\n\t * Get child issues (sub-issues) of a parent issue\n\t */\n\tasync getChildIssues(input: GetChildIssuesInput): Promise<ChildIssueResult[]> {\n\t\tconst { number, repo } = input\n\n\t\tconst issueNumber = parseInt(number, 10)\n\t\tif (isNaN(issueNumber)) {\n\t\t\tthrow new Error(`Invalid GitHub issue number: ${number}. GitHub issue IDs must be numeric.`)\n\t\t}\n\n\t\treturn await getSubIssues(issueNumber, repo)\n\t}\n\n\t/**\n\t * Close an issue\n\t */\n\tasync closeIssue(input: CloseIssueInput): Promise<void> {\n\t\tconst { number, repo } = input\n\n\t\tconst issueNumber = parseInt(number, 10)\n\t\tif (isNaN(issueNumber)) {\n\t\t\tthrow new Error(`Invalid GitHub issue number: ${number}. GitHub issue IDs must be numeric.`)\n\t\t}\n\n\t\tawait closeGhIssue(issueNumber, repo)\n\t}\n\n\t/**\n\t * Reopen a closed issue\n\t */\n\tasync reopenIssue(input: ReopenIssueInput): Promise<void> {\n\t\tconst { number, repo } = input\n\n\t\tconst issueNumber = parseInt(number, 10)\n\t\tif (isNaN(issueNumber)) {\n\t\t\tthrow new Error(`Invalid GitHub issue number: ${number}. GitHub issue IDs must be numeric.`)\n\t\t}\n\n\t\tawait reopenGhIssue(issueNumber, repo)\n\t}\n\n\t/**\n\t * Edit an issue's properties\n\t * State changes are delegated to closeIssue/reopenIssue\n\t */\n\tasync editIssue(input: EditIssueInput): Promise<void> {\n\t\tconst { number, title, body, state, labels, repo } = input\n\n\t\tconst issueNumber = parseInt(number, 10)\n\t\tif (isNaN(issueNumber)) {\n\t\t\tthrow new Error(`Invalid GitHub issue number: ${number}. GitHub issue IDs must be numeric.`)\n\t\t}\n\n\t\t// Handle state changes via close/reopen\n\t\tif (state === 'closed') {\n\t\t\tawait this.closeIssue({ number, repo })\n\t\t} else if (state === 'open') {\n\t\t\tawait this.reopenIssue({ number, repo })\n\t\t}\n\n\t\t// Handle other field updates\n\t\tif (title !== undefined || body !== undefined || labels !== undefined) {\n\t\t\tawait editGhIssue(\n\t\t\t\tissueNumber,\n\t\t\t\t{\n\t\t\t\t\t...(title !== undefined && { title }),\n\t\t\t\t\t...(body !== undefined && { body }),\n\t\t\t\t\t...(labels !== undefined && { labels }),\n\t\t\t\t},\n\t\t\t\trepo\n\t\t\t)\n\t\t}\n\t}\n}\n","import { appendFileSync } from 'node:fs'\nimport { join, dirname, basename, extname } from 'node:path'\n\n/**\n * Utility class for converting HTML details/summary format to Linear's collapsible format\n *\n * Converts:\n * <details>\n * <summary>Header</summary>\n * CONTENT\n * </details>\n *\n * Into Linear format:\n * +++ Header\n *\n * CONTENT\n *\n * +++\n */\nexport class LinearMarkupConverter {\n\t/**\n\t * Convert HTML details/summary blocks to Linear's collapsible format\n\t * Handles nested details blocks recursively\n\t *\n\t * @param text - Text containing HTML details/summary blocks\n\t * @returns Text with details/summary converted to Linear format\n\t */\n\tstatic convertDetailsToLinear(text: string): string {\n\t\tif (!text) {\n\t\t\treturn text\n\t\t}\n\n\t\t// Process from innermost to outermost to handle nesting correctly\n\t\t// Keep converting until no more details blocks are found\n\t\tlet previousText = ''\n\t\tlet currentText = text\n\n\t\twhile (previousText !== currentText) {\n\t\t\tpreviousText = currentText\n\t\t\tcurrentText = this.convertSinglePass(currentText)\n\t\t}\n\n\t\treturn currentText\n\t}\n\n\t/**\n\t * Perform a single pass of details block conversion\n\t * Converts the innermost details blocks first\n\t */\n\tprivate static convertSinglePass(text: string): string {\n\t\t// Match <details> blocks with optional attributes on the details tag\n\t\t// Supports multiline content between tags\n\t\tconst detailsRegex = /<details[^>]*>\\s*<summary[^>]*>(.*?)<\\/summary>\\s*(.*?)\\s*<\\/details>/gis\n\n\t\treturn text.replace(detailsRegex, (_match, summary, content) => {\n\t\t\t// Clean up the summary - trim whitespace and decode HTML entities\n\t\t\tconst cleanSummary = this.cleanText(summary)\n\n\t\t\t// Clean up the content - preserve internal structure but normalize outer whitespace\n\t\t\t// Note: Don't recursively convert here - the while loop handles that\n\t\t\tconst cleanContent = this.cleanContent(content)\n\n\t\t\t// Build Linear collapsible format\n\t\t\t// Always include blank lines around content for readability\n\t\t\tif (cleanContent) {\n\t\t\t\treturn `+++ ${cleanSummary}\\n\\n${cleanContent}\\n\\n+++`\n\t\t\t} else {\n\t\t\t\t// Empty content - use minimal format\n\t\t\t\treturn `+++ ${cleanSummary}\\n\\n+++`\n\t\t\t}\n\t\t})\n\t}\n\n\t/**\n\t * Clean text by trimming whitespace and decoding common HTML entities\n\t */\n\tprivate static cleanText(text: string): string {\n\t\treturn text\n\t\t\t.trim()\n\t\t\t.replace(/&lt;/g, '<')\n\t\t\t.replace(/&gt;/g, '>')\n\t\t\t.replace(/&amp;/g, '&')\n\t\t\t.replace(/&quot;/g, '\"')\n\t\t\t.replace(/&#39;/g, \"'\")\n\t}\n\n\t/**\n\t * Clean content while preserving internal structure\n\t * - Removes leading/trailing whitespace\n\t * - Normalizes internal blank lines (max 2 consecutive newlines)\n\t * - Preserves code blocks and other formatting\n\t */\n\tprivate static cleanContent(content: string): string {\n\t\tif (!content) {\n\t\t\treturn ''\n\t\t}\n\n\t\t// Trim outer whitespace\n\t\tlet cleaned = content.trim()\n\n\t\t// Normalize excessive blank lines (3+ newlines -> 2 newlines)\n\t\tcleaned = cleaned.replace(/\\n{3,}/g, '\\n\\n')\n\n\t\treturn cleaned\n\t}\n\n\t/**\n\t * Check if text contains HTML details/summary blocks\n\t * Useful for conditional conversion\n\t */\n\tstatic hasDetailsBlocks(text: string): boolean {\n\t\tif (!text) {\n\t\t\treturn false\n\t\t}\n\n\t\tconst detailsRegex = /<details[^>]*>.*?<summary[^>]*>.*?<\\/summary>.*?<\\/details>/is\n\t\treturn detailsRegex.test(text)\n\t}\n\n\t/**\n\t * Remove wrapper tags from code sample details blocks\n\t * Identifies details blocks where summary contains \"X lines\" pattern\n\t * and removes the details/summary tags while preserving the content\n\t *\n\t * @param text - Text containing potential code sample details blocks\n\t * @returns Text with code sample wrappers removed\n\t */\n\tstatic removeCodeSampleWrappers(text: string): string {\n\t\tif (!text) {\n\t\t\treturn text\n\t\t}\n\n\t\t// Match details blocks where summary contains \"X lines\" (e.g., \"45 lines\", \"120 lines\")\n\t\t// Pattern: <details><summary>...N lines...</summary>CONTENT</details>\n\t\t// Use [^<]* to match summary content without allowing nested tags to interfere\n\t\t// Then use [\\s\\S]*? for the content to allow any characters including newlines\n\t\tconst codeSampleRegex = /<details[^>]*>\\s*<summary[^>]*>([^<]*\\d+\\s+lines[^<]*)<\\/summary>\\s*([\\s\\S]*?)<\\/details>/gi\n\n\t\treturn text.replace(codeSampleRegex, (_match, _summary, content) => {\n\t\t\t// Return just the content, without any wrapper tags\n\t\t\t// Preserve the content exactly as-is\n\t\t\treturn content.trim()\n\t\t})\n\t}\n\n\t/**\n\t * Convert text for Linear - applies all necessary conversions\n\t * Currently only converts details/summary blocks, but can be extended\n\t * for other HTML to Linear markdown conversions\n\t */\n\tstatic convertToLinear(text: string): string {\n\t\tif (!text) {\n\t\t\treturn text\n\t\t}\n\n\t\t// Log input if logging is enabled\n\t\tthis.logConversion('INPUT', text)\n\n\t\t// Apply all conversions\n\t\tlet converted = text\n\n\t\t// First, remove code sample wrappers (details blocks with \"X lines\" pattern)\n\t\t// This prevents them from being converted to Linear's +++ format\n\t\tconverted = this.removeCodeSampleWrappers(converted)\n\n\t\t// Then convert remaining details/summary blocks to Linear format\n\t\tconverted = this.convertDetailsToLinear(converted)\n\n\t\t// Log output if logging is enabled\n\t\tthis.logConversion('OUTPUT', converted)\n\n\t\treturn converted\n\t}\n\n\t/**\n\t * Log conversion input/output if LINEAR_MARKDOWN_LOG_FILE is set\n\t */\n\tprivate static logConversion(label: string, content: string): void {\n\t\tconst logFilePath = process.env.LINEAR_MARKDOWN_LOG_FILE\n\t\tif (!logFilePath) {\n\t\t\treturn\n\t\t}\n\n\t\ttry {\n\t\t\tconst timestampedPath = this.getTimestampedLogPath(logFilePath)\n\t\t\tconst timestamp = new Date().toISOString()\n\t\t\tconst separator = '================================'\n\n\t\t\tconst logEntry = `${separator}\\n[${timestamp}] CONVERSION ${label}\\n${separator}\\n${label}:\\n${content}\\n\\n`\n\n\t\t\tappendFileSync(timestampedPath, logEntry, 'utf-8')\n\t\t} catch {\n\t\t\t// Silently fail - don't crash if logging fails\n\t\t\t// This is a debug feature and shouldn't break the conversion\n\t\t}\n\t}\n\n\t/**\n\t * Generate timestamped log file path\n\t * Example: debug.log -> debug-20231202-161234.log\n\t */\n\tprivate static getTimestampedLogPath(logFilePath: string): string {\n\t\tconst dir = dirname(logFilePath)\n\t\tconst ext = extname(logFilePath)\n\t\tconst base = basename(logFilePath, ext)\n\n\t\t// Generate timestamp: YYYYMMDD-HHMMSS\n\t\tconst now = new Date()\n\t\tconst timestamp = [\n\t\t\tnow.getFullYear(),\n\t\t\tString(now.getMonth() + 1).padStart(2, '0'),\n\t\t\tString(now.getDate()).padStart(2, '0'),\n\t\t].join('') + '-' + [\n\t\t\tString(now.getHours()).padStart(2, '0'),\n\t\t\tString(now.getMinutes()).padStart(2, '0'),\n\t\t\tString(now.getSeconds()).padStart(2, '0'),\n\t\t].join('')\n\n\t\treturn join(dir, `${base}-${timestamp}${ext}`)\n\t}\n}\n","/**\n * Linear implementation of Issue Management Provider\n * Uses @linear/sdk for all operations\n */\n\nimport type {\n\tIssueManagementProvider,\n\tGetIssueInput,\n\tGetPRInput,\n\tGetCommentInput,\n\tCreateCommentInput,\n\tUpdateCommentInput,\n\tCreateIssueInput,\n\tCreateChildIssueInput,\n\tCreateDependencyInput,\n\tGetDependenciesInput,\n\tRemoveDependencyInput,\n\tGetChildIssuesInput,\n\tCloseIssueInput,\n\tReopenIssueInput,\n\tEditIssueInput,\n\tCreateIssueResult,\n\tIssueResult,\n\tPRResult,\n\tCommentDetailResult,\n\tCommentResult,\n\tDependenciesResult,\n\tChildIssueResult,\n} from './types.js'\nimport {\n\tfetchLinearIssue,\n\tcreateLinearComment,\n\tgetLinearComment,\n\tupdateLinearComment,\n\tfetchLinearIssueComments,\n\tcreateLinearIssue,\n\tcreateLinearChildIssue,\n\tcreateLinearIssueRelation,\n\tgetLinearIssueDependencies,\n\tfindLinearIssueRelation,\n\tdeleteLinearIssueRelation,\n\tgetLinearChildIssues,\n\tupdateLinearIssueState,\n\teditLinearIssue,\n} from '../utils/linear.js'\nimport { LinearMarkupConverter } from '../utils/linear-markup-converter.js'\nimport { processMarkdownImages } from '../utils/image-processor.js'\n\n/**\n * Linear-specific implementation of IssueManagementProvider\n */\nexport class LinearIssueManagementProvider implements IssueManagementProvider {\n\treadonly providerName = 'linear'\n\treadonly issuePrefix = ''\n\n\t/**\n\t * Cached team key extracted from issue identifiers (e.g., \"ENG-123\" -> \"ENG\")\n\t * Used as fallback when teamKey is not explicitly provided to createIssue()\n\t */\n\tprivate cachedTeamKey: string | undefined = undefined\n\n\t/**\n\t * Fetch issue details using Linear SDK\n\t */\n\tasync getIssue(input: GetIssueInput): Promise<IssueResult> {\n\t\tconst { number, includeComments = true } = input\n\n\t\t// Extract and cache team key from identifier (e.g., \"ENG-123\" -> \"ENG\")\n\t\t// This enables createIssue() to use the team key as a fallback\n\t\tconst match = number.match(/^([A-Z]{2,})-\\d+$/i)\n\t\tif (match?.[1]) {\n\t\t\tthis.cachedTeamKey = match[1].toUpperCase()\n\t\t}\n\n\t\t// Fetch issue - Linear uses alphanumeric identifiers like \"ENG-123\"\n\t\tconst raw = await fetchLinearIssue(number)\n\n\t\t// Map Linear state name to open/closed\n\t\tconst state = raw.state && (raw.state.toLowerCase().includes('done') || raw.state.toLowerCase().includes('completed') || raw.state.toLowerCase().includes('canceled'))\n\t\t\t? 'closed'\n\t\t\t: 'open'\n\n\t\t// Build result\n\t\tconst result: IssueResult = {\n\t\t\tid: raw.identifier,\n\t\t\ttitle: raw.title,\n\t\t\tbody: raw.description ?? '',\n\t\t\tstate,\n\t\t\turl: raw.url,\n\t\t\tprovider: 'linear',\n\t\t\tauthor: null, // Linear SDK doesn't return author in basic fetch\n\n\t\t\t// Linear-specific fields\n\t\t\tlinearState: raw.state,\n\t\t\tcreatedAt: raw.createdAt,\n\t\t\tupdatedAt: raw.updatedAt,\n\t\t}\n\n\t\t// Fetch comments if requested\n\t\tif (includeComments) {\n\t\t\ttry {\n\t\t\t\tconst comments = await this.fetchIssueComments(number)\n\t\t\t\tif (comments) {\n\t\t\t\t\tresult.comments = comments\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// If comments fail, continue without them\n\t\t\t}\n\t\t}\n\n\t\t// Process images in body and comments to make them accessible\n\t\tresult.body = await processMarkdownImages(result.body, 'linear')\n\t\tif (result.comments) {\n\t\t\tfor (const comment of result.comments) {\n\t\t\t\tcomment.body = await processMarkdownImages(comment.body, 'linear')\n\t\t\t}\n\t\t}\n\n\t\treturn result\n\t}\n\n\t/**\n\t * Fetch pull request details\n\t * Linear does not support PRs - this throws an error directing to use GitHub\n\t */\n\tasync getPR(_input: GetPRInput): Promise<PRResult> {\n\t\tthrow new Error('Linear does not support pull requests. PRs exist only on GitHub. Use the GitHub provider for PR operations.')\n\t}\n\n\t/**\n\t * Fetch comments for an issue\n\t */\n\tprivate async fetchIssueComments(identifier: string): Promise<IssueResult['comments']> {\n\t\ttry {\n\t\t\tconst comments = await fetchLinearIssueComments(identifier)\n\n\t\t\treturn comments.map(comment => ({\n\t\t\t\tid: comment.id,\n\t\t\t\tbody: comment.body,\n\t\t\t\tcreatedAt: comment.createdAt,\n\t\t\t\tauthor: null, // Linear SDK doesn't return comment author info in basic fetch\n\t\t\t\t...(comment.updatedAt && { updatedAt: comment.updatedAt }),\n\t\t\t}))\n\t\t} catch {\n\t\t\treturn []\n\t\t}\n\t}\n\n\t/**\n\t * Fetch a specific comment by ID\n\t */\n\tasync getComment(input: GetCommentInput): Promise<CommentDetailResult> {\n\t\tconst { commentId } = input\n\n\t\tconst raw = await getLinearComment(commentId)\n\n\t\t// Process images to make them accessible\n\t\tconst processedBody = await processMarkdownImages(raw.body, 'linear')\n\n\t\treturn {\n\t\t\tid: raw.id,\n\t\t\tbody: processedBody,\n\t\t\tauthor: null, // Linear SDK doesn't return comment author info in basic fetch\n\t\t\tcreated_at: raw.createdAt,\n\t\t}\n\t}\n\n\t/**\n\t * Create a new comment on an issue\n\t */\n\tasync createComment(input: CreateCommentInput): Promise<CommentResult> {\n\t\tconst { number, body } = input\n\t\t// Note: Linear doesn't distinguish between issue and PR comments\n\t\t// (Linear doesn't have PRs - that's GitHub-specific)\n\n\t\t// Convert HTML details/summary blocks to Linear's collapsible format\n\t\tconst convertedBody = LinearMarkupConverter.convertToLinear(body)\n\n\t\tconst result = await createLinearComment(number, convertedBody)\n\n\t\treturn {\n\t\t\tid: result.id,\n\t\t\turl: result.url,\n\t\t\tcreated_at: result.createdAt,\n\t\t}\n\t}\n\n\t/**\n\t * Update an existing comment\n\t */\n\tasync updateComment(input: UpdateCommentInput): Promise<CommentResult> {\n\t\tconst { commentId, body } = input\n\n\t\t// Convert HTML details/summary blocks to Linear's collapsible format\n\t\tconst convertedBody = LinearMarkupConverter.convertToLinear(body)\n\n\t\tconst result = await updateLinearComment(commentId, convertedBody)\n\n\t\treturn {\n\t\t\tid: result.id,\n\t\t\turl: result.url,\n\t\t\tupdated_at: result.updatedAt,\n\t\t}\n\t}\n\n\t/**\n\t * Create a new issue\n\t */\n\tasync createIssue(input: CreateIssueInput): Promise<CreateIssueResult> {\n\t\tconst { title, body, labels, teamKey } = input\n\n\t\t// Fallback chain: explicit param > settings (via env) > cached key from getIssue()\n\t\tconst effectiveTeamKey = teamKey ?? process.env.LINEAR_TEAM_KEY ?? this.cachedTeamKey\n\n\t\tif (!effectiveTeamKey) {\n\t\t\tthrow new Error('teamKey is required for Linear issue creation. Configure issueManagement.linear.teamId in settings, or call getIssue first to extract the team from an issue identifier.')\n\t\t}\n\n\t\tconst result = await createLinearIssue(title, body, effectiveTeamKey, labels)\n\n\t\treturn {\n\t\t\tid: result.identifier,\n\t\t\turl: result.url,\n\t\t}\n\t}\n\n\t/**\n\t * Create a child issue linked to a parent issue\n\t * Linear supports atomic creation with parentId field\n\t */\n\tasync createChildIssue(input: CreateChildIssueInput): Promise<CreateIssueResult> {\n\t\tconst { parentId, title, body, labels, teamKey } = input\n\n\t\t// Fetch parent issue to get UUID (parentId in input is identifier like \"ENG-123\")\n\t\tconst parentIssue = await fetchLinearIssue(parentId)\n\n\t\t// Extract team key from parent identifier if not provided\n\t\tconst match = parentId.match(/^([A-Z]{2,})-\\d+$/i)\n\t\tconst effectiveTeamKey = teamKey ?? match?.[1]?.toUpperCase() ?? process.env.LINEAR_TEAM_KEY ?? this.cachedTeamKey\n\n\t\tif (!effectiveTeamKey) {\n\t\t\tthrow new Error('teamKey is required for Linear child issue creation. Provide teamKey parameter or use a parent identifier with team prefix.')\n\t\t}\n\n\t\t// Create child issue with parent's UUID\n\t\tconst result = await createLinearChildIssue(\n\t\t\ttitle,\n\t\t\tbody,\n\t\t\teffectiveTeamKey,\n\t\t\tparentIssue.id, // UUID, not identifier\n\t\t\tlabels\n\t\t)\n\n\t\treturn {\n\t\t\tid: result.identifier,\n\t\t\turl: result.url,\n\t\t}\n\t}\n\n\t/**\n\t * Create a blocking dependency between two issues\n\t */\n\tasync createDependency(input: CreateDependencyInput): Promise<void> {\n\t\tconst { blockingIssue, blockedIssue } = input\n\n\t\t// Fetch both issues to get their UUIDs\n\t\tconst [blockingIssueData, blockedIssueData] = await Promise.all([\n\t\t\tfetchLinearIssue(blockingIssue),\n\t\t\tfetchLinearIssue(blockedIssue),\n\t\t])\n\n\t\t// Create the blocking relation (blockingIssue blocks blockedIssue)\n\t\tawait createLinearIssueRelation(blockingIssueData.id, blockedIssueData.id)\n\t}\n\n\t/**\n\t * Get dependencies for an issue\n\t */\n\tasync getDependencies(input: GetDependenciesInput): Promise<DependenciesResult> {\n\t\tconst { number, direction } = input\n\n\t\treturn await getLinearIssueDependencies(number, direction)\n\t}\n\n\t/**\n\t * Remove a blocking dependency between two issues\n\t */\n\tasync removeDependency(input: RemoveDependencyInput): Promise<void> {\n\t\tconst { blockingIssue, blockedIssue } = input\n\n\t\t// Find the relation ID\n\t\tconst relationId = await findLinearIssueRelation(blockingIssue, blockedIssue)\n\n\t\tif (!relationId) {\n\t\t\tthrow new Error(`No blocking dependency found from ${blockingIssue} to ${blockedIssue}`)\n\t\t}\n\n\t\t// Delete the relation\n\t\tawait deleteLinearIssueRelation(relationId)\n\t}\n\n\t/**\n\t * Get child issues of a parent issue\n\t */\n\tasync getChildIssues(input: GetChildIssuesInput): Promise<ChildIssueResult[]> {\n\t\tconst { number } = input\n\t\t// repo is ignored for Linear\n\t\treturn await getLinearChildIssues(number)\n\t}\n\n\t/**\n\t * Close an issue by transitioning to \"Done\" state\n\t */\n\tasync closeIssue(input: CloseIssueInput): Promise<void> {\n\t\tconst { number } = input\n\t\t// repo is ignored for Linear\n\t\tawait updateLinearIssueState(number, 'Done')\n\t}\n\n\t/**\n\t * Reopen a closed issue by transitioning to \"Todo\" state\n\t */\n\tasync reopenIssue(input: ReopenIssueInput): Promise<void> {\n\t\tconst { number } = input\n\t\t// repo is ignored for Linear\n\t\tawait updateLinearIssueState(number, 'Todo')\n\t}\n\n\t/**\n\t * Edit an issue's properties\n\t * State changes are delegated to closeIssue/reopenIssue\n\t */\n\tasync editIssue(input: EditIssueInput): Promise<void> {\n\t\tconst { number, title, body, state } = input\n\t\t// repo and labels are ignored for Linear\n\n\t\t// Handle state changes via close/reopen\n\t\tif (state === 'closed') {\n\t\t\tawait this.closeIssue({ number })\n\t\t} else if (state === 'open') {\n\t\t\tawait this.reopenIssue({ number })\n\t\t}\n\n\t\t// Handle title/body updates\n\t\tif (title !== undefined || body !== undefined) {\n\t\t\tawait editLinearIssue(number, {\n\t\t\t\t...(title !== undefined && { title }),\n\t\t\t\t...(body !== undefined && { description: body }),\n\t\t\t})\n\t\t}\n\t}\n}\n","/**\n * Jira implementation of Issue Management Provider\n * Uses JiraIssueTracker for all operations\n * Normalizes Jira-specific fields to provider-agnostic core fields\n */\n\nimport type {\n\tIssueManagementProvider,\n\tGetIssueInput,\n\tGetPRInput,\n\tPRResult,\n\tGetCommentInput,\n\tCreateCommentInput,\n\tUpdateCommentInput,\n\tCreateIssueInput,\n\tCreateChildIssueInput,\n\tCreateDependencyInput,\n\tGetDependenciesInput,\n\tDependenciesResult,\n\tRemoveDependencyInput,\n\tGetChildIssuesInput,\n\tCloseIssueInput,\n\tReopenIssueInput,\n\tEditIssueInput,\n\tChildIssueResult,\n\tCreateIssueResult,\n\tIssueResult,\n\tCommentDetailResult,\n\tCommentResult,\n\tFlexibleAuthor,\n} from './types.js'\nimport { escapeJql } from '../utils/jira.js'\nimport { JiraIssueTracker } from '../lib/providers/jira/JiraIssueTracker.js'\nimport type { JiraTrackerConfig } from '../lib/providers/jira/JiraIssueTracker.js'\nimport type { Issue } from '../types/index.js'\nimport { SettingsManager } from '../lib/SettingsManager.js'\nimport type { IloomSettings } from '../lib/SettingsManager.js'\n\n/**\n * Normalize Jira author to FlexibleAuthor format\n */\nfunction normalizeAuthor(author: { displayName?: string; emailAddress?: string; accountId?: string } | null | undefined): FlexibleAuthor | null {\n\tif (!author) return null\n\n\treturn {\n\t\tid: author.accountId ?? author.emailAddress ?? 'unknown',\n\t\tdisplayName: author.displayName ?? author.emailAddress ?? 'Unknown',\n\t\t...(author.emailAddress && { email: author.emailAddress }),\n\t\t...(author.accountId && { accountId: author.accountId }),\n\t}\n}\n/**\n * Extract Jira configuration from settings (for cli usage) or environment variables (in mcp server)\n */\nconst getJiraTrackerConfig = (settings: IloomSettings): JiraTrackerConfig => {\n\tconst jiraSettings = settings.issueManagement?.jira\n\n\tif (jiraSettings?.host && jiraSettings?.username && jiraSettings?.apiToken && jiraSettings?.projectKey) {\n\t\t\tconst config: JiraTrackerConfig = {\n\t\t\thost: jiraSettings.host,\n\t\t\tusername: jiraSettings.username,\n\t\t\tapiToken: jiraSettings.apiToken,\n\t\t\tprojectKey: jiraSettings.projectKey,\n\t\t}\n\n\t\tif (jiraSettings.transitionMappings) {\n\t\t\tconfig.transitionMappings = jiraSettings.transitionMappings\n\t\t}\n\t\tif (jiraSettings.defaultIssueType) {\n\t\t\tconfig.defaultIssueType = jiraSettings.defaultIssueType\n\t\t}\n\t\tif (jiraSettings.defaultSubtaskType) {\n\t\t\tconfig.defaultSubtaskType = jiraSettings.defaultSubtaskType\n\t\t}\n\n\t\treturn config;\n\t}\n\n\tif (process.env.JIRA_HOST && process.env.JIRA_USERNAME && process.env.JIRA_API_TOKEN && process.env.JIRA_PROJECT_KEY) {\n\t\tconst config: JiraTrackerConfig = {\n\t\t\thost: process.env.JIRA_HOST,\n\t\t\tusername: process.env.JIRA_USERNAME,\n\t\t\tapiToken: process.env.JIRA_API_TOKEN,\n\t\t\tprojectKey: process.env.JIRA_PROJECT_KEY,\n\t\t}\n\n\t\tif (process.env.JIRA_TRANSITION_MAPPINGS) {\n\t\t\ttry {\n\t\t\t\tconfig.transitionMappings = JSON.parse(process.env.JIRA_TRANSITION_MAPPINGS)\n\t\t\t} catch {\n\t\t\t\tthrow new Error('Invalid JSON in JIRA_TRANSITION_MAPPINGS environment variable')\n\t\t\t}\n\t\t}\n\t\tif (process.env.JIRA_DEFAULT_ISSUE_TYPE) {\n\t\t\tconfig.defaultIssueType = process.env.JIRA_DEFAULT_ISSUE_TYPE\n\t\t}\n\t\tif (process.env.JIRA_DEFAULT_SUBTASK_TYPE) {\n\t\t\tconfig.defaultSubtaskType = process.env.JIRA_DEFAULT_SUBTASK_TYPE\n\t\t}\n\n\t\treturn config\n\t}\n\n\tthrow new Error(\n\t\t'Missing required Jira settings: issueManagement.jira.{host, username, apiToken, projectKey} or corresponding environment variables'\n\t)\t\n}\n\n/**\n * Jira-specific implementation of IssueManagementProvider\n */\nexport class JiraIssueManagementProvider implements IssueManagementProvider {\n\treadonly providerName = 'jira'\n\treadonly issuePrefix = ''\n\tprivate tracker: JiraIssueTracker\n\tprivate projectKey: string\n\n\tconstructor(settings: IloomSettings) {\n\t\tconst config = getJiraTrackerConfig(settings);\n\n\t\tthis.tracker = new JiraIssueTracker(config)\n\t\tthis.projectKey = config.projectKey\n\t}\n\n\t/**\n\t * Static factory for convenience when settings aren't pre-loaded\n\t */\n\tstatic async create(): Promise<JiraIssueManagementProvider> {\n\t\tconst settingsManager = new SettingsManager()\n\t\tconst settings = await settingsManager.loadSettings()\n\t\treturn new JiraIssueManagementProvider(settings)\n\t}\n\n\t/**\n\t * Fetch issue details using JiraIssueTracker\n\t */\n\tasync getIssue(input: GetIssueInput): Promise<IssueResult> {\n\t\tconst { number, includeComments = true } = input\n\n\t\t// Fetch issue from Jira\n\t\tconst issue = await this.tracker.getIssue(number)\n\t\tconst issueExt = issue as Issue & {\n\t\t\tid?: string\n\t\t\tkey?: string\n\t\t\tauthor?: {\n\t\t\t\tdisplayName?: string\n\t\t\t\temailAddress?: string\n\t\t\t\taccountId?: string\n\t\t\t}\n\t\t\tissueType?: string\n\t\t\tpriority?: string\n\t\t\tstatus?: string\n\t\t}\n\n\t\t// Normalize to IssueResult format\n\t\tconst result: IssueResult = {\n\t\t\tid: issueExt.id ?? String(issue.number),\n\t\t\ttitle: issue.title,\n\t\t\tbody: issue.body,\n\t\t\tstate: issue.state,\n\t\t\turl: issue.url,\n\t\t\tprovider: 'jira',\n\t\t\tauthor: normalizeAuthor(issueExt.author),\n\t\t\tnumber: issue.number,\n\t\t\tkey: issueExt.key,\n\t\t\t// Preserve Jira-specific fields\n\t\t\t...(issueExt.issueType && { issueType: issueExt.issueType }),\n\t\t\t...(issueExt.priority && { priority: issueExt.priority }),\n\t\t\t...(issueExt.status && { status: issueExt.status }),\n\t\t}\n\n\t\t// Add labels if present\n\t\tif (issue.labels && issue.labels.length > 0) {\n\t\t\tresult.labels = issue.labels.map(label => ({ name: label }))\n\t\t}\n\n\t\t// Add assignees if present - Issue type uses assignees array of strings\n\t\tif (issue.assignees && issue.assignees.length > 0) {\n\t\t\tresult.assignees = issue.assignees.map(name => ({\n\t\t\t\tid: name,\n\t\t\t\tdisplayName: name,\n\t\t\t}))\n\t\t}\n\n\t\t// Fetch and add comments if requested\n\t\tif (includeComments) {\n\t\t\tconst comments = await this.tracker.getComments(number)\n\t\t\tresult.comments = comments.map((comment: {\n\t\t\t\tid: string\n\t\t\t\tbody: string\n\t\t\t\tauthor: { displayName: string; emailAddress: string; accountId: string }\n\t\t\t\tcreatedAt: string\n\t\t\t\tupdatedAt: string\n\t\t\t}) => ({\n\t\t\t\tid: comment.id,\n\t\t\t\tbody: comment.body,\n\t\t\t\tauthor: normalizeAuthor(comment.author),\n\t\t\t\tcreatedAt: comment.createdAt,\n\t\t\t\tupdatedAt: comment.updatedAt,\n\t\t\t}))\n\t\t}\n\n\t\treturn result\n\t}\n\n\t/**\n\t * Fetch a specific comment by ID\n\t */\n\tasync getComment(input: GetCommentInput): Promise<CommentDetailResult> {\n\t\tconst { commentId, number } = input\n\n\t\t// Fetch all comments and find the specific one\n\t\tconst comments = await this.tracker.getComments(number)\n\t\tconst comment = comments.find(c => c.id === commentId)\n\n\t\tif (!comment) {\n\t\t\tthrow new Error(`Comment ${commentId} not found on issue ${number}`)\n\t\t}\n\n\t\treturn {\n\t\t\tid: comment.id,\n\t\t\tbody: comment.body,\n\t\t\tauthor: normalizeAuthor(comment.author),\n\t\t\tcreated_at: comment.createdAt,\n\t\t\tupdated_at: comment.updatedAt,\n\t\t}\n\t}\n\n\t/**\n\t * Create a new comment on an issue\n\t */\n\tasync createComment(input: CreateCommentInput): Promise<CommentResult> {\n\t\tconst { number, body } = input\n\t\tconst normalizedKey = this.tracker.normalizeIdentifier(number)\n\n\t\t// Jira doesn't distinguish between issue and PR comments\n\t\tconst comment = await this.tracker.addComment(normalizedKey, body)\n\n\t\treturn {\n\t\t\tid: comment.id,\n\t\t\turl: `${this.tracker.getConfig().host}/browse/${normalizedKey}?focusedCommentId=${comment.id}`,\n\t\t\tcreated_at: new Date().toISOString(),\n\t\t}\n\t}\n\n\t/**\n\t * Update an existing comment\n\t */\n\tasync updateComment(input: UpdateCommentInput): Promise<CommentResult> {\n\t\tconst { commentId, number, body } = input\n\t\tconst normalizedKey = this.tracker.normalizeIdentifier(number)\n\n\t\t// Update comment via tracker\n\t\tawait this.tracker.updateComment(normalizedKey, commentId, body)\n\n\t\treturn {\n\t\t\tid: commentId,\n\t\t\turl: `${this.tracker.getConfig().host}/browse/${normalizedKey}?focusedCommentId=${commentId}`,\n\t\t\tupdated_at: new Date().toISOString(),\n\t\t}\n\t}\n\n\t/**\n\t * Create a new issue\n\t */\n\tasync createIssue(input: CreateIssueInput): Promise<CreateIssueResult> {\n\t\tconst { title, body } = input\n\n\t\t// Create issue via tracker (labels not supported in current implementation)\n\t\tconst issue = await this.tracker.createIssue(title, body)\n\n\t\tconst result: CreateIssueResult = {\n\t\t\tid: String(issue.number),\n\t\t\turl: issue.url,\n\t\t}\n\n\t\t// Only add number if it's actually a number\n\t\tif (typeof issue.number === 'number') {\n\t\t\tresult.number = issue.number\n\t\t}\n\n\t\treturn result\n\t}\n\n\t/**\n\t * Fetch pull request details\n\t * Jira does not have pull requests - throw like Linear does\n\t */\n\tasync getPR(_input: GetPRInput): Promise<PRResult> {\n\t\tthrow new Error(\n\t\t\t'Jira does not support pull requests. PRs exist only on GitHub. Use the GitHub provider for PR operations.'\n\t\t)\n\t}\n\n\t/**\n\t * Create a child issue linked to a parent issue\n\t * Uses Jira's parent field to create a subtask\n\t */\n\tasync createChildIssue(input: CreateChildIssueInput): Promise<CreateIssueResult> {\n\t\tconst { parentId, title, body } = input\n\t\tconst parentKey = this.tracker.normalizeIdentifier(parentId)\n\n\t\tconst jiraIssue = await this.tracker.getApiClient().createIssueWithParent(\n\t\t\tthis.projectKey,\n\t\t\ttitle,\n\t\t\tbody,\n\t\t\tparentKey,\n\t\t\tthis.tracker.getConfig().defaultSubtaskType\n\t\t)\n\n\t\treturn {\n\t\t\tid: jiraIssue.key,\n\t\t\turl: `${this.tracker.getConfig().host}/browse/${jiraIssue.key}`,\n\t\t}\n\t}\n\n\t/**\n\t * Create a blocking dependency between two issues\n\t * Uses Jira issue links with \"Blocks\" link type\n\t */\n\tasync createDependency(input: CreateDependencyInput): Promise<void> {\n\t\tconst blockingKey = this.tracker.normalizeIdentifier(input.blockingIssue)\n\t\tconst blockedKey = this.tracker.normalizeIdentifier(input.blockedIssue)\n\n\t\t// In Jira \"Blocks\" link type: inwardIssue = blocker, outwardIssue = blocked\n\t\tawait this.tracker.getApiClient().createIssueLink(blockingKey, blockedKey, 'Blocks')\n\t}\n\n\t/**\n\t * Get dependencies for an issue\n\t * Parses issue links of type \"Blocks\"\n\t */\n\tasync getDependencies(input: GetDependenciesInput): Promise<DependenciesResult> {\n\t\tconst issueKey = this.tracker.normalizeIdentifier(input.number)\n\t\tconst host = this.tracker.getConfig().host\n\n\t\tconst issue = await this.tracker.getApiClient().getIssue(issueKey)\n\t\tconst links = issue.fields.issuelinks ?? []\n\n\t\tconst blocking: DependenciesResult['blocking'] = []\n\t\tconst blockedBy: DependenciesResult['blockedBy'] = []\n\n\t\tfor (const link of links) {\n\t\t\tif (link.type.name !== 'Blocks') continue\n\n\t\t\t// inwardIssue present = the other issue is the blocker\n\t\t\t// → that issue blocks this issue → blockedBy\n\t\t\tif (link.inwardIssue) {\n\t\t\t\tblockedBy.push({\n\t\t\t\t\tid: link.inwardIssue.key,\n\t\t\t\t\ttitle: link.inwardIssue.fields.summary,\n\t\t\t\t\turl: `${host}/browse/${link.inwardIssue.key}`,\n\t\t\t\t\tstate: link.inwardIssue.fields.status.name.toLowerCase(),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\t// outwardIssue present = the other issue is blocked by this issue\n\t\t\t// → this issue blocks that issue → blocking\n\t\t\tif (link.outwardIssue) {\n\t\t\t\tblocking.push({\n\t\t\t\t\tid: link.outwardIssue.key,\n\t\t\t\t\ttitle: link.outwardIssue.fields.summary,\n\t\t\t\t\turl: `${host}/browse/${link.outwardIssue.key}`,\n\t\t\t\t\tstate: link.outwardIssue.fields.status.name.toLowerCase(),\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\tif (input.direction === 'blocking') {\n\t\t\treturn { blocking, blockedBy: [] }\n\t\t}\n\t\tif (input.direction === 'blocked_by') {\n\t\t\treturn { blocking: [], blockedBy }\n\t\t}\n\t\treturn { blocking, blockedBy }\n\t}\n\n\t/**\n\t * Remove a blocking dependency between two issues\n\t * Finds the matching \"Blocks\" link and deletes it\n\t */\n\tasync removeDependency(input: RemoveDependencyInput): Promise<void> {\n\t\tconst blockingKey = this.tracker.normalizeIdentifier(input.blockingIssue)\n\t\tconst blockedKey = this.tracker.normalizeIdentifier(input.blockedIssue)\n\n\t\t// Fetch the blocked issue to find the link\n\t\tconst issue = await this.tracker.getApiClient().getIssue(blockedKey)\n\t\tconst links = issue.fields.issuelinks ?? []\n\n\t\t// When fetching the blocked issue (B), the blocking issue (A) appears as\n\t\t// inwardIssue in the link data\n\t\tconst matchingLink = links.find(link =>\n\t\t\tlink.type.name === 'Blocks' && link.inwardIssue?.key === blockingKey\n\t\t)\n\n\t\tif (!matchingLink) {\n\t\t\tthrow new Error(\n\t\t\t\t`No \"Blocks\" dependency found from ${blockingKey} to ${blockedKey}`\n\t\t\t)\n\t\t}\n\n\t\tawait this.tracker.getApiClient().deleteIssueLink(matchingLink.id)\n\t}\n\n\t/**\n\t * Get child issues of a parent issue\n\t * Uses JQL search: parent = KEY\n\t */\n\tasync getChildIssues(input: GetChildIssuesInput): Promise<ChildIssueResult[]> {\n\t\tconst parentKey = this.tracker.normalizeIdentifier(input.number)\n\t\tconst host = this.tracker.getConfig().host\n\n\t\tconst issues = await this.tracker.getApiClient().searchIssues(`parent = \"${escapeJql(parentKey)}\"`)\n\n\t\treturn issues.map(issue => ({\n\t\t\tid: issue.key,\n\t\t\ttitle: issue.fields.summary,\n\t\t\turl: `${host}/browse/${issue.key}`,\n\t\t\tstate: issue.fields.status.name.toLowerCase(),\n\t\t}))\n\t}\n\n\t/**\n\t * Close an issue by transitioning to \"Done\" state\n\t */\n\tasync closeIssue(input: CloseIssueInput): Promise<void> {\n\t\tconst issueKey = this.tracker.normalizeIdentifier(input.number)\n\t\tawait this.tracker.closeIssue(issueKey)\n\t}\n\n\t/**\n\t * Reopen a closed issue\n\t */\n\tasync reopenIssue(input: ReopenIssueInput): Promise<void> {\n\t\tconst issueKey = this.tracker.normalizeIdentifier(input.number)\n\t\tawait this.tracker.reopenIssue(issueKey)\n\t}\n\n\t/**\n\t * Edit an issue's properties\n\t * State changes are delegated to closeIssue/reopenIssue\n\t */\n\tasync editIssue(input: EditIssueInput): Promise<void> {\n\t\tconst { number, title, body, state } = input\n\n\t\t// Handle state changes via close/reopen\n\t\tif (state === 'closed') {\n\t\t\tawait this.closeIssue({ number })\n\t\t} else if (state === 'open') {\n\t\t\tawait this.reopenIssue({ number })\n\t\t}\n\n\t\t// Handle title/body updates via Jira API\n\t\tif (title !== undefined || body !== undefined) {\n\t\t\tconst issueKey = this.tracker.normalizeIdentifier(number)\n\t\t\tawait this.tracker.getApiClient().updateIssue(issueKey, {\n\t\t\t\t...(title !== undefined && { summary: title }),\n\t\t\t\t...(body !== undefined && { description: body }),\n\t\t\t})\n\t\t}\n\t}\n}\n","/**\n * Factory for creating issue management providers\n */\n\nimport type { IssueManagementProvider, IssueProvider } from './types.js'\nimport { GitHubIssueManagementProvider } from './GitHubIssueManagementProvider.js'\nimport { LinearIssueManagementProvider } from './LinearIssueManagementProvider.js'\nimport { JiraIssueManagementProvider } from './JiraIssueManagementProvider.js'\nimport type { IloomSettings } from '../lib/SettingsManager.js'\n\n/**\n * Factory class for creating issue management providers\n */\nexport class IssueManagementProviderFactory {\n\t/**\n\t * Create an issue management provider based on the provider type\n\t * @param provider - The provider type (github, linear, jira)\n\t * @param settings - Required for Jira provider, optional for others\n\t */\n\tstatic create(provider: IssueProvider, settings?: IloomSettings): IssueManagementProvider {\n\t\tswitch (provider) {\n\t\t\tcase 'github':\n\t\t\t\treturn new GitHubIssueManagementProvider()\n\t\t\tcase 'linear':\n\t\t\t\treturn new LinearIssueManagementProvider()\n\t\t\tcase 'jira':\n\t\t\t\tif (!settings) {\n\t\t\t\t\tthrow new Error('Settings required for Jira provider')\n\t\t\t\t}\n\t\t\t\treturn new JiraIssueManagementProvider(settings)\n\t\t\tdefault:\n\t\t\t\tthrow new Error(`Unsupported issue management provider: ${provider}`)\n\t\t}\n\t}\n}\n"],"mappings":"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","names":["join","extname","normalizeAuthor"]}
@@ -1,19 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ JiraIssueTracker,
3
4
  LinearServiceError,
4
5
  createLinearIssue,
5
6
  fetchLinearIssue,
7
+ getLinearChildIssues,
6
8
  updateLinearIssueState
7
- } from "./chunk-FJDRTVJX.js";
9
+ } from "./chunk-HEXKPKCK.js";
8
10
  import {
9
11
  GitHubService
10
- } from "./chunk-OFDN5NKS.js";
11
- import {
12
- promptConfirmation
13
- } from "./chunk-7JDMYTFZ.js";
12
+ } from "./chunk-KXDRI47U.js";
14
13
  import {
15
14
  getLogger
16
15
  } from "./chunk-6MLEBAYZ.js";
16
+ import {
17
+ promptConfirmation
18
+ } from "./chunk-7JDMYTFZ.js";
17
19
 
18
20
  // src/lib/LinearService.ts
19
21
  var LinearService = class {
@@ -127,6 +129,15 @@ var LinearService = class {
127
129
  const issue = await this.fetchIssue(identifier);
128
130
  return issue.url;
129
131
  }
132
+ /**
133
+ * Fetch child issues of a Linear parent issue
134
+ * @param parentIdentifier - Linear issue identifier (e.g., "ENG-123")
135
+ * @param _repo - Repository (unused for Linear)
136
+ * @returns Array of child issues
137
+ */
138
+ async getChildIssues(parentIdentifier, _repo) {
139
+ return getLinearChildIssues(parentIdentifier, this.config.apiToken ? { apiToken: this.config.apiToken } : void 0);
140
+ }
130
141
  /**
131
142
  * Move a Linear issue to "In Progress" state
132
143
  * @param identifier - Linear issue identifier
@@ -136,6 +147,23 @@ var LinearService = class {
136
147
  getLogger().info(`Moving Linear issue ${identifier} to In Progress`);
137
148
  await updateLinearIssueState(String(identifier), "In Progress");
138
149
  }
150
+ /**
151
+ * Move a Linear issue to "In Review" state
152
+ * @param identifier - Linear issue identifier
153
+ * @throws LinearServiceError if state update fails
154
+ */
155
+ async moveIssueToReadyForReview(identifier) {
156
+ getLogger().info(`Moving Linear issue ${identifier} to In Review`);
157
+ await updateLinearIssueState(String(identifier), "In Review");
158
+ }
159
+ /**
160
+ * Normalize identifier to canonical form (uppercase for Linear keys)
161
+ * @param identifier - Linear issue identifier (e.g., "eng-123" or "ENG-123")
162
+ * @returns Uppercase identifier (e.g., "ENG-123")
163
+ */
164
+ normalizeIdentifier(identifier) {
165
+ return String(identifier).toUpperCase();
166
+ }
139
167
  /**
140
168
  * Extract issue context for AI prompts
141
169
  * @param entity - Issue (Linear doesn't have PRs)
@@ -178,7 +206,7 @@ var IssueTrackerFactory = class {
178
206
  * @throws Error if provider type is not supported
179
207
  */
180
208
  static create(settings) {
181
- var _a, _b;
209
+ var _a, _b, _c;
182
210
  const provider = ((_a = settings.issueManagement) == null ? void 0 : _a.provider) ?? "github";
183
211
  getLogger().debug(`IssueTrackerFactory: Creating tracker for provider "${provider}"`);
184
212
  getLogger().debug(`IssueTrackerFactory: issueManagement settings:`, JSON.stringify(settings.issueManagement, null, 2));
@@ -201,6 +229,32 @@ var IssueTrackerFactory = class {
201
229
  getLogger().debug(`IssueTrackerFactory: Creating LinearService with config:`, JSON.stringify(linearConfig, null, 2));
202
230
  return new LinearService(linearConfig);
203
231
  }
232
+ case "jira": {
233
+ const jiraSettings = (_c = settings.issueManagement) == null ? void 0 : _c.jira;
234
+ if (!(jiraSettings == null ? void 0 : jiraSettings.host)) {
235
+ throw new Error("Jira host is required. Configure issueManagement.jira.host in .iloom/settings.json");
236
+ }
237
+ if (!(jiraSettings == null ? void 0 : jiraSettings.username)) {
238
+ throw new Error("Jira username is required. Configure issueManagement.jira.username in .iloom/settings.json");
239
+ }
240
+ if (!(jiraSettings == null ? void 0 : jiraSettings.apiToken)) {
241
+ throw new Error("Jira API token is required. Configure issueManagement.jira.apiToken in .iloom/settings.local.json");
242
+ }
243
+ if (!(jiraSettings == null ? void 0 : jiraSettings.projectKey)) {
244
+ throw new Error("Jira project key is required. Configure issueManagement.jira.projectKey in .iloom/settings.json");
245
+ }
246
+ const jiraConfig = {
247
+ host: jiraSettings.host,
248
+ username: jiraSettings.username,
249
+ apiToken: jiraSettings.apiToken,
250
+ projectKey: jiraSettings.projectKey
251
+ };
252
+ if (jiraSettings.transitionMappings) {
253
+ jiraConfig.transitionMappings = jiraSettings.transitionMappings;
254
+ }
255
+ getLogger().debug(`IssueTrackerFactory: Creating JiraIssueTracker for host: ${jiraSettings.host}`);
256
+ return new JiraIssueTracker(jiraConfig);
257
+ }
204
258
  default:
205
259
  throw new Error(`Unsupported issue tracker provider: ${provider}`);
206
260
  }
@@ -221,4 +275,4 @@ var IssueTrackerFactory = class {
221
275
  export {
222
276
  IssueTrackerFactory
223
277
  };
224
- //# sourceMappingURL=chunk-KXGQYLFZ.js.map
278
+ //# sourceMappingURL=chunk-UKBAJ2QQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/LinearService.ts","../src/lib/IssueTrackerFactory.ts"],"sourcesContent":["/**\n * LinearService - IssueTracker implementation for Linear\n * Implements issue tracking operations using the @linear/sdk\n */\n\nimport type { Issue, PullRequest, IssueTrackerInputDetection } from '../types/index.js'\nimport type { LinearIssue } from '../types/linear.js'\nimport { LinearServiceError } from '../types/linear.js'\nimport {\n fetchLinearIssue,\n createLinearIssue,\n updateLinearIssueState,\n getLinearChildIssues,\n} from '../utils/linear.js'\nimport { promptConfirmation } from '../utils/prompt.js'\nimport type { IssueTracker } from './IssueTracker.js'\nimport { getLogger } from '../utils/logger-context.js'\n\n/**\n * Linear service configuration options\n */\nexport interface LinearServiceConfig {\n /** Linear team key (e.g., \"ENG\", \"PLAT\") */\n teamId?: string\n /** Branch naming template (e.g., \"feat/{{key}}__{{title}}\") */\n branchFormat?: string\n /** Linear API token (lin_api_...). If provided, sets process.env.LINEAR_API_TOKEN */\n apiToken?: string\n}\n\n/**\n * Linear implementation of IssueTracker interface\n */\nexport class LinearService implements IssueTracker {\n // IssueTracker interface implementation\n readonly providerName = 'linear'\n readonly supportsPullRequests = false // Linear doesn't have pull requests\n\n private config: LinearServiceConfig\n private prompter: (message: string) => Promise<boolean>\n\n constructor(\n config?: LinearServiceConfig,\n options?: { prompter?: (message: string) => Promise<boolean> },\n ) {\n this.config = config ?? {}\n this.prompter = options?.prompter ?? promptConfirmation\n\n // Set API token from config if provided (follows mcp.ts pattern)\n if (this.config.apiToken) {\n process.env.LINEAR_API_TOKEN = this.config.apiToken\n }\n }\n\n /**\n * Detect if input matches Linear identifier format (TEAM-NUMBER)\n * @param input - User input string\n * @param _repo - Repository (unused for Linear)\n * @returns Detection result with type and identifier\n */\n public async detectInputType(\n input: string,\n _repo?: string,\n ): Promise<IssueTrackerInputDetection> {\n getLogger().debug(`LinearService.detectInputType called with input: \"${input}\"`)\n\n // Pattern: TEAM-NUMBER (e.g., ENG-123, PLAT-456)\n // Requires at least 2 letters before dash to avoid conflict with PR-123 format\n const linearPattern = /^([A-Z]{2,}-\\d+)$/i\n const match = input.match(linearPattern)\n\n if (!match?.[1]) {\n getLogger().debug(`LinearService: Input \"${input}\" does not match Linear pattern`)\n return { type: 'unknown', identifier: null, rawInput: input }\n }\n\n const identifier = match[1].toUpperCase()\n getLogger().debug(`LinearService: Matched Linear identifier: ${identifier}`)\n\n // Validate the issue exists in Linear\n getLogger().debug(`LinearService: Checking if ${identifier} is a valid Linear issue via SDK`)\n const issue = await this.isValidIssue(identifier)\n\n if (issue) {\n getLogger().debug(`LinearService: Issue ${identifier} found: \"${issue.title}\"`)\n return { type: 'issue', identifier, rawInput: input }\n }\n\n // Not found\n getLogger().debug(`LinearService: Issue ${identifier} NOT found by SDK`)\n return { type: 'unknown', identifier: null, rawInput: input }\n }\n\n /**\n * Fetch a Linear issue by identifier\n * @param identifier - Linear issue identifier (string or number)\n * @param _repo - Repository (unused for Linear)\n * @returns Generic Issue type\n * @throws LinearServiceError if issue not found\n */\n public async fetchIssue(identifier: string | number, _repo?: string): Promise<Issue> {\n const linearIssue = await fetchLinearIssue(String(identifier))\n return this.mapLinearIssueToIssue(linearIssue)\n }\n\n /**\n * Check if an issue identifier is valid (silent validation)\n * @param identifier - Linear issue identifier\n * @param _repo - Repository (unused for Linear)\n * @returns Issue if valid, false if not found\n */\n public async isValidIssue(identifier: string | number, _repo?: string): Promise<Issue | false> {\n try {\n return await this.fetchIssue(identifier)\n } catch (error) {\n // Return false for NOT_FOUND errors (expected during detection)\n if (error instanceof LinearServiceError && error.code === 'NOT_FOUND') {\n return false\n }\n // Re-throw unexpected errors\n throw error\n }\n }\n\n /**\n * Validate issue state and prompt user if closed\n * @param issue - Issue to validate\n * @throws LinearServiceError if user cancels due to closed issue\n */\n public async validateIssueState(issue: Issue): Promise<void> {\n if (issue.state === 'closed') {\n const shouldContinue = await this.prompter(\n `Issue ${issue.number} is closed. Continue anyway?`,\n )\n\n if (!shouldContinue) {\n throw new LinearServiceError('INVALID_STATE', 'User cancelled due to closed issue')\n }\n }\n }\n\n /**\n * Create a new Linear issue\n * @param title - Issue title\n * @param body - Issue description (markdown)\n * @param _repository - Repository (unused for Linear)\n * @param labels - Optional label names\n * @returns Created issue identifier and URL\n * @throws LinearServiceError if teamId not configured or creation fails\n */\n public async createIssue(\n title: string,\n body: string,\n _repository?: string,\n labels?: string[],\n ): Promise<{ number: string | number; url: string }> {\n // Require teamId configuration\n if (!this.config.teamId) {\n throw new LinearServiceError(\n 'INVALID_STATE',\n 'Linear teamId not configured. Run `il init` to configure Linear settings.',\n )\n }\n\n getLogger().info(`Creating Linear issue in team ${this.config.teamId}: ${title}`)\n\n const result = await createLinearIssue(title, body, this.config.teamId, labels)\n\n return {\n number: result.identifier,\n url: result.url,\n }\n }\n\n /**\n * Get the web URL for a Linear issue\n * @param identifier - Linear issue identifier\n * @param _repo - Repository (unused for Linear)\n * @returns Issue URL\n */\n public async getIssueUrl(identifier: string | number, _repo?: string): Promise<string> {\n const issue = await this.fetchIssue(identifier)\n return issue.url\n }\n\n /**\n * Fetch child issues of a Linear parent issue\n * @param parentIdentifier - Linear issue identifier (e.g., \"ENG-123\")\n * @param _repo - Repository (unused for Linear)\n * @returns Array of child issues\n */\n public async getChildIssues(parentIdentifier: string, _repo?: string): Promise<Array<{ id: string; title: string; url: string; state: string }>> {\n return getLinearChildIssues(parentIdentifier, this.config.apiToken ? { apiToken: this.config.apiToken } : undefined)\n }\n\n /**\n * Move a Linear issue to \"In Progress\" state\n * @param identifier - Linear issue identifier\n * @throws LinearServiceError if state update fails\n */\n public async moveIssueToInProgress(identifier: string | number): Promise<void> {\n getLogger().info(`Moving Linear issue ${identifier} to In Progress`)\n await updateLinearIssueState(String(identifier), 'In Progress')\n }\n\n /**\n * Move a Linear issue to \"In Review\" state\n * @param identifier - Linear issue identifier\n * @throws LinearServiceError if state update fails\n */\n public async moveIssueToReadyForReview(identifier: string | number): Promise<void> {\n getLogger().info(`Moving Linear issue ${identifier} to In Review`)\n await updateLinearIssueState(String(identifier), 'In Review')\n }\n\n /**\n * Normalize identifier to canonical form (uppercase for Linear keys)\n * @param identifier - Linear issue identifier (e.g., \"eng-123\" or \"ENG-123\")\n * @returns Uppercase identifier (e.g., \"ENG-123\")\n */\n public normalizeIdentifier(identifier: string | number): string {\n return String(identifier).toUpperCase()\n }\n\n /**\n * Extract issue context for AI prompts\n * @param entity - Issue (Linear doesn't have PRs)\n * @returns Formatted context string\n */\n public extractContext(entity: Issue | PullRequest): string {\n // Linear doesn't have PRs, always an issue\n const issue = entity as Issue\n return `Linear Issue ${issue.number}: ${issue.title}\\nState: ${issue.state}\\n\\n${issue.body}`\n }\n\n /**\n * Map Linear API issue to generic Issue type\n * @param linear - Linear issue from SDK\n * @returns Generic Issue type\n */\n private mapLinearIssueToIssue(linear: LinearIssue): Issue {\n return {\n number: linear.identifier, // Keep as string (e.g., \"ENG-123\")\n title: linear.title,\n body: linear.description ?? '',\n state: linear.state ? (linear.state.toLowerCase().includes('done') || linear.state.toLowerCase().includes('completed') || linear.state.toLowerCase().includes('canceled') ? 'closed' : 'open') : 'open',\n labels: [],\n assignees: [],\n url: linear.url,\n }\n }\n}\n","// IssueTrackerFactory - creates appropriate IssueTracker based on settings\n// Follows pattern from database provider instantiation\n\nimport type { IssueTracker } from './IssueTracker.js'\nimport { GitHubService } from './GitHubService.js'\nimport { LinearService, type LinearServiceConfig } from './LinearService.js'\nimport { JiraIssueTracker, type JiraTrackerConfig } from './providers/jira/index.js'\nimport type { IloomSettings } from './SettingsManager.js'\nimport { getLogger } from '../utils/logger-context.js'\n\nexport type IssueTrackerProviderType = 'github' | 'linear' | 'jira'\n\n/**\n * Factory for creating IssueTracker instances based on settings\n * Provides a single point of provider instantiation\n *\n * Usage:\n * const tracker = IssueTrackerFactory.create(settings, { useClaude: true })\n * const issue = await tracker.fetchIssue(123)\n */\nexport class IssueTrackerFactory {\n\t/**\n\t * Create an IssueTracker instance based on settings configuration\n\t * Defaults to GitHub if no provider specified\n\t *\n\t * @param settings - iloom settings containing issueManagement.provider\n\t * @returns IssueTracker instance configured for the specified provider\n\t * @throws Error if provider type is not supported\n\t */\n\tstatic create(settings: IloomSettings): IssueTracker {\n\t\tconst provider = settings.issueManagement?.provider ?? 'github'\n\n\t\tgetLogger().debug(`IssueTrackerFactory: Creating tracker for provider \"${provider}\"`)\n\t\tgetLogger().debug(`IssueTrackerFactory: issueManagement settings:`, JSON.stringify(settings.issueManagement, null, 2))\n\n\t\tswitch (provider) {\n\t\t\tcase 'github':\n\t\t\t\tgetLogger().debug('IssueTrackerFactory: Creating GitHubService')\n\t\t\t\treturn new GitHubService()\n\t\t\tcase 'linear': {\n\t\t\t\tconst linearSettings = settings.issueManagement?.linear\n\t\t\t\tconst linearConfig: LinearServiceConfig = {}\n\n\t\t\t\tif (linearSettings?.teamId) {\n\t\t\t\t\tlinearConfig.teamId = linearSettings.teamId\n\t\t\t\t}\n\t\t\t\tif (linearSettings?.branchFormat) {\n\t\t\t\t\tlinearConfig.branchFormat = linearSettings.branchFormat\n\t\t\t\t}\n\t\t\t\tif (linearSettings?.apiToken) {\n\t\t\t\t\tlinearConfig.apiToken = linearSettings.apiToken\n\t\t\t\t}\n\n\t\t\t\tgetLogger().debug(`IssueTrackerFactory: Creating LinearService with config:`, JSON.stringify(linearConfig, null, 2))\n\t\t\t\treturn new LinearService(linearConfig)\n\t\t\t}\n\t\t\tcase 'jira': {\n\t\t\t\tconst jiraSettings = settings.issueManagement?.jira\n\t\t\t\t\n\t\t\t\tif (!jiraSettings?.host) {\n\t\t\t\t\tthrow new Error('Jira host is required. Configure issueManagement.jira.host in .iloom/settings.json')\n\t\t\t\t}\n\t\t\t\tif (!jiraSettings?.username) {\n\t\t\t\t\tthrow new Error('Jira username is required. Configure issueManagement.jira.username in .iloom/settings.json')\n\t\t\t\t}\n\t\t\t\tif (!jiraSettings?.apiToken) {\n\t\t\t\t\tthrow new Error('Jira API token is required. Configure issueManagement.jira.apiToken in .iloom/settings.local.json')\n\t\t\t\t}\n\t\t\t\tif (!jiraSettings?.projectKey) {\n\t\t\t\t\tthrow new Error('Jira project key is required. Configure issueManagement.jira.projectKey in .iloom/settings.json')\n\t\t\t\t}\n\n\t\t\t\tconst jiraConfig: JiraTrackerConfig = {\n\t\t\t\t\thost: jiraSettings.host,\n\t\t\t\t\tusername: jiraSettings.username,\n\t\t\t\t\tapiToken: jiraSettings.apiToken,\n\t\t\t\t\tprojectKey: jiraSettings.projectKey,\n\t\t\t\t}\n\n\t\t\t\tif (jiraSettings.transitionMappings) {\n\t\t\t\t\tjiraConfig.transitionMappings = jiraSettings.transitionMappings\n\t\t\t\t}\n\n\t\t\t\tgetLogger().debug(`IssueTrackerFactory: Creating JiraIssueTracker for host: ${jiraSettings.host}`)\n\t\t\t\treturn new JiraIssueTracker(jiraConfig)\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tthrow new Error(`Unsupported issue tracker provider: ${provider}`)\n\t\t}\n\t}\n\n\t/**\n\t * Get the configured provider name from settings\n\t * Defaults to 'github' if not configured\n\t *\n\t * @param settings - iloom settings\n\t * @returns Provider type string\n\t */\n\tstatic getProviderName(settings: IloomSettings): IssueTrackerProviderType {\n\t\treturn (settings.issueManagement?.provider ?? 'github') as IssueTrackerProviderType\n\t}\n}\n"],"mappings":"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","names":[]}
@@ -73,7 +73,7 @@ function parseJsonStreamOutput(output) {
73
73
  }
74
74
  }
75
75
  async function launchClaude(prompt, options = {}) {
76
- const { model, permissionMode, addDir, headless = false, appendSystemPrompt, mcpConfig, allowedTools, disallowedTools, agents, sessionId, noSessionPersistence, outputFormat, verbose, jsonMode } = options;
76
+ const { model, permissionMode, addDir, headless = false, appendSystemPrompt, mcpConfig, allowedTools, disallowedTools, agents, sessionId, noSessionPersistence, outputFormat, verbose, jsonMode, passthroughStdout, env: extraEnv, signal } = options;
77
77
  const log = getLogger();
78
78
  const args = [];
79
79
  if (headless) {
@@ -117,7 +117,37 @@ async function launchClaude(prompt, options = {}) {
117
117
  if (noSessionPersistence && headless) {
118
118
  args.push("--no-session-persistence");
119
119
  }
120
+ const claudeEnv = { ...process.env, CLAUDECODE: "0" };
121
+ function attachAbortSignal(subprocess) {
122
+ if (!signal) return;
123
+ const onAbort = () => {
124
+ subprocess.kill("SIGTERM");
125
+ };
126
+ signal.addEventListener("abort", onAbort, { once: true });
127
+ subprocess.on("exit", () => {
128
+ signal.removeEventListener("abort", onAbort);
129
+ });
130
+ }
120
131
  try {
132
+ if (headless && passthroughStdout) {
133
+ const subprocess = execa("claude", args, {
134
+ input: prompt,
135
+ timeout: 0,
136
+ ...addDir && { cwd: addDir },
137
+ env: { ...claudeEnv, ...extraEnv },
138
+ // CLAUDECODE=0 + any extra env vars
139
+ stdio: ["pipe", "inherit", "pipe"]
140
+ // stdin: pipe (for prompt), stdout: inherit (passthrough), stderr: pipe (capture errors)
141
+ });
142
+ attachAbortSignal(subprocess);
143
+ try {
144
+ await subprocess;
145
+ } catch (err) {
146
+ if (signal == null ? void 0 : signal.aborted) return;
147
+ throw err;
148
+ }
149
+ return;
150
+ }
121
151
  if (headless) {
122
152
  const isDebugMode = logger.isDebugEnabled();
123
153
  const execaOptions = {
@@ -127,10 +157,13 @@ async function launchClaude(prompt, options = {}) {
127
157
  ...addDir && { cwd: addDir },
128
158
  // Run Claude in the worktree directory
129
159
  verbose: isDebugMode,
160
+ env: { ...claudeEnv, ...extraEnv },
161
+ // CLAUDECODE=0 + any extra env vars
130
162
  ...isDebugMode && { stdio: ["pipe", "pipe", "pipe"] }
131
163
  // Enable streaming in debug mode
132
164
  };
133
165
  const subprocess = execa("claude", args, execaOptions);
166
+ attachAbortSignal(subprocess);
134
167
  const isJsonStreamFormat = args.includes("--output-format") && args.includes("stream-json");
135
168
  let outputBuffer = "";
136
169
  let isStreaming = false;
@@ -155,7 +188,13 @@ async function launchClaude(prompt, options = {}) {
155
188
  }
156
189
  });
157
190
  }
158
- const result = await subprocess;
191
+ let result;
192
+ try {
193
+ result = await subprocess;
194
+ } catch (subprocessError) {
195
+ if (signal == null ? void 0 : signal.aborted) return;
196
+ throw subprocessError;
197
+ }
159
198
  if (isStreaming) {
160
199
  const rawOutput = outputBuffer.trim();
161
200
  if (!isDebugMode && !jsonMode) {
@@ -177,16 +216,26 @@ async function launchClaude(prompt, options = {}) {
177
216
  }
178
217
  } else {
179
218
  try {
180
- await execa("claude", [...args, "--", prompt], {
219
+ const interactiveSubprocess = execa("claude", [...args, "--", prompt], {
181
220
  ...addDir && { cwd: addDir },
182
221
  stdio: ["inherit", "inherit", "pipe"],
183
222
  // Capture stderr to detect session conflicts
184
223
  timeout: 0,
185
224
  // Disable timeout
186
- verbose: logger.isDebugEnabled()
225
+ verbose: logger.isDebugEnabled(),
226
+ env: { ...claudeEnv, ...extraEnv }
227
+ // CLAUDECODE=0 + any extra env vars
187
228
  });
229
+ attachAbortSignal(interactiveSubprocess);
230
+ try {
231
+ await interactiveSubprocess;
232
+ } catch (err) {
233
+ if (signal == null ? void 0 : signal.aborted) return;
234
+ throw err;
235
+ }
188
236
  return;
189
237
  } catch (interactiveError) {
238
+ if (signal == null ? void 0 : signal.aborted) return;
190
239
  const interactiveExecaError = interactiveError;
191
240
  const interactiveErrorMessage = interactiveExecaError.stderr ?? interactiveExecaError.message ?? "";
192
241
  const sessionMatch = interactiveErrorMessage.match(/Session ID ([0-9a-f-]+) is already in use/i);
@@ -199,18 +248,27 @@ async function launchClaude(prompt, options = {}) {
199
248
  return true;
200
249
  });
201
250
  resumeArgs.push("--resume", conflictSessionId);
202
- await execa("claude", resumeArgs, {
251
+ const resumeSubprocess = execa("claude", resumeArgs, {
203
252
  ...addDir && { cwd: addDir },
204
253
  stdio: "inherit",
205
254
  timeout: 0,
206
- verbose: logger.isDebugEnabled()
255
+ verbose: logger.isDebugEnabled(),
256
+ env: claudeEnv
207
257
  });
258
+ attachAbortSignal(resumeSubprocess);
259
+ try {
260
+ await resumeSubprocess;
261
+ } catch (err) {
262
+ if (signal == null ? void 0 : signal.aborted) return;
263
+ throw err;
264
+ }
208
265
  return;
209
266
  }
210
267
  throw interactiveError;
211
268
  }
212
269
  }
213
270
  } catch (error) {
271
+ if (signal == null ? void 0 : signal.aborted) return;
214
272
  const execaError = error;
215
273
  const errorMessage = execaError.stderr ?? execaError.message ?? "Unknown Claude CLI error";
216
274
  const sessionInUseMatch = errorMessage.match(/Session ID ([0-9a-f-]+) is already in use/i);
@@ -231,6 +289,7 @@ async function launchClaude(prompt, options = {}) {
231
289
  timeout: 0,
232
290
  ...addDir && { cwd: addDir },
233
291
  verbose: isDebugMode,
292
+ env: claudeEnv,
234
293
  ...isDebugMode && { stdio: ["pipe", "pipe", "pipe"] }
235
294
  };
236
295
  const subprocess = execa("claude", resumeArgs, execaOptions);
@@ -283,7 +342,8 @@ async function launchClaude(prompt, options = {}) {
283
342
  ...addDir && { cwd: addDir },
284
343
  stdio: "inherit",
285
344
  timeout: 0,
286
- verbose: logger.isDebugEnabled()
345
+ verbose: logger.isDebugEnabled(),
346
+ env: claudeEnv
287
347
  });
288
348
  return;
289
349
  }
@@ -361,8 +421,10 @@ Generate a git branch name for the following issue:
361
421
  const result = await launchClaude(prompt, {
362
422
  model,
363
423
  headless: true,
364
- noSessionPersistence: true
424
+ noSessionPersistence: true,
365
425
  // Utility operation - don't persist session
426
+ env: { CLAUDE_CODE_SIMPLE: "1" }
427
+ // Minimal mode - no MCP, hooks, or CLAUDE.md loading
366
428
  });
367
429
  const branchName = result.trim().toLowerCase();
368
430
  logger.debug("Claude returned branch name", { branchName, issueNumber });
@@ -390,4 +452,4 @@ export {
390
452
  launchClaudeInNewTerminalWindow,
391
453
  generateBranchName
392
454
  };
393
- //# sourceMappingURL=chunk-FO5GGFOV.js.map
455
+ //# sourceMappingURL=chunk-UR5DGNUO.js.map