agent-coderag 1.4.0__tar.gz → 1.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agent_coderag-1.4.0/agent_coderag.egg-info → agent_coderag-1.4.1}/PKG-INFO +3 -3
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/README.md +2 -2
- {agent_coderag-1.4.0 → agent_coderag-1.4.1/agent_coderag.egg-info}/PKG-INFO +3 -3
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/agent_coderag.egg-info/SOURCES.txt +2 -0
- agent_coderag-1.4.1/agent_coderag.egg-info/entry_points.txt +2 -0
- agent_coderag-1.4.1/code_rag/__init__.py +69 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/core/error_codes.py +1 -0
- agent_coderag-1.4.1/code_rag/entry/args.py +113 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/entry/cli.py +25 -125
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/intelligence/distiller.py +2 -1
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/intelligence/openai_embedder.py +3 -1
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/storage/duckdb_impl.py +14 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/pyproject.toml +2 -2
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_cli_detailed.py +40 -2
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_cli_embeddings.py +2 -1
- agent_coderag-1.4.1/tests/test_cli_startup.py +123 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_distiller.py +3 -3
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_error_codes.py +4 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_indexing_embeddings.py +1 -1
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_openai_embedder.py +12 -12
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_readme_lifetime_storage.py +1 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_storage_embeddings.py +71 -0
- agent_coderag-1.4.0/agent_coderag.egg-info/entry_points.txt +0 -2
- agent_coderag-1.4.0/code_rag/__init__.py +0 -21
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/LICENSE +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/NOTICE +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/agent_coderag.egg-info/dependency_links.txt +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/agent_coderag.egg-info/requires.txt +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/agent_coderag.egg-info/top_level.txt +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/api/__init__.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/api/client.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/api/models.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/core/__init__.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/core/constants.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/core/exceptions.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/core/interfaces.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/core/models.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/core/utils.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/discovery/__init__.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/discovery/dependency.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/discovery/java_discovery.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/discovery/manager.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/discovery/providers/__init__.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/discovery/providers/base.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/discovery/providers/csharp.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/discovery/providers/go.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/discovery/providers/java.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/discovery/providers/javascript.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/discovery/providers/python.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/discovery/providers/rust.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/entry/__init__.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/intelligence/__init__.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/intelligence/embedder.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/intelligence/factory.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/parsers/__init__.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/parsers/languages.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/parsers/multi_parser.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/parsers/tree_sitter.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/paths.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/services/__init__.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/services/config.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/services/dependencies.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/services/discovery_api.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/services/factory.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/services/indexing.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/services/search.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/services/setup.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/services/sync.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/storage/__init__.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/code_rag/storage/db_connection.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/setup.cfg +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_api_client.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_api_lazy_db.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_api_models.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_api_rebuild_wipe.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_cli.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_cli_json_parity.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_code_rag_simple.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_coderag_lifetime.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_csharp_discovery_detailed.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_db_connection.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_db_path.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_dependency_discovery.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_discovery_manager_detailed.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_discovery_providers_extra.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_discovery_python.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_distiller_config_embedding.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_distiller_extra.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_embedder.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_embedder_detailed.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_embedder_factory.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_embedder_interface.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_embedder_stubs.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_factory_async.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_indexing_detailed.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_indexing_extra.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_interfaces.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_js_discovery_detailed.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_languages.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_local_onnx_embedder.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_models.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_multi_parser.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_readme_library_usage.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_readme_offline_embeddings.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_rust_discovery_detailed.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_storage_detailed.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_sync_worker_errors.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_tree_sitter_parser.py +0 -0
- {agent_coderag-1.4.0 → agent_coderag-1.4.1}/tests/test_utils_detailed.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-coderag
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.1
|
|
4
4
|
Summary: Lightweight semantic code search and distillation utility for AI coding agents. It solves the API knowledge gap via real-time local signature extraction and intent analysis without PyTorch. Optimized for token efficiency, it compresses codebase context into compact semantic summaries stored in a local DuckDB vector similarity index.
|
|
5
5
|
Author-email: Igor Boloban <naranor@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -189,10 +189,10 @@ async def main():
|
|
|
189
189
|
- **Explicit path:** `CodeRAG(db=...)` / `agent-coderag --db ...`. Path is a **file**, not a directory. Relative paths resolve against **process cwd**, not `root`.
|
|
190
190
|
- **Sidecars:** DuckDB may write WAL sidecars (e.g. `.coderag.db.wal`) beside the index during writes; locks should not persist after an operation finishes.
|
|
191
191
|
- **Connect timeout:** `connect_timeout_seconds=5` (CLI `--connect-timeout`) waits on file locks, then raises `StorageBusyError` (`ErrorCode.STORAGE_BUSY`). Pass `0` for a single attempt.
|
|
192
|
-
- **Read-only search:** `search` (and `api` when storage is needed) opens read-only. A missing index file is an error — use `sync`/`rebuild` to create it. With `--json`, success is a hit array; errors are `{"status":"error","message":...}`
|
|
192
|
+
- **Read-only search:** `search` (and `api` when storage is needed) opens read-only. A missing **index file** is an error — use `sync`/`rebuild` to create it. An index file that exists but has no embeddings table (e.g. opened/written without a completed vector sync) raises `StorageError` with `ErrorCode.EMBEDDINGS_MISSING` — run `sync` (library: `CodeRAG.sync`) before search. With `--json`, success is a hit array; errors are `{"status":"error","message":...}` and include `"code"` when the exception carries an `ErrorCode`.
|
|
193
193
|
- **Lifetime:** embedder/parser/distiller stay warm; DuckDB opens per operation and closes afterward. One `CodeRAG` instance serializes overlapping ops. `config()` with embedding flags / `--clear-embedding` closes the process embedder so the next op rebuilds it; distill-only `config` refreshes Distiller and keeps the embedder.
|
|
194
194
|
- **`api()` without DB:** providers that do not need the index (e.g. Python) skip DuckDB entirely; Java uses a short read-only open for JAR cache lookup.
|
|
195
|
-
- **Errors:** catch `CodeRAGError` and inspect `.code` — `STORAGE_BUSY`, `STORAGE_CORRUPT`, `EMBEDDING_MISMATCH` (`from code_rag import ErrorCode`).
|
|
195
|
+
- **Errors:** catch `CodeRAGError` and inspect `.code` — `STORAGE_BUSY`, `STORAGE_CORRUPT`, `EMBEDDING_MISMATCH`, `EMBEDDINGS_MISSING` (`from code_rag import ErrorCode`).
|
|
196
196
|
|
|
197
197
|
---
|
|
198
198
|
|
|
@@ -143,10 +143,10 @@ async def main():
|
|
|
143
143
|
- **Explicit path:** `CodeRAG(db=...)` / `agent-coderag --db ...`. Path is a **file**, not a directory. Relative paths resolve against **process cwd**, not `root`.
|
|
144
144
|
- **Sidecars:** DuckDB may write WAL sidecars (e.g. `.coderag.db.wal`) beside the index during writes; locks should not persist after an operation finishes.
|
|
145
145
|
- **Connect timeout:** `connect_timeout_seconds=5` (CLI `--connect-timeout`) waits on file locks, then raises `StorageBusyError` (`ErrorCode.STORAGE_BUSY`). Pass `0` for a single attempt.
|
|
146
|
-
- **Read-only search:** `search` (and `api` when storage is needed) opens read-only. A missing index file is an error — use `sync`/`rebuild` to create it. With `--json`, success is a hit array; errors are `{"status":"error","message":...}`
|
|
146
|
+
- **Read-only search:** `search` (and `api` when storage is needed) opens read-only. A missing **index file** is an error — use `sync`/`rebuild` to create it. An index file that exists but has no embeddings table (e.g. opened/written without a completed vector sync) raises `StorageError` with `ErrorCode.EMBEDDINGS_MISSING` — run `sync` (library: `CodeRAG.sync`) before search. With `--json`, success is a hit array; errors are `{"status":"error","message":...}` and include `"code"` when the exception carries an `ErrorCode`.
|
|
147
147
|
- **Lifetime:** embedder/parser/distiller stay warm; DuckDB opens per operation and closes afterward. One `CodeRAG` instance serializes overlapping ops. `config()` with embedding flags / `--clear-embedding` closes the process embedder so the next op rebuilds it; distill-only `config` refreshes Distiller and keeps the embedder.
|
|
148
148
|
- **`api()` without DB:** providers that do not need the index (e.g. Python) skip DuckDB entirely; Java uses a short read-only open for JAR cache lookup.
|
|
149
|
-
- **Errors:** catch `CodeRAGError` and inspect `.code` — `STORAGE_BUSY`, `STORAGE_CORRUPT`, `EMBEDDING_MISMATCH` (`from code_rag import ErrorCode`).
|
|
149
|
+
- **Errors:** catch `CodeRAGError` and inspect `.code` — `STORAGE_BUSY`, `STORAGE_CORRUPT`, `EMBEDDING_MISMATCH`, `EMBEDDINGS_MISSING` (`from code_rag import ErrorCode`).
|
|
150
150
|
|
|
151
151
|
---
|
|
152
152
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-coderag
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.1
|
|
4
4
|
Summary: Lightweight semantic code search and distillation utility for AI coding agents. It solves the API knowledge gap via real-time local signature extraction and intent analysis without PyTorch. Optimized for token efficiency, it compresses codebase context into compact semantic summaries stored in a local DuckDB vector similarity index.
|
|
5
5
|
Author-email: Igor Boloban <naranor@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -189,10 +189,10 @@ async def main():
|
|
|
189
189
|
- **Explicit path:** `CodeRAG(db=...)` / `agent-coderag --db ...`. Path is a **file**, not a directory. Relative paths resolve against **process cwd**, not `root`.
|
|
190
190
|
- **Sidecars:** DuckDB may write WAL sidecars (e.g. `.coderag.db.wal`) beside the index during writes; locks should not persist after an operation finishes.
|
|
191
191
|
- **Connect timeout:** `connect_timeout_seconds=5` (CLI `--connect-timeout`) waits on file locks, then raises `StorageBusyError` (`ErrorCode.STORAGE_BUSY`). Pass `0` for a single attempt.
|
|
192
|
-
- **Read-only search:** `search` (and `api` when storage is needed) opens read-only. A missing index file is an error — use `sync`/`rebuild` to create it. With `--json`, success is a hit array; errors are `{"status":"error","message":...}`
|
|
192
|
+
- **Read-only search:** `search` (and `api` when storage is needed) opens read-only. A missing **index file** is an error — use `sync`/`rebuild` to create it. An index file that exists but has no embeddings table (e.g. opened/written without a completed vector sync) raises `StorageError` with `ErrorCode.EMBEDDINGS_MISSING` — run `sync` (library: `CodeRAG.sync`) before search. With `--json`, success is a hit array; errors are `{"status":"error","message":...}` and include `"code"` when the exception carries an `ErrorCode`.
|
|
193
193
|
- **Lifetime:** embedder/parser/distiller stay warm; DuckDB opens per operation and closes afterward. One `CodeRAG` instance serializes overlapping ops. `config()` with embedding flags / `--clear-embedding` closes the process embedder so the next op rebuilds it; distill-only `config` refreshes Distiller and keeps the embedder.
|
|
194
194
|
- **`api()` without DB:** providers that do not need the index (e.g. Python) skip DuckDB entirely; Java uses a short read-only open for JAR cache lookup.
|
|
195
|
-
- **Errors:** catch `CodeRAGError` and inspect `.code` — `STORAGE_BUSY`, `STORAGE_CORRUPT`, `EMBEDDING_MISMATCH` (`from code_rag import ErrorCode`).
|
|
195
|
+
- **Errors:** catch `CodeRAGError` and inspect `.code` — `STORAGE_BUSY`, `STORAGE_CORRUPT`, `EMBEDDING_MISMATCH`, `EMBEDDINGS_MISSING` (`from code_rag import ErrorCode`).
|
|
196
196
|
|
|
197
197
|
---
|
|
198
198
|
|
|
@@ -33,6 +33,7 @@ code_rag/discovery/providers/javascript.py
|
|
|
33
33
|
code_rag/discovery/providers/python.py
|
|
34
34
|
code_rag/discovery/providers/rust.py
|
|
35
35
|
code_rag/entry/__init__.py
|
|
36
|
+
code_rag/entry/args.py
|
|
36
37
|
code_rag/entry/cli.py
|
|
37
38
|
code_rag/intelligence/__init__.py
|
|
38
39
|
code_rag/intelligence/distiller.py
|
|
@@ -63,6 +64,7 @@ tests/test_cli.py
|
|
|
63
64
|
tests/test_cli_detailed.py
|
|
64
65
|
tests/test_cli_embeddings.py
|
|
65
66
|
tests/test_cli_json_parity.py
|
|
67
|
+
tests/test_cli_startup.py
|
|
66
68
|
tests/test_code_rag_simple.py
|
|
67
69
|
tests/test_coderag_lifetime.py
|
|
68
70
|
tests/test_csharp_discovery_detailed.py
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Public package surface. Imports stay lazy so ``agent-coderag --help`` does not load DuckDB, ONNX, or LiteLLM."""
|
|
2
|
+
|
|
3
|
+
import importlib
|
|
4
|
+
|
|
5
|
+
# Names are assigned so static checkers accept ``__all__``, then removed so
|
|
6
|
+
# attribute access goes through ``__getattr__`` and stays lazy.
|
|
7
|
+
CodeRAG = None
|
|
8
|
+
SyncResult = None
|
|
9
|
+
ApiReport = None
|
|
10
|
+
SetupResult = None
|
|
11
|
+
KnowledgeUnit = None
|
|
12
|
+
UnitKind = None
|
|
13
|
+
DistillerConfig = None
|
|
14
|
+
CodeRAGError = None
|
|
15
|
+
ErrorCode = None
|
|
16
|
+
StorageBusyError = None
|
|
17
|
+
default_db_path = None
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"CodeRAG",
|
|
21
|
+
"SyncResult",
|
|
22
|
+
"ApiReport",
|
|
23
|
+
"SetupResult",
|
|
24
|
+
"KnowledgeUnit",
|
|
25
|
+
"UnitKind",
|
|
26
|
+
"DistillerConfig",
|
|
27
|
+
"CodeRAGError",
|
|
28
|
+
"ErrorCode",
|
|
29
|
+
"StorageBusyError",
|
|
30
|
+
"default_db_path",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
_EXPORTS = {
|
|
34
|
+
"CodeRAG": ("code_rag.api.client", "CodeRAG"),
|
|
35
|
+
"SyncResult": ("code_rag.api.models", "SyncResult"),
|
|
36
|
+
"ApiReport": ("code_rag.api.models", "ApiReport"),
|
|
37
|
+
"SetupResult": ("code_rag.api.models", "SetupResult"),
|
|
38
|
+
"KnowledgeUnit": ("code_rag.core.models", "KnowledgeUnit"),
|
|
39
|
+
"UnitKind": ("code_rag.core.models", "UnitKind"),
|
|
40
|
+
"DistillerConfig": ("code_rag.intelligence.distiller", "DistillerConfig"),
|
|
41
|
+
"CodeRAGError": ("code_rag.core.exceptions", "CodeRAGError"),
|
|
42
|
+
"ErrorCode": ("code_rag.core.error_codes", "ErrorCode"),
|
|
43
|
+
"StorageBusyError": ("code_rag.core.exceptions", "StorageBusyError"),
|
|
44
|
+
"default_db_path": ("code_rag.paths", "default_db_path"),
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
del (
|
|
48
|
+
CodeRAG,
|
|
49
|
+
SyncResult,
|
|
50
|
+
ApiReport,
|
|
51
|
+
SetupResult,
|
|
52
|
+
KnowledgeUnit,
|
|
53
|
+
UnitKind,
|
|
54
|
+
DistillerConfig,
|
|
55
|
+
CodeRAGError,
|
|
56
|
+
ErrorCode,
|
|
57
|
+
StorageBusyError,
|
|
58
|
+
default_db_path,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def __getattr__(name):
|
|
63
|
+
target = _EXPORTS.get(name)
|
|
64
|
+
if target is None:
|
|
65
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
66
|
+
module_name, attr = target
|
|
67
|
+
value = getattr(importlib.import_module(module_name), attr)
|
|
68
|
+
globals()[name] = value
|
|
69
|
+
return value
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Argparse entry. Importing this module must stay free of DuckDB, ONNX, and LiteLLM."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import importlib
|
|
5
|
+
import logging
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from code_rag.core.constants import DEFAULT_CONNECT_TIMEOUT_SECONDS
|
|
9
|
+
from code_rag.core.exceptions import CodeRAGError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
13
|
+
parser = argparse.ArgumentParser(description="CodeRAG: API Knowledge Bridge.")
|
|
14
|
+
parser.add_argument(
|
|
15
|
+
"--db",
|
|
16
|
+
default=None,
|
|
17
|
+
help=(
|
|
18
|
+
"DuckDB index file. Default: resolve legacy code_rag.db in cwd/root, "
|
|
19
|
+
"else use .coderag.db under project root."
|
|
20
|
+
),
|
|
21
|
+
)
|
|
22
|
+
parser.add_argument("--onnx", help="Path to local ONNX model file.")
|
|
23
|
+
parser.add_argument(
|
|
24
|
+
"--connect-timeout",
|
|
25
|
+
dest="connect_timeout",
|
|
26
|
+
type=float,
|
|
27
|
+
default=DEFAULT_CONNECT_TIMEOUT_SECONDS,
|
|
28
|
+
help=(
|
|
29
|
+
"Seconds to wait for a DuckDB file lock before StorageBusyError "
|
|
30
|
+
f"(default {DEFAULT_CONNECT_TIMEOUT_SECONDS:g}). 0 = no retry."
|
|
31
|
+
),
|
|
32
|
+
)
|
|
33
|
+
parser.add_argument(
|
|
34
|
+
"--verbose", action="store_true", help="Enable verbose logging."
|
|
35
|
+
)
|
|
36
|
+
parser.add_argument("--json", action="store_true", help="Output results in JSON.")
|
|
37
|
+
|
|
38
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
39
|
+
|
|
40
|
+
sync = subparsers.add_parser("sync", help="Index code units.")
|
|
41
|
+
sync.add_argument("path", nargs="?", help="File or directory to index.")
|
|
42
|
+
sync.add_argument("--all", action="store_true", help="Index all supported files.")
|
|
43
|
+
sync.add_argument("--force", action="store_true", help="Force re-distillation.")
|
|
44
|
+
sync.add_argument(
|
|
45
|
+
"--allow-build-execution",
|
|
46
|
+
action="store_true",
|
|
47
|
+
help="Execute repository Maven/Gradle build files during dependency sync (trusted projects only).",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
search = subparsers.add_parser("search", help="Semantic search.")
|
|
51
|
+
search.add_argument("query", help="Natural language query.")
|
|
52
|
+
search.add_argument("--limit", type=int, default=5, help="Result limit.")
|
|
53
|
+
|
|
54
|
+
api = subparsers.add_parser("api", help="Discover library API.")
|
|
55
|
+
api.add_argument("library", help="Library name (e.g., pydantic).")
|
|
56
|
+
api.add_argument("--lang", help="Target language.")
|
|
57
|
+
|
|
58
|
+
cfg = subparsers.add_parser("config", help="Manage LLM configuration.")
|
|
59
|
+
cfg.add_argument("--url", help="API base URL.")
|
|
60
|
+
cfg.add_argument("--key", help="API key.")
|
|
61
|
+
cfg.add_argument("--model", help="Model name.")
|
|
62
|
+
cfg.add_argument("--provider", help="Provider name (openai, ollama).")
|
|
63
|
+
cfg.add_argument(
|
|
64
|
+
"--embedding-url", dest="embedding_url", help="Embedding API base URL."
|
|
65
|
+
)
|
|
66
|
+
cfg.add_argument("--embedding-key", dest="embedding_key", help="Embedding API key.")
|
|
67
|
+
cfg.add_argument(
|
|
68
|
+
"--embedding-model", dest="embedding_model", help="Remote embedding model id."
|
|
69
|
+
)
|
|
70
|
+
cfg.add_argument(
|
|
71
|
+
"--embedding-provider",
|
|
72
|
+
dest="embedding_provider",
|
|
73
|
+
help="LiteLLM custom_llm_provider for embeddings.",
|
|
74
|
+
)
|
|
75
|
+
cfg.add_argument(
|
|
76
|
+
"--clear-embedding",
|
|
77
|
+
dest="clear_embedding",
|
|
78
|
+
action="store_true",
|
|
79
|
+
help="Clear remote embedding config (local MiniLM).",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
setup = subparsers.add_parser("setup", help="Initial setup (download models).")
|
|
83
|
+
setup.add_argument("--force", action="store_true", help="Force model redownload.")
|
|
84
|
+
|
|
85
|
+
subparsers.add_parser("rebuild", help="Full re-index of the project.")
|
|
86
|
+
return parser
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def main() -> None:
|
|
90
|
+
parser = build_parser()
|
|
91
|
+
try:
|
|
92
|
+
args = parser.parse_args()
|
|
93
|
+
|
|
94
|
+
if args.verbose:
|
|
95
|
+
logging.basicConfig(level=logging.INFO)
|
|
96
|
+
else:
|
|
97
|
+
logging.basicConfig(level=logging.WARNING)
|
|
98
|
+
|
|
99
|
+
if not args.command:
|
|
100
|
+
parser.print_help()
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
# Heavy stack (DuckDB, ONNX, parsers) loads only for a real subcommand.
|
|
104
|
+
dispatch = importlib.import_module("code_rag.entry.cli").dispatch
|
|
105
|
+
dispatch(args)
|
|
106
|
+
except CodeRAGError as exc:
|
|
107
|
+
print(f"Error: {exc}", file=sys.stderr)
|
|
108
|
+
sys.exit(1)
|
|
109
|
+
except KeyboardInterrupt:
|
|
110
|
+
sys.exit(0)
|
|
111
|
+
except Exception as exc:
|
|
112
|
+
logging.getLogger(__name__).error("Unexpected error: %s", exc)
|
|
113
|
+
sys.exit(1)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import asyncio
|
|
3
|
-
import argparse
|
|
4
3
|
import sys
|
|
5
4
|
import json
|
|
6
5
|
from pathlib import Path
|
|
@@ -43,7 +42,6 @@ __all__ = [
|
|
|
43
42
|
"get_global_dir",
|
|
44
43
|
"requests",
|
|
45
44
|
"validate_path",
|
|
46
|
-
"main",
|
|
47
45
|
]
|
|
48
46
|
|
|
49
47
|
|
|
@@ -59,6 +57,14 @@ def _coderag_from_args(args) -> CodeRAG:
|
|
|
59
57
|
)
|
|
60
58
|
|
|
61
59
|
|
|
60
|
+
def _emit_json_error(exc: Exception) -> None:
|
|
61
|
+
payload = {"status": "error", "message": str(exc)}
|
|
62
|
+
code = getattr(exc, "code", None)
|
|
63
|
+
if isinstance(exc, CodeRAGError) and code is not None:
|
|
64
|
+
payload["code"] = code.value if hasattr(code, "value") else str(code)
|
|
65
|
+
print(json.dumps(payload))
|
|
66
|
+
|
|
67
|
+
|
|
62
68
|
def load_ignore_patterns() -> pathspec.PathSpec:
|
|
63
69
|
"""Loads ignore patterns from the current directory's .gitignore or defaults."""
|
|
64
70
|
return sync_service.load_ignore_patterns(Path.cwd())
|
|
@@ -105,7 +111,7 @@ async def sync_cmd(args):
|
|
|
105
111
|
except Exception as e:
|
|
106
112
|
logger.error("Sync failed: %s", e)
|
|
107
113
|
if args.json:
|
|
108
|
-
|
|
114
|
+
_emit_json_error(e)
|
|
109
115
|
else:
|
|
110
116
|
print(f"Error: {e}", file=sys.stderr)
|
|
111
117
|
raise SystemExit(1) from e
|
|
@@ -146,7 +152,7 @@ async def search_cmd(args):
|
|
|
146
152
|
except Exception as e:
|
|
147
153
|
logger.error("Search failed: %s", e)
|
|
148
154
|
if args.json:
|
|
149
|
-
|
|
155
|
+
_emit_json_error(e)
|
|
150
156
|
else:
|
|
151
157
|
print(f"Error: {e}", file=sys.stderr)
|
|
152
158
|
raise SystemExit(1) from e
|
|
@@ -166,7 +172,7 @@ async def api_cmd(args):
|
|
|
166
172
|
except Exception as e:
|
|
167
173
|
logger.error("API discovery failed: %s", e)
|
|
168
174
|
if args.json:
|
|
169
|
-
|
|
175
|
+
_emit_json_error(e)
|
|
170
176
|
else:
|
|
171
177
|
print(f"Error: {e}", file=sys.stderr)
|
|
172
178
|
finally:
|
|
@@ -277,7 +283,7 @@ async def rebuild_cmd(args):
|
|
|
277
283
|
except Exception as exc:
|
|
278
284
|
logger.error("Rebuild failed: %s", exc)
|
|
279
285
|
if args.json:
|
|
280
|
-
|
|
286
|
+
_emit_json_error(exc)
|
|
281
287
|
else:
|
|
282
288
|
print(f"Error: {exc}", file=sys.stderr)
|
|
283
289
|
raise SystemExit(1) from exc
|
|
@@ -285,122 +291,16 @@ async def rebuild_cmd(args):
|
|
|
285
291
|
await rag.close()
|
|
286
292
|
|
|
287
293
|
|
|
288
|
-
def
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
)
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
dest="connect_timeout",
|
|
302
|
-
type=float,
|
|
303
|
-
default=DEFAULT_CONNECT_TIMEOUT_SECONDS,
|
|
304
|
-
help=(
|
|
305
|
-
"Seconds to wait for a DuckDB file lock before StorageBusyError "
|
|
306
|
-
f"(default {DEFAULT_CONNECT_TIMEOUT_SECONDS:g}). 0 = no retry."
|
|
307
|
-
),
|
|
308
|
-
)
|
|
309
|
-
parser.add_argument(
|
|
310
|
-
"--verbose", action="store_true", help="Enable verbose logging."
|
|
311
|
-
)
|
|
312
|
-
parser.add_argument("--json", action="store_true", help="Output results in JSON.")
|
|
313
|
-
|
|
314
|
-
subparsers = parser.add_subparsers(dest="command")
|
|
315
|
-
|
|
316
|
-
# Sync
|
|
317
|
-
sync = subparsers.add_parser("sync", help="Index code units.")
|
|
318
|
-
sync.add_argument("path", nargs="?", help="File or directory to index.")
|
|
319
|
-
sync.add_argument("--all", action="store_true", help="Index all supported files.")
|
|
320
|
-
sync.add_argument("--force", action="store_true", help="Force re-distillation.")
|
|
321
|
-
sync.add_argument(
|
|
322
|
-
"--allow-build-execution",
|
|
323
|
-
action="store_true",
|
|
324
|
-
help="Execute repository Maven/Gradle build files during dependency sync (trusted projects only).",
|
|
325
|
-
)
|
|
326
|
-
|
|
327
|
-
# Search
|
|
328
|
-
search = subparsers.add_parser("search", help="Semantic search.")
|
|
329
|
-
search.add_argument("query", help="Natural language query.")
|
|
330
|
-
search.add_argument("--limit", type=int, default=5, help="Result limit.")
|
|
331
|
-
|
|
332
|
-
# API
|
|
333
|
-
api = subparsers.add_parser("api", help="Discover library API.")
|
|
334
|
-
api.add_argument("library", help="Library name (e.g., pydantic).")
|
|
335
|
-
api.add_argument("--lang", help="Target language.")
|
|
336
|
-
|
|
337
|
-
# Config
|
|
338
|
-
cfg = subparsers.add_parser("config", help="Manage LLM configuration.")
|
|
339
|
-
cfg.add_argument("--url", help="API base URL.")
|
|
340
|
-
cfg.add_argument("--key", help="API key.")
|
|
341
|
-
cfg.add_argument("--model", help="Model name.")
|
|
342
|
-
cfg.add_argument("--provider", help="Provider name (openai, ollama).")
|
|
343
|
-
cfg.add_argument(
|
|
344
|
-
"--embedding-url", dest="embedding_url", help="Embedding API base URL."
|
|
345
|
-
)
|
|
346
|
-
cfg.add_argument("--embedding-key", dest="embedding_key", help="Embedding API key.")
|
|
347
|
-
cfg.add_argument(
|
|
348
|
-
"--embedding-model", dest="embedding_model", help="Remote embedding model id."
|
|
349
|
-
)
|
|
350
|
-
cfg.add_argument(
|
|
351
|
-
"--embedding-provider",
|
|
352
|
-
dest="embedding_provider",
|
|
353
|
-
help="LiteLLM custom_llm_provider for embeddings.",
|
|
354
|
-
)
|
|
355
|
-
cfg.add_argument(
|
|
356
|
-
"--clear-embedding",
|
|
357
|
-
dest="clear_embedding",
|
|
358
|
-
action="store_true",
|
|
359
|
-
help="Clear remote embedding config (local MiniLM).",
|
|
360
|
-
)
|
|
361
|
-
|
|
362
|
-
# Setup
|
|
363
|
-
setup = subparsers.add_parser("setup", help="Initial setup (download models).")
|
|
364
|
-
setup.add_argument("--force", action="store_true", help="Force model redownload.")
|
|
365
|
-
|
|
366
|
-
# Rebuild
|
|
367
|
-
subparsers.add_parser("rebuild", help="Full re-index of the project.")
|
|
368
|
-
return parser
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
def main():
|
|
372
|
-
parser = _build_arg_parser()
|
|
373
|
-
try:
|
|
374
|
-
args = parser.parse_args()
|
|
375
|
-
|
|
376
|
-
if args.verbose:
|
|
377
|
-
logging.basicConfig(level=logging.INFO)
|
|
378
|
-
else:
|
|
379
|
-
logging.basicConfig(level=logging.WARNING)
|
|
380
|
-
|
|
381
|
-
if args.command == "sync":
|
|
382
|
-
asyncio.run(sync_cmd(args))
|
|
383
|
-
elif args.command == "search":
|
|
384
|
-
asyncio.run(search_cmd(args))
|
|
385
|
-
elif args.command == "api":
|
|
386
|
-
asyncio.run(api_cmd(args))
|
|
387
|
-
elif args.command == "config":
|
|
388
|
-
config_cmd(args)
|
|
389
|
-
elif args.command == "setup":
|
|
390
|
-
asyncio.run(setup_cmd(args))
|
|
391
|
-
elif args.command == "rebuild":
|
|
392
|
-
asyncio.run(rebuild_cmd(args))
|
|
393
|
-
else:
|
|
394
|
-
parser.print_help()
|
|
395
|
-
except CodeRAGError as e:
|
|
396
|
-
print(f"Error: {e}", file=sys.stderr)
|
|
397
|
-
sys.exit(1)
|
|
398
|
-
except KeyboardInterrupt:
|
|
399
|
-
sys.exit(0)
|
|
400
|
-
except Exception as e:
|
|
401
|
-
logger.error("Unexpected error: %s", e)
|
|
402
|
-
sys.exit(1)
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
if __name__ == "__main__":
|
|
406
|
-
main()
|
|
294
|
+
def dispatch(args) -> None:
|
|
295
|
+
if args.command == "sync":
|
|
296
|
+
asyncio.run(sync_cmd(args))
|
|
297
|
+
elif args.command == "search":
|
|
298
|
+
asyncio.run(search_cmd(args))
|
|
299
|
+
elif args.command == "api":
|
|
300
|
+
asyncio.run(api_cmd(args))
|
|
301
|
+
elif args.command == "config":
|
|
302
|
+
config_cmd(args)
|
|
303
|
+
elif args.command == "setup":
|
|
304
|
+
asyncio.run(setup_cmd(args))
|
|
305
|
+
elif args.command == "rebuild":
|
|
306
|
+
asyncio.run(rebuild_cmd(args))
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import json
|
|
3
|
+
import importlib
|
|
3
4
|
from typing import Optional
|
|
4
|
-
import litellm
|
|
5
5
|
from pydantic import BaseModel, ConfigDict, field_validator
|
|
6
6
|
from ..core.exceptions import IntelligenceError
|
|
7
7
|
from ..core.interfaces import IIntelligence
|
|
@@ -114,6 +114,7 @@ SUMMARY:
|
|
|
114
114
|
if self.config.provider == "ollama" and not model_id.startswith("ollama/"):
|
|
115
115
|
model_id = f"ollama/{model_id}"
|
|
116
116
|
|
|
117
|
+
litellm = importlib.import_module("litellm")
|
|
117
118
|
response = await litellm.acompletion(
|
|
118
119
|
model=model_id,
|
|
119
120
|
messages=[{"role": "user", "content": prompt}],
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import importlib
|
|
2
3
|
import logging
|
|
3
4
|
from typing import Optional
|
|
4
5
|
|
|
5
|
-
import litellm
|
|
6
6
|
import numpy as np
|
|
7
7
|
|
|
8
8
|
from ..core.constants import EMBEDDING_REQUEST_TIMEOUT
|
|
@@ -74,6 +74,8 @@ class OpenAICompatEmbedder(IEmbedder):
|
|
|
74
74
|
}
|
|
75
75
|
if self._provider is not None:
|
|
76
76
|
kwargs["custom_llm_provider"] = self._provider
|
|
77
|
+
litellm = importlib.import_module("litellm")
|
|
78
|
+
|
|
77
79
|
async with self._embed_lock:
|
|
78
80
|
try:
|
|
79
81
|
response = await litellm.aembedding(**kwargs)
|
|
@@ -18,6 +18,10 @@ from ..core.exceptions import StorageError, IntelligenceError
|
|
|
18
18
|
|
|
19
19
|
logger = logging.getLogger(__name__)
|
|
20
20
|
|
|
21
|
+
EMBEDDINGS_MISSING_MSG = (
|
|
22
|
+
"Embeddings table is missing; run sync (CodeRAG.sync) before search."
|
|
23
|
+
)
|
|
24
|
+
|
|
21
25
|
|
|
22
26
|
class AccessMode(str, Enum):
|
|
23
27
|
"""Whether a connection may write. Not part of the public package API."""
|
|
@@ -315,6 +319,16 @@ class DuckDBStorage(IStorage):
|
|
|
315
319
|
self._pending_wipe = False
|
|
316
320
|
|
|
317
321
|
async def _bind_embeddings(self) -> None:
|
|
322
|
+
if self.mode is AccessMode.READ_ONLY:
|
|
323
|
+
exists = await self._run_on_executor(
|
|
324
|
+
lambda: _table_exists(self.conn, "unit_embeddings")
|
|
325
|
+
)
|
|
326
|
+
if not exists:
|
|
327
|
+
raise StorageError(
|
|
328
|
+
EMBEDDINGS_MISSING_MSG,
|
|
329
|
+
code=ErrorCode.EMBEDDINGS_MISSING,
|
|
330
|
+
)
|
|
331
|
+
|
|
318
332
|
dim = await bind_embedder_dimension(
|
|
319
333
|
self.conn,
|
|
320
334
|
self.embedder,
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agent-coderag"
|
|
7
|
-
version = "1.4.
|
|
7
|
+
version = "1.4.1"
|
|
8
8
|
description = "Lightweight semantic code search and distillation utility for AI coding agents. It solves the API knowledge gap via real-time local signature extraction and intent analysis without PyTorch. Optimized for token efficiency, it compresses codebase context into compact semantic summaries stored in a local DuckDB vector similarity index."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -57,7 +57,7 @@ Issues = "https://github.com/naranor/agent-coderag/issues"
|
|
|
57
57
|
Changelog = "https://github.com/naranor/agent-coderag/blob/main/CHANGELOG.md"
|
|
58
58
|
|
|
59
59
|
[project.scripts]
|
|
60
|
-
agent-coderag = "code_rag.entry.
|
|
60
|
+
agent-coderag = "code_rag.entry.args:main"
|
|
61
61
|
|
|
62
62
|
[tool.setuptools]
|
|
63
63
|
package-data = {"*" = ["*.md"]}
|
|
@@ -6,6 +6,7 @@ from io import StringIO
|
|
|
6
6
|
from unittest.mock import MagicMock, patch, AsyncMock
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
|
+
from code_rag.entry import args as cli_args
|
|
9
10
|
from code_rag.entry import cli
|
|
10
11
|
from code_rag.core.exceptions import CodeRAGError
|
|
11
12
|
from code_rag.api.models import ApiReport
|
|
@@ -175,6 +176,43 @@ class TestCLIDetailed:
|
|
|
175
176
|
data = json.loads(sys.stdout.getvalue())
|
|
176
177
|
assert data["status"] == "error"
|
|
177
178
|
assert "Database file not found" in data["message"]
|
|
179
|
+
assert "code" not in data
|
|
180
|
+
finally:
|
|
181
|
+
sys.stdout = old_stdout
|
|
182
|
+
instances[0].close.assert_awaited()
|
|
183
|
+
|
|
184
|
+
@pytest.mark.asyncio
|
|
185
|
+
async def test_search_cmd_json_error_includes_code(self):
|
|
186
|
+
from code_rag.core.error_codes import ErrorCode
|
|
187
|
+
from code_rag.core.exceptions import StorageError
|
|
188
|
+
|
|
189
|
+
fake_cls, instances = fake_coderag_class(
|
|
190
|
+
search=AsyncMock(
|
|
191
|
+
side_effect=StorageError(
|
|
192
|
+
"Embeddings table is missing; run sync (CodeRAG.sync) before search.",
|
|
193
|
+
code=ErrorCode.EMBEDDINGS_MISSING,
|
|
194
|
+
)
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
with patch("code_rag.entry.cli.CodeRAG", fake_cls):
|
|
198
|
+
args = argparse.Namespace(
|
|
199
|
+
db="x.db",
|
|
200
|
+
onnx=None,
|
|
201
|
+
verbose=False,
|
|
202
|
+
json=True,
|
|
203
|
+
query="auth",
|
|
204
|
+
limit=5,
|
|
205
|
+
)
|
|
206
|
+
old_stdout = sys.stdout
|
|
207
|
+
sys.stdout = StringIO()
|
|
208
|
+
try:
|
|
209
|
+
with pytest.raises(SystemExit) as exc:
|
|
210
|
+
await cli.search_cmd(args)
|
|
211
|
+
assert exc.value.code == 1
|
|
212
|
+
data = json.loads(sys.stdout.getvalue())
|
|
213
|
+
assert data["status"] == "error"
|
|
214
|
+
assert data["code"] == "EMBEDDINGS_MISSING"
|
|
215
|
+
assert "sync" in data["message"]
|
|
178
216
|
finally:
|
|
179
217
|
sys.stdout = old_stdout
|
|
180
218
|
instances[0].close.assert_awaited()
|
|
@@ -350,7 +388,7 @@ class TestCLIDetailed:
|
|
|
350
388
|
def test_cli_main_exception_handling(self):
|
|
351
389
|
"""Test main entry point handles exceptions gracefully."""
|
|
352
390
|
with patch(
|
|
353
|
-
"code_rag.entry.
|
|
391
|
+
"code_rag.entry.args.argparse.ArgumentParser.parse_args"
|
|
354
392
|
) as mock_parse, patch("code_rag.entry.cli.asyncio.run"):
|
|
355
393
|
mock_parse.side_effect = CodeRAGError("Known Error")
|
|
356
394
|
|
|
@@ -358,7 +396,7 @@ class TestCLIDetailed:
|
|
|
358
396
|
sys.stderr = StringIO()
|
|
359
397
|
try:
|
|
360
398
|
with pytest.raises(SystemExit) as exc:
|
|
361
|
-
|
|
399
|
+
cli_args.main()
|
|
362
400
|
assert exc.value.code == 1
|
|
363
401
|
assert "Known Error" in sys.stderr.getvalue()
|
|
364
402
|
finally:
|
|
@@ -7,6 +7,7 @@ from unittest.mock import patch
|
|
|
7
7
|
import pytest
|
|
8
8
|
|
|
9
9
|
from code_rag.core.exceptions import IntelligenceError
|
|
10
|
+
from code_rag.entry import args as cli_args
|
|
10
11
|
from code_rag.entry import cli
|
|
11
12
|
from code_rag.intelligence.distiller import DistillerConfig
|
|
12
13
|
from tests.fake_coderag import fake_coderag_class
|
|
@@ -206,6 +207,6 @@ def test_argparse_embedding_dests():
|
|
|
206
207
|
],
|
|
207
208
|
),
|
|
208
209
|
):
|
|
209
|
-
|
|
210
|
+
cli_args.main()
|
|
210
211
|
assert captured["clear"] is True
|
|
211
212
|
assert captured["eurl"] == "http://e"
|