@probelabs/probe 0.6.0-rc288 → 0.6.0-rc291

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/bin/binaries/probe-v0.6.0-rc291-aarch64-apple-darwin.tar.gz +0 -0
  2. package/bin/binaries/probe-v0.6.0-rc291-aarch64-unknown-linux-musl.tar.gz +0 -0
  3. package/bin/binaries/probe-v0.6.0-rc291-x86_64-apple-darwin.tar.gz +0 -0
  4. package/bin/binaries/probe-v0.6.0-rc291-x86_64-pc-windows-msvc.zip +0 -0
  5. package/bin/binaries/probe-v0.6.0-rc291-x86_64-unknown-linux-musl.tar.gz +0 -0
  6. package/build/agent/ProbeAgent.js +95 -14
  7. package/build/agent/index.js +401 -86261
  8. package/build/agent/shared/prompts.js +27 -6
  9. package/build/extract.js +4 -2
  10. package/build/mcp/index.js +122 -9
  11. package/build/mcp/index.ts +162 -17
  12. package/build/search.js +6 -5
  13. package/build/tools/vercel.js +56 -23
  14. package/build/utils/error-types.js +2 -2
  15. package/build/utils/path-validation.js +1 -1
  16. package/cjs/agent/ProbeAgent.cjs +193 -45
  17. package/cjs/index.cjs +193 -45
  18. package/package.json +2 -1
  19. package/src/agent/ProbeAgent.js +95 -14
  20. package/src/agent/shared/prompts.js +27 -6
  21. package/src/extract.js +4 -2
  22. package/src/mcp/index.ts +162 -17
  23. package/src/search.js +6 -5
  24. package/src/tools/vercel.js +56 -23
  25. package/src/utils/error-types.js +2 -2
  26. package/src/utils/path-validation.js +1 -1
  27. package/bin/binaries/probe-v0.6.0-rc288-aarch64-apple-darwin.tar.gz +0 -0
  28. package/bin/binaries/probe-v0.6.0-rc288-aarch64-unknown-linux-musl.tar.gz +0 -0
  29. package/bin/binaries/probe-v0.6.0-rc288-x86_64-apple-darwin.tar.gz +0 -0
  30. package/bin/binaries/probe-v0.6.0-rc288-x86_64-pc-windows-msvc.zip +0 -0
  31. package/bin/binaries/probe-v0.6.0-rc288-x86_64-unknown-linux-musl.tar.gz +0 -0
package/cjs/index.cjs CHANGED
@@ -1447,13 +1447,13 @@ function categorizeError(error40) {
1447
1447
  if (lowerMessage.includes("path does not exist") || lowerMessage.includes("no such file or directory") || errorCode === "enoent") {
1448
1448
  return new PathError(message, {
1449
1449
  originalError: error40,
1450
- suggestion: "The specified path does not exist. Please verify the path or use a different directory."
1450
+ suggestion: "The specified path does not exist. Use the listFiles tool to check the correct directory structure, then retry with a valid path."
1451
1451
  });
1452
1452
  }
1453
1453
  if (lowerMessage.includes("not a directory") || errorCode === "enotdir") {
1454
1454
  return new PathError(message, {
1455
1455
  originalError: error40,
1456
- suggestion: "The path is not a directory. Please provide a valid directory path."
1456
+ suggestion: "The path is not a directory. Use the listFiles tool to find the correct directory, then retry."
1457
1457
  });
1458
1458
  }
1459
1459
  if (lowerMessage.includes("permission denied") || errorCode === "eacces") {
@@ -1709,7 +1709,7 @@ async function validateCwdPath(inputPath, defaultPath = process.cwd()) {
1709
1709
  }
1710
1710
  if (error40.code === "ENOENT") {
1711
1711
  throw new PathError(`Path does not exist: ${normalizedPath}`, {
1712
- suggestion: "The specified path does not exist. Please verify the path is correct or use a different directory.",
1712
+ suggestion: "The specified path does not exist. Use the listFiles tool to check the correct directory structure, then retry with a valid path.",
1713
1713
  details: { path: normalizedPath }
1714
1714
  });
1715
1715
  }
@@ -1944,7 +1944,8 @@ var init_search = __esm({
1944
1944
  session: "--session",
1945
1945
  timeout: "--timeout",
1946
1946
  language: "--language",
1947
- format: "--format"
1947
+ format: "--format",
1948
+ lsp: "--lsp"
1948
1949
  };
1949
1950
  }
1950
1951
  });
