@getmikk/mcp-server 1.8.1 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -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)
236820
- continue;
236821
- if (fn.moduleId === callee.moduleId)
236832
+ if (!callee || callee.moduleId === "unknown" || fn.moduleId === callee.moduleId)
236822
236833
  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)
236901
+ if (!callee || fn.moduleId === callee.moduleId || fn.moduleId === "unknown" || callee.moduleId === "unknown")
236937
236902
  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)
236948
- 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);
@@ -238603,9 +238669,17 @@ function getSemanticSearcher(projectRoot) {
238603
238669
  return s;
238604
238670
  }
238605
238671
  function registerTools(server2, projectRoot) {
238672
+ server2.tool(
238673
+ "mikk_test_tool",
238674
+ "A simple test tool that returns a static message.",
238675
+ {},
238676
+ async () => {
238677
+ return { content: [{ type: "text", text: "Mikk test tool executed successfully." }] };
238678
+ }
238679
+ );
238606
238680
  server2.tool(
238607
238681
  "mikk_get_project_overview",
238608
- "Get a high-level overview of the project: modules, function counts, file counts, tech stack",
238682
+ "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
238683
  {},
238610
238684
  async () => {
238611
238685
  const { contract, lock, staleness } = await loadContractAndLock(projectRoot);
@@ -238637,7 +238711,7 @@ function registerTools(server2, projectRoot) {
238637
238711
  );
238638
238712
  server2.tool(
238639
238713
  "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.",
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.",
238641
238715
  {
238642
238716
  question: external_exports.string().describe("The architecture question or task description"),
238643
238717
  maxHops: external_exports.number().optional().default(4).describe("Graph traversal depth (default: 4)"),
@@ -238681,7 +238755,7 @@ ${staleness}` : "";
238681
238755
  );
238682
238756
  server2.tool(
238683
238757
  "mikk_impact_analysis",
238684
- "Analyze the blast radius of changing a specific file. Returns which functions and modules would be impacted.",
238758
+ "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
238759
  {
238686
238760
  file: external_exports.string().describe("The file path (relative to project root) to analyze impact for")
238687
238761
  },
@@ -238731,9 +238805,50 @@ ${staleness}` : "";
238731
238805
  return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
238732
238806
  }
238733
238807
  );
238808
+ server2.tool(
238809
+ "mikk_search_functions",
238810
+ "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.",
238811
+ {
238812
+ query: external_exports.string().describe("The search query for function names or IDs"),
238813
+ limit: external_exports.number().optional().default(10).describe("Maximum number of results to return")
238814
+ },
238815
+ async ({ query, limit }) => {
238816
+ const { lock, staleness } = await loadContractAndLock(projectRoot);
238817
+ const allFunctions = Object.values(lock.functions);
238818
+ const queryLower = query.toLowerCase();
238819
+ const substringMatches = allFunctions.filter((fn) => fn.name.toLowerCase().includes(queryLower) || fn.id.toLowerCase().includes(queryLower)).map((fn, i) => ({ id: fn.id, score: 100 - i }));
238820
+ const bm25 = new BM25Index();
238821
+ for (const fn of allFunctions) {
238822
+ bm25.addDocument(fn.id, buildFunctionTokens(fn));
238823
+ }
238824
+ const bm25Matches = bm25.search(query, limit * 2);
238825
+ const fused = reciprocalRankFusion(substringMatches, bm25Matches);
238826
+ const matches = fused.slice(0, limit).map((result) => {
238827
+ const fn = lock.functions[result.id];
238828
+ if (!fn) return null;
238829
+ return {
238830
+ name: fn.name,
238831
+ file: fn.file,
238832
+ module: fn.moduleId,
238833
+ exported: fn.isExported,
238834
+ lines: `${fn.startLine}-${fn.endLine}`,
238835
+ relevance: Math.round(result.score * 1e4) / 1e4
238836
+ };
238837
+ }).filter(Boolean);
238838
+ if (matches.length === 0) {
238839
+ return { content: [{ type: "text", text: `No functions matching "${query}" found.` }] };
238840
+ }
238841
+ const response = {
238842
+ matches,
238843
+ searchMethod: "hybrid (BM25 + substring via RRF)",
238844
+ warning: staleness
238845
+ };
238846
+ return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
238847
+ }
238848
+ );
238734
238849
  server2.tool(
238735
238850
  "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.",
238851
+ "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
238852
  {
238738
238853
  files: external_exports.array(external_exports.string()).describe("The file paths (relative to project root) you are about to edit")
238739
238854
  },
@@ -238772,7 +238887,7 @@ ${staleness}` : "";
238772
238887
  rule: v.rule,
238773
238888
  from: `${v.from.moduleName}::${v.from.functionName}`,
238774
238889
  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}"`
238890
+ message: `${v.from.moduleName}::${v.from.functionName} -> ${v.to.moduleName}::${v.to.functionName} violates: "${v.rule}"`
238776
238891
  }));
238777
238892
  const circularWarnings = detectCircularDeps(fileFns, lock);
238778
238893
  fileReports[file] = {
@@ -238796,14 +238911,14 @@ ${staleness}` : "";
238796
238911
  constraintStatus: totalViolations === 0 ? "pass" : "fail",
238797
238912
  files: fileReports,
238798
238913
  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."
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."
238800
238915
  };
238801
238916
  return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
238802
238917
  }
238803
238918
  );
238804
238919
  server2.tool(
238805
238920
  "mikk_list_modules",
238806
- "List all declared modules with their file/function counts and descriptions",
238921
+ "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
238922
  {},
238808
238923
  async () => {
238809
238924
  const { contract, lock, staleness } = await loadContractAndLock(projectRoot);
@@ -238829,7 +238944,7 @@ ${staleness}` : "";
238829
238944
  );
238830
238945
  server2.tool(
238831
238946
  "mikk_get_module_detail",
238832
- "Get detailed information about a specific module: its functions, files, exported API, and internal call graph",
238947
+ "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
238948
  {
238834
238949
  moduleId: external_exports.string().describe('The module ID (e.g., "packages-core", "lib-auth")')
238835
238950
  },
@@ -238868,7 +238983,7 @@ ${staleness}` : "";
238868
238983
  );
238869
238984
  server2.tool(
238870
238985
  "mikk_get_function_detail",
238871
- "Get detailed info about a specific function by name: params, return type, call graph, error handling, etc.",
238986
+ "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
238987
  {
238873
238988
  name: external_exports.string().describe('Function name to search for (e.g., "parseFiles", "GraphBuilder.build")')
238874
238989
  },
@@ -238914,36 +239029,9 @@ ${staleness}` : "";
238914
239029
  return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
238915
239030
  }
238916
239031
  );
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
239032
  server2.tool(
238945
239033
  "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.",
239034
+ '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
239035
  {
238948
239036
  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
239037
  topK: external_exports.number().optional().default(10).describe("Number of results to return (default: 10)")
@@ -238955,7 +239043,7 @@ ${staleness}` : "";
238955
239043
  content: [{
238956
239044
  type: "text",
238957
239045
  text: [
238958
- "\u274C Semantic search requires @xenova/transformers.",
239046
+ "\x9D\u0152 Semantic search requires @xenova/transformers.",
238959
239047
  "",
238960
239048
  "Install it in your project root:",
238961
239049
  " npm install @xenova/transformers",
@@ -238984,7 +239072,7 @@ ${staleness}` : "";
238984
239072
  );
238985
239073
  server2.tool(
238986
239074
  "mikk_get_constraints",
238987
- "Get all declared architectural constraints and design decisions for this project",
239075
+ "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
239076
  {},
238989
239077
  async () => {
238990
239078
  const { contract, staleness } = await loadContractAndLock(projectRoot);
@@ -238999,7 +239087,7 @@ ${staleness}` : "";
238999
239087
  );
239000
239088
  server2.tool(
239001
239089
  "mikk_get_file",
239002
- "Read the raw source content of any file in the project. Use this to see the actual code before editing.",
239090
+ "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
239091
  {
239004
239092
  file: external_exports.string().describe('File path relative to project root (e.g., "src/auth/verify.ts")')
239005
239093
  },
@@ -239033,7 +239121,7 @@ ${content}`
239033
239121
  );
239034
239122
  server2.tool(
239035
239123
  "mikk_find_usages",
239036
- "Find everything that calls a specific function. Essential before renaming or changing a function signature.",
239124
+ "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
239125
  {
239038
239126
  name: external_exports.string().describe("Function name to find callers of")
239039
239127
  },
@@ -239067,7 +239155,7 @@ ${content}`
239067
239155
  );
239068
239156
  server2.tool(
239069
239157
  "mikk_get_routes",
239070
- "Get all detected HTTP routes (Express/Koa/Hono style) with their methods, paths, handlers, and middlewares",
239158
+ "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
239159
  {},
239072
239160
  async () => {
239073
239161
  const { lock, staleness } = await loadContractAndLock(projectRoot);
@@ -239084,7 +239172,7 @@ ${content}`
239084
239172
  );
239085
239173
  server2.tool(
239086
239174
  "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.",
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.",
239088
239176
  {
239089
239177
  moduleId: external_exports.string().optional().describe("Filter results to a specific module ID")
239090
239178
  },
@@ -239109,7 +239197,7 @@ ${content}`
239109
239197
  );
239110
239198
  server2.tool(
239111
239199
  "mikk_manage_adr",
239112
- "Manage Architectural Decision Records (ADRs) in mikk.json. Actions: list, get, add, update, remove. ADRs document WHY architectural constraints exist.",
239200
+ "CRUD for Architectural Decision Records (ADRs) in mikk.json. Actions: list, get, add, update, remove. WHEN TO USE: When making architectural changes \u2014 document WHY so future AI agents understand. AFTER THIS: ADRs automatically surface in mikk_query_context responses. Required for add: id, title, reason.",
239113
239201
  {
239114
239202
  action: external_exports.enum(["list", "get", "add", "update", "remove"]).describe("The CRUD action to perform"),
239115
239203
  id: external_exports.string().optional().describe("ADR id (required for get, update, remove)"),
@@ -239166,7 +239254,7 @@ ${content}`
239166
239254
  );
239167
239255
  server2.tool(
239168
239256
  "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.",
239257
+ "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
239258
  {},
239171
239259
  async () => {
239172
239260
  const { lock, staleness } = await loadContractAndLock(projectRoot);
@@ -239217,7 +239305,7 @@ ${content}`
239217
239305
  );
239218
239306
  server2.tool(
239219
239307
  "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.",
239308
+ "Read file scoped to specific functions. Returns bodies with metadata headers (params, calls, calledBy). WHEN TO USE: When you know which functions you need \u2014 saves tokens vs mikk_get_file. AFTER THIS: Use mikk_before_edit before making changes. TIP: This is the preferred way to read code \u2014 always specify function names when possible.",
239221
239309
  {
239222
239310
  file: external_exports.string().describe("File path relative to project root"),
239223
239311
  functions: external_exports.array(external_exports.string()).optional().describe("Function names to extract. If omitted, returns the whole file.")
@@ -239257,11 +239345,11 @@ ${content}` }]
239257
239345
  (f) => (f.name === fnName || f.name.endsWith(`.${fnName}`)) && (f.file === normalizedFile || f.file.endsWith("/" + normalizedFile))
239258
239346
  );
239259
239347
  if (!fn) {
239260
- sections.push(`// \u274C Function "${fnName}" not found in ${file}`);
239348
+ sections.push(`// \x9D\u0152 Function "${fnName}" not found in ${file}`);
239261
239349
  continue;
239262
239350
  }
239263
239351
  const header = [
239264
- `// \u2500\u2500 ${fn.name} \u2500\u2500`,
239352
+ `// ${fn.name} `,
239265
239353
  `// File: ${fn.file}:${fn.startLine}-${fn.endLine}`,
239266
239354
  `// Module: ${fn.moduleId}`,
239267
239355
  fn.purpose ? `// Purpose: ${fn.purpose}` : null,
@@ -239285,7 +239373,7 @@ ${staleness}` : "";
239285
239373
  );
239286
239374
  server2.tool(
239287
239375
  "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.",
239376
+ "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
239377
  {},
239290
239378
  async () => {
239291
239379
  const { contract, lock, staleness } = await loadContractAndLock(projectRoot);
@@ -239347,6 +239435,107 @@ ${staleness}` : "";
239347
239435
  return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
239348
239436
  }
239349
239437
  );
239438
+ server2.tool(
239439
+ "mikk_git_diff_impact",
239440
+ "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.",
239441
+ {
239442
+ ref: external_exports.string().optional().default("HEAD~1").describe("Git ref to diff against (default: HEAD~1)"),
239443
+ staged: external_exports.boolean().optional().default(false).describe("If true, diff staged changes only")
239444
+ },
239445
+ async ({ ref, staged }) => {
239446
+ const { lock, staleness } = await loadContractAndLock(projectRoot);
239447
+ try {
239448
+ const diffArgs = staged ? "--cached" : ref;
239449
+ const rawDiff = (0, import_node_child_process.execSync)(`git diff ${diffArgs} --unified=0 --no-color`, {
239450
+ cwd: projectRoot,
239451
+ encoding: "utf-8",
239452
+ maxBuffer: 10 * 1024 * 1024
239453
+ });
239454
+ if (!rawDiff.trim()) {
239455
+ return { content: [{ type: "text", text: "No changes found in git diff." }] };
239456
+ }
239457
+ const fileHunks = parseDiffHunks(rawDiff);
239458
+ const affectedSymbols = [];
239459
+ for (const hunk of fileHunks) {
239460
+ const fileFns = Object.values(lock.functions).filter((fn) => fn.file === hunk.file || fn.file.endsWith(hunk.file));
239461
+ const affected = fileFns.filter((fn) => hunk.changedLines.some((l) => l >= fn.startLine && l <= fn.endLine));
239462
+ if (affected.length > 0 || hunk.isNew || hunk.isDeleted) {
239463
+ affectedSymbols.push({
239464
+ file: hunk.file,
239465
+ type: hunk.isNew ? "added" : hunk.isDeleted ? "deleted" : "modified",
239466
+ functions: affected.map((fn) => ({ name: fn.name, moduleId: fn.moduleId }))
239467
+ });
239468
+ }
239469
+ }
239470
+ const totalFns = affectedSymbols.reduce((s, f) => s + f.functions.length, 0);
239471
+ return { content: [{ type: "text", text: JSON.stringify({
239472
+ summary: `${affectedSymbols.length} file(s), ${totalFns} function(s) affected`,
239473
+ affectedSymbols,
239474
+ warning: staleness
239475
+ }, null, 2) }] };
239476
+ } catch (err) {
239477
+ return { content: [{ type: "text", text: `Git diff failed: ${err.message}` }], isError: true };
239478
+ }
239479
+ }
239480
+ );
239481
+ server2.tool(
239482
+ "mikk_rename",
239483
+ "Plan a coordinated multi-file rename. Finds all call sites and import locations for a function and provides a step-by-step edit plan. WHEN TO USE: Before renaming any function \u2014 ensures you update ALL call sites. AFTER THIS: Execute the edit plan, then run mikk analyze.",
239484
+ {
239485
+ functionName: external_exports.string().describe("The current function name to rename"),
239486
+ newName: external_exports.string().describe("The desired new name")
239487
+ },
239488
+ async ({ functionName, newName }) => {
239489
+ const { lock, staleness } = await loadContractAndLock(projectRoot);
239490
+ const targetFn = Object.values(lock.functions).find(
239491
+ (fn) => fn.name === functionName || fn.id.endsWith(`:${functionName}`)
239492
+ );
239493
+ if (!targetFn) {
239494
+ return {
239495
+ content: [{
239496
+ type: "text",
239497
+ text: `Function "${functionName}" not found. Use mikk_search_functions to find the correct name.`
239498
+ }],
239499
+ isError: true
239500
+ };
239501
+ }
239502
+ const callers = targetFn.calledBy.map((callerId) => lock.functions[callerId]).filter(Boolean).map((fn) => ({
239503
+ callerName: fn.name,
239504
+ file: fn.file,
239505
+ module: fn.moduleId,
239506
+ lineRange: `${fn.startLine}-${fn.endLine}`
239507
+ }));
239508
+ const filesImporting = Object.values(lock.files).filter(
239509
+ (file) => file.imports?.some((imp) => imp.includes(functionName) || imp.includes(targetFn.file))
239510
+ );
239511
+ const instructions = [
239512
+ `1. Rename definition in ${targetFn.file}:${targetFn.startLine}`,
239513
+ ...callers.map((c, i) => `${i + 2}. Update call in ${c.file} (${c.callerName}, lines ${c.lineRange})`),
239514
+ ...targetFn.isExported ? filesImporting.map((f, i) => `${callers.length + i + 2}. Update import in ${f.path}`) : [],
239515
+ `${callers.length + (targetFn.isExported ? filesImporting.length : 0) + 2}. Run \`mikk analyze\` to update the lock`
239516
+ ];
239517
+ return {
239518
+ content: [{
239519
+ type: "text",
239520
+ text: JSON.stringify({
239521
+ target: {
239522
+ currentName: functionName,
239523
+ newName,
239524
+ file: targetFn.file,
239525
+ line: targetFn.startLine,
239526
+ module: targetFn.moduleId,
239527
+ isExported: targetFn.isExported
239528
+ },
239529
+ callSites: callers,
239530
+ importSites: filesImporting.map((f) => ({ file: f.path, module: f.moduleId })),
239531
+ totalEdits: 1 + callers.length + filesImporting.length,
239532
+ instructions,
239533
+ warning: staleness
239534
+ }, null, 2)
239535
+ }]
239536
+ };
239537
+ }
239538
+ );
239350
239539
  }
