@softerist/heuristic-mcp 3.2.7 → 3.2.9

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/lib/config.js CHANGED
@@ -4,7 +4,7 @@ import os from 'os';
4
4
  import { fileURLToPath } from 'url';
5
5
  import { ProjectDetector } from './project-detector.js';
6
6
  import { parseJsonc } from './settings-editor.js';
7
- import { getLegacyWorkspaceCachePath, getWorkspaceCachePath } from './workspace-cache-key.js';
7
+ import { getWorkspaceCachePathCandidates } from './workspace-cache-key.js';
8
8
  import {
9
9
  EMBEDDING_PROCESS_DEFAULT_GC_MAX_REQUESTS_WITHOUT_COLLECTION,
10
10
  EMBEDDING_PROCESS_DEFAULT_GC_MIN_INTERVAL_MS,
@@ -37,6 +37,7 @@ const DEFAULT_INDEXING_CONFIG = {
37
37
  maxResults: 5, // Maximum number of semantic search results to return
38
38
  watchFiles: true, // Enable file system watcher to re-index changed files in real-time
39
39
  indexCheckpointIntervalMs: 5000, // Periodic cache checkpoint during full indexing (0 disables)
40
+ shutdownIndexWaitMs: 3000, // Max wait during shutdown for cooperative index checkpointing
40
41
  };
41
42
 
42
43
  const DEFAULT_LOGGING_CONFIG = {
@@ -389,6 +390,7 @@ const DEFAULT_CONFIG = {
389
390
  },
390
391
  watchFiles: DEFAULT_INDEXING_CONFIG.watchFiles,
391
392
  indexCheckpointIntervalMs: DEFAULT_INDEXING_CONFIG.indexCheckpointIntervalMs,
393
+ shutdownIndexWaitMs: DEFAULT_INDEXING_CONFIG.shutdownIndexWaitMs,
392
394
  verbose: DEFAULT_LOGGING_CONFIG.verbose,
393
395
  memoryLogIntervalMs: DEFAULT_LOGGING_CONFIG.memoryLogIntervalMs,
394
396
  saveReaderWaitTimeoutMs: DEFAULT_CACHE_CONFIG.saveReaderWaitTimeoutMs,
@@ -880,23 +882,36 @@ export async function loadConfig(workspaceDir = null) {
880
882
  : path.join(baseDir, userConfig.cacheDirectory);
881
883
  } else {
882
884
  // Use global cache directory to prevent cluttering project root.
883
- // Workspace path is normalized for hashing so Windows drive-letter case
884
- // differences (e.g. F:\ vs f:\) resolve to the same cache id.
885
+ // Workspace path is normalized for hashing so Windows case variations map
886
+ // to one canonical cache key while preserving fallback compatibility.
885
887
  const globalCacheRoot = getGlobalCacheDir();
886
- const normalizedCachePath = getWorkspaceCachePath(config.searchDirectory, globalCacheRoot);
887
- const legacyCachePath = getLegacyWorkspaceCachePath(config.searchDirectory, globalCacheRoot);
888
-
889
- if (
890
- normalizedCachePath !== legacyCachePath &&
891
- !(await pathExists(normalizedCachePath)) &&
892
- (await pathExists(legacyCachePath))
893
- ) {
894
- config.cacheDirectory = legacyCachePath;
895
- console.info(
896
- `[Config] Using legacy cache path for compatibility: ${path.basename(legacyCachePath)}`
897
- );
898
- } else {
899
- config.cacheDirectory = normalizedCachePath;
888
+ const cachePaths = getWorkspaceCachePathCandidates(config.searchDirectory, globalCacheRoot);
889
+ let cacheResolutionMode = 'canonical';
890
+
891
+ config.cacheDirectory = cachePaths.canonical;
892
+ if (!(await pathExists(cachePaths.canonical))) {
893
+ if (
894
+ cachePaths.compatDriveCase !== cachePaths.canonical &&
895
+ (await pathExists(cachePaths.compatDriveCase))
896
+ ) {
897
+ config.cacheDirectory = cachePaths.compatDriveCase;
898
+ cacheResolutionMode = 'compat-drivecase';
899
+ console.info(
900
+ `[Config] Using drive-case compatible cache path: ${path.basename(cachePaths.compatDriveCase)}`
901
+ );
902
+ } else if (
903
+ cachePaths.legacy !== cachePaths.canonical &&
904
+ (await pathExists(cachePaths.legacy))
905
+ ) {
906
+ config.cacheDirectory = cachePaths.legacy;
907
+ cacheResolutionMode = 'legacy';
908
+ console.info(
909
+ `[Config] Using legacy cache path for compatibility: ${path.basename(cachePaths.legacy)}`
910
+ );
911
+ }
912
+ }
913
+ if (config.verbose || cacheResolutionMode !== 'canonical') {
914
+ console.info(`[Config] Cache resolution mode: ${cacheResolutionMode}`);
900
915
  }
901
916
 
902
917
  // Support legacy .smart-coding-cache if it already exists in the project root
@@ -1085,6 +1100,17 @@ export async function loadConfig(workspaceDir = null) {
1085
1100
  }
1086
1101
  }
1087
1102
 
1103
+ if (process.env.SMART_CODING_SHUTDOWN_INDEX_WAIT_MS !== undefined) {
1104
+ const value = parseInt(process.env.SMART_CODING_SHUTDOWN_INDEX_WAIT_MS, 10);
1105
+ if (!isNaN(value) && value >= 0) {
1106
+ config.shutdownIndexWaitMs = value;
1107
+ } else {
1108
+ console.warn(
1109
+ `[Config] Invalid SMART_CODING_SHUTDOWN_INDEX_WAIT_MS: ${process.env.SMART_CODING_SHUTDOWN_INDEX_WAIT_MS}, using default`
1110
+ );
1111
+ }
1112
+ }
1113
+
1088
1114
  if (process.env.SMART_CODING_SEMANTIC_WEIGHT !== undefined) {
1089
1115
  const value = parseFloat(process.env.SMART_CODING_SEMANTIC_WEIGHT);
1090
1116
  if (!isNaN(value) && value >= 0 && value <= 1) {
@@ -7,13 +7,22 @@ export function normalizeWorkspacePathForCacheKey(workspacePath) {
7
7
  return resolved;
8
8
  }
9
9
 
10
- // Windows paths are case-insensitive for drive letters; normalize only the
11
- // drive prefix so F:\repo and f:\repo map to the same cache key while
12
- // preserving existing segment casing for backward compatibility.
10
+ // Windows paths are case-insensitive. Normalize the whole path so casing
11
+ // changes (including folder segments) map to one cache key.
12
+ return resolved.toLowerCase();
13
+ }
14
+
15
+ export function normalizeWorkspacePathForCompatDriveLetterKey(workspacePath) {
16
+ const resolved = path.resolve(workspacePath);
17
+ if (process.platform !== 'win32') {
18
+ return resolved;
19
+ }
20
+
21
+ // Compatibility behavior used by older versions: normalize drive letter
22
+ // casing only.
13
23
  if (/^[A-Za-z]:/.test(resolved)) {
14
24
  return `${resolved[0].toLowerCase()}${resolved.slice(1)}`;
15
25
  }
16
-
17
26
  return resolved;
18
27
  }
19
28
 
@@ -22,6 +31,11 @@ export function getWorkspaceCacheKey(workspacePath) {
22
31
  return crypto.createHash('md5').update(normalized).digest('hex').slice(0, 12);
23
32
  }
24
33
 
34
+ export function getDriveLetterCompatWorkspaceCacheKey(workspacePath) {
35
+ const normalized = normalizeWorkspacePathForCompatDriveLetterKey(workspacePath);
36
+ return crypto.createHash('md5').update(normalized).digest('hex').slice(0, 12);
37
+ }
38
+
25
39
  export function getLegacyWorkspaceCacheKey(workspacePath) {
26
40
  const resolved = path.resolve(workspacePath);
27
41
  return crypto.createHash('md5').update(resolved).digest('hex').slice(0, 12);
@@ -31,6 +45,22 @@ export function getWorkspaceCachePath(workspacePath, globalCacheRoot) {
31
45
  return path.join(globalCacheRoot, 'heuristic-mcp', getWorkspaceCacheKey(workspacePath));
32
46
  }
33
47
 
48
+ export function getDriveLetterCompatWorkspaceCachePath(workspacePath, globalCacheRoot) {
49
+ return path.join(
50
+ globalCacheRoot,
51
+ 'heuristic-mcp',
52
+ getDriveLetterCompatWorkspaceCacheKey(workspacePath)
53
+ );
54
+ }
55
+
34
56
  export function getLegacyWorkspaceCachePath(workspacePath, globalCacheRoot) {
35
57
  return path.join(globalCacheRoot, 'heuristic-mcp', getLegacyWorkspaceCacheKey(workspacePath));
36
58
  }
59
+
60
+ export function getWorkspaceCachePathCandidates(workspacePath, globalCacheRoot) {
61
+ return {
62
+ canonical: getWorkspaceCachePath(workspacePath, globalCacheRoot),
63
+ compatDriveCase: getDriveLetterCompatWorkspaceCachePath(workspacePath, globalCacheRoot),
64
+ legacy: getLegacyWorkspaceCachePath(workspacePath, globalCacheRoot),
65
+ };
66
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softerist/heuristic-mcp",
3
- "version": "3.2.7",
3
+ "version": "3.2.9",
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",
package/search-configs.js DELETED
@@ -1,36 +0,0 @@
1
- import { loadConfig } from './lib/config.js';
2
- import { EmbeddingsCache } from './lib/cache.js';
3
- import { HybridSearch } from './features/hybrid-search.js';
4
- import { pipeline, env } from '@huggingface/transformers';
5
-
6
- // Force same thread config as server
7
- if (env?.backends?.onnx) {
8
- env.backends.onnx.numThreads = 2;
9
- if (env.backends.onnx.wasm) {
10
- env.backends.onnx.wasm.numThreads = 2;
11
- }
12
- }
13
-
14
- async function searchConfigs() {
15
- const config = await loadConfig(process.cwd());
16
- const cache = new EmbeddingsCache(config);
17
- await cache.load();
18
-
19
- const embedder = async (text) => {
20
- const pipe = await pipeline('feature-extraction', config.embeddingModel, {
21
- session_options: { numThreads: 2 },
22
- dtype: 'fp32',
23
- });
24
- return pipe(text, { pooling: 'mean', normalize: true });
25
- };
26
-
27
- const searcher = new HybridSearch(embedder, cache, config);
28
- const { results } = await searcher.search('configuration files, config, settings');
29
-
30
- console.info(JSON.stringify(results, null, 2));
31
- }
32
-
33
- searchConfigs().catch((err) => {
34
- console.error(err);
35
- process.exit(1);
36
- });