kodit 0.5.0__py3-none-any.whl → 0.5.1__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 (64) hide show
  1. kodit/_version.py +2 -2
  2. kodit/app.py +10 -12
  3. kodit/application/factories/server_factory.py +53 -11
  4. kodit/application/services/commit_indexing_application_service.py +188 -31
  5. kodit/config.py +3 -3
  6. kodit/domain/enrichments/__init__.py +1 -0
  7. kodit/domain/enrichments/architecture/__init__.py +1 -0
  8. kodit/domain/enrichments/architecture/architecture.py +20 -0
  9. kodit/domain/enrichments/architecture/physical/__init__.py +1 -0
  10. kodit/domain/enrichments/architecture/physical/discovery_notes.py +14 -0
  11. kodit/domain/enrichments/architecture/physical/formatter.py +11 -0
  12. kodit/domain/enrichments/architecture/physical/physical.py +17 -0
  13. kodit/domain/enrichments/development/__init__.py +1 -0
  14. kodit/domain/enrichments/development/development.py +18 -0
  15. kodit/domain/enrichments/development/snippet/__init__.py +1 -0
  16. kodit/domain/enrichments/development/snippet/snippet.py +21 -0
  17. kodit/domain/enrichments/enricher.py +17 -0
  18. kodit/domain/enrichments/enrichment.py +39 -0
  19. kodit/domain/enrichments/request.py +12 -0
  20. kodit/domain/enrichments/response.py +11 -0
  21. kodit/domain/enrichments/usage/__init__.py +1 -0
  22. kodit/domain/enrichments/usage/api_docs.py +19 -0
  23. kodit/domain/enrichments/usage/usage.py +18 -0
  24. kodit/domain/protocols.py +7 -6
  25. kodit/domain/services/enrichment_service.py +9 -30
  26. kodit/domain/services/physical_architecture_service.py +182 -0
  27. kodit/domain/value_objects.py +6 -23
  28. kodit/infrastructure/api/v1/routers/commits.py +81 -0
  29. kodit/infrastructure/api/v1/schemas/enrichment.py +29 -0
  30. kodit/infrastructure/cloning/git/git_python_adaptor.py +71 -4
  31. kodit/infrastructure/enricher/__init__.py +1 -0
  32. kodit/infrastructure/enricher/enricher_factory.py +53 -0
  33. kodit/infrastructure/{enrichment/litellm_enrichment_provider.py → enricher/litellm_enricher.py} +20 -33
  34. kodit/infrastructure/{enrichment/local_enrichment_provider.py → enricher/local_enricher.py} +19 -24
  35. kodit/infrastructure/enricher/null_enricher.py +36 -0
  36. kodit/infrastructure/mappers/enrichment_mapper.py +83 -0
  37. kodit/infrastructure/mappers/snippet_mapper.py +20 -22
  38. kodit/infrastructure/physical_architecture/__init__.py +1 -0
  39. kodit/infrastructure/physical_architecture/detectors/__init__.py +1 -0
  40. kodit/infrastructure/physical_architecture/detectors/docker_compose_detector.py +336 -0
  41. kodit/infrastructure/physical_architecture/formatters/__init__.py +1 -0
  42. kodit/infrastructure/physical_architecture/formatters/narrative_formatter.py +149 -0
  43. kodit/infrastructure/slicing/api_doc_extractor.py +836 -0
  44. kodit/infrastructure/slicing/ast_analyzer.py +1128 -0
  45. kodit/infrastructure/slicing/slicer.py +56 -391
  46. kodit/infrastructure/sqlalchemy/enrichment_v2_repository.py +118 -0
  47. kodit/infrastructure/sqlalchemy/entities.py +46 -38
  48. kodit/infrastructure/sqlalchemy/git_branch_repository.py +22 -11
  49. kodit/infrastructure/sqlalchemy/git_commit_repository.py +23 -14
  50. kodit/infrastructure/sqlalchemy/git_repository.py +27 -17
  51. kodit/infrastructure/sqlalchemy/git_tag_repository.py +22 -11
  52. kodit/infrastructure/sqlalchemy/snippet_v2_repository.py +101 -106
  53. kodit/migrations/versions/19f8c7faf8b9_add_generic_enrichment_type.py +260 -0
  54. kodit/utils/dump_config.py +361 -0
  55. kodit/utils/dump_openapi.py +5 -6
  56. {kodit-0.5.0.dist-info → kodit-0.5.1.dist-info}/METADATA +1 -1
  57. {kodit-0.5.0.dist-info → kodit-0.5.1.dist-info}/RECORD +61 -30
  58. kodit/infrastructure/enrichment/__init__.py +0 -1
  59. kodit/infrastructure/enrichment/enrichment_factory.py +0 -52
  60. kodit/infrastructure/enrichment/null_enrichment_provider.py +0 -19
  61. /kodit/infrastructure/{enrichment → enricher}/utils.py +0 -0
  62. {kodit-0.5.0.dist-info → kodit-0.5.1.dist-info}/WHEEL +0 -0
  63. {kodit-0.5.0.dist-info → kodit-0.5.1.dist-info}/entry_points.txt +0 -0
  64. {kodit-0.5.0.dist-info → kodit-0.5.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,53 @@
1
+ """Enricher factory for creating generic enricher domain services."""
2
+
3
+ from kodit.config import AppContext, Endpoint
4
+ from kodit.domain.enrichments.enricher import Enricher
5
+ from kodit.infrastructure.enricher.litellm_enricher import LiteLLMEnricher
6
+ from kodit.infrastructure.enricher.local_enricher import LocalEnricher
7
+ from kodit.infrastructure.enricher.null_enricher import NullEnricher
8
+ from kodit.log import log_event
9
+
10
+
11
+ def _get_endpoint_configuration(app_context: AppContext) -> Endpoint | None:
12
+ """Get the endpoint configuration for the enricher service.
13
+
14
+ Args:
15
+ app_context: The application context.
16
+
17
+ Returns:
18
+ The endpoint configuration or None.
19
+
20
+ """
21
+ return app_context.enrichment_endpoint or None
22
+
23
+
24
+ def enricher_domain_service_factory(
25
+ app_context: AppContext,
26
+ *,
27
+ use_null_enricher: bool = False,
28
+ ) -> Enricher:
29
+ """Create an enricher domain service.
30
+
31
+ Args:
32
+ app_context: The application context.
33
+ use_null_enricher: Whether to use the null enricher instead.
34
+
35
+ Returns:
36
+ An enricher domain service instance.
37
+
38
+ """
39
+ enricher: Enricher
40
+
41
+ if use_null_enricher:
42
+ log_event("kodit.enricher", {"provider": "null"})
43
+ enricher = NullEnricher()
44
+ else:
45
+ endpoint = _get_endpoint_configuration(app_context)
46
+ if endpoint:
47
+ log_event("kodit.enricher", {"provider": "litellm"})
48
+ enricher = LiteLLMEnricher(endpoint=endpoint)
49
+ else:
50
+ log_event("kodit.enricher", {"provider": "local"})
51
+ enricher = LocalEnricher()
52
+
53
+ return enricher
@@ -1,4 +1,4 @@
1
- """LiteLLM enrichment provider implementation."""
1
+ """LiteLLM enricher implementation."""
2
2
 
3
3
  import asyncio
4
4
  from collections.abc import AsyncGenerator
@@ -10,27 +10,22 @@ import structlog
10
10
  from litellm import acompletion
11
11
 
12
12
  from kodit.config import Endpoint
13
- from kodit.domain.services.enrichment_service import EnrichmentProvider
14
- from kodit.domain.value_objects import EnrichmentRequest, EnrichmentResponse
15
- from kodit.infrastructure.enrichment.utils import clean_thinking_tags
13
+ from kodit.domain.enrichments.enricher import Enricher
14
+ from kodit.domain.enrichments.request import EnrichmentRequest
15
+ from kodit.domain.enrichments.response import EnrichmentResponse
16
+ from kodit.infrastructure.enricher.utils import clean_thinking_tags
16
17
 
17
- ENRICHMENT_SYSTEM_PROMPT = """
18
- You are a professional software developer. You will be given a snippet of code.
19
- Please provide a concise explanation of the code.
20
- """
21
-
22
- # Default tuned conservatively for broad provider compatibility
23
18
  DEFAULT_NUM_PARALLEL_TASKS = 20
24
19
 
25
20
 
26
- class LiteLLMEnrichmentProvider(EnrichmentProvider):
27
- """LiteLLM enrichment provider that supports 100+ providers."""
21
+ class LiteLLMEnricher(Enricher):
22
+ """LiteLLM enricher that supports 100+ providers."""
28
23
 
29
24
  def __init__(
30
25
  self,
31
26
  endpoint: Endpoint,
32
27
  ) -> None:
33
- """Initialize the LiteLLM enrichment provider.
28
+ """Initialize the LiteLLM enricher.
34
29
 
35
30
  Args:
36
31
  endpoint: The endpoint configuration containing all settings.
@@ -44,23 +39,20 @@ class LiteLLMEnrichmentProvider(EnrichmentProvider):
44
39
  self.num_parallel_tasks = (
45
40
  endpoint.num_parallel_tasks or DEFAULT_NUM_PARALLEL_TASKS
46
41
  )
47
- self.timeout = endpoint.timeout or 30.0
42
+ self.timeout = endpoint.timeout
48
43
  self.extra_params = endpoint.extra_params or {}
49
44
 
50
- # Configure LiteLLM with custom HTTPX client for Unix socket support if needed
51
45
  self._setup_litellm_client()
52
46
 
53
47
  def _setup_litellm_client(self) -> None:
54
48
  """Set up LiteLLM with custom HTTPX client for Unix socket support."""
55
49
  if self.socket_path:
56
- # Create HTTPX client with Unix socket transport
57
50
  transport = httpx.AsyncHTTPTransport(uds=self.socket_path)
58
51
  unix_client = httpx.AsyncClient(
59
52
  transport=transport,
60
- base_url="http://localhost", # Base URL for Unix socket
53
+ base_url="http://localhost",
61
54
  timeout=self.timeout,
62
55
  )
63
- # Set as LiteLLM's async client session
64
56
  litellm.aclient_session = unix_client
65
57
 
66
58
  async def _call_chat_completion(self, messages: list[dict[str, str]]) -> Any:
@@ -79,20 +71,17 @@ class LiteLLMEnrichmentProvider(EnrichmentProvider):
79
71
  "timeout": self.timeout,
80
72
  }
