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/README.md +87 -8
- package/dist/codebase/codeAnalyzer.js +1155 -0
- package/dist/codebase/codebaseIndexer.js +1 -1
- package/dist/database.js +12 -1
- package/dist/mcp/toolRegistry.js +4 -2
- package/dist/tools/goofy/exportProjectMemories.js +243 -0
- package/dist/tools/goofy/findWhatISaid.js +1 -1
- package/dist/tools/goofy/importProjectMemories.js +9 -9
- package/embedding-sandbox/frankenstein-embeddings.py +32 -16
- package/embedding-sandbox/server.mjs +40 -7
- package/mcp-proxy.cjs +92 -35
- package/package.json +14 -3
- package/scripts/specmem-init.cjs +1 -1
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
69
|
-
const bodyStart = headerEnd + 4;
|
|
70
|
-
const bodyEnd = bodyStart + contentLength;
|
|
75
|
+
if (remaining.length < bodyEnd) break;
|
|
71
76
|
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
77
|
-
remaining
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
90
|
-
|
|
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(
|
|
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(
|
|
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 || '
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "3.7.20",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
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",
|
package/scripts/specmem-init.cjs
CHANGED
|
@@ -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: "
|
|
6534
|
+
SPECMEM_MAX_HEAP_MB: "1024"
|
|
6535
6535
|
}
|
|
6536
6536
|
};
|
|
6537
6537
|
claudeJson.projects[projectPath].hasTrustDialogAccepted = true;
|