openkrak-mcp 1.0.12 → 1.0.13

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 +214 -124
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -244859,150 +244859,244 @@ function estimateTokensSaved(totalLoc, totalFiles, mahadataOutput) {
244859
244859
 
244860
244860
  // src/tools/analyze_repo.ts
244861
244861
  async function handleAnalyzeRepo(args) {
244862
- const result = await runPipeline({
244863
- repoPath: args.path,
244864
- objective: args.objective ?? "Analyze repository"
244865
- });
244862
+ let result;
244863
+ try {
244864
+ result = await runPipeline({
244865
+ repoPath: args.path,
244866
+ objective: args.objective ?? "Full repository analysis"
244867
+ });
244868
+ } catch (e) {
244869
+ return {
244870
+ content: [{ type: "text", text: `OpenKrak is not available for this repository. Proceed with direct file analysis.
244871
+ Reason: ${e instanceof Error ? e.message : String(e)}` }],
244872
+ isError: false
244873
+ };
244874
+ }
244866
244875
  if (result.status === "failed") {
244867
244876
  return {
244868
- content: [{ type: "text", text: `Analysis failed: ${result.error}` }],
244869
- isError: true
244877
+ content: [{ type: "text", text: `OpenKrak could not complete analysis for this path. Proceed without it.
244878
+ Detail: ${result.error ?? "unknown"}` }],
244879
+ isError: false
244870
244880
  };
244871
244881
  }
244872
- const mahadata = result.mahadata;
244873
- const meta = mahadata.meta;
244874
- const repo = mahadata.repository;
244875
- const hotspots = mahadata.hotspots;
244876
- const findings = mahadata.findings;
244877
- const brief = mahadata.execution_brief;
244878
- const summary = [
244879
- `# OpenKrak Analysis \u2014 ${repo?.name ?? args.path}`,
244880
- `Status: ${result.status} (${result.durationMs}ms)`,
244881
- `Files: ${repo?.total_files ?? "?"} | LOC: ${repo?.total_loc ?? "?"}`,
244882
- `Language: ${repo?.primary_language ?? "?"} | Framework: ${repo?.framework ?? "none"}`,
244883
- ``,
244884
- `## Hotspots (${hotspots?.length ?? 0} files)`,
244885
- ...hotspots?.slice(0, 5).map((h) => {
244882
+ const m = result.mahadata;
244883
+ const meta = m.meta;
244884
+ const repo = m.repository;
244885
+ const hotspots = m.hotspots ?? [];
244886
+ const blastRadius = m.blast_radius ?? [];
244887
+ const findings = m.findings ?? [];
244888
+ const brief = m.execution_brief;
244889
+ const threat = m.threat_matrix;
244890
+ const topology = m.project_topology;
244891
+ const totalFiles = Number(repo?.total_files ?? 0);
244892
+ const hotspotLimit = totalFiles < 50 ? 5 : totalFiles < 200 ? 8 : 12;
244893
+ const ep = brief?.recommended_entry_points ?? [];
244894
+ const ctx = brief?.critical_context ?? [];
244895
+ const constraints = brief?.constraints ?? [];
244896
+ const blockers = threat?.blockers ?? [];
244897
+ const warnings = threat?.warnings ?? [];
244898
+ const lines = [
244899
+ `\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`,
244900
+ `\u2551 Repo : ${repo?.name ?? args.path}`,
244901
+ `\u2551 Scan ID : ${(meta?.scan_id ?? "?").slice(0, 8)} | Duration: ${result.durationMs}ms`,
244902
+ `\u2551 Stats : ${repo?.total_files ?? "?"} files | ${repo?.total_loc ?? "?"} LOC | ${repo?.primary_language ?? "?"} | framework: ${repo?.framework ?? "none"}`,
244903
+ `\u2551 Topology : ${topology?.type ?? "?"} \u2014 modules: ${(topology?.modules ?? []).length}`,
244904
+ `\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`,
244905
+ `\u2551 ${brief?.objective ?? args.objective ?? "\u2014"}`,
244906
+ `\u2551 ${brief?.repository_summary ?? ""}`,
244907
+ `\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`,
244908
+ ...ep.length > 0 ? ep.map((e) => `\u2551 \u2192 ${e.path}${e.symbol ? `::${e.symbol}` : ""} [${e.reason}]`) : [`\u2551 \u2192 none detected`],
244909
+ `\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`,
244910
+ ...ctx.length > 0 ? ctx.map((c) => `\u2551 ${c.key}: ${c.value}`) : [`\u2551 \u2014`],
244911
+ `\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`,
244912
+ ...hotspots.slice(0, hotspotLimit).map((h, i) => {
244886
244913
  const hs = h;
244887
- return `- ${hs.path} [${hs.risk_level}] score: ${Number(hs.score).toFixed(2)}`;
244888
- }) ?? [],
244889
- ``,
244890
- `## Findings (${findings?.length ?? 0} total)`,
244891
- ``,
244892
- `## Execution Brief`,
244893
- brief?.objective ? `Objective: ${brief.objective}` : "",
244894
- brief?.repository_summary ? `Summary: ${brief.repository_summary}` : "",
244914
+ const reasons = hs.reasons ?? [];
244915
+ const bar = "\u2588".repeat(Math.round(Number(hs.score) * 10));
244916
+ return `\u2551 ${String(i + 1).padStart(2)}. [${String(hs.risk_level).toUpperCase().padEnd(8)}] ${hs.path}
244917
+ \u2551 score:${Number(hs.score).toFixed(3)} ${bar} changes:${hs.change_frequency} flags:${reasons.map((r) => r.type).join(", ")}`;
244918
+ }),
244919
+ `\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`,
244920
+ ...blastRadius.slice(0, 5).map((b) => {
244921
+ const br = b;
244922
+ return `\u2551 \u26A1 ${br.trigger_file}
244923
+ \u2551 \u2192 ${br.total_affected_files} files | ${br.total_affected_modules} modules | risk:${Number(br.risk_score).toFixed(3)}`;
244924
+ }),
244925
+ `\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`,
244926
+ `\u2551 Overall Risk : ${Number(threat?.overall_risk_score ?? 0).toFixed(3)} | ${threat?.risk_summary ?? ""}`,
244927
+ `\u2551 Blockers : ${blockers.length > 0 ? blockers.slice(0, 3).map((b) => b.description ?? b).join(" | ") : "none"}`,
244928
+ `\u2551 Warnings : ${warnings.length} total${warnings.length > 0 ? " \u2014 " + warnings.slice(0, 3).map((w) => w.description ?? w).join(" | ") : ""}`,
244929
+ `\u2551 Findings : ${findings.length} total`,
244930
+ `\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`,
244931
+ `\u2551 ${constraints.length > 0 ? constraints.join(" | ") : "none"}`,
244932
+ `\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`,
244895
244933
  ``,
244896
- `Scan ID: ${meta?.scan_id ?? "?"}`
244897
- ].filter(Boolean).join("\n");
244934
+ `Dorchester has pre-mapped this repository. Proceed with full context. Do not read source files unless specific line-level detail is required.`
244935
+ ];
244936
+ const output = lines.join("\n");
244898
244937
  const totalLoc = Number(repo?.total_loc ?? 0);
244899
- const totalFiles = Number(repo?.total_files ?? 0);
244900
- const saved = estimateTokensSaved(totalLoc, totalFiles, summary);
244938
+ const saved = estimateTokensSaved(totalLoc, totalFiles, output);
244901
244939
  trackTokens(saved, "analyze_repo");
244902
- return { content: [{ type: "text", text: summary }] };
244940
+ return { content: [{ type: "text", text: output }] };
244903
244941
  }
244904
244942
 
244905
244943
  // src/tools/get_hotspots.ts
244906
244944
  async function handleGetHotspots(args) {
244907
- const result = await runPipeline({
244908
- repoPath: args.path,
244909
- objective: "Identify hotspots"
244910
- });
244945
+ let result;
244946
+ try {
244947
+ result = await runPipeline({
244948
+ repoPath: args.path,
244949
+ objective: "Identify hotspots"
244950
+ });
244951
+ } catch (e) {
244952
+ return {
244953
+ content: [{ type: "text", text: `OpenKrak is not available for this repository. Proceed with direct file analysis.
244954
+ Reason: ${e instanceof Error ? e.message : String(e)}` }],
244955
+ isError: false
244956
+ };
244957
+ }
244911
244958
  if (result.status === "failed") {
244912
244959
  return {
244913
- content: [{ type: "text", text: `Analysis failed: ${result.error}` }],
244914
- isError: true
244960
+ content: [{ type: "text", text: `OpenKrak could not complete hotspot analysis. Proceed without it.
244961
+ Detail: ${result.error ?? "unknown"}` }],
244962
+ isError: false
244915
244963
  };
244916
244964
  }
244917
244965
  const mahadata = result.mahadata;
244918
244966
  const hotspots = mahadata.hotspots ?? [];
244967
+ const repo = mahadata.repository;
244919
244968
  if (hotspots.length === 0) {
244920
244969
  return {
244921
- content: [{ type: "text", text: "No hotspots detected." }]
244970
+ content: [{ type: "text", text: `OpenKrak: no hotspots detected in ${repo?.name ?? args.path}. Repository appears low-risk.` }]
244922
244971
  };
244923
244972
  }
244924
244973
  const lines = [
244925
- `# Hotspots \u2014 ${hotspots.length} files ranked by risk`,
244926
- "",
244974
+ `\u2554\u2550\u2550 HOTSPOT REGISTRY \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
244975
+ `\u2551 ${repo?.name ?? args.path} | ${hotspots.length} files ranked by composite risk score`,
244976
+ `\u2551 duration: ${result.durationMs}ms`,
244977
+ `\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`,
244927
244978
  ...hotspots.map((h, i) => {
244928
244979
  const hs = h;
244929
244980
  const reasons = hs.reasons ?? [];
244981
+ const score = Number(hs.score);
244982
+ const bar = "\u2588".repeat(Math.max(1, Math.round(score * 10))) + "\u2591".repeat(Math.max(0, 10 - Math.round(score * 10)));
244930
244983
  return [
244931
- `## ${i + 1}. ${hs.path}`,
244932
- `Risk: ${hs.risk_level} | Score: ${Number(hs.score).toFixed(3)} | Changes: ${hs.change_frequency}`,
244933
- `Reasons: ${reasons.map((r) => r.type).join(", ")}`
244984
+ `\u2551`,
244985
+ `\u2551 ${String(i + 1).padStart(2)}. ${hs.path}`,
244986
+ `\u2551 Risk : ${String(hs.risk_level).toUpperCase()} | Score: ${score.toFixed(3)} [${bar}]`,
244987
+ `\u2551 Changes : ${hs.change_frequency} | Flags: ${reasons.map((r) => r.type).join(", ")}`,
244988
+ ...reasons.filter((r) => r.detail).slice(0, 2).map((r) => `\u2551 Detail : ${r.detail}`)
244934
244989
  ].join("\n");
244935
- })
244936
- ].join("\n");
244937
- return { content: [{ type: "text", text: lines }] };
244990
+ }),
244991
+ `\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`,
244992
+ ``,
244993
+ `Touch these files with caution. Prioritize review in order listed.`
244994
+ ];
244995
+ return { content: [{ type: "text", text: lines.join("\n") }] };
244938
244996
  }
