@iloom/cli 0.10.0 → 0.10.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 +2 -2
- package/dist/{BranchNamingService-ECJHBB67.js → BranchNamingService-25KSZAEM.js} +2 -2
- package/dist/ClaudeContextManager-66GR4BGM.js +14 -0
- package/dist/ClaudeService-7KM5NA5Z.js +13 -0
- package/dist/{LoomLauncher-L64HHS3T.js → LoomLauncher-TDLZSYG2.js} +6 -6
- package/dist/{PromptTemplateManager-DULSVRRE.js → PromptTemplateManager-YOE2SIPG.js} +2 -2
- package/dist/README.md +2 -2
- package/dist/{SettingsManager-BQDQA3FK.js → SettingsManager-FNKCOZMQ.js} +2 -2
- package/dist/{build-5GO3XW26.js → build-VHGEMXBA.js} +6 -6
- package/dist/chunk-4E7LCFUG.js +24 -0
- package/dist/chunk-4E7LCFUG.js.map +1 -0
- package/dist/{chunk-MNHZB4Z2.js → chunk-4FGEGQW4.js} +3 -3
- package/dist/{chunk-LXLMMXXY.js → chunk-5FJWO4IT.js} +17 -12
- package/dist/chunk-5FJWO4IT.js.map +1 -0
- package/dist/{chunk-ZHPNZC75.js → chunk-5RPBYK5Q.js} +26 -21
- package/dist/chunk-5RPBYK5Q.js.map +1 -0
- package/dist/{chunk-WY4QBK43.js → chunk-63QWFWH3.js} +2 -2
- package/dist/{chunk-YYAKPQBT.js → chunk-7VHJNVLF.js} +19 -9
- package/dist/chunk-7VHJNVLF.js.map +1 -0
- package/dist/{chunk-SF2P22EE.js → chunk-C6HNNJIV.js} +2 -2
- package/dist/{chunk-5MWV33NN.js → chunk-CVCTIDDK.js} +2 -2
- package/dist/{chunk-RYWFS37M.js → chunk-E6KOWMKA.js} +2 -2
- package/dist/{chunk-6EU6TCF6.js → chunk-EVPZFV3K.js} +5 -5
- package/dist/{chunk-ZEWU5PZK.js → chunk-G5V75JD5.js} +2 -2
- package/dist/chunk-GRISNU6G.js +651 -0
- package/dist/chunk-GRISNU6G.js.map +1 -0
- package/dist/{chunk-VGGST52X.js → chunk-I5T677EA.js} +2 -2
- package/dist/{chunk-VECNX6VX.js → chunk-KIK2ZFAL.js} +2 -2
- package/dist/{chunk-FB47TIJG.js → chunk-KKV5WH5M.js} +4 -23
- package/dist/chunk-KKV5WH5M.js.map +1 -0
- package/dist/{chunk-ZW2LKWWE.js → chunk-KVHIAWVT.js} +3 -3
- package/dist/{chunk-3D7WQM7I.js → chunk-LLHXQS3C.js} +2 -2
- package/dist/{chunk-Y4YZTHZE.js → chunk-LUKXJSRI.js} +2 -2
- package/dist/{ignite-CGOV3TD4.js → chunk-OTGH2HRS.js} +105 -71
- package/dist/chunk-OTGH2HRS.js.map +1 -0
- package/dist/{chunk-J5S7DFYC.js → chunk-QVLPWNE3.js} +2 -2
- package/dist/chunk-RJ3VBUFK.js +781 -0
- package/dist/chunk-RJ3VBUFK.js.map +1 -0
- package/dist/{chunk-SN3SQCFK.js → chunk-S7PZA6IV.js} +4 -4
- package/dist/{chunk-UWGVCXRF.js → chunk-SKSYYBCU.js} +23 -1
- package/dist/chunk-SKSYYBCU.js.map +1 -0
- package/dist/{chunk-JO2LZ6EQ.js → chunk-SWSJWA2S.js} +2 -2
- package/dist/{chunk-ONQYPICO.js → chunk-UR5DGNUO.js} +60 -6
- package/dist/chunk-UR5DGNUO.js.map +1 -0
- package/dist/{chunk-4WJNIR5O.js → chunk-UUEW5KWB.js} +1 -1
- package/dist/chunk-UUEW5KWB.js.map +1 -0
- package/dist/{chunk-NRSWLOAZ.js → chunk-WXIM2WS7.js} +4 -4
- package/dist/{chunk-UD3WJDIV.js → chunk-ZNMPGMHY.js} +11 -774
- package/dist/chunk-ZNMPGMHY.js.map +1 -0
- package/dist/{claude-P3NQR6IJ.js → claude-7GGEWVEM.js} +2 -2
- package/dist/{cleanup-6UCPVMFG.js → cleanup-6PVAC4NI.js} +19 -17
- package/dist/{cleanup-6UCPVMFG.js.map → cleanup-6PVAC4NI.js.map} +1 -1
- package/dist/cli.js +154 -614
- package/dist/cli.js.map +1 -1
- package/dist/{commit-L3EPY5QG.js → commit-FZR5XDQG.js} +12 -10
- package/dist/commit-FZR5XDQG.js.map +1 -0
- package/dist/{compile-ZS4HYRX5.js → compile-7ALJHZ4N.js} +6 -6
- package/dist/{contribute-ORDDQGSL.js → contribute-5GKLK3BQ.js} +3 -3
- package/dist/{dev-server-FYZ2AQIH.js → dev-server-7SMIB7OF.js} +8 -8
- package/dist/{feedback-TMBXSCM5.js → feedback-G2GJFN2F.js} +10 -8
- package/dist/{feedback-TMBXSCM5.js.map → feedback-G2GJFN2F.js.map} +1 -1
- package/dist/{git-ET64COO3.js → git-GTLKAZRJ.js} +3 -3
- package/dist/ignite-H2O5Y5A2.js +34 -0
- package/dist/ignite-H2O5Y5A2.js.map +1 -0
- package/dist/index.d.ts +113 -18
- package/dist/index.js +177 -12
- package/dist/index.js.map +1 -1
- package/dist/{init-GFQ5W7GK.js → init-32YOKXRL.js} +8 -8
- package/dist/{issues-T4ZZSPEG.js → issues-4UUAQ5K6.js} +3 -3
- package/dist/{lint-6TQXDZ3T.js → lint-AAN2NZWG.js} +6 -6
- package/dist/mcp/harness-server.js +140 -0
- package/dist/mcp/harness-server.js.map +1 -0
- package/dist/mcp/issue-management-server.js +140 -18
- package/dist/mcp/issue-management-server.js.map +1 -1
- package/dist/{open-5QZGXQRF.js → open-FXWW3VI4.js} +8 -8
- package/dist/{plan-U7ZQWLFY.js → plan-RQ5FPIGF.js} +338 -36
- package/dist/plan-RQ5FPIGF.js.map +1 -0
- package/dist/prompts/CLAUDE.md +2 -2
- package/dist/prompts/init-prompt.txt +102 -27
- package/dist/prompts/issue-prompt.txt +46 -0
- package/dist/prompts/plan-prompt.txt +59 -19
- package/dist/prompts/swarm-orchestrator-prompt.txt +107 -80
- package/dist/{rebase-DWIB77KV.js → rebase-6NVLX5V7.js} +17 -8
- package/dist/rebase-6NVLX5V7.js.map +1 -0
- package/dist/{recap-MX63HAKV.js → recap-OMBOKJST.js} +6 -6
- package/dist/{run-O3TFNQFC.js → run-BBXLRIZB.js} +8 -8
- package/dist/schema/settings.schema.json +36 -2
- package/dist/{shell-G6VC2CYR.js → shell-RF7LTND5.js} +5 -5
- package/dist/{summary-FWHAX55O.js → summary-WTQZ7XG2.js} +9 -9
- package/dist/{test-F7JNJZYP.js → test-SGO6I5Z7.js} +6 -6
- package/dist/{test-git-BTAOIUE2.js → test-git-XM4TM65W.js} +3 -3
- package/dist/{test-jira-CHYNV33F.js → test-jira-LDTOYFSD.js} +3 -3
- package/dist/{test-prefix-Q6TFSU6F.js → test-prefix-GBO37XCN.js} +3 -3
- package/dist/{test-webserver-EONCG7E7.js → test-webserver-NZ3JTVLL.js} +5 -5
- package/dist/{vscode-VA5X4P25.js → vscode-6XUGHJKL.js} +5 -5
- package/package.json +1 -1
- package/dist/ClaudeContextManager-QXX6ZFST.js +0 -14
- package/dist/ClaudeService-NJNK2SUH.js +0 -13
- package/dist/chunk-4WJNIR5O.js.map +0 -1
- package/dist/chunk-FB47TIJG.js.map +0 -1
- package/dist/chunk-LXLMMXXY.js.map +0 -1
- package/dist/chunk-ONQYPICO.js.map +0 -1
- package/dist/chunk-UD3WJDIV.js.map +0 -1
- package/dist/chunk-UVD4CZKS.js +0 -101
- package/dist/chunk-UVD4CZKS.js.map +0 -1
- package/dist/chunk-UWGVCXRF.js.map +0 -1
- package/dist/chunk-YYAKPQBT.js.map +0 -1
- package/dist/chunk-ZHPNZC75.js.map +0 -1
- package/dist/commit-L3EPY5QG.js.map +0 -1
- package/dist/ignite-CGOV3TD4.js.map +0 -1
- package/dist/plan-U7ZQWLFY.js.map +0 -1
- package/dist/rebase-DWIB77KV.js.map +0 -1
- /package/dist/{BranchNamingService-ECJHBB67.js.map → BranchNamingService-25KSZAEM.js.map} +0 -0
- /package/dist/{ClaudeContextManager-QXX6ZFST.js.map → ClaudeContextManager-66GR4BGM.js.map} +0 -0
- /package/dist/{ClaudeService-NJNK2SUH.js.map → ClaudeService-7KM5NA5Z.js.map} +0 -0
- /package/dist/{LoomLauncher-L64HHS3T.js.map → LoomLauncher-TDLZSYG2.js.map} +0 -0
- /package/dist/{PromptTemplateManager-DULSVRRE.js.map → PromptTemplateManager-YOE2SIPG.js.map} +0 -0
- /package/dist/{SettingsManager-BQDQA3FK.js.map → SettingsManager-FNKCOZMQ.js.map} +0 -0
- /package/dist/{build-5GO3XW26.js.map → build-VHGEMXBA.js.map} +0 -0
- /package/dist/{chunk-MNHZB4Z2.js.map → chunk-4FGEGQW4.js.map} +0 -0
- /package/dist/{chunk-WY4QBK43.js.map → chunk-63QWFWH3.js.map} +0 -0
- /package/dist/{chunk-SF2P22EE.js.map → chunk-C6HNNJIV.js.map} +0 -0
- /package/dist/{chunk-5MWV33NN.js.map → chunk-CVCTIDDK.js.map} +0 -0
- /package/dist/{chunk-RYWFS37M.js.map → chunk-E6KOWMKA.js.map} +0 -0
- /package/dist/{chunk-6EU6TCF6.js.map → chunk-EVPZFV3K.js.map} +0 -0
- /package/dist/{chunk-ZEWU5PZK.js.map → chunk-G5V75JD5.js.map} +0 -0
- /package/dist/{chunk-VGGST52X.js.map → chunk-I5T677EA.js.map} +0 -0
- /package/dist/{chunk-VECNX6VX.js.map → chunk-KIK2ZFAL.js.map} +0 -0
- /package/dist/{chunk-ZW2LKWWE.js.map → chunk-KVHIAWVT.js.map} +0 -0
- /package/dist/{chunk-3D7WQM7I.js.map → chunk-LLHXQS3C.js.map} +0 -0
- /package/dist/{chunk-Y4YZTHZE.js.map → chunk-LUKXJSRI.js.map} +0 -0
- /package/dist/{chunk-J5S7DFYC.js.map → chunk-QVLPWNE3.js.map} +0 -0
- /package/dist/{chunk-SN3SQCFK.js.map → chunk-S7PZA6IV.js.map} +0 -0
- /package/dist/{chunk-JO2LZ6EQ.js.map → chunk-SWSJWA2S.js.map} +0 -0
- /package/dist/{chunk-NRSWLOAZ.js.map → chunk-WXIM2WS7.js.map} +0 -0
- /package/dist/{claude-P3NQR6IJ.js.map → claude-7GGEWVEM.js.map} +0 -0
- /package/dist/{compile-ZS4HYRX5.js.map → compile-7ALJHZ4N.js.map} +0 -0
- /package/dist/{contribute-ORDDQGSL.js.map → contribute-5GKLK3BQ.js.map} +0 -0
- /package/dist/{dev-server-FYZ2AQIH.js.map → dev-server-7SMIB7OF.js.map} +0 -0
- /package/dist/{git-ET64COO3.js.map → git-GTLKAZRJ.js.map} +0 -0
- /package/dist/{init-GFQ5W7GK.js.map → init-32YOKXRL.js.map} +0 -0
- /package/dist/{issues-T4ZZSPEG.js.map → issues-4UUAQ5K6.js.map} +0 -0
- /package/dist/{lint-6TQXDZ3T.js.map → lint-AAN2NZWG.js.map} +0 -0
- /package/dist/{open-5QZGXQRF.js.map → open-FXWW3VI4.js.map} +0 -0
- /package/dist/{recap-MX63HAKV.js.map → recap-OMBOKJST.js.map} +0 -0
- /package/dist/{run-O3TFNQFC.js.map → run-BBXLRIZB.js.map} +0 -0
- /package/dist/{shell-G6VC2CYR.js.map → shell-RF7LTND5.js.map} +0 -0
- /package/dist/{summary-FWHAX55O.js.map → summary-WTQZ7XG2.js.map} +0 -0
- /package/dist/{test-F7JNJZYP.js.map → test-SGO6I5Z7.js.map} +0 -0
- /package/dist/{test-git-BTAOIUE2.js.map → test-git-XM4TM65W.js.map} +0 -0
- /package/dist/{test-jira-CHYNV33F.js.map → test-jira-LDTOYFSD.js.map} +0 -0
- /package/dist/{test-prefix-Q6TFSU6F.js.map → test-prefix-GBO37XCN.js.map} +0 -0
- /package/dist/{test-webserver-EONCG7E7.js.map → test-webserver-NZ3JTVLL.js.map} +0 -0
- /package/dist/{vscode-VA5X4P25.js.map → vscode-6XUGHJKL.js.map} +0 -0
|
@@ -4,32 +4,32 @@ import {
|
|
|
4
4
|
} from "./chunk-BYUMEDDD.js";
|
|
5
5
|
import {
|
|
6
6
|
ShellCompletion
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-KIK2ZFAL.js";
|
|
8
8
|
import {
|
|
9
9
|
TelemetryService
|
|
10
10
|
} from "./chunk-RSYT7MVI.js";
|
|
11
|
-
import "./chunk-
|
|
11
|
+
import "./chunk-I5T677EA.js";
|
|
12
12
|
import {
|
|
13
13
|
FirstRunManager
|
|
14
14
|
} from "./chunk-Q7POFB5Q.js";
|
|
15
15
|
import {
|
|
16
16
|
AgentManager
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-C6HNNJIV.js";
|
|
18
18
|
import {
|
|
19
19
|
parseGitRemotes
|
|
20
20
|
} from "./chunk-FXDYIV3K.js";
|
|
21
21
|
import {
|
|
22
22
|
detectClaudeCli,
|
|
23
23
|
launchClaude
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-UR5DGNUO.js";
|
|
25
25
|
import {
|
|
26
26
|
PromptTemplateManager
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-UUEW5KWB.js";
|
|
28
28
|
import {
|
|
29
29
|
getRepoRoot,
|
|
30
30
|
isFileGitignored
|
|
31
|
-
} from "./chunk-
|
|
32
|
-
import "./chunk-
|
|
31
|
+
} from "./chunk-4FGEGQW4.js";
|
|
32
|
+
import "./chunk-7VHJNVLF.js";
|
|
33
33
|
import "./chunk-KB64WNBZ.js";
|
|
34
34
|
import "./chunk-6MLEBAYZ.js";
|
|
35
35
|
import "./chunk-7JDMYTFZ.js";
|
|
@@ -461,4 +461,4 @@ var InitCommand = class {
|
|
|
461
461
|
export {
|
|
462
462
|
InitCommand
|
|
463
463
|
};
|
|
464
|
-
//# sourceMappingURL=init-
|
|
464
|
+
//# sourceMappingURL=init-32YOKXRL.js.map
|
|
@@ -4,10 +4,10 @@ import {
|
|
|
4
4
|
} from "./chunk-4232AHNQ.js";
|
|
5
5
|
import {
|
|
6
6
|
findMainWorktreePathWithSettings
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-4FGEGQW4.js";
|
|
8
8
|
import {
|
|
9
9
|
SettingsManager
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-7VHJNVLF.js";
|
|
11
11
|
import "./chunk-KB64WNBZ.js";
|
|
12
12
|
import {
|
|
13
13
|
IssueTrackerFactory
|
|
@@ -176,4 +176,4 @@ var IssuesCommand = class {
|
|
|
176
176
|
export {
|
|
177
177
|
IssuesCommand
|
|
178
178
|
};
|
|
179
|
-
//# sourceMappingURL=issues-
|
|
179
|
+
//# sourceMappingURL=issues-4UUAQ5K6.js.map
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ScriptCommandBase
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-WXIM2WS7.js";
|
|
5
5
|
import "./chunk-WWKOVDWC.js";
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-63QWFWH3.js";
|
|
7
|
+
import "./chunk-I5T677EA.js";
|
|
8
8
|
import "./chunk-YQ57ORTV.js";
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
9
|
+
import "./chunk-4FGEGQW4.js";
|
|
10
|
+
import "./chunk-7VHJNVLF.js";
|
|
11
11
|
import "./chunk-KB64WNBZ.js";
|
|
12
12
|
import "./chunk-6MLEBAYZ.js";
|
|
13
13
|
import "./chunk-VT4PDUYT.js";
|
|
@@ -24,4 +24,4 @@ var LintCommand = class extends ScriptCommandBase {
|
|
|
24
24
|
export {
|
|
25
25
|
LintCommand
|
|
26
26
|
};
|
|
27
|
-
//# sourceMappingURL=lint-
|
|
27
|
+
//# sourceMappingURL=lint-AAN2NZWG.js.map
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/mcp/harness-server.ts
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
import net from "net";
|
|
9
|
+
var SIGNAL_TIMEOUT_MS = 3e4;
|
|
10
|
+
function validateEnvironment() {
|
|
11
|
+
const socketPath = process.env.ILOOM_HARNESS_SOCKET;
|
|
12
|
+
if (!socketPath) {
|
|
13
|
+
console.error("Missing required environment variable: ILOOM_HARNESS_SOCKET");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
return socketPath;
|
|
17
|
+
}
|
|
18
|
+
var validatedSocketPath = null;
|
|
19
|
+
function getSocketPath() {
|
|
20
|
+
if (!validatedSocketPath) {
|
|
21
|
+
throw new Error("ILOOM_HARNESS_SOCKET not validated - validateEnvironment() must be called first");
|
|
22
|
+
}
|
|
23
|
+
return validatedSocketPath;
|
|
24
|
+
}
|
|
25
|
+
function sendSignalToHarness(socketPath, message) {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const socket = net.createConnection(socketPath);
|
|
28
|
+
let responseData = "";
|
|
29
|
+
let settled = false;
|
|
30
|
+
const timeoutHandle = globalThis.setTimeout(() => {
|
|
31
|
+
if (!settled) {
|
|
32
|
+
settled = true;
|
|
33
|
+
socket.destroy();
|
|
34
|
+
reject(new Error("Harness did not respond within 30s."));
|
|
35
|
+
}
|
|
36
|
+
}, SIGNAL_TIMEOUT_MS);
|
|
37
|
+
socket.on("connect", () => {
|
|
38
|
+
const payload = JSON.stringify(message) + "\n";
|
|
39
|
+
socket.write(payload);
|
|
40
|
+
});
|
|
41
|
+
socket.on("data", (chunk) => {
|
|
42
|
+
responseData += chunk.toString();
|
|
43
|
+
const newlineIndex = responseData.indexOf("\n");
|
|
44
|
+
if (newlineIndex !== -1) {
|
|
45
|
+
const line = responseData.slice(0, newlineIndex).trim();
|
|
46
|
+
if (!settled) {
|
|
47
|
+
settled = true;
|
|
48
|
+
globalThis.clearTimeout(timeoutHandle);
|
|
49
|
+
socket.destroy();
|
|
50
|
+
try {
|
|
51
|
+
const parsed = JSON.parse(line);
|
|
52
|
+
resolve(parsed);
|
|
53
|
+
} catch {
|
|
54
|
+
reject(new Error(`Harness returned invalid JSON: ${line}`));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
socket.on("error", (err) => {
|
|
60
|
+
if (!settled) {
|
|
61
|
+
settled = true;
|
|
62
|
+
globalThis.clearTimeout(timeoutHandle);
|
|
63
|
+
if (err.code === "EPIPE" || err.code === "ECONNRESET") {
|
|
64
|
+
reject(new Error("Harness closed connection before responding."));
|
|
65
|
+
} else {
|
|
66
|
+
reject(err);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
socket.on("close", () => {
|
|
71
|
+
if (!settled) {
|
|
72
|
+
settled = true;
|
|
73
|
+
globalThis.clearTimeout(timeoutHandle);
|
|
74
|
+
reject(new Error("Harness closed connection before responding."));
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
var server = new McpServer({
|
|
80
|
+
name: "iloom-harness",
|
|
81
|
+
version: "0.1.0"
|
|
82
|
+
});
|
|
83
|
+
server.registerTool(
|
|
84
|
+
"signal",
|
|
85
|
+
{
|
|
86
|
+
title: "Signal",
|
|
87
|
+
description: "Send a structured signal to the iloom harness process and return the response. Use this to notify the harness of workflow events (e.g., done, status update).",
|
|
88
|
+
inputSchema: {
|
|
89
|
+
type: z.string().describe('Signal type (e.g., "done", "status")'),
|
|
90
|
+
data: z.record(z.unknown()).optional().describe("Optional payload data for the signal")
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
async ({ type, data }) => {
|
|
94
|
+
const socketPath = getSocketPath();
|
|
95
|
+
const message = { type };
|
|
96
|
+
if (data !== void 0) {
|
|
97
|
+
message.data = data;
|
|
98
|
+
}
|
|
99
|
+
let response;
|
|
100
|
+
try {
|
|
101
|
+
response = await sendSignalToHarness(socketPath, message);
|
|
102
|
+
} catch (err) {
|
|
103
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
104
|
+
const isTimeout = errorMessage.includes("within 30s");
|
|
105
|
+
const text = isTimeout ? "Error: Harness did not respond within 30s." : `Error: ${errorMessage}`;
|
|
106
|
+
return {
|
|
107
|
+
content: [{ type: "text", text }],
|
|
108
|
+
isError: true
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
content: [{ type: "text", text: JSON.stringify(response) }]
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
async function main() {
|
|
117
|
+
console.error("=== Iloom Harness MCP Server Starting ===");
|
|
118
|
+
console.error(`PID: ${process.pid}`);
|
|
119
|
+
console.error(`Node version: ${process.version}`);
|
|
120
|
+
console.error(`CWD: ${process.cwd()}`);
|
|
121
|
+
console.error(`Script: ${fileURLToPath(import.meta.url)}`);
|
|
122
|
+
console.error("Environment variables:");
|
|
123
|
+
console.error(` ILOOM_HARNESS_SOCKET=${process.env.ILOOM_HARNESS_SOCKET ?? "<not set>"}`);
|
|
124
|
+
validatedSocketPath = validateEnvironment();
|
|
125
|
+
console.error(`Harness socket path: ${validatedSocketPath}`);
|
|
126
|
+
const transport = new StdioServerTransport();
|
|
127
|
+
await server.connect(transport);
|
|
128
|
+
console.error("=== Iloom Harness MCP Server READY (stdio transport) ===");
|
|
129
|
+
}
|
|
130
|
+
var isMain = process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1];
|
|
131
|
+
if (isMain) {
|
|
132
|
+
main().catch((error) => {
|
|
133
|
+
console.error("Fatal error starting MCP server:", error);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
export {
|
|
138
|
+
sendSignalToHarness
|
|
139
|
+
};
|
|
140
|
+
//# sourceMappingURL=harness-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/mcp/harness-server.ts"],"sourcesContent":["/**\n * Harness MCP Server\n *\n * Provides Claude with a `signal` tool to send structured messages to the iloom\n * harness process via Unix domain socket. This is the Claude-side counterpart to\n * the HarnessServer (#762).\n *\n * Environment variables:\n * - ILOOM_HARNESS_SOCKET: Path to the harness Unix domain socket (required)\n */\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { z } from 'zod'\nimport { fileURLToPath } from 'node:url'\nimport net from 'net'\n\nconst SIGNAL_TIMEOUT_MS = 30_000\n\n/**\n * Validate required environment variables\n * Exits with error if missing\n */\nfunction validateEnvironment(): string {\n\tconst socketPath = process.env.ILOOM_HARNESS_SOCKET\n\n\tif (!socketPath) {\n\t\tconsole.error('Missing required environment variable: ILOOM_HARNESS_SOCKET')\n\t\tprocess.exit(1)\n\t}\n\n\treturn socketPath\n}\n\nlet validatedSocketPath: string | null = null\n\n/**\n * Get the validated socket path\n * Throws if called before validateEnvironment()\n */\nfunction getSocketPath(): string {\n\tif (!validatedSocketPath) {\n\t\tthrow new Error('ILOOM_HARNESS_SOCKET not validated - validateEnvironment() must be called first')\n\t}\n\treturn validatedSocketPath\n}\n\n/**\n * Send a signal message to the harness via Unix domain socket and wait for response.\n * Returns the parsed response object, or throws on error/timeout.\n */\nexport function sendSignalToHarness(\n\tsocketPath: string,\n\tmessage: { type: string; data?: Record<string, unknown> }\n): Promise<Record<string, unknown>> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst socket = net.createConnection(socketPath)\n\n\t\tlet responseData = ''\n\t\tlet settled = false\n\n\t\tconst timeoutHandle = globalThis.setTimeout(() => {\n\t\t\tif (!settled) {\n\t\t\t\tsettled = true\n\t\t\t\tsocket.destroy()\n\t\t\t\treject(new Error('Harness did not respond within 30s.'))\n\t\t\t}\n\t\t}, SIGNAL_TIMEOUT_MS)\n\n\t\tsocket.on('connect', () => {\n\t\t\tconst payload = JSON.stringify(message) + '\\n'\n\t\t\tsocket.write(payload)\n\t\t})\n\n\t\tsocket.on('data', (chunk: Buffer) => {\n\t\t\tresponseData += chunk.toString()\n\n\t\t\tconst newlineIndex = responseData.indexOf('\\n')\n\t\t\tif (newlineIndex !== -1) {\n\t\t\t\tconst line = responseData.slice(0, newlineIndex).trim()\n\t\t\t\tif (!settled) {\n\t\t\t\t\tsettled = true\n\t\t\t\t\tglobalThis.clearTimeout(timeoutHandle)\n\t\t\t\t\tsocket.destroy()\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst parsed = JSON.parse(line) as Record<string, unknown>\n\t\t\t\t\t\tresolve(parsed)\n\t\t\t\t\t} catch {\n\t\t\t\t\t\treject(new Error(`Harness returned invalid JSON: ${line}`))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\n\t\tsocket.on('error', (err: NodeJS.ErrnoException) => {\n\t\t\tif (!settled) {\n\t\t\t\tsettled = true\n\t\t\t\tglobalThis.clearTimeout(timeoutHandle)\n\t\t\t\t// EPIPE and ECONNRESET mean the connection was closed before we could write/read\n\t\t\t\tif (err.code === 'EPIPE' || err.code === 'ECONNRESET') {\n\t\t\t\t\treject(new Error('Harness closed connection before responding.'))\n\t\t\t\t} else {\n\t\t\t\t\treject(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\n\t\tsocket.on('close', () => {\n\t\t\tif (!settled) {\n\t\t\t\tsettled = true\n\t\t\t\tglobalThis.clearTimeout(timeoutHandle)\n\t\t\t\treject(new Error('Harness closed connection before responding.'))\n\t\t\t}\n\t\t})\n\t})\n}\n\n// Initialize MCP server\nconst server = new McpServer({\n\tname: 'iloom-harness',\n\tversion: '0.1.0',\n})\n\n// Register signal tool\nserver.registerTool(\n\t'signal',\n\t{\n\t\ttitle: 'Signal',\n\t\tdescription:\n\t\t\t'Send a structured signal to the iloom harness process and return the response. ' +\n\t\t\t'Use this to notify the harness of workflow events (e.g., done, status update).',\n\t\tinputSchema: {\n\t\t\ttype: z.string().describe('Signal type (e.g., \"done\", \"status\")'),\n\t\t\tdata: z.record(z.unknown()).optional().describe('Optional payload data for the signal'),\n\t\t},\n\t},\n\tasync ({ type, data }) => {\n\t\tconst socketPath = getSocketPath()\n\t\tconst message: { type: string; data?: Record<string, unknown> } = { type }\n\t\tif (data !== undefined) {\n\t\t\tmessage.data = data as Record<string, unknown>\n\t\t}\n\n\t\tlet response: Record<string, unknown>\n\t\ttry {\n\t\t\tresponse = await sendSignalToHarness(socketPath, message)\n\t\t} catch (err) {\n\t\t\tconst errorMessage = err instanceof Error ? err.message : String(err)\n\t\t\tconst isTimeout = errorMessage.includes('within 30s')\n\t\t\tconst text = isTimeout\n\t\t\t\t? 'Error: Harness did not respond within 30s.'\n\t\t\t\t: `Error: ${errorMessage}`\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: 'text' as const, text }],\n\t\t\t\tisError: true,\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tcontent: [{ type: 'text' as const, text: JSON.stringify(response) }],\n\t\t}\n\t}\n)\n\n// Main server startup\nasync function main(): Promise<void> {\n\tconsole.error('=== Iloom Harness MCP Server Starting ===')\n\tconsole.error(`PID: ${process.pid}`)\n\tconsole.error(`Node version: ${process.version}`)\n\tconsole.error(`CWD: ${process.cwd()}`)\n\tconsole.error(`Script: ${fileURLToPath(import.meta.url)}`)\n\n\tconsole.error('Environment variables:')\n\tconsole.error(` ILOOM_HARNESS_SOCKET=${process.env.ILOOM_HARNESS_SOCKET ?? '<not set>'}`)\n\n\tvalidatedSocketPath = validateEnvironment()\n\tconsole.error(`Harness socket path: ${validatedSocketPath}`)\n\n\tconst transport = new StdioServerTransport()\n\tawait server.connect(transport)\n\tconsole.error('=== Iloom Harness MCP Server READY (stdio transport) ===')\n}\n\n// Only run main when executed directly (not when imported in tests)\nconst isMain = process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]\nif (isMain) {\n\tmain().catch((error) => {\n\t\tconsole.error('Fatal error starting MCP server:', error)\n\t\tprocess.exit(1)\n\t})\n}\n"],"mappings":";;;AAUA,SAAS,iBAAiB;AAC1B,SAAS,4BAA4B;AACrC,SAAS,SAAS;AAClB,SAAS,qBAAqB;AAC9B,OAAO,SAAS;AAEhB,IAAM,oBAAoB;AAM1B,SAAS,sBAA8B;AACtC,QAAM,aAAa,QAAQ,IAAI;AAE/B,MAAI,CAAC,YAAY;AAChB,YAAQ,MAAM,6DAA6D;AAC3E,YAAQ,KAAK,CAAC;AAAA,EACf;AAEA,SAAO;AACR;AAEA,IAAI,sBAAqC;AAMzC,SAAS,gBAAwB;AAChC,MAAI,CAAC,qBAAqB;AACzB,UAAM,IAAI,MAAM,iFAAiF;AAAA,EAClG;AACA,SAAO;AACR;AAMO,SAAS,oBACf,YACA,SACmC;AACnC,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACvC,UAAM,SAAS,IAAI,iBAAiB,UAAU;AAE9C,QAAI,eAAe;AACnB,QAAI,UAAU;AAEd,UAAM,gBAAgB,WAAW,WAAW,MAAM;AACjD,UAAI,CAAC,SAAS;AACb,kBAAU;AACV,eAAO,QAAQ;AACf,eAAO,IAAI,MAAM,qCAAqC,CAAC;AAAA,MACxD;AAAA,IACD,GAAG,iBAAiB;AAEpB,WAAO,GAAG,WAAW,MAAM;AAC1B,YAAM,UAAU,KAAK,UAAU,OAAO,IAAI;AAC1C,aAAO,MAAM,OAAO;AAAA,IACrB,CAAC;AAED,WAAO,GAAG,QAAQ,CAAC,UAAkB;AACpC,sBAAgB,MAAM,SAAS;AAE/B,YAAM,eAAe,aAAa,QAAQ,IAAI;AAC9C,UAAI,iBAAiB,IAAI;AACxB,cAAM,OAAO,aAAa,MAAM,GAAG,YAAY,EAAE,KAAK;AACtD,YAAI,CAAC,SAAS;AACb,oBAAU;AACV,qBAAW,aAAa,aAAa;AACrC,iBAAO,QAAQ;AACf,cAAI;AACH,kBAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,oBAAQ,MAAM;AAAA,UACf,QAAQ;AACP,mBAAO,IAAI,MAAM,kCAAkC,IAAI,EAAE,CAAC;AAAA,UAC3D;AAAA,QACD;AAAA,MACD;AAAA,IACD,CAAC;AAED,WAAO,GAAG,SAAS,CAAC,QAA+B;AAClD,UAAI,CAAC,SAAS;AACb,kBAAU;AACV,mBAAW,aAAa,aAAa;AAErC,YAAI,IAAI,SAAS,WAAW,IAAI,SAAS,cAAc;AACtD,iBAAO,IAAI,MAAM,8CAA8C,CAAC;AAAA,QACjE,OAAO;AACN,iBAAO,GAAG;AAAA,QACX;AAAA,MACD;AAAA,IACD,CAAC;AAED,WAAO,GAAG,SAAS,MAAM;AACxB,UAAI,CAAC,SAAS;AACb,kBAAU;AACV,mBAAW,aAAa,aAAa;AACrC,eAAO,IAAI,MAAM,8CAA8C,CAAC;AAAA,MACjE;AAAA,IACD,CAAC;AAAA,EACF,CAAC;AACF;AAGA,IAAM,SAAS,IAAI,UAAU;AAAA,EAC5B,MAAM;AAAA,EACN,SAAS;AACV,CAAC;AAGD,OAAO;AAAA,EACN;AAAA,EACA;AAAA,IACC,OAAO;AAAA,IACP,aACC;AAAA,IAED,aAAa;AAAA,MACZ,MAAM,EAAE,OAAO,EAAE,SAAS,sCAAsC;AAAA,MAChE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,EAAE,SAAS,sCAAsC;AAAA,IACvF;AAAA,EACD;AAAA,EACA,OAAO,EAAE,MAAM,KAAK,MAAM;AACzB,UAAM,aAAa,cAAc;AACjC,UAAM,UAA4D,EAAE,KAAK;AACzE,QAAI,SAAS,QAAW;AACvB,cAAQ,OAAO;AAAA,IAChB;AAEA,QAAI;AACJ,QAAI;AACH,iBAAW,MAAM,oBAAoB,YAAY,OAAO;AAAA,IACzD,SAAS,KAAK;AACb,YAAM,eAAe,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AACpE,YAAM,YAAY,aAAa,SAAS,YAAY;AACpD,YAAM,OAAO,YACV,+CACA,UAAU,YAAY;AACzB,aAAO;AAAA,QACN,SAAS,CAAC,EAAE,MAAM,QAAiB,KAAK,CAAC;AAAA,QACzC,SAAS;AAAA,MACV;AAAA,IACD;AAEA,WAAO;AAAA,MACN,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,KAAK,UAAU,QAAQ,EAAE,CAAC;AAAA,IACpE;AAAA,EACD;AACD;AAGA,eAAe,OAAsB;AACpC,UAAQ,MAAM,2CAA2C;AACzD,UAAQ,MAAM,QAAQ,QAAQ,GAAG,EAAE;AACnC,UAAQ,MAAM,iBAAiB,QAAQ,OAAO,EAAE;AAChD,UAAQ,MAAM,QAAQ,QAAQ,IAAI,CAAC,EAAE;AACrC,UAAQ,MAAM,WAAW,cAAc,YAAY,GAAG,CAAC,EAAE;AAEzD,UAAQ,MAAM,wBAAwB;AACtC,UAAQ,MAAM,0BAA0B,QAAQ,IAAI,wBAAwB,WAAW,EAAE;AAEzF,wBAAsB,oBAAoB;AAC1C,UAAQ,MAAM,wBAAwB,mBAAmB,EAAE;AAE3D,QAAM,YAAY,IAAI,qBAAqB;AAC3C,QAAM,OAAO,QAAQ,SAAS;AAC9B,UAAQ,MAAM,0DAA0D;AACzE;AAGA,IAAM,SAAS,QAAQ,KAAK,CAAC,KAAK,cAAc,YAAY,GAAG,MAAM,QAAQ,KAAK,CAAC;AACnF,IAAI,QAAQ;AACX,OAAK,EAAE,MAAM,CAAC,UAAU;AACvB,YAAQ,MAAM,oCAAoC,KAAK;AACvD,YAAQ,KAAK,CAAC;AAAA,EACf,CAAC;AACF;","names":[]}
|
|
@@ -2831,6 +2831,7 @@ import { z } from "zod";
|
|
|
2831
2831
|
import deepmerge from "deepmerge";
|
|
2832
2832
|
var BaseAgentSettingsSchema = z.object({
|
|
2833
2833
|
model: z.enum(["sonnet", "opus", "haiku"]).optional().describe("Claude model shorthand: sonnet, opus, or haiku"),
|
|
2834
|
+
swarmModel: z.enum(["sonnet", "opus", "haiku"]).optional().describe("Model to use for this agent in swarm mode. Overrides the base model when running inside swarm workers."),
|
|
2834
2835
|
enabled: z.boolean().optional().describe("Whether this agent is enabled. Defaults to true."),
|
|
2835
2836
|
providers: z.record(
|
|
2836
2837
|
z.enum(["claude", "gemini", "codex"]),
|
|
@@ -2839,10 +2840,12 @@ var BaseAgentSettingsSchema = z.object({
|
|
|
2839
2840
|
review: z.boolean().optional().describe("Whether artifacts from this agent should be reviewed before posting (defaults to false)")
|
|
2840
2841
|
});
|
|
2841
2842
|
var AgentSettingsSchema = BaseAgentSettingsSchema.extend({
|
|
2842
|
-
agents: z.record(z.string(), BaseAgentSettingsSchema).optional().describe("Nested per-agent
|
|
2843
|
+
agents: z.record(z.string(), BaseAgentSettingsSchema).optional().describe("Nested per-agent settings. Only meaningful under the iloom-swarm-worker agent entry for sub-agent timeout configuration."),
|
|
2844
|
+
subAgentTimeout: z.number().min(1, "Sub-agent timeout must be at least 1 minute").max(120, "Sub-agent timeout cannot exceed 120 minutes").default(10).describe("Timeout in minutes for sub-agent claude -p invocations in swarm mode. Applies to each phase agent (evaluator, analyzer, planner, implementer) when invoked via the Bash tool. Default: 10 minutes. Only meaningful under the iloom-swarm-worker agent entry.")
|
|
2843
2845
|
});
|
|
2844
2846
|
var SpinAgentSettingsSchema = z.object({
|
|
2845
|
-
model: z.enum(["sonnet", "opus", "haiku"]).default("opus").describe("Claude model shorthand for spin orchestrator")
|
|
2847
|
+
model: z.enum(["sonnet", "opus", "haiku"]).default("opus").describe("Claude model shorthand for spin orchestrator"),
|
|
2848
|
+
swarmModel: z.enum(["sonnet", "opus", "haiku"]).optional().describe("Model for the spin orchestrator when running in swarm mode. Overrides spin.model for swarm workflows.")
|
|
2846
2849
|
});
|
|
2847
2850
|
var PlanCommandSettingsSchema = z.object({
|
|
2848
2851
|
model: z.enum(["sonnet", "opus", "haiku"]).default("opus").describe("Claude model shorthand for plan command"),
|
|
@@ -2935,7 +2938,7 @@ var IloomSettingsSchema = z.object({
|
|
|
2935
2938
|
copyGitIgnoredPatterns: z.array(z.string().min(1, "Pattern cannot be empty")).optional().describe(`Glob patterns for gitignored files to copy to looms (e.g., ["*.db", "data/*.sqlite"]). Great for local dbs and large test data files that are too big to commit to git. Note: .env (dotenv-flow) files, iloom's and claude's local settings are automatically copied and do not need to be specified here.`),
|
|
2936
2939
|
workflows: WorkflowsSettingsSchema.describe("Per-workflow-type permission configurations"),
|
|
2937
2940
|
agents: z.record(z.string(), AgentSettingsSchema).optional().nullable().describe(
|
|
2938
|
-
|
|
2941
|
+
"Per-agent configuration overrides. Available agents: iloom-issue-analyzer (analyzes issues), iloom-issue-planner (creates implementation plans), iloom-issue-analyze-and-plan (combined analysis and planning), iloom-issue-complexity-evaluator (evaluates complexity), iloom-issue-enhancer (enhances issue descriptions), iloom-issue-implementer (implements code changes), iloom-code-reviewer (reviews code changes against requirements), iloom-artifact-reviewer (reviews artifacts before posting), iloom-swarm-worker (swarm worker agent, dynamically generated). Use swarmModel on any agent to override its model in swarm mode."
|
|
2939
2942
|
),
|
|
2940
2943
|
spin: SpinAgentSettingsSchema.optional().describe(
|
|
2941
2944
|
"Spin orchestrator configuration. Model defaults to opus when not configured."
|
|
@@ -3033,10 +3036,11 @@ var IloomSettingsSchemaNoDefaults = z.object({
|
|
|
3033
3036
|
copyGitIgnoredPatterns: z.array(z.string().min(1, "Pattern cannot be empty")).optional().describe(`Glob patterns for gitignored files to copy to looms (e.g., ["*.db", "data/*.sqlite"]). Great for local dbs and large test data files that are too big to commit to git. Note: .env (dotenv-flow) files, iloom's and claude's local settings are automatically copied and do not need to be specified here.`),
|
|
3034
3037
|
workflows: WorkflowsSettingsSchemaNoDefaults.describe("Per-workflow-type permission configurations"),
|
|
3035
3038
|
agents: z.record(z.string(), AgentSettingsSchema).optional().nullable().describe(
|
|
3036
|
-
|
|
3039
|
+
"Per-agent configuration overrides. Available agents: iloom-issue-analyzer (analyzes issues), iloom-issue-planner (creates implementation plans), iloom-issue-analyze-and-plan (combined analysis and planning), iloom-issue-complexity-evaluator (evaluates complexity), iloom-issue-enhancer (enhances issue descriptions), iloom-issue-implementer (implements code changes), iloom-code-reviewer (reviews code changes against requirements), iloom-artifact-reviewer (reviews artifacts before posting), iloom-swarm-worker (swarm worker agent, dynamically generated). Use swarmModel on any agent to override its model in swarm mode."
|
|
3037
3040
|
),
|
|
3038
3041
|
spin: z.object({
|
|
3039
|
-
model: z.enum(["sonnet", "opus", "haiku"]).optional()
|
|
3042
|
+
model: z.enum(["sonnet", "opus", "haiku"]).optional(),
|
|
3043
|
+
swarmModel: z.enum(["sonnet", "opus", "haiku"]).optional()
|
|
3040
3044
|
}).optional().describe("Spin orchestrator configuration"),
|
|
3041
3045
|
plan: z.object({
|
|
3042
3046
|
model: z.enum(["sonnet", "opus", "haiku"]).optional(),
|
|
@@ -3326,9 +3330,15 @@ ${errorMessages.join("\n")}`
|
|
|
3326
3330
|
* @param settings - Pre-loaded settings object
|
|
3327
3331
|
* @returns Model shorthand ('opus', 'sonnet', or 'haiku')
|
|
3328
3332
|
*/
|
|
3329
|
-
getSpinModel(settings2) {
|
|
3330
|
-
var _a;
|
|
3331
|
-
|
|
3333
|
+
getSpinModel(settings2, mode) {
|
|
3334
|
+
var _a, _b;
|
|
3335
|
+
if (mode === "swarm") {
|
|
3336
|
+
if ((_a = settings2 == null ? void 0 : settings2.spin) == null ? void 0 : _a.swarmModel) {
|
|
3337
|
+
return settings2.spin.swarmModel;
|
|
3338
|
+
}
|
|
3339
|
+
return "opus";
|
|
3340
|
+
}
|
|
3341
|
+
return ((_b = settings2 == null ? void 0 : settings2.spin) == null ? void 0 : _b.model) ?? SpinAgentSettingsSchema.parse({}).model;
|
|
3332
3342
|
}
|
|
3333
3343
|
/**
|
|
3334
3344
|
* Get the plan command model with default applied
|
|
@@ -3722,6 +3732,108 @@ var IssueManagementProviderFactory = class {
|
|
|
3722
3732
|
}
|
|
3723
3733
|
};
|
|
3724
3734
|
|
|
3735
|
+
// src/utils/jira-wiki-sanitizer.ts
|
|
3736
|
+
var JiraWikiSanitizer = class {
|
|
3737
|
+
/**
|
|
3738
|
+
* Sanitize body text by converting unambiguous Jira Wiki patterns to Markdown.
|
|
3739
|
+
* Preserves content inside backtick-fenced code blocks.
|
|
3740
|
+
* Returns text unchanged if no Wiki patterns detected.
|
|
3741
|
+
*/
|
|
3742
|
+
static sanitize(text) {
|
|
3743
|
+
if (!text) {
|
|
3744
|
+
return "";
|
|
3745
|
+
}
|
|
3746
|
+
const segments = this.splitByCodeBlocks(text);
|
|
3747
|
+
const converted = segments.map((segment) => {
|
|
3748
|
+
if (segment.isCode) {
|
|
3749
|
+
return segment.text;
|
|
3750
|
+
}
|
|
3751
|
+
return this.convertSegment(segment.text);
|
|
3752
|
+
});
|
|
3753
|
+
return converted.join("");
|
|
3754
|
+
}
|
|
3755
|
+
/**
|
|
3756
|
+
* Check if text contains unambiguous Jira Wiki patterns.
|
|
3757
|
+
* Only checks for patterns that are safe to convert.
|
|
3758
|
+
*/
|
|
3759
|
+
static hasJiraWikiPatterns(text) {
|
|
3760
|
+
if (!text) {
|
|
3761
|
+
return false;
|
|
3762
|
+
}
|
|
3763
|
+
if (/^h[1-6]\.\s+/m.test(text)) {
|
|
3764
|
+
return true;
|
|
3765
|
+
}
|
|
3766
|
+
if (/\{code(?::[^}]*)?\}/i.test(text)) {
|
|
3767
|
+
return true;
|
|
3768
|
+
}
|
|
3769
|
+
if (/\{quote\}/i.test(text)) {
|
|
3770
|
+
return true;
|
|
3771
|
+
}
|
|
3772
|
+
if (/\[[^\]|]+\|https?:\/\/[^\]]+\]/.test(text)) {
|
|
3773
|
+
return true;
|
|
3774
|
+
}
|
|
3775
|
+
return false;
|
|
3776
|
+
}
|
|
3777
|
+
/**
|
|
3778
|
+
* Split text into segments, separating existing Markdown fenced code blocks
|
|
3779
|
+
* from the rest of the content. This ensures we don't modify content inside
|
|
3780
|
+
* code blocks (e.g., Jira Wiki examples shown in a Markdown code block).
|
|
3781
|
+
*/
|
|
3782
|
+
static splitByCodeBlocks(text) {
|
|
3783
|
+
const segments = [];
|
|
3784
|
+
const codeBlockRegex = /^(`{3,})[^\n]*\n[\s\S]*?^\1\s*$/gm;
|
|
3785
|
+
let lastIndex = 0;
|
|
3786
|
+
for (const match of text.matchAll(codeBlockRegex)) {
|
|
3787
|
+
const matchStart = match.index ?? 0;
|
|
3788
|
+
if (matchStart > lastIndex) {
|
|
3789
|
+
segments.push({ text: text.slice(lastIndex, matchStart), isCode: false });
|
|
3790
|
+
}
|
|
3791
|
+
segments.push({ text: match[0], isCode: true });
|
|
3792
|
+
lastIndex = matchStart + match[0].length;
|
|
3793
|
+
}
|
|
3794
|
+
if (lastIndex < text.length) {
|
|
3795
|
+
segments.push({ text: text.slice(lastIndex), isCode: false });
|
|
3796
|
+
}
|
|
3797
|
+
return segments;
|
|
3798
|
+
}
|
|
3799
|
+
/**
|
|
3800
|
+
* Apply all safe Jira Wiki -> Markdown conversions to a text segment.
|
|
3801
|
+
*/
|
|
3802
|
+
static convertSegment(text) {
|
|
3803
|
+
let result = text;
|
|
3804
|
+
result = result.replace(/^h([1-6])\.\s+(.*?)$/gm, (_match, level, content) => {
|
|
3805
|
+
const hashes = "#".repeat(parseInt(level, 10));
|
|
3806
|
+
return `${hashes} ${content}`;
|
|
3807
|
+
});
|
|
3808
|
+
result = result.replace(
|
|
3809
|
+
/\{code:([^}]+)\}\s*\n([\s\S]*?)\n?\s*\{code\}/gi,
|
|
3810
|
+
(_match, lang, content) => {
|
|
3811
|
+
return "```" + lang.trim() + "\n" + content + "\n```";
|
|
3812
|
+
}
|
|
3813
|
+
);
|
|
3814
|
+
result = result.replace(
|
|
3815
|
+
/\{code\}\s*\n([\s\S]*?)\n?\s*\{code\}/gi,
|
|
3816
|
+
(_match, content) => {
|
|
3817
|
+
return "```\n" + content + "\n```";
|
|
3818
|
+
}
|
|
3819
|
+
);
|
|
3820
|
+
result = result.replace(
|
|
3821
|
+
/\{quote\}\s*\n([\s\S]*?)\n?\s*\{quote\}/gi,
|
|
3822
|
+
(_match, content) => {
|
|
3823
|
+
const lines = content.split("\n");
|
|
3824
|
+
return lines.map((line) => `> ${line}`).join("\n");
|
|
3825
|
+
}
|
|
3826
|
+
);
|
|
3827
|
+
result = result.replace(
|
|
3828
|
+
/\[([^\]|]+)\|(https?:\/\/[^\]]+)\]/g,
|
|
3829
|
+
(_match, linkText, url) => {
|
|
3830
|
+
return `[${linkText}](${url})`;
|
|
3831
|
+
}
|
|
3832
|
+
);
|
|
3833
|
+
return result;
|
|
3834
|
+
}
|
|
3835
|
+
};
|
|
3836
|
+
|
|
3725
3837
|
// src/mcp/issue-management-server.ts
|
|
3726
3838
|
var settings;
|
|
3727
3839
|
function validateEnvironment() {
|
|
@@ -4018,7 +4130,8 @@ server.registerTool(
|
|
|
4018
4130
|
inputSchema: {
|
|
4019
4131
|
number: z2.string().describe("The issue or PR identifier"),
|
|
4020
4132
|
body: z2.string().describe("The comment body (markdown supported)"),
|
|
4021
|
-
type: z2.enum(["issue", "pr"]).describe("Type of entity to comment on (issue or pr)")
|
|
4133
|
+
type: z2.enum(["issue", "pr"]).describe("Type of entity to comment on (issue or pr)"),
|
|
4134
|
+
markupLanguage: z2.literal("GFM").describe("The markup language for the body content. Must be GitHub Flavored Markdown (GFM).")
|
|
4022
4135
|
},
|
|
4023
4136
|
outputSchema: {
|
|
4024
4137
|
id: z2.string(),
|
|
@@ -4029,9 +4142,10 @@ server.registerTool(
|
|
|
4029
4142
|
async ({ number, body, type }) => {
|
|
4030
4143
|
console.error(`Creating ${type} comment on ${number}`);
|
|
4031
4144
|
try {
|
|
4145
|
+
const sanitizedBody = JiraWikiSanitizer.sanitize(body);
|
|
4032
4146
|
const providerType = type === "pr" ? "github" : process.env.ISSUE_PROVIDER;
|
|
4033
4147
|
const provider = IssueManagementProviderFactory.create(providerType, settings);
|
|
4034
|
-
const result = await provider.createComment({ number, body, type });
|
|
4148
|
+
const result = await provider.createComment({ number, body: sanitizedBody, type });
|
|
4035
4149
|
console.error(
|
|
4036
4150
|
`Comment created successfully: ${result.id} at ${result.url}`
|
|
4037
4151
|
);
|
|
@@ -4060,7 +4174,8 @@ server.registerTool(
|
|
|
4060
4174
|
commentId: z2.string().describe("The comment identifier to update"),
|
|
4061
4175
|
number: z2.string().describe("The issue or PR identifier (context for providers that need it)"),
|
|
4062
4176
|
body: z2.string().describe("The updated comment body (markdown supported)"),
|
|
4063
|
-
type: z2.enum(["issue", "pr"]).optional().describe("Optional type to route PR comments to GitHub regardless of configured provider")
|
|
4177
|
+
type: z2.enum(["issue", "pr"]).optional().describe("Optional type to route PR comments to GitHub regardless of configured provider"),
|
|
4178
|
+
markupLanguage: z2.literal("GFM").describe("The markup language for the body content. Must be GitHub Flavored Markdown (GFM).")
|
|
4064
4179
|
},
|
|
4065
4180
|
outputSchema: {
|
|
4066
4181
|
id: z2.string(),
|
|
@@ -4071,9 +4186,10 @@ server.registerTool(
|
|
|
4071
4186
|
async ({ commentId, number, body, type }) => {
|
|
4072
4187
|
console.error(`Updating comment ${commentId} on ${type === "pr" ? "PR" : "issue"} ${number}`);
|
|
4073
4188
|
try {
|
|
4189
|
+
const sanitizedBody = JiraWikiSanitizer.sanitize(body);
|
|
4074
4190
|
const providerType = type === "pr" ? "github" : process.env.ISSUE_PROVIDER;
|
|
4075
4191
|
const provider = IssueManagementProviderFactory.create(providerType, settings);
|
|
4076
|
-
const result = await provider.updateComment({ commentId, number, body });
|
|
4192
|
+
const result = await provider.updateComment({ commentId, number, body: sanitizedBody });
|
|
4077
4193
|
console.error(
|
|
4078
4194
|
`Comment updated successfully: ${result.id} at ${result.url}`
|
|
4079
4195
|
);
|
|
@@ -4105,7 +4221,8 @@ server.registerTool(
|
|
|
4105
4221
|
teamKey: z2.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.'),
|
|
4106
4222
|
repo: z2.string().optional().describe(
|
|
4107
4223
|
'Optional repository in "owner/repo" format or full GitHub URL. When not provided, uses the current repository. GitHub only.'
|
|
4108
|
-
)
|
|
4224
|
+
),
|
|
4225
|
+
markupLanguage: z2.literal("GFM").describe("The markup language for the body content. Must be GitHub Flavored Markdown (GFM).")
|
|
4109
4226
|
},
|
|
4110
4227
|
outputSchema: {
|
|
4111
4228
|
id: z2.string().describe("Issue identifier"),
|
|
@@ -4116,11 +4233,12 @@ server.registerTool(
|
|
|
4116
4233
|
async ({ title, body, labels, teamKey, repo }) => {
|
|
4117
4234
|
console.error(`Creating issue: ${title}${repo ? ` in ${repo}` : ""}`);
|
|
4118
4235
|
try {
|
|
4236
|
+
const sanitizedBody = JiraWikiSanitizer.sanitize(body);
|
|
4119
4237
|
const provider = IssueManagementProviderFactory.create(
|
|
4120
4238
|
process.env.ISSUE_PROVIDER,
|
|
4121
4239
|
settings
|
|
4122
4240
|
);
|
|
4123
|
-
const result = await provider.createIssue({ title, body, labels, teamKey, repo });
|
|
4241
|
+
const result = await provider.createIssue({ title, body: sanitizedBody, labels, teamKey, repo });
|
|
4124
4242
|
console.error(`Issue created successfully: ${result.id} at ${result.url}`);
|
|
4125
4243
|
return {
|
|
4126
4244
|
content: [
|
|
@@ -4151,7 +4269,8 @@ server.registerTool(
|
|
|
4151
4269
|
teamKey: z2.string().optional().describe('Team key for Linear (e.g., "ENG"). Falls back to parent team. Ignored for GitHub.'),
|
|
4152
4270
|
repo: z2.string().optional().describe(
|
|
4153
4271
|
'Optional repository in "owner/repo" format or full GitHub URL. When not provided, uses the current repository. GitHub only.'
|
|
4154
|
-
)
|
|
4272
|
+
),
|
|
4273
|
+
markupLanguage: z2.literal("GFM").describe("The markup language for the body content. Must be GitHub Flavored Markdown (GFM).")
|
|
4155
4274
|
},
|
|
4156
4275
|
outputSchema: {
|
|
4157
4276
|
id: z2.string().describe("Issue identifier"),
|
|
@@ -4162,11 +4281,12 @@ server.registerTool(
|
|
|
4162
4281
|
async ({ parentId, title, body, labels, teamKey, repo }) => {
|
|
4163
4282
|
console.error(`Creating child issue for parent ${parentId}: ${title}${repo ? ` in ${repo}` : ""}`);
|
|
4164
4283
|
try {
|
|
4284
|
+
const sanitizedBody = JiraWikiSanitizer.sanitize(body);
|
|
4165
4285
|
const provider = IssueManagementProviderFactory.create(
|
|
4166
4286
|
process.env.ISSUE_PROVIDER,
|
|
4167
4287
|
settings
|
|
4168
4288
|
);
|
|
4169
|
-
const result = await provider.createChildIssue({ parentId, title, body, labels, teamKey, repo });
|
|
4289
|
+
const result = await provider.createChildIssue({ parentId, title, body: sanitizedBody, labels, teamKey, repo });
|
|
4170
4290
|
console.error(`Child issue created successfully: ${result.id} at ${result.url}`);
|
|
4171
4291
|
return {
|
|
4172
4292
|
content: [
|
|
@@ -4453,7 +4573,8 @@ server.registerTool(
|
|
|
4453
4573
|
labels: z2.array(z2.string()).optional().describe("Labels to add to the issue"),
|
|
4454
4574
|
repo: z2.string().optional().describe(
|
|
4455
4575
|
'Optional repository in "owner/repo" format or full GitHub URL. When not provided, uses the current repository. GitHub only.'
|
|
4456
|
-
)
|
|
4576
|
+
),
|
|
4577
|
+
markupLanguage: z2.literal("GFM").optional().describe("The markup language for the body content. Must be GitHub Flavored Markdown (GFM).")
|
|
4457
4578
|
},
|
|
4458
4579
|
outputSchema: {
|
|
4459
4580
|
success: z2.boolean().describe("Whether the issue was edited successfully")
|
|
@@ -4462,11 +4583,12 @@ server.registerTool(
|
|
|
4462
4583
|
async ({ number, title, body, state, labels, repo }) => {
|
|
4463
4584
|
console.error(`Editing issue ${number}${repo ? ` in ${repo}` : ""}`);
|
|
4464
4585
|
try {
|
|
4586
|
+
const sanitizedBody = body ? JiraWikiSanitizer.sanitize(body) : void 0;
|
|
4465
4587
|
const provider = IssueManagementProviderFactory.create(
|
|
4466
4588
|
process.env.ISSUE_PROVIDER,
|
|
4467
4589
|
settings
|
|
4468
4590
|
);
|
|
4469
|
-
await provider.editIssue({ number, title, body, state, labels, repo });
|
|
4591
|
+
await provider.editIssue({ number, title, body: sanitizedBody, state, labels, repo });
|
|
4470
4592
|
console.error(`Issue edited successfully: ${number}`);
|
|
4471
4593
|
return {
|
|
4472
4594
|
content: [
|