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