claude-mpm 4.0.23__py3-none-any.whl → 4.0.25__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 (60) hide show
  1. claude_mpm/BUILD_NUMBER +1 -1
  2. claude_mpm/VERSION +1 -1
  3. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +4 -1
  4. claude_mpm/agents/BASE_PM.md +3 -0
  5. claude_mpm/agents/templates/code_analyzer.json +2 -2
  6. claude_mpm/cli/commands/agents.py +453 -113
  7. claude_mpm/cli/commands/aggregate.py +107 -15
  8. claude_mpm/cli/commands/cleanup.py +142 -10
  9. claude_mpm/cli/commands/config.py +358 -224
  10. claude_mpm/cli/commands/info.py +184 -75
  11. claude_mpm/cli/commands/mcp_command_router.py +5 -76
  12. claude_mpm/cli/commands/mcp_install_commands.py +68 -36
  13. claude_mpm/cli/commands/mcp_server_commands.py +30 -37
  14. claude_mpm/cli/commands/memory.py +331 -61
  15. claude_mpm/cli/commands/monitor.py +101 -7
  16. claude_mpm/cli/commands/run.py +368 -8
  17. claude_mpm/cli/commands/tickets.py +206 -24
  18. claude_mpm/cli/parsers/mcp_parser.py +3 -0
  19. claude_mpm/cli/shared/__init__.py +40 -0
  20. claude_mpm/cli/shared/argument_patterns.py +212 -0
  21. claude_mpm/cli/shared/command_base.py +234 -0
  22. claude_mpm/cli/shared/error_handling.py +238 -0
  23. claude_mpm/cli/shared/output_formatters.py +231 -0
  24. claude_mpm/config/agent_config.py +29 -8
  25. claude_mpm/core/container.py +6 -4
  26. claude_mpm/core/service_registry.py +4 -2
  27. claude_mpm/core/shared/__init__.py +17 -0
  28. claude_mpm/core/shared/config_loader.py +320 -0
  29. claude_mpm/core/shared/path_resolver.py +277 -0
  30. claude_mpm/core/shared/singleton_manager.py +208 -0
  31. claude_mpm/hooks/claude_hooks/memory_integration.py +4 -2
  32. claude_mpm/hooks/claude_hooks/response_tracking.py +14 -3
  33. claude_mpm/hooks/memory_integration_hook.py +11 -2
  34. claude_mpm/services/agents/deployment/agent_deployment.py +43 -23
  35. claude_mpm/services/agents/deployment/deployment_wrapper.py +71 -0
  36. claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +1 -0
  37. claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +43 -0
  38. claude_mpm/services/agents/deployment/processors/agent_deployment_context.py +4 -0
  39. claude_mpm/services/agents/deployment/processors/agent_processor.py +1 -1
  40. claude_mpm/services/agents/loading/base_agent_manager.py +11 -3
  41. claude_mpm/services/agents/registry/deployed_agent_discovery.py +14 -5
  42. claude_mpm/services/event_aggregator.py +4 -2
  43. claude_mpm/services/mcp_gateway/config/config_loader.py +89 -28
  44. claude_mpm/services/mcp_gateway/config/configuration.py +29 -0
  45. claude_mpm/services/mcp_gateway/registry/service_registry.py +22 -5
  46. claude_mpm/services/memory/builder.py +6 -1
  47. claude_mpm/services/response_tracker.py +3 -1
  48. claude_mpm/services/runner_configuration_service.py +15 -6
  49. claude_mpm/services/shared/__init__.py +20 -0
  50. claude_mpm/services/shared/async_service_base.py +219 -0
  51. claude_mpm/services/shared/config_service_base.py +292 -0
  52. claude_mpm/services/shared/lifecycle_service_base.py +317 -0
  53. claude_mpm/services/shared/manager_base.py +303 -0
  54. claude_mpm/services/shared/service_factory.py +308 -0
  55. {claude_mpm-4.0.23.dist-info → claude_mpm-4.0.25.dist-info}/METADATA +19 -13
  56. {claude_mpm-4.0.23.dist-info → claude_mpm-4.0.25.dist-info}/RECORD +60 -44
  57. {claude_mpm-4.0.23.dist-info → claude_mpm-4.0.25.dist-info}/WHEEL +0 -0
  58. {claude_mpm-4.0.23.dist-info → claude_mpm-4.0.25.dist-info}/entry_points.txt +0 -0
  59. {claude_mpm-4.0.23.dist-info → claude_mpm-4.0.25.dist-info}/licenses/LICENSE +0 -0
  60. {claude_mpm-4.0.23.dist-info → claude_mpm-4.0.25.dist-info}/top_level.txt +0 -0
