claude-mpm 4.0.23__py3-none-any.whl → 4.0.28__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.
- claude_mpm/BUILD_NUMBER +1 -1
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +4 -1
- claude_mpm/agents/BASE_PM.md +3 -0
- claude_mpm/agents/templates/code_analyzer.json +2 -2
- claude_mpm/cli/commands/agents.py +453 -113
- claude_mpm/cli/commands/aggregate.py +107 -15
- claude_mpm/cli/commands/cleanup.py +142 -10
- claude_mpm/cli/commands/config.py +358 -224
- claude_mpm/cli/commands/info.py +184 -75
- claude_mpm/cli/commands/mcp_command_router.py +5 -76
- claude_mpm/cli/commands/mcp_install_commands.py +68 -36
- claude_mpm/cli/commands/mcp_server_commands.py +30 -37
- claude_mpm/cli/commands/memory.py +331 -61
- claude_mpm/cli/commands/monitor.py +101 -7
- claude_mpm/cli/commands/run.py +368 -8
- claude_mpm/cli/commands/tickets.py +206 -24
- claude_mpm/cli/parsers/mcp_parser.py +3 -0
- claude_mpm/cli/shared/__init__.py +40 -0
- claude_mpm/cli/shared/argument_patterns.py +212 -0
- claude_mpm/cli/shared/command_base.py +234 -0
- claude_mpm/cli/shared/error_handling.py +238 -0
- claude_mpm/cli/shared/output_formatters.py +231 -0
- claude_mpm/config/agent_config.py +29 -8
- claude_mpm/core/container.py +6 -4
- claude_mpm/core/service_registry.py +4 -2
- claude_mpm/core/shared/__init__.py +17 -0
- claude_mpm/core/shared/config_loader.py +320 -0
- claude_mpm/core/shared/path_resolver.py +277 -0
- claude_mpm/core/shared/singleton_manager.py +208 -0
- claude_mpm/hooks/claude_hooks/memory_integration.py +4 -2
- claude_mpm/hooks/claude_hooks/response_tracking.py +14 -3
- claude_mpm/hooks/memory_integration_hook.py +11 -2
- claude_mpm/services/agents/deployment/agent_deployment.py +49 -23
- claude_mpm/services/agents/deployment/deployment_wrapper.py +71 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +1 -0
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +43 -0
- claude_mpm/services/agents/deployment/processors/agent_deployment_context.py +4 -0
- claude_mpm/services/agents/deployment/processors/agent_processor.py +1 -1
- claude_mpm/services/agents/loading/base_agent_manager.py +11 -3
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +14 -5
- claude_mpm/services/event_aggregator.py +4 -2
- claude_mpm/services/mcp_gateway/config/config_loader.py +89 -28
- claude_mpm/services/mcp_gateway/config/configuration.py +29 -0
- claude_mpm/services/mcp_gateway/registry/service_registry.py +22 -5
- claude_mpm/services/memory/builder.py +6 -1
- claude_mpm/services/response_tracker.py +3 -1
- claude_mpm/services/runner_configuration_service.py +15 -6
- claude_mpm/services/shared/__init__.py +20 -0
- claude_mpm/services/shared/async_service_base.py +219 -0
- claude_mpm/services/shared/config_service_base.py +292 -0
- claude_mpm/services/shared/lifecycle_service_base.py +317 -0
- claude_mpm/services/shared/manager_base.py +303 -0
- claude_mpm/services/shared/service_factory.py +308 -0
- {claude_mpm-4.0.23.dist-info → claude_mpm-4.0.28.dist-info}/METADATA +19 -13
- {claude_mpm-4.0.23.dist-info → claude_mpm-4.0.28.dist-info}/RECORD +60 -44
- {claude_mpm-4.0.23.dist-info → claude_mpm-4.0.28.dist-info}/WHEEL +0 -0
- {claude_mpm-4.0.23.dist-info → claude_mpm-4.0.28.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.0.23.dist-info → claude_mpm-4.0.28.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.0.23.dist-info → claude_mpm-4.0.28.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared path resolution utilities to reduce duplication.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import List, Optional, Union
|
|
8
|
+
|
|
9
|
+
from ..logger import get_logger
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PathResolver:
|
|
13
|
+
"""
|
|
14
|
+
Centralized path resolution utility.
|
|
15
|
+
|
|
16
|
+
Reduces duplication by providing standard patterns for:
|
|
17
|
+
- Working directory resolution
|
|
18
|
+
- Configuration directory discovery
|
|
19
|
+
- Agent directory resolution
|
|
20
|
+
- Memory directory resolution
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, base_dir: Union[str, Path] = None):
|
|
24
|
+
"""
|
|
25
|
+
Initialize path resolver.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
base_dir: Base directory for relative paths
|
|
29
|
+
"""
|
|
30
|
+
self.base_dir = Path(base_dir) if base_dir else self._get_working_dir()
|
|
31
|
+
self.logger = get_logger("path_resolver")
|
|
32
|
+
|
|
33
|
+
def _get_working_dir(self) -> Path:
|
|
34
|
+
"""Get working directory respecting CLAUDE_MPM_USER_PWD."""
|
|
35
|
+
# Use CLAUDE_MPM_USER_PWD if available (when called via shell script)
|
|
36
|
+
user_pwd = os.environ.get("CLAUDE_MPM_USER_PWD")
|
|
37
|
+
if user_pwd:
|
|
38
|
+
return Path(user_pwd)
|
|
39
|
+
return Path.cwd()
|
|
40
|
+
|
|
41
|
+
def resolve_config_dir(self, create: bool = False) -> Path:
|
|
42
|
+
"""
|
|
43
|
+
Resolve configuration directory.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
create: Whether to create directory if it doesn't exist
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Path to configuration directory
|
|
50
|
+
"""
|
|
51
|
+
config_dir = self.base_dir / ".claude-mpm"
|
|
52
|
+
|
|
53
|
+
if create and not config_dir.exists():
|
|
54
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
55
|
+
self.logger.debug(f"Created config directory: {config_dir}")
|
|
56
|
+
|
|
57
|
+
return config_dir
|
|
58
|
+
|
|
59
|
+
def resolve_agents_dir(self, create: bool = False) -> Path:
|
|
60
|
+
"""
|
|
61
|
+
Resolve agents directory.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
create: Whether to create directory if it doesn't exist
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Path to agents directory
|
|
68
|
+
"""
|
|
69
|
+
agents_dir = self.resolve_config_dir(create) / "agents"
|
|
70
|
+
|
|
71
|
+
if create and not agents_dir.exists():
|
|
72
|
+
agents_dir.mkdir(parents=True, exist_ok=True)
|
|
73
|
+
self.logger.debug(f"Created agents directory: {agents_dir}")
|
|
74
|
+
|
|
75
|
+
return agents_dir
|
|
76
|
+
|
|
77
|
+
def resolve_memories_dir(self, create: bool = False) -> Path:
|
|
78
|
+
"""
|
|
79
|
+
Resolve memories directory.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
create: Whether to create directory if it doesn't exist
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Path to memories directory
|
|
86
|
+
"""
|
|
87
|
+
memories_dir = self.resolve_config_dir(create) / "memories"
|
|
88
|
+
|
|
89
|
+
if create and not memories_dir.exists():
|
|
90
|
+
memories_dir.mkdir(parents=True, exist_ok=True)
|
|
91
|
+
self.logger.debug(f"Created memories directory: {memories_dir}")
|
|
92
|
+
|
|
93
|
+
return memories_dir
|
|
94
|
+
|
|
95
|
+
def resolve_logs_dir(self, create: bool = False) -> Path:
|
|
96
|
+
"""
|
|
97
|
+
Resolve logs directory.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
create: Whether to create directory if it doesn't exist
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Path to logs directory
|
|
104
|
+
"""
|
|
105
|
+
logs_dir = self.resolve_config_dir(create) / "logs"
|
|
106
|
+
|
|
107
|
+
if create and not logs_dir.exists():
|
|
108
|
+
logs_dir.mkdir(parents=True, exist_ok=True)
|
|
109
|
+
self.logger.debug(f"Created logs directory: {logs_dir}")
|
|
110
|
+
|
|
111
|
+
return logs_dir
|
|
112
|
+
|
|
113
|
+
def resolve_temp_dir(self, create: bool = False) -> Path:
|
|
114
|
+
"""
|
|
115
|
+
Resolve temporary directory.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
create: Whether to create directory if it doesn't exist
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Path to temporary directory
|
|
122
|
+
"""
|
|
123
|
+
temp_dir = self.resolve_config_dir(create) / "tmp"
|
|
124
|
+
|
|
125
|
+
if create and not temp_dir.exists():
|
|
126
|
+
temp_dir.mkdir(parents=True, exist_ok=True)
|
|
127
|
+
self.logger.debug(f"Created temp directory: {temp_dir}")
|
|
128
|
+
|
|
129
|
+
return temp_dir
|
|
130
|
+
|
|
131
|
+
def find_agent_file(self, agent_name: str, filename: str = None) -> Optional[Path]:
|
|
132
|
+
"""
|
|
133
|
+
Find agent file in standard locations.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
agent_name: Name of the agent
|
|
137
|
+
filename: Specific filename to look for (defaults to agent_name.md)
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Path to agent file if found
|
|
141
|
+
"""
|
|
142
|
+
if filename is None:
|
|
143
|
+
filename = f"{agent_name}.md"
|
|
144
|
+
|
|
145
|
+
# Search locations in order of preference
|
|
146
|
+
search_paths = [
|
|
147
|
+
self.resolve_agents_dir(), # Project agents
|
|
148
|
+
Path.home() / ".claude" / "agents", # User agents
|
|
149
|
+
self.base_dir / "agents", # Local agents directory
|
|
150
|
+
self.base_dir, # Current directory
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
for search_path in search_paths:
|
|
154
|
+
if not search_path.exists():
|
|
155
|
+
continue
|
|
156
|
+
|
|
157
|
+
agent_file = search_path / filename
|
|
158
|
+
if agent_file.exists() and agent_file.is_file():
|
|
159
|
+
self.logger.debug(f"Found agent file: {agent_file}")
|
|
160
|
+
return agent_file
|
|
161
|
+
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
def find_memory_file(self, agent_name: str, filename: str = None) -> Optional[Path]:
|
|
165
|
+
"""
|
|
166
|
+
Find memory file for an agent.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
agent_name: Name of the agent
|
|
170
|
+
filename: Specific filename to look for (defaults to agent_name.md)
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Path to memory file if found
|
|
174
|
+
"""
|
|
175
|
+
if filename is None:
|
|
176
|
+
filename = f"{agent_name}.md"
|
|
177
|
+
|
|
178
|
+
memories_dir = self.resolve_memories_dir()
|
|
179
|
+
memory_file = memories_dir / filename
|
|
180
|
+
|
|
181
|
+
if memory_file.exists() and memory_file.is_file():
|
|
182
|
+
return memory_file
|
|
183
|
+
|
|
184
|
+
return None
|
|
185
|
+
|
|
186
|
+
def find_config_file(self,
|
|
187
|
+
filename: str,
|
|
188
|
+
search_paths: List[Union[str, Path]] = None) -> Optional[Path]:
|
|
189
|
+
"""
|
|
190
|
+
Find configuration file in standard locations.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
filename: Configuration filename
|
|
194
|
+
search_paths: Additional search paths
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
Path to configuration file if found
|
|
198
|
+
"""
|
|
199
|
+
default_paths = [
|
|
200
|
+
self.resolve_config_dir(),
|
|
201
|
+
self.base_dir,
|
|
202
|
+
Path.home() / ".claude-mpm",
|
|
203
|
+
]
|
|
204
|
+
|
|
205
|
+
if search_paths:
|
|
206
|
+
all_paths = [Path(p) for p in search_paths] + default_paths
|
|
207
|
+
else:
|
|
208
|
+
all_paths = default_paths
|
|
209
|
+
|
|
210
|
+
for search_path in all_paths:
|
|
211
|
+
if not search_path.exists():
|
|
212
|
+
continue
|
|
213
|
+
|
|
214
|
+
config_file = search_path / filename
|
|
215
|
+
if config_file.exists() and config_file.is_file():
|
|
216
|
+
self.logger.debug(f"Found config file: {config_file}")
|
|
217
|
+
return config_file
|
|
218
|
+
|
|
219
|
+
return None
|
|
220
|
+
|
|
221
|
+
def ensure_directory(self, path: Union[str, Path]) -> Path:
|
|
222
|
+
"""
|
|
223
|
+
Ensure directory exists.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
path: Directory path
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Path object for the directory
|
|
230
|
+
"""
|
|
231
|
+
dir_path = Path(path)
|
|
232
|
+
|
|
233
|
+
if not dir_path.exists():
|
|
234
|
+
dir_path.mkdir(parents=True, exist_ok=True)
|
|
235
|
+
self.logger.debug(f"Created directory: {dir_path}")
|
|
236
|
+
elif not dir_path.is_dir():
|
|
237
|
+
raise ValueError(f"Path exists but is not a directory: {dir_path}")
|
|
238
|
+
|
|
239
|
+
return dir_path
|
|
240
|
+
|
|
241
|
+
def resolve_relative_path(self, path: Union[str, Path]) -> Path:
|
|
242
|
+
"""
|
|
243
|
+
Resolve path relative to base directory.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
path: Path to resolve
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
Resolved absolute path
|
|
250
|
+
"""
|
|
251
|
+
path_obj = Path(path)
|
|
252
|
+
|
|
253
|
+
if path_obj.is_absolute():
|
|
254
|
+
return path_obj
|
|
255
|
+
|
|
256
|
+
return (self.base_dir / path_obj).resolve()
|
|
257
|
+
|
|
258
|
+
def get_path_info(self) -> dict:
|
|
259
|
+
"""
|
|
260
|
+
Get information about resolved paths.
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
Dictionary with path information
|
|
264
|
+
"""
|
|
265
|
+
return {
|
|
266
|
+
"base_dir": str(self.base_dir),
|
|
267
|
+
"config_dir": str(self.resolve_config_dir()),
|
|
268
|
+
"agents_dir": str(self.resolve_agents_dir()),
|
|
269
|
+
"memories_dir": str(self.resolve_memories_dir()),
|
|
270
|
+
"logs_dir": str(self.resolve_logs_dir()),
|
|
271
|
+
"temp_dir": str(self.resolve_temp_dir()),
|
|
272
|
+
"working_dir_from_env": os.environ.get("CLAUDE_MPM_USER_PWD"),
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
def __repr__(self) -> str:
|
|
276
|
+
"""String representation."""
|
|
277
|
+
return f"PathResolver(base_dir={self.base_dir})"
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared singleton management utilities to reduce duplication.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import threading
|
|
6
|
+
from typing import Any, Dict, Optional, Type, TypeVar
|
|
7
|
+
|
|
8
|
+
from ..logger import get_logger
|
|
9
|
+
|
|
10
|
+
T = TypeVar('T')
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SingletonManager:
|
|
14
|
+
"""
|
|
15
|
+
Centralized singleton management utility.
|
|
16
|
+
|
|
17
|
+
Reduces duplication by providing thread-safe singleton patterns
|
|
18
|
+
that can be used across different classes.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
_instances: Dict[Type, Any] = {}
|
|
22
|
+
_locks: Dict[Type, threading.Lock] = {}
|
|
23
|
+
_global_lock = threading.Lock()
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def get_instance(cls,
|
|
27
|
+
singleton_class: Type[T],
|
|
28
|
+
*args,
|
|
29
|
+
force_new: bool = False,
|
|
30
|
+
**kwargs) -> T:
|
|
31
|
+
"""
|
|
32
|
+
Get singleton instance of a class.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
singleton_class: Class to get singleton instance of
|
|
36
|
+
*args: Arguments for class constructor
|
|
37
|
+
force_new: Force creation of new instance
|
|
38
|
+
**kwargs: Keyword arguments for class constructor
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Singleton instance
|
|
42
|
+
"""
|
|
43
|
+
# Get or create lock for this class
|
|
44
|
+
if singleton_class not in cls._locks:
|
|
45
|
+
with cls._global_lock:
|
|
46
|
+
if singleton_class not in cls._locks:
|
|
47
|
+
cls._locks[singleton_class] = threading.Lock()
|
|
48
|
+
|
|
49
|
+
# Get instance with class-specific lock
|
|
50
|
+
with cls._locks[singleton_class]:
|
|
51
|
+
if force_new or singleton_class not in cls._instances:
|
|
52
|
+
logger = get_logger("singleton_manager")
|
|
53
|
+
logger.debug(f"Creating singleton instance: {singleton_class.__name__}")
|
|
54
|
+
|
|
55
|
+
instance = singleton_class(*args, **kwargs)
|
|
56
|
+
cls._instances[singleton_class] = instance
|
|
57
|
+
|
|
58
|
+
return instance
|
|
59
|
+
|
|
60
|
+
return cls._instances[singleton_class]
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def has_instance(cls, singleton_class: Type) -> bool:
|
|
64
|
+
"""
|
|
65
|
+
Check if singleton instance exists.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
singleton_class: Class to check
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
True if instance exists
|
|
72
|
+
"""
|
|
73
|
+
return singleton_class in cls._instances
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def clear_instance(cls, singleton_class: Type) -> None:
|
|
77
|
+
"""
|
|
78
|
+
Clear singleton instance.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
singleton_class: Class to clear instance for
|
|
82
|
+
"""
|
|
83
|
+
if singleton_class in cls._locks:
|
|
84
|
+
with cls._locks[singleton_class]:
|
|
85
|
+
if singleton_class in cls._instances:
|
|
86
|
+
logger = get_logger("singleton_manager")
|
|
87
|
+
logger.debug(f"Clearing singleton instance: {singleton_class.__name__}")
|
|
88
|
+
del cls._instances[singleton_class]
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def clear_all_instances(cls) -> None:
|
|
92
|
+
"""Clear all singleton instances."""
|
|
93
|
+
with cls._global_lock:
|
|
94
|
+
logger = get_logger("singleton_manager")
|
|
95
|
+
logger.debug(f"Clearing {len(cls._instances)} singleton instances")
|
|
96
|
+
cls._instances.clear()
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def get_instance_info(cls) -> Dict[str, Any]:
|
|
100
|
+
"""
|
|
101
|
+
Get information about managed instances.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Dictionary with instance information
|
|
105
|
+
"""
|
|
106
|
+
return {
|
|
107
|
+
"instance_count": len(cls._instances),
|
|
108
|
+
"instance_types": [cls_type.__name__ for cls_type in cls._instances.keys()],
|
|
109
|
+
"lock_count": len(cls._locks)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class SingletonMixin:
|
|
114
|
+
"""
|
|
115
|
+
Mixin class to add singleton behavior to any class.
|
|
116
|
+
|
|
117
|
+
Usage:
|
|
118
|
+
class MyService(SingletonMixin):
|
|
119
|
+
def __init__(self):
|
|
120
|
+
super().__init__()
|
|
121
|
+
# Your initialization code
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
def __new__(cls, *args, **kwargs):
|
|
125
|
+
"""Override __new__ to implement singleton pattern."""
|
|
126
|
+
return SingletonManager.get_instance(cls, *args, **kwargs)
|
|
127
|
+
|
|
128
|
+
@classmethod
|
|
129
|
+
def get_instance(cls, *args, **kwargs):
|
|
130
|
+
"""Get singleton instance."""
|
|
131
|
+
return SingletonManager.get_instance(cls, *args, **kwargs)
|
|
132
|
+
|
|
133
|
+
@classmethod
|
|
134
|
+
def clear_instance(cls):
|
|
135
|
+
"""Clear singleton instance."""
|
|
136
|
+
SingletonManager.clear_instance(cls)
|
|
137
|
+
|
|
138
|
+
@classmethod
|
|
139
|
+
def has_instance(cls) -> bool:
|
|
140
|
+
"""Check if instance exists."""
|
|
141
|
+
return SingletonManager.has_instance(cls)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def singleton(cls: Type[T]) -> Type[T]:
|
|
145
|
+
"""
|
|
146
|
+
Decorator to make a class a singleton.
|
|
147
|
+
|
|
148
|
+
Usage:
|
|
149
|
+
@singleton
|
|
150
|
+
class MyService:
|
|
151
|
+
def __init__(self):
|
|
152
|
+
# Your initialization code
|
|
153
|
+
pass
|
|
154
|
+
"""
|
|
155
|
+
original_new = cls.__new__
|
|
156
|
+
|
|
157
|
+
def new_new(cls_inner, *args, **kwargs):
|
|
158
|
+
return SingletonManager.get_instance(cls_inner, *args, **kwargs)
|
|
159
|
+
|
|
160
|
+
cls.__new__ = new_new
|
|
161
|
+
|
|
162
|
+
# Add convenience methods
|
|
163
|
+
cls.get_instance = classmethod(lambda cls_inner, *args, **kwargs:
|
|
164
|
+
SingletonManager.get_instance(cls_inner, *args, **kwargs))
|
|
165
|
+
cls.clear_instance = classmethod(lambda cls_inner:
|
|
166
|
+
SingletonManager.clear_instance(cls_inner))
|
|
167
|
+
cls.has_instance = classmethod(lambda cls_inner:
|
|
168
|
+
SingletonManager.has_instance(cls_inner))
|
|
169
|
+
|
|
170
|
+
return cls
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# Example usage patterns
|
|
174
|
+
if __name__ == "__main__":
|
|
175
|
+
# Example 1: Using SingletonMixin
|
|
176
|
+
class ConfigService(SingletonMixin):
|
|
177
|
+
def __init__(self, config_path: str = "default.yaml"):
|
|
178
|
+
self.config_path = config_path
|
|
179
|
+
self.loaded = True
|
|
180
|
+
|
|
181
|
+
# Example 2: Using @singleton decorator
|
|
182
|
+
@singleton
|
|
183
|
+
class LoggerService:
|
|
184
|
+
def __init__(self, log_level: str = "INFO"):
|
|
185
|
+
self.log_level = log_level
|
|
186
|
+
self.initialized = True
|
|
187
|
+
|
|
188
|
+
# Example 3: Using SingletonManager directly
|
|
189
|
+
class DatabaseService:
|
|
190
|
+
def __init__(self, connection_string: str):
|
|
191
|
+
self.connection_string = connection_string
|
|
192
|
+
self.connected = True
|
|
193
|
+
|
|
194
|
+
# Test the patterns
|
|
195
|
+
config1 = ConfigService("config1.yaml")
|
|
196
|
+
config2 = ConfigService("config2.yaml") # Same instance as config1
|
|
197
|
+
assert config1 is config2
|
|
198
|
+
assert config1.config_path == "config1.yaml" # First initialization wins
|
|
199
|
+
|
|
200
|
+
logger1 = LoggerService("DEBUG")
|
|
201
|
+
logger2 = LoggerService("ERROR") # Same instance as logger1
|
|
202
|
+
assert logger1 is logger2
|
|
203
|
+
assert logger1.log_level == "DEBUG" # First initialization wins
|
|
204
|
+
|
|
205
|
+
db1 = SingletonManager.get_instance(DatabaseService, "postgres://localhost")
|
|
206
|
+
db2 = SingletonManager.get_instance(DatabaseService, "mysql://localhost") # Same instance
|
|
207
|
+
assert db1 is db2
|
|
208
|
+
assert db1.connection_string == "postgres://localhost" # First initialization wins
|
|
@@ -22,6 +22,7 @@ try:
|
|
|
22
22
|
paths.ensure_in_path()
|
|
23
23
|
|
|
24
24
|
from claude_mpm.core.config import Config
|
|
25
|
+
from claude_mpm.core.shared.config_loader import ConfigLoader
|
|
25
26
|
from claude_mpm.hooks.base_hook import HookContext, HookType
|
|
26
27
|
from claude_mpm.hooks.memory_integration_hook import (
|
|
27
28
|
MemoryPostDelegationHook,
|
|
@@ -60,8 +61,9 @@ class MemoryHookManager:
|
|
|
60
61
|
are triggered at the right times during agent delegation.
|
|
61
62
|
"""
|
|
62
63
|
try:
|
|
63
|
-
# Create configuration
|
|
64
|
-
|
|
64
|
+
# Create configuration using ConfigLoader
|
|
65
|
+
config_loader = ConfigLoader()
|
|
66
|
+
config = config_loader.load_main_config()
|
|
65
67
|
|
|
66
68
|
# Only initialize if memory system is enabled
|
|
67
69
|
if not config.get("memory.enabled", True):
|
|
@@ -51,11 +51,22 @@ class ResponseTrackingManager:
|
|
|
51
51
|
response tracking without code changes.
|
|
52
52
|
"""
|
|
53
53
|
try:
|
|
54
|
-
# Create configuration with optional config file
|
|
54
|
+
# Create configuration with optional config file using ConfigLoader
|
|
55
55
|
config_file = os.environ.get("CLAUDE_PM_CONFIG_FILE")
|
|
56
56
|
from claude_mpm.core.config import Config
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
from claude_mpm.core.shared.config_loader import ConfigLoader, ConfigPattern
|
|
58
|
+
|
|
59
|
+
config_loader = ConfigLoader()
|
|
60
|
+
if config_file:
|
|
61
|
+
# Use specific config file with ConfigLoader
|
|
62
|
+
pattern = ConfigPattern(
|
|
63
|
+
filenames=[os.path.basename(config_file)],
|
|
64
|
+
search_paths=[os.path.dirname(config_file)],
|
|
65
|
+
env_prefix="CLAUDE_MPM_"
|
|
66
|
+
)
|
|
67
|
+
config = config_loader.load_config(pattern, cache_key=f"response_tracking_{config_file}")
|
|
68
|
+
else:
|
|
69
|
+
config = config_loader.load_main_config()
|
|
59
70
|
|
|
60
71
|
# Check if response tracking is enabled (check both sections for compatibility)
|
|
61
72
|
response_tracking_enabled = config.get("response_tracking.enabled", False)
|
|
@@ -17,6 +17,7 @@ from typing import Any, Dict, List
|
|
|
17
17
|
|
|
18
18
|
from claude_mpm.core.config import Config
|
|
19
19
|
from claude_mpm.core.logger import get_logger
|
|
20
|
+
from claude_mpm.core.shared.config_loader import ConfigLoader
|
|
20
21
|
from claude_mpm.hooks.base_hook import (
|
|
21
22
|
HookContext,
|
|
22
23
|
HookResult,
|
|
@@ -65,7 +66,11 @@ class MemoryPreDelegationHook(PreDelegationHook):
|
|
|
65
66
|
config: Optional Config object. If not provided, will create default Config.
|
|
66
67
|
"""
|
|
67
68
|
super().__init__(name="memory_pre_delegation", priority=20)
|
|
68
|
-
|
|
69
|
+
if config:
|
|
70
|
+
self.config = config
|
|
71
|
+
else:
|
|
72
|
+
config_loader = ConfigLoader()
|
|
73
|
+
self.config = config_loader.load_main_config()
|
|
69
74
|
|
|
70
75
|
# Initialize memory manager only if available
|
|
71
76
|
if MEMORY_MANAGER_AVAILABLE and AgentMemoryManager:
|
|
@@ -196,7 +201,11 @@ class MemoryPostDelegationHook(PostDelegationHook):
|
|
|
196
201
|
config: Optional Config object. If not provided, will create default Config.
|
|
197
202
|
"""
|
|
198
203
|
super().__init__(name="memory_post_delegation", priority=80)
|
|
199
|
-
|
|
204
|
+
if config:
|
|
205
|
+
self.config = config
|
|
206
|
+
else:
|
|
207
|
+
config_loader = ConfigLoader()
|
|
208
|
+
self.config = config_loader.load_main_config()
|
|
200
209
|
|
|
201
210
|
# Initialize memory manager only if available
|
|
202
211
|
if MEMORY_MANAGER_AVAILABLE and AgentMemoryManager:
|