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
@@ -11,14 +11,59 @@ const node_child_process_1 = require("node:child_process");
11
11
  exports.DIFF_MAX_LINES = 500;
12
12
  exports.SYMBOL_MAX = 10;
13
13
  const KEYWORD_SKIP = new Set([
14
- "public", "private", "internal", "protected", "open", "final", "static",
15
- "override", "class", "struct", "enum", "func", "function", "def", "const",
16
- "let", "var", "export", "async", "await", "import", "return", "if", "else",
17
- "for", "while", "switch", "case", "guard", "interface", "abstract", "sealed",
18
- "data", "suspend", "inline", "typealias", "extension", "protocol", "throws",
19
- "mutating", "nonmutating", "convenience", "required", "weak", "unowned",
20
- "lazy", "dynamic", "optional", "objc", "nonisolated", "isolated",
21
- "consuming", "borrowing",
14
+ "public",
15
+ "private",
16
+ "internal",
17
+ "protected",
18
+ "open",
19
+ "final",
20
+ "static",
21
+ "override",
22
+ "class",
23
+ "struct",
24
+ "enum",
25
+ "func",
26
+ "function",
27
+ "def",
28
+ "const",
29
+ "let",
30
+ "var",
31
+ "export",
32
+ "async",
33
+ "await",
34
+ "import",
35
+ "return",
36
+ "if",
37
+ "else",
38
+ "for",
39
+ "while",
40
+ "switch",
41
+ "case",
42
+ "guard",
43
+ "interface",
44
+ "abstract",
45
+ "sealed",
46
+ "data",
47
+ "suspend",
48
+ "inline",
49
+ "typealias",
50
+ "extension",
51
+ "protocol",
52
+ "throws",
53
+ "mutating",
54
+ "nonmutating",
55
+ "convenience",
56
+ "required",
57
+ "weak",
58
+ "unowned",
59
+ "lazy",
60
+ "dynamic",
61
+ "optional",
62
+ "objc",
63
+ "nonisolated",
64
+ "isolated",
65
+ "consuming",
66
+ "borrowing",
22
67
  ]);
23
68
  const DECL_RE = /(?:function|class|struct|enum|interface|func|def)\s+([a-zA-Z_][a-zA-Z0-9_]*)/g;
24
69
  const IDENT_RE = /[a-zA-Z_][a-zA-Z0-9_]*/g;
