@iloom/cli 0.7.6 → 0.8.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.
- package/LICENSE +1 -1
- package/README.md +32 -3
- package/dist/{ClaudeContextManager-Y2YJC6BU.js → ClaudeContextManager-RDP6CLK6.js} +5 -5
- package/dist/{ClaudeService-NDVFQRKC.js → ClaudeService-FKPOQRA4.js} +4 -4
- package/dist/GitHubService-ACZVNTJE.js +12 -0
- package/dist/{LoomLauncher-U2B3VHPC.js → LoomLauncher-NHZMEVTQ.js} +5 -5
- package/dist/{MetadataManager-XJ2YB762.js → MetadataManager-W3C54UYT.js} +2 -2
- package/dist/{PRManager-7F3AAY66.js → PRManager-H4TUZTZL.js} +5 -5
- package/dist/{PromptTemplateManager-7L3HJQQU.js → PromptTemplateManager-OUYDHOPI.js} +2 -2
- package/dist/README.md +32 -3
- package/dist/{SettingsManager-YU4VYPTW.js → SettingsManager-VCVLL32H.js} +4 -2
- package/dist/{SettingsMigrationManager-KZKDG66H.js → SettingsMigrationManager-LEBMJP3B.js} +3 -3
- package/dist/agents/iloom-code-reviewer.md +735 -0
- package/dist/agents/iloom-framework-detector.md +1 -1
- package/dist/agents/iloom-issue-analyze-and-plan.md +2 -2
- package/dist/agents/iloom-issue-analyzer.md +2 -2
- package/dist/agents/iloom-issue-complexity-evaluator.md +1 -1
- package/dist/agents/iloom-issue-enhancer.md +2 -2
- package/dist/agents/iloom-issue-implementer.md +3 -3
- package/dist/agents/iloom-issue-planner.md +2 -2
- package/dist/{build-HQ5HGA3T.js → build-H4DK3DMQ.js} +7 -7
- package/dist/{chunk-N7FVXZNI.js → chunk-4BSXZ5YZ.js} +31 -9
- package/dist/chunk-4BSXZ5YZ.js.map +1 -0
- package/dist/{chunk-VYKKWU36.js → chunk-4KGRPHM6.js} +3 -3
- package/dist/{chunk-CFQVOTHO.js → chunk-52MVUK5V.js} +2 -2
- package/dist/{chunk-TIYJEEVO.js → chunk-66QOCD5N.js} +1 -1
- package/dist/chunk-66QOCD5N.js.map +1 -0
- package/dist/chunk-7JDMYTFZ.js +251 -0
- package/dist/chunk-7JDMYTFZ.js.map +1 -0
- package/dist/{chunk-7LSSNB7Y.js → chunk-7ZEHSSUP.js} +2 -2
- package/dist/chunk-A4UQY3M2.js +75 -0
- package/dist/chunk-A4UQY3M2.js.map +1 -0
- package/dist/{chunk-ELJKYFSH.js → chunk-BCQDYAOJ.js} +4 -4
- package/dist/{chunk-F2PWIRV4.js → chunk-BYUMEDDD.js} +2 -2
- package/dist/{chunk-CAXFWFV6.js → chunk-ECP77QGE.js} +4 -4
- package/dist/{chunk-6YAMWLCP.js → chunk-EQOFNPEY.js} +3 -3
- package/dist/{chunk-ZA575VLF.js → chunk-GDS2HXSW.js} +4 -4
- package/dist/{chunk-UDRZY65Y.js → chunk-HSGZW3ID.js} +2 -2
- package/dist/{chunk-WFQ5CLTR.js → chunk-IWIIOFEB.js} +56 -5
- package/dist/chunk-IWIIOFEB.js.map +1 -0
- package/dist/{chunk-VWGKGNJP.js → chunk-KBEIQP4G.js} +3 -1
- package/dist/chunk-KBEIQP4G.js.map +1 -0
- package/dist/{chunk-ETY2SBW5.js → chunk-NR64HNF7.js} +17 -15
- package/dist/chunk-NR64HNF7.js.map +1 -0
- package/dist/{chunk-WT4UGBE2.js → chunk-PBSHQVCT.js} +5 -5
- package/dist/{chunk-64HCHVJM.js → chunk-PLI3JQWT.js} +2 -2
- package/dist/{chunk-USJSNHGG.js → chunk-PVW6JE7E.js} +3 -3
- package/dist/{chunk-HBJITKSZ.js → chunk-RNBIISBZ.js} +161 -3
- package/dist/chunk-RNBIISBZ.js.map +1 -0
- package/dist/{chunk-C7YW5IMS.js → chunk-RODL2HVY.js} +17 -6
- package/dist/{chunk-C7YW5IMS.js.map → chunk-RODL2HVY.js.map} +1 -1
- package/dist/{chunk-3K3WY3BN.js → chunk-SC6X5EBG.js} +4 -4
- package/dist/{chunk-NEPH2O4C.js → chunk-SSASIBDJ.js} +3 -3
- package/dist/{chunk-GCPAZSGV.js → chunk-THS5L54H.js} +150 -3
- package/dist/chunk-THS5L54H.js.map +1 -0
- package/dist/{chunk-5V74K5ZA.js → chunk-TVH67KEO.js} +25 -2
- package/dist/chunk-TVH67KEO.js.map +1 -0
- package/dist/{chunk-NPEMVE27.js → chunk-UDZCTLD6.js} +115 -3
- package/dist/chunk-UDZCTLD6.js.map +1 -0
- package/dist/{chunk-ENMTWE74.js → chunk-VZYSM7N7.js} +2 -2
- package/dist/{chunk-WZYBHD7P.js → chunk-XHNACIHO.js} +2 -2
- package/dist/{chunk-XAMBIVXE.js → chunk-XJHQVOT6.js} +2 -2
- package/dist/{chunk-O36JLYNW.js → chunk-XU5A6BWA.js} +4 -7
- package/dist/chunk-XU5A6BWA.js.map +1 -0
- package/dist/{cleanup-IO4KV2DL.js → cleanup-OGE7V7AD.js} +16 -16
- package/dist/cli.js +317 -164
- package/dist/cli.js.map +1 -1
- package/dist/{commit-3ULFKXNB.js → commit-534QIRHY.js} +10 -10
- package/dist/{compile-CT7IR7O2.js → compile-ZOAODFN2.js} +7 -7
- package/dist/{contribute-GXKOIA42.js → contribute-7USRBWRM.js} +6 -6
- package/dist/{dev-server-OAP3RZC6.js → dev-server-TYYJM3XA.js} +9 -9
- package/dist/{feedback-ZLAX3BVL.js → feedback-HZVLOTQJ.js} +9 -9
- package/dist/{git-ENLT2VNI.js → git-GUNOPP4Q.js} +4 -4
- package/dist/hooks/iloom-hook.js +75 -3
- package/dist/{ignite-HA2OJF6Z.js → ignite-ZO7SGUKP.js} +85 -25
- package/dist/ignite-ZO7SGUKP.js.map +1 -0
- package/dist/index.d.ts +85 -2
- package/dist/index.js +133 -73
- package/dist/index.js.map +1 -1
- package/dist/init-MZBIXQ7V.js +21 -0
- package/dist/{lint-HAVU4U34.js → lint-MDVUV3W2.js} +7 -7
- package/dist/mcp/issue-management-server.js +569 -2
- package/dist/mcp/issue-management-server.js.map +1 -1
- package/dist/{neon-helpers-3KBC4A3Y.js → neon-helpers-VVFFTLXE.js} +3 -3
- package/dist/{open-IN3LUZXX.js → open-2LPZ7XXW.js} +9 -9
- package/dist/plan-PIME6UNY.js +371 -0
- package/dist/plan-PIME6UNY.js.map +1 -0
- package/dist/{projects-CTRTTMSK.js → projects-325GEEGJ.js} +2 -2
- package/dist/{prompt-3SAZYRUN.js → prompt-ONNPSNKM.js} +2 -2
- package/dist/prompts/init-prompt.txt +83 -3
- package/dist/prompts/issue-prompt.txt +51 -3
- package/dist/prompts/plan-prompt.txt +435 -0
- package/dist/prompts/pr-prompt.txt +38 -0
- package/dist/prompts/regular-prompt.txt +53 -3
- package/dist/{rebase-RLEVFHWN.js → rebase-7YS3N274.js} +6 -6
- package/dist/{recap-ZKGHZCX6.js → recap-GSXFEOD6.js} +6 -6
- package/dist/{run-QEIS2EH2.js → run-XPGCMFFO.js} +9 -9
- package/dist/schema/settings.schema.json +57 -1
- package/dist/{shell-2NNSIU34.js → shell-2SPM3Z5O.js} +6 -6
- package/dist/{summary-MPOOQIOX.js → summary-C5VVSJAJ.js} +11 -11
- package/dist/{test-75WAA6DU.js → test-N2725YRI.js} +7 -7
- package/dist/{test-git-E2BLXR6M.js → test-git-ZPSPA2TP.js} +4 -4
- package/dist/{test-prefix-A7JGGYAA.js → test-prefix-6DLB2BHE.js} +4 -4
- package/dist/{test-webserver-J6SMNLU2.js → test-webserver-XLJ2TZFP.js} +6 -6
- package/package.json +1 -1
- package/dist/GitHubService-O7U4UQ7N.js +0 -12
- package/dist/agents/iloom-issue-reviewer.md +0 -139
- package/dist/chunk-5V74K5ZA.js.map +0 -1
- package/dist/chunk-ETY2SBW5.js.map +0 -1
- package/dist/chunk-GCPAZSGV.js.map +0 -1
- package/dist/chunk-HBJITKSZ.js.map +0 -1
- package/dist/chunk-N7FVXZNI.js.map +0 -1
- package/dist/chunk-NPEMVE27.js.map +0 -1
- package/dist/chunk-O36JLYNW.js.map +0 -1
- package/dist/chunk-TIYJEEVO.js.map +0 -1
- package/dist/chunk-VWGKGNJP.js.map +0 -1
- package/dist/chunk-WFQ5CLTR.js.map +0 -1
- package/dist/chunk-ZX3GTM7O.js +0 -119
- package/dist/chunk-ZX3GTM7O.js.map +0 -1
- package/dist/ignite-HA2OJF6Z.js.map +0 -1
- package/dist/init-S6IEGRSX.js +0 -21
- /package/dist/{ClaudeContextManager-Y2YJC6BU.js.map → ClaudeContextManager-RDP6CLK6.js.map} +0 -0
- /package/dist/{ClaudeService-NDVFQRKC.js.map → ClaudeService-FKPOQRA4.js.map} +0 -0
- /package/dist/{GitHubService-O7U4UQ7N.js.map → GitHubService-ACZVNTJE.js.map} +0 -0
- /package/dist/{LoomLauncher-U2B3VHPC.js.map → LoomLauncher-NHZMEVTQ.js.map} +0 -0
- /package/dist/{MetadataManager-XJ2YB762.js.map → MetadataManager-W3C54UYT.js.map} +0 -0
- /package/dist/{PRManager-7F3AAY66.js.map → PRManager-H4TUZTZL.js.map} +0 -0
- /package/dist/{PromptTemplateManager-7L3HJQQU.js.map → PromptTemplateManager-OUYDHOPI.js.map} +0 -0
- /package/dist/{SettingsManager-YU4VYPTW.js.map → SettingsManager-VCVLL32H.js.map} +0 -0
- /package/dist/{SettingsMigrationManager-KZKDG66H.js.map → SettingsMigrationManager-LEBMJP3B.js.map} +0 -0
- /package/dist/{build-HQ5HGA3T.js.map → build-H4DK3DMQ.js.map} +0 -0
- /package/dist/{chunk-VYKKWU36.js.map → chunk-4KGRPHM6.js.map} +0 -0
- /package/dist/{chunk-CFQVOTHO.js.map → chunk-52MVUK5V.js.map} +0 -0
- /package/dist/{chunk-7LSSNB7Y.js.map → chunk-7ZEHSSUP.js.map} +0 -0
- /package/dist/{chunk-ELJKYFSH.js.map → chunk-BCQDYAOJ.js.map} +0 -0
- /package/dist/{chunk-F2PWIRV4.js.map → chunk-BYUMEDDD.js.map} +0 -0
- /package/dist/{chunk-CAXFWFV6.js.map → chunk-ECP77QGE.js.map} +0 -0
- /package/dist/{chunk-6YAMWLCP.js.map → chunk-EQOFNPEY.js.map} +0 -0
- /package/dist/{chunk-ZA575VLF.js.map → chunk-GDS2HXSW.js.map} +0 -0
- /package/dist/{chunk-UDRZY65Y.js.map → chunk-HSGZW3ID.js.map} +0 -0
- /package/dist/{chunk-WT4UGBE2.js.map → chunk-PBSHQVCT.js.map} +0 -0
- /package/dist/{chunk-64HCHVJM.js.map → chunk-PLI3JQWT.js.map} +0 -0
- /package/dist/{chunk-USJSNHGG.js.map → chunk-PVW6JE7E.js.map} +0 -0
- /package/dist/{chunk-3K3WY3BN.js.map → chunk-SC6X5EBG.js.map} +0 -0
- /package/dist/{chunk-NEPH2O4C.js.map → chunk-SSASIBDJ.js.map} +0 -0
- /package/dist/{chunk-ENMTWE74.js.map → chunk-VZYSM7N7.js.map} +0 -0
- /package/dist/{chunk-WZYBHD7P.js.map → chunk-XHNACIHO.js.map} +0 -0
- /package/dist/{chunk-XAMBIVXE.js.map → chunk-XJHQVOT6.js.map} +0 -0
- /package/dist/{cleanup-IO4KV2DL.js.map → cleanup-OGE7V7AD.js.map} +0 -0
- /package/dist/{commit-3ULFKXNB.js.map → commit-534QIRHY.js.map} +0 -0
- /package/dist/{compile-CT7IR7O2.js.map → compile-ZOAODFN2.js.map} +0 -0
- /package/dist/{contribute-GXKOIA42.js.map → contribute-7USRBWRM.js.map} +0 -0
- /package/dist/{dev-server-OAP3RZC6.js.map → dev-server-TYYJM3XA.js.map} +0 -0
- /package/dist/{feedback-ZLAX3BVL.js.map → feedback-HZVLOTQJ.js.map} +0 -0
- /package/dist/{git-ENLT2VNI.js.map → git-GUNOPP4Q.js.map} +0 -0
- /package/dist/{init-S6IEGRSX.js.map → init-MZBIXQ7V.js.map} +0 -0
- /package/dist/{lint-HAVU4U34.js.map → lint-MDVUV3W2.js.map} +0 -0
- /package/dist/{neon-helpers-3KBC4A3Y.js.map → neon-helpers-VVFFTLXE.js.map} +0 -0
- /package/dist/{open-IN3LUZXX.js.map → open-2LPZ7XXW.js.map} +0 -0
- /package/dist/{projects-CTRTTMSK.js.map → projects-325GEEGJ.js.map} +0 -0
- /package/dist/{prompt-3SAZYRUN.js.map → prompt-ONNPSNKM.js.map} +0 -0
- /package/dist/{rebase-RLEVFHWN.js.map → rebase-7YS3N274.js.map} +0 -0
- /package/dist/{recap-ZKGHZCX6.js.map → recap-GSXFEOD6.js.map} +0 -0
- /package/dist/{run-QEIS2EH2.js.map → run-XPGCMFFO.js.map} +0 -0
- /package/dist/{shell-2NNSIU34.js.map → shell-2SPM3Z5O.js.map} +0 -0
- /package/dist/{summary-MPOOQIOX.js.map → summary-C5VVSJAJ.js.map} +0 -0
- /package/dist/{test-75WAA6DU.js.map → test-N2725YRI.js.map} +0 -0
- /package/dist/{test-git-E2BLXR6M.js.map → test-git-ZPSPA2TP.js.map} +0 -0
- /package/dist/{test-prefix-A7JGGYAA.js.map → test-prefix-6DLB2BHE.js.map} +0 -0
- /package/dist/{test-webserver-J6SMNLU2.js.map → test-webserver-XLJ2TZFP.js.map} +0 -0
|
@@ -11,7 +11,7 @@ async function executeGhCommand(args, options) {
|
|
|
11
11
|
timeout: (options == null ? void 0 : options.timeout) ?? 3e4,
|
|
12
12
|
encoding: "utf8"
|
|
13
13
|
});
|
|
14
|
-
const isJson = args.includes("--json") || args.includes("--jq") || args.includes("--format") && args[args.indexOf("--format") + 1] === "json";
|
|
14
|
+
const isJson = args.includes("--json") || args.includes("--jq") || args.includes("--format") && args[args.indexOf("--format") + 1] === "json" || args[0] === "api" && args[1] === "graphql";
|
|
15
15
|
const data = isJson ? JSON.parse(result.stdout) : result.stdout;
|
|
16
16
|
return data;
|
|
17
17
|
}
|
|
@@ -267,6 +267,148 @@ async function addSubIssue(parentNodeId, childNodeId) {
|
|
|
267
267
|
`subIssueId=${childNodeId}`
|
|
268
268
|
]);
|
|
269
269
|
}
|
|
270
|
+
async function getSubIssues(issueNumber, repo) {
|
|
271
|
+
var _a, _b;
|
|
272
|
+
logger.debug("Fetching GitHub sub-issues", { issueNumber, repo });
|
|
273
|
+
const parentNodeId = await getIssueNodeId(issueNumber, repo);
|
|
274
|
+
const query = `
|
|
275
|
+
query getSubIssues($parentId: ID!) {
|
|
276
|
+
node(id: $parentId) {
|
|
277
|
+
... on Issue {
|
|
278
|
+
subIssues(first: 100) {
|
|
279
|
+
nodes {
|
|
280
|
+
number
|
|
281
|
+
title
|
|
282
|
+
url
|
|
283
|
+
state
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
`;
|
|
290
|
+
try {
|
|
291
|
+
const result = await executeGhCommand([
|
|
292
|
+
"api",
|
|
293
|
+
"graphql",
|
|
294
|
+
"-H",
|
|
295
|
+
"GraphQL-Features: sub_issues",
|
|
296
|
+
"-f",
|
|
297
|
+
`query=${query}`,
|
|
298
|
+
"-F",
|
|
299
|
+
`parentId=${parentNodeId}`
|
|
300
|
+
]);
|
|
301
|
+
const subIssues = ((_b = (_a = result.data.node) == null ? void 0 : _a.subIssues) == null ? void 0 : _b.nodes) ?? [];
|
|
302
|
+
return subIssues.map((issue) => ({
|
|
303
|
+
id: String(issue.number),
|
|
304
|
+
title: issue.title,
|
|
305
|
+
url: issue.url,
|
|
306
|
+
state: issue.state.toLowerCase()
|
|
307
|
+
}));
|
|
308
|
+
} catch (error) {
|
|
309
|
+
if (error instanceof Error) {
|
|
310
|
+
const errorMessage = error.message;
|
|
311
|
+
const stderr = "stderr" in error ? error.stderr ?? "" : "";
|
|
312
|
+
const combinedError = `${errorMessage} ${stderr}`;
|
|
313
|
+
if (combinedError.includes("sub_issues") || combinedError.includes("null")) {
|
|
314
|
+
return [];
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
throw error;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
async function getIssueDatabaseId(issueNumber, repo) {
|
|
321
|
+
logger.debug("Fetching GitHub issue database ID", { issueNumber, repo });
|
|
322
|
+
const apiPath = repo ? `repos/${repo}/issues/${issueNumber}` : `repos/:owner/:repo/issues/${issueNumber}`;
|
|
323
|
+
const result = await executeGhCommand([
|
|
324
|
+
"api",
|
|
325
|
+
apiPath,
|
|
326
|
+
"--jq",
|
|
327
|
+
"{id: .id}"
|
|
328
|
+
]);
|
|
329
|
+
return result.id;
|
|
330
|
+
}
|
|
331
|
+
async function getIssueDependencies(issueNumber, direction, repo) {
|
|
332
|
+
logger.debug("Fetching GitHub issue dependencies", { issueNumber, direction, repo });
|
|
333
|
+
const apiPath = repo ? `repos/${repo}/issues/${issueNumber}/dependencies/${direction}` : `repos/:owner/:repo/issues/${issueNumber}/dependencies/${direction}`;
|
|
334
|
+
try {
|
|
335
|
+
const result = await executeGhCommand([
|
|
336
|
+
"api",
|
|
337
|
+
"-H",
|
|
338
|
+
"Accept: application/vnd.github+json",
|
|
339
|
+
"-H",
|
|
340
|
+
"X-GitHub-Api-Version: 2022-11-28",
|
|
341
|
+
"--jq",
|
|
342
|
+
".",
|
|
343
|
+
apiPath
|
|
344
|
+
]);
|
|
345
|
+
return (result ?? []).map((dep) => ({
|
|
346
|
+
id: String(dep.number),
|
|
347
|
+
databaseId: dep.id,
|
|
348
|
+
title: dep.title,
|
|
349
|
+
url: dep.html_url,
|
|
350
|
+
state: dep.state
|
|
351
|
+
}));
|
|
352
|
+
} catch (error) {
|
|
353
|
+
if (error instanceof Error) {
|
|
354
|
+
const errorMessage = error.message;
|
|
355
|
+
const stderr = "stderr" in error ? error.stderr ?? "" : "";
|
|
356
|
+
const combinedError = `${errorMessage} ${stderr}`;
|
|
357
|
+
if (combinedError.includes("404") && combinedError.includes("dependencies")) {
|
|
358
|
+
return [];
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
throw error;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
async function createIssueDependency(blockedIssueNumber, blockingIssueDatabaseId, repo) {
|
|
365
|
+
logger.debug("Creating GitHub issue dependency", { blockedIssueNumber, blockingIssueDatabaseId, repo });
|
|
366
|
+
const apiPath = repo ? `repos/${repo}/issues/${blockedIssueNumber}/dependencies/blocked_by` : `repos/:owner/:repo/issues/${blockedIssueNumber}/dependencies/blocked_by`;
|
|
367
|
+
try {
|
|
368
|
+
await executeGhCommand([
|
|
369
|
+
"api",
|
|
370
|
+
"-X",
|
|
371
|
+
"POST",
|
|
372
|
+
"-H",
|
|
373
|
+
"Accept: application/vnd.github+json",
|
|
374
|
+
"-H",
|
|
375
|
+
"X-GitHub-Api-Version: 2022-11-28",
|
|
376
|
+
apiPath,
|
|
377
|
+
"-F",
|
|
378
|
+
`issue_id=${blockingIssueDatabaseId}`
|
|
379
|
+
]);
|
|
380
|
+
} catch (error) {
|
|
381
|
+
if (error instanceof Error) {
|
|
382
|
+
const errorMessage = error.message;
|
|
383
|
+
const stderr = "stderr" in error ? error.stderr ?? "" : "";
|
|
384
|
+
const combinedError = `${errorMessage} ${stderr}`;
|
|
385
|
+
if (combinedError.includes("422") || combinedError.includes("already exists") || combinedError.includes("Unprocessable Entity")) {
|
|
386
|
+
throw new Error(`Dependency already exists: issue #${blockedIssueNumber} is already blocked by the specified issue`);
|
|
387
|
+
}
|
|
388
|
+
if (combinedError.includes("404") || combinedError.includes("Not Found")) {
|
|
389
|
+
throw new Error(`Issue not found: unable to create dependency for issue #${blockedIssueNumber}. The issue may not exist or you may not have access to it.`);
|
|
390
|
+
}
|
|
391
|
+
if (combinedError.includes("403") || combinedError.includes("Forbidden") || combinedError.includes("not enabled")) {
|
|
392
|
+
throw new Error(`Dependencies feature not enabled: the repository may not have issue dependencies enabled. This feature requires GitHub Enterprise or specific repository settings.`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
throw error;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
async function removeIssueDependency(blockedIssueNumber, blockingIssueDatabaseId, repo) {
|
|
399
|
+
logger.debug("Removing GitHub issue dependency", { blockedIssueNumber, blockingIssueDatabaseId, repo });
|
|
400
|
+
const apiPath = repo ? `repos/${repo}/issues/${blockedIssueNumber}/dependencies/blocked_by/${blockingIssueDatabaseId}` : `repos/:owner/:repo/issues/${blockedIssueNumber}/dependencies/blocked_by/${blockingIssueDatabaseId}`;
|
|
401
|
+
await executeGhCommand([
|
|
402
|
+
"api",
|
|
403
|
+
"-X",
|
|
404
|
+
"DELETE",
|
|
405
|
+
"-H",
|
|
406
|
+
"Accept: application/vnd.github+json",
|
|
407
|
+
"-H",
|
|
408
|
+
"X-GitHub-Api-Version: 2022-11-28",
|
|
409
|
+
apiPath
|
|
410
|
+
]);
|
|
411
|
+
}
|
|
270
412
|
|
|
271
413
|
export {
|
|
272
414
|
executeGhCommand,
|
|
@@ -284,6 +426,11 @@ export {
|
|
|
284
426
|
createPRComment,
|
|
285
427
|
getRepoInfo,
|
|
286
428
|
getIssueNodeId,
|
|
287
|
-
addSubIssue
|
|
429
|
+
addSubIssue,
|
|
430
|
+
getSubIssues,
|
|
431
|
+
getIssueDatabaseId,
|
|
432
|
+
getIssueDependencies,
|
|
433
|
+
createIssueDependency,
|
|
434
|
+
removeIssueDependency
|
|
288
435
|
};
|
|
289
|
-
//# sourceMappingURL=chunk-
|
|
436
|
+
//# sourceMappingURL=chunk-THS5L54H.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/github.ts"],"sourcesContent":["import { execa } from 'execa'\nimport type {\n\tGitHubIssue,\n\tGitHubPullRequest,\n\tGitHubProject,\n\tGitHubAuthStatus,\n\tProjectItem,\n\tProjectField,\n} from '../types/github.js'\nimport { logger } from './logger.js'\n\n// Core GitHub CLI execution wrapper\nexport async function executeGhCommand<T = unknown>(\n\targs: string[],\n\toptions?: { cwd?: string; timeout?: number }\n): Promise<T> {\n\tconst result = await execa('gh', args, {\n\t\tcwd: options?.cwd ?? process.cwd(),\n\t\ttimeout: options?.timeout ?? 30000,\n\t\tencoding: 'utf8',\n\t})\n\n\t// Parse JSON output if --json flag, --format json, --jq, or GraphQL was used\n\tconst isJson =\n\t\targs.includes('--json') ||\n\t\targs.includes('--jq') ||\n\t\t(args.includes('--format') && args[args.indexOf('--format') + 1] === 'json') ||\n\t\t(args[0] === 'api' && args[1] === 'graphql')\n\tconst data = isJson ? JSON.parse(result.stdout) : result.stdout\n\n\treturn data as T\n}\n\n// Authentication checking\nexport async function checkGhAuth(): Promise<GitHubAuthStatus> {\n\ttry {\n\t\tconst output = await executeGhCommand<string>(['auth', 'status'])\n\n\t\t// Parse auth status output - handle both old and new formats\n\t\t// Old format: \"Logged in to github.com as username\"\n\t\t// New format: \"✓ Logged in to github.com account username (keyring)\"\n\n\t\t// Split output into lines to find the active account\n\t\tconst lines = output.split('\\n')\n\t\tlet username: string | undefined\n\t\tlet scopes: string[] = []\n\n\t\t// Find the active account (look for \"Active account: true\" or first account if none marked)\n\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\tconst line = lines[i]\n\n\t\t\t// Match new format: \"✓ Logged in to github.com account username\"\n\t\t\tconst newFormatMatch = line?.match(/Logged in to github\\.com account ([^\\s(]+)/)\n\t\t\tif (newFormatMatch) {\n\t\t\t\tconst accountName = newFormatMatch[1]\n\n\t\t\t\t// Check if this is the active account\n\t\t\t\tconst nextFewLines = lines.slice(i + 1, i + 5).join('\\n')\n\t\t\t\tconst isActive = nextFewLines.includes('Active account: true')\n\n\t\t\t\t// If this is the active account, or we haven't found one yet and there's no \"Active account\" marker\n\t\t\t\tif (isActive || (!username && !output.includes('Active account:'))) {\n\t\t\t\t\tusername = accountName\n\n\t\t\t\t\t// Find scopes for this account\n\t\t\t\t\tconst scopeMatch = nextFewLines.match(/Token scopes: (.+)/)\n\t\t\t\t\tif (scopeMatch?.[1]) {\n\t\t\t\t\t\tscopes = scopeMatch[1].split(', ').map(scope => scope.replace(/^'|'$/g, ''))\n\t\t\t\t\t}\n\n\t\t\t\t\t// If this is the active account, we're done\n\t\t\t\t\tif (isActive) break\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Fallback: match old format\n\t\t\tif (!username) {\n\t\t\t\tconst oldFormatMatch = line?.match(/Logged in to github\\.com as ([^\\s]+)/)\n\t\t\t\tif (oldFormatMatch) {\n\t\t\t\t\tusername = oldFormatMatch[1]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// If scopes not yet extracted, try the old \"Token scopes\" format\n\t\tif (scopes.length === 0) {\n\t\t\tconst scopeMatch = output.match(/Token scopes: (.+)/)\n\t\t\tscopes = scopeMatch?.[1]?.split(', ').map(scope => scope.replace(/^'|'$/g, '')) ?? []\n\t\t}\n\n\t\treturn {\n\t\t\thasAuth: true,\n\t\t\tscopes,\n\t\t\t...(username && { username }),\n\t\t}\n\t} catch (error) {\n\t\t// Only return \"no auth\" for specific authentication errors\n\t\tif (error instanceof Error && 'stderr' in error && (error as {stderr?: string}).stderr?.includes('You are not logged into any GitHub hosts')) {\n\t\t\treturn { hasAuth: false, scopes: [] }\n\t\t}\n\t\t// Re-throw unexpected errors\n\t\tthrow error\n\t}\n}\n\nexport async function hasProjectScope(): Promise<boolean> {\n\tconst auth = await checkGhAuth()\n\treturn auth.scopes.includes('project')\n}\n\n// Issue fetching\nexport async function fetchGhIssue(\n\tissueNumber: number,\n\trepo?: string\n): Promise<GitHubIssue> {\n\tlogger.debug('Fetching GitHub issue', { issueNumber, repo })\n\n\tconst args = [\n\t\t'issue',\n\t\t'view',\n\t\tString(issueNumber),\n\t\t'--json',\n\t\t'number,title,body,state,labels,assignees,url,createdAt,updatedAt',\n\t]\n\n\tif (repo) {\n\t\targs.push('--repo', repo)\n\t}\n\n\treturn executeGhCommand<GitHubIssue>(args)\n}\n\n// PR fetching\nexport async function fetchGhPR(\n\tprNumber: number,\n\trepo?: string\n): Promise<GitHubPullRequest> {\n\tlogger.debug('Fetching GitHub PR', { prNumber, repo })\n\n\tconst args = [\n\t\t'pr',\n\t\t'view',\n\t\tString(prNumber),\n\t\t'--json',\n\t\t'number,title,body,state,headRefName,baseRefName,url,isDraft,mergeable,createdAt,updatedAt',\n\t]\n\n\tif (repo) {\n\t\targs.push('--repo', repo)\n\t}\n\n\treturn executeGhCommand<GitHubPullRequest>(args)\n}\n\n// Project operations\nexport async function fetchProjectList(\n\towner: string\n): Promise<GitHubProject[]> {\n\tconst result = await executeGhCommand<{ projects: GitHubProject[] }>([\n\t\t'project',\n\t\t'list',\n\t\t'--owner',\n\t\towner,\n\t\t'--limit',\n\t\t'100',\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result?.projects ?? []\n}\n\nexport async function fetchProjectItems(\n\tprojectNumber: number,\n\towner: string\n): Promise<ProjectItem[]> {\n\tconst result = await executeGhCommand<{ items: ProjectItem[] }>([\n\t\t'project',\n\t\t'item-list',\n\t\tString(projectNumber),\n\t\t'--owner',\n\t\towner,\n\t\t'--limit',\n\t\t'10000',\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result?.items ?? []\n}\n\nexport async function fetchProjectFields(\n\tprojectNumber: number,\n\towner: string\n): Promise<{ fields: ProjectField[] }> {\n\tconst result = await executeGhCommand<{ fields: ProjectField[] }>([\n\t\t'project',\n\t\t'field-list',\n\t\tString(projectNumber),\n\t\t'--owner',\n\t\towner,\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result ?? { fields: [] }\n}\n\nexport async function updateProjectItemField(\n\titemId: string,\n\tprojectId: string,\n\tfieldId: string,\n\toptionId: string\n): Promise<void> {\n\tawait executeGhCommand([\n\t\t'project',\n\t\t'item-edit',\n\t\t'--id',\n\t\titemId,\n\t\t'--project-id',\n\t\tprojectId,\n\t\t'--field-id',\n\t\tfieldId,\n\t\t'--single-select-option-id',\n\t\toptionId,\n\t\t'--format',\n\t\t'json',\n\t])\n}\n\n// GitHub Issue Operations\n\ninterface IssueCreateResponse {\n\tnumber: string | number\n\turl: string\n}\n\n/**\n * Create a new GitHub issue\n * @param title - The issue title\n * @param body - The issue body (markdown supported)\n * @param options - Optional configuration\n * @param options.repo - Repository in format \"owner/repo\" (uses current repo if not provided)\n * @param options.labels - Array of label names to add to the issue\n * @returns Issue metadata including number and URL\n */\nexport async function createIssue(\n\ttitle: string,\n\tbody: string,\n\toptions?: { repo?: string | undefined; labels?: string[] | undefined }\n): Promise<IssueCreateResponse> {\n\tconst { repo, labels } = options ?? {}\n\n\tlogger.debug('Creating GitHub issue', { title, repo, labels })\n\n\tconst args = [\n\t\t'issue',\n\t\t'create',\n\t\t'--title',\n\t\ttitle,\n\t\t'--body',\n\t\tbody,\n\t]\n\n\t// Add repo if provided\n\tif (repo) {\n\t\targs.splice(2, 0, '--repo', repo)\n\t}\n\n\t// Add labels if provided\n\tif (labels && labels.length > 0) {\n\t\targs.push('--label', labels.join(','))\n\t}\n\n\tconst execaOptions: { timeout: number; encoding: 'utf8'; cwd?: string } = {\n\t\ttimeout: 30000,\n\t\tencoding: 'utf8',\n\t}\n\n\tif (!repo) {\n\t\texecaOptions.cwd = process.cwd()\n\t}\n\n\tconst result = await execa('gh', args, execaOptions)\n\n\t// Parse the URL from the output (format: \"https://github.com/owner/repo/issues/123\")\n\tconst urlMatch = result.stdout.trim().match(/https:\\/\\/github\\.com\\/[^/]+\\/[^/]+\\/issues\\/(\\d+)/)\n\tif (!urlMatch?.[1]) {\n\t\tthrow new Error(`Failed to parse issue URL from gh output: ${result.stdout}`)\n\t}\n\n\tconst issueNumber = parseInt(urlMatch[1], 10)\n\tconst issueUrl = urlMatch[0]\n\n\treturn {\n\t\tnumber: issueNumber,\n\t\turl: issueUrl,\n\t}\n}\n\n/**\n * @deprecated Use createIssue with options.repo instead\n * Create a new GitHub issue in a specific repository\n * @param title - Issue title\n * @param body - Issue body (markdown)\n * @param repository - Repository in format \"owner/repo\"\n * @param labels - Optional array of label names to add to the issue\n * @returns Issue number and URL\n */\nexport async function createIssueInRepo(\n\ttitle: string,\n\tbody: string,\n\trepository: string,\n\tlabels?: string[]\n): Promise<IssueCreateResponse> {\n\treturn createIssue(title, body, { repo: repository, labels })\n}\n\n// GitHub Comment Operations\n\ninterface CommentResponse {\n\tid: number\n\turl: string\n\tcreated_at?: string\n\tupdated_at?: string\n}\n\ninterface RepoInfo {\n\towner: string\n\tname: string\n}\n\n/**\n * Create a comment on a GitHub issue\n * @param issueNumber - The issue number\n * @param body - The comment body (markdown supported)\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Comment metadata including ID and URL\n */\nexport async function createIssueComment(\n\tissueNumber: number,\n\tbody: string,\n\trepo?: string\n): Promise<CommentResponse> {\n\tlogger.debug('Creating issue comment', { issueNumber, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${issueNumber}/comments`\n\t\t: `repos/:owner/:repo/issues/${issueNumber}/comments`\n\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\tapiPath,\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, created_at: .created_at}',\n\t])\n}\n\n/**\n * Update an existing GitHub comment\n * @param commentId - The comment ID\n * @param body - The updated comment body (markdown supported)\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Updated comment metadata\n */\nexport async function updateIssueComment(\n\tcommentId: number,\n\tbody: string,\n\trepo?: string\n): Promise<CommentResponse> {\n\tlogger.debug('Updating issue comment', { commentId, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/comments/${commentId}`\n\t\t: `repos/:owner/:repo/issues/comments/${commentId}`\n\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\tapiPath,\n\t\t'-X',\n\t\t'PATCH',\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, updated_at: .updated_at}',\n\t])\n}\n\n/**\n * Create a comment on a GitHub pull request\n * Note: PR comments use the same endpoint as issue comments\n * @param prNumber - The PR number\n * @param body - The comment body (markdown supported)\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Comment metadata including ID and URL\n */\nexport async function createPRComment(\n\tprNumber: number,\n\tbody: string,\n\trepo?: string\n): Promise<CommentResponse> {\n\tlogger.debug('Creating PR comment', { prNumber, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${prNumber}/comments`\n\t\t: `repos/:owner/:repo/issues/${prNumber}/comments`\n\n\t// PR comments use the issues endpoint\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\tapiPath,\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, created_at: .created_at}',\n\t])\n}\n\n/**\n * Get repository owner and name from current directory\n * @returns Repository owner and name\n */\nexport async function getRepoInfo(): Promise<RepoInfo> {\n\tlogger.debug('Fetching repository info')\n\n\tconst result = await executeGhCommand<{ owner: { login: string }; name: string }>([\n\t\t'repo',\n\t\t'view',\n\t\t'--json',\n\t\t'owner,name',\n\t])\n\n\treturn {\n\t\towner: result.owner.login,\n\t\tname: result.name,\n\t}\n}\n\n// GitHub Sub-Issue Operations\n\n/**\n * Get the GraphQL node ID for a GitHub issue\n * Required for sub-issue API which uses node IDs, not issue numbers\n * @param issueNumber - The issue number\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns GraphQL node ID (e.g., \"I_kwDOPvp_cc7...\")\n */\nexport async function getIssueNodeId(\n\tissueNumber: number,\n\trepo?: string\n): Promise<string> {\n\tlogger.debug('Fetching GitHub issue node ID', { issueNumber, repo })\n\n\tconst args = ['issue', 'view', String(issueNumber), '--json', 'id']\n\tif (repo) {\n\t\targs.push('--repo', repo)\n\t}\n\n\tconst result = await executeGhCommand<{ id: string }>(args)\n\treturn result.id\n}\n\n/**\n * Link a child issue to a parent issue using GitHub's sub-issue API\n * Requires GraphQL-Features: sub_issues header\n * @param parentNodeId - GraphQL node ID of the parent issue\n * @param childNodeId - GraphQL node ID of the child issue\n */\nexport async function addSubIssue(\n\tparentNodeId: string,\n\tchildNodeId: string\n): Promise<void> {\n\tlogger.debug('Linking child issue to parent', { parentNodeId, childNodeId })\n\n\tconst mutation = `\n\t\tmutation addSubIssue($parentId: ID!, $subIssueId: ID!) {\n\t\t\taddSubIssue(input: { issueId: $parentId, subIssueId: $subIssueId }) {\n\t\t\t\tissue { id }\n\t\t\t\tsubIssue { id }\n\t\t\t}\n\t\t}\n\t`\n\n\tawait executeGhCommand([\n\t\t'api', 'graphql',\n\t\t'-H', 'GraphQL-Features: sub_issues',\n\t\t'-f', `query=${mutation}`,\n\t\t'-F', `parentId=${parentNodeId}`,\n\t\t'-F', `subIssueId=${childNodeId}`,\n\t])\n}\n\n/**\n * Get sub-issues (children) of a parent GitHub issue\n * Uses GraphQL to query the sub-issue relationship\n * @param issueNumber - The parent issue number\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Array of child issues with id, title, url, and state\n */\nexport async function getSubIssues(\n\tissueNumber: number,\n\trepo?: string\n): Promise<Array<{ id: string; title: string; url: string; state: string }>> {\n\tlogger.debug('Fetching GitHub sub-issues', { issueNumber, repo })\n\n\t// Get the node ID for the parent issue\n\tconst parentNodeId = await getIssueNodeId(issueNumber, repo)\n\n\t// Query sub-issues using GraphQL\n\tconst query = `\n\t\tquery getSubIssues($parentId: ID!) {\n\t\t\tnode(id: $parentId) {\n\t\t\t\t... on Issue {\n\t\t\t\t\tsubIssues(first: 100) {\n\t\t\t\t\t\tnodes {\n\t\t\t\t\t\t\tnumber\n\t\t\t\t\t\t\ttitle\n\t\t\t\t\t\t\turl\n\t\t\t\t\t\t\tstate\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t`\n\n\tinterface SubIssueNode {\n\t\tnumber: number\n\t\ttitle: string\n\t\turl: string\n\t\tstate: string\n\t}\n\n\tinterface SubIssuesResponse {\n\t\tdata: {\n\t\t\tnode: {\n\t\t\t\tsubIssues: {\n\t\t\t\t\tnodes: SubIssueNode[]\n\t\t\t\t}\n\t\t\t} | null\n\t\t}\n\t}\n\n\ttry {\n\t\tconst result = await executeGhCommand<SubIssuesResponse>([\n\t\t\t'api', 'graphql',\n\t\t\t'-H', 'GraphQL-Features: sub_issues',\n\t\t\t'-f', `query=${query}`,\n\t\t\t'-F', `parentId=${parentNodeId}`,\n\t\t])\n\n\t\tconst subIssues = result.data.node?.subIssues?.nodes ?? []\n\n\t\treturn subIssues.map(issue => ({\n\t\t\tid: String(issue.number),\n\t\t\ttitle: issue.title,\n\t\t\turl: issue.url,\n\t\t\tstate: issue.state.toLowerCase(),\n\t\t}))\n\t} catch (error) {\n\t\t// Return empty array if sub-issues feature is not available or no children\n\t\tif (error instanceof Error) {\n\t\t\tconst errorMessage = error.message\n\t\t\tconst stderr = 'stderr' in error ? (error as { stderr?: string }).stderr ?? '' : ''\n\t\t\tconst combinedError = `${errorMessage} ${stderr}`\n\n\t\t\t// Check for feature not available or empty result\n\t\t\tif (combinedError.includes('sub_issues') || combinedError.includes('null')) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t}\n\t\tthrow error\n\t}\n}\n\n// GitHub Issue Dependency Operations\n\n/**\n * GitHub dependency result from API\n */\ninterface GitHubDependency {\n\tid: number\n\tnumber: number\n\ttitle: string\n\tstate: string\n\thtml_url: string\n}\n\n/**\n * Get the internal database ID for a GitHub issue\n * Required for dependency API which uses database IDs, not node IDs\n * @param issueNumber - The issue number\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Internal GitHub issue database ID\n */\nexport async function getIssueDatabaseId(\n\tissueNumber: number,\n\trepo?: string\n): Promise<number> {\n\tlogger.debug('Fetching GitHub issue database ID', { issueNumber, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${issueNumber}`\n\t\t: `repos/:owner/:repo/issues/${issueNumber}`\n\n\tconst result = await executeGhCommand<{ id: number }>([\n\t\t'api',\n\t\tapiPath,\n\t\t'--jq',\n\t\t'{id: .id}',\n\t])\n\n\treturn result.id\n}\n\n/**\n * Get dependencies for a GitHub issue\n * Uses GitHub's issue dependencies API\n * @param issueNumber - The issue number\n * @param direction - 'blocking' for issues this blocks, 'blocked_by' for issues blocking this\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Array of dependency objects with id, title, url, state\n */\nexport async function getIssueDependencies(\n\tissueNumber: number,\n\tdirection: 'blocking' | 'blocked_by',\n\trepo?: string\n): Promise<Array<{ id: string; databaseId: number; title: string; url: string; state: string }>> {\n\tlogger.debug('Fetching GitHub issue dependencies', { issueNumber, direction, repo })\n\n\t// Use the dependencies API with the appropriate direction endpoint\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${issueNumber}/dependencies/${direction}`\n\t\t: `repos/:owner/:repo/issues/${issueNumber}/dependencies/${direction}`\n\n\ttry {\n\t\tconst result = await executeGhCommand<GitHubDependency[]>([\n\t\t\t'api',\n\t\t\t'-H', 'Accept: application/vnd.github+json',\n\t\t\t'-H', 'X-GitHub-Api-Version: 2022-11-28',\n\t\t\t'--jq', '.',\n\t\t\tapiPath,\n\t\t])\n\n\t\treturn (result ?? []).map(dep => ({\n\t\t\tid: String(dep.number),\n\t\t\tdatabaseId: dep.id,\n\t\t\ttitle: dep.title,\n\t\t\turl: dep.html_url,\n\t\t\tstate: dep.state,\n\t\t}))\n\t} catch (error) {\n\t\t// Return empty array for 404 on the dependencies endpoint\n\t\t// This indicates the issue exists but has no dependencies configured\n\t\tif (error instanceof Error) {\n\t\t\tconst errorMessage = error.message\n\t\t\tconst stderr = 'stderr' in error ? (error as { stderr?: string }).stderr ?? '' : ''\n\t\t\tconst combinedError = `${errorMessage} ${stderr}`\n\n\t\t\t// Check for 404 specifically on dependencies endpoint\n\t\t\tif (combinedError.includes('404') && combinedError.includes('dependencies')) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t}\n\t\tthrow error\n\t}\n}\n\n/**\n * Create a dependency between two issues (A blocks B)\n * Uses GitHub's issue dependencies API\n * @param blockedIssueNumber - The issue number that is blocked\n * @param blockingIssueDatabaseId - The database ID of the issue that blocks\n * @param repo - Optional repo in \"owner/repo\" format\n * @throws Error with specific message for: dependency already exists, issue not found, or dependencies feature not enabled\n */\nexport async function createIssueDependency(\n\tblockedIssueNumber: number,\n\tblockingIssueDatabaseId: number,\n\trepo?: string\n): Promise<void> {\n\tlogger.debug('Creating GitHub issue dependency', { blockedIssueNumber, blockingIssueDatabaseId, repo })\n\n\t// POST to the blocked issue's blocked_by endpoint with the blocking issue's database ID\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${blockedIssueNumber}/dependencies/blocked_by`\n\t\t: `repos/:owner/:repo/issues/${blockedIssueNumber}/dependencies/blocked_by`\n\n\ttry {\n\t\tawait executeGhCommand([\n\t\t\t'api',\n\t\t\t'-X', 'POST',\n\t\t\t'-H', 'Accept: application/vnd.github+json',\n\t\t\t'-H', 'X-GitHub-Api-Version: 2022-11-28',\n\t\t\tapiPath,\n\t\t\t'-F', `issue_id=${blockingIssueDatabaseId}`,\n\t\t])\n\t} catch (error) {\n\t\tif (error instanceof Error) {\n\t\t\tconst errorMessage = error.message\n\t\t\tconst stderr = 'stderr' in error ? (error as { stderr?: string }).stderr ?? '' : ''\n\t\t\tconst combinedError = `${errorMessage} ${stderr}`\n\n\t\t\t// Check for dependency already exists (422 Unprocessable Entity)\n\t\t\tif (combinedError.includes('422') || combinedError.includes('already exists') || combinedError.includes('Unprocessable Entity')) {\n\t\t\t\tthrow new Error(`Dependency already exists: issue #${blockedIssueNumber} is already blocked by the specified issue`)\n\t\t\t}\n\n\t\t\t// Check for issue not found (404)\n\t\t\tif (combinedError.includes('404') || combinedError.includes('Not Found')) {\n\t\t\t\tthrow new Error(`Issue not found: unable to create dependency for issue #${blockedIssueNumber}. The issue may not exist or you may not have access to it.`)\n\t\t\t}\n\n\t\t\t// Check for dependencies feature not enabled (403 or specific error message)\n\t\t\tif (combinedError.includes('403') || combinedError.includes('Forbidden') || combinedError.includes('not enabled')) {\n\t\t\t\tthrow new Error(`Dependencies feature not enabled: the repository may not have issue dependencies enabled. This feature requires GitHub Enterprise or specific repository settings.`)\n\t\t\t}\n\t\t}\n\n\t\t// Re-throw the original error if it doesn't match any known patterns\n\t\tthrow error\n\t}\n}\n\n/**\n * Remove a dependency between two issues (A blocks B)\n * Uses GitHub's issue dependencies API\n * @param blockedIssueNumber - The issue number that is blocked\n * @param blockingIssueDatabaseId - The database ID of the issue that blocks\n * @param repo - Optional repo in \"owner/repo\" format\n */\nexport async function removeIssueDependency(\n\tblockedIssueNumber: number,\n\tblockingIssueDatabaseId: number,\n\trepo?: string\n): Promise<void> {\n\tlogger.debug('Removing GitHub issue dependency', { blockedIssueNumber, blockingIssueDatabaseId, repo })\n\n\t// DELETE from the blocked issue's blocked_by endpoint with the blocking issue's database ID\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${blockedIssueNumber}/dependencies/blocked_by/${blockingIssueDatabaseId}`\n\t\t: `repos/:owner/:repo/issues/${blockedIssueNumber}/dependencies/blocked_by/${blockingIssueDatabaseId}`\n\n\tawait executeGhCommand([\n\t\t'api',\n\t\t'-X', 'DELETE',\n\t\t'-H', 'Accept: application/vnd.github+json',\n\t\t'-H', 'X-GitHub-Api-Version: 2022-11-28',\n\t\tapiPath,\n\t])\n}\n"],"mappings":"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","names":[]}
|
|
@@ -2,9 +2,31 @@
|
|
|
2
2
|
import {
|
|
3
3
|
extractIssueNumber,
|
|
4
4
|
extractPRNumber
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-GDS2HXSW.js";
|
|
6
6
|
|
|
7
7
|
// src/utils/IdentifierParser.ts
|
|
8
|
+
function matchIssueIdentifier(input) {
|
|
9
|
+
const trimmed = input.trim();
|
|
10
|
+
const linearPattern = /^([A-Z]{2,}-\d+)$/i;
|
|
11
|
+
const linearMatch = trimmed.match(linearPattern);
|
|
12
|
+
if (linearMatch == null ? void 0 : linearMatch[1]) {
|
|
13
|
+
return {
|
|
14
|
+
isIssueIdentifier: true,
|
|
15
|
+
type: "linear",
|
|
16
|
+
identifier: linearMatch[1].toUpperCase()
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const numericPattern = /^#?(\d+)$/;
|
|
20
|
+
const numericMatch = trimmed.match(numericPattern);
|
|
21
|
+
if (numericMatch == null ? void 0 : numericMatch[1]) {
|
|
22
|
+
return {
|
|
23
|
+
isIssueIdentifier: true,
|
|
24
|
+
type: "numeric",
|
|
25
|
+
identifier: numericMatch[1]
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return { isIssueIdentifier: false };
|
|
29
|
+
}
|
|
8
30
|
var IdentifierParser = class {
|
|
9
31
|
constructor(gitWorktreeManager) {
|
|
10
32
|
this.gitWorktreeManager = gitWorktreeManager;
|
|
@@ -83,6 +105,7 @@ var IdentifierParser = class {
|
|
|
83
105
|
};
|
|
84
106
|
|
|
85
107
|
export {
|
|
108
|
+
matchIssueIdentifier,
|
|
86
109
|
IdentifierParser
|
|
87
110
|
};
|
|
88
|
-
//# sourceMappingURL=chunk-
|
|
111
|
+
//# sourceMappingURL=chunk-TVH67KEO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/IdentifierParser.ts"],"sourcesContent":["import type { ParsedInput } from '../commands/start.js'\nimport type { GitWorktreeManager } from '../lib/GitWorktreeManager.js'\nimport { extractIssueNumber, extractPRNumber } from './git.js'\n\n/**\n * Result of parsing an issue identifier input\n */\nexport interface IssueIdentifierMatch {\n /** Whether the input matches an issue identifier pattern */\n isIssueIdentifier: boolean\n /** The type of identifier: 'numeric' (GitHub) or 'linear' (Linear format) */\n type?: 'numeric' | 'linear'\n /** The extracted identifier (without # prefix for numeric) */\n identifier?: string\n}\n\n/**\n * Check if a string looks like an issue identifier\n *\n * Matches:\n * - Numeric patterns: \"123\", \"#123\" (GitHub format)\n * - Linear patterns: \"ENG-123\", \"PLAT-456\" (requires at least 2 letters before dash)\n *\n * This is a pure pattern match - it does NOT validate that the issue exists.\n * Use IssueTracker.detectInputType() to validate existence.\n *\n * @param input - The input string to check\n * @returns Object with isIssueIdentifier flag and optional type/identifier\n */\nexport function matchIssueIdentifier(input: string): IssueIdentifierMatch {\n const trimmed = input.trim()\n\n // Check for Linear identifier format (TEAM-NUMBER, e.g., ENG-123, PLAT-456)\n // Requires at least 2 letters before dash to avoid conflict with PR-123 format\n const linearPattern = /^([A-Z]{2,}-\\d+)$/i\n const linearMatch = trimmed.match(linearPattern)\n if (linearMatch?.[1]) {\n return {\n isIssueIdentifier: true,\n type: 'linear',\n identifier: linearMatch[1].toUpperCase(),\n }\n }\n\n // Check for numeric pattern (GitHub format: 123 or #123)\n const numericPattern = /^#?(\\d+)$/\n const numericMatch = trimmed.match(numericPattern)\n if (numericMatch?.[1]) {\n return {\n isIssueIdentifier: true,\n type: 'numeric',\n identifier: numericMatch[1],\n }\n }\n\n return { isIssueIdentifier: false }\n}\n\n/**\n * IdentifierParser provides consistent identifier parsing across commands\n * using pattern-based detection without GitHub API calls.\n *\n * Detection Strategy:\n * 1. For numeric input (e.g., \"42\", \"#66\"):\n * - Check for PR worktree first (_pr_N pattern in path)\n * - Then check for issue worktree (issue-N pattern in branch)\n * 2. For alphanumeric input (e.g., \"ENG-123\"):\n * - Check for issue worktree with alphanumeric identifier\n * 3. For branch-style input (e.g., \"feat/issue-42__description\", \"pr/123\"):\n * - Find matching worktree by branch name\n * - Extract PR number from branch name if present (priority)\n * - Extract issue number from branch name if present\n * - Return as PR/issue type if number found, otherwise branch type\n *\n * This ensures:\n * - No unnecessary GitHub API calls\n * - Consistent behavior across finish/cleanup commands\n * - PR detection takes priority over issue detection\n * - Issue numbers are extracted from branch names for \"Fixes #N\" commit trailers\n */\nexport class IdentifierParser {\n\tconstructor(private gitWorktreeManager: GitWorktreeManager) {}\n\n\t/**\n\t * Parse identifier using pattern-based detection on existing worktrees.\n\t * Does NOT make GitHub API calls - only checks local worktree patterns.\n\t *\n\t * @param identifier - The identifier to parse (e.g., \"42\", \"#66\", \"ENG-123\", \"my-branch\")\n\t * @returns ParsedInput with type, number/branchName, and originalInput\n\t * @throws Error if no matching worktree is found\n\t */\n\tasync parseForPatternDetection(identifier: string): Promise<ParsedInput> {\n\t\t// Remove # prefix if present and trim whitespace\n\t\tconst cleanId = identifier.replace(/^#/, '').trim()\n\t\tconst originalInput = identifier\n\n\t\t// Check if input is numeric (GitHub-style issue/PR numbers)\n\t\tconst numericMatch = cleanId.match(/^(\\d+)$/)\n\n\t\tif (numericMatch?.[1]) {\n\t\t\tconst number = parseInt(numericMatch[1], 10)\n\n\t\t\t// Priority 1: Check for PR worktree (_pr_N pattern)\n\t\t\t// Pass empty string for branch name since we don't know it yet\n\t\t\tconst prWorktree = await this.gitWorktreeManager.findWorktreeForPR(number, '')\n\t\t\tif (prWorktree) {\n\t\t\t\treturn {\n\t\t\t\t\ttype: 'pr',\n\t\t\t\t\tnumber,\n\t\t\t\t\toriginalInput,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Priority 2: Check for issue worktree (issue-N pattern)\n\t\t\tconst issueWorktree = await this.gitWorktreeManager.findWorktreeForIssue(number)\n\t\t\tif (issueWorktree) {\n\t\t\t\treturn {\n\t\t\t\t\ttype: 'issue',\n\t\t\t\t\tnumber,\n\t\t\t\t\toriginalInput,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// No matching worktree found for numeric input\n\t\t\tthrow new Error(`No worktree found for identifier: ${identifier}`)\n\t\t}\n\n\t\t// Check if input is alphanumeric issue identifier (Linear/Jira-style: ABC-123, ENG-42)\n\t\tconst alphanumericMatch = cleanId.match(/^([A-Za-z]+-\\d+)$/)\n\n\t\tif (alphanumericMatch?.[1]) {\n\t\t\tconst alphanumericId = alphanumericMatch[1]\n\n\t\t\t// Check for issue worktree with alphanumeric identifier\n\t\t\tconst issueWorktree = await this.gitWorktreeManager.findWorktreeForIssue(alphanumericId)\n\t\t\tif (issueWorktree) {\n\t\t\t\treturn {\n\t\t\t\t\ttype: 'issue',\n\t\t\t\t\tnumber: alphanumericId,\n\t\t\t\t\toriginalInput,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// No matching worktree found for alphanumeric identifier\n\t\t\tthrow new Error(`No worktree found for identifier: ${identifier}`)\n\t\t}\n\n\t\t// Non-numeric/non-alphanumeric input: treat as branch name\n\t\tconst branchWorktree = await this.gitWorktreeManager.findWorktreeForBranch(cleanId)\n\t\tif (branchWorktree) {\n\t\t\t// Priority 1: Check for PR pattern in the input\n\t\t\tconst prFromBranch = extractPRNumber(cleanId)\n\t\t\tif (prFromBranch !== null) {\n\t\t\t\treturn {\n\t\t\t\t\ttype: 'pr',\n\t\t\t\t\tnumber: prFromBranch,\n\t\t\t\t\toriginalInput,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Priority 2: Try to extract issue number from branch name\n\t\t\t// This handles cases like \"feat/issue-42__description\" passed as explicit input\n\t\t\tconst issueFromBranch = extractIssueNumber(cleanId)\n\t\t\tif (issueFromBranch !== null) {\n\t\t\t\treturn {\n\t\t\t\t\ttype: 'issue',\n\t\t\t\t\tnumber: issueFromBranch,\n\t\t\t\t\toriginalInput,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\ttype: 'branch',\n\t\t\t\tbranchName: cleanId,\n\t\t\t\toriginalInput,\n\t\t\t}\n\t\t}\n\n\t\t// No matching worktree found for branch name\n\t\tthrow new Error(`No worktree found for identifier: ${identifier}`)\n\t}\n}\n"],"mappings":";;;;;;;AA6BO,SAAS,qBAAqB,OAAqC;AACxE,QAAM,UAAU,MAAM,KAAK;AAI3B,QAAM,gBAAgB;AACtB,QAAM,cAAc,QAAQ,MAAM,aAAa;AAC/C,MAAI,2CAAc,IAAI;AACpB,WAAO;AAAA,MACL,mBAAmB;AAAA,MACnB,MAAM;AAAA,MACN,YAAY,YAAY,CAAC,EAAE,YAAY;AAAA,IACzC;AAAA,EACF;AAGA,QAAM,iBAAiB;AACvB,QAAM,eAAe,QAAQ,MAAM,cAAc;AACjD,MAAI,6CAAe,IAAI;AACrB,WAAO;AAAA,MACL,mBAAmB;AAAA,MACnB,MAAM;AAAA,MACN,YAAY,aAAa,CAAC;AAAA,IAC5B;AAAA,EACF;AAEA,SAAO,EAAE,mBAAmB,MAAM;AACpC;AAwBO,IAAM,mBAAN,MAAuB;AAAA,EAC7B,YAAoB,oBAAwC;AAAxC;AAAA,EAAyC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAU7D,MAAM,yBAAyB,YAA0C;AAExE,UAAM,UAAU,WAAW,QAAQ,MAAM,EAAE,EAAE,KAAK;AAClD,UAAM,gBAAgB;AAGtB,UAAM,eAAe,QAAQ,MAAM,SAAS;AAE5C,QAAI,6CAAe,IAAI;AACtB,YAAM,SAAS,SAAS,aAAa,CAAC,GAAG,EAAE;AAI3C,YAAM,aAAa,MAAM,KAAK,mBAAmB,kBAAkB,QAAQ,EAAE;AAC7E,UAAI,YAAY;AACf,eAAO;AAAA,UACN,MAAM;AAAA,UACN;AAAA,UACA;AAAA,QACD;AAAA,MACD;AAGA,YAAM,gBAAgB,MAAM,KAAK,mBAAmB,qBAAqB,MAAM;AAC/E,UAAI,eAAe;AAClB,eAAO;AAAA,UACN,MAAM;AAAA,UACN;AAAA,UACA;AAAA,QACD;AAAA,MACD;AAGA,YAAM,IAAI,MAAM,qCAAqC,UAAU,EAAE;AAAA,IAClE;AAGA,UAAM,oBAAoB,QAAQ,MAAM,mBAAmB;AAE3D,QAAI,uDAAoB,IAAI;AAC3B,YAAM,iBAAiB,kBAAkB,CAAC;AAG1C,YAAM,gBAAgB,MAAM,KAAK,mBAAmB,qBAAqB,cAAc;AACvF,UAAI,eAAe;AAClB,eAAO;AAAA,UACN,MAAM;AAAA,UACN,QAAQ;AAAA,UACR;AAAA,QACD;AAAA,MACD;AAGA,YAAM,IAAI,MAAM,qCAAqC,UAAU,EAAE;AAAA,IAClE;AAGA,UAAM,iBAAiB,MAAM,KAAK,mBAAmB,sBAAsB,OAAO;AAClF,QAAI,gBAAgB;AAEnB,YAAM,eAAe,gBAAgB,OAAO;AAC5C,UAAI,iBAAiB,MAAM;AAC1B,eAAO;AAAA,UACN,MAAM;AAAA,UACN,QAAQ;AAAA,UACR;AAAA,QACD;AAAA,MACD;AAIA,YAAM,kBAAkB,mBAAmB,OAAO;AAClD,UAAI,oBAAoB,MAAM;AAC7B,eAAO;AAAA,UACN,MAAM;AAAA,UACN,QAAQ;AAAA,UACR;AAAA,QACD;AAAA,MACD;AAEA,aAAO;AAAA,QACN,MAAM;AAAA,QACN,YAAY;AAAA,QACZ;AAAA,MACD;AAAA,IACD;AAGA,UAAM,IAAI,MAAM,qCAAqC,UAAU,EAAE;AAAA,EAClE;AACD;","names":[]}
|
|
@@ -3,20 +3,30 @@ import {
|
|
|
3
3
|
createLinearChildIssue,
|
|
4
4
|
createLinearComment,
|
|
5
5
|
createLinearIssue,
|
|
6
|
+
createLinearIssueRelation,
|
|
7
|
+
deleteLinearIssueRelation,
|
|
6
8
|
fetchLinearIssue,
|
|
7
9
|
fetchLinearIssueComments,
|
|
10
|
+
findLinearIssueRelation,
|
|
11
|
+
getLinearChildIssues,
|
|
8
12
|
getLinearComment,
|
|
13
|
+
getLinearIssueDependencies,
|
|
9
14
|
updateLinearComment
|
|
10
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-RNBIISBZ.js";
|
|
11
16
|
import {
|
|
12
17
|
addSubIssue,
|
|
13
18
|
createIssue,
|
|
14
19
|
createIssueComment,
|
|
20
|
+
createIssueDependency,
|
|
15
21
|
createPRComment,
|
|
16
22
|
executeGhCommand,
|
|
23
|
+
getIssueDatabaseId,
|
|
24
|
+
getIssueDependencies,
|
|
17
25
|
getIssueNodeId,
|
|
26
|
+
getSubIssues,
|
|
27
|
+
removeIssueDependency,
|
|
18
28
|
updateIssueComment
|
|
19
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-THS5L54H.js";
|
|
20
30
|
import {
|
|
21
31
|
logger
|
|
22
32
|
} from "./chunk-VT4PDUYT.js";
|
|
@@ -512,6 +522,72 @@ var GitHubIssueManagementProvider = class {
|
|
|
512
522
|
number: childNumber
|
|
513
523
|
};
|
|
514
524
|
}
|
|
525
|
+
/**
|
|
526
|
+
* Create a blocking dependency between two issues (A blocks B)
|
|
527
|
+
* Uses GitHub's sub-issues API: blocking issue becomes parent, blocked issue becomes sub-issue
|
|
528
|
+
*/
|
|
529
|
+
async createDependency(input) {
|
|
530
|
+
const { blockingIssue, blockedIssue, repo } = input;
|
|
531
|
+
const blockingNumber = parseInt(blockingIssue, 10);
|
|
532
|
+
if (isNaN(blockingNumber)) {
|
|
533
|
+
throw new Error(`Invalid GitHub issue number: ${blockingIssue}. GitHub issue IDs must be numeric.`);
|
|
534
|
+
}
|
|
535
|
+
const blockedNumber = parseInt(blockedIssue, 10);
|
|
536
|
+
if (isNaN(blockedNumber)) {
|
|
537
|
+
throw new Error(`Invalid GitHub issue number: ${blockedIssue}. GitHub issue IDs must be numeric.`);
|
|
538
|
+
}
|
|
539
|
+
const blockingDatabaseId = await getIssueDatabaseId(blockingNumber, repo);
|
|
540
|
+
await createIssueDependency(blockedNumber, blockingDatabaseId, repo);
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Get dependencies for an issue
|
|
544
|
+
*/
|
|
545
|
+
async getDependencies(input) {
|
|
546
|
+
const { number, direction, repo } = input;
|
|
547
|
+
const issueNumber = parseInt(number, 10);
|
|
548
|
+
if (isNaN(issueNumber)) {
|
|
549
|
+
throw new Error(`Invalid GitHub issue number: ${number}. GitHub issue IDs must be numeric.`);
|
|
550
|
+
}
|
|
551
|
+
const result = {
|
|
552
|
+
blocking: [],
|
|
553
|
+
blockedBy: []
|
|
554
|
+
};
|
|
555
|
+
if (direction === "blocking" || direction === "both") {
|
|
556
|
+
result.blocking = await getIssueDependencies(issueNumber, "blocking", repo);
|
|
557
|
+
}
|
|
558
|
+
if (direction === "blocked_by" || direction === "both") {
|
|
559
|
+
result.blockedBy = await getIssueDependencies(issueNumber, "blocked_by", repo);
|
|
560
|
+
}
|
|
561
|
+
return result;
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Remove a blocking dependency between two issues (A blocks B)
|
|
565
|
+
* Uses GitHub's sub-issues API: blocking issue is parent, blocked issue is sub-issue
|
|
566
|
+
*/
|
|
567
|
+
async removeDependency(input) {
|
|
568
|
+
const { blockingIssue, blockedIssue, repo } = input;
|
|
569
|
+
const blockingNumber = parseInt(blockingIssue, 10);
|
|
570
|
+
if (isNaN(blockingNumber)) {
|
|
571
|
+
throw new Error(`Invalid GitHub issue number: ${blockingIssue}. GitHub issue IDs must be numeric.`);
|
|
572
|
+
}
|
|
573
|
+
const blockedNumber = parseInt(blockedIssue, 10);
|
|
574
|
+
if (isNaN(blockedNumber)) {
|
|
575
|
+
throw new Error(`Invalid GitHub issue number: ${blockedIssue}. GitHub issue IDs must be numeric.`);
|
|
576
|
+
}
|
|
577
|
+
const blockingDatabaseId = await getIssueDatabaseId(blockingNumber, repo);
|
|
578
|
+
await removeIssueDependency(blockedNumber, blockingDatabaseId, repo);
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Get child issues (sub-issues) of a parent issue
|
|
582
|
+
*/
|
|
583
|
+
async getChildIssues(input) {
|
|
584
|
+
const { number, repo } = input;
|
|
585
|
+
const issueNumber = parseInt(number, 10);
|
|
586
|
+
if (isNaN(issueNumber)) {
|
|
587
|
+
throw new Error(`Invalid GitHub issue number: ${number}. GitHub issue IDs must be numeric.`);
|
|
588
|
+
}
|
|
589
|
+
return await getSubIssues(issueNumber, repo);
|
|
590
|
+
}
|
|
515
591
|
};
|
|
516
592
|
|
|
517
593
|
// src/utils/linear-markup-converter.ts
|
|
@@ -828,6 +904,42 @@ var LinearIssueManagementProvider = class {
|
|
|
828
904
|
url: result.url
|
|
829
905
|
};
|
|
830
906
|
}
|
|
907
|
+
/**
|
|
908
|
+
* Create a blocking dependency between two issues
|
|
909
|
+
*/
|
|
910
|
+
async createDependency(input) {
|
|
911
|
+
const { blockingIssue, blockedIssue } = input;
|
|
912
|
+
const [blockingIssueData, blockedIssueData] = await Promise.all([
|
|
913
|
+
fetchLinearIssue(blockingIssue),
|
|
914
|
+
fetchLinearIssue(blockedIssue)
|
|
915
|
+
]);
|
|
916
|
+
await createLinearIssueRelation(blockingIssueData.id, blockedIssueData.id);
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Get dependencies for an issue
|
|
920
|
+
*/
|
|
921
|
+
async getDependencies(input) {
|
|
922
|
+
const { number, direction } = input;
|
|
923
|
+
return await getLinearIssueDependencies(number, direction);
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Remove a blocking dependency between two issues
|
|
927
|
+
*/
|
|
928
|
+
async removeDependency(input) {
|
|
929
|
+
const { blockingIssue, blockedIssue } = input;
|
|
930
|
+
const relationId = await findLinearIssueRelation(blockingIssue, blockedIssue);
|
|
931
|
+
if (!relationId) {
|
|
932
|
+
throw new Error(`No blocking dependency found from ${blockingIssue} to ${blockedIssue}`);
|
|
933
|
+
}
|
|
934
|
+
await deleteLinearIssueRelation(relationId);
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Get child issues of a parent issue
|
|
938
|
+
*/
|
|
939
|
+
async getChildIssues(input) {
|
|
940
|
+
const { number } = input;
|
|
941
|
+
return await getLinearChildIssues(number);
|
|
942
|
+
}
|
|
831
943
|
};
|
|
832
944
|
|
|
833
945
|
// src/mcp/IssueManagementProviderFactory.ts
|
|
@@ -850,4 +962,4 @@ var IssueManagementProviderFactory = class {
|
|
|
850
962
|
export {
|
|
851
963
|
IssueManagementProviderFactory
|
|
852
964
|
};
|
|
853
|
-
//# sourceMappingURL=chunk-
|
|
965
|
+
//# sourceMappingURL=chunk-UDZCTLD6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/image-processor.ts","../src/mcp/GitHubIssueManagementProvider.ts","../src/utils/linear-markup-converter.ts","../src/mcp/LinearIssueManagementProvider.ts","../src/mcp/IssueManagementProviderFactory.ts"],"sourcesContent":["/* global fetch, AbortController, setTimeout, clearTimeout */\nimport { tmpdir } from 'node:os'\nimport { join, extname } from 'node:path'\nimport { existsSync, mkdirSync, createWriteStream, unlinkSync } from 'node:fs'\nimport { pipeline } from 'node:stream/promises'\nimport { Readable } from 'node:stream'\nimport { createHash } from 'node:crypto'\nimport { execa } from 'execa'\nimport { logger } from './logger.js'\nimport type { IssueProvider } from '../mcp/types.js'\n\n/**\n * Represents a matched image in markdown content\n */\nexport interface ImageMatch {\n fullMatch: string\n url: string\n isMarkdown: boolean // true for , false for <img>\n}\n\n/**\n * Supported image extensions\n */\nconst SUPPORTED_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg']\n\n/**\n * Maximum allowed image size (10MB)\n */\nconst MAX_IMAGE_SIZE = 10 * 1024 * 1024\n\n/**\n * Request timeout in milliseconds (30 seconds)\n */\nconst REQUEST_TIMEOUT_MS = 30000\n\n/**\n * Cache directory path for downloaded images\n */\nexport const CACHE_DIR = join(tmpdir(), 'iloom-images')\n\n/**\n * Cached GitHub auth token (module-level to avoid repeated `gh auth token` calls)\n */\nlet cachedGitHubToken: string | undefined\n\n/**\n * Extract all image URLs from markdown content\n * Handles both  and <img src=\"url\"> formats\n *\n * @param content - Markdown content to parse\n * @returns Array of image matches with full match string and URL\n */\nexport function extractMarkdownImageUrls(content: string): ImageMatch[] {\n if (!content) {\n return []\n }\n\n const matches: ImageMatch[] = []\n\n // Regex for markdown images: \n // Captures the entire match and the URL separately\n // Handles parentheses in URLs by matching balanced parens\n // The URL part matches: non-paren chars OR (balanced paren group)*, followed by non-paren/non-space chars\n const markdownRegex = /!\\[([^\\]]*)\\]\\(((?:[^()\\s]|\\((?:[^()\\s]|\\([^()]*\\))*\\))+)\\)/g\n let match: RegExpExecArray | null\n\n while ((match = markdownRegex.exec(content)) !== null) {\n const url = match[2]\n if (url) {\n matches.push({\n fullMatch: match[0],\n url,\n isMarkdown: true\n })\n }\n }\n\n // Regex for HTML img tags: <img ... src=\"url\" ...>\n // Handles both double and single quotes, and self-closing tags\n const htmlImgRegex = /<img\\s+[^>]*src=[\"']([^\"']+)[\"'][^>]*\\/?>/gi\n\n while ((match = htmlImgRegex.exec(content)) !== null) {\n const url = match[1]\n if (url) {\n matches.push({\n fullMatch: match[0],\n url,\n isMarkdown: false\n })\n }\n }\n\n return matches\n}\n\n/**\n * Check if URL requires authentication to download\n * - Linear: uploads.linear.app\n * - GitHub: private-user-images.githubusercontent.com\n *\n * @param url - Image URL to check\n * @returns true if URL requires authentication\n */\nexport function isAuthenticatedImageUrl(url: string): boolean {\n try {\n const parsedUrl = new URL(url)\n const hostname = parsedUrl.hostname.toLowerCase()\n\n // Linear uploads require authentication\n if (hostname === 'uploads.linear.app') {\n return true\n }\n\n // GitHub private user images require authentication\n if (hostname === 'private-user-images.githubusercontent.com') {\n return true\n }\n\n // GitHub user-attachments (uploaded images in issues/PRs) require authentication\n if (hostname === 'github.com' && parsedUrl.pathname.startsWith('/user-attachments/assets/')) {\n return true\n }\n\n return false\n } catch {\n // Invalid URL - treat as not authenticated\n return false\n }\n}\n\n/**\n * Get extension from URL pathname\n *\n * @param url - URL to extract extension from\n * @returns Extension including dot (e.g., '.png') or null if not found\n */\nfunction getExtensionFromUrl(url: string): string | null {\n try {\n const parsedUrl = new URL(url)\n const pathname = parsedUrl.pathname\n const ext = extname(pathname).toLowerCase()\n\n if (SUPPORTED_EXTENSIONS.includes(ext)) {\n return ext\n }\n return null\n } catch {\n return null\n }\n}\n\n/**\n * Generate cache key from URL\n * For GitHub URLs, strips JWT query params to ensure consistent caching\n * Returns hash + original extension\n *\n * @param url - Image URL to generate cache key for\n * @returns Cache key (hash + extension)\n */\nexport function getCacheKey(url: string): string {\n const parsedUrl = new URL(url)\n\n // For GitHub private images, remove jwt query param to get stable cache key\n // The jwt changes each fetch but the base URL is the same for the same image\n if (parsedUrl.hostname === 'private-user-images.githubusercontent.com') {\n parsedUrl.searchParams.delete('jwt')\n }\n\n // Get URL without volatile params for hashing\n const stableUrl = parsedUrl.toString()\n\n // Generate SHA256 hash of the stable URL (first 16 chars for brevity)\n const hash = createHash('sha256').update(stableUrl).digest('hex').slice(0, 16)\n\n // Extract extension from URL pathname, default to .png\n const ext = getExtensionFromUrl(url) ?? '.png'\n\n return `${hash}${ext}`\n}\n\n/**\n * Check if image is already cached\n * Returns file path if exists, undefined otherwise\n *\n * @param url - Image URL to check cache for\n * @returns Cached file path or undefined\n */\nexport function getCachedImagePath(url: string): string | undefined {\n const cacheKey = getCacheKey(url)\n const cachedPath = join(CACHE_DIR, cacheKey)\n\n if (existsSync(cachedPath)) {\n return cachedPath\n }\n return undefined\n}\n\n/**\n * Get authentication token for the given provider\n *\n * @param provider - Provider type ('github' or 'linear')\n * @returns Authentication token or undefined\n */\nasync function getAuthToken(provider: IssueProvider): Promise<string | undefined> {\n if (provider === 'github') {\n // Return cached token if available\n if (cachedGitHubToken !== undefined) {\n return cachedGitHubToken\n }\n\n try {\n // Execute `gh auth token` to get GitHub token\n const result = await execa('gh', ['auth', 'token'])\n cachedGitHubToken = result.stdout.trim()\n return cachedGitHubToken\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n logger.warn(`Failed to get GitHub auth token via gh CLI: ${message}`)\n return undefined\n }\n }\n\n if (provider === 'linear') {\n // Linear token from environment variable\n return process.env.LINEAR_API_TOKEN\n }\n\n return undefined\n}\n\n/**\n * Clear the cached GitHub auth token (for testing purposes)\n */\nexport function clearCachedGitHubToken(): void {\n cachedGitHubToken = undefined\n}\n\n/**\n * Download image from URL and stream it directly to a file\n *\n * @param url - Image URL to download\n * @param destPath - Destination file path\n * @param authHeader - Optional Authorization header value\n * @throws Error if download fails, times out, or exceeds size limit\n */\nexport async function downloadAndSaveImage(\n url: string,\n destPath: string,\n authHeader?: string\n): Promise<void> {\n const headers: Record<string, string> = {}\n if (authHeader) {\n headers['Authorization'] = authHeader\n }\n\n // Set up abort controller for timeout\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS)\n\n try {\n const response = await fetch(url, { headers, signal: controller.signal })\n\n if (!response.ok) {\n throw new Error(`Failed to download image: ${response.status} ${response.statusText}`)\n }\n\n // Check Content-Length header if available\n const contentLength = response.headers.get('Content-Length')\n if (contentLength && parseInt(contentLength, 10) > MAX_IMAGE_SIZE) {\n throw new Error(`Image too large: ${contentLength} bytes exceeds ${MAX_IMAGE_SIZE} byte limit`)\n }\n\n if (!response.body) {\n throw new Error('Response body is null')\n }\n\n // Convert ReadableStream to Node.js Readable\n const reader = response.body.getReader()\n let bytesWritten = 0\n\n const nodeReadable = new Readable({\n async read(): Promise<void> {\n try {\n const { done, value } = await reader.read()\n if (done) {\n this.push(null)\n return\n }\n\n bytesWritten += value.byteLength\n if (bytesWritten > MAX_IMAGE_SIZE) {\n reader.cancel()\n this.destroy(new Error(`Image too large: ${bytesWritten} bytes exceeds ${MAX_IMAGE_SIZE} byte limit`))\n return\n }\n\n this.push(Buffer.from(value))\n } catch (err) {\n this.destroy(err instanceof Error ? err : new Error(String(err)))\n }\n }\n })\n\n // Ensure cache directory exists\n if (!existsSync(CACHE_DIR)) {\n mkdirSync(CACHE_DIR, { recursive: true })\n }\n\n // Stream to file\n const writeStream = createWriteStream(destPath)\n\n try {\n await pipeline(nodeReadable, writeStream)\n } catch (pipelineError) {\n // Clean up partial file on error\n try {\n if (existsSync(destPath)) {\n unlinkSync(destPath)\n }\n } catch {\n // Ignore cleanup errors\n }\n throw pipelineError\n }\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n throw new Error(`Image download timed out after ${REQUEST_TIMEOUT_MS}ms`)\n }\n throw error\n } finally {\n clearTimeout(timeoutId)\n }\n}\n\n/**\n * Get the destination path for caching an image\n *\n * @param url - Original image URL (used to generate cache key)\n * @returns Local file path where image should be saved\n */\nexport function getCacheDestPath(url: string): string {\n // Ensure cache directory exists\n if (!existsSync(CACHE_DIR)) {\n mkdirSync(CACHE_DIR, { recursive: true })\n }\n\n // Generate cache key from URL\n const cacheKey = getCacheKey(url)\n return join(CACHE_DIR, cacheKey)\n}\n\n/**\n * Rewrite image URLs in markdown content\n *\n * @param content - Original markdown content\n * @param urlMap - Map of original URLs to local file paths\n * @returns Content with URLs replaced\n */\nexport function rewriteMarkdownUrls(\n content: string,\n urlMap: Map<string, string>\n): string {\n let result = content\n\n for (const [originalUrl, localPath] of urlMap) {\n // Escape special regex characters in the URL\n const escapedUrl = originalUrl.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n const urlRegex = new RegExp(escapedUrl, 'g')\n result = result.replace(urlRegex, localPath)\n }\n\n return result\n}\n\n/**\n * Main entry point: process all images in markdown content\n * Downloads authenticated images (with caching), saves locally, rewrites URLs\n *\n * @param content - Markdown content to process\n * @param provider - Image provider for authentication ('github' or 'linear')\n * @returns Content with authenticated image URLs replaced with local file paths\n */\nexport async function processMarkdownImages(\n content: string,\n provider: IssueProvider\n): Promise<string> {\n // Early return if empty\n if (!content) {\n return ''\n }\n\n // Extract all image URLs\n const images = extractMarkdownImageUrls(content)\n if (images.length === 0) {\n return content\n }\n\n // Filter to only authenticated URLs\n const authImages = images.filter(img => isAuthenticatedImageUrl(img.url))\n if (authImages.length === 0) {\n return content\n }\n\n // Get auth token for provider\n const authToken = await getAuthToken(provider)\n\n // Deduplicate URLs (same image might appear multiple times)\n const uniqueUrls = [...new Set(authImages.map(img => img.url))]\n\n // Build URL map - process all unique URLs in parallel\n const urlMap = new Map<string, string>()\n\n // Download/cache images in parallel\n const downloadPromises = uniqueUrls.map(async (url) => {\n try {\n // Check cache first\n const cachedPath = getCachedImagePath(url)\n if (cachedPath) {\n logger.debug(`Using cached image: ${cachedPath}`)\n return { url, localPath: cachedPath }\n }\n\n // Cache miss - download and stream directly to file\n logger.debug(`Downloading image: ${url}`)\n const destPath = getCacheDestPath(url)\n await downloadAndSaveImage(\n url,\n destPath,\n authToken ? `Bearer ${authToken}` : undefined\n )\n return { url, localPath: destPath }\n } catch (error) {\n // Graceful degradation - log warning, return null to keep original URL\n const message = error instanceof Error ? error.message : String(error)\n logger.warn(`Failed to download image ${url}: ${message}`)\n return null\n }\n })\n\n const results = await Promise.all(downloadPromises)\n\n // Build URL map from results\n for (const result of results) {\n if (result !== null) {\n urlMap.set(result.url, result.localPath)\n }\n }\n\n // Rewrite and return\n return rewriteMarkdownUrls(content, urlMap)\n}\n","/**\n * GitHub implementation of Issue Management Provider\n * Uses GitHub CLI for all operations\n * Normalizes GitHub-specific fields (login) to provider-agnostic core fields (id, displayName)\n */\n\nimport type {\n\tIssueManagementProvider,\n\tGetIssueInput,\n\tGetPRInput,\n\tGetCommentInput,\n\tCreateCommentInput,\n\tUpdateCommentInput,\n\tCreateIssueInput,\n\tCreateChildIssueInput,\n\tCreateDependencyInput,\n\tGetDependenciesInput,\n\tRemoveDependencyInput,\n\tGetChildIssuesInput,\n\tCreateIssueResult,\n\tIssueResult,\n\tPRResult,\n\tCommentDetailResult,\n\tCommentResult,\n\tDependenciesResult,\n\tChildIssueResult,\n\tFlexibleAuthor,\n} from './types.js'\nimport {\n\texecuteGhCommand,\n\tcreateIssueComment,\n\tupdateIssueComment,\n\tcreatePRComment,\n\tcreateIssue,\n\tgetIssueNodeId,\n\taddSubIssue,\n\tgetIssueDatabaseId,\n\tgetIssueDependencies,\n\tcreateIssueDependency,\n\tremoveIssueDependency,\n\tgetSubIssues,\n} from '../utils/github.js'\nimport { processMarkdownImages } from '../utils/image-processor.js'\n\n/**\n * GitHub-specific author structure from API\n */\ninterface GitHubAuthor {\n\tlogin: string\n\tid?: number\n\tavatarUrl?: string\n\turl?: string\n}\n\n/**\n * Normalize GitHub author to FlexibleAuthor format\n */\nfunction normalizeAuthor(author: GitHubAuthor | null | undefined): FlexibleAuthor | null {\n\tif (!author) return null\n\n\treturn {\n\t\tid: author.id ? String(author.id) : author.login,\n\t\tdisplayName: author.login, // GitHub uses login as primary identifier\n\t\tlogin: author.login, // Preserve original GitHub field\n\t\t...(author.avatarUrl && { avatarUrl: author.avatarUrl }),\n\t\t...(author.url && { url: author.url }),\n\t}\n}\n\n/**\n * Extract numeric comment ID from GitHub comment URL\n * URL format: https://github.com/owner/repo/issues/123#issuecomment-3615239386\n */\nexport function extractNumericIdFromUrl(url: string): string {\n\tconst match = url.match(/#issuecomment-(\\d+)$/)\n\tif (!match?.[1]) {\n\t\tthrow new Error(`Cannot extract comment ID from URL: ${url}`)\n\t}\n\treturn match[1]\n}\n\n/**\n * GitHub-specific implementation of IssueManagementProvider\n */\nexport class GitHubIssueManagementProvider implements IssueManagementProvider {\n\treadonly providerName = 'github'\n\treadonly issuePrefix = '#'\n\n\t/**\n\t * Fetch issue details using gh CLI\n\t * Normalizes GitHub-specific fields to provider-agnostic format\n\t */\n\tasync getIssue(input: GetIssueInput): Promise<IssueResult> {\n\t\tconst { number, includeComments = true, repo } = input\n\n\t\t// Convert string ID to number for GitHub CLI\n\t\tconst issueNumber = parseInt(number, 10)\n\t\tif (isNaN(issueNumber)) {\n\t\t\tthrow new Error(`Invalid GitHub issue number: ${number}. GitHub issue IDs must be numeric.`)\n\t\t}\n\n\t\t// Build fields list based on whether we need comments\n\t\tconst fields = includeComments\n\t\t\t? 'body,title,comments,labels,assignees,milestone,author,state,number,url'\n\t\t\t: 'body,title,labels,assignees,milestone,author,state,number,url'\n\n\t\t// Use gh issue view to fetch issue details\n\t\tinterface GitHubIssueResponse {\n\t\t\tnumber: number\n\t\t\ttitle: string\n\t\t\tbody: string\n\t\t\tstate: string\n\t\t\turl: string\n\t\t\tauthor?: GitHubAuthor\n\t\t\tlabels?: Array<{ name: string; color?: string; description?: string }>\n\t\t\tassignees?: Array<GitHubAuthor>\n\t\t\tmilestone?: { title: string; number?: number; state?: string }\n\t\t\tcomments?: Array<{\n\t\t\t\tid: number\n\t\t\t\tauthor: GitHubAuthor\n\t\t\t\tbody: string\n\t\t\t\tcreatedAt: string\n\t\t\t\tupdatedAt?: string\n\t\t\t\turl: string\n\t\t\t}>\n\t\t}\n\n\t\tconst args = [\n\t\t\t'issue',\n\t\t\t'view',\n\t\t\tString(issueNumber),\n\t\t\t'--json',\n\t\t\tfields,\n\t\t]\n\n\t\t// Add --repo flag if repo is provided (gh CLI handles both owner/repo and URL formats)\n\t\tif (repo) {\n\t\t\targs.push('--repo', repo)\n\t\t}\n\n\t\tconst raw = await executeGhCommand<GitHubIssueResponse>(args)\n\n\t\t// Normalize to IssueResult with core fields + passthrough\n\t\tconst result: IssueResult = {\n\t\t\t// Core fields\n\t\t\tid: String(raw.number),\n\t\t\ttitle: raw.title,\n\t\t\tbody: raw.body,\n\t\t\tstate: raw.state,\n\t\t\turl: raw.url,\n\t\t\tprovider: 'github',\n\n\t\t\t// Normalized author\n\t\t\tauthor: normalizeAuthor(raw.author),\n\n\t\t\t// Optional flexible fields\n\t\t\t...(raw.assignees && {\n\t\t\t\tassignees: raw.assignees.map(a => normalizeAuthor(a)).filter((a): a is FlexibleAuthor => a !== null),\n\t\t\t}),\n\t\t\t...(raw.labels && {\n\t\t\t\tlabels: raw.labels,\n\t\t\t}),\n\n\t\t\t// GitHub-specific passthrough fields\n\t\t\t...(raw.milestone && {\n\t\t\t\tmilestone: raw.milestone,\n\t\t\t}),\n\t\t}\n\n\t\t// Handle comments with normalized authors\n\t\t// Use extractNumericIdFromUrl to get REST API-compatible numeric IDs from comment URLs\n\t\t// (GitHub CLI returns GraphQL node IDs in the id field, but REST API expects numeric IDs)\n\t\tif (raw.comments !== undefined) {\n\t\t\tresult.comments = raw.comments.map(comment => ({\n\t\t\t\tid: extractNumericIdFromUrl(comment.url),\n\t\t\t\tbody: comment.body,\n\t\t\t\tcreatedAt: comment.createdAt,\n\t\t\t\tauthor: normalizeAuthor(comment.author),\n\t\t\t\t...(comment.updatedAt && { updatedAt: comment.updatedAt }),\n\t\t\t}))\n\t\t}\n\n\t\t// Process authenticated images in body and comments\n\t\tresult.body = await processMarkdownImages(result.body, 'github')\n\t\tif (result.comments) {\n\t\t\tfor (const comment of result.comments) {\n\t\t\t\tcomment.body = await processMarkdownImages(comment.body, 'github')\n\t\t\t}\n\t\t}\n\n\t\treturn result\n\t}\n\n\t/**\n\t * Fetch pull request details using gh CLI\n\t * Normalizes GitHub-specific fields to provider-agnostic format\n\t */\n\tasync getPR(input: GetPRInput): Promise<PRResult> {\n\t\tconst { number, includeComments = true, repo } = input\n\n\t\t// Convert string ID to number for GitHub CLI\n\t\tconst prNumber = parseInt(number, 10)\n\t\tif (isNaN(prNumber)) {\n\t\t\tthrow new Error(`Invalid GitHub PR number: ${number}. GitHub PR IDs must be numeric.`)\n\t\t}\n\n\t\t// Build fields list based on whether we need comments\n\t\tconst baseFields = 'number,title,body,state,url,author,headRefName,baseRefName,files,commits'\n\t\tconst fields = includeComments\n\t\t\t? `${baseFields},comments`\n\t\t\t: baseFields\n\n\t\t// GitHub PR response structure\n\t\tinterface GitHubPRResponse {\n\t\t\tnumber: number\n\t\t\ttitle: string\n\t\t\tbody: string\n\t\t\tstate: string\n\t\t\turl: string\n\t\t\tauthor?: GitHubAuthor\n\t\t\theadRefName: string\n\t\t\tbaseRefName: string\n\t\t\tfiles?: Array<{\n\t\t\t\tpath: string\n\t\t\t\tadditions: number\n\t\t\t\tdeletions: number\n\t\t\t}>\n\t\t\tcommits?: Array<{\n\t\t\t\toid: string\n\t\t\t\tmessageHeadline: string\n\t\t\t\tauthors: Array<{ name: string; email: string }>\n\t\t\t}>\n\t\t\tcomments?: Array<{\n\t\t\t\tid: number\n\t\t\t\tauthor: GitHubAuthor\n\t\t\t\tbody: string\n\t\t\t\tcreatedAt: string\n\t\t\t\tupdatedAt?: string\n\t\t\t\turl: string\n\t\t\t}>\n\t\t}\n\n\t\tconst args = [\n\t\t\t'pr',\n\t\t\t'view',\n\t\t\tString(prNumber),\n\t\t\t'--json',\n\t\t\tfields,\n\t\t]\n\n\t\t// Add --repo flag if repo is provided\n\t\tif (repo) {\n\t\t\targs.push('--repo', repo)\n\t\t}\n\n\t\tconst raw = await executeGhCommand<GitHubPRResponse>(args)\n\n\t\t// Normalize to PRResult with core fields + passthrough\n\t\tconst result: PRResult = {\n\t\t\t// Core fields\n\t\t\tid: String(raw.number),\n\t\t\tnumber: raw.number,\n\t\t\ttitle: raw.title,\n\t\t\tbody: raw.body,\n\t\t\tstate: raw.state,\n\t\t\turl: raw.url,\n\n\t\t\t// Normalized author\n\t\t\tauthor: normalizeAuthor(raw.author),\n\n\t\t\t// PR-specific fields\n\t\t\theadRefName: raw.headRefName,\n\t\t\tbaseRefName: raw.baseRefName,\n\n\t\t\t// Optional files\n\t\t\t...(raw.files && {\n\t\t\t\tfiles: raw.files,\n\t\t\t}),\n\n\t\t\t// Optional commits - normalize author\n\t\t\t...(raw.commits && {\n\t\t\t\tcommits: raw.commits.map(commit => ({\n\t\t\t\t\toid: commit.oid,\n\t\t\t\t\tmessageHeadline: commit.messageHeadline,\n\t\t\t\t\tauthor: commit.authors?.[0]\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\tid: commit.authors[0].email,\n\t\t\t\t\t\t\tdisplayName: commit.authors[0].name,\n\t\t\t\t\t\t\tname: commit.authors[0].name,\n\t\t\t\t\t\t\temail: commit.authors[0].email,\n\t\t\t\t\t\t}\n\t\t\t\t\t\t: null,\n\t\t\t\t})),\n\t\t\t}),\n\t\t}\n\n\t\t// Handle comments with normalized authors\n\t\t// Use extractNumericIdFromUrl to get REST API-compatible numeric IDs from comment URLs\n\t\tif (raw.comments !== undefined) {\n\t\t\tresult.comments = raw.comments.map(comment => ({\n\t\t\t\tid: extractNumericIdFromUrl(comment.url),\n\t\t\t\tbody: comment.body,\n\t\t\t\tcreatedAt: comment.createdAt,\n\t\t\t\tauthor: normalizeAuthor(comment.author),\n\t\t\t\t...(comment.updatedAt && { updatedAt: comment.updatedAt }),\n\t\t\t}))\n\t\t}\n\n\t\t// Process authenticated images in body and comments\n\t\tresult.body = await processMarkdownImages(result.body, 'github')\n\t\tif (result.comments) {\n\t\t\tfor (const comment of result.comments) {\n\t\t\t\tcomment.body = await processMarkdownImages(comment.body, 'github')\n\t\t\t}\n\t\t}\n\n\t\treturn result\n\t}\n\n\t/**\n\t * Fetch a specific comment by ID using gh API\n\t * Normalizes author to FlexibleAuthor format\n\t */\n\tasync getComment(input: GetCommentInput): Promise<CommentDetailResult> {\n\t\tconst { commentId, repo } = input\n\t\t// Note: GitHub doesn't need the issue number parameter - comment IDs are globally unique\n\t\t// But we accept it for interface compatibility with other providers\n\n\t\t// Convert string ID to number for GitHub API\n\t\tconst numericCommentId = parseInt(commentId, 10)\n\t\tif (isNaN(numericCommentId)) {\n\t\t\tthrow new Error(`Invalid GitHub comment ID: ${commentId}. GitHub comment IDs must be numeric.`)\n\t\t}\n\n\t\t// GitHub API response structure\n\t\tinterface GitHubCommentResponse {\n\t\t\tid: number\n\t\t\tbody: string\n\t\t\tuser: GitHubAuthor\n\t\t\tcreated_at: string\n\t\t\tupdated_at?: string\n\t\t\thtml_url?: string\n\t\t\treactions?: Record<string, unknown>\n\t\t}\n\n\t\t// Use explicit repo path if provided, otherwise use :owner/:repo placeholder\n\t\tconst apiPath = repo\n\t\t\t? `repos/${repo}/issues/comments/${numericCommentId}`\n\t\t\t: `repos/:owner/:repo/issues/comments/${numericCommentId}`\n\n\t\t// Use gh api to fetch specific comment\n\t\tconst raw = await executeGhCommand<GitHubCommentResponse>([\n\t\t\t'api',\n\t\t\tapiPath,\n\t\t\t'--jq',\n\t\t\t'{id: .id, body: .body, user: .user, created_at: .created_at, updated_at: .updated_at, html_url: .html_url, reactions: .reactions}',\n\t\t])\n\n\t\t// Process authenticated images in comment body\n\t\tconst processedBody = await processMarkdownImages(raw.body, 'github')\n\n\t\t// Normalize to CommentDetailResult\n\t\treturn {\n\t\t\tid: String(raw.id),\n\t\t\tbody: processedBody,\n\t\t\tauthor: normalizeAuthor(raw.user),\n\t\t\tcreated_at: raw.created_at,\n\t\t\t...(raw.updated_at && { updated_at: raw.updated_at }),\n\t\t\t// Passthrough GitHub-specific fields\n\t\t\t...(raw.html_url && { html_url: raw.html_url }),\n\t\t\t...(raw.reactions && { reactions: raw.reactions }),\n\t\t}\n\t}\n\n\t/**\n\t * Create a new comment on an issue or PR\n\t */\n\tasync createComment(input: CreateCommentInput): Promise<CommentResult> {\n\t\tconst { number, body, type } = input\n\n\t\t// Convert string ID to number for GitHub utilities\n\t\tconst numericId = parseInt(number, 10)\n\t\tif (isNaN(numericId)) {\n\t\t\tthrow new Error(`Invalid GitHub ${type} number: ${number}. GitHub IDs must be numeric.`)\n\t\t}\n\n\t\t// Delegate to existing GitHub utilities\n\t\tconst result =\n\t\t\ttype === 'issue'\n\t\t\t\t? await createIssueComment(numericId, body)\n\t\t\t\t: await createPRComment(numericId, body)\n\n\t\t// Convert numeric ID to string for the interface\n\t\treturn {\n\t\t\t...result,\n\t\t\tid: String(result.id),\n\t\t}\n\t}\n\n\t/**\n\t * Update an existing comment\n\t */\n\tasync updateComment(input: UpdateCommentInput): Promise<CommentResult> {\n\t\tconst { commentId, body } = input\n\t\t// Note: GitHub doesn't need the issue number parameter - comment IDs are globally unique\n\t\t// But we accept it for interface compatibility with other providers\n\n\t\t// Convert string ID to number for GitHub utility\n\t\tconst numericCommentId = parseInt(commentId, 10)\n\t\tif (isNaN(numericCommentId)) {\n\t\t\tthrow new Error(`Invalid GitHub comment ID: ${commentId}. GitHub comment IDs must be numeric.`)\n\t\t}\n\n\t\t// Delegate to existing GitHub utility\n\t\tconst result = await updateIssueComment(numericCommentId, body)\n\n\t\t// Convert numeric ID to string for the interface\n\t\treturn {\n\t\t\t...result,\n\t\t\tid: String(result.id),\n\t\t}\n\t}\n\n\t/**\n\t * Create a new issue\n\t */\n\tasync createIssue(input: CreateIssueInput): Promise<CreateIssueResult> {\n\t\tconst { title, body, labels, repo } = input\n\t\t// teamKey is ignored for GitHub\n\n\t\tconst result = await createIssue(title, body, { labels, repo })\n\n\t\t// Ensure number is numeric\n\t\tconst issueNumber = typeof result.number === 'number'\n\t\t\t? result.number\n\t\t\t: parseInt(String(result.number), 10)\n\n\t\treturn {\n\t\t\tid: String(issueNumber),\n\t\t\turl: result.url,\n\t\t\tnumber: issueNumber,\n\t\t}\n\t}\n\n\t/**\n\t * Create a child issue linked to a parent issue\n\t * GitHub requires two-step process: create issue, then link via GraphQL\n\t */\n\tasync createChildIssue(input: CreateChildIssueInput): Promise<CreateIssueResult> {\n\t\tconst { parentId, title, body, labels, repo } = input\n\t\t// teamKey is ignored for GitHub\n\n\t\t// Convert parent identifier to number\n\t\tconst parentNumber = parseInt(parentId, 10)\n\t\tif (isNaN(parentNumber)) {\n\t\t\tthrow new Error(`Invalid GitHub parent issue number: ${parentId}. GitHub issue IDs must be numeric.`)\n\t\t}\n\n\t\t// Step 1: Get parent issue's GraphQL node ID\n\t\tconst parentNodeId = await getIssueNodeId(parentNumber, repo)\n\n\t\t// Step 2: Create the child issue\n\t\tconst childResult = await createIssue(title, body, { labels, repo })\n\t\tconst childNumber = typeof childResult.number === 'number'\n\t\t\t? childResult.number\n\t\t\t: parseInt(String(childResult.number), 10)\n\n\t\t// Step 3: Get child issue's GraphQL node ID\n\t\tconst childNodeId = await getIssueNodeId(childNumber, repo)\n\n\t\t// Step 4: Link child to parent via GraphQL mutation\n\t\tawait addSubIssue(parentNodeId, childNodeId)\n\n\t\treturn {\n\t\t\tid: String(childNumber),\n\t\t\turl: childResult.url,\n\t\t\tnumber: childNumber,\n\t\t}\n\t}\n\n\t/**\n\t * Create a blocking dependency between two issues (A blocks B)\n\t * Uses GitHub's sub-issues API: blocking issue becomes parent, blocked issue becomes sub-issue\n\t */\n\tasync createDependency(input: CreateDependencyInput): Promise<void> {\n\t\tconst { blockingIssue, blockedIssue, repo } = input\n\n\t\t// Convert string IDs to numbers\n\t\tconst blockingNumber = parseInt(blockingIssue, 10)\n\t\tif (isNaN(blockingNumber)) {\n\t\t\tthrow new Error(`Invalid GitHub issue number: ${blockingIssue}. GitHub issue IDs must be numeric.`)\n\t\t}\n\n\t\tconst blockedNumber = parseInt(blockedIssue, 10)\n\t\tif (isNaN(blockedNumber)) {\n\t\t\tthrow new Error(`Invalid GitHub issue number: ${blockedIssue}. GitHub issue IDs must be numeric.`)\n\t\t}\n\n\t\t// Get the database ID of the blocking issue\n\t\t// GitHub API: POST /issues/{blocked_issue_number}/dependencies/blocked_by with body issue_id={blocking_database_id}\n\t\tconst blockingDatabaseId = await getIssueDatabaseId(blockingNumber, repo)\n\n\t\t// Create the dependency: path uses blocked issue number, body uses blocking issue DB ID\n\t\tawait createIssueDependency(blockedNumber, blockingDatabaseId, repo)\n\t}\n\n\t/**\n\t * Get dependencies for an issue\n\t */\n\tasync getDependencies(input: GetDependenciesInput): Promise<DependenciesResult> {\n\t\tconst { number, direction, repo } = input\n\n\t\tconst issueNumber = parseInt(number, 10)\n\t\tif (isNaN(issueNumber)) {\n\t\t\tthrow new Error(`Invalid GitHub issue number: ${number}. GitHub issue IDs must be numeric.`)\n\t\t}\n\n\t\tconst result: DependenciesResult = {\n\t\t\tblocking: [],\n\t\t\tblockedBy: [],\n\t\t}\n\n\t\t// Fetch dependencies based on direction\n\t\tif (direction === 'blocking' || direction === 'both') {\n\t\t\tresult.blocking = await getIssueDependencies(issueNumber, 'blocking', repo)\n\t\t}\n\n\t\tif (direction === 'blocked_by' || direction === 'both') {\n\t\t\tresult.blockedBy = await getIssueDependencies(issueNumber, 'blocked_by', repo)\n\t\t}\n\n\t\treturn result\n\t}\n\n\t/**\n\t * Remove a blocking dependency between two issues (A blocks B)\n\t * Uses GitHub's sub-issues API: blocking issue is parent, blocked issue is sub-issue\n\t */\n\tasync removeDependency(input: RemoveDependencyInput): Promise<void> {\n\t\tconst { blockingIssue, blockedIssue, repo } = input\n\n\t\t// Convert string IDs to numbers\n\t\tconst blockingNumber = parseInt(blockingIssue, 10)\n\t\tif (isNaN(blockingNumber)) {\n\t\t\tthrow new Error(`Invalid GitHub issue number: ${blockingIssue}. GitHub issue IDs must be numeric.`)\n\t\t}\n\n\t\tconst blockedNumber = parseInt(blockedIssue, 10)\n\t\tif (isNaN(blockedNumber)) {\n\t\t\tthrow new Error(`Invalid GitHub issue number: ${blockedIssue}. GitHub issue IDs must be numeric.`)\n\t\t}\n\n\t\t// Get the database ID of the blocking issue\n\t\t// GitHub API: DELETE /issues/{blocked_issue_number}/dependencies/blocked_by with body issue_id={blocking_database_id}\n\t\tconst blockingDatabaseId = await getIssueDatabaseId(blockingNumber, repo)\n\n\t\t// Remove the dependency: path uses blocked issue number, body uses blocking issue DB ID\n\t\tawait removeIssueDependency(blockedNumber, blockingDatabaseId, repo)\n\t}\n\n\t/**\n\t * Get child issues (sub-issues) of a parent issue\n\t */\n\tasync getChildIssues(input: GetChildIssuesInput): Promise<ChildIssueResult[]> {\n\t\tconst { number, repo } = input\n\n\t\tconst issueNumber = parseInt(number, 10)\n\t\tif (isNaN(issueNumber)) {\n\t\t\tthrow new Error(`Invalid GitHub issue number: ${number}. GitHub issue IDs must be numeric.`)\n\t\t}\n\n\t\treturn await getSubIssues(issueNumber, repo)\n\t}\n}\n","import { appendFileSync } from 'node:fs'\nimport { join, dirname, basename, extname } from 'node:path'\n\n/**\n * Utility class for converting HTML details/summary format to Linear's collapsible format\n *\n * Converts:\n * <details>\n * <summary>Header</summary>\n * CONTENT\n * </details>\n *\n * Into Linear format:\n * +++ Header\n *\n * CONTENT\n *\n * +++\n */\nexport class LinearMarkupConverter {\n\t/**\n\t * Convert HTML details/summary blocks to Linear's collapsible format\n\t * Handles nested details blocks recursively\n\t *\n\t * @param text - Text containing HTML details/summary blocks\n\t * @returns Text with details/summary converted to Linear format\n\t */\n\tstatic convertDetailsToLinear(text: string): string {\n\t\tif (!text) {\n\t\t\treturn text\n\t\t}\n\n\t\t// Process from innermost to outermost to handle nesting correctly\n\t\t// Keep converting until no more details blocks are found\n\t\tlet previousText = ''\n\t\tlet currentText = text\n\n\t\twhile (previousText !== currentText) {\n\t\t\tpreviousText = currentText\n\t\t\tcurrentText = this.convertSinglePass(currentText)\n\t\t}\n\n\t\treturn currentText\n\t}\n\n\t/**\n\t * Perform a single pass of details block conversion\n\t * Converts the innermost details blocks first\n\t */\n\tprivate static convertSinglePass(text: string): string {\n\t\t// Match <details> blocks with optional attributes on the details tag\n\t\t// Supports multiline content between tags\n\t\tconst detailsRegex = /<details[^>]*>\\s*<summary[^>]*>(.*?)<\\/summary>\\s*(.*?)\\s*<\\/details>/gis\n\n\t\treturn text.replace(detailsRegex, (_match, summary, content) => {\n\t\t\t// Clean up the summary - trim whitespace and decode HTML entities\n\t\t\tconst cleanSummary = this.cleanText(summary)\n\n\t\t\t// Clean up the content - preserve internal structure but normalize outer whitespace\n\t\t\t// Note: Don't recursively convert here - the while loop handles that\n\t\t\tconst cleanContent = this.cleanContent(content)\n\n\t\t\t// Build Linear collapsible format\n\t\t\t// Always include blank lines around content for readability\n\t\t\tif (cleanContent) {\n\t\t\t\treturn `+++ ${cleanSummary}\\n\\n${cleanContent}\\n\\n+++`\n\t\t\t} else {\n\t\t\t\t// Empty content - use minimal format\n\t\t\t\treturn `+++ ${cleanSummary}\\n\\n+++`\n\t\t\t}\n\t\t})\n\t}\n\n\t/**\n\t * Clean text by trimming whitespace and decoding common HTML entities\n\t */\n\tprivate static cleanText(text: string): string {\n\t\treturn text\n\t\t\t.trim()\n\t\t\t.replace(/</g, '<')\n\t\t\t.replace(/>/g, '>')\n\t\t\t.replace(/&/g, '&')\n\t\t\t.replace(/"/g, '\"')\n\t\t\t.replace(/'/g, \"'\")\n\t}\n\n\t/**\n\t * Clean content while preserving internal structure\n\t * - Removes leading/trailing whitespace\n\t * - Normalizes internal blank lines (max 2 consecutive newlines)\n\t * - Preserves code blocks and other formatting\n\t */\n\tprivate static cleanContent(content: string): string {\n\t\tif (!content) {\n\t\t\treturn ''\n\t\t}\n\n\t\t// Trim outer whitespace\n\t\tlet cleaned = content.trim()\n\n\t\t// Normalize excessive blank lines (3+ newlines -> 2 newlines)\n\t\tcleaned = cleaned.replace(/\\n{3,}/g, '\\n\\n')\n\n\t\treturn cleaned\n\t}\n\n\t/**\n\t * Check if text contains HTML details/summary blocks\n\t * Useful for conditional conversion\n\t */\n\tstatic hasDetailsBlocks(text: string): boolean {\n\t\tif (!text) {\n\t\t\treturn false\n\t\t}\n\n\t\tconst detailsRegex = /<details[^>]*>.*?<summary[^>]*>.*?<\\/summary>.*?<\\/details>/is\n\t\treturn detailsRegex.test(text)\n\t}\n\n\t/**\n\t * Remove wrapper tags from code sample details blocks\n\t * Identifies details blocks where summary contains \"X lines\" pattern\n\t * and removes the details/summary tags while preserving the content\n\t *\n\t * @param text - Text containing potential code sample details blocks\n\t * @returns Text with code sample wrappers removed\n\t */\n\tstatic removeCodeSampleWrappers(text: string): string {\n\t\tif (!text) {\n\t\t\treturn text\n\t\t}\n\n\t\t// Match details blocks where summary contains \"X lines\" (e.g., \"45 lines\", \"120 lines\")\n\t\t// Pattern: <details><summary>...N lines...</summary>CONTENT</details>\n\t\t// Use [^<]* to match summary content without allowing nested tags to interfere\n\t\t// Then use [\\s\\S]*? for the content to allow any characters including newlines\n\t\tconst codeSampleRegex = /<details[^>]*>\\s*<summary[^>]*>([^<]*\\d+\\s+lines[^<]*)<\\/summary>\\s*([\\s\\S]*?)<\\/details>/gi\n\n\t\treturn text.replace(codeSampleRegex, (_match, _summary, content) => {\n\t\t\t// Return just the content, without any wrapper tags\n\t\t\t// Preserve the content exactly as-is\n\t\t\treturn content.trim()\n\t\t})\n\t}\n\n\t/**\n\t * Convert text for Linear - applies all necessary conversions\n\t * Currently only converts details/summary blocks, but can be extended\n\t * for other HTML to Linear markdown conversions\n\t */\n\tstatic convertToLinear(text: string): string {\n\t\tif (!text) {\n\t\t\treturn text\n\t\t}\n\n\t\t// Log input if logging is enabled\n\t\tthis.logConversion('INPUT', text)\n\n\t\t// Apply all conversions\n\t\tlet converted = text\n\n\t\t// First, remove code sample wrappers (details blocks with \"X lines\" pattern)\n\t\t// This prevents them from being converted to Linear's +++ format\n\t\tconverted = this.removeCodeSampleWrappers(converted)\n\n\t\t// Then convert remaining details/summary blocks to Linear format\n\t\tconverted = this.convertDetailsToLinear(converted)\n\n\t\t// Log output if logging is enabled\n\t\tthis.logConversion('OUTPUT', converted)\n\n\t\treturn converted\n\t}\n\n\t/**\n\t * Log conversion input/output if LINEAR_MARKDOWN_LOG_FILE is set\n\t */\n\tprivate static logConversion(label: string, content: string): void {\n\t\tconst logFilePath = process.env.LINEAR_MARKDOWN_LOG_FILE\n\t\tif (!logFilePath) {\n\t\t\treturn\n\t\t}\n\n\t\ttry {\n\t\t\tconst timestampedPath = this.getTimestampedLogPath(logFilePath)\n\t\t\tconst timestamp = new Date().toISOString()\n\t\t\tconst separator = '================================'\n\n\t\t\tconst logEntry = `${separator}\\n[${timestamp}] CONVERSION ${label}\\n${separator}\\n${label}:\\n${content}\\n\\n`\n\n\t\t\tappendFileSync(timestampedPath, logEntry, 'utf-8')\n\t\t} catch {\n\t\t\t// Silently fail - don't crash if logging fails\n\t\t\t// This is a debug feature and shouldn't break the conversion\n\t\t}\n\t}\n\n\t/**\n\t * Generate timestamped log file path\n\t * Example: debug.log -> debug-20231202-161234.log\n\t */\n\tprivate static getTimestampedLogPath(logFilePath: string): string {\n\t\tconst dir = dirname(logFilePath)\n\t\tconst ext = extname(logFilePath)\n\t\tconst base = basename(logFilePath, ext)\n\n\t\t// Generate timestamp: YYYYMMDD-HHMMSS\n\t\tconst now = new Date()\n\t\tconst timestamp = [\n\t\t\tnow.getFullYear(),\n\t\t\tString(now.getMonth() + 1).padStart(2, '0'),\n\t\t\tString(now.getDate()).padStart(2, '0'),\n\t\t].join('') + '-' + [\n\t\t\tString(now.getHours()).padStart(2, '0'),\n\t\t\tString(now.getMinutes()).padStart(2, '0'),\n\t\t\tString(now.getSeconds()).padStart(2, '0'),\n\t\t].join('')\n\n\t\treturn join(dir, `${base}-${timestamp}${ext}`)\n\t}\n}\n","/**\n * Linear implementation of Issue Management Provider\n * Uses @linear/sdk for all operations\n */\n\nimport type {\n\tIssueManagementProvider,\n\tGetIssueInput,\n\tGetPRInput,\n\tGetCommentInput,\n\tCreateCommentInput,\n\tUpdateCommentInput,\n\tCreateIssueInput,\n\tCreateChildIssueInput,\n\tCreateDependencyInput,\n\tGetDependenciesInput,\n\tRemoveDependencyInput,\n\tGetChildIssuesInput,\n\tCreateIssueResult,\n\tIssueResult,\n\tPRResult,\n\tCommentDetailResult,\n\tCommentResult,\n\tDependenciesResult,\n\tChildIssueResult,\n} from './types.js'\nimport {\n\tfetchLinearIssue,\n\tcreateLinearComment,\n\tgetLinearComment,\n\tupdateLinearComment,\n\tfetchLinearIssueComments,\n\tcreateLinearIssue,\n\tcreateLinearChildIssue,\n\tcreateLinearIssueRelation,\n\tgetLinearIssueDependencies,\n\tfindLinearIssueRelation,\n\tdeleteLinearIssueRelation,\n\tgetLinearChildIssues,\n} from '../utils/linear.js'\nimport { LinearMarkupConverter } from '../utils/linear-markup-converter.js'\nimport { processMarkdownImages } from '../utils/image-processor.js'\n\n/**\n * Linear-specific implementation of IssueManagementProvider\n */\nexport class LinearIssueManagementProvider implements IssueManagementProvider {\n\treadonly providerName = 'linear'\n\treadonly issuePrefix = ''\n\n\t/**\n\t * Cached team key extracted from issue identifiers (e.g., \"ENG-123\" -> \"ENG\")\n\t * Used as fallback when teamKey is not explicitly provided to createIssue()\n\t */\n\tprivate cachedTeamKey: string | undefined = undefined\n\n\t/**\n\t * Fetch issue details using Linear SDK\n\t */\n\tasync getIssue(input: GetIssueInput): Promise<IssueResult> {\n\t\tconst { number, includeComments = true } = input\n\n\t\t// Extract and cache team key from identifier (e.g., \"ENG-123\" -> \"ENG\")\n\t\t// This enables createIssue() to use the team key as a fallback\n\t\tconst match = number.match(/^([A-Z]{2,})-\\d+$/i)\n\t\tif (match?.[1]) {\n\t\t\tthis.cachedTeamKey = match[1].toUpperCase()\n\t\t}\n\n\t\t// Fetch issue - Linear uses alphanumeric identifiers like \"ENG-123\"\n\t\tconst raw = await fetchLinearIssue(number)\n\n\t\t// Map Linear state name to open/closed\n\t\tconst state = raw.state && (raw.state.toLowerCase().includes('done') || raw.state.toLowerCase().includes('completed') || raw.state.toLowerCase().includes('canceled'))\n\t\t\t? 'closed'\n\t\t\t: 'open'\n\n\t\t// Build result\n\t\tconst result: IssueResult = {\n\t\t\tid: raw.identifier,\n\t\t\ttitle: raw.title,\n\t\t\tbody: raw.description ?? '',\n\t\t\tstate,\n\t\t\turl: raw.url,\n\t\t\tprovider: 'linear',\n\t\t\tauthor: null, // Linear SDK doesn't return author in basic fetch\n\n\t\t\t// Linear-specific fields\n\t\t\tlinearState: raw.state,\n\t\t\tcreatedAt: raw.createdAt,\n\t\t\tupdatedAt: raw.updatedAt,\n\t\t}\n\n\t\t// Fetch comments if requested\n\t\tif (includeComments) {\n\t\t\ttry {\n\t\t\t\tconst comments = await this.fetchIssueComments(number)\n\t\t\t\tif (comments) {\n\t\t\t\t\tresult.comments = comments\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// If comments fail, continue without them\n\t\t\t}\n\t\t}\n\n\t\t// Process images in body and comments to make them accessible\n\t\tresult.body = await processMarkdownImages(result.body, 'linear')\n\t\tif (result.comments) {\n\t\t\tfor (const comment of result.comments) {\n\t\t\t\tcomment.body = await processMarkdownImages(comment.body, 'linear')\n\t\t\t}\n\t\t}\n\n\t\treturn result\n\t}\n\n\t/**\n\t * Fetch pull request details\n\t * Linear does not support PRs - this throws an error directing to use GitHub\n\t */\n\tasync getPR(_input: GetPRInput): Promise<PRResult> {\n\t\tthrow new Error('Linear does not support pull requests. PRs exist only on GitHub. Use the GitHub provider for PR operations.')\n\t}\n\n\t/**\n\t * Fetch comments for an issue\n\t */\n\tprivate async fetchIssueComments(identifier: string): Promise<IssueResult['comments']> {\n\t\ttry {\n\t\t\tconst comments = await fetchLinearIssueComments(identifier)\n\n\t\t\treturn comments.map(comment => ({\n\t\t\t\tid: comment.id,\n\t\t\t\tbody: comment.body,\n\t\t\t\tcreatedAt: comment.createdAt,\n\t\t\t\tauthor: null, // Linear SDK doesn't return comment author info in basic fetch\n\t\t\t\t...(comment.updatedAt && { updatedAt: comment.updatedAt }),\n\t\t\t}))\n\t\t} catch {\n\t\t\treturn []\n\t\t}\n\t}\n\n\t/**\n\t * Fetch a specific comment by ID\n\t */\n\tasync getComment(input: GetCommentInput): Promise<CommentDetailResult> {\n\t\tconst { commentId } = input\n\n\t\tconst raw = await getLinearComment(commentId)\n\n\t\t// Process images to make them accessible\n\t\tconst processedBody = await processMarkdownImages(raw.body, 'linear')\n\n\t\treturn {\n\t\t\tid: raw.id,\n\t\t\tbody: processedBody,\n\t\t\tauthor: null, // Linear SDK doesn't return comment author info in basic fetch\n\t\t\tcreated_at: raw.createdAt,\n\t\t}\n\t}\n\n\t/**\n\t * Create a new comment on an issue\n\t */\n\tasync createComment(input: CreateCommentInput): Promise<CommentResult> {\n\t\tconst { number, body } = input\n\t\t// Note: Linear doesn't distinguish between issue and PR comments\n\t\t// (Linear doesn't have PRs - that's GitHub-specific)\n\n\t\t// Convert HTML details/summary blocks to Linear's collapsible format\n\t\tconst convertedBody = LinearMarkupConverter.convertToLinear(body)\n\n\t\tconst result = await createLinearComment(number, convertedBody)\n\n\t\treturn {\n\t\t\tid: result.id,\n\t\t\turl: result.url,\n\t\t\tcreated_at: result.createdAt,\n\t\t}\n\t}\n\n\t/**\n\t * Update an existing comment\n\t */\n\tasync updateComment(input: UpdateCommentInput): Promise<CommentResult> {\n\t\tconst { commentId, body } = input\n\n\t\t// Convert HTML details/summary blocks to Linear's collapsible format\n\t\tconst convertedBody = LinearMarkupConverter.convertToLinear(body)\n\n\t\tconst result = await updateLinearComment(commentId, convertedBody)\n\n\t\treturn {\n\t\t\tid: result.id,\n\t\t\turl: result.url,\n\t\t\tupdated_at: result.updatedAt,\n\t\t}\n\t}\n\n\t/**\n\t * Create a new issue\n\t */\n\tasync createIssue(input: CreateIssueInput): Promise<CreateIssueResult> {\n\t\tconst { title, body, labels, teamKey } = input\n\n\t\t// Fallback chain: explicit param > settings (via env) > cached key from getIssue()\n\t\tconst effectiveTeamKey = teamKey ?? process.env.LINEAR_TEAM_KEY ?? this.cachedTeamKey\n\n\t\tif (!effectiveTeamKey) {\n\t\t\tthrow 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.')\n\t\t}\n\n\t\tconst result = await createLinearIssue(title, body, effectiveTeamKey, labels)\n\n\t\treturn {\n\t\t\tid: result.identifier,\n\t\t\turl: result.url,\n\t\t}\n\t}\n\n\t/**\n\t * Create a child issue linked to a parent issue\n\t * Linear supports atomic creation with parentId field\n\t */\n\tasync createChildIssue(input: CreateChildIssueInput): Promise<CreateIssueResult> {\n\t\tconst { parentId, title, body, labels, teamKey } = input\n\n\t\t// Fetch parent issue to get UUID (parentId in input is identifier like \"ENG-123\")\n\t\tconst parentIssue = await fetchLinearIssue(parentId)\n\n\t\t// Extract team key from parent identifier if not provided\n\t\tconst match = parentId.match(/^([A-Z]{2,})-\\d+$/i)\n\t\tconst effectiveTeamKey = teamKey ?? match?.[1]?.toUpperCase() ?? process.env.LINEAR_TEAM_KEY ?? this.cachedTeamKey\n\n\t\tif (!effectiveTeamKey) {\n\t\t\tthrow new Error('teamKey is required for Linear child issue creation. Provide teamKey parameter or use a parent identifier with team prefix.')\n\t\t}\n\n\t\t// Create child issue with parent's UUID\n\t\tconst result = await createLinearChildIssue(\n\t\t\ttitle,\n\t\t\tbody,\n\t\t\teffectiveTeamKey,\n\t\t\tparentIssue.id, // UUID, not identifier\n\t\t\tlabels\n\t\t)\n\n\t\treturn {\n\t\t\tid: result.identifier,\n\t\t\turl: result.url,\n\t\t}\n\t}\n\n\t/**\n\t * Create a blocking dependency between two issues\n\t */\n\tasync createDependency(input: CreateDependencyInput): Promise<void> {\n\t\tconst { blockingIssue, blockedIssue } = input\n\n\t\t// Fetch both issues to get their UUIDs\n\t\tconst [blockingIssueData, blockedIssueData] = await Promise.all([\n\t\t\tfetchLinearIssue(blockingIssue),\n\t\t\tfetchLinearIssue(blockedIssue),\n\t\t])\n\n\t\t// Create the blocking relation (blockingIssue blocks blockedIssue)\n\t\tawait createLinearIssueRelation(blockingIssueData.id, blockedIssueData.id)\n\t}\n\n\t/**\n\t * Get dependencies for an issue\n\t */\n\tasync getDependencies(input: GetDependenciesInput): Promise<DependenciesResult> {\n\t\tconst { number, direction } = input\n\n\t\treturn await getLinearIssueDependencies(number, direction)\n\t}\n\n\t/**\n\t * Remove a blocking dependency between two issues\n\t */\n\tasync removeDependency(input: RemoveDependencyInput): Promise<void> {\n\t\tconst { blockingIssue, blockedIssue } = input\n\n\t\t// Find the relation ID\n\t\tconst relationId = await findLinearIssueRelation(blockingIssue, blockedIssue)\n\n\t\tif (!relationId) {\n\t\t\tthrow new Error(`No blocking dependency found from ${blockingIssue} to ${blockedIssue}`)\n\t\t}\n\n\t\t// Delete the relation\n\t\tawait deleteLinearIssueRelation(relationId)\n\t}\n\n\t/**\n\t * Get child issues of a parent issue\n\t */\n\tasync getChildIssues(input: GetChildIssuesInput): Promise<ChildIssueResult[]> {\n\t\tconst { number } = input\n\t\t// repo is ignored for Linear\n\t\treturn await getLinearChildIssues(number)\n\t}\n}\n","/**\n * Factory for creating issue management providers\n */\n\nimport type { IssueManagementProvider, IssueProvider } from './types.js'\nimport { GitHubIssueManagementProvider } from './GitHubIssueManagementProvider.js'\nimport { LinearIssueManagementProvider } from './LinearIssueManagementProvider.js'\n\n/**\n * Factory class for creating issue management providers\n */\nexport class IssueManagementProviderFactory {\n\t/**\n\t * Create an issue management provider based on the provider type\n\t */\n\tstatic create(provider: IssueProvider): IssueManagementProvider {\n\t\tswitch (provider) {\n\t\t\tcase 'github':\n\t\t\t\treturn new GitHubIssueManagementProvider()\n\t\t\tcase 'linear':\n\t\t\t\treturn new LinearIssueManagementProvider()\n\t\t\tdefault:\n\t\t\t\tthrow new Error(`Unsupported issue management provider: ${provider}`)\n\t\t}\n\t}\n}\n"],"mappings":"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","names":["join","extname"]}
|