@probelabs/probe 0.6.0-rc288 → 0.6.0-rc291

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 (31) hide show
  1. package/bin/binaries/probe-v0.6.0-rc291-aarch64-apple-darwin.tar.gz +0 -0
  2. package/bin/binaries/probe-v0.6.0-rc291-aarch64-unknown-linux-musl.tar.gz +0 -0
  3. package/bin/binaries/probe-v0.6.0-rc291-x86_64-apple-darwin.tar.gz +0 -0
  4. package/bin/binaries/probe-v0.6.0-rc291-x86_64-pc-windows-msvc.zip +0 -0
  5. package/bin/binaries/probe-v0.6.0-rc291-x86_64-unknown-linux-musl.tar.gz +0 -0
  6. package/build/agent/ProbeAgent.js +95 -14
  7. package/build/agent/index.js +401 -86261
  8. package/build/agent/shared/prompts.js +27 -6
  9. package/build/extract.js +4 -2
  10. package/build/mcp/index.js +122 -9
  11. package/build/mcp/index.ts +162 -17
  12. package/build/search.js +6 -5
  13. package/build/tools/vercel.js +56 -23
  14. package/build/utils/error-types.js +2 -2
  15. package/build/utils/path-validation.js +1 -1
  16. package/cjs/agent/ProbeAgent.cjs +193 -45
  17. package/cjs/index.cjs +193 -45
  18. package/package.json +2 -1
  19. package/src/agent/ProbeAgent.js +95 -14
  20. package/src/agent/shared/prompts.js +27 -6
  21. package/src/extract.js +4 -2
  22. package/src/mcp/index.ts +162 -17
  23. package/src/search.js +6 -5
  24. package/src/tools/vercel.js +56 -23
  25. package/src/utils/error-types.js +2 -2
  26. package/src/utils/path-validation.js +1 -1
  27. package/bin/binaries/probe-v0.6.0-rc288-aarch64-apple-darwin.tar.gz +0 -0
  28. package/bin/binaries/probe-v0.6.0-rc288-aarch64-unknown-linux-musl.tar.gz +0 -0
  29. package/bin/binaries/probe-v0.6.0-rc288-x86_64-apple-darwin.tar.gz +0 -0
  30. package/bin/binaries/probe-v0.6.0-rc288-x86_64-pc-windows-msvc.zip +0 -0
  31. package/bin/binaries/probe-v0.6.0-rc288-x86_64-unknown-linux-musl.tar.gz +0 -0
@@ -1444,11 +1444,13 @@ export class ProbeAgent {
1444
1444
  result = await this._executeWithVercelProvider(options, controller);
1445
1445
  }
1446
1446
 
1447
- // Wrap textStream so limiter slot is held until stream completes
1447
+ // Wrap textStream so limiter slot is held until stream completes.
1448
+ // result.textStream is a read-only getter on DefaultStreamTextResult,
1449
+ // so we wrap the result in a Proxy that intercepts the textStream property.
1448
1450
  if (limiter && result.textStream) {
1449
1451
  const originalStream = result.textStream;
1450
1452
  const debug = this.debug;
1451
- result.textStream = (async function* () {
1453
+ const wrappedStream = (async function* () {
1452
1454
  try {
1453
1455
  for await (const chunk of originalStream) {
1454
1456
  yield chunk;
@@ -1461,6 +1463,13 @@ export class ProbeAgent {
1461
1463
  }
1462
1464
  }
1463
1465
  })();
1466
+ return new Proxy(result, {
1467
+ get(target, prop) {
1468
+ if (prop === 'textStream') return wrappedStream;
1469
+ const value = target[prop];
1470
+ return typeof value === 'function' ? value.bind(target) : value;
1471
+ }
1472
+ });
1464
1473
  } else if (limiter) {
1465
1474
  // No textStream (shouldn't happen, but release just in case)
1466
1475
  limiter.release(null);
@@ -2976,9 +2985,9 @@ ${extractGuidance2}
2976
2985
  Follow these instructions carefully:
2977
2986
  1. Analyze the user's request.
2978
2987
  2. Use the available tools step-by-step to fulfill the request.
2979
- 3. You should always prefer the search tool for code-related questions.${this.searchDelegate ? ' Ask natural language questions — the search subagent handles keyword formulation and returns extracted code blocks. Use extract only to expand context or read full files.' : ' Search handles stemming and case variations automatically — do NOT try keyword variations manually. Read full files only if really necessary.'}
2980
- 4. Ensure to get really deep and understand the full picture before answering.
2981
- 5. Once the task is fully completed, provide your final answer directly as text.
2988
+ 3. You MUST use the search tool before answering ANY code-related question. NEVER answer from memory or general knowledge — your answers must be grounded in actual code found via search/extract.${this.searchDelegate ? ' Ask natural language questions — the search subagent handles keyword formulation and returns extracted code blocks. Use extract only to expand context or read full files.' : ' Search handles stemming and case variations automatically — do NOT try keyword variations manually. Read full files only if really necessary.'}
2989
+ 4. Ensure to get really deep and understand the full picture before answering. Follow call chains — if function A calls B, search for B too. Look for related subsystems (e.g., if asked about rate limiting, also check for quota, throttling, smoothing).
2990
+ 5. Once the task is fully completed, provide your final answer directly as text. Always cite specific files and line numbers as evidence. Do NOT output planning or thinking text — go straight to the answer.
2982
2991
  6. ${this.searchDelegate ? 'Ask clear, specific questions when searching. Each search should target a distinct concept or question.' : 'Prefer concise and focused search queries. Use specific keywords and phrases to narrow down results.'}
2983
2992
  7. NEVER use bash for code exploration (no grep, cat, find, head, tail, awk, sed) — always use search and extract tools instead. Bash is only for system operations like building, running tests, or git commands.${this.allowEdit ? `
2984
2993
  7. When modifying files, choose the appropriate tool:
@@ -3483,6 +3492,25 @@ Follow these instructions carefully:
3483
3492
  if (recentTexts.every(t => detectStuckResponse(t))) return true;
3484
3493
  }
3485
3494
 
3495
+ // Circuit breaker: repeated identical tool calls (e.g. model ignores dedup message)
3496
+ if (steps.length >= 3) {
3497
+ const last3 = steps.slice(-3);
3498
+ const allHaveTools = last3.every(s => s.toolCalls?.length === 1);
3499
+ if (allHaveTools) {
3500
+ const signatures = last3.map(s => {
3501
+ const tc = s.toolCalls[0];
3502
+ return `${tc.toolName}::${JSON.stringify(tc.args ?? tc.input)}`;
3503
+ });
3504
+ if (signatures[0] === signatures[1] && signatures[1] === signatures[2]) {
3505
+ if (this.debug) {
3506
+ console.log(`[DEBUG] Circuit breaker: 3 consecutive identical tool calls detected (${last3[0].toolCalls[0].toolName}), forcing stop`);
3507
+ }
3508
+ return true;
3509
+ }
3510
+ }
3511
+
3512
+ }
3513
+
3486
3514
  return false;
3487
3515
  },
3488
3516
  prepareStep: ({ steps, stepNumber }) => {
@@ -3493,6 +3521,42 @@ Follow these instructions carefully:
3493
3521
  };
3494
3522
  }
3495
3523
 
3524
+ // Force text-only response after 2 consecutive identical tool calls
3525
+ if (steps.length >= 2) {
3526
+ const last2 = steps.slice(-2);
3527
+ if (last2.every(s => s.toolCalls?.length === 1)) {
3528
+ const tc1 = last2[0].toolCalls[0];
3529
+ const tc2 = last2[1].toolCalls[0];
3530
+ const sig1 = `${tc1.toolName}::${JSON.stringify(tc1.args ?? tc1.input)}`;
3531
+ const sig2 = `${tc2.toolName}::${JSON.stringify(tc2.args ?? tc2.input)}`;
3532
+ if (sig1 === sig2) {
3533
+ if (this.debug) {
3534
+ console.log(`[DEBUG] prepareStep: 2 consecutive identical tool calls (${tc1.toolName}), forcing toolChoice=none`);
3535
+ console.log(`[DEBUG] sig: ${sig1.substring(0, 200)}`);
3536
+ }
3537
+ return { toolChoice: 'none' };
3538
+ }
3539
+ }
3540
+ }
3541
+
3542
+ // Force text-only response after 3 consecutive tool errors
3543
+ // (e.g. workspace deleted mid-run — let the model produce its answer)
3544
+ if (steps.length >= 3) {
3545
+ const last3 = steps.slice(-3);
3546
+ const allErrors = last3.every(s =>
3547
+ s.toolResults?.length > 0 && s.toolResults.every(tr => {
3548
+ const r = typeof tr.result === 'string' ? tr.result : '';
3549
+ return r.includes('<error ') || r.includes('does not exist');
3550
+ })
3551
+ );
3552
+ if (allErrors) {
3553
+ if (this.debug) {
3554
+ console.log(`[DEBUG] prepareStep: 3 consecutive tool errors, forcing toolChoice=none`);
3555
+ }
3556
+ return { toolChoice: 'none' };
3557
+ }
3558
+ }
3559
+
3496
3560
  const lastStep = steps[steps.length - 1];
3497
3561
  const modelJustStopped = lastStep?.finishReason === 'stop'
3498
3562
  && (!lastStep?.toolCalls || lastStep.toolCalls.length === 0);
@@ -3529,10 +3593,12 @@ Here is the result to review:
3529
3593
  ${resultToReview}
3530
3594
  </result>
3531
3595
 
3532
- Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. If something needs to be fixed or is missing, do it now, then respond with the COMPLETE updated answer (everything you did in total, not just the fix).`;
3596
+ IMPORTANT: First review ALL completed work in the conversation above before taking any action.
3597
+ Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. If your text has inaccuracies, fix the text. Only call a tool if you find a genuinely MISSING action — NEVER redo work that was already completed successfully. Respond with the COMPLETE corrected answer.`;
3533
3598
 
3534
3599
  return {
3535
- userMessage: completionPromptMessage
3600
+ userMessage: completionPromptMessage,
3601
+ toolChoice: 'none' // Force text-only review — no tool calls
3536
3602
  };
3537
3603
  }
3538
3604
  }
@@ -3585,7 +3651,13 @@ Double-check your response based on the criteria above. If everything looks good
3585
3651
  }
3586
3652
 
3587
3653
  if (this.debug) {
3588
- console.log(`[DEBUG] Step ${currentIteration}/${maxIterations} finished (reason: ${finishReason}, tools: ${toolResults?.length || 0})`);
3654
+ const toolSummary = toolCalls?.length
3655
+ ? toolCalls.map(tc => {
3656
+ const args = tc.args ? JSON.stringify(tc.args) : '';
3657
+ return args ? `${tc.toolName}(${debugTruncate(args, 120)})` : tc.toolName;
3658
+ }).join(', ')
3659
+ : 'none';
3660
+ console.log(`[DEBUG] Step ${currentIteration}/${maxIterations} finished (reason: ${finishReason}, tools: [${toolSummary}])`);
3589
3661
  if (text) {
3590
3662
  console.log(`[DEBUG] model text: ${debugTruncate(text)}`);
3591
3663
  }
@@ -3627,11 +3699,20 @@ Double-check your response based on the criteria above. If everything looks good
3627
3699
  const executeAIRequest = async () => {
3628
3700
  const result = await this.streamTextWithRetryAndFallback(streamOptions);
3629
3701
 
3630
- // Collect the final text
3631
- const finalText = await result.text;
3702
+ // Use only the last step's text as the final answer.
3703
+ // result.text concatenates ALL steps (including intermediate planning text),
3704
+ // but the user should only see the final answer from the last step.
3705
+ const steps = await result.steps;
3706
+ let finalText;
3707
+ if (steps && steps.length > 1) {
3708
+ // Multi-step: use last step's text (the actual answer after tool calls)
3709
+ const lastStepText = steps[steps.length - 1].text;
3710
+ finalText = lastStepText || await result.text;
3711
+ } else {
3712
+ finalText = await result.text;
3713
+ }
3632
3714
 
3633
3715
  if (this.debug) {
3634
- const steps = await result.steps;
3635
3716
  console.log(`[DEBUG] streamText completed: ${steps?.length || 0} steps, finalText=${finalText?.length || 0} chars`);
3636
3717
  }
3637
3718
 
@@ -3722,16 +3803,16 @@ Here is the result to review:
3722
3803
  ${finalResult}
3723
3804
  </result>
3724
3805
 
3725
- Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. If something needs to be fixed or is missing, do it now, then respond with the COMPLETE updated answer (everything you did in total, not just the fix).`;
3806
+ IMPORTANT: First review ALL completed work in the conversation above before taking any action.
3807
+ Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. If your text has inaccuracies, fix the text. Only call a tool if you find a genuinely MISSING action — NEVER redo work that was already completed successfully. Respond with the COMPLETE corrected answer.`;
3726
3808
 
3727
3809
  currentMessages.push({ role: 'user', content: completionPromptMessage });
3728
3810
 
3729
- const completionMaxIterations = 5;
3730
3811
  const completionStreamOptions = {
3731
3812
  model: this.provider ? this.provider(this.model) : this.model,
3732
3813
  messages: this.prepareMessagesWithImages(currentMessages),
3733
3814
  tools,
3734
- stopWhen: stepCountIs(completionMaxIterations),
3815
+ toolChoice: 'none', // Force text-only response — no tool calls during review
3735
3816
  maxTokens: maxResponseTokens,
3736
3817
  temperature: 0.3,
3737
3818
  onStepFinish: ({ toolResults, text, finishReason, usage }) => {
@@ -8,27 +8,47 @@ export const predefinedPrompts = {
8
8
  CRITICAL - You are READ-ONLY:
9
9
  You must NEVER create, modify, delete, or write files. You are strictly an exploration and analysis tool. If asked to make changes, implement features, fix bugs, or modify a PR, refuse and explain that file modifications must be done by the engineer tool — your role is only to investigate code and answer questions. Do not attempt workarounds using bash commands (echo, cat, tee, sed, etc.) to write files.
10
10
 
11
+ CRITICAL - ALWAYS search before answering:
12
+ You must NEVER answer questions about the codebase from memory or general knowledge. ALWAYS use the search and extract tools first to find the actual code, then base your answer ONLY on what you found. Even if you think you know the answer, you MUST verify it against the actual code. Your answers must be grounded in code evidence, not assumptions.
13
+
11
14
  When exploring code:
12
15
  - Provide clear, concise explanations based on user request
13
16
  - Find and highlight the most relevant code snippets, if required
14
- - Trace function calls and data flow through the system
17
+ - Trace function calls and data flow through the system — follow the FULL call chain, not just the entry point
15
18
  - Try to understand the user's intent and provide relevant information
16
19
  - Understand high level picture
17
20
  - Balance detail with clarity in your explanations
21
+ - Search using SYNONYMS and alternative terms — code naming often differs from the concept name (e.g., "authentication" might be named verify_credentials, check_token, validate_session)
22
+ - When you find a key function, look at what it CALLS and what CALLS it to discover the complete picture
23
+ - Before answering, ask yourself: "Did I cover all the major components? Are there related subsystems I missed?" If yes, do one more search round.
18
24
 
19
25
  When providing answers:
26
+ - Be EXHAUSTIVE: cover ALL components you discovered, not just the main ones. If you found 10 related files, discuss all 10, not just the top 3. Users want the complete picture.
27
+ - After drafting your answer, do a self-check: "What did I find in my searches that I haven't mentioned yet?" Add any missing components.
28
+ - Include data structures, configuration options, and error handling — not just the happy path.
20
29
  - Always include a "References" section at the end of your response
21
30
  - List all relevant source code locations you found during exploration
22
31
  - Use the format: file_path:line_number or file_path#symbol_name
23
32
  - Group references by file when multiple locations are from the same file
24
33
  - Include brief descriptions of what each reference contains`,
25
34
 
26
- 'code-searcher': `You are ProbeChat Code Searcher, a specialized AI assistant focused ONLY on locating relevant code. Your sole job is to find and return ALL relevant code locations. Do NOT answer questions or explain anything.
35
+ 'code-searcher': `You are ProbeChat Code Explorer & Searcher. Your job is to EXPLORE the codebase to find ALL relevant code locations for the query, then return them as JSON targets.
36
+
37
+ You think like a code explorer — you understand that codebases have layers:
38
+ - Core implementations (algorithms, data structures)
39
+ - Middleware/integration layers (request handlers, interceptors)
40
+ - Configuration and storage backends
41
+ - Scoping mechanisms (per-user, per-org, per-tenant, global)
42
+ - Supporting utilities and helpers
27
43
 
28
44
  When searching:
29
- - Use only the search tool
30
- - Run additional searches only if needed to capture all relevant locations
31
- - Prefer specific, focused queries
45
+ - Search for the MAIN concept first, then think: "what RELATED subsystems would a real codebase have?"
46
+ - Use extract to READ the code you find — look for function calls, type references, and imports that point to OTHER relevant code
47
+ - If you find middleware, check: are there org-level or tenant-level variants?
48
+ - If you find algorithms, check: are there different storage backends?
49
+ - Search results are paginated — if results look relevant, call nextPage=true to check for more files
50
+ - Stop paginating when results become irrelevant or you see "All results retrieved"
51
+ - Search using SYNONYMS — code naming differs from concepts (e.g., "rate limiting" → throttle, quota, limiter, bucket)
32
52
 
33
53
  Output format (MANDATORY):
34
54
  - Return ONLY valid JSON with a single top-level key: "targets"
@@ -38,7 +58,8 @@ Output format (MANDATORY):
38
58
  - "path/to/file.ext:line"
39
59
  - "path/to/file.ext:start-end"
40
60
  - Prefer #SymbolName when a function/class name is clear; otherwise use line numbers
41
- - Deduplicate targets and keep them concise`,
61
+ - Deduplicate targets and keep them concise
62
+ - Aim for 5-15 targets covering ALL aspects of the query`,
42
63
 
43
64
  'architect': `You are ProbeChat Architect, a specialized AI assistant focused on software architecture and design. Your primary function is to help users understand, analyze, and design software systems using the provided code analysis tools.
44
65
 
package/src/extract.js CHANGED
@@ -18,7 +18,8 @@ const EXTRACT_FLAG_MAP = {
18
18
  allowTests: '--allow-tests',
19
19
  contextLines: '--context',
20
20
  format: '--format',
21
- inputFile: '--input-file'
21
+ inputFile: '--input-file',
22
+ lsp: '--lsp'
22
23
  };
23
24
 
24
25
  /**
@@ -31,7 +32,8 @@ const EXTRACT_FLAG_MAP = {
31
32
  * @param {string} [options.cwd] - Working directory for resolving relative file paths
32
33
  * @param {boolean} [options.allowTests] - Include test files
33
34
  * @param {number} [options.contextLines] - Number of context lines to include
34
- * @param {string} [options.format] - Output format ('markdown', 'plain', 'json', 'xml', 'color', 'outline-xml', 'outline-diff')
35
+ * @param {string} [options.format] - Output format ('markdown', 'plain', 'json')
36
+ * @param {boolean} [options.lsp] - Use LSP (Language Server Protocol) for call hierarchy and reference graphs
35
37
  * @param {Object} [options.binaryOptions] - Options for getting the binary
36
38
  * @param {boolean} [options.binaryOptions.forceDownload] - Force download even if binary exists
37
39
  * @param {string} [options.binaryOptions.version] - Specific version to download
package/src/mcp/index.ts CHANGED
@@ -21,11 +21,13 @@ import { fileURLToPath } from 'url';
21
21
  // Import from parent package
22
22
  import { search, query, extract, grep, getBinaryPath, setBinaryPath } from '../index.js';
23
23
 
24
+ type OutputFormat = 'outline' | 'outline-xml' | 'json';
25
+
24
26
  // Parse command-line arguments
25
- function parseArgs(): { timeout?: number; format?: string } {
27
+ function parseArgs(): { timeout?: number; lsp?: boolean; format?: OutputFormat } {
26
28
  const args = process.argv.slice(2);
27
- const config: { timeout?: number; format?: string } = {};
28
-
29
+ const config: { timeout?: number; lsp?: boolean; format?: OutputFormat } = {};
30
+
29
31
  for (let i = 0; i < args.length; i++) {
30
32
  if ((args[i] === '--timeout' || args[i] === '-t') && i + 1 < args.length) {
31
33
  const timeout = parseInt(args[i + 1], 10);
@@ -36,9 +38,17 @@ function parseArgs(): { timeout?: number; format?: string } {
36
38
  console.error(`Invalid timeout value: ${args[i + 1]}. Using default.`);
37
39
  }
38
40
  i++; // Skip the next argument
41
+ } else if (args[i] === '--lsp') {
42
+ config.lsp = true;
43
+ console.error('LSP mode enabled');
39
44
  } else if (args[i] === '--format' && i + 1 < args.length) {
40
- config.format = args[i + 1];
41
- console.error(`Format set to ${config.format}`);
45
+ const format = args[i + 1] as OutputFormat;
46
+ if (format === 'outline' || format === 'outline-xml' || format === 'json') {
47
+ config.format = format;
48
+ console.error(`Output format set to ${format}`);
49
+ } else {
50
+ console.error(`Invalid format value: ${args[i + 1]}. Using default.`);
51
+ }
42
52
  i++; // Skip the next argument
43
53
  } else if (args[i] === '--help' || args[i] === '-h') {
44
54
  console.error(`
@@ -49,7 +59,9 @@ Usage:
49
59
 
50
60
  Options:
51
61
  --timeout, -t <seconds> Set timeout for search operations (default: 30)
52
- --format <format> Set output format (default: outline)
62
+ --lsp Enable LSP (Language Server Protocol) for enhanced features
63
+ Automatically initializes language servers for the current workspace
64
+ --format <format> Output format for search responses (outline|outline-xml|json)
53
65
  --help, -h Show this help message
54
66
  `);
55
67
  process.exit(0);
@@ -120,17 +132,37 @@ interface SearchCodeArgs {
120
132
  exact?: boolean;
121
133
  strictElasticSyntax?: boolean;
122
134
  session?: string;
123
- nextPage?: boolean;
135
+ timeout?: number;
136
+ noGitignore?: boolean;
137
+ lsp?: boolean;
138
+ }
139
+
140
+ interface QueryCodeArgs {
141
+ path: string;
142
+ pattern: string;
143
+ language?: string;
144
+ ignore?: string[];
145
+ allowTests?: boolean;
146
+ maxResults?: number;
147
+ format?: 'markdown' | 'plain' | 'json' | 'color';
148
+ timeout?: number;
149
+ noGitignore?: boolean;
124
150
  }
125
151
 
126
152
  interface ExtractCodeArgs {
127
153
  path: string;
128
154
  files: string[];
155
+ allowTests?: boolean;
156
+ contextLines?: number;
157
+ format?: 'markdown' | 'plain' | 'json';
158
+ timeout?: number;
159
+ noGitignore?: boolean;
160
+ lsp?: boolean;
129
161
  }
130
162
 
131
163
  interface GrepArgs {
132
164
  pattern: string;
133
- paths: string | string[];
165
+ paths: string[];
134
166
  ignoreCase?: boolean;
135
167
  count?: boolean;
136
168
  context?: number;
@@ -139,11 +171,17 @@ interface GrepArgs {
139
171
  class ProbeServer {
140
172
  private server: Server;
141
173
  private defaultTimeout: number;
142
- private defaultFormat?: string;
143
-
144
- constructor(timeout: number = 30, format?: string) {
174
+ private lspEnabled: boolean;
175
+ private defaultFormat: OutputFormat;
176
+
177
+ constructor(
178
+ timeout: number = 30,
179
+ lspEnabled: boolean = false,
180
+ defaultFormat: OutputFormat = 'outline-xml'
181
+ ) {
145
182
  this.defaultTimeout = timeout;
146
- this.defaultFormat = format;
183
+ this.lspEnabled = lspEnabled;
184
+ this.defaultFormat = defaultFormat;
147
185
  this.server = new Server(
148
186
  {
149
187
  name: '@probelabs/probe',
@@ -201,8 +239,11 @@ class ProbeServer {
201
239
  },
202
240
  nextPage: {
203
241
  type: 'boolean',
204
- description: 'Set to true when requesting the next page of results. Requires passing the same session ID from the previous search output.',
205
- default: false
242
+ description: 'Skip .gitignore files (will use PROBE_NO_GITIGNORE environment variable if not set)',
243
+ },
244
+ lsp: {
245
+ type: 'boolean',
246
+ description: 'Use LSP (Language Server Protocol) for call hierarchy, reference counts, and enhanced symbol information',
206
247
  }
207
248
  },
208
249
  required: ['path', 'query']
@@ -221,7 +262,34 @@ class ProbeServer {
221
262
  files: {
222
263
  type: 'array',
223
264
  items: { type: 'string' },
224
- description: 'Array of file paths to extract from. Formats: "file.js" (entire file), "file.js:42" (code block at line 42), "file.js:10-20" (lines 10-20), "file.js#funcName" (specific symbol). Line numbers and symbols are part of the path string, not separate parameters. Paths can be absolute or relative to the project directory.',
265
+ description: 'Files and lines or sybmbols to extract from: /path/to/file.rs:10, /path/to/file.rs#func_name Path should be absolute.',
266
+ },
267
+ allowTests: {
268
+ type: 'boolean',
269
+ description: 'Allow test files and test code blocks in results (disabled by default)',
270
+ },
271
+ contextLines: {
272
+ type: 'number',
273
+ description: 'Number of context lines to include before and after the extracted block when AST parsing fails to find a suitable node',
274
+ default: 0
275
+ },
276
+ format: {
277
+ type: 'string',
278
+ enum: ['markdown', 'plain', 'json'],
279
+ description: 'Output format for the extracted code',
280
+ default: 'markdown'
281
+ },
282
+ timeout: {
283
+ type: 'number',
284
+ description: 'Timeout for the extract operation in seconds (default: 30)',
285
+ },
286
+ noGitignore: {
287
+ type: 'boolean',
288
+ description: 'Skip .gitignore files (will use PROBE_NO_GITIGNORE environment variable if not set)',
289
+ },
290
+ lsp: {
291
+ type: 'boolean',
292
+ description: 'Use LSP (Language Server Protocol) for call hierarchy, reference counts, and enhanced symbol information',
225
293
  }
226
294
  },
227
295
  required: ['path', 'files'],
@@ -365,7 +433,24 @@ class ProbeServer {
365
433
  } else if (this.defaultFormat === 'json') {
366
434
  options.json = true;
367
435
  }
368
-
436
+ if (args.session !== undefined && args.session.trim() !== '') {
437
+ options.session = args.session;
438
+ } else {
439
+ options.session = "new";
440
+ }
441
+ // Use timeout from args, or fall back to instance default
442
+ if (args.timeout !== undefined) {
443
+ options.timeout = args.timeout;
444
+ } else if (this.defaultTimeout !== undefined) {
445
+ options.timeout = this.defaultTimeout;
446
+ }
447
+ // Pass LSP flag if enabled globally or per-request
448
+ if (args.lsp !== undefined) {
449
+ options.lsp = args.lsp;
450
+ } else if (this.lspEnabled) {
451
+ options.lsp = true;
452
+ }
453
+
369
454
  console.error("Executing search with options:", JSON.stringify(options, null, 2));
370
455
 
371
456
  try {
@@ -405,6 +490,19 @@ class ProbeServer {
405
490
  allowTests: true, // Include test files by default
406
491
  };
407
492
 
493
+ // Use noGitignore from args, or fall back to PROBE_NO_GITIGNORE environment variable
494
+ if (args.noGitignore !== undefined) {
495
+ options.noGitignore = args.noGitignore;
496
+ } else if (process.env.PROBE_NO_GITIGNORE) {
497
+ options.noGitignore = process.env.PROBE_NO_GITIGNORE === 'true';
498
+ }
499
+ // Pass LSP flag if enabled globally or per-request
500
+ if (args.lsp !== undefined) {
501
+ options.lsp = args.lsp;
502
+ } else if (this.lspEnabled) {
503
+ options.lsp = true;
504
+ }
505
+
408
506
  // Call extract with the complete options object
409
507
  try {
410
508
  // Track request size for token usage
@@ -507,6 +605,48 @@ class ProbeServer {
507
605
  // The @probelabs/probe package now handles binary path management internally
508
606
  // We don't need to verify or download the binary in the MCP server anymore
509
607
 
608
+ // Initialize LSP servers for the current workspace if --lsp flag is enabled
609
+ if (this.lspEnabled) {
610
+ const workspaceRoot = process.cwd();
611
+ console.error(`Initializing LSP servers for workspace: ${workspaceRoot}`);
612
+
613
+ try {
614
+ // Execute probe lsp init command to pre-warm language servers
615
+ // Use recursive flag to discover nested projects in monorepos
616
+ const initCmd = process.platform === 'win32'
617
+ ? `probe lsp init -w "${workspaceRoot}" --recursive`
618
+ : `probe lsp init -w '${workspaceRoot}' --recursive`;
619
+
620
+ const { stdout, stderr } = await execAsync(initCmd, {
621
+ timeout: 10000, // 10 second timeout for initialization - don't wait too long
622
+ env: { ...process.env }
623
+ });
624
+
625
+ if (stderr && !stderr.includes('Successfully initialized')) {
626
+ console.error(`LSP initialization warnings: ${stderr}`);
627
+ }
628
+
629
+ console.error(`LSP servers initialized successfully for workspace: ${workspaceRoot}`);
630
+
631
+ // Parse initialization output to show what was initialized
632
+ if (stdout) {
633
+ const lines = stdout.split('\n');
634
+ const initializedServers = lines.filter(line =>
635
+ line.includes('✓') || line.includes('language server')
636
+ );
637
+ if (initializedServers.length > 0) {
638
+ console.error('Initialized language servers:');
639
+ initializedServers.forEach(line => console.error(` ${line.trim()}`));
640
+ }
641
+ }
642
+ } catch (error: any) {
643
+ // Don't fail MCP server startup if LSP initialization fails
644
+ // LSP will still work with cold start on first use
645
+ console.error(`Warning: Failed to initialize LSP servers: ${error.message || error}`);
646
+ console.error('LSP features will still be available but may have slower first-use performance');
647
+ }
648
+ }
649
+
510
650
  // Just connect the server to the transport
511
651
  const transport = new StdioServerTransport();
512
652
  await this.server.connect(transport);
@@ -514,5 +654,10 @@ class ProbeServer {
514
654
  }
515
655
  }
516
656
 
517
- const server = new ProbeServer(cliConfig.timeout, cliConfig.format || 'outline');
657
+ // Instantiate server with (timeout, lspEnabled, format)
658
+ const server = new ProbeServer(
659
+ cliConfig.timeout ?? 30,
660
+ cliConfig.lsp ?? false,
661
+ cliConfig.format || 'outline-xml'
662
+ );
518
663
  server.run().catch(console.error);
package/src/search.js CHANGED
@@ -32,7 +32,8 @@ const SEARCH_FLAG_MAP = {
32
32
  session: '--session',
33
33
  timeout: '--timeout',
34
34
  language: '--language',
35
- format: '--format'
35
+ format: '--format',
36
+ lsp: '--lsp'
36
37
  };
37
38
 
38
39
  /**
@@ -58,7 +59,7 @@ const SEARCH_FLAG_MAP = {
58
59
  * @param {string} [options.session] - Session ID for caching results
59
60
  * @param {number} [options.timeout] - Timeout in seconds (default: 30)
60
61
  * @param {string} [options.language] - Limit search to files of a specific programming language
61
- * @param {string} [options.format] - Output format ('json', 'outline-xml', etc.)
62
+ * @param {boolean} [options.lsp] - Use LSP (Language Server Protocol) for enhanced symbol information
62
63
  * @param {Object} [options.binaryOptions] - Options for getting the binary
63
64
  * @param {boolean} [options.binaryOptions.forceDownload] - Force download even if binary exists
64
65
  * @param {string} [options.binaryOptions.version] - Specific version to download
@@ -85,8 +86,8 @@ export async function search(options) {
85
86
  if (options.json && !options.format) {
86
87
  cliArgs.push('--format', 'json');
87
88
  } else if (options.format) {
88
- // Format is already handled by buildCliArgs through SEARCH_FLAG_MAP
89
- // but we need to ensure json parsing for json format
89
+ // Format is handled by buildCliArgs through SEARCH_FLAG_MAP.
90
+ // Ensure json parsing is enabled for json format.
90
91
  if (options.format === 'json') {
91
92
  options.json = true;
92
93
  }
@@ -257,4 +258,4 @@ export async function search(options) {
257
258
  };
258
259
  throw structuredError;
259
260
  }
260
- }
261
+ }