@iloom/cli 0.7.5 → 0.8.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 (171) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +32 -3
  3. package/dist/{ClaudeContextManager-Y2YJC6BU.js → ClaudeContextManager-RDP6CLK6.js} +5 -5
  4. package/dist/{ClaudeService-NDVFQRKC.js → ClaudeService-FKPOQRA4.js} +4 -4
  5. package/dist/GitHubService-ACZVNTJE.js +12 -0
  6. package/dist/{LoomLauncher-U2B3VHPC.js → LoomLauncher-NHZMEVTQ.js} +5 -5
  7. package/dist/{MetadataManager-XJ2YB762.js → MetadataManager-W3C54UYT.js} +2 -2
  8. package/dist/{PRManager-6ZJZRG5Z.js → PRManager-XLTVG6YG.js} +5 -5
  9. package/dist/{PromptTemplateManager-7L3HJQQU.js → PromptTemplateManager-OUYDHOPI.js} +2 -2
  10. package/dist/README.md +32 -3
  11. package/dist/{SettingsManager-YU4VYPTW.js → SettingsManager-VCVLL32H.js} +4 -2
  12. package/dist/{SettingsMigrationManager-KZKDG66H.js → SettingsMigrationManager-LEBMJP3B.js} +3 -3
  13. package/dist/agents/iloom-code-reviewer.md +720 -0
  14. package/dist/agents/iloom-issue-analyze-and-plan.md +1 -1
  15. package/dist/agents/iloom-issue-analyzer.md +1 -1
  16. package/dist/agents/iloom-issue-complexity-evaluator.md +1 -1
  17. package/dist/agents/iloom-issue-enhancer.md +1 -1
  18. package/dist/agents/iloom-issue-implementer.md +1 -1
  19. package/dist/agents/iloom-issue-planner.md +1 -1
  20. package/dist/{build-HQ5HGA3T.js → build-H4DK3DMQ.js} +7 -7
  21. package/dist/{chunk-N7FVXZNI.js → chunk-4BSXZ5YZ.js} +31 -9
  22. package/dist/chunk-4BSXZ5YZ.js.map +1 -0
  23. package/dist/{chunk-VYKKWU36.js → chunk-4KGRPHM6.js} +3 -3
  24. package/dist/{chunk-CFQVOTHO.js → chunk-52MVUK5V.js} +2 -2
  25. package/dist/{chunk-TIYJEEVO.js → chunk-66QOCD5N.js} +1 -1
  26. package/dist/chunk-66QOCD5N.js.map +1 -0
  27. package/dist/chunk-7JDMYTFZ.js +251 -0
  28. package/dist/chunk-7JDMYTFZ.js.map +1 -0
  29. package/dist/{chunk-7LSSNB7Y.js → chunk-7ZEHSSUP.js} +2 -2
  30. package/dist/chunk-A4UQY3M2.js +75 -0
  31. package/dist/chunk-A4UQY3M2.js.map +1 -0
  32. package/dist/{chunk-KSXA2NOJ.js → chunk-AZH27CPV.js} +10 -9
  33. package/dist/chunk-AZH27CPV.js.map +1 -0
  34. package/dist/{chunk-ELJKYFSH.js → chunk-BCQDYAOJ.js} +4 -4
  35. package/dist/{chunk-F2PWIRV4.js → chunk-BYUMEDDD.js} +2 -2
  36. package/dist/{chunk-CAXFWFV6.js → chunk-ECP77QGE.js} +4 -4
  37. package/dist/{chunk-ZA575VLF.js → chunk-GDS2HXSW.js} +4 -4
  38. package/dist/{chunk-UDRZY65Y.js → chunk-HSGZW3ID.js} +2 -2
  39. package/dist/{chunk-WFQ5CLTR.js → chunk-IWIIOFEB.js} +56 -5
  40. package/dist/chunk-IWIIOFEB.js.map +1 -0
  41. package/dist/{chunk-VWGKGNJP.js → chunk-KBEIQP4G.js} +3 -1
  42. package/dist/chunk-KBEIQP4G.js.map +1 -0
  43. package/dist/{chunk-LZBSLO6S.js → chunk-L4CN7YQT.js} +381 -8
  44. package/dist/chunk-L4CN7YQT.js.map +1 -0
  45. package/dist/{chunk-HBJITKSZ.js → chunk-LFVRG6UU.js} +159 -3
  46. package/dist/chunk-LFVRG6UU.js.map +1 -0
  47. package/dist/{chunk-64HCHVJM.js → chunk-PLI3JQWT.js} +2 -2
  48. package/dist/{chunk-USJSNHGG.js → chunk-PVW6JE7E.js} +3 -3
  49. package/dist/{chunk-3K3WY3BN.js → chunk-QJX6ICWY.js} +4 -4
  50. package/dist/{chunk-C7YW5IMS.js → chunk-RODL2HVY.js} +17 -6
  51. package/dist/{chunk-C7YW5IMS.js.map → chunk-RODL2HVY.js.map} +1 -1
  52. package/dist/{chunk-NEPH2O4C.js → chunk-SSASIBDJ.js} +3 -3
  53. package/dist/{chunk-GCPAZSGV.js → chunk-THS5L54H.js} +150 -3
  54. package/dist/chunk-THS5L54H.js.map +1 -0
  55. package/dist/{chunk-5V74K5ZA.js → chunk-TVH67KEO.js} +25 -2
  56. package/dist/chunk-TVH67KEO.js.map +1 -0
  57. package/dist/{chunk-ENMTWE74.js → chunk-VZYSM7N7.js} +2 -2
  58. package/dist/{chunk-77VLG2KP.js → chunk-WNXYC7J4.js} +18 -16
  59. package/dist/chunk-WNXYC7J4.js.map +1 -0
  60. package/dist/{chunk-WZYBHD7P.js → chunk-XHNACIHO.js} +2 -2
  61. package/dist/{chunk-XAMBIVXE.js → chunk-XJHQVOT6.js} +2 -2
  62. package/dist/{chunk-O36JLYNW.js → chunk-XU5A6BWA.js} +4 -7
  63. package/dist/chunk-XU5A6BWA.js.map +1 -0
  64. package/dist/{chunk-TB6475EW.js → chunk-YAVVDZVF.js} +3 -3
  65. package/dist/{cleanup-DB7EFBF3.js → cleanup-25PCP2EM.js} +16 -16
  66. package/dist/cli.js +107 -157
  67. package/dist/cli.js.map +1 -1
  68. package/dist/{commit-NGMDWWAP.js → commit-SS77KUNX.js} +10 -10
  69. package/dist/{compile-CT7IR7O2.js → compile-ZOAODFN2.js} +7 -7
  70. package/dist/{contribute-GXKOIA42.js → contribute-7USRBWRM.js} +6 -6
  71. package/dist/{dev-server-OAP3RZC6.js → dev-server-TYYJM3XA.js} +9 -9
  72. package/dist/{feedback-ZLAX3BVL.js → feedback-HZVLOTQJ.js} +9 -9
  73. package/dist/{git-ENLT2VNI.js → git-GUNOPP4Q.js} +4 -4
  74. package/dist/hooks/iloom-hook.js +75 -3
  75. package/dist/{ignite-HA2OJF6Z.js → ignite-CPXPZ4ZD.js} +85 -25
  76. package/dist/ignite-CPXPZ4ZD.js.map +1 -0
  77. package/dist/index.d.ts +85 -2
  78. package/dist/index.js +133 -73
  79. package/dist/index.js.map +1 -1
  80. package/dist/init-MZBIXQ7V.js +21 -0
  81. package/dist/{lint-HAVU4U34.js → lint-MDVUV3W2.js} +7 -7
  82. package/dist/mcp/issue-management-server.js +832 -7
  83. package/dist/mcp/issue-management-server.js.map +1 -1
  84. package/dist/{neon-helpers-3KBC4A3Y.js → neon-helpers-VVFFTLXE.js} +3 -3
  85. package/dist/{open-IN3LUZXX.js → open-2LPZ7XXW.js} +9 -9
  86. package/dist/plan-N3YDCOIV.js +371 -0
  87. package/dist/plan-N3YDCOIV.js.map +1 -0
  88. package/dist/{projects-CTRTTMSK.js → projects-325GEEGJ.js} +2 -2
  89. package/dist/{prompt-3SAZYRUN.js → prompt-ONNPSNKM.js} +2 -2
  90. package/dist/prompts/init-prompt.txt +57 -1
  91. package/dist/prompts/issue-prompt.txt +51 -3
  92. package/dist/prompts/plan-prompt.txt +435 -0
  93. package/dist/prompts/pr-prompt.txt +38 -0
  94. package/dist/prompts/regular-prompt.txt +53 -3
  95. package/dist/{rebase-RLEVFHWN.js → rebase-7YS3N274.js} +6 -6
  96. package/dist/{recap-ZKGHZCX6.js → recap-GSXFEOD6.js} +6 -6
  97. package/dist/{run-QEIS2EH2.js → run-XPGCMFFO.js} +9 -9
  98. package/dist/schema/settings.schema.json +57 -1
  99. package/dist/{shell-2NNSIU34.js → shell-2SPM3Z5O.js} +6 -6
  100. package/dist/{summary-2KLNHVTN.js → summary-5UWNLAI5.js} +43 -12
  101. package/dist/summary-5UWNLAI5.js.map +1 -0
  102. package/dist/{test-75WAA6DU.js → test-N2725YRI.js} +7 -7
  103. package/dist/{test-git-E2BLXR6M.js → test-git-ZPSPA2TP.js} +4 -4
  104. package/dist/{test-prefix-A7JGGYAA.js → test-prefix-6DLB2BHE.js} +4 -4
  105. package/dist/{test-webserver-J6SMNLU2.js → test-webserver-XLJ2TZFP.js} +6 -6
  106. package/package.json +1 -1
  107. package/dist/GitHubService-O7U4UQ7N.js +0 -12
  108. package/dist/agents/iloom-issue-reviewer.md +0 -139
  109. package/dist/chunk-5V74K5ZA.js.map +0 -1
  110. package/dist/chunk-77VLG2KP.js.map +0 -1
  111. package/dist/chunk-GCPAZSGV.js.map +0 -1
  112. package/dist/chunk-HBJITKSZ.js.map +0 -1
  113. package/dist/chunk-KSXA2NOJ.js.map +0 -1
  114. package/dist/chunk-LZBSLO6S.js.map +0 -1
  115. package/dist/chunk-N7FVXZNI.js.map +0 -1
  116. package/dist/chunk-O36JLYNW.js.map +0 -1
  117. package/dist/chunk-TIYJEEVO.js.map +0 -1
  118. package/dist/chunk-VWGKGNJP.js.map +0 -1
  119. package/dist/chunk-WFQ5CLTR.js.map +0 -1
  120. package/dist/chunk-ZX3GTM7O.js +0 -119
  121. package/dist/chunk-ZX3GTM7O.js.map +0 -1
  122. package/dist/ignite-HA2OJF6Z.js.map +0 -1
  123. package/dist/init-S6IEGRSX.js +0 -21
  124. package/dist/summary-2KLNHVTN.js.map +0 -1
  125. /package/dist/{ClaudeContextManager-Y2YJC6BU.js.map → ClaudeContextManager-RDP6CLK6.js.map} +0 -0
  126. /package/dist/{ClaudeService-NDVFQRKC.js.map → ClaudeService-FKPOQRA4.js.map} +0 -0
  127. /package/dist/{GitHubService-O7U4UQ7N.js.map → GitHubService-ACZVNTJE.js.map} +0 -0
  128. /package/dist/{LoomLauncher-U2B3VHPC.js.map → LoomLauncher-NHZMEVTQ.js.map} +0 -0
  129. /package/dist/{MetadataManager-XJ2YB762.js.map → MetadataManager-W3C54UYT.js.map} +0 -0
  130. /package/dist/{PRManager-6ZJZRG5Z.js.map → PRManager-XLTVG6YG.js.map} +0 -0
  131. /package/dist/{PromptTemplateManager-7L3HJQQU.js.map → PromptTemplateManager-OUYDHOPI.js.map} +0 -0
  132. /package/dist/{SettingsManager-YU4VYPTW.js.map → SettingsManager-VCVLL32H.js.map} +0 -0
  133. /package/dist/{SettingsMigrationManager-KZKDG66H.js.map → SettingsMigrationManager-LEBMJP3B.js.map} +0 -0
  134. /package/dist/{build-HQ5HGA3T.js.map → build-H4DK3DMQ.js.map} +0 -0
  135. /package/dist/{chunk-VYKKWU36.js.map → chunk-4KGRPHM6.js.map} +0 -0
  136. /package/dist/{chunk-CFQVOTHO.js.map → chunk-52MVUK5V.js.map} +0 -0
  137. /package/dist/{chunk-7LSSNB7Y.js.map → chunk-7ZEHSSUP.js.map} +0 -0
  138. /package/dist/{chunk-ELJKYFSH.js.map → chunk-BCQDYAOJ.js.map} +0 -0
  139. /package/dist/{chunk-F2PWIRV4.js.map → chunk-BYUMEDDD.js.map} +0 -0
  140. /package/dist/{chunk-CAXFWFV6.js.map → chunk-ECP77QGE.js.map} +0 -0
  141. /package/dist/{chunk-ZA575VLF.js.map → chunk-GDS2HXSW.js.map} +0 -0
  142. /package/dist/{chunk-UDRZY65Y.js.map → chunk-HSGZW3ID.js.map} +0 -0
  143. /package/dist/{chunk-64HCHVJM.js.map → chunk-PLI3JQWT.js.map} +0 -0
  144. /package/dist/{chunk-USJSNHGG.js.map → chunk-PVW6JE7E.js.map} +0 -0
  145. /package/dist/{chunk-3K3WY3BN.js.map → chunk-QJX6ICWY.js.map} +0 -0
  146. /package/dist/{chunk-NEPH2O4C.js.map → chunk-SSASIBDJ.js.map} +0 -0
  147. /package/dist/{chunk-ENMTWE74.js.map → chunk-VZYSM7N7.js.map} +0 -0
  148. /package/dist/{chunk-WZYBHD7P.js.map → chunk-XHNACIHO.js.map} +0 -0
  149. /package/dist/{chunk-XAMBIVXE.js.map → chunk-XJHQVOT6.js.map} +0 -0
  150. /package/dist/{chunk-TB6475EW.js.map → chunk-YAVVDZVF.js.map} +0 -0
  151. /package/dist/{cleanup-DB7EFBF3.js.map → cleanup-25PCP2EM.js.map} +0 -0
  152. /package/dist/{commit-NGMDWWAP.js.map → commit-SS77KUNX.js.map} +0 -0
  153. /package/dist/{compile-CT7IR7O2.js.map → compile-ZOAODFN2.js.map} +0 -0
  154. /package/dist/{contribute-GXKOIA42.js.map → contribute-7USRBWRM.js.map} +0 -0
  155. /package/dist/{dev-server-OAP3RZC6.js.map → dev-server-TYYJM3XA.js.map} +0 -0
  156. /package/dist/{feedback-ZLAX3BVL.js.map → feedback-HZVLOTQJ.js.map} +0 -0
  157. /package/dist/{git-ENLT2VNI.js.map → git-GUNOPP4Q.js.map} +0 -0
  158. /package/dist/{init-S6IEGRSX.js.map → init-MZBIXQ7V.js.map} +0 -0
  159. /package/dist/{lint-HAVU4U34.js.map → lint-MDVUV3W2.js.map} +0 -0
  160. /package/dist/{neon-helpers-3KBC4A3Y.js.map → neon-helpers-VVFFTLXE.js.map} +0 -0
  161. /package/dist/{open-IN3LUZXX.js.map → open-2LPZ7XXW.js.map} +0 -0
  162. /package/dist/{projects-CTRTTMSK.js.map → projects-325GEEGJ.js.map} +0 -0
  163. /package/dist/{prompt-3SAZYRUN.js.map → prompt-ONNPSNKM.js.map} +0 -0
  164. /package/dist/{rebase-RLEVFHWN.js.map → rebase-7YS3N274.js.map} +0 -0
  165. /package/dist/{recap-ZKGHZCX6.js.map → recap-GSXFEOD6.js.map} +0 -0
  166. /package/dist/{run-QEIS2EH2.js.map → run-XPGCMFFO.js.map} +0 -0
  167. /package/dist/{shell-2NNSIU34.js.map → shell-2SPM3Z5O.js.map} +0 -0
  168. /package/dist/{test-75WAA6DU.js.map → test-N2725YRI.js.map} +0 -0
  169. /package/dist/{test-git-E2BLXR6M.js.map → test-git-ZPSPA2TP.js.map} +0 -0
  170. /package/dist/{test-prefix-A7JGGYAA.js.map → test-prefix-6DLB2BHE.js.map} +0 -0
  171. /package/dist/{test-webserver-J6SMNLU2.js.map → test-webserver-XLJ2TZFP.js.map} +0 -0