81
73
 
82
- # Add API key if provided
83
74
  if self.api_key:
84
75
  kwargs["api_key"] = self.api_key
85
76
 
86
- # Add base_url if provided
87
77
  if self.base_url:
88
78
  kwargs["api_base"] = self.base_url
89
79
 
90
- # Add extra parameters
91
80
  kwargs.update(self.extra_params)
92
81
 
93
82
  try:
94
- # Use litellm's async completion function
95
83
  response = await acompletion(**kwargs)
84
+ self.log.debug("enrichment request", request=kwargs, response=response)
96
85
  return (
97
86
  response.model_dump() if hasattr(response, "model_dump") else response
98
87
  )
@@ -108,30 +97,31 @@ class LiteLLMEnrichmentProvider(EnrichmentProvider):
108
97
  """Enrich a list of requests using LiteLLM.
109
98
 
110
99
  Args:
111
- requests: List of enrichment requests.
100
+ requests: List of generic enrichment requests.
112
101
 
113
102
  Yields:
114
- Enrichment responses as they are processed.
103
+ Generic enrichment responses as they are processed.
115
104
 
116
105
  """
117
106
  if not requests:
118
107
  self.log.warning("No requests for enrichment")
119
108
  return
120
109
 
121
- # Process requests in parallel with a semaphore to limit concurrent requests
122
110
  sem = asyncio.Semaphore(self.num_parallel_tasks)
123
111
 
124
- async def process_request(request: EnrichmentRequest) -> EnrichmentResponse:
112
+ async def process_request(
113
+ request: EnrichmentRequest,
114
+ ) -> EnrichmentResponse:
125
115
  async with sem:
126
116
  if not request.text:
127
117
  return EnrichmentResponse(
128
- snippet_id=request.snippet_id,
118
+ id=request.id,
129
119
  text="",
130
120
  )
131
121
  messages = [
132
122
  {
133
123
  "role": "system",
134
- "content": ENRICHMENT_SYSTEM_PROMPT,
124
+ "content": request.system_prompt,
135
125
  },
136
126
  {"role": "user", "content": request.text},
137
127
  ]
@@ -141,22 +131,19 @@ class LiteLLMEnrichmentProvider(EnrichmentProvider):
141
131
  .get("message", {})
142
132
  .get("content", "")
143
133
  )
144
- # Remove thinking tags from the response
145
134
  cleaned_content = clean_thinking_tags(content or "")
146
135
  return EnrichmentResponse(
147
- snippet_id=request.snippet_id,
136
+ id=request.id,
148
137
  text=cleaned_content,
149
138
  )
150
139
 
151
- # Create tasks for all requests
152
140
  tasks = [process_request(request) for request in requests]
153
141
 
154
- # Process all requests and yield results as they complete
155
142
  for task in asyncio.as_completed(tasks):
156
143
  yield await task
157
144
 
