Skip to content

refactor: wave 2 — typed errors, structured logging, no mutable globals#49

Merged
ty13r merged 1 commit intomainfrom
refactor/wave-2-hygiene
Apr 19, 2026
Merged

refactor: wave 2 — typed errors, structured logging, no mutable globals#49
ty13r merged 1 commit intomainfrom
refactor/wave-2-hygiene

Conversation

@ty13r
Copy link
Copy Markdown
Owner

@ty13r ty13r commented Apr 19, 2026

Summary

Wave 2 of the clean-code overhaul — cross-cutting hygiene across the backend. Enforces docs/clean-code.md §4 (error handling) and §5 (data / no mutable globals).

New modules

  • skillforge/errors.pySkldError base + domain subtypes (SpawnError, BreedError, JudgeError, ParseError, ValidationError, AgentSDKError, DBError, etc.). ParseError and ValidationError also inherit from ValueError so legacy call sites catching ValueError keep working.
  • skillforge/agents/_json.pyextract_json_array() consolidated from two identical copies in spawner.py and challenge_designer.py. Raises ParseError (is-a ValueError).
  • skillforge/engine/run_registry.pyRunRegistry encapsulates the two mutable module globals (PENDING_PARENTS and _active_runs) behind explicit accessors. Singleton registry instance threaded through engine/evolution.py, api/routes.py, api/debug.py, and main.py.

Exception + logging hygiene

Ruff now enforces BLE + TRY on the whole codebase (deferred from Wave 1). Outcome:

  • Every remaining broad except Exception now carries a one-line # noqa: BLE001 — rationale explaining why the catch is a legitimate boundary (boot-time lifespan, LLM SDK calls, background tasks, WebSocket handlers, event persistence).
  • Where possible, catches were narrowed to specific types: OSError, json.JSONDecodeError, yaml.YAMLError, UnicodeDecodeError, zipfile.BadZipFile, etc.
  • All 6 remaining print(...) diagnostics in library code replaced with logger.exception / logger.warning / logger.debug (breeder + competitor_sdk + events).
  • TRY400 fixes: logger.error inside exceptlogger.exception.
  • TRY401 fixes: dropped redundant exc args from logger.exception format strings (logger already captures the traceback).
  • TRY004 left ignored with rationale — our isinstance checks validate LLM-parsed payloads, so ValueError is the right semantic ("malformed value"), not TypeError ("programmer passed wrong type").

Tests

tests/test_seeds.py and tests/test_uploads.py now access pending-parent state through the new RunRegistry instead of the old PENDING_PARENTS module global.

Test plan

  • uv run ruff check skillforge — clean (BLE + TRY now enabled)
  • uv run mypy skillforge — 54 files pass
  • uv run pytest tests/ — 403 passed, 2 skipped, 0 failed
  • cd frontend && npm run build / lint / format:check / test — all green (frontend untouched)

What's next

Wave 3: backend hotspot decomposition (queries.py at 1362 LOC → package split; routes.py at 707 LOC → by-resource split; spawner/breeder/variant_evolution decomposed into pure planners + thin I/O shells).

🤖 Generated with Claude Code

Per docs/clean-code.md §4-5: typed exception hierarchy, structured
logging, no mutable module globals, de-duplicated JSON parsing.

New modules
-----------
- skillforge/errors.py: SkldError base + SpawnError, BreedError,
  JudgeError, ParseError, ValidationError, AgentSDKError, DBError, etc.
  ParseError/ValidationError inherit from ValueError so legacy callers
  catching ValueError keep working.

- skillforge/agents/_json.py: extract_json_array() consolidated from
  identical copies in spawner.py + challenge_designer.py. Raises
  ParseError (which is-a ValueError) instead of raw ValueError. Both
  agents now import it.

- skillforge/engine/run_registry.py: RunRegistry encapsulates the two
  mutable module globals (PENDING_PARENTS in engine/evolution.py:46
  and _active_runs in api/routes.py:36) behind explicit accessors.
  Module-level singleton 'registry' threaded through
  engine/evolution.py, api/routes.py, api/debug.py, and main.py.

Exception hygiene
-----------------
Ruff now enforces BLE + TRY on the whole codebase. All 21 remaining
bare 'except Exception' catches surfaced by BLE001 are now
justified at genuine boundaries (boot-time lifespan, LLM SDK calls,
background tasks, WS handlers, event persistence) with a noqa tag
carrying a one-line rationale. Narrow catches replaced with specific
types where possible (OSError, json.JSONDecodeError, YAMLError, etc.).

Logging
-------
All 'print(...)' diagnostics in library code replaced with
logger.exception / logger.warning / logger.debug calls:
- breeder.py: 6 prints -> structured logger.exception
- competitor_sdk.py: stderr print -> logger.exception
- events.py: 'except Exception: pass' -> logger.debug + noqa

TRY400/TRY401 fixes applied uniformly: logger.error inside except ->
logger.exception; dropped redundant exc args from logger.exception
format strings (the logger already captures the traceback).

Tests
-----
tests/test_seeds.py and tests/test_uploads.py updated to access
pending-parent state through the new RunRegistry instead of the old
PENDING_PARENTS module global.

QA
--
  ruff check skillforge      - clean (BLE + TRY now enabled)
  mypy skillforge            - 54 files pass
  pytest tests/              - 403 passed, 2 skipped, 0 failed
  frontend build + vitest    - untouched, still passing

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ty13r ty13r merged commit c83eb30 into main Apr 19, 2026
2 checks passed
@ty13r ty13r deleted the refactor/wave-2-hygiene branch April 19, 2026 18:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant