band-aider-pub/tests/band_aider/test_alpha_loop.py
2026-10-17 11:57:37 -07:00

2634 lines
101 KiB
Python

# SPDX-FileCopyrightText: 2026 Art Sea LLC
#
# SPDX-License-Identifier: AGPL-3.0-only
from __future__ import annotations
from band_aider.judgement.test_runner_strategy import TestRunOutcome
import logging
from collections.abc import Callable, Mapping, Sequence
from unittest.mock import Mock
import pytest
from band_aider.context.beta_workflow_story import (
AcceptedSourceStory,
DirectTestCandidate,
)
from band_aider.context.beta_workflow_judgement import BetaWorkflowJudgementService
from band_aider.context.cot_task_generator import CotTaskRecord
from band_aider.context.targeted_failure_owner_check import TargetedAlphaOrBetaRoutingDecision
from band_aider.context.memento import Memento
from band_aider.context import admitted_sources, provenance
from band_aider.context.provenance import FutureGuidance, Phase, Provenance
from band_aider.context.work_completion import WorkCompletion
from band_aider.context.working_backlog import BackLog as DurableBackLog
from band_aider.context.working_backlog import BacklogLane
from band_aider.judgement.alpha_blank_source_correction_check import (
AlphaBlankSourceCorrectionDecision,
)
from band_aider.judgement.bi_dependency_discovery_strategy import (
BiDependencyDiscoveryStrategy,
)
from band_aider.judgement.edit_request import AiderEditRequest
from band_aider.context.validation_receipts import ValidationReceipt, ValidationReceipts
from band_aider.judgement.code_editor import CodeEditor, CodeEditorResult, EditorRecord
from band_aider.judgement.repo_exec import current_repo_exec
from band_aider.judgement.code_edit_or_leave_it import (
CODE_EDIT_OR_LEAVE_IT_LEAVE_TOKEN,
CodeEditLeaveSummary,
)
from tests.band_aider_loop_impl import *
def test_alpha_remediation_keeps_historical_context_through_acceptance_rollback(tmp_path: Path) -> None:
"""A later remediation keeps prior inspection support in both editor and acceptance attempts."""
file_manager = _file_manager(tmp_path)
file_manager.repo_exec.write_text(file_manager.src_root / "snapshot.py", "def snapshot():\n return 1\n")
file_manager.resync()
source = file_manager.require_src("src/foo.py")
support = file_manager.require_src("src/snapshot.py")
test = file_manager.require_test("tests/test_foo.py")
file_manager.client = AlphaAcceptanceSequenceClient([
{"outcome": "fail", "reason": ["return 1"], "instruction": "Recheck the source contract."},
{"outcome": "pass", "reason": ["return 1"], "instruction": ""},
])
context = _alpha_context(file_manager, BackLog(), source, test)
graph = Provenance.open(repo_root=file_manager.repo_root, repo_exec=file_manager.repo_exec)
inspection = InspectionBacklog(source, "inspect", upstream_classes=[support], downstream_classes=[source])
graph.start(Phase.ALPHA, inspection)
testing = TestingBacklog(test, "test")
graph.track_provenance(inspection.message_id, Phase.BETA, testing)
remediation = FailedQAItem(source, "repair", downstream_classes=[source])
graph.track_provenance(testing.message_id, Phase.ALPHA, remediation)
worker = SequenceSourceWorker(
lambda request: CodeEditorResult([], "no changes", []),
lambda request: CodeEditorResult([], "no changes", []),
)
loop = AlphaLoop(gate=_alpha_gate(context), context=context, aider_worker=worker)
with provenance.bind(graph), provenance.item_scope(Phase.ALPHA, remediation):
before = (dict(graph.messages), graph.edges)
loop._run_source_work([source], {source.location: "repair"}, [], [], is_remediation=True)
assert admitted_sources.current_sources(file_manager) == [source, support]
assert (graph.messages, graph.edges) == before
assert len(worker.requests) == 2
assert all(support.resolve_path(file_manager) in request.readonly_files for request in worker.requests)
assert len(file_manager.client.acceptance_prompts) == 2
assert all("snapshot.py" in prompt for prompt in file_manager.client.acceptance_prompts)
def test_inspection_carrier_keeps_cot_out_of_aider_authority() -> None:
"""Inspection retains its CoT as a judgement input rather than an edit boundary."""
file_manager = _file_manager(Path("/testbed"))
file_manager.client = AlphaAcceptanceSequenceClient(
[{"outcome": "pass", "reason": ["return 2"], "instruction": ""}]
)
file_manager.repo_exec.write_text(
file_manager.src_root / "focus.py",
"def focus():\n return 1\n",
)
file_manager.repo_exec.write_text(
file_manager.src_root / "upstream.py",
"def upstream():\n return 1\n",
)
file_manager.repo_exec.write_text(
file_manager.src_root / "downstream.py",
"def downstream():\n return 1\n",
)
file_manager.prompt = "IMMUTABLE INSPECTION COT"
file_manager.resync()
source = file_manager.require_src("src/focus.py")
upstream = file_manager.require_src("src/upstream.py")
downstream = file_manager.require_src("src/downstream.py")
backlog = BackLog()
context = _alpha_context(
file_manager,
backlog,
source,
file_manager.require_test("tests/test_foo.py"),
)
context.backlog = InspectionScenarioContributorBacklog(
backlog=backlog,
file_manager=file_manager,
source=source,
readonly_sources=[upstream, downstream],
guidance="inspect the selected source",
)
item = InspectionBacklog(
focused_spotlight=source,
summary="inspect the focused source",
upstream_classes=[upstream],
downstream_classes=[downstream],
)
handled = AlphaLoop(
gate=_alpha_gate(context),
context=context,
aider_worker=InspectionBoundaryEditor(
editable=file_manager.source_path(source.location),
readonly=[
file_manager.source_path(downstream.location),
file_manager.source_path(upstream.location),
],
),
)._run_inspection_source_item(item)
assert [item.location for item in handled] == [source.location]
assert file_manager.repo_exec.read_text(file_manager.source_path(source.location)) == (
"def focus():\n return 2\n"
)
def test_inspection_no_work_closes_before_the_alpha_editor() -> None:
"""A grounded no-work inspection result must not create a blank edit assignment."""
file_manager = _file_manager(Path("/testbed"))
file_manager.repo_exec.write_text(
file_manager.src_root / "downstream.py",
"def downstream():\n return 1\n",
)
file_manager.prompt = "IMMUTABLE INSPECTION COT"
file_manager.resync()
source = file_manager.require_src("src/downstream.py")
backlog = BackLog()
context = _alpha_context(
file_manager,
backlog,
source,
file_manager.require_test("tests/test_foo.py"),
)
context.backlog = InspectionScenarioContributorBacklog(
backlog=backlog,
file_manager=file_manager,
source=source,
readonly_sources=[],
guidance="",
)
item = InspectionBacklog(focused_spotlight=source, summary="downstream inspection")
assert AlphaLoop(
gate=_alpha_gate(context),
context=context,
)._run_inspection_source_item(item) == []
def test_alpha_postflight_callback_runs_after_testing_staging_and_persist(
tmp_path: Path,
) -> None:
"""Verify Alpha reports the original item only after postflight persistence."""
file_manager = _file_manager(tmp_path)
file_manager.resync()
source = file_manager.require_src("src/foo.py")
test = file_manager.require_test("tests/test_foo.py")
backlog = BackLog()
item = InspectionBacklog(source, "inspect source", downstream_classes=[source])
backlog.queue_inspection(item)
file_manager.src_dirty[source.location] = True
context = _alpha_context(file_manager, backlog, source, test)
context.backlog = ScenarioContributorBacklog(
backlog=backlog,
file_manager=file_manager,
tests=[test],
candidates=[
DirectTestCandidate(
focused_test=test,
targeted_result=TestRunResult("pytest", TestRunOutcome.Success, 0, "passed"),
)
],
routed_by_test={},
)
claimed = context.move_inbound_from_backlog()
assert claimed is not None
def after_persist(received_sources: tuple[ClassMeta, ...]) -> None:
"""Validate that the durable Alpha state exists before the completion callback."""
assert received_sources == (source,)
assert len(context.outbound_queue) == 1
assert file_manager.repo_exec.list_files(
file_manager.repo_root / ".band-aider-chkpts",
["*.pkl"],
recursive=False,
)
context.alpha_postflight_callback = after_persist
AlphaLoop(gate=_alpha_gate(context), context=context)._stage_postflight_alpha_results(
claimed.value,
[source],
)
assert context.outbound_queue[0].value.focused_spotlight is test
def _failed_item(
source: ClassMeta,
test: ClassMeta,
*,
summary: str = "repair source",
upstream_sources: list[ClassMeta] | None = None,
stack_trace: list[str] | None = None,
error_message: str = "failed",
raw_output: str = "",
) -> FailedQAItem:
"""Create one remediation item in the new topology shape."""
return FailedQAItem(
focused_spotlight=source,
summary=summary,
upstream_classes=list(upstream_sources or []),
downstream_classes=[source],
failed_test=FailingTest(
test=test,
stack_trace=stack_trace or ["failed"],
error_message=error_message,
raw_output=raw_output,
),
)
class ScenarioContributorBacklog(ContributorBacklog):
"""Contributor backlog with fixed direct observations for one isolated scenario."""
def __init__(
self,
*,
backlog: DurableBackLog,
file_manager: FileManager,
tests: list[ClassMeta],
candidates: list[DirectTestCandidate],
routed_by_test: Mapping[str, Sequence[FailedQAItem | TestingBacklog]],
use_real_routes: bool = False,
) -> None:
"""Bind stationary observations to the real ContributorBacklog selection workflow."""
super().__init__(
backlog=backlog,
get_tests=lambda _sources: list(tests),
file_manager=file_manager,
parser=file_manager.parser,
language_strategy_set=_language_strategy_set(
file_manager,
runner=PassingRunner(),
),
)
self._scenario_candidates = list(candidates)
self._routed_by_test = {
location: list(items)
for location, items in routed_by_test.items()
}
self._use_real_routes = use_real_routes
def _direct_test_candidates(
self,
story: AcceptedSourceStory,
test_files: list[ClassMeta],
) -> list[DirectTestCandidate]:
"""Return the scenario's complete direct-test census for its accepted source."""
del story
expected_locations = [candidate.focused_test.location for candidate in self._scenario_candidates]
actual_locations = [test.location for test in test_files]
if actual_locations != expected_locations:
raise AssertionError(
f"Expected direct-test census {expected_locations}, received {actual_locations}."
)
return list(self._scenario_candidates)
def _route_failed_direct_candidate(
self,
story: AcceptedSourceStory,
candidate: DirectTestCandidate,
assignment: str,
*,
regression_star: bool,
future_guidance: tuple[FutureGuidance, ...] = (),
logger: logging.Logger | None = None,
) -> list[FailedQAItem | TestingBacklog]:
"""Return the fixed owner-specific work for one observed failed test."""
if self._use_real_routes:
return super()._route_failed_direct_candidate(
story,
candidate,
assignment,
regression_star=regression_star,
future_guidance=future_guidance,
logger=logger,
)
del story, assignment, regression_star, future_guidance, logger
routed = self._routed_by_test.get(candidate.focused_test.location)
if routed is None:
raise AssertionError(
f"No route is permitted for {candidate.focused_test.location}."
)
return list(routed)
def _failed_candidate(test: ClassMeta, result: TestRunResult) -> DirectTestCandidate:
"""Build one complete failed direct observation from stationary runner evidence."""
return DirectTestCandidate(
focused_test=test,
targeted_result=result,
failing=FailingTest(
test=test,
stack_trace=list(result.stack_trace),
error_message=f"Test command failed with return code {result.returncode}.",
raw_output=result.output,
),
)
def _failed_test_of(item: FailedQAItem) -> FailingTest:
"""Return required remediation evidence while making the scenario invariant explicit."""
if item.failed_test is None:
raise AssertionError("A remediation scenario must preserve its failed-test evidence.")
return item.failed_test
def _focused_class_of(item: InspectionBacklog) -> ClassMeta:
"""Return the required focus of one outbound item in an Alpha scenario."""
if item.focused_spotlight is None:
raise AssertionError("An Alpha outbound scenario requires a focused class.")
return item.focused_spotlight
class InspectionScenarioContributorBacklog(ContributorBacklog):
"""Contributor backlog that supplies one fixed inspection conclusion."""
def __init__(
self,
*,
backlog: DurableBackLog,
file_manager: FileManager,
source: ClassMeta,
readonly_sources: list[ClassMeta],
guidance: str,
) -> None:
"""Bind fixed inspection guidance to the real Alpha-facing backlog surface."""
super().__init__(
backlog=backlog,
get_tests=lambda _sources: [],
file_manager=file_manager,
parser=file_manager.parser,
language_strategy_set=_language_strategy_set(
file_manager,
runner=PassingRunner(),
),
)
self._expected_source = source
self._expected_source_path = file_manager.source_path(source.location)
self._expected_readonly_sources = list(readonly_sources)
self._guidance = guidance
def inspection_work_needed_for(
self,
class_meta: ClassMeta,
prompt: str,
inspection_items: list[InspectionBacklog],
*,
request: AiderEditRequest,
readonly_sources: list[ClassMeta],
) -> str:
"""Validate Alpha's inspection boundary and return stationary work guidance."""
assert class_meta is self._expected_source
assert prompt == "IMMUTABLE INSPECTION COT"
assert len(inspection_items) == 1
assert request.authoritative_prompt == ""
assert request.editable_files == [self._expected_source_path]
assert readonly_sources == self._expected_readonly_sources
return self._guidance
class InspectionBoundaryEditor(CodeEditor):
"""Concrete source editor that validates the final inspection edit boundary."""
def __init__(
self,
*,
editable: Path,
readonly: list[Path],
) -> None:
"""Store the sole legal source-edit scope for this scenario."""
self._editable = editable
self._readonly = list(readonly)
self.new_files_callback: Callable[..., bool] | None = None
@property
def inner(self) -> CodeEditor | None:
"""Expose this deterministic editor as an undecorated worker."""
return None
def edit(
self,
request: AiderEditRequest,
logger: logging.Logger | None = None,
) -> CodeEditorResult:
"""Assert the semantic boundary and return one stationary source edit."""
del logger
assert request.authoritative_prompt.startswith("You are Alpha.")
assert "IMMUTABLE INSPECTION COT" not in request.authoritative_prompt
assert request.editable_files == [self._editable]
assert request.readonly_files == self._readonly
current_repo_exec().write_text(self._editable, "def focus():\n return 2\n")
return CodeEditorResult([self._editable], "inspect the selected source", [])
class SourceThenTestRoutingService(BetaWorkflowJudgementService):
"""Real routing-service subtype with one source decision followed by test decisions."""
def __init__(self) -> None:
"""Set the finite stationary routing sequence required by the retry scenario."""
super().__init__()
self._outcomes = ["source", "test", "test"]
def route_targeted_failure(
self,
*,
file_manager: FileManager,
story: TargetedFailureStory,
previous_cot_records: tuple[CotTaskRecord, ...] = (),
logger: logging.Logger | None = None,
) -> TargetedAlphaOrBetaRoutingDecision:
"""Return the next configured ownership decision without endpoint activity."""
# An exhausted script is an invalid test setup, not another routing decision.
if not self._outcomes:
raise AssertionError("The routing scenario received an unexpected extra decision.")
outcome = self._outcomes.pop(0)
assert story.focused_test is not None
decision = TargetedAlphaOrBetaRoutingDecision(
client=file_manager.client,
file_manager=file_manager,
request=AiderEditRequest(
authoritative_prompt="Route the stationary failure.",
editable_files=[file_manager.test_path(story.focused_test.location)],
readonly_files=list(map(lambda source: source.resolve_path(file_manager), story.candidate_sources)),
previous_cot_records=previous_cot_records,
),
story=story,
logger=logger,
)
decision.outcome = outcome
decision.reason = f"Stationary test scenario selected {outcome} ownership."
return decision
class RoutingClientThatReturnsTest(FakeClient):
"""Completion client that establishes ordinary Alpha-postflight test ownership."""
def complete(self, prompt: str, logger: logging.Logger | None = None) -> str:
"""Return a grounded test route and defer unrelated prompts to the shared fake."""
if "Judge Alpha-or-Beta routing for this targeted test failure." in prompt:
return json.dumps(
{
"outcome": "test",
"reason": "The observed failure belongs to the selected test assignment.",
"additional": {
"test_work_needed": "Repair the selected test assertion.",
},
}
)
return super().complete(prompt, logger)
class AlphaBlankRepairClient(FakeClient):
"""AlphaBlankRepairClient serves one source preflight response and one correction response."""
def __init__(
self,
*,
preflight_paths: list[str],
correction_response: str,
) -> None:
"""Store deterministic responses and capture prompt counts."""
super().__init__()
self.preflight_paths = preflight_paths
self.correction_response = correction_response
self.preflight_calls = 0
self.correction_calls = 0
def complete(self, prompt: str, logger: logging.Logger | None = None) -> str:
"""Return the configured preflight or repair response for Alpha prompts."""
if "Return exactly one JSON array of repo-relative source paths." in prompt:
self.preflight_calls += 1
return json.dumps(self.preflight_paths)
if "Infer the missing Alpha source-edit step." in prompt:
self.correction_calls += 1
return self.correction_response
return super().complete(prompt, logger)
class BlankThenFilledSourceWorker(CodeEditor):
"""BlankThenFilledSourceWorker leaves one preflight source blank, then fills it on retry."""
def __init__(self, backlog: DurableBackLog | None = None) -> None:
"""Store request history and optional backlog assertions."""
self.backlog = backlog
self.requests: list[AiderEditRequest] = []
self.new_files_callback: Callable[..., bool] | None = None
@property
def inner(self) -> CodeEditor | None:
"""Expose this nominal fake as an undecorated editor."""
return None
def edit(
self,
request: AiderEditRequest,
logger: logging.Logger | None = None,
) -> CodeEditorResult:
"""Perform one blank first pass and one repaired second pass."""
del logger
self.requests.append(request)
if len(self.requests) == 1:
current_repo_exec().write_text(
request.editable_files[0],
"def foo():\n return 2\n",
)
return CodeEditorResult([request.editable_files[0]], "first source edit", [])
if self.backlog is not None:
assert not self.backlog.testing
current_repo_exec().write_text(
request.editable_files[1],
"class Extracted:\n pass\n",
)
return CodeEditorResult([request.editable_files[1]], "repaired blank source", [])
class SpyCorrectionAlphaLoop(AlphaLoop):
"""SpyCorrectionAlphaLoop records specialized blank-source correction checks."""
def _blank_source_correction_check(
self,
*,
editable_sources: list[ClassMeta],
readonly_sources: list[ClassMeta],
readonly_tests: list[ClassMeta],
blank_sources: list[ClassMeta],
work_needed_by_location: dict[str, str],
previous_summary: str,
request: AiderEditRequest,
) -> AlphaBlankSourceCorrectionDecision:
"""Record the blank-source correction check before returning it."""
if not hasattr(self, "correction_checks"):
self.correction_checks = []
check = super()._blank_source_correction_check(
editable_sources=editable_sources,
readonly_sources=readonly_sources,
readonly_tests=readonly_tests,
blank_sources=blank_sources,
work_needed_by_location=work_needed_by_location,
previous_summary=previous_summary,
request=request,
)
self.correction_checks.append(check)
return check
class AlphaAcceptanceSequenceClient(FakeClient):
"""AlphaAcceptanceSequenceClient returns configured acceptance outcomes in order."""
def __init__(
self,
outcomes: list[dict[str, object] | Exception],
*,
preflight_paths: list[str] | None = None,
) -> None:
"""Store scripted acceptance responses and optional preflight file requests."""
super().__init__()
self.outcomes = list(outcomes)
self.preflight_paths = list(preflight_paths or [])
self.acceptance_prompts: list[str] = []
def complete(self, prompt: str, logger: logging.Logger | None = None) -> str:
"""Return configured preflight and Alpha acceptance responses."""
if "Return exactly one JSON array of repo-relative source paths." in prompt:
return json.dumps(self.preflight_paths)
if (
"ACCEPTANCE ROLE" in prompt
and "ALPHA EDIT ACCEPTANCE" in prompt
and "DECISION RULE" in prompt
):
self.acceptance_prompts.append(prompt)
outcome = self.outcomes.pop(0) if self.outcomes else {
"outcome": "pass",
"reason": ["def foo():"],
"instruction": "",
}
if isinstance(outcome, Exception):
raise outcome
return json.dumps(outcome)
return super().complete(prompt, logger)
class SequenceSourceWorker(CodeEditor):
"""SequenceSourceWorker runs one configured mutation per edit request."""
def __init__(self, *actions: Callable[[AiderEditRequest], CodeEditorResult]) -> None:
self.actions = list(actions)
self.requests: list[AiderEditRequest] = []
self.new_files_callback: Callable[..., bool] | None = None
@property
def inner(self) -> CodeEditor | None:
"""Expose this nominal fake as an undecorated editor."""
return None
def edit(
self,
request: AiderEditRequest,
logger: logging.Logger | None = None,
) -> CodeEditorResult:
"""Record the request and execute the next configured action."""
del logger
self.requests.append(request)
action = self.actions.pop(0)
return action(request)
def test_alpha_request_includes_pinned_context(tmp_path: Path) -> None:
"""Verify Alpha passes pinned summaries through AiderEditRequest."""
file_manager = _file_manager(tmp_path)
file_manager.resync()
source = file_manager.require_src("src/foo.py")
test = file_manager.require_test("tests/test_foo.py")
backlog = BackLog()
context = _alpha_context(file_manager, backlog, source, test)
context.pinned_context = [
"Interface summary:\nRoot interface summary.\n\nDiff summary:\nRoot diff summary."
]
worker = NoChangeWorker()
AlphaLoop(gate=_alpha_gate(context), context=context, aider_worker=worker).run()
assert worker.requests[0].pinned_context == context.pinned_context
def test_alpha_consumes_inspection_before_source_work(
tmp_path: Path,
) -> None:
"""Verify Alpha consumes inspection before creating downstream testing."""
file_manager = _file_manager(tmp_path)
file_manager.resync()
source = file_manager.require_src("src/foo.py")
test = file_manager.require_test("tests/test_foo.py")
backlog = BackLog()
inspection = InspectionBacklog(source, "inspect foo")
backlog.queue_inspection(inspection)
context = _alpha_context(file_manager, backlog, source, test)
graph = Provenance.open(repo_root=file_manager.repo_root, repo_exec=file_manager.repo_exec)
loop = HandlingAlphaLoop(gate=_alpha_gate(context), context=context, provenance=graph)
loop.run()
assert loop.work_needed_by_location[source.location]
assert not backlog.inspection
assert not backlog.testing
def test_alpha_consumes_remediation_before_source_work(
tmp_path: Path,
) -> None:
"""Verify Alpha consumes remediation before creating downstream testing."""
file_manager = _file_manager(tmp_path)
file_manager.resync()
source = file_manager.require_src("src/foo.py")
test = file_manager.require_test("tests/test_foo.py")
backlog = BackLog()
remediation = _failed_item(source, test)
backlog.queue_remediation(remediation)
context = _alpha_context(file_manager, backlog, source, test)
graph = Provenance.open(repo_root=file_manager.repo_root, repo_exec=file_manager.repo_exec)
loop = HandlingAlphaLoop(gate=_alpha_gate(context), context=context, provenance=graph)
loop.run()
assert not backlog.testing
assert not backlog.remediation
def test_alpha_source_remediation_request_passes_structured_failure_evidence(
tmp_path: Path,
) -> None:
"""Verify Alpha forwards remediation failure evidence into AiderEditRequest."""
file_manager = _file_manager(tmp_path)
file_manager.repo_exec.write_text(
file_manager.src_root / "helper.py",
"def helper():\n return 1\n",
)
file_manager.repo_exec.write_text(
file_manager.test_root / "support.py",
"def support():\n return 1\n",
)
file_manager.resync()
source = file_manager.require_src("src/foo.py")
readonly_source = file_manager.require_src("src/helper.py")
test = file_manager.require_test("tests/test_foo.py")
support = file_manager.require_test("tests/support.py")
backlog = BackLog(
remediation=[
_failed_item(
source,
test,
upstream_sources=[readonly_source],
stack_trace=[
"tests/test_foo.py:4: AssertionError: expected 2 but got 1",
"src/foo.py:1: in foo",
],
error_message="AssertionError: expected 2 but got 1",
raw_output=(
"tests/test_foo.py:4: AssertionError: expected 2 but got 1\n"
"src/foo.py:1: in foo\n"
),
)
]
)
backlog.remediation[0].failure_context_sources = [support, test, readonly_source]
context = _alpha_context(file_manager, backlog, source, test)
worker = RecordingSourceWorker([])
AlphaLoop(gate=_alpha_gate(context), context=context, aider_worker=worker).run()
assert worker.requests[0].is_remediation
assert worker.requests[0].remediation_originator_test == test
assert worker.requests[0].failure_error_message == "AssertionError: expected 2 but got 1"
assert worker.requests[0].failure_stack_trace == [
"tests/test_foo.py:4: AssertionError: expected 2 but got 1",
"src/foo.py:1: in foo",
]
assert worker.requests[0].failure_raw_output == (
"tests/test_foo.py:4: AssertionError: expected 2 but got 1\n"
"src/foo.py:1: in foo\n"
)
assert worker.requests[0].readonly_files == [
file_manager.src_root / "helper.py",
file_manager.test_root / "support.py",
file_manager.test_root / "test_foo.py",
]
assert worker.requests[0].backlog_guidance == ["src/foo.py: Refresh metadata."]
def test_alpha_readonly_paths_preserve_duplicate_topology_context(tmp_path: Path) -> None:
"""Verify Alpha keeps duplicate readonly topology paths when upstream/downstream repeat them."""
file_manager = _file_manager(tmp_path)
file_manager.repo_exec.write_text(
file_manager.src_root / "helper.py",
"def helper():\n return 1\n",
)
file_manager.resync()
source = file_manager.require_src("src/foo.py")
helper = file_manager.require_src("src/helper.py")
test = file_manager.require_test("tests/test_foo.py")
context = _alpha_context(file_manager, BackLog(), source, test)
loop = AlphaLoop(gate=_alpha_gate(context), context=context)
readonly = loop._readonly_paths(
[source],
[helper, helper],
[test, test],
)
assert readonly == [
file_manager.src_root / "helper.py",
file_manager.src_root / "helper.py",
file_manager.test_root / "test_foo.py",
file_manager.test_root / "test_foo.py",
]
def test_alpha_source_remediation_skips_preflight_new_file_requests(
tmp_path: Path,
) -> None:
"""Verify remediation work skips Alpha preflight and never requests new files."""
file_manager = _file_manager(tmp_path)
file_manager.client = AlphaPreflightClient({"src/foo.py": ["src/pkg/extracted_class.py"]})
file_manager.resync()
source = file_manager.require_src("src/foo.py")
test = file_manager.require_test("tests/test_foo.py")
backlog = BackLog(remediation=[_failed_item(source, test)])
requested_payloads: list[dict[object, str]] = []
def new_files_callback(files=None, **kwargs):
payload = {}
if files:
payload.update(files)
payload.update(kwargs)
requested_payloads.append(payload)
return True
context = _alpha_context(file_manager, backlog, source, test)
context.new_files_callback = new_files_callback
worker = RecordingSourceWorker([])
AlphaLoop(gate=_alpha_gate(context), context=context, aider_worker=worker).run()
assert worker.requests
assert file_manager.client.prompts == []
assert requested_payloads == []
assert not file_manager.repo_exec.exists(
file_manager.src_root / "pkg/extracted_class.py"
)
def test_alpha_worker_edit_refreshes_source_and_creates_testing_backlog(
tmp_path: Path,
) -> None:
"""Verify Alpha calls AiderWorker, refreshes changed source files, and routes testing backlog."""
file_manager = _file_manager(tmp_path)
file_manager.resync()
source = file_manager.require_src("src/foo.py")
test = file_manager.require_test("tests/test_foo.py")
backlog = BackLog()
context = _alpha_context(file_manager, backlog, source, test)
worker = EditingSourceWorker()
AlphaLoop(gate=_alpha_gate(context), context=context, aider_worker=worker).run()
assert worker.requests
assert file_manager.src_dirty[source.location] is True
assert [_focused_class_of(item).location for item in backlog.testing] == ["tests/test_foo.py"]
def test_alpha_worker_no_changed_paths_creates_no_testing_backlog(
tmp_path: Path,
) -> None:
"""Verify Alpha does not fake handled sources when Aider reports no changed paths."""
file_manager = _file_manager(tmp_path)
file_manager.resync()
source = file_manager.require_src("src/foo.py")
test = file_manager.require_test("tests/test_foo.py")
backlog = BackLog()
context = _alpha_context(file_manager, backlog, source, test)
worker = NoChangeWorker()
summary = AlphaLoop(gate=_alpha_gate(context), context=context, aider_worker=worker).run()
assert worker.requests
assert "no changes" in summary
assert not backlog.testing
def test_alpha_no_memento_diff_still_runs_acceptance_and_skips_testing_backlog(
tmp_path: Path,
) -> None:
"""Verify no-diff Alpha acceptance consumes mementos without creating Beta work."""
file_manager = _file_manager(tmp_path)
client = AlphaAcceptanceSequenceClient(
[{"outcome": "pass", "reason": ["return 1"], "instruction": ""}]
)
file_manager.client = client
file_manager.resync()
source = file_manager.require_src("src/foo.py")
test = file_manager.require_test("tests/test_foo.py")
backlog = BackLog()
context = _alpha_context(file_manager, backlog, source, test)
worker = NoChangeWorker()
summary = AlphaLoop(gate=_alpha_gate(context), context=context, aider_worker=worker).run()
assert worker.requests
assert client.acceptance_prompts
assert "no changes" in summary
assert not file_manager.repo_exec.exists(
Memento(file_manager.source_path(source.location), file_manager.repo_exec).path
)
assert not backlog.testing
def test_alpha_preflight_runs_once_per_source_and_includes_only_that_source_content(
tmp_path: Path,
) -> None:
"""Verify Alpha preflights each source separately with full content for that source only."""
file_manager = _file_manager(tmp_path)
file_manager.repo_exec.write_text(
file_manager.src_root / "bar.py",
"class Bar:\n pass\n",
)
client = AlphaPreflightClient(
{
"src/foo.py": [],
"src/bar.py": [],
}
)
file_manager.client = client
file_manager.resync()
foo = file_manager.require_src("src/foo.py")
bar = file_manager.require_src("src/bar.py")
backlog = BackLog()
test = file_manager.require_test("tests/test_foo.py")
context = _alpha_context(file_manager, backlog, foo, test)
loop = AlphaLoop(gate=_alpha_gate(context), context=context)
graph = Provenance.open(repo_root=file_manager.repo_root, repo_exec=file_manager.repo_exec)
item = InspectionBacklog(foo, "inspect foo")
prior_alpha = graph.start(Phase.ALPHA, InspectionBacklog(foo, "prior source work"))
beta = graph.track_provenance(
prior_alpha,
Phase.BETA,
TestingBacklog(test, "test the prior source work"),
)
graph.track_provenance(beta, Phase.ALPHA, item)
with provenance.bind(graph):
with provenance.item_scope(Phase.ALPHA, item):
assert loop._preflight_new_source_files(
[foo, bar],
{
"src/foo.py": "inspect foo",
"src/bar.py": "inspect bar",
},
) == []
assert len(client.prompts) == 2
first, second = client.prompts
assert "====== BEGIN LOCATION ======\nsrc/foo.py\n====== END LOCATION ======" in first
assert "def foo():\n return 1\n" in first
assert "BEGIN SUMMARY" not in first
assert "BEGIN RELEVANCE" not in first
assert "BEGIN RELEVANT SYMBOLS" not in first
assert "class Bar" not in first
assert "====== BEGIN LOCATION ======\nsrc/bar.py\n====== END LOCATION ======" in second
assert "class Bar:\n pass\n" in second
assert "def foo" not in second
def test_alpha_preflight_concatenates_dedupes_and_keeps_multiple_paths_per_source(
tmp_path: Path,
) -> None:
"""Verify Alpha merges ordered per-source responses without collapsing multi-file requests."""
file_manager = _file_manager(tmp_path)
file_manager.repo_exec.write_text(
file_manager.src_root / "bar.py",
"class Bar:\n pass\n",
)
file_manager.client = AlphaPreflightClient(
{
"src/foo.py": [
"src/pkg/model_response_error.py",
"src/pkg/malformed_model_response_error.py",
],
"src/bar.py": [
"src/pkg/malformed_model_response_error.py",
"src/pkg/openai_compatible_client.py",
],
}
)
file_manager.resync()
foo = file_manager.require_src("src/foo.py")
bar = file_manager.require_src("src/bar.py")
backlog = BackLog()
test = file_manager.require_test("tests/test_foo.py")
context = _alpha_context(file_manager, backlog, foo, test)
requested_payloads: list[dict[Path, str]] = []
def new_files_callback(files=None, **kwargs):
payload = {}
if files:
payload.update(files)
payload.update(kwargs)
requested_payloads.append(payload)
for file_name, reason in payload.items():
path = file_manager.source_path(str(file_name))
file_manager.repo_exec.write_text(path, "")
file_manager.upsert_src(
ClassMeta(
location=file_manager.source_location(path),
summary=reason,
relevance=[reason],
relevant_symbols={},
),
reason,
)
return True
context.new_files_callback = new_files_callback
loop = AlphaLoop(gate=_alpha_gate(context), context=context)
requested = loop._preflight_new_source_files(
[foo, bar],
{
"src/foo.py": "extract foo classes into separate files",
"src/bar.py": "extract bar client into separate file",
},
)
assert [source.location for source in requested] == [
"src/pkg/model_response_error.py",
"src/pkg/malformed_model_response_error.py",
"src/pkg/openai_compatible_client.py",
]
assert requested_payloads == [
{
Path("src/pkg/model_response_error.py"): "Required by Alpha source preflight for src/foo.py.",
Path("src/pkg/malformed_model_response_error.py"): "Required by Alpha source preflight for src/foo.py.",
Path("src/pkg/openai_compatible_client.py"): "Required by Alpha source preflight for src/bar.py.",
}
]
def test_alpha_preflight_created_source_becomes_editable_before_aider_runs(
tmp_path: Path,
) -> None:
"""Verify Alpha preflight provisions extraction targets before the edit request is built."""
file_manager = _file_manager(tmp_path)
file_manager.client = AlphaPreflightClient(
{"src/foo.py": ["src/pkg/extracted_class.py"]}
)
file_manager.resync()
source = file_manager.require_src("src/foo.py")
test = file_manager.require_test("tests/test_foo.py")
backlog = BackLog()
context = _alpha_context(file_manager, backlog, source, test)
events: list[tuple[str, object]] = []
def new_files_callback(files=None, **kwargs):
payload = {}
if files:
payload.update(files)
payload.update(kwargs)
events.append(("callback", dict(payload)))
for file_name, reason in payload.items():
path = file_manager.source_path(str(file_name))
file_manager.repo_exec.write_text(path, "")
file_manager.upsert_src(
ClassMeta(
location=file_manager.source_location(path),
summary=reason,
relevance=[reason],
relevant_symbols={},
),
reason,
)
return True
context.new_files_callback = new_files_callback
def fill_preflight_source(request: AiderEditRequest) -> CodeEditorResult:
"""Fill the provisioned source after the callback has installed its blank file."""
assert not request.is_remediation
assert request.remediation_originator_test is None
events.append(("edit", list(request.editable_files)))
file_manager.repo_exec.write_text(
request.editable_files[1],
"class ExtractedClass:\n pass\n",
)
return CodeEditorResult([request.editable_files[1]], "filled preflight source", [])
worker = SequenceSourceWorker(fill_preflight_source)
loop = AlphaLoop(gate=_alpha_gate(context), context=context, aider_worker=worker)
loop._run_source_work(
[source],
{"src/foo.py": "extract Foo into its own file"},
readonly_sources=[],
readonly_tests=[],
is_remediation=False,
)
requested_path = file_manager.src_root / "pkg/extracted_class.py"
assert events[0][0] == "callback"
assert events[1][0] == "edit"
assert file_manager.require_src("src/pkg/extracted_class.py") is not None
assert worker.requests[0].editable_files == [
file_manager.src_root / "foo.py",
requested_path,
]
def test_alpha_blank_preflight_source_triggers_correction_retry_with_same_scope(
tmp_path: Path,
) -> None:
"""Verify Alpha retries blank preflight sources with the same scope and repair guidance."""
file_manager = _file_manager(tmp_path)
file_manager.client = AlphaBlankRepairClient(
preflight_paths=["src/pkg/extracted_class.py"],
correction_response=json.dumps(
{
"instruction": "Populate src/pkg/extracted_class.py with the extracted implementation.",
"reason": "The new extraction target stayed blank after the first edit.",
"blank_files": ["src/pkg/extracted_class.py"],
}
),
)
file_manager.repo_exec.write_text(
file_manager.src_root / "helper.py",
"def helper():\n return 3\n",
)
file_manager.repo_exec.write_text(
file_manager.test_root / "test_helper.py",
"def test_helper():\n assert True\n",
)
file_manager.resync()
source = file_manager.require_src("src/foo.py")
readonly_source = file_manager.require_src("src/helper.py")
readonly_test = file_manager.require_test("tests/test_helper.py")
backlog = BackLog()
test = file_manager.require_test("tests/test_foo.py")
def new_files_callback(files=None, **kwargs):
payload = {}
if files:
payload.update(files)
payload.update(kwargs)
for file_name, reason in payload.items():
path = file_manager.source_path(str(file_name))
file_manager.repo_exec.write_text(path, "")
file_manager.upsert_src(
ClassMeta(
location=file_manager.source_location(path),
summary=reason,
relevance=[reason],
relevant_symbols={},
),
reason,
)
return True
context = _alpha_context(file_manager, backlog, source, test)
context.new_files_callback = new_files_callback
worker = BlankThenFilledSourceWorker()
loop = SpyCorrectionAlphaLoop(
gate=_alpha_gate(context),
context=context,
aider_worker=worker,
)
graph = Provenance.open(repo_root=file_manager.repo_root, repo_exec=file_manager.repo_exec)
item = InspectionBacklog(source, "extract Foo")
graph.start(Phase.ALPHA, item)
with provenance.bind(graph):
with provenance.item_scope(Phase.ALPHA, item):
loop._run_source_work(
[source],
{"src/foo.py": "extract Foo into src/pkg/extracted_class.py"},
readonly_sources=[readonly_source],
readonly_tests=[readonly_test],
is_remediation=False,
)
assert len(worker.requests) == 2
assert file_manager.client.preflight_calls == 1
assert file_manager.client.correction_calls == 1
assert len(loop.correction_checks) == 2
assert isinstance(loop.correction_checks[0], AlphaBlankSourceCorrectionDecision)
assert loop.correction_checks[0].outcome == "fail"
assert loop.correction_checks[1].outcome == "pass"
assert worker.requests[1].editable_files == worker.requests[0].editable_files
assert worker.requests[1].readonly_files == worker.requests[0].readonly_files
assert worker.requests[1].backlog_guidance[:-1] == worker.requests[0].backlog_guidance
assert "ALPHA EDIT ACCEPTANCE FAILED" in worker.requests[1].backlog_guidance[-1]
assert "Populate src/pkg/extracted_class.py with the extracted implementation." in (
worker.requests[1].backlog_guidance[-1]
)
assert file_manager.repo_exec.exists(
file_manager.src_root / "pkg/extracted_class.py"
)
def test_alpha_waits_for_nonblank_preflight_source_before_creating_beta_backlog(
tmp_path: Path,
) -> None:
"""Verify Alpha delays Beta backlog creation until preflight-created source files are nonblank."""
file_manager = _file_manager(tmp_path)
file_manager.client = AlphaBlankRepairClient(
preflight_paths=["src/pkg/extracted_class.py"],
correction_response=json.dumps(
{
"instruction": "Populate src/pkg/extracted_class.py with the extracted implementation.",
"reason": "The extraction target is still blank.",
"blank_files": ["src/pkg/extracted_class.py"],
}
),
)
file_manager.resync()
source = file_manager.require_src("src/foo.py")
test = file_manager.require_test("tests/test_foo.py")
backlog = BackLog()
def new_files_callback(files=None, **kwargs):
payload = {}
if files:
payload.update(files)
payload.update(kwargs)
for file_name, reason in payload.items():
path = file_manager.source_path(str(file_name))
file_manager.repo_exec.write_text(path, "")
file_manager.upsert_src(
ClassMeta(
location=file_manager.source_location(path),
summary=reason,
relevance=[reason],
relevant_symbols={},
),
reason,
)
return True
context = _alpha_context(file_manager, backlog, source, test)
context.new_files_callback = new_files_callback
worker = BlankThenFilledSourceWorker(backlog)
summary = AlphaLoop(
gate=_alpha_gate(context),
context=context,
aider_worker=worker,
).run()
assert len(worker.requests) == 2
assert file_manager.repo_exec.read_text(
file_manager.src_root / "pkg/extracted_class.py"
) == "class Extracted:\n pass\n"
assert file_manager.src_dirty["src/pkg/extracted_class.py"] is True
assert backlog.testing
assert all(
source.location in [downstream.location for downstream in item.downstream_classes]
for item in backlog.testing
)
assert all(item.downstream_classes[0].location == "src/pkg/extracted_class.py" for item in backlog.testing)
assert "Alpha completed source work for 1 file(s)." in summary
def test_alpha_rolls_back_existing_memento_before_preflight(tmp_path: Path) -> None:
"""Verify stale source mementos are rolled back before preflight and editing."""
file_manager = _file_manager(tmp_path)
file_manager.client = AlphaAcceptanceSequenceClient(
[{"outcome": "pass", "reason": ["def foo():"], "instruction": ""}]
)
file_manager.resync()
source = file_manager.require_src("src/foo.py")
source_path = file_manager.src_root / "foo.py"
file_manager.repo_exec.write_text(source_path, "corrupted\n")
file_manager.repo_exec.write_text(
Memento(source_path, file_manager.repo_exec).path,
"def foo():\n return 1\n",
)
def action(request):
assert file_manager.repo_exec.read_text(
request.editable_files[0]
) == "def foo():\n return 1\n"
file_manager.repo_exec.write_text(
request.editable_files[0],
"def foo():\n return 2\n",
)
return CodeEditorResult([request.editable_files[0]], "accepted edit", [])
context = _alpha_context(file_manager, BackLog(), source, file_manager.require_test("tests/test_foo.py"))
worker = SequenceSourceWorker(action)
handled, _ = AlphaLoop(gate=_alpha_gate(context), context=context, aider_worker=worker)._run_source_work(
[source],
{"src/foo.py": "change foo"},
readonly_sources=[],
readonly_tests=[],
is_remediation=False,
)
assert [item.location for item in handled] == ["src/foo.py"]
assert not file_manager.repo_exec.exists(
Memento(source_path, file_manager.repo_exec).path
)
def test_alpha_preflight_memento_rolls_back_before_editor_and_source_remains(tmp_path: Path) -> None:
"""Verify post-preflight recovery restores stale preflight mementos without deleting the source."""
file_manager = _file_manager(tmp_path)
file_manager.client = AlphaAcceptanceSequenceClient(
[{"outcome": "pass", "reason": ["def foo():"], "instruction": ""}],
preflight_paths=["src/pkg/extracted.py"],
)
file_manager.resync()
source = file_manager.require_src("src/foo.py")
def new_files_callback(files=None, **kwargs):
payload = {}
if files:
payload.update(files)
payload.update(kwargs)
for file_name, reason in payload.items():
path = file_manager.source_path(str(file_name))
file_manager.repo_exec.write_text(path, "corrupted preflight\n")
memento = Memento(path, file_manager.repo_exec)
file_manager.repo_exec.write_text(memento.path, "")
file_manager.repo_exec.write_text(
Memento(path, file_manager.repo_exec, layer=1).path,
"corrupted preflight\n",
)
file_manager.upsert_src(
ClassMeta(file_manager.source_location(path), reason, [reason], {}),
reason,
)
return True
def action(request):
preflight_path = request.editable_files[1]
assert file_manager.repo_exec.exists(preflight_path)
assert file_manager.repo_exec.read_text(preflight_path) == ""
assert file_manager.repo_exec.exists(
Memento(preflight_path, file_manager.repo_exec).path
)
file_manager.repo_exec.write_text(
preflight_path,
"class Extracted:\n pass\n",
)
return CodeEditorResult([preflight_path], "accepted preflight edit", [])
context = _alpha_context(file_manager, BackLog(), source, file_manager.require_test("tests/test_foo.py"))
context.new_files_callback = new_files_callback
worker = SequenceSourceWorker(action)
AlphaLoop(gate=_alpha_gate(context), context=context, aider_worker=worker)._run_source_work(
[source],
{"src/foo.py": "extract class"},
readonly_sources=[],
readonly_tests=[],
is_remediation=False,
)
preflight_path = file_manager.src_root / "pkg/extracted.py"
assert file_manager.repo_exec.exists(preflight_path)
assert file_manager.repo_exec.read_text(preflight_path) == "class Extracted:\n pass\n"
assert not file_manager.repo_exec.exists(
Memento(preflight_path, file_manager.repo_exec).path
)
def test_alpha_no_work_inbound_clears_previous_receipts(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""An inspection no-op cannot retain another iteration's remediation evidence."""
file_manager = _file_manager(tmp_path)
file_manager.resync()
source = file_manager.require_src("src/foo.py")
test = file_manager.require_test("tests/test_foo.py")
context = _alpha_context(file_manager, BackLog(), source, test)
loop = AlphaLoop(gate=_alpha_gate(context), context=context, aider_worker=SequenceSourceWorker())
loop.validation_receipts = ValidationReceipts((ValidationReceipt.from_result(test,
TestRunResult("pytest previous", TestRunOutcome.Success, 0, "previous pass", test_name="test_foo"),
test_definition="def test_foo(): pass", generation=1),), generation=1)
inspection = Mock(return_value=[])
postflight = Mock()
monkeypatch.setattr(loop, "_run_inspection_source_item", inspection)
monkeypatch.setattr(loop, "_stage_postflight_alpha_results", postflight)
item = InspectionBacklog(source, "inspect foo")
loop._process_inbound_item(item, resume=True)
assert loop.validation_receipts.receipts == ()
inspection.assert_called_once_with(item)
postflight.assert_called_once_with(item, [], verify_unchanged=True)
def test_alpha_acceptance_receives_exact_pre_and_memento_diff(tmp_path: Path) -> None:
"""Verify acceptance evidence comes from mementos, not editor-reported paths."""
file_manager = _file_manager(tmp_path)
client = AlphaAcceptanceSequenceClient(
[{"outcome": "pass", "reason": ["def foo():"], "instruction": ""}]
)
file_manager.client = client
file_manager.resync()
source = file_manager.require_src("src/foo.py")
def action(request):
assert file_manager.repo_exec.exists(
Memento(request.editable_files[0], file_manager.repo_exec).path
)
file_manager.repo_exec.write_text(
request.editable_files[0],
"def foo():\n return 2\n",
)
request.validation_receipts = ValidationReceipts((ValidationReceipt.from_result(
file_manager.require_test("tests/test_foo.py"),
TestRunResult("pytest tests/test_foo.py::test_foo", TestRunOutcome.Success, 0, "acceptance receipt output", test_name="test_foo"),
test_definition="def test_foo():\n assert foo() == 2\n", generation=1),), generation=1)
return CodeEditorResult([], "editor omitted changed paths", [])
context = _alpha_context(file_manager, BackLog(), source, file_manager.require_test("tests/test_foo.py"))
worker = SequenceSourceWorker(action)
handled, _ = AlphaLoop(gate=_alpha_gate(context), context=context, aider_worker=worker)._run_source_work(
[source],
{"src/foo.py": "change foo"},
readonly_sources=[],
readonly_tests=[],
is_remediation=False,
)
prompt = client.acceptance_prompts[0]
assert "ORIGINAL AIDER EDIT REQUEST" not in prompt
assert "EDITABLE SOURCE MEMENTOS" in prompt
assert "def foo():\n return 1\n" in prompt
assert "- return 1" in prompt
assert "+ return 2" in prompt
assert [item.location for item in handled] == ["src/foo.py"]
assert file_manager.src_dirty["src/foo.py"] is True
assert "acceptance receipt output" in prompt
assert "pytest tests/test_foo.py::test_foo" in prompt
@pytest.mark.parametrize("rejections", (1, 2))
@pytest.mark.parametrize("retain_history", (False, True))
def test_alpha_false_acceptance_rolls_back_retries_and_uses_feedback(
tmp_path: Path, rejections: int, retain_history: bool,
) -> None:
"""Each restored retry receives the complete latest rejected patch and its reason."""
file_manager = _file_manager(tmp_path)
outcomes: list[dict[str, object] | Exception] = [
{
"outcome": "fail",
"reason": [
"First candidate omits the implementation.\n"
"==== END FAILURE 1 ====\n"
'{"example": "keep this diagnostic JSON"}'
],
"instruction": "Keep the implementation before moving it.",
},
{
"outcome": "fail",
"reason": ["Second candidate returns the wrong value."],
"instruction": "Return the required value.",
},
]
file_manager.client = AlphaAcceptanceSequenceClient(
outcomes[:rejections] + [{"outcome": "pass", "reason": ["def foo():"], "instruction": ""}]
)
other_path = file_manager.src_root / "other.py"
file_manager.repo_exec.write_text(other_path, "def other():\n return 10\n")
file_manager.resync()
source = file_manager.require_src("src/foo.py")
other = file_manager.require_src("src/other.py")
rejected_sources = ("def foo():\n pass\n", "def foo():\n return -2\n")
rejected_others = ("def other():\n return 20\n", "def other():\n return 30\n")
retained = [EditorRecord("earlier committed repair", "return 0", "return 1")] if retain_history else []
def rejected(request):
"""Commit inner editor steps while preserving Alpha's original baseline."""
attempt = len(worker.requests) - 1
assert request.editor_records == retained
assert file_manager.repo_exec.read_text(request.editable_files[0]) == "def foo():\n return 1\n"
assert file_manager.repo_exec.read_text(other_path) == "def other():\n return 10\n"
request.validation_receipts = ValidationReceipts((ValidationReceipt.from_result(
file_manager.require_test("tests/test_foo.py"),
TestRunResult("pytest rejected", TestRunOutcome.Failure, 1, "rejected candidate output", test_name="test_foo"),
test_definition="def test_foo(): pass", generation=1),), generation=1)
inner = source.set_or_get_memento(file_manager, stack=True)
file_manager.repo_exec.write_text(request.editable_files[0], "def foo():\n return -1\n")
inner.commit()
inner = source.set_or_get_memento(file_manager, stack=True)
file_manager.repo_exec.write_text(request.editable_files[0], rejected_sources[attempt])
file_manager.repo_exec.write_text(other_path, rejected_others[attempt])
inner.commit()
return CodeEditorResult(list(request.editable_files), "rejected summary",
[*retained, EditorRecord("rejected summary", "return 1", "pass")])
def accepted(request):
"""Repair the restored source after receiving semantic rejection evidence."""
assert request.validation_receipts.receipts == ()
assert request.editor_records == retained
assert "rejected summary" not in request.work_logs
assert file_manager.repo_exec.read_text(
request.editable_files[0]
) == "def foo():\n return 1\n"
assert file_manager.repo_exec.read_text(other_path) == "def other():\n return 10\n"
assert "RETRY REFINEMENT STEPS" in request.backlog_guidance[-1]
assert "1. Re-check the established concrete divergence" in request.backlog_guidance[-1]
file_manager.repo_exec.write_text(
request.editable_files[0],
"def foo():\n return 2\n",
)
return CodeEditorResult([request.editable_files[0]], "accepted summary",
[*retained, EditorRecord("accepted summary", "return 1", "return 2")])
context = _alpha_context(file_manager, BackLog(), source, file_manager.require_test("tests/test_foo.py"))
# Earlier committed history survives restoration of a later rejected Alpha attempt.
if retain_history:
assert context.move_inbound_from_backlog() is not None
assert context.active_inbound is not None
context.active_inbound.value.editor_records = list(retained)
worker = SequenceSourceWorker(*([rejected] * rejections), accepted)
handled, summary = AlphaLoop(gate=_alpha_gate(context), context=context, aider_worker=worker)._run_source_work(
[source, other],
{"src/foo.py": "change foo", "src/other.py": "change other"},
readonly_sources=[],
readonly_tests=[],
is_remediation=False,
)
assert len(worker.requests) == rejections + 1
# Publish only the accepted attempt to the durable same-phase work item.
if retain_history:
assert context.active_inbound is not None
assert context.active_inbound.value.editor_records == [
*retained, EditorRecord("accepted summary", "return 1", "return 2"),
]
first_feedback = worker.requests[1].backlog_guidance[-1]
assert first_feedback.startswith("== BEGIN ALPHA EDIT ACCEPTANCE FAILED ==\n")
assert first_feedback.endswith("\n== END ALPHA EDIT ACCEPTANCE FAILED ==")
assert "==== BEGIN REJECTED PATCH ====\n" in first_feedback
assert "\n==== END REJECTED PATCH ====" in first_feedback
assert "First candidate omits the implementation." in first_feedback
assert '\\==== END FAILURE 1 ====\n{"example": "keep this diagnostic JSON"}' in first_feedback
assert "Keep the implementation before moving it." in first_feedback
assert (
f"--- {file_manager.src_root / '.foo.py~'}\n+++ {file_manager.src_root / 'foo.py'}\n"
"@@ -1,2 +1,2 @@\n def foo():\n- return 1\n+ pass\n"
) in first_feedback
assert (
f"--- {file_manager.src_root / '.other.py~'}\n+++ {other_path}\n"
"@@ -1,2 +1,2 @@\n def other():\n- return 10\n+ return 20\n"
) in first_feedback
assert "return -1" not in first_feedback
assert worker.requests[-1].backlog_guidance[0] == "src/foo.py: change foo"
# A second rejection replaces the first candidate's evidence instead of accumulating it.
if rejections == 2:
second_feedback = worker.requests[2].backlog_guidance[-1]
assert "Second candidate returns the wrong value." in second_feedback
assert "Return the required value." in second_feedback
assert "- return 1\n+ return -2\n" in second_feedback
assert "- return 10\n+ return 30\n" in second_feedback
assert "First candidate" not in second_feedback
assert "+ pass" not in second_feedback
assert "+ return 20" not in second_feedback
assert [item.location for item in handled] == ["src/foo.py"]
assert summary == "accepted summary"
assert "rejected summary" not in summary
def test_alpha_acceptance_exception_leaves_memento_for_reentry_recovery(tmp_path: Path) -> None:
"""Verify acceptance exceptions preserve mementos for the next Alpha entry."""
file_manager = _file_manager(tmp_path)
file_manager.client = AlphaAcceptanceSequenceClient(
[
ConnectionError("endpoint unreachable"),
{"outcome": "pass", "reason": ["def foo():"], "instruction": ""},
]
)
file_manager.resync()
source = file_manager.require_src("src/foo.py")
assert source is not None
def first(request):
file_manager.repo_exec.write_text(
request.editable_files[0],
"def foo():\n return 9\n",
)
return CodeEditorResult([request.editable_files[0]], "endpoint failure summary", [])
def accepted(request):
assert file_manager.repo_exec.read_text(
request.editable_files[0]
) == "def foo():\n return 1\n"
file_manager.repo_exec.write_text(
request.editable_files[0],
"def foo():\n return 2\n",
)
return CodeEditorResult([request.editable_files[0]], "accepted summary", [])
test = file_manager.require_test("tests/test_foo.py")
assert test is not None
context = _alpha_context(file_manager, BackLog(), source, test)
source_path = file_manager.src_root / "foo.py"
worker = SequenceSourceWorker(first, accepted)
loop = AlphaLoop(gate=_alpha_gate(context), context=context, aider_worker=worker)
with pytest.raises(ConnectionError, match="endpoint unreachable"):
loop._run_source_work(
[source],
{"src/foo.py": "change foo"},
readonly_sources=[],
readonly_tests=[],
is_remediation=False,
)
assert len(worker.requests) == 1
assert file_manager.repo_exec.read_text(source_path) == "def foo():\n return 9\n"
assert file_manager.repo_exec.exists(
Memento(source_path, file_manager.repo_exec).path
)
handled, _ = loop._run_source_work(
[source],
{"src/foo.py": "change foo"},
readonly_sources=[],
readonly_tests=[],
is_remediation=False,
)
assert [item.location for item in handled] == ["src/foo.py"]
assert len(worker.requests) == 2
assert file_manager.repo_exec.read_text(source_path) == "def foo():\n return 2\n"
assert not file_manager.repo_exec.exists(
Memento(source_path, file_manager.repo_exec).path
)
def test_alpha_leave_it_summary_skips_blank_and_acceptance_checks(tmp_path: Path) -> None:
"""Verify leave-it judgement summary bypasses Alpha acceptance checks as tie-breaker."""
file_manager = _file_manager(tmp_path)
file_manager.client = AlphaAcceptanceSequenceClient([RuntimeError("should not run")])
file_manager.resync()
source = file_manager.require_src("src/foo.py")
def leave_it(request):
return CodeEditorResult([], CodeEditLeaveSummary(
f"CODE EDIT OR LEAVE IT\n{CODE_EDIT_OR_LEAVE_IT_LEAVE_TOKEN}\n"
"Judgement summary:\nNo changes needed."
), [])
context = _alpha_context(file_manager, BackLog(), source, file_manager.require_test("tests/test_foo.py"))
worker = SequenceSourceWorker(leave_it)
loop = AlphaLoop(gate=_alpha_gate(context), context=context, aider_worker=worker)
handled, summary = loop._run_source_work(
[source],
{"src/foo.py": "change foo"},
readonly_sources=[],
readonly_tests=[],
is_remediation=False,
)
assert handled == []
assert CODE_EDIT_OR_LEAVE_IT_LEAVE_TOKEN not in summary
assert len(worker.requests) == 1
assert file_manager.client.acceptance_prompts == []
def test_alpha_literal_leave_token_in_ordinary_editor_text_does_not_skip_acceptance(
tmp_path: Path,
) -> None:
"""A token quoted by source or a normal editor summary is not runtime leave control."""
file_manager = _file_manager(tmp_path)
file_manager.client = AlphaAcceptanceSequenceClient(
[{"outcome": "pass", "reason": ["LEAVE_TOKEN_LITERAL = '[CODE_EDIT_OR_LEAVE_IT:LEAVE_IT]'"], "instruction": ""}]
)
file_manager.resync()
source = file_manager.require_src("src/foo.py")
def literal_source_text(request):
file_manager.repo_exec.write_text(
request.editable_files[0],
"LEAVE_TOKEN_LITERAL = '[CODE_EDIT_OR_LEAVE_IT:LEAVE_IT]'\n"
)
return CodeEditorResult([request.editable_files[0]], "Rendered source quoted [CODE_EDIT_OR_LEAVE_IT:LEAVE_IT] as ordinary evidence.", [])
context = _alpha_context(
file_manager,
BackLog(),
source,
file_manager.require_test("tests/test_foo.py"),
)
worker = SequenceSourceWorker(literal_source_text)
loop = AlphaLoop(gate=_alpha_gate(context), context=context, aider_worker=worker)
handled, _ = loop._run_source_work(
[source],
{"src/foo.py": "update the focused source"},
readonly_sources=[],
readonly_tests=[],
is_remediation=False,
)
assert [item.location for item in handled] == ["src/foo.py"]
assert file_manager.client.acceptance_prompts
def test_alpha_blank_deleted_preflight_source_is_recreated_for_retry(tmp_path: Path) -> None:
"""Verify blank preflight files deleted by an edit are restored as blank real files."""
file_manager = _file_manager(tmp_path)
file_manager.client = AlphaBlankRepairClient(
preflight_paths=["src/pkg/extracted.py"],
correction_response=json.dumps(
{
"instruction": "Populate src/pkg/extracted.py with the extracted implementation.",
"reason": "The preflight source was deleted and must be restored.",
"blank_files": ["src/pkg/extracted.py"],
}
),
)
file_manager.resync()
source = file_manager.require_src("src/foo.py")
def new_files_callback(files=None, **kwargs):
payload = {}
if files:
payload.update(files)
payload.update(kwargs)
for file_name, reason in payload.items():
path = file_manager.source_path(str(file_name))
file_manager.repo_exec.write_text(path, "")
file_manager.upsert_src(ClassMeta(file_manager.source_location(path), reason, [reason], {}), reason)
return True
def deletes_blank(request):
file_manager.repo_exec.delete(request.editable_files[1])
return CodeEditorResult([request.editable_files[1]], "deleted blank preflight", [])
def fills_blank(request):
assert file_manager.repo_exec.exists(request.editable_files[1])
assert file_manager.repo_exec.read_text(request.editable_files[1]) == ""
file_manager.repo_exec.write_text(
request.editable_files[1],
"class Extracted:\n pass\n",
)
return CodeEditorResult([request.editable_files[1]], "accepted preflight", [])
context = _alpha_context(file_manager, BackLog(), source, file_manager.require_test("tests/test_foo.py"))
context.new_files_callback = new_files_callback
worker = SequenceSourceWorker(deletes_blank, fills_blank)
AlphaLoop(gate=_alpha_gate(context), context=context, aider_worker=worker)._run_source_work(
[source],
{"src/foo.py": "extract class"},
readonly_sources=[],
readonly_tests=[],
is_remediation=False,
)
assert len(worker.requests) == 2
assert file_manager.client.correction_calls == 1
assert file_manager.repo_exec.read_text(
file_manager.src_root / "pkg/extracted.py"
) == "class Extracted:\n pass\n"
def test_alpha_unexpected_editor_exception_leaves_memento_for_reentry_recovery(tmp_path: Path) -> None:
"""Verify non-acceptance exceptions preserve mementos for the next Alpha entry."""
file_manager = _file_manager(tmp_path)
file_manager.client = AlphaAcceptanceSequenceClient(
[{"outcome": "pass", "reason": ["def foo():"], "instruction": ""}]
)
file_manager.resync()
source = file_manager.require_src("src/foo.py")
source_path = file_manager.src_root / "foo.py"
def raises(request):
file_manager.repo_exec.write_text(request.editable_files[0], "corrupted\n")
raise RuntimeError("editor failed")
def accepted(request):
assert file_manager.repo_exec.read_text(
request.editable_files[0]
) == "def foo():\n return 1\n"
file_manager.repo_exec.write_text(
request.editable_files[0],
"def foo():\n return 2\n",
)
return CodeEditorResult([request.editable_files[0]], "accepted after recovery", [])
context = _alpha_context(file_manager, BackLog(), source, file_manager.require_test("tests/test_foo.py"))
loop = AlphaLoop(gate=_alpha_gate(context), context=context, aider_worker=SequenceSourceWorker(raises))
with pytest.raises(RuntimeError):
loop._run_source_work([source], {"src/foo.py": "change foo"}, [], [], is_remediation=False)
assert file_manager.repo_exec.exists(
Memento(source_path, file_manager.repo_exec).path
)
loop.aider_worker = SequenceSourceWorker(accepted)
handled, _ = loop._run_source_work([source], {"src/foo.py": "change foo"}, [], [], is_remediation=False)
assert [item.location for item in handled] == ["src/foo.py"]
assert not file_manager.repo_exec.exists(
Memento(source_path, file_manager.repo_exec).path
)
def test_alpha_commits_mementos_before_testing_backlog_creation(tmp_path: Path) -> None:
"""Verify Beta backlog creation observes committed Alpha mementos."""
file_manager = _file_manager(tmp_path)
file_manager.client = AlphaAcceptanceSequenceClient(
[{"outcome": "pass", "reason": ["def foo():"], "instruction": ""}]
)
file_manager.resync()
source = file_manager.require_src("src/foo.py")
test = file_manager.require_test("tests/test_foo.py")
backlog = BackLog()
observed_mementos: list[bool] = []
contributor = ContributorBacklog(
backlog=backlog,
get_tests=lambda source_files: observed_mementos.append(
file_manager.repo_exec.exists(
Memento(
file_manager.source_path(source_files[0].location),
file_manager.repo_exec,
).path
)
) or [test],
file_manager=file_manager,
parser=file_manager.parser,
language_strategy_set=_language_strategy_set(file_manager, runner=PassingRunner()),
)
context = _alpha_context(file_manager, backlog, source, test)
context.backlog = contributor
def action(request):
file_manager.repo_exec.write_text(
request.editable_files[0],
"def foo():\n return 2\n",
)
return CodeEditorResult([request.editable_files[0]], "accepted edit", [])
AlphaLoop(gate=_alpha_gate(context), context=context, aider_worker=SequenceSourceWorker(action)).run()
assert observed_mementos == [False]
assert backlog.testing
def test_alpha_acceptance_commits_each_outer_memento(
tmp_path: Path,
) -> None:
"""Verify accepted Alpha work removes every outer source snapshot."""
file_manager = _file_manager(tmp_path)
file_manager.repo_exec.write_text(
file_manager.src_root / "bar.py",
"def bar():\n return 1\n",
)
file_manager.client = AlphaAcceptanceSequenceClient(
[{"outcome": "pass", "reason": ["def foo():"], "instruction": ""}]
)
file_manager.resync()
source = file_manager.require_src("src/foo.py")
other_source = file_manager.require_src("src/bar.py")
source_path = file_manager.source_path(source.location)
other_source_path = file_manager.source_path(other_source.location)
context = _alpha_context(file_manager, BackLog(), source, file_manager.require_test("tests/test_foo.py"))
def action(request):
file_manager.repo_exec.write_text(
request.editable_files[0],
"def foo():\n return 2\n",
)
file_manager.repo_exec.write_text(
request.editable_files[1],
"def bar():\n return 2\n",
)
return CodeEditorResult([request.editable_files[0]], "accepted edit", [])
AlphaLoop(
gate=_alpha_gate(context),
context=context,
aider_worker=SequenceSourceWorker(action),
)._run_source_work(
[source, other_source],
{
source.location: "change foo",
other_source.location: "change bar",
},
readonly_sources=[],
readonly_tests=[],
is_remediation=False,
)
assert not file_manager.repo_exec.exists(
Memento(source_path, file_manager.repo_exec).path
)
assert not file_manager.repo_exec.exists(
Memento(other_source_path, file_manager.repo_exec).path
)
def test_alpha_skips_testing_backlog_for_completed_source_test_pair(
tmp_path: Path,
) -> None:
"""Verify completed source/test pairs do not re-enter testing backlog."""
file_manager = _file_manager(tmp_path)
file_manager.resync()
source = file_manager.require_src("src/foo.py")
test = file_manager.require_test("tests/test_foo.py")
source_key = source.location
backlog = BackLog(
completion={
source_key: WorkCompletion(
location=source.location,
completed=True,
tests={
test.location: WorkCompletion(
location=test.location,
completed=True,
)
},
)
}
)
contributor = ContributorBacklog(
backlog=backlog,
get_tests=lambda source_files: [test],
file_manager=file_manager,
parser=file_manager.parser,
language_strategy_set=_language_strategy_set(file_manager, runner=PassingRunner()),
)
created = contributor.create_outbound_for(source, original_cot=file_manager.prompt)
assert created == []
assert not backlog.testing
def test_alpha_pops_source_backlog_one_item_per_worker_request(
tmp_path: Path,
) -> None:
"""Verify Alpha drains source backlog one popped item per worker request."""
file_manager = _file_manager(tmp_path)
file_manager.client = AlphaAcceptanceSequenceClient([
{"outcome": "pass", "reason": ["def foo():"], "instruction": ""},
{"outcome": "pass", "reason": ["def bar():"], "instruction": ""},
])
file_manager.client.acceptance_reasons = ("return 1",)
file_manager.repo_exec.write_text(
file_manager.src_root / "bar.py",
"def bar():\n return 1\n",
)
file_manager.resync()
foo = file_manager.require_src("src/foo.py")
bar = file_manager.require_src("src/bar.py")
test = file_manager.require_test("tests/test_foo.py")
backlog = BackLog()
backlog.queue_inspection(InspectionBacklog(foo, "inspect foo"))
backlog.queue_inspection(
InspectionBacklog(
bar,
"inspect bar",
upstream_classes=[foo],
)
)
context = _alpha_context(file_manager, backlog, foo, test)
worker = NoChangeWorker()
AlphaLoop(gate=_alpha_gate(context), context=context, aider_worker=worker).run()
assert not backlog.inspection
assert len(worker.requests) == 2
assert worker.requests[0].editable_files == [file_manager.src_root / "foo.py"]
assert worker.requests[1].editable_files == [file_manager.src_root / "bar.py"]
assert file_manager.src_root / "foo.py" in worker.requests[1].readonly_files
def test_alpha_readonly_paths_ignore_readonly_tests_and_editables(tmp_path: Path) -> None:
"""Verify Alpha readonly preloading keeps only supplied readonly sources."""
file_manager = _file_manager(tmp_path)
file_manager.repo_exec.write_text(
file_manager.src_root / "bar.py",
"from foo import foo\n",
)
file_manager.repo_exec.write_text(
file_manager.test_root / "test_helper.py",
"def test_helper():\n assert True\n",
)
file_manager.resync()
foo = file_manager.require_src("src/foo.py")
bar = file_manager.require_src("src/bar.py")
readonly_test = file_manager.require_test("tests/test_helper.py")
context = _alpha_context(file_manager, BackLog(), bar, file_manager.require_test("tests/test_foo.py"))
loop = AlphaLoop(gate=_alpha_gate(context), context=context)
readonly_paths = loop._readonly_paths(
[bar],
[foo],
[readonly_test],
)
assert readonly_paths == [
file_manager.source_path(foo.location),
file_manager.test_path(readonly_test.location),
]
def test_alpha_preserves_prior_testing_and_all_source_remediation_before_stopping(
tmp_path: Path,
) -> None:
"""Verify Alpha retains prior test work and every source repair before stopping."""
file_manager = _file_manager(tmp_path)
file_manager.repo_exec.write_text(
file_manager.src_root / "helper.py",
"def helper():\n return 1\n",
)
for suffix in ("b", "c", "d"):
file_manager.repo_exec.write_text(
file_manager.test_root / f"test_{suffix}.py",
"from foo import foo\n\n"
f"def test_{suffix}():\n"
" assert foo() == 1\n"
)
file_manager.resync()
source = file_manager.require_src("src/foo.py")
helper = file_manager.require_src("src/helper.py")
tests = [
file_manager.require_test(location)
for location in (
"tests/test_foo.py",
"tests/test_b.py",
"tests/test_c.py",
"tests/test_d.py",
)
]
file_manager.src_dirty[source.location] = True
results = [
TestRunResult("pytest", TestRunOutcome.Failure, 1, f"failure {test.location}")
for test in tests
]
backlog = BackLog()
inbound = InspectionBacklog(source, "change foo", downstream_classes=[source])
backlog.queue_inspection(inbound)
contributor = ScenarioContributorBacklog(
backlog=backlog,
file_manager=file_manager,
tests=tests,
candidates=[
_failed_candidate(test, result)
for test, result in zip(tests, results)
],
routed_by_test={
tests[0].location: [_testing_item(source, tests[0])],
tests[1].location: [_testing_item(source, tests[1])],
tests[2].location: [
_failed_item(source, tests[2]),
_failed_item(helper, tests[2]),
],
},
)
context = _alpha_context(file_manager, backlog, source, tests[0])
context.backlog = contributor
claimed = context.move_inbound_from_backlog()
assert claimed is not None
AlphaLoop(
gate=_alpha_gate(context),
context=context,
)._stage_postflight_alpha_results(claimed.value, [source])
assert [staged.lane for staged in context.outbound_queue] == [
BacklogLane.Testing,
BacklogLane.Testing,
BacklogLane.Remediation,
BacklogLane.Remediation,
]
claimed.commit()
context.flush_outbound_with_wal()
assert [item.focused_spotlight for item in backlog.testing] == tests[:2]
assert {_focused_class_of(item).location for item in backlog.remediation} == {
source.location,
helper.location,
}
assert all(_failed_test_of(item).test is tests[2] for item in backlog.remediation)
def test_alpha_first_source_remediation_stops_later_routing_after_census(
tmp_path: Path,
) -> None:
"""Verify the first source repair stops later routing after the direct-test census."""
file_manager = _file_manager(tmp_path)
file_manager.repo_exec.write_text(
file_manager.src_root / "memento.py",
"class Memento:\n"
" pass\n"
)
file_manager.repo_exec.write_text(
file_manager.src_root / "class_meta.py",
"from memento import Memento\n\n"
"class ClassMeta:\n"
" def set_or_get_memento(self):\n"
" return Memento().capture()\n"
)
file_manager.repo_exec.write_text(
file_manager.test_root / "test_cached_file_patcher.py",
"from class_meta import ClassMeta\n\n"
"def test_cached_file_patcher_trains_after_three_successful_editor_calls():\n"
" assert ClassMeta().set_or_get_memento() is not None\n"
)
file_manager.repo_exec.write_text(
file_manager.test_root / "test_class_meta_cache.py",
"from class_meta import ClassMeta\n\n"
"def test_class_meta_cache_reuses_the_current_memento():\n"
" assert ClassMeta().set_or_get_memento() is not None\n"
)
file_manager.resync()
memento = file_manager.require_src("src/memento.py")
class_meta = file_manager.require_src("src/class_meta.py")
focused_test = file_manager.require_test("tests/test_cached_file_patcher.py")
other_test = file_manager.require_test("tests/test_class_meta_cache.py")
file_manager.src_dirty[memento.location] = True
result = TestRunResult(
"pytest",
TestRunOutcome.Failure,
1,
"AttributeError: 'Memento' object has no attribute 'capture'",
[
"src/class_meta.py:5: in set_or_get_memento",
"return Memento().capture()",
],
)
other_result = TestRunResult(
"pytest",
TestRunOutcome.Failure,
1,
"AttributeError: ClassMeta cache cannot capture Memento",
["src/class_meta.py:5: return Memento().capture()"],
)
backlog = BackLog()
inbound = InspectionBacklog(
memento,
"Remove public Memento.capture.",
downstream_classes=[memento],
)
backlog.queue_inspection(inbound)
contributor = ScenarioContributorBacklog(
backlog=backlog,
file_manager=file_manager,
tests=[focused_test, other_test],
candidates=[
_failed_candidate(focused_test, result),
_failed_candidate(other_test, other_result),
],
routed_by_test={
focused_test.location: [_failed_item(
class_meta,
focused_test,
error_message="Test command failed with return code 1.",
stack_trace=result.stack_trace,
raw_output=result.output,
)],
},
)
context = _alpha_context(file_manager, backlog, memento, focused_test)
context.backlog = contributor
claimed = context.move_inbound_from_backlog()
assert claimed is not None
AlphaLoop(
gate=_alpha_gate(context),
context=context,
)._stage_postflight_alpha_results(claimed.value, [memento])
assert len(context.outbound_queue) == 1
assert all(
staged.lane is BacklogLane.Remediation
for staged in context.outbound_queue
)
assert not backlog.remediation
assert not backlog.testing
claimed.commit()
context.flush_outbound_with_wal()
assert len(backlog.remediation) == 1
remediation = backlog.remediation[0]
assert remediation.focused_spotlight is class_meta
observed_failure = _failed_test_of(remediation)
assert observed_failure.test is focused_test
assert observed_failure.error_message == "Test command failed with return code 1."
assert observed_failure.stack_trace == result.stack_trace
assert observed_failure.raw_output == result.output
assert not backlog.testing
def test_alpha_direct_failure_routes_to_testing_with_failure_context(
tmp_path: Path,
) -> None:
"""Verify test ownership preserves the existing TestingBacklog failure contract only."""
file_manager = _file_manager(tmp_path)
file_manager.resync()
source = file_manager.require_src("src/foo.py")
focused_test = file_manager.require_test("tests/test_foo.py")
file_manager.src_dirty[source.location] = True
result = TestRunResult(
"pytest",
TestRunOutcome.Failure,
1,
"AssertionError: stale test expectation",
["tests/test_foo.py:4: assert foo() == 1"],
)
backlog = BackLog()
inbound = InspectionBacklog(source, "change foo", downstream_classes=[source])
backlog.queue_inspection(inbound)
contributor = ScenarioContributorBacklog(
backlog=backlog,
file_manager=file_manager,
tests=[focused_test],
candidates=[_failed_candidate(focused_test, result)],
routed_by_test={
focused_test.location: [
TestingBacklog(
focused_spotlight=focused_test,
summary="FAILED CANDIDATE AND REGRESSION STAR",
downstream_classes=[source],
original_cot="goal",
change_type=ChangeType.Update,
failure_error_message="Test command failed with return code 1.",
failure_stack_trace=result.stack_trace,
failure_raw_output=result.output,
)
],
},
)
context = _alpha_context(file_manager, backlog, source, focused_test)
context.backlog = contributor
claimed = context.move_inbound_from_backlog()
assert claimed is not None
AlphaLoop(
gate=_alpha_gate(context),
context=context,
)._stage_postflight_alpha_results(claimed.value, [source])
assert len(context.outbound_queue) == 1
assert context.outbound_queue[0].lane is BacklogLane.Testing
assert not backlog.remediation
claimed.commit()
context.flush_outbound_with_wal()
assert not backlog.remediation
assert len(backlog.testing) == 1
testing = backlog.testing[0]
assert testing.focused_spotlight is focused_test
assert testing.failure_error_message == "Test command failed with return code 1."
assert testing.failure_stack_trace == result.stack_trace
assert testing.failure_raw_output == result.output
assert testing.failure_context_sources == []
assert testing.upstream_classes == []
assert testing.downstream_classes == [source]
assert testing.original_cot == "goal"
assert testing.change_type is ChangeType.Update
assert testing.summary == "FAILED CANDIDATE AND REGRESSION STAR"
def test_alpha_direct_failure_preserves_source_obligation_without_dependency_candidates(
tmp_path: Path,
) -> None:
"""A routed source remains the likely culprit when dependency discovery is empty."""
file_manager = _file_manager(tmp_path)
file_manager.repo_exec.write_text(
file_manager.test_root / "test_other.py",
"from foo import foo\n\n"
"def test_other():\n"
" assert foo() == 1\n"
)
file_manager.resync()
source = file_manager.require_src("src/foo.py")
focused_test = file_manager.require_test("tests/test_foo.py")
other_test = file_manager.require_test("tests/test_other.py")
file_manager.src_dirty[source.location] = True
result = TestRunResult(
"pytest",
TestRunOutcome.Failure,
1,
"AssertionError: ownership needs reconsideration",
["src/foo.py:1: def foo():", "tests/test_foo.py:4: assert foo() == 1"],
)
other_result = TestRunResult(
"pytest",
TestRunOutcome.Failure,
1,
"AssertionError: second test-owned candidate",
["tests/test_other.py:4: assert foo() == 1"],
)
runner = SequenceTargetedRunner([result, other_result])
backlog = BackLog()
inbound = InspectionBacklog(source, "change foo", downstream_classes=[source])
backlog.queue_inspection(inbound)
file_manager.client = FakeClient()
backlog.judgement_service = SourceThenTestRoutingService()
contributor = ScenarioContributorBacklog(
backlog=backlog,
file_manager=file_manager,
tests=[focused_test, other_test],
candidates=[
_failed_candidate(focused_test, result),
_failed_candidate(other_test, other_result),
],
routed_by_test={},
use_real_routes=True,
)
contributor._language_strategy_set.bindings[BiDependencyDiscoveryStrategy] = (
EmptyDependencyStrategy()
)
context = _alpha_context(file_manager, backlog, source, focused_test)
context.backlog = contributor
claimed = context.move_inbound_from_backlog()
assert claimed is not None
AlphaLoop(
gate=_alpha_gate(context),
context=context,
)._stage_postflight_alpha_results(claimed.value, [source])
assert len(context.outbound_queue) == 1
assert context.outbound_queue[0].lane is BacklogLane.Remediation
assert not backlog.remediation
claimed.commit()
context.flush_outbound_with_wal()
assert not backlog.testing
assert len(backlog.remediation) == 1
assert backlog.remediation[0].focused_spotlight == source
assert backlog.remediation[0].failure_raw_output == result.output
def test_alpha_postflight_routing_uses_result_despite_restart_history(
tmp_path: Path,
) -> None:
"""A completed test-owned route cannot be reinterpreted as a dead end."""
file_manager = _file_manager(tmp_path)
file_manager.resync()
source = file_manager.require_src("src/foo.py")
focused_test = file_manager.require_test("tests/test_foo.py")
result = TestRunResult(
"pytest",
TestRunOutcome.Failure,
1,
"AssertionError: selected test premise is stale",
["tests/test_foo.py:4: assert foo() == 1"],
)
backlog = BackLog()
file_manager.client = RoutingClientThatReturnsTest()
contributor = ScenarioContributorBacklog(
backlog=backlog,
file_manager=file_manager,
tests=[focused_test],
candidates=[_failed_candidate(focused_test, result)],
routed_by_test={},
use_real_routes=True,
)
contributor._language_strategy_set.bindings[BiDependencyDiscoveryStrategy] = (
EmptyDependencyStrategy()
)
context = _alpha_context(file_manager, backlog, source, focused_test)
context.backlog = contributor
graph = Provenance.open(repo_root=file_manager.repo_root, repo_exec=file_manager.repo_exec)
item = InspectionBacklog(source, "change foo", downstream_classes=[source])
prior_beta = graph.start(Phase.BETA, TestingBacklog(focused_test, "Prior test work."))
graph.track_provenance(prior_beta, Phase.ALPHA, item)
with provenance.bind(graph):
with provenance.item_scope(Phase.ALPHA, item):
routed = contributor._route_failed_direct_candidate(
AcceptedSourceStory(
current_source=source,
source_effect="changed",
source_diff="",
upstream_classes=[],
downstream_classes=[source],
original_cot="goal",
),
_failed_candidate(focused_test, result),
"FAILED CANDIDATE",
regression_star=True,
logger=None,
)
assert len(routed) == 1
assert isinstance(routed[0], TestingBacklog)
def test_alpha_routes_every_candidate_when_all_failures_are_test_owned(
tmp_path: Path,
) -> None:
"""Verify no early stop occurs when every direct candidate produces TestingBacklog."""
file_manager = _file_manager(tmp_path)
for suffix in ("b", "c"):
file_manager.repo_exec.write_text(
file_manager.test_root / f"test_{suffix}.py",
"from foo import foo\n\n"
f"def test_{suffix}():\n"
" assert foo() == 1\n"
)
file_manager.resync()
source = file_manager.require_src("src/foo.py")
tests = [
file_manager.require_test(location)
for location in (
"tests/test_foo.py",
"tests/test_b.py",
"tests/test_c.py",
)
]
file_manager.src_dirty[source.location] = True
results = [TestRunResult("pytest", TestRunOutcome.Failure, 1, test.location) for test in tests]
backlog = BackLog()
contributor = ScenarioContributorBacklog(
backlog=backlog,
file_manager=file_manager,
tests=tests,
candidates=[
_failed_candidate(test, result)
for test, result in zip(tests, results)
],
routed_by_test={
test.location: [_testing_item(source, test)]
for test in tests
},
)
created = contributor.create_outbound_for(source, original_cot="goal")
assert [item.focused_spotlight for item in created] == tests
assert [item.focused_spotlight for item in backlog.testing] == tests
assert not backlog.remediation
def test_alpha_passing_direct_regression_star_remains_testing_work(
tmp_path: Path,
) -> None:
"""Verify passing direct candidates retain semantic-vote regression-star selection."""
file_manager = _file_manager(tmp_path)
file_manager.repo_exec.write_text(
file_manager.test_root / "test_other.py",
"from foo import foo\n\n"
"def test_other():\n"
" assert foo() == 1\n"
)
file_manager.resync()
source = file_manager.require_src("src/foo.py")
first_test = file_manager.require_test("tests/test_foo.py")
regression_star = file_manager.require_test("tests/test_other.py")
file_manager.src_dirty[source.location] = True
backlog = BackLog()
contributor = ScenarioContributorBacklog(
backlog=backlog,
file_manager=file_manager,
tests=[first_test, regression_star],
candidates=[
DirectTestCandidate(
focused_test=first_test,
targeted_result=TestRunResult("pytest", TestRunOutcome.Success, 0, "first passed"),
semantic_vote_tally=0,
),
DirectTestCandidate(
focused_test=regression_star,
targeted_result=TestRunResult("pytest", TestRunOutcome.Success, 0, "star passed"),
semantic_vote_tally=1,
),
],
routed_by_test={},
)
created = contributor.create_outbound_for(
source,
queue=False,
original_cot="goal",
)
assert len(created) == 1
assert isinstance(created[0], TestingBacklog)
assert created[0].focused_spotlight is regression_star
assert created[0].summary == "REGRESSION STAR"
assert created[0].failure_error_message == ""
assert not backlog.remediation
assert not backlog.testing
@pytest.mark.parametrize("during_construction", (False, True))
def test_alpha_acceptance_stale_failure_rolls_back_before_historical_revival(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, during_construction: bool,
) -> None:
"""A redirect raised during acceptance construction cannot leave its candidate applied."""
from unittest.mock import Mock
from band_aider.context.provenance import DeadEndError, Direction
manager = _file_manager(tmp_path)
source = manager.require_src("src/foo.py")
test = manager.require_test("tests/test_foo.py")
path = source.resolve_path(manager)
original = manager.repo_exec.read_text(path)
context = _alpha_context(manager, BackLog(), source, test)
worker = EditingSourceWorker()
loop = AlphaLoop(gate=_alpha_gate(context), context=context, aider_worker=worker)
error = DeadEndError(
direction=Direction.ROLLFORWARD, current_message_id="active",
message="Stale remediation failure", justification="Fresh test changed; return through existing history.",
)
# The owning transaction must unwind either acceptance entry point.
if during_construction:
monkeypatch.setattr("band_aider.judgement.alpha_edit_acceptance_check.RemediationCheck", Mock(side_effect=error))
else:
monkeypatch.setattr("band_aider.loop.alpha_loop.AlphaEditAcceptanceDecision", Mock(return_value=Mock(side_effect=error)))
with pytest.raises(DeadEndError) as caught:
loop._run_source_work(
[source], {source.location: "repair"}, readonly_sources=[], readonly_tests=[test],
failing_test=FailingTest(test, ["tests/test_foo.py:2: in test_foo"], "old error"), is_remediation=True,
)
assert caught.value is error
assert "- return 1" in error.message
assert "+ return 2" in error.message
assert manager.repo_exec.read_text(path) == original
assert len(worker.requests) == 1
assert not manager.repo_exec.exists(Memento(path, manager.repo_exec).path)
def test_alpha_carries_cleared_snapshot_diff_into_next_edit(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""Clearing a leftover stack carries the entire discarded attempt into the next edit."""
from unittest.mock import Mock
manager = _file_manager(tmp_path)
source = manager.require_src("src/foo.py")
test = manager.require_test("tests/test_foo.py")
path = source.resolve_path(manager)
source.set_or_get_memento(manager)
manager.repo_exec.write_text(path, "def foo():\n return -5\n")
# An unused higher snapshot must not hide the base-to-current candidate being discarded.
source.set_or_get_memento(manager, stack=True)
context = _alpha_context(manager, BackLog(), source, test)
worker = EditingSourceWorker()
loop = AlphaLoop(gate=_alpha_gate(context), context=context, aider_worker=worker)
monkeypatch.setattr("band_aider.loop.alpha_loop.AlphaEditAcceptanceDecision", Mock(return_value=Mock(return_value=True)))
loop._run_source_work(
[source], {source.location: "repair"}, readonly_sources=[], readonly_tests=[test],
failing_test=None, is_remediation=True,
)
guidance = "\n".join(worker.requests[0].backlog_guidance)
assert "- return 1\n+ return -5" in guidance
assert manager.repo_exec.read_text(path) == "def foo():\n return 2\n"
assert not Memento.current(path, manager.repo_exec)
def test_direct_postflight_retains_canonical_test_failure(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""Direct Alpha postflight carries exactly the failure captured by the shared runner."""
from unittest.mock import Mock
from band_aider.judgement.test_runner_strategy import TestRunnerStrategy
manager = _file_manager(tmp_path)
source = manager.require_src("src/foo.py")
test = manager.require_test("tests/test_foo.py")
result = TestRunResult("direct test", TestRunOutcome.Failure, 1, "\nraw λ \r\n", ["tests/test_foo.py:2 ", "", "assert λ\r"])
runner = Mock(spec=TestRunnerStrategy)
runner.run_tests_for.return_value = result
runner.failure_reports.return_value = [FailingTest.from_result(test, result)]
strategies = _language_strategy_set(manager, runner=runner)
backlog = ContributorBacklog(
backlog=BackLog(), get_tests=lambda _sources: [test], file_manager=manager,
parser=manager.parser, language_strategy_set=strategies,
)
scorer = Mock()
scorer.score_candidates.return_value = []
monkeypatch.setattr("band_aider.context.working_backlog.Model2VecSimilarityCheck", Mock(return_value=scorer))
story = AcceptedSourceStory(source, "accepted", "source diff", [], [], "original COT")
[candidate] = backlog._direct_test_candidates(story, [test])
assert candidate.failing is not None
assert candidate.failing.to_record() == {
"outcome": "failure",
"error_message": "Test command failed with return code 1.",
"stack_trace": result.stack_trace,
"raw_output": result.output,
}
runner.run_tests_for.assert_called_once_with([test], max_failures=1)