grepmax 0.26.5 → 0.26.7

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.
@@ -10,7 +10,7 @@
10
10
  "name": "grepmax",
11
11
  "source": "./plugins/grepmax",
12
12
  "description": "Semantic code search for Claude Code. Automatically indexes your project and provides intelligent search capabilities.",
13
- "version": "0.26.5",
13
+ "version": "0.26.7",
14
14
  "author": {
15
15
  "name": "Robert Owens",
16
16
  "email": "robowens@me.com"
package/README.md CHANGED
@@ -124,7 +124,7 @@ Plugins auto-update when you run `npm install -g grepmax@latest` — no need to
124
124
 
125
125
  ### MCP Server
126
126
 
127
- `gmax mcp` starts a stdio-based MCP server for clients that support MCP but can't run shell commands (Cursor, Windsurf, custom agents).
127
+ `gmax mcp` starts a stdio-based MCP server for clients that support MCP but can't run shell commands (Cursor, Windsurf, custom agents). Search-backed tools use the singleton daemon when available, avoiding a separate embedding worker per MCP session, and fall back to an in-process worker only when the daemon is unavailable or does not support the request.
128
128
 
129
129
  | Tool | Description |
130
130
  | --- | --- |
@@ -215,7 +215,7 @@ gmax watch stop # Stop daemon
215
215
  gmax status # See all projects + watcher status
216
216
  ```
217
217
 
218
- The daemon auto-starts when you run `gmax add`, `gmax index`, `gmax remove`, or `gmax summarize`. It shuts down after 30 minutes of inactivity.
218
+ The daemon auto-starts when you run `gmax add`, `gmax index`, `gmax remove`, `gmax summarize`, or `gmax mcp`. It shuts down after 30 minutes of inactivity. File-change batches are processed concurrently while reserving worker capacity for searches; LanceDB compaction runs after writes rather than on every maintenance tick.
219
219
 
220
220
  ## Local LLM (optional)
221
221
 
@@ -374,9 +374,11 @@ fixtures/
374
374
  | --- | --- | --- |
375
375
  | `GMAX_EMBED_MODE` | Force `cpu` or `gpu` | Auto-detect |
376
376
  | `GMAX_WORKER_THREADS` | Worker threads for embedding | 50% of cores |
377
+ | `GMAX_WORKER_RSS_RECYCLE_MB` | Recycle workers that remain above this RSS; `0` disables the check | `1536` |
377
378
  | `GMAX_DEBUG` | Debug logging | Off |
378
379
  | `GMAX_SUMMARIZER` | Enable summarizer auto-start (`1`) | Off |
379
380
  | `GMAX_RERANK` | Enable ColBERT rerank (`1`) — off by default since v0.17.1 ([why](docs/known-limitations.md)) | Off |
381
+ | `GMAX_ANN` | Experimental IVF_FLAT vector index (`1`); leave off unless validating recall on your corpus | Off |
380
382
 
381
383
  ## Troubleshooting
382
384
 
@@ -389,6 +391,8 @@ gmax index --reset # Full reindex from scratch
389
391
  gmax watch stop && gmax watch --daemon -b # Restart daemon
390
392
  ```
391
393
 
394
+ `gmax doctor` reports ANN index state. `ANN: vector index not built` is normal with the default exact-search configuration.
395
+
392
396
  ## Contributing
393
397
 
394
398
  See [CLAUDE.md](CLAUDE.md) for development setup, commands, and architecture details.
@@ -88,7 +88,7 @@ exports.doctor = new commander_1.Command("doctor")
88
88
  .option("--fix", "Auto-fix detected issues (compact, prune, remove stale locks)", false)
89
89
  .option("--agent", "Compact output for AI agents", false)
90
90
  .action((opts) => __awaiter(void 0, void 0, void 0, function* () {
91
- var _a, _b, _c, _d, _e, _f, _g, _h;
91
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
92
92
  if (!opts.agent)
93
93
  console.log("gmax Doctor\n");
94
94
  const root = config_1.PATHS.globalRoot;
@@ -228,6 +228,11 @@ exports.doctor = new commander_1.Command("doctor")
228
228
  const logicalSize = tableStats.totalBytes;
229
229
  const { numFragments, numSmallFragments } = tableStats.fragmentStats;
230
230
  const versions = yield table.listVersions();
231
+ const indices = yield table.listIndices();
232
+ const vectorIndex = indices.find((index) => index.name === "vector_idx" || index.columns.includes("vector"));
233
+ const vectorIndexStats = vectorIndex
234
+ ? yield table.indexStats(vectorIndex.name).catch(() => undefined)
235
+ : undefined;
231
236
  // Lock status
232
237
  const lockPath = path.join(config_1.PATHS.globalRoot, "LOCK");
233
238
  let lockStatus = "none";
@@ -273,7 +278,7 @@ exports.doctor = new commander_1.Command("doctor")
273
278
  ? "LOW"
274
279
  : "ok";
275
280
  }
276
- catch (_j) { }
281
+ catch (_o) { }
277
282
  const staleChunkerProjects = projects.filter((p) => {
278
283
  var _a;
279
284
  return p.status === "indexed" &&
@@ -301,6 +306,10 @@ exports.doctor = new commander_1.Command("doctor")
301
306
  `legacy_embedding=${legacyEmbeddingCount}`,
302
307
  `schema_dim=${physicalDim !== null && physicalDim !== void 0 ? physicalDim : "none"}`,
303
308
  `schema_dim_ok=${schemaGap ? "false" : "true"}`,
309
+ `ann_index=${vectorIndex ? "present" : "absent"}`,
310
+ `ann_indexed=${(_f = vectorIndexStats === null || vectorIndexStats === void 0 ? void 0 : vectorIndexStats.numIndexedRows) !== null && _f !== void 0 ? _f : 0}`,
311
+ `ann_unindexed=${(_g = vectorIndexStats === null || vectorIndexStats === void 0 ? void 0 : vectorIndexStats.numUnindexedRows) !== null && _g !== void 0 ? _g : 0}`,
312
+ `ann_metric=${(_h = vectorIndexStats === null || vectorIndexStats === void 0 ? void 0 : vectorIndexStats.distanceType) !== null && _h !== void 0 ? _h : "none"}`,
304
313
  ];
305
314
  console.log(fields.join("\t"));
306
315
  if (schemaGap) {
@@ -354,6 +363,12 @@ exports.doctor = new commander_1.Command("doctor")
354
363
  else if (physicalDim) {
355
364
  console.log(`ok Schema: vector table is ${physicalDim}d`);
356
365
  }
366
+ if (vectorIndexStats) {
367
+ console.log(`ok ANN: ${vectorIndexStats.numIndexedRows.toLocaleString()} indexed, ${vectorIndexStats.numUnindexedRows.toLocaleString()} unindexed, metric=${(_j = vectorIndexStats.distanceType) !== null && _j !== void 0 ? _j : "unknown"}`);
368
+ }
369
+ else {
370
+ console.log("INFO ANN: vector index not built (set GMAX_ANN=1)");
371
+ }
357
372
  // Disk space
358
373
  if (diskLevel !== "ok") {
359
374
  console.log(`WARN Disk: ${formatSize(availBytes)} available (${diskLevel})`);
@@ -468,7 +483,7 @@ exports.doctor = new commander_1.Command("doctor")
468
483
  yield mc.close();
469
484
  }
470
485
  }
471
- catch (_k) { }
486
+ catch (_p) { }
472
487
  }
473
488
  }
474
489
  // --fix auto-remediation
@@ -515,7 +530,7 @@ exports.doctor = new commander_1.Command("doctor")
515
530
  const done = yield sendStreamingCommand({ cmd: "index", root: p.root, reset: true }, () => { }).catch((e) => ({ ok: false, error: String(e) }));
516
531
  if (done.ok &&
517
532
  (!("degraded" in done) || done.degraded !== true)) {
518
- const chunks = (_f = done.indexed) !== null && _f !== void 0 ? _f : 0;
533
+ const chunks = (_k = done.indexed) !== null && _k !== void 0 ? _k : 0;
519
534
  if (opts.agent) {
520
535
  console.log(`stale_chunker_reindexed\tname=${name}\tchunks=${chunks}`);
521
536
  }
@@ -527,7 +542,7 @@ exports.doctor = new commander_1.Command("doctor")
527
542
  else if (!opts.agent) {
528
543
  const degraded = "degraded" in done && done.degraded === true;
529
544
  const reason = degraded
530
- ? `${(_g = ("scanErrors" in done ? done.scanErrors : 0)) !== null && _g !== void 0 ? _g : 0} scan errors, ${(_h = ("failedFiles" in done ? done.failedFiles : 0)) !== null && _h !== void 0 ? _h : 0} file failures`
545
+ ? `${(_l = ("scanErrors" in done ? done.scanErrors : 0)) !== null && _l !== void 0 ? _l : 0} scan errors, ${(_m = ("failedFiles" in done ? done.failedFiles : 0)) !== null && _m !== void 0 ? _m : 0} file failures`
531
546
  : done.error;
532
547
  console.log(`FAIL ${name}: reindex failed (${reason})`);
533
548
  }
@@ -53,6 +53,7 @@ exports.mcp = void 0;
53
53
  exports.toStringArray = toStringArray;
54
54
  exports.ok = ok;
55
55
  exports.err = err;
56
+ exports.shouldFallbackMcpDaemonSearch = shouldFallbackMcpDaemonSearch;
56
57
  exports.isExplicitCrossProjectSearch = isExplicitCrossProjectSearch;
57
58
  exports.resolveMcpProject = resolveMcpProject;
58
59
  exports.containMcpTarget = containMcpTarget;
@@ -84,6 +85,7 @@ const annotator_1 = require("../lib/skeleton/annotator");
84
85
  const retriever_1 = require("../lib/skeleton/retriever");
85
86
  const skeletonizer_1 = require("../lib/skeleton/skeletonizer");
86
87
  const symbol_extractor_1 = require("../lib/skeleton/symbol-extractor");
88
+ const ann_config_1 = require("../lib/store/ann-config");
87
89
  const meta_cache_1 = require("../lib/store/meta-cache");
88
90
  const vector_db_1 = require("../lib/store/vector-db");
89
91
  const cross_project_1 = require("../lib/utils/cross-project");
@@ -121,6 +123,15 @@ function ok(text) {
121
123
  function err(text) {
122
124
  return { content: [{ type: "text", text }], isError: true };
123
125
  }
126
+ function shouldFallbackMcpDaemonSearch(error) {
127
+ const message = String(error !== null && error !== void 0 ? error : "").toLowerCase();
128
+ return (message === "enoent" ||
129
+ message === "econnrefused" ||
130
+ message.includes("project not watched") ||
131
+ message.includes("daemon not ready") ||
132
+ message.includes("oversize") ||
133
+ message.includes("unknown command"));
134
+ }
124
135
  function isExplicitCrossProjectSearch(args, isSearchAll = false) {
125
136
  return (isSearchAll ||
126
137
  args.scope === "all" ||
@@ -339,6 +350,29 @@ exports.mcp = new commander_1.Command("mcp")
339
350
  _searcher = new searcher_1.Searcher(getVectorDb());
340
351
  return _searcher;
341
352
  }
353
+ function daemonSearch(args) {
354
+ return __awaiter(this, void 0, void 0, function* () {
355
+ var _a;
356
+ const { sendDaemonCommand } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
357
+ const response = yield sendDaemonCommand(Object.assign({ cmd: ((_a = args.filters) === null || _a === void 0 ? void 0 : _a.projectRoots) ? "search-v2" : "search" }, args), { timeoutMs: 60000 });
358
+ if (!response.ok) {
359
+ if (shouldFallbackMcpDaemonSearch(response.error))
360
+ return null;
361
+ const detail = [response.error, response.hint]
362
+ .filter((value) => typeof value === "string" && value)
363
+ .join(": ");
364
+ throw new Error(detail || "daemon search failed");
365
+ }
366
+ return {
367
+ data: Array.isArray(response.data)
368
+ ? response.data
369
+ : [],
370
+ warnings: Array.isArray(response.warnings)
371
+ ? response.warnings.filter((warning) => typeof warning === "string")
372
+ : undefined,
373
+ };
374
+ });
375
+ }
342
376
  function getSkeletonizer() {
343
377
  return __awaiter(this, void 0, void 0, function* () {
344
378
  if (!_skeletonizer) {
@@ -394,7 +428,7 @@ exports.mcp = new commander_1.Command("mcp")
394
428
  // --- Tool handlers ---
395
429
  function handleSemanticSearch(args_1) {
396
430
  return __awaiter(this, arguments, void 0, function* (args, isSearchAll = false) {
397
- var _a, _b;
431
+ var _a, _b, _c;
398
432
  const query = String(args.query || "");
399
433
  if (!query)
400
434
  return err("Missing required parameter: query");
@@ -482,7 +516,6 @@ exports.mcp = new commander_1.Command("mcp")
482
516
  ? new Set((_a = filters.projectRoots) !== null && _a !== void 0 ? _a : [])
483
517
  : new Set([resolvedRoot]);
484
518
  (0, embedding_status_1.assertEmbeddingSearchCompatible)((0, project_registry_1.listProjects)().filter((project) => selectedRoots.has(project.root)), (0, index_config_1.readGlobalConfig)());
485
- const searcher = getSearcher();
486
519
  // Aider-style seeding: the agent passes its open files / discussed
487
520
  // symbols; the searcher biases candidate generation toward them.
488
521
  const parseSeedList = (v) => {
@@ -500,9 +533,15 @@ exports.mcp = new commander_1.Command("mcp")
500
533
  const seeds = seedFiles || seedSymbols
501
534
  ? { files: seedFiles, symbols: seedSymbols }
502
535
  : undefined;
503
- const result = yield searcher.search(query, limit, { rerank: process.env.GMAX_RERANK === "1", seeds }, Object.keys(filters).length > 0 ? filters : undefined, pathPrefix);
536
+ const effectiveFilters = Object.keys(filters).length > 0 ? filters : undefined;
537
+ const searchOptions = {
538
+ rerank: process.env.GMAX_RERANK === "1",
539
+ seeds,
540
+ };
541
+ const result = (_b = (yield daemonSearch(Object.assign({ projectRoot: resolvedRoot, query,
542
+ limit, filters: effectiveFilters, pathPrefix }, searchOptions)))) !== null && _b !== void 0 ? _b : (yield getSearcher().search(query, limit, searchOptions, effectiveFilters, pathPrefix));
504
543
  const allowedRoots = searchAll
505
- ? ((_b = filters.projectRoots) !== null && _b !== void 0 ? _b : [])
544
+ ? ((_c = filters.projectRoots) !== null && _c !== void 0 ? _c : [])
506
545
  : [resolvedRoot];
507
546
  result.data = result.data.filter((chunk) => {
508
547
  const candidate = chunkAbsPath(chunk);
@@ -645,7 +684,7 @@ exports.mcp = new commander_1.Command("mcp")
645
684
  const regex = new RegExp(namePattern, "i");
646
685
  results = results.filter((r) => r.symbols.some((s) => regex.test(s)));
647
686
  }
648
- catch (_c) {
687
+ catch (_d) {
649
688
  // Invalid regex — skip filter
650
689
  }
651
690
  }
@@ -688,7 +727,7 @@ exports.mcp = new commander_1.Command("mcp")
688
727
  output += `\n${traceLines.join("\n")}`;
689
728
  }
690
729
  }
691
- catch (_d) {
730
+ catch (_e) {
692
731
  // Trace failed — return search results without trace
693
732
  }
694
733
  }
@@ -1891,6 +1930,7 @@ exports.mcp = new commander_1.Command("mcp")
1891
1930
  // --- New command handlers (Phase 4) ---
1892
1931
  function handleDiffChanges(args) {
1893
1932
  return __awaiter(this, void 0, void 0, function* () {
1933
+ var _a;
1894
1934
  ensureWatcher();
1895
1935
  const { getChangedFiles } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/git")));
1896
1936
  const ref = typeof args.ref === "string" ? args.ref : undefined;
@@ -1910,8 +1950,11 @@ exports.mcp = new commander_1.Command("mcp")
1910
1950
  }
1911
1951
  const rel = (p) => p.startsWith(`${root}/`) ? p.slice(root.length + 1) : p;
1912
1952
  if (query) {
1913
- const searcher = getSearcher();
1914
- const response = yield searcher.search(query, limit, { rerank: process.env.GMAX_RERANK === "1" }, {}, root);
1953
+ const searchOptions = {
1954
+ rerank: process.env.GMAX_RERANK === "1",
1955
+ };
1956
+ const response = (_a = (yield daemonSearch(Object.assign({ projectRoot: root, query,
1957
+ limit, filters: {}, pathPrefix: root }, searchOptions)))) !== null && _a !== void 0 ? _a : (yield getSearcher().search(query, limit, searchOptions, {}, root));
1915
1958
  const changedSet = new Set(changedFiles);
1916
1959
  // searcher.search() returns mapped chunks (path under metadata.path);
1917
1960
  // changedFiles are absolute, so match on the resolved absolute path.
@@ -2100,8 +2143,7 @@ exports.mcp = new commander_1.Command("mcp")
2100
2143
  const source = sourceRows[0];
2101
2144
  if (!source.vector || source.vector.length === 0)
2102
2145
  return ok("Source chunk has no embedding.");
2103
- const results = yield table
2104
- .vectorSearch(source.vector)
2146
+ const results = yield (0, ann_config_1.configureAnnVectorQuery)(table.vectorSearch(source.vector))
2105
2147
  .select([
2106
2148
  "path",
2107
2149
  "start_line",
@@ -2132,7 +2174,7 @@ exports.mcp = new commander_1.Command("mcp")
2132
2174
  }
2133
2175
  function handleBuildContext(args) {
2134
2176
  return __awaiter(this, void 0, void 0, function* () {
2135
- var _a, _b;
2177
+ var _a, _b, _c;
2136
2178
  ensureWatcher();
2137
2179
  const topic = String(args.topic || "");
2138
2180
  if (!topic)
@@ -2144,8 +2186,8 @@ exports.mcp = new commander_1.Command("mcp")
2144
2186
  if (!proj)
2145
2187
  return err("Project not added to gmax yet.");
2146
2188
  (0, embedding_status_1.assertEmbeddingSearchCompatible)([proj], (0, index_config_1.readGlobalConfig)());
2147
- const searcher = getSearcher();
2148
- const response = yield searcher.search(topic, limit, { rerank: process.env.GMAX_RERANK === "1" }, {}, root);
2189
+ const searchOptions = { rerank: process.env.GMAX_RERANK === "1" };
2190
+ const response = (_a = (yield daemonSearch(Object.assign({ projectRoot: root, query: topic, limit, filters: {}, pathPrefix: root }, searchOptions)))) !== null && _a !== void 0 ? _a : (yield getSearcher().search(topic, limit, searchOptions, {}, root));
2149
2191
  if (response.data.length === 0)
2150
2192
  return ok(`No results found for "${topic}".`);
2151
2193
  const rel = (p) => p.startsWith(`${root}/`) ? p.slice(root.length + 1) : p;
@@ -2167,7 +2209,7 @@ exports.mcp = new commander_1.Command("mcp")
2167
2209
  const absP = searchResultPath(r);
2168
2210
  const startLine = searchResultStartLine(r);
2169
2211
  const endLine = searchResultEndLine(r, startLine);
2170
- const sym = (_b = (_a = toStringArray(r.defined_symbols)) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : "";
2212
+ const sym = (_c = (_b = toStringArray(r.defined_symbols)) === null || _b === void 0 ? void 0 : _b[0]) !== null && _c !== void 0 ? _c : "";
2171
2213
  try {
2172
2214
  const sourcePath = (0, path_containment_1.resolveContainedFile)(root, absP);
2173
2215
  const content = fs.readFileSync(sourcePath, "utf-8");
@@ -2181,7 +2223,7 @@ exports.mcp = new commander_1.Command("mcp")
2181
2223
  sections.push(blob);
2182
2224
  tokensUsed += estTokens(blob);
2183
2225
  }
2184
- catch (_c) {
2226
+ catch (_d) {
2185
2227
  /* skip unreadable */
2186
2228
  }
2187
2229
  }
@@ -2769,6 +2811,9 @@ exports.mcp = new commander_1.Command("mcp")
2769
2811
  },
2770
2812
  }, handleReviewRisk);
2771
2813
  yield server.connect(transport);
2814
+ // Start the singleton daemon in the background without delaying MCP startup.
2815
+ void Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client"))).then(({ ensureDaemonRunning }) => ensureDaemonRunning())
2816
+ .catch((e) => console.error("[MCP] Daemon startup failed:", e));
2772
2817
  // Kick off index readiness check and watcher in background
2773
2818
  ensureIndexReady().catch((e) => console.error("[MCP] Index readiness check failed:", e));
2774
2819
  ensureWatcher();
@@ -70,7 +70,7 @@ function shouldFallbackFromDaemonError(error) {
70
70
  */
71
71
  function runSearch(params) {
72
72
  return __awaiter(this, void 0, void 0, function* () {
73
- var _a, _b;
73
+ var _a, _b, _c;
74
74
  const { pattern, options, projectRoot, effectiveRoot, paths, projectStatus, searchFilters, pathFilter, seeds, } = params;
75
75
  // Tracks a DB opened by the in-process path so it can be (a) returned to the
76
76
  // caller for closing after render, or (b) closed here if indexing throws.
@@ -201,6 +201,7 @@ function runSearch(params) {
201
201
  return { kind: "dry-run", vectorDb: openedDb };
202
202
  }
203
203
  yield vectorDb.createFTSIndex();
204
+ yield ((_b = vectorDb.createVectorIndex) === null || _b === void 0 ? void 0 : _b.call(vectorDb));
204
205
  if (result.degraded) {
205
206
  spinner.warn(`Indexing incomplete (${result.processed}/${result.total}) • ${result.scanErrors} scan errors • ${result.failedFiles} failed. Results may be partial.`);
206
207
  }
@@ -228,7 +229,7 @@ function runSearch(params) {
228
229
  }
229
230
  }
230
231
  // Ensure a watcher is running for live reindexing
231
- if (!process.env.VITEST && !((_b = process.env.NODE_ENV) === null || _b === void 0 ? void 0 : _b.includes("test"))) {
232
+ if (!process.env.VITEST && !((_c = process.env.NODE_ENV) === null || _c === void 0 ? void 0 : _c.includes("test"))) {
232
233
  const { launchWatcher } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/watcher-launcher")));
233
234
  const launched = yield launchWatcher(projectRoot);
234
235
  if (!launched.ok && launched.reason === "spawn-failed") {
@@ -260,7 +261,7 @@ function runSearch(params) {
260
261
  try {
261
262
  yield openedDb.close();
262
263
  }
263
- catch (_c) { }
264
+ catch (_d) { }
264
265
  }
265
266
  throw e;
266
267
  }
@@ -44,6 +44,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.similar = void 0;
46
46
  const commander_1 = require("commander");
47
+ const ann_config_1 = require("../lib/store/ann-config");
47
48
  const vector_db_1 = require("../lib/store/vector-db");
48
49
  const agent_errors_1 = require("../lib/utils/agent-errors");
49
50
  const arrow_1 = require("../lib/utils/arrow");
@@ -122,8 +123,7 @@ exports.similar = new commander_1.Command("similar")
122
123
  exclude: opts.exclude,
123
124
  });
124
125
  const pathScope = buildScopeWhere(scope);
125
- const results = yield table
126
- .vectorSearch(sourceVector)
126
+ const results = yield (0, ann_config_1.configureAnnVectorQuery)(table.vectorSearch(sourceVector))
127
127
  .select([
128
128
  "path",
129
129
  "start_line",
@@ -58,6 +58,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
58
58
  (_a = (_c = process.env).GMAX_WORKER_COUNT) !== null && _a !== void 0 ? _a : (_c.GMAX_WORKER_COUNT = "1");
59
59
  const path = __importStar(require("node:path"));
60
60
  const config_1 = require("./config");
61
+ const ann_config_1 = require("./lib/store/ann-config");
61
62
  const vector_db_1 = require("./lib/store/vector-db");
62
63
  const exit_1 = require("./lib/utils/exit");
63
64
  const filter_builder_1 = require("./lib/utils/filter-builder");
@@ -114,8 +115,7 @@ function probe(table, sym, expectedFile) {
114
115
  "defined_symbols",
115
116
  "referenced_symbols",
116
117
  ];
117
- const vectorRows = (yield table
118
- .vectorSearch(dense)
118
+ const vectorRows = (yield (0, ann_config_1.configureAnnVectorQuery)(table.vectorSearch(dense))
119
119
  .select([...columns, "_distance"])
120
120
  .where(where)
121
121
  .limit(PRE_K)
@@ -75,6 +75,7 @@ const path = __importStar(require("node:path"));
75
75
  const config_1 = require("./config");
76
76
  const callsites_1 = require("./lib/graph/callsites");
77
77
  const graph_builder_1 = require("./lib/graph/graph-builder");
78
+ const ann_config_1 = require("./lib/store/ann-config");
78
79
  const vector_db_1 = require("./lib/store/vector-db");
79
80
  const exit_1 = require("./lib/utils/exit");
80
81
  const filter_builder_1 = require("./lib/utils/filter-builder");
@@ -128,8 +129,7 @@ function probe(target) {
128
129
  "defined_symbols",
129
130
  "referenced_symbols",
130
131
  ];
131
- const vectorRows = (yield table
132
- .vectorSearch(dense)
132
+ const vectorRows = (yield (0, ann_config_1.configureAnnVectorQuery)(table.vectorSearch(dense))
133
133
  .select([...columns, "_distance"])
134
134
  .where(where)
135
135
  .limit(PRE_K)
@@ -60,6 +60,7 @@ exports.buildFindings = buildFindings;
60
60
  exports.analyzeSurprisingConnections = analyzeSurprisingConnections;
61
61
  const path = __importStar(require("node:path"));
62
62
  const callsites_1 = require("../graph/callsites");
63
+ const ann_config_1 = require("../store/ann-config");
63
64
  const arrow_1 = require("../utils/arrow");
64
65
  const scope_filter_1 = require("../utils/scope-filter");
65
66
  exports.DEFAULT_SURPRISE_OPTIONS = {
@@ -520,8 +521,7 @@ function analyzeSurprisingConnections(table_1, projectRoot_1) {
520
521
  };
521
522
  const pairs = new Map();
522
523
  for (const source of anchors) {
523
- const neighbors = (yield table
524
- .vectorSearch(source.vector)
524
+ const neighbors = (yield (0, ann_config_1.configureAnnVectorQuery)(table.vectorSearch(source.vector))
525
525
  .select([...exports.SURPRISE_COLUMNS, "_distance"])
526
526
  .where(where)
527
527
  .limit(opts.neighbors + 8)
@@ -440,6 +440,7 @@ class Daemon {
440
440
  if (this.vectorDb) {
441
441
  yield this.vectorDb.ensureTable();
442
442
  yield this.vectorDb.createFTSIndex();
443
+ yield this.vectorDb.createVectorIndex(false, 5, true);
443
444
  }
444
445
  const pool = this.workerPool;
445
446
  if (!pool)
@@ -44,6 +44,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.ProjectBatchProcessor = void 0;
46
46
  const path = __importStar(require("node:path"));
47
+ const config_1 = require("../../config");
47
48
  const vector_db_1 = require("../store/vector-db");
48
49
  const cache_check_1 = require("../utils/cache-check");
49
50
  const file_utils_1 = require("../utils/file-utils");
@@ -55,9 +56,12 @@ const watcher_batch_1 = require("./watcher-batch");
55
56
  const DEBOUNCE_MS = 2000;
56
57
  const MAX_RETRIES = 5;
57
58
  const MAX_BATCH_SIZE = 50;
59
+ // Pool priority cannot preempt an active processFile, so keep one worker free
60
+ // for latency-sensitive encodeQuery/rerank work during indexing bursts.
61
+ const DEFAULT_BATCH_CONCURRENCY = Math.max(1, config_1.CONFIG.WORKER_THREADS - 1);
58
62
  class ProjectBatchProcessor {
59
63
  constructor(opts) {
60
- var _a, _b;
64
+ var _a, _b, _c;
61
65
  this.pending = new Map();
62
66
  this.retryCount = new Map();
63
67
  this.retryAt = new Map();
@@ -86,6 +90,7 @@ class ProjectBatchProcessor {
86
90
  this.filePolicy =
87
91
  (_b = opts.filePolicy) !== null && _b !== void 0 ? _b : new file_policy_1.ProjectFilePolicy(opts.projectRoot);
88
92
  this.wtag = `watch:${path.basename(opts.projectRoot)}`;
93
+ this.concurrency = Math.max(1, Math.floor((_c = opts.concurrency) !== null && _c !== void 0 ? _c : DEFAULT_BATCH_CONCURRENCY));
89
94
  const taskTimeoutMs = (() => {
90
95
  var _a;
91
96
  const fromEnv = Number.parseInt((_a = process.env.GMAX_WORKER_TASK_TIMEOUT_MS) !== null && _a !== void 0 ? _a : "", 10);
@@ -238,7 +243,7 @@ class ProjectBatchProcessor {
238
243
  (0, logger_1.log)(this.wtag, `Processing ${batch.size} changed files: ${filenames.join(", ")}`);
239
244
  const start = Date.now();
240
245
  let reindexed = 0;
241
- let processed = 0;
246
+ let settled = 0;
242
247
  let backoffOverrideMs = 0;
243
248
  try {
244
249
  // No lock needed — daemon is the single writer to LanceDB/MetaCache
@@ -268,14 +273,8 @@ class ProjectBatchProcessor {
268
273
  }
269
274
  this.pending.set(absPath, event);
270
275
  };
271
- for (const [absPath, event] of batch) {
272
- if (batchAc.signal.aborted)
273
- break;
274
- processed++;
275
- if (batch.size > 10 &&
276
- (processed % 10 === 0 || processed === batch.size)) {
277
- (0, logger_1.log)(this.wtag, `Progress: ${processed}/${batch.size} (${reindexed} reindexed)`);
278
- }
276
+ let stopDispatch = false;
277
+ const processOne = (absPath, event) => __awaiter(this, void 0, void 0, function* () {
279
278
  // Reclassify every event at apply time. A catchup-derived unlink may be
280
279
  // stale if the file was recreated after its directory was scanned.
281
280
  try {
@@ -283,7 +282,7 @@ class ProjectBatchProcessor {
283
282
  if (classification.status === "error") {
284
283
  console.error(`[${this.wtag}] Policy could not classify ${absPath}:`, classification.error);
285
284
  retryFailures.add(absPath);
286
- continue;
285
+ return;
287
286
  }
288
287
  if (classification.status === "excluded" ||
289
288
  classification.status === "missing") {
@@ -293,11 +292,11 @@ class ProjectBatchProcessor {
293
292
  reindexed++;
294
293
  }
295
294
  completed.add(absPath);
296
- continue;
295
+ return;
297
296
  }
298
297
  const stats = classification.stat;
299
298
  if (diskPressure === "critical") {
300
- continue;
299
+ return;
301
300
  }
302
301
  const cached = this.metaCache.get(absPath);
303
302
  const forceReprocess = batchForceGenerations.has(absPath);
@@ -305,7 +304,7 @@ class ProjectBatchProcessor {
305
304
  (0, cache_coherence_1.isMetaEntryCacheCurrent)(cached, absPath) &&
306
305
  (0, cache_check_1.isFileCached)(cached, stats)) {
307
306
  completed.add(absPath);
308
- continue;
307
+ return;
309
308
  }
310
309
  // Fast path: if only mtime changed but size matches and we have a hash,
311
310
  // verify in-process instead of dispatching to a worker (~220ms saved).
@@ -321,7 +320,7 @@ class ProjectBatchProcessor {
321
320
  if (hash === cached.hash) {
322
321
  metaUpdates.set(absPath, Object.assign(Object.assign({}, cached), { mtimeMs: snapshot.mtimeMs }));
323
322
  completed.add(absPath);
324
- continue;
323
+ return;
325
324
  }
326
325
  }
327
326
  const result = yield pool.processFile({
@@ -334,19 +333,19 @@ class ProjectBatchProcessor {
334
333
  const current = yield this.filePolicy.classifyFile(absPath);
335
334
  if (current.status === "error") {
336
335
  retryFailures.add(absPath);
337
- continue;
336
+ return;
338
337
  }
339
338
  if (current.status === "excluded" || current.status === "missing") {
340
339
  deletes.push(absPath);
341
340
  metaDeletes.push(absPath);
342
341
  reindexed++;
343
342
  completed.add(absPath);
344
- continue;
343
+ return;
345
344
  }
346
345
  if (current.stat.mtimeMs !== result.mtimeMs ||
347
346
  current.stat.size !== result.size) {
348
347
  retryFailures.add(absPath);
349
- continue;
348
+ return;
350
349
  }
351
350
  const metaEntry = {
352
351
  hash: result.hash,
@@ -361,14 +360,14 @@ class ProjectBatchProcessor {
361
360
  cached.hasVectors === result.vectors.length > 0) {
362
361
  metaUpdates.set(absPath, metaEntry);
363
362
  completed.add(absPath);
364
- continue;
363
+ return;
365
364
  }
366
365
  if (result.shouldDelete) {
367
366
  deletes.push(absPath);
368
367
  metaUpdates.set(absPath, metaEntry);
369
368
  reindexed++;
370
369
  completed.add(absPath);
371
- continue;
370
+ return;
372
371
  }
373
372
  deletes.push(absPath);
374
373
  if (result.vectors.length > 0) {
@@ -380,7 +379,7 @@ class ProjectBatchProcessor {
380
379
  }
381
380
  catch (err) {
382
381
  if (batchAc.signal.aborted)
383
- break;
382
+ return;
384
383
  const code = err === null || err === void 0 ? void 0 : err.code;
385
384
  if (code === "ENOENT") {
386
385
  deletes.push(absPath);
@@ -392,12 +391,40 @@ class ProjectBatchProcessor {
392
391
  console.error(`[${this.wtag}] Failed to process ${absPath}:`, err);
393
392
  if (!pool.isHealthy()) {
394
393
  console.error(`[${this.wtag}] Worker pool unhealthy, aborting batch`);
395
- break;
394
+ stopDispatch = true;
395
+ return;
396
396
  }
397
397
  retryFailures.add(absPath);
398
398
  }
399
399
  }
400
+ finally {
401
+ settled++;
402
+ if (batch.size > 10 &&
403
+ (settled % 10 === 0 || settled === batch.size)) {
404
+ (0, logger_1.log)(this.wtag, `Progress: ${settled}/${batch.size} (${reindexed} reindexed)`);
405
+ }
406
+ }
407
+ });
408
+ const activeTasks = [];
409
+ const schedule = (task) => __awaiter(this, void 0, void 0, function* () {
410
+ const taskPromise = task();
411
+ activeTasks.push(taskPromise);
412
+ const removeActiveTask = () => {
413
+ const index = activeTasks.indexOf(taskPromise);
414
+ if (index !== -1)
415
+ activeTasks.splice(index, 1);
416
+ };
417
+ void taskPromise.then(removeActiveTask, removeActiveTask);
418
+ if (activeTasks.length >= this.concurrency) {
419
+ yield Promise.race(activeTasks);
420
+ }
421
+ });
422
+ for (const [absPath, event] of batch) {
423
+ if (batchAc.signal.aborted || stopDispatch)
424
+ break;
425
+ yield schedule(() => processOne(absPath, event));
400
426
  }
427
+ yield Promise.allSettled(activeTasks);
401
428
  const pureDeleteCandidates = new Set(metaDeletes);
402
429
  const authoritativeMetaDeletes = new Set();
403
430
  for (const absPath of pureDeleteCandidates) {