@iloom/cli 0.6.1 → 0.7.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 (177) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +35 -18
  3. package/dist/{BranchNamingService-B5PVRR7F.js → BranchNamingService-FLPUUFOB.js} +2 -2
  4. package/dist/ClaudeContextManager-KE5TBZVZ.js +14 -0
  5. package/dist/ClaudeService-CRSETT3A.js +13 -0
  6. package/dist/{GitHubService-S2OGUTDR.js → GitHubService-O7U4UQ7N.js} +3 -3
  7. package/dist/{LoomLauncher-5LFM4LXB.js → LoomLauncher-NL65LSKP.js} +6 -6
  8. package/dist/{MetadataManager-DFI73J3G.js → MetadataManager-XJ2YB762.js} +2 -2
  9. package/dist/PRManager-2ABCWXHW.js +16 -0
  10. package/dist/{ProjectCapabilityDetector-S5FLNCFI.js → ProjectCapabilityDetector-UZYW32SY.js} +3 -3
  11. package/dist/{PromptTemplateManager-C3DK6XZL.js → PromptTemplateManager-7L3HJQQU.js} +2 -2
  12. package/dist/README.md +35 -18
  13. package/dist/{SettingsManager-35F5RUJH.js → SettingsManager-YU4VYPTW.js} +2 -2
  14. package/dist/agents/iloom-issue-analyze-and-plan.md +42 -17
  15. package/dist/agents/iloom-issue-analyzer.md +14 -14
  16. package/dist/agents/iloom-issue-complexity-evaluator.md +38 -15
  17. package/dist/agents/iloom-issue-enhancer.md +15 -15
  18. package/dist/agents/iloom-issue-implementer.md +44 -15
  19. package/dist/agents/iloom-issue-planner.md +121 -17
  20. package/dist/agents/iloom-issue-reviewer.md +15 -15
  21. package/dist/{build-FJVYP7EV.js → build-O2EJHDEW.js} +9 -9
  22. package/dist/{chunk-ZPSTA5PR.js → chunk-3CDWFEGL.js} +2 -2
  23. package/dist/{chunk-VU3QMIP2.js → chunk-453NC377.js} +91 -15
  24. package/dist/chunk-453NC377.js.map +1 -0
  25. package/dist/{chunk-UQIXZ3BA.js → chunk-5V74K5ZA.js} +2 -2
  26. package/dist/{chunk-7WANFUIK.js → chunk-6TL3BYH6.js} +2 -2
  27. package/dist/{chunk-5TXLVEXT.js → chunk-C3AKFAIR.js} +2 -2
  28. package/dist/{chunk-K7SEEHKO.js → chunk-CNSTXBJ3.js} +7 -419
  29. package/dist/chunk-CNSTXBJ3.js.map +1 -0
  30. package/dist/{chunk-VDA5JMB4.js → chunk-EPPPDVHD.js} +21 -8
  31. package/dist/chunk-EPPPDVHD.js.map +1 -0
  32. package/dist/{chunk-LVBRMTE6.js → chunk-FEAJR6PN.js} +6 -6
  33. package/dist/{chunk-6YSFTPKW.js → chunk-FM4KBPVA.js} +18 -13
  34. package/dist/chunk-FM4KBPVA.js.map +1 -0
  35. package/dist/{chunk-AEIMYF4P.js → chunk-FP7G7DG3.js} +6 -2
  36. package/dist/chunk-FP7G7DG3.js.map +1 -0
  37. package/dist/{chunk-LT3SGBR7.js → chunk-GCPAZSGV.js} +36 -2
  38. package/dist/{chunk-LT3SGBR7.js.map → chunk-GCPAZSGV.js.map} +1 -1
  39. package/dist/chunk-GJMEKEI5.js +517 -0
  40. package/dist/chunk-GJMEKEI5.js.map +1 -0
  41. package/dist/{chunk-64O2UIWO.js → chunk-GV5X6XUE.js} +4 -4
  42. package/dist/{chunk-7Q66W4OH.js → chunk-HBJITKSZ.js} +37 -1
  43. package/dist/chunk-HBJITKSZ.js.map +1 -0
  44. package/dist/{chunk-7HIRPCKU.js → chunk-HVQNVRAF.js} +2 -2
  45. package/dist/{chunk-BXCPJJYM.js → chunk-ITN64ENQ.js} +1 -1
  46. package/dist/chunk-ITN64ENQ.js.map +1 -0
  47. package/dist/{chunk-6U6VI4SZ.js → chunk-KVS4XGBQ.js} +4 -4
  48. package/dist/{chunk-AXX3QIKK.js → chunk-LLWX3PCW.js} +2 -2
  49. package/dist/{chunk-2A7WQKBE.js → chunk-LQBLDI47.js} +96 -6
  50. package/dist/chunk-LQBLDI47.js.map +1 -0
  51. package/dist/{chunk-SN3Z6EZO.js → chunk-N7FVXZNI.js} +2 -2
  52. package/dist/chunk-NTIZLX42.js +822 -0
  53. package/dist/chunk-NTIZLX42.js.map +1 -0
  54. package/dist/{chunk-I75JMBNB.js → chunk-S7YMZQUD.js} +31 -43
  55. package/dist/chunk-S7YMZQUD.js.map +1 -0
  56. package/dist/chunk-TIYJEEVO.js +79 -0
  57. package/dist/chunk-TIYJEEVO.js.map +1 -0
  58. package/dist/{chunk-EK3XCAAS.js → chunk-UDRZY65Y.js} +2 -2
  59. package/dist/{chunk-3PT7RKL5.js → chunk-USJSNHGG.js} +2 -2
  60. package/dist/{chunk-CFUWQHCJ.js → chunk-VWGKGNJP.js} +114 -35
  61. package/dist/chunk-VWGKGNJP.js.map +1 -0
  62. package/dist/{chunk-F6WVM437.js → chunk-WFQ5CLTR.js} +6 -3
  63. package/dist/chunk-WFQ5CLTR.js.map +1 -0
  64. package/dist/{chunk-TRQ76ISK.js → chunk-Z6BO53V7.js} +9 -9
  65. package/dist/{chunk-GEXP5IOF.js → chunk-ZA575VLF.js} +21 -8
  66. package/dist/chunk-ZA575VLF.js.map +1 -0
  67. package/dist/{claude-H33OQMXO.js → claude-6H36IBHO.js} +4 -2
  68. package/dist/{cleanup-BRUAINKE.js → cleanup-ZPOMRSNN.js} +20 -16
  69. package/dist/cleanup-ZPOMRSNN.js.map +1 -0
  70. package/dist/cli.js +341 -954
  71. package/dist/cli.js.map +1 -1
  72. package/dist/commit-6S2RIA2K.js +237 -0
  73. package/dist/commit-6S2RIA2K.js.map +1 -0
  74. package/dist/{compile-ULNO5F7Q.js → compile-LRMAADUT.js} +9 -9
  75. package/dist/{contribute-Q6GX6AXK.js → contribute-GXKOIA42.js} +5 -5
  76. package/dist/{dev-server-4RCDJ5MU.js → dev-server-GREJUEKW.js} +22 -74
  77. package/dist/dev-server-GREJUEKW.js.map +1 -0
  78. package/dist/{feedback-O4Q55SVS.js → feedback-G7G5QCY4.js} +10 -10
  79. package/dist/{git-FVMGBHC2.js → git-ENLT2VNI.js} +6 -4
  80. package/dist/hooks/iloom-hook.js +30 -2
  81. package/dist/{ignite-VHV65WEZ.js → ignite-YUAOJ5PP.js} +20 -20
  82. package/dist/ignite-YUAOJ5PP.js.map +1 -0
  83. package/dist/index.d.ts +71 -27
  84. package/dist/index.js +196 -266
  85. package/dist/index.js.map +1 -1
  86. package/dist/init-XQQMFDM6.js +21 -0
  87. package/dist/{lint-5JMCWE4Y.js → lint-OFVN7FT6.js} +9 -9
  88. package/dist/mcp/issue-management-server.js +359 -13
  89. package/dist/mcp/issue-management-server.js.map +1 -1
  90. package/dist/mcp/recap-server.js +13 -4
  91. package/dist/mcp/recap-server.js.map +1 -1
  92. package/dist/{open-WHVUYGPY.js → open-MCWQAPSZ.js} +25 -76
  93. package/dist/open-MCWQAPSZ.js.map +1 -0
  94. package/dist/{projects-SA76I4TZ.js → projects-PQOTWUII.js} +11 -4
  95. package/dist/projects-PQOTWUII.js.map +1 -0
  96. package/dist/prompts/init-prompt.txt +62 -51
  97. package/dist/prompts/issue-prompt.txt +132 -63
  98. package/dist/prompts/pr-prompt.txt +3 -3
  99. package/dist/prompts/regular-prompt.txt +16 -18
  100. package/dist/prompts/session-summary-prompt.txt +13 -13
  101. package/dist/{rebase-Y4AS6LQW.js → rebase-RKQED567.js} +53 -8
  102. package/dist/rebase-RKQED567.js.map +1 -0
  103. package/dist/{recap-VOOUXOGP.js → recap-ZKGHZCX6.js} +6 -6
  104. package/dist/{run-NCRK5NPR.js → run-CCG24PBC.js} +25 -76
  105. package/dist/run-CCG24PBC.js.map +1 -0
  106. package/dist/schema/settings.schema.json +14 -3
  107. package/dist/{shell-SBLXVOVJ.js → shell-2NNSIU34.js} +6 -6
  108. package/dist/{summary-CVFAMDOJ.js → summary-G6L3VAKK.js} +11 -10
  109. package/dist/{summary-CVFAMDOJ.js.map → summary-G6L3VAKK.js.map} +1 -1
  110. package/dist/{test-3KIVXI6J.js → test-QZDOEUIO.js} +9 -9
  111. package/dist/{test-git-ZB6AGGRW.js → test-git-E2BLXR6M.js} +4 -4
  112. package/dist/{test-prefix-FBGXKMPA.js → test-prefix-A7JGGYAA.js} +4 -4
  113. package/dist/{test-webserver-YVQD42W6.js → test-webserver-NRMGT2HB.js} +29 -8
  114. package/dist/test-webserver-NRMGT2HB.js.map +1 -0
  115. package/package.json +3 -1
  116. package/dist/ClaudeContextManager-6J2EB4QU.js +0 -14
  117. package/dist/ClaudeService-O2PB22GX.js +0 -13
  118. package/dist/PRManager-GB3FOJ2W.js +0 -14
  119. package/dist/chunk-2A7WQKBE.js.map +0 -1
  120. package/dist/chunk-6YSFTPKW.js.map +0 -1
  121. package/dist/chunk-7Q66W4OH.js.map +0 -1
  122. package/dist/chunk-AEIMYF4P.js.map +0 -1
  123. package/dist/chunk-BXCPJJYM.js.map +0 -1
  124. package/dist/chunk-CFUWQHCJ.js.map +0 -1
  125. package/dist/chunk-F6WVM437.js.map +0 -1
  126. package/dist/chunk-GEXP5IOF.js.map +0 -1
  127. package/dist/chunk-I75JMBNB.js.map +0 -1
  128. package/dist/chunk-K7SEEHKO.js.map +0 -1
  129. package/dist/chunk-VDA5JMB4.js.map +0 -1
  130. package/dist/chunk-VU3QMIP2.js.map +0 -1
  131. package/dist/chunk-W6WVRHJ6.js +0 -251
  132. package/dist/chunk-W6WVRHJ6.js.map +0 -1
  133. package/dist/cleanup-BRUAINKE.js.map +0 -1
  134. package/dist/dev-server-4RCDJ5MU.js.map +0 -1
  135. package/dist/ignite-VHV65WEZ.js.map +0 -1
  136. package/dist/init-UTYRHNJJ.js +0 -21
  137. package/dist/open-WHVUYGPY.js.map +0 -1
  138. package/dist/projects-SA76I4TZ.js.map +0 -1
  139. package/dist/rebase-Y4AS6LQW.js.map +0 -1
  140. package/dist/run-NCRK5NPR.js.map +0 -1
  141. package/dist/test-webserver-YVQD42W6.js.map +0 -1
  142. /package/dist/{BranchNamingService-B5PVRR7F.js.map → BranchNamingService-FLPUUFOB.js.map} +0 -0
  143. /package/dist/{ClaudeContextManager-6J2EB4QU.js.map → ClaudeContextManager-KE5TBZVZ.js.map} +0 -0
  144. /package/dist/{ClaudeService-O2PB22GX.js.map → ClaudeService-CRSETT3A.js.map} +0 -0
  145. /package/dist/{GitHubService-S2OGUTDR.js.map → GitHubService-O7U4UQ7N.js.map} +0 -0
  146. /package/dist/{LoomLauncher-5LFM4LXB.js.map → LoomLauncher-NL65LSKP.js.map} +0 -0
  147. /package/dist/{MetadataManager-DFI73J3G.js.map → MetadataManager-XJ2YB762.js.map} +0 -0
  148. /package/dist/{PRManager-GB3FOJ2W.js.map → PRManager-2ABCWXHW.js.map} +0 -0
  149. /package/dist/{ProjectCapabilityDetector-S5FLNCFI.js.map → ProjectCapabilityDetector-UZYW32SY.js.map} +0 -0
  150. /package/dist/{PromptTemplateManager-C3DK6XZL.js.map → PromptTemplateManager-7L3HJQQU.js.map} +0 -0
  151. /package/dist/{SettingsManager-35F5RUJH.js.map → SettingsManager-YU4VYPTW.js.map} +0 -0
  152. /package/dist/{build-FJVYP7EV.js.map → build-O2EJHDEW.js.map} +0 -0
  153. /package/dist/{chunk-ZPSTA5PR.js.map → chunk-3CDWFEGL.js.map} +0 -0
  154. /package/dist/{chunk-UQIXZ3BA.js.map → chunk-5V74K5ZA.js.map} +0 -0
  155. /package/dist/{chunk-7WANFUIK.js.map → chunk-6TL3BYH6.js.map} +0 -0
  156. /package/dist/{chunk-5TXLVEXT.js.map → chunk-C3AKFAIR.js.map} +0 -0
  157. /package/dist/{chunk-LVBRMTE6.js.map → chunk-FEAJR6PN.js.map} +0 -0
  158. /package/dist/{chunk-64O2UIWO.js.map → chunk-GV5X6XUE.js.map} +0 -0
  159. /package/dist/{chunk-7HIRPCKU.js.map → chunk-HVQNVRAF.js.map} +0 -0
  160. /package/dist/{chunk-6U6VI4SZ.js.map → chunk-KVS4XGBQ.js.map} +0 -0
  161. /package/dist/{chunk-AXX3QIKK.js.map → chunk-LLWX3PCW.js.map} +0 -0
  162. /package/dist/{chunk-SN3Z6EZO.js.map → chunk-N7FVXZNI.js.map} +0 -0
  163. /package/dist/{chunk-EK3XCAAS.js.map → chunk-UDRZY65Y.js.map} +0 -0
  164. /package/dist/{chunk-3PT7RKL5.js.map → chunk-USJSNHGG.js.map} +0 -0
  165. /package/dist/{chunk-TRQ76ISK.js.map → chunk-Z6BO53V7.js.map} +0 -0
  166. /package/dist/{claude-H33OQMXO.js.map → claude-6H36IBHO.js.map} +0 -0
  167. /package/dist/{compile-ULNO5F7Q.js.map → compile-LRMAADUT.js.map} +0 -0
  168. /package/dist/{contribute-Q6GX6AXK.js.map → contribute-GXKOIA42.js.map} +0 -0
  169. /package/dist/{feedback-O4Q55SVS.js.map → feedback-G7G5QCY4.js.map} +0 -0
  170. /package/dist/{git-FVMGBHC2.js.map → git-ENLT2VNI.js.map} +0 -0
  171. /package/dist/{init-UTYRHNJJ.js.map → init-XQQMFDM6.js.map} +0 -0
  172. /package/dist/{lint-5JMCWE4Y.js.map → lint-OFVN7FT6.js.map} +0 -0
  173. /package/dist/{recap-VOOUXOGP.js.map → recap-ZKGHZCX6.js.map} +0 -0
  174. /package/dist/{shell-SBLXVOVJ.js.map → shell-2NNSIU34.js.map} +0 -0
  175. /package/dist/{test-3KIVXI6J.js.map → test-QZDOEUIO.js.map} +0 -0
  176. /package/dist/{test-git-ZB6AGGRW.js.map → test-git-E2BLXR6M.js.map} +0 -0
  177. /package/dist/{test-prefix-FBGXKMPA.js.map → test-prefix-A7JGGYAA.js.map} +0 -0
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ InitCommand
4
+ } from "./chunk-FEAJR6PN.js";
5
+ import "./chunk-Q7POFB5Q.js";
6
+ import "./chunk-F2PWIRV4.js";
7
+ import "./chunk-N7FVXZNI.js";
8
+ import "./chunk-UDRZY65Y.js";
9
+ import "./chunk-FXDYIV3K.js";
10
+ import "./chunk-TIYJEEVO.js";
11
+ import "./chunk-ZA575VLF.js";
12
+ import "./chunk-WFQ5CLTR.js";
13
+ import "./chunk-VWGKGNJP.js";
14
+ import "./chunk-ZX3GTM7O.js";
15
+ import "./chunk-FP7G7DG3.js";
16
+ import "./chunk-6MLEBAYZ.js";
17
+ import "./chunk-VT4PDUYT.js";
18
+ export {
19
+ InitCommand
20
+ };
21
+ //# sourceMappingURL=init-XQQMFDM6.js.map
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ScriptCommandBase
4
- } from "./chunk-TRQ76ISK.js";
5
- import "./chunk-AXX3QIKK.js";
6
- import "./chunk-UQIXZ3BA.js";
7
- import "./chunk-BXCPJJYM.js";
8
- import "./chunk-EK3XCAAS.js";
9
- import "./chunk-GEXP5IOF.js";
10
- import "./chunk-F6WVM437.js";
11
- import "./chunk-CFUWQHCJ.js";
4
+ } from "./chunk-Z6BO53V7.js";
5
+ import "./chunk-5V74K5ZA.js";
6
+ import "./chunk-LLWX3PCW.js";
7
+ import "./chunk-UDRZY65Y.js";
8
+ import "./chunk-ITN64ENQ.js";
9
+ import "./chunk-ZA575VLF.js";
10
+ import "./chunk-WFQ5CLTR.js";
11
+ import "./chunk-VWGKGNJP.js";
12
12
  import "./chunk-6MLEBAYZ.js";
