vortexa 0.1.3__tar.gz → 0.2.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.
- {vortexa-0.1.3 → vortexa-0.2.0}/PKG-INFO +5 -7
- {vortexa-0.1.3 → vortexa-0.2.0}/pyproject.toml +3 -8
- {vortexa-0.1.3 → vortexa-0.2.0}/src/vortexa/core/chunking.py +1 -1
- {vortexa-0.1.3 → vortexa-0.2.0}/src/vortexa/core/embedding.py +2 -2
- {vortexa-0.1.3 → vortexa-0.2.0}/src/vortexa/core/indexer.py +15 -2
- {vortexa-0.1.3 → vortexa-0.2.0}/src/vortexa/interfaces/cli.py +46 -7
- {vortexa-0.1.3 → vortexa-0.2.0}/src/vortexa/interfaces/mcp_server.py +1 -1
- {vortexa-0.1.3 → vortexa-0.2.0}/src/vortexa/storage/walker.py +0 -1
- {vortexa-0.1.3 → vortexa-0.2.0}/src/vortexa.egg-info/PKG-INFO +5 -7
- {vortexa-0.1.3 → vortexa-0.2.0}/src/vortexa.egg-info/requires.txt +1 -5
- {vortexa-0.1.3 → vortexa-0.2.0}/LICENSE +0 -0
- {vortexa-0.1.3 → vortexa-0.2.0}/README.md +0 -0
- {vortexa-0.1.3 → vortexa-0.2.0}/setup.cfg +0 -0
- {vortexa-0.1.3 → vortexa-0.2.0}/src/vortexa/__init__.py +0 -0
- {vortexa-0.1.3 → vortexa-0.2.0}/src/vortexa/core/__init__.py +0 -0
- {vortexa-0.1.3 → vortexa-0.2.0}/src/vortexa/core/language.py +0 -0
- {vortexa-0.1.3 → vortexa-0.2.0}/src/vortexa/core/lf4_model.py +0 -0
- {vortexa-0.1.3 → vortexa-0.2.0}/src/vortexa/core/types.py +0 -0
- {vortexa-0.1.3 → vortexa-0.2.0}/src/vortexa/interfaces/__init__.py +0 -0
- {vortexa-0.1.3 → vortexa-0.2.0}/src/vortexa/interfaces/watcher.py +0 -0
- {vortexa-0.1.3 → vortexa-0.2.0}/src/vortexa/search/__init__.py +0 -0
- {vortexa-0.1.3 → vortexa-0.2.0}/src/vortexa/search/ranking.py +0 -0
- {vortexa-0.1.3 → vortexa-0.2.0}/src/vortexa/search/search.py +0 -0
- {vortexa-0.1.3 → vortexa-0.2.0}/src/vortexa/search/tokens.py +0 -0
- {vortexa-0.1.3 → vortexa-0.2.0}/src/vortexa/storage/__init__.py +0 -0
- {vortexa-0.1.3 → vortexa-0.2.0}/src/vortexa/storage/bm25.py +0 -0
- {vortexa-0.1.3 → vortexa-0.2.0}/src/vortexa/storage/vector_store.py +0 -0
- {vortexa-0.1.3 → vortexa-0.2.0}/src/vortexa.egg-info/SOURCES.txt +0 -0
- {vortexa-0.1.3 → vortexa-0.2.0}/src/vortexa.egg-info/dependency_links.txt +0 -0
- {vortexa-0.1.3 → vortexa-0.2.0}/src/vortexa.egg-info/entry_points.txt +0 -0
- {vortexa-0.1.3 → vortexa-0.2.0}/src/vortexa.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vortexa
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Codebase indexing and semantic search engine
|
|
5
5
|
Author-email: VortexAI <koulabhay25@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -28,12 +28,10 @@ Requires-Dist: pathspec>=0.12.0
|
|
|
28
28
|
Requires-Dist: huggingface-hub>=0.20.0
|
|
29
29
|
Requires-Dist: tokenizers>=0.19.0
|
|
30
30
|
Requires-Dist: safetensors>=0.4.0
|
|
31
|
-
|
|
32
|
-
Requires-Dist: model2vec>=0.3.0
|
|
33
|
-
Requires-Dist: sentence-transformers>=2.2.0
|
|
34
|
-
Requires-Dist: tree-sitter-language-pack>=0.1.0
|
|
35
|
-
Provides-Extra: mcp
|
|
36
|
-
Requires-Dist: fastmcp>=2.0.0; extra == "mcp"
|
|
31
|
+
Requires-Dist: fastmcp>=2.0.0
|
|
32
|
+
Requires-Dist: model2vec>=0.3.0
|
|
33
|
+
Requires-Dist: sentence-transformers>=2.2.0
|
|
34
|
+
Requires-Dist: tree-sitter-language-pack>=0.1.0
|
|
37
35
|
Dynamic: license-file
|
|
38
36
|
|
|
39
37
|
<div align="center">
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "vortexa"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Codebase indexing and semantic search engine"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -34,17 +34,12 @@ dependencies = [
|
|
|
34
34
|
"huggingface-hub>=0.20.0",
|
|
35
35
|
"tokenizers>=0.19.0",
|
|
36
36
|
"safetensors>=0.4.0",
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
[project.optional-dependencies]
|
|
40
|
-
full = [
|
|
37
|
+
"fastmcp>=2.0.0",
|
|
41
38
|
"model2vec>=0.3.0",
|
|
42
39
|
"sentence-transformers>=2.2.0",
|
|
43
40
|
"tree-sitter-language-pack>=0.1.0",
|
|
44
41
|
]
|
|
45
|
-
|
|
46
|
-
"fastmcp>=2.0.0",
|
|
47
|
-
]
|
|
42
|
+
|
|
48
43
|
|
|
49
44
|
[project.scripts]
|
|
50
45
|
vortexa = "vortexa.interfaces.cli:main"
|
|
@@ -31,7 +31,7 @@ class ChunkBoundary:
|
|
|
31
31
|
def _get_parser(language: str):
|
|
32
32
|
"""Get a tree-sitter parser for the given language. Returns None if unavailable."""
|
|
33
33
|
try:
|
|
34
|
-
from tree_sitter_language_pack import get_parser as _get_ts_parser
|
|
34
|
+
from tree_sitter_language_pack import get_parser as _get_ts_parser
|
|
35
35
|
|
|
36
36
|
return _get_ts_parser(language)
|
|
37
37
|
except Exception:
|
|
@@ -61,7 +61,7 @@ class Model2VecEmbedder:
|
|
|
61
61
|
if self._model is None:
|
|
62
62
|
with self._lock:
|
|
63
63
|
if self._model is None: # Double-checked locking
|
|
64
|
-
from model2vec import StaticModel
|
|
64
|
+
from model2vec import StaticModel
|
|
65
65
|
logger.info("Loading embedding model: %s", self._model_id)
|
|
66
66
|
self._model = StaticModel.from_pretrained(self._model_id)
|
|
67
67
|
|
|
@@ -111,7 +111,7 @@ class SentenceTransformerEmbedder:
|
|
|
111
111
|
if self._model is None:
|
|
112
112
|
with self._lock:
|
|
113
113
|
if self._model is None:
|
|
114
|
-
from sentence_transformers import SentenceTransformer
|
|
114
|
+
from sentence_transformers import SentenceTransformer
|
|
115
115
|
logger.info("Loading sentence-transformers model: %s", self._model_name)
|
|
116
116
|
self._model = SentenceTransformer(self._model_name, device=self._device)
|
|
117
117
|
|
|
@@ -42,6 +42,19 @@ _MAX_FILE_BYTES = 1_000_000 # 1 MB
|
|
|
42
42
|
# Hash of the chunking logic for memoization — bump when chunking changes
|
|
43
43
|
_CHUNKING_LOGIC_VERSION = "2"
|
|
44
44
|
|
|
45
|
+
_GLOBAL_INDEX_ROOT = Path.home() / ".vortexa"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _index_dir_for_root(root: Path) -> Path:
|
|
49
|
+
"""Derive the global index directory for a given project root.
|
|
50
|
+
|
|
51
|
+
Stores under ``~/.vortexa/<hex_hash>/`` where *hex_hash* is the first
|
|
52
|
+
16 chars of the SHA-256 of the resolved absolute path. This keeps every
|
|
53
|
+
indexed project isolated while avoiding path-length issues.
|
|
54
|
+
"""
|
|
55
|
+
path_hash = hashlib.sha256(str(root).encode()).hexdigest()[:16]
|
|
56
|
+
return _GLOBAL_INDEX_ROOT / path_hash
|
|
57
|
+
|
|
45
58
|
|
|
46
59
|
def _file_hash(path: Path) -> str:
|
|
47
60
|
"""Compute SHA256 hash of file contents."""
|
|
@@ -80,11 +93,11 @@ class CodebaseIndexer:
|
|
|
80
93
|
:param root: Root directory of the codebase to index.
|
|
81
94
|
:param model: Embedding model (Encoder or Embedder). Created from model_id if None.
|
|
82
95
|
:param model_id: Model ID for auto-creating embedder (ignored if model provided).
|
|
83
|
-
:param index_dir: Directory for persistent storage. Defaults to
|
|
96
|
+
:param index_dir: Directory for persistent storage. Defaults to ~/.vortexa/<hash>/.
|
|
84
97
|
:param chunk_config: Chunking configuration.
|
|
85
98
|
"""
|
|
86
99
|
self.root = Path(root).resolve()
|
|
87
|
-
self.index_dir = Path(index_dir) if index_dir else self.root
|
|
100
|
+
self.index_dir = Path(index_dir) if index_dir else _index_dir_for_root(self.root)
|
|
88
101
|
self.chunk_config = chunk_config or ChunkConfig()
|
|
89
102
|
|
|
90
103
|
# Resolve model: prefer explicit model, else create LF4Embedder
|
|
@@ -8,7 +8,7 @@ import logging
|
|
|
8
8
|
import sys
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
|
|
11
|
-
from vortexa.core.indexer import CodebaseIndexer
|
|
11
|
+
from vortexa.core.indexer import CodebaseIndexer, _index_dir_for_root
|
|
12
12
|
|
|
13
13
|
logger = logging.getLogger(__name__)
|
|
14
14
|
|
|
@@ -112,7 +112,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
112
112
|
parser.add_argument(
|
|
113
113
|
"environment_details",
|
|
114
114
|
nargs="?",
|
|
115
|
-
help="optional root path, JSON environment details, or
|
|
115
|
+
help="optional root path, JSON environment details, or text containing a root path to index and search (defaults to current directory)",
|
|
116
116
|
)
|
|
117
117
|
parser.add_argument(
|
|
118
118
|
"--root",
|
|
@@ -138,12 +138,12 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
138
138
|
parser.add_argument(
|
|
139
139
|
"--force",
|
|
140
140
|
action="store_true",
|
|
141
|
-
help="force a full re-index before searching",
|
|
141
|
+
help="force a full re-index before searching (ignores existing index)",
|
|
142
142
|
)
|
|
143
143
|
parser.add_argument(
|
|
144
144
|
"--no-index",
|
|
145
145
|
action="store_true",
|
|
146
|
-
help="skip indexing and search the existing index
|
|
146
|
+
help="skip indexing entirely and search the existing index (errors if none)",
|
|
147
147
|
)
|
|
148
148
|
parser.add_argument(
|
|
149
149
|
"--plain",
|
|
@@ -153,6 +153,12 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
153
153
|
return parser
|
|
154
154
|
|
|
155
155
|
|
|
156
|
+
def _has_existing_index(root: Path) -> bool:
|
|
157
|
+
"""Check if a persistent index already exists for this root."""
|
|
158
|
+
index_dir = _index_dir_for_root(root)
|
|
159
|
+
return (index_dir / "state.lmdb").exists()
|
|
160
|
+
|
|
161
|
+
|
|
156
162
|
def run_query(args: argparse.Namespace, parser: argparse.ArgumentParser) -> int:
|
|
157
163
|
if not args.query:
|
|
158
164
|
raise SystemExit("query is required when using -q/--query")
|
|
@@ -167,15 +173,48 @@ def run_query(args: argparse.Namespace, parser: argparse.ArgumentParser) -> int:
|
|
|
167
173
|
parser.error(str(exc))
|
|
168
174
|
indexer = CodebaseIndexer(root=root)
|
|
169
175
|
|
|
176
|
+
has_index = _has_existing_index(root)
|
|
177
|
+
|
|
170
178
|
if args.no_index:
|
|
179
|
+
if not has_index:
|
|
180
|
+
raise SystemExit(
|
|
181
|
+
f"No index found for {root}. Run without --no-index to build one."
|
|
182
|
+
)
|
|
171
183
|
indexer._load_state()
|
|
172
|
-
|
|
173
|
-
|
|
184
|
+
print(f"[vortexa] Loaded existing index ({len(indexer.chunks)} chunks)", file=sys.stderr)
|
|
185
|
+
elif args.force:
|
|
186
|
+
stats = indexer.index(force=True, include_text_files=args.include_text)
|
|
174
187
|
print(
|
|
175
|
-
f"[vortexa]
|
|
188
|
+
f"[vortexa] Re-indexed: {stats.indexed_files} files, "
|
|
176
189
|
f"{stats.total_chunks} chunks in {stats.index_time_ms:.0f}ms",
|
|
177
190
|
file=sys.stderr,
|
|
178
191
|
)
|
|
192
|
+
else:
|
|
193
|
+
if not has_index:
|
|
194
|
+
stats = indexer.index(include_text_files=args.include_text)
|
|
195
|
+
print(
|
|
196
|
+
f"[vortexa] Indexed: {stats.indexed_files} files, "
|
|
197
|
+
f"{stats.total_chunks} chunks in {stats.index_time_ms:.0f}ms",
|
|
198
|
+
file=sys.stderr,
|
|
199
|
+
)
|
|
200
|
+
else:
|
|
201
|
+
indexer._load_state()
|
|
202
|
+
before = len(indexer.chunks)
|
|
203
|
+
stats = indexer.index(include_text_files=args.include_text)
|
|
204
|
+
after = len(indexer.chunks)
|
|
205
|
+
if after == before and stats.memo_misses == 0:
|
|
206
|
+
print(
|
|
207
|
+
f"[vortexa] Index up to date ({after} chunks, no changes)",
|
|
208
|
+
file=sys.stderr,
|
|
209
|
+
)
|
|
210
|
+
else:
|
|
211
|
+
added = after - before
|
|
212
|
+
label = f"+{added} chunks" if added else f"{stats.memo_misses} updated"
|
|
213
|
+
print(
|
|
214
|
+
f"[vortexa] Updated: {stats.indexed_files} files, "
|
|
215
|
+
f"{after} chunks ({label}) in {stats.index_time_ms:.0f}ms",
|
|
216
|
+
file=sys.stderr,
|
|
217
|
+
)
|
|
179
218
|
|
|
180
219
|
query = args.query
|
|
181
220
|
results = indexer.search(query, top_k=args.top_k, alpha=args.alpha)
|
|
@@ -14,7 +14,7 @@ from pathlib import Path
|
|
|
14
14
|
|
|
15
15
|
os.environ.setdefault("HF_HUB_DISABLE_SYMLINKS_WARNING", "1")
|
|
16
16
|
|
|
17
|
-
from fastmcp import FastMCP # ty: ignore[unresolved-import]
|
|
17
|
+
from fastmcp import FastMCP # ty: ignore[unresolved-import, unused-ignore-comment]
|
|
18
18
|
|
|
19
19
|
logger = logging.getLogger(__name__)
|
|
20
20
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vortexa
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Codebase indexing and semantic search engine
|
|
5
5
|
Author-email: VortexAI <koulabhay25@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -28,12 +28,10 @@ Requires-Dist: pathspec>=0.12.0
|
|
|
28
28
|
Requires-Dist: huggingface-hub>=0.20.0
|
|
29
29
|
Requires-Dist: tokenizers>=0.19.0
|
|
30
30
|
Requires-Dist: safetensors>=0.4.0
|
|
31
|
-
|
|
32
|
-
Requires-Dist: model2vec>=0.3.0
|
|
33
|
-
Requires-Dist: sentence-transformers>=2.2.0
|
|
34
|
-
Requires-Dist: tree-sitter-language-pack>=0.1.0
|
|
35
|
-
Provides-Extra: mcp
|
|
36
|
-
Requires-Dist: fastmcp>=2.0.0; extra == "mcp"
|
|
31
|
+
Requires-Dist: fastmcp>=2.0.0
|
|
32
|
+
Requires-Dist: model2vec>=0.3.0
|
|
33
|
+
Requires-Dist: sentence-transformers>=2.2.0
|
|
34
|
+
Requires-Dist: tree-sitter-language-pack>=0.1.0
|
|
37
35
|
Dynamic: license-file
|
|
38
36
|
|
|
39
37
|
<div align="center">
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|