@probelabs/probe 0.6.0-rc280 → 0.6.0-rc282

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.
@@ -2876,7 +2876,7 @@ ${searchToolDesc1}
2876
2876
  - searchFiles: Find files by name patterns`;
2877
2877
 
2878
2878
  if (this.enableBash) {
2879
- systemPrompt += `\n- bash: Execute bash commands for system operations`;
2879
+ systemPrompt += `\n- bash: Execute bash commands for system operations (building, running tests, git, etc.). NEVER use bash for code exploration (no grep, cat, find, head, tail) — always use search and extract tools instead, they are faster and more accurate.`;
2880
2880
  }
2881
2881
 
2882
2882
  const searchGuidance1 = this.searchDelegate
@@ -2942,7 +2942,7 @@ ${searchToolDesc2}
2942
2942
  - searchFiles: Find files by name patterns`;
2943
2943
 
2944
2944
  if (this.enableBash) {
2945
- systemPrompt += `\n- bash: Execute bash commands for system operations`;
2945
+ systemPrompt += `\n- bash: Execute bash commands for system operations (building, running tests, git, etc.). NEVER use bash for code exploration (no grep, cat, find, head, tail) — always use search and extract tools instead, they are faster and more accurate.`;
2946
2946
  }
2947
2947
 
2948
2948
  const searchGuidance2 = this.searchDelegate
@@ -3018,7 +3018,8 @@ Follow these instructions carefully:
3018
3018
  3. You should always prefer the search tool for code-related questions.${this.searchDelegate ? ' Ask natural language questions — the search subagent handles keyword formulation and returns extracted code blocks. Use extract only to expand context or read full files.' : ' Search handles stemming and case variations automatically — do NOT try keyword variations manually. Read full files only if really necessary.'}
3019
3019
  4. Ensure to get really deep and understand the full picture before answering.
3020
3020
  5. Once the task is fully completed, use the attempt_completion tool to provide the final result.