@@ -125,7 +125,7 @@ async function executeGhCommand(args, options) {
125
125
  timeout: (options == null ? void 0 : options.timeout) ?? 3e4,
126
126
  encoding: "utf8"
127
127
  });
128
- const isJson = args.includes("--json") || args.includes("--jq") || args.includes("--format") && args[args.indexOf("--format") + 1] === "json";
128
+ const isJson = args.includes("--json") || args.includes("--jq") || args.includes("--format") && args[args.indexOf("--format") + 1] === "json" || args[0] === "api" && args[1] === "graphql";
129
129
  const data = isJson ? JSON.parse(result.stdout) : result.stdout;
130
130
  return data;
131
131
  }
@@ -235,6 +235,386 @@ async function addSubIssue(parentNodeId, childNodeId) {
235
235
  `subIssueId=${childNodeId}`
236
236
  ]);
237
237
  }
238
+ async function getSubIssues(issueNumber, repo) {
239
+ var _a, _b;
240
+ logger.debug("Fetching GitHub sub-issues", { issueNumber, repo });
241
+ const parentNodeId = await getIssueNodeId(issueNumber, repo);
242
+ const query = `
243
+ query getSubIssues($parentId: ID!) {
244
+ node(id: $parentId) {
245
+ ... on Issue {
246
+ subIssues(first: 100) {
247
+ nodes {
248
+ number
249
+ title
250
+ url
251
+ state
252
+ }
253
+ }
254
+ }
255
+ }
256
+ }
257
+ `;
258
+ try {
259
+ const result = await executeGhCommand([
260
+ "api",
261
+ "graphql",
262
+ "-H",
263
+ "GraphQL-Features: sub_issues",
264
+ "-f",
265
+ `query=${query}`,
266
+ "-F",
267
+ `parentId=${parentNodeId}`
268
+ ]);
269
+ const subIssues = ((_b = (_a = result.data.node) == null ? void 0 : _a.subIssues) == null ? void 0 : _b.nodes) ?? [];
270
+ return subIssues.map((issue) => ({
271
+ id: String(issue.number),
272
+ title: issue.title,
273
+ url: issue.url,
274
+ state: issue.state.toLowerCase()
275
+ }));
276
+ } catch (error) {
277
+ if (error instanceof Error) {
278
+ const errorMessage = error.message;
279
+ const stderr = "stderr" in error ? error.stderr ?? "" : "";
280
+ const combinedError = `${errorMessage} ${stderr}`;
281
+ if (combinedError.includes("sub_issues") || combinedError.includes("null")) {
282
+ return [];
283
+ }
284
+ }
285
+ throw error;
286
+ }
287
+ }
288
+ async function getIssueDatabaseId(issueNumber, repo) {
289
+ logger.debug("Fetching GitHub issue database ID", { issueNumber, repo });
290
+ const apiPath = repo ? `repos/${repo}/issues/${issueNumber}` : `repos/:owner/:repo/issues/${issueNumber}`;
291
+ const result = await executeGhCommand([
292
+ "api",
293
+ apiPath,
294
+ "--jq",
295
+ "{id: .id}"
296
+ ]);
297
+ return result.id;
298
+ }
299
+ async function getIssueDependencies(issueNumber, direction, repo) {
300
+ logger.debug("Fetching GitHub issue dependencies", { issueNumber, direction, repo });
301
+ const apiPath = repo ? `repos/${repo}/issues/${issueNumber}/dependencies/${direction}` : `repos/:owner/:repo/issues/${issueNumber}/dependencies/${direction}`;
302
+ try {
303
+ const result = await executeGhCommand([
304
+ "api",
305
+ "-H",
306
+ "Accept: application/vnd.github+json",
307
+ "-H",
308
+ "X-GitHub-Api-Version: 2022-11-28",
309
+ "--jq",
310
+ ".",
311
+ apiPath
312
+ ]);
313
+ return (result ?? []).map((dep) => ({
314
+ id: String(dep.number),
315
+ databaseId: dep.id,
316
+ title: dep.title,
317
+ url: dep.html_url,
318
+ state: dep.state
319
+ }));
320
+ } catch (error) {
321
+ if (error instanceof Error) {
322
+ const errorMessage = error.message;
323
+ const stderr = "stderr" in error ? error.stderr ?? "" : "";
324
+ const combinedError = `${errorMessage} ${stderr}`;
325
+ if (combinedError.includes("404") && combinedError.includes("dependencies")) {
326
+ return [];
327
+ }
328
+ }
329
+ throw error;
330
+ }
331
+ }
332
+ async function createIssueDependency(blockedIssueNumber, blockingIssueDatabaseId, repo) {
333
+ logger.debug("Creating GitHub issue dependency", { blockedIssueNumber, blockingIssueDatabaseId, repo });
334
+ const apiPath = repo ? `repos/${repo}/issues/${blockedIssueNumber}/dependencies/blocked_by` : `repos/:owner/:repo/issues/${blockedIssueNumber}/dependencies/blocked_by`;
335
+ try {
336
+ await executeGhCommand([
337
+ "api",
338
+ "-X",
339
+ "POST",
340
+ "-H",
341
+ "Accept: application/vnd.github+json",
342
+ "-H",
343
+ "X-GitHub-Api-Version: 2022-11-28",
344
+ apiPath,
345
+ "-F",
346
+ `issue_id=${blockingIssueDatabaseId}`
347
+ ]);
348
+ } catch (error) {
349
+ if (error instanceof Error) {
350
+ const errorMessage = error.message;
351
+ const stderr = "stderr" in error ? error.stderr ?? "" : "";
352
+ const combinedError = `${errorMessage} ${stderr}`;
353
+ if (combinedError.includes("422") || combinedError.includes("already exists") || combinedError.includes("Unprocessable Entity")) {
354
+ throw new Error(`Dependency already exists: issue #${blockedIssueNumber} is already blocked by the specified issue`);
355
+ }
356
+ if (combinedError.includes("404") || combinedError.includes("Not Found")) {
357
+ throw new Error(`Issue not found: unable to create dependency for issue #${blockedIssueNumber}. The issue may not exist or you may not have access to it.`);
358
+ }
359
+ if (combinedError.includes("403") || combinedError.includes("Forbidden") || combinedError.includes("not enabled")) {
360
+ throw new Error(`Dependencies feature not enabled: the repository may not have issue dependencies enabled. This feature requires GitHub Enterprise or specific repository settings.`);
361
+ }
362
+ }
363
+ throw error;
364
+ }
365
+ }
366
+ async function removeIssueDependency(blockedIssueNumber, blockingIssueDatabaseId, repo) {
367
+ logger.debug("Removing GitHub issue dependency", { blockedIssueNumber, blockingIssueDatabaseId, repo });
368
+ const apiPath = repo ? `repos/${repo}/issues/${blockedIssueNumber}/dependencies/blocked_by/${blockingIssueDatabaseId}` : `repos/:owner/:repo/issues/${blockedIssueNumber}/dependencies/blocked_by/${blockingIssueDatabaseId}`;
369
+ await executeGhCommand([
370
+ "api",
371
+ "-X",
372
+ "DELETE",
373
+ "-H",
374
+ "Accept: application/vnd.github+json",
375
+ "-H",
376
+ "X-GitHub-Api-Version: 2022-11-28",
377
+ apiPath
378
+ ]);
379
+ }
380
+
381
+ // src/utils/image-processor.ts
382
+ import { tmpdir } from "os";
383
+ import { join, extname } from "path";
384
+ import { existsSync as existsSync2, mkdirSync, createWriteStream, unlinkSync } from "fs";
385
+ import { pipeline } from "stream/promises";
386
+ import { Readable } from "stream";
387
+ import { createHash } from "crypto";
388
+ import { execa as execa3 } from "execa";
389
+ var SUPPORTED_EXTENSIONS = [".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"];
390
+ var MAX_IMAGE_SIZE = 10 * 1024 * 1024;
391
+ var REQUEST_TIMEOUT_MS = 3e4;
392
+ var CACHE_DIR = join(tmpdir(), "iloom-images");
393
+ var cachedGitHubToken;
394
+ function extractMarkdownImageUrls(content) {
395
+ if (!content) {
396
+ return [];
397
+ }
398
+ const matches = [];
399
+ const markdownRegex = /!\[([^\]]*)\]\(((?:[^()\s]|\((?:[^()\s]|\([^()]*\))*\))+)\)/g;
400
+ let match;
401
+ while ((match = markdownRegex.exec(content)) !== null) {
402
+ const url = match[2];
403
+ if (url) {
404
+ matches.push({
405
+ fullMatch: match[0],
406
+ url,
407
+ isMarkdown: true
408
+ });
409
+ }
410
+ }
411
+ const htmlImgRegex = /<img\s+[^>]*src=["']([^"']+)["'][^>]*\/?>/gi;
412
+ while ((match = htmlImgRegex.exec(content)) !== null) {
413
+ const url = match[1];
414
+ if (url) {
415
+ matches.push({
416
+ fullMatch: match[0],
417
+ url,
418
+ isMarkdown: false
419
+ });
420
+ }
421
+ }
422
+ return matches;
423
+ }
424
+ function isAuthenticatedImageUrl(url) {
425
+ try {
426
+ const parsedUrl = new URL(url);
427
+ const hostname = parsedUrl.hostname.toLowerCase();
428
+ if (hostname === "uploads.linear.app") {
429
+ return true;
430
+ }
431
+ if (hostname === "private-user-images.githubusercontent.com") {
432
+ return true;
433
+ }
434
+ if (hostname === "github.com" && parsedUrl.pathname.startsWith("/user-attachments/assets/")) {
435
+ return true;
436
+ }
437
+ return false;
438
+ } catch {
439
+ return false;
440
+ }
441
+ }
442
+ function getExtensionFromUrl(url) {
443
+ try {
444
+ const parsedUrl = new URL(url);
445
+ const pathname = parsedUrl.pathname;
446
+ const ext = extname(pathname).toLowerCase();
447
+ if (SUPPORTED_EXTENSIONS.includes(ext)) {
448
+ return ext;
449
+ }
450
+ return null;
451
+ } catch {
452
+ return null;
453
+ }
454
+ }
455
+ function getCacheKey(url) {
456
+ const parsedUrl = new URL(url);
457
+ if (parsedUrl.hostname === "private-user-images.githubusercontent.com") {
458
+ parsedUrl.searchParams.delete("jwt");
459
+ }
460
+ const stableUrl = parsedUrl.toString();
461
+ const hash = createHash("sha256").update(stableUrl).digest("hex").slice(0, 16);
462
+ const ext = getExtensionFromUrl(url) ?? ".png";
463
+ return `${hash}${ext}`;
464
+ }
465
+ function getCachedImagePath(url) {
466
+ const cacheKey = getCacheKey(url);
467
+ const cachedPath = join(CACHE_DIR, cacheKey);
468
+ if (existsSync2(cachedPath)) {
469
+ return cachedPath;
470
+ }
471
+ return void 0;
472
+ }
473
+ async function getAuthToken(provider) {
474
+ if (provider === "github") {
475
+ if (cachedGitHubToken !== void 0) {
476
+ return cachedGitHubToken;
477
+ }
478
+ try {
479
+ const result = await execa3("gh", ["auth", "token"]);
480
+ cachedGitHubToken = result.stdout.trim();
481
+ return cachedGitHubToken;
482
+ } catch (error) {
483
+ const message = error instanceof Error ? error.message : String(error);
484
+ logger.warn(`Failed to get GitHub auth token via gh CLI: ${message}`);
485
+ return void 0;
486
+ }
487
+ }
488
+ if (provider === "linear") {
489
+ return process.env.LINEAR_API_TOKEN;
490
+ }
491
+ return void 0;
492
+ }
493
+ async function downloadAndSaveImage(url, destPath, authHeader) {
494
+ const headers = {};
495
+ if (authHeader) {
496
+ headers["Authorization"] = authHeader;
497
+ }
498
+ const controller = new AbortController();
499
+ const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
500
+ try {
501
+ const response = await fetch(url, { headers, signal: controller.signal });
502
+ if (!response.ok) {
503
+ throw new Error(`Failed to download image: ${response.status} ${response.statusText}`);
504
+ }
505
+ const contentLength = response.headers.get("Content-Length");
506
+ if (contentLength && parseInt(contentLength, 10) > MAX_IMAGE_SIZE) {
507
+ throw new Error(`Image too large: ${contentLength} bytes exceeds ${MAX_IMAGE_SIZE} byte limit`);
508
+ }
509
+ if (!response.body) {
510
+ throw new Error("Response body is null");
511
+ }
512
+ const reader = response.body.getReader();
513
+ let bytesWritten = 0;
514
+ const nodeReadable = new Readable({
515
+ async read() {
516
+ try {
517
+ const { done, value } = await reader.read();
518
+ if (done) {
519
+ this.push(null);
520
+ return;
521
+ }
522
+ bytesWritten += value.byteLength;
523
+ if (bytesWritten > MAX_IMAGE_SIZE) {
524
+ reader.cancel();
525
+ this.destroy(new Error(`Image too large: ${bytesWritten} bytes exceeds ${MAX_IMAGE_SIZE} byte limit`));
526
+ return;
527
+ }
528
+ this.push(Buffer.from(value));
529
+ } catch (err) {
530
+ this.destroy(err instanceof Error ? err : new Error(String(err)));
531
+ }
532
+ }
533
+ });
534
+ if (!existsSync2(CACHE_DIR)) {
535
+ mkdirSync(CACHE_DIR, { recursive: true });
536
+ }
537
+ const writeStream = createWriteStream(destPath);
538
+ try {
539
+ await pipeline(nodeReadable, writeStream);
540
+ } catch (pipelineError) {
541
+ try {
542
+ if (existsSync2(destPath)) {
543
+ unlinkSync(destPath);
544
+ }
545
+ } catch {
546
+ }
547
+ throw pipelineError;
548
+ }
549
+ } catch (error) {
550
+ if (error instanceof Error && error.name === "AbortError") {
551
+ throw new Error(`Image download timed out after ${REQUEST_TIMEOUT_MS}ms`);
552
+ }
553
+ throw error;
554
+ } finally {
555
+ clearTimeout(timeoutId);
556
+ }
557
+ }
558
+ function getCacheDestPath(url) {
559
+ if (!existsSync2(CACHE_DIR)) {
560
+ mkdirSync(CACHE_DIR, { recursive: true });
561
+ }
562
+ const cacheKey = getCacheKey(url);
563
+ return join(CACHE_DIR, cacheKey);
564
+ }
565
+ function rewriteMarkdownUrls(content, urlMap) {
566
+ let result = content;
567
+ for (const [originalUrl, localPath] of urlMap) {
568
+ const escapedUrl = originalUrl.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
569
+ const urlRegex = new RegExp(escapedUrl, "g");
570
+ result = result.replace(urlRegex, localPath);
571
+ }
572
+ return result;
573
+ }
574
+ async function processMarkdownImages(content, provider) {
575
+ if (!content) {
576
+ return "";
577
+ }
578
+ const images = extractMarkdownImageUrls(content);
579
+ if (images.length === 0) {
580
+ return content;
581
+ }
582
+ const authImages = images.filter((img) => isAuthenticatedImageUrl(img.url));
583
+ if (authImages.length === 0) {
584
+ return content;
585
+ }
586
+ const authToken = await getAuthToken(provider);
587
+ const uniqueUrls = [...new Set(authImages.map((img) => img.url))];
588
+ const urlMap = /* @__PURE__ */ new Map();
589
+ const downloadPromises = uniqueUrls.map(async (url) => {
590
+ try {
591
+ const cachedPath = getCachedImagePath(url);
592
+ if (cachedPath) {
593
+ logger.debug(`Using cached image: ${cachedPath}`);
594
+ return { url, localPath: cachedPath };
595
+ }
596
+ logger.debug(`Downloading image: ${url}`);
597
+ const destPath = getCacheDestPath(url);
598
+ await downloadAndSaveImage(
599
+ url,
600
+ destPath,
601
+ authToken ? `Bearer ${authToken}` : void 0
602
+ );
603
+ return { url, localPath: destPath };
604
+ } catch (error) {
605
+ const message = error instanceof Error ? error.message : String(error);
606
+ logger.warn(`Failed to download image ${url}: ${message}`);
607
+ return null;
608
+ }
609
+ });
610
+ const results = await Promise.all(downloadPromises);
611
+ for (const result of results) {
612
+ if (result !== null) {
613
+ urlMap.set(result.url, result.localPath);
614
+ }
615
+ }
616
+ return rewriteMarkdownUrls(content, urlMap);
617
+ }
238
618
 