13
13
  import "./chunk-VT4PDUYT.js";
14
14
 
@@ -24,4 +24,4 @@ var LintCommand = class extends ScriptCommandBase {
24
24
  export {
25
25
  LintCommand
26
26
  };
27
- //# sourceMappingURL=lint-5JMCWE4Y.js.map
27
+ //# sourceMappingURL=lint-OFVN7FT6.js.map
@@ -129,6 +129,42 @@ async function executeGhCommand(args, options) {
129
129
  const data = isJson ? JSON.parse(result.stdout) : result.stdout;
130
130
  return data;
131
131
  }
132
+ async function createIssue(title, body, options) {
133
+ const { repo, labels } = options ?? {};
134
+ logger.debug("Creating GitHub issue", { title, repo, labels });
135
+ const args = [
136
+ "issue",
137
+ "create",
138
+ "--title",
139
+ title,
140
+ "--body",
141
+ body
142
+ ];
143
+ if (repo) {
144
+ args.splice(2, 0, "--repo", repo);
145
+ }
146
+ if (labels && labels.length > 0) {
147
+ args.push("--label", labels.join(","));
148
+ }
149
+ const execaOptions = {
150
+ timeout: 3e4,
151
+ encoding: "utf8"
152
+ };
153
+ if (!repo) {
154
+ execaOptions.cwd = process.cwd();
155
+ }
156
+ const result = await execa2("gh", args, execaOptions);
157
+ const urlMatch = result.stdout.trim().match(/https:\/\/github\.com\/[^/]+\/[^/]+\/issues\/(\d+)/);
158
+ if (!(urlMatch == null ? void 0 : urlMatch[1])) {
159
+ throw new Error(`Failed to parse issue URL from gh output: ${result.stdout}`);
160
+ }
161
+ const issueNumber = parseInt(urlMatch[1], 10);
162
+ const issueUrl = urlMatch[0];
163
+ return {
164
+ number: issueNumber,
165
+ url: issueUrl
166
+ };
167
+ }
132
168
  async function createIssueComment(issueNumber, body, repo) {
133
169
  logger.debug("Creating issue comment", { issueNumber, repo });
134
170
  const apiPath = repo ? `repos/${repo}/issues/${issueNumber}/comments` : `repos/:owner/:repo/issues/${issueNumber}/comments`;
@@ -167,6 +203,38 @@ async function createPRComment(prNumber, body, repo) {
167
203
  "{id: .id, url: .html_url, created_at: .created_at}"
168
204
  ]);
169
205
  }
