grepmax 0.18.1 → 0.20.0

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.
Files changed (78) hide show
  1. package/.claude-plugin/marketplace.json +23 -0
  2. package/README.md +28 -3
  3. package/dist/commands/add.js +4 -2
  4. package/dist/commands/claude-code.js +1 -1
  5. package/dist/commands/codex.js +3 -1
  6. package/dist/commands/context.js +1 -1
  7. package/dist/commands/dead.js +2 -6
  8. package/dist/commands/doctor.js +26 -7
  9. package/dist/commands/extract.js +3 -5
  10. package/dist/commands/impact.js +6 -4
  11. package/dist/commands/index.js +7 -2
  12. package/dist/commands/log.js +4 -2
  13. package/dist/commands/mcp.js +594 -722
  14. package/dist/commands/peek.js +15 -9
  15. package/dist/commands/plugin.js +36 -22
  16. package/dist/commands/project.js +16 -6
  17. package/dist/commands/related.js +8 -9
  18. package/dist/commands/remove.js +9 -2
  19. package/dist/commands/review.js +1 -1
  20. package/dist/commands/setup.js +4 -2
  21. package/dist/commands/similar.js +4 -4
  22. package/dist/commands/status.js +6 -4
  23. package/dist/commands/summarize.js +6 -4
  24. package/dist/commands/symbols.js +1 -1
  25. package/dist/commands/test-find.js +2 -2
  26. package/dist/commands/trace.js +12 -5
  27. package/dist/commands/watch.js +33 -9
  28. package/dist/eval-graph-nav.js +4 -1
  29. package/dist/eval-graph-recovery-probe.js +56 -14
  30. package/dist/eval-graph-spotcheck.js +10 -2
  31. package/dist/eval-graph-totals.js +5 -2
  32. package/dist/eval-oss.js +212 -37
  33. package/dist/eval-seed.js +13 -4
  34. package/dist/index.js +9 -9
  35. package/dist/lib/daemon/daemon.js +32 -13
  36. package/dist/lib/daemon/ipc-handler.js +24 -5
  37. package/dist/lib/daemon/mlx-server-manager.js +11 -3
  38. package/dist/lib/daemon/process-manager.js +1 -2
  39. package/dist/lib/daemon/watcher-manager.js +22 -6
  40. package/dist/lib/graph/callsites.js +151 -25
  41. package/dist/lib/graph/graph-builder.js +2 -2
  42. package/dist/lib/graph/impact.js +6 -6
  43. package/dist/lib/index/batch-processor.js +16 -6
  44. package/dist/lib/index/chunker.js +2 -0
  45. package/dist/lib/index/syncer.js +22 -10
  46. package/dist/lib/index/watcher-batch.js +10 -1
  47. package/dist/lib/llm/config.js +2 -1
  48. package/dist/lib/llm/diff.js +56 -11
  49. package/dist/lib/llm/investigate.js +52 -11
  50. package/dist/lib/llm/review.js +21 -8
  51. package/dist/lib/llm/server.js +25 -9
  52. package/dist/lib/llm/tools.js +1 -1
  53. package/dist/lib/output/agent-search-formatter.js +25 -3
  54. package/dist/lib/output/index-state-footer.js +1 -1
  55. package/dist/lib/review/risk.js +2 -4
  56. package/dist/lib/search/pagerank.js +1 -1
  57. package/dist/lib/search/searcher.js +43 -17
  58. package/dist/lib/search/seed-weight.js +4 -1
  59. package/dist/lib/skeleton/symbol-extractor.js +2 -1
  60. package/dist/lib/store/vector-db.js +21 -10
  61. package/dist/lib/utils/cross-project.js +5 -1
  62. package/dist/lib/utils/daemon-client.js +39 -1
  63. package/dist/lib/utils/filter-builder.js +22 -0
  64. package/dist/lib/utils/git.js +10 -2
  65. package/dist/lib/utils/project-registry.js +3 -2
  66. package/dist/lib/utils/scope-filter.js +6 -6
  67. package/dist/lib/utils/watcher-launcher.js +4 -1
  68. package/dist/lib/utils/watcher-store.js +2 -1
  69. package/dist/lib/workers/embeddings/granite.js +4 -1
  70. package/dist/lib/workers/embeddings/mlx-client.js +7 -2
  71. package/dist/lib/workers/pool.js +30 -7
  72. package/dist/lib/workers/process-child.js +1 -1
  73. package/package.json +23 -19
  74. package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
  75. package/plugins/grepmax/hooks/cwd-changed.js +4 -1
  76. package/plugins/grepmax/hooks/pre-grep.js +2 -1
  77. package/plugins/grepmax/hooks/start.js +45 -10
  78. package/plugins/grepmax/hooks/subagent-start.js +1 -4