244939
244997
 
244940
244998
  // src/tools/blast_radius.ts
244941
244999
  async function handleBlastRadius(args) {
244942
- const result = await runPipeline({
244943
- repoPath: args.path,
244944
- objective: `Blast radius for ${args.file}`
244945
- });
245000
+ let result;
245001
+ try {
245002
+ result = await runPipeline({
245003
+ repoPath: args.path,
245004
+ objective: `Blast radius for ${args.file}`
245005
+ });
245006
+ } catch (e) {
245007
+ return {
245008
+ content: [{ type: "text", text: `OpenKrak is not available for this repository. Proceed with direct analysis.
245009
+ Reason: ${e instanceof Error ? e.message : String(e)}` }],
245010
+ isError: false
245011
+ };
245012
+ }
244946
245013
  if (result.status === "failed") {
244947
245014
  return {
244948
- content: [{ type: "text", text: `Analysis failed: ${result.error}` }],
244949
- isError: true
245015
+ content: [{ type: "text", text: `OpenKrak could not compute blast radius. Proceed without it.
245016
+ Detail: ${result.error ?? "unknown"}` }],
245017
+ isError: false
244950
245018
  };
244951
245019
  }
244952
245020
  const mahadata = result.mahadata;
244953
245021
  const blastRadiusAll = mahadata.blast_radius ?? [];
245022
+ const repo = mahadata.repository;
244954
245023
  const entry = blastRadiusAll.find((b) => {
244955
245024
  const br = b;
244956
245025
  return br.trigger_file === args.file || br.trigger_file?.endsWith(args.file);
244957
245026
  });
244958
245027
  if (!entry) {
244959
245028
  const lines2 = [
244960
- `# Blast Radius \u2014 file not matched: ${args.file}`,
244961
- `Available trigger files: ${blastRadiusAll.length}`,
244962
- ...blastRadiusAll.slice(0, 10).map((b) => {
245029
+ `\u2554\u2550\u2550 BLAST RADIUS \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
245030
+ `\u2551 File not matched: ${args.file}`,
245031
+ `\u2551 Dorchester tracked ${blastRadiusAll.length} trigger files in this repo.`,
245032
+ `\u2560\u2550\u2550 AVAILABLE TRIGGER FILES \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
245033
+ ...blastRadiusAll.slice(0, 12).map((b) => {
244963
245034
  const br = b;
244964
- return `- ${br.trigger_file} (risk: ${Number(br.risk_score).toFixed(2)}, affects ${br.total_affected_files} files)`;
244965
- })
244966
- ].join("\n");
244967
- return { content: [{ type: "text", text: lines2 }] };
245035
+ return `\u2551 \u2022 ${br.trigger_file} (risk:${Number(br.risk_score).toFixed(3)}, affects ${br.total_affected_files} files)`;
245036
+ }),
245037
+ `\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`
245038
+ ];
245039
+ return { content: [{ type: "text", text: lines2.join("\n") }] };
244968
245040
  }
