@softerist/heuristic-mcp 3.2.12 → 3.2.13

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.
@@ -3,6 +3,7 @@ import fs from 'fs/promises';
3
3
  import { dotSimilarity } from '../lib/utils.js';
4
4
  import { extractSymbolsFromContent } from '../lib/call-graph.js';
5
5
  import { embedQueryInChildProcess } from '../lib/embed-query-process.js';
6
+ import { normalizePathKey } from '../lib/path-utils.js';
6
7
  import {
7
8
  STAT_CONCURRENCY_LIMIT,
8
9
  SEARCH_BATCH_SIZE,
@@ -27,6 +28,10 @@ function alignQueryVectorDimension(vector, targetDim) {
27
28
  return sliced;
28
29
  }
29
30
 
31
+ function toFileKey(file) {
32
+ return normalizePathKey(file);
33
+ }
34
+
30
35
  export class HybridSearch {
31
36
  constructor(embedder, cache, config) {
32
37
  this.embedder = embedder;
@@ -36,6 +41,13 @@ export class HybridSearch {
36
41
  this._lastAccess = new Map();
37
42
  }
38
43
 
44
+ setFileModTime(file, mtimeMs) {
45
+ const key = toFileKey(file);
46
+ if (!key) return;
47
+ this.fileModTimes.set(key, mtimeMs);
48
+ this._lastAccess.set(key, Date.now());
49
+ }
50
+
39
51
  async getChunkContent(chunkOrIndex) {
40
52
  return await this.cache.getChunkContent(chunkOrIndex);
41
53
  }
@@ -54,20 +66,28 @@ export class HybridSearch {
54
66
  }
55
67
 
56
68
  async populateFileModTimes(files) {
57
- const uniqueFiles = new Set(files);
69
+ const uniqueFilesByKey = new Map();
70
+ for (const file of files) {
71
+ const key = toFileKey(file);
72
+ if (!key) continue;
73
+ if (!uniqueFilesByKey.has(key)) {
74
+ uniqueFilesByKey.set(key, file);
75
+ }
76
+ }
58
77
  const missing = [];
78
+ const now = Date.now();
59
79
 
60
- for (const file of uniqueFiles) {
61
- if (!this.fileModTimes.has(file)) {
80
+ for (const [key, file] of uniqueFilesByKey) {
81
+ if (!this.fileModTimes.has(key)) {
62
82
  const meta = this.cache.getFileMeta(file);
63
83
  if (meta && typeof meta.mtimeMs === 'number') {
64
- this.fileModTimes.set(file, meta.mtimeMs);
65
- this._lastAccess.set(file, Date.now());
84
+ this.fileModTimes.set(key, meta.mtimeMs);
85
+ this._lastAccess.set(key, now);
66
86
  } else {
67
- missing.push(file);
87
+ missing.push({ key, file });
68
88
  }
69
89
  } else {
70
- this._lastAccess.set(file, Date.now());
90
+ this._lastAccess.set(key, now);
71
91
  }
72
92
  }
73
93
 
@@ -79,13 +99,15 @@ export class HybridSearch {
79
99
 
80
100
  const worker = async (startIdx) => {
81
101
  for (let i = startIdx; i < missing.length; i += workerCount) {
82
- const file = missing[i];
102
+ const item = missing[i];
103
+ if (!item) continue;
104
+ const { key, file } = item;
83
105
  try {
84
106
  const stats = await fs.stat(file);
85
- this.fileModTimes.set(file, stats.mtimeMs);
86
- this._lastAccess.set(file, Date.now());
107
+ this.fileModTimes.set(key, stats.mtimeMs);
108
+ this._lastAccess.set(key, Date.now());
87
109
  } catch {
88
- this.fileModTimes.set(file, null);
110
+ this.fileModTimes.set(key, null);
89
111
  }
90
112
  }
91
113
  };
@@ -109,7 +131,10 @@ export class HybridSearch {
109
131
  }
110
132
 
111
133
  clearFileModTime(file) {
112
- this.fileModTimes.delete(file);
134
+ const key = toFileKey(file);
135
+ if (!key) return;
136
+ this.fileModTimes.delete(key);
137
+ this._lastAccess.delete(key);
113
138
  }
114
139
 
115
140
  async search(query, maxResults) {
@@ -259,11 +284,11 @@ export class HybridSearch {
259
284
  await this.populateFileModTimes(candidates.map((chunk) => chunk.file));
260
285
  } else {
261
286
  for (const chunk of candidates) {
262
- if (!this.fileModTimes.has(chunk.file)) {
263
- const meta = this.cache.getFileMeta(chunk.file);
264
- if (meta && typeof meta.mtimeMs === 'number') {
265
- this.fileModTimes.set(chunk.file, meta.mtimeMs);
266
- }
287
+ const chunkKey = toFileKey(chunk.file);
288
+ if (!chunkKey || this.fileModTimes.has(chunkKey)) continue;
289
+ const meta = this.cache.getFileMeta(chunk.file);
290
+ if (meta && typeof meta.mtimeMs === 'number') {
291
+ this.setFileModTime(chunk.file, meta.mtimeMs);
267
292
  }
268
293
  }
269
294
  }
@@ -323,7 +348,8 @@ export class HybridSearch {
323
348
  }
324
349
 
325
350
  if (recencyBoostEnabled) {
326
- const mtime = this.fileModTimes.get(chunkInfo.file);
351
+ const chunkKey = toFileKey(chunkInfo.file);
352
+ const mtime = chunkKey ? this.fileModTimes.get(chunkKey) : undefined;
327
353
  if (typeof mtime === 'number') {
328
354
  const ageMs = now - mtime;
329
355
  const recencyFactor = Math.max(0, 1 - ageMs / recencyDecayMs);
@@ -380,7 +406,9 @@ export class HybridSearch {
380
406
  const relatedFiles = await this.cache.getRelatedFiles(Array.from(symbolsFromTop));
381
407
 
382
408
  for (const chunk of scoredChunks) {
383
- const proximity = relatedFiles.get(chunk.file);
409
+ const chunkKey = toFileKey(chunk.file);
410
+ const proximity =
411
+ relatedFiles.get(chunk.file) ?? (chunkKey ? relatedFiles.get(chunkKey) : undefined);
384
412
  if (proximity) {
385
413
  chunk.score += proximity * this.config.callGraphBoost;
386
414
  }
@@ -10,6 +10,7 @@ import { fileURLToPath } from 'url';
10
10
  import { smartChunk, hashContent } from '../lib/utils.js';
11
11
  import { extractCallData } from '../lib/call-graph.js';
12
12
  import { forceShutdownEmbeddingPool, isEmbeddingPoolActive } from '../lib/embed-query-process.js';
13
+ import { normalizePathKey } from '../lib/path-utils.js';
13
14
 
14
15
  import ignore from 'ignore';
15
16
 
@@ -31,6 +32,10 @@ function normalizePath(value) {
31
32
  return value.split(path.sep).join('/');
32
33
  }
33
34
 
35
+ function toFileKey(value) {
36
+ return normalizePathKey(value);
37
+ }
38
+
34
39
  function globToRegExp(pattern) {
35
40
  let regex = '^';
36
41
  for (let i = 0; i < pattern.length; i += 1) {
@@ -2149,7 +2154,14 @@ export class CodebaseIndexer {
2149
2154
  if (this.server && this.server.hybridSearch && this.server.hybridSearch.fileModTimes) {
2150
2155
  for (const stat of fileStats) {
2151
2156
  if (stat && stat.file && typeof stat.mtimeMs === 'number') {
2152
- this.server.hybridSearch.fileModTimes.set(stat.file, stat.mtimeMs);
2157
+ if (typeof this.server.hybridSearch.setFileModTime === 'function') {
2158
+ this.server.hybridSearch.setFileModTime(stat.file, stat.mtimeMs);
2159
+ } else {
2160
+ const key = toFileKey(stat.file);
2161
+ if (key) {
2162
+ this.server.hybridSearch.fileModTimes.set(key, stat.mtimeMs);
2163
+ }
2164
+ }
2153
2165
  }
2154
2166
  }
2155
2167
  }
@@ -2233,7 +2245,16 @@ export class CodebaseIndexer {
2233
2245
 
2234
2246
  this.sendProgress(5, 100, `Discovered ${files.length} files`);
2235
2247
 
2236
- const currentFilesSet = new Set(files);
2248
+ const currentFileKeySet = new Set();
2249
+ const currentFilePathByKey = new Map();
2250
+ for (const file of files) {
2251
+ const key = toFileKey(file);
2252
+ if (!key) continue;
2253
+ currentFileKeySet.add(key);
2254
+ if (!currentFilePathByKey.has(key)) {
2255
+ currentFilePathByKey.set(key, file);
2256
+ }
2257
+ }
2237
2258
 
2238
2259
  if (!force) {
2239
2260
  const cachedFiles =
@@ -2241,7 +2262,8 @@ export class CodebaseIndexer {
2241
2262
  let prunedCount = 0;
2242
2263
 
2243
2264
  for (const cachedFile of cachedFiles) {
2244
- if (!currentFilesSet.has(cachedFile)) {
2265
+ const cachedKey = toFileKey(cachedFile);
2266
+ if (!cachedKey || !currentFileKeySet.has(cachedKey)) {
2245
2267
  this.cache.removeFileFromStore(cachedFile);
2246
2268
  this.cache.deleteFileHash(cachedFile);
2247
2269
  prunedCount++;
@@ -2254,26 +2276,48 @@ export class CodebaseIndexer {
2254
2276
  }
2255
2277
  }
2256
2278
 
2257
- const prunedCallGraph = this.cache.pruneCallGraphData(currentFilesSet);
2279
+ const prunedCallGraph = this.cache.pruneCallGraphData(currentFileKeySet);
2258
2280
  if (prunedCallGraph > 0 && this.config.verbose) {
2259
2281
  console.info(`[Indexer] Pruned ${prunedCallGraph} call-graph entries`);
2260
2282
  }
2261
2283
  }
2262
2284
 
2263
2285
  const filesToProcess = await this.preFilterFiles(files);
2264
- const filesToProcessSet = new Set(filesToProcess.map((entry) => entry.file));
2265
- const filesToProcessByFile = new Map(filesToProcess.map((entry) => [entry.file, entry]));
2286
+ const filesToProcessKeys = new Set();
2287
+ const filesToProcessByKey = new Map();
2288
+ for (const entry of filesToProcess) {
2289
+ const key = toFileKey(entry?.file);
2290
+ if (!key) continue;
2291
+ filesToProcessKeys.add(key);
2292
+ if (!filesToProcessByKey.has(key)) {
2293
+ filesToProcessByKey.set(key, entry);
2294
+ }
2295
+ }
2266
2296
 
2267
2297
  if (this.config.callGraphEnabled && this.cache.getVectorStore().length > 0) {
2268
- const cachedFiles = new Set(this.cache.getVectorStore().map((c) => c.file));
2269
- const callDataFiles = new Set(this.cache.getFileCallDataKeys());
2298
+ const cachedFileKeys = new Set();
2299
+ for (const chunk of this.cache.getVectorStore()) {
2300
+ const key = toFileKey(chunk?.file);
2301
+ if (key) cachedFileKeys.add(key);
2302
+ }
2303
+ const callDataFiles = new Set();
2304
+ for (const file of this.cache.getFileCallDataKeys()) {
2305
+ const key = toFileKey(file);
2306
+ if (key) callDataFiles.add(key);
2307
+ }
2270
2308
 
2271
2309
  const missingCallData = [];
2272
- for (const file of cachedFiles) {
2273
- if (!callDataFiles.has(file) && currentFilesSet.has(file)) {
2274
- missingCallData.push(file);
2275
- const existing = filesToProcessByFile.get(file);
2276
- if (existing) existing.force = true;
2310
+ for (const key of cachedFileKeys) {
2311
+ if (!callDataFiles.has(key) && currentFileKeySet.has(key)) {
2312
+ const existing = filesToProcessByKey.get(key);
2313
+ if (existing) {
2314
+ existing.force = true;
2315
+ continue;
2316
+ }
2317
+ const concretePath = currentFilePathByKey.get(key);
2318
+ if (concretePath) {
2319
+ missingCallData.push({ key, file: concretePath });
2320
+ }
2277
2321
  }
2278
2322
  }
2279
2323
 
@@ -2285,7 +2329,7 @@ export class CodebaseIndexer {
2285
2329
  for (let i = 0; i < missingCallData.length; i += BATCH_SIZE) {
2286
2330
  const batch = missingCallData.slice(i, i + BATCH_SIZE);
2287
2331
  const results = await Promise.all(
2288
- batch.map(async (file) => {
2332
+ batch.map(async ({ file }) => {
2289
2333
  try {
2290
2334
  const stats = await fs.stat(file);
2291
2335
  if (!stats || typeof stats.isDirectory !== 'function') {
@@ -2304,9 +2348,15 @@ export class CodebaseIndexer {
2304
2348
 
2305
2349
  for (const result of results) {
2306
2350
  if (!result) continue;
2307
- if (!filesToProcessSet.has(result.file)) {
2351
+ const key = toFileKey(result.file);
2352
+ if (!key) continue;
2353
+ if (!filesToProcessKeys.has(key)) {
2308
2354
  filesToProcess.push(result);
2309
- filesToProcessSet.add(result.file);
2355
+ filesToProcessKeys.add(key);
2356
+ filesToProcessByKey.set(key, result);
2357
+ } else {
2358
+ const existing = filesToProcessByKey.get(key);
2359
+ if (existing) existing.force = existing.force || result.force === true;
2310
2360
  }
2311
2361
  }
2312
2362
  }
package/lib/cache.js CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  } from './vector-store-binary.js';
10
10
  import { SqliteVectorStore } from './vector-store-sqlite.js';
11
11
  import { isNonProjectDirectory } from './config.js';
12
+ import { normalizePathKey } from './path-utils.js';
12
13
  import {
13
14
  JSON_WORKER_THRESHOLD_BYTES,
14
15
  ANN_DIMENSION_SAMPLE_SIZE,
@@ -226,6 +227,26 @@ function serializeFileHashEntry(entry) {
226
227
  return normalizeFileHashEntry(entry);
227
228
  }
228
229
 
230
+ function fileKey(filePath) {
231
+ return normalizePathKey(filePath);
232
+ }
233
+
234
+ function numericOrNegInfinity(value) {
235
+ return Number.isFinite(value) ? value : Number.NEGATIVE_INFINITY;
236
+ }
237
+
238
+ function shouldPreferFileHashEntry(candidate, current) {
239
+ const candidateMtime = numericOrNegInfinity(candidate?.mtimeMs);
240
+ const currentMtime = numericOrNegInfinity(current?.mtimeMs);
241
+ if (candidateMtime !== currentMtime) return candidateMtime > currentMtime;
242
+
243
+ const candidateSize = numericOrNegInfinity(candidate?.size);
244
+ const currentSize = numericOrNegInfinity(current?.size);
245
+ if (candidateSize !== currentSize) return candidateSize > currentSize;
246
+
247
+ return false;
248
+ }
249
+
229
250
  function computeAnnCapacity(total, config) {
230
251
  const factor = typeof config.annCapacityFactor === 'number' ? config.annCapacityFactor : 1.2;
231
252
  const extra = Number.isInteger(config.annCapacityExtra) ? config.annCapacityExtra : 1024;
@@ -674,20 +695,29 @@ export class EmbeddingsCache {
674
695
 
675
696
  const hasCacheData = Array.isArray(cacheData);
676
697
  const hasHashData = hashData && typeof hashData === 'object';
698
+ let normalizedHashAliasCollapses = 0;
699
+ let normalizedCallGraphAliasCollapses = 0;
677
700
 
678
701
  if (hasCacheData) {
702
+ const isWin32 = process.platform === 'win32';
679
703
  const allowedExtensions = new Set(
680
- (this.config.fileExtensions || []).map((ext) => `.${ext}`)
704
+ (this.config.fileExtensions || []).map((ext) => `.${String(ext).toLowerCase()}`)
705
+ );
706
+ const allowedFileNames = new Set(
707
+ (this.config.fileNames || []).map((name) =>
708
+ isWin32 ? String(name).toLowerCase() : String(name)
709
+ )
681
710
  );
682
- const allowedFileNames = new Set(this.config.fileNames || []);
683
711
  const applyExtensionFilter = !this.binaryStore;
684
712
  const shouldKeepFile = (filePath) => {
685
- const ext = path.extname(filePath);
713
+ const ext = path.extname(filePath).toLowerCase();
686
714
  if (allowedExtensions.has(ext)) return true;
687
- return allowedFileNames.has(path.basename(filePath));
715
+ const baseName = path.basename(filePath);
716
+ const normalizedBaseName = isWin32 ? baseName.toLowerCase() : baseName;
717
+ return allowedFileNames.has(normalizedBaseName);
688
718
  };
689
719
 
690
- const rawHashes = hasHashData ? new Map(Object.entries(hashData)) : new Map();
720
+ const rawHashes = hasHashData ? Object.entries(hashData) : [];
691
721
  this.vectorStore = [];
692
722
  this.fileHashes.clear();
693
723
 
@@ -707,8 +737,17 @@ export class EmbeddingsCache {
707
737
  for (const [file, entry] of rawHashes) {
708
738
  if (!applyExtensionFilter || shouldKeepFile(file)) {
709
739
  const normalized = normalizeFileHashEntry(entry);
710
- if (normalized) {
711
- this.fileHashes.set(file, normalized);
740
+ const key = fileKey(file);
741
+ if (normalized && key) {
742
+ const existing = this.fileHashes.get(key);
743
+ if (existing) {
744
+ normalizedHashAliasCollapses += 1;
745
+ if (shouldPreferFileHashEntry(normalized, existing)) {
746
+ this.fileHashes.set(key, normalized);
747
+ }
748
+ } else {
749
+ this.fileHashes.set(key, normalized);
750
+ }
712
751
  }
713
752
  }
714
753
  }
@@ -739,11 +778,31 @@ export class EmbeddingsCache {
739
778
  try {
740
779
  const callGraphData = await fs.readFile(callGraphFile, 'utf8');
741
780
  const parsed = JSON.parse(callGraphData);
742
- this.fileCallData = new Map(Object.entries(parsed));
781
+ const normalizedCallData = new Map();
782
+ if (parsed && typeof parsed === 'object') {
783
+ for (const [file, data] of Object.entries(parsed)) {
784
+ const key = fileKey(file);
785
+ if (!key) continue;
786
+ if (normalizedCallData.has(key)) {
787
+ normalizedCallGraphAliasCollapses += 1;
788
+ }
789
+ normalizedCallData.set(key, data);
790
+ }
791
+ }
792
+ this.fileCallData = normalizedCallData;
743
793
  if (this.config.verbose) {
744
794
  console.info(`[Cache] Loaded call-graph data for ${this.fileCallData.size} files`);
745
795
  }
746
796
  } catch {}
797
+
798
+ if (
799
+ this.config.verbose &&
800
+ (normalizedHashAliasCollapses > 0 || normalizedCallGraphAliasCollapses > 0)
801
+ ) {
802
+ console.info(
803
+ `[Cache] Normalized path-key aliases on load (file-hashes=${normalizedHashAliasCollapses}, call-graph=${normalizedCallGraphAliasCollapses})`
804
+ );
805
+ }
747
806
  } catch (error) {
748
807
  console.warn('[Cache] Failed to load cache:', error.message);
749
808
  this.clearInMemoryState();
@@ -943,8 +1002,9 @@ export class EmbeddingsCache {
943
1002
  const hashEntries = {};
944
1003
  for (const [file, entry] of this.fileHashes) {
945
1004
  const serialized = serializeFileHashEntry(entry);
946
- if (serialized) {
947
- hashEntries[file] = serialized;
1005
+ const key = fileKey(file);
1006
+ if (serialized && key) {
1007
+ hashEntries[key] = serialized;
948
1008
  }
949
1009
  }
950
1010
 
@@ -955,9 +1015,15 @@ export class EmbeddingsCache {
955
1015
 
956
1016
  const callGraphFile = path.join(this.config.cacheDirectory, CALL_GRAPH_FILE);
957
1017
  if (this.fileCallData.size > 0) {
1018
+ const callGraphEntries = {};
1019
+ for (const [file, data] of this.fileCallData) {
1020
+ const key = fileKey(file);
1021
+ if (!key) continue;
1022
+ callGraphEntries[key] = data;
1023
+ }
958
1024
  await fs.writeFile(
959
1025
  callGraphFile,
960
- JSON.stringify(Object.fromEntries(this.fileCallData), null, 2)
1026
+ JSON.stringify(callGraphEntries, null, 2)
961
1027
  );
962
1028
  } else {
963
1029
  await fs.rm(callGraphFile, { force: true });
@@ -1071,7 +1137,9 @@ export class EmbeddingsCache {
1071
1137
  }
1072
1138
 
1073
1139
  getFileHash(file) {
1074
- const entry = this.fileHashes.get(file);
1140
+ const key = fileKey(file);
1141
+ if (!key) return undefined;
1142
+ const entry = this.fileHashes.get(key);
1075
1143
  if (typeof entry === 'string') return entry;
1076
1144
  return entry?.hash;
1077
1145
  }
@@ -1095,23 +1163,31 @@ export class EmbeddingsCache {
1095
1163
  if (!iterator) return;
1096
1164
  for (const [file, entry] of iterator) {
1097
1165
  const normalized = normalizeFileHashEntry(entry);
1098
- if (normalized) {
1099
- this.fileHashes.set(file, normalized);
1166
+ const key = fileKey(file);
1167
+ if (normalized && key) {
1168
+ const existing = this.fileHashes.get(key);
1169
+ if (!existing || shouldPreferFileHashEntry(normalized, existing)) {
1170
+ this.fileHashes.set(key, normalized);
1171
+ }
1100
1172
  }
1101
1173
  }
1102
1174
  }
1103
1175
 
1104
1176
  setFileHash(file, hash, meta = null) {
1177
+ const key = fileKey(file);
1178
+ if (!key) return;
1105
1179
  const entry = { hash };
1106
1180
  if (meta && typeof meta === 'object') {
1107
1181
  if (Number.isFinite(meta.mtimeMs)) entry.mtimeMs = meta.mtimeMs;
1108
1182
  if (Number.isFinite(meta.size)) entry.size = meta.size;
1109
1183
  }
1110
- this.fileHashes.set(file, entry);
1184
+ this.fileHashes.set(key, entry);
1111
1185
  }
1112
1186
 
1113
1187
  getFileMeta(file) {
1114
- const entry = this.fileHashes.get(file);
1188
+ const key = fileKey(file);
1189
+ if (!key) return null;
1190
+ const entry = this.fileHashes.get(key);
1115
1191
  if (!entry) return null;
1116
1192
  if (typeof entry === 'string') return { hash: entry };
1117
1193
  return entry;
@@ -1194,16 +1270,20 @@ export class EmbeddingsCache {
1194
1270
  }
1195
1271
 
1196
1272
  deleteFileHash(file) {
1197
- this.fileHashes.delete(file);
1273
+ const key = fileKey(file);
1274
+ if (!key) return;
1275
+ this.fileHashes.delete(key);
1198
1276
  }
1199
1277
 
1200
1278
  async removeFileFromStore(file) {
1201
1279
  if (!Array.isArray(this.vectorStore)) return;
1280
+ const targetKey = fileKey(file);
1281
+ if (!targetKey) return;
1202
1282
 
1203
1283
  let w = 0;
1204
1284
  for (let r = 0; r < this.vectorStore.length; r++) {
1205
1285
  const chunk = this.vectorStore[r];
1206
- if (chunk.file !== file) {
1286
+ if (fileKey(chunk.file) !== targetKey) {
1207
1287
  chunk._index = w;
1208
1288
  this.vectorStore[w++] = chunk;
1209
1289
  }
@@ -1213,7 +1293,7 @@ export class EmbeddingsCache {
1213
1293
  this.invalidateAnnIndex();
1214
1294
  this.removeFileCallData(file);
1215
1295
 
1216
- this.fileHashes.delete(file);
1296
+ this.deleteFileHash(file);
1217
1297
  }
1218
1298
 
1219
1299
  addToStore(chunk) {
@@ -1627,10 +1707,15 @@ export class EmbeddingsCache {
1627
1707
 
1628
1708
  pruneCallGraphData(validFiles) {
1629
1709
  if (!validFiles || this.fileCallData.size === 0) return 0;
1710
+ const validKeys = new Set();
1711
+ for (const file of validFiles) {
1712
+ const key = fileKey(file);
1713
+ if (key) validKeys.add(key);
1714
+ }
1630
1715
 
1631
1716
  let pruned = 0;
1632
1717
  for (const file of Array.from(this.fileCallData.keys())) {
1633
- if (!validFiles.has(file)) {
1718
+ if (!validKeys.has(fileKey(file))) {
1634
1719
  this.fileCallData.delete(file);
1635
1720
  pruned++;
1636
1721
  }
@@ -1641,11 +1726,15 @@ export class EmbeddingsCache {
1641
1726
  }
1642
1727
 
1643
1728
  getFileCallData(file) {
1644
- return this.fileCallData.get(file);
1729
+ const key = fileKey(file);
1730
+ if (!key) return undefined;
1731
+ return this.fileCallData.get(key);
1645
1732
  }
1646
1733
 
1647
1734
  hasFileCallData(file) {
1648
- return this.fileCallData.has(file);
1735
+ const key = fileKey(file);
1736
+ if (!key) return false;
1737
+ return this.fileCallData.has(key);
1649
1738
  }
1650
1739
 
1651
1740
  getFileCallDataKeys() {
@@ -1657,21 +1746,21 @@ export class EmbeddingsCache {
1657
1746
  }
1658
1747
 
1659
1748
  setFileCallData(file, data) {
1660
- this.fileCallData.set(file, data);
1749
+ const key = fileKey(file);
1750
+ if (!key) return;
1751
+ this.fileCallData.set(key, data);
1661
1752
  this.callGraph = null;
1662
1753
  }
1663
1754
 
1664
1755
  setFileCallDataEntries(entries) {
1665
- if (entries instanceof Map) {
1666
- this.fileCallData = entries;
1667
- } else {
1668
- this.fileCallData.clear();
1669
- if (entries && typeof entries === 'object') {
1670
- for (const [file, data] of Object.entries(entries)) {
1671
- this.fileCallData.set(file, data);
1672
- }
1673
- }
1674
- }
1756
+ const normalized = new Map();
1757
+ const iterator = entries instanceof Map ? entries.entries() : Object.entries(entries || {});
1758
+ for (const [file, data] of iterator) {
1759
+ const key = fileKey(file);
1760
+ if (!key) continue;
1761
+ normalized.set(key, data);
1762
+ }
1763
+ this.fileCallData = normalized;
1675
1764
  this.callGraph = null;
1676
1765
  }
1677
1766
 
@@ -1681,7 +1770,9 @@ export class EmbeddingsCache {
1681
1770
  }
1682
1771
 
1683
1772
  removeFileCallData(file) {
1684
- this.fileCallData.delete(file);
1773
+ const key = fileKey(file);
1774
+ if (!key) return;
1775
+ this.fileCallData.delete(key);
1685
1776
  this.callGraph = null;
1686
1777
  }
1687
1778
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softerist/heuristic-mcp",
3
- "version": "3.2.12",
3
+ "version": "3.2.13",
4
4
  "description": "An enhanced MCP server providing intelligent semantic code search with find-similar-code, recency ranking, and improved chunking. Fork of smart-coding-mcp.",
5
5
  "type": "module",
6
6
  "main": "index.js",