@probelabs/probe 0.6.0-rc102 → 0.6.0-rc103

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.
@@ -1821,7 +1821,7 @@ async function delegate({ task, timeout = 300, debug = false, currentIteration =
1821
1821
  console.error(`[DELEGATE] Using binary at: ${binaryPath}`);
1822
1822
  console.error(`[DELEGATE] Command args: ${args.join(" ")}`);
1823
1823
  }
1824
- return new Promise((resolve2, reject) => {
1824
+ return new Promise((resolve3, reject) => {
1825
1825
  const delegationSpan = tracer ? tracer.createDelegationSpan(sessionId, task) : null;
1826
1826
  const process2 = spawn(binaryPath, args, {
1827
1827
  stdio: ["pipe", "pipe", "pipe"],
@@ -1886,7 +1886,7 @@ async function delegate({ task, timeout = 300, debug = false, currentIteration =
1886
1886
  delegationSpan.end();
1887
1887
  }
1888
1888
  }
1889
- resolve2(response);
1889
+ resolve3(response);
1890
1890
  } else {
1891
1891
  const errorMessage = stderr.trim() || `Delegate process failed with exit code ${code}`;
1892
1892
  if (debug) {
@@ -2516,20 +2516,20 @@ var init_simpleTelemetry = __esm({
2516
2516
  }
2517
2517
  async flush() {
2518
2518
  if (this.stream) {
2519
- return new Promise((resolve2) => {
2520
- this.stream.once("drain", resolve2);
2519
+ return new Promise((resolve3) => {
2520
+ this.stream.once("drain", resolve3);
2521
2521
  if (!this.stream.writableNeedDrain) {
2522
- resolve2();
2522
+ resolve3();
2523
2523
  }
2524
2524
  });
2525
2525
  }
2526
2526
  }
2527
2527
  async shutdown() {
2528
2528
  if (this.stream) {
2529
- return new Promise((resolve2) => {
2529
+ return new Promise((resolve3) => {
2530
2530
  this.stream.end(() => {
2531
2531
  console.log(`[SimpleTelemetry] File stream closed: ${this.filePath}`);
2532
- resolve2();
2532
+ resolve3();
2533
2533
  });
2534
2534
  });
2535
2535
  }
@@ -2975,7 +2975,7 @@ function createMockProvider() {
2975
2975
  provider: "mock",
2976
2976
  // Mock the doGenerate method used by Vercel AI SDK
2977
2977
  doGenerate: async ({ messages, tools: tools2 }) => {
2978
- await new Promise((resolve2) => setTimeout(resolve2, 10));
2978
+ await new Promise((resolve3) => setTimeout(resolve3, 10));
2979
2979
  return {
2980
2980
  text: "This is a mock response for testing",
2981
2981
  toolCalls: [],
@@ -4865,7 +4865,10 @@ import { createGoogleGenerativeAI } from "@ai-sdk/google";
4865
4865
  import { streamText } from "ai";
4866
4866
  import { randomUUID as randomUUID4 } from "crypto";
4867
4867
  import { EventEmitter as EventEmitter2 } from "events";
4868
- var MAX_TOOL_ITERATIONS, MAX_HISTORY_MESSAGES, ProbeAgent;
4868
+ import { existsSync as existsSync3 } from "fs";
4869
+ import { readFile, stat } from "fs/promises";
4870
+ import { resolve, isAbsolute } from "path";
4871
+ var MAX_TOOL_ITERATIONS, MAX_HISTORY_MESSAGES, SUPPORTED_IMAGE_EXTENSIONS, MAX_IMAGE_FILE_SIZE, ProbeAgent;
4869
4872
  var init_ProbeAgent = __esm({
4870
4873
  "src/agent/ProbeAgent.js"() {
4871
4874
  "use strict";
@@ -4879,6 +4882,8 @@ var init_ProbeAgent = __esm({
4879
4882
  init_mcp();
4880
4883
  MAX_TOOL_ITERATIONS = parseInt(process.env.MAX_TOOL_ITERATIONS || "30", 10);
4881
4884
  MAX_HISTORY_MESSAGES = 100;
4885
+ SUPPORTED_IMAGE_EXTENSIONS = ["png", "jpg", "jpeg", "webp", "gif", "bmp", "svg"];
4886
+ MAX_IMAGE_FILE_SIZE = 20 * 1024 * 1024;
4882
4887
  ProbeAgent = class {
4883
4888
  /**
4884
4889
  * Create a new ProbeAgent instance
@@ -4922,6 +4927,8 @@ var init_ProbeAgent = __esm({
4922
4927
  }
4923
4928
  this.initializeTools();
4924
4929
  this.history = [];
4930
+ this.pendingImages = /* @__PURE__ */ new Map();
4931
+ this.currentImages = [];
4925
4932
  this.events = new EventEmitter2();
4926
4933
  this.enableMcp = !!options.enableMcp || process.env.ENABLE_MCP === "1";
4927
4934
  this.mcpConfigPath = options.mcpConfigPath || null;
@@ -5047,6 +5054,175 @@ var init_ProbeAgent = __esm({
5047
5054
  console.log(`Using Google API with model: ${this.model}${apiUrl ? ` (URL: ${apiUrl})` : ""}`);
5048
5055
  }
5049
5056
  }
5057
+ /**
5058
+ * Process assistant response content and detect/load image references
5059
+ * @param {string} content - The assistant's response content
5060
+ * @returns {Promise<void>}
5061
+ */
5062
+ async processImageReferences(content) {
5063
+ if (!content) return;
5064
+ const extensionsPattern = `(?:${SUPPORTED_IMAGE_EXTENSIONS.join("|")})`;
5065
+ const imagePatterns = [
5066
+ // Direct file path mentions: "./screenshot.png", "/path/to/image.jpg", etc.
5067
+ new RegExp(`(?:\\.?\\.\\/)?[^\\s"'<>\\[\\]]+\\.${extensionsPattern}(?!\\w)`, "gi"),
5068
+ // Contextual mentions: "look at image.png", "the file screenshot.jpg shows"
5069
+ new RegExp(`(?:image|file|screenshot|diagram|photo|picture|graphic)\\s*:?\\s*([^\\s"'<>\\[\\]]+\\.${extensionsPattern})(?!\\w)`, "gi"),
5070
+ // Tool result mentions: often contain file paths
5071
+ new RegExp(`(?:found|saved|created|generated).*?([^\\s"'<>\\[\\]]+\\.${extensionsPattern})(?!\\w)`, "gi")
5072
+ ];
5073
+ const foundPaths = /* @__PURE__ */ new Set();
5074
+ for (const pattern of imagePatterns) {
5075
+ let match;
5076
+ while ((match = pattern.exec(content)) !== null) {
5077
+ const imagePath = match[1] || match[0];
5078
+ if (imagePath && imagePath.length > 0) {
5079
+ foundPaths.add(imagePath.trim());
5080
+ }
5081
+ }
5082
+ }
5083
+ if (foundPaths.size === 0) return;
5084
+ if (this.debug) {
5085
+ console.log(`[DEBUG] Found ${foundPaths.size} potential image references:`, Array.from(foundPaths));
5086
+ }
5087
+ for (const imagePath of foundPaths) {
5088
+ await this.loadImageIfValid(imagePath);
5089
+ }
5090
+ }
5091
+ /**
5092
+ * Load and cache an image if it's valid and accessible
5093
+ * @param {string} imagePath - Path to the image file
5094
+ * @returns {Promise<boolean>} - True if image was loaded successfully
5095
+ */
5096
+ async loadImageIfValid(imagePath) {
5097
+ try {
5098
+ if (this.pendingImages.has(imagePath)) {
5099
+ if (this.debug) {
5100
+ console.log(`[DEBUG] Image already loaded: ${imagePath}`);
5101
+ }
5102
+ return true;
5103
+ }
5104
+ const allowedDirs = this.allowedFolders && this.allowedFolders.length > 0 ? this.allowedFolders : [process.cwd()];
5105
+ let absolutePath;
5106
+ let isPathAllowed = false;
5107
+ if (isAbsolute(imagePath)) {
5108
+ absolutePath = imagePath;
5109
+ isPathAllowed = allowedDirs.some((dir) => absolutePath.startsWith(resolve(dir)));
5110
+ } else {
5111
+ for (const dir of allowedDirs) {
5112
+ const resolvedPath = resolve(dir, imagePath);
5113
+ if (resolvedPath.startsWith(resolve(dir))) {
5114
+ absolutePath = resolvedPath;
5115
+ isPathAllowed = true;
5116
+ break;
5117
+ }
5118
+ }
5119
+ }
5120
+ if (!isPathAllowed) {
5121
+ if (this.debug) {
5122
+ console.log(`[DEBUG] Image path outside allowed directories: ${imagePath}`);
5123
+ }
5124
+ return false;
5125
+ }
5126
+ let fileStats;
5127
+ try {
5128
+ fileStats = await stat(absolutePath);
5129
+ } catch (error) {
5130
+ if (this.debug) {
5131
+ console.log(`[DEBUG] Image file not found: ${absolutePath}`);
5132
+ }
5133
+ return false;
5134
+ }
5135
+ if (fileStats.size > MAX_IMAGE_FILE_SIZE) {
5136
+ if (this.debug) {
5137
+ console.log(`[DEBUG] Image file too large: ${absolutePath} (${fileStats.size} bytes, max: ${MAX_IMAGE_FILE_SIZE})`);
5138
+ }
5139
+ return false;
5140
+ }
5141
+ const extension = absolutePath.toLowerCase().split(".").pop();
5142
+ if (!SUPPORTED_IMAGE_EXTENSIONS.includes(extension)) {
5143
+ if (this.debug) {
5144
+ console.log(`[DEBUG] Unsupported image format: ${extension}`);
5145
+ }
5146
+ return false;
5147
+ }
5148
+ const mimeTypes = {
5149
+ "png": "image/png",
5150
+ "jpg": "image/jpeg",
5151
+ "jpeg": "image/jpeg",
5152
+ "webp": "image/webp",
5153
+ "gif": "image/gif",
5154
+ "bmp": "image/bmp",
5155
+ "svg": "image/svg+xml"
5156
+ };
5157
+ const mimeType = mimeTypes[extension];
5158
+ const fileBuffer = await readFile(absolutePath);
5159
+ const base64Data = fileBuffer.toString("base64");
5160
+ const dataUrl = `data:${mimeType};base64,${base64Data}`;
5161
+ this.pendingImages.set(imagePath, dataUrl);
5162
+ if (this.debug) {
5163
+ console.log(`[DEBUG] Successfully loaded image: ${imagePath} (${fileBuffer.length} bytes)`);
5164
+ }
5165
+ return true;
5166
+ } catch (error) {
5167
+ if (this.debug) {
5168
+ console.log(`[DEBUG] Failed to load image ${imagePath}: ${error.message}`);
5169
+ }
5170
+ return false;
5171
+ }
5172
+ }
5173
+ /**
5174
+ * Get all currently loaded images as an array for AI model consumption
5175
+ * @returns {Array<string>} - Array of base64 data URLs
5176
+ */
5177
+ getCurrentImages() {
5178
+ return Array.from(this.pendingImages.values());
5179
+ }
5180
+ /**
5181
+ * Clear loaded images (useful for new conversations)
5182
+ */
5183
+ clearLoadedImages() {
5184
+ this.pendingImages.clear();
5185
+ this.currentImages = [];
5186
+ if (this.debug) {
5187
+ console.log("[DEBUG] Cleared all loaded images");
5188
+ }
5189
+ }
5190
+ /**
5191
+ * Prepare messages for AI consumption, adding images to the latest user message if available
5192
+ * @param {Array} messages - Current conversation messages
5193
+ * @returns {Array} - Messages formatted for AI SDK with potential image content
5194
+ */
5195
+ prepareMessagesWithImages(messages) {
5196
+ const loadedImages = this.getCurrentImages();
5197
+ if (loadedImages.length === 0) {
5198
+ return messages;
5199
+ }
5200
+ const messagesWithImages = [...messages];
5201
+ const lastUserMessageIndex = messagesWithImages.map((m) => m.role).lastIndexOf("user");
5202
+ if (lastUserMessageIndex === -1) {
5203
+ if (this.debug) {
5204
+ console.log("[DEBUG] No user messages found to attach images to");
5205
+ }
5206
+ return messages;
5207
+ }
5208
+ const lastUserMessage = messagesWithImages[lastUserMessageIndex];
5209
+ if (typeof lastUserMessage.content === "string") {
5210
+ messagesWithImages[lastUserMessageIndex] = {
5211
+ ...lastUserMessage,
5212
+ content: [
5213
+ { type: "text", text: lastUserMessage.content },
5214
+ ...loadedImages.map((imageData) => ({
5215
+ type: "image",
5216
+ image: imageData
5217
+ }))
5218
+ ]
5219
+ };
5220
+ if (this.debug) {
5221
+ console.log(`[DEBUG] Added ${loadedImages.length} images to the latest user message`);
5222
+ }
5223
+ }
5224
+ return messagesWithImages;
5225
+ }
5050
5226
  /**
5051
5227
  * Initialize mock model for testing
5052
5228
  */
@@ -5428,9 +5604,10 @@ You are working with a repository located at: ${searchDirectory}
5428
5604
  let assistantResponseContent = "";
5429
5605
  try {
5430
5606
  const executeAIRequest = async () => {
5607
+ const messagesForAI = this.prepareMessagesWithImages(currentMessages);
5431
5608
  const result = await streamText({
5432
5609
  model: this.provider(this.model),
5433
- messages: currentMessages,
5610
+ messages: messagesForAI,
5434
5611
  maxTokens: maxResponseTokens,
5435
5612
  temperature: 0.3
5436
5613
  });
@@ -5464,6 +5641,9 @@ You are working with a repository located at: ${searchDirectory}
5464
5641
  const assistantPreview = createMessagePreview(assistantResponseContent);
5465
5642
  console.log(`[DEBUG] Assistant response (${assistantResponseContent.length} chars): ${assistantPreview}`);
5466
5643
  }
5644
+ if (assistantResponseContent) {
5645
+ await this.processImageReferences(assistantResponseContent);
5646
+ }
5467
5647
  const validTools = [
5468
5648
  "search",
5469
5649
  "query",
@@ -5588,12 +5768,17 @@ ${toolResultContent}
5588
5768
  throw toolError;
5589
5769
  }
5590
5770
  currentMessages.push({ role: "assistant", content: assistantResponseContent });
5771
+ const toolResultContent = typeof toolResult === "string" ? toolResult : JSON.stringify(toolResult, null, 2);
5772
+ const toolResultMessage = `<tool_result>
5773
+ ${toolResultContent}
5774
+ </tool_result>`;
5591
5775
  currentMessages.push({
5592
5776
  role: "user",
5593
- content: `<tool_result>
5594
- ${typeof toolResult === "string" ? toolResult : JSON.stringify(toolResult, null, 2)}
5595
- </tool_result>`
5777
+ content: toolResultMessage
5596
5778
  });
5779
+ if (toolResultContent) {
5780
+ await this.processImageReferences(toolResultContent);
5781
+ }
5597
5782
  if (this.debug) {
5598
5783
  console.log(`[DEBUG] Tool ${toolName} executed successfully. Result length: ${typeof toolResult === "string" ? toolResult.length : JSON.stringify(toolResult).length}`);
5599
5784
  }
@@ -6094,8 +6279,8 @@ import {
6094
6279
  ListToolsRequestSchema,
6095
6280
  McpError
6096
6281
  } from "@modelcontextprotocol/sdk/types.js";
6097
- import { readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
6098
- import { resolve } from "path";
6282
+ import { readFileSync as readFileSync2, existsSync as existsSync4 } from "fs";
6283
+ import { resolve as resolve2 } from "path";
6099
6284
 
6100
6285
  // src/agent/acp/server.js
6101
6286
  import { randomUUID as randomUUID5 } from "crypto";
@@ -6354,8 +6539,8 @@ var ACPConnection = class extends EventEmitter3 {
6354
6539
  if (params !== null) {
6355
6540
  message.params = params;
6356
6541
  }
6357
- return new Promise((resolve2, reject) => {
6358
- this.pendingRequests.set(id, { resolve: resolve2, reject });
6542
+ return new Promise((resolve3, reject) => {
6543
+ this.pendingRequests.set(id, { resolve: resolve3, reject });
6359
6544
  this.sendMessage(message);
6360
6545
  setTimeout(() => {
6361
6546
  if (this.pendingRequests.has(id)) {
@@ -6763,8 +6948,8 @@ import { randomUUID as randomUUID6 } from "crypto";
6763
6948
  function readInputContent(input) {
6764
6949
  if (!input) return null;
6765
6950
  try {
6766
- const resolvedPath = resolve(input);
6767
- if (existsSync3(resolvedPath)) {
6951
+ const resolvedPath = resolve2(input);
6952
+ if (existsSync4(resolvedPath)) {
6768
6953
  return readFileSync2(resolvedPath, "utf-8").trim();
6769
6954
  }
6770
6955
  } catch (error) {
@@ -6772,7 +6957,7 @@ function readInputContent(input) {
6772
6957
  return input;
6773
6958
  }
6774
6959
  function readFromStdin() {
6775
- return new Promise((resolve2, reject) => {
6960
+ return new Promise((resolve3, reject) => {
6776
6961
  let data = "";
6777
6962
  let hasReceivedData = false;
6778
6963
  let dataChunks = [];
@@ -6797,7 +6982,7 @@ function readFromStdin() {
6797
6982
  if (!trimmed && dataChunks.length === 0) {
6798
6983
  reject(new Error("No input received from stdin"));
6799
6984
  } else {
6800
- resolve2(trimmed);
6985
+ resolve3(trimmed);
6801
6986
  }
6802
6987
  });
6803
6988
  process.stdin.on("error", (error) => {
@@ -6964,6 +7149,7 @@ var ProbeAgentMcpServer = class {
6964
7149
  }
6965
7150
  }
6966
7151
  );
7152
+ this.agent = null;
6967
7153
  this.setupToolHandlers();
6968
7154
  this.server.onerror = (error) => console.error("[MCP Error]", error);
6969
7155
  process.on("SIGINT", async () => {
@@ -7075,18 +7261,24 @@ var ProbeAgentMcpServer = class {
7075
7261
  throw new Error("Schema could not be read");
7076
7262
  }
7077
7263
  }
7078
- const agentConfig = {
7079
- path: args.path || process.cwd(),
7080
- promptType: args.prompt || "code-explorer",
7081
- customPrompt: systemPrompt,
7082
- provider: args.provider,
7083
- model: args.model,
7084
- allowEdit: !!args.allow_edit,
7085
- debug: process.env.DEBUG === "1",
7086
- maxResponseTokens: args.max_response_tokens,
7087
- disableMermaidValidation: !!args.no_mermaid_validation
7088
- };
7089
- const agent = new ProbeAgent(agentConfig);
7264
+ if (!this.agent) {
7265
+ if (process.env.DEBUG === "1") {
7266
+ console.error("[DEBUG] Initializing AI agent on first MCP tool call");
7267
+ }
7268
+ const agentConfig2 = {
7269
+ path: args.path || process.cwd(),
7270
+ promptType: args.prompt || "code-explorer",
7271
+ customPrompt: systemPrompt,
7272
+ provider: args.provider,
7273
+ model: args.model,
7274
+ allowEdit: !!args.allow_edit,
7275
+ debug: process.env.DEBUG === "1",
7276
+ maxResponseTokens: args.max_response_tokens,
7277
+ disableMermaidValidation: !!args.no_mermaid_validation
7278
+ };
7279
+ this.agent = new ProbeAgent(agentConfig2);
7280
+ }
7281
+ const agent = this.agent;
7090
7282
  let result = await agent.answer(query2, [], { schema });
7091
7283
  if (schema) {
7092
7284
  const schemaPrompt = `Now you need to respond according to this schema:
@@ -7147,7 +7339,7 @@ Please reformat your previous response to match this schema exactly. Only return
7147
7339
  } catch (error) {
7148
7340
  }
7149
7341
  }
7150
- const tokenUsage = agent.getTokenUsage();
7342
+ const tokenUsage = this.agent.getTokenUsage();
7151
7343
  console.error(`Token usage: ${JSON.stringify(tokenUsage)}`);
7152
7344
  return {
7153
7345
  content: [
@@ -7272,7 +7464,7 @@ async function main() {
7272
7464
  process.exit(1);
7273
7465
  }
7274
7466
  }
7275
- const agentConfig = {
7467
+ const agentConfig2 = {
7276
7468
  path: config.path,
7277
7469
  promptType: config.prompt,
7278
7470
  customPrompt: systemPrompt,
@@ -7283,7 +7475,7 @@ async function main() {
7283
7475
  maxResponseTokens: config.maxResponseTokens,
7284
7476
  disableMermaidValidation: config.noMermaidValidation
7285
7477
  };
7286
- const agent = new ProbeAgent(agentConfig);
7478
+ const agent = new ProbeAgent(agentConfig2);
7287
7479
  let result;
7288
7480
  if (appTracer) {
7289
7481
  const sessionSpan = appTracer.createSessionSpan({
@@ -8,7 +8,7 @@ import path from 'path';
8
8
  import fs from 'fs-extra';
9
9
  import { fileURLToPath } from 'url';
10
10
  // Import from parent package
11
- import { search, query, extract } from '../index.js';
11
+ import { search, extract } from '../index.js';
12
12
  // Parse command-line arguments
13
13
  function parseArgs() {
14
14
  const args = process.argv.slice(2);
@@ -128,38 +128,22 @@ class ProbeServer {
128
128
  },
129
129
  query: {
130
130
  type: 'string',
131
- description: 'Elastic search query. Supports logical operators (AND, OR, NOT), and grouping with parentheses. Examples: "config", "(term1 OR term2) AND term3". Use quotes for exact matches, like function or type names.',
132
- },
133
- filesOnly: {
134
- type: 'boolean',
135
- description: 'Skip AST parsing and just output unique files',
136
- },
137
- ignore: {
138
- type: 'array',
139
- items: { type: 'string' },
140
- description: 'Custom patterns to ignore (in addition to .gitignore and common patterns)'
141
- },
142
- excludeFilenames: {
143
- type: 'boolean',
144
- description: 'Exclude filenames from being used for matching'
131
+ description: 'Elastic search query. Supports logical operators (AND, OR, NOT), and grouping with parentheses. For exact matches of specific identifiers, use quotes: "MyFunction", "SpecificStruct", "exact_variable_name". Examples: "config", "(term1 OR term2) AND term3", "getUserData", "struct Config".',
145
132
  },
146
133
  exact: {
147
134
  type: 'boolean',
148
- description: 'Perform exact search without tokenization (case-insensitive)'
135
+ description: 'When you exactly know what you are looking for, like known function name, struct name, or variable name, set this flag for precise matching'
149
136
  },
150
137
  allowTests: {
151
138
  type: 'boolean',
152
- description: 'Allow test files and test code blocks in results (disabled by default)'
139
+ description: 'Allow test files and test code blocks in results (enabled by default)',
140
+ default: true
153
141
  },
154
142
  session: {
155
143
  type: 'string',
156
144
  description: 'Session identifier for caching. Set to "new" if unknown, or want to reset cache. Re-use session ID returned from previous searches',
157
145
  default: "new",
158
146
  },
159
- timeout: {
160
- type: 'number',
161
- description: 'Timeout for the search operation in seconds (default: 30)',
162
- },
163
147
  noGitignore: {
164
148
  type: 'boolean',
165
149
  description: 'Skip .gitignore files (will use PROBE_NO_GITIGNORE environment variable if not set)',
@@ -168,50 +152,6 @@ class ProbeServer {
168
152
  required: ['path', 'query']
169
153
  },
170
154
  },
171
- {
172
- name: 'query_code',
173
- description: "Search code using ast-grep structural pattern matching. Use this tool to find specific code structures like functions, classes, or methods.",
174
- inputSchema: {
175
- type: 'object',
176
- properties: {
177
- path: {
178
- type: 'string',
179
- description: 'Absolute path to the directory to search in (e.g., "/Users/username/projects/myproject").',
180
- },
181
- pattern: {
182
- type: 'string',
183
- description: 'The ast-grep pattern to search for. Examples: "fn $NAME($$$PARAMS) $$$BODY" for Rust functions, "def $NAME($$$PARAMS): $$$BODY" for Python functions.',
184
- },
185
- language: {
186
- type: 'string',
187
- description: 'The programming language to search in. If not specified, the tool will try to infer the language from file extensions. Supported languages: rust, javascript, typescript, python, go, c, cpp, java, ruby, php, swift, csharp, yaml.',
188
- },
189
- ignore: {
190
- type: 'array',
191
- items: { type: 'string' },
192
- description: 'Custom patterns to ignore (in addition to common patterns)',
193
- },
194
- maxResults: {
195
- type: 'number',
196
- description: 'Maximum number of results to return'
197
- },
198
- format: {
199
- type: 'string',
200
- enum: ['markdown', 'plain', 'json', 'color'],
201
- description: 'Output format for the query results'
202
- },
203
- timeout: {
204
- type: 'number',
205
- description: 'Timeout for the query operation in seconds (default: 30)',
206
- },
207
- noGitignore: {
208
- type: 'boolean',
209
- description: 'Skip .gitignore files (will use PROBE_NO_GITIGNORE environment variable if not set)',
210
- }
211
- },
212
- required: ['path', 'pattern']
213
- },
214
- },
215
155
  {
216
156
  name: 'extract_code',
217
157
  description: "Extract code blocks from files based on line number, or symbol name. Fetch full file when line number is not provided.",
@@ -229,22 +169,8 @@ class ProbeServer {
229
169
  },
230
170
  allowTests: {
231
171
  type: 'boolean',
232
- description: 'Allow test files and test code blocks in results (disabled by default)',
233
- },
234
- contextLines: {
235
- type: 'number',
236
- description: 'Number of context lines to include before and after the extracted block when AST parsing fails to find a suitable node',
237
- default: 0
238
- },
239
- format: {
240
- type: 'string',
241
- enum: ['markdown', 'plain', 'json'],
242
- description: 'Output format for the extracted code',
243
- default: 'markdown'
244
- },
245
- timeout: {
246
- type: 'number',
247
- description: 'Timeout for the extract operation in seconds (default: 30)',
172
+ description: 'Allow test files and test code blocks in results (enabled by default)',
173
+ default: true
248
174
  },
249
175
  noGitignore: {
250
176
  type: 'boolean',
@@ -257,8 +183,8 @@ class ProbeServer {
257
183
  ],
258
184
  }));
259
185
  this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
260
- if (request.params.name !== 'search_code' && request.params.name !== 'query_code' && request.params.name !== 'extract_code' &&
261
- request.params.name !== 'probe' && request.params.name !== 'query' && request.params.name !== 'extract') {
186
+ if (request.params.name !== 'search_code' && request.params.name !== 'extract_code' &&
187
+ request.params.name !== 'probe' && request.params.name !== 'extract') {
262
188
  throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
263
189
  }
264
190
  try {
@@ -282,10 +208,6 @@ class ProbeServer {
282
208
  }
283
209
  result = await this.executeCodeSearch(args);
284
210
  }
285
- else if (request.params.name === 'query_code' || request.params.name === 'query') {
286
- const args = request.params.arguments;
287
- result = await this.executeCodeQuery(args);
288
- }
289
211
  else { // extract_code or extract
290
212
  const args = request.params.arguments;
291
213
  result = await this.executeCodeExtract(args);
@@ -331,20 +253,19 @@ class ProbeServer {
331
253
  query: args.query
332
254
  };
333
255
  // Add optional parameters only if they exist
334
- if (args.filesOnly !== undefined)
335
- options.filesOnly = args.filesOnly;
336
- if (args.ignore !== undefined)
337
- options.ignore = args.ignore;
338
- if (args.excludeFilenames !== undefined)
339
- options.excludeFilenames = args.excludeFilenames;
340
256
  if (args.exact !== undefined)
341
257
  options.exact = args.exact;
342
258
  if (args.maxResults !== undefined)
343
259
  options.maxResults = args.maxResults;
344
260
  if (args.maxTokens !== undefined)
345
261
  options.maxTokens = args.maxTokens;
346
- if (args.allowTests !== undefined)
262
+ // Set allowTests to true by default if not specified
263
+ if (args.allowTests !== undefined) {
347
264
  options.allowTests = args.allowTests;
265
+ }
266
+ else {
267
+ options.allowTests = true;
268
+ }
348
269
  // Use noGitignore from args, or fall back to PROBE_NO_GITIGNORE environment variable
349
270
  if (args.noGitignore !== undefined) {
350
271
  options.noGitignore = args.noGitignore;
@@ -358,13 +279,6 @@ class ProbeServer {
358
279
  else {
359
280
  options.session = "new";
360
281
  }
361
- // Use timeout from args, or fall back to instance default
362
- if (args.timeout !== undefined) {
363
- options.timeout = args.timeout;
364
- }
365
- else if (this.defaultTimeout !== undefined) {
366
- options.timeout = this.defaultTimeout;
367
- }
368
282
  // Handle format options
369
283
  if (this.defaultFormat === 'outline-xml') {
370
284
  // For outline-xml format, we pass it as a format flag to the search command
@@ -394,45 +308,6 @@ class ProbeServer {
394
308
  throw new McpError('MethodNotFound', `Error executing code search: ${error.message || String(error)}`);
395
309
  }
396
310
  }
397
- async executeCodeQuery(args) {
398
- try {
399
- // Validate required parameters
400
- if (!args.path) {
401
- throw new Error("Path is required");
402
- }
403
- if (!args.pattern) {
404
- throw new Error("Pattern is required");
405
- }
406
- // Create a single options object with both pattern and path
407
- const options = {
408
- path: args.path,
409
- pattern: args.pattern,
410
- language: args.language,
411
- ignore: args.ignore,
412
- allowTests: args.allowTests,
413
- maxResults: args.maxResults,
414
- format: args.format,
415
- timeout: args.timeout || this.defaultTimeout
416
- };
417
- // Use noGitignore from args, or fall back to PROBE_NO_GITIGNORE environment variable
418
- if (args.noGitignore !== undefined) {
419
- options.noGitignore = args.noGitignore;
420
- }
421
- else if (process.env.PROBE_NO_GITIGNORE) {
422
- options.noGitignore = process.env.PROBE_NO_GITIGNORE === 'true';
423
- }
424
- console.log("Executing query with options:", JSON.stringify({
425
- path: options.path,
426
- pattern: options.pattern
427
- }));
428
- const result = await query(options);
429
- return result;
430
- }
431
- catch (error) {
432
- console.error('Error executing code query:', error);
433
- throw new McpError('MethodNotFound', `Error executing code query: ${error.message || String(error)}`);
434
- }
435
- }
436
311
  async executeCodeExtract(args) {
437
312
  try {
438
313
  // Validate required parameters
@@ -446,11 +321,15 @@ class ProbeServer {
446
321
  const options = {
447
322
  files: args.files,
448
323
  path: args.path,
449
- allowTests: args.allowTests,
450
- contextLines: args.contextLines,
451
- format: args.format,
452
- timeout: args.timeout || this.defaultTimeout
324
+ format: 'xml'
453
325
  };
326
+ // Set allowTests to true by default if not specified
327
+ if (args.allowTests !== undefined) {
328
+ options.allowTests = args.allowTests;
329
+ }
330
+ else {
331
+ options.allowTests = true;
332
+ }
454
333
  // Use noGitignore from args, or fall back to PROBE_NO_GITIGNORE environment variable
455
334
  if (args.noGitignore !== undefined) {
456
335
  options.noGitignore = args.noGitignore;