@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.
Files changed (155) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +2 -2
  3. package/dist/{BranchNamingService-ECJHBB67.js → BranchNamingService-25KSZAEM.js} +2 -2
  4. package/dist/ClaudeContextManager-66GR4BGM.js +14 -0
  5. package/dist/ClaudeService-7KM5NA5Z.js +13 -0
  6. package/dist/{LoomLauncher-L64HHS3T.js → LoomLauncher-TDLZSYG2.js} +6 -6
  7. package/dist/{PromptTemplateManager-DULSVRRE.js → PromptTemplateManager-YOE2SIPG.js} +2 -2
  8. package/dist/README.md +2 -2
  9. package/dist/{SettingsManager-BQDQA3FK.js → SettingsManager-FNKCOZMQ.js} +2 -2
  10. package/dist/{build-5GO3XW26.js → build-VHGEMXBA.js} +6 -6
  11. package/dist/chunk-4E7LCFUG.js +24 -0
  12. package/dist/chunk-4E7LCFUG.js.map +1 -0
  13. package/dist/{chunk-MNHZB4Z2.js → chunk-4FGEGQW4.js} +3 -3
  14. package/dist/{chunk-LXLMMXXY.js → chunk-5FJWO4IT.js} +17 -12
  15. package/dist/chunk-5FJWO4IT.js.map +1 -0
  16. package/dist/{chunk-ZHPNZC75.js → chunk-5RPBYK5Q.js} +26 -21
  17. package/dist/chunk-5RPBYK5Q.js.map +1 -0
  18. package/dist/{chunk-WY4QBK43.js → chunk-63QWFWH3.js} +2 -2
  19. package/dist/{chunk-YYAKPQBT.js → chunk-7VHJNVLF.js} +19 -9
  20. package/dist/chunk-7VHJNVLF.js.map +1 -0
  21. package/dist/{chunk-SF2P22EE.js → chunk-C6HNNJIV.js} +2 -2
  22. package/dist/{chunk-5MWV33NN.js → chunk-CVCTIDDK.js} +2 -2
  23. package/dist/{chunk-RYWFS37M.js → chunk-E6KOWMKA.js} +2 -2
  24. package/dist/{chunk-6EU6TCF6.js → chunk-EVPZFV3K.js} +5 -5
  25. package/dist/{chunk-ZEWU5PZK.js → chunk-G5V75JD5.js} +2 -2
  26. package/dist/chunk-GRISNU6G.js +651 -0
  27. package/dist/chunk-GRISNU6G.js.map +1 -0
  28. package/dist/{chunk-VGGST52X.js → chunk-I5T677EA.js} +2 -2
  29. package/dist/{chunk-VECNX6VX.js → chunk-KIK2ZFAL.js} +2 -2
  30. package/dist/{chunk-FB47TIJG.js → chunk-KKV5WH5M.js} +4 -23
  31. package/dist/chunk-KKV5WH5M.js.map +1 -0
  32. package/dist/{chunk-ZW2LKWWE.js → chunk-KVHIAWVT.js} +3 -3
  33. package/dist/{chunk-3D7WQM7I.js → chunk-LLHXQS3C.js} +2 -2
  34. package/dist/{chunk-Y4YZTHZE.js → chunk-LUKXJSRI.js} +2 -2
  35. package/dist/{ignite-CGOV3TD4.js → chunk-OTGH2HRS.js} +105 -71
  36. package/dist/chunk-OTGH2HRS.js.map +1 -0
  37. package/dist/{chunk-J5S7DFYC.js → chunk-QVLPWNE3.js} +2 -2
  38. package/dist/chunk-RJ3VBUFK.js +781 -0
  39. package/dist/chunk-RJ3VBUFK.js.map +1 -0
  40. package/dist/{chunk-SN3SQCFK.js → chunk-S7PZA6IV.js} +4 -4
  41. package/dist/{chunk-UWGVCXRF.js → chunk-SKSYYBCU.js} +23 -1
  42. package/dist/chunk-SKSYYBCU.js.map +1 -0
  43. package/dist/{chunk-JO2LZ6EQ.js → chunk-SWSJWA2S.js} +2 -2
  44. package/dist/{chunk-ONQYPICO.js → chunk-UR5DGNUO.js} +60 -6
  45. package/dist/chunk-UR5DGNUO.js.map +1 -0
  46. package/dist/{chunk-4WJNIR5O.js → chunk-UUEW5KWB.js} +1 -1
  47. package/dist/chunk-UUEW5KWB.js.map +1 -0
  48. package/dist/{chunk-NRSWLOAZ.js → chunk-WXIM2WS7.js} +4 -4
  49. package/dist/{chunk-UD3WJDIV.js → chunk-ZNMPGMHY.js} +11 -774
  50. package/dist/chunk-ZNMPGMHY.js.map +1 -0
  51. package/dist/{claude-P3NQR6IJ.js → claude-7GGEWVEM.js} +2 -2
  52. package/dist/{cleanup-6UCPVMFG.js → cleanup-6PVAC4NI.js} +19 -17
  53. package/dist/{cleanup-6UCPVMFG.js.map → cleanup-6PVAC4NI.js.map} +1 -1
  54. package/dist/cli.js +154 -614
  55. package/dist/cli.js.map +1 -1
  56. package/dist/{commit-L3EPY5QG.js → commit-FZR5XDQG.js} +12 -10
  57. package/dist/commit-FZR5XDQG.js.map +1 -0
  58. package/dist/{compile-ZS4HYRX5.js → compile-7ALJHZ4N.js} +6 -6
  59. package/dist/{contribute-ORDDQGSL.js → contribute-5GKLK3BQ.js} +3 -3
  60. package/dist/{dev-server-FYZ2AQIH.js → dev-server-7SMIB7OF.js} +8 -8
  61. package/dist/{feedback-TMBXSCM5.js → feedback-G2GJFN2F.js} +10 -8
  62. package/dist/{feedback-TMBXSCM5.js.map → feedback-G2GJFN2F.js.map} +1 -1
  63. package/dist/{git-ET64COO3.js → git-GTLKAZRJ.js} +3 -3
  64. package/dist/ignite-H2O5Y5A2.js +34 -0
  65. package/dist/ignite-H2O5Y5A2.js.map +1 -0
  66. package/dist/index.d.ts +113 -18
  67. package/dist/index.js +177 -12
  68. package/dist/index.js.map +1 -1
  69. package/dist/{init-GFQ5W7GK.js → init-32YOKXRL.js} +8 -8
  70. package/dist/{issues-T4ZZSPEG.js → issues-4UUAQ5K6.js} +3 -3
  71. package/dist/{lint-6TQXDZ3T.js → lint-AAN2NZWG.js} +6 -6
  72. package/dist/mcp/harness-server.js +140 -0
  73. package/dist/mcp/harness-server.js.map +1 -0
  74. package/dist/mcp/issue-management-server.js +140 -18
  75. package/dist/mcp/issue-management-server.js.map +1 -1
  76. package/dist/{open-5QZGXQRF.js → open-FXWW3VI4.js} +8 -8
  77. package/dist/{plan-U7ZQWLFY.js → plan-RQ5FPIGF.js} +338 -36
  78. package/dist/plan-RQ5FPIGF.js.map +1 -0
  79. package/dist/prompts/CLAUDE.md +2 -2
  80. package/dist/prompts/init-prompt.txt +102 -27
  81. package/dist/prompts/issue-prompt.txt +46 -0
  82. package/dist/prompts/plan-prompt.txt +59 -19
  83. package/dist/prompts/swarm-orchestrator-prompt.txt +107 -80
  84. package/dist/{rebase-DWIB77KV.js → rebase-6NVLX5V7.js} +17 -8
  85. package/dist/rebase-6NVLX5V7.js.map +1 -0
  86. package/dist/{recap-MX63HAKV.js → recap-OMBOKJST.js} +6 -6
  87. package/dist/{run-O3TFNQFC.js → run-BBXLRIZB.js} +8 -8
  88. package/dist/schema/settings.schema.json +36 -2
  89. package/dist/{shell-G6VC2CYR.js → shell-RF7LTND5.js} +5 -5
  90. package/dist/{summary-FWHAX55O.js → summary-WTQZ7XG2.js} +9 -9
  91. package/dist/{test-F7JNJZYP.js → test-SGO6I5Z7.js} +6 -6
  92. package/dist/{test-git-BTAOIUE2.js → test-git-XM4TM65W.js} +3 -3
  93. package/dist/{test-jira-CHYNV33F.js → test-jira-LDTOYFSD.js} +3 -3
  94. package/dist/{test-prefix-Q6TFSU6F.js → test-prefix-GBO37XCN.js} +3 -3
  95. package/dist/{test-webserver-EONCG7E7.js → test-webserver-NZ3JTVLL.js} +5 -5
  96. package/dist/{vscode-VA5X4P25.js → vscode-6XUGHJKL.js} +5 -5
  97. package/package.json +1 -1
  98. package/dist/ClaudeContextManager-QXX6ZFST.js +0 -14
  99. package/dist/ClaudeService-NJNK2SUH.js +0 -13
  100. package/dist/chunk-4WJNIR5O.js.map +0 -1
  101. package/dist/chunk-FB47TIJG.js.map +0 -1
  102. package/dist/chunk-LXLMMXXY.js.map +0 -1
  103. package/dist/chunk-ONQYPICO.js.map +0 -1
  104. package/dist/chunk-UD3WJDIV.js.map +0 -1
  105. package/dist/chunk-UVD4CZKS.js +0 -101
  106. package/dist/chunk-UVD4CZKS.js.map +0 -1
  107. package/dist/chunk-UWGVCXRF.js.map +0 -1
  108. package/dist/chunk-YYAKPQBT.js.map +0 -1
  109. package/dist/chunk-ZHPNZC75.js.map +0 -1
  110. package/dist/commit-L3EPY5QG.js.map +0 -1
  111. package/dist/ignite-CGOV3TD4.js.map +0 -1
  112. package/dist/plan-U7ZQWLFY.js.map +0 -1
  113. package/dist/rebase-DWIB77KV.js.map +0 -1
  114. /package/dist/{BranchNamingService-ECJHBB67.js.map → BranchNamingService-25KSZAEM.js.map} +0 -0
  115. /package/dist/{ClaudeContextManager-QXX6ZFST.js.map → ClaudeContextManager-66GR4BGM.js.map} +0 -0
  116. /package/dist/{ClaudeService-NJNK2SUH.js.map → ClaudeService-7KM5NA5Z.js.map} +0 -0
  117. /package/dist/{LoomLauncher-L64HHS3T.js.map → LoomLauncher-TDLZSYG2.js.map} +0 -0
  118. /package/dist/{PromptTemplateManager-DULSVRRE.js.map → PromptTemplateManager-YOE2SIPG.js.map} +0 -0
  119. /package/dist/{SettingsManager-BQDQA3FK.js.map → SettingsManager-FNKCOZMQ.js.map} +0 -0
  120. /package/dist/{build-5GO3XW26.js.map → build-VHGEMXBA.js.map} +0 -0
  121. /package/dist/{chunk-MNHZB4Z2.js.map → chunk-4FGEGQW4.js.map} +0 -0
  122. /package/dist/{chunk-WY4QBK43.js.map → chunk-63QWFWH3.js.map} +0 -0
  123. /package/dist/{chunk-SF2P22EE.js.map → chunk-C6HNNJIV.js.map} +0 -0
  124. /package/dist/{chunk-5MWV33NN.js.map → chunk-CVCTIDDK.js.map} +0 -0
  125. /package/dist/{chunk-RYWFS37M.js.map → chunk-E6KOWMKA.js.map} +0 -0
  126. /package/dist/{chunk-6EU6TCF6.js.map → chunk-EVPZFV3K.js.map} +0 -0
  127. /package/dist/{chunk-ZEWU5PZK.js.map → chunk-G5V75JD5.js.map} +0 -0
  128. /package/dist/{chunk-VGGST52X.js.map → chunk-I5T677EA.js.map} +0 -0
  129. /package/dist/{chunk-VECNX6VX.js.map → chunk-KIK2ZFAL.js.map} +0 -0
  130. /package/dist/{chunk-ZW2LKWWE.js.map → chunk-KVHIAWVT.js.map} +0 -0
  131. /package/dist/{chunk-3D7WQM7I.js.map → chunk-LLHXQS3C.js.map} +0 -0
  132. /package/dist/{chunk-Y4YZTHZE.js.map → chunk-LUKXJSRI.js.map} +0 -0
  133. /package/dist/{chunk-J5S7DFYC.js.map → chunk-QVLPWNE3.js.map} +0 -0
  134. /package/dist/{chunk-SN3SQCFK.js.map → chunk-S7PZA6IV.js.map} +0 -0
  135. /package/dist/{chunk-JO2LZ6EQ.js.map → chunk-SWSJWA2S.js.map} +0 -0
  136. /package/dist/{chunk-NRSWLOAZ.js.map → chunk-WXIM2WS7.js.map} +0 -0
  137. /package/dist/{claude-P3NQR6IJ.js.map → claude-7GGEWVEM.js.map} +0 -0
  138. /package/dist/{compile-ZS4HYRX5.js.map → compile-7ALJHZ4N.js.map} +0 -0
  139. /package/dist/{contribute-ORDDQGSL.js.map → contribute-5GKLK3BQ.js.map} +0 -0
  140. /package/dist/{dev-server-FYZ2AQIH.js.map → dev-server-7SMIB7OF.js.map} +0 -0
  141. /package/dist/{git-ET64COO3.js.map → git-GTLKAZRJ.js.map} +0 -0
  142. /package/dist/{init-GFQ5W7GK.js.map → init-32YOKXRL.js.map} +0 -0
  143. /package/dist/{issues-T4ZZSPEG.js.map → issues-4UUAQ5K6.js.map} +0 -0
  144. /package/dist/{lint-6TQXDZ3T.js.map → lint-AAN2NZWG.js.map} +0 -0
  145. /package/dist/{open-5QZGXQRF.js.map → open-FXWW3VI4.js.map} +0 -0
  146. /package/dist/{recap-MX63HAKV.js.map → recap-OMBOKJST.js.map} +0 -0
  147. /package/dist/{run-O3TFNQFC.js.map → run-BBXLRIZB.js.map} +0 -0
  148. /package/dist/{shell-G6VC2CYR.js.map → shell-RF7LTND5.js.map} +0 -0
  149. /package/dist/{summary-FWHAX55O.js.map → summary-WTQZ7XG2.js.map} +0 -0
  150. /package/dist/{test-F7JNJZYP.js.map → test-SGO6I5Z7.js.map} +0 -0
  151. /package/dist/{test-git-BTAOIUE2.js.map → test-git-XM4TM65W.js.map} +0 -0
  152. /package/dist/{test-jira-CHYNV33F.js.map → test-jira-LDTOYFSD.js.map} +0 -0
  153. /package/dist/{test-prefix-Q6TFSU6F.js.map → test-prefix-GBO37XCN.js.map} +0 -0
  154. /package/dist/{test-webserver-EONCG7E7.js.map → test-webserver-NZ3JTVLL.js.map} +0 -0
  155. /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-VECNX6VX.js";
