kodit 0.2.5__py3-none-any.whl → 0.2.6__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/application/services/indexing_application_service.py +26 -2
- kodit/application/services/snippet_application_service.py +6 -0
- kodit/cli.py +14 -1
- kodit/domain/errors.py +5 -0
- kodit/domain/services/source_service.py +5 -3
- kodit/infrastructure/cloning/folder/factory.py +10 -1
- kodit/infrastructure/cloning/git/factory.py +15 -1
- kodit/infrastructure/indexing/index_repository.py +0 -5
- kodit/infrastructure/indexing/indexing_factory.py +2 -0
- kodit/infrastructure/sqlalchemy/embedding_repository.py +0 -2
- kodit/infrastructure/sqlalchemy/file_repository.py +19 -14
- kodit/infrastructure/sqlalchemy/repository.py +51 -39
- kodit/infrastructure/sqlalchemy/snippet_repository.py +15 -11
- kodit/mcp.py +1 -0
- {kodit-0.2.5.dist-info → kodit-0.2.6.dist-info}/METADATA +1 -1
- {kodit-0.2.5.dist-info → kodit-0.2.6.dist-info}/RECORD +20 -19
- {kodit-0.2.5.dist-info → kodit-0.2.6.dist-info}/WHEEL +0 -0
- {kodit-0.2.5.dist-info → kodit-0.2.6.dist-info}/entry_points.txt +0 -0
- {kodit-0.2.5.dist-info → kodit-0.2.6.dist-info}/licenses/LICENSE +0 -0
kodit/_version.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Application service for indexing operations."""
|
|
2
2
|
|
|
3
3
|
import structlog
|
|
4
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
4
5
|
|
|
5
6
|
from kodit.application.commands.snippet_commands import CreateIndexSnippetsCommand
|
|
6
7
|
from kodit.application.services.snippet_application_service import (
|
|
@@ -8,6 +9,7 @@ from kodit.application.services.snippet_application_service import (
|
|
|
8
9
|
)
|
|
9
10
|
from kodit.domain.entities import Snippet
|
|
10
11
|
from kodit.domain.enums import SnippetExtractionStrategy
|
|
12
|
+
from kodit.domain.errors import EmptySourceError
|
|
11
13
|
from kodit.domain.interfaces import ProgressCallback
|
|
12
14
|
from kodit.domain.services.bm25_service import BM25DomainService
|
|
13
15
|
from kodit.domain.services.embedding_service import EmbeddingDomainService
|
|
@@ -51,6 +53,7 @@ class IndexingApplicationService:
|
|
|
51
53
|
text_search_service: EmbeddingDomainService,
|
|
52
54
|
enrichment_service: EnrichmentDomainService,
|
|
53
55
|
snippet_application_service: SnippetApplicationService,
|
|
56
|
+
session: AsyncSession,
|
|
54
57
|
) -> None:
|
|
55
58
|
"""Initialize the indexing application service.
|
|
56
59
|
|
|
@@ -62,11 +65,13 @@ class IndexingApplicationService:
|
|
|
62
65
|
text_search_service: The text search domain service.
|
|
63
66
|
enrichment_service: The enrichment domain service.
|
|
64
67
|
snippet_application_service: The snippet application service.
|
|
68
|
+
session: The database session for transaction management.
|
|
65
69
|
|
|
66
70
|
"""
|
|
67
71
|
self.indexing_domain_service = indexing_domain_service
|
|
68
72
|
self.source_service = source_service
|
|
69
73
|
self.snippet_application_service = snippet_application_service
|
|
74
|
+
self.session = session
|
|
70
75
|
self.log = structlog.get_logger(__name__)
|
|
71
76
|
self.bm25_service = bm25_service
|
|
72
77
|
self.code_search_service = code_search_service
|
|
@@ -93,7 +98,12 @@ class IndexingApplicationService:
|
|
|
93
98
|
|
|
94
99
|
# Create the index
|
|
95
100
|
request = IndexCreateRequest(source_id=source.id)
|
|
96
|
-
|
|
101
|
+
index_view = await self.indexing_domain_service.create_index(request)
|
|
102
|
+
|
|
103
|
+
# Commit the index creation
|
|
104
|
+
await self.session.commit()
|
|
105
|
+
|
|
106
|
+
return index_view
|
|
97
107
|
|
|
98
108
|
async def list_indexes(self) -> list[IndexView]:
|
|
99
109
|
"""List all available indexes with their details.
|
|
@@ -125,7 +135,7 @@ class IndexingApplicationService:
|
|
|
125
135
|
progress_callback: Optional progress callback for reporting progress.
|
|
126
136
|
|
|
127
137
|
Raises:
|
|
128
|
-
ValueError: If the index doesn't exist.
|
|
138
|
+
ValueError: If the index doesn't exist or no indexable snippets are found.
|
|
129
139
|
|
|
130
140
|
"""
|
|
131
141
|
log_event("kodit.index.run")
|
|
@@ -138,8 +148,11 @@ class IndexingApplicationService:
|
|
|
138
148
|
|
|
139
149
|
# Delete old snippets so we don't duplicate
|
|
140
150
|
await self.indexing_domain_service.delete_all_snippets(index.id)
|
|
151
|
+
# Commit the deletion
|
|
152
|
+
await self.session.commit()
|
|
141
153
|
|
|
142
154
|
# Create snippets for supported file types using the snippet application service
|
|
155
|
+
# (snippet_application_service handles its own commits)
|
|
143
156
|
self.log.info("Creating snippets for files", index_id=index.id)
|
|
144
157
|
command = CreateIndexSnippetsCommand(
|
|
145
158
|
index_id=index.id, strategy=SnippetExtractionStrategy.METHOD_BASED
|
|
@@ -150,6 +163,11 @@ class IndexingApplicationService:
|
|
|
150
163
|
|
|
151
164
|
snippets = await self.indexing_domain_service.get_snippets_for_index(index.id)
|
|
152
165
|
|
|
166
|
+
# Check if any snippets were extracted
|
|
167
|
+
if not snippets:
|
|
168
|
+
msg = f"No indexable snippets found for index {index.id}"
|
|
169
|
+
raise EmptySourceError(msg)
|
|
170
|
+
|
|
153
171
|
# Create BM25 index
|
|
154
172
|
self.log.info("Creating keyword index")
|
|
155
173
|
reporter = Reporter(self.log, progress_callback)
|
|
@@ -184,6 +202,8 @@ class IndexingApplicationService:
|
|
|
184
202
|
|
|
185
203
|
# Update index timestamp
|
|
186
204
|
await self.indexing_domain_service.update_index_timestamp(index.id)
|
|
205
|
+
# Commit the timestamp update
|
|
206
|
+
await self.session.commit()
|
|
187
207
|
|
|
188
208
|
async def _create_bm25_index(
|
|
189
209
|
self, snippets: list[Snippet], progress_callback: ProgressCallback | None = None
|
|
@@ -264,6 +284,10 @@ class IndexingApplicationService:
|
|
|
264
284
|
"enrichment", processed, len(snippets), "Enriching snippets..."
|
|
265
285
|
)
|
|
266
286
|
|
|
287
|
+
# Commit all snippet content updates as a single transaction
|
|
288
|
+
if enriched_contents:
|
|
289
|
+
await self.session.commit()
|
|
290
|
+
|
|
267
291
|
await reporter.done("enrichment")
|
|
268
292
|
|
|
269
293
|
async def _create_text_embeddings(
|
|
@@ -4,6 +4,7 @@ from pathlib import Path
|
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
6
|
import structlog
|
|
7
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
7
8
|
|
|
8
9
|
from kodit.application.commands.snippet_commands import (
|
|
9
10
|
CreateIndexSnippetsCommand,
|
|
@@ -28,6 +29,7 @@ class SnippetApplicationService:
|
|
|
28
29
|
snippet_extraction_service: SnippetExtractionDomainService,
|
|
29
30
|
snippet_repository: SnippetRepository,
|
|
30
31
|
file_repository: FileRepository,
|
|
32
|
+
session: AsyncSession,
|
|
31
33
|
) -> None:
|
|
32
34
|
"""Initialize the snippet application service.
|
|
33
35
|
|
|
@@ -35,11 +37,13 @@ class SnippetApplicationService:
|
|
|
35
37
|
snippet_extraction_service: Domain service for snippet extraction
|
|
36
38
|
snippet_repository: Repository for snippet persistence
|
|
37
39
|
file_repository: Repository for file operations
|
|
40
|
+
session: The database session for transaction management
|
|
38
41
|
|
|
39
42
|
"""
|
|
40
43
|
self.snippet_extraction_service = snippet_extraction_service
|
|
41
44
|
self.snippet_repository = snippet_repository
|
|
42
45
|
self.file_repository = file_repository
|
|
46
|
+
self.session = session
|
|
43
47
|
self.log = structlog.get_logger(__name__)
|
|
44
48
|
|
|
45
49
|
async def extract_snippets_from_file(
|
|
@@ -140,4 +144,6 @@ class SnippetApplicationService:
|
|
|
140
144
|
message=f"Processing {file.cloned_path}...",
|
|
141
145
|
)
|
|
142
146
|
|
|
147
|
+
# Commit all snippet creations in a single transaction
|
|
148
|
+
await self.session.commit()
|
|
143
149
|
await reporter.done("create_snippets")
|
kodit/cli.py
CHANGED
|
@@ -19,6 +19,7 @@ from kodit.config import (
|
|
|
19
19
|
with_app_context,
|
|
20
20
|
with_session,
|
|
21
21
|
)
|
|
22
|
+
from kodit.domain.errors import EmptySourceError
|
|
22
23
|
from kodit.domain.services.source_service import SourceService
|
|
23
24
|
from kodit.domain.value_objects import MultiSearchRequest
|
|
24
25
|
from kodit.infrastructure.indexing.indexing_factory import (
|
|
@@ -58,6 +59,7 @@ def create_snippet_application_service(
|
|
|
58
59
|
snippet_extraction_service=snippet_extraction_service,
|
|
59
60
|
snippet_repository=snippet_repository,
|
|
60
61
|
file_repository=file_repository,
|
|
62
|
+
session=session,
|
|
61
63
|
)
|
|
62
64
|
|
|
63
65
|
|
|
@@ -100,6 +102,7 @@ async def index(
|
|
|
100
102
|
sources: list[str],
|
|
101
103
|
) -> None:
|
|
102
104
|
"""List indexes, or index data sources."""
|
|
105
|
+
log = structlog.get_logger(__name__)
|
|
103
106
|
source_service = SourceService(
|
|
104
107
|
clone_dir=app_context.get_clone_dir(),
|
|
105
108
|
session_factory=lambda: session,
|
|
@@ -152,7 +155,17 @@ async def index(
|
|
|
152
155
|
|
|
153
156
|
# Create a new progress callback for the indexing operations
|
|
154
157
|
indexing_progress_callback = create_multi_stage_progress_callback()
|
|
155
|
-
|
|
158
|
+
try:
|
|
159
|
+
await service.run_index(index.id, indexing_progress_callback)
|
|
160
|
+
except EmptySourceError as e:
|
|
161
|
+
log.exception("Empty source error", error=e)
|
|
162
|
+
msg = f"""{e}. This could mean:
|
|
163
|
+
• The repository contains no supported file types
|
|
164
|
+
• All files are excluded by ignore patterns
|
|
165
|
+
• The files contain no extractable code snippets
|
|
166
|
+
Please check the repository contents and try again.
|
|
167
|
+
"""
|
|
168
|
+
click.echo(msg)
|
|
156
169
|
|
|
157
170
|
|
|
158
171
|
@cli.group()
|
kodit/domain/errors.py
ADDED
|
@@ -49,7 +49,7 @@ class SourceService:
|
|
|
49
49
|
"""Create a source."""
|
|
50
50
|
async with self._session_factory() as session:
|
|
51
51
|
repo = SqlAlchemySourceRepository(session)
|
|
52
|
-
git_factory, folder_factory = self._build_factories(repo)
|
|
52
|
+
git_factory, folder_factory = self._build_factories(repo, session)
|
|
53
53
|
|
|
54
54
|
if is_valid_clone_target(uri_or_path_like):
|
|
55
55
|
source = await git_factory.create(uri_or_path_like, progress_callback)
|
|
@@ -60,17 +60,18 @@ class SourceService:
|
|
|
60
60
|
else:
|
|
61
61
|
raise ValueError(f"Unsupported source: {uri_or_path_like}")
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
# Factories handle their own commits now
|
|
64
64
|
return source
|
|
65
65
|
|
|
66
66
|
def _build_factories(
|
|
67
|
-
self, repository: SourceRepository
|
|
67
|
+
self, repository: SourceRepository, session: AsyncSession
|
|
68
68
|
) -> tuple[GitSourceFactory, FolderSourceFactory]:
|
|
69
69
|
# Git-specific collaborators
|
|
70
70
|
git_wc = GitWorkingCopyProvider(self.clone_dir)
|
|
71
71
|
git_factory = GitSourceFactory(
|
|
72
72
|
repository=repository,
|
|
73
73
|
working_copy=git_wc,
|
|
74
|
+
session=session,
|
|
74
75
|
)
|
|
75
76
|
|
|
76
77
|
# Folder-specific collaborators
|
|
@@ -78,6 +79,7 @@ class SourceService:
|
|
|
78
79
|
folder_factory = FolderSourceFactory(
|
|
79
80
|
repository=repository,
|
|
80
81
|
working_copy=fold_wc,
|
|
82
|
+
session=session,
|
|
81
83
|
)
|
|
82
84
|
|
|
83
85
|
return git_factory, folder_factory
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
5
|
import structlog
|
|
6
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
6
7
|
|
|
7
8
|
from kodit.domain.entities import AuthorFileMapping, Source, SourceType
|
|
8
9
|
from kodit.domain.interfaces import NullProgressCallback, ProgressCallback
|
|
@@ -22,6 +23,7 @@ class FolderSourceFactory:
|
|
|
22
23
|
self,
|
|
23
24
|
repository: SourceRepository,
|
|
24
25
|
working_copy: FolderWorkingCopyProvider,
|
|
26
|
+
session: AsyncSession,
|
|
25
27
|
) -> None:
|
|
26
28
|
"""Initialize the source factory."""
|
|
27
29
|
self.log = structlog.get_logger(__name__)
|
|
@@ -29,6 +31,7 @@ class FolderSourceFactory:
|
|
|
29
31
|
self.working_copy = working_copy
|
|
30
32
|
self.metadata_extractor = FolderFileMetadataExtractor()
|
|
31
33
|
self.author_extractor = NoOpAuthorExtractor()
|
|
34
|
+
self.session = session
|
|
32
35
|
|
|
33
36
|
async def create(
|
|
34
37
|
self, uri: str, progress_callback: ProgressCallback | None = None
|
|
@@ -55,7 +58,7 @@ class FolderSourceFactory:
|
|
|
55
58
|
clone_path = await self.working_copy.prepare(directory.as_uri())
|
|
56
59
|
|
|
57
60
|
# Create source record
|
|
58
|
-
source = await self.repository.
|
|
61
|
+
source = await self.repository.save(
|
|
59
62
|
Source(
|
|
60
63
|
uri=directory.as_uri(),
|
|
61
64
|
cloned_path=str(clone_path),
|
|
@@ -63,12 +66,18 @@ class FolderSourceFactory:
|
|
|
63
66
|
)
|
|
64
67
|
)
|
|
65
68
|
|
|
69
|
+
# Commit source creation so we get an ID for foreign key relationships
|
|
70
|
+
await self.session.commit()
|
|
71
|
+
|
|
66
72
|
# Get all files to process
|
|
67
73
|
files = [f for f in clone_path.rglob("*") if f.is_file()]
|
|
68
74
|
|
|
69
75
|
# Process files
|
|
70
76
|
await self._process_files(source, files, progress_callback)
|
|
71
77
|
|
|
78
|
+
# Commit file processing
|
|
79
|
+
await self.session.commit()
|
|
80
|
+
|
|
72
81
|
return source
|
|
73
82
|
|
|
74
83
|
async def _process_files(
|
|
@@ -5,6 +5,7 @@ from pathlib import Path
|
|
|
5
5
|
|
|
6
6
|
import git
|
|
7
7
|
import structlog
|
|
8
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
8
9
|
|
|
9
10
|
from kodit.domain.entities import AuthorFileMapping, Source, SourceType
|
|
10
11
|
from kodit.domain.interfaces import NullProgressCallback, ProgressCallback
|
|
@@ -26,6 +27,7 @@ class GitSourceFactory:
|
|
|
26
27
|
self,
|
|
27
28
|
repository: SourceRepository,
|
|
28
29
|
working_copy: GitWorkingCopyProvider,
|
|
30
|
+
session: AsyncSession,
|
|
29
31
|
) -> None:
|
|
30
32
|
"""Initialize the source factory."""
|
|
31
33
|
self.log = structlog.get_logger(__name__)
|
|
@@ -33,6 +35,7 @@ class GitSourceFactory:
|
|
|
33
35
|
self.working_copy = working_copy
|
|
34
36
|
self.metadata_extractor = GitFileMetadataExtractor()
|
|
35
37
|
self.author_extractor = GitAuthorExtractor(repository)
|
|
38
|
+
self.session = session
|
|
36
39
|
|
|
37
40
|
async def create(
|
|
38
41
|
self, uri: str, progress_callback: ProgressCallback | None = None
|
|
@@ -62,7 +65,7 @@ class GitSourceFactory:
|
|
|
62
65
|
|
|
63
66
|
# Create source record
|
|
64
67
|
self.log.debug("Creating source", uri=uri, clone_path=str(clone_path))
|
|
65
|
-
source = await self.repository.
|
|
68
|
+
source = await self.repository.save(
|
|
66
69
|
Source(
|
|
67
70
|
uri=uri,
|
|
68
71
|
cloned_path=str(clone_path),
|
|
@@ -70,6 +73,9 @@ class GitSourceFactory:
|
|
|
70
73
|
)
|
|
71
74
|
)
|
|
72
75
|
|
|
76
|
+
# Commit source creation so we get an ID for foreign key relationships
|
|
77
|
+
await self.session.commit()
|
|
78
|
+
|
|
73
79
|
# Get files to process using ignore patterns
|
|
74
80
|
ignore_provider = GitIgnorePatternProvider(clone_path)
|
|
75
81
|
ignore_service = IgnoreService(ignore_provider)
|
|
@@ -83,6 +89,9 @@ class GitSourceFactory:
|
|
|
83
89
|
self.log.info("Inspecting files", source_id=source.id, num_files=len(files))
|
|
84
90
|
await self._process_files(source, files, progress_callback)
|
|
85
91
|
|
|
92
|
+
# Commit file processing
|
|
93
|
+
await self.session.commit()
|
|
94
|
+
|
|
86
95
|
return source
|
|
87
96
|
|
|
88
97
|
async def _process_files(
|
|
@@ -111,6 +120,11 @@ class GitSourceFactory:
|
|
|
111
120
|
|
|
112
121
|
# Extract authors
|
|
113
122
|
authors = await self.author_extractor.extract(path, source)
|
|
123
|
+
|
|
124
|
+
# Commit authors so they get IDs before creating mappings
|
|
125
|
+
if authors:
|
|
126
|
+
await self.session.commit()
|
|
127
|
+
|
|
114
128
|
for author in authors:
|
|
115
129
|
await self.repository.upsert_author_file_mapping(
|
|
116
130
|
AuthorFileMapping(
|
|
@@ -42,7 +42,6 @@ class SQLAlchemyIndexRepository(IndexRepository):
|
|
|
42
42
|
|
|
43
43
|
index = Index(source_id=source_id)
|
|
44
44
|
self.session.add(index)
|
|
45
|
-
await self.session.commit()
|
|
46
45
|
|
|
47
46
|
# Get source for the view
|
|
48
47
|
source_query = select(Source).where(Source.id == source_id)
|
|
@@ -158,7 +157,6 @@ class SQLAlchemyIndexRepository(IndexRepository):
|
|
|
158
157
|
|
|
159
158
|
if index:
|
|
160
159
|
index.updated_at = datetime.now(UTC)
|
|
161
|
-
await self.session.commit()
|
|
162
160
|
|
|
163
161
|
async def delete_all_snippets(self, index_id: int) -> None:
|
|
164
162
|
"""Delete all snippets for an index.
|
|
@@ -178,7 +176,6 @@ class SQLAlchemyIndexRepository(IndexRepository):
|
|
|
178
176
|
# Now delete the snippets
|
|
179
177
|
query = delete(Snippet).where(Snippet.index_id == index_id)
|
|
180
178
|
await self.session.execute(query)
|
|
181
|
-
await self.session.commit()
|
|
182
179
|
|
|
183
180
|
async def get_snippets_for_index(self, index_id: int) -> list[Snippet]:
|
|
184
181
|
"""Get all snippets for an index.
|
|
@@ -207,7 +204,6 @@ class SQLAlchemyIndexRepository(IndexRepository):
|
|
|
207
204
|
content=snippet["content"],
|
|
208
205
|
)
|
|
209
206
|
self.session.add(db_snippet)
|
|
210
|
-
await self.session.commit()
|
|
211
207
|
|
|
212
208
|
async def update_snippet_content(self, snippet_id: int, content: str) -> None:
|
|
213
209
|
"""Update the content of an existing snippet.
|
|
@@ -224,7 +220,6 @@ class SQLAlchemyIndexRepository(IndexRepository):
|
|
|
224
220
|
if snippet:
|
|
225
221
|
snippet.content = content
|
|
226
222
|
# SQLAlchemy will automatically track this change
|
|
227
|
-
await self.session.commit()
|
|
228
223
|
|
|
229
224
|
async def list_snippets_by_ids(self, ids: list[int]) -> list[tuple[dict, dict]]:
|
|
230
225
|
"""List snippets by IDs.
|
|
@@ -53,6 +53,7 @@ def create_snippet_application_service(
|
|
|
53
53
|
snippet_extraction_service=snippet_extraction_service,
|
|
54
54
|
snippet_repository=snippet_repository,
|
|
55
55
|
file_repository=file_repository,
|
|
56
|
+
session=session,
|
|
56
57
|
)
|
|
57
58
|
|
|
58
59
|
|
|
@@ -108,4 +109,5 @@ def create_indexing_application_service(
|
|
|
108
109
|
text_search_service=text_search_service,
|
|
109
110
|
enrichment_service=enrichment_service,
|
|
110
111
|
snippet_application_service=snippet_application_service,
|
|
112
|
+
session=session,
|
|
111
113
|
)
|
|
@@ -30,7 +30,6 @@ class SqlAlchemyEmbeddingRepository:
|
|
|
30
30
|
|
|
31
31
|
"""
|
|
32
32
|
self.session.add(embedding)
|
|
33
|
-
await self.session.commit()
|
|
34
33
|
return embedding
|
|
35
34
|
|
|
36
35
|
async def get_embedding_by_snippet_id_and_type(
|
|
@@ -81,7 +80,6 @@ class SqlAlchemyEmbeddingRepository:
|
|
|
81
80
|
embeddings = result.scalars().all()
|
|
82
81
|
for embedding in embeddings:
|
|
83
82
|
await self.session.delete(embedding)
|
|
84
|
-
await self.session.commit()
|
|
85
83
|
|
|
86
84
|
async def list_semantic_results(
|
|
87
85
|
self, embedding_type: EmbeddingType, embedding: list[float], top_k: int = 10
|
|
@@ -21,6 +21,25 @@ class SqlAlchemyFileRepository(FileRepository):
|
|
|
21
21
|
"""
|
|
22
22
|
self.session = session
|
|
23
23
|
|
|
24
|
+
async def get(self, id: int) -> File | None: # noqa: A002
|
|
25
|
+
"""Get a file by ID."""
|
|
26
|
+
return await self.session.get(File, id)
|
|
27
|
+
|
|
28
|
+
async def save(self, entity: File) -> File:
|
|
29
|
+
"""Save entity."""
|
|
30
|
+
self.session.add(entity)
|
|
31
|
+
return entity
|
|
32
|
+
|
|
33
|
+
async def delete(self, id: int) -> None: # noqa: A002
|
|
34
|
+
"""Delete entity by ID."""
|
|
35
|
+
file = await self.get(id)
|
|
36
|
+
if file:
|
|
37
|
+
await self.session.delete(file)
|
|
38
|
+
|
|
39
|
+
async def list(self) -> Sequence[File]:
|
|
40
|
+
"""List all entities."""
|
|
41
|
+
return (await self.session.scalars(select(File))).all()
|
|
42
|
+
|
|
24
43
|
async def get_files_for_index(self, index_id: int) -> Sequence[File]:
|
|
25
44
|
"""Get all files for an index.
|
|
26
45
|
|
|
@@ -57,17 +76,3 @@ class SqlAlchemyFileRepository(FileRepository):
|
|
|
57
76
|
query = select(File).where(File.id == file_id)
|
|
58
77
|
result = await self.session.execute(query)
|
|
59
78
|
return result.scalar_one_or_none()
|
|
60
|
-
|
|
61
|
-
async def save(self, file: File) -> File:
|
|
62
|
-
"""Save file using SQLAlchemy.
|
|
63
|
-
|
|
64
|
-
Args:
|
|
65
|
-
file: The file to save
|
|
66
|
-
|
|
67
|
-
Returns:
|
|
68
|
-
The saved file
|
|
69
|
-
|
|
70
|
-
"""
|
|
71
|
-
self.session.add(file)
|
|
72
|
-
await self.session.commit()
|
|
73
|
-
return file
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""SQLAlchemy repository."""
|
|
2
2
|
|
|
3
3
|
from collections.abc import Sequence
|
|
4
|
+
from typing import cast
|
|
4
5
|
|
|
5
6
|
from sqlalchemy import select
|
|
6
7
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
@@ -16,41 +17,43 @@ class SqlAlchemySourceRepository(SourceRepository):
|
|
|
16
17
|
"""Initialize the repository."""
|
|
17
18
|
self._session = session
|
|
18
19
|
|
|
19
|
-
async def get(self,
|
|
20
|
+
async def get(self, id: int) -> Source | None: # noqa: A002
|
|
20
21
|
"""Get a source by ID."""
|
|
21
|
-
return await self._session.get(Source,
|
|
22
|
+
return await self._session.get(Source, id)
|
|
23
|
+
|
|
24
|
+
async def save(self, entity: Source) -> Source:
|
|
25
|
+
"""Save entity."""
|
|
26
|
+
self._session.add(entity)
|
|
27
|
+
return entity
|
|
28
|
+
|
|
29
|
+
async def delete(self, id: int) -> None: # noqa: A002
|
|
30
|
+
"""Delete entity by ID."""
|
|
31
|
+
source = await self.get(id)
|
|
32
|
+
if source:
|
|
33
|
+
await self._session.delete(source)
|
|
34
|
+
|
|
35
|
+
async def list(self) -> Sequence[Source]:
|
|
36
|
+
"""List all entities."""
|
|
37
|
+
stmt = select(Source)
|
|
38
|
+
return (await self._session.scalars(stmt)).all()
|
|
22
39
|
|
|
23
40
|
async def get_by_uri(self, uri: str) -> Source | None:
|
|
24
41
|
"""Get a source by URI."""
|
|
25
42
|
stmt = select(Source).where(Source.uri == uri)
|
|
26
|
-
return await self._session.scalar(stmt)
|
|
43
|
+
return cast("Source | None", await self._session.scalar(stmt))
|
|
27
44
|
|
|
28
|
-
async def
|
|
29
|
-
|
|
45
|
+
async def list_by_type(
|
|
46
|
+
self, source_type: SourceType | None = None
|
|
47
|
+
) -> Sequence[Source]:
|
|
48
|
+
"""List sources by type."""
|
|
30
49
|
stmt = select(Source)
|
|
31
50
|
if source_type is not None:
|
|
32
51
|
stmt = stmt.where(Source.type == source_type)
|
|
33
52
|
return (await self._session.scalars(stmt)).all()
|
|
34
53
|
|
|
35
|
-
async def add(self, source: Source) -> None:
|
|
36
|
-
"""Add a source."""
|
|
37
|
-
self._session.add(source) # INSERT on flush
|
|
38
|
-
await self._session.flush() # Flush to get the ID
|
|
39
|
-
|
|
40
|
-
async def create_source(self, source: Source) -> Source:
|
|
41
|
-
"""Create a source and commit it."""
|
|
42
|
-
self._session.add(source)
|
|
43
|
-
await self._session.commit()
|
|
44
|
-
return source
|
|
45
|
-
|
|
46
|
-
async def remove(self, source: Source) -> None:
|
|
47
|
-
"""Remove a source."""
|
|
48
|
-
await self._session.delete(source) # DELETE on flush
|
|
49
|
-
|
|
50
54
|
async def create_file(self, file: File) -> File:
|
|
51
55
|
"""Create a new file record."""
|
|
52
56
|
self._session.add(file)
|
|
53
|
-
await self._session.commit()
|
|
54
57
|
return file
|
|
55
58
|
|
|
56
59
|
async def upsert_author(self, author: Author) -> Author:
|
|
@@ -59,14 +62,13 @@ class SqlAlchemySourceRepository(SourceRepository):
|
|
|
59
62
|
stmt = select(Author).where(
|
|
60
63
|
Author.name == author.name, Author.email == author.email
|
|
61
64
|
)
|
|
62
|
-
existing_author = await self._session.scalar(stmt)
|
|
65
|
+
existing_author = cast("Author | None", await self._session.scalar(stmt))
|
|
63
66
|
|
|
64
67
|
if existing_author:
|
|
65
68
|
return existing_author
|
|
66
69
|
|
|
67
70
|
# Author doesn't exist, create new one
|
|
68
71
|
self._session.add(author)
|
|
69
|
-
await self._session.commit()
|
|
70
72
|
return author
|
|
71
73
|
|
|
72
74
|
async def upsert_author_file_mapping(
|
|
@@ -78,14 +80,15 @@ class SqlAlchemySourceRepository(SourceRepository):
|
|
|
78
80
|
AuthorFileMapping.author_id == mapping.author_id,
|
|
79
81
|
AuthorFileMapping.file_id == mapping.file_id,
|
|
80
82
|
)
|
|
81
|
-
existing_mapping =
|
|
83
|
+
existing_mapping = cast(
|
|
84
|
+
"AuthorFileMapping | None", await self._session.scalar(stmt)
|
|
85
|
+
)
|
|
82
86
|
|
|
83
87
|
if existing_mapping:
|
|
84
88
|
return existing_mapping
|
|
85
89
|
|
|
86
90
|
# Mapping doesn't exist, create new one
|
|
87
91
|
self._session.add(mapping)
|
|
88
|
-
await self._session.commit()
|
|
89
92
|
return mapping
|
|
90
93
|
|
|
91
94
|
|
|
@@ -96,26 +99,35 @@ class SqlAlchemyAuthorRepository(AuthorRepository):
|
|
|
96
99
|
"""Initialize the repository."""
|
|
97
100
|
self._session = session
|
|
98
101
|
|
|
99
|
-
async def get(self,
|
|
102
|
+
async def get(self, id: int) -> Author | None: # noqa: A002
|
|
100
103
|
"""Get an author by ID."""
|
|
101
|
-
return await self._session.get(Author,
|
|
104
|
+
return await self._session.get(Author, id)
|
|
102
105
|
|
|
103
|
-
async def
|
|
104
|
-
"""
|
|
105
|
-
|
|
106
|
+
async def save(self, entity: Author) -> Author:
|
|
107
|
+
"""Save entity."""
|
|
108
|
+
self._session.add(entity)
|
|
109
|
+
return entity
|
|
106
110
|
|
|
107
|
-
async def
|
|
108
|
-
"""
|
|
109
|
-
|
|
111
|
+
async def delete(self, id: int) -> None: # noqa: A002
|
|
112
|
+
"""Delete entity by ID."""
|
|
113
|
+
author = await self.get(id)
|
|
114
|
+
if author:
|
|
115
|
+
await self._session.delete(author)
|
|
110
116
|
|
|
111
117
|
async def list(self) -> Sequence[Author]:
|
|
112
118
|
"""List authors."""
|
|
113
119
|
return (await self._session.scalars(select(Author))).all()
|
|
114
120
|
|
|
115
|
-
async def
|
|
116
|
-
"""
|
|
117
|
-
|
|
121
|
+
async def get_by_name(self, name: str) -> Author | None:
|
|
122
|
+
"""Get an author by name."""
|
|
123
|
+
return cast(
|
|
124
|
+
"Author | None",
|
|
125
|
+
await self._session.scalar(select(Author).where(Author.name == name)),
|
|
126
|
+
)
|
|
118
127
|
|
|
119
|
-
async def
|
|
120
|
-
"""
|
|
121
|
-
|
|
128
|
+
async def get_by_email(self, email: str) -> Author | None:
|
|
129
|
+
"""Get an author by email."""
|
|
130
|
+
return cast(
|
|
131
|
+
"Author | None",
|
|
132
|
+
await self._session.scalar(select(Author).where(Author.email == email)),
|
|
133
|
+
)
|
|
@@ -21,19 +21,24 @@ class SqlAlchemySnippetRepository(SnippetRepository):
|
|
|
21
21
|
"""
|
|
22
22
|
self.session = session
|
|
23
23
|
|
|
24
|
-
async def
|
|
25
|
-
"""
|
|
24
|
+
async def get(self, id: int) -> Snippet | None: # noqa: A002
|
|
25
|
+
"""Get a snippet by ID."""
|
|
26
|
+
return await self.session.get(Snippet, id)
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
async def save(self, entity: Snippet) -> Snippet:
|
|
29
|
+
"""Save entity."""
|
|
30
|
+
self.session.add(entity)
|
|
31
|
+
return entity
|
|
29
32
|
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
async def delete(self, id: int) -> None: # noqa: A002
|
|
34
|
+
"""Delete entity by ID."""
|
|
35
|
+
snippet = await self.get(id)
|
|
36
|
+
if snippet:
|
|
37
|
+
await self.session.delete(snippet)
|
|
32
38
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
await self.session.
|
|
36
|
-
return snippet
|
|
39
|
+
async def list(self) -> Sequence[Snippet]:
|
|
40
|
+
"""List all entities."""
|
|
41
|
+
return (await self.session.scalars(select(Snippet))).all()
|
|
37
42
|
|
|
38
43
|
async def get_by_id(self, snippet_id: int) -> Snippet | None:
|
|
39
44
|
"""Get a snippet by ID.
|
|
@@ -72,4 +77,3 @@ class SqlAlchemySnippetRepository(SnippetRepository):
|
|
|
72
77
|
"""
|
|
73
78
|
query = delete(Snippet).where(Snippet.index_id == index_id)
|
|
74
79
|
await self.session.execute(query)
|
|
75
|
-
await self.session.commit()
|
kodit/mcp.py
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
kodit/.gitignore,sha256=ztkjgRwL9Uud1OEi36hGQeDGk3OLK1NfDEO8YqGYy8o,11
|
|
2
2
|
kodit/__init__.py,sha256=aEKHYninUq1yh6jaNfvJBYg-6fenpN132nJt1UU6Jxs,59
|
|
3
|
-
kodit/_version.py,sha256=
|
|
3
|
+
kodit/_version.py,sha256=nObnONsicQ3YX6SG5MVBxmIp5dmRacXDauSqZijWQbY,511
|
|
4
4
|
kodit/app.py,sha256=qKBWJ0VNSY_M6G3VFfAQ0133q5bnS99cUFD0p396taw,1032
|
|
5
|
-
kodit/cli.py,sha256=
|
|
5
|
+
kodit/cli.py,sha256=JnhTlG1s04O0m8AzsBdrwP8T_BqSZMPXnRLvI7T_Gxc,12004
|
|
6
6
|
kodit/config.py,sha256=3yh7hfLSILjZK_qJMhcExwRcrWJ0b5Eb1JjjOvMPJZo,4146
|
|
7
7
|
kodit/database.py,sha256=kI9yBm4uunsgV4-QeVoCBL0wLzU4kYmYv5qZilGnbPE,1740
|
|
8
8
|
kodit/log.py,sha256=sHPHYetlMcKTor2VaFLMyao1_fZ_xhuzqXCAt5F5UMU,8575
|
|
9
|
-
kodit/mcp.py,sha256=
|
|
9
|
+
kodit/mcp.py,sha256=bUvG4by2CDN3QSkKNUer0yxpzAGbq-hg8HTU7S5fMv4,6217
|
|
10
10
|
kodit/middleware.py,sha256=I6FOkqG9-8RH5kR1-0ZoQWfE4qLCB8lZYv8H_OCH29o,2714
|
|
11
11
|
kodit/reporting.py,sha256=icce1ZyiADsA_Qz-mSjgn2H4SSqKuGfLKnw-yrl9nsg,2722
|
|
12
12
|
kodit/application/__init__.py,sha256=mH50wTpgP9dhbKztFsL8Dda9Hi18TSnMVxXtpp4aGOA,35
|
|
13
13
|
kodit/application/commands/__init__.py,sha256=AOVs25fwboBnMCWdgDB7fPbAYTljurAPVMkATIGRKuk,38
|
|
14
14
|
kodit/application/commands/snippet_commands.py,sha256=WzRrnJOnLpIK8-wvN7c-ecGs_4LosQ_jR30dQkFqFBY,600
|
|
15
15
|
kodit/application/services/__init__.py,sha256=p5UQNw-H5sxQvs5Etfte93B3cJ1kKW6DNxK34uFvU1E,38
|
|
16
|
-
kodit/application/services/indexing_application_service.py,sha256=
|
|
17
|
-
kodit/application/services/snippet_application_service.py,sha256=
|
|
16
|
+
kodit/application/services/indexing_application_service.py,sha256=tfdEiFTVvqkZQ6I_ZkW5IECQJHZi35OJX4bj96lkZrc,14455
|
|
17
|
+
kodit/application/services/snippet_application_service.py,sha256=2qW7ZIUQ63bdtAngf6xgQ09N-vygNpd12sXwyKwOcSs,5037
|
|
18
18
|
kodit/domain/__init__.py,sha256=TCpg4Xx-oF4mKV91lo4iXqMEfBT1OoRSYnbG-zVWolA,66
|
|
19
19
|
kodit/domain/entities.py,sha256=6XVuwDIQjkBw5Bm51io5ZUxB6_O4A774CDke2bfKWTY,5584
|
|
20
20
|
kodit/domain/enums.py,sha256=Ik_h3D3eZ0FsSlPsU0ikm-Yv3Rmvzicffi9yBn19UIE,191
|
|
21
|
+
kodit/domain/errors.py,sha256=yIsgCjM_yOFIg8l7l-t7jM8pgeAX4cfPq0owf7iz3DA,106
|
|
21
22
|
kodit/domain/interfaces.py,sha256=Jkd0Ob4qSvhZHI9jRPFQ1n5Cv0SvU-y3Z-HCw2ikc4I,742
|
|
22
23
|
kodit/domain/repositories.py,sha256=bdKxSKGI6XzrpzeKcv-NDV2JBirbEMRK-Y4UCZmDtoY,2706
|
|
23
24
|
kodit/domain/value_objects.py,sha256=4Vs7Uk1wQgTjnCkZlOlw7E1Q8NiyAmBpFv38Lhs3WZ0,3869
|
|
@@ -28,7 +29,7 @@ kodit/domain/services/enrichment_service.py,sha256=XsXg3nV-KN4rqtC7Zro_ZiZ6RSq-1
|
|
|
28
29
|
kodit/domain/services/ignore_service.py,sha256=boEN-IRLmUtwO9ZnuACaVFZbIKrtUG8YwnsXKEDIG28,1136
|
|
29
30
|
kodit/domain/services/indexing_service.py,sha256=FEizu2GkvZA32xHOYXXch0LuHoWg6Z-BbJMPjZslzjc,5853
|
|
30
31
|
kodit/domain/services/snippet_extraction_service.py,sha256=QW_99bXWpr8g6ZI-hp4Aj57VCSrUf71dLwQca5T6pyg,3065
|
|
31
|
-
kodit/domain/services/source_service.py,sha256=
|
|
32
|
+
kodit/domain/services/source_service.py,sha256=9XGS3imJn65v855cztsJSaaFod6LhkF2xfUVMaytx-A,3068
|
|
32
33
|
kodit/infrastructure/__init__.py,sha256=HzEYIjoXnkz_i_MHO2e0sIVYweUcRnl2RpyBiTbMObU,28
|
|
33
34
|
kodit/infrastructure/bm25/__init__.py,sha256=DmGbrEO34FOJy4e685BbyxLA7gPW1eqs2gAxsp6JOuM,34
|
|
34
35
|
kodit/infrastructure/bm25/bm25_factory.py,sha256=I4eo7qRslnyXIRkBf-StZ5ga2Evrr5J5YFocTChFD3g,884
|
|
@@ -37,10 +38,10 @@ kodit/infrastructure/bm25/vectorchord_bm25_repository.py,sha256=0Db9XWFjiS4TFrsN
|
|
|
37
38
|
kodit/infrastructure/cloning/__init__.py,sha256=IzIvX-yeRRFZ-lfvPVSEe_qXszO6DGQdjKwwDigexyQ,30
|
|
38
39
|
kodit/infrastructure/cloning/metadata.py,sha256=C5LLmsUzi29RhSbzVDNqiShbekg7qdp1ihGUyFXy5yM,4277
|
|
39
40
|
kodit/infrastructure/cloning/folder/__init__.py,sha256=w6ykrVtbYJlUDEXAjqgf6w2rMsUMCrrpIbl3QMjubgY,37
|
|
40
|
-
kodit/infrastructure/cloning/folder/factory.py,sha256=
|
|
41
|
+
kodit/infrastructure/cloning/folder/factory.py,sha256=vl1hwnYA7lczjotn2fahJQAt7IK96CSArx8cSaRFKeY,4242
|
|
41
42
|
kodit/infrastructure/cloning/folder/working_copy.py,sha256=FPhwzuPj40yGoYvwcm9VG8mv8MbJxwfby_N5JS-_daA,1154
|
|
42
43
|
kodit/infrastructure/cloning/git/__init__.py,sha256=20ePcp0qE6BuLsjsv4KYB1DzKhMIMsPXwEqIEZtjTJs,34
|
|
43
|
-
kodit/infrastructure/cloning/git/factory.py,sha256=
|
|
44
|
+
kodit/infrastructure/cloning/git/factory.py,sha256=1f0TKM9R_65WUMEhoJwBKTeo9xNYhm6VtscY7SqD6yU,5012
|
|
44
45
|
kodit/infrastructure/cloning/git/working_copy.py,sha256=DMW_p7WWGoSeyDI9g55ItwsRomZSotXWRrlopqwszaQ,1115
|
|
45
46
|
kodit/infrastructure/embedding/__init__.py,sha256=F-8nLlWAerYJ0MOIA4tbXHLan8bW5rRR84vzxx6tRKI,39
|
|
46
47
|
kodit/infrastructure/embedding/embedding_factory.py,sha256=1AypjhWJGxvLnZt1SEH_FHPk9P0Vkt9fXdSGzFPp2ow,3432
|
|
@@ -63,8 +64,8 @@ kodit/infrastructure/ignore/__init__.py,sha256=VzFv8XOzHmsu0MEGnWVSF6KsgqLBmvHlR
|
|
|
63
64
|
kodit/infrastructure/ignore/ignore_pattern_provider.py,sha256=9m2XCsgW87UBTfzHr6Z0Ns6WpzwkLir3zyBY3PwsgXk,2225
|
|
64
65
|
kodit/infrastructure/indexing/__init__.py,sha256=7UPRa2jwCAsa0Orsp6PqXSF8iIXJVzXHMFmrKkI9yH8,38
|
|
65
66
|
kodit/infrastructure/indexing/fusion_service.py,sha256=mXUUcx3-8e75mWkxXMfl30HIoFXrTNHzB1w90MmEbak,1806
|
|
66
|
-
kodit/infrastructure/indexing/index_repository.py,sha256=
|
|
67
|
-
kodit/infrastructure/indexing/indexing_factory.py,sha256=
|
|
67
|
+
kodit/infrastructure/indexing/index_repository.py,sha256=4aSCBE_Gn9ihOx_kXOpUTTIv6_Q71-VRFHEBgpWaAEw,8906
|
|
68
|
+
kodit/infrastructure/indexing/indexing_factory.py,sha256=KHA8c0XR9QrgqSR6gRUQk9wp6md97_oA1lwZFzoJAtk,3964
|
|
68
69
|
kodit/infrastructure/snippet_extraction/__init__.py,sha256=v6KqrRDjSj0nt87m7UwRGx2GN_fz_14VWq9Q0uABR_s,54
|
|
69
70
|
kodit/infrastructure/snippet_extraction/language_detection_service.py,sha256=Lo9xPLVia-70yP9gzyH4cQcBQzsp7WXjGOa5NBggScg,1158
|
|
70
71
|
kodit/infrastructure/snippet_extraction/snippet_extraction_factory.py,sha256=LGbm614KCPNsM9K8r1z-E763NyAMIZA9ETJ_C61EknA,2759
|
|
@@ -76,10 +77,10 @@ kodit/infrastructure/snippet_extraction/languages/javascript.scm,sha256=Ini5TsVN
|
|
|
76
77
|
kodit/infrastructure/snippet_extraction/languages/python.scm,sha256=ee85R9PBzwye3IMTE7-iVoKWd_ViU3EJISTyrFGrVeo,429
|
|
77
78
|
kodit/infrastructure/snippet_extraction/languages/typescript.scm,sha256=U-ujbbv4tylbUBj9wuhL-e5cW6hmgPCNs4xrIX3r_hE,448
|
|
78
79
|
kodit/infrastructure/sqlalchemy/__init__.py,sha256=UXPMSF_hgWaqr86cawRVqM8XdVNumQyyK5B8B97GnlA,33
|
|
79
|
-
kodit/infrastructure/sqlalchemy/embedding_repository.py,sha256=
|
|
80
|
-
kodit/infrastructure/sqlalchemy/file_repository.py,sha256=
|
|
81
|
-
kodit/infrastructure/sqlalchemy/repository.py,sha256=
|
|
82
|
-
kodit/infrastructure/sqlalchemy/snippet_repository.py,sha256=
|
|
80
|
+
kodit/infrastructure/sqlalchemy/embedding_repository.py,sha256=dCCRV5rD8T5xBksPKvdr-z4F72WKo4-dH9mXazDByXQ,7476
|
|
81
|
+
kodit/infrastructure/sqlalchemy/file_repository.py,sha256=9_kXHJ1YiWA1ingpvBNq8cuxkMu59PHwl_m9_Ttnq2o,2353
|
|
82
|
+
kodit/infrastructure/sqlalchemy/repository.py,sha256=EpZnOjR3wfPEqIauWw_KczpkSqBQPTq5sIyCpJCuW2w,4565
|
|
83
|
+
kodit/infrastructure/sqlalchemy/snippet_repository.py,sha256=RYhBnyvvBo-6obrUkds6BhEjchs4HYQL8k9x0Cy7BtM,2430
|
|
83
84
|
kodit/infrastructure/ui/__init__.py,sha256=CzbLOBwIZ6B6iAHEd1L8cIBydCj-n_kobxJAhz2I9_Y,32
|
|
84
85
|
kodit/infrastructure/ui/progress.py,sha256=BaAeMEgXlSSb0c_t_NPxnThIktkzzCS9kegb5ExULJs,4791
|
|
85
86
|
kodit/infrastructure/ui/spinner.py,sha256=GcP115qtR0VEnGfMEtsGoAUpRzVGUSfiUXfoJJERngA,2357
|
|
@@ -92,8 +93,8 @@ kodit/migrations/versions/85155663351e_initial.py,sha256=Cg7zlF871o9ShV5rQMQ1v7h
|
|
|
92
93
|
kodit/migrations/versions/9e53ea8bb3b0_add_authors.py,sha256=a32Zm8KUQyiiLkjKNPYdaJDgjW6VsV-GhaLnPnK_fpI,3884
|
|
93
94
|
kodit/migrations/versions/__init__.py,sha256=9-lHzptItTzq_fomdIRBegQNm4Znx6pVjwD4MiqRIdo,36
|
|
94
95
|
kodit/migrations/versions/c3f5137d30f5_index_all_the_things.py,sha256=rI8LmjF-I2OMxZ2nOIF_NRmqOLXe45hL_iz_nx97DTQ,1680
|
|
95
|
-
kodit-0.2.
|
|
96
|
-
kodit-0.2.
|
|
97
|
-
kodit-0.2.
|
|
98
|
-
kodit-0.2.
|
|
99
|
-
kodit-0.2.
|
|
96
|
+
kodit-0.2.6.dist-info/METADATA,sha256=q9yeLcPnH31fKZe5i5Bzw30NDuJge6NsCJDVToc0-OE,5867
|
|
97
|
+
kodit-0.2.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
98
|
+
kodit-0.2.6.dist-info/entry_points.txt,sha256=hoTn-1aKyTItjnY91fnO-rV5uaWQLQ-Vi7V5et2IbHY,40
|
|
99
|
+
kodit-0.2.6.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
100
|
+
kodit-0.2.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|