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
@@ -1,12 +1,13 @@
1
1
  """Queue service for managing tasks."""
2
2
 
3
3
  from collections.abc import Callable
4
+ from typing import Any
4
5
 
5
6
  import structlog
6
7
  from sqlalchemy.ext.asyncio import AsyncSession
7
8
 
8
9
  from kodit.domain.entities import Task
9
- from kodit.domain.value_objects import TaskType
10
+ from kodit.domain.value_objects import QueuePriority, TaskOperation
10
11
  from kodit.infrastructure.sqlalchemy.task_repository import (
11
12
  create_task_repository,
12
13
  )
@@ -46,9 +47,29 @@ class QueueService:
46
47
  payload=task.payload,
47
48
  )
48
49
 
49
- async def list_tasks(self, task_type: TaskType | None = None) -> list[Task]:
50
+ async def enqueue_tasks(
51
+ self,
52
+ tasks: list[TaskOperation],
53
+ base_priority: QueuePriority,
54
+ payload: dict[str, Any],
55
+ ) -> None:
56
+ """Queue repository tasks."""
57
+ priority_offset = len(tasks) * 10
58
+ for task in tasks:
59
+ await self.enqueue_task(
60
+ Task.create(
61
+ task,
62
+ base_priority + priority_offset,
63
+ payload,
64
+ )
65
+ )
66
+ priority_offset -= 10
67
+
68
+ async def list_tasks(
69
+ self, task_operation: TaskOperation | None = None
70
+ ) -> list[Task]:
50
71
  """List all tasks in the queue."""
51
- return await self.task_repository.list(task_type)
72
+ return await self.task_repository.list(task_operation)
52
73
 
53
74
  async def get_task(self, task_id: str) -> Task | None:
54
75
  """Get a specific task by ID."""
@@ -1,86 +1,102 @@
1
1
  """Reporting."""
2
2
 
3
- from enum import StrEnum
4
- from types import TracebackType
3
+ from collections.abc import AsyncGenerator
4
+ from contextlib import asynccontextmanager
5
5
  from typing import TYPE_CHECKING
6
6
 
7
7
  import structlog
8
8
 
9
- from kodit.domain.value_objects import Progress, ReportingState
9
+ from kodit.domain.entities import TaskStatus
10
+ from kodit.domain.value_objects import TaskOperation, TrackableType
10
11
 
11
12
  if TYPE_CHECKING:
12
13
  from kodit.domain.protocols import ReportingModule
13
14
 
14
15
 
15
- class OperationType(StrEnum):
16
- """Operation type."""
17
-
18
- ROOT = "kodit.root"
19
- CREATE_INDEX = "kodit.index.create"
20
- RUN_INDEX = "kodit.index.run"
16
+ class ProgressTracker:
17
+ """Progress tracker.
21
18
 
19
+ Provides a reactive wrapper around TaskStatus domain entities that automatically
20
+ propagates state changes to the database and reporting modules. This pattern was
21
+ chosen over a traditional service-repository approach because:
22
+ - State changes must trigger immediate side effects (database writes, notifications)
23
+ - Multiple consumers need real-time updates without polling
24
+ - The wrapper pattern allows transparent interception of all state mutations
22
25
 
23
- class ProgressTracker:
24
- """Progress tracker."""
26
+ The tracker monitors all modifications to the underlying TaskStatus and ensures
27
+ consistency across all downstream systems.
28
+ """
25
29
 
26
- def __init__(self, name: str, parent: "ProgressTracker | None" = None) -> None:
30
+ def __init__(
31
+ self,
32
+ task_status: TaskStatus,
33
+ ) -> None:
27
34
  """Initialize the progress tracker."""
28
- self._parent: ProgressTracker | None = parent
29
- self._children: list[ProgressTracker] = []
35
+ self.task_status = task_status
30
36
  self._log = structlog.get_logger(__name__)
31
37
  self._subscribers: list[ReportingModule] = []
32
- self._snapshot: Progress = Progress(name=name, state=ReportingState.IN_PROGRESS)
33
-
34
- def __enter__(self) -> "ProgressTracker":
35
- """Enter the operation."""
36
- self._notify_subscribers()
37
- return self
38
38
 