7
+ } from "./chunk-KIK2ZFAL.js";
8
8
  import {
9
9
  TelemetryService
10
10
  } from "./chunk-RSYT7MVI.js";
11
- import "./chunk-VGGST52X.js";
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-SF2P22EE.js";
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-ONQYPICO.js";
24
+ } from "./chunk-UR5DGNUO.js";
25
25
  import {
26
26
  PromptTemplateManager
27
- } from "./chunk-4WJNIR5O.js";
27
+ } from "./chunk-UUEW5KWB.js";
28
28
  import {
29
29
  getRepoRoot,
30
30
  isFileGitignored
31
- } from "./chunk-MNHZB4Z2.js";
32
- import "./chunk-YYAKPQBT.js";
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-GFQ5W7GK.js.map
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-MNHZB4Z2.js";
7
+ } from "./chunk-4FGEGQW4.js";
8
8
  import {
9
9
  SettingsManager
10
- } from "./chunk-YYAKPQBT.js";
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-T4ZZSPEG.js.map
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-NRSWLOAZ.js";
4
+ } from "./chunk-WXIM2WS7.js";
5
5
  import "./chunk-WWKOVDWC.js";
6
- import "./chunk-WY4QBK43.js";
7
- import "./chunk-VGGST52X.js";
6
+ import "./chunk-63QWFWH3.js";
7
+ import "./chunk-I5T677EA.js";
8
8
  import "./chunk-YQ57ORTV.js";
9
- import "./chunk-MNHZB4Z2.js";
10
- import "./chunk-YYAKPQBT.js";
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-6TQXDZ3T.js.map
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":"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","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 model overrides for swarm mode. Configure under agents.iloom-swarm-worker.agents.<agent-name>.model to set a different model for phase agents when running inside swarm workers. Fallback chain: swarm-specific agent model > explicit swarm worker model > base agent model. Only meaningful under the iloom-swarm-worker agent entry.")
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
- '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). The iloom-swarm-worker agent supports a nested "agents" sub-record for configuring phase agent models specifically in swarm mode.'
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
- '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). The iloom-swarm-worker agent supports a nested "agents" sub-record for configuring phase agent models specifically in swarm mode.'
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
- return ((_a = settings2 == null ? void 0 : settings2.spin) == null ? void 0 : _a.model) ?? SpinAgentSettingsSchema.parse({}).model;
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: [