206
+ async function getIssueNodeId(issueNumber, repo) {
207
+ logger.debug("Fetching GitHub issue node ID", { issueNumber, repo });
208
+ const args = ["issue", "view", String(issueNumber), "--json", "id"];
209
+ if (repo) {
210
+ args.push("--repo", repo);
211
+ }
212
+ const result = await executeGhCommand(args);
213
+ return result.id;
214
+ }
215
+ async function addSubIssue(parentNodeId, childNodeId) {
216
+ logger.debug("Linking child issue to parent", { parentNodeId, childNodeId });
217
+ const mutation = `
218
+ mutation addSubIssue($parentId: ID!, $subIssueId: ID!) {
219
+ addSubIssue(input: { issueId: $parentId, subIssueId: $subIssueId }) {
220
+ issue { id }
221
+ subIssue { id }
222
+ }
223
+ }
224
+ `;
225
+ await executeGhCommand([
226
+ "api",
227
+ "graphql",
228
+ "-H",
229
+ "GraphQL-Features: sub_issues",
230
+ "-f",
231
+ `query=${mutation}`,
232
+ "-F",
233
+ `parentId=${parentNodeId}`,
234
+ "-F",
235
+ `subIssueId=${childNodeId}`
236
+ ]);
237
+ }
170
238
 
