openkrak-mcp 1.0.17 → 1.0.19

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 +208 -9
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -242516,10 +242516,11 @@ function detectSecurityPatterns(filePath, content) {
242516
242516
  }
242517
242517
  async function classifyTopology(repoRoot, allFiles) {
242518
242518
  const type = await detectProjectType(repoRoot);
242519
+ const framework = await detectFramework(repoRoot, allFiles);
242519
242520
  const entryPoints = detectEntryPoints(allFiles);
242520
242521
  const layers = detectLayers(allFiles);
242521
242522
  const modules = detectModules(allFiles);
242522
- return { type, entry_points: entryPoints, layers, modules };
242523
+ return { type, entry_points: entryPoints, layers, modules, framework };
242523
242524
  }
242524
242525
  async function detectProjectType(repoRoot) {
242525
242526
  try {
@@ -242529,7 +242530,7 @@ async function detectProjectType(repoRoot) {
242529
242530
  if (pkg.private === true && Array.isArray(pkg.workspaces)) return "monorepo";
242530
242531
  } catch {
242531
242532
  }
242532
- for (const config of ["nx.json", "lerna.json"]) {
242533
+ for (const config of ["nx.json", "lerna.json", "pnpm-workspace.yaml", "turbo.json"]) {
242533
242534
  try {
242534
242535
  await import_promises3.default.access(import_path4.default.join(repoRoot, config));
242535
242536
  return "monorepo";
@@ -242538,12 +242539,111 @@ async function detectProjectType(repoRoot) {
242538
242539
  }
242539
242540
  return "monolith";
242540
242541
  }
242542
+ async function readFrameworkFromPkg(pkgPath) {
242543
+ try {
242544
+ const pkg = JSON.parse(await import_promises3.default.readFile(pkgPath, "utf-8"));
242545
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
242546
+ if (deps["next"]) return `next@${deps["next"].replace(/[\^~]/g, "")}`;
242547
+ if (deps["nuxt"]) return `nuxt@${deps["nuxt"].replace(/[\^~]/g, "")}`;
242548
+ if (deps["@remix-run/react"]) return "remix";
242549
+ if (deps["astro"]) return `astro@${deps["astro"].replace(/[\^~]/g, "")}`;
242550
+ if (deps["svelte"]) return "svelte";
242551
+ if (deps["vue"]) return `vue@${deps["vue"].replace(/[\^~]/g, "")}`;
242552
+ if (deps["react"]) return `react@${deps["react"].replace(/[\^~]/g, "")}`;
242553
+ if (deps["express"]) return `express@${deps["express"].replace(/[\^~]/g, "")}`;
242554
+ if (deps["fastify"]) return `fastify@${deps["fastify"].replace(/[\^~]/g, "")}`;
242555
+ if (deps["hono"]) return `hono@${deps["hono"].replace(/[\^~]/g, "")}`;
242556
+ if (deps["@nestjs/core"]) return "nestjs";
242557
+ if (deps["@angular/core"]) return "angular";
242558
+ } catch {
242559
+ }
242560
+ return null;
242561
+ }
242562
+ async function findSubPackageRoots(repoRoot, allFiles) {
242563
+ const candidates = /* @__PURE__ */ new Set();
242564
+ for (const f of allFiles) {
242565
+ const parts = f.split("/");
242566
+ if (parts.length >= 1) candidates.add(parts[0]);
242567
+ if (parts.length >= 2) candidates.add(`${parts[0]}/${parts[1]}`);
242568
+ }
242569
+ const roots = [];
242570
+ for (const candidate of candidates) {
242571
+ const pkgPath = import_path4.default.join(repoRoot, candidate, "package.json");
242572
+ try {
242573
+ await import_promises3.default.access(pkgPath);
242574
+ roots.push(candidate);
242575
+ } catch {
242576
+ }
242577
+ }
242578
+ return roots;
242579
+ }
242580
+ async function detectFramework(repoRoot, allFiles) {
242581
+ const rootFramework = await readFrameworkFromPkg(import_path4.default.join(repoRoot, "package.json"));
242582
+ if (rootFramework) return rootFramework;
242583
+ const configChecks = [
242584
+ ["next.config.js", "next"],
242585
+ ["next.config.mjs", "next"],
242586
+ ["next.config.ts", "next"],
242587
+ ["vite.config.ts", "vite"],
242588
+ ["astro.config.mjs", "astro"]
242589
+ ];
242590
+ for (const [cfg, name] of configChecks) {
242591
+ try {
242592
+ await import_promises3.default.access(import_path4.default.join(repoRoot, cfg));
242593
+ return name;
242594
+ } catch {
242595
+ }
242596
+ }
242597
+ if (allFiles && allFiles.length > 0) {
242598
+ const subRoots = await findSubPackageRoots(repoRoot, allFiles);
242599
+ const detected = [];
242600
+ for (const subRoot of subRoots) {
242601
+ const fw = await readFrameworkFromPkg(import_path4.default.join(repoRoot, subRoot, "package.json"));
242602
+ if (fw && !detected.includes(fw)) detected.push(fw);
242603
+ }
242604
+ for (const subRoot of subRoots) {
242605
+ for (const [cfg, name] of configChecks) {
242606
+ try {
242607
+ await import_promises3.default.access(import_path4.default.join(repoRoot, subRoot, cfg));
242608
+ if (!detected.includes(name)) detected.push(name);
242609
+ } catch {
242610
+ }
242611
+ }
242612
+ }
242613
+ if (detected.length === 1) return detected[0];
242614
+ if (detected.length > 1) {
242615
+ const prefixCount = /* @__PURE__ */ new Map();
242616
+ for (const d of detected) {
242617
+ const prefix = d.split("@")[0];
242618
+ prefixCount.set(prefix, (prefixCount.get(prefix) ?? 0) + 1);
242619
+ }
242620
+ return [...prefixCount.entries()].map(([fw, count]) => count > 1 ? `${fw} (\xD7${count})` : fw).join(", ");
242621
+ }
242622
+ }
242623
+ return null;
242624
+ }
242541
242625
  function detectEntryPoints(files) {
242542
242626
  const entries = [];
242543
242627
  for (const f of files) {
242544
- if (/\/(index|main|app|server|cli)\.(ts|js)x?$/.test(f)) {
242545
- const type = f.includes("cli") ? "cli" : f.includes("server") || f.includes("app") ? "api" : "main";
242628
+ if (/\/(index|main|server|cli)\.(ts|js)x?$/.test(f)) {
242629
+ const type = f.includes("cli") ? "cli" : f.includes("server") ? "api" : "main";
242546
242630
  entries.push({ path: f, type });
242631
+ continue;
242632
+ }
242633
+ if (/(?:^|\/)app\/.*page\.(ts|js)x?$/.test(f) || /(?:^|\/)app\/layout\.(ts|js)x?$/.test(f)) {
242634
+ entries.push({ path: f, type: "api" });
242635
+ continue;
242636
+ }
242637
+ if (/(?:^|\/)app\/.*(?:loading|error|not-found)\.(ts|js)x?$/.test(f)) {
242638
+ entries.push({ path: f, type: "api" });
242639
+ continue;
242640
+ }
242641
+ if (/(?:^|\/)app\/.*route\.(ts|js)x?$/.test(f) || /(?:^|\/)pages\/api\//.test(f)) {
242642
+ entries.push({ path: f, type: "api" });
242643
+ continue;
242644
+ }
242645
+ if (/^src\/(main|index|App)\.(ts|js)x?$/.test(f)) {
242646
+ entries.push({ path: f, type: "main" });
242547
242647
  }
242548
242648
  }
242549
242649
  return entries;
@@ -242975,6 +243075,73 @@ function buildImpactChains(candidates) {
242975
243075
  function describeMechanism(root, related, depth) {
242976
243076
  return `${root.type} in ${root.file} propagates via ${depth} dependency hop(s) to ${related.type} in ${related.file}`;
242977
243077
  }
243078
+ var CROSS_BUNDLE_SAFE_EXPORTS = /* @__PURE__ */ new Set([
243079
+ // action_contracts
243080
+ "ActionType",
243081
+ "PlannedAction",
243082
+ "ShellCommandSpec",
243083
+ "TEST_COMMAND_WHITELIST",
243084
+ "SENSITIVE_KEY_RE",
243085
+ "DESTRUCTIVE_RE",
243086
+ // orchestrator / plan
243087
+ "OrchestratorPlan",
243088
+ "OrchestratorStep",
243089
+ // contracts / store
243090
+ "MahadataStore",
243091
+ "InMemoryMahadataStore",
243092
+ "Mahadata",
243093
+ "TokenCounter",
243094
+ "LLMClientConfig",
243095
+ // handlers exposed via MCP tool layer
243096
+ "handleAnalyzeRepo",
243097
+ "handleBlastRadius",
243098
+ "handleGetHotspots",
243099
+ "handleGetMahadata",
243100
+ // shared utilities
243101
+ "calculateRiskScore",
243102
+ "assembleGraph",
243103
+ "classifyTopology",
243104
+ "resolveTaskDependencies",
243105
+ // constants / prompts
243106
+ "SYSTEM_PROMPT",
243107
+ "JSON_TAG_RE",
243108
+ // Next.js / framework reserved exports — always safe
243109
+ "metadata",
243110
+ "nextConfig",
243111
+ "geistSans",
243112
+ "geistMono",
243113
+ "default"
243114
+ // default exports are always externally consumable
243115
+ ]);
243116
+ var SAFE_EXPORT_PREFIXES = [
243117
+ "handle",
243118
+ // MCP handlers
243119
+ "create",
243120
+ // factory functions
243121
+ "build",
243122
+ // builder functions
243123
+ "run"
243124
+ // runner functions exposed to orchestrator
243125
+ ];
243126
+ var CROSS_BUNDLE_BARREL_PATTERNS = [
243127
+ /\/contracts\/index\.ts$/,
243128
+ /\/action_contracts\/index\.ts$/,
243129
+ /\/action_layer\/index\.ts$/,
243130
+ /\/shared\/index\.ts$/,
243131
+ /\/shared\/logger\/index\.ts$/,
243132
+ /\/orchestrator\/index\.ts$/
243133
+ ];
243134
+ function isCrossBundleSafeSymbol(symbolName, filePath) {
243135
+ if (CROSS_BUNDLE_SAFE_EXPORTS.has(symbolName)) return true;
243136
+ for (const prefix of SAFE_EXPORT_PREFIXES) {
243137
+ if (symbolName.startsWith(prefix)) return true;
243138
+ }
243139
+ if (symbolName === "logger" || symbolName.startsWith("logger")) return true;
243140
+ for (const pattern of CROSS_BUNDLE_BARREL_PATTERNS) {
243141
+ if (pattern.test(filePath)) return true;
243142
+ }
243143
+ return false;
243144
+ }
242978
243145
  function tagNoise(findings, chains) {
242979
243146
  const suppressionReasons = /* @__PURE__ */ new Map();
242980
243147
  const severityOrder = {
@@ -242984,6 +243151,17 @@ function tagNoise(findings, chains) {
242984
243151
  low: 1,
242985
243152
  info: 0
242986
243153
  };
243154
+ for (const f of findings) {
243155
+ if (f.type !== "dead_code") continue;
243156
+ if (suppressionReasons.has(f.id)) continue;
243157
+ const symbolName = f.symbol ? f.symbol.split("::")[1] ?? f.symbol : "";
243158
+ if (isCrossBundleSafeSymbol(symbolName, f.file)) {
243159
+ suppressionReasons.set(
243160
+ f.id,
243161
+ `Cross-bundle false positive: '${symbolName}' is a known exported interface consumed outside this scan boundary.`
243162
+ );
243163
+ }
243164
+ }
242987
243165
  const groupedByFileType = /* @__PURE__ */ new Map();
242988
243166
  for (const f of findings) {
242989
243167
  const key = `${f.file}::${f.type}`;
@@ -243339,6 +243517,7 @@ function detectGodObjects(graph, couplingScores, fileRole) {
243339
243517
  for (const [file, count] of nodeCountByFile) {
243340
243518
  if (count <= NODE_COUNT_THRESHOLD) continue;
243341
243519
  if (fileRole.get(file) === "test") continue;
243520
+ if (file.endsWith("/index.ts") || file.endsWith("/index.js") || file === "index.ts") continue;
243342
243521
  const coupling = couplingScores.get(file);
243343
243522
  if (!coupling) continue;
243344
243523
  if (coupling.fan_in <= FAN_IN_THRESHOLD && coupling.fan_out <= FAN_OUT_THRESHOLD) continue;
@@ -243366,7 +243545,8 @@ function aggregateHotspots(params) {
243366
243545
  cycleFindings,
243367
243546
  affectedSymbolsByFile,
243368
243547
  cyclePaths = [],
243369
- graph
243548
+ graph,
243549
+ changeFrequencyMap = /* @__PURE__ */ new Map()
243370
243550
  } = params;
243371
243551
  const cycleFilesFromPaths = /* @__PURE__ */ new Map();
243372
243552
  for (const path7 of cyclePaths) {
@@ -243418,7 +243598,10 @@ function aggregateHotspots(params) {
243418
243598
  const isRepository = file.includes("/repositories/") || file.includes("/repository/") || file.toLowerCase().includes("repository");
243419
243599
  const isFoundational = isRepository && methodCount >= 3 && (coupling?.fan_in ?? 0) >= 1;
243420
243600
  const foundationalBonus = isFoundational ? 0.25 : 0;
243421
- const rawScore = couplingScore * 0.4 + complexityScore * 0.15 + methodScore * 0.1 + violationPenalty + godObjectBonus + cyclePenalty + foundationalBonus;
243601
+ const changeFreq = changeFrequencyMap.get(file) ?? 0;
243602
+ const maxChangeFreq = Math.max(1, ...[...changeFrequencyMap.values()]);
243603
+ const changeFreqScore = Math.min(1, changeFreq / maxChangeFreq);
243604
+ const rawScore = couplingScore * 0.35 + complexityScore * 0.15 + methodScore * 0.1 + changeFreqScore * 0.1 + violationPenalty + godObjectBonus + cyclePenalty + foundationalBonus;
243422
243605
  const finalScore = Math.min(1, rawScore);
243423
243606
  const reasons = [];
243424
243607
  if (coupling && couplingScore > 0) {
@@ -243555,10 +243738,11 @@ function buildArchitectureViolationFindings(violations) {
243555
243738
  }));
243556
243739
  }
243557
243740
  var logger3 = createLogger("hotspot_registry");
243558
- async function runHotspotRegistry(store) {
243741
+ async function runHotspotRegistry(store, opts) {
243559
243742
  const start = Date.now();
243560
243743
  const errors = [];
243561
243744
  const scanId = store.getMeta().scan_id;
243745
+ const changeFrequencyMap = opts?.changeFrequencyMap ?? /* @__PURE__ */ new Map();
243562
243746
  const log = logger3.child({ scan_id: scanId });
243563
243747
  log.info({ event: "hotspot_registry.start" }, "Hotspot Registry started");
243564
243748
  const graph = store.getDependencyGraph();
@@ -243610,7 +243794,8 @@ async function runHotspotRegistry(store) {
243610
243794
  cycleFindings,
243611
243795
  affectedSymbolsByFile,
243612
243796
  cyclePaths: graph.stats.cycle_paths ?? [],
243613
- graph
243797
+ graph,
243798
+ changeFrequencyMap
243614
243799
  });
243615
243800
  const couplingFindings = buildCouplingFindings(couplingScores.values(), graph.nodes);
243616
243801
  const complexityFindings = buildComplexityFindings(complexityScores);
@@ -244856,8 +245041,22 @@ async function runPipeline(options) {
244856
245041
  try {
244857
245042
  logger7.info("Step 1/6: DeepStrike");
244858
245043
  await runDeepStrike(store, { repoRoot: options.repoPath });
245044
+ const changeFrequencyMap = /* @__PURE__ */ new Map();
245045
+ try {
245046
+ const { execSync } = await import("child_process");
245047
+ const gitLog = execSync("git log --name-only --pretty=format: -- .", {
245048
+ cwd: options.repoPath,
245049
+ encoding: "utf-8",
245050
+ timeout: 8e3
245051
+ });
245052
+ for (const line of gitLog.split("\n")) {
245053
+ const trimmed = line.trim().replace(/\\/g, "/");
245054
+ if (trimmed) changeFrequencyMap.set(trimmed, (changeFrequencyMap.get(trimmed) ?? 0) + 1);
245055
+ }
245056
+ } catch {
245057
+ }
244859
245058
  logger7.info("Step 2/6: Hotspot Registry");
244860
- await runHotspotRegistry(store);
245059
+ await runHotspotRegistry(store, { repoRoot: options.repoPath, changeFrequencyMap });
244861
245060
  logger7.info("Step 3/6: Correlation Engine");
244862
245061
  await runCorrelationEngine(store);
244863
245062
  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.17",
3
+ "version": "1.0.19",
4
4
  "description": "OpenKrak MCP Server - AI coding intelligence via Dorchester engine",
5
5
  "mcpName": "io.github.FrnzJulianBergmann/openkrak",
6
6
  "type": "commonjs",