graph-dependency-analyzer 0.2.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.
- graph_dependency_analyzer-0.2.0.dist-info/METADATA +158 -0
- graph_dependency_analyzer-0.2.0.dist-info/RECORD +86 -0
- graph_dependency_analyzer-0.2.0.dist-info/WHEEL +5 -0
- graph_dependency_analyzer-0.2.0.dist-info/entry_points.txt +2 -0
- graph_dependency_analyzer-0.2.0.dist-info/top_level.txt +1 -0
- src/__init__.py +0 -0
- src/application/__init__.py +0 -0
- src/application/dtos/__init__.py +0 -0
- src/application/dtos/affected_component.py +59 -0
- src/application/dtos/batch_processing_result.py +17 -0
- src/application/dtos/impact_analysis_response.py +56 -0
- src/application/dtos/impact_query_request.py +38 -0
- src/application/dtos/index_batch_request.py +53 -0
- src/application/dtos/index_batch_response.py +72 -0
- src/application/dtos/index_result.py +20 -0
- src/application/dtos/repository_index_result.py +79 -0
- src/application/exceptions.py +41 -0
- src/application/services/__init__.py +0 -0
- src/application/services/file_scanner_service.py +258 -0
- src/application/services/progress_tracker.py +132 -0
- src/application/use_cases/__init__.py +0 -0
- src/application/use_cases/clear_all_data_use_case.py +180 -0
- src/application/use_cases/index_batch_use_case.py +153 -0
- src/application/use_cases/index_repository_use_case.py +683 -0
- src/application/use_cases/indexing_orchestrator.py +181 -0
- src/application/use_cases/list_repositories_use_case.py +84 -0
- src/cli.py +512 -0
- src/config/__init__.py +5 -0
- src/config/container.py +211 -0
- src/config/logging_config.py +123 -0
- src/config/settings.py +165 -0
- src/domain/__init__.py +0 -0
- src/domain/entities/__init__.py +20 -0
- src/domain/entities/ast_node.py +32 -0
- src/domain/entities/code_chunk.py +45 -0
- src/domain/entities/code_file.py +44 -0
- src/domain/entities/dependency_graph.py +110 -0
- src/domain/entities/repository.py +46 -0
- src/domain/exceptions.py +36 -0
- src/domain/repositories/__init__.py +22 -0
- src/domain/repositories/i_code_parser.py +50 -0
- src/domain/repositories/i_dependency_extractor.py +35 -0
- src/domain/repositories/i_embedding_provider.py +31 -0
- src/domain/repositories/i_graph_repository.py +78 -0
- src/domain/repositories/i_vector_repository.py +55 -0
- src/domain/services/__init__.py +8 -0
- src/domain/services/dependency_extractor.py +962 -0
- src/domain/services/semantic_chunk_builder.py +719 -0
- src/domain/value_objects/__init__.py +12 -0
- src/domain/value_objects/chunk_type.py +24 -0
- src/domain/value_objects/dependency_type.py +22 -0
- src/domain/value_objects/node_type.py +22 -0
- src/domain/value_objects/qualified_name.py +78 -0
- src/infrastructure/__init__.py +0 -0
- src/infrastructure/exceptions.py +41 -0
- src/infrastructure/external_apis/__init__.py +0 -0
- src/infrastructure/external_apis/ollama_embedding_provider.py +134 -0
- src/infrastructure/external_apis/openai_embedding_provider.py +231 -0
- src/infrastructure/parsing/__init__.py +20 -0
- src/infrastructure/parsing/config_file_parser.py +347 -0
- src/infrastructure/parsing/gradle_dependency_parser.py +159 -0
- src/infrastructure/parsing/groovy_parser.py +198 -0
- src/infrastructure/parsing/maven_dependency_parser.py +147 -0
- src/infrastructure/parsing/microfrontend_config_parser.py +242 -0
- src/infrastructure/parsing/npm_package_dependency_parser.py +255 -0
- src/infrastructure/parsing/python_parser.py +211 -0
- src/infrastructure/parsing/sql_schema_parser.py +658 -0
- src/infrastructure/parsing/tree_sitter_java_parser.py +434 -0
- src/infrastructure/parsing/tree_sitter_typescript_parser.py +600 -0
- src/infrastructure/persistence/__init__.py +0 -0
- src/infrastructure/persistence/graph_export_repository.py +443 -0
- src/infrastructure/persistence/neo4j_dependency_repository.py +657 -0
- src/infrastructure/persistence/qdrant_vector_repository.py +357 -0
- src/infrastructure/persistence/repository_relationship_repository.py +1312 -0
- src/presentation/__init__.py +0 -0
- src/presentation/api/__init__.py +0 -0
- src/presentation/api/rest/__init__.py +0 -0
- src/presentation/api/rest/exception_handlers.py +174 -0
- src/presentation/api/rest/main.py +80 -0
- src/presentation/api/rest/routers/__init__.py +5 -0
- src/presentation/api/rest/routers/admin.py +103 -0
- src/presentation/api/rest/routers/analysis.py +980 -0
- src/presentation/api/rest/routers/export.py +442 -0
- src/presentation/api/rest/routers/health.py +117 -0
- src/presentation/api/rest/routers/indexing.py +148 -0
- src/presentation/api/rest/routers/relationships.py +1089 -0
src/config/container.py
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Container de injeção de dependências usando dependency-injector.
|
|
3
|
+
|
|
4
|
+
Requirements: 12.1, 12.2, 12.3, 12.4, 12.5
|
|
5
|
+
"""
|
|
6
|
+
from functools import lru_cache
|
|
7
|
+
|
|
8
|
+
from dependency_injector import containers, providers
|
|
9
|
+
|
|
10
|
+
from src.config.settings import Settings
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _build_embedding_provider(config):
|
|
14
|
+
"""
|
|
15
|
+
Factory that returns the correct embedding provider based on EMBEDDING_PROVIDER env var.
|
|
16
|
+
|
|
17
|
+
- "flow" or "openai" → OpenAIEmbeddingProvider (uses OPENAI_BASE_URL + OPENAI_API_KEY)
|
|
18
|
+
- "ollama" → OllamaEmbeddingProvider (uses OLLAMA_BASE_URL + OLLAMA_EMBEDDING_MODEL)
|
|
19
|
+
"""
|
|
20
|
+
provider_type = getattr(config, "embedding_provider", "flow").lower()
|
|
21
|
+
|
|
22
|
+
if provider_type == "ollama":
|
|
23
|
+
from src.infrastructure.external_apis.ollama_embedding_provider import OllamaEmbeddingProvider
|
|
24
|
+
import structlog
|
|
25
|
+
structlog.get_logger(__name__).info(
|
|
26
|
+
"embedding_provider_selected",
|
|
27
|
+
provider="ollama",
|
|
28
|
+
model=getattr(config, "ollama_embedding_model", "nomic-embed-text"),
|
|
29
|
+
base_url=getattr(config, "ollama_base_url", "http://localhost:11434"),
|
|
30
|
+
)
|
|
31
|
+
return OllamaEmbeddingProvider(config=config)
|
|
32
|
+
else:
|
|
33
|
+
from src.infrastructure.external_apis.openai_embedding_provider import OpenAIEmbeddingProvider
|
|
34
|
+
import structlog
|
|
35
|
+
structlog.get_logger(__name__).info(
|
|
36
|
+
"embedding_provider_selected",
|
|
37
|
+
provider=provider_type,
|
|
38
|
+
model=getattr(config, "openai_embedding_model", "text-embedding-3-small"),
|
|
39
|
+
base_url=getattr(config, "openai_base_url", ""),
|
|
40
|
+
)
|
|
41
|
+
return OpenAIEmbeddingProvider(config=config)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Container(containers.DeclarativeContainer):
|
|
45
|
+
"""
|
|
46
|
+
Container central de injeção de dependências.
|
|
47
|
+
|
|
48
|
+
Registra todos os componentes da aplicação e gerencia seu ciclo de vida:
|
|
49
|
+
- Configurações (singleton)
|
|
50
|
+
- Componentes de infraestrutura (singletons)
|
|
51
|
+
- Use cases (factories)
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
# Configuração
|
|
55
|
+
config = providers.Singleton(Settings)
|
|
56
|
+
|
|
57
|
+
# === Application Services ===
|
|
58
|
+
file_scanner = providers.Factory(
|
|
59
|
+
"src.application.services.file_scanner_service.FileScannerService"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# === Infrastructure - Parsing ===
|
|
63
|
+
java_parser = providers.Singleton( # FIXED: Renamed from tree_sitter_parser (Task 7.1)
|
|
64
|
+
"src.infrastructure.parsing.tree_sitter_java_parser.TreeSitterJavaParser"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
typescript_parser = providers.Singleton( # Task 12.1
|
|
68
|
+
"src.infrastructure.parsing.tree_sitter_typescript_parser.TreeSitterTypeScriptParser"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
npm_parser = providers.Singleton( # Task 12.2
|
|
72
|
+
"src.infrastructure.parsing.npm_package_dependency_parser.NpmPackageDependencyParser"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
microfrontend_parser = providers.Factory( # Task 12.3
|
|
76
|
+
"src.infrastructure.parsing.microfrontend_config_parser.MicrofrontendConfigParser",
|
|
77
|
+
typescript_parser=typescript_parser # Inject typescript_parser dependency
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
config_parser = providers.Singleton( # Task 12.4 (for DependencyExtractor)
|
|
81
|
+
"src.infrastructure.parsing.config_file_parser.ConfigFileParser"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# === Domain Services ===
|
|
85
|
+
dependency_extractor = providers.Factory( # Task 12.4
|
|
86
|
+
"src.domain.services.dependency_extractor.DependencyExtractor",
|
|
87
|
+
typescript_parser=typescript_parser,
|
|
88
|
+
npm_parser=npm_parser,
|
|
89
|
+
microfrontend_parser=microfrontend_parser,
|
|
90
|
+
config_parser=config_parser
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
chunk_builder = providers.Factory(
|
|
94
|
+
"src.domain.services.semantic_chunk_builder.SemanticChunkBuilder"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# === Infrastructure - External APIs ===
|
|
98
|
+
# Embedding provider is selected by EMBEDDING_PROVIDER env var:
|
|
99
|
+
# "flow" → OpenAIEmbeddingProvider with CI&T Flow proxy (default)
|
|
100
|
+
# "openai" → OpenAIEmbeddingProvider with OpenAI directly
|
|
101
|
+
# "ollama" → OllamaEmbeddingProvider with local Ollama instance
|
|
102
|
+
embedding_provider = providers.Singleton(
|
|
103
|
+
_build_embedding_provider,
|
|
104
|
+
config=config.provided.embedding,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# === Infrastructure - Clients ===
|
|
108
|
+
qdrant_client = providers.Singleton(
|
|
109
|
+
"qdrant_client.QdrantClient",
|
|
110
|
+
url=config.provided.qdrant_url
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Neo4j driver - usar Callable para chamar método estático
|
|
114
|
+
neo4j_driver = providers.Resource(
|
|
115
|
+
lambda uri, user, password: __import__('neo4j').AsyncGraphDatabase.driver(
|
|
116
|
+
uri, auth=(user, password)
|
|
117
|
+
),
|
|
118
|
+
uri=config.provided.neo4j_uri,
|
|
119
|
+
user=config.provided.neo4j_user,
|
|
120
|
+
password=config.provided.neo4j_password
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# === Infrastructure - Persistence ===
|
|
124
|
+
qdrant_repository = providers.Singleton(
|
|
125
|
+
"src.infrastructure.persistence.qdrant_vector_repository.QdrantVectorRepository",
|
|
126
|
+
client=qdrant_client,
|
|
127
|
+
collection_name=config.provided.qdrant_collection,
|
|
128
|
+
batch_size=providers.Object(1000),
|
|
129
|
+
vector_size=config.provided.qdrant_vector_size,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
neo4j_repository = providers.Singleton(
|
|
133
|
+
"src.infrastructure.persistence.neo4j_dependency_repository.Neo4jDependencyGraphRepository",
|
|
134
|
+
driver=neo4j_driver,
|
|
135
|
+
batch_size=providers.Object(100)
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
sql_parser = providers.Singleton(
|
|
139
|
+
"src.infrastructure.parsing.sql_schema_parser.SqlSchemaParser"
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
python_parser = providers.Singleton(
|
|
143
|
+
"src.infrastructure.parsing.python_parser.PythonParser"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
groovy_parser = providers.Singleton(
|
|
147
|
+
"src.infrastructure.parsing.groovy_parser.GroovyParser"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# === Use Cases ===
|
|
151
|
+
index_repository_use_case = providers.Factory(
|
|
152
|
+
"src.application.use_cases.index_repository_use_case.IndexRepositoryUseCase",
|
|
153
|
+
file_scanner=file_scanner,
|
|
154
|
+
java_parser=java_parser,
|
|
155
|
+
typescript_parser=typescript_parser,
|
|
156
|
+
dependency_extractor=dependency_extractor,
|
|
157
|
+
chunk_builder=chunk_builder,
|
|
158
|
+
embedding_provider=embedding_provider,
|
|
159
|
+
qdrant_repository=qdrant_repository,
|
|
160
|
+
neo4j_repository=neo4j_repository,
|
|
161
|
+
sql_parser=sql_parser,
|
|
162
|
+
python_parser=python_parser,
|
|
163
|
+
groovy_parser=groovy_parser,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
indexing_orchestrator = providers.Factory(
|
|
167
|
+
"src.application.use_cases.indexing_orchestrator.IndexingOrchestrator",
|
|
168
|
+
index_repository_use_case=index_repository_use_case
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
index_batch_use_case = providers.Factory(
|
|
172
|
+
"src.application.use_cases.index_batch_use_case.IndexBatchUseCase",
|
|
173
|
+
orchestrator=indexing_orchestrator,
|
|
174
|
+
settings=config
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
list_repositories_use_case = providers.Factory(
|
|
178
|
+
"src.application.use_cases.list_repositories_use_case.ListRepositoriesUseCase",
|
|
179
|
+
graph_repository=neo4j_repository
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
clear_all_data_use_case = providers.Factory(
|
|
183
|
+
"src.application.use_cases.clear_all_data_use_case.ClearAllDataUseCase",
|
|
184
|
+
graph_repository=neo4j_repository,
|
|
185
|
+
qdrant_client=qdrant_client,
|
|
186
|
+
collection_name="code_chunks"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
graph_export_repository = providers.Singleton(
|
|
190
|
+
"src.infrastructure.persistence.graph_export_repository.GraphExportRepository",
|
|
191
|
+
driver=neo4j_driver,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
relationship_repository = providers.Singleton(
|
|
195
|
+
"src.infrastructure.persistence.repository_relationship_repository.RepositoryRelationshipRepository",
|
|
196
|
+
driver=neo4j_driver,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@lru_cache
|
|
201
|
+
def get_container() -> Container:
|
|
202
|
+
"""
|
|
203
|
+
Retorna instância singleton do Container.
|
|
204
|
+
|
|
205
|
+
Usa @lru_cache para garantir que apenas uma instância do container
|
|
206
|
+
é criada e reutilizada em toda a aplicação.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
Container: Container de injeção de dependências
|
|
210
|
+
"""
|
|
211
|
+
return Container()
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuração de logging estruturado com structlog.
|
|
3
|
+
|
|
4
|
+
Requirements: 11.1, 11.2, 11.3, 11.4, 11.5
|
|
5
|
+
"""
|
|
6
|
+
import logging
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Any, Literal
|
|
9
|
+
|
|
10
|
+
import structlog
|
|
11
|
+
from structlog.types import EventDict, WrappedLogger
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Lista de campos sensíveis que devem ser sanitizados
|
|
15
|
+
SENSITIVE_FIELDS = {
|
|
16
|
+
"password",
|
|
17
|
+
"api_key",
|
|
18
|
+
"token",
|
|
19
|
+
"secret",
|
|
20
|
+
"authorization",
|
|
21
|
+
"auth",
|
|
22
|
+
"apikey",
|
|
23
|
+
"access_token",
|
|
24
|
+
"refresh_token",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def sanitize_event(
|
|
29
|
+
logger: WrappedLogger,
|
|
30
|
+
method_name: str,
|
|
31
|
+
event_dict: EventDict
|
|
32
|
+
) -> EventDict:
|
|
33
|
+
"""
|
|
34
|
+
Sanitiza dados sensíveis substituindo valores por ***REDACTED***.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
logger: Logger do structlog
|
|
38
|
+
method_name: Nome do método sendo chamado
|
|
39
|
+
event_dict: Dicionário do evento de log
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
EventDict com dados sensíveis sanitizados
|
|
43
|
+
"""
|
|
44
|
+
for key in list(event_dict.keys()):
|
|
45
|
+
# Verificar se a chave é sensível (case-insensitive)
|
|
46
|
+
if key.lower() in SENSITIVE_FIELDS:
|
|
47
|
+
event_dict[key] = "***REDACTED***"
|
|
48
|
+
|
|
49
|
+
return event_dict
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def configure_logging(
|
|
53
|
+
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO"
|
|
54
|
+
) -> None:
|
|
55
|
+
"""
|
|
56
|
+
Configura o sistema de logging estruturado com structlog.
|
|
57
|
+
|
|
58
|
+
Configuração inclui:
|
|
59
|
+
- Formato JSON
|
|
60
|
+
- Timestamp ISO8601
|
|
61
|
+
- Nível de log
|
|
62
|
+
- Nome do logger
|
|
63
|
+
- Mensagem e contexto estruturado
|
|
64
|
+
- Sanitização de dados sensíveis
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
log_level: Nível de log (padrão: INFO)
|
|
68
|
+
"""
|
|
69
|
+
# Mapear string para nível do logging
|
|
70
|
+
level_map = {
|
|
71
|
+
"DEBUG": logging.DEBUG,
|
|
72
|
+
"INFO": logging.INFO,
|
|
73
|
+
"WARNING": logging.WARNING,
|
|
74
|
+
"ERROR": logging.ERROR,
|
|
75
|
+
"CRITICAL": logging.CRITICAL,
|
|
76
|
+
}
|
|
77
|
+
numeric_level = level_map.get(log_level.upper(), logging.INFO)
|
|
78
|
+
|
|
79
|
+
# Configurar logging padrão do Python
|
|
80
|
+
logging.basicConfig(
|
|
81
|
+
format="%(message)s",
|
|
82
|
+
stream=sys.stdout,
|
|
83
|
+
level=numeric_level,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Configurar structlog
|
|
87
|
+
structlog.configure(
|
|
88
|
+
processors=[
|
|
89
|
+
# Adicionar nível de log
|
|
90
|
+
structlog.stdlib.add_log_level,
|
|
91
|
+
# Adicionar nome do logger
|
|
92
|
+
structlog.stdlib.add_logger_name,
|
|
93
|
+
# Adicionar timestamp ISO8601
|
|
94
|
+
structlog.processors.TimeStamper(fmt="iso"),
|
|
95
|
+
# Sanitizar dados sensíveis
|
|
96
|
+
sanitize_event,
|
|
97
|
+
# Adicionar informações de exceção
|
|
98
|
+
structlog.processors.ExceptionRenderer(),
|
|
99
|
+
# Processar stack info
|
|
100
|
+
structlog.processors.StackInfoRenderer(),
|
|
101
|
+
# Formatar para dict
|
|
102
|
+
structlog.processors.dict_tracebacks,
|
|
103
|
+
# Renderizar como JSON
|
|
104
|
+
structlog.processors.JSONRenderer(),
|
|
105
|
+
],
|
|
106
|
+
wrapper_class=structlog.stdlib.BoundLogger,
|
|
107
|
+
context_class=dict,
|
|
108
|
+
logger_factory=structlog.stdlib.LoggerFactory(),
|
|
109
|
+
cache_logger_on_first_use=True,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def get_logger(name: str) -> structlog.stdlib.BoundLogger:
|
|
114
|
+
"""
|
|
115
|
+
Retorna um logger estruturado configurado.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
name: Nome do logger (geralmente __name__ do módulo)
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Logger estruturado do structlog
|
|
122
|
+
"""
|
|
123
|
+
return structlog.get_logger(name)
|
src/config/settings.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sistema de configuração centralizado usando Pydantic Settings.
|
|
3
|
+
|
|
4
|
+
Requirements: 10.1, 10.2, 10.3, 10.4, 10.5, 10.6
|
|
5
|
+
"""
|
|
6
|
+
from functools import lru_cache
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Literal
|
|
9
|
+
|
|
10
|
+
from pydantic import Field
|
|
11
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
12
|
+
|
|
13
|
+
# Resolve .env relative to the project root (two levels up from this file)
|
|
14
|
+
_PROJECT_ROOT = Path(__file__).parent.parent.parent
|
|
15
|
+
_ENV_FILE = _PROJECT_ROOT / ".env"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DatabaseConfig:
|
|
19
|
+
"""Agrupamento de configurações Neo4j (namespace apenas)."""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class VectorConfig:
|
|
24
|
+
"""Agrupamento de configurações Qdrant (namespace apenas)."""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class EmbeddingConfig:
|
|
29
|
+
"""Agrupamento de configurações OpenAI (namespace apenas)."""
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class RepositoryConfig:
|
|
34
|
+
"""Agrupamento de configurações de repositórios (namespace apenas)."""
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Settings(BaseSettings):
|
|
39
|
+
"""
|
|
40
|
+
Configurações centralizadas da aplicação.
|
|
41
|
+
|
|
42
|
+
Todas as configurações são carregadas de variáveis de ambiente via .env file.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
# Configurações gerais
|
|
46
|
+
environment: Literal["development", "staging", "production"] = Field(
|
|
47
|
+
default="development",
|
|
48
|
+
description="Ambiente de execução"
|
|
49
|
+
)
|
|
50
|
+
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = Field(
|
|
51
|
+
default="INFO",
|
|
52
|
+
description="Nível de log"
|
|
53
|
+
)
|
|
54
|
+
api_port: int = Field(default=8000, description="Porta da API")
|
|
55
|
+
|
|
56
|
+
# Neo4j Database Configuration
|
|
57
|
+
neo4j_uri: str = Field(..., description="URI de conexão Neo4j (ex: bolt://localhost:7687)")
|
|
58
|
+
neo4j_user: str = Field(..., description="Usuário Neo4j")
|
|
59
|
+
neo4j_password: str = Field(..., description="Senha Neo4j")
|
|
60
|
+
neo4j_pool_size: int = Field(default=50, description="Tamanho do pool de conexões")
|
|
61
|
+
|
|
62
|
+
# Qdrant Vector Database Configuration
|
|
63
|
+
qdrant_url: str = Field(..., description="URL do servidor Qdrant")
|
|
64
|
+
qdrant_collection: str = Field(default="code_chunks", description="Nome da collection")
|
|
65
|
+
qdrant_vector_size: int = Field(default=1536, description="Dimensão dos vetores")
|
|
66
|
+
|
|
67
|
+
# Embedding provider selection
|
|
68
|
+
# Options:
|
|
69
|
+
# "openai" — OpenAI API directly (default)
|
|
70
|
+
# "flow" — CI&T Flow proxy (uses OPENAI_BASE_URL + OPENAI_API_KEY)
|
|
71
|
+
# "ollama" — Local Ollama instance (free, no rate limits)
|
|
72
|
+
embedding_provider: Literal["openai", "flow", "ollama"] = Field(
|
|
73
|
+
default="flow",
|
|
74
|
+
description="Embedding provider: openai | flow | ollama"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# OpenAI / Flow Embeddings Configuration
|
|
78
|
+
openai_api_key: str = Field(default="", description="Chave API OpenAI ou Flow token")
|
|
79
|
+
openai_base_url: str = Field(
|
|
80
|
+
default="https://api.openai.com/v1",
|
|
81
|
+
description="URL base da API OpenAI ou Flow proxy"
|
|
82
|
+
)
|
|
83
|
+
openai_embedding_model: str = Field(
|
|
84
|
+
default="text-embedding-3-small",
|
|
85
|
+
description="Modelo de embedding OpenAI/Flow"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Ollama Configuration (used when EMBEDDING_PROVIDER=ollama)
|
|
89
|
+
ollama_base_url: str = Field(
|
|
90
|
+
default="http://localhost:11434",
|
|
91
|
+
description="URL do servidor Ollama local"
|
|
92
|
+
)
|
|
93
|
+
ollama_embedding_model: str = Field(
|
|
94
|
+
default="nomic-embed-text",
|
|
95
|
+
description="Modelo de embedding Ollama (nomic-embed-text, mxbai-embed-large, all-minilm)"
|
|
96
|
+
)
|
|
97
|
+
# nomic-embed-text produces 768-dim vectors; mxbai-embed-large produces 1024-dim
|
|
98
|
+
# all-minilm produces 384-dim. Set QDRANT_VECTOR_SIZE to match.
|
|
99
|
+
ollama_embedding_dim: int = Field(
|
|
100
|
+
default=768,
|
|
101
|
+
description="Dimensão do vetor do modelo Ollama (768 para nomic-embed-text)"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Repositories Configuration
|
|
105
|
+
repositories_path: str = Field(..., description="Caminho base para repositórios")
|
|
106
|
+
|
|
107
|
+
model_config = SettingsConfigDict(
|
|
108
|
+
env_file=str(_ENV_FILE),
|
|
109
|
+
env_file_encoding="utf-8",
|
|
110
|
+
case_sensitive=False,
|
|
111
|
+
extra="ignore",
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def database(self) -> DatabaseConfig:
|
|
116
|
+
"""Acesso compatível ao namespace database."""
|
|
117
|
+
config = DatabaseConfig()
|
|
118
|
+
config.neo4j_uri = self.neo4j_uri
|
|
119
|
+
config.neo4j_user = self.neo4j_user
|
|
120
|
+
config.neo4j_password = self.neo4j_password
|
|
121
|
+
config.neo4j_pool_size = self.neo4j_pool_size
|
|
122
|
+
return config
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def vector(self) -> VectorConfig:
|
|
126
|
+
"""Acesso compatível ao namespace vector."""
|
|
127
|
+
config = VectorConfig()
|
|
128
|
+
config.qdrant_url = self.qdrant_url
|
|
129
|
+
config.qdrant_collection = self.qdrant_collection
|
|
130
|
+
config.qdrant_vector_size = self.qdrant_vector_size
|
|
131
|
+
return config
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def embedding(self) -> EmbeddingConfig:
|
|
135
|
+
"""Acesso compatível ao namespace embedding."""
|
|
136
|
+
config = EmbeddingConfig()
|
|
137
|
+
config.openai_api_key = self.openai_api_key
|
|
138
|
+
config.openai_base_url = self.openai_base_url
|
|
139
|
+
config.openai_embedding_model = self.openai_embedding_model
|
|
140
|
+
config.embedding_provider = self.embedding_provider
|
|
141
|
+
config.ollama_base_url = self.ollama_base_url
|
|
142
|
+
config.ollama_embedding_model = self.ollama_embedding_model
|
|
143
|
+
config.ollama_embedding_dim = self.ollama_embedding_dim
|
|
144
|
+
return config
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def repository(self) -> RepositoryConfig:
|
|
148
|
+
"""Acesso compatível ao namespace repository."""
|
|
149
|
+
config = RepositoryConfig()
|
|
150
|
+
config.repositories_path = self.repositories_path
|
|
151
|
+
return config
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@lru_cache
|
|
155
|
+
def get_settings() -> Settings:
|
|
156
|
+
"""
|
|
157
|
+
Retorna instância singleton de Settings.
|
|
158
|
+
|
|
159
|
+
Usa @lru_cache para garantir que apenas uma instância é criada
|
|
160
|
+
e reutilizada em toda a aplicação.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Settings: Configurações da aplicação
|
|
164
|
+
"""
|
|
165
|
+
return Settings()
|
src/domain/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Domain entities module."""
|
|
2
|
+
from src.domain.entities.repository import Repository
|
|
3
|
+
from src.domain.entities.code_file import CodeFile
|
|
4
|
+
from src.domain.entities.ast_node import ASTNode
|
|
5
|
+
from src.domain.entities.code_chunk import CodeChunk
|
|
6
|
+
from src.domain.entities.dependency_graph import (
|
|
7
|
+
DependencyGraph,
|
|
8
|
+
DependencyNode,
|
|
9
|
+
DependencyEdge,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"Repository",
|
|
14
|
+
"CodeFile",
|
|
15
|
+
"ASTNode",
|
|
16
|
+
"CodeChunk",
|
|
17
|
+
"DependencyGraph",
|
|
18
|
+
"DependencyNode",
|
|
19
|
+
"DependencyEdge",
|
|
20
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""ASTNode entity - represents a parsed AST structure."""
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class ASTNode:
|
|
7
|
+
"""
|
|
8
|
+
ASTNode entity representing a parsed Abstract Syntax Tree node.
|
|
9
|
+
|
|
10
|
+
Attributes:
|
|
11
|
+
node_type: Type of node (CLASS, METHOD, IMPORT, etc.)
|
|
12
|
+
name: Simple name of the element
|
|
13
|
+
qualified_name: Fully qualified name (e.g., com.example.Class.method)
|
|
14
|
+
line_start: Starting line number in source file
|
|
15
|
+
line_end: Ending line number in source file
|
|
16
|
+
metadata: Additional metadata (parameters, return_type, modifiers, etc.)
|
|
17
|
+
|
|
18
|
+
Business Rules:
|
|
19
|
+
- line_end must be >= line_start
|
|
20
|
+
- qualified_name must be unique within repository
|
|
21
|
+
"""
|
|
22
|
+
node_type: str
|
|
23
|
+
name: str
|
|
24
|
+
qualified_name: str
|
|
25
|
+
line_start: int
|
|
26
|
+
line_end: int
|
|
27
|
+
metadata: dict = field(default_factory=dict)
|
|
28
|
+
|
|
29
|
+
def __post_init__(self):
|
|
30
|
+
"""Validate business rules after initialization."""
|
|
31
|
+
if self.line_end < self.line_start:
|
|
32
|
+
raise ValueError("line_end must be >= line_start")
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""CodeChunk entity - represents a semantic code chunk for embeddings."""
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from uuid import UUID, uuid4
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class CodeChunk:
|
|
8
|
+
"""
|
|
9
|
+
CodeChunk entity representing a semantic code chunk.
|
|
10
|
+
|
|
11
|
+
Chunks are method-based (not line-based) and include rich metadata
|
|
12
|
+
for embedding generation and retrieval.
|
|
13
|
+
|
|
14
|
+
Attributes:
|
|
15
|
+
id: Unique identifier (UUID v4)
|
|
16
|
+
content: Full source code of the chunk
|
|
17
|
+
chunk_type: Type of chunk (METHOD, CLASS, IMPORT_BLOCK, etc.)
|
|
18
|
+
metadata: Rich metadata (qualified_name, parameters, line_range, etc.)
|
|
19
|
+
embedding: Optional embedding vector (1536 dimensions)
|
|
20
|
+
|
|
21
|
+
Business Rules:
|
|
22
|
+
- Chunks never contain partial methods (always complete)
|
|
23
|
+
- Embedding must be exactly 1536 dimensions if set
|
|
24
|
+
- Each chunk has a unique ID
|
|
25
|
+
"""
|
|
26
|
+
content: str
|
|
27
|
+
chunk_type: str
|
|
28
|
+
metadata: dict
|
|
29
|
+
id: UUID = field(default_factory=uuid4)
|
|
30
|
+
embedding: list[float] | None = None
|
|
31
|
+
|
|
32
|
+
def set_embedding(self, embedding: list[float]) -> None:
|
|
33
|
+
"""
|
|
34
|
+
Set the embedding vector for this chunk.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
embedding: 1536-dimensional embedding vector
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
ValueError: If embedding is not 1536 dimensions
|
|
41
|
+
"""
|
|
42
|
+
if len(embedding) != 1536:
|
|
43
|
+
raise ValueError("Embedding must have 1536 dimensions")
|
|
44
|
+
|
|
45
|
+
self.embedding = embedding
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""CodeFile entity - represents a source code file."""
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class CodeFile:
|
|
8
|
+
"""
|
|
9
|
+
CodeFile entity representing a single source code file.
|
|
10
|
+
|
|
11
|
+
Attributes:
|
|
12
|
+
file_path: Relative path to file within repository
|
|
13
|
+
language: Programming language (e.g., "java")
|
|
14
|
+
content: Full source code content
|
|
15
|
+
ast_nodes: List of parsed AST nodes
|
|
16
|
+
chunks: List of semantic code chunks
|
|
17
|
+
|
|
18
|
+
Business Rules:
|
|
19
|
+
- Each file belongs to exactly one repository
|
|
20
|
+
- File path must be relative to repository root
|
|
21
|
+
"""
|
|
22
|
+
file_path: Path
|
|
23
|
+
language: str
|
|
24
|
+
content: str
|
|
25
|
+
ast_nodes: list = field(default_factory=list)
|
|
26
|
+
chunks: list = field(default_factory=list)
|
|
27
|
+
|
|
28
|
+
def add_ast_node(self, ast_node) -> None:
|
|
29
|
+
"""
|
|
30
|
+
Add an AST node extracted from this file.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
ast_node: ASTNode entity to add
|
|
34
|
+
"""
|
|
35
|
+
self.ast_nodes.append(ast_node)
|
|
36
|
+
|
|
37
|
+
def add_chunk(self, chunk) -> None:
|
|
38
|
+
"""
|
|
39
|
+
Add a semantic chunk created from this file.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
chunk: CodeChunk entity to add
|
|
43
|
+
"""
|
|
44
|
+
self.chunks.append(chunk)
|