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
|
Binary file
|
|
@@ -115,6 +115,9 @@ class ConnectionManagerService:
|
|
|
115
115
|
- Fallback: HTTP POST for reliability when direct connection fails
|
|
116
116
|
- Eliminates duplicate events from multiple emission paths
|
|
117
117
|
"""
|
|
118
|
+
# Extract tool_call_id from data if present for correlation
|
|
119
|
+
tool_call_id = data.get("tool_call_id")
|
|
120
|
+
|
|
118
121
|
# Create event data for normalization
|
|
119
122
|
raw_event = {
|
|
120
123
|
"type": "hook",
|
|
@@ -123,6 +126,7 @@ class ConnectionManagerService:
|
|
|
123
126
|
"data": data,
|
|
124
127
|
"source": "claude_hooks", # Identify the source
|
|
125
128
|
"session_id": data.get("sessionId"), # Include session if available
|
|
129
|
+
"correlation_id": tool_call_id, # Set from tool_call_id for event correlation
|
|
126
130
|
}
|
|
127
131
|
|
|
128
132
|
# Normalize the event using EventNormalizer for consistent schema
|
|
@@ -103,6 +103,10 @@ class AgentMetadata:
|
|
|
103
103
|
author: Optional[str] = None
|
|
104
104
|
tags: List[str] = field(default_factory=list)
|
|
105
105
|
specializations: List[str] = field(default_factory=list)
|
|
106
|
+
# NEW: Collection metadata for enhanced agent matching
|
|
107
|
+
collection_id: Optional[str] = None # Format: owner/repo-name
|
|
108
|
+
source_path: Optional[str] = None # Relative path in repository
|
|
109
|
+
canonical_id: Optional[str] = None # Format: collection_id:agent_id
|
|
106
110
|
|
|
107
111
|
def increment_serial_version(self) -> None:
|
|
108
112
|
"""Increment the patch version number.
|
|
@@ -181,6 +185,9 @@ class AgentDefinition:
|
|
|
181
185
|
"author": self.metadata.author,
|
|
182
186
|
"tags": self.metadata.tags,
|
|
183
187
|
"specializations": self.metadata.specializations,
|
|
188
|
+
"collection_id": self.metadata.collection_id,
|
|
189
|
+
"source_path": self.metadata.source_path,
|
|
190
|
+
"canonical_id": self.metadata.canonical_id,
|
|
184
191
|
},
|
|
185
192
|
"primary_role": self.primary_role,
|
|
186
193
|
"when_to_use": self.when_to_use,
|
|
@@ -7,6 +7,12 @@ dashboard, which includes both the Socket.IO server and web interface.
|
|
|
7
7
|
|
|
8
8
|
WHY: Provides a simple command to start the monitoring dashboard that tracks
|
|
9
9
|
Claude MPM events and agent activity in real-time.
|
|
10
|
+
|
|
11
|
+
SINGLE INSTANCE ENFORCEMENT:
|
|
12
|
+
- Only ONE monitor instance runs at a time on port 8765 (default)
|
|
13
|
+
- If monitor already running on default port: reuse existing, open browser
|
|
14
|
+
- If user specifies --port explicitly: use that port, fail if busy
|
|
15
|
+
- No auto-increment port selection (prevents multiple instances)
|
|
10
16
|
"""
|
|
11
17
|
|
|
12
18
|
import argparse
|
|
@@ -15,12 +21,36 @@ import webbrowser
|
|
|
15
21
|
|
|
16
22
|
from claude_mpm.core.logging_config import get_logger
|
|
17
23
|
from claude_mpm.services.monitor.daemon import UnifiedMonitorDaemon
|
|
18
|
-
from claude_mpm.services.
|
|
24
|
+
from claude_mpm.services.monitor.daemon_manager import DaemonManager
|
|
19
25
|
|
|
20
26
|
DEFAULT_PORT = 8765
|
|
21
27
|
logger = get_logger(__name__)
|
|
22
28
|
|
|
23
29
|
|
|
30
|
+
def check_existing_monitor(host: str, port: int) -> bool:
|
|
31
|
+
"""Check if monitor is already running on the specified port.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
host: Host to check
|
|
35
|
+
port: Port to check
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
True if monitor is running, False otherwise
|
|
39
|
+
"""
|
|
40
|
+
try:
|
|
41
|
+
import requests
|
|
42
|
+
|
|
43
|
+
response = requests.get(f"http://{host}:{port}/health", timeout=2)
|
|
44
|
+
if response.status_code == 200:
|
|
45
|
+
data = response.json()
|
|
46
|
+
# Check if it's our claude-mpm-monitor service
|
|
47
|
+
if data.get("service") == "claude-mpm-monitor":
|
|
48
|
+
return True
|
|
49
|
+
except Exception:
|
|
50
|
+
pass
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
|
|
24
54
|
def main():
|
|
25
55
|
"""Main entry point for monitor launcher."""
|
|
26
56
|
parser = argparse.ArgumentParser(
|
|
@@ -30,8 +60,8 @@ def main():
|
|
|
30
60
|
parser.add_argument(
|
|
31
61
|
"--port",
|
|
32
62
|
type=int,
|
|
33
|
-
default=DEFAULT_PORT
|
|
34
|
-
help=f"Port to run on (default: {DEFAULT_PORT})",
|
|
63
|
+
default=None, # Changed: None means use DEFAULT_PORT with single-instance check
|
|
64
|
+
help=f"Port to run on (default: {DEFAULT_PORT}). If specified, fails if port is busy.",
|
|
35
65
|
)
|
|
36
66
|
|
|
37
67
|
parser.add_argument(
|
|
@@ -46,20 +76,70 @@ def main():
|
|
|
46
76
|
"--background", action="store_true", help="Run in background daemon mode"
|
|
47
77
|
)
|
|
48
78
|
|
|
79
|
+
parser.add_argument(
|
|
80
|
+
"--dev",
|
|
81
|
+
action="store_true",
|
|
82
|
+
help="Enable development mode with hot reload for Svelte changes",
|
|
83
|
+
)
|
|
84
|
+
|
|
49
85
|
args = parser.parse_args()
|
|
50
86
|
|
|
51
|
-
#
|
|
52
|
-
|
|
53
|
-
|
|
87
|
+
# Determine target port
|
|
88
|
+
user_specified_port = args.port is not None
|
|
89
|
+
target_port = args.port if user_specified_port else DEFAULT_PORT
|
|
90
|
+
|
|
91
|
+
# SINGLE INSTANCE ENFORCEMENT:
|
|
92
|
+
# Check if monitor already running on target port
|
|
93
|
+
if check_existing_monitor(args.host, target_port):
|
|
94
|
+
logger.info(f"Monitor already running at http://{args.host}:{target_port}")
|
|
54
95
|
|
|
55
|
-
|
|
56
|
-
|
|
96
|
+
# Open browser to existing instance if requested
|
|
97
|
+
if not args.no_browser:
|
|
98
|
+
url = f"http://{args.host}:{target_port}"
|
|
99
|
+
logger.info(f"Opening browser to existing instance: {url}")
|
|
100
|
+
webbrowser.open(url)
|
|
101
|
+
|
|
102
|
+
# Success - reusing existing instance
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
# Port selection logic:
|
|
106
|
+
# - If user specified --port: Use that exact port, fail if busy
|
|
107
|
+
# - If no --port: Use DEFAULT_PORT (8765), fail if busy
|
|
108
|
+
# - Never auto-increment to find free port
|
|
109
|
+
|
|
110
|
+
# Create daemon manager for port checking
|
|
111
|
+
daemon_manager = DaemonManager(port=target_port, host=args.host)
|
|
112
|
+
|
|
113
|
+
if not daemon_manager._is_port_available():
|
|
114
|
+
if user_specified_port:
|
|
115
|
+
# User explicitly requested a port - fail with clear message
|
|
116
|
+
logger.error(
|
|
117
|
+
f"Port {target_port} is already in use by another service. "
|
|
118
|
+
f"Please stop the existing service or choose a different port."
|
|
119
|
+
)
|
|
120
|
+
sys.exit(1)
|
|
121
|
+
else:
|
|
122
|
+
# Default port is busy - fail with helpful message
|
|
123
|
+
logger.error(
|
|
124
|
+
f"Default port {DEFAULT_PORT} is already in use by another service. "
|
|
125
|
+
f"Please stop the existing service with 'claude-mpm monitor stop' "
|
|
126
|
+
f"or specify a different port with --port."
|
|
127
|
+
)
|
|
128
|
+
sys.exit(1)
|
|
57
129
|
|
|
58
130
|
# Start the monitor daemon
|
|
59
|
-
|
|
131
|
+
if args.dev:
|
|
132
|
+
logger.info(
|
|
133
|
+
f"Starting Claude MPM monitor on {args.host}:{target_port} (DEV MODE - hot reload enabled)"
|
|
134
|
+
)
|
|
135
|
+
else:
|
|
136
|
+
logger.info(f"Starting Claude MPM monitor on {args.host}:{target_port}")
|
|
60
137
|
|
|
61
138
|
daemon = UnifiedMonitorDaemon(
|
|
62
|
-
host=args.host,
|
|
139
|
+
host=args.host,
|
|
140
|
+
port=target_port,
|
|
141
|
+
daemon_mode=args.background,
|
|
142
|
+
enable_hot_reload=args.dev,
|
|
63
143
|
)
|
|
64
144
|
|
|
65
145
|
success = daemon.start()
|
|
@@ -67,14 +147,14 @@ def main():
|
|
|
67
147
|
if success:
|
|
68
148
|
# Open browser if requested
|
|
69
149
|
if not args.no_browser:
|
|
70
|
-
url = f"http://{args.host}:{
|
|
150
|
+
url = f"http://{args.host}:{target_port}"
|
|
71
151
|
logger.info(f"Opening browser to {url}")
|
|
72
152
|
webbrowser.open(url)
|
|
73
153
|
|
|
74
154
|
if args.background:
|
|
75
|
-
logger.info(f"Monitor daemon started in background on port {
|
|
155
|
+
logger.info(f"Monitor daemon started in background on port {target_port}")
|
|
76
156
|
else:
|
|
77
|
-
logger.info(f"Monitor running on port {
|
|
157
|
+
logger.info(f"Monitor running on port {target_port}")
|
|
78
158
|
logger.info("Press Ctrl+C to stop")
|
|
79
159
|
else:
|
|
80
160
|
logger.error("Failed to start monitor")
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"""Agent Recommendation Service
|
|
2
|
+
|
|
3
|
+
WHY: Provides intelligent agent recommendations based on toolchain detection
|
|
4
|
+
and always-recommended core agents. Helps users discover and install the
|
|
5
|
+
most relevant agents for their project without manual selection.
|
|
6
|
+
|
|
7
|
+
DESIGN DECISION: Uses toolchain analysis to map detected languages/frameworks
|
|
8
|
+
to specific engineer agents, plus always includes core agents.
|
|
9
|
+
|
|
10
|
+
Architecture:
|
|
11
|
+
- Toolchain-based recommendations: Python → python-engineer, etc.
|
|
12
|
+
- Core agents (always recommended): qa-agent, research-agent, documentation-agent,
|
|
13
|
+
ticketing, local-ops-agent, version-control, security
|
|
14
|
+
- Confidence-based filtering: Only recommend high-confidence detections
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Dict, List, Optional, Set
|
|
19
|
+
|
|
20
|
+
from ...services.project.toolchain_analyzer import ToolchainAnalyzerService
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AgentRecommendationService:
|
|
24
|
+
"""Service for recommending agents based on project toolchain.
|
|
25
|
+
|
|
26
|
+
WHY: Users shouldn't have to manually figure out which agents to install.
|
|
27
|
+
This service provides intelligent recommendations based on detected stack.
|
|
28
|
+
|
|
29
|
+
DESIGN DECISION: Separated from configure.py for reusability and testability.
|
|
30
|
+
Can be used by CLI, API, or future auto-configuration features.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
# Core agents always included - matches ToolchainDetector.CORE_AGENTS
|
|
34
|
+
# Uses exact agent IDs from repository for consistency
|
|
35
|
+
CORE_AGENTS = {
|
|
36
|
+
"qa-agent",
|
|
37
|
+
"research-agent",
|
|
38
|
+
"documentation-agent",
|
|
39
|
+
"ticketing",
|
|
40
|
+
"local-ops-agent",
|
|
41
|
+
# Keep version-control and security as universal recommended agents
|
|
42
|
+
"version-control",
|
|
43
|
+
"security",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# Map detected languages to recommended engineer agents
|
|
47
|
+
LANGUAGE_TO_AGENTS: Dict[str, List[str]] = {
|
|
48
|
+
"python": [
|
|
49
|
+
"engineer/backend/python-engineer",
|
|
50
|
+
"qa/api-qa",
|
|
51
|
+
],
|
|
52
|
+
"javascript": [
|
|
53
|
+
"engineer/backend/javascript-engineer",
|
|
54
|
+
"engineer/data/typescript-engineer",
|
|
55
|
+
],
|
|
56
|
+
"typescript": [
|
|
57
|
+
"engineer/data/typescript-engineer",
|
|
58
|
+
"engineer/backend/javascript-engineer",
|
|
59
|
+
],
|
|
60
|
+
"rust": [
|
|
61
|
+
"engineer/backend/rust-engineer",
|
|
62
|
+
],
|
|
63
|
+
"go": [
|
|
64
|
+
"engineer/backend/golang-engineer",
|
|
65
|
+
],
|
|
66
|
+
"java": [
|
|
67
|
+
"engineer/backend/java-engineer",
|
|
68
|
+
],
|
|
69
|
+
"dart": [
|
|
70
|
+
"engineer/mobile/dart-engineer",
|
|
71
|
+
],
|
|
72
|
+
"php": [
|
|
73
|
+
"engineer/backend/php-engineer",
|
|
74
|
+
],
|
|
75
|
+
"ruby": [
|
|
76
|
+
"engineer/backend/ruby-engineer",
|
|
77
|
+
],
|
|
78
|
+
"swift": [
|
|
79
|
+
"engineer/mobile/swift-engineer",
|
|
80
|
+
],
|
|
81
|
+
"kotlin": [
|
|
82
|
+
"engineer/mobile/kotlin-engineer",
|
|
83
|
+
],
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# Map detected frameworks to recommended agents
|
|
87
|
+
FRAMEWORK_TO_AGENTS: Dict[str, List[str]] = {
|
|
88
|
+
# Frontend frameworks
|
|
89
|
+
"react": [
|
|
90
|
+
"engineer/frontend/react-engineer",
|
|
91
|
+
"qa/web-qa",
|
|
92
|
+
],
|
|
93
|
+
"nextjs": [
|
|
94
|
+
"engineer/frontend/nextjs-engineer",
|
|
95
|
+
"engineer/frontend/react-engineer",
|
|
96
|
+
"ops/platform/vercel-ops",
|
|
97
|
+
],
|
|
98
|
+
"vue": [
|
|
99
|
+
"engineer/frontend/vue-engineer",
|
|
100
|
+
],
|
|
101
|
+
"angular": [
|
|
102
|
+
"engineer/frontend/angular-engineer",
|
|
103
|
+
],
|
|
104
|
+
# Backend frameworks
|
|
105
|
+
"fastapi": [
|
|
106
|
+
"engineer/backend/python-engineer",
|
|
107
|
+
"qa/api-qa",
|
|
108
|
+
],
|
|
109
|
+
"django": [
|
|
110
|
+
"engineer/backend/python-engineer",
|
|
111
|
+
"qa/api-qa",
|
|
112
|
+
],
|
|
113
|
+
"flask": [
|
|
114
|
+
"engineer/backend/python-engineer",
|
|
115
|
+
"qa/api-qa",
|
|
116
|
+
],
|
|
117
|
+
"express": [
|
|
118
|
+
"engineer/backend/javascript-engineer",
|
|
119
|
+
"qa/api-qa",
|
|
120
|
+
],
|
|
121
|
+
"nest": [
|
|
122
|
+
"engineer/backend/javascript-engineer",
|
|
123
|
+
"qa/api-qa",
|
|
124
|
+
],
|
|
125
|
+
# Mobile frameworks
|
|
126
|
+
"flutter": [
|
|
127
|
+
"engineer/mobile/dart-engineer",
|
|
128
|
+
],
|
|
129
|
+
"react-native": [
|
|
130
|
+
"engineer/frontend/react-engineer",
|
|
131
|
+
"engineer/mobile/react-native-engineer",
|
|
132
|
+
],
|
|
133
|
+
# Desktop frameworks
|
|
134
|
+
"tauri": [
|
|
135
|
+
"engineer/mobile/tauri-engineer",
|
|
136
|
+
"engineer/backend/rust-engineer",
|
|
137
|
+
],
|
|
138
|
+
"electron": [
|
|
139
|
+
"engineer/backend/javascript-engineer",
|
|
140
|
+
],
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
def __init__(self, toolchain_analyzer: Optional[ToolchainAnalyzerService] = None):
|
|
144
|
+
"""Initialize agent recommendation service.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
toolchain_analyzer: Optional pre-initialized toolchain analyzer.
|
|
148
|
+
If None, creates a new instance.
|
|
149
|
+
"""
|
|
150
|
+
self.toolchain_analyzer = toolchain_analyzer or ToolchainAnalyzerService()
|
|
151
|
+
|
|
152
|
+
def get_recommended_agents(
|
|
153
|
+
self,
|
|
154
|
+
project_path: Optional[str] = None,
|
|
155
|
+
confidence_threshold: float = 0.5,
|
|
156
|
+
) -> Set[str]:
|
|
157
|
+
"""Get recommended agents for a project.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
project_path: Path to project directory. Defaults to cwd.
|
|
161
|
+
confidence_threshold: Minimum confidence for recommendations (0.0-1.0).
|
|
162
|
+
Only include detected components above this threshold.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
Set of recommended agent IDs (e.g., {"qa-agent", "research-agent", ...})
|
|
166
|
+
|
|
167
|
+
Example:
|
|
168
|
+
>>> service = AgentRecommendationService()
|
|
169
|
+
>>> recommended = service.get_recommended_agents()
|
|
170
|
+
>>> "qa-agent" in recommended
|
|
171
|
+
True
|
|
172
|
+
>>> # For Python project:
|
|
173
|
+
>>> "engineer/backend/python-engineer" in recommended
|
|
174
|
+
True
|
|
175
|
+
"""
|
|
176
|
+
# Start with core agents (always recommended)
|
|
177
|
+
recommended = self.CORE_AGENTS.copy()
|
|
178
|
+
|
|
179
|
+
# Analyze project toolchain
|
|
180
|
+
if project_path is None:
|
|
181
|
+
project_path = str(Path.cwd())
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
# ToolchainAnalyzerService.analyze_toolchain takes Path, not str
|
|
185
|
+
analysis = self.toolchain_analyzer.analyze_toolchain(Path(project_path))
|
|
186
|
+
except Exception as e:
|
|
187
|
+
# If analysis fails, just return core agents
|
|
188
|
+
print(f"Warning: Toolchain analysis failed: {e}")
|
|
189
|
+
return recommended
|
|
190
|
+
|
|
191
|
+
# Add language-specific agents
|
|
192
|
+
# ToolchainAnalysis has a single LanguageDetection object, not a list
|
|
193
|
+
if analysis.language_detection:
|
|
194
|
+
# Check primary language
|
|
195
|
+
primary_lang = analysis.language_detection.primary_language.lower()
|
|
196
|
+
if primary_lang in self.LANGUAGE_TO_AGENTS:
|
|
197
|
+
recommended.update(self.LANGUAGE_TO_AGENTS[primary_lang])
|
|
198
|
+
|
|
199
|
+
# Check secondary languages
|
|
200
|
+
for lang_component in analysis.language_detection.secondary_languages:
|
|
201
|
+
lang = lang_component.name.lower()
|
|
202
|
+
if lang in self.LANGUAGE_TO_AGENTS:
|
|
203
|
+
recommended.update(self.LANGUAGE_TO_AGENTS[lang])
|
|
204
|
+
|
|
205
|
+
# Add framework-specific agents
|
|
206
|
+
for framework in analysis.frameworks:
|
|
207
|
+
fw_name = framework.name.lower()
|
|
208
|
+
if fw_name in self.FRAMEWORK_TO_AGENTS:
|
|
209
|
+
recommended.update(self.FRAMEWORK_TO_AGENTS[fw_name])
|
|
210
|
+
|
|
211
|
+
return recommended
|
|
212
|
+
|
|
213
|
+
def get_detection_summary(self, project_path: Optional[str] = None) -> Dict:
|
|
214
|
+
"""Get human-readable summary of detected toolchain.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
project_path: Path to project directory. Defaults to cwd.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
Dict with keys:
|
|
221
|
+
- detected_languages: List of detected language names
|
|
222
|
+
- detected_frameworks: List of detected framework names
|
|
223
|
+
- recommended_count: Number of recommended agents
|
|
224
|
+
- detection_quality: "high", "medium", "low", or "none"
|
|
225
|
+
|
|
226
|
+
Example:
|
|
227
|
+
>>> summary = service.get_detection_summary()
|
|
228
|
+
>>> summary['detected_languages']
|
|
229
|
+
['Python', 'JavaScript']
|
|
230
|
+
>>> summary['recommended_count']
|
|
231
|
+
15
|
|
232
|
+
"""
|
|
233
|
+
if project_path is None:
|
|
234
|
+
project_path = str(Path.cwd())
|
|
235
|
+
|
|
236
|
+
try:
|
|
237
|
+
analysis = self.toolchain_analyzer.analyze_toolchain(Path(project_path))
|
|
238
|
+
|
|
239
|
+
# Extract languages from LanguageDetection object
|
|
240
|
+
languages = []
|
|
241
|
+
if analysis.language_detection:
|
|
242
|
+
languages.append(analysis.language_detection.primary_language)
|
|
243
|
+
languages.extend(
|
|
244
|
+
comp.name
|
|
245
|
+
for comp in analysis.language_detection.secondary_languages
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
# Extract frameworks
|
|
249
|
+
frameworks = [fw.name for fw in analysis.frameworks]
|
|
250
|
+
|
|
251
|
+
# Get recommended agents
|
|
252
|
+
recommended = self.get_recommended_agents(project_path)
|
|
253
|
+
|
|
254
|
+
# Determine detection quality from overall_confidence
|
|
255
|
+
confidence_map = {
|
|
256
|
+
"high": "high",
|
|
257
|
+
"medium": "medium",
|
|
258
|
+
"low": "low",
|
|
259
|
+
"very_low": "low",
|
|
260
|
+
}
|
|
261
|
+
quality = confidence_map.get(
|
|
262
|
+
str(analysis.overall_confidence).lower(), "unknown"
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
"detected_languages": languages,
|
|
267
|
+
"detected_frameworks": frameworks,
|
|
268
|
+
"recommended_count": len(recommended),
|
|
269
|
+
"detection_quality": quality,
|
|
270
|
+
}
|
|
271
|
+
except Exception as e:
|
|
272
|
+
# Log the error for debugging
|
|
273
|
+
print(f"Warning: Toolchain analysis failed: {e}")
|
|
274
|
+
return {
|
|
275
|
+
"detected_languages": [],
|
|
276
|
+
"detected_frameworks": [],
|
|
277
|
+
"recommended_count": len(self.CORE_AGENTS),
|
|
278
|
+
"detection_quality": "none",
|
|
279
|
+
}
|