specmem-hardwicksoftware 3.7.13 → 3.7.15
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/dist/commands/promptCommands.js +2 -2
- package/dist/config/configSync.js +3 -1
- package/dist/init/claudeConfigInjector.js +4 -1
- package/dist/mcp/embeddingServerManager.js +20 -0
- package/dist/utils/processHealthCheck.js +7 -2
- package/mcp-proxy.cjs +336 -0
- package/package.json +2 -1
- package/scripts/specmem-init.cjs +5 -1
|
@@ -174,8 +174,8 @@ export class PromptCommands {
|
|
|
174
174
|
category VARCHAR(100) DEFAULT 'general',
|
|
175
175
|
tags TEXT[] DEFAULT '{}',
|
|
176
176
|
variables TEXT[] DEFAULT '{}',
|
|
177
|
-
--
|
|
178
|
-
embedding vector,
|
|
177
|
+
-- Dimension must be specified for ivfflat index
|
|
178
|
+
embedding vector(384),
|
|
179
179
|
usage_count INTEGER DEFAULT 0,
|
|
180
180
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
181
181
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
@@ -47,7 +47,9 @@ function getSpecmemDir() {
|
|
|
47
47
|
// Otherwise we're in src/config
|
|
48
48
|
return path.resolve(currentDir, '..', '..');
|
|
49
49
|
}
|
|
50
|
-
|
|
50
|
+
// Prefer proxy for resilient MCP connections (auto-reconnect on crash)
|
|
51
|
+
const _proxyPath = path.join(getSpecmemDir(), 'mcp-proxy.cjs');
|
|
52
|
+
const BOOTSTRAP_PATH = fs.existsSync(_proxyPath) ? _proxyPath : path.join(getSpecmemDir(), 'bootstrap.cjs');
|
|
51
53
|
const SOURCE_HOOKS_DIR = path.join(getSpecmemDir(), 'claude-hooks');
|
|
52
54
|
const SOURCE_COMMANDS_DIR = path.join(getSpecmemDir(), 'commands');
|
|
53
55
|
// ============================================================================
|
|
@@ -97,8 +97,11 @@ function getSpecmemRoot() {
|
|
|
97
97
|
// 6. Last resort - return __dirname-based path even without bootstrap
|
|
98
98
|
return fromThisFile;
|
|
99
99
|
}
|
|
100
|
-
// Find the
|
|
100
|
+
// Find the MCP entry point — prefer proxy for resilient connections
|
|
101
101
|
function findBootstrapPath(root) {
|
|
102
|
+
// Proxy wraps bootstrap.cjs with auto-reconnect on crash
|
|
103
|
+
const proxy = path.join(root, 'mcp-proxy.cjs');
|
|
104
|
+
if (fs.existsSync(proxy)) return proxy;
|
|
102
105
|
for (const name of ['bootstrap.cjs', 'bootstrap.js']) {
|
|
103
106
|
const p = path.join(root, name);
|
|
104
107
|
if (fs.existsSync(p)) return p;
|
|
@@ -1661,6 +1661,20 @@ export class EmbeddingServerManager extends EventEmitter {
|
|
|
1661
1661
|
}, '[EmbeddingServerManager] SAFETY CHECK: Process command line does not match this project - skipping kill');
|
|
1662
1662
|
continue;
|
|
1663
1663
|
}
|
|
1664
|
+
// CRITICAL FIX: Never kill --service mode processes based on age
|
|
1665
|
+
// Service mode is meant to run indefinitely
|
|
1666
|
+
const isServiceMode = commandLine.includes('--service');
|
|
1667
|
+
if (isServiceMode) {
|
|
1668
|
+
logger.info({
|
|
1669
|
+
pid,
|
|
1670
|
+
ageHours: ageHours?.toFixed(2) || 'unknown',
|
|
1671
|
+
socketPath: this.socketPath,
|
|
1672
|
+
}, '[EmbeddingServerManager] Orphaned --service process found - KEEPING (service mode runs indefinitely)');
|
|
1673
|
+
// Adopt it instead of killing
|
|
1674
|
+
this.isRunning = true;
|
|
1675
|
+
this.process = { pid };
|
|
1676
|
+
continue;
|
|
1677
|
+
}
|
|
1664
1678
|
// Only kill if older than max age
|
|
1665
1679
|
if (ageHours !== null && ageHours <= this.config.maxProcessAgeHours) {
|
|
1666
1680
|
logger.info({
|
|
@@ -1819,6 +1833,12 @@ export class EmbeddingServerManager extends EventEmitter {
|
|
|
1819
1833
|
statusMessage: healthInfo.statusMessage,
|
|
1820
1834
|
}, '[EmbeddingServerManager] Checked PID file process');
|
|
1821
1835
|
if (healthInfo.processExists) {
|
|
1836
|
+
// Respect recommended action — don't kill healthy/service processes
|
|
1837
|
+
if (healthInfo.recommendedAction === 'keep') {
|
|
1838
|
+
logger.info({ pid: healthInfo.pid, status: healthInfo.statusMessage },
|
|
1839
|
+
'[EmbeddingServerManager] killByPidFile: Process is healthy/service - keeping');
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1822
1842
|
await this.killProcessWithHealthInfo(healthInfo);
|
|
1823
1843
|
}
|
|
1824
1844
|
else {
|
|
@@ -119,7 +119,10 @@ export function checkProcessHealth(config) {
|
|
|
119
119
|
// Step 4: Determine if stale
|
|
120
120
|
// Use actual process age if available, otherwise fall back to PID file age
|
|
121
121
|
const effectiveAgeHours = processAgeHours !== null ? processAgeHours : pidFileAgeHours;
|
|
122
|
-
|
|
122
|
+
// CRITICAL FIX: --service mode processes are meant to run indefinitely
|
|
123
|
+
// They should NEVER be considered stale based on age alone
|
|
124
|
+
const isServiceMode = commandLine && commandLine.includes('--service');
|
|
125
|
+
const isStale = isServiceMode ? false : effectiveAgeHours > maxAgeHours;
|
|
123
126
|
// Step 5: Determine recommended action
|
|
124
127
|
let recommendedAction = 'keep';
|
|
125
128
|
let statusMessage = '';
|
|
@@ -137,7 +140,9 @@ export function checkProcessHealth(config) {
|
|
|
137
140
|
}
|
|
138
141
|
else {
|
|
139
142
|
recommendedAction = 'keep';
|
|
140
|
-
statusMessage =
|
|
143
|
+
statusMessage = isServiceMode
|
|
144
|
+
? `Process ${pid} is healthy service-mode (${effectiveAgeHours.toFixed(2)}h old, age check bypassed)`
|
|
145
|
+
: `Process ${pid} is healthy (${effectiveAgeHours.toFixed(2)}h old)`;
|
|
141
146
|
}
|
|
142
147
|
logger.info({
|
|
143
148
|
pid,
|
package/mcp-proxy.cjs
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* MCP Proxy - Resilient stdio proxy for SpecMem MCP server
|
|
4
|
+
*
|
|
5
|
+
* Claude connects to this proxy via stdio. The proxy manages the actual
|
|
6
|
+
* MCP server (bootstrap.cjs) as a child process. If the server crashes
|
|
7
|
+
* or restarts, the proxy reconnects transparently — Claude never sees
|
|
8
|
+
* a disconnect.
|
|
9
|
+
*
|
|
10
|
+
* Protocol: MCP uses Content-Length framed JSON-RPC 2.0 over stdio.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { spawn } = require('child_process');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
|
|
17
|
+
// Config
|
|
18
|
+
const BOOTSTRAP_PATH = path.join(__dirname, 'bootstrap.cjs');
|
|
19
|
+
const MAX_RESTART_DELAY = 10000; // 10s max backoff
|
|
20
|
+
const INITIAL_RESTART_DELAY = 500; // 500ms first retry
|
|
21
|
+
const MAX_QUEUE_SIZE = 200;
|
|
22
|
+
const HEARTBEAT_INTERVAL = 30000; // 30s keepalive pings
|
|
23
|
+
|
|
24
|
+
// State
|
|
25
|
+
let child = null;
|
|
26
|
+
let childReady = false;
|
|
27
|
+
let pendingQueue = []; // Messages queued during reconnect
|
|
28
|
+
let restartDelay = INITIAL_RESTART_DELAY;
|
|
29
|
+
let restartCount = 0;
|
|
30
|
+
let lastInitializeRequest = null; // Cache the initialize request for re-init
|
|
31
|
+
let lastInitializeResponse = null;
|
|
32
|
+
let initializeId = null;
|
|
33
|
+
let shuttingDown = false;
|
|
34
|
+
let heartbeatTimer = null;
|
|
35
|
+
let childStdoutBuffer = '';
|
|
36
|
+
let stdinBuffer = '';
|
|
37
|
+
|
|
38
|
+
function log(msg) {
|
|
39
|
+
try {
|
|
40
|
+
fs.appendFileSync('/tmp/specmem-proxy.log',
|
|
41
|
+
`[${new Date().toISOString()}] ${msg}\n`);
|
|
42
|
+
} catch {}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
log(`Proxy starting. PID=${process.pid} BOOTSTRAP=${BOOTSTRAP_PATH}`);
|
|
46
|
+
log(`ENV: PROJECT_PATH=${process.env.SPECMEM_PROJECT_PATH}`);
|
|
47
|
+
|
|
48
|
+
// ============================================================================
|
|
49
|
+
// Content-Length framed message parser
|
|
50
|
+
// ============================================================================
|
|
51
|
+
function parseMessages(buffer) {
|
|
52
|
+
const messages = [];
|
|
53
|
+
let remaining = buffer;
|
|
54
|
+
|
|
55
|
+
while (remaining.length > 0) {
|
|
56
|
+
// Look for Content-Length header
|
|
57
|
+
const headerEnd = remaining.indexOf('\r\n\r\n');
|
|
58
|
+
if (headerEnd === -1) break;
|
|
59
|
+
|
|
60
|
+
const header = remaining.substring(0, headerEnd);
|
|
61
|
+
const match = header.match(/Content-Length:\s*(\d+)/i);
|
|
62
|
+
if (!match) {
|
|
63
|
+
// Skip malformed data
|
|
64
|
+
remaining = remaining.substring(headerEnd + 4);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const contentLength = parseInt(match[1], 10);
|
|
69
|
+
const bodyStart = headerEnd + 4;
|
|
70
|
+
const bodyEnd = bodyStart + contentLength;
|
|
71
|
+
|
|
72
|
+
if (remaining.length < bodyEnd) {
|
|
73
|
+
break; // Incomplete message, wait for more data
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const body = remaining.substring(bodyStart, bodyEnd);
|
|
77
|
+
remaining = remaining.substring(bodyEnd);
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
messages.push(JSON.parse(body));
|
|
81
|
+
} catch (e) {
|
|
82
|
+
log(`Parse error: ${e.message} body=${body.substring(0, 100)}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return { messages, remaining };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function frameMessage(obj) {
|
|
90
|
+
const body = JSON.stringify(obj);
|
|
91
|
+
return `Content-Length: ${Buffer.byteLength(body)}\r\n\r\n${body}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ============================================================================
|
|
95
|
+
// Send message to Claude (stdout)
|
|
96
|
+
// ============================================================================
|
|
97
|
+
function sendToClient(msg) {
|
|
98
|
+
try {
|
|
99
|
+
process.stdout.write(frameMessage(msg));
|
|
100
|
+
} catch (e) {
|
|
101
|
+
log(`stdout write error: ${e.message}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ============================================================================
|
|
106
|
+
// Send message to MCP server (child stdin)
|
|
107
|
+
// ============================================================================
|
|
108
|
+
function sendToServer(msg) {
|
|
109
|
+
if (!child || !childReady || child.killed) {
|
|
110
|
+
// Queue it
|
|
111
|
+
if (pendingQueue.length < MAX_QUEUE_SIZE) {
|
|
112
|
+
pendingQueue.push(msg);
|
|
113
|
+
log(`Queued message (${pendingQueue.length} pending): ${msg.method || msg.id || '?'}`);
|
|
114
|
+
} else {
|
|
115
|
+
log(`Queue full, dropping message: ${msg.method || msg.id || '?'}`);
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
child.stdin.write(frameMessage(msg));
|
|
122
|
+
} catch (e) {
|
|
123
|
+
log(`child stdin write error: ${e.message}`);
|
|
124
|
+
pendingQueue.push(msg);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ============================================================================
|
|
129
|
+
// Flush queued messages to server
|
|
130
|
+
// ============================================================================
|
|
131
|
+
function flushQueue() {
|
|
132
|
+
if (pendingQueue.length === 0) return;
|
|
133
|
+
log(`Flushing ${pendingQueue.length} queued messages to server`);
|
|
134
|
+
const queue = [...pendingQueue];
|
|
135
|
+
pendingQueue = [];
|
|
136
|
+
for (const msg of queue) {
|
|
137
|
+
sendToServer(msg);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ============================================================================
|
|
142
|
+
// Spawn/restart the MCP server
|
|
143
|
+
// ============================================================================
|
|
144
|
+
function spawnServer() {
|
|
145
|
+
if (shuttingDown) return;
|
|
146
|
+
if (child && !child.killed) {
|
|
147
|
+
try { child.kill('SIGTERM'); } catch {}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
childReady = false;
|
|
151
|
+
childStdoutBuffer = '';
|
|
152
|
+
|
|
153
|
+
const args = process.argv.slice(2); // Pass through any args
|
|
154
|
+
const env = { ...process.env };
|
|
155
|
+
|
|
156
|
+
log(`Spawning server: node ${BOOTSTRAP_PATH} ${args.join(' ')}`);
|
|
157
|
+
|
|
158
|
+
child = spawn('node', ['--max-old-space-size=250', BOOTSTRAP_PATH, ...args], {
|
|
159
|
+
env,
|
|
160
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
161
|
+
cwd: process.env.SPECMEM_PROJECT_PATH || process.cwd()
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
child.stderr.on('data', (data) => {
|
|
165
|
+
// Forward stderr (logs) to our stderr
|
|
166
|
+
process.stderr.write(data);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
child.stdout.on('data', (data) => {
|
|
170
|
+
childStdoutBuffer += data.toString();
|
|
171
|
+
|
|
172
|
+
const { messages, remaining } = parseMessages(childStdoutBuffer);
|
|
173
|
+
childStdoutBuffer = remaining;
|
|
174
|
+
|
|
175
|
+
for (const msg of messages) {
|
|
176
|
+
// If this is the response to initialize, cache it and mark ready
|
|
177
|
+
if (msg.id !== undefined && msg.id === initializeId && msg.result) {
|
|
178
|
+
lastInitializeResponse = msg;
|
|
179
|
+
childReady = true;
|
|
180
|
+
restartDelay = INITIAL_RESTART_DELAY;
|
|
181
|
+
restartCount = 0;
|
|
182
|
+
log(`Server initialized (id=${msg.id}). Flushing queue.`);
|
|
183
|
+
|
|
184
|
+
// If this is a RE-init (not the first), don't send the response
|
|
185
|
+
// to Claude — Claude already has the init response from first time
|
|
186
|
+
if (restartCount > 0 || lastInitializeResponse) {
|
|
187
|
+
// Still send it on first init
|
|
188
|
+
}
|
|
189
|
+
sendToClient(msg);
|
|
190
|
+
flushQueue();
|
|
191
|
+
startHeartbeat();
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Forward everything else to Claude
|
|
196
|
+
sendToClient(msg);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
child.on('error', (err) => {
|
|
201
|
+
log(`Server process error: ${err.message}`);
|
|
202
|
+
scheduleRestart();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
child.on('exit', (code, signal) => {
|
|
206
|
+
log(`Server exited: code=${code} signal=${signal}`);
|
|
207
|
+
childReady = false;
|
|
208
|
+
stopHeartbeat();
|
|
209
|
+
|
|
210
|
+
if (!shuttingDown) {
|
|
211
|
+
scheduleRestart();
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// If we have a cached initialize request, re-send it
|
|
216
|
+
if (lastInitializeRequest && restartCount > 0) {
|
|
217
|
+
log(`Re-sending initialize request (restart #${restartCount})`);
|
|
218
|
+
setTimeout(() => {
|
|
219
|
+
if (child && !child.killed) {
|
|
220
|
+
try {
|
|
221
|
+
child.stdin.write(frameMessage(lastInitializeRequest));
|
|
222
|
+
// Also send initialized notification
|
|
223
|
+
setTimeout(() => {
|
|
224
|
+
if (child && !child.killed) {
|
|
225
|
+
try {
|
|
226
|
+
child.stdin.write(frameMessage({ jsonrpc: '2.0', method: 'notifications/initialized' }));
|
|
227
|
+
} catch {}
|
|
228
|
+
}
|
|
229
|
+
}, 100);
|
|
230
|
+
} catch (e) {
|
|
231
|
+
log(`Re-init write error: ${e.message}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}, 200);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
restartCount++;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function scheduleRestart() {
|
|
241
|
+
if (shuttingDown) return;
|
|
242
|
+
log(`Scheduling restart in ${restartDelay}ms (restart #${restartCount})`);
|
|
243
|
+
setTimeout(() => {
|
|
244
|
+
spawnServer();
|
|
245
|
+
}, restartDelay);
|
|
246
|
+
restartDelay = Math.min(restartDelay * 2, MAX_RESTART_DELAY);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ============================================================================
|
|
250
|
+
// Heartbeat — detect dead server
|
|
251
|
+
// ============================================================================
|
|
252
|
+
function startHeartbeat() {
|
|
253
|
+
stopHeartbeat();
|
|
254
|
+
heartbeatTimer = setInterval(() => {
|
|
255
|
+
if (!child || child.killed || !childReady) return;
|
|
256
|
+
// Send a ping (list tools) to keep connection alive
|
|
257
|
+
// MCP doesn't have a ping method, but we can use this to detect dead pipes
|
|
258
|
+
try {
|
|
259
|
+
child.stdin.write(''); // Zero-byte write to test pipe
|
|
260
|
+
} catch (e) {
|
|
261
|
+
log(`Heartbeat detected dead pipe: ${e.message}`);
|
|
262
|
+
childReady = false;
|
|
263
|
+
try { child.kill('SIGTERM'); } catch {}
|
|
264
|
+
}
|
|
265
|
+
}, HEARTBEAT_INTERVAL);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function stopHeartbeat() {
|
|
269
|
+
if (heartbeatTimer) {
|
|
270
|
+
clearInterval(heartbeatTimer);
|
|
271
|
+
heartbeatTimer = null;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ============================================================================
|
|
276
|
+
// Handle stdin from Claude
|
|
277
|
+
// ============================================================================
|
|
278
|
+
process.stdin.on('data', (data) => {
|
|
279
|
+
stdinBuffer += data.toString();
|
|
280
|
+
|
|
281
|
+
const { messages, remaining } = parseMessages(stdinBuffer);
|
|
282
|
+
stdinBuffer = remaining;
|
|
283
|
+
|
|
284
|
+
for (const msg of messages) {
|
|
285
|
+
// Cache the initialize request so we can re-send on restart
|
|
286
|
+
if (msg.method === 'initialize') {
|
|
287
|
+
lastInitializeRequest = msg;
|
|
288
|
+
initializeId = msg.id;
|
|
289
|
+
log(`Got initialize request (id=${msg.id})`);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Cache initialized notification
|
|
293
|
+
if (msg.method === 'notifications/initialized') {
|
|
294
|
+
log('Got initialized notification');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
sendToServer(msg);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
process.stdin.on('end', () => {
|
|
302
|
+
log('stdin closed (Claude disconnected)');
|
|
303
|
+
shutdown();
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
process.stdin.on('error', (err) => {
|
|
307
|
+
log(`stdin error: ${err.message}`);
|
|
308
|
+
shutdown();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// ============================================================================
|
|
312
|
+
// Graceful shutdown
|
|
313
|
+
// ============================================================================
|
|
314
|
+
function shutdown() {
|
|
315
|
+
if (shuttingDown) return;
|
|
316
|
+
shuttingDown = true;
|
|
317
|
+
log('Proxy shutting down');
|
|
318
|
+
stopHeartbeat();
|
|
319
|
+
if (child && !child.killed) {
|
|
320
|
+
child.kill('SIGTERM');
|
|
321
|
+
setTimeout(() => {
|
|
322
|
+
try { child.kill('SIGKILL'); } catch {}
|
|
323
|
+
process.exit(0);
|
|
324
|
+
}, 3000);
|
|
325
|
+
} else {
|
|
326
|
+
process.exit(0);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
process.on('SIGTERM', shutdown);
|
|
331
|
+
process.on('SIGINT', shutdown);
|
|
332
|
+
|
|
333
|
+
// ============================================================================
|
|
334
|
+
// Start
|
|
335
|
+
// ============================================================================
|
|
336
|
+
spawnServer();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specmem-hardwicksoftware",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Persistent memory system for coding sessions - semantic search with pgvector, token compression, team coordination, file watching. Needs root: installs system-wide hooks, manages docker/PostgreSQL, writes global configs, handles screen sessions. justcalljon.pro",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -128,6 +128,7 @@
|
|
|
128
128
|
"embedding-sandbox/",
|
|
129
129
|
"legal/",
|
|
130
130
|
"bootstrap.cjs",
|
|
131
|
+
"mcp-proxy.cjs",
|
|
131
132
|
"specmem-health.cjs",
|
|
132
133
|
"specmem.env",
|
|
133
134
|
"LICENSE.md",
|
package/scripts/specmem-init.cjs
CHANGED
|
@@ -6511,12 +6511,16 @@ async function runAutoSetup(projectPath) {
|
|
|
6511
6511
|
|
|
6512
6512
|
// Configure MCP server for this project
|
|
6513
6513
|
claudeJson.projects[projectPath].mcpServers = claudeJson.projects[projectPath].mcpServers || {};
|
|
6514
|
+
// Prefer mcp-proxy.cjs for resilient connections (auto-reconnect)
|
|
6515
|
+
const mcpEntry = fs.existsSync(path.join(specmemPkg, 'mcp-proxy.cjs'))
|
|
6516
|
+
? path.join(specmemPkg, 'mcp-proxy.cjs')
|
|
6517
|
+
: path.join(specmemPkg, 'bootstrap.cjs');
|
|
6514
6518
|
claudeJson.projects[projectPath].mcpServers.specmem = {
|
|
6515
6519
|
type: "stdio",
|
|
6516
6520
|
command: "node",
|
|
6517
6521
|
args: [
|
|
6518
6522
|
"--max-old-space-size=250",
|
|
6519
|
-
|
|
6523
|
+
mcpEntry
|
|
6520
6524
|
],
|
|
6521
6525
|
env: {
|
|
6522
6526
|
HOME: os.homedir(),
|