Co-authored-by: Alexis Tacnet <alexis@mistral.ai>
Co-authored-by: Clément Drouin <clement.drouin@mistral.ai>
Co-authored-by: Guillaume LE GOFF <guillaume.lgf@gmail.com>
Co-authored-by: Lucas Marandat <31749711+lucasmrdt@users.noreply.github.com>
Co-authored-by: Maxime Dolores <maxime.dolores@ext.mistral.ai>
Co-authored-by: Pierre Rossinès <pierre.rossines@mistral.ai>
Co-authored-by: Quentin <quentin.torroba@mistral.ai>
Co-authored-by: Val <102326092+vdeva@users.noreply.github.com>
Co-authored-by: Vincent G <10739306+VinceOPS@users.noreply.github.com>
Co-authored-by: p.vezia <166131032+le-codeur-rapide@users.noreply.github.com>
Co-authored-by: Hiba Chaabnia <Hiba-Chaabnia@users.noreply.github.com>
Co-authored-by: Nikhil Bhima <nikhilbhima@users.noreply.github.com>
Co-authored-by: Nkipohcs <Nkipohcs@users.noreply.github.com>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
Mathias Gesbert 2026-06-04 18:26:35 +02:00 committed by GitHub
parent ad0d5c9520
commit 3f8487f761
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
197 changed files with 10819 additions and 2830 deletions

View file

