claude-mpm 5.4.91__py3-none-any.whl → 5.4.95__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 (100) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/cli/commands/monitor.py +2 -2
  3. claude_mpm/cli/startup_logging.py +2 -2
  4. claude_mpm/commands/mpm-session-resume.md +1 -1
  5. claude_mpm/core/unified_config.py +1 -1
  6. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.C33zOoyM.css +1 -0
  7. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.CW1J-YuA.css +1 -0
  8. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BS0ej2w8.js → 1WZnGYqX.js} +1 -1
  9. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{7ZAeO_Uj.js → 67pF3qNn.js} +1 -1
  10. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BCWDw8BF.js → 6RxdMKe4.js} +1 -1
  11. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BVFqgd56.js → 8cZrfX0h.js} +1 -1
  12. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CDNOxKrg.js → 9a6T2nm-.js} +1 -1
  13. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{MJf6AOIJ.js → B443AUzu.js} +1 -1
  14. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B8AwtY2H.js +1 -0
  15. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DNI1jw9S.js → BF15LAsF.js} +1 -1
  16. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DOeJfApz.js → BRcwIQNr.js} +1 -1
  17. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BfXd4Xj4.js → BV6nKitt.js} +1 -1
  18. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DWDi9IaK.js → BViJ8lZt.js} +1 -1
  19. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BnFPFynJ.js → BcQ-Q0FE.js} +1 -1
  20. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B_fnSNFx.js → Bpyvgze_.js} +1 -1
  21. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BzTRqg-z.js +1 -0
  22. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C0Fr8dve.js +1 -0
  23. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{EKp_wsKE.js → C3rbW_a-.js} +1 -1
  24. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C_l0vq62.js → C8WYN38h.js} +1 -1
  25. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CDi5wzaD.js → C9I8FlXH.js} +1 -1
  26. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BwpSELyW.js → CIQcWgO2.js} +1 -1
  27. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CQ94FMOU.js → CIctN7YN.js} +1 -1
  28. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzkNB1Vu.js → CKrS_JZW.js} +1 -1
  29. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D0Fj1OdD.js → CR6P9C4A.js} +1 -1
  30. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{NsEh4Ivo.js → CRRR9MD_.js} +1 -1
  31. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
  32. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DdIDcQsD.js → CSXtMOf0.js} +1 -1
  33. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C5Dg_JxJ.js → CT-sbxSk.js} +1 -1
  34. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CyrTH56Q.js → CWm6DJsp.js} +1 -1
  35. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C86quetY.js → CpqQ1Kzn.js} +1 -1
  36. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dqtg3hb8.js → D9iCMida.js} +1 -1
  37. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DJN4AVXS.js → D9ykgMoY.js} +1 -1
  38. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{14Ru8gxt.js → DL2Ldur1.js} +1 -1
  39. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DLeM8wSV.js → DPfltzjH.js} +1 -1
  40. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Vzk33B_K.js → DR8nis88.js} +2 -2
  41. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CvWciI1W.js → DUliQN2b.js} +1 -1
  42. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DOViuQX_.js → DXlhR01x.js} +1 -1
  43. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{s04HIjWg.js → D_lyTybS.js} +1 -1
  44. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{WiqB4NUY.js → DngoTTgh.js} +1 -1
  45. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CT5eAo1x.js → DqkmHtDC.js} +1 -1
  46. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DJuK4-OP.js → DsDh8EYs.js} +1 -1
  47. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C7i47te_.js → DypDmXgd.js} +1 -1
  48. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BfMC7wDI.js → IPYC-LnN.js} +1 -1
  49. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{URSAF6IJ.js → JTLiF7dt.js} +1 -1
  50. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B06ALsCS.js → JpevfAFt.js} +1 -1
  51. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DY1XQ8fi.js → R8CEIRAd.js} +1 -1
  52. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D1ARDjz0.js → Zxy7qc-l.js} +1 -1
  53. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
  54. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{vJiSSdpk.js → qtd3IeO4.js} +1 -1
  55. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BXs4CVzO.js → ulBFON_C.js} +1 -1
  56. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DTgfNBV9.js → wQVh1CoA.js} +1 -1
  57. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/{app.CnXU_fEX.js → app.Dr7t0z2J.js} +2 -2
  58. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
  59. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.BuxSUm_s.js → 0.RgBboRvH.js} +1 -1
  60. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{1.znyTz9u3.js → 1.DG-KkbDf.js} +1 -1
  61. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.D_jnf-x6.js +1 -0
  62. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -1
  63. claude_mpm/dashboard/static/svelte-build/index.html +8 -8
  64. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  65. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  66. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
  67. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  68. claude_mpm/hooks/claude_hooks/event_handlers.py +46 -10
  69. claude_mpm/hooks/claude_hooks/hook_handler.py +3 -1
  70. claude_mpm/hooks/claude_hooks/response_tracking.py +2 -16
  71. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
  72. claude_mpm/hooks/claude_hooks/services/connection_manager.py +20 -0
  73. claude_mpm/services/agents/cache_git_manager.py +1 -1
  74. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +3 -0
  75. claude_mpm/services/diagnostics/checks/agent_sources_check.py +30 -0
  76. claude_mpm/services/diagnostics/checks/configuration_check.py +24 -0
  77. claude_mpm/services/diagnostics/checks/installation_check.py +22 -0
  78. claude_mpm/services/diagnostics/checks/mcp_services_check.py +23 -0
  79. claude_mpm/services/diagnostics/doctor_reporter.py +31 -1
  80. claude_mpm/services/diagnostics/models.py +14 -1
  81. claude_mpm/services/monitor/daemon_manager.py +15 -4
  82. claude_mpm/services/monitor/management/lifecycle.py +8 -2
  83. claude_mpm/services/monitor/server.py +106 -16
  84. claude_mpm/services/skills/selective_skill_deployer.py +114 -16
  85. {claude_mpm-5.4.91.dist-info → claude_mpm-5.4.95.dist-info}/METADATA +1 -1
  86. {claude_mpm-5.4.91.dist-info → claude_mpm-5.4.95.dist-info}/RECORD +91 -89
  87. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.B7S5qgOx.css +0 -1
  88. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.D3t4z6uz.css +0 -1
  89. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +0 -1
  90. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/AeivYILh.js +0 -1
  91. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cn4nXAfg.js +0 -1
  92. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D_vpdI7l.js +0 -325
  93. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DnL7ky1O.js +0 -1
  94. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.CUaAfoQJ.js +0 -1
  95. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.CLVHDDxl.js +0 -1
  96. {claude_mpm-5.4.91.dist-info → claude_mpm-5.4.95.dist-info}/WHEEL +0 -0
  97. {claude_mpm-5.4.91.dist-info → claude_mpm-5.4.95.dist-info}/entry_points.txt +0 -0
  98. {claude_mpm-5.4.91.dist-info → claude_mpm-5.4.95.dist-info}/licenses/LICENSE +0 -0
  99. {claude_mpm-5.4.91.dist-info → claude_mpm-5.4.95.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  100. {claude_mpm-5.4.91.dist-info → claude_mpm-5.4.95.dist-info}/top_level.txt +0 -0
@@ -115,7 +115,7 @@ class EventHandlers:
115
115
  f"Stored prompt for comprehensive tracking: session {session_id[:8]}...",
116
116
  file=sys.stderr,
117
117
  )
