187 lines
6.4 KiB
Python
187 lines
6.4 KiB
Python
# SPDX-FileCopyrightText: 2026 Art Sea LLC
|
|
#
|
|
# SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
"""Repository-boundary regression coverage using a nominal in-memory executor."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from io import StringIO
|
|
import logging
|
|
from typing import Mapping
|
|
|
|
import pytest
|
|
|
|
from band_aider.context.class_meta import ClassMeta
|
|
from band_aider.context.provenance import FutureGuidance
|
|
from band_aider.judgement.contextual_file_renderer import ContextualFileRenderer
|
|
from band_aider.judgement.edit_request import AiderEditRequest
|
|
from band_aider.judgement.file_manager import FileManager
|
|
from band_aider.judgement.prompt_formatter import MarkdownPromptFormatterBuilder
|
|
from band_aider.judgement.search_replace_token_generator import SearchReplaceTokenGenerator
|
|
from band_aider.reporting.change_report_writer import ChangeReportWriter
|
|
from band_aider.strategies.oop_signature_extract import (
|
|
OOpSignature,
|
|
OOpSignatureExtract,
|
|
OOpSignatureRender,
|
|
)
|
|
from band_aider.strategies.python.python_source_discovery import PythonSourceDiscovery
|
|
from tests.band_aider_loop_impl import _file_manager
|
|
from tests.support.completion_clients import StaticCompletionClient
|
|
from tests.support.memory_path import MemoryPath
|
|
from tests.support.memory_repo_exec import MemoryRepoExec
|
|
|
|
|
|
class _NoCards(OOpSignatureExtract):
|
|
"""Return no dependency cards for the isolated prompt rendering scenario."""
|
|
|
|
def extract(self, module: str, name: str) -> OOpSignature | None:
|
|
"""Return no card because the scenario owns only direct file evidence."""
|
|
|
|
del module, name
|
|
return None
|
|
|
|
def extract_references(
|
|
self,
|
|
sources_by_location: Mapping[str, str],
|
|
) -> tuple[OOpSignature, ...]:
|
|
"""Return no cards because no hidden declaration evidence is in scope."""
|
|
|
|
del sources_by_location
|
|
return ()
|
|
|
|
def extract_source(
|
|
self,
|
|
location: str,
|
|
source: str,
|
|
) -> tuple[OOpSignature, ...]:
|
|
"""Return no cards because the scenario owns direct file evidence only."""
|
|
|
|
del location, source
|
|
return ()
|
|
|
|
|
|
class _NoCardRender(OOpSignatureRender):
|
|
"""Reject unexpected card rendering in the direct-file evidence scenario."""
|
|
|
|
def render(self, signature: OOpSignature, output: StringIO) -> None:
|
|
"""Fail the test if a scenario unexpectedly requires a declaration card."""
|
|
|
|
del signature, output
|
|
raise AssertionError("No reference cards should be rendered by this fixture.")
|
|
|
|
|
|
class _BoundGenerator(SearchReplaceTokenGenerator):
|
|
"""Declare the production FileManager binding used by this renderer scenario."""
|
|
|
|
file_manager: FileManager | None
|
|
|
|
|
|
@pytest.fixture
|
|
def memory_root() -> MemoryPath:
|
|
"""Return one lexical project root backed by a fresh memory repository."""
|
|
|
|
return MemoryPath.from_repo(MemoryRepoExec())
|
|
|
|
|
|
def test_class_meta_get_source_uses_repo_exec(memory_root: MemoryPath) -> None:
|
|
"""Verify ClassMeta reads source through FileManager's execution boundary."""
|
|
|
|
file_manager = _file_manager(memory_root)
|
|
source_path = file_manager.src_root / "pkg" / "mod.py"
|
|
file_manager.repo_exec.write_text(source_path, "hello from memory")
|
|
meta = ClassMeta(
|
|
location="src/pkg/mod.py",
|
|
summary="",
|
|
relevance=[],
|
|
relevant_symbols={},
|
|
)
|
|
|
|
assert meta.get_source(file_manager) == "hello from memory"
|
|
|
|
|
|
def test_python_ast_maintainer_reads_through_repo_exec(memory_root: MemoryPath) -> None:
|
|
"""Verify Python discovery receives source text from the in-memory boundary."""
|
|
|
|
source = "def f():\n return 1\n"
|
|
source_path = memory_root / "src" / "any.py"
|
|
memory_root.repository.write_text(source_path, source)
|
|
discovery = PythonSourceDiscovery(
|
|
src_root=memory_root / "src",
|
|
test_root=memory_root / "tests",
|
|
repo_exec=memory_root.repository,
|
|
)
|
|
|
|
beats = discovery.searchable_beats(
|
|
str(source_path),
|
|
memory_root.repository.read_text(source_path),
|
|
)
|
|
|
|
assert any(getattr(beat, "kind", "") == "dependency_callable" for beat in beats)
|
|
|
|
|
|
def test_change_report_writer_uses_repo_exec_for_report(memory_root: MemoryPath) -> None:
|
|
"""Verify reports are persisted through the FileManager repository boundary."""
|
|
|
|
file_manager = _file_manager(memory_root)
|
|
writer = ChangeReportWriter(repo_root=memory_root)
|
|
|
|
path = writer.write(
|
|
original_job="job",
|
|
cot_tasks=[],
|
|
file_manager=file_manager,
|
|
final_summary="ok",
|
|
)
|
|
|
|
assert file_manager.repo_exec.exists(path)
|
|
assert "Final summary: ok" in file_manager.repo_exec.read_text(path)
|
|
|
|
|
|
def test_generator_prompt_reads_through_repo_exec(memory_root: MemoryPath) -> None:
|
|
"""Verify the direct-edit prompt excludes decision-only future guidance."""
|
|
|
|
file_manager = _file_manager(memory_root)
|
|
editable_path = file_manager.src_root / "x.py"
|
|
file_manager.repo_exec.write_text(editable_path, "FROM_REPO_EXEC\n")
|
|
generator = _BoundGenerator(
|
|
(StaticCompletionClient("stationary response"),),
|
|
memory_root,
|
|
memory_root.repository,
|
|
prompt_formatter_builder=MarkdownPromptFormatterBuilder(),
|
|
)
|
|
generator.file_manager = file_manager
|
|
request = AiderEditRequest(
|
|
authoritative_prompt="do it",
|
|
editable_files=[editable_path],
|
|
readonly_files=[],
|
|
future_guidance=(FutureGuidance(
|
|
origin_message_id="later-phase",
|
|
addressed_message_id="earlier-phase",
|
|
message="Revisit the selected source file.",
|
|
justification="The observed failure belongs to another operation.",
|
|
),),
|
|
)
|
|
renderer = ContextualFileRenderer(
|
|
file_manager=file_manager,
|
|
client=generator.clients[0],
|
|
editable_files=[editable_path],
|
|
readonly_files=[],
|
|
prompt="do it",
|
|
logger=logging.getLogger("repo-exec-io-test"),
|
|
prompt_formatter_builder=MarkdownPromptFormatterBuilder(),
|
|
oop_signature_extract=_NoCards(),
|
|
oop_signature_render=_NoCardRender(),
|
|
)
|
|
|
|
prompt = generator.render_prompt(
|
|
request,
|
|
editable_files=[editable_path],
|
|
readonly_files=[],
|
|
renderer=renderer,
|
|
)
|
|
|
|
assert "FROM_REPO_EXEC" in prompt
|
|
assert "REVISIT CONTEXT" not in prompt
|
|
assert "FUTURE GUIDANCE" not in prompt
|
|
assert "Revisit the selected source file." not in prompt
|
|
assert "The observed failure belongs to another operation." not in prompt
|