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,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
- self.logger = logging.getLogger(__name__)
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: