@probelabs/probe 0.6.0-rc302 → 0.6.0-rc303
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-rc302-aarch64-apple-darwin.tar.gz → probe-v0.6.0-rc303-aarch64-apple-darwin.tar.gz} +0 -0
- package/bin/binaries/{probe-v0.6.0-rc302-aarch64-unknown-linux-musl.tar.gz → probe-v0.6.0-rc303-aarch64-unknown-linux-musl.tar.gz} +0 -0
- package/bin/binaries/{probe-v0.6.0-rc302-x86_64-apple-darwin.tar.gz → probe-v0.6.0-rc303-x86_64-apple-darwin.tar.gz} +0 -0
- package/bin/binaries/{probe-v0.6.0-rc302-x86_64-pc-windows-msvc.zip → probe-v0.6.0-rc303-x86_64-pc-windows-msvc.zip} +0 -0
- package/bin/binaries/{probe-v0.6.0-rc302-x86_64-unknown-linux-musl.tar.gz → probe-v0.6.0-rc303-x86_64-unknown-linux-musl.tar.gz} +0 -0
- package/build/tools/vercel.js +101 -3
- package/cjs/agent/ProbeAgent.cjs +89 -3
- package/cjs/index.cjs +89 -3
- package/package.json +1 -1
- package/src/tools/vercel.js +101 -3
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/build/tools/vercel.js
CHANGED
|
@@ -248,8 +248,23 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
248
248
|
'- Searching "getUserData" ALREADY matches "get", "user", "data" and their variations.',
|
|
249
249
|
'- NEVER repeat the same search query — you will get the same results. Changing the path does NOT change this.',
|
|
250
250
|
'- NEVER search trivial variations of the same keyword (e.g., AllowedIPs then allowedIps then allowed_ips). This is wasteful — probe handles it.',
|
|
251
|
-
'
|
|
252
|
-
'
|
|
251
|
+
'',
|
|
252
|
+
'When a search returns no results:',
|
|
253
|
+
'- If you searched a SUBFOLDER (e.g., path="gateway/"), the term might exist elsewhere.',
|
|
254
|
+
' Try searching from the workspace root (omit the path parameter) or a different directory.',
|
|
255
|
+
' But do NOT retry the same subfolder with different quoting — that will not help.',
|
|
256
|
+
'- If you searched the WORKSPACE ROOT and got no results, the term does not exist in this codebase.',
|
|
257
|
+
' Changing quotes, adding "func " prefix, or switching to method syntax will NOT help.',
|
|
258
|
+
'- These are ALL the same failed search, NOT different searches:',
|
|
259
|
+
' search("func ctxGetData") → no results',
|
|
260
|
+
' search("ctxGetData") → no results ← WASTED, same concept, different quoting',
|
|
261
|
+
' search(ctxGetData) → no results ← WASTED, same concept, no quotes',
|
|
262
|
+
' search("ctx.GetData") → no results ← WASTED, method syntax of same concept',
|
|
263
|
+
' After the FIRST "no results" at a given scope, either widen the search path or try',
|
|
264
|
+
' a fundamentally different approach: search for a broader concept, use listFiles',
|
|
265
|
+
' to discover actual function names, or extract a known file to read real code.',
|
|
266
|
+
'- If 2 searches return no results for a concept (across different scopes), the code likely',
|
|
267
|
+
' uses different naming than you expect — discover the real names via extract or listFiles.',
|
|
253
268
|
'',
|
|
254
269
|
'When to use exact=true:',
|
|
255
270
|
'- Use exact=true when searching for a KNOWN symbol name (function, type, variable, struct).',
|
|
@@ -302,6 +317,21 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
302
317
|
' → search "ForwardMessage" → search "ForwardMessage" → search "ForwardMessage" (WRONG: repeating the exact same query)',
|
|
303
318
|
' → search "authentication" → wait → search "session management" → wait (WRONG: these are independent, run them in parallel)',
|
|
304
319
|
'',
|
|
320
|
+
' WORST pattern — retrying a non-existent function with quote/syntax variations (this wastes 30 minutes):',
|
|
321
|
+
' → search "func ctxGetData" → no results',
|
|
322
|
+
' → search "ctxGetData" → no results ← WRONG: same term without "func" prefix',
|
|
323
|
+
' → search "ctx.GetData" → no results ← WRONG: method syntax of same concept',
|
|
324
|
+
' → search "ctx.SetData" → no results ← WRONG: Set variant of same concept',
|
|
325
|
+
' → search ctxGetData → no results ← WRONG: unquoted version of same term',
|
|
326
|
+
' → extract api.go → extract api.go → extract api.go (8 times!) ← WRONG: re-reading same file',
|
|
327
|
+
' FIX: After "func ctxGetData" returns no results in gateway/:',
|
|
328
|
+
' Option A: Widen scope — search from the workspace root (omit path) in case the',
|
|
329
|
+
' function is defined in a different package (e.g., apidef/, user/, config/).',
|
|
330
|
+
' Option B: Discover real names — extract a file you KNOW uses context (e.g., a',
|
|
331
|
+
' middleware file) and READ what functions it actually calls.',
|
|
332
|
+
' Option C: Browse — use listFiles to see what files exist and extract the relevant ones.',
|
|
333
|
+
' NEVER: retry the same concept with different quoting in the same directory.',
|
|
334
|
+
'',
|
|
305
335
|
'Keyword tips:',
|
|
306
336
|
'- Common programming keywords are filtered as stopwords when unquoted: function, class, return, new, struct, impl, var, let, const, etc.',
|
|
307
337
|
'- Avoid searching for these alone — combine with a specific term (e.g., "middleware function" is fine, "function" alone is too generic).',
|
|
@@ -340,7 +370,7 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
340
370
|
' - Type references and imports → include type definitions.',
|
|
341
371
|
' - Registered handlers/middleware → include all registered items.',
|
|
342
372
|
'6. If a search returns results, use extract to verify relevance. Run multiple extracts in parallel too.',
|
|
343
|
-
'7. If a search returns NO results
|
|
373
|
+
'7. If a search returns NO results: widen the path scope if you searched a subfolder, or move on. Do NOT retry with quote/syntax variations — they search the same index.',
|
|
344
374
|
'8. Once you have enough targets (typically 5-15), output your final JSON answer immediately.',
|
|
345
375
|
'',
|
|
346
376
|
`Query: ${searchQuery}`,
|
|
@@ -388,8 +418,30 @@ export const searchTool = (options = {}) => {
|
|
|
388
418
|
const dupBlockCounts = new Map();
|
|
389
419
|
// Track pagination counts per query to cap runaway pagination
|
|
390
420
|
const paginationCounts = new Map();
|
|
421
|
+
// Track consecutive no-result searches (circuit breaker)
|
|
422
|
+
let consecutiveNoResults = 0;
|
|
423
|
+
const MAX_CONSECUTIVE_NO_RESULTS = 4;
|
|
424
|
+
// Track normalized query concepts for fuzzy dedup (catches quote/syntax variations)
|
|
425
|
+
const failedConcepts = new Map(); // normalizedKey → count
|
|
391
426
|
const MAX_PAGES_PER_QUERY = 3;
|
|
392
427
|
|
|
428
|
+
/**
|
|
429
|
+
* Normalize a search query to detect syntax-level duplicates.
|
|
430
|
+
* Strips quotes, dots, underscores/hyphens, and lowercases.
|
|
431
|
+
* "ctxGetData", "ctx.GetData", "ctx_get_data" all → "ctxgetdata"
|
|
432
|
+
* Note: does NOT strip language keywords (func, type) — those change search
|
|
433
|
+
* semantics and are already handled as stopwords by the Rust search engine.
|
|
434
|
+
*/
|
|
435
|
+
function normalizeQueryConcept(query) {
|
|
436
|
+
if (!query) return '';
|
|
437
|
+
return query
|
|
438
|
+
.replace(/^["']|["']$/g, '') // strip outer quotes
|
|
439
|
+
.replace(/\./g, '') // "ctx.GetData" → "ctxGetData"
|
|
440
|
+
.replace(/[_\-\s]+/g, '') // strip underscores/hyphens/spaces
|
|
441
|
+
.toLowerCase()
|
|
442
|
+
.trim();
|
|
443
|
+
}
|
|
444
|
+
|
|
393
445
|
return tool({
|
|
394
446
|
name: 'search',
|
|
395
447
|
description: searchDelegate
|
|
@@ -478,6 +530,35 @@ export const searchTool = (options = {}) => {
|
|
|
478
530
|
}
|
|
479
531
|
previousSearches.set(searchKey, { hadResults: false });
|
|
480
532
|
paginationCounts.set(searchKey, 0);
|
|
533
|
+
|
|
534
|
+
// Fuzzy concept dedup: catch quote/syntax variations of the same failed concept
|
|
535
|
+
// e.g., "func ctxGetData", "ctxGetData", "ctx.GetData" all normalize to "ctxgetdata"
|
|
536
|
+
const normalizedKey = `${searchPath}::${normalizeQueryConcept(searchQuery)}`;
|
|
537
|
+
if (failedConcepts.has(normalizedKey) && failedConcepts.get(normalizedKey) >= 2) {
|
|
538
|
+
const conceptCount = failedConcepts.get(normalizedKey) + 1;
|
|
539
|
+
failedConcepts.set(normalizedKey, conceptCount);
|
|
540
|
+
if (debug) {
|
|
541
|
+
console.error(`[CONCEPT-DEDUP] Blocked variation of failed concept (${conceptCount}x): "${searchQuery}" normalized to "${normalizeQueryConcept(searchQuery)}"`);
|
|
542
|
+
}
|
|
543
|
+
const isSubfolder = path && path !== effectiveSearchCwd && path !== '.';
|
|
544
|
+
const scopeHint = isSubfolder
|
|
545
|
+
? `\n- Try searching from the workspace root (omit the path parameter) — the term may exist in a different directory`
|
|
546
|
+
: `\n- The term does not exist in this codebase at any path`;
|
|
547
|
+
return `CONCEPT ALREADY FAILED (${conceptCount} variations tried). You already searched for "${normalizeQueryConcept(searchQuery)}" with different quoting/syntax in this path and got NO results each time. Changing quotes, adding "func" prefix, or switching to method syntax will NOT change the results.\n\nChange your strategy:${scopeHint}\n- Use extract on a file you ALREADY found to read actual code and discover real function/type names\n- Use listFiles to browse directories and find what functions actually exist\n- Search for a BROADER concept (e.g., instead of "ctxGetData", try "context" or "middleware data access")\n- If you have enough information from prior searches, provide your final answer NOW`;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Circuit breaker: too many consecutive no-result searches means the model
|
|
551
|
+
// is stuck in a loop guessing names that don't exist
|
|
552
|
+
if (consecutiveNoResults >= MAX_CONSECUTIVE_NO_RESULTS) {
|
|
553
|
+
if (debug) {
|
|
554
|
+
console.error(`[CIRCUIT-BREAKER] ${consecutiveNoResults} consecutive no-result searches, blocking: "${searchQuery}"`);
|
|
555
|
+
}
|
|
556
|
+
const isSubfolderCB = path && path !== effectiveSearchCwd && path !== '.';
|
|
557
|
+
const cbScopeHint = isSubfolderCB
|
|
558
|
+
? `\n- You have been searching in "${path}" — try searching from the workspace root or a different directory`
|
|
559
|
+
: '';
|
|
560
|
+
return `CIRCUIT BREAKER: Your last ${consecutiveNoResults} searches ALL returned no results. You appear to be guessing function/type names that don't match what's actually in the code.\n\nChange your approach:${cbScopeHint}\n1. Use extract on files you already found — read the actual code to discover real function names\n2. Use listFiles to browse directories and see what files/functions actually exist\n3. If you found some results earlier, those are likely sufficient — provide your final answer\n\nRetrying search query variations will not help. Discover real names from real code instead.`;
|
|
561
|
+
}
|
|
481
562
|
} else {
|
|
482
563
|
// Cap pagination to prevent runaway page-through of broad queries
|
|
483
564
|
const pageCount = (paginationCounts.get(searchKey) || 0) + 1;
|
|
@@ -493,11 +574,28 @@ export const searchTool = (options = {}) => {
|
|
|
493
574
|
const result = maybeAnnotate(await runRawSearch());
|
|
494
575
|
// Track whether this search had results for better dedup messages
|
|
495
576
|
if (typeof result === 'string' && result.includes('No results found')) {
|
|
577
|
+
// Track consecutive no-results and failed concepts for circuit breaker
|
|
578
|
+
consecutiveNoResults++;
|
|
579
|
+
const normalizedKey = `${searchPath}::${normalizeQueryConcept(searchQuery)}`;
|
|
580
|
+
failedConcepts.set(normalizedKey, (failedConcepts.get(normalizedKey) || 0) + 1);
|
|
581
|
+
if (debug) {
|
|
582
|
+
console.error(`[NO-RESULTS] consecutiveNoResults=${consecutiveNoResults}, concept "${normalizeQueryConcept(searchQuery)}" failed ${failedConcepts.get(normalizedKey)}x`);
|
|
583
|
+
}
|
|
496
584
|
// Append contextual hint for ticket/issue ID queries
|
|
497
585
|
if (/^[A-Z]+-\d+$/.test(searchQuery.trim()) || /^[A-Z]+-\d+$/.test(searchQuery.replace(/"/g, '').trim())) {
|
|
498
586
|
return result + '\n\n⚠️ Your query looks like a ticket/issue ID (e.g., JIRA-1234). Ticket IDs are rarely present in source code. Search for the technical concepts described in the ticket instead (e.g., function names, error messages, variable names).';
|
|
499
587
|
}
|
|
588
|
+
// Add a hint when approaching the circuit breaker threshold
|
|
589
|
+
if (consecutiveNoResults >= MAX_CONSECUTIVE_NO_RESULTS - 1) {
|
|
590
|
+
const isSubfolderWarn = path && path !== effectiveSearchCwd && path !== '.';
|
|
591
|
+
const warnScopeHint = isSubfolderWarn
|
|
592
|
+
? ` You are searching in "${path}" — consider searching from the workspace root or a different directory.`
|
|
593
|
+
: '';
|
|
594
|
+
return result + `\n\n⚠️ WARNING: ${consecutiveNoResults} consecutive searches returned no results.${warnScopeHint} Before your next action: use extract on a file you already found to read actual code, or use listFiles to discover what functions really exist. One more failed search will trigger the circuit breaker.`;
|
|
595
|
+
}
|
|
500
596
|
} else if (typeof result === 'string') {
|
|
597
|
+
// Successful search — reset consecutive counter
|
|
598
|
+
consecutiveNoResults = 0;
|
|
501
599
|
const entry = previousSearches.get(searchKey);
|
|
502
600
|
if (entry) entry.hadResults = true;
|
|
503
601
|
}
|
package/cjs/agent/ProbeAgent.cjs
CHANGED
|
@@ -27561,8 +27561,23 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
27561
27561
|
'- Searching "getUserData" ALREADY matches "get", "user", "data" and their variations.',
|
|
27562
27562
|
"- NEVER repeat the same search query \u2014 you will get the same results. Changing the path does NOT change this.",
|
|
27563
27563
|
"- NEVER search trivial variations of the same keyword (e.g., AllowedIPs then allowedIps then allowed_ips). This is wasteful \u2014 probe handles it.",
|
|
27564
|
-
"
|
|
27565
|
-
"
|
|
27564
|
+
"",
|
|
27565
|
+
"When a search returns no results:",
|
|
27566
|
+
'- If you searched a SUBFOLDER (e.g., path="gateway/"), the term might exist elsewhere.',
|
|
27567
|
+
" Try searching from the workspace root (omit the path parameter) or a different directory.",
|
|
27568
|
+
" But do NOT retry the same subfolder with different quoting \u2014 that will not help.",
|
|
27569
|
+
"- If you searched the WORKSPACE ROOT and got no results, the term does not exist in this codebase.",
|
|
27570
|
+
' Changing quotes, adding "func " prefix, or switching to method syntax will NOT help.',
|
|
27571
|
+
"- These are ALL the same failed search, NOT different searches:",
|
|
27572
|
+
' search("func ctxGetData") \u2192 no results',
|
|
27573
|
+
' search("ctxGetData") \u2192 no results \u2190 WASTED, same concept, different quoting',
|
|
27574
|
+
" search(ctxGetData) \u2192 no results \u2190 WASTED, same concept, no quotes",
|
|
27575
|
+
' search("ctx.GetData") \u2192 no results \u2190 WASTED, method syntax of same concept',
|
|
27576
|
+
' After the FIRST "no results" at a given scope, either widen the search path or try',
|
|
27577
|
+
" a fundamentally different approach: search for a broader concept, use listFiles",
|
|
27578
|
+
" to discover actual function names, or extract a known file to read real code.",
|
|
27579
|
+
"- If 2 searches return no results for a concept (across different scopes), the code likely",
|
|
27580
|
+
" uses different naming than you expect \u2014 discover the real names via extract or listFiles.",
|
|
27566
27581
|
"",
|
|
27567
27582
|
"When to use exact=true:",
|
|
27568
27583
|
"- Use exact=true when searching for a KNOWN symbol name (function, type, variable, struct).",
|
|
@@ -27615,6 +27630,21 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
27615
27630
|
' \u2192 search "ForwardMessage" \u2192 search "ForwardMessage" \u2192 search "ForwardMessage" (WRONG: repeating the exact same query)',
|
|
27616
27631
|
' \u2192 search "authentication" \u2192 wait \u2192 search "session management" \u2192 wait (WRONG: these are independent, run them in parallel)',
|
|
27617
27632
|
"",
|
|
27633
|
+
" WORST pattern \u2014 retrying a non-existent function with quote/syntax variations (this wastes 30 minutes):",
|
|
27634
|
+
' \u2192 search "func ctxGetData" \u2192 no results',
|
|
27635
|
+
' \u2192 search "ctxGetData" \u2192 no results \u2190 WRONG: same term without "func" prefix',
|
|
27636
|
+
' \u2192 search "ctx.GetData" \u2192 no results \u2190 WRONG: method syntax of same concept',
|
|
27637
|
+
' \u2192 search "ctx.SetData" \u2192 no results \u2190 WRONG: Set variant of same concept',
|
|
27638
|
+
" \u2192 search ctxGetData \u2192 no results \u2190 WRONG: unquoted version of same term",
|
|
27639
|
+
" \u2192 extract api.go \u2192 extract api.go \u2192 extract api.go (8 times!) \u2190 WRONG: re-reading same file",
|
|
27640
|
+
' FIX: After "func ctxGetData" returns no results in gateway/:',
|
|
27641
|
+
" Option A: Widen scope \u2014 search from the workspace root (omit path) in case the",
|
|
27642
|
+
" function is defined in a different package (e.g., apidef/, user/, config/).",
|
|
27643
|
+
" Option B: Discover real names \u2014 extract a file you KNOW uses context (e.g., a",
|
|
27644
|
+
" middleware file) and READ what functions it actually calls.",
|
|
27645
|
+
" Option C: Browse \u2014 use listFiles to see what files exist and extract the relevant ones.",
|
|
27646
|
+
" NEVER: retry the same concept with different quoting in the same directory.",
|
|
27647
|
+
"",
|
|
27618
27648
|
"Keyword tips:",
|
|
27619
27649
|
"- Common programming keywords are filtered as stopwords when unquoted: function, class, return, new, struct, impl, var, let, const, etc.",
|
|
27620
27650
|
'- Avoid searching for these alone \u2014 combine with a specific term (e.g., "middleware function" is fine, "function" alone is too generic).',
|
|
@@ -27653,7 +27683,7 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
27653
27683
|
" - Type references and imports \u2192 include type definitions.",
|
|
27654
27684
|
" - Registered handlers/middleware \u2192 include all registered items.",
|
|
27655
27685
|
"6. If a search returns results, use extract to verify relevance. Run multiple extracts in parallel too.",
|
|
27656
|
-
"7. If a search returns NO results
|
|
27686
|
+
"7. If a search returns NO results: widen the path scope if you searched a subfolder, or move on. Do NOT retry with quote/syntax variations \u2014 they search the same index.",
|
|
27657
27687
|
"8. Once you have enough targets (typically 5-15), output your final JSON answer immediately.",
|
|
27658
27688
|
"",
|
|
27659
27689
|
`Query: ${searchQuery}`,
|
|
@@ -27713,7 +27743,14 @@ var init_vercel = __esm({
|
|
|
27713
27743
|
const previousSearches = /* @__PURE__ */ new Map();
|
|
27714
27744
|
const dupBlockCounts = /* @__PURE__ */ new Map();
|
|
27715
27745
|
const paginationCounts = /* @__PURE__ */ new Map();
|
|
27746
|
+
let consecutiveNoResults = 0;
|
|
27747
|
+
const MAX_CONSECUTIVE_NO_RESULTS = 4;
|
|
27748
|
+
const failedConcepts = /* @__PURE__ */ new Map();
|
|
27716
27749
|
const MAX_PAGES_PER_QUERY = 3;
|
|
27750
|
+
function normalizeQueryConcept(query2) {
|
|
27751
|
+
if (!query2) return "";
|
|
27752
|
+
return query2.replace(/^["']|["']$/g, "").replace(/\./g, "").replace(/[_\-\s]+/g, "").toLowerCase().trim();
|
|
27753
|
+
}
|
|
27717
27754
|
return (0, import_ai.tool)({
|
|
27718
27755
|
name: "search",
|
|
27719
27756
|
description: searchDelegate ? searchDelegateDescription : searchDescription,
|
|
@@ -27782,6 +27819,41 @@ var init_vercel = __esm({
|
|
|
27782
27819
|
}
|
|
27783
27820
|
previousSearches.set(searchKey, { hadResults: false });
|
|
27784
27821
|
paginationCounts.set(searchKey, 0);
|
|
27822
|
+
const normalizedKey = `${searchPath}::${normalizeQueryConcept(searchQuery)}`;
|
|
27823
|
+
if (failedConcepts.has(normalizedKey) && failedConcepts.get(normalizedKey) >= 2) {
|
|
27824
|
+
const conceptCount = failedConcepts.get(normalizedKey) + 1;
|
|
27825
|
+
failedConcepts.set(normalizedKey, conceptCount);
|
|
27826
|
+
if (debug) {
|
|
27827
|
+
console.error(`[CONCEPT-DEDUP] Blocked variation of failed concept (${conceptCount}x): "${searchQuery}" normalized to "${normalizeQueryConcept(searchQuery)}"`);
|
|
27828
|
+
}
|
|
27829
|
+
const isSubfolder = path9 && path9 !== effectiveSearchCwd && path9 !== ".";
|
|
27830
|
+
const scopeHint = isSubfolder ? `
|
|
27831
|
+
- Try searching from the workspace root (omit the path parameter) \u2014 the term may exist in a different directory` : `
|
|
27832
|
+
- The term does not exist in this codebase at any path`;
|
|
27833
|
+
return `CONCEPT ALREADY FAILED (${conceptCount} variations tried). You already searched for "${normalizeQueryConcept(searchQuery)}" with different quoting/syntax in this path and got NO results each time. Changing quotes, adding "func" prefix, or switching to method syntax will NOT change the results.
|
|
27834
|
+
|
|
27835
|
+
Change your strategy:${scopeHint}
|
|
27836
|
+
- Use extract on a file you ALREADY found to read actual code and discover real function/type names
|
|
27837
|
+
- Use listFiles to browse directories and find what functions actually exist
|
|
27838
|
+
- Search for a BROADER concept (e.g., instead of "ctxGetData", try "context" or "middleware data access")
|
|
27839
|
+
- If you have enough information from prior searches, provide your final answer NOW`;
|
|
27840
|
+
}
|
|
27841
|
+
if (consecutiveNoResults >= MAX_CONSECUTIVE_NO_RESULTS) {
|
|
27842
|
+
if (debug) {
|
|
27843
|
+
console.error(`[CIRCUIT-BREAKER] ${consecutiveNoResults} consecutive no-result searches, blocking: "${searchQuery}"`);
|
|
27844
|
+
}
|
|
27845
|
+
const isSubfolderCB = path9 && path9 !== effectiveSearchCwd && path9 !== ".";
|
|
27846
|
+
const cbScopeHint = isSubfolderCB ? `
|
|
27847
|
+
- You have been searching in "${path9}" \u2014 try searching from the workspace root or a different directory` : "";
|
|
27848
|
+
return `CIRCUIT BREAKER: Your last ${consecutiveNoResults} searches ALL returned no results. You appear to be guessing function/type names that don't match what's actually in the code.
|
|
27849
|
+
|
|
27850
|
+
Change your approach:${cbScopeHint}
|
|
27851
|
+
1. Use extract on files you already found \u2014 read the actual code to discover real function names
|
|
27852
|
+
2. Use listFiles to browse directories and see what files/functions actually exist
|
|
27853
|
+
3. If you found some results earlier, those are likely sufficient \u2014 provide your final answer
|
|
27854
|
+
|
|
27855
|
+
Retrying search query variations will not help. Discover real names from real code instead.`;
|
|
27856
|
+
}
|
|
27785
27857
|
} else {
|
|
27786
27858
|
const pageCount = (paginationCounts.get(searchKey) || 0) + 1;
|
|
27787
27859
|
paginationCounts.set(searchKey, pageCount);
|
|
@@ -27795,10 +27867,24 @@ var init_vercel = __esm({
|
|
|
27795
27867
|
try {
|
|
27796
27868
|
const result = maybeAnnotate(await runRawSearch());
|
|
27797
27869
|
if (typeof result === "string" && result.includes("No results found")) {
|
|
27870
|
+
consecutiveNoResults++;
|
|
27871
|
+
const normalizedKey = `${searchPath}::${normalizeQueryConcept(searchQuery)}`;
|
|
27872
|
+
failedConcepts.set(normalizedKey, (failedConcepts.get(normalizedKey) || 0) + 1);
|
|
27873
|
+
if (debug) {
|
|
27874
|
+
console.error(`[NO-RESULTS] consecutiveNoResults=${consecutiveNoResults}, concept "${normalizeQueryConcept(searchQuery)}" failed ${failedConcepts.get(normalizedKey)}x`);
|
|
27875
|
+
}
|
|
27798
27876
|
if (/^[A-Z]+-\d+$/.test(searchQuery.trim()) || /^[A-Z]+-\d+$/.test(searchQuery.replace(/"/g, "").trim())) {
|
|
27799
27877
|
return result + "\n\n\u26A0\uFE0F Your query looks like a ticket/issue ID (e.g., JIRA-1234). Ticket IDs are rarely present in source code. Search for the technical concepts described in the ticket instead (e.g., function names, error messages, variable names).";
|
|
27800
27878
|
}
|
|
27879
|
+
if (consecutiveNoResults >= MAX_CONSECUTIVE_NO_RESULTS - 1) {
|
|
27880
|
+
const isSubfolderWarn = path9 && path9 !== effectiveSearchCwd && path9 !== ".";
|
|
27881
|
+
const warnScopeHint = isSubfolderWarn ? ` You are searching in "${path9}" \u2014 consider searching from the workspace root or a different directory.` : "";
|
|
27882
|
+
return result + `
|
|
27883
|
+
|
|
27884
|
+
\u26A0\uFE0F WARNING: ${consecutiveNoResults} consecutive searches returned no results.${warnScopeHint} Before your next action: use extract on a file you already found to read actual code, or use listFiles to discover what functions really exist. One more failed search will trigger the circuit breaker.`;
|
|
27885
|
+
}
|
|
27801
27886
|
} else if (typeof result === "string") {
|
|
27887
|
+
consecutiveNoResults = 0;
|
|
27802
27888
|
const entry = previousSearches.get(searchKey);
|
|
27803
27889
|
if (entry) entry.hadResults = true;
|
|
27804
27890
|
}
|
package/cjs/index.cjs
CHANGED
|
@@ -103080,8 +103080,23 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
103080
103080
|
'- Searching "getUserData" ALREADY matches "get", "user", "data" and their variations.',
|
|
103081
103081
|
"- NEVER repeat the same search query \u2014 you will get the same results. Changing the path does NOT change this.",
|
|
103082
103082
|
"- NEVER search trivial variations of the same keyword (e.g., AllowedIPs then allowedIps then allowed_ips). This is wasteful \u2014 probe handles it.",
|
|
103083
|
-
"
|
|
103084
|
-
"
|
|
103083
|
+
"",
|
|
103084
|
+
"When a search returns no results:",
|
|
103085
|
+
'- If you searched a SUBFOLDER (e.g., path="gateway/"), the term might exist elsewhere.',
|
|
103086
|
+
" Try searching from the workspace root (omit the path parameter) or a different directory.",
|
|
103087
|
+
" But do NOT retry the same subfolder with different quoting \u2014 that will not help.",
|
|
103088
|
+
"- If you searched the WORKSPACE ROOT and got no results, the term does not exist in this codebase.",
|
|
103089
|
+
' Changing quotes, adding "func " prefix, or switching to method syntax will NOT help.',
|
|
103090
|
+
"- These are ALL the same failed search, NOT different searches:",
|
|
103091
|
+
' search("func ctxGetData") \u2192 no results',
|
|
103092
|
+
' search("ctxGetData") \u2192 no results \u2190 WASTED, same concept, different quoting',
|
|
103093
|
+
" search(ctxGetData) \u2192 no results \u2190 WASTED, same concept, no quotes",
|
|
103094
|
+
' search("ctx.GetData") \u2192 no results \u2190 WASTED, method syntax of same concept',
|
|
103095
|
+
' After the FIRST "no results" at a given scope, either widen the search path or try',
|
|
103096
|
+
" a fundamentally different approach: search for a broader concept, use listFiles",
|
|
103097
|
+
" to discover actual function names, or extract a known file to read real code.",
|
|
103098
|
+
"- If 2 searches return no results for a concept (across different scopes), the code likely",
|
|
103099
|
+
" uses different naming than you expect \u2014 discover the real names via extract or listFiles.",
|
|
103085
103100
|
"",
|
|
103086
103101
|
"When to use exact=true:",
|
|
103087
103102
|
"- Use exact=true when searching for a KNOWN symbol name (function, type, variable, struct).",
|
|
@@ -103134,6 +103149,21 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
103134
103149
|
' \u2192 search "ForwardMessage" \u2192 search "ForwardMessage" \u2192 search "ForwardMessage" (WRONG: repeating the exact same query)',
|
|
103135
103150
|
' \u2192 search "authentication" \u2192 wait \u2192 search "session management" \u2192 wait (WRONG: these are independent, run them in parallel)',
|
|
103136
103151
|
"",
|
|
103152
|
+
" WORST pattern \u2014 retrying a non-existent function with quote/syntax variations (this wastes 30 minutes):",
|
|
103153
|
+
' \u2192 search "func ctxGetData" \u2192 no results',
|
|
103154
|
+
' \u2192 search "ctxGetData" \u2192 no results \u2190 WRONG: same term without "func" prefix',
|
|
103155
|
+
' \u2192 search "ctx.GetData" \u2192 no results \u2190 WRONG: method syntax of same concept',
|
|
103156
|
+
' \u2192 search "ctx.SetData" \u2192 no results \u2190 WRONG: Set variant of same concept',
|
|
103157
|
+
" \u2192 search ctxGetData \u2192 no results \u2190 WRONG: unquoted version of same term",
|
|
103158
|
+
" \u2192 extract api.go \u2192 extract api.go \u2192 extract api.go (8 times!) \u2190 WRONG: re-reading same file",
|
|
103159
|
+
' FIX: After "func ctxGetData" returns no results in gateway/:',
|
|
103160
|
+
" Option A: Widen scope \u2014 search from the workspace root (omit path) in case the",
|
|
103161
|
+
" function is defined in a different package (e.g., apidef/, user/, config/).",
|
|
103162
|
+
" Option B: Discover real names \u2014 extract a file you KNOW uses context (e.g., a",
|
|
103163
|
+
" middleware file) and READ what functions it actually calls.",
|
|
103164
|
+
" Option C: Browse \u2014 use listFiles to see what files exist and extract the relevant ones.",
|
|
103165
|
+
" NEVER: retry the same concept with different quoting in the same directory.",
|
|
103166
|
+
"",
|
|
103137
103167
|
"Keyword tips:",
|
|
103138
103168
|
"- Common programming keywords are filtered as stopwords when unquoted: function, class, return, new, struct, impl, var, let, const, etc.",
|
|
103139
103169
|
'- Avoid searching for these alone \u2014 combine with a specific term (e.g., "middleware function" is fine, "function" alone is too generic).',
|
|
@@ -103172,7 +103202,7 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
103172
103202
|
" - Type references and imports \u2192 include type definitions.",
|
|
103173
103203
|
" - Registered handlers/middleware \u2192 include all registered items.",
|
|
103174
103204
|
"6. If a search returns results, use extract to verify relevance. Run multiple extracts in parallel too.",
|
|
103175
|
-
"7. If a search returns NO results
|
|
103205
|
+
"7. If a search returns NO results: widen the path scope if you searched a subfolder, or move on. Do NOT retry with quote/syntax variations \u2014 they search the same index.",
|
|
103176
103206
|
"8. Once you have enough targets (typically 5-15), output your final JSON answer immediately.",
|
|
103177
103207
|
"",
|
|
103178
103208
|
`Query: ${searchQuery}`,
|
|
@@ -103232,7 +103262,14 @@ var init_vercel = __esm({
|
|
|
103232
103262
|
const previousSearches = /* @__PURE__ */ new Map();
|
|
103233
103263
|
const dupBlockCounts = /* @__PURE__ */ new Map();
|
|
103234
103264
|
const paginationCounts = /* @__PURE__ */ new Map();
|
|
103265
|
+
let consecutiveNoResults = 0;
|
|
103266
|
+
const MAX_CONSECUTIVE_NO_RESULTS = 4;
|
|
103267
|
+
const failedConcepts = /* @__PURE__ */ new Map();
|
|
103235
103268
|
const MAX_PAGES_PER_QUERY = 3;
|
|
103269
|
+
function normalizeQueryConcept(query2) {
|
|
103270
|
+
if (!query2) return "";
|
|
103271
|
+
return query2.replace(/^["']|["']$/g, "").replace(/\./g, "").replace(/[_\-\s]+/g, "").toLowerCase().trim();
|
|
103272
|
+
}
|
|
103236
103273
|
return (0, import_ai5.tool)({
|
|
103237
103274
|
name: "search",
|
|
103238
103275
|
description: searchDelegate ? searchDelegateDescription : searchDescription,
|
|
@@ -103301,6 +103338,41 @@ var init_vercel = __esm({
|
|
|
103301
103338
|
}
|
|
103302
103339
|
previousSearches.set(searchKey, { hadResults: false });
|
|
103303
103340
|
paginationCounts.set(searchKey, 0);
|
|
103341
|
+
const normalizedKey = `${searchPath}::${normalizeQueryConcept(searchQuery)}`;
|
|
103342
|
+
if (failedConcepts.has(normalizedKey) && failedConcepts.get(normalizedKey) >= 2) {
|
|
103343
|
+
const conceptCount = failedConcepts.get(normalizedKey) + 1;
|
|
103344
|
+
failedConcepts.set(normalizedKey, conceptCount);
|
|
103345
|
+
if (debug) {
|
|
103346
|
+
console.error(`[CONCEPT-DEDUP] Blocked variation of failed concept (${conceptCount}x): "${searchQuery}" normalized to "${normalizeQueryConcept(searchQuery)}"`);
|
|
103347
|
+
}
|
|
103348
|
+
const isSubfolder = path9 && path9 !== effectiveSearchCwd && path9 !== ".";
|
|
103349
|
+
const scopeHint = isSubfolder ? `
|
|
103350
|
+
- Try searching from the workspace root (omit the path parameter) \u2014 the term may exist in a different directory` : `
|
|
103351
|
+
- The term does not exist in this codebase at any path`;
|
|
103352
|
+
return `CONCEPT ALREADY FAILED (${conceptCount} variations tried). You already searched for "${normalizeQueryConcept(searchQuery)}" with different quoting/syntax in this path and got NO results each time. Changing quotes, adding "func" prefix, or switching to method syntax will NOT change the results.
|
|
103353
|
+
|
|
103354
|
+
Change your strategy:${scopeHint}
|
|
103355
|
+
- Use extract on a file you ALREADY found to read actual code and discover real function/type names
|
|
103356
|
+
- Use listFiles to browse directories and find what functions actually exist
|
|
103357
|
+
- Search for a BROADER concept (e.g., instead of "ctxGetData", try "context" or "middleware data access")
|
|
103358
|
+
- If you have enough information from prior searches, provide your final answer NOW`;
|
|
103359
|
+
}
|
|
103360
|
+
if (consecutiveNoResults >= MAX_CONSECUTIVE_NO_RESULTS) {
|
|
103361
|
+
if (debug) {
|
|
103362
|
+
console.error(`[CIRCUIT-BREAKER] ${consecutiveNoResults} consecutive no-result searches, blocking: "${searchQuery}"`);
|
|
103363
|
+
}
|
|
103364
|
+
const isSubfolderCB = path9 && path9 !== effectiveSearchCwd && path9 !== ".";
|
|
103365
|
+
const cbScopeHint = isSubfolderCB ? `
|
|
103366
|
+
- You have been searching in "${path9}" \u2014 try searching from the workspace root or a different directory` : "";
|
|
103367
|
+
return `CIRCUIT BREAKER: Your last ${consecutiveNoResults} searches ALL returned no results. You appear to be guessing function/type names that don't match what's actually in the code.
|
|
103368
|
+
|
|
103369
|
+
Change your approach:${cbScopeHint}
|
|
103370
|
+
1. Use extract on files you already found \u2014 read the actual code to discover real function names
|
|
103371
|
+
2. Use listFiles to browse directories and see what files/functions actually exist
|
|
103372
|
+
3. If you found some results earlier, those are likely sufficient \u2014 provide your final answer
|
|
103373
|
+
|
|
103374
|
+
Retrying search query variations will not help. Discover real names from real code instead.`;
|
|
103375
|
+
}
|
|
103304
103376
|
} else {
|
|
103305
103377
|
const pageCount = (paginationCounts.get(searchKey) || 0) + 1;
|
|
103306
103378
|
paginationCounts.set(searchKey, pageCount);
|
|
@@ -103314,10 +103386,24 @@ var init_vercel = __esm({
|
|
|
103314
103386
|
try {
|
|
103315
103387
|
const result = maybeAnnotate(await runRawSearch());
|
|
103316
103388
|
if (typeof result === "string" && result.includes("No results found")) {
|
|
103389
|
+
consecutiveNoResults++;
|
|
103390
|
+
const normalizedKey = `${searchPath}::${normalizeQueryConcept(searchQuery)}`;
|
|
103391
|
+
failedConcepts.set(normalizedKey, (failedConcepts.get(normalizedKey) || 0) + 1);
|
|
103392
|
+
if (debug) {
|
|
103393
|
+
console.error(`[NO-RESULTS] consecutiveNoResults=${consecutiveNoResults}, concept "${normalizeQueryConcept(searchQuery)}" failed ${failedConcepts.get(normalizedKey)}x`);
|
|
103394
|
+
}
|
|
103317
103395
|
if (/^[A-Z]+-\d+$/.test(searchQuery.trim()) || /^[A-Z]+-\d+$/.test(searchQuery.replace(/"/g, "").trim())) {
|
|
103318
103396
|
return result + "\n\n\u26A0\uFE0F Your query looks like a ticket/issue ID (e.g., JIRA-1234). Ticket IDs are rarely present in source code. Search for the technical concepts described in the ticket instead (e.g., function names, error messages, variable names).";
|
|
103319
103397
|
}
|
|
103398
|
+
if (consecutiveNoResults >= MAX_CONSECUTIVE_NO_RESULTS - 1) {
|
|
103399
|
+
const isSubfolderWarn = path9 && path9 !== effectiveSearchCwd && path9 !== ".";
|
|
103400
|
+
const warnScopeHint = isSubfolderWarn ? ` You are searching in "${path9}" \u2014 consider searching from the workspace root or a different directory.` : "";
|
|
103401
|
+
return result + `
|
|
103402
|
+
|
|
103403
|
+
\u26A0\uFE0F WARNING: ${consecutiveNoResults} consecutive searches returned no results.${warnScopeHint} Before your next action: use extract on a file you already found to read actual code, or use listFiles to discover what functions really exist. One more failed search will trigger the circuit breaker.`;
|
|
103404
|
+
}
|
|
103320
103405
|
} else if (typeof result === "string") {
|
|
103406
|
+
consecutiveNoResults = 0;
|
|
103321
103407
|
const entry = previousSearches.get(searchKey);
|
|
103322
103408
|
if (entry) entry.hadResults = true;
|
|
103323
103409
|
}
|
package/package.json
CHANGED
package/src/tools/vercel.js
CHANGED
|
@@ -248,8 +248,23 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
248
248
|
'- Searching "getUserData" ALREADY matches "get", "user", "data" and their variations.',
|
|
249
249
|
'- NEVER repeat the same search query — you will get the same results. Changing the path does NOT change this.',
|
|
250
250
|
'- NEVER search trivial variations of the same keyword (e.g., AllowedIPs then allowedIps then allowed_ips). This is wasteful — probe handles it.',
|
|
251
|
-
'
|
|
252
|
-
'
|
|
251
|
+
'',
|
|
252
|
+
'When a search returns no results:',
|
|
253
|
+
'- If you searched a SUBFOLDER (e.g., path="gateway/"), the term might exist elsewhere.',
|
|
254
|
+
' Try searching from the workspace root (omit the path parameter) or a different directory.',
|
|
255
|
+
' But do NOT retry the same subfolder with different quoting — that will not help.',
|
|
256
|
+
'- If you searched the WORKSPACE ROOT and got no results, the term does not exist in this codebase.',
|
|
257
|
+
' Changing quotes, adding "func " prefix, or switching to method syntax will NOT help.',
|
|
258
|
+
'- These are ALL the same failed search, NOT different searches:',
|
|
259
|
+
' search("func ctxGetData") → no results',
|
|
260
|
+
' search("ctxGetData") → no results ← WASTED, same concept, different quoting',
|
|
261
|
+
' search(ctxGetData) → no results ← WASTED, same concept, no quotes',
|
|
262
|
+
' search("ctx.GetData") → no results ← WASTED, method syntax of same concept',
|
|
263
|
+
' After the FIRST "no results" at a given scope, either widen the search path or try',
|
|
264
|
+
' a fundamentally different approach: search for a broader concept, use listFiles',
|
|
265
|
+
' to discover actual function names, or extract a known file to read real code.',
|
|
266
|
+
'- If 2 searches return no results for a concept (across different scopes), the code likely',
|
|
267
|
+
' uses different naming than you expect — discover the real names via extract or listFiles.',
|
|
253
268
|
'',
|
|
254
269
|
'When to use exact=true:',
|
|
255
270
|
'- Use exact=true when searching for a KNOWN symbol name (function, type, variable, struct).',
|
|
@@ -302,6 +317,21 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
302
317
|
' → search "ForwardMessage" → search "ForwardMessage" → search "ForwardMessage" (WRONG: repeating the exact same query)',
|
|
303
318
|
' → search "authentication" → wait → search "session management" → wait (WRONG: these are independent, run them in parallel)',
|
|
304
319
|
'',
|
|
320
|
+
' WORST pattern — retrying a non-existent function with quote/syntax variations (this wastes 30 minutes):',
|
|
321
|
+
' → search "func ctxGetData" → no results',
|
|
322
|
+
' → search "ctxGetData" → no results ← WRONG: same term without "func" prefix',
|
|
323
|
+
' → search "ctx.GetData" → no results ← WRONG: method syntax of same concept',
|
|
324
|
+
' → search "ctx.SetData" → no results ← WRONG: Set variant of same concept',
|
|
325
|
+
' → search ctxGetData → no results ← WRONG: unquoted version of same term',
|
|
326
|
+
' → extract api.go → extract api.go → extract api.go (8 times!) ← WRONG: re-reading same file',
|
|
327
|
+
' FIX: After "func ctxGetData" returns no results in gateway/:',
|
|
328
|
+
' Option A: Widen scope — search from the workspace root (omit path) in case the',
|
|
329
|
+
' function is defined in a different package (e.g., apidef/, user/, config/).',
|
|
330
|
+
' Option B: Discover real names — extract a file you KNOW uses context (e.g., a',
|
|
331
|
+
' middleware file) and READ what functions it actually calls.',
|
|
332
|
+
' Option C: Browse — use listFiles to see what files exist and extract the relevant ones.',
|
|
333
|
+
' NEVER: retry the same concept with different quoting in the same directory.',
|
|
334
|
+
'',
|
|
305
335
|
'Keyword tips:',
|
|
306
336
|
'- Common programming keywords are filtered as stopwords when unquoted: function, class, return, new, struct, impl, var, let, const, etc.',
|
|
307
337
|
'- Avoid searching for these alone — combine with a specific term (e.g., "middleware function" is fine, "function" alone is too generic).',
|
|
@@ -340,7 +370,7 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
340
370
|
' - Type references and imports → include type definitions.',
|
|
341
371
|
' - Registered handlers/middleware → include all registered items.',
|
|
342
372
|
'6. If a search returns results, use extract to verify relevance. Run multiple extracts in parallel too.',
|
|
343
|
-
'7. If a search returns NO results
|
|
373
|
+
'7. If a search returns NO results: widen the path scope if you searched a subfolder, or move on. Do NOT retry with quote/syntax variations — they search the same index.',
|
|
344
374
|
'8. Once you have enough targets (typically 5-15), output your final JSON answer immediately.',
|
|
345
375
|
'',
|
|
346
376
|
`Query: ${searchQuery}`,
|
|
@@ -388,8 +418,30 @@ export const searchTool = (options = {}) => {
|
|
|
388
418
|
const dupBlockCounts = new Map();
|
|
389
419
|
// Track pagination counts per query to cap runaway pagination
|
|
390
420
|
const paginationCounts = new Map();
|
|
421
|
+
// Track consecutive no-result searches (circuit breaker)
|
|
422
|
+
let consecutiveNoResults = 0;
|
|
423
|
+
const MAX_CONSECUTIVE_NO_RESULTS = 4;
|
|
424
|
+
// Track normalized query concepts for fuzzy dedup (catches quote/syntax variations)
|
|
425
|
+
const failedConcepts = new Map(); // normalizedKey → count
|
|
391
426
|
const MAX_PAGES_PER_QUERY = 3;
|
|
392
427
|
|
|
428
|
+
/**
|
|
429
|
+
* Normalize a search query to detect syntax-level duplicates.
|
|
430
|
+
* Strips quotes, dots, underscores/hyphens, and lowercases.
|
|
431
|
+
* "ctxGetData", "ctx.GetData", "ctx_get_data" all → "ctxgetdata"
|
|
432
|
+
* Note: does NOT strip language keywords (func, type) — those change search
|
|
433
|
+
* semantics and are already handled as stopwords by the Rust search engine.
|
|
434
|
+
*/
|
|
435
|
+
function normalizeQueryConcept(query) {
|
|
436
|
+
if (!query) return '';
|
|
437
|
+
return query
|
|
438
|
+
.replace(/^["']|["']$/g, '') // strip outer quotes
|
|
439
|
+
.replace(/\./g, '') // "ctx.GetData" → "ctxGetData"
|
|
440
|
+
.replace(/[_\-\s]+/g, '') // strip underscores/hyphens/spaces
|
|
441
|
+
.toLowerCase()
|
|
442
|
+
.trim();
|
|
443
|
+
}
|
|
444
|
+
|
|
393
445
|
return tool({
|
|
394
446
|
name: 'search',
|
|
395
447
|
description: searchDelegate
|
|
@@ -478,6 +530,35 @@ export const searchTool = (options = {}) => {
|
|
|
478
530
|
}
|
|
479
531
|
previousSearches.set(searchKey, { hadResults: false });
|
|
480
532
|
paginationCounts.set(searchKey, 0);
|
|
533
|
+
|
|
534
|
+
// Fuzzy concept dedup: catch quote/syntax variations of the same failed concept
|
|
535
|
+
// e.g., "func ctxGetData", "ctxGetData", "ctx.GetData" all normalize to "ctxgetdata"
|
|
536
|
+
const normalizedKey = `${searchPath}::${normalizeQueryConcept(searchQuery)}`;
|
|
537
|
+
if (failedConcepts.has(normalizedKey) && failedConcepts.get(normalizedKey) >= 2) {
|
|
538
|
+
const conceptCount = failedConcepts.get(normalizedKey) + 1;
|
|
539
|
+
failedConcepts.set(normalizedKey, conceptCount);
|
|
540
|
+
if (debug) {
|
|
541
|
+
console.error(`[CONCEPT-DEDUP] Blocked variation of failed concept (${conceptCount}x): "${searchQuery}" normalized to "${normalizeQueryConcept(searchQuery)}"`);
|
|
542
|
+
}
|
|
543
|
+
const isSubfolder = path && path !== effectiveSearchCwd && path !== '.';
|
|
544
|
+
const scopeHint = isSubfolder
|
|
545
|
+
? `\n- Try searching from the workspace root (omit the path parameter) — the term may exist in a different directory`
|
|
546
|
+
: `\n- The term does not exist in this codebase at any path`;
|
|
547
|
+
return `CONCEPT ALREADY FAILED (${conceptCount} variations tried). You already searched for "${normalizeQueryConcept(searchQuery)}" with different quoting/syntax in this path and got NO results each time. Changing quotes, adding "func" prefix, or switching to method syntax will NOT change the results.\n\nChange your strategy:${scopeHint}\n- Use extract on a file you ALREADY found to read actual code and discover real function/type names\n- Use listFiles to browse directories and find what functions actually exist\n- Search for a BROADER concept (e.g., instead of "ctxGetData", try "context" or "middleware data access")\n- If you have enough information from prior searches, provide your final answer NOW`;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Circuit breaker: too many consecutive no-result searches means the model
|
|
551
|
+
// is stuck in a loop guessing names that don't exist
|
|
552
|
+
if (consecutiveNoResults >= MAX_CONSECUTIVE_NO_RESULTS) {
|
|
553
|
+
if (debug) {
|
|
554
|
+
console.error(`[CIRCUIT-BREAKER] ${consecutiveNoResults} consecutive no-result searches, blocking: "${searchQuery}"`);
|
|
555
|
+
}
|
|
556
|
+
const isSubfolderCB = path && path !== effectiveSearchCwd && path !== '.';
|
|
557
|
+
const cbScopeHint = isSubfolderCB
|
|
558
|
+
? `\n- You have been searching in "${path}" — try searching from the workspace root or a different directory`
|
|
559
|
+
: '';
|
|
560
|
+
return `CIRCUIT BREAKER: Your last ${consecutiveNoResults} searches ALL returned no results. You appear to be guessing function/type names that don't match what's actually in the code.\n\nChange your approach:${cbScopeHint}\n1. Use extract on files you already found — read the actual code to discover real function names\n2. Use listFiles to browse directories and see what files/functions actually exist\n3. If you found some results earlier, those are likely sufficient — provide your final answer\n\nRetrying search query variations will not help. Discover real names from real code instead.`;
|
|
561
|
+
}
|
|
481
562
|
} else {
|
|
482
563
|
// Cap pagination to prevent runaway page-through of broad queries
|
|
483
564
|
const pageCount = (paginationCounts.get(searchKey) || 0) + 1;
|
|
@@ -493,11 +574,28 @@ export const searchTool = (options = {}) => {
|
|
|
493
574
|
const result = maybeAnnotate(await runRawSearch());
|
|
494
575
|
// Track whether this search had results for better dedup messages
|
|
495
576
|
if (typeof result === 'string' && result.includes('No results found')) {
|
|
577
|
+
// Track consecutive no-results and failed concepts for circuit breaker
|
|
578
|
+
consecutiveNoResults++;
|
|
579
|
+
const normalizedKey = `${searchPath}::${normalizeQueryConcept(searchQuery)}`;
|
|
580
|
+
failedConcepts.set(normalizedKey, (failedConcepts.get(normalizedKey) || 0) + 1);
|
|
581
|
+
if (debug) {
|
|
582
|
+
console.error(`[NO-RESULTS] consecutiveNoResults=${consecutiveNoResults}, concept "${normalizeQueryConcept(searchQuery)}" failed ${failedConcepts.get(normalizedKey)}x`);
|
|
583
|
+
}
|
|
496
584
|
// Append contextual hint for ticket/issue ID queries
|
|
497
585
|
if (/^[A-Z]+-\d+$/.test(searchQuery.trim()) || /^[A-Z]+-\d+$/.test(searchQuery.replace(/"/g, '').trim())) {
|
|
498
586
|
return result + '\n\n⚠️ Your query looks like a ticket/issue ID (e.g., JIRA-1234). Ticket IDs are rarely present in source code. Search for the technical concepts described in the ticket instead (e.g., function names, error messages, variable names).';
|
|
499
587
|
}
|
|
588
|
+
// Add a hint when approaching the circuit breaker threshold
|
|
589
|
+
if (consecutiveNoResults >= MAX_CONSECUTIVE_NO_RESULTS - 1) {
|
|
590
|
+
const isSubfolderWarn = path && path !== effectiveSearchCwd && path !== '.';
|
|
591
|
+
const warnScopeHint = isSubfolderWarn
|
|
592
|
+
? ` You are searching in "${path}" — consider searching from the workspace root or a different directory.`
|
|
593
|
+
: '';
|
|
594
|
+
return result + `\n\n⚠️ WARNING: ${consecutiveNoResults} consecutive searches returned no results.${warnScopeHint} Before your next action: use extract on a file you already found to read actual code, or use listFiles to discover what functions really exist. One more failed search will trigger the circuit breaker.`;
|
|
595
|
+
}
|
|
500
596
|
} else if (typeof result === 'string') {
|
|
597
|
+
// Successful search — reset consecutive counter
|
|
598
|
+
consecutiveNoResults = 0;
|
|
501
599
|
const entry = previousSearches.get(searchKey);
|
|
502
600
|
if (entry) entry.hadResults = true;
|
|
503
601
|
}
|