118
- except Exception:
118
+ except Exception: # nosec B110
119
119
  # Response tracking is optional - silently continue if it fails
120
120
  pass
121
121
 
@@ -297,7 +297,7 @@ class EventHandlers:
297
297
  mhm = getattr(self.hook_handler, "memory_hook_manager", None)
298
298
  if mhm and hasattr(mhm, "trigger_pre_delegation_hook"):
299
299
  mhm.trigger_pre_delegation_hook(agent_type, tool_input, session_id)
300
- except Exception:
300
+ except Exception: # nosec B110
301
301
  # Memory hooks are optional
302
302
  pass
303
303
 
@@ -390,7 +390,7 @@ class EventHandlers:
390
390
  os.chdir(working_dir)
391
391
 
392
392
  # Run git command to get current branch
393
- result = subprocess.run(
393
+ result = subprocess.run( # nosec B603 B607
394
394
  ["git", "branch", "--show-current"],
395
395
  capture_output=True,
396
396
  text=True,
@@ -500,7 +500,7 @@ class EventHandlers:
500
500
  mhm = getattr(self.hook_handler, "memory_hook_manager", None)
501
501
  if mhm and hasattr(mhm, "trigger_post_delegation_hook"):
502
502
  mhm.trigger_post_delegation_hook(agent_type, event, session_id)
503
- except Exception:
503
+ except Exception: # nosec B110
504
504
  # Memory hooks are optional
505
505
  pass
506
506
 
@@ -514,7 +514,7 @@ class EventHandlers:
514
514
  rtm.track_agent_response(
515
515
  session_id, agent_type, event, delegation_requests
516
516
  )
517
- except Exception:
517
+ except Exception: # nosec B110
518
518
  # Response tracking is optional
519
519
  pass
520
520
 
@@ -576,13 +576,49 @@ class EventHandlers:
576
576
  if DEBUG:
577
577
  self._log_stop_event_debug(event, session_id, metadata)
578
578
 
579
+ # Auto-pause integration (independent of response tracking)
580
+ # WHY HERE: Auto-pause must work even when response_tracking is disabled
581
+ # Extract usage data directly from event and trigger auto-pause if thresholds crossed
582
+ if "usage" in event:
583
+ auto_pause = getattr(self.hook_handler, "auto_pause_handler", None)
584
+ if auto_pause:
585
+ try:
586
+ usage_data = event["usage"]
587
+ metadata["usage"] = {
588
+ "input_tokens": usage_data.get("input_tokens", 0),
589
+ "output_tokens": usage_data.get("output_tokens", 0),
590
+ "cache_creation_input_tokens": usage_data.get(
591
+ "cache_creation_input_tokens", 0
592
+ ),
593
+ "cache_read_input_tokens": usage_data.get(
594
+ "cache_read_input_tokens", 0
595
+ ),
596
+ }
597
+
598
+ threshold_crossed = auto_pause.on_usage_update(metadata["usage"])
599
+ if threshold_crossed:
600
+ warning = auto_pause.emit_threshold_warning(threshold_crossed)
601
+ print(f"\n⚠️ {warning}", file=sys.stderr)
602
+
603
+ if DEBUG:
604
+ print(
605
+ f" - Auto-pause threshold crossed: {threshold_crossed}",
606
+ file=sys.stderr,
607
+ )
608
+ except Exception as e:
609
+ if DEBUG:
610
+ print(
611
+ f"Auto-pause error in handle_stop_fast: {e}",
612
+ file=sys.stderr,
613
+ )
614
+
579
615
  # Track response if enabled
580
616
  try:
581
617
  rtm = getattr(self.hook_handler, "response_tracking_manager", None)
582
618
  if rtm and hasattr(rtm, "track_stop_response"):
583
619
  pending_prompts = getattr(self.hook_handler, "pending_prompts", {})
584
620
  rtm.track_stop_response(event, session_id, metadata, pending_prompts)
585
- except Exception:
621
+ except Exception: # nosec B110
586
622
  # Response tracking is optional
587
623
  pass
588
624
 
@@ -624,7 +660,7 @@ class EventHandlers:
624
660
  f" - response_tracker exists: {tracker_exists}",
625
661
  file=sys.stderr,
626
662
  )
627
- except Exception:
663
+ except Exception: # nosec B110
628
664
  # If debug logging fails, just skip it
629
665
  pass
630
666
 
@@ -690,7 +726,7 @@ class EventHandlers:
690
726
  try:
691
727
  # Get the original request data (with fuzzy matching fallback)
692
728
  delegation_requests = getattr(self.hook_handler, "delegation_requests", {})
693
- request_info = delegation_requests.get(session_id)
729
+ request_info = delegation_requests.get(session_id) # nosec B113
694
730
 
695
731
  # If exact match fails, try partial matching
696
732
  if not request_info and session_id:
@@ -715,7 +751,7 @@ class EventHandlers:
715
751
  f" - ✅ Fuzzy match found: {stored_sid[:16]}...",
716
752
  file=sys.stderr,
717
753
  )
