@probelabs/probe 0.6.0-rc263 → 0.6.0-rc265
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-rc265-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc265-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc265-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc265-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc265-x86_64-unknown-linux-musl.tar.gz +0 -0
- package/build/agent/ProbeAgent.js +1 -0
- package/build/agent/bashExecutor.js +233 -7
- package/build/agent/index.js +244 -67
- package/build/agent/shared/prompts.js +25 -2
- package/build/tools/edit.js +7 -0
- package/cjs/agent/ProbeAgent.cjs +410 -80
- package/cjs/index.cjs +410 -80
- package/package.json +2 -2
- package/src/agent/ProbeAgent.js +1 -0
- package/src/agent/bashExecutor.js +233 -7
- package/src/agent/shared/prompts.js +25 -2
- package/src/tools/edit.js +7 -0
- package/bin/binaries/probe-v0.6.0-rc263-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc263-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc263-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc263-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc263-x86_64-unknown-linux-musl.tar.gz +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@probelabs/probe",
|
|
3
|
-
"version": "0.6.0-
|
|
3
|
+
"version": "0.6.0-rc265",
|
|
4
4
|
"description": "Node.js wrapper for the probe code search tool",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"module": "src/index.js",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"@anthropic-ai/claude-agent-sdk": "^0.1.46",
|
|
81
81
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
82
82
|
"@nyariv/sandboxjs": "github:probelabs/SandboxJS",
|
|
83
|
-
"@probelabs/maid": "^0.0.
|
|
83
|
+
"@probelabs/maid": "^0.0.26",
|
|
84
84
|
"acorn": "^8.15.0",
|
|
85
85
|
"acorn-walk": "^8.3.4",
|
|
86
86
|
"adm-zip": "^0.5.16",
|
package/src/agent/ProbeAgent.js
CHANGED
|
@@ -2771,6 +2771,7 @@ Follow these instructions carefully:
|
|
|
2771
2771
|
* For rewriting entire functions/classes/methods, use the symbol parameter instead (no exact text matching needed).
|
|
2772
2772
|
* 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.' : ''}
|
|
2773
2773
|
* 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.
|
|
2774
|
+
* 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.
|
|
2774
2775
|
- Use 'create' for new files or complete file rewrites.
|
|
2775
2776
|
- If an edit fails, read the error message — it tells you exactly how to fix the call and retry.
|
|
2776
2777
|
- 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.` : ''}
|
|
@@ -8,6 +8,191 @@ import { resolve, join } from 'path';
|
|
|
8
8
|
import { existsSync } from 'fs';
|
|
9
9
|
import { parseCommandForExecution, isComplexCommand } from './bashCommandUtils.js';
|
|
10
10
|
|
|
11
|
+
// ─── Interactive Command Detection ─────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Split a command string by shell operators (&&, ||, |, ;) while respecting quotes.
|
|
15
|
+
* Used for interactive command detection in complex pipelines.
|
|
16
|
+
* @param {string} command - Command string to split
|
|
17
|
+
* @returns {string[]} Array of individual command strings
|
|
18
|
+
*/
|
|
19
|
+
function splitCommandComponents(command) {
|
|
20
|
+
const parts = [];
|
|
21
|
+
let current = '';
|
|
22
|
+
let inQuote = false;
|
|
23
|
+
let quoteChar = '';
|
|
24
|
+
|
|
25
|
+
for (let i = 0; i < command.length; i++) {
|
|
26
|
+
const c = command[i];
|
|
27
|
+
const next = command[i + 1] || '';
|
|
28
|
+
|
|
29
|
+
// Handle escape sequences
|
|
30
|
+
if (c === '\\' && !inQuote) {
|
|
31
|
+
current += c + next;
|
|
32
|
+
i++;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (inQuote && quoteChar === '"' && c === '\\' && next) {
|
|
36
|
+
current += c + next;
|
|
37
|
+
i++;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Track quotes
|
|
42
|
+
if (!inQuote && (c === '"' || c === "'")) {
|
|
43
|
+
inQuote = true;
|
|
44
|
+
quoteChar = c;
|
|
45
|
+
current += c;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (inQuote && c === quoteChar) {
|
|
49
|
+
inQuote = false;
|
|
50
|
+
current += c;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Split on operators outside quotes
|
|
55
|
+
if (!inQuote) {
|
|
56
|
+
if ((c === '&' && next === '&') || (c === '|' && next === '|')) {
|
|
57
|
+
if (current.trim()) parts.push(current.trim());
|
|
58
|
+
current = '';
|
|
59
|
+
i++;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (c === '|' || c === ';') {
|
|
63
|
+
if (current.trim()) parts.push(current.trim());
|
|
64
|
+
current = '';
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
current += c;
|
|
70
|
+
}
|
|
71
|
+
if (current.trim()) parts.push(current.trim());
|
|
72
|
+
return parts;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Check a single (non-compound) command for interactive behavior.
|
|
77
|
+
* Strips leading env-var assignments (e.g. GIT_EDITOR=true) before checking.
|
|
78
|
+
*
|
|
79
|
+
* @param {string} command - Single command string
|
|
80
|
+
* @returns {string|null} Error message with suggestion, or null if not interactive
|
|
81
|
+
*/
|
|
82
|
+
function checkSingleCommandInteractive(command) {
|
|
83
|
+
let effective = command.trim();
|
|
84
|
+
|
|
85
|
+
// Strip leading VAR=VALUE prefixes (e.g. "GIT_EDITOR=true git rebase --continue")
|
|
86
|
+
while (/^\w+=\S*\s/.test(effective)) {
|
|
87
|
+
effective = effective.replace(/^\w+=\S*\s+/, '');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const parts = effective.split(/\s+/);
|
|
91
|
+
const base = parts[0];
|
|
92
|
+
const args = parts.slice(1);
|
|
93
|
+
|
|
94
|
+
// ── Interactive editors ──
|
|
95
|
+
if (['vi', 'vim', 'nvim', 'nano', 'emacs', 'pico', 'joe', 'mcedit'].includes(base)) {
|
|
96
|
+
return `'${base}' is an interactive editor and cannot run without a terminal. Use non-interactive file manipulation commands instead.`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ── Interactive pagers ──
|
|
100
|
+
if (['less', 'more'].includes(base)) {
|
|
101
|
+
return `'${base}' is an interactive pager. Use 'cat', 'head', or 'tail' instead.`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── Git commands that open an editor ──
|
|
105
|
+
if (base === 'git') {
|
|
106
|
+
const sub = args[0];
|
|
107
|
+
|
|
108
|
+
// git commit without -m / --message / -C / -c / --fixup / --squash / --no-edit
|
|
109
|
+
if (sub === 'commit') {
|
|
110
|
+
const hasNonInteractiveFlag = args.some(a =>
|
|
111
|
+
a === '-m' || a.startsWith('--message') ||
|
|
112
|
+
a === '-C' || a === '-c' ||
|
|
113
|
+
a.startsWith('--fixup') || a.startsWith('--squash') ||
|
|
114
|
+
a === '--allow-empty-message' || a === '--no-edit'
|
|
115
|
+
);
|
|
116
|
+
if (!hasNonInteractiveFlag) {
|
|
117
|
+
return "Interactive command: 'git commit' opens an editor for the commit message. Use 'git commit -m \"your message\"' instead.";
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// git rebase --continue / --skip (opens editor for commit message)
|
|
122
|
+
if (sub === 'rebase' && (args.includes('--continue') || args.includes('--skip'))) {
|
|
123
|
+
return "Interactive command: 'git rebase --continue' opens an editor. Set environment variable GIT_EDITOR=true to accept default messages, e.g. pass env: {GIT_EDITOR: 'true'} or prepend GIT_EDITOR=true to the command.";
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// git rebase -i / --interactive
|
|
127
|
+
if (sub === 'rebase' && (args.includes('-i') || args.includes('--interactive'))) {
|
|
128
|
+
return "Interactive command: 'git rebase -i' requires an interactive editor. Interactive rebase cannot run without a terminal.";
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// git merge without --no-edit / --no-commit / --ff-only
|
|
132
|
+
if (sub === 'merge' && !args.includes('--no-edit') && !args.includes('--no-commit') && !args.includes('--ff-only')) {
|
|
133
|
+
return "Interactive command: 'git merge' may open an editor for the merge commit message. Add '--no-edit' to accept the default message.";
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// git cherry-pick without --no-edit
|
|
137
|
+
if (sub === 'cherry-pick' && !args.includes('--no-edit')) {
|
|
138
|
+
return "Interactive command: 'git cherry-pick' may open an editor. Add '--no-edit' to accept the default message.";
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// git revert without --no-edit
|
|
142
|
+
if (sub === 'revert' && !args.includes('--no-edit')) {
|
|
143
|
+
return "Interactive command: 'git revert' opens an editor. Add '--no-edit' to accept the default message.";
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// git tag -a without -m
|
|
147
|
+
if (sub === 'tag' && args.includes('-a') && !args.some(a => a === '-m' || a.startsWith('--message'))) {
|
|
148
|
+
return "Interactive command: 'git tag -a' opens an editor for the tag message. Use 'git tag -a <name> -m \"message\"' instead.";
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// git add -i / --interactive / -p / --patch
|
|
152
|
+
if (sub === 'add' && (args.includes('-i') || args.includes('--interactive') || args.includes('-p') || args.includes('--patch'))) {
|
|
153
|
+
return "Interactive command: 'git add -i/-p' requires interactive input. Use 'git add <files>' to stage specific files instead.";
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ── Interactive REPLs (no arguments = interactive mode) ──
|
|
158
|
+
if (['python', 'python3', 'node', 'irb', 'ghci', 'lua', 'R', 'ruby'].includes(base) && args.length === 0) {
|
|
159
|
+
return `Interactive command: '${base}' without arguments starts an interactive REPL. Provide a script file or use '-c'/'--eval' for inline code.`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ── Database clients without query flag ──
|
|
163
|
+
if (base === 'mysql' && !args.some(a => a === '-e' || a.startsWith('--execute'))) {
|
|
164
|
+
return "Interactive command: 'mysql' without -e flag starts an interactive session. Use 'mysql -e \"SQL QUERY\"' instead.";
|
|
165
|
+
}
|
|
166
|
+
if (base === 'psql' && !args.some(a => a === '-c' || a.startsWith('--command') || a === '-f' || a.startsWith('--file'))) {
|
|
167
|
+
return "Interactive command: 'psql' without -c flag starts an interactive session. Use 'psql -c \"SQL QUERY\"' instead.";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── Interactive TUI tools ──
|
|
171
|
+
if (['top', 'htop', 'btop', 'nmon'].includes(base)) {
|
|
172
|
+
return `Interactive command: '${base}' is an interactive TUI tool. Use 'ps aux' or 'top -b -n 1' for non-interactive process listing.`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Check if a command (simple or complex) would require interactive TTY input.
|
|
180
|
+
* For complex commands (with &&, ||, |, ;), checks each component individually.
|
|
181
|
+
*
|
|
182
|
+
* @param {string} command - Full command string
|
|
183
|
+
* @returns {string|null} Error message with suggestion for non-interactive alternative, or null if OK
|
|
184
|
+
*/
|
|
185
|
+
export function checkInteractiveCommand(command) {
|
|
186
|
+
if (!command || typeof command !== 'string') return null;
|
|
187
|
+
|
|
188
|
+
const components = splitCommandComponents(command.trim());
|
|
189
|
+
for (const component of components) {
|
|
190
|
+
const result = checkSingleCommandInteractive(component);
|
|
191
|
+
if (result) return result;
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
11
196
|
/**
|
|
12
197
|
* Execute a bash command with security controls
|
|
13
198
|
* @param {string} command - Command to execute
|
|
@@ -50,6 +235,26 @@ export async function executeBashCommand(command, options = {}) {
|
|
|
50
235
|
|
|
51
236
|
const startTime = Date.now();
|
|
52
237
|
|
|
238
|
+
// Check for interactive commands that would hang without a TTY
|
|
239
|
+
const interactiveError = checkInteractiveCommand(command);
|
|
240
|
+
if (interactiveError) {
|
|
241
|
+
if (debug) {
|
|
242
|
+
console.log(`[BashExecutor] Blocked interactive command: "${command}"`);
|
|
243
|
+
console.log(`[BashExecutor] Reason: ${interactiveError}`);
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
success: false,
|
|
247
|
+
error: interactiveError,
|
|
248
|
+
stdout: '',
|
|
249
|
+
stderr: interactiveError,
|
|
250
|
+
exitCode: 1,
|
|
251
|
+
command,
|
|
252
|
+
workingDirectory: cwd,
|
|
253
|
+
duration: 0,
|
|
254
|
+
interactive: true
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
53
258
|
if (debug) {
|
|
54
259
|
console.log(`[BashExecutor] Executing command: "${command}"`);
|
|
55
260
|
console.log(`[BashExecutor] Working directory: "${cwd}"`);
|
|
@@ -57,11 +262,16 @@ export async function executeBashCommand(command, options = {}) {
|
|
|
57
262
|
}
|
|
58
263
|
|
|
59
264
|
return new Promise((resolve, reject) => {
|
|
60
|
-
// Create environment
|
|
265
|
+
// Create environment with non-interactive safety defaults.
|
|
266
|
+
// These prevent commands from opening editors or TTY prompts
|
|
267
|
+
// when stdin is not available (which would cause hangs).
|
|
61
268
|
const processEnv = {
|
|
62
269
|
...process.env,
|
|
63
270
|
...env
|
|
64
271
|
};
|
|
272
|
+
// Only set defaults if not already provided by user config
|
|
273
|
+
if (!processEnv.GIT_EDITOR) processEnv.GIT_EDITOR = 'true';
|
|
274
|
+
if (!processEnv.GIT_TERMINAL_PROMPT) processEnv.GIT_TERMINAL_PROMPT = '0';
|
|
65
275
|
|
|
66
276
|
// Check if this is a complex command (contains pipes, operators, etc.)
|
|
67
277
|
const isComplex = isComplexCommand(command);
|
|
@@ -97,12 +307,17 @@ export async function executeBashCommand(command, options = {}) {
|
|
|
97
307
|
useShell = false;
|
|
98
308
|
}
|
|
99
309
|
|
|
100
|
-
// Spawn the process
|
|
310
|
+
// Spawn the process in a new session (detached: true → setsid on Linux).
|
|
311
|
+
// This detaches the child from the parent's controlling terminal, making
|
|
312
|
+
// /dev/tty unavailable. Any program that tries to open an interactive
|
|
313
|
+
// editor or TTY prompt (e.g. vim from git rebase) will get ENXIO and
|
|
314
|
+
// fail immediately instead of hanging forever.
|
|
101
315
|
const child = spawn(cmd, cmdArgs, {
|
|
102
316
|
cwd,
|
|
103
317
|
env: processEnv,
|
|
104
318
|
stdio: ['ignore', 'pipe', 'pipe'], // stdin ignored, capture stdout/stderr
|
|
105
319
|
shell: useShell, // false for security
|
|
320
|
+
detached: true, // new session — no controlling terminal
|
|
106
321
|
windowsHide: true
|
|
107
322
|
});
|
|
108
323
|
|
|
@@ -111,17 +326,28 @@ export async function executeBashCommand(command, options = {}) {
|
|
|
111
326
|
let killed = false;
|
|
112
327
|
let timeoutHandle;
|
|
113
328
|
|
|
329
|
+
// Helper: kill the entire process group (negative PID) so that
|
|
330
|
+
// sub-processes spawned by the command (e.g. an editor) are also killed.
|
|
331
|
+
// Falls back to killing just the child if process.kill fails.
|
|
332
|
+
const killProcessGroup = (signal) => {
|
|
333
|
+
try {
|
|
334
|
+
if (child.pid) process.kill(-child.pid, signal);
|
|
335
|
+
} catch {
|
|
336
|
+
try { child.kill(signal); } catch { /* already dead */ }
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
|
|
114
340
|
// Set timeout
|
|
115
341
|
if (timeout > 0) {
|
|
116
342
|
timeoutHandle = setTimeout(() => {
|
|
117
343
|
if (!killed) {
|
|
118
344
|
killed = true;
|
|
119
|
-
|
|
120
|
-
|
|
345
|
+
killProcessGroup('SIGTERM');
|
|
346
|
+
|
|
121
347
|
// Force kill after 5 seconds if still running
|
|
122
348
|
setTimeout(() => {
|
|
123
349
|
if (child.exitCode === null) {
|
|
124
|
-
|
|
350
|
+
killProcessGroup('SIGKILL');
|
|
125
351
|
}
|
|
126
352
|
}, 5000);
|
|
127
353
|
}
|
|
@@ -137,7 +363,7 @@ export async function executeBashCommand(command, options = {}) {
|
|
|
137
363
|
// Buffer overflow
|
|
138
364
|
if (!killed) {
|
|
139
365
|
killed = true;
|
|
140
|
-
|
|
366
|
+
killProcessGroup('SIGTERM');
|
|
141
367
|
}
|
|
142
368
|
}
|
|
143
369
|
});
|
|
@@ -151,7 +377,7 @@ export async function executeBashCommand(command, options = {}) {
|
|
|
151
377
|
// Buffer overflow
|
|
152
378
|
if (!killed) {
|
|
153
379
|
killed = true;
|
|
154
|
-
|
|
380
|
+
killProcessGroup('SIGTERM');
|
|
155
381
|
}
|
|
156
382
|
}
|
|
157
383
|
});
|
|
@@ -87,6 +87,7 @@ If the solution is clear, you can jump to implementation right away. If not, ask
|
|
|
87
87
|
- Avoid implementing special cases when a general approach works
|
|
88
88
|
- Never expose secrets, API keys, or credentials in generated code. Never log sensitive information.
|
|
89
89
|
- Do not surprise the user with unrequested changes. Do what was asked, including reasonable follow-up actions, but do not refactor surrounding code or add features that were not requested.
|
|
90
|
+
- When editing files, keep edits focused and minimal. For changes spanning more than a few lines, prefer line-targeted editing (start_line/end_line) over text replacement (old_string) — it constrains scope and prevents accidental removal of adjacent content. Never include unrelated sections in an edit operation.
|
|
90
91
|
- After every significant change, verify the project still builds and passes linting. Do not wait until the end to discover breakage.
|
|
91
92
|
|
|
92
93
|
# After Implementation
|
|
@@ -97,11 +98,33 @@ If the solution is clear, you can jump to implementation right away. If not, ask
|
|
|
97
98
|
|
|
98
99
|
# GitHub Integration
|
|
99
100
|
- Use the \`gh\` CLI for all GitHub operations: issues, pull requests, checks, releases.
|
|
100
|
-
- To create a pull request: commit your changes, push the branch, then use \`gh pr create --title "..." --body "..."\`.
|
|
101
101
|
- To view issues or PRs: \`gh issue view <number>\`, \`gh pr view <number>\`.
|
|
102
102
|
- If given a GitHub URL, use \`gh\` to fetch the relevant information rather than guessing.
|
|
103
103
|
- Always return the pull request URL to the user after creating one.
|
|
104
|
-
- When checking GitHub Actions, only read logs of failed jobs — do not waste time on successful ones. Use \`gh run view <run-id> --log-failed\` to fetch only the relevant output
|
|
104
|
+
- When checking GitHub Actions, only read logs of failed jobs — do not waste time on successful ones. Use \`gh run view <run-id> --log-failed\` to fetch only the relevant output.
|
|
105
|
+
|
|
106
|
+
# Pull Request Creation
|
|
107
|
+
- Commit your changes, push the branch, then use \`gh pr create --title "..." --body "..."\`.
|
|
108
|
+
- **PR title**: Keep it short (under 72 characters). Use imperative mood describing the change (e.g. "Add retry logic for API calls", "Fix race condition in cache invalidation"). Prefix with the type of change when useful: \`fix:\`, \`feat:\`, \`refactor:\`, \`docs:\`, \`test:\`, \`chore:\`.
|
|
109
|
+
- **PR body**: MUST follow this structure:
|
|
110
|
+
|
|
111
|
+
\`\`\`
|
|
112
|
+
## Problem / Task
|
|
113
|
+
<What problem is being solved or what task was requested. If there is a linked issue, reference it with #number. Be specific about the root cause or motivation.>
|
|
114
|
+
|
|
115
|
+
## Changes
|
|
116
|
+
<Concise list of what was actually changed. Describe each meaningful change — files modified, logic added/removed, and why. Do NOT just list filenames; explain what each change does.>
|
|
117
|
+
|
|
118
|
+
## Testing
|
|
119
|
+
<What tests were added, modified, or run. Include:
|
|
120
|
+
- New test names and what they verify
|
|
121
|
+
- Whether existing tests still pass
|
|
122
|
+
- Manual verification steps if applicable
|
|
123
|
+
- Commands used to validate (e.g. \`make test\`, \`npm test\`)>
|
|
124
|
+
\`\`\`
|
|
125
|
+
|
|
126
|
+
- If the task originated from a GitHub issue, always reference it in the PR body (e.g. "Fixes #123" or "Closes #123") so the issue is automatically closed on merge. If it originated from an external ticket system (Jira, Linear, etc.), include the ticket ID and link in the Problem / Task section (e.g. "Resolves PROJ-456").
|
|
127
|
+
- Do not leave the PR body empty or vague. Every PR must clearly communicate what was done and why so reviewers can understand the change without reading every line of diff.`,
|
|
105
128
|
|
|
106
129
|
'support': `You are ProbeChat Support, a specialized AI assistant focused on helping developers troubleshoot issues and solve problems. Your primary function is to help users diagnose errors, understand unexpected behaviors, and find solutions using the provided code analysis tools.
|
|
107
130
|
|
package/src/tools/edit.js
CHANGED
|
@@ -448,6 +448,13 @@ Parameters:
|
|
|
448
448
|
return `Error editing file: Multiple occurrences found - the old_string appears ${occurrences} times in ${file_path}. To fix: (1) Set replace_all=true to replace all occurrences, or (2) Include more surrounding lines in old_string to make the match unique (add the full line or adjacent lines for context).`;
|
|
449
449
|
}
|
|
450
450
|
|
|
451
|
+
// Guard against over-scoped text edits (replacing large blocks with tiny replacements)
|
|
452
|
+
const oldLines = matchTarget.split('\n').length;
|
|
453
|
+
const newLines = new_string.split('\n').length;
|
|
454
|
+
if (oldLines >= 20 && newLines < oldLines * 0.5) {
|
|
455
|
+
return `Error editing file: Edit scope too large — replacing ${oldLines} lines with ${newLines} lines risks accidental content deletion. To fix: (1) Use line-targeted editing (start_line/end_line) instead to constrain scope, or (2) Split into smaller, focused edits that each target only the lines you intend to change.`;
|
|
456
|
+
}
|
|
457
|
+
|
|
451
458
|
// Perform the replacement
|
|
452
459
|
let newContent;
|
|
453
460
|
if (replace_all) {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|