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.
Files changed (96) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/agent_loader.py +2 -2
  3. claude_mpm/agents/agent_loader_integration.py +2 -2
  4. claude_mpm/agents/async_agent_loader.py +2 -2
  5. claude_mpm/agents/base_agent_loader.py +2 -2
  6. claude_mpm/agents/frontmatter_validator.py +2 -2
  7. claude_mpm/agents/system_agent_config.py +2 -2
  8. claude_mpm/agents/templates/data_engineer.json +1 -2
  9. claude_mpm/cli/commands/doctor.py +2 -2
  10. claude_mpm/cli/commands/mpm_init.py +560 -47
  11. claude_mpm/cli/commands/mpm_init_handler.py +6 -0
  12. claude_mpm/cli/parsers/mpm_init_parser.py +39 -1
  13. claude_mpm/cli/startup_logging.py +11 -9
  14. claude_mpm/commands/mpm-init.md +76 -12
  15. claude_mpm/config/agent_config.py +2 -2
  16. claude_mpm/config/paths.py +2 -2
  17. claude_mpm/core/agent_name_normalizer.py +2 -2
  18. claude_mpm/core/config.py +2 -1
  19. claude_mpm/core/config_aliases.py +2 -2
  20. claude_mpm/core/file_utils.py +1 -0
  21. claude_mpm/core/log_manager.py +2 -2
  22. claude_mpm/core/tool_access_control.py +2 -2
  23. claude_mpm/core/unified_agent_registry.py +2 -2
  24. claude_mpm/core/unified_paths.py +2 -2
  25. claude_mpm/experimental/cli_enhancements.py +3 -2
  26. claude_mpm/hooks/base_hook.py +2 -2
  27. claude_mpm/hooks/instruction_reinforcement.py +2 -2
  28. claude_mpm/hooks/memory_integration_hook.py +1 -1
  29. claude_mpm/hooks/validation_hooks.py +2 -2
  30. claude_mpm/scripts/mpm_doctor.py +2 -2
  31. claude_mpm/services/agents/loading/agent_profile_loader.py +2 -2
  32. claude_mpm/services/agents/loading/base_agent_manager.py +2 -2
  33. claude_mpm/services/agents/loading/framework_agent_loader.py +2 -2
  34. claude_mpm/services/agents/management/agent_capabilities_generator.py +2 -2
  35. claude_mpm/services/agents/management/agent_management_service.py +2 -2
  36. claude_mpm/services/agents/memory/content_manager.py +5 -2
  37. claude_mpm/services/agents/memory/memory_categorization_service.py +5 -2
  38. claude_mpm/services/agents/memory/memory_file_service.py +28 -6
  39. claude_mpm/services/agents/memory/memory_format_service.py +5 -2
  40. claude_mpm/services/agents/memory/memory_limits_service.py +4 -2
  41. claude_mpm/services/agents/registry/deployed_agent_discovery.py +2 -2
  42. claude_mpm/services/agents/registry/modification_tracker.py +4 -4
  43. claude_mpm/services/async_session_logger.py +2 -1
  44. claude_mpm/services/claude_session_logger.py +2 -2
  45. claude_mpm/services/core/path_resolver.py +3 -2
  46. claude_mpm/services/diagnostics/diagnostic_runner.py +4 -3
  47. claude_mpm/services/event_bus/direct_relay.py +2 -1
  48. claude_mpm/services/event_bus/event_bus.py +2 -1
  49. claude_mpm/services/event_bus/relay.py +2 -2
  50. claude_mpm/services/framework_claude_md_generator/content_assembler.py +2 -2
  51. claude_mpm/services/infrastructure/daemon_manager.py +2 -2
  52. claude_mpm/services/memory/cache/simple_cache.py +2 -2
  53. claude_mpm/services/project/archive_manager.py +981 -0
  54. claude_mpm/services/project/documentation_manager.py +536 -0
  55. claude_mpm/services/project/enhanced_analyzer.py +491 -0
  56. claude_mpm/services/project/project_organizer.py +904 -0
  57. claude_mpm/services/response_tracker.py +2 -2
  58. claude_mpm/services/socketio/handlers/connection.py +14 -33
  59. claude_mpm/services/socketio/server/eventbus_integration.py +2 -2
  60. claude_mpm/services/unified/__init__.py +65 -0
  61. claude_mpm/services/unified/analyzer_strategies/__init__.py +44 -0
  62. claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +473 -0
  63. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +643 -0
  64. claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +804 -0
  65. claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +661 -0
  66. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +696 -0
  67. claude_mpm/services/unified/deployment_strategies/__init__.py +97 -0
  68. claude_mpm/services/unified/deployment_strategies/base.py +557 -0
  69. claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +486 -0
  70. claude_mpm/services/unified/deployment_strategies/local.py +594 -0
  71. claude_mpm/services/unified/deployment_strategies/utils.py +672 -0
  72. claude_mpm/services/unified/deployment_strategies/vercel.py +471 -0
  73. claude_mpm/services/unified/interfaces.py +499 -0
  74. claude_mpm/services/unified/migration.py +532 -0
  75. claude_mpm/services/unified/strategies.py +551 -0
  76. claude_mpm/services/unified/unified_analyzer.py +534 -0
  77. claude_mpm/services/unified/unified_config.py +688 -0
  78. claude_mpm/services/unified/unified_deployment.py +470 -0
  79. claude_mpm/services/version_control/version_parser.py +5 -4
  80. claude_mpm/storage/state_storage.py +2 -2
  81. claude_mpm/utils/agent_dependency_loader.py +49 -0
  82. claude_mpm/utils/common.py +542 -0
  83. claude_mpm/utils/database_connector.py +298 -0
  84. claude_mpm/utils/error_handler.py +2 -1
  85. claude_mpm/utils/log_cleanup.py +2 -2
  86. claude_mpm/utils/path_operations.py +2 -2
  87. claude_mpm/utils/robust_installer.py +56 -0
  88. claude_mpm/utils/session_logging.py +2 -2
  89. claude_mpm/utils/subprocess_utils.py +2 -2
  90. claude_mpm/validation/agent_validator.py +2 -2
  91. {claude_mpm-4.3.20.dist-info → claude_mpm-4.4.0.dist-info}/METADATA +1 -1
  92. {claude_mpm-4.3.20.dist-info → claude_mpm-4.4.0.dist-info}/RECORD +96 -71
  93. {claude_mpm-4.3.20.dist-info → claude_mpm-4.4.0.dist-info}/WHEEL +0 -0
  94. {claude_mpm-4.3.20.dist-info → claude_mpm-4.4.0.dist-info}/entry_points.txt +0 -0
  95. {claude_mpm-4.3.20.dist-info → claude_mpm-4.4.0.dist-info}/licenses/LICENSE +0 -0
  96. {claude_mpm-4.3.20.dist-info → claude_mpm-4.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,486 @@
1
+ """
2
+ Cloud Deployment Strategies
3
+ ===========================
4
+
5
+ Consolidated cloud deployment strategies for Railway, AWS, Docker, and Git.
6
+ Reduces duplication by sharing common cloud deployment patterns.
7
+ """
8
+
9
+ import json
10
+ import subprocess
11
+ from datetime import datetime
12
+ from pathlib import Path
13
+ from typing import Any, Dict, List, Optional
14
+
15
+ from claude_mpm.core.logging_utils import get_logger
16
+ from claude_mpm.services.unified.strategies import StrategyMetadata, StrategyPriority
17
+
18
+ from .base import DeploymentContext, DeploymentResult, DeploymentStrategy
19
+ from .utils import (
20
+ check_docker_container,
21
+ prepare_deployment_artifact,
22
+ rollback_docker_deployment,
23
+ verify_deployment_health,
24
+ )
25
+
26
+
27
+ class RailwayDeploymentStrategy(DeploymentStrategy):
28
+ """Deploy to Railway platform."""
29
+
30
+ def __init__(self):
31
+ """Initialize Railway strategy."""
32
+ super().__init__(StrategyMetadata(
33
+ name="RailwayDeploymentStrategy",
34
+ description="Deploy to Railway cloud platform",
35
+ supported_types=["application", "service", "*"],
36
+ supported_operations=["deploy", "rollback", "verify"],
37
+ priority=StrategyPriority.NORMAL,
38
+ tags={"railway", "cloud", "paas"},
39
+ ))
40
+ self._logger = get_logger(f"{__name__}.RailwayDeploymentStrategy")
41
+
42
+ def validate(self, context: DeploymentContext) -> List[str]:
43
+ """Validate Railway deployment."""
44
+ errors = []
45
+
46
+ # Check Railway CLI
47
+ try:
48
+ subprocess.run(["railway", "--version"], capture_output=True, check=True)
49
+ except:
50
+ errors.append("Railway CLI not installed. Install with: npm i -g @railway/cli")
51
+
52
+ # Check authentication
53
+ try:
54
+ subprocess.run(["railway", "whoami"], capture_output=True, check=True)
55
+ except:
56
+ errors.append("Not authenticated with Railway. Run: railway login")
57
+
58
+ return errors
59
+
60
+ def prepare(self, context: DeploymentContext) -> List[Path]:
61
+ """Prepare Railway artifacts."""
62
+ artifact_path, metadata = prepare_deployment_artifact(
63
+ context.source, "directory", context.config
64
+ )
65
+ return [artifact_path]
66
+
67
+ def execute(self, context: DeploymentContext, artifacts: List[Path]) -> Dict[str, Any]:
68
+ """Execute Railway deployment."""
69
+ deploy_dir = artifacts[0] if artifacts else Path(context.source)
70
+
71
+ cmd = ["railway", "up"]
72
+ if context.config.get("service"):
73
+ cmd.extend(["--service", context.config["service"]])
74
+ if context.config.get("environment"):
75
+ cmd.extend(["--environment", context.config["environment"]])
76
+
77
+ try:
78
+ result = subprocess.run(cmd, cwd=deploy_dir, capture_output=True, text=True, check=True)
79
+
80
+ # Parse deployment URL from output
81
+ deployment_url = None
82
+ for line in result.stdout.split("\n"):
83
+ if "https://" in line:
84
+ import re
85
+ match = re.search(r"https://[^\s]+", line)
86
+ if match:
87
+ deployment_url = match.group(0)
88
+ break
89
+
90
+ return {
91
+ "deployment_id": f"railway_{datetime.now().timestamp()}",
92
+ "deployment_url": deployment_url,
93
+ "deployed_path": deploy_dir,
94
+ "stdout": result.stdout,
95
+ }
96
+ except subprocess.CalledProcessError as e:
97
+ raise Exception(f"Railway deployment failed: {e.stderr}")
98
+
99
+ def verify(self, context: DeploymentContext, deployment_info: Dict[str, Any]) -> bool:
100
+ """Verify Railway deployment."""
101
+ return verify_deployment_health(
102
+ "railway", deployment_info, ["accessibility"]
103
+ )["status"] == "healthy"
104
+
105
+ def rollback(self, context: DeploymentContext, result: DeploymentResult) -> bool:
106
+ """Railway doesn't support CLI rollback."""
107
+ self._logger.warning("Railway rollback must be done via dashboard")
108
+ return False
109
+
110
+ def get_health_status(self, deployment_info: Dict[str, Any]) -> Dict[str, Any]:
111
+ """Get Railway deployment health."""
112
+ return verify_deployment_health("railway", deployment_info)
113
+
114
+
115
+ class AWSDeploymentStrategy(DeploymentStrategy):
116
+ """Deploy to AWS (Lambda, EC2, ECS)."""
117
+
118
+ def __init__(self):
119
+ """Initialize AWS strategy."""
120
+ super().__init__(StrategyMetadata(
121
+ name="AWSDeploymentStrategy",
122
+ description="Deploy to AWS services",
123
+ supported_types=["lambda", "ec2", "ecs", "application", "*"],
124
+ supported_operations=["deploy", "rollback", "verify"],
125
+ priority=StrategyPriority.NORMAL,
126
+ tags={"aws", "cloud", "serverless"},
127
+ ))
128
+ self._logger = get_logger(f"{__name__}.AWSDeploymentStrategy")
129
+
130
+ def validate(self, context: DeploymentContext) -> List[str]:
131
+ """Validate AWS deployment."""
132
+ errors = []
133
+
134
+ # Check AWS CLI
135
+ try:
136
+ subprocess.run(["aws", "--version"], capture_output=True, check=True)
137
+ except:
138
+ errors.append("AWS CLI not installed")
139
+
140
+ # Check credentials
141
+ try:
142
+ subprocess.run(["aws", "sts", "get-caller-identity"], capture_output=True, check=True)
143
+ except:
144
+ errors.append("AWS credentials not configured")
145
+
146
+ # Validate service type
147
+ service = context.config.get("service", "lambda")
148
+ if service not in ["lambda", "ec2", "ecs", "s3"]:
149
+ errors.append(f"Unsupported AWS service: {service}")
150
+
151
+ return errors
152
+
153
+ def prepare(self, context: DeploymentContext) -> List[Path]:
154
+ """Prepare AWS deployment artifacts."""
155
+ service = context.config.get("service", "lambda")
156
+
157
+ if service == "lambda":
158
+ # Create ZIP for Lambda
159
+ artifact_path, _ = prepare_deployment_artifact(
160
+ context.source, "zip", context.config
161
+ )
162
+ return [artifact_path]
163
+ else:
164
+ artifact_path, _ = prepare_deployment_artifact(
165
+ context.source, "directory", context.config
166
+ )
167
+ return [artifact_path]
168
+
169
+ def execute(self, context: DeploymentContext, artifacts: List[Path]) -> Dict[str, Any]:
170
+ """Execute AWS deployment."""
171
+ service = context.config.get("service", "lambda")
172
+
173
+ if service == "lambda":
174
+ return self._deploy_lambda(context, artifacts[0])
175
+ elif service == "s3":
176
+ return self._deploy_s3(context, artifacts[0])
177
+ else:
178
+ raise NotImplementedError(f"AWS {service} deployment not implemented")
179
+
180
+ def _deploy_lambda(self, context: DeploymentContext, artifact: Path) -> Dict[str, Any]:
181
+ """Deploy AWS Lambda function."""
182
+ function_name = context.config.get("function_name", artifact.stem)
183
+
184
+ # Check if function exists
185
+ try:
186
+ subprocess.run(
187
+ ["aws", "lambda", "get-function", "--function-name", function_name],
188
+ capture_output=True, check=True
189
+ )
190
+ # Update existing function
191
+ cmd = [
192
+ "aws", "lambda", "update-function-code",
193
+ "--function-name", function_name,
194
+ "--zip-file", f"fileb://{artifact}"
195
+ ]
196
+ except:
197
+ # Create new function
198
+ cmd = [
199
+ "aws", "lambda", "create-function",
200
+ "--function-name", function_name,
201
+ "--runtime", context.config.get("runtime", "python3.9"),
202
+ "--role", context.config.get("role"),
203
+ "--handler", context.config.get("handler", "index.handler"),
204
+ "--zip-file", f"fileb://{artifact}"
205
+ ]
206
+
207
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
208
+ response = json.loads(result.stdout)
209
+
210
+ return {
211
+ "deployment_id": response.get("FunctionArn"),
212
+ "deployment_url": response.get("FunctionUrl"),
213
+ "deployed_path": artifact,
214
+ "function_arn": response.get("FunctionArn"),
215
+ }
216
+
217
+ def _deploy_s3(self, context: DeploymentContext, artifact: Path) -> Dict[str, Any]:
218
+ """Deploy to S3 bucket."""
219
+ bucket = context.config.get("bucket")
220
+ prefix = context.config.get("prefix", "")
221
+
222
+ cmd = ["aws", "s3", "sync", str(artifact), f"s3://{bucket}/{prefix}"]
223
+ subprocess.run(cmd, check=True)
224
+
225
+ return {
226
+ "deployment_id": f"s3://{bucket}/{prefix}",
227
+ "deployment_url": f"https://{bucket}.s3.amazonaws.com/{prefix}",
228
+ "deployed_path": artifact,
229
+ }
230
+
231
+ def verify(self, context: DeploymentContext, deployment_info: Dict[str, Any]) -> bool:
232
+ """Verify AWS deployment."""
233
+ service = context.config.get("service", "lambda")
234
+
235
+ if service == "lambda" and "function_arn" in deployment_info:
236
+ try:
237
+ cmd = ["aws", "lambda", "get-function", "--function-name",
238
+ deployment_info["function_arn"]]
239
+ subprocess.run(cmd, capture_output=True, check=True)
240
+ return True
241
+ except:
242
+ return False
243
+
244
+ return True
245
+
246
+ def rollback(self, context: DeploymentContext, result: DeploymentResult) -> bool:
247
+ """Rollback AWS deployment."""
248
+ # AWS Lambda supports versioning for rollback
249
+ if context.config.get("service") == "lambda" and result.previous_version:
250
+ function_name = context.config.get("function_name")
251
+ cmd = [
252
+ "aws", "lambda", "update-alias",
253
+ "--function-name", function_name,
254
+ "--name", "PROD",
255
+ "--function-version", result.previous_version
256
+ ]
257
+ try:
258
+ subprocess.run(cmd, check=True)
259
+ return True
260
+ except:
261
+ pass
262
+ return False
263
+
264
+ def get_health_status(self, deployment_info: Dict[str, Any]) -> Dict[str, Any]:
265
+ """Get AWS deployment health."""
266
+ return verify_deployment_health("aws", deployment_info)
267
+
268
+
269
+ class DockerDeploymentStrategy(DeploymentStrategy):
270
+ """Deploy using Docker containers."""
271
+
272
+ def __init__(self):
273
+ """Initialize Docker strategy."""
274
+ super().__init__(StrategyMetadata(
275
+ name="DockerDeploymentStrategy",
276
+ description="Deploy using Docker containers",
277
+ supported_types=["container", "application", "service", "*"],
278
+ supported_operations=["deploy", "rollback", "verify", "stop"],
279
+ priority=StrategyPriority.HIGH,
280
+ tags={"docker", "container", "microservice"},
281
+ ))
282
+ self._logger = get_logger(f"{__name__}.DockerDeploymentStrategy")
283
+
284
+ def validate(self, context: DeploymentContext) -> List[str]:
285
+ """Validate Docker deployment."""
286
+ errors = []
287
+
288
+ # Check Docker
289
+ try:
290
+ subprocess.run(["docker", "--version"], capture_output=True, check=True)
291
+ except:
292
+ errors.append("Docker not installed or not running")
293
+
294
+ # Check Dockerfile exists
295
+ source_path = Path(context.source)
296
+ if source_path.is_dir():
297
+ dockerfile = source_path / "Dockerfile"
298
+ if not dockerfile.exists():
299
+ errors.append(f"No Dockerfile found in {source_path}")
300
+
301
+ return errors
302
+
303
+ def prepare(self, context: DeploymentContext) -> List[Path]:
304
+ """Prepare Docker artifacts."""
305
+ return [Path(context.source)]
306
+
307
+ def execute(self, context: DeploymentContext, artifacts: List[Path]) -> Dict[str, Any]:
308
+ """Execute Docker deployment."""
309
+ source_dir = artifacts[0] if artifacts[0].is_dir() else artifacts[0].parent
310
+ image_name = context.config.get("image_name", f"app_{datetime.now().timestamp()}")
311
+ container_name = context.config.get("container_name", image_name)
312
+
313
+ # Build image
314
+ build_cmd = ["docker", "build", "-t", image_name, str(source_dir)]
315
+ subprocess.run(build_cmd, check=True)
316
+
317
+ # Stop existing container if exists
318
+ subprocess.run(["docker", "stop", container_name], capture_output=True, check=False)
319
+ subprocess.run(["docker", "rm", container_name], capture_output=True, check=False)
320
+
321
+ # Run container
322
+ run_cmd = ["docker", "run", "-d", "--name", container_name]
323
+
324
+ # Add port mapping
325
+ if "ports" in context.config:
326
+ for port_map in context.config["ports"]:
327
+ run_cmd.extend(["-p", port_map])
328
+
329
+ # Add environment variables
330
+ if "env" in context.config:
331
+ for key, value in context.config["env"].items():
332
+ run_cmd.extend(["-e", f"{key}={value}"])
333
+
334
+ run_cmd.append(image_name)
335
+
336
+ result = subprocess.run(run_cmd, capture_output=True, text=True, check=True)
337
+ container_id = result.stdout.strip()
338
+
339
+ return {
340
+ "deployment_id": container_id[:12],
341
+ "container_id": container_id,
342
+ "image_name": image_name,
343
+ "container_name": container_name,
344
+ "deployed_path": source_dir,
345
+ }
346
+
347
+ def verify(self, context: DeploymentContext, deployment_info: Dict[str, Any]) -> bool:
348
+ """Verify Docker deployment."""
349
+ return check_docker_container(deployment_info.get("container_id"))
350
+
351
+ def rollback(self, context: DeploymentContext, result: DeploymentResult) -> bool:
352
+ """Rollback Docker deployment."""
353
+ return rollback_docker_deployment(result.to_dict())
354
+
355
+ def get_health_status(self, deployment_info: Dict[str, Any]) -> Dict[str, Any]:
356
+ """Get Docker container health."""
357
+ container_id = deployment_info.get("container_id")
358
+ health = {"status": "unknown", "container_id": container_id}
359
+
360
+ if container_id:
361
+ health["running"] = check_docker_container(container_id)
362
+ health["status"] = "healthy" if health["running"] else "unhealthy"
363
+
364
+ return health
365
+
366
+
367
+ class GitDeploymentStrategy(DeploymentStrategy):
368
+ """Deploy using Git (GitHub, GitLab)."""
369
+
370
+ def __init__(self):
371
+ """Initialize Git strategy."""
372
+ super().__init__(StrategyMetadata(
373
+ name="GitDeploymentStrategy",
374
+ description="Deploy using Git repositories",
375
+ supported_types=["repository", "code", "*"],
376
+ supported_operations=["deploy", "rollback", "verify"],
377
+ priority=StrategyPriority.NORMAL,
378
+ tags={"git", "github", "gitlab", "version-control"},
379
+ ))
380
+ self._logger = get_logger(f"{__name__}.GitDeploymentStrategy")
381
+
382
+ def validate(self, context: DeploymentContext) -> List[str]:
383
+ """Validate Git deployment."""
384
+ errors = []
385
+
386
+ # Check Git
387
+ try:
388
+ subprocess.run(["git", "--version"], capture_output=True, check=True)
389
+ except:
390
+ errors.append("Git not installed")
391
+
392
+ # Check remote URL
393
+ if not context.config.get("remote_url"):
394
+ errors.append("Git remote URL required")
395
+
396
+ return errors
397
+
398
+ def prepare(self, context: DeploymentContext) -> List[Path]:
399
+ """Prepare Git artifacts."""
400
+ return [Path(context.source)]
401
+
402
+ def execute(self, context: DeploymentContext, artifacts: List[Path]) -> Dict[str, Any]:
403
+ """Execute Git deployment."""
404
+ source_dir = artifacts[0] if artifacts[0].is_dir() else artifacts[0].parent
405
+ remote_url = context.config.get("remote_url")
406
+ branch = context.config.get("branch", "main")
407
+
408
+ # Initialize git if needed
409
+ if not (source_dir / ".git").exists():
410
+ subprocess.run(["git", "init"], cwd=source_dir, check=True)
411
+
412
+ # Add remote
413
+ subprocess.run(
414
+ ["git", "remote", "add", "deploy", remote_url],
415
+ cwd=source_dir, capture_output=True, check=False
416
+ )
417
+
418
+ # Add all files
419
+ subprocess.run(["git", "add", "."], cwd=source_dir, check=True)
420
+
421
+ # Commit
422
+ commit_msg = context.config.get("commit_message", "Deploy via Claude MPM")
423
+ subprocess.run(
424
+ ["git", "commit", "-m", commit_msg],
425
+ cwd=source_dir, capture_output=True, check=False
426
+ )
427
+
428
+ # Push
429
+ subprocess.run(
430
+ ["git", "push", "-u", "deploy", branch],
431
+ cwd=source_dir, check=True
432
+ )
433
+
434
+ # Get commit hash
435
+ result = subprocess.run(
436
+ ["git", "rev-parse", "HEAD"],
437
+ cwd=source_dir, capture_output=True, text=True, check=True
438
+ )
439
+ commit_hash = result.stdout.strip()
440
+
441
+ return {
442
+ "deployment_id": commit_hash[:8],
443
+ "commit_hash": commit_hash,
444
+ "remote_url": remote_url,
445
+ "branch": branch,
446
+ "deployed_path": source_dir,
447
+ }
448
+
449
+ def verify(self, context: DeploymentContext, deployment_info: Dict[str, Any]) -> bool:
450
+ """Verify Git deployment."""
451
+ # Check if commit exists on remote
452
+ try:
453
+ subprocess.run(
454
+ ["git", "ls-remote", deployment_info.get("remote_url"),
455
+ deployment_info.get("commit_hash")],
456
+ capture_output=True, check=True
457
+ )
458
+ return True
459
+ except:
460
+ return False
461
+
462
+ def rollback(self, context: DeploymentContext, result: DeploymentResult) -> bool:
463
+ """Rollback Git deployment."""
464
+ if result.previous_version:
465
+ try:
466
+ subprocess.run(
467
+ ["git", "checkout", result.previous_version],
468
+ cwd=result.deployed_path, check=True
469
+ )
470
+ subprocess.run(
471
+ ["git", "push", "--force", "deploy",
472
+ f"{result.previous_version}:{context.config.get('branch', 'main')}"],
473
+ cwd=result.deployed_path, check=True
474
+ )
475
+ return True
476
+ except:
477
+ pass
478
+ return False
479
+
480
+ def get_health_status(self, deployment_info: Dict[str, Any]) -> Dict[str, Any]:
481
+ """Get Git deployment health."""
482
+ return {
483
+ "status": "healthy" if deployment_info.get("commit_hash") else "unhealthy",
484
+ "commit": deployment_info.get("commit_hash", "unknown"),
485
+ "branch": deployment_info.get("branch", "unknown"),
486
+ }