agent-coderag 1.3.2__tar.gz → 1.3.3__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.
Files changed (80) hide show
  1. {agent_coderag-1.3.2/agent_coderag.egg-info → agent_coderag-1.3.3}/PKG-INFO +17 -1
  2. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/README.md +15 -0
  3. {agent_coderag-1.3.2 → agent_coderag-1.3.3/agent_coderag.egg-info}/PKG-INFO +17 -1
  4. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/agent_coderag.egg-info/SOURCES.txt +14 -0
  5. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/agent_coderag.egg-info/requires.txt +1 -0
  6. agent_coderag-1.3.3/code_rag/__init__.py +16 -0
  7. agent_coderag-1.3.3/code_rag/api/__init__.py +6 -0
  8. agent_coderag-1.3.3/code_rag/api/client.py +87 -0
  9. agent_coderag-1.3.3/code_rag/api/models.py +20 -0
  10. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/code_rag/core/manager.py +1 -1
  11. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/code_rag/entry/cli.py +69 -103
  12. agent_coderag-1.3.3/code_rag/services/config.py +25 -0
  13. agent_coderag-1.3.3/code_rag/services/discovery_api.py +8 -0
  14. agent_coderag-1.3.3/code_rag/services/factory.py +28 -0
  15. agent_coderag-1.3.3/code_rag/services/search.py +6 -0
  16. agent_coderag-1.3.3/code_rag/services/setup.py +72 -0
  17. agent_coderag-1.3.3/code_rag/services/sync.py +101 -0
  18. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/pyproject.toml +2 -1
  19. agent_coderag-1.3.3/tests/test_api_client.py +186 -0
  20. agent_coderag-1.3.3/tests/test_api_models.py +25 -0
  21. agent_coderag-1.3.3/tests/test_cli_json_parity.py +100 -0
  22. agent_coderag-1.3.3/tests/test_readme_library_usage.py +8 -0
  23. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/LICENSE +0 -0
  24. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/NOTICE +0 -0
  25. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/agent_coderag.egg-info/dependency_links.txt +0 -0
  26. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/agent_coderag.egg-info/entry_points.txt +0 -0
  27. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/agent_coderag.egg-info/top_level.txt +0 -0
  28. {agent_coderag-1.3.2/code_rag → agent_coderag-1.3.3/code_rag/core}/__init__.py +0 -0
  29. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/code_rag/core/constants.py +0 -0
  30. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/code_rag/core/exceptions.py +0 -0
  31. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/code_rag/core/interfaces.py +0 -0
  32. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/code_rag/core/models.py +0 -0
  33. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/code_rag/core/utils.py +0 -0
  34. {agent_coderag-1.3.2/code_rag/core → agent_coderag-1.3.3/code_rag/discovery}/__init__.py +0 -0
  35. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/code_rag/discovery/dependency.py +0 -0
  36. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/code_rag/discovery/java_discovery.py +0 -0
  37. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/code_rag/discovery/manager.py +0 -0
  38. {agent_coderag-1.3.2/code_rag/discovery → agent_coderag-1.3.3/code_rag/discovery/providers}/__init__.py +0 -0
  39. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/code_rag/discovery/providers/base.py +0 -0
  40. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/code_rag/discovery/providers/csharp.py +0 -0
  41. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/code_rag/discovery/providers/go.py +0 -0
  42. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/code_rag/discovery/providers/java.py +0 -0
  43. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/code_rag/discovery/providers/javascript.py +0 -0
  44. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/code_rag/discovery/providers/python.py +0 -0
  45. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/code_rag/discovery/providers/rust.py +0 -0
  46. {agent_coderag-1.3.2/code_rag/discovery/providers → agent_coderag-1.3.3/code_rag/entry}/__init__.py +0 -0
  47. {agent_coderag-1.3.2/code_rag/entry → agent_coderag-1.3.3/code_rag/intelligence}/__init__.py +0 -0
  48. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/code_rag/intelligence/distiller.py +0 -0
  49. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/code_rag/intelligence/embedder.py +0 -0
  50. {agent_coderag-1.3.2/code_rag/intelligence → agent_coderag-1.3.3/code_rag/parsers}/__init__.py +0 -0
  51. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/code_rag/parsers/languages.py +0 -0
  52. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/code_rag/parsers/multi_parser.py +0 -0
  53. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/code_rag/parsers/tree_sitter.py +0 -0
  54. {agent_coderag-1.3.2/code_rag/parsers → agent_coderag-1.3.3/code_rag/services}/__init__.py +0 -0
  55. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/code_rag/storage/__init__.py +0 -0
  56. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/code_rag/storage/duckdb_impl.py +0 -0
  57. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/setup.cfg +0 -0
  58. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/tests/test_cli.py +0 -0
  59. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/tests/test_cli_detailed.py +0 -0
  60. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/tests/test_code_rag_simple.py +0 -0
  61. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/tests/test_csharp_discovery_detailed.py +0 -0
  62. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/tests/test_dependency_discovery.py +0 -0
  63. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/tests/test_discovery_manager_detailed.py +0 -0
  64. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/tests/test_discovery_providers_extra.py +0 -0
  65. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/tests/test_discovery_python.py +0 -0
  66. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/tests/test_distiller.py +0 -0
  67. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/tests/test_distiller_extra.py +0 -0
  68. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/tests/test_embedder.py +0 -0
  69. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/tests/test_embedder_detailed.py +0 -0
  70. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/tests/test_interfaces.py +0 -0
  71. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/tests/test_js_discovery_detailed.py +0 -0
  72. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/tests/test_languages.py +0 -0
  73. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/tests/test_manager_detailed.py +0 -0
  74. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/tests/test_manager_extra.py +0 -0
  75. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/tests/test_models.py +0 -0
  76. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/tests/test_multi_parser.py +0 -0
  77. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/tests/test_rust_discovery_detailed.py +0 -0
  78. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/tests/test_storage_detailed.py +0 -0
  79. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/tests/test_tree_sitter_parser.py +0 -0
  80. {agent_coderag-1.3.2 → agent_coderag-1.3.3}/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.2