158
145
  async def close(self) -> None:
159
- """Close the provider and cleanup HTTPX client if using Unix sockets."""
146
+ """Close the enricher and cleanup HTTPX client if using Unix sockets."""
160
147
  if (
161
148
  self.socket_path
162
149
  and hasattr(litellm, "aclient_session")
@@ -1,4 +1,4 @@
1
- """Local enrichment provider implementation."""
1
+ """Local enricher implementation."""
2
2
 
3
3
  import asyncio
4
4
  import os
@@ -8,28 +8,24 @@ from typing import Any
8
8
  import structlog
9
9
  import tiktoken
10
10
 
11
- from kodit.domain.services.enrichment_service import EnrichmentProvider
12
- from kodit.domain.value_objects import EnrichmentRequest, EnrichmentResponse
13
- from kodit.infrastructure.enrichment.utils import clean_thinking_tags
11
+ from kodit.domain.enrichments.enricher import Enricher
12
+ from kodit.domain.enrichments.request import EnrichmentRequest
13
+ from kodit.domain.enrichments.response import EnrichmentResponse
14
+ from kodit.infrastructure.enricher.utils import clean_thinking_tags
14
15
 
15
- ENRICHMENT_SYSTEM_PROMPT = """
16
- You are a professional software developer. You will be given a snippet of code.
17
- Please provide a concise explanation of the code.
18
- """
16
+ DEFAULT_ENRICHER_MODEL = "Qwen/Qwen3-0.6B"
17
+ DEFAULT_CONTEXT_WINDOW_SIZE = 2048
19
18
 
20
- DEFAULT_ENRICHMENT_MODEL = "Qwen/Qwen3-0.6B"
21
- DEFAULT_CONTEXT_WINDOW_SIZE = 2048 # Small so it works even on low-powered devices
22
19
 
23
-
24
- class LocalEnrichmentProvider(EnrichmentProvider):
25
- """Local enrichment provider implementation."""
20
+ class LocalEnricher(Enricher):
21
+ """Local enricher implementation using local models."""
26
22
 
27
23
  def __init__(
28
24
  self,
29
- model_name: str = DEFAULT_ENRICHMENT_MODEL,
25
+ model_name: str = DEFAULT_ENRICHER_MODEL,
30
26
  context_window: int = DEFAULT_CONTEXT_WINDOW_SIZE,
31
27
  ) -> None:
32
- """Initialize the local enrichment provider.
28
+ """Initialize the local enricher.
33
29
 
34
30
  Args:
35
31
  model_name: The model name to use for enrichment.
@@ -49,13 +45,13 @@ class LocalEnrichmentProvider(EnrichmentProvider):
49
45
  """Enrich a list of requests using local model.
50
46
 
51
47
  Args:
52
- requests: List of enrichment requests.
48
+ requests: List of generic enrichment requests.
53
49
 
54
50
  Yields:
55
- Enrichment responses as they are processed.
51
+ Generic enrichment responses as they are processed.
56
52
 
57
53
  """
58
- # Remove empty snippets
54
+ # Remove empty requests
59
55
  requests = [req for req in requests if req.text]
60
56
 
61
57
  if not requests:
@@ -73,7 +69,7 @@ class LocalEnrichmentProvider(EnrichmentProvider):
73
69
  self.model_name, padding_side="left"
74
70
  )
75
71
  if self.model is None:
