test: make plugin test results mean something - #246
Conversation
|
Warning Review limit reached
Next review available in: 12 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThe PR migrates baseball scrolling to shared core classes with a legacy fallback, throttles live-content logging for both scoreboards, adds plugin test execution tooling, validates manifest fields in CI, and updates release metadata. ChangesScoreboard platform updates
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant BaseballPlugin
participant ScrollDisplayManager
participant SportsScroll
BaseballPlugin->>ScrollDisplayManager: prepare baseball game content
ScrollDisplayManager->>SportsScroll: delegate shared display and frame management
SportsScroll-->>ScrollDisplayManager: return scroll state and rendered frames
ScrollDisplayManager-->>BaseballPlugin: provide display status and cached content
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
⚔️ Resolve merge conflicts 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Not up to standards ⛔🔴 Issues
|
| Category | Results |
|---|---|
| Security | 1 minor 2 critical 1 medium |
🟢 Metrics 25 complexity
Metric Results Complexity 25
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (2)
plugins/baseball-scoreboard/scroll_display.py (2)
209-215: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueReturn early when the renderer is missing.
_get_game_renderer()returnsNonewhenGameRendereris unavailable. The loop then raisesAttributeErroron every game at Line 244 and logs a full traceback each time before the method returnsFalse. One early return gives the same result with one log line.♻️ Proposed early return
renderer = self._get_game_renderer(game_card_width) + if renderer is None: + self.logger.error("GameRenderer not available; cannot prepare scroll content") + return False # Pass rankings cache to renderer if available - if renderer and rankings_cache: + if rankings_cache: renderer.set_rankings_cache(rankings_cache)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugins/baseball-scoreboard/scroll_display.py` around lines 209 - 215, In the game-rendering loop, update the flow immediately after _get_game_renderer(game_card_width) to return False when the renderer is None, logging the unavailability once before returning. Keep the rankings_cache assignment and subsequent rendering logic unchanged for valid renderers, preventing later renderer calls from raising AttributeError for each game.
298-304: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUse explicit
Optionalforrankings_cache. All three signatures declarerankings_cache: Dict[str, int] = None. PEP 484 prohibits implicitOptional, and Ruff reports RUF013 at each site. Theprepare_scroll_contentsignatures already useOptional[Dict[str, int]], so this is also an internal inconsistency.
plugins/baseball-scoreboard/scroll_display.py#L298-L304: change theprepare_contentparameter torankings_cache: Optional[Dict[str, int]] = None.plugins/baseball-scoreboard/scroll_display_legacy.py#L604-L610: change theprepare_and_displayparameter torankings_cache: Optional[Dict[str, int]] = None.plugins/baseball-scoreboard/scroll_display_legacy.py#L630-L636: change theprepare_contentparameter torankings_cache: Optional[Dict[str, int]] = None.
Optionalis already imported in both files.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugins/baseball-scoreboard/scroll_display.py` around lines 298 - 304, Replace the implicit nullable rankings_cache annotations with Optional[Dict[str, int]] = None in prepare_content in plugins/baseball-scoreboard/scroll_display.py at lines 298-304, prepare_and_display in plugins/baseball-scoreboard/scroll_display_legacy.py at lines 604-610, and prepare_content in plugins/baseball-scoreboard/scroll_display_legacy.py at lines 630-636. Optional is already imported; no other changes are needed.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@plugins/baseball-scoreboard/scroll_display_legacy.py`:
- Around line 581-583: Replace the undefined ScrollDisplay annotation in
LegacyScrollDisplayManager.__init__ with the locally defined LegacyScrollDisplay
type (or a safely deferred annotation), ensuring construction works on legacy
cores. Add a construction test for LegacyScrollDisplayManager to exercise the
fallback path.
In `@plugins/football-scoreboard/manager.py`:
- Around line 1978-1996: Update the live-content flow around has_live_content()
in plugins/football-scoreboard/manager.py:1978-1996 to include active
celebration status in the throttled state and INFO log path rather than
returning before it, preserving correct transition logging into and out of
celebration. Add a regression test in
plugins/football-scoreboard/test_live_content_log_throttle.py:86-143 covering an
active celebration and its transition back to normal live-content state.
In `@plugins/football-scoreboard/manifest.json`:
- Around line 27-31: Rename the minimum-version key in the Football manifest’s
lowest-minimum versions entry from ledmatrix_min to ledmatrix_min_version,
leaving any other legacy ledmatrix_min entries unchanged.
In `@scripts/check_manifest_version_fields.py`:
- Around line 61-66: Update the validation condition in the manifest-checking
logic around OLD and NEW so versions[0] must contain NEW and must not contain
OLD. Report a problem when NEW is missing or OLD is present, including the
duplicate-field case where both exist, while preserving the existing message
context and allowance for older entries.
In `@scripts/run_plugin_tests.py`:
- Around line 92-95: Update the plugin ID handling in main() before aggregating
test scripts: validate each explicitly requested ID against the available
plugins, and return FAIL when any ID is unknown or has no corresponding plugin
tests. Preserve the existing aggregation and execution behavior for valid plugin
IDs.
- Around line 52-59: Establish one explicit core-checkout contract across both
sites: in scripts/run_plugin_tests.py lines 52-59, resolve the supplied core
path relative to the runner’s original invocation context and pass it to the
child through a dedicated environment variable or argument; in
plugins/baseball-scoreboard/test_baseball_plugin.py lines 393-396, update the
pre-flight configuration-template check to read and use that explicit core path
instead of resolving config/config.template.json from the child working
directory.
---
Nitpick comments:
In `@plugins/baseball-scoreboard/scroll_display.py`:
- Around line 209-215: In the game-rendering loop, update the flow immediately
after _get_game_renderer(game_card_width) to return False when the renderer is
None, logging the unavailability once before returning. Keep the rankings_cache
assignment and subsequent rendering logic unchanged for valid renderers,
preventing later renderer calls from raising AttributeError for each game.
- Around line 298-304: Replace the implicit nullable rankings_cache annotations
with Optional[Dict[str, int]] = None in prepare_content in
plugins/baseball-scoreboard/scroll_display.py at lines 298-304,
prepare_and_display in plugins/baseball-scoreboard/scroll_display_legacy.py at
lines 604-610, and prepare_content in
plugins/baseball-scoreboard/scroll_display_legacy.py at lines 630-636. Optional
is already imported; no other changes are needed.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: ed69cc84-ec0d-47cf-9f48-102a2e377b7b
📒 Files selected for processing (17)
.github/workflows/test-plugins.ymlplugins.jsonplugins/baseball-scoreboard/CHANGELOG.mdplugins/baseball-scoreboard/manager.pyplugins/baseball-scoreboard/manifest.jsonplugins/baseball-scoreboard/scroll_display.pyplugins/baseball-scoreboard/scroll_display_legacy.pyplugins/baseball-scoreboard/test_baseball_plugin.pyplugins/baseball-scoreboard/test_live_content_log_throttle.pyplugins/baseball-scoreboard/test_test_mode_live_games.pyplugins/football-scoreboard/CHANGELOG.mdplugins/football-scoreboard/manager.pyplugins/football-scoreboard/manifest.jsonplugins/football-scoreboard/test_football_plugin.pyplugins/football-scoreboard/test_live_content_log_throttle.pyscripts/check_manifest_version_fields.pyscripts/run_plugin_tests.py
| # Create scroll displays for each game type | ||
| self._scroll_displays: Dict[str, ScrollDisplay] = {} | ||
| self._current_game_type: Optional[str] = None |
There was a problem hiding this comment.
🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win
ScrollDisplay is undefined; the legacy fallback raises NameError on construction.
This module defines LegacyScrollDisplay, not ScrollDisplay. Python evaluates the annotation of an attribute target at runtime, so Dict[str, ScrollDisplay] is evaluated when LegacyScrollDisplayManager.__init__ runs, and it raises NameError: name 'ScrollDisplay' is not defined.
The alias in scroll_display.py Line 61 binds ScrollDisplay in that module's namespace only. It does not resolve this reference.
Effect: on any core that predates src.common.sports_scroll, every LegacyScrollDisplayManager construction fails. plugins/baseball-scoreboard/manager.py catches the exception and sets self._scroll_manager = None, so scroll mode is silently disabled. This defeats the fallback that this change adds.
🐛 Proposed fix
# Create scroll displays for each game type
- self._scroll_displays: Dict[str, ScrollDisplay] = {}
+ self._scroll_displays: Dict[str, LegacyScrollDisplay] = {}
self._current_game_type: Optional[str] = NoneAdd a construction test for LegacyScrollDisplayManager so the fallback path is exercised in CI. Do you want me to generate that test?
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Create scroll displays for each game type | |
| self._scroll_displays: Dict[str, ScrollDisplay] = {} | |
| self._current_game_type: Optional[str] = None | |
| # Create scroll displays for each game type | |
| self._scroll_displays: Dict[str, LegacyScrollDisplay] = {} | |
| self._current_game_type: Optional[str] = None |
🧰 Tools
🪛 Ruff (0.16.0)
[error] 582-582: Undefined name ScrollDisplay
(F821)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@plugins/baseball-scoreboard/scroll_display_legacy.py` around lines 581 - 583,
Replace the undefined ScrollDisplay annotation in
LegacyScrollDisplayManager.__init__ with the locally defined LegacyScrollDisplay
type (or a safely deferred annotation), ensuring construction works on legacy
cores. Add a construction test for LegacyScrollDisplayManager to exercise the
fallback path.
Source: Linters/SAST tools
| { | ||
| "released": "2026-08-03", | ||
| "version": "2.10.1", | ||
| "ledmatrix_min": "2.0.0" | ||
| }, |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 6 'ledmatrix_min(_version)?|deprecated|newest|latest' \
scripts/check_manifest_version_fields.py \
plugins/baseball-scoreboard/manifest.json \
plugins/football-scoreboard/manifest.jsonRepository: ChuckBuilds/ledmatrix-plugins
Length of output: 50385
Rename the lowest-minimum legacy minimum-version key to ledmatrix_min_version.
ledmatrix_min is the deprecated spelling, but the Football manifest still uses it as the minimum versions[] entry. Update Line 30 to ledmatrix_min_version; older ledmatrix_min entries may remain as-is.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@plugins/football-scoreboard/manifest.json` around lines 27 - 31, Rename the
minimum-version key in the Football manifest’s lowest-minimum versions entry
from ledmatrix_min to ledmatrix_min_version, leaving any other legacy
ledmatrix_min entries unchanged.
| if OLD in head and NEW not in head: | ||
| problems.append( | ||
| f"{plugin_id}: versions[0] ({version_label}) uses the deprecated " | ||
| f"'{OLD}'. Rename it to '{NEW}' — this is the entry the store and " | ||
| f"loader actually read. Older entries can stay as they are." | ||
| ) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Reject missing and duplicate minimum-version fields.
Line 61 reports ledmatrix_min only when ledmatrix_min_version is absent. A newest entry with both fields passes. A newest entry with neither field also passes. Both cases bypass the stated requirement for versions[0] to use ledmatrix_min_version.
Require ledmatrix_min_version and reject ledmatrix_min whenever it is present.
Proposed fix
- if OLD in head and NEW not in head:
+ if NEW not in head:
problems.append(
- f"{plugin_id}: versions[0] ({version_label}) uses the deprecated "
- f"'{OLD}'. Rename it to '{NEW}' — this is the entry the store and "
- f"loader actually read. Older entries can stay as they are."
+ f"{plugin_id}: versions[0] ({version_label}) is missing '{NEW}'."
)
+ if OLD in head:
+ problems.append(
+ f"{plugin_id}: versions[0] ({version_label}) contains deprecated "
+ f"'{OLD}'. Remove it and use only '{NEW}'."
+ )📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if OLD in head and NEW not in head: | |
| problems.append( | |
| f"{plugin_id}: versions[0] ({version_label}) uses the deprecated " | |
| f"'{OLD}'. Rename it to '{NEW}' — this is the entry the store and " | |
| f"loader actually read. Older entries can stay as they are." | |
| ) | |
| if NEW not in head: | |
| problems.append( | |
| f"{plugin_id}: versions[0] ({version_label}) is missing '{NEW}'." | |
| ) | |
| if OLD in head: | |
| problems.append( | |
| f"{plugin_id}: versions[0] ({version_label}) contains deprecated " | |
| f"'{OLD}'. Remove it and use only '{NEW}'." | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/check_manifest_version_fields.py` around lines 61 - 66, Update the
validation condition in the manifest-checking logic around OLD and NEW so
versions[0] must contain NEW and must not contain OLD. Report a problem when NEW
is missing or OLD is present, including the duplicate-field case where both
exist, while preserving the existing message context and allowance for older
entries.
Every one of these standalone scripts signals only through an exit code, and
without a shared convention "not applicable here" was indistinguishable from
"broken". That cost real time: the same seven baseball/football "failures"
were re-baselined three separate times in one session to prove a change had
not caused them.
Categorising them properly, the seven were:
3 only a missing RGBMatrixEmulator in the runner's virtualenv -- they pass
in the core venv
1 a deliberate skip (test_score_antialiasing already exits 2)
2 interactive/hardware scripts: one prompts on stdin, one drives a real or
emulated matrix and loads the core config template
1 genuinely broken
That last one is the reason this matters. test_test_mode_live_games raised a
_StopAfterTestUpdate sentinel from its fake, but suppressed only
AttributeError -- so the sentinel escaped and the test died before reaching a
single assertion. It had been silently verifying nothing for as long as it had
been "failing", hidden in noise everyone had learned to ignore. Fixed by
suppressing both the sentinel and the buggy path's AttributeError; it now runs
its invariants and passes.
The convention: 0 pass, 2 skip (prerequisites absent), 1 fail. Scripts opt in
by printing "SKIP: <reason>" and exiting 2. scripts/run_plugin_tests.py runs a
plugin's scripts and reports pass/skip/fail, exiting non-zero only on real
failures.
The two interactive scripts now pre-flight their prerequisites and skip
instead of failing.
baseball + football: 21 passed, 3 skipped, 0 failed -- a clean signal for the
first time.
Fleet-wide `--all` reports 63 passed, 3 skipped, 7 failed. Those seven are
left for a follow-up and are now legible rather than lumped together: two are
"not applicable" (a font that ships with the core, a missing optional astral
dep) and five look like genuine staleness -- a test importing a class name
that no longer exists, a MockLogger without setLevel, a CacheManager called
with a keyword the core no longer takes, a bare cache_manager import, and two
lacrosse assertions.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Udr6MfaFLUPhX5Fgo67Jf5
Running it under a bare `python3` that lacks the plugins' dependencies reports every suite as failed -- 22 failures where the only thing wrong was the interpreter. Exactly the misclassification this script was written to end, so it should not be the thing that causes it. Scripts are spawned with sys.executable, and --core only sets PYTHONPATH. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Udr6MfaFLUPhX5Fgo67Jf5
a5d2778 to
bd9b6e5
Compare
Both findings on #246 reproduce. Unknown plugin ids looked like success. `run_plugin_tests.py a-typo` found no scripts, ran nothing, and returned 0. A typo or a removed plugin therefore reported clean. Unknown ids now fail. --core did not reach the scripts. It only prepended to PYTHONPATH, while children run with cwd set to the plugin directory -- so a script resolving a core asset relatively looked in the wrong place and skipped even though a core had been supplied. LEDMATRIX_CORE now carries the resolved absolute path, and baseball's pre-flight uses it, chdir'ing to the core because the core's own ConfigManager and DisplayManager resolve config/ and assets/ relatively too. That fix turns a SKIP into a FAIL, and the FAIL is correct: with the core finally reachable, test_baseball_plugin gets far enough to show it has drifted from the plugin API. It checks `plugin.initialized`, which no longer exists, and then `plugin.leagues`, which does not either. The script has been skipping for long enough that nobody noticed it had gone stale. I briefly patched around `initialized` and reverted it. Chasing the drift is a different job, and papering over it would be the same misclassification this whole change set exists to end -- a script that cannot run reported as fine. It belongs in the stale-test tranche this PR already catalogues, now with a concrete cause rather than "needs a LEDMatrix tree". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Udr6MfaFLUPhX5Fgo67Jf5
…0) (#247) * feat(football-scoreboard): adopt the core scroll orchestration (2.11.0) Second B5 adoption, and the second of the three lineages (baseball is the hockey/lacrosse/baseball/basketball/ufc family; football is its own). Orchestration -- scroll-helper configuration, frame pumping, completion, settings resolution, native global_config['target_fps'] -- now comes from the core's src.common.sports_scroll. Only the football-specific content half stays here: game cards and league separator icons. scroll_display.py goes 679 -> 310 lines. Guarded, exactly as baseball: scroll_display.py selects between the core-backed subclass and scroll_display_legacy (the previous implementation, frozen) at import. The except clause matches only the core module's own absence, so a failure raised inside a present-but-broken core surfaces rather than silently downgrading. The floor stays at 2.0.0. With a working fallback the plugin does not require 3.2.0, it prefers it, so nobody is cut off from updates; the floor rises at B6 when the fallback is deleted. versions[0] uses ledmatrix_min_version. Football needed less than baseball did: its __init__ sets only attributes the core base already provides, so no custom __init__. It does pin game_card_width at 128 where core sizes cards to the panel, so scroll_settings_defaults() overrides that -- the byte-identical gate depends on it. Content methods were lifted verbatim from the legacy source rather than retyped. Verified: - all 16 harness renders (8 sizes x 2 screens) byte-for-byte identical to 2.10.1: `diff -r` of the before/after output directories is empty - test_core_fallback.py added, asserting all three configurations this plugin can meet, and checked that it bites: sabotaging the guard to match the unguarded recipe makes it exit 1, the healthy tree exits 0 - 10 of 11 suites pass; the one failure is test_football_plugin.py, an interactive script whose skip guard is in the not-yet-merged #246 - module-collision check clean across 42 plugins Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Udr6MfaFLUPhX5Fgo67Jf5 * test(football-scoreboard): resolvability check, and fix the stale annotation Brings this plugin's fallback test up to the version that found the missing imports in the other six -- it resolves the globals each method reads, across both the display and manager classes and on both import paths, rather than checking that method names exist. This plugin's header was hand-written and is already clean; the point is that the old check could not have told us that. Also corrects the legacy manager's `Dict[str, ScrollDisplay]` annotation to the post-rename name. On Python 3.13 that annotation is not evaluated, so the reported NameError does not reproduce there, but it is wrong regardless and this repo supports 3.10 through 3.13. Not changed: get_all_vegas_content_items. It is not lost -- the core-backed manager inherits it from SportsScrollDisplayManager, and diffing the two shows the only differences are the type annotation, docstring, loop variable name and quote style. The logic is identical. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Udr6MfaFLUPhX5Fgo67Jf5 --------- Co-authored-by: Claude <noreply@anthropic.com>
Test-only. No plugin code changes, no version bumps — so the safety-harness gate doesn't apply.
Why
These are standalone scripts that signal only through an exit code, so "not applicable here" was indistinguishable from "broken". That cost real time: the same seven baseball/football "failures" were re-baselined three separate times in one session just to prove a change hadn't caused them.
Categorised properly, the seven were:
RGBMatrixEmulatorin the runner's virtualenv — they pass in the core venvtest_score_antialiasingalready exits 2)The one that mattered
test_test_mode_live_gamesraised a_StopAfterTestUpdatesentinel from its fake, but suppressed onlyAttributeError. The sentinel escaped uncaught and the test died before reaching a single assertion — it had been silently verifying nothing for as long as it had been "failing", hidden in noise everyone had learned to ignore.That is the actual cost of a noisy suite: not the wasted re-baselining, but that a real regression can hide in it indefinitely.
Fixed by suppressing both the sentinel and the buggy path's
AttributeError. It now runs its invariants and passes.The convention
Scripts opt in by printing
SKIP: <reason>and exiting 2.scripts/run_plugin_tests.pyruns a plugin's scripts and reports pass/skip/fail, exiting non-zero only on real failures.The two interactive scripts now pre-flight their prerequisites and skip rather than fail.
Result
baseball + football: 21 passed, 3 skipped, 0 failed — a clean signal for the first time.
Fleet-wide
--all: 63 passed, 3 skipped, 7 failed. Those seven are left for a follow-up and are now legible rather than lumped together:astraldepBasketballPluginManager, which no longer exists; aMockLoggerwithoutsetLevel;CacheManagercalled with aconfig_managerkeyword the core no longer takes; a barecache_managerimport; two lacrosse assertionsThe
CacheManagerone is worth a look on its own — a test calling a core API with a signature that has since changed is exactly the drift this repo's guarded-import discipline exists to catch.🤖 Generated with Claude Code
https://claude.ai/code/session_01Udr6MfaFLUPhX5Fgo67Jf5
Summary by CodeRabbit