@iloom/cli 0.7.4 → 0.7.6
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.
- package/LICENSE +1 -1
- package/README.md +2 -4
- package/dist/{BranchNamingService-UB2EJGFQ.js → BranchNamingService-AO7BPIUJ.js} +2 -2
- package/dist/{ClaudeContextManager-M57BQUMY.js → ClaudeContextManager-Y2YJC6BU.js} +4 -4
- package/dist/{ClaudeService-FLZ2IXAO.js → ClaudeService-NDVFQRKC.js} +3 -3
- package/dist/{LoomLauncher-5PPVFTFN.js → LoomLauncher-U2B3VHPC.js} +4 -4
- package/dist/{PRManager-YTG6XPMG.js → PRManager-7F3AAY66.js} +4 -4
- package/dist/README.md +2 -4
- package/dist/agents/iloom-issue-analyze-and-plan.md +1 -1
- package/dist/agents/iloom-issue-analyzer.md +1 -1
- package/dist/agents/iloom-issue-complexity-evaluator.md +1 -1
- package/dist/agents/iloom-issue-enhancer.md +1 -1
- package/dist/agents/iloom-issue-implementer.md +1 -1
- package/dist/agents/iloom-issue-planner.md +1 -1
- package/dist/agents/iloom-issue-reviewer.md +1 -1
- package/dist/{chunk-7GKMQJGQ.js → chunk-64HCHVJM.js} +2 -2
- package/dist/{chunk-JWUYPJ7K.js → chunk-6YAMWLCP.js} +3 -3
- package/dist/{chunk-33P5VSKS.js → chunk-C7YW5IMS.js} +2 -2
- package/dist/{chunk-37V2NBYR.js → chunk-CAXFWFV6.js} +2 -2
- package/dist/chunk-CFQVOTHO.js +111 -0
- package/dist/chunk-CFQVOTHO.js.map +1 -0
- package/dist/{chunk-AFRICMSW.js → chunk-ENMTWE74.js} +2 -2
- package/dist/{chunk-RVLRPQU4.js → chunk-ETY2SBW5.js} +21 -18
- package/dist/chunk-ETY2SBW5.js.map +1 -0
- package/dist/{chunk-ITIXKM24.js → chunk-IGKPPACU.js} +2 -2
- package/dist/chunk-IGKPPACU.js.map +1 -0
- package/dist/{chunk-GH4FLYV5.js → chunk-NEPH2O4C.js} +2 -2
- package/dist/{chunk-GJMEKEI5.js → chunk-NPEMVE27.js} +342 -6
- package/dist/chunk-NPEMVE27.js.map +1 -0
- package/dist/{chunk-XAHE76RL.js → chunk-O36JLYNW.js} +2 -2
- package/dist/{chunk-6VQNF44G.js → chunk-Q457PKGH.js} +2 -2
- package/dist/{chunk-7FM7AL7S.js → chunk-VYKKWU36.js} +2 -2
- package/dist/{chunk-EDDIAWVM.js → chunk-WT4UGBE2.js} +8 -7
- package/dist/chunk-WT4UGBE2.js.map +1 -0
- package/dist/{chunk-453NC377.js → chunk-WZYBHD7P.js} +3 -106
- package/dist/chunk-WZYBHD7P.js.map +1 -0
- package/dist/{claude-SNWHWWWM.js → claude-V4HRPR4Z.js} +2 -2
- package/dist/{cleanup-PLMS2KWF.js → cleanup-IO4KV2DL.js} +9 -6
- package/dist/{cleanup-PLMS2KWF.js.map → cleanup-IO4KV2DL.js.map} +1 -1
- package/dist/cli.js +84 -63
- package/dist/cli.js.map +1 -1
- package/dist/{commit-NAGJH4J4.js → commit-3ULFKXNB.js} +4 -4
- package/dist/{dev-server-UKAPBGUR.js → dev-server-OAP3RZC6.js} +4 -3
- package/dist/{dev-server-UKAPBGUR.js.map → dev-server-OAP3RZC6.js.map} +1 -1
- package/dist/{feedback-ICJ44XGB.js → feedback-ZLAX3BVL.js} +3 -3
- package/dist/{ignite-U2JSVOEZ.js → ignite-HA2OJF6Z.js} +20 -36
- package/dist/ignite-HA2OJF6Z.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/{init-YDKOPB54.js → init-S6IEGRSX.js} +3 -3
- package/dist/mcp/issue-management-server.js +420 -14
- package/dist/mcp/issue-management-server.js.map +1 -1
- package/dist/{open-QI63XQ4F.js → open-IN3LUZXX.js} +4 -3
- package/dist/{open-QI63XQ4F.js.map → open-IN3LUZXX.js.map} +1 -1
- package/dist/{projects-TWY4RT2Z.js → projects-CTRTTMSK.js} +25 -9
- package/dist/projects-CTRTTMSK.js.map +1 -0
- package/dist/prompts/issue-prompt.txt +16 -0
- package/dist/prompts/pr-prompt.txt +33 -13
- package/dist/prompts/regular-prompt.txt +7 -0
- package/dist/{rebase-AONLKM2V.js → rebase-RLEVFHWN.js} +3 -3
- package/dist/{run-YDVYORT2.js → run-QEIS2EH2.js} +4 -3
- package/dist/{run-YDVYORT2.js.map → run-QEIS2EH2.js.map} +1 -1
- package/dist/{summary-7KYFRAIM.js → summary-MPOOQIOX.js} +38 -7
- package/dist/summary-MPOOQIOX.js.map +1 -0
- package/dist/{test-webserver-NRMGT2HB.js → test-webserver-J6SMNLU2.js} +3 -2
- package/dist/{test-webserver-NRMGT2HB.js.map → test-webserver-J6SMNLU2.js.map} +1 -1
- package/package.json +1 -1
- package/dist/chunk-453NC377.js.map +0 -1
- package/dist/chunk-EDDIAWVM.js.map +0 -1
- package/dist/chunk-GJMEKEI5.js.map +0 -1
- package/dist/chunk-ITIXKM24.js.map +0 -1
- package/dist/chunk-RVLRPQU4.js.map +0 -1
- package/dist/ignite-U2JSVOEZ.js.map +0 -1
- package/dist/projects-TWY4RT2Z.js.map +0 -1
- package/dist/summary-7KYFRAIM.js.map +0 -1
- /package/dist/{BranchNamingService-UB2EJGFQ.js.map → BranchNamingService-AO7BPIUJ.js.map} +0 -0
- /package/dist/{ClaudeContextManager-M57BQUMY.js.map → ClaudeContextManager-Y2YJC6BU.js.map} +0 -0
- /package/dist/{ClaudeService-FLZ2IXAO.js.map → ClaudeService-NDVFQRKC.js.map} +0 -0
- /package/dist/{LoomLauncher-5PPVFTFN.js.map → LoomLauncher-U2B3VHPC.js.map} +0 -0
- /package/dist/{PRManager-YTG6XPMG.js.map → PRManager-7F3AAY66.js.map} +0 -0
- /package/dist/{chunk-7GKMQJGQ.js.map → chunk-64HCHVJM.js.map} +0 -0
- /package/dist/{chunk-JWUYPJ7K.js.map → chunk-6YAMWLCP.js.map} +0 -0
- /package/dist/{chunk-33P5VSKS.js.map → chunk-C7YW5IMS.js.map} +0 -0
- /package/dist/{chunk-37V2NBYR.js.map → chunk-CAXFWFV6.js.map} +0 -0
- /package/dist/{chunk-AFRICMSW.js.map → chunk-ENMTWE74.js.map} +0 -0
- /package/dist/{chunk-GH4FLYV5.js.map → chunk-NEPH2O4C.js.map} +0 -0
- /package/dist/{chunk-XAHE76RL.js.map → chunk-O36JLYNW.js.map} +0 -0
- /package/dist/{chunk-6VQNF44G.js.map → chunk-Q457PKGH.js.map} +0 -0
- /package/dist/{chunk-7FM7AL7S.js.map → chunk-VYKKWU36.js.map} +0 -0
- /package/dist/{claude-SNWHWWWM.js.map → claude-V4HRPR4Z.js.map} +0 -0
- /package/dist/{commit-NAGJH4J4.js.map → commit-3ULFKXNB.js.map} +0 -0
- /package/dist/{feedback-ICJ44XGB.js.map → feedback-ZLAX3BVL.js.map} +0 -0
- /package/dist/{init-YDKOPB54.js.map → init-S6IEGRSX.js.map} +0 -0
- /package/dist/{rebase-AONLKM2V.js.map → rebase-RLEVFHWN.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
InitCommand
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-C7YW5IMS.js";
|
|
5
5
|
import "./chunk-Q7POFB5Q.js";
|
|
6
6
|
import "./chunk-F2PWIRV4.js";
|
|
7
7
|
import "./chunk-N7FVXZNI.js";
|
|
@@ -12,10 +12,10 @@ import "./chunk-ZA575VLF.js";
|
|
|
12
12
|
import "./chunk-WFQ5CLTR.js";
|
|
13
13
|
import "./chunk-VWGKGNJP.js";
|
|
14
14
|
import "./chunk-ZX3GTM7O.js";
|
|
15
|
-
import "./chunk-
|
|
15
|
+
import "./chunk-IGKPPACU.js";
|
|
16
16
|
import "./chunk-6MLEBAYZ.js";
|
|
17
17
|
import "./chunk-VT4PDUYT.js";
|
|
18
18
|
export {
|
|
19
19
|
InitCommand
|
|
20
20
|
};
|
|
21
|
-
//# sourceMappingURL=init-
|
|
21
|
+
//# sourceMappingURL=init-S6IEGRSX.js.map
|
|
@@ -236,6 +236,244 @@ async function addSubIssue(parentNodeId, childNodeId) {
|
|
|
236
236
|
]);
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
+
// src/utils/image-processor.ts
|
|
240
|
+
import { tmpdir } from "os";
|
|
241
|
+
import { join, extname } from "path";
|
|
242
|
+
import { existsSync as existsSync2, mkdirSync, createWriteStream, unlinkSync } from "fs";
|
|
243
|
+
import { pipeline } from "stream/promises";
|
|
244
|
+
import { Readable } from "stream";
|
|
245
|
+
import { createHash } from "crypto";
|
|
246
|
+
import { execa as execa3 } from "execa";
|
|
247
|
+
var SUPPORTED_EXTENSIONS = [".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"];
|
|
248
|
+
var MAX_IMAGE_SIZE = 10 * 1024 * 1024;
|
|
249
|
+
var REQUEST_TIMEOUT_MS = 3e4;
|
|
250
|
+
var CACHE_DIR = join(tmpdir(), "iloom-images");
|
|
251
|
+
var cachedGitHubToken;
|
|
252
|
+
function extractMarkdownImageUrls(content) {
|
|
253
|
+
if (!content) {
|
|
254
|
+
return [];
|
|
255
|
+
}
|
|
256
|
+
const matches = [];
|
|
257
|
+
const markdownRegex = /!\[([^\]]*)\]\(((?:[^()\s]|\((?:[^()\s]|\([^()]*\))*\))+)\)/g;
|
|
258
|
+
let match;
|
|
259
|
+
while ((match = markdownRegex.exec(content)) !== null) {
|
|
260
|
+
const url = match[2];
|
|
261
|
+
if (url) {
|
|
262
|
+
matches.push({
|
|
263
|
+
fullMatch: match[0],
|
|
264
|
+
url,
|
|
265
|
+
isMarkdown: true
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const htmlImgRegex = /<img\s+[^>]*src=["']([^"']+)["'][^>]*\/?>/gi;
|
|
270
|
+
while ((match = htmlImgRegex.exec(content)) !== null) {
|
|
271
|
+
const url = match[1];
|
|
272
|
+
if (url) {
|
|
273
|
+
matches.push({
|
|
274
|
+
fullMatch: match[0],
|
|
275
|
+
url,
|
|
276
|
+
isMarkdown: false
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return matches;
|
|
281
|
+
}
|
|
282
|
+
function isAuthenticatedImageUrl(url) {
|
|
283
|
+
try {
|
|
284
|
+
const parsedUrl = new URL(url);
|
|
285
|
+
const hostname = parsedUrl.hostname.toLowerCase();
|
|
286
|
+
if (hostname === "uploads.linear.app") {
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
if (hostname === "private-user-images.githubusercontent.com") {
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
if (hostname === "github.com" && parsedUrl.pathname.startsWith("/user-attachments/assets/")) {
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
return false;
|
|
296
|
+
} catch {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function getExtensionFromUrl(url) {
|
|
301
|
+
try {
|
|
302
|
+
const parsedUrl = new URL(url);
|
|
303
|
+
const pathname = parsedUrl.pathname;
|
|
304
|
+
const ext = extname(pathname).toLowerCase();
|
|
305
|
+
if (SUPPORTED_EXTENSIONS.includes(ext)) {
|
|
306
|
+
return ext;
|
|
307
|
+
}
|
|
308
|
+
return null;
|
|
309
|
+
} catch {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function getCacheKey(url) {
|
|
314
|
+
const parsedUrl = new URL(url);
|
|
315
|
+
if (parsedUrl.hostname === "private-user-images.githubusercontent.com") {
|
|
316
|
+
parsedUrl.searchParams.delete("jwt");
|
|
317
|
+
}
|
|
318
|
+
const stableUrl = parsedUrl.toString();
|
|
319
|
+
const hash = createHash("sha256").update(stableUrl).digest("hex").slice(0, 16);
|
|
320
|
+
const ext = getExtensionFromUrl(url) ?? ".png";
|
|
321
|
+
return `${hash}${ext}`;
|
|
322
|
+
}
|
|
323
|
+
function getCachedImagePath(url) {
|
|
324
|
+
const cacheKey = getCacheKey(url);
|
|
325
|
+
const cachedPath = join(CACHE_DIR, cacheKey);
|
|
326
|
+
if (existsSync2(cachedPath)) {
|
|
327
|
+
return cachedPath;
|
|
328
|
+
}
|
|
329
|
+
return void 0;
|
|
330
|
+
}
|
|
331
|
+
async function getAuthToken(provider) {
|
|
332
|
+
if (provider === "github") {
|
|
333
|
+
if (cachedGitHubToken !== void 0) {
|
|
334
|
+
return cachedGitHubToken;
|
|
335
|
+
}
|
|
336
|
+
try {
|
|
337
|
+
const result = await execa3("gh", ["auth", "token"]);
|
|
338
|
+
cachedGitHubToken = result.stdout.trim();
|
|
339
|
+
return cachedGitHubToken;
|
|
340
|
+
} catch (error) {
|
|
341
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
342
|
+
logger.warn(`Failed to get GitHub auth token via gh CLI: ${message}`);
|
|
343
|
+
return void 0;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (provider === "linear") {
|
|
347
|
+
return process.env.LINEAR_API_TOKEN;
|
|
348
|
+
}
|
|
349
|
+
return void 0;
|
|
350
|
+
}
|
|
351
|
+
async function downloadAndSaveImage(url, destPath, authHeader) {
|
|
352
|
+
const headers = {};
|
|
353
|
+
if (authHeader) {
|
|
354
|
+
headers["Authorization"] = authHeader;
|
|
355
|
+
}
|
|
356
|
+
const controller = new AbortController();
|
|
357
|
+
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
358
|
+
try {
|
|
359
|
+
const response = await fetch(url, { headers, signal: controller.signal });
|
|
360
|
+
if (!response.ok) {
|
|
361
|
+
throw new Error(`Failed to download image: ${response.status} ${response.statusText}`);
|
|
362
|
+
}
|
|
363
|
+
const contentLength = response.headers.get("Content-Length");
|
|
364
|
+
if (contentLength && parseInt(contentLength, 10) > MAX_IMAGE_SIZE) {
|
|
365
|
+
throw new Error(`Image too large: ${contentLength} bytes exceeds ${MAX_IMAGE_SIZE} byte limit`);
|
|
366
|
+
}
|
|
367
|
+
if (!response.body) {
|
|
368
|
+
throw new Error("Response body is null");
|
|
369
|
+
}
|
|
370
|
+
const reader = response.body.getReader();
|
|
371
|
+
let bytesWritten = 0;
|
|
372
|
+
const nodeReadable = new Readable({
|
|
373
|
+
async read() {
|
|
374
|
+
try {
|
|
375
|
+
const { done, value } = await reader.read();
|
|
376
|
+
if (done) {
|
|
377
|
+
this.push(null);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
bytesWritten += value.byteLength;
|
|
381
|
+
if (bytesWritten > MAX_IMAGE_SIZE) {
|
|
382
|
+
reader.cancel();
|
|
383
|
+
this.destroy(new Error(`Image too large: ${bytesWritten} bytes exceeds ${MAX_IMAGE_SIZE} byte limit`));
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
this.push(Buffer.from(value));
|
|
387
|
+
} catch (err) {
|
|
388
|
+
this.destroy(err instanceof Error ? err : new Error(String(err)));
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
if (!existsSync2(CACHE_DIR)) {
|
|
393
|
+
mkdirSync(CACHE_DIR, { recursive: true });
|
|
394
|
+
}
|
|
395
|
+
const writeStream = createWriteStream(destPath);
|
|
396
|
+
try {
|
|
397
|
+
await pipeline(nodeReadable, writeStream);
|
|
398
|
+
} catch (pipelineError) {
|
|
399
|
+
try {
|
|
400
|
+
if (existsSync2(destPath)) {
|
|
401
|
+
unlinkSync(destPath);
|
|
402
|
+
}
|
|
403
|
+
} catch {
|
|
404
|
+
}
|
|
405
|
+
throw pipelineError;
|
|
406
|
+
}
|
|
407
|
+
} catch (error) {
|
|
408
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
409
|
+
throw new Error(`Image download timed out after ${REQUEST_TIMEOUT_MS}ms`);
|
|
410
|
+
}
|
|
411
|
+
throw error;
|
|
412
|
+
} finally {
|
|
413
|
+
clearTimeout(timeoutId);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
function getCacheDestPath(url) {
|
|
417
|
+
if (!existsSync2(CACHE_DIR)) {
|
|
418
|
+
mkdirSync(CACHE_DIR, { recursive: true });
|
|
419
|
+
}
|
|
420
|
+
const cacheKey = getCacheKey(url);
|
|
421
|
+
return join(CACHE_DIR, cacheKey);
|
|
422
|
+
}
|
|
423
|
+
function rewriteMarkdownUrls(content, urlMap) {
|
|
424
|
+
let result = content;
|
|
425
|
+
for (const [originalUrl, localPath] of urlMap) {
|
|
426
|
+
const escapedUrl = originalUrl.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
427
|
+
const urlRegex = new RegExp(escapedUrl, "g");
|
|
428
|
+
result = result.replace(urlRegex, localPath);
|
|
429
|
+
}
|
|
430
|
+
return result;
|
|
431
|
+
}
|
|
432
|
+
async function processMarkdownImages(content, provider) {
|
|
433
|
+
if (!content) {
|
|
434
|
+
return "";
|
|
435
|
+
}
|
|
436
|
+
const images = extractMarkdownImageUrls(content);
|
|
437
|
+
if (images.length === 0) {
|
|
438
|
+
return content;
|
|
439
|
+
}
|
|
440
|
+
const authImages = images.filter((img) => isAuthenticatedImageUrl(img.url));
|
|
441
|
+
if (authImages.length === 0) {
|
|
442
|
+
return content;
|
|
443
|
+
}
|
|
444
|
+
const authToken = await getAuthToken(provider);
|
|
445
|
+
const uniqueUrls = [...new Set(authImages.map((img) => img.url))];
|
|
446
|
+
const urlMap = /* @__PURE__ */ new Map();
|
|
447
|
+
const downloadPromises = uniqueUrls.map(async (url) => {
|
|
448
|
+
try {
|
|
449
|
+
const cachedPath = getCachedImagePath(url);
|
|
450
|
+
if (cachedPath) {
|
|
451
|
+
logger.debug(`Using cached image: ${cachedPath}`);
|
|
452
|
+
return { url, localPath: cachedPath };
|
|
453
|
+
}
|
|
454
|
+
logger.debug(`Downloading image: ${url}`);
|
|
455
|
+
const destPath = getCacheDestPath(url);
|
|
456
|
+
await downloadAndSaveImage(
|
|
457
|
+
url,
|
|
458
|
+
destPath,
|
|
459
|
+
authToken ? `Bearer ${authToken}` : void 0
|
|
460
|
+
);
|
|
461
|
+
return { url, localPath: destPath };
|
|
462
|
+
} catch (error) {
|
|
463
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
464
|
+
logger.warn(`Failed to download image ${url}: ${message}`);
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
const results = await Promise.all(downloadPromises);
|
|
469
|
+
for (const result of results) {
|
|
470
|
+
if (result !== null) {
|
|
471
|
+
urlMap.set(result.url, result.localPath);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return rewriteMarkdownUrls(content, urlMap);
|
|
475
|
+
}
|
|
476
|
+
|
|
239
477
|
// src/mcp/GitHubIssueManagementProvider.ts
|
|
240
478
|
function normalizeAuthor(author) {
|
|
241
479
|
if (!author) return null;
|
|
@@ -314,6 +552,86 @@ var GitHubIssueManagementProvider = class {
|
|
|
314
552
|
...comment.updatedAt && { updatedAt: comment.updatedAt }
|
|
315
553
|
}));
|
|
316
554
|
}
|
|
555
|
+
result.body = await processMarkdownImages(result.body, "github");
|
|
556
|
+
if (result.comments) {
|
|
557
|
+
for (const comment of result.comments) {
|
|
558
|
+
comment.body = await processMarkdownImages(comment.body, "github");
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return result;
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Fetch pull request details using gh CLI
|
|
565
|
+
* Normalizes GitHub-specific fields to provider-agnostic format
|
|
566
|
+
*/
|
|
567
|
+
async getPR(input) {
|
|
568
|
+
const { number, includeComments = true, repo } = input;
|
|
569
|
+
const prNumber = parseInt(number, 10);
|
|
570
|
+
if (isNaN(prNumber)) {
|
|
571
|
+
throw new Error(`Invalid GitHub PR number: ${number}. GitHub PR IDs must be numeric.`);
|
|
572
|
+
}
|
|
573
|
+
const baseFields = "number,title,body,state,url,author,headRefName,baseRefName,files,commits";
|
|
574
|
+
const fields = includeComments ? `${baseFields},comments` : baseFields;
|
|
575
|
+
const args = [
|
|
576
|
+
"pr",
|
|
577
|
+
"view",
|
|
578
|
+
String(prNumber),
|
|
579
|
+
"--json",
|
|
580
|
+
fields
|
|
581
|
+
];
|
|
582
|
+
if (repo) {
|
|
583
|
+
args.push("--repo", repo);
|
|
584
|
+
}
|
|
585
|
+
const raw = await executeGhCommand(args);
|
|
586
|
+
const result = {
|
|
587
|
+
// Core fields
|
|
588
|
+
id: String(raw.number),
|
|
589
|
+
number: raw.number,
|
|
590
|
+
title: raw.title,
|
|
591
|
+
body: raw.body,
|
|
592
|
+
state: raw.state,
|
|
593
|
+
url: raw.url,
|
|
594
|
+
// Normalized author
|
|
595
|
+
author: normalizeAuthor(raw.author),
|
|
596
|
+
// PR-specific fields
|
|
597
|
+
headRefName: raw.headRefName,
|
|
598
|
+
baseRefName: raw.baseRefName,
|
|
599
|
+
// Optional files
|
|
600
|
+
...raw.files && {
|
|
601
|
+
files: raw.files
|
|
602
|
+
},
|
|
603
|
+
// Optional commits - normalize author
|
|
604
|
+
...raw.commits && {
|
|
605
|
+
commits: raw.commits.map((commit) => {
|
|
606
|
+
var _a;
|
|
607
|
+
return {
|
|
608
|
+
oid: commit.oid,
|
|
609
|
+
messageHeadline: commit.messageHeadline,
|
|
610
|
+
author: ((_a = commit.authors) == null ? void 0 : _a[0]) ? {
|
|
611
|
+
id: commit.authors[0].email,
|
|
612
|
+
displayName: commit.authors[0].name,
|
|
613
|
+
name: commit.authors[0].name,
|
|
614
|
+
email: commit.authors[0].email
|
|
615
|
+
} : null
|
|
616
|
+
};
|
|
617
|
+
})
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
if (raw.comments !== void 0) {
|
|
621
|
+
result.comments = raw.comments.map((comment) => ({
|
|
622
|
+
id: extractNumericIdFromUrl(comment.url),
|
|
623
|
+
body: comment.body,
|
|
624
|
+
createdAt: comment.createdAt,
|
|
625
|
+
author: normalizeAuthor(comment.author),
|
|
626
|
+
...comment.updatedAt && { updatedAt: comment.updatedAt }
|
|
627
|
+
}));
|
|
628
|
+
}
|
|
629
|
+
result.body = await processMarkdownImages(result.body, "github");
|
|
630
|
+
if (result.comments) {
|
|
631
|
+
for (const comment of result.comments) {
|
|
632
|
+
comment.body = await processMarkdownImages(comment.body, "github");
|
|
633
|
+
}
|
|
634
|
+
}
|
|
317
635
|
return result;
|
|
318
636
|
}
|
|
319
637
|
/**
|
|
@@ -333,9 +651,10 @@ var GitHubIssueManagementProvider = class {
|
|
|
333
651
|
"--jq",
|
|
334
652
|
"{id: .id, body: .body, user: .user, created_at: .created_at, updated_at: .updated_at, html_url: .html_url, reactions: .reactions}"
|
|
335
653
|
]);
|
|
654
|
+
const processedBody = await processMarkdownImages(raw.body, "github");
|
|
336
655
|
return {
|
|
337
656
|
id: String(raw.id),
|
|
338
|
-
body:
|
|
657
|
+
body: processedBody,
|
|
339
658
|
author: normalizeAuthor(raw.user),
|
|
340
659
|
created_at: raw.created_at,
|
|
341
660
|
...raw.updated_at && { updated_at: raw.updated_at },
|
|
@@ -684,7 +1003,7 @@ async function fetchLinearIssueComments(identifier) {
|
|
|
684
1003
|
|
|
685
1004
|
// src/utils/linear-markup-converter.ts
|
|
686
1005
|
import { appendFileSync } from "fs";
|
|
687
|
-
import { join, dirname, basename, extname } from "path";
|
|
1006
|
+
import { join as join2, dirname, basename, extname as extname2 } from "path";
|
|
688
1007
|
var LinearMarkupConverter = class {
|
|
689
1008
|
/**
|
|
690
1009
|
* Convert HTML details/summary blocks to Linear's collapsible format
|
|
@@ -820,7 +1139,7 @@ ${content}
|
|
|
820
1139
|
*/
|
|
821
1140
|
static getTimestampedLogPath(logFilePath) {
|
|
822
1141
|
const dir = dirname(logFilePath);
|
|
823
|
-
const ext =
|
|
1142
|
+
const ext = extname2(logFilePath);
|
|
824
1143
|
const base = basename(logFilePath, ext);
|
|
825
1144
|
const now = /* @__PURE__ */ new Date();
|
|
826
1145
|
const timestamp = [
|
|
@@ -832,7 +1151,7 @@ ${content}
|
|
|
832
1151
|
String(now.getMinutes()).padStart(2, "0"),
|
|
833
1152
|
String(now.getSeconds()).padStart(2, "0")
|
|
834
1153
|
].join("");
|
|
835
|
-
return
|
|
1154
|
+
return join2(dir, `${base}-${timestamp}${ext}`);
|
|
836
1155
|
}
|
|
837
1156
|
};
|
|
838
1157
|
|
|
@@ -881,8 +1200,21 @@ var LinearIssueManagementProvider = class {
|
|
|
881
1200
|
} catch {
|
|
882
1201
|
}
|
|
883
1202
|
}
|
|
1203
|
+
result.body = await processMarkdownImages(result.body, "linear");
|
|
1204
|
+
if (result.comments) {
|
|
1205
|
+
for (const comment of result.comments) {
|
|
1206
|
+
comment.body = await processMarkdownImages(comment.body, "linear");
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
884
1209
|
return result;
|
|
885
1210
|
}
|
|
1211
|
+
/**
|
|
1212
|
+
* Fetch pull request details
|
|
1213
|
+
* Linear does not support PRs - this throws an error directing to use GitHub
|
|
1214
|
+
*/
|
|
1215
|
+
async getPR(_input) {
|
|
1216
|
+
throw new Error("Linear does not support pull requests. PRs exist only on GitHub. Use the GitHub provider for PR operations.");
|
|
1217
|
+
}
|
|
886
1218
|
/**
|
|
887
1219
|
* Fetch comments for an issue
|
|
888
1220
|
*/
|
|
@@ -907,9 +1239,10 @@ var LinearIssueManagementProvider = class {
|
|
|
907
1239
|
async getComment(input) {
|
|
908
1240
|
const { commentId } = input;
|
|
909
1241
|
const raw = await getLinearComment(commentId);
|
|
1242
|
+
const processedBody = await processMarkdownImages(raw.body, "linear");
|
|
910
1243
|
return {
|
|
911
1244
|
id: raw.id,
|
|
912
|
-
body:
|
|
1245
|
+
body: processedBody,
|
|
913
1246
|
author: null,
|
|
914
1247
|
// Linear SDK doesn't return comment author info in basic fetch
|
|
915
1248
|
created_at: raw.createdAt
|
|
@@ -1104,6 +1437,80 @@ server.registerTool(
|
|
|
1104
1437
|
}
|
|
1105
1438
|
}
|
|
1106
1439
|
);
|
|
1440
|
+
server.registerTool(
|
|
1441
|
+
"get_pr",
|
|
1442
|
+
{
|
|
1443
|
+
title: "Get Pull Request",
|
|
1444
|
+
description: "Fetch pull request details including title, body, comments, files, commits, and branch information. PRs only exist on GitHub, so this tool always uses GitHub regardless of configured issue tracker. Author fields have normalized core fields: { id, displayName } plus provider-specific fields.",
|
|
1445
|
+
inputSchema: {
|
|
1446
|
+
number: z.string().describe("The PR number"),
|
|
1447
|
+
includeComments: z.boolean().optional().describe("Whether to include comments (default: true)"),
|
|
1448
|
+
repo: z.string().optional().describe(
|
|
1449
|
+
'Optional repository in "owner/repo" format or full GitHub URL. When not provided, uses the current repository.'
|
|
1450
|
+
)
|
|
1451
|
+
},
|
|
1452
|
+
outputSchema: {
|
|
1453
|
+
// Core validated fields
|
|
1454
|
+
id: z.string().describe("PR identifier"),
|
|
1455
|
+
number: z.number().describe("PR number"),
|
|
1456
|
+
title: z.string().describe("PR title"),
|
|
1457
|
+
body: z.string().describe("PR body/description"),
|
|
1458
|
+
state: z.string().describe("PR state (OPEN, CLOSED, MERGED)"),
|
|
1459
|
+
url: z.string().describe("PR URL"),
|
|
1460
|
+
// Branch info
|
|
1461
|
+
headRefName: z.string().describe("Source branch name"),
|
|
1462
|
+
baseRefName: z.string().describe("Target branch name"),
|
|
1463
|
+
// Flexible author - core fields + passthrough
|
|
1464
|
+
author: flexibleAuthorSchema.nullable().describe(
|
|
1465
|
+
"PR author with normalized { id, displayName } plus provider-specific fields"
|
|
1466
|
+
),
|
|
1467
|
+
// Optional flexible arrays
|
|
1468
|
+
files: z.array(
|
|
1469
|
+
z.object({
|
|
1470
|
+
path: z.string(),
|
|
1471
|
+
additions: z.number(),
|
|
1472
|
+
deletions: z.number()
|
|
1473
|
+
}).passthrough()
|
|
1474
|
+
).optional().describe("Changed files in the PR"),
|
|
1475
|
+
commits: z.array(
|
|
1476
|
+
z.object({
|
|
1477
|
+
oid: z.string(),
|
|
1478
|
+
messageHeadline: z.string(),
|
|
1479
|
+
author: flexibleAuthorSchema.nullable()
|
|
1480
|
+
}).passthrough()
|
|
1481
|
+
).optional().describe("Commits in the PR"),
|
|
1482
|
+
comments: z.array(
|
|
1483
|
+
z.object({
|
|
1484
|
+
id: z.string(),
|
|
1485
|
+
body: z.string(),
|
|
1486
|
+
author: flexibleAuthorSchema.nullable(),
|
|
1487
|
+
createdAt: z.string()
|
|
1488
|
+
}).passthrough()
|
|
1489
|
+
).optional().describe("PR comments")
|
|
1490
|
+
}
|
|
1491
|
+
},
|
|
1492
|
+
async ({ number, includeComments, repo }) => {
|
|
1493
|
+
console.error(`Fetching PR ${number}${repo ? ` from ${repo}` : ""}`);
|
|
1494
|
+
try {
|
|
1495
|
+
const provider = new GitHubIssueManagementProvider();
|
|
1496
|
+
const result = await provider.getPR({ number, includeComments, repo });
|
|
1497
|
+
console.error(`PR fetched successfully: #${result.number} - ${result.title}`);
|
|
1498
|
+
return {
|
|
1499
|
+
content: [
|
|
1500
|
+
{
|
|
1501
|
+
type: "text",
|
|
1502
|
+
text: JSON.stringify(result)
|
|
1503
|
+
}
|
|
1504
|
+
],
|
|
1505
|
+
structuredContent: result
|
|
1506
|
+
};
|
|
1507
|
+
} catch (error) {
|
|
1508
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1509
|
+
console.error(`Failed to fetch PR: ${errorMessage}`);
|
|
1510
|
+
throw new Error(`Failed to fetch PR: ${errorMessage}`);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
);
|
|
1107
1514
|
server.registerTool(
|
|
1108
1515
|
"get_comment",
|
|
1109
1516
|
{
|
|
@@ -1169,9 +1576,8 @@ server.registerTool(
|
|
|
1169
1576
|
async ({ number, body, type }) => {
|
|
1170
1577
|
console.error(`Creating ${type} comment on ${number}`);
|
|
1171
1578
|
try {
|
|
1172
|
-
const
|
|
1173
|
-
|
|
1174
|
-
);
|
|
1579
|
+
const providerType = type === "pr" ? "github" : process.env.ISSUE_PROVIDER;
|
|
1580
|
+
const provider = IssueManagementProviderFactory.create(providerType);
|
|
1175
1581
|
const result = await provider.createComment({ number, body, type });
|
|
1176
1582
|
console.error(
|
|
1177
1583
|
`Comment created successfully: ${result.id} at ${result.url}`
|
|
@@ -1200,7 +1606,8 @@ server.registerTool(
|
|
|
1200
1606
|
inputSchema: {
|
|
1201
1607
|
commentId: z.string().describe("The comment identifier to update"),
|
|
1202
1608
|
number: z.string().describe("The issue or PR identifier (context for providers that need it)"),
|
|
1203
|
-
body: z.string().describe("The updated comment body (markdown supported)")
|
|
1609
|
+
body: z.string().describe("The updated comment body (markdown supported)"),
|
|
1610
|
+
type: z.enum(["issue", "pr"]).optional().describe("Optional type to route PR comments to GitHub regardless of configured provider")
|
|
1204
1611
|
},
|
|
1205
1612
|
outputSchema: {
|
|
1206
1613
|
id: z.string(),
|
|
@@ -1208,12 +1615,11 @@ server.registerTool(
|
|
|
1208
1615
|
updated_at: z.string().optional()
|
|
1209
1616
|
}
|
|
1210
1617
|
},
|
|
1211
|
-
async ({ commentId, number, body }) => {
|
|
1212
|
-
console.error(`Updating comment ${commentId} on issue ${number}`);
|
|
1618
|
+
async ({ commentId, number, body, type }) => {
|
|
1619
|
+
console.error(`Updating comment ${commentId} on ${type === "pr" ? "PR" : "issue"} ${number}`);
|
|
1213
1620
|
try {
|
|
1214
|
-
const
|
|
1215
|
-
|
|
1216
|
-
);
|
|
1621
|
+
const providerType = type === "pr" ? "github" : process.env.ISSUE_PROVIDER;
|
|
1622
|
+
const provider = IssueManagementProviderFactory.create(providerType);
|
|
1217
1623
|
const result = await provider.updateComment({ commentId, number, body });
|
|
1218
1624
|
console.error(
|
|
1219
1625
|
`Comment updated successfully: ${result.id} at ${result.url}`
|