@probelabs/probe 0.6.0-rc288 → 0.6.0-rc290

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cjs/index.cjs CHANGED
@@ -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:
@@ -98753,9 +98776,9 @@ Workspace: ${this.allowedFolders.join(", ")}`;
98753
98776
  Follow these instructions carefully:
98754
98777
  1. Analyze the user's request.
98755
98778
  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.
98779
+ 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."}
98780
+ 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).
98781
+ 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
98782
  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
98783
  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
98784
  7. When modifying files, choose the appropriate tool:
@@ -99141,6 +99164,22 @@ You are working with a workspace. Available paths: ${workspaceDesc}
99141
99164
  if (recentTexts.every((t) => t && t === recentTexts[0])) return true;
99142
99165
  if (recentTexts.every((t) => detectStuckResponse(t))) return true;
99143
99166
  }
99167
+ if (steps.length >= 3) {
99168
+ const last3 = steps.slice(-3);
99169
+ const allHaveTools = last3.every((s) => s.toolCalls?.length === 1);
99170
+ if (allHaveTools) {
99171
+ const signatures = last3.map((s) => {
99172
+ const tc = s.toolCalls[0];
99173
+ return `${tc.toolName}::${JSON.stringify(tc.args ?? tc.input)}`;
99174
+ });
99175
+ if (signatures[0] === signatures[1] && signatures[1] === signatures[2]) {
99176
+ if (this.debug) {
99177
+ console.log(`[DEBUG] Circuit breaker: 3 consecutive identical tool calls detected (${last3[0].toolCalls[0].toolName}), forcing stop`);
99178
+ }
99179
+ return true;
99180
+ }
99181
+ }
99182
+ }
99144
99183
  return false;
99145
99184
  },
99146
99185
  prepareStep: ({ steps, stepNumber }) => {
@@ -99149,6 +99188,22 @@ You are working with a workspace. Available paths: ${workspaceDesc}
99149
99188
  toolChoice: "none"
99150
99189
  };
99151
99190
  }
99191
+ if (steps.length >= 2) {
99192
+ const last2 = steps.slice(-2);
99193
+ if (last2.every((s) => s.toolCalls?.length === 1)) {
99194
+ const tc1 = last2[0].toolCalls[0];
99195
+ const tc2 = last2[1].toolCalls[0];
99196
+ const sig1 = `${tc1.toolName}::${JSON.stringify(tc1.args ?? tc1.input)}`;
99197
+ const sig2 = `${tc2.toolName}::${JSON.stringify(tc2.args ?? tc2.input)}`;
99198
+ if (sig1 === sig2) {
99199
+ if (this.debug) {
99200
+ console.log(`[DEBUG] prepareStep: 2 consecutive identical tool calls (${tc1.toolName}), forcing toolChoice=none`);
99201
+ console.log(`[DEBUG] sig: ${sig1.substring(0, 200)}`);
99202
+ }
99203
+ return { toolChoice: "none" };
99204
+ }
99205
+ }
99206
+ }
99152
99207
  const lastStep = steps[steps.length - 1];
99153
99208
  const modelJustStopped = lastStep?.finishReason === "stop" && (!lastStep?.toolCalls || lastStep.toolCalls.length === 0);
99154
99209
  if (modelJustStopped) {
@@ -99179,7 +99234,9 @@ ${resultToReview}
99179
99234
 
99180
99235
  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).`;
99181
99236
  return {
99182
- userMessage: completionPromptMessage
99237
+ userMessage: completionPromptMessage,
99238
+ toolChoice: "none"
99239
+ // Force text-only review — no tool calls
99183
99240
  };
99184
99241
  }
99185
99242
  }
@@ -99221,7 +99278,11 @@ Double-check your response based on the criteria above. If everything looks good
99221
99278
  options.onStream(text);
99222
99279
  }