@@ -122,6 +167,7 @@ function extractSymbols(diff) {
122
167
  const idents = [];
123
168
  let m;
124
169
  IDENT_RE.lastIndex = 0;
170
+ // biome-ignore lint/suspicious/noAssignInExpressions: idiomatic regex exec loop
125
171
  while ((m = IDENT_RE.exec(ctx)) !== null) {
126
172
  if (!KEYWORD_SKIP.has(m[0]))
127
173
  idents.push(m[0]);
@@ -134,15 +180,14 @@ function extractSymbols(diff) {
134
180
  if (line.startsWith("+") && !line.startsWith("+++")) {
135
181
  DECL_RE.lastIndex = 0;
136
182
  let m;
183
+ // biome-ignore lint/suspicious/noAssignInExpressions: idiomatic regex exec loop
137
184
  while ((m = DECL_RE.exec(line)) !== null) {
138
185
  symbols.add(m[1]);
139
186
  }
140
187
  }
141
188
  }
142
189
  // Filter short identifiers and cap
143
- return [...symbols]
144
- .filter((s) => s.length >= 2)
145
- .slice(0, exports.SYMBOL_MAX);
190
+ return [...symbols].filter((s) => s.length >= 2).slice(0, exports.SYMBOL_MAX);
146
191
  }
147
192
  /**
148
193
  * Detect languages from file extensions.
@@ -45,6 +45,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
45
45
  return (mod && mod.__esModule) ? mod : { "default": mod };
46
46
  };
47
47
  Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.looksLikeRawToolCall = looksLikeRawToolCall;
49
+ exports.toolCallLeakHint = toolCallLeakHint;
50
+ exports.finalizeAnswer = finalizeAnswer;
48
51
  exports.investigate = investigate;
49
52
  const path = __importStar(require("node:path"));
50
53
  const openai_1 = __importDefault(require("openai"));
@@ -56,9 +59,38 @@ const config_1 = require("./config");
56
59
  const prompts_1 = require("./prompts");
57
60
  const tools_1 = require("./tools");
58
61
  function stripThinkTags(text) {
59
- return text
60
- .replace(/<think(?:ing)?>[\s\S]*?<\/think(?:ing)?>/g, "")
61
- .trim();
62
+ return text.replace(/<think(?:ing)?>[\s\S]*?<\/think(?:ing)?>/g, "").trim();
63
+ }
64
+ /**
65
+ * Detect raw model tool-call markup leaking into `message.content`.
66
+ *
67
+ * Some local models (notably Qwen3.5-35B-A3B, which emits Qwen-XML tool calls)
68
+ * trip a known llama.cpp `peg-native` parser bug: the call is never extracted
69
+ * into structured `tool_calls` and instead leaks into `content` as raw markup
70
+ * like `<tool_call>…`, `<function=peek>…`, or `<parameter=…>`. Returning that
71
+ * verbatim as the "answer" is what this guards against.
72
+ */
73
+ function looksLikeRawToolCall(text) {
74
+ return /<tool_call\b|<\/?function[=\s>]|<parameter[=\s>]/i.test(text);
75
+ }
76
+ /** Guidance shown when a non-tool-calling model leaks raw tool-call markup. */
77
+ function toolCallLeakHint(modelName) {
78
+ return (`The investigate tool needs a model that emits structured tool calls, but ` +
79
+ `"${modelName}" returned raw tool-call markup instead. This is a known ` +
80
+ `llama.cpp limitation for Qwen-XML tool-calling models — switch GMAX_LLM_MODEL ` +
81
+ `to a Hermes-JSON tool-calling model (e.g. Qwen3-30B-A3B-Instruct-2507), or ` +
82
+ `use review_commit / the gmax CLI search tools for this question.`);
83
+ }
84
+ /**
85
+ * Turn a model message's content into the final answer: detect a tool-call leak
86
+ * (return a clear hint), otherwise strip reasoning tags.
87
+ */
88
+ function finalizeAnswer(content, modelName) {
89
+ if (content && looksLikeRawToolCall(content)) {
90
+ return toolCallLeakHint(modelName);
91
+ }
92
+ const stripped = content ? stripThinkTags(content) : "";
93
+ return stripped || "(no response)";
62
94
  }
63
95
  function investigate(opts) {
64
96
  return __awaiter(this, void 0, void 0, function* () {
@@ -83,7 +115,12 @@ function investigate(opts) {
83
115
  const vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
84
116
  const searcher = new searcher_1.Searcher(vectorDb);
85
117
  const graphBuilder = new graph_builder_1.GraphBuilder(vectorDb, projectRoot);
86
- const ctx = { vectorDb, searcher, graphBuilder, projectRoot };
118
+ const ctx = {
119
+ vectorDb,
120
+ searcher,
121
+ graphBuilder,
122
+ projectRoot,
123
+ };
87
124
  const messages = [
88
125
  { role: "system", content: prompts_1.SYSTEM_PROMPT },
89
126
  { role: "user", content: question },
@@ -113,9 +150,7 @@ function investigate(opts) {
113
150
  if (verbose) {
114
151
  process.stderr.write(`[R${round}] Final answer (${roundMs}ms)\n`);
115
152
  }
116
- const answer = message.content
117
- ? stripThinkTags(message.content)
118
- : "(no response)";
153
+ const answer = finalizeAnswer(message.content, modelName);
119
154
  return {
120
155
  answer,
121
156
  rounds,
@@ -160,7 +195,11 @@ function investigate(opts) {
160
195
  if (verbose) {
161
196
  process.stderr.write(` ${fn.name}() — BLOCKED (limit)\n`);
162
197
  }
163
- messages.push({ role: "tool", tool_call_id: tc.id, content: result });
198
+ messages.push({
199
+ role: "tool",
200
+ tool_call_id: tc.id,
201
+ content: result,
202
+ });
164
203
  continue;
165
204
  }
166
205
  }
@@ -187,6 +226,10 @@ function investigate(opts) {
187
226
  process.stderr.write(`[R${maxRounds}] Round cap reached, forcing final answer\n`);
188
227
  }
189
228
  messages.push({ role: "user", content: prompts_1.FORCE_FINAL_MESSAGE });
229
+ // No tools on the synthesis call. Note: tool_choice:"none" does NOT stop this
230
+ // model from emitting a stray tool call here (verified 2026-06-28 — it still
231
+ // leaks raw markup into content), so the finalizeAnswer guard is what protects
232
+ // this path rather than a request-shape tweak.
190
233
  const response = yield client.chat.completions.create({
191
234
  model: modelName,
192
235
  messages,
@@ -195,9 +238,7 @@ function investigate(opts) {
195
238
  if (!((_c = response.choices) === null || _c === void 0 ? void 0 : _c.length)) {
196
239
  throw new Error("LLM returned empty response");
197
240
  }
198
- const answer = response.choices[0].message.content
199
- ? stripThinkTags(response.choices[0].message.content)
200
- : "(no response)";
241
+ const answer = finalizeAnswer(response.choices[0].message.content, modelName);
201
242
  return {
202
243
  answer,
203
244
  rounds: rounds + 1,
@@ -49,6 +49,7 @@ exports.reviewCommit = reviewCommit;
49
49
  const path = __importStar(require("node:path"));
50
50
  const openai_1 = __importDefault(require("openai"));
51
51
  const graph_builder_1 = require("../graph/graph-builder");
52
+ const risk_1 = require("../review/risk");
52
53
  const searcher_1 = require("../search/searcher");
53
54
  const vector_db_1 = require("../store/vector-db");
54
55
  const project_root_1 = require("../utils/project-root");
@@ -56,7 +57,6 @@ const config_1 = require("./config");
56
57
  const diff_1 = require("./diff");
57
58
  const report_1 = require("./report");
58
59
  const tools_1 = require("./tools");
59
- const risk_1 = require("../review/risk");
60
60
  function reviewCommit(opts) {
61
61
  return __awaiter(this, void 0, void 0, function* () {
62
62
  var _a, _b, _c, _d;
@@ -88,13 +88,21 @@ function reviewCommit(opts) {
88
88
  try {
89
89
  const searcher = new searcher_1.Searcher(vectorDb);
90
90
  const graphBuilder = new graph_builder_1.GraphBuilder(vectorDb, projectRoot);
91
- const ctx = { vectorDb, searcher, graphBuilder, projectRoot };
91
+ const ctx = {
92
+ vectorDb,
93
+ searcher,
94
+ graphBuilder,
95
+ projectRoot,
96
+ };
92
97
  // Deterministic risk ranking (Phase 8) — gives the LLM an explicit
93
98
  // blast-radius × tests × churn ordering to anchor its judgement, rather
94
99
  // than inferring importance from prose alone.
95
100
  const [context, riskInputs] = yield Promise.all([
96
101
  gatherContext(symbols, changedFiles, ctx, verbose),
97
- (0, risk_1.gatherRiskInputs)(commitRef, projectRoot, { vectorDb, graphBuilder }).catch(() => []),
102
+ (0, risk_1.gatherRiskInputs)(commitRef, projectRoot, {
103
+ vectorDb,
104
+ graphBuilder,
105
+ }).catch(() => []),
98
106
  ]);
99
107
  contextStr = context;
100
108
  const riskRows = (0, risk_1.computeRiskTable)(riskInputs);
@@ -202,7 +210,11 @@ function gatherContext(symbols, changedFiles, ctx, verbose) {
202
210
  if (s.status !== "fulfilled")
203
211
  continue;
204
212
  const { type, result } = s.value;
205
- if (result.startsWith("(") && (result.includes("not found") || result.includes("error") || result.includes("not indexed") || result.includes("none")))
213
+ if (result.startsWith("(") &&
214
+ (result.includes("not found") ||
215
+ result.includes("error") ||
216
+ result.includes("not indexed") ||
217
+ result.includes("none")))
206
218
  continue;
207
219
  if (type === "peek")
208
220
  peekResults.push(result);
@@ -356,12 +368,13 @@ ${diff}
356
368
  // Response parsing
357
369
  // ---------------------------------------------------------------------------
358
370
  function stripThinkTags(text) {
359
- return text
360
- .replace(/<think(?:ing)?>[\s\S]*?<\/think(?:ing)?>/g, "")
361
- .trim();
371
+ return text.replace(/<think(?:ing)?>[\s\S]*?<\/think(?:ing)?>/g, "").trim();
362
372
  }
363
373
  function parseFindings(content) {
364
- const empty = { findings: [], summary: "Parse error — could not extract findings from model output." };
374
+ const empty = {
375
+ findings: [],
376
+ summary: "Parse error — could not extract findings from model output.",
377
+ };
365
378
  if (!content)
366
379
  return empty;
367
380
  // Strip think tags and markdown fences
@@ -85,7 +85,9 @@ class LlmServer {
85
85
  const runningModel = (_b = (_a = body === null || body === void 0 ? void 0 : body.data) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.id;
86
86
  if (runningModel) {
87
87
  const configBasename = path.basename(this.config.model);
88
- if (runningModel !== configBasename && !configBasename.includes(runningModel) && !runningModel.includes(configBasename)) {
88
+ if (runningModel !== configBasename &&
89
+ !configBasename.includes(runningModel) &&
90
+ !runningModel.includes(configBasename)) {
89
91
  console.log(`[llm] Model mismatch: running "${runningModel}" but config expects "${configBasename}" — will restart`);
90
92
  resolve(false);
91
93
  return;
@@ -141,13 +143,25 @@ class LlmServer {
141
143
  throw new Error(`Model file not found: "${this.config.model}". Set GMAX_LLM_MODEL to a valid .gguf path`);
142
144
  }
143
145
  const logFd = (0, log_rotate_1.openRotatedLog)(config_1.PATHS.llmLogFile);
144
- const child = (0, node_child_process_1.spawn)(binary, [
145
- "-m", this.config.model,
146
- "--host", this.config.host,
147
- "--port", String(this.config.port),
148
- "-ngl", String(this.config.ngl),
149
- "--ctx-size", String(this.config.ctxSize),
150
- ], { detached: true, stdio: ["ignore", logFd, logFd] });
146
+ const args = [
147
+ "-m",
148
+ this.config.model,
149
+ "--host",
150
+ this.config.host,
151
+ "--port",
152
+ String(this.config.port),
153
+ "-ngl",
154
+ String(this.config.ngl),
155
+ "--ctx-size",
156
+ String(this.config.ctxSize),
157
+ ];
158
+ if (this.config.reasoningFormat) {
159
+ args.push("--reasoning-format", this.config.reasoningFormat);
160
+ }
161
+ const child = (0, node_child_process_1.spawn)(binary, args, {
162
+ detached: true,
163
+ stdio: ["ignore", logFd, logFd],
164
+ });
151
165
  child.unref();
152
166
  fs.closeSync(logFd);
153
167
  const pid = child.pid;
@@ -256,7 +270,9 @@ class LlmServer {
256
270
  pid: alive ? pid : null,
257
271
  port: this.config.port,
258
272
  model: this.config.model,
259
- uptime: alive && this.startTime ? Math.floor((Date.now() - this.startTime) / 1000) : 0,
273
+ uptime: alive && this.startTime
274
+ ? Math.floor((Date.now() - this.startTime) / 1000)
275
+ : 0,
260
276
  };
261
277
  }
262
278
  startIdleWatchdog() {
@@ -244,7 +244,7 @@ function executePeek(args, ctx) {
244
244
  const metaRows = yield table
245
245
  .query()
246
246
  .select(["is_exported", "start_line", "end_line"])
247
- .where(`array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(symbol)}') AND path LIKE '${(0, filter_builder_1.escapeSqlString)(prefix)}%'`)
247
+ .where(`array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(symbol)}') AND ${(0, filter_builder_1.pathStartsWith)(prefix)}`)
248
248
  .limit(1)
249
249
  .toArray();
250
250
  const exported = metaRows.length > 0 && Boolean(metaRows[0].is_exported);
@@ -102,9 +102,31 @@ function firstSignatureLine(chunk) {
102
102
  return "";
103
103
  }
104
104
  const QUERY_STOPWORDS = new Set([
105
- "the", "and", "for", "with", "that", "this", "from", "into", "where",
106
- "when", "how", "what", "does", "not", "are", "was", "has", "have", "its",
107
- "all", "can", "use", "uses", "using", "code",
105
+ "the",
106
+ "and",
107
+ "for",
108
+ "with",
109
+ "that",
110
+ "this",
111
+ "from",
112
+ "into",
113
+ "where",
114
+ "when",
115
+ "how",
116
+ "what",
117
+ "does",
118
+ "not",
119
+ "are",
120
+ "was",
121
+ "has",
122
+ "have",
123
+ "its",
124
+ "all",
125
+ "can",
126
+ "use",
127
+ "uses",
128
+ "using",
129
+ "code",
108
130
  ]);
109
131
  function extractQueryTerms(query) {
110
132
  if (!query)
@@ -14,7 +14,7 @@ exports.formatIndexStateFooter = formatIndexStateFooter;
14
14
  * while results may actually be incomplete.
15
15
  */
16
16
  function formatIndexStateFooter(state, opts) {
17
- if (!state || !state.indexing)
17
+ if (!(state === null || state === void 0 ? void 0 : state.indexing))
18
18
  return null;
19
19
  const count = state.pendingFiles > 0 ? `~${state.pendingFiles} files pending` : null;
20
20
  if (opts.agent) {
@@ -40,9 +40,7 @@ function computeRiskTable(inputs) {
40
40
  /** Render the ranking — TSV-ish for agents, an aligned table for humans. */
41
41
  function formatRiskTable(rows, opts) {
42
42
  if (rows.length === 0) {
43
- return opts.agent
44
- ? "(no changed symbols)"
45
- : "No changed symbols to rank.";
43
+ return opts.agent ? "(no changed symbols)" : "No changed symbols to rank.";
46
44
  }
47
45
  if (opts.agent) {
48
46
  return rows
@@ -70,7 +68,7 @@ function gatherRiskInputs(ref, projectRoot, deps) {
70
68
  if (symbols.length === 0)
71
69
  return [];
72
70
  const root = projectRoot.endsWith("/") ? projectRoot : `${projectRoot}/`;
73
- const relativize = (f) => (f.startsWith(root) ? f.slice(root.length) : f);
71
+ const relativize = (f) => f.startsWith(root) ? f.slice(root.length) : f;
74
72
  const inputs = yield Promise.all(symbols.map((symbol) => __awaiter(this, void 0, void 0, function* () {
75
73
  var _a, _b;
76
74
  const [callers, loc, tests] = yield Promise.all([
@@ -147,7 +147,7 @@ function buildGraphFromDb(db, pathPrefix) {
147
147
  const rows = yield table
148
148
  .query()
149
149
  .select(["defined_symbols", "referenced_symbols"])
150
- .where(`path LIKE '${(0, filter_builder_1.escapeSqlString)(prefix)}%'`)
150
+ .where((0, filter_builder_1.pathStartsWith)(prefix))
151
151
  .toArray();
152
152
  const nodes = new Set();
153
153
  const edges = new Map();
@@ -29,7 +29,9 @@ function readSymbolArray(val) {
29
29
  if (typeof maybe.toArray === "function") {
30
30
  try {
31
31
  const a = maybe.toArray();
32
- return Array.isArray(a) ? a.filter((v) => typeof v === "string") : [];
32
+ return Array.isArray(a)
33
+ ? a.filter((v) => typeof v === "string")
34
+ : [];
33
35
  }
34
36
  catch (_a) {
35
37
  return [];
@@ -49,7 +51,7 @@ function buildWhereClause(pathPrefix, filters, searchIntent) {
49
51
  var _a;
50
52
  const parts = [];
51
53
  if (pathPrefix) {
52
- parts.push(`path LIKE '${(0, filter_builder_1.escapeSqlString)((0, filter_builder_1.normalizePath)(pathPrefix))}%'`);
54
+ parts.push((0, filter_builder_1.pathStartsWith)((0, filter_builder_1.normalizePath)(pathPrefix)));
53
55
  }
54
56
  const fileFilter = filters === null || filters === void 0 ? void 0 : filters.file;
55
57
  if (typeof fileFilter === "string" && fileFilter) {
@@ -60,7 +62,7 @@ function buildWhereClause(pathPrefix, filters, searchIntent) {
60
62
  const absExclude = pathPrefix
61
63
  ? (0, filter_builder_1.normalizePath)(pathPrefix + excludeFilter)
62
64
  : excludeFilter;
63
- parts.push(`path NOT LIKE '${(0, filter_builder_1.escapeSqlString)(absExclude)}%'`);
65
+ parts.push((0, filter_builder_1.pathNotStartsWith)(absExclude));
64
66
  }
65
67
  // New array-shape: pre-resolved absolute exclude prefixes from
66
68
  // resolveScope (or daemon IPC). Each becomes its own NOT LIKE clause.
@@ -69,7 +71,7 @@ function buildWhereClause(pathPrefix, filters, searchIntent) {
69
71
  for (const p of excludePrefixes) {
70
72
  if (typeof p === "string" && p) {
71
73
  const norm = (0, filter_builder_1.normalizePath)(p);
72
- parts.push(`path NOT LIKE '${(0, filter_builder_1.escapeSqlString)(norm)}%'`);
74
+ parts.push((0, filter_builder_1.pathNotStartsWith)(norm));
73
75
  }
74
76
  }
75
77
  }
@@ -82,7 +84,7 @@ function buildWhereClause(pathPrefix, filters, searchIntent) {
82
84
  for (const p of inPrefixes) {
83
85
  if (typeof p === "string" && p) {
84
86
  const norm = (0, filter_builder_1.normalizePath)(p);
85
- clauses.push(`path LIKE '${(0, filter_builder_1.escapeSqlString)(norm)}%'`);
87
+ clauses.push((0, filter_builder_1.pathStartsWith)(norm));
86
88
  }
87
89
  }
88
90
  if (clauses.length === 1)
@@ -104,7 +106,7 @@ function buildWhereClause(pathPrefix, filters, searchIntent) {
104
106
  const roots = projectRoots.split(",");
105
107
  const clauses = roots.map((r) => {
106
108
  const prefix = r.endsWith("/") ? r : `${r}/`;
107
- return `path LIKE '${(0, filter_builder_1.escapeSqlString)(prefix)}%'`;
109
+ return (0, filter_builder_1.pathStartsWith)(prefix);
108
110
  });
109
111
  parts.push(`(${clauses.join(" OR ")})`);
110
112
  }
@@ -112,7 +114,7 @@ function buildWhereClause(pathPrefix, filters, searchIntent) {
112
114
  if (typeof excludeRoots === "string" && excludeRoots) {
113
115
  for (const r of excludeRoots.split(",")) {
114
116
  const prefix = r.endsWith("/") ? r : `${r}/`;
115
- parts.push(`path NOT LIKE '${(0, filter_builder_1.escapeSqlString)(prefix)}%'`);
117
+ parts.push((0, filter_builder_1.pathNotStartsWith)(prefix));
116
118
  }
117
119
  }
118
120
  const defFilter = filters === null || filters === void 0 ? void 0 : filters.def;
@@ -441,9 +443,21 @@ class Searcher {
441
443
  // loaded), so pull it into the lightweight path when symbols were seeded.
442
444
  const needDefinedSymbols = pagerankEnabled || symbolQuery !== null || seedCtx.symbols.size > 0;
443
445
  const LIGHTWEIGHT_COLUMNS = [
444
- "id", "path", "hash", "chunk_index", "start_line", "end_line",
445
- "is_anchor", "chunk_type", "role", "complexity", "is_exported",
446
- "content", "parent_symbol", "referenced_symbols", "pooled_colbert_48d",
446
+ "id",
447
+ "path",
448
+ "hash",
449
+ "chunk_index",
450
+ "start_line",
451
+ "end_line",
452
+ "is_anchor",
453
+ "chunk_type",
454
+ "role",
455
+ "complexity",
456
+ "is_exported",
457
+ "content",
458
+ "parent_symbol",
459
+ "referenced_symbols",
460
+ "pooled_colbert_48d",
447
461
  ...(needDefinedSymbols ? ["defined_symbols"] : []),
448
462
  ];
449
463
  // _distance is auto-added by vectorSearch, _score by FTS — include each
@@ -457,7 +471,7 @@ class Searcher {
457
471
  if (whereClause) {
458
472
  vectorQuery = vectorQuery.where(whereClause);
459
473
  }
460
- let vectorResults = (yield vectorQuery.toArray()).map((r) => (Object.assign({}, r)));
474
+ const vectorResults = (yield vectorQuery.toArray()).map((r) => (Object.assign({}, r)));
461
475
  let ftsResults = [];
462
476
  let ftsSearchFailed = false;
463
477
  if (this.ftsAvailable) {
@@ -566,7 +580,7 @@ class Searcher {
566
580
  var _a, _b;
567
581
  const ka = a.id || `${a.path}:${a.chunk_index}`;
568
582
  const kb = b.id || `${b.path}:${b.chunk_index}`;
569
- return ((_a = candidateScores.get(kb)) !== null && _a !== void 0 ? _a : 0) - ((_b = candidateScores.get(ka)) !== null && _b !== void 0 ? _b : 0);
583
+ return (((_a = candidateScores.get(kb)) !== null && _a !== void 0 ? _a : 0) - ((_b = candidateScores.get(ka)) !== null && _b !== void 0 ? _b : 0));
570
584
  });
571
585
  }
572
586
  }
@@ -751,7 +765,12 @@ class Searcher {
751
765
  record: doc,
752
766
  score: boosted,
753
767
  breakdown: explain
754
- ? { rerank: base, fused: fusedScore, boost: blended > 0 ? boosted / blended : 1, normalized: 0 }
768
+ ? {
769
+ rerank: base,
770
+ fused: fusedScore,
771
+ boost: blended > 0 ? boosted / blended : 1,
772
+ normalized: 0,
773
+ }
755
774
  : undefined,
756
775
  };
757
776
  });
@@ -766,12 +785,14 @@ class Searcher {
766
785
  const envWeight = Number.parseFloat((_o = process.env.GMAX_PR_WEIGHT) !== null && _o !== void 0 ? _o : "");
767
786
  const PR_WEIGHT = Number.isFinite(envWeight) && envWeight >= 0 ? envWeight : 0.05;
768
787
  for (const item of scored) {
769
- const raw = item.record.defined_symbols;
788
+ const raw = item.record
789
+ .defined_symbols;
770
790
  let defs = [];
771
791
  if (Array.isArray(raw)) {
772
792
  defs = raw.filter((v) => typeof v === "string");
773
793
  }
774
- else if (raw && typeof raw.toArray === "function") {
794
+ else if (raw &&
795
+ typeof raw.toArray === "function") {
775
796
  try {
776
797
  const arr = raw.toArray();
777
798
  if (Array.isArray(arr)) {
@@ -817,8 +838,13 @@ class Searcher {
817
838
  const displayRows = yield table
818
839
  .query()
819
840
  .select([
820
- "id", "defined_symbols", "parent_symbol", "imports", "exports",
821
- "summary", "file_skeleton",
841
+ "id",
842
+ "defined_symbols",
843
+ "parent_symbol",
844
+ "imports",
845
+ "exports",
846
+ "summary",
847
+ "file_skeleton",
822
848
  ])
823
849
  .where(`id IN (${finalIds.join(",")})`)
824
850
  .limit(finalIds.length)
@@ -64,7 +64,10 @@ function seedParamsFromEnv(env = process.env) {
64
64
  function buildSeedContext(spec) {
65
65
  var _a, _b;
66
66
  const fileSuffixes = ((_a = spec === null || spec === void 0 ? void 0 : spec.files) !== null && _a !== void 0 ? _a : [])
67
- .map((f) => f.trim().toLowerCase().replace(/^\.?\//, ""))
67
+ .map((f) => f
68
+ .trim()
69
+ .toLowerCase()
70
+ .replace(/^\.?\//, ""))
68
71
  .filter((f) => f.length > 0);
69
72
  const symbols = new Set(((_b = spec === null || spec === void 0 ? void 0 : spec.symbols) !== null && _b !== void 0 ? _b : []).map((s) => s.trim()).filter((s) => s.length > 0));
70
73
  return {
@@ -13,7 +13,8 @@ function extractSymbolsFromSkeleton(annotatedSkeleton) {
13
13
  const line = Number.parseInt(m[1], 10);
14
14
  const sig = m[2].trim();
15
15
  const exported = sig.includes("export ");
16
- const type = ((_a = sig.match(/\b(class|interface|type|function|def|fn|func)\b/)) === null || _a === void 0 ? void 0 : _a[1]) || "other";
16
+ const type = ((_a = sig.match(/\b(class|interface|type|function|def|fn|func)\b/)) === null || _a === void 0 ? void 0 : _a[1]) ||
17
+ "other";
17
18
  const name = ((_b = sig.match(/(?:function|class|interface|type|def|fn|func)\s+(\w+)/)) === null || _b === void 0 ? void 0 : _b[1]) ||
18
19
  ((_c = sig.match(/^(?:async\s+)?(\w+)\s*[(<]/)) === null || _c === void 0 ? void 0 : _c[1]) ||
19
20
  "unknown";