mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-08 22:21:45 +00:00
Include file contents in apply_patch error responses
When apply_patch fails because a context anchor line doesn't match, the model had no visibility into what the file actually contained and would retry blindly. Now the error includes numbered file contents (truncated to 9K chars via HeadTail mode) so the model can self-correct in one try. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3e1185d19b
commit
225a383690
2 changed files with 67 additions and 3 deletions
|
|
@ -119,8 +119,8 @@ If completing the task requires writing or modifying files:
|
|||
and focused on the task.
|
||||
- Use `git log` and `git blame` to search the history of the codebase if additional context is needed.
|
||||
- NEVER add copyright or license headers unless specifically requested.
|
||||
- Do not waste tokens re-reading files after calling apply_patch on them. The tool call will \
|
||||
fail if it did not work.
|
||||
- When apply_patch fails, the error includes the current file contents — use them to construct \
|
||||
a corrected patch without re-reading the file.
|
||||
- Do not `git commit` your changes or create new git branches unless explicitly requested.
|
||||
|
||||
# Validating Your Work
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use crate::sandbox::Sandbox;
|
||||
use crate::tool_registry::RegisteredTool;
|
||||
use crate::truncation::{truncate_output, TruncationMode};
|
||||
use arc_llm::types::ToolDefinition;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
|
@ -152,7 +153,8 @@ pub async fn apply_patch_operations(
|
|||
}
|
||||
PatchOperation::Update { path, hunks } => {
|
||||
let original = env.read_file(path, None, None).await?;
|
||||
let updated = apply_hunks(&original, hunks)?;
|
||||
let updated = apply_hunks(&original, hunks)
|
||||
.map_err(|err| format_patch_error(&err, path, &original))?;
|
||||
env.write_file(path, &updated).await?;
|
||||
results.push(format!("Updated file: {path}"));
|
||||
}
|
||||
|
|
@ -241,6 +243,17 @@ fn apply_hunks(content: &str, hunks: &[Hunk]) -> Result<String, String> {
|
|||
Ok(lines.join("\n"))
|
||||
}
|
||||
|
||||
fn format_patch_error(error: &str, path: &str, content: &str) -> String {
|
||||
let numbered: String = content
|
||||
.lines()
|
||||
.enumerate()
|
||||
.map(|(i, line)| format!("{}|{}", i + 1, line))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
let truncated = truncate_output(&numbered, 9_000, TruncationMode::HeadTail);
|
||||
format!("{error}\n\nCurrent contents of {path}:\n{truncated}")
|
||||
}
|
||||
|
||||
pub fn make_apply_patch_tool() -> RegisteredTool {
|
||||
RegisteredTool {
|
||||
definition: ToolDefinition {
|
||||
|
|
@ -647,4 +660,55 @@ mod tests {
|
|||
assert!(content.contains("println!(\"new\")"));
|
||||
assert!(!content.contains("println!(\"old\")"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_patch_error_includes_numbered_contents() {
|
||||
let result = format_patch_error(
|
||||
"Could not find context line in file: 'fn missing()'",
|
||||
"src/lib.rs",
|
||||
"fn hello() {\n println!(\"hi\");\n}",
|
||||
);
|
||||
assert!(result.contains("Could not find context line in file: 'fn missing()'"));
|
||||
assert!(result.contains("Current contents of src/lib.rs:"));
|
||||
assert!(result.contains("1|fn hello() {"));
|
||||
assert!(result.contains("2| println!(\"hi\");"));
|
||||
assert!(result.contains("3|}"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_patch_error_truncates_large_files() {
|
||||
let lines: Vec<String> = (1..=1_000)
|
||||
.map(|i| format!("line number {:04}", i))
|
||||
.collect();
|
||||
let content = lines.join("\n");
|
||||
let result = format_patch_error("some error", "big.txt", &content);
|
||||
assert!(result.len() < 10_000);
|
||||
assert!(result.contains("truncated") || result.contains("removed"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn apply_patch_error_includes_file_contents() {
|
||||
let mut files = HashMap::new();
|
||||
files.insert(
|
||||
"src/game.py".to_string(),
|
||||
"def real_fn():\n pass".to_string(),
|
||||
);
|
||||
let env = MutableMockSandbox::new(files);
|
||||
|
||||
let ops = vec![PatchOperation::Update {
|
||||
path: "src/game.py".into(),
|
||||
hunks: vec![Hunk {
|
||||
context_line: "def nonexistent():".into(),
|
||||
changes: vec![
|
||||
Change::Remove(" old_body()".into()),
|
||||
Change::Add(" new_body()".into()),
|
||||
],
|
||||
}],
|
||||
}];
|
||||
|
||||
let err = apply_patch_operations(&ops, &env).await.unwrap_err();
|
||||
assert!(err.contains("Could not find context line"));
|
||||
assert!(err.contains("Current contents of src/game.py:"));
|
||||
assert!(err.contains("1|def real_fn():"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue