tools/file: output diff format (context=bare, new=+, changes=unified diff)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5efcafa9a6
commit
496087a874
2
go.mod
2
go.mod
|
|
@ -3,3 +3,5 @@ module ollie
|
|||
go 1.25.6
|
||||
|
||||
require gopkg.in/yaml.v3 v3.0.1
|
||||
|
||||
require github.com/aymanbagabas/go-udiff v0.4.1
|
||||
|
|
|
|||
2
go.sum
2
go.sum
|
|
@ -1,3 +1,5 @@
|
|||
github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=
|
||||
github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
package file
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/aymanbagabas/go-udiff"
|
||||
)
|
||||
|
||||
// unifiedDiff produces a unified diff of oldContent → newContent for display.
|
||||
func unifiedDiff(path, oldContent, newContent string) string {
|
||||
return udiff.Unified(path, path, oldContent, newContent)
|
||||
}
|
||||
|
||||
// plusLines formats content as diff "added" lines, each prefixed with "+".
|
||||
// Used by file_write for new files.
|
||||
func plusLines(content string) string {
|
||||
lines := strings.Split(strings.TrimRight(content, "\n"), "\n")
|
||||
sb := strings.Builder{}
|
||||
for _, l := range lines {
|
||||
sb.WriteByte('+')
|
||||
sb.WriteString(l)
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@ var ToolEdit = tools.ToolInfo{
|
|||
|
||||
Usage:
|
||||
- You must use your ` + "`" + `Read` + "`" + ` tool at least once in the conversation before editing. This tool will error if you attempt an edit without reading the file.
|
||||
- When editing text from Read tool output, ensure you preserve 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 old_string or new_string.
|
||||
- When editing text from Read tool output, ensure you preserve the exact indentation (tabs/spaces) as it appears AFTER the leading "+" prefix. Never include the leading "+" in old_string or new_string.
|
||||
- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.
|
||||
- Only use emojis if the user explicitly requests it. Avoid adding emojis to files unless asked.
|
||||
- The edit will FAIL if ` + "`" + `old_string` + "`" + ` is not unique in the file. Either provide a larger string with more surrounding context to make it unique or use ` + "`" + `replace_all` + "`" + ` to change every instance of ` + "`" + `old_string` + "`" + `.
|
||||
|
|
@ -67,7 +67,7 @@ func (s *Server) dispatchEdit(_ context.Context, raw json.RawMessage) (string, e
|
|||
return "", fmt.Errorf("writing file: %w", err)
|
||||
}
|
||||
|
||||
return "File edited successfully", nil
|
||||
return unifiedDiff(a.FilePath, original, newContent), nil
|
||||
}
|
||||
|
||||
// robustReplace tries exact → whitespace-normalized → indentation-flexible → trimmed-boundary.
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ Usage:
|
|||
- By default, it reads up to 2000 lines starting from the beginning of the file
|
||||
- You can optionally specify a line offset and limit (especially handy for long files), but it's recommended to read the whole file by not providing these parameters
|
||||
- Any lines longer than 2000 characters will be truncated
|
||||
- Results are returned with line numbers in the format: line number + "| " + content, starting at 1
|
||||
- Results are returned in diff format: existing content lines have no prefix
|
||||
- This tool can only read files, not directories. To read a directory, use an ls command via the Bash tool.
|
||||
- You can call multiple tools in a single response. It is always better to speculatively read multiple potentially useful files in parallel.
|
||||
- If you read a file that exists but has empty contents "(empty file)" will be returned.`,
|
||||
|
|
@ -82,7 +82,7 @@ func (s *Server) dispatchRead(_ context.Context, raw json.RawMessage) (string, e
|
|||
if len(line) > maxLineLen {
|
||||
line = line[:maxLineLen] + "..."
|
||||
}
|
||||
lines = append(lines, fmt.Sprintf("%05d| %s", lineNum, line))
|
||||
lines = append(lines, line)
|
||||
}
|
||||
lineNum++
|
||||
if lineNum > off+lim {
|
||||
|
|
@ -97,7 +97,7 @@ func (s *Server) dispatchRead(_ context.Context, raw json.RawMessage) (string, e
|
|||
}
|
||||
|
||||
s.markRead(a.FilePath)
|
||||
return strings.Join(lines, "\n"), nil
|
||||
return strings.Join(lines, "\n") + "\n", nil
|
||||
}
|
||||
|
||||
// openChecked validates and opens a regular file within size limits.
|
||||
|
|
|
|||
|
|
@ -47,10 +47,19 @@ func (s *Server) dispatchWrite(_ context.Context, raw json.RawMessage) (string,
|
|||
return errText("existing file must be read first. Use the Read tool to examine the file contents."), nil
|
||||
}
|
||||
|
||||
var oldContent string
|
||||
if exists {
|
||||
oldContent, _ = readFileChecked(a.FilePath)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(a.FilePath, []byte(a.Content), 0644); err != nil { //nolint:gosec
|
||||
return "", fmt.Errorf("writing file: %w", err)
|
||||
}
|
||||
|
||||
s.markRead(a.FilePath)
|
||||
return "File written successfully", nil
|
||||
|
||||
if !exists {
|
||||
return plusLines(a.Content), nil
|
||||
}
|
||||
return unifiedDiff(a.FilePath, oldContent, a.Content), nil
|
||||
}
|
||||
|
|
|
|||
Reference in New Issue