@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 +416 -198
- package/dist/index.cjs.map +4 -4
- package/package.json +7 -5
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 =
|
|
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 =
|
|
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
|
|
41882
|
+
return Parser2.parseIsolatedEntityName(text, languageVersion);
|
|
41883
41883
|
}
|
|
41884
41884
|
function parseJsonText(fileName, sourceText) {
|
|
41885
|
-
return
|
|
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 =
|
|
41896
|
+
const result = Parser2.JSDocParser.parseIsolatedJSDocComment(content, start, length2);
|
|
41897
41897
|
if (result && result.jsDoc) {
|
|
41898
|
-
|
|
41898
|
+
Parser2.fixupParentReferences(result.jsDoc);
|
|
41899
41899
|
}
|
|
41900
41900
|
return result;
|
|
41901
41901
|
}
|
|
41902
41902
|
function parseJSDocTypeExpressionForTests(content, start, length2) {
|
|
41903
|
-
return
|
|
41903
|
+
return Parser2.JSDocParser.parseJSDocTypeExpressionForTests(content, start, length2);
|
|
41904
41904
|
}
|
|
41905
|
-
var
|
|
41906
|
-
((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
49744
|
-
})(
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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()
|
|
236785
|
-
const
|
|
236786
|
-
|
|
236787
|
-
|
|
236788
|
-
return { type: "deny", fromModuleId:
|
|
236789
|
-
|
|
236790
|
-
|
|
236791
|
-
|
|
236792
|
-
|
|
236793
|
-
|
|
236794
|
-
|
|
236795
|
-
const
|
|
236796
|
-
if (
|
|
236797
|
-
return { type: "
|
|
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
|
|
236824
|
-
if (
|
|
236825
|
-
violations.push(
|
|
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 (
|
|
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
|
|
236838
|
-
if (
|
|
236839
|
-
violations.push(
|
|
236846
|
+
const v = this.checkFileImport(file, importedFile);
|
|
236847
|
+
if (v)
|
|
236848
|
+
violations.push(v);
|
|
236840
236849
|
}
|
|
236841
236850
|
}
|
|
236842
|
-
const
|
|
236843
|
-
const
|
|
236844
|
-
|
|
236845
|
-
|
|
236846
|
-
|
|
236847
|
-
|
|
236848
|
-
|
|
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
|
-
|
|
236860
|
-
|
|
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
|
-
|
|
236872
|
-
|
|
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
|
-
|
|
236900
|
-
if (
|
|
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
|
-
|
|
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}
|
|
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("
|
|
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
|
|
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
|
|
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 \
|
|
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
|
|
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
|
|
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:
|
|
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 ? "\
|
|
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
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
"\
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 \
|
|
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
|
-
"
|
|
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
|
|
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
|
|
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(`// \
|
|
239348
|
+
sections.push(`// \x9D\u0152 Function "${fnName}" not found in ${file}`);
|
|
239261
239349
|
continue;
|
|
239262
239350
|
}
|
|
239263
239351
|
const header = [
|
|
239264
|
-
`//
|
|
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
|
|
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 = `\
|
|
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 = `\
|
|
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(`\
|
|
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.
|
|
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",
|