171
239
  // src/mcp/GitHubIssueManagementProvider.ts
172
240
  function normalizeAuthor(author) {
@@ -191,25 +259,30 @@ function extractNumericIdFromUrl(url) {
191
259
  var GitHubIssueManagementProvider = class {
192
260
  constructor() {
193
261
  this.providerName = "github";
262
+ this.issuePrefix = "#";
194
263
  }
195
264
  /**
196
265
  * Fetch issue details using gh CLI
197
266
  * Normalizes GitHub-specific fields to provider-agnostic format
198
267
  */
199
268
  async getIssue(input) {
200
- const { number, includeComments = true } = input;
269
+ const { number, includeComments = true, repo } = input;
201
270
  const issueNumber = parseInt(number, 10);
202
271
  if (isNaN(issueNumber)) {
203
272
  throw new Error(`Invalid GitHub issue number: ${number}. GitHub issue IDs must be numeric.`);
204
273
  }
205
274
  const fields = includeComments ? "body,title,comments,labels,assignees,milestone,author,state,number,url" : "body,title,labels,assignees,milestone,author,state,number,url";
206
- const raw = await executeGhCommand([
275
+ const args = [
207
276
  "issue",
208
277
  "view",
209
278
  String(issueNumber),
210
279
  "--json",
211
280
  fields
212
- ]);
281
+ ];
282
+ if (repo) {
283
+ args.push("--repo", repo);
284
+ }
285
+ const raw = await executeGhCommand(args);
213
286
  const result = {
214
287
  // Core fields
215
288
  id: String(raw.number),
@@ -248,14 +321,15 @@ var GitHubIssueManagementProvider = class {
248
321
  * Normalizes author to FlexibleAuthor format
249
322
  */
250
323
  async getComment(input) {
251
- const { commentId } = input;
324
+ const { commentId, repo } = input;
252
325
  const numericCommentId = parseInt(commentId, 10);
253
326
  if (isNaN(numericCommentId)) {
254
327
  throw new Error(`Invalid GitHub comment ID: ${commentId}. GitHub comment IDs must be numeric.`);
255
328
  }
329
+ const apiPath = repo ? `repos/${repo}/issues/comments/${numericCommentId}` : `repos/:owner/:repo/issues/comments/${numericCommentId}`;
256
330
  const raw = await executeGhCommand([
257
331
  "api",
258
- `repos/:owner/:repo/issues/comments/${numericCommentId}`,
332
+ apiPath,
259
333
  "--jq",
260
334
  "{id: .id, body: .body, user: .user, created_at: .created_at, updated_at: .updated_at, html_url: .html_url, reactions: .reactions}"
261
335
  ]);
@@ -300,6 +374,40 @@ var GitHubIssueManagementProvider = class {
300
374
  id: String(result.id)
301
375
  };
302
376
  }
377
+ /**
378
+ * Create a new issue
379
+ */
380
+ async createIssue(input) {
381
+ const { title, body, labels, repo } = input;
382
+ const result = await createIssue(title, body, { labels, repo });
383
+ const issueNumber = typeof result.number === "number" ? result.number : parseInt(String(result.number), 10);
384
+ return {
385
+ id: String(issueNumber),
386
+ url: result.url,
387
+ number: issueNumber
388
+ };
389
+ }
390
+ /**
391
+ * Create a child issue linked to a parent issue
392
+ * GitHub requires two-step process: create issue, then link via GraphQL
393
+ */
394
+ async createChildIssue(input) {
395
+ const { parentId, title, body, labels, repo } = input;
396
+ const parentNumber = parseInt(parentId, 10);
397
+ if (isNaN(parentNumber)) {
398
+ throw new Error(`Invalid GitHub parent issue number: ${parentId}. GitHub issue IDs must be numeric.`);
399
+ }
400
+ const parentNodeId = await getIssueNodeId(parentNumber, repo);
401
+ const childResult = await createIssue(title, body, { labels, repo });
402
+ const childNumber = typeof childResult.number === "number" ? childResult.number : parseInt(String(childResult.number), 10);
403
+ const childNodeId = await getIssueNodeId(childNumber, repo);
404
+ await addSubIssue(parentNodeId, childNodeId);
405
+ return {
406
+ id: String(childNumber),
407
+ url: childResult.url,
408
+ number: childNumber
409
+ };
410
+ }
303
411
  };
304
412
 
305
413
  // src/utils/linear.ts
@@ -319,6 +427,30 @@ var LinearServiceError = class _LinearServiceError extends Error {
319
427
  };
320
428
 
321
429
  // src/utils/linear.ts
430
+ function slugifyTitle(title, maxLength = 50) {
431
+ const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
432
+ if (slug.length <= maxLength) {
433
+ return slug;
434
+ }
435
+ const parts = slug.split("-");
436
+ let result = "";
437
+ for (const part of parts) {
438
+ const candidate = result ? `${result}-${part}` : part;
439
+ if (candidate.length > maxLength) {
440
+ break;
441
+ }
442
+ result = candidate;
443
+ }
444
+ return result || slug.slice(0, maxLength);
445
+ }
446
+ function buildLinearIssueUrl(identifier, title) {
447
+ const base = `https://linear.app/issue/${identifier}`;
448
+ if (title) {
449
+ const slug = slugifyTitle(title);
450
+ return slug ? `${base}/${slug}` : base;
451
+ }
452
+ return base;
453
+ }
322
454
  function getLinearApiToken() {
323
455
  const token = process.env.LINEAR_API_TOKEN;
324
456
  if (!token) {
@@ -383,6 +515,74 @@ async function fetchLinearIssue(identifier) {
383
515
  handleLinearError(error, "fetchLinearIssue");
384
516
  }
385
517
  }
518
+ async function createLinearIssue(title, body, teamKey, _labels) {
519
+ try {
520
+ logger.debug(`Creating Linear issue in team ${teamKey}: ${title}`);
521
+ const client = createLinearClient();
522
+ const teams = await client.teams();
523
+ const team = teams.nodes.find((t) => t.key === teamKey);
524
+ if (!team) {
525
+ throw new LinearServiceError("NOT_FOUND", `Linear team ${teamKey} not found`);
526
+ }
527
+ const issueInput = {
528
+ teamId: team.id,
529
+ title
530
+ };
531
+ if (body) {
532
+ issueInput.description = body;
533
+ }
534
+ const payload = await client.createIssue(issueInput);
535
+ const issue = await payload.issue;
536
+ if (!issue) {
537
+ throw new LinearServiceError("CLI_ERROR", "Failed to create Linear issue");
538
+ }
539
+ const url = issue.url ?? buildLinearIssueUrl(issue.identifier, title);
540
+ return {
541
+ identifier: issue.identifier,
542
+ url
543
+ };
544
+ } catch (error) {
545
+ if (error instanceof LinearServiceError) {
546
+ throw error;
547
+ }
548
+ handleLinearError(error, "createLinearIssue");
549
+ }
550
+ }
551
+ async function createLinearChildIssue(title, body, teamKey, parentId, _labels) {
552
+ try {
553
+ logger.debug(`Creating Linear child issue in team ${teamKey}: ${title}`);
554
+ const client = createLinearClient();
555
+ const teams = await client.teams();
556
+ const team = teams.nodes.find((t) => t.key === teamKey);
557
+ if (!team) {
558
+ throw new LinearServiceError("NOT_FOUND", `Linear team ${teamKey} not found`);
559
+ }
560
+ const issueInput = {
561
+ teamId: team.id,
562
+ title,
563
+ parentId
564
+ // UUID of parent issue
565
+ };
566
+ if (body) {
567
+ issueInput.description = body;
568
+ }
569
+ const payload = await client.createIssue(issueInput);
570
+ const issue = await payload.issue;
571
+ if (!issue) {
572
+ throw new LinearServiceError("CLI_ERROR", "Failed to create Linear child issue");
573
+ }
574
+ const url = issue.url ?? buildLinearIssueUrl(issue.identifier, title);
575
+ return {
576
+ identifier: issue.identifier,
577
+ url
578
+ };
579
+ } catch (error) {
580
+ if (error instanceof LinearServiceError) {
581
+ throw error;
582
+ }
583
+ handleLinearError(error, "createLinearChildIssue");
584
+ }
585
+ }
386
586
  async function createLinearComment(identifier, body) {
387
587
  try {
388
588
  logger.debug(`Creating comment on Linear issue ${identifier}`);
@@ -640,12 +840,22 @@ ${content}
640
840
  var LinearIssueManagementProvider = class {
641
841
  constructor() {
642
842
  this.providerName = "linear";
843
+ this.issuePrefix = "";
844
+ /**
845
+ * Cached team key extracted from issue identifiers (e.g., "ENG-123" -> "ENG")
846
+ * Used as fallback when teamKey is not explicitly provided to createIssue()
847
+ */
848
+ this.cachedTeamKey = void 0;
643
849
  }
644
850
  /**
645
851
  * Fetch issue details using Linear SDK
646
852
  */
647
853
  async getIssue(input) {
648
854
  const { number, includeComments = true } = input;
855
+ const match = number.match(/^([A-Z]{2,})-\d+$/i);
856
+ if (match == null ? void 0 : match[1]) {
857
+ this.cachedTeamKey = match[1].toUpperCase();
858
+ }
649
859
  const raw = await fetchLinearIssue(number);
650
860
  const state = raw.state && (raw.state.toLowerCase().includes("done") || raw.state.toLowerCase().includes("completed") || raw.state.toLowerCase().includes("canceled")) ? "closed" : "open";
651
861
  const result = {
@@ -731,6 +941,47 @@ var LinearIssueManagementProvider = class {
731
941
  updated_at: result.updatedAt
732
942
  };
733
943
  }
944
+ /**
945
+ * Create a new issue
946
+ */
947
+ async createIssue(input) {
948
+ const { title, body, labels, teamKey } = input;
949
+ const effectiveTeamKey = teamKey ?? process.env.LINEAR_TEAM_KEY ?? this.cachedTeamKey;
950
+ if (!effectiveTeamKey) {
951
+ throw new Error("teamKey is required for Linear issue creation. Configure issueManagement.linear.teamId in settings, or call getIssue first to extract the team from an issue identifier.");
952
+ }
953
+ const result = await createLinearIssue(title, body, effectiveTeamKey, labels);
954
+ return {
955
+ id: result.identifier,
956
+ url: result.url
957
+ };
958
+ }
959
+ /**
960
+ * Create a child issue linked to a parent issue
961
+ * Linear supports atomic creation with parentId field
962
+ */
963
+ async createChildIssue(input) {
964
+ var _a;
965
+ const { parentId, title, body, labels, teamKey } = input;
966
+ const parentIssue = await fetchLinearIssue(parentId);
967
+ const match = parentId.match(/^([A-Z]{2,})-\d+$/i);
968
+ const effectiveTeamKey = teamKey ?? ((_a = match == null ? void 0 : match[1]) == null ? void 0 : _a.toUpperCase()) ?? process.env.LINEAR_TEAM_KEY ?? this.cachedTeamKey;
969
+ if (!effectiveTeamKey) {
970
+ throw new Error("teamKey is required for Linear child issue creation. Provide teamKey parameter or use a parent identifier with team prefix.");
971
+ }
972
+ const result = await createLinearChildIssue(
973
+ title,
974
+ body,
975
+ effectiveTeamKey,
976
+ parentIssue.id,
977
+ // UUID, not identifier
978
+ labels
979
+ );
980
+ return {
981
+ id: result.identifier,
982
+ url: result.url
983
+ };
984
+ }
734
985
  };
735
986
 
736
987
  // src/mcp/IssueManagementProviderFactory.ts
@@ -794,7 +1045,10 @@ server.registerTool(
794
1045
  description: "Fetch issue details including body, title, comments, labels, assignees, and other metadata. Author fields vary by provider: GitHub uses { login }, Linear uses { name, displayName }, Jira uses { displayName, accountId }. All authors have normalized core fields: { id, displayName } plus provider-specific fields.",
795
1046
  inputSchema: {
796
1047
  number: z.string().describe("The issue identifier"),
797
- includeComments: z.boolean().optional().describe("Whether to include comments (default: true)")
1048
+ includeComments: z.boolean().optional().describe("Whether to include comments (default: true)"),
1049
+ repo: z.string().optional().describe(
1050
+ 'Optional repository in "owner/repo" format or full GitHub URL. When not provided, uses the current repository. GitHub only.'
1051
+ )
798
1052
  },
799
1053
  outputSchema: {
800
1054
  // Core validated fields
@@ -826,13 +1080,13 @@ server.registerTool(
826
1080
  ).optional().describe("Issue comments with flexible author structure")
827
1081
  }
828
1082
  },
829
- async ({ number, includeComments }) => {
830
- console.error(`Fetching issue ${number}`);
1083
+ async ({ number, includeComments, repo }) => {
1084
+ console.error(`Fetching issue ${number}${repo ? ` from ${repo}` : ""}`);
831
1085
  try {
832
1086
  const provider = IssueManagementProviderFactory.create(
833
1087
  process.env.ISSUE_PROVIDER
834
1088
  );
835
- const result = await provider.getIssue({ number, includeComments });
1089
+ const result = await provider.getIssue({ number, includeComments, repo });
836
1090
  console.error(`Issue fetched successfully: ${result.number} - ${result.title}`);
837
1091
  return {
838
1092
  content: [
@@ -857,7 +1111,10 @@ server.registerTool(
857
1111
  description: "Fetch a specific comment by ID. Author has normalized core fields { id, displayName } plus provider-specific fields.",
858
1112
  inputSchema: {
859
1113
  commentId: z.string().describe("The comment identifier to fetch"),
860
- number: z.string().describe("The issue or PR identifier (context for providers that need it)")
1114
+ number: z.string().describe("The issue or PR identifier (context for providers that need it)"),
1115
+ repo: z.string().optional().describe(
1116
+ 'Optional repository in "owner/repo" format or full GitHub URL. When not provided, uses the current repository. GitHub only.'
1117
+ )
861
1118
  },
862
1119
  outputSchema: {
863
1120
  id: z.string().describe("Comment identifier"),
@@ -869,13 +1126,13 @@ server.registerTool(
869
1126
  updated_at: z.string().optional().describe("Comment last updated timestamp")
870
1127
  }
871
1128
  },
872
- async ({ commentId, number }) => {
873
- console.error(`Fetching comment ${commentId} from issue ${number}`);
1129
+ async ({ commentId, number, repo }) => {
1130
+ console.error(`Fetching comment ${commentId} from issue ${number}${repo ? ` in ${repo}` : ""}`);
874
1131
  try {
875
1132
  const provider = IssueManagementProviderFactory.create(
876
1133
  process.env.ISSUE_PROVIDER
877
1134
  );
878
- const result = await provider.getComment({ commentId, number });
1135
+ const result = await provider.getComment({ commentId, number, repo });
879
1136
  console.error(`Comment fetched successfully: ${result.id}`);
880
1137
  return {
881
1138
  content: [
@@ -977,6 +1234,95 @@ server.registerTool(
977
1234
  }
978
1235
  }
979
1236
  );
1237
+ server.registerTool(
1238
+ "create_issue",
1239
+ {
1240
+ title: "Create Issue",
1241
+ description: 'Create a new issue in the configured issue tracker. For GitHub: creates issue in the configured repository. For Linear: requires teamKey parameter (e.g., "ENG", "PLAT"), or configure issueManagement.linear.teamId in settings, or call get_issue first to auto-detect the team.',
1242
+ inputSchema: {
1243
+ title: z.string().describe("The issue title"),
1244
+ body: z.string().describe("The issue body/description (markdown supported)"),
1245
+ labels: z.array(z.string()).optional().describe("Optional labels to apply to the issue"),
1246
+ teamKey: z.string().optional().describe('Team key for Linear (e.g., "ENG"). Falls back to settings or team extracted from previous get_issue call. Ignored for GitHub.'),
1247
+ repo: z.string().optional().describe(
1248
+ 'Optional repository in "owner/repo" format or full GitHub URL. When not provided, uses the current repository. GitHub only.'
1249
+ )
1250
+ },
1251
+ outputSchema: {
1252
+ id: z.string().describe("Issue identifier"),
1253
+ url: z.string().describe("Issue URL"),
1254
+ number: z.number().optional().describe("Issue number (GitHub only)")
1255
+ }
1256
+ },
1257
+ async ({ title, body, labels, teamKey, repo }) => {
1258
+ console.error(`Creating issue: ${title}${repo ? ` in ${repo}` : ""}`);
1259
+ try {
1260
+ const provider = IssueManagementProviderFactory.create(
1261
+ process.env.ISSUE_PROVIDER
1262
+ );
1263
+ const result = await provider.createIssue({ title, body, labels, teamKey, repo });
1264
+ console.error(`Issue created successfully: ${result.id} at ${result.url}`);
1265
+ return {
1266
+ content: [
1267
+ {
1268
+ type: "text",
1269
+ text: JSON.stringify(result)
1270
+ }
1271
+ ],
1272
+ structuredContent: result
1273
+ };
1274
+ } catch (error) {
1275
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1276
+ console.error(`Failed to create issue: ${errorMessage}`);
1277
+ throw new Error(`Failed to create issue: ${errorMessage}`);
1278
+ }
1279
+ }
1280
+ );
1281
+ server.registerTool(
1282
+ "create_child_issue",
1283
+ {
1284
+ title: "Create Child Issue",
1285
+ description: 'Create a new child issue linked to a parent issue. For GitHub: creates issue and links via sub-issue API (requires two API calls). For Linear: creates issue atomically with parent relationship. The parentId should be the parent issue identifier (GitHub issue number or Linear identifier like "ENG-123").',
1286
+ inputSchema: {
1287
+ parentId: z.string().describe('Parent issue identifier (GitHub issue number or Linear identifier like "ENG-123")'),
1288
+ title: z.string().describe("The child issue title"),
1289
+ body: z.string().describe("The child issue body/description (markdown supported)"),
1290
+ labels: z.array(z.string()).optional().describe("Optional labels to apply to the child issue"),
1291
+ teamKey: z.string().optional().describe('Team key for Linear (e.g., "ENG"). Falls back to parent team. Ignored for GitHub.'),
1292
+ repo: z.string().optional().describe(
1293
+ 'Optional repository in "owner/repo" format or full GitHub URL. When not provided, uses the current repository. GitHub only.'
1294
+ )
1295
+ },
1296
+ outputSchema: {
1297
+ id: z.string().describe("Issue identifier"),
1298
+ url: z.string().describe("Issue URL"),
1299
+ number: z.number().optional().describe("Issue number (GitHub only)")
1300
+ }
1301
+ },
1302
+ async ({ parentId, title, body, labels, teamKey, repo }) => {
1303
+ console.error(`Creating child issue for parent ${parentId}: ${title}${repo ? ` in ${repo}` : ""}`);
1304
+ try {
1305
+ const provider = IssueManagementProviderFactory.create(
1306
+ process.env.ISSUE_PROVIDER
1307
+ );
1308
+ const result = await provider.createChildIssue({ parentId, title, body, labels, teamKey, repo });
1309
+ console.error(`Child issue created successfully: ${result.id} at ${result.url}`);
1310
+ return {
1311
+ content: [
1312
+ {
1313
+ type: "text",
1314
+ text: JSON.stringify(result)
1315
+ }
1316
+ ],
1317
+ structuredContent: result
1318
+ };
1319
+ } catch (error) {
1320
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1321
+ console.error(`Failed to create child issue: ${errorMessage}`);
1322
+ throw new Error(`Failed to create child issue: ${errorMessage}`);
1323
+ }
1324
+ }
1325
+ );
980
1326
  async function main() {
981
1327
  console.error("Starting Issue Management MCP Server...");
982
1328
  const provider = validateEnvironment();