@getmikk/mcp-server 1.8.1 → 1.9.1

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
@@ -41845,7 +41845,7 @@ ${lanes.join("\n")}
41845
41845
  jsDocParsingMode
41846
41846
  } = typeof languageVersionOrOptions === "object" ? languageVersionOrOptions : { languageVersion: languageVersionOrOptions };
41847
41847
  if (languageVersion === 100) {
41848
- result = Parser.parseSourceFile(
41848
+ result = Parser2.parseSourceFile(
41849
41849
  fileName,
41850
41850
  sourceText,
41851
41851
  languageVersion,
@@ -41861,7 +41861,7 @@ ${lanes.join("\n")}
41861
41861
  file.impliedNodeFormat = format;
41862
41862
  return (overrideSetExternalModuleIndicator || setExternalModuleIndicator)(file);
41863
41863
  };
41864
- result = Parser.parseSourceFile(
41864
+ result = Parser2.parseSourceFile(
41865
41865
  fileName,
41866
41866
  sourceText,
41867
41867
  languageVersion,
@@ -41879,10 +41879,10 @@ ${lanes.join("\n")}
41879
41879
  return result;
41880
41880
  }
41881
41881
  function parseIsolatedEntityName(text, languageVersion) {
41882
- return Parser.parseIsolatedEntityName(text, languageVersion);
41882
+ return Parser2.parseIsolatedEntityName(text, languageVersion);
41883
41883
  }
41884
41884
  function parseJsonText(fileName, sourceText) {
41885
- return Parser.parseJsonText(fileName, sourceText);
41885
+ return Parser2.parseJsonText(fileName, sourceText);
41886
41886
  }
41887
41887
  function isExternalModule(file) {
41888
41888
  return file.externalModuleIndicator !== void 0;
@@ -41893,17 +41893,17 @@ ${lanes.join("\n")}
41893
41893
  return newSourceFile;
41894
41894
  }
41895
41895
  function parseIsolatedJSDocComment(content, start, length2) {
41896
- const result = Parser.JSDocParser.parseIsolatedJSDocComment(content, start, length2);
41896
+ const result = Parser2.JSDocParser.parseIsolatedJSDocComment(content, start, length2);
41897
41897
  if (result && result.jsDoc) {
41898
- Parser.fixupParentReferences(result.jsDoc);
41898
+ Parser2.fixupParentReferences(result.jsDoc);
41899
41899
  }
41900
41900
  return result;
41901
41901
  }
41902
41902
  function parseJSDocTypeExpressionForTests(content, start, length2) {
41903
- return Parser.JSDocParser.parseJSDocTypeExpressionForTests(content, start, length2);
41903
+ return Parser2.JSDocParser.parseJSDocTypeExpressionForTests(content, start, length2);
41904
41904
  }
41905
- var Parser;
41906
- ((Parser2) => {
41905
+ var Parser2;
41906
+ ((Parser22) => {
41907
41907
  var scanner2 = createScanner(
41908
41908
  99,
41909
41909
  /*skipTrivia*/
@@ -42030,7 +42030,7 @@ ${lanes.join("\n")}
42030
42030
  clearState();
42031
42031
  return result;
42032
42032
  }
42033
- Parser2.parseSourceFile = parseSourceFile;
42033
+ Parser22.parseSourceFile = parseSourceFile;
42034
42034
  function parseIsolatedEntityName2(content, languageVersion2) {
42035
42035
  initializeState(
42036
42036
  "",
@@ -42051,7 +42051,7 @@ ${lanes.join("\n")}
42051
42051
  clearState();
42052
42052
  return isValid2 ? entityName : void 0;
42053
42053
  }
42054
- Parser2.parseIsolatedEntityName = parseIsolatedEntityName2;
42054
+ Parser22.parseIsolatedEntityName = parseIsolatedEntityName2;
42055
42055
  function parseJsonText2(fileName2, sourceText2, languageVersion2 = 2, syntaxCursor2, setParentNodes = false) {
42056
42056
  initializeState(
42057
42057
  fileName2,
@@ -42147,7 +42147,7 @@ ${lanes.join("\n")}
42147
42147
  clearState();
42148
42148
  return result;
42149
42149
  }
42150
- Parser2.parseJsonText = parseJsonText2;
42150
+ Parser22.parseJsonText = parseJsonText2;
42151
42151
  function initializeState(_fileName, _sourceText, _languageVersion, _syntaxCursor, _scriptKind, _jsDocParsingMode) {
42152
42152
  NodeConstructor2 = objectAllocator.getNodeConstructor();
42153
42153
  TokenConstructor2 = objectAllocator.getTokenConstructor();
@@ -42219,7 +42219,7 @@ ${lanes.join("\n")}
42219
42219
  }
42220
42220
  sourceFlags = contextFlags;
42221
42221
  nextToken();
42222
- const statements = parseList(0, parseStatement);
42222
+ const statements = parseList2(0, parseStatement);
42223
42223
  Debug.assert(
42224
42224
  token() === 1
42225
42225
  /* EndOfFileToken */
@@ -42353,7 +42353,7 @@ ${lanes.join("\n")}
42353
42353
  true
42354
42354
  );
42355
42355
  }
42356
- Parser2.fixupParentReferences = fixupParentReferences;
42356
+ Parser22.fixupParentReferences = fixupParentReferences;
42357
42357
  function createSourceFile2(fileName2, languageVersion2, scriptKind2, isDeclarationFile, statements, endOfFileToken, flags, setExternalModuleIndicator2) {
42358
42358
  let sourceFile = factory2.createSourceFile(statements, endOfFileToken, flags);
42359
42359
  setTextRangePosWidth(sourceFile, 0, sourceText.length);
@@ -43261,7 +43261,7 @@ ${lanes.join("\n")}
43261
43261
  }
43262
43262
  return false;
43263
43263
  }
43264
- function parseList(kind, parseElement) {
43264
+ function parseList2(kind, parseElement) {
43265
43265
  const saveParsingContext = parsingContext;
43266
43266
  parsingContext |= 1 << kind;
43267
43267
  const list = [];
@@ -44344,7 +44344,7 @@ ${lanes.join("\n")}
44344
44344
  19
44345
44345
  /* OpenBraceToken */
44346
44346
  )) {
44347
- members = parseList(4, parseTypeMember);
44347
+ members = parseList2(4, parseTypeMember);
44348
44348
  parseExpected(
44349
44349
  20
44350
44350
  /* CloseBraceToken */
@@ -44422,7 +44422,7 @@ ${lanes.join("\n")}
44422
44422
  }
44423
44423
  const type = parseTypeAnnotation();
44424
44424
  parseSemicolon();
44425
- const members = parseList(4, parseTypeMember);
44425
+ const members = parseList2(4, parseTypeMember);
44426
44426
  parseExpected(
44427
44427
  20
44428
44428
  /* CloseBraceToken */
@@ -45843,7 +45843,7 @@ ${lanes.join("\n")}
45843
45843
  }
45844
45844
  function parseJsxAttributes() {
45845
45845
  const pos = getNodePos();
45846
- return finishNode(factory2.createJsxAttributes(parseList(13, parseJsxAttribute)), pos);
45846
+ return finishNode(factory2.createJsxAttributes(parseList2(13, parseJsxAttribute)), pos);
45847
45847
  }
45848
45848
  function parseJsxOpeningOrSelfClosingElementOrOpeningFragment(inExpressionContext) {
45849
45849
  const pos = getNodePos();
@@ -46585,7 +46585,7 @@ ${lanes.join("\n")}
46585
46585
  const openBraceParsed = parseExpected(19, diagnosticMessage);
46586
46586
  if (openBraceParsed || ignoreMissingOpenBrace) {
46587
46587
  const multiLine = scanner2.hasPrecedingLineBreak();
46588
- const statements = parseList(1, parseStatement);
46588
+ const statements = parseList2(1, parseStatement);
46589
46589
  parseExpectedMatchingBrackets(19, 20, openBraceParsed, openBracePosition);
46590
46590
  const result = withJSDoc(finishNode(factoryCreateBlock(statements, multiLine), pos), hasJSDoc);
46591
46591
  if (token() === 64) {
@@ -46825,7 +46825,7 @@ ${lanes.join("\n")}
46825
46825
  59
46826
46826
  /* ColonToken */
46827
46827
  );
46828
- const statements = parseList(3, parseStatement);
46828
+ const statements = parseList2(3, parseStatement);
46829
46829
  return withJSDoc(finishNode(factory2.createCaseClause(expression, statements), pos), hasJSDoc);
46830
46830
  }
46831
46831
  function parseDefaultClause() {
@@ -46838,7 +46838,7 @@ ${lanes.join("\n")}
46838
46838
  59
46839
46839
  /* ColonToken */
46840
46840
  );
46841
- const statements = parseList(3, parseStatement);
46841
+ const statements = parseList2(3, parseStatement);
46842
46842
  return finishNode(factory2.createDefaultClause(statements), pos);
46843
46843
  }
46844
46844
  function parseCaseOrDefaultClause() {
@@ -46850,7 +46850,7 @@ ${lanes.join("\n")}
46850
46850
  19
46851
46851
  /* OpenBraceToken */
46852
46852
  );
46853
- const clauses = parseList(2, parseCaseOrDefaultClause);
46853
+ const clauses = parseList2(2, parseCaseOrDefaultClause);
46854
46854
  parseExpected(
46855
46855
  20
46856
46856
  /* CloseBraceToken */
@@ -47974,7 +47974,7 @@ ${lanes.join("\n")}
47974
47974
  }
47975
47975
  function parseHeritageClauses() {
47976
47976
  if (isHeritageClause2()) {
47977
- return parseList(22, parseHeritageClause);
47977
+ return parseList2(22, parseHeritageClause);
47978
47978
  }
47979
47979
  return void 0;
47980
47980
  }
@@ -48011,7 +48011,7 @@ ${lanes.join("\n")}
48011
48011
  return token() === 96 || token() === 119;
48012
48012
  }
48013
48013
  function parseClassMembers() {
48014
- return parseList(5, parseClassElement);
48014
+ return parseList2(5, parseClassElement);
48015
48015
  }
48016
48016
  function parseInterfaceDeclaration(pos, hasJSDoc, modifiers) {
48017
48017
  parseExpected(
@@ -48080,7 +48080,7 @@ ${lanes.join("\n")}
48080
48080
  19
48081
48081
  /* OpenBraceToken */
48082
48082
  )) {
48083
- statements = parseList(1, parseStatement);
48083
+ statements = parseList2(1, parseStatement);
48084
48084
  parseExpected(
48085
48085
  20
48086
48086
  /* CloseBraceToken */
@@ -49740,8 +49740,8 @@ ${lanes.join("\n")}
49740
49740
  return result2;
49741
49741
  }
49742
49742
  }
49743
- })(JSDocParser = Parser2.JSDocParser || (Parser2.JSDocParser = {}));
49744
- })(Parser || (Parser = {}));
49743
+ })(JSDocParser = Parser22.JSDocParser || (Parser22.JSDocParser = {}));
49744
+ })(Parser2 || (Parser2 = {}));
49745
49745
  var incrementallyParsedFiles = /* @__PURE__ */ new WeakSet();
