diff --git a/README.md b/README.md index 81e9e2f15..b7099ad45 100644 --- a/README.md +++ b/README.md @@ -402,7 +402,7 @@ semble savings --verbose # also show breakdown by call type Savings are calculated as follows: for each call, semble records the total character count of the unique files containing returned chunks and the character count of the snippets returned. Estimated tokens saved is `(file chars − snippet chars) / 4` (4 chars per token). This is a conservative estimate: the baseline is reading matched files in full, which is how coding agents often explore unfamiliar code. -Stats are stored in the OS cache folder (`~/Library/Caches/semble/` on macOS, `~/.cache/semble/` on Linux, `%LOCALAPPDATA%\semble\Cache\` on Windows). +By default, stats are stored in the OS cache folder (`~/Library/Caches/semble/` on macOS, `~/.cache/semble/` on Linux, `%LOCALAPPDATA%\semble\Cache\` on Windows). To override this location you can supply an environment variable `SEMBLE_CACHE_LOCATION` which should be the full path to the target cache location e.g. 'd:\caches\storemysemblecachehere'. diff --git a/src/semble/cache.py b/src/semble/cache.py index 6f6ea49ee..e42454044 100644 --- a/src/semble/cache.py +++ b/src/semble/cache.py @@ -49,9 +49,11 @@ def _linux_cache_dir(name: str) -> Path: def resolve_cache_folder() -> Path: - """Resolves a cache folder, respects XDG_CACHE_HOME.""" + """Resolves a cache folder, respects SEMBLE_CACHE_LOCATION (highest precedence), XDG_CACHE_HOME.""" name = "semble" - if sys.platform == "win32": + if semble_cache_location := os.getenv("SEMBLE_CACHE_LOCATION"): + cache_dir = Path(semble_cache_location) + elif sys.platform == "win32": cache_dir = _windows_cache_dir(name) elif sys.platform == "darwin": cache_dir = _macos_cache_dir(name) diff --git a/tests/test_cache.py b/tests/test_cache.py index e46eb20d0..995705dab 100644 --- a/tests/test_cache.py +++ b/tests/test_cache.py @@ -93,6 +93,15 @@ def test_resolve_cache_folder(platform: str, mock_target: str, expected: Path) - assert result == expected +def test_resolve_cache_folder_semble_cache_location(tmp_path: Path) -> None: + """SEMBLE_CACHE_LOCATION takes precedence over all platform-specific helpers.""" + custom = tmp_path / "custom_cache" + with patch.dict("os.environ", {"SEMBLE_CACHE_LOCATION": str(custom)}): + result = resolve_cache_folder() + assert result == custom + assert custom.exists() + + def test_clear_cache(tmp_path: Path) -> None: """clear_cache removes the index directory when it exists and is a no-op otherwise.""" index_path = tmp_path / "index"