crukx-mcp 0.1.7 → 0.1.8

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.
@@ -1,10 +1,12 @@
1
1
  import {
2
- CONFIG_PATH,
3
2
  clearAuth,
4
3
  isTokenExpired,
5
4
  loadAuth,
6
5
  saveAuth
7
- } from "./chunk-SC34H3N5.js";
6
+ } from "./chunk-NPZHH5C5.js";
7
+ import {
8
+ CONFIG_PATH
9
+ } from "./chunk-QY33WYZF.js";
8
10
  export {
9
11
  CONFIG_PATH,
10
12
  clearAuth,
package/dist/bin/cli.js CHANGED
@@ -59,7 +59,7 @@ import { fileURLToPath } from "url";
59
59
  import { join as join2, dirname as pathDirname } from "path";
60
60
  var __dirname = pathDirname(fileURLToPath(import.meta.url));
61
61
  var program = new Command();
62
- program.name("crukx").description("Crukx CLI \u2014 reliability control plane for autonomous software engineering").version("0.1.7");
62
+ program.name("crukx").description("Crukx CLI \u2014 reliability control plane for autonomous software engineering").version("0.1.8");
63
63
  program.command("login").description("Authenticate with Crukx via browser").option("--api-url <url>", "Crukx gateway URL", getConfig().CRUKX_API_URL).action(async (opts) => {
64
64
  process.stderr.write("Opening browser to complete login\u2026\n");
65
65
  let startData;
@@ -140,63 +140,146 @@ Expires: ${auth.expires_at}
140
140
  API: ${auth.api_url}
141
141
  `);
142
142
  });
143
- program.command("install").description("Auto-install Crukx into all detected MCP-compatible clients").option("--dry-run", "Show what would be changed without writing").action(async (opts) => {
143
+ program.command("install").description("Auto-install Crukx into all detected MCP-compatible clients").option("--dry-run", "Show what would be changed without writing").option("--workspace", "Also write to .vscode/mcp.json in the current working directory").action(async (opts) => {
144
144
  const { existsSync: existsSync3, readFileSync: readFileSync3, writeFileSync: writeFileSync2 } = await import("fs");
145
145
  const { homedir: homedir2 } = await import("os");
146
146
  const home = homedir2();
147
+ const cwd = process.cwd();
147
148
  const SERVER_ENTRY = { command: "crukx", args: ["mcp"] };
148
- function injectMcpServers(raw, key) {
149
+ function injectMcpServers(raw, key = "crukx") {
149
150
  const cfg = raw ? JSON.parse(raw) : {};
150
151
  cfg.mcpServers = { ...cfg.mcpServers ?? {}, [key]: SERVER_ENTRY };
151
152
  return JSON.stringify(cfg, null, 2);
152
153
  }
154
+ function injectVSCode(raw) {
155
+ const cfg = raw ? JSON.parse(raw) : {};
156
+ cfg.servers = { ...cfg.servers ?? {}, crukx: { type: "stdio", ...SERVER_ENTRY } };
157
+ return JSON.stringify(cfg, null, 2);
158
+ }
153
159
  const clients = [
160
+ // ── VS Code global (GitHub Copilot) ──────────────────────────────
154
161
  {
155
- name: "VS Code (GitHub Copilot)",
162
+ name: "VS Code \u2014 global (~/.vscode/mcp.json)",
156
163
  configPath: join2(home, ".vscode", "mcp.json"),
157
- detect: () => existsSync3(join2(home, ".vscode")) || existsSync3("/usr/share/code") || existsSync3("/Applications/Visual Studio Code.app"),
164
+ detect: () => existsSync3(join2(home, ".vscode")) || existsSync3("/usr/share/code") || existsSync3("/usr/bin/code") || existsSync3("/snap/bin/code") || existsSync3("/Applications/Visual Studio Code.app") || existsSync3("C:\\Users\\" + (process.env.USERNAME ?? "") + "\\AppData\\Local\\Programs\\Microsoft VS Code\\Code.exe"),
165
+ inject: injectVSCode
166
+ },
167
+ // ── VS Code workspace (current directory) ────────────────────────
168
+ {
169
+ name: "VS Code \u2014 workspace (.vscode/mcp.json)",
170
+ configPath: join2(cwd, ".vscode", "mcp.json"),
171
+ detect: () => opts.workspace === true || existsSync3(join2(cwd, ".vscode")) || existsSync3(join2(cwd, ".git")),
172
+ // any git repo gets workspace config
173
+ inject: injectVSCode
174
+ },
175
+ // ── VS Code settings.json (fallback for older Copilot versions) ──
176
+ {
177
+ name: "VS Code \u2014 settings.json (mcp servers)",
178
+ configPath: process.platform === "win32" ? join2(home, "AppData", "Roaming", "Code", "User", "settings.json") : process.platform === "darwin" ? join2(home, "Library", "Application Support", "Code", "User", "settings.json") : join2(home, ".config", "Code", "User", "settings.json"),
179
+ detect: () => existsSync3(process.platform === "win32" ? join2(home, "AppData", "Roaming", "Code", "User") : process.platform === "darwin" ? join2(home, "Library", "Application Support", "Code", "User") : join2(home, ".config", "Code", "User")),
158
180
  inject: (raw) => {
159
181
  const cfg = raw ? JSON.parse(raw) : {};
160
- cfg.servers = { ...cfg.servers ?? {}, crukx: { type: "stdio", ...SERVER_ENTRY } };
182
+ cfg["mcp.servers"] = { ...cfg["mcp.servers"] ?? {}, crukx: { type: "stdio", ...SERVER_ENTRY } };
161
183
  return JSON.stringify(cfg, null, 2);
162
184
  }
163
185
  },
186
+ // ── Cursor ───────────────────────────────────────────────────────
164
187
  {
165
188
  name: "Cursor",
166
189
  configPath: join2(home, ".cursor", "mcp.json"),
167
- detect: () => existsSync3(join2(home, ".cursor")) || existsSync3("/Applications/Cursor.app"),
168
- inject: (raw) => injectMcpServers(raw, "crukx")
190
+ detect: () => existsSync3(join2(home, ".cursor")) || existsSync3("/Applications/Cursor.app") || existsSync3(join2(home, "AppData", "Local", "Programs", "cursor", "Cursor.exe")),
191
+ inject: injectMcpServers
169
192
  },
193
+ // ── Claude Desktop ───────────────────────────────────────────────
170
194
  {
171
195
  name: "Claude Desktop",
172
- configPath: process.platform === "darwin" ? join2(home, "Library", "Application Support", "Claude", "claude_desktop_config.json") : join2(home, "AppData", "Roaming", "Claude", "claude_desktop_config.json"),
173
- detect: () => existsSync3("/Applications/Claude.app") || existsSync3(join2(home, "Library", "Application Support", "Claude")) || existsSync3(join2(home, "AppData", "Local", "AnthropicClaude")),
174
- inject: (raw) => injectMcpServers(raw, "crukx")
196
+ configPath: process.platform === "darwin" ? join2(home, "Library", "Application Support", "Claude", "claude_desktop_config.json") : process.platform === "win32" ? join2(home, "AppData", "Roaming", "Claude", "claude_desktop_config.json") : join2(home, ".config", "Claude", "claude_desktop_config.json"),
197
+ detect: () => existsSync3("/Applications/Claude.app") || existsSync3(join2(home, "Library", "Application Support", "Claude")) || existsSync3(join2(home, "AppData", "Local", "AnthropicClaude")) || existsSync3(join2(home, ".config", "Claude")),
198
+ inject: injectMcpServers
175
199
  },
200
+ // ── Kiro ─────────────────────────────────────────────────────────
176
201
  {
177
202
  name: "Kiro",
178
203
  configPath: join2(home, ".kiro", "settings", "mcp.json"),
179
204
  detect: () => existsSync3(join2(home, ".kiro")),
180
- inject: (raw) => injectMcpServers(raw, "crukx")
205
+ inject: injectMcpServers
181
206
  },
207
+ // ── opencode ─────────────────────────────────────────────────────
182
208
  {
183
209
  name: "opencode",
184
210
  configPath: join2(home, ".config", "opencode", "config.json"),
185
211
  detect: () => existsSync3(join2(home, ".config", "opencode")),
186
212
  inject: (raw) => {
187
213
  const cfg = raw ? JSON.parse(raw) : {};
188
- cfg.mcp = {
189
- ...cfg.mcp ?? {},
190
- crukx: { type: "local", command: ["crukx", "mcp"], enabled: true }
214
+ cfg.mcp = { ...cfg.mcp ?? {}, crukx: { type: "local", command: ["crukx", "mcp"], enabled: true } };
215
+ return JSON.stringify(cfg, null, 2);
216
+ }
217
+ },
218
+ // ── OpenAI Codex CLI ─────────────────────────────────────────────
219
+ {
220
+ name: "OpenAI Codex CLI",
221
+ configPath: join2(home, ".codex", "config.json"),
222
+ detect: () => existsSync3(join2(home, ".codex")) || existsSync3(join2(home, ".config", "codex")),
223
+ inject: injectMcpServers
224
+ },
225
+ // ── Windsurf (Codeium) ───────────────────────────────────────────
226
+ {
227
+ name: "Windsurf",
228
+ configPath: join2(home, ".codeium", "windsurf", "mcp_config.json"),
229
+ detect: () => existsSync3(join2(home, ".codeium")) || existsSync3("/Applications/Windsurf.app") || existsSync3(join2(home, "AppData", "Local", "Programs", "Windsurf", "Windsurf.exe")),
230
+ inject: injectMcpServers
231
+ },
232
+ // ── Zed ──────────────────────────────────────────────────────────
233
+ {
234
+ name: "Zed",
235
+ configPath: process.platform === "darwin" ? join2(home, "Library", "Application Support", "Zed", "settings.json") : join2(home, ".config", "zed", "settings.json"),
236
+ detect: () => existsSync3("/Applications/Zed.app") || existsSync3(join2(home, ".config", "zed")) || existsSync3(join2(home, "Library", "Application Support", "Zed")),
237
+ inject: (raw) => {
238
+ const cfg = raw ? JSON.parse(raw) : {};
239
+ cfg.context_servers = {
240
+ ...cfg.context_servers ?? {},
241
+ crukx: { command: { path: "crukx", args: ["mcp"] } }
191
242
  };
192
243
  return JSON.stringify(cfg, null, 2);
193
244
  }
245
+ },
246
+ // ── Continue (VS Code / JetBrains extension) ─────────────────────
247
+ {
248
+ name: "Continue",
249
+ configPath: join2(home, ".continue", "config.json"),
250
+ detect: () => existsSync3(join2(home, ".continue")),
251
+ inject: (raw) => {
252
+ const cfg = raw ? JSON.parse(raw) : {};
253
+ const existing = Array.isArray(cfg.mcpServers) ? cfg.mcpServers : [];
254
+ if (!existing.some((s) => s.name === "crukx")) {
255
+ existing.push({ name: "crukx", ...SERVER_ENTRY });
256
+ }
257
+ cfg.mcpServers = existing;
258
+ return JSON.stringify(cfg, null, 2);
259
+ }
260
+ },
261
+ // ── Cline (VS Code extension) ─────────────────────────────────────
262
+ {
263
+ name: "Cline",
264
+ configPath: process.platform === "win32" ? join2(home, "AppData", "Roaming", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json") : process.platform === "darwin" ? join2(home, "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json") : join2(home, ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
265
+ detect: () => existsSync3(process.platform === "win32" ? join2(home, "AppData", "Roaming", "Code", "User", "globalStorage", "saoudrizwan.claude-dev") : process.platform === "darwin" ? join2(home, "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev") : join2(home, ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev")),
266
+ inject: (raw) => {
267
+ const cfg = raw ? JSON.parse(raw) : { mcpServers: {} };
268
+ cfg.mcpServers = { ...cfg.mcpServers ?? {}, crukx: { ...SERVER_ENTRY, disabled: false, autoApprove: [] } };
269
+ return JSON.stringify(cfg, null, 2);
270
+ }
194
271
  }
195
272
  ];
196
- const detected = clients.filter((c) => c.detect());
273
+ const detected = clients.filter((c) => {
274
+ try {
275
+ return c.detect();
276
+ } catch {
277
+ return false;
278
+ }
279
+ });
197
280
  if (detected.length === 0) {
198
281
  process.stdout.write(
199
- "No supported MCP clients detected.\n\nSupported: VS Code, Cursor, Claude Desktop, Kiro, opencode\n\nManual setup: https://crukx.dev/docs/mcp\n"
282
+ "No supported MCP clients detected.\n\nSupported: VS Code, Cursor, Claude Desktop, Kiro, opencode, Codex CLI, Windsurf, Zed, Continue, Cline\n\nRun with --workspace to force-write .vscode/mcp.json in the current directory.\nManual setup: https://crukx.dev/docs/mcp\n"
200
283
  );
201
284
  return;
202
285
  }
@@ -208,7 +291,8 @@ program.command("install").description("Auto-install Crukx into all detected MCP
208
291
  const raw = existsSync3(client.configPath) ? readFileSync3(client.configPath, "utf-8") : null;
209
292
  try {
210
293
  const parsed = raw ? JSON.parse(raw) : {};
211
- if (parsed?.mcpServers?.crukx || parsed?.servers?.crukx || parsed?.mcp?.crukx) {
294
+ const alreadyIn = parsed?.mcpServers?.crukx || parsed?.servers?.crukx || parsed?.mcp?.crukx || parsed?.["mcp.servers"]?.crukx || parsed?.context_servers?.crukx || Array.isArray(parsed?.mcpServers) && parsed.mcpServers.some((s) => s.name === "crukx");
295
+ if (alreadyIn) {
212
296
  process.stdout.write(` \u2705 ${client.name} \u2014 already configured
213
297
  `);
214
298
  continue;
@@ -222,7 +306,7 @@ program.command("install").description("Auto-install Crukx into all detected MCP
222
306
  } else {
223
307
  mkdirSync2(dirname2(client.configPath), { recursive: true });
224
308
  writeFileSync2(client.configPath, newContent, "utf-8");
225
- process.stdout.write(` \u2705 ${client.name} \u2014 configured
309
+ process.stdout.write(` \u2705 ${client.name} \u2014 configured (${client.configPath})
226
310
  `);
227
311
  installed++;
228
312
  }
@@ -232,7 +316,9 @@ program.command("install").description("Auto-install Crukx into all detected MCP
232
316
  process.stdout.write("Dry run complete. Remove --dry-run to apply.\n");
233
317
  } else if (installed > 0) {
234
318
  process.stdout.write(
235
- `Installed in ${installed} client${installed > 1 ? "s" : ""}. Restart your editor, then ask:
319
+ `Installed in ${installed} client${installed > 1 ? "s" : ""}.
320
+
321
+ Restart your editor, then ask your AI agent:
236
322
  "Run a Crukx audit on this file"
237
323
  `
238
324
  );
@@ -0,0 +1,33 @@
1
+ import {
2
+ CONFIG_PATH
3
+ } from "./chunk-QY33WYZF.js";
4
+
5
+ // src/auth/index.ts
6
+ import { mkdirSync, writeFileSync, readFileSync, existsSync } from "fs";
7
+ import { dirname } from "path";
8
+ function saveAuth(auth) {
9
+ mkdirSync(dirname(CONFIG_PATH), { recursive: true });
10
+ const existing = loadAuth() ?? {};
11
+ writeFileSync(CONFIG_PATH, JSON.stringify({ ...existing, ...auth }, null, 2));
12
+ }
13
+ function loadAuth() {
14
+ if (!existsSync(CONFIG_PATH)) return null;
15
+ try {
16
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
17
+ } catch {
18
+ return null;
19
+ }
20
+ }
21
+ function clearAuth() {
22
+ if (existsSync(CONFIG_PATH)) writeFileSync(CONFIG_PATH, "{}");
23
+ }
24
+ function isTokenExpired(auth) {
25
+ return new Date(auth.expires_at) <= new Date(Date.now() + 6e4);
26
+ }
27
+
28
+ export {
29
+ saveAuth,
30
+ loadAuth,
31
+ clearAuth,
32
+ isTokenExpired
33
+ };
@@ -1,7 +1,3 @@
1
- // src/auth/index.ts
2
- import { mkdirSync, writeFileSync, readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
3
- import { dirname } from "path";
4
-
5
1
  // src/config/index.ts
6
2
  import "dotenv/config";
7
3
  import { z } from "zod";
@@ -28,32 +24,7 @@ function getConfig() {
28
24
  }
29
25
  var CONFIG_PATH = join(homedir(), ".crukx", "config.json");
30
26
 
31
- // src/auth/index.ts
32
- function saveAuth(auth) {
33
- mkdirSync(dirname(CONFIG_PATH), { recursive: true });
34
- const existing = loadAuth() ?? {};
35
- writeFileSync(CONFIG_PATH, JSON.stringify({ ...existing, ...auth }, null, 2));
36
- }
37
- function loadAuth() {
38
- if (!existsSync2(CONFIG_PATH)) return null;
39
- try {
40
- return JSON.parse(readFileSync2(CONFIG_PATH, "utf-8"));
41
- } catch {
42
- return null;
43
- }
44
- }
45
- function clearAuth() {
46
- if (existsSync2(CONFIG_PATH)) writeFileSync(CONFIG_PATH, "{}");
47
- }
48
- function isTokenExpired(auth) {
49
- return new Date(auth.expires_at) <= new Date(Date.now() + 6e4);
50
- }
51
-
52
27
  export {
53
28
  getConfig,
54
- CONFIG_PATH,
55
- saveAuth,
56
- loadAuth,
57
- clearAuth,
58
- isTokenExpired
29
+ CONFIG_PATH
59
30
  };
@@ -0,0 +1,8 @@
1
+ import {
2
+ CONFIG_PATH,
3
+ getConfig
4
+ } from "./chunk-QY33WYZF.js";
5
+ export {
6
+ CONFIG_PATH,
7
+ getConfig
8
+ };
package/dist/server.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import {
2
- getConfig,
3
2
  loadAuth
4
- } from "./chunk-SC34H3N5.js";
3
+ } from "./chunk-NPZHH5C5.js";
4
+ import {
5
+ getConfig
6
+ } from "./chunk-QY33WYZF.js";
5
7
 
6
8
  // src/server.ts
7
9
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -375,6 +377,150 @@ The code looks clean \u2014 no specific remediation needed.`;
375
377
  }
376
378
  }
377
379
  );
380
+ server2.tool(
381
+ "self_heal",
382
+ [
383
+ "Autonomous self-healing loop: audit code \u2192 generate fixes using your LLM \u2192 re-audit to validate improvement.",
384
+ "",
385
+ "Inspired by the upskill generate\u2192evaluate\u2192refine pattern:",
386
+ " 1. Run a full Crukx audit on the code",
387
+ " 2. For each critical/high finding, generate a concrete fix using your configured LLM provider",
388
+ " 3. Apply all fixes to produce healed code",
389
+ " 4. Re-audit the healed code to validate the reliability score improved",
390
+ " 5. Return the fixed code + before/after score comparison",
391
+ "",
392
+ "Use this tool when:",
393
+ '- The user says "fix this", "heal this code", "auto-fix the issues"',
394
+ "- swarm_audit or security_scan found critical/high issues and the user wants them resolved",
395
+ "- You want to validate that a fix actually improves the reliability score",
396
+ "",
397
+ "Returns: healed code, score delta (before \u2192 after), and a summary of what was fixed."
398
+ ].join("\n"),
399
+ {
400
+ code: z2.string().describe(
401
+ "The code to heal. Paste the full file or snippet with issues."
402
+ ),
403
+ provider_url: z2.string().optional().describe(
404
+ 'OpenAI-compatible API base URL of your LLM provider. Defaults to OpenRouter. Examples: "https://openrouter.ai/api/v1", "http://localhost:11434/v1" (Ollama), "https://api.openai.com/v1".'
405
+ ),
406
+ provider_key: z2.string().optional().describe(
407
+ "API key for your LLM provider. If omitted, uses the Crukx gateway key."
408
+ ),
409
+ model: z2.string().optional().describe(
410
+ 'Model to use for generating fixes. Examples: "google/gemini-flash-1.5", "gpt-4o-mini", "llama3.2". Defaults to "google/gemini-flash-1.5".'
411
+ ),
412
+ max_iterations: z2.number().int().min(1).max(3).optional().describe(
413
+ "How many heal\u2192re-audit cycles to run (1\u20133). Default: 1. Use 2\u20133 for stubborn issues."
414
+ )
415
+ },
416
+ async ({ code, provider_url, provider_key, model, max_iterations }) => {
417
+ try {
418
+ const auth = (await import("./auth-AY7JI6KG.js")).loadAuth();
419
+ const cfg = (await import("./config-4D4N6QUQ.js")).getConfig();
420
+ const apiBase = (provider_url ?? "https://openrouter.ai/api/v1").replace(/\/$/, "");
421
+ const apiKey = provider_key ?? auth?.access_token ?? cfg.CRUKX_ACCESS_TOKEN ?? "";
422
+ const llmModel = model ?? "google/gemini-flash-1.5";
423
+ const iterations = max_iterations ?? 1;
424
+ const initialAudit = await swarmAudit({ diff: code });
425
+ const initialScore = initialAudit.summary.reliabilityScore;
426
+ const blocking = initialAudit.findings.filter(
427
+ (f) => f.severity === "critical" || f.severity === "high"
428
+ );
429
+ if (blocking.length === 0) {
430
+ return {
431
+ content: [{
432
+ type: "text",
433
+ text: [
434
+ `# Self-Heal Result`,
435
+ ``,
436
+ `**No critical or high issues found** \u2014 nothing to heal.`,
437
+ `**Score:** ${initialScore}/100 (Grade ${grade(initialScore)})`,
438
+ ``,
439
+ `The code is already clean. Run \`swarm_audit\` for full details.`
440
+ ].join("\n")
441
+ }]
442
+ };
443
+ }
444
+ let currentCode = code;
445
+ let currentScore = initialScore;
446
+ const healLog = [];
447
+ for (let i = 0; i < iterations; i++) {
448
+ const findingsList = blocking.slice(0, 10).map(
449
+ (f, idx) => `${idx + 1}. [${f.severity.toUpperCase()}] ${f.rule} at line ${f.line}: ${f.message}
450
+ Fix: ${f.suggestion ?? "apply best practice"}`
451
+ ).join("\n");
452
+ const healPrompt = `You are a senior engineer performing automated code healing.
453
+
454
+ The following code has reliability/security issues that must be fixed:
455
+
456
+ ISSUES TO FIX:
457
+ ${findingsList}
458
+
459
+ CODE:
460
+ \`\`\`
461
+ ${currentCode.slice(0, 1e4)}
462
+ \`\`\`
463
+
464
+ Return ONLY the complete fixed code with all issues resolved. No explanation, no markdown fences, just the raw fixed code.
465
+ Preserve all existing functionality. Make minimal changes \u2014 only fix the listed issues.`;
466
+ const llmRes = await fetch(`${apiBase}/chat/completions`, {
467
+ method: "POST",
468
+ headers: {
469
+ "Authorization": `Bearer ${apiKey}`,
470
+ "Content-Type": "application/json",
471
+ "HTTP-Referer": "https://crukx.dev",
472
+ "X-Title": "Crukx Self-Heal"
473
+ },
474
+ body: JSON.stringify({
475
+ model: llmModel,
476
+ messages: [{ role: "user", content: healPrompt }],
477
+ temperature: 0.1,
478
+ max_tokens: 8192
479
+ }),
480
+ signal: AbortSignal.timeout(6e4)
481
+ });
482
+ if (!llmRes.ok) {
483
+ const err = await llmRes.text();
484
+ throw new Error(`LLM provider error ${llmRes.status}: ${err.slice(0, 200)}`);
485
+ }
486
+ const llmData = await llmRes.json();
487
+ const healedCode = llmData.choices?.[0]?.message?.content?.trim() ?? "";
488
+ if (!healedCode) throw new Error("LLM returned empty response");
489
+ currentCode = healedCode.replace(/^```[\w]*\n?/, "").replace(/\n?```$/, "");
490
+ const reAudit = await swarmAudit({ diff: currentCode });
491
+ const newScore = reAudit.summary.reliabilityScore;
492
+ const delta = newScore - currentScore;
493
+ healLog.push(
494
+ `**Iteration ${i + 1}:** ${currentScore} \u2192 ${newScore} (${delta >= 0 ? "+" : ""}${delta}) \u2014 ${reAudit.summary.criticalCount} critical, ${reAudit.summary.highCount} high remaining`
495
+ );
496
+ currentScore = newScore;
497
+ if (reAudit.summary.criticalCount === 0 && reAudit.summary.highCount === 0) break;
498
+ }
499
+ const totalDelta = currentScore - initialScore;
500
+ const lines = [
501
+ `# Self-Heal Complete`,
502
+ ``,
503
+ `**Score:** ${initialScore}/100 \u2192 ${currentScore}/100 (${totalDelta >= 0 ? "+" : ""}${totalDelta})`,
504
+ `**Gate:** ${gate(currentScore, 0, 0)}`,
505
+ `**Model used:** ${llmModel}`,
506
+ ``,
507
+ `## Heal Log`,
508
+ ...healLog,
509
+ ``,
510
+ `## Fixed Code`,
511
+ `\`\`\``,
512
+ currentCode,
513
+ `\`\`\``
514
+ ];
515
+ return { content: [{ type: "text", text: lines.join("\n") }] };
516
+ } catch (err) {
517
+ return {
518
+ content: [{ type: "text", text: `\u274C Self-heal failed: ${err instanceof Error ? err.message : String(err)}` }],
519
+ isError: true
520
+ };
521
+ }
522
+ }
523
+ );
378
524
  server2.tool(
379
525
  "crukx_health",
380
526
  [
@@ -391,7 +537,7 @@ The code looks clean \u2014 no specific remediation needed.`;
391
537
  async () => {
392
538
  try {
393
539
  const result = await healthCheck();
394
- const auth = (await import("./auth-J6323WQJ.js")).loadAuth();
540
+ const auth = (await import("./auth-AY7JI6KG.js")).loadAuth();
395
541
  const text = [
396
542
  `# Crukx Gateway Status`,
397
543
  ``,
@@ -424,7 +570,7 @@ Run \`crukx login\` to re-authenticate.`
424
570
  // src/server.ts
425
571
  var server = new McpServer({
426
572
  name: "crukx",
427
- version: "0.1.3"
573
+ version: "0.1.8"
428
574
  }, {
429
575
  instructions: `You have access to Crukx \u2014 a 7-agent AI reliability engine for code quality, security, and correctness.
430
576
 
@@ -457,6 +603,10 @@ Use this when the user references a previous audit run.
457
603
  **crukx_health** \u2014 Verify gateway connectivity and auth.
458
604
  Use this if tools are returning errors or the user asks about connection status.
459
605
 
606
+ **self_heal** \u2014 Autonomous fix loop: audit \u2192 generate fixes with your LLM \u2192 re-audit to validate improvement.
607
+ Use this when the user says "fix this", "heal this code", or wants issues auto-resolved.
608
+ Pass the code and optionally your provider URL/key/model. Returns fixed code + before/after score.
609
+
460
610
  ## When to use which tool
461
611
 
462
612
  | Situation | Tool |
@@ -465,6 +615,7 @@ Use this if tools are returning errors or the user asks about connection status.
465
615
  | "Is this code secure?" | security_scan |
466
616
  | "Quick score before merge" | reliability_score |
467
617
  | "How do I fix this?" | suggest_fix |
618
+ | "Fix it for me / auto-heal" | self_heal |
468
619
  | "Show me audit #abc" | report_fetch |
469
620
  | "Is Crukx connected?" | crukx_health |
470
621
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crukx-mcp",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Crukx MCP server — reliability control plane for autonomous software engineering",
5
5
  "type": "module",
6
6
  "main": "./dist/server.js",