claude-mpm 4.0.28__py3-none-any.whl → 4.0.30__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +48 -3
- claude_mpm/agents/BASE_PM.md +20 -15
- claude_mpm/agents/INSTRUCTIONS.md +12 -2
- claude_mpm/agents/templates/agent-manager.json +24 -0
- claude_mpm/agents/templates/agent-manager.md +304 -0
- claude_mpm/agents/templates/documentation.json +16 -3
- claude_mpm/agents/templates/engineer.json +19 -5
- claude_mpm/agents/templates/ops.json +19 -5
- claude_mpm/agents/templates/qa.json +16 -3
- claude_mpm/agents/templates/refactoring_engineer.json +25 -7
- claude_mpm/agents/templates/research.json +19 -5
- claude_mpm/cli/__init__.py +4 -0
- claude_mpm/cli/commands/__init__.py +4 -0
- claude_mpm/cli/commands/agent_manager.py +521 -0
- claude_mpm/cli/commands/agents.py +2 -1
- claude_mpm/cli/commands/cleanup.py +1 -1
- claude_mpm/cli/commands/doctor.py +209 -0
- claude_mpm/cli/commands/mcp.py +3 -3
- claude_mpm/cli/commands/mcp_install_commands.py +12 -30
- claude_mpm/cli/commands/mcp_server_commands.py +9 -9
- claude_mpm/cli/commands/memory.py +1 -1
- claude_mpm/cli/commands/run.py +31 -2
- claude_mpm/cli/commands/run_config_checker.py +1 -1
- claude_mpm/cli/parsers/agent_manager_parser.py +247 -0
- claude_mpm/cli/parsers/base_parser.py +12 -1
- claude_mpm/cli/parsers/mcp_parser.py +1 -1
- claude_mpm/cli/parsers/run_parser.py +1 -1
- claude_mpm/cli/shared/__init__.py +1 -1
- claude_mpm/cli/startup_logging.py +463 -0
- claude_mpm/constants.py +2 -0
- claude_mpm/core/claude_runner.py +81 -2
- claude_mpm/core/constants.py +2 -2
- claude_mpm/core/framework_loader.py +45 -11
- claude_mpm/core/interactive_session.py +82 -3
- claude_mpm/core/output_style_manager.py +6 -6
- claude_mpm/core/socketio_pool.py +2 -2
- claude_mpm/core/unified_paths.py +128 -0
- claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
- claude_mpm/dashboard/static/built/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/built/dashboard.js +1 -1
- claude_mpm/dashboard/static/built/socket-client.js +1 -1
- claude_mpm/dashboard/static/css/dashboard.css +170 -0
- claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/dashboard.js +1 -1
- claude_mpm/dashboard/static/dist/socket-client.js +1 -1
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +21 -3
- claude_mpm/dashboard/static/js/components/module-viewer.js +129 -1
- claude_mpm/dashboard/static/js/dashboard.js +116 -0
- claude_mpm/dashboard/static/js/socket-client.js +0 -1
- claude_mpm/hooks/claude_hooks/connection_pool.py +1 -1
- claude_mpm/hooks/claude_hooks/hook_handler.py +1 -1
- claude_mpm/scripts/mcp_server.py +2 -2
- claude_mpm/services/agents/agent_builder.py +455 -0
- claude_mpm/services/agents/deployment/agent_template_builder.py +10 -3
- claude_mpm/services/agents/deployment/agent_validator.py +1 -0
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +69 -1
- claude_mpm/services/diagnostics/__init__.py +18 -0
- claude_mpm/services/diagnostics/checks/__init__.py +30 -0
- claude_mpm/services/diagnostics/checks/agent_check.py +319 -0
- claude_mpm/services/diagnostics/checks/base_check.py +64 -0
- claude_mpm/services/diagnostics/checks/claude_desktop_check.py +283 -0
- claude_mpm/services/diagnostics/checks/common_issues_check.py +354 -0
- claude_mpm/services/diagnostics/checks/configuration_check.py +300 -0
- claude_mpm/services/diagnostics/checks/filesystem_check.py +233 -0
- claude_mpm/services/diagnostics/checks/installation_check.py +255 -0
- claude_mpm/services/diagnostics/checks/mcp_check.py +315 -0
- claude_mpm/services/diagnostics/checks/monitor_check.py +282 -0
- claude_mpm/services/diagnostics/checks/startup_log_check.py +322 -0
- claude_mpm/services/diagnostics/diagnostic_runner.py +247 -0
- claude_mpm/services/diagnostics/doctor_reporter.py +283 -0
- claude_mpm/services/diagnostics/models.py +120 -0
- claude_mpm/services/mcp_gateway/core/interfaces.py +1 -1
- claude_mpm/services/mcp_gateway/main.py +1 -1
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +3 -3
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +1 -1
- claude_mpm/services/mcp_gateway/server/stdio_server.py +3 -3
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +2 -2
- claude_mpm/services/memory/__init__.py +2 -0
- claude_mpm/services/socketio/handlers/connection.py +27 -33
- claude_mpm/services/socketio/handlers/registry.py +39 -7
- claude_mpm/services/socketio/server/core.py +72 -22
- claude_mpm/validation/frontmatter_validator.py +1 -1
- {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/METADATA +4 -1
- {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/RECORD +89 -67
- /claude_mpm/cli/shared/{command_base.py → base_command.py} +0 -0
- {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/WHEEL +0 -0
- {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/top_level.txt +0 -0
|
@@ -190,7 +190,7 @@ def add_top_level_run_arguments(parser: argparse.ArgumentParser) -> None:
|
|
|
190
190
|
run_group.add_argument(
|
|
191
191
|
"--resume",
|
|
192
192
|
action="store_true",
|
|
193
|
-
help="Pass --resume flag to Claude
|
|
193
|
+
help="Pass --resume flag to Claude Code to resume the last conversation",
|
|
194
194
|
)
|
|
195
195
|
run_group.add_argument(
|
|
196
196
|
"--force",
|
|
@@ -322,6 +322,13 @@ def create_parser(
|
|
|
322
322
|
except ImportError:
|
|
323
323
|
pass
|
|
324
324
|
|
|
325
|
+
try:
|
|
326
|
+
from .agent_manager_parser import add_agent_manager_subparser
|
|
327
|
+
|
|
328
|
+
add_agent_manager_subparser(subparsers)
|
|
329
|
+
except ImportError:
|
|
330
|
+
pass
|
|
331
|
+
|
|
325
332
|
# Import and add additional command parsers from commands module
|
|
326
333
|
try:
|
|
327
334
|
from ..commands.aggregate import add_aggregate_parser
|
|
@@ -331,6 +338,10 @@ def create_parser(
|
|
|
331
338
|
from ..commands.cleanup import add_cleanup_parser
|
|
332
339
|
|
|
333
340
|
add_cleanup_parser(subparsers)
|
|
341
|
+
|
|
342
|
+
from ..commands.doctor import add_doctor_parser
|
|
343
|
+
|
|
344
|
+
add_doctor_parser(subparsers)
|
|
334
345
|
except ImportError:
|
|
335
346
|
# Commands module may not be available during testing or refactoring
|
|
336
347
|
pass
|
|
@@ -172,7 +172,7 @@ def add_mcp_subparser(subparsers) -> argparse.ArgumentParser:
|
|
|
172
172
|
"--test", action="store_true", help="Run in test mode with debug output"
|
|
173
173
|
)
|
|
174
174
|
server_mcp_parser.add_argument(
|
|
175
|
-
"--instructions", action="store_true", help="Show setup instructions for Claude
|
|
175
|
+
"--instructions", action="store_true", help="Show setup instructions for Claude Code"
|
|
176
176
|
)
|
|
177
177
|
|
|
178
178
|
return mcp_parser
|
|
@@ -78,7 +78,7 @@ def add_run_arguments(parser: argparse.ArgumentParser) -> None:
|
|
|
78
78
|
run_group.add_argument(
|
|
79
79
|
"--resume",
|
|
80
80
|
action="store_true",
|
|
81
|
-
help="Pass --resume flag to Claude
|
|
81
|
+
help="Pass --resume flag to Claude Code to resume the last conversation",
|
|
82
82
|
)
|
|
83
83
|
|
|
84
84
|
# Dependency checking options
|
|
@@ -13,7 +13,7 @@ from .argument_patterns import (
|
|
|
13
13
|
add_memory_arguments,
|
|
14
14
|
add_output_arguments,
|
|
15
15
|
)
|
|
16
|
-
from .
|
|
16
|
+
from .base_command import AgentCommand, BaseCommand, CommandResult, MemoryCommand
|
|
17
17
|
from .error_handling import CLIErrorHandler, handle_cli_errors
|
|
18
18
|
from .output_formatters import OutputFormatter, format_output
|
|
19
19
|
|
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Startup logging utilities for MCP server and monitor setup status.
|
|
3
|
+
|
|
4
|
+
WHY: This module provides detailed startup logging for better debugging
|
|
5
|
+
visibility. It logs MCP server installation/configuration status and
|
|
6
|
+
monitor service initialization status during the startup sequence.
|
|
7
|
+
|
|
8
|
+
DESIGN DECISIONS:
|
|
9
|
+
- Use consistent INFO log format with existing startup messages
|
|
10
|
+
- Gracefully handle missing dependencies or services
|
|
11
|
+
- Provide informative but concise status messages
|
|
12
|
+
- Include helpful context for debugging
|
|
13
|
+
- Ensure logging works in all deployment contexts (dev, pipx, pip)
|
|
14
|
+
- Capture all startup logs to timestamped files for analysis
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
import os
|
|
19
|
+
import shutil
|
|
20
|
+
import subprocess
|
|
21
|
+
import sys
|
|
22
|
+
from datetime import datetime
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Any, Dict, Optional, Tuple
|
|
25
|
+
|
|
26
|
+
from ..core.logger import get_logger
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class StartupStatusLogger:
|
|
30
|
+
"""Logs MCP server and monitor setup status during startup."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, logger_name: str = "startup_status"):
|
|
33
|
+
"""Initialize the startup status logger."""
|
|
34
|
+
self.logger = get_logger(logger_name)
|
|
35
|
+
|
|
36
|
+
def log_mcp_server_status(self) -> None:
|
|
37
|
+
"""
|
|
38
|
+
Log MCP server installation and configuration status.
|
|
39
|
+
|
|
40
|
+
Checks:
|
|
41
|
+
- MCP server executable availability
|
|
42
|
+
- MCP server version if available
|
|
43
|
+
- MCP configuration in ~/.claude.json
|
|
44
|
+
- MCP-related errors or warnings
|
|
45
|
+
"""
|
|
46
|
+
try:
|
|
47
|
+
# Check if MCP server executable is available
|
|
48
|
+
mcp_executable = self._find_mcp_executable()
|
|
49
|
+
if mcp_executable:
|
|
50
|
+
self.logger.info(f"MCP Server: Installed at {mcp_executable}")
|
|
51
|
+
|
|
52
|
+
# Try to get version
|
|
53
|
+
version = self._get_mcp_version(mcp_executable)
|
|
54
|
+
if version:
|
|
55
|
+
self.logger.info(f"MCP Server: Version {version}")
|
|
56
|
+
else:
|
|
57
|
+
self.logger.info("MCP Server: Version unknown")
|
|
58
|
+
else:
|
|
59
|
+
self.logger.info("MCP Server: Not found in PATH")
|
|
60
|
+
|
|
61
|
+
# Check MCP configuration in ~/.claude.json
|
|
62
|
+
config_status = self._check_mcp_configuration()
|
|
63
|
+
if config_status["found"]:
|
|
64
|
+
self.logger.info("MCP Server: Configuration found in ~/.claude.json")
|
|
65
|
+
if config_status["servers_count"] > 0:
|
|
66
|
+
self.logger.info(f"MCP Server: {config_status['servers_count']} server(s) configured")
|
|
67
|
+
else:
|
|
68
|
+
self.logger.info("MCP Server: No servers configured")
|
|
69
|
+
else:
|
|
70
|
+
self.logger.info("MCP Server: No configuration found in ~/.claude.json")
|
|
71
|
+
|
|
72
|
+
# Check for claude-mpm MCP gateway status
|
|
73
|
+
gateway_status = self._check_mcp_gateway_status()
|
|
74
|
+
if gateway_status["configured"]:
|
|
75
|
+
self.logger.info("MCP Gateway: Claude MPM gateway configured")
|
|
76
|
+
else:
|
|
77
|
+
self.logger.info("MCP Gateway: Claude MPM gateway not configured")
|
|
78
|
+
|
|
79
|
+
except Exception as e:
|
|
80
|
+
self.logger.warning(f"MCP Server: Status check failed - {e}")
|
|
81
|
+
|
|
82
|
+
def log_monitor_setup_status(self, monitor_mode: bool = False, websocket_port: int = 8765) -> None:
|
|
83
|
+
"""
|
|
84
|
+
Log monitor service initialization status.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
monitor_mode: Whether monitor mode is enabled
|
|
88
|
+
websocket_port: WebSocket port for monitoring
|
|
89
|
+
|
|
90
|
+
Checks:
|
|
91
|
+
- Monitor service initialization status
|
|
92
|
+
- Which monitors are enabled/disabled
|
|
93
|
+
- Monitor configuration details
|
|
94
|
+
- Monitor-related errors or warnings
|
|
95
|
+
"""
|
|
96
|
+
try:
|
|
97
|
+
if monitor_mode:
|
|
98
|
+
self.logger.info("Monitor: Mode enabled")
|
|
99
|
+
|
|
100
|
+
# Check SocketIO dependencies
|
|
101
|
+
socketio_status = self._check_socketio_dependencies()
|
|
102
|
+
if socketio_status["available"]:
|
|
103
|
+
self.logger.info("Monitor: Socket.IO dependencies available")
|
|
104
|
+
else:
|
|
105
|
+
self.logger.info(f"Monitor: Socket.IO dependencies missing - {socketio_status['error']}")
|
|
106
|
+
|
|
107
|
+
# Check if server is running
|
|
108
|
+
server_running = self._check_socketio_server_running(websocket_port)
|
|
109
|
+
if server_running:
|
|
110
|
+
self.logger.info(f"Monitor: Socket.IO server running on port {websocket_port}")
|
|
111
|
+
else:
|
|
112
|
+
self.logger.info(f"Monitor: Socket.IO server will start on port {websocket_port}")
|
|
113
|
+
|
|
114
|
+
# Check response logging configuration
|
|
115
|
+
logging_config = self._check_response_logging_config()
|
|
116
|
+
if logging_config["enabled"]:
|
|
117
|
+
self.logger.info(f"Monitor: Response logging enabled to {logging_config['directory']}")
|
|
118
|
+
else:
|
|
119
|
+
self.logger.info("Monitor: Response logging disabled")
|
|
120
|
+
|
|
121
|
+
else:
|
|
122
|
+
self.logger.info("Monitor: Mode disabled")
|
|
123
|
+
|
|
124
|
+
# Still check if there's an existing server running
|
|
125
|
+
server_running = self._check_socketio_server_running(websocket_port)
|
|
126
|
+
if server_running:
|
|
127
|
+
self.logger.info(f"Monitor: Background Socket.IO server detected on port {websocket_port}")
|
|
128
|
+
|
|
129
|
+
except Exception as e:
|
|
130
|
+
self.logger.warning(f"Monitor: Status check failed - {e}")
|
|
131
|
+
|
|
132
|
+
def _find_mcp_executable(self) -> Optional[str]:
|
|
133
|
+
"""Find MCP server executable in PATH."""
|
|
134
|
+
# Common MCP executable names
|
|
135
|
+
executables = ["claude-mpm-mcp", "mcp", "claude-mcp"]
|
|
136
|
+
|
|
137
|
+
for exe_name in executables:
|
|
138
|
+
exe_path = shutil.which(exe_name)
|
|
139
|
+
if exe_path:
|
|
140
|
+
return exe_path
|
|
141
|
+
|
|
142
|
+
# Check if it's installed as a Python package
|
|
143
|
+
try:
|
|
144
|
+
result = subprocess.run(
|
|
145
|
+
[sys.executable, "-m", "claude_mpm.scripts.mcp_server", "--version"],
|
|
146
|
+
capture_output=True,
|
|
147
|
+
text=True,
|
|
148
|
+
timeout=5
|
|
149
|
+
)
|
|
150
|
+
if result.returncode == 0:
|
|
151
|
+
return f"{sys.executable} -m claude_mpm.scripts.mcp_server"
|
|
152
|
+
except Exception:
|
|
153
|
+
pass
|
|
154
|
+
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
def _get_mcp_version(self, executable: str) -> Optional[str]:
|
|
158
|
+
"""Get MCP server version."""
|
|
159
|
+
try:
|
|
160
|
+
# Try --version flag
|
|
161
|
+
result = subprocess.run(
|
|
162
|
+
executable.split() + ["--version"],
|
|
163
|
+
capture_output=True,
|
|
164
|
+
text=True,
|
|
165
|
+
timeout=5
|
|
166
|
+
)
|
|
167
|
+
if result.returncode == 0:
|
|
168
|
+
# Extract version from output
|
|
169
|
+
output = result.stdout.strip()
|
|
170
|
+
if output:
|
|
171
|
+
return output
|
|
172
|
+
|
|
173
|
+
# Try version command
|
|
174
|
+
result = subprocess.run(
|
|
175
|
+
executable.split() + ["version"],
|
|
176
|
+
capture_output=True,
|
|
177
|
+
text=True,
|
|
178
|
+
timeout=5
|
|
179
|
+
)
|
|
180
|
+
if result.returncode == 0:
|
|
181
|
+
output = result.stdout.strip()
|
|
182
|
+
if output:
|
|
183
|
+
return output
|
|
184
|
+
|
|
185
|
+
except Exception:
|
|
186
|
+
pass
|
|
187
|
+
|
|
188
|
+
return None
|
|
189
|
+
|
|
190
|
+
def _check_mcp_configuration(self) -> Dict[str, Any]:
|
|
191
|
+
"""Check MCP configuration in ~/.claude.json."""
|
|
192
|
+
claude_json_path = Path.home() / ".claude.json"
|
|
193
|
+
|
|
194
|
+
result = {
|
|
195
|
+
"found": False,
|
|
196
|
+
"servers_count": 0,
|
|
197
|
+
"error": None
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
if not claude_json_path.exists():
|
|
202
|
+
return result
|
|
203
|
+
|
|
204
|
+
import json
|
|
205
|
+
with open(claude_json_path, 'r') as f:
|
|
206
|
+
config = json.load(f)
|
|
207
|
+
|
|
208
|
+
result["found"] = True
|
|
209
|
+
|
|
210
|
+
# Check for MCP servers configuration
|
|
211
|
+
mcp_config = config.get("mcpServers", {})
|
|
212
|
+
result["servers_count"] = len(mcp_config)
|
|
213
|
+
|
|
214
|
+
except Exception as e:
|
|
215
|
+
result["error"] = str(e)
|
|
216
|
+
|
|
217
|
+
return result
|
|
218
|
+
|
|
219
|
+
def _check_mcp_gateway_status(self) -> Dict[str, Any]:
|
|
220
|
+
"""Check Claude MPM MCP gateway configuration status."""
|
|
221
|
+
result = {
|
|
222
|
+
"configured": False,
|
|
223
|
+
"error": None
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
try:
|
|
227
|
+
# Check if MCP gateway startup verification is available
|
|
228
|
+
from ..services.mcp_gateway.core.startup_verification import is_mcp_gateway_configured
|
|
229
|
+
result["configured"] = is_mcp_gateway_configured()
|
|
230
|
+
except ImportError:
|
|
231
|
+
# MCP gateway not available
|
|
232
|
+
pass
|
|
233
|
+
except Exception as e:
|
|
234
|
+
result["error"] = str(e)
|
|
235
|
+
|
|
236
|
+
return result
|
|
237
|
+
|
|
238
|
+
def _check_socketio_dependencies(self) -> Dict[str, Any]:
|
|
239
|
+
"""Check if Socket.IO dependencies are available."""
|
|
240
|
+
result = {
|
|
241
|
+
"available": False,
|
|
242
|
+
"error": None
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
import socketio
|
|
247
|
+
import aiohttp
|
|
248
|
+
import engineio
|
|
249
|
+
result["available"] = True
|
|
250
|
+
except ImportError as e:
|
|
251
|
+
result["error"] = f"Missing dependencies: {e}"
|
|
252
|
+
except Exception as e:
|
|
253
|
+
result["error"] = str(e)
|
|
254
|
+
|
|
255
|
+
return result
|
|
256
|
+
|
|
257
|
+
def _check_socketio_server_running(self, port: int) -> bool:
|
|
258
|
+
"""Check if Socket.IO server is running on specified port."""
|
|
259
|
+
try:
|
|
260
|
+
import socket
|
|
261
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
262
|
+
s.settimeout(1)
|
|
263
|
+
result = s.connect_ex(('localhost', port))
|
|
264
|
+
return result == 0
|
|
265
|
+
except Exception:
|
|
266
|
+
return False
|
|
267
|
+
|
|
268
|
+
def _check_response_logging_config(self) -> Dict[str, Any]:
|
|
269
|
+
"""Check response logging configuration."""
|
|
270
|
+
result = {
|
|
271
|
+
"enabled": False,
|
|
272
|
+
"directory": None,
|
|
273
|
+
"error": None
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
try:
|
|
277
|
+
from ..core.shared.config_loader import ConfigLoader
|
|
278
|
+
|
|
279
|
+
config_loader = ConfigLoader()
|
|
280
|
+
config = config_loader.load_main_config()
|
|
281
|
+
|
|
282
|
+
# Check response logging configuration
|
|
283
|
+
response_logging = config.get("response_logging", {})
|
|
284
|
+
result["enabled"] = response_logging.get("enabled", False)
|
|
285
|
+
|
|
286
|
+
if result["enabled"]:
|
|
287
|
+
log_dir = response_logging.get("session_directory", ".claude-mpm/responses")
|
|
288
|
+
if not Path(log_dir).is_absolute():
|
|
289
|
+
log_dir = Path.cwd() / log_dir
|
|
290
|
+
result["directory"] = str(log_dir)
|
|
291
|
+
|
|
292
|
+
except Exception as e:
|
|
293
|
+
result["error"] = str(e)
|
|
294
|
+
|
|
295
|
+
return result
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def setup_startup_logging(project_root: Optional[Path] = None) -> Path:
|
|
299
|
+
"""
|
|
300
|
+
Set up logging to both console and file for startup.
|
|
301
|
+
|
|
302
|
+
WHY: Capture all startup logs (INFO, WARNING, ERROR, DEBUG) to timestamped
|
|
303
|
+
files for later analysis by the doctor command. This helps diagnose
|
|
304
|
+
startup issues that users may not notice in the console output.
|
|
305
|
+
|
|
306
|
+
DESIGN DECISIONS:
|
|
307
|
+
- Use ISO-like timestamp format for easy sorting and reading
|
|
308
|
+
- Store in .claude-mpm/logs/startup/ directory
|
|
309
|
+
- Keep all historical startup logs for pattern analysis
|
|
310
|
+
- Add file handler to root logger to capture ALL module logs
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
project_root: Root directory for the project (defaults to cwd)
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
Path to the created log file
|
|
317
|
+
"""
|
|
318
|
+
if project_root is None:
|
|
319
|
+
project_root = Path.cwd()
|
|
320
|
+
|
|
321
|
+
# Create log directory
|
|
322
|
+
log_dir = project_root / ".claude-mpm" / "logs" / "startup"
|
|
323
|
+
log_dir.mkdir(parents=True, exist_ok=True)
|
|
324
|
+
|
|
325
|
+
# Generate timestamp for log file
|
|
326
|
+
timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
|
|
327
|
+
log_file = log_dir / f"startup-{timestamp}.log"
|
|
328
|
+
|
|
329
|
+
# Create file handler with detailed formatting
|
|
330
|
+
file_handler = logging.FileHandler(log_file, encoding='utf-8')
|
|
331
|
+
file_handler.setLevel(logging.DEBUG) # Capture all levels to file
|
|
332
|
+
|
|
333
|
+
# Format with timestamp, logger name, level, and message
|
|
334
|
+
formatter = logging.Formatter(
|
|
335
|
+
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
336
|
+
datefmt='%Y-%m-%d %H:%M:%S'
|
|
337
|
+
)
|
|
338
|
+
file_handler.setFormatter(formatter)
|
|
339
|
+
|
|
340
|
+
# Add to claude_mpm logger to capture all our logs
|
|
341
|
+
# (Don't add to root logger to avoid duplicates from propagation)
|
|
342
|
+
claude_logger = logging.getLogger("claude_mpm")
|
|
343
|
+
claude_logger.addHandler(file_handler)
|
|
344
|
+
claude_logger.setLevel(logging.DEBUG) # Ensure all levels are captured
|
|
345
|
+
|
|
346
|
+
# Log startup header
|
|
347
|
+
logger = get_logger("startup")
|
|
348
|
+
logger.info("="*60)
|
|
349
|
+
logger.info(f"Claude MPM Startup - {datetime.now().isoformat()}")
|
|
350
|
+
logger.info(f"Log file: {log_file}")
|
|
351
|
+
logger.info("="*60)
|
|
352
|
+
|
|
353
|
+
# Log system information
|
|
354
|
+
logger.info(f"Python: {sys.version}")
|
|
355
|
+
logger.info(f"Platform: {sys.platform}")
|
|
356
|
+
logger.info(f"CWD: {Path.cwd()}")
|
|
357
|
+
logger.info(f"Project root: {project_root}")
|
|
358
|
+
|
|
359
|
+
return log_file
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def cleanup_old_startup_logs(project_root: Optional[Path] = None,
|
|
363
|
+
keep_days: int = 7,
|
|
364
|
+
keep_min_count: int = 10) -> int:
|
|
365
|
+
"""
|
|
366
|
+
Clean up old startup log files.
|
|
367
|
+
|
|
368
|
+
WHY: Prevent unbounded growth of startup logs while keeping enough
|
|
369
|
+
history for debugging patterns.
|
|
370
|
+
|
|
371
|
+
DESIGN DECISIONS:
|
|
372
|
+
- Keep logs from last N days
|
|
373
|
+
- Always keep minimum count regardless of age
|
|
374
|
+
- Return count of deleted files for reporting
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
project_root: Root directory for the project
|
|
378
|
+
keep_days: Number of days to keep logs
|
|
379
|
+
keep_min_count: Minimum number of logs to keep regardless of age
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
Number of log files deleted
|
|
383
|
+
"""
|
|
384
|
+
if project_root is None:
|
|
385
|
+
project_root = Path.cwd()
|
|
386
|
+
|
|
387
|
+
log_dir = project_root / ".claude-mpm" / "logs" / "startup"
|
|
388
|
+
|
|
389
|
+
if not log_dir.exists():
|
|
390
|
+
return 0
|
|
391
|
+
|
|
392
|
+
# Get all startup log files
|
|
393
|
+
log_files = sorted(log_dir.glob("startup-*.log"),
|
|
394
|
+
key=lambda p: p.stat().st_mtime,
|
|
395
|
+
reverse=True) # Newest first
|
|
396
|
+
|
|
397
|
+
if len(log_files) <= keep_min_count:
|
|
398
|
+
return 0 # Keep minimum count
|
|
399
|
+
|
|
400
|
+
# Calculate cutoff time
|
|
401
|
+
cutoff_time = datetime.now().timestamp() - (keep_days * 24 * 60 * 60)
|
|
402
|
+
|
|
403
|
+
deleted_count = 0
|
|
404
|
+
for log_file in log_files[keep_min_count:]: # Skip minimum count
|
|
405
|
+
if log_file.stat().st_mtime < cutoff_time:
|
|
406
|
+
try:
|
|
407
|
+
log_file.unlink()
|
|
408
|
+
deleted_count += 1
|
|
409
|
+
except Exception:
|
|
410
|
+
pass # Ignore deletion errors
|
|
411
|
+
|
|
412
|
+
return deleted_count
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def get_latest_startup_log(project_root: Optional[Path] = None) -> Optional[Path]:
|
|
416
|
+
"""
|
|
417
|
+
Get the path to the most recent startup log file.
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
project_root: Root directory for the project
|
|
421
|
+
|
|
422
|
+
Returns:
|
|
423
|
+
Path to latest log file or None if no logs exist
|
|
424
|
+
"""
|
|
425
|
+
if project_root is None:
|
|
426
|
+
project_root = Path.cwd()
|
|
427
|
+
|
|
428
|
+
log_dir = project_root / ".claude-mpm" / "logs" / "startup"
|
|
429
|
+
|
|
430
|
+
if not log_dir.exists():
|
|
431
|
+
return None
|
|
432
|
+
|
|
433
|
+
log_files = sorted(log_dir.glob("startup-*.log"),
|
|
434
|
+
key=lambda p: p.stat().st_mtime,
|
|
435
|
+
reverse=True)
|
|
436
|
+
|
|
437
|
+
return log_files[0] if log_files else None
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def log_startup_status(monitor_mode: bool = False, websocket_port: int = 8765) -> None:
|
|
441
|
+
"""
|
|
442
|
+
Log comprehensive startup status for MCP server and monitor setup.
|
|
443
|
+
|
|
444
|
+
This function should be called during application startup to provide
|
|
445
|
+
detailed information about MCP and monitor setup status.
|
|
446
|
+
|
|
447
|
+
Args:
|
|
448
|
+
monitor_mode: Whether monitor mode is enabled
|
|
449
|
+
websocket_port: WebSocket port for monitoring
|
|
450
|
+
"""
|
|
451
|
+
try:
|
|
452
|
+
status_logger = StartupStatusLogger("cli")
|
|
453
|
+
|
|
454
|
+
# Log MCP server status
|
|
455
|
+
status_logger.log_mcp_server_status()
|
|
456
|
+
|
|
457
|
+
# Log monitor setup status
|
|
458
|
+
status_logger.log_monitor_setup_status(monitor_mode, websocket_port)
|
|
459
|
+
|
|
460
|
+
except Exception as e:
|
|
461
|
+
# Don't let logging failures prevent startup
|
|
462
|
+
logger = get_logger("cli")
|
|
463
|
+
logger.debug(f"Startup status logging failed: {e}")
|
claude_mpm/constants.py
CHANGED
|
@@ -33,12 +33,14 @@ class CLICommands(str, Enum):
|
|
|
33
33
|
TICKETS = "tickets"
|
|
34
34
|
INFO = "info"
|
|
35
35
|
AGENTS = "agents"
|
|
36
|
+
AGENT_MANAGER = "agent-manager"
|
|
36
37
|
MEMORY = "memory"
|
|
37
38
|
MONITOR = "monitor"
|
|
38
39
|
CONFIG = "config"
|
|
39
40
|
AGGREGATE = "aggregate"
|
|
40
41
|
CLEANUP = "cleanup-memory"
|
|
41
42
|
MCP = "mcp"
|
|
43
|
+
DOCTOR = "doctor"
|
|
42
44
|
|
|
43
45
|
def with_prefix(self, prefix: CLIPrefix = CLIPrefix.MPM) -> str:
|
|
44
46
|
"""Get command with prefix."""
|
claude_mpm/core/claude_runner.py
CHANGED
|
@@ -210,6 +210,10 @@ class ClaudeRunner:
|
|
|
210
210
|
else:
|
|
211
211
|
self.system_instructions = self._load_system_instructions()
|
|
212
212
|
|
|
213
|
+
# Deploy output style early (before Claude Code launches)
|
|
214
|
+
# This ensures the "Claude MPM" output style is active on startup
|
|
215
|
+
self._deploy_output_style()
|
|
216
|
+
|
|
213
217
|
# Create session log file using configuration service
|
|
214
218
|
self.session_log_file = self.configuration_service.create_session_log_file(
|
|
215
219
|
self.project_logger, self.log_level, config_data
|
|
@@ -497,8 +501,9 @@ class ClaudeRunner:
|
|
|
497
501
|
|
|
498
502
|
if needs_update:
|
|
499
503
|
# Build the agent markdown using the pre-initialized service and base agent data
|
|
500
|
-
|
|
501
|
-
|
|
504
|
+
# Use template_builder service instead of removed _build_agent_markdown method
|
|
505
|
+
agent_content = project_deployment.template_builder.build_agent_markdown(
|
|
506
|
+
agent_name, json_file, base_agent_data, source_info="project"
|
|
502
507
|
)
|
|
503
508
|
|
|
504
509
|
# Mark as project agent
|
|
@@ -731,6 +736,80 @@ Use these agents to delegate specialized work via the Task tool.
|
|
|
731
736
|
# Fallback if service not available
|
|
732
737
|
return "v0.0.0"
|
|
733
738
|
|
|
739
|
+
def _deploy_output_style(self) -> None:
|
|
740
|
+
"""Deploy the Claude MPM output style before Claude Code launches.
|
|
741
|
+
|
|
742
|
+
This method ensures the output style is set to "Claude MPM" on startup
|
|
743
|
+
by deploying the style file and updating Claude Code settings.
|
|
744
|
+
Only works for Claude Code >= 1.0.83.
|
|
745
|
+
"""
|
|
746
|
+
try:
|
|
747
|
+
from claude_mpm.core.output_style_manager import OutputStyleManager
|
|
748
|
+
|
|
749
|
+
# Create OutputStyleManager instance
|
|
750
|
+
output_style_manager = OutputStyleManager()
|
|
751
|
+
|
|
752
|
+
# Check if Claude Code supports output styles
|
|
753
|
+
if not output_style_manager.supports_output_styles():
|
|
754
|
+
self.logger.debug(
|
|
755
|
+
f"Claude Code version {output_style_manager.claude_version or 'unknown'} "
|
|
756
|
+
"does not support output styles (requires >= 1.0.83)"
|
|
757
|
+
)
|
|
758
|
+
return
|
|
759
|
+
|
|
760
|
+
# Check if output style is already deployed and active
|
|
761
|
+
settings_file = Path.home() / ".claude" / "settings.json"
|
|
762
|
+
if settings_file.exists():
|
|
763
|
+
try:
|
|
764
|
+
import json
|
|
765
|
+
settings = json.loads(settings_file.read_text())
|
|
766
|
+
if settings.get("activeOutputStyle") == "claude-mpm":
|
|
767
|
+
# Already active, check if file exists
|
|
768
|
+
output_style_file = Path.home() / ".claude" / "output-styles" / "claude-mpm.md"
|
|
769
|
+
if output_style_file.exists():
|
|
770
|
+
self.logger.debug("Output style 'Claude MPM' already deployed and active")
|
|
771
|
+
return
|
|
772
|
+
except Exception:
|
|
773
|
+
pass # Continue with deployment if we can't read settings
|
|
774
|
+
|
|
775
|
+
# Read the OUTPUT_STYLE.md content if it exists
|
|
776
|
+
output_style_path = Path(__file__).parent.parent / "agents" / "OUTPUT_STYLE.md"
|
|
777
|
+
|
|
778
|
+
if output_style_path.exists():
|
|
779
|
+
# Use existing OUTPUT_STYLE.md content
|
|
780
|
+
output_style_content = output_style_path.read_text()
|
|
781
|
+
self.logger.debug("Using existing OUTPUT_STYLE.md content")
|
|
782
|
+
else:
|
|
783
|
+
# Extract output style content from framework instructions
|
|
784
|
+
output_style_content = output_style_manager.extract_output_style_content()
|
|
785
|
+
self.logger.debug("Extracted output style from framework instructions")
|
|
786
|
+
|
|
787
|
+
# Deploy the output style
|
|
788
|
+
deployed = output_style_manager.deploy_output_style(output_style_content)
|
|
789
|
+
|
|
790
|
+
if deployed:
|
|
791
|
+
self.logger.info("✅ Output style 'Claude MPM' deployed and activated on startup")
|
|
792
|
+
if self.project_logger:
|
|
793
|
+
self.project_logger.log_system(
|
|
794
|
+
"Output style 'Claude MPM' deployed and activated on startup",
|
|
795
|
+
level="INFO",
|
|
796
|
+
component="output_style"
|
|
797
|
+
)
|
|
798
|
+
else:
|
|
799
|
+
self.logger.warning("Failed to deploy output style")
|
|
800
|
+
|
|
801
|
+
except ImportError as e:
|
|
802
|
+
self.logger.warning(f"Could not import OutputStyleManager: {e}")
|
|
803
|
+
except Exception as e:
|
|
804
|
+
# Don't fail startup if output style deployment fails
|
|
805
|
+
self.logger.warning(f"Error deploying output style: {e}")
|
|
806
|
+
if self.project_logger:
|
|
807
|
+
self.project_logger.log_system(
|
|
808
|
+
f"Output style deployment error: {e}",
|
|
809
|
+
level="WARNING",
|
|
810
|
+
component="output_style"
|
|
811
|
+
)
|
|
812
|
+
|
|
734
813
|
def _launch_subprocess_interactive(self, cmd: list, env: dict):
|
|
735
814
|
"""Launch Claude as a subprocess with PTY for interactive mode.
|
|
736
815
|
|
claude_mpm/core/constants.py
CHANGED
|
@@ -41,8 +41,8 @@ class NetworkConfig:
|
|
|
41
41
|
"""Network-related configuration constants."""
|
|
42
42
|
|
|
43
43
|
# Port ranges
|
|
44
|
-
SOCKETIO_PORT_RANGE: Tuple[int, int] = (
|
|
45
|
-
DEFAULT_SOCKETIO_PORT =
|
|
44
|
+
SOCKETIO_PORT_RANGE: Tuple[int, int] = (8765, 8785)
|
|
45
|
+
DEFAULT_SOCKETIO_PORT = 8765
|
|
46
46
|
DEFAULT_DASHBOARD_PORT = 8765
|
|
47
47
|
|
|
48
48
|
# Connection timeouts (seconds)
|