claude-mpm 5.0.9__py3-none-any.whl → 5.4.41__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/__init__.py +4 -0
- claude_mpm/agents/BASE_AGENT.md +164 -0
- claude_mpm/agents/{PM_INSTRUCTIONS_TEACH.md → CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md} +721 -41
- claude_mpm/agents/MEMORY.md +1 -1
- claude_mpm/agents/PM_INSTRUCTIONS.md +468 -468
- claude_mpm/agents/WORKFLOW.md +5 -254
- claude_mpm/agents/agent_loader.py +13 -44
- claude_mpm/agents/base_agent.json +1 -1
- claude_mpm/agents/frontmatter_validator.py +70 -2
- claude_mpm/agents/templates/circuit-breakers.md +431 -45
- claude_mpm/cli/__init__.py +0 -1
- claude_mpm/cli/__main__.py +4 -0
- claude_mpm/cli/chrome_devtools_installer.py +175 -0
- claude_mpm/cli/commands/agent_state_manager.py +18 -27
- claude_mpm/cli/commands/agents.py +175 -37
- claude_mpm/cli/commands/auto_configure.py +723 -236
- claude_mpm/cli/commands/config.py +88 -2
- claude_mpm/cli/commands/configure.py +1262 -157
- claude_mpm/cli/commands/configure_agent_display.py +25 -6
- claude_mpm/cli/commands/mpm_init/core.py +225 -46
- 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 +1 -1
- claude_mpm/cli/commands/profile.py +277 -0
- claude_mpm/cli/commands/skills.py +214 -189
- claude_mpm/cli/commands/summarize.py +413 -0
- claude_mpm/cli/executor.py +21 -3
- claude_mpm/cli/interactive/agent_wizard.py +85 -10
- claude_mpm/cli/parsers/agents_parser.py +54 -9
- claude_mpm/cli/parsers/auto_configure_parser.py +13 -138
- claude_mpm/cli/parsers/base_parser.py +12 -0
- claude_mpm/cli/parsers/config_parser.py +153 -83
- claude_mpm/cli/parsers/profile_parser.py +148 -0
- claude_mpm/cli/parsers/skills_parser.py +3 -2
- claude_mpm/cli/startup.py +879 -149
- claude_mpm/commands/mpm-config.md +28 -0
- claude_mpm/commands/mpm-doctor.md +9 -22
- claude_mpm/commands/mpm-help.md +5 -287
- claude_mpm/commands/mpm-init.md +81 -507
- claude_mpm/commands/mpm-monitor.md +15 -402
- claude_mpm/commands/mpm-organize.md +120 -0
- claude_mpm/commands/mpm-postmortem.md +6 -108
- claude_mpm/commands/mpm-session-resume.md +12 -363
- claude_mpm/commands/mpm-status.md +5 -69
- claude_mpm/commands/mpm-ticket-view.md +52 -495
- claude_mpm/commands/mpm-version.md +5 -107
- claude_mpm/config/agent_sources.py +27 -0
- claude_mpm/core/config.py +2 -4
- claude_mpm/core/framework/formatters/content_formatter.py +3 -13
- claude_mpm/core/framework/loaders/agent_loader.py +8 -5
- claude_mpm/core/framework/loaders/instruction_loader.py +52 -11
- claude_mpm/core/framework_loader.py +4 -2
- claude_mpm/core/logger.py +13 -0
- claude_mpm/core/optimized_startup.py +59 -0
- claude_mpm/core/output_style_manager.py +173 -43
- claude_mpm/core/shared/config_loader.py +1 -1
- claude_mpm/core/socketio_pool.py +3 -3
- claude_mpm/core/unified_agent_registry.py +134 -16
- claude_mpm/core/unified_config.py +22 -0
- claude_mpm/dashboard/static/svelte-build/_app/env.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.B_FtCwCQ.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.Cl_eSA4x.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BgChzWQ1.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIXEwuWe.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CWc5urbQ.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DMkZpdF2.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DjhvlsAc.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/N4qtv3Hx.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/uj46x2Wr.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.DTL5mJO-.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.DzuEhzqh.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/0.CAGBuiOw.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.DFLC8jdE.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.DPvEihJJ.js +10 -0
- claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -0
- claude_mpm/dashboard/static/svelte-build/favicon.svg +7 -0
- claude_mpm/dashboard/static/svelte-build/index.html +36 -0
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +211 -78
- claude_mpm/hooks/claude_hooks/hook_handler.py +155 -1
- claude_mpm/hooks/claude_hooks/installer.py +33 -10
- claude_mpm/hooks/claude_hooks/memory_integration.py +28 -0
- claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +30 -6
- claude_mpm/hooks/memory_integration_hook.py +46 -1
- claude_mpm/init.py +63 -19
- claude_mpm/models/agent_definition.py +7 -0
- claude_mpm/models/git_repository.py +3 -3
- claude_mpm/scripts/claude-hook-handler.sh +58 -18
- claude_mpm/scripts/launch_monitor.py +93 -13
- claude_mpm/scripts/start_activity_logging.py +0 -0
- claude_mpm/services/agents/agent_builder.py +3 -3
- claude_mpm/services/agents/agent_recommendation_service.py +278 -0
- claude_mpm/services/agents/agent_review_service.py +280 -0
- claude_mpm/services/agents/cache_git_manager.py +6 -6
- claude_mpm/services/agents/deployment/agent_deployment.py +29 -7
- claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -5
- claude_mpm/services/agents/deployment/agent_template_builder.py +5 -3
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +2 -2
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +320 -29
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +546 -68
- claude_mpm/services/agents/git_source_manager.py +36 -2
- claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
- claude_mpm/services/agents/recommender.py +5 -3
- claude_mpm/services/agents/single_tier_deployment_service.py +2 -2
- claude_mpm/services/agents/sources/git_source_sync_service.py +13 -6
- claude_mpm/services/agents/startup_sync.py +22 -2
- claude_mpm/services/agents/toolchain_detector.py +10 -6
- claude_mpm/services/analysis/__init__.py +11 -1
- claude_mpm/services/analysis/clone_detector.py +1030 -0
- claude_mpm/services/command_deployment_service.py +81 -10
- claude_mpm/services/diagnostics/checks/agent_check.py +2 -2
- claude_mpm/services/diagnostics/checks/agent_sources_check.py +1 -1
- claude_mpm/services/event_bus/config.py +3 -1
- claude_mpm/services/git/git_operations_service.py +101 -16
- claude_mpm/services/monitor/daemon.py +9 -2
- claude_mpm/services/monitor/daemon_manager.py +39 -3
- claude_mpm/services/monitor/management/lifecycle.py +8 -1
- claude_mpm/services/monitor/server.py +698 -22
- claude_mpm/services/pm_skills_deployer.py +676 -0
- claude_mpm/services/profile_manager.py +331 -0
- claude_mpm/services/project/project_organizer.py +4 -0
- claude_mpm/services/self_upgrade_service.py +120 -12
- claude_mpm/services/skills/__init__.py +3 -0
- claude_mpm/services/skills/git_skill_source_manager.py +130 -2
- claude_mpm/services/skills/selective_skill_deployer.py +704 -0
- claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
- claude_mpm/services/skills_deployer.py +126 -9
- claude_mpm/services/socketio/dashboard_server.py +1 -0
- claude_mpm/services/socketio/event_normalizer.py +51 -6
- claude_mpm/services/socketio/server/core.py +386 -108
- claude_mpm/services/version_control/git_operations.py +103 -0
- claude_mpm/skills/skill_manager.py +92 -3
- claude_mpm/utils/agent_dependency_loader.py +14 -2
- claude_mpm/utils/agent_filters.py +17 -44
- claude_mpm/utils/gitignore.py +3 -0
- claude_mpm/utils/migration.py +4 -4
- claude_mpm/utils/robust_installer.py +47 -3
- {claude_mpm-5.0.9.dist-info → claude_mpm-5.4.41.dist-info}/METADATA +57 -87
- {claude_mpm-5.0.9.dist-info → claude_mpm-5.4.41.dist-info}/RECORD +160 -211
- claude_mpm-5.4.41.dist-info/entry_points.txt +5 -0
- claude_mpm-5.4.41.dist-info/licenses/LICENSE +94 -0
- claude_mpm-5.4.41.dist-info/licenses/LICENSE-FAQ.md +153 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
- claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
- claude_mpm/agents/BASE_OPS.md +0 -219
- claude_mpm/agents/BASE_PM.md +0 -480
- claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
- claude_mpm/agents/BASE_QA.md +0 -167
- claude_mpm/agents/BASE_RESEARCH.md +0 -53
- claude_mpm/agents/base_agent_loader.py +0 -601
- claude_mpm/cli/commands/agents_detect.py +0 -380
- claude_mpm/cli/commands/agents_recommend.py +0 -309
- claude_mpm/cli/ticket_cli.py +0 -35
- claude_mpm/commands/mpm-agents-auto-configure.md +0 -278
- claude_mpm/commands/mpm-agents-detect.md +0 -177
- claude_mpm/commands/mpm-agents-list.md +0 -131
- claude_mpm/commands/mpm-agents-recommend.md +0 -223
- claude_mpm/commands/mpm-config-view.md +0 -150
- claude_mpm/commands/mpm-ticket-organize.md +0 -304
- 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/hooks/claude_hooks/__pycache__/__init__.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/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/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 -977
- 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-5.0.9.dist-info/entry_points.txt +0 -10
- claude_mpm-5.0.9.dist-info/licenses/LICENSE +0 -21
- /claude_mpm/agents/{OUTPUT_STYLE.md → CLAUDE_MPM_OUTPUT_STYLE.md} +0 -0
- {claude_mpm-5.0.9.dist-info → claude_mpm-5.4.41.dist-info}/WHEEL +0 -0
- {claude_mpm-5.0.9.dist-info → claude_mpm-5.4.41.dist-info}/top_level.txt +0 -0
|
@@ -1,455 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Code Analysis Runner for Dashboard
|
|
4
|
-
===================================
|
|
5
|
-
|
|
6
|
-
WHY: Manages subprocess execution of code analysis, streaming results to
|
|
7
|
-
Socket.IO clients in real-time while handling cancellation and error recovery.
|
|
8
|
-
|
|
9
|
-
DESIGN DECISIONS:
|
|
10
|
-
- Use subprocess for isolation and cancellation support
|
|
11
|
-
- Stream output line-by-line for real-time updates
|
|
12
|
-
- Queue multiple analysis requests
|
|
13
|
-
- Handle process lifecycle management
|
|
14
|
-
- Convert analyzer events to Socket.IO events
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
import json
|
|
18
|
-
import os
|
|
19
|
-
import subprocess
|
|
20
|
-
import sys
|
|
21
|
-
import threading
|
|
22
|
-
from dataclasses import asdict, dataclass
|
|
23
|
-
from datetime import datetime, timezone
|
|
24
|
-
from pathlib import Path
|
|
25
|
-
from queue import Queue
|
|
26
|
-
from typing import Any, Dict, List, Optional
|
|
27
|
-
|
|
28
|
-
from ..core.logging_config import get_logger
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@dataclass
|
|
32
|
-
class AnalysisRequest:
|
|
33
|
-
"""Represents a code analysis request."""
|
|
34
|
-
|
|
35
|
-
request_id: str
|
|
36
|
-
path: str
|
|
37
|
-
languages: Optional[List[str]] = None
|
|
38
|
-
max_depth: Optional[int] = None
|
|
39
|
-
ignore_patterns: Optional[List[str]] = None
|
|
40
|
-
timestamp: datetime = None
|
|
41
|
-
|
|
42
|
-
def __post_init__(self):
|
|
43
|
-
if self.timestamp is None:
|
|
44
|
-
self.timestamp = datetime.now(timezone.utc)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class CodeAnalysisRunner:
|
|
48
|
-
"""Manages code analysis subprocess execution for the dashboard.
|
|
49
|
-
|
|
50
|
-
WHY: Provides isolation between the dashboard server and analysis process,
|
|
51
|
-
allowing for cancellation, resource limits, and crash recovery.
|
|
52
|
-
"""
|
|
53
|
-
|
|
54
|
-
def __init__(self, socketio_server):
|
|
55
|
-
"""Initialize the analysis runner.
|
|
56
|
-
|
|
57
|
-
Args:
|
|
58
|
-
socketio_server: SocketIOServer instance for broadcasting events
|
|
59
|
-
"""
|
|
60
|
-
self.logger = get_logger(__name__)
|
|
61
|
-
self.server = socketio_server
|
|
62
|
-
self.current_process = None
|
|
63
|
-
self.current_request = None
|
|
64
|
-
self.request_queue = Queue()
|
|
65
|
-
self.running = False
|
|
66
|
-
self.worker_thread = None
|
|
67
|
-
self.cancel_event = threading.Event()
|
|
68
|
-
|
|
69
|
-
# Statistics
|
|
70
|
-
self.stats = {
|
|
71
|
-
"analyses_started": 0,
|
|
72
|
-
"analyses_completed": 0,
|
|
73
|
-
"analyses_cancelled": 0,
|
|
74
|
-
"analyses_failed": 0,
|
|
75
|
-
"total_files": 0,
|
|
76
|
-
"total_nodes": 0,
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
def start(self):
|
|
80
|
-
"""Start the analysis runner worker thread."""
|
|
81
|
-
if self.running:
|
|
82
|
-
return
|
|
83
|
-
|
|
84
|
-
self.running = True
|
|
85
|
-
self.cancel_event.clear()
|
|
86
|
-
self.worker_thread = threading.Thread(target=self._worker_loop, daemon=True)
|
|
87
|
-
self.worker_thread.start()
|
|
88
|
-
self.logger.info("Code analysis runner started")
|
|
89
|
-
|
|
90
|
-
def stop(self):
|
|
91
|
-
"""Stop the analysis runner and cleanup."""
|
|
92
|
-
self.running = False
|
|
93
|
-
self.cancel_current()
|
|
94
|
-
|
|
95
|
-
# Add sentinel to queue to wake up worker
|
|
96
|
-
self.request_queue.put(None)
|
|
97
|
-
|
|
98
|
-
if self.worker_thread:
|
|
99
|
-
self.worker_thread.join(timeout=5)
|
|
100
|
-
|
|
101
|
-
self.logger.info("Code analysis runner stopped")
|
|
102
|
-
|
|
103
|
-
def request_analysis(
|
|
104
|
-
self,
|
|
105
|
-
request_id: str,
|
|
106
|
-
path: str,
|
|
107
|
-
languages: Optional[List[str]] = None,
|
|
108
|
-
max_depth: Optional[int] = None,
|
|
109
|
-
ignore_patterns: Optional[List[str]] = None,
|
|
110
|
-
) -> bool:
|
|
111
|
-
"""Queue a new analysis request.
|
|
112
|
-
|
|
113
|
-
Args:
|
|
114
|
-
request_id: Unique request identifier
|
|
115
|
-
path: Directory path to analyze
|
|
116
|
-
languages: Optional list of languages to filter
|
|
117
|
-
max_depth: Optional maximum directory depth
|
|
118
|
-
ignore_patterns: Optional list of patterns to ignore
|
|
119
|
-
|
|
120
|
-
Returns:
|
|
121
|
-
True if request was queued successfully
|
|
122
|
-
"""
|
|
123
|
-
# Validate path
|
|
124
|
-
analysis_path = Path(path).resolve()
|
|
125
|
-
if not analysis_path.exists():
|
|
126
|
-
self._emit_error(request_id, f"Path does not exist: {path}")
|
|
127
|
-
return False
|
|
128
|
-
|
|
129
|
-
if not analysis_path.is_dir():
|
|
130
|
-
self._emit_error(request_id, f"Path is not a directory: {path}")
|
|
131
|
-
return False
|
|
132
|
-
|
|
133
|
-
# Create request
|
|
134
|
-
request = AnalysisRequest(
|
|
135
|
-
request_id=request_id,
|
|
136
|
-
path=str(analysis_path),
|
|
137
|
-
languages=languages,
|
|
138
|
-
max_depth=max_depth,
|
|
139
|
-
ignore_patterns=ignore_patterns,
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
# Queue request
|
|
143
|
-
self.request_queue.put(request)
|
|
144
|
-
self.logger.info(f"Queued analysis request {request_id} for {path}")
|
|
145
|
-
|
|
146
|
-
# Emit queued event
|
|
147
|
-
self._emit_event(
|
|
148
|
-
"code:analysis:queued",
|
|
149
|
-
{
|
|
150
|
-
"request_id": request_id,
|
|
151
|
-
"path": str(analysis_path),
|
|
152
|
-
"queue_size": self.request_queue.qsize(),
|
|
153
|
-
},
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
return True
|
|
157
|
-
|
|
158
|
-
def cancel_current(self):
|
|
159
|
-
"""Cancel the currently running analysis."""
|
|
160
|
-
if self.current_process and self.current_process.poll() is None:
|
|
161
|
-
self.cancel_event.set()
|
|
162
|
-
|
|
163
|
-
# Try graceful termination first
|
|
164
|
-
self.current_process.terminate()
|
|
165
|
-
try:
|
|
166
|
-
self.current_process.wait(timeout=2)
|
|
167
|
-
except subprocess.TimeoutExpired:
|
|
168
|
-
# Force kill if needed
|
|
169
|
-
self.current_process.kill()
|
|
170
|
-
self.current_process.wait()
|
|
171
|
-
|
|
172
|
-
self.stats["analyses_cancelled"] += 1
|
|
173
|
-
|
|
174
|
-
if self.current_request:
|
|
175
|
-
self._emit_event(
|
|
176
|
-
"code:analysis:cancelled",
|
|
177
|
-
{
|
|
178
|
-
"request_id": self.current_request.request_id,
|
|
179
|
-
"path": self.current_request.path,
|
|
180
|
-
},
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
self.logger.info("Cancelled current analysis")
|
|
184
|
-
|
|
185
|
-
def get_status(self) -> Dict[str, Any]:
|
|
186
|
-
"""Get current runner status.
|
|
187
|
-
|
|
188
|
-
Returns:
|
|
189
|
-
Dictionary with current status and statistics
|
|
190
|
-
"""
|
|
191
|
-
return {
|
|
192
|
-
"running": self.running,
|
|
193
|
-
"current_request": (
|
|
194
|
-
asdict(self.current_request) if self.current_request else None
|
|
195
|
-
),
|
|
196
|
-
"queue_size": self.request_queue.qsize(),
|
|
197
|
-
"stats": self.stats.copy(),
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
def _worker_loop(self):
|
|
201
|
-
"""Worker thread loop for processing analysis requests."""
|
|
202
|
-
while self.running:
|
|
203
|
-
try:
|
|
204
|
-
# Get next request (blocking with timeout)
|
|
205
|
-
request = self.request_queue.get(timeout=1)
|
|
206
|
-
|
|
207
|
-
if request is None: # Sentinel value
|
|
208
|
-
break
|
|
209
|
-
|
|
210
|
-
# Reset cancel event
|
|
211
|
-
self.cancel_event.clear()
|
|
212
|
-
|
|
213
|
-
# Process request
|
|
214
|
-
self._process_request(request)
|
|
215
|
-
|
|
216
|
-
except Exception as e:
|
|
217
|
-
self.logger.error(f"Error in worker loop: {e}")
|
|
218
|
-
|
|
219
|
-
def _process_request(self, request: AnalysisRequest):
|
|
220
|
-
"""Process a single analysis request.
|
|
221
|
-
|
|
222
|
-
Args:
|
|
223
|
-
request: The analysis request to process
|
|
224
|
-
"""
|
|
225
|
-
self.current_request = request
|
|
226
|
-
self.stats["analyses_started"] += 1
|
|
227
|
-
|
|
228
|
-
try:
|
|
229
|
-
# Emit start event
|
|
230
|
-
self._emit_event(
|
|
231
|
-
"code:analysis:start",
|
|
232
|
-
{
|
|
233
|
-
"request_id": request.request_id,
|
|
234
|
-
"path": request.path,
|
|
235
|
-
"languages": request.languages,
|
|
236
|
-
"timestamp": request.timestamp.isoformat(),
|
|
237
|
-
},
|
|
238
|
-
)
|
|
239
|
-
|
|
240
|
-
# Build command
|
|
241
|
-
cmd = self._build_command(request)
|
|
242
|
-
self.logger.info(f"Starting analysis subprocess: {' '.join(cmd)}")
|
|
243
|
-
|
|
244
|
-
# Start subprocess
|
|
245
|
-
try:
|
|
246
|
-
self.current_process = subprocess.Popen(
|
|
247
|
-
cmd,
|
|
248
|
-
stdout=subprocess.PIPE,
|
|
249
|
-
stderr=subprocess.PIPE,
|
|
250
|
-
text=True,
|
|
251
|
-
bufsize=1,
|
|
252
|
-
universal_newlines=True,
|
|
253
|
-
env=self._get_subprocess_env(),
|
|
254
|
-
)
|
|
255
|
-
self.logger.debug(
|
|
256
|
-
f"Subprocess started with PID: {self.current_process.pid}"
|
|
257
|
-
)
|
|
258
|
-
except FileNotFoundError as e:
|
|
259
|
-
raise subprocess.SubprocessError(
|
|
260
|
-
f"Python executable not found: {cmd[0]}"
|
|
261
|
-
) from e
|
|
262
|
-
except Exception as e:
|
|
263
|
-
raise subprocess.SubprocessError(
|
|
264
|
-
f"Failed to start subprocess: {e}"
|
|
265
|
-
) from e
|
|
266
|
-
|
|
267
|
-
# Process output
|
|
268
|
-
self._process_output(request)
|
|
269
|
-
|
|
270
|
-
# Wait for completion
|
|
271
|
-
return_code = self.current_process.wait()
|
|
272
|
-
|
|
273
|
-
if self.cancel_event.is_set():
|
|
274
|
-
# Analysis was cancelled
|
|
275
|
-
pass # Event already emitted in cancel_current
|
|
276
|
-
elif return_code == 0:
|
|
277
|
-
# Success
|
|
278
|
-
self.stats["analyses_completed"] += 1
|
|
279
|
-
self._emit_event(
|
|
280
|
-
"code:analysis:complete",
|
|
281
|
-
{
|
|
282
|
-
"request_id": request.request_id,
|
|
283
|
-
"path": request.path,
|
|
284
|
-
"stats": {
|
|
285
|
-
"total_files": self.stats["total_files"],
|
|
286
|
-
"total_nodes": self.stats["total_nodes"],
|
|
287
|
-
},
|
|
288
|
-
},
|
|
289
|
-
)
|
|
290
|
-
else:
|
|
291
|
-
# Failure - capture any remaining stderr
|
|
292
|
-
stderr_output = ""
|
|
293
|
-
if self.current_process.stderr:
|
|
294
|
-
try:
|
|
295
|
-
# Read any remaining stderr output
|
|
296
|
-
stderr_lines = []
|
|
297
|
-
for line in self.current_process.stderr:
|
|
298
|
-
stderr_lines.append(line.strip())
|
|
299
|
-
stderr_output = "\n".join(stderr_lines)
|
|
300
|
-
except Exception as e:
|
|
301
|
-
stderr_output = f"Failed to read stderr: {e}"
|
|
302
|
-
|
|
303
|
-
self.stats["analyses_failed"] += 1
|
|
304
|
-
error_msg = f"Analysis failed with code {return_code}"
|
|
305
|
-
if stderr_output:
|
|
306
|
-
error_msg += f": {stderr_output}"
|
|
307
|
-
|
|
308
|
-
self.logger.error(f"Subprocess failed: {error_msg}")
|
|
309
|
-
self._emit_error(request.request_id, error_msg)
|
|
310
|
-
|
|
311
|
-
except subprocess.SubprocessError as e:
|
|
312
|
-
self.logger.error(f"Subprocess error for request {request.request_id}: {e}")
|
|
313
|
-
self.stats["analyses_failed"] += 1
|
|
314
|
-
self._emit_error(request.request_id, f"Failed to start analyzer: {e}")
|
|
315
|
-
except Exception as e:
|
|
316
|
-
self.logger.error(
|
|
317
|
-
f"Error processing request {request.request_id}: {e}", exc_info=True
|
|
318
|
-
)
|
|
319
|
-
self.stats["analyses_failed"] += 1
|
|
320
|
-
self._emit_error(request.request_id, str(e))
|
|
321
|
-
|
|
322
|
-
finally:
|
|
323
|
-
self.current_process = None
|
|
324
|
-
self.current_request = None
|
|
325
|
-
|
|
326
|
-
def _build_command(self, request: AnalysisRequest) -> List[str]:
|
|
327
|
-
"""Build the subprocess command for analysis.
|
|
328
|
-
|
|
329
|
-
Args:
|
|
330
|
-
request: The analysis request
|
|
331
|
-
|
|
332
|
-
Returns:
|
|
333
|
-
Command list for subprocess.Popen
|
|
334
|
-
"""
|
|
335
|
-
# Get Python executable
|
|
336
|
-
python_exe = sys.executable
|
|
337
|
-
|
|
338
|
-
# Build command - use the CLI analyze-code command
|
|
339
|
-
cmd = [
|
|
340
|
-
python_exe,
|
|
341
|
-
"-m",
|
|
342
|
-
"claude_mpm",
|
|
343
|
-
"analyze-code",
|
|
344
|
-
request.path,
|
|
345
|
-
"--emit-events",
|
|
346
|
-
"--output",
|
|
347
|
-
"json",
|
|
348
|
-
]
|
|
349
|
-
|
|
350
|
-
# Add optional parameters
|
|
351
|
-
if request.languages:
|
|
352
|
-
cmd.extend(["--languages", ",".join(request.languages)])
|
|
353
|
-
|
|
354
|
-
if request.max_depth:
|
|
355
|
-
cmd.extend(["--max-depth", str(request.max_depth)])
|
|
356
|
-
|
|
357
|
-
if request.ignore_patterns:
|
|
358
|
-
for pattern in request.ignore_patterns:
|
|
359
|
-
cmd.extend(["--ignore", pattern])
|
|
360
|
-
|
|
361
|
-
return cmd
|
|
362
|
-
|
|
363
|
-
def _get_subprocess_env(self) -> Dict[str, str]:
|
|
364
|
-
"""Get environment variables for subprocess.
|
|
365
|
-
|
|
366
|
-
Returns:
|
|
367
|
-
Environment dictionary for subprocess
|
|
368
|
-
"""
|
|
369
|
-
env = os.environ.copy()
|
|
370
|
-
|
|
371
|
-
# Ensure Socket.IO URL is set for event emission
|
|
372
|
-
env["SOCKETIO_URL"] = f"http://localhost:{self.server.port}"
|
|
373
|
-
|
|
374
|
-
# Set Python path to include our modules
|
|
375
|
-
python_path = env.get("PYTHONPATH", "")
|
|
376
|
-
src_path = str(Path(__file__).parent.parent.parent)
|
|
377
|
-
if src_path not in python_path:
|
|
378
|
-
env["PYTHONPATH"] = f"{src_path}:{python_path}" if python_path else src_path
|
|
379
|
-
|
|
380
|
-
return env
|
|
381
|
-
|
|
382
|
-
def _process_output(self, request: AnalysisRequest):
|
|
383
|
-
"""Process subprocess output and emit events.
|
|
384
|
-
|
|
385
|
-
Args:
|
|
386
|
-
request: The current analysis request
|
|
387
|
-
"""
|
|
388
|
-
if not self.current_process:
|
|
389
|
-
return
|
|
390
|
-
|
|
391
|
-
# Read output line by line
|
|
392
|
-
for line in iter(self.current_process.stdout.readline, ""):
|
|
393
|
-
if self.cancel_event.is_set():
|
|
394
|
-
break
|
|
395
|
-
|
|
396
|
-
line = line.strip()
|
|
397
|
-
if not line:
|
|
398
|
-
continue
|
|
399
|
-
|
|
400
|
-
try:
|
|
401
|
-
# Parse JSON event
|
|
402
|
-
event = json.loads(line)
|
|
403
|
-
|
|
404
|
-
# Route event to appropriate handler
|
|
405
|
-
event_type = event.get("type")
|
|
406
|
-
event_data = event.get("data", {})
|
|
407
|
-
|
|
408
|
-
# Add request ID to event data
|
|
409
|
-
event_data["request_id"] = request.request_id
|
|
410
|
-
|
|
411
|
-
# Update statistics based on event type
|
|
412
|
-
if event_type == "code:file:complete":
|
|
413
|
-
self.stats["total_files"] += 1
|
|
414
|
-
elif event_type == "code:node:found":
|
|
415
|
-
self.stats["total_nodes"] += 1
|
|
416
|
-
|
|
417
|
-
# Emit to Socket.IO clients
|
|
418
|
-
self._emit_event(event_type, event_data)
|
|
419
|
-
|
|
420
|
-
except json.JSONDecodeError:
|
|
421
|
-
# Not JSON, treat as log message
|
|
422
|
-
self.logger.debug(f"Analyzer output: {line}")
|
|
423
|
-
except Exception as e:
|
|
424
|
-
self.logger.warning(f"Error processing analyzer output: {e}")
|
|
425
|
-
|
|
426
|
-
def _emit_event(self, event_type: str, data: Dict[str, Any]):
|
|
427
|
-
"""Emit an event to Socket.IO clients.
|
|
428
|
-
|
|
429
|
-
Args:
|
|
430
|
-
event_type: Type of event
|
|
431
|
-
data: Event data
|
|
432
|
-
"""
|
|
433
|
-
if self.server:
|
|
434
|
-
# Add timestamp if not present
|
|
435
|
-
if "timestamp" not in data:
|
|
436
|
-
data["timestamp"] = datetime.now(timezone.utc).isoformat()
|
|
437
|
-
|
|
438
|
-
# Broadcast to all clients
|
|
439
|
-
self.server.broadcast_event(event_type, data)
|
|
440
|
-
|
|
441
|
-
def _emit_error(self, request_id: str, message: str):
|
|
442
|
-
"""Emit an error event.
|
|
443
|
-
|
|
444
|
-
Args:
|
|
445
|
-
request_id: Request that caused the error
|
|
446
|
-
message: Error message
|
|
447
|
-
"""
|
|
448
|
-
self._emit_event(
|
|
449
|
-
"code:analysis:error",
|
|
450
|
-
{
|
|
451
|
-
"request_id": request_id,
|
|
452
|
-
"message": message,
|
|
453
|
-
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
454
|
-
},
|
|
455
|
-
)
|
claude_mpm/dashboard/index.html
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>Claude MPM Dashboard</title>
|
|
7
|
-
<meta http-equiv="refresh" content="0; url=/dashboard?autoconnect=true&port=8765">
|
|
8
|
-
</head>
|
|
9
|
-
<body>
|
|
10
|
-
<p>Redirecting to Claude MPM Dashboard...</p>
|
|
11
|
-
<p>If you are not redirected, <a href="/dashboard?autoconnect=true&port=8765">click here</a>.</p>
|
|
12
|
-
</body>
|
|
13
|
-
</html>
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Open the dashboard statically in the browser."""
|
|
3
|
-
|
|
4
|
-
import webbrowser
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
try:
|
|
8
|
-
from ..services.port_manager import PortManager
|
|
9
|
-
except ImportError:
|
|
10
|
-
# Fallback for when running as standalone script
|
|
11
|
-
import sys
|
|
12
|
-
|
|
13
|
-
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
|
14
|
-
from claude_mpm.services.port_manager import PortManager
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def discover_socketio_port():
|
|
18
|
-
"""Discover the port of the running SocketIO server, preferring 8765."""
|
|
19
|
-
try:
|
|
20
|
-
port_manager = PortManager()
|
|
21
|
-
instances = port_manager.list_active_instances()
|
|
22
|
-
|
|
23
|
-
if instances:
|
|
24
|
-
# First, check if port 8765 is being used
|
|
25
|
-
for instance in instances:
|
|
26
|
-
if instance.get("port") == 8765:
|
|
27
|
-
return 8765
|
|
28
|
-
|
|
29
|
-
# If 8765 is not available, return the first active instance port
|
|
30
|
-
return instances[0].get("port", 8765)
|
|
31
|
-
print("⚠️ No active SocketIO instances found, using default port 8765")
|
|
32
|
-
return 8765
|
|
33
|
-
except Exception as e:
|
|
34
|
-
print(f"⚠️ Failed to discover SocketIO port: {e}")
|
|
35
|
-
print(" Using default port 8765")
|
|
36
|
-
return 8765
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def open_dashboard(port=8765, autoconnect=True):
|
|
40
|
-
"""Open the dashboard HTML file directly in the browser.
|
|
41
|
-
|
|
42
|
-
Args:
|
|
43
|
-
port: Socket.IO server port to connect to (defaults to 8765, auto-discovers if needed)
|
|
44
|
-
autoconnect: Whether to auto-connect on load
|
|
45
|
-
"""
|
|
46
|
-
# If default port 8765 is specified, check if we need to auto-discover
|
|
47
|
-
if port == 8765:
|
|
48
|
-
discovered_port = discover_socketio_port()
|
|
49
|
-
if discovered_port != 8765:
|
|
50
|
-
print(
|
|
51
|
-
f"🔍 SocketIO server found on port {discovered_port} instead of default 8765"
|
|
52
|
-
)
|
|
53
|
-
port = discovered_port
|
|
54
|
-
# Build HTTP URL to connect to the SocketIO server's dashboard
|
|
55
|
-
dashboard_url = f"http://localhost:{port}"
|
|
56
|
-
|
|
57
|
-
print(f"🌐 Opening dashboard: {dashboard_url}")
|
|
58
|
-
print(f"📡 Dashboard served by Socket.IO server at localhost:{port}")
|
|
59
|
-
webbrowser.open(dashboard_url)
|
|
60
|
-
|
|
61
|
-
return dashboard_url
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if __name__ == "__main__":
|
|
65
|
-
# Test opening the dashboard
|
|
66
|
-
open_dashboard()
|