3
+ Version: 1.3.3
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
@@ -29,6 +29,7 @@ Requires-Dist: onnxruntime
29
29
  Requires-Dist: tokenizers
30
30
  Requires-Dist: pydantic
31
31
  Requires-Dist: httpx
32
+ Requires-Dist: requests
32
33
  Requires-Dist: aiofiles
33
34
  Requires-Dist: pathspec
34
35
  Requires-Dist: tree-sitter
@@ -148,6 +149,21 @@ agent-coderag api fmt
148
149
 
149
150
  ---
150
151
 
152
+ ## Library Usage
153
+
154
+ ```python
155
+ from code_rag import CodeRAG
156
+
157
+ async def main():
158
+ async with CodeRAG(db="code_rag.db") as rag:
159
+ await rag.setup()
160
+ await rag.sync(index_all=True)
161
+ hits = await rag.search("authentication middleware", limit=5)
162
+ report = await rag.api("pydantic", lang="python")
163
+ ```
164
+
165
+ ---
166
+
151
167
  ## Supported Ecosystems (Discovery)
152
168
 
153
169
  | Language | Method | Discovery Source |
@@ -103,6 +103,21 @@ agent-coderag api fmt
103
103
 
104
104
  ---
105
105
 
106
+ ## Library Usage
107
+
108
+ ```python
109
+ from code_rag import CodeRAG
110
+
111
+ async def main():
112
+ async with CodeRAG(db="code_rag.db") as rag:
113
+ await rag.setup()
114
+ await rag.sync(index_all=True)
115
+ hits = await rag.search("authentication middleware", limit=5)
116
+ report = await rag.api("pydantic", lang="python")
117
+ ```
118
+
119
+ ---
120
+
106
121
  ## Supported Ecosystems (Discovery)
107
122
 
108
123
  | Language | Method | Discovery Source |
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-coderag
3
- Version: 1.3.2
3
+ Version: 1.3.3
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
@@ -29,6 +29,7 @@ Requires-Dist: onnxruntime
29
29
  Requires-Dist: tokenizers
30
30
  Requires-Dist: pydantic
31
31
  Requires-Dist: httpx
32
+ Requires-Dist: requests
32
33
  Requires-Dist: aiofiles
33
34
  Requires-Dist: pathspec
34
35
  Requires-Dist: tree-sitter
@@ -148,6 +149,21 @@ agent-coderag api fmt
148
149
 
149
150
  ---
150
151
 
152
+ ## Library Usage
153
+
154
+ ```python
155
+ from code_rag import CodeRAG
156
+
157
+ async def main():
158
+ async with CodeRAG(db="code_rag.db") as rag:
159
+ await rag.setup()
160
+ await rag.sync(index_all=True)
161
+ hits = await rag.search("authentication middleware", limit=5)
162
+ report = await rag.api("pydantic", lang="python")
163
+ ```
164
+
165
+ ---
166
+
151
167
  ## Supported Ecosystems (Discovery)
152
168
 
153
169
  | Language | Method | Discovery Source |
@@ -9,6 +9,9 @@ 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/api/__init__.py
13
+ code_rag/api/client.py
14
+ code_rag/api/models.py
12
15
  code_rag/core/__init__.py
13
16
  code_rag/core/constants.py
14
17
  code_rag/core/exceptions.py
@@ -37,10 +40,20 @@ code_rag/parsers/__init__.py
37
40
  code_rag/parsers/languages.py
38
41
  code_rag/parsers/multi_parser.py
39
42
  code_rag/parsers/tree_sitter.py
43
+ code_rag/services/__init__.py
44
+ code_rag/services/config.py
45
+ code_rag/services/discovery_api.py
46
+ code_rag/services/factory.py
47
+ code_rag/services/search.py
48
+ code_rag/services/setup.py
49
+ code_rag/services/sync.py
40
50
  code_rag/storage/__init__.py
41
51
  code_rag/storage/duckdb_impl.py
52
+ tests/test_api_client.py
53
+ tests/test_api_models.py
42
54
  tests/test_cli.py
43
55
  tests/test_cli_detailed.py
56
+ tests/test_cli_json_parity.py
44
57
  tests/test_code_rag_simple.py
45
58
  tests/test_csharp_discovery_detailed.py
46
59
  tests/test_dependency_discovery.py
@@ -58,6 +71,7 @@ tests/test_manager_detailed.py
58
71
  tests/test_manager_extra.py
59
72
  tests/test_models.py
60
73
  tests/test_multi_parser.py
74
+ tests/test_readme_library_usage.py
61
75
  tests/test_rust_discovery_detailed.py
62
76
  tests/test_storage_detailed.py
63
77
  tests/test_tree_sitter_parser.py
@@ -5,6 +5,7 @@ onnxruntime
5
5
  tokenizers
6
6
  pydantic
7
7
  httpx
8
+ requests
8
9
  aiofiles
9
10
  pathspec
10
11
  tree-sitter
@@ -0,0 +1,16 @@
1
+ from code_rag.api.client import CodeRAG
2
+ from code_rag.api.models import SyncResult, ApiReport, SetupResult
3
+ from code_rag.core.models import KnowledgeUnit, UnitKind
4
+ from code_rag.intelligence.distiller import DistillerConfig
5
+ from code_rag.core.exceptions import CodeRAGError
6
+
7
+ __all__ = [
8
+ "CodeRAG",
9
+ "SyncResult",
10
+ "ApiReport",
11
+ "SetupResult",
12
+ "KnowledgeUnit",
13
+ "UnitKind",
14
+ "DistillerConfig",
15
+ "CodeRAGError",
16
+ ]
@@ -0,0 +1,6 @@
1
+ """Public API package for CodeRAG.
2
+
3
+ `CodeRAG` lives in `code_rag.api.client` and is re-exported from top-level
4
+ `code_rag`. Import models from `code_rag.api.models` directly; this package
5
+ init stays empty to avoid circular imports with `code_rag.services.*`.
6
+ """
@@ -0,0 +1,87 @@
1
+ from pathlib import Path
2
+ from typing import Optional, Union
3
+
4
+ from code_rag.api.models import ApiReport, SetupResult, SyncResult
5
+ from code_rag.core.models import KnowledgeUnit
6
+ from code_rag.intelligence.distiller import DistillerConfig
7
+ from code_rag.services.config import load_or_update_config
8
+ from code_rag.services.discovery_api import run_api
9
+ from code_rag.services.factory import create_manager
10
+ from code_rag.services.search import run_search
11
+ from code_rag.services.setup import run_setup
12
+ from code_rag.services.sync import run_rebuild, run_sync
13
+
14
+
15
+ class CodeRAG:
16
+ """Public async facade over CodeRAG services and the storage/search manager."""
17
+
18
+ def __init__(
19
+ self,
20
+ db: str = "code_rag.db",
21
+ onnx: Optional[str] = None,
22
+ *,
23
+ root: Optional[Union[str, Path]] = None,
24
+ allow_build_execution: bool = False,
25
+ ):
26
+ self._db = db
27
+ self._onnx = onnx
28
+ self._root = Path(root) if root is not None else Path.cwd()
29
+ self._allow_build_execution = allow_build_execution
30
+ self._manager = None
31
+
32
+ async def __aenter__(self) -> "CodeRAG":
33
+ return self
34
+
35
+ async def __aexit__(self, *_exc) -> None:
36
+ await self.close()
37
+
38
+ async def close(self) -> None:
39
+ if self._manager is not None:
40
+ await self._manager.close()
41
+ self._manager = None
42
+
43
+ def _ensure_manager(self):
44
+ if self._manager is None:
45
+ self._manager = create_manager(
46
+ self._db, self._onnx, allow_build_execution=self._allow_build_execution
47
+ )
48
+ return self._manager
49
+
50
+ async def config(
51
+ self,
52
+ *,
53
+ url: Optional[str] = None,
54
+ key: Optional[str] = None,
55
+ model: Optional[str] = None,
56
+ provider: Optional[str] = None,
57
+ ) -> DistillerConfig:
58
+ return load_or_update_config(url=url, key=key, model=model, provider=provider)
59
+
60
+ async def setup(self, *, force: bool = False) -> SetupResult:
61
+ return await run_setup(force=force)
62
+
63
+ async def sync(
64
+ self,
65
+ path: Optional[str] = None,
66
+ *,
67
+ index_all: bool = False,
68
+ force: bool = False,
69
+ ) -> SyncResult:
70
+ if path is None and not index_all:
71
+ return SyncResult(status="success", indexed_files=0)
72
+ return await run_sync(
73
+ self._ensure_manager(),
74
+ root=self._root,
75
+ path=path,
76
+ index_all=index_all,
77
+ force=force,
78
+ )
79
+
80
+ async def search(self, query: str, *, limit: int = 5) -> list[KnowledgeUnit]:
81
+ return await run_search(self._ensure_manager(), query, limit=limit)
82
+
83
+ async def api(self, library: str, *, lang: Optional[str] = None) -> ApiReport:
84
+ return await run_api(self._ensure_manager(), library, lang=lang)
85
+
86
+ async def rebuild(self) -> SyncResult:
87
+ return await run_rebuild(self._ensure_manager(), root=self._root)
@@ -0,0 +1,20 @@
1
+ from pydantic import BaseModel, ConfigDict
2
+
3
+
4
+ class SyncResult(BaseModel):
5
+ status: str
6
+ indexed_files: int
7
+
8
+
9
+ class ApiReport(BaseModel):
10
+ library: str
11
+ language: str
12
+ report: str
13
+
14
+
15
+ class SetupResult(BaseModel):
16
+ model_config = ConfigDict(protected_namespaces=())
17
+
18
+ model_dir: str
19
+ downloaded: list[str]
20
+ skipped: list[str]
@@ -19,7 +19,7 @@ class CodeRAGManager:
19
19
  Orchestrates the RAG workflow: parsing, distillation, and storage.