49746
49746
  function markAsIncrementallyParsed(sourceFile) {
49747
49747
  if (incrementallyParsedFiles.has(sourceFile)) {
@@ -49768,7 +49768,7 @@ ${lanes.join("\n")}
49768
49768
  return sourceFile;
49769
49769
  }
49770
49770
  if (sourceFile.statements.length === 0) {
49771
- return Parser.parseSourceFile(
49771
+ return Parser2.parseSourceFile(
49772
49772
  sourceFile.fileName,
49773
49773
  newText,
49774
49774
  sourceFile.languageVersion,
@@ -49782,7 +49782,7 @@ ${lanes.join("\n")}
49782
49782
  );
49783
49783
  }
49784
49784
  markAsIncrementallyParsed(sourceFile);
49785
- Parser.fixupParentReferences(sourceFile);
49785
+ Parser2.fixupParentReferences(sourceFile);
49786
49786
  const oldText = sourceFile.text;
49787
49787
  const syntaxCursor = createSyntaxCursor(sourceFile);
49788
49788
  const changeRange = extendToAffectedRange(sourceFile, textChangeRange);
@@ -49792,7 +49792,7 @@ ${lanes.join("\n")}
49792
49792
  Debug.assert(textSpanEnd(textChangeRangeNewSpan(changeRange)) === textSpanEnd(textChangeRangeNewSpan(textChangeRange)));
49793
49793
  const delta = textChangeRangeNewSpan(changeRange).length - changeRange.span.length;
49794
49794
  updateTokenPositionsAndMarkElements(sourceFile, changeRange.span.start, textSpanEnd(changeRange.span), textSpanEnd(textChangeRangeNewSpan(changeRange)), delta, oldText, newText, aggressiveChecks);
49795
- const result = Parser.parseSourceFile(
49795
+ const result = Parser2.parseSourceFile(
49796
49796
  sourceFile.fileName,
49797
49797
  newText,
49798
49798
  sourceFile.languageVersion,
@@ -236750,6 +236750,7 @@ var EMPTY_COMPLETION_RESULT = {
236750
236750
  // src/tools.ts
236751
236751
  var path4 = __toESM(require("node:path"), 1);
236752
236752
  var fs6 = __toESM(require("node:fs/promises"), 1);
236753
+ var import_node_child_process = require("node:child_process");
236753
236754
  var import_node_crypto = require("node:crypto");
236754
236755
 
236755
236756
  // ../core/dist/parser/typescript/ts-extractor.js
@@ -236780,22 +236781,34 @@ var LockNotFoundError = class extends MikkError {
236780
236781
 
236781
236782
  // ../core/dist/parser/boundary-checker.js
236782
236783
  var path = __toESM(require("node:path"), 1);
236784
+ function stripPrefix(s) {
236785
+ return s.trim().replace(/^module:/, "");
236786
+ }
236787
+ function parseList(raw) {
236788
+ return raw.split(/,\s*/).map(stripPrefix).filter(Boolean);
236789
+ }
236783
236790
  function parseConstraint(constraint) {
236784
- const c = constraint.trim().toLowerCase();
236785
- const denyMatch = c.match(/^module:(\S+)\s+cannot\s+import\s+(.+)$/);
236786
- if (denyMatch) {
236787
- const toModules = denyMatch[2].split(",").map((s) => s.trim().replace("module:", "")).filter(Boolean);
236788
- return { type: "deny", fromModuleId: denyMatch[1], toModuleIds: toModules, raw: constraint };
236789
- }
236790
- const allowOnlyMatch = c.match(/^module:(\S+)\s+can\s+only\s+import\s+(.+)$/);
236791
- if (allowOnlyMatch) {
236792
- const toModules = allowOnlyMatch[2].split(",").map((s) => s.trim().replace("module:", "")).filter(Boolean);
236793
- return { type: "allow_only", fromModuleId: allowOnlyMatch[1], toModuleIds: toModules, raw: constraint };
236794
- }
236795
- const isolatedMatch = c.match(/^module:(\S+)\s+(has\s+no\s+imports|is\s+isolated)$/);
236796
- if (isolatedMatch) {
236797
- return { type: "isolated", fromModuleId: isolatedMatch[1], toModuleIds: [], raw: constraint };
236798
- }
236791
+ const c = constraint.trim();
236792
+ const l = c.toLowerCase();
236793
+ const natDeny = l.match(/^(\S+)\s+(?:must\s+not|cannot|should\s+not)\s+(?:import\s+from|import|call\s+into|call)\s+(.+)$/);
236794
+ if (natDeny)
236795
+ return { type: "deny", fromModuleId: stripPrefix(natDeny[1]), toModuleIds: parseList(natDeny[2]), raw: c };
236796
+ const natAllow = l.match(/^(\S+)\s+can\s+only\s+(?:import\s+from|import)\s+(.+)$/);
236797
+ if (natAllow)
236798
+ return { type: "allow_only", fromModuleId: stripPrefix(natAllow[1]), toModuleIds: parseList(natAllow[2]), raw: c };
236799
+ const natIso = l.match(/^(\S+)\s+(?:is\s+isolated|has\s+no\s+imports)$/);
236800
+ if (natIso)
236801
+ return { type: "isolated", fromModuleId: stripPrefix(natIso[1]), toModuleIds: [], raw: c };
236802
+ const legDeny = l.match(/^module:(\S+)\s+cannot\s+import\s+(.+)$/);
236803
+ if (legDeny)
236804
+ return { type: "deny", fromModuleId: legDeny[1], toModuleIds: parseList(legDeny[2]), raw: c };
236805
+ const legAllow = l.match(/^module:(\S+)\s+can\s+only\s+import\s+(.+)$/);
236806
+ if (legAllow)
236807
+ return { type: "allow_only", fromModuleId: legAllow[1], toModuleIds: parseList(legAllow[2]), raw: c };
236808
+ const legIso = l.match(/^module:(\S+)\s+(?:has\s+no\s+imports|is\s+isolated)$/);
236809
+ if (legIso)
236810
+ return { type: "isolated", fromModuleId: legIso[1], toModuleIds: [], raw: c };
236811
+ console.warn(`[mikk] Constraint skipped: "${c}" - use "auth must not import from payments"`);
236799
236812
  return null;
236800
236813
  }
236801
236814
  var BoundaryChecker = class {
@@ -236803,160 +236816,113 @@ var BoundaryChecker = class {
236803
236816
  lock;
236804
236817
  rules;
236805
236818
  moduleNames;
236806
- // id → name
236807
236819
  constructor(contract, lock) {
236808
236820
  this.contract = contract;
236809
236821
  this.lock = lock;
236810
236822
  this.rules = contract.declared.constraints.map(parseConstraint).filter((r) => r !== null);
236811
236823
  this.moduleNames = new Map(contract.declared.modules.map((m) => [m.id, m.name]));
236812
236824
  }
236813
- /** Run boundary check. Returns pass/fail + all violations. */
236814
236825
  check() {
236815
236826
  const violations = [];
236816
236827
  for (const fn of Object.values(this.lock.functions)) {
236828
+ if (fn.moduleId === "unknown")
236829
+ continue;
236817
236830
  for (const calleeId of fn.calls) {
236818
236831
  const callee = this.lock.functions[calleeId];
236819
- if (!callee)
236832
+ if (!callee || callee.moduleId === "unknown" || fn.moduleId === callee.moduleId)
236820
236833
  continue;
236821
- if (fn.moduleId === callee.moduleId)
236822
- continue;
236823
- const violation = this.checkCall(fn, callee);
236824
- if (violation)
236825
- violations.push(violation);
236834
+ const v = this.checkCall(fn, callee);
236835
+ if (v)
236836
+ violations.push(v);
236826
236837
  }
236827
236838
  }
236828
236839
  for (const file of Object.values(this.lock.files)) {
236829
- if (!file.imports || file.imports.length === 0)
236840
+ if (file.moduleId === "unknown" || !file.imports?.length)
236830
236841
  continue;
236831
236842
  for (const importedPath of file.imports) {
236832
236843
  const importedFile = this.lock.files[importedPath];
236833
- if (!importedFile)
236834
- continue;
236835
- if (file.moduleId === importedFile.moduleId)
236844
+ if (!importedFile || importedFile.moduleId === "unknown" || file.moduleId === importedFile.moduleId)
236836
236845
  continue;
236837
- const violation = this.checkFileImport(file, importedFile);
236838
- if (violation)
236839
- violations.push(violation);
236846
+ const v = this.checkFileImport(file, importedFile);
236847
+ if (v)
236848
+ violations.push(v);
236840
236849
  }
236841
236850
  }
236842
- const errorCount = violations.filter((v) => v.severity === "error").length;
236843
- const warnCount = violations.filter((v) => v.severity === "warning").length;
236844
- const summary = violations.length === 0 ? `\u2713 All module boundaries respected (${Object.keys(this.lock.functions).length} functions, ${Object.keys(this.lock.files).length} files checked)` : `\u2717 ${errorCount} boundary error(s), ${warnCount} warning(s) found`;
236845
- return {
236846
- pass: errorCount === 0,
236847
- violations,
236848
- summary
236849
- };
236851
+ const seen = /* @__PURE__ */ new Set();
236852
+ const unique = violations.filter((v) => {
236853
+ const key = `${v.from.functionId}|${v.to.functionId}|${v.rule}`;
236854
+ if (seen.has(key))
236855
+ return false;
236856
+ seen.add(key);
236857
+ return true;
236858
+ });
236859
+ const fnCount = Object.keys(this.lock.functions).length;
236860
+ const fileCount = Object.keys(this.lock.files).length;
236861
+ const errorCount = unique.filter((v) => v.severity === "error").length;
236862
+ const warnCount = unique.filter((v) => v.severity === "warning").length;
236863
+ const summary = unique.length === 0 ? `All module boundaries respected (${fnCount} functions, ${fileCount} files checked)` : `${errorCount} boundary error(s), ${warnCount} warning(s) found`;
236864
+ return { pass: errorCount === 0, violations: unique, summary };
236850
236865
  }
236851
- /**
236852
- * Check a single cross-module call against parsed rules.
236853
- * Returns a violation if the call is forbidden, null if it's allowed.
236854
- */
236855
236866
  checkCall(caller, callee) {
236856
236867
  for (const rule of this.rules) {
236857
236868
  if (rule.fromModuleId !== caller.moduleId)
236858
236869
  continue;
236859
- let forbidden = false;
236860
- let ruleDesc = rule.raw;
236861
- if (rule.type === "isolated") {
236862
- forbidden = true;
236863
- } else if (rule.type === "deny") {
236864
- forbidden = rule.toModuleIds.includes(callee.moduleId);
236865
- } else if (rule.type === "allow_only") {
236866
- forbidden = !rule.toModuleIds.includes(callee.moduleId);
236867
- }
236868
- if (forbidden) {
236870
+ const forbidden = rule.type === "isolated" ? true : rule.type === "deny" ? rule.toModuleIds.includes(callee.moduleId) : !rule.toModuleIds.includes(callee.moduleId);
236871
+ if (forbidden)
236869
236872
  return {
236870
- from: {
236871
- functionId: caller.id,
236872
- functionName: caller.name,
236873
- file: caller.file,
236874
- moduleId: caller.moduleId,
236875
- moduleName: this.moduleNames.get(caller.moduleId) ?? caller.moduleId
236876
- },
236877
- to: {
236878
- functionId: callee.id,
236879
- functionName: callee.name,
236880
- file: callee.file,
236881
- moduleId: callee.moduleId,
236882
- moduleName: this.moduleNames.get(callee.moduleId) ?? callee.moduleId
236883
- },
236884
- rule: ruleDesc,
236873
+ from: { functionId: caller.id, functionName: caller.name, file: caller.file, moduleId: caller.moduleId, moduleName: this.moduleNames.get(caller.moduleId) ?? caller.moduleId },
236874
+ to: { functionId: callee.id, functionName: callee.name, file: callee.file, moduleId: callee.moduleId, moduleName: this.moduleNames.get(callee.moduleId) ?? callee.moduleId },
236875
+ rule: rule.raw,
236885
236876
  severity: "error"
236886
236877
  };
236887
- }
236888
236878
  }
236889
236879
  return null;
236890
236880
  }
236891
- /**
236892
- * Check a single cross-module file import against parsed rules.
236893
- * Returns a violation if the import is forbidden, null if it's allowed.
236894
- */
236895
236881
  checkFileImport(sourceFile, targetFile) {
236896
236882
  for (const rule of this.rules) {
236897
236883
  if (rule.fromModuleId !== sourceFile.moduleId)
236898
236884
  continue;
236899
- let forbidden = false;
236900
- if (rule.type === "isolated") {
236901
- forbidden = true;
236902
- } else if (rule.type === "deny") {
236903
- forbidden = rule.toModuleIds.includes(targetFile.moduleId);
236904
- } else if (rule.type === "allow_only") {
236905
- forbidden = !rule.toModuleIds.includes(targetFile.moduleId);
236906
- }
236907
- if (forbidden) {
236885
+ const forbidden = rule.type === "isolated" ? true : rule.type === "deny" ? rule.toModuleIds.includes(targetFile.moduleId) : !rule.toModuleIds.includes(targetFile.moduleId);
236886
+ if (forbidden)
236908
236887
  return {
236909
- from: {
236910
- functionId: `file:${sourceFile.path}`,
236911
- functionName: path.basename(sourceFile.path),
236912
- file: sourceFile.path,
236913
- moduleId: sourceFile.moduleId,
236914
- moduleName: this.moduleNames.get(sourceFile.moduleId) ?? sourceFile.moduleId
236915
- },
236916
- to: {
236917
- functionId: `file:${targetFile.path}`,
236918
- functionName: path.basename(targetFile.path),
236919
- file: targetFile.path,
236920
- moduleId: targetFile.moduleId,
236921
- moduleName: this.moduleNames.get(targetFile.moduleId) ?? targetFile.moduleId
236922
- },
236888
+ from: { functionId: `file:${sourceFile.path}`, functionName: path.basename(sourceFile.path), file: sourceFile.path, moduleId: sourceFile.moduleId, moduleName: this.moduleNames.get(sourceFile.moduleId) ?? sourceFile.moduleId },
236889
+ to: { functionId: `file:${targetFile.path}`, functionName: path.basename(targetFile.path), file: targetFile.path, moduleId: targetFile.moduleId, moduleName: this.moduleNames.get(targetFile.moduleId) ?? targetFile.moduleId },
236923
236890
  rule: rule.raw,
236924
236891
  severity: "error"
236925
236892
  };
236926
- }
236927
236893
  }
236928
236894
  return null;
236929
236895
  }
236930
- /** Return all cross-module call pairs (useful for generating allow rules) */
236931
236896
  allCrossModuleCalls() {
236932
236897
  const counts = /* @__PURE__ */ new Map();
236933
236898
  for (const fn of Object.values(this.lock.functions)) {
236934
236899
  for (const calleeId of fn.calls) {
236935
236900
  const callee = this.lock.functions[calleeId];
236936
- if (!callee || fn.moduleId === callee.moduleId)
236937
- continue;
236938
- const key = `${fn.moduleId}\u2192${callee.moduleId}`;
236939
- counts.set(key, (counts.get(key) ?? 0) + 1);
236940
- }
236941
- }
236942
- for (const file of Object.values(this.lock.files)) {
236943
- if (!file.imports)
236944
- continue;
236945
- for (const importedPath of file.imports) {
236946
- const importedFile = this.lock.files[importedPath];
236947
- if (!importedFile || file.moduleId === importedFile.moduleId)
236901
+ if (!callee || fn.moduleId === callee.moduleId || fn.moduleId === "unknown" || callee.moduleId === "unknown")
236948
236902
  continue;
236949
- const key = `${file.moduleId}\u2192${importedFile.moduleId}`;
236903
+ const key = `${fn.moduleId}>${callee.moduleId}`;
236950
236904
  counts.set(key, (counts.get(key) ?? 0) + 1);
236951
236905
  }
236952
236906
  }
236953
236907
  return [...counts.entries()].map(([key, count]) => {
236954
- const [from, to] = key.split("\u2192");
236908
+ const [from, to] = key.split(">");
236955
236909
  return { from, to, count };
236956
236910
  }).sort((a, b) => b.count - a.count);
236957
236911
  }
236958
236912
  };
236959
236913
 
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);
236921
+ };
236922
+ var _require = getRequire();
236923
+ var ParserModule = _require("web-tree-sitter");
236924
+ var Parser = ParserModule.Parser || ParserModule;
236925
+
236960
236926
  // ../core/dist/graph/impact-analyzer.js
236961
236927
  var ImpactAnalyzer = class {
236962
236928
  graph;
@@ -237190,7 +237156,7 @@ var MikkContractSchema = external_exports.object({
237190
237156
  description: external_exports.string(),
237191
237157
  language: external_exports.string(),
237192
237158
  framework: external_exports.string().optional(),
237193
- entryPoints: external_exports.array(external_exports.string())
237159
+ entryPoints: external_exports.array(external_exports.string()).default([])
237194
237160
  }),
237195
237161
  declared: external_exports.object({
237196
237162
  modules: external_exports.array(MikkModuleSchema),
@@ -237317,7 +237283,7 @@ var ContractReader = class {
237317
237283
  } catch {
237318
237284
  throw new ContractNotFoundError(contractPath);
237319
237285
  }
237320
- const json = JSON.parse(content);
237286
+ const json = JSON.parse(content.replace(/^\uFEFF/, ""));
237321
237287
  const result = MikkContractSchema.safeParse(json);
237322
237288
  if (!result.success) {
237323
237289
  const errors = result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n");
@@ -237339,7 +237305,7 @@ var LockReader = class {
237339
237305
  } catch {
237340
237306
  throw new LockNotFoundError();
237341
237307
  }
237342
- const json = JSON.parse(content);
237308
+ const json = JSON.parse(content.replace(/^\uFEFF/, ""));
237343
237309
  const hydrated = hydrateLock(json);
237344
237310
  const result = MikkLockSchema.safeParse(hydrated);
237345
237311
  if (!result.success) {
@@ -237663,6 +237629,106 @@ var AdrManager = class {
237663
237629
  // ../core/dist/hash/hash-store.js
237664
237630
  var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
237665
237631
 
237632
+ // ../core/dist/search/bm25.js
237633
+ var K1 = 1.2;
237634
+ var B = 0.75;
237635
+ var BM25Index = class {
237636
+ documents = [];
237637
+ documentFrequency = /* @__PURE__ */ new Map();
237638
+ // term → how many docs contain it
237639
+ avgDocLength = 0;
237640
+ /** Clear the index */
237641
+ clear() {
237642
+ this.documents = [];
237643
+ this.documentFrequency.clear();
237644
+ this.avgDocLength = 0;
237645
+ }
237646
+ /** Add a document with pre-tokenized terms */
237647
+ addDocument(id, tokens) {
237648
+ const normalizedTokens = tokens.map((t) => t.toLowerCase());
237649
+ this.documents.push({ id, tokens: normalizedTokens, length: normalizedTokens.length });
237650
+ const uniqueTerms = new Set(normalizedTokens);
237651
+ for (const term of uniqueTerms) {
237652
+ this.documentFrequency.set(term, (this.documentFrequency.get(term) ?? 0) + 1);
237653
+ }
237654
+ this.avgDocLength = this.documents.reduce((sum, d) => sum + d.length, 0) / this.documents.length;
237655
+ }
237656
+ /** Search the index and return ranked results */
237657
+ search(query, limit = 20) {
237658
+ const queryTokens = tokenize(query);
237659
+ if (queryTokens.length === 0 || this.documents.length === 0)
237660
+ return [];
237661
+ const N = this.documents.length;
237662
+ const results = [];
237663
+ for (const doc of this.documents) {
237664
+ let score = 0;
237665
+ for (const term of queryTokens) {
237666
+ const df = this.documentFrequency.get(term) ?? 0;
237667
+ if (df === 0)
237668
+ continue;
237669
+ const idf = Math.log((N - df + 0.5) / (df + 0.5) + 1);
237670
+ let tf = 0;
237671
+ for (const t of doc.tokens) {
237672
+ if (t === term)
237673
+ tf++;
237674
+ }
237675
+ const tfNorm = tf * (K1 + 1) / (tf + K1 * (1 - B + B * (doc.length / this.avgDocLength)));
237676
+ score += idf * tfNorm;
237677
+ }
237678
+ if (score > 0) {
237679
+ results.push({ id: doc.id, score });
237680
+ }
237681
+ }
237682
+ results.sort((a, b) => b.score - a.score);
237683
+ return results.slice(0, limit);
237684
+ }
237685
+ };
237686
+ function reciprocalRankFusion(...rankedLists) {
237687
+ const K = 60;
237688
+ const scores = /* @__PURE__ */ new Map();
237689
+ for (const list of rankedLists) {
237690
+ for (let rank = 0; rank < list.length; rank++) {
237691
+ const item = list[rank];
237692
+ scores.set(item.id, (scores.get(item.id) ?? 0) + 1 / (K + rank + 1));
237693
+ }
237694
+ }
237695
+ return [...scores.entries()].map(([id, score]) => ({ id, score })).sort((a, b) => b.score - a.score);
237696
+ }
237697
+ function tokenize(text) {
237698
+ const tokens = [];
237699
+ const words = text.split(/[^a-zA-Z0-9]+/).filter(Boolean);
237700
+ for (const word of words) {
237701
+ const camelParts = word.replace(/([a-z])([A-Z])/g, "$1 $2").split(" ");
237702
+ for (const part of camelParts) {
237703
+ const lower = part.toLowerCase();
237704
+ if (lower.length >= 2) {
237705
+ tokens.push(lower);
237706
+ }
237707
+ }
237708
+ }
237709
+ return tokens;
237710
+ }
237711
+ function buildFunctionTokens(fn) {
237712
+ const parts = [];
237713
+ parts.push(...tokenize(fn.name));
237714
+ parts.push(...tokenize(fn.name));
237715
+ const filename = fn.file.split("/").pop() ?? fn.file;
237716
+ parts.push(...tokenize(filename.replace(/\.[^.]+$/, "")));
237717
+ if (fn.purpose) {
237718
+ parts.push(...tokenize(fn.purpose));
237719
+ }
237720
+ if (fn.params) {
237721
+ for (const p of fn.params) {
237722
+ parts.push(...tokenize(p.name));
237723
+ parts.push(...tokenize(p.type));
237724
+ }
237725
+ }
237726
+ if (fn.returnType) {
237727
+ parts.push(...tokenize(fn.returnType));
237728
+ }
237729
+ return parts;
237730
+ }
237731
+
237666
237732
  // ../core/dist/utils/fs.js
237667
237733
  var import_fast_glob = __toESM(require_out4(), 1);
237668
237734
 
@@ -238486,7 +238552,7 @@ var SemanticSearcher = class _SemanticSearcher {
238486
238552
  }
238487
238553
  /**
238488
238554
  * Build (or load from cache) embeddings for every function in the lock.
238489
- * Safe to call on every MCP request cache hit is O(1) disk read.
238555
+ * Safe to call on every MCP request -- cache hit is O(1) disk read.
238490
238556
  */
238491
238557
  async index(lock) {
238492
238558
  const fingerprint = lockFingerprint(lock);
@@ -238593,6 +238659,38 @@ var IntentSchema = external_exports.object({
238593
238659
  // src/tools.ts
238594
238660
  var projectCache = /* @__PURE__ */ new Map();
238595
238661
  var CACHE_TTL_MS = 3e4;
238662
+ var _CPT = 4;
238663
+ var _ALC = 42;
238664
+ var _tallies = /* @__PURE__ */ new Map();
238665
+ function _tally(r) {
238666
+ let t = _tallies.get(r);
238667
+ if (!t) {
238668
+ t = { calls: 0, used: 0, raw: 0, saved: 0, start: Date.now() };
238669
+ _tallies.set(r, t);
238670
+ }
238671
+ return t;
238672
+ }
238673
+ function _tok(o) {
238674
+ return Math.max(1, Math.round(JSON.stringify(o).length / _CPT));
238675
+ }
238676
+ function _fileTok(lock, fp) {
238677
+ const fs22 = Object.values(lock.functions).filter((f) => f.file === fp);
238678
+ const ln = fs22.length > 0 ? Math.max(...fs22.map((f) => f.endLine)) : 80;
238679
+ return Math.round(ln * _ALC / _CPT);
238680
+ }
238681
+ function _filesTok(lock, fps) {
238682
+ return fps.reduce((s, f) => s + _fileTok(lock, f), 0);
238683
+ }
238684
+ function _track(root, raw, resp) {
238685
+ const used = _tok(resp);
238686
+ const saved = Math.max(0, raw - used);
238687
+ const t = _tally(root);
238688
+ t.calls++;
238689
+ t.used += used;
238690
+ t.raw += raw;
238691
+ t.saved += saved;
238692
+ return { used, raw, saved, sessionSaved: t.saved, sessionCalls: t.calls };
238693
+ }
238596
238694
  var semanticSearchers = /* @__PURE__ */ new Map();
238597
238695
  function getSemanticSearcher(projectRoot) {
238598
238696
  let s = semanticSearchers.get(projectRoot);
@@ -238603,9 +238701,17 @@ function getSemanticSearcher(projectRoot) {
238603
238701
  return s;
238604
238702
  }
238605
238703
  function registerTools(server2, projectRoot) {
238704
+ server2.tool(
238705
+ "mikk_test_tool",
238706
+ "A simple test tool that returns a static message.",
238707
+ {},
238708
+ async () => {
238709
+ return { content: [{ type: "text", text: "Mikk test tool executed successfully." }] };
238710
+ }
238711
+ );
238606
238712
  server2.tool(
238607
238713
  "mikk_get_project_overview",
238608
- "Get a high-level overview of the project: modules, function counts, file counts, tech stack",
238714
+ "Get a high-level overview: modules, function counts, file counts, constraints. WHEN TO USE: When you need raw project stats. For session start, prefer mikk_get_session_context instead. AFTER THIS: Use mikk_query_context with your task, or mikk_list_modules to drill into a module.",
238609
238715
  {},
238610
238716
  async () => {
238611
238717
  const { contract, lock, staleness } = await loadContractAndLock(projectRoot);
@@ -238632,12 +238738,14 @@ function registerTools(server2, projectRoot) {
238632
238738
  warning: staleness,
238633
238739
  hint: "Next: Use mikk_query_context with your task description, or mikk_list_modules to explore the architecture."
238634
238740
  };
238741
+ const _rawOverview = Math.min(15, Object.keys(lock.files).length) * Math.round(80 * _ALC / _CPT);
238742
+ overview.tokens = _track(projectRoot, _rawOverview, overview);
238635
238743
  return { content: [{ type: "text", text: JSON.stringify(overview, null, 2) }] };
238636
238744
  }
238637
238745
  );
238638
238746
  server2.tool(
238639
238747
  "mikk_query_context",
238640
- "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.",
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.",
238641
238749
  {
238642
238750
  question: external_exports.string().describe("The architecture question or task description"),
238643
238751
  maxHops: external_exports.number().optional().default(4).describe("Graph traversal depth (default: 4)"),
@@ -238674,14 +238782,20 @@ function registerTools(server2, projectRoot) {
238674
238782
  const warning = staleness ? `
238675
238783
 
238676
238784
  ${staleness}` : "";
238785
+ const _rawQC = (tokenBudget ?? 6e3) * 3;
238786
+ const _tokQC = _track(projectRoot, _rawQC, output);
238787
+ const tokLine = `
238788
+
238789
+ ---
238790
+ // tokens: ${JSON.stringify(_tokQC)}`;
238677
238791
  return {
238678
- 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." }]
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 }]
238679
238793
  };
238680
238794
  }
238681
238795
  );
238682
238796
  server2.tool(
238683
238797
  "mikk_impact_analysis",
238684
- "Analyze the blast radius of changing a specific file. Returns which functions and modules would be impacted.",
238798
+ "Analyze the blast radius of changing a file. Returns impacted functions classified by severity (critical/high/medium/low). WHEN TO USE: Before refactoring, renaming, or modifying shared code. AFTER THIS: Use mikk_get_function_detail on critical/high items to review them.",
238685
238799
  {
238686
238800
  file: external_exports.string().describe("The file path (relative to project root) to analyze impact for")
238687
238801
  },
@@ -238728,12 +238842,55 @@ ${staleness}` : "";
238728
238842
  warning: staleness,
238729
238843
  hint: "Next: Use mikk_get_function_detail on critical/high items to review them. Then mikk_before_edit to validate your planned changes."
238730
238844
  };
238845
+ const _rawIA = _fileTok(lock, normalizedFile) + result.impacted.length * Math.round(40 * _ALC / _CPT);
238846
+ response.tokens = _track(projectRoot, _rawIA, response);
238847
+ return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
238848
+ }
238849
+ );
238850
+ server2.tool(
238851
+ "mikk_search_functions",
238852
+ "Search for functions by name or ID using a hybrid BM25+substring search. WHEN TO USE: When you need to find a function but are unsure of its exact name or location. AFTER THIS: Use mikk_get_function_detail to get more information about a specific function.",
238853
+ {
238854
+ query: external_exports.string().describe("The search query for function names or IDs"),
238855
+ limit: external_exports.number().optional().default(10).describe("Maximum number of results to return")
238856
+ },
238857
+ async ({ query, limit }) => {
238858
+ const { lock, staleness } = await loadContractAndLock(projectRoot);
238859
+ const allFunctions = Object.values(lock.functions);
238860
+ const queryLower = query.toLowerCase();
238861
+ const substringMatches = allFunctions.filter((fn) => fn.name.toLowerCase().includes(queryLower) || fn.id.toLowerCase().includes(queryLower)).map((fn, i) => ({ id: fn.id, score: 100 - i }));
238862
+ const bm25 = new BM25Index();
238863
+ for (const fn of allFunctions) {
238864
+ bm25.addDocument(fn.id, buildFunctionTokens(fn));
238865
+ }
238866
+ const bm25Matches = bm25.search(query, limit * 2);
238867
+ const fused = reciprocalRankFusion(substringMatches, bm25Matches);
238868
+ const matches = fused.slice(0, limit).map((result) => {
238869
+ const fn = lock.functions[result.id];
238870
+ if (!fn) return null;
238871
+ return {
238872
+ name: fn.name,
238873
+ file: fn.file,
238874
+ module: fn.moduleId,
238875
+ exported: fn.isExported,
238876
+ lines: `${fn.startLine}-${fn.endLine}`,
238877
+ relevance: Math.round(result.score * 1e4) / 1e4
238878
+ };
238879
+ }).filter(Boolean);
238880
+ if (matches.length === 0) {
238881
+ return { content: [{ type: "text", text: `No functions matching "${query}" found.` }] };
238882
+ }
238883
+ const response = {
238884
+ matches,
238885
+ searchMethod: "hybrid (BM25 + substring via RRF)",
238886
+ warning: staleness
238887
+ };
238731
238888
  return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
238732
238889
  }
238733
238890
  );
238734
238891
  server2.tool(
238735
238892
  "mikk_before_edit",
238736
- "Call this BEFORE editing any file. Returns the blast radius, exported functions at risk, architectural constraint violations, and circular dependency warnings. This is your safety check.",
238893
+ "MANDATORY: Call BEFORE editing any file. Returns blast radius, exported functions at risk, constraint violations (6 rule types), and circular dependency warnings. WHEN TO USE: ALWAYS before modifying files. AFTER THIS: If constraintStatus is fail, redesign your approach. If pass, proceed with edits. TIP: Pass multiple files for combined blast radius.",
238737
238894
  {
238738
238895
  files: external_exports.array(external_exports.string()).describe("The file paths (relative to project root) you are about to edit")
238739
238896
  },
@@ -238772,7 +238929,7 @@ ${staleness}` : "";
238772
238929
  rule: v.rule,
238773
238930
  from: `${v.from.moduleName}::${v.from.functionName}`,
238774
238931
  to: `${v.to.moduleName}::${v.to.functionName}`,
238775
- message: `\u274C ${v.from.moduleName}::${v.from.functionName} \u2192 ${v.to.moduleName}::${v.to.functionName} violates: "${v.rule}"`
238932
+ message: `${v.from.moduleName}::${v.from.functionName} -> ${v.to.moduleName}::${v.to.functionName} violates: "${v.rule}"`
238776
238933
  }));
238777
238934
  const circularWarnings = detectCircularDeps(fileFns, lock);
238778
238935
  fileReports[file] = {
@@ -238796,14 +238953,16 @@ ${staleness}` : "";
238796
238953
  constraintStatus: totalViolations === 0 ? "pass" : "fail",
238797
238954
  files: fileReports,
238798
238955
  warning: staleness,
238799
- hint: totalViolations > 0 ? "\u26A0\uFE0F 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."
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."
238800
238957
  };
238958
+ const _rawBE = _filesTok(lock, filesToEdit) * 4;
238959
+ response.tokens = _track(projectRoot, _rawBE, response);
238801
238960
  return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
238802
238961
  }
238803
238962
  );
238804
238963
  server2.tool(
238805
238964
  "mikk_list_modules",
238806
- "List all declared modules with their file/function counts and descriptions",
238965
+ "List all declared modules with file counts, function counts, entry points, and descriptions. WHEN TO USE: To explore the project structure. Good starting point after mikk_get_session_context. AFTER THIS: Use mikk_get_module_detail with a specific moduleId.",
238807
238966
  {},
238808
238967
  async () => {
238809
238968
  const { contract, lock, staleness } = await loadContractAndLock(projectRoot);
@@ -238829,7 +238988,7 @@ ${staleness}` : "";
238829
238988
  );
238830
238989
  server2.tool(
238831
238990
  "mikk_get_module_detail",
238832
- "Get detailed information about a specific module: its functions, files, exported API, and internal call graph",
238991
+ "Deep dive into a single module: all functions, files, exported API surface, internal call graph. WHEN TO USE: After mikk_list_modules to understand a specific module. AFTER THIS: Use mikk_get_function_detail for specific functions, or mikk_before_edit if modifying files in this module.",
238833
238992
  {
238834
238993
  moduleId: external_exports.string().describe('The module ID (e.g., "packages-core", "lib-auth")')
238835
238994
  },
@@ -238868,7 +239027,7 @@ ${staleness}` : "";
238868
239027
  );
238869
239028
  server2.tool(
238870
239029
  "mikk_get_function_detail",
238871
- "Get detailed info about a specific function by name: params, return type, call graph, error handling, etc.",
239030
+ "360-degree view of a function: params, return type, source body, call graph (who calls it + what it calls), error handling, edge cases. WHEN TO USE: When you need to understand a specific function in depth. AFTER THIS: Use mikk_find_usages to see all callers. TIP: Pass full qualified name (e.g. GraphBuilder.build) for class methods.",
238872
239031
  {
238873
239032
  name: external_exports.string().describe('Function name to search for (e.g., "parseFiles", "GraphBuilder.build")')
238874
239033
  },
@@ -238914,36 +239073,9 @@ ${staleness}` : "";
238914
239073
  return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
238915
239074
  }
238916
239075
  );
238917
- server2.tool(
238918
- "mikk_search_functions",
238919
- "Search for functions by name pattern (substring match). Returns matching function names, files, and modules.",
238920
- {
238921
- query: external_exports.string().describe("Search query \u2014 matched against function names (case-insensitive)"),
238922
- limit: external_exports.number().optional().default(20).describe("Max results to return (default: 20)")
238923
- },
238924
- async ({ query, limit }) => {
238925
- const { lock, staleness } = await loadContractAndLock(projectRoot);
238926
- const queryLower = query.toLowerCase();
238927
- const matches = Object.values(lock.functions).filter((fn) => fn.name.toLowerCase().includes(queryLower) || fn.id.toLowerCase().includes(queryLower)).slice(0, limit).map((fn) => ({
238928
- name: fn.name,
238929
- file: fn.file,
238930
- module: fn.moduleId,
238931
- exported: fn.isExported,
238932
- lines: `${fn.startLine}-${fn.endLine}`
238933
- }));
238934
- if (matches.length === 0) {
238935
- return { content: [{ type: "text", text: `No functions matching "${query}" found.` }] };
238936
- }
238937
- const response = {
238938
- matches,
238939
- warning: staleness
238940
- };
238941
- return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
238942
- }
238943
- );
238944
239076
  server2.tool(
238945
239077
  "mikk_semantic_search",
238946
- "Find functions by meaning, not by name. Uses local vector embeddings (Xenova/all-MiniLM-L6-v2) to rank functions by semantic similarity to a natural-language query. Requires @xenova/transformers to be installed.",
239078
+ 'Find functions by meaning using local vector embeddings. Query "validate JWT" returns verifyToken ranked by cosine similarity. WHEN TO USE: When you dont know the function name but know what it does. Complements mikk_search_functions (keyword). AFTER THIS: Use mikk_get_function_detail on top matches. Requires @xenova/transformers (22MB model, downloads once).',
238947
239079
  {
238948
239080
  query: external_exports.string().describe('Natural-language description of what you are looking for (e.g. "validate a JWT token", "send an email notification")'),
238949
239081
  topK: external_exports.number().optional().default(10).describe("Number of results to return (default: 10)")
@@ -238955,7 +239087,7 @@ ${staleness}` : "";
238955
239087
  content: [{
238956
239088
  type: "text",
238957
239089
  text: [
238958
- "\u274C Semantic search requires @xenova/transformers.",
239090
+ "\xC3\u201A\xC2\x9D\xC3\u2026\xE2\u20AC\u2122 Semantic search requires @xenova/transformers.",
238959
239091
  "",
238960
239092
  "Install it in your project root:",
238961
239093
  " npm install @xenova/transformers",
@@ -238984,7 +239116,7 @@ ${staleness}` : "";
238984
239116
  );
238985
239117
  server2.tool(
238986
239118
  "mikk_get_constraints",
238987
- "Get all declared architectural constraints and design decisions for this project",
239119
+ "Get all architectural constraints and ADRs. WHEN TO USE: Before cross-module changes, or when mikk_before_edit reports violations. Understand WHY a constraint exists. AFTER THIS: Use mikk_manage_adr to add/update decisions. 6 constraint types: no-import, must-use, no-call, layer, naming, max-files.",
238988
239120
  {},
238989
239121
  async () => {
238990
239122
  const { contract, staleness } = await loadContractAndLock(projectRoot);
@@ -238999,7 +239131,7 @@ ${staleness}` : "";
238999
239131
  );
239000
239132
  server2.tool(
239001
239133
  "mikk_get_file",
239002
- "Read the raw source content of any file in the project. Use this to see the actual code before editing.",
239134
+ "Read raw source of a file. TIP: Prefer mikk_read_file with function names to save tokens. WHEN TO USE: When you need entire file content (config files, small files). AFTER THIS: Use mikk_before_edit before making changes.",
239003
239135
  {
239004
239136
  file: external_exports.string().describe('File path relative to project root (e.g., "src/auth/verify.ts")')
239005
239137
  },
@@ -239033,7 +239165,7 @@ ${content}`
239033
239165
  );
239034
239166
  server2.tool(
239035
239167
  "mikk_find_usages",
239036
- "Find everything that calls a specific function. Essential before renaming or changing a function signature.",
239168
+ "Find every function that calls a specific function. Essential before renaming or changing signatures. WHEN TO USE: Before renaming, refactoring, or changing a function interface. AFTER THIS: Review each caller to ensure your change wont break them. Use mikk_read_file to see caller code.",
239037
239169
  {
239038
239170
  name: external_exports.string().describe("Function name to find callers of")
239039
239171
  },
@@ -239067,7 +239199,7 @@ ${content}`
239067
239199
  );
239068
239200
  server2.tool(
239069
239201
  "mikk_get_routes",
239070
- "Get all detected HTTP routes (Express/Koa/Hono style) with their methods, paths, handlers, and middlewares",
239202
+ "Get all detected HTTP routes with methods, paths, handlers, and middleware chains. WHEN TO USE: When working on API endpoints. Shows Express/Koa/Hono route registrations detected from AST. AFTER THIS: Use mikk_get_function_detail on a handler to see its implementation.",
239071
239203
  {},
239072
239204
  async () => {
239073
239205
  const { lock, staleness } = await loadContractAndLock(projectRoot);
@@ -239084,7 +239216,7 @@ ${content}`
239084
239216
  );
239085
239217
  server2.tool(
239086
239218
  "mikk_dead_code",
239087
- "Detect dead code \u2014 functions with zero callers after exempting exports, entry points, route handlers, tests, and constructors. Use this before refactoring or cleanup.",
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.",
239088
239220
  {
239089
239221
  moduleId: external_exports.string().optional().describe("Filter results to a specific module ID")
239090
239222
  },
@@ -239109,7 +239241,7 @@ ${content}`
239109
239241
  );
239110
239242
  server2.tool(
239111
239243
  "mikk_manage_adr",
239112
- "Manage Architectural Decision Records (ADRs) in mikk.json. Actions: list, get, add, update, remove. ADRs document WHY architectural constraints exist.",
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.",
239113
239245
  {
239114
239246
  action: external_exports.enum(["list", "get", "add", "update", "remove"]).describe("The CRUD action to perform"),
239115
239247
  id: external_exports.string().optional().describe("ADR id (required for get, update, remove)"),
@@ -239166,7 +239298,7 @@ ${content}`
239166
239298
  );
239167
239299
  server2.tool(
239168
239300
  "mikk_get_changes",
239169
- "Detect files that have changed since last analysis. Call this at the start of every AI session to understand what's different. Returns added, modified, and deleted files.",
239301
+ "Detect files added, modified, and deleted since last mikk analyze. WHEN TO USE: At session start (after mikk_get_session_context), or after making edits to see what drifted. AFTER THIS: Run mikk analyze to update the lock, then mikk_impact_analysis on modified files. Uses SHA-256 hash comparison for accurate drift detection.",
239170
239302
  {},
239171
239303
  async () => {
239172
239304
  const { lock, staleness } = await loadContractAndLock(projectRoot);
@@ -239212,12 +239344,14 @@ ${content}`
239212
239344
  warning: staleness,
239213
239345
  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."
239214
239346
  };
239347
+ const _rawGC = Math.min(50, Object.keys(lock.files).length) * Math.round(60 * _ALC / _CPT);
239348
+ response.tokens = _track(projectRoot, _rawGC, response);
239215
239349
  return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
239216
239350
  }
239217
239351
  );
239218
239352
  server2.tool(
239219
239353
  "mikk_read_file",
239220
- "Read file content scoped to specific functions. Returns function bodies with metadata headers (params, returnType, calledBy) instead of dumping the whole file. Use this instead of mikk_get_file when you know which functions you need.",
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.",
239221
239355
  {
239222
239356
  file: external_exports.string().describe("File path relative to project root"),
239223
239357
  functions: external_exports.array(external_exports.string()).optional().describe("Function names to extract. If omitted, returns the whole file.")
@@ -239257,11 +239391,11 @@ ${content}` }]
239257
239391
  (f) => (f.name === fnName || f.name.endsWith(`.${fnName}`)) && (f.file === normalizedFile || f.file.endsWith("/" + normalizedFile))
239258
239392
  );
239259
239393
  if (!fn) {
239260
- sections.push(`// \u274C Function "${fnName}" not found in ${file}`);
239394
+ sections.push(`// \xC3\u201A\xC2\x9D\xC3\u2026\xE2\u20AC\u2122 Function "${fnName}" not found in ${file}`);
239261
239395
  continue;
239262
239396
  }
239263
239397
  const header = [
239264
- `// \u2500\u2500 ${fn.name} \u2500\u2500`,
239398
+ `// ${fn.name} `,
239265
239399
  `// File: ${fn.file}:${fn.startLine}-${fn.endLine}`,
239266
239400
  `// Module: ${fn.moduleId}`,
239267
239401
  fn.purpose ? `// Purpose: ${fn.purpose}` : null,
@@ -239280,12 +239414,15 @@ ${body}`);
239280
239414
  const warningText = staleness ? `
239281
239415
 
239282
239416
  ${staleness}` : "";
239283
- return { content: [{ type: "text", text: output + warningText }] };
239417
+ const _rawRF = _fileTok(lock, file.replace(/\\/g, "/"));
239418
+ const _tokRF = _track(projectRoot, _rawRF, output);
239419
+ return { content: [{ type: "text", text: output + warningText + `
239420
+ // tokens: ${JSON.stringify(_tokRF)}` }] };
239284
239421
  }
239285
239422
  );
239286
239423
  server2.tool(
239287
239424
  "mikk_get_session_context",
239288
- "One-shot context for AI session start. Combines project overview + recent changes + hot modules + constraint status in a single call. Call this at the beginning of every new AI conversation.",
239425
+ "CALL THIS FIRST. One-shot context for session start: project overview + constraint status + hot modules + recently modified files + active decisions. WHEN TO USE: At the very beginning of every AI conversation. This is your onboarding. AFTER THIS: Use mikk_query_context with your task description, or mikk_get_changes for detailed drift.",
239289
239426
  {},
239290
239427
  async () => {
239291
239428
  const { contract, lock, staleness } = await loadContractAndLock(projectRoot);
@@ -239344,6 +239481,140 @@ ${staleness}` : "";
239344
239481
  warning: staleness,
239345
239482
  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."
239346
239483
  };
239484
+ const _rawSC = Math.min(20, Object.keys(lock.files).length) * Math.round(100 * _ALC / _CPT);
239485
+ response.tokens = _track(projectRoot, _rawSC, response);
239486
+ return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
239487
+ }
239488
+ );
239489
+ server2.tool(
239490
+ "mikk_git_diff_impact",
239491
+ "Map git diff hunks to affected symbols. Shows which functions were modified/added/deleted. WHEN TO USE: After commits/merges to understand symbol-level changes. AFTER THIS: Use mikk_impact_analysis on affected files.",
239492
+ {
239493
+ ref: external_exports.string().optional().default("HEAD~1").describe("Git ref to diff against (default: HEAD~1)"),
239494
+ staged: external_exports.boolean().optional().default(false).describe("If true, diff staged changes only")
239495
+ },
239496
+ async ({ ref, staged }) => {
239497
+ const { lock, staleness } = await loadContractAndLock(projectRoot);
239498
+ try {
239499
+ const diffArgs = staged ? "--cached" : ref;
239500
+ const rawDiff = (0, import_node_child_process.execSync)(`git diff ${diffArgs} --unified=0 --no-color`, {
239501
+ cwd: projectRoot,
239502
+ encoding: "utf-8",
239503
+ maxBuffer: 10 * 1024 * 1024
239504
+ });
239505
+ if (!rawDiff.trim()) {
239506
+ return { content: [{ type: "text", text: "No changes found in git diff." }] };
239507
+ }
239508
+ const fileHunks = parseDiffHunks(rawDiff);
239509
+ const affectedSymbols = [];
239510
+ for (const hunk of fileHunks) {
239511
+ const fileFns = Object.values(lock.functions).filter((fn) => fn.file === hunk.file || fn.file.endsWith(hunk.file));
239512
+ const affected = fileFns.filter((fn) => hunk.changedLines.some((l) => l >= fn.startLine && l <= fn.endLine));
239513
+ if (affected.length > 0 || hunk.isNew || hunk.isDeleted) {
239514
+ affectedSymbols.push({
239515
+ file: hunk.file,
239516
+ type: hunk.isNew ? "added" : hunk.isDeleted ? "deleted" : "modified",
239517
+ functions: affected.map((fn) => ({ name: fn.name, moduleId: fn.moduleId }))
239518
+ });
239519
+ }
239520
+ }
239521
+ 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) }] };
239527
+ } catch (err) {
239528
+ return { content: [{ type: "text", text: `Git diff failed: ${err.message}` }], isError: true };
239529
+ }
239530
+ }
239531
+ );
239532
+ server2.tool(
239533
+ "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.",
239535
+ {
239536
+ functionName: external_exports.string().describe("The current function name to rename"),
239537
+ newName: external_exports.string().describe("The desired new name")
239538
+ },
239539
+ async ({ functionName, newName }) => {
239540
+ const { lock, staleness } = await loadContractAndLock(projectRoot);
239541
+ const targetFn = Object.values(lock.functions).find(
239542
+ (fn) => fn.name === functionName || fn.id.endsWith(`:${functionName}`)
239543
+ );
239544
+ if (!targetFn) {
239545
+ return {
239546
+ content: [{
239547
+ type: "text",
239548
+ text: `Function "${functionName}" not found. Use mikk_search_functions to find the correct name.`
239549
+ }],
239550
+ isError: true
239551
+ };
239552
+ }
239553
+ const callers = targetFn.calledBy.map((callerId) => lock.functions[callerId]).filter(Boolean).map((fn) => ({
239554
+ callerName: fn.name,
239555
+ file: fn.file,
239556
+ module: fn.moduleId,
239557
+ lineRange: `${fn.startLine}-${fn.endLine}`
239558
+ }));
239559
+ const filesImporting = Object.values(lock.files).filter(
239560
+ (file) => file.imports?.some((imp) => imp.includes(functionName) || imp.includes(targetFn.file))
239561
+ );
239562
+ const instructions = [
239563
+ `1. Rename definition in ${targetFn.file}:${targetFn.startLine}`,
239564
+ ...callers.map((c, i) => `${i + 2}. Update call in ${c.file} (${c.callerName}, lines ${c.lineRange})`),
239565
+ ...targetFn.isExported ? filesImporting.map((f, i) => `${callers.length + i + 2}. Update import in ${f.path}`) : [],
239566
+ `${callers.length + (targetFn.isExported ? filesImporting.length : 0) + 2}. Run \`mikk analyze\` to update the lock`
239567
+ ];
239568
+ return {
239569
+ content: [{
239570
+ type: "text",
239571
+ text: JSON.stringify({
239572
+ target: {
239573
+ currentName: functionName,
239574
+ newName,
239575
+ file: targetFn.file,
239576
+ line: targetFn.startLine,
239577
+ module: targetFn.moduleId,
239578
+ isExported: targetFn.isExported
239579
+ },
239580
+ callSites: callers,
239581
+ importSites: filesImporting.map((f) => ({ file: f.path, module: f.moduleId })),
239582
+ totalEdits: 1 + callers.length + filesImporting.length,
239583
+ instructions,
239584
+ warning: staleness
239585
+ }, null, 2)
239586
+ }]
239587
+ };
239588
+ }
239589
+ );
239590
+ server2.tool(
239591
+ "mikk_token_stats",
239592
+ "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.",
239593
+ {},
239594
+ async () => {
239595
+ const t = _tally(projectRoot);
239596
+ const { lock } = await loadContractAndLock(projectRoot);
239597
+ const totalFileLine = Object.values(lock.functions).reduce((s, f) => s + (f.endLine - f.startLine + 1), 0);
239598
+ const fullCodebaseTok = Math.round(totalFileLine * _ALC / _CPT);
239599
+ const elapsedMin = Math.round((Date.now() - t.start) / 6e4);
239600
+ const response = {
239601
+ session: {
239602
+ calls: t.calls,
239603
+ elapsedMinutes: elapsedMin
239604
+ },
239605
+ tokens: {
239606
+ used: t.used,
239607
+ rawWouldHaveCost: t.raw,
239608
+ saved: t.saved,
239609
+ savingsPercent: t.raw > 0 ? Math.round(t.saved / t.raw * 100) : 0
239610
+ },
239611
+ context: {
239612
+ fullCodebaseTokens: fullCodebaseTok,
239613
+ percentOfCodebaseRead: t.raw > 0 ? Math.round(t.used / fullCodebaseTok * 100) : 0,
239614
+ note: "Full codebase = if agent read every tracked source line once"
239615
+ },
239616
+ 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."
239617
+ };
239347
239618
  return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
239348
239619
  }
239349
239620
  );
@@ -239360,7 +239631,7 @@ async function loadContractAndLock(projectRoot) {
239360
239631
  const syncStatus = lock.syncState?.status ?? "unknown";
239361
239632
  let staleness = null;
239362
239633
  if (syncStatus === "drifted" || syncStatus === "conflict") {
239363
- staleness = `\u26A0\uFE0F Lock file is ${syncStatus}. Run \`mikk analyze\` for accurate results.`;
239634
+ staleness = `\xC3\u201A\xC2\x8F Lock file is ${syncStatus}. Run \`mikk analyze\` for accurate results.`;
239364
239635
  }
239365
239636
  if (!staleness) {
239366
239637
  const fileEntries = Object.entries(lock.files);
@@ -239383,7 +239654,7 @@ async function loadContractAndLock(projectRoot) {
239383
239654
  }
239384
239655
  }
239385
239656
  if (mismatched > 0) {
239386
- staleness = `\u26A0\uFE0F STALE: ${mismatched} file(s) changed since last analysis (${mismatchedFiles.slice(0, 3).join(", ")}${mismatched > 3 ? "..." : ""}). Run \`mikk analyze\`.`;
239657
+ staleness = `\xC3\u201A\xC2\x8F STALE: ${mismatched} file(s) changed since last analysis (${mismatchedFiles.slice(0, 3).join(", ")}${mismatched > 3 ? "..." : ""}). Run \`mikk analyze\`.`;
239387
239658
  }
239388
239659
  }
239389
239660
  const graph = buildGraphFromLock(lock);
@@ -239455,7 +239726,7 @@ function detectCircularDeps(fns, lock) {
239455
239726
  const cycleStart = cyclePath.indexOf(id);
239456
239727
  const cycle = cyclePath.slice(cycleStart).map((cid) => lock.functions[cid]?.name ?? cid);
239457
239728
  cycle.push(lock.functions[id]?.name ?? id);
239458
- warnings.push(`\u26A0\uFE0F Circular: ${cycle.join(" \u2192 ")}`);
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 ")}`);
239459
239730
  return true;
239460
239731
  }
239461
239732
  if (visited.has(id)) return false;
@@ -239501,6 +239772,35 @@ function isSourceFile(filePath) {
239501
239772
  const ext = path4.extname(filePath);
239502
239773
  return [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".go", ".py"].includes(ext);
239503
239774
  }
239775
+ function parseDiffHunks(diff) {
239776
+ const files = /* @__PURE__ */ new Map();
239777
+ let currentFile = "";
239778
+ let nextIsNew = false;
239779
+ for (const line of diff.split("\n")) {
239780
+ if (line.startsWith("--- ") && line.includes("/dev/null")) {
239781
+ nextIsNew = true;
239782
+ } else if (line.startsWith("+++ ")) {
239783
+ currentFile = line.slice(6);
239784
+ if (currentFile !== "/dev/null" && !files.has(currentFile)) {
239785
+ files.set(currentFile, { changedLines: [], isNew: nextIsNew, isDeleted: false });
239786
+ }
239787
+ if (currentFile === "/dev/null") {
239788
+ const prev = [...files.keys()].pop();
239789
+ if (prev) files.get(prev).isDeleted = true;
239790
+ }
239791
+ nextIsNew = false;
239792
+ } else if (line.startsWith("@@ ") && currentFile && files.has(currentFile)) {
239793
+ const match = line.match(/@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/);
239794
+ if (match) {
239795
+ const start = parseInt(match[1], 10);
239796
+ const count = parseInt(match[2] ?? "1", 10);
239797
+ const entry = files.get(currentFile);
239798
+ for (let i = 0; i < count; i++) entry.changedLines.push(start + i);
239799
+ }
239800
+ }
239801
+ }
239802
+ return [...files.entries()].map(([file, data]) => ({ file, ...data }));
239803
+ }
239504
239804
 
239505
239805
  // src/resources.ts
239506
239806
  var path5 = __toESM(require("node:path"), 1);
@@ -239561,7 +239861,7 @@ async function safeRead(filePath) {
239561
239861
  }
239562
239862
 
239563
239863
  // src/server.ts
239564
- var VERSION = true ? "1.8.1" : "0.0.0-dev";
239864
+ var VERSION = true ? "1.9.1" : "0.0.0-dev";
239565
239865
  function createMikkMcpServer(projectRoot) {
239566
239866
  const server2 = new McpServer({
239567
239867
  name: "mikk",
@@ -239671,9 +239971,6 @@ async function startStdioServer() {
239671
239971
  const transport = new StdioServerTransport();
239672
239972
  await server2.connect(transport);
239673
239973
  }
239674
-
239675
- // src/index.ts
239676
- startStdioServer();
239677
239974
  // Annotate the CommonJS export names for ESM import in node:
239678
239975
  0 && (module.exports = {
239679
239976
  createMikkMcpServer,