kodit 0.4.1__py3-none-any.whl → 0.4.2__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 (42) hide show
  1. kodit/_version.py +2 -2
  2. kodit/app.py +4 -2
  3. kodit/application/factories/code_indexing_factory.py +54 -7
  4. kodit/application/factories/reporting_factory.py +27 -0
  5. kodit/application/services/auto_indexing_service.py +16 -4
  6. kodit/application/services/code_indexing_application_service.py +115 -133
  7. kodit/application/services/indexing_worker_service.py +18 -20
  8. kodit/application/services/queue_service.py +12 -14
  9. kodit/application/services/reporting.py +86 -0
  10. kodit/application/services/sync_scheduler.py +21 -20
  11. kodit/cli.py +14 -18
  12. kodit/config.py +24 -1
  13. kodit/database.py +2 -1
  14. kodit/domain/protocols.py +9 -1
  15. kodit/domain/services/bm25_service.py +1 -6
  16. kodit/domain/services/index_service.py +22 -58
  17. kodit/domain/value_objects.py +57 -9
  18. kodit/infrastructure/api/v1/dependencies.py +23 -10
  19. kodit/infrastructure/cloning/git/working_copy.py +36 -7
  20. kodit/infrastructure/embedding/embedding_factory.py +8 -3
  21. kodit/infrastructure/embedding/embedding_providers/litellm_embedding_provider.py +48 -55
  22. kodit/infrastructure/git/git_utils.py +3 -2
  23. kodit/infrastructure/mappers/index_mapper.py +1 -0
  24. kodit/infrastructure/reporting/__init__.py +1 -0
  25. kodit/infrastructure/reporting/log_progress.py +65 -0
  26. kodit/infrastructure/reporting/tdqm_progress.py +73 -0
  27. kodit/infrastructure/sqlalchemy/embedding_repository.py +47 -68
  28. kodit/infrastructure/sqlalchemy/entities.py +28 -2
  29. kodit/infrastructure/sqlalchemy/index_repository.py +274 -236
  30. kodit/infrastructure/sqlalchemy/task_repository.py +55 -39
  31. kodit/infrastructure/sqlalchemy/unit_of_work.py +59 -0
  32. kodit/mcp.py +10 -2
  33. {kodit-0.4.1.dist-info → kodit-0.4.2.dist-info}/METADATA +1 -1
  34. {kodit-0.4.1.dist-info → kodit-0.4.2.dist-info}/RECORD +37 -36
  35. kodit/domain/interfaces.py +0 -27
  36. kodit/infrastructure/ui/__init__.py +0 -1
  37. kodit/infrastructure/ui/progress.py +0 -170
  38. kodit/infrastructure/ui/spinner.py +0 -74
  39. kodit/reporting.py +0 -78
  40. {kodit-0.4.1.dist-info → kodit-0.4.2.dist-info}/WHEEL +0 -0
  41. {kodit-0.4.1.dist-info → kodit-0.4.2.dist-info}/entry_points.txt +0 -0
  42. {kodit-0.4.1.dist-info → kodit-0.4.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,7 @@
1
1
  """Task repository for the task queue."""
2
2
 
3
+ from collections.abc import Callable
4
+
3
5
  import structlog
4
6
  from sqlalchemy import select
5
7
  from sqlalchemy.ext.asyncio import AsyncSession
@@ -9,14 +11,23 @@ from kodit.domain.protocols import TaskRepository
9
11
  from kodit.domain.value_objects import TaskType
10
12
  from kodit.infrastructure.mappers.task_mapper import TaskMapper, TaskTypeMapper
11
13
  from kodit.infrastructure.sqlalchemy import entities as db_entities
14
+ from kodit.infrastructure.sqlalchemy.unit_of_work import SqlAlchemyUnitOfWork
15
+
16
+
17
+ def create_task_repository(
18
+ session_factory: Callable[[], AsyncSession],
19
+ ) -> TaskRepository:
20
+ """Create an index repository."""
21
+ uow = SqlAlchemyUnitOfWork(session_factory=session_factory)
22
+ return SqlAlchemyTaskRepository(uow)
12
23
 
13
24
 
14
25
  class SqlAlchemyTaskRepository(TaskRepository):
15
26
  """Repository for task persistence using the existing Task entity."""
16
27
 
17
- def __init__(self, session: AsyncSession) -> None:
28
+ def __init__(self, uow: SqlAlchemyUnitOfWork) -> None:
18
29
  """Initialize the repository."""
19
- self.session = session
30
+ self.uow = uow
20
31
  self.log = structlog.get_logger(__name__)
21
32
 
22
33
  async def add(
@@ -24,58 +35,63 @@ class SqlAlchemyTaskRepository(TaskRepository):
24
35
  task: Task,
25
36
  ) -> None:
26
37
  """Create a new task in the database."""
27
- self.session.add(TaskMapper.from_domain_task(task))
38
+ async with self.uow:
39
+ self.uow.session.add(TaskMapper.from_domain_task(task))
28
40
 
29
41
  async def get(self, task_id: str) -> Task | None:
30
42
  """Get a task by ID."""
31
- stmt = select(db_entities.Task).where(db_entities.Task.dedup_key == task_id)
32
- result = await self.session.execute(stmt)
33
- db_task = result.scalar_one_or_none()
34
- if not db_task:
35
- return None
36
- return TaskMapper.to_domain_task(db_task)
43
+ async with self.uow:
44
+ stmt = select(db_entities.Task).where(db_entities.Task.dedup_key == task_id)
45
+ result = await self.uow.session.execute(stmt)
46
+ db_task = result.scalar_one_or_none()
47
+ if not db_task:
48
+ return None
49
+ return TaskMapper.to_domain_task(db_task)
37
50
 
38
51
  async def take(self) -> Task | None:
39
52
  """Take a task for processing and remove it from the database."""
40
- stmt = (
41
- select(db_entities.Task)
42
- .order_by(db_entities.Task.priority.desc(), db_entities.Task.created_at)
43
- .limit(1)
44
- )
45
- result = await self.session.execute(stmt)
46
- db_task = result.scalar_one_or_none()
47
- if not db_task:
48
- return None
49
- await self.session.delete(db_task)
50
- return TaskMapper.to_domain_task(db_task)
53
+ async with self.uow:
54
+ stmt = (
55
+ select(db_entities.Task)
56
+ .order_by(db_entities.Task.priority.desc(), db_entities.Task.created_at)
57
+ .limit(1)
58
+ )
59
+ result = await self.uow.session.execute(stmt)
60
+ db_task = result.scalar_one_or_none()
61
+ if not db_task:
62
+ return None
63
+ await self.uow.session.delete(db_task)
64
+ return TaskMapper.to_domain_task(db_task)
51
65
 
52
66
  async def update(self, task: Task) -> None:
53
67
  """Update a task in the database."""
54
- stmt = select(db_entities.Task).where(db_entities.Task.dedup_key == task.id)
55
- result = await self.session.execute(stmt)
56
- db_task = result.scalar_one_or_none()
68
+ async with self.uow:
69
+ stmt = select(db_entities.Task).where(db_entities.Task.dedup_key == task.id)
70
+ result = await self.uow.session.execute(stmt)
71
+ db_task = result.scalar_one_or_none()
57
72
 
58
- if not db_task:
59
- raise ValueError(f"Task not found: {task.id}")
73
+ if not db_task:
74
+ raise ValueError(f"Task not found: {task.id}")
60
75
 
61
- db_task.priority = task.priority
62
- db_task.payload = task.payload
76
+ db_task.priority = task.priority
77
+ db_task.payload = task.payload
63
78
 
64
79
  async def list(self, task_type: TaskType | None = None) -> list[Task]:
65
80
  """List tasks with optional status filter."""
66
- stmt = select(db_entities.Task)
81
+ async with self.uow:
82
+ stmt = select(db_entities.Task)
67
83
 
68
- if task_type:
69
- stmt = stmt.where(
70
- db_entities.Task.type == TaskTypeMapper.from_domain_type(task_type)
71
- )
84
+ if task_type:
85
+ stmt = stmt.where(
86
+ db_entities.Task.type == TaskTypeMapper.from_domain_type(task_type)
87
+ )
72
88
 
73
- stmt = stmt.order_by(
74
- db_entities.Task.priority.desc(), db_entities.Task.created_at
75
- )
89
+ stmt = stmt.order_by(
90
+ db_entities.Task.priority.desc(), db_entities.Task.created_at
91
+ )
76
92
 
77
- result = await self.session.execute(stmt)
78
- records = result.scalars().all()
93
+ result = await self.uow.session.execute(stmt)
94
+ records = result.scalars().all()
79
95
 
80
- # Convert to domain entities
81
- return [TaskMapper.to_domain_task(record) for record in records]
96
+ # Convert to domain entities
97
+ return [TaskMapper.to_domain_task(record) for record in records]
@@ -0,0 +1,59 @@
1
+ """SQLAlchemy implementation of Unit of Work pattern."""
2
+
3
+ from collections.abc import Callable
4
+ from types import TracebackType
5
+
6
+ from sqlalchemy.ext.asyncio import AsyncSession
7
+
8
+
9
+ class SqlAlchemyUnitOfWork:
10
+ """SQLAlchemy implementation of Unit of Work pattern."""
11
+
12
+ def __init__(self, session_factory: Callable[[], AsyncSession]) -> None:
13
+ """Initialize the unit of work with a session factory."""
14
+ self._session_factory = session_factory
15
+ self._session: AsyncSession | None = None
16
+
17
+ @property
18
+ def session(self) -> AsyncSession:
19
+ """Get the current session."""
20
+ if self._session is None:
21
+ raise RuntimeError("UnitOfWork must be used within async context")
22
+ return self._session
23
+
24
+ async def __aenter__(self) -> "SqlAlchemyUnitOfWork":
25
+ """Enter the unit of work context."""
26
+ self._session = self._session_factory()
27
+ return self
28
+
29
+ async def __aexit__(
30
+ self,
31
+ exc_type: type[BaseException] | None,
32
+ exc_val: BaseException | None,
33
+ exc_tb: TracebackType | None,
34
+ ) -> None:
35
+ """Exit the unit of work context."""
36
+ if self._session:
37
+ if exc_type is not None:
38
+ await self._session.rollback()
39
+ await self._session.commit()
40
+ await self._session.close()
41
+ self._session = None
42
+
43
+ async def commit(self) -> None:
44
+ """Commit the current transaction."""
45
+ if self._session is None:
46
+ raise RuntimeError("UnitOfWork must be used within async context")
47
+ await self._session.commit()
48
+
49
+ async def rollback(self) -> None:
50
+ """Rollback the current transaction."""
51
+ if self._session is None:
52
+ raise RuntimeError("UnitOfWork must be used within async context")
53
+ await self._session.rollback()
54
+
55
+ async def flush(self) -> None:
56
+ """Flush pending changes to the database without committing."""
57
+ if self._session is None:
58
+ raise RuntimeError("UnitOfWork must be used within async context")
59
+ await self._session.flush()
kodit/mcp.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """MCP server for kodit."""
2
2
 
3
- from collections.abc import AsyncIterator
3
+ from collections.abc import AsyncIterator, Callable
4
4
  from contextlib import asynccontextmanager
5
5
  from dataclasses import dataclass
6
6
  from pathlib import Path
@@ -15,6 +15,7 @@ from kodit._version import version
15
15
  from kodit.application.factories.code_indexing_factory import (
16
16
  create_code_indexing_application_service,
17
17
  )
18
+ from kodit.application.factories.reporting_factory import create_server_operation
18
19
  from kodit.config import AppContext
19
20
  from kodit.database import Database
20
21
  from kodit.domain.value_objects import (
@@ -32,6 +33,7 @@ class MCPContext:
32
33
  """Context for the MCP server."""
33
34
 
34
35
  session: AsyncSession
36
+ session_factory: Callable[[], AsyncSession]
35
37
  app_context: AppContext
36
38
 
37
39
 
@@ -55,7 +57,11 @@ async def mcp_lifespan(_: FastMCP) -> AsyncIterator[MCPContext]:
55
57
  if _mcp_db is None:
56
58
  _mcp_db = await app_context.get_db()
57
59
  async with _mcp_db.session_factory() as session:
58
- yield MCPContext(session=session, app_context=app_context)
60
+ yield MCPContext(
61
+ session=session,
62
+ app_context=app_context,
63
+ session_factory=_mcp_db.session_factory,
64
+ )
59
65
 
60
66
 
61
67
  def create_mcp_server(name: str, instructions: str | None = None) -> FastMCP:
@@ -174,6 +180,8 @@ def register_mcp_tools(mcp_server: FastMCP) -> None:
174
180
  service = create_code_indexing_application_service(
175
181
  app_context=mcp_context.app_context,
176
182
  session=mcp_context.session,
183
+ session_factory=mcp_context.session_factory,
184
+ operation=create_server_operation(),
177
185
  )
178
186
 
179
187
  log.debug("Searching for snippets")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kodit
3
- Version: 0.4.1
3
+ Version: 0.4.2
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/
@@ -1,36 +1,36 @@
1
1
  kodit/.gitignore,sha256=ztkjgRwL9Uud1OEi36hGQeDGk3OLK1NfDEO8YqGYy8o,11
2
2
  kodit/__init__.py,sha256=aEKHYninUq1yh6jaNfvJBYg-6fenpN132nJt1UU6Jxs,59
3
- kodit/_version.py,sha256=k7cu0JKra64gmMNU_UfA5sw2eNc_GRvf3QmesiYAy8g,704
4
- kodit/app.py,sha256=FKqHbJNoHpnBb5KLJaUINDm0a5cHpxlrQ5qNoWnHsBc,4326
5
- kodit/cli.py,sha256=VUZD4cPRgAnrKEWUl2PbS-nOA0FkDVqmJ2SR0g1yJsk,28202
3
+ kodit/_version.py,sha256=A45grTqzrHuDn1CT9K5GVUbY4_Q3OSTcXAl3zdHzcEI,704
4
+ kodit/app.py,sha256=xLy0cM3fduXSQSws3wq9fWg5eJB1xD6vrMpkVFYpnhA,4468
5
+ kodit/cli.py,sha256=ugy0L9m5lVgudLebD5FpmZfJEAVAtvxbCvUTfJvU46Y,27948
6
6
  kodit/cli_utils.py,sha256=bW4rIm-elrsyM_pSGHh30zV0_oX7V-64pL3YSaBcOt0,2810
7
- kodit/config.py,sha256=EOyB3BfSDfGJeOts2vrOyX8lhGXbAHVUl69kzjqGHXE,10326
8
- kodit/database.py,sha256=kI9yBm4uunsgV4-QeVoCBL0wLzU4kYmYv5qZilGnbPE,1740
7
+ kodit/config.py,sha256=wKXUb06j7VbpD7ydCARd6_DNeAY5tLeJqHvhWozFhyI,11052
8
+ kodit/database.py,sha256=k93byjVUX1VjAb0hLZxUo4liEKKxAWUBJNw2e7rzaiI,1771
9
9
  kodit/log.py,sha256=ZpM0eMo_DVGQqrHxg0VV6dMrN2AAmu_3C0I3G7p2nMw,8828
10
- kodit/mcp.py,sha256=aEcPc8dQiZaR0AswCZZNxcm_rhhUZNsEBimYti0ibSI,7221
10
+ kodit/mcp.py,sha256=GWh9krkcP37wh8ZmvfXaGJPknhaautBxzvbMMr5FRdg,7555
11
11
  kodit/middleware.py,sha256=TiwebNpaEmiP7QRuZrfZcCL51IUefQyNLSPuzVyk8UM,2813
12
- kodit/reporting.py,sha256=icce1ZyiADsA_Qz-mSjgn2H4SSqKuGfLKnw-yrl9nsg,2722
13
12
  kodit/application/__init__.py,sha256=mH50wTpgP9dhbKztFsL8Dda9Hi18TSnMVxXtpp4aGOA,35
14
13
  kodit/application/factories/__init__.py,sha256=bU5CvEnaBePZ7JbkCOp1MGTNP752bnU2uEqmfy5FdRk,37
15
- kodit/application/factories/code_indexing_factory.py,sha256=R9f0wsj4-3NJFS5SEt_-OIGR_s_01gJXaL3PkZd8MlU,5911
14
+ kodit/application/factories/code_indexing_factory.py,sha256=4c5LS2t7FOHiNS_Xb5sPRngf3we-VbTWKa-NcjZmf0Q,7300
15
+ kodit/application/factories/reporting_factory.py,sha256=Plf3c1KIx36eM5YefU5svPr9QeaNcKFH5UlmDuET8R0,1013
16
16
  kodit/application/services/__init__.py,sha256=p5UQNw-H5sxQvs5Etfte93B3cJ1kKW6DNxK34uFvU1E,38
17
- kodit/application/services/auto_indexing_service.py,sha256=O5BNR5HypgghzUFG4ykIWMl9mxHCUExnBmJuITIhECk,3457
18
- kodit/application/services/code_indexing_application_service.py,sha256=nrnd_Md-D0AfNKku7Aqt3YHDbXsBV9f44Z6XsjhiF3E,15877
19
- kodit/application/services/indexing_worker_service.py,sha256=Un4PytnWJU4uwROcxOMUFkt4cD7nmPezaBLsEHrMN6U,5185
20
- kodit/application/services/queue_service.py,sha256=vf_TEl76B0F0RSvfCeGDuM-QFzW-VUuj3zQaRmDPEYI,1921
21
- kodit/application/services/sync_scheduler.py,sha256=aLpEczZdTM8ubfAEY0Ajdh3MLfDcB9s-0ILZJrtIuZs,3504
17
+ kodit/application/services/auto_indexing_service.py,sha256=rJPWiV755eskFNKjYliPr1WMFylXlG8BWPpFcwwOhm0,3973
18
+ kodit/application/services/code_indexing_application_service.py,sha256=tLbbo-fyAc3iZoCOJU9lIfhNI_6Lz9SqQdfjeN5m8yA,16213
19
+ kodit/application/services/indexing_worker_service.py,sha256=B8MdXrzjaYVS7zVTTz8cXUQItkGb8Fk1aXeim2dfCJw,5311
20
+ kodit/application/services/queue_service.py,sha256=G42lR31maFRZ9cSvnWZrzeyb4P1R6yFqrcHWVKAqc9U,1924
21
+ kodit/application/services/reporting.py,sha256=hDisTU_XUBTfiOtnJ5-6x0jj8rHSlq9zZgeNnBT7W5Y,2834
22
+ kodit/application/services/sync_scheduler.py,sha256=FUUpDtxUh7Eg-lnzOrUHzmSWGpzdpYJQYgPhnQYwTcg,3446
22
23
  kodit/domain/__init__.py,sha256=TCpg4Xx-oF4mKV91lo4iXqMEfBT1OoRSYnbG-zVWolA,66
23
24
  kodit/domain/entities.py,sha256=QsCzKXT7gF9jTPAjJo5lqjFGRsIklAFC2qRy_Gt3RbA,10377
24
25
  kodit/domain/errors.py,sha256=yIsgCjM_yOFIg8l7l-t7jM8pgeAX4cfPq0owf7iz3DA,106
25
- kodit/domain/interfaces.py,sha256=Jkd0Ob4qSvhZHI9jRPFQ1n5Cv0SvU-y3Z-HCw2ikc4I,742
26
- kodit/domain/protocols.py,sha256=GA0CCvmhvQ3F4MseeQUVw3NeIgUoaV7V_7TdAaU70Is,2587
27
- kodit/domain/value_objects.py,sha256=dkfbg99PSCrfj6nJ7tZ2UzDG3QUgNa_Cpj2gLakDM5k,17512
26
+ kodit/domain/protocols.py,sha256=RGNOlHyvNq6Nx_95ETTO9DkzeZmjtubfC7qdGvA5iPk,2753
27
+ kodit/domain/value_objects.py,sha256=uIpAdIvq6VefEGa8yq5Uqyuyit72SHtDptnoOUd73u0,18882
28
28
  kodit/domain/services/__init__.py,sha256=Q1GhCK_PqKHYwYE4tkwDz5BIyXkJngLBBOHhzvX8nzo,42
29
- kodit/domain/services/bm25_service.py,sha256=nsfTan3XtDwXuuAu1LUv-6Jukm6qFKVqqCVymjyepZQ,3625
29
+ kodit/domain/services/bm25_service.py,sha256=seRo0V-zW6Uq-Y67j0-zp1xz93gbfQgvlEbpQeYHN1U,3529
30
30
  kodit/domain/services/embedding_service.py,sha256=7drYRC2kjg0WJmo06a2E9N0vDnwInUlBB96twjz2BT8,4526
31
31
  kodit/domain/services/enrichment_service.py,sha256=XsXg3nV-KN4rqtC7Zro_ZiZ6RSq-1eA1MG6IDzFGyBA,1316
32
32
  kodit/domain/services/index_query_service.py,sha256=cDQkgpJ3JbyeZ3z3GTIqH1JzhhKE_LBIwYE6b-lakwU,2172
33
- kodit/domain/services/index_service.py,sha256=uVwDUEQWfZ5yJRvcjaWW7P9gCZttmnlkI51IHz52eew,11554
33
+ kodit/domain/services/index_service.py,sha256=TSvM-UuOtq30hz6eNPgu9AEVFrLDugdYoBgBf1xZDcI,10377
34
34
  kodit/infrastructure/__init__.py,sha256=HzEYIjoXnkz_i_MHO2e0sIVYweUcRnl2RpyBiTbMObU,28
35
35
  kodit/infrastructure/api/__init__.py,sha256=U0TSMPpHrlj1zbAtleuZjU3nXGwudyMe-veNBgvODwM,34
36
36
  kodit/infrastructure/api/client/__init__.py,sha256=6RSYqeuxjDe_zTUq48D0F-VfBBUvDmTkO3K3vD61q3I,349
@@ -42,7 +42,7 @@ kodit/infrastructure/api/client/search_client.py,sha256=f4mM5ZJpAuR7w-i9yASbh4SY
42
42
  kodit/infrastructure/api/middleware/__init__.py,sha256=6m7eE5k5buboJbuzyX5E9-Tf99yNwFaeJF0f_6HwLyM,30
43
43
  kodit/infrastructure/api/middleware/auth.py,sha256=QSnMcMLWvfumqN1iG4ePj2vEZb2Dlsgr-WHptkEkkhE,1064
44
44
  kodit/infrastructure/api/v1/__init__.py,sha256=hQ03es21FSgzQlmdP5xWZzK80woIvuYGjiZLwFYuYwk,151
45
- kodit/infrastructure/api/v1/dependencies.py,sha256=jaM000IfSnvU8uzwnC1cBZsfsMC-19jWFjObHfqBYuM,2475
45
+ kodit/infrastructure/api/v1/dependencies.py,sha256=MBmCpTtwDAtdsLjJ06Bzod3Vwqon8mMASknZobdoaMU,2919
46
46
  kodit/infrastructure/api/v1/routers/__init__.py,sha256=YYyeiuyphIPc-Q_2totF8zfR0BoseOH4ZYFdHP0ed_M,218
47
47
  kodit/infrastructure/api/v1/routers/indexes.py,sha256=_lUir1M0SW6kPHeGqjiPjtSa50rY4PN2es5TZEpSHYE,3442
48
48
  kodit/infrastructure/api/v1/routers/queue.py,sha256=EZbR-G0qDO9W5ajV_75GRk2pW1Qdgc0ggOwrGKlBE2A,2138
@@ -59,15 +59,15 @@ kodit/infrastructure/bm25/vectorchord_bm25_repository.py,sha256=p6ht5K-jlDTvEkmo
59
59
  kodit/infrastructure/cloning/__init__.py,sha256=IzIvX-yeRRFZ-lfvPVSEe_qXszO6DGQdjKwwDigexyQ,30
60
60
  kodit/infrastructure/cloning/metadata.py,sha256=GD2UnCC1oR82RD0SVUqk9CJOqzXPxhOAHVOp7jqN6Qc,3571
61
61
  kodit/infrastructure/cloning/git/__init__.py,sha256=20ePcp0qE6BuLsjsv4KYB1DzKhMIMsPXwEqIEZtjTJs,34
62
- kodit/infrastructure/cloning/git/working_copy.py,sha256=qYcrR5qP1rhWZiYGMT1p-1Alavi_YvQLXx4MgIV7eXs,2611
62
+ kodit/infrastructure/cloning/git/working_copy.py,sha256=Lt_NWSoQ1pZAi0u_MKUhrwGeul4XWf3zqCuzG3dn70s,3608
63
63
  kodit/infrastructure/embedding/__init__.py,sha256=F-8nLlWAerYJ0MOIA4tbXHLan8bW5rRR84vzxx6tRKI,39
64
- kodit/infrastructure/embedding/embedding_factory.py,sha256=wngBD2g6NniHDq_-KcYhhwSvmcMYyI8yIzoXvGQvu1U,3287
64
+ kodit/infrastructure/embedding/embedding_factory.py,sha256=BNhrrYQAkcnXkuuQy-Q-lwJhyoGONsTsbgN4t0UdGeY,3395
65
65
  kodit/infrastructure/embedding/local_vector_search_repository.py,sha256=ExweyNEL5cP-g3eDhGqZSih7zhdOrop2WdFPPJL-tB4,3505
66
66
  kodit/infrastructure/embedding/vectorchord_vector_search_repository.py,sha256=PIoU0HsDlaoXDXnGjOR0LAkAcW4JiE3ymJy_SBhEopc,8030
67
67
  kodit/infrastructure/embedding/embedding_providers/__init__.py,sha256=qeZ-oAIAxMl5QqebGtO1lq-tHjl_ucAwOXePklcwwGk,34
68
68
  kodit/infrastructure/embedding/embedding_providers/batching.py,sha256=a8CL9PX2VLmbeg616fc_lQzfC4BWTVn32m4SEhXpHxc,3279
69
69
  kodit/infrastructure/embedding/embedding_providers/hash_embedding_provider.py,sha256=V6OdCuWyQQOvo3OJGRi-gBKDApIcrELydFg7T696P5s,2257
70
- kodit/infrastructure/embedding/embedding_providers/litellm_embedding_provider.py,sha256=5LCrPSQn3ZaLZ1XTKzJV_LzANH7FdaR4NL-gJupaiDE,5579
70
+ kodit/infrastructure/embedding/embedding_providers/litellm_embedding_provider.py,sha256=9Q5he_MI8xXENODwCvYCbhVawTjTv1bArGQrmxoWLas,5297
71
71
  kodit/infrastructure/embedding/embedding_providers/local_embedding_provider.py,sha256=9aLV1Zg4KMhYWlGRwgAUtswW4aIabNqbsipWhAn64RI,4133
72
72
  kodit/infrastructure/enrichment/__init__.py,sha256=8acZKNzql8Fs0lceFu9U3KoUrOptRBtVIxr_Iw6lz3Y,40
73
73
  kodit/infrastructure/enrichment/enrichment_factory.py,sha256=NFGY6u9SJ_GOgiB_RtotbQmte0kGFQUymwzZCbbsx34,1530
@@ -76,25 +76,26 @@ kodit/infrastructure/enrichment/local_enrichment_provider.py,sha256=aVU3_kbLJ0Bi
76
76
  kodit/infrastructure/enrichment/null_enrichment_provider.py,sha256=DhZkJBnkvXg_XSAs-oKiFnKqYFPnmTl3ikdxrqeEfbc,713
77
77
  kodit/infrastructure/enrichment/utils.py,sha256=FE9UCuxxzSdoHrmAC8Si2b5D6Nf6kVqgM1yjUVyCvW0,930
78
78
  kodit/infrastructure/git/__init__.py,sha256=0iMosFzudj4_xNIMe2SRbV6l5bWqkjnUsZoFsoZFuM8,33
79
- kodit/infrastructure/git/git_utils.py,sha256=3Fg2ZX9pkp8Mk1mWuW30PSO_ZKXrPL7wTCS9TMTfIUM,765
79
+ kodit/infrastructure/git/git_utils.py,sha256=5lH94AcF7Hac4h6kBzo_B9pzC1S6AK2-Dy13gz--Zf0,781
80
80
  kodit/infrastructure/ignore/__init__.py,sha256=VzFv8XOzHmsu0MEGnWVSF6KsgqLBmvHlRqAkT1Xb1MY,36
81
81
  kodit/infrastructure/ignore/ignore_pattern_provider.py,sha256=zdxun3GodLfXxyssBK8QDUK58xb4fBJ0SKcHUyn3pzM,2131
82
82
  kodit/infrastructure/indexing/__init__.py,sha256=7UPRa2jwCAsa0Orsp6PqXSF8iIXJVzXHMFmrKkI9yH8,38
83
83
  kodit/infrastructure/indexing/fusion_service.py,sha256=2B0guBsuKz19uWcs18sIJpUJPzXoRvULgl7UNWQGysA,1809
84
84
  kodit/infrastructure/mappers/__init__.py,sha256=QPHOjNreXmBPPovZ6elnYFS0vD-IsmrGl4TT01FCKro,77
85
- kodit/infrastructure/mappers/index_mapper.py,sha256=ZSfu8kjTaa8_UY0nTqr4b02NS3VrjqZYkduCN71AL2g,12743
85
+ kodit/infrastructure/mappers/index_mapper.py,sha256=XWtv_him2Sd9dR-Jy_ndy9jYXVtv3LttzmmUGzNK6CE,12825
86
86
  kodit/infrastructure/mappers/task_mapper.py,sha256=QW7uL8rji6QJ7RRdHwbvkWqmwDcUDGTYPLwbwiKlViY,2919
87
+ kodit/infrastructure/reporting/__init__.py,sha256=4Qu38YbDOaeDqLdT_CbK8tOZHTKGrHRXncVKlGRzOeQ,32
88
+ kodit/infrastructure/reporting/log_progress.py,sha256=sNF0oeg56NaTfO3DVg1AXQWwgrHSTaZrOWqPDq-FhVE,2180
89
+ kodit/infrastructure/reporting/tdqm_progress.py,sha256=g01P7PItQqqSXzM5jjXL6uOUIKJQ6O9zaO1WZZ7XKSM,2512
87
90
  kodit/infrastructure/slicing/__init__.py,sha256=x7cjvHA9Ay2weUYE_dpdAaPaStp20M-4U2b5MLgT5KM,37
88
91
  kodit/infrastructure/slicing/language_detection_service.py,sha256=JGJXrq9bLyfnisWJXeP7y1jbZMmKAISdPBlRBCosUcE,684
89
92
  kodit/infrastructure/slicing/slicer.py,sha256=GOqJykd00waOTO1WJHyE5KUgJ2RLx2rOQ7M7T_u5LLg,35600
90
93
  kodit/infrastructure/sqlalchemy/__init__.py,sha256=UXPMSF_hgWaqr86cawRVqM8XdVNumQyyK5B8B97GnlA,33
91
- kodit/infrastructure/sqlalchemy/embedding_repository.py,sha256=dC2Wzj_zQiWExwfScE1LAGiiyxPyg0YepwyLOgDwcs4,7905
92
- kodit/infrastructure/sqlalchemy/entities.py,sha256=2iG_2NoKS26rJXimLFL8whRqFsUvKNGFAXCkQYK5GtE,6951
93
- kodit/infrastructure/sqlalchemy/index_repository.py,sha256=QQNsyLBI09YLUPLguB9qvqPZMxtg1p2twpm7sO_gNlo,23598
94
- kodit/infrastructure/sqlalchemy/task_repository.py,sha256=yazMO6Kw0Pb2b3L8wlGKOFA0QuMFcWBUXYFGZdtZo0w,2874
95
- kodit/infrastructure/ui/__init__.py,sha256=CzbLOBwIZ6B6iAHEd1L8cIBydCj-n_kobxJAhz2I9_Y,32
96
- kodit/infrastructure/ui/progress.py,sha256=SHEUoQA_x36z4nqHrQduVrrWIvFfX6QxAawC7zQ50pw,6433
97
- kodit/infrastructure/ui/spinner.py,sha256=GcP115qtR0VEnGfMEtsGoAUpRzVGUSfiUXfoJJERngA,2357
94
+ kodit/infrastructure/sqlalchemy/embedding_repository.py,sha256=YYxbUEdzDdlKdy0FyAP4EzhJMAIdEnNZiXT6hzPHk9I,7731
95
+ kodit/infrastructure/sqlalchemy/entities.py,sha256=P3BitWqnTxMVXmyez7OX-SB3-UG66XorqvPMjXspwoM,7894
96
+ kodit/infrastructure/sqlalchemy/index_repository.py,sha256=x8MPl0j7GrW_lEZh464EZyb0w935p_EHv2NIMNxjJu0,25680
97
+ kodit/infrastructure/sqlalchemy/task_repository.py,sha256=60ECbxiXC2_UR80f4uPSmJiP_so7PTBzZG_w1WXSiuE,3546
98
+ kodit/infrastructure/sqlalchemy/unit_of_work.py,sha256=gK-C8yk2HYBrAEDrblWxBrldrGb83SBHn-8lURkFeMg,2093
98
99
  kodit/migrations/README,sha256=ISVtAOvqvKk_5ThM5ioJE-lMkvf9IbknFUFVU_vPma4,58
99
100
  kodit/migrations/__init__.py,sha256=lP5MuwlyWRMO6UcDWnQcQ3G-GYHcFb6rl9gYPHJ1sjo,40
100
101
  kodit/migrations/env.py,sha256=m57TkFLYjQ4w2aw1YICXkeek27M6qjwRDMHvThWqIL0,2383
@@ -111,8 +112,8 @@ kodit/utils/__init__.py,sha256=DPEB1i8evnLF4Ns3huuAYg-0pKBFKUFuiDzOKG9r-sw,33
111
112
  kodit/utils/dump_openapi.py,sha256=29VdjHpNSaGAg7RjQw0meq1OLhljCx1ElgBlTC8xoF4,1247
112
113
  kodit/utils/generate_api_paths.py,sha256=TMtx9v55podDfUmiWaHgJHLtEWLV2sLL-5ejGFMPzAo,3569
113
114
  kodit/utils/path_utils.py,sha256=thK6YGGNvQThdBaCYCCeCvS1L8x-lwl3AoGht2jnjGw,1645
114
- kodit-0.4.1.dist-info/METADATA,sha256=Mf4UuPg2D08hfp1STtsZ1DwOHA2a1J4ba5_-T7Pifr4,7702
115
- kodit-0.4.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
116
- kodit-0.4.1.dist-info/entry_points.txt,sha256=hoTn-1aKyTItjnY91fnO-rV5uaWQLQ-Vi7V5et2IbHY,40
117
- kodit-0.4.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
118
- kodit-0.4.1.dist-info/RECORD,,
115
+ kodit-0.4.2.dist-info/METADATA,sha256=bC5eza2ORs3v3w5-bwW1uybuk8b4JdNI13GZQvrP4ps,7702
116
+ kodit-0.4.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
117
+ kodit-0.4.2.dist-info/entry_points.txt,sha256=hoTn-1aKyTItjnY91fnO-rV5uaWQLQ-Vi7V5et2IbHY,40
118
+ kodit-0.4.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
119
+ kodit-0.4.2.dist-info/RECORD,,
@@ -1,27 +0,0 @@
1
- """Domain interfaces."""
2
-
3
- from abc import ABC, abstractmethod
4
-
5
- from kodit.domain.value_objects import ProgressEvent
6
-
7
-
8
- class ProgressCallback(ABC):
9
- """Abstract interface for progress callbacks."""
10
-
11
- @abstractmethod
12
- async def on_progress(self, event: ProgressEvent) -> None:
13
- """On progress hook."""
14
-
15
- @abstractmethod
16
- async def on_complete(self, operation: str) -> None:
17
- """On complete hook."""
18
-
19
-
20
- class NullProgressCallback(ProgressCallback):
21
- """Null implementation of progress callback that does nothing."""
22
-
23
- async def on_progress(self, event: ProgressEvent) -> None:
24
- """Do nothing on progress."""
25
-
26
- async def on_complete(self, operation: str) -> None:
27
- """Do nothing on complete."""
@@ -1 +0,0 @@
1
- """UI infrastructure module."""
@@ -1,170 +0,0 @@
1
- """Progress UI implementations."""
2
-
3
- from collections.abc import Callable
4
-
5
- import structlog
6
- from tqdm import tqdm # type: ignore[import-untyped]
7
-
8
- from kodit.domain.interfaces import ProgressCallback
9
- from kodit.domain.value_objects import ProgressEvent
10
-
11
-
12
- class TQDMProgressCallback(ProgressCallback):
13
- """TQDM-based progress callback implementation."""
14
-
15
- def __init__(self, pbar: "tqdm") -> None:
16
- """Initialize with a TQDM progress bar."""
17
- self.pbar = pbar
18
-
19
- async def on_progress(self, event: ProgressEvent) -> None:
20
- """Update the TQDM progress bar."""
21
- # Update total if it changes
22
- if event.total != self.pbar.total:
23
- self.pbar.total = event.total
24
-
25
- # Update the progress bar
26
- self.pbar.n = event.current
27
- self.pbar.refresh()
28
-
29
- # Update description if message is provided
30
- if event.message:
31
- # Fix the event message to a specific size so it's not jumping around
32
- # If it's too small, add spaces
33
- # If it's too large, truncate
34
- if len(event.message) < 30:
35
- self.pbar.set_description(
36
- event.message + " " * (30 - len(event.message))
37
- )
38
- else:
39
- self.pbar.set_description(event.message[-30:])
40
-
41
- async def on_complete(self, operation: str) -> None:
42
- """Complete the progress bar."""
43
- # TQDM will handle cleanup with leave=False
44
-
45
-
46
- class LogProgressCallback(ProgressCallback):
47
- """Log-based progress callback for server environments."""
48
-
49
- def __init__(self, milestone_interval: int = 10) -> None:
50
- """Initialize with milestone logging interval.
51
-
52
- Args:
53
- milestone_interval: Percentage interval for logging (default: 10%)
54
-
55
- """
56
- self.milestone_interval = milestone_interval
57
- self._last_logged_percentage = -1
58
- self.log = structlog.get_logger()
59
-
60
- async def on_progress(self, event: ProgressEvent) -> None:
61
- """Log progress at milestone intervals."""
62
- percentage = int(event.percentage)
63
-
64
- # Log at milestone intervals (0%, 10%, 20%, etc.)
65
- milestone = (percentage // self.milestone_interval) * self.milestone_interval
66
-
67
- if milestone > self._last_logged_percentage and milestone <= percentage:
68
- self.log.info(
69
- "Progress milestone reached",
70
- operation=event.operation,
71
- percentage=milestone,
72
- current=event.current,
73
- total=event.total,
74
- message=event.message,
75
- )
76
- self._last_logged_percentage = milestone
77
-
78
- async def on_complete(self, operation: str) -> None:
79
- """Log completion of the operation."""
80
- self.log.info("Operation completed", operation=operation)
81
-
82
-
83
- class LazyProgressCallback(ProgressCallback):
84
- """Progress callback that only shows progress when there's actual work to do."""
85
-
86
- def __init__(self, create_pbar_func: Callable[[], tqdm]) -> None:
87
- """Initialize with a function that creates a progress bar."""
88
- self.create_pbar_func = create_pbar_func
89
- self._callback: ProgressCallback | None = None
90
- self._has_work = False
91
-
92
- async def on_progress(self, event: ProgressEvent) -> None:
93
- """Update progress, creating the actual callback if needed."""
94
- if not self._has_work:
95
- self._has_work = True
96
- # Only create the progress bar when we actually have work to do
97
- pbar = self.create_pbar_func()
98
- self._callback = TQDMProgressCallback(pbar)
99
-
100
- if self._callback:
101
- await self._callback.on_progress(event)
102
-
103
- async def on_complete(self, operation: str) -> None:
104
- """Complete the progress operation."""
105
- if self._callback:
106
- await self._callback.on_complete(operation)
107
-
108
-
109
- class MultiStageProgressCallback(ProgressCallback):
110
- """Progress callback that handles multiple stages with separate progress bars."""
111
-
112
- def __init__(self, create_pbar_func: Callable[[str], tqdm]) -> None:
113
- """Initialize with a function that creates progress bars."""
114
- self.create_pbar_func = create_pbar_func
115
- self._current_callback: ProgressCallback | None = None
116
- self._current_operation: str | None = None
117
-
118
- async def on_progress(self, event: ProgressEvent) -> None:
119
- """Update progress for the current operation."""
120
- # If this is a new operation, create a new progress bar
121
- if self._current_operation != event.operation:
122
- # Create a new progress bar for this operation
123
- pbar = self.create_pbar_func(event.operation)
124
- self._current_callback = TQDMProgressCallback(pbar)
125
- self._current_operation = event.operation
126
-
127
- # Update the current progress bar
128
- if self._current_callback:
129
- await self._current_callback.on_progress(event)
130
-
131
- async def on_complete(self, operation: str) -> None:
132
- """Complete the current operation."""
133
- if self._current_callback and self._current_operation == operation:
134
- await self._current_callback.on_complete(operation)
135
- self._current_callback = None
136
- self._current_operation = None
137
-
138
-
139
- def create_progress_bar(desc: str = "Processing", unit: str = "items") -> "tqdm":
140
- """Create a progress bar with the given description and unit."""
141
- from tqdm import tqdm
142
-
143
- return tqdm(
144
- desc=desc,
145
- unit=unit,
146
- leave=False,
147
- dynamic_ncols=True,
148
- total=None, # Will be set dynamically
149
- position=0, # Position at top
150
- mininterval=0.1, # Update at most every 0.1 seconds
151
- )
152
-
153
-
154
- def create_lazy_progress_callback() -> LazyProgressCallback:
155
- """Create a lazy progress callback that only shows progress when needed."""
156
- return LazyProgressCallback(
157
- lambda: create_progress_bar("Processing files", "files")
158
- )
159
-
160
-
161
- def create_multi_stage_progress_callback() -> MultiStageProgressCallback:
162
- """Create a multi-stage progress callback for indexing operations."""
163
- return MultiStageProgressCallback(
164
- lambda operation: create_progress_bar(operation, "items")
165
- )
166
-
167
-
168
- def create_log_progress_callback(milestone_interval: int = 10) -> LogProgressCallback:
169
- """Create a log-based progress callback for server environments."""
170
- return LogProgressCallback(milestone_interval=milestone_interval)