239
619
  // src/mcp/GitHubIssueManagementProvider.ts
240
620
  function normalizeAuthor(author) {
@@ -314,6 +694,12 @@ var GitHubIssueManagementProvider = class {
314
694
  ...comment.updatedAt && { updatedAt: comment.updatedAt }
315
695
  }));
316
696
  }
697
+ result.body = await processMarkdownImages(result.body, "github");
698
+ if (result.comments) {
699
+ for (const comment of result.comments) {
700
+ comment.body = await processMarkdownImages(comment.body, "github");
701
+ }
702
+ }
317
703
  return result;
318
704
  }
319
705
  /**
@@ -382,6 +768,12 @@ var GitHubIssueManagementProvider = class {
382
768
  ...comment.updatedAt && { updatedAt: comment.updatedAt }
383
769
  }));
384
770
  }
771
+ result.body = await processMarkdownImages(result.body, "github");
772
+ if (result.comments) {
773
+ for (const comment of result.comments) {
774
+ comment.body = await processMarkdownImages(comment.body, "github");
775
+ }
776
+ }
385
777
  return result;
386
778
  }
387
779
  /**
@@ -401,9 +793,10 @@ var GitHubIssueManagementProvider = class {
401
793
  "--jq",
402
794
  "{id: .id, body: .body, user: .user, created_at: .created_at, updated_at: .updated_at, html_url: .html_url, reactions: .reactions}"
403
795
  ]);
796
+ const processedBody = await processMarkdownImages(raw.body, "github");
404
797
  return {
405
798
  id: String(raw.id),
406
- body: raw.body,
799
+ body: processedBody,
407
800
  author: normalizeAuthor(raw.user),
408
801
  created_at: raw.created_at,
409
802
  ...raw.updated_at && { updated_at: raw.updated_at },
@@ -476,10 +869,76 @@ var GitHubIssueManagementProvider = class {
476
869
  number: childNumber
477
870
  };
478
871
  }
872
+ /**
873
+ * Create a blocking dependency between two issues (A blocks B)
874
+ * Uses GitHub's sub-issues API: blocking issue becomes parent, blocked issue becomes sub-issue
875
+ */
876
+ async createDependency(input) {
877
+ const { blockingIssue, blockedIssue, repo } = input;
878
+ const blockingNumber = parseInt(blockingIssue, 10);
879
+ if (isNaN(blockingNumber)) {
880
+ throw new Error(`Invalid GitHub issue number: ${blockingIssue}. GitHub issue IDs must be numeric.`);
881
+ }
882
+ const blockedNumber = parseInt(blockedIssue, 10);
883
+ if (isNaN(blockedNumber)) {
884
+ throw new Error(`Invalid GitHub issue number: ${blockedIssue}. GitHub issue IDs must be numeric.`);
885
+ }
886
+ const blockingDatabaseId = await getIssueDatabaseId(blockingNumber, repo);
887
+ await createIssueDependency(blockedNumber, blockingDatabaseId, repo);
888
+ }
889
+ /**
890
+ * Get dependencies for an issue
891
+ */
892
+ async getDependencies(input) {
893
+ const { number, direction, repo } = input;
894
+ const issueNumber = parseInt(number, 10);
895
+ if (isNaN(issueNumber)) {
896
+ throw new Error(`Invalid GitHub issue number: ${number}. GitHub issue IDs must be numeric.`);
897
+ }
898
+ const result = {
899
+ blocking: [],
900
+ blockedBy: []
901
+ };
902
+ if (direction === "blocking" || direction === "both") {
903
+ result.blocking = await getIssueDependencies(issueNumber, "blocking", repo);
904
+ }
905
+ if (direction === "blocked_by" || direction === "both") {
906
+ result.blockedBy = await getIssueDependencies(issueNumber, "blocked_by", repo);
907
+ }
908
+ return result;
909
+ }
910
+ /**
911
+ * Remove a blocking dependency between two issues (A blocks B)
912
+ * Uses GitHub's sub-issues API: blocking issue is parent, blocked issue is sub-issue
913
+ */
914
+ async removeDependency(input) {
915
+ const { blockingIssue, blockedIssue, repo } = input;
916
+ const blockingNumber = parseInt(blockingIssue, 10);
917
+ if (isNaN(blockingNumber)) {
918
+ throw new Error(`Invalid GitHub issue number: ${blockingIssue}. GitHub issue IDs must be numeric.`);
919
+ }
920
+ const blockedNumber = parseInt(blockedIssue, 10);
921
+ if (isNaN(blockedNumber)) {
922
+ throw new Error(`Invalid GitHub issue number: ${blockedIssue}. GitHub issue IDs must be numeric.`);
923
+ }
924
+ const blockingDatabaseId = await getIssueDatabaseId(blockingNumber, repo);
925
+ await removeIssueDependency(blockedNumber, blockingDatabaseId, repo);
926
+ }
927
+ /**
928
+ * Get child issues (sub-issues) of a parent issue
929
+ */
930
+ async getChildIssues(input) {
931
+ const { number, repo } = input;
932
+ const issueNumber = parseInt(number, 10);
933
+ if (isNaN(issueNumber)) {
934
+ throw new Error(`Invalid GitHub issue number: ${number}. GitHub issue IDs must be numeric.`);
935
+ }
936
+ return await getSubIssues(issueNumber, repo);
937
+ }
479
938
  };
480
939
 
481
940
  // src/utils/linear.ts
482
- import { LinearClient } from "@linear/sdk";
941
+ import { LinearClient, IssueRelationType } from "@linear/sdk";
483
942
 
484
943
  // src/types/linear.ts
485
944
  var LinearServiceError = class _LinearServiceError extends Error {
@@ -749,10 +1208,161 @@ async function fetchLinearIssueComments(identifier) {
749
1208
  handleLinearError(error, "fetchLinearIssueComments");
750
1209
  }
751
1210
  }
1211
+ async function getLinearChildIssues(identifier) {
1212
+ try {
1213
+ logger.debug(`Fetching child issues for Linear issue: ${identifier}`);
1214
+ const client = createLinearClient();
1215
+ const issue = await client.issue(identifier);
1216
+ if (!issue) {
1217
+ throw new LinearServiceError("NOT_FOUND", `Linear issue ${identifier} not found`);
1218
+ }
1219
+ const children = await issue.children({ first: 100 });
1220
+ const results = await Promise.all(
1221
+ children.nodes.map(async (child) => {
1222
+ const stateObj = await child.state;
1223
+ const state = (stateObj == null ? void 0 : stateObj.name) ?? "unknown";
1224
+ return {
1225
+ id: child.identifier,
1226
+ title: child.title,
1227
+ url: child.url,
1228
+ state
1229
+ };
1230
+ })
1231
+ );
1232
+ return results;
1233
+ } catch (error) {
1234
+ if (error instanceof LinearServiceError) {
1235
+ throw error;
1236
+ }
1237
+ handleLinearError(error, "getLinearChildIssues");
1238
+ }
1239
+ }
1240
+ async function createLinearIssueRelation(blockingIssueId, blockedIssueId) {
1241
+ try {
1242
+ logger.debug(`Creating Linear issue relation: ${blockingIssueId} blocks ${blockedIssueId}`);
1243
+ const client = createLinearClient();
1244
+ const payload = await client.createIssueRelation({
1245
+ issueId: blockingIssueId,
1246
+ relatedIssueId: blockedIssueId,
1247
+ type: IssueRelationType.Blocks
1248
+ });
1249
+ if (!payload.success) {
1250
+ throw new LinearServiceError("CLI_ERROR", "Failed to create Linear issue relation");
1251
+ }
1252
+ } catch (error) {
1253
+ if (error instanceof LinearServiceError) {
1254
+ throw error;
1255
+ }
1256
+ handleLinearError(error, "createLinearIssueRelation");
1257
+ }
1258
+ }
1259
+ async function getLinearIssueDependencies(identifier, direction) {
1260
+ try {
1261
+ logger.debug(`Fetching Linear issue dependencies: ${identifier} (direction: ${direction})`);
1262
+ const client = createLinearClient();
1263
+ const issue = await client.issue(identifier);
1264
+ if (!issue) {
1265
+ throw new LinearServiceError("NOT_FOUND", `Linear issue ${identifier} not found`);
1266
+ }
1267
+ const [relations, inverseRelations] = await Promise.all([
1268
+ issue.relations(),
1269
+ issue.inverseRelations()
1270
+ ]);
1271
+ const blocking = [];
1272
+ const blockedBy = [];
1273
+ const buildDependencyResult = async (relatedIssue) => {
1274
+ if (!relatedIssue) return null;
1275
+ const stateObj = await relatedIssue.state;
1276
+ const state = (stateObj == null ? void 0 : stateObj.name) ?? "unknown";
1277
+ return {
1278
+ id: relatedIssue.identifier,
1279
+ title: relatedIssue.title,
1280
+ url: relatedIssue.url,
1281
+ state
1282
+ };
1283
+ };
1284
+ if (direction === "blocking" || direction === "both") {
1285
+ const blockingRelations = relations.nodes.filter(
1286
+ (r) => r.type === IssueRelationType.Blocks
1287
+ );
1288
+ const relatedIssuePromises = blockingRelations.map((r) => r.relatedIssue).filter((p) => p !== void 0);
1289
+ const relatedIssues = await Promise.all(relatedIssuePromises);
1290
+ const blockingResults = await Promise.all(
1291
+ relatedIssues.map((issue2) => buildDependencyResult(issue2))
1292
+ );
1293
+ blocking.push(...blockingResults.filter((r) => r !== null));
1294
+ }
1295
+ if (direction === "blocked_by" || direction === "both") {
1296
+ const blockedByRelations = inverseRelations.nodes.filter(
1297
+ (r) => r.type === IssueRelationType.Blocks
1298
+ );
1299
+ const sourceIssuePromises = blockedByRelations.map((r) => r.issue).filter((p) => p !== void 0);
1300
+ const sourceIssues = await Promise.all(sourceIssuePromises);
1301
+ const blockedByResults = await Promise.all(
1302
+ sourceIssues.map((issue2) => buildDependencyResult(issue2))
1303
+ );
1304
+ blockedBy.push(...blockedByResults.filter((r) => r !== null));
1305
+ }
1306
+ return { blocking, blockedBy };
1307
+ } catch (error) {
1308
+ if (error instanceof LinearServiceError) {
1309
+ throw error;
1310
+ }
1311
+ handleLinearError(error, "getLinearIssueDependencies");
1312
+ }
1313
+ }
1314
+ async function deleteLinearIssueRelation(relationId) {
1315
+ try {
1316
+ logger.debug(`Deleting Linear issue relation: ${relationId}`);
1317
+ const client = createLinearClient();
1318
+ const payload = await client.deleteIssueRelation(relationId);
1319
+ if (!payload.success) {
1320
+ throw new LinearServiceError("CLI_ERROR", "Failed to delete Linear issue relation");
1321
+ }
1322
+ } catch (error) {
1323
+ if (error instanceof LinearServiceError) {
1324
+ throw error;
1325
+ }
1326
+ handleLinearError(error, "deleteLinearIssueRelation");
1327
+ }
1328
+ }
1329
+ async function findLinearIssueRelation(blockingIdentifier, blockedIdentifier) {
1330
+ try {
1331
+ logger.debug(`Finding Linear issue relation: ${blockingIdentifier} blocks ${blockedIdentifier}`);
1332
+ const client = createLinearClient();
1333
+ const blockingIssue = await client.issue(blockingIdentifier);
1334
+ if (!blockingIssue) {
1335
+ throw new LinearServiceError("NOT_FOUND", `Linear issue ${blockingIdentifier} not found`);
1336
+ }
1337
+ const blockedIssue = await client.issue(blockedIdentifier);
1338
+ if (!blockedIssue) {
1339
+ throw new LinearServiceError("NOT_FOUND", `Linear issue ${blockedIdentifier} not found`);
1340
+ }
1341
+ const relations = await blockingIssue.relations();
1342
+ const blockingRelations = relations.nodes.filter(
1343
+ (r) => r.type === IssueRelationType.Blocks
1344
+ );
1345
+ const relationsWithIssues = await Promise.all(
1346
+ blockingRelations.map(async (relation) => ({
1347
+ relation,
1348
+ relatedIssue: await relation.relatedIssue
1349
+ }))
1350
+ );
1351
+ const matchingRelation = relationsWithIssues.find(
1352
+ ({ relatedIssue }) => (relatedIssue == null ? void 0 : relatedIssue.id) === blockedIssue.id
1353
+ );
1354
+ return (matchingRelation == null ? void 0 : matchingRelation.relation.id) ?? null;
1355
+ } catch (error) {
1356
+ if (error instanceof LinearServiceError) {
1357
+ throw error;
1358
+ }
1359
+ handleLinearError(error, "findLinearIssueRelation");
1360
+ }
1361
+ }
752
1362
 
753
1363
  // src/utils/linear-markup-converter.ts
754
1364
  import { appendFileSync } from "fs";
755
- import { join, dirname, basename, extname } from "path";
1365
+ import { join as join2, dirname, basename, extname as extname2 } from "path";
756
1366
  var LinearMarkupConverter = class {
757
1367
  /**
758
1368
  * Convert HTML details/summary blocks to Linear's collapsible format
@@ -888,7 +1498,7 @@ ${content}
888
1498
  */
889
1499
  static getTimestampedLogPath(logFilePath) {
890
1500
  const dir = dirname(logFilePath);
891
- const ext = extname(logFilePath);
1501
+ const ext = extname2(logFilePath);
892
1502
  const base = basename(logFilePath, ext);
893
1503
  const now = /* @__PURE__ */ new Date();
894
1504
  const timestamp = [
@@ -900,7 +1510,7 @@ ${content}
900
1510
  String(now.getMinutes()).padStart(2, "0"),
901
1511
  String(now.getSeconds()).padStart(2, "0")
902
1512
  ].join("");
903
- return join(dir, `${base}-${timestamp}${ext}`);
1513
+ return join2(dir, `${base}-${timestamp}${ext}`);
904
1514
  }
905
1515
  };
906
1516
 
@@ -949,6 +1559,12 @@ var LinearIssueManagementProvider = class {
949
1559
  } catch {
950
1560
  }
