@getmikk/mcp-server 1.9.1 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +807 -172
- package/dist/index.cjs.map +4 -4
- package/package.json +7 -7
package/dist/index.cjs
CHANGED
|
@@ -6,6 +6,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
9
12
|
var __commonJS = (cb, mod) => function __require() {
|
|
10
13
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
11
14
|
};
|
|
@@ -6800,6 +6803,20 @@ var require_dist = __commonJS({
|
|
|
6800
6803
|
}
|
|
6801
6804
|
});
|
|
6802
6805
|
|
|
6806
|
+
// ../core/dist/parser/base-parser.js
|
|
6807
|
+
var init_base_parser = __esm({
|
|
6808
|
+
"../core/dist/parser/base-parser.js"() {
|
|
6809
|
+
"use strict";
|
|
6810
|
+
}
|
|
6811
|
+
});
|
|
6812
|
+
|
|
6813
|
+
// ../core/dist/hash/file-hasher.js
|
|
6814
|
+
var init_file_hasher = __esm({
|
|
6815
|
+
"../core/dist/hash/file-hasher.js"() {
|
|
6816
|
+
"use strict";
|
|
6817
|
+
}
|
|
6818
|
+
});
|
|
6819
|
+
|
|
6803
6820
|
// ../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/typescript.js
|
|
6804
6821
|
var require_typescript = __commonJS({
|
|
6805
6822
|
"../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/typescript.js"(exports2, module2) {
|
|
@@ -15394,7 +15411,7 @@ ${lanes.join("\n")}
|
|
|
15394
15411
|
writeOutputIsTTY() {
|
|
15395
15412
|
return process.stdout.isTTY;
|
|
15396
15413
|
},
|
|
15397
|
-
readFile:
|
|
15414
|
+
readFile: readFile6,
|
|
15398
15415
|
writeFile: writeFile22,
|
|
15399
15416
|
watchFile: watchFile2,
|
|
15400
15417
|
watchDirectory,
|
|
@@ -15600,7 +15617,7 @@ ${lanes.join("\n")}
|
|
|
15600
15617
|
callback
|
|
15601
15618
|
);
|
|
15602
15619
|
}
|
|
15603
|
-
function
|
|
15620
|
+
function readFile6(fileName, _encoding) {
|
|
15604
15621
|
let buffer;
|
|
15605
15622
|
try {
|
|
15606
15623
|
buffer = _fs.readFileSync(fileName);
|
|
@@ -52040,7 +52057,7 @@ ${lanes.join("\n")}
|
|
|
52040
52057
|
const possibleOption = getSpellingSuggestion(unknownOption, diagnostics.optionDeclarations, getOptionName);
|
|
52041
52058
|
return possibleOption ? createDiagnosticForNodeInSourceFileOrCompilerDiagnostic(sourceFile, node, diagnostics.unknownDidYouMeanDiagnostic, unknownOptionErrorText || unknownOption, possibleOption.name) : createDiagnosticForNodeInSourceFileOrCompilerDiagnostic(sourceFile, node, diagnostics.unknownOptionDiagnostic, unknownOptionErrorText || unknownOption);
|
|
52042
52059
|
}
|
|
52043
|
-
function parseCommandLineWorker(diagnostics, commandLine,
|
|
52060
|
+
function parseCommandLineWorker(diagnostics, commandLine, readFile6) {
|
|
52044
52061
|
const options = {};
|
|
52045
52062
|
let watchOptions;
|
|
52046
52063
|
const fileNames = [];
|
|
@@ -52088,7 +52105,7 @@ ${lanes.join("\n")}
|
|
|
52088
52105
|
}
|
|
52089
52106
|
}
|
|
52090
52107
|
function parseResponseFile(fileName) {
|
|
52091
|
-
const text = tryReadFile(fileName,
|
|
52108
|
+
const text = tryReadFile(fileName, readFile6 || ((fileName2) => sys.readFile(fileName2)));
|
|
52092
52109
|
if (!isString(text)) {
|
|
52093
52110
|
errors.push(text);
|
|
52094
52111
|
return;
|
|
@@ -52190,8 +52207,8 @@ ${lanes.join("\n")}
|
|
|
52190
52207
|
unknownDidYouMeanDiagnostic: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1,
|
|
52191
52208
|
optionTypeMismatchDiagnostic: Diagnostics.Compiler_option_0_expects_an_argument
|
|
52192
52209
|
};
|
|
52193
|
-
function parseCommandLine(commandLine,
|
|
52194
|
-
return parseCommandLineWorker(compilerOptionsDidYouMeanDiagnostics, commandLine,
|
|
52210
|
+
function parseCommandLine(commandLine, readFile6) {
|
|
52211
|
+
return parseCommandLineWorker(compilerOptionsDidYouMeanDiagnostics, commandLine, readFile6);
|
|
52195
52212
|
}
|
|
52196
52213
|
function getOptionFromName(optionName, allowShort) {
|
|
52197
52214
|
return getOptionDeclarationFromName(getOptionsNameMap, optionName, allowShort);
|
|
@@ -52273,8 +52290,8 @@ ${lanes.join("\n")}
|
|
|
52273
52290
|
watchOptionsToExtend
|
|
52274
52291
|
);
|
|
52275
52292
|
}
|
|
52276
|
-
function readConfigFile(fileName,
|
|
52277
|
-
const textOrDiagnostic = tryReadFile(fileName,
|
|
52293
|
+
function readConfigFile(fileName, readFile6) {
|
|
52294
|
+
const textOrDiagnostic = tryReadFile(fileName, readFile6);
|
|
52278
52295
|
return isString(textOrDiagnostic) ? parseConfigFileTextToJson(fileName, textOrDiagnostic) : { config: {}, error: textOrDiagnostic };
|
|
52279
52296
|
}
|
|
52280
52297
|
function parseConfigFileTextToJson(fileName, jsonText) {
|
|
@@ -52289,14 +52306,14 @@ ${lanes.join("\n")}
|
|
|
52289
52306
|
error: jsonSourceFile.parseDiagnostics.length ? jsonSourceFile.parseDiagnostics[0] : void 0
|
|
52290
52307
|
};
|
|
52291
52308
|
}
|
|
52292
|
-
function readJsonConfigFile(fileName,
|
|
52293
|
-
const textOrDiagnostic = tryReadFile(fileName,
|
|
52309
|
+
function readJsonConfigFile(fileName, readFile6) {
|
|
52310
|
+
const textOrDiagnostic = tryReadFile(fileName, readFile6);
|
|
52294
52311
|
return isString(textOrDiagnostic) ? parseJsonText(fileName, textOrDiagnostic) : { fileName, parseDiagnostics: [textOrDiagnostic] };
|
|
52295
52312
|
}
|
|
52296
|
-
function tryReadFile(fileName,
|
|
52313
|
+
function tryReadFile(fileName, readFile6) {
|
|
52297
52314
|
let text;
|
|
52298
52315
|
try {
|
|
52299
|
-
text =
|
|
52316
|
+
text = readFile6(fileName);
|
|
52300
52317
|
} catch (e) {
|
|
52301
52318
|
return createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e.message);
|
|
52302
52319
|
}
|
|
@@ -141824,12 +141841,12 @@ ${lanes.join("\n")}
|
|
|
141824
141841
|
function createCompilerHost(options, setParentNodes) {
|
|
141825
141842
|
return createCompilerHostWorker(options, setParentNodes);
|
|
141826
141843
|
}
|
|
141827
|
-
function createGetSourceFile(
|
|
141844
|
+
function createGetSourceFile(readFile6, setParentNodes) {
|
|
141828
141845
|
return (fileName, languageVersionOrOptions, onError) => {
|
|
141829
141846
|
let text;
|
|
141830
141847
|
try {
|
|
141831
141848
|
mark("beforeIORead");
|
|
141832
|
-
text =
|
|
141849
|
+
text = readFile6(fileName);
|
|
141833
141850
|
mark("afterIORead");
|
|
141834
141851
|
measure("I/O Read", "beforeIORead", "afterIORead");
|
|
141835
141852
|
} catch (e) {
|
|
@@ -142733,7 +142750,7 @@ ${lanes.join("\n")}
|
|
|
142733
142750
|
getRedirectFromOutput,
|
|
142734
142751
|
forEachResolvedProjectReference: forEachResolvedProjectReference2
|
|
142735
142752
|
});
|
|
142736
|
-
const
|
|
142753
|
+
const readFile6 = host.readFile.bind(host);
|
|
142737
142754
|
(_e = tracing) == null ? void 0 : _e.push(tracing.Phase.Program, "shouldProgramCreateNewSourceFiles", { hasOldProgram: !!oldProgram });
|
|
142738
142755
|
const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options);
|
|
142739
142756
|
(_f = tracing) == null ? void 0 : _f.pop();
|
|
@@ -142959,7 +142976,7 @@ ${lanes.join("\n")}
|
|
|
142959
142976
|
shouldTransformImportCall,
|
|
142960
142977
|
emitBuildInfo,
|
|
142961
142978
|
fileExists: fileExists2,
|
|
142962
|
-
readFile:
|
|
142979
|
+
readFile: readFile6,
|
|
142963
142980
|
directoryExists,
|
|
142964
142981
|
getSymlinkCache,
|
|
142965
142982
|
realpath: (_o = host.realpath) == null ? void 0 : _o.bind(host),
|
|
@@ -217133,6 +217150,34 @@ Additional information: BADCLIENT: Bad error code, ${badCode} not found in range
|
|
|
217133
217150
|
}
|
|
217134
217151
|
});
|
|
217135
217152
|
|
|
217153
|
+
// ../core/dist/parser/tree-sitter/queries.js
|
|
217154
|
+
var init_queries = __esm({
|
|
217155
|
+
"../core/dist/parser/tree-sitter/queries.js"() {
|
|
217156
|
+
"use strict";
|
|
217157
|
+
}
|
|
217158
|
+
});
|
|
217159
|
+
|
|
217160
|
+
// ../core/dist/parser/tree-sitter/parser.js
|
|
217161
|
+
var import_node_module, import_meta, getRequire, _require, ParserModule, Parser;
|
|
217162
|
+
var init_parser = __esm({
|
|
217163
|
+
"../core/dist/parser/tree-sitter/parser.js"() {
|
|
217164
|
+
"use strict";
|
|
217165
|
+
import_node_module = require("node:module");
|
|
217166
|
+
init_file_hasher();
|
|
217167
|
+
init_base_parser();
|
|
217168
|
+
init_queries();
|
|
217169
|
+
import_meta = {};
|
|
217170
|
+
getRequire = () => {
|
|
217171
|
+
if (typeof require !== "undefined")
|
|
217172
|
+
return require;
|
|
217173
|
+
return (0, import_node_module.createRequire)(import_meta.url);
|
|
217174
|
+
};
|
|
217175
|
+
_require = getRequire();
|
|
217176
|
+
ParserModule = _require("web-tree-sitter");
|
|
217177
|
+
Parser = ParserModule.Parser || ParserModule;
|
|
217178
|
+
}
|
|
217179
|
+
});
|
|
217180
|
+
|
|
217136
217181
|
// ../../node_modules/.bun/fast-glob@3.3.3/node_modules/fast-glob/out/utils/array.js
|
|
217137
217182
|
var require_array = __commonJS({
|
|
217138
217183
|
"../../node_modules/.bun/fast-glob@3.3.3/node_modules/fast-glob/out/utils/array.js"(exports2) {
|
|
@@ -236753,11 +236798,24 @@ var fs6 = __toESM(require("node:fs/promises"), 1);
|
|
|
236753
236798
|
var import_node_child_process = require("node:child_process");
|
|
236754
236799
|
var import_node_crypto = require("node:crypto");
|
|
236755
236800
|
|
|
236756
|
-
// ../core/dist/parser/
|
|
236757
|
-
var
|
|
236801
|
+
// ../core/dist/parser/oxc-parser.js
|
|
236802
|
+
var import_oxc_parser = require("oxc-parser");
|
|
236803
|
+
init_base_parser();
|
|
236758
236804
|
|
|
236759
|
-
// ../core/dist/parser/
|
|
236760
|
-
var
|
|
236805
|
+
// ../core/dist/parser/oxc-resolver.js
|
|
236806
|
+
var import_oxc_resolver = require("oxc-resolver");
|
|
236807
|
+
|
|
236808
|
+
// ../core/dist/parser/oxc-parser.js
|
|
236809
|
+
init_file_hasher();
|
|
236810
|
+
|
|
236811
|
+
// ../core/dist/parser/go/go-parser.js
|
|
236812
|
+
init_base_parser();
|
|
236813
|
+
|
|
236814
|
+
// ../core/dist/parser/go/go-extractor.js
|
|
236815
|
+
init_file_hasher();
|
|
236816
|
+
|
|
236817
|
+
// ../core/dist/parser/go/go-parser.js
|
|
236818
|
+
init_file_hasher();
|
|
236761
236819
|
|
|
236762
236820
|
// ../core/dist/utils/errors.js
|
|
236763
236821
|
var MikkError = class extends Error {
|
|
@@ -236779,6 +236837,29 @@ var LockNotFoundError = class extends MikkError {
|
|
|
236779
236837
|
}
|
|
236780
236838
|
};
|
|
236781
236839
|
|
|
236840
|
+
// ../core/dist/parser/index.js
|
|
236841
|
+
init_base_parser();
|
|
236842
|
+
|
|
236843
|
+
// ../core/dist/parser/typescript/ts-parser.js
|
|
236844
|
+
init_base_parser();
|
|
236845
|
+
|
|
236846
|
+
// ../core/dist/parser/typescript/ts-extractor.js
|
|
236847
|
+
var import_typescript = __toESM(require_typescript(), 1);
|
|
236848
|
+
init_file_hasher();
|
|
236849
|
+
|
|
236850
|
+
// ../core/dist/parser/typescript/ts-parser.js
|
|
236851
|
+
init_file_hasher();
|
|
236852
|
+
|
|
236853
|
+
// ../core/dist/parser/javascript/js-parser.js
|
|
236854
|
+
init_base_parser();
|
|
236855
|
+
|
|
236856
|
+
// ../core/dist/parser/javascript/js-extractor.js
|
|
236857
|
+
var import_typescript2 = __toESM(require_typescript(), 1);
|
|
236858
|
+
init_file_hasher();
|
|
236859
|
+
|
|
236860
|
+
// ../core/dist/parser/javascript/js-parser.js
|
|
236861
|
+
init_file_hasher();
|
|
236862
|
+
|
|
236782
236863
|
// ../core/dist/parser/boundary-checker.js
|
|
236783
236864
|
var path = __toESM(require("node:path"), 1);
|
|
236784
236865
|
function stripPrefix(s) {
|
|
@@ -236839,7 +236920,10 @@ var BoundaryChecker = class {
|
|
|
236839
236920
|
for (const file of Object.values(this.lock.files)) {
|
|
236840
236921
|
if (file.moduleId === "unknown" || !file.imports?.length)
|
|
236841
236922
|
continue;
|
|
236842
|
-
for (const
|
|
236923
|
+
for (const imp of file.imports) {
|
|
236924
|
+
const importedPath = imp.resolvedPath;
|
|
236925
|
+
if (!importedPath)
|
|
236926
|
+
continue;
|
|
236843
236927
|
const importedFile = this.lock.files[importedPath];
|
|
236844
236928
|
if (!importedFile || importedFile.moduleId === "unknown" || file.moduleId === importedFile.moduleId)
|
|
236845
236929
|
continue;
|
|
@@ -236911,95 +236995,236 @@ var BoundaryChecker = class {
|
|
|
236911
236995
|
}
|
|
236912
236996
|
};
|
|
236913
236997
|
|
|
236914
|
-
// ../core/dist/parser/
|
|
236915
|
-
|
|
236916
|
-
|
|
236917
|
-
|
|
236918
|
-
|
|
236919
|
-
|
|
236920
|
-
|
|
236998
|
+
// ../core/dist/parser/index.js
|
|
236999
|
+
init_parser();
|
|
237000
|
+
|
|
237001
|
+
// ../core/dist/graph/risk-engine.js
|
|
237002
|
+
var RiskEngine = class {
|
|
237003
|
+
graph;
|
|
237004
|
+
constructor(graph) {
|
|
237005
|
+
this.graph = graph;
|
|
237006
|
+
}
|
|
237007
|
+
/**
|
|
237008
|
+
* Compute the absolute risk score (0-100) for modifying a specific node.
|
|
237009
|
+
* Formula: Base Risk = (Connected Nodes * 1.5) + (Depth * 2) + Modifiers
|
|
237010
|
+
*/
|
|
237011
|
+
scoreNode(nodeId) {
|
|
237012
|
+
const node = this.graph.nodes.get(nodeId);
|
|
237013
|
+
if (!node)
|
|
237014
|
+
return 0;
|
|
237015
|
+
const context = this.analyzeContext(nodeId);
|
|
237016
|
+
const modifiers = this.analyzeModifiers(node);
|
|
237017
|
+
let score = context.connectedNodesCount * 1.5 + context.dependencyDepth * 2;
|
|
237018
|
+
if (modifiers.isAuthOrSecurity)
|
|
237019
|
+
score += 30;
|
|
237020
|
+
if (modifiers.isDatabaseOrState)
|
|
237021
|
+
score += 20;
|
|
237022
|
+
if (modifiers.isPublicAPI)
|
|
237023
|
+
score += 15;
|
|
237024
|
+
return Math.min(Math.max(score, 0), 100);
|
|
237025
|
+
}
|
|
237026
|
+
analyzeContext(nodeId) {
|
|
237027
|
+
const visited = /* @__PURE__ */ new Set();
|
|
237028
|
+
let maxDepth = 0;
|
|
237029
|
+
const queue = [{ id: nodeId, depth: 0 }];
|
|
237030
|
+
let queueHead = 0;
|
|
237031
|
+
visited.add(nodeId);
|
|
237032
|
+
let connectedNodesCount = 0;
|
|
237033
|
+
while (queueHead < queue.length) {
|
|
237034
|
+
const current = queue[queueHead++];
|
|
237035
|
+
maxDepth = Math.max(maxDepth, current.depth);
|
|
237036
|
+
const inEdges = this.graph.inEdges.get(current.id) || [];
|
|
237037
|
+
connectedNodesCount += inEdges.length;
|
|
237038
|
+
for (const edge of inEdges) {
|
|
237039
|
+
if (!visited.has(edge.from)) {
|
|
237040
|
+
visited.add(edge.from);
|
|
237041
|
+
queue.push({ id: edge.from, depth: current.depth + 1 });
|
|
237042
|
+
}
|
|
237043
|
+
}
|
|
237044
|
+
}
|
|
237045
|
+
return {
|
|
237046
|
+
connectedNodesCount,
|
|
237047
|
+
dependencyDepth: maxDepth
|
|
237048
|
+
};
|
|
237049
|
+
}
|
|
237050
|
+
analyzeModifiers(node) {
|
|
237051
|
+
const nameAndFile = `${node.name} ${node.file}`.toLowerCase();
|
|
237052
|
+
const authKeywords = ["auth", "login", "jwt", "verify", "token", "crypt", "hash", "password"];
|
|
237053
|
+
const dbKeywords = ["db", "query", "sql", "insert", "update", "delete", "redis", "cache", "transaction"];
|
|
237054
|
+
return {
|
|
237055
|
+
isAuthOrSecurity: authKeywords.some((kw) => nameAndFile.includes(kw)),
|
|
237056
|
+
isDatabaseOrState: dbKeywords.some((kw) => nameAndFile.includes(kw)),
|
|
237057
|
+
isPublicAPI: !!node.metadata?.isExported
|
|
237058
|
+
};
|
|
237059
|
+
}
|
|
237060
|
+
};
|
|
237061
|
+
|
|
237062
|
+
// ../core/dist/graph/confidence-engine.js
|
|
237063
|
+
var ConfidenceEngine = class {
|
|
237064
|
+
graph;
|
|
237065
|
+
constructor(graph) {
|
|
237066
|
+
this.graph = graph;
|
|
237067
|
+
}
|
|
237068
|
+
/**
|
|
237069
|
+
* Compute confidence decay along a specific path of node IDs.
|
|
237070
|
+
* @param pathIds Array of node IDs forming a path (e.g. ['A', 'B', 'C'])
|
|
237071
|
+
* @returns Cumulative confidence score from 0.0 to 1.0
|
|
237072
|
+
*/
|
|
237073
|
+
calculatePathConfidence(pathIds) {
|
|
237074
|
+
if (pathIds.length < 2)
|
|
237075
|
+
return 1;
|
|
237076
|
+
let totalConfidence = 1;
|
|
237077
|
+
for (let i = 0; i < pathIds.length - 1; i++) {
|
|
237078
|
+
const current = pathIds[i];
|
|
237079
|
+
const next = pathIds[i + 1];
|
|
237080
|
+
const outEdges = this.graph.outEdges.get(current) || [];
|
|
237081
|
+
let maxEdgeConfidence = 0;
|
|
237082
|
+
for (const edge of outEdges) {
|
|
237083
|
+
if (edge.to === next) {
|
|
237084
|
+
if (edge.confidence > maxEdgeConfidence) {
|
|
237085
|
+
maxEdgeConfidence = edge.confidence;
|
|
237086
|
+
}
|
|
237087
|
+
}
|
|
237088
|
+
}
|
|
237089
|
+
if (maxEdgeConfidence === 0) {
|
|
237090
|
+
return 0;
|
|
237091
|
+
}
|
|
237092
|
+
totalConfidence *= maxEdgeConfidence;
|
|
237093
|
+
}
|
|
237094
|
+
return totalConfidence;
|
|
237095
|
+
}
|
|
237096
|
+
/**
|
|
237097
|
+
* Calculates the overall aggregated confidence for a target node
|
|
237098
|
+
* by averaging the confidence of all paths leading to it.
|
|
237099
|
+
*/
|
|
237100
|
+
calculateNodeAggregatedConfidence(paths) {
|
|
237101
|
+
if (paths.length === 0)
|
|
237102
|
+
return 1;
|
|
237103
|
+
const pathConfidences = paths.map((path6) => this.calculatePathConfidence(path6));
|
|
237104
|
+
const sum = pathConfidences.reduce((a, b) => a + b, 0);
|
|
237105
|
+
return Number((sum / paths.length).toFixed(3));
|
|
237106
|
+
}
|
|
236921
237107
|
};
|
|
236922
|
-
var _require = getRequire();
|
|
236923
|
-
var ParserModule = _require("web-tree-sitter");
|
|
236924
|
-
var Parser = ParserModule.Parser || ParserModule;
|
|
236925
237108
|
|
|
236926
237109
|
// ../core/dist/graph/impact-analyzer.js
|
|
236927
237110
|
var ImpactAnalyzer = class {
|
|
236928
237111
|
graph;
|
|
237112
|
+
riskEngine;
|
|
237113
|
+
confidenceEngine;
|
|
236929
237114
|
constructor(graph) {
|
|
236930
237115
|
this.graph = graph;
|
|
237116
|
+
this.riskEngine = new RiskEngine(graph);
|
|
237117
|
+
this.confidenceEngine = new ConfidenceEngine(graph);
|
|
236931
237118
|
}
|
|
236932
237119
|
/** Given a list of changed node IDs, find everything impacted */
|
|
236933
237120
|
analyze(changedNodeIds) {
|
|
236934
|
-
const visited = /* @__PURE__ */ new
|
|
236935
|
-
const
|
|
236936
|
-
|
|
237121
|
+
const visited = /* @__PURE__ */ new Map();
|
|
237122
|
+
const queue = changedNodeIds.map((id) => ({ id, depth: 0, path: [id], pathSet: /* @__PURE__ */ new Set([id]) }));
|
|
237123
|
+
let queueHead = 0;
|
|
236937
237124
|
let maxDepth = 0;
|
|
236938
|
-
const
|
|
236939
|
-
const
|
|
236940
|
-
|
|
236941
|
-
const
|
|
236942
|
-
if (
|
|
236943
|
-
|
|
236944
|
-
|
|
236945
|
-
|
|
236946
|
-
|
|
236947
|
-
|
|
236948
|
-
|
|
236949
|
-
|
|
236950
|
-
depthMap.set(current, depth);
|
|
237125
|
+
const entryPoints = /* @__PURE__ */ new Set();
|
|
237126
|
+
const criticalModules = /* @__PURE__ */ new Set();
|
|
237127
|
+
while (queueHead < queue.length) {
|
|
237128
|
+
const { id: current, depth, path: path6, pathSet } = queue[queueHead++];
|
|
237129
|
+
if (!visited.has(current)) {
|
|
237130
|
+
visited.set(current, { depth, paths: [path6] });
|
|
237131
|
+
} else {
|
|
237132
|
+
visited.get(current).paths.push(path6);
|
|
237133
|
+
if (depth < visited.get(current).depth) {
|
|
237134
|
+
visited.get(current).depth = depth;
|
|
237135
|
+
}
|
|
237136
|
+
}
|
|
236951
237137
|
maxDepth = Math.max(maxDepth, depth);
|
|
237138
|
+
const node = this.graph.nodes.get(current);
|
|
237139
|
+
if (node?.metadata?.isExported) {
|
|
237140
|
+
entryPoints.add(current);
|
|
237141
|
+
}
|
|
236952
237142
|
const dependents = this.graph.inEdges.get(current) || [];
|
|
236953
237143
|
for (const edge of dependents) {
|
|
236954
|
-
if (
|
|
236955
|
-
|
|
237144
|
+
if (edge.type === "contains")
|
|
237145
|
+
continue;
|
|
237146
|
+
if (!pathSet.has(edge.from)) {
|
|
237147
|
+
const newPathSet = new Set(pathSet);
|
|
237148
|
+
newPathSet.add(edge.from);
|
|
237149
|
+
queue.push({
|
|
237150
|
+
id: edge.from,
|
|
237151
|
+
depth: depth + 1,
|
|
237152
|
+
path: [...path6, edge.from],
|
|
237153
|
+
pathSet: newPathSet
|
|
237154
|
+
});
|
|
236956
237155
|
}
|
|
236957
237156
|
}
|
|
236958
237157
|
}
|
|
236959
|
-
const
|
|
237158
|
+
const impactedIds = Array.from(visited.keys()).filter((id) => !changedNodeIds.includes(id));
|
|
237159
|
+
let totalRisk = 0;
|
|
237160
|
+
let totalConfidence = 0;
|
|
236960
237161
|
const classified = {
|
|
236961
237162
|
critical: [],
|
|
236962
237163
|
high: [],
|
|
236963
237164
|
medium: [],
|
|
236964
237165
|
low: []
|
|
236965
237166
|
};
|
|
236966
|
-
for (const id of
|
|
237167
|
+
for (const id of impactedIds) {
|
|
237168
|
+
const context = visited.get(id);
|
|
236967
237169
|
const node = this.graph.nodes.get(id);
|
|
236968
|
-
|
|
236969
|
-
|
|
236970
|
-
const
|
|
236971
|
-
|
|
236972
|
-
|
|
236973
|
-
|
|
237170
|
+
let risk = this.riskEngine.scoreNode(id);
|
|
237171
|
+
const reversedPaths = context.paths.map((p) => [...p].reverse());
|
|
237172
|
+
const confidence = this.confidenceEngine.calculateNodeAggregatedConfidence(reversedPaths);
|
|
237173
|
+
if (context.depth === 1 && node?.moduleId) {
|
|
237174
|
+
const crossesBoundary = changedNodeIds.some((id2) => {
|
|
237175
|
+
const changedNode = this.graph.nodes.get(id2);
|
|
237176
|
+
if (!changedNode?.moduleId || !node.moduleId) {
|
|
237177
|
+
return false;
|
|
237178
|
+
}
|
|
237179
|
+
return changedNode.moduleId !== node.moduleId;
|
|
237180
|
+
});
|
|
237181
|
+
if (crossesBoundary) {
|
|
237182
|
+
risk = Math.max(risk, 80);
|
|
237183
|
+
}
|
|
237184
|
+
}
|
|
237185
|
+
totalRisk += risk;
|
|
237186
|
+
totalConfidence += confidence;
|
|
237187
|
+
const impactEntry = {
|
|
236974
237188
|
nodeId: id,
|
|
236975
|
-
label: node
|
|
236976
|
-
file: node
|
|
236977
|
-
|
|
236978
|
-
risk,
|
|
236979
|
-
depth
|
|
237189
|
+
label: node?.name || "unknown",
|
|
237190
|
+
file: node?.file || "unknown",
|
|
237191
|
+
risk: risk >= 80 ? "CRITICAL" : risk >= 60 ? "HIGH" : risk >= 40 ? "MEDIUM" : "LOW",
|
|
237192
|
+
riskScore: risk,
|
|
237193
|
+
depth: context.depth
|
|
236980
237194
|
};
|
|
236981
|
-
|
|
236982
|
-
|
|
237195
|
+
if (risk >= 80)
|
|
237196
|
+
classified.critical.push(impactEntry);
|
|
237197
|
+
else if (risk >= 60)
|
|
237198
|
+
classified.high.push(impactEntry);
|
|
237199
|
+
else if (risk >= 40)
|
|
237200
|
+
classified.medium.push(impactEntry);
|
|
237201
|
+
else
|
|
237202
|
+
classified.low.push(impactEntry);
|
|
237203
|
+
if (risk > 70 && node?.moduleId) {
|
|
237204
|
+
criticalModules.add(node.moduleId);
|
|
237205
|
+
}
|
|
237206
|
+
}
|
|
237207
|
+
const avgConfidence = impactedIds.length > 0 ? totalConfidence / impactedIds.length : 1;
|
|
237208
|
+
const riskScore = impactedIds.length > 0 ? Math.min(Math.max(totalRisk / impactedIds.length, 0), 100) : 0;
|
|
237209
|
+
const allImpacted = [
|
|
237210
|
+
...classified.critical,
|
|
237211
|
+
...classified.high,
|
|
237212
|
+
...classified.medium,
|
|
237213
|
+
...classified.low
|
|
237214
|
+
];
|
|
236983
237215
|
return {
|
|
236984
237216
|
changed: changedNodeIds,
|
|
236985
|
-
impacted,
|
|
237217
|
+
impacted: impactedIds,
|
|
237218
|
+
allImpacted,
|
|
236986
237219
|
depth: maxDepth,
|
|
236987
|
-
|
|
237220
|
+
entryPoints: Array.from(entryPoints),
|
|
237221
|
+
criticalModules: Array.from(criticalModules),
|
|
237222
|
+
paths: Array.from(visited.values()).flatMap((v) => v.paths),
|
|
237223
|
+
confidence: Number(avgConfidence.toFixed(3)),
|
|
237224
|
+
riskScore: Math.round(riskScore),
|
|
236988
237225
|
classified
|
|
236989
237226
|
};
|
|
236990
237227
|
}
|
|
236991
|
-
/**
|
|
236992
|
-
* How confident are we in this impact analysis?
|
|
236993
|
-
* High = few nodes affected, shallow depth
|
|
236994
|
-
* Low = many nodes affected, deep chains
|
|
236995
|
-
*/
|
|
236996
|
-
computeConfidence(impactedCount, depth) {
|
|
236997
|
-
if (impactedCount < 5 && depth < 3)
|
|
236998
|
-
return "high";
|
|
236999
|
-
if (impactedCount < 20 && depth < 6)
|
|
237000
|
-
return "medium";
|
|
237001
|
-
return "low";
|
|
237002
|
-
}
|
|
237003
237228
|
};
|
|
237004
237229
|
|
|
237005
237230
|
// ../core/dist/graph/dead-code-detector.js
|
|
@@ -237007,7 +237232,6 @@ var ENTRY_POINT_PATTERNS = [
|
|
|
237007
237232
|
/^(main|bootstrap|start|init|setup|configure|register|mount)$/i,
|
|
237008
237233
|
/^(app|server|index|mod|program)$/i,
|
|
237009
237234
|
/Handler$/i,
|
|
237010
|
-
// Express/Koa/Hono handlers
|
|
237011
237235
|
/Middleware$/i,
|
|
237012
237236
|
/Controller$/i,
|
|
237013
237237
|
/^use[A-Z]/,
|
|
@@ -237023,14 +237247,26 @@ var TEST_PATTERNS = [
|
|
|
237023
237247
|
/\.spec\./,
|
|
237024
237248
|
/__test__/
|
|
237025
237249
|
];
|
|
237250
|
+
var DYNAMIC_USAGE_PATTERNS = [
|
|
237251
|
+
/^addEventListener$/i,
|
|
237252
|
+
/^removeEventListener$/i,
|
|
237253
|
+
/^on[A-Z]/,
|
|
237254
|
+
/(invoke|dispatch|emit|call|apply)/i,
|
|
237255
|
+
/^ngOnInit$/i,
|
|
237256
|
+
/^componentDidMount$/i,
|
|
237257
|
+
/^componentWillUnmount$/i
|
|
237258
|
+
];
|
|
237026
237259
|
var DeadCodeDetector = class {
|
|
237027
237260
|
graph;
|
|
237028
237261
|
lock;
|
|
237029
237262
|
routeHandlers;
|
|
237263
|
+
/** Files that have at least one unresolved import (empty resolvedPath) */
|
|
237264
|
+
filesWithUnresolvedImports;
|
|
237030
237265
|
constructor(graph, lock) {
|
|
237031
237266
|
this.graph = graph;
|
|
237032
237267
|
this.lock = lock;
|
|
237033
237268
|
this.routeHandlers = new Set((lock.routes ?? []).map((r) => r.handler).filter(Boolean));
|
|
237269
|
+
this.filesWithUnresolvedImports = this.buildUnresolvedImportFileSet();
|
|
237034
237270
|
}
|
|
237035
237271
|
detect() {
|
|
237036
237272
|
const dead = [];
|
|
@@ -237049,13 +237285,15 @@ var DeadCodeDetector = class {
|
|
|
237049
237285
|
continue;
|
|
237050
237286
|
if (this.isExempt(fn, id))
|
|
237051
237287
|
continue;
|
|
237288
|
+
const confidence = this.inferConfidence(fn);
|
|
237052
237289
|
const entry = {
|
|
237053
237290
|
id,
|
|
237054
237291
|
name: fn.name,
|
|
237055
237292
|
file: fn.file,
|
|
237056
237293
|
moduleId,
|
|
237057
237294
|
type: "function",
|
|
237058
|
-
reason: this.inferReason(fn,
|
|
237295
|
+
reason: this.inferReason(fn),
|
|
237296
|
+
confidence
|
|
237059
237297
|
};
|
|
237060
237298
|
dead.push(entry);
|
|
237061
237299
|
byModule[moduleId].dead++;
|
|
@@ -237069,9 +237307,7 @@ var DeadCodeDetector = class {
|
|
|
237069
237307
|
}
|
|
237070
237308
|
const inEdges = this.graph.inEdges.get(id) || [];
|
|
237071
237309
|
const hasCallers = inEdges.some((e) => e.type === "calls" || e.type === "imports");
|
|
237072
|
-
if (hasCallers)
|
|
237073
|
-
continue;
|
|
237074
|
-
if (cls.isExported)
|
|
237310
|
+
if (hasCallers || cls.isExported)
|
|
237075
237311
|
continue;
|
|
237076
237312
|
const entry = {
|
|
237077
237313
|
id,
|
|
@@ -237079,7 +237315,8 @@ var DeadCodeDetector = class {
|
|
|
237079
237315
|
file: cls.file,
|
|
237080
237316
|
moduleId,
|
|
237081
237317
|
type: "class",
|
|
237082
|
-
reason: "Class has no callers or importers and is not exported"
|
|
237318
|
+
reason: "Class has no callers or importers and is not exported",
|
|
237319
|
+
confidence: this.filesWithUnresolvedImports.has(cls.file) ? "medium" : "high"
|
|
237083
237320
|
};
|
|
237084
237321
|
dead.push(entry);
|
|
237085
237322
|
byModule[moduleId].dead++;
|
|
@@ -237094,7 +237331,7 @@ var DeadCodeDetector = class {
|
|
|
237094
237331
|
byModule
|
|
237095
237332
|
};
|
|
237096
237333
|
}
|
|
237097
|
-
// ───
|
|
237334
|
+
// ─── Private helpers ───────────────────────────────────────────
|
|
237098
237335
|
isExempt(fn, id) {
|
|
237099
237336
|
if (fn.isExported)
|
|
237100
237337
|
return true;
|
|
@@ -237106,24 +237343,85 @@ var DeadCodeDetector = class {
|
|
|
237106
237343
|
return true;
|
|
237107
237344
|
if (fn.name === "constructor" || fn.name === "__init__")
|
|
237108
237345
|
return true;
|
|
237109
|
-
if (this.isCalledByExportedInSameFile(fn
|
|
237346
|
+
if (this.isCalledByExportedInSameFile(fn))
|
|
237110
237347
|
return true;
|
|
237111
237348
|
return false;
|
|
237112
237349
|
}
|
|
237113
|
-
isCalledByExportedInSameFile(fn
|
|
237114
|
-
|
|
237115
|
-
|
|
237116
|
-
|
|
237117
|
-
|
|
237350
|
+
isCalledByExportedInSameFile(fn) {
|
|
237351
|
+
const file = fn.file;
|
|
237352
|
+
const visited = /* @__PURE__ */ new Set();
|
|
237353
|
+
const queue = [fn.id];
|
|
237354
|
+
while (queue.length > 0) {
|
|
237355
|
+
const currentId = queue.pop();
|
|
237356
|
+
if (visited.has(currentId))
|
|
237357
|
+
continue;
|
|
237358
|
+
visited.add(currentId);
|
|
237359
|
+
const current = this.lock.functions[currentId];
|
|
237360
|
+
if (!current)
|
|
237361
|
+
continue;
|
|
237362
|
+
for (const callerId of current.calledBy) {
|
|
237363
|
+
if (visited.has(callerId))
|
|
237364
|
+
continue;
|
|
237365
|
+
const caller = this.lock.functions[callerId];
|
|
237366
|
+
if (!caller)
|
|
237367
|
+
continue;
|
|
237368
|
+
if (caller.file !== file)
|
|
237369
|
+
continue;
|
|
237370
|
+
if (caller.isExported)
|
|
237371
|
+
return true;
|
|
237372
|
+
queue.push(callerId);
|
|
237118
237373
|
}
|
|
237119
237374
|
}
|
|
237120
237375
|
return false;
|
|
237121
237376
|
}
|
|
237122
|
-
|
|
237377
|
+
/**
|
|
237378
|
+
* Assign a confidence level to a dead code finding.
|
|
237379
|
+
*
|
|
237380
|
+
* Priority (first match wins):
|
|
237381
|
+
* medium — lock.calledBy has entries that didn't become graph edges:
|
|
237382
|
+
* something references this function but resolution failed.
|
|
237383
|
+
* medium — file has unresolved imports: the graph may be incomplete.
|
|
237384
|
+
* low — function name matches common dynamic-dispatch patterns.
|
|
237385
|
+
* high — none of the above: safe to remove.
|
|
237386
|
+
*/
|
|
237387
|
+
inferConfidence(fn) {
|
|
237388
|
+
if (DYNAMIC_USAGE_PATTERNS.some((p) => p.test(fn.name)))
|
|
237389
|
+
return "low";
|
|
237390
|
+
if (fn.calledBy.length > 0)
|
|
237391
|
+
return "medium";
|
|
237392
|
+
if (this.filesWithUnresolvedImports.has(fn.file))
|
|
237393
|
+
return "medium";
|
|
237394
|
+
return "high";
|
|
237395
|
+
}
|
|
237396
|
+
inferReason(fn) {
|
|
237123
237397
|
if (fn.calledBy.length === 0) {
|
|
237124
237398
|
return "No callers found anywhere in the codebase";
|
|
237125
237399
|
}
|
|
237126
|
-
return `${fn.calledBy.length}
|
|
237400
|
+
return `${fn.calledBy.length} reference(s) in lock but none resolved to active call edges`;
|
|
237401
|
+
}
|
|
237402
|
+
/**
|
|
237403
|
+
* Build the set of file paths that have at least one import whose
|
|
237404
|
+
* resolvedPath is empty. Used to downgrade confidence for all dead
|
|
237405
|
+
* findings in those files, since the graph may be incomplete.
|
|
237406
|
+
*
|
|
237407
|
+
* We derive this from the lock's file entries. Each file entry stores
|
|
237408
|
+
* its imports; any import with an empty resolvedPath (or no match in
|
|
237409
|
+
* the graph nodes) indicates an unresolved dependency.
|
|
237410
|
+
*/
|
|
237411
|
+
buildUnresolvedImportFileSet() {
|
|
237412
|
+
const result = /* @__PURE__ */ new Set();
|
|
237413
|
+
if (!this.lock.files)
|
|
237414
|
+
return result;
|
|
237415
|
+
for (const [filePath, fileInfo] of Object.entries(this.lock.files)) {
|
|
237416
|
+
const imports = fileInfo.imports ?? [];
|
|
237417
|
+
for (const imp of imports) {
|
|
237418
|
+
if (!imp.resolvedPath || imp.resolvedPath === "") {
|
|
237419
|
+
result.add(filePath);
|
|
237420
|
+
break;
|
|
237421
|
+
}
|
|
237422
|
+
}
|
|
237423
|
+
}
|
|
237424
|
+
return result;
|
|
237127
237425
|
}
|
|
237128
237426
|
};
|
|
237129
237427
|
|
|
@@ -237149,6 +237447,19 @@ var MikkOverwriteSchema = external_exports.object({
|
|
|
237149
237447
|
lastOverwrittenBy: external_exports.string().optional(),
|
|
237150
237448
|
lastOverwrittenAt: external_exports.string().optional()
|
|
237151
237449
|
}).default({ mode: "never", requireConfirmation: true });
|
|
237450
|
+
var MikkPolicySchema = external_exports.object({
|
|
237451
|
+
maxRiskScore: external_exports.number().default(70),
|
|
237452
|
+
maxImpactNodes: external_exports.number().default(10),
|
|
237453
|
+
protectedModules: external_exports.array(external_exports.string()).default(["auth", "security", "billing"]),
|
|
237454
|
+
enforceStrictBoundaries: external_exports.boolean().default(true),
|
|
237455
|
+
requireReasoningForCritical: external_exports.boolean().default(true)
|
|
237456
|
+
}).default({
|
|
237457
|
+
maxRiskScore: 70,
|
|
237458
|
+
maxImpactNodes: 10,
|
|
237459
|
+
protectedModules: ["auth", "security", "billing"],
|
|
237460
|
+
enforceStrictBoundaries: true,
|
|
237461
|
+
requireReasoningForCritical: true
|
|
237462
|
+
});
|
|
237152
237463
|
var MikkContractSchema = external_exports.object({
|
|
237153
237464
|
version: external_exports.string(),
|
|
237154
237465
|
project: external_exports.object({
|
|
@@ -237163,7 +237474,8 @@ var MikkContractSchema = external_exports.object({
|
|
|
237163
237474
|
constraints: external_exports.array(external_exports.string()).default([]),
|
|
237164
237475
|
decisions: external_exports.array(MikkDecisionSchema).default([])
|
|
237165
237476
|
}),
|
|
237166
|
-
overwrite: MikkOverwriteSchema
|
|
237477
|
+
overwrite: MikkOverwriteSchema,
|
|
237478
|
+
policies: MikkPolicySchema
|
|
237167
237479
|
});
|
|
237168
237480
|
var MikkLockFunctionSchema = external_exports.object({
|
|
237169
237481
|
id: external_exports.string(),
|
|
@@ -237189,7 +237501,9 @@ var MikkLockFunctionSchema = external_exports.object({
|
|
|
237189
237501
|
line: external_exports.number(),
|
|
237190
237502
|
type: external_exports.enum(["try-catch", "throw"]),
|
|
237191
237503
|
detail: external_exports.string()
|
|
237192
|
-
})).optional()
|
|
237504
|
+
})).optional(),
|
|
237505
|
+
confidence: external_exports.number().optional(),
|
|
237506
|
+
riskScore: external_exports.number().optional()
|
|
237193
237507
|
});
|
|
237194
237508
|
var MikkLockModuleSchema = external_exports.object({
|
|
237195
237509
|
id: external_exports.string(),
|
|
@@ -237197,12 +237511,17 @@ var MikkLockModuleSchema = external_exports.object({
|
|
|
237197
237511
|
hash: external_exports.string(),
|
|
237198
237512
|
fragmentPath: external_exports.string()
|
|
237199
237513
|
});
|
|
237514
|
+
var MikkLockImportSchema = external_exports.object({
|
|
237515
|
+
source: external_exports.string(),
|
|
237516
|
+
resolvedPath: external_exports.string().optional(),
|
|
237517
|
+
names: external_exports.array(external_exports.string()).optional()
|
|
237518
|
+
});
|
|
237200
237519
|
var MikkLockFileSchema = external_exports.object({
|
|
237201
237520
|
path: external_exports.string(),
|
|
237202
237521
|
hash: external_exports.string(),
|
|
237203
237522
|
moduleId: external_exports.string(),
|
|
237204
237523
|
lastModified: external_exports.string(),
|
|
237205
|
-
imports: external_exports.array(
|
|
237524
|
+
imports: external_exports.array(MikkLockImportSchema).optional()
|
|
237206
237525
|
});
|
|
237207
237526
|
var MikkLockClassSchema = external_exports.object({
|
|
237208
237527
|
id: external_exports.string(),
|
|
@@ -237218,7 +237537,9 @@ var MikkLockClassSchema = external_exports.object({
|
|
|
237218
237537
|
line: external_exports.number(),
|
|
237219
237538
|
type: external_exports.enum(["try-catch", "throw"]),
|
|
237220
237539
|
detail: external_exports.string()
|
|
237221
|
-
})).optional()
|
|
237540
|
+
})).optional(),
|
|
237541
|
+
confidence: external_exports.number().optional(),
|
|
237542
|
+
riskScore: external_exports.number().optional()
|
|
237222
237543
|
});
|
|
237223
237544
|
var MikkLockGenericSchema = external_exports.object({
|
|
237224
237545
|
id: external_exports.string(),
|
|
@@ -237272,22 +237593,49 @@ var MikkLockSchema = external_exports.object({
|
|
|
237272
237593
|
})
|
|
237273
237594
|
});
|
|
237274
237595
|
|
|
237275
|
-
// ../core/dist/contract/
|
|
237596
|
+
// ../core/dist/contract/lock-compiler.js
|
|
237597
|
+
init_file_hasher();
|
|
237598
|
+
|
|
237599
|
+
// ../core/dist/hash/tree-hasher.js
|
|
237600
|
+
init_file_hasher();
|
|
237601
|
+
|
|
237602
|
+
// ../core/dist/utils/json.js
|
|
237276
237603
|
var fs = __toESM(require("node:fs/promises"), 1);
|
|
237604
|
+
async function readJsonSafe(filePath, fileLabel = "JSON file") {
|
|
237605
|
+
let content;
|
|
237606
|
+
try {
|
|
237607
|
+
content = await fs.readFile(filePath, "utf-8");
|
|
237608
|
+
} catch (e) {
|
|
237609
|
+
if (e.code === "ENOENT") {
|
|
237610
|
+
throw e;
|
|
237611
|
+
}
|
|
237612
|
+
throw new Error(`Failed to read ${fileLabel}: ${e.message}`);
|
|
237613
|
+
}
|
|
237614
|
+
const sanitized = content.replace(/^\uFEFF/, "");
|
|
237615
|
+
try {
|
|
237616
|
+
return JSON.parse(sanitized);
|
|
237617
|
+
} catch (e) {
|
|
237618
|
+
throw new Error(`Malformed ${fileLabel}: Syntax error - ${e.message}`);
|
|
237619
|
+
}
|
|
237620
|
+
}
|
|
237621
|
+
|
|
237622
|
+
// ../core/dist/contract/contract-reader.js
|
|
237277
237623
|
var ContractReader = class {
|
|
237278
237624
|
/** Read and validate mikk.json */
|
|
237279
237625
|
async read(contractPath) {
|
|
237280
|
-
let
|
|
237626
|
+
let json;
|
|
237281
237627
|
try {
|
|
237282
|
-
|
|
237283
|
-
} catch {
|
|
237284
|
-
|
|
237628
|
+
json = await readJsonSafe(contractPath, "mikk.json");
|
|
237629
|
+
} catch (e) {
|
|
237630
|
+
if (e.code === "ENOENT") {
|
|
237631
|
+
throw new ContractNotFoundError(contractPath);
|
|
237632
|
+
}
|
|
237633
|
+
throw e;
|
|
237285
237634
|
}
|
|
237286
|
-
const json = JSON.parse(content.replace(/^\uFEFF/, ""));
|
|
237287
237635
|
const result = MikkContractSchema.safeParse(json);
|
|
237288
237636
|
if (!result.success) {
|
|
237289
237637
|
const errors = result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n");
|
|
237290
|
-
throw new Error(`Invalid mikk.json:
|
|
237638
|
+
throw new Error(`Invalid mikk.json structure:
|
|
237291
237639
|
${errors}`);
|
|
237292
237640
|
}
|
|
237293
237641
|
return result.data;
|
|
@@ -237299,18 +237647,20 @@ var fs2 = __toESM(require("node:fs/promises"), 1);
|
|
|
237299
237647
|
var LockReader = class {
|
|
237300
237648
|
/** Read and validate mikk.lock.json */
|
|
237301
237649
|
async read(lockPath) {
|
|
237302
|
-
let
|
|
237650
|
+
let json;
|
|
237303
237651
|
try {
|
|
237304
|
-
|
|
237305
|
-
} catch {
|
|
237306
|
-
|
|
237652
|
+
json = await readJsonSafe(lockPath, "mikk.lock.json");
|
|
237653
|
+
} catch (e) {
|
|
237654
|
+
if (e.code === "ENOENT") {
|
|
237655
|
+
throw new LockNotFoundError();
|
|
237656
|
+
}
|
|
237657
|
+
throw e;
|
|
237307
237658
|
}
|
|
237308
|
-
const json = JSON.parse(content.replace(/^\uFEFF/, ""));
|
|
237309
237659
|
const hydrated = hydrateLock(json);
|
|
237310
237660
|
const result = MikkLockSchema.safeParse(hydrated);
|
|
237311
237661
|
if (!result.success) {
|
|
237312
237662
|
const errors = result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n");
|
|
237313
|
-
throw new Error(`Invalid mikk.lock.json:
|
|
237663
|
+
throw new Error(`Invalid mikk.lock.json structure:
|
|
237314
237664
|
${errors}`);
|
|
237315
237665
|
}
|
|
237316
237666
|
return result.data;
|
|
@@ -237410,8 +237760,9 @@ function compactifyLock(lock) {
|
|
|
237410
237760
|
};
|
|
237411
237761
|
if (file.moduleId && file.moduleId !== "unknown")
|
|
237412
237762
|
c.moduleId = file.moduleId;
|
|
237413
|
-
if (file.imports && file.imports.length > 0)
|
|
237414
|
-
c.imports = file.imports;
|
|
237763
|
+
if (file.imports && file.imports.length > 0) {
|
|
237764
|
+
c.imports = file.imports.map(normalizeImportEntry);
|
|
237765
|
+
}
|
|
237415
237766
|
out.files[key] = c;
|
|
237416
237767
|
}
|
|
237417
237768
|
if (lock.contextFiles && lock.contextFiles.length > 0) {
|
|
@@ -237441,7 +237792,10 @@ function hydrateLock(raw) {
|
|
|
237441
237792
|
const hasFnIndex = fnIndex.length > 0;
|
|
237442
237793
|
const fileModuleMap = {};
|
|
237443
237794
|
for (const [key, c] of Object.entries(raw.files || {})) {
|
|
237444
|
-
|
|
237795
|
+
const moduleId = c.moduleId || "unknown";
|
|
237796
|
+
const normalizedKey = normalizeFilePath(key);
|
|
237797
|
+
fileModuleMap[key] = moduleId;
|
|
237798
|
+
fileModuleMap[normalizedKey] = moduleId;
|
|
237445
237799
|
}
|
|
237446
237800
|
out.functions = {};
|
|
237447
237801
|
for (const [key, c] of Object.entries(raw.functions || {})) {
|
|
@@ -237450,6 +237804,7 @@ function hydrateLock(raw) {
|
|
|
237450
237804
|
const lines = c.lines || [c.startLine || 0, c.endLine || 0];
|
|
237451
237805
|
const calls = (c.calls || []).map((v) => typeof v === "number" ? fnIndex[v] ?? null : v).filter(Boolean);
|
|
237452
237806
|
const calledBy = (c.calledBy || []).map((v) => typeof v === "number" ? fnIndex[v] ?? null : v).filter(Boolean);
|
|
237807
|
+
const normalizedFile = normalizeFilePath(file);
|
|
237453
237808
|
out.functions[fullId] = {
|
|
237454
237809
|
id: fullId,
|
|
237455
237810
|
name,
|
|
@@ -237460,7 +237815,7 @@ function hydrateLock(raw) {
|
|
|
237460
237815
|
// P4: empty string when not stored
|
|
237461
237816
|
calls,
|
|
237462
237817
|
calledBy,
|
|
237463
|
-
moduleId: fileModuleMap[file] || c.moduleId || "unknown",
|
|
237818
|
+
moduleId: fileModuleMap[normalizedFile] || fileModuleMap[file] || c.moduleId || "unknown",
|
|
237464
237819
|
// P6: derive from file
|
|
237465
237820
|
...c.params ? { params: c.params } : {},
|
|
237466
237821
|
...c.returnType ? { returnType: c.returnType } : {},
|
|
@@ -237530,7 +237885,7 @@ function hydrateLock(raw) {
|
|
|
237530
237885
|
hash: c.hash || "",
|
|
237531
237886
|
moduleId: c.moduleId || "unknown",
|
|
237532
237887
|
lastModified: c.lastModified || "",
|
|
237533
|
-
...c.imports && c.imports.length > 0 ? { imports: c.imports } : {}
|
|
237888
|
+
...c.imports && c.imports.length > 0 ? { imports: c.imports.map(normalizeImportEntry) } : {}
|
|
237534
237889
|
};
|
|
237535
237890
|
}
|
|
237536
237891
|
out.modules = raw.modules;
|
|
@@ -237565,6 +237920,20 @@ function parseEntityKeyFull(key) {
|
|
|
237565
237920
|
name: rest.slice(lastColon + 1)
|
|
237566
237921
|
};
|
|
237567
237922
|
}
|
|
237923
|
+
function normalizeImportEntry(entry) {
|
|
237924
|
+
if (!entry)
|
|
237925
|
+
return { source: "" };
|
|
237926
|
+
if (typeof entry === "string")
|
|
237927
|
+
return { source: entry };
|
|
237928
|
+
return {
|
|
237929
|
+
source: entry.source,
|
|
237930
|
+
resolvedPath: entry.resolvedPath || void 0,
|
|
237931
|
+
names: entry.names?.length ? entry.names : void 0
|
|
237932
|
+
};
|
|
237933
|
+
}
|
|
237934
|
+
function normalizeFilePath(p) {
|
|
237935
|
+
return (p || "").replace(/\\/g, "/").toLowerCase();
|
|
237936
|
+
}
|
|
237568
237937
|
|
|
237569
237938
|
// ../core/dist/contract/adr-manager.js
|
|
237570
237939
|
var fs3 = __toESM(require("node:fs/promises"), 1);
|
|
@@ -237626,6 +237995,9 @@ var AdrManager = class {
|
|
|
237626
237995
|
}
|
|
237627
237996
|
};
|
|
237628
237997
|
|
|
237998
|
+
// ../core/dist/hash/index.js
|
|
237999
|
+
init_file_hasher();
|
|
238000
|
+
|
|
237629
238001
|
// ../core/dist/hash/hash-store.js
|
|
237630
238002
|
var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
|
|
237631
238003
|
|
|
@@ -237637,11 +238009,14 @@ var BM25Index = class {
|
|
|
237637
238009
|
documentFrequency = /* @__PURE__ */ new Map();
|
|
237638
238010
|
// term → how many docs contain it
|
|
237639
238011
|
avgDocLength = 0;
|
|
238012
|
+
totalDocLength = 0;
|
|
238013
|
+
// running total — avoids O(n²) recompute on every addDocument
|
|
237640
238014
|
/** Clear the index */
|
|
237641
238015
|
clear() {
|
|
237642
238016
|
this.documents = [];
|
|
237643
238017
|
this.documentFrequency.clear();
|
|
237644
238018
|
this.avgDocLength = 0;
|
|
238019
|
+
this.totalDocLength = 0;
|
|
237645
238020
|
}
|
|
237646
238021
|
/** Add a document with pre-tokenized terms */
|
|
237647
238022
|
addDocument(id, tokens) {
|
|
@@ -237651,7 +238026,8 @@ var BM25Index = class {
|
|
|
237651
238026
|
for (const term of uniqueTerms) {
|
|
237652
238027
|
this.documentFrequency.set(term, (this.documentFrequency.get(term) ?? 0) + 1);
|
|
237653
238028
|
}
|
|
237654
|
-
this.
|
|
238029
|
+
this.totalDocLength += normalizedTokens.length;
|
|
238030
|
+
this.avgDocLength = this.totalDocLength / this.documents.length;
|
|
237655
238031
|
}
|
|
237656
238032
|
/** Search the index and return ranked results */
|
|
237657
238033
|
search(query, limit = 20) {
|
|
@@ -237852,25 +238228,90 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
|
237852
238228
|
"my",
|
|
237853
238229
|
"your"
|
|
237854
238230
|
]);
|
|
237855
|
-
|
|
237856
|
-
|
|
238231
|
+
var SHORT_TECH_WORDS = /* @__PURE__ */ new Set([
|
|
238232
|
+
"ai",
|
|
238233
|
+
"ml",
|
|
238234
|
+
"ui",
|
|
238235
|
+
"ux",
|
|
238236
|
+
"ts",
|
|
238237
|
+
"js",
|
|
238238
|
+
"db",
|
|
238239
|
+
"io",
|
|
238240
|
+
"id",
|
|
238241
|
+
"ip",
|
|
238242
|
+
"ci",
|
|
238243
|
+
"cd",
|
|
238244
|
+
"qa",
|
|
238245
|
+
"api",
|
|
238246
|
+
"mcp",
|
|
238247
|
+
"jwt",
|
|
238248
|
+
"sql"
|
|
238249
|
+
]);
|
|
238250
|
+
function normalizeKeyword(value) {
|
|
238251
|
+
return value.toLowerCase().trim().replace(/[^a-z0-9_-]/g, "");
|
|
238252
|
+
}
|
|
238253
|
+
function extractKeywords2(task, requiredKeywords = []) {
|
|
238254
|
+
const out = [];
|
|
238255
|
+
const seen = /* @__PURE__ */ new Set();
|
|
238256
|
+
for (const match of task.matchAll(/"([^"]+)"|'([^']+)'/g)) {
|
|
238257
|
+
const phrase = (match[1] ?? match[2] ?? "").toLowerCase().trim();
|
|
238258
|
+
if (!phrase || seen.has(phrase))
|
|
238259
|
+
continue;
|
|
238260
|
+
seen.add(phrase);
|
|
238261
|
+
out.push(phrase);
|
|
238262
|
+
}
|
|
238263
|
+
const words = task.toLowerCase().replace(/[^a-z0-9\s_-]/g, " ").split(/\s+/).map(normalizeKeyword).filter((w) => {
|
|
238264
|
+
if (!w || STOP_WORDS.has(w))
|
|
238265
|
+
return false;
|
|
238266
|
+
if (w.length > 2)
|
|
238267
|
+
return true;
|
|
238268
|
+
return SHORT_TECH_WORDS.has(w);
|
|
238269
|
+
});
|
|
238270
|
+
for (const w of words) {
|
|
238271
|
+
if (seen.has(w))
|
|
238272
|
+
continue;
|
|
238273
|
+
seen.add(w);
|
|
238274
|
+
out.push(w);
|
|
238275
|
+
}
|
|
238276
|
+
const expandedRequired = requiredKeywords.flatMap((item) => item.split(/[,\s]+/)).map(normalizeKeyword).filter(Boolean);
|
|
238277
|
+
for (const kw of expandedRequired) {
|
|
238278
|
+
if (seen.has(kw))
|
|
238279
|
+
continue;
|
|
238280
|
+
seen.add(kw);
|
|
238281
|
+
out.push(kw);
|
|
238282
|
+
}
|
|
238283
|
+
return out;
|
|
237857
238284
|
}
|
|
237858
238285
|
function keywordScore(fn, keywords) {
|
|
237859
238286
|
if (keywords.length === 0)
|
|
237860
|
-
return 0;
|
|
238287
|
+
return { score: 0, matchedKeywords: [] };
|
|
237861
238288
|
const nameLower = fn.name.toLowerCase();
|
|
237862
238289
|
const fileLower = fn.file.toLowerCase();
|
|
238290
|
+
const fileNoExt = fileLower.replace(/\.(d\.ts|ts|tsx|js|jsx|mjs|cjs|mts|cts)\b/g, " ");
|
|
238291
|
+
const purposeLower = (fn.purpose ?? "").toLowerCase();
|
|
238292
|
+
const tokenSet = /* @__PURE__ */ new Set([
|
|
238293
|
+
...nameLower.match(/[a-z0-9]+/g) ?? [],
|
|
238294
|
+
...fileNoExt.match(/[a-z0-9]+/g) ?? [],
|
|
238295
|
+
...purposeLower.match(/[a-z0-9]+/g) ?? []
|
|
238296
|
+
]);
|
|
237863
238297
|
let score = 0;
|
|
238298
|
+
const matched = [];
|
|
237864
238299
|
for (const kw of keywords) {
|
|
237865
|
-
|
|
238300
|
+
const shortKw = kw.length <= 2;
|
|
238301
|
+
const exactName = nameLower === kw;
|
|
238302
|
+
const partial2 = shortKw ? tokenSet.has(kw) : nameLower.includes(kw) || fileLower.includes(kw) || purposeLower.includes(kw);
|
|
238303
|
+
if (exactName) {
|
|
237866
238304
|
score = Math.max(score, WEIGHT.KEYWORD_EXACT);
|
|
237867
|
-
|
|
238305
|
+
matched.push(kw);
|
|
238306
|
+
} else if (partial2) {
|
|
237868
238307
|
score = Math.max(score, WEIGHT.KEYWORD_PARTIAL);
|
|
238308
|
+
matched.push(kw);
|
|
237869
238309
|
}
|
|
237870
238310
|
}
|
|
237871
|
-
return score;
|
|
238311
|
+
return { score, matchedKeywords: matched };
|
|
237872
238312
|
}
|
|
237873
|
-
function resolveSeeds(query, contract, lock) {
|
|
238313
|
+
function resolveSeeds(query, contract, lock, keywords) {
|
|
238314
|
+
const strictMode = query.relevanceMode === "strict";
|
|
237874
238315
|
const seeds = /* @__PURE__ */ new Set();
|
|
237875
238316
|
if (query.focusFiles && query.focusFiles.length > 0) {
|
|
237876
238317
|
for (const filePath of query.focusFiles) {
|
|
@@ -237890,14 +238331,13 @@ function resolveSeeds(query, contract, lock) {
|
|
|
237890
238331
|
}
|
|
237891
238332
|
}
|
|
237892
238333
|
if (seeds.size === 0) {
|
|
237893
|
-
const keywords = extractKeywords2(query.task);
|
|
237894
238334
|
for (const fn of Object.values(lock.functions)) {
|
|
237895
|
-
if (keywordScore(fn, keywords) >= WEIGHT.KEYWORD_PARTIAL) {
|
|
238335
|
+
if (keywordScore(fn, keywords).score >= WEIGHT.KEYWORD_PARTIAL) {
|
|
237896
238336
|
seeds.add(fn.id);
|
|
237897
238337
|
}
|
|
237898
238338
|
}
|
|
237899
238339
|
}
|
|
237900
|
-
if (seeds.size === 0) {
|
|
238340
|
+
if (!strictMode && seeds.size === 0) {
|
|
237901
238341
|
const taskLower = query.task.toLowerCase();
|
|
237902
238342
|
for (const mod of contract.declared.modules) {
|
|
237903
238343
|
if (taskLower.includes(mod.id.toLowerCase()) || taskLower.includes(mod.name.toLowerCase())) {
|
|
@@ -237929,25 +238369,72 @@ var ContextBuilder = class {
|
|
|
237929
238369
|
* 6. Group survivors by module, emit structured context
|
|
237930
238370
|
*/
|
|
237931
238371
|
build(query) {
|
|
238372
|
+
const relevanceMode = query.relevanceMode ?? "balanced";
|
|
238373
|
+
const strictMode = relevanceMode === "strict";
|
|
237932
238374
|
const tokenBudget = query.tokenBudget ?? DEFAULT_TOKEN_BUDGET;
|
|
237933
238375
|
const maxHops = query.maxHops ?? 4;
|
|
237934
|
-
const
|
|
238376
|
+
const requiredKeywords = query.requiredKeywords ?? [];
|
|
238377
|
+
const keywords = extractKeywords2(query.task, requiredKeywords);
|
|
238378
|
+
const requiredKeywordSet = new Set(requiredKeywords.flatMap((item) => item.split(/[,\s]+/)).map(normalizeKeyword).filter(Boolean));
|
|
238379
|
+
const seeds = resolveSeeds(query, this.contract, this.lock, keywords);
|
|
238380
|
+
const seedSet = new Set(seeds);
|
|
237935
238381
|
const proximityMap = seeds.length > 0 ? bfsNeighbors(seeds, this.lock.functions, maxHops) : /* @__PURE__ */ new Map();
|
|
237936
|
-
const keywords = extractKeywords2(query.task);
|
|
237937
238382
|
const allFunctions = Object.values(this.lock.functions);
|
|
238383
|
+
const focusFiles = query.focusFiles ?? [];
|
|
238384
|
+
const focusModules = new Set(query.focusModules ?? []);
|
|
238385
|
+
const requireAllKeywords = query.requireAllKeywords ?? false;
|
|
238386
|
+
const minKeywordMatches = query.minKeywordMatches ?? 1;
|
|
238387
|
+
const strictPassIds = /* @__PURE__ */ new Set();
|
|
238388
|
+
const reasons = [];
|
|
238389
|
+
const suggestions = [];
|
|
238390
|
+
const nearMissSuggestions = [];
|
|
237938
238391
|
const scored = allFunctions.map((fn) => {
|
|
237939
238392
|
let score = 0;
|
|
237940
238393
|
const depth = proximityMap.get(fn.id);
|
|
237941
238394
|
if (depth !== void 0) {
|
|
237942
238395
|
score += depthToScore(depth);
|
|
237943
238396
|
}
|
|
237944
|
-
|
|
237945
|
-
|
|
238397
|
+
const kwInfo = keywordScore(fn, keywords);
|
|
238398
|
+
score += kwInfo.score;
|
|
238399
|
+
const matchedSet = new Set(kwInfo.matchedKeywords);
|
|
238400
|
+
const inFocusFile = focusFiles.some((filePath) => fn.file.includes(filePath) || filePath.includes(fn.file));
|
|
238401
|
+
const inFocusModule = focusModules.has(fn.moduleId);
|
|
238402
|
+
const inFocus = inFocusFile || inFocusModule;
|
|
238403
|
+
const requiredPass = requiredKeywordSet.size === 0 ? true : [...requiredKeywordSet].every((kw) => matchedSet.has(kw));
|
|
238404
|
+
const generalPass = requireAllKeywords ? keywords.length > 0 && matchedSet.size >= keywords.length : keywords.length === 0 ? false : matchedSet.size >= minKeywordMatches;
|
|
238405
|
+
const keywordPass = requiredPass && generalPass;
|
|
238406
|
+
if (keywordPass)
|
|
238407
|
+
strictPassIds.add(fn.id);
|
|
238408
|
+
if (strictMode) {
|
|
238409
|
+
const isSeed = seedSet.has(fn.id);
|
|
238410
|
+
const seedFromFocus = isSeed && (inFocus || focusFiles.length > 0 || focusModules.size > 0);
|
|
238411
|
+
if (!(inFocus || keywordPass || seedFromFocus)) {
|
|
238412
|
+
if (kwInfo.score > 0) {
|
|
238413
|
+
nearMissSuggestions.push(`${fn.name} (${fn.file}:${fn.startLine})`);
|
|
238414
|
+
}
|
|
238415
|
+
return { fn, score: -1 };
|
|
238416
|
+
}
|
|
238417
|
+
}
|
|
238418
|
+
if (!strictMode && fn.calledBy.length === 0)
|
|
237946
238419
|
score += WEIGHT.ENTRY_POINT;
|
|
237947
238420
|
return { fn, score };
|
|
237948
238421
|
});
|
|
237949
238422
|
scored.sort((a, b) => b.score - a.score);
|
|
237950
|
-
const
|
|
238423
|
+
for (const { fn, score } of scored) {
|
|
238424
|
+
if (score <= 0)
|
|
238425
|
+
continue;
|
|
238426
|
+
suggestions.push(`${fn.name} (${fn.file}:${fn.startLine})`);
|
|
238427
|
+
if (suggestions.length >= 5)
|
|
238428
|
+
break;
|
|
238429
|
+
}
|
|
238430
|
+
for (const s of nearMissSuggestions) {
|
|
238431
|
+
if (suggestions.includes(s))
|
|
238432
|
+
continue;
|
|
238433
|
+
suggestions.push(s);
|
|
238434
|
+
if (suggestions.length >= 5)
|
|
238435
|
+
break;
|
|
238436
|
+
}
|
|
238437
|
+
let selected = [];
|
|
237951
238438
|
let usedTokens = 0;
|
|
237952
238439
|
for (const { fn, score } of scored) {
|
|
237953
238440
|
if (score <= 0 && seeds.length > 0)
|
|
@@ -237961,6 +238448,51 @@ var ContextBuilder = class {
|
|
|
237961
238448
|
selected.push(fn);
|
|
237962
238449
|
usedTokens += tokens;
|
|
237963
238450
|
}
|
|
238451
|
+
if (strictMode) {
|
|
238452
|
+
if (requiredKeywordSet.size > 0) {
|
|
238453
|
+
reasons.push(`required terms: ${[...requiredKeywordSet].join(", ")}`);
|
|
238454
|
+
}
|
|
238455
|
+
if (strictPassIds.size === 0) {
|
|
238456
|
+
reasons.push("no functions matched strict keyword filters");
|
|
238457
|
+
}
|
|
238458
|
+
}
|
|
238459
|
+
if (strictMode && query.exactOnly) {
|
|
238460
|
+
selected = selected.filter((fn) => strictPassIds.has(fn.id));
|
|
238461
|
+
usedTokens = selected.reduce((sum, fn) => sum + estimateTokens(this.buildFunctionSnippet(fn, query)), 0);
|
|
238462
|
+
if (selected.length === 0 && strictPassIds.size > 0) {
|
|
238463
|
+
reasons.push("exact matches exist but did not fit token budget or max function limit");
|
|
238464
|
+
}
|
|
238465
|
+
}
|
|
238466
|
+
if (strictMode && query.failFast && selected.length === 0) {
|
|
238467
|
+
reasons.push("fail-fast enabled: returning no context when exact match set is empty");
|
|
238468
|
+
return {
|
|
238469
|
+
project: {
|
|
238470
|
+
name: this.contract.project.name,
|
|
238471
|
+
language: this.contract.project.language,
|
|
238472
|
+
description: this.contract.project.description,
|
|
238473
|
+
moduleCount: this.contract.declared.modules.length,
|
|
238474
|
+
functionCount: Object.keys(this.lock.functions).length
|
|
238475
|
+
},
|
|
238476
|
+
modules: [],
|
|
238477
|
+
constraints: this.contract.declared.constraints,
|
|
238478
|
+
decisions: this.contract.declared.decisions.map((d) => ({
|
|
238479
|
+
title: d.title,
|
|
238480
|
+
reason: d.reason
|
|
238481
|
+
})),
|
|
238482
|
+
contextFiles: [],
|
|
238483
|
+
routes: [],
|
|
238484
|
+
prompt: "",
|
|
238485
|
+
meta: {
|
|
238486
|
+
seedCount: seeds.length,
|
|
238487
|
+
totalFunctionsConsidered: allFunctions.length,
|
|
238488
|
+
selectedFunctions: 0,
|
|
238489
|
+
estimatedTokens: 0,
|
|
238490
|
+
keywords,
|
|
238491
|
+
reasons,
|
|
238492
|
+
suggestions: suggestions.length > 0 ? suggestions : void 0
|
|
238493
|
+
}
|
|
238494
|
+
};
|
|
238495
|
+
}
|
|
237964
238496
|
const byModule = /* @__PURE__ */ new Map();
|
|
237965
238497
|
for (const fn of selected) {
|
|
237966
238498
|
if (!byModule.has(fn.moduleId))
|
|
@@ -237981,6 +238513,8 @@ var ContextBuilder = class {
|
|
|
237981
238513
|
});
|
|
237982
238514
|
}
|
|
237983
238515
|
contextModules.sort((a, b) => b.functions.length - a.functions.length);
|
|
238516
|
+
const contextFiles = strictMode ? [] : this.lock.contextFiles;
|
|
238517
|
+
const routes = strictMode ? [] : this.lock.routes;
|
|
237984
238518
|
return {
|
|
237985
238519
|
project: {
|
|
237986
238520
|
name: this.contract.project.name,
|
|
@@ -237995,12 +238529,12 @@ var ContextBuilder = class {
|
|
|
237995
238529
|
title: d.title,
|
|
237996
238530
|
reason: d.reason
|
|
237997
238531
|
})),
|
|
237998
|
-
contextFiles:
|
|
238532
|
+
contextFiles: contextFiles?.map((cf) => ({
|
|
237999
238533
|
path: cf.path,
|
|
238000
238534
|
content: readContextFile(cf.path, query.projectRoot),
|
|
238001
238535
|
type: cf.type
|
|
238002
238536
|
})),
|
|
238003
|
-
routes:
|
|
238537
|
+
routes: routes?.map((r) => ({
|
|
238004
238538
|
method: r.method,
|
|
238005
238539
|
path: r.path,
|
|
238006
238540
|
handler: r.handler,
|
|
@@ -238014,7 +238548,9 @@ var ContextBuilder = class {
|
|
|
238014
238548
|
totalFunctionsConsidered: allFunctions.length,
|
|
238015
238549
|
selectedFunctions: selected.length,
|
|
238016
238550
|
estimatedTokens: usedTokens,
|
|
238017
|
-
keywords
|
|
238551
|
+
keywords,
|
|
238552
|
+
reasons: reasons.length > 0 ? reasons : void 0,
|
|
238553
|
+
suggestions: selected.length === 0 && suggestions.length > 0 ? suggestions : void 0
|
|
238018
238554
|
}
|
|
238019
238555
|
};
|
|
238020
238556
|
}
|
|
@@ -238086,6 +238622,7 @@ var ContextBuilder = class {
|
|
|
238086
238622
|
/** Generate the natural-language prompt section */
|
|
238087
238623
|
generatePrompt(query, modules) {
|
|
238088
238624
|
const lines = [];
|
|
238625
|
+
const strictMode = query.relevanceMode === "strict";
|
|
238089
238626
|
lines.push("=== ARCHITECTURAL CONTEXT ===");
|
|
238090
238627
|
lines.push(`Project: ${this.contract.project.name} (${this.contract.project.language})`);
|
|
238091
238628
|
if (this.contract.project.description) {
|
|
@@ -238094,7 +238631,7 @@ var ContextBuilder = class {
|
|
|
238094
238631
|
lines.push(`Task: ${query.task}`);
|
|
238095
238632
|
lines.push("");
|
|
238096
238633
|
const routes = this.lock.routes;
|
|
238097
|
-
if (routes && routes.length > 0) {
|
|
238634
|
+
if (!strictMode && routes && routes.length > 0) {
|
|
238098
238635
|
lines.push("=== HTTP ROUTES ===");
|
|
238099
238636
|
for (const r of routes) {
|
|
238100
238637
|
const mw = r.middlewares.length > 0 ? ` [${r.middlewares.join(", ")}]` : "";
|
|
@@ -238103,7 +238640,7 @@ var ContextBuilder = class {
|
|
|
238103
238640
|
lines.push("");
|
|
238104
238641
|
}
|
|
238105
238642
|
const ctxFiles = this.lock.contextFiles;
|
|
238106
|
-
if (ctxFiles && ctxFiles.length > 0) {
|
|
238643
|
+
if (!strictMode && ctxFiles && ctxFiles.length > 0) {
|
|
238107
238644
|
lines.push("=== DATA MODELS & SCHEMAS ===");
|
|
238108
238645
|
for (const cf of ctxFiles) {
|
|
238109
238646
|
lines.push(`--- ${cf.path} (${cf.type}) ---`);
|
|
@@ -238370,6 +238907,16 @@ var ClaudeProvider = class {
|
|
|
238370
238907
|
lines.push(` <seeds_found>${context.meta?.seedCount ?? 0}</seeds_found>`);
|
|
238371
238908
|
lines.push(` <functions_selected>${context.meta?.selectedFunctions ?? 0} of ${context.meta?.totalFunctionsConsidered ?? 0}</functions_selected>`);
|
|
238372
238909
|
lines.push(` <estimated_tokens>${context.meta?.estimatedTokens ?? 0}</estimated_tokens>`);
|
|
238910
|
+
if (context.meta?.reasons && context.meta.reasons.length > 0) {
|
|
238911
|
+
for (const reason of context.meta.reasons) {
|
|
238912
|
+
lines.push(` <reason>${esc2(reason)}</reason>`);
|
|
238913
|
+
}
|
|
238914
|
+
}
|
|
238915
|
+
if (context.meta?.suggestions && context.meta.suggestions.length > 0) {
|
|
238916
|
+
for (const s of context.meta.suggestions) {
|
|
238917
|
+
lines.push(` <suggestion>${esc2(s)}</suggestion>`);
|
|
238918
|
+
}
|
|
238919
|
+
}
|
|
238373
238920
|
lines.push("</context_meta>");
|
|
238374
238921
|
lines.push("");
|
|
238375
238922
|
for (const mod of context.modules) {
|
|
@@ -238412,6 +238959,22 @@ var ClaudeProvider = class {
|
|
|
238412
238959
|
lines.push("</module>");
|
|
238413
238960
|
lines.push("");
|
|
238414
238961
|
}
|
|
238962
|
+
if (context.modules.length === 0 && context.meta?.reasons?.length) {
|
|
238963
|
+
lines.push("<no_match_reason>");
|
|
238964
|
+
for (const reason of context.meta.reasons) {
|
|
238965
|
+
lines.push(` <item>${esc2(reason)}</item>`);
|
|
238966
|
+
}
|
|
238967
|
+
lines.push("</no_match_reason>");
|
|
238968
|
+
lines.push("");
|
|
238969
|
+
if (context.meta.suggestions && context.meta.suggestions.length > 0) {
|
|
238970
|
+
lines.push("<did_you_mean>");
|
|
238971
|
+
for (const suggestion of context.meta.suggestions) {
|
|
238972
|
+
lines.push(` <item>${esc2(suggestion)}</item>`);
|
|
238973
|
+
}
|
|
238974
|
+
lines.push("</did_you_mean>");
|
|
238975
|
+
lines.push("");
|
|
238976
|
+
}
|
|
238977
|
+
}
|
|
238415
238978
|
if (context.contextFiles && context.contextFiles.length > 0) {
|
|
238416
238979
|
lines.push("<context_files>");
|
|
238417
238980
|
for (const cf of context.contextFiles) {
|
|
@@ -238472,6 +239035,20 @@ var CompactProvider = class {
|
|
|
238472
239035
|
`Task keywords: ${context.meta?.keywords?.join(", ") ?? ""}`,
|
|
238473
239036
|
""
|
|
238474
239037
|
];
|
|
239038
|
+
if (context.modules.length === 0 && context.meta?.reasons?.length) {
|
|
239039
|
+
lines.push("No exact context selected:");
|
|
239040
|
+
for (const reason of context.meta.reasons) {
|
|
239041
|
+
lines.push(`- ${reason}`);
|
|
239042
|
+
}
|
|
239043
|
+
if (context.meta.suggestions && context.meta.suggestions.length > 0) {
|
|
239044
|
+
lines.push("");
|
|
239045
|
+
lines.push("Did you mean:");
|
|
239046
|
+
for (const suggestion of context.meta.suggestions) {
|
|
239047
|
+
lines.push(`- ${suggestion}`);
|
|
239048
|
+
}
|
|
239049
|
+
}
|
|
239050
|
+
lines.push("");
|
|
239051
|
+
}
|
|
238475
239052
|
for (const mod of context.modules) {
|
|
238476
239053
|
lines.push(`## ${mod.name}`);
|
|
238477
239054
|
for (const fn of mod.functions) {
|
|
@@ -238659,6 +239236,22 @@ var IntentSchema = external_exports.object({
|
|
|
238659
239236
|
// src/tools.ts
|
|
238660
239237
|
var projectCache = /* @__PURE__ */ new Map();
|
|
238661
239238
|
var CACHE_TTL_MS = 3e4;
|
|
239239
|
+
function invalidateCache(projectRoot) {
|
|
239240
|
+
projectCache.delete(projectRoot);
|
|
239241
|
+
}
|
|
239242
|
+
var MAX_SEARCHER_ROOTS = 5;
|
|
239243
|
+
function getSemanticSearcher(projectRoot) {
|
|
239244
|
+
let s = semanticSearchers.get(projectRoot);
|
|
239245
|
+
if (!s) {
|
|
239246
|
+
if (semanticSearchers.size >= MAX_SEARCHER_ROOTS) {
|
|
239247
|
+
const oldestKey = semanticSearchers.keys().next().value;
|
|
239248
|
+
if (oldestKey !== void 0) semanticSearchers.delete(oldestKey);
|
|
239249
|
+
}
|
|
239250
|
+
s = new SemanticSearcher(projectRoot);
|
|
239251
|
+
semanticSearchers.set(projectRoot, s);
|
|
239252
|
+
}
|
|
239253
|
+
return s;
|
|
239254
|
+
}
|
|
238662
239255
|
var _CPT = 4;
|
|
238663
239256
|
var _ALC = 42;
|
|
238664
239257
|
var _tallies = /* @__PURE__ */ new Map();
|
|
@@ -238692,14 +239285,6 @@ function _track(root, raw, resp) {
|
|
|
238692
239285
|
return { used, raw, saved, sessionSaved: t.saved, sessionCalls: t.calls };
|
|
238693
239286
|
}
|
|
238694
239287
|
var semanticSearchers = /* @__PURE__ */ new Map();
|
|
238695
|
-
function getSemanticSearcher(projectRoot) {
|
|
238696
|
-
let s = semanticSearchers.get(projectRoot);
|
|
238697
|
-
if (!s) {
|
|
238698
|
-
s = new SemanticSearcher(projectRoot);
|
|
238699
|
-
semanticSearchers.set(projectRoot, s);
|
|
238700
|
-
}
|
|
238701
|
-
return s;
|
|
238702
|
-
}
|
|
238703
239288
|
function registerTools(server2, projectRoot) {
|
|
238704
239289
|
server2.tool(
|
|
238705
239290
|
"mikk_test_tool",
|
|
@@ -238745,16 +239330,23 @@ function registerTools(server2, projectRoot) {
|
|
|
238745
239330
|
);
|
|
238746
239331
|
server2.tool(
|
|
238747
239332
|
"mikk_query_context",
|
|
238748
|
-
"Ask an architecture question \
|
|
239333
|
+
"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.",
|
|
238749
239334
|
{
|
|
238750
239335
|
question: external_exports.string().describe("The architecture question or task description"),
|
|
238751
239336
|
maxHops: external_exports.number().optional().default(4).describe("Graph traversal depth (default: 4)"),
|
|
238752
239337
|
tokenBudget: external_exports.number().optional().default(6e3).describe("Max tokens for function bodies (default: 6000)"),
|
|
238753
239338
|
focusFile: external_exports.string().optional().describe("Anchor traversal from a specific file path"),
|
|
238754
239339
|
focusModule: external_exports.string().optional().describe("Anchor traversal from a specific module ID"),
|
|
239340
|
+
strict: external_exports.boolean().optional().default(false).describe("High-precision mode: include only tightly relevant context"),
|
|
239341
|
+
requiredTerms: external_exports.array(external_exports.string()).optional().describe("Required terms that must match returned context"),
|
|
239342
|
+
requireAllKeywords: external_exports.boolean().optional().default(false).describe("In strict mode, require all extracted keywords"),
|
|
239343
|
+
minKeywordMatches: external_exports.number().optional().default(1).describe("In strict mode, minimum keyword hits per function"),
|
|
239344
|
+
exactOnly: external_exports.boolean().optional().default(false).describe("Hard gate: keep only strict keyword matches"),
|
|
239345
|
+
failFast: external_exports.boolean().optional().default(false).describe("Return no context if strict filters find no exact match"),
|
|
239346
|
+
autoFallback: external_exports.boolean().optional().default(true).describe("When strict mode returns empty, retry with balanced retrieval"),
|
|
238755
239347
|
provider: external_exports.enum(["claude", "generic", "compact"]).optional().default("generic").describe("AI provider format: claude (XML tags), generic (plain), compact (minimal tokens)")
|
|
238756
239348
|
},
|
|
238757
|
-
async ({ question, maxHops, tokenBudget, focusFile, focusModule, provider }) => {
|
|
239349
|
+
async ({ question, maxHops, tokenBudget, focusFile, focusModule, strict, requiredTerms, requireAllKeywords, minKeywordMatches, exactOnly, failFast, autoFallback, provider }) => {
|
|
238758
239350
|
const { contract, lock, staleness } = await loadContractAndLock(projectRoot);
|
|
238759
239351
|
const query = {
|
|
238760
239352
|
task: question,
|
|
@@ -238764,10 +239356,37 @@ function registerTools(server2, projectRoot) {
|
|
|
238764
239356
|
focusModules: focusModule ? [focusModule] : void 0,
|
|
238765
239357
|
includeCallGraph: true,
|
|
238766
239358
|
includeBodies: true,
|
|
239359
|
+
relevanceMode: strict ? "strict" : "balanced",
|
|
239360
|
+
requiredKeywords: requiredTerms,
|
|
239361
|
+
requireAllKeywords,
|
|
239362
|
+
minKeywordMatches,
|
|
239363
|
+
exactOnly,
|
|
239364
|
+
failFast,
|
|
238767
239365
|
projectRoot
|
|
238768
239366
|
};
|
|
238769
239367
|
const builder = new ContextBuilder(contract, lock);
|
|
238770
|
-
|
|
239368
|
+
let ctx = builder.build(query);
|
|
239369
|
+
let fallbackUsed = false;
|
|
239370
|
+
if (autoFallback !== false && strict && ctx.modules.length === 0) {
|
|
239371
|
+
const relaxed = {
|
|
239372
|
+
...query,
|
|
239373
|
+
relevanceMode: "balanced",
|
|
239374
|
+
requiredKeywords: void 0,
|
|
239375
|
+
requireAllKeywords: false,
|
|
239376
|
+
minKeywordMatches: 1,
|
|
239377
|
+
exactOnly: false,
|
|
239378
|
+
failFast: false
|
|
239379
|
+
};
|
|
239380
|
+
const fallback = builder.build(relaxed);
|
|
239381
|
+
if (fallback.modules.length > 0) {
|
|
239382
|
+
ctx = fallback;
|
|
239383
|
+
fallbackUsed = true;
|
|
239384
|
+
ctx.meta.reasons = [
|
|
239385
|
+
...ctx.meta.reasons ?? [],
|
|
239386
|
+
"strict query had no exact matches; returned balanced fallback context"
|
|
239387
|
+
];
|
|
239388
|
+
}
|
|
239389
|
+
}
|
|
238771
239390
|
if (ctx.modules.length === 0) {
|
|
238772
239391
|
return {
|
|
238773
239392
|
content: [{
|
|
@@ -238782,6 +239401,7 @@ function registerTools(server2, projectRoot) {
|
|
|
238782
239401
|
const warning = staleness ? `
|
|
238783
239402
|
|
|
238784
239403
|
${staleness}` : "";
|
|
239404
|
+
const fallbackNote = fallbackUsed ? "Note: strict mode had no exact matches; showing balanced fallback context.\n\n" : "";
|
|
238785
239405
|
const _rawQC = (tokenBudget ?? 6e3) * 3;
|
|
238786
239406
|
const _tokQC = _track(projectRoot, _rawQC, output);
|
|
238787
239407
|
const tokLine = `
|
|
@@ -238789,7 +239409,7 @@ ${staleness}` : "";
|
|
|
238789
239409
|
---
|
|
238790
239410
|
// tokens: ${JSON.stringify(_tokQC)}`;
|
|
238791
239411
|
return {
|
|
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 }]
|
|
239412
|
+
content: [{ type: "text", text: fallbackNote + 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 }]
|
|
238793
239413
|
};
|
|
238794
239414
|
}
|
|
238795
239415
|
);
|
|
@@ -238821,7 +239441,7 @@ ${staleness}` : "";
|
|
|
238821
239441
|
const result = analyzer.analyze(fileNodes.map((n) => n.id));
|
|
238822
239442
|
const impactedDetails = result.impacted.slice(0, 30).map((id) => {
|
|
238823
239443
|
const node = graph.nodes.get(id);
|
|
238824
|
-
return { function: node?.
|
|
239444
|
+
return { function: node?.name ?? id, file: node?.file ?? "", module: node?.moduleId ?? "" };
|
|
238825
239445
|
});
|
|
238826
239446
|
const response = {
|
|
238827
239447
|
file,
|
|
@@ -238915,7 +239535,7 @@ ${staleness}` : "";
|
|
|
238915
239535
|
const result = analyzer.analyze(fileFns.map((fn) => fn.id));
|
|
238916
239536
|
const impactedDetails = result.impacted.slice(0, 20).map((id) => {
|
|
238917
239537
|
const node = graph.nodes.get(id);
|
|
238918
|
-
return { function: node?.
|
|
239538
|
+
return { function: node?.name ?? id, file: node?.file ?? "", module: node?.moduleId ?? "" };
|
|
238919
239539
|
});
|
|
238920
239540
|
const exportedAtRisk = fileFns.filter((fn) => fn.isExported).map((fn) => ({
|
|
238921
239541
|
name: fn.name,
|
|
@@ -238953,7 +239573,7 @@ ${staleness}` : "";
|
|
|
238953
239573
|
constraintStatus: totalViolations === 0 ? "pass" : "fail",
|
|
238954
239574
|
files: fileReports,
|
|
238955
239575
|
warning: staleness,
|
|
238956
|
-
hint: totalViolations > 0 ? "\
|
|
239576
|
+
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."
|
|
238957
239577
|
};
|
|
238958
239578
|
const _rawBE = _filesTok(lock, filesToEdit) * 4;
|
|
238959
239579
|
response.tokens = _track(projectRoot, _rawBE, response);
|
|
@@ -239087,7 +239707,7 @@ ${staleness}` : "";
|
|
|
239087
239707
|
content: [{
|
|
239088
239708
|
type: "text",
|
|
239089
239709
|
text: [
|
|
239090
|
-
"\
|
|
239710
|
+
"\u26A0 Semantic search requires @xenova/transformers.",
|
|
239091
239711
|
"",
|
|
239092
239712
|
"Install it in your project root:",
|
|
239093
239713
|
" npm install @xenova/transformers",
|
|
@@ -239216,7 +239836,7 @@ ${content}`
|
|
|
239216
239836
|
);
|
|
239217
239837
|
server2.tool(
|
|
239218
239838
|
"mikk_dead_code",
|
|
239219
|
-
"Detect dead code \
|
|
239839
|
+
"Detect dead code \u2014 functions with zero callers after exempting exports, entry points, route handlers, tests, and constructors. Use this before refactoring or cleanup.",
|
|
239220
239840
|
{
|
|
239221
239841
|
moduleId: external_exports.string().optional().describe("Filter results to a specific module ID")
|
|
239222
239842
|
},
|
|
@@ -239241,7 +239861,7 @@ ${content}`
|
|
|
239241
239861
|
);
|
|
239242
239862
|
server2.tool(
|
|
239243
239863
|
"mikk_manage_adr",
|
|
239244
|
-
"CRUD for Architectural Decision Records (ADRs) in mikk.json. Actions: list, get, add, update, remove. WHEN TO USE: When making architectural changes \
|
|
239864
|
+
"CRUD for Architectural Decision Records (ADRs) in mikk.json. Actions: list, get, add, update, remove. WHEN TO USE: When making architectural changes \u2014 document WHY so future AI agents understand. AFTER THIS: ADRs automatically surface in mikk_query_context responses. Required for add: id, title, reason.",
|
|
239245
239865
|
{
|
|
239246
239866
|
action: external_exports.enum(["list", "get", "add", "update", "remove"]).describe("The CRUD action to perform"),
|
|
239247
239867
|
id: external_exports.string().optional().describe("ADR id (required for get, update, remove)"),
|
|
@@ -239351,7 +239971,7 @@ ${content}`
|
|
|
239351
239971
|
);
|
|
239352
239972
|
server2.tool(
|
|
239353
239973
|
"mikk_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 \
|
|
239974
|
+
"Read file scoped to specific functions. Returns bodies with metadata headers (params, calls, calledBy). WHEN TO USE: When you know which functions you need \u2014 saves tokens vs mikk_get_file. AFTER THIS: Use mikk_before_edit before making changes. TIP: This is the preferred way to read code \u2014 always specify function names when possible.",
|
|
239355
239975
|
{
|
|
239356
239976
|
file: external_exports.string().describe("File path relative to project root"),
|
|
239357
239977
|
functions: external_exports.array(external_exports.string()).optional().describe("Function names to extract. If omitted, returns the whole file.")
|
|
@@ -239391,7 +240011,7 @@ ${content}` }]
|
|
|
239391
240011
|
(f) => (f.name === fnName || f.name.endsWith(`.${fnName}`)) && (f.file === normalizedFile || f.file.endsWith("/" + normalizedFile))
|
|
239392
240012
|
);
|
|
239393
240013
|
if (!fn) {
|
|
239394
|
-
sections.push(`// \
|
|
240014
|
+
sections.push(`// \u26A0 Function "${fnName}" not found in ${file}`);
|
|
239395
240015
|
continue;
|
|
239396
240016
|
}
|
|
239397
240017
|
const header = [
|
|
@@ -239519,11 +240139,16 @@ ${staleness}` : "";
|
|
|
239519
240139
|
}
|
|
239520
240140
|
}
|
|
239521
240141
|
const totalFns = affectedSymbols.reduce((s, f) => s + f.functions.length, 0);
|
|
239522
|
-
return {
|
|
239523
|
-
|
|
239524
|
-
|
|
239525
|
-
|
|
239526
|
-
|
|
240142
|
+
return {
|
|
240143
|
+
content: [{
|
|
240144
|
+
type: "text",
|
|
240145
|
+
text: JSON.stringify({
|
|
240146
|
+
summary: `${affectedSymbols.length} file(s), ${totalFns} function(s) affected`,
|
|
240147
|
+
affectedSymbols,
|
|
240148
|
+
warning: staleness
|
|
240149
|
+
}, null, 2)
|
|
240150
|
+
}]
|
|
240151
|
+
};
|
|
239527
240152
|
} catch (err) {
|
|
239528
240153
|
return { content: [{ type: "text", text: `Git diff failed: ${err.message}` }], isError: true };
|
|
239529
240154
|
}
|
|
@@ -239531,7 +240156,7 @@ ${staleness}` : "";
|
|
|
239531
240156
|
);
|
|
239532
240157
|
server2.tool(
|
|
239533
240158
|
"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 \
|
|
240159
|
+
"Plan a coordinated multi-file rename. Finds all call sites and import locations for a function and provides a step-by-step edit plan. WHEN TO USE: Before renaming any function \u2014 ensures you update ALL call sites. AFTER THIS: Execute the edit plan, then run mikk analyze.",
|
|
239535
240160
|
{
|
|
239536
240161
|
functionName: external_exports.string().describe("The current function name to rename"),
|
|
239537
240162
|
newName: external_exports.string().describe("The desired new name")
|
|
@@ -239557,7 +240182,7 @@ ${staleness}` : "";
|
|
|
239557
240182
|
lineRange: `${fn.startLine}-${fn.endLine}`
|
|
239558
240183
|
}));
|
|
239559
240184
|
const filesImporting = Object.values(lock.files).filter(
|
|
239560
|
-
(file) => file.imports?.some((imp) => imp.includes(functionName) || imp.
|
|
240185
|
+
(file) => file.imports?.some((imp) => imp.names.includes(functionName) || imp.source === targetFn.file)
|
|
239561
240186
|
);
|
|
239562
240187
|
const instructions = [
|
|
239563
240188
|
`1. Rename definition in ${targetFn.file}:${targetFn.startLine}`,
|
|
@@ -239620,9 +240245,19 @@ ${staleness}` : "";
|
|
|
239620
240245
|
);
|
|
239621
240246
|
}
|
|
239622
240247
|
async function loadContractAndLock(projectRoot) {
|
|
240248
|
+
const lockFilePath = path4.join(projectRoot, "mikk.lock.json");
|
|
239623
240249
|
const cached2 = projectCache.get(projectRoot);
|
|
239624
|
-
if (cached2
|
|
239625
|
-
|
|
240250
|
+
if (cached2) {
|
|
240251
|
+
try {
|
|
240252
|
+
const stat2 = await fs6.stat(lockFilePath);
|
|
240253
|
+
if (stat2.mtimeMs > cached2.cachedAt) {
|
|
240254
|
+
invalidateCache(projectRoot);
|
|
240255
|
+
} else if (Date.now() - cached2.cachedAt < CACHE_TTL_MS) {
|
|
240256
|
+
return { contract: cached2.contract, lock: cached2.lock, staleness: cached2.staleness };
|
|
240257
|
+
}
|
|
240258
|
+
} catch {
|
|
240259
|
+
invalidateCache(projectRoot);
|
|
240260
|
+
}
|
|
239626
240261
|
}
|
|
239627
240262
|
const contractReader = new ContractReader();
|
|
239628
240263
|
const lockReader = new LockReader();
|
|
@@ -239631,7 +240266,7 @@ async function loadContractAndLock(projectRoot) {
|
|
|
239631
240266
|
const syncStatus = lock.syncState?.status ?? "unknown";
|
|
239632
240267
|
let staleness = null;
|
|
239633
240268
|
if (syncStatus === "drifted" || syncStatus === "conflict") {
|
|
239634
|
-
staleness = `\
|
|
240269
|
+
staleness = `\u26A0 Lock file is ${syncStatus}. Run \`mikk analyze\` for accurate results.`;
|
|
239635
240270
|
}
|
|
239636
240271
|
if (!staleness) {
|
|
239637
240272
|
const fileEntries = Object.entries(lock.files);
|
|
@@ -239654,7 +240289,7 @@ async function loadContractAndLock(projectRoot) {
|
|
|
239654
240289
|
}
|
|
239655
240290
|
}
|
|
239656
240291
|
if (mismatched > 0) {
|
|
239657
|
-
staleness = `\
|
|
240292
|
+
staleness = `\u26A0 STALE: ${mismatched} file(s) changed since last analysis (${mismatchedFiles.slice(0, 3).join(", ")}${mismatched > 3 ? "..." : ""}). Run \`mikk analyze\`.`;
|
|
239658
240293
|
}
|
|
239659
240294
|
}
|
|
239660
240295
|
const graph = buildGraphFromLock(lock);
|
|
@@ -239676,7 +240311,7 @@ function buildGraphFromLock(lock) {
|
|
|
239676
240311
|
nodes.set(fn.id, {
|
|
239677
240312
|
id: fn.id,
|
|
239678
240313
|
type: "function",
|
|
239679
|
-
|
|
240314
|
+
name: fn.name,
|
|
239680
240315
|
file: fn.file,
|
|
239681
240316
|
moduleId: fn.moduleId,
|
|
239682
240317
|
metadata: {
|
|
@@ -239697,7 +240332,7 @@ function buildGraphFromLock(lock) {
|
|
|
239697
240332
|
nodes.set(file.path, {
|
|
239698
240333
|
id: file.path,
|
|
239699
240334
|
type: "file",
|
|
239700
|
-
|
|
240335
|
+
name: path4.basename(file.path),
|
|
239701
240336
|
file: file.path,
|
|
239702
240337
|
moduleId: file.moduleId,
|
|
239703
240338
|
metadata: {}
|
|
@@ -239706,7 +240341,7 @@ function buildGraphFromLock(lock) {
|
|
|
239706
240341
|
for (const fn of Object.values(lock.functions)) {
|
|
239707
240342
|
for (const calleeId of fn.calls) {
|
|
239708
240343
|
if (!nodes.has(calleeId)) continue;
|
|
239709
|
-
const edge = {
|
|
240344
|
+
const edge = { from: fn.id, to: calleeId, type: "calls", confidence: 1 };
|
|
239710
240345
|
edges.push(edge);
|
|
239711
240346
|
const out = outEdges.get(fn.id) ?? [];
|
|
239712
240347
|
out.push(edge);
|
|
@@ -239726,7 +240361,7 @@ function detectCircularDeps(fns, lock) {
|
|
|
239726
240361
|
const cycleStart = cyclePath.indexOf(id);
|
|
239727
240362
|
const cycle = cyclePath.slice(cycleStart).map((cid) => lock.functions[cid]?.name ?? cid);
|
|
239728
240363
|
cycle.push(lock.functions[id]?.name ?? id);
|
|
239729
|
-
warnings.push(`\
|
|
240364
|
+
warnings.push(`\u26A0 Circular: ${cycle.join(" -> ")}`);
|
|
239730
240365
|
return true;
|
|
239731
240366
|
}
|
|
239732
240367
|
if (visited.has(id)) return false;
|
|
@@ -239861,7 +240496,7 @@ async function safeRead(filePath) {
|
|
|
239861
240496
|
}
|
|
239862
240497
|
|
|
239863
240498
|
// src/server.ts
|
|
239864
|
-
var VERSION = true ? "1.
|
|
240499
|
+
var VERSION = true ? "1.10.0" : "0.0.0-dev";
|
|
239865
240500
|
function createMikkMcpServer(projectRoot) {
|
|
239866
240501
|
const server2 = new McpServer({
|
|
239867
240502
|
name: "mikk",
|