openkrak-mcp 1.1.3 → 1.1.5

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 +132 -52
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -242135,8 +242135,19 @@ function assembleGraph(allNodes, rawEdges, barrelReExports = []) {
242135
242135
  edges.push(edge);
242136
242136
  }
242137
242137
  }
242138
+ const fileClassIndex = /* @__PURE__ */ new Map();
242139
+ const fileBarrelIndex = /* @__PURE__ */ new Map();
242140
+ for (const node of allNodes) {
242141
+ if (node.kind === "class") {
242142
+ const arr = fileClassIndex.get(node.file) ?? [];
242143
+ arr.push(node);
242144
+ fileClassIndex.set(node.file, arr);
242145
+ } else if (node.kind === "variable" && node.name === "default") {
242146
+ fileBarrelIndex.set(node.file, node);
242147
+ }
242148
+ }
242138
242149
  for (const raw of rawEdges) {
242139
- const resolvedTo = resolveToNodeId(raw.to, fileNodeMap, nodeById);
242150
+ const resolvedTo = resolveToNodeId(raw.to, fileNodeMap, nodeById, fileClassIndex, fileBarrelIndex);
242140
242151
  const edgeId = `${raw.from}->${resolvedTo}:${raw.kind}`;
242141
242152
  const edge = {
242142
242153
  id: edgeId,
@@ -242254,21 +242265,18 @@ function assembleGraph(allNodes, rawEdges, barrelReExports = []) {
242254
242265
  const stats = computeStats(allNodes, finalEdges);
242255
242266
  return { nodes: allNodes, edges: finalEdges, stats };
242256
242267
  }
242257
- function resolveToNodeId(to, fileNodeMap, nodeById) {
242268
+ function resolveToNodeId(to, fileNodeMap, _nodeById, fileClassIndex, fileBarrelIndex) {
242258
242269
  const fileNodeId = fileNodeMap.get(to) ?? fileNodeMap.get(to + ".ts") ?? fileNodeMap.get(to + ".tsx") ?? fileNodeMap.get(to.replace(/\.ts$/, ".tsx")) ?? fileNodeMap.get(to.replace(/\.tsx$/, ".ts")) ?? fileNodeMap.get(to + "/index.ts") ?? fileNodeMap.get(to + "/index.tsx");
242259
- if (fileNodeId && nodeById) {
242260
- const filePath = fileNodeId.split("::")[0];
242261
- const barrelNode = [...nodeById.values()].find(
242262
- (n) => n.file === filePath && n.kind === "variable" && n.name === "default"
242263
- );
242264
- if (barrelNode) return barrelNode.id;
242265
- const classNodes = [...nodeById.values()].filter(
242266
- (n) => n.file === filePath && n.kind === "class"
242267
- );
242268
- if (classNodes.length === 1) return classNodes[0].id;
242270
+ if (fileNodeId) {
242271
+ if (fileClassIndex && fileBarrelIndex) {
242272
+ const filePath = fileNodeId.split("::")[0];
242273
+ const barrelNode = fileBarrelIndex.get(filePath);
242274
+ if (barrelNode) return barrelNode.id;
242275
+ const classNodes = fileClassIndex.get(filePath) ?? [];
242276
+ if (classNodes.length === 1) return classNodes[0].id;
242277
+ }
242269
242278
  return fileNodeId;
242270
242279
  }
242271
- if (fileNodeId) return fileNodeId;
242272
242280
  return to;
242273
242281
  }
242274
242282
  function computeStats(nodes, edges) {
@@ -242928,7 +242936,7 @@ async function runDeepStrike(store, opts) {
242928
242936
  allFindings.push(...secFindings);
242929
242937
  try {
242930
242938
  const partialEntry = await buildFileIndexEntry(opts.repoRoot, relPath, content, symbols.length);
242931
- const cyclomaticByFunc = isTypeScriptOrJS(relPath) ? extractCyclomaticComplexity(parseFile(absPath, content, opts.repoRoot).ast, relPath) : /* @__PURE__ */ new Map();
242939
+ const cyclomaticByFunc = isTypeScriptOrJS(relPath) ? extractCyclomaticComplexity(ast, relPath) : /* @__PURE__ */ new Map();
242932
242940
  const entry = {
242933
242941
  ...partialEntry,
242934
242942
  complexity: {
@@ -245241,74 +245249,146 @@ async function handleAnalyzeRepo(args) {
245241
245249
  });
245242
245250
  } catch (e) {
245243
245251
  return {
245244
- content: [{ type: "text", text: `OpenKrak is not available for this repository. Proceed with direct file analysis.
245245
- Reason: ${e instanceof Error ? e.message : String(e)}` }],
245252
+ content: [{ type: "text", text: `OpenKrak unavailable: ${e instanceof Error ? e.message : String(e)}
245253
+ Proceed with direct file analysis.` }],
245246
245254
  isError: false
245247
245255
  };
245248
245256
  }
245249
245257
  if (result.status === "failed") {
245250
245258
  return {
245251
- content: [{ type: "text", text: `OpenKrak could not complete analysis for this path. Proceed without it.
245252
- Detail: ${result.error ?? "unknown"}` }],
245259
+ content: [{ type: "text", text: `Dorchester pipeline failed: ${result.error ?? "unknown"}
245260
+ Proceed without brief.` }],
245253
245261
  isError: false
245254
245262
  };
245255
245263
  }
245256
245264
  const m = result.mahadata;
245257
245265
  const meta = m.meta;
245258
245266
  const repo = m.repository;
245267
+ const topo = m.project_topology;
245268
+ const brief = m.execution_brief;
245269
+ const threat = m.threat_matrix;
245259
245270
  const hotspots = m.hotspots ?? [];
245260
245271
  const blastRadius = m.blast_radius ?? [];
245261
245272
  const findings = m.findings ?? [];
245262
- const brief = m.execution_brief;
245263
- const threat = m.threat_matrix;
245264
- const topology = m.project_topology;
245265
- const totalFiles = Number(repo?.total_files ?? 0);
245266
- const hotspotLimit = totalFiles < 50 ? 5 : totalFiles < 200 ? 8 : 12;
245267
245273
  const ep = brief?.recommended_entry_points ?? [];
245268
245274
  const ctx = brief?.critical_context ?? [];
245269
245275
  const constraints = brief?.constraints ?? [];
245270
245276
  const blockers = threat?.blockers ?? [];
245271
245277
  const warnings = threat?.warnings ?? [];
245278
+ const modules = topo?.modules ?? [];
245279
+ const layers = topo?.layers ?? [];
245280
+ const langs = repo?.languages ?? [];
245281
+ const totalFiles = Number(repo?.total_files ?? 0);
245282
+ const totalLoc = Number(repo?.total_loc ?? 0);
245283
+ const critical = findings.filter((f) => f.severity === "critical");
245284
+ const high = findings.filter((f) => f.severity === "high");
245285
+ const medium = findings.filter((f) => f.severity === "medium");
245286
+ const deadCode = findings.filter((f) => f.type === "dead_code" && !f.noise_suppressed);
245287
+ const unresolved = findings.filter((f) => f.type === "missing_symbol" && !f.noise_suppressed);
245288
+ const cycles = findings.filter((f) => f.type === "circular_dependency");
245289
+ const riskScore = Number(threat?.overall_risk_score ?? 0);
245290
+ const riskBar = "\u2588".repeat(Math.round(riskScore * 20)) + "\u2591".repeat(20 - Math.round(riskScore * 20));
245272
245291
  const lines = [
245273
- `\u2554\u2550\u2550 DORCHESTER SCAN COMPLETE \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
245274
- `\u2551 Repo : ${repo?.name ?? args.path}`,
245275
- `\u2551 Scan ID : ${(meta?.scan_id ?? "?").slice(0, 8)} | Duration: ${result.durationMs}ms`,
245276
- `\u2551 Stats : ${repo?.total_files ?? "?"} files | ${repo?.total_loc ?? "?"} LOC | ${repo?.primary_language ?? "?"} | framework: ${repo?.framework ?? "none"}`,
245277
- `\u2551 Topology : ${topology?.type ?? "?"} \u2014 modules: ${(topology?.modules ?? []).length}`,
245278
- `\u2560\u2550\u2550 OBJECTIVE \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
245292
+ `\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557`,
245293
+ `\u2551 D O R C H E S T E R E N G I N E \u2014 S C A N \u2551`,
245294
+ `\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563`,
245295
+ `\u2551 Repo ${(repo?.name ?? args.path).toString().padEnd(59)}\u2551`,
245296
+ `\u2551 Scan ID ${(meta?.scan_id ?? "?").slice(0, 8).padEnd(59)}\u2551`,
245297
+ `\u2551 Duration ${String(result.durationMs + "ms").padEnd(59)}\u2551`,
245298
+ `\u2560\u2550\u2550 REPOSITORY \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563`,
245299
+ `\u2551 Files : ${totalFiles}`,
245300
+ `\u2551 LOC : ${totalLoc.toLocaleString()}`,
245301
+ `\u2551 Language : ${repo?.primary_language ?? "?"}${langs.length > 1 ? " [" + langs.map((l) => `${l.name} ${l.percentage}%`).join(", ") + "]" : ""}`,
245302
+ `\u2551 Framework : ${repo?.framework ?? "none detected"}`,
245303
+ `\u2551 Topology : ${topo?.type ?? "?"} \u2014 ${modules.length} modules`,
245304
+ ...modules.length > 0 ? modules.map((mod) => `\u2551 \u2022 ${mod.name.padEnd(22)} ${mod.responsibility}`) : [],
245305
+ ...layers.length > 0 ? [
245306
+ `\u2551 Layers :`,
245307
+ ...layers.map((l) => `\u2551 \u2022 ${l.name.padEnd(18)} ${l.paths.length} files`)
245308
+ ] : [],
245309
+ `\u2560\u2550\u2550 OBJECTIVE \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563`,
245279
245310
  `\u2551 ${brief?.objective ?? args.objective ?? "\u2014"}`,
245280
245311
  `\u2551 ${brief?.repository_summary ?? ""}`,
245281
- `\u2560\u2550\u2550 ENTRY POINTS \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
245282
- ...ep.length > 0 ? ep.map((e) => `\u2551 \u2192 ${e.path}${e.symbol ? `::${e.symbol}` : ""} [${e.reason}]`) : [`\u2551 \u2192 none detected`],
245283
- `\u2560\u2550\u2550 CRITICAL CONTEXT \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
245284
- ...ctx.length > 0 ? ctx.map((c) => `\u2551 ${c.key}: ${c.value}`) : [`\u2551 \u2014`],
245285
- `\u2560\u2550\u2550 HOTSPOT REGISTRY (${hotspots.length} files ranked) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
245286
- ...hotspots.slice(0, hotspotLimit).map((h, i) => {
245312
+ `\u2560\u2550\u2550 ENTRY POINTS (${ep.length}) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563`,
245313
+ ...ep.length > 0 ? ep.map((e) => `\u2551 \u2192 ${e.path}${e.symbol ? `::${e.symbol}` : ""}${e.reason ? ` [${e.reason}]` : ""}`) : [`\u2551 none detected`],
245314
+ `\u2560\u2550\u2550 CRITICAL CONTEXT \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563`,
245315
+ ...ctx.length > 0 ? ctx.map((c) => `\u2551 ${c.key}: ${c.value}`) : [`\u2551 \u2014`],
245316
+ ...constraints.length > 0 ? [`\u2551 Constraints: ${constraints.join(" | ")}`] : [],
245317
+ `\u2560\u2550\u2550 HOTSPOT REGISTRY (${hotspots.length} files ranked) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563`,
245318
+ ...hotspots.map((h, i) => {
245287
245319
  const hs = h;
245288
245320
  const reasons = hs.reasons ?? [];
245289
- const bar = "\u2588".repeat(Math.round(Number(hs.score) * 10));
245290
- return `\u2551 ${String(i + 1).padStart(2)}. [${String(hs.risk_level).toUpperCase().padEnd(8)}] ${hs.path}
245291
- \u2551 score:${Number(hs.score).toFixed(3)} ${bar} changes:${hs.change_frequency} flags:${reasons.map((r) => r.type).join(", ")}`;
245321
+ const score = Number(hs.score);
245322
+ const bar = "\u2588".repeat(Math.round(score * 10)) + "\u2591".repeat(10 - Math.round(score * 10));
245323
+ const level = String(hs.risk_level).toUpperCase();
245324
+ const freq = hs.change_frequency !== void 0 ? ` git-chg:${hs.change_frequency}` : "";
245325
+ return [
245326
+ `\u2551 ${String(i + 1).padStart(2)}. [${level.padEnd(8)}] ${hs.path}`,
245327
+ `\u2551 ${bar} ${score.toFixed(3)}${freq}`,
245328
+ `\u2551 flags: ${reasons.map((r) => r.type).join(", ")}`
245329
+ ].join("\n");
245292
245330
  }),
245293
- `\u2560\u2550\u2550 BLAST RADIUS (top ${Math.min(5, blastRadius.length)}) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
245294
- ...blastRadius.slice(0, 5).map((b) => {
245331
+ `\u2560\u2550\u2550 BLAST RADIUS (top ${Math.min(blastRadius.length, 8)}) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563`,
245332
+ ...blastRadius.slice(0, 8).map((b) => {
245295
245333
  const br = b;
245296
- return `\u2551 \u26A1 ${br.trigger_file}
245297
- \u2551 \u2192 ${br.total_affected_files} files | ${br.total_affected_modules} modules | risk:${Number(br.risk_score).toFixed(3)}`;
245334
+ const affected = br.affected_files ?? [];
245335
+ const preview = affected.slice(0, 3).join(", ") + (affected.length > 3 ? ` +${affected.length - 3} more` : "");
245336
+ return [
245337
+ `\u2551 \u26A1 ${br.trigger_file}`,
245338
+ `\u2551 \u2192 ${br.total_affected_files} files ${br.total_affected_modules} modules risk:${Number(br.risk_score).toFixed(3)}`,
245339
+ ...preview ? [`\u2551 cascades: ${preview}`] : []
245340
+ ].join("\n");
245298
245341
  }),
245299
- `\u2560\u2550\u2550 THREAT MATRIX \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
245300
- `\u2551 Overall Risk : ${Number(threat?.overall_risk_score ?? 0).toFixed(3)} | ${threat?.risk_summary ?? ""}`,
245301
- `\u2551 Blockers : ${blockers.length > 0 ? blockers.slice(0, 3).map((b) => b.description ?? b).join(" | ") : "none"}`,
245302
- `\u2551 Warnings : ${warnings.length} total${warnings.length > 0 ? " \u2014 " + warnings.slice(0, 3).map((w) => w.description ?? w).join(" | ") : ""}`,
245303
- `\u2551 Findings : ${findings.length} total`,
245304
- `\u2560\u2550\u2550 CONSTRAINTS \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
245305
- `\u2551 ${constraints.length > 0 ? constraints.join(" | ") : "none"}`,
245306
- `\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
245342
+ `\u2560\u2550\u2550 THREAT MATRIX \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563`,
245343
+ `\u2551 Overall Risk : ${riskScore.toFixed(3)} [${riskBar}]`,
245344
+ `\u2551 Architecture : ${threat?.architecture_risk ?? "\u2014"}`,
245345
+ `\u2551 Reliability : ${threat?.reliability_risk ?? "\u2014"}`,
245346
+ `\u2551 Maintainability: ${threat?.maintainability_risk ?? "\u2014"}`,
245347
+ `\u2551 Summary : ${threat?.risk_summary ?? "\u2014"}`,
245348
+ ...blockers.length > 0 ? [
245349
+ `\u2551 \u2500\u2500 Blockers (${blockers.length}) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`,
245350
+ ...blockers.map((b) => `\u2551 \u2716 ${b.description ?? b.type ?? JSON.stringify(b)}`)
245351
+ ] : [`\u2551 Blockers: none`],
245352
+ ...warnings.length > 0 ? [
245353
+ `\u2551 \u2500\u2500 Warnings (${warnings.length}) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`,
245354
+ ...warnings.map((w) => `\u2551 \u25B2 ${w.description ?? w.type ?? JSON.stringify(w)}`)
245355
+ ] : [`\u2551 Warnings: none`],
245356
+ `\u2560\u2550\u2550 FINDINGS BREAKDOWN (${findings.length} total) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563`,
245357
+ `\u2551 Critical : ${critical.length}`,
245358
+ `\u2551 High : ${high.length}`,
245359
+ `\u2551 Medium : ${medium.length}`,
245360
+ `\u2551 Cycles : ${cycles.length}${cycles.length > 0 ? " \u2190 fix before refactor" : ""}`,
245361
+ `\u2551 Unresolved imports : ${unresolved.length}${unresolved.length > 0 ? " \u2014 " + unresolved.slice(0, 5).map((f) => f.file ?? "?").join(", ") : ""}`,
245362
+ `\u2551 Dead code (genuine): ${deadCode.length}${deadCode.length > 0 ? " \u2014 " + deadCode.slice(0, 5).map((f) => f.symbol ?? f.file ?? "?").join(", ") : ""}`,
245363
+ ...critical.length > 0 ? [
245364
+ `\u2551 \u2500\u2500 Critical findings \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`,
245365
+ ...critical.slice(0, 10).map((f) => {
245366
+ const fi = f;
245367
+ return `\u2551 \u2716 [${String(fi.type ?? "").padEnd(20)}] ${fi.file ?? "?"}:${fi.line_start ?? "?"} ${fi.message ?? ""}`;
245368
+ })
245369
+ ] : [],
245370
+ ...high.length > 0 ? [
245371
+ `\u2551 \u2500\u2500 High findings \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`,
245372
+ ...high.slice(0, 8).map((f) => {
245373
+ const fi = f;
245374
+ return `\u2551 \u25B2 [${String(fi.type ?? "").padEnd(20)}] ${fi.file ?? "?"}:${fi.line_start ?? "?"} ${fi.message ?? ""}`;
245375
+ })
245376
+ ] : [],
245377
+ `\u2560\u2550\u2550 WHAT TO DO \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563`,
245378
+ ...blockers.length > 0 ? [`\u2551 1. Resolve blockers before anything else.`] : [],
245379
+ ...cycles.length > 0 ? [`\u2551 ${blockers.length > 0 ? "2" : "1"}. Break ${cycles.length} circular import cycle(s) \u2014 replace barrel imports with direct file paths.`] : [],
245380
+ ...hotspots.filter((h) => h.risk_level === "critical").length > 0 ? [
245381
+ `\u2551 ${(blockers.length > 0 ? 1 : 0) + (cycles.length > 0 ? 1 : 0) + 1}. Refactor critical hotspots:`,
245382
+ ...hotspots.filter((h) => h.risk_level === "critical").slice(0, 3).map(
245383
+ (h) => `\u2551 \u2192 ${h.path}`
245384
+ )
245385
+ ] : [],
245386
+ `\u2551 Audit lib/utils or shared utilities with 5+ dependents before touching.`,
245387
+ `\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D`,
245307
245388
  ``,
245308
- `Dorchester has pre-mapped this repository. Proceed with full context. Do not read source files unless specific line-level detail is required.`
245389
+ `Dorchester pre-mapped this repository in ${result.durationMs}ms. Proceed from this brief. Do not read source files unless specific line-level detail is required by the user.`
245309
245390
  ];
245310
245391
  const output = lines.join("\n");
245311
- const totalLoc = Number(repo?.total_loc ?? 0);
245312
245392
  const saved = estimateTokensSaved(totalLoc, totalFiles, output);
245313
245393
  trackTokens(saved, "analyze_repo");
245314
245394
  return { content: [{ type: "text", text: output }] };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openkrak-mcp",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "OpenKrak MCP Server - AI coding intelligence via Dorchester engine",
5
5
  "mcpName": "io.github.FrnzJulianBergmann/openkrak",
6
6
  "type": "commonjs",