@probelabs/probe 0.6.0-rc266 → 0.6.0-rc268

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.
@@ -1836,9 +1836,13 @@ export class ProbeAgent {
1836
1836
  }
1837
1837
 
1838
1838
  // Add all enabled tools from toolImplementations
1839
+ // Note: MCP tools are also in toolImplementations but have no schema in _getToolSchemaAndDescription.
1840
+ // They are handled separately via mcpBridge.getVercelTools() below, so we skip them here.
1839
1841
  for (const [toolName, toolImpl] of Object.entries(this.toolImplementations)) {
1840
1842
  // Get schema and description for this tool
1841
- const { schema, description } = this._getToolSchemaAndDescription(toolName);
1843
+ const toolInfo = this._getToolSchemaAndDescription(toolName);
1844
+ if (!toolInfo) continue;
1845
+ const { schema, description } = toolInfo;
1842
1846
  if (schema && description) {
1843
1847
  nativeTools[toolName] = wrapTool(toolName, schema, description, toolImpl.execute);
1844
1848
  }
@@ -2944,11 +2948,11 @@ Follow these instructions carefully:
2944
2948
  6. Prefer concise and focused search queries. Use specific keywords and phrases to narrow down results.${this.allowEdit ? `
2945
2949
  7. When modifying files, choose the appropriate tool:
2946
2950
  - Use 'edit' for all code modifications:
2947
- * For small changes (a line or a few lines), use old_string + new_string copy old_string verbatim from the file.
2948
- * For rewriting entire functions/classes/methods, use the symbol parameter instead (no exact text matching needed).
2949
- * For editing specific lines from search/extract output, use start_line (and optionally end_line) with the line numbers shown in the output.${this.hashLines ? ' Line references include content hashes (e.g. "42:ab") for integrity verification.' : ''}
2951
+ * 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.
2950
2952
  * For editing inside large functions: first use extract with the symbol target (e.g. "file.js#myFunction") to see the function with line numbers${this.hashLines ? ' and hashes' : ''}, then use start_line/end_line to surgically edit specific lines within it.
2951
- * IMPORTANT: Keep old_string as small as possible — include only the lines you need to change plus minimal context for uniqueness. For replacing large blocks (10+ lines), prefer line-targeted editing with start_line/end_line to constrain scope.
2953
+ * For rewriting entire functions/classes/methods, use the symbol parameter instead (no exact text matching needed).
2954
+ * FALLBACK ONLY: Use old_string + new_string for simple single-line changes where the text is unique. Copy old_string verbatim from the file. Keep old_string as small as possible.
2955
+ * IMPORTANT: After multiple edits to the same file, re-read the changed areas before continuing — use extract with a targeted symbol (e.g. "file.js#myFunction") or a line range (e.g. "file.js:50-80") instead of re-reading the full file.
2952
2956
  - Use 'create' for new files or complete file rewrites.
2953
2957
  - If an edit fails, read the error message — it tells you exactly how to fix the call and retry.
2954
2958
  - The system tracks which files you've seen via search/extract. If you try to edit a file you haven't read, or one that changed since you last read it, the edit will fail with instructions to re-read first. Always use extract before editing to ensure you have current file content.` : ''}
@@ -12015,6 +12015,15 @@ Example: <extract><targets>${displayPath}</targets></extract>`;
12015
12015
  if (typeof old_string !== "string") {
12016
12016
  return `Error editing file: Invalid old_string - must be a string. Provide the exact text to find in the file, or use the symbol parameter instead for AST-aware editing by name.`;
12017
12017
  }
12018
+ if (options.fileTracker) {
12019
+ const staleCheck = options.fileTracker.checkTextEditStaleness(resolvedPath);
12020
+ if (!staleCheck.ok) {
12021
+ const displayPath = toRelativePath(resolvedPath, workspaceRoot);
12022
+ return `Error editing ${displayPath}: ${staleCheck.message}
12023
+
12024
+ Example: <extract><targets>${displayPath}</targets></extract>`;
12025
+ }
12026
+ }
12018
12027
  const content = await fs6.readFile(resolvedPath, "utf-8");
12019
12028
  let matchTarget = old_string;
12020
12029
  let matchStrategy = "exact";
@@ -12048,7 +12057,10 @@ Example: <extract><targets>${displayPath}</targets></extract>`;
12048
12057
  return `Error editing file: No changes made - the replacement result is identical to the original. Verify that old_string and new_string are actually different. If fuzzy matching was used, the matched text may already equal new_string.`;
12049
12058
  }
12050
12059
  await fs6.writeFile(resolvedPath, newContent, "utf-8");
12051
- if (options.fileTracker) await options.fileTracker.trackFileAfterWrite(resolvedPath);
12060
+ if (options.fileTracker) {
12061
+ await options.fileTracker.trackFileAfterWrite(resolvedPath);
12062
+ options.fileTracker.recordTextEdit(resolvedPath);
12063
+ }
12052
12064
  const replacedCount = replace_all ? occurrences : 1;
12053
12065
  if (debug) {
12054
12066
  console.error(`[Edit] Successfully edited ${resolvedPath}, replaced ${replacedCount} occurrence(s)`);
@@ -30126,6 +30138,8 @@ var init_fileTracker = __esm({
30126
30138
  this.debug = options.debug || false;
30127
30139
  this._seenFiles = /* @__PURE__ */ new Set();
30128
30140
  this._contentRecords = /* @__PURE__ */ new Map();
30141
+ this._textEditCounts = /* @__PURE__ */ new Map();
30142
+ this.maxConsecutiveTextEdits = 3;
30129
30143
  }
30130
30144
  /**
30131
30145
  * Mark a file as "seen" — the LLM has read its content.
@@ -30133,6 +30147,7 @@ var init_fileTracker = __esm({
30133
30147
  */
30134
30148
  markFileSeen(resolvedPath) {
30135
30149
  this._seenFiles.add(resolvedPath);
30150
+ this._textEditCounts.set(resolvedPath, 0);
30136
30151
  if (this.debug) {
30137
30152
  console.error(`[FileTracker] Marked as seen: ${resolvedPath}`);
30138
30153
  }
@@ -30278,9 +30293,37 @@ var init_fileTracker = __esm({
30278
30293
  * @param {string} resolvedPath - Absolute path to the file
30279
30294
  */
30280
30295
  async trackFileAfterWrite(resolvedPath) {
30281
- this.markFileSeen(resolvedPath);
30296
+ this._seenFiles.add(resolvedPath);
30282
30297
  this.invalidateFileRecords(resolvedPath);
30283
30298
  }
30299
+ /**
30300
+ * Record a text-mode edit (old_string/new_string) to a file.
30301
+ * Increments the consecutive edit counter.
30302
+ * @param {string} resolvedPath - Absolute path to the file
30303
+ */
30304
+ recordTextEdit(resolvedPath) {
30305
+ const count = (this._textEditCounts.get(resolvedPath) || 0) + 1;
30306
+ this._textEditCounts.set(resolvedPath, count);
30307
+ if (this.debug) {
30308
+ console.error(`[FileTracker] Text edit #${count} for ${resolvedPath}`);
30309
+ }
30310
+ }
30311
+ /**
30312
+ * Check if a file has had too many consecutive text edits without a re-read.
30313
+ * @param {string} resolvedPath - Absolute path to the file
30314
+ * @returns {{ok: boolean, editCount?: number, message?: string}}
30315
+ */
30316
+ checkTextEditStaleness(resolvedPath) {
30317
+ const count = this._textEditCounts.get(resolvedPath) || 0;
30318
+ if (count >= this.maxConsecutiveTextEdits) {
30319
+ return {
30320
+ ok: false,
30321
+ editCount: count,
30322
+ message: `This file has been edited ${count} times without being re-read. Use 'extract' to re-read the current file content before making more edits, to ensure you are working with the actual state of the file.`
30323
+ };
30324
+ }
30325
+ return { ok: true, editCount: count };
30326
+ }
30284
30327
  /**
30285
30328
  * Update the stored hash for a symbol after a successful write.
30286
30329
  * Enables chained edits to the same symbol.
@@ -30324,6 +30367,7 @@ var init_fileTracker = __esm({
30324
30367
  clear() {
30325
30368
  this._seenFiles.clear();
30326
30369
  this._contentRecords.clear();
30370
+ this._textEditCounts.clear();
30327
30371
  }
30328
30372
  };
30329
30373
  }
@@ -70658,6 +70702,9 @@ var init_prompts = __esm({
70658
70702
  predefinedPrompts = {
70659
70703
  "code-explorer": `You are ProbeChat Code Explorer, a specialized AI assistant focused on helping developers, product managers, and QAs understand and navigate codebases. Your primary function is to answer questions based on code, explain how systems work, and provide insights into code functionality using the provided code analysis tools.
70660
70704
 
70705
+ CRITICAL - You are READ-ONLY:
70706
+ 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.
70707
+
70661
70708
  When exploring code:
70662
70709
  - Provide clear, concise explanations based on user request
70663
70710
  - Find and highlight the most relevant code snippets, if required
@@ -83111,7 +83158,9 @@ var init_ProbeAgent = __esm({
83111
83158
  return nativeTools;
83112
83159
  }
83113
83160
  for (const [toolName, toolImpl] of Object.entries(this.toolImplementations)) {
83114
- const { schema, description } = this._getToolSchemaAndDescription(toolName);
83161
+ const toolInfo = this._getToolSchemaAndDescription(toolName);
83162
+ if (!toolInfo) continue;
83163
+ const { schema, description } = toolInfo;
83115
83164
  if (schema && description) {
83116
83165
  nativeTools[toolName] = wrapTool(toolName, schema, description, toolImpl.execute);
83117
83166
  }
@@ -84014,11 +84063,11 @@ Follow these instructions carefully:
84014
84063
  6. Prefer concise and focused search queries. Use specific keywords and phrases to narrow down results.${this.allowEdit ? `
84015
84064
  7. When modifying files, choose the appropriate tool:
84016
84065
  - Use 'edit' for all code modifications:
84017
- * For small changes (a line or a few lines), use old_string + new_string \u2014 copy old_string verbatim from the file.
84018
- * For rewriting entire functions/classes/methods, use the symbol parameter instead (no exact text matching needed).
84019
- * For editing specific lines from search/extract output, use start_line (and optionally end_line) with the line numbers shown in the output.${this.hashLines ? ' Line references include content hashes (e.g. "42:ab") for integrity verification.' : ""}
84066
+ * 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.
84020
84067
  * For editing inside large functions: first use extract with the symbol target (e.g. "file.js#myFunction") to see the function with line numbers${this.hashLines ? " and hashes" : ""}, then use start_line/end_line to surgically edit specific lines within it.
84021
- * IMPORTANT: Keep old_string as small as possible \u2014 include only the lines you need to change plus minimal context for uniqueness. For replacing large blocks (10+ lines), prefer line-targeted editing with start_line/end_line to constrain scope.
84068
+ * For rewriting entire functions/classes/methods, use the symbol parameter instead (no exact text matching needed).
84069
+ * FALLBACK ONLY: Use old_string + new_string for simple single-line changes where the text is unique. Copy old_string verbatim from the file. Keep old_string as small as possible.
84070
+ * IMPORTANT: After multiple edits to the same file, re-read the changed areas before continuing \u2014 use extract with a targeted symbol (e.g. "file.js#myFunction") or a line range (e.g. "file.js:50-80") instead of re-reading the full file.
84022
84071
  - Use 'create' for new files or complete file rewrites.
84023
84072
  - If an edit fails, read the error message \u2014 it tells you exactly how to fix the call and retry.
84024
84073
  - The system tracks which files you've seen via search/extract. If you try to edit a file you haven't read, or one that changed since you last read it, the edit will fail with instructions to re-read first. Always use extract before editing to ensure you have current file content.` : ""}
@@ -5,6 +5,9 @@
5
5
  export const predefinedPrompts = {
6
6
  'code-explorer': `You are ProbeChat Code Explorer, a specialized AI assistant focused on helping developers, product managers, and QAs understand and navigate codebases. Your primary function is to answer questions based on code, explain how systems work, and provide insights into code functionality using the provided code analysis tools.
7
7
 
8
+ CRITICAL - You are READ-ONLY:
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
+
8
11
  When exploring code:
9
12
  - Provide clear, concise explanations based on user request
10
13
  - Find and highlight the most relevant code snippets, if required
@@ -420,6 +420,15 @@ Parameters:
420
420
 
421
421
  // ─── Text-based edit mode ───
422
422
 
423
+ // Check if file has had too many consecutive text edits without a re-read
424
+ if (options.fileTracker) {
425
+ const staleCheck = options.fileTracker.checkTextEditStaleness(resolvedPath);
426
+ if (!staleCheck.ok) {
427
+ const displayPath = toRelativePath(resolvedPath, workspaceRoot);
428
+ return `Error editing ${displayPath}: ${staleCheck.message}\n\nExample: <extract><targets>${displayPath}</targets></extract>`;
429
+ }
430
+ }
431
+
423
432
  // Read the file
424
433
  const content = await fs.readFile(resolvedPath, 'utf-8');
425
434
 
@@ -470,7 +479,10 @@ Parameters:
470
479
 
471
480
  // Write the file back
472
481
  await fs.writeFile(resolvedPath, newContent, 'utf-8');
473
- if (options.fileTracker) await options.fileTracker.trackFileAfterWrite(resolvedPath);
482
+ if (options.fileTracker) {
483
+ await options.fileTracker.trackFileAfterWrite(resolvedPath);
484
+ options.fileTracker.recordTextEdit(resolvedPath);
485
+ }
474
486
 
475
487
  const replacedCount = replace_all ? occurrences : 1;
476
488
 
@@ -95,6 +95,10 @@ export class FileTracker {
95
95
  this._seenFiles = new Set();
96
96
  /** @type {Map<string, {contentHash: string, startLine: number, endLine: number, symbolName: string|null, source: string, timestamp: number}>} */
97
97
  this._contentRecords = new Map();
98
+ /** @type {Map<string, number>} Consecutive text edits since last read, per file */
99
+ this._textEditCounts = new Map();
100
+ /** @type {number} Max consecutive text edits before requiring a re-read */
101
+ this.maxConsecutiveTextEdits = 3;
98
102
  }
99
103
 
100
104
  /**
@@ -103,6 +107,7 @@ export class FileTracker {
103
107
  */
104
108
  markFileSeen(resolvedPath) {
105
109
  this._seenFiles.add(resolvedPath);
110
+ this._textEditCounts.set(resolvedPath, 0);
106
111
  if (this.debug) {
107
112
  console.error(`[FileTracker] Marked as seen: ${resolvedPath}`);
108
113
  }
@@ -264,10 +269,40 @@ export class FileTracker {
264
269
  * @param {string} resolvedPath - Absolute path to the file
265
270
  */
266
271
  async trackFileAfterWrite(resolvedPath) {
267
- this.markFileSeen(resolvedPath);
272
+ this._seenFiles.add(resolvedPath);
268
273
  this.invalidateFileRecords(resolvedPath);
269
274
  }
270
275
 
276
+ /**
277
+ * Record a text-mode edit (old_string/new_string) to a file.
278
+ * Increments the consecutive edit counter.
279
+ * @param {string} resolvedPath - Absolute path to the file
280
+ */
281
+ recordTextEdit(resolvedPath) {
282
+ const count = (this._textEditCounts.get(resolvedPath) || 0) + 1;
283
+ this._textEditCounts.set(resolvedPath, count);
284
+ if (this.debug) {
285
+ console.error(`[FileTracker] Text edit #${count} for ${resolvedPath}`);
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Check if a file has had too many consecutive text edits without a re-read.
291
+ * @param {string} resolvedPath - Absolute path to the file
292
+ * @returns {{ok: boolean, editCount?: number, message?: string}}
293
+ */
294
+ checkTextEditStaleness(resolvedPath) {
295
+ const count = this._textEditCounts.get(resolvedPath) || 0;
296
+ if (count >= this.maxConsecutiveTextEdits) {
297
+ return {
298
+ ok: false,
299
+ editCount: count,
300
+ message: `This file has been edited ${count} times without being re-read. Use 'extract' to re-read the current file content before making more edits, to ensure you are working with the actual state of the file.`
301
+ };
302
+ }
303
+ return { ok: true, editCount: count };
304
+ }
305
+
271
306
  /**
272
307
  * Update the stored hash for a symbol after a successful write.
273
308
  * Enables chained edits to the same symbol.
@@ -314,5 +349,6 @@ export class FileTracker {
314
349
  clear() {
315
350
  this._seenFiles.clear();
316
351
  this._contentRecords.clear();
352
+ this._textEditCounts.clear();
317
353
  }
318
354
  }