99223
99280
  if (this.debug) {
99224
- console.log(`[DEBUG] Step ${currentIteration}/${maxIterations} finished (reason: ${finishReason}, tools: ${toolResults?.length || 0})`);
99281
+ const toolSummary = toolCalls?.length ? toolCalls.map((tc) => {
99282
+ const args = tc.args ? JSON.stringify(tc.args) : "";
99283
+ return args ? `${tc.toolName}(${debugTruncate(args, 120)})` : tc.toolName;
99284
+ }).join(", ") : "none";
99285
+ console.log(`[DEBUG] Step ${currentIteration}/${maxIterations} finished (reason: ${finishReason}, tools: [${toolSummary}])`);
99225
99286
  if (text) {
99226
99287
  console.log(`[DEBUG] model text: ${debugTruncate(text)}`);
99227
99288
  }
@@ -99254,9 +99315,15 @@ Double-check your response based on the criteria above. If everything looks good
99254
99315
  }
99255
99316
  const executeAIRequest = async () => {
99256
99317
  const result = await this.streamTextWithRetryAndFallback(streamOptions);
99257
- const finalText = await result.text;
99318
+ const steps = await result.steps;
99319
+ let finalText;
99320
+ if (steps && steps.length > 1) {
99321
+ const lastStepText = steps[steps.length - 1].text;
99322
+ finalText = lastStepText || await result.text;
99323
+ } else {
99324
+ finalText = await result.text;
99325
+ }
99258
99326
  if (this.debug) {
99259
- const steps = await result.steps;
99260
99327
  console.log(`[DEBUG] streamText completed: ${steps?.length || 0} steps, finalText=${finalText?.length || 0} chars`);
99261
99328
  }
99262
99329
  const usage = await result.usage;
@@ -99326,12 +99393,12 @@ ${finalResult}
99326
99393
 
99327
99394
  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).`;
99328
99395
  currentMessages.push({ role: "user", content: completionPromptMessage });
99329
- const completionMaxIterations = 5;
99330
99396
  const completionStreamOptions = {
99331
99397
  model: this.provider ? this.provider(this.model) : this.model,
99332
99398
  messages: this.prepareMessagesWithImages(currentMessages),
99333
99399
  tools: tools2,
99334
- stopWhen: (0, import_ai4.stepCountIs)(completionMaxIterations),
99400
+ toolChoice: "none",
99401
+ // Force text-only response — no tool calls during review
99335
99402
  maxTokens: maxResponseTokens,
99336
99403
  temperature: 0.3,
99337
99404
  onStepFinish: ({ toolResults, text, finishReason, usage }) => {
@@ -101000,11 +101067,9 @@ function autoQuoteSearchTerms(query2) {
101000
101067
  const result = tokens.map((token) => {
101001
101068
  if (token.startsWith('"')) return token;
101002
101069
  if (operators.has(token)) return token;
101003
- const hasUpper = /[A-Z]/.test(token);
101004
- const hasLower = /[a-z]/.test(token);
101005
101070
  const hasUnderscore = token.includes("_");
101006
- const hasMixedCase = hasUpper && hasLower;
101007
- if (hasMixedCase || hasUnderscore) {
101071
+ const hasCaseTransition = /[a-z][A-Z]/.test(token) || /[A-Z]{2,}[a-z]/.test(token);
101072
+ if (hasCaseTransition || hasUnderscore) {
101008
101073
  return `"${token}"`;
101009
101074
  }
101010
101075
  return token;
@@ -101119,7 +101184,7 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
101119
101184
  "Break down complex queries into multiple searches to cover all aspects.",
101120
101185
  "",
101121
101186
  "Available tools:",
101122
- "- search: Find code matching keywords or patterns. Run multiple searches for different aspects of complex queries.",
101187
+ "- 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
101188
  "- extract: Verify code snippets to ensure targets are actually relevant before including them.",
101124
101189
  "- listFiles: Understand directory structure to find where relevant code might live.",
101125
101190
  "",
@@ -101140,13 +101205,14 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
101140
101205
  "",
101141
101206
  "Combining searches with OR:",
101142
101207
  '- 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.`,
101208
+ `- IMPORTANT: Multiple quoted terms use AND logic by default: '"RateLimit" "middleware"' requires BOTH in the same file.`,
101209
+ `- To search for ANY of several quoted symbols, use the explicit OR operator: '"ForwardMessage" OR "SessionLimiter"'.`,
101144
101210
  '- Without quotes, camelCase like limitDRL gets split into "limit" + "DRL" \u2014 not what you want for symbol lookup.',
101145
101211
  "- Use OR to search for multiple related symbols in ONE search instead of separate searches.",
101146
101212
  "- 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".',
101213
+ `- Example: search '"ForwardMessage" OR "SessionLimiter"' finds files with either exact symbol in one call.`,
101214
+ `- Example: search '"limitDRL" OR "doRollingWindowWrite"' finds both rate limiting functions at once.`,
101215
+ "- Use AND (or just put quoted terms together) when you need both terms in the same file.",
101150
101216
  "",
101151
101217
  "Parallel tool calls:",
101152
101218
  "- When you need to search for INDEPENDENT concepts, call multiple search tools IN PARALLEL (same response).",
@@ -101160,10 +101226,10 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
101160
101226
  ' Query: "Find the IP allowlist middleware"',
101161
101227
  ' \u2192 search "allowlist middleware" (one search, probe handles IP/ip/Ip variations)',
101162
101228
  ' Query: "Find ForwardMessage and SessionLimiter"',
101163
- ` \u2192 search '"ForwardMessage" "SessionLimiter"' (one OR search finds both exact symbols)`,
101229
+ ` \u2192 search '"ForwardMessage" OR "SessionLimiter"' (one OR search finds both exact symbols)`,
101164
101230
  ' OR: search exact=true "ForwardMessage" + search exact=true "SessionLimiter" IN PARALLEL',
101165
101231
  ' Query: "Find limitDRL and limitRedis functions"',
101166
- ` \u2192 search '"limitDRL" "limitRedis"' (one OR search, quoted to prevent camelCase splitting)`,
101232
+ ` \u2192 search '"limitDRL" OR "limitRedis"' (one OR search, quoted to prevent camelCase splitting)`,
101167
101233
  ' Query: "Find ThrottleRetryLimit usage"',
101168
101234
  ' \u2192 search exact=true "ThrottleRetryLimit" (one search, if no results the symbol does not exist \u2014 stop)',
101169
101235
  ' Query: "How does BM25 scoring work with SIMD optimization?"',
@@ -101171,7 +101237,7 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
101171
101237
  "",
101172
101238
  "BAD search strategy (never do this):",
