@softerist/heuristic-mcp 3.0.17 → 3.1.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/config.jsonc +23 -6
- package/features/ann-config.js +7 -14
- package/features/clear-cache.js +3 -3
- package/features/find-similar-code.js +17 -22
- package/features/hybrid-search.js +59 -67
- package/features/index-codebase.js +305 -268
- package/features/lifecycle.js +370 -176
- package/features/package-version.js +15 -26
- package/features/register.js +75 -57
- package/features/resources.js +21 -47
- package/features/set-workspace.js +31 -43
- package/index.js +818 -172
- package/lib/cache-utils.js +95 -99
- package/lib/cache.js +121 -166
- package/lib/cli.js +246 -238
- package/lib/config.js +232 -62
- package/lib/constants.js +22 -2
- package/lib/embed-query-process.js +13 -29
- package/lib/embedding-process.js +29 -19
- package/lib/embedding-worker.js +166 -149
- package/lib/ignore-patterns.js +39 -39
- package/lib/json-writer.js +7 -34
- package/lib/logging.js +11 -42
- package/lib/onnx-backend.js +4 -4
- package/lib/path-utils.js +4 -21
- package/lib/project-detector.js +3 -3
- package/lib/server-lifecycle.js +109 -15
- package/lib/settings-editor.js +25 -18
- package/lib/slice-normalize.js +6 -16
- package/lib/tokenizer.js +56 -109
- package/lib/utils.js +62 -81
- package/lib/vector-store-binary.js +7 -7
- package/lib/vector-store-sqlite.js +35 -67
- package/lib/workspace-cache-key.js +36 -0
- package/lib/workspace-env.js +55 -14
- package/package.json +86 -86
package/lib/config.js
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import os from 'os';
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { ProjectDetector } from './project-detector.js';
|
|
6
|
+
import { parseJsonc } from './settings-editor.js';
|
|
7
|
+
import {
|
|
8
|
+
getLegacyWorkspaceCachePath,
|
|
9
|
+
getWorkspaceCachePath,
|
|
10
|
+
} from './workspace-cache-key.js';
|
|
8
11
|
import {
|
|
9
12
|
EMBEDDING_PROCESS_DEFAULT_GC_MAX_REQUESTS_WITHOUT_COLLECTION,
|
|
10
13
|
EMBEDDING_PROCESS_DEFAULT_GC_MIN_INTERVAL_MS,
|
|
11
14
|
EMBEDDING_PROCESS_DEFAULT_GC_RSS_THRESHOLD_MB,
|
|
12
15
|
} from './constants.js';
|
|
13
|
-
import { getWorkspaceEnvKeys } from './workspace-env.js';
|
|
16
|
+
import { getWorkspaceEnvDiagnosticKeys, getWorkspaceEnvKeys } from './workspace-env.js';
|
|
14
17
|
|
|
15
18
|
const DEFAULT_MEMORY_CLEANUP_CONFIG = {
|
|
16
19
|
enableExplicitGc: true, // Require --expose-gc for more aggressive memory cleanup
|
|
@@ -36,6 +39,7 @@ const DEFAULT_INDEXING_CONFIG = {
|
|
|
36
39
|
prefilterContentMaxBytes: 512 * 1024, // 512KB - cache content during prefilter to avoid double reads
|
|
37
40
|
maxResults: 5, // Maximum number of semantic search results to return
|
|
38
41
|
watchFiles: true, // Enable file system watcher to re-index changed files in real-time
|
|
42
|
+
indexCheckpointIntervalMs: 5000, // Periodic cache checkpoint during full indexing (0 disables)
|
|
39
43
|
};
|
|
40
44
|
|
|
41
45
|
const DEFAULT_LOGGING_CONFIG = {
|
|
@@ -43,10 +47,11 @@ const DEFAULT_LOGGING_CONFIG = {
|
|
|
43
47
|
memoryLogIntervalMs: 5000, // Verbose memory log cadence during indexing (ms)
|
|
44
48
|
};
|
|
45
49
|
|
|
46
|
-
const DEFAULT_CACHE_CONFIG = {
|
|
47
|
-
enableCache: true, // Whether to persist and reload embeddings between sessions
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
const DEFAULT_CACHE_CONFIG = {
|
|
51
|
+
enableCache: true, // Whether to persist and reload embeddings between sessions
|
|
52
|
+
allowSystemWorkspaceCache: false, // Safety: block cache writes for IDE/system install directories unless explicitly overridden
|
|
53
|
+
saveReaderWaitTimeoutMs: 5000, // Max wait for active reads before saving binary cache
|
|
54
|
+
cacheVectorAssumeFinite: true, // Assume vectors are finite (skip validation)
|
|
50
55
|
cacheVectorFloatDigits: null, // Decimal precision for cached vectors (null = default)
|
|
51
56
|
cacheWriteHighWaterMark: 262144, // Write stream highWaterMark for cache files
|
|
52
57
|
cacheVectorFlushChars: 262144, // Flush threshold (chars) for JSON writer
|
|
@@ -56,13 +61,14 @@ const DEFAULT_CACHE_CONFIG = {
|
|
|
56
61
|
cacheVectorJoinChunkSize: 2048, // Chunk size for JSON join optimization
|
|
57
62
|
};
|
|
58
63
|
|
|
59
|
-
const DEFAULT_WORKER_CONFIG = {
|
|
60
|
-
workerThreads: 'auto', // 0 = run in main thread (no workers), "auto" = CPU cores - 1, or set a number
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
const DEFAULT_WORKER_CONFIG = {
|
|
65
|
+
workerThreads: 'auto', // 0 = run in main thread (no workers), "auto" = CPU cores - 1, or set a number
|
|
66
|
+
workerDisableHeavyModelOnWindows: true, // Safety guard: disable worker-pool mode for heavy models on Windows unless explicitly opted in
|
|
67
|
+
workerBatchTimeoutMs: 120000, // Timeout per worker batch before fallback (ms)
|
|
68
|
+
workerFailureThreshold: 1, // Open circuit after N worker failures
|
|
69
|
+
workerFailureCooldownMs: 10 * 60 * 1000, // Cooldown before retrying workers
|
|
70
|
+
workerMaxChunksPerBatch: 100, // Cap chunks per worker batch to reduce hang risk
|
|
71
|
+
allowSingleThreadFallback: false, // Allow fallback to main-thread embeddings if workers fail
|
|
66
72
|
failFastEmbeddingErrors: false, // Abort worker embedding batch after repeated consecutive embed failures
|
|
67
73
|
};
|
|
68
74
|
|
|
@@ -126,9 +132,11 @@ const SEARCH_KEYS = Object.freeze(Object.keys(DEFAULT_SEARCH_CONFIG));
|
|
|
126
132
|
const CALL_GRAPH_KEYS = Object.freeze(Object.keys(DEFAULT_CALL_GRAPH_CONFIG));
|
|
127
133
|
const ANN_KEYS = Object.freeze(Object.keys(DEFAULT_ANN_CONFIG));
|
|
128
134
|
|
|
129
|
-
const DEFAULT_CONFIG = {
|
|
130
|
-
searchDirectory: '.',
|
|
131
|
-
|
|
135
|
+
const DEFAULT_CONFIG = {
|
|
136
|
+
searchDirectory: '.',
|
|
137
|
+
autoStopOtherServersOnStartup: true, // Ensure newly started workspace server can replace stale older heuristic-mcp instances
|
|
138
|
+
requireTrustedWorkspaceSignalForTools: false, // Fail workspace-bound tools when no current roots/env workspace signal is available
|
|
139
|
+
fileExtensions: [
|
|
132
140
|
// JavaScript/TypeScript
|
|
133
141
|
'js',
|
|
134
142
|
'ts',
|
|
@@ -383,14 +391,16 @@ const DEFAULT_CONFIG = {
|
|
|
383
391
|
removeDuplicates: true, // Remove duplicate workspace caches
|
|
384
392
|
},
|
|
385
393
|
watchFiles: DEFAULT_INDEXING_CONFIG.watchFiles,
|
|
394
|
+
indexCheckpointIntervalMs: DEFAULT_INDEXING_CONFIG.indexCheckpointIntervalMs,
|
|
386
395
|
verbose: DEFAULT_LOGGING_CONFIG.verbose,
|
|
387
396
|
memoryLogIntervalMs: DEFAULT_LOGGING_CONFIG.memoryLogIntervalMs,
|
|
388
397
|
saveReaderWaitTimeoutMs: DEFAULT_CACHE_CONFIG.saveReaderWaitTimeoutMs,
|
|
389
|
-
workerThreads: DEFAULT_WORKER_CONFIG.workerThreads,
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
398
|
+
workerThreads: DEFAULT_WORKER_CONFIG.workerThreads,
|
|
399
|
+
workerDisableHeavyModelOnWindows: DEFAULT_WORKER_CONFIG.workerDisableHeavyModelOnWindows,
|
|
400
|
+
workerBatchTimeoutMs: DEFAULT_WORKER_CONFIG.workerBatchTimeoutMs,
|
|
401
|
+
workerFailureThreshold: DEFAULT_WORKER_CONFIG.workerFailureThreshold,
|
|
402
|
+
workerFailureCooldownMs: DEFAULT_WORKER_CONFIG.workerFailureCooldownMs,
|
|
403
|
+
workerMaxChunksPerBatch: DEFAULT_WORKER_CONFIG.workerMaxChunksPerBatch,
|
|
394
404
|
allowSingleThreadFallback: DEFAULT_WORKER_CONFIG.allowSingleThreadFallback,
|
|
395
405
|
failFastEmbeddingErrors: DEFAULT_WORKER_CONFIG.failFastEmbeddingErrors,
|
|
396
406
|
embeddingProcessPerBatch: DEFAULT_EMBEDDING_CONFIG.embeddingProcessPerBatch,
|
|
@@ -630,6 +640,32 @@ async function resolveWorkspaceCandidate(rawValue) {
|
|
|
630
640
|
return candidate;
|
|
631
641
|
}
|
|
632
642
|
|
|
643
|
+
function formatWorkspaceProbeValue(rawValue, maxLength = 120) {
|
|
644
|
+
const value = String(rawValue || '').trim();
|
|
645
|
+
if (!value) return '';
|
|
646
|
+
if (value.length <= maxLength) return value;
|
|
647
|
+
return `${value.slice(0, maxLength - 3)}...`;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function logWorkspaceEnvProbe(entries) {
|
|
651
|
+
if (!entries || entries.length === 0) {
|
|
652
|
+
console.info('[Config] Workspace env probe: no workspace-like environment variables were set.');
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const preview = entries.slice(0, 10).map((entry) => {
|
|
657
|
+
const scope = entry.priority ? 'priority' : 'diagnostic';
|
|
658
|
+
const status = entry.ignoredNonProject
|
|
659
|
+
? `ignored-non-project:${entry.resolvedPath}`
|
|
660
|
+
: entry.resolvedPath
|
|
661
|
+
? `valid:${entry.resolvedPath}`
|
|
662
|
+
: `invalid:${entry.value}`;
|
|
663
|
+
return `${entry.key}[${scope}]=${status}`;
|
|
664
|
+
});
|
|
665
|
+
const suffix = entries.length > 10 ? ` (+${entries.length - 10} more)` : '';
|
|
666
|
+
console.info(`[Config] Workspace env probe: ${preview.join('; ')}${suffix}`);
|
|
667
|
+
}
|
|
668
|
+
|
|
633
669
|
function logWorkspaceResolution(resolution) {
|
|
634
670
|
if (!resolution || !resolution.path) return;
|
|
635
671
|
|
|
@@ -659,6 +695,24 @@ function logWorkspaceResolution(resolution) {
|
|
|
659
695
|
console.info(`[Config] Workspace resolution: process.cwd() -> ${resolution.path}`);
|
|
660
696
|
}
|
|
661
697
|
|
|
698
|
+
/**
|
|
699
|
+
* Heuristic check: is this path likely an IDE install or system directory
|
|
700
|
+
* rather than a user workspace? Used to warn when workspace resolution
|
|
701
|
+
* falls back to process.cwd() and lands in a non-project location.
|
|
702
|
+
*/
|
|
703
|
+
export function isNonProjectDirectory(dir) {
|
|
704
|
+
const normalized = dir.replace(/\\/g, '/').toLowerCase();
|
|
705
|
+
const markers = [
|
|
706
|
+
'/program files/',
|
|
707
|
+
'/program files (x86)/',
|
|
708
|
+
'/appdata/local/programs/',
|
|
709
|
+
'/appdata/roaming/',
|
|
710
|
+
'/applications/',
|
|
711
|
+
'/contents/resources/',
|
|
712
|
+
];
|
|
713
|
+
return markers.some((m) => normalized.includes(m));
|
|
714
|
+
}
|
|
715
|
+
|
|
662
716
|
async function resolveWorkspaceDir(workspaceDir) {
|
|
663
717
|
if (workspaceDir) {
|
|
664
718
|
return {
|
|
@@ -673,42 +727,90 @@ async function resolveWorkspaceDir(workspaceDir) {
|
|
|
673
727
|
};
|
|
674
728
|
}
|
|
675
729
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
730
|
+
const prioritizedEnvKeys = getWorkspaceEnvKeys();
|
|
731
|
+
const prioritizedEnvKeySet = new Set(prioritizedEnvKeys);
|
|
732
|
+
const workspaceEnvProbe = [];
|
|
733
|
+
|
|
734
|
+
for (const key of prioritizedEnvKeys) {
|
|
735
|
+
const rawValue = process.env[key];
|
|
736
|
+
if (rawValue === undefined || rawValue === null || String(rawValue).trim() === '') {
|
|
737
|
+
continue;
|
|
738
|
+
}
|
|
739
|
+
const value = formatWorkspaceProbeValue(rawValue);
|
|
740
|
+
const candidate = await resolveWorkspaceCandidate(rawValue);
|
|
741
|
+
workspaceEnvProbe.push({
|
|
742
|
+
key,
|
|
743
|
+
value,
|
|
744
|
+
resolvedPath: candidate,
|
|
745
|
+
priority: true,
|
|
746
|
+
ignoredNonProject: Boolean(candidate && isNonProjectDirectory(candidate)),
|
|
747
|
+
});
|
|
748
|
+
if (candidate && !isNonProjectDirectory(candidate)) {
|
|
749
|
+
return {
|
|
750
|
+
path: candidate,
|
|
751
|
+
source: 'env',
|
|
752
|
+
envKey: key,
|
|
753
|
+
workspaceEnvProbe,
|
|
683
754
|
};
|
|
684
755
|
}
|
|
685
756
|
}
|
|
686
757
|
|
|
758
|
+
for (const key of getWorkspaceEnvDiagnosticKeys()) {
|
|
759
|
+
if (prioritizedEnvKeySet.has(key)) continue;
|
|
760
|
+
const rawValue = process.env[key];
|
|
761
|
+
if (rawValue === undefined || rawValue === null || String(rawValue).trim() === '') {
|
|
762
|
+
continue;
|
|
763
|
+
}
|
|
764
|
+
const value = formatWorkspaceProbeValue(rawValue);
|
|
765
|
+
const candidate = await resolveWorkspaceCandidate(rawValue);
|
|
766
|
+
workspaceEnvProbe.push({
|
|
767
|
+
key,
|
|
768
|
+
value,
|
|
769
|
+
resolvedPath: candidate,
|
|
770
|
+
priority: false,
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
logWorkspaceEnvProbe(workspaceEnvProbe);
|
|
775
|
+
|
|
687
776
|
const cwd = path.resolve(process.cwd());
|
|
777
|
+
|
|
778
|
+
// Note: if CWD is an IDE/system dir, MCP roots detection in index.js will auto-correct.
|
|
779
|
+
if (isNonProjectDirectory(cwd)) {
|
|
780
|
+
console.info(
|
|
781
|
+
`[Config] CWD "${cwd}" appears to be an IDE/system directory. MCP roots detection will attempt auto-correction.`
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
|
|
688
785
|
const root = await findWorkspaceRoot(cwd);
|
|
689
786
|
if (root !== cwd) {
|
|
690
787
|
return {
|
|
691
788
|
path: root,
|
|
692
789
|
source: 'cwd-root-search',
|
|
693
790
|
fromPath: cwd,
|
|
791
|
+
workspaceEnvProbe,
|
|
694
792
|
};
|
|
695
793
|
}
|
|
696
794
|
return {
|
|
697
795
|
path: cwd,
|
|
698
796
|
source: 'cwd',
|
|
797
|
+
workspaceEnvProbe,
|
|
699
798
|
};
|
|
700
799
|
}
|
|
701
800
|
|
|
702
801
|
export async function loadConfig(workspaceDir = null) {
|
|
802
|
+
let workspaceResolution = null;
|
|
803
|
+
let baseDir = null;
|
|
804
|
+
let searchDirectoryFromConfig = false;
|
|
805
|
+
|
|
703
806
|
try {
|
|
704
807
|
// Determine the base directory for configuration
|
|
705
|
-
let baseDir;
|
|
706
808
|
let configPath;
|
|
707
809
|
|
|
708
810
|
let serverDir = null;
|
|
709
811
|
if (workspaceDir) {
|
|
710
812
|
// Workspace mode: load config from workspace root
|
|
711
|
-
|
|
813
|
+
workspaceResolution = await resolveWorkspaceDir(workspaceDir);
|
|
712
814
|
baseDir = workspaceResolution.path;
|
|
713
815
|
console.info(`[Config] Workspace mode: ${baseDir}`);
|
|
714
816
|
logWorkspaceResolution(workspaceResolution);
|
|
@@ -717,7 +819,7 @@ export async function loadConfig(workspaceDir = null) {
|
|
|
717
819
|
// but use process.cwd() as base for searching if not specified otherwise
|
|
718
820
|
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
719
821
|
serverDir = path.resolve(scriptDir, '..');
|
|
720
|
-
|
|
822
|
+
workspaceResolution = await resolveWorkspaceDir(null);
|
|
721
823
|
baseDir = workspaceResolution.path;
|
|
722
824
|
logWorkspaceResolution(workspaceResolution);
|
|
723
825
|
}
|
|
@@ -772,6 +874,7 @@ export async function loadConfig(workspaceDir = null) {
|
|
|
772
874
|
|
|
773
875
|
// Set search directory (respect user override when provided)
|
|
774
876
|
if (userConfig.searchDirectory) {
|
|
877
|
+
searchDirectoryFromConfig = true;
|
|
775
878
|
config.searchDirectory = path.isAbsolute(userConfig.searchDirectory)
|
|
776
879
|
? userConfig.searchDirectory
|
|
777
880
|
: path.join(baseDir, userConfig.searchDirectory);
|
|
@@ -779,25 +882,35 @@ export async function loadConfig(workspaceDir = null) {
|
|
|
779
882
|
config.searchDirectory = baseDir;
|
|
780
883
|
}
|
|
781
884
|
|
|
782
|
-
// Determine cache directory
|
|
783
|
-
if (userConfig.cacheDirectory) {
|
|
784
|
-
// User explicitly set a cache path in their config file
|
|
785
|
-
config.cacheDirectory = path.isAbsolute(userConfig.cacheDirectory)
|
|
786
|
-
? userConfig.cacheDirectory
|
|
787
|
-
: path.join(baseDir, userConfig.cacheDirectory);
|
|
788
|
-
} else {
|
|
789
|
-
// Use global cache directory to prevent cluttering project root
|
|
790
|
-
//
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
885
|
+
// Determine cache directory
|
|
886
|
+
if (userConfig.cacheDirectory) {
|
|
887
|
+
// User explicitly set a cache path in their config file
|
|
888
|
+
config.cacheDirectory = path.isAbsolute(userConfig.cacheDirectory)
|
|
889
|
+
? userConfig.cacheDirectory
|
|
890
|
+
: path.join(baseDir, userConfig.cacheDirectory);
|
|
891
|
+
} else {
|
|
892
|
+
// Use global cache directory to prevent cluttering project root.
|
|
893
|
+
// Workspace path is normalized for hashing so Windows drive-letter case
|
|
894
|
+
// differences (e.g. F:\ vs f:\) resolve to the same cache id.
|
|
895
|
+
const globalCacheRoot = getGlobalCacheDir();
|
|
896
|
+
const normalizedCachePath = getWorkspaceCachePath(config.searchDirectory, globalCacheRoot);
|
|
897
|
+
const legacyCachePath = getLegacyWorkspaceCachePath(config.searchDirectory, globalCacheRoot);
|
|
898
|
+
|
|
899
|
+
if (
|
|
900
|
+
normalizedCachePath !== legacyCachePath &&
|
|
901
|
+
!(await pathExists(normalizedCachePath)) &&
|
|
902
|
+
(await pathExists(legacyCachePath))
|
|
903
|
+
) {
|
|
904
|
+
config.cacheDirectory = legacyCachePath;
|
|
905
|
+
console.info(
|
|
906
|
+
`[Config] Using legacy cache path for compatibility: ${path.basename(legacyCachePath)}`
|
|
907
|
+
);
|
|
908
|
+
} else {
|
|
909
|
+
config.cacheDirectory = normalizedCachePath;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// Support legacy .smart-coding-cache if it already exists in the project root
|
|
913
|
+
const legacyPath = path.join(baseDir, '.smart-coding-cache');
|
|
801
914
|
try {
|
|
802
915
|
const stats = await fs.stat(legacyPath);
|
|
803
916
|
if (stats.isDirectory()) {
|
|
@@ -840,6 +953,16 @@ export async function loadConfig(workspaceDir = null) {
|
|
|
840
953
|
console.warn(`[Config] Error: ${error.message}`);
|
|
841
954
|
}
|
|
842
955
|
|
|
956
|
+
config.workspaceResolution = {
|
|
957
|
+
source: workspaceResolution?.source || 'unknown',
|
|
958
|
+
envKey: workspaceResolution?.envKey || null,
|
|
959
|
+
fromPath: workspaceResolution?.fromPath || null,
|
|
960
|
+
baseDirectory: baseDir,
|
|
961
|
+
searchDirectory: config.searchDirectory,
|
|
962
|
+
searchDirectoryFromConfig,
|
|
963
|
+
workspaceEnvProbe: workspaceResolution?.workspaceEnvProbe || [],
|
|
964
|
+
};
|
|
965
|
+
|
|
843
966
|
// Apply environment variable overrides (prefix: SMART_CODING_) with validation
|
|
844
967
|
if (process.env.SMART_CODING_VERBOSE !== undefined) {
|
|
845
968
|
const value = process.env.SMART_CODING_VERBOSE;
|
|
@@ -932,10 +1055,43 @@ export async function loadConfig(workspaceDir = null) {
|
|
|
932
1055
|
}
|
|
933
1056
|
}
|
|
934
1057
|
|
|
935
|
-
if (process.env.SMART_CODING_WATCH_FILES !== undefined) {
|
|
936
|
-
const value = process.env.SMART_CODING_WATCH_FILES;
|
|
937
|
-
if (value === 'true' || value === 'false') {
|
|
938
|
-
config.watchFiles = value === 'true';
|
|
1058
|
+
if (process.env.SMART_CODING_WATCH_FILES !== undefined) {
|
|
1059
|
+
const value = process.env.SMART_CODING_WATCH_FILES;
|
|
1060
|
+
if (value === 'true' || value === 'false') {
|
|
1061
|
+
config.watchFiles = value === 'true';
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
if (process.env.SMART_CODING_REQUIRE_TRUSTED_WORKSPACE_SIGNAL_FOR_TOOLS !== undefined) {
|
|
1066
|
+
const value = process.env.SMART_CODING_REQUIRE_TRUSTED_WORKSPACE_SIGNAL_FOR_TOOLS;
|
|
1067
|
+
if (value === 'true' || value === 'false') {
|
|
1068
|
+
config.requireTrustedWorkspaceSignalForTools = value === 'true';
|
|
1069
|
+
} else {
|
|
1070
|
+
console.warn(
|
|
1071
|
+
`[Config] Invalid SMART_CODING_REQUIRE_TRUSTED_WORKSPACE_SIGNAL_FOR_TOOLS: ${value}, using default`
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
if (process.env.SMART_CODING_AUTO_STOP_OTHER_SERVERS_ON_STARTUP !== undefined) {
|
|
1077
|
+
const value = process.env.SMART_CODING_AUTO_STOP_OTHER_SERVERS_ON_STARTUP;
|
|
1078
|
+
if (value === 'true' || value === 'false') {
|
|
1079
|
+
config.autoStopOtherServersOnStartup = value === 'true';
|
|
1080
|
+
} else {
|
|
1081
|
+
console.warn(
|
|
1082
|
+
`[Config] Invalid SMART_CODING_AUTO_STOP_OTHER_SERVERS_ON_STARTUP: ${value}, using default`
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
if (process.env.SMART_CODING_INDEX_CHECKPOINT_INTERVAL_MS !== undefined) {
|
|
1088
|
+
const value = parseInt(process.env.SMART_CODING_INDEX_CHECKPOINT_INTERVAL_MS, 10);
|
|
1089
|
+
if (!isNaN(value) && value >= 0) {
|
|
1090
|
+
config.indexCheckpointIntervalMs = value;
|
|
1091
|
+
} else {
|
|
1092
|
+
console.warn(
|
|
1093
|
+
`[Config] Invalid SMART_CODING_INDEX_CHECKPOINT_INTERVAL_MS: ${process.env.SMART_CODING_INDEX_CHECKPOINT_INTERVAL_MS}, using default`
|
|
1094
|
+
);
|
|
939
1095
|
}
|
|
940
1096
|
}
|
|
941
1097
|
|
|
@@ -1138,7 +1294,7 @@ export async function loadConfig(workspaceDir = null) {
|
|
|
1138
1294
|
}
|
|
1139
1295
|
}
|
|
1140
1296
|
|
|
1141
|
-
if (process.env.SMART_CODING_WORKER_THREADS !== undefined) {
|
|
1297
|
+
if (process.env.SMART_CODING_WORKER_THREADS !== undefined) {
|
|
1142
1298
|
const value = process.env.SMART_CODING_WORKER_THREADS.trim().toLowerCase();
|
|
1143
1299
|
if (value === 'auto') {
|
|
1144
1300
|
config.workerThreads = 'auto';
|
|
@@ -1152,7 +1308,21 @@ export async function loadConfig(workspaceDir = null) {
|
|
|
1152
1308
|
);
|
|
1153
1309
|
}
|
|
1154
1310
|
}
|
|
1155
|
-
}
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
const workerDisableHeavyModelOnWindowsEnv =
|
|
1314
|
+
process.env.SMART_CODING_WORKER_DISABLE_HEAVY_MODEL_ON_WINDOWS ??
|
|
1315
|
+
process.env.SMART_CODING_WORKER_DISABLE_HEAVY_MODEL_WINDOWS;
|
|
1316
|
+
if (workerDisableHeavyModelOnWindowsEnv !== undefined) {
|
|
1317
|
+
const value = workerDisableHeavyModelOnWindowsEnv;
|
|
1318
|
+
if (value === 'true' || value === 'false') {
|
|
1319
|
+
config.workerDisableHeavyModelOnWindows = value === 'true';
|
|
1320
|
+
} else {
|
|
1321
|
+
console.warn(
|
|
1322
|
+
`[Config] Invalid SMART_CODING_WORKER_DISABLE_HEAVY_MODEL_ON_WINDOWS: ${value}, using default`
|
|
1323
|
+
);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1156
1326
|
|
|
1157
1327
|
if (process.env.SMART_CODING_EMBEDDING_FAIL_FAST_BREAKER !== undefined) {
|
|
1158
1328
|
const value = process.env.SMART_CODING_EMBEDDING_FAIL_FAST_BREAKER;
|
package/lib/constants.js
CHANGED
|
@@ -25,15 +25,35 @@ export const WORKSPACE_ENV_VARS = Object.freeze([
|
|
|
25
25
|
]);
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
28
|
+
* Prefixes for dynamic workspace-related env vars (provider-specific).
|
|
29
|
+
*/
|
|
30
|
+
export const DYNAMIC_WORKSPACE_ENV_PREFIXES = Object.freeze([
|
|
31
|
+
'CODEX_',
|
|
32
|
+
'ANTIGRAVITY_',
|
|
33
|
+
'CURSOR_',
|
|
34
|
+
'CLAUDE_',
|
|
35
|
+
'WINDSURF_',
|
|
36
|
+
'WARP_',
|
|
37
|
+
'MCP_',
|
|
38
|
+
'VSCODE_',
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Backward-compatible alias for legacy single-prefix consumers.
|
|
29
43
|
*/
|
|
30
|
-
export const DYNAMIC_WORKSPACE_ENV_PREFIX =
|
|
44
|
+
export const DYNAMIC_WORKSPACE_ENV_PREFIX = DYNAMIC_WORKSPACE_ENV_PREFIXES[0];
|
|
31
45
|
|
|
32
46
|
/**
|
|
33
47
|
* Pattern used when ranking provider-specific workspace env vars.
|
|
34
48
|
*/
|
|
35
49
|
export const WORKSPACE_ENV_KEY_PATTERN = /(WORKSPACE|PROJECT|ROOT|CWD|DIR)/i;
|
|
36
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Broad token used to discover unknown provider keys safely.
|
|
53
|
+
* We only auto-discover generic env keys containing "WORKSPACE".
|
|
54
|
+
*/
|
|
55
|
+
export const WORKSPACE_ENV_GENERIC_DISCOVERY_PATTERN = /WORKSPACE/i;
|
|
56
|
+
|
|
37
57
|
// ================================
|
|
38
58
|
// Chunking Constants
|
|
39
59
|
// ================================
|
|
@@ -6,7 +6,7 @@ import readline from 'readline';
|
|
|
6
6
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
7
|
const EMBEDDING_PROCESS_PATH = path.join(__dirname, 'embedding-process.js');
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
let persistentChild = null;
|
|
11
11
|
let childReadline = null;
|
|
12
12
|
let idleTimer = null;
|
|
@@ -14,16 +14,13 @@ let currentConfig = null;
|
|
|
14
14
|
let pendingRequests = [];
|
|
15
15
|
let isProcessingRequest = false;
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
const DEFAULT_IDLE_TIMEOUT_MS = 30000;
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
* Get or create the persistent embedding child process.
|
|
22
|
-
* The child stays alive for consecutive queries, then exits after idle timeout.
|
|
23
|
-
*/
|
|
20
|
+
|
|
24
21
|
function getOrCreateChild(config) {
|
|
25
22
|
if (persistentChild && !persistentChild.killed) {
|
|
26
|
-
|
|
23
|
+
|
|
27
24
|
resetIdleTimer(config);
|
|
28
25
|
return persistentChild;
|
|
29
26
|
}
|
|
@@ -41,7 +38,7 @@ function getOrCreateChild(config) {
|
|
|
41
38
|
|
|
42
39
|
currentConfig = config;
|
|
43
40
|
|
|
44
|
-
|
|
41
|
+
|
|
45
42
|
childReadline = readline.createInterface({
|
|
46
43
|
input: persistentChild.stdout,
|
|
47
44
|
crlfDelay: Infinity,
|
|
@@ -50,7 +47,7 @@ function getOrCreateChild(config) {
|
|
|
50
47
|
childReadline.on('line', (line) => {
|
|
51
48
|
if (!line.trim()) return;
|
|
52
49
|
|
|
53
|
-
|
|
50
|
+
|
|
54
51
|
if (pendingRequests.length > 0) {
|
|
55
52
|
const { resolve, reject, startTime } = pendingRequests.shift();
|
|
56
53
|
try {
|
|
@@ -93,7 +90,7 @@ function getOrCreateChild(config) {
|
|
|
93
90
|
persistentChild.on('error', (err) => {
|
|
94
91
|
console.error(`[EmbedPool] Child process error: ${err.message}`);
|
|
95
92
|
cleanupChild();
|
|
96
|
-
|
|
93
|
+
|
|
97
94
|
for (const { reject } of pendingRequests) {
|
|
98
95
|
reject(new Error(`Child process error: ${err.message}`));
|
|
99
96
|
}
|
|
@@ -105,7 +102,7 @@ function getOrCreateChild(config) {
|
|
|
105
102
|
console.info(`[EmbedPool] Child process exited with code ${code}`);
|
|
106
103
|
}
|
|
107
104
|
cleanupChild();
|
|
108
|
-
|
|
105
|
+
|
|
109
106
|
for (const { reject } of pendingRequests) {
|
|
110
107
|
reject(new Error(`Child process exited unexpectedly with code ${code}`));
|
|
111
108
|
}
|
|
@@ -154,10 +151,10 @@ function cleanupChild() {
|
|
|
154
151
|
function shutdownChild() {
|
|
155
152
|
if (persistentChild && !persistentChild.killed) {
|
|
156
153
|
try {
|
|
157
|
-
|
|
154
|
+
|
|
158
155
|
persistentChild.stdin.write(JSON.stringify({ type: 'shutdown' }) + '\n');
|
|
159
156
|
} catch {
|
|
160
|
-
|
|
157
|
+
|
|
161
158
|
persistentChild.kill();
|
|
162
159
|
}
|
|
163
160
|
}
|
|
@@ -183,15 +180,7 @@ function processNextRequest() {
|
|
|
183
180
|
}
|
|
184
181
|
}
|
|
185
182
|
|
|
186
|
-
|
|
187
|
-
* Embed a single query string using a persistent child process.
|
|
188
|
-
* The child process stays alive for consecutive queries, then exits after idle timeout.
|
|
189
|
-
* This gives fast consecutive searches + memory cleanup after idle period.
|
|
190
|
-
*
|
|
191
|
-
* @param {string} query - The query text to embed
|
|
192
|
-
* @param {object} config - Configuration object with embeddingModel and embeddingProcessNumThreads
|
|
193
|
-
* @returns {Promise<Float32Array>} - The embedding vector
|
|
194
|
-
*/
|
|
183
|
+
|
|
195
184
|
export async function embedQueryInChildProcess(query, config) {
|
|
196
185
|
return new Promise((resolve, reject) => {
|
|
197
186
|
const payload = {
|
|
@@ -212,17 +201,12 @@ export async function embedQueryInChildProcess(query, config) {
|
|
|
212
201
|
});
|
|
213
202
|
}
|
|
214
203
|
|
|
215
|
-
|
|
216
|
-
* Force shutdown the persistent child process to immediately free memory.
|
|
217
|
-
* Called when user explicitly wants to free memory.
|
|
218
|
-
*/
|
|
204
|
+
|
|
219
205
|
export function forceShutdownEmbeddingPool() {
|
|
220
206
|
shutdownChild();
|
|
221
207
|
}
|
|
222
208
|
|
|
223
|
-
|
|
224
|
-
* Check if the persistent child process is currently running.
|
|
225
|
-
*/
|
|
209
|
+
|
|
226
210
|
export function isEmbeddingPoolActive() {
|
|
227
211
|
return persistentChild !== null && !persistentChild.killed;
|
|
228
212
|
}
|