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.
Files changed (2) hide show
  1. package/dist/index.js +204 -50
  2. 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
- graph.nodes.filter((n) => n.is_entry_point).map((n) => n.id)
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 === 0 && !entryPoints.has(node.id) && !testFiles.has(node.file)) {
242302
- const isExported = node.is_exported;
242303
- findings.push({
242304
- id: (0, import_crypto2.randomUUID)(),
242305
- type: "dead_code",
242306
- severity: isExported ? "low" : "medium",
242307
- file: node.file,
242308
- line_start: node.line_start,
242309
- line_end: node.line_end,
242310
- symbol: node.id,
242311
- title: `Dead code: ${node.name}`,
242312
- description: `Symbol '${node.name}' has no incoming references and is not an entry point.`,
242313
- raw_data: { incoming_edge_count: 0, last_referenced_at: null },
242314
- source_component: "deepstrike",
242315
- is_structural: true
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 (!edge.to.includes("/") && !edge.to.startsWith(".")) continue;
242326
- if (!knownNodeIds.has(edge.to)) {
242327
- findings.push({
242328
- id: (0, import_crypto2.randomUUID)(),
242329
- type: "missing_symbol",
242330
- severity: "high",
242331
- file: edge.file,
242332
- line_start: edge.line,
242333
- line_end: edge.line,
242334
- symbol: null,
242335
- title: `Unresolved import: ${edge.to}`,
242336
- description: `Import target '${edge.to}' could not be resolved in the dependency graph.`,
242337
- raw_data: {
242338
- import_statement: edge.to,
242339
- attempted_resolution: edge.to
242340
- },
242341
- source_component: "deepstrike",
242342
- is_structural: true
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, 3).join(" \u2192 ")}${cycle.length > 3 ? " ..." : ""}`,
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|app|server|cli)\.(ts|js)x?$/.test(f)) {
242471
- const type = f.includes("cli") ? "cli" : f.includes("server") || f.includes("app") ? "api" : "main";
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 rawScore = couplingScore * 0.4 + complexityScore * 0.15 + methodScore * 0.1 + violationPenalty + godObjectBonus + cyclePenalty + foundationalBonus;
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");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openkrak-mcp",
3
- "version": "1.0.16",
3
+ "version": "1.0.18",
4
4
  "description": "OpenKrak MCP Server - AI coding intelligence via Dorchester engine",
5
5
  "mcpName": "io.github.FrnzJulianBergmann/openkrak",
6
6
  "type": "commonjs",