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.
Files changed (89) hide show
  1. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +48 -3
  2. claude_mpm/agents/BASE_PM.md +20 -15
  3. claude_mpm/agents/INSTRUCTIONS.md +12 -2
  4. claude_mpm/agents/templates/agent-manager.json +24 -0
  5. claude_mpm/agents/templates/agent-manager.md +304 -0
  6. claude_mpm/agents/templates/documentation.json +16 -3
  7. claude_mpm/agents/templates/engineer.json +19 -5
  8. claude_mpm/agents/templates/ops.json +19 -5
  9. claude_mpm/agents/templates/qa.json +16 -3
  10. claude_mpm/agents/templates/refactoring_engineer.json +25 -7
  11. claude_mpm/agents/templates/research.json +19 -5
  12. claude_mpm/cli/__init__.py +4 -0
  13. claude_mpm/cli/commands/__init__.py +4 -0
  14. claude_mpm/cli/commands/agent_manager.py +521 -0
  15. claude_mpm/cli/commands/agents.py +2 -1
  16. claude_mpm/cli/commands/cleanup.py +1 -1
  17. claude_mpm/cli/commands/doctor.py +209 -0
  18. claude_mpm/cli/commands/mcp.py +3 -3
  19. claude_mpm/cli/commands/mcp_install_commands.py +12 -30
  20. claude_mpm/cli/commands/mcp_server_commands.py +9 -9
  21. claude_mpm/cli/commands/memory.py +1 -1
  22. claude_mpm/cli/commands/run.py +31 -2
  23. claude_mpm/cli/commands/run_config_checker.py +1 -1
  24. claude_mpm/cli/parsers/agent_manager_parser.py +247 -0
  25. claude_mpm/cli/parsers/base_parser.py +12 -1
  26. claude_mpm/cli/parsers/mcp_parser.py +1 -1
  27. claude_mpm/cli/parsers/run_parser.py +1 -1
  28. claude_mpm/cli/shared/__init__.py +1 -1
  29. claude_mpm/cli/startup_logging.py +463 -0
  30. claude_mpm/constants.py +2 -0
  31. claude_mpm/core/claude_runner.py +81 -2
  32. claude_mpm/core/constants.py +2 -2
  33. claude_mpm/core/framework_loader.py +45 -11
  34. claude_mpm/core/interactive_session.py +82 -3
  35. claude_mpm/core/output_style_manager.py +6 -6
  36. claude_mpm/core/socketio_pool.py +2 -2
  37. claude_mpm/core/unified_paths.py +128 -0
  38. claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
  39. claude_mpm/dashboard/static/built/components/module-viewer.js +1 -1
  40. claude_mpm/dashboard/static/built/dashboard.js +1 -1
  41. claude_mpm/dashboard/static/built/socket-client.js +1 -1
  42. claude_mpm/dashboard/static/css/dashboard.css +170 -0
  43. claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
  44. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  45. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  46. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +21 -3
  47. claude_mpm/dashboard/static/js/components/module-viewer.js +129 -1
  48. claude_mpm/dashboard/static/js/dashboard.js +116 -0
  49. claude_mpm/dashboard/static/js/socket-client.js +0 -1
  50. claude_mpm/hooks/claude_hooks/connection_pool.py +1 -1
  51. claude_mpm/hooks/claude_hooks/hook_handler.py +1 -1
  52. claude_mpm/scripts/mcp_server.py +2 -2
  53. claude_mpm/services/agents/agent_builder.py +455 -0
  54. claude_mpm/services/agents/deployment/agent_template_builder.py +10 -3
  55. claude_mpm/services/agents/deployment/agent_validator.py +1 -0
  56. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +69 -1
  57. claude_mpm/services/diagnostics/__init__.py +18 -0
  58. claude_mpm/services/diagnostics/checks/__init__.py +30 -0
  59. claude_mpm/services/diagnostics/checks/agent_check.py +319 -0
  60. claude_mpm/services/diagnostics/checks/base_check.py +64 -0
  61. claude_mpm/services/diagnostics/checks/claude_desktop_check.py +283 -0
  62. claude_mpm/services/diagnostics/checks/common_issues_check.py +354 -0
  63. claude_mpm/services/diagnostics/checks/configuration_check.py +300 -0
  64. claude_mpm/services/diagnostics/checks/filesystem_check.py +233 -0
  65. claude_mpm/services/diagnostics/checks/installation_check.py +255 -0
  66. claude_mpm/services/diagnostics/checks/mcp_check.py +315 -0
  67. claude_mpm/services/diagnostics/checks/monitor_check.py +282 -0
  68. claude_mpm/services/diagnostics/checks/startup_log_check.py +322 -0
  69. claude_mpm/services/diagnostics/diagnostic_runner.py +247 -0
  70. claude_mpm/services/diagnostics/doctor_reporter.py +283 -0
  71. claude_mpm/services/diagnostics/models.py +120 -0
  72. claude_mpm/services/mcp_gateway/core/interfaces.py +1 -1
  73. claude_mpm/services/mcp_gateway/main.py +1 -1
  74. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +3 -3
  75. claude_mpm/services/mcp_gateway/server/stdio_handler.py +1 -1
  76. claude_mpm/services/mcp_gateway/server/stdio_server.py +3 -3
  77. claude_mpm/services/mcp_gateway/tools/ticket_tools.py +2 -2
  78. claude_mpm/services/memory/__init__.py +2 -0
  79. claude_mpm/services/socketio/handlers/connection.py +27 -33
  80. claude_mpm/services/socketio/handlers/registry.py +39 -7
  81. claude_mpm/services/socketio/server/core.py +72 -22
  82. claude_mpm/validation/frontmatter_validator.py +1 -1
  83. {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/METADATA +4 -1
  84. {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/RECORD +89 -67
  85. /claude_mpm/cli/shared/{command_base.py → base_command.py} +0 -0
  86. {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/WHEEL +0 -0
  87. {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/entry_points.txt +0 -0
  88. {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/licenses/LICENSE +0 -0
  89. {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 Desktop to resume the last conversation",
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 Desktop"
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 Desktop to resume the last conversation",
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 .command_base import AgentCommand, BaseCommand, CommandResult, MemoryCommand
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."""
@@ -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
- agent_content = project_deployment._build_agent_markdown(
501
- agent_name, json_file, base_agent_data
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
 
@@ -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] = (8080, 8099)
45
- DEFAULT_SOCKETIO_PORT = 8080
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)