@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/build/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}")`);
|
|
427
452
|
}
|
|
428
|
-
|
|
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.';
|
|
455
|
+
}
|
|
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
|
|
@@ -449,7 +478,11 @@ export const searchTool = (options = {}) => {
|
|
|
449
478
|
return result;
|
|
450
479
|
} catch (error) {
|
|
451
480
|
console.error('Error executing search command:', error);
|
|
452
|
-
|
|
481
|
+
const formatted = formatErrorForAI(error);
|
|
482
|
+
if (error.category === 'path_error' || error.message?.includes('does not exist')) {
|
|
483
|
+
return formatted + '\n\nThe path does not exist. Use the listFiles tool to verify the correct directory structure before retrying. If the workspace itself is gone, output your final answer with whatever information you have.';
|
|
484
|
+
}
|
|
485
|
+
return formatted;
|
|
453
486
|
}
|
|
454
487
|
}
|
|
455
488
|
|
|
@@ -181,14 +181,14 @@ export function categorizeError(error) {
|
|
|
181
181
|
errorCode === 'enoent') {
|
|
182
182
|
return new PathError(message, {
|
|
183
183
|
originalError: error,
|
|
184
|
-
suggestion: 'The specified path does not exist.
|
|
184
|
+
suggestion: 'The specified path does not exist. Use the listFiles tool to check the correct directory structure, then retry with a valid path.'
|
|
185
185
|
});
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
if (lowerMessage.includes('not a directory') || errorCode === 'enotdir') {
|
|
189
189
|
return new PathError(message, {
|
|
190
190
|
originalError: error,
|
|
191
|
-
suggestion: 'The path is not a directory.
|
|
191
|
+
suggestion: 'The path is not a directory. Use the listFiles tool to find the correct directory, then retry.'
|
|
192
192
|
});
|
|
193
193
|
}
|
|
194
194
|
|
|
@@ -110,7 +110,7 @@ export async function validateCwdPath(inputPath, defaultPath = process.cwd()) {
|
|
|
110
110
|
}
|
|
111
111
|
if (error.code === 'ENOENT') {
|
|
112
112
|
throw new PathError(`Path does not exist: ${normalizedPath}`, {
|
|
113
|
-
suggestion: 'The specified path does not exist.
|
|
113
|
+
suggestion: 'The specified path does not exist. Use the listFiles tool to check the correct directory structure, then retry with a valid path.',
|
|
114
114
|
details: { path: normalizedPath }
|
|
115
115
|
});
|
|
116
116
|
}
|