@@ -10,6 +10,8 @@ This module handles:
10
10
  - Environment variable support for agent paths
11
11
  - Project-specific agent overrides
12
12
  - Tier precedence configuration
13
+
14
+ UPDATED: Migrated to use shared ConfigLoader pattern (TSK-0141)
13
15
  """
14
16
 
15
17
  import json
@@ -21,6 +23,7 @@ from pathlib import Path
21
23
  from typing import Any, Dict, List, Optional
22
24
 
23
25
  from claude_mpm.core.unified_paths import get_path_manager
26
+ from claude_mpm.core.shared.config_loader import ConfigLoader, ConfigPattern
24
27
 
25
28
  logger = logging.getLogger(__name__)
26
29
 
@@ -62,6 +65,9 @@ class AgentConfig:
62
65
  validate_on_load: bool = True
63
66
  strict_validation: bool = False
64
67
 
68
+ # ConfigLoader instance for consistent configuration loading
69
+ _config_loader: ConfigLoader = field(default_factory=ConfigLoader, init=False)
70
+
65
71
  @classmethod
66
72
  def from_environment(cls) -> "AgentConfig":
67
73
  """
@@ -147,14 +153,29 @@ class AgentConfig:
147
153
  return cls()
148
154
 
149
155
  try:
150
- if config_file.suffix in [".yaml", ".yml"]:
151
- import yaml
152
-
153
- with open(config_file, "r") as f:
154
- data = yaml.safe_load(f)
155
- else:
156
- with open(config_file, "r") as f:
157
- data = json.load(f)
156
+ # Use ConfigLoader for consistent file loading
157
+ config_loader = ConfigLoader()
158
+
159
+ # Create a pattern for agent configuration
160
+ pattern = ConfigPattern(
161
+ filenames=[config_file.name],
162
+ search_paths=[str(config_file.parent)],
163
+ env_prefix="CLAUDE_MPM_AGENT_",
164
+ defaults={
165
+ "enable_project_agents": True,
166
+ "enable_user_agents": True,
167
+ "enable_system_agents": True,
168
+ "enable_hot_reload": True,
169
+ "cache_ttl_seconds": 3600,
170
+ "enable_caching": True,
171
+ "validate_on_load": True,
172
+ "strict_validation": False,
173
+ "precedence_mode": "override"
174
+ }
175
+ )
176
+
177
+ loaded_config = config_loader.load_config(pattern, cache_key=f"agent_{config_file}")
178
+ data = loaded_config.to_dict()
158
179
 
159
180
  config = cls()
160
181
 
@@ -250,8 +250,9 @@ class DIContainer(IServiceContainer):
250
250
  instance: Pre-created instance to register as singleton
251
251
 
252
252
  Examples:
253
- # Register a pre-created instance
254
- config = Config()
253
+ # Register a pre-created instance using ConfigLoader
254
+ config_loader = ConfigLoader()
255
+ config = config_loader.load_main_config()
255
256
  container.register_instance(IConfig, config)