@@ -2180,7 +2181,8 @@ var init_extract = __esm({
2180
2181
  allowTests: "--allow-tests",
2181
2182
  contextLines: "--context",
2182
2183
  format: "--format",
2183
- inputFile: "--input-file"
2184
+ inputFile: "--input-file",
2185
+ lsp: "--lsp"
2184
2186
  };
2185
2187
  }
2186
2188
  });
@@ -73070,26 +73072,46 @@ var init_prompts = __esm({
73070
73072
  CRITICAL - You are READ-ONLY:
73071
73073
  You must NEVER create, modify, delete, or write files. You are strictly an exploration and analysis tool. If asked to make changes, implement features, fix bugs, or modify a PR, refuse and explain that file modifications must be done by the engineer tool \u2014 your role is only to investigate code and answer questions. Do not attempt workarounds using bash commands (echo, cat, tee, sed, etc.) to write files.
73072
73074
 
73075
+ CRITICAL - ALWAYS search before answering:
73076
+ You must NEVER answer questions about the codebase from memory or general knowledge. ALWAYS use the search and extract tools first to find the actual code, then base your answer ONLY on what you found. Even if you think you know the answer, you MUST verify it against the actual code. Your answers must be grounded in code evidence, not assumptions.
73077
+
73073
73078
  When exploring code:
73074
73079
  - Provide clear, concise explanations based on user request
73075
73080
  - Find and highlight the most relevant code snippets, if required
73076
- - Trace function calls and data flow through the system
73081
+ - Trace function calls and data flow through the system \u2014 follow the FULL call chain, not just the entry point
73077
73082
  - Try to understand the user's intent and provide relevant information
73078
73083
  - Understand high level picture
73079
73084
  - Balance detail with clarity in your explanations
73085
+ - Search using SYNONYMS and alternative terms \u2014 code naming often differs from the concept name (e.g., "authentication" might be named verify_credentials, check_token, validate_session)
73086
+ - When you find a key function, look at what it CALLS and what CALLS it to discover the complete picture
73087
+ - Before answering, ask yourself: "Did I cover all the major components? Are there related subsystems I missed?" If yes, do one more search round.
73080
73088
 
73081
73089
  When providing answers:
73090
+ - Be EXHAUSTIVE: cover ALL components you discovered, not just the main ones. If you found 10 related files, discuss all 10, not just the top 3. Users want the complete picture.
73091
+ - After drafting your answer, do a self-check: "What did I find in my searches that I haven't mentioned yet?" Add any missing components.
73092
+ - Include data structures, configuration options, and error handling \u2014 not just the happy path.
73082
73093
  - Always include a "References" section at the end of your response
73083
73094
  - List all relevant source code locations you found during exploration
73084
73095
  - Use the format: file_path:line_number or file_path#symbol_name
73085
73096
  - Group references by file when multiple locations are from the same file
73086
73097
  - Include brief descriptions of what each reference contains`,
73087
- "code-searcher": `You are ProbeChat Code Searcher, a specialized AI assistant focused ONLY on locating relevant code. Your sole job is to find and return ALL relevant code locations. Do NOT answer questions or explain anything.
73098
+ "code-searcher": `You are ProbeChat Code Explorer & Searcher. Your job is to EXPLORE the codebase to find ALL relevant code locations for the query, then return them as JSON targets.
73099
+
73100
+ You think like a code explorer \u2014 you understand that codebases have layers:
73101
+ - Core implementations (algorithms, data structures)
73102
+ - Middleware/integration layers (request handlers, interceptors)
73103
+ - Configuration and storage backends
73104
+ - Scoping mechanisms (per-user, per-org, per-tenant, global)
73105
+ - Supporting utilities and helpers
73088
73106
 
73089
73107
  When searching:
73090
- - Use only the search tool
73091
- - Run additional searches only if needed to capture all relevant locations
73092
- - Prefer specific, focused queries
73108
+ - Search for the MAIN concept first, then think: "what RELATED subsystems would a real codebase have?"
73109
+ - Use extract to READ the code you find \u2014 look for function calls, type references, and imports that point to OTHER relevant code
73110
+ - If you find middleware, check: are there org-level or tenant-level variants?
73111
+ - If you find algorithms, check: are there different storage backends?
73112
+ - Search results are paginated \u2014 if results look relevant, call nextPage=true to check for more files
73113
+ - Stop paginating when results become irrelevant or you see "All results retrieved"
73114
+ - Search using SYNONYMS \u2014 code naming differs from concepts (e.g., "rate limiting" \u2192 throttle, quota, limiter, bucket)
73093
73115
 
73094
73116
  Output format (MANDATORY):
73095
73117
  - Return ONLY valid JSON with a single top-level key: "targets"
@@ -73099,7 +73121,8 @@ Output format (MANDATORY):
73099
73121
  - "path/to/file.ext:line"
73100
73122
  - "path/to/file.ext:start-end"
73101
73123
  - Prefer #SymbolName when a function/class name is clear; otherwise use line numbers
73102
- - Deduplicate targets and keep them concise`,
73124
+ - Deduplicate targets and keep them concise
73125
+ - Aim for 5-15 targets covering ALL aspects of the query`,
73103
73126
  "architect": `You are ProbeChat Architect, a specialized AI assistant focused on software architecture and design. Your primary function is to help users understand, analyze, and design software systems using the provided code analysis tools.
73104
73127
 
73105
73128
  When analyzing code:
@@ -82677,6 +82700,9 @@ var require_utils2 = __commonJS({
82677
82700
  sandboxGlobal
82678
82701
  };
82679
82702
  context.prototypeWhitelist.set(Object.getPrototypeOf([][Symbol.iterator]()), /* @__PURE__ */ new Set());
82703
+ context.prototypeWhitelist.set(Object.getPrototypeOf(""[Symbol.iterator]()), /* @__PURE__ */ new Set());
82704
+ context.prototypeWhitelist.set(Object.getPrototypeOf((/* @__PURE__ */ new Set())[Symbol.iterator]()), /* @__PURE__ */ new Set());
82705
+ context.prototypeWhitelist.set(Object.getPrototypeOf((/* @__PURE__ */ new Map())[Symbol.iterator]()), /* @__PURE__ */ new Set());
82680
82706
  return context;
82681
82707
  }
82682
82708
  function createExecContext(sandbox, executionTree, evalContext) {
@@ -83815,6 +83841,18 @@ var require_executor = __commonJS({
83815
83841
  a = void 0;
83816
83842
  }
83817
83843
  }
83844
+ if (op === 29 && !a) {
83845
+ done(void 0, a);
83846
+ return;
83847
+ }
83848
+ if (op === 30 && a) {
83849
+ done(void 0, a);
83850
+ return;
83851
+ }
83852
+ if (op === 85 && a !== null && a !== void 0) {
83853
+ done(void 0, a);
83854
+ return;
83855
+ }
83818
83856
  let bobj;
83819
83857
  try {
83820
83858
  let ad;
@@ -83877,6 +83915,18 @@ var require_executor = __commonJS({
83877
83915
  a = void 0;
83878
83916
  }
83879
83917
  }
83918
+ if (op === 29 && !a) {
83919
+ done(void 0, a);
83920
+ return;
83921
+ }
83922
+ if (op === 30 && a) {
83923
+ done(void 0, a);
83924
+ return;
83925
+ }
83926
+ if (op === 85 && a !== null && a !== void 0) {
83927
+ done(void 0, a);
83928
+ return;
83929
+ }
83880
83930
  let bobj;
83881
83931
  try {
83882
83932
  bobj = syncDone((d) => execSync(ticks, tree[2], scope, context, d, inLoopOrSwitch)).result;
@@ -97515,7 +97565,7 @@ var init_ProbeAgent = __esm({
97515
97565
  if (limiter && result.textStream) {
97516
97566
  const originalStream = result.textStream;
97517
97567
  const debug = this.debug;
97518
- result.textStream = (async function* () {
97568
+ const wrappedStream = (async function* () {
97519
97569
  try {
97520
97570
  for await (const chunk of originalStream) {
97521
97571
  yield chunk;
@@ -97528,6 +97578,13 @@ var init_ProbeAgent = __esm({
97528
97578
  }
97529
97579
  }
97530
97580
  })();
97581
+ return new Proxy(result, {
97582
+ get(target, prop) {
97583
+ if (prop === "textStream") return wrappedStream;
97584
+ const value = target[prop];
97585
+ return typeof value === "function" ? value.bind(target) : value;
97586
+ }
97587
+ });
97531
97588
  } else if (limiter) {
97532
97589
  limiter.release(null);
97533
97590
  }
@@ -98753,9 +98810,9 @@ Workspace: ${this.allowedFolders.join(", ")}`;
98753
98810
  Follow these instructions carefully:
98754
98811
  1. Analyze the user's request.
98755
98812
  2. Use the available tools step-by-step to fulfill the request.
98756
- 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."}
98757
- 4. Ensure to get really deep and understand the full picture before answering.
98758
- 5. Once the task is fully completed, provide your final answer directly as text.
98813
+ 3. You MUST use the search tool before answering ANY code-related question. NEVER answer from memory or general knowledge \u2014 your answers must be grounded in actual code found via search/extract.${this.searchDelegate ? " Ask natural language questions \u2014 the search subagent handles keyword formulation and returns extracted code blocks. Use extract only to expand context or read full files." : " Search handles stemming and case variations automatically \u2014 do NOT try keyword variations manually. Read full files only if really necessary."}
98814
+ 4. Ensure to get really deep and understand the full picture before answering. Follow call chains \u2014 if function A calls B, search for B too. Look for related subsystems (e.g., if asked about rate limiting, also check for quota, throttling, smoothing).
98815
+ 5. Once the task is fully completed, provide your final answer directly as text. Always cite specific files and line numbers as evidence. Do NOT output planning or thinking text \u2014 go straight to the answer.
98759
98816
  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."}
98760
98817
  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 ? `
98761
98818
  7. When modifying files, choose the appropriate tool:
@@ -99141,6 +99198,22 @@ You are working with a workspace. Available paths: ${workspaceDesc}
99141
99198
  if (recentTexts.every((t) => t && t === recentTexts[0])) return true;
99142
99199
  if (recentTexts.every((t) => detectStuckResponse(t))) return true;
99143
99200
  }
99201
+ if (steps.length >= 3) {
99202
+ const last3 = steps.slice(-3);
99203
+ const allHaveTools = last3.every((s) => s.toolCalls?.length === 1);
99204
+ if (allHaveTools) {
99205
+ const signatures = last3.map((s) => {
99206
+ const tc = s.toolCalls[0];
99207
+ return `${tc.toolName}::${JSON.stringify(tc.args ?? tc.input)}`;
99208
+ });
99209
+ if (signatures[0] === signatures[1] && signatures[1] === signatures[2]) {
99210
+ if (this.debug) {
99211
+ console.log(`[DEBUG] Circuit breaker: 3 consecutive identical tool calls detected (${last3[0].toolCalls[0].toolName}), forcing stop`);
99212
+ }
99213
+ return true;
99214
+ }
99215
+ }
99216
+ }
99144
99217
  return false;
99145
99218
  },
99146
99219
  prepareStep: ({ steps, stepNumber }) => {
@@ -99149,6 +99222,37 @@ You are working with a workspace. Available paths: ${workspaceDesc}
99149
99222
  toolChoice: "none"
99150
99223
  };
99151
99224
  }
99225
+ if (steps.length >= 2) {
99226
+ const last2 = steps.slice(-2);
99227
+ if (last2.every((s) => s.toolCalls?.length === 1)) {
99228
+ const tc1 = last2[0].toolCalls[0];
99229
+ const tc2 = last2[1].toolCalls[0];
99230
+ const sig1 = `${tc1.toolName}::${JSON.stringify(tc1.args ?? tc1.input)}`;
99231
+ const sig2 = `${tc2.toolName}::${JSON.stringify(tc2.args ?? tc2.input)}`;
99232
+ if (sig1 === sig2) {
99233
+ if (this.debug) {
99234
+ console.log(`[DEBUG] prepareStep: 2 consecutive identical tool calls (${tc1.toolName}), forcing toolChoice=none`);
99235
+ console.log(`[DEBUG] sig: ${sig1.substring(0, 200)}`);
99236
+ }
99237
+ return { toolChoice: "none" };
99238
+ }
99239
+ }
99240
+ }
99241
+ if (steps.length >= 3) {
99242
+ const last3 = steps.slice(-3);
99243
+ const allErrors = last3.every(
99244
+ (s) => s.toolResults?.length > 0 && s.toolResults.every((tr) => {
99245
+ const r = typeof tr.result === "string" ? tr.result : "";
99246
+ return r.includes("<error ") || r.includes("does not exist");
99247
+ })
99248
+ );
99249
+ if (allErrors) {
99250
+ if (this.debug) {
99251
+ console.log(`[DEBUG] prepareStep: 3 consecutive tool errors, forcing toolChoice=none`);
99252
+ }
99253
+ return { toolChoice: "none" };
99254
+ }
99255
+ }
99152
99256
  const lastStep = steps[steps.length - 1];
99153
99257
  const modelJustStopped = lastStep?.finishReason === "stop" && (!lastStep?.toolCalls || lastStep.toolCalls.length === 0);
99154
99258
  if (modelJustStopped) {
@@ -99177,9 +99281,12 @@ Here is the result to review:
99177
99281
  ${resultToReview}
99178
99282
  </result>
99179
99283
 
99180
- Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. If something needs to be fixed or is missing, do it now, then respond with the COMPLETE updated answer (everything you did in total, not just the fix).`;
99284
+ IMPORTANT: First review ALL completed work in the conversation above before taking any action.
99285
+ Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. If your text has inaccuracies, fix the text. Only call a tool if you find a genuinely MISSING action \u2014 NEVER redo work that was already completed successfully. Respond with the COMPLETE corrected answer.`;
99181
99286
  return {
99182
- userMessage: completionPromptMessage
99287
+ userMessage: completionPromptMessage,
99288
+ toolChoice: "none"
99289
+ // Force text-only review — no tool calls
99183
99290
  };
99184
99291
  }
99185
99292
  }
@@ -99221,7 +99328,11 @@ Double-check your response based on the criteria above. If everything looks good
99221
99328
  options.onStream(text);
99222
99329
  }
99223
99330
  if (this.debug) {
99224
- console.log(`[DEBUG] Step ${currentIteration}/${maxIterations} finished (reason: ${finishReason}, tools: ${toolResults?.length || 0})`);
99331
+ const toolSummary = toolCalls?.length ? toolCalls.map((tc) => {
99332
+ const args = tc.args ? JSON.stringify(tc.args) : "";
99333
+ return args ? `${tc.toolName}(${debugTruncate(args, 120)})` : tc.toolName;
99334
+ }).join(", ") : "none";
99335
+ console.log(`[DEBUG] Step ${currentIteration}/${maxIterations} finished (reason: ${finishReason}, tools: [${toolSummary}])`);
99225
99336
  if (text) {
99226
99337
  console.log(`[DEBUG] model text: ${debugTruncate(text)}`);
99227
99338
  }
@@ -99254,9 +99365,15 @@ Double-check your response based on the criteria above. If everything looks good
99254
99365
  }
99255
99366
  const executeAIRequest = async () => {
99256
99367
  const result = await this.streamTextWithRetryAndFallback(streamOptions);
99257
- const finalText = await result.text;
99368
+ const steps = await result.steps;
99369
+ let finalText;
99370
+ if (steps && steps.length > 1) {
99371
+ const lastStepText = steps[steps.length - 1].text;
99372
+ finalText = lastStepText || await result.text;
99373
+ } else {
99374
+ finalText = await result.text;
99375
+ }
99258
99376
  if (this.debug) {
99259
- const steps = await result.steps;
99260
99377
  console.log(`[DEBUG] streamText completed: ${steps?.length || 0} steps, finalText=${finalText?.length || 0} chars`);
99261
99378
  }
99262
99379
  const usage = await result.usage;
@@ -99324,14 +99441,15 @@ Here is the result to review:
99324
99441
  ${finalResult}
99325
99442
  </result>
99326
99443
 
99327
- Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. If something needs to be fixed or is missing, do it now, then respond with the COMPLETE updated answer (everything you did in total, not just the fix).`;
99444
+ IMPORTANT: First review ALL completed work in the conversation above before taking any action.
99445
+ Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. If your text has inaccuracies, fix the text. Only call a tool if you find a genuinely MISSING action \u2014 NEVER redo work that was already completed successfully. Respond with the COMPLETE corrected answer.`;
99328
99446
  currentMessages.push({ role: "user", content: completionPromptMessage });
99329
- const completionMaxIterations = 5;
99330
99447
  const completionStreamOptions = {
99331
99448
  model: this.provider ? this.provider(this.model) : this.model,
99332
99449
  messages: this.prepareMessagesWithImages(currentMessages),
99333
99450
  tools: tools2,
99334
- stopWhen: (0, import_ai4.stepCountIs)(completionMaxIterations),
99451
+ toolChoice: "none",
99452
+ // Force text-only response — no tool calls during review
99335
99453
  maxTokens: maxResponseTokens,
99336
99454
  temperature: 0.3,
99337
99455
  onStepFinish: ({ toolResults, text, finishReason, usage }) => {
@@ -101000,11 +101118,9 @@ function autoQuoteSearchTerms(query2) {
101000
101118
  const result = tokens.map((token) => {
101001
101119
  if (token.startsWith('"')) return token;
101002
101120
  if (operators.has(token)) return token;
101003
- const hasUpper = /[A-Z]/.test(token);
101004
- const hasLower = /[a-z]/.test(token);
101005
101121
  const hasUnderscore = token.includes("_");
101006
- const hasMixedCase = hasUpper && hasLower;
101007
- if (hasMixedCase || hasUnderscore) {
101122
+ const hasCaseTransition = /[a-z][A-Z]/.test(token) || /[A-Z]{2,}[a-z]/.test(token);
101123
+ if (hasCaseTransition || hasUnderscore) {
101008
101124
  return `"${token}"`;
101009
101125
  }
101010
101126
  return token;
@@ -101119,7 +101235,7 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
101119
101235
  "Break down complex queries into multiple searches to cover all aspects.",
101120
101236
  "",
101121
101237
  "Available tools:",
101122
- "- search: Find code matching keywords or patterns. Run multiple searches for different aspects of complex queries.",
101238
+ "- search: Find code matching keywords or patterns. Results are paginated \u2014 use nextPage=true when results are relevant to get more. Run multiple searches for different aspects.",
101123
101239
  "- extract: Verify code snippets to ensure targets are actually relevant before including them.",
101124
101240
  "- listFiles: Understand directory structure to find where relevant code might live.",
101125
101241
  "",
@@ -101140,13 +101256,14 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
101140
101256
  "",
101141
101257
  "Combining searches with OR:",
101142
101258
  '- Multiple unquoted words use OR logic: rate limit matches files containing EITHER "rate" OR "limit".',
101143
- `- For known symbol names, quote each term to prevent splitting: '"limitDRL" "limitRedis"' matches either exact symbol.`,
101259
+ `- IMPORTANT: Multiple quoted terms use AND logic by default: '"RateLimit" "middleware"' requires BOTH in the same file.`,
101260
+ `- To search for ANY of several quoted symbols, use the explicit OR operator: '"ForwardMessage" OR "SessionLimiter"'.`,
101144
101261
  '- Without quotes, camelCase like limitDRL gets split into "limit" + "DRL" \u2014 not what you want for symbol lookup.',
101145
101262
  "- Use OR to search for multiple related symbols in ONE search instead of separate searches.",
101146
101263
  "- This is much faster than running separate searches sequentially.",
101147
- `- Example: search '"ForwardMessage" "SessionLimiter"' finds files with either exact symbol in one call.`,
101148
- `- Example: search '"limitDRL" "doRollingWindowWrite"' finds both rate limiting functions at once.`,
101149
- '- Use AND only when you need both terms to appear in the same file: "rate AND limit".',
101264
+ `- Example: search '"ForwardMessage" OR "SessionLimiter"' finds files with either exact symbol in one call.`,
101265
+ `- Example: search '"limitDRL" OR "doRollingWindowWrite"' finds both rate limiting functions at once.`,
101266
+ "- Use AND (or just put quoted terms together) when you need both terms in the same file.",
101150
101267
  "",
101151
101268
  "Parallel tool calls:",
101152
101269
  "- When you need to search for INDEPENDENT concepts, call multiple search tools IN PARALLEL (same response).",
@@ -101160,10 +101277,10 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
101160
101277
  ' Query: "Find the IP allowlist middleware"',
101161
101278
  ' \u2192 search "allowlist middleware" (one search, probe handles IP/ip/Ip variations)',
101162
101279
  ' Query: "Find ForwardMessage and SessionLimiter"',
101163
- ` \u2192 search '"ForwardMessage" "SessionLimiter"' (one OR search finds both exact symbols)`,
101280
+ ` \u2192 search '"ForwardMessage" OR "SessionLimiter"' (one OR search finds both exact symbols)`,
101164
101281
  ' OR: search exact=true "ForwardMessage" + search exact=true "SessionLimiter" IN PARALLEL',
101165
101282
  ' Query: "Find limitDRL and limitRedis functions"',
101166
- ` \u2192 search '"limitDRL" "limitRedis"' (one OR search, quoted to prevent camelCase splitting)`,
101283
+ ` \u2192 search '"limitDRL" OR "limitRedis"' (one OR search, quoted to prevent camelCase splitting)`,
101167
101284
  ' Query: "Find ThrottleRetryLimit usage"',
101168
101285
  ' \u2192 search exact=true "ThrottleRetryLimit" (one search, if no results the symbol does not exist \u2014 stop)',
101169
101286
  ' Query: "How does BM25 scoring work with SIMD optimization?"',
@@ -101171,7 +101288,7 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
101171
101288
  "",
101172
101289
  "BAD search strategy (never do this):",
101173
101290
  ' \u2192 search "AllowedIPs" \u2192 search "allowedIps" \u2192 search "allowed_ips" (WRONG: case/style variations, probe handles them)',
101174
- ` \u2192 search "limitDRL" \u2192 search "LimitDRL" (WRONG: case variation \u2014 combine with OR: '"limitDRL" "limitRedis"')`,
101291
+ ` \u2192 search "limitDRL" \u2192 search "LimitDRL" (WRONG: case variation \u2014 combine with OR: '"limitDRL" OR "limitRedis"')`,
101175
101292
  ' \u2192 search "throttle_retry_limit" after searching "ThrottleRetryLimit" (WRONG: snake_case variation, probe handles it)',
101176
101293
  ' \u2192 search "ThrottleRetryLimit" path=tyk \u2192 search "ThrottleRetryLimit" path=gateway \u2192 search "ThrottleRetryLimit" path=apidef (WRONG: same query on different paths \u2014 probe searches recursively)',
101177
101294
  ' \u2192 search "func (k *RateLimitAndQuotaCheck) handleRateLimitFailure" (WRONG: do not search full function signatures, just use exact=true "handleRateLimitFailure")',
@@ -101184,15 +101301,34 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
101184
101301
  '- To bypass stopword filtering: wrap terms in quotes ("return", "struct") or set exact=true. Both disable stemming and splitting too.',
101185
101302
  '- camelCase terms are split: getUserData becomes "get", "user", "data" \u2014 so one search covers all naming styles.',
101186
101303
  '- Do NOT search for full function signatures like "func (r *Type) Method(args)". Just search for the method name with exact=true.',
101304
+ '- Do NOT search for file names (e.g., "sliding_log.go"). Use listFiles to discover files by name.',
101305
+ "",
101306
+ "PAGINATION:",
101307
+ "- Search results are paginated (~20k tokens per page).",
101308
+ "- If your search returned relevant files, call the same query with nextPage=true to check for more.",
101309
+ '- Keep paginating while results stay relevant. Stop when results are off-topic or "All results retrieved".',
101310
+ "",
101311
+ "WHEN TO STOP:",
101312
+ "- After you have explored the main concept AND related subsystems.",
101313
+ "- Once you have 5-15 targets covering different aspects of the query.",
101314
+ '- If you get a "DUPLICATE SEARCH BLOCKED" message, move on.',
101187
101315
  "",
101188
101316
  "Strategy:",
101189
- "1. Analyze the query - identify key concepts and group related symbols",
101190
- `2. Combine related symbols into OR searches: '"symbolA" "symbolB"' finds files with either (quote to prevent splitting)`,
101191
- "3. Run INDEPENDENT searches in PARALLEL \u2014 do not wait for one to finish before starting another",
101317
+ "1. Analyze the query \u2014 identify key concepts, then brainstorm SYNONYMS and alternative terms for each.",
101318
+ ' Code naming often differs from the concept: "authentication" \u2192 verify, credentials, login, auth;',
101319
+ ' "rate limiting" \u2192 throttle, quota, limiter, bucket; "error handling" \u2192 catch, recover, panic.',
101320
+ " Think about what a developer would NAME the function/struct/variable, not just the concept.",
101321
+ "2. Run INDEPENDENT searches in PARALLEL \u2014 search for the main concept AND synonyms simultaneously.",
101322
+ " After each search, check if results are relevant. If yes, call nextPage=true for more results.",
101323
+ `3. Combine related symbols into OR searches: '"symbolA" OR "symbolB"' finds files with either.`,
101192
101324
  "4. For known symbol names use exact=true. For concepts use default (exact=false).",
101193
- "5. If a search returns results, use extract to verify relevance. Run multiple extracts in parallel too.",
101194
- "6. If a search returns NO results, the term does not exist. Do NOT retry with variations, different paths, or longer strings. Move on.",
101195
- "7. Combine all relevant targets in your final response",
101325
+ "5. After your first round of searches, READ the extracted code and look for connected code:",
101326
+ " - Function calls to other important functions \u2192 include those targets.",
101327
+ " - Type references and imports \u2192 include type definitions.",
101328
+ " - Registered handlers/middleware \u2192 include all registered items.",
101329
+ "6. If a search returns results, use extract to verify relevance. Run multiple extracts in parallel too.",
101330
+ "7. If a search returns NO results, the term does not exist. Do NOT retry with variations. Move on.",
101331
+ "8. Once you have enough targets (typically 5-15), output your final JSON answer immediately.",
101196
101332
  "",
101197
101333
  `Query: ${searchQuery}`,
101198
101334
  `Search path(s): ${searchPath}`,
@@ -101201,7 +101337,9 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
101201
101337
  'Return ONLY valid JSON: {"targets": ["path/to/file.ext#Symbol", "path/to/file.ext:line", "path/to/file.ext:start-end"]}',
101202
101338
  'IMPORTANT: Use ABSOLUTE file paths in targets (e.g., "/full/path/to/file.ext#Symbol"). If you only have relative paths, make them relative to the search path above.',
101203
101339
  "Prefer #Symbol when a function/class name is clear; otherwise use line numbers.",
101204
- "Deduplicate targets. Do NOT explain or answer - ONLY return the JSON targets."
101340
+ "Deduplicate targets. Do NOT explain or answer - ONLY return the JSON targets.",
101341
+ "",
101342
+ "Remember: if your search returned relevant results, use nextPage=true to check for more before outputting."
101205
101343
  ].join("\n");
101206
101344
  }
101207
101345
  var import_ai5, import_fs11, CODE_SEARCH_SCHEMA, searchTool, queryTool, extractTool, delegateTool, analyzeAllTool;
@@ -101246,6 +101384,7 @@ var init_vercel = __esm({
101246
101384
  return result;
101247
101385
  };
101248
101386
  const previousSearches = /* @__PURE__ */ new Set();
101387
+ let consecutiveDupBlocks = 0;
101249
101388
  const paginationCounts = /* @__PURE__ */ new Map();
101250
101389
  const MAX_PAGES_PER_QUERY = 3;
101251
101390
  return (0, import_ai5.tool)({
@@ -101298,12 +101437,17 @@ var init_vercel = __esm({
101298
101437
  const searchKey = `${searchQuery}::${exact || false}`;
101299
101438
  if (!nextPage) {
101300
101439
  if (previousSearches.has(searchKey)) {
101440
+ consecutiveDupBlocks++;
101301
101441
  if (debug) {
101302
- console.error(`[DEDUP] Blocked duplicate search: "${searchQuery}" (path: "${searchPath}")`);
101442
+ console.error(`[DEDUP] Blocked duplicate search (${consecutiveDupBlocks}x): "${searchQuery}" (path: "${searchPath}")`);
101303
101443
  }
101304
- return "DUPLICATE SEARCH BLOCKED: You already searched for this exact query. Changing the path does NOT give different results \u2014 probe searches recursively. Do NOT repeat the same search. Try a genuinely different keyword, use extract to examine results you already found, or provide your final answer if you have enough information.";
101444
+ if (consecutiveDupBlocks >= 3) {
101445
+ return "STOP. You have been blocked " + consecutiveDupBlocks + " times for repeating searches. You MUST output your final JSON answer NOW with whatever targets you have found. Do NOT call any more tools.";
101446
+ }
101447
+ return "DUPLICATE SEARCH BLOCKED (" + consecutiveDupBlocks + "x). You already searched for this. Do NOT repeat \u2014 probe searches recursively across all paths. Either: (1) use extract on results you already found, (2) try a COMPLETELY different keyword, or (3) output your final answer NOW.";
101305
101448
  }
101306
101449
  previousSearches.add(searchKey);
101450
+ consecutiveDupBlocks = 0;
101307
101451
  paginationCounts.set(searchKey, 0);
101308
101452
  } else {
101309
101453
  const pageCount = (paginationCounts.get(searchKey) || 0) + 1;
@@ -101324,7 +101468,11 @@ var init_vercel = __esm({
101324
101468
  return result;
101325
101469
  } catch (error40) {
101326
101470
  console.error("Error executing search command:", error40);
101327
- return formatErrorForAI(error40);
101471
+ const formatted = formatErrorForAI(error40);
101472
+ if (error40.category === "path_error" || error40.message?.includes("does not exist")) {
101473
+ return formatted + "\n\nThe path does not exist. Use the listFiles tool to verify the correct directory structure before retrying. If the workspace itself is gone, output your final answer with whatever information you have.";
101474
+ }
101475
+ return formatted;
101328
101476
  }
101329
101477
  }
101330
101478
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@probelabs/probe",
3
- "version": "0.6.0-rc288",
3
+ "version": "0.6.0-rc291",
4
4
  "description": "Node.js wrapper for the probe code search tool",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -49,6 +49,7 @@
49
49
  ],
50
50
  "scripts": {
51
51
  "postinstall": "node scripts/postinstall.js",
52
+ "prepare": "npm run build:mcp",
52
53
  "build:mcp": "node scripts/build-mcp.cjs",
53
54
  "build:agent": "node scripts/build-agent.cjs",
54
55
  "build:types": "echo 'TypeScript definitions already manually created'",