239351
239540
  async function loadContractAndLock(projectRoot) {
239352
239541
  const cached2 = projectCache.get(projectRoot);
@@ -239360,7 +239549,7 @@ async function loadContractAndLock(projectRoot) {
239360
239549
  const syncStatus = lock.syncState?.status ?? "unknown";
239361
239550
  let staleness = null;
239362
239551
  if (syncStatus === "drifted" || syncStatus === "conflict") {
239363
- staleness = `\u26A0\uFE0F Lock file is ${syncStatus}. Run \`mikk analyze\` for accurate results.`;
239552
+ staleness = `\x8F Lock file is ${syncStatus}. Run \`mikk analyze\` for accurate results.`;
239364
239553
  }
239365
239554
  if (!staleness) {
239366
239555
  const fileEntries = Object.entries(lock.files);
@@ -239383,7 +239572,7 @@ async function loadContractAndLock(projectRoot) {
239383
239572
  }
239384
239573
  }
239385
239574
  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\`.`;
239575
+ staleness = `\x8F STALE: ${mismatched} file(s) changed since last analysis (${mismatchedFiles.slice(0, 3).join(", ")}${mismatched > 3 ? "..." : ""}). Run \`mikk analyze\`.`;
239387
239576
  }
239388
239577
  }
239389
239578
  const graph = buildGraphFromLock(lock);
