@getmikk/mcp-server 1.8.1 → 1.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/mikk-mcp.js +10 -1
- package/dist/index.cjs +500 -203
- 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)
|
|
236832
|
+
if (!callee || callee.moduleId === "unknown" || fn.moduleId === callee.moduleId)
|
|
236820
236833
|
continue;
|
|
236821
|
-
|
|
236822
|
-
|
|
236823
|
-
|
|
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 (
|
|
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)
|
|
236937
|
-
continue;
|
|
236938
|
-
const key = `${fn.moduleId}\u2192${callee.moduleId}`;
|
|
236939
|
-
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
236940
|
-
}
|
|
236941
|
-
}
|
|
236942
|
-
for (const file of Object.values(this.lock.files)) {
|
|
236943
|
-
if (!file.imports)
|
|
236944
|
-
continue;
|
|
236945
|
-
for (const importedPath of file.imports) {
|
|
236946
|
-
const importedFile = this.lock.files[importedPath];
|
|
236947
|
-
if (!importedFile || file.moduleId === importedFile.moduleId)
|
|
236901
|
+
if (!callee || fn.moduleId === callee.moduleId || fn.moduleId === "unknown" || callee.moduleId === "unknown")
|
|
236948
236902
|
continue;
|
|
236949
|
-
const key = `${
|
|
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);
|
|
@@ -238593,6 +238659,38 @@ var IntentSchema = external_exports.object({
|
|
|
238593
238659
|
// src/tools.ts
|
|
238594
238660
|
var projectCache = /* @__PURE__ */ new Map();
|
|
238595
238661
|
var CACHE_TTL_MS = 3e4;
|
|
238662
|
+
var _CPT = 4;
|
|
238663
|
+
var _ALC = 42;
|
|
238664
|
+
var _tallies = /* @__PURE__ */ new Map();
|
|
238665
|
+
function _tally(r) {
|
|
238666
|
+
let t = _tallies.get(r);
|
|
238667
|
+
if (!t) {
|
|
238668
|
+
t = { calls: 0, used: 0, raw: 0, saved: 0, start: Date.now() };
|
|
238669
|
+
_tallies.set(r, t);
|
|
238670
|
+
}
|
|
238671
|
+
return t;
|
|
238672
|
+
}
|
|
238673
|
+
function _tok(o) {
|
|
238674
|
+
return Math.max(1, Math.round(JSON.stringify(o).length / _CPT));
|
|
238675
|
+
}
|
|
238676
|
+
function _fileTok(lock, fp) {
|
|
238677
|
+
const fs22 = Object.values(lock.functions).filter((f) => f.file === fp);
|
|
238678
|
+
const ln = fs22.length > 0 ? Math.max(...fs22.map((f) => f.endLine)) : 80;
|
|
238679
|
+
return Math.round(ln * _ALC / _CPT);
|
|
238680
|
+
}
|
|
238681
|
+
function _filesTok(lock, fps) {
|
|
238682
|
+
return fps.reduce((s, f) => s + _fileTok(lock, f), 0);
|
|
238683
|
+
}
|
|
238684
|
+
function _track(root, raw, resp) {
|
|
238685
|
+
const used = _tok(resp);
|
|
238686
|
+
const saved = Math.max(0, raw - used);
|
|
238687
|
+
const t = _tally(root);
|
|
238688
|
+
t.calls++;
|
|
238689
|
+
t.used += used;
|
|
238690
|
+
t.raw += raw;
|
|
238691
|
+
t.saved += saved;
|
|
238692
|
+
return { used, raw, saved, sessionSaved: t.saved, sessionCalls: t.calls };
|
|
238693
|
+
}
|
|
238596
238694
|
var semanticSearchers = /* @__PURE__ */ new Map();
|
|
238597
238695
|
function getSemanticSearcher(projectRoot) {
|
|
238598
238696
|
let s = semanticSearchers.get(projectRoot);
|
|
@@ -238603,9 +238701,17 @@ function getSemanticSearcher(projectRoot) {
|
|
|
238603
238701
|
return s;
|
|
238604
238702
|
}
|
|
238605
238703
|
function registerTools(server2, projectRoot) {
|
|
238704
|
+
server2.tool(
|
|
238705
|
+
"mikk_test_tool",
|
|
238706
|
+
"A simple test tool that returns a static message.",
|
|
238707
|
+
{},
|
|
238708
|
+
async () => {
|
|
238709
|
+
return { content: [{ type: "text", text: "Mikk test tool executed successfully." }] };
|
|
238710
|
+
}
|
|
238711
|
+
);
|
|
238606
238712
|
server2.tool(
|
|
238607
238713
|
"mikk_get_project_overview",
|
|
238608
|
-
"Get a high-level overview
|
|
238714
|
+
"Get a high-level overview: modules, function counts, file counts, constraints. WHEN TO USE: When you need raw project stats. For session start, prefer mikk_get_session_context instead. AFTER THIS: Use mikk_query_context with your task, or mikk_list_modules to drill into a module.",
|
|
238609
238715
|
{},
|
|
238610
238716
|
async () => {
|
|
238611
238717
|
const { contract, lock, staleness } = await loadContractAndLock(projectRoot);
|
|
@@ -238632,12 +238738,14 @@ function registerTools(server2, projectRoot) {
|
|
|
238632
238738
|
warning: staleness,
|
|
238633
238739
|
hint: "Next: Use mikk_query_context with your task description, or mikk_list_modules to explore the architecture."
|
|
238634
238740
|
};
|
|
238741
|
+
const _rawOverview = Math.min(15, Object.keys(lock.files).length) * Math.round(80 * _ALC / _CPT);
|
|
238742
|
+
overview.tokens = _track(projectRoot, _rawOverview, overview);
|
|
238635
238743
|
return { content: [{ type: "text", text: JSON.stringify(overview, null, 2) }] };
|
|
238636
238744
|
}
|
|
238637
238745
|
);
|
|
238638
238746
|
server2.tool(
|
|
238639
238747
|
"mikk_query_context",
|
|
238640
|
-
"Ask an architecture question \
|
|
238748
|
+
"Ask an architecture question \xC3\xA2\xE2\u201A\xAC\xC2\x9D returns graph-traced context with relevant functions, files, and call chains. Use this to understand how code flows through the project.",
|
|
238641
238749
|
{
|
|
238642
238750
|
question: external_exports.string().describe("The architecture question or task description"),
|
|
238643
238751
|
maxHops: external_exports.number().optional().default(4).describe("Graph traversal depth (default: 4)"),
|
|
@@ -238674,14 +238782,20 @@ function registerTools(server2, projectRoot) {
|
|
|
238674
238782
|
const warning = staleness ? `
|
|
238675
238783
|
|
|
238676
238784
|
${staleness}` : "";
|
|
238785
|
+
const _rawQC = (tokenBudget ?? 6e3) * 3;
|
|
238786
|
+
const _tokQC = _track(projectRoot, _rawQC, output);
|
|
238787
|
+
const tokLine = `
|
|
238788
|
+
|
|
238789
|
+
---
|
|
238790
|
+
// tokens: ${JSON.stringify(_tokQC)}`;
|
|
238677
238791
|
return {
|
|
238678
|
-
content: [{ type: "text", text: output + warning + "\n\n---\nHint: Use mikk_before_edit on any files you plan to modify, then mikk_impact_analysis to see the full blast radius." }]
|
|
238792
|
+
content: [{ type: "text", text: output + warning + "\n\n---\nHint: Use mikk_before_edit on any files you plan to modify, then mikk_impact_analysis to see the full blast radius." + tokLine }]
|
|
238679
238793
|
};
|
|
238680
238794
|
}
|
|
238681
238795
|
);
|
|
238682
238796
|
server2.tool(
|
|
238683
238797
|
"mikk_impact_analysis",
|
|
238684
|
-
"Analyze the blast radius of changing a
|
|
238798
|
+
"Analyze the blast radius of changing a file. Returns impacted functions classified by severity (critical/high/medium/low). WHEN TO USE: Before refactoring, renaming, or modifying shared code. AFTER THIS: Use mikk_get_function_detail on critical/high items to review them.",
|
|
238685
238799
|
{
|
|
238686
238800
|
file: external_exports.string().describe("The file path (relative to project root) to analyze impact for")
|
|
238687
238801
|
},
|
|
@@ -238728,12 +238842,55 @@ ${staleness}` : "";
|
|
|
238728
238842
|
warning: staleness,
|
|
238729
238843
|
hint: "Next: Use mikk_get_function_detail on critical/high items to review them. Then mikk_before_edit to validate your planned changes."
|
|
238730
238844
|
};
|
|
238845
|
+
const _rawIA = _fileTok(lock, normalizedFile) + result.impacted.length * Math.round(40 * _ALC / _CPT);
|
|
238846
|
+
response.tokens = _track(projectRoot, _rawIA, response);
|
|
238847
|
+
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
238848
|
+
}
|
|
238849
|
+
);
|
|
238850
|
+
server2.tool(
|
|
238851
|
+
"mikk_search_functions",
|
|
238852
|
+
"Search for functions by name or ID using a hybrid BM25+substring search. WHEN TO USE: When you need to find a function but are unsure of its exact name or location. AFTER THIS: Use mikk_get_function_detail to get more information about a specific function.",
|
|
238853
|
+
{
|
|
238854
|
+
query: external_exports.string().describe("The search query for function names or IDs"),
|
|
238855
|
+
limit: external_exports.number().optional().default(10).describe("Maximum number of results to return")
|
|
238856
|
+
},
|
|
238857
|
+
async ({ query, limit }) => {
|
|
238858
|
+
const { lock, staleness } = await loadContractAndLock(projectRoot);
|
|
238859
|
+
const allFunctions = Object.values(lock.functions);
|
|
238860
|
+
const queryLower = query.toLowerCase();
|
|
238861
|
+
const substringMatches = allFunctions.filter((fn) => fn.name.toLowerCase().includes(queryLower) || fn.id.toLowerCase().includes(queryLower)).map((fn, i) => ({ id: fn.id, score: 100 - i }));
|
|
238862
|
+
const bm25 = new BM25Index();
|
|
238863
|
+
for (const fn of allFunctions) {
|
|
238864
|
+
bm25.addDocument(fn.id, buildFunctionTokens(fn));
|
|
238865
|
+
}
|
|
238866
|
+
const bm25Matches = bm25.search(query, limit * 2);
|
|
238867
|
+
const fused = reciprocalRankFusion(substringMatches, bm25Matches);
|
|
238868
|
+
const matches = fused.slice(0, limit).map((result) => {
|
|
238869
|
+
const fn = lock.functions[result.id];
|
|
238870
|
+
if (!fn) return null;
|
|
238871
|
+
return {
|
|
238872
|
+
name: fn.name,
|
|
238873
|
+
file: fn.file,
|
|
238874
|
+
module: fn.moduleId,
|
|
238875
|
+
exported: fn.isExported,
|
|
238876
|
+
lines: `${fn.startLine}-${fn.endLine}`,
|
|
238877
|
+
relevance: Math.round(result.score * 1e4) / 1e4
|
|
238878
|
+
};
|
|
238879
|
+
}).filter(Boolean);
|
|
238880
|
+
if (matches.length === 0) {
|
|
238881
|
+
return { content: [{ type: "text", text: `No functions matching "${query}" found.` }] };
|
|
238882
|
+
}
|
|
238883
|
+
const response = {
|
|
238884
|
+
matches,
|
|
238885
|
+
searchMethod: "hybrid (BM25 + substring via RRF)",
|
|
238886
|
+
warning: staleness
|
|
238887
|
+
};
|
|
238731
238888
|
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
238732
238889
|
}
|
|
238733
238890
|
);
|
|
238734
238891
|
server2.tool(
|
|
238735
238892
|
"mikk_before_edit",
|
|
238736
|
-
"Call
|
|
238893
|
+
"MANDATORY: Call BEFORE editing any file. Returns blast radius, exported functions at risk, constraint violations (6 rule types), and circular dependency warnings. WHEN TO USE: ALWAYS before modifying files. AFTER THIS: If constraintStatus is fail, redesign your approach. If pass, proceed with edits. TIP: Pass multiple files for combined blast radius.",
|
|
238737
238894
|
{
|
|
238738
238895
|
files: external_exports.array(external_exports.string()).describe("The file paths (relative to project root) you are about to edit")
|
|
238739
238896
|
},
|
|
@@ -238772,7 +238929,7 @@ ${staleness}` : "";
|
|
|
238772
238929
|
rule: v.rule,
|
|
238773
238930
|
from: `${v.from.moduleName}::${v.from.functionName}`,
|
|
238774
238931
|
to: `${v.to.moduleName}::${v.to.functionName}`,
|
|
238775
|
-
message:
|
|
238932
|
+
message: `${v.from.moduleName}::${v.from.functionName} -> ${v.to.moduleName}::${v.to.functionName} violates: "${v.rule}"`
|
|
238776
238933
|
}));
|
|
238777
238934
|
const circularWarnings = detectCircularDeps(fileFns, lock);
|
|
238778
238935
|
fileReports[file] = {
|
|
@@ -238796,14 +238953,16 @@ ${staleness}` : "";
|
|
|
238796
238953
|
constraintStatus: totalViolations === 0 ? "pass" : "fail",
|
|
238797
238954
|
files: fileReports,
|
|
238798
238955
|
warning: staleness,
|
|
238799
|
-
hint: totalViolations > 0 ? "\
|
|
238956
|
+
hint: totalViolations > 0 ? "\xC3\u201A\xC2\x8F Constraint violations detected! Review the violations before proceeding. Use mikk_get_constraints for full rule context." : "All constraints satisfied. If safe, proceed with your edits."
|
|
238800
238957
|
};
|
|
238958
|
+
const _rawBE = _filesTok(lock, filesToEdit) * 4;
|
|
238959
|
+
response.tokens = _track(projectRoot, _rawBE, response);
|
|
238801
238960
|
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
238802
238961
|
}
|
|
238803
238962
|
);
|
|
238804
238963
|
server2.tool(
|
|
238805
238964
|
"mikk_list_modules",
|
|
238806
|
-
"List all declared modules with
|
|
238965
|
+
"List all declared modules with file counts, function counts, entry points, and descriptions. WHEN TO USE: To explore the project structure. Good starting point after mikk_get_session_context. AFTER THIS: Use mikk_get_module_detail with a specific moduleId.",
|
|
238807
238966
|
{},
|
|
238808
238967
|
async () => {
|
|
238809
238968
|
const { contract, lock, staleness } = await loadContractAndLock(projectRoot);
|
|
@@ -238829,7 +238988,7 @@ ${staleness}` : "";
|
|
|
238829
238988
|
);
|
|
238830
238989
|
server2.tool(
|
|
238831
238990
|
"mikk_get_module_detail",
|
|
238832
|
-
"
|
|
238991
|
+
"Deep dive into a single module: all functions, files, exported API surface, internal call graph. WHEN TO USE: After mikk_list_modules to understand a specific module. AFTER THIS: Use mikk_get_function_detail for specific functions, or mikk_before_edit if modifying files in this module.",
|
|
238833
238992
|
{
|
|
238834
238993
|
moduleId: external_exports.string().describe('The module ID (e.g., "packages-core", "lib-auth")')
|
|
238835
238994
|
},
|
|
@@ -238868,7 +239027,7 @@ ${staleness}` : "";
|
|
|
238868
239027
|
);
|
|
238869
239028
|
server2.tool(
|
|
238870
239029
|
"mikk_get_function_detail",
|
|
238871
|
-
"
|
|
239030
|
+
"360-degree view of a function: params, return type, source body, call graph (who calls it + what it calls), error handling, edge cases. WHEN TO USE: When you need to understand a specific function in depth. AFTER THIS: Use mikk_find_usages to see all callers. TIP: Pass full qualified name (e.g. GraphBuilder.build) for class methods.",
|
|
238872
239031
|
{
|
|
238873
239032
|
name: external_exports.string().describe('Function name to search for (e.g., "parseFiles", "GraphBuilder.build")')
|
|
238874
239033
|
},
|
|
@@ -238914,36 +239073,9 @@ ${staleness}` : "";
|
|
|
238914
239073
|
return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
|
|
238915
239074
|
}
|
|
238916
239075
|
);
|
|
238917
|
-
server2.tool(
|
|
238918
|
-
"mikk_search_functions",
|
|
238919
|
-
"Search for functions by name pattern (substring match). Returns matching function names, files, and modules.",
|
|
238920
|
-
{
|
|
238921
|
-
query: external_exports.string().describe("Search query \u2014 matched against function names (case-insensitive)"),
|
|
238922
|
-
limit: external_exports.number().optional().default(20).describe("Max results to return (default: 20)")
|
|
238923
|
-
},
|
|
238924
|
-
async ({ query, limit }) => {
|
|
238925
|
-
const { lock, staleness } = await loadContractAndLock(projectRoot);
|
|
238926
|
-
const queryLower = query.toLowerCase();
|
|
238927
|
-
const matches = Object.values(lock.functions).filter((fn) => fn.name.toLowerCase().includes(queryLower) || fn.id.toLowerCase().includes(queryLower)).slice(0, limit).map((fn) => ({
|
|
238928
|
-
name: fn.name,
|
|
238929
|
-
file: fn.file,
|
|
238930
|
-
module: fn.moduleId,
|
|
238931
|
-
exported: fn.isExported,
|
|
238932
|
-
lines: `${fn.startLine}-${fn.endLine}`
|
|
238933
|
-
}));
|
|
238934
|
-
if (matches.length === 0) {
|
|
238935
|
-
return { content: [{ type: "text", text: `No functions matching "${query}" found.` }] };
|
|
238936
|
-
}
|
|
238937
|
-
const response = {
|
|
238938
|
-
matches,
|
|
238939
|
-
warning: staleness
|
|
238940
|
-
};
|
|
238941
|
-
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
238942
|
-
}
|
|
238943
|
-
);
|
|
238944
239076
|
server2.tool(
|
|
238945
239077
|
"mikk_semantic_search",
|
|
238946
|
-
|
|
239078
|
+
'Find functions by meaning using local vector embeddings. Query "validate JWT" returns verifyToken ranked by cosine similarity. WHEN TO USE: When you dont know the function name but know what it does. Complements mikk_search_functions (keyword). AFTER THIS: Use mikk_get_function_detail on top matches. Requires @xenova/transformers (22MB model, downloads once).',
|
|
238947
239079
|
{
|
|
238948
239080
|
query: external_exports.string().describe('Natural-language description of what you are looking for (e.g. "validate a JWT token", "send an email notification")'),
|
|
238949
239081
|
topK: external_exports.number().optional().default(10).describe("Number of results to return (default: 10)")
|
|
@@ -238955,7 +239087,7 @@ ${staleness}` : "";
|
|
|
238955
239087
|
content: [{
|
|
238956
239088
|
type: "text",
|
|
238957
239089
|
text: [
|
|
238958
|
-
"\
|
|
239090
|
+
"\xC3\u201A\xC2\x9D\xC3\u2026\xE2\u20AC\u2122 Semantic search requires @xenova/transformers.",
|
|
238959
239091
|
"",
|
|
238960
239092
|
"Install it in your project root:",
|
|
238961
239093
|
" npm install @xenova/transformers",
|
|
@@ -238984,7 +239116,7 @@ ${staleness}` : "";
|
|
|
238984
239116
|
);
|
|
238985
239117
|
server2.tool(
|
|
238986
239118
|
"mikk_get_constraints",
|
|
238987
|
-
"Get all
|
|
239119
|
+
"Get all architectural constraints and ADRs. WHEN TO USE: Before cross-module changes, or when mikk_before_edit reports violations. Understand WHY a constraint exists. AFTER THIS: Use mikk_manage_adr to add/update decisions. 6 constraint types: no-import, must-use, no-call, layer, naming, max-files.",
|
|
238988
239120
|
{},
|
|
238989
239121
|
async () => {
|
|
238990
239122
|
const { contract, staleness } = await loadContractAndLock(projectRoot);
|
|
@@ -238999,7 +239131,7 @@ ${staleness}` : "";
|
|
|
238999
239131
|
);
|
|
239000
239132
|
server2.tool(
|
|
239001
239133
|
"mikk_get_file",
|
|
239002
|
-
"Read
|
|
239134
|
+
"Read raw source of a file. TIP: Prefer mikk_read_file with function names to save tokens. WHEN TO USE: When you need entire file content (config files, small files). AFTER THIS: Use mikk_before_edit before making changes.",
|
|
239003
239135
|
{
|
|
239004
239136
|
file: external_exports.string().describe('File path relative to project root (e.g., "src/auth/verify.ts")')
|
|
239005
239137
|
},
|
|
@@ -239033,7 +239165,7 @@ ${content}`
|
|
|
239033
239165
|
);
|
|
239034
239166
|
server2.tool(
|
|
239035
239167
|
"mikk_find_usages",
|
|
239036
|
-
"Find
|
|
239168
|
+
"Find every function that calls a specific function. Essential before renaming or changing signatures. WHEN TO USE: Before renaming, refactoring, or changing a function interface. AFTER THIS: Review each caller to ensure your change wont break them. Use mikk_read_file to see caller code.",
|
|
239037
239169
|
{
|
|
239038
239170
|
name: external_exports.string().describe("Function name to find callers of")
|
|
239039
239171
|
},
|
|
@@ -239067,7 +239199,7 @@ ${content}`
|
|
|
239067
239199
|
);
|
|
239068
239200
|
server2.tool(
|
|
239069
239201
|
"mikk_get_routes",
|
|
239070
|
-
"Get all detected HTTP routes
|
|
239202
|
+
"Get all detected HTTP routes with methods, paths, handlers, and middleware chains. WHEN TO USE: When working on API endpoints. Shows Express/Koa/Hono route registrations detected from AST. AFTER THIS: Use mikk_get_function_detail on a handler to see its implementation.",
|
|
239071
239203
|
{},
|
|
239072
239204
|
async () => {
|
|
239073
239205
|
const { lock, staleness } = await loadContractAndLock(projectRoot);
|
|
@@ -239084,7 +239216,7 @@ ${content}`
|
|
|
239084
239216
|
);
|
|
239085
239217
|
server2.tool(
|
|
239086
239218
|
"mikk_dead_code",
|
|
239087
|
-
"Detect dead code \
|
|
239219
|
+
"Detect dead code \xC3\xA2\xE2\u201A\xAC\xC2\x9D functions with zero callers after exempting exports, entry points, route handlers, tests, and constructors. Use this before refactoring or cleanup.",
|
|
239088
239220
|
{
|
|
239089
239221
|
moduleId: external_exports.string().optional().describe("Filter results to a specific module ID")
|
|
239090
239222
|
},
|
|
@@ -239109,7 +239241,7 @@ ${content}`
|
|
|
239109
239241
|
);
|
|
239110
239242
|
server2.tool(
|
|
239111
239243
|
"mikk_manage_adr",
|
|
239112
|
-
"
|
|
239244
|
+
"CRUD for Architectural Decision Records (ADRs) in mikk.json. Actions: list, get, add, update, remove. WHEN TO USE: When making architectural changes \xC3\xA2\xE2\u201A\xAC\xE2\u20AC\x9D document WHY so future AI agents understand. AFTER THIS: ADRs automatically surface in mikk_query_context responses. Required for add: id, title, reason.",
|
|
239113
239245
|
{
|
|
239114
239246
|
action: external_exports.enum(["list", "get", "add", "update", "remove"]).describe("The CRUD action to perform"),
|
|
239115
239247
|
id: external_exports.string().optional().describe("ADR id (required for get, update, remove)"),
|
|
@@ -239166,7 +239298,7 @@ ${content}`
|
|
|
239166
239298
|
);
|
|
239167
239299
|
server2.tool(
|
|
239168
239300
|
"mikk_get_changes",
|
|
239169
|
-
"Detect files
|
|
239301
|
+
"Detect files added, modified, and deleted since last mikk analyze. WHEN TO USE: At session start (after mikk_get_session_context), or after making edits to see what drifted. AFTER THIS: Run mikk analyze to update the lock, then mikk_impact_analysis on modified files. Uses SHA-256 hash comparison for accurate drift detection.",
|
|
239170
239302
|
{},
|
|
239171
239303
|
async () => {
|
|
239172
239304
|
const { lock, staleness } = await loadContractAndLock(projectRoot);
|
|
@@ -239212,12 +239344,14 @@ ${content}`
|
|
|
239212
239344
|
warning: staleness,
|
|
239213
239345
|
hint: modified.length + added.length > 0 ? "Run `mikk analyze` to update the lock file with these changes." : "Codebase is in sync with the lock file."
|
|
239214
239346
|
};
|
|
239347
|
+
const _rawGC = Math.min(50, Object.keys(lock.files).length) * Math.round(60 * _ALC / _CPT);
|
|
239348
|
+
response.tokens = _track(projectRoot, _rawGC, response);
|
|
239215
239349
|
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
239216
239350
|
}
|
|
239217
239351
|
);
|
|
239218
239352
|
server2.tool(
|
|
239219
239353
|
"mikk_read_file",
|
|
239220
|
-
"Read file
|
|
239354
|
+
"Read file scoped to specific functions. Returns bodies with metadata headers (params, calls, calledBy). WHEN TO USE: When you know which functions you need \xC3\xA2\xE2\u201A\xAC\xE2\u20AC\x9D saves tokens vs mikk_get_file. AFTER THIS: Use mikk_before_edit before making changes. TIP: This is the preferred way to read code \xC3\xA2\xE2\u201A\xAC\xE2\u20AC\x9D always specify function names when possible.",
|
|
239221
239355
|
{
|
|
239222
239356
|
file: external_exports.string().describe("File path relative to project root"),
|
|
239223
239357
|
functions: external_exports.array(external_exports.string()).optional().describe("Function names to extract. If omitted, returns the whole file.")
|
|
@@ -239257,11 +239391,11 @@ ${content}` }]
|
|
|
239257
239391
|
(f) => (f.name === fnName || f.name.endsWith(`.${fnName}`)) && (f.file === normalizedFile || f.file.endsWith("/" + normalizedFile))
|
|
239258
239392
|
);
|
|
239259
239393
|
if (!fn) {
|
|
239260
|
-
sections.push(`// \
|
|
239394
|
+
sections.push(`// \xC3\u201A\xC2\x9D\xC3\u2026\xE2\u20AC\u2122 Function "${fnName}" not found in ${file}`);
|
|
239261
239395
|
continue;
|
|
239262
239396
|
}
|
|
239263
239397
|
const header = [
|
|
239264
|
-
`//
|
|
239398
|
+
`// ${fn.name} `,
|
|
239265
239399
|
`// File: ${fn.file}:${fn.startLine}-${fn.endLine}`,
|
|
239266
239400
|
`// Module: ${fn.moduleId}`,
|
|
239267
239401
|
fn.purpose ? `// Purpose: ${fn.purpose}` : null,
|
|
@@ -239280,12 +239414,15 @@ ${body}`);
|
|
|
239280
239414
|
const warningText = staleness ? `
|
|
239281
239415
|
|
|
239282
239416
|
${staleness}` : "";
|
|
239283
|
-
|
|
239417
|
+
const _rawRF = _fileTok(lock, file.replace(/\\/g, "/"));
|
|
239418
|
+
const _tokRF = _track(projectRoot, _rawRF, output);
|
|
239419
|
+
return { content: [{ type: "text", text: output + warningText + `
|
|
239420
|
+
// tokens: ${JSON.stringify(_tokRF)}` }] };
|
|
239284
239421
|
}
|
|
239285
239422
|
);
|
|
239286
239423
|
server2.tool(
|
|
239287
239424
|
"mikk_get_session_context",
|
|
239288
|
-
"One-shot context for
|
|
239425
|
+
"CALL THIS FIRST. One-shot context for session start: project overview + constraint status + hot modules + recently modified files + active decisions. WHEN TO USE: At the very beginning of every AI conversation. This is your onboarding. AFTER THIS: Use mikk_query_context with your task description, or mikk_get_changes for detailed drift.",
|
|
239289
239426
|
{},
|
|
239290
239427
|
async () => {
|
|
239291
239428
|
const { contract, lock, staleness } = await loadContractAndLock(projectRoot);
|
|
@@ -239344,6 +239481,140 @@ ${staleness}` : "";
|
|
|
239344
239481
|
warning: staleness,
|
|
239345
239482
|
hint: changedCount > 0 ? `${changedCount} file(s) may have changed. Run \`mikk analyze\` for accurate results, or use mikk_get_changes for details.` : "Codebase is in sync. Use mikk_query_context with your task description to get started."
|
|
239346
239483
|
};
|
|
239484
|
+
const _rawSC = Math.min(20, Object.keys(lock.files).length) * Math.round(100 * _ALC / _CPT);
|
|
239485
|
+
response.tokens = _track(projectRoot, _rawSC, response);
|
|
239486
|
+
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
239487
|
+
}
|
|
239488
|
+
);
|
|
239489
|
+
server2.tool(
|
|
239490
|
+
"mikk_git_diff_impact",
|
|
239491
|
+
"Map git diff hunks to affected symbols. Shows which functions were modified/added/deleted. WHEN TO USE: After commits/merges to understand symbol-level changes. AFTER THIS: Use mikk_impact_analysis on affected files.",
|
|
239492
|
+
{
|
|
239493
|
+
ref: external_exports.string().optional().default("HEAD~1").describe("Git ref to diff against (default: HEAD~1)"),
|
|
239494
|
+
staged: external_exports.boolean().optional().default(false).describe("If true, diff staged changes only")
|
|
239495
|
+
},
|
|
239496
|
+
async ({ ref, staged }) => {
|
|
239497
|
+
const { lock, staleness } = await loadContractAndLock(projectRoot);
|
|
239498
|
+
try {
|
|
239499
|
+
const diffArgs = staged ? "--cached" : ref;
|
|
239500
|
+
const rawDiff = (0, import_node_child_process.execSync)(`git diff ${diffArgs} --unified=0 --no-color`, {
|
|
239501
|
+
cwd: projectRoot,
|
|
239502
|
+
encoding: "utf-8",
|
|
239503
|
+
maxBuffer: 10 * 1024 * 1024
|
|
239504
|
+
});
|
|
239505
|
+
if (!rawDiff.trim()) {
|
|
239506
|
+
return { content: [{ type: "text", text: "No changes found in git diff." }] };
|
|
239507
|
+
}
|
|
239508
|
+
const fileHunks = parseDiffHunks(rawDiff);
|
|
239509
|
+
const affectedSymbols = [];
|
|
239510
|
+
for (const hunk of fileHunks) {
|
|
239511
|
+
const fileFns = Object.values(lock.functions).filter((fn) => fn.file === hunk.file || fn.file.endsWith(hunk.file));
|
|
239512
|
+
const affected = fileFns.filter((fn) => hunk.changedLines.some((l) => l >= fn.startLine && l <= fn.endLine));
|
|
239513
|
+
if (affected.length > 0 || hunk.isNew || hunk.isDeleted) {
|
|
239514
|
+
affectedSymbols.push({
|
|
239515
|
+
file: hunk.file,
|
|
239516
|
+
type: hunk.isNew ? "added" : hunk.isDeleted ? "deleted" : "modified",
|
|
239517
|
+
functions: affected.map((fn) => ({ name: fn.name, moduleId: fn.moduleId }))
|
|
239518
|
+
});
|
|
239519
|
+
}
|
|
239520
|
+
}
|
|
239521
|
+
const totalFns = affectedSymbols.reduce((s, f) => s + f.functions.length, 0);
|
|
239522
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
239523
|
+
summary: `${affectedSymbols.length} file(s), ${totalFns} function(s) affected`,
|
|
239524
|
+
affectedSymbols,
|
|
239525
|
+
warning: staleness
|
|
239526
|
+
}, null, 2) }] };
|
|
239527
|
+
} catch (err) {
|
|
239528
|
+
return { content: [{ type: "text", text: `Git diff failed: ${err.message}` }], isError: true };
|
|
239529
|
+
}
|
|
239530
|
+
}
|
|
239531
|
+
);
|
|
239532
|
+
server2.tool(
|
|
239533
|
+
"mikk_rename",
|
|
239534
|
+
"Plan a coordinated multi-file rename. Finds all call sites and import locations for a function and provides a step-by-step edit plan. WHEN TO USE: Before renaming any function \xC3\xA2\xE2\u201A\xAC\xE2\u20AC\x9D ensures you update ALL call sites. AFTER THIS: Execute the edit plan, then run mikk analyze.",
|
|
239535
|
+
{
|
|
239536
|
+
functionName: external_exports.string().describe("The current function name to rename"),
|
|
239537
|
+
newName: external_exports.string().describe("The desired new name")
|
|
239538
|
+
},
|
|
239539
|
+
async ({ functionName, newName }) => {
|
|
239540
|
+
const { lock, staleness } = await loadContractAndLock(projectRoot);
|
|
239541
|
+
const targetFn = Object.values(lock.functions).find(
|
|
239542
|
+
(fn) => fn.name === functionName || fn.id.endsWith(`:${functionName}`)
|
|
239543
|
+
);
|
|
239544
|
+
if (!targetFn) {
|
|
239545
|
+
return {
|
|
239546
|
+
content: [{
|
|
239547
|
+
type: "text",
|
|
239548
|
+
text: `Function "${functionName}" not found. Use mikk_search_functions to find the correct name.`
|
|
239549
|
+
}],
|
|
239550
|
+
isError: true
|
|
239551
|
+
};
|
|
239552
|
+
}
|
|
239553
|
+
const callers = targetFn.calledBy.map((callerId) => lock.functions[callerId]).filter(Boolean).map((fn) => ({
|
|
239554
|
+
callerName: fn.name,
|
|
239555
|
+
file: fn.file,
|
|
239556
|
+
module: fn.moduleId,
|
|
239557
|
+
lineRange: `${fn.startLine}-${fn.endLine}`
|
|
239558
|
+
}));
|
|
239559
|
+
const filesImporting = Object.values(lock.files).filter(
|
|
239560
|
+
(file) => file.imports?.some((imp) => imp.includes(functionName) || imp.includes(targetFn.file))
|
|
239561
|
+
);
|
|
239562
|
+
const instructions = [
|
|
239563
|
+
`1. Rename definition in ${targetFn.file}:${targetFn.startLine}`,
|
|
239564
|
+
...callers.map((c, i) => `${i + 2}. Update call in ${c.file} (${c.callerName}, lines ${c.lineRange})`),
|
|
239565
|
+
...targetFn.isExported ? filesImporting.map((f, i) => `${callers.length + i + 2}. Update import in ${f.path}`) : [],
|
|
239566
|
+
`${callers.length + (targetFn.isExported ? filesImporting.length : 0) + 2}. Run \`mikk analyze\` to update the lock`
|
|
239567
|
+
];
|
|
239568
|
+
return {
|
|
239569
|
+
content: [{
|
|
239570
|
+
type: "text",
|
|
239571
|
+
text: JSON.stringify({
|
|
239572
|
+
target: {
|
|
239573
|
+
currentName: functionName,
|
|
239574
|
+
newName,
|
|
239575
|
+
file: targetFn.file,
|
|
239576
|
+
line: targetFn.startLine,
|
|
239577
|
+
module: targetFn.moduleId,
|
|
239578
|
+
isExported: targetFn.isExported
|
|
239579
|
+
},
|
|
239580
|
+
callSites: callers,
|
|
239581
|
+
importSites: filesImporting.map((f) => ({ file: f.path, module: f.moduleId })),
|
|
239582
|
+
totalEdits: 1 + callers.length + filesImporting.length,
|
|
239583
|
+
instructions,
|
|
239584
|
+
warning: staleness
|
|
239585
|
+
}, null, 2)
|
|
239586
|
+
}]
|
|
239587
|
+
};
|
|
239588
|
+
}
|
|
239589
|
+
);
|
|
239590
|
+
server2.tool(
|
|
239591
|
+
"mikk_token_stats",
|
|
239592
|
+
"Show token savings for this session \u2014 how many tokens Mikk saved vs. the agent reading raw source files. WHEN TO USE: Any time. Useful at end of session to see cumulative efficiency. Returns per-session totals and cost estimates.",
|
|
239593
|
+
{},
|
|
239594
|
+
async () => {
|
|
239595
|
+
const t = _tally(projectRoot);
|
|
239596
|
+
const { lock } = await loadContractAndLock(projectRoot);
|
|
239597
|
+
const totalFileLine = Object.values(lock.functions).reduce((s, f) => s + (f.endLine - f.startLine + 1), 0);
|
|
239598
|
+
const fullCodebaseTok = Math.round(totalFileLine * _ALC / _CPT);
|
|
239599
|
+
const elapsedMin = Math.round((Date.now() - t.start) / 6e4);
|
|
239600
|
+
const response = {
|
|
239601
|
+
session: {
|
|
239602
|
+
calls: t.calls,
|
|
239603
|
+
elapsedMinutes: elapsedMin
|
|
239604
|
+
},
|
|
239605
|
+
tokens: {
|
|
239606
|
+
used: t.used,
|
|
239607
|
+
rawWouldHaveCost: t.raw,
|
|
239608
|
+
saved: t.saved,
|
|
239609
|
+
savingsPercent: t.raw > 0 ? Math.round(t.saved / t.raw * 100) : 0
|
|
239610
|
+
},
|
|
239611
|
+
context: {
|
|
239612
|
+
fullCodebaseTokens: fullCodebaseTok,
|
|
239613
|
+
percentOfCodebaseRead: t.raw > 0 ? Math.round(t.used / fullCodebaseTok * 100) : 0,
|
|
239614
|
+
note: "Full codebase = if agent read every tracked source line once"
|
|
239615
|
+
},
|
|
239616
|
+
interpretation: t.saved > 0 ? `Mikk saved ~${t.saved.toLocaleString()} tokens this session (${Math.round(t.saved / t.raw * 100)}% reduction). Roughly ${Math.round(t.saved / 1e3)}k tokens = ~${(t.saved * 3e-6).toFixed(3)} USD at GPT-4o rates.` : "No tools called yet this session."
|
|
239617
|
+
};
|
|
239347
239618
|
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
239348
239619
|
}
|
|
239349
239620
|
);
|
|
@@ -239360,7 +239631,7 @@ async function loadContractAndLock(projectRoot) {
|
|
|
239360
239631
|
const syncStatus = lock.syncState?.status ?? "unknown";
|
|
239361
239632
|
let staleness = null;
|
|
239362
239633
|
if (syncStatus === "drifted" || syncStatus === "conflict") {
|
|
239363
|
-
staleness = `\
|
|
239634
|
+
staleness = `\xC3\u201A\xC2\x8F Lock file is ${syncStatus}. Run \`mikk analyze\` for accurate results.`;
|
|
239364
239635
|
}
|
|
239365
239636
|
if (!staleness) {
|
|
239366
239637
|
const fileEntries = Object.entries(lock.files);
|
|
@@ -239383,7 +239654,7 @@ async function loadContractAndLock(projectRoot) {
|
|
|
239383
239654
|
}
|
|
239384
239655
|
}
|
|
239385
239656
|
if (mismatched > 0) {
|
|
239386
|
-
staleness = `\
|
|
239657
|
+
staleness = `\xC3\u201A\xC2\x8F STALE: ${mismatched} file(s) changed since last analysis (${mismatchedFiles.slice(0, 3).join(", ")}${mismatched > 3 ? "..." : ""}). Run \`mikk analyze\`.`;
|
|
239387
239658
|
}
|
|
239388
239659
|
}
|
|
239389
239660
|
const graph = buildGraphFromLock(lock);
|
|
@@ -239455,7 +239726,7 @@ function detectCircularDeps(fns, lock) {
|
|
|
239455
239726
|
const cycleStart = cyclePath.indexOf(id);
|
|
239456
239727
|
const cycle = cyclePath.slice(cycleStart).map((cid) => lock.functions[cid]?.name ?? cid);
|
|
239457
239728
|
cycle.push(lock.functions[id]?.name ?? id);
|
|
239458
|
-
warnings.push(`\
|
|
239729
|
+
warnings.push(`\xC3\u201A\xC2\x8F Circular: ${cycle.join(" \xC3\xA2\xE2\u201A\xAC\xC2\xA0\xC3\xA2\xE2\u201A\xAC\xE2\u201E\xA2 ")}`);
|
|
239459
239730
|
return true;
|
|
239460
239731
|
}
|
|
239461
239732
|
if (visited.has(id)) return false;
|
|
@@ -239501,6 +239772,35 @@ function isSourceFile(filePath) {
|
|
|
239501
239772
|
const ext = path4.extname(filePath);
|
|
239502
239773
|
return [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".go", ".py"].includes(ext);
|
|
239503
239774
|
}
|
|
239775
|
+
function parseDiffHunks(diff) {
|
|
239776
|
+
const files = /* @__PURE__ */ new Map();
|
|
239777
|
+
let currentFile = "";
|
|
239778
|
+
let nextIsNew = false;
|
|
239779
|
+
for (const line of diff.split("\n")) {
|
|
239780
|
+
if (line.startsWith("--- ") && line.includes("/dev/null")) {
|
|
239781
|
+
nextIsNew = true;
|
|
239782
|
+
} else if (line.startsWith("+++ ")) {
|
|
239783
|
+
currentFile = line.slice(6);
|
|
239784
|
+
if (currentFile !== "/dev/null" && !files.has(currentFile)) {
|
|
239785
|
+
files.set(currentFile, { changedLines: [], isNew: nextIsNew, isDeleted: false });
|
|
239786
|
+
}
|
|
239787
|
+
if (currentFile === "/dev/null") {
|
|
239788
|
+
const prev = [...files.keys()].pop();
|
|
239789
|
+
if (prev) files.get(prev).isDeleted = true;
|
|
239790
|
+
}
|
|
239791
|
+
nextIsNew = false;
|
|
239792
|
+
} else if (line.startsWith("@@ ") && currentFile && files.has(currentFile)) {
|
|
239793
|
+
const match = line.match(/@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/);
|
|
239794
|
+
if (match) {
|
|
239795
|
+
const start = parseInt(match[1], 10);
|
|
239796
|
+
const count = parseInt(match[2] ?? "1", 10);
|
|
239797
|
+
const entry = files.get(currentFile);
|
|
239798
|
+
for (let i = 0; i < count; i++) entry.changedLines.push(start + i);
|
|
239799
|
+
}
|
|
239800
|
+
}
|
|
239801
|
+
}
|
|
239802
|
+
return [...files.entries()].map(([file, data]) => ({ file, ...data }));
|
|
239803
|
+
}
|
|
239504
239804
|
|
|
239505
239805
|
// src/resources.ts
|
|
239506
239806
|
var path5 = __toESM(require("node:path"), 1);
|
|
@@ -239561,7 +239861,7 @@ async function safeRead(filePath) {
|
|
|
239561
239861
|
}
|
|
239562
239862
|
|
|
239563
239863
|
// src/server.ts
|
|
239564
|
-
var VERSION = true ? "1.
|
|
239864
|
+
var VERSION = true ? "1.9.1" : "0.0.0-dev";
|
|
239565
239865
|
function createMikkMcpServer(projectRoot) {
|
|
239566
239866
|
const server2 = new McpServer({
|
|
239567
239867
|
name: "mikk",
|
|
@@ -239671,9 +239971,6 @@ async function startStdioServer() {
|
|
|
239671
239971
|
const transport = new StdioServerTransport();
|
|
239672
239972
|
await server2.connect(transport);
|
|
239673
239973
|
}
|
|
239674
|
-
|
|
239675
|
-
// src/index.ts
|
|
239676
|
-
startStdioServer();
|
|
239677
239974
|
// Annotate the CommonJS export names for ESM import in node:
|
|
239678
239975
|
0 && (module.exports = {
|
|
239679
239976
|
createMikkMcpServer,
|