grepmax 0.26.57 → 0.26.59

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.
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.DaemonPausedError = exports.PAUSED_CACHE_OPTIONS = exports.PAUSED_RESULT_LIMIT = exports.PAUSED_QUERY_TIMEOUT_MS = void 0;
13
+ exports.isPausedRead = isPausedRead;
14
+ exports.validatePausedRead = validatePausedRead;
15
+ exports.searchPausedIndex = searchPausedIndex;
16
+ const intent_1 = require("../search/intent");
17
+ const per_file_1 = require("../search/per-file");
18
+ const searcher_1 = require("../search/searcher");
19
+ const arrow_1 = require("../utils/arrow");
20
+ const query_timeout_1 = require("../utils/query-timeout");
21
+ const rows_handler_1 = require("./rows-handler");
22
+ exports.PAUSED_QUERY_TIMEOUT_MS = 2000;
23
+ exports.PAUSED_RESULT_LIMIT = 20;
24
+ exports.PAUSED_CACHE_OPTIONS = {
25
+ readOnly: true,
26
+ indexCacheMb: 32,
27
+ metadataCacheMb: 16,
28
+ };
29
+ class DaemonPausedError extends Error {
30
+ constructor(reason) {
31
+ super(`gmax is serving bounded reads; indexing and heavy work are paused: ${reason}`);
32
+ this.code = "DAEMON_PAUSED";
33
+ this.name = "DaemonPausedError";
34
+ }
35
+ }
36
+ exports.DaemonPausedError = DaemonPausedError;
37
+ function isPausedRead(name) {
38
+ return [
39
+ "keyword-search",
40
+ "rows.locate",
41
+ "rows.skeleton",
42
+ "graph.resolve",
43
+ "unwatch",
44
+ "llm-stop",
45
+ ].includes(name);
46
+ }
47
+ /** Refuse oversized requests rather than silently truncating exact lookups. */
48
+ function validatePausedRead(name, payload) {
49
+ if (!isPausedRead(name))
50
+ throw new DaemonPausedError(`${name} needs the full service`);
51
+ if (name === "graph.resolve" &&
52
+ (typeof payload.target !== "string" ||
53
+ payload.target.length > 4096 ||
54
+ !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(payload.target)))
55
+ throw new DaemonPausedError("file-wide symbol enumeration is paused; use an exact symbol or bounded file lookup");
56
+ if (name === "rows.skeleton" && typeof payload.path !== "string")
57
+ throw new DaemonPausedError("paused skeleton lookup requires an exact file path");
58
+ if (name !== "rows.locate")
59
+ return;
60
+ const matches = Array.isArray(payload.matches) ? payload.matches : [];
61
+ const limit = payload.limit === undefined ? exports.PAUSED_RESULT_LIMIT : Number(payload.limit);
62
+ if (matches.length > 4 ||
63
+ !Number.isInteger(limit) ||
64
+ limit < 1 ||
65
+ limit > exports.PAUSED_RESULT_LIMIT ||
66
+ payload.scoped === false ||
67
+ matches.some((m) => !m ||
68
+ !["definedSymbol", "path"].includes(String(m.kind)))) {
69
+ throw new DaemonPausedError("use at most four scoped symbol/file lookups with a limit of 20");
70
+ }
71
+ }
72
+ /** Existing FTS only: no embedding, vector columns, rebuild or scan fallback. */
73
+ function searchPausedIndex(db, payload, reason, signal) {
74
+ return __awaiter(this, void 0, void 0, function* () {
75
+ var _a, _b;
76
+ var _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
77
+ signal.throwIfAborted();
78
+ const root = (0, rows_handler_1.assertReadableProject)(payload.projectRoot);
79
+ for (const candidate of (_c = (_a = payload.filters) === null || _a === void 0 ? void 0 : _a.projectRoots) !== null && _c !== void 0 ? _c : [root])
80
+ (0, rows_handler_1.assertReadableProject)(candidate);
81
+ if (payload.query.length > 4096)
82
+ throw new DaemonPausedError("query exceeds 4096 characters");
83
+ const requestedLimit = (_d = payload.limit) !== null && _d !== void 0 ? _d : 10;
84
+ if (!Number.isInteger(requestedLimit) || requestedLimit < 1)
85
+ throw new Error("invalid limit");
86
+ const limit = Math.min(requestedLimit, exports.PAUSED_RESULT_LIMIT);
87
+ const maxPerFile = (0, per_file_1.validateMaxPerFile)(payload.maxPerFile);
88
+ yield db.adoptFTSIndex();
89
+ signal.throwIfAborted();
90
+ const table = yield db.ensureTable();
91
+ const prefix = (_e = payload.pathPrefix) !== null && _e !== void 0 ? _e : `${root.replace(/\/$/, "")}/`;
92
+ const where = (0, searcher_1.buildWhereClause)(((_b = payload.filters) === null || _b === void 0 ? void 0 : _b.projectRoots) ? undefined : prefix, payload.filters, (0, intent_1.detectIntent)(payload.query));
93
+ let query = table
94
+ .search(payload.query)
95
+ .fastSearch()
96
+ .select([
97
+ "path",
98
+ "hash",
99
+ "content",
100
+ "start_line",
101
+ "end_line",
102
+ "chunk_index",
103
+ "role",
104
+ "defined_symbols",
105
+ "parent_symbol",
106
+ "_score",
107
+ ])
108
+ .limit(exports.PAUSED_RESULT_LIMIT);
109
+ if (where)
110
+ query = query.where(where);
111
+ const rows = yield (0, query_timeout_1.withQueryTimeout)(query.toArray({ timeoutMs: exports.PAUSED_QUERY_TIMEOUT_MS }), "paused keyword search", exports.PAUSED_QUERY_TIMEOUT_MS);
112
+ signal.throwIfAborted();
113
+ const data = [];
114
+ const counts = new Map();
115
+ const best = Math.max(1, ...rows.map((r) => Number(r._score) || 0));
116
+ for (const row of rows) {
117
+ const path = String((_f = row.path) !== null && _f !== void 0 ? _f : "");
118
+ const count = (_g = counts.get(path)) !== null && _g !== void 0 ? _g : 0;
119
+ if (maxPerFile !== undefined && count >= maxPerFile)
120
+ continue;
121
+ counts.set(path, count + 1);
122
+ data.push({
123
+ type: "text",
124
+ text: String((_h = row.content) !== null && _h !== void 0 ? _h : "").slice(0, 8192),
125
+ score: (Number(row._score) || 0) / best,
126
+ metadata: { path, hash: String((_j = row.hash) !== null && _j !== void 0 ? _j : "") },
127
+ generated_metadata: {
128
+ start_line: Number((_k = row.start_line) !== null && _k !== void 0 ? _k : 0),
129
+ end_line: Number((_l = row.end_line) !== null && _l !== void 0 ? _l : 0),
130
+ },
131
+ chunk_index: Number((_m = row.chunk_index) !== null && _m !== void 0 ? _m : 0),
132
+ role: String((_o = row.role) !== null && _o !== void 0 ? _o : ""),
133
+ parent_symbol: String((_p = row.parent_symbol) !== null && _p !== void 0 ? _p : ""),
134
+ defined_symbols: (0, arrow_1.toArr)(row.defined_symbols).filter((s) => typeof s === "string"),
135
+ });
136
+ if (data.length >= limit)
137
+ break;
138
+ }
139
+ return {
140
+ ok: true,
141
+ data,
142
+ warnings: [
143
+ `Keyword search only; embeddings and indexing are paused: ${reason}. Results use the retained FTS index and keyword scores; unindexed rows are excluded.`,
144
+ ...(requestedLimit > limit
145
+ ? [`Paused search is limited to ${limit} results.`]
146
+ : []),
147
+ ...(payload.includeGraph || payload.includeSkeletons
148
+ ? [
149
+ "Inline graph/skeleton enrichment is paused; use bounded file lookups.",
150
+ ]
151
+ : []),
152
+ ],
153
+ };
154
+ });
155
+ }
@@ -440,7 +440,7 @@ function normalizeRow(row, select) {
440
440
  /** Run a batch of row selects, one result array per matcher, order preserved. */
441
441
  function runLocate(deps, req) {
442
442
  return __awaiter(this, void 0, void 0, function* () {
443
- var _a, _b;
443
+ var _a, _b, _c;
444
444
  const table = yield tableOf(deps);
445
445
  const scoped = req.scoped !== false;
446
446
  const pathScope = (0, scope_filter_1.buildScopeWhere)(req.scope);
@@ -452,9 +452,11 @@ function runLocate(deps, req) {
452
452
  .select(req.select)
453
453
  .where(scoped ? `${where} AND ${pathScope}` : where);
454
454
  query = query.limit((_a = req.limit) !== null && _a !== void 0 ? _a : exports.MAX_LOCATE_ROWS);
455
- const rows = yield (0, query_timeout_2.withQueryTimeout)(query.toArray(query_timeout_1.QUERY_EXECUTION_OPTIONS), label);
455
+ const rows = yield (0, query_timeout_2.withQueryTimeout)(query.toArray({
456
+ timeoutMs: (_b = deps.queryTimeoutMs) !== null && _b !== void 0 ? _b : query_timeout_1.QUERY_EXECUTION_OPTIONS.timeoutMs,
457
+ }), label, deps.queryTimeoutMs);
456
458
  results.push(rows
457
- .slice(0, (_b = req.limit) !== null && _b !== void 0 ? _b : exports.MAX_LOCATE_ROWS)
459
+ .slice(0, (_c = req.limit) !== null && _c !== void 0 ? _c : exports.MAX_LOCATE_ROWS)
458
460
  .map((row) => normalizeRow(row, req.select)));
459
461
  }
460
462
  return results;
@@ -525,23 +527,27 @@ function runSkeleton(deps, req) {
525
527
  const db = deps.vectorDb;
526
528
  let filePath = (_b = req.path) !== null && _b !== void 0 ? _b : null;
527
529
  if (!filePath && req.symbol) {
528
- filePath = yield findFileBySymbol(db, req.symbol, req.projectRoot);
530
+ filePath = yield findFileBySymbol(db, req.symbol, req.projectRoot, deps.queryTimeoutMs);
529
531
  }
530
532
  if (!filePath)
531
533
  return { path: null, skeleton: null };
532
- return { path: filePath, skeleton: yield (0, retriever_1.getStoredSkeleton)(db, filePath) };
534
+ return {
535
+ path: filePath,
536
+ skeleton: yield (0, retriever_1.getStoredSkeleton)(db, filePath, deps.queryTimeoutMs),
537
+ };
533
538
  });
534
539
  }
535
540
  /** FTS lookup for the file that defines `symbol`; null when nothing matches. */
536
- function findFileBySymbol(db, symbol, projectRoot) {
537
- return __awaiter(this, void 0, void 0, function* () {
541
+ function findFileBySymbol(db_1, symbol_1, projectRoot_1) {
542
+ return __awaiter(this, arguments, void 0, function* (db, symbol, projectRoot, timeoutMs = query_timeout_1.QUERY_EXECUTION_OPTIONS.timeoutMs) {
538
543
  try {
539
544
  const table = yield db.ensureTable();
540
545
  const results = yield table
541
546
  .search(symbol)
547
+ .select(["path", "defined_symbols"])
542
548
  .where((0, filter_builder_1.pathStartsWith)(`${projectRoot}/`))
543
549
  .limit(10)
544
- .toArray(query_timeout_1.QUERY_EXECUTION_OPTIONS);
550
+ .toArray({ timeoutMs });
545
551
  for (const result of results) {
546
552
  const defined = result.defined_symbols;
547
553
  if ((0, arrow_1.toArr)(defined).includes(symbol)) {
@@ -12,8 +12,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.getStoredSkeleton = getStoredSkeleton;
13
13
  const filter_builder_1 = require("../utils/filter-builder");
14
14
  const query_timeout_1 = require("../utils/query-timeout");
15
- function getStoredSkeleton(db, filePath) {
16
- return __awaiter(this, void 0, void 0, function* () {
15
+ function getStoredSkeleton(db_1, filePath_1) {
16
+ return __awaiter(this, arguments, void 0, function* (db, filePath, timeoutMs = query_timeout_1.QUERY_EXECUTION_OPTIONS.timeoutMs) {
17
17
  try {
18
18
  const table = yield db.ensureTable();
19
19
  // LanceDB query
@@ -22,7 +22,7 @@ function getStoredSkeleton(db, filePath) {
22
22
  .select(["file_skeleton"])
23
23
  .where(`path = '${(0, filter_builder_1.escapeSqlString)(filePath)}' AND is_anchor = true`)
24
24
  .limit(1)
25
- .toArray(query_timeout_1.QUERY_EXECUTION_OPTIONS);
25
+ .toArray({ timeoutMs });
26
26
  if (results.length > 0) {
27
27
  const skel = results[0].file_skeleton;
28
28
  if (typeof skel === "string" && skel.length > 0) {
@@ -213,14 +213,16 @@ function envMb(name, fallback) {
213
213
  const parsed = Number(process.env[name]);
214
214
  return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
215
215
  }
216
- function createLanceSession() {
216
+ function createLanceSession(options = {}) {
217
+ var _a, _b;
217
218
  const mb = (n) => BigInt(n * 1024 * 1024);
218
- return new lancedb.Session(mb(LANCE_INDEX_CACHE_MB), mb(LANCE_METADATA_CACHE_MB));
219
+ return new lancedb.Session(mb((_a = options.indexCacheMb) !== null && _a !== void 0 ? _a : LANCE_INDEX_CACHE_MB), mb((_b = options.metadataCacheMb) !== null && _b !== void 0 ? _b : LANCE_METADATA_CACHE_MB));
219
220
  }
220
221
  class VectorDB {
221
- constructor(lancedbDir, vectorDim, suppliedLease) {
222
+ constructor(lancedbDir, vectorDim, suppliedLease, options = {}) {
222
223
  this.lancedbDir = lancedbDir;
223
224
  this.suppliedLease = suppliedLease;
225
+ this.options = options;
224
226
  this.db = null;
225
227
  this.session = null;
226
228
  this.closed = false;
@@ -270,6 +272,11 @@ class VectorDB {
270
272
  this.vectorDim = vectorDim !== null && vectorDim !== void 0 ? vectorDim : (0, index_config_1.readGlobalConfig)().vectorDim;
271
273
  this.unregisterCleanup = (0, cleanup_1.registerCleanup)(() => this.close());
272
274
  }
275
+ assertMutationAllowed() {
276
+ if (this.options.readOnly)
277
+ throw new Error("gmax store is read-only");
278
+ (0, maintenance_policy_1.assertStoreMutationAllowed)();
279
+ }
273
280
  /**
274
281
  * Start a periodic maintenance timer (FTS rebuild + optimize).
275
282
  * Call once from the daemon — replaces per-processor maintenance intervals.
@@ -372,7 +379,7 @@ class VectorDB {
372
379
  }
373
380
  const creatingStore = !fs.existsSync(this.lancedbDir);
374
381
  if (creatingStore) {
375
- (0, maintenance_policy_1.assertStoreMutationAllowed)();
382
+ this.assertMutationAllowed();
376
383
  (0, maintenance_policy_1.assertFreshDiskMutationAllowed)(this.lancedbDir);
377
384
  }
378
385
  yield this.getLease();
@@ -381,11 +388,11 @@ class VectorDB {
381
388
  }
382
389
  if (!this.db) {
383
390
  if (creatingStore || !fs.existsSync(this.lancedbDir)) {
384
- (0, maintenance_policy_1.assertStoreMutationAllowed)();
391
+ this.assertMutationAllowed();
385
392
  (0, maintenance_policy_1.assertFreshDiskMutationAllowed)(this.lancedbDir);
386
393
  }
387
394
  fs.mkdirSync(this.lancedbDir, { recursive: true });
388
- this.session = createLanceSession();
395
+ this.session = createLanceSession(this.options);
389
396
  // 0.38 accepts the legacy third argument in its types but drops it in
390
397
  // the JS wrapper. Put the session in native ConnectionOptions instead.
391
398
  this.db = yield lancedb.connect(this.lancedbDir, {
@@ -430,6 +437,7 @@ class VectorDB {
430
437
  /** Upgrade and retain this instance's current lease for transfer to a new DB. */
431
438
  upgradeStoreLease(signal) {
432
439
  return __awaiter(this, void 0, void 0, function* () {
440
+ this.assertMutationAllowed();
433
441
  if (this.closed)
434
442
  throw new Error("VectorDB connection is closed");
435
443
  if (this.exclusiveMutationPromise)
@@ -526,7 +534,7 @@ class VectorDB {
526
534
  */
527
535
  withWriteGate(fn) {
528
536
  return __awaiter(this, void 0, void 0, function* () {
529
- (0, maintenance_policy_1.assertStoreMutationAllowed)();
537
+ this.assertMutationAllowed();
530
538
  this.ensureDiskOk();
531
539
  while (this.exclusiveMutationPromise || this.compactingPromise) {
532
540
  if (this.closed)
@@ -535,7 +543,7 @@ class VectorDB {
535
543
  }
536
544
  if (this.closed)
537
545
  throw new Error("VectorDB connection is closed");
538
- (0, maintenance_policy_1.assertStoreMutationAllowed)();
546
+ this.assertMutationAllowed();
539
547
  this.ensureDiskOk();
540
548
  this.activeWrites++;
541
549
  try {
@@ -575,7 +583,7 @@ class VectorDB {
575
583
  }
576
584
  withExclusiveTableMutation(mutation) {
577
585
  return __awaiter(this, void 0, void 0, function* () {
578
- (0, maintenance_policy_1.assertStoreMutationAllowed)();
586
+ this.assertMutationAllowed();
579
587
  this.ensureDiskOk();
580
588
  if (this.closed)
581
589
  throw new Error("VectorDB connection is closed");
@@ -602,7 +610,7 @@ class VectorDB {
602
610
  });
603
611
  }
604
612
  yield Promise.all([this.drainWrites(), this.drainCompactions()]);
605
- (0, maintenance_policy_1.assertStoreMutationAllowed)();
613
+ this.assertMutationAllowed();
606
614
  this.ensureDiskOk();
607
615
  const db = yield this.getDb();
608
616
  const result = yield mutation(db);
@@ -773,7 +781,8 @@ class VectorDB {
773
781
  }
774
782
  ensureTable() {
775
783
  return __awaiter(this, void 0, void 0, function* () {
776
- if ((0, maintenance_policy_1.storeMutationDeniedReason)() !== null ||
784
+ if (this.options.readOnly ||
785
+ (0, maintenance_policy_1.storeMutationDeniedReason)() !== null ||
777
786
  this.checkDiskPressure(true) === "critical") {
778
787
  const existing = yield this.openExistingTableUnsafe();
779
788
  if (!existing)
@@ -794,7 +803,7 @@ class VectorDB {
794
803
  catch (err) {
795
804
  if (!isMissingTableError(err))
796
805
  throw err;
797
- (0, maintenance_policy_1.assertStoreMutationAllowed)();
806
+ this.assertMutationAllowed();
798
807
  (0, logger_1.log)("db", `Creating table (${this.vectorDim}d)`);
799
808
  const schema = this.buildSchema();
800
809
  table = yield db.createTable(TABLE_NAME, [this.seedRow()], {
@@ -805,7 +814,7 @@ class VectorDB {
805
814
  return table;
806
815
  }
807
816
  yield this.validateSchema(table);
808
- (0, maintenance_policy_1.assertStoreMutationAllowed)();
817
+ this.assertMutationAllowed();
809
818
  yield this.evolveSchema(table);
810
819
  return table;
811
820
  });
@@ -944,7 +953,7 @@ class VectorDB {
944
953
  }
945
954
  /** Whether this instance may create or rebuild shared indexes. */
946
955
  canBuildIndexes() {
947
- return this.indexOwner;
956
+ return this.indexOwner && !this.options.readOnly;
948
957
  }
949
958
  /**
950
959
  * Adopt the FTS index if one already exists, without ever building it.
@@ -92,14 +92,19 @@ function spawnDaemon() {
92
92
  * spawn fails.
93
93
  */
94
94
  function spawnDaemonProcess() {
95
- return __awaiter(this, void 0, void 0, function* () {
95
+ return __awaiter(this, arguments, void 0, function* (options = {}) {
96
96
  const denied = (0, autostart_1.daemonStartDeniedReason)();
97
- if (denied !== null)
97
+ if (denied !== null && !options.readOnly)
98
98
  throw new Error(`gmax: ${denied}`);
99
99
  const logFile = path.join(config_1.PATHS.logsDir, "daemon.log");
100
100
  const out = (0, log_rotate_1.openRotatedLog)(logFile);
101
101
  try {
102
- const child = (0, node_child_process_1.spawn)(process.argv[0], [process.argv[1], "watch", "--daemon"], {
102
+ const child = (0, node_child_process_1.spawn)(process.argv[0], [
103
+ process.argv[1],
104
+ "watch",
105
+ "--daemon",
106
+ ...(options.readOnly ? ["--read-only"] : []),
107
+ ], {
103
108
  detached: true,
104
109
  stdio: ["ignore", out, out],
105
110
  cwd: process.cwd(),
@@ -122,6 +122,12 @@ class OperationCoordinator {
122
122
  void task.then(cleanup, cleanup);
123
123
  return task;
124
124
  }
125
+ /** Cancel existing work without closing admission for subsequent bounded reads. */
126
+ abortAndDrain(reason) {
127
+ for (const controller of this.controllers)
128
+ controller.abort(reason);
129
+ return Promise.allSettled([...this.activeTasks]).then(() => { });
130
+ }
125
131
  close(reason = new OperationClosedError()) {
126
132
  if (this.closePromise)
127
133
  return this.closePromise;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.26.57",
3
+ "version": "0.26.59",
4
4
  "author": "Robert Owens <78518764+reowens@users.noreply.github.com>",
5
5
  "homepage": "https://github.com/reowens/grepmax",
6
6
  "bugs": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.26.57",
3
+ "version": "0.26.59",
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",