@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.
@@ -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 mixed case (upper+lower) or underscores
73
- const hasUpper = /[A-Z]/.test(token);
74
- const hasLower = /[a-z]/.test(token);
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 hasMixedCase = hasUpper && hasLower;
77
- if (hasMixedCase || hasUnderscore) {
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 of complex queries.',
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
- '- For known symbol names, quote each term to prevent splitting: \'"limitDRL" "limitRedis"\' matches either exact symbol.',
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 only when you need both terms to appear in the same file: "rate AND limit".',
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 - identify key concepts and group related symbols',
308
- '2. Combine related symbols into OR searches: \'"symbolA" "symbolB"\' finds files with either (quote to prevent splitting)',
309
- '3. Run INDEPENDENT searches in PARALLEL do not wait for one to finish before starting another',
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. If a search returns results, use extract to verify relevance. Run multiple extracts in parallel too.',
312
- '6. If a search returns NO results, the term does not exist. Do NOT retry with variations, different paths, or longer strings. Move on.',
313
- '7. Combine all relevant targets in your final response',
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: You already searched for this exact query. Changing the path does NOT give different results — probe searches recursively. Do NOT repeat the same search. Try a genuinely different keyword, use extract to examine results you already found, or provide your final answer if you have enough information.';
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
@@ -25821,7 +25821,8 @@ var init_search = __esm({
25821
25821
  session: "--session",
25822
25822
  timeout: "--timeout",
25823
25823
  language: "--language",
25824
- format: "--format"
25824
+ format: "--format",
25825
+ lsp: "--lsp"
25825
25826
  };
25826
25827
  }
25827
25828
  });
@@ -26057,7 +26058,8 @@ var init_extract = __esm({
26057
26058
  allowTests: "--allow-tests",
26058
26059
  contextLines: "--context",
26059
26060
  format: "--format",
26060
- inputFile: "--input-file"
26061
+ inputFile: "--input-file",
26062
+ lsp: "--lsp"
26061
26063
  };
26062
26064
  }
26063
26065
  });
@@ -27291,11 +27293,9 @@ function autoQuoteSearchTerms(query2) {
27291
27293
  const result = tokens.map((token) => {
27292
27294
  if (token.startsWith('"')) return token;
27293
27295
  if (operators.has(token)) return token;
27294
- const hasUpper = /[A-Z]/.test(token);
27295
- const hasLower = /[a-z]/.test(token);
27296
27296
  const hasUnderscore = token.includes("_");
27297
- const hasMixedCase = hasUpper && hasLower;
27298
- if (hasMixedCase || hasUnderscore) {
27297
+ const hasCaseTransition = /[a-z][A-Z]/.test(token) || /[A-Z]{2,}[a-z]/.test(token);
27298
+ if (hasCaseTransition || hasUnderscore) {
27299
27299
  return `"${token}"`;
27300
27300
  }
27301
27301
  return token;
@@ -27410,7 +27410,7 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
27410
27410
  "Break down complex queries into multiple searches to cover all aspects.",
27411
27411
  "",
27412
27412
  "Available tools:",
27413
- "- search: Find code matching keywords or patterns. Run multiple searches for different aspects of complex queries.",
27413
+ "- search: Find code matching keywords or patterns. Results are paginated \u2014 use nextPage=true when results are relevant to get more. Run multiple searches for different aspects.",
27414
27414
  "- extract: Verify code snippets to ensure targets are actually relevant before including them.",
27415
27415
  "- listFiles: Understand directory structure to find where relevant code might live.",
27416
27416
  "",
@@ -27431,13 +27431,14 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
27431
27431
  "",
27432
27432
  "Combining searches with OR:",
27433
27433
  '- Multiple unquoted words use OR logic: rate limit matches files containing EITHER "rate" OR "limit".',
27434
- `- For known symbol names, quote each term to prevent splitting: '"limitDRL" "limitRedis"' matches either exact symbol.`,
27434
+ `- IMPORTANT: Multiple quoted terms use AND logic by default: '"RateLimit" "middleware"' requires BOTH in the same file.`,
27435
+ `- To search for ANY of several quoted symbols, use the explicit OR operator: '"ForwardMessage" OR "SessionLimiter"'.`,
27435
27436
  '- Without quotes, camelCase like limitDRL gets split into "limit" + "DRL" \u2014 not what you want for symbol lookup.',
27436
27437
  "- Use OR to search for multiple related symbols in ONE search instead of separate searches.",
27437
27438
  "- This is much faster than running separate searches sequentially.",
27438
- `- Example: search '"ForwardMessage" "SessionLimiter"' finds files with either exact symbol in one call.`,
27439
- `- Example: search '"limitDRL" "doRollingWindowWrite"' finds both rate limiting functions at once.`,
27440
- '- Use AND only when you need both terms to appear in the same file: "rate AND limit".',
27439
+ `- Example: search '"ForwardMessage" OR "SessionLimiter"' finds files with either exact symbol in one call.`,
27440
+ `- Example: search '"limitDRL" OR "doRollingWindowWrite"' finds both rate limiting functions at once.`,
27441
+ "- Use AND (or just put quoted terms together) when you need both terms in the same file.",
27441
27442
  "",
27442
27443
  "Parallel tool calls:",
27443
27444
  "- When you need to search for INDEPENDENT concepts, call multiple search tools IN PARALLEL (same response).",
@@ -27451,10 +27452,10 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
27451
27452
  ' Query: "Find the IP allowlist middleware"',
27452
27453
  ' \u2192 search "allowlist middleware" (one search, probe handles IP/ip/Ip variations)',
27453
27454
  ' Query: "Find ForwardMessage and SessionLimiter"',
27454
- ` \u2192 search '"ForwardMessage" "SessionLimiter"' (one OR search finds both exact symbols)`,
27455
+ ` \u2192 search '"ForwardMessage" OR "SessionLimiter"' (one OR search finds both exact symbols)`,
27455
27456
  ' OR: search exact=true "ForwardMessage" + search exact=true "SessionLimiter" IN PARALLEL',
27456
27457
  ' Query: "Find limitDRL and limitRedis functions"',
27457
- ` \u2192 search '"limitDRL" "limitRedis"' (one OR search, quoted to prevent camelCase splitting)`,
27458
+ ` \u2192 search '"limitDRL" OR "limitRedis"' (one OR search, quoted to prevent camelCase splitting)`,
27458
27459
  ' Query: "Find ThrottleRetryLimit usage"',
27459
27460
  ' \u2192 search exact=true "ThrottleRetryLimit" (one search, if no results the symbol does not exist \u2014 stop)',
27460
27461
  ' Query: "How does BM25 scoring work with SIMD optimization?"',
@@ -27462,7 +27463,7 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
27462
27463
  "",
27463
27464
  "BAD search strategy (never do this):",
27464
27465
  ' \u2192 search "AllowedIPs" \u2192 search "allowedIps" \u2192 search "allowed_ips" (WRONG: case/style variations, probe handles them)',
27465
- ` \u2192 search "limitDRL" \u2192 search "LimitDRL" (WRONG: case variation \u2014 combine with OR: '"limitDRL" "limitRedis"')`,
27466
+ ` \u2192 search "limitDRL" \u2192 search "LimitDRL" (WRONG: case variation \u2014 combine with OR: '"limitDRL" OR "limitRedis"')`,
27466
27467
  ' \u2192 search "throttle_retry_limit" after searching "ThrottleRetryLimit" (WRONG: snake_case variation, probe handles it)',
27467
27468
  ' \u2192 search "ThrottleRetryLimit" path=tyk \u2192 search "ThrottleRetryLimit" path=gateway \u2192 search "ThrottleRetryLimit" path=apidef (WRONG: same query on different paths \u2014 probe searches recursively)',
27468
27469
  ' \u2192 search "func (k *RateLimitAndQuotaCheck) handleRateLimitFailure" (WRONG: do not search full function signatures, just use exact=true "handleRateLimitFailure")',
@@ -27475,15 +27476,34 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
27475
27476
  '- To bypass stopword filtering: wrap terms in quotes ("return", "struct") or set exact=true. Both disable stemming and splitting too.',
27476
27477
  '- camelCase terms are split: getUserData becomes "get", "user", "data" \u2014 so one search covers all naming styles.',
27477
27478
  '- Do NOT search for full function signatures like "func (r *Type) Method(args)". Just search for the method name with exact=true.',
27479
+ '- Do NOT search for file names (e.g., "sliding_log.go"). Use listFiles to discover files by name.',
27480
+ "",
27481
+ "PAGINATION:",
27482
+ "- Search results are paginated (~20k tokens per page).",
27483
+ "- If your search returned relevant files, call the same query with nextPage=true to check for more.",
27484
+ '- Keep paginating while results stay relevant. Stop when results are off-topic or "All results retrieved".',
27485
+ "",
27486
+ "WHEN TO STOP:",
27487
+ "- After you have explored the main concept AND related subsystems.",
27488
+ "- Once you have 5-15 targets covering different aspects of the query.",
27489
+ '- If you get a "DUPLICATE SEARCH BLOCKED" message, move on.',
27478
27490
  "",
27479
27491
  "Strategy:",
27480
- "1. Analyze the query - identify key concepts and group related symbols",
27481
- `2. Combine related symbols into OR searches: '"symbolA" "symbolB"' finds files with either (quote to prevent splitting)`,
27482
- "3. Run INDEPENDENT searches in PARALLEL \u2014 do not wait for one to finish before starting another",
27492
+ "1. Analyze the query \u2014 identify key concepts, then brainstorm SYNONYMS and alternative terms for each.",
27493
+ ' Code naming often differs from the concept: "authentication" \u2192 verify, credentials, login, auth;',
27494
+ ' "rate limiting" \u2192 throttle, quota, limiter, bucket; "error handling" \u2192 catch, recover, panic.',
27495
+ " Think about what a developer would NAME the function/struct/variable, not just the concept.",
27496
+ "2. Run INDEPENDENT searches in PARALLEL \u2014 search for the main concept AND synonyms simultaneously.",
27497
+ " After each search, check if results are relevant. If yes, call nextPage=true for more results.",
27498
+ `3. Combine related symbols into OR searches: '"symbolA" OR "symbolB"' finds files with either.`,
27483
27499
  "4. For known symbol names use exact=true. For concepts use default (exact=false).",
27484
- "5. If a search returns results, use extract to verify relevance. Run multiple extracts in parallel too.",
27485
- "6. If a search returns NO results, the term does not exist. Do NOT retry with variations, different paths, or longer strings. Move on.",
27486
- "7. Combine all relevant targets in your final response",
27500
+ "5. After your first round of searches, READ the extracted code and look for connected code:",
27501
+ " - Function calls to other important functions \u2192 include those targets.",
27502
+ " - Type references and imports \u2192 include type definitions.",
27503
+ " - Registered handlers/middleware \u2192 include all registered items.",
27504
+ "6. If a search returns results, use extract to verify relevance. Run multiple extracts in parallel too.",
27505
+ "7. If a search returns NO results, the term does not exist. Do NOT retry with variations. Move on.",
27506
+ "8. Once you have enough targets (typically 5-15), output your final JSON answer immediately.",
27487
27507
  "",
27488
27508
  `Query: ${searchQuery}`,
27489
27509
  `Search path(s): ${searchPath}`,
@@ -27492,7 +27512,9 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
27492
27512
  'Return ONLY valid JSON: {"targets": ["path/to/file.ext#Symbol", "path/to/file.ext:line", "path/to/file.ext:start-end"]}',
27493
27513
  '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.',
27494
27514
  "Prefer #Symbol when a function/class name is clear; otherwise use line numbers.",
27495
- "Deduplicate targets. Do NOT explain or answer - ONLY return the JSON targets."
27515
+ "Deduplicate targets. Do NOT explain or answer - ONLY return the JSON targets.",
27516
+ "",
27517
+ "Remember: if your search returned relevant results, use nextPage=true to check for more before outputting."
27496
27518
  ].join("\n");
27497
27519
  }
27498
27520
  var import_ai, import_fs4, CODE_SEARCH_SCHEMA, searchTool, queryTool, extractTool, delegateTool, analyzeAllTool;
@@ -27537,6 +27559,7 @@ var init_vercel = __esm({
27537
27559
  return result;
27538
27560
  };
27539
27561
  const previousSearches = /* @__PURE__ */ new Set();
27562
+ let consecutiveDupBlocks = 0;
27540
27563
  const paginationCounts = /* @__PURE__ */ new Map();
27541
27564
  const MAX_PAGES_PER_QUERY = 3;
27542
27565
  return (0, import_ai.tool)({
@@ -27589,12 +27612,17 @@ var init_vercel = __esm({
27589
27612
  const searchKey = `${searchQuery}::${exact || false}`;
27590
27613
  if (!nextPage) {
27591
27614
  if (previousSearches.has(searchKey)) {
27615
+ consecutiveDupBlocks++;
27592
27616
  if (debug) {
27593
- console.error(`[DEDUP] Blocked duplicate search: "${searchQuery}" (path: "${searchPath}")`);
27617
+ console.error(`[DEDUP] Blocked duplicate search (${consecutiveDupBlocks}x): "${searchQuery}" (path: "${searchPath}")`);
27594
27618
  }
27595
- return "DUPLICATE SEARCH BLOCKED: You already searched for this exact query. Changing the path does NOT give different results \u2014 probe searches recursively. Do NOT repeat the same search. Try a genuinely different keyword, use extract to examine results you already found, or provide your final answer if you have enough information.";
27619
+ if (consecutiveDupBlocks >= 3) {
27620
+ 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.";
27621
+ }
27622
+ return "DUPLICATE SEARCH BLOCKED (" + consecutiveDupBlocks + "x). You already searched for this. Do NOT repeat \u2014 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.";
27596
27623
  }
27597
27624
  previousSearches.add(searchKey);
27625
+ consecutiveDupBlocks = 0;
27598
27626
  paginationCounts.set(searchKey, 0);
27599
27627
  } else {
27600
27628
  const pageCount = (paginationCounts.get(searchKey) || 0) + 1;
@@ -88375,26 +88403,46 @@ var init_prompts = __esm({
88375
88403
  CRITICAL - You are READ-ONLY:
88376
88404
  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 \u2014 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.
88377
88405
 
88406
+ CRITICAL - ALWAYS search before answering:
88407
+ 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.
88408
+
88378
88409
  When exploring code:
88379
88410
  - Provide clear, concise explanations based on user request
88380
88411
  - Find and highlight the most relevant code snippets, if required
88381
- - Trace function calls and data flow through the system
88412
+ - Trace function calls and data flow through the system \u2014 follow the FULL call chain, not just the entry point
88382
88413
  - Try to understand the user's intent and provide relevant information
88383
88414
  - Understand high level picture
88384
88415
  - Balance detail with clarity in your explanations
88416
+ - Search using SYNONYMS and alternative terms \u2014 code naming often differs from the concept name (e.g., "authentication" might be named verify_credentials, check_token, validate_session)
88417
+ - When you find a key function, look at what it CALLS and what CALLS it to discover the complete picture
88418
+ - Before answering, ask yourself: "Did I cover all the major components? Are there related subsystems I missed?" If yes, do one more search round.
88385
88419
 
88386
88420
  When providing answers:
88421
+ - 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.
88422
+ - 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.
88423
+ - Include data structures, configuration options, and error handling \u2014 not just the happy path.
88387
88424
  - Always include a "References" section at the end of your response
88388
88425
  - List all relevant source code locations you found during exploration
88389
88426
  - Use the format: file_path:line_number or file_path#symbol_name
88390
88427
  - Group references by file when multiple locations are from the same file
88391
88428
  - Include brief descriptions of what each reference contains`,
88392
- "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.
88429
+ "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.
88430
+
88431
+ You think like a code explorer \u2014 you understand that codebases have layers:
88432
+ - Core implementations (algorithms, data structures)
88433
+ - Middleware/integration layers (request handlers, interceptors)
88434
+ - Configuration and storage backends
88435
+ - Scoping mechanisms (per-user, per-org, per-tenant, global)
88436
+ - Supporting utilities and helpers
88393
88437
 
88394
88438
  When searching:
88395
- - Use only the search tool
88396
- - Run additional searches only if needed to capture all relevant locations
88397
- - Prefer specific, focused queries
88439
+ - Search for the MAIN concept first, then think: "what RELATED subsystems would a real codebase have?"
88440
+ - Use extract to READ the code you find \u2014 look for function calls, type references, and imports that point to OTHER relevant code
88441
+ - If you find middleware, check: are there org-level or tenant-level variants?
88442
+ - If you find algorithms, check: are there different storage backends?
88443
+ - Search results are paginated \u2014 if results look relevant, call nextPage=true to check for more files
88444
+ - Stop paginating when results become irrelevant or you see "All results retrieved"
88445
+ - Search using SYNONYMS \u2014 code naming differs from concepts (e.g., "rate limiting" \u2192 throttle, quota, limiter, bucket)
88398
88446
 
88399
88447
  Output format (MANDATORY):
88400
88448
  - Return ONLY valid JSON with a single top-level key: "targets"
@@ -88404,7 +88452,8 @@ Output format (MANDATORY):
88404
88452
  - "path/to/file.ext:line"
88405
88453
  - "path/to/file.ext:start-end"
88406
88454
  - Prefer #SymbolName when a function/class name is clear; otherwise use line numbers
88407
- - Deduplicate targets and keep them concise`,
88455
+ - Deduplicate targets and keep them concise
88456
+ - Aim for 5-15 targets covering ALL aspects of the query`,
88408
88457
  "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.
88409
88458
 
88410
88459
  When analyzing code:
@@ -101777,9 +101826,9 @@ Workspace: ${this.allowedFolders.join(", ")}`;
101777
101826
  Follow these instructions carefully:
101778
101827
  1. Analyze the user's request.
101779
101828
  2. Use the available tools step-by-step to fulfill the request.
101780
- 3. You should always prefer the search tool for code-related questions.${this.searchDelegate ? " Ask natural language questions \u2014 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 \u2014 do NOT try keyword variations manually. Read full files only if really necessary."}
101781
- 4. Ensure to get really deep and understand the full picture before answering.
101782
- 5. Once the task is fully completed, provide your final answer directly as text.
101829
+ 3. You MUST use the search tool before answering ANY code-related question. NEVER answer from memory or general knowledge \u2014 your answers must be grounded in actual code found via search/extract.${this.searchDelegate ? " Ask natural language questions \u2014 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 \u2014 do NOT try keyword variations manually. Read full files only if really necessary."}
101830
+ 4. Ensure to get really deep and understand the full picture before answering. Follow call chains \u2014 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).
101831
+ 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 \u2014 go straight to the answer.
101783
101832
  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."}
101784
101833
  7. NEVER use bash for code exploration (no grep, cat, find, head, tail, awk, sed) \u2014 always use search and extract tools instead. Bash is only for system operations like building, running tests, or git commands.${this.allowEdit ? `
101785
101834
  7. When modifying files, choose the appropriate tool:
@@ -102165,6 +102214,22 @@ You are working with a workspace. Available paths: ${workspaceDesc}
102165
102214
  if (recentTexts.every((t) => t && t === recentTexts[0])) return true;
102166
102215
  if (recentTexts.every((t) => detectStuckResponse(t))) return true;
102167
102216
  }
102217
+ if (steps.length >= 3) {
102218
+ const last3 = steps.slice(-3);
102219
+ const allHaveTools = last3.every((s) => s.toolCalls?.length === 1);
102220
+ if (allHaveTools) {
102221
+ const signatures = last3.map((s) => {
102222
+ const tc = s.toolCalls[0];
102223
+ return `${tc.toolName}::${JSON.stringify(tc.args ?? tc.input)}`;
102224
+ });
102225
+ if (signatures[0] === signatures[1] && signatures[1] === signatures[2]) {
102226
+ if (this.debug) {
102227
+ console.log(`[DEBUG] Circuit breaker: 3 consecutive identical tool calls detected (${last3[0].toolCalls[0].toolName}), forcing stop`);
102228
+ }
102229
+ return true;
102230
+ }
102231
+ }
102232
+ }
102168
102233
  return false;
102169
102234
  },
102170
102235
  prepareStep: ({ steps, stepNumber }) => {
@@ -102173,6 +102238,22 @@ You are working with a workspace. Available paths: ${workspaceDesc}
102173
102238
  toolChoice: "none"
102174
102239
  };
102175
102240
  }
102241
+ if (steps.length >= 2) {
102242
+ const last2 = steps.slice(-2);
102243
+ if (last2.every((s) => s.toolCalls?.length === 1)) {
102244
+ const tc1 = last2[0].toolCalls[0];
102245
+ const tc2 = last2[1].toolCalls[0];
102246
+ const sig1 = `${tc1.toolName}::${JSON.stringify(tc1.args ?? tc1.input)}`;
102247
+ const sig2 = `${tc2.toolName}::${JSON.stringify(tc2.args ?? tc2.input)}`;
102248
+ if (sig1 === sig2) {
102249
+ if (this.debug) {
102250
+ console.log(`[DEBUG] prepareStep: 2 consecutive identical tool calls (${tc1.toolName}), forcing toolChoice=none`);
102251
+ console.log(`[DEBUG] sig: ${sig1.substring(0, 200)}`);
102252
+ }
102253
+ return { toolChoice: "none" };
102254
+ }
102255
+ }
102256
+ }
102176
102257
  const lastStep = steps[steps.length - 1];
102177
102258
  const modelJustStopped = lastStep?.finishReason === "stop" && (!lastStep?.toolCalls || lastStep.toolCalls.length === 0);
102178
102259
  if (modelJustStopped) {
@@ -102203,7 +102284,9 @@ ${resultToReview}
102203
102284
 
102204
102285
  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).`;
102205
102286
  return {
102206
- userMessage: completionPromptMessage
102287
+ userMessage: completionPromptMessage,
102288
+ toolChoice: "none"
102289
+ // Force text-only review — no tool calls
102207
102290
  };
102208
102291
  }
102209
102292
  }
@@ -102245,7 +102328,11 @@ Double-check your response based on the criteria above. If everything looks good
102245
102328
  options.onStream(text);
102246
102329
  }
102247
102330
  if (this.debug) {
102248
- console.log(`[DEBUG] Step ${currentIteration}/${maxIterations} finished (reason: ${finishReason}, tools: ${toolResults?.length || 0})`);
102331
+ const toolSummary = toolCalls?.length ? toolCalls.map((tc) => {
102332
+ const args = tc.args ? JSON.stringify(tc.args) : "";
102333
+ return args ? `${tc.toolName}(${debugTruncate(args, 120)})` : tc.toolName;
102334
+ }).join(", ") : "none";
102335
+ console.log(`[DEBUG] Step ${currentIteration}/${maxIterations} finished (reason: ${finishReason}, tools: [${toolSummary}])`);
102249
102336
  if (text) {
102250
102337
  console.log(`[DEBUG] model text: ${debugTruncate(text)}`);
102251
102338
  }
@@ -102278,9 +102365,15 @@ Double-check your response based on the criteria above. If everything looks good
102278
102365
  }
102279
102366
  const executeAIRequest = async () => {
102280
102367
  const result = await this.streamTextWithRetryAndFallback(streamOptions);
102281
- const finalText = await result.text;
102368
+ const steps = await result.steps;
102369
+ let finalText;
102370
+ if (steps && steps.length > 1) {
102371
+ const lastStepText = steps[steps.length - 1].text;
102372
+ finalText = lastStepText || await result.text;
102373
+ } else {
102374
+ finalText = await result.text;
102375
+ }
102282
102376
  if (this.debug) {
102283
- const steps = await result.steps;
102284
102377
  console.log(`[DEBUG] streamText completed: ${steps?.length || 0} steps, finalText=${finalText?.length || 0} chars`);
102285
102378
  }
102286
102379
  const usage = await result.usage;
@@ -102350,12 +102443,12 @@ ${finalResult}
102350
102443
 
102351
102444
  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).`;
102352
102445
  currentMessages.push({ role: "user", content: completionPromptMessage });
102353
- const completionMaxIterations = 5;
102354
102446
  const completionStreamOptions = {
102355
102447
  model: this.provider ? this.provider(this.model) : this.model,
102356
102448
  messages: this.prepareMessagesWithImages(currentMessages),
102357
102449
  tools: tools2,
102358
- stopWhen: (0, import_ai6.stepCountIs)(completionMaxIterations),
102450
+ toolChoice: "none",
102451
+ // Force text-only response — no tool calls during review
102359
102452
  maxTokens: maxResponseTokens,
102360
102453
  temperature: 0.3,
102361
102454
  onStepFinish: ({ toolResults, text, finishReason, usage }) => {