39
- def __exit__(
40
- self,
41
- exc_type: type[BaseException] | None,
42
- exc_value: BaseException | None,
43
- traceback: TracebackType | None,
44
- ) -> None:
45
- """Exit the operation."""
46
- if exc_value:
47
- self._snapshot = self._snapshot.with_error(exc_value)
48
- self._snapshot = self._snapshot.with_state(
49
- ReportingState.FAILED, str(exc_value)
39
+ @staticmethod
40
+ def create(
41
+ operation: TaskOperation,
42
+ parent: "TaskStatus | None" = None,
43
+ trackable_type: TrackableType | None = None,
44
+ trackable_id: int | None = None,
45
+ ) -> "ProgressTracker":
46
+ """Create a progress tracker."""
47
+ return ProgressTracker(
48
+ TaskStatus.create(
49
+ operation=operation,
50
+ trackable_type=trackable_type,
51
+ trackable_id=trackable_id,
52
+ parent=parent,
50
53
  )
54
+ )
51
55
 
52
- if self._snapshot.state == ReportingState.IN_PROGRESS:
53
- self._snapshot = self._snapshot.with_progress(100)
54
- self._snapshot = self._snapshot.with_state(ReportingState.COMPLETED)
55
- self._notify_subscribers()
56
-
57
- def create_child(self, name: str) -> "ProgressTracker":
56
+ @asynccontextmanager
57
+ async def create_child(
58
+ self,
59
+ operation: TaskOperation,
60
+ trackable_type: TrackableType | None = None,
61
+ trackable_id: int | None = None,
62
+ ) -> AsyncGenerator["ProgressTracker", None]:
58
63
  """Create a child step."""
59
- s = ProgressTracker(name, self)
60
- self._children.append(s)
61
- for subscriber in self._subscribers:
62
- s.subscribe(subscriber)
63
- return s
64
-
65
- def skip(self, reason: str | None = None) -> None:
64
+ c = ProgressTracker.create(
65
+ operation=operation,
66
+ parent=self.task_status,
67
+ trackable_type=trackable_type or self.task_status.trackable_type,
68
+ trackable_id=trackable_id or self.task_status.trackable_id,
69
+ )
70
+ try:
71
+ for subscriber in self._subscribers:
72
+ c.subscribe(subscriber)
73
+
74
+ await c.notify_subscribers()
75
+ yield c
76
+ except Exception as e: # noqa: BLE001
77
+ c.task_status.fail(str(e))
78
+ finally:
79
+ c.task_status.complete()
80
+ await c.notify_subscribers()
81
+
82
+ async def skip(self, reason: str) -> None:
66
83
  """Skip the step."""
67
- self._snapshot = self._snapshot.with_state(ReportingState.SKIPPED, reason or "")
84
+ self.task_status.skip(reason)
68
85
 
69
86
  def subscribe(self, subscriber: "ReportingModule") -> None:
70
87
  """Subscribe to the step."""
71
88
  self._subscribers.append(subscriber)
72
89
 
73
- def set_total(self, total: int) -> None:
90
+ async def set_total(self, total: int) -> None:
74
91
  """Set the total for the step."""
75
- self._snapshot = self._snapshot.with_total(total)
76
- self._notify_subscribers()
92
+ self.task_status.set_total(total)
77
93
 
78
- def set_current(self, current: int) -> None:
94
+ async def set_current(self, current: int, message: str | None = None) -> None:
79
95
  """Progress the step."""
80
- self._snapshot = self._snapshot.with_progress(current)
81
- self._notify_subscribers()
96
+ self.task_status.set_current(current, message)
97
+ await self.notify_subscribers()
82
98
 
83
- def _notify_subscribers(self) -> None:
84
- """Notify the subscribers."""
99
+ async def notify_subscribers(self) -> None:
100
+ """Notify the subscribers only if progress has changed."""
85
101
  for subscriber in self._subscribers:
86
- subscriber.on_change(self._snapshot)
102
+ await subscriber.on_change(self.task_status)
@@ -1,18 +1,16 @@
1
1
  """Service for scheduling periodic sync operations."""
2
2
 
3
3
  import asyncio
4
- from collections.abc import Callable
5
4
  from contextlib import suppress
6
5
 
7
6
  import structlog
8
- from sqlalchemy.ext.asyncio import AsyncSession
9
7
 
10
8
  from kodit.application.services.queue_service import QueueService
11
- from kodit.domain.entities import Task
12
- from kodit.domain.services.index_query_service import IndexQueryService
13
- from kodit.domain.value_objects import QueuePriority
14
- from kodit.infrastructure.indexing.fusion_service import ReciprocalRankFusionService
15
- from kodit.infrastructure.sqlalchemy.index_repository import create_index_repository
9
+ from kodit.domain.protocols import GitRepoRepository
10
+ from kodit.domain.value_objects import (
11
+ PrescribedOperations,
12
+ QueuePriority,
13
+ )
16
14
 
17
15
 
18
16
  class SyncSchedulerService:
@@ -20,10 +18,12 @@ class SyncSchedulerService:
20
18
 
21
19
  def __init__(
22
20
  self,
23
- session_factory: Callable[[], AsyncSession],
21
+ queue_service: QueueService,
22
+ repo_repository: GitRepoRepository,
24
23
  ) -> None:
25
24
  """Initialize the sync scheduler service."""
26
- self.session_factory = session_factory
25
+ self.queue_service = queue_service
26
+ self.repo_repository = repo_repository
27
27
  self.log = structlog.get_logger(__name__)
28
28
  self._sync_task: asyncio.Task | None = None
29
29
  self._shutdown_event = asyncio.Event()
@@ -67,28 +67,12 @@ class SyncSchedulerService:
67
67
  """Perform a sync operation on all indexes."""
68
68
  self.log.info("Starting sync operation")
69
69
 
70
- # Create services
71
- queue_service = QueueService(session_factory=self.session_factory)
72
- index_query_service = IndexQueryService(
73
- index_repository=create_index_repository(
74
- session_factory=self.session_factory
75
- ),
76
- fusion_service=ReciprocalRankFusionService(),
77
- )
78
-
79
- # Get all existing indexes
80
- all_indexes = await index_query_service.list_indexes()
81
-
82
- if not all_indexes:
83
- self.log.info("No indexes found to sync")
84
- return
85
-
86
- self.log.info("Adding sync tasks to queue", count=len(all_indexes))
87
-
88
- # Sync each index
89
- for index in all_indexes:
90
- await queue_service.enqueue_task(
91
- Task.create_index_update_task(index.id, QueuePriority.BACKGROUND)
70
+ # Sync each index - queue all 5 tasks with priority ordering
71
+ for repo in await self.repo_repository.get_all():
72
+ await self.queue_service.enqueue_tasks(
73
+ tasks=PrescribedOperations.SYNC_REPOSITORY,
74
+ base_priority=QueuePriority.BACKGROUND,
75
+ payload={"repository_id": repo.id},
92
76
  )
93
77
 
94
78
  self.log.info("Sync operation completed")