@softerist/heuristic-mcp 3.0.16 → 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 +819 -173
- package/lib/cache-utils.js +95 -99
- package/lib/cache.js +121 -166
- package/lib/cli.js +114 -21
- 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/cli.js
CHANGED
|
@@ -1,10 +1,33 @@
|
|
|
1
1
|
export const DEFAULT_LOG_TAIL_LINES = 200;
|
|
2
|
+
const FLAG_ARGS_WITH_VALUES = new Set(['--tail', '--workspace', '--start', '--register', '--clear']);
|
|
3
|
+
const COMMAND_ALIASES = Object.freeze({
|
|
4
|
+
status: '--status',
|
|
5
|
+
stat: '--status',
|
|
6
|
+
log: '--logs',
|
|
7
|
+
logs: '--logs',
|
|
8
|
+
start: '--start',
|
|
9
|
+
stop: '--stop',
|
|
10
|
+
cache: '--cache',
|
|
11
|
+
'clear-cache': '--clear-cache',
|
|
12
|
+
clearcache: '--clear-cache',
|
|
13
|
+
clear: '--clear',
|
|
14
|
+
clean: '--clear',
|
|
15
|
+
mem: '--mem',
|
|
16
|
+
memory: '--mem',
|
|
17
|
+
version: '--version',
|
|
18
|
+
help: '--help',
|
|
19
|
+
register: '--register',
|
|
20
|
+
});
|
|
21
|
+
const FLAG_ALIASES = Object.freeze({
|
|
22
|
+
'--log': '--logs',
|
|
23
|
+
});
|
|
2
24
|
|
|
3
25
|
export function printHelp(defaultTailLines = DEFAULT_LOG_TAIL_LINES) {
|
|
4
26
|
console.info(`Heuristic MCP Server
|
|
5
27
|
|
|
6
28
|
Usage:
|
|
7
29
|
heuristic-mcp [options]
|
|
30
|
+
heuristic-mcp <command> [args]
|
|
8
31
|
|
|
9
32
|
Options:
|
|
10
33
|
--cache Show cache status and cleanup recommendations (dry-run)
|
|
@@ -21,33 +44,97 @@ Options:
|
|
|
21
44
|
--workspace <path> Workspace path (used by IDE launch / log viewer)
|
|
22
45
|
--version, -v Show version
|
|
23
46
|
--help, -h Show this help
|
|
47
|
+
|
|
24
48
|
`);
|
|
25
49
|
}
|
|
26
50
|
|
|
27
|
-
export function
|
|
28
|
-
const
|
|
29
|
-
|
|
51
|
+
export function normalizeCliArgs(rawArgs = []) {
|
|
52
|
+
const normalized = [];
|
|
53
|
+
let expectsValue = false;
|
|
30
54
|
|
|
31
|
-
const
|
|
32
|
-
|
|
55
|
+
for (const token of rawArgs) {
|
|
56
|
+
if (expectsValue) {
|
|
57
|
+
normalized.push(token);
|
|
58
|
+
expectsValue = false;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
33
61
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
62
|
+
if (token.startsWith('--')) {
|
|
63
|
+
const eqIdx = token.indexOf('=');
|
|
64
|
+
if (eqIdx !== -1) {
|
|
65
|
+
const flagPart = token.slice(0, eqIdx);
|
|
66
|
+
const valuePart = token.slice(eqIdx + 1);
|
|
67
|
+
const mappedFlag = FLAG_ALIASES[flagPart] || flagPart;
|
|
68
|
+
normalized.push(`${mappedFlag}=${valuePart}`);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const mappedFlag = FLAG_ALIASES[token] || token;
|
|
73
|
+
normalized.push(mappedFlag);
|
|
74
|
+
if (FLAG_ARGS_WITH_VALUES.has(mappedFlag)) {
|
|
75
|
+
expectsValue = true;
|
|
76
|
+
}
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (token.startsWith('-')) {
|
|
81
|
+
normalized.push(token);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const mappedCommand = COMMAND_ALIASES[token.toLowerCase()];
|
|
86
|
+
if (mappedCommand) {
|
|
87
|
+
normalized.push(mappedCommand);
|
|
88
|
+
if (FLAG_ARGS_WITH_VALUES.has(mappedCommand)) {
|
|
89
|
+
expectsValue = true;
|
|
90
|
+
}
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
39
93
|
|
|
40
|
-
|
|
41
|
-
if (rawWorkspace && rawWorkspace.includes('${')) {
|
|
42
|
-
console.error(
|
|
43
|
-
`[Server] IDE variable not expanded: ${rawWorkspace}, falling back to auto-detected workspace`
|
|
44
|
-
);
|
|
45
|
-
return null;
|
|
94
|
+
normalized.push(token);
|
|
46
95
|
}
|
|
47
96
|
|
|
48
|
-
return
|
|
97
|
+
return normalized;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function shouldDefaultToHelp(
|
|
101
|
+
args,
|
|
102
|
+
runtime = { stdinIsTTY: process.stdin.isTTY, stdoutIsTTY: process.stdout.isTTY }
|
|
103
|
+
) {
|
|
104
|
+
return args.length === 0 && Boolean(runtime.stdinIsTTY && runtime.stdoutIsTTY);
|
|
49
105
|
}
|
|
50
106
|
|
|
107
|
+
export function parseWorkspaceDir(args) {
|
|
108
|
+
const isUnexpandedWorkspaceValue = (value) =>
|
|
109
|
+
value.includes('${') || /\{\{.+\}\}/.test(value) || /%[A-Za-z_][A-Za-z0-9_]*%/.test(value);
|
|
110
|
+
|
|
111
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
112
|
+
const arg = args[i];
|
|
113
|
+
if (!arg.startsWith('--workspace')) continue;
|
|
114
|
+
|
|
115
|
+
let rawWorkspace = null;
|
|
116
|
+
if (arg.startsWith('--workspace=')) {
|
|
117
|
+
rawWorkspace = arg.slice('--workspace='.length);
|
|
118
|
+
} else if (arg === '--workspace' && i + 1 < args.length) {
|
|
119
|
+
rawWorkspace = args[i + 1];
|
|
120
|
+
i += 1;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!rawWorkspace) continue;
|
|
124
|
+
|
|
125
|
+
if (isUnexpandedWorkspaceValue(rawWorkspace)) {
|
|
126
|
+
console.error(
|
|
127
|
+
`[Server] IDE variable not expanded: ${rawWorkspace}, falling back to next workspace candidate`
|
|
128
|
+
);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return rawWorkspace;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
51
138
|
export function collectUnknownFlags(rawArgs, knownFlags, flagsWithValue) {
|
|
52
139
|
const unknownFlags = [];
|
|
53
140
|
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
@@ -67,12 +154,15 @@ export function collectUnknownFlags(rawArgs, knownFlags, flagsWithValue) {
|
|
|
67
154
|
return unknownFlags;
|
|
68
155
|
}
|
|
69
156
|
|
|
70
|
-
export function parseArgs(
|
|
71
|
-
|
|
157
|
+
export function parseArgs(
|
|
158
|
+
argv = process.argv,
|
|
159
|
+
runtime = { stdinIsTTY: process.stdin.isTTY, stdoutIsTTY: process.stdout.isTTY }
|
|
160
|
+
) {
|
|
161
|
+
const args = normalizeCliArgs(argv.slice(2));
|
|
72
162
|
const rawArgs = [...args];
|
|
73
163
|
|
|
74
164
|
const wantsVersion = args.includes('--version') || args.includes('-v');
|
|
75
|
-
const wantsHelp = args.includes('--help') || args.includes('-h');
|
|
165
|
+
const wantsHelp = args.includes('--help') || args.includes('-h') || shouldDefaultToHelp(args, runtime);
|
|
76
166
|
const wantsCache = args.includes('--cache');
|
|
77
167
|
const wantsClean = args.includes('--clean');
|
|
78
168
|
const wantsStatus = args.includes('--status');
|
|
@@ -132,14 +222,16 @@ export function parseArgs(argv = process.argv) {
|
|
|
132
222
|
'--tail',
|
|
133
223
|
'--no-follow',
|
|
134
224
|
'--start',
|
|
225
|
+
'--register',
|
|
135
226
|
'--stop',
|
|
136
227
|
'--workspace',
|
|
228
|
+
'--fix',
|
|
137
229
|
'--version',
|
|
138
230
|
'-v',
|
|
139
231
|
'--help',
|
|
140
232
|
'-h',
|
|
141
233
|
]);
|
|
142
|
-
const flagsWithValue =
|
|
234
|
+
const flagsWithValue = FLAG_ARGS_WITH_VALUES;
|
|
143
235
|
const unknownFlags = collectUnknownFlags(rawArgs, knownFlags, flagsWithValue);
|
|
144
236
|
|
|
145
237
|
return {
|
|
@@ -164,3 +256,4 @@ export function parseArgs(argv = process.argv) {
|
|
|
164
256
|
unknownFlags,
|
|
165
257
|
};
|
|
166
258
|
}
|
|
259
|
+
|
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
|
// ================================
|