@@ -239455,7 +239644,7 @@ function detectCircularDeps(fns, lock) {
239455
239644
  const cycleStart = cyclePath.indexOf(id);
239456
239645
  const cycle = cyclePath.slice(cycleStart).map((cid) => lock.functions[cid]?.name ?? cid);
239457
239646
  cycle.push(lock.functions[id]?.name ?? id);
239458
- warnings.push(`\u26A0\uFE0F Circular: ${cycle.join(" \u2192 ")}`);
239647
+ warnings.push(`\x8F Circular: ${cycle.join(" \u2020\u2019 ")}`);
239459
239648
  return true;
239460
239649
  }
239461
239650
  if (visited.has(id)) return false;
@@ -239501,6 +239690,35 @@ function isSourceFile(filePath) {
239501
239690
  const ext = path4.extname(filePath);
239502
239691
  return [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".go", ".py"].includes(ext);
239503
239692
  }
239693
+ function parseDiffHunks(diff) {
239694
+ const files = /* @__PURE__ */ new Map();
239695
+ let currentFile = "";
239696
+ let nextIsNew = false;
239697
+ for (const line of diff.split("\n")) {
239698
+ if (line.startsWith("--- ") && line.includes("/dev/null")) {
239699
+ nextIsNew = true;
239700
+ } else if (line.startsWith("+++ ")) {
239701
+ currentFile = line.slice(6);
239702
+ if (currentFile !== "/dev/null" && !files.has(currentFile)) {
239703
+ files.set(currentFile, { changedLines: [], isNew: nextIsNew, isDeleted: false });
239704
+ }
239705
+ if (currentFile === "/dev/null") {
239706
+ const prev = [...files.keys()].pop();
239707
+ if (prev) files.get(prev).isDeleted = true;
239708
+ }
239709
+ nextIsNew = false;
239710
+ } else if (line.startsWith("@@ ") && currentFile && files.has(currentFile)) {
239711
+ const match = line.match(/@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/);
239712
+ if (match) {
239713
+ const start = parseInt(match[1], 10);
239714
+ const count = parseInt(match[2] ?? "1", 10);
239715
+ const entry = files.get(currentFile);
239716
+ for (let i = 0; i < count; i++) entry.changedLines.push(start + i);
239717
+ }
239718
+ }
239719
+ }
239720
+ return [...files.entries()].map(([file, data]) => ({ file, ...data }));
239721
+ }
239504
239722
 
239505
239723
  // src/resources.ts
239506
239724
  var path5 = __toESM(require("node:path"), 1);
@@ -239561,7 +239779,7 @@ async function safeRead(filePath) {
239561
239779
  }
239562
239780
 
239563
239781
  // src/server.ts
239564
- var VERSION = true ? "1.8.1" : "0.0.0-dev";
239782
+ var VERSION = true ? "1.9.0" : "0.0.0-dev";
239565
239783
  function createMikkMcpServer(projectRoot) {
239566
239784
  const server2 = new McpServer({
239567
239785
  name: "mikk",