specmem-hardwicksoftware 3.7.4 → 3.7.6

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.
@@ -1518,9 +1518,14 @@ export class EmbeddingServerManager extends EventEmitter {
1518
1518
  // Environment variables are null-separated
1519
1519
  const envVars = environ.split('\0');
1520
1520
  for (const envVar of envVars) {
1521
+ // Check both env var names - MCP manager uses SPECMEM_EMBEDDING_SOCKET,
1522
+ // but specmem-init spawns with SPECMEM_SOCKET_PATH
1521
1523
  if (envVar.startsWith('SPECMEM_EMBEDDING_SOCKET=')) {
1522
1524
  return envVar.split('=')[1];
1523
1525
  }
1526
+ if (envVar.startsWith('SPECMEM_SOCKET_PATH=')) {
1527
+ return envVar.split('=')[1];
1528
+ }
1524
1529
  }
1525
1530
  return null;
1526
1531
  }
@@ -70,7 +70,31 @@ export function checkProcessHealth(config) {
70
70
  if (commandLine) {
71
71
  const isEmbedding = commandLine.includes('frankenstein-embeddings.py') ||
72
72
  (expectedProcessName && commandLine.includes(expectedProcessName));
73
- const isCorrectProject = !projectPath || commandLine.includes(projectPath);
73
+ // Check project path in command line first, then fall back to env vars
74
+ // CRITICAL FIX: The embedding server receives project path via SPECMEM_PROJECT_PATH
75
+ // and socket path via SPECMEM_SOCKET_PATH env vars, NOT command line args.
76
+ // Without this check, every embedding server appears to be "wrong project" and gets killed.
77
+ let isCorrectProject = !projectPath || commandLine.includes(projectPath);
78
+ if (!isCorrectProject && projectPath && isEmbedding) {
79
+ // Check process environment for SPECMEM_PROJECT_PATH
80
+ const processEnvProjectPath = getProcessEnvVar(pid, 'SPECMEM_PROJECT_PATH');
81
+ if (processEnvProjectPath) {
82
+ isCorrectProject = processEnvProjectPath === projectPath || processEnvProjectPath.startsWith(projectPath);
83
+ if (isCorrectProject) {
84
+ logger.debug({ pid, processEnvProjectPath, expectedProject: projectPath },
85
+ '[ProcessHealthCheck] Project path matched via SPECMEM_PROJECT_PATH env var');
86
+ }
87
+ }
88
+ // Also check SPECMEM_SOCKET_PATH which contains the project path
89
+ if (!isCorrectProject) {
90
+ const processSocketPath = getProcessEnvVar(pid, 'SPECMEM_SOCKET_PATH');
91
+ if (processSocketPath && processSocketPath.includes(projectPath)) {
92
+ isCorrectProject = true;
93
+ logger.debug({ pid, processSocketPath, expectedProject: projectPath },
94
+ '[ProcessHealthCheck] Project path matched via SPECMEM_SOCKET_PATH env var');
95
+ }
96
+ }
97
+ }
74
98
  isEmbeddingServer = isEmbedding && isCorrectProject;
75
99
  if (isEmbedding && !isCorrectProject) {
76
100
  logger.warn({
@@ -144,6 +168,30 @@ export function checkProcessHealth(config) {
144
168
  // ============================================================================
145
169
  // LOW-LEVEL UTILITIES
146
170
  // ============================================================================
171
+ /**
172
+ * Read a specific environment variable from a running process via /proc
173
+ * Returns the value or null if not found/readable
174
+ */
175
+ function getProcessEnvVar(pid, varName) {
176
+ try {
177
+ const environPath = `/proc/${pid}/environ`;
178
+ if (!existsSync(environPath)) {
179
+ return null;
180
+ }
181
+ const environ = readFileSync(environPath, 'utf8');
182
+ const prefix = varName + '=';
183
+ const envVars = environ.split('\0');
184
+ for (const envVar of envVars) {
185
+ if (envVar.startsWith(prefix)) {
186
+ return envVar.slice(prefix.length);
187
+ }
188
+ }
189
+ return null;
190
+ }
191
+ catch {
192
+ return null;
193
+ }
194
+ }
147
195
  /**
148
196
  * Read PID file with timestamp
149
197
  * Format: PID:TIMESTAMP
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specmem-hardwicksoftware",
3
- "version": "3.7.4",
3
+ "version": "3.7.6",
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",
@@ -3129,6 +3129,14 @@ async function indexCodebase(projectPath, ui, embeddingResult) {
3129
3129
  // Use Python directly - built-in thread limiting via torch.set_num_threads()
3130
3130
  initLog('[EMBED] Starting frankenstein-embeddings.py directly');
3131
3131
  const pythonPath = getPythonPath();
3132
+
3133
+ // Redirect stdout/stderr to log file instead of piping
3134
+ // CRITICAL: Piped stdio keeps a reference to the child process even after unref(),
3135
+ // which can cause SIGTERM/SIGPIPE when the parent's event loop is under heavy load
3136
+ // (e.g. during batch embedding). Using 'ignore' or file descriptors prevents this.
3137
+ const embedLogPath = path.join(projectPath, 'specmem', 'sockets', 'embedding-autostart.log');
3138
+ const embedLogFd = fs.openSync(embedLogPath, 'a');
3139
+
3132
3140
  const embeddingProcess = spawn(pythonPath, [embeddingScript], {
3133
3141
  cwd: path.dirname(embeddingScript),
3134
3142
  env: {
@@ -3137,7 +3145,7 @@ async function indexCodebase(projectPath, ui, embeddingResult) {
3137
3145
  SPECMEM_PROJECT_PATH: projectPath
3138
3146
  },
3139
3147
  detached: true,
3140
- stdio: ['ignore', 'pipe', 'pipe']
3148
+ stdio: ['ignore', embedLogFd, embedLogFd]
3141
3149
  });
3142
3150
 
3143
3151
  // error handler BEFORE unref - prevents silent spawn failures
@@ -3145,13 +3153,8 @@ async function indexCodebase(projectPath, ui, embeddingResult) {
3145
3153
  ui.setSubStatus('Embedding spawn error: ' + err.message);
3146
3154
  });
3147
3155
 
3148
- // CRITICAL: Consume stdout/stderr so Python startup banner doesn't leak to terminal
3149
- embeddingProcess.stdout.on('data', (chunk) => {
3150
- initLog('[EMBED-STDOUT] ' + chunk.toString().trim());
3151
- });
3152
- embeddingProcess.stderr.on('data', (chunk) => {
3153
- initLog('[EMBED-STDERR] ' + chunk.toString().trim());
3154
- });
3156
+ // Close the fd in the parent so only the child holds it
3157
+ fs.closeSync(embedLogFd);
3155
3158
 
3156
3159
  embeddingProcess.unref();
3157
3160