76
- os.environ["TOKENIZERS_PARALLELISM"] = "false" # Avoid warnings
72
+ os.environ["TOKENIZERS_PARALLELISM"] = "false"
77
73
  self.model = AutoModelForCausalLM.from_pretrained(
78
74
  self.model_name,
79
75
  torch_dtype="auto",
@@ -83,13 +79,13 @@ class LocalEnrichmentProvider(EnrichmentProvider):
83
79
 
84
80
  await asyncio.to_thread(_init_model)
85
81
 
86
- # Prepare prompts
82
+ # Prepare prompts with custom system prompts
87
83
  prompts = [
88
84
  {
89
- "id": req.snippet_id,
85
+ "id": req.id,
90
86
  "text": self.tokenizer.apply_chat_template( # type: ignore[attr-defined]
91
87
  [
92
- {"role": "system", "content": ENRICHMENT_SYSTEM_PROMPT},
88
+ {"role": "system", "content": req.system_prompt},
93
89
  {"role": "user", "content": req.text},
94
90
  ],
95
91
  tokenize=False,
@@ -121,9 +117,8 @@ class LocalEnrichmentProvider(EnrichmentProvider):
121
117
  )
122
118
 
123
119
  content = await asyncio.to_thread(process_prompt, prompt)
124
- # Remove thinking tags from the response
125
120
  cleaned_content = clean_thinking_tags(content)
126
121
  yield EnrichmentResponse(
127
- snippet_id=prompt["id"],
122
+ id=prompt["id"],
128
123
  text=cleaned_content,
129
124
  )
@@ -0,0 +1,36 @@
1
+ """Null enricher implementation."""
2
+
3
+ from collections.abc import AsyncGenerator
4
+
5
+ import structlog
6
+
7
+ from kodit.domain.enrichments.enricher import Enricher
8
+ from kodit.domain.enrichments.request import EnrichmentRequest
9
+ from kodit.domain.enrichments.response import EnrichmentResponse
10
+
11
+
12
+ class NullEnricher(Enricher):
13
+ """Null enricher that returns empty responses."""
14
+
15
+ def __init__(self) -> None:
16
+ """Initialize the null enricher."""
17
+ self.log = structlog.get_logger(__name__)
18
+
19
+ async def enrich(
20
+ self, requests: list[EnrichmentRequest]
21
+ ) -> AsyncGenerator[EnrichmentResponse, None]:
22
+ """Return empty responses for all requests.
23
+
24
+ Args:
25
+ requests: List of generic enrichment requests.
26
+
27
+ Yields:
28
+ Empty generic enrichment responses.
29
+
30
+ """
31
+ self.log.info("NullEnricher: returning empty responses", count=len(requests))
32
+ for request in requests:
33
+ yield EnrichmentResponse(
34
+ id=request.id,
35
+ text="",
36
+ )
@@ -0,0 +1,83 @@
1
+ """Enrichment mapper."""
2
+
3
+ from kodit.domain.enrichments.architecture.architecture import (
4
+ ENRICHMENT_TYPE_ARCHITECTURE,
5
+ )
6
+ from kodit.domain.enrichments.architecture.physical.physical import (
7
+ ENRICHMENT_SUBTYPE_PHYSICAL,
8
+ PhysicalArchitectureEnrichment,
9
+ )
10
+ from kodit.domain.enrichments.development.development import ENRICHMENT_TYPE_DEVELOPMENT
11
+ from kodit.domain.enrichments.development.snippet.snippet import (
12
+ ENRICHMENT_SUBTYPE_SNIPPET_SUMMARY,
13
+ SnippetEnrichment,
14
+ )
15
+ from kodit.domain.enrichments.enrichment import EnrichmentV2
16
+ from kodit.domain.enrichments.usage.api_docs import (
17
+ ENRICHMENT_SUBTYPE_API_DOCS,
18
+ APIDocEnrichment,
19
+ )
20
+ from kodit.domain.enrichments.usage.usage import ENRICHMENT_TYPE_USAGE
21
+ from kodit.infrastructure.sqlalchemy import entities as db_entities
22
+
23
+
24
+ class EnrichmentMapper:
25
+ """Maps between domain enrichment entities and database entities."""
26
+
27
+ @staticmethod
28
+ def to_database(domain_enrichment: EnrichmentV2) -> db_entities.EnrichmentV2:
29
+ """Convert domain enrichment to database entity."""
30
+ return db_entities.EnrichmentV2(
31
+ id=domain_enrichment.id,
32
+ type=domain_enrichment.type,
33
+ subtype=domain_enrichment.subtype,
34
+ content=domain_enrichment.content,
35
+ created_at=domain_enrichment.created_at,
36
+ updated_at=domain_enrichment.updated_at,
37
+ )
38
+
39
+ @staticmethod
40
+ def to_domain(
41
+ db_enrichment: db_entities.EnrichmentV2,
42
+ entity_type: str, # noqa: ARG004
43
+ entity_id: str,
44
+ ) -> EnrichmentV2:
45
+ """Convert database enrichment to domain entity."""
46
+ # Use the stored type and subtype to determine the correct domain class
47
+ if (
48
+ db_enrichment.type == ENRICHMENT_TYPE_DEVELOPMENT
49
+ and db_enrichment.subtype == ENRICHMENT_SUBTYPE_SNIPPET_SUMMARY
50
+ ):
51
+ return SnippetEnrichment(
52
+ id=db_enrichment.id,
53
+ entity_id=entity_id,
54
+ content=db_enrichment.content,
55
+ created_at=db_enrichment.created_at,
56
+ updated_at=db_enrichment.updated_at,
57
+ )
58
+ if (
59
+ db_enrichment.type == ENRICHMENT_TYPE_USAGE
60
+ and db_enrichment.subtype == ENRICHMENT_SUBTYPE_API_DOCS
61
+ ):
62
+ return APIDocEnrichment(
63
+ id=db_enrichment.id,
64
+ entity_id=entity_id,
65
+ content=db_enrichment.content,
66
+ created_at=db_enrichment.created_at,
67
+ updated_at=db_enrichment.updated_at,
68
+ )
69
+ if (
70
+ db_enrichment.type == ENRICHMENT_TYPE_ARCHITECTURE
71
+ and db_enrichment.subtype == ENRICHMENT_SUBTYPE_PHYSICAL
72
+ ):
73
+ return PhysicalArchitectureEnrichment(
74
+ id=db_enrichment.id,
75
+ entity_id=entity_id,
76
+ content=db_enrichment.content,
77
+ created_at=db_enrichment.created_at,
78
+ updated_at=db_enrichment.updated_at,
79
+ )
80
+
81
+ raise ValueError(
82
+ f"Unknown enrichment type: {db_enrichment.type}/{db_enrichment.subtype}"
83
+ )
@@ -1,6 +1,8 @@
1
1
  """Mapping between domain Git entities and SQLAlchemy entities."""
2
2
 
3
3
  import kodit.domain.entities.git as domain_git_entities
4
+ from kodit.domain.enrichments.development.snippet.snippet import SnippetEnrichment
5
+ from kodit.domain.enrichments.enrichment import EnrichmentV2
4
6
  from kodit.domain.value_objects import Enrichment, EnrichmentType
5
7
  from kodit.infrastructure.sqlalchemy import entities as db_entities
6
8
 
@@ -12,19 +14,17 @@ class SnippetMapper:
12
14
  self,
13
15
  db_snippet: db_entities.SnippetV2,
14
16
  db_files: list[db_entities.GitCommitFile],
15
- db_enrichments: list[db_entities.Enrichment],
17
+ db_enrichments: list[EnrichmentV2],
16
18
  ) -> domain_git_entities.SnippetV2:
17
19
  """Convert SQLAlchemy SnippetV2 to domain SnippetV2."""
18
- # Convert enrichments
19
- enrichments = []
20
- for db_enrichment in db_enrichments:
21
- # Map from SQLAlchemy enum to domain enum
22
- enrichment_type = EnrichmentType(db_enrichment.type.value)
23
- enrichment = Enrichment(
24
- type=enrichment_type,
25
- content=db_enrichment.content,
20
+ # Convert enrichments from SnippetEnrichment to Enrichment value objects
21
+ enrichments: list[Enrichment] = [
22
+ Enrichment(
23
+ type=EnrichmentType.SUMMARIZATION,
24
+ content=enrichment.content,
26
25
  )
27
- enrichments.append(enrichment)
26
+ for enrichment in db_enrichments
27
+ ]
28
28
 
29
29
  derives_from = [
30
30
  domain_git_entities.GitFile(
@@ -59,20 +59,18 @@ class SnippetMapper:
59
59
  )
60
60
 
61
61
  def from_domain_enrichments(
62
- self, snippet_sha: str, enrichments: list[Enrichment]
63
- ) -> list[db_entities.Enrichment]:
64
- """Convert domain enrichments to SQLAlchemy enrichments."""
65
- db_enrichments = []
66
- for enrichment in enrichments:
67
- # Map from domain enum to SQLAlchemy enum
68
- db_enrichment_type = db_entities.EnrichmentType(enrichment.type.value)
69
- db_enrichment = db_entities.Enrichment(
70
- snippet_sha=snippet_sha,
71
- type=db_enrichment_type,
62
+ self,
63
+ snippet_sha: str,
64
+ enrichments: list[Enrichment],
65
+ ) -> list[SnippetEnrichment]:
66
+ """Convert domain enrichments to SnippetEnrichment entities."""
67
+ return [
68
+ SnippetEnrichment(
69
+ entity_id=snippet_sha,
72
70
  content=enrichment.content,
73
71
  )
74
- db_enrichments.append(db_enrichment)
75
- return db_enrichments
72
+ for enrichment in enrichments
73
+ ]
76
74
 
77
75
  def to_domain_commit_index(
78
76
  self,
@@ -0,0 +1 @@
1
+ """Physical architecture discovery infrastructure."""
@@ -0,0 +1 @@
1
+ """Component detectors for physical architecture discovery."""