@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.
- package/bin/binaries/probe-v0.6.0-rc291-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc291-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc291-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc291-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc291-x86_64-unknown-linux-musl.tar.gz +0 -0
- package/build/agent/ProbeAgent.js +95 -14
- package/build/agent/index.js +401 -86261
- package/build/agent/shared/prompts.js +27 -6
- package/build/extract.js +4 -2
- package/build/mcp/index.js +122 -9
- package/build/mcp/index.ts +162 -17
- package/build/search.js +6 -5
- package/build/tools/vercel.js +56 -23
- package/build/utils/error-types.js +2 -2
- package/build/utils/path-validation.js +1 -1
- package/cjs/agent/ProbeAgent.cjs +193 -45
- package/cjs/index.cjs +193 -45
- package/package.json +2 -1
- package/src/agent/ProbeAgent.js +95 -14
- package/src/agent/shared/prompts.js +27 -6
- package/src/extract.js +4 -2
- package/src/mcp/index.ts +162 -17
- package/src/search.js +6 -5
- package/src/tools/vercel.js +56 -23
- package/src/utils/error-types.js +2 -2
- package/src/utils/path-validation.js +1 -1
- package/bin/binaries/probe-v0.6.0-rc288-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc288-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc288-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc288-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc288-x86_64-unknown-linux-musl.tar.gz +0 -0
package/src/agent/ProbeAgent.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
3631
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
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'
|
|
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?:
|
|
27
|
+
function parseArgs(): { timeout?: number; lsp?: boolean; format?: OutputFormat } {
|
|
26
28
|
const args = process.argv.slice(2);
|
|
27
|
-
const config: { timeout?: number; format?:
|
|
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
|
-
|
|
41
|
-
|
|
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
|
-
--
|
|
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
|
-
|
|
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
|
|
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
|
|
143
|
-
|
|
144
|
-
|
|
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.
|
|
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: '
|
|
205
|
-
|
|
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: '
|
|
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
|
-
|
|
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 {
|
|
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
|
|
89
|
-
//
|
|
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
|
+
}
|