From 666e21c3e313c861487067ccaadd2f9c5ebd8d56 Mon Sep 17 00:00:00 2001 From: stephantul Date: Thu, 4 Jun 2026 12:20:20 +0200 Subject: [PATCH 1/3] feat: add clear command to clear indices --- src/semble/cli.py | 45 ++++++++++- tests/test_cache.py | 12 ++- tests/test_cli.py | 181 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 232 insertions(+), 6 deletions(-) diff --git a/src/semble/cli.py b/src/semble/cli.py index 26070d9dd..8ad070140 100644 --- a/src/semble/cli.py +++ b/src/semble/cli.py @@ -1,19 +1,26 @@ import argparse import asyncio import json +import re import sys import warnings from importlib.util import find_spec +from shutil import rmtree +from typing import Literal from model2vec.utils import get_package_extras -from semble.cache import find_index_from_cache_folder +from semble.cache import find_index_from_cache_folder, resolve_cache_folder from semble.index import SembleIndex +from semble.index.types import PersistencePath from semble.stats import format_savings_report from semble.types import ContentType from semble.utils import format_results, is_git_url, resolve_chunk -_CLI_DISPATCH_ARGS = frozenset({"search", "find-related", "install", "uninstall", "savings", "-h", "--help"}) +_CLI_DISPATCH_ARGS = frozenset({"search", "find-related", "install", "uninstall", "savings", "-h", "--help", "clear"}) +_CLEAR_CHOICE = Literal["all", "index", "savings"] + +_SHA_256_REGEX = re.compile(r"^[a-f0-9]{64}$") def _build_index(path: str, content: list[ContentType]) -> SembleIndex: @@ -131,6 +138,35 @@ def _run_find_related(path: str, file_path: str, line: int, top_k: int, content: _maybe_save_index(index, path) +def _run_clear(type: _CLEAR_CHOICE) -> None: + """Run the `clear` subcommand.""" + cache_folder = resolve_cache_folder() + if type == "index" or type == "all": + indexes = [] + for path in cache_folder.glob("**/index"): + if not _SHA_256_REGEX.match(path.parent.name): + continue + if PersistencePath.from_path(path).non_existing(): + continue + indexes.append(path) + + if not indexes: + print(f"No indexes found to clear in `{cache_folder}`") + else: + for path in indexes: + index_folder = path.parent + rmtree(index_folder) + print(f"Cleared index at `{index_folder}`") + + if type == "savings" or type == "all": + path = cache_folder / "savings.jsonl" + if not path.exists(): + print(f"No savings file found at `{path}`") + else: + path.unlink() + print(f"Cleared savings at `{path}`") + + def _cli_main() -> None: parser = argparse.ArgumentParser(prog="semble") sub = parser.add_subparsers(dest="command") @@ -141,6 +177,9 @@ def _cli_main() -> None: search_p.add_argument("-k", "--top-k", type=int, default=5, help="Number of results (default: 5).") _add_content_args(search_p) + clear_p = sub.add_parser("clear", help="Clear the index cache.") + clear_p.add_argument("type", choices=["all", "index", "savings"], help="Type of cache to clear.") + related_p = sub.add_parser("find-related", help="Find code similar to a specific location.") related_p.add_argument("file_path", help="File path as shown in search results.") related_p.add_argument("line", type=int, help="Line number (1-indexed).") @@ -162,6 +201,8 @@ def _cli_main() -> None: from semble.installer import run run(args.command) + elif args.command == "clear": + _run_clear(args.type) elif args.command == "search": _run_search(args.path, args.query, args.top_k, _resolve_content(args.content, args.include_text_files)) elif args.command == "find-related": diff --git a/tests/test_cache.py b/tests/test_cache.py index 995705dab..453f7a2aa 100644 --- a/tests/test_cache.py +++ b/tests/test_cache.py @@ -81,14 +81,18 @@ def test_save_index_to_cache(tmp_path: Path) -> None: [ ("win32", "semble.cache._windows_cache_dir", Path("/win")), ("linux", "semble.cache._linux_cache_dir", Path("/linux")), + ("darwin", "semble.cache._macos_cache_dir", Path("/macos")), ], ) def test_resolve_cache_folder(platform: str, mock_target: str, expected: Path) -> None: """resolve_cache_folder calls the correct platform helper.""" - with patch.object(sys, "platform", platform): - with patch(mock_target, return_value=expected) as mock_fn: - with patch("pathlib.Path.mkdir"): - result = resolve_cache_folder() + with ( + patch.object(sys, "platform", platform), + patch.dict("os.environ", {}, clear=True), + patch(mock_target, return_value=expected) as mock_fn, + patch("pathlib.Path.mkdir"), + ): + result = resolve_cache_folder() mock_fn.assert_called_once_with("semble") assert result == expected diff --git a/tests/test_cli.py b/tests/test_cli.py index e5bb4afd5..7882a03bd 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -229,3 +229,184 @@ def test_agent_file_tools_are_bash_only() -> None: tools = [t.strip() for t in tools_line.removeprefix("tools:").split(",")] assert set(tools) == {"Bash", "Read"}, f"Unexpected tools in agent file: {tools}" assert not any("mcp__" in t for t in tools) + + +# ---------- _run_clear / clear command tests ---------- + + +def _make_valid_index_dir(cache_folder: Path, sha: str = "a" * 64) -> Path: + """Create a fake valid index directory with the expected structure.""" + index_dir = cache_folder / sha / "index" + index_dir.mkdir(parents=True) + # Create the files that PersistencePath.non_existing checks + (index_dir / "chunks.json").write_text("[]") + (index_dir / "bm25_index").write_text("") + (index_dir / "semantic_index").write_text("") + (index_dir / "metadata.json").write_text("{}") + return index_dir + + +def test_run_clear_index_with_valid_indexes(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: + """_run_clear('index') finds and reports valid indexes.""" + from semble.cli import _run_clear + + _make_valid_index_dir(tmp_path, "a" * 64) + _make_valid_index_dir(tmp_path, "b" * 64) + + with patch("semble.cli.resolve_cache_folder", return_value=tmp_path): + _run_clear("index") + + out = capsys.readouterr().out + assert "Cleared index" in out + # Both SHA dirs should appear + assert "a" * 64 in out + assert "b" * 64 in out + + +def test_run_clear_index_no_indexes_found(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: + """_run_clear('index') prints a message when no indexes exist.""" + from semble.cli import _run_clear + + with patch("semble.cli.resolve_cache_folder", return_value=tmp_path): + _run_clear("index") + + out = capsys.readouterr().out + assert "No indexes found" in out + + +def test_run_clear_index_skips_non_sha_dirs(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: + """_run_clear('index') ignores directories whose name is not a valid SHA-256.""" + from semble.cli import _run_clear + + # Create an index dir with a non-SHA parent name + bad_dir = tmp_path / "not-a-sha" / "index" + bad_dir.mkdir(parents=True) + (bad_dir / "chunks.json").write_text("[]") + (bad_dir / "bm25_index").write_text("") + (bad_dir / "semantic_index").write_text("") + (bad_dir / "metadata.json").write_text("{}") + + with patch("semble.cli.resolve_cache_folder", return_value=tmp_path): + _run_clear("index") + + out = capsys.readouterr().out + assert "No indexes found" in out + + +def test_run_clear_index_skips_incomplete_indexes(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: + """_run_clear('index') skips indexes with missing persistence files.""" + from semble.cli import _run_clear + + # Create an index dir with only some files (missing chunks.json, etc.) + index_dir = tmp_path / ("c" * 64) / "index" + index_dir.mkdir(parents=True) + # Don't create any persistence files — non_existing() will return a non-empty list + + with patch("semble.cli.resolve_cache_folder", return_value=tmp_path): + _run_clear("index") + + out = capsys.readouterr().out + assert "No indexes found" in out + + +def test_run_clear_savings_removes_file(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: + """_run_clear('savings') deletes savings.jsonl when it exists.""" + from semble.cli import _run_clear + + savings_file = tmp_path / "savings.jsonl" + savings_file.write_text('{"tokens": 100}\n') + + with patch("semble.cli.resolve_cache_folder", return_value=tmp_path): + _run_clear("savings") + + assert not savings_file.exists() + out = capsys.readouterr().out + assert "Cleared savings" in out + + +def test_run_clear_savings_no_file(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: + """_run_clear('savings') prints a message when no savings file exists.""" + from semble.cli import _run_clear + + with patch("semble.cli.resolve_cache_folder", return_value=tmp_path): + _run_clear("savings") + + out = capsys.readouterr().out + assert "No savings file found" in out + + +def test_run_clear_all_clears_both(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: + """_run_clear('all') handles both indexes and savings.""" + from semble.cli import _run_clear + + _make_valid_index_dir(tmp_path, "d" * 64) + savings_file = tmp_path / "savings.jsonl" + savings_file.write_text('{"tokens": 50}\n') + + with patch("semble.cli.resolve_cache_folder", return_value=tmp_path): + _run_clear("all") + + out = capsys.readouterr().out + assert "Cleared index" in out + assert "d" * 64 in out + assert "Cleared savings" in out + assert not savings_file.exists() + + +def test_run_clear_all_nothing_to_clear(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: + """_run_clear('all') reports both missing when cache is empty.""" + from semble.cli import _run_clear + + with patch("semble.cli.resolve_cache_folder", return_value=tmp_path): + _run_clear("all") + + out = capsys.readouterr().out + assert "No indexes found" in out + assert "No savings file found" in out + + +def test_cli_clear_command(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None: + """The `semble clear index` CLI dispatches to _run_clear correctly.""" + _make_valid_index_dir(tmp_path, "e" * 64) + monkeypatch.setattr(sys, "argv", ["semble", "clear", "index"]) + + with patch("semble.cli.resolve_cache_folder", return_value=tmp_path): + _cli_main() + + out = capsys.readouterr().out + assert "Cleared index" in out + assert "e" * 64 in out + + +def test_cli_clear_savings_command( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] +) -> None: + """The `semble clear savings` CLI removes the savings file.""" + savings_file = tmp_path / "savings.jsonl" + savings_file.write_text('{"tokens": 200}\n') + monkeypatch.setattr(sys, "argv", ["semble", "clear", "savings"]) + + with patch("semble.cli.resolve_cache_folder", return_value=tmp_path): + _cli_main() + + assert not savings_file.exists() + out = capsys.readouterr().out + assert "Cleared savings" in out + + +def test_cli_clear_all_command( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] +) -> None: + """The `semble clear all` CLI clears indexes and savings.""" + _make_valid_index_dir(tmp_path, "f" * 64) + savings_file = tmp_path / "savings.jsonl" + savings_file.write_text('{"tokens": 300}\n') + monkeypatch.setattr(sys, "argv", ["semble", "clear", "all"]) + + with patch("semble.cli.resolve_cache_folder", return_value=tmp_path): + _cli_main() + + assert not savings_file.exists() + out = capsys.readouterr().out + assert "Cleared index" in out + assert "Cleared savings" in out From 0251059b76df29e600e3014e5aabbc2fb873d696 Mon Sep 17 00:00:00 2001 From: stephantul Date: Thu, 4 Jun 2026 12:31:49 +0200 Subject: [PATCH 2/3] address comments --- src/semble/cli.py | 8 ++++---- tests/test_cli.py | 6 +++--- uv.lock | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/semble/cli.py b/src/semble/cli.py index 8ad070140..98254d190 100644 --- a/src/semble/cli.py +++ b/src/semble/cli.py @@ -138,12 +138,12 @@ def _run_find_related(path: str, file_path: str, line: int, top_k: int, content: _maybe_save_index(index, path) -def _run_clear(type: _CLEAR_CHOICE) -> None: +def _run_clear(clear_type: _CLEAR_CHOICE) -> None: """Run the `clear` subcommand.""" cache_folder = resolve_cache_folder() - if type == "index" or type == "all": + if clear_type == "index" or clear_type == "all": indexes = [] - for path in cache_folder.glob("**/index"): + for path in cache_folder.glob("*/index"): if not _SHA_256_REGEX.match(path.parent.name): continue if PersistencePath.from_path(path).non_existing(): @@ -158,7 +158,7 @@ def _run_clear(type: _CLEAR_CHOICE) -> None: rmtree(index_folder) print(f"Cleared index at `{index_folder}`") - if type == "savings" or type == "all": + if clear_type == "savings" or clear_type == "all": path = cache_folder / "savings.jsonl" if not path.exists(): print(f"No savings file found at `{path}`") diff --git a/tests/test_cli.py b/tests/test_cli.py index 7882a03bd..31c962bec 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -231,9 +231,6 @@ def test_agent_file_tools_are_bash_only() -> None: assert not any("mcp__" in t for t in tools) -# ---------- _run_clear / clear command tests ---------- - - def _make_valid_index_dir(cache_folder: Path, sha: str = "a" * 64) -> Path: """Create a fake valid index directory with the expected structure.""" index_dir = cache_folder / sha / "index" @@ -261,6 +258,8 @@ def test_run_clear_index_with_valid_indexes(tmp_path: Path, capsys: pytest.Captu # Both SHA dirs should appear assert "a" * 64 in out assert "b" * 64 in out + assert not (tmp_path / ("a" * 64)).exists() + assert not (tmp_path / ("b" * 64)).exists() def test_run_clear_index_no_indexes_found(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: @@ -350,6 +349,7 @@ def test_run_clear_all_clears_both(tmp_path: Path, capsys: pytest.CaptureFixture assert "Cleared index" in out assert "d" * 64 in out assert "Cleared savings" in out + assert not (tmp_path / ("d" * 64)).exists() assert not savings_file.exists() diff --git a/uv.lock b/uv.lock index 63f2277d0..d0db5aa64 100644 --- a/uv.lock +++ b/uv.lock @@ -10,7 +10,7 @@ resolution-markers = [ [options] exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. -exclude-newer-span = "P1W" +exclude-newer-span = "P3D" [[package]] name = "annotated-doc" From ae352164880d118a8e954816170383642e935e77 Mon Sep 17 00:00:00 2001 From: stephantul Date: Thu, 4 Jun 2026 21:38:27 +0200 Subject: [PATCH 3/3] fix tests --- tests/test_cli.py | 233 +++++++++++++++++++--------------------------- 1 file changed, 96 insertions(+), 137 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index 31c962bec..841bf8609 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,11 +1,12 @@ import sys +import warnings from importlib.resources import files from pathlib import Path from unittest.mock import MagicMock, patch import pytest -from semble.cli import _cli_main, _maybe_save_index, main +from semble.cli import _cli_main, _maybe_save_index, _run_clear, main from semble.types import ContentType, SearchResult from tests.conftest import make_chunk @@ -172,8 +173,6 @@ def test_include_text_files_cli_deprecated( capsys: pytest.CaptureFixture[str], ) -> None: """--include-text-files on CLI raises DeprecationWarning.""" - import warnings - chunk = make_chunk("def foo(): pass", "src/foo.py") fake_index = MagicMock() fake_index.search.return_value = [SearchResult(chunk=chunk, score=0.9)] @@ -243,170 +242,130 @@ def _make_valid_index_dir(cache_folder: Path, sha: str = "a" * 64) -> Path: return index_dir -def test_run_clear_index_with_valid_indexes(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: - """_run_clear('index') finds and reports valid indexes.""" - from semble.cli import _run_clear - - _make_valid_index_dir(tmp_path, "a" * 64) - _make_valid_index_dir(tmp_path, "b" * 64) - - with patch("semble.cli.resolve_cache_folder", return_value=tmp_path): - _run_clear("index") - - out = capsys.readouterr().out - assert "Cleared index" in out - # Both SHA dirs should appear - assert "a" * 64 in out - assert "b" * 64 in out - assert not (tmp_path / ("a" * 64)).exists() - assert not (tmp_path / ("b" * 64)).exists() - - -def test_run_clear_index_no_indexes_found(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: - """_run_clear('index') prints a message when no indexes exist.""" - from semble.cli import _run_clear - - with patch("semble.cli.resolve_cache_folder", return_value=tmp_path): - _run_clear("index") - - out = capsys.readouterr().out - assert "No indexes found" in out - - -def test_run_clear_index_skips_non_sha_dirs(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: - """_run_clear('index') ignores directories whose name is not a valid SHA-256.""" - from semble.cli import _run_clear - - # Create an index dir with a non-SHA parent name - bad_dir = tmp_path / "not-a-sha" / "index" - bad_dir.mkdir(parents=True) - (bad_dir / "chunks.json").write_text("[]") - (bad_dir / "bm25_index").write_text("") - (bad_dir / "semantic_index").write_text("") - (bad_dir / "metadata.json").write_text("{}") - - with patch("semble.cli.resolve_cache_folder", return_value=tmp_path): - _run_clear("index") - - out = capsys.readouterr().out - assert "No indexes found" in out - - -def test_run_clear_index_skips_incomplete_indexes(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: - """_run_clear('index') skips indexes with missing persistence files.""" - from semble.cli import _run_clear - - # Create an index dir with only some files (missing chunks.json, etc.) - index_dir = tmp_path / ("c" * 64) / "index" - index_dir.mkdir(parents=True) - # Don't create any persistence files — non_existing() will return a non-empty list +@pytest.mark.parametrize( + ("scenario", "expected_in_output"), + [ + ("valid", ["Cleared index", "a" * 64, "b" * 64]), + ("empty", ["No indexes found"]), + ("non_sha", ["No indexes found"]), + ("incomplete", ["No indexes found"]), + ], +) +def test_run_clear_index( + scenario: str, expected_in_output: list[str], tmp_path: Path, capsys: pytest.CaptureFixture[str] +) -> None: + """_run_clear('index') finds valid indexes, and skips non-SHA/incomplete/empty dirs.""" + if scenario == "valid": + _make_valid_index_dir(tmp_path, "a" * 64) + _make_valid_index_dir(tmp_path, "b" * 64) + elif scenario == "non_sha": + bad_dir = tmp_path / "not-a-sha" / "index" + bad_dir.mkdir(parents=True) + (bad_dir / "chunks.json").write_text("[]") + (bad_dir / "bm25_index").write_text("") + (bad_dir / "semantic_index").write_text("") + (bad_dir / "metadata.json").write_text("{}") + elif scenario == "incomplete": + index_dir = tmp_path / ("c" * 64) / "index" + index_dir.mkdir(parents=True) with patch("semble.cli.resolve_cache_folder", return_value=tmp_path): _run_clear("index") out = capsys.readouterr().out - assert "No indexes found" in out + for fragment in expected_in_output: + assert fragment in out + if scenario == "valid": + assert not (tmp_path / ("a" * 64)).exists() + assert not (tmp_path / ("b" * 64)).exists() -def test_run_clear_savings_removes_file(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: - """_run_clear('savings') deletes savings.jsonl when it exists.""" - from semble.cli import _run_clear +@pytest.mark.parametrize( + ("create_file", "expected"), + [ + (True, "Cleared savings"), + (False, "No savings file found"), + ], +) +def test_run_clear_savings( + create_file: bool, expected: str, tmp_path: Path, capsys: pytest.CaptureFixture[str] +) -> None: + """_run_clear('savings') deletes the file when present, reports missing otherwise.""" savings_file = tmp_path / "savings.jsonl" - savings_file.write_text('{"tokens": 100}\n') + if create_file: + savings_file.write_text('{"tokens": 100}\n') with patch("semble.cli.resolve_cache_folder", return_value=tmp_path): _run_clear("savings") - assert not savings_file.exists() + if create_file: + assert not savings_file.exists() out = capsys.readouterr().out - assert "Cleared savings" in out - + assert expected in out -def test_run_clear_savings_no_file(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: - """_run_clear('savings') prints a message when no savings file exists.""" - from semble.cli import _run_clear - with patch("semble.cli.resolve_cache_folder", return_value=tmp_path): - _run_clear("savings") - - out = capsys.readouterr().out - assert "No savings file found" in out - - -def test_run_clear_all_clears_both(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: +@pytest.mark.parametrize( + ("populate", "expected_fragments"), + [ + (True, ["Cleared index", "d" * 64, "Cleared savings"]), + (False, ["No indexes found", "No savings file found"]), + ], +) +def test_run_clear_all( + populate: bool, expected_fragments: list[str], tmp_path: Path, capsys: pytest.CaptureFixture[str] +) -> None: """_run_clear('all') handles both indexes and savings.""" - from semble.cli import _run_clear - - _make_valid_index_dir(tmp_path, "d" * 64) - savings_file = tmp_path / "savings.jsonl" - savings_file.write_text('{"tokens": 50}\n') - - with patch("semble.cli.resolve_cache_folder", return_value=tmp_path): - _run_clear("all") - - out = capsys.readouterr().out - assert "Cleared index" in out - assert "d" * 64 in out - assert "Cleared savings" in out - assert not (tmp_path / ("d" * 64)).exists() - assert not savings_file.exists() - - -def test_run_clear_all_nothing_to_clear(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: - """_run_clear('all') reports both missing when cache is empty.""" - from semble.cli import _run_clear + if populate: + _make_valid_index_dir(tmp_path, "d" * 64) + (tmp_path / "savings.jsonl").write_text('{"tokens": 50}\n') with patch("semble.cli.resolve_cache_folder", return_value=tmp_path): _run_clear("all") out = capsys.readouterr().out - assert "No indexes found" in out - assert "No savings file found" in out - - -def test_cli_clear_command(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None: - """The `semble clear index` CLI dispatches to _run_clear correctly.""" - _make_valid_index_dir(tmp_path, "e" * 64) - monkeypatch.setattr(sys, "argv", ["semble", "clear", "index"]) - - with patch("semble.cli.resolve_cache_folder", return_value=tmp_path): - _cli_main() + for fragment in expected_fragments: + assert fragment in out - out = capsys.readouterr().out - assert "Cleared index" in out - assert "e" * 64 in out + if populate: + assert not (tmp_path / ("d" * 64)).exists() + assert not (tmp_path / "savings.jsonl").exists() -def test_cli_clear_savings_command( - tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] +@pytest.mark.parametrize( + ("subcommand", "setup_index", "setup_savings", "expected_fragments"), + [ + ("index", True, False, ["Cleared index", "e" * 64]), + ("savings", False, True, ["Cleared savings"]), + ("all", True, True, ["Cleared index", "Cleared savings"]), + ], +) +def test_cli_clear_command( + subcommand: str, + setup_index: bool, + setup_savings: bool, + expected_fragments: list[str], + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture[str], ) -> None: - """The `semble clear savings` CLI removes the savings file.""" + """The `semble clear ` CLI dispatches to _run_clear correctly.""" + sha = "e" * 64 + if setup_index: + _make_valid_index_dir(tmp_path, sha) savings_file = tmp_path / "savings.jsonl" - savings_file.write_text('{"tokens": 200}\n') - monkeypatch.setattr(sys, "argv", ["semble", "clear", "savings"]) + if setup_savings: + savings_file.write_text('{"tokens": 200}\n') + monkeypatch.setattr(sys, "argv", ["semble", "clear", subcommand]) with patch("semble.cli.resolve_cache_folder", return_value=tmp_path): _cli_main() - assert not savings_file.exists() out = capsys.readouterr().out - assert "Cleared savings" in out - - -def test_cli_clear_all_command( - tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] -) -> None: - """The `semble clear all` CLI clears indexes and savings.""" - _make_valid_index_dir(tmp_path, "f" * 64) - savings_file = tmp_path / "savings.jsonl" - savings_file.write_text('{"tokens": 300}\n') - monkeypatch.setattr(sys, "argv", ["semble", "clear", "all"]) - - with patch("semble.cli.resolve_cache_folder", return_value=tmp_path): - _cli_main() + for fragment in expected_fragments: + assert fragment in out - assert not savings_file.exists() - out = capsys.readouterr().out - assert "Cleared index" in out - assert "Cleared savings" in out + if setup_index: + assert not (tmp_path / sha).exists() + if setup_savings: + assert not savings_file.exists()