From 38b6f5c00f49612c5fbd59677ff2eb78402b80cf Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 19 Jun 2026 23:29:10 -0400 Subject: [PATCH] TIER-2 READ conductor/code_styleguides/error_handling.md end-to-end before Phase 5: refactor(gui_2): migrate L1284 _populate_auto_slices outline to Result[T] (Phase 5) Extract _populate_auto_slices_outline_result helper from the mcp_client.{py,ts_c,ts_cpp}_get_code_outline try/except in App._populate_auto_slices. Legacy wrapper drains errors to app._last_request_errors per FR-BC-4 event-handler pattern. [pre-audit] L1284 INTERNAL_BROAD_CATCH [post-audit] V count: 14 -> 13 (L1284 removed) --- src/gui_2.py | 50 ++++++++++++++++++++++++++++++-------- tests/test_gui_2_result.py | 49 +++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 10 deletions(-) diff --git a/src/gui_2.py b/src/gui_2.py index 4d5953cc..49fe482d 100644 --- a/src/gui_2.py +++ b/src/gui_2.py @@ -1265,23 +1265,20 @@ class App: self.ai_status = 'paths applied and session reset' def _populate_auto_slices(self, f_item: models.FileItem) -> None: - from src import mcp_client import re from pathlib import Path import os proj_dir = str(Path(self.controller.active_project_path).parent.resolve()) if getattr(self, 'controller', None) and self.controller.active_project_path else None abs_path = f_item.path if os.path.isabs(f_item.path) else os.path.join(proj_dir or '.', f_item.path) mcp_client.configure([{"path": abs_path}], [proj_dir] if proj_dir else None) - - f_path_lower = f_item.path.lower() - #TODO(Ed): Exception(Review) - try: - if f_path_lower.endswith('.py'): outline = mcp_client.py_get_code_outline(abs_path) - elif f_path_lower.endswith(('.c', '.h')): outline = mcp_client.ts_c_get_code_outline(abs_path) - elif f_path_lower.endswith(('.cpp', '.hpp', '.cxx', '.cc')): outline = mcp_client.ts_cpp_get_code_outline(abs_path) - else: return - except Exception: + outline_result = _populate_auto_slices_outline_result(self, f_item, abs_path) + if not outline_result.ok: + if not hasattr(self, '_last_request_errors'): self._last_request_errors = [] + self._last_request_errors.append(("_populate_auto_slices.outline", outline_result.errors[0])) + return + outline = outline_result.data + if not outline: return if outline.startswith("ERROR") or outline.startswith("ACCESS DENIED"): @@ -7730,4 +7727,37 @@ def _render_ast_inspector_file_content_result(app: "App", f_path: str) -> Result #endregion: Phase 4 Modal/Dialog Result Helpers +#region: Phase 5 Event Handler Result Helpers (result_migration_gui_2_20260619) + +def _populate_auto_slices_outline_result(app: "App", f_item, abs_path: str) -> Result[str]: + """Drain-aware variant of L1284 _populate_auto_slices outline fetch. + + Extracts the mcp_client.{py,ts_c,ts_cpp}_get_code_outline try/except + from App._populate_auto_slices into a Result-returning helper. On success + (extension matches and outline fetch succeeded), returns + Result(data=outline). On "skip" (no matching extension), returns + Result(data=""). On exception, returns Result(data="", + errors=[ErrorInfo]). + + The legacy wrapper drains errors to app._last_request_errors (per FR-BC-4 + event-handler drain pattern; data plane attribute). + + [C: src/gui_2.py:App._populate_auto_slices (L1284 legacy wrapper)] + """ + f_path_lower = f_item.path.lower() + try: + if f_path_lower.endswith('.py'): return Result(data=mcp_client.py_get_code_outline(abs_path)) + elif f_path_lower.endswith(('.c', '.h')): return Result(data=mcp_client.ts_c_get_code_outline(abs_path)) + elif f_path_lower.endswith(('.cpp', '.hpp', '.cxx', '.cc')): return Result(data=mcp_client.ts_cpp_get_code_outline(abs_path)) + else: return Result(data="") + except Exception as e: + return Result(data="", errors=[ErrorInfo( + kind=ErrorKind.INTERNAL, + message=f"AST outline fetch failed for {f_item.path}: {e}", + source="gui_2._populate_auto_slices_outline_result", + original=e, + )]) + +#endregion: Phase 5 Event Handler Result Helpers + #endregion: MMA diff --git a/tests/test_gui_2_result.py b/tests/test_gui_2_result.py index 5eb80f89..e911121d 100644 --- a/tests/test_gui_2_result.py +++ b/tests/test_gui_2_result.py @@ -790,3 +790,52 @@ def test_phase_4_invariant_all_3_migration_sites_have_tests(): f"Phase 4 invariant: missing failure test for L{line}. " f"Expected a test matching '{failure_pattern}'." ) + + +# ============================================================================= +# Phase 5 Tests - Migration of 11 INTERNAL_BROAD_CATCH event-handler sites to Result[T] +# Each site gets 2 tests: success and failure. +# Migration pattern: legacy wrapper routes errors to app._last_request_errors (per FR-BC-4). +# ============================================================================= + + +def test_phase_5_l1284_populate_auto_slices_outline_result_success(): + """ + L1284 _populate_auto_slices_outline_result returns Result.ok=True on success. + + The helper wraps the mcp_client.{py,ts_c,ts_cpp}_get_code_outline try/except + in App._populate_auto_slices. On success (Python file extension matches), + returns Result(data=outline). + """ + from src import gui_2 + from unittest.mock import MagicMock, patch + app = MagicMock() + f_item = MagicMock() + f_item.path = "/proj/foo/src/bar.py" + abs_path = "/proj/foo/src/bar.py" + with patch.object(gui_2.mcp_client, "py_get_code_outline", return_value="[def] foo (Lines 1-10)"): + result = gui_2._populate_auto_slices_outline_result(app, f_item, abs_path) + assert result.ok, f"Expected ok=True on success, got errors: {result.errors}" + assert result.data == "[def] foo (Lines 1-10)" + + +def test_phase_5_l1284_populate_auto_slices_outline_result_failure(): + """ + L1284 _populate_auto_slices_outline_result returns Result.ok=False with ErrorInfo on failure. + + When the underlying mcp_client outline fetch raises, the helper converts the + exception to ErrorInfo and returns Result(data="", errors=[ErrorInfo]). + """ + from src import gui_2 + from unittest.mock import MagicMock, patch + app = MagicMock() + f_item = MagicMock() + f_item.path = "/proj/foo/src/bar.py" + abs_path = "/proj/foo/src/bar.py" + with patch.object(gui_2.mcp_client, "py_get_code_outline", side_effect=RuntimeError("outline fetch failed")): + result = gui_2._populate_auto_slices_outline_result(app, f_item, abs_path) + assert not result.ok, f"Expected ok=False on failure, got data: {result.data}" + assert result.errors, "Expected at least one error on failure" + err = result.errors[0] + assert err.source == "gui_2._populate_auto_slices_outline_result" + assert "outline fetch failed" in err.message