@probelabs/probe 0.6.0-rc288 → 0.6.0-rc290
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-rc290-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc290-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc290-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc290-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc290-x86_64-unknown-linux-musl.tar.gz +0 -0
- package/build/agent/ProbeAgent.js +61 -10
- 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 +51 -22
- package/cjs/agent/ProbeAgent.cjs +131 -38
- package/cjs/index.cjs +131 -38
- package/package.json +2 -1
- package/src/agent/ProbeAgent.js +61 -10
- 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 +51 -22
- 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/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
|
+
}
|
package/src/tools/vercel.js
CHANGED
|
@@ -69,12 +69,12 @@ function autoQuoteSearchTerms(query) {
|
|
|
69
69
|
if (token.startsWith('"')) return token;
|
|
70
70
|
// Boolean operator
|
|
71
71
|
if (operators.has(token)) return token;
|
|
72
|
-
// Check if token needs quoting: has
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
// Check if token needs quoting: has camelCase/PascalCase transitions or underscores
|
|
73
|
+
// Simple capitalized words like "Redis" or "Limiter" should NOT be quoted —
|
|
74
|
+
// only quote when there's an actual case transition (e.g., "getUserData", "NewSlidingLog")
|
|
75
75
|
const hasUnderscore = token.includes('_');
|
|
76
|
-
const
|
|
77
|
-
if (
|
|
76
|
+
const hasCaseTransition = /[a-z][A-Z]/.test(token) || /[A-Z]{2,}[a-z]/.test(token);
|
|
77
|
+
if (hasCaseTransition || hasUnderscore) {
|
|
78
78
|
return `"${token}"`;
|
|
79
79
|
}
|
|
80
80
|
return token;
|
|
@@ -237,7 +237,7 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
237
237
|
'Break down complex queries into multiple searches to cover all aspects.',
|
|
238
238
|
'',
|
|
239
239
|
'Available tools:',
|
|
240
|
-
'- search: Find code matching keywords or patterns. Run multiple searches for different aspects
|
|
240
|
+
'- search: Find code matching keywords or patterns. Results are paginated — use nextPage=true when results are relevant to get more. Run multiple searches for different aspects.',
|
|
241
241
|
'- extract: Verify code snippets to ensure targets are actually relevant before including them.',
|
|
242
242
|
'- listFiles: Understand directory structure to find where relevant code might live.',
|
|
243
243
|
'',
|
|
@@ -258,13 +258,14 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
258
258
|
'',
|
|
259
259
|
'Combining searches with OR:',
|
|
260
260
|
'- Multiple unquoted words use OR logic: rate limit matches files containing EITHER "rate" OR "limit".',
|
|
261
|
-
'-
|
|
261
|
+
'- IMPORTANT: Multiple quoted terms use AND logic by default: \'"RateLimit" "middleware"\' requires BOTH in the same file.',
|
|
262
|
+
'- To search for ANY of several quoted symbols, use the explicit OR operator: \'"ForwardMessage" OR "SessionLimiter"\'.',
|
|
262
263
|
'- Without quotes, camelCase like limitDRL gets split into "limit" + "DRL" — not what you want for symbol lookup.',
|
|
263
264
|
'- Use OR to search for multiple related symbols in ONE search instead of separate searches.',
|
|
264
265
|
'- This is much faster than running separate searches sequentially.',
|
|
265
|
-
'- Example: search \'"ForwardMessage" "SessionLimiter"\' finds files with either exact symbol in one call.',
|
|
266
|
-
'- Example: search \'"limitDRL" "doRollingWindowWrite"\' finds both rate limiting functions at once.',
|
|
267
|
-
'- Use AND
|
|
266
|
+
'- Example: search \'"ForwardMessage" OR "SessionLimiter"\' finds files with either exact symbol in one call.',
|
|
267
|
+
'- Example: search \'"limitDRL" OR "doRollingWindowWrite"\' finds both rate limiting functions at once.',
|
|
268
|
+
'- Use AND (or just put quoted terms together) when you need both terms in the same file.',
|
|
268
269
|
'',
|
|
269
270
|
'Parallel tool calls:',
|
|
270
271
|
'- When you need to search for INDEPENDENT concepts, call multiple search tools IN PARALLEL (same response).',
|
|
@@ -278,10 +279,10 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
278
279
|
' Query: "Find the IP allowlist middleware"',
|
|
279
280
|
' → search "allowlist middleware" (one search, probe handles IP/ip/Ip variations)',
|
|
280
281
|
' Query: "Find ForwardMessage and SessionLimiter"',
|
|
281
|
-
' → search \'"ForwardMessage" "SessionLimiter"\' (one OR search finds both exact symbols)',
|
|
282
|
+
' → search \'"ForwardMessage" OR "SessionLimiter"\' (one OR search finds both exact symbols)',
|
|
282
283
|
' OR: search exact=true "ForwardMessage" + search exact=true "SessionLimiter" IN PARALLEL',
|
|
283
284
|
' Query: "Find limitDRL and limitRedis functions"',
|
|
284
|
-
' → search \'"limitDRL" "limitRedis"\' (one OR search, quoted to prevent camelCase splitting)',
|
|
285
|
+
' → search \'"limitDRL" OR "limitRedis"\' (one OR search, quoted to prevent camelCase splitting)',
|
|
285
286
|
' Query: "Find ThrottleRetryLimit usage"',
|
|
286
287
|
' → search exact=true "ThrottleRetryLimit" (one search, if no results the symbol does not exist — stop)',
|
|
287
288
|
' Query: "How does BM25 scoring work with SIMD optimization?"',
|
|
@@ -289,7 +290,7 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
289
290
|
'',
|
|
290
291
|
'BAD search strategy (never do this):',
|
|
291
292
|
' → search "AllowedIPs" → search "allowedIps" → search "allowed_ips" (WRONG: case/style variations, probe handles them)',
|
|
292
|
-
' → search "limitDRL" → search "LimitDRL" (WRONG: case variation — combine with OR: \'"limitDRL" "limitRedis"\')',
|
|
293
|
+
' → search "limitDRL" → search "LimitDRL" (WRONG: case variation — combine with OR: \'"limitDRL" OR "limitRedis"\')',
|
|
293
294
|
' → search "throttle_retry_limit" after searching "ThrottleRetryLimit" (WRONG: snake_case variation, probe handles it)',
|
|
294
295
|
' → search "ThrottleRetryLimit" path=tyk → search "ThrottleRetryLimit" path=gateway → search "ThrottleRetryLimit" path=apidef (WRONG: same query on different paths — probe searches recursively)',
|
|
295
296
|
' → search "func (k *RateLimitAndQuotaCheck) handleRateLimitFailure" (WRONG: do not search full function signatures, just use exact=true "handleRateLimitFailure")',
|
|
@@ -302,15 +303,34 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
302
303
|
'- To bypass stopword filtering: wrap terms in quotes ("return", "struct") or set exact=true. Both disable stemming and splitting too.',
|
|
303
304
|
'- camelCase terms are split: getUserData becomes "get", "user", "data" — so one search covers all naming styles.',
|
|
304
305
|
'- Do NOT search for full function signatures like "func (r *Type) Method(args)". Just search for the method name with exact=true.',
|
|
306
|
+
'- Do NOT search for file names (e.g., "sliding_log.go"). Use listFiles to discover files by name.',
|
|
307
|
+
'',
|
|
308
|
+
'PAGINATION:',
|
|
309
|
+
'- Search results are paginated (~20k tokens per page).',
|
|
310
|
+
'- If your search returned relevant files, call the same query with nextPage=true to check for more.',
|
|
311
|
+
'- Keep paginating while results stay relevant. Stop when results are off-topic or "All results retrieved".',
|
|
312
|
+
'',
|
|
313
|
+
'WHEN TO STOP:',
|
|
314
|
+
'- After you have explored the main concept AND related subsystems.',
|
|
315
|
+
'- Once you have 5-15 targets covering different aspects of the query.',
|
|
316
|
+
'- If you get a "DUPLICATE SEARCH BLOCKED" message, move on.',
|
|
305
317
|
'',
|
|
306
318
|
'Strategy:',
|
|
307
|
-
'1. Analyze the query
|
|
308
|
-
'
|
|
309
|
-
'
|
|
319
|
+
'1. Analyze the query — identify key concepts, then brainstorm SYNONYMS and alternative terms for each.',
|
|
320
|
+
' Code naming often differs from the concept: "authentication" → verify, credentials, login, auth;',
|
|
321
|
+
' "rate limiting" → throttle, quota, limiter, bucket; "error handling" → catch, recover, panic.',
|
|
322
|
+
' Think about what a developer would NAME the function/struct/variable, not just the concept.',
|
|
323
|
+
'2. Run INDEPENDENT searches in PARALLEL — search for the main concept AND synonyms simultaneously.',
|
|
324
|
+
' After each search, check if results are relevant. If yes, call nextPage=true for more results.',
|
|
325
|
+
'3. Combine related symbols into OR searches: \'"symbolA" OR "symbolB"\' finds files with either.',
|
|
310
326
|
'4. For known symbol names use exact=true. For concepts use default (exact=false).',
|
|
311
|
-
'5.
|
|
312
|
-
'
|
|
313
|
-
'
|
|
327
|
+
'5. After your first round of searches, READ the extracted code and look for connected code:',
|
|
328
|
+
' - Function calls to other important functions → include those targets.',
|
|
329
|
+
' - Type references and imports → include type definitions.',
|
|
330
|
+
' - Registered handlers/middleware → include all registered items.',
|
|
331
|
+
'6. If a search returns results, use extract to verify relevance. Run multiple extracts in parallel too.',
|
|
332
|
+
'7. If a search returns NO results, the term does not exist. Do NOT retry with variations. Move on.',
|
|
333
|
+
'8. Once you have enough targets (typically 5-15), output your final JSON answer immediately.',
|
|
314
334
|
'',
|
|
315
335
|
`Query: ${searchQuery}`,
|
|
316
336
|
`Search path(s): ${searchPath}`,
|
|
@@ -319,7 +339,9 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
319
339
|
'Return ONLY valid JSON: {"targets": ["path/to/file.ext#Symbol", "path/to/file.ext:line", "path/to/file.ext:start-end"]}',
|
|
320
340
|
'IMPORTANT: Use ABSOLUTE file paths in targets (e.g., "/full/path/to/file.ext#Symbol"). If you only have relative paths, make them relative to the search path above.',
|
|
321
341
|
'Prefer #Symbol when a function/class name is clear; otherwise use line numbers.',
|
|
322
|
-
'Deduplicate targets. Do NOT explain or answer - ONLY return the JSON targets.'
|
|
342
|
+
'Deduplicate targets. Do NOT explain or answer - ONLY return the JSON targets.',
|
|
343
|
+
'',
|
|
344
|
+
'Remember: if your search returned relevant results, use nextPage=true to check for more before outputting.'
|
|
323
345
|
].join('\n');
|
|
324
346
|
}
|
|
325
347
|
|
|
@@ -351,6 +373,8 @@ export const searchTool = (options = {}) => {
|
|
|
351
373
|
|
|
352
374
|
// Track previous non-paginated searches to detect and block duplicates
|
|
353
375
|
const previousSearches = new Set();
|
|
376
|
+
// Track how many times a duplicate search has been blocked (for escalating messages)
|
|
377
|
+
let consecutiveDupBlocks = 0;
|
|
354
378
|
// Track pagination counts per query to cap runaway pagination
|
|
355
379
|
const paginationCounts = new Map();
|
|
356
380
|
const MAX_PAGES_PER_QUERY = 3;
|
|
@@ -422,12 +446,17 @@ export const searchTool = (options = {}) => {
|
|
|
422
446
|
const searchKey = `${searchQuery}::${exact || false}`;
|
|
423
447
|
if (!nextPage) {
|
|
424
448
|
if (previousSearches.has(searchKey)) {
|
|
449
|
+
consecutiveDupBlocks++;
|
|
425
450
|
if (debug) {
|
|
426
|
-
console.error(`[DEDUP] Blocked duplicate search: "${searchQuery}" (path: "${searchPath}")`);
|
|
451
|
+
console.error(`[DEDUP] Blocked duplicate search (${consecutiveDupBlocks}x): "${searchQuery}" (path: "${searchPath}")`);
|
|
452
|
+
}
|
|
453
|
+
if (consecutiveDupBlocks >= 3) {
|
|
454
|
+
return 'STOP. You have been blocked ' + consecutiveDupBlocks + ' times for repeating searches. You MUST output your final JSON answer NOW with whatever targets you have found. Do NOT call any more tools.';
|
|
427
455
|
}
|
|
428
|
-
return 'DUPLICATE SEARCH BLOCKED
|
|
456
|
+
return 'DUPLICATE SEARCH BLOCKED (' + consecutiveDupBlocks + 'x). You already searched for this. Do NOT repeat — probe searches recursively across all paths. Either: (1) use extract on results you already found, (2) try a COMPLETELY different keyword, or (3) output your final answer NOW.';
|
|
429
457
|
}
|
|
430
458
|
previousSearches.add(searchKey);
|
|
459
|
+
consecutiveDupBlocks = 0; // Reset on successful new search
|
|
431
460
|
paginationCounts.set(searchKey, 0);
|
|
432
461
|
} else {
|
|
433
462
|
// Cap pagination to prevent runaway page-through of broad queries
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|