@ -0,0 +1,230 @@
from __future__ import annotations
import asyncio
from pathlib import Path
import pytest
from tests.mock.utils import collect_result
from vibe.core.tools.base import BaseToolState, ToolError
from vibe.core.tools.builtins.edit import Edit, EditArgs, EditConfig, EditResult
from vibe.core.tools.ui import ToolCallDisplay, ToolResultDisplay
from vibe.core.types import ToolResultEvent
def _make_edit() -> Edit:
return Edit(config_getter=lambda: EditConfig(), state=BaseToolState())
def _write(tmp_path: Path, name: str, content: str) -> Path:
p = tmp_path / name
p.write_text(content)
return p
@pytest.mark.asyncio
async def test_exact_match_replaces(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
_write(tmp_path, "f.txt", "hello world\n")
edit = _make_edit()
result = await collect_result(
edit.run(EditArgs(file_path="f.txt", old_string="hello", new_string="goodbye"))
)
assert (tmp_path / "f.txt").read_text() == "goodbye world\n"
assert result.file == str(tmp_path / "f.txt")
assert result.message == "The file has been updated successfully."
@pytest.mark.asyncio
async def test_replace_all(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.chdir(tmp_path)
_write(tmp_path, "f.txt", "aaa bbb aaa\n")
edit = _make_edit()
result = await collect_result(
edit.run(
EditArgs(
file_path="f.txt", old_string="aaa", new_string="ccc", replace_all=True
)
)
)
assert (tmp_path / "f.txt").read_text() == "ccc bbb ccc\n"
assert result.message == (
"The file has been updated. All occurrences were successfully replaced"
)
@pytest.mark.asyncio
async def test_not_found_raises(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
_write(tmp_path, "f.txt", "hello world\n")
edit = _make_edit()
with pytest.raises(ToolError, match="String to replace not found in file"):
await collect_result(
edit.run(EditArgs(file_path="f.txt", old_string="missing", new_string="x"))
)
@pytest.mark.asyncio
async def test_multiple_matches_without_replace_all_raises(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
_write(tmp_path, "f.txt", "aaa bbb aaa\n")
edit = _make_edit()
with pytest.raises(ToolError, match="Found 2 matches"):
await collect_result(
edit.run(EditArgs(file_path="f.txt", old_string="aaa", new_string="ccc"))
)
@pytest.mark.asyncio
async def test_old_equals_new_raises(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
_write(tmp_path, "f.txt", "hello\n")
edit = _make_edit()
with pytest.raises(ToolError, match="No changes to make"):
await collect_result(
edit.run(
EditArgs(file_path="f.txt", old_string="hello", new_string="hello")
)
)
@pytest.mark.asyncio
async def test_empty_old_string_raises(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
_write(tmp_path, "f.txt", "hello\n")
edit = _make_edit()
with pytest.raises(ToolError, match="old_string cannot be empty"):
await collect_result(
edit.run(EditArgs(file_path="f.txt", old_string="", new_string="x"))
)
@pytest.mark.asyncio
async def test_file_not_found_raises(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
edit = _make_edit()
with pytest.raises(ToolError, match="File does not exist"):
await collect_result(
edit.run(
EditArgs(
file_path="/nonexistent/file.py", old_string="x", new_string="y"
)
)
)
@pytest.mark.asyncio
async def test_empty_file_path_raises(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
edit = _make_edit()
with pytest.raises(ToolError, match="File path cannot be empty"):
await collect_result(
edit.run(EditArgs(file_path="", old_string="x", new_string="y"))
)
@pytest.mark.asyncio
async def test_deletion_removes_exact_string(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
_write(tmp_path, "f.txt", "line1\nline2\nline3\n")
edit = _make_edit()
await collect_result(
edit.run(EditArgs(file_path="f.txt", old_string="line2\n", new_string=""))
)
assert (tmp_path / "f.txt").read_text() == "line1\nline3\n"
@pytest.mark.asyncio
async def test_parallel_edits_same_file_all_land(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
_write(tmp_path, "f.txt", "A\nB\nC\nD\n")
edit = _make_edit()
await asyncio.gather(
collect_result(
edit.run(EditArgs(file_path="f.txt", old_string="A", new_string="A1"))
),
collect_result(
edit.run(EditArgs(file_path="f.txt", old_string="B", new_string="B1"))
),
collect_result(
edit.run(EditArgs(file_path="f.txt", old_string="C", new_string="C1"))
),
collect_result(
edit.run(EditArgs(file_path="f.txt", old_string="D", new_string="D1"))
),
)
assert (tmp_path / "f.txt").read_text() == "A1\nB1\nC1\nD1\n"
@pytest.mark.asyncio
async def test_relative_path_resolved_from_cwd(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
(tmp_path / "sub").mkdir(exist_ok=True)
_write(tmp_path / "sub", "f.txt", "old")
edit = _make_edit()
result = await collect_result(
edit.run(EditArgs(file_path="sub/f.txt", old_string="old", new_string="new"))
)
assert (tmp_path / "sub" / "f.txt").read_text() == "new"
assert result.file == str(tmp_path / "sub" / "f.txt")
def test_format_call_display() -> None:
args = EditArgs(file_path="/abs/foo.py", old_string="old", new_string="new")
display = Edit.format_call_display(args)
assert isinstance(display, ToolCallDisplay)
assert display.summary == "Editing foo.py"
def test_get_result_display() -> None:
result = EditResult(
file="/path/to/foo.py",
message="The file has been updated successfully.",
old_string="old",
new_string="new",
)
event = ToolResultEvent(
tool_call_id="test", tool_name="edit", tool_class=None, result=result
)
display = Edit.get_result_display(event)
assert isinstance(display, ToolResultDisplay)
assert display.success is True
assert "foo.py" in display.message

View file

@ -0,0 +1,355 @@
from __future__ import annotations
from collections.abc import Iterator
from pathlib import Path
import pytest
from tests.mock.utils import collect_result
from vibe.core.config.harness_files import (
init_harness_files_manager,
reset_harness_files_manager,
)
from vibe.core.tools.base import ToolError
from vibe.core.tools.builtins.read import (
DEFAULT_LINE_LIMIT,
MAX_BYTES,
Read,
ReadArgs,
ReadConfig,
ReadResult,
ReadState,
_add_line_numbers,
)
from vibe.core.tools.ui import ToolCallDisplay, ToolResultDisplay
from vibe.core.trusted_folders import trusted_folders_manager
from vibe.core.types import ToolResultEvent
from vibe.core.utils import VIBE_WARNING_TAG
def _make_read() -> Read:
return Read(config_getter=lambda: ReadConfig(), state=ReadState())
@pytest.mark.asyncio
async def test_reads_entire_small_file(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
(tmp_path / "hello.txt").write_text("line one\nline two\n", encoding="utf-8")
tool = _make_read()
result = await collect_result(
tool.run(ReadArgs(file_path=str(tmp_path / "hello.txt")))
)
assert result.num_lines == 2
assert result.total_lines == 2
assert result.start_line == 1
assert "line one" in result.content
assert "line two" in result.content
@pytest.mark.asyncio
async def test_reads_with_offset_and_limit(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
content = "".join(f"line {i}\n" for i in range(1, 11))
(tmp_path / "f.txt").write_text(content, encoding="utf-8")
tool = _make_read()
result = await collect_result(
tool.run(ReadArgs(file_path=str(tmp_path / "f.txt"), offset=3, limit=2))
)
assert result.num_lines == 2
assert result.start_line == 3
# Bounded read stops at the limit, so the true total is unknown.
assert result.total_lines is None
assert result.was_truncated is True
assert "line 3" in result.content
assert "line 4" in result.content
assert "line 2" not in result.content
assert "line 5" not in result.content
@pytest.mark.asyncio
async def test_empty_file_returns_warning(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
(tmp_path / "empty.txt").write_text("", encoding="utf-8")
tool = _make_read()
result = await collect_result(
tool.run(ReadArgs(file_path=str(tmp_path / "empty.txt")))
)
assert result.num_lines == 0
assert VIBE_WARNING_TAG in result.content
assert "empty" in result.content
@pytest.mark.asyncio
async def test_offset_beyond_file_returns_warning(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
(tmp_path / "short.txt").write_text("one\ntwo\n", encoding="utf-8")
tool = _make_read()
result = await collect_result(
tool.run(ReadArgs(file_path=str(tmp_path / "short.txt"), offset=100))
)
assert result.num_lines == 0
assert VIBE_WARNING_TAG in result.content
assert "shorter" in result.content
@pytest.mark.asyncio
async def test_exceeds_max_bytes_raises(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
# Create file that generates output exceeding MAX_BYTES
big_line = "x" * 200 + "\n"
lines_needed = (MAX_BYTES // len(big_line)) + 100
(tmp_path / "big.txt").write_text(big_line * lines_needed, encoding="utf-8")
tool = _make_read()
with pytest.raises(ToolError, match="exceeds maximum allowed size"):
await collect_result(tool.run(ReadArgs(file_path=str(tmp_path / "big.txt"))))
@pytest.mark.asyncio
async def test_truncated_when_more_lines_than_limit(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
content = "".join(f"line {i}\n" for i in range(1, 101))
(tmp_path / "f.txt").write_text(content, encoding="utf-8")
tool = _make_read()
result = await collect_result(
tool.run(ReadArgs(file_path=str(tmp_path / "f.txt"), limit=10))
)
assert result.num_lines == 10
assert result.was_truncated is True
assert result.total_lines is None
@pytest.mark.asyncio
async def test_single_oversized_line_raises(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
(tmp_path / "wide.txt").write_text("x" * (MAX_BYTES + 10), encoding="utf-8")
tool = _make_read()
with pytest.raises(ToolError, match="exceeds maximum allowed size"):
await collect_result(tool.run(ReadArgs(file_path=str(tmp_path / "wide.txt"))))
@pytest.mark.asyncio
async def test_file_not_found_raises(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
tool = _make_read()
with pytest.raises(ToolError, match="File not found"):
await collect_result(tool.run(ReadArgs(file_path=str(tmp_path / "nope.txt"))))
@pytest.mark.asyncio
async def test_empty_path_raises(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
tool = _make_read()
with pytest.raises(ToolError, match="file_path cannot be empty"):
await collect_result(tool.run(ReadArgs(file_path="")))
@pytest.mark.asyncio
async def test_directory_raises(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
(tmp_path / "adir").mkdir()
tool = _make_read()
with pytest.raises(ToolError, match="directory"):
await collect_result(tool.run(ReadArgs(file_path=str(tmp_path / "adir"))))
@pytest.mark.asyncio
async def test_relative_path_resolved(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
(tmp_path / "sub").mkdir()
(tmp_path / "sub" / "f.txt").write_text("ok\n", encoding="utf-8")
tool = _make_read()
result = await collect_result(tool.run(ReadArgs(file_path="sub/f.txt")))
assert result.num_lines == 1
assert str(tmp_path / "sub" / "f.txt") == result.file_path
def test_line_number_format() -> None:
formatted = _add_line_numbers(["hello", "world"], start=1)
lines = formatted.split("\n")
assert lines[0] == " 1\u2192hello"
assert lines[1] == " 2\u2192world"
def test_default_limit_is_2000() -> None:
assert DEFAULT_LINE_LIMIT == 2000
def test_format_call_display() -> None:
args = ReadArgs(file_path="/some/file.py")
display = Read.format_call_display(args)
assert isinstance(display, ToolCallDisplay)
assert "file.py" in display.summary
def test_format_call_display_with_offset_limit() -> None:
args = ReadArgs(file_path="/some/file.py", offset=10, limit=50)
display = Read.format_call_display(args)
assert "from line 10" in display.summary
assert "limit 50" in display.summary
def test_get_result_display() -> None:
result = ReadResult(
file_path="/path/to/foo.py",
content="...",
num_lines=10,
start_line=1,
total_lines=10,
)
event = ToolResultEvent(
tool_call_id="test", tool_name="read", tool_class=None, result=result
)
display = Read.get_result_display(event)
assert isinstance(display, ToolResultDisplay)
assert display.success is True
assert "10 lines" in display.message
assert "foo.py" in display.message
def test_get_result_display_truncated() -> None:
result = ReadResult(
file_path="/path/to/foo.py",
content="...",
num_lines=10,
start_line=1,
total_lines=100,
)
event = ToolResultEvent(
tool_call_id="test", tool_name="read", tool_class=None, result=result
)
display = Read.get_result_display(event)
assert "truncated" in display.message
def test_get_result_display_truncated_via_flag() -> None:
result = ReadResult(
file_path="/path/to/foo.py",
content="...",
num_lines=10,
start_line=1,
total_lines=None,
was_truncated=True,
)
event = ToolResultEvent(
tool_call_id="test", tool_name="read", tool_class=None, result=result
)
display = Read.get_result_display(event)
assert "truncated" in display.message
@pytest.fixture()
def _setup_manager(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Iterator[None]:
monkeypatch.chdir(tmp_path)
monkeypatch.setattr(trusted_folders_manager, "is_trusted", lambda _: True)
monkeypatch.setattr(
trusted_folders_manager, "find_trust_root", lambda _: tmp_path.resolve()
)
reset_harness_files_manager()
init_harness_files_manager("user", "project")
yield
reset_harness_files_manager()
@pytest.mark.usefixtures("_setup_manager")
def test_agents_md_injection(tmp_path: Path) -> None:
sub = tmp_path / "sub"
sub.mkdir()
(sub / "AGENTS.md").write_text("# Sub instructions", encoding="utf-8")
target = sub / "file.py"
target.write_text("hello", encoding="utf-8")
tool = _make_read()
result = ReadResult(
file_path=str(target), content="hello", num_lines=1, start_line=1, total_lines=1
)
annotation = tool.get_result_extra(result)
assert annotation is not None
assert VIBE_WARNING_TAG in annotation
assert "# Sub instructions" in annotation
@pytest.mark.usefixtures("_setup_manager")
def test_agents_md_deduplicates(tmp_path: Path) -> None:
sub = tmp_path / "sub"
sub.mkdir()
(sub / "AGENTS.md").write_text("# Sub", encoding="utf-8")
(sub / "a.py").write_text("a", encoding="utf-8")
(sub / "b.py").write_text("b", encoding="utf-8")
tool = _make_read()
r1 = ReadResult(
file_path=str(sub / "a.py"),
content="a",
num_lines=1,
start_line=1,
total_lines=1,
)
assert tool.get_result_extra(r1) is not None
r2 = ReadResult(
file_path=str(sub / "b.py"),
content="b",
num_lines=1,
start_line=1,
total_lines=1,
)
assert tool.get_result_extra(r2) is None
def test_agents_md_returns_none_when_not_initialized(tmp_path: Path) -> None:
reset_harness_files_manager()
tool = _make_read()
result = ReadResult(
file_path=str(tmp_path / "file.py"),
content="",
num_lines=0,
start_line=1,
total_lines=0,
)
assert tool.get_result_extra(result) is None
reset_harness_files_manager()

View file

@ -1,217 +0,0 @@
from __future__ import annotations
from collections.abc import Iterator
from pathlib import Path
import pytest
from tests.mock.utils import collect_result
from vibe.core.config.harness_files import (
init_harness_files_manager,
reset_harness_files_manager,
)
from vibe.core.tools.builtins.read_file import (
ReadFile,
ReadFileArgs,
ReadFileResult,
ReadFileState,
ReadFileToolConfig,
)
from vibe.core.trusted_folders import trusted_folders_manager
from vibe.core.utils import VIBE_WARNING_TAG
@pytest.fixture()
def _setup_manager(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Iterator[None]:
"""Initialize harness files manager for tests, reset after."""
monkeypatch.chdir(tmp_path)
monkeypatch.setattr(trusted_folders_manager, "is_trusted", lambda _: True)
monkeypatch.setattr(
trusted_folders_manager, "find_trust_root", lambda _: tmp_path.resolve()
)
reset_harness_files_manager()
init_harness_files_manager("user", "project")
yield
reset_harness_files_manager()
def _make_read_file() -> ReadFile:
return ReadFile(config_getter=lambda: ReadFileToolConfig(), state=ReadFileState())
class TestReadFileExecution:
@pytest.mark.asyncio
async def test_run_with_large_offset_still_reads_lines(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
test_file = tmp_path / "large_file.txt"
test_file.write_text(
"".join(f"line {i}\n" for i in range(200)), encoding="utf-8"
)
tool = ReadFile(
config_getter=lambda: ReadFileToolConfig(max_read_bytes=64),
state=ReadFileState(),
)
result = await collect_result(
tool.run(ReadFileArgs(path=str(test_file), offset=50, limit=2))
)
assert result.content == "line 50\nline 51\n"
assert result.lines_read == 2
assert result.was_truncated
@pytest.mark.asyncio
async def test_run_marks_truncated_when_max_read_bytes_exceeded(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
test_file = tmp_path / "big.txt"
test_file.write_text(
"".join(f"line {i}\n" for i in range(100)), encoding="utf-8"
)
tool = ReadFile(
config_getter=lambda: ReadFileToolConfig(max_read_bytes=20),
state=ReadFileState(),
)
result = await collect_result(tool.run(ReadFileArgs(path=str(test_file))))
assert result.was_truncated
assert result.lines_read > 0
@pytest.mark.asyncio
async def test_run_not_truncated_when_eof_reached(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
test_file = tmp_path / "small.txt"
test_file.write_text("a\nb\nc\n", encoding="utf-8")
tool = ReadFile(
config_getter=lambda: ReadFileToolConfig(max_read_bytes=1024),
state=ReadFileState(),
)
result = await collect_result(tool.run(ReadFileArgs(path=str(test_file))))
assert not result.was_truncated
assert result.lines_read == 3
@pytest.mark.asyncio
async def test_run_not_truncated_when_limit_matches_remaining_lines(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
test_file = tmp_path / "exact.txt"
test_file.write_text("a\nb\nc\n", encoding="utf-8")
tool = ReadFile(
config_getter=lambda: ReadFileToolConfig(max_read_bytes=1024),
state=ReadFileState(),
)
result = await collect_result(
tool.run(ReadFileArgs(path=str(test_file), limit=10))
)
assert not result.was_truncated
assert result.lines_read == 3
class TestGetResultExtra:
@pytest.mark.usefixtures("_setup_manager")
def test_returns_none_when_no_agents_md(self, tmp_path: Path) -> None:
sub = tmp_path / "sub"
sub.mkdir()
target = sub / "file.py"
target.write_text("hello", encoding="utf-8")
tool = _make_read_file()
result = ReadFileResult(
path=str(target), content="hello", lines_read=1, was_truncated=False
)
assert tool.get_result_extra(result) is None
@pytest.mark.usefixtures("_setup_manager")
def test_returns_tagged_content_when_agents_md_found(self, tmp_path: Path) -> None:
sub = tmp_path / "sub"
sub.mkdir()
(sub / "AGENTS.md").write_text("# Sub instructions", encoding="utf-8")
target = sub / "file.py"
target.write_text("hello", encoding="utf-8")
tool = _make_read_file()
result = ReadFileResult(
path=str(target), content="hello", lines_read=1, was_truncated=False
)
annotation = tool.get_result_extra(result)
assert annotation is not None
assert f"<{VIBE_WARNING_TAG}>" in annotation
assert f"</{VIBE_WARNING_TAG}>" in annotation
assert "# Sub instructions" in annotation
assert "project instructions for this directory" in annotation
@pytest.mark.usefixtures("_setup_manager")
def test_deduplicates_across_calls(self, tmp_path: Path) -> None:
sub = tmp_path / "sub"
sub.mkdir()
(sub / "AGENTS.md").write_text("# Sub", encoding="utf-8")
file1 = sub / "a.py"
file2 = sub / "b.py"
file1.write_text("a", encoding="utf-8")
file2.write_text("b", encoding="utf-8")
tool = _make_read_file()
result1 = ReadFileResult(
path=str(file1), content="a", lines_read=1, was_truncated=False
)
assert tool.get_result_extra(result1) is not None
# Second call for a different file in the same dir → no duplicate
result2 = ReadFileResult(
path=str(file2), content="b", lines_read=1, was_truncated=False
)
assert tool.get_result_extra(result2) is None
@pytest.mark.usefixtures("_setup_manager")
def test_injects_new_dir_after_dedup(self, tmp_path: Path) -> None:
sub_a = tmp_path / "a"
sub_b = tmp_path / "b"
sub_a.mkdir()
sub_b.mkdir()
(sub_a / "AGENTS.md").write_text("# A", encoding="utf-8")
(sub_b / "AGENTS.md").write_text("# B", encoding="utf-8")
file_a = sub_a / "f.py"
file_b = sub_b / "f.py"
file_a.write_text("", encoding="utf-8")
file_b.write_text("", encoding="utf-8")
tool = _make_read_file()
r1 = ReadFileResult(
path=str(file_a), content="", lines_read=0, was_truncated=False
)
ann1 = tool.get_result_extra(r1)
assert ann1 is not None
assert "# A" in ann1
# Different subdirectory → should inject its AGENTS.md
r2 = ReadFileResult(
path=str(file_b), content="", lines_read=0, was_truncated=False
)
ann2 = tool.get_result_extra(r2)
assert ann2 is not None
assert "# B" in ann2
def test_returns_none_when_manager_not_initialized(self, tmp_path: Path) -> None:
reset_harness_files_manager()
tool = _make_read_file()
result = ReadFileResult(
path=str(tmp_path / "file.py"),
content="",
lines_read=0,
was_truncated=False,
)
assert tool.get_result_extra(result) is None
reset_harness_files_manager()