mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-08 09:49:34 -10:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe98067153 | ||
|
|
52b3338a40 |
No files matched your search
@@ -829,3 +829,61 @@ func TestGlobSkipsWorkspaceExcludedDirectoriesAndBinaryFiles(t *testing.T) {
|
||||
t.Fatalf("expected only keep.txt, got:\n%s", res.Output)
|
||||
}
|
||||
}
|
||||
|
||||
// A backwards range (end_line < start_line) is a caller-arithmetic slip, not a
|
||||
// fatal error: read_file recovers by reading just start_line and surfaces a note
|
||||
// explaining the adjustment, matching how an end_line past EOF is auto-clamped.
|
||||
// It must never hard-error the way it used to.
|
||||
func TestReadFileToolRecoversBackwardsRange(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
writeTestFile(t, filepath.Join(root, "notes.txt"), "alpha\nbeta\ngamma\ndelta\nepsilon")
|
||||
|
||||
result := NewReadFileTool(root).Run(context.Background(), map[string]any{
|
||||
"path": "notes.txt",
|
||||
"start_line": 3,
|
||||
"end_line": 2, // backwards
|
||||
})
|
||||
|
||||
if result.Status != StatusOK {
|
||||
t.Fatalf("backwards range must recover, not error; got %s: %s", result.Status, result.Output)
|
||||
}
|
||||
if strings.Contains(result.Output, "must be greater than or equal to start_line") {
|
||||
t.Fatalf("backwards range still hard-errors: %q", result.Output)
|
||||
}
|
||||
for _, want := range []string{
|
||||
"end_line 2 was before start_line 3", // the note
|
||||
"only line 3 was read",
|
||||
"3 | gamma",
|
||||
} {
|
||||
if !strings.Contains(result.Output, want) {
|
||||
t.Fatalf("expected %q in output, got %q", want, result.Output)
|
||||
}
|
||||
}
|
||||
// It read exactly start_line, nothing adjacent.
|
||||
if strings.Contains(result.Output, "beta") || strings.Contains(result.Output, "delta") {
|
||||
t.Fatalf("recovery read the wrong lines: %q", result.Output)
|
||||
}
|
||||
}
|
||||
|
||||
// A valid range with end_line >= start_line is byte-for-byte unchanged: the
|
||||
// recovery path must not touch normal reads (no stray note, exact same window).
|
||||
func TestReadFileToolValidRangeUnchangedByRecovery(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
writeTestFile(t, filepath.Join(root, "notes.txt"), "alpha\nbeta\ngamma\ndelta\nepsilon")
|
||||
|
||||
result := NewReadFileTool(root).Run(context.Background(), map[string]any{
|
||||
"path": "notes.txt",
|
||||
"start_line": 2,
|
||||
"end_line": 4,
|
||||
})
|
||||
if result.Status != StatusOK {
|
||||
t.Fatalf("valid range failed: %s: %s", result.Status, result.Output)
|
||||
}
|
||||
// Byte-for-byte: header, blank separator, the exact selected lines, and no
|
||||
// recovery note or trailing content. An altered header/separator or a stray
|
||||
// out-of-range line would fail this where a substring check would not.
|
||||
const want = "File: notes.txt (lines 2-4 of 5)\n\n2 | beta\n3 | gamma\n4 | delta"
|
||||
if result.Output != want {
|
||||
t.Fatalf("valid-range output changed by the recovery path:\n got: %q\nwant: %q", result.Output, want)
|
||||
}
|
||||
}
|
||||
@@ -106,8 +106,18 @@ func renderReadFileRange(absolutePath string, relativePath string, total int, st
|
||||
if endLine == 0 || endLine > total {
|
||||
endLine = total
|
||||
}
|
||||
// A backwards range (end_line < start_line) is a caller slip, not a fatal
|
||||
// error. Recover the way an end_line past EOF is already recovered a few lines
|
||||
// up (clamped down to the last line): clamp end_line UP to start_line and read
|
||||
// just that one line. Erroring here instead cost the caller a whole retry for
|
||||
// an off-by-arithmetic mistake, and it was inconsistent — start_line past EOF
|
||||
// and end_line past EOF both recover gracefully; only this case hard-failed.
|
||||
// The recovery is surfaced in the output (rangeNote) so it is never silent: the
|
||||
// caller sees it read a different range than it asked for and why.
|
||||
rangeNote := ""
|
||||
if endLine < startLine {
|
||||
return errorResult("Error: Invalid arguments for read_file: end_line must be greater than or equal to start_line")
|
||||
rangeNote = fmt.Sprintf("(note: end_line %d was before start_line %d, so only line %d was read — pass end_line >= start_line to read a wider range)", endLine, startLine, startLine)
|
||||
endLine = startLine
|
||||
}
|
||||
|
||||
truncated := false
|
||||
@@ -135,7 +145,12 @@ func renderReadFileRange(absolutePath string, relativePath string, total int, st
|
||||
budgetedOutput = newHeadTailOutputBudgetBuilder(readOutputBudgetBytes, "use start_line/end_line or max_lines to continue with a smaller range")
|
||||
}
|
||||
budgetedOutput.WriteString(header)
|
||||
budgetedOutput.WriteString("\n\n")
|
||||
budgetedOutput.WriteString("\n")
|
||||
if rangeNote != "" {
|
||||
budgetedOutput.WriteString(rangeNote)
|
||||
budgetedOutput.WriteString("\n")
|
||||
}
|
||||
budgetedOutput.WriteString("\n")
|
||||
if err := appendReadFileRange(budgetedOutput, absolutePath, startLine, selectedLines, width); err != nil {
|
||||
return errorResult("Error reading file " + relativePath + ": " + err.Error())
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user