@iloom/cli 0.9.2 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +159 -40
  3. package/dist/{BranchNamingService-K6XNWQ6C.js → BranchNamingService-ECJHBB67.js} +2 -2
  4. package/dist/ClaudeContextManager-QXX6ZFST.js +14 -0
  5. package/dist/ClaudeService-NJNK2SUH.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-L64HHS3T.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-DULSVRRE.js} +2 -2
  12. package/dist/README.md +159 -40
  13. package/dist/{SettingsManager-AW3JTJHD.js → SettingsManager-BQDQA3FK.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-5GO3XW26.js} +9 -9
  23. package/dist/{chunk-NUACL52E.js → chunk-3D7WQM7I.js} +2 -2
  24. package/dist/chunk-4232AHNQ.js +35 -0
  25. package/dist/chunk-4232AHNQ.js.map +1 -0
  26. package/dist/{chunk-QN47QVBX.js → chunk-4WJNIR5O.js} +1 -1
  27. package/dist/chunk-4WJNIR5O.js.map +1 -0
  28. package/dist/{chunk-A7NJF73J.js → chunk-5MWV33NN.js} +4 -4
  29. package/dist/{chunk-3I4ONZRT.js → chunk-6EU6TCF6.js} +10 -10
  30. package/dist/chunk-6EU6TCF6.js.map +1 -0
  31. package/dist/{chunk-CWRI4JC3.js → chunk-FB47TIJG.js} +29 -11
  32. package/dist/chunk-FB47TIJG.js.map +1 -0
  33. package/dist/chunk-HEXKPKCK.js +1396 -0
  34. package/dist/chunk-HEXKPKCK.js.map +1 -0
  35. package/dist/{chunk-KAYXR544.js → chunk-J5S7DFYC.js} +2 -2
  36. package/dist/{chunk-ULSWCPQG.js → chunk-JO2LZ6EQ.js} +476 -5
  37. package/dist/chunk-JO2LZ6EQ.js.map +1 -0
  38. package/dist/{chunk-KBEIQP4G.js → chunk-KB64WNBZ.js} +43 -3
  39. package/dist/chunk-KB64WNBZ.js.map +1 -0
  40. package/dist/{chunk-OFDN5NKS.js → chunk-KXDRI47U.js} +69 -12
  41. package/dist/chunk-KXDRI47U.js.map +1 -0
  42. package/dist/{chunk-R4YWBGY6.js → chunk-LXLMMXXY.js} +54 -14
  43. package/dist/chunk-LXLMMXXY.js.map +1 -0
  44. package/dist/{chunk-AR5QKYNE.js → chunk-MNHZB4Z2.js} +4 -4
  45. package/dist/{chunk-TL72BGP6.js → chunk-MORRVYPT.js} +2 -2
  46. package/dist/{chunk-KJTVU3HZ.js → chunk-NRSWLOAZ.js} +8 -8
  47. package/dist/chunk-NRSWLOAZ.js.map +1 -0
  48. package/dist/{chunk-FO5GGFOV.js → chunk-ONQYPICO.js} +13 -5
  49. package/dist/chunk-ONQYPICO.js.map +1 -0
  50. package/dist/{chunk-7ZEHSSUP.js → chunk-P4O6EH46.js} +4 -4
  51. package/dist/chunk-QZWEJVWV.js +207 -0
  52. package/dist/chunk-QZWEJVWV.js.map +1 -0
  53. package/dist/chunk-RSYT7MVI.js +202 -0
  54. package/dist/chunk-RSYT7MVI.js.map +1 -0
  55. package/dist/{chunk-Z2TWEXR7.js → chunk-RYWFS37M.js} +6 -6
  56. package/dist/chunk-RYWFS37M.js.map +1 -0
  57. package/dist/{chunk-B7U6OKUR.js → chunk-SF2P22EE.js} +11 -3
  58. package/dist/chunk-SF2P22EE.js.map +1 -0
  59. package/dist/{chunk-6IIL5M2L.js → chunk-SN3SQCFK.js} +10 -8
  60. package/dist/{chunk-6IIL5M2L.js.map → chunk-SN3SQCFK.js.map} +1 -1
  61. package/dist/{chunk-SOSQILHO.js → chunk-UD3WJDIV.js} +92 -82
  62. package/dist/chunk-UD3WJDIV.js.map +1 -0
  63. package/dist/{chunk-KXGQYLFZ.js → chunk-UKBAJ2QQ.js} +61 -7
  64. package/dist/chunk-UKBAJ2QQ.js.map +1 -0
  65. package/dist/{chunk-W6DP5RVR.js → chunk-UVD4CZKS.js} +3 -3
  66. package/dist/chunk-UWGVCXRF.js +207 -0
  67. package/dist/chunk-UWGVCXRF.js.map +1 -0
  68. package/dist/{chunk-NWMORW3U.js → chunk-VECNX6VX.js} +2 -2
  69. package/dist/{chunk-4CO6KG5S.js → chunk-VG45TUYK.js} +53 -7
  70. package/dist/{chunk-4CO6KG5S.js.map → chunk-VG45TUYK.js.map} +1 -1
  71. package/dist/{chunk-TC7APDKU.js → chunk-VGGST52X.js} +2 -2
  72. package/dist/{chunk-4LKGCFGG.js → chunk-WWKOVDWC.js} +2 -2
  73. package/dist/{chunk-YKFCCV6S.js → chunk-WY4QBK43.js} +7 -7
  74. package/dist/chunk-WY4QBK43.js.map +1 -0
  75. package/dist/chunk-Y4YZTHZE.js +73 -0
  76. package/dist/chunk-Y4YZTHZE.js.map +1 -0
  77. package/dist/{chunk-VOGGLPG5.js → chunk-YQ57ORTV.js} +14 -1
  78. package/dist/chunk-YQ57ORTV.js.map +1 -0
  79. package/dist/{chunk-RI2YL6TK.js → chunk-YYAKPQBT.js} +65 -18
  80. package/dist/chunk-YYAKPQBT.js.map +1 -0
  81. package/dist/{chunk-IZIYLYPK.js → chunk-ZEWU5PZK.js} +2 -2
  82. package/dist/{chunk-VPTAX5TR.js → chunk-ZHPNZC75.js} +12 -12
  83. package/dist/chunk-ZHPNZC75.js.map +1 -0
  84. package/dist/{chunk-DGG2VY7B.js → chunk-ZW2LKWWE.js} +9 -9
  85. package/dist/chunk-ZW2LKWWE.js.map +1 -0
  86. package/dist/{claude-TP2QO3BU.js → claude-P3NQR6IJ.js} +2 -2
  87. package/dist/{cleanup-PJRIFFU4.js → cleanup-6UCPVMFG.js} +81 -32
  88. package/dist/cleanup-6UCPVMFG.js.map +1 -0
  89. package/dist/cli.js +638 -349
  90. package/dist/cli.js.map +1 -1
  91. package/dist/{commit-IVP3M4HG.js → commit-L3EPY5QG.js} +21 -20
  92. package/dist/commit-L3EPY5QG.js.map +1 -0
  93. package/dist/{compile-R2J65HBQ.js → compile-ZS4HYRX5.js} +9 -9
  94. package/dist/{contribute-VDZXHK5Y.js → contribute-ORDDQGSL.js} +14 -6
  95. package/dist/contribute-ORDDQGSL.js.map +1 -0
  96. package/dist/{dev-server-7F622OEO.js → dev-server-FYZ2AQIH.js} +29 -15
  97. package/dist/dev-server-FYZ2AQIH.js.map +1 -0
  98. package/dist/{feedback-E7VET7CL.js → feedback-TMBXSCM5.js} +15 -15
  99. package/dist/{git-2QDQ2X2S.js → git-ET64COO3.js} +4 -4
  100. package/dist/hooks/iloom-hook.js +15 -0
  101. package/dist/ignite-CGOV3TD4.js +1393 -0
  102. package/dist/ignite-CGOV3TD4.js.map +1 -0
  103. package/dist/index.d.ts +382 -53
  104. package/dist/index.js +1167 -36
  105. package/dist/index.js.map +1 -1
  106. package/dist/{init-676DHF6R.js → init-GFQ5W7GK.js} +57 -21
  107. package/dist/init-GFQ5W7GK.js.map +1 -0
  108. package/dist/{issues-PJSOLOBJ.js → issues-T4ZZSPEG.js} +61 -20
  109. package/dist/issues-T4ZZSPEG.js.map +1 -0
  110. package/dist/{lint-CJM7BAIM.js → lint-6TQXDZ3T.js} +9 -9
  111. package/dist/mcp/issue-management-server.js +2471 -256
  112. package/dist/mcp/issue-management-server.js.map +1 -1
  113. package/dist/mcp/recap-server.js +144 -21
  114. package/dist/mcp/recap-server.js.map +1 -1
  115. package/dist/{neon-helpers-VVFFTLXE.js → neon-helpers-CQN2PB4S.js} +3 -3
  116. package/dist/neon-helpers-CQN2PB4S.js.map +1 -0
  117. package/dist/{open-544H7JF5.js → open-5QZGXQRF.js} +15 -15
  118. package/dist/open-5QZGXQRF.js.map +1 -0
  119. package/dist/{plan-Q7ELXDLC.js → plan-U7ZQWLFY.js} +41 -25
  120. package/dist/plan-U7ZQWLFY.js.map +1 -0
  121. package/dist/{projects-LH362JZQ.js → projects-2UOXFLNZ.js} +4 -4
  122. package/dist/prompts/CLAUDE.md +62 -0
  123. package/dist/prompts/init-prompt.txt +347 -26
  124. package/dist/prompts/issue-prompt.txt +427 -54
  125. package/dist/prompts/plan-prompt.txt +97 -16
  126. package/dist/prompts/pr-prompt.txt +44 -1
  127. package/dist/prompts/regular-prompt.txt +42 -1
  128. package/dist/prompts/session-summary-prompt.txt +14 -0
  129. package/dist/prompts/swarm-orchestrator-prompt.txt +437 -0
  130. package/dist/{rebase-YND35CIE.js → rebase-DWIB77KV.js} +10 -10
  131. package/dist/{recap-3W7COH7D.js → recap-MX63HAKV.js} +47 -19
  132. package/dist/recap-MX63HAKV.js.map +1 -0
  133. package/dist/{run-QUXJKDQQ.js → run-O3TFNQFC.js} +15 -15
  134. package/dist/run-O3TFNQFC.js.map +1 -0
  135. package/dist/schema/package-iloom.schema.json +58 -0
  136. package/dist/schema/settings.schema.json +115 -15
  137. package/dist/{shell-QGECBLST.js → shell-G6VC2CYR.js} +14 -7
  138. package/dist/shell-G6VC2CYR.js.map +1 -0
  139. package/dist/{summary-G2T4452H.js → summary-FWHAX55O.js} +27 -25
  140. package/dist/summary-FWHAX55O.js.map +1 -0
  141. package/dist/{test-EA5NQFDC.js → test-F7JNJZYP.js} +9 -9
  142. package/dist/{test-git-M7LSLEFL.js → test-git-BTAOIUE2.js} +4 -4
  143. package/dist/test-jira-CHYNV33F.js +96 -0
  144. package/dist/test-jira-CHYNV33F.js.map +1 -0
  145. package/dist/{test-prefix-64NAAUON.js → test-prefix-Q6TFSU6F.js} +4 -4
  146. package/dist/{test-webserver-OK6Z5FJM.js → test-webserver-EONCG7E7.js} +6 -6
  147. package/dist/{vscode-AR5NNXXI.js → vscode-VA5X4P25.js} +7 -7
  148. package/package.json +5 -1
  149. package/dist/ClaudeContextManager-HR5JQKAI.js +0 -14
  150. package/dist/ClaudeService-TK7FMC2X.js +0 -13
  151. package/dist/chunk-3I4ONZRT.js.map +0 -1
  152. package/dist/chunk-B7U6OKUR.js.map +0 -1
  153. package/dist/chunk-CWRI4JC3.js.map +0 -1
  154. package/dist/chunk-DGG2VY7B.js.map +0 -1
  155. package/dist/chunk-FJDRTVJX.js +0 -520
  156. package/dist/chunk-FJDRTVJX.js.map +0 -1
  157. package/dist/chunk-FO5GGFOV.js.map +0 -1
  158. package/dist/chunk-KBEIQP4G.js.map +0 -1
  159. package/dist/chunk-KJTVU3HZ.js.map +0 -1
  160. package/dist/chunk-KXGQYLFZ.js.map +0 -1
  161. package/dist/chunk-OFDN5NKS.js.map +0 -1
  162. package/dist/chunk-QN47QVBX.js.map +0 -1
  163. package/dist/chunk-R4YWBGY6.js.map +0 -1
  164. package/dist/chunk-RI2YL6TK.js.map +0 -1
  165. package/dist/chunk-SOSQILHO.js.map +0 -1
  166. package/dist/chunk-ULSWCPQG.js.map +0 -1
  167. package/dist/chunk-VOGGLPG5.js.map +0 -1
  168. package/dist/chunk-VPTAX5TR.js.map +0 -1
  169. package/dist/chunk-WHI5KEOX.js +0 -121
  170. package/dist/chunk-WHI5KEOX.js.map +0 -1
  171. package/dist/chunk-YKFCCV6S.js.map +0 -1
  172. package/dist/chunk-Z2TWEXR7.js.map +0 -1
  173. package/dist/cleanup-PJRIFFU4.js.map +0 -1
  174. package/dist/commit-IVP3M4HG.js.map +0 -1
  175. package/dist/contribute-VDZXHK5Y.js.map +0 -1
  176. package/dist/dev-server-7F622OEO.js.map +0 -1
  177. package/dist/ignite-IW35CDBD.js +0 -784
  178. package/dist/ignite-IW35CDBD.js.map +0 -1
  179. package/dist/init-676DHF6R.js.map +0 -1
  180. package/dist/issues-PJSOLOBJ.js.map +0 -1
  181. package/dist/open-544H7JF5.js.map +0 -1
  182. package/dist/plan-Q7ELXDLC.js.map +0 -1
  183. package/dist/recap-3W7COH7D.js.map +0 -1
  184. package/dist/run-QUXJKDQQ.js.map +0 -1
  185. package/dist/shell-QGECBLST.js.map +0 -1
  186. package/dist/summary-G2T4452H.js.map +0 -1
  187. /package/dist/{BranchNamingService-K6XNWQ6C.js.map → BranchNamingService-ECJHBB67.js.map} +0 -0
  188. /package/dist/{ClaudeContextManager-HR5JQKAI.js.map → ClaudeContextManager-QXX6ZFST.js.map} +0 -0
  189. /package/dist/{ClaudeService-TK7FMC2X.js.map → ClaudeService-NJNK2SUH.js.map} +0 -0
  190. /package/dist/{GitHubService-TGWJN4V4.js.map → GitHubService-MEHKHUQP.js.map} +0 -0
  191. /package/dist/{MetadataManager-W3C54UYT.js.map → IssueTrackerFactory-NG53YX5S.js.map} +0 -0
  192. /package/dist/{LoomLauncher-73NXL2CL.js.map → LoomLauncher-L64HHS3T.js.map} +0 -0
  193. /package/dist/{ProjectCapabilityDetector-N5L7T4IY.js.map → MetadataManager-5QZSTKNN.js.map} +0 -0
  194. /package/dist/{PromptTemplateManager-36YLQRHP.js.map → ProjectCapabilityDetector-5KSYUTBJ.js.map} +0 -0
  195. /package/dist/{SettingsManager-AW3JTJHD.js.map → PromptTemplateManager-DULSVRRE.js.map} +0 -0
  196. /package/dist/{claude-TP2QO3BU.js.map → SettingsManager-BQDQA3FK.js.map} +0 -0
  197. /package/dist/{build-THZI572G.js.map → build-5GO3XW26.js.map} +0 -0
  198. /package/dist/{chunk-NUACL52E.js.map → chunk-3D7WQM7I.js.map} +0 -0
  199. /package/dist/{chunk-A7NJF73J.js.map → chunk-5MWV33NN.js.map} +0 -0
  200. /package/dist/{chunk-KAYXR544.js.map → chunk-J5S7DFYC.js.map} +0 -0
  201. /package/dist/{chunk-AR5QKYNE.js.map → chunk-MNHZB4Z2.js.map} +0 -0
  202. /package/dist/{chunk-TL72BGP6.js.map → chunk-MORRVYPT.js.map} +0 -0
  203. /package/dist/{chunk-7ZEHSSUP.js.map → chunk-P4O6EH46.js.map} +0 -0
  204. /package/dist/{chunk-W6DP5RVR.js.map → chunk-UVD4CZKS.js.map} +0 -0
  205. /package/dist/{chunk-NWMORW3U.js.map → chunk-VECNX6VX.js.map} +0 -0
  206. /package/dist/{chunk-TC7APDKU.js.map → chunk-VGGST52X.js.map} +0 -0
  207. /package/dist/{chunk-4LKGCFGG.js.map → chunk-WWKOVDWC.js.map} +0 -0
  208. /package/dist/{chunk-IZIYLYPK.js.map → chunk-ZEWU5PZK.js.map} +0 -0
  209. /package/dist/{git-2QDQ2X2S.js.map → claude-P3NQR6IJ.js.map} +0 -0
  210. /package/dist/{compile-R2J65HBQ.js.map → compile-ZS4HYRX5.js.map} +0 -0
  211. /package/dist/{feedback-E7VET7CL.js.map → feedback-TMBXSCM5.js.map} +0 -0
  212. /package/dist/{neon-helpers-VVFFTLXE.js.map → git-ET64COO3.js.map} +0 -0
  213. /package/dist/{lint-CJM7BAIM.js.map → lint-6TQXDZ3T.js.map} +0 -0
  214. /package/dist/{projects-LH362JZQ.js.map → projects-2UOXFLNZ.js.map} +0 -0
  215. /package/dist/{rebase-YND35CIE.js.map → rebase-DWIB77KV.js.map} +0 -0
  216. /package/dist/{test-EA5NQFDC.js.map → test-F7JNJZYP.js.map} +0 -0
  217. /package/dist/{test-git-M7LSLEFL.js.map → test-git-BTAOIUE2.js.map} +0 -0
  218. /package/dist/{test-prefix-64NAAUON.js.map → test-prefix-Q6TFSU6F.js.map} +0 -0
  219. /package/dist/{test-webserver-OK6Z5FJM.js.map → test-webserver-EONCG7E7.js.map} +0 -0
  220. /package/dist/{vscode-AR5NNXXI.js.map → vscode-VA5X4P25.js.map} +0 -0