3021
- 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.'}${this.allowEdit ? `
3021
+ 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.'}
3022
+ 7. NEVER use bash for code exploration (no grep, cat, find, head, tail, awk, sed) — always use search and extract tools instead. Bash is only for system operations like building, running tests, or git commands.${this.allowEdit ? `
3022
3023
  7. When modifying files, choose the appropriate tool:
3023
3024
  - Use 'edit' for all code modifications:
3024
3025
  * PREFERRED: Use start_line (and optionally end_line) for line-targeted editing — this is the safest and most precise approach.${this.hashLines ? ' Use the line:hash references from extract/search output (e.g. "42:ab") for integrity verification.' : ''} Always use extract first to see line numbers${this.hashLines ? ' and hashes' : ''}, then edit by line reference.
@@ -3035,22 +3036,30 @@ Follow these instructions carefully:
3035
3036
  // Use predefined prompts from shared module (imported at top of file)
3036
3037
  let systemMessage = '';
3037
3038
 
3038
- // Use custom prompt if provided
3039
- if (this.customPrompt) {
3039
+ // Build system message from predefined prompt + optional custom prompt
3040
+ if (this.customPrompt && this.promptType && predefinedPrompts[this.promptType]) {
3041
+ // Both: use predefined as base, append custom wrapped in tag
3042
+ systemMessage = "<role>" + predefinedPrompts[this.promptType] + "</role>";
3043
+ systemMessage += commonInstructions;
3044
+ systemMessage += "\n<custom-instructions>\n" + this.customPrompt + "\n</custom-instructions>";
3045
+ if (this.debug) {
3046
+ console.log(`[DEBUG] Using predefined prompt: ${this.promptType} + custom prompt`);
3047
+ }
3048
+ } else if (this.customPrompt) {
3049
+ // Only custom prompt
3040
3050
  systemMessage = "<role>" + this.customPrompt + "</role>";
3041
3051
  if (this.debug) {
3042
3052
  console.log(`[DEBUG] Using custom prompt`);
3043
3053
  }
3044
- }
3045
- // Use predefined prompt if specified
3046
- else if (this.promptType && predefinedPrompts[this.promptType]) {
3054
+ } else if (this.promptType && predefinedPrompts[this.promptType]) {
3055
+ // Only predefined prompt
3047
3056
  systemMessage = "<role>" + predefinedPrompts[this.promptType] + "</role>";
3048
3057
  if (this.debug) {
3049
3058
  console.log(`[DEBUG] Using predefined prompt: ${this.promptType}`);
3050
3059
  }
3051
3060
  systemMessage += commonInstructions;
3052
3061
  } else {
3053
- // Use the default prompt (code explorer) if no prompt type is specified
3062
+ // Default: code explorer
3054
3063
  systemMessage = "<role>" + predefinedPrompts['code-explorer'] + "</role>";
3055
3064
  if (this.debug) {
3056
3065
  console.log(`[DEBUG] Using default prompt: code explorer`);
@@ -9185,13 +9185,19 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
9185
9185
  "- listFiles: Understand directory structure to find where relevant code might live.",
9186
9186
  "",
9187
9187
  "CRITICAL - How probe search works (do NOT ignore):",
9188
- "- By default (exact=false), probe ALREADY handles stemming, case-insensitive matching, and camelCase/snake_case splitting.",
9188
+ "- By default (exact=false), probe ALREADY handles stemming, case-insensitive matching, and camelCase/snake_case splitting automatically.",
9189
9189
  '- Searching "allowed_ips" ALREADY matches "AllowedIPs", "allowedIps", "allowed_ips", etc. Do NOT manually try case/style variations.',
9190
9190
  '- Searching "getUserData" ALREADY matches "get", "user", "data" and their variations.',
9191
- "- NEVER repeat the same search query \u2014 you will get the same results.",
9191
+ "- NEVER repeat the same search query \u2014 you will get the same results. Changing the path does NOT change this.",
9192
9192
  "- NEVER search trivial variations of the same keyword (e.g., AllowedIPs then allowedIps then allowed_ips). This is wasteful \u2014 probe handles it.",
9193
- "- If a search returns no results, the term likely does not exist in that path. Try a genuinely DIFFERENT keyword or concept, not a variation.",
9194
- "- If 2-3 consecutive searches return no results for a concept, STOP searching for it and move on.",
9193
+ "- If a search returns no results, the term likely does not exist. Try a genuinely DIFFERENT keyword or concept, not a variation.",
9194
+ "- If 2-3 searches return no results for a concept, STOP searching for it and move on. Do NOT keep retrying.",
9195
+ "",
9196
+ "When to use exact=true:",
9197
+ "- Use exact=true when searching for a KNOWN symbol name (function, type, variable, struct).",
9198
+ "- exact=true matches the literal string only \u2014 no stemming, no splitting.",
9199
+ '- This is ideal for precise lookups: exact=true "ForwardMessage", exact=true "SessionLimiter", exact=true "ThrottleRetryLimit".',
9200
+ "- Do NOT use exact=true for exploratory/conceptual queries \u2014 use the default for those.",
9195
9201
  "",
9196
9202
  "GOOD search strategy (do this):",
9197
9203
  ' Query: "How does authentication work and how are sessions managed?"',
@@ -9200,10 +9206,18 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
9200
9206
  ' \u2192 search "allowlist middleware" (one search, probe handles IP/ip/Ip variations)',
9201
9207
  ' Query: "How does BM25 scoring work with SIMD optimization?"',
9202
9208
  ' \u2192 search "BM25 scoring" \u2192 search "SIMD optimization" (two different concepts)',
9209
+ ' Query: "Find ForwardMessage and SessionLimiter functions"',
9210
+ ' \u2192 search exact=true "ForwardMessage" \u2192 search exact=true "SessionLimiter" (known symbols, use exact)',
9211
+ ' Query: "Find ThrottleRetryLimit usage"',
9212
+ ' \u2192 search exact=true "ThrottleRetryLimit" (one search, if no results the symbol does not exist \u2014 stop)',
9203
9213
  "",
9204
9214
  "BAD search strategy (never do this):",
9205
- ' \u2192 search "AllowedIPs" \u2192 search "allowedIps" \u2192 search "allowed_ips" (WRONG: these are trivial case variations, probe handles them)',
9206
- ' \u2192 search "CIDR" \u2192 search "cidr" \u2192 search "Cidr" \u2192 search "*cidr*" (WRONG: same keyword repeated with variations)',
9215
+ ' \u2192 search "AllowedIPs" \u2192 search "allowedIps" \u2192 search "allowed_ips" (WRONG: case/style variations, probe handles them)',
9216
+ ' \u2192 search "limitDRL" \u2192 search "LimitDRL" (WRONG: case variation of same term)',
9217
+ ' \u2192 search "throttle_retry_limit" after searching "ThrottleRetryLimit" (WRONG: snake_case variation, probe handles it)',
9218
+ ' \u2192 search "ThrottleRetryLimit" path=tyk \u2192 search "ThrottleRetryLimit" path=gateway \u2192 search "ThrottleRetryLimit" path=apidef (WRONG: same query on different paths hoping for different results)',
9219
+ ' \u2192 search "func (k *RateLimitAndQuotaCheck) handleRateLimitFailure" (WRONG: do not search full function signatures, just use exact=true "handleRateLimitFailure")',
9220
+ ' \u2192 search "ForwardMessage" \u2192 search "ForwardMessage" \u2192 search "ForwardMessage" (WRONG: repeating the exact same query)',
9207
9221
  ' \u2192 search "error handling" \u2192 search "error handling" \u2192 search "error handling" (WRONG: repeating exact same query)',
9208
9222
  "",
9209
9223
  "Keyword tips:",
@@ -9212,12 +9226,13 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
9212
9226
  '- To bypass stopword filtering: wrap terms in quotes ("return", "struct") or set exact=true. Both disable stemming and splitting too.',
9213
9227
  "- Multiple words without operators use OR logic: foo bar = foo OR bar. Use AND explicitly if you need both: foo AND bar.",
9214
9228
  '- camelCase terms are split: getUserData becomes "get", "user", "data" \u2014 so one search covers all naming styles.',
9229
+ '- Do NOT search for full function signatures like "func (r *Type) Method(args)". Just search for the method name with exact=true.',
9215
9230
  "",
9216
9231
  "Strategy:",
9217
9232
  "1. Analyze the query - identify key concepts, entities, and relationships",
9218
- "2. Run ONE focused search per concept with the most natural keyword. Trust probe to handle variations.",
9233
+ "2. Run ONE focused search per concept. For known symbol names use exact=true. For concepts use default (exact=false).",
9219
9234
  "3. If a search returns results, use extract to verify relevance",
9220
- "4. Only try a different keyword if the first one returned irrelevant results (not if it returned no results \u2014 that means the concept is absent)",
9235
+ "4. If a search returns NO results, the term does not exist in the codebase. Do NOT retry with variations, different paths, or longer strings. Move on.",
9221
9236
  "5. Combine all relevant targets in your final response",
9222
9237
  "",
9223
9238
  `Query: ${searchQuery}`,
@@ -9538,6 +9553,36 @@ var init_vercel = __esm({
9538
9553
  } else if (targets) {
9539
9554
  const parsedTargets = parseTargets(targets);
9540
9555
  extractFiles = parsedTargets.map((target) => resolveTargetPath(target, effectiveCwd));
9556
+ if (options.allowedFolders && options.allowedFolders.length > 0) {
9557
+ extractFiles = extractFiles.map((target) => {
9558
+ const { filePart, suffix } = splitTargetSuffix(target);
9559
+ if (existsSync(filePart)) return target;
9560
+ const cwdPrefix = effectiveCwd.endsWith("/") ? effectiveCwd : effectiveCwd + "/";
9561
+ const relativePart = filePart.startsWith(cwdPrefix) ? filePart.slice(cwdPrefix.length) : null;
9562
+ if (relativePart) {
9563
+ for (const folder of options.allowedFolders) {
9564
+ const candidate = folder + "/" + relativePart;
9565
+ if (existsSync(candidate)) {
9566
+ if (debug) console.error(`[extract] Auto-fixed path: ${filePart} \u2192 ${candidate}`);
9567
+ return candidate + suffix;
9568
+ }
9569
+ }
9570
+ }
9571
+ for (const folder of options.allowedFolders) {
9572
+ const folderPrefix = folder.endsWith("/") ? folder : folder + "/";
9573
+ const wsParent = folderPrefix.replace(/[^/]+\/$/, "");
9574
+ if (filePart.startsWith(wsParent)) {
9575
+ const tail = filePart.slice(wsParent.length);
9576
+ const candidate = folderPrefix + tail;
9577
+ if (candidate !== filePart && existsSync(candidate)) {
9578
+ if (debug) console.error(`[extract] Auto-fixed path via workspace: ${filePart} \u2192 ${candidate}`);
9579
+ return candidate + suffix;
9580
+ }
9581
+ }
9582
+ }
9583
+ return target;
9584
+ });
9585
+ }
9541
9586
  let effectiveFormat = format;
9542
9587
  if (outline && format === "outline-xml") {
9543
9588
  effectiveFormat = "xml";
@@ -84097,7 +84142,7 @@ ${searchToolDesc1}
84097
84142
  - searchFiles: Find files by name patterns`;
84098
84143
  if (this.enableBash) {
84099
84144
  systemPrompt += `
84100
- - bash: Execute bash commands for system operations`;
84145
+ - bash: Execute bash commands for system operations (building, running tests, git, etc.). NEVER use bash for code exploration (no grep, cat, find, head, tail) \u2014 always use search and extract tools instead, they are faster and more accurate.`;
84101
84146
  }
84102
84147
  const searchGuidance1 = this.searchDelegate ? "1. Start with search \u2014 ask a question about what you want to understand. It returns extracted code blocks directly." : "1. Start with search to find relevant code patterns. One search per concept is usually enough \u2014 probe handles stemming and case variations.";
84103
84148
  const extractGuidance1 = this.searchDelegate ? "2. Use extract only if you need more context or a full file" : "2. Use extract to get detailed context when needed";
@@ -84152,7 +84197,7 @@ ${searchToolDesc2}
84152
84197
  - searchFiles: Find files by name patterns`;
84153
84198
  if (this.enableBash) {
84154
84199
  systemPrompt += `
84155
- - bash: Execute bash commands for system operations`;
84200
+ - bash: Execute bash commands for system operations (building, running tests, git, etc.). NEVER use bash for code exploration (no grep, cat, find, head, tail) \u2014 always use search and extract tools instead, they are faster and more accurate.`;
84156
84201
  }
84157
84202
  const searchGuidance2 = this.searchDelegate ? "1. Start with search \u2014 ask a question about what you want to understand. It returns extracted code blocks directly." : "1. Start with search to find relevant code patterns. One search per concept is usually enough \u2014 probe handles stemming and case variations.";
84158
84203
  const extractGuidance2 = this.searchDelegate ? "2. Use extract only if you need more context or a full file" : "2. Use extract to get detailed context when needed";
@@ -84218,7 +84263,8 @@ Follow these instructions carefully:
84218
84263
  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."}
84219
84264
  4. Ensure to get really deep and understand the full picture before answering.
84220
84265
  5. Once the task is fully completed, use the attempt_completion tool to provide the final result.
84221
- 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."}${this.allowEdit ? `
84266
+ 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."}
84267
+ 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 ? `
84222
84268
  7. When modifying files, choose the appropriate tool:
84223
84269
  - Use 'edit' for all code modifications:
84224
84270
  * PREFERRED: Use start_line (and optionally end_line) for line-targeted editing \u2014 this is the safest and most precise approach.${this.hashLines ? ' Use the line:hash references from extract/search output (e.g. "42:ab") for integrity verification.' : ""} Always use extract first to see line numbers${this.hashLines ? " and hashes" : ""}, then edit by line reference.
@@ -84232,7 +84278,14 @@ Follow these instructions carefully:
84232
84278
  </instructions>
84233
84279
  `;
84234
84280
  let systemMessage = "";
84235
- if (this.customPrompt) {
84281
+ if (this.customPrompt && this.promptType && predefinedPrompts[this.promptType]) {
84282
+ systemMessage = "<role>" + predefinedPrompts[this.promptType] + "</role>";
84283
+ systemMessage += commonInstructions;
84284
+ systemMessage += "\n<custom-instructions>\n" + this.customPrompt + "\n</custom-instructions>";
84285
+ if (this.debug) {
84286
+ console.log(`[DEBUG] Using predefined prompt: ${this.promptType} + custom prompt`);
84287
+ }
84288
+ } else if (this.customPrompt) {
84236
84289
  systemMessage = "<role>" + this.customPrompt + "</role>";
84237
84290
  if (this.debug) {
84238
84291
  console.log(`[DEBUG] Using custom prompt`);
@@ -154,7 +154,7 @@ export const searchDelegateDescription = 'Search code in the repository by askin
154
154
  export const queryDescription = 'Search code using ast-grep structural pattern matching. Use this tool to find specific code structures like functions, classes, or methods.';
155
155
  export const extractDescription = 'Extract code blocks from files based on file paths and optional line numbers. Use this tool to see complete context after finding relevant files. Line numbers from output can be used with edit start_line/end_line for precise editing.';
156
156
  export const delegateDescription = 'Automatically delegate big distinct tasks to specialized probe subagents within the agentic loop. Used by AI agents to break down complex requests into focused, parallel tasks.';
157
- export const bashDescription = 'Execute bash commands for system exploration and development tasks. Secure by default with built-in allow/deny lists.';
157
+ export const bashDescription = 'Execute bash commands for system operations: building, running tests, git, package management, etc. NEVER use for code exploration (no grep, cat, find, head, tail) — use search and extract tools instead. Secure by default with built-in allow/deny lists.';
158
158
  export const analyzeAllDescription = 'Answer questions that require analyzing ALL matching data in the codebase. Use for aggregate questions like "What features exist?", "List all API endpoints", "Count TODO comments". The AI automatically plans the search strategy, processes all results via map-reduce, and synthesizes a comprehensive answer. WARNING: Slower than search - only use when you need complete coverage.';
159
159
 
160
160
 
@@ -144,13 +144,19 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
144
144
  '- listFiles: Understand directory structure to find where relevant code might live.',
145
145
  '',
146
146
  'CRITICAL - How probe search works (do NOT ignore):',
147
- '- By default (exact=false), probe ALREADY handles stemming, case-insensitive matching, and camelCase/snake_case splitting.',
147
+ '- By default (exact=false), probe ALREADY handles stemming, case-insensitive matching, and camelCase/snake_case splitting automatically.',
148
148
  '- Searching "allowed_ips" ALREADY matches "AllowedIPs", "allowedIps", "allowed_ips", etc. Do NOT manually try case/style variations.',
149
149
  '- Searching "getUserData" ALREADY matches "get", "user", "data" and their variations.',
150
- '- NEVER repeat the same search query — you will get the same results.',
150
+ '- NEVER repeat the same search query — you will get the same results. Changing the path does NOT change this.',
151
151
  '- NEVER search trivial variations of the same keyword (e.g., AllowedIPs then allowedIps then allowed_ips). This is wasteful — probe handles it.',
152
- '- If a search returns no results, the term likely does not exist in that path. Try a genuinely DIFFERENT keyword or concept, not a variation.',
153
- '- If 2-3 consecutive searches return no results for a concept, STOP searching for it and move on.',
152
+ '- If a search returns no results, the term likely does not exist. Try a genuinely DIFFERENT keyword or concept, not a variation.',
153
+ '- If 2-3 searches return no results for a concept, STOP searching for it and move on. Do NOT keep retrying.',
154
+ '',
155
+ 'When to use exact=true:',
156
+ '- Use exact=true when searching for a KNOWN symbol name (function, type, variable, struct).',
157
+ '- exact=true matches the literal string only — no stemming, no splitting.',
158
+ '- This is ideal for precise lookups: exact=true "ForwardMessage", exact=true "SessionLimiter", exact=true "ThrottleRetryLimit".',
159
+ '- Do NOT use exact=true for exploratory/conceptual queries — use the default for those.',
154
160
  '',
155
161
  'GOOD search strategy (do this):',
156
162
  ' Query: "How does authentication work and how are sessions managed?"',
@@ -159,10 +165,18 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
159
165
  ' → search "allowlist middleware" (one search, probe handles IP/ip/Ip variations)',
160
166
  ' Query: "How does BM25 scoring work with SIMD optimization?"',
161
167
  ' → search "BM25 scoring" → search "SIMD optimization" (two different concepts)',
168
+ ' Query: "Find ForwardMessage and SessionLimiter functions"',
169
+ ' → search exact=true "ForwardMessage" → search exact=true "SessionLimiter" (known symbols, use exact)',
170
+ ' Query: "Find ThrottleRetryLimit usage"',
171
+ ' → search exact=true "ThrottleRetryLimit" (one search, if no results the symbol does not exist — stop)',
162
172
  '',
163
173
  'BAD search strategy (never do this):',
164
- ' → search "AllowedIPs" → search "allowedIps" → search "allowed_ips" (WRONG: these are trivial case variations, probe handles them)',
165
- ' → search "CIDR" → search "cidr" → search "Cidr" → search "*cidr*" (WRONG: same keyword repeated with variations)',
174
+ ' → search "AllowedIPs" → search "allowedIps" → search "allowed_ips" (WRONG: case/style variations, probe handles them)',
175
+ ' → search "limitDRL" → search "LimitDRL" (WRONG: case variation of same term)',
176
+ ' → search "throttle_retry_limit" after searching "ThrottleRetryLimit" (WRONG: snake_case variation, probe handles it)',
177
+ ' → search "ThrottleRetryLimit" path=tyk → search "ThrottleRetryLimit" path=gateway → search "ThrottleRetryLimit" path=apidef (WRONG: same query on different paths hoping for different results)',
178
+ ' → search "func (k *RateLimitAndQuotaCheck) handleRateLimitFailure" (WRONG: do not search full function signatures, just use exact=true "handleRateLimitFailure")',
179
+ ' → search "ForwardMessage" → search "ForwardMessage" → search "ForwardMessage" (WRONG: repeating the exact same query)',
166
180
  ' → search "error handling" → search "error handling" → search "error handling" (WRONG: repeating exact same query)',
167
181
  '',
168
182
  'Keyword tips:',
@@ -171,12 +185,13 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
171
185
  '- To bypass stopword filtering: wrap terms in quotes ("return", "struct") or set exact=true. Both disable stemming and splitting too.',
172
186
  '- Multiple words without operators use OR logic: foo bar = foo OR bar. Use AND explicitly if you need both: foo AND bar.',
173
187
  '- camelCase terms are split: getUserData becomes "get", "user", "data" — so one search covers all naming styles.',
188
+ '- Do NOT search for full function signatures like "func (r *Type) Method(args)". Just search for the method name with exact=true.',
174
189
  '',
175
190
  'Strategy:',
176
191
  '1. Analyze the query - identify key concepts, entities, and relationships',
177
- '2. Run ONE focused search per concept with the most natural keyword. Trust probe to handle variations.',
192
+ '2. Run ONE focused search per concept. For known symbol names use exact=true. For concepts use default (exact=false).',
178
193
  '3. If a search returns results, use extract to verify relevance',
179
- '4. Only try a different keyword if the first one returned irrelevant results (not if it returned no results that means the concept is absent)',
194
+ '4. If a search returns NO results, the term does not exist in the codebase. Do NOT retry with variations, different paths, or longer strings. Move on.',
180
195
  '5. Combine all relevant targets in your final response',
181
196
  '',
182
197
  `Query: ${searchQuery}`,
@@ -570,6 +585,50 @@ export const extractTool = (options = {}) => {
570
585
  // Resolve relative paths in targets against cwd
571
586
  extractFiles = parsedTargets.map(target => resolveTargetPath(target, effectiveCwd));
572
587
 
588
+ // Auto-fix: if resolved paths don't exist, try allowedFolders subdirs
589
+ // Handles when search returns relative paths (e.g., "gateway/file.go") and
590
+ // model constructs wrong absolute paths (e.g., /workspace/gateway/file.go
591
+ // instead of /workspace/tyk/gateway/file.go)
592
+ if (options.allowedFolders && options.allowedFolders.length > 0) {
593
+ extractFiles = extractFiles.map(target => {
594
+ const { filePart, suffix } = splitTargetSuffix(target);
595
+ if (existsSync(filePart)) return target;
596
+
597
+ // Try resolving the relative tail against each allowedFolder
598
+ const cwdPrefix = (effectiveCwd.endsWith('/') ? effectiveCwd : effectiveCwd + '/');
599
+ const relativePart = filePart.startsWith(cwdPrefix)
600
+ ? filePart.slice(cwdPrefix.length)
601
+ : null;
602
+
603
+ if (relativePart) {
604
+ for (const folder of options.allowedFolders) {
605
+ const candidate = folder + '/' + relativePart;
606
+ if (existsSync(candidate)) {
607
+ if (debug) console.error(`[extract] Auto-fixed path: ${filePart} → ${candidate}`);
608
+ return candidate + suffix;
609
+ }
610
+ }
611
+ }
612
+
613
+ // Try stripping workspace prefix and resolving against allowedFolders
614
+ // e.g., /tmp/visor-workspaces/abc/gateway/file.go → try each folder + gateway/file.go
615
+ for (const folder of options.allowedFolders) {
616
+ const folderPrefix = folder.endsWith('/') ? folder : folder + '/';
617
+ const wsParent = folderPrefix.replace(/[^/]+\/$/, '');
618
+ if (filePart.startsWith(wsParent)) {
619
+ const tail = filePart.slice(wsParent.length);
620
+ const candidate = folderPrefix + tail;
621
+ if (candidate !== filePart && existsSync(candidate)) {
622
+ if (debug) console.error(`[extract] Auto-fixed path via workspace: ${filePart} → ${candidate}`);
623
+ return candidate + suffix;
624
+ }
625
+ }
626
+ }
627
+
628
+ return target;
629
+ });
630
+ }
631
+
573
632
  // Apply format mapping for outline-xml to xml
574
633
  let effectiveFormat = format;
575
634
  if (outline && format === 'outline-xml') {
@@ -4205,7 +4205,7 @@ var init_NormalizedSchema = __esm({
4205
4205
  if (this.isDocumentSchema()) {
4206
4206
  return member([15, 0], memberName);
4207
4207
  }
4208
- throw new Error(`@smithy/core/schema - ${this.getName(true)} has no no member=${memberName}.`);
4208
+ throw new Error(`@smithy/core/schema - ${this.getName(true)} has no member=${memberName}.`);
4209
4209
  }
4210
4210
  getMemberSchemas() {
4211
4211
  const buffer = {};
@@ -5996,7 +5996,7 @@ var init_EventStreamSerde = __esm({
5996
5996
  throw new Error("@smithy/core/event-streams - non-struct member not supported in event stream union.");
5997
5997
  }
5998
5998
  }
5999
- const messageSerialization = serializer.flush();
5999
+ const messageSerialization = serializer.flush() ?? new Uint8Array();
6000
6000
  const body = typeof messageSerialization === "string" ? (this.serdeContext?.utf8Decoder ?? import_util_utf8.fromUtf8)(messageSerialization) : messageSerialization;
6001
6001
  return {
6002
6002
  body,
@@ -36563,13 +36563,19 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
36563
36563
  "- listFiles: Understand directory structure to find where relevant code might live.",
36564
36564
  "",
36565
36565
  "CRITICAL - How probe search works (do NOT ignore):",
36566
- "- By default (exact=false), probe ALREADY handles stemming, case-insensitive matching, and camelCase/snake_case splitting.",
36566
+ "- By default (exact=false), probe ALREADY handles stemming, case-insensitive matching, and camelCase/snake_case splitting automatically.",
36567
36567
  '- Searching "allowed_ips" ALREADY matches "AllowedIPs", "allowedIps", "allowed_ips", etc. Do NOT manually try case/style variations.',
36568
36568
  '- Searching "getUserData" ALREADY matches "get", "user", "data" and their variations.',
36569
- "- NEVER repeat the same search query \u2014 you will get the same results.",
36569
+ "- NEVER repeat the same search query \u2014 you will get the same results. Changing the path does NOT change this.",
36570
36570
  "- NEVER search trivial variations of the same keyword (e.g., AllowedIPs then allowedIps then allowed_ips). This is wasteful \u2014 probe handles it.",
36571
- "- If a search returns no results, the term likely does not exist in that path. Try a genuinely DIFFERENT keyword or concept, not a variation.",
36572
- "- If 2-3 consecutive searches return no results for a concept, STOP searching for it and move on.",
36571
+ "- If a search returns no results, the term likely does not exist. Try a genuinely DIFFERENT keyword or concept, not a variation.",
36572
+ "- If 2-3 searches return no results for a concept, STOP searching for it and move on. Do NOT keep retrying.",
36573
+ "",
36574
+ "When to use exact=true:",
36575
+ "- Use exact=true when searching for a KNOWN symbol name (function, type, variable, struct).",
36576
+ "- exact=true matches the literal string only \u2014 no stemming, no splitting.",
36577
+ '- This is ideal for precise lookups: exact=true "ForwardMessage", exact=true "SessionLimiter", exact=true "ThrottleRetryLimit".',
36578
+ "- Do NOT use exact=true for exploratory/conceptual queries \u2014 use the default for those.",
36573
36579
  "",
36574
36580
  "GOOD search strategy (do this):",
36575
36581
  ' Query: "How does authentication work and how are sessions managed?"',
@@ -36578,10 +36584,18 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
36578
36584
  ' \u2192 search "allowlist middleware" (one search, probe handles IP/ip/Ip variations)',
36579
36585
  ' Query: "How does BM25 scoring work with SIMD optimization?"',
36580
36586
  ' \u2192 search "BM25 scoring" \u2192 search "SIMD optimization" (two different concepts)',
36587
+ ' Query: "Find ForwardMessage and SessionLimiter functions"',
36588
+ ' \u2192 search exact=true "ForwardMessage" \u2192 search exact=true "SessionLimiter" (known symbols, use exact)',
36589
+ ' Query: "Find ThrottleRetryLimit usage"',
36590
+ ' \u2192 search exact=true "ThrottleRetryLimit" (one search, if no results the symbol does not exist \u2014 stop)',
36581
36591
  "",
36582
36592
  "BAD search strategy (never do this):",
36583
- ' \u2192 search "AllowedIPs" \u2192 search "allowedIps" \u2192 search "allowed_ips" (WRONG: these are trivial case variations, probe handles them)',
36584
- ' \u2192 search "CIDR" \u2192 search "cidr" \u2192 search "Cidr" \u2192 search "*cidr*" (WRONG: same keyword repeated with variations)',
36593
+ ' \u2192 search "AllowedIPs" \u2192 search "allowedIps" \u2192 search "allowed_ips" (WRONG: case/style variations, probe handles them)',
36594
+ ' \u2192 search "limitDRL" \u2192 search "LimitDRL" (WRONG: case variation of same term)',
36595
+ ' \u2192 search "throttle_retry_limit" after searching "ThrottleRetryLimit" (WRONG: snake_case variation, probe handles it)',
36596
+ ' \u2192 search "ThrottleRetryLimit" path=tyk \u2192 search "ThrottleRetryLimit" path=gateway \u2192 search "ThrottleRetryLimit" path=apidef (WRONG: same query on different paths hoping for different results)',
36597
+ ' \u2192 search "func (k *RateLimitAndQuotaCheck) handleRateLimitFailure" (WRONG: do not search full function signatures, just use exact=true "handleRateLimitFailure")',
36598
+ ' \u2192 search "ForwardMessage" \u2192 search "ForwardMessage" \u2192 search "ForwardMessage" (WRONG: repeating the exact same query)',
36585
36599
  ' \u2192 search "error handling" \u2192 search "error handling" \u2192 search "error handling" (WRONG: repeating exact same query)',
36586
36600
  "",
36587
36601
  "Keyword tips:",
@@ -36590,12 +36604,13 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
36590
36604
  '- To bypass stopword filtering: wrap terms in quotes ("return", "struct") or set exact=true. Both disable stemming and splitting too.',
36591
36605
  "- Multiple words without operators use OR logic: foo bar = foo OR bar. Use AND explicitly if you need both: foo AND bar.",
36592
36606
  '- camelCase terms are split: getUserData becomes "get", "user", "data" \u2014 so one search covers all naming styles.',
36607
+ '- Do NOT search for full function signatures like "func (r *Type) Method(args)". Just search for the method name with exact=true.',
36593
36608
  "",
36594
36609
  "Strategy:",
36595
36610
  "1. Analyze the query - identify key concepts, entities, and relationships",
36596
- "2. Run ONE focused search per concept with the most natural keyword. Trust probe to handle variations.",
36611
+ "2. Run ONE focused search per concept. For known symbol names use exact=true. For concepts use default (exact=false).",
36597
36612
  "3. If a search returns results, use extract to verify relevance",
36598
- "4. Only try a different keyword if the first one returned irrelevant results (not if it returned no results \u2014 that means the concept is absent)",
36613
+ "4. If a search returns NO results, the term does not exist in the codebase. Do NOT retry with variations, different paths, or longer strings. Move on.",
36599
36614
  "5. Combine all relevant targets in your final response",
36600
36615
  "",
36601
36616
  `Query: ${searchQuery}`,
@@ -36918,6 +36933,36 @@ var init_vercel = __esm({
36918
36933
  } else if (targets) {
36919
36934
  const parsedTargets = parseTargets(targets);
36920
36935
  extractFiles = parsedTargets.map((target) => resolveTargetPath(target, effectiveCwd));
36936
+ if (options.allowedFolders && options.allowedFolders.length > 0) {
36937
+ extractFiles = extractFiles.map((target) => {
36938
+ const { filePart, suffix } = splitTargetSuffix(target);
36939
+ if ((0, import_fs4.existsSync)(filePart)) return target;
36940
+ const cwdPrefix = effectiveCwd.endsWith("/") ? effectiveCwd : effectiveCwd + "/";
36941
+ const relativePart = filePart.startsWith(cwdPrefix) ? filePart.slice(cwdPrefix.length) : null;
36942
+ if (relativePart) {
36943
+ for (const folder of options.allowedFolders) {
36944
+ const candidate = folder + "/" + relativePart;
36945
+ if ((0, import_fs4.existsSync)(candidate)) {
36946
+ if (debug) console.error(`[extract] Auto-fixed path: ${filePart} \u2192 ${candidate}`);
36947
+ return candidate + suffix;
36948
+ }
36949
+ }
36950
+ }
36951
+ for (const folder of options.allowedFolders) {
36952
+ const folderPrefix = folder.endsWith("/") ? folder : folder + "/";
36953
+ const wsParent = folderPrefix.replace(/[^/]+\/$/, "");
36954
+ if (filePart.startsWith(wsParent)) {
36955
+ const tail = filePart.slice(wsParent.length);
36956
+ const candidate = folderPrefix + tail;
36957
+ if (candidate !== filePart && (0, import_fs4.existsSync)(candidate)) {
36958
+ if (debug) console.error(`[extract] Auto-fixed path via workspace: ${filePart} \u2192 ${candidate}`);
36959
+ return candidate + suffix;
36960
+ }
36961
+ }
36962
+ }
36963
+ return target;
36964
+ });
36965
+ }
36921
36966
  let effectiveFormat = format2;
36922
36967
  if (outline && format2 === "outline-xml") {
36923
36968
  effectiveFormat = "xml";
@@ -111043,7 +111088,7 @@ ${searchToolDesc1}
111043
111088
  - searchFiles: Find files by name patterns`;
111044
111089
  if (this.enableBash) {
111045
111090
  systemPrompt += `
111046
- - bash: Execute bash commands for system operations`;
111091
+ - bash: Execute bash commands for system operations (building, running tests, git, etc.). NEVER use bash for code exploration (no grep, cat, find, head, tail) \u2014 always use search and extract tools instead, they are faster and more accurate.`;
111047
111092
  }
111048
111093
  const searchGuidance1 = this.searchDelegate ? "1. Start with search \u2014 ask a question about what you want to understand. It returns extracted code blocks directly." : "1. Start with search to find relevant code patterns. One search per concept is usually enough \u2014 probe handles stemming and case variations.";
111049
111094
  const extractGuidance1 = this.searchDelegate ? "2. Use extract only if you need more context or a full file" : "2. Use extract to get detailed context when needed";
@@ -111098,7 +111143,7 @@ ${searchToolDesc2}
111098
111143
  - searchFiles: Find files by name patterns`;
111099
111144
  if (this.enableBash) {
111100
111145
  systemPrompt += `
111101
- - bash: Execute bash commands for system operations`;
111146
+ - bash: Execute bash commands for system operations (building, running tests, git, etc.). NEVER use bash for code exploration (no grep, cat, find, head, tail) \u2014 always use search and extract tools instead, they are faster and more accurate.`;
111102
111147
  }
111103
111148
  const searchGuidance2 = this.searchDelegate ? "1. Start with search \u2014 ask a question about what you want to understand. It returns extracted code blocks directly." : "1. Start with search to find relevant code patterns. One search per concept is usually enough \u2014 probe handles stemming and case variations.";
111104
111149
  const extractGuidance2 = this.searchDelegate ? "2. Use extract only if you need more context or a full file" : "2. Use extract to get detailed context when needed";
@@ -111164,7 +111209,8 @@ Follow these instructions carefully:
111164
111209
  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."}
111165
111210
  4. Ensure to get really deep and understand the full picture before answering.
111166
111211
  5. Once the task is fully completed, use the attempt_completion tool to provide the final result.
111167
- 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."}${this.allowEdit ? `
111212
+ 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."}
111213
+ 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 ? `
111168
111214
  7. When modifying files, choose the appropriate tool:
111169
111215
  - Use 'edit' for all code modifications:
111170
111216
  * PREFERRED: Use start_line (and optionally end_line) for line-targeted editing \u2014 this is the safest and most precise approach.${this.hashLines ? ' Use the line:hash references from extract/search output (e.g. "42:ab") for integrity verification.' : ""} Always use extract first to see line numbers${this.hashLines ? " and hashes" : ""}, then edit by line reference.
@@ -111178,7 +111224,14 @@ Follow these instructions carefully:
111178
111224
  </instructions>
111179
111225
  `;
111180
111226
  let systemMessage = "";
111181
- if (this.customPrompt) {
111227
+ if (this.customPrompt && this.promptType && predefinedPrompts[this.promptType]) {
111228
+ systemMessage = "<role>" + predefinedPrompts[this.promptType] + "</role>";
111229
+ systemMessage += commonInstructions;
111230
+ systemMessage += "\n<custom-instructions>\n" + this.customPrompt + "\n</custom-instructions>";
111231
+ if (this.debug) {
111232
+ console.log(`[DEBUG] Using predefined prompt: ${this.promptType} + custom prompt`);
111233
+ }
111234
+ } else if (this.customPrompt) {
111182
111235
  systemMessage = "<role>" + this.customPrompt + "</role>";
111183
111236
  if (this.debug) {
111184
111237
  console.log(`[DEBUG] Using custom prompt`);
package/cjs/index.cjs CHANGED
@@ -6063,7 +6063,7 @@ var init_NormalizedSchema = __esm({
6063
6063
  if (this.isDocumentSchema()) {
6064
6064
  return member([15, 0], memberName);
6065
6065
  }
6066
- throw new Error(`@smithy/core/schema - ${this.getName(true)} has no no member=${memberName}.`);
6066
+ throw new Error(`@smithy/core/schema - ${this.getName(true)} has no member=${memberName}.`);
6067
6067
  }
6068
6068
  getMemberSchemas() {
6069
6069
  const buffer = {};
@@ -7854,7 +7854,7 @@ var init_EventStreamSerde = __esm({
7854
7854
  throw new Error("@smithy/core/event-streams - non-struct member not supported in event stream union.");
7855
7855
  }
7856
7856
  }
7857
- const messageSerialization = serializer.flush();
7857
+ const messageSerialization = serializer.flush() ?? new Uint8Array();
7858
7858
  const body = typeof messageSerialization === "string" ? (this.serdeContext?.utf8Decoder ?? import_util_utf8.fromUtf8)(messageSerialization) : messageSerialization;
7859
7859
  return {
7860
7860
  body,
@@ -36422,7 +36422,7 @@ var init_common2 = __esm({
36422
36422
  queryDescription = "Search code using ast-grep structural pattern matching. Use this tool to find specific code structures like functions, classes, or methods.";
36423
36423
  extractDescription = "Extract code blocks from files based on file paths and optional line numbers. Use this tool to see complete context after finding relevant files. Line numbers from output can be used with edit start_line/end_line for precise editing.";
36424
36424
  delegateDescription = "Automatically delegate big distinct tasks to specialized probe subagents within the agentic loop. Used by AI agents to break down complex requests into focused, parallel tasks.";
36425
- bashDescription = "Execute bash commands for system exploration and development tasks. Secure by default with built-in allow/deny lists.";
36425
+ bashDescription = "Execute bash commands for system operations: building, running tests, git, package management, etc. NEVER use for code exploration (no grep, cat, find, head, tail) \u2014 use search and extract tools instead. Secure by default with built-in allow/deny lists.";
36426
36426
  analyzeAllDescription = 'Answer questions that require analyzing ALL matching data in the codebase. Use for aggregate questions like "What features exist?", "List all API endpoints", "Count TODO comments". The AI automatically plans the search strategy, processes all results via map-reduce, and synthesizes a comprehensive answer. WARNING: Slower than search - only use when you need complete coverage.';
36427
36427
  }
36428
36428
  });
@@ -108200,7 +108200,7 @@ ${searchToolDesc1}
108200
108200
  - searchFiles: Find files by name patterns`;
108201
108201
  if (this.enableBash) {
108202
108202
  systemPrompt += `
108203
- - bash: Execute bash commands for system operations`;
108203
+ - bash: Execute bash commands for system operations (building, running tests, git, etc.). NEVER use bash for code exploration (no grep, cat, find, head, tail) \u2014 always use search and extract tools instead, they are faster and more accurate.`;
108204
108204
  }
108205
108205
  const searchGuidance1 = this.searchDelegate ? "1. Start with search \u2014 ask a question about what you want to understand. It returns extracted code blocks directly." : "1. Start with search to find relevant code patterns. One search per concept is usually enough \u2014 probe handles stemming and case variations.";
108206
108206
  const extractGuidance1 = this.searchDelegate ? "2. Use extract only if you need more context or a full file" : "2. Use extract to get detailed context when needed";
@@ -108255,7 +108255,7 @@ ${searchToolDesc2}
108255
108255
  - searchFiles: Find files by name patterns`;
108256
108256
  if (this.enableBash) {
108257
108257
  systemPrompt += `
108258
- - bash: Execute bash commands for system operations`;
108258
+ - bash: Execute bash commands for system operations (building, running tests, git, etc.). NEVER use bash for code exploration (no grep, cat, find, head, tail) \u2014 always use search and extract tools instead, they are faster and more accurate.`;
108259
108259
  }
108260
108260
  const searchGuidance2 = this.searchDelegate ? "1. Start with search \u2014 ask a question about what you want to understand. It returns extracted code blocks directly." : "1. Start with search to find relevant code patterns. One search per concept is usually enough \u2014 probe handles stemming and case variations.";
108261
108261
  const extractGuidance2 = this.searchDelegate ? "2. Use extract only if you need more context or a full file" : "2. Use extract to get detailed context when needed";
@@ -108321,7 +108321,8 @@ Follow these instructions carefully:
108321
108321
  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."}
108322
108322
  4. Ensure to get really deep and understand the full picture before answering.
108323
108323
  5. Once the task is fully completed, use the attempt_completion tool to provide the final result.
108324
- 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."}${this.allowEdit ? `
108324
+ 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."}
108325
+ 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 ? `
108325
108326
  7. When modifying files, choose the appropriate tool:
108326
108327
  - Use 'edit' for all code modifications:
108327
108328
  * PREFERRED: Use start_line (and optionally end_line) for line-targeted editing \u2014 this is the safest and most precise approach.${this.hashLines ? ' Use the line:hash references from extract/search output (e.g. "42:ab") for integrity verification.' : ""} Always use extract first to see line numbers${this.hashLines ? " and hashes" : ""}, then edit by line reference.
@@ -108335,7 +108336,14 @@ Follow these instructions carefully:
108335
108336
  </instructions>
108336
108337
  `;
108337
108338
  let systemMessage = "";
108338
- if (this.customPrompt) {
108339
+ if (this.customPrompt && this.promptType && predefinedPrompts[this.promptType]) {
108340
+ systemMessage = "<role>" + predefinedPrompts[this.promptType] + "</role>";
108341
+ systemMessage += commonInstructions;
108342
+ systemMessage += "\n<custom-instructions>\n" + this.customPrompt + "\n</custom-instructions>";
108343
+ if (this.debug) {
108344
+ console.log(`[DEBUG] Using predefined prompt: ${this.promptType} + custom prompt`);
108345
+ }
108346
+ } else if (this.customPrompt) {
108339
108347
  systemMessage = "<role>" + this.customPrompt + "</role>";
108340
108348
  if (this.debug) {
108341
108349
  console.log(`[DEBUG] Using custom prompt`);
@@ -110717,13 +110725,19 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
110717
110725
  "- listFiles: Understand directory structure to find where relevant code might live.",
110718
110726
  "",
110719
110727
  "CRITICAL - How probe search works (do NOT ignore):",
110720
- "- By default (exact=false), probe ALREADY handles stemming, case-insensitive matching, and camelCase/snake_case splitting.",
110728
+ "- By default (exact=false), probe ALREADY handles stemming, case-insensitive matching, and camelCase/snake_case splitting automatically.",
110721
110729
  '- Searching "allowed_ips" ALREADY matches "AllowedIPs", "allowedIps", "allowed_ips", etc. Do NOT manually try case/style variations.',
110722
110730
  '- Searching "getUserData" ALREADY matches "get", "user", "data" and their variations.',
110723
- "- NEVER repeat the same search query \u2014 you will get the same results.",
110731
+ "- NEVER repeat the same search query \u2014 you will get the same results. Changing the path does NOT change this.",
110724
110732
  "- NEVER search trivial variations of the same keyword (e.g., AllowedIPs then allowedIps then allowed_ips). This is wasteful \u2014 probe handles it.",
110725
- "- If a search returns no results, the term likely does not exist in that path. Try a genuinely DIFFERENT keyword or concept, not a variation.",
110726
- "- If 2-3 consecutive searches return no results for a concept, STOP searching for it and move on.",
110733
+ "- If a search returns no results, the term likely does not exist. Try a genuinely DIFFERENT keyword or concept, not a variation.",
110734
+ "- If 2-3 searches return no results for a concept, STOP searching for it and move on. Do NOT keep retrying.",
110735
+ "",
110736
+ "When to use exact=true:",
110737
+ "- Use exact=true when searching for a KNOWN symbol name (function, type, variable, struct).",
110738
+ "- exact=true matches the literal string only \u2014 no stemming, no splitting.",
110739
+ '- This is ideal for precise lookups: exact=true "ForwardMessage", exact=true "SessionLimiter", exact=true "ThrottleRetryLimit".',
110740
+ "- Do NOT use exact=true for exploratory/conceptual queries \u2014 use the default for those.",
110727
110741
  "",
110728
110742
  "GOOD search strategy (do this):",
110729
110743
  ' Query: "How does authentication work and how are sessions managed?"',
@@ -110732,10 +110746,18 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
110732
110746
  ' \u2192 search "allowlist middleware" (one search, probe handles IP/ip/Ip variations)',
110733
110747
  ' Query: "How does BM25 scoring work with SIMD optimization?"',
110734
110748
  ' \u2192 search "BM25 scoring" \u2192 search "SIMD optimization" (two different concepts)',
110749
+ ' Query: "Find ForwardMessage and SessionLimiter functions"',
110750
+ ' \u2192 search exact=true "ForwardMessage" \u2192 search exact=true "SessionLimiter" (known symbols, use exact)',
110751
+ ' Query: "Find ThrottleRetryLimit usage"',
110752
+ ' \u2192 search exact=true "ThrottleRetryLimit" (one search, if no results the symbol does not exist \u2014 stop)',
110735
110753
  "",
110736
110754
  "BAD search strategy (never do this):",
110737
- ' \u2192 search "AllowedIPs" \u2192 search "allowedIps" \u2192 search "allowed_ips" (WRONG: these are trivial case variations, probe handles them)',
110738
- ' \u2192 search "CIDR" \u2192 search "cidr" \u2192 search "Cidr" \u2192 search "*cidr*" (WRONG: same keyword repeated with variations)',
110755
+ ' \u2192 search "AllowedIPs" \u2192 search "allowedIps" \u2192 search "allowed_ips" (WRONG: case/style variations, probe handles them)',
110756
+ ' \u2192 search "limitDRL" \u2192 search "LimitDRL" (WRONG: case variation of same term)',
110757
+ ' \u2192 search "throttle_retry_limit" after searching "ThrottleRetryLimit" (WRONG: snake_case variation, probe handles it)',
110758
+ ' \u2192 search "ThrottleRetryLimit" path=tyk \u2192 search "ThrottleRetryLimit" path=gateway \u2192 search "ThrottleRetryLimit" path=apidef (WRONG: same query on different paths hoping for different results)',
110759
+ ' \u2192 search "func (k *RateLimitAndQuotaCheck) handleRateLimitFailure" (WRONG: do not search full function signatures, just use exact=true "handleRateLimitFailure")',
110760
+ ' \u2192 search "ForwardMessage" \u2192 search "ForwardMessage" \u2192 search "ForwardMessage" (WRONG: repeating the exact same query)',
110739
110761
  ' \u2192 search "error handling" \u2192 search "error handling" \u2192 search "error handling" (WRONG: repeating exact same query)',
110740
110762
  "",
110741
110763
  "Keyword tips:",
@@ -110744,12 +110766,13 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
110744
110766
  '- To bypass stopword filtering: wrap terms in quotes ("return", "struct") or set exact=true. Both disable stemming and splitting too.',
110745
110767
  "- Multiple words without operators use OR logic: foo bar = foo OR bar. Use AND explicitly if you need both: foo AND bar.",
110746
110768
  '- camelCase terms are split: getUserData becomes "get", "user", "data" \u2014 so one search covers all naming styles.',
110769
+ '- Do NOT search for full function signatures like "func (r *Type) Method(args)". Just search for the method name with exact=true.',
110747
110770
  "",
110748
110771
  "Strategy:",
110749
110772
  "1. Analyze the query - identify key concepts, entities, and relationships",
110750
- "2. Run ONE focused search per concept with the most natural keyword. Trust probe to handle variations.",
110773
+ "2. Run ONE focused search per concept. For known symbol names use exact=true. For concepts use default (exact=false).",
110751
110774
  "3. If a search returns results, use extract to verify relevance",
110752
- "4. Only try a different keyword if the first one returned irrelevant results (not if it returned no results \u2014 that means the concept is absent)",
110775
+ "4. If a search returns NO results, the term does not exist in the codebase. Do NOT retry with variations, different paths, or longer strings. Move on.",
110753
110776
  "5. Combine all relevant targets in your final response",
110754
110777
  "",
110755
110778
  `Query: ${searchQuery}`,
@@ -111072,6 +111095,36 @@ var init_vercel = __esm({
111072
111095
  } else if (targets) {
111073
111096
  const parsedTargets = parseTargets(targets);
111074
111097
  extractFiles = parsedTargets.map((target) => resolveTargetPath(target, effectiveCwd));
111098
+ if (options.allowedFolders && options.allowedFolders.length > 0) {
111099
+ extractFiles = extractFiles.map((target) => {
111100
+ const { filePart, suffix } = splitTargetSuffix(target);
111101
+ if ((0, import_fs11.existsSync)(filePart)) return target;
111102
+ const cwdPrefix = effectiveCwd.endsWith("/") ? effectiveCwd : effectiveCwd + "/";
111103
+ const relativePart = filePart.startsWith(cwdPrefix) ? filePart.slice(cwdPrefix.length) : null;
111104
+ if (relativePart) {
111105
+ for (const folder of options.allowedFolders) {
111106
+ const candidate = folder + "/" + relativePart;
111107
+ if ((0, import_fs11.existsSync)(candidate)) {
111108
+ if (debug) console.error(`[extract] Auto-fixed path: ${filePart} \u2192 ${candidate}`);
111109
+ return candidate + suffix;
111110
+ }
111111
+ }
111112
+ }
111113
+ for (const folder of options.allowedFolders) {
111114
+ const folderPrefix = folder.endsWith("/") ? folder : folder + "/";
111115
+ const wsParent = folderPrefix.replace(/[^/]+\/$/, "");
111116
+ if (filePart.startsWith(wsParent)) {
111117
+ const tail = filePart.slice(wsParent.length);
111118
+ const candidate = folderPrefix + tail;
111119
+ if (candidate !== filePart && (0, import_fs11.existsSync)(candidate)) {
111120
+ if (debug) console.error(`[extract] Auto-fixed path via workspace: ${filePart} \u2192 ${candidate}`);
111121
+ return candidate + suffix;
111122
+ }
111123
+ }
111124
+ }
111125
+ return target;
111126
+ });
111127
+ }
111075
111128
  let effectiveFormat = format2;
111076
111129
  if (outline && format2 === "outline-xml") {
111077
111130
  effectiveFormat = "xml";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@probelabs/probe",
3
- "version": "0.6.0-rc280",
3
+ "version": "0.6.0-rc282",
4
4
  "description": "Node.js wrapper for the probe code search tool",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -75,7 +75,7 @@
75
75
  "dependencies": {
76
76
  "@ai-sdk/amazon-bedrock": "^1.0.8",
77
77
  "@ai-sdk/anthropic": "^2.0.8",
78
- "@ai-sdk/google": "^2.0.14",
78
+ "@ai-sdk/google": "^3.0.37",
79
79
  "@ai-sdk/openai": "^2.0.10",
80
80
  "@anthropic-ai/claude-agent-sdk": "^0.1.46",
81
81
  "@modelcontextprotocol/sdk": "^1.0.0",
@@ -2876,7 +2876,7 @@ ${searchToolDesc1}
2876
2876
  - searchFiles: Find files by name patterns`;
2877
2877
 
2878
2878
  if (this.enableBash) {
2879
- systemPrompt += `\n- bash: Execute bash commands for system operations`;
2879
+ systemPrompt += `\n- bash: Execute bash commands for system operations (building, running tests, git, etc.). NEVER use bash for code exploration (no grep, cat, find, head, tail) — always use search and extract tools instead, they are faster and more accurate.`;
2880
2880
  }
2881
2881
 
2882
2882
  const searchGuidance1 = this.searchDelegate
@@ -2942,7 +2942,7 @@ ${searchToolDesc2}
2942
2942
  - searchFiles: Find files by name patterns`;
2943
2943
 
2944
2944
  if (this.enableBash) {
2945
- systemPrompt += `\n- bash: Execute bash commands for system operations`;
2945
+ systemPrompt += `\n- bash: Execute bash commands for system operations (building, running tests, git, etc.). NEVER use bash for code exploration (no grep, cat, find, head, tail) — always use search and extract tools instead, they are faster and more accurate.`;
2946
2946
  }
2947
2947
 
2948
2948
  const searchGuidance2 = this.searchDelegate
@@ -3018,7 +3018,8 @@ Follow these instructions carefully:
3018
3018
  3. You should always prefer the search tool for code-related questions.${this.searchDelegate ? ' Ask natural language questions — the search subagent handles keyword formulation and returns extracted code blocks. Use extract only to expand context or read full files.' : ' Search handles stemming and case variations automatically — do NOT try keyword variations manually. Read full files only if really necessary.'}
3019
3019
  4. Ensure to get really deep and understand the full picture before answering.
3020
3020
  5. Once the task is fully completed, use the attempt_completion tool to provide the final result.
3021
- 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.'}${this.allowEdit ? `
3021
+ 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.'}
3022
+ 7. NEVER use bash for code exploration (no grep, cat, find, head, tail, awk, sed) — always use search and extract tools instead. Bash is only for system operations like building, running tests, or git commands.${this.allowEdit ? `
3022
3023
  7. When modifying files, choose the appropriate tool:
3023
3024
  - Use 'edit' for all code modifications:
3024
3025
  * PREFERRED: Use start_line (and optionally end_line) for line-targeted editing — this is the safest and most precise approach.${this.hashLines ? ' Use the line:hash references from extract/search output (e.g. "42:ab") for integrity verification.' : ''} Always use extract first to see line numbers${this.hashLines ? ' and hashes' : ''}, then edit by line reference.
@@ -3035,22 +3036,30 @@ Follow these instructions carefully:
3035
3036
  // Use predefined prompts from shared module (imported at top of file)
3036
3037
  let systemMessage = '';
3037
3038
 
3038
- // Use custom prompt if provided
3039
- if (this.customPrompt) {
3039
+ // Build system message from predefined prompt + optional custom prompt
3040
+ if (this.customPrompt && this.promptType && predefinedPrompts[this.promptType]) {
3041
+ // Both: use predefined as base, append custom wrapped in tag
3042
+ systemMessage = "<role>" + predefinedPrompts[this.promptType] + "</role>";
3043
+ systemMessage += commonInstructions;
3044
+ systemMessage += "\n<custom-instructions>\n" + this.customPrompt + "\n</custom-instructions>";
3045
+ if (this.debug) {
3046
+ console.log(`[DEBUG] Using predefined prompt: ${this.promptType} + custom prompt`);
3047
+ }
3048
+ } else if (this.customPrompt) {
3049
+ // Only custom prompt
3040
3050
  systemMessage = "<role>" + this.customPrompt + "</role>";
3041
3051
  if (this.debug) {
3042
3052
  console.log(`[DEBUG] Using custom prompt`);
3043
3053
  }
3044
- }
3045
- // Use predefined prompt if specified
3046
- else if (this.promptType && predefinedPrompts[this.promptType]) {
3054
+ } else if (this.promptType && predefinedPrompts[this.promptType]) {
3055
+ // Only predefined prompt
3047
3056
  systemMessage = "<role>" + predefinedPrompts[this.promptType] + "</role>";
3048
3057
  if (this.debug) {
3049
3058
  console.log(`[DEBUG] Using predefined prompt: ${this.promptType}`);
3050
3059
  }
3051
3060
  systemMessage += commonInstructions;
3052
3061
  } else {
3053
- // Use the default prompt (code explorer) if no prompt type is specified
3062
+ // Default: code explorer
3054
3063
  systemMessage = "<role>" + predefinedPrompts['code-explorer'] + "</role>";
3055
3064
  if (this.debug) {
3056
3065
  console.log(`[DEBUG] Using default prompt: code explorer`);
@@ -154,7 +154,7 @@ export const searchDelegateDescription = 'Search code in the repository by askin
154
154
  export const queryDescription = 'Search code using ast-grep structural pattern matching. Use this tool to find specific code structures like functions, classes, or methods.';
155
155
  export const extractDescription = 'Extract code blocks from files based on file paths and optional line numbers. Use this tool to see complete context after finding relevant files. Line numbers from output can be used with edit start_line/end_line for precise editing.';
156
156
  export const delegateDescription = 'Automatically delegate big distinct tasks to specialized probe subagents within the agentic loop. Used by AI agents to break down complex requests into focused, parallel tasks.';
157
- export const bashDescription = 'Execute bash commands for system exploration and development tasks. Secure by default with built-in allow/deny lists.';
157
+ export const bashDescription = 'Execute bash commands for system operations: building, running tests, git, package management, etc. NEVER use for code exploration (no grep, cat, find, head, tail) — use search and extract tools instead. Secure by default with built-in allow/deny lists.';
158
158
  export const analyzeAllDescription = 'Answer questions that require analyzing ALL matching data in the codebase. Use for aggregate questions like "What features exist?", "List all API endpoints", "Count TODO comments". The AI automatically plans the search strategy, processes all results via map-reduce, and synthesizes a comprehensive answer. WARNING: Slower than search - only use when you need complete coverage.';
159
159
 
160
160
 
@@ -144,13 +144,19 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
144
144
  '- listFiles: Understand directory structure to find where relevant code might live.',
145
145
  '',
146
146
  'CRITICAL - How probe search works (do NOT ignore):',
147
- '- By default (exact=false), probe ALREADY handles stemming, case-insensitive matching, and camelCase/snake_case splitting.',
147
+ '- By default (exact=false), probe ALREADY handles stemming, case-insensitive matching, and camelCase/snake_case splitting automatically.',
148
148
  '- Searching "allowed_ips" ALREADY matches "AllowedIPs", "allowedIps", "allowed_ips", etc. Do NOT manually try case/style variations.',
149
149
  '- Searching "getUserData" ALREADY matches "get", "user", "data" and their variations.',
150
- '- NEVER repeat the same search query — you will get the same results.',
150
+ '- NEVER repeat the same search query — you will get the same results. Changing the path does NOT change this.',
151
151
  '- NEVER search trivial variations of the same keyword (e.g., AllowedIPs then allowedIps then allowed_ips). This is wasteful — probe handles it.',
152
- '- If a search returns no results, the term likely does not exist in that path. Try a genuinely DIFFERENT keyword or concept, not a variation.',
153
- '- If 2-3 consecutive searches return no results for a concept, STOP searching for it and move on.',
152
+ '- If a search returns no results, the term likely does not exist. Try a genuinely DIFFERENT keyword or concept, not a variation.',
153
+ '- If 2-3 searches return no results for a concept, STOP searching for it and move on. Do NOT keep retrying.',
154
+ '',
155
+ 'When to use exact=true:',
156
+ '- Use exact=true when searching for a KNOWN symbol name (function, type, variable, struct).',
157
+ '- exact=true matches the literal string only — no stemming, no splitting.',
158
+ '- This is ideal for precise lookups: exact=true "ForwardMessage", exact=true "SessionLimiter", exact=true "ThrottleRetryLimit".',
159
+ '- Do NOT use exact=true for exploratory/conceptual queries — use the default for those.',
154
160
  '',
155
161
  'GOOD search strategy (do this):',
156
162
  ' Query: "How does authentication work and how are sessions managed?"',
@@ -159,10 +165,18 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
159
165
  ' → search "allowlist middleware" (one search, probe handles IP/ip/Ip variations)',
160
166
  ' Query: "How does BM25 scoring work with SIMD optimization?"',
161
167
  ' → search "BM25 scoring" → search "SIMD optimization" (two different concepts)',
168
+ ' Query: "Find ForwardMessage and SessionLimiter functions"',
169
+ ' → search exact=true "ForwardMessage" → search exact=true "SessionLimiter" (known symbols, use exact)',
170
+ ' Query: "Find ThrottleRetryLimit usage"',
171
+ ' → search exact=true "ThrottleRetryLimit" (one search, if no results the symbol does not exist — stop)',
162
172
  '',
163
173
  'BAD search strategy (never do this):',
164
- ' → search "AllowedIPs" → search "allowedIps" → search "allowed_ips" (WRONG: these are trivial case variations, probe handles them)',
165
- ' → search "CIDR" → search "cidr" → search "Cidr" → search "*cidr*" (WRONG: same keyword repeated with variations)',
174
+ ' → search "AllowedIPs" → search "allowedIps" → search "allowed_ips" (WRONG: case/style variations, probe handles them)',
175
+ ' → search "limitDRL" → search "LimitDRL" (WRONG: case variation of same term)',
176
+ ' → search "throttle_retry_limit" after searching "ThrottleRetryLimit" (WRONG: snake_case variation, probe handles it)',
177
+ ' → search "ThrottleRetryLimit" path=tyk → search "ThrottleRetryLimit" path=gateway → search "ThrottleRetryLimit" path=apidef (WRONG: same query on different paths hoping for different results)',
178
+ ' → search "func (k *RateLimitAndQuotaCheck) handleRateLimitFailure" (WRONG: do not search full function signatures, just use exact=true "handleRateLimitFailure")',
179
+ ' → search "ForwardMessage" → search "ForwardMessage" → search "ForwardMessage" (WRONG: repeating the exact same query)',
166
180
  ' → search "error handling" → search "error handling" → search "error handling" (WRONG: repeating exact same query)',
167
181
  '',
168
182
  'Keyword tips:',
@@ -171,12 +185,13 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
171
185
  '- To bypass stopword filtering: wrap terms in quotes ("return", "struct") or set exact=true. Both disable stemming and splitting too.',
172
186
  '- Multiple words without operators use OR logic: foo bar = foo OR bar. Use AND explicitly if you need both: foo AND bar.',
173
187
  '- camelCase terms are split: getUserData becomes "get", "user", "data" — so one search covers all naming styles.',
188
+ '- Do NOT search for full function signatures like "func (r *Type) Method(args)". Just search for the method name with exact=true.',
174
189
  '',
175
190
  'Strategy:',
176
191
  '1. Analyze the query - identify key concepts, entities, and relationships',
177
- '2. Run ONE focused search per concept with the most natural keyword. Trust probe to handle variations.',
192
+ '2. Run ONE focused search per concept. For known symbol names use exact=true. For concepts use default (exact=false).',
178
193
  '3. If a search returns results, use extract to verify relevance',
179
- '4. Only try a different keyword if the first one returned irrelevant results (not if it returned no results that means the concept is absent)',
194
+ '4. If a search returns NO results, the term does not exist in the codebase. Do NOT retry with variations, different paths, or longer strings. Move on.',
180
195
  '5. Combine all relevant targets in your final response',
181
196
  '',
182
197
  `Query: ${searchQuery}`,
@@ -570,6 +585,50 @@ export const extractTool = (options = {}) => {
570
585
  // Resolve relative paths in targets against cwd
571
586
  extractFiles = parsedTargets.map(target => resolveTargetPath(target, effectiveCwd));
572
587
 
588
+ // Auto-fix: if resolved paths don't exist, try allowedFolders subdirs
589
+ // Handles when search returns relative paths (e.g., "gateway/file.go") and
590
+ // model constructs wrong absolute paths (e.g., /workspace/gateway/file.go
591
+ // instead of /workspace/tyk/gateway/file.go)
592
+ if (options.allowedFolders && options.allowedFolders.length > 0) {
593
+ extractFiles = extractFiles.map(target => {
594
+ const { filePart, suffix } = splitTargetSuffix(target);
595
+ if (existsSync(filePart)) return target;
596
+
597
+ // Try resolving the relative tail against each allowedFolder
598
+ const cwdPrefix = (effectiveCwd.endsWith('/') ? effectiveCwd : effectiveCwd + '/');
599
+ const relativePart = filePart.startsWith(cwdPrefix)
600
+ ? filePart.slice(cwdPrefix.length)
601
+ : null;
602
+
603
+ if (relativePart) {
604
+ for (const folder of options.allowedFolders) {
605
+ const candidate = folder + '/' + relativePart;
606
+ if (existsSync(candidate)) {
607
+ if (debug) console.error(`[extract] Auto-fixed path: ${filePart} → ${candidate}`);
608
+ return candidate + suffix;
609
+ }
610
+ }
611
+ }
612
+
613
+ // Try stripping workspace prefix and resolving against allowedFolders
614
+ // e.g., /tmp/visor-workspaces/abc/gateway/file.go → try each folder + gateway/file.go
615
+ for (const folder of options.allowedFolders) {
616
+ const folderPrefix = folder.endsWith('/') ? folder : folder + '/';
617
+ const wsParent = folderPrefix.replace(/[^/]+\/$/, '');
618
+ if (filePart.startsWith(wsParent)) {
619
+ const tail = filePart.slice(wsParent.length);
620
+ const candidate = folderPrefix + tail;
621
+ if (candidate !== filePart && existsSync(candidate)) {
622
+ if (debug) console.error(`[extract] Auto-fixed path via workspace: ${filePart} → ${candidate}`);
623
+ return candidate + suffix;
624
+ }
625
+ }
626
+ }
627
+
628
+ return target;
629
+ });
630
+ }
631
+
573
632
  // Apply format mapping for outline-xml to xml
574
633
  let effectiveFormat = format;
575
634
  if (outline && format === 'outline-xml') {