Merge branch 'tier2/result_migration_gui_2_20260619'

This commit is contained in:
ed
2026-06-20 07:23:02 -04:00
20 changed files with 7970 additions and 472 deletions
File diff suppressed because it is too large Load Diff
+139
View File
@@ -0,0 +1,139 @@
# Phase 1 Site Inventory — src/gui_2.py
## Phase Summary
| Phase | Count | Description |
|-------|-------|-------------|
| Phase 3 | 8 | Render-loop sites (called every frame, must not break rendering) |
| Phase 4 | 3 | Modal/dialog sites (can trigger imgui.open_popup inline) |
| Phase 5 | 13 | Event handler sites (accumulate in app._last_request_errors or similar) |
| Phase 7 | 1 | Worker/background sites (use app._report_worker_error; thread-safety) |
| Phase 8 | 4 | Property setter / state mutation / startup callback sites |
| Phase 9 | 1 | Helper/utility module-level sites |
| Phase 10 | 8 | INTERNAL_SILENT_SWALLOW sites (logging-only bodies, sliming-prone) |
| Phase 11 | 2 | INTERNAL_RETHROW classification (2 rethrow sites) |
| Phase 12 | 2 | UNCLEAR classification (lazy module loading, need Phase 1 audit review) |
**Total: 42 sites**
---
## Site Inventory
| L# | Category | Phase | Context | Migration Target | Rationale |
|----|----------|-------|---------|------------------|-----------|
| 65 | UNCLEAR | 12 | _resolve | Retain lazy-loading fallback; document as intentional sentinel pattern | Lazy module loader fallback; AttributeError caught and leads to submodule attempt; not sliming |
| 69 | UNCLEAR | 12 | _resolve | Retain lazy-loading fallback; document as intentional sentinel pattern | ImportError/ModuleNotFoundError caught and returns _FiledialogStub; legitimate fallback |
| 216 | INTERNAL_SILENT_SWALLOW | 10 | _detect_refresh_rate_win32 | Accumulate in app._last_request_errors via app._append_diagnostic_error | Logging-only body; returns 0.0 fallback; sliming-prone |
| 241 | INTERNAL_SILENT_SWALLOW | 10 | _resolve_font_path | Accumulate in app._last_request_errors | Logging-only body at thirdparty boundary; returns fallback path silently |
| 567 | INTERNAL_SILENT_SWALLOW | 10 | _post_init | Phase 8 startup callback — accumulate via app._append_diagnostic_error | Startup callback; calls _diag_layout_state which logs to stderr |
| 591 | INTERNAL_BROAD_CATCH | 8 | _diag_layout_state | _render_diag_layout_result() -> Result[None, ErrorInfo] | One-shot startup diagnostic; uses sys.stderr.write; should use Result-drain helper |
| 684 | INTERNAL_SILENT_SWALLOW | 10 | run | Phase 8 startup guard — accumulate via app._append_diagnostic_error | Startup exception guard for immapp.run; logs to stderr then returns |
| 731 | INTERNAL_BROAD_CATCH | 3 | _load_fonts | _render_load_fonts_result() -> Result[None, ErrorInfo] | Called from run() at startup; thirdparty font loading; must not break render |
| 742 | INTERNAL_BROAD_CATCH | 3 | _load_fonts | _render_load_fonts_result() -> Result[None, ErrorInfo] | Second thirdparty font loading call; same helper as line 731 |
| 757 | INTERNAL_RETHROW | 11 | __getattr__ | Pattern 1: reraise AttributeError as ErrorInfo(kind=PROGRAMMER_ERROR) | First raise AttributeError — programmer raised, not caught then rethrown |
| 760 | INTERNAL_RETHROW | 11 | __getattr__ | Pattern 1: reraise AttributeError as ErrorInfo(kind=PROGRAMMER_ERROR) | Second raise AttributeError — programmer raised, not caught then rethrown |
| 905 | INTERNAL_BROAD_CATCH | 8 | _capture_workspace_profile | _capture_workspace_profile_result() -> Result[str, ErrorInfo] | Property setter-equivalent; imgui.save_ini_settings_to_memory thirdparty call |
| 979 | INTERNAL_SILENT_SWALLOW | 10 | shutdown | Phase 8 shutdown method — accumulate via app._append_diagnostic_error | Shutdown handler; bare except: swallows all errors silently |
| 1079 | INTERNAL_SILENT_SWALLOW | 8 | _gui_func | _render_first_frame_timing_result() -> Result[None, ErrorInfo] | First-frame callback timing; not in render hot path; uses sys.stderr.write |
| 1123 | INTERNAL_BROAD_CATCH | 3 | _gui_func | _render_main_interface_result() -> Result[None, ErrorInfo] | Render loop site; render_main_interface(self) called every frame |
| 1172 | INTERNAL_BROAD_CATCH | 3 | _show_menus | _render_show_menus_result() -> Result[None, ErrorInfo] | Render-loop menu bar; calls thirdparty win32gui functions every frame |
| 1198 | INTERNAL_BROAD_CATCH | 3 | _show_menus | _render_show_menus_result() -> Result[None, ErrorInfo] | Second win32gui call in _show_menus; same helper |
| 1223 | INTERNAL_BROAD_CATCH | 3 | _show_menus | _render_show_menus_result() -> Result[None, ErrorInfo] | Third win32gui call in _show_menus; same helper |
| 1285 | INTERNAL_BROAD_CATCH | 3 | _handle_history_logic | _render_history_logic_result() -> Result[None, ErrorInfo] | Render-loop history handler; called every frame |
| 1335 | INTERNAL_BROAD_CATCH | 5 | _populate_auto_slices | Accumulate in app._last_request_errors via _handle_mcp_error | Event handler; mcp_client calls; result accumulates in error state |
| 1344 | INTERNAL_BROAD_CATCH | 5 | _populate_auto_slices | Accumulate in app._last_request_errors via _handle_mcp_error | Second mcp_client call; same error drain |
| 1398 | INTERNAL_SILENT_SWALLOW | 9 | _close_vscode_diff | _handle_close_vscode_diff_result() -> Result[None, ErrorInfo] | Helper/utility method; process cleanup; exceptions drained not swallowed |
| 1418 | INTERNAL_BROAD_CATCH | 5 | _apply_pending_patch | Accumulate in app._last_request_errors via _handle_patch_error | Event handler for patch modal; error goes to modal message |
| 1444 | INTERNAL_BROAD_CATCH | 5 | _open_patch_in_external_editor | Accumulate in app._last_request_errors via _handle_patch_error | Event handler for external editor launch; exceptions set _patch_error_message |
| 1479 | INTERNAL_BROAD_CATCH | 5 | request_patch_from_tier4 | Accumulate in app._last_request_errors via _handle_tier4_error | Event handler; calls run_tier4_patch_generation; error drains to modal |
| 1593 | INTERNAL_SILENT_SWALLOW | 10 | render_main_interface | Phase 3 render — use _render_main_interface_result() not sys.stderr | Called from _gui_func render loop; exception logged to stderr |
| 1619 | INTERNAL_SILENT_SWALLOW | 10 | render_main_interface | Phase 3 render — use _render_main_interface_result() not sys.stderr | Second logging site in render_main_interface; auto-save failure |
| 3214 | INTERNAL_BROAD_CATCH | 5 | render_tool_preset_manager_content | Accumulate in app._last_request_errors via _handle_preset_error | Modal content renderer; exception drains to ai_status |
| 3449 | INTERNAL_BROAD_CATCH | 4 | render_persona_editor_window | render_persona_editor_result() -> Result[None, ErrorInfo] (modal) | Modal window renderer; can call imgui.open_popup; Phase 4 |
| 3633 | INTERNAL_BROAD_CATCH | 5 | render_context_batch_actions | Accumulate in app._last_request_errors via _handle_context_error | Modal content renderer; exception from _do_generate() drains to preview |
| 3769 | INTERNAL_BROAD_CATCH | 4 | render_ast_inspector_modal | render_ast_inspector_result() -> Result[None, ErrorInfo] (modal) | Modal renderer; makes mcp_client calls; Phase 4 |
| 3796 | INTERNAL_BROAD_CATCH | 4 | render_ast_inspector_modal | render_ast_inspector_result() -> Result[None, ErrorInfo] (modal) | Second mcp_client call; same helper |
| 4418 | INTERNAL_BROAD_CATCH | 7 | worker | Use app._report_worker_error(msg) with thread-safe accumulation | Background worker thread; thread-safe error reporting |
| 4836 | INTERNAL_SILENT_SWALLOW | 8 | _on_warmup_complete_callback | Phase 8 startup callback — thread-safe Result accumulation | IO pool thread callback; lock-protected append; bare except pass |
| 4849 | INTERNAL_BROAD_CATCH | 3 | render_warmup_status_indicator | _render_warmup_status_result() -> Result[None, ErrorInfo] | Render-loop indicator; called every frame |
| 5430 | INTERNAL_BROAD_CATCH | 5 | render_operations_hub | Accumulate in app._last_request_errors via _handle_ops_error | Tab content renderer; exception drains to ai_status |
| 5836 | INTERNAL_BROAD_CATCH | 5 | render_text_viewer_window | Accumulate in app._last_request_errors via _handle_text_viewer_error | Window renderer; exception drains to error text display |
| 5970 | INTERNAL_BROAD_CATCH | 5 | render_external_editor_panel | Accumulate in app._last_request_errors via _handle_external_editor_error | Panel renderer; exception drains to panel error text |
| 6817 | INTERNAL_SILENT_SWALLOW | 10 | render_tier_stream_panel | Phase 3 render — use _render_tier_stream_result() not sys.stderr | Render-loop panel; exception from imgui.set_scroll_here_y logged to stderr |
| 7152 | INTERNAL_SILENT_SWALLOW | 5 | render_task_dag_panel | Accumulate in app._last_request_errors via _handle_dag_error | Modal content renderer; exception drains to error display |
| 7168 | INTERNAL_SILENT_SWALLOW | 5 | render_task_dag_panel | Accumulate in app._last_request_errors via _handle_dag_error | Second exception site; ticket ID parsing error |
| 7258 | INTERNAL_BROAD_CATCH | 5 | render_beads_tab | Accumulate in app._last_request_errors via _handle_beads_error | Tab renderer; exception drains to error text |
---
## Migration Target Naming Conventions
### Render-loop helpers (Phase 3)
- _render_<feature>_result() — returns Result[None, ErrorInfo], called from render loop
### Modal/dialog helpers (Phase 4)
- render_<modal>_result() — returns Result[None, ErrorInfo], modal content renderers
### Event handler error drains (Phase 5)
- _handle_<context>_error(msg: str) — accumulates in app._last_request_errors
### Worker/background helpers (Phase 7)
- app._report_worker_error(msg: str) — thread-safe error reporting
### Property setter / state mutation helpers (Phase 8)
- _capture_<profile>_result() — returns Result[T, ErrorInfo] for state capture
- _render_<feature>_result() for startup callbacks
### Helper/utility (Phase 9)
- _handle_<operation>_result() — utility method error handling
### SILENT_SWALLOW drains (Phase 10)
- _append_diagnostic_error(context: str, msg: str) — accumulates diagnostic errors
- For render-loop SILENT_SWALLOW: same helper as Phase 3
### INTERNAL_RETHROW patterns (Phase 11)
- Pattern 1: ErrorInfo(kind=PROGRAMMER_ERROR) for raise AttributeError
- Pattern 2: raise ErrorInfo(kind=PROGRAMMER_ERROR) from caught exception
- Pattern 3: drain to sys.stderr.write + sys.exit(1)
---
## Sites Inspected (line ranges)
| Lines Read | Purpose |
|------------|---------|
| 50-100 | _resolve, _LazyModule, _FiledialogStub (UNCLEAR sites) |
| 210-250 | _detect_refresh_rate_win32, _resolve_font_path |
| 560-600 | _post_init, _diag_layout_state |
| 680-770 | run, _load_fonts, __getattr__ |
| 800-820 | _get_active_capabilities (compliant baseline) |
| 860-920 | _apply_snapshot, _capture_workspace_profile |
| 975-1000 | shutdown |
| 1070-1140 | _gui_func |
| 1165-1240 | _show_menus |
| 1280-1360 | _handle_history_logic, _populate_auto_slices |
| 1390-1500 | _close_vscode_diff, _apply_pending_patch, _open_patch_in_external_editor, request_patch_from_tier4 |
| 1585-1640 | render_main_interface |
| 3200-3260 | render_tool_preset_manager_content |
| 3440-3500 | render_persona_editor_window |
| 3625-3680 | render_context_batch_actions |
| 3760-3820 | render_ast_inspector_modal |
| 4410-4470 | worker (context preview) |
| 4830-4870 | _on_warmup_complete_callback, render_warmup_status_indicator |
| 5420-5480 | render_operations_hub |
| 5830-5900 | render_text_viewer_window |
| 5960-6020 | render_external_editor_panel |
| 6810-6860 | render_tier_stream_panel |
| 7145-7190 | render_task_dag_panel |
| 7250-7282 | render_beads_tab |
---
## Confidence Notes
- Lines 757, 760 (__getattr__ raises): Both are raise AttributeError(name) — these are original raises, not rethrows. Audit classifies as INTERNAL_RETHROW but pattern is actually INTERNAL_PROGRAMMER_RAISE. Recommend Phase 11 as Pattern 1 (reraise as ErrorInfo(kind=PROGRAMMER_ERROR)).
- Lines 65, 69 (_resolve): These are legitimate lazy-loading fallbacks with _FiledialogStub sentinel. Not sliming. Recommend Phase 12 for UNCLEAR resolution — may be reclassified as INTERNAL_COMPLIANT.
- Lines 1593, 1619 (render_main_interface): Both are in render_main_interface called from _gui_func render loop. Phase 10 (SILENT_SWALLOW) for logging bodies; Phase 3 for the render site. Recommend Phase 3 helper with stderr-to-Result drain.
- Line 6817 (render_tier_stream_panel): SILENT_SWALLOW with sys.stderr.write in render loop. Phase 10 for logging body; Phase 3 for render site.
- Line 1079 (_gui_func first-frame timing): Startup callback, not render hot path. Phase 8 rather than Phase 3.
+159
View File
@@ -137,3 +137,162 @@ def test_phase7_migrated_sites_no_longer_silent_swallow():
f"Phase 7 regression: L{f.line} should not be "
f"INTERNAL_SILENT_SWALLOW after migration; got {f.category}"
)
# Phase 11 Task 11.4 - Regression-guard tests for dunder-method bare-raise heuristic.
# Per Phase 11 spec (INTERNAL_RETHROW classification for dunder methods):
# - Bare `raise AttributeError(name)` / `raise NameError(name)` in
# `__getattr__`, `__getattribute__`, `__setattr__`, `__delattr__` is the
# canonical dunder-method programmer-error pattern (per styleguide
# "Re-Raise Patterns": bare raises are reserved for programmer errors).
# - The audit previously classified these as INTERNAL_RETHROW (suspicious);
# the heuristic must reclassify them as INTERNAL_PROGRAMMER_RAISE.
DUNDER_RAISE_TESTS = {
"__getattr__": "def __getattr__(self, name):\n if name == 'controller':\n raise AttributeError(name)\n raise AttributeError(name)",
"__getattribute__": "def __getattribute__(self, name):\n if name == 'controller':\n raise AttributeError(name)\n raise AttributeError(name)",
"__setattr__": "def __setattr__(self, name, value):\n raise AttributeError(name)",
"__delattr__": "def __delattr__(self, name):\n raise AttributeError(name)",
}
def _classify_first_raise(source, func_name):
tree = ast.parse(source)
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef) and node.name == func_name:
visitor = ExceptionVisitor(str(ROOT / "src" / "_test_dummy.py"))
visitor._func_stack = [node]
for sub in ast.walk(node):
if isinstance(sub, ast.Raise) and sub.exc is not None:
return visitor._classify_raise(sub)
raise AssertionError(f"No raise found in {func_name}")
def test_bare_raise_attribute_error_in_getattr_is_programmer_raise():
src = DUNDER_RAISE_TESTS["__getattr__"]
category, hint = _classify_first_raise(src, "__getattr__")
assert category == "INTERNAL_PROGRAMMER_RAISE", (
f"Phase 11 regression: bare `raise AttributeError(name)` in __getattr__ "
f"should be INTERNAL_PROGRAMMER_RAISE (canonical dunder-method pattern); "
f"got {category}. Hint: {hint}"
)
def test_bare_raise_name_error_in_getattr_is_programmer_raise():
src = (
"def __getattr__(self, name):\n"
" if not hasattr(self, 'x'):\n"
" raise NameError(name)\n"
" return self.x"
)
category, hint = _classify_first_raise(src, "__getattr__")
assert category == "INTERNAL_PROGRAMMER_RAISE", (
f"Phase 11 regression: bare `raise NameError(name)` in __getattr__ "
f"should be INTERNAL_PROGRAMMER_RAISE (canonical dunder-method pattern); "
f"got {category}. Hint: {hint}"
)
def test_bare_raise_in_setattr_is_programmer_raise():
src = DUNDER_RAISE_TESTS["__setattr__"]
category, hint = _classify_first_raise(src, "__setattr__")
assert category == "INTERNAL_PROGRAMMER_RAISE", (
f"Phase 11 regression: bare `raise AttributeError` in __setattr__ "
f"should be INTERNAL_PROGRAMMER_RAISE (canonical dunder-method pattern); "
f"got {category}. Hint: {hint}"
)
def test_bare_raise_in_delattr_is_programmer_raise():
src = DUNDER_RAISE_TESTS["__delattr__"]
category, hint = _classify_first_raise(src, "__delattr__")
assert category == "INTERNAL_PROGRAMMER_RAISE", (
f"Phase 11 regression: bare `raise AttributeError` in __delattr__ "
f"should be INTERNAL_PROGRAMMER_RAISE (canonical dunder-method pattern); "
f"got {category}. Hint: {hint}"
)
def test_bare_raise_in_getattribute_is_programmer_raise():
src = DUNDER_RAISE_TESTS["__getattribute__"]
category, hint = _classify_first_raise(src, "__getattribute__")
assert category == "INTERNAL_PROGRAMMER_RAISE", (
f"Phase 11 regression: bare `raise AttributeError(name)` in __getattribute__ "
f"should be INTERNAL_PROGRAMMER_RAISE (canonical dunder-method pattern); "
f"got {category}. Hint: {hint}"
)
# Phase 12 Task 12.1 - Regression-guard tests for the lazy-loading sentinel
# fallback heuristic.
# Per Phase 12 spec (INTERNAL_COMPLIANT classification for lazy-loading
# sentinel fallbacks in methods named _resolve/_load/_get/_try_load):
# - The except body must NOT re-raise
# - The except body must assign to a self.<attr> (directly or via nested try)
# - The except set must be in {AttributeError, ImportError, ModuleNotFoundError}
# - The enclosing function name must be in the lazy-loader set
# Pre-Phase 12 baseline: 2 UNCLEAR sites in src/gui_2.py at L65, L69
# (both in _LazyModule._resolve). Post-Phase 12: 0 UNCLEAR.
def test_lazy_loading_sentinel_fallback_in_resolve_is_compliant():
src = (
"def _resolve(self):\n"
" try:\n"
" self._cached = getattr(self._mod, self._attr_name)\n"
" except AttributeError:\n"
" try:\n"
" self._cached = _importlib.import_module(self._sub_name)\n"
" except (ImportError, ModuleNotFoundError):\n"
" self._cached = _FiledialogStub()\n"
" return self._cached\n"
)
visitor = _make_visitor(src, "_resolve")
try_node = _find_handler(visitor)
handler = try_node.handlers[0]
category, hint = visitor._classify_except(handler, try_node)
assert category == "INTERNAL_COMPLIANT", (
f"Phase 12 regression: lazy-loading sentinel fallback in `_resolve` "
f"(L65-style nested try with `self._cached = _FiledialogStub()`) "
f"should be INTERNAL_COMPLIANT (canonical graceful-degradation pattern); "
f"got {category}. Hint: {hint}"
)
def test_lazy_loading_sentinel_fallback_in_load_is_compliant():
src = (
"def _load(self, name):\n"
" try:\n"
" self._cached = _importlib.import_module(name)\n"
" except (ImportError, ModuleNotFoundError):\n"
" self._cached = _FooStub()\n"
" return self._cached\n"
)
visitor = _make_visitor(src, "_load")
try_node = _find_handler(visitor)
handler = try_node.handlers[0]
category, hint = visitor._classify_except(handler, try_node)
assert category == "INTERNAL_COMPLIANT", (
f"Phase 12 regression: lazy-loading sentinel fallback in `_load` "
f"(direct `self._cached = _FooStub()`) should be INTERNAL_COMPLIANT "
f"(canonical graceful-degradation pattern); got {category}. Hint: {hint}"
)
def test_lazy_loading_sentinel_fallback_in_get_is_compliant():
src = (
"def _get(self, attr_name):\n"
" try:\n"
" return getattr(self._module, attr_name)\n"
" except AttributeError:\n"
" self._cached = _BarStub()\n"
" return self._cached\n"
)
visitor = _make_visitor(src, "_get")
try_node = _find_handler(visitor)
handler = try_node.handlers[0]
category, hint = visitor._classify_except(handler, try_node)
assert category == "INTERNAL_COMPLIANT", (
f"Phase 12 regression: lazy-loading sentinel fallback in `_get` "
f"(direct `self._cached = _BarStub()`) should be INTERNAL_COMPLIANT "
f"(canonical graceful-degradation pattern); got {category}. Hint: {hint}"
)
File diff suppressed because it is too large Load Diff