claude-mpm 4.0.19__py3-none-any.whl → 4.0.22__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/__main__.py +4 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +38 -2
- claude_mpm/agents/INSTRUCTIONS.md +74 -0
- claude_mpm/agents/OUTPUT_STYLE.md +84 -0
- claude_mpm/agents/WORKFLOW.md +308 -4
- claude_mpm/agents/agents_metadata.py +52 -0
- claude_mpm/agents/base_agent_loader.py +75 -19
- claude_mpm/agents/templates/__init__.py +4 -0
- claude_mpm/agents/templates/api_qa.json +206 -0
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/agents/templates/research.json +24 -16
- claude_mpm/agents/templates/ticketing.json +18 -5
- claude_mpm/agents/templates/vercel_ops_agent.json +281 -0
- claude_mpm/agents/templates/vercel_ops_instructions.md +582 -0
- claude_mpm/cli/__init__.py +23 -1
- claude_mpm/cli/__main__.py +4 -0
- claude_mpm/cli/commands/mcp_command_router.py +87 -1
- claude_mpm/cli/commands/mcp_install_commands.py +207 -26
- claude_mpm/cli/commands/memory.py +32 -5
- claude_mpm/cli/commands/run.py +33 -6
- claude_mpm/cli/parsers/base_parser.py +5 -0
- claude_mpm/cli/parsers/mcp_parser.py +23 -0
- claude_mpm/cli/parsers/run_parser.py +5 -0
- claude_mpm/cli/utils.py +17 -4
- claude_mpm/constants.py +1 -0
- claude_mpm/core/base_service.py +8 -2
- claude_mpm/core/config.py +122 -32
- claude_mpm/core/framework_loader.py +385 -34
- claude_mpm/core/interactive_session.py +77 -12
- claude_mpm/core/oneshot_session.py +7 -1
- claude_mpm/core/output_style_manager.py +468 -0
- claude_mpm/core/unified_paths.py +190 -21
- claude_mpm/hooks/claude_hooks/hook_handler.py +91 -16
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +3 -0
- claude_mpm/init.py +1 -0
- claude_mpm/scripts/socketio_daemon.py +67 -7
- claude_mpm/scripts/socketio_daemon_hardened.py +897 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +216 -10
- claude_mpm/services/agents/deployment/agent_template_builder.py +37 -1
- claude_mpm/services/agents/deployment/async_agent_deployment.py +65 -1
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +441 -0
- claude_mpm/services/agents/memory/__init__.py +0 -2
- claude_mpm/services/agents/memory/agent_memory_manager.py +577 -44
- claude_mpm/services/agents/memory/content_manager.py +144 -14
- claude_mpm/services/agents/memory/template_generator.py +7 -354
- claude_mpm/services/mcp_gateway/server/stdio_server.py +61 -169
- claude_mpm/services/memory_hook_service.py +62 -4
- claude_mpm/services/runner_configuration_service.py +5 -9
- claude_mpm/services/socketio/server/broadcaster.py +32 -1
- claude_mpm/services/socketio/server/core.py +4 -0
- claude_mpm/services/socketio/server/main.py +23 -4
- claude_mpm/services/subprocess_launcher_service.py +5 -0
- {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/METADATA +1 -1
- {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/RECORD +60 -54
- claude_mpm/services/agents/memory/analyzer.py +0 -430
- {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/WHEEL +0 -0
- {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/top_level.txt +0 -0
claude_mpm/core/config.py
CHANGED
|
@@ -8,6 +8,7 @@ and default values with proper validation and type conversion.
|
|
|
8
8
|
import json
|
|
9
9
|
import logging
|
|
10
10
|
import os
|
|
11
|
+
import threading
|
|
11
12
|
from pathlib import Path
|
|
12
13
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
13
14
|
|
|
@@ -24,6 +25,9 @@ class Config:
|
|
|
24
25
|
"""
|
|
25
26
|
Configuration manager for Claude PM services.
|
|
26
27
|
|
|
28
|
+
Implements singleton pattern to ensure configuration is loaded only once
|
|
29
|
+
and shared across all services.
|
|
30
|
+
|
|
27
31
|
Supports loading from:
|
|
28
32
|
- Python dictionaries
|
|
29
33
|
- JSON files
|
|
@@ -31,6 +35,30 @@ class Config:
|
|
|
31
35
|
- Environment variables
|
|
32
36
|
"""
|
|
33
37
|
|
|
38
|
+
_instance = None
|
|
39
|
+
_initialized = False
|
|
40
|
+
_success_logged = False # Class-level flag to track if success message was already logged
|
|
41
|
+
_lock = threading.Lock() # Thread safety for singleton initialization
|
|
42
|
+
|
|
43
|
+
def __new__(cls, *args, **kwargs):
|
|
44
|
+
"""Implement singleton pattern to ensure single configuration instance.
|
|
45
|
+
|
|
46
|
+
WHY: Configuration was being loaded 11 times during startup, once for each service.
|
|
47
|
+
This singleton pattern ensures configuration is loaded only once and reused.
|
|
48
|
+
Thread-safe implementation prevents race conditions during concurrent initialization.
|
|
49
|
+
"""
|
|
50
|
+
if cls._instance is None:
|
|
51
|
+
with cls._lock:
|
|
52
|
+
# Double-check locking pattern for thread safety
|
|
53
|
+
if cls._instance is None:
|
|
54
|
+
cls._instance = super().__new__(cls)
|
|
55
|
+
logger.info("Creating new Config singleton instance")
|
|
56
|
+
else:
|
|
57
|
+
logger.debug("Reusing existing Config singleton instance (concurrent init)")
|
|
58
|
+
else:
|
|
59
|
+
logger.debug("Reusing existing Config singleton instance")
|
|
60
|
+
return cls._instance
|
|
61
|
+
|
|
34
62
|
def __init__(
|
|
35
63
|
self,
|
|
36
64
|
config: Optional[Dict[str, Any]] = None,
|
|
@@ -45,48 +73,82 @@ class Config:
|
|
|
45
73
|
config_file: Path to configuration file (JSON or YAML)
|
|
46
74
|
env_prefix: Prefix for environment variables
|
|
47
75
|
"""
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
76
|
+
# Skip initialization if already done (singleton pattern)
|
|
77
|
+
# Use thread-safe check to prevent concurrent initialization
|
|
78
|
+
if Config._initialized:
|
|
79
|
+
logger.debug("Config already initialized, skipping re-initialization")
|
|
80
|
+
# If someone tries to load a different config file after initialization,
|
|
81
|
+
# log a debug message but don't reload
|
|
82
|
+
if config_file and str(config_file) != getattr(self, '_loaded_from', None):
|
|
83
|
+
logger.debug(
|
|
84
|
+
f"Ignoring config_file parameter '{config_file}' - "
|
|
85
|
+
f"configuration already loaded from '{getattr(self, '_loaded_from', 'defaults')}'"
|
|
86
|
+
)
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
# Thread-safe initialization - acquire lock for ENTIRE initialization process
|
|
90
|
+
with Config._lock:
|
|
91
|
+
# Double-check pattern - check again inside the lock
|
|
92
|
+
if Config._initialized:
|
|
93
|
+
logger.debug("Config already initialized (concurrent), skipping re-initialization")
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
Config._initialized = True
|
|
97
|
+
logger.info("Initializing Config singleton for the first time")
|
|
98
|
+
|
|
99
|
+
# Initialize instance variables inside the lock to ensure thread safety
|
|
100
|
+
self._config: Dict[str, Any] = {}
|
|
101
|
+
self._env_prefix = env_prefix
|
|
102
|
+
self._config_mgr = ConfigurationManager(cache_enabled=True)
|
|
103
|
+
|
|
104
|
+
# Load base configuration
|
|
105
|
+
if config:
|
|
106
|
+
self._config.update(config)
|
|
107
|
+
|
|
108
|
+
# Track where configuration was loaded from
|
|
109
|
+
self._loaded_from = None
|
|
110
|
+
# Track the actual file we loaded from to prevent re-loading
|
|
111
|
+
self._actual_loaded_file = None
|
|
112
|
+
|
|
113
|
+
# Load from file if provided
|
|
114
|
+
# Note: Only ONE config file should be loaded, and success message shown only once
|
|
115
|
+
if config_file:
|
|
116
|
+
self.load_file(config_file, is_initial_load=True)
|
|
117
|
+
self._loaded_from = str(config_file)
|
|
69
118
|
else:
|
|
70
|
-
#
|
|
71
|
-
|
|
72
|
-
if
|
|
73
|
-
self.load_file(
|
|
119
|
+
# Try to load from standard location: .claude-mpm/configuration.yaml
|
|
120
|
+
default_config = Path.cwd() / ".claude-mpm" / "configuration.yaml"
|
|
121
|
+
if default_config.exists():
|
|
122
|
+
self.load_file(default_config, is_initial_load=True)
|
|
123
|
+
self._loaded_from = str(default_config)
|
|
124
|
+
elif (alt_config := Path.cwd() / ".claude-mpm" / "configuration.yml").exists():
|
|
125
|
+
# Also try .yml extension (using walrus operator for cleaner code)
|
|
126
|
+
self.load_file(alt_config, is_initial_load=True)
|
|
74
127
|
self._loaded_from = str(alt_config)
|
|
75
128
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
129
|
+
# Load from environment variables (new and legacy prefixes)
|
|
130
|
+
self._load_env_vars()
|
|
131
|
+
self._load_legacy_env_vars()
|
|
79
132
|
|
|
80
|
-
|
|
81
|
-
|
|
133
|
+
# Apply defaults
|
|
134
|
+
self._apply_defaults()
|
|
82
135
|
|
|
83
|
-
def load_file(self, file_path: Union[str, Path]) -> None:
|
|
136
|
+
def load_file(self, file_path: Union[str, Path], is_initial_load: bool = True) -> None:
|
|
84
137
|
"""Load configuration from file with enhanced error handling.
|
|
85
138
|
|
|
86
139
|
WHY: Configuration loading failures can cause silent issues. We need
|
|
87
140
|
to provide clear, actionable error messages to help users fix problems.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
file_path: Path to the configuration file
|
|
144
|
+
is_initial_load: Whether this is the initial configuration load (for logging control)
|
|
88
145
|
"""
|
|
89
146
|
file_path = Path(file_path)
|
|
147
|
+
|
|
148
|
+
# Check if we've already loaded from this exact file to prevent duplicate messages
|
|
149
|
+
if hasattr(self, '_actual_loaded_file') and self._actual_loaded_file == str(file_path):
|
|
150
|
+
logger.debug(f"Configuration already loaded from {file_path}, skipping reload")
|
|
151
|
+
return
|
|
90
152
|
|
|
91
153
|
if not file_path.exists():
|
|
92
154
|
logger.warning(f"Configuration file not found: {file_path}")
|
|
@@ -113,7 +175,23 @@ class Config:
|
|
|
113
175
|
file_config = self._config_mgr.load_auto(file_path)
|
|
114
176
|
if file_config:
|
|
115
177
|
self._config = self._config_mgr.merge_configs(self._config, file_config)
|
|
116
|
-
|
|
178
|
+
# Track that we've successfully loaded from this file
|
|
179
|
+
self._actual_loaded_file = str(file_path)
|
|
180
|
+
|
|
181
|
+
# Only log success message once using class-level flag to avoid duplicate messages
|
|
182
|
+
# Check if we should log success message (thread-safe for reads after initialization)
|
|
183
|
+
if is_initial_load:
|
|
184
|
+
if not Config._success_logged:
|
|
185
|
+
# Set flag IMMEDIATELY before logging to prevent any possibility of duplicate
|
|
186
|
+
# messages. No lock needed here since we're already inside __init__ lock
|
|
187
|
+
Config._success_logged = True
|
|
188
|
+
logger.info(f"✓ Successfully loaded configuration from {file_path}")
|
|
189
|
+
else:
|
|
190
|
+
# Configuration already successfully loaded before, just debug log
|
|
191
|
+
logger.debug(f"Configuration already loaded, skipping success message for {file_path}")
|
|
192
|
+
else:
|
|
193
|
+
# Not initial load (shouldn't happen in normal flow, but handle gracefully)
|
|
194
|
+
logger.debug(f"Configuration reloaded from {file_path}")
|
|
117
195
|
|
|
118
196
|
# Log important configuration values for debugging
|
|
119
197
|
if logger.isEnabledFor(logging.DEBUG):
|
|
@@ -789,3 +867,15 @@ class Config:
|
|
|
789
867
|
def __repr__(self) -> str:
|
|
790
868
|
"""String representation of configuration."""
|
|
791
869
|
return f"<Config({len(self._config)} keys)>"
|
|
870
|
+
|
|
871
|
+
@classmethod
|
|
872
|
+
def reset_singleton(cls):
|
|
873
|
+
"""Reset the singleton instance (mainly for testing purposes).
|
|
874
|
+
|
|
875
|
+
WHY: During testing, we may need to reset the singleton to test different
|
|
876
|
+
configurations. This method allows controlled reset of the singleton state.
|
|
877
|
+
"""
|
|
878
|
+
cls._instance = None
|
|
879
|
+
cls._initialized = False
|
|
880
|
+
cls._success_logged = False
|
|
881
|
+
logger.debug("Config singleton reset")
|