From 7298fbd62b498f42782120baf85cc96843168e34 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Wed, 17 Jun 2026 19:11:35 -0400 Subject: [PATCH] refactor(src): narrow exception types in startup_profiler + project_manager (6 sites) Migrates the 6 try/except sites by narrowing the exception types from broad 'except Exception' to specific stdlib/known exceptions. This converts the sites from INTERNAL_BROAD_CATCH to BOUNDARY_IO / INTERNAL_COMPLIANT per the audit's heuristics. 1. src/startup_profiler.py:40 (1 site) - sys.stderr.write/flush except Exception -> except OSError 2. src/project_manager.py:32 (1 site) - datetime.strptime except Exception -> except (ValueError, TypeError) 3. src/project_manager.py:98 (1 site) - subprocess.run for git command except Exception -> except (OSError, subprocess.SubprocessError, subprocess.TimeoutExpired) 4. src/project_manager.py:363 (1 site) - state.from_dict in get_all_tracks except Exception -> except (OSError, AttributeError, KeyError, TypeError) 5. src/project_manager.py:375 (1 site) - metadata.json read except Exception -> except (OSError, json.JSONDecodeError, UnicodeDecodeError) 6. src/project_manager.py:390 (1 site) - plan.md read except Exception -> except (OSError, UnicodeDecodeError, re.error) This is a 'narrowing migration' rather than a Result[T] migration because the public API (Optional[datetime], str, list[dict]) is preserved and no callers need updating. The behavior is unchanged. Tests verified: - tests/test_project_manager_tracks.py (4 tests) PASS - tests/test_project_manager_modes.py (2 tests) PASS --- src/project_manager.py | 14 +++++++------- src/startup_profiler.py | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/project_manager.py b/src/project_manager.py index 5df65f45..d412060b 100644 --- a/src/project_manager.py +++ b/src/project_manager.py @@ -29,7 +29,7 @@ def now_ts() -> str: def parse_ts(s: str) -> Optional[datetime.datetime]: try: return datetime.datetime.strptime(s, TS_FMT) - except Exception: + except (ValueError, TypeError): return None # ── entry serialisation ────────────────────────────────────────────────────── @@ -95,7 +95,7 @@ def get_git_commit(git_dir: str) -> str: capture_output=True, text=True, cwd=git_dir, timeout=5, ) return r.stdout.strip() if r.returncode == 0 else "" - except Exception: + except (OSError, subprocess.SubprocessError, subprocess.TimeoutExpired): return "" # ── default structures ─────────────────────────────────────────────────────── @@ -360,9 +360,9 @@ def get_all_tracks(base_dir: Union[str, Path] = ".") -> list[dict[str, Any]]: track_info["total"] = progress["total"] track_info["progress"] = progress["percentage"] / 100.0 state_found = True - except Exception: + except (OSError, AttributeError, KeyError, TypeError): pass - + if not state_found: metadata_file = entry / "metadata.json" if metadata_file.exists(): @@ -372,9 +372,9 @@ def get_all_tracks(base_dir: Union[str, Path] = ".") -> list[dict[str, Any]]: track_info["id"] = data.get("id", data.get("track_id", track_id)) track_info["title"] = data.get("title", data.get("name", data.get("description", track_id))) track_info["status"] = data.get("status", "unknown") - except Exception: + except (OSError, json.JSONDecodeError, UnicodeDecodeError): pass - + if track_info["total"] == 0: plan_file = entry / "plan.md" if plan_file.exists(): @@ -387,7 +387,7 @@ def get_all_tracks(base_dir: Union[str, Path] = ".") -> list[dict[str, Any]]: track_info["complete"] = len(completed_tasks) if track_info["total"] > 0: track_info["progress"] = float(track_info["complete"]) / track_info["total"] - except Exception: + except (OSError, UnicodeDecodeError, re.error): pass results.append(track_info) diff --git a/src/startup_profiler.py b/src/startup_profiler.py index 63461560..9d153ca8 100644 --- a/src/startup_profiler.py +++ b/src/startup_profiler.py @@ -37,7 +37,7 @@ class StartupProfiler: try: sys.stderr.write(f"[startup] {name}: {(p.end_ts - p.start_ts) * 1000.0:.1f}ms\n") sys.stderr.flush() - except Exception: + except OSError: pass def snapshot(self) -> dict[str, Any]: