@softerist/heuristic-mcp 2.1.12 → 2.1.23
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/features/lifecycle.js +226 -28
- package/features/register.js +54 -15
- package/index.js +21 -1
- package/lib/cache.js +8 -1
- package/lib/config.js +44 -2
- package/package.json +15 -15
- package/scripts/postinstall.js +9 -0
package/features/lifecycle.js
CHANGED
|
@@ -6,30 +6,55 @@ export async function stop() {
|
|
|
6
6
|
console.log('[Lifecycle] Stopping Heuristic MCP servers...');
|
|
7
7
|
try {
|
|
8
8
|
const platform = process.platform;
|
|
9
|
-
|
|
9
|
+
const currentPid = process.pid;
|
|
10
|
+
let pids = [];
|
|
10
11
|
|
|
11
12
|
if (platform === 'win32') {
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
const { stdout } = await execPromise(`wmic process where "CommandLine like '%heuristic-mcp/index.js%'" get ProcessId`);
|
|
14
|
+
pids = stdout.trim().split(/\s+/).filter(p => p && !isNaN(p) && parseInt(p) !== currentPid);
|
|
14
15
|
} else {
|
|
15
|
-
// Unix
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
// Unix: Use pgrep to get all matching PIDs
|
|
17
|
+
try {
|
|
18
|
+
const { stdout } = await execPromise(`pgrep -f \"heuristic-mcp.*index.js\"`);
|
|
19
|
+
const allPids = stdout.trim().split(/\s+/).filter(p => p && !isNaN(p));
|
|
20
|
+
|
|
21
|
+
// Filter out current PID and dead processes
|
|
22
|
+
pids = [];
|
|
23
|
+
for (const p of allPids) {
|
|
24
|
+
const pid = parseInt(p);
|
|
25
|
+
if (pid === currentPid) continue;
|
|
26
|
+
try {
|
|
27
|
+
process.kill(pid, 0);
|
|
28
|
+
pids.push(p);
|
|
29
|
+
} catch (e) {}
|
|
30
|
+
}
|
|
31
|
+
} catch (e) {
|
|
32
|
+
// pgrep returns code 1 if no processes found, which is fine
|
|
33
|
+
if (e.code === 1) pids = [];
|
|
34
|
+
else throw e;
|
|
35
|
+
}
|
|
19
36
|
}
|
|
20
37
|
|
|
21
|
-
|
|
22
|
-
console.log('[Lifecycle] ✅ Stopped all running instances.');
|
|
23
|
-
} catch (error) {
|
|
24
|
-
// pkill (Linux/Mac) returns exit code 1 if no process matched.
|
|
25
|
-
// wmic (Windows) might throw specific errors if none found.
|
|
26
|
-
// We treat exit code 1 as "Success, nothing was running".
|
|
27
|
-
if (error.code === 1 || error.code === '1' || error.message.includes('No Instance(s) Available')) {
|
|
38
|
+
if (pids.length === 0) {
|
|
28
39
|
console.log('[Lifecycle] No running instances found (already stopped).');
|
|
29
|
-
|
|
30
|
-
// Don't fail hard, just warn
|
|
31
|
-
console.warn(`[Lifecycle] Warning: Stop command finished with unexpected result: ${error.message}`);
|
|
40
|
+
return;
|
|
32
41
|
}
|
|
42
|
+
|
|
43
|
+
// Kill each process
|
|
44
|
+
let killedCount = 0;
|
|
45
|
+
for (const pid of pids) {
|
|
46
|
+
try {
|
|
47
|
+
process.kill(parseInt(pid), 'SIGTERM');
|
|
48
|
+
killedCount++;
|
|
49
|
+
} catch (e) {
|
|
50
|
+
// Ignore if process already gone
|
|
51
|
+
if (e.code !== 'ESRCH') console.warn(`[Lifecycle] Failed to kill PID ${pid}: ${e.message}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log(`[Lifecycle] ✅ Stopped ${killedCount} running instance(s).`);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.warn(`[Lifecycle] Warning: Stop command encountered an error: ${error.message}`);
|
|
33
58
|
}
|
|
34
59
|
}
|
|
35
60
|
|
|
@@ -49,17 +74,37 @@ export async function start() {
|
|
|
49
74
|
export async function status() {
|
|
50
75
|
try {
|
|
51
76
|
const platform = process.platform;
|
|
52
|
-
|
|
77
|
+
const currentPid = process.pid;
|
|
78
|
+
let pids = [];
|
|
53
79
|
|
|
54
80
|
if (platform === 'win32') {
|
|
55
|
-
|
|
81
|
+
const { stdout } = await execPromise(`wmic process where "CommandLine like '%heuristic-mcp/index.js%'" get ProcessId`);
|
|
82
|
+
pids = stdout.trim().split(/\s+/).filter(p => p && !isNaN(p) && parseInt(p) !== currentPid);
|
|
56
83
|
} else {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
84
|
+
try {
|
|
85
|
+
const { stdout } = await execPromise(`pgrep -f "heuristic-mcp.*index.js"`);
|
|
86
|
+
const allPids = stdout.trim().split(/\s+/).filter(p => p && !isNaN(p));
|
|
87
|
+
|
|
88
|
+
// Filter out current PID and dead processes (e.g. ephemeral shell wrappers)
|
|
89
|
+
const validPids = [];
|
|
90
|
+
for (const p of allPids) {
|
|
91
|
+
const pid = parseInt(p);
|
|
92
|
+
if (pid === currentPid) continue;
|
|
60
93
|
|
|
61
|
-
|
|
62
|
-
|
|
94
|
+
try {
|
|
95
|
+
// Check if process is still alive
|
|
96
|
+
process.kill(pid, 0);
|
|
97
|
+
validPids.push(p);
|
|
98
|
+
} catch (e) {
|
|
99
|
+
// Process is dead or access denied
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
pids = validPids;
|
|
103
|
+
} catch (e) {
|
|
104
|
+
if (e.code === 1) pids = [];
|
|
105
|
+
else throw e;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
63
108
|
|
|
64
109
|
if (pids.length > 0) {
|
|
65
110
|
console.log(`[Lifecycle] 🟢 Server is RUNNING. PID(s): ${pids.join(', ')}`);
|
|
@@ -67,11 +112,164 @@ export async function status() {
|
|
|
67
112
|
console.log('[Lifecycle] ⚪ Server is STOPPED.');
|
|
68
113
|
}
|
|
69
114
|
} catch (error) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
115
|
+
console.error(`[Lifecycle] Failed to check status: ${error.message}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function logs() {
|
|
120
|
+
const fs = await import('fs/promises');
|
|
121
|
+
const path = await import('path');
|
|
122
|
+
const os = await import('os');
|
|
123
|
+
const crypto = await import('crypto');
|
|
124
|
+
|
|
125
|
+
console.log('[Logs] Searching for cache directories...\n');
|
|
126
|
+
|
|
127
|
+
// Determine global cache root
|
|
128
|
+
function getGlobalCacheDir() {
|
|
129
|
+
if (process.platform === 'win32') {
|
|
130
|
+
return process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
|
|
131
|
+
} else if (process.platform === 'darwin') {
|
|
132
|
+
return path.join(os.homedir(), 'Library', 'Caches');
|
|
133
|
+
}
|
|
134
|
+
return process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const globalCacheRoot = path.join(getGlobalCacheDir(), 'heuristic-mcp');
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
// List all cache directories
|
|
141
|
+
const cacheDirs = await fs.readdir(globalCacheRoot).catch(() => []);
|
|
142
|
+
|
|
143
|
+
if (cacheDirs.length === 0) {
|
|
144
|
+
console.log('[Logs] No cache directories found.');
|
|
145
|
+
console.log(`[Logs] Expected location: ${globalCacheRoot}`);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
console.log(`[Logs] Found ${cacheDirs.length} cache director${cacheDirs.length === 1 ? 'y' : 'ies'} in ${globalCacheRoot}\n`);
|
|
150
|
+
|
|
151
|
+
for (const dir of cacheDirs) {
|
|
152
|
+
const cacheDir = path.join(globalCacheRoot, dir);
|
|
153
|
+
const metaFile = path.join(cacheDir, 'meta.json');
|
|
154
|
+
|
|
155
|
+
console.log(`${'─'.repeat(60)}`);
|
|
156
|
+
console.log(`📁 Cache: ${dir}`);
|
|
157
|
+
console.log(` Path: ${cacheDir}`);
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const metaData = JSON.parse(await fs.readFile(metaFile, 'utf-8'));
|
|
161
|
+
|
|
162
|
+
console.log(` Status: ✅ Valid cache`);
|
|
163
|
+
console.log(` Workspace: ${metaData.workspace || 'Unknown'}`);
|
|
164
|
+
console.log(` Files indexed: ${metaData.filesIndexed ?? 'N/A'}`);
|
|
165
|
+
console.log(` Chunks stored: ${metaData.chunksStored ?? 'N/A'}`);
|
|
166
|
+
console.log(` Embedding model: ${metaData.embeddingModel}`);
|
|
167
|
+
|
|
168
|
+
if (metaData.lastSaveTime) {
|
|
169
|
+
const saveDate = new Date(metaData.lastSaveTime);
|
|
170
|
+
const now = new Date();
|
|
171
|
+
const ageMs = now - saveDate;
|
|
172
|
+
const ageHours = Math.floor(ageMs / (1000 * 60 * 60));
|
|
173
|
+
const ageMins = Math.floor((ageMs % (1000 * 60 * 60)) / (1000 * 60));
|
|
174
|
+
|
|
175
|
+
console.log(` Last saved: ${saveDate.toLocaleString()} (${ageHours}h ${ageMins}m ago)`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Check file sizes
|
|
179
|
+
const files = ['embeddings.json', 'file-hashes.json', 'call-graph.json', 'ann-index.bin'];
|
|
180
|
+
const sizes = [];
|
|
181
|
+
for (const file of files) {
|
|
182
|
+
try {
|
|
183
|
+
const stat = await fs.stat(path.join(cacheDir, file));
|
|
184
|
+
sizes.push(`${file}: ${(stat.size / 1024).toFixed(1)}KB`);
|
|
185
|
+
} catch {}
|
|
186
|
+
}
|
|
187
|
+
if (sizes.length > 0) {
|
|
188
|
+
console.log(` Files: ${sizes.join(', ')}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Verify indexing completion
|
|
192
|
+
if (metaData.filesIndexed && metaData.filesIndexed > 0 && metaData.chunksStored && metaData.chunksStored > 0) {
|
|
193
|
+
console.log(` Indexing: ✅ COMPLETE (${metaData.filesIndexed} files → ${metaData.chunksStored} chunks)`);
|
|
194
|
+
} else if (metaData.filesIndexed === 0) {
|
|
195
|
+
console.log(` Indexing: ⚠️ NO FILES (check excludePatterns in config)`);
|
|
196
|
+
} else {
|
|
197
|
+
console.log(` Indexing: ⚠️ INCOMPLETE or UNKNOWN`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
} catch (e) {
|
|
201
|
+
console.log(` Status: ❌ Invalid or corrupted (${e.message})`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
console.log(`${'─'.repeat(60)}\n`);
|
|
206
|
+
|
|
207
|
+
// Show important paths
|
|
208
|
+
console.log('[Paths] Important locations:');
|
|
209
|
+
|
|
210
|
+
// Global npm bin
|
|
211
|
+
const { execSync } = await import('child_process');
|
|
212
|
+
let npmBin = 'unknown';
|
|
213
|
+
try {
|
|
214
|
+
npmBin = execSync('npm bin -g', { encoding: 'utf-8' }).trim();
|
|
215
|
+
} catch {}
|
|
216
|
+
|
|
217
|
+
// Check all known MCP config paths (same as register.js)
|
|
218
|
+
const home = os.homedir();
|
|
219
|
+
const mcpConfigs = [];
|
|
220
|
+
|
|
221
|
+
// Antigravity
|
|
222
|
+
const antigravityConfig = path.join(home, '.gemini', 'antigravity', 'mcp_config.json');
|
|
223
|
+
const antigravityExists = await fs.access(antigravityConfig).then(() => true).catch(() => false);
|
|
224
|
+
mcpConfigs.push({ name: 'Antigravity', path: antigravityConfig, exists: antigravityExists });
|
|
225
|
+
|
|
226
|
+
// Claude Desktop
|
|
227
|
+
let claudeConfig = null;
|
|
228
|
+
if (process.platform === 'darwin') {
|
|
229
|
+
claudeConfig = path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
230
|
+
} else if (process.platform === 'win32') {
|
|
231
|
+
claudeConfig = path.join(process.env.APPDATA || '', 'Claude', 'claude_desktop_config.json');
|
|
232
|
+
}
|
|
233
|
+
if (claudeConfig) {
|
|
234
|
+
const claudeExists = await fs.access(claudeConfig).then(() => true).catch(() => false);
|
|
235
|
+
mcpConfigs.push({ name: 'Claude Desktop', path: claudeConfig, exists: claudeExists });
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// VS Code (MCP extension uses settings.json or dedicated config)
|
|
239
|
+
let vscodeConfig = null;
|
|
240
|
+
if (process.platform === 'darwin') {
|
|
241
|
+
vscodeConfig = path.join(home, 'Library', 'Application Support', 'Code', 'User', 'settings.json');
|
|
242
|
+
} else if (process.platform === 'win32') {
|
|
243
|
+
vscodeConfig = path.join(process.env.APPDATA || '', 'Code', 'User', 'settings.json');
|
|
244
|
+
} else {
|
|
245
|
+
vscodeConfig = path.join(home, '.config', 'Code', 'User', 'settings.json');
|
|
246
|
+
}
|
|
247
|
+
const vscodeExists = await fs.access(vscodeConfig).then(() => true).catch(() => false);
|
|
248
|
+
mcpConfigs.push({ name: 'VS Code', path: vscodeConfig, exists: vscodeExists });
|
|
249
|
+
|
|
250
|
+
// Cursor (uses similar structure to VS Code)
|
|
251
|
+
let cursorConfig = null;
|
|
252
|
+
if (process.platform === 'darwin') {
|
|
253
|
+
cursorConfig = path.join(home, 'Library', 'Application Support', 'Cursor', 'User', 'settings.json');
|
|
254
|
+
} else if (process.platform === 'win32') {
|
|
255
|
+
cursorConfig = path.join(process.env.APPDATA || '', 'Cursor', 'User', 'settings.json');
|
|
73
256
|
} else {
|
|
74
|
-
|
|
257
|
+
cursorConfig = path.join(home, '.config', 'Cursor', 'User', 'settings.json');
|
|
258
|
+
}
|
|
259
|
+
const cursorExists = await fs.access(cursorConfig).then(() => true).catch(() => false);
|
|
260
|
+
mcpConfigs.push({ name: 'Cursor', path: cursorConfig, exists: cursorExists });
|
|
261
|
+
|
|
262
|
+
console.log(` 📦 Global npm bin: ${npmBin}`);
|
|
263
|
+
console.log(` ⚙️ MCP configs:`);
|
|
264
|
+
for (const cfg of mcpConfigs) {
|
|
265
|
+
const status = cfg.exists ? '\x1b[32m(exists)\x1b[0m' : '\x1b[90m(not found)\x1b[0m';
|
|
266
|
+
console.log(` - ${cfg.name}: ${cfg.path} ${status}`);
|
|
75
267
|
}
|
|
268
|
+
console.log(` 💾 Cache root: ${globalCacheRoot}`);
|
|
269
|
+
console.log(` 📁 Current dir: ${process.cwd()}`);
|
|
270
|
+
console.log('');
|
|
271
|
+
|
|
272
|
+
} catch (error) {
|
|
273
|
+
console.error(`[Logs] Error reading cache: ${error.message}`);
|
|
76
274
|
}
|
|
77
275
|
}
|
package/features/register.js
CHANGED
|
@@ -23,35 +23,59 @@ function getConfigPaths() {
|
|
|
23
23
|
const paths = [];
|
|
24
24
|
|
|
25
25
|
// Antigravity
|
|
26
|
-
|
|
26
|
+
paths.push({
|
|
27
|
+
name: 'Antigravity',
|
|
28
|
+
path: path.join(home, '.gemini', 'antigravity', 'mcp_config.json')
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Claude Desktop
|
|
32
|
+
if (platform === 'darwin') {
|
|
27
33
|
paths.push({
|
|
28
|
-
name: '
|
|
29
|
-
path:
|
|
34
|
+
name: 'Claude Desktop',
|
|
35
|
+
path: path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json')
|
|
30
36
|
});
|
|
31
|
-
} else {
|
|
37
|
+
} else if (platform === 'win32') {
|
|
32
38
|
paths.push({
|
|
33
|
-
name: '
|
|
34
|
-
path:
|
|
39
|
+
name: 'Claude Desktop',
|
|
40
|
+
path: path.join(process.env.APPDATA || '', 'Claude', 'claude_desktop_config.json')
|
|
35
41
|
});
|
|
36
42
|
}
|
|
37
43
|
|
|
38
|
-
//
|
|
44
|
+
// VS Code (MCP extension config in settings.json)
|
|
39
45
|
if (platform === 'darwin') {
|
|
40
46
|
paths.push({
|
|
41
|
-
name: '
|
|
42
|
-
path:
|
|
47
|
+
name: 'VS Code',
|
|
48
|
+
path: path.join(home, 'Library', 'Application Support', 'Code', 'User', 'settings.json')
|
|
43
49
|
});
|
|
44
50
|
} else if (platform === 'win32') {
|
|
45
51
|
paths.push({
|
|
46
|
-
name: '
|
|
47
|
-
path:
|
|
52
|
+
name: 'VS Code',
|
|
53
|
+
path: path.join(process.env.APPDATA || '', 'Code', 'User', 'settings.json')
|
|
54
|
+
});
|
|
55
|
+
} else {
|
|
56
|
+
paths.push({
|
|
57
|
+
name: 'VS Code',
|
|
58
|
+
path: path.join(home, '.config', 'Code', 'User', 'settings.json')
|
|
48
59
|
});
|
|
49
60
|
}
|
|
50
61
|
|
|
51
|
-
// Cursor (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
62
|
+
// Cursor (uses similar structure to VS Code)
|
|
63
|
+
if (platform === 'darwin') {
|
|
64
|
+
paths.push({
|
|
65
|
+
name: 'Cursor',
|
|
66
|
+
path: path.join(home, 'Library', 'Application Support', 'Cursor', 'User', 'settings.json')
|
|
67
|
+
});
|
|
68
|
+
} else if (platform === 'win32') {
|
|
69
|
+
paths.push({
|
|
70
|
+
name: 'Cursor',
|
|
71
|
+
path: path.join(process.env.APPDATA || '', 'Cursor', 'User', 'settings.json')
|
|
72
|
+
});
|
|
73
|
+
} else {
|
|
74
|
+
paths.push({
|
|
75
|
+
name: 'Cursor',
|
|
76
|
+
path: path.join(home, '.config', 'Cursor', 'User', 'settings.json')
|
|
77
|
+
});
|
|
78
|
+
}
|
|
55
79
|
|
|
56
80
|
return paths;
|
|
57
81
|
}
|
|
@@ -135,6 +159,15 @@ export async function register(filter = null) {
|
|
|
135
159
|
forceLog('\n\x1b[36m' + '='.repeat(60));
|
|
136
160
|
forceLog(' 🚀 Heuristic MCP Installed & Configured! ');
|
|
137
161
|
forceLog('='.repeat(60) + '\x1b[0m');
|
|
162
|
+
|
|
163
|
+
// Show important paths
|
|
164
|
+
const home = os.homedir();
|
|
165
|
+
const cacheRoot = process.platform === 'win32'
|
|
166
|
+
? path.join(process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local'), 'heuristic-mcp')
|
|
167
|
+
: process.platform === 'darwin'
|
|
168
|
+
? path.join(home, 'Library', 'Caches', 'heuristic-mcp')
|
|
169
|
+
: path.join(process.env.XDG_CACHE_HOME || path.join(home, '.cache'), 'heuristic-mcp');
|
|
170
|
+
|
|
138
171
|
forceLog(`
|
|
139
172
|
\x1b[33mACTION REQUIRED:\x1b[0m
|
|
140
173
|
1. \x1b[1mRestart your IDE\x1b[0m (or reload the window) to load the new config.
|
|
@@ -145,7 +178,13 @@ export async function register(filter = null) {
|
|
|
145
178
|
- \x1b[1mIndexing:\x1b[0m Will begin immediately after restart.
|
|
146
179
|
- \x1b[1mUsage:\x1b[0m You can work while it indexes (it catches up!).
|
|
147
180
|
|
|
181
|
+
\x1b[90mPATHS:\x1b[0m
|
|
182
|
+
- \x1b[1mMCP Config:\x1b[0m ${configPaths.map(p => p.path).join(', ')}
|
|
183
|
+
- \x1b[1mCache:\x1b[0m ${cacheRoot}
|
|
184
|
+
- \x1b[1mCheck status:\x1b[0m heuristic-mcp --logs
|
|
185
|
+
|
|
148
186
|
\x1b[36mHappy Coding! 🤖\x1b[0m
|
|
149
187
|
`);
|
|
188
|
+
forceLog(`\n\x1b[90m(Please wait while npm finalizes the installation...)\x1b[0m`);
|
|
150
189
|
}
|
|
151
190
|
}
|
package/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
-
import { stop, start, status } from "./features/lifecycle.js";
|
|
3
|
+
import { stop, start, status, logs } from "./features/lifecycle.js";
|
|
4
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
5
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
6
6
|
import { pipeline } from "@xenova/transformers";
|
|
@@ -41,6 +41,11 @@ if (args.includes('--status')) {
|
|
|
41
41
|
process.exit(0);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
if (args.includes('--logs')) {
|
|
45
|
+
await logs();
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
|
|
44
49
|
// Check if --register flag is present
|
|
45
50
|
if (args.includes('--register')) {
|
|
46
51
|
// Extract optional filter (e.g. --register antigravity)
|
|
@@ -134,6 +139,7 @@ async function initialize() {
|
|
|
134
139
|
|
|
135
140
|
// Initialize cache
|
|
136
141
|
cache = new EmbeddingsCache(config);
|
|
142
|
+
console.error(`[Server] Cache directory: ${config.cacheDirectory}`);
|
|
137
143
|
await cache.load();
|
|
138
144
|
|
|
139
145
|
// Initialize features
|
|
@@ -240,6 +246,20 @@ process.on('SIGINT', async () => {
|
|
|
240
246
|
|
|
241
247
|
process.on('SIGTERM', async () => {
|
|
242
248
|
console.error("\n[Server] Received SIGTERM, shutting down...");
|
|
249
|
+
|
|
250
|
+
// Stop file watcher
|
|
251
|
+
if (indexer && indexer.watcher) {
|
|
252
|
+
await indexer.watcher.close();
|
|
253
|
+
console.error("[Server] File watcher stopped");
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Save cache
|
|
257
|
+
if (cache) {
|
|
258
|
+
await cache.save();
|
|
259
|
+
console.error("[Server] Cache saved");
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
console.error("[Server] Goodbye!");
|
|
243
263
|
process.exit(0);
|
|
244
264
|
});
|
|
245
265
|
|
package/lib/cache.js
CHANGED
|
@@ -193,9 +193,16 @@ export class EmbeddingsCache {
|
|
|
193
193
|
const cacheFile = path.join(this.config.cacheDirectory, "embeddings.json");
|
|
194
194
|
const hashFile = path.join(this.config.cacheDirectory, "file-hashes.json");
|
|
195
195
|
const metaFile = path.join(this.config.cacheDirectory, CACHE_META_FILE);
|
|
196
|
+
|
|
197
|
+
// Include indexing stats in meta for verification
|
|
198
|
+
const uniqueFiles = new Set(this.vectorStore.map(chunk => chunk.file));
|
|
196
199
|
this.cacheMeta = {
|
|
197
200
|
version: CACHE_META_VERSION,
|
|
198
|
-
embeddingModel: this.config.embeddingModel
|
|
201
|
+
embeddingModel: this.config.embeddingModel,
|
|
202
|
+
lastSaveTime: new Date().toISOString(),
|
|
203
|
+
filesIndexed: uniqueFiles.size,
|
|
204
|
+
chunksStored: this.vectorStore.length,
|
|
205
|
+
workspace: this.config.searchDirectory || null
|
|
199
206
|
};
|
|
200
207
|
|
|
201
208
|
await Promise.all([
|
package/lib/config.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import fs from "fs/promises";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import crypto from "crypto";
|
|
3
5
|
import { fileURLToPath } from "url";
|
|
4
6
|
import { ProjectDetector } from "./project-detector.js";
|
|
5
7
|
|
|
@@ -58,7 +60,7 @@ const DEFAULT_CONFIG = {
|
|
|
58
60
|
maxFileSize: 1048576, // 1MB - skip files larger than this
|
|
59
61
|
maxResults: 5,
|
|
60
62
|
enableCache: true,
|
|
61
|
-
cacheDirectory:
|
|
63
|
+
cacheDirectory: null, // Will be set dynamically by loadConfig()
|
|
62
64
|
watchFiles: true,
|
|
63
65
|
verbose: false,
|
|
64
66
|
workerThreads: "auto", // "auto" = CPU cores - 1, or set a number
|
|
@@ -127,7 +129,34 @@ export async function loadConfig(workspaceDir = null) {
|
|
|
127
129
|
|
|
128
130
|
// Set search and cache directories
|
|
129
131
|
config.searchDirectory = baseDir;
|
|
130
|
-
|
|
132
|
+
|
|
133
|
+
// Determine cache directory
|
|
134
|
+
if (userConfig.cacheDirectory) {
|
|
135
|
+
// User explicitly set a cache path in their config.json
|
|
136
|
+
config.cacheDirectory = path.isAbsolute(userConfig.cacheDirectory)
|
|
137
|
+
? userConfig.cacheDirectory
|
|
138
|
+
: path.join(baseDir, userConfig.cacheDirectory);
|
|
139
|
+
} else {
|
|
140
|
+
// Use global cache directory to prevent cluttering project root
|
|
141
|
+
// Hash the absolute path to ensure uniqueness per project
|
|
142
|
+
const projectHash = crypto.createHash('md5').update(baseDir).digest('hex').slice(0, 12);
|
|
143
|
+
const globalCacheRoot = getGlobalCacheDir();
|
|
144
|
+
config.cacheDirectory = path.join(globalCacheRoot, "heuristic-mcp", projectHash);
|
|
145
|
+
|
|
146
|
+
// Support legacy .smart-coding-cache if it already exists in the project root
|
|
147
|
+
const legacyPath = path.join(baseDir, ".smart-coding-cache");
|
|
148
|
+
try {
|
|
149
|
+
const stats = await fs.stat(legacyPath);
|
|
150
|
+
if (stats.isDirectory()) {
|
|
151
|
+
config.cacheDirectory = legacyPath;
|
|
152
|
+
if (config.verbose) {
|
|
153
|
+
console.error(`[Config] Using existing local cache: ${legacyPath}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
} catch {
|
|
157
|
+
// Legacy folder doesn't exist, using global path
|
|
158
|
+
}
|
|
159
|
+
}
|
|
131
160
|
|
|
132
161
|
// Smart project detection
|
|
133
162
|
if (config.smartIndexing !== false) {
|
|
@@ -375,6 +404,19 @@ export async function loadConfig(workspaceDir = null) {
|
|
|
375
404
|
return config;
|
|
376
405
|
}
|
|
377
406
|
|
|
407
|
+
/**
|
|
408
|
+
* Get platform-specific global cache directory
|
|
409
|
+
*/
|
|
410
|
+
function getGlobalCacheDir() {
|
|
411
|
+
if (process.platform === 'win32') {
|
|
412
|
+
return process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
|
|
413
|
+
}
|
|
414
|
+
if (process.platform === 'darwin') {
|
|
415
|
+
return path.join(os.homedir(), 'Library', 'Caches');
|
|
416
|
+
}
|
|
417
|
+
return process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache');
|
|
418
|
+
}
|
|
419
|
+
|
|
378
420
|
export function getConfig() {
|
|
379
421
|
return config;
|
|
380
422
|
}
|
package/package.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softerist/heuristic-mcp",
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
3
|
+
"version": "2.1.23",
|
|
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
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"heuristic-mcp": "index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node index.js",
|
|
12
|
+
"dev": "node --watch index.js",
|
|
13
|
+
"test": "vitest run",
|
|
14
|
+
"test:watch": "vitest",
|
|
15
|
+
"clear-cache": "node scripts/clear-cache.js",
|
|
16
|
+
"postinstall": "node scripts/postinstall.js"
|
|
17
|
+
},
|
|
18
18
|
"keywords": [
|
|
19
19
|
"mcp",
|
|
20
20
|
"semantic-search",
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { register } from '../features/register.js';
|
|
2
|
+
|
|
3
|
+
// Run the registration process
|
|
4
|
+
console.log('[PostInstall] Running Heuristic MCP registration...');
|
|
5
|
+
register().catch(err => {
|
|
6
|
+
console.error('[PostInstall] Registration failed:', err.message);
|
|
7
|
+
// Don't fail the install if registration fails, just warn
|
|
8
|
+
process.exit(0);
|
|
9
|
+
});
|