Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion emrg/tools/read_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,10 +286,23 @@ async def execute(self, arguments: dict) -> ToolResult:
for i, line in enumerate(selected):
result_lines.append(f"{start + i + 1:6d}\t{line}")

# Reached exactly when `start_line` is past the last line: `start >= end` forces
# `end == total_lines`, so the `lines {start + 1}-{end}` this used to print was
# **always descending** — a range that cannot be a range, offered where the caller
# asked why nothing came back. Measured 2026-10-10 (`cyc20261010-204628`) on a
# 10-line file: `start_line=50` answered `(empty range: lines 50-10 of 10)` and
# `start_line=11` answered `(empty range: lines 11-10 of 10)`. The test that pinned
# it (`test_read_start_line_beyond_eof`) asserted only that the substring
# `empty range` appeared, so the descending range satisfied it. This branch names
# the condition and the two numbers it holds — the request, and the subject's size.
if not result_lines:
plural = "" if total_lines == 1 else "s"
return ToolResult(
name="read",
content=f"(empty range: lines {start + 1}-{end} of {total_lines})",
content=(
f"(no lines: start_line={start_line} is past the end of the file, "
f"which has {total_lines} line{plural})"
),
)

if offset_note is not None:
Expand Down
33 changes: 31 additions & 2 deletions tests/test_read_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,40 @@ def test_read_binary_fails(temp_file):


def test_read_start_line_beyond_eof(temp_file):
"""The message names the request and the subject's size — and is not a range.

The branch is reached exactly when `start_line` is past the last line, so the
`lines {start + 1}-{end}` it used to print was always descending (measured 2026-10-10,
`cyc20261010-204628`: a 10-line file answered `start_line=50` with `lines 50-10 of
10`). This test asserted the substring `empty range`, which that descending range
satisfies — a wording pinned by the presence of its words rather than by what they
say. Both numbers the message holds are asserted now, and the descending shape is
refused, so a message naming the wrong start line or the wrong file size fails here.
"""
tool = ReadTool()
f, _ = temp_file
# 6 lines (5 + trailing newline), start_line=100 is way beyond
# 5 lines (the fixture writes 5, and the trailing newline is not a line)
result = _run(tool.execute({"file_path": str(f), "start_line": 100}))
assert "(empty range" in result.content or "empty range" in result.content
assert "start_line=100" in result.content, result.content
assert "5 lines" in result.content, result.content
assert "100-5" not in result.content, result.content
assert "lines 100" not in result.content, result.content


def test_a_one_line_file_past_its_end_is_counted_in_the_singular(tmp_path):
"""The singular half of the message above, which no test reached.

`plural = "" if total_lines == 1 else "s"` is a branch, and a branch needs a reading:
measured 2026-10-10, replacing it with the bare `"s"` left every test in this file
green, so a one-line file read past its end could have been made to say `1 lines`
with no red anywhere. It is reachable — `start_line=2` on a one-line file goes
through this branch — and the plural side above does not reach it.
"""
f = tmp_path / "one.txt"
f.write_text("only\n")
result = _run(ReadTool().execute({"file_path": str(f), "start_line": 2}))
assert "which has 1 line" in result.content, result.content
assert "1 lines" not in result.content, result.content


def test_read_truncation_message(temp_file):
Expand Down
Loading