From 80335dc2c8c9665f8ec157e789ea25f175519dd7 Mon Sep 17 00:00:00 2001 From: 0neStep <146049978+AperturePlus@users.noreply.github.com> Date: Sat, 14 Mar 2026 14:54:03 +0800 Subject: [PATCH] fix(cli): scope metadata db path to target repository Why: Index/search/update resolved .aci/index.db from CWD, which breaks cross-directory CLI usage and causes 'Path has not been indexed' errors. What: Added a project-scoped metadata DB path helper and wired index/search/update to initialize services with /.aci/index.db instead of a CWD-relative default. Added unit tests to verify the scoped path behavior in CLI flows. Test: uv run ruff check src tests (pass) Test: uv run pytest tests/unit/test_cli.py tests/unit/test_cli_metadata_db_path.py -q (pass) Test: uv run pytest tests/ -v --tb=short -q --durations=10 (interrupted; suite showed pre-existing failures while running) Test: uv run mypy src --ignore-missing-imports --no-error-summary (fails with many pre-existing typing issues) --- src/aci/cli/__init__.py | 21 ++++++---- tests/unit/test_cli_metadata_db_path.py | 51 +++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 tests/unit/test_cli_metadata_db_path.py diff --git a/src/aci/cli/__init__.py b/src/aci/cli/__init__.py index cd3ded7..4516db7 100644 --- a/src/aci/cli/__init__.py +++ b/src/aci/cli/__init__.py @@ -55,7 +55,12 @@ context_settings={"color": False}, ) -def get_services(): +def _project_metadata_db_path(path: Path) -> Path: + """Return the metadata DB path scoped to a project root.""" + return path.resolve() / ".aci" / "index.db" + + +def get_services(metadata_db_path: Path | None = None): """ Initialize services from .env with config-driven settings. @@ -67,7 +72,7 @@ def get_services(): Tuple of (config, embedding_client, vector_store, metadata_store, file_scanner, chunker, reranker) """ - container = create_services() + container = create_services(metadata_db_path=metadata_db_path) return ( container.config, container.embedding_client, @@ -104,7 +109,7 @@ def index( file_scanner, chunker, reranker, - ) = get_services() + ) = get_services(metadata_db_path=_project_metadata_db_path(path)) # Use config workers if not overridden by CLI actual_workers = workers if workers is not None else cfg.indexing.max_workers @@ -221,6 +226,9 @@ def search( ): """Search the indexed codebase.""" try: + # Determine the search base path + search_base = path if path is not None else Path.cwd() + ( cfg, embedding_client, @@ -229,10 +237,7 @@ def search( file_scanner, chunker, config_reranker, - ) = get_services() - - # Determine the search base path - search_base = path if path is not None else Path.cwd() + ) = get_services(metadata_db_path=_project_metadata_db_path(search_base)) # Use centralized repository resolution for path validation and collection name resolution = resolve_repository(search_base, metadata_store) @@ -380,7 +385,7 @@ def update( file_scanner, chunker, reranker, - ) = get_services() + ) = get_services(metadata_db_path=_project_metadata_db_path(path)) with Progress( SpinnerColumn(), diff --git a/tests/unit/test_cli_metadata_db_path.py b/tests/unit/test_cli_metadata_db_path.py new file mode 100644 index 0000000..0a89b3b --- /dev/null +++ b/tests/unit/test_cli_metadata_db_path.py @@ -0,0 +1,51 @@ +from pathlib import Path + +import pytest +import typer + +import aci.cli as cli + + +def test_project_metadata_db_path_is_scoped_to_project_root(tmp_path: Path) -> None: + nested = tmp_path / "repo" / "subdir" + nested.mkdir(parents=True) + + db_path = cli._project_metadata_db_path(nested) + + assert db_path == nested.resolve() / ".aci" / "index.db" + + +def test_index_uses_project_scoped_metadata_db_path(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: + indexed_path = tmp_path / "repo" + indexed_path.mkdir() + captured: dict[str, Path | None] = {"metadata_db_path": None} + + def fake_get_services(metadata_db_path: Path | None = None): + captured["metadata_db_path"] = metadata_db_path + raise RuntimeError("stop") + + monkeypatch.setattr(cli, "get_services", fake_get_services) + + with pytest.raises(typer.Exit): + cli.index(path=indexed_path, workers=None) + + assert captured["metadata_db_path"] == indexed_path.resolve() / ".aci" / "index.db" + + +def test_search_uses_explicit_path_for_project_scoped_metadata_db_path( + monkeypatch: pytest.MonkeyPatch, tmp_path: Path +) -> None: + search_path = tmp_path / "repo" + search_path.mkdir() + captured: dict[str, Path | None] = {"metadata_db_path": None} + + def fake_get_services(metadata_db_path: Path | None = None): + captured["metadata_db_path"] = metadata_db_path + raise RuntimeError("stop") + + monkeypatch.setattr(cli, "get_services", fake_get_services) + + with pytest.raises(typer.Exit): + cli.search(query="hello", path=search_path) + + assert captured["metadata_db_path"] == search_path.resolve() / ".aci" / "index.db"