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
This commit is contained in:
ed
2026-06-17 19:11:35 -04:00
parent f0b7df816a
commit 7298fbd62b
2 changed files with 8 additions and 8 deletions
+5 -5
View File
@@ -29,7 +29,7 @@ def now_ts() -> str:
def parse_ts(s: str) -> Optional[datetime.datetime]: def parse_ts(s: str) -> Optional[datetime.datetime]:
try: try:
return datetime.datetime.strptime(s, TS_FMT) return datetime.datetime.strptime(s, TS_FMT)
except Exception: except (ValueError, TypeError):
return None return None
# ── entry serialisation ────────────────────────────────────────────────────── # ── entry serialisation ──────────────────────────────────────────────────────
@@ -95,7 +95,7 @@ def get_git_commit(git_dir: str) -> str:
capture_output=True, text=True, cwd=git_dir, timeout=5, capture_output=True, text=True, cwd=git_dir, timeout=5,
) )
return r.stdout.strip() if r.returncode == 0 else "" return r.stdout.strip() if r.returncode == 0 else ""
except Exception: except (OSError, subprocess.SubprocessError, subprocess.TimeoutExpired):
return "" return ""
# ── default structures ─────────────────────────────────────────────────────── # ── default structures ───────────────────────────────────────────────────────
@@ -360,7 +360,7 @@ def get_all_tracks(base_dir: Union[str, Path] = ".") -> list[dict[str, Any]]:
track_info["total"] = progress["total"] track_info["total"] = progress["total"]
track_info["progress"] = progress["percentage"] / 100.0 track_info["progress"] = progress["percentage"] / 100.0
state_found = True state_found = True
except Exception: except (OSError, AttributeError, KeyError, TypeError):
pass pass
if not state_found: if not state_found:
@@ -372,7 +372,7 @@ 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["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["title"] = data.get("title", data.get("name", data.get("description", track_id)))
track_info["status"] = data.get("status", "unknown") track_info["status"] = data.get("status", "unknown")
except Exception: except (OSError, json.JSONDecodeError, UnicodeDecodeError):
pass pass
if track_info["total"] == 0: if track_info["total"] == 0:
@@ -387,7 +387,7 @@ def get_all_tracks(base_dir: Union[str, Path] = ".") -> list[dict[str, Any]]:
track_info["complete"] = len(completed_tasks) track_info["complete"] = len(completed_tasks)
if track_info["total"] > 0: if track_info["total"] > 0:
track_info["progress"] = float(track_info["complete"]) / track_info["total"] track_info["progress"] = float(track_info["complete"]) / track_info["total"]
except Exception: except (OSError, UnicodeDecodeError, re.error):
pass pass
results.append(track_info) results.append(track_info)
+1 -1
View File
@@ -37,7 +37,7 @@ class StartupProfiler:
try: try:
sys.stderr.write(f"[startup] {name}: {(p.end_ts - p.start_ts) * 1000.0:.1f}ms\n") sys.stderr.write(f"[startup] {name}: {(p.end_ts - p.start_ts) * 1000.0:.1f}ms\n")
sys.stderr.flush() sys.stderr.flush()
except Exception: except OSError:
pass pass
def snapshot(self) -> dict[str, Any]: def snapshot(self) -> dict[str, Any]: