claude-mpm 4.2.21__py3-none-any.whl → 4.2.22__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/VERSION CHANGED
@@ -1 +1 @@
1
- 4.2.21
1
+ 4.2.22
@@ -105,12 +105,44 @@ class MonitorCommand(BaseCommand):
105
105
 
106
106
  # Start the daemon
107
107
  if self.daemon.start():
108
- mode_info = " in background" if daemon_mode else " in foreground"
108
+ # For daemon mode, verify it actually started
109
+ if daemon_mode:
110
+ # Give it a moment to fully initialize
111
+ import time
112
+ time.sleep(0.5)
113
+
114
+ # Check if it's actually running
115
+ if not self.daemon.lifecycle.is_running():
116
+ return CommandResult.error_result(
117
+ "Monitor daemon failed to start. Check ~/.claude-mpm/monitor-daemon.log for details."
118
+ )
119
+
120
+ # Get the actual PID
121
+ actual_pid = self.daemon.lifecycle.get_pid()
122
+ mode_info = f" in background (PID: {actual_pid})"
123
+ else:
124
+ mode_info = " in foreground"
125
+
109
126
  return CommandResult.success_result(
110
127
  f"Unified monitor daemon started on {host}:{port}{mode_info}",
111
128
  data={"url": f"http://{host}:{port}", "port": port, "mode": mode_str},
112
129
  )
113
- return CommandResult.error_result("Failed to start unified monitor daemon")
130
+
131
+ # Check if error was due to port already in use
132
+ import socket
133
+ try:
134
+ test_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
135
+ test_sock.connect((host, port))
136
+ test_sock.close()
137
+ return CommandResult.error_result(
138
+ f"Port {port} is already in use. Try 'claude-mpm monitor stop' first or use a different port."
139
+ )
140
+ except:
141
+ pass
142
+
143
+ return CommandResult.error_result(
144
+ "Failed to start unified monitor daemon. Check ~/.claude-mpm/monitor-daemon.log for details."
145
+ )
114
146
 
115
147
  def _stop_monitor(self, args) -> CommandResult:
116
148
  """Stop the unified monitor daemon."""
@@ -57,9 +57,11 @@ class UnifiedMonitorDaemon:
57
57
  self.daemon_mode = daemon_mode
58
58
  self.logger = get_logger(__name__)
59
59
 
60
- # Daemon management
60
+ # Daemon management with port for verification
61
61
  self.lifecycle = DaemonLifecycle(
62
- pid_file=pid_file or self._get_default_pid_file(), log_file=log_file
62
+ pid_file=pid_file or self._get_default_pid_file(),
63
+ log_file=log_file,
64
+ port=port
63
65
  )
64
66
 
65
67
  # Core server
@@ -102,6 +104,13 @@ class UnifiedMonitorDaemon:
102
104
  existing_pid = self.lifecycle.get_pid()
103
105
  self.logger.warning(f"Daemon already running with PID {existing_pid}")
104
106
  return False
107
+
108
+ # Verify port is available before forking
109
+ port_available, error_msg = self.lifecycle.verify_port_available(self.host)
110
+ if not port_available:
111
+ self.logger.error(error_msg)
112
+ print(f"Error: {error_msg}", file=sys.stderr)
113
+ return False
105
114
 
106
115
  # Wait for any pre-warming threads to complete before forking
107
116
  self._wait_for_prewarm_completion()
@@ -112,7 +121,17 @@ class UnifiedMonitorDaemon:
112
121
  return False
113
122
 
114
123
  # Start the server in daemon mode
115
- return self._run_server()
124
+ # This will run in the child process
125
+ try:
126
+ result = self._run_server()
127
+ if not result:
128
+ # Report failure before exiting
129
+ self.lifecycle._report_startup_error("Failed to start server")
130
+ return result
131
+ except Exception as e:
132
+ # Report any exceptions during startup
133
+ self.lifecycle._report_startup_error(f"Server startup exception: {e}")
134
+ raise
116
135
 
117
136
  def _start_foreground(self) -> bool:
118
137
  """Start in foreground mode."""
@@ -140,11 +159,17 @@ class UnifiedMonitorDaemon:
140
159
  try:
141
160
  # Ensure components exist before starting
142
161
  if not self.health_monitor:
143
- self.logger.error("Health monitor not initialized")
162
+ error_msg = "Health monitor not initialized"
163
+ self.logger.error(error_msg)
164
+ if self.daemon_mode:
165
+ self.lifecycle._report_startup_error(error_msg)
144
166
  return False
145
167
 
146
168
  if not self.server:
147
- self.logger.error("Server not initialized")
169
+ error_msg = "Server not initialized"
170
+ self.logger.error(error_msg)
171
+ if self.daemon_mode:
172
+ self.lifecycle._report_startup_error(error_msg)
148
173
  return False
149
174
 
150
175
  # Start health monitoring
@@ -153,11 +178,18 @@ class UnifiedMonitorDaemon:
153
178
  # Start the unified server
154
179
  success = self.server.start()
155
180
  if not success:
156
- self.logger.error("Failed to start unified monitor server")
181
+ error_msg = "Failed to start unified monitor server"
182
+ self.logger.error(error_msg)
183
+ if self.daemon_mode:
184
+ self.lifecycle._report_startup_error(error_msg)
157
185
  return False
158
186
 
159
187
  self.running = True
160
188
  self.logger.info("Unified monitor daemon started successfully")
189
+
190
+ # Report successful startup to parent (for daemon mode)
191
+ if self.daemon_mode:
192
+ self.lifecycle._report_startup_success()
161
193
 
162
194
  # Keep running until shutdown
163
195
  if self.daemon_mode:
@@ -15,10 +15,12 @@ DESIGN DECISIONS:
15
15
 
16
16
  import os
17
17
  import signal
18
+ import socket
18
19
  import sys
20
+ import tempfile
19
21
  import time
20
22
  from pathlib import Path
21
- from typing import Optional
23
+ from typing import Optional, Tuple
22
24
 
23
25
  from ....core.logging_config import get_logger
24
26
 
@@ -30,16 +32,20 @@ class DaemonLifecycle:
30
32
  handling, and graceful shutdown capabilities.
31
33
  """
32
34
 
33
- def __init__(self, pid_file: str, log_file: Optional[str] = None):
35
+ def __init__(self, pid_file: str, log_file: Optional[str] = None, port: int = 8765):
34
36
  """Initialize daemon lifecycle manager.
35
37
 
36
38
  Args:
37
39
  pid_file: Path to PID file
38
40
  log_file: Path to log file for daemon mode
41
+ port: Port number for startup verification
39
42
  """
40
43
  self.pid_file = Path(pid_file)
41
44
  self.log_file = Path(log_file) if log_file else None
45
+ self.port = port
42
46
  self.logger = get_logger(__name__)
47
+ # Create a temporary file for startup status communication
48
+ self.startup_status_file = None
43
49
 
44
50
  def daemonize(self) -> bool:
45
51
  """Daemonize the current process.
@@ -50,14 +56,20 @@ class DaemonLifecycle:
50
56
  try:
51
57
  # Clean up any existing asyncio event loops before forking
52
58
  self._cleanup_event_loops()
59
+
60
+ # Create a temporary file for startup status communication
61
+ with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.status') as f:
62
+ self.startup_status_file = f.name
63
+ f.write("starting")
53
64
 
54
65
  # First fork
55
66
  pid = os.fork()
56
67
  if pid > 0:
57
- # Parent process exits
58
- sys.exit(0)
68
+ # Parent process - wait for child to confirm startup
69
+ return self._parent_wait_for_startup(pid)
59
70
  except OSError as e:
60
71
  self.logger.error(f"First fork failed: {e}")
72
+ self._report_startup_error(f"First fork failed: {e}")
61
73
  return False
62
74
 
63
75
  # Decouple from parent environment
@@ -69,22 +81,33 @@ class DaemonLifecycle:
69
81
  # Second fork
70
82
  pid = os.fork()
71
83
  if pid > 0:
72
- # Parent process exits
84
+ # First child process exits
73
85
  sys.exit(0)
74
86
  except OSError as e:
75
87
  self.logger.error(f"Second fork failed: {e}")
88
+ self._report_startup_error(f"Second fork failed: {e}")
89
+ return False
90
+
91
+ # Set up error logging before redirecting streams
92
+ self._setup_early_error_logging()
93
+
94
+ # Write PID file first (before stream redirection)
95
+ try:
96
+ self.write_pid_file()
97
+ except Exception as e:
98
+ self._report_startup_error(f"Failed to write PID file: {e}")
76
99
  return False
77
100
 
78
101
  # Redirect standard file descriptors
79
102
  self._redirect_streams()
80
103
 
81
- # Write PID file
82
- self.write_pid_file()
83
-
84
104
  # Setup signal handlers
85
105
  self._setup_signal_handlers()
86
106
 
87
107
  self.logger.info(f"Daemon process started with PID {os.getpid()}")
108
+
109
+ # Report successful startup (after basic setup but before server start)
110
+ self._report_startup_success()
88
111
  return True
89
112
 
90
113
  def _redirect_streams(self):
@@ -105,13 +128,16 @@ class DaemonLifecycle:
105
128
  os.dup2(log_out.fileno(), sys.stdout.fileno())
106
129
  os.dup2(log_out.fileno(), sys.stderr.fileno())
107
130
  else:
108
- # Redirect to /dev/null
109
- with open("/dev/null", "w") as null_out:
110
- os.dup2(null_out.fileno(), sys.stdout.fileno())
111
- os.dup2(null_out.fileno(), sys.stderr.fileno())
131
+ # Default to a daemon log file instead of /dev/null for errors
132
+ default_log = Path.home() / ".claude-mpm" / "monitor-daemon.log"
133
+ default_log.parent.mkdir(parents=True, exist_ok=True)
134
+ with open(default_log, "a") as log_out:
135
+ os.dup2(log_out.fileno(), sys.stdout.fileno())
136
+ os.dup2(log_out.fileno(), sys.stderr.fileno())
112
137
 
113
138
  except Exception as e:
114
139
  self.logger.error(f"Error redirecting streams: {e}")
140
+ self._report_startup_error(f"Failed to redirect streams: {e}")
115
141
 
116
142
  def write_pid_file(self):
117
143
  """Write PID to PID file."""
@@ -336,3 +362,138 @@ class DaemonLifecycle:
336
362
  self.logger.debug(f"Error getting process info: {e}")
337
363
 
338
364
  return status
365
+
366
+ def _parent_wait_for_startup(self, child_pid: int, timeout: float = 10.0) -> bool:
367
+ """Parent process waits for child daemon to report startup status.
368
+
369
+ Args:
370
+ child_pid: PID of the child process
371
+ timeout: Maximum time to wait for startup
372
+
373
+ Returns:
374
+ True if child started successfully, False otherwise
375
+ """
376
+ import time
377
+ start_time = time.time()
378
+
379
+ # Wait for child to update status file
380
+ while time.time() - start_time < timeout:
381
+ try:
382
+ # Check if status file exists and read it
383
+ if self.startup_status_file and Path(self.startup_status_file).exists():
384
+ with open(self.startup_status_file, 'r') as f:
385
+ status = f.read().strip()
386
+
387
+ if status == "success":
388
+ # Child started successfully
389
+ self._cleanup_status_file()
390
+ return True
391
+ elif status.startswith("error:"):
392
+ # Child reported an error
393
+ error_msg = status[6:] # Remove "error:" prefix
394
+ self.logger.error(f"Daemon startup failed: {error_msg}")
395
+ print(f"Error: Failed to start monitor daemon: {error_msg}", file=sys.stderr)
396
+ self._cleanup_status_file()
397
+ return False
398
+ elif status == "starting":
399
+ # Still starting, continue waiting
400
+ pass
401
+
402
+ # Also check if child process is still alive
403
+ try:
404
+ os.kill(child_pid, 0) # Check if process exists
405
+ except ProcessLookupError:
406
+ # Child process died
407
+ self.logger.error("Child daemon process died during startup")
408
+ print("Error: Monitor daemon process died during startup", file=sys.stderr)
409
+ self._cleanup_status_file()
410
+ return False
411
+
412
+ except Exception as e:
413
+ self.logger.debug(f"Error checking startup status: {e}")
414
+
415
+ time.sleep(0.1) # Check every 100ms
416
+
417
+ # Timeout reached
418
+ self.logger.error(f"Daemon startup timed out after {timeout} seconds")
419
+ print(f"Error: Monitor daemon startup timed out after {timeout} seconds", file=sys.stderr)
420
+ self._cleanup_status_file()
421
+ return False
422
+
423
+ def _report_startup_success(self):
424
+ """Report successful startup to parent process."""
425
+ if self.startup_status_file:
426
+ try:
427
+ with open(self.startup_status_file, 'w') as f:
428
+ f.write("success")
429
+ except Exception as e:
430
+ self.logger.error(f"Failed to report startup success: {e}")
431
+
432
+ def _report_startup_error(self, error_msg: str):
433
+ """Report startup error to parent process.
434
+
435
+ Args:
436
+ error_msg: Error message to report
437
+ """
438
+ if self.startup_status_file:
439
+ try:
440
+ with open(self.startup_status_file, 'w') as f:
441
+ f.write(f"error:{error_msg}")
442
+ except Exception:
443
+ pass # Can't report if file write fails
444
+
445
+ def _cleanup_status_file(self):
446
+ """Clean up the temporary status file."""
447
+ if self.startup_status_file:
448
+ try:
449
+ Path(self.startup_status_file).unlink(missing_ok=True)
450
+ except Exception:
451
+ pass # Ignore cleanup errors
452
+ finally:
453
+ self.startup_status_file = None
454
+
455
+ def _setup_early_error_logging(self):
456
+ """Set up error logging before stream redirection.
457
+
458
+ This ensures we can capture and report errors that occur during
459
+ daemon initialization, especially port binding errors.
460
+ """
461
+ try:
462
+ # If no log file specified, create a default one
463
+ if not self.log_file:
464
+ default_log = Path.home() / ".claude-mpm" / "monitor-daemon.log"
465
+ default_log.parent.mkdir(parents=True, exist_ok=True)
466
+ self.log_file = default_log
467
+
468
+ # Configure logger to write to file immediately
469
+ import logging
470
+ file_handler = logging.FileHandler(self.log_file)
471
+ file_handler.setLevel(logging.DEBUG)
472
+ formatter = logging.Formatter(
473
+ '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
474
+ )
475
+ file_handler.setFormatter(formatter)
476
+ self.logger.addHandler(file_handler)
477
+
478
+ except Exception as e:
479
+ # If we can't set up logging, at least try to report the error
480
+ self._report_startup_error(f"Failed to setup error logging: {e}")
481
+
482
+ def verify_port_available(self, host: str = "localhost") -> Tuple[bool, Optional[str]]:
483
+ """Verify that the port is available for binding.
484
+
485
+ Args:
486
+ host: Host to check port on
487
+
488
+ Returns:
489
+ Tuple of (is_available, error_message)
490
+ """
491
+ try:
492
+ test_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
493
+ test_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
494
+ test_sock.bind((host, self.port))
495
+ test_sock.close()
496
+ return True, None
497
+ except OSError as e:
498
+ error_msg = f"Port {self.port} is already in use or cannot be bound: {e}"
499
+ return False, error_msg
@@ -79,6 +79,7 @@ class UnifiedMonitorServer:
79
79
  self.running = False
80
80
  self.loop = None
81
81
  self.server_thread = None
82
+ self.startup_error = None # Track startup errors
82
83
 
83
84
  # Heartbeat tracking
84
85
  self.heartbeat_task: Optional[asyncio.Task] = None
@@ -106,10 +107,15 @@ class UnifiedMonitorServer:
106
107
  for _ in range(50): # Wait up to 5 seconds
107
108
  if self.running:
108
109
  break
110
+ if self.startup_error:
111
+ # Server thread reported an error
112
+ self.logger.error(f"Server startup failed: {self.startup_error}")
113
+ return False
109
114
  time.sleep(0.1)
110
115
 
111
116
  if not self.running:
112
- self.logger.error("Server failed to start within timeout")
117
+ error_msg = self.startup_error or "Server failed to start within timeout"
118
+ self.logger.error(error_msg)
113
119
  return False
114
120
 
115
121
  self.logger.info("Unified monitor server started successfully")
@@ -131,8 +137,17 @@ class UnifiedMonitorServer:
131
137
  # Run the async server
132
138
  loop.run_until_complete(self._start_async_server())
133
139
 
140
+ except OSError as e:
141
+ # Specific handling for port binding errors
142
+ if "Address already in use" in str(e) or "[Errno 48]" in str(e):
143
+ self.logger.error(f"Port {self.port} is already in use: {e}")
144
+ self.startup_error = f"Port {self.port} is already in use"
145
+ else:
146
+ self.logger.error(f"OS error in server thread: {e}")
147
+ self.startup_error = str(e)
134
148
  except Exception as e:
135
149
  self.logger.error(f"Error in server thread: {e}")
150
+ self.startup_error = str(e)
136
151
  finally:
137
152
  # Always ensure loop cleanup happens
138
153
  if loop is not None:
@@ -212,11 +227,23 @@ class UnifiedMonitorServer:
212
227
  self.runner = web.AppRunner(self.app)
213
228
  await self.runner.setup()
214
229
 
215
- self.site = web.TCPSite(self.runner, self.host, self.port)
216
- await self.site.start()
217
-
218
- self.running = True
219
- self.logger.info(f"Server running on http://{self.host}:{self.port}")
230
+ try:
231
+ self.site = web.TCPSite(self.runner, self.host, self.port)
232
+ await self.site.start()
233
+
234
+ self.running = True
235
+ self.logger.info(f"Server running on http://{self.host}:{self.port}")
236
+ except OSError as e:
237
+ # Port binding error - make sure it's reported clearly
238
+ if "Address already in use" in str(e) or "[Errno 48]" in str(e):
239
+ error_msg = f"Port {self.port} is already in use. Another process may be using this port."
240
+ self.logger.error(error_msg)
241
+ self.startup_error = error_msg
242
+ raise OSError(error_msg) from e
243
+ else:
244
+ self.logger.error(f"Failed to bind to {self.host}:{self.port}: {e}")
245
+ self.startup_error = str(e)
246
+ raise
220
247
 
221
248
  # Keep the server running
222
249
  while self.running:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-mpm
3
- Version: 4.2.21
3
+ Version: 4.2.22
4
4
  Summary: Claude Multi-Agent Project Manager - Orchestrate Claude with agent delegation and ticket tracking
5
5
  Author-email: Bob Matsuoka <bob@matsuoka.com>
6
6
  Maintainer: Claude MPM Team
@@ -149,11 +149,18 @@ pip install claude-mpm
149
149
 
150
150
  Or with pipx (recommended for isolated installation):
151
151
  ```bash
152
+ # Install with monitor support (recommended)
153
+ pipx install "claude-mpm[monitor]"
154
+
155
+ # Basic installation without monitor
152
156
  pipx install claude-mpm
157
+
153
158
  # Configure MCP for pipx users:
154
159
  claude-mpm mcp-pipx-config
155
160
  ```
156
161
 
162
+ **💡 Pipx Tip**: Use `"claude-mpm[monitor]"` to get full monitoring dashboard functionality! The `[monitor]` optional dependency includes Socket.IO and async web server components needed for real-time agent monitoring.
163
+
157
164
  **🎉 Pipx Support Now Fully Functional!** Recent improvements ensure complete compatibility:
158
165
  - ✅ Socket.IO daemon script path resolution (fixed)
159
166
  - ✅ Commands directory access (fixed)
@@ -1,5 +1,5 @@
1
1
  claude_mpm/BUILD_NUMBER,sha256=toytnNjkIKPgQaGwDqQdC1rpNTAdSEc6Vja50d7Ovug,4
2
- claude_mpm/VERSION,sha256=wsCsu3SGhnVCrJ3G8gVo7Vf1VJhypvvtmS7mEhwhYs4,7
2
+ claude_mpm/VERSION,sha256=Ot17YPeSmtj6ZTyrKo7TK2xXA1paIoGEVSS9MGsMyHA,7
3
3
  claude_mpm/__init__.py,sha256=lyTZAYGH4DTaFGLRNWJKk5Q5oTjzN5I6AXmfVX-Jff0,1512
4
4
  claude_mpm/__main__.py,sha256=Ro5UBWBoQaSAIoSqWAr7zkbLyvi4sSy28WShqAhKJG0,723
5
5
  claude_mpm/constants.py,sha256=I946iCQzIIPRZVVJ8aO7lA4euiyDnNw2IX7EelAOkIE,5915
@@ -83,7 +83,7 @@ claude_mpm/cli/commands/mcp_pipx_config.py,sha256=sE62VD6Q1CcO2k1nlbIhHMfAJFQTZf
83
83
  claude_mpm/cli/commands/mcp_server_commands.py,sha256=-1G_2Y5ScTvzDd-kY8fTAao2H6FH7DnsLimleF1rVqQ,6197
84
84
  claude_mpm/cli/commands/mcp_tool_commands.py,sha256=q17GzlFT3JiLTrDqwPO2tz1-fKmPO5QU449syTnKTz4,1283
85
85
  claude_mpm/cli/commands/memory.py,sha256=Yzfs3_oiKciv3sfOoDm2lJL4M9idG7ARV3-sNw1ge_g,26186
86
- claude_mpm/cli/commands/monitor.py,sha256=Z7JPvx1PO4t8vUsWVZvWqdEkPtDG7E-01Bn1lUjY2sY,8213
86
+ claude_mpm/cli/commands/monitor.py,sha256=WeUprVOIbuZuxmw4GJo54RPTnYIIJ5LgFonAymeTW7k,9442
87
87
  claude_mpm/cli/commands/mpm_init.py,sha256=lO7N91ZHn_n18XbchUUcYoyme7L5NLcXVnhWm5F_Gq8,22367
88
88
  claude_mpm/cli/commands/mpm_init_handler.py,sha256=-pCB0XL3KipqGtnta8CC7Lg5TPMwstEhMFBcgF4aaa4,2919
89
89
  claude_mpm/cli/commands/run.py,sha256=qS3eolLiDrE8EXLQJioB6kL1ONr_l0c3OE3qMUJCqbA,43489
@@ -548,9 +548,9 @@ claude_mpm/services/memory/cache/__init__.py,sha256=6M6-P8ParyxX8vOgp_IxHgLMvacr
548
548
  claude_mpm/services/memory/cache/shared_prompt_cache.py,sha256=crnYPUT8zcS7TvoE1vW7pyaf4T77N5rJ1wUf_YQ2vvo,28704
549
549
  claude_mpm/services/memory/cache/simple_cache.py,sha256=qsTjbcsPxj-kNfaod9VN_uE5NioIwpfkUin_mMVUJCg,10218
550
550
  claude_mpm/services/monitor/__init__.py,sha256=X7gxSLUm9Fg_zEsX6LtCHP2ipF0qj6Emkun20h2So7g,745
551
- claude_mpm/services/monitor/daemon.py,sha256=f51ulONe_IyHpfFTFaEl93CQc5gnNT3nrQGMdb6hfo0,15351
551
+ claude_mpm/services/monitor/daemon.py,sha256=oFMDnMZABAqKEuuj62W2gmDSxUSr0jHflzddMVfXz8k,16739
552
552
  claude_mpm/services/monitor/event_emitter.py,sha256=JzRLNg8PUJ5s3ulNnq_D4yqCPItvidJzu8DmFxriieQ,12224
553
- claude_mpm/services/monitor/server.py,sha256=Z7K4pKEbrDtEVVJzrB6QVPVhW_3eEcTaQAnpP3boJco,26401
553
+ claude_mpm/services/monitor/server.py,sha256=ELo1CLucGAiXLh7iSkwM82nCCwE_ANgqd5sMG0TwUY0,27899
554
554
  claude_mpm/services/monitor/handlers/__init__.py,sha256=jgPIf4IJVERm_tAeD9834tfx9IcxtlHj5r9rhEWpkfM,701
555
555
  claude_mpm/services/monitor/handlers/code_analysis.py,sha256=mHyI27Wp6WVmUBc0m0i991ogyFZBTvkrfR7Kf3EAk5U,11474
556
556
  claude_mpm/services/monitor/handlers/dashboard.py,sha256=uGBhb-6RG6u4WLipUXgdx7RCW-vb_qek5dIfHIwAC7o,9805
@@ -558,7 +558,7 @@ claude_mpm/services/monitor/handlers/file.py,sha256=p3C4wffl0GIcN00b-KkrmZ8F-Amd
558
558
  claude_mpm/services/monitor/handlers/hooks.py,sha256=dlrmyFu8WChlvn6-sND9DLjSbm5nrMfNZrAgoWN-2No,17582
559
559
  claude_mpm/services/monitor/management/__init__.py,sha256=mxaEFRgvvgV85gUpXu_DsnHtywihdP14EisvISAVZuQ,525
560
560
  claude_mpm/services/monitor/management/health.py,sha256=Wm92Cli_4cWD6B89KX_CdpAvvevuEaGB8Ah59ILhFww,3772
561
- claude_mpm/services/monitor/management/lifecycle.py,sha256=Cahpc1-R09ihDYVWiMI9wnv-Qw20cNhHHcJyxZ9JcBo,10575
561
+ claude_mpm/services/monitor/management/lifecycle.py,sha256=-KuTXEkpoZQmqVvYE2q7EmwpezY4ZM5Yvp71cWYD93Q,17667
562
562
  claude_mpm/services/project/__init__.py,sha256=IUclN1L7ChHCNya7PJiVxu4nttxsrj3WRIpwyA1A_hw,512
563
563
  claude_mpm/services/project/analyzer.py,sha256=VHlLrP8-S5gr12w4Yzs7-6d7LWdJKISHPCFSG7SDiQU,38434
564
564
  claude_mpm/services/project/analyzer_refactored.py,sha256=USYEdPAhSoGPqZCpaT89Dw6ElFW_L1yXSURheQjAhLA,18243
@@ -639,9 +639,9 @@ claude_mpm/utils/subprocess_utils.py,sha256=zgiwLqh_17WxHpySvUPH65pb4bzIeUGOAYUJ
639
639
  claude_mpm/validation/__init__.py,sha256=YZhwE3mhit-lslvRLuwfX82xJ_k4haZeKmh4IWaVwtk,156
640
640
  claude_mpm/validation/agent_validator.py,sha256=3Lo6LK-Mw9IdnL_bd3zl_R6FkgSVDYKUUM7EeVVD3jc,20865
641
641
  claude_mpm/validation/frontmatter_validator.py,sha256=u8g4Eyd_9O6ugj7Un47oSGh3kqv4wMkuks2i_CtWRvM,7028
642
- claude_mpm-4.2.21.dist-info/licenses/LICENSE,sha256=lpaivOlPuBZW1ds05uQLJJswy8Rp_HMNieJEbFlqvLk,1072
643
- claude_mpm-4.2.21.dist-info/METADATA,sha256=79KK9Dg30eyjYMaOvrTCuyKmVtkZNKPR8gdrjJ5bp90,14110
644
- claude_mpm-4.2.21.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
645
- claude_mpm-4.2.21.dist-info/entry_points.txt,sha256=FDPZgz8JOvD-6iuXY2l9Zbo9zYVRuE4uz4Qr0vLeGOk,471
646
- claude_mpm-4.2.21.dist-info/top_level.txt,sha256=1nUg3FEaBySgm8t-s54jK5zoPnu3_eY6EP6IOlekyHA,11
647
- claude_mpm-4.2.21.dist-info/RECORD,,
642
+ claude_mpm-4.2.22.dist-info/licenses/LICENSE,sha256=lpaivOlPuBZW1ds05uQLJJswy8Rp_HMNieJEbFlqvLk,1072
643
+ claude_mpm-4.2.22.dist-info/METADATA,sha256=hPxeNHmoQ1VlgLWWOaox9vJ5hTTpODq2O67IhCUbt5g,14451
644
+ claude_mpm-4.2.22.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
645
+ claude_mpm-4.2.22.dist-info/entry_points.txt,sha256=FDPZgz8JOvD-6iuXY2l9Zbo9zYVRuE4uz4Qr0vLeGOk,471
646
+ claude_mpm-4.2.22.dist-info/top_level.txt,sha256=1nUg3FEaBySgm8t-s54jK5zoPnu3_eY6EP6IOlekyHA,11
647
+ claude_mpm-4.2.22.dist-info/RECORD,,