@getmikk/mcp-server 1.9.0 → 1.9.2

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/bin/mikk-mcp.js CHANGED
@@ -18,4 +18,13 @@ if (idx !== -1 && process.argv[idx + 1]) {
18
18
  process.env.MIKK_PROJECT_ROOT = projectRoot
19
19
 
20
20
  // Load the CJS bundle (auto-starts stdio server via src/index.ts)
21
- require('../dist/index.cjs')
21
+ const mod = require('../dist/index.cjs')
22
+ if (mod.startStdioServer) {
23
+ mod.startStdioServer().catch(err => {
24
+ console.error('Failed to start MCP server:', err)
25
+ process.exit(1)
26
+ })
27
+ } else {
28
+ console.error('MCP server bundle is missing startStdioServer export.')
29
+ process.exit(1)
30
+ }
package/dist/index.cjs CHANGED
@@ -15394,7 +15394,7 @@ ${lanes.join("\n")}
15394
15394
  writeOutputIsTTY() {
15395
15395
  return process.stdout.isTTY;
15396
15396
  },
15397
- readFile: readFile7,
15397
+ readFile: readFile6,
15398
15398
  writeFile: writeFile22,
15399
15399
  watchFile: watchFile2,
15400
15400
  watchDirectory,
@@ -15600,7 +15600,7 @@ ${lanes.join("\n")}
15600
15600
  callback
15601
15601
  );
15602
15602
  }
