openkrak-mcp 1.0.16 → 1.0.18
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/index.js +204 -50
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -242241,6 +242241,66 @@ function computeStats(nodes, edges) {
|
|
|
242241
242241
|
edge_kind_breakdown: edgeBreakdown
|
|
242242
242242
|
};
|
|
242243
242243
|
}
|
|
242244
|
+
var EXTERNAL_PREFIXES = [
|
|
242245
|
+
"node:",
|
|
242246
|
+
"fs/",
|
|
242247
|
+
"path",
|
|
242248
|
+
"crypto",
|
|
242249
|
+
"os",
|
|
242250
|
+
"http",
|
|
242251
|
+
"https",
|
|
242252
|
+
"stream",
|
|
242253
|
+
"util",
|
|
242254
|
+
"events",
|
|
242255
|
+
"buffer",
|
|
242256
|
+
"url",
|
|
242257
|
+
"querystring",
|
|
242258
|
+
"child_process",
|
|
242259
|
+
"cluster",
|
|
242260
|
+
"next/",
|
|
242261
|
+
"react",
|
|
242262
|
+
"react-dom",
|
|
242263
|
+
"react/",
|
|
242264
|
+
"@next/",
|
|
242265
|
+
"@vercel/",
|
|
242266
|
+
"@modelcontextprotocol/",
|
|
242267
|
+
"@typescript-eslint/",
|
|
242268
|
+
"@base-ui/",
|
|
242269
|
+
"hono",
|
|
242270
|
+
"hono/",
|
|
242271
|
+
"zod",
|
|
242272
|
+
"openai",
|
|
242273
|
+
"pino",
|
|
242274
|
+
"pino/",
|
|
242275
|
+
"kysely",
|
|
242276
|
+
"p-retry",
|
|
242277
|
+
"simple-git",
|
|
242278
|
+
"dorchester",
|
|
242279
|
+
"framer-motion",
|
|
242280
|
+
"lucide-react",
|
|
242281
|
+
"mathjs",
|
|
242282
|
+
"katex",
|
|
242283
|
+
"tailwindcss",
|
|
242284
|
+
"typescript",
|
|
242285
|
+
"tsup",
|
|
242286
|
+
"tsx",
|
|
242287
|
+
"vitest",
|
|
242288
|
+
"jest",
|
|
242289
|
+
"payhip",
|
|
242290
|
+
"stripe",
|
|
242291
|
+
"@stripe/"
|
|
242292
|
+
];
|
|
242293
|
+
function isExternalImport(target) {
|
|
242294
|
+
if (!target.includes("/") && !target.startsWith(".")) return true;
|
|
242295
|
+
for (const prefix of EXTERNAL_PREFIXES) {
|
|
242296
|
+
if (target.startsWith(prefix)) return true;
|
|
242297
|
+
}
|
|
242298
|
+
if (target.endsWith(".css") || target.endsWith(".scss") || target.endsWith(".svg")) return true;
|
|
242299
|
+
return false;
|
|
242300
|
+
}
|
|
242301
|
+
function isBarrelFile(filePath) {
|
|
242302
|
+
return filePath.endsWith("/index.ts") || filePath.endsWith("/index.js") || filePath === "index.ts" || filePath === "index.js";
|
|
242303
|
+
}
|
|
242244
242304
|
function detectCycles(graph) {
|
|
242245
242305
|
const adjMap = /* @__PURE__ */ new Map();
|
|
242246
242306
|
for (const edge of graph.edges) {
|
|
@@ -242261,11 +242321,14 @@ function detectCycles(graph) {
|
|
|
242261
242321
|
const visited = /* @__PURE__ */ new Set();
|
|
242262
242322
|
const inStack = /* @__PURE__ */ new Set();
|
|
242263
242323
|
const cyclePaths = [];
|
|
242324
|
+
const MAX_CYCLES = 20;
|
|
242264
242325
|
function dfs(nodeId, path7) {
|
|
242326
|
+
if (cyclePaths.length >= MAX_CYCLES) return;
|
|
242265
242327
|
visited.add(nodeId);
|
|
242266
242328
|
inStack.add(nodeId);
|
|
242267
242329
|
path7.push(nodeId);
|
|
242268
242330
|
for (const neighbor of adjMap.get(nodeId) ?? []) {
|
|
242331
|
+
if (cyclePaths.length >= MAX_CYCLES) break;
|
|
242269
242332
|
if (!visited.has(neighbor)) {
|
|
242270
242333
|
dfs(neighbor, [...path7]);
|
|
242271
242334
|
} else if (inStack.has(neighbor)) {
|
|
@@ -242278,6 +242341,7 @@ function detectCycles(graph) {
|
|
|
242278
242341
|
inStack.delete(nodeId);
|
|
242279
242342
|
}
|
|
242280
242343
|
for (const node of graph.nodes) {
|
|
242344
|
+
if (cyclePaths.length >= MAX_CYCLES) break;
|
|
242281
242345
|
if (!visited.has(node.id)) {
|
|
242282
242346
|
dfs(node.id, []);
|
|
242283
242347
|
}
|
|
@@ -242291,57 +242355,67 @@ function detectDeadCode(graph, fileIndex) {
|
|
|
242291
242355
|
incomingCount.set(edge.to, (incomingCount.get(edge.to) ?? 0) + 1);
|
|
242292
242356
|
}
|
|
242293
242357
|
const testFiles = new Set(fileIndex.filter((f) => f.role === "test").map((f) => f.path));
|
|
242294
|
-
const entryPoints = new Set(
|
|
242295
|
-
|
|
242296
|
-
);
|
|
242358
|
+
const entryPoints = new Set(graph.nodes.filter((n) => n.is_entry_point).map((n) => n.id));
|
|
242359
|
+
const barrelFiles = new Set(fileIndex.filter((f) => isBarrelFile(f.path)).map((f) => f.path));
|
|
242297
242360
|
const findings = [];
|
|
242361
|
+
const MAX_DEAD_CODE_PER_FILE = 3;
|
|
242362
|
+
const deadCountPerFile = /* @__PURE__ */ new Map();
|
|
242298
242363
|
for (const node of graph.nodes) {
|
|
242299
242364
|
if (node.kind === "file") continue;
|
|
242365
|
+
if (testFiles.has(node.file)) continue;
|
|
242366
|
+
if (entryPoints.has(node.id)) continue;
|
|
242367
|
+
if (node.is_exported && barrelFiles.has(node.file)) continue;
|
|
242368
|
+
if (node.is_exported) continue;
|
|
242300
242369
|
const incoming = incomingCount.get(node.id) ?? 0;
|
|
242301
|
-
if (incoming
|
|
242302
|
-
|
|
242303
|
-
|
|
242304
|
-
|
|
242305
|
-
|
|
242306
|
-
|
|
242307
|
-
|
|
242308
|
-
|
|
242309
|
-
|
|
242310
|
-
|
|
242311
|
-
|
|
242312
|
-
|
|
242313
|
-
|
|
242314
|
-
|
|
242315
|
-
|
|
242316
|
-
|
|
242317
|
-
|
|
242370
|
+
if (incoming > 0) continue;
|
|
242371
|
+
const fileCount = deadCountPerFile.get(node.file) ?? 0;
|
|
242372
|
+
if (fileCount >= MAX_DEAD_CODE_PER_FILE) continue;
|
|
242373
|
+
deadCountPerFile.set(node.file, fileCount + 1);
|
|
242374
|
+
findings.push({
|
|
242375
|
+
id: (0, import_crypto2.randomUUID)(),
|
|
242376
|
+
type: "dead_code",
|
|
242377
|
+
severity: "low",
|
|
242378
|
+
file: node.file,
|
|
242379
|
+
line_start: node.line_start,
|
|
242380
|
+
line_end: node.line_end,
|
|
242381
|
+
symbol: node.id,
|
|
242382
|
+
title: `Dead code: ${node.name}`,
|
|
242383
|
+
description: `Symbol '${node.name}' has no incoming references and is not exported or an entry point.`,
|
|
242384
|
+
raw_data: { incoming_edge_count: 0, last_referenced_at: null },
|
|
242385
|
+
source_component: "deepstrike",
|
|
242386
|
+
is_structural: true
|
|
242387
|
+
});
|
|
242318
242388
|
}
|
|
242319
242389
|
return findings;
|
|
242320
242390
|
}
|
|
242321
242391
|
function detectMissingSymbols(graph, knownNodeIds) {
|
|
242322
242392
|
const findings = [];
|
|
242393
|
+
const seen = /* @__PURE__ */ new Set();
|
|
242394
|
+
const MAX_MISSING = 30;
|
|
242323
242395
|
for (const edge of graph.edges) {
|
|
242324
242396
|
if (edge.kind !== "import") continue;
|
|
242325
|
-
if (
|
|
242326
|
-
if (
|
|
242327
|
-
|
|
242328
|
-
|
|
242329
|
-
|
|
242330
|
-
|
|
242331
|
-
|
|
242332
|
-
|
|
242333
|
-
|
|
242334
|
-
|
|
242335
|
-
|
|
242336
|
-
|
|
242337
|
-
|
|
242338
|
-
|
|
242339
|
-
|
|
242340
|
-
|
|
242341
|
-
|
|
242342
|
-
|
|
242343
|
-
}
|
|
242344
|
-
|
|
242397
|
+
if (isExternalImport(edge.to)) continue;
|
|
242398
|
+
if (knownNodeIds.has(edge.to)) continue;
|
|
242399
|
+
if (seen.has(edge.to)) continue;
|
|
242400
|
+
if (findings.length >= MAX_MISSING) break;
|
|
242401
|
+
seen.add(edge.to);
|
|
242402
|
+
findings.push({
|
|
242403
|
+
id: (0, import_crypto2.randomUUID)(),
|
|
242404
|
+
type: "missing_symbol",
|
|
242405
|
+
severity: "medium",
|
|
242406
|
+
file: edge.file,
|
|
242407
|
+
line_start: edge.line,
|
|
242408
|
+
line_end: edge.line,
|
|
242409
|
+
symbol: null,
|
|
242410
|
+
title: `Unresolved import: ${edge.to}`,
|
|
242411
|
+
description: `Import target '${edge.to}' could not be resolved. May indicate a missing file, broken alias, or uninstalled dependency.`,
|
|
242412
|
+
raw_data: {
|
|
242413
|
+
import_statement: edge.to,
|
|
242414
|
+
attempted_resolution: edge.to
|
|
242415
|
+
},
|
|
242416
|
+
source_component: "deepstrike",
|
|
242417
|
+
is_structural: true
|
|
242418
|
+
});
|
|
242345
242419
|
}
|
|
242346
242420
|
return findings;
|
|
242347
242421
|
}
|
|
@@ -242360,7 +242434,7 @@ function buildCycleFindings(cyclePaths, nodes) {
|
|
|
242360
242434
|
line_end: null,
|
|
242361
242435
|
symbol: cycle[0],
|
|
242362
242436
|
title: `Circular dependency (${cycle.length} nodes)`,
|
|
242363
|
-
description: `Cycle: ${cycle.slice(0,
|
|
242437
|
+
description: `Cycle: ${cycle.slice(0, 4).join(" \u2192 ")}${cycle.length > 4 ? " ..." : ""}`,
|
|
242364
242438
|
raw_data: { cycle_path: cycle, cycle_length: cycle.length },
|
|
242365
242439
|
source_component: "deepstrike",
|
|
242366
242440
|
is_structural: true
|
|
@@ -242442,10 +242516,11 @@ function detectSecurityPatterns(filePath, content) {
|
|
|
242442
242516
|
}
|
|
242443
242517
|
async function classifyTopology(repoRoot, allFiles) {
|
|
242444
242518
|
const type = await detectProjectType(repoRoot);
|
|
242519
|
+
const framework = await detectFramework(repoRoot);
|
|
242445
242520
|
const entryPoints = detectEntryPoints(allFiles);
|
|
242446
242521
|
const layers = detectLayers(allFiles);
|
|
242447
242522
|
const modules = detectModules(allFiles);
|
|
242448
|
-
return { type, entry_points: entryPoints, layers, modules };
|
|
242523
|
+
return { type, entry_points: entryPoints, layers, modules, framework };
|
|
242449
242524
|
}
|
|
242450
242525
|
async function detectProjectType(repoRoot) {
|
|
242451
242526
|
try {
|
|
@@ -242455,7 +242530,7 @@ async function detectProjectType(repoRoot) {
|
|
|
242455
242530
|
if (pkg.private === true && Array.isArray(pkg.workspaces)) return "monorepo";
|
|
242456
242531
|
} catch {
|
|
242457
242532
|
}
|
|
242458
|
-
for (const config of ["nx.json", "lerna.json"]) {
|
|
242533
|
+
for (const config of ["nx.json", "lerna.json", "pnpm-workspace.yaml", "turbo.json"]) {
|
|
242459
242534
|
try {
|
|
242460
242535
|
await import_promises3.default.access(import_path4.default.join(repoRoot, config));
|
|
242461
242536
|
return "monorepo";
|
|
@@ -242464,12 +242539,70 @@ async function detectProjectType(repoRoot) {
|
|
|
242464
242539
|
}
|
|
242465
242540
|
return "monolith";
|
|
242466
242541
|
}
|
|
242542
|
+
async function detectFramework(repoRoot) {
|
|
242543
|
+
try {
|
|
242544
|
+
const pkgPath = import_path4.default.join(repoRoot, "package.json");
|
|
242545
|
+
const pkg = JSON.parse(await import_promises3.default.readFile(pkgPath, "utf-8"));
|
|
242546
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
242547
|
+
if (deps["next"]) return `next@${deps["next"].replace(/[\^~]/g, "")}`;
|
|
242548
|
+
if (deps["nuxt"]) return `nuxt@${deps["nuxt"].replace(/[\^~]/g, "")}`;
|
|
242549
|
+
if (deps["@remix-run/react"]) return "remix";
|
|
242550
|
+
if (deps["astro"]) return `astro@${deps["astro"].replace(/[\^~]/g, "")}`;
|
|
242551
|
+
if (deps["svelte"]) return "svelte";
|
|
242552
|
+
if (deps["vue"]) return `vue@${deps["vue"].replace(/[\^~]/g, "")}`;
|
|
242553
|
+
if (deps["react"]) return `react@${deps["react"].replace(/[\^~]/g, "")}`;
|
|
242554
|
+
if (deps["express"]) return `express@${deps["express"].replace(/[\^~]/g, "")}`;
|
|
242555
|
+
if (deps["fastify"]) return `fastify@${deps["fastify"].replace(/[\^~]/g, "")}`;
|
|
242556
|
+
if (deps["hono"]) return `hono@${deps["hono"].replace(/[\^~]/g, "")}`;
|
|
242557
|
+
if (deps["@nestjs/core"]) return "nestjs";
|
|
242558
|
+
if (deps["@angular/core"]) return "angular";
|
|
242559
|
+
} catch {
|
|
242560
|
+
}
|
|
242561
|
+
try {
|
|
242562
|
+
await import_promises3.default.access(import_path4.default.join(repoRoot, "next.config.js"));
|
|
242563
|
+
return "next";
|
|
242564
|
+
} catch {
|
|
242565
|
+
}
|
|
242566
|
+
try {
|
|
242567
|
+
await import_promises3.default.access(import_path4.default.join(repoRoot, "next.config.mjs"));
|
|
242568
|
+
return "next";
|
|
242569
|
+
} catch {
|
|
242570
|
+
}
|
|
242571
|
+
try {
|
|
242572
|
+
await import_promises3.default.access(import_path4.default.join(repoRoot, "next.config.ts"));
|
|
242573
|
+
return "next";
|
|
242574
|
+
} catch {
|
|
242575
|
+
}
|
|
242576
|
+
try {
|
|
242577
|
+
await import_promises3.default.access(import_path4.default.join(repoRoot, "vite.config.ts"));
|
|
242578
|
+
return "vite";
|
|
242579
|
+
} catch {
|
|
242580
|
+
}
|
|
242581
|
+
try {
|
|
242582
|
+
await import_promises3.default.access(import_path4.default.join(repoRoot, "astro.config.mjs"));
|
|
242583
|
+
return "astro";
|
|
242584
|
+
} catch {
|
|
242585
|
+
}
|
|
242586
|
+
return null;
|
|
242587
|
+
}
|
|
242467
242588
|
function detectEntryPoints(files) {
|
|
242468
242589
|
const entries = [];
|
|
242469
242590
|
for (const f of files) {
|
|
242470
|
-
if (/\/(index|main|
|
|
242471
|
-
const type = f.includes("cli") ? "cli" : f.includes("server")
|
|
242591
|
+
if (/\/(index|main|server|cli)\.(ts|js)x?$/.test(f)) {
|
|
242592
|
+
const type = f.includes("cli") ? "cli" : f.includes("server") ? "api" : "main";
|
|
242472
242593
|
entries.push({ path: f, type });
|
|
242594
|
+
continue;
|
|
242595
|
+
}
|
|
242596
|
+
if (/^app\/.*page\.(ts|js)x?$/.test(f) || /^app\/layout\.(ts|js)x?$/.test(f)) {
|
|
242597
|
+
entries.push({ path: f, type: "api" });
|
|
242598
|
+
continue;
|
|
242599
|
+
}
|
|
242600
|
+
if (/^app\/.*route\.(ts|js)x?$/.test(f) || /^pages\/api\//.test(f)) {
|
|
242601
|
+
entries.push({ path: f, type: "api" });
|
|
242602
|
+
continue;
|
|
242603
|
+
}
|
|
242604
|
+
if (/^src\/(main|index|App)\.(ts|js)x?$/.test(f)) {
|
|
242605
|
+
entries.push({ path: f, type: "main" });
|
|
242473
242606
|
}
|
|
242474
242607
|
}
|
|
242475
242608
|
return entries;
|
|
@@ -243265,6 +243398,7 @@ function detectGodObjects(graph, couplingScores, fileRole) {
|
|
|
243265
243398
|
for (const [file, count] of nodeCountByFile) {
|
|
243266
243399
|
if (count <= NODE_COUNT_THRESHOLD) continue;
|
|
243267
243400
|
if (fileRole.get(file) === "test") continue;
|
|
243401
|
+
if (file.endsWith("/index.ts") || file.endsWith("/index.js") || file === "index.ts") continue;
|
|
243268
243402
|
const coupling = couplingScores.get(file);
|
|
243269
243403
|
if (!coupling) continue;
|
|
243270
243404
|
if (coupling.fan_in <= FAN_IN_THRESHOLD && coupling.fan_out <= FAN_OUT_THRESHOLD) continue;
|
|
@@ -243292,7 +243426,8 @@ function aggregateHotspots(params) {
|
|
|
243292
243426
|
cycleFindings,
|
|
243293
243427
|
affectedSymbolsByFile,
|
|
243294
243428
|
cyclePaths = [],
|
|
243295
|
-
graph
|
|
243429
|
+
graph,
|
|
243430
|
+
changeFrequencyMap = /* @__PURE__ */ new Map()
|
|
243296
243431
|
} = params;
|
|
243297
243432
|
const cycleFilesFromPaths = /* @__PURE__ */ new Map();
|
|
243298
243433
|
for (const path7 of cyclePaths) {
|
|
@@ -243344,7 +243479,10 @@ function aggregateHotspots(params) {
|
|
|
243344
243479
|
const isRepository = file.includes("/repositories/") || file.includes("/repository/") || file.toLowerCase().includes("repository");
|
|
243345
243480
|
const isFoundational = isRepository && methodCount >= 3 && (coupling?.fan_in ?? 0) >= 1;
|
|
243346
243481
|
const foundationalBonus = isFoundational ? 0.25 : 0;
|
|
243347
|
-
const
|
|
243482
|
+
const changeFreq = changeFrequencyMap.get(file) ?? 0;
|
|
243483
|
+
const maxChangeFreq = Math.max(1, ...[...changeFrequencyMap.values()]);
|
|
243484
|
+
const changeFreqScore = Math.min(1, changeFreq / maxChangeFreq);
|
|
243485
|
+
const rawScore = couplingScore * 0.35 + complexityScore * 0.15 + methodScore * 0.1 + changeFreqScore * 0.1 + violationPenalty + godObjectBonus + cyclePenalty + foundationalBonus;
|
|
243348
243486
|
const finalScore = Math.min(1, rawScore);
|
|
243349
243487
|
const reasons = [];
|
|
243350
243488
|
if (coupling && couplingScore > 0) {
|
|
@@ -243481,10 +243619,11 @@ function buildArchitectureViolationFindings(violations) {
|
|
|
243481
243619
|
}));
|
|
243482
243620
|
}
|
|
243483
243621
|
var logger3 = createLogger("hotspot_registry");
|
|
243484
|
-
async function runHotspotRegistry(store) {
|
|
243622
|
+
async function runHotspotRegistry(store, opts) {
|
|
243485
243623
|
const start = Date.now();
|
|
243486
243624
|
const errors = [];
|
|
243487
243625
|
const scanId = store.getMeta().scan_id;
|
|
243626
|
+
const changeFrequencyMap = opts?.changeFrequencyMap ?? /* @__PURE__ */ new Map();
|
|
243488
243627
|
const log = logger3.child({ scan_id: scanId });
|
|
243489
243628
|
log.info({ event: "hotspot_registry.start" }, "Hotspot Registry started");
|
|
243490
243629
|
const graph = store.getDependencyGraph();
|
|
@@ -243536,7 +243675,8 @@ async function runHotspotRegistry(store) {
|
|
|
243536
243675
|
cycleFindings,
|
|
243537
243676
|
affectedSymbolsByFile,
|
|
243538
243677
|
cyclePaths: graph.stats.cycle_paths ?? [],
|
|
243539
|
-
graph
|
|
243678
|
+
graph,
|
|
243679
|
+
changeFrequencyMap
|
|
243540
243680
|
});
|
|
243541
243681
|
const couplingFindings = buildCouplingFindings(couplingScores.values(), graph.nodes);
|
|
243542
243682
|
const complexityFindings = buildComplexityFindings(complexityScores);
|
|
@@ -244782,8 +244922,22 @@ async function runPipeline(options) {
|
|
|
244782
244922
|
try {
|
|
244783
244923
|
logger7.info("Step 1/6: DeepStrike");
|
|
244784
244924
|
await runDeepStrike(store, { repoRoot: options.repoPath });
|
|
244925
|
+
const changeFrequencyMap = /* @__PURE__ */ new Map();
|
|
244926
|
+
try {
|
|
244927
|
+
const { execSync } = await import("child_process");
|
|
244928
|
+
const gitLog = execSync("git log --name-only --pretty=format: -- .", {
|
|
244929
|
+
cwd: options.repoPath,
|
|
244930
|
+
encoding: "utf-8",
|
|
244931
|
+
timeout: 8e3
|
|
244932
|
+
});
|
|
244933
|
+
for (const line of gitLog.split("\n")) {
|
|
244934
|
+
const trimmed = line.trim().replace(/\\/g, "/");
|
|
244935
|
+
if (trimmed) changeFrequencyMap.set(trimmed, (changeFrequencyMap.get(trimmed) ?? 0) + 1);
|
|
244936
|
+
}
|
|
244937
|
+
} catch {
|
|
244938
|
+
}
|
|
244785
244939
|
logger7.info("Step 2/6: Hotspot Registry");
|
|
244786
|
-
await runHotspotRegistry(store);
|
|
244940
|
+
await runHotspotRegistry(store, { repoRoot: options.repoPath, changeFrequencyMap });
|
|
244787
244941
|
logger7.info("Step 3/6: Correlation Engine");
|
|
244788
244942
|
await runCorrelationEngine(store);
|
|
244789
244943
|
logger7.info("Step 4/6: Blast Radius Engine");
|