@@ -105,9 +105,20 @@ function handleCommand(daemon, cmd, conn) {
105
105
  var _a, _b, _c, _d, _e;
106
106
  try {
107
107
  (0, logger_1.debug)("daemon", `ipc cmd=${cmd.cmd}${cmd.root ? ` root=${cmd.root}` : ""}`);
108
+ // The socket listens before LanceDB/MetaCache are open so liveness probes
109
+ // succeed during slow startup. Gate resource-dependent commands until those
110
+ // stores exist; ping/shutdown must always work (probes + restart).
111
+ if (cmd.cmd !== "ping" && cmd.cmd !== "shutdown" && !daemon.isReady()) {
112
+ return { ok: false, error: "daemon initializing" };
113
+ }
108
114
  switch (cmd.cmd) {
109
115
  case "ping":
110
- return { ok: true, pid: process.pid, uptime: daemon.uptime(), version: DAEMON_VERSION };
116
+ return {
117
+ ok: true,
118
+ pid: process.pid,
119
+ uptime: daemon.uptime(),
120
+ version: DAEMON_VERSION,
121
+ };
111
122
  case "watch": {
112
123
  const root = String(cmd.root || "");
113
124
  if (!root)
@@ -135,7 +146,9 @@ function handleCommand(daemon, cmd, conn) {
135
146
  const fromPid = (_b = cmd.from_pid) !== null && _b !== void 0 ? _b : "?";
136
147
  const fromPpid = (_c = cmd.from_ppid) !== null && _c !== void 0 ? _c : "?";
137
148
  const fromVer = (_d = cmd.from_version) !== null && _d !== void 0 ? _d : "?";
138
- const fromArgv = Array.isArray(cmd.from_argv) ? cmd.from_argv.join(" ") : "?";
149
+ const fromArgv = Array.isArray(cmd.from_argv)
150
+ ? cmd.from_argv.join(" ")
151
+ : "?";
139
152
  const fromParentCmd = (_e = cmd.from_parent_cmd) !== null && _e !== void 0 ? _e : "?";
140
153
  console.log(`[daemon] shutdown command received via IPC: reason=${reason} from_pid=${fromPid} from_ppid=${fromPpid} from_version=${fromVer} from_argv=[${fromArgv}] from_parent_cmd=[${fromParentCmd}]`);
141
154
  setImmediate(() => daemon.shutdown());
@@ -155,12 +168,16 @@ function handleCommand(daemon, cmd, conn) {
155
168
  conn.on("close", onClose);
156
169
  try {
157
170
  const limitRaw = typeof cmd.limit === "number" ? cmd.limit : 10;
158
- const skeletonLimitRaw = typeof cmd.skeletonLimit === "number" ? cmd.skeletonLimit : undefined;
171
+ const skeletonLimitRaw = typeof cmd.skeletonLimit === "number"
172
+ ? cmd.skeletonLimit
173
+ : undefined;
159
174
  // Accept both the legacy single-string `exclude` and the new
160
175
  // `excludePrefixes`/`inPrefixes` arrays (--in/--exclude on the CLI).
161
176
  // Daemon may outlive a CLI restart, so keep both wire shapes for
162
177
  // one release; drop the single-string form in v0.17.x.
163
- const filters = cmd.filters && typeof cmd.filters === "object" && !Array.isArray(cmd.filters)
178
+ const filters = cmd.filters &&
179
+ typeof cmd.filters === "object" &&
180
+ !Array.isArray(cmd.filters)
164
181
  ? cmd.filters
165
182
  : undefined;
166
183
  const resp = yield daemon.search({
@@ -171,7 +188,9 @@ function handleCommand(daemon, cmd, conn) {
171
188
  pathPrefix: typeof cmd.pathPrefix === "string" ? cmd.pathPrefix : undefined,
172
189
  rerank: cmd.rerank === true,
173
190
  explain: cmd.explain === true,
174
- seeds: cmd.seeds && typeof cmd.seeds === "object" && !Array.isArray(cmd.seeds)
191
+ seeds: cmd.seeds &&
192
+ typeof cmd.seeds === "object" &&
193
+ !Array.isArray(cmd.seeds)
175
194
  ? cmd.seeds
176
195
  : undefined,
177
196
  includeSkeletons: cmd.includeSkeletons === true,
@@ -69,15 +69,23 @@ class MlxServerManager {
69
69
  return __awaiter(this, void 0, void 0, function* () {
70
70
  const port = parseInt(process.env.MLX_EMBED_PORT || "8100", 10);
71
71
  return new Promise((resolve) => {
72
- const req = http.get({ hostname: "127.0.0.1", port, path: "/health", timeout: 2000 }, (res) => { res.resume(); resolve(res.statusCode === 200); });
72
+ const req = http.get({ hostname: "127.0.0.1", port, path: "/health", timeout: 2000 }, (res) => {
73
+ res.resume();
74
+ resolve(res.statusCode === 200);
75
+ });
73
76
  req.on("error", () => resolve(false));
74
- req.on("timeout", () => { req.destroy(); resolve(false); });
77
+ req.on("timeout", () => {
78
+ req.destroy();
79
+ resolve(false);
80
+ });
75
81
  });
76
82
  });
77
83
  }
78
84
  getPortPid(port) {
79
85
  try {
80
- const out = (0, node_child_process_1.execSync)(`lsof -ti :${port}`, { timeout: 5000 }).toString().trim();
86
+ const out = (0, node_child_process_1.execSync)(`lsof -ti :${port}`, { timeout: 5000 })
87
+ .toString()
88
+ .trim();
81
89
  const pid = parseInt(out.split("\n")[0], 10);
82
90
  return Number.isFinite(pid) ? pid : null;
83
91
  }
@@ -83,8 +83,7 @@ class ProcessManager {
83
83
  killStaleProcesses() {
84
84
  return __awaiter(this, void 0, void 0, function* () {
85
85
  // 1. Check for other daemon processes
86
- const daemonPids = this.findProcessesByTitle("gmax-daemon")
87
- .filter((pid) => pid !== process.pid);
86
+ const daemonPids = this.findProcessesByTitle("gmax-daemon").filter((pid) => pid !== process.pid);
88
87
  const workerPids = this.findProcessesByTitle("gmax-worker");
89
88
  if (daemonPids.length === 0 && workerPids.length === 0) {
90
89
  (0, logger_1.log)("daemon", "No stale processes found");
@@ -50,9 +50,9 @@ var __asyncValues = (this && this.__asyncValues) || function (o) {
50
50
  };
51
51
  Object.defineProperty(exports, "__esModule", { value: true });
52
52
  exports.WatcherManager = void 0;
53
- const watcher = __importStar(require("@parcel/watcher"));
54
53
  const fs = __importStar(require("node:fs"));
55
54
  const path = __importStar(require("node:path"));
55
+ const watcher = __importStar(require("@parcel/watcher"));
56
56
  const batch_processor_1 = require("../index/batch-processor");
57
57
  const watcher_1 = require("../index/watcher");
58
58
  const logger_1 = require("../utils/logger");
@@ -250,7 +250,7 @@ class WatcherManager {
250
250
  return;
251
251
  }
252
252
  // Backoff: wait before re-subscribing (3s, 6s, 12s)
253
- const delayMs = 3000 * Math.pow(2, fails - 1);
253
+ const delayMs = 3000 * Math.pow(2, (fails - 1));
254
254
  console.error(`[daemon:${name}] Recovering watcher (attempt ${fails}/${MAX_WATCHER_RETRIES}, backoff ${delayMs}ms)...`);
255
255
  setTimeout(() => {
256
256
  if (this.deps.getShuttingDown()) {
@@ -368,17 +368,20 @@ class WatcherManager {
368
368
  const bn = path.basename(absPath).toLowerCase();
369
369
  if (!INDEXABLE_EXTENSIONS.has(ext) && !INDEXABLE_EXTENSIONS.has(bn))
370
370
  continue;
371
- seenPaths.add(absPath);
372
371
  try {
373
372
  const stats = yield fs.promises.stat(absPath);
374
- // Skip files that are too large or empty — they'll never be indexed
373
+ // Skip files that are too large or empty — they'll never be indexed.
374
+ // Leave them OUT of seenPaths so a file that became empty/oversized is
375
+ // treated as deleted by the purge sweep below and its now-stale chunks
376
+ // are removed, rather than lingering as unsearchable-but-present rows.
375
377
  if (stats.size === 0 || stats.size > MAX_FILE_SIZE_BYTES)
376
378
  continue;
379
+ seenPaths.add(absPath);
377
380
  const cached = metaCache.get(absPath);
378
381
  if (!isFileCached(cached, stats)) {
379
382
  // Fast path: if only mtime changed but size is identical and we have a hash,
380
383
  // just verify the hash in-process instead of sending to a worker.
381
- if (cached && cached.hash && cached.size === stats.size) {
384
+ if ((cached === null || cached === void 0 ? void 0 : cached.hash) && cached.size === stats.size) {
382
385
  const { computeBufferHash } = yield Promise.resolve().then(() => __importStar(require("../utils/file-utils")));
383
386
  const buf = yield fs.promises.readFile(absPath);
384
387
  const hash = computeBufferHash(buf);
@@ -400,7 +403,7 @@ class WatcherManager {
400
403
  // batch processor drain and compaction run between bursts.
401
404
  if (queued % 500 === 0) {
402
405
  (0, logger_1.debug)("catchup", `${path.basename(root)}: throttle pause at ${queued} queued`);
403
- yield new Promise(r => setTimeout(r, 5000));
406
+ yield new Promise((r) => setTimeout(r, 5000));
404
407
  }
405
408
  }
406
409
  else {
@@ -439,6 +442,19 @@ class WatcherManager {
439
442
  }
440
443
  unwatchProject(root) {
441
444
  return __awaiter(this, void 0, void 0, function* () {
445
+ // Stop poll-mode timers + their FSEvents recovery probe first, so a removed
446
+ // project can't keep scanning until full daemon shutdown. These live
447
+ // independently of the processor, so clear them even on the early return.
448
+ const pollInterval = this.pollIntervals.get(root);
449
+ if (pollInterval) {
450
+ clearInterval(pollInterval);
451
+ this.pollIntervals.delete(root);
452
+ }
453
+ const recoveryTimer = this.pollRecoveryTimers.get(root);
454
+ if (recoveryTimer) {
455
+ clearInterval(recoveryTimer);
456
+ this.pollRecoveryTimers.delete(root);
457
+ }
442
458
  const processor = this.deps.processors.get(root);
443
459
  if (!processor)
444
460
  return;
@@ -110,37 +110,163 @@ function resolveCallSites(callers, targetSymbol, fileCache = new Map()) {
110
110
  */
111
111
  const BUILTIN_CALLEES = new Set([
112
112
  // Object/primitive methods
113
- "toString", "valueOf", "hasOwnProperty",
113
+ "toString",
114
+ "valueOf",
115
+ "hasOwnProperty",
114
116
  // Array/String methods
115
- "push", "pop", "shift", "unshift", "slice", "splice", "concat", "join",
116
- "map", "filter", "reduce", "forEach", "find", "findIndex", "some", "every",
117
- "includes", "indexOf", "lastIndexOf", "sort", "reverse", "flat", "flatMap",
118
- "keys", "values", "entries", "fill",
119
- "split", "trim", "trimStart", "trimEnd", "replace", "replaceAll", "match",
120
- "matchAll", "test", "exec", "padStart", "padEnd", "repeat", "charAt",
121
- "charCodeAt", "codePointAt", "startsWith", "endsWith", "toLowerCase",
122
- "toUpperCase", "substring", "substr", "localeCompare", "normalize",
117
+ "push",
118
+ "pop",
119
+ "shift",
120
+ "unshift",
121
+ "slice",
122
+ "splice",
123
+ "concat",
124
+ "join",
125
+ "map",
126
+ "filter",
127
+ "reduce",
128
+ "forEach",
129
+ "find",
130
+ "findIndex",
131
+ "some",
132
+ "every",
133
+ "includes",
134
+ "indexOf",
135
+ "lastIndexOf",
136
+ "sort",
137
+ "reverse",
138
+ "flat",
139
+ "flatMap",
140
+ "keys",
141
+ "values",
142
+ "entries",
143
+ "fill",
144
+ "split",
145
+ "trim",
146
+ "trimStart",
147
+ "trimEnd",
148
+ "replace",
149
+ "replaceAll",
150
+ "match",
151
+ "matchAll",
152
+ "test",
153
+ "exec",
154
+ "padStart",
155
+ "padEnd",
156
+ "repeat",
157
+ "charAt",
158
+ "charCodeAt",
159
+ "codePointAt",
160
+ "startsWith",
161
+ "endsWith",
162
+ "toLowerCase",
163
+ "toUpperCase",
164
+ "substring",
165
+ "substr",
166
+ "localeCompare",
167
+ "normalize",
123
168
  // Map/Set methods
124
- "get", "set", "has", "add", "delete", "clear",
169
+ "get",
170
+ "set",
171
+ "has",
172
+ "add",
173
+ "delete",
174
+ "clear",
125
175
  // Math/Date/Number members
126
- "trunc", "floor", "ceil", "round", "abs", "min", "max", "random", "pow",
127
- "sqrt", "log2", "log10", "sign", "now", "parse", "parseInt", "parseFloat",
128
- "isInteger", "isFinite", "isNaN", "toFixed", "toISOString",
176
+ "trunc",
177
+ "floor",
178
+ "ceil",
179
+ "round",
180
+ "abs",
181
+ "min",
182
+ "max",
183
+ "random",
184
+ "pow",
185
+ "sqrt",
186
+ "log2",
187
+ "log10",
188
+ "sign",
189
+ "now",
190
+ "parse",
191
+ "parseInt",
192
+ "parseFloat",
193
+ "isInteger",
194
+ "isFinite",
195
+ "isNaN",
196
+ "toFixed",
197
+ "toISOString",
129
198
  // Promise/Function members
130
- "then", "catch", "finally", "resolve", "reject", "all", "allSettled",
131
- "race", "any", "call", "apply", "bind",
199
+ "then",
200
+ "catch",
201
+ "finally",
202
+ "resolve",
203
+ "reject",
204
+ "all",
205
+ "allSettled",
206
+ "race",
207
+ "any",
208
+ "call",
209
+ "apply",
210
+ "bind",
132
211
  // JSON / global constructors & functions
133
- "JSON", "stringify", "Math", "Date", "Promise", "Array", "Object", "String",
134
- "Number", "Boolean", "Symbol", "RegExp", "Error", "TypeError", "RangeError",
135
- "Map", "Set", "WeakMap", "WeakSet", "Buffer", "URL", "URLSearchParams",
136
- "isArray", "from", "of", "assign", "freeze", "create", "getOwnPropertyNames",
137
- "fromEntries", "setTimeout", "setInterval", "clearTimeout", "clearInterval",
138
- "queueMicrotask", "structuredClone", "fetch", "console", "process",
139
- "require", "encodeURIComponent", "decodeURIComponent", "encodeURI",
140
- "decodeURI", "atob", "btoa",
212
+ "JSON",
213
+ "stringify",
214
+ "Math",
215
+ "Date",
216
+ "Promise",
217
+ "Array",
218
+ "Object",
219
+ "String",
220
+ "Number",
221
+ "Boolean",
222
+ "Symbol",
223
+ "RegExp",
224
+ "Error",
225
+ "TypeError",
226
+ "RangeError",
227
+ "Map",
228
+ "Set",
229
+ "WeakMap",
230
+ "WeakSet",
231
+ "Buffer",
232
+ "URL",
233
+ "URLSearchParams",
234
+ "isArray",
235
+ "from",
236
+ "of",
237
+ "assign",
238
+ "freeze",
239
+ "create",
240
+ "getOwnPropertyNames",
241
+ "fromEntries",
242
+ "setTimeout",
243
+ "setInterval",
244
+ "clearTimeout",
245
+ "clearInterval",
246
+ "queueMicrotask",
247
+ "structuredClone",
248
+ "fetch",
249
+ "console",
250
+ "process",
251
+ "require",
252
+ "encodeURIComponent",
253
+ "decodeURIComponent",
254
+ "encodeURI",
255
+ "decodeURI",
256
+ "atob",
257
+ "btoa",
141
258
  // console members
142
- "warn", "info", "debug", "trace", "table", "dir", "group", "groupEnd",
143
- "assert", "time", "timeEnd",
259
+ "warn",
260
+ "info",
261
+ "debug",
262
+ "trace",
263
+ "table",
264
+ "dir",
265
+ "group",
266
+ "groupEnd",
267
+ "assert",
268
+ "time",
269
+ "timeEnd",
144
270
  ]);
145
271
  /** True when an UNRESOLVED callee name is a known JS/TS builtin. */
146
272
  function isBuiltinCallee(name) {
@@ -27,10 +27,10 @@ class GraphBuilder {
27
27
  scopeWhere(condition) {
28
28
  let result = condition;
29
29
  if (this.pathPrefix) {
30
- result = `${result} AND path LIKE '${(0, filter_builder_1.escapeSqlString)(this.pathPrefix)}%'`;
30
+ result = `${result} AND ${(0, filter_builder_1.pathStartsWith)(this.pathPrefix)}`;
31
31
  }
32
32
  for (const ex of this.excludePrefixes) {
33
- result = `${result} AND path NOT LIKE '${(0, filter_builder_1.escapeSqlString)(ex)}%'`;
33
+ result = `${result} AND ${(0, filter_builder_1.pathNotStartsWith)(ex)}`;
34
34
  }
35
35
  return result;
36
36
  }
@@ -66,10 +66,10 @@ function expandFileSymbols(symbols, vectorDb, projectRoot, excludePrefixes) {
66
66
  return symbols;
67
67
  const table = yield vectorDb.ensureTable();
68
68
  const prefix = projectRoot.endsWith("/") ? projectRoot : `${projectRoot}/`;
69
- let where = `array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(symbols[0])}') AND path LIKE '${(0, filter_builder_1.escapeSqlString)(prefix)}%'`;
69
+ let where = `array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(symbols[0])}') AND ${(0, filter_builder_1.pathStartsWith)(prefix)}`;
70
70
  for (const ex of excludePrefixes !== null && excludePrefixes !== void 0 ? excludePrefixes : []) {
71
71
  const exNorm = ex.endsWith("/") ? ex : `${ex}/`;
72
- where += ` AND path NOT LIKE '${(0, filter_builder_1.escapeSqlString)(exNorm)}%'`;
72
+ where += ` AND ${(0, filter_builder_1.pathNotStartsWith)(exNorm)}`;
73
73
  }
74
74
  // Find the file that defines this symbol
75
75
  const defRows = yield table
@@ -146,10 +146,10 @@ function findImportFallbackTests(expandedSymbols, originalSymbols, vectorDb, pro
146
146
  // test body's referenced_symbols ends up empty).
147
147
  const table = yield vectorDb.ensureTable();
148
148
  const prefix = projectRoot.endsWith("/") ? projectRoot : `${projectRoot}/`;
149
- let pathScope = `path LIKE '${(0, filter_builder_1.escapeSqlString)(prefix)}%'`;
149
+ let pathScope = (0, filter_builder_1.pathStartsWith)(prefix);
150
150
  for (const ex of excludePrefixes !== null && excludePrefixes !== void 0 ? excludePrefixes : []) {
151
151
  const exNorm = ex.endsWith("/") ? ex : `${ex}/`;
152
- pathScope += ` AND path NOT LIKE '${(0, filter_builder_1.escapeSqlString)(exNorm)}%'`;
152
+ pathScope += ` AND ${(0, filter_builder_1.pathNotStartsWith)(exNorm)}`;
153
153
  }
154
154
  // Textual matching runs on the ORIGINAL targets only: matching co-defined
155
155
  // file symbols (helpers like `log`) textually drags in every test file
@@ -203,10 +203,10 @@ function walkCallers(symbol, graphBuilder, testHits, currentHop, maxDepth, visit
203
203
  function findDependents(symbols_1, vectorDb_1, projectRoot_1, excludePaths_1) {
204
204
  return __awaiter(this, arguments, void 0, function* (symbols, vectorDb, projectRoot, excludePaths, limit = 10, excludePrefixes) {
205
205
  const table = yield vectorDb.ensureTable();
206
- let pathScope = `path LIKE '${(0, filter_builder_1.escapeSqlString)(projectRoot)}/%'`;
206
+ let pathScope = (0, filter_builder_1.pathStartsWith)(`${projectRoot}/`);
207
207
  for (const ex of excludePrefixes !== null && excludePrefixes !== void 0 ? excludePrefixes : []) {
208
208
  const exNorm = ex.endsWith("/") ? ex : `${ex}/`;
209
- pathScope += ` AND path NOT LIKE '${(0, filter_builder_1.escapeSqlString)(exNorm)}%'`;
209
+ pathScope += ` AND ${(0, filter_builder_1.pathNotStartsWith)(exNorm)}`;
210
210
  }
211
211
  const counts = new Map();
212
212
  for (const sym of symbols) {
@@ -46,10 +46,10 @@ exports.ProjectBatchProcessor = void 0;
46
46
  const fs = __importStar(require("node:fs"));
47
47
  const path = __importStar(require("node:path"));
48
48
  const config_1 = require("../../config");
49
+ const vector_db_1 = require("../store/vector-db");
49
50
  const cache_check_1 = require("../utils/cache-check");
50
51
  const file_utils_1 = require("../utils/file-utils");
51
52
  const logger_1 = require("../utils/logger");
52
- const vector_db_1 = require("../store/vector-db");
53
53
  const pool_1 = require("../workers/pool");
54
54
  const watcher_batch_1 = require("./watcher-batch");
55
55
  // Fast path-segment check to reject events that leak through FSEvents overflow.
@@ -164,7 +164,8 @@ class ProjectBatchProcessor {
164
164
  break;
165
165
  attempted.add(absPath);
166
166
  processed++;
167
- if (batch.size > 10 && (processed % 10 === 0 || processed === batch.size)) {
167
+ if (batch.size > 10 &&
168
+ (processed % 10 === 0 || processed === batch.size)) {
168
169
  (0, logger_1.log)(this.wtag, `Progress: ${processed}/${batch.size} (${reindexed} reindexed)`);
169
170
  }
170
171
  if (event === "unlink") {
@@ -176,15 +177,24 @@ class ProjectBatchProcessor {
176
177
  // change or add
177
178
  try {
178
179
  const stats = yield fs.promises.stat(absPath);
179
- if (!(0, file_utils_1.isIndexableFile)(absPath, stats.size))
180
+ if (!(0, file_utils_1.isIndexableFile)(absPath, stats.size)) {
181
+ // File became non-indexable (emptied or now too large). If we
182
+ // indexed it before, drop its chunks + meta so search stops
183
+ // returning stale content; otherwise there's nothing to clean up.
184
+ if (this.metaCache.get(absPath)) {
185
+ deletes.push(absPath);
186
+ metaDeletes.push(absPath);
187
+ reindexed++;
188
+ }
180
189
  continue;
190
+ }
181
191
  const cached = this.metaCache.get(absPath);
182
192
  if ((0, cache_check_1.isFileCached)(cached, stats)) {
183
193
  continue;
184
194
  }
185
195
  // Fast path: if only mtime changed but size matches and we have a hash,
186
196
  // verify in-process instead of dispatching to a worker (~220ms saved).
187
- if (cached && cached.hash && cached.size === stats.size) {
197
+ if ((cached === null || cached === void 0 ? void 0 : cached.hash) && cached.size === stats.size) {
188
198
  const buf = yield fs.promises.readFile(absPath);
189
199
  const hash = (0, file_utils_1.computeBufferHash)(buf);
190
200
  if (hash === cached.hash) {
@@ -319,8 +329,8 @@ class ProjectBatchProcessor {
319
329
  }
320
330
  }
321
331
  if (dropped > 0) {
322
- const droppedPaths = [...batch.keys()].filter(p => !requeued.has(p));
323
- (0, logger_1.log)(this.wtag, `Dropped ${dropped} file(s) after ${MAX_RETRIES} retries: ${droppedPaths.map(p => path.basename(p)).join(", ")}`);
332
+ const droppedPaths = [...batch.keys()].filter((p) => !requeued.has(p));
333
+ (0, logger_1.log)(this.wtag, `Dropped ${dropped} file(s) after ${MAX_RETRIES} retries: ${droppedPaths.map((p) => path.basename(p)).join(", ")}`);
324
334
  }
325
335
  backoffOverrideMs = this.pending.size > 0 ? backoffMs : 0;
326
336
  }
@@ -256,6 +256,7 @@ class TreeSitterChunker {
256
256
  if (c.definedSymbols && c.content.length > 100) {
257
257
  const extra = [];
258
258
  let m;
259
+ // biome-ignore lint/suspicious/noAssignInExpressions: idiomatic regex exec loop
259
260
  while ((m = PROP_FN_RE.exec(c.content)) !== null) {
260
261
  const name = m[1];
261
262
  if (name.length > 1 && !c.definedSymbols.includes(name)) {
@@ -278,6 +279,7 @@ class TreeSitterChunker {
278
279
  const existing = new Set(c.referencedSymbols);
279
280
  let jm;
280
281
  const jsxRefs = [];
282
+ // biome-ignore lint/suspicious/noAssignInExpressions: idiomatic regex exec loop
281
283
  while ((jm = JSX_RE.exec(c.content)) !== null) {
282
284
  // Extract base component name (e.g., "Foo" from "Foo.Bar")
283
285
  const full = jm[1];
@@ -55,11 +55,11 @@ exports.initialSync = initialSync;
55
55
  const fs = __importStar(require("node:fs"));
56
56
  const path = __importStar(require("node:path"));
57
57
  const config_1 = require("../../config");
58
- const logger_1 = require("../utils/logger");
59
58
  const meta_cache_1 = require("../store/meta-cache");
60
59
  const vector_db_1 = require("../store/vector-db");
61
60
  // isIndexableFile no longer used — extension check inlined for performance
62
61
  const lock_1 = require("../utils/lock");
62
+ const logger_1 = require("../utils/logger");
63
63
  const project_root_1 = require("../utils/project-root");
64
64
  const pool_1 = require("../workers/pool");
65
65
  const index_config_1 = require("./index-config");
@@ -74,13 +74,24 @@ function flushBatch(db, meta, vectors, pendingMeta, pendingDeletes, dryRun) {
74
74
  return __awaiter(this, void 0, void 0, function* () {
75
75
  if (dryRun)
76
76
  return;
77
- // 1. Write to VectorDB first (source of truth for data)
78
- if (pendingDeletes.length > 0) {
79
- yield db.deletePaths(pendingDeletes);
80
- }
77
+ // 1. Insert the new vectors FIRST, then delete the old chunks for those paths
78
+ // (excluding the just-inserted ids). Deleting first would leave a file
79
+ // unsearchable if the insert then fails — the old, still-valid chunks would
80
+ // already be gone. Mirrors batch-processor's insert-first flush. Paths in
81
+ // pendingDeletes with no new vectors (emptied / non-indexable files) match
82
+ // no excluded id, so all their old chunks are removed.
83
+ const newIds = vectors.map((v) => v.id);
81
84
  if (vectors.length > 0) {
82
85
  yield db.insertBatch(vectors);
83
86
  }
87
+ if (pendingDeletes.length > 0) {
88
+ if (newIds.length > 0) {
89
+ yield db.deletePathsExcludingIds(pendingDeletes, newIds);
90
+ }
91
+ else {
92
+ yield db.deletePaths(pendingDeletes);
93
+ }
94
+ }
84
95
  // 2. Update MetaCache only after VectorDB write succeeds
85
96
  for (const [p, entry] of pendingMeta.entries()) {
86
97
  meta.put(p, entry);
@@ -173,7 +184,8 @@ function initialSync(options) {
173
184
  }
174
185
  projectKeys.clear();
175
186
  }
176
- else if (projectKeys.size > 0 && vectorFileCount < projectKeys.size * 0.8) {
187
+ else if (projectKeys.size > 0 &&
188
+ vectorFileCount < projectKeys.size * 0.8) {
177
189
  (0, logger_1.log)("index", `Partial cache detected: ${vectorFileCount} files in vectors vs ${projectKeys.size} in cache — clearing cache to re-embed missing files`);
178
190
  for (const key of projectKeys) {
179
191
  mc.delete(key);
@@ -351,13 +363,13 @@ function initialSync(options) {
351
363
  return; // Broken symlink
352
364
  }
353
365
  }
354
- if (!stats.isFile() || stats.size === 0 || stats.size > config_1.MAX_FILE_SIZE_BYTES) {
366
+ if (!stats.isFile() ||
367
+ stats.size === 0 ||
368
+ stats.size > config_1.MAX_FILE_SIZE_BYTES) {
355
369
  return;
356
370
  }
357
371
  // Use absolute path as the key for MetaCache
358
- const cached = treatAsEmptyCache
359
- ? undefined
360
- : mc.get(absPath);
372
+ const cached = treatAsEmptyCache ? undefined : mc.get(absPath);
361
373
  if (cached &&
362
374
  cached.mtimeMs === stats.mtimeMs &&
363
375
  cached.size === stats.size) {
@@ -67,8 +67,17 @@ function processBatchCore(batch, metaCache, pool, signal) {
67
67
  }
68
68
  try {
69
69
  const stats = yield fs.promises.stat(absPath);
70
- if (!(0, file_utils_1.isIndexableFile)(absPath, stats.size))
70
+ if (!(0, file_utils_1.isIndexableFile)(absPath, stats.size)) {
71
+ // File became non-indexable (emptied or now too large). If we indexed
72
+ // it before, drop its chunks + meta so search stops returning stale
73
+ // content; otherwise there's nothing to clean up.
74
+ if (metaCache.get(absPath)) {
75
+ deletes.push(absPath);
76
+ metaDeletes.push(absPath);
77
+ reindexed++;
78
+ }
71
79
  continue;
80
+ }
72
81
  const cached = metaCache.get(absPath);
73
82
  if ((0, cache_check_1.isFileCached)(cached, stats)) {
74
83
  continue;
@@ -10,7 +10,7 @@ function envInt(key, fallback) {
10
10
  return Number.isFinite(n) && n > 0 ? n : fallback;
11
11
  }
12
12
  function getLlmConfig() {
13
- var _a, _b, _c;
13
+ var _a, _b, _c, _d;
14
14
  return {
15
15
  model: (_a = process.env.GMAX_LLM_MODEL) !== null && _a !== void 0 ? _a : DEFAULT_MODEL,
16
16
  binary: (_b = process.env.GMAX_LLM_BINARY) !== null && _b !== void 0 ? _b : "llama-server",
@@ -21,5 +21,6 @@ function getLlmConfig() {
21
21
  maxTokens: envInt("GMAX_LLM_MAX_TOKENS", 8192),
22
22
  idleTimeoutMin: envInt("GMAX_LLM_IDLE_TIMEOUT", 30),
23
23
  startupWaitSec: envInt("GMAX_LLM_STARTUP_WAIT", 60),
24
+ reasoningFormat: (_d = process.env.GMAX_LLM_REASONING_FORMAT) !== null && _d !== void 0 ? _d : "deepseek",
24
25
  };
25
26
  }