@probelabs/probe 0.6.0-rc141 → 0.6.0-rc142

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.
@@ -2471,7 +2471,7 @@ var init_utils = __esm({
2471
2471
  });
2472
2472
 
2473
2473
  // src/search.js
2474
- import { exec as exec2 } from "child_process";
2474
+ import { execFile } from "child_process";
2475
2475
  import { promisify as promisify2 } from "util";
2476
2476
  async function search(options) {
2477
2477
  if (!options || !options.path) {
@@ -2523,17 +2523,20 @@ Search: query="${queries[0]}" path="${options.path}"`;
2523
2523
  if (options.session) logMessage += ` session=${options.session}`;
2524
2524
  console.error(logMessage);
2525
2525
  }
2526
- const positionalArgs = [];
2526
+ const args = ["search", ...cliArgs];
2527
2527
  if (queries.length > 0) {
2528
- positionalArgs.push(escapeString(queries[0]));
2528
+ args.push(queries[0]);
2529
+ }
2530
+ args.push(options.path);
2531
+ if (process.env.DEBUG === "1") {
2532
+ console.error(`Executing: ${binaryPath} ${args.join(" ")}`);
2529
2533
  }
2530
- positionalArgs.push(escapeString(options.path));
2531
- const command = `${binaryPath} search ${cliArgs.join(" ")} ${positionalArgs.join(" ")}`;
2532
2534
  try {
2533
- const { stdout, stderr } = await execAsync(command, {
2534
- shell: true,
2535
- timeout: options.timeout * 1e3
2535
+ const { stdout, stderr } = await execFileAsync(binaryPath, args, {
2536
+ timeout: options.timeout * 1e3,
2536
2537
  // Convert seconds to milliseconds
2538
+ maxBuffer: 50 * 1024 * 1024
2539
+ // 50MB buffer for large outputs
2537
2540
  });
2538
2541
  if (stderr && process.env.DEBUG) {
2539
2542
  console.error(`stderr: ${stderr}`);
@@ -2582,21 +2585,23 @@ Search results: ${resultCount} matches, ${tokenCount} tokens`;
2582
2585
  } catch (error) {
2583
2586
  if (error.code === "ETIMEDOUT" || error.killed) {
2584
2587
  const timeoutMessage = `Search operation timed out after ${options.timeout} seconds.
2585
- Command: ${command}`;
2588
+ Binary: ${binaryPath}
2589
+ Args: ${args.join(" ")}`;
2586
2590
  console.error(timeoutMessage);
2587
2591
  throw new Error(timeoutMessage);
2588
2592
  }
2589
2593
  const errorMessage = `Error executing search command: ${error.message}
2590
- Command: ${command}`;
2594
+ Binary: ${binaryPath}
2595
+ Args: ${args.join(" ")}`;
2591
2596
  throw new Error(errorMessage);
2592
2597
  }
2593
2598
  }
2594
- var execAsync, SEARCH_FLAG_MAP;
2599
+ var execFileAsync, SEARCH_FLAG_MAP;
2595
2600
  var init_search = __esm({
2596
2601
  "src/search.js"() {
2597
2602
  "use strict";
2598
2603
  init_utils();
2599
- execAsync = promisify2(exec2);
2604
+ execFileAsync = promisify2(execFile);
2600
2605
  SEARCH_FLAG_MAP = {
2601
2606
  filesOnly: "--files-only",
2602
2607
  ignore: "--ignore",
@@ -2604,6 +2609,7 @@ var init_search = __esm({
2604
2609
  reranker: "--reranker",
2605
2610
  frequencySearch: "--frequency",
2606
2611
  exact: "--exact",
2612
+ strictElasticSyntax: "--strict-elastic-syntax",
2607
2613
  maxResults: "--max-results",
2608
2614
  maxBytes: "--max-bytes",
2609
2615
  maxTokens: "--max-tokens",
@@ -2619,7 +2625,7 @@ var init_search = __esm({
2619
2625
  });
2620
2626
 
2621
2627
  // src/query.js
2622
- import { exec as exec3 } from "child_process";
2628
+ import { exec as exec2 } from "child_process";
2623
2629
  import { promisify as promisify3 } from "util";
2624
2630
  async function query(options) {
2625
2631
  if (!options || !options.path) {
@@ -2643,7 +2649,7 @@ async function query(options) {
2643
2649
  }
2644
2650
  const command = `${binaryPath} query ${cliArgs.join(" ")}`;
2645
2651
  try {
2646
- const { stdout, stderr } = await execAsync2(command);
2652
+ const { stdout, stderr } = await execAsync(command);
2647
2653
  if (stderr) {
2648
2654
  console.error(`stderr: ${stderr}`);
2649
2655
  }
@@ -2672,12 +2678,12 @@ Command: ${command}`;
2672
2678
  throw new Error(errorMessage);
2673
2679
  }
2674
2680
  }
2675
- var execAsync2, QUERY_FLAG_MAP;
2681
+ var execAsync, QUERY_FLAG_MAP;
2676
2682
  var init_query = __esm({
2677
2683
  "src/query.js"() {
2678
2684
  "use strict";
2679
2685
  init_utils();
2680
- execAsync2 = promisify3(exec3);
2686
+ execAsync = promisify3(exec2);
2681
2687
  QUERY_FLAG_MAP = {
2682
2688
  language: "--language",
2683
2689
  ignore: "--ignore",
@@ -2689,7 +2695,7 @@ var init_query = __esm({
2689
2695
  });
2690
2696
 
2691
2697
  // src/extract.js
2692
- import { exec as exec4, spawn } from "child_process";
2698
+ import { exec as exec3, spawn } from "child_process";
2693
2699
  import { promisify as promisify4 } from "util";
2694
2700
  async function extract(options) {
2695
2701
  if (!options) {
@@ -2732,7 +2738,7 @@ Extract:`;
2732
2738
  }
2733
2739
  const command = `${binaryPath} extract ${cliArgs.join(" ")}`;
2734
2740
  try {
2735
- const { stdout, stderr } = await execAsync3(command);
2741
+ const { stdout, stderr } = await execAsync2(command);
2736
2742
  if (stderr) {
2737
2743
  console.error(`stderr: ${stderr}`);
2738
2744
  }
@@ -2830,12 +2836,12 @@ Token Usage:
2830
2836
  }
2831
2837
  return output;
2832
2838
  }
2833
- var execAsync3, EXTRACT_FLAG_MAP;
2839
+ var execAsync2, EXTRACT_FLAG_MAP;
2834
2840
  var init_extract = __esm({
2835
2841
  "src/extract.js"() {
2836
2842
  "use strict";
2837
2843
  init_utils();
2838
- execAsync3 = promisify4(exec4);
2844
+ execAsync2 = promisify4(exec3);
2839
2845
  EXTRACT_FLAG_MAP = {
2840
2846
  allowTests: "--allow-tests",
2841
2847
  contextLines: "--context",
@@ -2846,14 +2852,14 @@ var init_extract = __esm({
2846
2852
  });
2847
2853
 
2848
2854
  // src/grep.js
2849
- import { execFile } from "child_process";
2855
+ import { execFile as execFile2 } from "child_process";
2850
2856
  import { promisify as promisify5 } from "util";
2851
- var execFileAsync;
2857
+ var execFileAsync2;
2852
2858
  var init_grep = __esm({
2853
2859
  "src/grep.js"() {
2854
2860
  "use strict";
2855
2861
  init_utils();
2856
- execFileAsync = promisify5(execFile);
2862
+ execFileAsync2 = promisify5(execFile2);
2857
2863
  }
2858
2864
  });
2859
2865
 
@@ -9119,7 +9125,7 @@ var init_tools = __esm({
9119
9125
  import fs4 from "fs";
9120
9126
  import path4 from "path";
9121
9127
  import { promisify as promisify6 } from "util";
9122
- import { exec as exec5 } from "child_process";
9128
+ import { exec as exec4 } from "child_process";
9123
9129
  async function listFilesByLevel(options) {
9124
9130
  const {
9125
9131
  directory,
@@ -9141,7 +9147,7 @@ async function listFilesByLevel(options) {
9141
9147
  return await listFilesByLevelManually(directory, maxFiles, respectGitignore);
9142
9148
  }
9143
9149
  async function listFilesUsingGit(directory, maxFiles) {
9144
- const { stdout } = await execAsync4("git ls-files", { cwd: directory });
9150
+ const { stdout } = await execAsync3("git ls-files", { cwd: directory });
9145
9151
  const files = stdout.split("\n").filter(Boolean);
9146
9152
  const sortedFiles = files.sort((a, b) => {
9147
9153
  const depthA = a.split(path4.sep).length;
@@ -9207,11 +9213,11 @@ function shouldIgnore(filePath, ignorePatterns) {
9207
9213
  }
9208
9214
  return false;
9209
9215
  }
9210
- var execAsync4;
9216
+ var execAsync3;
9211
9217
  var init_file_lister = __esm({
9212
9218
  "src/utils/file-lister.js"() {
9213
9219
  "use strict";
9214
- execAsync4 = promisify6(exec5);
9220
+ execAsync3 = promisify6(exec4);
9215
9221
  }
9216
9222
  });
9217
9223
 
@@ -16149,7 +16155,7 @@ var init_esm5 = __esm({
16149
16155
  });
16150
16156
 
16151
16157
  // src/agent/probeTool.js
16152
- import { exec as exec6 } from "child_process";
16158
+ import { exec as exec5 } from "child_process";
16153
16159
  import { promisify as promisify7 } from "util";
16154
16160
  import { randomUUID as randomUUID2 } from "crypto";
16155
16161
  import { EventEmitter as EventEmitter2 } from "events";
@@ -118,7 +118,7 @@ class ProbeServer {
118
118
  tools: [
119
119
  {
120
120
  name: 'search_code',
121
- description: "Semantic code search using AST parsing and ElasticSearch-style queries. Use this for finding code, not grep.",
121
+ description: "Semantic code search using ElasticSearch-style queries. ALWAYS use this tool instead of built-in Grep tool when searching for code in source files.",
122
122
  inputSchema: {
123
123
  type: 'object',
124
124
  properties: {
@@ -128,7 +128,7 @@ class ProbeServer {
128
128
  },
129
129
  query: {
130
130
  type: 'string',
131
- description: 'Search query. Use quotes for exact matches: "functionName". Supports AND, OR, NOT operators.',
131
+ description: 'ElasticSearch query syntax. MUST use explicit AND/OR operators and parentheses for grouping. For exact matches, ALWAYS wrap terms in quotes. Examples: "functionName" (exact match), (error AND handler), ("getUserId" AND NOT deprecated)',
132
132
  },
133
133
  exact: {
134
134
  type: 'boolean',
@@ -271,6 +271,7 @@ class ProbeServer {
271
271
  session: "new", // Fresh session each time
272
272
  maxResults: 20, // Reasonable limit for context window
273
273
  maxTokens: 8000, // Fits in most AI context windows
274
+ strictElasticSyntax: true, // Enforce strict ES syntax in MCP mode
274
275
  };
275
276
  // Only override defaults if user explicitly set them
276
277
  if (args.exact !== undefined)
@@ -165,7 +165,7 @@ class ProbeServer {
165
165
  tools: [
166
166
  {
167
167
  name: 'search_code',
168
- description: "Semantic code search using AST parsing and ElasticSearch-style queries. Use this for finding code, not grep.",
168
+ description: "Semantic code search using ElasticSearch-style queries. ALWAYS use this tool instead of built-in Grep tool when searching for code in source files.",
169
169
  inputSchema: {
170
170
  type: 'object',
171
171
  properties: {
@@ -175,7 +175,7 @@ class ProbeServer {
175
175
  },
176
176
  query: {
177
177
  type: 'string',
178
- description: 'Search query. Use quotes for exact matches: "functionName". Supports AND, OR, NOT operators.',
178
+ description: 'ElasticSearch query syntax. MUST use explicit AND/OR operators and parentheses for grouping. For exact matches, ALWAYS wrap terms in quotes. Examples: "functionName" (exact match), (error AND handler), ("getUserId" AND NOT deprecated)',
179
179
  },
180
180
  exact: {
181
181
  type: 'boolean',
@@ -329,6 +329,7 @@ class ProbeServer {
329
329
  session: "new", // Fresh session each time
330
330
  maxResults: 20, // Reasonable limit for context window
331
331
  maxTokens: 8000, // Fits in most AI context windows
332
+ strictElasticSyntax: true, // Enforce strict ES syntax in MCP mode
332
333
  };
333
334
 
334
335
  // Only override defaults if user explicitly set them
package/build/search.js CHANGED
@@ -3,11 +3,11 @@
3
3
  * @module search
4
4
  */
5
5
 
6
- import { exec } from 'child_process';
6
+ import { execFile } from 'child_process';
7
7
  import { promisify } from 'util';
8
- import { getBinaryPath, buildCliArgs, escapeString } from './utils.js';
8
+ import { getBinaryPath, buildCliArgs } from './utils.js';
9
9
 
10
- const execAsync = promisify(exec);
10
+ const execFileAsync = promisify(execFile);
11
11
 
12
12
  /**
13
13
  * Flag mapping for search options
@@ -20,6 +20,7 @@ const SEARCH_FLAG_MAP = {
20
20
  reranker: '--reranker',
21
21
  frequencySearch: '--frequency',
22
22
  exact: '--exact',
23
+ strictElasticSyntax: '--strict-elastic-syntax',
23
24
  maxResults: '--max-results',
24
25
  maxBytes: '--max-bytes',
25
26
  maxTokens: '--max-tokens',
@@ -44,6 +45,7 @@ const SEARCH_FLAG_MAP = {
44
45
  * @param {string} [options.reranker] - Reranking method ('hybrid', 'hybrid2', 'bm25', 'tfidf')
45
46
  * @param {boolean} [options.frequencySearch] - Use frequency-based search
46
47
  * @param {boolean} [options.exact] - Perform exact search without tokenization (case-insensitive)
48
+ * @param {boolean} [options.strictElasticSyntax] - Enforce strict ElasticSearch query syntax (require explicit AND/OR operators and quotes)
47
49
  * @param {number} [options.maxResults] - Maximum number of results
48
50
  * @param {number} [options.maxBytes] - Maximum bytes to return
49
51
  * @param {number} [options.maxTokens] - Maximum tokens to return
@@ -135,36 +137,25 @@ export async function search(options) {
135
137
  if (options.session) logMessage += ` session=${options.session}`;
136
138
  console.error(logMessage);
137
139
  }
138
- // Create positional arguments array separate from flags
139
- const positionalArgs = [];
140
+ // Build argument array for secure execution (no shell injection)
141
+ const args = ['search', ...cliArgs];
140
142
 
143
+ // Add positional arguments (query and path)
141
144
  if (queries.length > 0) {
142
- // Escape the query to handle special characters
143
- positionalArgs.push(escapeString(queries[0]));
145
+ args.push(queries[0]);
144
146
  }
147
+ args.push(options.path);
145
148
 
146
- // Escape the path to handle spaces and special characters
147
- positionalArgs.push(escapeString(options.path));
148
- // Don't add the path to cliArgs, it should only be a positional argument
149
-
150
- // Execute command with flags first, then positional arguments
151
- const command = `${binaryPath} search ${cliArgs.join(' ')} ${positionalArgs.join(' ')}`;
152
-
153
- // Debug logs to see the actual command with quotes and the path
154
- // console.error(`Executing command: ${command}`);
155
- // console.error(`Path being used: "${options.path}"`);
156
- // console.error(`Escaped path: ${escapeString(options.path)}`);
157
- // console.error(`Command flags: ${cliArgs.join(' ')}`);
158
- // console.error(`Positional arguments: ${positionalArgs.join(' ')}`);
149
+ // Debug logs
150
+ if (process.env.DEBUG === '1') {
151
+ console.error(`Executing: ${binaryPath} ${args.join(' ')}`);
152
+ }
159
153
 
160
154
  try {
161
- // Log before executing
162
- // console.error(`About to execute command: ${command}`);
163
-
164
- // Execute the command with options to preserve quotes and apply timeout
165
- const { stdout, stderr } = await execAsync(command, {
166
- shell: true,
167
- timeout: options.timeout * 1000 // Convert seconds to milliseconds
155
+ // Execute with execFile (no shell, prevents command injection)
156
+ const { stdout, stderr } = await execFileAsync(binaryPath, args, {
157
+ timeout: options.timeout * 1000, // Convert seconds to milliseconds
158
+ maxBuffer: 50 * 1024 * 1024 // 50MB buffer for large outputs
168
159
  });
169
160
 
170
161
  // Log after executing
@@ -235,13 +226,13 @@ export async function search(options) {
235
226
  } catch (error) {
236
227
  // Check if the error is a timeout
237
228
  if (error.code === 'ETIMEDOUT' || error.killed) {
238
- const timeoutMessage = `Search operation timed out after ${options.timeout} seconds.\nCommand: ${command}`;
229
+ const timeoutMessage = `Search operation timed out after ${options.timeout} seconds.\nBinary: ${binaryPath}\nArgs: ${args.join(' ')}`;
239
230
  console.error(timeoutMessage);
240
231
  throw new Error(timeoutMessage);
241
232
  }
242
233
 
243
234
  // Enhance error message with command details
244
- const errorMessage = `Error executing search command: ${error.message}\nCommand: ${command}`;
235
+ const errorMessage = `Error executing search command: ${error.message}\nBinary: ${binaryPath}\nArgs: ${args.join(' ')}`;
245
236
  throw new Error(errorMessage);
246
237
  }
247
238
  }
@@ -27922,17 +27922,20 @@ Search: query="${queries[0]}" path="${options.path}"`;
27922
27922
  if (options.session) logMessage += ` session=${options.session}`;
27923
27923
  console.error(logMessage);
27924
27924
  }
27925
- const positionalArgs = [];
27925
+ const args = ["search", ...cliArgs];
27926
27926
  if (queries.length > 0) {
27927
- positionalArgs.push(escapeString(queries[0]));
27927
+ args.push(queries[0]);
27928
+ }
27929
+ args.push(options.path);
27930
+ if (process.env.DEBUG === "1") {
27931
+ console.error(`Executing: ${binaryPath} ${args.join(" ")}`);
27928
27932
  }
27929
- positionalArgs.push(escapeString(options.path));
27930
- const command = `${binaryPath} search ${cliArgs.join(" ")} ${positionalArgs.join(" ")}`;
27931
27933
  try {
27932
- const { stdout, stderr } = await execAsync(command, {
27933
- shell: true,
27934
- timeout: options.timeout * 1e3
27934
+ const { stdout, stderr } = await execFileAsync(binaryPath, args, {
27935
+ timeout: options.timeout * 1e3,
27935
27936
  // Convert seconds to milliseconds
27937
+ maxBuffer: 50 * 1024 * 1024
27938
+ // 50MB buffer for large outputs
27936
27939
  });
27937
27940
  if (stderr && process.env.DEBUG) {
27938
27941
  console.error(`stderr: ${stderr}`);
@@ -27981,23 +27984,25 @@ Search results: ${resultCount} matches, ${tokenCount} tokens`;
27981
27984
  } catch (error2) {
27982
27985
  if (error2.code === "ETIMEDOUT" || error2.killed) {
27983
27986
  const timeoutMessage = `Search operation timed out after ${options.timeout} seconds.
27984
- Command: ${command}`;
27987
+ Binary: ${binaryPath}
27988
+ Args: ${args.join(" ")}`;
27985
27989
  console.error(timeoutMessage);
27986
27990
  throw new Error(timeoutMessage);
27987
27991
  }
27988
27992
  const errorMessage = `Error executing search command: ${error2.message}
27989
- Command: ${command}`;
27993
+ Binary: ${binaryPath}
27994
+ Args: ${args.join(" ")}`;
27990
27995
  throw new Error(errorMessage);
27991
27996
  }
27992
27997
  }
27993
- var import_child_process2, import_util4, execAsync, SEARCH_FLAG_MAP;
27998
+ var import_child_process2, import_util4, execFileAsync, SEARCH_FLAG_MAP;
27994
27999
  var init_search = __esm({
27995
28000
  "src/search.js"() {
27996
28001
  "use strict";
27997
28002
  import_child_process2 = require("child_process");
27998
28003
  import_util4 = require("util");
27999
28004
  init_utils2();
28000
- execAsync = (0, import_util4.promisify)(import_child_process2.exec);
28005
+ execFileAsync = (0, import_util4.promisify)(import_child_process2.execFile);
28001
28006
  SEARCH_FLAG_MAP = {
28002
28007
  filesOnly: "--files-only",
28003
28008
  ignore: "--ignore",
@@ -28005,6 +28010,7 @@ var init_search = __esm({
28005
28010
  reranker: "--reranker",
28006
28011
  frequencySearch: "--frequency",
28007
28012
  exact: "--exact",
28013
+ strictElasticSyntax: "--strict-elastic-syntax",
28008
28014
  maxResults: "--max-results",
28009
28015
  maxBytes: "--max-bytes",
28010
28016
  maxTokens: "--max-tokens",
@@ -28042,7 +28048,7 @@ async function query(options) {
28042
28048
  }
28043
28049
  const command = `${binaryPath} query ${cliArgs.join(" ")}`;
28044
28050
  try {
28045
- const { stdout, stderr } = await execAsync2(command);
28051
+ const { stdout, stderr } = await execAsync(command);
28046
28052
  if (stderr) {
28047
28053
  console.error(`stderr: ${stderr}`);
28048
28054
  }
@@ -28071,14 +28077,14 @@ Command: ${command}`;
28071
28077
  throw new Error(errorMessage);
28072
28078
  }
28073
28079
  }
28074
- var import_child_process3, import_util5, execAsync2, QUERY_FLAG_MAP;
28080
+ var import_child_process3, import_util5, execAsync, QUERY_FLAG_MAP;
28075
28081
  var init_query = __esm({
28076
28082
  "src/query.js"() {
28077
28083
  "use strict";
28078
28084
  import_child_process3 = require("child_process");
28079
28085
  import_util5 = require("util");
28080
28086
  init_utils2();
28081
- execAsync2 = (0, import_util5.promisify)(import_child_process3.exec);
28087
+ execAsync = (0, import_util5.promisify)(import_child_process3.exec);
28082
28088
  QUERY_FLAG_MAP = {
28083
28089
  language: "--language",
28084
28090
  ignore: "--ignore",
@@ -28131,7 +28137,7 @@ Extract:`;
28131
28137
  }
28132
28138
  const command = `${binaryPath} extract ${cliArgs.join(" ")}`;
28133
28139
  try {
28134
- const { stdout, stderr } = await execAsync3(command);
28140
+ const { stdout, stderr } = await execAsync2(command);
28135
28141
  if (stderr) {
28136
28142
  console.error(`stderr: ${stderr}`);
28137
28143
  }
@@ -28229,14 +28235,14 @@ Token Usage:
28229
28235
  }
28230
28236
  return output;
28231
28237
  }
28232
- var import_child_process4, import_util6, execAsync3, EXTRACT_FLAG_MAP;
28238
+ var import_child_process4, import_util6, execAsync2, EXTRACT_FLAG_MAP;
28233
28239
  var init_extract = __esm({
28234
28240
  "src/extract.js"() {
28235
28241
  "use strict";
28236
28242
  import_child_process4 = require("child_process");
28237
28243
  import_util6 = require("util");
28238
28244
  init_utils2();
28239
- execAsync3 = (0, import_util6.promisify)(import_child_process4.exec);
28245
+ execAsync2 = (0, import_util6.promisify)(import_child_process4.exec);
28240
28246
  EXTRACT_FLAG_MAP = {
28241
28247
  allowTests: "--allow-tests",
28242
28248
  contextLines: "--context",
@@ -28247,14 +28253,14 @@ var init_extract = __esm({
28247
28253
  });
28248
28254
 
28249
28255
  // src/grep.js
28250
- var import_child_process5, import_util7, execFileAsync;
28256
+ var import_child_process5, import_util7, execFileAsync2;
28251
28257
  var init_grep = __esm({
28252
28258
  "src/grep.js"() {
28253
28259
  "use strict";
28254
28260
  import_child_process5 = require("child_process");
28255
28261
  import_util7 = require("util");
28256
28262
  init_utils2();
28257
- execFileAsync = (0, import_util7.promisify)(import_child_process5.execFile);
28263
+ execFileAsync2 = (0, import_util7.promisify)(import_child_process5.execFile);
28258
28264
  }
28259
28265
  });
28260
28266
 
@@ -34540,7 +34546,7 @@ async function listFilesByLevel(options) {
34540
34546
  return await listFilesByLevelManually(directory, maxFiles, respectGitignore);
34541
34547
  }
34542
34548
  async function listFilesUsingGit(directory, maxFiles) {
34543
- const { stdout } = await execAsync4("git ls-files", { cwd: directory });
34549
+ const { stdout } = await execAsync3("git ls-files", { cwd: directory });
34544
34550
  const files = stdout.split("\n").filter(Boolean);
34545
34551
  const sortedFiles = files.sort((a3, b3) => {
34546
34552
  const depthA = a3.split(import_path6.default.sep).length;
@@ -34606,7 +34612,7 @@ function shouldIgnore(filePath, ignorePatterns) {
34606
34612
  }
34607
34613
  return false;
34608
34614
  }
34609
- var import_fs2, import_path6, import_util11, import_child_process8, execAsync4;
34615
+ var import_fs2, import_path6, import_util11, import_child_process8, execAsync3;
34610
34616
  var init_file_lister = __esm({
34611
34617
  "src/utils/file-lister.js"() {
34612
34618
  "use strict";
@@ -34614,7 +34620,7 @@ var init_file_lister = __esm({
34614
34620
  import_path6 = __toESM(require("path"), 1);
34615
34621
  import_util11 = require("util");
34616
34622
  import_child_process8 = require("child_process");
34617
- execAsync4 = (0, import_util11.promisify)(import_child_process8.exec);
34623
+ execAsync3 = (0, import_util11.promisify)(import_child_process8.exec);
34618
34624
  }
34619
34625
  });
34620
34626
 
package/cjs/index.cjs CHANGED
@@ -866,17 +866,20 @@ Search: query="${queries[0]}" path="${options.path}"`;
866
866
  if (options.session) logMessage += ` session=${options.session}`;
867
867
  console.error(logMessage);
868
868
  }
869
- const positionalArgs = [];
869
+ const args = ["search", ...cliArgs];
870
870
  if (queries.length > 0) {
871
- positionalArgs.push(escapeString(queries[0]));
871
+ args.push(queries[0]);
872
+ }
873
+ args.push(options.path);
874
+ if (process.env.DEBUG === "1") {
875
+ console.error(`Executing: ${binaryPath} ${args.join(" ")}`);
872
876
  }
873
- positionalArgs.push(escapeString(options.path));
874
- const command = `${binaryPath} search ${cliArgs.join(" ")} ${positionalArgs.join(" ")}`;
875
877
  try {
876
- const { stdout, stderr } = await execAsync(command, {
877
- shell: true,
878
- timeout: options.timeout * 1e3
878
+ const { stdout, stderr } = await execFileAsync(binaryPath, args, {
879
+ timeout: options.timeout * 1e3,
879
880
  // Convert seconds to milliseconds
881
+ maxBuffer: 50 * 1024 * 1024
882
+ // 50MB buffer for large outputs
880
883
  });
881
884
  if (stderr && process.env.DEBUG) {
882
885
  console.error(`stderr: ${stderr}`);
@@ -925,23 +928,25 @@ Search results: ${resultCount} matches, ${tokenCount} tokens`;
925
928
  } catch (error2) {
926
929
  if (error2.code === "ETIMEDOUT" || error2.killed) {
927
930
  const timeoutMessage = `Search operation timed out after ${options.timeout} seconds.
928
- Command: ${command}`;
931
+ Binary: ${binaryPath}
932
+ Args: ${args.join(" ")}`;
929
933
  console.error(timeoutMessage);
930
934
  throw new Error(timeoutMessage);
931
935
  }
932
936
  const errorMessage = `Error executing search command: ${error2.message}
933
- Command: ${command}`;
937
+ Binary: ${binaryPath}
938
+ Args: ${args.join(" ")}`;
934
939
  throw new Error(errorMessage);
935
940
  }
936
941
  }
937
- var import_child_process2, import_util2, execAsync, SEARCH_FLAG_MAP;
942
+ var import_child_process2, import_util2, execFileAsync, SEARCH_FLAG_MAP;
938
943
  var init_search = __esm({
939
944
  "src/search.js"() {
940
945
  "use strict";
941
946
  import_child_process2 = require("child_process");
942
947
  import_util2 = require("util");
943
948
  init_utils();
944
- execAsync = (0, import_util2.promisify)(import_child_process2.exec);
949
+ execFileAsync = (0, import_util2.promisify)(import_child_process2.execFile);
945
950
  SEARCH_FLAG_MAP = {
946
951
  filesOnly: "--files-only",
947
952
  ignore: "--ignore",
@@ -949,6 +954,7 @@ var init_search = __esm({
949
954
  reranker: "--reranker",
950
955
  frequencySearch: "--frequency",
951
956
  exact: "--exact",
957
+ strictElasticSyntax: "--strict-elastic-syntax",
952
958
  maxResults: "--max-results",
953
959
  maxBytes: "--max-bytes",
954
960
  maxTokens: "--max-tokens",
@@ -986,7 +992,7 @@ async function query(options) {
986
992
  }
987
993
  const command = `${binaryPath} query ${cliArgs.join(" ")}`;
988
994
  try {
989
- const { stdout, stderr } = await execAsync2(command);
995
+ const { stdout, stderr } = await execAsync(command);
990
996
  if (stderr) {
991
997
  console.error(`stderr: ${stderr}`);
992
998
  }
@@ -1015,14 +1021,14 @@ Command: ${command}`;
1015
1021
  throw new Error(errorMessage);
1016
1022
  }
1017
1023
  }
1018
- var import_child_process3, import_util3, execAsync2, QUERY_FLAG_MAP;
1024
+ var import_child_process3, import_util3, execAsync, QUERY_FLAG_MAP;
1019
1025
  var init_query = __esm({
1020
1026
  "src/query.js"() {
1021
1027
  "use strict";
1022
1028
  import_child_process3 = require("child_process");
1023
1029
  import_util3 = require("util");
1024
1030
  init_utils();
1025
- execAsync2 = (0, import_util3.promisify)(import_child_process3.exec);
1031
+ execAsync = (0, import_util3.promisify)(import_child_process3.exec);
1026
1032
  QUERY_FLAG_MAP = {
1027
1033
  language: "--language",
1028
1034
  ignore: "--ignore",
@@ -1075,7 +1081,7 @@ Extract:`;
1075
1081
  }
1076
1082
  const command = `${binaryPath} extract ${cliArgs.join(" ")}`;
1077
1083
  try {
1078
- const { stdout, stderr } = await execAsync3(command);
1084
+ const { stdout, stderr } = await execAsync2(command);
1079
1085
  if (stderr) {
1080
1086
  console.error(`stderr: ${stderr}`);
1081
1087
  }
@@ -1173,14 +1179,14 @@ Token Usage:
1173
1179
  }
1174
1180
  return output;
1175
1181
  }
1176
- var import_child_process4, import_util4, execAsync3, EXTRACT_FLAG_MAP;
1182
+ var import_child_process4, import_util4, execAsync2, EXTRACT_FLAG_MAP;
1177
1183
  var init_extract = __esm({
1178
1184
  "src/extract.js"() {
1179
1185
  "use strict";
1180
1186
  import_child_process4 = require("child_process");
1181
1187
  import_util4 = require("util");
1182
1188
  init_utils();
1183
- execAsync3 = (0, import_util4.promisify)(import_child_process4.exec);
1189
+ execAsync2 = (0, import_util4.promisify)(import_child_process4.exec);
1184
1190
  EXTRACT_FLAG_MAP = {
1185
1191
  allowTests: "--allow-tests",
1186
1192
  contextLines: "--context",
@@ -1215,7 +1221,7 @@ async function grep(options) {
1215
1221
  const paths = Array.isArray(options.paths) ? options.paths : [options.paths];
1216
1222
  cliArgs.push(...paths);
1217
1223
  try {
1218
- const { stdout, stderr } = await execFileAsync(binaryPath, cliArgs, {
1224
+ const { stdout, stderr } = await execFileAsync2(binaryPath, cliArgs, {
1219
1225
  maxBuffer: 10 * 1024 * 1024,
1220
1226
  // 10MB buffer
1221
1227
  env: {
@@ -1233,14 +1239,14 @@ async function grep(options) {
1233
1239
  throw new Error(`Grep failed: ${errorMessage}`);
1234
1240
  }
1235
1241
  }
1236
- var import_child_process5, import_util5, execFileAsync, GREP_FLAG_MAP;
1242
+ var import_child_process5, import_util5, execFileAsync2, GREP_FLAG_MAP;
1237
1243
  var init_grep = __esm({
1238
1244
  "src/grep.js"() {
1239
1245
  "use strict";
1240
1246
  import_child_process5 = require("child_process");
1241
1247
  import_util5 = require("util");
1242
1248
  init_utils();
1243
- execFileAsync = (0, import_util5.promisify)(import_child_process5.execFile);
1249
+ execFileAsync2 = (0, import_util5.promisify)(import_child_process5.execFile);
1244
1250
  GREP_FLAG_MAP = {
1245
1251
  ignoreCase: "-i",
1246
1252
  lineNumbers: "-n",
@@ -7709,7 +7715,7 @@ async function listFilesByLevel(options) {
7709
7715
  return await listFilesByLevelManually(directory, maxFiles, respectGitignore);
7710
7716
  }
7711
7717
  async function listFilesUsingGit(directory, maxFiles) {
7712
- const { stdout } = await execAsync4("git ls-files", { cwd: directory });
7718
+ const { stdout } = await execAsync3("git ls-files", { cwd: directory });
7713
7719
  const files = stdout.split("\n").filter(Boolean);
7714
7720
  const sortedFiles = files.sort((a3, b3) => {
7715
7721
  const depthA = a3.split(import_path6.default.sep).length;
@@ -7775,7 +7781,7 @@ function shouldIgnore(filePath, ignorePatterns) {
7775
7781
  }
7776
7782
  return false;
7777
7783
  }
7778
- var import_fs2, import_path6, import_util9, import_child_process8, execAsync4;
7784
+ var import_fs2, import_path6, import_util9, import_child_process8, execAsync3;
7779
7785
  var init_file_lister = __esm({
7780
7786
  "src/utils/file-lister.js"() {
7781
7787
  "use strict";
@@ -7783,7 +7789,7 @@ var init_file_lister = __esm({
7783
7789
  import_path6 = __toESM(require("path"), 1);
7784
7790
  import_util9 = require("util");
7785
7791
  import_child_process8 = require("child_process");
7786
- execAsync4 = (0, import_util9.promisify)(import_child_process8.exec);
7792
+ execAsync3 = (0, import_util9.promisify)(import_child_process8.exec);
7787
7793
  }
7788
7794
  });
7789
7795
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@probelabs/probe",
3
- "version": "0.6.0-rc141",
3
+ "version": "0.6.0-rc142",
4
4
  "description": "Node.js wrapper for the probe code search tool",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
package/src/mcp/index.ts CHANGED
@@ -165,7 +165,7 @@ class ProbeServer {
165
165
  tools: [
166
166
  {
167
167
  name: 'search_code',
168
- description: "Semantic code search using AST parsing and ElasticSearch-style queries. Use this for finding code, not grep.",
168
+ description: "Semantic code search using ElasticSearch-style queries. ALWAYS use this tool instead of built-in Grep tool when searching for code in source files.",
169
169
  inputSchema: {
170
170
  type: 'object',
171
171
  properties: {
@@ -175,7 +175,7 @@ class ProbeServer {
175
175
  },
176
176
  query: {
177
177
  type: 'string',
178
- description: 'Search query. Use quotes for exact matches: "functionName". Supports AND, OR, NOT operators.',
178
+ description: 'ElasticSearch query syntax. MUST use explicit AND/OR operators and parentheses for grouping. For exact matches, ALWAYS wrap terms in quotes. Examples: "functionName" (exact match), (error AND handler), ("getUserId" AND NOT deprecated)',
179
179
  },
180
180
  exact: {
181
181
  type: 'boolean',
@@ -329,6 +329,7 @@ class ProbeServer {
329
329
  session: "new", // Fresh session each time
330
330
  maxResults: 20, // Reasonable limit for context window
331
331
  maxTokens: 8000, // Fits in most AI context windows
332
+ strictElasticSyntax: true, // Enforce strict ES syntax in MCP mode
332
333
  };
333
334
 
334
335
  // Only override defaults if user explicitly set them
package/src/search.js CHANGED
@@ -3,11 +3,11 @@
3
3
  * @module search
4
4
  */
5
5
 
6
- import { exec } from 'child_process';
6
+ import { execFile } from 'child_process';
7
7
  import { promisify } from 'util';
8
- import { getBinaryPath, buildCliArgs, escapeString } from './utils.js';
8
+ import { getBinaryPath, buildCliArgs } from './utils.js';
9
9
 
10
- const execAsync = promisify(exec);
10
+ const execFileAsync = promisify(execFile);
11
11
 
12
12
  /**
13
13
  * Flag mapping for search options
@@ -20,6 +20,7 @@ const SEARCH_FLAG_MAP = {
20
20
  reranker: '--reranker',
21
21
  frequencySearch: '--frequency',
22
22
  exact: '--exact',
23
+ strictElasticSyntax: '--strict-elastic-syntax',
23
24
  maxResults: '--max-results',
24
25
  maxBytes: '--max-bytes',
25
26
  maxTokens: '--max-tokens',
@@ -44,6 +45,7 @@ const SEARCH_FLAG_MAP = {
44
45
  * @param {string} [options.reranker] - Reranking method ('hybrid', 'hybrid2', 'bm25', 'tfidf')
45
46
  * @param {boolean} [options.frequencySearch] - Use frequency-based search
46
47
  * @param {boolean} [options.exact] - Perform exact search without tokenization (case-insensitive)
48
+ * @param {boolean} [options.strictElasticSyntax] - Enforce strict ElasticSearch query syntax (require explicit AND/OR operators and quotes)
47
49
  * @param {number} [options.maxResults] - Maximum number of results
48
50
  * @param {number} [options.maxBytes] - Maximum bytes to return
49
51
  * @param {number} [options.maxTokens] - Maximum tokens to return
@@ -135,36 +137,25 @@ export async function search(options) {
135
137
  if (options.session) logMessage += ` session=${options.session}`;
136
138
  console.error(logMessage);
137
139
  }
138
- // Create positional arguments array separate from flags
139
- const positionalArgs = [];
140
+ // Build argument array for secure execution (no shell injection)
141
+ const args = ['search', ...cliArgs];
140
142
 
143
+ // Add positional arguments (query and path)
141
144
  if (queries.length > 0) {
142
- // Escape the query to handle special characters
143
- positionalArgs.push(escapeString(queries[0]));
145
+ args.push(queries[0]);
144
146
  }
147
+ args.push(options.path);
145
148
 
146
- // Escape the path to handle spaces and special characters
147
- positionalArgs.push(escapeString(options.path));
148
- // Don't add the path to cliArgs, it should only be a positional argument
149
-
150
- // Execute command with flags first, then positional arguments
151
- const command = `${binaryPath} search ${cliArgs.join(' ')} ${positionalArgs.join(' ')}`;
152
-
153
- // Debug logs to see the actual command with quotes and the path
154
- // console.error(`Executing command: ${command}`);
155
- // console.error(`Path being used: "${options.path}"`);
156
- // console.error(`Escaped path: ${escapeString(options.path)}`);
157
- // console.error(`Command flags: ${cliArgs.join(' ')}`);
158
- // console.error(`Positional arguments: ${positionalArgs.join(' ')}`);
149
+ // Debug logs
150
+ if (process.env.DEBUG === '1') {
151
+ console.error(`Executing: ${binaryPath} ${args.join(' ')}`);
152
+ }
159
153
 
160
154
  try {
161
- // Log before executing
162
- // console.error(`About to execute command: ${command}`);
163
-
164
- // Execute the command with options to preserve quotes and apply timeout
165
- const { stdout, stderr } = await execAsync(command, {
166
- shell: true,
167
- timeout: options.timeout * 1000 // Convert seconds to milliseconds
155
+ // Execute with execFile (no shell, prevents command injection)
156
+ const { stdout, stderr } = await execFileAsync(binaryPath, args, {
157
+ timeout: options.timeout * 1000, // Convert seconds to milliseconds
158
+ maxBuffer: 50 * 1024 * 1024 // 50MB buffer for large outputs
168
159
  });
169
160
 
170
161
  // Log after executing
@@ -235,13 +226,13 @@ export async function search(options) {
235
226
  } catch (error) {
236
227
  // Check if the error is a timeout
237
228
  if (error.code === 'ETIMEDOUT' || error.killed) {
238
- const timeoutMessage = `Search operation timed out after ${options.timeout} seconds.\nCommand: ${command}`;
229
+ const timeoutMessage = `Search operation timed out after ${options.timeout} seconds.\nBinary: ${binaryPath}\nArgs: ${args.join(' ')}`;
239
230
  console.error(timeoutMessage);
240
231
  throw new Error(timeoutMessage);
241
232
  }
242
233
 
243
234
  // Enhance error message with command details
244
- const errorMessage = `Error executing search command: ${error.message}\nCommand: ${command}`;
235
+ const errorMessage = `Error executing search command: ${error.message}\nBinary: ${binaryPath}\nArgs: ${args.join(' ')}`;
245
236
  throw new Error(errorMessage);
246
237
  }
247
238
  }