@iloom/cli 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/README.md +234 -667
  2. package/dist/BranchNamingService-OMWKUYMM.js +13 -0
  3. package/dist/ClaudeContextManager-3VXA6UPR.js +13 -0
  4. package/dist/ClaudeService-6CPK43N4.js +12 -0
  5. package/dist/GitHubService-EBOETDIW.js +11 -0
  6. package/dist/{LoomLauncher-CTSWJL35.js → LoomLauncher-JF7JZMTZ.js} +63 -32
  7. package/dist/LoomLauncher-JF7JZMTZ.js.map +1 -0
  8. package/dist/ProjectCapabilityDetector-34LU7JJ4.js +9 -0
  9. package/dist/{PromptTemplateManager-WII75TKH.js → PromptTemplateManager-A52RUAMS.js} +2 -2
  10. package/dist/README.md +234 -667
  11. package/dist/{SettingsManager-XOYCLH3D.js → SettingsManager-ZCWJ56WP.js} +12 -4
  12. package/dist/SettingsMigrationManager-AGIIIPDQ.js +10 -0
  13. package/dist/agents/iloom-issue-analyze-and-plan.md +125 -35
  14. package/dist/agents/iloom-issue-analyzer.md +284 -32
  15. package/dist/agents/iloom-issue-complexity-evaluator.md +40 -21
  16. package/dist/agents/iloom-issue-enhancer.md +69 -48
  17. package/dist/agents/iloom-issue-implementer.md +36 -25
  18. package/dist/agents/iloom-issue-planner.md +35 -24
  19. package/dist/agents/iloom-issue-reviewer.md +62 -9
  20. package/dist/{chunk-SWCRXDZC.js → chunk-3RUPPQRG.js} +1 -18
  21. package/dist/chunk-3RUPPQRG.js.map +1 -0
  22. package/dist/{chunk-HBVFXN7R.js → chunk-4BGK7T6X.js} +26 -3
  23. package/dist/chunk-4BGK7T6X.js.map +1 -0
  24. package/dist/{chunk-6LEQW46Y.js → chunk-4E4LD3QR.js} +72 -2
  25. package/dist/{chunk-6LEQW46Y.js.map → chunk-4E4LD3QR.js.map} +1 -1
  26. package/dist/{chunk-CWR2SANQ.js → chunk-EBISESAP.js} +1 -1
  27. package/dist/{chunk-TS6DL67T.js → chunk-G2IEYOLQ.js} +11 -38
  28. package/dist/chunk-G2IEYOLQ.js.map +1 -0
  29. package/dist/chunk-HBYZH6GD.js +1989 -0
  30. package/dist/chunk-HBYZH6GD.js.map +1 -0
  31. package/dist/chunk-INW24J2W.js +55 -0
  32. package/dist/chunk-INW24J2W.js.map +1 -0
  33. package/dist/{chunk-ZMNQBJUI.js → chunk-IP7SMKIF.js} +61 -22
  34. package/dist/chunk-IP7SMKIF.js.map +1 -0
  35. package/dist/{chunk-4IV6W4U5.js → chunk-IXKLYTWO.js} +12 -12
  36. package/dist/chunk-IXKLYTWO.js.map +1 -0
  37. package/dist/{chunk-JNKJ7NJV.js → chunk-JKXJ7BGL.js} +6 -2
  38. package/dist/{chunk-JNKJ7NJV.js.map → chunk-JKXJ7BGL.js.map} +1 -1
  39. package/dist/{chunk-LAPY6NAE.js → chunk-JQFO7QQN.js} +68 -12
  40. package/dist/{chunk-LAPY6NAE.js.map → chunk-JQFO7QQN.js.map} +1 -1
  41. package/dist/{SettingsMigrationManager-MTQIMI54.js → chunk-KLBYVHPK.js} +3 -2
  42. package/dist/{chunk-USVVV3FP.js → chunk-MKWYLDFK.js} +5 -5
  43. package/dist/chunk-O5OH5MRX.js +396 -0
  44. package/dist/chunk-O5OH5MRX.js.map +1 -0
  45. package/dist/{chunk-DJUGYNQE.js → chunk-PA6Q6AWM.js} +16 -3
  46. package/dist/chunk-PA6Q6AWM.js.map +1 -0
  47. package/dist/chunk-RO26VS3W.js +444 -0
  48. package/dist/chunk-RO26VS3W.js.map +1 -0
  49. package/dist/{chunk-VETG35MF.js → chunk-TSKY3JI7.js} +3 -3
  50. package/dist/{chunk-VETG35MF.js.map → chunk-TSKY3JI7.js.map} +1 -1
  51. package/dist/{chunk-LHP6ROUM.js → chunk-U5QDY7ZD.js} +4 -16
  52. package/dist/chunk-U5QDY7ZD.js.map +1 -0
  53. package/dist/{chunk-SPYPLHMK.js → chunk-VU3QMIP2.js} +34 -2
  54. package/dist/chunk-VU3QMIP2.js.map +1 -0
  55. package/dist/{chunk-PVAVNJKS.js → chunk-WEN5C5DM.js} +10 -1
  56. package/dist/chunk-WEN5C5DM.js.map +1 -0
  57. package/dist/{chunk-2PLUQT6J.js → chunk-XPKDPZ5D.js} +2 -2
  58. package/dist/{chunk-RF2YI2XJ.js → chunk-ZBQVSHVT.js} +5 -5
  59. package/dist/chunk-ZBQVSHVT.js.map +1 -0
  60. package/dist/{chunk-GZP4UGGM.js → chunk-ZM3CFL5L.js} +2 -2
  61. package/dist/{chunk-BLCTGFZN.js → chunk-ZT3YZB4K.js} +3 -4
  62. package/dist/chunk-ZT3YZB4K.js.map +1 -0
  63. package/dist/{chunk-MFU53H6J.js → chunk-ZWFBBPJI.js} +6 -6
  64. package/dist/{chunk-MFU53H6J.js.map → chunk-ZWFBBPJI.js.map} +1 -1
  65. package/dist/{claude-ZIWDG4XG.js → claude-LUZ35IMK.js} +2 -2
  66. package/dist/{cleanup-FEIVZSIV.js → cleanup-3MONU4PU.js} +88 -27
  67. package/dist/cleanup-3MONU4PU.js.map +1 -0
  68. package/dist/cli.js +2511 -62
  69. package/dist/cli.js.map +1 -1
  70. package/dist/{contribute-EMZKCAC6.js → contribute-UWJAGIG7.js} +6 -6
  71. package/dist/{feedback-LFNMQBAZ.js → feedback-W3BXTGIM.js} +15 -14
  72. package/dist/{feedback-LFNMQBAZ.js.map → feedback-W3BXTGIM.js.map} +1 -1
  73. package/dist/{git-WC6HZLOT.js → git-34Z6QVDS.js} +4 -2
  74. package/dist/{ignite-MQWVJEAB.js → ignite-KVJEFXNO.js} +32 -27
  75. package/dist/ignite-KVJEFXNO.js.map +1 -0
  76. package/dist/index.d.ts +359 -45
  77. package/dist/index.js +1267 -503
  78. package/dist/index.js.map +1 -1
  79. package/dist/{init-GJDYN2IK.js → init-L55Q73H4.js} +104 -40
  80. package/dist/init-L55Q73H4.js.map +1 -0
  81. package/dist/mcp/issue-management-server.js +934 -0
  82. package/dist/mcp/issue-management-server.js.map +1 -0
  83. package/dist/{neon-helpers-ZVIRPKCI.js → neon-helpers-WPUACUVC.js} +3 -3
  84. package/dist/neon-helpers-WPUACUVC.js.map +1 -0
  85. package/dist/{open-NXSN7XOC.js → open-LNRZL3UU.js} +39 -36
  86. package/dist/open-LNRZL3UU.js.map +1 -0
  87. package/dist/{prompt-ANTQWHUF.js → prompt-7INJ7YRU.js} +4 -2
  88. package/dist/prompt-7INJ7YRU.js.map +1 -0
  89. package/dist/prompts/init-prompt.txt +541 -98
  90. package/dist/prompts/issue-prompt.txt +27 -27
  91. package/dist/{rebase-DUNFOJVS.js → rebase-C4WNCVGM.js} +6 -6
  92. package/dist/{remote-ZCXJVVNW.js → remote-VUNCQZ6J.js} +3 -2
  93. package/dist/remote-VUNCQZ6J.js.map +1 -0
  94. package/dist/{run-O7ZK7CKA.js → run-IOGNIOYN.js} +39 -36
  95. package/dist/run-IOGNIOYN.js.map +1 -0
  96. package/dist/schema/settings.schema.json +59 -3
  97. package/dist/{test-git-T76HOTIA.js → test-git-J7I5MFYH.js} +3 -3
  98. package/dist/{test-prefix-6HJUVQMH.js → test-prefix-ZCONBCBX.js} +3 -3
  99. package/dist/{test-webserver-M2I3EV4J.js → test-webserver-DAHONWCS.js} +4 -4
  100. package/dist/test-webserver-DAHONWCS.js.map +1 -0
  101. package/package.json +3 -2
  102. package/dist/ClaudeContextManager-LVCYRM6Q.js +0 -13
  103. package/dist/ClaudeService-WVTWB3DK.js +0 -12
  104. package/dist/GitHubService-7E2S5NNZ.js +0 -11
  105. package/dist/LoomLauncher-CTSWJL35.js.map +0 -1
  106. package/dist/add-issue-OBI325W7.js +0 -69
  107. package/dist/add-issue-OBI325W7.js.map +0 -1
  108. package/dist/chunk-4IV6W4U5.js.map +0 -1
  109. package/dist/chunk-BLCTGFZN.js.map +0 -1
  110. package/dist/chunk-CVLAZRNB.js +0 -54
  111. package/dist/chunk-CVLAZRNB.js.map +0 -1
  112. package/dist/chunk-DJUGYNQE.js.map +0 -1
  113. package/dist/chunk-H4E4THUZ.js +0 -55
  114. package/dist/chunk-H4E4THUZ.js.map +0 -1
  115. package/dist/chunk-H5LDRGVK.js +0 -642
  116. package/dist/chunk-H5LDRGVK.js.map +0 -1
  117. package/dist/chunk-HBVFXN7R.js.map +0 -1
  118. package/dist/chunk-LHP6ROUM.js.map +0 -1
  119. package/dist/chunk-PVAVNJKS.js.map +0 -1
  120. package/dist/chunk-RF2YI2XJ.js.map +0 -1
  121. package/dist/chunk-SPYPLHMK.js.map +0 -1
  122. package/dist/chunk-SWCRXDZC.js.map +0 -1
  123. package/dist/chunk-SYOSCMIT.js +0 -545
  124. package/dist/chunk-SYOSCMIT.js.map +0 -1
  125. package/dist/chunk-T3KEIB4D.js +0 -243
  126. package/dist/chunk-T3KEIB4D.js.map +0 -1
  127. package/dist/chunk-TS6DL67T.js.map +0 -1
  128. package/dist/chunk-ZMNQBJUI.js.map +0 -1
  129. package/dist/cleanup-FEIVZSIV.js.map +0 -1
  130. package/dist/enhance-MNA4ZGXW.js +0 -176
  131. package/dist/enhance-MNA4ZGXW.js.map +0 -1
  132. package/dist/finish-TX5CJICB.js +0 -1749
  133. package/dist/finish-TX5CJICB.js.map +0 -1
  134. package/dist/ignite-MQWVJEAB.js.map +0 -1
  135. package/dist/init-GJDYN2IK.js.map +0 -1
  136. package/dist/mcp/chunk-6SDFJ42P.js +0 -62
  137. package/dist/mcp/chunk-6SDFJ42P.js.map +0 -1
  138. package/dist/mcp/claude-NDFOCQQQ.js +0 -249
  139. package/dist/mcp/claude-NDFOCQQQ.js.map +0 -1
  140. package/dist/mcp/color-QS5BFCNN.js +0 -168
  141. package/dist/mcp/color-QS5BFCNN.js.map +0 -1
  142. package/dist/mcp/github-comment-server.js +0 -168
  143. package/dist/mcp/github-comment-server.js.map +0 -1
  144. package/dist/mcp/terminal-OMNRFWB3.js +0 -227
  145. package/dist/mcp/terminal-OMNRFWB3.js.map +0 -1
  146. package/dist/open-NXSN7XOC.js.map +0 -1
  147. package/dist/run-O7ZK7CKA.js.map +0 -1
  148. package/dist/start-73I5W7WW.js +0 -983
  149. package/dist/start-73I5W7WW.js.map +0 -1
  150. package/dist/test-webserver-M2I3EV4J.js.map +0 -1
  151. /package/dist/{ClaudeContextManager-LVCYRM6Q.js.map → BranchNamingService-OMWKUYMM.js.map} +0 -0
  152. /package/dist/{ClaudeService-WVTWB3DK.js.map → ClaudeContextManager-3VXA6UPR.js.map} +0 -0
  153. /package/dist/{GitHubService-7E2S5NNZ.js.map → ClaudeService-6CPK43N4.js.map} +0 -0
  154. /package/dist/{PromptTemplateManager-WII75TKH.js.map → GitHubService-EBOETDIW.js.map} +0 -0
  155. /package/dist/{SettingsManager-XOYCLH3D.js.map → ProjectCapabilityDetector-34LU7JJ4.js.map} +0 -0
  156. /package/dist/{claude-ZIWDG4XG.js.map → PromptTemplateManager-A52RUAMS.js.map} +0 -0
  157. /package/dist/{git-WC6HZLOT.js.map → SettingsManager-ZCWJ56WP.js.map} +0 -0
  158. /package/dist/{neon-helpers-ZVIRPKCI.js.map → SettingsMigrationManager-AGIIIPDQ.js.map} +0 -0
  159. /package/dist/{chunk-CWR2SANQ.js.map → chunk-EBISESAP.js.map} +0 -0
  160. /package/dist/{SettingsMigrationManager-MTQIMI54.js.map → chunk-KLBYVHPK.js.map} +0 -0
  161. /package/dist/{chunk-USVVV3FP.js.map → chunk-MKWYLDFK.js.map} +0 -0
  162. /package/dist/{chunk-2PLUQT6J.js.map → chunk-XPKDPZ5D.js.map} +0 -0
  163. /package/dist/{chunk-GZP4UGGM.js.map → chunk-ZM3CFL5L.js.map} +0 -0
  164. /package/dist/{prompt-ANTQWHUF.js.map → claude-LUZ35IMK.js.map} +0 -0
  165. /package/dist/{contribute-EMZKCAC6.js.map → contribute-UWJAGIG7.js.map} +0 -0
  166. /package/dist/{remote-ZCXJVVNW.js.map → git-34Z6QVDS.js.map} +0 -0
  167. /package/dist/{rebase-DUNFOJVS.js.map → rebase-C4WNCVGM.js.map} +0 -0
  168. /package/dist/{test-git-T76HOTIA.js.map → test-git-J7I5MFYH.js.map} +0 -0
  169. /package/dist/{test-prefix-6HJUVQMH.js.map → test-prefix-ZCONBCBX.js.map} +0 -0
