kodit 0.4.2__py3-none-any.whl → 0.5.0__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.

Files changed (100) hide show
  1. kodit/_version.py +2 -2
  2. kodit/app.py +59 -24
  3. kodit/application/factories/reporting_factory.py +16 -7
  4. kodit/application/factories/server_factory.py +311 -0
  5. kodit/application/services/code_search_application_service.py +144 -0
  6. kodit/application/services/commit_indexing_application_service.py +543 -0
  7. kodit/application/services/indexing_worker_service.py +13 -46
  8. kodit/application/services/queue_service.py +24 -3
  9. kodit/application/services/reporting.py +70 -54
  10. kodit/application/services/sync_scheduler.py +15 -31
  11. kodit/cli.py +2 -763
  12. kodit/cli_utils.py +2 -9
  13. kodit/config.py +3 -96
  14. kodit/database.py +38 -1
  15. kodit/domain/entities/__init__.py +276 -0
  16. kodit/domain/entities/git.py +190 -0
  17. kodit/domain/factories/__init__.py +1 -0
  18. kodit/domain/factories/git_repo_factory.py +76 -0
  19. kodit/domain/protocols.py +270 -46
  20. kodit/domain/services/bm25_service.py +5 -1
  21. kodit/domain/services/embedding_service.py +3 -0
  22. kodit/domain/services/git_repository_service.py +429 -0
  23. kodit/domain/services/git_service.py +300 -0
  24. kodit/domain/services/task_status_query_service.py +19 -0
  25. kodit/domain/value_objects.py +113 -147
  26. kodit/infrastructure/api/client/__init__.py +0 -2
  27. kodit/infrastructure/api/v1/__init__.py +0 -4
  28. kodit/infrastructure/api/v1/dependencies.py +105 -44
  29. kodit/infrastructure/api/v1/routers/__init__.py +0 -6
  30. kodit/infrastructure/api/v1/routers/commits.py +271 -0
  31. kodit/infrastructure/api/v1/routers/queue.py +2 -2
  32. kodit/infrastructure/api/v1/routers/repositories.py +282 -0
  33. kodit/infrastructure/api/v1/routers/search.py +31 -14
  34. kodit/infrastructure/api/v1/schemas/__init__.py +0 -24
  35. kodit/infrastructure/api/v1/schemas/commit.py +96 -0
  36. kodit/infrastructure/api/v1/schemas/context.py +2 -0
  37. kodit/infrastructure/api/v1/schemas/repository.py +128 -0
  38. kodit/infrastructure/api/v1/schemas/search.py +12 -9
  39. kodit/infrastructure/api/v1/schemas/snippet.py +58 -0
  40. kodit/infrastructure/api/v1/schemas/tag.py +31 -0
  41. kodit/infrastructure/api/v1/schemas/task_status.py +41 -0
  42. kodit/infrastructure/bm25/local_bm25_repository.py +16 -4
  43. kodit/infrastructure/bm25/vectorchord_bm25_repository.py +68 -52
  44. kodit/infrastructure/cloning/git/git_python_adaptor.py +467 -0
  45. kodit/infrastructure/cloning/git/working_copy.py +10 -3
  46. kodit/infrastructure/embedding/embedding_factory.py +3 -2
  47. kodit/infrastructure/embedding/local_vector_search_repository.py +1 -1
  48. kodit/infrastructure/embedding/vectorchord_vector_search_repository.py +111 -84
  49. kodit/infrastructure/enrichment/litellm_enrichment_provider.py +19 -26
  50. kodit/infrastructure/enrichment/local_enrichment_provider.py +41 -30
  51. kodit/infrastructure/indexing/fusion_service.py +1 -1
  52. kodit/infrastructure/mappers/git_mapper.py +193 -0
  53. kodit/infrastructure/mappers/snippet_mapper.py +106 -0
  54. kodit/infrastructure/mappers/task_mapper.py +5 -44
  55. kodit/infrastructure/mappers/task_status_mapper.py +85 -0
  56. kodit/infrastructure/reporting/db_progress.py +23 -0
  57. kodit/infrastructure/reporting/log_progress.py +13 -38
  58. kodit/infrastructure/reporting/telemetry_progress.py +21 -0
  59. kodit/infrastructure/slicing/slicer.py +32 -31
  60. kodit/infrastructure/sqlalchemy/embedding_repository.py +43 -23
  61. kodit/infrastructure/sqlalchemy/entities.py +428 -131
  62. kodit/infrastructure/sqlalchemy/git_branch_repository.py +263 -0
  63. kodit/infrastructure/sqlalchemy/git_commit_repository.py +337 -0
  64. kodit/infrastructure/sqlalchemy/git_repository.py +252 -0
  65. kodit/infrastructure/sqlalchemy/git_tag_repository.py +257 -0
  66. kodit/infrastructure/sqlalchemy/snippet_v2_repository.py +484 -0
  67. kodit/infrastructure/sqlalchemy/task_repository.py +29 -23
  68. kodit/infrastructure/sqlalchemy/task_status_repository.py +91 -0
  69. kodit/infrastructure/sqlalchemy/unit_of_work.py +10 -14
  70. kodit/mcp.py +12 -26
  71. kodit/migrations/env.py +1 -1
  72. kodit/migrations/versions/04b80f802e0c_foreign_key_review.py +100 -0
  73. kodit/migrations/versions/7f15f878c3a1_add_new_git_entities.py +690 -0
  74. kodit/migrations/versions/b9cd1c3fd762_add_task_status.py +77 -0
  75. kodit/migrations/versions/f9e5ef5e688f_add_git_commits_number.py +43 -0
  76. kodit/py.typed +0 -0
  77. kodit/utils/dump_openapi.py +7 -4
  78. kodit/utils/path_utils.py +29 -0
  79. {kodit-0.4.2.dist-info → kodit-0.5.0.dist-info}/METADATA +3 -3
  80. kodit-0.5.0.dist-info/RECORD +137 -0
  81. kodit/application/factories/code_indexing_factory.py +0 -193
  82. kodit/application/services/auto_indexing_service.py +0 -103
  83. kodit/application/services/code_indexing_application_service.py +0 -393
  84. kodit/domain/entities.py +0 -323
  85. kodit/domain/services/index_query_service.py +0 -70
  86. kodit/domain/services/index_service.py +0 -267
  87. kodit/infrastructure/api/client/index_client.py +0 -57
  88. kodit/infrastructure/api/v1/routers/indexes.py +0 -119
  89. kodit/infrastructure/api/v1/schemas/index.py +0 -101
  90. kodit/infrastructure/bm25/bm25_factory.py +0 -28
  91. kodit/infrastructure/cloning/__init__.py +0 -1
  92. kodit/infrastructure/cloning/metadata.py +0 -98
  93. kodit/infrastructure/mappers/index_mapper.py +0 -345
  94. kodit/infrastructure/reporting/tdqm_progress.py +0 -73
  95. kodit/infrastructure/slicing/language_detection_service.py +0 -18
  96. kodit/infrastructure/sqlalchemy/index_repository.py +0 -646
  97. kodit-0.4.2.dist-info/RECORD +0 -119
  98. {kodit-0.4.2.dist-info → kodit-0.5.0.dist-info}/WHEEL +0 -0
  99. {kodit-0.4.2.dist-info → kodit-0.5.0.dist-info}/entry_points.txt +0 -0
  100. {kodit-0.4.2.dist-info → kodit-0.5.0.dist-info}/licenses/LICENSE +0 -0
kodit/cli.py CHANGED
@@ -1,3 +1,4 @@
1
+ # ruff: noqa: ARG001
1
2
  """Command line interface for kodit."""
2
3
 
3
4
  import signal
@@ -8,26 +9,10 @@ from typing import Any
8
9
  import click
9
10
  import structlog
10
11
  import uvicorn
11
- from pytable_formatter import Cell, Table # type: ignore[import-untyped]
12
12
 
13
- from kodit.application.factories.code_indexing_factory import (
14
- create_cli_code_indexing_application_service,
15
- )
16
13
  from kodit.config import (
17
14
  AppContext,
18
- with_app_context,
19
- wrap_async,
20
- )
21
- from kodit.domain.errors import EmptySourceError
22
- from kodit.domain.services.index_query_service import IndexQueryService
23
- from kodit.domain.value_objects import (
24
- MultiSearchRequest,
25
- MultiSearchResult,
26
- SnippetSearchFilters,
27
15
  )
28
- from kodit.infrastructure.api.client import IndexClient, SearchClient
29
- from kodit.infrastructure.indexing.fusion_service import ReciprocalRankFusionService
30
- from kodit.infrastructure.sqlalchemy.index_repository import create_index_repository
31
16
  from kodit.log import configure_logging, configure_telemetry, log_event
32
17
  from kodit.mcp import create_stdio_mcp_server
33
18
 
@@ -61,753 +46,6 @@ def cli(
61
46
  ctx.obj = config
62
47
 
63
48
 
64
- async def _handle_auto_index(
65
- app_context: AppContext,
66
- sources: list[str], # noqa: ARG001
67
- ) -> list[str]:
68
- """Handle auto-index option and return sources to process."""
69
- log = structlog.get_logger(__name__)
70
- log.info("Auto-indexing configuration", config=app_context.auto_indexing)
71
- if not app_context.auto_indexing or not app_context.auto_indexing.sources:
72
- click.echo("No auto-index sources configured.")
73
- return []
74
- auto_sources = app_context.auto_indexing.sources
75
- click.echo(f"Auto-indexing {len(auto_sources)} configured sources...")
76
- return [source.uri for source in auto_sources]
77
-
78
-
79
- async def _handle_sync(
80
- service: Any,
81
- index_query_service: IndexQueryService,
82
- sources: list[str],
83
- ) -> None:
84
- """Handle sync operation."""
85
- log = structlog.get_logger(__name__)
86
- log_event("kodit.cli.index.sync")
87
-
88
- # Get all existing indexes
89
- all_indexes = await index_query_service.list_indexes()
90
-
91
- if not all_indexes:
92
- click.echo("No existing indexes found to sync.")
93
- return
94
-
95
- # Filter indexes if specific sources are provided
96
- indexes_to_sync = all_indexes
97
- if sources:
98
- # Filter indexes that match the provided sources
99
- source_uris = set(sources)
100
- indexes_to_sync = [
101
- index
102
- for index in all_indexes
103
- if str(index.source.working_copy.remote_uri) in source_uris
104
- ]
105
-
106
- if not indexes_to_sync:
107
- click.echo(
108
- f"No indexes found for the specified sources: {', '.join(sources)}"
109
- )
110
- return
111
-
112
- click.echo(f"Syncing {len(indexes_to_sync)} indexes...")
113
-
114
- # Sync each index
115
- for index in indexes_to_sync:
116
- click.echo(f"Syncing: {index.source.working_copy.remote_uri}")
117
-
118
- try:
119
- await service.run_index(index)
120
- click.echo(f"✓ Sync completed: {index.source.working_copy.remote_uri}")
121
- except Exception as e:
122
- log.exception("Sync failed", index_id=index.id, error=e)
123
- click.echo(f"✗ Sync failed: {index.source.working_copy.remote_uri} - {e}")
124
-
125
-
126
- async def _handle_list_indexes(index_query_service: IndexQueryService) -> None:
127
- """Handle listing all indexes."""
128
- log_event("kodit.cli.index.list")
129
- # No source specified, list all indexes
130
- indexes = await index_query_service.list_indexes()
131
- headers: list[str | Cell] = [
132
- "ID",
133
- "Created At",
134
- "Updated At",
135
- "Source",
136
- "Num Snippets",
137
- ]
138
- data = [
139
- [
140
- index.id,
141
- index.created_at,
142
- index.updated_at,
143
- index.source.working_copy.remote_uri,
144
- len(index.source.working_copy.files),
145
- ]
146
- for index in indexes
147
- ]
148
- click.echo(Table(headers=headers, data=data))
149
-
150
-
151
- @cli.command()
152
- @click.argument("sources", nargs=-1)
153
- @click.option(
154
- "--auto-index", is_flag=True, help="Index all configured auto-index sources"
155
- )
156
- @click.option("--sync", is_flag=True, help="Sync existing indexes with their remotes")
157
- @with_app_context
158
- @wrap_async
159
- async def index(
160
- app_context: AppContext,
161
- sources: list[str],
162
- *, # Force keyword-only arguments
163
- auto_index: bool,
164
- sync: bool,
165
- ) -> None:
166
- """List indexes, index data sources, or sync existing indexes."""
167
- if not app_context.is_remote:
168
- # Local mode - use existing implementation
169
- await _index_local(app_context, sources, auto_index, sync)
170
- else:
171
- # Remote mode - use API clients
172
- await _index_remote(app_context, sources, auto_index, sync)
173
-
174
-
175
- async def _index_local(
176
- app_context: AppContext,
177
- sources: list[str],
178
- auto_index: bool, # noqa: FBT001
179
- sync: bool, # noqa: FBT001
180
- ) -> None:
181
- """Handle index operations in local mode."""
182
- log = structlog.get_logger(__name__)
183
-
184
- # Get database session
185
- db = await app_context.get_db()
186
- async with db.session_factory() as session:
187
- service = create_cli_code_indexing_application_service(
188
- app_context=app_context,
189
- session=session,
190
- session_factory=db.session_factory,
191
- )
192
- index_query_service = IndexQueryService(
193
- index_repository=create_index_repository(
194
- session_factory=db.session_factory
195
- ),
196
- fusion_service=ReciprocalRankFusionService(),
197
- )
198
-
199
- if auto_index:
200
- sources = await _handle_auto_index(app_context, sources)
201
- if not sources:
202
- return
203
-
204
- if sync:
205
- await _handle_sync(service, index_query_service, sources)
206
- return
207
-
208
- if not sources:
209
- await _handle_list_indexes(index_query_service)
210
- return
211
-
212
- # Handle source indexing
213
- for source in sources:
214
- if Path(source).is_file():
215
- msg = "File indexing is not implemented yet"
216
- raise click.UsageError(msg)
217
-
218
- # Index source with progress
219
- log_event("kodit.cli.index.create")
220
-
221
- # Create a lazy progress callback that only shows progress when needed
222
- index = await service.create_index_from_uri(source)
223
-
224
- # Create a new progress callback for the indexing operations
225
- try:
226
- await service.run_index(index)
227
- except EmptySourceError as e:
228
- log.exception("Empty source error", error=e)
229
- msg = f"""{e}. This could mean:
230
- • The repository contains no supported file types
231
- • All files are excluded by ignore patterns
232
- • The files contain no extractable code snippets
233
- Please check the repository contents and try again.
234
- """
235
- click.echo(msg)
236
-
237
-
238
- async def _index_remote(
239
- app_context: AppContext,
240
- sources: list[str],
241
- auto_index: bool, # noqa: FBT001
242
- sync: bool, # noqa: FBT001
243
- ) -> None:
244
- """Handle index operations in remote mode."""
245
- if sync:
246
- # Sync operation not available in remote mode
247
- click.echo("⚠️ Warning: Index sync is not implemented in remote mode")
248
- click.echo("Please use the server's auto-sync functionality or sync locally")
249
- return
250
-
251
- if auto_index:
252
- click.echo("⚠️ Warning: Auto-index is not implemented in remote mode")
253
- click.echo("Please configure sources to be auto-indexed on the server")
254
- return
255
-
256
- # Create API client
257
- index_client = IndexClient(
258
- base_url=app_context.remote.server_url or "",
259
- api_key=app_context.remote.api_key,
260
- timeout=app_context.remote.timeout,
261
- max_retries=app_context.remote.max_retries,
262
- verify_ssl=app_context.remote.verify_ssl,
263
- )
264
-
265
- try:
266
- if not sources:
267
- # List indexes
268
- indexes = await index_client.list_indexes()
269
- _display_indexes_remote(indexes)
270
- return
271
-
272
- # Create new indexes
273
- for source in sources:
274
- click.echo(f"Creating index for: {source}")
275
- index = await index_client.create_index(source)
276
- click.echo(f"✓ Index created with ID: {index.id}")
277
-
278
- finally:
279
- await index_client.close()
280
-
281
-
282
- def _display_indexes_remote(indexes: list[Any]) -> None:
283
- """Display indexes for remote mode."""
284
- headers = [
285
- "ID",
286
- "Created At",
287
- "Updated At",
288
- "Source",
289
- ]
290
- data = [
291
- [
292
- index.id,
293
- index.attributes.created_at,
294
- index.attributes.updated_at,
295
- index.attributes.uri,
296
- ]
297
- for index in indexes
298
- ]
299
-
300
- click.echo(Table(headers=headers, data=data))
301
-
302
-
303
- async def _search_local( # noqa: PLR0913
304
- app_context: AppContext,
305
- keywords: list[str] | None = None,
306
- code_query: str | None = None,
307
- text_query: str | None = None,
308
- top_k: int = 10,
309
- language: str | None = None,
310
- author: str | None = None,
311
- created_after: str | None = None,
312
- created_before: str | None = None,
313
- source_repo: str | None = None,
314
- output_format: str = "text",
315
- event_name: str = "kodit.cli.search",
316
- ) -> None:
317
- """Handle search operations in local mode."""
318
- log_event(event_name)
319
-
320
- # Get database session
321
- db = await app_context.get_db()
322
- async with db.session_factory() as session:
323
- service = create_cli_code_indexing_application_service(
324
- app_context=app_context,
325
- session=session,
326
- session_factory=db.session_factory,
327
- )
328
-
329
- filters = _parse_filters(
330
- language, author, created_after, created_before, source_repo
331
- )
332
-
333
- snippets = await service.search(
334
- MultiSearchRequest(
335
- keywords=keywords,
336
- code_query=code_query,
337
- text_query=text_query,
338
- top_k=top_k,
339
- filters=filters,
340
- )
341
- )
342
-
343
- if len(snippets) == 0:
344
- click.echo("No snippets found")
345
- return
346
-
347
- if output_format == "text":
348
- click.echo(MultiSearchResult.to_string(snippets))
349
- elif output_format == "json":
350
- click.echo(MultiSearchResult.to_jsonlines(snippets))
351
-
352
-
353
- async def _search_remote( # noqa: PLR0913
354
- app_context: AppContext,
355
- keywords: list[str] | None = None,
356
- code_query: str | None = None,
357
- text_query: str | None = None,
358
- top_k: int = 10,
359
- language: str | None = None,
360
- author: str | None = None,
361
- created_after: str | None = None,
362
- created_before: str | None = None,
363
- source_repo: str | None = None,
364
- output_format: str = "text",
365
- ) -> None:
366
- """Handle search operations in remote mode."""
367
- from datetime import datetime
368
-
369
- # Parse date filters for API
370
- parsed_start_date = None
371
- if created_after:
372
- try:
373
- parsed_start_date = datetime.fromisoformat(created_after)
374
- except ValueError as err:
375
- raise ValueError(
376
- f"Invalid date format for --created-after: {created_after}. "
377
- "Expected ISO 8601 format (YYYY-MM-DD)"
378
- ) from err
379
-
380
- parsed_end_date = None
381
- if created_before:
382
- try:
383
- parsed_end_date = datetime.fromisoformat(created_before)
384
- except ValueError as err:
385
- raise ValueError(
386
- f"Invalid date format for --created-before: {created_before}. "
387
- "Expected ISO 8601 format (YYYY-MM-DD)"
388
- ) from err
389
-
390
- # Create API client
391
- search_client = SearchClient(
392
- base_url=app_context.remote.server_url or "",
393
- api_key=app_context.remote.api_key,
394
- timeout=app_context.remote.timeout,
395
- max_retries=app_context.remote.max_retries,
396
- verify_ssl=app_context.remote.verify_ssl,
397
- )
398
-
399
- try:
400
- # Perform search
401
- snippets = await search_client.search(
402
- keywords=keywords,
403
- code_query=code_query,
404
- text_query=text_query,
405
- limit=top_k,
406
- languages=[language] if language else None,
407
- authors=[author] if author else None,
408
- start_date=parsed_start_date,
409
- end_date=parsed_end_date,
410
- sources=[source_repo] if source_repo else None,
411
- )
412
-
413
- if len(snippets) == 0:
414
- click.echo("No snippets found")
415
- return
416
-
417
- if output_format == "text":
418
- _display_snippets_remote_text(snippets)
419
- elif output_format == "json":
420
- _display_snippets_remote_json(snippets)
421
-
422
- finally:
423
- await search_client.close()
424
-
425
-
426
- def _display_snippets_remote_text(snippets: list[Any]) -> None:
427
- """Display snippets in text format for remote mode."""
428
- for i, snippet in enumerate(snippets):
429
- click.echo(f"\n--- Snippet {i + 1} ---")
430
- click.echo(f"ID: {snippet.id}")
431
- click.echo(f"Language: {snippet.attributes.language}")
432
- click.echo(f"Source: {snippet.attributes.source_uri}")
433
- click.echo(f"Path: {snippet.attributes.relative_path}")
434
- click.echo(f"Authors: {', '.join(snippet.attributes.authors)}")
435
- click.echo(f"Summary: {snippet.attributes.summary}")
436
- click.echo("\nContent:")
437
- click.echo(snippet.attributes.content)
438
-
439
-
440
- def _display_snippets_remote_json(snippets: list[Any]) -> None:
441
- """Display snippets in JSON format for remote mode."""
442
- import json
443
-
444
- for snippet in snippets:
445
- snippet_data = {
446
- "id": snippet.id,
447
- "language": snippet.attributes.language,
448
- "source_uri": snippet.attributes.source_uri,
449
- "relative_path": snippet.attributes.relative_path,
450
- "authors": snippet.attributes.authors,
451
- "summary": snippet.attributes.summary,
452
- "content": snippet.attributes.content,
453
- "created_at": snippet.attributes.created_at.isoformat(),
454
- "updated_at": snippet.attributes.updated_at.isoformat(),
455
- }
456
- click.echo(json.dumps(snippet_data))
457
-
458
-
459
- @cli.group()
460
- def search() -> None:
461
- """Search for snippets in the database."""
462
-
463
-
464
- # Utility for robust filter parsing
465
- def _parse_filters(
466
- language: str | None,
467
- author: str | None,
468
- created_after: str | None,
469
- created_before: str | None,
470
- source_repo: str | None,
471
- ) -> SnippetSearchFilters | None:
472
- from datetime import datetime
473
-
474
- # Normalize language to lowercase if provided
475
- norm_language = language.lower() if language else None
476
- # Try to parse dates, raise error if invalid
477
- parsed_created_after = None
478
- if created_after:
479
- try:
480
- parsed_created_after = datetime.fromisoformat(created_after)
481
- except ValueError as err:
482
- raise ValueError(
483
- f"Invalid date format for --created-after: {created_after}. "
484
- "Expected ISO 8601 format (YYYY-MM-DD)"
485
- ) from err
486
- parsed_created_before = None
487
- if created_before:
488
- try:
489
- parsed_created_before = datetime.fromisoformat(created_before)
490
- except ValueError as err:
491
- raise ValueError(
492
- f"Invalid date format for --created-before: {created_before}. "
493
- "Expected ISO 8601 format (YYYY-MM-DD)"
494
- ) from err
495
- # Return None if no filters provided, otherwise return SnippetSearchFilters
496
- # Check if any original parameters were provided (not just the parsed values)
497
- if any(
498
- [
499
- language,
500
- author,
501
- created_after,
502
- created_before,
503
- source_repo,
504
- ]
505
- ):
506
- return SnippetSearchFilters(
507
- language=norm_language,
508
- author=author,
509
- created_after=parsed_created_after,
510
- created_before=parsed_created_before,
511
- source_repo=source_repo,
512
- )
513
- return None
514
-
515
-
516
- @search.command()
517
- @click.argument("query")
518
- @click.option("--top-k", default=10, help="Number of snippets to retrieve")
519
- @click.option(
520
- "--language", help="Filter by programming language (e.g., python, go, javascript)"
521
- )
522
- @click.option("--author", help="Filter by author name")
523
- @click.option(
524
- "--created-after", help="Filter snippets created after this date (YYYY-MM-DD)"
525
- )
526
- @click.option(
527
- "--created-before", help="Filter snippets created before this date (YYYY-MM-DD)"
528
- )
529
- @click.option(
530
- "--source-repo", help="Filter by source repository (e.g., github.com/example/repo)"
531
- )
532
- @click.option("--output-format", default="text", help="Format to display snippets in")
533
- @with_app_context
534
- @wrap_async
535
- async def code( # noqa: PLR0913
536
- app_context: AppContext,
537
- query: str,
538
- top_k: int,
539
- language: str | None,
540
- author: str | None,
541
- created_after: str | None,
542
- created_before: str | None,
543
- source_repo: str | None,
544
- output_format: str,
545
- ) -> None:
546
- """Search for snippets using semantic code search.
547
-
548
- This works best if your query is code.
549
- """
550
- if not app_context.is_remote:
551
- await _search_local(
552
- app_context,
553
- code_query=query,
554
- top_k=top_k,
555
- language=language,
556
- author=author,
557
- created_after=created_after,
558
- created_before=created_before,
559
- source_repo=source_repo,
560
- output_format=output_format,
561
- event_name="kodit.cli.search.code",
562
- )
563
- else:
564
- await _search_remote(
565
- app_context,
566
- code_query=query,
567
- top_k=top_k,
568
- language=language,
569
- author=author,
570
- created_after=created_after,
571
- created_before=created_before,
572
- source_repo=source_repo,
573
- output_format=output_format,
574
- )
575
-
576
-
577
- @search.command()
578
- @click.argument("keywords", nargs=-1)
579
- @click.option("--top-k", default=10, help="Number of snippets to retrieve")
580
- @click.option(
581
- "--language", help="Filter by programming language (e.g., python, go, javascript)"
582
- )
583
- @click.option("--author", help="Filter by author name")
584
- @click.option(
585
- "--created-after", help="Filter snippets created after this date (YYYY-MM-DD)"
586
- )
587
- @click.option(
588
- "--created-before", help="Filter snippets created before this date (YYYY-MM-DD)"
589
- )
590
- @click.option(
591
- "--source-repo", help="Filter by source repository (e.g., github.com/example/repo)"
592
- )
593
- @click.option("--output-format", default="text", help="Format to display snippets in")
594
- @with_app_context
595
- @wrap_async
596
- async def keyword( # noqa: PLR0913
597
- app_context: AppContext,
598
- keywords: list[str],
599
- top_k: int,
600
- language: str | None,
601
- author: str | None,
602
- created_after: str | None,
603
- created_before: str | None,
604
- source_repo: str | None,
605
- output_format: str,
606
- ) -> None:
607
- """Search for snippets using keyword search."""
608
- # Override remote configuration if provided via CLI
609
- if not app_context.is_remote:
610
- await _search_local(
611
- app_context,
612
- keywords=keywords,
613
- top_k=top_k,
614
- language=language,
615
- author=author,
616
- created_after=created_after,
617
- created_before=created_before,
618
- source_repo=source_repo,
619
- output_format=output_format,
620
- event_name="kodit.cli.search.keyword",
621
- )
622
- else:
623
- await _search_remote(
624
- app_context,
625
- keywords=keywords,
626
- top_k=top_k,
627
- language=language,
628
- author=author,
629
- created_after=created_after,
630
- created_before=created_before,
631
- source_repo=source_repo,
632
- output_format=output_format,
633
- )
634
-
635
-
636
- @search.command()
637
- @click.argument("query")
638
- @click.option("--top-k", default=10, help="Number of snippets to retrieve")
639
- @click.option(
640
- "--language", help="Filter by programming language (e.g., python, go, javascript)"
641
- )
642
- @click.option("--author", help="Filter by author name")
643
- @click.option(
644
- "--created-after", help="Filter snippets created after this date (YYYY-MM-DD)"
645
- )
646
- @click.option(
647
- "--created-before", help="Filter snippets created before this date (YYYY-MM-DD)"
648
- )
649
- @click.option(
650
- "--source-repo", help="Filter by source repository (e.g., github.com/example/repo)"
651
- )
652
- @click.option("--output-format", default="text", help="Format to display snippets in")
653
- @with_app_context
654
- @wrap_async
655
- async def text( # noqa: PLR0913
656
- app_context: AppContext,
657
- query: str,
658
- top_k: int,
659
- language: str | None,
660
- author: str | None,
661
- created_after: str | None,
662
- created_before: str | None,
663
- source_repo: str | None,
664
- output_format: str,
665
- ) -> None:
666
- """Search for snippets using semantic text search.
667
-
668
- This works best if your query is text.
669
- """
670
- if not app_context.is_remote:
671
- await _search_local(
672
- app_context,
673
- text_query=query,
674
- top_k=top_k,
675
- language=language,
676
- author=author,
677
- created_after=created_after,
678
- created_before=created_before,
679
- source_repo=source_repo,
680
- output_format=output_format,
681
- event_name="kodit.cli.search.text",
682
- )
683
- else:
684
- await _search_remote(
685
- app_context,
686
- text_query=query,
687
- top_k=top_k,
688
- language=language,
689
- author=author,
690
- created_after=created_after,
691
- created_before=created_before,
692
- source_repo=source_repo,
693
- output_format=output_format,
694
- )
695
-
696
-
697
- @search.command()
698
- @click.option("--top-k", default=10, help="Number of snippets to retrieve")
699
- @click.option("--keywords", required=True, help="Comma separated list of keywords")
700
- @click.option("--code", required=True, help="Semantic code search query")
701
- @click.option("--text", required=True, help="Semantic text search query")
702
- @click.option(
703
- "--language", help="Filter by programming language (e.g., python, go, javascript)"
704
- )
705
- @click.option("--author", help="Filter by author name")
706
- @click.option(
707
- "--created-after", help="Filter snippets created after this date (YYYY-MM-DD)"
708
- )
709
- @click.option(
710
- "--created-before", help="Filter snippets created before this date (YYYY-MM-DD)"
711
- )
712
- @click.option(
713
- "--source-repo", help="Filter by source repository (e.g., github.com/example/repo)"
714
- )
715
- @click.option("--output-format", default="text", help="Format to display snippets in")
716
- @with_app_context
717
- @wrap_async
718
- async def hybrid( # noqa: PLR0913
719
- app_context: AppContext,
720
- top_k: int,
721
- keywords: str,
722
- code: str,
723
- text: str,
724
- language: str | None,
725
- author: str | None,
726
- created_after: str | None,
727
- created_before: str | None,
728
- source_repo: str | None,
729
- output_format: str,
730
- ) -> None:
731
- """Search for snippets using hybrid search."""
732
- # Parse keywords into a list of strings
733
- keywords_list = [k.strip().lower() for k in keywords.split(",")]
734
-
735
- if not app_context.is_remote:
736
- await _search_local(
737
- app_context,
738
- keywords=keywords_list,
739
- code_query=code,
740
- text_query=text,
741
- top_k=top_k,
742
- language=language,
743
- author=author,
744
- created_after=created_after,
745
- created_before=created_before,
746
- source_repo=source_repo,
747
- output_format=output_format,
748
- event_name="kodit.cli.search.hybrid",
749
- )
750
- else:
751
- await _search_remote(
752
- app_context,
753
- keywords=keywords_list,
754
- code_query=code,
755
- text_query=text,
756
- top_k=top_k,
757
- language=language,
758
- author=author,
759
- created_after=created_after,
760
- created_before=created_before,
761
- source_repo=source_repo,
762
- output_format=output_format,
763
- )
764
-
765
-
766
- @cli.group()
767
- def show() -> None:
768
- """Show information about elements in the database."""
769
-
770
-
771
- @show.command()
772
- @click.option("--by-path", help="File or directory path to search for snippets")
773
- @click.option("--by-source", help="Source URI to filter snippets by")
774
- @click.option("--output-format", default="text", help="Format to display snippets in")
775
- @with_app_context
776
- @wrap_async
777
- async def snippets(
778
- app_context: AppContext,
779
- by_path: str | None,
780
- by_source: str | None,
781
- output_format: str,
782
- ) -> None:
783
- """Show snippets with optional filtering by path or source."""
784
- if not app_context.is_remote:
785
- # Local mode
786
- log_event("kodit.cli.show.snippets")
787
- db = await app_context.get_db()
788
- async with db.session_factory() as session:
789
- service = create_cli_code_indexing_application_service(
790
- app_context=app_context,
791
- session=session,
792
- session_factory=db.session_factory,
793
- )
794
- snippets = await service.list_snippets(
795
- file_path=by_path, source_uri=by_source
796
- )
797
- if output_format == "text":
798
- click.echo(MultiSearchResult.to_string(snippets))
799
- elif output_format == "json":
800
- click.echo(MultiSearchResult.to_jsonlines(snippets))
801
- else:
802
- # Remote mode - not supported
803
- click.echo("⚠️ Warning: 'show snippets' is not implemented in remote mode")
804
- click.echo(
805
- "This functionality is only available when connected directly "
806
- "to the database"
807
- )
808
- click.echo("Use 'kodit search' commands instead for remote snippet retrieval")
809
-
810
-
811
49
  @cli.command()
812
50
  @click.option("--host", default="127.0.0.1", help="Host to bind the server to")
813
51
  @click.option("--port", default=8080, help="Port to bind the server to")
@@ -823,6 +61,7 @@ def serve(
823
61
  # Disable uvicorn's websockets deprecation warnings
824
62
  warnings.filterwarnings("ignore", category=DeprecationWarning, module="websockets")
825
63
  warnings.filterwarnings("ignore", category=DeprecationWarning, module="uvicorn")
64
+ warnings.filterwarnings("ignore", category=DeprecationWarning, module="httpx")
826
65
 
827
66
  # Configure uvicorn with graceful shutdown
828
67
  config = uvicorn.Config(