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.
- package/dist/index.js +208 -9
- 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|
|
|
242545
|
-
const type = f.includes("cli") ? "cli" : f.includes("server")
|
|
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
|
|
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");
|