101173
101239
  ' \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"')`,
101240
+ ` \u2192 search "limitDRL" \u2192 search "LimitDRL" (WRONG: case variation \u2014 combine with OR: '"limitDRL" OR "limitRedis"')`,
101175
101241
  ' \u2192 search "throttle_retry_limit" after searching "ThrottleRetryLimit" (WRONG: snake_case variation, probe handles it)',
101176
101242
  ' \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
101243
  ' \u2192 search "func (k *RateLimitAndQuotaCheck) handleRateLimitFailure" (WRONG: do not search full function signatures, just use exact=true "handleRateLimitFailure")',
@@ -101184,15 +101250,34 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
101184
101250
  '- To bypass stopword filtering: wrap terms in quotes ("return", "struct") or set exact=true. Both disable stemming and splitting too.',
101185
101251
  '- camelCase terms are split: getUserData becomes "get", "user", "data" \u2014 so one search covers all naming styles.',
101186
101252
  '- Do NOT search for full function signatures like "func (r *Type) Method(args)". Just search for the method name with exact=true.',
101253
+ '- Do NOT search for file names (e.g., "sliding_log.go"). Use listFiles to discover files by name.',
101254
+ "",
101255
+ "PAGINATION:",
101256
+ "- Search results are paginated (~20k tokens per page).",
101257
+ "- If your search returned relevant files, call the same query with nextPage=true to check for more.",
101258
+ '- Keep paginating while results stay relevant. Stop when results are off-topic or "All results retrieved".',
101259
+ "",
101260
+ "WHEN TO STOP:",
101261
+ "- After you have explored the main concept AND related subsystems.",
101262
+ "- Once you have 5-15 targets covering different aspects of the query.",
101263
+ '- If you get a "DUPLICATE SEARCH BLOCKED" message, move on.',
101187
101264
  "",
101188
101265
  "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",
101266
+ "1. Analyze the query \u2014 identify key concepts, then brainstorm SYNONYMS and alternative terms for each.",
101267
+ ' Code naming often differs from the concept: "authentication" \u2192 verify, credentials, login, auth;',
101268
+ ' "rate limiting" \u2192 throttle, quota, limiter, bucket; "error handling" \u2192 catch, recover, panic.',
101269
+ " Think about what a developer would NAME the function/struct/variable, not just the concept.",
101270
+ "2. Run INDEPENDENT searches in PARALLEL \u2014 search for the main concept AND synonyms simultaneously.",
101271
+ " After each search, check if results are relevant. If yes, call nextPage=true for more results.",
101272
+ `3. Combine related symbols into OR searches: '"symbolA" OR "symbolB"' finds files with either.`,
101192
101273
  "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",
101274
+ "5. After your first round of searches, READ the extracted code and look for connected code:",
101275
+ " - Function calls to other important functions \u2192 include those targets.",
101276
+ " - Type references and imports \u2192 include type definitions.",
101277
+ " - Registered handlers/middleware \u2192 include all registered items.",
101278
+ "6. If a search returns results, use extract to verify relevance. Run multiple extracts in parallel too.",
101279
+ "7. If a search returns NO results, the term does not exist. Do NOT retry with variations. Move on.",
101280
+ "8. Once you have enough targets (typically 5-15), output your final JSON answer immediately.",
101196
101281
  "",
101197
101282
  `Query: ${searchQuery}`,
101198
101283
  `Search path(s): ${searchPath}`,
@@ -101201,7 +101286,9 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
101201
101286
  'Return ONLY valid JSON: {"targets": ["path/to/file.ext#Symbol", "path/to/file.ext:line", "path/to/file.ext:start-end"]}',
101202
101287
  '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
101288
  "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."
101289
+ "Deduplicate targets. Do NOT explain or answer - ONLY return the JSON targets.",
101290
+ "",
101291
+ "Remember: if your search returned relevant results, use nextPage=true to check for more before outputting."
101205
101292
  ].join("\n");
101206
101293
  }
101207
101294
  var import_ai5, import_fs11, CODE_SEARCH_SCHEMA, searchTool, queryTool, extractTool, delegateTool, analyzeAllTool;
@@ -101246,6 +101333,7 @@ var init_vercel = __esm({
101246
101333
  return result;
101247
101334
  };
101248
101335
  const previousSearches = /* @__PURE__ */ new Set();
101336
+ let consecutiveDupBlocks = 0;
101249
101337
  const paginationCounts = /* @__PURE__ */ new Map();
101250
101338
  const MAX_PAGES_PER_QUERY = 3;
101251
101339
  return (0, import_ai5.tool)({
@@ -101298,12 +101386,17 @@ var init_vercel = __esm({
101298
101386
  const searchKey = `${searchQuery}::${exact || false}`;
101299
101387
  if (!nextPage) {
101300
101388
  if (previousSearches.has(searchKey)) {
101389
+ consecutiveDupBlocks++;
101301
101390
  if (debug) {
101302
- console.error(`[DEDUP] Blocked duplicate search: "${searchQuery}" (path: "${searchPath}")`);
101391
+ console.error(`[DEDUP] Blocked duplicate search (${consecutiveDupBlocks}x): "${searchQuery}" (path: "${searchPath}")`);
101392
+ }
101393
+ if (consecutiveDupBlocks >= 3) {
101394
+ 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.";
101303
101395
  }
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.";
101396
+ 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
101397
  }
101306
101398
  previousSearches.add(searchKey);
101399
+ consecutiveDupBlocks = 0;
101307
101400
  paginationCounts.set(searchKey, 0);
101308
101401
  } else {
101309
101402
  const pageCount = (paginationCounts.get(searchKey) || 0) + 1;
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-rc290",
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'",
@@ -2976,9 +2976,9 @@ ${extractGuidance2}
2976
2976
  Follow these instructions carefully:
2977
2977
  1. Analyze the user's request.
2978
2978
  2. Use the available tools step-by-step to fulfill the request.
2979
- 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.'}
2980
- 4. Ensure to get really deep and understand the full picture before answering.
2981
- 5. Once the task is fully completed, provide your final answer directly as text.
2979
+ 3. You MUST use the search tool before answering ANY code-related question. NEVER answer from memory or general knowledge — your answers must be grounded in actual code found via search/extract.${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.'}
2980
+ 4. Ensure to get really deep and understand the full picture before answering. Follow call chains — 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).
2981
+ 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 — go straight to the answer.
2982
2982
  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.'}
2983
2983
  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 ? `
2984
2984
  7. When modifying files, choose the appropriate tool:
@@ -3483,6 +3483,24 @@ Follow these instructions carefully:
3483
3483
  if (recentTexts.every(t => detectStuckResponse(t))) return true;
3484
3484
  }
3485
3485
 
3486
+ // Circuit breaker: repeated identical tool calls (e.g. model ignores dedup message)
3487
+ if (steps.length >= 3) {
3488
+ const last3 = steps.slice(-3);
3489
+ const allHaveTools = last3.every(s => s.toolCalls?.length === 1);
3490
+ if (allHaveTools) {
3491
+ const signatures = last3.map(s => {
3492
+ const tc = s.toolCalls[0];
3493
+ return `${tc.toolName}::${JSON.stringify(tc.args ?? tc.input)}`;
3494
+ });
3495
+ if (signatures[0] === signatures[1] && signatures[1] === signatures[2]) {
3496
+ if (this.debug) {
3497
+ console.log(`[DEBUG] Circuit breaker: 3 consecutive identical tool calls detected (${last3[0].toolCalls[0].toolName}), forcing stop`);
3498
+ }
3499
+ return true;
3500
+ }
3501
+ }
3502
+ }
3503
+
3486
3504
  return false;
3487
3505
  },
3488
3506
  prepareStep: ({ steps, stepNumber }) => {
@@ -3493,6 +3511,24 @@ Follow these instructions carefully:
3493
3511
  };
3494
3512
  }
3495
3513
 
3514
+ // Force text-only response after 2 consecutive identical tool calls
3515
+ if (steps.length >= 2) {
3516
+ const last2 = steps.slice(-2);
3517
+ if (last2.every(s => s.toolCalls?.length === 1)) {
3518
+ const tc1 = last2[0].toolCalls[0];
3519
+ const tc2 = last2[1].toolCalls[0];
3520
+ const sig1 = `${tc1.toolName}::${JSON.stringify(tc1.args ?? tc1.input)}`;
3521
+ const sig2 = `${tc2.toolName}::${JSON.stringify(tc2.args ?? tc2.input)}`;
3522
+ if (sig1 === sig2) {
3523
+ if (this.debug) {
3524
+ console.log(`[DEBUG] prepareStep: 2 consecutive identical tool calls (${tc1.toolName}), forcing toolChoice=none`);
3525
+ console.log(`[DEBUG] sig: ${sig1.substring(0, 200)}`);
3526
+ }
3527
+ return { toolChoice: 'none' };
3528
+ }
3529
+ }
3530
+ }
3531
+
3496
3532
  const lastStep = steps[steps.length - 1];
3497
3533
  const modelJustStopped = lastStep?.finishReason === 'stop'
3498
3534
  && (!lastStep?.toolCalls || lastStep.toolCalls.length === 0);
