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