claude-mpm 3.8.1__py3-none-any.whl → 3.9.2__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 (33) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +59 -135
  3. claude_mpm/agents/MEMORY.md +39 -30
  4. claude_mpm/agents/WORKFLOW.md +54 -4
  5. claude_mpm/agents/agents_metadata.py +25 -1
  6. claude_mpm/agents/schema/agent_schema.json +1 -1
  7. claude_mpm/agents/templates/backup/research_agent_2025011_234551.json +88 -0
  8. claude_mpm/agents/templates/project_organizer.json +178 -0
  9. claude_mpm/agents/templates/research.json +33 -30
  10. claude_mpm/agents/templates/ticketing.json +3 -3
  11. claude_mpm/cli/commands/agents.py +8 -3
  12. claude_mpm/core/claude_runner.py +31 -10
  13. claude_mpm/core/config.py +2 -2
  14. claude_mpm/core/container.py +96 -25
  15. claude_mpm/core/framework_loader.py +43 -1
  16. claude_mpm/core/interactive_session.py +47 -0
  17. claude_mpm/hooks/claude_hooks/hook_handler_fixed.py +454 -0
  18. claude_mpm/services/agents/deployment/agent_deployment.py +144 -43
  19. claude_mpm/services/agents/memory/agent_memory_manager.py +4 -3
  20. claude_mpm/services/framework_claude_md_generator/__init__.py +10 -3
  21. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +14 -11
  22. claude_mpm/services/response_tracker.py +3 -5
  23. claude_mpm/services/ticket_manager.py +2 -2
  24. claude_mpm/services/ticket_manager_di.py +1 -1
  25. claude_mpm/services/version_control/semantic_versioning.py +80 -7
  26. claude_mpm/services/version_control/version_parser.py +528 -0
  27. claude_mpm-3.9.2.dist-info/METADATA +200 -0
  28. {claude_mpm-3.8.1.dist-info → claude_mpm-3.9.2.dist-info}/RECORD +32 -28
  29. claude_mpm-3.8.1.dist-info/METADATA +0 -327
  30. {claude_mpm-3.8.1.dist-info → claude_mpm-3.9.2.dist-info}/WHEEL +0 -0
  31. {claude_mpm-3.8.1.dist-info → claude_mpm-3.9.2.dist-info}/entry_points.txt +0 -0
  32. {claude_mpm-3.8.1.dist-info → claude_mpm-3.9.2.dist-info}/licenses/LICENSE +0 -0
  33. {claude_mpm-3.8.1.dist-info → claude_mpm-3.9.2.dist-info}/top_level.txt +0 -0
@@ -18,6 +18,7 @@ from enum import Enum
18
18
  from typing import Any, Callable, Dict, List, Optional, Set, Type, TypeVar, Union
19
19
 
20
20
  from .logger import get_logger
21
+ from claude_mpm.services.core.interfaces import IServiceContainer
21
22
 
22
23
  logger = get_logger(__name__)
23
24
 
@@ -144,10 +145,13 @@ class ServiceScope:
144
145
  self.dispose()
145
146
 
146
147
 
147
- class DIContainer:
148
+ class DIContainer(IServiceContainer):
148
149
  """
149
150
  Enhanced Dependency Injection Container.
150
151
 
152
+ Implements IServiceContainer interface to provide a complete
153
+ dependency injection solution.
154
+
151
155
  Provides:
152
156
  - Service registration with multiple lifetime options
153
157
  - Automatic constructor injection
@@ -174,6 +178,34 @@ class DIContainer:
174
178
  self._current_scope: Optional[ServiceScope] = None
175
179
 
176
180
  def register(
181
+ self,
182
+ service_type: type,
183
+ implementation: type,
184
+ singleton: bool = True
185
+ ) -> None:
186
+ """
187
+ Register a service implementation (IServiceContainer interface method).
188
+
189
+ Args:
190
+ service_type: The interface/base type to register
191
+ implementation: The concrete implementation class
192
+ singleton: Whether to use singleton lifetime (default True)
193
+
194
+ Examples:
195
+ # Register interface with implementation
196
+ container.register(ILogger, ConsoleLogger)
197
+
198
+ # Register as transient (new instance each time)
199
+ container.register(IService, ServiceImpl, singleton=False)
200
+ """
201
+ lifetime = ServiceLifetime.SINGLETON if singleton else ServiceLifetime.TRANSIENT
202
+ self._register_internal(
203
+ service_type=service_type,
204
+ implementation=implementation,
205
+ lifetime=lifetime
206
+ )
207
+
208
+ def _register_internal(
177
209
  self,
178
210
  service_type: Type[T],
179
211
  implementation: Optional[Union[Type[T], Callable[..., T]]] = None,
@@ -183,7 +215,7 @@ class DIContainer:
183
215
  dependencies: Optional[Dict[str, Type]] = None
184
216
  ) -> None:
185
217
  """
186
- Register a service in the container.
218
+ Internal registration method with full flexibility.
187
219
 
188
220
  Args:
189
221
  service_type: The interface/base type to register
@@ -192,19 +224,6 @@ class DIContainer:
192
224
  factory: Optional factory function
193
225
  instance: Pre-created instance (for singleton)
194
226
  dependencies: Explicit dependency mapping for constructor params
195
-
196
- Examples:
197
- # Register interface with implementation
198
- container.register(ILogger, ConsoleLogger)
199
-
200
- # Register with factory
201
- container.register(IDatabase, factory=lambda c: Database(c.resolve(IConfig)))
202
-
203
- # Register singleton instance
204
- container.register(IConfig, instance=Config())
205
-
206
- # Register with explicit dependencies
207
- container.register(IService, ServiceImpl, dependencies={'logger': ILogger})
208
227
  """
209
228
  with self._lock:
210
229
  registration = ServiceRegistration(
@@ -220,6 +239,50 @@ class DIContainer:
220
239
  # If instance provided, store as singleton
221
240
  if instance is not None:
222
241
  self._singletons[service_type] = instance
242
+
243
+ def register_instance(self, service_type: type, instance: Any) -> None:
244
+ """
245
+ Register a service instance (IServiceContainer interface method).
246
+
247
+ Args:
248
+ service_type: The interface/base type to register
249
+ instance: Pre-created instance to register as singleton
250
+
251
+ Examples:
252
+ # Register a pre-created instance
253
+ config = Config()
254
+ container.register_instance(IConfig, config)
255
+ """
256
+ self._register_internal(
257
+ service_type=service_type,
258
+ instance=instance,
259
+ lifetime=ServiceLifetime.SINGLETON
260
+ )
261
+
262
+ def resolve_all(self, service_type: type) -> List[Any]:
263
+ """
264
+ Resolve all implementations of a service type (IServiceContainer interface method).
265
+
266
+ Args:
267
+ service_type: The type to resolve
268
+
269
+ Returns:
270
+ List of all registered implementations for the service type
271
+
272
+ Examples:
273
+ # Register multiple implementations
274
+ container.register(IPlugin, PluginA)
275
+ container.register(IPlugin, PluginB)
276
+
277
+ # Get all implementations
278
+ plugins = container.resolve_all(IPlugin)
279
+ """
280
+ with self._lock:
281
+ # For now, return a list with the single registered implementation
282
+ # In the future, we could support multiple registrations for the same type
283
+ if service_type in self._registrations:
284
+ return [self._resolve_internal(service_type)]
285
+ return []
223
286
 
224
287
  def register_singleton(
225
288
  self,
@@ -274,12 +337,12 @@ class DIContainer:
274
337
  else:
275
338
  # Normal registration without name
276
339
  if instance is not None:
277
- self.register(interface, instance=instance)
340
+ self._register_internal(interface, instance=instance)
278
341
  elif implementation is not None and not inspect.isclass(implementation):
279
342
  # It's an instance passed as implementation (backward compatibility)
280
- self.register(interface, instance=implementation)
343
+ self._register_internal(interface, instance=implementation)
281
344
  else:
282
- self.register(interface, implementation, lifetime=ServiceLifetime.SINGLETON)
345
+ self._register_internal(interface, implementation, lifetime=ServiceLifetime.SINGLETON)
283
346
 
284
347
  # Handle disposal handler
285
348
  if dispose_handler:
@@ -321,7 +384,7 @@ class DIContainer:
321
384
  context = container.get(IRequestContext) # Created
322
385
  context2 = container.get(IRequestContext) # Same instance
323
386
  """
324
- self.register(interface, implementation, lifetime=ServiceLifetime.SCOPED)
387
+ self._register_internal(interface, implementation, lifetime=ServiceLifetime.SCOPED)
325
388
  if name:
326
389
  self._named_registrations[name] = self._registrations[interface]
327
390
 
@@ -335,7 +398,7 @@ class DIContainer:
335
398
 
336
399
  Convenience method for registering transient services.
337
400
  """
338
- self.register(service_type, implementation, lifetime=ServiceLifetime.TRANSIENT)
401
+ self._register_internal(service_type, implementation, lifetime=ServiceLifetime.TRANSIENT)
339
402
 
340
403
  def register_factory(
341
404
  self,
@@ -363,7 +426,7 @@ class DIContainer:
363
426
  lifetime=ServiceLifetime.SINGLETON
364
427
  )
365
428
  """
366
- self.register(interface, factory=factory, lifetime=lifetime)
429
+ self._register_internal(interface, factory=factory, lifetime=lifetime)
367
430
  self._factories[interface] = factory
368
431
  if name:
369
432
  self._named_registrations[name] = self._registrations[interface]
@@ -551,6 +614,12 @@ class DIContainer:
551
614
  module = frame.f_globals
552
615
  if param_type in module:
553
616
  param_type = module[param_type]
617
+ else:
618
+ # Try looking in registrations by name
619
+ for reg_type in self._registrations:
620
+ if reg_type.__name__ == param_type:
621
+ param_type = reg_type
622
+ break
554
623
  except:
555
624
  # If we can't resolve, skip this parameter
556
625
  if param.default != param.empty:
@@ -564,11 +633,13 @@ class DIContainer:
564
633
  param_type = next((arg for arg in args if arg is not type(None)), None)
565
634
 
566
635
  if param_type and param_type in self._registrations:
567
- # Avoid circular dependency by checking if we're already resolving this type
568
- if param_type not in self._resolving:
636
+ # Check for circular dependency
637
+ if param_type in self._resolving:
638
+ # Circular dependency detected
639
+ cycle = " -> ".join(str(t.__name__) for t in self._resolving) + f" -> {param_type.__name__}"
640
+ raise CircularDependencyError(f"Circular dependency detected: {cycle}")
641
+ else:
569
642
  kwargs[param_name] = self.resolve(param_type)
570
- elif param.default != param.empty:
571
- kwargs[param_name] = param.default
572
643
  elif param.default != param.empty:
573
644
  # Use default value
574
645
  kwargs[param_name] = param.default
@@ -204,6 +204,37 @@ class FrameworkLoader:
204
204
  content["project_workflow"] = "system"
205
205
  self.logger.info("Using system WORKFLOW.md")
206
206
 
207
+ def _load_memory_instructions(self, content: Dict[str, Any]) -> None:
208
+ """
209
+ Load MEMORY.md with project-specific override support.
210
+
211
+ Precedence:
212
+ 1. Project-specific: .claude-mpm/agents/MEMORY.md
213
+ 2. System default: src/claude_mpm/agents/MEMORY.md
214
+
215
+ Args:
216
+ content: Dictionary to update with memory instructions
217
+ """
218
+ # Check for project-specific memory instructions first
219
+ project_memory_path = Path.cwd() / ".claude-mpm" / "agents" / "MEMORY.md"
220
+ if project_memory_path.exists():
221
+ loaded_content = self._try_load_file(project_memory_path, "project-specific MEMORY.md")
222
+ if loaded_content:
223
+ content["memory_instructions"] = loaded_content
224
+ content["project_memory"] = "project"
225
+ self.logger.info("Using project-specific MEMORY.md")
226
+ return
227
+
228
+ # Fall back to system memory instructions
229
+ if self.framework_path:
230
+ system_memory_path = self.framework_path / "src" / "claude_mpm" / "agents" / "MEMORY.md"
231
+ if system_memory_path.exists():
232
+ loaded_content = self._try_load_file(system_memory_path, "system MEMORY.md")
233
+ if loaded_content:
234
+ content["memory_instructions"] = loaded_content
235
+ content["project_memory"] = "system"
236
+ self.logger.info("Using system MEMORY.md")
237
+
207
238
  def _load_single_agent(self, agent_file: Path) -> tuple[Optional[str], Optional[str]]:
208
239
  """
209
240
  Load a single agent file.
@@ -277,7 +308,9 @@ class FrameworkLoader:
277
308
  "working_claude_md": "",
278
309
  "framework_instructions": "",
279
310
  "workflow_instructions": "",
280
- "project_workflow": ""
311
+ "project_workflow": "",
312
+ "memory_instructions": "",
313
+ "project_memory": ""
281
314
  }
282
315
 
283
316
  # Load instructions file from working directory
@@ -311,6 +344,9 @@ class FrameworkLoader:
311
344
  # Load WORKFLOW.md - check for project-specific first, then system
312
345
  self._load_workflow_instructions(content)
313
346
 
347
+ # Load MEMORY.md - check for project-specific first, then system
348
+ self._load_memory_instructions(content)
349
+
314
350
  # Discover agent directories
315
351
  agents_dir, templates_dir, main_dir = self._discover_framework_paths()
316
352
 
@@ -364,6 +400,12 @@ class FrameworkLoader:
364
400
  instructions += f"\n\n{workflow_content}\n"
365
401
  # Note: project-specific workflow is being used (logged elsewhere)
366
402
 
403
+ # Add MEMORY.md after workflow instructions
404
+ if self.framework_content.get("memory_instructions"):
405
+ memory_content = self._strip_metadata_comments(self.framework_content['memory_instructions'])
406
+ instructions += f"\n\n{memory_content}\n"
407
+ # Note: project-specific memory instructions being used (logged elsewhere)
408
+
367
409
  # Add dynamic agent capabilities section
368
410
  instructions += self._generate_agent_capabilities_section()
369
411
 
@@ -40,6 +40,29 @@ class InteractiveSession:
40
40
  self.session_id = None
41
41
  self.original_cwd = os.getcwd()
42
42
 
43
+ # Initialize response tracking for interactive sessions
44
+ # WHY: Interactive sessions need response logging just like oneshot sessions.
45
+ # The hook system captures events, but we need the ResponseTracker to be
46
+ # initialized to actually store them.
47
+ self.response_tracker = None
48
+
49
+ # Check if response logging is enabled in configuration
50
+ try:
51
+ response_config = self.runner.config.get('response_logging', {})
52
+ response_logging_enabled = response_config.get('enabled', False)
53
+ except (AttributeError, TypeError):
54
+ # Handle mock or missing config gracefully
55
+ response_logging_enabled = False
56
+
57
+ if response_logging_enabled:
58
+ try:
59
+ from claude_mpm.services.response_tracker import ResponseTracker
60
+ self.response_tracker = ResponseTracker(self.runner.config)
61
+ self.logger.info("Response tracking initialized for interactive session")
62
+ except Exception as e:
63
+ self.logger.warning(f"Failed to initialize response tracker: {e}")
64
+ # Continue without response tracking - not fatal
65
+
43
66
  def initialize_interactive_session(self) -> Tuple[bool, Optional[str]]:
44
67
  """Initialize the interactive session environment.
45
68
 
@@ -71,6 +94,18 @@ class InteractiveSession:
71
94
  component="session"
72
95
  )
73
96
 
97
+ # Initialize response tracking session if enabled
98
+ # WHY: The ResponseTracker needs to know about the session to properly
99
+ # correlate prompts and responses during the interactive session.
100
+ if self.response_tracker and self.response_tracker.enabled:
101
+ try:
102
+ # Set the session ID in the tracker for correlation
103
+ if hasattr(self.response_tracker, 'session_logger') and self.response_tracker.session_logger:
104
+ self.response_tracker.session_logger.set_session_id(self.session_id)
105
+ self.logger.debug(f"Response tracker session ID set to: {self.session_id}")
106
+ except Exception as e:
107
+ self.logger.debug(f"Could not set session ID in response tracker: {e}")
108
+
74
109
  return True, None
75
110
 
76
111
  except Exception as e:
@@ -213,6 +248,18 @@ class InteractiveSession:
213
248
  "event": "session_end",
214
249
  "session_id": self.session_id
215
250
  })
251
+
252
+ # Clean up response tracker if initialized
253
+ # WHY: Ensure proper cleanup of response tracking resources and
254
+ # finalize any pending response logs.
255
+ if self.response_tracker:
256
+ try:
257
+ # Clear the session ID to stop tracking this session
258
+ if hasattr(self.response_tracker, 'session_logger') and self.response_tracker.session_logger:
259
+ self.response_tracker.session_logger.set_session_id(None)
260
+ self.logger.debug("Response tracker session cleared")
261
+ except Exception as e:
262
+ self.logger.debug(f"Error clearing response tracker session: {e}")
216
263
 
217
264
  except Exception as e:
218
265
  self.logger.debug(f"Error during cleanup: {e}")