718
- request_info = delegation_requests.get(stored_sid)
754
+ request_info = delegation_requests.get(stored_sid) # nosec B113
719
755
  # Update the key to use the current session_id for consistency
720
756
  if request_info:
721
757
  delegation_requests[session_id] = request_info
@@ -822,7 +858,7 @@ class EventHandlers:
822
858
  if rtm and hasattr(rtm, "track_assistant_response"):
823
859
  pending_prompts = getattr(self.hook_handler, "pending_prompts", {})
824
860
  rtm.track_assistant_response(event, pending_prompts)
825
- except Exception:
861
+ except Exception: # nosec B110
826
862
  # Response tracking is optional
827
863
  pass
828
864
 
@@ -550,13 +550,15 @@ class ClaudeHookHandler:
550
550
  # Build hook execution data
551
551
  hook_data = {
552
552
  "hook_name": hook_type,
553
- "hook_type": hook_type,
553
+ "hook_type": hook_type, # Actual hook type (PreToolUse, UserPromptSubmit, etc.)
554
+ "hook_event_type": hook_type, # Additional field for clarity
554
555
  "session_id": session_id,
555
556
  "working_directory": working_dir,
556
557
  "success": success,
557
558
  "duration_ms": duration_ms,
558
559
  "result_summary": summary,
559
560
  "timestamp": datetime.now(timezone.utc).isoformat(),
561
+ "source": "claude_hook_handler", # Explicit source identification
560
562
  }
561
563
 
562
564
  # Add error information if present
@@ -306,6 +306,8 @@ class ResponseTrackingManager:
306
306
  )
307
307
 
308
308
  # Capture Claude API usage data if available
309
+ # NOTE: Usage data is already captured in metadata by handle_stop_fast()
310
+ # which also handles auto-pause triggering (even when response tracking disabled)
309
311
  if "usage" in event:
310
312
  usage_data = event["usage"]
311
313
  metadata["usage"] = {
@@ -327,22 +329,6 @@ class ResponseTrackingManager:
327
329
  file=sys.stderr,
328
330
  )
329
331
 
330
- # Auto-pause integration
331
- auto_pause = getattr(self, "auto_pause_handler", None)
332
- if auto_pause and metadata.get("usage"):
333
- try:
334
- threshold_crossed = auto_pause.on_usage_update(
335
- metadata["usage"]
336
- )
337
- if threshold_crossed:
338
- warning = auto_pause.emit_threshold_warning(
339
- threshold_crossed
340
- )
341
- print(f"\n⚠️ {warning}", file=sys.stderr)
342
- except Exception as e:
343
- if DEBUG:
344
- print(f"Auto-pause error: {e}", file=sys.stderr)
345
-
346
332
  # Track the main Claude response
347
333
  file_path = self.response_tracker.track_response(
348
334
  agent_name="claude_main",
@@ -134,6 +134,26 @@ class ConnectionManagerService:
134
134
  # Otherwise use "hook" as the type
135
135
  if event == "hook_execution":
136
136
  hook_type = data.get("hook_type", "unknown")
137
+
138
+ # BUGFIX: Validate hook_type is meaningful (not generic/invalid values)
139
+ # Problem: Dashboard shows "hook hook" instead of "PreToolUse", "UserPromptSubmit", etc.
140
+ # Root cause: hook_type defaults to "hook" or "unknown", providing no useful information
141
+ # Solution: Fallback to hook_name, then to descriptive "hook_execution_untyped"
142
+ if hook_type in ("hook", "unknown", "", None):
143
+ # Try fallback to hook_name field (set by _emit_hook_execution_event)
144
+ hook_type = data.get("hook_name", "unknown_hook")
145
+
146
+ # Final fallback if still generic - use descriptive name
147
+ if hook_type in ("hook", "unknown", "", None):
148
+ hook_type = "hook_execution_untyped"
149
+
150
+ # Debug log when we detect invalid hook_type for troubleshooting
151
+ if DEBUG:
152
+ print(
153
+ f"⚠️ Invalid hook_type detected, using fallback: {hook_type}",
154
+ file=sys.stderr,
155
+ )
156
+
137
157
  event_type = hook_type
138
158
  else:
139
159
  event_type = "hook"
@@ -88,7 +88,7 @@ class CacheGitManager:
88
88
  if self.repo_path:
89
89
  logger.debug(f"Initialized CacheGitManager for repo: {self.repo_path}")
90
90
  else:
91
- logger.warning(f"Cache path is not a git repository: {cache_path}")
91
+ logger.debug(f"Cache path is not a git repository: {cache_path}")
92
92
 
93
93
  def _find_git_root(self) -> Optional[Path]:
94
94
  """
@@ -491,6 +491,9 @@ class RemoteAgentDiscoveryService:
491
491
  "SKILL.md",
492
492
  "SKILLS.md",
493
493
  "skill-template.md",
494
+ # Legacy agents superseded by newer versions
495
+ # TODO: Remove after bobmatnyc/claude-mpm-agents#XXX is merged
496
+ "memory-manager.md", # Superseded by memory-manager-agent.md (v1.2.0)
494
497
  }
495
498
  md_files = [f for f in md_files if f.name not in excluded_files]
496
499
 
@@ -69,6 +69,13 @@ class AgentSourcesCheck(BaseDiagnosticCheck):
69
69
  fix_command="claude-mpm agent-source add https://github.com/bobmatnyc/claude-mpm-agents",
70
70
  fix_description="Add default system repository",
71
71
  sub_results=sub_results if self.verbose else [],
72
+ explanation=(
73
+ "Agent sources define where Claude MPM discovers specialized agents. "
74
+ "Without configured sources, no agents can be deployed or delegated to. "
75
+ "This is a critical component for multi-agent workflows."
76
+ ),
77
+ severity="critical",
78
+ doc_link="https://github.com/bobmatnyc/claude-mpm/blob/main/docs/guides/agent-sources.md",
72
79
  )
73
80
 
74
81
  # Check 2: Configuration is valid YAML
@@ -141,16 +148,30 @@ class AgentSourcesCheck(BaseDiagnosticCheck):
141
148
  r for r in sub_results if r.status == ValidationSeverity.WARNING
142
149
  ]
143
150
 
151
+ # Determine status and enhanced troubleshooting info (issue #125)
144
152
  if error_results:
145
153
  status = ValidationSeverity.ERROR
146
154
  message = f"Agent sources have {len(error_results)} critical issue(s)"
147
155
  fix_command = None
148
156
  fix_description = None
157
+ severity = "critical"
158
+ explanation = (
159
+ "Agent sources are the foundation of Claude MPM's delegation system. "
160
+ "Critical errors prevent agent discovery and deployment, blocking "
161
+ "multi-agent workflows entirely."
162
+ )
163
+ doc_link = "https://github.com/bobmatnyc/claude-mpm/blob/main/docs/guides/agent-sources.md"
149
164
  elif warning_results:
150
165
  status = ValidationSeverity.WARNING
151
166
  message = f"Agent sources have {len(warning_results)} minor issue(s)"
152
167
  fix_command = "claude-mpm agent-source update"
153
168
  fix_description = "Update all sources to refresh cache"
169
+ severity = "medium"
170
+ explanation = (
171
+ "Some agent sources have issues but the system can still function. "
172
+ "You may have limited agent availability or outdated cache."
173
+ )
174
+ doc_link = "https://github.com/bobmatnyc/claude-mpm/blob/main/docs/guides/agent-sources.md"
154
175
  else:
155
176
  status = OperationResult.SUCCESS
156
177
  enabled_count = details["enabled_sources"]
@@ -158,6 +179,12 @@ class AgentSourcesCheck(BaseDiagnosticCheck):
158
179
  message = f"All checks passed ({enabled_count} source(s), {agents_count} agent(s))"
159
180
  fix_command = None
160
181
  fix_description = None
182
+ severity = "info"
183
+ explanation = (
184
+ "Agent sources are properly configured and agents are discoverable. "
185
+ "You can delegate tasks to specialized agents."
186
+ )
187
+ doc_link = ""
161
188
 
162
189
  return DiagnosticResult(
163
190
  category=self.category,
@@ -167,6 +194,9 @@ class AgentSourcesCheck(BaseDiagnosticCheck):
167
194
  fix_command=fix_command,
168
195
  fix_description=fix_description,
169
196
  sub_results=sub_results if self.verbose else [],
197
+ explanation=explanation,
198
+ severity=severity,
199
+ doc_link=doc_link,
170
200
  )
171
201
 
172
202
  except Exception as e:
@@ -63,12 +63,36 @@ class ConfigurationCheck(BaseDiagnosticCheck):
63
63
  status = OperationResult.SUCCESS
64
64
  message = "Configuration is valid"
65
65
 
66
+ # Add enhanced troubleshooting info (issue #125)
67
+ severity = "medium"
68
+ explanation = ""
69
+ doc_link = ""
70
+
71
+ if status == ValidationSeverity.ERROR:
72
+ severity = "high"
73
+ explanation = (
74
+ "Configuration files control how Claude MPM behaves. Critical errors "
75
+ "in configuration may prevent features from working correctly or cause "
76
+ "unexpected behavior."
77
+ )
78
+ doc_link = "https://github.com/bobmatnyc/claude-mpm/blob/main/docs/configuration.md"
79
+ elif status == ValidationSeverity.WARNING:
80
+ severity = "low"
81
+ explanation = (
82
+ "Configuration has minor issues that may affect optional features. "
83
+ "Core functionality should still work."
84
+ )
85
+ doc_link = "https://github.com/bobmatnyc/claude-mpm/blob/main/docs/configuration.md"
86
+
66
87
  return DiagnosticResult(
67
88
  category=self.category,
68
89
  status=status,
69
90
  message=message,
70
91
  details=details,
71
92
  sub_results=sub_results if self.verbose else [],
93
+ explanation=explanation,
94
+ severity=severity,
95
+ doc_link=doc_link,
72
96
  )
73
97
 
74
98
  except Exception as e:
@@ -65,12 +65,34 @@ class InstallationCheck(BaseDiagnosticCheck):
65
65
  status = OperationResult.SUCCESS
66
66
  message = "Installation is healthy"
67
67
 
68
+ # Determine severity and explanation based on status (issue #125)
69
+ severity = "medium"
70
+ explanation = ""
71
+ doc_link = ""
72
+
73
+ if status == ValidationSeverity.ERROR:
74
+ severity = "high"
75
+ explanation = (
76
+ "Claude MPM installation verification failed. Critical components are missing "
77
+ "or misconfigured, which will prevent the system from functioning properly."
78
+ )
79
+ doc_link = "https://github.com/bobmatnyc/claude-mpm/blob/main/docs/installation.md"
80
+ elif status == ValidationSeverity.WARNING:
81
+ severity = "medium"
82
+ explanation = (
83
+ "Installation is functional but has minor issues. These may affect "
84
+ "performance or features but won't prevent basic operation."
85
+ )
86
+
68
87
  return DiagnosticResult(
69
88
  category=self.category,
70
89
  status=status,
71
90
  message=message,
72
91
  details=details,
73
92
  sub_results=sub_results if self.verbose else [],
93
+ explanation=explanation,
94
+ severity=severity,
95
+ doc_link=doc_link,
74
96
  )
75
97
 
76
98
  except Exception as e:
@@ -216,12 +216,35 @@ class MCPServicesCheck(BaseDiagnosticCheck):
216
216
  status = ValidationSeverity.WARNING
217
217
  message = f"All {total_services} MCP services installed but connections not tested"
218
218
 
219
+ # Enhanced troubleshooting info (issue #125)
220
+ severity = "medium"
221
+ explanation = ""
222
+ doc_link = ""
223
+
224
+ if status == ValidationSeverity.ERROR:
225
+ severity = "high"
226
+ explanation = (
227
+ "MCP services provide enhanced capabilities like vector search, browser automation, "
228
+ "and ticket management. Critical errors prevent these services from functioning."
229
+ )
230
+ doc_link = "https://github.com/bobmatnyc/claude-mpm/blob/main/docs/mcp-services.md"
231
+ elif status == ValidationSeverity.WARNING:
232
+ severity = "low"
233
+ explanation = (
234
+ "MCP services are optional but provide powerful features. "
235
+ "Some services may not be installed or configured properly."
236
+ )
237
+ doc_link = "https://github.com/bobmatnyc/claude-mpm/blob/main/docs/mcp-services.md"
238
+
219
239
  return DiagnosticResult(
220
240
  category=self.category,
221
241
  status=status,
222
242
  message=message,
223
243
  details=details,
224
244
  sub_results=sub_results if self.verbose else [],
245
+ explanation=explanation,
246
+ severity=severity,
247
+ doc_link=doc_link,
225
248
  )
226
249
 
227
250
  except Exception as e:
@@ -28,6 +28,15 @@ class DoctorReporter:
28
28
  OperationResult.SKIPPED: "⏭️ ",
29
29
  }
30
30
 
31
+ # Severity level emojis (issue #125)
32
+ SEVERITY_SYMBOLS = {
33
+ "critical": "🔴",
34
+ "high": "🟠",
35
+ "medium": "🟡",
36
+ "low": "🟢",
37
+ "info": "🔵",
38
+ }
39
+
31
40
  # ANSI color codes
32
41
  COLORS = {
33
42
  "reset": "\033[0m",
@@ -93,8 +102,15 @@ class DoctorReporter:
93
102
  symbol = self.STATUS_SYMBOLS.get(result.status, "?")
94
103
  color = self._get_status_color(result.status)
95
104
 
105
+ # Add severity indicator if present (issue #125)
106
+ severity_prefix = ""
107
+ if result.severity and result.severity != "medium":
108
+ severity_symbol = self.SEVERITY_SYMBOLS.get(result.severity, "")
109
+ if severity_symbol:
110
+ severity_prefix = f"{severity_symbol} {result.severity.upper()}: "
111
+
96
112
  # Main result line
97
- line = f"{indent_str}{symbol} {result.category}: "
113
+ line = f"{indent_str}{severity_prefix}{symbol} {result.category}: "
98
114
 
99
115
  if result.status == OperationResult.SUCCESS:
100
116
  line += self._color("OK", color)
@@ -111,6 +127,15 @@ class DoctorReporter:
111
127
  message_indent = " " + indent_str
112
128
  print(f"{message_indent}{result.message}")
113
129
 
130
+ # Explanation (issue #125)
131
+ if result.explanation:
132
+ # Format multi-line explanations with proper indentation
133
+ explanation_lines = result.explanation.split("\n")
134
+ for explanation_line in explanation_lines:
135
+ print(
136
+ f"{message_indent}{self._color(explanation_line.strip(), 'gray')}"
137
+ )
138
+
114
139
  # Details (in verbose mode)
115
140
  if self.verbose and result.details:
116
141
  for key, value in result.details.items():
@@ -126,6 +151,11 @@ class DoctorReporter:
126
151
  if result.fix_description:
127
152
  print(f"{fix_indent} {self._color(result.fix_description, 'gray')}")
128
153
 
154
+ # Documentation link (issue #125)
155
+ if result.doc_link:
156
+ doc_indent = " " + indent_str
157
+ print(f"{doc_indent}{self._color('📖 Docs:', 'blue')} {result.doc_link}")
158
+
129
159
  # Sub-results (in verbose mode)
130
160
  if self.verbose and result.sub_results:
131
161
  for sub_result in result.sub_results:
@@ -51,9 +51,14 @@ class DiagnosticResult:
51
51
  fix_description: Optional[str] = None
52
52
  sub_results: List["DiagnosticResult"] = field(default_factory=list)
53
53
 
54
+ # Enhanced troubleshooting fields (issue #125)
55
+ explanation: str = "" # What this check means and why it matters
56
+ severity: str = "medium" # critical, high, medium, low, info
57
+ doc_link: str = "" # Link to relevant documentation
58
+
54
59
  def to_dict(self) -> Dict[str, Any]:
55
60
  """Convert to dictionary for JSON serialization."""
56
- return {
61
+ result = {
57
62
  "category": self.category,
58
63
  "status": self.status.value,
59
64
  "message": self.message,
@@ -62,6 +67,14 @@ class DiagnosticResult:
62
67
  "fix_description": self.fix_description,
63
68
  "sub_results": [r.to_dict() for r in self.sub_results],
64
69
  }
70
+ # Include enhanced fields if present
71
+ if self.explanation:
72
+ result["explanation"] = self.explanation
73
+ if self.severity != "medium":
74
+ result["severity"] = self.severity
75
+ if self.doc_link:
76
+ result["doc_link"] = self.doc_link
77
+ return result
65
78
 
66
79
  @property
67
80
  def has_issues(self) -> bool:
@@ -95,10 +95,10 @@ class DaemonManager:
95
95
  def _get_default_log_file(self) -> Path:
96
96
  """Get default log file path with port number to support multiple daemons."""
97
97
  project_root = Path.cwd()
98
- claude_mpm_dir = project_root / ".claude-mpm"
99
- claude_mpm_dir.mkdir(exist_ok=True)
98
+ logs_dir = project_root / ".claude-mpm" / "logs"
99
+ logs_dir.mkdir(parents=True, exist_ok=True)
100
100
  # Include port in filename to support multiple daemon instances
101
- return claude_mpm_dir / f"monitor-daemon-{self.port}.log"
101
+ return logs_dir / f"monitor-daemon-{self.port}.log"
102
102
 
103
103
  def cleanup_port_conflicts(self, max_retries: int = 3) -> bool:
104
104
  """Clean up any processes using the daemon port.
@@ -649,14 +649,24 @@ class DaemonManager:
649
649
 
650
650
  # Wait for the subprocess to write its PID file and bind to port
651
651
  # The subprocess will write the PID file after it starts successfully
652
- max_wait = 10 # seconds
652
+ # Allow configuration via environment variable (default 30s to account for agent/skill sync)
653
+ max_wait = int(os.environ.get("CLAUDE_MPM_MONITOR_TIMEOUT", "30"))
653
654
  start_time = time.time()
654
655
  pid_file_found = False
655
656
  port_bound = False
657
+ last_progress_log = 0.0
656
658
 
657
659
  self.logger.debug(f"Waiting up to {max_wait}s for daemon to start...")
658
660
 
659
661
  while time.time() - start_time < max_wait:
662
+ # Log progress every 5 seconds to show we're waiting
663
+ elapsed = time.time() - start_time
664
+ if elapsed - last_progress_log >= 5.0:
665
+ self.logger.info(
666
+ f"Waiting for monitor daemon... ({int(elapsed)}s elapsed, syncing agents/skills)"
667
+ )
668
+ last_progress_log = elapsed
669
+
660
670
  # Check if process is still running
661
671
  returncode = process.poll()
662
672
  if returncode is not None:
@@ -976,6 +986,7 @@ class DaemonManager:
976
986
  os.dup2(null_in.fileno(), sys.stdin.fileno())
977
987
 
978
988
  # Redirect stdout and stderr to log file
989
+ # Ensure logs directory exists
979
990
  self.log_file.parent.mkdir(parents=True, exist_ok=True)
980
991
  with self.log_file.open("a") as log_out:
981
992
  os.dup2(log_out.fileno(), sys.stdout.fileno())
@@ -128,12 +128,16 @@ class DaemonLifecycle:
128
128
  # Redirect stdout and stderr
129
129
  if self.log_file:
130
130
  # Redirect to log file
131
+ # Ensure logs directory exists
132
+ self.log_file.parent.mkdir(parents=True, exist_ok=True)
131
133
  with self.log_file.open("a") as log_out:
132
134
  os.dup2(log_out.fileno(), sys.stdout.fileno())
133
135
  os.dup2(log_out.fileno(), sys.stderr.fileno())
134
136
  else:
135
137
  # Default to a daemon log file instead of /dev/null for errors
136
- default_log = Path.home() / ".claude-mpm" / "monitor-daemon.log"
138
+ default_log = (
139
+ Path.home() / ".claude-mpm" / "logs" / "monitor-daemon.log"
140
+ )
137
141
  default_log.parent.mkdir(parents=True, exist_ok=True)
138
142
  with default_log.open("a") as log_out:
139
143
  os.dup2(log_out.fileno(), sys.stdout.fileno())
@@ -475,7 +479,9 @@ class DaemonLifecycle:
475
479
  try:
476
480
  # If no log file specified, create a default one
477
481
  if not self.log_file:
478
- default_log = Path.home() / ".claude-mpm" / "monitor-daemon.log"
482
+ default_log = (
483
+ Path.home() / ".claude-mpm" / "logs" / "monitor-daemon.log"
484
+ )
479
485
  default_log.parent.mkdir(parents=True, exist_ok=True)
480
486
  self.log_file = default_log
481
487