specmem-hardwicksoftware 3.7.19 → 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.
@@ -900,7 +900,7 @@ export class CodebaseIndexer {
900
900
  const analyzableLanguages = [
901
901
  'typescript', 'typescript-react', 'javascript', 'javascript-react',
902
902
  'python', 'go', 'rust', 'java', 'kotlin', 'scala',
903
- 'ruby', 'php', 'c', 'cpp', 'swift'
903
+ 'ruby', 'php', 'c', 'cpp', 'swift', 'html'
904
904
  ];
905
905
  return analyzableLanguages.includes(language);
906
906
  }
@@ -821,7 +821,7 @@ export class FindWhatISaid {
821
821
  });
822
822
  }
823
823
  catch (embeddingError) {
824
- clearTimeout(embeddingTimeoutId); // Prevent dangling timer on error path
824
+ // embeddingTimeoutId is scoped inside withEmbeddingRetry — already cleared there
825
825
  const embeddingDuration = Date.now() - embeddingStartTime;
826
826
  const err = embeddingError;
827
827
  // ============================================================================
@@ -293,32 +293,48 @@ def _detect_best_onnx_file():
293
293
  """
294
294
  Detect CPU features and return the best ONNX model file name.
295
295
  Priority: avx512_vnni > avx512 > avx2 > default
296
+ Falls back to whatever .onnx file exists if the optimal one isn't found.
296
297
  """
298
+ # Ordered by preference (best first)
299
+ candidates = []
300
+
297
301
  try:
298
302
  with open('/proc/cpuinfo', 'r') as f:
299
303
  cpuinfo = f.read().lower()
300
304
 
301
- # Check for AVX512 VNNI (best for INT8)
302
305
  if 'avx512_vnni' in cpuinfo or 'avx512vnni' in cpuinfo:
303
- print("🚀 CPU supports AVX512-VNNI - using optimized INT8 model", file=sys.stderr)
304
- return "onnx/model_qint8_avx512_vnni.onnx"
305
-
306
- # Check for AVX512 (good INT8 support)
306
+ candidates.append(("onnx/model_qint8_avx512_vnni.onnx", "AVX512-VNNI"))
307
307
  if 'avx512f' in cpuinfo or 'avx512' in cpuinfo:
308
- print("🚀 CPU supports AVX512 - using INT8 quantized model", file=sys.stderr)
309
- return "onnx/model_qint8_avx512.onnx"
310
-
311
- # Check for AVX2 (common, decent performance)
308
+ candidates.append(("onnx/model_qint8_avx512.onnx", "AVX512"))
312
309
  if 'avx2' in cpuinfo:
313
- print("🚀 CPU supports AVX2 - using UINT8 quantized model", file=sys.stderr)
314
- return "onnx/model_quint8_avx2.onnx"
315
-
316
- # Fallback to unoptimized
317
- print("â„šī¸ Using default ONNX model (no AVX optimization)", file=sys.stderr)
318
- return "onnx/model.onnx"
310
+ candidates.append(("onnx/model_quint8_avx2.onnx", "AVX2"))
319
311
  except Exception as e:
320
312
  print(f"âš ī¸ Could not detect CPU features: {e}", file=sys.stderr)
321
- return "onnx/model.onnx"
313
+
314
+ # Always add standard fallbacks
315
+ candidates.append(("onnx/model_quantized.onnx", "quantized"))
316
+ candidates.append(("onnx/model.onnx", "default"))
317
+
318
+ # Check which files actually exist in the bundled model dir
319
+ bundled_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'models', 'all-MiniLM-L6-v2')
320
+ for onnx_file, label in candidates:
321
+ full_path = os.path.join(bundled_dir, onnx_file)
322
+ if os.path.isfile(full_path):
323
+ print(f"🚀 Using {label} ONNX model: {onnx_file}", file=sys.stderr)
324
+ return onnx_file
325
+
326
+ # Last resort: find ANY .onnx file in the bundled dir
327
+ onnx_dir = os.path.join(bundled_dir, 'onnx')
328
+ if os.path.isdir(onnx_dir):
329
+ for f in os.listdir(onnx_dir):
330
+ if f.endswith('.onnx'):
331
+ result = f"onnx/{f}"
332
+ print(f"🔍 Auto-detected ONNX model: {result}", file=sys.stderr)
333
+ return result
334
+
335
+ # Nothing found - return default and let SentenceTransformer handle it
336
+ print("â„šī¸ No bundled ONNX model found - using default", file=sys.stderr)
337
+ return "onnx/model.onnx"
322
338
 
323
339
  _BEST_ONNX_FILE = _detect_best_onnx_file()
324
340
 
@@ -57,6 +57,13 @@ const getMachineSocketPath = () => {
57
57
  const SOCKET_PATH = process.env.SOCKET_PATH || getMachineSocketPath();
58
58
  const MODEL_NAME = 'Xenova/all-MiniLM-L6-v2';
59
59
 
60
+ // Bundled model: shipped with npm package, used as fallback when HF cache unavailable
61
+ import { fileURLToPath } from 'url';
62
+ import { dirname } from 'path';
63
+ const __filename_esm = fileURLToPath(import.meta.url);
64
+ const __dirname_esm = dirname(__filename_esm);
65
+ const BUNDLED_MODEL_DIR = join(__dirname_esm, 'models', 'all-MiniLM-L6-v2');
66
+
60
67
  // Dynamic dimensions - detected from model and database
61
68
  let NATIVE_DIM = null;
62
69
  let TARGET_DIM = null;
@@ -78,13 +85,39 @@ async function loadModel() {
78
85
  try {
79
86
  console.log('[Sandbox] Loading model from local cache...');
80
87
 
81
- // Force local-only mode - will fail if model not pre-downloaded
82
- extractor = await pipeline('feature-extraction', MODEL_NAME, {
83
- // Use local cache only - no downloads allowed
84
- local_files_only: true,
85
- // Use CPU only (safer, no GPU driver access)
86
- device: 'cpu'
87
- });
88
+ // Try HF cache first, fall back to bundled model
89
+ let modelSource = MODEL_NAME;
90
+ try {
91
+ extractor = await pipeline('feature-extraction', MODEL_NAME, {
92
+ local_files_only: true,
93
+ device: 'cpu'
94
+ });
95
+ } catch (hfErr) {
96
+ // HF cache miss — try bundled model shipped with npm package
97
+ if (existsSync(BUNDLED_MODEL_DIR)) {
98
+ console.log(`[Sandbox] HF cache miss, loading bundled model: ${BUNDLED_MODEL_DIR}`);
99
+ // Ensure model.onnx exists (bundled may only have model_quint8_avx2.onnx)
100
+ const onnxDir = join(BUNDLED_MODEL_DIR, 'onnx');
101
+ const modelOnnx = join(onnxDir, 'model.onnx');
102
+ if (!existsSync(modelOnnx) && existsSync(onnxDir)) {
103
+ // Find any .onnx file and symlink as model.onnx
104
+ const { readdirSync, symlinkSync } = await import('fs');
105
+ const onnxFiles = readdirSync(onnxDir).filter(f => f.endsWith('.onnx'));
106
+ if (onnxFiles.length > 0) {
107
+ try { symlinkSync(onnxFiles[0], modelOnnx); } catch {}
108
+ }
109
+ }
110
+ extractor = await pipeline('feature-extraction', BUNDLED_MODEL_DIR, {
111
+ local_files_only: true,
112
+ device: 'cpu'
113
+ });
114
+ modelSource = BUNDLED_MODEL_DIR;
115
+ } else {
116
+ throw hfErr;
117
+ }
118
+ }
119
+
120
+ // Skip the duplicate pipeline call below — extractor is already loaded
88
121
 
89
122
  modelReady = true;
90
123
 
package/mcp-proxy.cjs CHANGED
@@ -261,7 +261,15 @@ function spawnServer() {
261
261
  stopHeartbeat();
262
262
 
263
263
  if (!shuttingDown) {
264
- 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
+ }
265
273
  }
266
274
  });
267
275
 
@@ -386,6 +394,15 @@ function shutdown() {
386
394
  process.on('SIGTERM', shutdown);
387
395
  process.on('SIGINT', shutdown);
388
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
+
389
406
  // ============================================================================
390
407
  // Start
391
408
  // ============================================================================
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "specmem-hardwicksoftware",
3
- "version": "3.7.19",
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",