claude-mpm 5.0.2__py3-none-any.whl โ 5.4.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.
Potentially problematic release.
This version of claude-mpm might be problematic. Click here for more details.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +2002 -0
- claude_mpm/agents/PM_INSTRUCTIONS.md +1218 -905
- claude_mpm/agents/agent_loader.py +10 -17
- claude_mpm/agents/base_agent_loader.py +10 -35
- claude_mpm/agents/frontmatter_validator.py +68 -0
- claude_mpm/agents/templates/circuit-breakers.md +431 -45
- claude_mpm/cli/__init__.py +0 -1
- claude_mpm/cli/commands/__init__.py +2 -0
- claude_mpm/cli/commands/agent_state_manager.py +67 -23
- claude_mpm/cli/commands/agents.py +446 -25
- claude_mpm/cli/commands/auto_configure.py +535 -233
- claude_mpm/cli/commands/configure.py +1500 -147
- claude_mpm/cli/commands/configure_agent_display.py +13 -6
- claude_mpm/cli/commands/mpm_init/core.py +158 -1
- claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
- claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
- claude_mpm/cli/commands/postmortem.py +401 -0
- claude_mpm/cli/commands/run.py +1 -39
- claude_mpm/cli/commands/skills.py +322 -19
- claude_mpm/cli/commands/summarize.py +413 -0
- claude_mpm/cli/executor.py +8 -0
- claude_mpm/cli/interactive/agent_wizard.py +302 -195
- claude_mpm/cli/parsers/agents_parser.py +137 -0
- claude_mpm/cli/parsers/auto_configure_parser.py +13 -0
- claude_mpm/cli/parsers/base_parser.py +9 -0
- claude_mpm/cli/parsers/skills_parser.py +7 -0
- claude_mpm/cli/startup.py +133 -85
- claude_mpm/commands/mpm-agents-auto-configure.md +2 -2
- claude_mpm/commands/mpm-agents-list.md +2 -2
- claude_mpm/commands/mpm-config-view.md +2 -2
- claude_mpm/commands/mpm-help.md +3 -0
- claude_mpm/commands/{mpm-ticket-organize.md โ mpm-organize.md} +4 -5
- claude_mpm/commands/mpm-postmortem.md +123 -0
- claude_mpm/commands/mpm-session-resume.md +2 -2
- claude_mpm/commands/mpm-ticket-view.md +2 -2
- claude_mpm/config/agent_presets.py +312 -82
- claude_mpm/config/agent_sources.py +27 -0
- claude_mpm/config/skill_presets.py +392 -0
- claude_mpm/constants.py +1 -0
- claude_mpm/core/claude_runner.py +2 -25
- claude_mpm/core/framework/loaders/agent_loader.py +8 -5
- claude_mpm/core/framework/loaders/file_loader.py +54 -101
- claude_mpm/core/interactive_session.py +19 -5
- claude_mpm/core/oneshot_session.py +16 -4
- claude_mpm/core/output_style_manager.py +173 -43
- claude_mpm/core/protocols/__init__.py +23 -0
- claude_mpm/core/protocols/runner_protocol.py +103 -0
- claude_mpm/core/protocols/session_protocol.py +131 -0
- claude_mpm/core/shared/singleton_manager.py +11 -4
- claude_mpm/core/socketio_pool.py +3 -3
- claude_mpm/core/system_context.py +38 -0
- claude_mpm/core/unified_agent_registry.py +134 -16
- claude_mpm/core/unified_config.py +22 -0
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +35 -2
- claude_mpm/hooks/claude_hooks/hook_handler.py +4 -0
- claude_mpm/hooks/claude_hooks/memory_integration.py +12 -1
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
- claude_mpm/models/agent_definition.py +7 -0
- claude_mpm/scripts/launch_monitor.py +93 -13
- claude_mpm/services/agents/agent_recommendation_service.py +279 -0
- claude_mpm/services/agents/cache_git_manager.py +621 -0
- claude_mpm/services/agents/deployment/agent_template_builder.py +3 -2
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +110 -3
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +518 -55
- claude_mpm/services/agents/git_source_manager.py +20 -0
- claude_mpm/services/agents/sources/git_source_sync_service.py +45 -6
- claude_mpm/services/agents/toolchain_detector.py +6 -5
- claude_mpm/services/analysis/__init__.py +35 -0
- claude_mpm/services/analysis/clone_detector.py +1030 -0
- claude_mpm/services/analysis/postmortem_reporter.py +474 -0
- claude_mpm/services/analysis/postmortem_service.py +765 -0
- claude_mpm/services/command_deployment_service.py +106 -5
- claude_mpm/services/core/base.py +7 -2
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +7 -15
- claude_mpm/services/event_bus/config.py +3 -1
- claude_mpm/services/git/git_operations_service.py +8 -8
- claude_mpm/services/mcp_config_manager.py +75 -145
- claude_mpm/services/mcp_service_verifier.py +6 -3
- claude_mpm/services/monitor/daemon.py +37 -10
- claude_mpm/services/monitor/daemon_manager.py +134 -21
- claude_mpm/services/monitor/server.py +225 -19
- claude_mpm/services/project/project_organizer.py +4 -0
- claude_mpm/services/runner_configuration_service.py +16 -3
- claude_mpm/services/session_management_service.py +16 -4
- claude_mpm/services/socketio/event_normalizer.py +15 -1
- claude_mpm/services/socketio/server/core.py +160 -21
- claude_mpm/services/version_control/git_operations.py +103 -0
- claude_mpm/utils/agent_filters.py +261 -0
- claude_mpm/utils/gitignore.py +3 -0
- claude_mpm/utils/migration.py +372 -0
- claude_mpm/utils/progress.py +5 -1
- {claude_mpm-5.0.2.dist-info โ claude_mpm-5.4.3.dist-info}/METADATA +69 -84
- {claude_mpm-5.0.2.dist-info โ claude_mpm-5.4.3.dist-info}/RECORD +112 -153
- {claude_mpm-5.0.2.dist-info โ claude_mpm-5.4.3.dist-info}/entry_points.txt +0 -2
- claude_mpm/dashboard/analysis_runner.py +0 -455
- claude_mpm/dashboard/index.html +0 -13
- claude_mpm/dashboard/open_dashboard.py +0 -66
- claude_mpm/dashboard/static/css/activity.css +0 -1958
- claude_mpm/dashboard/static/css/connection-status.css +0 -370
- claude_mpm/dashboard/static/css/dashboard.css +0 -4701
- claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
- claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
- claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
- claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
- claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
- claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
- claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
- claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
- claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
- claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
- claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
- claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
- claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
- claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
- claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
- claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
- claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
- claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
- claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
- claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
- claude_mpm/dashboard/static/js/connection-manager.js +0 -536
- claude_mpm/dashboard/static/js/dashboard.js +0 -1914
- claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
- claude_mpm/dashboard/static/js/socket-client.js +0 -1474
- claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
- claude_mpm/dashboard/static/socket.io.min.js +0 -7
- claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
- claude_mpm/dashboard/templates/code_simple.html +0 -153
- claude_mpm/dashboard/templates/index.html +0 -606
- claude_mpm/dashboard/test_dashboard.html +0 -372
- claude_mpm/scripts/mcp_server.py +0 -75
- claude_mpm/scripts/mcp_wrapper.py +0 -39
- claude_mpm/services/mcp_gateway/__init__.py +0 -159
- claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
- claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
- claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
- claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
- claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
- claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
- claude_mpm/services/mcp_gateway/core/base.py +0 -312
- claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
- claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
- claude_mpm/services/mcp_gateway/core/process_pool.py +0 -971
- claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
- claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
- claude_mpm/services/mcp_gateway/main.py +0 -589
- claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
- claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
- claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
- claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
- claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
- claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
- claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
- claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
- claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
- /claude_mpm/agents/{OUTPUT_STYLE.md โ CLAUDE_MPM_OUTPUT_STYLE.md} +0 -0
- {claude_mpm-5.0.2.dist-info โ claude_mpm-5.4.3.dist-info}/WHEEL +0 -0
- {claude_mpm-5.0.2.dist-info โ claude_mpm-5.4.3.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.0.2.dist-info โ claude_mpm-5.4.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Protocol definitions for session management dependencies.
|
|
2
|
+
|
|
3
|
+
These protocols use Python's typing.Protocol for structural subtyping,
|
|
4
|
+
allowing dependency injection without circular imports.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Dict, Optional, Protocol, Tuple
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class InteractiveSessionProtocol(Protocol):
|
|
11
|
+
"""Protocol for interactive session orchestration.
|
|
12
|
+
|
|
13
|
+
This protocol defines the interface that SessionManagementService
|
|
14
|
+
needs from InteractiveSession without requiring a full import.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def initialize_interactive_session(self) -> Tuple[bool, Optional[str]]:
|
|
18
|
+
"""Initialize the interactive session environment.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
Tuple of (success, error_message)
|
|
22
|
+
"""
|
|
23
|
+
...
|
|
24
|
+
|
|
25
|
+
def setup_interactive_environment(self) -> Tuple[bool, Dict[str, Any]]:
|
|
26
|
+
"""Set up the interactive environment including agents and commands.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Tuple of (success, environment_dict)
|
|
30
|
+
"""
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
def handle_interactive_input(self, environment: Dict[str, Any]) -> bool:
|
|
34
|
+
"""Handle the interactive input/output loop.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
environment: Dictionary with command, env vars, and session info
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
True if successful, False otherwise
|
|
41
|
+
"""
|
|
42
|
+
...
|
|
43
|
+
|
|
44
|
+
def cleanup_interactive_session(self) -> None:
|
|
45
|
+
"""Clean up resources after interactive session ends."""
|
|
46
|
+
...
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class OneshotSessionProtocol(Protocol):
|
|
50
|
+
"""Protocol for oneshot session orchestration.
|
|
51
|
+
|
|
52
|
+
This protocol defines the interface that SessionManagementService
|
|
53
|
+
needs from OneshotSession without requiring a full import.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def initialize_session(self, prompt: str) -> Tuple[bool, Optional[str]]:
|
|
57
|
+
"""Initialize the oneshot session.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
prompt: The command or prompt to execute
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Tuple of (success, error_message)
|
|
64
|
+
"""
|
|
65
|
+
...
|
|
66
|
+
|
|
67
|
+
def deploy_agents(self) -> bool:
|
|
68
|
+
"""Deploy agents for the session.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
True if successful, False otherwise
|
|
72
|
+
"""
|
|
73
|
+
...
|
|
74
|
+
|
|
75
|
+
def setup_infrastructure(self) -> Dict[str, Any]:
|
|
76
|
+
"""Set up session infrastructure.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Dictionary with infrastructure configuration
|
|
80
|
+
"""
|
|
81
|
+
...
|
|
82
|
+
|
|
83
|
+
def execute_command(
|
|
84
|
+
self, prompt: str, context: Optional[str], infrastructure: Dict[str, Any]
|
|
85
|
+
) -> Tuple[bool, Optional[str]]:
|
|
86
|
+
"""Execute the command with given context and infrastructure.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
prompt: Command to execute
|
|
90
|
+
context: Optional context
|
|
91
|
+
infrastructure: Infrastructure configuration
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Tuple of (success, response)
|
|
95
|
+
"""
|
|
96
|
+
...
|
|
97
|
+
|
|
98
|
+
def cleanup_session(self) -> None:
|
|
99
|
+
"""Clean up session resources."""
|
|
100
|
+
...
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class SessionManagementProtocol(Protocol):
|
|
104
|
+
"""Protocol for session management service.
|
|
105
|
+
|
|
106
|
+
This protocol defines the interface that ClaudeRunner needs from
|
|
107
|
+
SessionManagementService without requiring a full import.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
def run_interactive_session(self, initial_context: Optional[str] = None) -> bool:
|
|
111
|
+
"""Run Claude in interactive mode.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
initial_context: Optional initial context to pass to Claude
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
True if successful, False otherwise
|
|
118
|
+
"""
|
|
119
|
+
...
|
|
120
|
+
|
|
121
|
+
def run_oneshot_session(self, prompt: str, context: Optional[str] = None) -> bool:
|
|
122
|
+
"""Run Claude with a single prompt.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
prompt: The command or prompt to execute
|
|
126
|
+
context: Optional context to prepend to the prompt
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
True if successful, False otherwise
|
|
130
|
+
"""
|
|
131
|
+
...
|
|
@@ -16,11 +16,14 @@ class SingletonManager:
|
|
|
16
16
|
|
|
17
17
|
Reduces duplication by providing thread-safe singleton patterns
|
|
18
18
|
that can be used across different classes.
|
|
19
|
+
|
|
20
|
+
Uses RLock (reentrant locks) to support recursive calls from
|
|
21
|
+
SingletonMixin.__new__ and @singleton decorator patterns.
|
|
19
22
|
"""
|
|
20
23
|
|
|
21
24
|
_instances: Dict[Type, Any] = {}
|
|
22
|
-
_locks: Dict[Type, threading.
|
|
23
|
-
_global_lock = threading.
|
|
25
|
+
_locks: Dict[Type, threading.RLock] = {}
|
|
26
|
+
_global_lock = threading.RLock()
|
|
24
27
|
|
|
25
28
|
@classmethod
|
|
26
29
|
def get_instance(
|
|
@@ -42,7 +45,7 @@ class SingletonManager:
|
|
|
42
45
|
if singleton_class not in cls._locks:
|
|
43
46
|
with cls._global_lock:
|
|
44
47
|
if singleton_class not in cls._locks:
|
|
45
|
-
cls._locks[singleton_class] = threading.
|
|
48
|
+
cls._locks[singleton_class] = threading.RLock()
|
|
46
49
|
|
|
47
50
|
# Get instance with class-specific lock
|
|
48
51
|
with cls._locks[singleton_class]:
|
|
@@ -50,9 +53,13 @@ class SingletonManager:
|
|
|
50
53
|
logger = get_logger("singleton_manager")
|
|
51
54
|
logger.debug(f"Creating singleton instance: {singleton_class.__name__}")
|
|
52
55
|
|
|
53
|
-
|
|
56
|
+
# Use object.__new__ to bypass SingletonMixin.__new__ and avoid recursion
|
|
57
|
+
instance = object.__new__(singleton_class)
|
|
54
58
|
cls._instances[singleton_class] = instance
|
|
55
59
|
|
|
60
|
+
# Now call __init__ explicitly with the stored instance
|
|
61
|
+
instance.__init__(*args, **kwargs)
|
|
62
|
+
|
|
56
63
|
return instance
|
|
57
64
|
|
|
58
65
|
return cls._instances[singleton_class]
|
claude_mpm/core/socketio_pool.py
CHANGED
|
@@ -55,8 +55,8 @@ class CircuitState(Enum):
|
|
|
55
55
|
class ConnectionStats:
|
|
56
56
|
"""Connection statistics for monitoring."""
|
|
57
57
|
|
|
58
|
-
created_at: datetime = field(default_factory=datetime.now)
|
|
59
|
-
last_used: datetime = field(default_factory=datetime.now)
|
|
58
|
+
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
59
|
+
last_used: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
60
60
|
events_sent: int = 0
|
|
61
61
|
errors: int = 0
|
|
62
62
|
consecutive_errors: int = 0
|
|
@@ -70,7 +70,7 @@ class BatchEvent:
|
|
|
70
70
|
namespace: str
|
|
71
71
|
event: str
|
|
72
72
|
data: Dict[str, Any]
|
|
73
|
-
timestamp: datetime = field(default_factory=datetime.now)
|
|
73
|
+
timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
74
74
|
|
|
75
75
|
|
|
76
76
|
class CircuitBreaker:
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""System context utilities for Claude runner.
|
|
2
|
+
|
|
3
|
+
This module provides shared context creation functions that can be used
|
|
4
|
+
across different modules without circular dependencies.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def create_simple_context() -> str:
|
|
9
|
+
"""Create basic context for Claude.
|
|
10
|
+
|
|
11
|
+
This function is extracted to avoid circular imports between
|
|
12
|
+
claude_runner.py and interactive_session.py.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
Basic system context string for Claude
|
|
16
|
+
"""
|
|
17
|
+
return """You are Claude Code running in Claude MPM (Multi-Agent Project Manager).
|
|
18
|
+
|
|
19
|
+
You have access to native subagents via the Task tool with subagent_type parameter:
|
|
20
|
+
- engineer: For coding, implementation, and technical tasks
|
|
21
|
+
- qa: For testing, validation, and quality assurance
|
|
22
|
+
- documentation: For docs, guides, and explanations
|
|
23
|
+
- research: For investigation and analysis
|
|
24
|
+
- security: For security-related tasks
|
|
25
|
+
- ops: For deployment and infrastructure
|
|
26
|
+
- version-control: For git and version management
|
|
27
|
+
- data-engineer: For data processing and APIs
|
|
28
|
+
|
|
29
|
+
Use these agents by calling: Task(description="task description", subagent_type="agent_name")
|
|
30
|
+
|
|
31
|
+
IMPORTANT: The Task tool accepts both naming formats:
|
|
32
|
+
- Capitalized format: "Research", "Engineer", "QA", "Version Control", "Data Engineer"
|
|
33
|
+
- Lowercase format: "research", "engineer", "qa", "version-control", "data-engineer"
|
|
34
|
+
|
|
35
|
+
Both formats work correctly. When you see capitalized names (matching TodoWrite prefixes),
|
|
36
|
+
automatically normalize them to lowercase-hyphenated format for the Task tool.
|
|
37
|
+
|
|
38
|
+
Work efficiently and delegate appropriately to subagents when needed."""
|
|
@@ -84,6 +84,12 @@ class AgentMetadata:
|
|
|
84
84
|
version: str = "1.0.0"
|
|
85
85
|
author: str = ""
|
|
86
86
|
tags: List[str] = None
|
|
87
|
+
# NEW: Collection-based identification fields
|
|
88
|
+
collection_id: Optional[str] = None # Format: owner/repo-name
|
|
89
|
+
source_path: Optional[str] = None # Relative path in repo
|
|
90
|
+
canonical_id: Optional[str] = (
|
|
91
|
+
None # Format: collection_id:agent_id or legacy:filename
|
|
92
|
+
)
|
|
87
93
|
|
|
88
94
|
def __post_init__(self):
|
|
89
95
|
"""Initialize default values for mutable fields."""
|
|
@@ -168,26 +174,16 @@ class UnifiedAgentRegistry:
|
|
|
168
174
|
if project_path.exists():
|
|
169
175
|
self.discovery_paths.append(project_path)
|
|
170
176
|
|
|
171
|
-
#
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
local_project_path.exists()
|
|
175
|
-
and local_project_path not in self.discovery_paths
|
|
176
|
-
):
|
|
177
|
-
self.discovery_paths.append(local_project_path)
|
|
178
|
-
logger.debug(f"Added local project templates path: {local_project_path}")
|
|
177
|
+
# NOTE: .claude-mpm/agents/ is deprecated in the simplified architecture
|
|
178
|
+
# Source agents come from ~/.claude-mpm/cache/remote-agents/
|
|
179
|
+
# Deployed agents go to .claude/agents/
|
|
179
180
|
|
|
180
|
-
# User-level agents
|
|
181
|
+
# User-level agents (deprecated in simplified architecture)
|
|
182
|
+
# Keeping for backward compatibility but not actively used
|
|
181
183
|
user_path = self.path_manager.get_user_agents_dir()
|
|
182
184
|
if user_path.exists():
|
|
183
185
|
self.discovery_paths.append(user_path)
|
|
184
186
|
|
|
185
|
-
# Also check for user JSON templates in ~/.claude-mpm/agents/
|
|
186
|
-
local_user_path = Path.home() / ".claude-mpm" / "agents"
|
|
187
|
-
if local_user_path.exists() and local_user_path not in self.discovery_paths:
|
|
188
|
-
self.discovery_paths.append(local_user_path)
|
|
189
|
-
logger.debug(f"Added local user templates path: {local_user_path}")
|
|
190
|
-
|
|
191
187
|
# System-level agents (includes templates as a subdirectory)
|
|
192
188
|
system_path = self.path_manager.get_system_agents_dir()
|
|
193
189
|
if system_path.exists():
|
|
@@ -690,6 +686,111 @@ class UnifiedAgentRegistry:
|
|
|
690
686
|
"""Get all memory-aware agents."""
|
|
691
687
|
return self.list_agents(agent_type=AgentType.MEMORY_AWARE)
|
|
692
688
|
|
|
689
|
+
def get_agents_by_collection(self, collection_id: str) -> List[AgentMetadata]:
|
|
690
|
+
"""Get all agents from a specific collection.
|
|
691
|
+
|
|
692
|
+
NEW: Enables collection-based agent selection.
|
|
693
|
+
|
|
694
|
+
Args:
|
|
695
|
+
collection_id: Collection identifier (e.g., "bobmatnyc/claude-mpm-agents")
|
|
696
|
+
|
|
697
|
+
Returns:
|
|
698
|
+
List of agents from the specified collection
|
|
699
|
+
|
|
700
|
+
Example:
|
|
701
|
+
>>> registry = get_agent_registry()
|
|
702
|
+
>>> agents = registry.get_agents_by_collection("bobmatnyc/claude-mpm-agents")
|
|
703
|
+
>>> len(agents)
|
|
704
|
+
45
|
|
705
|
+
"""
|
|
706
|
+
if not self.registry:
|
|
707
|
+
self.discover_agents()
|
|
708
|
+
|
|
709
|
+
collection_agents = [
|
|
710
|
+
agent
|
|
711
|
+
for agent in self.registry.values()
|
|
712
|
+
if agent.collection_id == collection_id
|
|
713
|
+
]
|
|
714
|
+
|
|
715
|
+
return sorted(collection_agents, key=lambda a: a.name)
|
|
716
|
+
|
|
717
|
+
def list_collections(self) -> List[Dict[str, Any]]:
|
|
718
|
+
"""List all available collections with agent counts.
|
|
719
|
+
|
|
720
|
+
NEW: Provides overview of available collections.
|
|
721
|
+
|
|
722
|
+
Returns:
|
|
723
|
+
List of collection info dictionaries with:
|
|
724
|
+
- collection_id: Collection identifier
|
|
725
|
+
- agent_count: Number of agents in collection
|
|
726
|
+
- agents: List of agent names in collection
|
|
727
|
+
|
|
728
|
+
Example:
|
|
729
|
+
>>> registry = get_agent_registry()
|
|
730
|
+
>>> collections = registry.list_collections()
|
|
731
|
+
>>> collections
|
|
732
|
+
[
|
|
733
|
+
{
|
|
734
|
+
"collection_id": "bobmatnyc/claude-mpm-agents",
|
|
735
|
+
"agent_count": 45,
|
|
736
|
+
"agents": ["pm", "engineer", "qa", ...]
|
|
737
|
+
}
|
|
738
|
+
]
|
|
739
|
+
"""
|
|
740
|
+
if not self.registry:
|
|
741
|
+
self.discover_agents()
|
|
742
|
+
|
|
743
|
+
# Group agents by collection_id
|
|
744
|
+
collections_map: Dict[str, List[str]] = {}
|
|
745
|
+
|
|
746
|
+
for agent in self.registry.values():
|
|
747
|
+
if not agent.collection_id:
|
|
748
|
+
# Skip agents without collection (legacy or local)
|
|
749
|
+
continue
|
|
750
|
+
|
|
751
|
+
if agent.collection_id not in collections_map:
|
|
752
|
+
collections_map[agent.collection_id] = []
|
|
753
|
+
|
|
754
|
+
collections_map[agent.collection_id].append(agent.name)
|
|
755
|
+
|
|
756
|
+
# Convert to list format
|
|
757
|
+
collections = [
|
|
758
|
+
{
|
|
759
|
+
"collection_id": coll_id,
|
|
760
|
+
"agent_count": len(agent_names),
|
|
761
|
+
"agents": sorted(agent_names),
|
|
762
|
+
}
|
|
763
|
+
for coll_id, agent_names in collections_map.items()
|
|
764
|
+
]
|
|
765
|
+
|
|
766
|
+
return sorted(collections, key=lambda c: c["collection_id"])
|
|
767
|
+
|
|
768
|
+
def get_agent_by_canonical_id(self, canonical_id: str) -> Optional[AgentMetadata]:
|
|
769
|
+
"""Get agent by canonical ID (primary matching key).
|
|
770
|
+
|
|
771
|
+
NEW: Primary matching method using canonical_id.
|
|
772
|
+
|
|
773
|
+
Args:
|
|
774
|
+
canonical_id: Canonical identifier (e.g., "bobmatnyc/claude-mpm-agents:pm")
|
|
775
|
+
|
|
776
|
+
Returns:
|
|
777
|
+
AgentMetadata if found, None otherwise
|
|
778
|
+
|
|
779
|
+
Example:
|
|
780
|
+
>>> registry = get_agent_registry()
|
|
781
|
+
>>> agent = registry.get_agent_by_canonical_id("bobmatnyc/claude-mpm-agents:pm")
|
|
782
|
+
>>> agent.name
|
|
783
|
+
'Project Manager Agent'
|
|
784
|
+
"""
|
|
785
|
+
if not self.registry:
|
|
786
|
+
self.discover_agents()
|
|
787
|
+
|
|
788
|
+
for agent in self.registry.values():
|
|
789
|
+
if agent.canonical_id == canonical_id:
|
|
790
|
+
return agent
|
|
791
|
+
|
|
792
|
+
return None
|
|
793
|
+
|
|
693
794
|
def add_discovery_path(self, path: Union[str, Path]) -> None:
|
|
694
795
|
"""Add a new path for agent discovery."""
|
|
695
796
|
path = Path(path)
|
|
@@ -809,6 +910,21 @@ def get_registry_stats() -> Dict[str, Any]:
|
|
|
809
910
|
return get_agent_registry().get_registry_stats()
|
|
810
911
|
|
|
811
912
|
|
|
913
|
+
def get_agents_by_collection(collection_id: str) -> List[AgentMetadata]:
|
|
914
|
+
"""Get all agents from a specific collection."""
|
|
915
|
+
return get_agent_registry().get_agents_by_collection(collection_id)
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
def list_collections() -> List[Dict[str, Any]]:
|
|
919
|
+
"""List all available collections."""
|
|
920
|
+
return get_agent_registry().list_collections()
|
|
921
|
+
|
|
922
|
+
|
|
923
|
+
def get_agent_by_canonical_id(canonical_id: str) -> Optional[AgentMetadata]:
|
|
924
|
+
"""Get agent by canonical ID."""
|
|
925
|
+
return get_agent_registry().get_agent_by_canonical_id(canonical_id)
|
|
926
|
+
|
|
927
|
+
|
|
812
928
|
# Legacy function names for backward compatibility
|
|
813
929
|
def listAgents() -> List[str]:
|
|
814
930
|
"""Legacy function: Get list of agent names."""
|
|
@@ -838,14 +954,16 @@ __all__ = [
|
|
|
838
954
|
"discover_agents",
|
|
839
955
|
"discover_agents_sync",
|
|
840
956
|
"get_agent",
|
|
957
|
+
"get_agent_by_canonical_id",
|
|
841
958
|
"get_agent_names",
|
|
842
959
|
"get_agent_registry",
|
|
960
|
+
"get_agents_by_collection",
|
|
843
961
|
"get_core_agents",
|
|
844
962
|
"get_project_agents",
|
|
845
963
|
"get_registry_stats",
|
|
846
964
|
"get_specialized_agents",
|
|
847
|
-
# Legacy compatibility
|
|
848
965
|
"listAgents",
|
|
849
966
|
"list_agents",
|
|
850
967
|
"list_agents_all",
|
|
968
|
+
"list_collections",
|
|
851
969
|
]
|
|
@@ -218,6 +218,27 @@ class DevelopmentConfig(BaseModel):
|
|
|
218
218
|
)
|
|
219
219
|
|
|
220
220
|
|
|
221
|
+
class DocumentationConfig(BaseModel):
|
|
222
|
+
"""Documentation routing and management configuration."""
|
|
223
|
+
|
|
224
|
+
docs_path: str = Field(
|
|
225
|
+
default="docs/research/",
|
|
226
|
+
description="Default path for session documentation (relative to project root)",
|
|
227
|
+
)
|
|
228
|
+
attach_to_tickets: bool = Field(
|
|
229
|
+
default=True,
|
|
230
|
+
description="Attach work products to tickets when ticket context exists",
|
|
231
|
+
)
|
|
232
|
+
backup_locally: bool = Field(
|
|
233
|
+
default=True,
|
|
234
|
+
description="Always create local backup copies of documentation",
|
|
235
|
+
)
|
|
236
|
+
enable_ticket_detection: bool = Field(
|
|
237
|
+
default=True,
|
|
238
|
+
description="Enable automatic ticket context detection from user messages",
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
|
|
221
242
|
class UnifiedConfig(BaseSettings):
|
|
222
243
|
"""
|
|
223
244
|
Unified configuration model for Claude MPM.
|
|
@@ -242,6 +263,7 @@ class UnifiedConfig(BaseSettings):
|
|
|
242
263
|
performance: PerformanceConfig = Field(default_factory=PerformanceConfig)
|
|
243
264
|
sessions: SessionConfig = Field(default_factory=SessionConfig)
|
|
244
265
|
development: DevelopmentConfig = Field(default_factory=DevelopmentConfig)
|
|
266
|
+
documentation: DocumentationConfig = Field(default_factory=DocumentationConfig)
|
|
245
267
|
|
|
246
268
|
# Path configuration
|
|
247
269
|
base_path: Optional[Path] = Field(
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Cross-process correlation storage using .claude-mpm directory."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_correlation_dir() -> Path:
|
|
9
|
+
"""Get correlation directory in project's .claude-mpm folder."""
|
|
10
|
+
# Use CWD's .claude-mpm directory (where hooks run from)
|
|
11
|
+
cwd = Path.cwd()
|
|
12
|
+
return cwd / ".claude-mpm" / "correlations"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
TTL_SECONDS = 3600 # 1 hour
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CorrelationManager:
|
|
19
|
+
"""Manages correlation IDs across separate hook processes."""
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def store(session_id: str, tool_call_id: str, tool_name: str) -> None:
|
|
23
|
+
"""Store correlation data for later retrieval by post_tool."""
|
|
24
|
+
correlation_dir = get_correlation_dir()
|
|
25
|
+
correlation_dir.mkdir(parents=True, exist_ok=True)
|
|
26
|
+
filepath = correlation_dir / f"correlation_{session_id}.json"
|
|
27
|
+
data = {
|
|
28
|
+
"tool_call_id": tool_call_id,
|
|
29
|
+
"tool_name": tool_name,
|
|
30
|
+
"timestamp": time.time(),
|
|
31
|
+
}
|
|
32
|
+
filepath.write_text(json.dumps(data))
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def retrieve(session_id: str) -> str | None:
|
|
36
|
+
"""Retrieve and delete correlation data from temp file."""
|
|
37
|
+
correlation_dir = get_correlation_dir()
|
|
38
|
+
filepath = correlation_dir / f"correlation_{session_id}.json"
|
|
39
|
+
if not filepath.exists():
|
|
40
|
+
return None
|
|
41
|
+
try:
|
|
42
|
+
data = json.loads(filepath.read_text())
|
|
43
|
+
filepath.unlink() # Delete after reading
|
|
44
|
+
return data.get("tool_call_id")
|
|
45
|
+
except (json.JSONDecodeError, OSError):
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def cleanup_old() -> None:
|
|
50
|
+
"""Remove correlation files older than TTL."""
|
|
51
|
+
correlation_dir = get_correlation_dir()
|
|
52
|
+
if not correlation_dir.exists():
|
|
53
|
+
return
|
|
54
|
+
now = time.time()
|
|
55
|
+
for filepath in correlation_dir.glob("correlation_*.json"):
|
|
56
|
+
try:
|
|
57
|
+
if now - filepath.stat().st_mtime > TTL_SECONDS:
|
|
58
|
+
filepath.unlink()
|
|
59
|
+
except OSError:
|
|
60
|
+
pass
|
|
@@ -9,6 +9,7 @@ import os
|
|
|
9
9
|
import re
|
|
10
10
|
import subprocess
|
|
11
11
|
import sys
|
|
12
|
+
import uuid
|
|
12
13
|
from datetime import datetime, timezone
|
|
13
14
|
from pathlib import Path
|
|
14
15
|
from typing import Optional
|
|
@@ -134,6 +135,9 @@ class EventHandlers:
|
|
|
134
135
|
tool_name = event.get("tool_name", "")
|
|
135
136
|
tool_input = event.get("tool_input", {})
|
|
136
137
|
|
|
138
|
+
# Generate unique tool call ID for correlation with post_tool event
|
|
139
|
+
tool_call_id = str(uuid.uuid4())
|
|
140
|
+
|
|
137
141
|
# Extract key parameters based on tool type
|
|
138
142
|
tool_params = extract_tool_parameters(tool_name, tool_input)
|
|
139
143
|
|
|
@@ -144,6 +148,8 @@ class EventHandlers:
|
|
|
144
148
|
working_dir = event.get("cwd", "")
|
|
145
149
|
git_branch = self._get_git_branch(working_dir) if working_dir else "Unknown"
|
|
146
150
|
|
|
151
|
+
timestamp = datetime.now(timezone.utc).isoformat()
|
|
152
|
+
|
|
147
153
|
pre_tool_data = {
|
|
148
154
|
"tool_name": tool_name,
|
|
149
155
|
"operation_type": operation_type,
|
|
@@ -151,15 +157,27 @@ class EventHandlers:
|
|
|
151
157
|
"session_id": event.get("session_id", ""),
|
|
152
158
|
"working_directory": working_dir,
|
|
153
159
|
"git_branch": git_branch,
|
|
154
|
-
"timestamp":
|
|
160
|
+
"timestamp": timestamp,
|
|
155
161
|
"parameter_count": len(tool_input) if isinstance(tool_input, dict) else 0,
|
|
156
162
|
"is_file_operation": tool_name
|
|
157
163
|
in ["Write", "Edit", "MultiEdit", "Read", "LS", "Glob"],
|
|
158
164
|
"is_execution": tool_name in ["Bash", "NotebookEdit"],
|
|
159
165
|
"is_delegation": tool_name == "Task",
|
|
160
166
|
"security_risk": assess_security_risk(tool_name, tool_input),
|
|
167
|
+
"correlation_id": tool_call_id, # Add correlation_id for pre/post correlation
|
|
161
168
|
}
|
|
162
169
|
|
|
170
|
+
# Store tool_call_id using CorrelationManager for cross-process retrieval
|
|
171
|
+
if session_id:
|
|
172
|
+
from .correlation_manager import CorrelationManager
|
|
173
|
+
|
|
174
|
+
CorrelationManager.store(session_id, tool_call_id, tool_name)
|
|
175
|
+
if DEBUG:
|
|
176
|
+
print(
|
|
177
|
+
f" - Generated tool_call_id: {tool_call_id[:8]}... for session {session_id[:8]}...",
|
|
178
|
+
file=sys.stderr,
|
|
179
|
+
)
|
|
180
|
+
|
|
163
181
|
# Add delegation-specific data if this is a Task tool
|
|
164
182
|
if tool_name == "Task" and isinstance(tool_input, dict):
|
|
165
183
|
self._handle_task_delegation(tool_input, pre_tool_data, session_id)
|
|
@@ -375,6 +393,7 @@ class EventHandlers:
|
|
|
375
393
|
"""
|
|
376
394
|
tool_name = event.get("tool_name", "")
|
|
377
395
|
exit_code = event.get("exit_code", 0)
|
|
396
|
+
session_id = event.get("session_id", "")
|
|
378
397
|
|
|
379
398
|
# Extract result data
|
|
380
399
|
result_data = extract_tool_results(event)
|
|
@@ -386,6 +405,16 @@ class EventHandlers:
|
|
|
386
405
|
working_dir = event.get("cwd", "")
|
|
387
406
|
git_branch = self._get_git_branch(working_dir) if working_dir else "Unknown"
|
|
388
407
|
|
|
408
|
+
# Retrieve tool_call_id using CorrelationManager for cross-process correlation
|
|
409
|
+
from .correlation_manager import CorrelationManager
|
|
410
|
+
|
|
411
|
+
tool_call_id = CorrelationManager.retrieve(session_id) if session_id else None
|
|
412
|
+
if DEBUG and tool_call_id:
|
|
413
|
+
print(
|
|
414
|
+
f" - Retrieved tool_call_id: {tool_call_id[:8]}... for session {session_id[:8]}...",
|
|
415
|
+
file=sys.stderr,
|
|
416
|
+
)
|
|
417
|
+
|
|
389
418
|
post_tool_data = {
|
|
390
419
|
"tool_name": tool_name,
|
|
391
420
|
"exit_code": exit_code,
|
|
@@ -399,7 +428,7 @@ class EventHandlers:
|
|
|
399
428
|
),
|
|
400
429
|
"duration_ms": duration,
|
|
401
430
|
"result_summary": result_data,
|
|
402
|
-
"session_id":
|
|
431
|
+
"session_id": session_id,
|
|
403
432
|
"working_directory": working_dir,
|
|
404
433
|
"git_branch": git_branch,
|
|
405
434
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
@@ -412,6 +441,10 @@ class EventHandlers:
|
|
|
412
441
|
),
|
|
413
442
|
}
|
|
414
443
|
|
|
444
|
+
# Add correlation_id if available for correlation with pre_tool
|
|
445
|
+
if tool_call_id:
|
|
446
|
+
post_tool_data["correlation_id"] = tool_call_id
|
|
447
|
+
|
|
415
448
|
# Handle Task delegation completion for memory hooks and response tracking
|
|
416
449
|
if tool_name == "Task":
|
|
417
450
|
session_id = event.get("session_id", "")
|
|
@@ -304,6 +304,10 @@ class ClaudeHookHandler:
|
|
|
304
304
|
# Perform periodic cleanup if needed
|
|
305
305
|
if self.state_manager.increment_events_processed():
|
|
306
306
|
self.state_manager.cleanup_old_entries()
|
|
307
|
+
# Also cleanup old correlation files
|
|
308
|
+
from .correlation_manager import CorrelationManager
|
|
309
|
+
|
|
310
|
+
CorrelationManager.cleanup_old()
|
|
307
311
|
if DEBUG:
|
|
308
312
|
print(
|
|
309
313
|
f"๐งน Performed cleanup after {self.state_manager.events_processed} events",
|
|
@@ -5,8 +5,19 @@ This module provides utilities for integrating with the memory system,
|
|
|
5
5
|
including pre and post delegation hooks.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import logging
|
|
9
9
|
import sys
|
|
10
|
+
|
|
11
|
+
# Reconfigure logging to INFO level BEFORE kuzu-memory imports
|
|
12
|
+
# This overrides kuzu-memory's WARNING-level basicConfig (fixes 1M-445)
|
|
13
|
+
logging.basicConfig(
|
|
14
|
+
level=logging.INFO,
|
|
15
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
16
|
+
force=True, # Python 3.8+ - reconfigures root logger
|
|
17
|
+
stream=sys.stderr,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
import os
|
|
10
21
|
from datetime import datetime, timezone
|
|
11
22
|
from typing import Optional
|
|
12
23
|
|
|
Binary file
|