@@ -0,0 +1,1396 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ getLogger
4
+ } from "./chunk-6MLEBAYZ.js";
5
+ import {
6
+ promptConfirmation
7
+ } from "./chunk-7JDMYTFZ.js";
8
+ import {
9
+ logger
10
+ } from "./chunk-VT4PDUYT.js";
11
+
12
+ // src/utils/linear.ts
13
+ import { LinearClient, IssueRelationType } from "@linear/sdk";
14
+
15
+ // src/types/linear.ts
16
+ var LinearServiceError = class _LinearServiceError extends Error {
17
+ constructor(code, message, details) {
18
+ super(message);
19
+ this.code = code;
20
+ this.details = details;
21
+ this.name = "LinearServiceError";
22
+ if (Error.captureStackTrace) {
23
+ Error.captureStackTrace(this, _LinearServiceError);
24
+ }
25
+ }
26
+ };
27
+
28
+ // src/utils/linear.ts
29
+ function slugifyTitle(title, maxLength = 50) {
30
+ const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
31
+ if (slug.length <= maxLength) {
32
+ return slug;
33
+ }
34
+ const parts = slug.split("-");
35
+ let result = "";
36
+ for (const part of parts) {
37
+ const candidate = result ? `${result}-${part}` : part;
38
+ if (candidate.length > maxLength) {
39
+ break;
40
+ }
41
+ result = candidate;
42
+ }
43
+ return result || slug.slice(0, maxLength);
44
+ }
45
+ function buildLinearIssueUrl(identifier, title) {
46
+ const base = `https://linear.app/issue/${identifier}`;
47
+ if (title) {
48
+ const slug = slugifyTitle(title);
49
+ return slug ? `${base}/${slug}` : base;
50
+ }
51
+ return base;
52
+ }
53
+ function getLinearApiToken() {
54
+ const token = process.env.LINEAR_API_TOKEN;
55
+ if (!token) {
56
+ throw new LinearServiceError(
57
+ "UNAUTHORIZED",
58
+ "LINEAR_API_TOKEN not set. Configure in settings.local.json or set environment variable."
59
+ );
60
+ }
61
+ return token;
62
+ }
63
+ function createLinearClient(apiToken) {
64
+ const token = apiToken ?? getLinearApiToken();
65
+ return new LinearClient({ apiKey: token });
66
+ }
67
+ function handleLinearError(error, context) {
68
+ logger.debug(`${context}: Handling error`, { error });
69
+ const errorMessage = error instanceof Error ? error.message : String(error);
70
+ if (errorMessage.includes("not found") || errorMessage.includes("Not found")) {
71
+ throw new LinearServiceError("NOT_FOUND", "Linear issue or resource not found", { error });
72
+ }
73
+ if (errorMessage.includes("unauthorized") || errorMessage.includes("Unauthorized") || errorMessage.includes("Invalid API key")) {
74
+ throw new LinearServiceError(
75
+ "UNAUTHORIZED",
76
+ "Linear authentication failed. Check LINEAR_API_TOKEN.",
77
+ { error }
78
+ );
79
+ }
80
+ if (errorMessage.includes("rate limit")) {
81
+ throw new LinearServiceError("RATE_LIMITED", "Linear API rate limit exceeded", { error });
82
+ }
83
+ throw new LinearServiceError("CLI_ERROR", `Linear SDK error: ${errorMessage}`, { error });
84
+ }
85
+ async function fetchLinearIssue(identifier) {
86
+ try {
87
+ logger.debug(`Fetching Linear issue: ${identifier}`);
88
+ const client = createLinearClient();
89
+ const issue = await client.issue(identifier);
90
+ if (!issue) {
91
+ throw new LinearServiceError("NOT_FOUND", `Linear issue ${identifier} not found`);
92
+ }
93
+ const result = {
94
+ id: issue.id,
95
+ identifier: issue.identifier,
96
+ title: issue.title,
97
+ url: issue.url,
98
+ createdAt: issue.createdAt.toISOString(),
99
+ updatedAt: issue.updatedAt.toISOString()
100
+ };
101
+ if (issue.description) {
102
+ result.description = issue.description;
103
+ }
104
+ if (issue.state) {
105
+ const state = await issue.state;
106
+ if (state == null ? void 0 : state.name) {
107
+ result.state = state.name;
108
+ }
109
+ }
110
+ return result;
111
+ } catch (error) {
112
+ if (error instanceof LinearServiceError) {
113
+ throw error;
114
+ }
115
+ handleLinearError(error, "fetchLinearIssue");
116
+ }
117
+ }
118
+ async function createLinearIssue(title, body, teamKey, _labels) {
119
+ try {
120
+ logger.debug(`Creating Linear issue in team ${teamKey}: ${title}`);
121
+ const client = createLinearClient();
122
+ const teams = await client.teams();
123
+ const team = teams.nodes.find((t) => t.key === teamKey);
124
+ if (!team) {
125
+ throw new LinearServiceError("NOT_FOUND", `Linear team ${teamKey} not found`);
126
+ }
127
+ const issueInput = {
128
+ teamId: team.id,
129
+ title
130
+ };
131
+ if (body) {
132
+ issueInput.description = body;
133
+ }
134
+ const payload = await client.createIssue(issueInput);
135
+ const issue = await payload.issue;
136
+ if (!issue) {
137
+ throw new LinearServiceError("CLI_ERROR", "Failed to create Linear issue");
138
+ }
139
+ const url = issue.url ?? buildLinearIssueUrl(issue.identifier, title);
140
+ return {
141
+ identifier: issue.identifier,
142
+ url
143
+ };
144
+ } catch (error) {
145
+ if (error instanceof LinearServiceError) {
146
+ throw error;
147
+ }
148
+ handleLinearError(error, "createLinearIssue");
149
+ }
150
+ }
151
+ async function createLinearChildIssue(title, body, teamKey, parentId, _labels) {
152
+ try {
153
+ logger.debug(`Creating Linear child issue in team ${teamKey}: ${title}`);
154
+ const client = createLinearClient();
155
+ const teams = await client.teams();
156
+ const team = teams.nodes.find((t) => t.key === teamKey);
157
+ if (!team) {
158
+ throw new LinearServiceError("NOT_FOUND", `Linear team ${teamKey} not found`);
159
+ }
160
+ const issueInput = {
161
+ teamId: team.id,
162
+ title,
163
+ parentId
164
+ // UUID of parent issue
165
+ };
166
+ if (body) {
167
+ issueInput.description = body;
168
+ }
169
+ const payload = await client.createIssue(issueInput);
170
+ const issue = await payload.issue;
171
+ if (!issue) {
172
+ throw new LinearServiceError("CLI_ERROR", "Failed to create Linear child issue");
173
+ }
174
+ const url = issue.url ?? buildLinearIssueUrl(issue.identifier, title);
175
+ return {
176
+ identifier: issue.identifier,
177
+ url
178
+ };
179
+ } catch (error) {
180
+ if (error instanceof LinearServiceError) {
181
+ throw error;
182
+ }
183
+ handleLinearError(error, "createLinearChildIssue");
184
+ }
185
+ }
186
+ async function createLinearComment(identifier, body) {
187
+ try {
188
+ logger.debug(`Creating comment on Linear issue ${identifier}`);
189
+ const client = createLinearClient();
190
+ const issue = await client.issue(identifier);
191
+ if (!issue) {
192
+ throw new LinearServiceError("NOT_FOUND", `Linear issue ${identifier} not found`);
193
+ }
194
+ const payload = await client.createComment({
195
+ issueId: issue.id,
196
+ body
197
+ });
198
+ const comment = await payload.comment;
199
+ if (!comment) {
200
+ throw new LinearServiceError("CLI_ERROR", "Failed to create Linear comment");
201
+ }
202
+ return {
203
+ id: comment.id,
204
+ body: comment.body,
205
+ createdAt: comment.createdAt.toISOString(),
206
+ updatedAt: comment.updatedAt.toISOString(),
207
+ url: comment.url
208
+ };
209
+ } catch (error) {
210
+ if (error instanceof LinearServiceError) {
211
+ throw error;
212
+ }
213
+ handleLinearError(error, "createLinearComment");
214
+ }
215
+ }
216
+ async function updateLinearIssueState(identifier, stateName) {
217
+ try {
218
+ logger.debug(`Updating Linear issue ${identifier} state to: ${stateName}`);
219
+ const client = createLinearClient();
220
+ const issue = await client.issue(identifier);
221
+ if (!issue) {
222
+ throw new LinearServiceError("NOT_FOUND", `Linear issue ${identifier} not found`);
223
+ }
224
+ const team = await issue.team;
225
+ if (!team) {
226
+ throw new LinearServiceError("CLI_ERROR", "Issue has no team");
227
+ }
228
+ const states = await team.states();
229
+ const state = states.nodes.find((s) => s.name === stateName);
230
+ if (!state) {
231
+ throw new LinearServiceError(
232
+ "NOT_FOUND",
233
+ `State "${stateName}" not found in team ${team.key}`
234
+ );
235
+ }
236
+ await client.updateIssue(issue.id, {
237
+ stateId: state.id
238
+ });
239
+ } catch (error) {
240
+ if (error instanceof LinearServiceError) {
241
+ throw error;
242
+ }
243
+ handleLinearError(error, "updateLinearIssueState");
244
+ }
245
+ }
246
+ async function editLinearIssue(identifier, updates) {
247
+ try {
248
+ logger.debug(`Editing Linear issue ${identifier}`, { updates });
249
+ const client = createLinearClient();
250
+ const issue = await client.issue(identifier);
251
+ if (!issue) {
252
+ throw new LinearServiceError("NOT_FOUND", `Linear issue ${identifier} not found`);
253
+ }
254
+ const updatePayload = {};
255
+ if (updates.title !== void 0) {
256
+ updatePayload.title = updates.title;
257
+ }
258
+ if (updates.description !== void 0) {
259
+ updatePayload.description = updates.description;
260
+ }
261
+ if (Object.keys(updatePayload).length > 0) {
262
+ await client.updateIssue(issue.id, updatePayload);
263
+ }
264
+ } catch (error) {
265
+ if (error instanceof LinearServiceError) {
266
+ throw error;
267
+ }
268
+ handleLinearError(error, "editLinearIssue");
269
+ }
270
+ }
271
+ async function getLinearComment(commentId) {
272
+ try {
273
+ logger.debug(`Fetching Linear comment: ${commentId}`);
274
+ const client = createLinearClient();
275
+ const comment = await client.comment({ id: commentId });
276
+ if (!comment) {
277
+ throw new LinearServiceError("NOT_FOUND", `Linear comment ${commentId} not found`);
278
+ }
279
+ return {
280
+ id: comment.id,
281
+ body: comment.body,
282
+ createdAt: comment.createdAt.toISOString(),
283
+ updatedAt: comment.updatedAt.toISOString(),
284
+ url: comment.url
285
+ };
286
+ } catch (error) {
287
+ if (error instanceof LinearServiceError) {
288
+ throw error;
289
+ }
290
+ handleLinearError(error, "getLinearComment");
291
+ }
292
+ }
293
+ async function updateLinearComment(commentId, body) {
294
+ try {
295
+ logger.debug(`Updating Linear comment: ${commentId}`);
296
+ const client = createLinearClient();
297
+ const payload = await client.updateComment(commentId, { body });
298
+ const comment = await payload.comment;
299
+ if (!comment) {
300
+ throw new LinearServiceError("CLI_ERROR", "Failed to update Linear comment");
301
+ }
302
+ return {
303
+ id: comment.id,
304
+ body: comment.body,
305
+ createdAt: comment.createdAt.toISOString(),
306
+ updatedAt: comment.updatedAt.toISOString(),
307
+ url: comment.url
308
+ };
309
+ } catch (error) {
310
+ if (error instanceof LinearServiceError) {
311
+ throw error;
312
+ }
313
+ handleLinearError(error, "updateLinearComment");
314
+ }
315
+ }
316
+ async function fetchLinearIssueComments(identifier) {
317
+ try {
318
+ logger.debug(`Fetching comments for Linear issue: ${identifier}`);
319
+ const client = createLinearClient();
320
+ const issue = await client.issue(identifier);
321
+ if (!issue) {
322
+ throw new LinearServiceError("NOT_FOUND", `Linear issue ${identifier} not found`);
323
+ }
324
+ const comments = await issue.comments({ first: 100 });
325
+ return comments.nodes.map((comment) => ({
326
+ id: comment.id,
327
+ body: comment.body,
328
+ createdAt: comment.createdAt.toISOString(),
329
+ updatedAt: comment.updatedAt.toISOString(),
330
+ url: comment.url
331
+ }));
332
+ } catch (error) {
333
+ if (error instanceof LinearServiceError) {
334
+ throw error;
335
+ }
336
+ handleLinearError(error, "fetchLinearIssueComments");
337
+ }
338
+ }
339
+ async function getLinearChildIssues(identifier, options) {
340
+ try {
341
+ logger.debug(`Fetching child issues for Linear issue: ${identifier}`);
342
+ const client = createLinearClient(options == null ? void 0 : options.apiToken);
343
+ const issue = await client.issue(identifier);
344
+ if (!issue) {
345
+ throw new LinearServiceError("NOT_FOUND", `Linear issue ${identifier} not found`);
346
+ }
347
+ const children = await issue.children({ first: 100 });
348
+ const results = await Promise.all(
349
+ children.nodes.map(async (child) => {
350
+ const stateObj = await child.state;
351
+ const state = (stateObj == null ? void 0 : stateObj.name) ?? "unknown";
352
+ return {
353
+ id: child.identifier,
354
+ title: child.title,
355
+ url: child.url,
356
+ state
357
+ };
358
+ })
359
+ );
360
+ return results;
361
+ } catch (error) {
362
+ if (error instanceof LinearServiceError) {
363
+ throw error;
364
+ }
365
+ handleLinearError(error, "getLinearChildIssues");
366
+ }
367
+ }
368
+ async function createLinearIssueRelation(blockingIssueId, blockedIssueId) {
369
+ try {
370
+ logger.debug(`Creating Linear issue relation: ${blockingIssueId} blocks ${blockedIssueId}`);
371
+ const client = createLinearClient();
372
+ const payload = await client.createIssueRelation({
373
+ issueId: blockingIssueId,
374
+ relatedIssueId: blockedIssueId,
375
+ type: IssueRelationType.Blocks
376
+ });
377
+ if (!payload.success) {
378
+ throw new LinearServiceError("CLI_ERROR", "Failed to create Linear issue relation");
379
+ }
380
+ } catch (error) {
381
+ if (error instanceof LinearServiceError) {
382
+ throw error;
383
+ }
384
+ handleLinearError(error, "createLinearIssueRelation");
385
+ }
386
+ }
387
+ async function getLinearIssueDependencies(identifier, direction) {
388
+ try {
389
+ logger.debug(`Fetching Linear issue dependencies: ${identifier} (direction: ${direction})`);
390
+ const client = createLinearClient();
391
+ const issue = await client.issue(identifier);
392
+ if (!issue) {
393
+ throw new LinearServiceError("NOT_FOUND", `Linear issue ${identifier} not found`);
394
+ }
395
+ const [relations, inverseRelations] = await Promise.all([
396
+ issue.relations(),
397
+ issue.inverseRelations()
398
+ ]);
399
+ const blocking = [];
400
+ const blockedBy = [];
401
+ const buildDependencyResult = async (relatedIssue) => {
402
+ if (!relatedIssue) return null;
403
+ const stateObj = await relatedIssue.state;
404
+ const state = (stateObj == null ? void 0 : stateObj.name) ?? "unknown";
405
+ return {
406
+ id: relatedIssue.identifier,
407
+ title: relatedIssue.title,
408
+ url: relatedIssue.url,
409
+ state
410
+ };
411
+ };
412
+ if (direction === "blocking" || direction === "both") {
413
+ const blockingRelations = relations.nodes.filter(
414
+ (r) => r.type === IssueRelationType.Blocks
415
+ );
416
+ const relatedIssuePromises = blockingRelations.map((r) => r.relatedIssue).filter((p) => p !== void 0);
417
+ const relatedIssues = await Promise.all(relatedIssuePromises);
418
+ const blockingResults = await Promise.all(
419
+ relatedIssues.map((issue2) => buildDependencyResult(issue2))
420
+ );
421
+ blocking.push(...blockingResults.filter((r) => r !== null));
422
+ }
423
+ if (direction === "blocked_by" || direction === "both") {
424
+ const blockedByRelations = inverseRelations.nodes.filter(
425
+ (r) => r.type === IssueRelationType.Blocks
426
+ );
427
+ const sourceIssuePromises = blockedByRelations.map((r) => r.issue).filter((p) => p !== void 0);
428
+ const sourceIssues = await Promise.all(sourceIssuePromises);
429
+ const blockedByResults = await Promise.all(
430
+ sourceIssues.map((issue2) => buildDependencyResult(issue2))
431
+ );
432
+ blockedBy.push(...blockedByResults.filter((r) => r !== null));
433
+ }
434
+ return { blocking, blockedBy };
435
+ } catch (error) {
436
+ if (error instanceof LinearServiceError) {
437
+ throw error;
438
+ }
439
+ handleLinearError(error, "getLinearIssueDependencies");
440
+ }
441
+ }
442
+ async function deleteLinearIssueRelation(relationId) {
443
+ try {
444
+ logger.debug(`Deleting Linear issue relation: ${relationId}`);
445
+ const client = createLinearClient();
446
+ const payload = await client.deleteIssueRelation(relationId);
447
+ if (!payload.success) {
448
+ throw new LinearServiceError("CLI_ERROR", "Failed to delete Linear issue relation");
449
+ }
450
+ } catch (error) {
451
+ if (error instanceof LinearServiceError) {
452
+ throw error;
453
+ }
454
+ handleLinearError(error, "deleteLinearIssueRelation");
455
+ }
456
+ }
457
+ async function fetchLinearIssueList(teamKey, options) {
458
+ try {
459
+ const limit = (options == null ? void 0 : options.limit) ?? 100;
460
+ logger.debug(`Fetching Linear issue list for team ${teamKey}`, { limit, mine: options == null ? void 0 : options.mine });
461
+ const client = createLinearClient(options == null ? void 0 : options.apiToken);
462
+ const teams = await client.teams();
463
+ const team = teams.nodes.find((t) => t.key === teamKey);
464
+ if (!team) {
465
+ throw new LinearServiceError("NOT_FOUND", `Linear team ${teamKey} not found`);
466
+ }
467
+ const filter = {
468
+ state: {
469
+ type: {
470
+ nin: ["completed", "canceled"]
471
+ }
472
+ }
473
+ };
474
+ if (options == null ? void 0 : options.mine) {
475
+ filter.assignee = { isMe: { eq: true } };
476
+ }
477
+ const issues = await team.issues({
478
+ first: limit,
479
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- PaginationOrderBy is a const enum incompatible with isolatedModules
480
+ orderBy: "updatedAt",
481
+ filter
482
+ });
483
+ const results = await Promise.all(
484
+ issues.nodes.map(async (issue) => {
485
+ const stateObj = await issue.state;
486
+ const state = (stateObj == null ? void 0 : stateObj.name) ?? "unknown";
487
+ return {
488
+ id: issue.identifier,
489
+ title: issue.title,
490
+ updatedAt: issue.updatedAt.toISOString(),
491
+ url: issue.url,
492
+ state
493
+ };
494
+ })
495
+ );
496
+ return results;
497
+ } catch (error) {
498
+ if (error instanceof LinearServiceError) {
499
+ throw error;
500
+ }
501
+ handleLinearError(error, "fetchLinearIssueList");
502
+ }
503
+ }
504
+ async function findLinearIssueRelation(blockingIdentifier, blockedIdentifier) {
505
+ try {
506
+ logger.debug(`Finding Linear issue relation: ${blockingIdentifier} blocks ${blockedIdentifier}`);
507
+ const client = createLinearClient();
508
+ const blockingIssue = await client.issue(blockingIdentifier);
509
+ if (!blockingIssue) {
510
+ throw new LinearServiceError("NOT_FOUND", `Linear issue ${blockingIdentifier} not found`);
511
+ }
512
+ const blockedIssue = await client.issue(blockedIdentifier);
513
+ if (!blockedIssue) {
514
+ throw new LinearServiceError("NOT_FOUND", `Linear issue ${blockedIdentifier} not found`);
515
+ }
516
+ const relations = await blockingIssue.relations();
517
+ const blockingRelations = relations.nodes.filter(
518
+ (r) => r.type === IssueRelationType.Blocks
519
+ );
520
+ const relationsWithIssues = await Promise.all(
521
+ blockingRelations.map(async (relation) => ({
522
+ relation,
523
+ relatedIssue: await relation.relatedIssue
524
+ }))
525
+ );
526
+ const matchingRelation = relationsWithIssues.find(
527
+ ({ relatedIssue }) => (relatedIssue == null ? void 0 : relatedIssue.id) === blockedIssue.id
528
+ );
529
+ return (matchingRelation == null ? void 0 : matchingRelation.relation.id) ?? null;
530
+ } catch (error) {
531
+ if (error instanceof LinearServiceError) {
532
+ throw error;
533
+ }
534
+ handleLinearError(error, "findLinearIssueRelation");
535
+ }
536
+ }
537
+
538
+ // src/lib/providers/jira/JiraApiClient.ts
539
+ import https from "https";
540
+
541
+ // src/lib/providers/jira/AdfMarkdownConverter.ts
542
+ import { Parser } from "extended-markdown-adf-parser";
543
+ var parser = new Parser();
544
+ function sanitizeCodeMarks(node) {
545
+ var _a;
546
+ if ((_a = node.marks) == null ? void 0 : _a.some((mark) => mark.type === "code")) {
547
+ node.marks = [{ type: "code" }];
548
+ }
549
+ if (node.content && Array.isArray(node.content)) {
550
+ node.content = node.content.map((child) => sanitizeCodeMarks(child));
551
+ }
552
+ return node;
553
+ }
554
+ var BLOCK_LEVEL_TYPES = /* @__PURE__ */ new Set([
555
+ "paragraph",
556
+ "bulletList",
557
+ "orderedList",
558
+ "codeBlock",
559
+ "heading",
560
+ "blockquote",
561
+ "rule",
562
+ "mediaGroup",
563
+ "nestedExpand",
564
+ "panel",
565
+ "table",
566
+ "taskList",
567
+ "decisionList",
568
+ "mediaSingle"
569
+ ]);
570
+ function wrapTableCellContent(node) {
571
+ if (node.content && Array.isArray(node.content)) {
572
+ node.content = node.content.map((child) => wrapTableCellContent(child));
573
+ }
574
+ if (node.type !== "tableCell" && node.type !== "tableHeader") {
575
+ return node;
576
+ }
577
+ if (!node.content || node.content.length === 0) {
578
+ return node;
579
+ }
580
+ const allInline = node.content.every((child) => !BLOCK_LEVEL_TYPES.has(child.type));
581
+ if (allInline) {
582
+ node.content = [{ type: "paragraph", content: node.content }];
583
+ } else {
584
+ const newContent = [];
585
+ let inlineRun = [];
586
+ for (const child of node.content) {
587
+ if (BLOCK_LEVEL_TYPES.has(child.type)) {
588
+ if (inlineRun.length > 0) {
589
+ newContent.push({ type: "paragraph", content: inlineRun });
590
+ inlineRun = [];
591
+ }
592
+ newContent.push(child);
593
+ } else {
594
+ inlineRun.push(child);
595
+ }
596
+ }
597
+ if (inlineRun.length > 0) {
598
+ newContent.push({ type: "paragraph", content: inlineRun });
599
+ }
600
+ node.content = newContent;
601
+ }
602
+ return node;
603
+ }
604
+ var taskIdCounter = 0;
605
+ function getCanonicalPlainText(text) {
606
+ const miniAdf = parser.markdownToAdf(text);
607
+ return getPlainText(miniAdf);
608
+ }
609
+ function extractCheckboxBlocks(markdown) {
610
+ var _a, _b;
611
+ const lines = markdown.split("\n");
612
+ const blocks = [];
613
+ let i = 0;
614
+ while (i < lines.length) {
615
+ const bulletLines = [];
616
+ let blockIndent = null;
617
+ while (i < lines.length) {
618
+ const line = lines[i] ?? "";
619
+ const checkboxMatch = line.match(/^(\s*)[-*+] \[([ xX])\] (.*)$/);
620
+ if (checkboxMatch) {
621
+ const indent = ((_a = checkboxMatch[1]) == null ? void 0 : _a.length) ?? 0;
622
+ if (blockIndent === null) {
623
+ blockIndent = indent;
624
+ } else if (indent !== blockIndent) {
625
+ break;
626
+ }
627
+ const state = checkboxMatch[2] === " " ? "TODO" : "DONE";
628
+ bulletLines.push({ isCheckbox: true, state, rawText: checkboxMatch[3] ?? "" });
629
+ i++;
630
+ } else if (line.match(/^\s*[-*+] /)) {
631
+ const indentMatch = line.match(/^(\s*)/);
632
+ const indent = ((_b = indentMatch == null ? void 0 : indentMatch[1]) == null ? void 0 : _b.length) ?? 0;
633
+ if (blockIndent === null) {
634
+ blockIndent = indent;
635
+ } else if (indent !== blockIndent) {
636
+ break;
637
+ }
638
+ bulletLines.push({ isCheckbox: false, state: null, rawText: "" });
639
+ i++;
640
+ } else if (bulletLines.length > 0 && line.match(/^\s/) && line.trim() !== "") {
641
+ const lastItem = bulletLines[bulletLines.length - 1];
642
+ if (lastItem) {
643
+ lastItem.rawText += "\n" + line.trim();
644
+ }
645
+ i++;
646
+ } else {
647
+ break;
648
+ }
649
+ }
650
+ if (bulletLines.length > 0) {
651
+ const allCheckboxes = bulletLines.every((l) => l.isCheckbox);
652
+ if (allCheckboxes) {
653
+ blocks.push({
654
+ states: bulletLines.map((l) => l.state),
655
+ texts: bulletLines.map((l) => getCanonicalPlainText(l.rawText))
656
+ });
657
+ }
658
+ } else {
659
+ i++;
660
+ }
661
+ }
662
+ return blocks;
663
+ }
664
+ function getPlainText(node) {
665
+ if (node.type === "text" && node.text !== void 0) return node.text;
666
+ if (!node.content) return "";
667
+ return node.content.map(getPlainText).join("");
668
+ }
669
+ function convertCheckboxesToTaskList(node, blocks) {
670
+ const cursor = { index: 0 };
671
+ return convertCheckboxesRecursive(node, blocks, cursor);
672
+ }
673
+ function convertCheckboxesRecursive(node, blocks, cursor) {
674
+ var _a;
675
+ if (node.type === "bulletList" && node.content && node.content.length > 0 && cursor.index < blocks.length) {
676
+ const block = blocks[cursor.index];
677
+ if (!block) return node;
678
+ if (node.content.length === block.states.length) {
679
+ const plaintexts = node.content.map((listItem) => getPlainText(listItem));
680
+ const matches = plaintexts.every((text, i) => text === block.texts[i]);
681
+ if (matches) {
682
+ const allSimple = node.content.every((item) => {
683
+ var _a2, _b;
684
+ return ((_a2 = item.content) == null ? void 0 : _a2.length) === 1 && ((_b = item.content[0]) == null ? void 0 : _b.type) === "paragraph";
685
+ });
686
+ if (!allSimple) {
687
+ cursor.index++;
688
+ return node;
689
+ }
690
+ cursor.index++;
691
+ node.type = "taskList";
692
+ node.attrs = { localId: `tasklist-${++taskIdCounter}` };
693
+ for (const [i, listItem] of node.content.entries()) {
694
+ listItem.type = "taskItem";
695
+ listItem.attrs = {
696
+ localId: `task-${++taskIdCounter}`,
697
+ state: block.states[i]
698
+ };
699
+ const firstChild = (_a = listItem.content) == null ? void 0 : _a[0];
700
+ if ((firstChild == null ? void 0 : firstChild.type) === "paragraph" && firstChild.content) {
701
+ listItem.content = firstChild.content;
702
+ }
703
+ }
704
+ return node;
705
+ }
706
+ }
707
+ }
708
+ if (node.content && Array.isArray(node.content)) {
709
+ node.content = node.content.map((child) => convertCheckboxesRecursive(child, blocks, cursor));
710
+ }
711
+ return node;
712
+ }
713
+ function convertDetailsToExpandSyntax(markdown) {
714
+ if (!markdown) return markdown;
715
+ let previousText = "";
716
+ let currentText = markdown;
717
+ while (previousText !== currentText) {
718
+ previousText = currentText;
719
+ currentText = currentText.replace(
720
+ /<details[^>]*>\s*<summary[^>]*>([\s\S]*?)<\/summary>([\s\S]*?)<\/details>/gi,
721
+ (_match, summary, content) => {
722
+ const cleanSummary = summary.trim().replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'");
723
+ let cleanContent = content.trim();
724
+ cleanContent = cleanContent.replace(/\n{3,}/g, "\n\n");
725
+ if (cleanContent) {
726
+ return `~~~expand title="${cleanSummary}"
727
+ ${cleanContent}
728
+ ~~~`;
729
+ } else {
730
+ return `~~~expand title="${cleanSummary}"
731
+ ~~~`;
732
+ }
733
+ }
734
+ );
735
+ }
736
+ return currentText;
737
+ }
738
+ function adfToMarkdown(adf) {
739
+ if (!adf) return "";
740
+ if (typeof adf === "string") return adf;
741
+ return parser.adfToMarkdown(adf);
742
+ }
743
+ function markdownToAdf(markdown) {
744
+ if (!markdown) {
745
+ return { type: "doc", version: 1, content: [] };
746
+ }
747
+ taskIdCounter = 0;
748
+ const checkboxBlocks = extractCheckboxBlocks(markdown);
749
+ const preprocessed = convertDetailsToExpandSyntax(markdown);
750
+ const adf = parser.markdownToAdf(preprocessed);
751
+ let result = sanitizeCodeMarks(adf);
752
+ result = wrapTableCellContent(result);
753
+ result = convertCheckboxesToTaskList(result, checkboxBlocks);
754
+ return result;
755
+ }
756
+
757
+ // src/lib/providers/jira/JiraApiClient.ts
758
+ var JiraApiClient = class {
759
+ constructor(config) {
760
+ this.baseUrl = `${config.host.replace(/\/$/, "")}/rest/api/3`;
761
+ const credentials = Buffer.from(`${config.username}:${config.apiToken}`).toString("base64");
762
+ this.authHeader = `Basic ${credentials}`;
763
+ }
764
+ /**
765
+ * Make an HTTP request to Jira API
766
+ */
767
+ async request(method, endpoint, body) {
768
+ const url = new URL(`${this.baseUrl}${endpoint}`);
769
+ getLogger().debug(`Jira API ${method} request`, { url: url.toString() });
770
+ if (body) {
771
+ getLogger().debug("Jira API request body", JSON.stringify(body, null, 2));
772
+ }
773
+ return new Promise((resolve, reject) => {
774
+ const options = {
775
+ hostname: url.hostname,
776
+ port: url.port || 443,
777
+ path: url.pathname + url.search,
778
+ method,
779
+ headers: {
780
+ "Authorization": this.authHeader,
781
+ "Accept": "application/json",
782
+ "Content-Type": "application/json"
783
+ }
784
+ };
785
+ const req = https.request({ ...options, timeout: 3e4 }, (res) => {
786
+ const chunks = [];
787
+ res.on("data", (chunk) => {
788
+ chunks.push(chunk);
789
+ });
790
+ res.on("end", () => {
791
+ var _a;
792
+ const data = Buffer.concat(chunks).toString("utf8");
793
+ if (!res.statusCode || res.statusCode < 200 || res.statusCode >= 300) {
794
+ let errorDetail = data;
795
+ try {
796
+ const parsed = JSON.parse(data);
797
+ const parts = [];
798
+ if ((_a = parsed.errorMessages) == null ? void 0 : _a.length) {
799
+ parts.push(`messages: ${parsed.errorMessages.join(", ")}`);
800
+ }
801
+ if (parsed.errors && Object.keys(parsed.errors).length) {
802
+ parts.push(`field errors: ${JSON.stringify(parsed.errors)}`);
803
+ }
804
+ if (parts.length) {
805
+ errorDetail = parts.join("; ");
806
+ }
807
+ } catch {
808
+ }
809
+ reject(new Error(`Jira API error (${res.statusCode}): ${errorDetail}`));
810
+ return;
811
+ }
812
+ if (res.statusCode === 204 || !data) {
813
+ resolve({});
814
+ return;
815
+ }
816
+ try {
817
+ resolve(JSON.parse(data));
818
+ } catch (error) {
819
+ reject(new Error(`Failed to parse Jira API response: ${error}`));
820
+ }
821
+ });
822
+ });
823
+ req.on("timeout", () => {
824
+ req.destroy();
825
+ reject(new Error("Jira API request timed out after 30 seconds"));
826
+ });
827
+ req.on("error", (error) => {
828
+ reject(new Error(`Jira API request failed: ${error.message}`));
829
+ });
830
+ if (body) {
831
+ req.write(JSON.stringify(body));
832
+ }
833
+ req.end();
834
+ });
835
+ }
836
+ /**
837
+ * Make a GET request to Jira API
838
+ */
839
+ async get(endpoint) {
840
+ return this.request("GET", endpoint);
841
+ }
842
+ /**
843
+ * Make a POST request to Jira API
844
+ */
845
+ async post(endpoint, body) {
846
+ return this.request("POST", endpoint, body);
847
+ }
848
+ /**
849
+ * Make a PUT request to Jira API
850
+ */
851
+ async put(endpoint, body) {
852
+ return this.request("PUT", endpoint, body);
853
+ }
854
+ /**
855
+ * Make a DELETE request to Jira API
856
+ */
857
+ async delete(endpoint) {
858
+ await this.request("DELETE", endpoint);
859
+ }
860
+ /**
861
+ * Fetch an issue by key (e.g., "PROJ-123")
862
+ */
863
+ async getIssue(issueKey) {
864
+ return this.get(`/issue/${issueKey}`);
865
+ }
866
+ /**
867
+ * Add a comment to an issue
868
+ * Accepts Markdown content which is converted to ADF for Jira
869
+ */
870
+ async addComment(issueKey, body) {
871
+ const adfBody = markdownToAdf(body);
872
+ getLogger().debug("Adding comment to Jira issue", { issueKey, bodyLength: body.length });
873
+ return this.post(`/issue/${issueKey}/comment`, {
874
+ body: adfBody
875
+ });
876
+ }
877
+ /**
878
+ * Get all comments for an issue
879
+ */
880
+ async getComments(issueKey) {
881
+ const response = await this.get(`/issue/${issueKey}/comment?maxResults=5000`);
882
+ if (response.total > response.comments.length) {
883
+ getLogger().warn(`Comments truncated for issue ${issueKey}: returned ${response.comments.length} of ${response.total} total comments`);
884
+ }
885
+ return response.comments;
886
+ }
887
+ /**
888
+ * Update a comment on an issue
889
+ * Accepts Markdown content which is converted to ADF for Jira
890
+ */
891
+ async updateComment(issueKey, commentId, body) {
892
+ return this.put(`/issue/${issueKey}/comment/${commentId}`, {
893
+ body: markdownToAdf(body)
894
+ });
895
+ }
896
+ /**
897
+ * Get available transitions for an issue
898
+ */
899
+ async getTransitions(issueKey) {
900
+ const response = await this.get(`/issue/${issueKey}/transitions`);
901
+ return response.transitions;
902
+ }
903
+ /**
904
+ * Transition an issue to a new state
905
+ */
906
+ async transitionIssue(issueKey, transitionId) {
907
+ await this.post(`/issue/${issueKey}/transitions`, {
908
+ transition: {
909
+ id: transitionId
910
+ }
911
+ });
912
+ }
913
+ /**
914
+ * Create a new issue
915
+ * Accepts Markdown description which is converted to ADF for Jira
916
+ */
917
+ async createIssue(projectKey, summary, description, issueType = "Task") {
918
+ return this.post("/issue", {
919
+ fields: {
920
+ project: {
921
+ key: projectKey
922
+ },
923
+ summary,
924
+ description: markdownToAdf(description),
925
+ issuetype: {
926
+ name: issueType
927
+ }
928
+ }
929
+ });
930
+ }
931
+ /**
932
+ * Update an issue's fields (summary, description)
933
+ * @param issueKey - Jira issue key (e.g., "PROJ-123")
934
+ * @param fields - Fields to update
935
+ */
936
+ async updateIssue(issueKey, fields) {
937
+ const updateFields = {};
938
+ if (fields.summary !== void 0) {
939
+ updateFields.summary = fields.summary;
940
+ }
941
+ if (fields.description !== void 0) {
942
+ updateFields.description = markdownToAdf(fields.description);
943
+ }
944
+ await this.put(`/issue/${issueKey}`, { fields: updateFields });
945
+ }
946
+ /**
947
+ * Create an issue with a parent (subtask or child issue)
948
+ * Accepts Markdown description which is converted to ADF for Jira
949
+ */
950
+ async createIssueWithParent(projectKey, summary, description, parentKey, issueType = "Subtask") {
951
+ return this.post("/issue", {
952
+ fields: {
953
+ project: {
954
+ key: projectKey
955
+ },
956
+ summary,
957
+ description: markdownToAdf(description),
958
+ issuetype: {
959
+ name: issueType
960
+ },
961
+ parent: {
962
+ key: parentKey
963
+ }
964
+ }
965
+ });
966
+ }
967
+ /**
968
+ * Create an issue link (dependency/relationship between issues)
969
+ * @param inwardKey - The issue key for the inward side (e.g., the blocked issue)
970
+ * @param outwardKey - The issue key for the outward side (e.g., the blocking issue)
971
+ * @param linkType - The link type name (e.g., "Blocks")
972
+ */
973
+ async createIssueLink(inwardKey, outwardKey, linkType) {
974
+ await this.post("/issueLink", {
975
+ type: {
976
+ name: linkType
977
+ },
978
+ inwardIssue: {
979
+ key: inwardKey
980
+ },
981
+ outwardIssue: {
982
+ key: outwardKey
983
+ }
984
+ });
985
+ }
986
+ /**
987
+ * Delete an issue link by ID
988
+ */
989
+ async deleteIssueLink(linkId) {
990
+ await this.delete(`/issueLink/${linkId}`);
991
+ }
992
+ /**
993
+ * Search issues using JQL
994
+ * Automatically paginates through all results up to MAX_SEARCH_RESULTS.
995
+ */
996
+ async searchIssues(jql) {
997
+ const MAX_SEARCH_RESULTS = 5e3;
998
+ const allIssues = [];
999
+ let nextPageToken;
1000
+ const maxResults = 100;
1001
+ while (allIssues.length < MAX_SEARCH_RESULTS) {
1002
+ const body = {
1003
+ jql,
1004
+ maxResults,
1005
+ fields: [
1006
+ "summary",
1007
+ "description",
1008
+ "status",
1009
+ "issuetype",
1010
+ "project",
1011
+ "assignee",
1012
+ "reporter",
1013
+ "labels",
1014
+ "created",
1015
+ "updated",
1016
+ "issuelinks",
1017
+ "parent"
1018
+ ]
1019
+ };
1020
+ if (nextPageToken) {
1021
+ body.nextPageToken = nextPageToken;
1022
+ }
1023
+ const response = await this.post(
1024
+ "/search/jql",
1025
+ body
1026
+ );
1027
+ allIssues.push(...response.issues);
1028
+ if (!response.nextPageToken || response.issues.length === 0) {
1029
+ break;
1030
+ }
1031
+ nextPageToken = response.nextPageToken;
1032
+ }
1033
+ if (allIssues.length >= MAX_SEARCH_RESULTS) {
1034
+ getLogger().warn(`Search results truncated at ${MAX_SEARCH_RESULTS} issues. The query matched more results than the safety cap allows.`, { jql, returnedCount: allIssues.length });
1035
+ }
1036
+ return allIssues;
1037
+ }
1038
+ /**
1039
+ * Test connection to Jira API
1040
+ */
1041
+ async testConnection() {
1042
+ try {
1043
+ await this.get("/myself");
1044
+ return true;
1045
+ } catch (error) {
1046
+ const message = error instanceof Error ? error.message : String(error);
1047
+ if (message.includes("Jira API error (401)") || message.includes("Jira API error (403)")) {
1048
+ getLogger().error("Jira connection test failed: authentication error", { error });
1049
+ return false;
1050
+ }
1051
+ throw error;
1052
+ }
1053
+ }
1054
+ };
1055
+
1056
+ // src/lib/providers/jira/JiraIssueTracker.ts
1057
+ var JiraIssueTracker = class {
1058
+ constructor(config, options) {
1059
+ this.providerName = "jira";
1060
+ this.supportsPullRequests = false;
1061
+ this.config = config;
1062
+ this.client = new JiraApiClient({
1063
+ host: config.host,
1064
+ username: config.username,
1065
+ apiToken: config.apiToken
1066
+ });
1067
+ this.prompter = (options == null ? void 0 : options.prompter) ?? promptConfirmation;
1068
+ }
1069
+ /**
1070
+ * Normalize identifier to canonical uppercase form
1071
+ * Jira issue keys are case-sensitive in the API (must be uppercase)
1072
+ */
1073
+ normalizeIdentifier(identifier) {
1074
+ return String(identifier).toUpperCase();
1075
+ }
1076
+ /**
1077
+ * Detect input type from user input
1078
+ * Jira issues follow pattern: PROJECTKEY-123 (case-insensitive)
1079
+ */
1080
+ async detectInputType(input) {
1081
+ const jiraPattern = /^([A-Z][A-Z0-9]+)-(\d+)$/i;
1082
+ const match = input.match(jiraPattern);
1083
+ if (!match) {
1084
+ return { type: "unknown", identifier: null, rawInput: input };
1085
+ }
1086
+ const issueKey = this.normalizeIdentifier(input);
1087
+ getLogger().debug("Checking if input is a Jira issue", { issueKey });
1088
+ try {
1089
+ await this.client.getIssue(issueKey);
1090
+ return { type: "issue", identifier: issueKey, rawInput: input };
1091
+ } catch (error) {
1092
+ if (error instanceof Error && (/404/.test(error.message) || /not found/i.test(error.message))) {
1093
+ getLogger().debug("Issue not found", { issueKey, error });
1094
+ return { type: "unknown", identifier: null, rawInput: input };
1095
+ }
1096
+ throw error;
1097
+ }
1098
+ }
1099
+ /**
1100
+ * Fetch issue details
1101
+ */
1102
+ async fetchIssue(identifier) {
1103
+ const issueKey = this.normalizeIdentifier(identifier);
1104
+ getLogger().debug("Fetching Jira issue", { issueKey });
1105
+ const jiraIssue = await this.client.getIssue(issueKey);
1106
+ return this.mapJiraIssueToIssue(jiraIssue);
1107
+ }
1108
+ /**
1109
+ * Check if issue exists (silent validation)
1110
+ */
1111
+ async isValidIssue(identifier) {
1112
+ try {
1113
+ return await this.fetchIssue(identifier);
1114
+ } catch (error) {
1115
+ if (error instanceof Error && (/404/.test(error.message) || /not found/i.test(error.message))) {
1116
+ getLogger().debug("Issue validation failed: not found", { identifier, error });
1117
+ return false;
1118
+ }
1119
+ throw error;
1120
+ }
1121
+ }
1122
+ /**
1123
+ * Validate issue state
1124
+ * Note: Jira doesn't have a simple "closed" state - depends on workflow
1125
+ */
1126
+ async validateIssueState(issue) {
1127
+ getLogger().debug("Jira issue state", { issueKey: issue.number, state: issue.state });
1128
+ if (issue.state === "closed") {
1129
+ const shouldContinue = await this.prompter(
1130
+ `Issue ${issue.number} is in a completed state. Continue anyway?`
1131
+ );
1132
+ if (!shouldContinue) {
1133
+ throw new Error("User cancelled due to completed issue");
1134
+ }
1135
+ }
1136
+ }
1137
+ /**
1138
+ * Create a new issue
1139
+ */
1140
+ async createIssue(title, body, _repository, _labels) {
1141
+ getLogger().debug("Creating Jira issue", { title, projectKey: this.config.projectKey });
1142
+ const jiraIssue = await this.client.createIssue(
1143
+ this.config.projectKey,
1144
+ title,
1145
+ body,
1146
+ this.config.defaultIssueType
1147
+ );
1148
+ return {
1149
+ number: jiraIssue.key,
1150
+ url: `${this.config.host}/browse/${jiraIssue.key}`
1151
+ };
1152
+ }
1153
+ /**
1154
+ * Get issue URL
1155
+ */
1156
+ async getIssueUrl(identifier) {
1157
+ const issueKey = this.normalizeIdentifier(identifier);
1158
+ return `${this.config.host}/browse/${issueKey}`;
1159
+ }
1160
+ /**
1161
+ * Move issue to "In Progress" state
1162
+ * Uses configured transition mapping or default transition name
1163
+ */
1164
+ async moveIssueToInProgress(identifier) {
1165
+ var _a;
1166
+ const issueKey = this.normalizeIdentifier(identifier);
1167
+ getLogger().debug("Moving Jira issue to In Progress", { issueKey });
1168
+ const transitions = await this.client.getTransitions(issueKey);
1169
+ const transitionName = ((_a = this.config.transitionMappings) == null ? void 0 : _a["In Progress"]) ?? this.findTransitionByName(transitions, ["In Progress", "Start Progress", "Start"]);
1170
+ if (!transitionName) {
1171
+ throw new Error(
1172
+ `Could not find "In Progress" transition for ${issueKey}. Available transitions: ${transitions.map((t) => t.name).join(", ")}. Configure custom mapping in settings.json: issueManagement.jira.transitionMappings`
1173
+ );
1174
+ }
1175
+ const transition = transitions.find((t) => t.name === transitionName);
1176
+ if (!transition) {
1177
+ throw new Error(`Transition "${transitionName}" not found`);
1178
+ }
1179
+ await this.client.transitionIssue(issueKey, transition.id);
1180
+ getLogger().info("Issue transitioned successfully", { issueKey, transition: transitionName });
1181
+ }
1182
+ /**
1183
+ * Move issue to "Ready for Review" state
1184
+ * Uses configured transition mapping or default transition name
1185
+ */
1186
+ async moveIssueToReadyForReview(identifier) {
1187
+ var _a;
1188
+ const issueKey = this.normalizeIdentifier(identifier);
1189
+ getLogger().debug("Moving Jira issue to Ready for Review", { issueKey });
1190
+ const transitions = await this.client.getTransitions(issueKey);
1191
+ const transitionName = ((_a = this.config.transitionMappings) == null ? void 0 : _a["Ready for Review"]) ?? this.findTransitionByName(transitions, ["Ready for Review", "In Review", "Code Review", "Review"]);
1192
+ if (!transitionName) {
1193
+ throw new Error(
1194
+ `Could not find "Ready for Review" transition for ${issueKey}. Available transitions: ${transitions.map((t) => t.name).join(", ")}. Configure custom mapping in settings.json: issueManagement.jira.transitionMappings`
1195
+ );
1196
+ }
1197
+ const transition = transitions.find((t) => t.name === transitionName);
1198
+ if (!transition) {
1199
+ throw new Error(`Transition "${transitionName}" not found`);
1200
+ }
1201
+ await this.client.transitionIssue(issueKey, transition.id);
1202
+ getLogger().info("Issue transitioned to Ready for Review", { issueKey, transition: transitionName });
1203
+ }
1204
+ /**
1205
+ * Close an issue by transitioning to "Done" state
1206
+ * Uses configured transition mapping or default transition names
1207
+ */
1208
+ async closeIssue(identifier) {
1209
+ var _a;
1210
+ const issueKey = this.normalizeIdentifier(identifier);
1211
+ getLogger().debug("Closing Jira issue", { issueKey });
1212
+ const transitions = await this.client.getTransitions(issueKey);
1213
+ const transitionName = ((_a = this.config.transitionMappings) == null ? void 0 : _a["Done"]) ?? this.findTransitionByName(transitions, ["Done", "Close", "Closed", "Resolve", "Resolved"]);
1214
+ if (!transitionName) {
1215
+ throw new Error(
1216
+ `Could not find "Done" transition for ${issueKey}. Available transitions: ${transitions.map((t) => t.name).join(", ")}. Configure custom mapping in settings.json: issueManagement.jira.transitionMappings`
1217
+ );
1218
+ }
1219
+ const transition = transitions.find((t) => t.name === transitionName);
1220
+ if (!transition) {
1221
+ throw new Error(`Transition "${transitionName}" not found`);
1222
+ }
1223
+ await this.client.transitionIssue(issueKey, transition.id);
1224
+ getLogger().info("Issue closed successfully", { issueKey, transition: transitionName });
1225
+ }
1226
+ /**
1227
+ * Reopen an issue by transitioning back to an open state
1228
+ * Uses configured transition mapping or default transition names
1229
+ */
1230
+ async reopenIssue(identifier) {
1231
+ var _a;
1232
+ const issueKey = this.normalizeIdentifier(identifier);
1233
+ getLogger().debug("Reopening Jira issue", { issueKey });
1234
+ const transitions = await this.client.getTransitions(issueKey);
1235
+ const transitionName = ((_a = this.config.transitionMappings) == null ? void 0 : _a["Reopen"]) ?? this.findTransitionByName(transitions, ["Reopen", "To Do", "Open", "Backlog"]);
1236
+ if (!transitionName) {
1237
+ throw new Error(
1238
+ `Could not find "Reopen" transition for ${issueKey}. Available transitions: ${transitions.map((t) => t.name).join(", ")}. Configure custom mapping in settings.json: issueManagement.jira.transitionMappings`
1239
+ );
1240
+ }
1241
+ const transition = transitions.find((t) => t.name === transitionName);
1242
+ if (!transition) {
1243
+ throw new Error(`Transition "${transitionName}" not found`);
1244
+ }
1245
+ await this.client.transitionIssue(issueKey, transition.id);
1246
+ getLogger().info("Issue reopened successfully", { issueKey, transition: transitionName });
1247
+ }
1248
+ /**
1249
+ * Extract context from issue for AI prompts
1250
+ */
1251
+ extractContext(entity) {
1252
+ return `Issue: ${entity.number}
1253
+ Title: ${entity.title}
1254
+ Status: ${entity.state}
1255
+ URL: ${entity.url}
1256
+
1257
+ Description:
1258
+ ${entity.body}
1259
+
1260
+ ${entity.labels.length > 0 ? `Labels: ${entity.labels.join(", ")}` : ""}
1261
+ ${entity.assignees.length > 0 ? `Assignees: ${entity.assignees.join(", ")}` : ""}`;
1262
+ }
1263
+ /**
1264
+ * Fetch child issues of a Jira parent issue using JQL
1265
+ * @param parentIdentifier - Jira issue key (e.g., "PROJ-123")
1266
+ * @param _repo - Repository (unused for Jira)
1267
+ * @returns Array of child issues
1268
+ */
1269
+ async getChildIssues(parentIdentifier, _repo) {
1270
+ const parentKey = this.normalizeIdentifier(parentIdentifier);
1271
+ const jiraKeyPattern = /^[A-Z][A-Z0-9]+-\d+$/;
1272
+ if (!jiraKeyPattern.test(parentKey)) {
1273
+ getLogger().warn(`Invalid Jira issue key format: ${parentKey}`);
1274
+ return [];
1275
+ }
1276
+ const issues = await this.client.searchIssues(`parent = ${parentKey}`);
1277
+ return issues.map((issue) => ({
1278
+ id: issue.key,
1279
+ title: issue.fields.summary,
1280
+ url: `${this.config.host}/browse/${issue.key}`,
1281
+ state: issue.fields.status.name.toLowerCase()
1282
+ }));
1283
+ }
1284
+ /**
1285
+ * Get issue details (alias for fetchIssue for MCP compatibility)
1286
+ */
1287
+ async getIssue(identifier) {
1288
+ return this.fetchIssue(identifier);
1289
+ }
1290
+ /**
1291
+ * Get all comments for an issue
1292
+ */
1293
+ async getComments(identifier) {
1294
+ const issueKey = this.normalizeIdentifier(identifier);
1295
+ getLogger().debug("Fetching Jira comments", { issueKey });
1296
+ const comments = await this.client.getComments(issueKey);
1297
+ return comments.map((comment) => ({
1298
+ id: comment.id,
1299
+ body: adfToMarkdown(comment.body),
1300
+ author: comment.author,
1301
+ createdAt: comment.created,
1302
+ updatedAt: comment.updated
1303
+ }));
1304
+ }
1305
+ /**
1306
+ * Add a comment to an issue
1307
+ */
1308
+ async addComment(identifier, body) {
1309
+ const issueKey = this.normalizeIdentifier(identifier);
1310
+ getLogger().debug("Adding Jira comment", { issueKey });
1311
+ const comment = await this.client.addComment(issueKey, body);
1312
+ return { id: comment.id };
1313
+ }
1314
+ /**
1315
+ * Update an existing comment
1316
+ */
1317
+ async updateComment(identifier, commentId, body) {
1318
+ const issueKey = this.normalizeIdentifier(identifier);
1319
+ getLogger().debug("Updating Jira comment", { issueKey, commentId });
1320
+ await this.client.updateComment(issueKey, commentId, body);
1321
+ }
1322
+ /**
1323
+ * Get the underlying API client (for direct API access by MCP provider)
1324
+ */
1325
+ getApiClient() {
1326
+ return this.client;
1327
+ }
1328
+ /**
1329
+ * Get configuration (for MCP provider)
1330
+ */
1331
+ getConfig() {
1332
+ return this.config;
1333
+ }
1334
+ /**
1335
+ * Map Jira API issue to generic Issue type
1336
+ */
1337
+ mapJiraIssueToIssue(jiraIssue) {
1338
+ const description = adfToMarkdown(jiraIssue.fields.description);
1339
+ return {
1340
+ id: jiraIssue.id,
1341
+ key: jiraIssue.key,
1342
+ number: jiraIssue.key,
1343
+ title: jiraIssue.fields.summary,
1344
+ body: description,
1345
+ state: this.mapJiraStatusToState(jiraIssue.fields.status.name),
1346
+ labels: jiraIssue.fields.labels,
1347
+ assignees: jiraIssue.fields.assignee ? [jiraIssue.fields.assignee.displayName] : [],
1348
+ assignee: jiraIssue.fields.assignee,
1349
+ author: jiraIssue.fields.reporter,
1350
+ url: `${this.config.host}/browse/${jiraIssue.key}`,
1351
+ issueType: jiraIssue.fields.issuetype.name,
1352
+ status: jiraIssue.fields.status.name
1353
+ };
1354
+ }
1355
+ mapJiraStatusToState(statusName) {
1356
+ const normalized = statusName.toLowerCase();
1357
+ const closedStatuses = ["done", "closed", "resolved", "cancelled", "canceled"];
1358
+ return closedStatuses.includes(normalized) ? "closed" : "open";
1359
+ }
1360
+ /**
1361
+ * Find a transition by name, trying multiple possible names
1362
+ */
1363
+ findTransitionByName(transitions, names) {
1364
+ for (const name of names) {
1365
+ const transition = transitions.find(
1366
+ (t) => t.name.toLowerCase() === name.toLowerCase()
1367
+ );
1368
+ if (transition) {
1369
+ return transition.name;
1370
+ }
1371
+ }
1372
+ return null;
1373
+ }
1374
+ };
1375
+
1376
+ export {
1377
+ LinearServiceError,
1378
+ fetchLinearIssue,
1379
+ createLinearIssue,
1380
+ createLinearChildIssue,
1381
+ createLinearComment,
1382
+ updateLinearIssueState,
1383
+ editLinearIssue,
1384
+ getLinearComment,
1385
+ updateLinearComment,
1386
+ fetchLinearIssueComments,
1387
+ getLinearChildIssues,
1388
+ createLinearIssueRelation,
1389
+ getLinearIssueDependencies,
1390
+ deleteLinearIssueRelation,
1391
+ fetchLinearIssueList,
1392
+ findLinearIssueRelation,
1393
+ JiraApiClient,
1394
+ JiraIssueTracker
1395
+ };
1396
+ //# sourceMappingURL=chunk-HEXKPKCK.js.map