claude-mpm 4.1.2__py3-none-any.whl → 4.1.3__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 (53) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/templates/engineer.json +33 -11
  3. claude_mpm/cli/commands/agents.py +556 -1009
  4. claude_mpm/cli/commands/memory.py +248 -927
  5. claude_mpm/cli/commands/run.py +139 -484
  6. claude_mpm/cli/startup_logging.py +76 -0
  7. claude_mpm/core/agent_registry.py +6 -10
  8. claude_mpm/core/framework_loader.py +114 -595
  9. claude_mpm/core/logging_config.py +2 -4
  10. claude_mpm/hooks/claude_hooks/event_handlers.py +7 -117
  11. claude_mpm/hooks/claude_hooks/hook_handler.py +91 -755
  12. claude_mpm/hooks/claude_hooks/hook_handler_original.py +1040 -0
  13. claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +347 -0
  14. claude_mpm/hooks/claude_hooks/services/__init__.py +13 -0
  15. claude_mpm/hooks/claude_hooks/services/connection_manager.py +190 -0
  16. claude_mpm/hooks/claude_hooks/services/duplicate_detector.py +106 -0
  17. claude_mpm/hooks/claude_hooks/services/state_manager.py +282 -0
  18. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +374 -0
  19. claude_mpm/services/agents/deployment/agent_deployment.py +42 -454
  20. claude_mpm/services/agents/deployment/base_agent_locator.py +132 -0
  21. claude_mpm/services/agents/deployment/deployment_results_manager.py +185 -0
  22. claude_mpm/services/agents/deployment/single_agent_deployer.py +315 -0
  23. claude_mpm/services/agents/memory/agent_memory_manager.py +42 -508
  24. claude_mpm/services/agents/memory/memory_categorization_service.py +165 -0
  25. claude_mpm/services/agents/memory/memory_file_service.py +103 -0
  26. claude_mpm/services/agents/memory/memory_format_service.py +201 -0
  27. claude_mpm/services/agents/memory/memory_limits_service.py +99 -0
  28. claude_mpm/services/agents/registry/__init__.py +1 -1
  29. claude_mpm/services/cli/__init__.py +18 -0
  30. claude_mpm/services/cli/agent_cleanup_service.py +407 -0
  31. claude_mpm/services/cli/agent_dependency_service.py +395 -0
  32. claude_mpm/services/cli/agent_listing_service.py +463 -0
  33. claude_mpm/services/cli/agent_output_formatter.py +605 -0
  34. claude_mpm/services/cli/agent_validation_service.py +589 -0
  35. claude_mpm/services/cli/dashboard_launcher.py +424 -0
  36. claude_mpm/services/cli/memory_crud_service.py +617 -0
  37. claude_mpm/services/cli/memory_output_formatter.py +604 -0
  38. claude_mpm/services/cli/session_manager.py +513 -0
  39. claude_mpm/services/cli/socketio_manager.py +498 -0
  40. claude_mpm/services/cli/startup_checker.py +370 -0
  41. claude_mpm/services/core/cache_manager.py +311 -0
  42. claude_mpm/services/core/memory_manager.py +637 -0
  43. claude_mpm/services/core/path_resolver.py +498 -0
  44. claude_mpm/services/core/service_container.py +520 -0
  45. claude_mpm/services/core/service_interfaces.py +436 -0
  46. claude_mpm/services/diagnostics/checks/agent_check.py +65 -19
  47. {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/METADATA +1 -1
  48. {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/RECORD +52 -22
  49. claude_mpm/cli/commands/run_config_checker.py +0 -159
  50. {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/WHEEL +0 -0
  51. {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/entry_points.txt +0 -0
  52. {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/licenses/LICENSE +0 -0
  53. {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,520 @@
1
+ """
2
+ Service Container with Dependency Injection
3
+ ===========================================
4
+
5
+ WHY: This service container provides comprehensive dependency injection to manage
6
+ all services in the Claude MPM framework, enabling loose coupling, better testability,
7
+ and simplified service management.
8
+
9
+ DESIGN DECISIONS:
10
+ - Thread-safe implementation for concurrent service resolution
11
+ - Support for singleton, transient, and factory patterns
12
+ - Automatic dependency resolution based on constructor parameters
13
+ - Circular dependency detection and prevention
14
+ - Lazy initialization support for expensive services
15
+
16
+ FEATURES:
17
+ - Service registration (interface → implementation mapping)
18
+ - Automatic dependency injection based on type hints
19
+ - Multiple lifetime management strategies
20
+ - Factory pattern support for complex creation logic
21
+ - Service resolution with automatic dependency graph traversal
22
+ """
23
+
24
+ import inspect
25
+ import threading
26
+ from enum import Enum
27
+ from typing import Any, Callable, Dict, List, Optional, Type, TypeVar, Union
28
+
29
+ from claude_mpm.core.logger import get_logger
30
+
31
+ # Type variable for generic service types
32
+ T = TypeVar("T")
33
+
34
+
35
+ class ServiceLifetime(Enum):
36
+ """Service lifetime management strategies."""
37
+
38
+ SINGLETON = "singleton" # Single instance for entire application lifetime
39
+ TRANSIENT = "transient" # New instance for each resolution
40
+ SCOPED = "scoped" # Single instance per scope (e.g., request)
41
+
42
+
43
+ class ServiceDescriptor:
44
+ """Describes a registered service."""
45
+
46
+ def __init__(
47
+ self,
48
+ service_type: Type,
49
+ implementation: Optional[Type] = None,
50
+ instance: Optional[Any] = None,
51
+ factory: Optional[Callable] = None,
52
+ lifetime: ServiceLifetime = ServiceLifetime.SINGLETON,
53
+ ):
54
+ """
55
+ Initialize service descriptor.
56
+
57
+ Args:
58
+ service_type: The interface or base type
59
+ implementation: The concrete implementation class
60
+ instance: Pre-created instance (for singleton registration)
61
+ factory: Factory function to create instances
62
+ lifetime: Service lifetime management strategy
63
+ """
64
+ self.service_type = service_type
65
+ self.implementation = implementation
66
+ self.instance = instance
67
+ self.factory = factory
68
+ self.lifetime = lifetime
69
+ self.lock = threading.RLock() # For thread-safe singleton creation
70
+
71
+ # Validate descriptor
72
+ if not any([implementation, instance, factory]):
73
+ raise ValueError(
74
+ "Service descriptor must have either implementation, instance, or factory"
75
+ )
76
+
77
+
78
+ class CircularDependencyError(Exception):
79
+ """Raised when circular dependencies are detected."""
80
+
81
+ def __init__(self, resolution_chain: List[Type]):
82
+ self.resolution_chain = resolution_chain
83
+ chain_str = " -> ".join(t.__name__ for t in resolution_chain)
84
+ super().__init__(f"Circular dependency detected: {chain_str}")
85
+
86
+
87
+ class ServiceNotFoundError(Exception):
88
+ """Raised when a required service is not registered."""
89
+
90
+ def __init__(self, service_type: Type):
91
+ self.service_type = service_type
92
+ super().__init__(f"Service not registered: {service_type.__name__}")
93
+
94
+
95
+ class ServiceContainer:
96
+ """
97
+ Dependency injection container for service management.
98
+
99
+ This container provides:
100
+ - Service registration with lifetime management
101
+ - Automatic dependency resolution
102
+ - Circular dependency detection
103
+ - Thread-safe service creation
104
+ - Factory pattern support
105
+ """
106
+
107
+ def __init__(self):
108
+ """Initialize the service container."""
109
+ self.logger = get_logger("service_container")
110
+ self._services: Dict[Type, ServiceDescriptor] = {}
111
+ self._lock = threading.RLock()
112
+ self._resolution_stack: threading.local = threading.local()
113
+
114
+ # Register the container itself for self-injection scenarios
115
+ self.register_instance(ServiceContainer, self)
116
+
117
+ self.logger.debug("Service container initialized")
118
+
119
+ def register(
120
+ self,
121
+ service_type: Type[T],
122
+ implementation: Type[T],
123
+ lifetime: Union[ServiceLifetime, bool] = ServiceLifetime.SINGLETON,
124
+ ) -> None:
125
+ """
126
+ Register a service implementation.
127
+
128
+ Args:
129
+ service_type: The interface or base type to register
130
+ implementation: The concrete implementation class
131
+ lifetime: Service lifetime (ServiceLifetime enum or bool for backward compat)
132
+ If bool: True = SINGLETON, False = TRANSIENT
133
+
134
+ Example:
135
+ container.register(ICacheManager, CacheManager, ServiceLifetime.SINGLETON)
136
+ # or for backward compatibility:
137
+ container.register(ICacheManager, CacheManager, singleton=True)
138
+ """
139
+ # Handle backward compatibility for singleton parameter
140
+ if isinstance(lifetime, bool):
141
+ lifetime = (
142
+ ServiceLifetime.SINGLETON if lifetime else ServiceLifetime.TRANSIENT
143
+ )
144
+
145
+ with self._lock:
146
+ # Validate implementation
147
+ if not self._is_valid_implementation(service_type, implementation):
148
+ self.logger.warning(
149
+ f"Implementation {implementation.__name__} may not properly implement "
150
+ f"{service_type.__name__}"
151
+ )
152
+
153
+ descriptor = ServiceDescriptor(
154
+ service_type=service_type,
155
+ implementation=implementation,
156
+ lifetime=lifetime,
157
+ )
158
+
159
+ self._services[service_type] = descriptor
160
+
161
+ self.logger.debug(
162
+ f"Registered {implementation.__name__} for {service_type.__name__} "
163
+ f"with {lifetime.value} lifetime"
164
+ )
165
+
166
+ def register_instance(self, service_type: Type[T], instance: T) -> None:
167
+ """
168
+ Register a pre-created service instance (always singleton).
169
+
170
+ Args:
171
+ service_type: The interface or base type to register
172
+ instance: The pre-created instance
173
+
174
+ Example:
175
+ cache_manager = CacheManager()
176
+ container.register_instance(ICacheManager, cache_manager)
177
+ """
178
+ with self._lock:
179
+ descriptor = ServiceDescriptor(
180
+ service_type=service_type,
181
+ instance=instance,
182
+ lifetime=ServiceLifetime.SINGLETON,
183
+ )
184
+
185
+ self._services[service_type] = descriptor
186
+
187
+ self.logger.debug(
188
+ f"Registered instance of {instance.__class__.__name__} "
189
+ f"for {service_type.__name__}"
190
+ )
191
+
192
+ def register_factory(
193
+ self,
194
+ service_type: Type[T],
195
+ factory: Callable[[], T],
196
+ lifetime: ServiceLifetime = ServiceLifetime.TRANSIENT,
197
+ ) -> None:
198
+ """
199
+ Register a factory function for service creation.
200
+
201
+ Args:
202
+ service_type: The interface or base type to register
203
+ factory: Factory function that creates instances
204
+ lifetime: Service lifetime management strategy
205
+
206
+ Example:
207
+ def create_logger():
208
+ return Logger(config=get_config())
209
+
210
+ container.register_factory(ILogger, create_logger)
211
+ """
212
+ with self._lock:
213
+ descriptor = ServiceDescriptor(
214
+ service_type=service_type, factory=factory, lifetime=lifetime
215
+ )
216
+
217
+ self._services[service_type] = descriptor
218
+
219
+ self.logger.debug(
220
+ f"Registered factory for {service_type.__name__} "
221
+ f"with {lifetime.value} lifetime"
222
+ )
223
+
224
+ def resolve(self, service_type: Type[T]) -> T:
225
+ """
226
+ Resolve a service by type with automatic dependency injection.
227
+
228
+ Args:
229
+ service_type: The interface or type to resolve
230
+
231
+ Returns:
232
+ The resolved service instance
233
+
234
+ Raises:
235
+ ServiceNotFoundError: If the service is not registered
236
+ CircularDependencyError: If circular dependencies are detected
237
+
238
+ Example:
239
+ cache_manager = container.resolve(ICacheManager)
240
+ """
241
+ # Initialize resolution stack for this thread if needed
242
+ if not hasattr(self._resolution_stack, "stack"):
243
+ self._resolution_stack.stack = []
244
+
245
+ # Check for circular dependencies
246
+ if service_type in self._resolution_stack.stack:
247
+ raise CircularDependencyError(self._resolution_stack.stack + [service_type])
248
+
249
+ try:
250
+ # Add to resolution stack
251
+ self._resolution_stack.stack.append(service_type)
252
+
253
+ with self._lock:
254
+ # Check if service is registered
255
+ if service_type not in self._services:
256
+ raise ServiceNotFoundError(service_type)
257
+
258
+ descriptor = self._services[service_type]
259
+
260
+ # Handle different lifetime strategies
261
+ if descriptor.lifetime == ServiceLifetime.SINGLETON:
262
+ return self._resolve_singleton(descriptor)
263
+ if descriptor.lifetime == ServiceLifetime.TRANSIENT:
264
+ return self._resolve_transient(descriptor)
265
+ if descriptor.lifetime == ServiceLifetime.SCOPED:
266
+ # TODO: Implement scoped lifetime (requires scope context)
267
+ return self._resolve_transient(descriptor)
268
+ raise ValueError(f"Unknown lifetime: {descriptor.lifetime}")
269
+
270
+ finally:
271
+ # Remove from resolution stack
272
+ self._resolution_stack.stack.pop()
273
+
274
+ def resolve_all(self, service_type: Type[T]) -> List[T]:
275
+ """
276
+ Resolve all implementations of a service type.
277
+
278
+ Args:
279
+ service_type: The interface or base type to resolve
280
+
281
+ Returns:
282
+ List of all registered implementations
283
+
284
+ Example:
285
+ handlers = container.resolve_all(IEventHandler)
286
+ """
287
+ results = []
288
+
289
+ with self._lock:
290
+ for registered_type, descriptor in self._services.items():
291
+ # Check if registered type is subclass of requested type
292
+ if self._is_assignable(registered_type, service_type):
293
+ try:
294
+ instance = self.resolve(registered_type)
295
+ results.append(instance)
296
+ except Exception as e:
297
+ self.logger.warning(
298
+ f"Failed to resolve {registered_type.__name__}: {e}"
299
+ )
300
+
301
+ return results
302
+
303
+ def is_registered(self, service_type: Type) -> bool:
304
+ """
305
+ Check if a service type is registered.
306
+
307
+ Args:
308
+ service_type: The interface or type to check
309
+
310
+ Returns:
311
+ True if the service is registered, False otherwise
312
+
313
+ Example:
314
+ if container.is_registered(ICacheManager):
315
+ cache = container.resolve(ICacheManager)
316
+ """
317
+ with self._lock:
318
+ return service_type in self._services
319
+
320
+ def clear(self) -> None:
321
+ """Clear all registered services except the container itself."""
322
+ with self._lock:
323
+ # Keep reference to self registration
324
+ self_descriptor = self._services.get(ServiceContainer)
325
+
326
+ # Clear all services
327
+ self._services.clear()
328
+
329
+ # Re-register self if it was registered
330
+ if self_descriptor:
331
+ self._services[ServiceContainer] = self_descriptor
332
+
333
+ self.logger.debug("Service container cleared")
334
+
335
+ # Private helper methods
336
+
337
+ def _resolve_singleton(self, descriptor: ServiceDescriptor) -> Any:
338
+ """Resolve a singleton service."""
339
+ with descriptor.lock:
340
+ # Return existing instance if available
341
+ if descriptor.instance is not None:
342
+ return descriptor.instance
343
+
344
+ # Create new instance
345
+ if descriptor.factory:
346
+ instance = descriptor.factory()
347
+ elif descriptor.implementation:
348
+ instance = self._create_instance(descriptor.implementation)
349
+ else:
350
+ raise ValueError("No way to create service instance")
351
+
352
+ # Cache the instance
353
+ descriptor.instance = instance
354
+
355
+ return instance
356
+
357
+ def _resolve_transient(self, descriptor: ServiceDescriptor) -> Any:
358
+ """Resolve a transient service (new instance each time)."""
359
+ if descriptor.instance is not None:
360
+ # Pre-created instances are always returned as-is
361
+ return descriptor.instance
362
+
363
+ if descriptor.factory:
364
+ return descriptor.factory()
365
+ if descriptor.implementation:
366
+ return self._create_instance(descriptor.implementation)
367
+ raise ValueError("No way to create service instance")
368
+
369
+ def _create_instance(self, implementation: Type) -> Any:
370
+ """
371
+ Create an instance with automatic dependency injection.
372
+
373
+ Args:
374
+ implementation: The class to instantiate
375
+
376
+ Returns:
377
+ The created instance with injected dependencies
378
+ """
379
+ # Get constructor signature
380
+ try:
381
+ sig = inspect.signature(implementation.__init__)
382
+ except (ValueError, TypeError):
383
+ # Fallback for classes without proper __init__
384
+ return implementation()
385
+
386
+ # Resolve constructor dependencies
387
+ kwargs = {}
388
+ for param_name, param in sig.parameters.items():
389
+ # Skip 'self' parameter
390
+ if param_name == "self":
391
+ continue
392
+
393
+ # Get type hint for parameter
394
+ param_type = param.annotation
395
+
396
+ # Skip if no type hint or if it's not a class
397
+ if param_type == inspect.Parameter.empty:
398
+ # Use default value if available
399
+ if param.default != inspect.Parameter.empty:
400
+ kwargs[param_name] = param.default
401
+ continue
402
+
403
+ # Handle Optional types
404
+ origin = getattr(param_type, "__origin__", None)
405
+ if origin is Union:
406
+ # Get the non-None type from Optional[T]
407
+ args = getattr(param_type, "__args__", ())
408
+ non_none_types = [t for t in args if t != type(None)]
409
+ if non_none_types:
410
+ param_type = non_none_types[0]
411
+ else:
412
+ continue
413
+
414
+ # Try to resolve the dependency
415
+ if isinstance(param_type, type) and self.is_registered(param_type):
416
+ try:
417
+ kwargs[param_name] = self.resolve(param_type)
418
+ self.logger.debug(
419
+ f"Injected {param_type.__name__} into {implementation.__name__}.{param_name}"
420
+ )
421
+ except CircularDependencyError:
422
+ # Re-raise circular dependency errors to trigger proper handling
423
+ raise
424
+ except Exception as e:
425
+ self.logger.warning(
426
+ f"Failed to inject {param_type.__name__} into "
427
+ f"{implementation.__name__}.{param_name}: {e}"
428
+ )
429
+ # Use default value if available, otherwise fail if required
430
+ if param.default != inspect.Parameter.empty:
431
+ kwargs[param_name] = param.default
432
+ else:
433
+ # Required parameter with no default - can't create instance
434
+ raise
435
+ elif param.default != inspect.Parameter.empty:
436
+ # Use default value if available
437
+ kwargs[param_name] = param.default
438
+
439
+ # Create instance with resolved dependencies
440
+ return implementation(**kwargs)
441
+
442
+ def _is_valid_implementation(
443
+ self, service_type: Type, implementation: Type
444
+ ) -> bool:
445
+ """Check if implementation properly implements the service type."""
446
+ # If service_type is ABC, check if implementation has all abstract methods
447
+ if hasattr(service_type, "__abstractmethods__"):
448
+ abstract_methods = getattr(service_type, "__abstractmethods__", set())
449
+ for method in abstract_methods:
450
+ if not hasattr(implementation, method):
451
+ return False
452
+
453
+ # Check if implementation is subclass (for classes)
454
+ try:
455
+ if issubclass(implementation, service_type):
456
+ return True
457
+ except TypeError:
458
+ # Not classes, can't check subclass relationship
459
+ pass
460
+
461
+ return True # Assume valid if we can't determine otherwise
462
+
463
+ def _is_assignable(self, from_type: Type, to_type: Type) -> bool:
464
+ """Check if from_type can be assigned to to_type."""
465
+ try:
466
+ return issubclass(from_type, to_type)
467
+ except TypeError:
468
+ return from_type == to_type
469
+
470
+ def get_registration_info(self) -> Dict[str, Any]:
471
+ """
472
+ Get information about all registered services.
473
+
474
+ Returns:
475
+ Dictionary with registration information
476
+ """
477
+ info = {}
478
+
479
+ with self._lock:
480
+ for service_type, descriptor in self._services.items():
481
+ type_name = service_type.__name__
482
+
483
+ info[type_name] = {
484
+ "lifetime": descriptor.lifetime.value,
485
+ "has_instance": descriptor.instance is not None,
486
+ "has_factory": descriptor.factory is not None,
487
+ "implementation": (
488
+ descriptor.implementation.__name__
489
+ if descriptor.implementation
490
+ else None
491
+ ),
492
+ }
493
+
494
+ return info
495
+
496
+
497
+ # Global container instance (optional, for convenience)
498
+ _global_container: Optional[ServiceContainer] = None
499
+ _global_lock = threading.Lock()
500
+
501
+
502
+ def get_global_container() -> ServiceContainer:
503
+ """
504
+ Get or create the global service container.
505
+
506
+ Returns:
507
+ The global service container instance
508
+
509
+ Example:
510
+ container = get_global_container()
511
+ container.register(IMyService, MyService)
512
+ """
513
+ global _global_container
514
+
515
+ if _global_container is None:
516
+ with _global_lock:
517
+ if _global_container is None:
518
+ _global_container = ServiceContainer()
519
+
520
+ return _global_container