@getmikk/mcp-server 1.9.1 → 1.10.0

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.cjs CHANGED
@@ -6,6 +6,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
9
12
  var __commonJS = (cb, mod) => function __require() {
10
13
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
14
  };
@@ -6800,6 +6803,20 @@ var require_dist = __commonJS({
6800
6803
  }
6801
6804
  });
6802
6805
 
6806
+ // ../core/dist/parser/base-parser.js
6807
+ var init_base_parser = __esm({
6808
+ "../core/dist/parser/base-parser.js"() {
6809
+ "use strict";
6810
+ }
6811
+ });
6812
+
6813
+ // ../core/dist/hash/file-hasher.js
6814
+ var init_file_hasher = __esm({
6815
+ "../core/dist/hash/file-hasher.js"() {
6816
+ "use strict";
6817
+ }
6818
+ });
6819
+
6803
6820
  // ../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/typescript.js
6804
6821
  var require_typescript = __commonJS({
6805
6822
  "../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/typescript.js"(exports2, module2) {
@@ -15394,7 +15411,7 @@ ${lanes.join("\n")}
15394
15411
  writeOutputIsTTY() {
15395
15412
  return process.stdout.isTTY;
15396
15413
  },
15397
- readFile: readFile7,
15414
+ readFile: readFile6,
15398
15415
  writeFile: writeFile22,
15399
15416
  watchFile: watchFile2,
15400
15417
  watchDirectory,
@@ -15600,7 +15617,7 @@ ${lanes.join("\n")}
15600
15617
  callback
15601
15618
  );
15602
15619
  }
15603
- function readFile7(fileName, _encoding) {
15620
+ function readFile6(fileName, _encoding) {
15604
15621
  let buffer;
15605
15622
  try {
15606
15623
  buffer = _fs.readFileSync(fileName);
@@ -52040,7 +52057,7 @@ ${lanes.join("\n")}
52040
52057
  const possibleOption = getSpellingSuggestion(unknownOption, diagnostics.optionDeclarations, getOptionName);
52041
52058
  return possibleOption ? createDiagnosticForNodeInSourceFileOrCompilerDiagnostic(sourceFile, node, diagnostics.unknownDidYouMeanDiagnostic, unknownOptionErrorText || unknownOption, possibleOption.name) : createDiagnosticForNodeInSourceFileOrCompilerDiagnostic(sourceFile, node, diagnostics.unknownOptionDiagnostic, unknownOptionErrorText || unknownOption);
52042
52059
  }
52043
- function parseCommandLineWorker(diagnostics, commandLine, readFile7) {
52060
+ function parseCommandLineWorker(diagnostics, commandLine, readFile6) {
52044
52061
  const options = {};
52045
52062
  let watchOptions;
52046
52063
  const fileNames = [];
@@ -52088,7 +52105,7 @@ ${lanes.join("\n")}
52088
52105
  }
52089
52106
  }
52090
52107
  function parseResponseFile(fileName) {
52091
- const text = tryReadFile(fileName, readFile7 || ((fileName2) => sys.readFile(fileName2)));
52108
+ const text = tryReadFile(fileName, readFile6 || ((fileName2) => sys.readFile(fileName2)));
52092
52109
  if (!isString(text)) {
52093
52110
  errors.push(text);
52094
52111
  return;
@@ -52190,8 +52207,8 @@ ${lanes.join("\n")}
52190
52207
  unknownDidYouMeanDiagnostic: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1,
52191
52208
  optionTypeMismatchDiagnostic: Diagnostics.Compiler_option_0_expects_an_argument
52192
52209
  };
52193
- function parseCommandLine(commandLine, readFile7) {
52194
- return parseCommandLineWorker(compilerOptionsDidYouMeanDiagnostics, commandLine, readFile7);
52210
+ function parseCommandLine(commandLine, readFile6) {
52211
+ return parseCommandLineWorker(compilerOptionsDidYouMeanDiagnostics, commandLine, readFile6);
52195
52212
  }
52196
52213
  function getOptionFromName(optionName, allowShort) {
52197
52214
  return getOptionDeclarationFromName(getOptionsNameMap, optionName, allowShort);
@@ -52273,8 +52290,8 @@ ${lanes.join("\n")}
52273
52290
  watchOptionsToExtend
52274
52291
  );
52275
52292
  }
52276
- function readConfigFile(fileName, readFile7) {
52277
- const textOrDiagnostic = tryReadFile(fileName, readFile7);
52293
+ function readConfigFile(fileName, readFile6) {
52294
+ const textOrDiagnostic = tryReadFile(fileName, readFile6);
52278
52295
  return isString(textOrDiagnostic) ? parseConfigFileTextToJson(fileName, textOrDiagnostic) : { config: {}, error: textOrDiagnostic };
52279
52296
  }
52280
52297
  function parseConfigFileTextToJson(fileName, jsonText) {
@@ -52289,14 +52306,14 @@ ${lanes.join("\n")}
52289
52306
  error: jsonSourceFile.parseDiagnostics.length ? jsonSourceFile.parseDiagnostics[0] : void 0
52290
52307
  };
52291
52308
  }
52292
- function readJsonConfigFile(fileName, readFile7) {
52293
- const textOrDiagnostic = tryReadFile(fileName, readFile7);
52309
+ function readJsonConfigFile(fileName, readFile6) {
52310
+ const textOrDiagnostic = tryReadFile(fileName, readFile6);
52294
52311
  return isString(textOrDiagnostic) ? parseJsonText(fileName, textOrDiagnostic) : { fileName, parseDiagnostics: [textOrDiagnostic] };
52295
52312
  }
52296
- function tryReadFile(fileName, readFile7) {
52313
+ function tryReadFile(fileName, readFile6) {
52297
52314
  let text;
52298
52315
  try {
52299
- text = readFile7(fileName);
52316
+ text = readFile6(fileName);
52300
52317
  } catch (e) {
52301
52318
  return createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e.message);
52302
52319
  }
@@ -141824,12 +141841,12 @@ ${lanes.join("\n")}
141824
141841
  function createCompilerHost(options, setParentNodes) {
141825
141842
  return createCompilerHostWorker(options, setParentNodes);
141826
141843
  }
141827
- function createGetSourceFile(readFile7, setParentNodes) {
141844
+ function createGetSourceFile(readFile6, setParentNodes) {
141828
141845
  return (fileName, languageVersionOrOptions, onError) => {
141829
141846
  let text;
141830
141847
  try {
141831
141848
  mark("beforeIORead");
141832
- text = readFile7(fileName);
141849
+ text = readFile6(fileName);
141833
141850
  mark("afterIORead");
141834
141851
  measure("I/O Read", "beforeIORead", "afterIORead");
141835
141852
  } catch (e) {
@@ -142733,7 +142750,7 @@ ${lanes.join("\n")}
142733
142750
  getRedirectFromOutput,
142734
142751
  forEachResolvedProjectReference: forEachResolvedProjectReference2
142735
142752
  });
142736
- const readFile7 = host.readFile.bind(host);
142753
+ const readFile6 = host.readFile.bind(host);
142737
142754
  (_e = tracing) == null ? void 0 : _e.push(tracing.Phase.Program, "shouldProgramCreateNewSourceFiles", { hasOldProgram: !!oldProgram });
142738
142755
  const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options);
142739
142756
  (_f = tracing) == null ? void 0 : _f.pop();
@@ -142959,7 +142976,7 @@ ${lanes.join("\n")}
142959
142976
  shouldTransformImportCall,
142960
142977
  emitBuildInfo,
142961
142978
  fileExists: fileExists2,
142962
- readFile: readFile7,
142979
+ readFile: readFile6,
142963
142980
  directoryExists,
142964
142981
  getSymlinkCache,
142965
142982
  realpath: (_o = host.realpath) == null ? void 0 : _o.bind(host),
@@ -217133,6 +217150,34 @@ Additional information: BADCLIENT: Bad error code, ${badCode} not found in range
217133
217150
  }
217134
217151
  });
217135
217152
 
217153
+ // ../core/dist/parser/tree-sitter/queries.js
217154
+ var init_queries = __esm({
217155
+ "../core/dist/parser/tree-sitter/queries.js"() {
217156
+ "use strict";
217157
+ }
217158
+ });
217159
+
217160
+ // ../core/dist/parser/tree-sitter/parser.js
217161
+ var import_node_module, import_meta, getRequire, _require, ParserModule, Parser;
217162
+ var init_parser = __esm({
217163
+ "../core/dist/parser/tree-sitter/parser.js"() {
217164
+ "use strict";
217165
+ import_node_module = require("node:module");
217166
+ init_file_hasher();
217167
+ init_base_parser();
217168
+ init_queries();
217169
+ import_meta = {};
217170
+ getRequire = () => {
217171
+ if (typeof require !== "undefined")
217172
+ return require;
217173
+ return (0, import_node_module.createRequire)(import_meta.url);
217174
+ };
217175
+ _require = getRequire();
217176
+ ParserModule = _require("web-tree-sitter");
217177
+ Parser = ParserModule.Parser || ParserModule;
217178
+ }
217179
+ });
217180
+
217136
217181
  // ../../node_modules/.bun/fast-glob@3.3.3/node_modules/fast-glob/out/utils/array.js
217137
217182
  var require_array = __commonJS({
217138
217183
  "../../node_modules/.bun/fast-glob@3.3.3/node_modules/fast-glob/out/utils/array.js"(exports2) {
@@ -236753,11 +236798,24 @@ var fs6 = __toESM(require("node:fs/promises"), 1);
236753
236798
  var import_node_child_process = require("node:child_process");
236754
236799
  var import_node_crypto = require("node:crypto");
236755
236800
 
236756
- // ../core/dist/parser/typescript/ts-extractor.js
236757
- var import_typescript = __toESM(require_typescript(), 1);
236801
+ // ../core/dist/parser/oxc-parser.js
236802
+ var import_oxc_parser = require("oxc-parser");
236803
+ init_base_parser();
236758
236804
 
236759
- // ../core/dist/parser/javascript/js-extractor.js
236760
- var import_typescript2 = __toESM(require_typescript(), 1);
236805
+ // ../core/dist/parser/oxc-resolver.js
236806
+ var import_oxc_resolver = require("oxc-resolver");
236807
+
236808
+ // ../core/dist/parser/oxc-parser.js
236809
+ init_file_hasher();
236810
+
236811
+ // ../core/dist/parser/go/go-parser.js
236812
+ init_base_parser();
236813
+
236814
+ // ../core/dist/parser/go/go-extractor.js
236815
+ init_file_hasher();
236816
+
236817
+ // ../core/dist/parser/go/go-parser.js
236818
+ init_file_hasher();
236761
236819
 
236762
236820
  // ../core/dist/utils/errors.js
236763
236821
  var MikkError = class extends Error {
@@ -236779,6 +236837,29 @@ var LockNotFoundError = class extends MikkError {
236779
236837
  }
236780
236838
  };
236781
236839
 
236840
+ // ../core/dist/parser/index.js
236841
+ init_base_parser();
236842
+
236843
+ // ../core/dist/parser/typescript/ts-parser.js
236844
+ init_base_parser();
236845
+
236846
+ // ../core/dist/parser/typescript/ts-extractor.js
236847
+ var import_typescript = __toESM(require_typescript(), 1);
236848
+ init_file_hasher();
236849
+
236850
+ // ../core/dist/parser/typescript/ts-parser.js
236851
+ init_file_hasher();
236852
+
236853
+ // ../core/dist/parser/javascript/js-parser.js
236854
+ init_base_parser();
236855
+
236856
+ // ../core/dist/parser/javascript/js-extractor.js
236857
+ var import_typescript2 = __toESM(require_typescript(), 1);
236858
+ init_file_hasher();
236859
+
236860
+ // ../core/dist/parser/javascript/js-parser.js
236861
+ init_file_hasher();
236862
+
236782
236863
  // ../core/dist/parser/boundary-checker.js
236783
236864
  var path = __toESM(require("node:path"), 1);
236784
236865
  function stripPrefix(s) {
@@ -236839,7 +236920,10 @@ var BoundaryChecker = class {
236839
236920
  for (const file of Object.values(this.lock.files)) {
236840
236921
  if (file.moduleId === "unknown" || !file.imports?.length)
236841
236922
  continue;
236842
- for (const importedPath of file.imports) {
236923
+ for (const imp of file.imports) {
236924
+ const importedPath = imp.resolvedPath;
236925
+ if (!importedPath)
236926
+ continue;
236843
236927
  const importedFile = this.lock.files[importedPath];
236844
236928
  if (!importedFile || importedFile.moduleId === "unknown" || file.moduleId === importedFile.moduleId)
236845
236929
  continue;
@@ -236911,95 +236995,236 @@ var BoundaryChecker = class {
236911
236995
  }
236912
236996
  };
236913
236997
 
236914
- // ../core/dist/parser/tree-sitter/parser.js
236915
- var import_node_module = require("node:module");
236916
- var import_meta = {};
236917
- var getRequire = () => {
236918
- if (typeof require !== "undefined")
236919
- return require;
236920
- return (0, import_node_module.createRequire)(import_meta.url);
236998
+ // ../core/dist/parser/index.js
236999
+ init_parser();
237000
+
237001
+ // ../core/dist/graph/risk-engine.js
237002
+ var RiskEngine = class {
237003
+ graph;
237004
+ constructor(graph) {
237005
+ this.graph = graph;
237006
+ }
237007
+ /**
237008
+ * Compute the absolute risk score (0-100) for modifying a specific node.
237009
+ * Formula: Base Risk = (Connected Nodes * 1.5) + (Depth * 2) + Modifiers
237010
+ */
237011
+ scoreNode(nodeId) {
237012
+ const node = this.graph.nodes.get(nodeId);
237013
+ if (!node)
237014
+ return 0;
237015
+ const context = this.analyzeContext(nodeId);
237016
+ const modifiers = this.analyzeModifiers(node);
237017
+ let score = context.connectedNodesCount * 1.5 + context.dependencyDepth * 2;
237018
+ if (modifiers.isAuthOrSecurity)
237019
+ score += 30;
237020
+ if (modifiers.isDatabaseOrState)
237021
+ score += 20;
237022
+ if (modifiers.isPublicAPI)
237023
+ score += 15;
237024
+ return Math.min(Math.max(score, 0), 100);
237025
+ }
237026
+ analyzeContext(nodeId) {
237027
+ const visited = /* @__PURE__ */ new Set();
237028
+ let maxDepth = 0;
237029
+ const queue = [{ id: nodeId, depth: 0 }];
237030
+ let queueHead = 0;
237031
+ visited.add(nodeId);
237032
+ let connectedNodesCount = 0;
237033
+ while (queueHead < queue.length) {
237034
+ const current = queue[queueHead++];
237035
+ maxDepth = Math.max(maxDepth, current.depth);
237036
+ const inEdges = this.graph.inEdges.get(current.id) || [];
237037
+ connectedNodesCount += inEdges.length;
237038
+ for (const edge of inEdges) {
237039
+ if (!visited.has(edge.from)) {
237040
+ visited.add(edge.from);
237041
+ queue.push({ id: edge.from, depth: current.depth + 1 });
237042
+ }
237043
+ }
237044
+ }
237045
+ return {
237046
+ connectedNodesCount,
237047
+ dependencyDepth: maxDepth
237048
+ };
237049
+ }
237050
+ analyzeModifiers(node) {
237051
+ const nameAndFile = `${node.name} ${node.file}`.toLowerCase();
237052
+ const authKeywords = ["auth", "login", "jwt", "verify", "token", "crypt", "hash", "password"];
237053
+ const dbKeywords = ["db", "query", "sql", "insert", "update", "delete", "redis", "cache", "transaction"];
237054
+ return {
237055
+ isAuthOrSecurity: authKeywords.some((kw) => nameAndFile.includes(kw)),
237056
+ isDatabaseOrState: dbKeywords.some((kw) => nameAndFile.includes(kw)),
237057
+ isPublicAPI: !!node.metadata?.isExported
237058
+ };
237059
+ }
237060
+ };
237061
+
237062
+ // ../core/dist/graph/confidence-engine.js
237063
+ var ConfidenceEngine = class {
237064
+ graph;
237065
+ constructor(graph) {
237066
+ this.graph = graph;
237067
+ }
237068
+ /**
237069
+ * Compute confidence decay along a specific path of node IDs.
237070
+ * @param pathIds Array of node IDs forming a path (e.g. ['A', 'B', 'C'])
237071
+ * @returns Cumulative confidence score from 0.0 to 1.0
237072
+ */
237073
+ calculatePathConfidence(pathIds) {
237074
+ if (pathIds.length < 2)
237075
+ return 1;
237076
+ let totalConfidence = 1;
237077
+ for (let i = 0; i < pathIds.length - 1; i++) {
237078
+ const current = pathIds[i];
237079
+ const next = pathIds[i + 1];
237080
+ const outEdges = this.graph.outEdges.get(current) || [];
237081
+ let maxEdgeConfidence = 0;
237082
+ for (const edge of outEdges) {
237083
+ if (edge.to === next) {
237084
+ if (edge.confidence > maxEdgeConfidence) {
237085
+ maxEdgeConfidence = edge.confidence;
237086
+ }
237087
+ }
237088
+ }
237089
+ if (maxEdgeConfidence === 0) {
237090
+ return 0;
237091
+ }
237092
+ totalConfidence *= maxEdgeConfidence;
237093
+ }
237094
+ return totalConfidence;
237095
+ }
237096
+ /**
237097
+ * Calculates the overall aggregated confidence for a target node
237098
+ * by averaging the confidence of all paths leading to it.
237099
+ */
237100
+ calculateNodeAggregatedConfidence(paths) {
237101
+ if (paths.length === 0)
237102
+ return 1;
237103
+ const pathConfidences = paths.map((path6) => this.calculatePathConfidence(path6));
237104
+ const sum = pathConfidences.reduce((a, b) => a + b, 0);
237105
+ return Number((sum / paths.length).toFixed(3));
237106
+ }
236921
237107
  };
236922
- var _require = getRequire();
236923
- var ParserModule = _require("web-tree-sitter");
236924
- var Parser = ParserModule.Parser || ParserModule;
236925
237108
 
236926
237109
  // ../core/dist/graph/impact-analyzer.js
236927
237110
  var ImpactAnalyzer = class {
236928
237111
  graph;
237112
+ riskEngine;
237113
+ confidenceEngine;
236929
237114
  constructor(graph) {
236930
237115
  this.graph = graph;
237116
+ this.riskEngine = new RiskEngine(graph);
237117
+ this.confidenceEngine = new ConfidenceEngine(graph);
236931
237118
  }
236932
237119
  /** Given a list of changed node IDs, find everything impacted */
236933
237120
  analyze(changedNodeIds) {
236934
- const visited = /* @__PURE__ */ new Set();
236935
- const depthMap = /* @__PURE__ */ new Map();
236936
- const queue = changedNodeIds.map((id) => ({ id, depth: 0 }));
237121
+ const visited = /* @__PURE__ */ new Map();
237122
+ const queue = changedNodeIds.map((id) => ({ id, depth: 0, path: [id], pathSet: /* @__PURE__ */ new Set([id]) }));
237123
+ let queueHead = 0;
236937
237124
  let maxDepth = 0;
236938
- const changedSet = new Set(changedNodeIds);
236939
- const changedModules = /* @__PURE__ */ new Set();
236940
- for (const id of changedNodeIds) {
236941
- const node = this.graph.nodes.get(id);
236942
- if (node)
236943
- changedModules.add(node.moduleId);
236944
- }
236945
- while (queue.length > 0) {
236946
- const { id: current, depth } = queue.shift();
236947
- if (visited.has(current))
236948
- continue;
236949
- visited.add(current);
236950
- depthMap.set(current, depth);
237125
+ const entryPoints = /* @__PURE__ */ new Set();
237126
+ const criticalModules = /* @__PURE__ */ new Set();
237127
+ while (queueHead < queue.length) {
237128
+ const { id: current, depth, path: path6, pathSet } = queue[queueHead++];
237129
+ if (!visited.has(current)) {
237130
+ visited.set(current, { depth, paths: [path6] });
237131
+ } else {
237132
+ visited.get(current).paths.push(path6);
237133
+ if (depth < visited.get(current).depth) {
237134
+ visited.get(current).depth = depth;
237135
+ }
237136
+ }
236951
237137
  maxDepth = Math.max(maxDepth, depth);
237138
+ const node = this.graph.nodes.get(current);
237139
+ if (node?.metadata?.isExported) {
237140
+ entryPoints.add(current);
237141
+ }
236952
237142
  const dependents = this.graph.inEdges.get(current) || [];
236953
237143
  for (const edge of dependents) {
236954
- if (!visited.has(edge.source) && edge.type !== "contains") {
236955
- queue.push({ id: edge.source, depth: depth + 1 });
237144
+ if (edge.type === "contains")
237145
+ continue;
237146
+ if (!pathSet.has(edge.from)) {
237147
+ const newPathSet = new Set(pathSet);
237148
+ newPathSet.add(edge.from);
237149
+ queue.push({
237150
+ id: edge.from,
237151
+ depth: depth + 1,
237152
+ path: [...path6, edge.from],
237153
+ pathSet: newPathSet
237154
+ });
236956
237155
  }
236957
237156
  }
236958
237157
  }
236959
- const impacted = [...visited].filter((id) => !changedSet.has(id));
237158
+ const impactedIds = Array.from(visited.keys()).filter((id) => !changedNodeIds.includes(id));
237159
+ let totalRisk = 0;
237160
+ let totalConfidence = 0;
236960
237161
  const classified = {
236961
237162
  critical: [],
236962
237163
  high: [],
236963
237164
  medium: [],
236964
237165
  low: []
236965
237166
  };
236966
- for (const id of impacted) {
237167
+ for (const id of impactedIds) {
237168
+ const context = visited.get(id);
236967
237169
  const node = this.graph.nodes.get(id);
236968
- if (!node)
236969
- continue;
236970
- const depth = depthMap.get(id) ?? 999;
236971
- const crossesBoundary = !changedModules.has(node.moduleId);
236972
- const risk = depth === 1 && crossesBoundary ? "critical" : depth === 1 ? "high" : depth === 2 ? "medium" : "low";
236973
- const entry = {
237170
+ let risk = this.riskEngine.scoreNode(id);
237171
+ const reversedPaths = context.paths.map((p) => [...p].reverse());
237172
+ const confidence = this.confidenceEngine.calculateNodeAggregatedConfidence(reversedPaths);
237173
+ if (context.depth === 1 && node?.moduleId) {
237174
+ const crossesBoundary = changedNodeIds.some((id2) => {
237175
+ const changedNode = this.graph.nodes.get(id2);
237176
+ if (!changedNode?.moduleId || !node.moduleId) {
237177
+ return false;
237178
+ }
237179
+ return changedNode.moduleId !== node.moduleId;
237180
+ });
237181
+ if (crossesBoundary) {
237182
+ risk = Math.max(risk, 80);
237183
+ }
237184
+ }
237185
+ totalRisk += risk;
237186
+ totalConfidence += confidence;
237187
+ const impactEntry = {
236974
237188
  nodeId: id,
236975
- label: node.label,
236976
- file: node.file,
236977
- moduleId: node.moduleId,
236978
- risk,
236979
- depth
237189
+ label: node?.name || "unknown",
237190
+ file: node?.file || "unknown",
237191
+ risk: risk >= 80 ? "CRITICAL" : risk >= 60 ? "HIGH" : risk >= 40 ? "MEDIUM" : "LOW",
237192
+ riskScore: risk,
237193
+ depth: context.depth
236980
237194
  };
236981
- classified[risk].push(entry);
236982
- }
237195
+ if (risk >= 80)
237196
+ classified.critical.push(impactEntry);
237197
+ else if (risk >= 60)
237198
+ classified.high.push(impactEntry);
237199
+ else if (risk >= 40)
237200
+ classified.medium.push(impactEntry);
237201
+ else
237202
+ classified.low.push(impactEntry);
237203
+ if (risk > 70 && node?.moduleId) {
237204
+ criticalModules.add(node.moduleId);
237205
+ }
237206
+ }
237207
+ const avgConfidence = impactedIds.length > 0 ? totalConfidence / impactedIds.length : 1;
237208
+ const riskScore = impactedIds.length > 0 ? Math.min(Math.max(totalRisk / impactedIds.length, 0), 100) : 0;
237209
+ const allImpacted = [
237210
+ ...classified.critical,
237211
+ ...classified.high,
237212
+ ...classified.medium,
237213
+ ...classified.low
237214
+ ];
236983
237215
  return {
236984
237216
  changed: changedNodeIds,
236985
- impacted,
237217
+ impacted: impactedIds,
237218
+ allImpacted,
236986
237219
  depth: maxDepth,
236987
- confidence: this.computeConfidence(impacted.length, maxDepth),
237220
+ entryPoints: Array.from(entryPoints),
237221
+ criticalModules: Array.from(criticalModules),
237222
+ paths: Array.from(visited.values()).flatMap((v) => v.paths),
237223
+ confidence: Number(avgConfidence.toFixed(3)),
237224
+ riskScore: Math.round(riskScore),
236988
237225
  classified
236989
237226
  };
236990
237227
  }
236991
- /**
236992
- * How confident are we in this impact analysis?
236993
- * High = few nodes affected, shallow depth
236994
- * Low = many nodes affected, deep chains
236995
- */
236996
- computeConfidence(impactedCount, depth) {
236997
- if (impactedCount < 5 && depth < 3)
236998
- return "high";
236999
- if (impactedCount < 20 && depth < 6)
237000
- return "medium";
237001
- return "low";
237002
- }
237003
237228
  };
237004
237229
 
237005
237230
  // ../core/dist/graph/dead-code-detector.js
@@ -237007,7 +237232,6 @@ var ENTRY_POINT_PATTERNS = [
237007
237232
  /^(main|bootstrap|start|init|setup|configure|register|mount)$/i,
237008
237233
  /^(app|server|index|mod|program)$/i,
237009
237234
  /Handler$/i,
237010
- // Express/Koa/Hono handlers
237011
237235
  /Middleware$/i,
237012
237236
  /Controller$/i,
237013
237237
  /^use[A-Z]/,
@@ -237023,14 +237247,26 @@ var TEST_PATTERNS = [
237023
237247
  /\.spec\./,
237024
237248
  /__test__/
237025
237249
  ];
237250
+ var DYNAMIC_USAGE_PATTERNS = [
237251
+ /^addEventListener$/i,
237252
+ /^removeEventListener$/i,
237253
+ /^on[A-Z]/,
237254
+ /(invoke|dispatch|emit|call|apply)/i,
237255
+ /^ngOnInit$/i,
237256
+ /^componentDidMount$/i,
237257
+ /^componentWillUnmount$/i
237258
+ ];
237026
237259
  var DeadCodeDetector = class {
237027
237260
  graph;
237028
237261
  lock;
237029
237262
  routeHandlers;
237263
+ /** Files that have at least one unresolved import (empty resolvedPath) */
237264
+ filesWithUnresolvedImports;
237030
237265
  constructor(graph, lock) {
237031
237266
  this.graph = graph;
237032
237267
  this.lock = lock;
237033
237268
  this.routeHandlers = new Set((lock.routes ?? []).map((r) => r.handler).filter(Boolean));
237269
+ this.filesWithUnresolvedImports = this.buildUnresolvedImportFileSet();
237034
237270
  }
237035
237271
  detect() {
237036
237272
  const dead = [];
@@ -237049,13 +237285,15 @@ var DeadCodeDetector = class {
237049
237285
  continue;
237050
237286
  if (this.isExempt(fn, id))
237051
237287
  continue;
237288
+ const confidence = this.inferConfidence(fn);
237052
237289
  const entry = {
237053
237290
  id,
237054
237291
  name: fn.name,
237055
237292
  file: fn.file,
237056
237293
  moduleId,
237057
237294
  type: "function",
237058
- reason: this.inferReason(fn, id)
237295
+ reason: this.inferReason(fn),
237296
+ confidence
237059
237297
  };
237060
237298
  dead.push(entry);
237061
237299
  byModule[moduleId].dead++;
@@ -237069,9 +237307,7 @@ var DeadCodeDetector = class {
237069
237307
  }
237070
237308
  const inEdges = this.graph.inEdges.get(id) || [];
237071
237309
  const hasCallers = inEdges.some((e) => e.type === "calls" || e.type === "imports");
237072
- if (hasCallers)
237073
- continue;
237074
- if (cls.isExported)
237310
+ if (hasCallers || cls.isExported)
237075
237311
  continue;
237076
237312
  const entry = {
237077
237313
  id,
@@ -237079,7 +237315,8 @@ var DeadCodeDetector = class {
237079
237315
  file: cls.file,
237080
237316
  moduleId,
237081
237317
  type: "class",
237082
- reason: "Class has no callers or importers and is not exported"
237318
+ reason: "Class has no callers or importers and is not exported",
237319
+ confidence: this.filesWithUnresolvedImports.has(cls.file) ? "medium" : "high"
237083
237320
  };
237084
237321
  dead.push(entry);
237085
237322
  byModule[moduleId].dead++;
@@ -237094,7 +237331,7 @@ var DeadCodeDetector = class {
237094
237331
  byModule
237095
237332
  };
237096
237333
  }
237097
- // ─── Exemption checks ──────────────────────────────────────────
237334
+ // ─── Private helpers ───────────────────────────────────────────
237098
237335
  isExempt(fn, id) {
237099
237336
  if (fn.isExported)
237100
237337
  return true;
@@ -237106,24 +237343,85 @@ var DeadCodeDetector = class {
237106
237343
  return true;
237107
237344
  if (fn.name === "constructor" || fn.name === "__init__")
237108
237345
  return true;
237109
- if (this.isCalledByExportedInSameFile(fn, id))
237346
+ if (this.isCalledByExportedInSameFile(fn))
237110
237347
  return true;
237111
237348
  return false;
237112
237349
  }
237113
- isCalledByExportedInSameFile(fn, fnId) {
237114
- for (const callerId of fn.calledBy) {
237115
- const caller = this.lock.functions[callerId];
237116
- if (caller && caller.isExported && caller.file === fn.file) {
237117
- return true;
237350
+ isCalledByExportedInSameFile(fn) {
237351
+ const file = fn.file;
237352
+ const visited = /* @__PURE__ */ new Set();
237353
+ const queue = [fn.id];
237354
+ while (queue.length > 0) {
237355
+ const currentId = queue.pop();
237356
+ if (visited.has(currentId))
237357
+ continue;
237358
+ visited.add(currentId);
237359
+ const current = this.lock.functions[currentId];
237360
+ if (!current)
237361
+ continue;
237362
+ for (const callerId of current.calledBy) {
237363
+ if (visited.has(callerId))
237364
+ continue;
237365
+ const caller = this.lock.functions[callerId];
237366
+ if (!caller)
237367
+ continue;
237368
+ if (caller.file !== file)
237369
+ continue;
237370
+ if (caller.isExported)
237371
+ return true;
237372
+ queue.push(callerId);
237118
237373
  }
237119
237374
  }
237120
237375
  return false;
237121
237376
  }
237122
- inferReason(fn, id) {
237377
+ /**
237378
+ * Assign a confidence level to a dead code finding.
237379
+ *
237380
+ * Priority (first match wins):
237381
+ * medium — lock.calledBy has entries that didn't become graph edges:
237382
+ * something references this function but resolution failed.
237383
+ * medium — file has unresolved imports: the graph may be incomplete.
237384
+ * low — function name matches common dynamic-dispatch patterns.
237385
+ * high — none of the above: safe to remove.
237386
+ */
237387
+ inferConfidence(fn) {
237388
+ if (DYNAMIC_USAGE_PATTERNS.some((p) => p.test(fn.name)))
237389
+ return "low";
237390
+ if (fn.calledBy.length > 0)
237391
+ return "medium";
237392
+ if (this.filesWithUnresolvedImports.has(fn.file))
237393
+ return "medium";
237394
+ return "high";
237395
+ }
237396
+ inferReason(fn) {
237123
237397
  if (fn.calledBy.length === 0) {
237124
237398
  return "No callers found anywhere in the codebase";
237125
237399
  }
237126
- return `${fn.calledBy.length} references exist but none resolved to active call edges`;
237400
+ return `${fn.calledBy.length} reference(s) in lock but none resolved to active call edges`;
237401
+ }
237402
+ /**
237403
+ * Build the set of file paths that have at least one import whose
237404
+ * resolvedPath is empty. Used to downgrade confidence for all dead
237405
+ * findings in those files, since the graph may be incomplete.
237406
+ *
237407
+ * We derive this from the lock's file entries. Each file entry stores
237408
+ * its imports; any import with an empty resolvedPath (or no match in
237409
+ * the graph nodes) indicates an unresolved dependency.
237410
+ */
237411
+ buildUnresolvedImportFileSet() {
237412
+ const result = /* @__PURE__ */ new Set();
237413
+ if (!this.lock.files)
237414
+ return result;
237415
+ for (const [filePath, fileInfo] of Object.entries(this.lock.files)) {
237416
+ const imports = fileInfo.imports ?? [];
237417
+ for (const imp of imports) {
237418
+ if (!imp.resolvedPath || imp.resolvedPath === "") {
237419
+ result.add(filePath);
237420
+ break;
237421
+ }
237422
+ }
237423
+ }
237424
+ return result;
237127
237425
  }
237128
237426
  };
237129
237427
 
@@ -237149,6 +237447,19 @@ var MikkOverwriteSchema = external_exports.object({
237149
237447
  lastOverwrittenBy: external_exports.string().optional(),
237150
237448
  lastOverwrittenAt: external_exports.string().optional()
237151
237449
  }).default({ mode: "never", requireConfirmation: true });
237450
+ var MikkPolicySchema = external_exports.object({
237451
+ maxRiskScore: external_exports.number().default(70),
237452
+ maxImpactNodes: external_exports.number().default(10),
237453
+ protectedModules: external_exports.array(external_exports.string()).default(["auth", "security", "billing"]),
237454
+ enforceStrictBoundaries: external_exports.boolean().default(true),
237455
+ requireReasoningForCritical: external_exports.boolean().default(true)
237456
+ }).default({
237457
+ maxRiskScore: 70,
237458
+ maxImpactNodes: 10,
237459
+ protectedModules: ["auth", "security", "billing"],
237460
+ enforceStrictBoundaries: true,
237461
+ requireReasoningForCritical: true
237462
+ });
237152
237463
  var MikkContractSchema = external_exports.object({
237153
237464
  version: external_exports.string(),
237154
237465
  project: external_exports.object({
@@ -237163,7 +237474,8 @@ var MikkContractSchema = external_exports.object({
237163
237474
  constraints: external_exports.array(external_exports.string()).default([]),
237164
237475
  decisions: external_exports.array(MikkDecisionSchema).default([])
237165
237476
  }),
237166
- overwrite: MikkOverwriteSchema
237477
+ overwrite: MikkOverwriteSchema,
237478
+ policies: MikkPolicySchema
237167
237479
  });
237168
237480
  var MikkLockFunctionSchema = external_exports.object({
237169
237481
  id: external_exports.string(),
@@ -237189,7 +237501,9 @@ var MikkLockFunctionSchema = external_exports.object({
237189
237501
  line: external_exports.number(),
237190
237502
  type: external_exports.enum(["try-catch", "throw"]),
237191
237503
  detail: external_exports.string()
237192
- })).optional()
237504
+ })).optional(),
237505
+ confidence: external_exports.number().optional(),
237506
+ riskScore: external_exports.number().optional()
237193
237507
  });
237194
237508
  var MikkLockModuleSchema = external_exports.object({
237195
237509
  id: external_exports.string(),
@@ -237197,12 +237511,17 @@ var MikkLockModuleSchema = external_exports.object({
237197
237511
  hash: external_exports.string(),
237198
237512
  fragmentPath: external_exports.string()
237199
237513
  });
237514
+ var MikkLockImportSchema = external_exports.object({
237515
+ source: external_exports.string(),
237516
+ resolvedPath: external_exports.string().optional(),
237517
+ names: external_exports.array(external_exports.string()).optional()
237518
+ });
237200
237519
  var MikkLockFileSchema = external_exports.object({
237201
237520
  path: external_exports.string(),
237202
237521
  hash: external_exports.string(),
237203
237522
  moduleId: external_exports.string(),
237204
237523
  lastModified: external_exports.string(),
237205
- imports: external_exports.array(external_exports.string()).optional()
237524
+ imports: external_exports.array(MikkLockImportSchema).optional()
237206
237525
  });
237207
237526
  var MikkLockClassSchema = external_exports.object({
237208
237527
  id: external_exports.string(),
@@ -237218,7 +237537,9 @@ var MikkLockClassSchema = external_exports.object({
237218
237537
  line: external_exports.number(),
237219
237538
  type: external_exports.enum(["try-catch", "throw"]),
237220
237539
  detail: external_exports.string()
237221
- })).optional()
237540
+ })).optional(),
237541
+ confidence: external_exports.number().optional(),
237542
+ riskScore: external_exports.number().optional()
237222
237543
  });
237223
237544
  var MikkLockGenericSchema = external_exports.object({
237224
237545
  id: external_exports.string(),
@@ -237272,22 +237593,49 @@ var MikkLockSchema = external_exports.object({
237272
237593
  })
237273
237594
  });
237274
237595
 
237275
- // ../core/dist/contract/contract-reader.js
237596
+ // ../core/dist/contract/lock-compiler.js
237597
+ init_file_hasher();
237598
+
237599
+ // ../core/dist/hash/tree-hasher.js
237600
+ init_file_hasher();
237601
+
237602
+ // ../core/dist/utils/json.js
237276
237603
  var fs = __toESM(require("node:fs/promises"), 1);
237604
+ async function readJsonSafe(filePath, fileLabel = "JSON file") {
237605
+ let content;
237606
+ try {
237607
+ content = await fs.readFile(filePath, "utf-8");
237608
+ } catch (e) {
237609
+ if (e.code === "ENOENT") {
237610
+ throw e;
237611
+ }
237612
+ throw new Error(`Failed to read ${fileLabel}: ${e.message}`);
237613
+ }
237614
+ const sanitized = content.replace(/^\uFEFF/, "");
237615
+ try {
237616
+ return JSON.parse(sanitized);
237617
+ } catch (e) {
237618
+ throw new Error(`Malformed ${fileLabel}: Syntax error - ${e.message}`);
237619
+ }
237620
+ }
237621
+
237622
+ // ../core/dist/contract/contract-reader.js
237277
237623
  var ContractReader = class {
237278
237624
  /** Read and validate mikk.json */
237279
237625
  async read(contractPath) {
237280
- let content;
237626
+ let json;
237281
237627
  try {
237282
- content = await fs.readFile(contractPath, "utf-8");
237283
- } catch {
237284
- throw new ContractNotFoundError(contractPath);
237628
+ json = await readJsonSafe(contractPath, "mikk.json");
237629
+ } catch (e) {
237630
+ if (e.code === "ENOENT") {
237631
+ throw new ContractNotFoundError(contractPath);
237632
+ }
237633
+ throw e;
237285
237634
  }
237286
- const json = JSON.parse(content.replace(/^\uFEFF/, ""));
237287
237635
  const result = MikkContractSchema.safeParse(json);
237288
237636
  if (!result.success) {
237289
237637
  const errors = result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n");
237290
- throw new Error(`Invalid mikk.json:
237638
+ throw new Error(`Invalid mikk.json structure:
237291
237639
  ${errors}`);
237292
237640
  }
237293
237641
  return result.data;
@@ -237299,18 +237647,20 @@ var fs2 = __toESM(require("node:fs/promises"), 1);
237299
237647
  var LockReader = class {
237300
237648
  /** Read and validate mikk.lock.json */
237301
237649
  async read(lockPath) {
237302
- let content;
237650
+ let json;
237303
237651
  try {
237304
- content = await fs2.readFile(lockPath, "utf-8");
237305
- } catch {
237306
- throw new LockNotFoundError();
237652
+ json = await readJsonSafe(lockPath, "mikk.lock.json");
237653
+ } catch (e) {
237654
+ if (e.code === "ENOENT") {
237655
+ throw new LockNotFoundError();
237656
+ }
237657
+ throw e;
237307
237658
  }
237308
- const json = JSON.parse(content.replace(/^\uFEFF/, ""));
237309
237659
  const hydrated = hydrateLock(json);
237310
237660
  const result = MikkLockSchema.safeParse(hydrated);
237311
237661
  if (!result.success) {
237312
237662
  const errors = result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n");
237313
- throw new Error(`Invalid mikk.lock.json:
237663
+ throw new Error(`Invalid mikk.lock.json structure:
237314
237664
  ${errors}`);
237315
237665
  }
237316
237666
  return result.data;
@@ -237410,8 +237760,9 @@ function compactifyLock(lock) {
237410
237760
  };
237411
237761
  if (file.moduleId && file.moduleId !== "unknown")
237412
237762
  c.moduleId = file.moduleId;
237413
- if (file.imports && file.imports.length > 0)
237414
- c.imports = file.imports;
237763
+ if (file.imports && file.imports.length > 0) {
237764
+ c.imports = file.imports.map(normalizeImportEntry);
237765
+ }
237415
237766
  out.files[key] = c;
237416
237767
  }
237417
237768
  if (lock.contextFiles && lock.contextFiles.length > 0) {
@@ -237441,7 +237792,10 @@ function hydrateLock(raw) {
237441
237792
  const hasFnIndex = fnIndex.length > 0;
237442
237793
  const fileModuleMap = {};
237443
237794
  for (const [key, c] of Object.entries(raw.files || {})) {
237444
- fileModuleMap[key] = c.moduleId || "unknown";
237795
+ const moduleId = c.moduleId || "unknown";
237796
+ const normalizedKey = normalizeFilePath(key);
237797
+ fileModuleMap[key] = moduleId;
237798
+ fileModuleMap[normalizedKey] = moduleId;
237445
237799
  }
237446
237800
  out.functions = {};
237447
237801
  for (const [key, c] of Object.entries(raw.functions || {})) {
@@ -237450,6 +237804,7 @@ function hydrateLock(raw) {
237450
237804
  const lines = c.lines || [c.startLine || 0, c.endLine || 0];
237451
237805
  const calls = (c.calls || []).map((v) => typeof v === "number" ? fnIndex[v] ?? null : v).filter(Boolean);
237452
237806
  const calledBy = (c.calledBy || []).map((v) => typeof v === "number" ? fnIndex[v] ?? null : v).filter(Boolean);
237807
+ const normalizedFile = normalizeFilePath(file);
237453
237808
  out.functions[fullId] = {
237454
237809
  id: fullId,
237455
237810
  name,
@@ -237460,7 +237815,7 @@ function hydrateLock(raw) {
237460
237815
  // P4: empty string when not stored
237461
237816
  calls,
237462
237817
  calledBy,
237463
- moduleId: fileModuleMap[file] || c.moduleId || "unknown",
237818
+ moduleId: fileModuleMap[normalizedFile] || fileModuleMap[file] || c.moduleId || "unknown",
237464
237819
  // P6: derive from file
237465
237820
  ...c.params ? { params: c.params } : {},
237466
237821
  ...c.returnType ? { returnType: c.returnType } : {},
@@ -237530,7 +237885,7 @@ function hydrateLock(raw) {
237530
237885
  hash: c.hash || "",
237531
237886
  moduleId: c.moduleId || "unknown",
237532
237887
  lastModified: c.lastModified || "",
237533
- ...c.imports && c.imports.length > 0 ? { imports: c.imports } : {}
237888
+ ...c.imports && c.imports.length > 0 ? { imports: c.imports.map(normalizeImportEntry) } : {}
237534
237889
  };
237535
237890
  }
237536
237891
  out.modules = raw.modules;
@@ -237565,6 +237920,20 @@ function parseEntityKeyFull(key) {
237565
237920
  name: rest.slice(lastColon + 1)
237566
237921
  };
237567
237922
  }
237923
+ function normalizeImportEntry(entry) {
237924
+ if (!entry)
237925
+ return { source: "" };
237926
+ if (typeof entry === "string")
237927
+ return { source: entry };
237928
+ return {
237929
+ source: entry.source,
237930
+ resolvedPath: entry.resolvedPath || void 0,
237931
+ names: entry.names?.length ? entry.names : void 0
237932
+ };
237933
+ }
237934
+ function normalizeFilePath(p) {
237935
+ return (p || "").replace(/\\/g, "/").toLowerCase();
237936
+ }
237568
237937
 
237569
237938
  // ../core/dist/contract/adr-manager.js
237570
237939
  var fs3 = __toESM(require("node:fs/promises"), 1);
@@ -237626,6 +237995,9 @@ var AdrManager = class {
237626
237995
  }
237627
237996
  };
237628
237997
 
237998
+ // ../core/dist/hash/index.js
237999
+ init_file_hasher();
238000
+
237629
238001
  // ../core/dist/hash/hash-store.js
237630
238002
  var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
237631
238003
 
@@ -237637,11 +238009,14 @@ var BM25Index = class {
237637
238009
  documentFrequency = /* @__PURE__ */ new Map();
237638
238010
  // term → how many docs contain it
237639
238011
  avgDocLength = 0;
238012
+ totalDocLength = 0;
238013
+ // running total — avoids O(n²) recompute on every addDocument
237640
238014
  /** Clear the index */
237641
238015
  clear() {
237642
238016
  this.documents = [];
237643
238017
  this.documentFrequency.clear();
237644
238018
  this.avgDocLength = 0;
238019
+ this.totalDocLength = 0;
237645
238020
  }
237646
238021
  /** Add a document with pre-tokenized terms */
237647
238022
  addDocument(id, tokens) {
@@ -237651,7 +238026,8 @@ var BM25Index = class {
237651
238026
  for (const term of uniqueTerms) {
237652
238027
  this.documentFrequency.set(term, (this.documentFrequency.get(term) ?? 0) + 1);
237653
238028
  }
237654
- this.avgDocLength = this.documents.reduce((sum, d) => sum + d.length, 0) / this.documents.length;
238029
+ this.totalDocLength += normalizedTokens.length;
238030
+ this.avgDocLength = this.totalDocLength / this.documents.length;
237655
238031
  }
237656
238032
  /** Search the index and return ranked results */
237657
238033
  search(query, limit = 20) {
@@ -237852,25 +238228,90 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
237852
238228
  "my",
237853
238229
  "your"
237854
238230
  ]);
237855
- function extractKeywords2(task) {
237856
- return task.toLowerCase().replace(/[^a-z0-9\s_-]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !STOP_WORDS.has(w));
238231
+ var SHORT_TECH_WORDS = /* @__PURE__ */ new Set([
238232
+ "ai",
238233
+ "ml",
238234
+ "ui",
238235
+ "ux",
238236
+ "ts",
238237
+ "js",
238238
+ "db",
238239
+ "io",
238240
+ "id",
238241
+ "ip",
238242
+ "ci",
238243
+ "cd",
238244
+ "qa",
238245
+ "api",
238246
+ "mcp",
238247
+ "jwt",
238248
+ "sql"
238249
+ ]);
238250
+ function normalizeKeyword(value) {
238251
+ return value.toLowerCase().trim().replace(/[^a-z0-9_-]/g, "");
238252
+ }
238253
+ function extractKeywords2(task, requiredKeywords = []) {
238254
+ const out = [];
238255
+ const seen = /* @__PURE__ */ new Set();
238256
+ for (const match of task.matchAll(/"([^"]+)"|'([^']+)'/g)) {
238257
+ const phrase = (match[1] ?? match[2] ?? "").toLowerCase().trim();
238258
+ if (!phrase || seen.has(phrase))
238259
+ continue;
238260
+ seen.add(phrase);
238261
+ out.push(phrase);
238262
+ }
238263
+ const words = task.toLowerCase().replace(/[^a-z0-9\s_-]/g, " ").split(/\s+/).map(normalizeKeyword).filter((w) => {
238264
+ if (!w || STOP_WORDS.has(w))
238265
+ return false;
238266
+ if (w.length > 2)
238267
+ return true;
238268
+ return SHORT_TECH_WORDS.has(w);
238269
+ });
238270
+ for (const w of words) {
238271
+ if (seen.has(w))
238272
+ continue;
238273
+ seen.add(w);
238274
+ out.push(w);
238275
+ }
238276
+ const expandedRequired = requiredKeywords.flatMap((item) => item.split(/[,\s]+/)).map(normalizeKeyword).filter(Boolean);
238277
+ for (const kw of expandedRequired) {
238278
+ if (seen.has(kw))
238279
+ continue;
238280
+ seen.add(kw);
238281
+ out.push(kw);
238282
+ }
238283
+ return out;
237857
238284
  }
237858
238285
  function keywordScore(fn, keywords) {
237859
238286
  if (keywords.length === 0)
237860
- return 0;
238287
+ return { score: 0, matchedKeywords: [] };
237861
238288
  const nameLower = fn.name.toLowerCase();
237862
238289
  const fileLower = fn.file.toLowerCase();
238290
+ const fileNoExt = fileLower.replace(/\.(d\.ts|ts|tsx|js|jsx|mjs|cjs|mts|cts)\b/g, " ");
238291
+ const purposeLower = (fn.purpose ?? "").toLowerCase();
238292
+ const tokenSet = /* @__PURE__ */ new Set([
238293
+ ...nameLower.match(/[a-z0-9]+/g) ?? [],
238294
+ ...fileNoExt.match(/[a-z0-9]+/g) ?? [],
238295
+ ...purposeLower.match(/[a-z0-9]+/g) ?? []
238296
+ ]);
237863
238297
  let score = 0;
238298
+ const matched = [];
237864
238299
  for (const kw of keywords) {
237865
- if (nameLower === kw) {
238300
+ const shortKw = kw.length <= 2;
238301
+ const exactName = nameLower === kw;
238302
+ const partial2 = shortKw ? tokenSet.has(kw) : nameLower.includes(kw) || fileLower.includes(kw) || purposeLower.includes(kw);
238303
+ if (exactName) {
237866
238304
  score = Math.max(score, WEIGHT.KEYWORD_EXACT);
237867
- } else if (nameLower.includes(kw) || fileLower.includes(kw)) {
238305
+ matched.push(kw);
238306
+ } else if (partial2) {
237868
238307
  score = Math.max(score, WEIGHT.KEYWORD_PARTIAL);
238308
+ matched.push(kw);
237869
238309
  }
237870
238310
  }
237871
- return score;
238311
+ return { score, matchedKeywords: matched };
237872
238312
  }
237873
- function resolveSeeds(query, contract, lock) {
238313
+ function resolveSeeds(query, contract, lock, keywords) {
238314
+ const strictMode = query.relevanceMode === "strict";
237874
238315
  const seeds = /* @__PURE__ */ new Set();
237875
238316
  if (query.focusFiles && query.focusFiles.length > 0) {
237876
238317
  for (const filePath of query.focusFiles) {
@@ -237890,14 +238331,13 @@ function resolveSeeds(query, contract, lock) {
237890
238331
  }
237891
238332
  }
237892
238333
  if (seeds.size === 0) {
237893
- const keywords = extractKeywords2(query.task);
237894
238334
  for (const fn of Object.values(lock.functions)) {
237895
- if (keywordScore(fn, keywords) >= WEIGHT.KEYWORD_PARTIAL) {
238335
+ if (keywordScore(fn, keywords).score >= WEIGHT.KEYWORD_PARTIAL) {
237896
238336
  seeds.add(fn.id);
237897
238337
  }
237898
238338
  }
237899
238339
  }
237900
- if (seeds.size === 0) {
238340
+ if (!strictMode && seeds.size === 0) {
237901
238341
  const taskLower = query.task.toLowerCase();
237902
238342
  for (const mod of contract.declared.modules) {
237903
238343
  if (taskLower.includes(mod.id.toLowerCase()) || taskLower.includes(mod.name.toLowerCase())) {
@@ -237929,25 +238369,72 @@ var ContextBuilder = class {
237929
238369
  * 6. Group survivors by module, emit structured context
237930
238370
  */
237931
238371
  build(query) {
238372
+ const relevanceMode = query.relevanceMode ?? "balanced";
238373
+ const strictMode = relevanceMode === "strict";
237932
238374
  const tokenBudget = query.tokenBudget ?? DEFAULT_TOKEN_BUDGET;
237933
238375
  const maxHops = query.maxHops ?? 4;
237934
- const seeds = resolveSeeds(query, this.contract, this.lock);
238376
+ const requiredKeywords = query.requiredKeywords ?? [];
238377
+ const keywords = extractKeywords2(query.task, requiredKeywords);
238378
+ const requiredKeywordSet = new Set(requiredKeywords.flatMap((item) => item.split(/[,\s]+/)).map(normalizeKeyword).filter(Boolean));
238379
+ const seeds = resolveSeeds(query, this.contract, this.lock, keywords);
238380
+ const seedSet = new Set(seeds);
237935
238381
  const proximityMap = seeds.length > 0 ? bfsNeighbors(seeds, this.lock.functions, maxHops) : /* @__PURE__ */ new Map();
237936
- const keywords = extractKeywords2(query.task);
237937
238382
  const allFunctions = Object.values(this.lock.functions);
238383
+ const focusFiles = query.focusFiles ?? [];
238384
+ const focusModules = new Set(query.focusModules ?? []);
238385
+ const requireAllKeywords = query.requireAllKeywords ?? false;
238386
+ const minKeywordMatches = query.minKeywordMatches ?? 1;
238387
+ const strictPassIds = /* @__PURE__ */ new Set();
238388
+ const reasons = [];
238389
+ const suggestions = [];
238390
+ const nearMissSuggestions = [];
237938
238391
  const scored = allFunctions.map((fn) => {
237939
238392
  let score = 0;
237940
238393
  const depth = proximityMap.get(fn.id);
237941
238394
  if (depth !== void 0) {
237942
238395
  score += depthToScore(depth);
237943
238396
  }
237944
- score += keywordScore(fn, keywords);
237945
- if (fn.calledBy.length === 0)
238397
+ const kwInfo = keywordScore(fn, keywords);
238398
+ score += kwInfo.score;
238399
+ const matchedSet = new Set(kwInfo.matchedKeywords);
238400
+ const inFocusFile = focusFiles.some((filePath) => fn.file.includes(filePath) || filePath.includes(fn.file));
238401
+ const inFocusModule = focusModules.has(fn.moduleId);
238402
+ const inFocus = inFocusFile || inFocusModule;
238403
+ const requiredPass = requiredKeywordSet.size === 0 ? true : [...requiredKeywordSet].every((kw) => matchedSet.has(kw));
238404
+ const generalPass = requireAllKeywords ? keywords.length > 0 && matchedSet.size >= keywords.length : keywords.length === 0 ? false : matchedSet.size >= minKeywordMatches;
238405
+ const keywordPass = requiredPass && generalPass;
238406
+ if (keywordPass)
238407
+ strictPassIds.add(fn.id);
238408
+ if (strictMode) {
238409
+ const isSeed = seedSet.has(fn.id);
238410
+ const seedFromFocus = isSeed && (inFocus || focusFiles.length > 0 || focusModules.size > 0);
238411
+ if (!(inFocus || keywordPass || seedFromFocus)) {
238412
+ if (kwInfo.score > 0) {
238413
+ nearMissSuggestions.push(`${fn.name} (${fn.file}:${fn.startLine})`);
238414
+ }
238415
+ return { fn, score: -1 };
238416
+ }
238417
+ }
238418
+ if (!strictMode && fn.calledBy.length === 0)
237946
238419
  score += WEIGHT.ENTRY_POINT;
237947
238420
  return { fn, score };
237948
238421
  });
237949
238422
  scored.sort((a, b) => b.score - a.score);
237950
- const selected = [];
238423
+ for (const { fn, score } of scored) {
238424
+ if (score <= 0)
238425
+ continue;
238426
+ suggestions.push(`${fn.name} (${fn.file}:${fn.startLine})`);
238427
+ if (suggestions.length >= 5)
238428
+ break;
238429
+ }
238430
+ for (const s of nearMissSuggestions) {
238431
+ if (suggestions.includes(s))
238432
+ continue;
238433
+ suggestions.push(s);
238434
+ if (suggestions.length >= 5)
238435
+ break;
238436
+ }
238437
+ let selected = [];
237951
238438
  let usedTokens = 0;
237952
238439
  for (const { fn, score } of scored) {
237953
238440
  if (score <= 0 && seeds.length > 0)
@@ -237961,6 +238448,51 @@ var ContextBuilder = class {
237961
238448
  selected.push(fn);
237962
238449
  usedTokens += tokens;
237963
238450
  }
238451
+ if (strictMode) {
238452
+ if (requiredKeywordSet.size > 0) {
238453
+ reasons.push(`required terms: ${[...requiredKeywordSet].join(", ")}`);
238454
+ }
238455
+ if (strictPassIds.size === 0) {
238456
+ reasons.push("no functions matched strict keyword filters");
238457
+ }
238458
+ }
238459
+ if (strictMode && query.exactOnly) {
238460
+ selected = selected.filter((fn) => strictPassIds.has(fn.id));
238461
+ usedTokens = selected.reduce((sum, fn) => sum + estimateTokens(this.buildFunctionSnippet(fn, query)), 0);
238462
+ if (selected.length === 0 && strictPassIds.size > 0) {
238463
+ reasons.push("exact matches exist but did not fit token budget or max function limit");
238464
+ }
238465
+ }
238466
+ if (strictMode && query.failFast && selected.length === 0) {
238467
+ reasons.push("fail-fast enabled: returning no context when exact match set is empty");
238468
+ return {
238469
+ project: {
238470
+ name: this.contract.project.name,
238471
+ language: this.contract.project.language,
238472
+ description: this.contract.project.description,
238473
+ moduleCount: this.contract.declared.modules.length,
238474
+ functionCount: Object.keys(this.lock.functions).length
238475
+ },
238476
+ modules: [],
238477
+ constraints: this.contract.declared.constraints,
238478
+ decisions: this.contract.declared.decisions.map((d) => ({
238479
+ title: d.title,
238480
+ reason: d.reason
238481
+ })),
238482
+ contextFiles: [],
238483
+ routes: [],
238484
+ prompt: "",
238485
+ meta: {
238486
+ seedCount: seeds.length,
238487
+ totalFunctionsConsidered: allFunctions.length,
238488
+ selectedFunctions: 0,
238489
+ estimatedTokens: 0,
238490
+ keywords,
238491
+ reasons,
238492
+ suggestions: suggestions.length > 0 ? suggestions : void 0
238493
+ }
238494
+ };
238495
+ }
237964
238496
  const byModule = /* @__PURE__ */ new Map();
237965
238497
  for (const fn of selected) {
237966
238498
  if (!byModule.has(fn.moduleId))
@@ -237981,6 +238513,8 @@ var ContextBuilder = class {
237981
238513
  });
237982
238514
  }
237983
238515
  contextModules.sort((a, b) => b.functions.length - a.functions.length);
238516
+ const contextFiles = strictMode ? [] : this.lock.contextFiles;
238517
+ const routes = strictMode ? [] : this.lock.routes;
237984
238518
  return {
237985
238519
  project: {
237986
238520
  name: this.contract.project.name,
@@ -237995,12 +238529,12 @@ var ContextBuilder = class {
237995
238529
  title: d.title,
237996
238530
  reason: d.reason
237997
238531
  })),
237998
- contextFiles: this.lock.contextFiles?.map((cf) => ({
238532
+ contextFiles: contextFiles?.map((cf) => ({
237999
238533
  path: cf.path,
238000
238534
  content: readContextFile(cf.path, query.projectRoot),
238001
238535
  type: cf.type
238002
238536
  })),
238003
- routes: this.lock.routes?.map((r) => ({
238537
+ routes: routes?.map((r) => ({
238004
238538
  method: r.method,
238005
238539
  path: r.path,
238006
238540
  handler: r.handler,
@@ -238014,7 +238548,9 @@ var ContextBuilder = class {
238014
238548
  totalFunctionsConsidered: allFunctions.length,
238015
238549
  selectedFunctions: selected.length,
238016
238550
  estimatedTokens: usedTokens,
238017
- keywords
238551
+ keywords,
238552
+ reasons: reasons.length > 0 ? reasons : void 0,
238553
+ suggestions: selected.length === 0 && suggestions.length > 0 ? suggestions : void 0
238018
238554
  }
238019
238555
  };
238020
238556
  }
@@ -238086,6 +238622,7 @@ var ContextBuilder = class {
238086
238622
  /** Generate the natural-language prompt section */
238087
238623
  generatePrompt(query, modules) {
238088
238624
  const lines = [];
238625
+ const strictMode = query.relevanceMode === "strict";
238089
238626
  lines.push("=== ARCHITECTURAL CONTEXT ===");
238090
238627
  lines.push(`Project: ${this.contract.project.name} (${this.contract.project.language})`);
238091
238628
  if (this.contract.project.description) {
@@ -238094,7 +238631,7 @@ var ContextBuilder = class {
238094
238631
  lines.push(`Task: ${query.task}`);
238095
238632
  lines.push("");
238096
238633
  const routes = this.lock.routes;
238097
- if (routes && routes.length > 0) {
238634
+ if (!strictMode && routes && routes.length > 0) {
238098
238635
  lines.push("=== HTTP ROUTES ===");
238099
238636
  for (const r of routes) {
238100
238637
  const mw = r.middlewares.length > 0 ? ` [${r.middlewares.join(", ")}]` : "";
@@ -238103,7 +238640,7 @@ var ContextBuilder = class {
238103
238640
  lines.push("");
238104
238641
  }
238105
238642
  const ctxFiles = this.lock.contextFiles;
238106
- if (ctxFiles && ctxFiles.length > 0) {
238643
+ if (!strictMode && ctxFiles && ctxFiles.length > 0) {
238107
238644
  lines.push("=== DATA MODELS & SCHEMAS ===");
238108
238645
  for (const cf of ctxFiles) {
238109
238646
  lines.push(`--- ${cf.path} (${cf.type}) ---`);
@@ -238370,6 +238907,16 @@ var ClaudeProvider = class {
238370
238907
  lines.push(` <seeds_found>${context.meta?.seedCount ?? 0}</seeds_found>`);
238371
238908
  lines.push(` <functions_selected>${context.meta?.selectedFunctions ?? 0} of ${context.meta?.totalFunctionsConsidered ?? 0}</functions_selected>`);
238372
238909
  lines.push(` <estimated_tokens>${context.meta?.estimatedTokens ?? 0}</estimated_tokens>`);
238910
+ if (context.meta?.reasons && context.meta.reasons.length > 0) {
238911
+ for (const reason of context.meta.reasons) {
238912
+ lines.push(` <reason>${esc2(reason)}</reason>`);
238913
+ }
238914
+ }
238915
+ if (context.meta?.suggestions && context.meta.suggestions.length > 0) {
238916
+ for (const s of context.meta.suggestions) {
238917
+ lines.push(` <suggestion>${esc2(s)}</suggestion>`);
238918
+ }
238919
+ }
238373
238920
  lines.push("</context_meta>");
238374
238921
  lines.push("");
238375
238922
  for (const mod of context.modules) {
@@ -238412,6 +238959,22 @@ var ClaudeProvider = class {
238412
238959
  lines.push("</module>");
238413
238960
  lines.push("");
238414
238961
  }
238962
+ if (context.modules.length === 0 && context.meta?.reasons?.length) {
238963
+ lines.push("<no_match_reason>");
238964
+ for (const reason of context.meta.reasons) {
238965
+ lines.push(` <item>${esc2(reason)}</item>`);
238966
+ }
238967
+ lines.push("</no_match_reason>");
238968
+ lines.push("");
238969
+ if (context.meta.suggestions && context.meta.suggestions.length > 0) {
238970
+ lines.push("<did_you_mean>");
238971
+ for (const suggestion of context.meta.suggestions) {
238972
+ lines.push(` <item>${esc2(suggestion)}</item>`);
238973
+ }
238974
+ lines.push("</did_you_mean>");
238975
+ lines.push("");
238976
+ }
238977
+ }
238415
238978
  if (context.contextFiles && context.contextFiles.length > 0) {
238416
238979
  lines.push("<context_files>");
238417
238980
  for (const cf of context.contextFiles) {
@@ -238472,6 +239035,20 @@ var CompactProvider = class {
238472
239035
  `Task keywords: ${context.meta?.keywords?.join(", ") ?? ""}`,
238473
239036
  ""
238474
239037
  ];
239038
+ if (context.modules.length === 0 && context.meta?.reasons?.length) {
239039
+ lines.push("No exact context selected:");
239040
+ for (const reason of context.meta.reasons) {
239041
+ lines.push(`- ${reason}`);
239042
+ }
239043
+ if (context.meta.suggestions && context.meta.suggestions.length > 0) {
239044
+ lines.push("");
239045
+ lines.push("Did you mean:");
239046
+ for (const suggestion of context.meta.suggestions) {
239047
+ lines.push(`- ${suggestion}`);
239048
+ }
239049
+ }
239050
+ lines.push("");
239051
+ }
238475
239052
  for (const mod of context.modules) {
238476
239053
  lines.push(`## ${mod.name}`);
238477
239054
  for (const fn of mod.functions) {
@@ -238659,6 +239236,22 @@ var IntentSchema = external_exports.object({
238659
239236
  // src/tools.ts
238660
239237
  var projectCache = /* @__PURE__ */ new Map();
238661
239238
  var CACHE_TTL_MS = 3e4;
239239
+ function invalidateCache(projectRoot) {
239240
+ projectCache.delete(projectRoot);
239241
+ }
239242
+ var MAX_SEARCHER_ROOTS = 5;
239243
+ function getSemanticSearcher(projectRoot) {
239244
+ let s = semanticSearchers.get(projectRoot);
239245
+ if (!s) {
239246
+ if (semanticSearchers.size >= MAX_SEARCHER_ROOTS) {
239247
+ const oldestKey = semanticSearchers.keys().next().value;
239248
+ if (oldestKey !== void 0) semanticSearchers.delete(oldestKey);
239249
+ }
239250
+ s = new SemanticSearcher(projectRoot);
239251
+ semanticSearchers.set(projectRoot, s);
239252
+ }
239253
+ return s;
239254
+ }
238662
239255
  var _CPT = 4;
238663
239256
  var _ALC = 42;
238664
239257
  var _tallies = /* @__PURE__ */ new Map();
@@ -238692,14 +239285,6 @@ function _track(root, raw, resp) {
238692
239285
  return { used, raw, saved, sessionSaved: t.saved, sessionCalls: t.calls };
238693
239286
  }
238694
239287
  var semanticSearchers = /* @__PURE__ */ new Map();
238695
- function getSemanticSearcher(projectRoot) {
238696
- let s = semanticSearchers.get(projectRoot);
238697
- if (!s) {
238698
- s = new SemanticSearcher(projectRoot);
238699
- semanticSearchers.set(projectRoot, s);
238700
- }
238701
- return s;
238702
- }
238703
239288
  function registerTools(server2, projectRoot) {
238704
239289
  server2.tool(
238705
239290
  "mikk_test_tool",
@@ -238745,16 +239330,23 @@ function registerTools(server2, projectRoot) {
238745
239330
  );
238746
239331
  server2.tool(
238747
239332
  "mikk_query_context",
238748
- "Ask an architecture question \xC3\xA2\xE2\u201A\xAC\xC2\x9D returns graph-traced context with relevant functions, files, and call chains. Use this to understand how code flows through the project.",
239333
+ "Ask an architecture question \u2014 returns graph-traced context with relevant functions, files, and call chains. Use this to understand how code flows through the project.",
238749
239334
  {
238750
239335
  question: external_exports.string().describe("The architecture question or task description"),
238751
239336
  maxHops: external_exports.number().optional().default(4).describe("Graph traversal depth (default: 4)"),
238752
239337
  tokenBudget: external_exports.number().optional().default(6e3).describe("Max tokens for function bodies (default: 6000)"),
238753
239338
  focusFile: external_exports.string().optional().describe("Anchor traversal from a specific file path"),
238754
239339
  focusModule: external_exports.string().optional().describe("Anchor traversal from a specific module ID"),
239340
+ strict: external_exports.boolean().optional().default(false).describe("High-precision mode: include only tightly relevant context"),
239341
+ requiredTerms: external_exports.array(external_exports.string()).optional().describe("Required terms that must match returned context"),
239342
+ requireAllKeywords: external_exports.boolean().optional().default(false).describe("In strict mode, require all extracted keywords"),
239343
+ minKeywordMatches: external_exports.number().optional().default(1).describe("In strict mode, minimum keyword hits per function"),
239344
+ exactOnly: external_exports.boolean().optional().default(false).describe("Hard gate: keep only strict keyword matches"),
239345
+ failFast: external_exports.boolean().optional().default(false).describe("Return no context if strict filters find no exact match"),
239346
+ autoFallback: external_exports.boolean().optional().default(true).describe("When strict mode returns empty, retry with balanced retrieval"),
238755
239347
  provider: external_exports.enum(["claude", "generic", "compact"]).optional().default("generic").describe("AI provider format: claude (XML tags), generic (plain), compact (minimal tokens)")
238756
239348
  },
238757
- async ({ question, maxHops, tokenBudget, focusFile, focusModule, provider }) => {
239349
+ async ({ question, maxHops, tokenBudget, focusFile, focusModule, strict, requiredTerms, requireAllKeywords, minKeywordMatches, exactOnly, failFast, autoFallback, provider }) => {
238758
239350
  const { contract, lock, staleness } = await loadContractAndLock(projectRoot);
238759
239351
  const query = {
238760
239352
  task: question,
@@ -238764,10 +239356,37 @@ function registerTools(server2, projectRoot) {
238764
239356
  focusModules: focusModule ? [focusModule] : void 0,
238765
239357
  includeCallGraph: true,
238766
239358
  includeBodies: true,
239359
+ relevanceMode: strict ? "strict" : "balanced",
239360
+ requiredKeywords: requiredTerms,
239361
+ requireAllKeywords,
239362
+ minKeywordMatches,
239363
+ exactOnly,
239364
+ failFast,
238767
239365
  projectRoot
238768
239366
  };
238769
239367
  const builder = new ContextBuilder(contract, lock);
238770
- const ctx = builder.build(query);
239368
+ let ctx = builder.build(query);
239369
+ let fallbackUsed = false;
239370
+ if (autoFallback !== false && strict && ctx.modules.length === 0) {
239371
+ const relaxed = {
239372
+ ...query,
239373
+ relevanceMode: "balanced",
239374
+ requiredKeywords: void 0,
239375
+ requireAllKeywords: false,
239376
+ minKeywordMatches: 1,
239377
+ exactOnly: false,
239378
+ failFast: false
239379
+ };
239380
+ const fallback = builder.build(relaxed);
239381
+ if (fallback.modules.length > 0) {
239382
+ ctx = fallback;
239383
+ fallbackUsed = true;
239384
+ ctx.meta.reasons = [
239385
+ ...ctx.meta.reasons ?? [],
239386
+ "strict query had no exact matches; returned balanced fallback context"
239387
+ ];
239388
+ }
239389
+ }
238771
239390
  if (ctx.modules.length === 0) {
238772
239391
  return {
238773
239392
  content: [{
@@ -238782,6 +239401,7 @@ function registerTools(server2, projectRoot) {
238782
239401
  const warning = staleness ? `
238783
239402
 
238784
239403
  ${staleness}` : "";
239404
+ const fallbackNote = fallbackUsed ? "Note: strict mode had no exact matches; showing balanced fallback context.\n\n" : "";
238785
239405
  const _rawQC = (tokenBudget ?? 6e3) * 3;
238786
239406
  const _tokQC = _track(projectRoot, _rawQC, output);
238787
239407
  const tokLine = `
@@ -238789,7 +239409,7 @@ ${staleness}` : "";
238789
239409
  ---
238790
239410
  // tokens: ${JSON.stringify(_tokQC)}`;
238791
239411
  return {
238792
- content: [{ type: "text", text: output + warning + "\n\n---\nHint: Use mikk_before_edit on any files you plan to modify, then mikk_impact_analysis to see the full blast radius." + tokLine }]
239412
+ content: [{ type: "text", text: fallbackNote + output + warning + "\n\n---\nHint: Use mikk_before_edit on any files you plan to modify, then mikk_impact_analysis to see the full blast radius." + tokLine }]
238793
239413
  };
238794
239414
  }
238795
239415
  );
@@ -238821,7 +239441,7 @@ ${staleness}` : "";
238821
239441
  const result = analyzer.analyze(fileNodes.map((n) => n.id));
238822
239442
  const impactedDetails = result.impacted.slice(0, 30).map((id) => {
238823
239443
  const node = graph.nodes.get(id);
238824
- return { function: node?.label ?? id, file: node?.file ?? "", module: node?.moduleId ?? "" };
239444
+ return { function: node?.name ?? id, file: node?.file ?? "", module: node?.moduleId ?? "" };
238825
239445
  });
238826
239446
  const response = {
238827
239447
  file,
@@ -238915,7 +239535,7 @@ ${staleness}` : "";
238915
239535
  const result = analyzer.analyze(fileFns.map((fn) => fn.id));
238916
239536
  const impactedDetails = result.impacted.slice(0, 20).map((id) => {
238917
239537
  const node = graph.nodes.get(id);
238918
- return { function: node?.label ?? id, file: node?.file ?? "", module: node?.moduleId ?? "" };
239538
+ return { function: node?.name ?? id, file: node?.file ?? "", module: node?.moduleId ?? "" };
238919
239539
  });
238920
239540
  const exportedAtRisk = fileFns.filter((fn) => fn.isExported).map((fn) => ({
238921
239541
  name: fn.name,
@@ -238953,7 +239573,7 @@ ${staleness}` : "";
238953
239573
  constraintStatus: totalViolations === 0 ? "pass" : "fail",
238954
239574
  files: fileReports,
238955
239575
  warning: staleness,
238956
- hint: totalViolations > 0 ? "\xC3\u201A\xC2\x8F Constraint violations detected! Review the violations before proceeding. Use mikk_get_constraints for full rule context." : "All constraints satisfied. If safe, proceed with your edits."
239576
+ hint: totalViolations > 0 ? "\u26A0 Constraint violations detected! Review the violations before proceeding. Use mikk_get_constraints for full rule context." : "All constraints satisfied. If safe, proceed with your edits."
238957
239577
  };
238958
239578
  const _rawBE = _filesTok(lock, filesToEdit) * 4;
238959
239579
  response.tokens = _track(projectRoot, _rawBE, response);
@@ -239087,7 +239707,7 @@ ${staleness}` : "";
239087
239707
  content: [{
239088
239708
  type: "text",
239089
239709
  text: [
239090
- "\xC3\u201A\xC2\x9D\xC3\u2026\xE2\u20AC\u2122 Semantic search requires @xenova/transformers.",
239710
+ "\u26A0 Semantic search requires @xenova/transformers.",
239091
239711
  "",
239092
239712
  "Install it in your project root:",
239093
239713
  " npm install @xenova/transformers",
@@ -239216,7 +239836,7 @@ ${content}`
239216
239836
  );
239217
239837
  server2.tool(
239218
239838
  "mikk_dead_code",
239219
- "Detect dead code \xC3\xA2\xE2\u201A\xAC\xC2\x9D functions with zero callers after exempting exports, entry points, route handlers, tests, and constructors. Use this before refactoring or cleanup.",
239839
+ "Detect dead code \u2014 functions with zero callers after exempting exports, entry points, route handlers, tests, and constructors. Use this before refactoring or cleanup.",
239220
239840
  {
239221
239841
  moduleId: external_exports.string().optional().describe("Filter results to a specific module ID")
239222
239842
  },
@@ -239241,7 +239861,7 @@ ${content}`
239241
239861
  );
239242
239862
  server2.tool(
239243
239863
  "mikk_manage_adr",
239244
- "CRUD for Architectural Decision Records (ADRs) in mikk.json. Actions: list, get, add, update, remove. WHEN TO USE: When making architectural changes \xC3\xA2\xE2\u201A\xAC\xE2\u20AC\x9D document WHY so future AI agents understand. AFTER THIS: ADRs automatically surface in mikk_query_context responses. Required for add: id, title, reason.",
239864
+ "CRUD for Architectural Decision Records (ADRs) in mikk.json. Actions: list, get, add, update, remove. WHEN TO USE: When making architectural changes \u2014 document WHY so future AI agents understand. AFTER THIS: ADRs automatically surface in mikk_query_context responses. Required for add: id, title, reason.",
239245
239865
  {
239246
239866
  action: external_exports.enum(["list", "get", "add", "update", "remove"]).describe("The CRUD action to perform"),
239247
239867
  id: external_exports.string().optional().describe("ADR id (required for get, update, remove)"),
@@ -239351,7 +239971,7 @@ ${content}`
239351
239971
  );
239352
239972
  server2.tool(
239353
239973
  "mikk_read_file",
239354
- "Read file scoped to specific functions. Returns bodies with metadata headers (params, calls, calledBy). WHEN TO USE: When you know which functions you need \xC3\xA2\xE2\u201A\xAC\xE2\u20AC\x9D saves tokens vs mikk_get_file. AFTER THIS: Use mikk_before_edit before making changes. TIP: This is the preferred way to read code \xC3\xA2\xE2\u201A\xAC\xE2\u20AC\x9D always specify function names when possible.",
239974
+ "Read file scoped to specific functions. Returns bodies with metadata headers (params, calls, calledBy). WHEN TO USE: When you know which functions you need \u2014 saves tokens vs mikk_get_file. AFTER THIS: Use mikk_before_edit before making changes. TIP: This is the preferred way to read code \u2014 always specify function names when possible.",
239355
239975
  {
239356
239976
  file: external_exports.string().describe("File path relative to project root"),
239357
239977
  functions: external_exports.array(external_exports.string()).optional().describe("Function names to extract. If omitted, returns the whole file.")
@@ -239391,7 +240011,7 @@ ${content}` }]
239391
240011
  (f) => (f.name === fnName || f.name.endsWith(`.${fnName}`)) && (f.file === normalizedFile || f.file.endsWith("/" + normalizedFile))
239392
240012
  );
239393
240013
  if (!fn) {
239394
- sections.push(`// \xC3\u201A\xC2\x9D\xC3\u2026\xE2\u20AC\u2122 Function "${fnName}" not found in ${file}`);
240014
+ sections.push(`// \u26A0 Function "${fnName}" not found in ${file}`);
239395
240015
  continue;
239396
240016
  }
239397
240017
  const header = [
@@ -239519,11 +240139,16 @@ ${staleness}` : "";
239519
240139
  }
239520
240140
  }
239521
240141
  const totalFns = affectedSymbols.reduce((s, f) => s + f.functions.length, 0);
239522
- return { content: [{ type: "text", text: JSON.stringify({
239523
- summary: `${affectedSymbols.length} file(s), ${totalFns} function(s) affected`,
239524
- affectedSymbols,
239525
- warning: staleness
239526
- }, null, 2) }] };
240142
+ return {
240143
+ content: [{
240144
+ type: "text",
240145
+ text: JSON.stringify({
240146
+ summary: `${affectedSymbols.length} file(s), ${totalFns} function(s) affected`,
240147
+ affectedSymbols,
240148
+ warning: staleness
240149
+ }, null, 2)
240150
+ }]
240151
+ };
239527
240152
  } catch (err) {
239528
240153
  return { content: [{ type: "text", text: `Git diff failed: ${err.message}` }], isError: true };
239529
240154
  }
@@ -239531,7 +240156,7 @@ ${staleness}` : "";
239531
240156
  );
239532
240157
  server2.tool(
239533
240158
  "mikk_rename",
239534
- "Plan a coordinated multi-file rename. Finds all call sites and import locations for a function and provides a step-by-step edit plan. WHEN TO USE: Before renaming any function \xC3\xA2\xE2\u201A\xAC\xE2\u20AC\x9D ensures you update ALL call sites. AFTER THIS: Execute the edit plan, then run mikk analyze.",
240159
+ "Plan a coordinated multi-file rename. Finds all call sites and import locations for a function and provides a step-by-step edit plan. WHEN TO USE: Before renaming any function \u2014 ensures you update ALL call sites. AFTER THIS: Execute the edit plan, then run mikk analyze.",
239535
240160
  {
239536
240161
  functionName: external_exports.string().describe("The current function name to rename"),
239537
240162
  newName: external_exports.string().describe("The desired new name")
@@ -239557,7 +240182,7 @@ ${staleness}` : "";
239557
240182
  lineRange: `${fn.startLine}-${fn.endLine}`
239558
240183
  }));
239559
240184
  const filesImporting = Object.values(lock.files).filter(
239560
- (file) => file.imports?.some((imp) => imp.includes(functionName) || imp.includes(targetFn.file))
240185
+ (file) => file.imports?.some((imp) => imp.names.includes(functionName) || imp.source === targetFn.file)
239561
240186
  );
239562
240187
  const instructions = [
239563
240188
  `1. Rename definition in ${targetFn.file}:${targetFn.startLine}`,
@@ -239620,9 +240245,19 @@ ${staleness}` : "";
239620
240245
  );
239621
240246
  }
239622
240247
  async function loadContractAndLock(projectRoot) {
240248
+ const lockFilePath = path4.join(projectRoot, "mikk.lock.json");
239623
240249
  const cached2 = projectCache.get(projectRoot);
239624
- if (cached2 && Date.now() - cached2.cachedAt < CACHE_TTL_MS) {
239625
- return { contract: cached2.contract, lock: cached2.lock, staleness: cached2.staleness };
240250
+ if (cached2) {
240251
+ try {
240252
+ const stat2 = await fs6.stat(lockFilePath);
240253
+ if (stat2.mtimeMs > cached2.cachedAt) {
240254
+ invalidateCache(projectRoot);
240255
+ } else if (Date.now() - cached2.cachedAt < CACHE_TTL_MS) {
240256
+ return { contract: cached2.contract, lock: cached2.lock, staleness: cached2.staleness };
240257
+ }
240258
+ } catch {
240259
+ invalidateCache(projectRoot);
240260
+ }
239626
240261
  }
239627
240262
  const contractReader = new ContractReader();
239628
240263
  const lockReader = new LockReader();
@@ -239631,7 +240266,7 @@ async function loadContractAndLock(projectRoot) {
239631
240266
  const syncStatus = lock.syncState?.status ?? "unknown";
239632
240267
  let staleness = null;
239633
240268
  if (syncStatus === "drifted" || syncStatus === "conflict") {
239634
- staleness = `\xC3\u201A\xC2\x8F Lock file is ${syncStatus}. Run \`mikk analyze\` for accurate results.`;
240269
+ staleness = `\u26A0 Lock file is ${syncStatus}. Run \`mikk analyze\` for accurate results.`;
239635
240270
  }
239636
240271
  if (!staleness) {
239637
240272
  const fileEntries = Object.entries(lock.files);
@@ -239654,7 +240289,7 @@ async function loadContractAndLock(projectRoot) {
239654
240289
  }
239655
240290
  }
239656
240291
  if (mismatched > 0) {
239657
- staleness = `\xC3\u201A\xC2\x8F STALE: ${mismatched} file(s) changed since last analysis (${mismatchedFiles.slice(0, 3).join(", ")}${mismatched > 3 ? "..." : ""}). Run \`mikk analyze\`.`;
240292
+ staleness = `\u26A0 STALE: ${mismatched} file(s) changed since last analysis (${mismatchedFiles.slice(0, 3).join(", ")}${mismatched > 3 ? "..." : ""}). Run \`mikk analyze\`.`;
239658
240293
  }
239659
240294
  }
239660
240295
  const graph = buildGraphFromLock(lock);
@@ -239676,7 +240311,7 @@ function buildGraphFromLock(lock) {
239676
240311
  nodes.set(fn.id, {
239677
240312
  id: fn.id,
239678
240313
  type: "function",
239679
- label: fn.name,
240314
+ name: fn.name,
239680
240315
  file: fn.file,
239681
240316
  moduleId: fn.moduleId,
239682
240317
  metadata: {
@@ -239697,7 +240332,7 @@ function buildGraphFromLock(lock) {
239697
240332
  nodes.set(file.path, {
239698
240333
  id: file.path,
239699
240334
  type: "file",
239700
- label: path4.basename(file.path),
240335
+ name: path4.basename(file.path),
239701
240336
  file: file.path,
239702
240337
  moduleId: file.moduleId,
239703
240338
  metadata: {}
@@ -239706,7 +240341,7 @@ function buildGraphFromLock(lock) {
239706
240341
  for (const fn of Object.values(lock.functions)) {
239707
240342
  for (const calleeId of fn.calls) {
239708
240343
  if (!nodes.has(calleeId)) continue;
239709
- const edge = { source: fn.id, target: calleeId, type: "calls" };
240344
+ const edge = { from: fn.id, to: calleeId, type: "calls", confidence: 1 };
239710
240345
  edges.push(edge);
239711
240346
  const out = outEdges.get(fn.id) ?? [];
239712
240347
  out.push(edge);
@@ -239726,7 +240361,7 @@ function detectCircularDeps(fns, lock) {
239726
240361
  const cycleStart = cyclePath.indexOf(id);
239727
240362
  const cycle = cyclePath.slice(cycleStart).map((cid) => lock.functions[cid]?.name ?? cid);
239728
240363
  cycle.push(lock.functions[id]?.name ?? id);
239729
- warnings.push(`\xC3\u201A\xC2\x8F Circular: ${cycle.join(" \xC3\xA2\xE2\u201A\xAC\xC2\xA0\xC3\xA2\xE2\u201A\xAC\xE2\u201E\xA2 ")}`);
240364
+ warnings.push(`\u26A0 Circular: ${cycle.join(" -> ")}`);
239730
240365
  return true;
239731
240366
  }
239732
240367
  if (visited.has(id)) return false;
@@ -239861,7 +240496,7 @@ async function safeRead(filePath) {
239861
240496
  }
239862
240497
 
239863
240498
  // src/server.ts
239864
- var VERSION = true ? "1.9.1" : "0.0.0-dev";
240499
+ var VERSION = true ? "1.10.0" : "0.0.0-dev";
239865
240500
  function createMikkMcpServer(projectRoot) {
239866
240501
  const server2 = new McpServer({
239867
240502
  name: "mikk",