gabriel / muse public
feat BREAKING dev
AI Agent gabriel · 149 days ago · Apr 25, 2026 · Diff

feat: Phase 6 — MsgpackCache ABC eliminates duplicated load/save boilerplate

Introduces muse/core/cache_base.py with MsgpackCache, an abstract base class that owns all Pattern-A disk I/O (mkstemp + os.replace) and dirty-flag tracking. SymbolCache, CallGraphCache, ImplicitEdgeCache, and _InvariantFileCache now inherit from it — each provides only _CACHE_FILENAME, _CACHE_VERSION, _TEMP_PREFIX, _deserialize_entries(), and _serialize_entries(). All load/save/get/put/prune/ size/empty implementations are shared.

_InvariantFileCache overrides load() to keep its repo_root signature; all other caches use the inherited muse_dir signature unchanged.

Adds tests/test_cache_base.py with 21 tests (Tiers 1/2/5/6/7) via a minimal _TestCache concrete subclass. All 175 cache tests pass (21 new + 154 existing per-cache tests with zero modifications — the regression gate holds).

Plan doc updated: Phase 6 marked complete, write pattern table corrected to reflect all four caches now on Pattern A since Phase 5.

sha256:3d749fd3b483ce4c7d658fbc9f21ed09b47ad84fdccaffec1c2d8a27ae89f12b sha
+77 ~43 −47 symbols
sha256:af1f419a5ae67cb2e5b1fdb7528b64fd15b17d906a95f40261c68371d5cfc565 snapshot
+77
symbols added
~43
symbols modified
−47
symbols removed
0
dead code introduced
Semantic Changes 167 symbols
~ muse/core/cache_base.py .py 22 symbols added
+ MsgpackCache class class MsgpackCache L47–222
+ __init__ method method __init__ L72–79
+ _deserialize_entries method method _deserialize_entries L87–93
+ _serialize_entries method method _serialize_entries L96–100
+ empty method method empty L142–147
+ get method method get L153–155
+ load method method load L107–139
+ prune method method prune L162–176
+ put method method put L157–160
+ save method method save L187–222
+ size method method size L179–181
+ ABC import import ABC L37–37
+ Any import import Any L38–38
+ abstractmethod method import abstractmethod L37–37
+ annotations import import annotations L31–31
+ logging import import logging L33–33 ← moved from muse/core/symbol_cache.py
+ msgpack import import msgpack L40–40
+ os import import os L34–34 ← moved from muse/plugins/code/_invariants.py
+ pathlib import import pathlib L35–35
+ read_msgpack_file import import read_msgpack_file L42–42 ← moved from muse/core/implicit_edge_cache.py
+ tempfile import import tempfile L36–36 ← moved from muse/plugins/code/_invariants.py
+ logger variable variable logger L44–44 ← moved from muse/core/symbol_cache.py
~ tests/test_cache_base.py .py 35 symbols added
+ TestDataIntegrity class class TestDataIntegrity L158–196
+ test_corrupt_bytes_returns_empty method method test_corrupt_bytes_returns_empty L159–163
+ test_invalid_entry_skipped_valid_survives method method test_invalid_entry_skipped_valid_survives L179–189
+ test_missing_entries_key_returns_empty method method test_missing_entries_key_returns_empty L172–177
+ test_no_tmp_leftover_after_save method method test_no_tmp_leftover_after_save L191–196
+ test_wrong_version_returns_empty method method test_wrong_version_returns_empty L165–170
+ TestIntegration class class TestIntegration L108–150
+ test_dirty_false_after_successful_save method method test_dirty_false_after_successful_save L145–150
+ test_load_missing_file_returns_empty method method test_load_missing_file_returns_empty L109–113
+ test_round_trip_data_intact method method test_round_trip_data_intact L122–131
+ test_save_creates_file_at_correct_path method method test_save_creates_file_at_correct_path L115–120
+ test_save_noop_when_not_dirty_mtime_unchanged method method test_save_noop_when_not_dirty_mtime_unchanged L133–143
+ TestPerformance class class TestPerformance L204–216
+ test_not_dirty_save_under_1ms method method test_not_dirty_save_under_1ms L205–216
+ TestSecurity class class TestSecurity L224–250
+ test_mode_000_file_returns_empty method method test_mode_000_file_returns_empty L225–236
+ test_save_overwrites_symlink_not_target method method test_save_overwrites_symlink_not_target L238–250
+ TestUnit class class TestUnit L59–100
+ test_empty_cache_dir_is_none method method test_empty_cache_dir_is_none L91–94
+ test_get_hit_after_put method method test_get_hit_after_put L69–72
+ test_get_miss_returns_none method method test_get_miss_returns_none L60–62
+ test_prune_noop_when_all_live method method test_prune_noop_when_all_live L82–85
+ test_prune_removes_stale_and_sets_dirty method method test_prune_removes_stale_and_sets_dirty L74–80
+ test_put_sets_dirty method method test_put_sets_dirty L64–67
+ test_save_on_empty_is_noop_no_file method method test_save_on_empty_is_noop_no_file L96–100
+ test_size_property method method test_size_property L87–89
+ _TestCache class class _TestCache L26–39
+ _deserialize_entries method method _deserialize_entries L34–36
+ _serialize_entries method method _serialize_entries L38–39
+ _make_muse_dir function function _make_muse_dir L47–51
+ MsgpackCache import import MsgpackCache L18–18
+ annotations import import annotations L11–11
+ msgpack import import msgpack L15–15 ← moved from muse/plugins/code/_invariants.py
+ pathlib import import pathlib L13–13
+ pytest import import pytest L16–16
~ docs/cache-reorganization-plan.md .md 8 symbols added, 19 symbols modified
+ code[python]@L323 variable variable code[python]@L323 L323–343
+ MsgpackCache ABC interface section MsgpackCache ABC interface L361–438
+ code[python]@L363 variable variable code[python]@L363 L363–437
+ Subclass changes class section Subclass changes L438–452
+ py section Test file: tests/test_cache_base.py L452–518
+ code[python]@L456 variable variable code[python]@L456 L456–470
+ py) section mkstemp prefixes registered in _MUSE_TEMP_PREFIXES (repo.py) L52–73
+ code[python]@L57 variable variable code[python]@L57 L57–67
~ Phases
~ muse/core/callgraph_cache.py .py 3 symbols added, 12 symbols removed, 4 symbols modified
__init__ method method __init__ L110–117
empty method method empty L170–172
load method method load L124–167
save method method save L208–242
size method method size L200–202
_cache_dir_path import import _cache_dir_path L81–81
logging import import logging L74–74
msgpack import import msgpack L79–79
os import import os L75–75
read_msgpack_file import import read_msgpack_file L82–82
tempfile import import tempfile L77–77
logger variable variable logger L84–84
+ _deserialize_entries method method _deserialize_entries L109–136
+ _serialize_entries method method _serialize_entries L138–143
+ MsgpackCache import import MsgpackCache L65–65
~ prune
~ put
~ muse/core/implicit_edge_cache.py .py 3 symbols added, 12 symbols removed, 7 symbols modified
__init__ method method __init__ L140–147
empty method method empty L195–197
load method method load L154–192
save method method save L230–264
size method method size L222–224
_cache_dir_path import import _cache_dir_path L69–69
logging import import logging L61–61
msgpack import import msgpack L67–67
os import import os L62–62
read_msgpack_file import import read_msgpack_file L71–71 → moved to muse/core/cache_base.py
tempfile import import tempfile L64–64
logger variable variable logger L78–78
+ _deserialize_entries method method _deserialize_entries L144–165
+ _serialize_entries method method _serialize_entries L167–172
+ MsgpackCache import import MsgpackCache L58–58
~ get
~ prune
~ put
~ muse/core/symbol_cache.py .py 3 symbols added, 12 symbols removed, 5 symbols modified
__init__ method method __init__ L135–142
empty method method empty L188–190
load method method load L149–185
save method method save L227–257
size method method size L219–221
_cache_dir_path import import _cache_dir_path L77–77
logging import import logging L69–69 → moved to muse/core/cache_base.py
msgpack import import msgpack L75–75
os import import os L70–70
read_msgpack_file import import read_msgpack_file L78–78
tempfile import import tempfile L72–72
logger variable variable logger L83–83 → moved to muse/core/cache_base.py
+ _deserialize_entries method method _deserialize_entries L148–168
+ _serialize_entries method method _serialize_entries L170–172
+ MsgpackCache import import MsgpackCache L73–73
~ prune
~ put
~ muse/plugins/code/_invariants.py .py 3 symbols added, 11 symbols removed, 8 symbols modified
_FILE_CACHE_NAME variable variable _FILE_CACHE_NAME L110–110
_InvariantCacheDoc class class _InvariantCacheDoc L147–151
__init__ method method __init__ L267–270
empty method method empty L331–338
save method method save L367–400
size method method size L341–343
_cache_dir import import _cache_dir L86–86
msgpack import import msgpack L84–84 → moved to tests/test_cache_base.py
os import import os L79–79 → moved to muse/core/cache_base.py
read_msgpack_file import import read_msgpack_file L95–95
tempfile import import tempfile L81–81 → moved to muse/core/cache_base.py
+ _deserialize_entries method method _deserialize_entries L261–290
+ _serialize_entries method method _serialize_entries L292–294
+ MsgpackCache import import MsgpackCache L82–82
~ get
~ load
~ prune
~ put
← Older Oldest on dev
All commits
Newer → Latest on dev

0 comments

No comments yet. Be the first to start the discussion.

To add a comment, use the Muse CLI: muse hub commit comment sha256:3d749fd3b483ce4c7d658fbc9f21ed09b47ad84fdccaffec1c2d8a27ae89f12b --body "your comment"