specmem-hardwicksoftware 3.7.17 → 3.7.20

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/mcp-proxy.cjs CHANGED
@@ -53,50 +53,84 @@ function parseMessages(buffer) {
53
53
  let remaining = buffer;
54
54
 
55
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
- }
56
+ remaining = remaining.trimStart();
57
+ if (remaining.length === 0) break;
58
+
59
+ // Mode 1: Content-Length framed (MCP spec)
60
+ if (remaining.startsWith('Content-Length:')) {
61
+ const headerEnd = remaining.indexOf('\r\n\r\n');
62
+ if (headerEnd === -1) break;
63
+
64
+ const header = remaining.substring(0, headerEnd);
65
+ const match = header.match(/Content-Length:\s*(\d+)/i);
66
+ if (!match) {
67
+ remaining = remaining.substring(headerEnd + 4);
68
+ continue;
69
+ }
70
+
71
+ const contentLength = parseInt(match[1], 10);
72
+ const bodyStart = headerEnd + 4;
73
+ const bodyEnd = bodyStart + contentLength;
67
74
 
68
- const contentLength = parseInt(match[1], 10);
69
- const bodyStart = headerEnd + 4;
70
- const bodyEnd = bodyStart + contentLength;
75
+ if (remaining.length < bodyEnd) break;
71
76
 
72
- if (remaining.length < bodyEnd) {
73
- break; // Incomplete message, wait for more data
77
+ const body = remaining.substring(bodyStart, bodyEnd);
78
+ remaining = remaining.substring(bodyEnd);
79
+
80
+ try {
81
+ messages.push(JSON.parse(body));
82
+ } catch (e) {
83
+ log(`Parse error (framed): ${e.message}`);
84
+ }
85
+ continue;
74
86
  }
75
87
 
76
- const body = remaining.substring(bodyStart, bodyEnd);
77
- remaining = remaining.substring(bodyEnd);
88
+ // Mode 2: Raw JSON (newline-delimited) — Claude Code sends this
89
+ if (remaining[0] === '{') {
90
+ // Find the end of this JSON object by tracking braces
91
+ let depth = 0;
92
+ let inString = false;
93
+ let escape = false;
94
+ let end = -1;
95
+ for (let i = 0; i < remaining.length; i++) {
96
+ const ch = remaining[i];
97
+ if (escape) { escape = false; continue; }
98
+ if (ch === '\\' && inString) { escape = true; continue; }
99
+ if (ch === '"') { inString = !inString; continue; }
100
+ if (inString) continue;
101
+ if (ch === '{') depth++;
102
+ if (ch === '}') { depth--; if (depth === 0) { end = i + 1; break; } }
103
+ }
104
+ if (end === -1) break; // Incomplete JSON
105
+
106
+ const jsonStr = remaining.substring(0, end);
107
+ remaining = remaining.substring(end);
78
108
 
79
- try {
80
- messages.push(JSON.parse(body));
81
- } catch (e) {
82
- log(`Parse error: ${e.message} body=${body.substring(0, 100)}`);
109
+ try {
110
+ messages.push(JSON.parse(jsonStr));
111
+ } catch (e) {
112
+ log(`Parse error (raw): ${e.message}`);
113
+ }
114
+ continue;
83
115
  }
116
+
117
+ // Skip unknown byte
118
+ remaining = remaining.substring(1);
84
119
  }
85
120
 
86
121
  return { messages, remaining };
87
122
  }
88
123
 
89
- function frameMessage(obj) {
90
- const body = JSON.stringify(obj);
91
- return `Content-Length: ${Buffer.byteLength(body)}\r\n\r\n${body}`;
124
+ function serializeMessage(obj) {
125
+ return JSON.stringify(obj) + '\n';
92
126
  }
93
127
 
94
128
  // ============================================================================
95
- // Send message to Claude (stdout)
129
+ // Send message to Claude (stdout) — newline-delimited JSON per MCP SDK
96
130
  // ============================================================================
97
131
  function sendToClient(msg) {
98
132
  try {
99
- process.stdout.write(frameMessage(msg));
133
+ process.stdout.write(serializeMessage(msg));
100
134
  } catch (e) {
101
135
  log(`stdout write error: ${e.message}`);
102
136
  }
@@ -130,7 +164,7 @@ function sendToServer(msg) {
130
164
  }
131
165
 
132
166
  try {
133
- child.stdin.write(frameMessage(msg));
167
+ child.stdin.write(serializeMessage(msg));
134
168
  } catch (e) {
135
169
  log(`child stdin write error: ${e.message}`);
136
170
  pendingQueue.push(msg);
@@ -170,8 +204,8 @@ function spawnServer() {
170
204
  // CRITICAL: Do NOT hardcode --max-old-space-size here
171
205
  // The proxy's own heap limit is set by Claude config args (e.g. --max-old-space-size=250)
172
206
  // but the child bootstrap needs MORE memory for all its initialization
173
- const heapLimit = process.env.SPECMEM_MAX_HEAP_MB || '512';
174
- child = spawn('node', [`--max-old-space-size=${heapLimit}`, BOOTSTRAP_PATH, ...args], {
207
+ const heapLimit = process.env.SPECMEM_MAX_HEAP_MB || '1024';
208
+ child = spawn('node', ['--expose-gc', `--max-old-space-size=${heapLimit}`, BOOTSTRAP_PATH, ...args], {
175
209
  env,
176
210
  stdio: ['pipe', 'pipe', 'pipe'],
177
211
  cwd: process.env.SPECMEM_PROJECT_PATH || process.cwd()
@@ -183,9 +217,12 @@ function spawnServer() {
183
217
  });
184
218
 
185
219
  child.stdout.on('data', (data) => {
186
- childStdoutBuffer += data.toString();
220
+ const raw = data.toString();
221
+ log(`CHILD STDOUT (${raw.length} bytes): ${raw.substring(0, 200)}`);
222
+ childStdoutBuffer += raw;
187
223
 
188
224
  const { messages, remaining } = parseMessages(childStdoutBuffer);
225
+ log(`CHILD PARSED: ${messages.length} messages, ${remaining.length} bytes remaining`);
189
226
  childStdoutBuffer = remaining;
190
227
 
191
228
  for (const msg of messages) {
@@ -224,7 +261,15 @@ function spawnServer() {
224
261
  stopHeartbeat();
225
262
 
226
263
  if (!shuttingDown) {
227
- scheduleRestart();
264
+ // If bootstrap was intentionally killed (SIGTERM/SIGKILL from init or system),
265
+ // don't restart — exit the proxy too. Init will start a fresh bootstrap.
266
+ // Only restart on crashes (non-zero exit code without signal).
267
+ if (signal === 'SIGTERM' || signal === 'SIGKILL') {
268
+ log(`Bootstrap was intentionally killed (${signal}) — proxy exiting`);
269
+ shutdown();
270
+ } else {
271
+ scheduleRestart();
272
+ }
228
273
  }
229
274
  });
230
275
 
@@ -234,12 +279,12 @@ function spawnServer() {
234
279
  setTimeout(() => {
235
280
  if (child && !child.killed) {
236
281
  try {
237
- child.stdin.write(frameMessage(lastInitializeRequest));
282
+ child.stdin.write(serializeMessage(lastInitializeRequest));
238
283
  // Also send initialized notification
239
284
  setTimeout(() => {
240
285
  if (child && !child.killed) {
241
286
  try {
242
- child.stdin.write(frameMessage({ jsonrpc: '2.0', method: 'notifications/initialized' }));
287
+ child.stdin.write(serializeMessage({ jsonrpc: '2.0', method: 'notifications/initialized' }));
243
288
  } catch {}
244
289
  }
245
290
  }, 100);
@@ -292,9 +337,12 @@ function stopHeartbeat() {
292
337
  // Handle stdin from Claude
293
338
  // ============================================================================
294
339
  process.stdin.on('data', (data) => {
295
- stdinBuffer += data.toString();
340
+ const raw = data.toString();
341
+ log(`STDIN RAW (${raw.length} bytes): ${raw.substring(0, 200)}`);
342
+ stdinBuffer += raw;
296
343
 
297
344
  const { messages, remaining } = parseMessages(stdinBuffer);
345
+ log(`STDIN PARSED: ${messages.length} messages, ${remaining.length} bytes remaining`);
298
346
  stdinBuffer = remaining;
299
347
 
300
348
  for (const msg of messages) {
@@ -346,6 +394,15 @@ function shutdown() {
346
394
  process.on('SIGTERM', shutdown);
347
395
  process.on('SIGINT', shutdown);
348
396
 
397
+ // Orphan detection: if parent (Claude) dies, proxy gets reparented to PID 1
398
+ // Check every 10s and exit if orphaned
399
+ setInterval(() => {
400
+ if (process.ppid === 1) {
401
+ log('Parent died (PPID=1), proxy shutting down');
402
+ shutdown();
403
+ }
404
+ }, 10000);
405
+
349
406
  // ============================================================================
350
407
  // Start
351
408
  // ============================================================================
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "specmem-hardwicksoftware",
3
- "version": "3.7.17",
3
+ "version": "3.7.20",
4
4
  "type": "module",
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",
5
+ "description": "Your Claude Code sessions don't have to start from scratch anymore — SpecMem gives your AI real memory. It won't forget your conversations, your code, or your architecture decisions between sessions. That's the whole point. Semantic code indexing that actually works: TypeScript, JavaScript, Python, Go, Rust, Java, Kotlin, C, C++, HTML and more. It doesn't just track functions — it gets classes, methods, fields, constants, enums, macros, imports, structs, the whole codebase graph. There's chat memory too, powered by pgvector embeddings. You've also got token compression, team coordination, multi-agent comms, and file watching built in. 74+ MCP tools. Runs on PostgreSQL + Docker. It's kind of a big deal. justcalljon.pro",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "bin": {
@@ -44,7 +44,18 @@
44
44
  "anthropic",
45
45
  "embeddings",
46
46
  "context",
47
- "hooks"
47
+ "hooks",
48
+ "code-indexing",
49
+ "typescript",
50
+ "javascript",
51
+ "python",
52
+ "java",
53
+ "cpp",
54
+ "rust",
55
+ "html",
56
+ "codebase-analysis",
57
+ "multi-language",
58
+ "team-coordination"
48
59
  ],
49
60
  "author": {
50
61
  "name": "Jonathan Hardwick",
@@ -6531,7 +6531,7 @@ async function runAutoSetup(projectPath) {
6531
6531
  SPECMEM_DB_NAME: "specmem_westayunprofessional",
6532
6532
  SPECMEM_DB_USER: "specmem_westayunprofessional",
6533
6533
  SPECMEM_DB_PASSWORD: "specmem_westayunprofessional",
6534
- SPECMEM_MAX_HEAP_MB: "512"
6534
+ SPECMEM_MAX_HEAP_MB: "1024"
6535
6535
  }
6536
6536
  };
6537
6537
  claudeJson.projects[projectPath].hasTrustDialogAccepted = true;