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
@@ -22,7 +22,12 @@ import yaml
|
|
22
22
|
from claude_mpm.core.logging_utils import get_logger
|
23
23
|
from claude_mpm.services.unified.strategies import StrategyMetadata, StrategyPriority
|
24
24
|
|
25
|
-
from .base import
|
25
|
+
from .base import (
|
26
|
+
DeploymentContext,
|
27
|
+
DeploymentResult,
|
28
|
+
DeploymentStrategy,
|
29
|
+
DeploymentType,
|
30
|
+
)
|
26
31
|
|
27
32
|
|
28
33
|
class LocalDeploymentStrategy(DeploymentStrategy):
|
@@ -83,13 +88,14 @@ class LocalDeploymentStrategy(DeploymentStrategy):
|
|
83
88
|
target_parent.mkdir(parents=True, exist_ok=True)
|
84
89
|
target_parent.rmdir() # Clean up test directory
|
85
90
|
except PermissionError:
|
86
|
-
errors.append(
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
91
|
+
errors.append(
|
92
|
+
f"No permission to create target directory: {target_parent}"
|
93
|
+
)
|
94
|
+
# Check write permissions
|
95
|
+
elif not target_parent.is_dir():
|
96
|
+
errors.append(f"Target parent is not a directory: {target_parent}")
|
97
|
+
elif not self._check_write_permission(target_parent):
|
98
|
+
errors.append(f"No write permission for target: {target_parent}")
|
93
99
|
|
94
100
|
# Validate deployment type specific requirements
|
95
101
|
if context.deployment_type == DeploymentType.AGENT:
|
@@ -209,16 +215,14 @@ class LocalDeploymentStrategy(DeploymentStrategy):
|
|
209
215
|
# Type-specific verification
|
210
216
|
if context.deployment_type == DeploymentType.AGENT:
|
211
217
|
return self._verify_agent_deployment(deployed_path, context)
|
212
|
-
|
218
|
+
if context.deployment_type == DeploymentType.CONFIG:
|
213
219
|
return self._verify_config_deployment(deployed_path, context)
|
214
|
-
|
220
|
+
if context.deployment_type == DeploymentType.TEMPLATE:
|
215
221
|
return self._verify_template_deployment(deployed_path, context)
|
216
222
|
|
217
223
|
return True
|
218
224
|
|
219
|
-
def rollback(
|
220
|
-
self, context: DeploymentContext, result: DeploymentResult
|
221
|
-
) -> bool:
|
225
|
+
def rollback(self, context: DeploymentContext, result: DeploymentResult) -> bool:
|
222
226
|
"""
|
223
227
|
Rollback local deployment.
|
224
228
|
|
@@ -252,12 +256,10 @@ class LocalDeploymentStrategy(DeploymentStrategy):
|
|
252
256
|
return True
|
253
257
|
|
254
258
|
except Exception as e:
|
255
|
-
self._logger.error(f"Rollback failed: {
|
259
|
+
self._logger.error(f"Rollback failed: {e!s}")
|
256
260
|
return False
|
257
261
|
|
258
|
-
def get_health_status(
|
259
|
-
self, deployment_info: Dict[str, Any]
|
260
|
-
) -> Dict[str, Any]:
|
262
|
+
def get_health_status(self, deployment_info: Dict[str, Any]) -> Dict[str, Any]:
|
261
263
|
"""
|
262
264
|
Get health status of local deployment.
|
263
265
|
|
@@ -307,7 +309,9 @@ class LocalDeploymentStrategy(DeploymentStrategy):
|
|
307
309
|
|
308
310
|
def _generate_deployment_id(self) -> str:
|
309
311
|
"""Generate unique deployment ID."""
|
310
|
-
return
|
312
|
+
return (
|
313
|
+
f"local_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{id(self) % 10000:04d}"
|
314
|
+
)
|
311
315
|
|
312
316
|
def _create_backup(self, context: DeploymentContext) -> Optional[Path]:
|
313
317
|
"""Create backup of target before deployment."""
|
@@ -333,7 +337,7 @@ class LocalDeploymentStrategy(DeploymentStrategy):
|
|
333
337
|
return backup_path
|
334
338
|
|
335
339
|
except Exception as e:
|
336
|
-
self._logger.warning(f"Failed to create backup: {
|
340
|
+
self._logger.warning(f"Failed to create backup: {e!s}")
|
337
341
|
return None
|
338
342
|
|
339
343
|
def _write_version_file(self, target_path: Path, version: str) -> None:
|
@@ -354,9 +358,11 @@ class LocalDeploymentStrategy(DeploymentStrategy):
|
|
354
358
|
errors.append(f"Invalid agent file format: {source_path.suffix}")
|
355
359
|
elif source_path.is_dir():
|
356
360
|
# Check for agent definition files
|
357
|
-
agent_files =
|
358
|
-
|
359
|
-
|
361
|
+
agent_files = (
|
362
|
+
list(source_path.glob("*.json"))
|
363
|
+
+ list(source_path.glob("*.yaml"))
|
364
|
+
+ list(source_path.glob("*.yml"))
|
365
|
+
)
|
360
366
|
if not agent_files:
|
361
367
|
errors.append(f"No agent definition files found in: {source_path}")
|
362
368
|
|
@@ -397,8 +403,9 @@ class LocalDeploymentStrategy(DeploymentStrategy):
|
|
397
403
|
) -> bool:
|
398
404
|
"""Verify agent deployment."""
|
399
405
|
# Check for valid YAML structure
|
400
|
-
yaml_files = list(deployed_path.glob("*.yaml")) +
|
401
|
-
|
406
|
+
yaml_files = list(deployed_path.glob("*.yaml")) + list(
|
407
|
+
deployed_path.glob("*.yml")
|
408
|
+
)
|
402
409
|
|
403
410
|
for yaml_file in yaml_files:
|
404
411
|
try:
|
@@ -411,7 +418,7 @@ class LocalDeploymentStrategy(DeploymentStrategy):
|
|
411
418
|
self._logger.error(f"Agent missing 'name' field: {yaml_file}")
|
412
419
|
return False
|
413
420
|
except Exception as e:
|
414
|
-
self._logger.error(f"Invalid agent YAML: {yaml_file}: {
|
421
|
+
self._logger.error(f"Invalid agent YAML: {yaml_file}: {e!s}")
|
415
422
|
return False
|
416
423
|
|
417
424
|
return True
|
@@ -436,7 +443,14 @@ class LocalDeploymentStrategy(DeploymentStrategy):
|
|
436
443
|
|
437
444
|
if source_path.is_file():
|
438
445
|
# Validate config file format
|
439
|
-
if source_path.suffix not in [
|
446
|
+
if source_path.suffix not in [
|
447
|
+
".json",
|
448
|
+
".yaml",
|
449
|
+
".yml",
|
450
|
+
".toml",
|
451
|
+
".ini",
|
452
|
+
".env",
|
453
|
+
]:
|
440
454
|
errors.append(f"Unsupported config format: {source_path.suffix}")
|
441
455
|
|
442
456
|
return errors
|
@@ -496,15 +510,16 @@ class LocalDeploymentStrategy(DeploymentStrategy):
|
|
496
510
|
|
497
511
|
# Process template with variables
|
498
512
|
if source_path.is_file():
|
499
|
-
processed = self._process_template(
|
513
|
+
processed = self._process_template(
|
514
|
+
source_path, context.config.get("variables", {})
|
515
|
+
)
|
500
516
|
artifacts.append(processed)
|
501
517
|
else:
|
502
518
|
# Process all template files in directory
|
503
519
|
for template_file in source_path.rglob("*"):
|
504
520
|
if template_file.is_file():
|
505
521
|
processed = self._process_template(
|
506
|
-
template_file,
|
507
|
-
context.config.get("variables", {})
|
522
|
+
template_file, context.config.get("variables", {})
|
508
523
|
)
|
509
524
|
artifacts.append(processed)
|
510
525
|
|
@@ -552,9 +567,7 @@ class LocalDeploymentStrategy(DeploymentStrategy):
|
|
552
567
|
|
553
568
|
return True
|
554
569
|
|
555
|
-
def _process_template(
|
556
|
-
self, template_path: Path, variables: Dict[str, Any]
|
557
|
-
) -> Path:
|
570
|
+
def _process_template(self, template_path: Path, variables: Dict[str, Any]) -> Path:
|
558
571
|
"""Process template file with variables."""
|
559
572
|
content = template_path.read_text()
|
560
573
|
|
@@ -578,7 +591,9 @@ class LocalDeploymentStrategy(DeploymentStrategy):
|
|
578
591
|
|
579
592
|
for artifact in artifacts:
|
580
593
|
if artifact.is_file():
|
581
|
-
dest =
|
594
|
+
dest = (
|
595
|
+
target_path / artifact.name if target_path.is_dir() else target_path
|
596
|
+
)
|
582
597
|
shutil.copy2(artifact, dest)
|
583
598
|
deployed.append(dest)
|
584
599
|
elif artifact.is_dir():
|
@@ -591,4 +606,4 @@ class LocalDeploymentStrategy(DeploymentStrategy):
|
|
591
606
|
|
592
607
|
self._logger.info(f"Deployed resource: {deployed[-1]}")
|
593
608
|
|
594
|
-
return deployed
|
609
|
+
return deployed
|
@@ -16,7 +16,6 @@ This module reduces ~5000 LOC of duplicated utility functions across:
|
|
16
16
|
|
17
17
|
import hashlib
|
18
18
|
import json
|
19
|
-
import os
|
20
19
|
import shutil
|
21
20
|
import subprocess
|
22
21
|
import tempfile
|
@@ -24,8 +23,6 @@ from datetime import datetime
|
|
24
23
|
from pathlib import Path
|
25
24
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
26
25
|
|
27
|
-
import yaml
|
28
|
-
|
29
26
|
from claude_mpm.core.logging_utils import get_logger
|
30
27
|
|
31
28
|
logger = get_logger(__name__)
|
@@ -34,6 +31,7 @@ logger = get_logger(__name__)
|
|
34
31
|
# Validation Utilities
|
35
32
|
# ====================
|
36
33
|
|
34
|
+
|
37
35
|
def validate_deployment_config(config: Dict[str, Any]) -> List[str]:
|
38
36
|
"""
|
39
37
|
Validate deployment configuration.
|
@@ -57,8 +55,16 @@ def validate_deployment_config(config: Dict[str, Any]) -> List[str]:
|
|
57
55
|
# Type validation
|
58
56
|
if "type" in config:
|
59
57
|
valid_types = [
|
60
|
-
"local",
|
61
|
-
"
|
58
|
+
"local",
|
59
|
+
"vercel",
|
60
|
+
"railway",
|
61
|
+
"aws",
|
62
|
+
"docker",
|
63
|
+
"git",
|
64
|
+
"agent",
|
65
|
+
"config",
|
66
|
+
"template",
|
67
|
+
"resource",
|
62
68
|
]
|
63
69
|
if config["type"] not in valid_types:
|
64
70
|
errors.append(f"Invalid deployment type: {config['type']}")
|
@@ -133,10 +139,11 @@ def validate_path_security(path: Path, base_path: Path) -> bool:
|
|
133
139
|
# Artifact Preparation
|
134
140
|
# ====================
|
135
141
|
|
142
|
+
|
136
143
|
def prepare_deployment_artifact(
|
137
144
|
source: Union[str, Path],
|
138
145
|
artifact_type: str = "auto",
|
139
|
-
config: Optional[Dict[str, Any]] = None
|
146
|
+
config: Optional[Dict[str, Any]] = None,
|
140
147
|
) -> Tuple[Path, Dict[str, Any]]:
|
141
148
|
"""
|
142
149
|
Prepare deployment artifact from source.
|
@@ -230,10 +237,11 @@ def create_tar_artifact(source: Path, output_dir: Path) -> Path:
|
|
230
237
|
# Health Check Utilities
|
231
238
|
# ======================
|
232
239
|
|
240
|
+
|
233
241
|
def verify_deployment_health(
|
234
242
|
deployment_type: str,
|
235
243
|
deployment_info: Dict[str, Any],
|
236
|
-
checks: Optional[List[str]] = None
|
244
|
+
checks: Optional[List[str]] = None,
|
237
245
|
) -> Dict[str, Any]:
|
238
246
|
"""
|
239
247
|
Perform health checks on deployment.
|
@@ -275,8 +283,7 @@ def verify_deployment_health(
|
|
275
283
|
if "integrity" in checks:
|
276
284
|
if "checksum" in deployment_info:
|
277
285
|
health["checks"]["integrity"] = verify_checksum(
|
278
|
-
deployment_info.get("deployed_path"),
|
279
|
-
deployment_info["checksum"]
|
286
|
+
deployment_info.get("deployed_path"), deployment_info["checksum"]
|
280
287
|
)
|
281
288
|
|
282
289
|
# Service-specific checks
|
@@ -285,9 +292,7 @@ def verify_deployment_health(
|
|
285
292
|
deployment_info.get("container_id")
|
286
293
|
)
|
287
294
|
elif deployment_type == "aws":
|
288
|
-
health["checks"]["aws_status"] = check_aws_deployment(
|
289
|
-
deployment_info
|
290
|
-
)
|
295
|
+
health["checks"]["aws_status"] = check_aws_deployment(deployment_info)
|
291
296
|
|
292
297
|
# Determine overall status
|
293
298
|
if all(health["checks"].values()):
|
@@ -308,6 +313,7 @@ def check_url_accessibility(url: str, timeout: int = 10) -> bool:
|
|
308
313
|
"""Check if URL is accessible."""
|
309
314
|
try:
|
310
315
|
import urllib.request
|
316
|
+
|
311
317
|
with urllib.request.urlopen(url, timeout=timeout) as response:
|
312
318
|
return response.status < 400
|
313
319
|
except Exception:
|
@@ -340,10 +346,11 @@ def check_aws_deployment(deployment_info: Dict[str, Any]) -> bool:
|
|
340
346
|
# Rollback Utilities
|
341
347
|
# ==================
|
342
348
|
|
349
|
+
|
343
350
|
def rollback_deployment(
|
344
351
|
deployment_type: str,
|
345
352
|
deployment_info: Dict[str, Any],
|
346
|
-
backup_info: Optional[Dict[str, Any]] = None
|
353
|
+
backup_info: Optional[Dict[str, Any]] = None,
|
347
354
|
) -> bool:
|
348
355
|
"""
|
349
356
|
Rollback deployment to previous state.
|
@@ -361,22 +368,20 @@ def rollback_deployment(
|
|
361
368
|
try:
|
362
369
|
if deployment_type == "local":
|
363
370
|
return rollback_local_deployment(deployment_info, backup_info)
|
364
|
-
|
371
|
+
if deployment_type == "docker":
|
365
372
|
return rollback_docker_deployment(deployment_info)
|
366
|
-
|
373
|
+
if deployment_type == "git":
|
367
374
|
return rollback_git_deployment(deployment_info)
|
368
|
-
|
369
|
-
|
370
|
-
return False
|
375
|
+
logger.warning(f"No rollback strategy for type: {deployment_type}")
|
376
|
+
return False
|
371
377
|
|
372
378
|
except Exception as e:
|
373
|
-
logger.error(f"Rollback failed: {
|
379
|
+
logger.error(f"Rollback failed: {e!s}")
|
374
380
|
return False
|
375
381
|
|
376
382
|
|
377
383
|
def rollback_local_deployment(
|
378
|
-
deployment_info: Dict[str, Any],
|
379
|
-
backup_info: Optional[Dict[str, Any]] = None
|
384
|
+
deployment_info: Dict[str, Any], backup_info: Optional[Dict[str, Any]] = None
|
380
385
|
) -> bool:
|
381
386
|
"""Rollback local filesystem deployment."""
|
382
387
|
deployed_path = Path(deployment_info.get("deployed_path", ""))
|
@@ -413,8 +418,7 @@ def rollback_docker_deployment(deployment_info: Dict[str, Any]) -> bool:
|
|
413
418
|
# Restore previous container if specified
|
414
419
|
if "previous_container" in deployment_info:
|
415
420
|
subprocess.run(
|
416
|
-
["docker", "start", deployment_info["previous_container"]],
|
417
|
-
check=True
|
421
|
+
["docker", "start", deployment_info["previous_container"]], check=True
|
418
422
|
)
|
419
423
|
|
420
424
|
return True
|
@@ -426,11 +430,7 @@ def rollback_git_deployment(deployment_info: Dict[str, Any]) -> bool:
|
|
426
430
|
previous_commit = deployment_info.get("previous_commit")
|
427
431
|
|
428
432
|
if repo_path.exists() and previous_commit:
|
429
|
-
subprocess.run(
|
430
|
-
["git", "checkout", previous_commit],
|
431
|
-
cwd=repo_path,
|
432
|
-
check=True
|
433
|
-
)
|
433
|
+
subprocess.run(["git", "checkout", previous_commit], cwd=repo_path, check=True)
|
434
434
|
return True
|
435
435
|
|
436
436
|
return False
|
@@ -439,6 +439,7 @@ def rollback_git_deployment(deployment_info: Dict[str, Any]) -> bool:
|
|
439
439
|
# Version Management
|
440
440
|
# ==================
|
441
441
|
|
442
|
+
|
442
443
|
def get_version_info(path: Union[str, Path]) -> Dict[str, Any]:
|
443
444
|
"""
|
444
445
|
Extract version information from deployment.
|
@@ -474,6 +475,7 @@ def get_version_info(path: Union[str, Path]) -> Dict[str, Any]:
|
|
474
475
|
elif version_file in ["setup.py", "pyproject.toml"]:
|
475
476
|
# Simple regex extraction
|
476
477
|
import re
|
478
|
+
|
477
479
|
content = file_path.read_text()
|
478
480
|
match = re.search(r'version\s*=\s*["\'](.*?)["\']', content)
|
479
481
|
if match:
|
@@ -491,9 +493,7 @@ def get_version_info(path: Union[str, Path]) -> Dict[str, Any]:
|
|
491
493
|
|
492
494
|
|
493
495
|
def update_version(
|
494
|
-
path: Union[str, Path],
|
495
|
-
new_version: str,
|
496
|
-
create_backup: bool = True
|
496
|
+
path: Union[str, Path], new_version: str, create_backup: bool = True
|
497
497
|
) -> bool:
|
498
498
|
"""
|
499
499
|
Update version in deployment.
|
@@ -520,13 +520,14 @@ def update_version(
|
|
520
520
|
return True
|
521
521
|
|
522
522
|
except Exception as e:
|
523
|
-
logger.error(f"Failed to update version: {
|
523
|
+
logger.error(f"Failed to update version: {e!s}")
|
524
524
|
return False
|
525
525
|
|
526
526
|
|
527
527
|
# Checksum and Integrity
|
528
528
|
# ======================
|
529
529
|
|
530
|
+
|
530
531
|
def calculate_checksum(path: Union[str, Path], algorithm: str = "sha256") -> str:
|
531
532
|
"""
|
532
533
|
Calculate checksum of file or directory.
|
@@ -558,9 +559,7 @@ def calculate_checksum(path: Union[str, Path], algorithm: str = "sha256") -> str
|
|
558
559
|
|
559
560
|
|
560
561
|
def verify_checksum(
|
561
|
-
path: Union[str, Path],
|
562
|
-
expected_checksum: str,
|
563
|
-
algorithm: str = "sha256"
|
562
|
+
path: Union[str, Path], expected_checksum: str, algorithm: str = "sha256"
|
564
563
|
) -> bool:
|
565
564
|
"""
|
566
565
|
Verify checksum of file or directory.
|
@@ -594,19 +593,19 @@ def get_size(path: Union[str, Path]) -> int:
|
|
594
593
|
|
595
594
|
if path.is_file():
|
596
595
|
return path.stat().st_size
|
597
|
-
|
596
|
+
if path.is_dir():
|
598
597
|
total_size = 0
|
599
598
|
for file_path in path.rglob("*"):
|
600
599
|
if file_path.is_file():
|
601
600
|
total_size += file_path.stat().st_size
|
602
601
|
return total_size
|
603
|
-
|
604
|
-
return 0
|
602
|
+
return 0
|
605
603
|
|
606
604
|
|
607
605
|
# Environment Management
|
608
606
|
# =====================
|
609
607
|
|
608
|
+
|
610
609
|
def load_env_file(env_file: Union[str, Path]) -> Dict[str, str]:
|
611
610
|
"""
|
612
611
|
Load environment variables from file.
|
@@ -650,10 +649,7 @@ def merge_environments(*env_dicts: Dict[str, str]) -> Dict[str, str]:
|
|
650
649
|
return merged
|
651
650
|
|
652
651
|
|
653
|
-
def export_env_to_file(
|
654
|
-
env_vars: Dict[str, str],
|
655
|
-
output_file: Union[str, Path]
|
656
|
-
) -> None:
|
652
|
+
def export_env_to_file(env_vars: Dict[str, str], output_file: Union[str, Path]) -> None:
|
657
653
|
"""
|
658
654
|
Export environment variables to file.
|
659
655
|
|
@@ -669,4 +665,4 @@ def export_env_to_file(
|
|
669
665
|
# Escape special characters in value
|
670
666
|
if " " in value or '"' in value:
|
671
667
|
value = f'"{value}"'
|
672
|
-
f.write(f"{key}={value}\n")
|
668
|
+
f.write(f"{key}={value}\n")
|
@@ -16,7 +16,11 @@ from typing import Any, Dict, List, Optional
|
|
16
16
|
from claude_mpm.core.logging_utils import get_logger
|
17
17
|
from claude_mpm.services.unified.strategies import StrategyMetadata, StrategyPriority
|
18
18
|
|
19
|
-
from .base import
|
19
|
+
from .base import (
|
20
|
+
DeploymentContext,
|
21
|
+
DeploymentResult,
|
22
|
+
DeploymentStrategy,
|
23
|
+
)
|
20
24
|
|
21
25
|
|
22
26
|
class VercelDeploymentStrategy(DeploymentStrategy):
|
@@ -117,11 +121,13 @@ class VercelDeploymentStrategy(DeploymentStrategy):
|
|
117
121
|
# Single file deployment (e.g., serverless function)
|
118
122
|
deploy_file = deploy_dir / source_path.name
|
119
123
|
import shutil
|
124
|
+
|
120
125
|
shutil.copy2(source_path, deploy_file)
|
121
126
|
artifacts.append(deploy_file)
|
122
127
|
else:
|
123
128
|
# Directory deployment
|
124
129
|
import shutil
|
130
|
+
|
125
131
|
shutil.copytree(source_path, deploy_dir / "app", dirs_exist_ok=True)
|
126
132
|
artifacts.append(deploy_dir / "app")
|
127
133
|
|
@@ -200,8 +206,7 @@ class VercelDeploymentStrategy(DeploymentStrategy):
|
|
200
206
|
"stdout": result.stdout,
|
201
207
|
"timestamp": datetime.now().isoformat(),
|
202
208
|
}
|
203
|
-
|
204
|
-
raise Exception("Could not parse deployment URL from Vercel output")
|
209
|
+
raise Exception("Could not parse deployment URL from Vercel output")
|
205
210
|
|
206
211
|
except subprocess.CalledProcessError as e:
|
207
212
|
self._logger.error(f"Vercel deployment failed: {e.stderr}")
|
@@ -235,18 +240,15 @@ class VercelDeploymentStrategy(DeploymentStrategy):
|
|
235
240
|
if response.status == 200:
|
236
241
|
self._logger.info(f"Deployment verified: {deployment_url}")
|
237
242
|
return True
|
238
|
-
|
239
|
-
|
240
|
-
return False
|
243
|
+
self._logger.error(f"Deployment returned status: {response.status}")
|
244
|
+
return False
|
241
245
|
|
242
246
|
except Exception as e:
|
243
|
-
self._logger.error(f"Failed to verify deployment: {
|
247
|
+
self._logger.error(f"Failed to verify deployment: {e!s}")
|
244
248
|
# May still be building, check via CLI
|
245
249
|
return self._check_deployment_status(deployment_info.get("deployment_id"))
|
246
250
|
|
247
|
-
def rollback(
|
248
|
-
self, context: DeploymentContext, result: DeploymentResult
|
249
|
-
) -> bool:
|
251
|
+
def rollback(self, context: DeploymentContext, result: DeploymentResult) -> bool:
|
250
252
|
"""
|
251
253
|
Rollback Vercel deployment.
|
252
254
|
|
@@ -280,23 +282,20 @@ class VercelDeploymentStrategy(DeploymentStrategy):
|
|
280
282
|
check=True,
|
281
283
|
)
|
282
284
|
|
283
|
-
self._logger.info(
|
285
|
+
self._logger.info("Rolled back to previous deployment")
|
284
286
|
return True
|
285
287
|
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
return False
|
288
|
+
self._logger.warning(
|
289
|
+
"No previous deployment ID available for rollback. "
|
290
|
+
"Manual rollback required via Vercel dashboard."
|
291
|
+
)
|
292
|
+
return False
|
292
293
|
|
293
294
|
except Exception as e:
|
294
|
-
self._logger.error(f"Rollback failed: {
|
295
|
+
self._logger.error(f"Rollback failed: {e!s}")
|
295
296
|
return False
|
296
297
|
|
297
|
-
def get_health_status(
|
298
|
-
self, deployment_info: Dict[str, Any]
|
299
|
-
) -> Dict[str, Any]:
|
298
|
+
def get_health_status(self, deployment_info: Dict[str, Any]) -> Dict[str, Any]:
|
300
299
|
"""
|
301
300
|
Get health status of Vercel deployment.
|
302
301
|
|
@@ -325,7 +324,9 @@ class VercelDeploymentStrategy(DeploymentStrategy):
|
|
325
324
|
# Check main deployment URL
|
326
325
|
with urllib.request.urlopen(deployment_url) as response:
|
327
326
|
health["checks"]["main_url"] = response.status == 200
|
328
|
-
health["response_time_ms"] = response.info().get(
|
327
|
+
health["response_time_ms"] = response.info().get(
|
328
|
+
"X-Vercel-Trace", "N/A"
|
329
|
+
)
|
329
330
|
|
330
331
|
# Check functions if configured
|
331
332
|
if deployment_info.get("functions"):
|
@@ -333,7 +334,9 @@ class VercelDeploymentStrategy(DeploymentStrategy):
|
|
333
334
|
func_url = f"{deployment_url}/api/{func_name}"
|
334
335
|
try:
|
335
336
|
with urllib.request.urlopen(func_url) as response:
|
336
|
-
health["checks"][f"function_{func_name}"] =
|
337
|
+
health["checks"][f"function_{func_name}"] = (
|
338
|
+
response.status < 500
|
339
|
+
)
|
337
340
|
except:
|
338
341
|
health["checks"][f"function_{func_name}"] = False
|
339
342
|
|
@@ -443,6 +446,7 @@ class VercelDeploymentStrategy(DeploymentStrategy):
|
|
443
446
|
if "https://" in line:
|
444
447
|
# Extract URL
|
445
448
|
import re
|
449
|
+
|
446
450
|
url_match = re.search(r"https://[^\s]+", line)
|
447
451
|
if url_match:
|
448
452
|
return url_match.group(0)
|
@@ -468,4 +472,6 @@ class VercelDeploymentStrategy(DeploymentStrategy):
|
|
468
472
|
|
469
473
|
def _generate_deployment_id(self) -> str:
|
470
474
|
"""Generate unique deployment ID."""
|
471
|
-
return
|
475
|
+
return (
|
476
|
+
f"vercel_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{id(self) % 10000:04d}"
|
477
|
+
)
|