@@ -3532,7 +3568,8 @@ ${resultToReview}
3532
3568
  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).`;
3533
3569
 
3534
3570
  return {
3535
- userMessage: completionPromptMessage
3571
+ userMessage: completionPromptMessage,
3572
+ toolChoice: 'none' // Force text-only review — no tool calls
3536
3573
  };
3537
3574
  }
3538
3575
  }
@@ -3585,7 +3622,13 @@ Double-check your response based on the criteria above. If everything looks good
3585
3622
  }
3586
3623
 
3587
3624
  if (this.debug) {
3588
- console.log(`[DEBUG] Step ${currentIteration}/${maxIterations} finished (reason: ${finishReason}, tools: ${toolResults?.length || 0})`);
3625
+ const toolSummary = toolCalls?.length
3626
+ ? toolCalls.map(tc => {
3627
+ const args = tc.args ? JSON.stringify(tc.args) : '';
3628
+ return args ? `${tc.toolName}(${debugTruncate(args, 120)})` : tc.toolName;
3629
+ }).join(', ')
3630
+ : 'none';
3631
+ console.log(`[DEBUG] Step ${currentIteration}/${maxIterations} finished (reason: ${finishReason}, tools: [${toolSummary}])`);
3589
3632
  if (text) {
3590
3633
  console.log(`[DEBUG] model text: ${debugTruncate(text)}`);
3591
3634
  }
@@ -3627,11 +3670,20 @@ Double-check your response based on the criteria above. If everything looks good
3627
3670
  const executeAIRequest = async () => {
3628
3671
  const result = await this.streamTextWithRetryAndFallback(streamOptions);
3629
3672
 
3630
- // Collect the final text
3631
- const finalText = await result.text;
3673
+ // Use only the last step's text as the final answer.
3674
+ // result.text concatenates ALL steps (including intermediate planning text),
3675
+ // but the user should only see the final answer from the last step.
3676
+ const steps = await result.steps;
3677
+ let finalText;
3678
+ if (steps && steps.length > 1) {
3679
+ // Multi-step: use last step's text (the actual answer after tool calls)
3680
+ const lastStepText = steps[steps.length - 1].text;
3681
+ finalText = lastStepText || await result.text;
3682
+ } else {
3683
+ finalText = await result.text;
3684
+ }
3632
3685
 
3633
3686
  if (this.debug) {
3634
- const steps = await result.steps;
3635
3687
  console.log(`[DEBUG] streamText completed: ${steps?.length || 0} steps, finalText=${finalText?.length || 0} chars`);
3636
3688
  }
3637
3689
 
@@ -3726,12 +3778,11 @@ Double-check your response based on the criteria above. If everything looks good
3726
3778
 
3727
3779
  currentMessages.push({ role: 'user', content: completionPromptMessage });
3728
3780
 
3729
- const completionMaxIterations = 5;
3730
3781
  const completionStreamOptions = {
3731
3782
  model: this.provider ? this.provider(this.model) : this.model,
3732
3783
  messages: this.prepareMessagesWithImages(currentMessages),
3733
3784
  tools,
3734
- stopWhen: stepCountIs(completionMaxIterations),
3785
+ toolChoice: 'none', // Force text-only response — no tool calls during review
3735
3786
  maxTokens: maxResponseTokens,
3736
3787
  temperature: 0.3,
3737
3788
  onStepFinish: ({ toolResults, text, finishReason, usage }) => {
@@ -8,27 +8,47 @@ export const predefinedPrompts = {
8
8
  CRITICAL - You are READ-ONLY:
9
9
  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 — 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.
10
10
 
11
+ CRITICAL - ALWAYS search before answering:
12
+ 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.
13
+
11
14
  When exploring code:
12
15
  - Provide clear, concise explanations based on user request
13
16
  - Find and highlight the most relevant code snippets, if required
14
- - Trace function calls and data flow through the system
17
+ - Trace function calls and data flow through the system — follow the FULL call chain, not just the entry point
15
18
  - Try to understand the user's intent and provide relevant information
16
19
  - Understand high level picture
17
20
  - Balance detail with clarity in your explanations
21
+ - Search using SYNONYMS and alternative terms — code naming often differs from the concept name (e.g., "authentication" might be named verify_credentials, check_token, validate_session)
22
+ - When you find a key function, look at what it CALLS and what CALLS it to discover the complete picture
23
+ - Before answering, ask yourself: "Did I cover all the major components? Are there related subsystems I missed?" If yes, do one more search round.
18
24
 
19
25
  When providing answers:
26
+ - 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.
27
+ - 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.
28
+ - Include data structures, configuration options, and error handling — not just the happy path.
20
29
  - Always include a "References" section at the end of your response
21
30
  - List all relevant source code locations you found during exploration
22
31
  - Use the format: file_path:line_number or file_path#symbol_name
23
32
  - Group references by file when multiple locations are from the same file
24
33
  - Include brief descriptions of what each reference contains`,