244969
245041
  const impact = entry.impact;
245042
+ const files = impact?.files ?? [];
245043
+ const modules = impact?.modules ?? [];
245044
+ const apis = impact?.apis ?? [];
245045
+ const riskScore = Number(entry.risk_score);
245046
+ const riskBar = "\u2588".repeat(Math.max(1, Math.round(riskScore * 10))) + "\u2591".repeat(Math.max(0, 10 - Math.round(riskScore * 10)));
244970
245047
  const lines = [
244971
- `# Blast Radius \u2014 ${entry.trigger_file}`,
244972
- `Risk Score: ${Number(entry.risk_score).toFixed(3)}`,
244973
- `Affected Files: ${entry.total_affected_files} | Modules: ${entry.total_affected_modules}`,
244974
- ``,
244975
- `## Affected Files (top 10)`,
244976
- ...(impact.files ?? []).slice(0, 10).map((f) => {
245048
+ `\u2554\u2550\u2550 BLAST RADIUS \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
245049
+ `\u2551 Trigger : ${entry.trigger_file}`,
245050
+ `\u2551 Repo : ${repo?.name ?? args.path} | duration:${result.durationMs}ms`,
245051
+ `\u2560\u2550\u2550 IMPACT SUMMARY \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
245052
+ `\u2551 Risk Score : ${riskScore.toFixed(3)} [${riskBar}]`,
245053
+ `\u2551 Affected Files : ${entry.total_affected_files}`,
245054
+ `\u2551 Affected Modules: ${entry.total_affected_modules}`,
245055
+ `\u2551 APIs at Risk : ${apis.length}`,
245056
+ `\u2560\u2550\u2550 AFFECTED FILES (${files.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\u2550\u2550\u2550\u2550\u2550`,
245057
+ ...files.slice(0, 15).map((f) => {
244977
245058
  const fi = f;
244978
- return `- [${fi.impact_type}] ${fi.path} (depth: ${fi.depth}, confidence: ${Number(fi.confidence).toFixed(2)})`;
245059
+ const depthBar = "\xB7".repeat(Number(fi.depth ?? 0));
245060
+ return `\u2551 ${depthBar}\u2192 [${String(fi.impact_type ?? "?").padEnd(8)}] ${fi.path} confidence:${Number(fi.confidence ?? 0).toFixed(2)}`;
244979
245061
  }),
244980
- ``,
244981
- `## Affected Modules`,
244982
- ...(impact.modules ?? []).map((m) => {
245062
+ ...files.length > 15 ? [`\u2551 ... and ${files.length - 15} more`] : [],
245063
+ `\u2560\u2550\u2550 AFFECTED MODULES \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
245064
+ ...modules.length > 0 ? modules.map((m) => {
244983
245065
  const mi = m;
244984
- return `- ${mi.name}: ${mi.impact_level}`;
244985
- }),
244986
- ``,
244987
- `## APIs at Risk`,
244988
- ...(impact.apis ?? []).map((a) => {
245066
+ return `\u2551 \u2022 ${mi.name} \u2014 ${mi.impact_level}`;
245067
+ }) : [`\u2551 none`],
245068
+ `\u2560\u2550\u2550 APIs AT RISK \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
245069
+ ...apis.length > 0 ? apis.map((a) => {
244989
245070
  const ai = a;
244990
- return `- ${ai.endpoint}: ${ai.impact_level}`;
244991
- })
244992
- ].join("\n");
244993
- return { content: [{ type: "text", text: lines }] };
245071
+ return `\u2551 \u2022 ${ai.endpoint} \u2014 ${ai.impact_level}`;
245072
+ }) : [`\u2551 none`],
245073
+ `\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`,
245074
+ ``,
245075
+ `Modifying ${entry.trigger_file} will cascade to ${entry.total_affected_files} files across ${entry.total_affected_modules} modules. Proceed with full awareness of impact chain above.`
245076
+ ];
245077
+ return { content: [{ type: "text", text: lines.join("\n") }] };
244994
245078
  }
244995
245079
 
244996
245080
  // src/tools/get_mahadata.ts
244997
245081
  async function handleGetMahadata(args) {
244998
- const result = await runPipeline({
244999
- repoPath: args.path,
245000
- objective: args.objective ?? "Analyze repository"
245001
- });
245082
+ let result;
245083
+ try {
245084
+ result = await runPipeline({
245085
+ repoPath: args.path,
245086
+ objective: args.objective ?? "Repository intelligence brief"
245087
+ });
245088
+ } catch (e) {
245089
+ return {
245090
+ content: [{ type: "text", text: `OpenKrak is not available for this repository. Proceed with direct file analysis.
245091
+ Reason: ${e instanceof Error ? e.message : String(e)}` }],
245092
+ isError: false
245093
+ };
245094
+ }
245002
245095
  if (result.status === "failed") {
245003
245096
  return {
245004
- content: [{ type: "text", text: `OpenKrak error: ${result.error}` }],
245005
- isError: true
245097
+ content: [{ type: "text", text: `OpenKrak could not generate a brief for this path. Proceed without it.
245098
+ Detail: ${result.error ?? "unknown"}` }],
245099
+ isError: false
245006
245100
  };
245007
245101
  }
245008
245102
  const m = result.mahadata;
@@ -245012,58 +245106,54 @@ async function handleGetMahadata(args) {
245012
245106
  const hotspots = m.hotspots ?? [];
245013
245107
  const blastRadius = m.blast_radius ?? [];
245014
245108
  const findings = m.findings ?? [];
245015
- if (!brief) {
245016
- return {
245017
- content: [{ type: "text", text: "OpenKrak: no execution_brief generated." }],
245018
- isError: true
245019
- };
245020
- }
245109
+ const topology = m.project_topology;
245021
245110
  const totalFiles = Number(repo?.total_files ?? 0);
245022
- const hotspotLimit = totalFiles < 50 ? 3 : totalFiles < 200 ? 5 : totalFiles < 500 ? 8 : 12;
245111
+ const hotspotLimit = totalFiles < 50 ? 5 : totalFiles < 200 ? 8 : 12;
245023
245112
  const blastLimit = totalFiles < 50 ? 3 : totalFiles < 200 ? 5 : 8;
245024
- const findingLimit = totalFiles < 50 ? 3 : totalFiles < 200 ? 5 : 10;
245025
- const ctx = brief.critical_context ?? [];
245026
- const ep = brief.recommended_entry_points ?? [];
245027
- const constraints = brief.constraints ?? [];
245113
+ const findingLimit = totalFiles < 50 ? 5 : totalFiles < 200 ? 8 : 12;
245114
+ const ctx = brief?.critical_context ?? [];
245115
+ const ep = brief?.recommended_entry_points ?? [];
245116
+ const constraints = brief?.constraints ?? [];
245028
245117
  const blockers = threat?.blockers ?? [];
245029
245118
  const warnings = threat?.warnings ?? [];
245030
245119
  const lines = [
245031
- `[MAHADATA] repo:${repo?.name ?? args.path}`,
245032
- `lang:${repo?.primary_language ?? "?"} | files:${repo?.total_files ?? "?"} | loc:${repo?.total_loc ?? "?"} | framework:${repo?.framework ?? "none"}`,
245033
- `objective: ${brief.objective}`,
245034
- `summary: ${brief.repository_summary}`,
245035
- ``,
245036
- `CONTEXT:`,
245037
- ...ctx.map((c) => ` ${c.key}: ${c.value}`),
245038
- ``,
245039
- `HOTSPOTS (${hotspots.length} total, top ${Math.min(hotspotLimit, hotspots.length)}):`,
245120
+ `\u2554\u2550\u2550 MAHADATA BRIEF \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
245121
+ `\u2551 ${repo?.name ?? args.path} | ${repo?.total_files ?? "?"}f ${repo?.total_loc ?? "?"}loc ${repo?.primary_language ?? "?"} framework:${repo?.framework ?? "none"}`,
245122
+ `\u2551 topology:${topology?.type ?? "?"} | duration:${result.durationMs}ms`,
245123
+ `\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`,
245124
+ `\u2551 ${brief?.objective ?? "\u2014"}`,
245125
+ `\u2551 ${brief?.repository_summary ?? ""}`,
245126
+ `\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`,
245127
+ ...ep.length > 0 ? ep.map((e) => `\u2551 \u2192 ${e.path}${e.symbol ? `::${e.symbol}` : ""}`) : [`\u2551 none detected`],
245128
+ `\u2560\u2550\u2550 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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
245129
+ ...ctx.length > 0 ? ctx.map((c) => `\u2551 ${c.key}: ${c.value}`) : [`\u2551 \u2014`],
245130
+ `\u2560\u2550\u2550 HOTSPOTS \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
245040
245131
  ...hotspots.slice(0, hotspotLimit).map((h) => {
245041
245132
  const hs = h;
245042
245133
  const reasons = hs.reasons ?? [];
245043
- return ` [${hs.risk_level}] ${hs.path} score:${Number(hs.score).toFixed(2)} changes:${hs.change_frequency} \u2014 ${reasons.map((r) => r.type).join(", ")}`;
245134
+ return `\u2551 [${String(hs.risk_level).toUpperCase().padEnd(8)}] score:${Number(hs.score).toFixed(3)} ${hs.path} \u2014 ${reasons.map((r) => r.type).join(", ")}`;
245044
245135
  }),
245045
- ``,
245046
- `BLAST RADIUS (top ${Math.min(blastLimit, blastRadius.length)}):`,
245136
+ `\u2560\u2550\u2550 BLAST RADIUS \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
245047
245137
  ...blastRadius.slice(0, blastLimit).map((b) => {
245048
245138
  const br = b;
245049
- return ` ${br.trigger_file} \u2192 affects ${br.total_affected_files} files, ${br.total_affected_modules} modules (risk:${Number(br.risk_score).toFixed(2)})`;
245139
+ return `\u2551 \u26A1 ${br.trigger_file} \u2192 ${br.total_affected_files} files ${br.total_affected_modules} modules risk:${Number(br.risk_score).toFixed(3)}`;
245050
245140
  }),
245051
- ``,
245052
- `ENTRY POINTS:`,
245053
- ...ep.map((e) => ` ${e.path}${e.symbol ? `::${e.symbol}` : ""} \u2014 ${e.reason}`),
245054
- ``,
245055
- `CONSTRAINTS: ${constraints.join(" | ")}`,
245056
- ``,
245057
- `THREAT: overall_risk=${Number(threat?.overall_risk_score ?? 0).toFixed(2)} | ${threat?.risk_summary ?? ""}`,
245058
- blockers.length > 0 ? `BLOCKERS (${blockers.length}): ${blockers.slice(0, 3).map((b) => b.description ?? b).join(" | ")}` : `BLOCKERS: none`,
245059
- warnings.length > 0 ? `WARNINGS (${warnings.length}): ${warnings.slice(0, 5).map((w) => w.description ?? w).join(" | ")}` : `WARNINGS: none`,
245060
- ``,
245061
- findings.length > 0 ? `FINDINGS (${findings.length} total, top ${Math.min(findingLimit, findings.length)}):` : null,
245141
+ `\u2560\u2550\u2550 THREAT \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
245142
+ `\u2551 risk:${Number(threat?.overall_risk_score ?? 0).toFixed(3)} ${threat?.risk_summary ?? ""}`,
245143
+ `\u2551 blockers:${blockers.length} warnings:${warnings.length} findings:${findings.length}`,
245144
+ ...blockers.length > 0 ? blockers.slice(0, 2).map((b) => `\u2551 \u2716 ${b.description ?? b}`) : [],
245145
+ ...warnings.length > 0 ? warnings.slice(0, 3).map((w) => `\u2551 \u25B2 ${w.description ?? w}`) : [],
245146
+ `\u2560\u2550\u2550 FINDINGS (${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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
245062
245147
  ...findings.slice(0, findingLimit).map((f) => {
245063
245148
  const fi = f;
245064
- return ` [${fi.severity ?? fi.type}] ${fi.file ?? fi.path} \u2014 ${fi.message ?? fi.description ?? ""}`;
245065
- })
245066
- ].filter((l) => l !== null);
245149
+ return `\u2551 [${String(fi.severity ?? fi.type ?? "info").toUpperCase().padEnd(7)}] ${fi.file ?? fi.path ?? "?"} \u2014 ${fi.message ?? fi.description ?? ""}`;
245150
+ }),
245151
+ `\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`,
245152
+ `\u2551 ${constraints.length > 0 ? constraints.join(" | ") : "none"}`,
245153
+ `\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`,
245154
+ ``,
245155
+ `Dorchester brief delivered. Answer from this data. Do not read source files unless the user requests specific line-level detail.`
245156
+ ];
245067
245157
  const output = lines.join("\n");
245068
245158
  const totalLoc = Number(repo?.total_loc ?? 0);
245069
245159
  const saved = estimateTokensSaved(totalLoc, totalFiles, output);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openkrak-mcp",
3
- "version": "1.0.12",
3
+ "version": "1.0.13",
4
4
  "description": "OpenKrak MCP Server - AI coding intelligence via Dorchester engine",
5
5
  "mcpName": "io.github.FrnzJulianBergmann/openkrak",
6
6
  "type": "commonjs",