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.
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.
|
|
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 (
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
190
|
-
|
|
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) =>
|
|
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
|
-
|
|
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" : ""}.
|
|
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
|
};
|
package/dist/server.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
-
getConfig,
|
|
3
2
|
loadAuth
|
|
4
|
-
} from "./chunk-
|
|
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-
|
|
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.
|
|
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
|
|