@@ -0,0 +1,934 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/mcp/issue-management-server.ts
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { z } from "zod";
7
+
8
+ // src/utils/github.ts
9
+ import { execa } from "execa";
10
+
11
+ // src/utils/logger.ts
12
+ import chalk, { Chalk } from "chalk";
13
+ var stdoutChalk = new Chalk({ level: chalk.level });
14
+ var stderrChalk = new Chalk({ level: chalk.level });
15
+ function formatMessage(message, ...args) {
16
+ const formattedArgs = args.map(
17
+ (arg) => typeof arg === "object" ? JSON.stringify(arg, null, 2) : String(arg)
18
+ );
19
+ return formattedArgs.length > 0 ? `${message} ${formattedArgs.join(" ")}` : message;
20
+ }
21
+ function formatWithEmoji(message, emoji, colorFn) {
22
+ if (message.trim()) {
23
+ return colorFn(`${emoji} ${message}`);
24
+ } else {
25
+ return "";
26
+ }
27
+ }
28
+ var globalDebugEnabled = false;
29
+ var logger = {
30
+ info: (message, ...args) => {
31
+ const formatted = formatMessage(message, ...args);
32
+ const output = formatWithEmoji(formatted, "\u{1F5C2}\uFE0F ", stdoutChalk.blue);
33
+ console.log(output);
34
+ },
35
+ success: (message, ...args) => {
36
+ const formatted = formatMessage(message, ...args);
37
+ const output = formatWithEmoji(formatted, "\u2705", stdoutChalk.green);
38
+ console.log(output);
39
+ },
40
+ warn: (message, ...args) => {
41
+ const formatted = formatMessage(message, ...args);
42
+ const output = formatWithEmoji(formatted, "\u26A0\uFE0F ", stderrChalk.yellow);
43
+ console.error(output);
44
+ },
45
+ error: (message, ...args) => {
46
+ const formatted = formatMessage(message, ...args);
47
+ const output = formatWithEmoji(formatted, "\u274C", stderrChalk.red);
48
+ console.error(output);
49
+ },
50
+ debug: (message, ...args) => {
51
+ if (globalDebugEnabled) {
52
+ const formatted = formatMessage(message, ...args);
53
+ const output = formatWithEmoji(formatted, "\u{1F50D}", stdoutChalk.gray);
54
+ console.log(output);
55
+ }
56
+ },
57
+ setDebug: (enabled) => {
58
+ globalDebugEnabled = enabled;
59
+ },
60
+ isDebugEnabled: () => {
61
+ return globalDebugEnabled;
62
+ }
63
+ };
64
+
65
+ // src/utils/github.ts
66
+ async function executeGhCommand(args, options) {
67
+ const result = await execa("gh", args, {
68
+ cwd: (options == null ? void 0 : options.cwd) ?? process.cwd(),
69
+ timeout: (options == null ? void 0 : options.timeout) ?? 3e4,
70
+ encoding: "utf8"
71
+ });
72
+ const isJson = args.includes("--json") || args.includes("--jq") || args.includes("--format") && args[args.indexOf("--format") + 1] === "json";
73
+ const data = isJson ? JSON.parse(result.stdout) : result.stdout;
74
+ return data;
75
+ }
76
+ async function createIssueComment(issueNumber, body, repo) {
77
+ logger.debug("Creating issue comment", { issueNumber, repo });
78
+ const apiPath = repo ? `repos/${repo}/issues/${issueNumber}/comments` : `repos/:owner/:repo/issues/${issueNumber}/comments`;
79
+ return executeGhCommand([
80
+ "api",
81
+ apiPath,
82
+ "-f",
83
+ `body=${body}`,
84
+ "--jq",
85
+ "{id: .id, url: .html_url, created_at: .created_at}"
86
+ ]);
87
+ }
88
+ async function updateIssueComment(commentId, body, repo) {
89
+ logger.debug("Updating issue comment", { commentId, repo });
90
+ const apiPath = repo ? `repos/${repo}/issues/comments/${commentId}` : `repos/:owner/:repo/issues/comments/${commentId}`;
91
+ return executeGhCommand([
92
+ "api",
93
+ apiPath,
94
+ "-X",
95
+ "PATCH",
96
+ "-f",
97
+ `body=${body}`,
98
+ "--jq",
99
+ "{id: .id, url: .html_url, updated_at: .updated_at}"
100
+ ]);
101
+ }
102
+ async function createPRComment(prNumber, body, repo) {
103
+ logger.debug("Creating PR comment", { prNumber, repo });
104
+ const apiPath = repo ? `repos/${repo}/issues/${prNumber}/comments` : `repos/:owner/:repo/issues/${prNumber}/comments`;
105
+ return executeGhCommand([
106
+ "api",
107
+ apiPath,
108
+ "-f",
109
+ `body=${body}`,
110
+ "--jq",
111
+ "{id: .id, url: .html_url, created_at: .created_at}"
112
+ ]);
113
+ }
114
+
115
+ // src/mcp/GitHubIssueManagementProvider.ts
116
+ function normalizeAuthor(author) {
117
+ if (!author) return null;
118
+ return {
119
+ id: author.id ? String(author.id) : author.login,
120
+ displayName: author.login,
121
+ // GitHub uses login as primary identifier
122
+ login: author.login,
123
+ // Preserve original GitHub field
124
+ ...author.avatarUrl && { avatarUrl: author.avatarUrl },
125
+ ...author.url && { url: author.url }
126
+ };
127
+ }
128
+ var GitHubIssueManagementProvider = class {
129
+ constructor() {
130
+ this.providerName = "github";
131
+ }
132
+ /**
133
+ * Fetch issue details using gh CLI
134
+ * Normalizes GitHub-specific fields to provider-agnostic format
135
+ */
136
+ async getIssue(input) {
137
+ const { number, includeComments = true } = input;
138
+ const issueNumber = parseInt(number, 10);
139
+ if (isNaN(issueNumber)) {
140
+ throw new Error(`Invalid GitHub issue number: ${number}. GitHub issue IDs must be numeric.`);
141
+ }
142
+ const fields = includeComments ? "body,title,comments,labels,assignees,milestone,author,state,number,url" : "body,title,labels,assignees,milestone,author,state,number,url";
143
+ const raw = await executeGhCommand([
144
+ "issue",
145
+ "view",
146
+ String(issueNumber),
147
+ "--json",
148
+ fields
149
+ ]);
150
+ const result = {
151
+ // Core fields
152
+ id: String(raw.number),
153
+ title: raw.title,
154
+ body: raw.body,
155
+ state: raw.state,
156
+ url: raw.url,
157
+ provider: "github",
158
+ // Normalized author
159
+ author: normalizeAuthor(raw.author),
160
+ // Optional flexible fields
161
+ ...raw.assignees && {
162
+ assignees: raw.assignees.map((a) => normalizeAuthor(a)).filter((a) => a !== null)
163
+ },
164
+ ...raw.labels && {
165
+ labels: raw.labels
166
+ },
167
+ // GitHub-specific passthrough fields
168
+ ...raw.milestone && {
169
+ milestone: raw.milestone
170
+ }
171
+ };
172
+ if (raw.comments !== void 0) {
173
+ result.comments = raw.comments.map((comment) => ({
174
+ id: String(comment.id),
175
+ body: comment.body,
176
+ createdAt: comment.createdAt,
177
+ author: normalizeAuthor(comment.author),
178
+ ...comment.updatedAt && { updatedAt: comment.updatedAt }
179
+ }));
180
+ }
181
+ return result;
182
+ }
183
+ /**
184
+ * Fetch a specific comment by ID using gh API
185
+ * Normalizes author to FlexibleAuthor format
186
+ */
187
+ async getComment(input) {
188
+ const { commentId } = input;
189
+ const numericCommentId = parseInt(commentId, 10);
190
+ if (isNaN(numericCommentId)) {
191
+ throw new Error(`Invalid GitHub comment ID: ${commentId}. GitHub comment IDs must be numeric.`);
192
+ }
193
+ const raw = await executeGhCommand([
194
+ "api",
195
+ `repos/:owner/:repo/issues/comments/${numericCommentId}`,
196
+ "--jq",
197
+ "{id: .id, body: .body, user: .user, created_at: .created_at, updated_at: .updated_at, html_url: .html_url, reactions: .reactions}"
198
+ ]);
199
+ return {
200
+ id: String(raw.id),
201
+ body: raw.body,
202
+ author: normalizeAuthor(raw.user),
203
+ created_at: raw.created_at,
204
+ ...raw.updated_at && { updated_at: raw.updated_at },
205
+ // Passthrough GitHub-specific fields
206
+ ...raw.html_url && { html_url: raw.html_url },
207
+ ...raw.reactions && { reactions: raw.reactions }
208
+ };
209
+ }
210
+ /**
211
+ * Create a new comment on an issue or PR
212
+ */
213
+ async createComment(input) {
214
+ const { number, body, type } = input;
215
+ const numericId = parseInt(number, 10);
216
+ if (isNaN(numericId)) {
217
+ throw new Error(`Invalid GitHub ${type} number: ${number}. GitHub IDs must be numeric.`);
218
+ }
219
+ const result = type === "issue" ? await createIssueComment(numericId, body) : await createPRComment(numericId, body);
220
+ return {
221
+ ...result,
222
+ id: String(result.id)
223
+ };
224
+ }
225
+ /**
226
+ * Update an existing comment
227
+ */
228
+ async updateComment(input) {
229
+ const { commentId, body } = input;
230
+ const numericCommentId = parseInt(commentId, 10);
231
+ if (isNaN(numericCommentId)) {
232
+ throw new Error(`Invalid GitHub comment ID: ${commentId}. GitHub comment IDs must be numeric.`);
233
+ }
234
+ const result = await updateIssueComment(numericCommentId, body);
235
+ return {
236
+ ...result,
237
+ id: String(result.id)
238
+ };
239
+ }
240
+ };
241
+
242
+ // src/utils/linear.ts
243
+ import { LinearClient } from "@linear/sdk";
244
+
245
+ // src/types/linear.ts
246
+ var LinearServiceError = class _LinearServiceError extends Error {
247
+ constructor(code, message, details) {
248
+ super(message);
249
+ this.code = code;
250
+ this.details = details;
251
+ this.name = "LinearServiceError";
252
+ if (Error.captureStackTrace) {
253
+ Error.captureStackTrace(this, _LinearServiceError);
254
+ }
255
+ }
256
+ };
257
+
258
+ // src/utils/linear.ts
259
+ function getLinearApiToken() {
260
+ const token = process.env.LINEAR_API_TOKEN;
261
+ if (!token) {
262
+ throw new LinearServiceError(
263
+ "UNAUTHORIZED",
264
+ "LINEAR_API_TOKEN not set. Configure in settings.local.json or set environment variable."
265
+ );
266
+ }
267
+ return token;
268
+ }
269
+ function createLinearClient() {
270
+ return new LinearClient({ apiKey: getLinearApiToken() });
271
+ }
272
+ function handleLinearError(error, context) {
273
+ logger.debug(`${context}: Handling error`, { error });
274
+ const errorMessage = error instanceof Error ? error.message : String(error);
275
+ if (errorMessage.includes("not found") || errorMessage.includes("Not found")) {
276
+ throw new LinearServiceError("NOT_FOUND", "Linear issue or resource not found", { error });
277
+ }
278
+ if (errorMessage.includes("unauthorized") || errorMessage.includes("Unauthorized") || errorMessage.includes("Invalid API key")) {
279
+ throw new LinearServiceError(
280
+ "UNAUTHORIZED",
281
+ "Linear authentication failed. Check LINEAR_API_TOKEN.",
282
+ { error }
283
+ );
284
+ }
285
+ if (errorMessage.includes("rate limit")) {
286
+ throw new LinearServiceError("RATE_LIMITED", "Linear API rate limit exceeded", { error });
287
+ }
288
+ throw new LinearServiceError("CLI_ERROR", `Linear SDK error: ${errorMessage}`, { error });
289
+ }
290
+ async function fetchLinearIssue(identifier) {
291
+ try {
292
+ logger.debug(`Fetching Linear issue: ${identifier}`);
293
+ const client = createLinearClient();
294
+ const issue = await client.issue(identifier);
295
+ if (!issue) {
296
+ throw new LinearServiceError("NOT_FOUND", `Linear issue ${identifier} not found`);
297
+ }
298
+ const result = {
299
+ id: issue.id,
300
+ identifier: issue.identifier,
301
+ title: issue.title,
302
+ url: issue.url,
303
+ createdAt: issue.createdAt.toISOString(),
304
+ updatedAt: issue.updatedAt.toISOString()
305
+ };
306
+ if (issue.description) {
307
+ result.description = issue.description;
308
+ }
309
+ if (issue.state) {
310
+ const state = await issue.state;
311
+ if (state == null ? void 0 : state.name) {
312
+ result.state = state.name;
313
+ }
314
+ }
315
+ return result;
316
+ } catch (error) {
317
+ if (error instanceof LinearServiceError) {
318
+ throw error;
319
+ }
320
+ handleLinearError(error, "fetchLinearIssue");
321
+ }
322
+ }
323
+ async function createLinearComment(identifier, body) {
324
+ try {
325
+ logger.debug(`Creating comment on Linear issue ${identifier}`);
326
+ const client = createLinearClient();
327
+ const issue = await client.issue(identifier);
328
+ if (!issue) {
329
+ throw new LinearServiceError("NOT_FOUND", `Linear issue ${identifier} not found`);
330
+ }
331
+ const payload = await client.createComment({
332
+ issueId: issue.id,
333
+ body
334
+ });
335
+ const comment = await payload.comment;
336
+ if (!comment) {
337
+ throw new LinearServiceError("CLI_ERROR", "Failed to create Linear comment");
338
+ }
339
+ return {
340
+ id: comment.id,
341
+ body: comment.body,
342
+ createdAt: comment.createdAt.toISOString(),
343
+ updatedAt: comment.updatedAt.toISOString(),
344
+ url: comment.url
345
+ };
346
+ } catch (error) {
347
+ if (error instanceof LinearServiceError) {
348
+ throw error;
349
+ }
350
+ handleLinearError(error, "createLinearComment");
351
+ }
352
+ }
353
+ async function getLinearComment(commentId) {
354
+ try {
355
+ logger.debug(`Fetching Linear comment: ${commentId}`);
356
+ const client = createLinearClient();
357
+ const comment = await client.comment({ id: commentId });
358
+ if (!comment) {
359
+ throw new LinearServiceError("NOT_FOUND", `Linear comment ${commentId} not found`);
360
+ }
361
+ return {
362
+ id: comment.id,
363
+ body: comment.body,
364
+ createdAt: comment.createdAt.toISOString(),
365
+ updatedAt: comment.updatedAt.toISOString(),
366
+ url: comment.url
367
+ };
368
+ } catch (error) {
369
+ if (error instanceof LinearServiceError) {
370
+ throw error;
371
+ }
372
+ handleLinearError(error, "getLinearComment");
373
+ }
374
+ }
375
+ async function updateLinearComment(commentId, body) {
376
+ try {
377
+ logger.debug(`Updating Linear comment: ${commentId}`);
378
+ const client = createLinearClient();
379
+ const payload = await client.updateComment(commentId, { body });
380
+ const comment = await payload.comment;
381
+ if (!comment) {
382
+ throw new LinearServiceError("CLI_ERROR", "Failed to update Linear comment");
383
+ }
384
+ return {
385
+ id: comment.id,
386
+ body: comment.body,
387
+ createdAt: comment.createdAt.toISOString(),
388
+ updatedAt: comment.updatedAt.toISOString(),
389
+ url: comment.url
390
+ };
391
+ } catch (error) {
392
+ if (error instanceof LinearServiceError) {
393
+ throw error;
394
+ }
395
+ handleLinearError(error, "updateLinearComment");
396
+ }
397
+ }
398
+ async function fetchLinearIssueComments(identifier) {
399
+ try {
400
+ logger.debug(`Fetching comments for Linear issue: ${identifier}`);
401
+ const client = createLinearClient();
402
+ const issue = await client.issue(identifier);
403
+ if (!issue) {
404
+ throw new LinearServiceError("NOT_FOUND", `Linear issue ${identifier} not found`);
405
+ }
406
+ const comments = await issue.comments({ first: 100 });
407
+ return comments.nodes.map((comment) => ({
408
+ id: comment.id,
409
+ body: comment.body,
410
+ createdAt: comment.createdAt.toISOString(),
411
+ updatedAt: comment.updatedAt.toISOString(),
412
+ url: comment.url
413
+ }));
414
+ } catch (error) {
415
+ if (error instanceof LinearServiceError) {
416
+ throw error;
417
+ }
418
+ handleLinearError(error, "fetchLinearIssueComments");
419
+ }
420
+ }
421
+
422
+ // src/utils/linear-markup-converter.ts
423
+ import { appendFileSync } from "fs";
424
+ import { join, dirname, basename, extname } from "path";
425
+ var LinearMarkupConverter = class {
426
+ /**
427
+ * Convert HTML details/summary blocks to Linear's collapsible format
428
+ * Handles nested details blocks recursively
429
+ *
430
+ * @param text - Text containing HTML details/summary blocks
431
+ * @returns Text with details/summary converted to Linear format
432
+ */
433
+ static convertDetailsToLinear(text) {
434
+ if (!text) {
435
+ return text;
436
+ }
437
+ let previousText = "";
438
+ let currentText = text;
439
+ while (previousText !== currentText) {
440
+ previousText = currentText;
441
+ currentText = this.convertSinglePass(currentText);
442
+ }
443
+ return currentText;
444
+ }
445
+ /**
446
+ * Perform a single pass of details block conversion
447
+ * Converts the innermost details blocks first
448
+ */
449
+ static convertSinglePass(text) {
450
+ const detailsRegex = /<details[^>]*>\s*<summary[^>]*>(.*?)<\/summary>\s*(.*?)\s*<\/details>/gis;
451
+ return text.replace(detailsRegex, (_match, summary, content) => {
452
+ const cleanSummary = this.cleanText(summary);
453
+ const cleanContent = this.cleanContent(content);
454
+ if (cleanContent) {
455
+ return `+++ ${cleanSummary}
456
+
457
+ ${cleanContent}
458
+
459
+ +++`;
460
+ } else {
461
+ return `+++ ${cleanSummary}
462
+
463
+ +++`;
464
+ }
465
+ });
466
+ }
467
+ /**
468
+ * Clean text by trimming whitespace and decoding common HTML entities
469
+ */
470
+ static cleanText(text) {
471
+ return text.trim().replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'");
472
+ }
473
+ /**
474
+ * Clean content while preserving internal structure
475
+ * - Removes leading/trailing whitespace
476
+ * - Normalizes internal blank lines (max 2 consecutive newlines)
477
+ * - Preserves code blocks and other formatting
478
+ */
479
+ static cleanContent(content) {
480
+ if (!content) {
481
+ return "";
482
+ }
483
+ let cleaned = content.trim();
484
+ cleaned = cleaned.replace(/\n{3,}/g, "\n\n");
485
+ return cleaned;
486
+ }
487
+ /**
488
+ * Check if text contains HTML details/summary blocks
489
+ * Useful for conditional conversion
490
+ */
491
+ static hasDetailsBlocks(text) {
492
+ if (!text) {
493
+ return false;
494
+ }
495
+ const detailsRegex = /<details[^>]*>.*?<summary[^>]*>.*?<\/summary>.*?<\/details>/is;
496
+ return detailsRegex.test(text);
497
+ }
498
+ /**
499
+ * Remove wrapper tags from code sample details blocks
500
+ * Identifies details blocks where summary contains "X lines" pattern
501
+ * and removes the details/summary tags while preserving the content
502
+ *
503
+ * @param text - Text containing potential code sample details blocks
504
+ * @returns Text with code sample wrappers removed
505
+ */
506
+ static removeCodeSampleWrappers(text) {
507
+ if (!text) {
508
+ return text;
509
+ }
510
+ const codeSampleRegex = /<details[^>]*>\s*<summary[^>]*>([^<]*\d+\s+lines[^<]*)<\/summary>\s*([\s\S]*?)<\/details>/gi;
511
+ return text.replace(codeSampleRegex, (_match, _summary, content) => {
512
+ return content.trim();
513
+ });
514
+ }
515
+ /**
516
+ * Convert text for Linear - applies all necessary conversions
517
+ * Currently only converts details/summary blocks, but can be extended
518
+ * for other HTML to Linear markdown conversions
519
+ */
520
+ static convertToLinear(text) {
521
+ if (!text) {
522
+ return text;
523
+ }
524
+ this.logConversion("INPUT", text);
525
+ let converted = text;
526
+ converted = this.removeCodeSampleWrappers(converted);
527
+ converted = this.convertDetailsToLinear(converted);
528
+ this.logConversion("OUTPUT", converted);
529
+ return converted;
530
+ }
531
+ /**
532
+ * Log conversion input/output if LINEAR_MARKDOWN_LOG_FILE is set
533
+ */
534
+ static logConversion(label, content) {
535
+ const logFilePath = process.env.LINEAR_MARKDOWN_LOG_FILE;
536
+ if (!logFilePath) {
537
+ return;
538
+ }
539
+ try {
540
+ const timestampedPath = this.getTimestampedLogPath(logFilePath);
541
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
542
+ const separator = "================================";
543
+ const logEntry = `${separator}
544
+ [${timestamp}] CONVERSION ${label}
545
+ ${separator}
546
+ ${label}:
547
+ ${content}
548
+
549
+ `;
550
+ appendFileSync(timestampedPath, logEntry, "utf-8");
551
+ } catch {
552
+ }
553
+ }
554
+ /**
555
+ * Generate timestamped log file path
556
+ * Example: debug.log -> debug-20231202-161234.log
557
+ */
558
+ static getTimestampedLogPath(logFilePath) {
559
+ const dir = dirname(logFilePath);
560
+ const ext = extname(logFilePath);
561
+ const base = basename(logFilePath, ext);
562
+ const now = /* @__PURE__ */ new Date();
563
+ const timestamp = [
564
+ now.getFullYear(),
565
+ String(now.getMonth() + 1).padStart(2, "0"),
566
+ String(now.getDate()).padStart(2, "0")
567
+ ].join("") + "-" + [
568
+ String(now.getHours()).padStart(2, "0"),
569
+ String(now.getMinutes()).padStart(2, "0"),
570
+ String(now.getSeconds()).padStart(2, "0")
571
+ ].join("");
572
+ return join(dir, `${base}-${timestamp}${ext}`);
573
+ }
574
+ };
575
+
576
+ // src/mcp/LinearIssueManagementProvider.ts
577
+ var LinearIssueManagementProvider = class {
578
+ constructor() {
579
+ this.providerName = "linear";
580
+ }
581
+ /**
582
+ * Fetch issue details using Linear SDK
583
+ */
584
+ async getIssue(input) {
585
+ const { number, includeComments = true } = input;
586
+ const raw = await fetchLinearIssue(number);
587
+ const state = raw.state && (raw.state.toLowerCase().includes("done") || raw.state.toLowerCase().includes("completed") || raw.state.toLowerCase().includes("canceled")) ? "closed" : "open";
588
+ const result = {
589
+ id: raw.identifier,
590
+ title: raw.title,
591
+ body: raw.description ?? "",
592
+ state,
593
+ url: raw.url,
594
+ provider: "linear",
595
+ author: null,
596
+ // Linear SDK doesn't return author in basic fetch
597
+ // Linear-specific fields
598
+ linearState: raw.state,
599
+ createdAt: raw.createdAt,
600
+ updatedAt: raw.updatedAt
601
+ };
602
+ if (includeComments) {
603
+ try {
604
+ const comments = await this.fetchIssueComments(number);
605
+ if (comments) {
606
+ result.comments = comments;
607
+ }
608
+ } catch {
609
+ }
610
+ }
611
+ return result;
612
+ }
613
+ /**
614
+ * Fetch comments for an issue
615
+ */
616
+ async fetchIssueComments(identifier) {
617
+ try {
618
+ const comments = await fetchLinearIssueComments(identifier);
619
+ return comments.map((comment) => ({
620
+ id: comment.id,
621
+ body: comment.body,
622
+ createdAt: comment.createdAt,
623
+ author: null,
624
+ // Linear SDK doesn't return comment author info in basic fetch
625
+ ...comment.updatedAt && { updatedAt: comment.updatedAt }
626
+ }));
627
+ } catch {
628
+ return [];
629
+ }
630
+ }
631
+ /**
632
+ * Fetch a specific comment by ID
633
+ */
634
+ async getComment(input) {
635
+ const { commentId } = input;
636
+ const raw = await getLinearComment(commentId);
637
+ return {
638
+ id: raw.id,
639
+ body: raw.body,
640
+ author: null,
641
+ // Linear SDK doesn't return comment author info in basic fetch
642
+ created_at: raw.createdAt
643
+ };
644
+ }
645
+ /**
646
+ * Create a new comment on an issue
647
+ */
648
+ async createComment(input) {
649
+ const { number, body } = input;
650
+ const convertedBody = LinearMarkupConverter.convertToLinear(body);
651
+ const result = await createLinearComment(number, convertedBody);
652
+ return {
653
+ id: result.id,
654
+ url: result.url,
655
+ created_at: result.createdAt
656
+ };
657
+ }
658
+ /**
659
+ * Update an existing comment
660
+ */
661
+ async updateComment(input) {
662
+ const { commentId, body } = input;
663
+ const convertedBody = LinearMarkupConverter.convertToLinear(body);
664
+ const result = await updateLinearComment(commentId, convertedBody);
665
+ return {
666
+ id: result.id,
667
+ url: result.url,
668
+ updated_at: result.updatedAt
669
+ };
670
+ }
671
+ };
672
+
673
+ // src/mcp/IssueManagementProviderFactory.ts
674
+ var IssueManagementProviderFactory = class {
675
+ /**
676
+ * Create an issue management provider based on the provider type
677
+ */
678
+ static create(provider) {
679
+ switch (provider) {
680
+ case "github":
681
+ return new GitHubIssueManagementProvider();
682
+ case "linear":
683
+ return new LinearIssueManagementProvider();
684
+ default:
685
+ throw new Error(`Unsupported issue management provider: ${provider}`);
686
+ }
687
+ }
688
+ };
689
+
690
+ // src/mcp/issue-management-server.ts
691
+ function validateEnvironment() {
692
+ const provider = process.env.ISSUE_PROVIDER;
693
+ if (!provider) {
694
+ console.error("Missing required environment variable: ISSUE_PROVIDER");
695
+ process.exit(1);
696
+ }
697
+ if (provider !== "github" && provider !== "linear") {
698
+ console.error(`Invalid ISSUE_PROVIDER: ${provider}. Must be 'github' or 'linear'`);
699
+ process.exit(1);
700
+ }
701
+ if (provider === "github") {
702
+ const required = ["REPO_OWNER", "REPO_NAME"];
703
+ const missing = required.filter((key) => !process.env[key]);
704
+ if (missing.length > 0) {
705
+ console.error(
706
+ `Missing required environment variables for GitHub provider: ${missing.join(", ")}`
707
+ );
708
+ process.exit(1);
709
+ }
710
+ }
711
+ if (provider === "linear") {
712
+ if (!process.env.LINEAR_API_TOKEN) {
713
+ console.error("Missing required environment variable for Linear provider: LINEAR_API_TOKEN");
714
+ process.exit(1);
715
+ }
716
+ }
717
+ return provider;
718
+ }
719
+ var server = new McpServer({
720
+ name: "issue-management-broker",
721
+ version: "0.1.0"
722
+ });
723
+ var flexibleAuthorSchema = z.object({
724
+ id: z.string(),
725
+ displayName: z.string()
726
+ }).passthrough();
727
+ server.registerTool(
728
+ "get_issue",
729
+ {
730
+ title: "Get Issue",
731
+ 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.",
732
+ inputSchema: {
733
+ number: z.string().describe("The issue identifier"),
734
+ includeComments: z.boolean().optional().describe("Whether to include comments (default: true)")
735
+ },
736
+ outputSchema: {
737
+ // Core validated fields
738
+ id: z.string().describe("Issue identifier"),
739
+ title: z.string().describe("Issue title"),
740
+ body: z.string().describe("Issue body/description"),
741
+ state: z.string().describe("Issue state (open, closed, etc.)"),
742
+ url: z.string().describe("Issue URL"),
743
+ provider: z.enum(["github", "linear"]).describe("Issue management provider"),
744
+ // Flexible author - core fields + passthrough
745
+ author: flexibleAuthorSchema.nullable().describe(
746
+ "Issue author with normalized { id, displayName } plus provider-specific fields"
747
+ ),
748
+ // Optional flexible arrays
749
+ assignees: z.array(flexibleAuthorSchema).optional().describe(
750
+ "Issue assignees with normalized { id, displayName } plus provider-specific fields"
751
+ ),
752
+ labels: z.array(
753
+ z.object({ name: z.string() }).passthrough()
754
+ ).optional().describe("Issue labels"),
755
+ // Comments with flexible author
756
+ comments: z.array(
757
+ z.object({
758
+ id: z.string(),
759
+ body: z.string(),
760
+ author: flexibleAuthorSchema.nullable(),
761
+ createdAt: z.string()
762
+ }).passthrough()
763
+ ).optional().describe("Issue comments with flexible author structure")
764
+ }
765
+ },
766
+ async ({ number, includeComments }) => {
767
+ console.error(`Fetching issue ${number}`);
768
+ try {
769
+ const provider = IssueManagementProviderFactory.create(
770
+ process.env.ISSUE_PROVIDER
771
+ );
772
+ const result = await provider.getIssue({ number, includeComments });
773
+ console.error(`Issue fetched successfully: ${result.number} - ${result.title}`);
774
+ return {
775
+ content: [
776
+ {
777
+ type: "text",
778
+ text: JSON.stringify(result)
779
+ }
780
+ ],
781
+ structuredContent: result
782
+ };
783
+ } catch (error) {
784
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
785
+ console.error(`Failed to fetch issue: ${errorMessage}`);
786
+ throw new Error(`Failed to fetch issue: ${errorMessage}`);
787
+ }
788
+ }
789
+ );
790
+ server.registerTool(
791
+ "get_comment",
792
+ {
793
+ title: "Get Comment",
794
+ description: "Fetch a specific comment by ID. Author has normalized core fields { id, displayName } plus provider-specific fields.",
795
+ inputSchema: {
796
+ commentId: z.string().describe("The comment identifier to fetch"),
797
+ number: z.string().describe("The issue or PR identifier (context for providers that need it)")
798
+ },
799
+ outputSchema: {
800
+ id: z.string().describe("Comment identifier"),
801
+ body: z.string().describe("Comment body content"),
802
+ author: flexibleAuthorSchema.nullable().describe(
803
+ "Comment author with normalized { id, displayName } plus provider-specific fields"
804
+ ),
805
+ created_at: z.string().describe("Comment creation timestamp"),
806
+ updated_at: z.string().optional().describe("Comment last updated timestamp")
807
+ }
808
+ },
809
+ async ({ commentId, number }) => {
810
+ console.error(`Fetching comment ${commentId} from issue ${number}`);
811
+ try {
812
+ const provider = IssueManagementProviderFactory.create(
813
+ process.env.ISSUE_PROVIDER
814
+ );
815
+ const result = await provider.getComment({ commentId, number });
816
+ console.error(`Comment fetched successfully: ${result.id}`);
817
+ return {
818
+ content: [
819
+ {
820
+ type: "text",
821
+ text: JSON.stringify(result)
822
+ }
823
+ ],
824
+ structuredContent: result
825
+ };
826
+ } catch (error) {
827
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
828
+ console.error(`Failed to fetch comment: ${errorMessage}`);
829
+ throw new Error(`Failed to fetch comment: ${errorMessage}`);
830
+ }
831
+ }
832
+ );
833
+ server.registerTool(
834
+ "create_comment",
835
+ {
836
+ title: "Create Comment",
837
+ description: "Create a new comment on an issue or pull request. Use this to start tracking a workflow phase.",
838
+ inputSchema: {
839
+ number: z.string().describe("The issue or PR identifier"),
840
+ body: z.string().describe("The comment body (markdown supported)"),
841
+ type: z.enum(["issue", "pr"]).describe("Type of entity to comment on (issue or pr)")
842
+ },
843
+ outputSchema: {
844
+ id: z.string(),
845
+ url: z.string(),
846
+ created_at: z.string().optional()
847
+ }
848
+ },
849
+ async ({ number, body, type }) => {
850
+ console.error(`Creating ${type} comment on ${number}`);
851
+ try {
852
+ const provider = IssueManagementProviderFactory.create(
853
+ process.env.ISSUE_PROVIDER
854
+ );
855
+ const result = await provider.createComment({ number, body, type });
856
+ console.error(
857
+ `Comment created successfully: ${result.id} at ${result.url}`
858
+ );
859
+ return {
860
+ content: [
861
+ {
862
+ type: "text",
863
+ text: JSON.stringify(result)
864
+ }
865
+ ],
866
+ structuredContent: result
867
+ };
868
+ } catch (error) {
869
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
870
+ console.error(`Failed to create comment: ${errorMessage}`);
871
+ throw new Error(`Failed to create ${type} comment: ${errorMessage}`);
872
+ }
873
+ }
874
+ );
875
+ server.registerTool(
876
+ "update_comment",
877
+ {
878
+ title: "Update Comment",
879
+ description: "Update an existing comment. Use this to update progress during a workflow phase.",
880
+ inputSchema: {
881
+ commentId: z.string().describe("The comment identifier to update"),
882
+ number: z.string().describe("The issue or PR identifier (context for providers that need it)"),
883
+ body: z.string().describe("The updated comment body (markdown supported)")
884
+ },
885
+ outputSchema: {
886
+ id: z.string(),
887
+ url: z.string(),
888
+ updated_at: z.string().optional()
889
+ }
890
+ },
891
+ async ({ commentId, number, body }) => {
892
+ console.error(`Updating comment ${commentId} on issue ${number}`);
893
+ try {
894
+ const provider = IssueManagementProviderFactory.create(
895
+ process.env.ISSUE_PROVIDER
896
+ );
897
+ const result = await provider.updateComment({ commentId, number, body });
898
+ console.error(
899
+ `Comment updated successfully: ${result.id} at ${result.url}`
900
+ );
901
+ return {
902
+ content: [
903
+ {
904
+ type: "text",
905
+ text: JSON.stringify(result)
906
+ }
907
+ ],
908
+ structuredContent: result
909
+ };
910
+ } catch (error) {
911
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
912
+ console.error(`Failed to update comment: ${errorMessage}`);
913
+ throw new Error(`Failed to update comment: ${errorMessage}`);
914
+ }
915
+ }
916
+ );
917
+ async function main() {
918
+ console.error("Starting Issue Management MCP Server...");
919
+ const provider = validateEnvironment();
920
+ console.error("Environment validated");
921
+ console.error(`Issue management provider: ${provider}`);
922
+ if (provider === "github") {
923
+ console.error(`Repository: ${process.env.REPO_OWNER}/${process.env.REPO_NAME}`);
924
+ console.error(`Event type: ${process.env.GITHUB_EVENT_NAME ?? "not specified"}`);
925
+ }
926
+ const transport = new StdioServerTransport();
927
+ await server.connect(transport);
928
+ console.error("Issue Management MCP Server running on stdio transport");
929
+ }
930
+ main().catch((error) => {
931
+ console.error("Fatal error starting MCP server:", error);
932
+ process.exit(1);
933
+ });
934
+ //# sourceMappingURL=issue-management-server.js.map