745 lines
34 KiB
Python
745 lines
34 KiB
Python
# SPDX-FileCopyrightText: 2026 Art Sea LLC
|
|
#
|
|
# SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import Callable
|
|
|
|
from band_aider.ai_client import llm_logging
|
|
from band_aider.ai_client.json_completion import load_json_completion
|
|
from band_aider.ai_client.openai_compatible_client import OpenAICompatibleClient
|
|
from band_aider.context.band_aider_facts import get_band_aider_facts
|
|
from band_aider.context.bootstrap_context import BootstrapContext
|
|
from band_aider.context.class_meta import ClassMeta
|
|
from band_aider.context.context_factory import context_factory
|
|
from band_aider.context.cot_task_generator import CotTaskRecord
|
|
from band_aider.context.global_context import GlobalContext, Intent, SovereignState
|
|
from band_aider.context.provenance import Provenance
|
|
from band_aider.context.resume_state import resume_settings
|
|
from band_aider.context.stabilization_context import StabilizationContext
|
|
from band_aider.context.traversed_frontier_history import TraversedFrontierHistory
|
|
from band_aider.judgement.code_parser import CodeParser
|
|
from band_aider.judgement.code_editor import CodeEditor
|
|
from band_aider.judgement.cross_cutting_concern_check import CrossCuttingConcernCheckFactory
|
|
from band_aider.judgement.current_cot_completed_check import CurrentCotCompletedCheckFactory
|
|
from band_aider.judgement.file_salience_check import FileSalienceCheckFactory
|
|
from band_aider.judgement.repository_idiom_check import RepositoryIdiomCheckFactory
|
|
from band_aider.judgement.tighten_design_work_check import TightenDesignWorkCheckFactory
|
|
from band_aider.judgement.intent_check import IntentCheckFactory
|
|
from band_aider.judgement.prompt_formatter import PromptFormatterBuilder, format_prompt
|
|
from band_aider.strategies import LanguageStrategySet
|
|
from band_aider.judgement.noop_gate import NoopGate
|
|
from band_aider.loop.bootstrap_loop import BootstrapLoop
|
|
from band_aider.loop.loop_gate import LoopGate
|
|
from band_aider.loop.mechanical_loop_gate import MechanicalLoopGate
|
|
from band_aider.loop.proto_loop import ProtoLoop
|
|
from band_aider.loop.stabilization_loop import StabilizationLoop
|
|
from band_aider.reporting.change_report_writer import ChangeReportWriter
|
|
from band_aider.ui.status import publish_cot_status, publish_loop_status
|
|
|
|
StabilizationGateFactory = Callable[[StabilizationContext], LoopGate]
|
|
|
|
|
|
@dataclass(init=False)
|
|
class SupremeLoop(ProtoLoop[LoopGate, GlobalContext]):
|
|
"""SupremeLoop wraps recursive, COT, and relevant-file loops around the base retry loop."""
|
|
|
|
file_salience_check_factory: FileSalienceCheckFactory | None = None
|
|
current_cot_completed_check_factory: CurrentCotCompletedCheckFactory | None = None
|
|
stabilization_gate_factory: StabilizationGateFactory | None = None
|
|
aider_worker: CodeEditor | None = None
|
|
cross_cutting_concern_check_factory: CrossCuttingConcernCheckFactory | None = None
|
|
repository_idiom_check_factory: RepositoryIdiomCheckFactory | None = None
|
|
tighten_design_work_check_factory: TightenDesignWorkCheckFactory | None = None
|
|
provenance: Provenance | None = None
|
|
|
|
def __init__(
|
|
self,
|
|
goal: str,
|
|
parser: CodeParser,
|
|
client: OpenAICompatibleClient,
|
|
repo_root: Path,
|
|
src_root: Path,
|
|
test_root: Path,
|
|
actor: str,
|
|
intent_check_factory: IntentCheckFactory,
|
|
prompt_formatter_builder: PromptFormatterBuilder,
|
|
original_prompt: str | None = None,
|
|
file_salience_check_factory: FileSalienceCheckFactory | None = None,
|
|
current_cot_completed_check_factory: CurrentCotCompletedCheckFactory | None = None,
|
|
stabilization_gate_factory: StabilizationGateFactory | None = None,
|
|
aider_worker: CodeEditor | None = None,
|
|
cross_cutting_concern_check_factory: CrossCuttingConcernCheckFactory | None = None,
|
|
repository_idiom_check_factory: RepositoryIdiomCheckFactory | None = None,
|
|
tighten_design_work_check_factory: TightenDesignWorkCheckFactory | None = None,
|
|
search_anchors: tuple[str, ...] = (),
|
|
parser_factory=None,
|
|
logger=None,
|
|
repo_exec=None,
|
|
language: str | None = None,
|
|
search_policy_scope: str = "broad",
|
|
search_policy_reason: str = "",
|
|
hydrate: bool | None = None,
|
|
context: GlobalContext | None = None,
|
|
provenance: Provenance | None = None,
|
|
) -> None:
|
|
"""Create SupremeLoop with a GlobalContext, no-op base gate, and optional edit worker."""
|
|
|
|
context_was_provided = context is not None
|
|
should_hydrate = False
|
|
if context is None:
|
|
context_kwargs = {
|
|
"goal": goal,
|
|
"parser": parser,
|
|
"client": client,
|
|
"repo_root": repo_root,
|
|
"src_root": src_root,
|
|
"test_root": test_root,
|
|
"actor": actor,
|
|
"prompt_formatter_builder": prompt_formatter_builder,
|
|
"intent_check_factory": intent_check_factory,
|
|
"original_prompt": original_prompt,
|
|
"search_anchors": tuple(search_anchors),
|
|
"parser_factory": parser_factory,
|
|
"search_policy_scope": search_policy_scope,
|
|
"search_policy_reason": search_policy_reason,
|
|
"repo_exec": repo_exec,
|
|
}
|
|
should_hydrate = resume_settings().hydrate if hydrate is None else hydrate
|
|
if should_hydrate:
|
|
context = context_factory.thaw(GlobalContext, **context_kwargs)
|
|
if context is None:
|
|
# A rejected checkpoint is not a hydrated run. Re-enter the
|
|
# ordinary fresh-context path so discovery and intent are built.
|
|
should_hydrate = False
|
|
context = context_factory.create(GlobalContext, **context_kwargs)
|
|
context.search_anchors = (
|
|
tuple(getattr(context, "search_anchors", ()))
|
|
if should_hydrate and not search_anchors
|
|
else tuple(search_anchors)
|
|
)
|
|
gate = NoopGate(context)
|
|
super().__init__(gate=gate, context=context, logger=logger)
|
|
context.logger = self.logger
|
|
if not context_was_provided and not should_hydrate:
|
|
context.sync()
|
|
self.logger.info(
|
|
"[probe] SupremeLoop context.sync complete relevant=%s "
|
|
"intent=%s",
|
|
len(getattr(context, "relevant_files", ())),
|
|
context.intent,
|
|
)
|
|
context.file_manager.capture_frozen_once()
|
|
# A direct Supreme caller owns its graph only when Sovereign did not supply one.
|
|
if provenance is None:
|
|
self.provenance = Provenance.open(
|
|
repo_root=context.file_manager.repo_root,
|
|
repo_exec=context.file_manager.repo_exec,
|
|
resume=resume_settings().hydrate,
|
|
)
|
|
else:
|
|
self.provenance = provenance
|
|
self.file_salience_check_factory = (
|
|
file_salience_check_factory or FileSalienceCheckFactory(
|
|
repo_root=context.file_manager.repo_root,
|
|
client=client,
|
|
prompt_formatter_builder=prompt_formatter_builder,
|
|
repo_exec=context.file_manager.repo_exec,
|
|
)
|
|
)
|
|
self.current_cot_completed_check_factory = (
|
|
current_cot_completed_check_factory
|
|
or CurrentCotCompletedCheckFactory(client, prompt_formatter_builder)
|
|
)
|
|
self.stabilization_gate_factory = stabilization_gate_factory
|
|
self.aider_worker = aider_worker
|
|
self.cross_cutting_concern_check_factory = (
|
|
cross_cutting_concern_check_factory
|
|
or CrossCuttingConcernCheckFactory(client, prompt_formatter_builder)
|
|
)
|
|
self.repository_idiom_check_factory = (
|
|
repository_idiom_check_factory
|
|
or RepositoryIdiomCheckFactory(client, prompt_formatter_builder)
|
|
)
|
|
self.tighten_design_work_check_factory = (
|
|
tighten_design_work_check_factory
|
|
or TightenDesignWorkCheckFactory(client, prompt_formatter_builder)
|
|
)
|
|
|
|
def _create_bootstrap_context(self) -> BootstrapContext:
|
|
"""Create a fresh bootstrap context from current pass relevance."""
|
|
|
|
visible_locations = [
|
|
candidate.location
|
|
for candidate in self.context.relevant_files
|
|
if self.context.file_manager.current_src(candidate.location) is not None
|
|
]
|
|
return context_factory.create(
|
|
BootstrapContext,
|
|
goal=self.context.goal,
|
|
classes=list(self.context.relevant_files),
|
|
upstream_file_manager=self.context.file_manager,
|
|
visible_locations=visible_locations,
|
|
search_anchors=self.context.search_anchors,
|
|
)
|
|
|
|
def _create_bootstrap_loop(self, *, thaw: bool = False) -> BootstrapLoop:
|
|
"""Create or thaw the bootstrap loop for reusable-construct mapping."""
|
|
|
|
# A resumed bootstrap must retain its checkpointed mapping progress.
|
|
if thaw:
|
|
bootstrap_context = context_factory.thaw(
|
|
BootstrapContext,
|
|
upstream_file_manager=self.context.file_manager,
|
|
)
|
|
else:
|
|
bootstrap_context = self._create_bootstrap_context()
|
|
bootstrap_context.search_anchors = self.context.search_anchors
|
|
return BootstrapLoop(
|
|
context=bootstrap_context,
|
|
language_strategy_set=LanguageStrategySet.get(self.context.file_manager),
|
|
aider_worker=self.aider_worker,
|
|
cross_cutting_concern_check_factory=self.cross_cutting_concern_check_factory,
|
|
repository_idiom_check_factory=self.repository_idiom_check_factory,
|
|
tighten_design_work_check_factory=self.tighten_design_work_check_factory,
|
|
logger=self.logger.getChild("bootstrap"),
|
|
)
|
|
|
|
def _run_bootstrap_if_needed(self, *, thaw: bool = False) -> str:
|
|
"""Run or resume bootstrap mapping and return a blocked summary when it stops."""
|
|
|
|
if self.context.bootstrap_completed:
|
|
return ""
|
|
self.context.bootstrap_started = True
|
|
self.context.bootstrap_completed = False
|
|
publish_loop_status(
|
|
previous_loop="SupremeLoop",
|
|
current_loop="BootstrapLoop",
|
|
next_loop="SupremeLoop",
|
|
current_item="Mapping: finding cross-cutting concerns",
|
|
)
|
|
loop = self._create_bootstrap_loop(thaw=thaw)
|
|
self._run(loop)
|
|
self.context.record_bootstrap_involved_locations(
|
|
list(
|
|
getattr(
|
|
loop.context,
|
|
"involved_locations",
|
|
list(loop.context.created_locations),
|
|
)
|
|
)
|
|
)
|
|
self.context.bootstrap_started = False
|
|
if loop.context.stage == "blocked":
|
|
self.context.bootstrap_completed = True
|
|
return f"Bootstrap mapping stopped: {loop.context.blocked_summary}"
|
|
self.context.bootstrap_completed = True
|
|
return ""
|
|
|
|
def _prepare_cot_after_bootstrap(self) -> bool:
|
|
"""Refresh in-memory relevance and break down ordinary COT tasks."""
|
|
|
|
story_cursor = self.context.story_cursor
|
|
self.context.refresh_relevant_files_from_current_metadata()
|
|
current_locations = {
|
|
candidate.location
|
|
for candidate in self.context.relevant_files
|
|
}
|
|
# Bootstrap discoveries remain part of this pass when their source still exists.
|
|
for location in story_cursor.bootstrap_involved_locations:
|
|
if location in current_locations:
|
|
continue
|
|
current = self.context.file_manager.current_src(location)
|
|
if current is None:
|
|
continue
|
|
self.context.relevant_files.append(current)
|
|
current_locations.add(location)
|
|
if not self.context.relevant_files:
|
|
return False
|
|
self.context.generate_cot_tasks()
|
|
return bool(story_cursor.cot_task_records)
|
|
|
|
def _bootstrap_surrogate_roots_for(self, root: ClassMeta) -> list[ClassMeta]:
|
|
"""Return bootstrap-involved sibling roots for the first stabilization story."""
|
|
|
|
story_cursor = self.context.story_cursor
|
|
task_cursor = story_cursor.task_cursor
|
|
bootstrap_locations = set(story_cursor.bootstrap_involved_locations)
|
|
# Surrogates only complete the first bootstrap-rooted stabilization story.
|
|
if (
|
|
task_cursor.current_cot_index != 0
|
|
or story_cursor.claimed_locations_this_recursive_pass
|
|
or root.location not in bootstrap_locations
|
|
):
|
|
return []
|
|
|
|
surrogates: list[ClassMeta] = []
|
|
for location in sorted(bootstrap_locations - {root.location}):
|
|
current = self.context.file_manager.current_src(location)
|
|
if current is None:
|
|
continue
|
|
surrogates.append(current)
|
|
return surrogates
|
|
|
|
def _create_stabilization_context(self, root: ClassMeta) -> StabilizationContext:
|
|
"""Create a fresh stabilization context around the selected root file."""
|
|
|
|
story_cursor = self.context.story_cursor
|
|
task_cursor = story_cursor.task_cursor
|
|
surrogate_roots = self._bootstrap_surrogate_roots_for(root)
|
|
context = context_factory.create(
|
|
StabilizationContext,
|
|
goal=(self.context.current_cot_task_record.task if self.context.current_cot_task_record else self.context.goal),
|
|
classes=self.context.classes,
|
|
upstream_file_manager=self.context.file_manager,
|
|
root_change=root,
|
|
surrogate_roots=surrogate_roots,
|
|
cot_index=task_cursor.current_cot_index,
|
|
total_cot=len(story_cursor.cot_task_records) or 1,
|
|
inherited_remediation_backlog=(
|
|
self.context.inherited_deferred_remediation_for_current_cot()
|
|
),
|
|
prior_traversed_frontiers=task_cursor.prior_traversed_frontiers,
|
|
)
|
|
return context
|
|
|
|
def _thaw_stabilization_context(self, root: ClassMeta) -> StabilizationContext:
|
|
"""Thaw the active stabilization context around the selected root file."""
|
|
|
|
story_cursor = self.context.story_cursor
|
|
task_cursor = story_cursor.task_cursor
|
|
context = context_factory.thaw(
|
|
StabilizationContext,
|
|
goal=(self.context.current_cot_task_record.task if self.context.current_cot_task_record else self.context.goal),
|
|
classes=self.context.classes,
|
|
upstream_file_manager=self.context.file_manager,
|
|
root_change=root,
|
|
cot_index=task_cursor.current_cot_index,
|
|
total_cot=len(story_cursor.cot_task_records) or 1,
|
|
)
|
|
# Resume cannot continue without the checkpoint that owns current COT state.
|
|
if context is None:
|
|
raise RuntimeError("Active StabilizationContext checkpoint was not available.")
|
|
return context
|
|
|
|
def _create_stabilization_gate(self, context: StabilizationContext) -> LoopGate:
|
|
"""Create the gate used by the stabilization loop for one selected root."""
|
|
|
|
if self.stabilization_gate_factory is not None:
|
|
return self.stabilization_gate_factory(context)
|
|
|
|
return MechanicalLoopGate(
|
|
context=context,
|
|
may_enter=lambda: (
|
|
context.upstream_file_manager is not None
|
|
and bool(context.frontier[0])
|
|
),
|
|
should_continue=lambda: True,
|
|
)
|
|
|
|
def _create_stabilization_loop(self, root: ClassMeta) -> StabilizationLoop:
|
|
"""Create a fresh stabilization loop for the selected root file."""
|
|
|
|
context = self._create_stabilization_context(root)
|
|
gate = self._create_stabilization_gate(context)
|
|
previous_root_location = (
|
|
self.context.previous_cot_root_location_for_current_cot()
|
|
)
|
|
previous_cot_root_readonly_file = (
|
|
self.context.file_manager.source_path(previous_root_location)
|
|
if previous_root_location is not None
|
|
else None
|
|
)
|
|
return StabilizationLoop(
|
|
gate=gate,
|
|
context=context,
|
|
language_strategy_set=LanguageStrategySet.get(self.context.file_manager),
|
|
aider_worker=self.aider_worker,
|
|
previous_cot_root_readonly_file=previous_cot_root_readonly_file,
|
|
previous_cot_records=self.context.previous_cot_records_for_current_cot(),
|
|
future_cot_records=self.context.future_cot_records_for_current_cot(),
|
|
provenance=self.provenance,
|
|
logger=self.logger.getChild("stabilization"),
|
|
)
|
|
|
|
def _thaw_stabilization_loop(self, root: ClassMeta) -> StabilizationLoop:
|
|
"""Thaw the active stabilization loop for the selected root file."""
|
|
|
|
context = self._thaw_stabilization_context(root)
|
|
gate = self._create_stabilization_gate(context)
|
|
previous_root_location = (
|
|
self.context.previous_cot_root_location_for_current_cot()
|
|
)
|
|
previous_cot_root_readonly_file = (
|
|
self.context.file_manager.source_path(previous_root_location)
|
|
if previous_root_location is not None
|
|
else None
|
|
)
|
|
return StabilizationLoop(
|
|
gate=gate,
|
|
context=context,
|
|
language_strategy_set=LanguageStrategySet.get(self.context.file_manager),
|
|
aider_worker=self.aider_worker,
|
|
previous_cot_root_readonly_file=previous_cot_root_readonly_file,
|
|
previous_cot_records=self.context.previous_cot_records_for_current_cot(),
|
|
future_cot_records=self.context.future_cot_records_for_current_cot(),
|
|
provenance=self.provenance,
|
|
logger=self.logger.getChild("stabilization"),
|
|
)
|
|
|
|
def _post_run(self) -> str:
|
|
"""Return the summary produced for one selected stabilization root."""
|
|
|
|
story_cursor = self.context.story_cursor
|
|
task_cursor = story_cursor.task_cursor
|
|
root = task_cursor.active_root
|
|
# A child summary is meaningful only while a root owns the active story.
|
|
if root is None:
|
|
return "No stabilization root was selected."
|
|
|
|
child_summaries = "\n".join(
|
|
story_cursor.marginal_result[
|
|
task_cursor.marginal_result_start:
|
|
]
|
|
)
|
|
# Preserve the distinction between an empty child result and completed work.
|
|
if not child_summaries.strip():
|
|
return f"Stabilization for {root.location} produced no child summary."
|
|
return f"Stabilization for {root.location} completed with summary:\n{child_summaries}"
|
|
|
|
def _prepare_work(self) -> ProtoLoop | None:
|
|
"""Return the stabilization loop for the active root selected by the backlog loop."""
|
|
|
|
task_cursor = self.context.story_cursor.task_cursor
|
|
active_root = task_cursor.active_root
|
|
# The protocol has no child work until the backlog selects a concrete root.
|
|
if active_root is None:
|
|
return None
|
|
loop = self._create_stabilization_loop(active_root)
|
|
return loop
|
|
|
|
def _active_stabilization_checkpoint_matches(self) -> bool:
|
|
"""Return whether the latest stabilization checkpoint matches the active root."""
|
|
|
|
task_cursor = self.context.story_cursor.task_cursor
|
|
root = task_cursor.active_root
|
|
# No selected root means there is no child checkpoint to resume.
|
|
if root is None:
|
|
return False
|
|
|
|
payload = context_factory.latest_payload_for(StabilizationContext)
|
|
# Only an existing payload can prove that the selected root is resumable.
|
|
if payload is None:
|
|
return False
|
|
|
|
checkpoint_root = payload.get("root_change")
|
|
return (
|
|
isinstance(checkpoint_root, ClassMeta)
|
|
and checkpoint_root.location == root.location
|
|
and payload.get("cot_index")
|
|
== task_cursor.current_cot_index
|
|
)
|
|
|
|
def _cot_record_completed(self, record: CotTaskRecord) -> bool:
|
|
"""Evaluate one completed COT root after stabilization has finished."""
|
|
|
|
if self.current_cot_completed_check_factory is None:
|
|
return True
|
|
current_root = self.context.file_manager.current_src(record.root.location)
|
|
root = current_root or record.root
|
|
check = self.current_cot_completed_check_factory.create(
|
|
self.context.original_prompt
|
|
)
|
|
return check(record.task, [root])
|
|
|
|
def _capture_traversed_frontier_history(
|
|
self,
|
|
stabilization_context: StabilizationContext,
|
|
) -> None:
|
|
"""Persist this accepted COT's local traversal as the next lineage head."""
|
|
|
|
task_cursor = self.context.story_cursor.task_cursor
|
|
previous = task_cursor.prior_traversed_frontiers
|
|
# A crash after this persist but before root completion must not duplicate a node.
|
|
if (
|
|
previous is not None
|
|
and previous.cot_index == stabilization_context.cot_index
|
|
and previous.root_location == stabilization_context.root_change.location
|
|
):
|
|
return
|
|
task_cursor.prior_traversed_frontiers = TraversedFrontierHistory(
|
|
cot_index=stabilization_context.cot_index,
|
|
root_location=stabilization_context.root_change.location,
|
|
frontiers=stabilization_context.traversed_frontier_snapshot(),
|
|
previous=previous,
|
|
)
|
|
self.context.persist()
|
|
|
|
def _stabilize_root(self, root: ClassMeta, *, thaw: bool = False) -> bool:
|
|
"""Run one root and advance only after local acceptance and durable handoff."""
|
|
|
|
task_cursor = self.context.story_cursor.task_cursor
|
|
self.context.mark_root_visited(root)
|
|
self.context.stabilization_started = True
|
|
publish_loop_status(
|
|
previous_loop=type(self).__name__,
|
|
current_loop="StabilizationLoop",
|
|
current_item=root.location,
|
|
)
|
|
# Resume must reuse the durable child context; a fresh COT creates one.
|
|
if thaw:
|
|
loop = self._thaw_stabilization_loop(root)
|
|
else:
|
|
loop = self._create_stabilization_loop(root)
|
|
# An accepted checkpoint needs only its parent-owned handoff retried on resume.
|
|
if not loop.context.accepted:
|
|
self._run(loop)
|
|
# Retain the active root and its child checkpoint when current-owned work remains unresolved.
|
|
if not loop.context.accepted:
|
|
self.logger.warning(
|
|
"COT %s for %s remains incomplete within current ownership: %s",
|
|
task_cursor.current_cot_index,
|
|
root.location,
|
|
loop.context.blocked_summary or "local work remains",
|
|
)
|
|
return False
|
|
handoff = loop.context.remaining_deferred_remediation
|
|
# Persist child-owned remediation before this root can leave the active COT.
|
|
if handoff.inherited_items or handoff.deferred_items:
|
|
if not self.context.handoff_deferred_remediation_to_next_cot(handoff):
|
|
self.logger.warning(
|
|
"COT %s for %s is locally accepted but deferred remediation remains untransferred.",
|
|
task_cursor.current_cot_index,
|
|
root.location,
|
|
)
|
|
return False
|
|
self.logger.info(
|
|
"Current COT closed with %s deferred source-remediation item(s) transferred.",
|
|
len(handoff.inherited_items) + len(handoff.deferred_items),
|
|
)
|
|
self._capture_traversed_frontier_history(loop.context)
|
|
story_roots = list(loop.context.t0)
|
|
summary = self._post_run()
|
|
self.context.mark_root_stabilized(root, summary, story_roots=story_roots)
|
|
return True
|
|
|
|
def _summarize_recursive_run(self, completed: bool) -> str:
|
|
"""Return the final supreme-loop summary from all recursive-pass summaries."""
|
|
|
|
story_cursor = self.context.story_cursor
|
|
net_effects = {
|
|
**self.context.file_manager.src_net_effects(),
|
|
**self.context.file_manager.test_net_effects(),
|
|
}
|
|
expected = {
|
|
"summary": "SUCCESS or FAILED followed by one concise sentence",
|
|
}
|
|
prompt = (
|
|
"Summarize this SupremeLoop run.\n"
|
|
"Start with SUCCESS if the original request appears satisfied, otherwise FAILED.\n"
|
|
"Use the loop summaries, current CDC evidence, and net effects as evidence.\n\n"
|
|
"Return strict JSON only with this shape:\n"
|
|
f"{json.dumps(expected, indent=2)}\n\n"
|
|
f"Original request:\n{self.context.original_prompt}\n\n"
|
|
f"Completed by relevance fixed point:\n{completed}\n\n"
|
|
f"Net effects:\n{json.dumps(net_effects, indent=2)}\n\n"
|
|
"Loop summaries:\n"
|
|
f"{json.dumps(story_cursor.run_summaries, indent=2)}"
|
|
)
|
|
data = load_json_completion(
|
|
self.context.client,
|
|
format_prompt(
|
|
self.context.prompt_formatter_builder,
|
|
"SUPREME SUMMARY",
|
|
prompt,
|
|
),
|
|
prompt_formatter_builder=self.context.prompt_formatter_builder,
|
|
references=[expected],
|
|
)
|
|
return str(data.get("summary") or ("SUCCESS" if completed else "FAILED"))
|
|
|
|
def _report_cot_tasks(self) -> list[str]:
|
|
"""Return the COT task list that should be persisted in the final report."""
|
|
|
|
story_cursor = self.context.story_cursor
|
|
tasks = [record.task for record in story_cursor.cot_task_records if record.task.strip()]
|
|
if tasks:
|
|
return tasks
|
|
history = story_cursor.stabilization_summaries_by_cot
|
|
if isinstance(history, dict):
|
|
return [str(task) for task in history if str(task).strip()]
|
|
return []
|
|
|
|
def _write_change_report(self, result: str) -> Path:
|
|
"""Persist the final markdown change report for the completed run."""
|
|
|
|
writer = ChangeReportWriter(repo_root=self.context.repo_root)
|
|
counts = (
|
|
writer.changed_file_counts(self.context.file_manager)
|
|
if hasattr(writer, "changed_file_counts")
|
|
else {}
|
|
)
|
|
path = writer.write(
|
|
original_job=self.context.original_prompt,
|
|
cot_tasks=self._report_cot_tasks(),
|
|
file_manager=self.context.file_manager,
|
|
final_summary=result,
|
|
)
|
|
facts = get_band_aider_facts()
|
|
facts.record_report(
|
|
report_path=path,
|
|
files_modified=int(counts.get("changed", 0) or 0),
|
|
files_added=int(counts.get("created", 0) or 0),
|
|
)
|
|
self._persist_facts_if_configured()
|
|
return path
|
|
|
|
def _persist_facts_if_configured(self) -> None:
|
|
"""Persist facts when the CLI has configured resume settings."""
|
|
|
|
try:
|
|
settings = resume_settings()
|
|
except RuntimeError:
|
|
return
|
|
checkpoint_dir = settings.checkpoint_dir
|
|
facts = get_band_aider_facts()
|
|
facts.persist(checkpoint_dir)
|
|
|
|
def run(self, logger_name: str | None = None) -> str:
|
|
"""Run recursive request passes, COT tasks, and relevant files around the base retry loop."""
|
|
|
|
if not self._logger_stack: self._logger_stack.append(llm_logging.logger_for_loop(self))
|
|
if logger_name: self._logger_stack.append(self.logger.getChild(llm_logging.safe_log_segment(logger_name)))
|
|
llm_logging.set_current_logger(self.logger)
|
|
try:
|
|
story_cursor = self.context.story_cursor
|
|
task_cursor = story_cursor.task_cursor
|
|
resume_stabilization = self.context.stabilization_started
|
|
# A retained active root resumes only when its child checkpoint still matches.
|
|
if resume_stabilization:
|
|
if (
|
|
task_cursor.active_root is not None
|
|
and not self._active_stabilization_checkpoint_matches()):
|
|
self.context.stabilization_started = False
|
|
# continue
|
|
start_index = task_cursor.current_cot_index
|
|
elif (
|
|
getattr(self.context, "bootstrap_started", False)
|
|
and not getattr(self.context, "bootstrap_completed", False)
|
|
):
|
|
bootstrap_blocked = self._run_bootstrap_if_needed(thaw=True)
|
|
# Bootstrap blockage is evidence; Stabilization remains the repair owner.
|
|
if bootstrap_blocked:
|
|
self.logger.warning(
|
|
"Bootstrap mapping stopped; attempting serious repair in stabilization: %s",
|
|
bootstrap_blocked,
|
|
)
|
|
story_cursor.run_summaries.append(bootstrap_blocked)
|
|
self.context.persist()
|
|
if not self._prepare_cot_after_bootstrap():
|
|
completed = not bool(self.context.relevant_files)
|
|
result = self._summarize_recursive_run(completed=completed)
|
|
self._finish_story(result)
|
|
return result
|
|
start_index = 0
|
|
elif (
|
|
getattr(self.context, "bootstrap_completed", False)
|
|
and not story_cursor.cot_task_records
|
|
):
|
|
if not self._prepare_cot_after_bootstrap():
|
|
completed = not bool(self.context.relevant_files)
|
|
result = self._summarize_recursive_run(completed=completed)
|
|
self._finish_story(result)
|
|
return result
|
|
start_index = 0
|
|
elif story_cursor.cot_task_records:
|
|
start_index = task_cursor.current_cot_index
|
|
else:
|
|
if not self.context.begin_recursive_pass():
|
|
completed = not bool(self.context.relevant_files)
|
|
result = self._summarize_recursive_run(completed=completed)
|
|
self._finish_story(result)
|
|
return result
|
|
if self.context.intent and self.context.intent & Intent.FEATURE:
|
|
bootstrap_blocked = self._run_bootstrap_if_needed()
|
|
# Bootstrap blockage is evidence; Stabilization remains the repair owner.
|
|
if bootstrap_blocked:
|
|
self.logger.warning(
|
|
"Bootstrap mapping stopped; attempting serious repair in stabilization: %s",
|
|
bootstrap_blocked,
|
|
)
|
|
story_cursor.run_summaries.append(bootstrap_blocked)
|
|
self.context.persist()
|
|
if not self._prepare_cot_after_bootstrap():
|
|
completed = not bool(self.context.relevant_files)
|
|
result = self._summarize_recursive_run(completed=completed)
|
|
self._finish_story(result)
|
|
return result
|
|
start_index = 0
|
|
|
|
# Each planned COT owns one sequential root stabilization attempt.
|
|
for index in range(start_index, len(story_cursor.cot_task_records)):
|
|
cot_started = get_band_aider_facts().begin_cot()
|
|
self.context.set_current_cot_task(index);
|
|
publish_cot_status(self.context)
|
|
self._logger_stack.append(self.logger.getChild(
|
|
llm_logging.safe_log_segment(f"{index}_{self.context.current_cot_task_record.task if self.context.current_cot_task_record else 'cot'}")));
|
|
llm_logging.set_current_logger(self.logger)
|
|
try:
|
|
if (
|
|
resume_stabilization
|
|
and task_cursor.active_root is not None
|
|
and task_cursor.active_stabilization_key == f"{self.context.current_cot_key()}:{task_cursor.active_root.location}"
|
|
and task_cursor.active_root.location not in self.context.stabilized_locations_for_current_cot()):
|
|
stabilized = self._stabilize_root(
|
|
task_cursor.active_root,
|
|
thaw=True,
|
|
)
|
|
if stabilized is False:
|
|
return self._post_run()
|
|
resume_stabilization = False
|
|
continue
|
|
elif (
|
|
resume_stabilization
|
|
and task_cursor.active_root is None
|
|
):
|
|
resume_stabilization = False
|
|
continue
|
|
resume_stabilization = False
|
|
|
|
record = self.context.current_cot_task_record
|
|
if record is None:
|
|
raise RuntimeError(
|
|
"COT task record is missing for the active COT index"
|
|
)
|
|
if self._stabilize_root(record.root) is False:
|
|
return self._post_run()
|
|
finally:
|
|
facts = get_band_aider_facts()
|
|
facts.finish_cot(cot_started)
|
|
self._persist_facts_if_configured()
|
|
self._logger_stack.pop()
|
|
llm_logging.set_current_logger(self.logger)
|
|
self.context.stabilization_started = False
|
|
completed = all(map(self._cot_record_completed, story_cursor.cot_task_records))
|
|
result = self._summarize_recursive_run(completed=completed)
|
|
self._finish_story(result)
|
|
return result
|
|
finally:
|
|
if logger_name: self._logger_stack.pop()
|
|
llm_logging.set_current_logger(self.logger if self._logger_stack else None)
|
|
|
|
def _finish_story(self, result: str) -> None:
|
|
"""Persist story completion boundary independently of CDC success."""
|
|
|
|
completion_state = SovereignState.BETWEEN_STORIES
|
|
self.context.supreme_result = result
|
|
self.context.supreme_completion_state = completion_state
|
|
persist = getattr(self.context, "persist", None)
|
|
# Some focused contexts intentionally omit checkpoint persistence.
|
|
if callable(persist):
|
|
persist()
|