20
20
  """
21
21
 
22
- def __init__(
22
+ def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments
23
23
  self,
24
24
  storage: IStorage,
25
25
  parser: IParser,
@@ -1,6 +1,5 @@
1
1
  import logging
2
2
  import asyncio
3
- import os
4
3
  import argparse
5
4
  import sys
6
5
  import json
@@ -15,49 +14,50 @@ from ..storage.duckdb_impl import DuckDBStorage
15
14
  from ..parsers.multi_parser import MultiParser
16
15
  from ..intelligence.distiller import Distiller, DistillerConfig
17
16
  from ..intelligence.embedder import Embedder, get_global_dir
18
- from ..parsers.languages import EXTENSION_TO_LANGUAGE
19
17
  from ..core.utils import validate_path
20
- from ..core.exceptions import CodeRAGError, DiscoveryError
18
+ from ..core.exceptions import CodeRAGError
19
+ from ..services import sync as sync_service
20
+ from ..services.discovery_api import run_api
21
+ from ..services.search import run_search
22
+ from ..services.setup import run_setup
21
23
 
22
24
  logger = logging.getLogger(__name__)
23
25
 
26
+ # Legacy compatibility surface: these names are imported/patched by existing
27
+ # integrations and tests, so they must stay resolvable on this module.
28
+ __all__ = [
29
+ "get_manager",
30
+ "load_ignore_patterns",
31
+ "should_index",
32
+ "sync_cmd",
33
+ "search_cmd",
34
+ "api_cmd",
35
+ "config_cmd",
36
+ "setup_cmd",
37
+ "rebuild_cmd",
38
+ "DistillerConfig",
39
+ "Embedder",
40
+ "DuckDBStorage",
41
+ "MultiParser",
42
+ "Distiller",
43
+ "get_global_dir",
44
+ "requests",
45
+ "validate_path",
46
+ "main",
47
+ ]
48
+
24
49
 
25
50
  def load_ignore_patterns() -> pathspec.PathSpec:
26
- """Loads ignore patterns from .gitignore or defaults."""
27
- lines = []
28
- if os.path.exists(".gitignore"):
29
- with open(".gitignore", "r", encoding="utf-8") as f:
30
- lines = f.readlines()
31
-
32
- common_excludes = [
33
- "node_modules/",
34
- "venv/",
35
- ".venv/",
36
- "__pycache__/",
37
- ".git/",
38
- ".idea/",
39
- ".vscode/",
40
- ".onnx",
41
- ".db",
42
- ".ai/",
43
- ]
44
- return pathspec.PathSpec.from_lines("gitignore", lines + common_excludes)
51
+ """Loads ignore patterns from the current directory's .gitignore or defaults."""
52
+ return sync_service.load_ignore_patterns(Path.cwd())
45
53
 
46
54
 
47
55
  def should_index(path: Path, ignore_spec: Optional[pathspec.PathSpec] = None) -> bool:
48
56
  """Filters files that should NOT be indexed."""
49
- # Convert backslashes to forward slashes for cross-platform matching
50
- path_str = str(path).replace(os.sep, "/")
51
-
52
- # 1. Check against ignore spec (including .gitignore and common excludes)
53
- if ignore_spec and ignore_spec.match_file(path_str):
54
- return False
55
-
56
- return path.suffix.lower() in EXTENSION_TO_LANGUAGE
57
+ return sync_service.should_index(path, ignore_spec)
57
58
 
58
59
 
59
60
  async def sync_cmd(args):
60
- # Validate input path
61
61
  if args.path:
62
62
  args.path = str(validate_path(args.path))
63
63
 
@@ -67,39 +67,13 @@ async def sync_cmd(args):
67
67
  allow_build_execution=getattr(args, "allow_build_execution", False),
68
68
  )
69
69
  try:
70
- ignore_spec = load_ignore_patterns()
71
-
72
- # Task 2: Sync dependencies before indexing
73
- try:
74
- await manager.sync_dependencies(args.path or ".")
75
- except DiscoveryError as de:
76
- logger.warning("Dependency discovery failed: %s", de)
77
-
78
- if args.path:
79
- target_path = Path(args.path)
80
- if target_path.is_file():
81
- if should_index(target_path, ignore_spec):
82
- await manager.sync_file(str(target_path), force_distill=args.force)
83
- else:
84
- paths = [
85
- str(p)
86
- for p in target_path.rglob("*")
87
- if p.is_file() and should_index(p, ignore_spec)
88
- ]
89
-
90
- if args.verbose:
91
- logger.info("Indexing %d files...", len(paths))
92
- await manager.sync_project(paths, force_distill=args.force)
93
- elif args.all:
94
- paths = [
95
- str(p)
96
- for p in Path(".").rglob("*")
97
- if p.is_file() and should_index(p, ignore_spec)
98
- ]
99
-
100
- if args.verbose:
101
- logger.info("Indexing %d files...", len(paths))
102
- await manager.sync_project(paths, force_distill=args.force)
70
+ await sync_service.run_sync(
71
+ manager,
72
+ root=Path.cwd(),
73
+ path=args.path,
74
+ index_all=bool(args.all),
75
+ force=bool(args.force),
76
+ )
103
77
 
104
78
  if args.json:
105
79
  print(json.dumps({"status": "success", "indexed_files": "auto"}))
@@ -116,7 +90,7 @@ async def sync_cmd(args):
116
90
  async def search_cmd(args):
117
91
  manager = get_manager(args.db, args.onnx)
118
92
  try:
119
- results = await manager.search(args.query, limit=args.limit)
93
+ results = await run_search(manager, args.query, limit=args.limit)
120
94
 
121
95
  if args.json:
122
96
  output = []
@@ -150,14 +124,12 @@ async def search_cmd(args):
150
124
  async def api_cmd(args):
151
125
  manager = get_manager(args.db, args.onnx)
152
126
  try:
153
- # Default to python if not specified
154
- lang = args.lang or "python"
155
- api_report = await manager.discovery.extract_api(args.library, language=lang)
127
+ report = await run_api(manager, args.library, lang=args.lang)
156
128
 
157
129
  if args.json:
158
- print(json.dumps({"library": args.library, "report": api_report}))
130
+ print(json.dumps({"library": report.library, "report": report.report}))
159
131
  else:
160
- print(api_report)
132
+ print(report.report)
161
133
  except Exception as e:
162
134
  logger.error("API discovery failed: %s", e)
163
135
  if args.json:
@@ -200,40 +172,33 @@ def config_cmd(args):
200
172
  async def setup_cmd(args):
201
173
  """Downloads necessary local models."""
202
174
  global_dir = get_global_dir()
203
- model_dir = global_dir / "models" / "mini-lm"
204
- model_dir.mkdir(parents=True, exist_ok=True)
205
-
206
- files = {
207
- "model.onnx": "https://huggingface.co/naranor/all-MiniLM-L6-v2-onnx/resolve/main/model.onnx",
208
- "tokenizer.json": "https://huggingface.co/naranor/all-MiniLM-L6-v2-onnx/resolve/main/tokenizer.json",
209
- }
210
175
 
211
176
  if not args.json:
212
177
  print(f"Setting up agent-coderag in {global_dir}...")
213
178
 
214
- for name, url in files.items():
215
- target_path = model_dir / name
216
- if target_path.exists() and not args.force:
217
- if not args.json:
218
- print(f" {name} already exists. Skipping.")
219
- continue
220
-
221
- if not args.json:
222
- print(f" Downloading {name}...")
223
- tmp_path = target_path.with_suffix(".tmp")
224
- try:
225
- response = requests.get(url, stream=True, timeout=30)
226
- response.raise_for_status()
227
- with open(tmp_path, "wb") as f:
228
- for chunk in response.iter_content(chunk_size=8192):
229
- f.write(chunk)
230
- os.replace(tmp_path, target_path)
231
- except Exception as e:
232
- if tmp_path.exists():
233
- tmp_path.unlink()
234
- if not args.json:
235
- print(f" Error downloading {name}: {e}")
179
+ def _on_progress(event: str, name: str, error: Optional[BaseException]) -> None:
180
+ if args.json:
236
181
  return
182
+ if event == "skipped":
183
+ print(f" {name} already exists. Skipping.")
184
+ elif event == "downloading":
185
+ print(f" Downloading {name}...")
186
+ elif event == "error":
187
+ print(f" Error downloading {name}: {error}")
188
+
189
+ try:
190
+ # Pass module-level requests.get so @patch("code_rag.entry.cli.requests.get")
191
+ # continues to intercept downloads in legacy CLI tests.
192
+ await run_setup(
193
+ force=args.force,
194
+ global_dir=global_dir,
195
+ on_progress=_on_progress,
196
+ http_get=requests.get,
197
+ )
198
+ except Exception:
199
+ # Legacy behaviour: per-file error already reported via on_progress;
200
+ # stop here without crashing or printing "Setup complete.".
201
+ return
237
202
 
238
203
  if not args.json:
239
204
  print("Setup complete.")
@@ -253,15 +218,16 @@ def get_manager(
253
218
  onnx_path: Optional[str] = None,
254
219
  allow_build_execution: bool = False,
255
220
  ) -> CodeRAGManager:
256
- # 1. Setup Intelligence
221
+ """Wires a manager from the module-level components (legacy patch points).
222
+
223
+ Mirrors ``code_rag.services.factory.create_manager`` but resolves the
224
+ components through this module so existing CLI tests can substitute them.
225
+ """
257
226
  config = DistillerConfig.load()
258
227
  distiller = Distiller(config)
259
228
  embedder = Embedder(model_path=onnx_path)
260
229
 
261
- # 2. Setup Storage
262
230
  storage = DuckDBStorage(db_path, embedder=embedder)
263
-
264
- # 3. Setup Parser
265
231
  parser = MultiParser()
266
232
 
