refactor(app_controller): migrate _cb_plan_epic, _cb_accept_tracks, _start_track_logic to Result (Phase 6 Groups 6.5+6.7 partial)
Migrates the 3 _bg_task closures in _cb_plan_epic and _cb_accept_tracks plus the 2 try/except sites in _start_track_logic to proper Result[T] propagation. Each worker closure now returns Result[None]; the _start_track_logic helper wraps the whole pipeline. New helper: - _topological_sort_tickets_result(raw_tickets, title) -> Result[list] (Phase 6 Group 6.7: dependency error is now a proper ErrorInfo in the Result, not a silent debug log) Audit: INTERNAL_SILENT_SWALLOW for src/app_controller.py: 17 -> 12.
This commit is contained in:
+48
-18
@@ -4417,7 +4417,7 @@ class AppController:
|
||||
"""
|
||||
[C: src/gui_2.py:App._render_mma_epic_planner, tests/test_mma_orchestration_gui.py:test_cb_plan_epic_launches_thread]
|
||||
"""
|
||||
def _bg_task() -> None:
|
||||
def _bg_task() -> "Result[None]":
|
||||
try:
|
||||
self.ai_status = "Planning Epic (Tier 1)..."
|
||||
history = orchestrator_pm.get_track_history_summary()
|
||||
@@ -4454,10 +4454,15 @@ class AppController:
|
||||
"action": "show_track_proposal",
|
||||
"payload": tracks
|
||||
})
|
||||
return OK
|
||||
except (OSError, IOError, ValueError, TypeError, KeyError, AttributeError, RuntimeError) as e:
|
||||
logging.getLogger(__name__).debug("Epic plan error: %s", e, extra={"source": "app_controller._cb_plan_epic._bg_task"})
|
||||
err = ErrorInfo(kind=ErrorKind.INTERNAL, message=str(e),
|
||||
source="app_controller._cb_plan_epic._bg_task", original=e)
|
||||
self.ai_status = f"Epic plan error: {e}"
|
||||
print(f"ERROR in _cb_plan_epic background task: {e}")
|
||||
result = Result(data=None, errors=[err])
|
||||
self._report_worker_error("cb_plan_epic", result)
|
||||
return result
|
||||
self.submit_io(_bg_task)
|
||||
|
||||
def _cb_accept_tracks(self) -> None:
|
||||
@@ -4466,7 +4471,7 @@ class AppController:
|
||||
"""
|
||||
self._show_track_proposal_modal = False
|
||||
|
||||
def _bg_task() -> None:
|
||||
def _bg_task() -> "Result[None]":
|
||||
# Generate skeletons once
|
||||
self.ai_status = "Phase 2: Generating skeletons for all tracks..."
|
||||
parser = ASTParser(language="python")
|
||||
@@ -4484,12 +4489,16 @@ class AppController:
|
||||
code = f.read()
|
||||
generated_skeletons += f"\nFile: {f_path}\n{parser.get_skeleton(code)}\n"
|
||||
except (OSError, IOError, UnicodeDecodeError) as e:
|
||||
logging.getLogger(__name__).debug("skeleton file read failed for %s: %s", f_path, e, extra={"source": "app_controller._cb_accept_tracks._bg_task.per_file"})
|
||||
pass
|
||||
err = ErrorInfo(kind=ErrorKind.INTERNAL, message=str(e),
|
||||
source=f"app_controller._cb_accept_tracks._bg_task.per_file[{f_path}]", original=e)
|
||||
self._report_worker_error(f"cb_accept_tracks.per_file[{f_path}]", Result(data=None, errors=[err]))
|
||||
except (OSError, IOError, ValueError, TypeError, KeyError, AttributeError, RuntimeError) as e:
|
||||
logging.getLogger(__name__).debug("skeleton generation error: %s", e, extra={"source": "app_controller._cb_accept_tracks._bg_task"})
|
||||
err = ErrorInfo(kind=ErrorKind.INTERNAL, message=str(e),
|
||||
source="app_controller._cb_accept_tracks._bg_task", original=e)
|
||||
self.ai_status = f"Error generating skeletons: {e}"
|
||||
return # Exit if skeleton generation fails
|
||||
result = Result(data=None, errors=[err])
|
||||
self._report_worker_error("cb_accept_tracks", result)
|
||||
return result
|
||||
# Now loop through tracks and call _start_track_logic with generated skeletons
|
||||
total_tracks = len(self.proposed_tracks)
|
||||
print(f"[DEBUG] _cb_accept_tracks: Starting {total_tracks} tracks...")
|
||||
@@ -4501,6 +4510,7 @@ class AppController:
|
||||
with self._pending_gui_tasks_lock:
|
||||
self._pending_gui_tasks.append({'action': 'refresh_from_project'}) # Ensure UI refresh after tracks are started
|
||||
self.ai_status = f"All {total_tracks} tracks accepted and execution started."
|
||||
return OK
|
||||
self.submit_io(_bg_task)
|
||||
|
||||
def _cb_start_track(self, user_data: Any = None) -> None:
|
||||
@@ -4549,7 +4559,30 @@ class AppController:
|
||||
self.submit_io(lambda: self._start_track_logic(track_data))
|
||||
self.ai_status = f"Track '{title}' started."
|
||||
|
||||
def _topological_sort_tickets_result(self, raw_tickets: list, title: str) -> "Result[list]":
|
||||
"""Phase 6 Group 6.7: topological sort with Result propagation.
|
||||
On ValueError: fall back to raw_tickets (preserves existing behavior)."""
|
||||
try:
|
||||
sorted_tickets_data = conductor_tech_lead.topological_sort(raw_tickets)
|
||||
return Result(data=sorted_tickets_data)
|
||||
except ValueError as e:
|
||||
err = ErrorInfo(kind=ErrorKind.INVALID_INPUT, message=str(e),
|
||||
source=f"app_controller._topological_sort_tickets_result[{title}]", original=e)
|
||||
print(f"Dependency error in track '{title}': {e}")
|
||||
self._report_worker_error(f"topological_sort[{title}]", Result(data=None, errors=[err]))
|
||||
return Result(data=raw_tickets, errors=[err])
|
||||
|
||||
def _start_track_logic(self, track_data: dict[str, Any], skeletons_str: str | None = None) -> None:
|
||||
result = self._start_track_logic_result(track_data, skeletons_str)
|
||||
if not result.ok:
|
||||
err = result.errors[0]
|
||||
self.ai_status = f"Track start error: {err.message}"
|
||||
print(f"ERROR in _start_track_logic: {err.message}")
|
||||
|
||||
def _start_track_logic_result(self, track_data: dict[str, Any], skeletons_str: str | None = None) -> "Result[None]":
|
||||
"""Phase 6 Group 6.7: track-start pipeline with Result propagation.
|
||||
On any unexpected failure: ErrorInfo(original=e). Caller drains via
|
||||
stderr write + ai_status update."""
|
||||
try:
|
||||
goal = track_data.get("goal", "")
|
||||
title = track_data.get("title") or track_data.get("goal", "Untitled Track")
|
||||
@@ -4575,15 +4608,11 @@ class AppController:
|
||||
if not raw_tickets:
|
||||
self.ai_status = f"Error: No tickets generated for track: {title}"
|
||||
print(f"Warning: No tickets generated for track: {title}")
|
||||
return
|
||||
return OK
|
||||
self.ai_status = "Phase 2: Sorting tickets..."
|
||||
try:
|
||||
sorted_tickets_data = conductor_tech_lead.topological_sort(raw_tickets)
|
||||
except ValueError as e:
|
||||
logging.getLogger(__name__).debug("dependency error in track '%s': %s", title, e, extra={"source": "app_controller._start_track_logic.topological_sort"})
|
||||
print(f"Dependency error in track '{title}': {e}")
|
||||
sorted_tickets_data = raw_tickets
|
||||
# 3. Create Track and Ticket objects
|
||||
sort_result = self._topological_sort_tickets_result(raw_tickets, title)
|
||||
sorted_tickets_data = sort_result.data
|
||||
# 3. Create Track and Ticket objects
|
||||
tickets = []
|
||||
for t_data in sorted_tickets_data:
|
||||
ticket = models.Ticket(
|
||||
@@ -4625,10 +4654,11 @@ class AppController:
|
||||
self.submit_io(engine.run, md_content=full_md)
|
||||
sys.stderr.write(f"[DEBUG] _start_track_logic: Engine thread spawned for {track_id}.\n")
|
||||
sys.stderr.flush()
|
||||
return OK
|
||||
except (OSError, IOError, ValueError, TypeError, KeyError, AttributeError, RuntimeError) as e:
|
||||
logging.getLogger(__name__).debug("track start error: %s", e, extra={"source": "app_controller._start_track_logic"})
|
||||
self.ai_status = f"Track start error: {e}"
|
||||
print(f"ERROR in _start_track_logic: {e}")
|
||||
err = ErrorInfo(kind=ErrorKind.INTERNAL, message=str(e),
|
||||
source="app_controller._start_track_logic_result", original=e)
|
||||
return Result(data=None, errors=[err])
|
||||
|
||||
def _cb_ticket_retry(self, ticket_id: str) -> None:
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user