@getmikk/mcp-server 1.9.2 → 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 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) {
@@ -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/typescript/ts-extractor.js
236757
- var import_typescript = __toESM(require_typescript(), 1);
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/javascript/js-extractor.js
236760
- var import_typescript2 = __toESM(require_typescript(), 1);
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 importedPath of file.imports) {
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,113 +236995,236 @@ var BoundaryChecker = class {
236911
236995
  }
236912
236996
  };
236913
236997
 
236914
- // ../core/dist/parser/tree-sitter/parser.js
236915
- var import_node_module = require("node:module");
236916
- var import_meta = {};
236917
- var getRequire = () => {
236918
- if (typeof require !== "undefined")
236919
- return require;
236920
- return (0, import_node_module.createRequire)(import_meta.url);
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 Set();
236935
- const depthMap = /* @__PURE__ */ new Map();
236936
- const pathConfidence = /* @__PURE__ */ new Map();
236937
- const queue = changedNodeIds.map((id) => ({ id, depth: 0, confidence: 1 }));
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;
236938
237124
  let maxDepth = 0;
236939
- const changedSet = new Set(changedNodeIds);
236940
- const changedModules = /* @__PURE__ */ new Set();
236941
- for (const id of changedNodeIds) {
236942
- const node = this.graph.nodes.get(id);
236943
- if (node)
236944
- changedModules.add(node.moduleId);
236945
- }
236946
- while (queue.length > 0) {
236947
- const { id: current, depth, confidence: pathConf } = queue.shift();
236948
- if (visited.has(current))
236949
- continue;
236950
- visited.add(current);
236951
- depthMap.set(current, depth);
236952
- pathConfidence.set(current, pathConf);
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
+ }
236953
237137
  maxDepth = Math.max(maxDepth, depth);
237138
+ const node = this.graph.nodes.get(current);
237139
+ if (node?.metadata?.isExported) {
237140
+ entryPoints.add(current);
237141
+ }
236954
237142
  const dependents = this.graph.inEdges.get(current) || [];
236955
237143
  for (const edge of dependents) {
236956
- if (!visited.has(edge.source) && edge.type !== "contains") {
236957
- const edgeConf = edge.confidence ?? 1;
236958
- const newPathConf = Math.min(pathConf, edgeConf);
236959
- queue.push({ id: edge.source, depth: depth + 1, confidence: newPathConf });
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
+ });
236960
237155
  }
236961
237156
  }
236962
237157
  }
236963
- const impacted = [...visited].filter((id) => !changedSet.has(id));
237158
+ const impactedIds = Array.from(visited.keys()).filter((id) => !changedNodeIds.includes(id));
237159
+ let totalRisk = 0;
237160
+ let totalConfidence = 0;
236964
237161
  const classified = {
236965
237162
  critical: [],
236966
237163
  high: [],
236967
237164
  medium: [],
236968
237165
  low: []
236969
237166
  };
236970
- for (const id of impacted) {
237167
+ for (const id of impactedIds) {
237168
+ const context = visited.get(id);
236971
237169
  const node = this.graph.nodes.get(id);
236972
- if (!node)
236973
- continue;
236974
- const depth = depthMap.get(id) ?? 999;
236975
- const crossesBoundary = !changedModules.has(node.moduleId);
236976
- const risk = depth === 1 && crossesBoundary ? "critical" : depth === 1 ? "high" : depth === 2 ? "medium" : "low";
236977
- const entry = {
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 = {
236978
237188
  nodeId: id,
236979
- label: node.label,
236980
- file: node.file,
236981
- moduleId: node.moduleId,
236982
- risk,
236983
- 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
236984
237194
  };
236985
- classified[risk].push(entry);
236986
- }
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
+ ];
236987
237215
  return {
236988
237216
  changed: changedNodeIds,
236989
- impacted,
237217
+ impacted: impactedIds,
237218
+ allImpacted,
236990
237219
  depth: maxDepth,
236991
- confidence: this.computeConfidence(impacted, pathConfidence),
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),
236992
237225
  classified
236993
237226
  };
236994
237227
  }
236995
- /**
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.
237004
- */
237005
- computeConfidence(impacted, pathConfidence) {
237006
- if (impacted.length === 0)
237007
- return "high";
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)
237018
- return "medium";
237019
- return "low";
237020
- }
237021
237228
  };
237022
237229
 
237023
237230
  // ../core/dist/graph/dead-code-detector.js
@@ -237141,10 +237348,29 @@ var DeadCodeDetector = class {
237141
237348
  return false;
237142
237349
  }
237143
237350
  isCalledByExportedInSameFile(fn) {
237144
- for (const callerId of fn.calledBy) {
237145
- const caller = this.lock.functions[callerId];
237146
- if (caller && caller.isExported && caller.file === fn.file)
237147
- return true;
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);
237373
+ }
237148
237374
  }
237149
237375
  return false;
237150
237376
  }
@@ -237159,12 +237385,12 @@ var DeadCodeDetector = class {
237159
237385
  * high — none of the above: safe to remove.
237160
237386
  */
237161
237387
  inferConfidence(fn) {
237388
+ if (DYNAMIC_USAGE_PATTERNS.some((p) => p.test(fn.name)))
237389
+ return "low";
237162
237390
  if (fn.calledBy.length > 0)
237163
237391
  return "medium";
237164
237392
  if (this.filesWithUnresolvedImports.has(fn.file))
237165
237393
  return "medium";
237166
- if (DYNAMIC_USAGE_PATTERNS.some((p) => p.test(fn.name)))
237167
- return "low";
237168
237394
  return "high";
237169
237395
  }
237170
237396
  inferReason(fn) {
@@ -237221,6 +237447,19 @@ var MikkOverwriteSchema = external_exports.object({
237221
237447
  lastOverwrittenBy: external_exports.string().optional(),
237222
237448
  lastOverwrittenAt: external_exports.string().optional()
237223
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
+ });
237224
237463
  var MikkContractSchema = external_exports.object({
237225
237464
  version: external_exports.string(),
237226
237465
  project: external_exports.object({
@@ -237235,7 +237474,8 @@ var MikkContractSchema = external_exports.object({
237235
237474
  constraints: external_exports.array(external_exports.string()).default([]),
237236
237475
  decisions: external_exports.array(MikkDecisionSchema).default([])
237237
237476
  }),
237238
- overwrite: MikkOverwriteSchema
237477
+ overwrite: MikkOverwriteSchema,
237478
+ policies: MikkPolicySchema
237239
237479
  });
237240
237480
  var MikkLockFunctionSchema = external_exports.object({
237241
237481
  id: external_exports.string(),
@@ -237261,7 +237501,9 @@ var MikkLockFunctionSchema = external_exports.object({
237261
237501
  line: external_exports.number(),
237262
237502
  type: external_exports.enum(["try-catch", "throw"]),
237263
237503
  detail: external_exports.string()
237264
- })).optional()
237504
+ })).optional(),
237505
+ confidence: external_exports.number().optional(),
237506
+ riskScore: external_exports.number().optional()
237265
237507
  });
237266
237508
  var MikkLockModuleSchema = external_exports.object({
237267
237509
  id: external_exports.string(),
@@ -237269,12 +237511,17 @@ var MikkLockModuleSchema = external_exports.object({
237269
237511
  hash: external_exports.string(),
237270
237512
  fragmentPath: external_exports.string()
237271
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
+ });
237272
237519
  var MikkLockFileSchema = external_exports.object({
237273
237520
  path: external_exports.string(),
237274
237521
  hash: external_exports.string(),
237275
237522
  moduleId: external_exports.string(),
237276
237523
  lastModified: external_exports.string(),
237277
- imports: external_exports.array(external_exports.string()).optional()
237524
+ imports: external_exports.array(MikkLockImportSchema).optional()
237278
237525
  });
237279
237526
  var MikkLockClassSchema = external_exports.object({
237280
237527
  id: external_exports.string(),
@@ -237290,7 +237537,9 @@ var MikkLockClassSchema = external_exports.object({
237290
237537
  line: external_exports.number(),
237291
237538
  type: external_exports.enum(["try-catch", "throw"]),
237292
237539
  detail: external_exports.string()
237293
- })).optional()
237540
+ })).optional(),
237541
+ confidence: external_exports.number().optional(),
237542
+ riskScore: external_exports.number().optional()
237294
237543
  });
237295
237544
  var MikkLockGenericSchema = external_exports.object({
237296
237545
  id: external_exports.string(),
@@ -237344,6 +237593,12 @@ var MikkLockSchema = external_exports.object({
237344
237593
  })
237345
237594
  });
237346
237595
 
237596
+ // ../core/dist/contract/lock-compiler.js
237597
+ init_file_hasher();
237598
+
237599
+ // ../core/dist/hash/tree-hasher.js
237600
+ init_file_hasher();
237601
+
237347
237602
  // ../core/dist/utils/json.js
237348
237603
  var fs = __toESM(require("node:fs/promises"), 1);
237349
237604
  async function readJsonSafe(filePath, fileLabel = "JSON file") {
@@ -237505,8 +237760,9 @@ function compactifyLock(lock) {
237505
237760
  };
237506
237761
  if (file.moduleId && file.moduleId !== "unknown")
237507
237762
  c.moduleId = file.moduleId;
237508
- if (file.imports && file.imports.length > 0)
237509
- c.imports = file.imports;
237763
+ if (file.imports && file.imports.length > 0) {
237764
+ c.imports = file.imports.map(normalizeImportEntry);
237765
+ }
237510
237766
  out.files[key] = c;
237511
237767
  }
237512
237768
  if (lock.contextFiles && lock.contextFiles.length > 0) {
@@ -237536,7 +237792,10 @@ function hydrateLock(raw) {
237536
237792
  const hasFnIndex = fnIndex.length > 0;
237537
237793
  const fileModuleMap = {};
237538
237794
  for (const [key, c] of Object.entries(raw.files || {})) {
237539
- fileModuleMap[key] = c.moduleId || "unknown";
237795
+ const moduleId = c.moduleId || "unknown";
237796
+ const normalizedKey = normalizeFilePath(key);
237797
+ fileModuleMap[key] = moduleId;
237798
+ fileModuleMap[normalizedKey] = moduleId;
237540
237799
  }
237541
237800
  out.functions = {};
237542
237801
  for (const [key, c] of Object.entries(raw.functions || {})) {
@@ -237545,6 +237804,7 @@ function hydrateLock(raw) {
237545
237804
  const lines = c.lines || [c.startLine || 0, c.endLine || 0];
237546
237805
  const calls = (c.calls || []).map((v) => typeof v === "number" ? fnIndex[v] ?? null : v).filter(Boolean);
237547
237806
  const calledBy = (c.calledBy || []).map((v) => typeof v === "number" ? fnIndex[v] ?? null : v).filter(Boolean);
237807
+ const normalizedFile = normalizeFilePath(file);
237548
237808
  out.functions[fullId] = {
237549
237809
  id: fullId,
237550
237810
  name,
@@ -237555,7 +237815,7 @@ function hydrateLock(raw) {
237555
237815
  // P4: empty string when not stored
237556
237816
  calls,
237557
237817
  calledBy,
237558
- moduleId: fileModuleMap[file] || c.moduleId || "unknown",
237818
+ moduleId: fileModuleMap[normalizedFile] || fileModuleMap[file] || c.moduleId || "unknown",
237559
237819
  // P6: derive from file
237560
237820
  ...c.params ? { params: c.params } : {},
237561
237821
  ...c.returnType ? { returnType: c.returnType } : {},
@@ -237625,7 +237885,7 @@ function hydrateLock(raw) {
237625
237885
  hash: c.hash || "",
237626
237886
  moduleId: c.moduleId || "unknown",
237627
237887
  lastModified: c.lastModified || "",
237628
- ...c.imports && c.imports.length > 0 ? { imports: c.imports } : {}
237888
+ ...c.imports && c.imports.length > 0 ? { imports: c.imports.map(normalizeImportEntry) } : {}
237629
237889
  };
237630
237890
  }
237631
237891
  out.modules = raw.modules;
@@ -237660,6 +237920,20 @@ function parseEntityKeyFull(key) {
237660
237920
  name: rest.slice(lastColon + 1)
237661
237921
  };
237662
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
+ }
237663
237937
 
237664
237938
  // ../core/dist/contract/adr-manager.js
237665
237939
  var fs3 = __toESM(require("node:fs/promises"), 1);
@@ -237721,6 +237995,9 @@ var AdrManager = class {
237721
237995
  }
237722
237996
  };
237723
237997
 
237998
+ // ../core/dist/hash/index.js
237999
+ init_file_hasher();
238000
+
237724
238001
  // ../core/dist/hash/hash-store.js
237725
238002
  var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
237726
238003
 
@@ -237732,11 +238009,14 @@ var BM25Index = class {
237732
238009
  documentFrequency = /* @__PURE__ */ new Map();
237733
238010
  // term → how many docs contain it
237734
238011
  avgDocLength = 0;
238012
+ totalDocLength = 0;
238013
+ // running total — avoids O(n²) recompute on every addDocument
237735
238014
  /** Clear the index */
237736
238015
  clear() {
237737
238016
  this.documents = [];
237738
238017
  this.documentFrequency.clear();
237739
238018
  this.avgDocLength = 0;
238019
+ this.totalDocLength = 0;
237740
238020
  }
237741
238021
  /** Add a document with pre-tokenized terms */
237742
238022
  addDocument(id, tokens) {
@@ -237746,7 +238026,8 @@ var BM25Index = class {
237746
238026
  for (const term of uniqueTerms) {
237747
238027
  this.documentFrequency.set(term, (this.documentFrequency.get(term) ?? 0) + 1);
237748
238028
  }
237749
- this.avgDocLength = this.documents.reduce((sum, d) => sum + d.length, 0) / this.documents.length;
238029
+ this.totalDocLength += normalizedTokens.length;
238030
+ this.avgDocLength = this.totalDocLength / this.documents.length;
237750
238031
  }
237751
238032
  /** Search the index and return ranked results */
237752
238033
  search(query, limit = 20) {
@@ -237947,25 +238228,90 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
237947
238228
  "my",
237948
238229
  "your"
237949
238230
  ]);
237950
- function extractKeywords2(task) {
237951
- return task.toLowerCase().replace(/[^a-z0-9\s_-]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !STOP_WORDS.has(w));
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;
237952
238284
  }
237953
238285
  function keywordScore(fn, keywords) {
237954
238286
  if (keywords.length === 0)
237955
- return 0;
238287
+ return { score: 0, matchedKeywords: [] };
237956
238288
  const nameLower = fn.name.toLowerCase();
237957
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
+ ]);
237958
238297
  let score = 0;
238298
+ const matched = [];
237959
238299
  for (const kw of keywords) {
237960
- if (nameLower === kw) {
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) {
237961
238304
  score = Math.max(score, WEIGHT.KEYWORD_EXACT);
237962
- } else if (nameLower.includes(kw) || fileLower.includes(kw)) {
238305
+ matched.push(kw);
238306
+ } else if (partial2) {
237963
238307
  score = Math.max(score, WEIGHT.KEYWORD_PARTIAL);
238308
+ matched.push(kw);
237964
238309
  }
237965
238310
  }
237966
- return score;
238311
+ return { score, matchedKeywords: matched };
237967
238312
  }
237968
- function resolveSeeds(query, contract, lock) {
238313
+ function resolveSeeds(query, contract, lock, keywords) {
238314
+ const strictMode = query.relevanceMode === "strict";
237969
238315
  const seeds = /* @__PURE__ */ new Set();
237970
238316
  if (query.focusFiles && query.focusFiles.length > 0) {
237971
238317
  for (const filePath of query.focusFiles) {
@@ -237985,14 +238331,13 @@ function resolveSeeds(query, contract, lock) {
237985
238331
  }
237986
238332
  }
237987
238333
  if (seeds.size === 0) {
237988
- const keywords = extractKeywords2(query.task);
237989
238334
  for (const fn of Object.values(lock.functions)) {
237990
- if (keywordScore(fn, keywords) >= WEIGHT.KEYWORD_PARTIAL) {
238335
+ if (keywordScore(fn, keywords).score >= WEIGHT.KEYWORD_PARTIAL) {
237991
238336
  seeds.add(fn.id);
237992
238337
  }
237993
238338
  }
237994
238339
  }
237995
- if (seeds.size === 0) {
238340
+ if (!strictMode && seeds.size === 0) {
237996
238341
  const taskLower = query.task.toLowerCase();
237997
238342
  for (const mod of contract.declared.modules) {
237998
238343
  if (taskLower.includes(mod.id.toLowerCase()) || taskLower.includes(mod.name.toLowerCase())) {
@@ -238024,25 +238369,72 @@ var ContextBuilder = class {
238024
238369
  * 6. Group survivors by module, emit structured context
238025
238370
  */
238026
238371
  build(query) {
238372
+ const relevanceMode = query.relevanceMode ?? "balanced";
238373
+ const strictMode = relevanceMode === "strict";
238027
238374
  const tokenBudget = query.tokenBudget ?? DEFAULT_TOKEN_BUDGET;
238028
238375
  const maxHops = query.maxHops ?? 4;
238029
- const seeds = resolveSeeds(query, this.contract, this.lock);
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);
238030
238381
  const proximityMap = seeds.length > 0 ? bfsNeighbors(seeds, this.lock.functions, maxHops) : /* @__PURE__ */ new Map();
238031
- const keywords = extractKeywords2(query.task);
238032
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 = [];
238033
238391
  const scored = allFunctions.map((fn) => {
238034
238392
  let score = 0;
238035
238393
  const depth = proximityMap.get(fn.id);
238036
238394
  if (depth !== void 0) {
238037
238395
  score += depthToScore(depth);
238038
238396
  }
238039
- score += keywordScore(fn, keywords);
238040
- if (fn.calledBy.length === 0)
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)
238041
238419
  score += WEIGHT.ENTRY_POINT;
238042
238420
  return { fn, score };
238043
238421
  });
238044
238422
  scored.sort((a, b) => b.score - a.score);
238045
- const selected = [];
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 = [];
238046
238438
  let usedTokens = 0;
238047
238439
  for (const { fn, score } of scored) {
238048
238440
  if (score <= 0 && seeds.length > 0)
@@ -238056,6 +238448,51 @@ var ContextBuilder = class {
238056
238448
  selected.push(fn);
238057
238449
  usedTokens += tokens;
238058
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
+ }
238059
238496
  const byModule = /* @__PURE__ */ new Map();
238060
238497
  for (const fn of selected) {
238061
238498
  if (!byModule.has(fn.moduleId))
@@ -238076,6 +238513,8 @@ var ContextBuilder = class {
238076
238513
  });
238077
238514
  }
238078
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;
238079
238518
  return {
238080
238519
  project: {
238081
238520
  name: this.contract.project.name,
@@ -238090,12 +238529,12 @@ var ContextBuilder = class {
238090
238529
  title: d.title,
238091
238530
  reason: d.reason
238092
238531
  })),
238093
- contextFiles: this.lock.contextFiles?.map((cf) => ({
238532
+ contextFiles: contextFiles?.map((cf) => ({
238094
238533
  path: cf.path,
238095
238534
  content: readContextFile(cf.path, query.projectRoot),
238096
238535
  type: cf.type
238097
238536
  })),
238098
- routes: this.lock.routes?.map((r) => ({
238537
+ routes: routes?.map((r) => ({
238099
238538
  method: r.method,
238100
238539
  path: r.path,
238101
238540
  handler: r.handler,
@@ -238109,7 +238548,9 @@ var ContextBuilder = class {
238109
238548
  totalFunctionsConsidered: allFunctions.length,
238110
238549
  selectedFunctions: selected.length,
238111
238550
  estimatedTokens: usedTokens,
238112
- keywords
238551
+ keywords,
238552
+ reasons: reasons.length > 0 ? reasons : void 0,
238553
+ suggestions: selected.length === 0 && suggestions.length > 0 ? suggestions : void 0
238113
238554
  }
238114
238555
  };
238115
238556
  }
@@ -238181,6 +238622,7 @@ var ContextBuilder = class {
238181
238622
  /** Generate the natural-language prompt section */
238182
238623
  generatePrompt(query, modules) {
238183
238624
  const lines = [];
238625
+ const strictMode = query.relevanceMode === "strict";
238184
238626
  lines.push("=== ARCHITECTURAL CONTEXT ===");
238185
238627
  lines.push(`Project: ${this.contract.project.name} (${this.contract.project.language})`);
238186
238628
  if (this.contract.project.description) {
@@ -238189,7 +238631,7 @@ var ContextBuilder = class {
238189
238631
  lines.push(`Task: ${query.task}`);
238190
238632
  lines.push("");
238191
238633
  const routes = this.lock.routes;
238192
- if (routes && routes.length > 0) {
238634
+ if (!strictMode && routes && routes.length > 0) {
238193
238635
  lines.push("=== HTTP ROUTES ===");
238194
238636
  for (const r of routes) {
238195
238637
  const mw = r.middlewares.length > 0 ? ` [${r.middlewares.join(", ")}]` : "";
@@ -238198,7 +238640,7 @@ var ContextBuilder = class {
238198
238640
  lines.push("");
238199
238641
  }
238200
238642
  const ctxFiles = this.lock.contextFiles;
238201
- if (ctxFiles && ctxFiles.length > 0) {
238643
+ if (!strictMode && ctxFiles && ctxFiles.length > 0) {
238202
238644
  lines.push("=== DATA MODELS & SCHEMAS ===");
238203
238645
  for (const cf of ctxFiles) {
238204
238646
  lines.push(`--- ${cf.path} (${cf.type}) ---`);
@@ -238465,6 +238907,16 @@ var ClaudeProvider = class {
238465
238907
  lines.push(` <seeds_found>${context.meta?.seedCount ?? 0}</seeds_found>`);
238466
238908
  lines.push(` <functions_selected>${context.meta?.selectedFunctions ?? 0} of ${context.meta?.totalFunctionsConsidered ?? 0}</functions_selected>`);
238467
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
+ }
238468
238920
  lines.push("</context_meta>");
238469
238921
  lines.push("");
238470
238922
  for (const mod of context.modules) {
@@ -238507,6 +238959,22 @@ var ClaudeProvider = class {
238507
238959
  lines.push("</module>");
238508
238960
  lines.push("");
238509
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
+ }
238510
238978
  if (context.contextFiles && context.contextFiles.length > 0) {
238511
238979
  lines.push("<context_files>");
238512
238980
  for (const cf of context.contextFiles) {
@@ -238567,6 +239035,20 @@ var CompactProvider = class {
238567
239035
  `Task keywords: ${context.meta?.keywords?.join(", ") ?? ""}`,
238568
239036
  ""
238569
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
+ }
238570
239052
  for (const mod of context.modules) {
238571
239053
  lines.push(`## ${mod.name}`);
238572
239054
  for (const fn of mod.functions) {
@@ -238754,6 +239236,22 @@ var IntentSchema = external_exports.object({
238754
239236
  // src/tools.ts
238755
239237
  var projectCache = /* @__PURE__ */ new Map();
238756
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
+ }
238757
239255
  var _CPT = 4;
238758
239256
  var _ALC = 42;
238759
239257
  var _tallies = /* @__PURE__ */ new Map();
@@ -238787,14 +239285,6 @@ function _track(root, raw, resp) {
238787
239285
  return { used, raw, saved, sessionSaved: t.saved, sessionCalls: t.calls };
238788
239286
  }
238789
239287
  var semanticSearchers = /* @__PURE__ */ new Map();
238790
- function getSemanticSearcher(projectRoot) {
238791
- let s = semanticSearchers.get(projectRoot);
238792
- if (!s) {
238793
- s = new SemanticSearcher(projectRoot);
238794
- semanticSearchers.set(projectRoot, s);
238795
- }
238796
- return s;
238797
- }
238798
239288
  function registerTools(server2, projectRoot) {
238799
239289
  server2.tool(
238800
239290
  "mikk_test_tool",
@@ -238847,9 +239337,16 @@ function registerTools(server2, projectRoot) {
238847
239337
  tokenBudget: external_exports.number().optional().default(6e3).describe("Max tokens for function bodies (default: 6000)"),
238848
239338
  focusFile: external_exports.string().optional().describe("Anchor traversal from a specific file path"),
238849
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"),
238850
239347
  provider: external_exports.enum(["claude", "generic", "compact"]).optional().default("generic").describe("AI provider format: claude (XML tags), generic (plain), compact (minimal tokens)")
238851
239348
  },
238852
- async ({ question, maxHops, tokenBudget, focusFile, focusModule, provider }) => {
239349
+ async ({ question, maxHops, tokenBudget, focusFile, focusModule, strict, requiredTerms, requireAllKeywords, minKeywordMatches, exactOnly, failFast, autoFallback, provider }) => {
238853
239350
  const { contract, lock, staleness } = await loadContractAndLock(projectRoot);
238854
239351
  const query = {
238855
239352
  task: question,
@@ -238859,10 +239356,37 @@ function registerTools(server2, projectRoot) {
238859
239356
  focusModules: focusModule ? [focusModule] : void 0,
238860
239357
  includeCallGraph: true,
238861
239358
  includeBodies: true,
239359
+ relevanceMode: strict ? "strict" : "balanced",
239360
+ requiredKeywords: requiredTerms,
239361
+ requireAllKeywords,
239362
+ minKeywordMatches,
239363
+ exactOnly,
239364
+ failFast,
238862
239365
  projectRoot
238863
239366
  };
238864
239367
  const builder = new ContextBuilder(contract, lock);
238865
- const ctx = builder.build(query);
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
+ }
238866
239390
  if (ctx.modules.length === 0) {
238867
239391
  return {
238868
239392
  content: [{
@@ -238877,6 +239401,7 @@ function registerTools(server2, projectRoot) {
238877
239401
  const warning = staleness ? `
238878
239402
 
238879
239403
  ${staleness}` : "";
239404
+ const fallbackNote = fallbackUsed ? "Note: strict mode had no exact matches; showing balanced fallback context.\n\n" : "";
238880
239405
  const _rawQC = (tokenBudget ?? 6e3) * 3;
238881
239406
  const _tokQC = _track(projectRoot, _rawQC, output);
238882
239407
  const tokLine = `
@@ -238884,7 +239409,7 @@ ${staleness}` : "";
238884
239409
  ---
238885
239410
  // tokens: ${JSON.stringify(_tokQC)}`;
238886
239411
  return {
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 }]
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 }]
238888
239413
  };
238889
239414
  }
238890
239415
  );
@@ -238916,7 +239441,7 @@ ${staleness}` : "";
238916
239441
  const result = analyzer.analyze(fileNodes.map((n) => n.id));
238917
239442
  const impactedDetails = result.impacted.slice(0, 30).map((id) => {
238918
239443
  const node = graph.nodes.get(id);
238919
- return { function: node?.label ?? id, file: node?.file ?? "", module: node?.moduleId ?? "" };
239444
+ return { function: node?.name ?? id, file: node?.file ?? "", module: node?.moduleId ?? "" };
238920
239445
  });
238921
239446
  const response = {
238922
239447
  file,
@@ -239010,7 +239535,7 @@ ${staleness}` : "";
239010
239535
  const result = analyzer.analyze(fileFns.map((fn) => fn.id));
239011
239536
  const impactedDetails = result.impacted.slice(0, 20).map((id) => {
239012
239537
  const node = graph.nodes.get(id);
239013
- return { function: node?.label ?? id, file: node?.file ?? "", module: node?.moduleId ?? "" };
239538
+ return { function: node?.name ?? id, file: node?.file ?? "", module: node?.moduleId ?? "" };
239014
239539
  });
239015
239540
  const exportedAtRisk = fileFns.filter((fn) => fn.isExported).map((fn) => ({
239016
239541
  name: fn.name,
@@ -239614,11 +240139,16 @@ ${staleness}` : "";
239614
240139
  }
239615
240140
  }
239616
240141
  const totalFns = affectedSymbols.reduce((s, f) => s + f.functions.length, 0);
239617
- return { content: [{ type: "text", text: JSON.stringify({
239618
- summary: `${affectedSymbols.length} file(s), ${totalFns} function(s) affected`,
239619
- affectedSymbols,
239620
- warning: staleness
239621
- }, null, 2) }] };
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
+ };
239622
240152
  } catch (err) {
239623
240153
  return { content: [{ type: "text", text: `Git diff failed: ${err.message}` }], isError: true };
239624
240154
  }
@@ -239652,7 +240182,7 @@ ${staleness}` : "";
239652
240182
  lineRange: `${fn.startLine}-${fn.endLine}`
239653
240183
  }));
239654
240184
  const filesImporting = Object.values(lock.files).filter(
239655
- (file) => file.imports?.some((imp) => imp.includes(functionName) || imp.includes(targetFn.file))
240185
+ (file) => file.imports?.some((imp) => imp.names.includes(functionName) || imp.source === targetFn.file)
239656
240186
  );
239657
240187
  const instructions = [
239658
240188
  `1. Rename definition in ${targetFn.file}:${targetFn.startLine}`,
@@ -239715,9 +240245,19 @@ ${staleness}` : "";
239715
240245
  );
239716
240246
  }
239717
240247
  async function loadContractAndLock(projectRoot) {
240248
+ const lockFilePath = path4.join(projectRoot, "mikk.lock.json");
239718
240249
  const cached2 = projectCache.get(projectRoot);
239719
- if (cached2 && Date.now() - cached2.cachedAt < CACHE_TTL_MS) {
239720
- return { contract: cached2.contract, lock: cached2.lock, staleness: cached2.staleness };
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
+ }
239721
240261
  }
239722
240262
  const contractReader = new ContractReader();
239723
240263
  const lockReader = new LockReader();
@@ -239771,7 +240311,7 @@ function buildGraphFromLock(lock) {
239771
240311
  nodes.set(fn.id, {
239772
240312
  id: fn.id,
239773
240313
  type: "function",
239774
- label: fn.name,
240314
+ name: fn.name,
239775
240315
  file: fn.file,
239776
240316
  moduleId: fn.moduleId,
239777
240317
  metadata: {
@@ -239792,7 +240332,7 @@ function buildGraphFromLock(lock) {
239792
240332
  nodes.set(file.path, {
239793
240333
  id: file.path,
239794
240334
  type: "file",
239795
- label: path4.basename(file.path),
240335
+ name: path4.basename(file.path),
239796
240336
  file: file.path,
239797
240337
  moduleId: file.moduleId,
239798
240338
  metadata: {}
@@ -239801,7 +240341,7 @@ function buildGraphFromLock(lock) {
239801
240341
  for (const fn of Object.values(lock.functions)) {
239802
240342
  for (const calleeId of fn.calls) {
239803
240343
  if (!nodes.has(calleeId)) continue;
239804
- const edge = { source: fn.id, target: calleeId, type: "calls" };
240344
+ const edge = { from: fn.id, to: calleeId, type: "calls", confidence: 1 };
239805
240345
  edges.push(edge);
239806
240346
  const out = outEdges.get(fn.id) ?? [];
239807
240347
  out.push(edge);
@@ -239956,7 +240496,7 @@ async function safeRead(filePath) {
239956
240496
  }
239957
240497
 
239958
240498
  // src/server.ts
239959
- var VERSION = true ? "1.9.2" : "0.0.0-dev";
240499
+ var VERSION = true ? "1.10.0" : "0.0.0-dev";
239960
240500
  function createMikkMcpServer(projectRoot) {
239961
240501
  const server2 = new McpServer({
239962
240502
  name: "mikk",