grepmax 0.26.4 → 0.26.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.
@@ -58,26 +58,6 @@ const config_1 = require("../../config");
58
58
  const embedding_generation_1 = require("../index/embedding-generation");
59
59
  const index_config_1 = require("../index/index-config");
60
60
  const logger_1 = require("../utils/logger");
61
- function reviveBufferLike(input) {
62
- if (input &&
63
- typeof input === "object" &&
64
- "type" in input &&
65
- input.type === "Buffer" &&
66
- Array.isArray(input.data)) {
67
- return Buffer.from(input.data);
68
- }
69
- return input;
70
- }
71
- function reviveProcessFileResult(result) {
72
- if (!result || !Array.isArray(result.vectors))
73
- return result;
74
- const vectors = result.vectors.map((v) => {
75
- const revived = reviveBufferLike(v.colbert);
76
- return revived && (Buffer.isBuffer(revived) || revived instanceof Int8Array)
77
- ? Object.assign(Object.assign({}, v), { colbert: revived }) : v;
78
- });
79
- return Object.assign(Object.assign({}, result), { vectors });
80
- }
81
61
  const TASK_TIMEOUT_MS = (() => {
82
62
  var _a;
83
63
  const fromEnv = Number.parseInt((_a = process.env.GMAX_WORKER_TASK_TIMEOUT_MS) !== null && _a !== void 0 ? _a : "", 10);
@@ -155,6 +135,7 @@ class ProcessWorker {
155
135
  this.child = childProcess.fork(modulePath, {
156
136
  execArgv: [...memArgs, ...execArgv],
157
137
  env: Object.assign(Object.assign({}, process.env), embeddingEnvironment),
138
+ serialization: "advanced",
158
139
  });
159
140
  }
160
141
  }
@@ -182,13 +163,13 @@ const MIN_KEEP_WORKERS = 1;
182
163
  // Recycle a worker whose RSS has grown past this. ONNX native memory lives
183
164
  // outside V8, so --max-old-space-size can't bound it; replacing the worker is
184
165
  // the only reclaim. The threshold must sit ABOVE the healthy working set or
185
- // the pool executes warm workers for normal behavior: measured steady state
186
- // is ~700-800 MB with MLX serving dense embeds and ~900-1050 MB in ONNX CPU
187
- // fallback (granite session loaded in-process), flat across task count and
188
- // file size. The old 800 default was calibrated for the pre-
189
- // enableCpuMemArena:false era (arenas pinned ~2 GB) and sat inside the
190
- // healthy range — every busy worker was recycled after nearly every task,
191
- // paying a full model reload each time. 0 (or negative) disables the check.
166
+ // the pool executes warm workers for normal behavior. A post-tree-cleanup
167
+ // soak on 2026-08-04 measured MLX p50/p95/max at 1056/1110/1122 MB (900 tasks)
168
+ // and ONNX at 1030/1182/1195 MB (600 tasks). Applying the 1.25x ONNX-p95 rule
169
+ // and rounding to a 128 MB boundary retains 1536 MB. The old 800 default sat
170
+ // inside the healthy range and recycled warm workers after normal tasks; the
171
+ // previously proposed 1024 MLX-only override is also below measured p50. 0
172
+ // (or negative) disables the check.
192
173
  const WORKER_RSS_RECYCLE_MB = (() => {
193
174
  var _a;
194
175
  const fromEnv = Number.parseInt((_a = process.env.GMAX_WORKER_RSS_RECYCLE_MB) !== null && _a !== void 0 ? _a : "", 10);
@@ -255,6 +236,12 @@ class WorkerPool {
255
236
  .map((w) => w.child.pid)
256
237
  .filter((pid) => pid !== undefined);
257
238
  }
239
+ /** Latest worker-reported RSS, sampled after each IPC message. */
240
+ getWorkerMemoryStats() {
241
+ return this.workers.flatMap((worker) => worker.child.pid === undefined
242
+ ? []
243
+ : [{ pid: worker.child.pid, rssBytes: worker.lastRssBytes }]);
244
+ }
258
245
  clearTaskTimeout(task) {
259
246
  if (task.timeout) {
260
247
  clearTimeout(task.timeout);
@@ -378,16 +365,12 @@ class WorkerPool {
378
365
  task.reject(new Error(msg.error));
379
366
  }
380
367
  else {
381
- let result = msg.result;
382
- if (task.method === "processFile") {
383
- result = reviveProcessFileResult(result);
384
- }
385
368
  const elapsed = task.startTime
386
369
  ? `${Date.now() - task.startTime}ms`
387
370
  : "?ms";
388
371
  const filePath = (_h = (_f = (_e = task.payload) === null || _e === void 0 ? void 0 : _e.path) !== null && _f !== void 0 ? _f : (_g = task.payload) === null || _g === void 0 ? void 0 : _g.absolutePath) !== null && _h !== void 0 ? _h : "";
389
372
  (0, logger_1.debug)("pool", `complete task=${task.id} method=${task.method} ${elapsed}${filePath ? ` file=${filePath}` : ""}`);
390
- task.resolve(result);
373
+ task.resolve(msg.result);
391
374
  }
392
375
  this.completeTask(task, worker);
393
376
  this.consecutiveRespawns = 0;
@@ -13,6 +13,7 @@ GPU operations.
13
13
  import asyncio
14
14
  import logging
15
15
  import os
16
+ import re
16
17
  import signal
17
18
  import socket
18
19
  import time
@@ -90,12 +91,22 @@ def is_port_in_use(port: int) -> bool:
90
91
  return s.connect_ex(("127.0.0.1", port)) == 0
91
92
 
92
93
 
94
+ # JSON can carry lone UTF-16 surrogates (a client slicing mid-codepoint);
95
+ # the HF fast tokenizer rejects them with a TypeError that fails the whole
96
+ # batch. Replace with U+FFFD so one bad text can't take down a batch.
97
+ _LONE_SURROGATE = re.compile("[\ud800-\udfff]")
98
+
99
+
93
100
  def embed_texts(texts: list[str]) -> mx.array:
94
101
  """Tokenize, forward pass, L2 normalize.
95
102
 
96
103
  mlx_embeddings model already does mean pooling internally —
97
104
  last_hidden_state is (batch, dim), not (batch, seq, dim).
98
105
  """
106
+ texts = [
107
+ _LONE_SURROGATE.sub("�", t) if _LONE_SURROGATE.search(t) else t
108
+ for t in texts
109
+ ]
99
110
  encoded = tokenizer(
100
111
  texts, padding=True, truncation=True, max_length=256, return_tensors="np"
101
112
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.26.4",
3
+ "version": "0.26.6",
4
4
  "author": "Robert Owens <78518764+reowens@users.noreply.github.com>",
5
5
  "homepage": "https://github.com/reowens/grepmax",
6
6
  "bugs": {
@@ -79,8 +79,8 @@
79
79
  "dependencies": {
80
80
  "@clack/prompts": "^1.6.0",
81
81
  "@huggingface/transformers": "^4.2.0",
82
- "@lancedb/lancedb": "^0.30.0",
83
- "@modelcontextprotocol/sdk": "^1.29.0",
82
+ "@lancedb/lancedb": "0.31.0",
83
+ "@modelcontextprotocol/sdk": "^1.30.0",
84
84
  "@parcel/watcher": "^2.5.6",
85
85
  "apache-arrow": "^18.1.0",
86
86
  "chalk": "^5.6.2",
@@ -99,11 +99,6 @@
99
99
  "web-tree-sitter": "^0.26.9",
100
100
  "zod": "^4.4.3"
101
101
  },
102
- "pnpm": {
103
- "overrides": {
104
- "mathjs": "15.2.0"
105
- }
106
- },
107
102
  "devDependencies": {
108
103
  "@biomejs/biome": "2.4.10",
109
104
  "@types/node": "^26.0.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.26.4",
3
+ "version": "0.26.6",
4
4
  "description": "Semantic code search for Claude Code. Automatically indexes your project and provides intelligent search capabilities.",
5
5
  "author": {
6
6
  "name": "Robert Owens",