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

174 lines
6.2 KiB
Python

# SPDX-FileCopyrightText: 2026 Art Sea LLC
#
# SPDX-License-Identifier: AGPL-3.0-only
"""Language-neutral input for one bounded code-edit attempt."""
from __future__ import annotations
from band_aider.judgement.test_runner_strategy import TestRunOutcome
from dataclasses import dataclass, field, replace
from enum import IntEnum
from pathlib import Path
from typing import TYPE_CHECKING, Sequence, TypeVar
from band_aider.strategies.compiler import CompileError, StaticCompileError
from band_aider.context.validation_receipts import ValidationReceipts
from band_aider.judgement.code_editor import EditorRecord
if TYPE_CHECKING:
from band_aider.context.class_meta import ClassMeta
from band_aider.context.cot_task_generator import CotTaskRecord
from band_aider.context.provenance import FutureGuidance
from band_aider.strategies.compiler import FrontierProvider
@dataclass
class AiderEditRequest:
"""Carry the current task, edit scope, and runtime workflow context."""
authoritative_prompt: str
editable_files: list[Path]
readonly_files: list[Path]
pinned_context: list[str] = field(default_factory=list)
backlog_guidance: list[str] = field(default_factory=list)
future_guidance: tuple[FutureGuidance, ...] = ()
previous_cot_records: tuple[CotTaskRecord, ...] = ()
failure_error_message: str = ""
failure_stack_trace: list[str] = field(default_factory=list)
failure_raw_output: str = ""
historical_logs: list[str] = field(default_factory=list)
change_logs: list[str] = field(default_factory=list)
allowed_additional_roots: list[Path] = field(default_factory=list)
is_remediation: bool = False
remediation_originator_test: ClassMeta | None = None
context: type | None = None
frontier_provider: FrontierProvider | None = None
failure_test_location: str = ""
failure_test_name: str = ""
failure_outcome: TestRunOutcome | None = None
failure_run_info: str = ""
validation_receipts: ValidationReceipts = field(default_factory=ValidationReceipts)
editor_records: list[EditorRecord] = field(default_factory=list)
@property
def work_logs(self) -> str:
"""Render retained edits as history without mutating durable backlog guidance."""
return "Work Logs\n\n" + "\n\n".join(map(EditorRecord.render, self.editor_records)) if self.editor_records else ""
@property
def guidance(self) -> list[str]:
"""Append current Work Logs to the ordinary guidance presented to this request's consumers."""
return [*self.backlog_guidance, *filter(None, (self.work_logs,))]
def validation_view(self) -> AiderEditRequest:
"""Project current matching execution without mutating captured history."""
file_scope = self.failure_outcome is not None and self.failure_outcome.is_error
receipt = self.validation_receipts.current_for(self.failure_test_location,
"" if file_scope else self.failure_test_name, file_scope=file_scope)
# Unknown identities and older observations cannot erase an incoming obligation.
if receipt is None:
return self
failure = receipt.failure
return replace(self, failure_error_message=failure.error_message if failure else "",
failure_stack_trace=list(failure.stack_trace) if failure else [],
failure_raw_output=failure.raw_output if failure else "",
failure_outcome=failure.outcome if failure else self.failure_outcome,
failure_run_info=failure.run_info if failure else "")
class AiderFailureMode(IntEnum):
"""Set the highest failure-evidence level visible to an edit request.
Example::
mode = AiderFailureMode.STATIC
"""
DEFAULT = 0 # PATCH
STATIC = 1 # COMPILE
SEMANTIC = 2
@classmethod
def attempt_level(
cls,
errors: Sequence[CompileError],
*,
default: AiderFailureMode,
) -> AiderFailureMode:
"""Return the earliest validation level reached by one compiler attempt."""
return min(
map(
lambda error: (
cls.STATIC
if isinstance(error, StaticCompileError)
else cls.SEMANTIC
),
errors,
),
default=default,
)
_Visible = TypeVar("_Visible")
@dataclass(frozen=True)
class AiderEditRequestMask:
"""Project one request without changing its caller-owned evidence or collections.
Example::
view = AiderEditRequestMask(request, AiderFailureMode.STATIC).projected()
"""
original: AiderEditRequest
mode: AiderFailureMode
@property
def has_semantic_failure(self) -> bool:
"""Return whether the request carries runtime semantic failure evidence."""
return any(
value.strip()
for value in (
self.original.failure_error_message,
*self.original.failure_stack_trace,
self.original.failure_raw_output,
)
)
def visible(
self,
*,
patch_level: _Visible,
compile_level: _Visible,
semantic_level: _Visible,
) -> tuple[_Visible, ...]:
"""Return the PATCH < COMPILE < SEMANTIC prefix through this workflow level."""
levels = (patch_level, compile_level, semantic_level)
return levels[: self.mode + 1]
def projected(self) -> AiderEditRequest:
"""Materialize the selected shallow view while retaining the original request."""
hidden_semantic: dict[str, object] = {
"failure_error_message": "",
"failure_stack_trace": [],
"failure_raw_output": "",
"failure_run_info": "",
}
# Lower repair levels hide backlog guidance only with runtime failure evidence.
if self.has_semantic_failure:
hidden_semantic["backlog_guidance"] = []
lower_level = replace(self.original, **hidden_semantic)
semantic_level = replace(self.original, backlog_guidance=[])
return self.visible(
patch_level=lower_level,
compile_level=lower_level,
semantic_level=semantic_level,
)[-1]