256
257
  """
257
258
  self._register_internal(
@@ -307,8 +308,9 @@ class DIContainer(IServiceContainer):
307
308
  # Register with implementation class
308
309
  container.register_singleton(ILogger, ConsoleLogger)
309
310
 
310
- # Register with instance
311
- container.register_singleton(IConfig, instance=Config())
311
+ # Register with instance using ConfigLoader
312
+ config_loader = ConfigLoader()
313
+ container.register_singleton(IConfig, instance=config_loader.load_main_config())
312
314
 
313
315
  # Register with disposal handler
314
316
  container.register_singleton(
@@ -11,6 +11,7 @@ from .base_service import BaseService
11
11
  from .config import Config
12
12
  from .container import DIContainer, ServiceLifetime, get_container
13
13
  from .logger import get_logger
14
+ from .shared.config_loader import ConfigLoader
14
15
 
15
16
  if TYPE_CHECKING:
16
17
  from claude_mpm.services.agents.deployment import AgentDeploymentService
@@ -46,8 +47,9 @@ class ServiceRegistry:
46
47
  from .agent_session_manager import AgentSessionManager
47
48
  from .session_manager import SessionManager
48
49
 
49
- # Register configuration as singleton with name
50
- config = Config()
50
+ # Register configuration as singleton with name using ConfigLoader
51
+ config_loader = ConfigLoader()
52
+ config = config_loader.load_main_config()
51
53
  self.container.register_singleton(Config, instance=config, name="main_config")
52
54
 
53
55
  # Register core services with proper lifetime management
@@ -0,0 +1,17 @@
1
+ """
2
+ Shared core utilities to reduce code duplication.
3
+
4
+ This module provides common utilities that can be used across
5
+ different parts of the application.
6
+ """
7
+
8
+ from .config_loader import ConfigLoader, ConfigPattern
9
+ from .path_resolver import PathResolver
10
+ from .singleton_manager import SingletonManager
11
+
12
+ __all__ = [
13
+ "ConfigLoader",
14
+ "ConfigPattern",
15
+ "PathResolver",
16
+ "SingletonManager",
17
+ ]
@@ -0,0 +1,320 @@
1
+ """
2
+ Shared configuration loading utilities to reduce duplication.
3
+ """
4
+
5
+ import os
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+ from typing import Any, Dict, List, Optional, Union
9
+
10
+ from ..config import Config
11
+ from ..logger import get_logger
12
+
13
+
14
+ @dataclass
15
+ class ConfigPattern:
16
+ """Configuration loading pattern definition."""
17
+
18
+ # File patterns to search for
19
+ filenames: List[str]
20
+
21
+ # Search paths (relative to working directory)
22
+ search_paths: List[str]
23
+
24
+ # Environment variable prefix
25
+ env_prefix: Optional[str] = None
26
+
27
+ # Default values
28
+ defaults: Optional[Dict[str, Any]] = None
29
+
30
+ # Required configuration keys
31
+ required_keys: Optional[List[str]] = None
32
+
33
+ # Configuration section name
34
+ section: Optional[str] = None
35
+
36
+
37
+ class ConfigLoader:
38
+ """
39
+ Centralized configuration loading utility.
40
+
41
+ Reduces duplication by providing standard patterns for:
42
+ - Configuration file discovery
43
+ - Environment variable loading
44
+ - Default value management
45
+ - Configuration validation
46
+ """
47
+
48
+ # Standard configuration patterns
49
+ AGENT_CONFIG = ConfigPattern(
50
+ filenames=[".agent.yaml", ".agent.yml", "agent.yaml", "agent.yml"],
51
+ search_paths=[".", ".claude-mpm", "agents"],
52
+ env_prefix="CLAUDE_MPM_AGENT_",
53
+ defaults={
54
+ "timeout": 30,
55
+ "max_retries": 3,
56
+ "log_level": "INFO"
57
+ },
58
+ required_keys=["name", "model"]
59
+ )
60
+
61
+ MEMORY_CONFIG = ConfigPattern(
62
+ filenames=[".memory.yaml", ".memory.yml", "memory.yaml", "memory.yml"],
63
+ search_paths=[".", ".claude-mpm", "memories"],
64
+ env_prefix="CLAUDE_MPM_MEMORY_",
65
+ defaults={
66
+ "max_entries": 1000,
67
+ "cleanup_interval": 3600,
68
+ "compression": True
69
+ }
70
+ )
71
+
72
+ SERVICE_CONFIG = ConfigPattern(
73
+ filenames=[".service.yaml", ".service.yml", "service.yaml", "service.yml"],
74
+ search_paths=[".", ".claude-mpm", "config"],
75
+ env_prefix="CLAUDE_MPM_SERVICE_",
76
+ defaults={
77
+ "enabled": True,
78
+ "auto_start": False,
79
+ "health_check_interval": 60
80
+ }
81
+ )
82
+
83
+ def __init__(self, working_dir: Union[str, Path] = None):
84
+ """
85
+ Initialize config loader.
86
+
87
+ Args:
88
+ working_dir: Working directory for relative paths
89
+ """
90
+ self.working_dir = Path(working_dir) if working_dir else Path.cwd()
91
+ self.logger = get_logger("config_loader")
92
+ self._cache: Dict[str, Config] = {}
93
+
94
+ def load_config(self,
95
+ pattern: ConfigPattern,
96
+ cache_key: str = None,
97
+ force_reload: bool = False) -> Config:
98
+ """
99
+ Load configuration using a pattern.
100
+
101
+ Args:
102
+ pattern: Configuration pattern to use
103
+ cache_key: Optional cache key (defaults to pattern hash)
104
+ force_reload: Force reload even if cached
105
+
106
+ Returns:
107
+ Loaded configuration
108
+ """
109
+ # Generate cache key if not provided
110
+ if cache_key is None:
111
+ cache_key = self._generate_cache_key(pattern)
112
+
113
+ # Check cache
114
+ if not force_reload and cache_key in self._cache:
115
+ self.logger.debug(f"Using cached config: {cache_key}")
116
+ return self._cache[cache_key]
117
+
118
+ # Start with defaults
119
+ config_data = pattern.defaults.copy() if pattern.defaults else {}
120
+
121
+ # Load from files
122
+ config_file = self._find_config_file(pattern)
123
+ if config_file:
124
+ file_config = self._load_config_file(config_file)
125
+ if pattern.section:
126
+ file_config = file_config.get(pattern.section, {})
127
+ config_data.update(file_config)
128
+ self.logger.info(f"Loaded config from: {config_file}")
129
+
130
+ # Load from environment
131
+ if pattern.env_prefix:
132
+ env_config = self._load_env_config(pattern.env_prefix)
133
+ config_data.update(env_config)
134
+ if env_config:
135
+ self.logger.debug(f"Loaded {len(env_config)} env vars with prefix {pattern.env_prefix}")
136
+
137
+ # Create config instance
138
+ config = Config(config_data)
139
+
140
+ # Validate required keys
141
+ if pattern.required_keys:
142
+ self._validate_required_keys(config, pattern.required_keys)
143
+
144
+ # Cache the result
145
+ self._cache[cache_key] = config
146
+
147
+ return config
148
+
149
+ def load_agent_config(self, agent_dir: Union[str, Path] = None) -> Config:
150
+ """Load agent configuration."""
151
+ pattern = self.AGENT_CONFIG
152
+ if agent_dir:
153
+ # Override search paths for specific agent directory
154
+ pattern = ConfigPattern(
155
+ filenames=pattern.filenames,
156
+ search_paths=[str(agent_dir)],
157
+ env_prefix=pattern.env_prefix,
158
+ defaults=pattern.defaults,
159
+ required_keys=pattern.required_keys
160
+ )
161
+
162
+ return self.load_config(pattern, cache_key=f"agent_{agent_dir}")
163
+
164
+ def load_main_config(self) -> Config:
165
+ """Load main application configuration."""
166
+ pattern = ConfigPattern(
167
+ filenames=["claude-mpm.yaml", "claude-mpm.yml", ".claude-mpm.yaml", ".claude-mpm.yml", "config.yaml", "config.yml"],
168
+ search_paths=["~/.config/claude-mpm", ".", "./config", "/etc/claude-mpm"],
169
+ env_prefix="CLAUDE_MPM_",
170
+ defaults={}
171
+ )
172
+ return self.load_config(pattern, cache_key="main_config")
173
+
174
+ def load_memory_config(self, memory_dir: Union[str, Path] = None) -> Config:
175
+ """Load memory configuration."""
176
+ pattern = self.MEMORY_CONFIG
177
+ if memory_dir:
178
+ pattern = ConfigPattern(
179
+ filenames=pattern.filenames,
180
+ search_paths=[str(memory_dir)],
181
+ env_prefix=pattern.env_prefix,
182
+ defaults=pattern.defaults
183
+ )
184
+
185
+ return self.load_config(pattern, cache_key=f"memory_{memory_dir}")
186
+
187
+ def load_service_config(self, service_name: str, config_dir: Union[str, Path] = None) -> Config:
188
+ """Load service configuration."""
189
+ pattern = self.SERVICE_CONFIG
190
+
191
+ # Add service-specific filenames
192
+ service_filenames = [
193
+ f".{service_name}.yaml",
194
+ f".{service_name}.yml",
195
+ f"{service_name}.yaml",
196
+ f"{service_name}.yml"
197
+ ]
198
+
199
+ pattern = ConfigPattern(
200
+ filenames=service_filenames + pattern.filenames,
201
+ search_paths=[str(config_dir)] if config_dir else pattern.search_paths,
202
+ env_prefix=f"CLAUDE_MPM_{service_name.upper()}_",
203
+ defaults=pattern.defaults,
204
+ section=service_name
205
+ )
206
+
207
+ return self.load_config(pattern, cache_key=f"service_{service_name}")
208
+
209
+ def _find_config_file(self, pattern: ConfigPattern) -> Optional[Path]:
210
+ """Find configuration file using pattern."""
211
+ for search_path in pattern.search_paths:
212
+ search_dir = self.working_dir / search_path
213
+ if not search_dir.exists():
214
+ continue
215
+
216
+ for filename in pattern.filenames:
217
+ config_file = search_dir / filename
218
+ if config_file.exists() and config_file.is_file():
219
+ return config_file
220
+
221
+ return None
222
+
223
+ def _load_config_file(self, config_file: Path) -> Dict[str, Any]:
224
+ """Load configuration from file."""
225
+ try:
226
+ import yaml
227
+
228
+ with open(config_file, 'r') as f:
229
+ if config_file.suffix.lower() in ('.yaml', '.yml'):
230
+ return yaml.safe_load(f) or {}
231
+ else:
232
+ # Try JSON as fallback
233
+ import json
234
+ f.seek(0)
235
+ return json.load(f)
236
+
237
+ except Exception as e:
238
+ self.logger.error(f"Failed to load config file {config_file}: {e}")
239
+ return {}
240
+
241
+ def _load_env_config(self, prefix: str) -> Dict[str, Any]:
242
+ """Load configuration from environment variables."""
243
+ config = {}
244
+
245
+ for key, value in os.environ.items():
246
+ if key.startswith(prefix):
247
+ # Convert env var name to config key
248
+ config_key = key[len(prefix):].lower().replace('_', '.')
249
+
250
+ # Try to parse value
251
+ parsed_value = self._parse_env_value(value)
252
+ config[config_key] = parsed_value
253
+
254
+ return config
255
+
256
+ def _parse_env_value(self, value: str) -> Any:
257
+ """Parse environment variable value."""
258
+ # Try boolean
259
+ if value.lower() in ('true', 'false'):
260
+ return value.lower() == 'true'
261
+
262
+ # Try integer
263
+ try:
264
+ return int(value)
265
+ except ValueError:
266
+ pass
267
+
268
+ # Try float
269
+ try:
270
+ return float(value)
271
+ except ValueError:
272
+ pass
273
+
274
+ # Try JSON
275
+ try:
276
+ import json
277
+ return json.loads(value)
278
+ except (json.JSONDecodeError, ValueError):
279
+ pass
280
+
281
+ # Return as string
282
+ return value
283
+
284
+ def _validate_required_keys(self, config: Config, required_keys: List[str]) -> None:
285
+ """Validate that required keys are present."""
286
+ missing_keys = []
287
+
288
+ for key in required_keys:
289
+ if config.get(key) is None:
290
+ missing_keys.append(key)
291
+
292
+ if missing_keys:
293
+ raise ValueError(f"Missing required configuration keys: {missing_keys}")
294
+
295
+ def _generate_cache_key(self, pattern: ConfigPattern) -> str:
296
+ """Generate cache key for pattern."""
297
+ import hashlib
298
+
299
+ # Create a string representation of the pattern
300
+ pattern_str = f"{pattern.filenames}_{pattern.search_paths}_{pattern.env_prefix}"
301
+
302
+ # Generate hash
303
+ return hashlib.md5(pattern_str.encode()).hexdigest()[:8]
304
+
305
+ def clear_cache(self, cache_key: str = None) -> None:
306
+ """Clear configuration cache."""
307
+ if cache_key:
308
+ self._cache.pop(cache_key, None)
309
+ self.logger.debug(f"Cleared cache for: {cache_key}")
310
+ else:
311
+ self._cache.clear()
312
+ self.logger.debug("Cleared all config cache")
313
+
314
+ def get_cache_info(self) -> Dict[str, Any]:
315
+ """Get cache information."""
316
+ return {
317
+ "cached_configs": len(self._cache),
318
+ "cache_keys": list(self._cache.keys()),
319
+ "working_dir": str(self.working_dir)
320
+ }