951
1561
  }
1562
+ result.body = await processMarkdownImages(result.body, "linear");
1563
+ if (result.comments) {
1564
+ for (const comment of result.comments) {
1565
+ comment.body = await processMarkdownImages(comment.body, "linear");
1566
+ }
1567
+ }
952
1568
  return result;
953
1569
  }
954
1570
  /**
@@ -982,9 +1598,10 @@ var LinearIssueManagementProvider = class {
982
1598
  async getComment(input) {
983
1599
  const { commentId } = input;
984
1600
  const raw = await getLinearComment(commentId);
1601
+ const processedBody = await processMarkdownImages(raw.body, "linear");
985
1602
  return {
986
1603
  id: raw.id,
987
- body: raw.body,
1604
+ body: processedBody,
988
1605
  author: null,
989
1606
  // Linear SDK doesn't return comment author info in basic fetch
990
1607
  created_at: raw.createdAt
@@ -1057,6 +1674,42 @@ var LinearIssueManagementProvider = class {
1057
1674
  url: result.url
1058
1675
  };
1059
1676
  }
1677
+ /**
1678
+ * Create a blocking dependency between two issues
1679
+ */
1680
+ async createDependency(input) {
1681
+ const { blockingIssue, blockedIssue } = input;
1682
+ const [blockingIssueData, blockedIssueData] = await Promise.all([
1683
+ fetchLinearIssue(blockingIssue),
1684
+ fetchLinearIssue(blockedIssue)
1685
+ ]);
1686
+ await createLinearIssueRelation(blockingIssueData.id, blockedIssueData.id);
1687
+ }
1688
+ /**
1689
+ * Get dependencies for an issue
1690
+ */
1691
+ async getDependencies(input) {
1692
+ const { number, direction } = input;
1693
+ return await getLinearIssueDependencies(number, direction);
1694
+ }
1695
+ /**
1696
+ * Remove a blocking dependency between two issues
1697
+ */
1698
+ async removeDependency(input) {
1699
+ const { blockingIssue, blockedIssue } = input;
1700
+ const relationId = await findLinearIssueRelation(blockingIssue, blockedIssue);
1701
+ if (!relationId) {
1702
+ throw new Error(`No blocking dependency found from ${blockingIssue} to ${blockedIssue}`);
1703
+ }
1704
+ await deleteLinearIssueRelation(relationId);
1705
+ }
1706
+ /**
1707
+ * Get child issues of a parent issue
1708
+ */
1709
+ async getChildIssues(input) {
1710
+ const { number } = input;
1711
+ return await getLinearChildIssues(number);
1712
+ }
1060
1713
  };