25
34
 
26
- '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.
35
+ '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.
36
+
37
+ You think like a code explorer — you understand that codebases have layers:
38
+ - Core implementations (algorithms, data structures)
39
+ - Middleware/integration layers (request handlers, interceptors)
40
+ - Configuration and storage backends
41
+ - Scoping mechanisms (per-user, per-org, per-tenant, global)
42
+ - Supporting utilities and helpers
27
43
 
28
44
  When searching:
29
- - Use only the search tool
30
- - Run additional searches only if needed to capture all relevant locations
31
- - Prefer specific, focused queries
45
+ - Search for the MAIN concept first, then think: "what RELATED subsystems would a real codebase have?"
46
+ - Use extract to READ the code you find — look for function calls, type references, and imports that point to OTHER relevant code
47
+ - If you find middleware, check: are there org-level or tenant-level variants?
48
+ - If you find algorithms, check: are there different storage backends?
49
+ - Search results are paginated — if results look relevant, call nextPage=true to check for more files
50
+ - Stop paginating when results become irrelevant or you see "All results retrieved"
51
+ - Search using SYNONYMS — code naming differs from concepts (e.g., "rate limiting" → throttle, quota, limiter, bucket)
32
52
 
33
53
  Output format (MANDATORY):
34
54
  - Return ONLY valid JSON with a single top-level key: "targets"
@@ -38,7 +58,8 @@ Output format (MANDATORY):
38
58
  - "path/to/file.ext:line"
39
59
  - "path/to/file.ext:start-end"
40
60
  - Prefer #SymbolName when a function/class name is clear; otherwise use line numbers
41
- - Deduplicate targets and keep them concise`,
61
+ - Deduplicate targets and keep them concise
62
+ - Aim for 5-15 targets covering ALL aspects of the query`,
42
63
 
43
64
  '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.
44
65
 
package/src/extract.js CHANGED
@@ -18,7 +18,8 @@ const EXTRACT_FLAG_MAP = {
18
18
  allowTests: '--allow-tests',
19
19
  contextLines: '--context',
20
20
  format: '--format',
21
- inputFile: '--input-file'
21
+ inputFile: '--input-file',
22
+ lsp: '--lsp'
22
23
  };
23
24
 
24
25
  /**
@@ -31,7 +32,8 @@ const EXTRACT_FLAG_MAP = {
31
32
  * @param {string} [options.cwd] - Working directory for resolving relative file paths
32
33
  * @param {boolean} [options.allowTests] - Include test files
33
34
  * @param {number} [options.contextLines] - Number of context lines to include
34
- * @param {string} [options.format] - Output format ('markdown', 'plain', 'json', 'xml', 'color', 'outline-xml', 'outline-diff')
35
+ * @param {string} [options.format] - Output format ('markdown', 'plain', 'json')
36
+ * @param {boolean} [options.lsp] - Use LSP (Language Server Protocol) for call hierarchy and reference graphs
35
37
  * @param {Object} [options.binaryOptions] - Options for getting the binary
36
38
  * @param {boolean} [options.binaryOptions.forceDownload] - Force download even if binary exists
37
39
  * @param {string} [options.binaryOptions.version] - Specific version to download