15603
- function readFile7(fileName, _encoding) {
15603
+ function readFile6(fileName, _encoding) {
15604
15604
  let buffer;
15605
15605
  try {
15606
15606
  buffer = _fs.readFileSync(fileName);
@@ -52040,7 +52040,7 @@ ${lanes.join("\n")}
52040
52040
  const possibleOption = getSpellingSuggestion(unknownOption, diagnostics.optionDeclarations, getOptionName);
52041
52041
  return possibleOption ? createDiagnosticForNodeInSourceFileOrCompilerDiagnostic(sourceFile, node, diagnostics.unknownDidYouMeanDiagnostic, unknownOptionErrorText || unknownOption, possibleOption.name) : createDiagnosticForNodeInSourceFileOrCompilerDiagnostic(sourceFile, node, diagnostics.unknownOptionDiagnostic, unknownOptionErrorText || unknownOption);
52042
52042
  }
52043
- function parseCommandLineWorker(diagnostics, commandLine, readFile7) {
52043
+ function parseCommandLineWorker(diagnostics, commandLine, readFile6) {
52044
52044
  const options = {};
52045
52045
  let watchOptions;
52046
52046
  const fileNames = [];
@@ -52088,7 +52088,7 @@ ${lanes.join("\n")}
52088
52088
  }
52089
52089
  }
52090
52090
  function parseResponseFile(fileName) {
52091
- const text = tryReadFile(fileName, readFile7 || ((fileName2) => sys.readFile(fileName2)));
52091
+ const text = tryReadFile(fileName, readFile6 || ((fileName2) => sys.readFile(fileName2)));
52092
52092
  if (!isString(text)) {
52093
52093
  errors.push(text);
52094
52094
  return;
@@ -52190,8 +52190,8 @@ ${lanes.join("\n")}
52190
52190
  unknownDidYouMeanDiagnostic: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1,
52191
52191
  optionTypeMismatchDiagnostic: Diagnostics.Compiler_option_0_expects_an_argument
52192
52192
  };
52193
- function parseCommandLine(commandLine, readFile7) {
52194
- return parseCommandLineWorker(compilerOptionsDidYouMeanDiagnostics, commandLine, readFile7);
52193
+ function parseCommandLine(commandLine, readFile6) {
52194
+ return parseCommandLineWorker(compilerOptionsDidYouMeanDiagnostics, commandLine, readFile6);
52195
52195
  }
52196
52196
  function getOptionFromName(optionName, allowShort) {
52197
52197
  return getOptionDeclarationFromName(getOptionsNameMap, optionName, allowShort);
@@ -52273,8 +52273,8 @@ ${lanes.join("\n")}
52273
52273
  watchOptionsToExtend
52274
52274
  );
52275
52275
  }
52276
- function readConfigFile(fileName, readFile7) {
52277
- const textOrDiagnostic = tryReadFile(fileName, readFile7);
52276
+ function readConfigFile(fileName, readFile6) {
52277
+ const textOrDiagnostic = tryReadFile(fileName, readFile6);
52278
52278
  return isString(textOrDiagnostic) ? parseConfigFileTextToJson(fileName, textOrDiagnostic) : { config: {}, error: textOrDiagnostic };
52279
52279
  }
52280
52280
  function parseConfigFileTextToJson(fileName, jsonText) {
@@ -52289,14 +52289,14 @@ ${lanes.join("\n")}
52289
52289
  error: jsonSourceFile.parseDiagnostics.length ? jsonSourceFile.parseDiagnostics[0] : void 0
52290
52290
  };
52291
52291
  }
52292
- function readJsonConfigFile(fileName, readFile7) {
52293
- const textOrDiagnostic = tryReadFile(fileName, readFile7);
52292
+ function readJsonConfigFile(fileName, readFile6) {
52293
+ const textOrDiagnostic = tryReadFile(fileName, readFile6);
52294
52294
  return isString(textOrDiagnostic) ? parseJsonText(fileName, textOrDiagnostic) : { fileName, parseDiagnostics: [textOrDiagnostic] };
52295
52295
  }
52296
- function tryReadFile(fileName, readFile7) {
52296
+ function tryReadFile(fileName, readFile6) {
52297
52297
  let text;
52298
52298
  try {
52299
- text = readFile7(fileName);
52299
+ text = readFile6(fileName);
52300
52300
  } catch (e) {
52301
52301
  return createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e.message);
52302
52302
  }
@@ -141824,12 +141824,12 @@ ${lanes.join("\n")}
141824
141824
  function createCompilerHost(options, setParentNodes) {
141825
141825
  return createCompilerHostWorker(options, setParentNodes);
141826
141826
  }
141827
- function createGetSourceFile(readFile7, setParentNodes) {
141827
+ function createGetSourceFile(readFile6, setParentNodes) {
141828
141828
  return (fileName, languageVersionOrOptions, onError) => {
141829
141829
  let text;
141830
141830
  try {
141831
141831
  mark("beforeIORead");
141832
- text = readFile7(fileName);
141832
+ text = readFile6(fileName);
141833
141833
  mark("afterIORead");
141834
141834
  measure("I/O Read", "beforeIORead", "afterIORead");
141835
141835
  } catch (e) {
@@ -142733,7 +142733,7 @@ ${lanes.join("\n")}
142733
142733
  getRedirectFromOutput,
142734
142734
  forEachResolvedProjectReference: forEachResolvedProjectReference2
142735
142735
  });
142736
- const readFile7 = host.readFile.bind(host);
142736
+ const readFile6 = host.readFile.bind(host);
142737
142737
  (_e = tracing) == null ? void 0 : _e.push(tracing.Phase.Program, "shouldProgramCreateNewSourceFiles", { hasOldProgram: !!oldProgram });
142738
142738
  const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options);
142739
142739
  (_f = tracing) == null ? void 0 : _f.pop();
@@ -142959,7 +142959,7 @@ ${lanes.join("\n")}
142959
142959
  shouldTransformImportCall,
142960
142960
  emitBuildInfo,
142961
142961
  fileExists: fileExists2,
142962
- readFile: readFile7,
142962
+ readFile: readFile6,
142963
142963
  directoryExists,
142964
142964
  getSymlinkCache,
142965
142965
  realpath: (_o = host.realpath) == null ? void 0 : _o.bind(host),
@@ -236933,7 +236933,8 @@ var ImpactAnalyzer = class {
236933
236933
  analyze(changedNodeIds) {
236934
236934
  const visited = /* @__PURE__ */ new Set();
236935
236935
  const depthMap = /* @__PURE__ */ new Map();
236936
- const queue = changedNodeIds.map((id) => ({ id, depth: 0 }));
236936
+ const pathConfidence = /* @__PURE__ */ new Map();
236937
+ const queue = changedNodeIds.map((id) => ({ id, depth: 0, confidence: 1 }));
236937
236938
  let maxDepth = 0;
236938
236939
  const changedSet = new Set(changedNodeIds);
236939
236940
  const changedModules = /* @__PURE__ */ new Set();
@@ -236943,16 +236944,19 @@ var ImpactAnalyzer = class {
236943
236944
  changedModules.add(node.moduleId);
236944
236945
  }
236945
236946
  while (queue.length > 0) {
236946
- const { id: current, depth } = queue.shift();
236947
+ const { id: current, depth, confidence: pathConf } = queue.shift();
236947
236948
  if (visited.has(current))
236948
236949
  continue;
236949
236950
  visited.add(current);
236950
236951
  depthMap.set(current, depth);
236952
+ pathConfidence.set(current, pathConf);
236951
236953
  maxDepth = Math.max(maxDepth, depth);
236952
236954
  const dependents = this.graph.inEdges.get(current) || [];
236953
236955
  for (const edge of dependents) {
236954
236956
  if (!visited.has(edge.source) && edge.type !== "contains") {
236955
- queue.push({ id: edge.source, depth: depth + 1 });
236957
+ const edgeConf = edge.confidence ?? 1;
236958
+ const newPathConf = Math.min(pathConf, edgeConf);
236959
+ queue.push({ id: edge.source, depth: depth + 1, confidence: newPathConf });
236956
236960
  }
236957
236961
  }
236958
236962
  }
@@ -236984,19 +236988,33 @@ var ImpactAnalyzer = class {
236984
236988
  changed: changedNodeIds,
236985
236989
  impacted,
236986
236990
  depth: maxDepth,
236987
- confidence: this.computeConfidence(impacted.length, maxDepth),
236991
+ confidence: this.computeConfidence(impacted, pathConfidence),
236988
236992
  classified
236989
236993
  };
236990
236994
  }
236991
236995
  /**
236992
- * How confident are we in this impact analysis?
236993
- * High = few nodes affected, shallow depth
236994
- * Low = many nodes affected, deep chains
236996
+ * Derive confidence from the actual quality of edges traversed, not from
236997
+ * result size. A small result built from fuzzy/unresolved edges is LOW
236998
+ * confidence; a large result built from high-confidence AST edges is HIGH.
236999
+ *
237000
+ * Algorithm:
237001
+ * - Compute the average minimum-path-confidence across all impacted nodes.
237002
+ * - Penalise for deep chains (they amplify uncertainty).
237003
+ * - Map the combined score to HIGH / MEDIUM / LOW.
236995
237004
  */
236996
- computeConfidence(impactedCount, depth) {
236997
- if (impactedCount < 5 && depth < 3)
237005
+ computeConfidence(impacted, pathConfidence) {
237006
+ if (impacted.length === 0)
236998
237007
  return "high";
236999
- if (impactedCount < 20 && depth < 6)
237008
+ let total = 0;
237009
+ for (const id of impacted) {
237010
+ total += pathConfidence.get(id) ?? 1;
237011
+ }
237012
+ const avgConf = total / impacted.length;
237013
+ const sizePenalty = impacted.length > 20 ? 0.15 : impacted.length > 10 ? 0.08 : 0;
237014
+ const score = avgConf - sizePenalty;
237015
+ if (score >= 0.75)
237016
+ return "high";
237017
+ if (score >= 0.5)
237000
237018
  return "medium";
237001
237019
  return "low";
237002
237020
  }
@@ -237007,7 +237025,6 @@ var ENTRY_POINT_PATTERNS = [
237007
237025
  /^(main|bootstrap|start|init|setup|configure|register|mount)$/i,
237008
237026
  /^(app|server|index|mod|program)$/i,
237009
237027
  /Handler$/i,
237010
- // Express/Koa/Hono handlers
237011
237028
  /Middleware$/i,
237012
237029
  /Controller$/i,
237013
237030
  /^use[A-Z]/,
@@ -237023,14 +237040,26 @@ var TEST_PATTERNS = [
237023
237040
  /\.spec\./,
237024
237041
  /__test__/
237025
237042
  ];
237043
+ var DYNAMIC_USAGE_PATTERNS = [
237044
+ /^addEventListener$/i,
237045
+ /^removeEventListener$/i,
237046
+ /^on[A-Z]/,
237047
+ /(invoke|dispatch|emit|call|apply)/i,
237048
+ /^ngOnInit$/i,
237049
+ /^componentDidMount$/i,
237050
+ /^componentWillUnmount$/i
237051
+ ];
237026
237052
  var DeadCodeDetector = class {
237027
237053
  graph;
237028
237054
  lock;
237029
237055
  routeHandlers;
237056
+ /** Files that have at least one unresolved import (empty resolvedPath) */
237057
+ filesWithUnresolvedImports;
237030
237058
  constructor(graph, lock) {
237031
237059
  this.graph = graph;
237032
237060
  this.lock = lock;
237033
237061
  this.routeHandlers = new Set((lock.routes ?? []).map((r) => r.handler).filter(Boolean));
237062
+ this.filesWithUnresolvedImports = this.buildUnresolvedImportFileSet();
237034
237063
  }
237035
237064
  detect() {
237036
237065
  const dead = [];
@@ -237049,13 +237078,15 @@ var DeadCodeDetector = class {
237049
237078
  continue;
237050
237079
  if (this.isExempt(fn, id))
237051
237080
  continue;
237081
+ const confidence = this.inferConfidence(fn);
237052
237082
  const entry = {
237053
237083
  id,
237054
237084
  name: fn.name,
237055
237085
  file: fn.file,
237056
237086
  moduleId,
237057
237087
  type: "function",
237058
- reason: this.inferReason(fn, id)
237088
+ reason: this.inferReason(fn),
237089
+ confidence
237059
237090
  };
237060
237091
  dead.push(entry);
237061
237092
  byModule[moduleId].dead++;
@@ -237069,9 +237100,7 @@ var DeadCodeDetector = class {
237069
237100
  }
237070
237101
  const inEdges = this.graph.inEdges.get(id) || [];
237071
237102
  const hasCallers = inEdges.some((e) => e.type === "calls" || e.type === "imports");
237072
- if (hasCallers)
237073
- continue;
237074
- if (cls.isExported)
237103
+ if (hasCallers || cls.isExported)
237075
237104
  continue;
237076
237105
  const entry = {
237077
237106
  id,
@@ -237079,7 +237108,8 @@ var DeadCodeDetector = class {
237079
237108
  file: cls.file,
237080
237109
  moduleId,
237081
237110
  type: "class",
237082
- reason: "Class has no callers or importers and is not exported"
237111
+ reason: "Class has no callers or importers and is not exported",
237112
+ confidence: this.filesWithUnresolvedImports.has(cls.file) ? "medium" : "high"
237083
237113
  };
237084
237114
  dead.push(entry);
237085
237115
  byModule[moduleId].dead++;
@@ -237094,7 +237124,7 @@ var DeadCodeDetector = class {
237094
237124
  byModule
237095
237125
  };
237096
237126
  }
237097
- // ─── Exemption checks ──────────────────────────────────────────
237127
+ // ─── Private helpers ───────────────────────────────────────────
237098
237128
  isExempt(fn, id) {
237099
237129
  if (fn.isExported)
237100
237130
  return true;
@@ -237106,24 +237136,66 @@ var DeadCodeDetector = class {
237106
237136
  return true;
237107
237137
  if (fn.name === "constructor" || fn.name === "__init__")
237108
237138
  return true;
237109
- if (this.isCalledByExportedInSameFile(fn, id))
237139
+ if (this.isCalledByExportedInSameFile(fn))
237110
237140
  return true;
237111
237141
  return false;
237112
237142
  }
237113
- isCalledByExportedInSameFile(fn, fnId) {
237143
+ isCalledByExportedInSameFile(fn) {
237114
237144
  for (const callerId of fn.calledBy) {
237115
237145
  const caller = this.lock.functions[callerId];
237116
- if (caller && caller.isExported && caller.file === fn.file) {
237146
+ if (caller && caller.isExported && caller.file === fn.file)
237117
237147
  return true;
237118
- }
237119
237148
  }
237120
237149
  return false;
237121
237150
  }
237122
- inferReason(fn, id) {
237151
+ /**
237152
+ * Assign a confidence level to a dead code finding.
237153
+ *
237154
+ * Priority (first match wins):
237155
+ * medium — lock.calledBy has entries that didn't become graph edges:
237156
+ * something references this function but resolution failed.
237157
+ * medium — file has unresolved imports: the graph may be incomplete.
237158
+ * low — function name matches common dynamic-dispatch patterns.
237159
+ * high — none of the above: safe to remove.
237160
+ */
237161
+ inferConfidence(fn) {
237162
+ if (fn.calledBy.length > 0)
237163
+ return "medium";
237164
+ if (this.filesWithUnresolvedImports.has(fn.file))
237165
+ return "medium";
237166
+ if (DYNAMIC_USAGE_PATTERNS.some((p) => p.test(fn.name)))
237167
+ return "low";
237168
+ return "high";
237169
+ }
237170
+ inferReason(fn) {
237123
237171
  if (fn.calledBy.length === 0) {
237124
237172
  return "No callers found anywhere in the codebase";
237125
237173
  }
237126
- return `${fn.calledBy.length} references exist but none resolved to active call edges`;
237174
+ return `${fn.calledBy.length} reference(s) in lock but none resolved to active call edges`;
237175
+ }
237176
+ /**
237177
+ * Build the set of file paths that have at least one import whose
237178
+ * resolvedPath is empty. Used to downgrade confidence for all dead
237179
+ * findings in those files, since the graph may be incomplete.
237180
+ *
237181
+ * We derive this from the lock's file entries. Each file entry stores
237182
+ * its imports; any import with an empty resolvedPath (or no match in
237183
+ * the graph nodes) indicates an unresolved dependency.
237184
+ */
237185
+ buildUnresolvedImportFileSet() {
237186
+ const result = /* @__PURE__ */ new Set();
237187
+ if (!this.lock.files)
237188
+ return result;
237189
+ for (const [filePath, fileInfo] of Object.entries(this.lock.files)) {
237190
+ const imports = fileInfo.imports ?? [];
237191
+ for (const imp of imports) {
237192
+ if (!imp.resolvedPath || imp.resolvedPath === "") {
237193
+ result.add(filePath);
237194
+ break;
237195
+ }
237196
+ }
237197
+ }
237198
+ return result;
237127
237199
  }
237128
237200
  };
237129
237201
 
@@ -237272,22 +237344,43 @@ var MikkLockSchema = external_exports.object({
237272
237344
  })
237273
237345
  });
237274
237346
 
237275
- // ../core/dist/contract/contract-reader.js
237347
+ // ../core/dist/utils/json.js
237276
237348
  var fs = __toESM(require("node:fs/promises"), 1);
237349
+ async function readJsonSafe(filePath, fileLabel = "JSON file") {
237350
+ let content;
237351
+ try {
237352
+ content = await fs.readFile(filePath, "utf-8");
237353
+ } catch (e) {
237354
+ if (e.code === "ENOENT") {
237355
+ throw e;
237356
+ }
237357
+ throw new Error(`Failed to read ${fileLabel}: ${e.message}`);
237358
+ }
237359
+ const sanitized = content.replace(/^\uFEFF/, "");
237360
+ try {
237361
+ return JSON.parse(sanitized);
237362
+ } catch (e) {
237363
+ throw new Error(`Malformed ${fileLabel}: Syntax error - ${e.message}`);
237364
+ }
237365
+ }
237366
+
237367
+ // ../core/dist/contract/contract-reader.js
237277
237368
  var ContractReader = class {
237278
237369
  /** Read and validate mikk.json */
237279
237370
  async read(contractPath) {
237280
- let content;
237371
+ let json;
237281
237372
  try {
237282
- content = await fs.readFile(contractPath, "utf-8");
237283
- } catch {
237284
- throw new ContractNotFoundError(contractPath);
237373
+ json = await readJsonSafe(contractPath, "mikk.json");
237374
+ } catch (e) {
237375
+ if (e.code === "ENOENT") {
237376
+ throw new ContractNotFoundError(contractPath);
237377
+ }
237378
+ throw e;
237285
237379
  }
237286
- const json = JSON.parse(content.replace(/^\uFEFF/, ""));
237287
237380
  const result = MikkContractSchema.safeParse(json);
237288
237381
  if (!result.success) {
237289
237382
  const errors = result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n");
237290
- throw new Error(`Invalid mikk.json:
237383
+ throw new Error(`Invalid mikk.json structure:
237291
237384
  ${errors}`);
237292
237385
  }
237293
237386
  return result.data;
@@ -237299,18 +237392,20 @@ var fs2 = __toESM(require("node:fs/promises"), 1);
237299
237392
  var LockReader = class {
237300
237393
  /** Read and validate mikk.lock.json */
237301
237394
  async read(lockPath) {
237302
- let content;
237395
+ let json;
237303
237396
  try {
237304
- content = await fs2.readFile(lockPath, "utf-8");
237305
- } catch {
237306
- throw new LockNotFoundError();
237397
+ json = await readJsonSafe(lockPath, "mikk.lock.json");
237398
+ } catch (e) {
237399
+ if (e.code === "ENOENT") {
237400
+ throw new LockNotFoundError();
237401
+ }
237402
+ throw e;
237307
237403
  }
237308
- const json = JSON.parse(content.replace(/^\uFEFF/, ""));
237309
237404
  const hydrated = hydrateLock(json);
237310
237405
  const result = MikkLockSchema.safeParse(hydrated);
237311
237406
  if (!result.success) {
237312
237407
  const errors = result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n");
237313
- throw new Error(`Invalid mikk.lock.json:
237408
+ throw new Error(`Invalid mikk.lock.json structure:
237314
237409
  ${errors}`);
237315
237410
  }
237316
237411
  return result.data;
@@ -238659,6 +238754,38 @@ var IntentSchema = external_exports.object({
238659
238754
  // src/tools.ts
238660
238755
  var projectCache = /* @__PURE__ */ new Map();
238661
238756
  var CACHE_TTL_MS = 3e4;
238757
+ var _CPT = 4;
238758
+ var _ALC = 42;
238759
+ var _tallies = /* @__PURE__ */ new Map();
238760
+ function _tally(r) {
238761
+ let t = _tallies.get(r);
238762
+ if (!t) {
238763
+ t = { calls: 0, used: 0, raw: 0, saved: 0, start: Date.now() };
238764
+ _tallies.set(r, t);
238765
+ }
238766
+ return t;
238767
+ }
238768
+ function _tok(o) {
238769
+ return Math.max(1, Math.round(JSON.stringify(o).length / _CPT));
238770
+ }
238771
+ function _fileTok(lock, fp) {
238772
+ const fs22 = Object.values(lock.functions).filter((f) => f.file === fp);
238773
+ const ln = fs22.length > 0 ? Math.max(...fs22.map((f) => f.endLine)) : 80;
238774
+ return Math.round(ln * _ALC / _CPT);
238775
+ }
238776
+ function _filesTok(lock, fps) {
238777
+ return fps.reduce((s, f) => s + _fileTok(lock, f), 0);
238778
+ }
238779
+ function _track(root, raw, resp) {
238780
+ const used = _tok(resp);
238781
+ const saved = Math.max(0, raw - used);
238782
+ const t = _tally(root);
238783
+ t.calls++;
238784
+ t.used += used;
238785
+ t.raw += raw;
238786
+ t.saved += saved;
238787
+ return { used, raw, saved, sessionSaved: t.saved, sessionCalls: t.calls };
238788
+ }
238662
238789
  var semanticSearchers = /* @__PURE__ */ new Map();
238663
238790
  function getSemanticSearcher(projectRoot) {
238664
238791
  let s = semanticSearchers.get(projectRoot);
@@ -238706,12 +238833,14 @@ function registerTools(server2, projectRoot) {
238706
238833
  warning: staleness,
238707
238834
  hint: "Next: Use mikk_query_context with your task description, or mikk_list_modules to explore the architecture."
238708
238835
  };
238836
+ const _rawOverview = Math.min(15, Object.keys(lock.files).length) * Math.round(80 * _ALC / _CPT);
238837
+ overview.tokens = _track(projectRoot, _rawOverview, overview);
238709
238838
  return { content: [{ type: "text", text: JSON.stringify(overview, null, 2) }] };
238710
238839
  }
238711
238840
  );
238712
238841
  server2.tool(
238713
238842
  "mikk_query_context",
238714
- "Ask an architecture question \u201D returns graph-traced context with relevant functions, files, and call chains. Use this to understand how code flows through the project.",
238843
+ "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.",
238715
238844
  {
238716
238845
  question: external_exports.string().describe("The architecture question or task description"),
238717
238846
  maxHops: external_exports.number().optional().default(4).describe("Graph traversal depth (default: 4)"),
@@ -238748,8 +238877,14 @@ function registerTools(server2, projectRoot) {
238748
238877
  const warning = staleness ? `
238749
238878
 
238750
238879
  ${staleness}` : "";
238880
+ const _rawQC = (tokenBudget ?? 6e3) * 3;
238881
+ const _tokQC = _track(projectRoot, _rawQC, output);
238882
+ const tokLine = `
238883
+
238884
+ ---
238885
+ // tokens: ${JSON.stringify(_tokQC)}`;
238751
238886
  return {
238752
- 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." }]
238887
+ 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 }]
238753
238888
  };
238754
238889
  }
238755
238890
  );
@@ -238802,6 +238937,8 @@ ${staleness}` : "";
238802
238937
  warning: staleness,
238803
238938
  hint: "Next: Use mikk_get_function_detail on critical/high items to review them. Then mikk_before_edit to validate your planned changes."
238804
238939
  };
238940
+ const _rawIA = _fileTok(lock, normalizedFile) + result.impacted.length * Math.round(40 * _ALC / _CPT);
238941
+ response.tokens = _track(projectRoot, _rawIA, response);
238805
238942
  return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
238806
238943
  }
238807
238944
  );
@@ -238911,8 +239048,10 @@ ${staleness}` : "";
238911
239048
  constraintStatus: totalViolations === 0 ? "pass" : "fail",
238912
239049
  files: fileReports,
238913
239050
  warning: staleness,
238914
- hint: totalViolations > 0 ? "\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."
239051
+ 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."
238915
239052
  };
239053
+ const _rawBE = _filesTok(lock, filesToEdit) * 4;
239054
+ response.tokens = _track(projectRoot, _rawBE, response);
238916
239055
  return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
238917
239056
  }
238918
239057
  );
@@ -239043,7 +239182,7 @@ ${staleness}` : "";
239043
239182
  content: [{
239044
239183
  type: "text",
239045
239184
  text: [
239046
- "\x9D\u0152 Semantic search requires @xenova/transformers.",
239185
+ "\u26A0 Semantic search requires @xenova/transformers.",
239047
239186
  "",
239048
239187
  "Install it in your project root:",
239049
239188
  " npm install @xenova/transformers",
@@ -239172,7 +239311,7 @@ ${content}`
239172
239311
  );
239173
239312
  server2.tool(
239174
239313
  "mikk_dead_code",
239175
- "Detect dead code \u201D functions with zero callers after exempting exports, entry points, route handlers, tests, and constructors. Use this before refactoring or cleanup.",
239314
+ "Detect dead code \u2014 functions with zero callers after exempting exports, entry points, route handlers, tests, and constructors. Use this before refactoring or cleanup.",
239176
239315
  {
239177
239316
  moduleId: external_exports.string().optional().describe("Filter results to a specific module ID")
239178
239317
  },
@@ -239300,6 +239439,8 @@ ${content}`
239300
239439
  warning: staleness,
239301
239440
  hint: modified.length + added.length > 0 ? "Run `mikk analyze` to update the lock file with these changes." : "Codebase is in sync with the lock file."
239302
239441
  };
239442
+ const _rawGC = Math.min(50, Object.keys(lock.files).length) * Math.round(60 * _ALC / _CPT);
239443
+ response.tokens = _track(projectRoot, _rawGC, response);
239303
239444
  return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
239304
239445
  }
239305
239446
  );
@@ -239345,7 +239486,7 @@ ${content}` }]
239345
239486
  (f) => (f.name === fnName || f.name.endsWith(`.${fnName}`)) && (f.file === normalizedFile || f.file.endsWith("/" + normalizedFile))
239346
239487
  );
239347
239488
  if (!fn) {
239348
- sections.push(`// \x9D\u0152 Function "${fnName}" not found in ${file}`);
239489
+ sections.push(`// \u26A0 Function "${fnName}" not found in ${file}`);
239349
239490
  continue;
239350
239491
  }
239351
239492
  const header = [
@@ -239368,7 +239509,10 @@ ${body}`);
239368
239509
  const warningText = staleness ? `
239369
239510
 
239370
239511
  ${staleness}` : "";
239371
- return { content: [{ type: "text", text: output + warningText }] };
239512
+ const _rawRF = _fileTok(lock, file.replace(/\\/g, "/"));
239513
+ const _tokRF = _track(projectRoot, _rawRF, output);
239514
+ return { content: [{ type: "text", text: output + warningText + `
239515
+ // tokens: ${JSON.stringify(_tokRF)}` }] };
239372
239516
  }
239373
239517
  );
239374
239518
  server2.tool(
@@ -239432,6 +239576,8 @@ ${staleness}` : "";
239432
239576
  warning: staleness,
239433
239577
  hint: changedCount > 0 ? `${changedCount} file(s) may have changed. Run \`mikk analyze\` for accurate results, or use mikk_get_changes for details.` : "Codebase is in sync. Use mikk_query_context with your task description to get started."
239434
239578
  };
239579
+ const _rawSC = Math.min(20, Object.keys(lock.files).length) * Math.round(100 * _ALC / _CPT);
239580
+ response.tokens = _track(projectRoot, _rawSC, response);
239435
239581
  return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
239436
239582
  }
239437
239583
  );
@@ -239536,6 +239682,37 @@ ${staleness}` : "";
239536
239682
  };
239537
239683
  }
239538
239684
  );
239685
+ server2.tool(
239686
+ "mikk_token_stats",
239687
+ "Show token savings for this session \u2014 how many tokens Mikk saved vs. the agent reading raw source files. WHEN TO USE: Any time. Useful at end of session to see cumulative efficiency. Returns per-session totals and cost estimates.",
239688
+ {},
239689
+ async () => {
239690
+ const t = _tally(projectRoot);
239691
+ const { lock } = await loadContractAndLock(projectRoot);
239692
+ const totalFileLine = Object.values(lock.functions).reduce((s, f) => s + (f.endLine - f.startLine + 1), 0);
239693
+ const fullCodebaseTok = Math.round(totalFileLine * _ALC / _CPT);
239694
+ const elapsedMin = Math.round((Date.now() - t.start) / 6e4);
239695
+ const response = {
239696
+ session: {
239697
+ calls: t.calls,
239698
+ elapsedMinutes: elapsedMin
239699
+ },
239700
+ tokens: {
239701
+ used: t.used,
239702
+ rawWouldHaveCost: t.raw,
239703
+ saved: t.saved,
239704
+ savingsPercent: t.raw > 0 ? Math.round(t.saved / t.raw * 100) : 0
239705
+ },
239706
+ context: {
239707
+ fullCodebaseTokens: fullCodebaseTok,
239708
+ percentOfCodebaseRead: t.raw > 0 ? Math.round(t.used / fullCodebaseTok * 100) : 0,
239709
+ note: "Full codebase = if agent read every tracked source line once"
239710
+ },
239711
+ interpretation: t.saved > 0 ? `Mikk saved ~${t.saved.toLocaleString()} tokens this session (${Math.round(t.saved / t.raw * 100)}% reduction). Roughly ${Math.round(t.saved / 1e3)}k tokens = ~${(t.saved * 3e-6).toFixed(3)} USD at GPT-4o rates.` : "No tools called yet this session."
239712
+ };
239713
+ return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
239714
+ }
239715
+ );
239539
239716
  }
239540
239717
  async function loadContractAndLock(projectRoot) {
239541
239718
  const cached2 = projectCache.get(projectRoot);
@@ -239549,7 +239726,7 @@ async function loadContractAndLock(projectRoot) {
239549
239726
  const syncStatus = lock.syncState?.status ?? "unknown";
239550
239727
  let staleness = null;
239551
239728
  if (syncStatus === "drifted" || syncStatus === "conflict") {
239552
- staleness = `\x8F Lock file is ${syncStatus}. Run \`mikk analyze\` for accurate results.`;
239729
+ staleness = `\u26A0 Lock file is ${syncStatus}. Run \`mikk analyze\` for accurate results.`;
239553
239730
  }
239554
239731
  if (!staleness) {
239555
239732
  const fileEntries = Object.entries(lock.files);
@@ -239572,7 +239749,7 @@ async function loadContractAndLock(projectRoot) {
239572
239749
  }
239573
239750
  }
239574
239751
  if (mismatched > 0) {
239575
- staleness = `\x8F STALE: ${mismatched} file(s) changed since last analysis (${mismatchedFiles.slice(0, 3).join(", ")}${mismatched > 3 ? "..." : ""}). Run \`mikk analyze\`.`;
239752
+ staleness = `\u26A0 STALE: ${mismatched} file(s) changed since last analysis (${mismatchedFiles.slice(0, 3).join(", ")}${mismatched > 3 ? "..." : ""}). Run \`mikk analyze\`.`;
239576
239753
  }
239577
239754
  }
239578
239755
  const graph = buildGraphFromLock(lock);
@@ -239644,7 +239821,7 @@ function detectCircularDeps(fns, lock) {
239644
239821
  const cycleStart = cyclePath.indexOf(id);
239645
239822
  const cycle = cyclePath.slice(cycleStart).map((cid) => lock.functions[cid]?.name ?? cid);
239646
239823
  cycle.push(lock.functions[id]?.name ?? id);
239647
- warnings.push(`\x8F Circular: ${cycle.join(" \u2020\u2019 ")}`);
239824
+ warnings.push(`\u26A0 Circular: ${cycle.join(" -> ")}`);
239648
239825
  return true;
239649
239826
  }
239650
239827
  if (visited.has(id)) return false;
@@ -239779,7 +239956,7 @@ async function safeRead(filePath) {
239779
239956
  }
239780
239957
 
239781
239958
  // src/server.ts
239782
- var VERSION = true ? "1.9.0" : "0.0.0-dev";
239959
+ var VERSION = true ? "1.9.2" : "0.0.0-dev";
239783
239960
  function createMikkMcpServer(projectRoot) {
239784
239961
  const server2 = new McpServer({
239785
239962
  name: "mikk",
@@ -239889,9 +240066,6 @@ async function startStdioServer() {
239889
240066
  const transport = new StdioServerTransport();
239890
240067
  await server2.connect(transport);
239891
240068
  }
239892
-
239893
- // src/index.ts
239894
- startStdioServer();
239895
240069
  // Annotate the CommonJS export names for ESM import in node:
239896
240070
  0 && (module.exports = {
239897
240071
  createMikkMcpServer,