claude-mpm 4.4.0__py3-none-any.whl → 4.4.4__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/WORKFLOW.md +2 -14
- claude_mpm/agents/agent_loader.py +3 -2
- claude_mpm/agents/agent_loader_integration.py +2 -1
- claude_mpm/agents/async_agent_loader.py +2 -2
- claude_mpm/agents/base_agent_loader.py +2 -2
- claude_mpm/agents/frontmatter_validator.py +1 -0
- claude_mpm/agents/system_agent_config.py +2 -1
- claude_mpm/cli/commands/configure.py +2 -29
- claude_mpm/cli/commands/doctor.py +44 -5
- claude_mpm/cli/commands/mpm_init.py +117 -63
- claude_mpm/cli/parsers/configure_parser.py +6 -15
- claude_mpm/cli/startup_logging.py +1 -3
- claude_mpm/config/agent_config.py +1 -1
- claude_mpm/config/paths.py +2 -1
- claude_mpm/core/agent_name_normalizer.py +1 -0
- claude_mpm/core/config.py +2 -1
- claude_mpm/core/config_aliases.py +2 -1
- claude_mpm/core/file_utils.py +0 -1
- claude_mpm/core/framework/__init__.py +38 -0
- claude_mpm/core/framework/formatters/__init__.py +11 -0
- claude_mpm/core/framework/formatters/capability_generator.py +367 -0
- claude_mpm/core/framework/formatters/content_formatter.py +288 -0
- claude_mpm/core/framework/formatters/context_generator.py +184 -0
- claude_mpm/core/framework/loaders/__init__.py +13 -0
- claude_mpm/core/framework/loaders/agent_loader.py +206 -0
- claude_mpm/core/framework/loaders/file_loader.py +223 -0
- claude_mpm/core/framework/loaders/instruction_loader.py +161 -0
- claude_mpm/core/framework/loaders/packaged_loader.py +232 -0
- claude_mpm/core/framework/processors/__init__.py +11 -0
- claude_mpm/core/framework/processors/memory_processor.py +230 -0
- claude_mpm/core/framework/processors/metadata_processor.py +146 -0
- claude_mpm/core/framework/processors/template_processor.py +244 -0
- claude_mpm/core/framework_loader.py +298 -1795
- claude_mpm/core/log_manager.py +2 -1
- claude_mpm/core/tool_access_control.py +1 -0
- claude_mpm/core/unified_agent_registry.py +2 -1
- claude_mpm/core/unified_paths.py +1 -0
- claude_mpm/experimental/cli_enhancements.py +1 -0
- claude_mpm/hooks/__init__.py +9 -1
- claude_mpm/hooks/base_hook.py +1 -0
- claude_mpm/hooks/instruction_reinforcement.py +1 -0
- claude_mpm/hooks/kuzu_memory_hook.py +359 -0
- claude_mpm/hooks/validation_hooks.py +1 -1
- claude_mpm/scripts/mpm_doctor.py +1 -0
- claude_mpm/services/agents/loading/agent_profile_loader.py +1 -1
- claude_mpm/services/agents/loading/base_agent_manager.py +1 -1
- claude_mpm/services/agents/loading/framework_agent_loader.py +1 -1
- claude_mpm/services/agents/management/agent_capabilities_generator.py +1 -0
- claude_mpm/services/agents/management/agent_management_service.py +1 -1
- claude_mpm/services/agents/memory/memory_categorization_service.py +0 -1
- claude_mpm/services/agents/memory/memory_file_service.py +6 -2
- claude_mpm/services/agents/memory/memory_format_service.py +0 -1
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +1 -1
- claude_mpm/services/async_session_logger.py +1 -1
- claude_mpm/services/claude_session_logger.py +1 -0
- claude_mpm/services/core/path_resolver.py +2 -0
- claude_mpm/services/diagnostics/checks/__init__.py +2 -0
- claude_mpm/services/diagnostics/checks/installation_check.py +126 -25
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +399 -0
- claude_mpm/services/diagnostics/diagnostic_runner.py +4 -0
- claude_mpm/services/diagnostics/doctor_reporter.py +259 -32
- claude_mpm/services/event_bus/direct_relay.py +2 -1
- claude_mpm/services/event_bus/event_bus.py +1 -0
- claude_mpm/services/event_bus/relay.py +3 -2
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +1 -1
- claude_mpm/services/infrastructure/daemon_manager.py +1 -1
- claude_mpm/services/mcp_config_manager.py +67 -4
- claude_mpm/services/mcp_gateway/core/process_pool.py +320 -0
- claude_mpm/services/mcp_gateway/core/startup_verification.py +2 -2
- claude_mpm/services/mcp_gateway/main.py +3 -13
- claude_mpm/services/mcp_gateway/server/stdio_server.py +4 -10
- claude_mpm/services/mcp_gateway/tools/__init__.py +14 -2
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +38 -6
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +527 -0
- claude_mpm/services/memory/cache/simple_cache.py +1 -1
- claude_mpm/services/project/archive_manager.py +159 -96
- claude_mpm/services/project/documentation_manager.py +64 -45
- claude_mpm/services/project/enhanced_analyzer.py +132 -89
- claude_mpm/services/project/project_organizer.py +225 -131
- claude_mpm/services/response_tracker.py +1 -1
- claude_mpm/services/shared/__init__.py +2 -1
- claude_mpm/services/shared/service_factory.py +8 -5
- claude_mpm/services/socketio/server/eventbus_integration.py +1 -1
- claude_mpm/services/unified/__init__.py +1 -1
- claude_mpm/services/unified/analyzer_strategies/__init__.py +3 -3
- claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +97 -53
- claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +81 -40
- claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +277 -178
- claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +196 -112
- claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +83 -49
- claude_mpm/services/unified/config_strategies/__init__.py +175 -0
- claude_mpm/services/unified/config_strategies/config_schema.py +735 -0
- claude_mpm/services/unified/config_strategies/context_strategy.py +750 -0
- claude_mpm/services/unified/config_strategies/error_handling_strategy.py +1009 -0
- claude_mpm/services/unified/config_strategies/file_loader_strategy.py +879 -0
- claude_mpm/services/unified/config_strategies/unified_config_service.py +814 -0
- claude_mpm/services/unified/config_strategies/validation_strategy.py +1144 -0
- claude_mpm/services/unified/deployment_strategies/__init__.py +7 -7
- claude_mpm/services/unified/deployment_strategies/base.py +24 -28
- claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +168 -88
- claude_mpm/services/unified/deployment_strategies/local.py +49 -34
- claude_mpm/services/unified/deployment_strategies/utils.py +39 -43
- claude_mpm/services/unified/deployment_strategies/vercel.py +30 -24
- claude_mpm/services/unified/interfaces.py +0 -26
- claude_mpm/services/unified/migration.py +17 -40
- claude_mpm/services/unified/strategies.py +9 -26
- claude_mpm/services/unified/unified_analyzer.py +48 -44
- claude_mpm/services/unified/unified_config.py +21 -19
- claude_mpm/services/unified/unified_deployment.py +21 -26
- claude_mpm/storage/state_storage.py +1 -0
- claude_mpm/utils/agent_dependency_loader.py +18 -6
- claude_mpm/utils/common.py +14 -12
- claude_mpm/utils/database_connector.py +15 -12
- claude_mpm/utils/error_handler.py +1 -0
- claude_mpm/utils/log_cleanup.py +1 -0
- claude_mpm/utils/path_operations.py +1 -0
- claude_mpm/utils/session_logging.py +1 -1
- claude_mpm/utils/subprocess_utils.py +1 -0
- claude_mpm/validation/agent_validator.py +1 -1
- {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/METADATA +23 -17
- {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/RECORD +126 -105
- claude_mpm/cli/commands/configure_tui.py +0 -1927
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +0 -645
- claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +0 -602
- {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/WHEEL +0 -0
- {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/top_level.txt +0 -0
@@ -29,21 +29,21 @@ Design Principles:
|
|
29
29
|
5. Metrics and monitoring integration
|
30
30
|
"""
|
31
31
|
|
32
|
-
from .base import
|
33
|
-
from .local import LocalDeploymentStrategy
|
34
|
-
from .vercel import VercelDeploymentStrategy
|
32
|
+
from .base import DeploymentContext, DeploymentResult, DeploymentStrategy
|
35
33
|
from .cloud_strategies import (
|
36
|
-
RailwayDeploymentStrategy,
|
37
34
|
AWSDeploymentStrategy,
|
38
35
|
DockerDeploymentStrategy,
|
39
36
|
GitDeploymentStrategy,
|
37
|
+
RailwayDeploymentStrategy,
|
40
38
|
)
|
39
|
+
from .local import LocalDeploymentStrategy
|
41
40
|
from .utils import (
|
42
|
-
validate_deployment_config,
|
43
41
|
prepare_deployment_artifact,
|
44
|
-
verify_deployment_health,
|
45
42
|
rollback_deployment,
|
43
|
+
validate_deployment_config,
|
44
|
+
verify_deployment_health,
|
46
45
|
)
|
46
|
+
from .vercel import VercelDeploymentStrategy
|
47
47
|
|
48
48
|
__all__ = [
|
49
49
|
# Base classes
|
@@ -94,4 +94,4 @@ def get_deployment_strategy(deployment_type: str) -> type[DeploymentStrategy]:
|
|
94
94
|
f"Unsupported deployment type: {deployment_type}. "
|
95
95
|
f"Supported types: {', '.join(DEPLOYMENT_STRATEGIES.keys())}"
|
96
96
|
)
|
97
|
-
return strategy_class
|
97
|
+
return strategy_class
|
@@ -31,6 +31,7 @@ from claude_mpm.services.unified.strategies import (
|
|
31
31
|
|
32
32
|
class DeploymentStatus(Enum):
|
33
33
|
"""Deployment status enumeration."""
|
34
|
+
|
34
35
|
PENDING = "pending"
|
35
36
|
VALIDATING = "validating"
|
36
37
|
PREPARING = "preparing"
|
@@ -43,6 +44,7 @@ class DeploymentStatus(Enum):
|
|
43
44
|
|
44
45
|
class DeploymentType(Enum):
|
45
46
|
"""Types of deployments."""
|
47
|
+
|
46
48
|
AGENT = "agent"
|
47
49
|
CONFIG = "config"
|
48
50
|
RESOURCE = "resource"
|
@@ -62,6 +64,7 @@ class DeploymentContext:
|
|
62
64
|
- multi_source_deployment_service.py
|
63
65
|
- deployment_config_loader.py
|
64
66
|
"""
|
67
|
+
|
65
68
|
# Core deployment info
|
66
69
|
source: Union[str, Path]
|
67
70
|
target: Union[str, Path]
|
@@ -110,6 +113,7 @@ class DeploymentResult:
|
|
110
113
|
|
111
114
|
Consolidates result patterns from multiple deployment services.
|
112
115
|
"""
|
116
|
+
|
113
117
|
# Core result
|
114
118
|
success: bool
|
115
119
|
status: DeploymentStatus
|
@@ -156,7 +160,9 @@ class DeploymentResult:
|
|
156
160
|
"version": self.version,
|
157
161
|
"previous_version": self.previous_version,
|
158
162
|
"started_at": self.started_at.isoformat() if self.started_at else None,
|
159
|
-
"completed_at":
|
163
|
+
"completed_at": (
|
164
|
+
self.completed_at.isoformat() if self.completed_at else None
|
165
|
+
),
|
160
166
|
"duration_seconds": self.duration_seconds,
|
161
167
|
"artifacts": [str(a) for a in self.artifacts],
|
162
168
|
"logs": self.logs,
|
@@ -255,7 +261,9 @@ class DeploymentStrategy(BaseDeploymentStrategy):
|
|
255
261
|
try:
|
256
262
|
# Validation phase
|
257
263
|
result.status = DeploymentStatus.VALIDATING
|
258
|
-
self._logger.info(
|
264
|
+
self._logger.info(
|
265
|
+
f"Validating deployment: {context.source} -> {context.target}"
|
266
|
+
)
|
259
267
|
|
260
268
|
validation_errors = self.validate(context)
|
261
269
|
if validation_errors:
|
@@ -309,7 +317,7 @@ class DeploymentStrategy(BaseDeploymentStrategy):
|
|
309
317
|
result.message = "Dry run completed successfully"
|
310
318
|
|
311
319
|
except Exception as e:
|
312
|
-
self._logger.error(f"Deployment failed: {
|
320
|
+
self._logger.error(f"Deployment failed: {e!s}")
|
313
321
|
result.status = DeploymentStatus.FAILED
|
314
322
|
result.message = str(e)
|
315
323
|
result.errors.append(str(e))
|
@@ -322,8 +330,8 @@ class DeploymentStrategy(BaseDeploymentStrategy):
|
|
322
330
|
result.status = DeploymentStatus.ROLLED_BACK
|
323
331
|
result.message += " (rolled back)"
|
324
332
|
except Exception as rollback_error:
|
325
|
-
self._logger.error(f"Rollback failed: {
|
326
|
-
result.errors.append(f"Rollback failed: {
|
333
|
+
self._logger.error(f"Rollback failed: {rollback_error!s}")
|
334
|
+
result.errors.append(f"Rollback failed: {rollback_error!s}")
|
327
335
|
|
328
336
|
finally:
|
329
337
|
result.completed_at = datetime.now()
|
@@ -353,7 +361,6 @@ class DeploymentStrategy(BaseDeploymentStrategy):
|
|
353
361
|
Returns:
|
354
362
|
List of validation errors (empty if valid)
|
355
363
|
"""
|
356
|
-
pass
|
357
364
|
|
358
365
|
@abstractmethod
|
359
366
|
def prepare(self, context: DeploymentContext) -> List[Path]:
|
@@ -366,7 +373,6 @@ class DeploymentStrategy(BaseDeploymentStrategy):
|
|
366
373
|
Returns:
|
367
374
|
List of prepared artifact paths
|
368
375
|
"""
|
369
|
-
pass
|
370
376
|
|
371
377
|
@abstractmethod
|
372
378
|
def execute(
|
@@ -382,7 +388,6 @@ class DeploymentStrategy(BaseDeploymentStrategy):
|
|
382
388
|
Returns:
|
383
389
|
Deployment information including deployment_id, deployed_path, etc.
|
384
390
|
"""
|
385
|
-
pass
|
386
391
|
|
387
392
|
@abstractmethod
|
388
393
|
def verify(
|
@@ -398,12 +403,9 @@ class DeploymentStrategy(BaseDeploymentStrategy):
|
|
398
403
|
Returns:
|
399
404
|
True if deployment verified successfully
|
400
405
|
"""
|
401
|
-
pass
|
402
406
|
|
403
407
|
@abstractmethod
|
404
|
-
def rollback(
|
405
|
-
self, context: DeploymentContext, result: DeploymentResult
|
406
|
-
) -> bool:
|
408
|
+
def rollback(self, context: DeploymentContext, result: DeploymentResult) -> bool:
|
407
409
|
"""
|
408
410
|
Rollback failed deployment.
|
409
411
|
|
@@ -414,12 +416,9 @@ class DeploymentStrategy(BaseDeploymentStrategy):
|
|
414
416
|
Returns:
|
415
417
|
True if rollback successful
|
416
418
|
"""
|
417
|
-
pass
|
418
419
|
|
419
420
|
@abstractmethod
|
420
|
-
def get_health_status(
|
421
|
-
self, deployment_info: Dict[str, Any]
|
422
|
-
) -> Dict[str, Any]:
|
421
|
+
def get_health_status(self, deployment_info: Dict[str, Any]) -> Dict[str, Any]:
|
423
422
|
"""
|
424
423
|
Get health status of deployment.
|
425
424
|
|
@@ -429,7 +428,6 @@ class DeploymentStrategy(BaseDeploymentStrategy):
|
|
429
428
|
Returns:
|
430
429
|
Health status information
|
431
430
|
"""
|
432
|
-
pass
|
433
431
|
|
434
432
|
# Helper methods
|
435
433
|
|
@@ -458,14 +456,13 @@ class DeploymentStrategy(BaseDeploymentStrategy):
|
|
458
456
|
|
459
457
|
if "agent" in source_path.name.lower():
|
460
458
|
return DeploymentType.AGENT
|
461
|
-
|
459
|
+
if "config" in source_path.name.lower():
|
462
460
|
return DeploymentType.CONFIG
|
463
|
-
|
461
|
+
if "template" in source_path.name.lower():
|
464
462
|
return DeploymentType.TEMPLATE
|
465
|
-
|
463
|
+
if source_path.suffix in [".yaml", ".yml", ".json"]:
|
466
464
|
return DeploymentType.CONFIG
|
467
|
-
|
468
|
-
return DeploymentType.RESOURCE
|
465
|
+
return DeploymentType.RESOURCE
|
469
466
|
|
470
467
|
def _collect_metrics(
|
471
468
|
self, context: DeploymentContext, result: DeploymentResult
|
@@ -484,9 +481,7 @@ class DeploymentStrategy(BaseDeploymentStrategy):
|
|
484
481
|
"deployment_type": context.deployment_type.value,
|
485
482
|
"source_size": self._get_size(context.source) if context.source else 0,
|
486
483
|
"artifact_count": len(result.artifacts),
|
487
|
-
"artifact_total_size": sum(
|
488
|
-
self._get_size(a) for a in result.artifacts
|
489
|
-
),
|
484
|
+
"artifact_total_size": sum(self._get_size(a) for a in result.artifacts),
|
490
485
|
"duration_seconds": result.duration_seconds,
|
491
486
|
"error_count": len(result.errors),
|
492
487
|
"warning_count": len(result.warnings),
|
@@ -497,7 +492,7 @@ class DeploymentStrategy(BaseDeploymentStrategy):
|
|
497
492
|
path = Path(path)
|
498
493
|
if path.is_file():
|
499
494
|
return path.stat().st_size
|
500
|
-
|
495
|
+
if path.is_dir():
|
501
496
|
return sum(f.stat().st_size for f in path.rglob("*") if f.is_file())
|
502
497
|
return 0
|
503
498
|
|
@@ -550,8 +545,9 @@ class DeploymentStrategy(BaseDeploymentStrategy):
|
|
550
545
|
target_path.unlink()
|
551
546
|
elif target_path.is_dir():
|
552
547
|
import shutil
|
548
|
+
|
553
549
|
shutil.rmtree(target_path)
|
554
550
|
return True
|
555
551
|
except Exception as e:
|
556
|
-
self._logger.error(f"Cleanup failed: {
|
557
|
-
return False
|
552
|
+
self._logger.error(f"Cleanup failed: {e!s}")
|
553
|
+
return False
|
@@ -10,7 +10,7 @@ import json
|
|
10
10
|
import subprocess
|
11
11
|
from datetime import datetime
|
12
12
|
from pathlib import Path
|
13
|
-
from typing import Any, Dict, List
|
13
|
+
from typing import Any, Dict, List
|
14
14
|
|
15
15
|
from claude_mpm.core.logging_utils import get_logger
|
16
16
|
from claude_mpm.services.unified.strategies import StrategyMetadata, StrategyPriority
|
@@ -29,14 +29,16 @@ class RailwayDeploymentStrategy(DeploymentStrategy):
|
|
29
29
|
|
30
30
|
def __init__(self):
|
31
31
|
"""Initialize Railway strategy."""
|
32
|
-
super().__init__(
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
32
|
+
super().__init__(
|
33
|
+
StrategyMetadata(
|
34
|
+
name="RailwayDeploymentStrategy",
|
35
|
+
description="Deploy to Railway cloud platform",
|
36
|
+
supported_types=["application", "service", "*"],
|
37
|
+
supported_operations=["deploy", "rollback", "verify"],
|
38
|
+
priority=StrategyPriority.NORMAL,
|
39
|
+
tags={"railway", "cloud", "paas"},
|
40
|
+
)
|
41
|
+
)
|
40
42
|
self._logger = get_logger(f"{__name__}.RailwayDeploymentStrategy")
|
41
43
|
|
42
44
|
def validate(self, context: DeploymentContext) -> List[str]:
|
@@ -47,7 +49,9 @@ class RailwayDeploymentStrategy(DeploymentStrategy):
|
|
47
49
|
try:
|
48
50
|
subprocess.run(["railway", "--version"], capture_output=True, check=True)
|
49
51
|
except:
|
50
|
-
errors.append(
|
52
|
+
errors.append(
|
53
|
+
"Railway CLI not installed. Install with: npm i -g @railway/cli"
|
54
|
+
)
|
51
55
|
|
52
56
|
# Check authentication
|
53
57
|
try:
|
@@ -64,7 +68,9 @@ class RailwayDeploymentStrategy(DeploymentStrategy):
|
|
64
68
|
)
|
65
69
|
return [artifact_path]
|
66
70
|
|
67
|
-
def execute(
|
71
|
+
def execute(
|
72
|
+
self, context: DeploymentContext, artifacts: List[Path]
|
73
|
+
) -> Dict[str, Any]:
|
68
74
|
"""Execute Railway deployment."""
|
69
75
|
deploy_dir = artifacts[0] if artifacts else Path(context.source)
|
70
76
|
|
@@ -75,13 +81,16 @@ class RailwayDeploymentStrategy(DeploymentStrategy):
|
|
75
81
|
cmd.extend(["--environment", context.config["environment"]])
|
76
82
|
|
77
83
|
try:
|
78
|
-
result = subprocess.run(
|
84
|
+
result = subprocess.run(
|
85
|
+
cmd, cwd=deploy_dir, capture_output=True, text=True, check=True
|
86
|
+
)
|
79
87
|
|
80
88
|
# Parse deployment URL from output
|
81
89
|
deployment_url = None
|
82
90
|
for line in result.stdout.split("\n"):
|
83
91
|
if "https://" in line:
|
84
92
|
import re
|
93
|
+
|
85
94
|
match = re.search(r"https://[^\s]+", line)
|
86
95
|
if match:
|
87
96
|
deployment_url = match.group(0)
|
@@ -96,11 +105,16 @@ class RailwayDeploymentStrategy(DeploymentStrategy):
|
|
96
105
|
except subprocess.CalledProcessError as e:
|
97
106
|
raise Exception(f"Railway deployment failed: {e.stderr}")
|
98
107
|
|
99
|
-
def verify(
|
108
|
+
def verify(
|
109
|
+
self, context: DeploymentContext, deployment_info: Dict[str, Any]
|
110
|
+
) -> bool:
|
100
111
|
"""Verify Railway deployment."""
|
101
|
-
return
|
102
|
-
"railway", deployment_info, ["accessibility"]
|
103
|
-
|
112
|
+
return (
|
113
|
+
verify_deployment_health("railway", deployment_info, ["accessibility"])[
|
114
|
+
"status"
|
115
|
+
]
|
116
|
+
== "healthy"
|
117
|
+
)
|
104
118
|
|
105
119
|
def rollback(self, context: DeploymentContext, result: DeploymentResult) -> bool:
|
106
120
|
"""Railway doesn't support CLI rollback."""
|
@@ -117,14 +131,16 @@ class AWSDeploymentStrategy(DeploymentStrategy):
|
|
117
131
|
|
118
132
|
def __init__(self):
|
119
133
|
"""Initialize AWS strategy."""
|
120
|
-
super().__init__(
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
134
|
+
super().__init__(
|
135
|
+
StrategyMetadata(
|
136
|
+
name="AWSDeploymentStrategy",
|
137
|
+
description="Deploy to AWS services",
|
138
|
+
supported_types=["lambda", "ec2", "ecs", "application", "*"],
|
139
|
+
supported_operations=["deploy", "rollback", "verify"],
|
140
|
+
priority=StrategyPriority.NORMAL,
|
141
|
+
tags={"aws", "cloud", "serverless"},
|
142
|
+
)
|
143
|
+
)
|
128
144
|
self._logger = get_logger(f"{__name__}.AWSDeploymentStrategy")
|
129
145
|
|
130
146
|
def validate(self, context: DeploymentContext) -> List[str]:
|
@@ -139,7 +155,9 @@ class AWSDeploymentStrategy(DeploymentStrategy):
|
|
139
155
|
|
140
156
|
# Check credentials
|
141
157
|
try:
|
142
|
-
subprocess.run(
|
158
|
+
subprocess.run(
|
159
|
+
["aws", "sts", "get-caller-identity"], capture_output=True, check=True
|
160
|
+
)
|
143
161
|
except:
|
144
162
|
errors.append("AWS credentials not configured")
|
145
163
|
|
@@ -160,24 +178,26 @@ class AWSDeploymentStrategy(DeploymentStrategy):
|
|
160
178
|
context.source, "zip", context.config
|
161
179
|
)
|
162
180
|
return [artifact_path]
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
return [artifact_path]
|
181
|
+
artifact_path, _ = prepare_deployment_artifact(
|
182
|
+
context.source, "directory", context.config
|
183
|
+
)
|
184
|
+
return [artifact_path]
|
168
185
|
|
169
|
-
def execute(
|
186
|
+
def execute(
|
187
|
+
self, context: DeploymentContext, artifacts: List[Path]
|
188
|
+
) -> Dict[str, Any]:
|
170
189
|
"""Execute AWS deployment."""
|
171
190
|
service = context.config.get("service", "lambda")
|
172
191
|
|
173
192
|
if service == "lambda":
|
174
193
|
return self._deploy_lambda(context, artifacts[0])
|
175
|
-
|
194
|
+
if service == "s3":
|
176
195
|
return self._deploy_s3(context, artifacts[0])
|
177
|
-
|
178
|
-
raise NotImplementedError(f"AWS {service} deployment not implemented")
|
196
|
+
raise NotImplementedError(f"AWS {service} deployment not implemented")
|
179
197
|
|
180
|
-
def _deploy_lambda(
|
198
|
+
def _deploy_lambda(
|
199
|
+
self, context: DeploymentContext, artifact: Path
|
200
|
+
) -> Dict[str, Any]:
|
181
201
|
"""Deploy AWS Lambda function."""
|
182
202
|
function_name = context.config.get("function_name", artifact.stem)
|
183
203
|
|
@@ -185,23 +205,35 @@ class AWSDeploymentStrategy(DeploymentStrategy):
|
|
185
205
|
try:
|
186
206
|
subprocess.run(
|
187
207
|
["aws", "lambda", "get-function", "--function-name", function_name],
|
188
|
-
capture_output=True,
|
208
|
+
capture_output=True,
|
209
|
+
check=True,
|
189
210
|
)
|
190
211
|
# Update existing function
|
191
212
|
cmd = [
|
192
|
-
"aws",
|
193
|
-
"
|
194
|
-
"
|
213
|
+
"aws",
|
214
|
+
"lambda",
|
215
|
+
"update-function-code",
|
216
|
+
"--function-name",
|
217
|
+
function_name,
|
218
|
+
"--zip-file",
|
219
|
+
f"fileb://{artifact}",
|
195
220
|
]
|
196
221
|
except:
|
197
222
|
# Create new function
|
198
223
|
cmd = [
|
199
|
-
"aws",
|
200
|
-
"
|
201
|
-
"
|
202
|
-
"--
|
203
|
-
|
204
|
-
"--
|
224
|
+
"aws",
|
225
|
+
"lambda",
|
226
|
+
"create-function",
|
227
|
+
"--function-name",
|
228
|
+
function_name,
|
229
|
+
"--runtime",
|
230
|
+
context.config.get("runtime", "python3.9"),
|
231
|
+
"--role",
|
232
|
+
context.config.get("role"),
|
233
|
+
"--handler",
|
234
|
+
context.config.get("handler", "index.handler"),
|
235
|
+
"--zip-file",
|
236
|
+
f"fileb://{artifact}",
|
205
237
|
]
|
206
238
|
|
207
239
|
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
@@ -228,14 +260,21 @@ class AWSDeploymentStrategy(DeploymentStrategy):
|
|
228
260
|
"deployed_path": artifact,
|
229
261
|
}
|
230
262
|
|
231
|
-
def verify(
|
263
|
+
def verify(
|
264
|
+
self, context: DeploymentContext, deployment_info: Dict[str, Any]
|
265
|
+
) -> bool:
|
232
266
|
"""Verify AWS deployment."""
|
233
267
|
service = context.config.get("service", "lambda")
|
234
268
|
|
235
269
|
if service == "lambda" and "function_arn" in deployment_info:
|
236
270
|
try:
|
237
|
-
cmd = [
|
238
|
-
|
271
|
+
cmd = [
|
272
|
+
"aws",
|
273
|
+
"lambda",
|
274
|
+
"get-function",
|
275
|
+
"--function-name",
|
276
|
+
deployment_info["function_arn"],
|
277
|
+
]
|
239
278
|
subprocess.run(cmd, capture_output=True, check=True)
|
240
279
|
return True
|
241
280
|
except:
|
@@ -249,10 +288,15 @@ class AWSDeploymentStrategy(DeploymentStrategy):
|
|
249
288
|
if context.config.get("service") == "lambda" and result.previous_version:
|
250
289
|
function_name = context.config.get("function_name")
|
251
290
|
cmd = [
|
252
|
-
"aws",
|
253
|
-
"
|
254
|
-
"
|
255
|
-
"--function-
|
291
|
+
"aws",
|
292
|
+
"lambda",
|
293
|
+
"update-alias",
|
294
|
+
"--function-name",
|
295
|
+
function_name,
|
296
|
+
"--name",
|
297
|
+
"PROD",
|
298
|
+
"--function-version",
|
299
|
+
result.previous_version,
|
256
300
|
]
|
257
301
|
try:
|
258
302
|
subprocess.run(cmd, check=True)
|
@@ -271,14 +315,16 @@ class DockerDeploymentStrategy(DeploymentStrategy):
|
|
271
315
|
|
272
316
|
def __init__(self):
|
273
317
|
"""Initialize Docker strategy."""
|
274
|
-
super().__init__(
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
318
|
+
super().__init__(
|
319
|
+
StrategyMetadata(
|
320
|
+
name="DockerDeploymentStrategy",
|
321
|
+
description="Deploy using Docker containers",
|
322
|
+
supported_types=["container", "application", "service", "*"],
|
323
|
+
supported_operations=["deploy", "rollback", "verify", "stop"],
|
324
|
+
priority=StrategyPriority.HIGH,
|
325
|
+
tags={"docker", "container", "microservice"},
|
326
|
+
)
|
327
|
+
)
|
282
328
|
self._logger = get_logger(f"{__name__}.DockerDeploymentStrategy")
|
283
329
|
|
284
330
|
def validate(self, context: DeploymentContext) -> List[str]:
|
@@ -304,10 +350,14 @@ class DockerDeploymentStrategy(DeploymentStrategy):
|
|
304
350
|
"""Prepare Docker artifacts."""
|
305
351
|
return [Path(context.source)]
|
306
352
|
|
307
|
-
def execute(
|
353
|
+
def execute(
|
354
|
+
self, context: DeploymentContext, artifacts: List[Path]
|
355
|
+
) -> Dict[str, Any]:
|
308
356
|
"""Execute Docker deployment."""
|
309
357
|
source_dir = artifacts[0] if artifacts[0].is_dir() else artifacts[0].parent
|
310
|
-
image_name = context.config.get(
|
358
|
+
image_name = context.config.get(
|
359
|
+
"image_name", f"app_{datetime.now().timestamp()}"
|
360
|
+
)
|
311
361
|
container_name = context.config.get("container_name", image_name)
|
312
362
|
|
313
363
|
# Build image
|
@@ -315,8 +365,12 @@ class DockerDeploymentStrategy(DeploymentStrategy):
|
|
315
365
|
subprocess.run(build_cmd, check=True)
|
316
366
|
|
317
367
|
# Stop existing container if exists
|
318
|
-
subprocess.run(
|
319
|
-
|
368
|
+
subprocess.run(
|
369
|
+
["docker", "stop", container_name], capture_output=True, check=False
|
370
|
+
)
|
371
|
+
subprocess.run(
|
372
|
+
["docker", "rm", container_name], capture_output=True, check=False
|
373
|
+
)
|
320
374
|
|
321
375
|
# Run container
|
322
376
|
run_cmd = ["docker", "run", "-d", "--name", container_name]
|
@@ -344,7 +398,9 @@ class DockerDeploymentStrategy(DeploymentStrategy):
|
|
344
398
|
"deployed_path": source_dir,
|
345
399
|
}
|
346
400
|
|
347
|
-
def verify(
|
401
|
+
def verify(
|
402
|
+
self, context: DeploymentContext, deployment_info: Dict[str, Any]
|
403
|
+
) -> bool:
|
348
404
|
"""Verify Docker deployment."""
|
349
405
|
return check_docker_container(deployment_info.get("container_id"))
|
350
406
|
|
@@ -369,14 +425,16 @@ class GitDeploymentStrategy(DeploymentStrategy):
|
|
369
425
|
|
370
426
|
def __init__(self):
|
371
427
|
"""Initialize Git strategy."""
|
372
|
-
super().__init__(
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
428
|
+
super().__init__(
|
429
|
+
StrategyMetadata(
|
430
|
+
name="GitDeploymentStrategy",
|
431
|
+
description="Deploy using Git repositories",
|
432
|
+
supported_types=["repository", "code", "*"],
|
433
|
+
supported_operations=["deploy", "rollback", "verify"],
|
434
|
+
priority=StrategyPriority.NORMAL,
|
435
|
+
tags={"git", "github", "gitlab", "version-control"},
|
436
|
+
)
|
437
|
+
)
|
380
438
|
self._logger = get_logger(f"{__name__}.GitDeploymentStrategy")
|
381
439
|
|
382
440
|
def validate(self, context: DeploymentContext) -> List[str]:
|
@@ -399,7 +457,9 @@ class GitDeploymentStrategy(DeploymentStrategy):
|
|
399
457
|
"""Prepare Git artifacts."""
|
400
458
|
return [Path(context.source)]
|
401
459
|
|
402
|
-
def execute(
|
460
|
+
def execute(
|
461
|
+
self, context: DeploymentContext, artifacts: List[Path]
|
462
|
+
) -> Dict[str, Any]:
|
403
463
|
"""Execute Git deployment."""
|
404
464
|
source_dir = artifacts[0] if artifacts[0].is_dir() else artifacts[0].parent
|
405
465
|
remote_url = context.config.get("remote_url")
|
@@ -412,7 +472,9 @@ class GitDeploymentStrategy(DeploymentStrategy):
|
|
412
472
|
# Add remote
|
413
473
|
subprocess.run(
|
414
474
|
["git", "remote", "add", "deploy", remote_url],
|
415
|
-
cwd=source_dir,
|
475
|
+
cwd=source_dir,
|
476
|
+
capture_output=True,
|
477
|
+
check=False,
|
416
478
|
)
|
417
479
|
|
418
480
|
# Add all files
|
@@ -422,19 +484,23 @@ class GitDeploymentStrategy(DeploymentStrategy):
|
|
422
484
|
commit_msg = context.config.get("commit_message", "Deploy via Claude MPM")
|
423
485
|
subprocess.run(
|
424
486
|
["git", "commit", "-m", commit_msg],
|
425
|
-
cwd=source_dir,
|
487
|
+
cwd=source_dir,
|
488
|
+
capture_output=True,
|
489
|
+
check=False,
|
426
490
|
)
|
427
491
|
|
428
492
|
# Push
|
429
493
|
subprocess.run(
|
430
|
-
["git", "push", "-u", "deploy", branch],
|
431
|
-
cwd=source_dir, check=True
|
494
|
+
["git", "push", "-u", "deploy", branch], cwd=source_dir, check=True
|
432
495
|
)
|
433
496
|
|
434
497
|
# Get commit hash
|
435
498
|
result = subprocess.run(
|
436
499
|
["git", "rev-parse", "HEAD"],
|
437
|
-
cwd=source_dir,
|
500
|
+
cwd=source_dir,
|
501
|
+
capture_output=True,
|
502
|
+
text=True,
|
503
|
+
check=True,
|
438
504
|
)
|
439
505
|
commit_hash = result.stdout.strip()
|
440
506
|
|
@@ -446,14 +512,21 @@ class GitDeploymentStrategy(DeploymentStrategy):
|
|
446
512
|
"deployed_path": source_dir,
|
447
513
|
}
|
448
514
|
|
449
|
-
def verify(
|
515
|
+
def verify(
|
516
|
+
self, context: DeploymentContext, deployment_info: Dict[str, Any]
|
517
|
+
) -> bool:
|
450
518
|
"""Verify Git deployment."""
|
451
519
|
# Check if commit exists on remote
|
452
520
|
try:
|
453
521
|
subprocess.run(
|
454
|
-
[
|
455
|
-
|
456
|
-
|
522
|
+
[
|
523
|
+
"git",
|
524
|
+
"ls-remote",
|
525
|
+
deployment_info.get("remote_url"),
|
526
|
+
deployment_info.get("commit_hash"),
|
527
|
+
],
|
528
|
+
capture_output=True,
|
529
|
+
check=True,
|
457
530
|
)
|
458
531
|
return True
|
459
532
|
except:
|
@@ -465,12 +538,19 @@ class GitDeploymentStrategy(DeploymentStrategy):
|
|
465
538
|
try:
|
466
539
|
subprocess.run(
|
467
540
|
["git", "checkout", result.previous_version],
|
468
|
-
cwd=result.deployed_path,
|
541
|
+
cwd=result.deployed_path,
|
542
|
+
check=True,
|
469
543
|
)
|
470
544
|
subprocess.run(
|
471
|
-
[
|
472
|
-
|
473
|
-
|
545
|
+
[
|
546
|
+
"git",
|
547
|
+
"push",
|
548
|
+
"--force",
|
549
|
+
"deploy",
|
550
|
+
f"{result.previous_version}:{context.config.get('branch', 'main')}",
|
551
|
+
],
|
552
|
+
cwd=result.deployed_path,
|
553
|
+
check=True,
|
474
554
|
)
|
475
555
|
return True
|
476
556
|
except:
|
@@ -483,4 +563,4 @@ class GitDeploymentStrategy(DeploymentStrategy):
|
|
483
563
|
"status": "healthy" if deployment_info.get("commit_hash") else "unhealthy",
|
484
564
|
"commit": deployment_info.get("commit_hash", "unknown"),
|
485
565
|
"branch": deployment_info.get("branch", "unknown"),
|
486
|
-
}
|
566
|
+
}
|