Skip to content

Commit bae5ba1

Browse files
perf: make read operation output consistent across tools (#1790)
1 parent 03f42a3 commit bae5ba1

File tree

5 files changed

+20
-20
lines changed

5 files changed

+20
-20
lines changed

crates/forge_domain/src/line_numbers.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ impl<T: AsRef<str>> LineNumbers for T {
1313
self.as_ref()
1414
.lines()
1515
.enumerate()
16-
.map(|(i, line)| format!("{}: {}", start + i, line))
16+
.map(|(i, line)| format!("{}:{}", start + i, line))
1717
.collect::<Vec<_>>()
1818
.join("\n")
1919
}
@@ -26,21 +26,21 @@ mod tests {
2626
#[test]
2727
fn test_numbered_default_start() {
2828
let text = "first line\nsecond line\nthird line";
29-
let expected = "1: first line\n2: second line\n3: third line";
29+
let expected = "1:first line\n2:second line\n3:third line";
3030
assert_eq!(text.numbered(), expected);
3131
}
3232

3333
#[test]
3434
fn test_numbered_from_custom_start() {
3535
let text = "alpha\nbeta\ngamma";
36-
let expected = "5: alpha\n6: beta\n7: gamma";
36+
let expected = "5:alpha\n6:beta\n7:gamma";
3737
assert_eq!(text.numbered_from(5), expected);
3838
}
3939

4040
#[test]
4141
fn test_numbered_single_line() {
4242
let text = "single line";
43-
let expected = "1: single line";
43+
let expected = "1:single line";
4444
assert_eq!(text.numbered(), expected);
4545
}
4646

@@ -54,7 +54,7 @@ mod tests {
5454
#[test]
5555
fn test_numbered_with_empty_lines() {
5656
let text = "line1\n\nline3";
57-
let expected = "1: line1\n2: \n3: line3";
57+
let expected = "1:line1\n2:\n3:line3";
5858
assert_eq!(text.numbered(), expected);
5959
}
6060
}

crates/forge_domain/src/snapshots/forge_domain__tool_usage__tests__tool_usage.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ expression: prompt
77
<tool>{"name":"write","description":"Use it to create a new file at a specified path with the provided content.\n Always provide absolute paths for file locations. The tool\n automatically handles the creation of any missing intermediary directories\n in the specified path.\n IMPORTANT: DO NOT attempt to use this tool to move or rename files, use the\n shell tool instead.","arguments":{"content":{"description":"The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified.","type":"string","is_required":true},"overwrite":{"description":"If set to true, existing files will be overwritten. If not set and the file exists, an error will be returned with the content of the existing file.","type":"boolean","is_required":false},"path":{"description":"The path of the file to write to (absolute path required)","type":"string","is_required":true}}}</tool>
88
<tool>{"name":"search","description":"Recursively searches directories for files by content (regex) and/or name\n (glob pattern). Provides context-rich results with line numbers for content\n matches. Two modes: content search (when regex provided) or file finder\n (when regex omitted). Uses case-insensitive Rust regex syntax. Requires\n absolute paths. Avoids binary files and excluded directories. Best for code\n exploration, API usage discovery, configuration settings, or finding\n patterns across projects. For large pages, returns the first 200\n lines and stores the complete content in a temporary file for\n subsequent access.","arguments":{"file_pattern":{"description":"Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*).","type":"string","is_required":false},"max_search_lines":{"description":"Maximum number of lines to return in the search results.","type":"integer","is_required":false},"path":{"description":"The absolute path of the directory or file to search in. If it's a directory, it will be searched recursively. If it's a file path, only that specific file will be searched.","type":"string","is_required":true},"regex":{"description":"The regular expression pattern to search for in file contents. Uses Rust regex syntax. If not provided, only file name matching will be performed.","type":"string","is_required":false},"start_index":{"description":"Starting index for the search results (1-based).","type":"integer","is_required":false}}}</tool>
99
<tool>{"name":"remove","description":"Request to remove a file at the specified path. Use this when you need to\n delete an existing file. The path must be absolute. This operation cannot\n be undone, so use it carefully.","arguments":{"path":{"description":"The path of the file to remove (absolute path required)","type":"string","is_required":true}}}</tool>
10-
<tool>{"name":"patch","description":"Modifies files with targeted line operations on matched patterns. Supports\n prepend, append, replace, replace_all, swap operations. Ideal for precise\n changes to configs, code, or docs while preserving context. Not suitable for\n complex refactoring or modifying all pattern occurrences - use `write`\n instead for complete rewrites and `undo` for undoing the last operation.\n Fails if search pattern isn\\'t found.\\\\n\\\\nUsage Guidelines:\\\\n-When editing\n text from Read tool output, ensure you preserve new lines and the exact\n indentation (tabs/spaces) as it appears AFTER the line number prefix. The\n line number prefix format is: line number + \\':\\' + one space. Everything\n after that space is the actual file content to match. Never include any part\n of the line number prefix in the search or content","arguments":{"content":{"description":"The text to replace it with (must be different from search)","type":"string","is_required":true},"operation":{"description":"The operation to perform on the matched text. Possible options are: - 'prepend': Add content before the matched text - 'append': Add content after the matched text - 'replace': Use only for specific, targeted replacements where you need to modify just the first match. - 'replace_all': Should be used for renaming variables, functions, types, or any widespread replacements across the file. This is the recommended choice for consistent refactoring operations as it ensures all occurrences are updated. - 'swap': Replace the matched text with another text (search for the second text and swap them)","type":"string","is_required":true},"path":{"description":"The path to the file to modify","type":"string","is_required":true},"search":{"description":"The text to replace. When skipped the patch operation applies to the entire content. `Append` adds the new content to the end, `Prepend` adds it to the beginning, and `Replace` fully overwrites the original content. `Swap` requires a search target, so without one, it makes no changes.","type":"string","is_required":false}}}</tool>
10+
<tool>{"name":"patch","description":"Modifies files with targeted line operations on matched patterns. Supports\n prepend, append, replace, replace_all, swap operations. Ideal for precise\n changes to configs, code, or docs while preserving context. Not suitable for\n complex refactoring or modifying all pattern occurrences - use `write`\n instead for complete rewrites and `undo` for undoing the last operation.\n Fails if search pattern isn\\'t found.\\\\n\\\\nUsage Guidelines:\\\\n-When editing\n text from Read tool output, ensure you preserve new lines and the exact\n indentation (tabs/spaces) as it appears AFTER the line number prefix. The\n line number prefix format is: line number + \\':\\'. Everything\n after that is the actual file content to match. Never include any part\n of the line number prefix in the search or content","arguments":{"content":{"description":"The text to replace it with (must be different from search)","type":"string","is_required":true},"operation":{"description":"The operation to perform on the matched text. Possible options are: - 'prepend': Add content before the matched text - 'append': Add content after the matched text - 'replace': Use only for specific, targeted replacements where you need to modify just the first match. - 'replace_all': Should be used for renaming variables, functions, types, or any widespread replacements across the file. This is the recommended choice for consistent refactoring operations as it ensures all occurrences are updated. - 'swap': Replace the matched text with another text (search for the second text and swap them)","type":"string","is_required":true},"path":{"description":"The path to the file to modify","type":"string","is_required":true},"search":{"description":"The text to replace. When skipped the patch operation applies to the entire content. `Append` adds the new content to the end, `Prepend` adds it to the beginning, and `Replace` fully overwrites the original content. `Swap` requires a search target, so without one, it makes no changes.","type":"string","is_required":false}}}</tool>
1111
<tool>{"name":"undo","description":"Reverts the most recent file operation (create/modify/delete) on a specific\n file. Use this tool when you need to recover from incorrect file changes or\n if a revert is requested by the user.","arguments":{"path":{"description":"The absolute path of the file to revert to its previous state.","type":"string","is_required":true}}}</tool>
1212
<tool>{"name":"shell","description":"Executes shell commands with safety measures using restricted bash (rbash).\n Prevents potentially harmful operations like absolute path execution and\n directory changes. Use for file system interaction, running utilities,\n installing packages, or executing build commands. For operations requiring\n unrestricted access, advise users to run forge CLI with \\'-u\\' flag. Returns\n complete output including stdout, stderr, and exit code for diagnostic\n purposes.","arguments":{"command":{"description":"The shell command to execute.","type":"string","is_required":true},"cwd":{"description":"The working directory where the command should be executed.","type":"string","is_required":true},"env":{"description":"Environment variable names to pass to command execution (e.g., [\"PATH\", \"HOME\", \"USER\"]). The system automatically reads the specified values and applies them during command execution.","type":"array","is_required":false},"keep_ansi":{"description":"Whether to preserve ANSI escape codes in the output. If true, ANSI escape codes will be preserved in the output. If false (default), ANSI escape codes will be stripped from the output.","type":"boolean","is_required":false}}}</tool>
1313
<tool>{"name":"fetch","description":"Input type for the net fetch tool","arguments":{"raw":{"description":"Get raw content without any markdown conversion (default: false)","type":"boolean","is_required":false},"url":{"description":"URL to fetch","type":"string","is_required":true}}}</tool>

crates/forge_domain/src/snapshots/forge_domain__tools__tests__tool_definition_json.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ expression: tools
122122
}
123123
{
124124
"title": "FSPatch",
125-
"description": "Modifies files with targeted line operations on matched patterns. Supports prepend, append, replace, replace_all, swap operations. Ideal for precise changes to configs, code, or docs while preserving context. Not suitable for complex refactoring or modifying all pattern occurrences - use `write` instead for complete rewrites and `undo` for undoing the last operation. Fails if search pattern isn't found.\\n\\nUsage Guidelines:\\n-When editing text from Read tool output, ensure you preserve new lines and the exact indentation (tabs/spaces) as it appears AFTER the line number prefix. The line number prefix format is: line number + ':' + one space. Everything after that space is the actual file content to match. Never include any part of the line number prefix in the search or content",
125+
"description": "Modifies files with targeted line operations on matched patterns. Supports prepend, append, replace, replace_all, swap operations. Ideal for precise changes to configs, code, or docs while preserving context. Not suitable for complex refactoring or modifying all pattern occurrences - use `write` instead for complete rewrites and `undo` for undoing the last operation. Fails if search pattern isn't found.\\n\\nUsage Guidelines:\\n-When editing text from Read tool output, ensure you preserve new lines and the exact indentation (tabs/spaces) as it appears AFTER the line number prefix. The line number prefix format is: line number + ':'. Everything after that is the actual file content to match. Never include any part of the line number prefix in the search or content",
126126
"type": "object",
127127
"required": [
128128
"content",

crates/forge_domain/src/tools.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,8 +239,8 @@ impl JsonSchema for PatchOperation {
239239
/// Fails if search pattern isn't found.\n\nUsage Guidelines:\n-When editing
240240
/// text from Read tool output, ensure you preserve new lines and the exact
241241
/// indentation (tabs/spaces) as it appears AFTER the line number prefix. The
242-
/// line number prefix format is: line number + ':' + one space. Everything
243-
/// after that space is the actual file content to match. Never include any part
242+
/// line number prefix format is: line number + ':'. Everything
243+
/// after that is the actual file content to match. Never include any part
244244
/// of the line number prefix in the search or content
245245
#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, ToolDescription, PartialEq)]
246246
pub struct FSPatch {

crates/forge_services/src/attachment.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -816,7 +816,7 @@ pub mod tests {
816816
assert_eq!(
817817
attachments[0].content,
818818
AttachmentContent::FileContent {
819-
content: "2: Line 2".to_string(),
819+
content: "2:Line 2".to_string(),
820820
start_line: 2,
821821
end_line: 2,
822822
total_lines: 5,
@@ -845,7 +845,7 @@ pub mod tests {
845845
assert_eq!(
846846
attachments[0].content,
847847
AttachmentContent::FileContent {
848-
content: "2: Line 2\n3: Line 3\n4: Line 4".to_string(),
848+
content: "2:Line 2\n3:Line 3\n4:Line 4".to_string(),
849849
start_line: 2,
850850
end_line: 4,
851851
total_lines: 6,
@@ -870,7 +870,7 @@ pub mod tests {
870870
assert_eq!(
871871
attachments[0].content,
872872
AttachmentContent::FileContent {
873-
content: "1: First\n2: Second".to_string(),
873+
content: "1:First\n2:Second".to_string(),
874874
start_line: 1,
875875
end_line: 2,
876876
total_lines: 4,
@@ -895,7 +895,7 @@ pub mod tests {
895895
assert_eq!(
896896
attachments[0].content,
897897
AttachmentContent::FileContent {
898-
content: "3: Gamma\n4: Delta\n5: Epsilon".to_string(),
898+
content: "3:Gamma\n4:Delta\n5:Epsilon".to_string(),
899899
start_line: 3,
900900
end_line: 5,
901901
total_lines: 5,
@@ -920,7 +920,7 @@ pub mod tests {
920920
assert_eq!(
921921
attachments[0].content,
922922
AttachmentContent::FileContent {
923-
content: "1: Only line".to_string(),
923+
content: "1:Only line".to_string(),
924924
start_line: 1,
925925
end_line: 1,
926926
total_lines: 1,
@@ -948,7 +948,7 @@ pub mod tests {
948948
assert_eq!(
949949
attachments[0].content,
950950
AttachmentContent::FileContent {
951-
content: "1: A1\n2: A2".to_string(),
951+
content: "1:A1\n2:A2".to_string(),
952952
start_line: 1,
953953
end_line: 2,
954954
total_lines: 3,
@@ -957,7 +957,7 @@ pub mod tests {
957957
assert_eq!(
958958
attachments[1].content,
959959
AttachmentContent::FileContent {
960-
content: "3: B3\n4: B4".to_string(),
960+
content: "3:B3\n4:B4".to_string(),
961961
start_line: 3,
962962
end_line: 4,
963963
total_lines: 4,
@@ -985,7 +985,7 @@ pub mod tests {
985985
assert_eq!(
986986
attachments[0].content,
987987
AttachmentContent::FileContent {
988-
content: "3: Meta3\n4: Meta4\n5: Meta5".to_string(),
988+
content: "3:Meta3\n4:Meta4\n5:Meta5".to_string(),
989989
start_line: 3,
990990
end_line: 5,
991991
total_lines: 7,
@@ -1017,7 +1017,7 @@ pub mod tests {
10171017
assert_eq!(
10181018
attachments_full[0].content,
10191019
AttachmentContent::FileContent {
1020-
content: "1: Full1\n2: Full2\n3: Full3\n4: Full4\n5: Full5".to_string(),
1020+
content: "1:Full1\n2:Full2\n3:Full3\n4:Full4\n5:Full5".to_string(),
10211021
start_line: 1,
10221022
end_line: 5,
10231023
total_lines: 5,
@@ -1028,7 +1028,7 @@ pub mod tests {
10281028
assert_eq!(
10291029
attachments_range[0].content,
10301030
AttachmentContent::FileContent {
1031-
content: "2: Full2\n3: Full3\n4: Full4".to_string(),
1031+
content: "2:Full2\n3:Full3\n4:Full4".to_string(),
10321032
start_line: 2,
10331033
end_line: 4,
10341034
total_lines: 5,
@@ -1039,7 +1039,7 @@ pub mod tests {
10391039
assert_eq!(
10401040
attachments_range_start[0].content,
10411041
AttachmentContent::FileContent {
1042-
content: "2: Full2\n3: Full3\n4: Full4\n5: Full5".to_string(),
1042+
content: "2:Full2\n3:Full3\n4:Full4\n5:Full5".to_string(),
10431043
start_line: 2,
10441044
end_line: 5,
10451045
total_lines: 5,

0 commit comments

Comments
 (0)