kodit 0.5.0__py3-none-any.whl → 0.5.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.
- kodit/_version.py +2 -2
- kodit/app.py +10 -12
- kodit/application/factories/server_factory.py +78 -11
- kodit/application/services/commit_indexing_application_service.py +188 -31
- kodit/application/services/enrichment_query_service.py +95 -0
- kodit/config.py +3 -3
- kodit/domain/enrichments/__init__.py +1 -0
- kodit/domain/enrichments/architecture/__init__.py +1 -0
- kodit/domain/enrichments/architecture/architecture.py +20 -0
- kodit/domain/enrichments/architecture/physical/__init__.py +1 -0
- kodit/domain/enrichments/architecture/physical/discovery_notes.py +14 -0
- kodit/domain/enrichments/architecture/physical/formatter.py +11 -0
- kodit/domain/enrichments/architecture/physical/physical.py +17 -0
- kodit/domain/enrichments/development/__init__.py +1 -0
- kodit/domain/enrichments/development/development.py +18 -0
- kodit/domain/enrichments/development/snippet/__init__.py +1 -0
- kodit/domain/enrichments/development/snippet/snippet.py +21 -0
- kodit/domain/enrichments/enricher.py +17 -0
- kodit/domain/enrichments/enrichment.py +39 -0
- kodit/domain/enrichments/request.py +12 -0
- kodit/domain/enrichments/response.py +11 -0
- kodit/domain/enrichments/usage/__init__.py +1 -0
- kodit/domain/enrichments/usage/api_docs.py +19 -0
- kodit/domain/enrichments/usage/usage.py +18 -0
- kodit/domain/protocols.py +7 -6
- kodit/domain/services/enrichment_service.py +9 -30
- kodit/domain/services/physical_architecture_service.py +182 -0
- kodit/domain/tracking/__init__.py +1 -0
- kodit/domain/tracking/resolution_service.py +81 -0
- kodit/domain/tracking/trackable.py +21 -0
- kodit/domain/value_objects.py +6 -23
- kodit/infrastructure/api/v1/dependencies.py +15 -0
- kodit/infrastructure/api/v1/routers/commits.py +81 -0
- kodit/infrastructure/api/v1/routers/repositories.py +99 -0
- kodit/infrastructure/api/v1/schemas/enrichment.py +29 -0
- kodit/infrastructure/cloning/git/git_python_adaptor.py +71 -4
- kodit/infrastructure/enricher/__init__.py +1 -0
- kodit/infrastructure/enricher/enricher_factory.py +53 -0
- kodit/infrastructure/{enrichment/litellm_enrichment_provider.py → enricher/litellm_enricher.py} +20 -33
- kodit/infrastructure/{enrichment/local_enrichment_provider.py → enricher/local_enricher.py} +19 -24
- kodit/infrastructure/enricher/null_enricher.py +36 -0
- kodit/infrastructure/mappers/enrichment_mapper.py +83 -0
- kodit/infrastructure/mappers/snippet_mapper.py +20 -22
- kodit/infrastructure/physical_architecture/__init__.py +1 -0
- kodit/infrastructure/physical_architecture/detectors/__init__.py +1 -0
- kodit/infrastructure/physical_architecture/detectors/docker_compose_detector.py +336 -0
- kodit/infrastructure/physical_architecture/formatters/__init__.py +1 -0
- kodit/infrastructure/physical_architecture/formatters/narrative_formatter.py +149 -0
- kodit/infrastructure/slicing/api_doc_extractor.py +836 -0
- kodit/infrastructure/slicing/ast_analyzer.py +1128 -0
- kodit/infrastructure/slicing/slicer.py +56 -391
- kodit/infrastructure/sqlalchemy/enrichment_v2_repository.py +118 -0
- kodit/infrastructure/sqlalchemy/entities.py +46 -38
- kodit/infrastructure/sqlalchemy/git_branch_repository.py +22 -11
- kodit/infrastructure/sqlalchemy/git_commit_repository.py +23 -14
- kodit/infrastructure/sqlalchemy/git_repository.py +27 -17
- kodit/infrastructure/sqlalchemy/git_tag_repository.py +22 -11
- kodit/infrastructure/sqlalchemy/snippet_v2_repository.py +101 -106
- kodit/migrations/versions/19f8c7faf8b9_add_generic_enrichment_type.py +260 -0
- kodit/utils/dump_config.py +361 -0
- kodit/utils/dump_openapi.py +5 -6
- {kodit-0.5.0.dist-info → kodit-0.5.2.dist-info}/METADATA +1 -1
- {kodit-0.5.0.dist-info → kodit-0.5.2.dist-info}/RECORD +67 -32
- kodit/infrastructure/enrichment/__init__.py +0 -1
- kodit/infrastructure/enrichment/enrichment_factory.py +0 -52
- kodit/infrastructure/enrichment/null_enrichment_provider.py +0 -19
- /kodit/infrastructure/{enrichment → enricher}/utils.py +0 -0
- {kodit-0.5.0.dist-info → kodit-0.5.2.dist-info}/WHEEL +0 -0
- {kodit-0.5.0.dist-info → kodit-0.5.2.dist-info}/entry_points.txt +0 -0
- {kodit-0.5.0.dist-info → kodit-0.5.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Physical architecture domain value objects."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class ArchitectureDiscoveryNotes:
|
|
8
|
+
"""Rich, narrative observations about repository architecture for LLM consumption.""" # noqa: E501
|
|
9
|
+
|
|
10
|
+
repository_context: str # High-level overview and discovery scope
|
|
11
|
+
component_observations: list[str] # Detailed findings about each component
|
|
12
|
+
connection_observations: list[str] # How components interact and communicate
|
|
13
|
+
infrastructure_observations: list[str] # Deployment, config, operational patterns
|
|
14
|
+
discovery_metadata: str # Methodology, confidence, limitations, timestamp
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Physical architecture formatter protocol."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Protocol
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PhysicalArchitectureFormatter(Protocol):
|
|
7
|
+
"""Formatter for converting architecture discovery notes to LLM-optimized text."""
|
|
8
|
+
|
|
9
|
+
def format_for_llm(self, notes: Any) -> str:
|
|
10
|
+
"""Format architecture discovery notes for LLM consumption."""
|
|
11
|
+
...
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Physical architecture enrichment domain entity."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from kodit.domain.enrichments.architecture.architecture import ArchitectureEnrichment
|
|
6
|
+
|
|
7
|
+
ENRICHMENT_SUBTYPE_PHYSICAL = "physical"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class PhysicalArchitectureEnrichment(ArchitectureEnrichment):
|
|
12
|
+
"""Enrichment containing physical architecture discovery for a commit."""
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def subtype(self) -> str | None:
|
|
16
|
+
"""Return the enrichment subtype."""
|
|
17
|
+
return ENRICHMENT_SUBTYPE_PHYSICAL
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Development enrichment package."""
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Development enrichment domain entity."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from kodit.domain.enrichments.enrichment import CommitEnrichment
|
|
7
|
+
|
|
8
|
+
ENRICHMENT_TYPE_DEVELOPMENT = "development"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class DevelopmentEnrichment(CommitEnrichment, ABC):
|
|
13
|
+
"""Enrichment containing development discovery for a commit."""
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def type(self) -> str:
|
|
17
|
+
"""Return the enrichment type."""
|
|
18
|
+
return ENRICHMENT_TYPE_DEVELOPMENT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Snippet enrichment package."""
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Snippet enrichment domain entity."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from kodit.domain.enrichments.development.development import DevelopmentEnrichment
|
|
6
|
+
|
|
7
|
+
ENRICHMENT_SUBTYPE_SNIPPET_SUMMARY = "snippet_summary"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class SnippetEnrichment(DevelopmentEnrichment):
|
|
12
|
+
"""Enrichment specific to code snippets."""
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def subtype(self) -> str | None:
|
|
16
|
+
"""Return the enrichment subtype."""
|
|
17
|
+
return ENRICHMENT_SUBTYPE_SNIPPET_SUMMARY
|
|
18
|
+
|
|
19
|
+
def entity_type_key(self) -> str:
|
|
20
|
+
"""Return the entity type key this enrichment is for."""
|
|
21
|
+
return "snippet_v2"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Enricher interface."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import AsyncGenerator
|
|
4
|
+
from typing import Protocol
|
|
5
|
+
|
|
6
|
+
from kodit.domain.enrichments.request import EnrichmentRequest
|
|
7
|
+
from kodit.domain.enrichments.response import EnrichmentResponse
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Enricher(Protocol):
|
|
11
|
+
"""Interface for text enrichment with custom prompts."""
|
|
12
|
+
|
|
13
|
+
def enrich(
|
|
14
|
+
self, requests: list[EnrichmentRequest]
|
|
15
|
+
) -> AsyncGenerator[EnrichmentResponse, None]:
|
|
16
|
+
"""Enrich a list of requests with custom system prompts."""
|
|
17
|
+
...
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Enrichment domain entities."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class EnrichmentV2(ABC):
|
|
10
|
+
"""Generic enrichment that can be attached to any entity."""
|
|
11
|
+
|
|
12
|
+
entity_id: str
|
|
13
|
+
content: str = ""
|
|
14
|
+
id: int | None = None
|
|
15
|
+
created_at: datetime | None = None
|
|
16
|
+
updated_at: datetime | None = None
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
@abstractmethod
|
|
20
|
+
def type(self) -> str:
|
|
21
|
+
"""Return the enrichment type."""
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def subtype(self) -> str | None:
|
|
26
|
+
"""Return the enrichment subtype (optional for hierarchical types)."""
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def entity_type_key(self) -> str:
|
|
30
|
+
"""Return the entity type key this enrichment is for."""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class CommitEnrichment(EnrichmentV2, ABC):
|
|
35
|
+
"""Enrichment specific to commits."""
|
|
36
|
+
|
|
37
|
+
def entity_type_key(self) -> str:
|
|
38
|
+
"""Return the entity type key this enrichment is for."""
|
|
39
|
+
return "git_commit"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Usage enrichment domain entities."""
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""API documentation enrichment entity."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from kodit.domain.enrichments.usage.usage import UsageEnrichment
|
|
6
|
+
|
|
7
|
+
ENRICHMENT_SUBTYPE_API_DOCS = "api_docs"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class APIDocEnrichment(UsageEnrichment):
|
|
12
|
+
"""API documentation enrichment for a module."""
|
|
13
|
+
|
|
14
|
+
language: str = ""
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def subtype(self) -> str | None:
|
|
18
|
+
"""Return the enrichment subtype."""
|
|
19
|
+
return ENRICHMENT_SUBTYPE_API_DOCS
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Usage enrichment domain entity."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from kodit.domain.enrichments.enrichment import CommitEnrichment
|
|
7
|
+
|
|
8
|
+
ENRICHMENT_TYPE_USAGE = "usage"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class UsageEnrichment(CommitEnrichment, ABC):
|
|
13
|
+
"""Enrichment containing development discovery for a commit."""
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def type(self) -> str:
|
|
17
|
+
"""Return the enrichment type."""
|
|
18
|
+
return ENRICHMENT_TYPE_USAGE
|
kodit/domain/protocols.py
CHANGED
|
@@ -217,7 +217,6 @@ class GitRepoRepository(ABC):
|
|
|
217
217
|
"""Delete a repository."""
|
|
218
218
|
|
|
219
219
|
|
|
220
|
-
|
|
221
220
|
class GitAdapter(ABC):
|
|
222
221
|
"""Abstract interface for Git operations."""
|
|
223
222
|
|
|
@@ -225,10 +224,6 @@ class GitAdapter(ABC):
|
|
|
225
224
|
async def clone_repository(self, remote_uri: str, local_path: Path) -> None:
|
|
226
225
|
"""Clone a repository to local path."""
|
|
227
226
|
|
|
228
|
-
@abstractmethod
|
|
229
|
-
async def checkout_commit(self, local_path: Path, commit_sha: str) -> None:
|
|
230
|
-
"""Checkout a specific commit in the repository."""
|
|
231
|
-
|
|
232
227
|
@abstractmethod
|
|
233
228
|
async def pull_repository(self, local_path: Path) -> None:
|
|
234
229
|
"""Pull latest changes for existing repository."""
|
|
@@ -247,7 +242,13 @@ class GitAdapter(ABC):
|
|
|
247
242
|
async def get_commit_files(
|
|
248
243
|
self, local_path: Path, commit_sha: str
|
|
249
244
|
) -> list[dict[str, Any]]:
|
|
250
|
-
"""Get all files in a specific commit."""
|
|
245
|
+
"""Get all files in a specific commit from the git tree."""
|
|
246
|
+
|
|
247
|
+
@abstractmethod
|
|
248
|
+
async def get_commit_file_data(
|
|
249
|
+
self, local_path: Path, commit_sha: str
|
|
250
|
+
) -> list[dict[str, Any]]:
|
|
251
|
+
"""Get file metadata for a commit, with files checked out to disk."""
|
|
251
252
|
|
|
252
253
|
@abstractmethod
|
|
253
254
|
async def repository_exists(self, local_path: Path) -> bool:
|
|
@@ -1,48 +1,27 @@
|
|
|
1
1
|
"""Domain service for enrichment operations."""
|
|
2
2
|
|
|
3
|
-
from abc import ABC, abstractmethod
|
|
4
3
|
from collections.abc import AsyncGenerator
|
|
5
4
|
|
|
6
|
-
from kodit.domain.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
EnrichmentResponse,
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class EnrichmentProvider(ABC):
|
|
14
|
-
"""Abstract enrichment provider interface."""
|
|
15
|
-
|
|
16
|
-
@abstractmethod
|
|
17
|
-
def enrich(
|
|
18
|
-
self, requests: list[EnrichmentRequest]
|
|
19
|
-
) -> AsyncGenerator[EnrichmentResponse, None]:
|
|
20
|
-
"""Enrich a list of requests."""
|
|
5
|
+
from kodit.domain.enrichments.enricher import Enricher
|
|
6
|
+
from kodit.domain.enrichments.request import EnrichmentRequest
|
|
7
|
+
from kodit.domain.enrichments.response import EnrichmentResponse
|
|
21
8
|
|
|
22
9
|
|
|
23
10
|
class EnrichmentDomainService:
|
|
24
11
|
"""Domain service for enrichment operations."""
|
|
25
12
|
|
|
26
|
-
def __init__(self,
|
|
27
|
-
"""Initialize the enrichment domain service.
|
|
28
|
-
|
|
29
|
-
Args:
|
|
30
|
-
enrichment_provider: The enrichment provider to use.
|
|
31
|
-
|
|
32
|
-
"""
|
|
33
|
-
self.enrichment_provider = enrichment_provider
|
|
13
|
+
def __init__(self, enricher: Enricher) -> None:
|
|
14
|
+
"""Initialize the enrichment domain service."""
|
|
15
|
+
self.enricher = enricher
|
|
34
16
|
|
|
35
17
|
async def enrich_documents(
|
|
36
|
-
self,
|
|
18
|
+
self, requests: list[EnrichmentRequest]
|
|
37
19
|
) -> AsyncGenerator[EnrichmentResponse, None]:
|
|
38
|
-
"""Enrich documents using the
|
|
39
|
-
|
|
40
|
-
Args:
|
|
41
|
-
request: The enrichment index request.
|
|
20
|
+
"""Enrich documents using the enricher.
|
|
42
21
|
|
|
43
22
|
Yields:
|
|
44
23
|
Enrichment responses as they are processed.
|
|
45
24
|
|
|
46
25
|
"""
|
|
47
|
-
async for response in self.
|
|
26
|
+
async for response in self.enricher.enrich(requests):
|
|
48
27
|
yield response
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""Core service for discovering physical architecture and generating narrative observations.""" # noqa: E501
|
|
2
|
+
|
|
3
|
+
from datetime import UTC, datetime
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from kodit.domain.enrichments.architecture.physical.discovery_notes import (
|
|
7
|
+
ArchitectureDiscoveryNotes,
|
|
8
|
+
)
|
|
9
|
+
from kodit.domain.enrichments.architecture.physical.formatter import (
|
|
10
|
+
PhysicalArchitectureFormatter,
|
|
11
|
+
)
|
|
12
|
+
from kodit.infrastructure.physical_architecture.detectors import docker_compose_detector
|
|
13
|
+
|
|
14
|
+
ARCHITECTURE_ENRICHMENT_SYSTEM_PROMPT = """You are an expert software architect.
|
|
15
|
+
Deliver the user's request succinctly.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
ARCHITECTURE_ENRICHMENT_TASK_PROMPT = """Convert the raw architecture discovery logs
|
|
19
|
+
into a clean, structured summary written in markdown.
|
|
20
|
+
|
|
21
|
+
<architecture_narrative>
|
|
22
|
+
{architecture_narrative}
|
|
23
|
+
</architecture_narrative>
|
|
24
|
+
|
|
25
|
+
**Return the following information**
|
|
26
|
+
|
|
27
|
+
## Services List
|
|
28
|
+
|
|
29
|
+
For each service, write one line:
|
|
30
|
+
- **[Service Name]**: [what it does] | Tech: [technology] | Ports: [ports]
|
|
31
|
+
|
|
32
|
+
## Service Dependencies
|
|
33
|
+
|
|
34
|
+
List the important connections:
|
|
35
|
+
- [Service A] → [Service B]: [why they connect]
|
|
36
|
+
|
|
37
|
+
## Mermaid Diagram
|
|
38
|
+
|
|
39
|
+
Output a Mermaid diagram depicting the architecture using the names of the services and
|
|
40
|
+
the ports that they expose.
|
|
41
|
+
|
|
42
|
+
## Key Information
|
|
43
|
+
|
|
44
|
+
Answer these questions in 1-2 sentences each:
|
|
45
|
+
1. What databases are used and for what?
|
|
46
|
+
2. What are the critical services that everything else depends on?
|
|
47
|
+
3. Are there any unusual communication patterns between services that people should be
|
|
48
|
+
aware of? (e.g. a different direction to what you'd expect)
|
|
49
|
+
|
|
50
|
+
## Rules:
|
|
51
|
+
- Skip duplicate services (keep only one instance)
|
|
52
|
+
- Don't list environment variables
|
|
53
|
+
- Don't describe Docker volumes in detail
|
|
54
|
+
- Focus on WHAT each service does, not HOW it's configured
|
|
55
|
+
- If a service name is unclear, make your best guess based on the information
|
|
56
|
+
- Keep descriptions to 10 words or less per service
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class PhysicalArchitectureService:
|
|
61
|
+
"""Core service for discovering physical architecture and generating narrative observations.""" # noqa: E501
|
|
62
|
+
|
|
63
|
+
def __init__(self, formatter: PhysicalArchitectureFormatter) -> None:
|
|
64
|
+
"""Initialize the service with detectors and formatter."""
|
|
65
|
+
self.docker_detector = docker_compose_detector.DockerComposeDetector()
|
|
66
|
+
self.formatter = formatter
|
|
67
|
+
|
|
68
|
+
async def discover_architecture(self, repo_path: Path) -> str:
|
|
69
|
+
"""Discover physical architecture and generate rich narrative observations."""
|
|
70
|
+
# Generate repository context overview
|
|
71
|
+
repo_context = await self._analyze_repository_context(repo_path)
|
|
72
|
+
|
|
73
|
+
# Collect observations from all detectors
|
|
74
|
+
component_notes = []
|
|
75
|
+
connection_notes = []
|
|
76
|
+
infrastructure_notes = []
|
|
77
|
+
|
|
78
|
+
# Run detectors and collect narrative observations
|
|
79
|
+
(
|
|
80
|
+
docker_component_notes,
|
|
81
|
+
docker_connection_notes,
|
|
82
|
+
docker_infrastructure_notes,
|
|
83
|
+
) = await self.docker_detector.analyze(repo_path)
|
|
84
|
+
component_notes.extend(docker_component_notes)
|
|
85
|
+
connection_notes.extend(docker_connection_notes)
|
|
86
|
+
infrastructure_notes.extend(docker_infrastructure_notes)
|
|
87
|
+
|
|
88
|
+
# Future: Add Kubernetes and code structure detectors when available
|
|
89
|
+
|
|
90
|
+
# Generate discovery metadata
|
|
91
|
+
discovery_metadata = self._generate_discovery_metadata(repo_path)
|
|
92
|
+
|
|
93
|
+
# Create comprehensive notes
|
|
94
|
+
notes = ArchitectureDiscoveryNotes(
|
|
95
|
+
repository_context=repo_context,
|
|
96
|
+
component_observations=component_notes,
|
|
97
|
+
connection_observations=connection_notes,
|
|
98
|
+
infrastructure_observations=infrastructure_notes,
|
|
99
|
+
discovery_metadata=discovery_metadata,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return self.formatter.format_for_llm(notes)
|
|
103
|
+
|
|
104
|
+
async def _analyze_repository_context(self, repo_path: Path) -> str:
|
|
105
|
+
"""Generate high-level repository context and scope."""
|
|
106
|
+
context_observations = []
|
|
107
|
+
|
|
108
|
+
# Check for basic repository structure
|
|
109
|
+
context_observations.append(f"Analyzing repository at {repo_path}")
|
|
110
|
+
|
|
111
|
+
# Check for common project indicators
|
|
112
|
+
has_docker_compose = bool(
|
|
113
|
+
list(repo_path.glob("docker-compose*.yml"))
|
|
114
|
+
+ list(repo_path.glob("docker-compose*.yaml"))
|
|
115
|
+
)
|
|
116
|
+
has_dockerfile = bool(list(repo_path.glob("Dockerfile*")))
|
|
117
|
+
has_k8s = bool(
|
|
118
|
+
list(repo_path.glob("**/k8s/**/*.yaml"))
|
|
119
|
+
+ list(repo_path.glob("**/kubernetes/**/*.yaml"))
|
|
120
|
+
)
|
|
121
|
+
has_package_json = (repo_path / "package.json").exists()
|
|
122
|
+
has_requirements_txt = (repo_path / "requirements.txt").exists()
|
|
123
|
+
has_go_mod = (repo_path / "go.mod").exists()
|
|
124
|
+
|
|
125
|
+
# Determine likely project type
|
|
126
|
+
project_indicators = []
|
|
127
|
+
if has_docker_compose:
|
|
128
|
+
project_indicators.append("Docker Compose orchestration")
|
|
129
|
+
if has_dockerfile:
|
|
130
|
+
project_indicators.append("containerized deployment")
|
|
131
|
+
if has_k8s:
|
|
132
|
+
project_indicators.append("Kubernetes deployment")
|
|
133
|
+
if has_package_json:
|
|
134
|
+
project_indicators.append("Node.js/JavaScript components")
|
|
135
|
+
if has_requirements_txt:
|
|
136
|
+
project_indicators.append("Python components")
|
|
137
|
+
if has_go_mod:
|
|
138
|
+
project_indicators.append("Go components")
|
|
139
|
+
|
|
140
|
+
if project_indicators:
|
|
141
|
+
context_observations.append(
|
|
142
|
+
f"Repository shows evidence of {', '.join(project_indicators)}, "
|
|
143
|
+
"suggesting a modern containerized application architecture."
|
|
144
|
+
)
|
|
145
|
+
else:
|
|
146
|
+
context_observations.append(
|
|
147
|
+
"Repository structure analysis shows limited infrastructure configuration. " # noqa: E501
|
|
148
|
+
"This may be a simple application or library without complex deployment requirements." # noqa: E501
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
return " ".join(context_observations)
|
|
152
|
+
|
|
153
|
+
def _generate_discovery_metadata(self, _repo_path: Path) -> str:
|
|
154
|
+
"""Document discovery methodology, confidence, and limitations."""
|
|
155
|
+
timestamp = datetime.now(UTC).isoformat()
|
|
156
|
+
|
|
157
|
+
metadata_parts = [
|
|
158
|
+
f"Analysis completed on {timestamp} using physical architecture discovery system version 1.0.", # noqa: E501
|
|
159
|
+
"Discovery methodology: Docker Compose parsing and infrastructure configuration analysis.", # noqa: E501
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
# Document detection sources used
|
|
163
|
+
sources_used = ["Docker Compose file analysis"]
|
|
164
|
+
# Future: Add Kubernetes manifest and code analysis sources
|
|
165
|
+
|
|
166
|
+
metadata_parts.append(f"Detection sources: {', '.join(sources_used)}.")
|
|
167
|
+
|
|
168
|
+
# Document confidence levels
|
|
169
|
+
metadata_parts.append(
|
|
170
|
+
"Confidence levels: High confidence for infrastructure-defined components, "
|
|
171
|
+
"medium confidence for inferred roles based on naming and configuration patterns." # noqa: E501
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Document limitations
|
|
175
|
+
limitations = [
|
|
176
|
+
"analysis limited to Docker Compose configurations",
|
|
177
|
+
"code-level analysis not yet implemented",
|
|
178
|
+
"runtime behavior patterns not captured",
|
|
179
|
+
]
|
|
180
|
+
metadata_parts.append(f"Current limitations: {', '.join(limitations)}.")
|
|
181
|
+
|
|
182
|
+
return " ".join(metadata_parts)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Tracking domain module."""
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Domain service for resolving trackables to commits."""
|
|
2
|
+
|
|
3
|
+
import structlog
|
|
4
|
+
|
|
5
|
+
from kodit.domain.protocols import (
|
|
6
|
+
GitBranchRepository,
|
|
7
|
+
GitCommitRepository,
|
|
8
|
+
GitTagRepository,
|
|
9
|
+
)
|
|
10
|
+
from kodit.domain.tracking.trackable import Trackable, TrackableReferenceType
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TrackableResolutionService:
|
|
14
|
+
"""Resolves trackables to ordered lists of commits.
|
|
15
|
+
|
|
16
|
+
This is a domain service because it orchestrates multiple aggregates
|
|
17
|
+
(branches, tags, commits) without belonging to any single entity.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
commit_repo: GitCommitRepository,
|
|
23
|
+
branch_repo: GitBranchRepository,
|
|
24
|
+
tag_repo: GitTagRepository,
|
|
25
|
+
) -> None:
|
|
26
|
+
"""Initialize the trackable resolution service."""
|
|
27
|
+
self.commit_repo = commit_repo
|
|
28
|
+
self.branch_repo = branch_repo
|
|
29
|
+
self.tag_repo = tag_repo
|
|
30
|
+
self.log = structlog.get_logger(__name__)
|
|
31
|
+
|
|
32
|
+
async def resolve_to_commits(
|
|
33
|
+
self, trackable: Trackable, limit: int = 100
|
|
34
|
+
) -> list[str]:
|
|
35
|
+
"""Resolve a trackable to an ordered list of commit SHAs.
|
|
36
|
+
|
|
37
|
+
Returns commits from newest to oldest based on git history.
|
|
38
|
+
"""
|
|
39
|
+
if trackable.type == TrackableReferenceType.BRANCH:
|
|
40
|
+
return await self._resolve_branch(trackable, limit)
|
|
41
|
+
if trackable.type == TrackableReferenceType.TAG:
|
|
42
|
+
return await self._resolve_tag(trackable, limit)
|
|
43
|
+
# COMMIT_SHA
|
|
44
|
+
return [trackable.identifier]
|
|
45
|
+
|
|
46
|
+
async def _resolve_branch(
|
|
47
|
+
self, trackable: Trackable, limit: int
|
|
48
|
+
) -> list[str]:
|
|
49
|
+
"""Get commits from branch HEAD backwards through history."""
|
|
50
|
+
branch = await self.branch_repo.get_by_name(
|
|
51
|
+
trackable.identifier, trackable.repo_id
|
|
52
|
+
)
|
|
53
|
+
# Walk commit history from head_commit backwards
|
|
54
|
+
return await self._walk_commit_history(
|
|
55
|
+
branch.head_commit.commit_sha, limit
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
async def _resolve_tag(self, trackable: Trackable, limit: int) -> list[str]:
|
|
59
|
+
"""Get commits from tag target backwards through history."""
|
|
60
|
+
tag = await self.tag_repo.get_by_name(
|
|
61
|
+
trackable.identifier, trackable.repo_id
|
|
62
|
+
)
|
|
63
|
+
return await self._walk_commit_history(
|
|
64
|
+
tag.target_commit.commit_sha, limit
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
async def _walk_commit_history(
|
|
68
|
+
self, start_sha: str, limit: int
|
|
69
|
+
) -> list[str]:
|
|
70
|
+
"""Walk commit history backwards from start_sha."""
|
|
71
|
+
result = []
|
|
72
|
+
current_sha: str | None = start_sha
|
|
73
|
+
|
|
74
|
+
for _ in range(limit):
|
|
75
|
+
if not current_sha:
|
|
76
|
+
break
|
|
77
|
+
result.append(current_sha)
|
|
78
|
+
commit = await self.commit_repo.get_by_sha(current_sha)
|
|
79
|
+
current_sha = commit.parent_commit_sha or None
|
|
80
|
+
|
|
81
|
+
return result
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Trackable value objects."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from enum import StrEnum
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TrackableReferenceType(StrEnum):
|
|
8
|
+
"""Types of git references that can be tracked."""
|
|
9
|
+
|
|
10
|
+
BRANCH = "branch"
|
|
11
|
+
TAG = "tag"
|
|
12
|
+
COMMIT_SHA = "commit_sha"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class Trackable:
|
|
17
|
+
"""Represents a trackable reference point in a git repository."""
|
|
18
|
+
|
|
19
|
+
type: TrackableReferenceType
|
|
20
|
+
identifier: str # e.g., "main", "v1.0.0", "abc123..."
|
|
21
|
+
repo_id: int
|
kodit/domain/value_objects.py
CHANGED
|
@@ -346,29 +346,6 @@ class EmbeddingResponse:
|
|
|
346
346
|
embedding: list[float]
|
|
347
347
|
|
|
348
348
|
|
|
349
|
-
@dataclass
|
|
350
|
-
class EnrichmentRequest:
|
|
351
|
-
"""Domain model for enrichment request."""
|
|
352
|
-
|
|
353
|
-
snippet_id: str
|
|
354
|
-
text: str
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
@dataclass
|
|
358
|
-
class EnrichmentResponse:
|
|
359
|
-
"""Domain model for enrichment response."""
|
|
360
|
-
|
|
361
|
-
snippet_id: str
|
|
362
|
-
text: str
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
@dataclass
|
|
366
|
-
class EnrichmentIndexRequest:
|
|
367
|
-
"""Domain model for enrichment index request."""
|
|
368
|
-
|
|
369
|
-
requests: list[EnrichmentRequest]
|
|
370
|
-
|
|
371
|
-
|
|
372
349
|
@dataclass
|
|
373
350
|
class IndexView:
|
|
374
351
|
"""Domain model for index information."""
|
|
@@ -640,6 +617,10 @@ class TaskOperation(StrEnum):
|
|
|
640
617
|
CREATE_CODE_EMBEDDINGS_FOR_COMMIT = "kodit.commit.create_code_embeddings"
|
|
641
618
|
CREATE_SUMMARY_ENRICHMENT_FOR_COMMIT = "kodit.commit.create_summary_enrichment"
|
|
642
619
|
CREATE_SUMMARY_EMBEDDINGS_FOR_COMMIT = "kodit.commit.create_summary_embeddings"
|
|
620
|
+
CREATE_ARCHITECTURE_ENRICHMENT_FOR_COMMIT = (
|
|
621
|
+
"kodit.commit.create_architecture_enrichment"
|
|
622
|
+
)
|
|
623
|
+
CREATE_PUBLIC_API_DOCS_FOR_COMMIT = "kodit.commit.create_public_api_docs"
|
|
643
624
|
|
|
644
625
|
def is_repository_operation(self) -> bool:
|
|
645
626
|
"""Check if the task operation is a repository operation."""
|
|
@@ -663,6 +644,8 @@ class PrescribedOperations:
|
|
|
663
644
|
TaskOperation.CREATE_CODE_EMBEDDINGS_FOR_COMMIT,
|
|
664
645
|
TaskOperation.CREATE_SUMMARY_ENRICHMENT_FOR_COMMIT,
|
|
665
646
|
TaskOperation.CREATE_SUMMARY_EMBEDDINGS_FOR_COMMIT,
|
|
647
|
+
TaskOperation.CREATE_ARCHITECTURE_ENRICHMENT_FOR_COMMIT,
|
|
648
|
+
TaskOperation.CREATE_PUBLIC_API_DOCS_FOR_COMMIT,
|
|
666
649
|
]
|
|
667
650
|
SYNC_REPOSITORY: ClassVar[list[TaskOperation]] = [
|
|
668
651
|
TaskOperation.SCAN_REPOSITORY,
|