267
233
  return CodeRAGManager(
@@ -0,0 +1,25 @@
1
+ from code_rag.intelligence.distiller import DistillerConfig
2
+
3
+
4
+ def load_or_update_config(
5
+ url: str | None = None,
6
+ key: str | None = None,
7
+ model: str | None = None,
8
+ provider: str | None = None,
9
+ ) -> DistillerConfig:
10
+ """Loads the persisted config, optionally updating and saving it."""
11
+ config = DistillerConfig.load()
12
+ if not any([url, key, model, provider]):
13
+ return config
14
+
15
+ if url is not None:
16
+ config.api_base = url
17
+ if key is not None:
18
+ config.api_key = key
19
+ if model is not None:
20
+ config.model = model
21
+ if provider is not None:
22
+ config.provider = provider
23
+
24
+ config.save()
25
+ return config
@@ -0,0 +1,8 @@
1
+ from code_rag.api.models import ApiReport
2
+
3
+
4
+ async def run_api(manager, library: str, *, lang: str | None = None) -> ApiReport:
5
+ """Discovers and returns the public API report for a library."""
6
+ language = lang or "python"
7
+ report = await manager.discovery.extract_api(library, language=language)
8
+ return ApiReport(library=library, language=language, report=report)
@@ -0,0 +1,28 @@
1
+ from typing import Optional
2
+
3
+ from code_rag.core.manager import CodeRAGManager
4
+ from code_rag.intelligence.distiller import Distiller, DistillerConfig
5
+ from code_rag.intelligence.embedder import Embedder
6
+ from code_rag.parsers.multi_parser import MultiParser
7
+ from code_rag.storage.duckdb_impl import DuckDBStorage
8
+
9
+
10
+ def create_manager(
11
+ db_path: str,
12
+ onnx_path: Optional[str] = None,
13
+ allow_build_execution: bool = False,
14
+ ) -> CodeRAGManager:
15
+ """Builds a fully wired CodeRAGManager (storage, parser, distiller)."""
16
+ config = DistillerConfig.load()
17
+ distiller = Distiller(config)
18
+ embedder = Embedder(model_path=onnx_path)
19
+
20
+ storage = DuckDBStorage(db_path, embedder=embedder)
21
+ parser = MultiParser()
22
+
23
+ return CodeRAGManager(
24
+ storage,
25
+ parser,
26
+ distiller,
27
+ allow_build_execution=allow_build_execution,
28
+ )
@@ -0,0 +1,6 @@
1
+ from code_rag.core.models import KnowledgeUnit
2
+
3
+
4
+ async def run_search(manager, query: str, *, limit: int = 5) -> list[KnowledgeUnit]:
5
+ """Performs semantic search across indexed units."""
6
+ return await manager.search(query, limit=limit)
@@ -0,0 +1,72 @@
1
+ import os
2
+ from pathlib import Path
3
+ from typing import Any, Callable, Optional
4
+
5
+ import requests
6
+
7
+ from code_rag.api.models import SetupResult
8
+ from code_rag.intelligence.embedder import get_global_dir
9
+
10
+ MODEL_FILES = {
11
+ "model.onnx": "https://huggingface.co/naranor/all-MiniLM-L6-v2-onnx/resolve/main/model.onnx",
12
+ "tokenizer.json": "https://huggingface.co/naranor/all-MiniLM-L6-v2-onnx/resolve/main/tokenizer.json",
13
+ }
14
+
15
+ HttpGet = Callable[..., Any]
16
+
17
+ # Progress callback signature: (event, name, error) where event is one of
18
+ # "downloading", "skipped", "error" and error is set only for "error".
19
+ ProgressCallback = Callable[[str, str, Optional[BaseException]], None]
20
+
21
+
22
+ def _download_file(get: HttpGet, url: str, target_path: Path) -> None:
23
+ tmp_path = target_path.with_suffix(".tmp")
24
+ try:
25
+ response = get(url, stream=True, timeout=30)
26
+ response.raise_for_status()
27
+ with open(tmp_path, "wb") as handle:
28
+ for chunk in response.iter_content(chunk_size=8192):
29
+ handle.write(chunk)
30
+ os.replace(tmp_path, target_path)
31
+ except Exception:
32
+ if tmp_path.exists():
33
+ tmp_path.unlink()
34
+ raise
35
+
36
+
37
+ async def run_setup(
38
+ *,
39
+ force: bool = False,
40
+ global_dir: Optional[Path] = None,
41
+ on_progress: Optional[ProgressCallback] = None,
42
+ http_get: Optional[HttpGet] = None,
43
+ ) -> SetupResult:
44
+ """Downloads missing local embedding model files into the global dir."""
45
+ target_dir = global_dir if global_dir is not None else get_global_dir()
46
+ model_dir = target_dir / "models" / "mini-lm"
47
+ model_dir.mkdir(parents=True, exist_ok=True)
48
+ get: HttpGet = http_get or requests.get
49
+
50
+ downloaded: list[str] = []
51
+ skipped: list[str] = []
52
+
53
+ for name, url in MODEL_FILES.items():
54
+ target_path = model_dir / name
55
+ if target_path.exists() and not force:
56
+ skipped.append(name)
57
+ if on_progress:
58
+ on_progress("skipped", name, None)
59
+ continue
60
+
61
+ if on_progress:
62
+ on_progress("downloading", name, None)
63
+
64
+ try:
65
+ _download_file(get, url, target_path)
66
+ downloaded.append(name)
67
+ except Exception as exc:
68
+ if on_progress:
69
+ on_progress("error", name, exc)
70
+ raise
71
+
72
+ return SetupResult(model_dir=str(model_dir), downloaded=downloaded, skipped=skipped)