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

95 lines
3.7 KiB
Python

# SPDX-FileCopyrightText: 2026 Art Sea LLC
#
# SPDX-License-Identifier: AGPL-3.0-only
"""Shared workflow story objects for Alpha postflight and Beta retry judgement calls."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any, TYPE_CHECKING
from band_aider.context.class_meta import ClassMeta
from band_aider.context.failing_test import FailingTest
from band_aider.judgement.test_runner_strategy import TestRunResult
from band_aider.judgement.code_editor import EditorRecord
if TYPE_CHECKING:
from band_aider.context.provenance import FutureGuidance
from band_aider.context.working_backlog import ChangeType, TestingBacklog
@dataclass(frozen=True)
class AcceptedSourceStory:
"""AcceptedSourceStory records the accepted source change and its test-planning evidence."""
current_source: ClassMeta
source_effect: str
source_diff: str
upstream_classes: list[ClassMeta]
downstream_classes: list[ClassMeta]
original_cot: str
editor_records: list[EditorRecord] = field(default_factory=list)
@dataclass(frozen=True)
class DirectTestCandidate:
"""DirectTestCandidate retains only the state needed after direct execution."""
focused_test: ClassMeta
targeted_result: TestRunResult
semantic_vote_tally: int = 0
failing: FailingTest | None = None
@dataclass(frozen=True)
class TargetedFailureStory:
"""TargetedFailureStory records one routed targeted failure and its shared evidence."""
testing_item: TestingBacklog | None
raw_output: str
work_needed: str
candidate_sources: list[ClassMeta]
failing: FailingTest
failure_phase: str
accepted_alpha_summary: str = ""
structural_evidence: dict[str, Any] | None = None
semantic_evidence: dict[str, Any] | None = None
previous_beta_guidance: str = ""
beta_edit_diff: str = ""
beta_editor_summary: str = ""
focused_test: ClassMeta | None = None
summary: str = ""
upstream_classes: list[ClassMeta] = field(default_factory=list)
downstream_classes: list[ClassMeta] = field(default_factory=list)
original_cot: str = ""
change_type: ChangeType | None = None
failure_context_sources: list[ClassMeta] = field(default_factory=list)
future_guidance: tuple[FutureGuidance, ...] = ()
editor_records: list[EditorRecord] = field(default_factory=list)
def __post_init__(self) -> None:
"""Normalize either Beta backlog provenance or direct Alpha evidence into one story."""
testing_item = self.testing_item
# Snapshot established Beta backlog metadata into the owner-neutral routing story.
if testing_item is not None:
object.__setattr__(self, "focused_test", testing_item.focused_spotlight)
object.__setattr__(self, "summary", testing_item.summary)
object.__setattr__(self, "upstream_classes", list(testing_item.upstream_classes))
object.__setattr__(self, "downstream_classes", list(testing_item.downstream_classes))
object.__setattr__(self, "original_cot", testing_item.original_cot)
object.__setattr__(self, "change_type", testing_item.change_type)
object.__setattr__(self, "future_guidance", testing_item.future_guidance)
object.__setattr__(self, "editor_records", list(testing_item.editor_records))
object.__setattr__(
self,
"failure_context_sources",
list(testing_item.failure_context_sources),
)
# Reject stories that cannot identify both the failed test and its planned change kind.
if self.focused_test is None or self.change_type is None:
raise ValueError(
"TargetedFailureStory requires focused_test and change_type evidence."
)