@probelabs/probe 0.6.0-rc267 → 0.6.0-rc269
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/binaries/{probe-v0.6.0-rc267-aarch64-apple-darwin.tar.gz → probe-v0.6.0-rc269-aarch64-apple-darwin.tar.gz} +0 -0
- package/bin/binaries/{probe-v0.6.0-rc267-aarch64-unknown-linux-musl.tar.gz → probe-v0.6.0-rc269-aarch64-unknown-linux-musl.tar.gz} +0 -0
- package/bin/binaries/probe-v0.6.0-rc269-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc269-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/{probe-v0.6.0-rc267-x86_64-unknown-linux-musl.tar.gz → probe-v0.6.0-rc269-x86_64-unknown-linux-musl.tar.gz} +0 -0
- package/build/agent/ProbeAgent.js +13 -5
- package/build/agent/index.js +60 -7
- package/build/agent/shared/prompts.js +3 -0
- package/build/tools/edit.js +13 -1
- package/build/tools/fileTracker.js +37 -1
- package/cjs/agent/ProbeAgent.cjs +60 -7
- package/cjs/index.cjs +60 -7
- package/package.json +1 -1
- package/src/agent/ProbeAgent.js +13 -5
- package/src/agent/shared/prompts.js +3 -0
- package/src/tools/edit.js +13 -1
- package/src/tools/fileTracker.js +37 -1
- package/bin/binaries/probe-v0.6.0-rc267-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc267-x86_64-pc-windows-msvc.zip +0 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1879,7 +1879,15 @@ export class ProbeAgent {
|
|
|
1879
1879
|
if (this.mcpBridge && !options._disableTools) {
|
|
1880
1880
|
const mcpTools = this.mcpBridge.getVercelTools(this._filterMcpTools(this.mcpBridge.getToolNames()));
|
|
1881
1881
|
for (const [name, mcpTool] of Object.entries(mcpTools)) {
|
|
1882
|
-
|
|
1882
|
+
// MCP tools have raw JSON Schema inputSchema that must be wrapped with jsonSchema()
|
|
1883
|
+
// for the Vercel AI SDK. Without wrapping, asSchema() misidentifies them as Zod schemas.
|
|
1884
|
+
const mcpSchema = mcpTool.inputSchema || mcpTool.parameters;
|
|
1885
|
+
const wrappedSchema = mcpSchema && mcpSchema._def ? mcpSchema : jsonSchema(mcpSchema || { type: 'object', properties: {} });
|
|
1886
|
+
nativeTools[name] = tool({
|
|
1887
|
+
description: mcpTool.description || `MCP tool: ${name}`,
|
|
1888
|
+
inputSchema: wrappedSchema,
|
|
1889
|
+
execute: mcpTool.execute,
|
|
1890
|
+
});
|
|
1883
1891
|
}
|
|
1884
1892
|
}
|
|
1885
1893
|
|
|
@@ -2948,11 +2956,11 @@ Follow these instructions carefully:
|
|
|
2948
2956
|
6. Prefer concise and focused search queries. Use specific keywords and phrases to narrow down results.${this.allowEdit ? `
|
|
2949
2957
|
7. When modifying files, choose the appropriate tool:
|
|
2950
2958
|
- Use 'edit' for all code modifications:
|
|
2951
|
-
*
|
|
2952
|
-
* For rewriting entire functions/classes/methods, use the symbol parameter instead (no exact text matching needed).
|
|
2953
|
-
* 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.' : ''}
|
|
2959
|
+
* 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.
|
|
2954
2960
|
* 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.
|
|
2955
|
-
*
|
|
2961
|
+
* For rewriting entire functions/classes/methods, use the symbol parameter instead (no exact text matching needed).
|
|
2962
|
+
* 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.
|
|
2963
|
+
* 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.
|
|
2956
2964
|
- Use 'create' for new files or complete file rewrites.
|
|
2957
2965
|
- If an edit fails, read the error message — it tells you exactly how to fix the call and retry.
|
|
2958
2966
|
- 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.` : ''}
|
package/build/agent/index.js
CHANGED
|
@@ -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)
|
|
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.
|
|
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
|
|
@@ -83143,7 +83190,13 @@ var init_ProbeAgent = __esm({
|
|
|
83143
83190
|
if (this.mcpBridge && !options._disableTools) {
|
|
83144
83191
|
const mcpTools = this.mcpBridge.getVercelTools(this._filterMcpTools(this.mcpBridge.getToolNames()));
|
|
83145
83192
|
for (const [name, mcpTool] of Object.entries(mcpTools)) {
|
|
83146
|
-
|
|
83193
|
+
const mcpSchema = mcpTool.inputSchema || mcpTool.parameters;
|
|
83194
|
+
const wrappedSchema = mcpSchema && mcpSchema._def ? mcpSchema : jsonSchema(mcpSchema || { type: "object", properties: {} });
|
|
83195
|
+
nativeTools[name] = tool5({
|
|
83196
|
+
description: mcpTool.description || `MCP tool: ${name}`,
|
|
83197
|
+
inputSchema: wrappedSchema,
|
|
83198
|
+
execute: mcpTool.execute
|
|
83199
|
+
});
|
|
83147
83200
|
}
|
|
83148
83201
|
}
|
|
83149
83202
|
if (this.apiType === "google" && this._geminiToolsEnabled && !options._disableTools) {
|
|
@@ -84016,11 +84069,11 @@ Follow these instructions carefully:
|
|
|
84016
84069
|
6. Prefer concise and focused search queries. Use specific keywords and phrases to narrow down results.${this.allowEdit ? `
|
|
84017
84070
|
7. When modifying files, choose the appropriate tool:
|
|
84018
84071
|
- Use 'edit' for all code modifications:
|
|
84019
|
-
*
|
|
84020
|
-
* For rewriting entire functions/classes/methods, use the symbol parameter instead (no exact text matching needed).
|
|
84021
|
-
* 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.' : ""}
|
|
84072
|
+
* 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.
|
|
84022
84073
|
* 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.
|
|
84023
|
-
*
|
|
84074
|
+
* For rewriting entire functions/classes/methods, use the symbol parameter instead (no exact text matching needed).
|
|
84075
|
+
* 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.
|
|
84076
|
+
* 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.
|
|
84024
84077
|
- Use 'create' for new files or complete file rewrites.
|
|
84025
84078
|
- If an edit fails, read the error message \u2014 it tells you exactly how to fix the call and retry.
|
|
84026
84079
|
- 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
|
package/build/tools/edit.js
CHANGED
|
@@ -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)
|
|
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.
|
|
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
|
}
|
package/cjs/agent/ProbeAgent.cjs
CHANGED
|
@@ -39398,6 +39398,15 @@ Example: <extract><targets>${displayPath}</targets></extract>`;
|
|
|
39398
39398
|
if (typeof old_string !== "string") {
|
|
39399
39399
|
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.`;
|
|
39400
39400
|
}
|
|
39401
|
+
if (options.fileTracker) {
|
|
39402
|
+
const staleCheck = options.fileTracker.checkTextEditStaleness(resolvedPath2);
|
|
39403
|
+
if (!staleCheck.ok) {
|
|
39404
|
+
const displayPath = toRelativePath(resolvedPath2, workspaceRoot);
|
|
39405
|
+
return `Error editing ${displayPath}: ${staleCheck.message}
|
|
39406
|
+
|
|
39407
|
+
Example: <extract><targets>${displayPath}</targets></extract>`;
|
|
39408
|
+
}
|
|
39409
|
+
}
|
|
39401
39410
|
const content = await import_fs5.promises.readFile(resolvedPath2, "utf-8");
|
|
39402
39411
|
let matchTarget = old_string;
|
|
39403
39412
|
let matchStrategy = "exact";
|
|
@@ -39431,7 +39440,10 @@ Example: <extract><targets>${displayPath}</targets></extract>`;
|
|
|
39431
39440
|
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.`;
|
|
39432
39441
|
}
|
|
39433
39442
|
await import_fs5.promises.writeFile(resolvedPath2, newContent, "utf-8");
|
|
39434
|
-
if (options.fileTracker)
|
|
39443
|
+
if (options.fileTracker) {
|
|
39444
|
+
await options.fileTracker.trackFileAfterWrite(resolvedPath2);
|
|
39445
|
+
options.fileTracker.recordTextEdit(resolvedPath2);
|
|
39446
|
+
}
|
|
39435
39447
|
const replacedCount = replace_all ? occurrences : 1;
|
|
39436
39448
|
if (debug) {
|
|
39437
39449
|
console.error(`[Edit] Successfully edited ${resolvedPath2}, replaced ${replacedCount} occurrence(s)`);
|
|
@@ -57509,6 +57521,8 @@ var init_fileTracker = __esm({
|
|
|
57509
57521
|
this.debug = options.debug || false;
|
|
57510
57522
|
this._seenFiles = /* @__PURE__ */ new Set();
|
|
57511
57523
|
this._contentRecords = /* @__PURE__ */ new Map();
|
|
57524
|
+
this._textEditCounts = /* @__PURE__ */ new Map();
|
|
57525
|
+
this.maxConsecutiveTextEdits = 3;
|
|
57512
57526
|
}
|
|
57513
57527
|
/**
|
|
57514
57528
|
* Mark a file as "seen" — the LLM has read its content.
|
|
@@ -57516,6 +57530,7 @@ var init_fileTracker = __esm({
|
|
|
57516
57530
|
*/
|
|
57517
57531
|
markFileSeen(resolvedPath2) {
|
|
57518
57532
|
this._seenFiles.add(resolvedPath2);
|
|
57533
|
+
this._textEditCounts.set(resolvedPath2, 0);
|
|
57519
57534
|
if (this.debug) {
|
|
57520
57535
|
console.error(`[FileTracker] Marked as seen: ${resolvedPath2}`);
|
|
57521
57536
|
}
|
|
@@ -57661,9 +57676,37 @@ var init_fileTracker = __esm({
|
|
|
57661
57676
|
* @param {string} resolvedPath - Absolute path to the file
|
|
57662
57677
|
*/
|
|
57663
57678
|
async trackFileAfterWrite(resolvedPath2) {
|
|
57664
|
-
this.
|
|
57679
|
+
this._seenFiles.add(resolvedPath2);
|
|
57665
57680
|
this.invalidateFileRecords(resolvedPath2);
|
|
57666
57681
|
}
|
|
57682
|
+
/**
|
|
57683
|
+
* Record a text-mode edit (old_string/new_string) to a file.
|
|
57684
|
+
* Increments the consecutive edit counter.
|
|
57685
|
+
* @param {string} resolvedPath - Absolute path to the file
|
|
57686
|
+
*/
|
|
57687
|
+
recordTextEdit(resolvedPath2) {
|
|
57688
|
+
const count = (this._textEditCounts.get(resolvedPath2) || 0) + 1;
|
|
57689
|
+
this._textEditCounts.set(resolvedPath2, count);
|
|
57690
|
+
if (this.debug) {
|
|
57691
|
+
console.error(`[FileTracker] Text edit #${count} for ${resolvedPath2}`);
|
|
57692
|
+
}
|
|
57693
|
+
}
|
|
57694
|
+
/**
|
|
57695
|
+
* Check if a file has had too many consecutive text edits without a re-read.
|
|
57696
|
+
* @param {string} resolvedPath - Absolute path to the file
|
|
57697
|
+
* @returns {{ok: boolean, editCount?: number, message?: string}}
|
|
57698
|
+
*/
|
|
57699
|
+
checkTextEditStaleness(resolvedPath2) {
|
|
57700
|
+
const count = this._textEditCounts.get(resolvedPath2) || 0;
|
|
57701
|
+
if (count >= this.maxConsecutiveTextEdits) {
|
|
57702
|
+
return {
|
|
57703
|
+
ok: false,
|
|
57704
|
+
editCount: count,
|
|
57705
|
+
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.`
|
|
57706
|
+
};
|
|
57707
|
+
}
|
|
57708
|
+
return { ok: true, editCount: count };
|
|
57709
|
+
}
|
|
57667
57710
|
/**
|
|
57668
57711
|
* Update the stored hash for a symbol after a successful write.
|
|
57669
57712
|
* Enables chained edits to the same symbol.
|
|
@@ -57707,6 +57750,7 @@ var init_fileTracker = __esm({
|
|
|
57707
57750
|
clear() {
|
|
57708
57751
|
this._seenFiles.clear();
|
|
57709
57752
|
this._contentRecords.clear();
|
|
57753
|
+
this._textEditCounts.clear();
|
|
57710
57754
|
}
|
|
57711
57755
|
};
|
|
57712
57756
|
}
|
|
@@ -97607,6 +97651,9 @@ var init_prompts = __esm({
|
|
|
97607
97651
|
predefinedPrompts = {
|
|
97608
97652
|
"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.
|
|
97609
97653
|
|
|
97654
|
+
CRITICAL - You are READ-ONLY:
|
|
97655
|
+
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.
|
|
97656
|
+
|
|
97610
97657
|
When exploring code:
|
|
97611
97658
|
- Provide clear, concise explanations based on user request
|
|
97612
97659
|
- Find and highlight the most relevant code snippets, if required
|
|
@@ -110091,7 +110138,13 @@ var init_ProbeAgent = __esm({
|
|
|
110091
110138
|
if (this.mcpBridge && !options._disableTools) {
|
|
110092
110139
|
const mcpTools = this.mcpBridge.getVercelTools(this._filterMcpTools(this.mcpBridge.getToolNames()));
|
|
110093
110140
|
for (const [name14, mcpTool] of Object.entries(mcpTools)) {
|
|
110094
|
-
|
|
110141
|
+
const mcpSchema = mcpTool.inputSchema || mcpTool.parameters;
|
|
110142
|
+
const wrappedSchema = mcpSchema && mcpSchema._def ? mcpSchema : (0, import_ai6.jsonSchema)(mcpSchema || { type: "object", properties: {} });
|
|
110143
|
+
nativeTools[name14] = (0, import_ai6.tool)({
|
|
110144
|
+
description: mcpTool.description || `MCP tool: ${name14}`,
|
|
110145
|
+
inputSchema: wrappedSchema,
|
|
110146
|
+
execute: mcpTool.execute
|
|
110147
|
+
});
|
|
110095
110148
|
}
|
|
110096
110149
|
}
|
|
110097
110150
|
if (this.apiType === "google" && this._geminiToolsEnabled && !options._disableTools) {
|
|
@@ -110964,11 +111017,11 @@ Follow these instructions carefully:
|
|
|
110964
111017
|
6. Prefer concise and focused search queries. Use specific keywords and phrases to narrow down results.${this.allowEdit ? `
|
|
110965
111018
|
7. When modifying files, choose the appropriate tool:
|
|
110966
111019
|
- Use 'edit' for all code modifications:
|
|
110967
|
-
*
|
|
110968
|
-
* For rewriting entire functions/classes/methods, use the symbol parameter instead (no exact text matching needed).
|
|
110969
|
-
* 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.' : ""}
|
|
111020
|
+
* 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.
|
|
110970
111021
|
* 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.
|
|
110971
|
-
*
|
|
111022
|
+
* For rewriting entire functions/classes/methods, use the symbol parameter instead (no exact text matching needed).
|
|
111023
|
+
* 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.
|
|
111024
|
+
* 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.
|
|
110972
111025
|
- Use 'create' for new files or complete file rewrites.
|
|
110973
111026
|
- If an edit fails, read the error message \u2014 it tells you exactly how to fix the call and retry.
|
|
110974
111027
|
- 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.` : ""}
|
package/cjs/index.cjs
CHANGED
|
@@ -36636,6 +36636,8 @@ var init_fileTracker = __esm({
|
|
|
36636
36636
|
this.debug = options.debug || false;
|
|
36637
36637
|
this._seenFiles = /* @__PURE__ */ new Set();
|
|
36638
36638
|
this._contentRecords = /* @__PURE__ */ new Map();
|
|
36639
|
+
this._textEditCounts = /* @__PURE__ */ new Map();
|
|
36640
|
+
this.maxConsecutiveTextEdits = 3;
|
|
36639
36641
|
}
|
|
36640
36642
|
/**
|
|
36641
36643
|
* Mark a file as "seen" — the LLM has read its content.
|
|
@@ -36643,6 +36645,7 @@ var init_fileTracker = __esm({
|
|
|
36643
36645
|
*/
|
|
36644
36646
|
markFileSeen(resolvedPath2) {
|
|
36645
36647
|
this._seenFiles.add(resolvedPath2);
|
|
36648
|
+
this._textEditCounts.set(resolvedPath2, 0);
|
|
36646
36649
|
if (this.debug) {
|
|
36647
36650
|
console.error(`[FileTracker] Marked as seen: ${resolvedPath2}`);
|
|
36648
36651
|
}
|
|
@@ -36788,9 +36791,37 @@ var init_fileTracker = __esm({
|
|
|
36788
36791
|
* @param {string} resolvedPath - Absolute path to the file
|
|
36789
36792
|
*/
|
|
36790
36793
|
async trackFileAfterWrite(resolvedPath2) {
|
|
36791
|
-
this.
|
|
36794
|
+
this._seenFiles.add(resolvedPath2);
|
|
36792
36795
|
this.invalidateFileRecords(resolvedPath2);
|
|
36793
36796
|
}
|
|
36797
|
+
/**
|
|
36798
|
+
* Record a text-mode edit (old_string/new_string) to a file.
|
|
36799
|
+
* Increments the consecutive edit counter.
|
|
36800
|
+
* @param {string} resolvedPath - Absolute path to the file
|
|
36801
|
+
*/
|
|
36802
|
+
recordTextEdit(resolvedPath2) {
|
|
36803
|
+
const count = (this._textEditCounts.get(resolvedPath2) || 0) + 1;
|
|
36804
|
+
this._textEditCounts.set(resolvedPath2, count);
|
|
36805
|
+
if (this.debug) {
|
|
36806
|
+
console.error(`[FileTracker] Text edit #${count} for ${resolvedPath2}`);
|
|
36807
|
+
}
|
|
36808
|
+
}
|
|
36809
|
+
/**
|
|
36810
|
+
* Check if a file has had too many consecutive text edits without a re-read.
|
|
36811
|
+
* @param {string} resolvedPath - Absolute path to the file
|
|
36812
|
+
* @returns {{ok: boolean, editCount?: number, message?: string}}
|
|
36813
|
+
*/
|
|
36814
|
+
checkTextEditStaleness(resolvedPath2) {
|
|
36815
|
+
const count = this._textEditCounts.get(resolvedPath2) || 0;
|
|
36816
|
+
if (count >= this.maxConsecutiveTextEdits) {
|
|
36817
|
+
return {
|
|
36818
|
+
ok: false,
|
|
36819
|
+
editCount: count,
|
|
36820
|
+
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.`
|
|
36821
|
+
};
|
|
36822
|
+
}
|
|
36823
|
+
return { ok: true, editCount: count };
|
|
36824
|
+
}
|
|
36794
36825
|
/**
|
|
36795
36826
|
* Update the stored hash for a symbol after a successful write.
|
|
36796
36827
|
* Enables chained edits to the same symbol.
|
|
@@ -36834,6 +36865,7 @@ var init_fileTracker = __esm({
|
|
|
36834
36865
|
clear() {
|
|
36835
36866
|
this._seenFiles.clear();
|
|
36836
36867
|
this._contentRecords.clear();
|
|
36868
|
+
this._textEditCounts.clear();
|
|
36837
36869
|
}
|
|
36838
36870
|
};
|
|
36839
36871
|
}
|
|
@@ -82634,6 +82666,9 @@ var init_prompts = __esm({
|
|
|
82634
82666
|
predefinedPrompts = {
|
|
82635
82667
|
"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.
|
|
82636
82668
|
|
|
82669
|
+
CRITICAL - You are READ-ONLY:
|
|
82670
|
+
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.
|
|
82671
|
+
|
|
82637
82672
|
When exploring code:
|
|
82638
82673
|
- Provide clear, concise explanations based on user request
|
|
82639
82674
|
- Find and highlight the most relevant code snippets, if required
|
|
@@ -107398,7 +107433,13 @@ var init_ProbeAgent = __esm({
|
|
|
107398
107433
|
if (this.mcpBridge && !options._disableTools) {
|
|
107399
107434
|
const mcpTools = this.mcpBridge.getVercelTools(this._filterMcpTools(this.mcpBridge.getToolNames()));
|
|
107400
107435
|
for (const [name14, mcpTool] of Object.entries(mcpTools)) {
|
|
107401
|
-
|
|
107436
|
+
const mcpSchema = mcpTool.inputSchema || mcpTool.parameters;
|
|
107437
|
+
const wrappedSchema = mcpSchema && mcpSchema._def ? mcpSchema : (0, import_ai4.jsonSchema)(mcpSchema || { type: "object", properties: {} });
|
|
107438
|
+
nativeTools[name14] = (0, import_ai4.tool)({
|
|
107439
|
+
description: mcpTool.description || `MCP tool: ${name14}`,
|
|
107440
|
+
inputSchema: wrappedSchema,
|
|
107441
|
+
execute: mcpTool.execute
|
|
107442
|
+
});
|
|
107402
107443
|
}
|
|
107403
107444
|
}
|
|
107404
107445
|
if (this.apiType === "google" && this._geminiToolsEnabled && !options._disableTools) {
|
|
@@ -108271,11 +108312,11 @@ Follow these instructions carefully:
|
|
|
108271
108312
|
6. Prefer concise and focused search queries. Use specific keywords and phrases to narrow down results.${this.allowEdit ? `
|
|
108272
108313
|
7. When modifying files, choose the appropriate tool:
|
|
108273
108314
|
- Use 'edit' for all code modifications:
|
|
108274
|
-
*
|
|
108275
|
-
* For rewriting entire functions/classes/methods, use the symbol parameter instead (no exact text matching needed).
|
|
108276
|
-
* 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.' : ""}
|
|
108315
|
+
* 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.
|
|
108277
108316
|
* 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.
|
|
108278
|
-
*
|
|
108317
|
+
* For rewriting entire functions/classes/methods, use the symbol parameter instead (no exact text matching needed).
|
|
108318
|
+
* 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.
|
|
108319
|
+
* 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.
|
|
108279
108320
|
- Use 'create' for new files or complete file rewrites.
|
|
108280
108321
|
- If an edit fails, read the error message \u2014 it tells you exactly how to fix the call and retry.
|
|
108281
108322
|
- 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.` : ""}
|
|
@@ -111517,6 +111558,15 @@ Example: <extract><targets>${displayPath}</targets></extract>`;
|
|
|
111517
111558
|
if (typeof old_string !== "string") {
|
|
111518
111559
|
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.`;
|
|
111519
111560
|
}
|
|
111561
|
+
if (options.fileTracker) {
|
|
111562
|
+
const staleCheck = options.fileTracker.checkTextEditStaleness(resolvedPath2);
|
|
111563
|
+
if (!staleCheck.ok) {
|
|
111564
|
+
const displayPath = toRelativePath(resolvedPath2, workspaceRoot);
|
|
111565
|
+
return `Error editing ${displayPath}: ${staleCheck.message}
|
|
111566
|
+
|
|
111567
|
+
Example: <extract><targets>${displayPath}</targets></extract>`;
|
|
111568
|
+
}
|
|
111569
|
+
}
|
|
111520
111570
|
const content = await import_fs11.promises.readFile(resolvedPath2, "utf-8");
|
|
111521
111571
|
let matchTarget = old_string;
|
|
111522
111572
|
let matchStrategy = "exact";
|
|
@@ -111550,7 +111600,10 @@ Example: <extract><targets>${displayPath}</targets></extract>`;
|
|
|
111550
111600
|
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.`;
|
|
111551
111601
|
}
|
|
111552
111602
|
await import_fs11.promises.writeFile(resolvedPath2, newContent, "utf-8");
|
|
111553
|
-
if (options.fileTracker)
|
|
111603
|
+
if (options.fileTracker) {
|
|
111604
|
+
await options.fileTracker.trackFileAfterWrite(resolvedPath2);
|
|
111605
|
+
options.fileTracker.recordTextEdit(resolvedPath2);
|
|
111606
|
+
}
|
|
111554
111607
|
const replacedCount = replace_all ? occurrences : 1;
|
|
111555
111608
|
if (debug) {
|
|
111556
111609
|
console.error(`[Edit] Successfully edited ${resolvedPath2}, replaced ${replacedCount} occurrence(s)`);
|
package/package.json
CHANGED
package/src/agent/ProbeAgent.js
CHANGED
|
@@ -1879,7 +1879,15 @@ export class ProbeAgent {
|
|
|
1879
1879
|
if (this.mcpBridge && !options._disableTools) {
|
|
1880
1880
|
const mcpTools = this.mcpBridge.getVercelTools(this._filterMcpTools(this.mcpBridge.getToolNames()));
|
|
1881
1881
|
for (const [name, mcpTool] of Object.entries(mcpTools)) {
|
|
1882
|
-
|
|
1882
|
+
// MCP tools have raw JSON Schema inputSchema that must be wrapped with jsonSchema()
|
|
1883
|
+
// for the Vercel AI SDK. Without wrapping, asSchema() misidentifies them as Zod schemas.
|
|
1884
|
+
const mcpSchema = mcpTool.inputSchema || mcpTool.parameters;
|
|
1885
|
+
const wrappedSchema = mcpSchema && mcpSchema._def ? mcpSchema : jsonSchema(mcpSchema || { type: 'object', properties: {} });
|
|
1886
|
+
nativeTools[name] = tool({
|
|
1887
|
+
description: mcpTool.description || `MCP tool: ${name}`,
|
|
1888
|
+
inputSchema: wrappedSchema,
|
|
1889
|
+
execute: mcpTool.execute,
|
|
1890
|
+
});
|
|
1883
1891
|
}
|
|
1884
1892
|
}
|
|
1885
1893
|
|
|
@@ -2948,11 +2956,11 @@ Follow these instructions carefully:
|
|
|
2948
2956
|
6. Prefer concise and focused search queries. Use specific keywords and phrases to narrow down results.${this.allowEdit ? `
|
|
2949
2957
|
7. When modifying files, choose the appropriate tool:
|
|
2950
2958
|
- Use 'edit' for all code modifications:
|
|
2951
|
-
*
|
|
2952
|
-
* For rewriting entire functions/classes/methods, use the symbol parameter instead (no exact text matching needed).
|
|
2953
|
-
* 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.' : ''}
|
|
2959
|
+
* 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.
|
|
2954
2960
|
* 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.
|
|
2955
|
-
*
|
|
2961
|
+
* For rewriting entire functions/classes/methods, use the symbol parameter instead (no exact text matching needed).
|
|
2962
|
+
* 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.
|
|
2963
|
+
* 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.
|
|
2956
2964
|
- Use 'create' for new files or complete file rewrites.
|
|
2957
2965
|
- If an edit fails, read the error message — it tells you exactly how to fix the call and retry.
|
|
2958
2966
|
- 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
|
package/src/tools/edit.js
CHANGED
|
@@ -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)
|
|
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
|
|
package/src/tools/fileTracker.js
CHANGED
|
@@ -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.
|
|
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
|
}
|
|
Binary file
|
|
Binary file
|