agent-coderag 1.3.5__tar.gz → 1.4.0__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.3.5/agent_coderag.egg-info → agent_coderag-1.4.0}/PKG-INFO +24 -4
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/README.md +23 -3
- {agent_coderag-1.3.5 → agent_coderag-1.4.0/agent_coderag.egg-info}/PKG-INFO +24 -4
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/agent_coderag.egg-info/SOURCES.txt +14 -4
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/__init__.py +6 -1
- agent_coderag-1.4.0/code_rag/api/client.py +236 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/core/constants.py +1 -0
- agent_coderag-1.4.0/code_rag/core/error_codes.py +7 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/core/exceptions.py +15 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/discovery/manager.py +4 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/discovery/providers/java.py +6 -2
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/entry/cli.py +54 -48
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/intelligence/distiller.py +20 -5
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/intelligence/factory.py +2 -1
- agent_coderag-1.4.0/code_rag/paths.py +20 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/services/config.py +36 -4
- agent_coderag-1.4.0/code_rag/services/dependencies.py +167 -0
- agent_coderag-1.4.0/code_rag/services/discovery_api.py +11 -0
- agent_coderag-1.4.0/code_rag/services/factory.py +16 -0
- agent_coderag-1.4.0/code_rag/services/indexing.py +212 -0
- agent_coderag-1.4.0/code_rag/services/search.py +9 -0
- agent_coderag-1.4.0/code_rag/services/sync.py +159 -0
- agent_coderag-1.4.0/code_rag/storage/db_connection.py +115 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/storage/duckdb_impl.py +149 -63
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/pyproject.toml +1 -1
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_api_client.py +49 -32
- agent_coderag-1.4.0/tests/test_api_lazy_db.py +100 -0
- agent_coderag-1.4.0/tests/test_api_rebuild_wipe.py +187 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_cli.py +48 -102
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_cli_detailed.py +90 -70
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_cli_embeddings.py +8 -18
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_cli_json_parity.py +14 -13
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_code_rag_simple.py +16 -10
- agent_coderag-1.4.0/tests/test_coderag_lifetime.py +320 -0
- agent_coderag-1.4.0/tests/test_db_connection.py +302 -0
- agent_coderag-1.4.0/tests/test_db_path.py +36 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_distiller.py +45 -27
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_distiller_config_embedding.py +2 -2
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_distiller_extra.py +10 -6
- agent_coderag-1.4.0/tests/test_error_codes.py +21 -0
- agent_coderag-1.4.0/tests/test_factory_async.py +53 -0
- agent_coderag-1.3.5/tests/test_manager_detailed.py → agent_coderag-1.4.0/tests/test_indexing_detailed.py +46 -46
- agent_coderag-1.3.5/tests/test_manager_embeddings.py → agent_coderag-1.4.0/tests/test_indexing_embeddings.py +51 -23
- agent_coderag-1.3.5/tests/test_manager_extra.py → agent_coderag-1.4.0/tests/test_indexing_extra.py +23 -19
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_readme_library_usage.py +1 -0
- agent_coderag-1.4.0/tests/test_readme_lifetime_storage.py +35 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_storage_detailed.py +7 -2
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_storage_embeddings.py +214 -43
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_sync_worker_errors.py +111 -79
- agent_coderag-1.3.5/code_rag/api/client.py +0 -110
- agent_coderag-1.3.5/code_rag/core/manager.py +0 -351
- agent_coderag-1.3.5/code_rag/services/discovery_api.py +0 -8
- agent_coderag-1.3.5/code_rag/services/factory.py +0 -30
- agent_coderag-1.3.5/code_rag/services/search.py +0 -6
- agent_coderag-1.3.5/code_rag/services/sync.py +0 -115
- agent_coderag-1.3.5/tests/test_api_rebuild_wipe.py +0 -171
- agent_coderag-1.3.5/tests/test_factory_async.py +0 -95
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/LICENSE +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/NOTICE +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/agent_coderag.egg-info/dependency_links.txt +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/agent_coderag.egg-info/entry_points.txt +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/agent_coderag.egg-info/requires.txt +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/agent_coderag.egg-info/top_level.txt +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/api/__init__.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/api/models.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/core/__init__.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/core/interfaces.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/core/models.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/core/utils.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/discovery/__init__.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/discovery/dependency.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/discovery/java_discovery.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/discovery/providers/__init__.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/discovery/providers/base.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/discovery/providers/csharp.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/discovery/providers/go.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/discovery/providers/javascript.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/discovery/providers/python.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/discovery/providers/rust.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/entry/__init__.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/intelligence/__init__.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/intelligence/embedder.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/intelligence/openai_embedder.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/parsers/__init__.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/parsers/languages.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/parsers/multi_parser.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/parsers/tree_sitter.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/services/__init__.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/services/setup.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/code_rag/storage/__init__.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/setup.cfg +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_api_models.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_csharp_discovery_detailed.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_dependency_discovery.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_discovery_manager_detailed.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_discovery_providers_extra.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_discovery_python.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_embedder.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_embedder_detailed.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_embedder_factory.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_embedder_interface.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_embedder_stubs.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_interfaces.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_js_discovery_detailed.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_languages.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_local_onnx_embedder.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_models.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_multi_parser.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_openai_embedder.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_readme_offline_embeddings.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_rust_discovery_detailed.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/tests/test_tree_sitter_parser.py +0 -0
- {agent_coderag-1.3.5 → agent_coderag-1.4.0}/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.
|
|
3
|
+
Version: 1.4.0
|
|
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
|
|
@@ -110,7 +110,7 @@ agent-coderag setup
|
|
|
110
110
|
agent-coderag config --url "http://localhost:11434" --provider "ollama" --model "qwen2.5-coder"
|
|
111
111
|
|
|
112
112
|
# Using OpenAI-compatible API (e.g. Groq, OpenRouter, DeepSeek)
|
|
113
|
-
agent-coderag config --url "https://api.deepseek.com" --key "your-api-key" --model "deepseek-chat"
|
|
113
|
+
agent-coderag config --url "https://api.deepseek.com" --provider "openai" --key "your-api-key" --model "deepseek-chat"
|
|
114
114
|
```
|
|
115
115
|
|
|
116
116
|
### Offline Mode (No Provider)
|
|
@@ -163,17 +163,37 @@ agent-coderag api fmt
|
|
|
163
163
|
|
|
164
164
|
## Library Usage
|
|
165
165
|
|
|
166
|
+
> **1.4.0 migration:** Storage lifetime and default DB resolution changed. Pin `agent-coderag<1.4` until you adapt (see [Database & lifetime](#database--lifetime)).
|
|
167
|
+
|
|
166
168
|
```python
|
|
167
|
-
from
|
|
169
|
+
from pathlib import Path
|
|
170
|
+
|
|
171
|
+
from code_rag import CodeRAG, default_db_path
|
|
168
172
|
|
|
169
173
|
async def main():
|
|
170
|
-
|
|
174
|
+
root = Path(".")
|
|
175
|
+
print(default_db_path(root)) # resolved path before first sync
|
|
176
|
+
|
|
177
|
+
# db=None (default): legacy code_rag.db in cwd/root, else root/.coderag.db
|
|
178
|
+
async with CodeRAG(root=root) as rag:
|
|
171
179
|
await rag.setup()
|
|
172
180
|
await rag.sync(index_all=True)
|
|
173
181
|
hits = await rag.search("authentication middleware", limit=5)
|
|
182
|
+
# Opens the DB only when a provider needs it (e.g. Java JAR cache).
|
|
174
183
|
report = await rag.api("pydantic", lang="python")
|
|
175
184
|
```
|
|
176
185
|
|
|
186
|
+
### Database & lifetime
|
|
187
|
+
|
|
188
|
+
- **Default path (`db=None`):** resolution order is (1) `./code_rag.db` if it exists, (2) else `{root}/code_rag.db` if it exists, (3) else `{root}/.coderag.db` (created on first `sync`/`rebuild`). Use `default_db_path(root)` to preview. New projects: prefer `.coderag.db` (step 3) or set `db=` explicitly.
|
|
189
|
+
- **Explicit path:** `CodeRAG(db=...)` / `agent-coderag --db ...`. Path is a **file**, not a directory. Relative paths resolve against **process cwd**, not `root`.
|
|
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
|
+
- **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":...}` (same shape as `sync`/`api` failures).
|
|
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
|
+
- **`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`).
|
|
196
|
+
|
|
177
197
|
---
|
|
178
198
|
|
|
179
199
|
## Supported Ecosystems (Discovery)
|
|
@@ -64,7 +64,7 @@ agent-coderag setup
|
|
|
64
64
|
agent-coderag config --url "http://localhost:11434" --provider "ollama" --model "qwen2.5-coder"
|
|
65
65
|
|
|
66
66
|
# Using OpenAI-compatible API (e.g. Groq, OpenRouter, DeepSeek)
|
|
67
|
-
agent-coderag config --url "https://api.deepseek.com" --key "your-api-key" --model "deepseek-chat"
|
|
67
|
+
agent-coderag config --url "https://api.deepseek.com" --provider "openai" --key "your-api-key" --model "deepseek-chat"
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
### Offline Mode (No Provider)
|
|
@@ -117,17 +117,37 @@ agent-coderag api fmt
|
|
|
117
117
|
|
|
118
118
|
## Library Usage
|
|
119
119
|
|
|
120
|
+
> **1.4.0 migration:** Storage lifetime and default DB resolution changed. Pin `agent-coderag<1.4` until you adapt (see [Database & lifetime](#database--lifetime)).
|
|
121
|
+
|
|
120
122
|
```python
|
|
121
|
-
from
|
|
123
|
+
from pathlib import Path
|
|
124
|
+
|
|
125
|
+
from code_rag import CodeRAG, default_db_path
|
|
122
126
|
|
|
123
127
|
async def main():
|
|
124
|
-
|
|
128
|
+
root = Path(".")
|
|
129
|
+
print(default_db_path(root)) # resolved path before first sync
|
|
130
|
+
|
|
131
|
+
# db=None (default): legacy code_rag.db in cwd/root, else root/.coderag.db
|
|
132
|
+
async with CodeRAG(root=root) as rag:
|
|
125
133
|
await rag.setup()
|
|
126
134
|
await rag.sync(index_all=True)
|
|
127
135
|
hits = await rag.search("authentication middleware", limit=5)
|
|
136
|
+
# Opens the DB only when a provider needs it (e.g. Java JAR cache).
|
|
128
137
|
report = await rag.api("pydantic", lang="python")
|
|
129
138
|
```
|
|
130
139
|
|
|
140
|
+
### Database & lifetime
|
|
141
|
+
|
|
142
|
+
- **Default path (`db=None`):** resolution order is (1) `./code_rag.db` if it exists, (2) else `{root}/code_rag.db` if it exists, (3) else `{root}/.coderag.db` (created on first `sync`/`rebuild`). Use `default_db_path(root)` to preview. New projects: prefer `.coderag.db` (step 3) or set `db=` explicitly.
|
|
143
|
+
- **Explicit path:** `CodeRAG(db=...)` / `agent-coderag --db ...`. Path is a **file**, not a directory. Relative paths resolve against **process cwd**, not `root`.
|
|
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
|
+
- **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":...}` (same shape as `sync`/`api` failures).
|
|
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
|
+
- **`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`).
|
|
150
|
+
|
|
131
151
|
---
|
|
132
152
|
|
|
133
153
|
## Supported Ecosystems (Discovery)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-coderag
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
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
|
|
@@ -110,7 +110,7 @@ agent-coderag setup
|
|
|
110
110
|
agent-coderag config --url "http://localhost:11434" --provider "ollama" --model "qwen2.5-coder"
|
|
111
111
|
|
|
112
112
|
# Using OpenAI-compatible API (e.g. Groq, OpenRouter, DeepSeek)
|
|
113
|
-
agent-coderag config --url "https://api.deepseek.com" --key "your-api-key" --model "deepseek-chat"
|
|
113
|
+
agent-coderag config --url "https://api.deepseek.com" --provider "openai" --key "your-api-key" --model "deepseek-chat"
|
|
114
114
|
```
|
|
115
115
|
|
|
116
116
|
### Offline Mode (No Provider)
|
|
@@ -163,17 +163,37 @@ agent-coderag api fmt
|
|
|
163
163
|
|
|
164
164
|
## Library Usage
|
|
165
165
|
|
|
166
|
+
> **1.4.0 migration:** Storage lifetime and default DB resolution changed. Pin `agent-coderag<1.4` until you adapt (see [Database & lifetime](#database--lifetime)).
|
|
167
|
+
|
|
166
168
|
```python
|
|
167
|
-
from
|
|
169
|
+
from pathlib import Path
|
|
170
|
+
|
|
171
|
+
from code_rag import CodeRAG, default_db_path
|
|
168
172
|
|
|
169
173
|
async def main():
|
|
170
|
-
|
|
174
|
+
root = Path(".")
|
|
175
|
+
print(default_db_path(root)) # resolved path before first sync
|
|
176
|
+
|
|
177
|
+
# db=None (default): legacy code_rag.db in cwd/root, else root/.coderag.db
|
|
178
|
+
async with CodeRAG(root=root) as rag:
|
|
171
179
|
await rag.setup()
|
|
172
180
|
await rag.sync(index_all=True)
|
|
173
181
|
hits = await rag.search("authentication middleware", limit=5)
|
|
182
|
+
# Opens the DB only when a provider needs it (e.g. Java JAR cache).
|
|
174
183
|
report = await rag.api("pydantic", lang="python")
|
|
175
184
|
```
|
|
176
185
|
|
|
186
|
+
### Database & lifetime
|
|
187
|
+
|
|
188
|
+
- **Default path (`db=None`):** resolution order is (1) `./code_rag.db` if it exists, (2) else `{root}/code_rag.db` if it exists, (3) else `{root}/.coderag.db` (created on first `sync`/`rebuild`). Use `default_db_path(root)` to preview. New projects: prefer `.coderag.db` (step 3) or set `db=` explicitly.
|
|
189
|
+
- **Explicit path:** `CodeRAG(db=...)` / `agent-coderag --db ...`. Path is a **file**, not a directory. Relative paths resolve against **process cwd**, not `root`.
|
|
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
|
+
- **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":...}` (same shape as `sync`/`api` failures).
|
|
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
|
+
- **`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`).
|
|
196
|
+
|
|
177
197
|
---
|
|
178
198
|
|
|
179
199
|
## Supported Ecosystems (Discovery)
|
|
@@ -9,14 +9,15 @@ agent_coderag.egg-info/entry_points.txt
|
|
|
9
9
|
agent_coderag.egg-info/requires.txt
|
|
10
10
|
agent_coderag.egg-info/top_level.txt
|
|
11
11
|
code_rag/__init__.py
|
|
12
|
+
code_rag/paths.py
|
|
12
13
|
code_rag/api/__init__.py
|
|
13
14
|
code_rag/api/client.py
|
|
14
15
|
code_rag/api/models.py
|
|
15
16
|
code_rag/core/__init__.py
|
|
16
17
|
code_rag/core/constants.py
|
|
18
|
+
code_rag/core/error_codes.py
|
|
17
19
|
code_rag/core/exceptions.py
|
|
18
20
|
code_rag/core/interfaces.py
|
|
19
|
-
code_rag/core/manager.py
|
|
20
21
|
code_rag/core/models.py
|
|
21
22
|
code_rag/core/utils.py
|
|
22
23
|
code_rag/discovery/__init__.py
|
|
@@ -44,14 +45,18 @@ code_rag/parsers/multi_parser.py
|
|
|
44
45
|
code_rag/parsers/tree_sitter.py
|
|
45
46
|
code_rag/services/__init__.py
|
|
46
47
|
code_rag/services/config.py
|
|
48
|
+
code_rag/services/dependencies.py
|
|
47
49
|
code_rag/services/discovery_api.py
|
|
48
50
|
code_rag/services/factory.py
|
|
51
|
+
code_rag/services/indexing.py
|
|
49
52
|
code_rag/services/search.py
|
|
50
53
|
code_rag/services/setup.py
|
|
51
54
|
code_rag/services/sync.py
|
|
52
55
|
code_rag/storage/__init__.py
|
|
56
|
+
code_rag/storage/db_connection.py
|
|
53
57
|
code_rag/storage/duckdb_impl.py
|
|
54
58
|
tests/test_api_client.py
|
|
59
|
+
tests/test_api_lazy_db.py
|
|
55
60
|
tests/test_api_models.py
|
|
56
61
|
tests/test_api_rebuild_wipe.py
|
|
57
62
|
tests/test_cli.py
|
|
@@ -59,7 +64,10 @@ tests/test_cli_detailed.py
|
|
|
59
64
|
tests/test_cli_embeddings.py
|
|
60
65
|
tests/test_cli_json_parity.py
|
|
61
66
|
tests/test_code_rag_simple.py
|
|
67
|
+
tests/test_coderag_lifetime.py
|
|
62
68
|
tests/test_csharp_discovery_detailed.py
|
|
69
|
+
tests/test_db_connection.py
|
|
70
|
+
tests/test_db_path.py
|
|
63
71
|
tests/test_dependency_discovery.py
|
|
64
72
|
tests/test_discovery_manager_detailed.py
|
|
65
73
|
tests/test_discovery_providers_extra.py
|
|
@@ -72,18 +80,20 @@ tests/test_embedder_detailed.py
|
|
|
72
80
|
tests/test_embedder_factory.py
|
|
73
81
|
tests/test_embedder_interface.py
|
|
74
82
|
tests/test_embedder_stubs.py
|
|
83
|
+
tests/test_error_codes.py
|
|
75
84
|
tests/test_factory_async.py
|
|
85
|
+
tests/test_indexing_detailed.py
|
|
86
|
+
tests/test_indexing_embeddings.py
|
|
87
|
+
tests/test_indexing_extra.py
|
|
76
88
|
tests/test_interfaces.py
|
|
77
89
|
tests/test_js_discovery_detailed.py
|
|
78
90
|
tests/test_languages.py
|
|
79
91
|
tests/test_local_onnx_embedder.py
|
|
80
|
-
tests/test_manager_detailed.py
|
|
81
|
-
tests/test_manager_embeddings.py
|
|
82
|
-
tests/test_manager_extra.py
|
|
83
92
|
tests/test_models.py
|
|
84
93
|
tests/test_multi_parser.py
|
|
85
94
|
tests/test_openai_embedder.py
|
|
86
95
|
tests/test_readme_library_usage.py
|
|
96
|
+
tests/test_readme_lifetime_storage.py
|
|
87
97
|
tests/test_readme_offline_embeddings.py
|
|
88
98
|
tests/test_rust_discovery_detailed.py
|
|
89
99
|
tests/test_storage_detailed.py
|
|
@@ -2,7 +2,9 @@ from code_rag.api.client import CodeRAG
|
|
|
2
2
|
from code_rag.api.models import SyncResult, ApiReport, SetupResult
|
|
3
3
|
from code_rag.core.models import KnowledgeUnit, UnitKind
|
|
4
4
|
from code_rag.intelligence.distiller import DistillerConfig
|
|
5
|
-
from code_rag.core.
|
|
5
|
+
from code_rag.core.error_codes import ErrorCode
|
|
6
|
+
from code_rag.core.exceptions import CodeRAGError, StorageBusyError
|
|
7
|
+
from code_rag.paths import default_db_path
|
|
6
8
|
|
|
7
9
|
__all__ = [
|
|
8
10
|
"CodeRAG",
|
|
@@ -13,4 +15,7 @@ __all__ = [
|
|
|
13
15
|
"UnitKind",
|
|
14
16
|
"DistillerConfig",
|
|
15
17
|
"CodeRAGError",
|
|
18
|
+
"ErrorCode",
|
|
19
|
+
"StorageBusyError",
|
|
20
|
+
"default_db_path",
|
|
16
21
|
]
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
from contextlib import asynccontextmanager
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import AsyncIterator, Optional, Union
|
|
6
|
+
|
|
7
|
+
from code_rag.api.models import ApiReport, SetupResult, SyncResult
|
|
8
|
+
from code_rag.core.constants import DEFAULT_CONNECT_TIMEOUT_SECONDS
|
|
9
|
+
from code_rag.core.interfaces import IEmbedder, IIntelligence, IParser
|
|
10
|
+
from code_rag.core.models import KnowledgeUnit
|
|
11
|
+
from code_rag.discovery.manager import DiscoveryManager
|
|
12
|
+
from code_rag.discovery.providers.java import JavaDiscoveryProvider
|
|
13
|
+
from code_rag.intelligence.distiller import Distiller, DistillerConfig
|
|
14
|
+
from code_rag.paths import resolve_db_path
|
|
15
|
+
from code_rag.services.config import (
|
|
16
|
+
distill_fields_requested,
|
|
17
|
+
embedding_fields_requested,
|
|
18
|
+
load_or_update_config,
|
|
19
|
+
)
|
|
20
|
+
from code_rag.services.discovery_api import run_api
|
|
21
|
+
from code_rag.services.factory import create_stack
|
|
22
|
+
from code_rag.services.search import run_search
|
|
23
|
+
from code_rag.services.setup import run_setup
|
|
24
|
+
from code_rag.services.indexing import IndexStack
|
|
25
|
+
from code_rag.services.sync import SyncOptions, run_rebuild, run_sync
|
|
26
|
+
from code_rag.storage.db_connection import AccessMode, open_db_connection
|
|
27
|
+
from code_rag.storage.duckdb_impl import DuckDBStorage
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class CodeRAG: # pylint: disable=too-many-instance-attributes
|
|
33
|
+
"""Public async facade: process-scoped stack + ephemeral DB per operation."""
|
|
34
|
+
|
|
35
|
+
def __init__( # pylint: disable=too-many-arguments
|
|
36
|
+
self,
|
|
37
|
+
db: Optional[str] = None,
|
|
38
|
+
onnx: Optional[str] = None,
|
|
39
|
+
*,
|
|
40
|
+
root: Optional[Union[str, Path]] = None,
|
|
41
|
+
allow_build_execution: bool = False,
|
|
42
|
+
connect_timeout_seconds: float = DEFAULT_CONNECT_TIMEOUT_SECONDS,
|
|
43
|
+
):
|
|
44
|
+
self._onnx = onnx
|
|
45
|
+
self._root = Path(root) if root is not None else Path.cwd()
|
|
46
|
+
self._allow_build_execution = allow_build_execution
|
|
47
|
+
self._connect_timeout_seconds = connect_timeout_seconds
|
|
48
|
+
self._db_path = resolve_db_path(db, root=self._root)
|
|
49
|
+
self._embedder: Optional[IEmbedder] = None
|
|
50
|
+
self._parser: Optional[IParser] = None
|
|
51
|
+
self._distiller: Optional[IIntelligence] = None
|
|
52
|
+
self._storage: Optional[DuckDBStorage] = None
|
|
53
|
+
self._op_lock = asyncio.Lock()
|
|
54
|
+
logger.info("Resolved database path: %s", self._db_path.resolve())
|
|
55
|
+
|
|
56
|
+
async def __aenter__(self) -> "CodeRAG":
|
|
57
|
+
return self
|
|
58
|
+
|
|
59
|
+
async def __aexit__(self, *_exc) -> None:
|
|
60
|
+
await self.close()
|
|
61
|
+
|
|
62
|
+
async def _ensure_stack(self) -> None:
|
|
63
|
+
if self._embedder is not None:
|
|
64
|
+
return
|
|
65
|
+
self._embedder, self._parser, self._distiller = await create_stack(self._onnx)
|
|
66
|
+
|
|
67
|
+
@asynccontextmanager
|
|
68
|
+
async def _with_storage(
|
|
69
|
+
self, mode: AccessMode, *, wipe: bool = False
|
|
70
|
+
) -> AsyncIterator[DuckDBStorage]:
|
|
71
|
+
async with self._op_lock:
|
|
72
|
+
await self._ensure_stack()
|
|
73
|
+
if self._embedder is None:
|
|
74
|
+
raise RuntimeError("process stack is not initialized")
|
|
75
|
+
storage = await open_db_connection(
|
|
76
|
+
self._db_path,
|
|
77
|
+
self._embedder,
|
|
78
|
+
mode=mode,
|
|
79
|
+
connect_timeout_seconds=self._connect_timeout_seconds,
|
|
80
|
+
wipe=wipe,
|
|
81
|
+
)
|
|
82
|
+
self._storage = storage
|
|
83
|
+
try:
|
|
84
|
+
yield storage
|
|
85
|
+
finally:
|
|
86
|
+
await storage.close()
|
|
87
|
+
self._storage = None
|
|
88
|
+
|
|
89
|
+
@asynccontextmanager
|
|
90
|
+
async def _metadata_ro_connection(self) -> AsyncIterator[DuckDBStorage]:
|
|
91
|
+
storage = await open_db_connection(
|
|
92
|
+
self._db_path,
|
|
93
|
+
None,
|
|
94
|
+
mode=AccessMode.READ_ONLY,
|
|
95
|
+
connect_timeout_seconds=self._connect_timeout_seconds,
|
|
96
|
+
)
|
|
97
|
+
self._storage = storage
|
|
98
|
+
try:
|
|
99
|
+
yield storage
|
|
100
|
+
finally:
|
|
101
|
+
await storage.close()
|
|
102
|
+
self._storage = None
|
|
103
|
+
|
|
104
|
+
async def close(self) -> None:
|
|
105
|
+
async with self._op_lock:
|
|
106
|
+
if self._storage is not None:
|
|
107
|
+
await self._storage.close()
|
|
108
|
+
self._storage = None
|
|
109
|
+
if self._embedder is not None:
|
|
110
|
+
await self._embedder.close()
|
|
111
|
+
self._embedder = None
|
|
112
|
+
self._parser = None
|
|
113
|
+
self._distiller = None
|
|
114
|
+
|
|
115
|
+
async def config( # pylint: disable=too-many-arguments
|
|
116
|
+
self,
|
|
117
|
+
*,
|
|
118
|
+
url: Optional[str] = None,
|
|
119
|
+
key: Optional[str] = None,
|
|
120
|
+
model: Optional[str] = None,
|
|
121
|
+
provider: Optional[str] = None,
|
|
122
|
+
embedding_url: Optional[str] = None,
|
|
123
|
+
embedding_key: Optional[str] = None,
|
|
124
|
+
embedding_model: Optional[str] = None,
|
|
125
|
+
embedding_provider: Optional[str] = None,
|
|
126
|
+
clear_embedding: bool = False,
|
|
127
|
+
) -> DistillerConfig:
|
|
128
|
+
touch_embedding = embedding_fields_requested(
|
|
129
|
+
embedding_url=embedding_url,
|
|
130
|
+
embedding_key=embedding_key,
|
|
131
|
+
embedding_model=embedding_model,
|
|
132
|
+
embedding_provider=embedding_provider,
|
|
133
|
+
clear_embedding=clear_embedding,
|
|
134
|
+
)
|
|
135
|
+
touch_distill = distill_fields_requested(
|
|
136
|
+
url=url, key=key, model=model, provider=provider
|
|
137
|
+
)
|
|
138
|
+
result = load_or_update_config(
|
|
139
|
+
url=url,
|
|
140
|
+
key=key,
|
|
141
|
+
model=model,
|
|
142
|
+
provider=provider,
|
|
143
|
+
embedding_url=embedding_url,
|
|
144
|
+
embedding_key=embedding_key,
|
|
145
|
+
embedding_model=embedding_model,
|
|
146
|
+
embedding_provider=embedding_provider,
|
|
147
|
+
clear_embedding=clear_embedding,
|
|
148
|
+
)
|
|
149
|
+
if touch_embedding or touch_distill:
|
|
150
|
+
await self._invalidate_stack_after_config(
|
|
151
|
+
embedding=touch_embedding, distill=touch_distill
|
|
152
|
+
)
|
|
153
|
+
return result
|
|
154
|
+
|
|
155
|
+
async def _invalidate_stack_after_config(
|
|
156
|
+
self, *, embedding: bool, distill: bool
|
|
157
|
+
) -> None:
|
|
158
|
+
"""Refresh process stack pieces affected by config().
|
|
159
|
+
|
|
160
|
+
Embedding changes close and drop the embedder (full stack rebuild on
|
|
161
|
+
next op). Distill-only changes replace Distiller and keep the embedder.
|
|
162
|
+
"""
|
|
163
|
+
async with self._op_lock:
|
|
164
|
+
if embedding and self._embedder is not None:
|
|
165
|
+
await self._embedder.close()
|
|
166
|
+
self._embedder = None
|
|
167
|
+
self._parser = None
|
|
168
|
+
self._distiller = None
|
|
169
|
+
return
|
|
170
|
+
if distill and self._distiller is not None:
|
|
171
|
+
self._distiller = Distiller(DistillerConfig.load())
|
|
172
|
+
|
|
173
|
+
async def setup(self, *, force: bool = False) -> SetupResult:
|
|
174
|
+
return await run_setup(force=force)
|
|
175
|
+
|
|
176
|
+
async def sync(
|
|
177
|
+
self,
|
|
178
|
+
path: Optional[str] = None,
|
|
179
|
+
*,
|
|
180
|
+
index_all: bool = False,
|
|
181
|
+
force: bool = False,
|
|
182
|
+
) -> SyncResult:
|
|
183
|
+
if path is None and not index_all:
|
|
184
|
+
return SyncResult(status="success", indexed_files=0)
|
|
185
|
+
async with self._with_storage(AccessMode.READ_WRITE) as storage:
|
|
186
|
+
if self._parser is None or self._distiller is None:
|
|
187
|
+
raise RuntimeError("process stack is not initialized")
|
|
188
|
+
stack = IndexStack(storage, self._parser, self._distiller)
|
|
189
|
+
return await run_sync(
|
|
190
|
+
stack,
|
|
191
|
+
SyncOptions(
|
|
192
|
+
root=self._root,
|
|
193
|
+
path=path,
|
|
194
|
+
index_all=index_all,
|
|
195
|
+
force=force,
|
|
196
|
+
allow_build_execution=self._allow_build_execution,
|
|
197
|
+
),
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
async def search(self, query: str, *, limit: int = 5) -> list[KnowledgeUnit]:
|
|
201
|
+
async with self._with_storage(AccessMode.READ_ONLY) as storage:
|
|
202
|
+
return await run_search(storage, query, limit=limit)
|
|
203
|
+
|
|
204
|
+
async def _api_java(
|
|
205
|
+
self,
|
|
206
|
+
discovery: DiscoveryManager,
|
|
207
|
+
provider: JavaDiscoveryProvider,
|
|
208
|
+
library: str,
|
|
209
|
+
language: str,
|
|
210
|
+
) -> ApiReport:
|
|
211
|
+
async with self._op_lock:
|
|
212
|
+
async with self._metadata_ro_connection() as storage:
|
|
213
|
+
provider.storage = storage
|
|
214
|
+
try:
|
|
215
|
+
return await run_api(discovery, library, lang=language)
|
|
216
|
+
finally:
|
|
217
|
+
provider.storage = None
|
|
218
|
+
|
|
219
|
+
async def api(self, library: str, *, lang: Optional[str] = None) -> ApiReport:
|
|
220
|
+
language = lang or "python"
|
|
221
|
+
discovery = DiscoveryManager()
|
|
222
|
+
provider = discovery.get_provider(language)
|
|
223
|
+
if isinstance(provider, JavaDiscoveryProvider):
|
|
224
|
+
return await self._api_java(discovery, provider, library, language)
|
|
225
|
+
return await run_api(discovery, library, lang=language)
|
|
226
|
+
|
|
227
|
+
async def rebuild(self) -> SyncResult:
|
|
228
|
+
async with self._with_storage(AccessMode.READ_WRITE, wipe=True) as storage:
|
|
229
|
+
if self._parser is None or self._distiller is None:
|
|
230
|
+
raise RuntimeError("process stack is not initialized")
|
|
231
|
+
stack = IndexStack(storage, self._parser, self._distiller)
|
|
232
|
+
return await run_rebuild(
|
|
233
|
+
stack,
|
|
234
|
+
root=self._root,
|
|
235
|
+
allow_build_execution=self._allow_build_execution,
|
|
236
|
+
)
|
|
@@ -2,15 +2,30 @@
|
|
|
2
2
|
Custom exception hierarchy for CodeRAG.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from .error_codes import ErrorCode
|
|
8
|
+
|
|
5
9
|
|
|
6
10
|
class CodeRAGError(Exception):
|
|
7
11
|
"""Base class for all CodeRAG exceptions."""
|
|
8
12
|
|
|
13
|
+
def __init__(self, message: str = "", *, code: Optional[ErrorCode] = None):
|
|
14
|
+
super().__init__(message)
|
|
15
|
+
self.code = code
|
|
16
|
+
|
|
9
17
|
|
|
10
18
|
class StorageError(CodeRAGError):
|
|
11
19
|
"""Raised when a storage operation fails."""
|
|
12
20
|
|
|
13
21
|
|
|
22
|
+
class StorageBusyError(StorageError):
|
|
23
|
+
"""Raised when storage is locked or busy."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, message: str = "Storage is busy"):
|
|
26
|
+
super().__init__(message, code=ErrorCode.STORAGE_BUSY)
|
|
27
|
+
|
|
28
|
+
|
|
14
29
|
class ParserError(CodeRAGError):
|
|
15
30
|
"""Raised when code parsing fails."""
|
|
16
31
|
|
|
@@ -36,6 +36,10 @@ class DiscoveryManager:
|
|
|
36
36
|
self._providers[language.lower()] = provider
|
|
37
37
|
logger.debug("Registered discovery provider for %s", language)
|
|
38
38
|
|
|
39
|
+
def get_provider(self, language: str) -> Optional[IDiscoveryProvider]:
|
|
40
|
+
"""Returns the provider for a language, if registered."""
|
|
41
|
+
return self._providers.get(language.lower())
|
|
42
|
+
|
|
39
43
|
async def extract_api(self, library_name: str, language: str) -> str:
|
|
40
44
|
"""
|
|
41
45
|
Extracts API for a library using the specified language provider.
|
|
@@ -10,6 +10,10 @@ from ...parsers.tree_sitter import TreeSitterParser
|
|
|
10
10
|
logger = logging.getLogger(__name__)
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
def _cached_jar_miss_message(library_name: str) -> str:
|
|
14
|
+
return f"Error: Could not find cached JAR for '{library_name}'. Run 'sync' first."
|
|
15
|
+
|
|
16
|
+
|
|
13
17
|
class JavaDiscoveryProvider(IDiscoveryProvider):
|
|
14
18
|
"""API discovery for Java libraries using bytecode/source analysis."""
|
|
15
19
|
|
|
@@ -26,11 +30,11 @@ class JavaDiscoveryProvider(IDiscoveryProvider):
|
|
|
26
30
|
Extracts Java API from a cached JAR file.
|
|
27
31
|
"""
|
|
28
32
|
if not self.storage:
|
|
29
|
-
return
|
|
33
|
+
return _cached_jar_miss_message(library_name)
|
|
30
34
|
|
|
31
35
|
jar_path = await self.storage.get_dependency_path(library_name)
|
|
32
36
|
if not jar_path or not os.path.exists(jar_path):
|
|
33
|
-
return
|
|
37
|
+
return _cached_jar_miss_message(library_name)
|
|
34
38
|
|
|
35
39
|
output = [f"# Public API for Java Library '{library_name}':"]
|
|
36
40
|
try:
|