kodit 0.1.13__py3-none-any.whl → 0.1.15__py3-none-any.whl
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.
Potentially problematic release.
This version of kodit might be problematic. Click here for more details.
- kodit/_version.py +2 -2
- kodit/bm25/keyword_search_factory.py +17 -0
- kodit/bm25/keyword_search_service.py +34 -0
- kodit/bm25/{bm25.py → local_bm25.py} +40 -14
- kodit/bm25/vectorchord_bm25.py +193 -0
- kodit/cli.py +14 -11
- kodit/config.py +9 -2
- kodit/database.py +4 -2
- kodit/embedding/embedding_factory.py +44 -0
- kodit/embedding/embedding_provider/__init__.py +1 -0
- kodit/embedding/embedding_provider/embedding_provider.py +53 -0
- kodit/embedding/embedding_provider/hash_embedding_provider.py +77 -0
- kodit/embedding/embedding_provider/local_embedding_provider.py +58 -0
- kodit/embedding/embedding_provider/openai_embedding_provider.py +63 -0
- kodit/embedding/embedding_repository.py +206 -0
- kodit/embedding/local_vector_search_service.py +50 -0
- kodit/embedding/vector_search_service.py +38 -0
- kodit/embedding/vectorchord_vector_search_service.py +145 -0
- kodit/indexing/indexing_repository.py +24 -4
- kodit/indexing/indexing_service.py +25 -30
- kodit/mcp.py +28 -7
- kodit/search/search_repository.py +0 -121
- kodit/search/search_service.py +12 -24
- kodit/source/source_service.py +9 -3
- kodit/util/__init__.py +1 -0
- kodit/util/spinner.py +59 -0
- {kodit-0.1.13.dist-info → kodit-0.1.15.dist-info}/METADATA +2 -1
- kodit-0.1.15.dist-info/RECORD +58 -0
- kodit/embedding/embedding.py +0 -203
- kodit-0.1.13.dist-info/RECORD +0 -44
- {kodit-0.1.13.dist-info → kodit-0.1.15.dist-info}/WHEEL +0 -0
- {kodit-0.1.13.dist-info → kodit-0.1.15.dist-info}/entry_points.txt +0 -0
- {kodit-0.1.13.dist-info → kodit-0.1.15.dist-info}/licenses/LICENSE +0 -0
|
@@ -13,13 +13,16 @@ import pydantic
|
|
|
13
13
|
import structlog
|
|
14
14
|
from tqdm.asyncio import tqdm
|
|
15
15
|
|
|
16
|
-
from kodit.bm25.
|
|
17
|
-
from kodit.embedding.
|
|
18
|
-
|
|
16
|
+
from kodit.bm25.keyword_search_service import BM25Document, KeywordSearchProvider
|
|
17
|
+
from kodit.embedding.vector_search_service import (
|
|
18
|
+
VectorSearchRequest,
|
|
19
|
+
VectorSearchService,
|
|
20
|
+
)
|
|
19
21
|
from kodit.indexing.indexing_models import Snippet
|
|
20
22
|
from kodit.indexing.indexing_repository import IndexRepository
|
|
21
23
|
from kodit.snippets.snippets import SnippetService
|
|
22
24
|
from kodit.source.source_service import SourceService
|
|
25
|
+
from kodit.util.spinner import Spinner
|
|
23
26
|
|
|
24
27
|
# List of MIME types that are blacklisted from being indexed
|
|
25
28
|
MIME_BLACKLIST = ["unknown/unknown"]
|
|
@@ -51,8 +54,8 @@ class IndexService:
|
|
|
51
54
|
self,
|
|
52
55
|
repository: IndexRepository,
|
|
53
56
|
source_service: SourceService,
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
keyword_search_provider: KeywordSearchProvider,
|
|
58
|
+
vector_search_service: VectorSearchService,
|
|
56
59
|
) -> None:
|
|
57
60
|
"""Initialize the index service.
|
|
58
61
|
|
|
@@ -65,8 +68,8 @@ class IndexService:
|
|
|
65
68
|
self.source_service = source_service
|
|
66
69
|
self.snippet_service = SnippetService()
|
|
67
70
|
self.log = structlog.get_logger(__name__)
|
|
68
|
-
self.
|
|
69
|
-
self.
|
|
71
|
+
self.keyword_search_provider = keyword_search_provider
|
|
72
|
+
self.code_search_service = vector_search_service
|
|
70
73
|
|
|
71
74
|
async def create(self, source_id: int) -> IndexView:
|
|
72
75
|
"""Create a new index for a source.
|
|
@@ -126,36 +129,27 @@ class IndexService:
|
|
|
126
129
|
msg = f"Index not found: {index_id}"
|
|
127
130
|
raise ValueError(msg)
|
|
128
131
|
|
|
129
|
-
# First delete all old snippets, if they exist
|
|
130
|
-
await self.repository.delete_all_snippets(index_id)
|
|
131
|
-
|
|
132
132
|
# Create snippets for supported file types
|
|
133
133
|
await self._create_snippets(index_id)
|
|
134
134
|
|
|
135
135
|
snippets = await self.repository.get_all_snippets(index_id)
|
|
136
136
|
|
|
137
137
|
self.log.info("Creating keyword index")
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
138
|
+
with Spinner():
|
|
139
|
+
await self.keyword_search_provider.index(
|
|
140
|
+
[
|
|
141
|
+
BM25Document(snippet_id=snippet.id, text=snippet.content)
|
|
142
|
+
for snippet in snippets
|
|
143
|
+
]
|
|
144
|
+
)
|
|
144
145
|
|
|
145
146
|
self.log.info("Creating semantic code index")
|
|
146
|
-
|
|
147
|
-
self.
|
|
148
|
-
[
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
):
|
|
153
|
-
await self.repository.add_embedding(
|
|
154
|
-
Embedding(
|
|
155
|
-
snippet_id=e.id,
|
|
156
|
-
embedding=e.embedding,
|
|
157
|
-
type=EmbeddingType.CODE,
|
|
158
|
-
)
|
|
147
|
+
with Spinner():
|
|
148
|
+
await self.code_search_service.index(
|
|
149
|
+
[
|
|
150
|
+
VectorSearchRequest(snippet.id, snippet.content)
|
|
151
|
+
for snippet in snippets
|
|
152
|
+
]
|
|
159
153
|
)
|
|
160
154
|
|
|
161
155
|
# Update index timestamp
|
|
@@ -174,6 +168,7 @@ class IndexService:
|
|
|
174
168
|
|
|
175
169
|
"""
|
|
176
170
|
files = await self.repository.files_for_index(index_id)
|
|
171
|
+
self.log.info("Creating snippets for files", index_id=index_id)
|
|
177
172
|
for file in tqdm(files, total=len(files), leave=False):
|
|
178
173
|
# Skip unsupported file types
|
|
179
174
|
if file.mime_type in MIME_BLACKLIST:
|
|
@@ -195,4 +190,4 @@ class IndexService:
|
|
|
195
190
|
file_id=file.id,
|
|
196
191
|
content=snippet.text,
|
|
197
192
|
)
|
|
198
|
-
await self.repository.
|
|
193
|
+
await self.repository.add_snippet_or_update_content(s)
|
kodit/mcp.py
CHANGED
|
@@ -12,9 +12,10 @@ from pydantic import Field
|
|
|
12
12
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
13
13
|
|
|
14
14
|
from kodit._version import version
|
|
15
|
+
from kodit.bm25.keyword_search_factory import keyword_search_factory
|
|
15
16
|
from kodit.config import AppContext
|
|
16
17
|
from kodit.database import Database
|
|
17
|
-
from kodit.embedding.
|
|
18
|
+
from kodit.embedding.embedding_factory import embedding_factory
|
|
18
19
|
from kodit.search.search_repository import SearchRepository
|
|
19
20
|
from kodit.search.search_service import SearchRequest, SearchResult, SearchService
|
|
20
21
|
|
|
@@ -53,7 +54,16 @@ async def mcp_lifespan(_: FastMCP) -> AsyncIterator[MCPContext]:
|
|
|
53
54
|
yield MCPContext(session=session, app_context=app_context)
|
|
54
55
|
|
|
55
56
|
|
|
56
|
-
mcp = FastMCP(
|
|
57
|
+
mcp = FastMCP(
|
|
58
|
+
"Kodit",
|
|
59
|
+
lifespan=mcp_lifespan,
|
|
60
|
+
# Note that instructions are not used by Cline.
|
|
61
|
+
instructions=(
|
|
62
|
+
"This server is used to assist with code generation by retrieving "
|
|
63
|
+
"code examples related to the user's intent."
|
|
64
|
+
"Call search() to retrieve relevant code examples."
|
|
65
|
+
),
|
|
66
|
+
)
|
|
57
67
|
|
|
58
68
|
|
|
59
69
|
@mcp.tool()
|
|
@@ -87,11 +97,19 @@ async def search(
|
|
|
87
97
|
),
|
|
88
98
|
],
|
|
89
99
|
) -> str:
|
|
90
|
-
"""Search for relevant
|
|
100
|
+
"""Search for pre-existing examples of relevant code.
|
|
91
101
|
|
|
92
|
-
|
|
93
|
-
|
|
102
|
+
⚠️ **ENFORCEMENT:**
|
|
103
|
+
For *every* user request that involves writing or modifying code (of any language or
|
|
104
|
+
domain), the assistant's *first* action **must** be to call the kodit.search MCP
|
|
105
|
+
tool. You may only produce or edit code *after* that tool call and its successful
|
|
106
|
+
result.
|
|
94
107
|
"""
|
|
108
|
+
# This docstring is used by the AI assistant to decide when to call the tool. If you
|
|
109
|
+
# want to update it, please make sure you thoroughly test the assistant's response
|
|
110
|
+
# to the updated tool call. See:
|
|
111
|
+
# tests/experiments/cline-prompt-regression-tests/cline_prompt_test.py
|
|
112
|
+
|
|
95
113
|
log = structlog.get_logger(__name__)
|
|
96
114
|
|
|
97
115
|
log.debug(
|
|
@@ -112,13 +130,16 @@ async def search(
|
|
|
112
130
|
|
|
113
131
|
log.debug("Creating embedding service")
|
|
114
132
|
embedding_service = embedding_factory(
|
|
115
|
-
mcp_context.app_context.
|
|
133
|
+
app_context=mcp_context.app_context, session=mcp_context.session
|
|
116
134
|
)
|
|
117
135
|
|
|
118
136
|
log.debug("Creating search service")
|
|
119
137
|
search_service = SearchService(
|
|
120
138
|
repository=search_repository,
|
|
121
|
-
|
|
139
|
+
keyword_search_provider=keyword_search_factory(
|
|
140
|
+
app_context=mcp_context.app_context,
|
|
141
|
+
session=mcp_context.session,
|
|
142
|
+
),
|
|
122
143
|
embedding_service=embedding_service,
|
|
123
144
|
)
|
|
124
145
|
|
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import TypeVar
|
|
4
4
|
|
|
5
|
-
import numpy as np
|
|
6
5
|
from sqlalchemy import (
|
|
7
6
|
select,
|
|
8
7
|
)
|
|
9
8
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
10
9
|
|
|
11
|
-
from kodit.embedding.embedding_models import Embedding, EmbeddingType
|
|
12
10
|
from kodit.indexing.indexing_models import Snippet
|
|
13
11
|
from kodit.source.source_models import File
|
|
14
12
|
|
|
@@ -57,122 +55,3 @@ class SearchRepository:
|
|
|
57
55
|
|
|
58
56
|
# Return results in the same order as input IDs
|
|
59
57
|
return [id_to_result[i] for i in ids]
|
|
60
|
-
|
|
61
|
-
async def list_semantic_results(
|
|
62
|
-
self, embedding_type: EmbeddingType, embedding: list[float], top_k: int = 10
|
|
63
|
-
) -> list[tuple[int, float]]:
|
|
64
|
-
"""List semantic results using cosine similarity.
|
|
65
|
-
|
|
66
|
-
This implementation fetches all embeddings of the given type and computes
|
|
67
|
-
cosine similarity in Python using NumPy for better performance.
|
|
68
|
-
|
|
69
|
-
Args:
|
|
70
|
-
embedding_type: The type of embeddings to search
|
|
71
|
-
embedding: The query embedding vector
|
|
72
|
-
top_k: Number of results to return
|
|
73
|
-
|
|
74
|
-
Returns:
|
|
75
|
-
List of (snippet_id, similarity_score) tuples, sorted by similarity
|
|
76
|
-
|
|
77
|
-
"""
|
|
78
|
-
# Step 1: Fetch embeddings from database
|
|
79
|
-
embeddings = await self._list_embedding_values(embedding_type)
|
|
80
|
-
if not embeddings:
|
|
81
|
-
return []
|
|
82
|
-
|
|
83
|
-
# Step 2: Convert to numpy arrays
|
|
84
|
-
stored_vecs, query_vec = self._prepare_vectors(embeddings, embedding)
|
|
85
|
-
|
|
86
|
-
# Step 3: Compute similarities
|
|
87
|
-
similarities = self._compute_similarities(stored_vecs, query_vec)
|
|
88
|
-
|
|
89
|
-
# Step 4: Get top-k results
|
|
90
|
-
return self._get_top_k_results(similarities, embeddings, top_k)
|
|
91
|
-
|
|
92
|
-
async def _list_embedding_values(
|
|
93
|
-
self, embedding_type: EmbeddingType
|
|
94
|
-
) -> list[tuple[int, list[float]]]:
|
|
95
|
-
"""List all embeddings of a given type from the database.
|
|
96
|
-
|
|
97
|
-
Args:
|
|
98
|
-
embedding_type: The type of embeddings to fetch
|
|
99
|
-
|
|
100
|
-
Returns:
|
|
101
|
-
List of (snippet_id, embedding) tuples
|
|
102
|
-
|
|
103
|
-
"""
|
|
104
|
-
# Only select the fields we need and use a more efficient query
|
|
105
|
-
query = select(Embedding.snippet_id, Embedding.embedding).where(
|
|
106
|
-
Embedding.type == embedding_type
|
|
107
|
-
)
|
|
108
|
-
rows = await self.session.execute(query)
|
|
109
|
-
return [tuple(row) for row in rows.all()] # Convert Row objects to tuples
|
|
110
|
-
|
|
111
|
-
def _prepare_vectors(
|
|
112
|
-
self, embeddings: list[tuple[int, list[float]]], query_embedding: list[float]
|
|
113
|
-
) -> tuple[np.ndarray, np.ndarray]:
|
|
114
|
-
"""Convert embeddings to numpy arrays.
|
|
115
|
-
|
|
116
|
-
Args:
|
|
117
|
-
embeddings: List of (snippet_id, embedding) tuples
|
|
118
|
-
query_embedding: Query embedding vector
|
|
119
|
-
|
|
120
|
-
Returns:
|
|
121
|
-
Tuple of (stored_vectors, query_vector) as numpy arrays
|
|
122
|
-
|
|
123
|
-
"""
|
|
124
|
-
try:
|
|
125
|
-
stored_vecs = np.array(
|
|
126
|
-
[emb[1] for emb in embeddings]
|
|
127
|
-
) # Use index 1 to get embedding
|
|
128
|
-
except ValueError as e:
|
|
129
|
-
if "inhomogeneous" in str(e):
|
|
130
|
-
msg = (
|
|
131
|
-
"The database has returned embeddings of different sizes. If you"
|
|
132
|
-
"have recently updated the embedding model, you will need to"
|
|
133
|
-
"delete your database and re-index your snippets."
|
|
134
|
-
)
|
|
135
|
-
raise ValueError(msg) from e
|
|
136
|
-
raise
|
|
137
|
-
|
|
138
|
-
query_vec = np.array(query_embedding)
|
|
139
|
-
return stored_vecs, query_vec
|
|
140
|
-
|
|
141
|
-
def _compute_similarities(
|
|
142
|
-
self, stored_vecs: np.ndarray, query_vec: np.ndarray
|
|
143
|
-
) -> np.ndarray:
|
|
144
|
-
"""Compute cosine similarities between stored vectors and query vector.
|
|
145
|
-
|
|
146
|
-
Args:
|
|
147
|
-
stored_vecs: Array of stored embedding vectors
|
|
148
|
-
query_vec: Query embedding vector
|
|
149
|
-
|
|
150
|
-
Returns:
|
|
151
|
-
Array of similarity scores
|
|
152
|
-
|
|
153
|
-
"""
|
|
154
|
-
stored_norms = np.linalg.norm(stored_vecs, axis=1)
|
|
155
|
-
query_norm = np.linalg.norm(query_vec)
|
|
156
|
-
return np.dot(stored_vecs, query_vec) / (stored_norms * query_norm)
|
|
157
|
-
|
|
158
|
-
def _get_top_k_results(
|
|
159
|
-
self,
|
|
160
|
-
similarities: np.ndarray,
|
|
161
|
-
embeddings: list[tuple[int, list[float]]],
|
|
162
|
-
top_k: int,
|
|
163
|
-
) -> list[tuple[int, float]]:
|
|
164
|
-
"""Get top-k results by similarity score.
|
|
165
|
-
|
|
166
|
-
Args:
|
|
167
|
-
similarities: Array of similarity scores
|
|
168
|
-
embeddings: List of (snippet_id, embedding) tuples
|
|
169
|
-
top_k: Number of results to return
|
|
170
|
-
|
|
171
|
-
Returns:
|
|
172
|
-
List of (snippet_id, similarity_score) tuples
|
|
173
|
-
|
|
174
|
-
"""
|
|
175
|
-
top_indices = np.argsort(similarities)[::-1][:top_k]
|
|
176
|
-
return [
|
|
177
|
-
(embeddings[i][0], float(similarities[i])) for i in top_indices
|
|
178
|
-
] # Use index 0 to get snippet_id
|
kodit/search/search_service.py
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
"""Search service."""
|
|
2
2
|
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
|
|
5
3
|
import pydantic
|
|
6
4
|
import structlog
|
|
7
5
|
|
|
8
|
-
from kodit.bm25.
|
|
9
|
-
from kodit.embedding.
|
|
10
|
-
from kodit.embedding.embedding_models import EmbeddingType
|
|
6
|
+
from kodit.bm25.keyword_search_service import BM25Result, KeywordSearchProvider
|
|
7
|
+
from kodit.embedding.vector_search_service import VectorSearchService
|
|
11
8
|
from kodit.search.search_repository import SearchRepository
|
|
12
9
|
|
|
13
10
|
|
|
@@ -44,25 +41,25 @@ class SearchService:
|
|
|
44
41
|
def __init__(
|
|
45
42
|
self,
|
|
46
43
|
repository: SearchRepository,
|
|
47
|
-
|
|
48
|
-
embedding_service:
|
|
44
|
+
keyword_search_provider: KeywordSearchProvider,
|
|
45
|
+
embedding_service: VectorSearchService,
|
|
49
46
|
) -> None:
|
|
50
47
|
"""Initialize the search service."""
|
|
51
48
|
self.repository = repository
|
|
52
49
|
self.log = structlog.get_logger(__name__)
|
|
53
|
-
self.
|
|
50
|
+
self.keyword_search_provider = keyword_search_provider
|
|
54
51
|
self.code_embedding_service = embedding_service
|
|
55
52
|
|
|
56
53
|
async def search(self, request: SearchRequest) -> list[SearchResult]:
|
|
57
54
|
"""Search for relevant data."""
|
|
58
55
|
fusion_list = []
|
|
59
56
|
if request.keywords:
|
|
60
|
-
snippet_ids = await self.repository.list_snippet_ids()
|
|
61
|
-
|
|
62
57
|
# Gather results for each keyword
|
|
63
|
-
result_ids: list[
|
|
58
|
+
result_ids: list[BM25Result] = []
|
|
64
59
|
for keyword in request.keywords:
|
|
65
|
-
results = self.
|
|
60
|
+
results = await self.keyword_search_provider.retrieve(
|
|
61
|
+
keyword, request.top_k
|
|
62
|
+
)
|
|
66
63
|
result_ids.extend(results)
|
|
67
64
|
|
|
68
65
|
# Sort results by score
|
|
@@ -76,19 +73,10 @@ class SearchService:
|
|
|
76
73
|
# Compute embedding for semantic query
|
|
77
74
|
semantic_results = []
|
|
78
75
|
if request.code_query:
|
|
79
|
-
query_embedding = await
|
|
80
|
-
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
query_results = await self.repository.list_semantic_results(
|
|
84
|
-
EmbeddingType.CODE, query_embedding, top_k=request.top_k
|
|
76
|
+
query_embedding = await self.code_embedding_service.retrieve(
|
|
77
|
+
request.code_query, top_k=request.top_k
|
|
85
78
|
)
|
|
86
|
-
|
|
87
|
-
# Sort results by score
|
|
88
|
-
query_results.sort(key=lambda x: x[1], reverse=True)
|
|
89
|
-
|
|
90
|
-
# Extract the snippet ids from the query results
|
|
91
|
-
semantic_results = [x[0] for x in query_results]
|
|
79
|
+
semantic_results = [x.snippet_id for x in query_embedding]
|
|
92
80
|
fusion_list.append(semantic_results)
|
|
93
81
|
|
|
94
82
|
if len(fusion_list) == 0:
|
kodit/source/source_service.py
CHANGED
|
@@ -109,6 +109,8 @@ class SourceService:
|
|
|
109
109
|
uri_or_path_like = uri_or_path_like + ".git"
|
|
110
110
|
try:
|
|
111
111
|
return await self._create_git_source(uri_or_path_like)
|
|
112
|
+
except git.GitCommandError:
|
|
113
|
+
raise
|
|
112
114
|
except ValueError:
|
|
113
115
|
pass
|
|
114
116
|
|
|
@@ -197,11 +199,14 @@ class SourceService:
|
|
|
197
199
|
clone_path.mkdir(parents=True, exist_ok=True)
|
|
198
200
|
|
|
199
201
|
try:
|
|
200
|
-
|
|
202
|
+
self.log.info("Cloning repository", uri=uri, clone_path=str(clone_path))
|
|
201
203
|
git.Repo.clone_from(uri, clone_path)
|
|
202
204
|
except git.GitCommandError as e:
|
|
203
|
-
|
|
204
|
-
|
|
205
|
+
if "already exists and is not an empty directory" in str(e):
|
|
206
|
+
self.log.info("Repository already exists, reusing...", uri=uri)
|
|
207
|
+
else:
|
|
208
|
+
msg = f"Failed to clone repository: {e}"
|
|
209
|
+
raise ValueError(msg) from e
|
|
205
210
|
|
|
206
211
|
source = await self.repository.create_source(
|
|
207
212
|
Source(uri=uri, cloned_path=str(clone_path)),
|
|
@@ -212,6 +217,7 @@ class SourceService:
|
|
|
212
217
|
file_count = sum(1 for _ in clone_path.rglob("*") if _.is_file())
|
|
213
218
|
|
|
214
219
|
# Process each file in the source directory
|
|
220
|
+
self.log.info("Inspecting files", source_id=source.id)
|
|
215
221
|
for path in tqdm(clone_path.rglob("*"), total=file_count, leave=False):
|
|
216
222
|
await self._process_file(source.id, path.absolute())
|
|
217
223
|
|
kodit/util/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Utility functions and classes."""
|
kodit/util/spinner.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Spinner for long-running tasks."""
|
|
2
|
+
|
|
3
|
+
import itertools
|
|
4
|
+
import sys
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Spinner:
|
|
10
|
+
"""Spinner for long-running tasks."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, delay: float = 0.1) -> None:
|
|
13
|
+
"""Initialize the spinner."""
|
|
14
|
+
self.spinner = itertools.cycle(["-", "/", "|", "\\"])
|
|
15
|
+
self.delay = delay
|
|
16
|
+
self.busy = False
|
|
17
|
+
self.spinner_visible = False
|
|
18
|
+
|
|
19
|
+
def write_next(self) -> None:
|
|
20
|
+
"""Write the next character of the spinner."""
|
|
21
|
+
with self._screen_lock:
|
|
22
|
+
if not self.spinner_visible:
|
|
23
|
+
sys.stdout.write(next(self.spinner))
|
|
24
|
+
self.spinner_visible = True
|
|
25
|
+
sys.stdout.flush()
|
|
26
|
+
|
|
27
|
+
def remove_spinner(self, cleanup: bool = False) -> None: # noqa: FBT001, FBT002
|
|
28
|
+
"""Remove the spinner."""
|
|
29
|
+
with self._screen_lock:
|
|
30
|
+
if self.spinner_visible:
|
|
31
|
+
sys.stdout.write("\b")
|
|
32
|
+
self.spinner_visible = False
|
|
33
|
+
if cleanup:
|
|
34
|
+
sys.stdout.write(" ") # overwrite spinner with blank
|
|
35
|
+
sys.stdout.write("\r") # move to next line
|
|
36
|
+
sys.stdout.flush()
|
|
37
|
+
|
|
38
|
+
def spinner_task(self) -> None:
|
|
39
|
+
"""Task that runs the spinner."""
|
|
40
|
+
while self.busy:
|
|
41
|
+
self.write_next()
|
|
42
|
+
time.sleep(self.delay)
|
|
43
|
+
self.remove_spinner()
|
|
44
|
+
|
|
45
|
+
def __enter__(self) -> None:
|
|
46
|
+
"""Enter the context manager."""
|
|
47
|
+
if sys.stdout.isatty():
|
|
48
|
+
self._screen_lock = threading.Lock()
|
|
49
|
+
self.busy = True
|
|
50
|
+
self.thread = threading.Thread(target=self.spinner_task)
|
|
51
|
+
self.thread.start()
|
|
52
|
+
|
|
53
|
+
def __exit__(self, exception: object, value: object, tb: object) -> None:
|
|
54
|
+
"""Exit the context manager."""
|
|
55
|
+
if sys.stdout.isatty():
|
|
56
|
+
self.busy = False
|
|
57
|
+
self.remove_spinner(cleanup=True)
|
|
58
|
+
else:
|
|
59
|
+
sys.stdout.write("\r")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kodit
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.15
|
|
4
4
|
Summary: Code indexing for better AI code generation
|
|
5
5
|
Project-URL: Homepage, https://docs.helixml.tech/kodit/
|
|
6
6
|
Project-URL: Documentation, https://docs.helixml.tech/kodit/
|
|
@@ -21,6 +21,7 @@ Requires-Dist: aiofiles>=24.1.0
|
|
|
21
21
|
Requires-Dist: aiosqlite>=0.20.0
|
|
22
22
|
Requires-Dist: alembic>=1.15.2
|
|
23
23
|
Requires-Dist: asgi-correlation-id>=4.3.4
|
|
24
|
+
Requires-Dist: asyncpg>=0.30.0
|
|
24
25
|
Requires-Dist: better-exceptions>=0.3.3
|
|
25
26
|
Requires-Dist: bm25s[core]>=0.2.12
|
|
26
27
|
Requires-Dist: click>=8.1.8
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
kodit/.gitignore,sha256=ztkjgRwL9Uud1OEi36hGQeDGk3OLK1NfDEO8YqGYy8o,11
|
|
2
|
+
kodit/__init__.py,sha256=aEKHYninUq1yh6jaNfvJBYg-6fenpN132nJt1UU6Jxs,59
|
|
3
|
+
kodit/_version.py,sha256=OX-WIjJlMaFvqRmCfLtOYEOYoiov9NdOA089N36rG-g,513
|
|
4
|
+
kodit/app.py,sha256=Mr5BFHOHx5zppwjC4XPWVvHjwgl1yrKbUjTWXKubJQM,891
|
|
5
|
+
kodit/cli.py,sha256=wAaMZQs-h6hyashWB3DBR2GIf496vfHmepcXhpa7-eM,8085
|
|
6
|
+
kodit/config.py,sha256=2W2u5J8j-Mbt-C4xzOuK-PeuDCx0S_rnCXPhBwvfLT4,4353
|
|
7
|
+
kodit/database.py,sha256=WB1KpVxUYPgiJGU0gJa2hqytYB8wJEJ5z3WayhWzNMU,2403
|
|
8
|
+
kodit/log.py,sha256=HU1OmuxO4FcVw61k4WW7Y4WM7BrDaeplw1PcBHhuIZY,5434
|
|
9
|
+
kodit/mcp.py,sha256=HA3R7YG0Al1A6MjSCSIi0hEGXG3WP7tix-N5AROasCM,5278
|
|
10
|
+
kodit/middleware.py,sha256=I6FOkqG9-8RH5kR1-0ZoQWfE4qLCB8lZYv8H_OCH29o,2714
|
|
11
|
+
kodit/bm25/__init__.py,sha256=j8zyriNWhbwE5Lbybzg1hQAhANlU9mKHWw4beeUR6og,19
|
|
12
|
+
kodit/bm25/keyword_search_factory.py,sha256=rp-wx3DJsc2KlELK1V337EyeYvmwnMQwUqOo1WVPSmg,631
|
|
13
|
+
kodit/bm25/keyword_search_service.py,sha256=aBbWQKgQmi2re3EIHdXFS00n7Wj3b2D0pZsLZ4qmHfE,754
|
|
14
|
+
kodit/bm25/local_bm25.py,sha256=AAbFhbQDqyL3d7jsPL7W4HsLxdoYctaDsREUXOLy6jM,3260
|
|
15
|
+
kodit/bm25/vectorchord_bm25.py,sha256=_nGrkUReYLLV-L8RIuIVLwjuhSYZl9T532n5OVf0kWs,6393
|
|
16
|
+
kodit/embedding/__init__.py,sha256=h9NXzDA1r-K23nvBajBV-RJzHJN0p3UJ7UQsmdnOoRw,24
|
|
17
|
+
kodit/embedding/embedding_factory.py,sha256=qzoxBS3scR-ABd-u9215uGES7c6clYy2DiKcSDQivnA,1603
|
|
18
|
+
kodit/embedding/embedding_models.py,sha256=rN90vSs86dYiqoawcp8E9jtwY31JoJXYfaDlsJK7uqc,656
|
|
19
|
+
kodit/embedding/embedding_repository.py,sha256=-ux3scpBzel8c0pMH9fNOEsSXFIzl-IfgaWrkTb1szo,6907
|
|
20
|
+
kodit/embedding/local_vector_search_service.py,sha256=hkF0qlfzjyGt400qIX9Mr6B7b7i8WvYIYWN2Z2C_pcs,1907
|
|
21
|
+
kodit/embedding/vector_search_service.py,sha256=pQJ129QjGrAWOXzqkywmgtDRpy8_gtzYgkivyqF9Vrs,1009
|
|
22
|
+
kodit/embedding/vectorchord_vector_search_service.py,sha256=OsVeM3gpoT8Ihzh-kEIzBm3xh_a4D-sErPvsQSKCME8,4732
|
|
23
|
+
kodit/embedding/embedding_provider/__init__.py,sha256=h9NXzDA1r-K23nvBajBV-RJzHJN0p3UJ7UQsmdnOoRw,24
|
|
24
|
+
kodit/embedding/embedding_provider/embedding_provider.py,sha256=NKs4nriup47R8xRciP07NE1-eZE9RPHklS7VH910UZ4,1537
|
|
25
|
+
kodit/embedding/embedding_provider/hash_embedding_provider.py,sha256=nAhlhh8j8PqqCCbhVl26Y8ntFBm2vJBCtB4X04g5Wwg,2638
|
|
26
|
+
kodit/embedding/embedding_provider/local_embedding_provider.py,sha256=4ER-UPq506Y0TWU6qcs0nUqw6bSKQkSrdog-DhNQWM8,1906
|
|
27
|
+
kodit/embedding/embedding_provider/openai_embedding_provider.py,sha256=bmUpegDgaF5Qj9uWcj1az4ADA2cKHUjraaMjGGPr83U,2076
|
|
28
|
+
kodit/indexing/__init__.py,sha256=cPyi2Iej3G1JFWlWr7X80_UrsMaTu5W5rBwgif1B3xo,75
|
|
29
|
+
kodit/indexing/indexing_models.py,sha256=6NX9HVcj6Pu9ePwHC7n-PWSyAgukpJq0nCNmUIigtbo,1282
|
|
30
|
+
kodit/indexing/indexing_repository.py,sha256=4RJ3zY8p6QxHrYW7dDjru_w94Eu19v2gQ4mdlTgcXvY,6331
|
|
31
|
+
kodit/indexing/indexing_service.py,sha256=T_dxOzNW_0OCpR4Fha1hHuNkmtLcDMZwL6t5xeu5VXQ,6613
|
|
32
|
+
kodit/migrations/README,sha256=ISVtAOvqvKk_5ThM5ioJE-lMkvf9IbknFUFVU_vPma4,58
|
|
33
|
+
kodit/migrations/__init__.py,sha256=lP5MuwlyWRMO6UcDWnQcQ3G-GYHcFb6rl9gYPHJ1sjo,40
|
|
34
|
+
kodit/migrations/env.py,sha256=w1M7OZh-ZeR2dPHS0ByXAUxQjfZQ8xIzMseWuzLDTWw,2469
|
|
35
|
+
kodit/migrations/script.py.mako,sha256=zWziKtiwYKEWuwPV_HBNHwa9LCT45_bi01-uSNFaOOE,703
|
|
36
|
+
kodit/migrations/versions/7c3bbc2ab32b_add_embeddings_table.py,sha256=-61qol9PfQKILCDQRA5jEaats9aGZs9Wdtp-j-38SF4,1644
|
|
37
|
+
kodit/migrations/versions/85155663351e_initial.py,sha256=Cg7zlF871o9ShV5rQMQ1v7hRV7fI59veDY9cjtTrs-8,3306
|
|
38
|
+
kodit/migrations/versions/__init__.py,sha256=9-lHzptItTzq_fomdIRBegQNm4Znx6pVjwD4MiqRIdo,36
|
|
39
|
+
kodit/search/__init__.py,sha256=4QbdjbrlhNKMovmuKHxJnUeZT7KNjTTFU0GdnuwUHdQ,36
|
|
40
|
+
kodit/search/search_repository.py,sha256=6q0k7JMTM_7hPK2TSA30CykGbc5N16kCL7HTjlbai0w,1563
|
|
41
|
+
kodit/search/search_service.py,sha256=-XlbP_50e1dKFJ5jBvex5FjLnffW43LcwQV_SeYNFB0,3944
|
|
42
|
+
kodit/snippets/__init__.py,sha256=-2coNoCRjTixU9KcP6alpmt7zqf37tCRWH3D7FPJ8dg,48
|
|
43
|
+
kodit/snippets/method_snippets.py,sha256=EVHhSNWahAC5nSXv9fWVFJY2yq25goHdCSCuENC07F8,4145
|
|
44
|
+
kodit/snippets/snippets.py,sha256=mwN0bM1Msu8ZeEsUHyQ7tx3Hj3vZsm8G7Wu4eWSkLY8,1539
|
|
45
|
+
kodit/snippets/languages/__init__.py,sha256=Bj5KKZSls2MQ8ZY1S_nHg447MgGZW-2WZM-oq6vjwwA,1187
|
|
46
|
+
kodit/snippets/languages/csharp.scm,sha256=gbBN4RiV1FBuTJF6orSnDFi8H9JwTw-d4piLJYsWUsc,222
|
|
47
|
+
kodit/snippets/languages/python.scm,sha256=ee85R9PBzwye3IMTE7-iVoKWd_ViU3EJISTyrFGrVeo,429
|
|
48
|
+
kodit/source/__init__.py,sha256=1NTZyPdjThVQpZO1Mp1ColVsS7sqYanOVLqnoqV9Ipo,83
|
|
49
|
+
kodit/source/source_models.py,sha256=xb42CaNDO1CUB8SIW-xXMrB6Ji8cFw-yeJ550xBEg9Q,2398
|
|
50
|
+
kodit/source/source_repository.py,sha256=0EksMpoLzdkfe8S4eeCm4Sf7TuxsOzOzaF4BBsMYo-4,3163
|
|
51
|
+
kodit/source/source_service.py,sha256=u_GaH07ewakThQJRfT8O_yZ54A52qLtJuM1bF3xUT2A,9633
|
|
52
|
+
kodit/util/__init__.py,sha256=bPu6CtqDWCRGU7VgW2_aiQrCBi8G89FS6k1PjvDajJ0,37
|
|
53
|
+
kodit/util/spinner.py,sha256=R9bzrHtBiIH6IfLbmsIVHL53s8vg-tqW4lwGGALu4dw,1932
|
|
54
|
+
kodit-0.1.15.dist-info/METADATA,sha256=8E-bw8L-Df5Hdt16R5IWkyw7uUAr13CwYfcEyFExaPw,2380
|
|
55
|
+
kodit-0.1.15.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
56
|
+
kodit-0.1.15.dist-info/entry_points.txt,sha256=hoTn-1aKyTItjnY91fnO-rV5uaWQLQ-Vi7V5et2IbHY,40
|
|
57
|
+
kodit-0.1.15.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
58
|
+
kodit-0.1.15.dist-info/RECORD,,
|