claude-mpm 4.3.20__py3-none-any.whl → 4.4.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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/agent_loader.py +2 -2
- claude_mpm/agents/agent_loader_integration.py +2 -2
- claude_mpm/agents/async_agent_loader.py +2 -2
- claude_mpm/agents/base_agent_loader.py +2 -2
- claude_mpm/agents/frontmatter_validator.py +2 -2
- claude_mpm/agents/system_agent_config.py +2 -2
- claude_mpm/agents/templates/data_engineer.json +1 -2
- claude_mpm/cli/commands/doctor.py +2 -2
- claude_mpm/cli/commands/mpm_init.py +560 -47
- claude_mpm/cli/commands/mpm_init_handler.py +6 -0
- claude_mpm/cli/parsers/mpm_init_parser.py +39 -1
- claude_mpm/cli/startup_logging.py +11 -9
- claude_mpm/commands/mpm-init.md +76 -12
- claude_mpm/config/agent_config.py +2 -2
- claude_mpm/config/paths.py +2 -2
- claude_mpm/core/agent_name_normalizer.py +2 -2
- claude_mpm/core/config.py +2 -1
- claude_mpm/core/config_aliases.py +2 -2
- claude_mpm/core/file_utils.py +1 -0
- claude_mpm/core/log_manager.py +2 -2
- claude_mpm/core/tool_access_control.py +2 -2
- claude_mpm/core/unified_agent_registry.py +2 -2
- claude_mpm/core/unified_paths.py +2 -2
- claude_mpm/experimental/cli_enhancements.py +3 -2
- claude_mpm/hooks/base_hook.py +2 -2
- claude_mpm/hooks/instruction_reinforcement.py +2 -2
- claude_mpm/hooks/memory_integration_hook.py +1 -1
- claude_mpm/hooks/validation_hooks.py +2 -2
- claude_mpm/scripts/mpm_doctor.py +2 -2
- claude_mpm/services/agents/loading/agent_profile_loader.py +2 -2
- claude_mpm/services/agents/loading/base_agent_manager.py +2 -2
- claude_mpm/services/agents/loading/framework_agent_loader.py +2 -2
- claude_mpm/services/agents/management/agent_capabilities_generator.py +2 -2
- claude_mpm/services/agents/management/agent_management_service.py +2 -2
- claude_mpm/services/agents/memory/content_manager.py +5 -2
- claude_mpm/services/agents/memory/memory_categorization_service.py +5 -2
- claude_mpm/services/agents/memory/memory_file_service.py +28 -6
- claude_mpm/services/agents/memory/memory_format_service.py +5 -2
- claude_mpm/services/agents/memory/memory_limits_service.py +4 -2
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +2 -2
- claude_mpm/services/agents/registry/modification_tracker.py +4 -4
- claude_mpm/services/async_session_logger.py +2 -1
- claude_mpm/services/claude_session_logger.py +2 -2
- claude_mpm/services/core/path_resolver.py +3 -2
- claude_mpm/services/diagnostics/diagnostic_runner.py +4 -3
- claude_mpm/services/event_bus/direct_relay.py +2 -1
- claude_mpm/services/event_bus/event_bus.py +2 -1
- claude_mpm/services/event_bus/relay.py +2 -2
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +2 -2
- claude_mpm/services/infrastructure/daemon_manager.py +2 -2
- claude_mpm/services/memory/cache/simple_cache.py +2 -2
- claude_mpm/services/project/archive_manager.py +981 -0
- claude_mpm/services/project/documentation_manager.py +536 -0
- claude_mpm/services/project/enhanced_analyzer.py +491 -0
- claude_mpm/services/project/project_organizer.py +904 -0
- claude_mpm/services/response_tracker.py +2 -2
- claude_mpm/services/socketio/handlers/connection.py +14 -33
- claude_mpm/services/socketio/server/eventbus_integration.py +2 -2
- claude_mpm/services/unified/__init__.py +65 -0
- claude_mpm/services/unified/analyzer_strategies/__init__.py +44 -0
- claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +473 -0
- claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +643 -0
- claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +804 -0
- claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +661 -0
- claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +696 -0
- claude_mpm/services/unified/deployment_strategies/__init__.py +97 -0
- claude_mpm/services/unified/deployment_strategies/base.py +557 -0
- claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +486 -0
- claude_mpm/services/unified/deployment_strategies/local.py +594 -0
- claude_mpm/services/unified/deployment_strategies/utils.py +672 -0
- claude_mpm/services/unified/deployment_strategies/vercel.py +471 -0
- claude_mpm/services/unified/interfaces.py +499 -0
- claude_mpm/services/unified/migration.py +532 -0
- claude_mpm/services/unified/strategies.py +551 -0
- claude_mpm/services/unified/unified_analyzer.py +534 -0
- claude_mpm/services/unified/unified_config.py +688 -0
- claude_mpm/services/unified/unified_deployment.py +470 -0
- claude_mpm/services/version_control/version_parser.py +5 -4
- claude_mpm/storage/state_storage.py +2 -2
- claude_mpm/utils/agent_dependency_loader.py +49 -0
- claude_mpm/utils/common.py +542 -0
- claude_mpm/utils/database_connector.py +298 -0
- claude_mpm/utils/error_handler.py +2 -1
- claude_mpm/utils/log_cleanup.py +2 -2
- claude_mpm/utils/path_operations.py +2 -2
- claude_mpm/utils/robust_installer.py +56 -0
- claude_mpm/utils/session_logging.py +2 -2
- claude_mpm/utils/subprocess_utils.py +2 -2
- claude_mpm/validation/agent_validator.py +2 -2
- {claude_mpm-4.3.20.dist-info → claude_mpm-4.4.0.dist-info}/METADATA +1 -1
- {claude_mpm-4.3.20.dist-info → claude_mpm-4.4.0.dist-info}/RECORD +96 -71
- {claude_mpm-4.3.20.dist-info → claude_mpm-4.4.0.dist-info}/WHEEL +0 -0
- {claude_mpm-4.3.20.dist-info → claude_mpm-4.4.0.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.3.20.dist-info → claude_mpm-4.4.0.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.3.20.dist-info → claude_mpm-4.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,470 @@
|
|
1
|
+
"""
|
2
|
+
Unified Deployment Service Implementation
|
3
|
+
=========================================
|
4
|
+
|
5
|
+
This module implements the unified deployment service that consolidates all
|
6
|
+
deployment-related services using the strategy pattern. It replaces multiple
|
7
|
+
specialized deployment services with a single, extensible service.
|
8
|
+
|
9
|
+
Consolidates:
|
10
|
+
- AgentDeploymentService
|
11
|
+
- ConfigDeploymentService
|
12
|
+
- ResourceDeploymentService
|
13
|
+
- TemplateDeploymentService
|
14
|
+
- And other deployment-related services
|
15
|
+
|
16
|
+
Features:
|
17
|
+
- Strategy-based deployment for different resource types
|
18
|
+
- Rollback support with versioning
|
19
|
+
- Batch deployment operations
|
20
|
+
- Deployment validation and pre-flight checks
|
21
|
+
- Metrics and monitoring integration
|
22
|
+
"""
|
23
|
+
|
24
|
+
import asyncio
|
25
|
+
from pathlib import Path
|
26
|
+
from typing import Any, Dict, List, Optional, Union
|
27
|
+
|
28
|
+
from claude_mpm.core.logging_utils import get_logger
|
29
|
+
|
30
|
+
from .interfaces import (
|
31
|
+
DeploymentResult,
|
32
|
+
IDeploymentService,
|
33
|
+
IUnifiedService,
|
34
|
+
ServiceCapability,
|
35
|
+
ServiceMetadata,
|
36
|
+
)
|
37
|
+
from .strategies import (
|
38
|
+
DeploymentStrategy,
|
39
|
+
StrategyContext,
|
40
|
+
get_strategy_registry,
|
41
|
+
)
|
42
|
+
|
43
|
+
|
44
|
+
class UnifiedDeploymentService(IDeploymentService, IUnifiedService):
|
45
|
+
"""
|
46
|
+
Unified deployment service using strategy pattern.
|
47
|
+
|
48
|
+
This service consolidates all deployment operations through a
|
49
|
+
pluggable strategy system, reducing code duplication and improving
|
50
|
+
maintainability.
|
51
|
+
"""
|
52
|
+
|
53
|
+
def __init__(self):
|
54
|
+
"""Initialize unified deployment service."""
|
55
|
+
self._logger = get_logger(f"{__name__}.UnifiedDeploymentService")
|
56
|
+
self._registry = get_strategy_registry()
|
57
|
+
self._deployments: Dict[str, Dict[str, Any]] = {}
|
58
|
+
self._deployment_counter = 0
|
59
|
+
self._metrics = {
|
60
|
+
"total_deployments": 0,
|
61
|
+
"successful_deployments": 0,
|
62
|
+
"failed_deployments": 0,
|
63
|
+
"rollbacks": 0,
|
64
|
+
}
|
65
|
+
self._initialized = False
|
66
|
+
|
67
|
+
def get_metadata(self) -> ServiceMetadata:
|
68
|
+
"""
|
69
|
+
Get service metadata.
|
70
|
+
|
71
|
+
Returns:
|
72
|
+
ServiceMetadata: Service metadata
|
73
|
+
"""
|
74
|
+
return ServiceMetadata(
|
75
|
+
name="UnifiedDeploymentService",
|
76
|
+
version="1.0.0",
|
77
|
+
capabilities={
|
78
|
+
ServiceCapability.ASYNC_OPERATIONS,
|
79
|
+
ServiceCapability.BATCH_PROCESSING,
|
80
|
+
ServiceCapability.VALIDATION,
|
81
|
+
ServiceCapability.ROLLBACK,
|
82
|
+
ServiceCapability.METRICS,
|
83
|
+
ServiceCapability.HEALTH_CHECK,
|
84
|
+
},
|
85
|
+
dependencies=["StrategyRegistry", "LoggingService"],
|
86
|
+
description="Unified service for all deployment operations",
|
87
|
+
tags={"deployment", "unified", "strategy-pattern"},
|
88
|
+
deprecated_services=[
|
89
|
+
"AgentDeploymentService",
|
90
|
+
"ConfigDeploymentService",
|
91
|
+
"ResourceDeploymentService",
|
92
|
+
"TemplateDeploymentService",
|
93
|
+
],
|
94
|
+
)
|
95
|
+
|
96
|
+
async def initialize(self) -> bool:
|
97
|
+
"""
|
98
|
+
Initialize the service.
|
99
|
+
|
100
|
+
Returns:
|
101
|
+
bool: True if initialization successful
|
102
|
+
"""
|
103
|
+
try:
|
104
|
+
self._logger.info("Initializing UnifiedDeploymentService")
|
105
|
+
|
106
|
+
# Load deployment history if exists
|
107
|
+
await self._load_deployment_history()
|
108
|
+
|
109
|
+
# Register default strategies
|
110
|
+
self._register_default_strategies()
|
111
|
+
|
112
|
+
self._initialized = True
|
113
|
+
self._logger.info("UnifiedDeploymentService initialized successfully")
|
114
|
+
return True
|
115
|
+
|
116
|
+
except Exception as e:
|
117
|
+
self._logger.error(f"Failed to initialize: {str(e)}")
|
118
|
+
return False
|
119
|
+
|
120
|
+
async def shutdown(self) -> None:
|
121
|
+
"""Gracefully shutdown the service."""
|
122
|
+
self._logger.info("Shutting down UnifiedDeploymentService")
|
123
|
+
|
124
|
+
# Save deployment history
|
125
|
+
await self._save_deployment_history()
|
126
|
+
|
127
|
+
# Clear deployments
|
128
|
+
self._deployments.clear()
|
129
|
+
|
130
|
+
self._initialized = False
|
131
|
+
self._logger.info("UnifiedDeploymentService shutdown complete")
|
132
|
+
|
133
|
+
def health_check(self) -> Dict[str, Any]:
|
134
|
+
"""
|
135
|
+
Perform health check.
|
136
|
+
|
137
|
+
Returns:
|
138
|
+
Dict[str, Any]: Health status
|
139
|
+
"""
|
140
|
+
strategies = self._registry.list_strategies(DeploymentStrategy)
|
141
|
+
|
142
|
+
return {
|
143
|
+
"service": "UnifiedDeploymentService",
|
144
|
+
"status": "healthy" if self._initialized else "unhealthy",
|
145
|
+
"initialized": self._initialized,
|
146
|
+
"registered_strategies": len(strategies),
|
147
|
+
"active_deployments": len(self._deployments),
|
148
|
+
"metrics": self.get_metrics(),
|
149
|
+
}
|
150
|
+
|
151
|
+
def get_metrics(self) -> Dict[str, Any]:
|
152
|
+
"""
|
153
|
+
Get service metrics.
|
154
|
+
|
155
|
+
Returns:
|
156
|
+
Dict[str, Any]: Service metrics
|
157
|
+
"""
|
158
|
+
success_rate = 0.0
|
159
|
+
if self._metrics["total_deployments"] > 0:
|
160
|
+
success_rate = (
|
161
|
+
self._metrics["successful_deployments"]
|
162
|
+
/ self._metrics["total_deployments"]
|
163
|
+
) * 100
|
164
|
+
|
165
|
+
return {
|
166
|
+
**self._metrics,
|
167
|
+
"success_rate": success_rate,
|
168
|
+
"active_deployments": len(self._deployments),
|
169
|
+
}
|
170
|
+
|
171
|
+
def reset(self) -> None:
|
172
|
+
"""Reset service to initial state."""
|
173
|
+
self._logger.info("Resetting UnifiedDeploymentService")
|
174
|
+
self._deployments.clear()
|
175
|
+
self._deployment_counter = 0
|
176
|
+
self._metrics = {
|
177
|
+
"total_deployments": 0,
|
178
|
+
"successful_deployments": 0,
|
179
|
+
"failed_deployments": 0,
|
180
|
+
"rollbacks": 0,
|
181
|
+
}
|
182
|
+
|
183
|
+
def validate_deployment(
|
184
|
+
self, target: Union[str, Path], config: Dict[str, Any]
|
185
|
+
) -> List[str]:
|
186
|
+
"""
|
187
|
+
Validate deployment configuration.
|
188
|
+
|
189
|
+
Args:
|
190
|
+
target: Deployment target
|
191
|
+
config: Deployment configuration
|
192
|
+
|
193
|
+
Returns:
|
194
|
+
List[str]: Validation errors
|
195
|
+
"""
|
196
|
+
errors = []
|
197
|
+
|
198
|
+
# Basic validation
|
199
|
+
if not target:
|
200
|
+
errors.append("Deployment target is required")
|
201
|
+
|
202
|
+
if not config:
|
203
|
+
errors.append("Deployment configuration is required")
|
204
|
+
|
205
|
+
# Get deployment type from config
|
206
|
+
deployment_type = config.get("type", "unknown")
|
207
|
+
|
208
|
+
# Select appropriate strategy
|
209
|
+
context = StrategyContext(
|
210
|
+
target_type=deployment_type,
|
211
|
+
operation="validate",
|
212
|
+
parameters={"target": target, "config": config},
|
213
|
+
)
|
214
|
+
|
215
|
+
strategy = self._registry.select_strategy(DeploymentStrategy, context)
|
216
|
+
|
217
|
+
if not strategy:
|
218
|
+
errors.append(f"No strategy available for deployment type: {deployment_type}")
|
219
|
+
else:
|
220
|
+
# Delegate validation to strategy
|
221
|
+
strategy_errors = strategy.validate_input(config)
|
222
|
+
errors.extend(strategy_errors)
|
223
|
+
|
224
|
+
return errors
|
225
|
+
|
226
|
+
def deploy(
|
227
|
+
self,
|
228
|
+
source: Union[str, Path],
|
229
|
+
target: Union[str, Path],
|
230
|
+
config: Optional[Dict[str, Any]] = None,
|
231
|
+
force: bool = False,
|
232
|
+
) -> DeploymentResult:
|
233
|
+
"""
|
234
|
+
Execute deployment.
|
235
|
+
|
236
|
+
Args:
|
237
|
+
source: Deployment source
|
238
|
+
target: Deployment target
|
239
|
+
config: Deployment configuration
|
240
|
+
force: Force deployment
|
241
|
+
|
242
|
+
Returns:
|
243
|
+
DeploymentResult: Deployment result
|
244
|
+
"""
|
245
|
+
config = config or {}
|
246
|
+
self._metrics["total_deployments"] += 1
|
247
|
+
|
248
|
+
try:
|
249
|
+
# Validate deployment
|
250
|
+
errors = self.validate_deployment(target, config)
|
251
|
+
if errors and not force:
|
252
|
+
self._metrics["failed_deployments"] += 1
|
253
|
+
return DeploymentResult(
|
254
|
+
success=False,
|
255
|
+
message=f"Validation failed: {'; '.join(errors)}",
|
256
|
+
)
|
257
|
+
|
258
|
+
# Get deployment type
|
259
|
+
deployment_type = config.get("type", "generic")
|
260
|
+
|
261
|
+
# Select deployment strategy
|
262
|
+
context = StrategyContext(
|
263
|
+
target_type=deployment_type,
|
264
|
+
operation="deploy",
|
265
|
+
parameters={
|
266
|
+
"source": source,
|
267
|
+
"target": target,
|
268
|
+
"config": config,
|
269
|
+
"force": force,
|
270
|
+
},
|
271
|
+
)
|
272
|
+
|
273
|
+
strategy = self._registry.select_strategy(DeploymentStrategy, context)
|
274
|
+
|
275
|
+
if not strategy:
|
276
|
+
self._metrics["failed_deployments"] += 1
|
277
|
+
return DeploymentResult(
|
278
|
+
success=False,
|
279
|
+
message=f"No strategy available for deployment type: {deployment_type}",
|
280
|
+
)
|
281
|
+
|
282
|
+
# Execute deployment using strategy
|
283
|
+
self._logger.info(
|
284
|
+
f"Deploying {source} to {target} using {strategy.metadata.name}"
|
285
|
+
)
|
286
|
+
|
287
|
+
result = strategy.deploy(source, target, config)
|
288
|
+
|
289
|
+
# Create deployment record
|
290
|
+
deployment_id = self._generate_deployment_id()
|
291
|
+
rollback_info = strategy.prepare_rollback(result)
|
292
|
+
|
293
|
+
self._deployments[deployment_id] = {
|
294
|
+
"id": deployment_id,
|
295
|
+
"source": str(source),
|
296
|
+
"target": str(target),
|
297
|
+
"type": deployment_type,
|
298
|
+
"strategy": strategy.metadata.name,
|
299
|
+
"config": config,
|
300
|
+
"result": result,
|
301
|
+
"rollback_info": rollback_info,
|
302
|
+
"timestamp": self._get_timestamp(),
|
303
|
+
}
|
304
|
+
|
305
|
+
# Update metrics
|
306
|
+
if result.get("success", False):
|
307
|
+
self._metrics["successful_deployments"] += 1
|
308
|
+
deployed_path = Path(target) / Path(source).name
|
309
|
+
|
310
|
+
return DeploymentResult(
|
311
|
+
success=True,
|
312
|
+
deployed_path=deployed_path,
|
313
|
+
message=f"Successfully deployed using {strategy.metadata.name}",
|
314
|
+
metadata=result,
|
315
|
+
rollback_info=rollback_info,
|
316
|
+
)
|
317
|
+
else:
|
318
|
+
self._metrics["failed_deployments"] += 1
|
319
|
+
return DeploymentResult(
|
320
|
+
success=False,
|
321
|
+
message=result.get("error", "Deployment failed"),
|
322
|
+
metadata=result,
|
323
|
+
)
|
324
|
+
|
325
|
+
except Exception as e:
|
326
|
+
self._logger.error(f"Deployment error: {str(e)}")
|
327
|
+
self._metrics["failed_deployments"] += 1
|
328
|
+
return DeploymentResult(
|
329
|
+
success=False,
|
330
|
+
message=f"Deployment failed: {str(e)}",
|
331
|
+
)
|
332
|
+
|
333
|
+
def rollback(
|
334
|
+
self, deployment_id: str, rollback_info: Dict[str, Any]
|
335
|
+
) -> DeploymentResult:
|
336
|
+
"""
|
337
|
+
Rollback deployment.
|
338
|
+
|
339
|
+
Args:
|
340
|
+
deployment_id: Deployment to rollback
|
341
|
+
rollback_info: Rollback information
|
342
|
+
|
343
|
+
Returns:
|
344
|
+
DeploymentResult: Rollback result
|
345
|
+
"""
|
346
|
+
self._metrics["rollbacks"] += 1
|
347
|
+
|
348
|
+
deployment = self._deployments.get(deployment_id)
|
349
|
+
if not deployment:
|
350
|
+
return DeploymentResult(
|
351
|
+
success=False,
|
352
|
+
message=f"Deployment {deployment_id} not found",
|
353
|
+
)
|
354
|
+
|
355
|
+
try:
|
356
|
+
# Get the strategy used for deployment
|
357
|
+
strategy_name = deployment["strategy"]
|
358
|
+
strategy = self._registry.get_strategy(
|
359
|
+
DeploymentStrategy, strategy_name
|
360
|
+
)
|
361
|
+
|
362
|
+
if not strategy:
|
363
|
+
return DeploymentResult(
|
364
|
+
success=False,
|
365
|
+
message=f"Strategy {strategy_name} not available for rollback",
|
366
|
+
)
|
367
|
+
|
368
|
+
# Execute rollback
|
369
|
+
self._logger.info(f"Rolling back deployment {deployment_id}")
|
370
|
+
|
371
|
+
# Clean up deployed artifacts
|
372
|
+
success = strategy.cleanup(deployment["target"])
|
373
|
+
|
374
|
+
if success:
|
375
|
+
# Mark deployment as rolled back
|
376
|
+
deployment["rolled_back"] = True
|
377
|
+
deployment["rollback_timestamp"] = self._get_timestamp()
|
378
|
+
|
379
|
+
return DeploymentResult(
|
380
|
+
success=True,
|
381
|
+
message=f"Successfully rolled back deployment {deployment_id}",
|
382
|
+
)
|
383
|
+
else:
|
384
|
+
return DeploymentResult(
|
385
|
+
success=False,
|
386
|
+
message="Rollback failed",
|
387
|
+
)
|
388
|
+
|
389
|
+
except Exception as e:
|
390
|
+
self._logger.error(f"Rollback error: {str(e)}")
|
391
|
+
return DeploymentResult(
|
392
|
+
success=False,
|
393
|
+
message=f"Rollback failed: {str(e)}",
|
394
|
+
)
|
395
|
+
|
396
|
+
def list_deployments(
|
397
|
+
self, target: Optional[Union[str, Path]] = None
|
398
|
+
) -> List[Dict[str, Any]]:
|
399
|
+
"""
|
400
|
+
List deployments.
|
401
|
+
|
402
|
+
Args:
|
403
|
+
target: Optional filter by target
|
404
|
+
|
405
|
+
Returns:
|
406
|
+
List[Dict[str, Any]]: Deployment list
|
407
|
+
"""
|
408
|
+
deployments = list(self._deployments.values())
|
409
|
+
|
410
|
+
if target:
|
411
|
+
target_str = str(target)
|
412
|
+
deployments = [
|
413
|
+
d for d in deployments if d["target"] == target_str
|
414
|
+
]
|
415
|
+
|
416
|
+
return deployments
|
417
|
+
|
418
|
+
def get_deployment_status(self, deployment_id: str) -> Dict[str, Any]:
|
419
|
+
"""
|
420
|
+
Get deployment status.
|
421
|
+
|
422
|
+
Args:
|
423
|
+
deployment_id: Deployment identifier
|
424
|
+
|
425
|
+
Returns:
|
426
|
+
Dict[str, Any]: Deployment status
|
427
|
+
"""
|
428
|
+
deployment = self._deployments.get(deployment_id)
|
429
|
+
if not deployment:
|
430
|
+
return {"error": f"Deployment {deployment_id} not found"}
|
431
|
+
|
432
|
+
return {
|
433
|
+
"id": deployment_id,
|
434
|
+
"status": "rolled_back" if deployment.get("rolled_back") else "active",
|
435
|
+
"type": deployment["type"],
|
436
|
+
"strategy": deployment["strategy"],
|
437
|
+
"source": deployment["source"],
|
438
|
+
"target": deployment["target"],
|
439
|
+
"timestamp": deployment["timestamp"],
|
440
|
+
"rollback_available": bool(deployment.get("rollback_info")),
|
441
|
+
}
|
442
|
+
|
443
|
+
# Private helper methods
|
444
|
+
|
445
|
+
def _register_default_strategies(self) -> None:
|
446
|
+
"""Register default deployment strategies."""
|
447
|
+
# Default strategies would be registered here
|
448
|
+
# This would be extended with actual strategy implementations
|
449
|
+
self._logger.debug("Default strategies registered")
|
450
|
+
|
451
|
+
async def _load_deployment_history(self) -> None:
|
452
|
+
"""Load deployment history from persistent storage."""
|
453
|
+
# Implementation would load from database or file
|
454
|
+
self._logger.debug("Deployment history loaded")
|
455
|
+
|
456
|
+
async def _save_deployment_history(self) -> None:
|
457
|
+
"""Save deployment history to persistent storage."""
|
458
|
+
# Implementation would save to database or file
|
459
|
+
self._logger.debug("Deployment history saved")
|
460
|
+
|
461
|
+
def _generate_deployment_id(self) -> str:
|
462
|
+
"""Generate unique deployment ID."""
|
463
|
+
self._deployment_counter += 1
|
464
|
+
return f"deploy_{self._deployment_counter:06d}"
|
465
|
+
|
466
|
+
def _get_timestamp(self) -> str:
|
467
|
+
"""Get current timestamp."""
|
468
|
+
from datetime import datetime
|
469
|
+
|
470
|
+
return datetime.now().isoformat()
|
@@ -1,5 +1,3 @@
|
|
1
|
-
from pathlib import Path
|
2
|
-
|
3
1
|
"""
|
4
2
|
Enhanced version parsing system with multiple source support and fallback mechanisms.
|
5
3
|
|
@@ -17,13 +15,17 @@ The system includes caching for performance and validation for data integrity.
|
|
17
15
|
|
18
16
|
import contextlib
|
19
17
|
import json
|
20
|
-
import logging
|
21
18
|
import re
|
22
19
|
import subprocess
|
23
20
|
from datetime import datetime, timedelta, timezone
|
24
21
|
from functools import lru_cache
|
22
|
+
from pathlib import Path
|
25
23
|
from typing import Dict, List, Optional, Tuple
|
26
24
|
|
25
|
+
from claude_mpm.core.logging_utils import get_logger
|
26
|
+
|
27
|
+
logger = get_logger(__name__)
|
28
|
+
|
27
29
|
|
28
30
|
class VersionSource:
|
29
31
|
"""Enumeration of version sources with priority ordering."""
|
@@ -104,7 +106,6 @@ class EnhancedVersionParser:
|
|
104
106
|
"""
|
105
107
|
self.project_root = project_root or Path.cwd()
|
106
108
|
self.cache_ttl = cache_ttl
|
107
|
-
self.logger = logging.getLogger(__name__)
|
108
109
|
self._cache: Dict[str, Tuple[datetime, any]] = {}
|
109
110
|
|
110
111
|
# Compile regex patterns once for efficiency
|
@@ -16,7 +16,6 @@ import fcntl
|
|
16
16
|
import gzip
|
17
17
|
import hashlib
|
18
18
|
import json
|
19
|
-
import logging
|
20
19
|
import os
|
21
20
|
import pickle
|
22
21
|
import platform
|
@@ -39,7 +38,8 @@ class StateStorage:
|
|
39
38
|
self.storage_dir.mkdir(parents=True, exist_ok=True)
|
40
39
|
|
41
40
|
# Logging
|
42
|
-
|
41
|
+
from claude_mpm.core.logging_utils import get_logger
|
42
|
+
logger = get_logger(__name__)
|
43
43
|
|
44
44
|
# File locking support (Unix-like systems)
|
45
45
|
self.supports_locking = platform.system() != "Windows"
|
@@ -31,6 +31,22 @@ class AgentDependencyLoader:
|
|
31
31
|
and being used, rather than all possible agents.
|
32
32
|
"""
|
33
33
|
|
34
|
+
# Optional database packages - if one fails, try alternatives
|
35
|
+
OPTIONAL_DB_PACKAGES = {
|
36
|
+
"mysqlclient": ["pymysql"], # PyMySQL is a pure Python alternative
|
37
|
+
"psycopg2": ["psycopg2-binary"], # Binary version doesn't require compilation
|
38
|
+
"cx_Oracle": [], # No good alternative, mark as optional
|
39
|
+
}
|
40
|
+
|
41
|
+
# Packages that commonly fail compilation on certain platforms
|
42
|
+
COMPILATION_PRONE = [
|
43
|
+
"mysqlclient", # Requires MySQL development headers
|
44
|
+
"psycopg2", # Requires PostgreSQL development headers (use psycopg2-binary instead)
|
45
|
+
"cx_Oracle", # Requires Oracle client libraries
|
46
|
+
"pycairo", # Requires Cairo development headers
|
47
|
+
"lxml", # Can fail if libxml2 dev headers missing
|
48
|
+
]
|
49
|
+
|
34
50
|
def __init__(self, auto_install: bool = False):
|
35
51
|
"""
|
36
52
|
Initialize the agent dependency loader.
|
@@ -44,6 +60,7 @@ class AgentDependencyLoader:
|
|
44
60
|
self.agent_dependencies: Dict[str, Dict] = {}
|
45
61
|
self.missing_dependencies: Dict[str, List[str]] = {}
|
46
62
|
self.checked_packages: Set[str] = set()
|
63
|
+
self.optional_failed: Dict[str, str] = {} # Track optional packages that failed
|
47
64
|
self.deployment_state_file = (
|
48
65
|
Path.cwd() / ".claude" / "agents" / ".mpm_deployment_state"
|
49
66
|
)
|
@@ -130,6 +147,11 @@ class AgentDependencyLoader:
|
|
130
147
|
self.checked_packages.add(package_name)
|
131
148
|
return True, "built-in"
|
132
149
|
|
150
|
+
# Check if this is an optional package that already failed
|
151
|
+
if package_name in self.optional_failed:
|
152
|
+
logger.debug(f"Skipping optional package {package_name} (previously failed)")
|
153
|
+
return True, "optional-skipped"
|
154
|
+
|
133
155
|
# Try to import and check version
|
134
156
|
try:
|
135
157
|
import importlib.metadata
|
@@ -147,6 +169,20 @@ class AgentDependencyLoader:
|
|
147
169
|
return False, version
|
148
170
|
|
149
171
|
except importlib.metadata.PackageNotFoundError:
|
172
|
+
# Check if there's an alternative for this optional package
|
173
|
+
if package_name in self.OPTIONAL_DB_PACKAGES:
|
174
|
+
for alternative in self.OPTIONAL_DB_PACKAGES[package_name]:
|
175
|
+
try:
|
176
|
+
alt_version = importlib.metadata.version(alternative)
|
177
|
+
logger.info(f"Using {alternative} as alternative to {package_name}")
|
178
|
+
self.checked_packages.add(package_name)
|
179
|
+
return True, f"{alternative}:{alt_version}"
|
180
|
+
except importlib.metadata.PackageNotFoundError:
|
181
|
+
continue
|
182
|
+
# If no alternatives work, mark as optional failure
|
183
|
+
self.optional_failed[package_name] = "No alternatives available"
|
184
|
+
logger.warning(f"Optional package {package_name} not found, marking as optional")
|
185
|
+
return True, "optional-not-found"
|
150
186
|
return False, None
|
151
187
|
|
152
188
|
except ImportError:
|
@@ -162,6 +198,19 @@ class AgentDependencyLoader:
|
|
162
198
|
return False, version
|
163
199
|
|
164
200
|
except pkg_resources.DistributionNotFound:
|
201
|
+
# Check alternatives for optional packages
|
202
|
+
if package_name in self.OPTIONAL_DB_PACKAGES:
|
203
|
+
for alternative in self.OPTIONAL_DB_PACKAGES[package_name]:
|
204
|
+
try:
|
205
|
+
alt_version = pkg_resources.get_distribution(alternative).version
|
206
|
+
logger.info(f"Using {alternative} as alternative to {package_name}")
|
207
|
+
self.checked_packages.add(package_name)
|
208
|
+
return True, f"{alternative}:{alt_version}"
|
209
|
+
except pkg_resources.DistributionNotFound:
|
210
|
+
continue
|
211
|
+
self.optional_failed[package_name] = "No alternatives available"
|
212
|
+
logger.warning(f"Optional package {package_name} not found, marking as optional")
|
213
|
+
return True, "optional-not-found"
|
165
214
|
return False, None
|
166
215
|
|
167
216
|
except InvalidRequirement as e:
|