specmem-hardwicksoftware 3.7.16 → 3.7.19
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/database.js
CHANGED
|
@@ -724,8 +724,16 @@ export class DatabaseManager {
|
|
|
724
724
|
let success = true;
|
|
725
725
|
let errorMsg;
|
|
726
726
|
let rowsAffected;
|
|
727
|
+
// CRITICAL FIX: Use dedicated client with ensureSearchPath to prevent
|
|
728
|
+
// schema cross-contamination. pool.query() can race with the fire-and-forget
|
|
729
|
+
// search_path set in pool.on('connect'), causing writes to wrong schema.
|
|
730
|
+
const client = await this.pool.connect();
|
|
727
731
|
try {
|
|
728
|
-
|
|
732
|
+
if (this.currentSchema) {
|
|
733
|
+
const safeSchema = '"' + this.currentSchema.replace(/"/g, '""') + '"';
|
|
734
|
+
await client.query('SET search_path TO ' + safeSchema + ', public');
|
|
735
|
+
}
|
|
736
|
+
const result = await client.query(text, params);
|
|
729
737
|
rowsAffected = result.rowCount ?? undefined;
|
|
730
738
|
const duration = Date.now() - start;
|
|
731
739
|
// Emit db:query:complete event via LWJEB
|
|
@@ -747,6 +755,9 @@ export class DatabaseManager {
|
|
|
747
755
|
coordinator?.emitDBQueryComplete(queryId, queryType, duration, false, undefined, errorMsg);
|
|
748
756
|
throw error;
|
|
749
757
|
}
|
|
758
|
+
finally {
|
|
759
|
+
client.release();
|
|
760
|
+
}
|
|
750
761
|
}
|
|
751
762
|
/**
|
|
752
763
|
* Execute query with GUARANTEED schema isolation.
|
package/dist/mcp/toolRegistry.js
CHANGED
|
@@ -54,8 +54,9 @@ import { SmartSearch } from '../tools/goofy/smartSearch.js';
|
|
|
54
54
|
// Import memory drilldown tools - gallery view + full drill-down
|
|
55
55
|
import { FindMemoryGallery } from '../tools/goofy/findMemoryGallery.js';
|
|
56
56
|
import { GetMemoryFull } from '../tools/goofy/getMemoryFull.js';
|
|
57
|
-
// Import project memory import
|
|
57
|
+
// Import project memory import/export tools - carry context across projects
|
|
58
58
|
import { ImportProjectMemories } from '../tools/goofy/importProjectMemories.js';
|
|
59
|
+
import { ExportProjectMemories } from '../tools/goofy/exportProjectMemories.js';
|
|
59
60
|
// Import MCP-based team communication tools (NEW - replaces HTTP team member comms)
|
|
60
61
|
import { createTeamCommTools } from './tools/teamComms.js';
|
|
61
62
|
// Import embedding server control tools (Phase 4 - user start/stop/status)
|
|
@@ -502,8 +503,9 @@ export function createToolRegistry(db, embeddingProvider) {
|
|
|
502
503
|
// Camera roll drilldown tools - zoom in/out on memories and code
|
|
503
504
|
registry.register(new DrillDown(db));
|
|
504
505
|
registry.register(new GetMemoryByDrilldownID(db));
|
|
505
|
-
// Project memory import
|
|
506
|
+
// Project memory import/export tools - carry context across projects
|
|
506
507
|
registry.register(new ImportProjectMemories(db, cachingProvider));
|
|
508
|
+
registry.register(new ExportProjectMemories(db));
|
|
507
509
|
// Team communication tools - multi-team member coordination
|
|
508
510
|
const teamCommTools = createTeamCommTools();
|
|
509
511
|
for (const tool of teamCommTools) {
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* exportProjectMemories - export memories from current project to JSON
|
|
3
|
+
*
|
|
4
|
+
* dumps memories from the current project schema so you can
|
|
5
|
+
* back them up, share them, or import them elsewhere
|
|
6
|
+
*/
|
|
7
|
+
import { logger } from '../../utils/logger.js';
|
|
8
|
+
import { getProjectPathForInsert } from '../../services/ProjectContext.js';
|
|
9
|
+
|
|
10
|
+
export class ExportProjectMemories {
|
|
11
|
+
db;
|
|
12
|
+
name = 'export_project_memories';
|
|
13
|
+
description = 'Export memories from the current project to JSON. Use for backups, sharing, or transferring memories between machines. Returns JSON array of memories.';
|
|
14
|
+
inputSchema = {
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: {
|
|
17
|
+
query: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
description: 'Optional semantic search query to filter which memories to export. If omitted, exports all (up to limit).'
|
|
20
|
+
},
|
|
21
|
+
tags: {
|
|
22
|
+
type: 'array',
|
|
23
|
+
items: { type: 'string' },
|
|
24
|
+
description: 'Optional tag filter - only export memories with these tags'
|
|
25
|
+
},
|
|
26
|
+
memoryTypes: {
|
|
27
|
+
type: 'array',
|
|
28
|
+
items: {
|
|
29
|
+
type: 'string',
|
|
30
|
+
enum: ['episodic', 'semantic', 'procedural', 'working', 'consolidated']
|
|
31
|
+
},
|
|
32
|
+
description: 'Optional memory type filter'
|
|
33
|
+
},
|
|
34
|
+
importance: {
|
|
35
|
+
type: 'array',
|
|
36
|
+
items: {
|
|
37
|
+
type: 'string',
|
|
38
|
+
enum: ['critical', 'high', 'medium', 'low', 'trivial']
|
|
39
|
+
},
|
|
40
|
+
description: 'Optional importance filter'
|
|
41
|
+
},
|
|
42
|
+
limit: {
|
|
43
|
+
type: 'number',
|
|
44
|
+
default: 500,
|
|
45
|
+
minimum: 1,
|
|
46
|
+
maximum: 50000,
|
|
47
|
+
description: 'Max number of memories to export (default: 500)'
|
|
48
|
+
},
|
|
49
|
+
outputPath: {
|
|
50
|
+
type: 'string',
|
|
51
|
+
description: 'Optional file path to write JSON output. If omitted, returns in response.'
|
|
52
|
+
},
|
|
53
|
+
includeEmbeddings: {
|
|
54
|
+
type: 'boolean',
|
|
55
|
+
default: false,
|
|
56
|
+
description: 'Include embedding vectors in export (large, usually not needed)'
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
required: []
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
constructor(db) {
|
|
63
|
+
this.db = db;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async execute(params) {
|
|
67
|
+
const { query, tags, memoryTypes, importance, outputPath, includeEmbeddings = false } = params;
|
|
68
|
+
const limit = Math.min(params.limit || 500, 50000);
|
|
69
|
+
const startTime = Date.now();
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const currentSchema = this.db.getProjectSchemaName();
|
|
73
|
+
const currentProjectPath = getProjectPathForInsert();
|
|
74
|
+
|
|
75
|
+
logger.info({
|
|
76
|
+
currentSchema,
|
|
77
|
+
currentProjectPath,
|
|
78
|
+
limit,
|
|
79
|
+
query: query?.slice(0, 50),
|
|
80
|
+
tags,
|
|
81
|
+
outputPath
|
|
82
|
+
}, 'Starting memory export');
|
|
83
|
+
|
|
84
|
+
// Build query
|
|
85
|
+
const conditions = [];
|
|
86
|
+
const queryParams = [];
|
|
87
|
+
let paramIndex = 1;
|
|
88
|
+
|
|
89
|
+
if (tags && tags.length > 0) {
|
|
90
|
+
conditions.push(`tags && $${paramIndex}::text[]`);
|
|
91
|
+
queryParams.push(tags);
|
|
92
|
+
paramIndex++;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (memoryTypes && memoryTypes.length > 0) {
|
|
96
|
+
conditions.push(`memory_type = ANY($${paramIndex}::text[])`);
|
|
97
|
+
queryParams.push(memoryTypes);
|
|
98
|
+
paramIndex++;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (importance && importance.length > 0) {
|
|
102
|
+
conditions.push(`importance = ANY($${paramIndex}::text[])`);
|
|
103
|
+
queryParams.push(importance);
|
|
104
|
+
paramIndex++;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const embedSelect = includeEmbeddings ? ', embedding' : '';
|
|
108
|
+
let selectQuery;
|
|
109
|
+
|
|
110
|
+
if (query) {
|
|
111
|
+
// Semantic search - need embedding
|
|
112
|
+
let embedding;
|
|
113
|
+
try {
|
|
114
|
+
const embProvider = this.db._embeddingProvider || null;
|
|
115
|
+
if (embProvider) {
|
|
116
|
+
embedding = await embProvider.generateEmbedding(query);
|
|
117
|
+
}
|
|
118
|
+
} catch (e) {
|
|
119
|
+
logger.warn({ error: e?.message }, 'Could not generate embedding for query filter');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (embedding) {
|
|
123
|
+
conditions.push(`embedding IS NOT NULL`);
|
|
124
|
+
const whereClause = conditions.length > 0 ? 'WHERE ' + conditions.join(' AND ') : '';
|
|
125
|
+
selectQuery = `
|
|
126
|
+
SELECT id, content, memory_type, importance, tags, metadata,
|
|
127
|
+
project_path, created_at, updated_at, expires_at,
|
|
128
|
+
1 - (embedding <=> $${paramIndex}::vector) as similarity
|
|
129
|
+
${embedSelect}
|
|
130
|
+
FROM memories
|
|
131
|
+
${whereClause}
|
|
132
|
+
ORDER BY embedding <=> $${paramIndex}::vector
|
|
133
|
+
LIMIT $${paramIndex + 1}
|
|
134
|
+
`;
|
|
135
|
+
queryParams.push(`[${embedding.join(',')}]`);
|
|
136
|
+
queryParams.push(limit);
|
|
137
|
+
} else {
|
|
138
|
+
// Fallback to text search
|
|
139
|
+
conditions.push(`content ILIKE $${paramIndex}`);
|
|
140
|
+
queryParams.push(`%${query}%`);
|
|
141
|
+
paramIndex++;
|
|
142
|
+
const whereClause = conditions.length > 0 ? 'WHERE ' + conditions.join(' AND ') : '';
|
|
143
|
+
selectQuery = `
|
|
144
|
+
SELECT id, content, memory_type, importance, tags, metadata,
|
|
145
|
+
project_path, created_at, updated_at, expires_at
|
|
146
|
+
${embedSelect}
|
|
147
|
+
FROM memories
|
|
148
|
+
${whereClause}
|
|
149
|
+
ORDER BY created_at DESC
|
|
150
|
+
LIMIT $${paramIndex}
|
|
151
|
+
`;
|
|
152
|
+
queryParams.push(limit);
|
|
153
|
+
}
|
|
154
|
+
} else {
|
|
155
|
+
const whereClause = conditions.length > 0 ? 'WHERE ' + conditions.join(' AND ') : '';
|
|
156
|
+
selectQuery = `
|
|
157
|
+
SELECT id, content, memory_type, importance, tags, metadata,
|
|
158
|
+
project_path, created_at, updated_at, expires_at
|
|
159
|
+
${embedSelect}
|
|
160
|
+
FROM memories
|
|
161
|
+
${whereClause}
|
|
162
|
+
ORDER BY created_at DESC
|
|
163
|
+
LIMIT $${paramIndex}
|
|
164
|
+
`;
|
|
165
|
+
queryParams.push(limit);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const result = await this.db.query(selectQuery, queryParams);
|
|
169
|
+
const memories = result.rows.map(row => ({
|
|
170
|
+
id: row.id,
|
|
171
|
+
content: row.content,
|
|
172
|
+
memory_type: row.memory_type,
|
|
173
|
+
importance: row.importance,
|
|
174
|
+
tags: row.tags,
|
|
175
|
+
metadata: row.metadata,
|
|
176
|
+
project_path: row.project_path,
|
|
177
|
+
created_at: row.created_at,
|
|
178
|
+
updated_at: row.updated_at,
|
|
179
|
+
expires_at: row.expires_at,
|
|
180
|
+
...(row.similarity !== undefined ? { similarity: Math.round(row.similarity * 1000) / 1000 } : {}),
|
|
181
|
+
...(includeEmbeddings && row.embedding ? { embedding: row.embedding } : {})
|
|
182
|
+
}));
|
|
183
|
+
|
|
184
|
+
const duration = Date.now() - startTime;
|
|
185
|
+
|
|
186
|
+
// Write to file if outputPath specified
|
|
187
|
+
if (outputPath) {
|
|
188
|
+
const fs = await import('fs');
|
|
189
|
+
const exportData = {
|
|
190
|
+
exportedAt: new Date().toISOString(),
|
|
191
|
+
sourceProject: currentProjectPath,
|
|
192
|
+
sourceSchema: currentSchema,
|
|
193
|
+
totalExported: memories.length,
|
|
194
|
+
filters: { query, tags, memoryTypes, importance, limit },
|
|
195
|
+
memories
|
|
196
|
+
};
|
|
197
|
+
fs.writeFileSync(outputPath, JSON.stringify(exportData, null, 2));
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
content: [{
|
|
201
|
+
type: 'text',
|
|
202
|
+
text: JSON.stringify({
|
|
203
|
+
success: true,
|
|
204
|
+
exported: memories.length,
|
|
205
|
+
outputPath,
|
|
206
|
+
sourceSchema: currentSchema,
|
|
207
|
+
duration: `${duration}ms`,
|
|
208
|
+
fileSizeKB: Math.round(fs.statSync(outputPath).size / 1024)
|
|
209
|
+
}, null, 2)
|
|
210
|
+
}]
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Return inline
|
|
215
|
+
return {
|
|
216
|
+
content: [{
|
|
217
|
+
type: 'text',
|
|
218
|
+
text: JSON.stringify({
|
|
219
|
+
success: true,
|
|
220
|
+
exported: memories.length,
|
|
221
|
+
sourceProject: currentProjectPath,
|
|
222
|
+
sourceSchema: currentSchema,
|
|
223
|
+
duration: `${duration}ms`,
|
|
224
|
+
memories
|
|
225
|
+
}, null, 2)
|
|
226
|
+
}]
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
} catch (err) {
|
|
230
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
231
|
+
logger.error({ error: errMsg }, 'Memory export failed');
|
|
232
|
+
return {
|
|
233
|
+
content: [{
|
|
234
|
+
type: 'text',
|
|
235
|
+
text: JSON.stringify({
|
|
236
|
+
error: 'Memory export failed',
|
|
237
|
+
details: errMsg
|
|
238
|
+
}, null, 2)
|
|
239
|
+
}]
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
@@ -104,12 +104,12 @@ export class ImportProjectMemories {
|
|
|
104
104
|
return {
|
|
105
105
|
content: [{
|
|
106
106
|
type: 'text',
|
|
107
|
-
text:
|
|
107
|
+
text: JSON.stringify({
|
|
108
108
|
error: `Source schema '${sourceSchema}' not found`,
|
|
109
109
|
sourceProject,
|
|
110
110
|
availableSchemas: schemaList || 'none',
|
|
111
111
|
hint: 'Make sure the source project path is correct and has been used with SpecMem before'
|
|
112
|
-
})
|
|
112
|
+
}, null, 2)
|
|
113
113
|
}]
|
|
114
114
|
};
|
|
115
115
|
}
|
|
@@ -176,12 +176,12 @@ export class ImportProjectMemories {
|
|
|
176
176
|
return {
|
|
177
177
|
content: [{
|
|
178
178
|
type: 'text',
|
|
179
|
-
text:
|
|
179
|
+
text: JSON.stringify({
|
|
180
180
|
result: 'No memories found matching criteria in source project',
|
|
181
181
|
sourceProject,
|
|
182
182
|
sourceSchema,
|
|
183
183
|
filters: { tags, memoryTypes, importance, query: query?.slice(0, 50) }
|
|
184
|
-
})
|
|
184
|
+
}, null, 2)
|
|
185
185
|
}]
|
|
186
186
|
};
|
|
187
187
|
}
|
|
@@ -200,7 +200,7 @@ export class ImportProjectMemories {
|
|
|
200
200
|
return {
|
|
201
201
|
content: [{
|
|
202
202
|
type: 'text',
|
|
203
|
-
text:
|
|
203
|
+
text: JSON.stringify({
|
|
204
204
|
dryRun: true,
|
|
205
205
|
wouldImport: sourceMemories.rows.length,
|
|
206
206
|
sourceProject,
|
|
@@ -210,7 +210,7 @@ export class ImportProjectMemories {
|
|
|
210
210
|
previewNote: sourceMemories.rows.length > 10
|
|
211
211
|
? `Showing 10 of ${sourceMemories.rows.length} memories`
|
|
212
212
|
: undefined
|
|
213
|
-
})
|
|
213
|
+
}, null, 2)
|
|
214
214
|
}]
|
|
215
215
|
};
|
|
216
216
|
}
|
|
@@ -315,7 +315,7 @@ export class ImportProjectMemories {
|
|
|
315
315
|
return {
|
|
316
316
|
content: [{
|
|
317
317
|
type: 'text',
|
|
318
|
-
text:
|
|
318
|
+
text: JSON.stringify(result, null, 2)
|
|
319
319
|
}]
|
|
320
320
|
};
|
|
321
321
|
|
|
@@ -325,11 +325,11 @@ export class ImportProjectMemories {
|
|
|
325
325
|
return {
|
|
326
326
|
content: [{
|
|
327
327
|
type: 'text',
|
|
328
|
-
text:
|
|
328
|
+
text: JSON.stringify({
|
|
329
329
|
error: 'Memory import failed',
|
|
330
330
|
details: errMsg,
|
|
331
331
|
sourceProject
|
|
332
|
-
})
|
|
332
|
+
}, null, 2)
|
|
333
333
|
}]
|
|
334
334
|
};
|
|
335
335
|
}
|
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;
|
|
74
|
+
|
|
75
|
+
if (remaining.length < bodyEnd) break;
|
|
67
76
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const bodyEnd = bodyStart + contentLength;
|
|
77
|
+
const body = remaining.substring(bodyStart, bodyEnd);
|
|
78
|
+
remaining = remaining.substring(bodyEnd);
|
|
71
79
|
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
78
105
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
106
|
+
const jsonStr = remaining.substring(0, end);
|
|
107
|
+
remaining = remaining.substring(end);
|
|
108
|
+
|
|
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
|
}
|
|
@@ -106,8 +140,20 @@ function sendToClient(msg) {
|
|
|
106
140
|
// Send message to MCP server (child stdin)
|
|
107
141
|
// ============================================================================
|
|
108
142
|
function sendToServer(msg) {
|
|
109
|
-
|
|
110
|
-
|
|
143
|
+
// CRITICAL: Always forward initialize and notifications/initialized immediately
|
|
144
|
+
// even before childReady — these are what MAKE the child ready.
|
|
145
|
+
// Without this, deadlock: proxy waits for init response, bootstrap waits for init request.
|
|
146
|
+
const isInitFlow = msg.method === 'initialize' || msg.method === 'notifications/initialized';
|
|
147
|
+
|
|
148
|
+
if (!child || child.killed) {
|
|
149
|
+
if (pendingQueue.length < MAX_QUEUE_SIZE) {
|
|
150
|
+
pendingQueue.push(msg);
|
|
151
|
+
log(`Queued message (no child) (${pendingQueue.length} pending): ${msg.method || msg.id || '?'}`);
|
|
152
|
+
}
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!childReady && !isInitFlow) {
|
|
111
157
|
if (pendingQueue.length < MAX_QUEUE_SIZE) {
|
|
112
158
|
pendingQueue.push(msg);
|
|
113
159
|
log(`Queued message (${pendingQueue.length} pending): ${msg.method || msg.id || '?'}`);
|
|
@@ -118,7 +164,7 @@ function sendToServer(msg) {
|
|
|
118
164
|
}
|
|
119
165
|
|
|
120
166
|
try {
|
|
121
|
-
child.stdin.write(
|
|
167
|
+
child.stdin.write(serializeMessage(msg));
|
|
122
168
|
} catch (e) {
|
|
123
169
|
log(`child stdin write error: ${e.message}`);
|
|
124
170
|
pendingQueue.push(msg);
|
|
@@ -158,8 +204,8 @@ function spawnServer() {
|
|
|
158
204
|
// CRITICAL: Do NOT hardcode --max-old-space-size here
|
|
159
205
|
// The proxy's own heap limit is set by Claude config args (e.g. --max-old-space-size=250)
|
|
160
206
|
// but the child bootstrap needs MORE memory for all its initialization
|
|
161
|
-
const heapLimit = process.env.SPECMEM_MAX_HEAP_MB || '
|
|
162
|
-
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], {
|
|
163
209
|
env,
|
|
164
210
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
165
211
|
cwd: process.env.SPECMEM_PROJECT_PATH || process.cwd()
|
|
@@ -171,9 +217,12 @@ function spawnServer() {
|
|
|
171
217
|
});
|
|
172
218
|
|
|
173
219
|
child.stdout.on('data', (data) => {
|
|
174
|
-
|
|
220
|
+
const raw = data.toString();
|
|
221
|
+
log(`CHILD STDOUT (${raw.length} bytes): ${raw.substring(0, 200)}`);
|
|
222
|
+
childStdoutBuffer += raw;
|
|
175
223
|
|
|
176
224
|
const { messages, remaining } = parseMessages(childStdoutBuffer);
|
|
225
|
+
log(`CHILD PARSED: ${messages.length} messages, ${remaining.length} bytes remaining`);
|
|
177
226
|
childStdoutBuffer = remaining;
|
|
178
227
|
|
|
179
228
|
for (const msg of messages) {
|
|
@@ -222,12 +271,12 @@ function spawnServer() {
|
|
|
222
271
|
setTimeout(() => {
|
|
223
272
|
if (child && !child.killed) {
|
|
224
273
|
try {
|
|
225
|
-
child.stdin.write(
|
|
274
|
+
child.stdin.write(serializeMessage(lastInitializeRequest));
|
|
226
275
|
// Also send initialized notification
|
|
227
276
|
setTimeout(() => {
|
|
228
277
|
if (child && !child.killed) {
|
|
229
278
|
try {
|
|
230
|
-
child.stdin.write(
|
|
279
|
+
child.stdin.write(serializeMessage({ jsonrpc: '2.0', method: 'notifications/initialized' }));
|
|
231
280
|
} catch {}
|
|
232
281
|
}
|
|
233
282
|
}, 100);
|
|
@@ -280,9 +329,12 @@ function stopHeartbeat() {
|
|
|
280
329
|
// Handle stdin from Claude
|
|
281
330
|
// ============================================================================
|
|
282
331
|
process.stdin.on('data', (data) => {
|
|
283
|
-
|
|
332
|
+
const raw = data.toString();
|
|
333
|
+
log(`STDIN RAW (${raw.length} bytes): ${raw.substring(0, 200)}`);
|
|
334
|
+
stdinBuffer += raw;
|
|
284
335
|
|
|
285
336
|
const { messages, remaining } = parseMessages(stdinBuffer);
|
|
337
|
+
log(`STDIN PARSED: ${messages.length} messages, ${remaining.length} bytes remaining`);
|
|
286
338
|
stdinBuffer = remaining;
|
|
287
339
|
|
|
288
340
|
for (const msg of messages) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specmem-hardwicksoftware",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.19",
|
|
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",
|
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;
|