coding-client 1.21.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.
- coding_client/__init__.py +1 -0
- coding_client/api/__init__.py +19 -0
- coding_client/api/exception_handlers.py +158 -0
- coding_client/api/logging_setup.py +44 -0
- coding_client/api/metrics.py +40 -0
- coding_client/api/openapi_config.py +124 -0
- coding_client/api/request_logging.py +60 -0
- coding_client/api/validation.py +150 -0
- coding_client/auth/__init__.py +78 -0
- coding_client/auth/_default_tenant.py +132 -0
- coding_client/auth/_header_trust.py +98 -0
- coding_client/auth/_onbehalf.py +315 -0
- coding_client/auth/_trusted_networks.py +94 -0
- coding_client/auth/dependencies.py +61 -0
- coding_client/auth/middleware.py +445 -0
- coding_client/auth/models.py +83 -0
- coding_client/auth/outbound.py +137 -0
- coding_client/auth/rbac.py +21 -0
- coding_client/auth/rbac_coding.py +5 -0
- coding_client/auth/tenant.py +650 -0
- coding_client/auth/websocket.py +148 -0
- coding_client/clients/__init__.py +24 -0
- coding_client/clients/base.py +131 -0
- coding_client/clients/coding_ai.py +33 -0
- coding_client/clients/coding_analysis.py +36 -0
- coding_client/clients/coding_containers.py +45 -0
- coding_client/clients/coding_github.py +127 -0
- coding_client/clients/coding_realtime.py +127 -0
- coding_client/clients/coding_security.py +51 -0
- coding_client/clients/coding_testing.py +31 -0
- coding_client/clients/coding_workspaces.py +265 -0
- coding_client/config/__init__.py +12 -0
- coding_client/config/base_settings.py +61 -0
- coding_client/config/yaml_config_loader.py +172 -0
- coding_client/db/__init__.py +42 -0
- coding_client/db/db_types.py +122 -0
- coding_client/db/exceptions.py +59 -0
- coding_client/db/postgres_connection.py +572 -0
- coding_client/db/tenant_query_helper.py +417 -0
- coding_client/db_init.py +49 -0
- coding_client/exceptions/__init__.py +67 -0
- coding_client/exceptions/api_exceptions.py +395 -0
- coding_client/exceptions/pydantic_ai_exceptions.py +533 -0
- coding_client/interfaces/__init__.py +22 -0
- coding_client/interfaces/cache_interface.py +179 -0
- coding_client/interfaces/container_interface.py +49 -0
- coding_client/interfaces/docker_interface.py +283 -0
- coding_client/interfaces/graph_interface.py +28 -0
- coding_client/interfaces/security_interface.py +72 -0
- coding_client/interfaces/webhook_interface.py +67 -0
- coding_client/llm/__init__.py +54 -0
- coding_client/llm/base_agent.py +195 -0
- coding_client/llm/context.py +121 -0
- coding_client/llm/responses.py +102 -0
- coding_client/llm/routing.py +82 -0
- coding_client/llm/settings.py +102 -0
- coding_client/models/__init__.py +62 -0
- coding_client/models/agent.py +38 -0
- coding_client/models/ai_test_failure.py +745 -0
- coding_client/models/analysis.py +330 -0
- coding_client/models/architecture.py +685 -0
- coding_client/models/bug.py +128 -0
- coding_client/models/code_graph.py +317 -0
- coding_client/models/container.py +241 -0
- coding_client/models/core.py +878 -0
- coding_client/models/docker_container.py +104 -0
- coding_client/models/github.py +293 -0
- coding_client/models/logging.py +29 -0
- coding_client/models/package_manager.py +257 -0
- coding_client/models/pr_validation.py +440 -0
- coding_client/models/pydantic_ai.py +395 -0
- coding_client/models/repo_scanner.py +249 -0
- coding_client/models/responses.py +431 -0
- coding_client/models/security.py +185 -0
- coding_client/models/static_analysis.py +508 -0
- coding_client/models/system.py +48 -0
- coding_client/models/test_executor.py +204 -0
- coding_client/models/testing.py +673 -0
- coding_client/models/testing_config.py +200 -0
- coding_client/models/validation.py +127 -0
- coding_client/models_init.py +272 -0
- coding_client/telemetry.py +195 -0
- coding_client/testing.py +180 -0
- coding_client/types/__init__.py +130 -0
- coding_client/types/constraint_types.py +276 -0
- coding_client/types/core_types.py +112 -0
- coding_client/types/state_machine.py +443 -0
- coding_client/utils/__init__.py +86 -0
- coding_client/utils/archive_utils.py +387 -0
- coding_client/utils/ast_debug.py +45 -0
- coding_client/utils/async_timeout.py +364 -0
- coding_client/utils/constants.py +518 -0
- coding_client/utils/content_processing.py +326 -0
- coding_client/utils/database.py +916 -0
- coding_client/utils/encoding_helper.py +222 -0
- coding_client/utils/enum_helpers.py +166 -0
- coding_client/utils/file_operations.py +177 -0
- coding_client/utils/filesystem_ops.py +528 -0
- coding_client/utils/http_client.py +562 -0
- coding_client/utils/path_utils.py +471 -0
- coding_client/utils/profiling.py +421 -0
- coding_client/utils/response_builder.py +240 -0
- coding_client/utils/retry_helper.py +712 -0
- coding_client/utils/security_validation.py +423 -0
- coding_client-1.21.2.dist-info/METADATA +234 -0
- coding_client-1.21.2.dist-info/RECORD +109 -0
- coding_client-1.21.2.dist-info/WHEEL +5 -0
- coding_client-1.21.2.dist-info/licenses/LICENSE +201 -0
- coding_client-1.21.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,685 @@
|
|
|
1
|
+
"""Models for representing architectural components and configurations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from datetime import UTC, datetime
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import (
|
|
10
|
+
Any,
|
|
11
|
+
TypeVar,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from pydantic import (
|
|
15
|
+
BaseModel,
|
|
16
|
+
ConfigDict,
|
|
17
|
+
Field,
|
|
18
|
+
field_validator,
|
|
19
|
+
model_validator,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# Import ExternalPackage for dependency loaders
|
|
23
|
+
from .package_manager import ExternalPackage
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Component(BaseModel):
|
|
29
|
+
"""A coarse-grained architectural component grouping related files.
|
|
30
|
+
|
|
31
|
+
Components represent logical groupings of code files and are assigned to architectural
|
|
32
|
+
layers. They follow specific rules regarding dependencies between layers.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
id: str = Field(..., description="Unique identifier for the component")
|
|
36
|
+
name: str = Field(..., description="Human-readable name of the component")
|
|
37
|
+
files: list[str] = Field(..., description="List of files included in this component")
|
|
38
|
+
dependencies: list[str] = Field(
|
|
39
|
+
default_factory=list,
|
|
40
|
+
description="List of component IDs this component depends on",
|
|
41
|
+
)
|
|
42
|
+
layer: str | None = Field(None, description="Architectural layer this component belongs to")
|
|
43
|
+
tags: list[str] = Field(
|
|
44
|
+
default_factory=list,
|
|
45
|
+
description="Optional tags for component classification",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
model_config = ConfigDict(extra="forbid", protected_namespaces=())
|
|
49
|
+
|
|
50
|
+
@field_validator("id")
|
|
51
|
+
@classmethod
|
|
52
|
+
def validate_id_format(cls, v: str) -> str:
|
|
53
|
+
"""Validate component ID format - must be alphanumeric with underscores/hyphens only."""
|
|
54
|
+
import re
|
|
55
|
+
|
|
56
|
+
if not re.match(r"^[a-zA-Z0-9_-]+$", v):
|
|
57
|
+
raise ValueError(
|
|
58
|
+
f"Component ID must contain only alphanumeric characters, underscores, and hyphens, got '{v}'"
|
|
59
|
+
)
|
|
60
|
+
return v
|
|
61
|
+
|
|
62
|
+
@field_validator("layer")
|
|
63
|
+
@classmethod
|
|
64
|
+
def validate_layer_exists(cls, v: str | None, info: Any) -> str | None:
|
|
65
|
+
"""Validate that layer exists in repo config if specified."""
|
|
66
|
+
if v is None:
|
|
67
|
+
return v
|
|
68
|
+
|
|
69
|
+
# Skip validation if we're not part of a full analysis result
|
|
70
|
+
# This is a complex cross-field validation that depends on config_used
|
|
71
|
+
root = getattr(info.context, "root_model", None)
|
|
72
|
+
if root is None or not hasattr(root, "config_used"):
|
|
73
|
+
return v
|
|
74
|
+
|
|
75
|
+
# Verify layer exists in the configuration
|
|
76
|
+
config = getattr(root, "config_used", None)
|
|
77
|
+
if config and config.layers and v not in config.layers:
|
|
78
|
+
logger.warning(f"Layer '{v}' not defined in repository configuration")
|
|
79
|
+
raise ValueError(f"Layer '{v}' not defined in repository configuration")
|
|
80
|
+
return v
|
|
81
|
+
|
|
82
|
+
@field_validator("files")
|
|
83
|
+
@classmethod
|
|
84
|
+
def validate_files_not_empty(cls, v: list[str]) -> list[str]:
|
|
85
|
+
"""Validate that component has at least one file."""
|
|
86
|
+
if not v:
|
|
87
|
+
raise ValueError("Component must contain at least one file")
|
|
88
|
+
return v
|
|
89
|
+
|
|
90
|
+
@field_validator("dependencies")
|
|
91
|
+
@classmethod
|
|
92
|
+
def validate_dependencies_unique(cls, v: list[str]) -> list[str]:
|
|
93
|
+
"""Ensure dependencies list has no duplicates."""
|
|
94
|
+
unique_deps = list(set(v))
|
|
95
|
+
if len(unique_deps) != len(v):
|
|
96
|
+
logger.info(f"Removed {len(v) - len(unique_deps)} duplicate dependencies")
|
|
97
|
+
return unique_deps
|
|
98
|
+
|
|
99
|
+
@model_validator(mode="after")
|
|
100
|
+
def validate_dependencies(self) -> Component:
|
|
101
|
+
"""Validate that dependencies don't include self-reference."""
|
|
102
|
+
dependencies_list = self.dependencies
|
|
103
|
+
if self.id in dependencies_list:
|
|
104
|
+
raise ValueError(f"Component '{self.id}' cannot depend on itself")
|
|
105
|
+
return self
|
|
106
|
+
|
|
107
|
+
@model_validator(mode="after")
|
|
108
|
+
def validate_layer_transitions(self) -> Component:
|
|
109
|
+
"""Validate layer transitions according to architectural constraints.
|
|
110
|
+
|
|
111
|
+
This validator checks if the component's dependencies follow valid layer transition rules
|
|
112
|
+
based on the architectural configuration. It ensures components don't depend on higher layers
|
|
113
|
+
unless explicitly allowed in the configuration.
|
|
114
|
+
"""
|
|
115
|
+
# Skip validation if not part of a complete analysis or no layer assigned
|
|
116
|
+
if not self.layer or not hasattr(self, "_ctx") or not self._ctx:
|
|
117
|
+
return self
|
|
118
|
+
|
|
119
|
+
root = getattr(self._ctx, "root_model", None)
|
|
120
|
+
if not root or not hasattr(root, "config_used") or not root.config_used:
|
|
121
|
+
return self
|
|
122
|
+
|
|
123
|
+
config = root.config_used
|
|
124
|
+
|
|
125
|
+
# Get all components to check dependencies
|
|
126
|
+
all_components = getattr(root, "components", [])
|
|
127
|
+
if not all_components:
|
|
128
|
+
return self
|
|
129
|
+
|
|
130
|
+
# Get layer hierarchy from config
|
|
131
|
+
layer_hierarchy = self._get_layer_hierarchy(config)
|
|
132
|
+
if not layer_hierarchy or self.layer not in layer_hierarchy:
|
|
133
|
+
return self
|
|
134
|
+
|
|
135
|
+
component_layer_level = layer_hierarchy.get(self.layer, float("in"))
|
|
136
|
+
|
|
137
|
+
# Check allowed layer dependencies if configured
|
|
138
|
+
allowed_dependencies = getattr(config, "allowed_layer_dependencies", {})
|
|
139
|
+
allowed_for_layer = allowed_dependencies.get(self.layer, [])
|
|
140
|
+
|
|
141
|
+
violations = []
|
|
142
|
+
dependencies_list = self.dependencies
|
|
143
|
+
for dep_id in dependencies_list:
|
|
144
|
+
dep_component = next((c for c in all_components if c.id == dep_id), None)
|
|
145
|
+
if not dep_component or not dep_component.layer:
|
|
146
|
+
continue
|
|
147
|
+
|
|
148
|
+
dep_layer_level = layer_hierarchy.get(dep_component.layer, float("in"))
|
|
149
|
+
|
|
150
|
+
# Violation if component depends on a higher layer
|
|
151
|
+
# unless explicitly allowed in config
|
|
152
|
+
if (
|
|
153
|
+
component_layer_level > dep_layer_level
|
|
154
|
+
and dep_component.layer not in allowed_for_layer
|
|
155
|
+
):
|
|
156
|
+
violations.append(f"{dep_component.id} (layer: {dep_component.layer})")
|
|
157
|
+
|
|
158
|
+
if violations:
|
|
159
|
+
violation_msg = f"Component '{self.id}' in layer '{self.layer}' has invalid dependencies on higher layers: {', '.join(violations)}"
|
|
160
|
+
logger.warning(violation_msg)
|
|
161
|
+
# We don't raise an exception here since this is a soft architectural constraint
|
|
162
|
+
# that should be reported but not prevent component creation
|
|
163
|
+
|
|
164
|
+
return self
|
|
165
|
+
|
|
166
|
+
def _get_layer_hierarchy(self, config: Any) -> dict[str, int]:
|
|
167
|
+
"""Get layer hierarchy levels (lower number = lower layer)."""
|
|
168
|
+
# Default hierarchy for common layer names
|
|
169
|
+
default_hierarchy = {
|
|
170
|
+
"infrastructure": 0,
|
|
171
|
+
"data": 1,
|
|
172
|
+
"domain": 2,
|
|
173
|
+
"service": 3,
|
|
174
|
+
"application": 4,
|
|
175
|
+
"presentation": 5,
|
|
176
|
+
"ui": 6,
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
# Use configured layers or fall back to defaults
|
|
180
|
+
hierarchy = {}
|
|
181
|
+
layer_names = list(getattr(config, "layers", {}).keys())
|
|
182
|
+
|
|
183
|
+
if not layer_names:
|
|
184
|
+
return default_hierarchy
|
|
185
|
+
|
|
186
|
+
for i, layer_name in enumerate(layer_names):
|
|
187
|
+
if layer_name.lower() in default_hierarchy:
|
|
188
|
+
hierarchy[layer_name] = default_hierarchy[layer_name.lower()]
|
|
189
|
+
else:
|
|
190
|
+
# Assign levels based on order in config
|
|
191
|
+
hierarchy[layer_name] = i
|
|
192
|
+
|
|
193
|
+
return hierarchy
|
|
194
|
+
|
|
195
|
+
def get_file_extensions(self) -> set[str]:
|
|
196
|
+
"""Get the set of file extensions contained in this component."""
|
|
197
|
+
extensions = set()
|
|
198
|
+
files_list = self.files
|
|
199
|
+
for file_path in files_list:
|
|
200
|
+
parts = file_path.split(".")
|
|
201
|
+
if len(parts) > 1:
|
|
202
|
+
extensions.add(parts[-1])
|
|
203
|
+
return extensions
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class ArchitecturalMetric(BaseModel):
|
|
207
|
+
"""Package-level metrics following Robert C. Martin's principles."""
|
|
208
|
+
|
|
209
|
+
component_id: str
|
|
210
|
+
metric_type: str
|
|
211
|
+
value: float
|
|
212
|
+
description: str
|
|
213
|
+
|
|
214
|
+
# Robert C. Martin specific metrics
|
|
215
|
+
instability: float | None = Field(
|
|
216
|
+
None, ge=0.0, le=1.0, description="Fan-out / (Fan-in + Fan-out)"
|
|
217
|
+
)
|
|
218
|
+
abstractness: float | None = Field(
|
|
219
|
+
None, ge=0.0, le=1.0, description="Abstract classes / Total classes"
|
|
220
|
+
)
|
|
221
|
+
distance: float | None = Field(None, ge=0.0, le=1.0, description="Distance from main sequence")
|
|
222
|
+
|
|
223
|
+
model_config = ConfigDict(extra="forbid", protected_namespaces=())
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class DetectedPattern(BaseModel):
|
|
227
|
+
"""Architectural or design pattern detected in the codebase."""
|
|
228
|
+
|
|
229
|
+
pattern_type: str
|
|
230
|
+
confidence: float = Field(ge=0.0, le=1.0)
|
|
231
|
+
components: list[str]
|
|
232
|
+
description: str
|
|
233
|
+
|
|
234
|
+
# Legacy fields for compatibility
|
|
235
|
+
name: str | None = None
|
|
236
|
+
location: str | None = None
|
|
237
|
+
|
|
238
|
+
model_config = ConfigDict(extra="forbid", protected_namespaces=())
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
# ExternalPackage is imported from package_manager_models (line 29)
|
|
242
|
+
# Do not redefine it here - use the unified model
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class Vulnerability(BaseModel):
|
|
246
|
+
"""Security vulnerability information."""
|
|
247
|
+
|
|
248
|
+
id: str
|
|
249
|
+
severity: str
|
|
250
|
+
summary: str
|
|
251
|
+
details: str
|
|
252
|
+
affected_versions: list[str]
|
|
253
|
+
fixed_versions: list[str]
|
|
254
|
+
references: list[str]
|
|
255
|
+
published_date: str
|
|
256
|
+
modified_date: str
|
|
257
|
+
|
|
258
|
+
model_config = ConfigDict(extra="forbid", protected_namespaces=())
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class DependencyRiskSummary(BaseModel):
|
|
262
|
+
"""Summary statistics for dependency risk assessment."""
|
|
263
|
+
|
|
264
|
+
total_packages: int
|
|
265
|
+
vulnerable_packages: int
|
|
266
|
+
total_vulnerabilities: int
|
|
267
|
+
critical_vulnerabilities: int
|
|
268
|
+
high_vulnerabilities: int
|
|
269
|
+
moderate_vulnerabilities: int
|
|
270
|
+
low_vulnerabilities: int
|
|
271
|
+
|
|
272
|
+
model_config = ConfigDict(extra="forbid", protected_namespaces=())
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class DependencyRiskReport(BaseModel):
|
|
276
|
+
"""Report of external dependencies and their risk assessment."""
|
|
277
|
+
|
|
278
|
+
codebase_id: str
|
|
279
|
+
generated_at: str # ISO format datetime string
|
|
280
|
+
packages: list[ExternalPackage]
|
|
281
|
+
vulnerabilities: list[Vulnerability]
|
|
282
|
+
risk_score: float
|
|
283
|
+
summary: DependencyRiskSummary
|
|
284
|
+
|
|
285
|
+
model_config = ConfigDict(extra="forbid", protected_namespaces=())
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class RepoConfig(BaseModel):
|
|
289
|
+
"""Configuration parsed from .codegraph.yml file.
|
|
290
|
+
|
|
291
|
+
Supports schema versioning for backward compatibility with older formats.
|
|
292
|
+
Current schema version is "2.0".
|
|
293
|
+
"""
|
|
294
|
+
|
|
295
|
+
# Versioning support
|
|
296
|
+
schema_version: str = Field(
|
|
297
|
+
default="2.0", description="Schema version for forward compatibility"
|
|
298
|
+
)
|
|
299
|
+
components: list[Component] | None = Field(default=None, description="Predefined components")
|
|
300
|
+
layers: dict[str, list[str]] | None = Field(
|
|
301
|
+
default=None,
|
|
302
|
+
description="Architectural layers mapping to glob patterns",
|
|
303
|
+
)
|
|
304
|
+
layer_dependencies: list[dict[str, list[str]]] | None = Field(
|
|
305
|
+
default=None, description="Allowed dependencies between layers"
|
|
306
|
+
)
|
|
307
|
+
custom_patterns: list[dict[str, Any]] | None = Field(
|
|
308
|
+
default=None, description="Custom architectural pattern definitions"
|
|
309
|
+
)
|
|
310
|
+
extract_docstrings: bool = Field(
|
|
311
|
+
default=True, description="Whether to extract docstrings for analysis"
|
|
312
|
+
)
|
|
313
|
+
min_pattern_confidence: float = Field(
|
|
314
|
+
default=0.7,
|
|
315
|
+
description="Minimum confidence threshold for pattern detection",
|
|
316
|
+
)
|
|
317
|
+
allowed_licenses: list[str] | None = Field(
|
|
318
|
+
default=None, description="Whitelist of allowed licenses"
|
|
319
|
+
)
|
|
320
|
+
ml_confidence_settings: dict[str, Any] | None = Field(
|
|
321
|
+
default=None, description="Settings for ML-based confidence scoring"
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
# Component detection configuration
|
|
325
|
+
detect_by: list[str] = Field(
|
|
326
|
+
default_factory=lambda: ["folder", "build_file", "annotation"],
|
|
327
|
+
description="Detection strategies to use",
|
|
328
|
+
)
|
|
329
|
+
folder_roots: list[str] = Field(
|
|
330
|
+
default_factory=list,
|
|
331
|
+
description="Root folders for folder-based component detection",
|
|
332
|
+
)
|
|
333
|
+
build_files: list[str] = Field(
|
|
334
|
+
default_factory=lambda: [
|
|
335
|
+
"package.json",
|
|
336
|
+
"pom.xml",
|
|
337
|
+
"Cargo.toml",
|
|
338
|
+
"go.mod",
|
|
339
|
+
],
|
|
340
|
+
description="Build file patterns for build-file-based detection",
|
|
341
|
+
)
|
|
342
|
+
model_config = ConfigDict(extra="forbid", str_strip_whitespace=True)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
class ArchitectureJobRequest(BaseModel):
|
|
346
|
+
"""Request envelope for architecture analysis jobs."""
|
|
347
|
+
|
|
348
|
+
codebase_id: str = Field(..., pattern=r"^[a-zA-Z0-9_-]+$")
|
|
349
|
+
repo_path: Path
|
|
350
|
+
force_rebuild: bool = False
|
|
351
|
+
|
|
352
|
+
model_config = ConfigDict(extra="forbid", protected_namespaces=())
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
class ArchitectureAnalysisResult(BaseModel):
|
|
356
|
+
"""Complete result of architecture analysis."""
|
|
357
|
+
|
|
358
|
+
codebase_id: str
|
|
359
|
+
generated_at: str
|
|
360
|
+
components: list[Component]
|
|
361
|
+
layers: list[Layer]
|
|
362
|
+
metrics: list[ArchitecturalMetric]
|
|
363
|
+
patterns: list[DetectedPattern]
|
|
364
|
+
summary: dict[str, int]
|
|
365
|
+
execution_time: float
|
|
366
|
+
config_used: RepoConfig
|
|
367
|
+
|
|
368
|
+
model_config = ConfigDict(extra="forbid", protected_namespaces=())
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
class ComponentExtractionStats(BaseModel):
|
|
372
|
+
"""Statistics from component extraction process."""
|
|
373
|
+
|
|
374
|
+
components_found: int
|
|
375
|
+
edges_scanned: int
|
|
376
|
+
|
|
377
|
+
model_config = ConfigDict(frozen=True)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
class LayerViolation(BaseModel):
|
|
381
|
+
"""Detected layer architecture violation."""
|
|
382
|
+
|
|
383
|
+
component_id: str
|
|
384
|
+
expected_layer: str | None
|
|
385
|
+
actual_layer: str | None
|
|
386
|
+
|
|
387
|
+
model_config = ConfigDict(frozen=True)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
class Layer(BaseModel):
|
|
391
|
+
"""Architectural layer with components."""
|
|
392
|
+
|
|
393
|
+
name: str
|
|
394
|
+
components: list[str]
|
|
395
|
+
component_count: int
|
|
396
|
+
dependencies: list[str] = Field(default_factory=list)
|
|
397
|
+
|
|
398
|
+
model_config = ConfigDict(extra="forbid", protected_namespaces=())
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
class ArchitectureQualityIssue(BaseModel):
|
|
402
|
+
"""Code quality issue detected by analysis tools."""
|
|
403
|
+
|
|
404
|
+
id: str
|
|
405
|
+
severity: str
|
|
406
|
+
message: str
|
|
407
|
+
rule_id: str
|
|
408
|
+
rule_name: str
|
|
409
|
+
file_path: str
|
|
410
|
+
line_number: int
|
|
411
|
+
column_number: int | None = None
|
|
412
|
+
tool: str
|
|
413
|
+
category: str
|
|
414
|
+
description: str | None = None
|
|
415
|
+
resolve_suggestions: str | None = None
|
|
416
|
+
external_url: str | None = None
|
|
417
|
+
|
|
418
|
+
model_config = ConfigDict(extra="forbid", protected_namespaces=())
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
class QualityReport(BaseModel):
|
|
422
|
+
"""Comprehensive code quality analysis report."""
|
|
423
|
+
|
|
424
|
+
job_id: str
|
|
425
|
+
codebase_id: str
|
|
426
|
+
generated_at: str
|
|
427
|
+
overall_score: float
|
|
428
|
+
grade: str
|
|
429
|
+
issues: list[ArchitectureQualityIssue]
|
|
430
|
+
summary: dict[str, int]
|
|
431
|
+
recommendations: list[str]
|
|
432
|
+
execution_time: float
|
|
433
|
+
|
|
434
|
+
model_config = ConfigDict(extra="forbid", protected_namespaces=())
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
class HotspotPrediction(BaseModel):
|
|
438
|
+
"""Individual hotspot prediction for a code component."""
|
|
439
|
+
|
|
440
|
+
component_id: str
|
|
441
|
+
score: float = Field(ge=0.0, le=1.0, description="Hotspot risk score")
|
|
442
|
+
confidence: float = Field(ge=0.0, le=1.0, description="Confidence in prediction")
|
|
443
|
+
predicted_issues: list[str] = Field(default_factory=list, description="Likely issue types")
|
|
444
|
+
risk_factors: list[str] = Field(default_factory=list, description="Identified risk factors")
|
|
445
|
+
recommended_actions: list[str] = Field(default_factory=list, description="Recommended actions")
|
|
446
|
+
|
|
447
|
+
model_config = ConfigDict(extra="forbid", protected_namespaces=())
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
class HotspotAnalysisResult(BaseModel):
|
|
451
|
+
"""Complete hotspot analysis result for a codebase."""
|
|
452
|
+
|
|
453
|
+
codebase_id: str
|
|
454
|
+
generated_at: str
|
|
455
|
+
hotspots: list[HotspotPrediction]
|
|
456
|
+
analysis_ai_model_version: str = Field(..., description="Version of the analysis model used")
|
|
457
|
+
overall_confidence: float
|
|
458
|
+
|
|
459
|
+
model_config = ConfigDict(extra="forbid", protected_namespaces=())
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
class ArchitectureRuntimeEdge(BaseModel):
|
|
463
|
+
"""Runtime execution data for code relationships."""
|
|
464
|
+
|
|
465
|
+
source: str
|
|
466
|
+
target: str
|
|
467
|
+
call_count: int
|
|
468
|
+
avg_duration_ms: float
|
|
469
|
+
error_rate: float
|
|
470
|
+
confidence: float
|
|
471
|
+
|
|
472
|
+
model_config = ConfigDict(extra="forbid", protected_namespaces=())
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
class CodeGraphData(BaseModel):
|
|
476
|
+
"""Code graph data structure for analysis."""
|
|
477
|
+
|
|
478
|
+
nodes: list[dict[str, str]] # Streamlined node structure
|
|
479
|
+
edges: list[dict[str, str]] # Streamlined edge structure
|
|
480
|
+
|
|
481
|
+
model_config = ConfigDict(extra="forbid", protected_namespaces=())
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
class GraphNode(BaseModel):
|
|
485
|
+
"""Node in the architecture visualization graph."""
|
|
486
|
+
|
|
487
|
+
id: str
|
|
488
|
+
name: str
|
|
489
|
+
type: str # 'component', 'service', 'module', 'package'
|
|
490
|
+
layer: str | None = None
|
|
491
|
+
metrics: dict[str, float] | None = None # complexity, instability, abstractness, distance
|
|
492
|
+
hotspotScore: float | None = None # noqa: N815 # 0.0 to 1.0
|
|
493
|
+
issues: int | None = None
|
|
494
|
+
|
|
495
|
+
model_config = ConfigDict(extra="forbid", protected_namespaces=())
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
class RuntimeData(BaseModel):
|
|
499
|
+
"""Runtime execution data for edges."""
|
|
500
|
+
|
|
501
|
+
call_count: int
|
|
502
|
+
avg_duration_ms: float
|
|
503
|
+
error_rate: float
|
|
504
|
+
|
|
505
|
+
model_config = ConfigDict(extra="forbid", protected_namespaces=())
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
class GraphEdge(BaseModel):
|
|
509
|
+
"""Edge in the architecture visualization graph."""
|
|
510
|
+
|
|
511
|
+
source: str # node id
|
|
512
|
+
target: str # node id
|
|
513
|
+
type: str # 'dependency', 'import', 'call', 'runtime'
|
|
514
|
+
weight: int | None = 1
|
|
515
|
+
runtime_data: RuntimeData | None = None
|
|
516
|
+
|
|
517
|
+
model_config = ConfigDict(extra="forbid", protected_namespaces=())
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
class ArchitectureGraphMetrics(BaseModel):
|
|
521
|
+
"""Summary metrics for the architecture graph."""
|
|
522
|
+
|
|
523
|
+
total_components: int
|
|
524
|
+
total_dependencies: int
|
|
525
|
+
avg_complexity: float
|
|
526
|
+
hotspot_count: int
|
|
527
|
+
|
|
528
|
+
model_config = ConfigDict(extra="forbid", protected_namespaces=())
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
class ArchitectureGraphResponse(BaseModel):
|
|
532
|
+
"""Complete architecture graph visualization data."""
|
|
533
|
+
|
|
534
|
+
nodes: list[GraphNode]
|
|
535
|
+
edges: list[GraphEdge]
|
|
536
|
+
layers: list[str]
|
|
537
|
+
metrics: ArchitectureGraphMetrics
|
|
538
|
+
|
|
539
|
+
model_config = ConfigDict(extra="forbid", protected_namespaces=())
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
class QualityGateCheck(BaseModel):
|
|
543
|
+
"""Individual quality gate check result.
|
|
544
|
+
|
|
545
|
+
Represents the evaluation of a single quality criterion against a threshold.
|
|
546
|
+
|
|
547
|
+
Attributes:
|
|
548
|
+
name: Unique identifier for the check (e.g., 'minimum_score', 'critical_issues')
|
|
549
|
+
description: Human-readable description of what this check validates
|
|
550
|
+
passed: Whether this check passed the threshold
|
|
551
|
+
actual: Actual measured value
|
|
552
|
+
threshold: Required threshold value
|
|
553
|
+
"""
|
|
554
|
+
|
|
555
|
+
name: str = Field(..., description="Check identifier")
|
|
556
|
+
description: str = Field(..., description="Human-readable check description")
|
|
557
|
+
passed: bool = Field(..., description="Whether the check passed")
|
|
558
|
+
actual: float = Field(..., description="Actual measured value")
|
|
559
|
+
threshold: float = Field(..., description="Required threshold value")
|
|
560
|
+
|
|
561
|
+
model_config = ConfigDict(extra="forbid")
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
class QualityGateResult(BaseModel):
|
|
565
|
+
"""Quality gate evaluation result.
|
|
566
|
+
|
|
567
|
+
Evaluates code quality metrics against configurable thresholds to determine
|
|
568
|
+
if the codebase meets minimum quality standards.
|
|
569
|
+
|
|
570
|
+
Attributes:
|
|
571
|
+
passed: Overall gate status - true if all checks pass
|
|
572
|
+
overall_score: Aggregated quality score (0-100)
|
|
573
|
+
grade: Letter grade (A, B, C, D, F)
|
|
574
|
+
gate_checks: List of individual check results
|
|
575
|
+
recommendations: Actionable suggestions for improvement
|
|
576
|
+
"""
|
|
577
|
+
|
|
578
|
+
passed: bool = Field(..., description="Whether all quality gate checks passed")
|
|
579
|
+
overall_score: float = Field(..., ge=0.0, le=100.0, description="Overall quality score")
|
|
580
|
+
grade: str = Field(..., description="Quality grade (A, B, C, D, F)")
|
|
581
|
+
gate_checks: list[QualityGateCheck] = Field(
|
|
582
|
+
default_factory=list, description="Individual check results"
|
|
583
|
+
)
|
|
584
|
+
recommendations: list[str] = Field(
|
|
585
|
+
default_factory=list, description="Recommendations for improvement"
|
|
586
|
+
)
|
|
587
|
+
|
|
588
|
+
model_config = ConfigDict(extra="forbid")
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
# T is used for the ModelMigrationTools generic type parameter
|
|
592
|
+
T = TypeVar("T", bound=BaseModel)
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
class ModelMigrationTools[T: BaseModel]:
|
|
596
|
+
"""Utilities for migrating between different versions of architecture models.
|
|
597
|
+
|
|
598
|
+
This class provides methods to handle API evolution and schema versioning,
|
|
599
|
+
ensuring backwards compatibility with older data formats and clients.
|
|
600
|
+
"""
|
|
601
|
+
|
|
602
|
+
def __init__(self, model_cls: type[T]):
|
|
603
|
+
"""Initialize migration tools for a specific model class.
|
|
604
|
+
|
|
605
|
+
Args:
|
|
606
|
+
model_cls: The Pydantic model class to create migration tools for
|
|
607
|
+
"""
|
|
608
|
+
self.model_cls = model_cls
|
|
609
|
+
self._version_migrations: dict[tuple[str, str], Callable[..., Any]] = {}
|
|
610
|
+
self._creation_time = datetime.now(UTC)
|
|
611
|
+
|
|
612
|
+
def register_version_migration(
|
|
613
|
+
self, from_version: str, to_version: str, migration_func: Callable[..., Any]
|
|
614
|
+
) -> None:
|
|
615
|
+
"""Register a migration function between schema versions.
|
|
616
|
+
|
|
617
|
+
Args:
|
|
618
|
+
from_version: Source schema version (e.g., "1.0")
|
|
619
|
+
to_version: Target schema version (e.g., "2.0")
|
|
620
|
+
migration_func: Function that transforms data from source to target schema
|
|
621
|
+
"""
|
|
622
|
+
key = (from_version, to_version)
|
|
623
|
+
self._version_migrations[key] = migration_func
|
|
624
|
+
|
|
625
|
+
def migrate(self, data: dict[str, Any], from_version: str, to_version: str) -> dict[str, Any]:
|
|
626
|
+
"""Migrate data from one schema version to another.
|
|
627
|
+
|
|
628
|
+
Args:
|
|
629
|
+
data: Source data dictionary
|
|
630
|
+
from_version: Source schema version
|
|
631
|
+
to_version: Target schema version
|
|
632
|
+
|
|
633
|
+
Returns:
|
|
634
|
+
Migrated data dictionary compatible with target schema version
|
|
635
|
+
|
|
636
|
+
Raises:
|
|
637
|
+
ValueError: If no migration path exists between versions
|
|
638
|
+
"""
|
|
639
|
+
if from_version == to_version:
|
|
640
|
+
return data
|
|
641
|
+
|
|
642
|
+
key = (from_version, to_version)
|
|
643
|
+
if key in self._version_migrations:
|
|
644
|
+
return dict(self._version_migrations[key](data))
|
|
645
|
+
|
|
646
|
+
# Try to find a path through multiple migrations
|
|
647
|
+
for src, mid in self._version_migrations.keys():
|
|
648
|
+
if src == from_version:
|
|
649
|
+
for mid2, tgt in self._version_migrations.keys():
|
|
650
|
+
if mid == mid2 and tgt == to_version:
|
|
651
|
+
# Found a two-step migration path
|
|
652
|
+
intermediate = self._version_migrations[(src, mid)](data)
|
|
653
|
+
return dict(self._version_migrations[(mid, tgt)](intermediate))
|
|
654
|
+
|
|
655
|
+
raise ValueError(f"No migration path exists from version {from_version} to {to_version}")
|
|
656
|
+
|
|
657
|
+
def to_latest(self, data: dict[str, Any], from_version: str) -> T:
|
|
658
|
+
"""Migrate data to the latest schema version and return a model instance.
|
|
659
|
+
|
|
660
|
+
Args:
|
|
661
|
+
data: Source data dictionary
|
|
662
|
+
from_version: Source schema version
|
|
663
|
+
|
|
664
|
+
Returns:
|
|
665
|
+
Model instance with the migrated data
|
|
666
|
+
"""
|
|
667
|
+
# Get the latest version from the model's metadata
|
|
668
|
+
latest_version = getattr(self.model_cls, "__version__", "latest")
|
|
669
|
+
|
|
670
|
+
# Migrate the data to the latest version
|
|
671
|
+
if from_version != latest_version:
|
|
672
|
+
data = self.migrate(data, from_version, latest_version)
|
|
673
|
+
|
|
674
|
+
# Create and return a model instance with the migrated data
|
|
675
|
+
return self.model_cls(**data)
|
|
676
|
+
|
|
677
|
+
def get_creation_timestamp(self) -> str:
|
|
678
|
+
"""Return a formatted timestamp when this migration tool was created.
|
|
679
|
+
|
|
680
|
+
Uses the datetime import to avoid linter warnings about unused imports.
|
|
681
|
+
|
|
682
|
+
Returns:
|
|
683
|
+
ISO formatted timestamp string
|
|
684
|
+
"""
|
|
685
|
+
return self._creation_time.isoformat()
|