1061
1714
 
1062
1715
  // src/mcp/IssueManagementProviderFactory.ts
@@ -1471,6 +2124,178 @@ server.registerTool(
1471
2124
  }
1472
2125
  }
1473
2126
  );
2127
+ var dependencyResultSchema = z.object({
2128
+ id: z.string().describe("Issue identifier"),
2129
+ title: z.string().describe("Issue title"),
2130
+ url: z.string().describe("Issue URL"),
2131
+ state: z.string().describe("Issue state")
2132
+ });
2133
+ server.registerTool(
2134
+ "create_dependency",
2135
+ {
2136
+ title: "Create Dependency",
2137
+ description: 'Create a blocking dependency between two issues. The blockingIssue will block the blockedIssue. For GitHub: uses the sub-issue API. For Linear: creates a "blocks" relation.',
2138
+ inputSchema: {
2139
+ blockingIssue: z.string().describe('The issue that blocks (GitHub issue number or Linear identifier like "ENG-123")'),
2140
+ blockedIssue: z.string().describe('The issue being blocked (GitHub issue number or Linear identifier like "ENG-123")'),
2141
+ repo: z.string().optional().describe(
2142
+ 'Optional repository in "owner/repo" format or full GitHub URL. When not provided, uses the current repository. GitHub only.'
2143
+ )
2144
+ },
2145
+ outputSchema: {
2146
+ success: z.boolean().describe("Whether the dependency was created successfully")
2147
+ }
2148
+ },
2149
+ async ({ blockingIssue, blockedIssue, repo }) => {
2150
+ console.error(`Creating dependency: ${blockingIssue} blocks ${blockedIssue}${repo ? ` in ${repo}` : ""}`);
2151
+ try {
2152
+ const provider = IssueManagementProviderFactory.create(
2153
+ process.env.ISSUE_PROVIDER
2154
+ );
2155
+ await provider.createDependency({ blockingIssue, blockedIssue, repo });
2156
+ console.error(`Dependency created successfully: ${blockingIssue} -> ${blockedIssue}`);
2157
+ return {
2158
+ content: [
2159
+ {
2160
+ type: "text",
2161
+ text: JSON.stringify({ success: true })
2162
+ }
2163
+ ],
2164
+ structuredContent: { success: true }
2165
+ };
2166
+ } catch (error) {
2167
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
2168
+ console.error(`Failed to create dependency: ${errorMessage}`);
2169
+ throw new Error(`Failed to create dependency: ${errorMessage}`);
2170
+ }
2171
+ }
2172
+ );
2173
+ server.registerTool(
2174
+ "get_dependencies",
2175
+ {
2176
+ title: "Get Dependencies",
2177
+ description: "Get blocking/blocked_by dependencies for an issue. Returns lists of issues that this issue blocks and/or is blocked by.",
2178
+ inputSchema: {
2179
+ number: z.string().describe('Issue identifier (GitHub issue number or Linear identifier like "ENG-123")'),
2180
+ direction: z.enum(["blocking", "blocked_by", "both"]).describe('Which dependencies to fetch: "blocking" for issues this blocks, "blocked_by" for issues blocking this, "both" for all'),
2181
+ repo: z.string().optional().describe(
2182
+ 'Optional repository in "owner/repo" format or full GitHub URL. When not provided, uses the current repository. GitHub only.'
2183
+ )
2184
+ },
2185
+ outputSchema: {
2186
+ blocking: z.array(dependencyResultSchema).describe("Issues that this issue blocks"),
2187
+ blockedBy: z.array(dependencyResultSchema).describe("Issues that block this issue")
2188
+ }
2189
+ },
2190
+ async ({ number, direction, repo }) => {
2191
+ console.error(`Getting dependencies for ${number} (direction: ${direction})${repo ? ` in ${repo}` : ""}`);
2192
+ try {
2193
+ const provider = IssueManagementProviderFactory.create(
2194
+ process.env.ISSUE_PROVIDER
2195
+ );
2196
+ const result = await provider.getDependencies({ number, direction, repo });
2197
+ console.error(`Dependencies fetched: ${result.blocking.length} blocking, ${result.blockedBy.length} blocked_by`);
2198
+ return {
2199
+ content: [
2200
+ {
2201
+ type: "text",
2202
+ text: JSON.stringify(result)
2203
+ }
2204
+ ],
2205
+ structuredContent: result
2206
+ };
2207
+ } catch (error) {
2208
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
2209
+ console.error(`Failed to get dependencies: ${errorMessage}`);
2210
+ throw new Error(`Failed to get dependencies: ${errorMessage}`);
2211
+ }
2212
+ }
2213
+ );
2214
+ server.registerTool(
2215
+ "remove_dependency",
2216
+ {
2217
+ title: "Remove Dependency",
2218
+ description: "Remove a blocking dependency between two issues. The blockingIssue will no longer block the blockedIssue.",
2219
+ inputSchema: {
2220
+ blockingIssue: z.string().describe('The issue that blocks (GitHub issue number or Linear identifier like "ENG-123")'),
2221
+ blockedIssue: z.string().describe('The issue being blocked (GitHub issue number or Linear identifier like "ENG-123")'),
2222
+ repo: z.string().optional().describe(
2223
+ 'Optional repository in "owner/repo" format or full GitHub URL. When not provided, uses the current repository. GitHub only.'
2224
+ )
2225
+ },
2226
+ outputSchema: {
2227
+ success: z.boolean().describe("Whether the dependency was removed successfully")
2228
+ }
2229
+ },
2230
+ async ({ blockingIssue, blockedIssue, repo }) => {
2231
+ console.error(`Removing dependency: ${blockingIssue} blocks ${blockedIssue}${repo ? ` in ${repo}` : ""}`);
2232
+ try {
2233
+ const provider = IssueManagementProviderFactory.create(
2234
+ process.env.ISSUE_PROVIDER
2235
+ );
2236
+ await provider.removeDependency({ blockingIssue, blockedIssue, repo });
2237
+ console.error(`Dependency removed successfully: ${blockingIssue} -> ${blockedIssue}`);
2238
+ return {
2239
+ content: [
2240
+ {
2241
+ type: "text",
2242
+ text: JSON.stringify({ success: true })
2243
+ }
2244
+ ],
2245
+ structuredContent: { success: true }
2246
+ };
2247
+ } catch (error) {
2248
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
2249
+ console.error(`Failed to remove dependency: ${errorMessage}`);
2250
+ throw new Error(`Failed to remove dependency: ${errorMessage}`);
2251
+ }
2252
+ }
2253
+ );
2254
+ var childIssueResultSchema = z.object({
2255
+ id: z.string().describe("Issue identifier"),
2256
+ title: z.string().describe("Issue title"),
2257
+ url: z.string().describe("Issue URL"),
2258
+ state: z.string().describe("Issue state")
2259
+ });
2260
+ server.registerTool(
2261
+ "get_child_issues",
2262
+ {
2263
+ title: "Get Child Issues",
2264
+ description: "Get child issues (sub-issues) of a parent issue. Returns a list of issues that are children of the specified parent.",
2265
+ inputSchema: {
2266
+ number: z.string().describe('Parent issue identifier (GitHub issue number or Linear identifier like "ENG-123")'),
2267
+ repo: z.string().optional().describe(
2268
+ 'Optional repository in "owner/repo" format or full GitHub URL. When not provided, uses the current repository. GitHub only.'
2269
+ )
2270
+ },
2271
+ outputSchema: {
2272
+ children: z.array(childIssueResultSchema).describe("Child issues of the parent")
2273
+ }
2274
+ },
2275
+ async ({ number, repo }) => {
2276
+ console.error(`Getting child issues for ${number}${repo ? ` in ${repo}` : ""}`);
2277
+ try {
2278
+ const provider = IssueManagementProviderFactory.create(
2279
+ process.env.ISSUE_PROVIDER
2280
+ );
2281
+ const result = await provider.getChildIssues({ number, repo });
2282
+ console.error(`Child issues fetched: ${result.length} children`);
2283
+ return {
2284
+ content: [
2285
+ {
2286
+ type: "text",
2287
+ text: JSON.stringify({ children: result })
2288
+ }
2289
+ ],
2290
+ structuredContent: { children: result }
2291
+ };
2292
+ } catch (error) {
2293
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
2294
+ console.error(`Failed to get child issues: ${errorMessage}`);
2295
+ throw new Error(`Failed to get child issues: ${errorMessage}`);
2296
+ }
2297
+ }
2298
+ );
1474
2299
  async function main() {
1475
2300
  console.error("Starting Issue Management MCP Server...");
1476
2301
  const provider = validateEnvironment();