claude-mpm 4.2.44__py3-none-any.whl → 4.3.0__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 (153) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_PM.md +77 -405
  3. claude_mpm/agents/{INSTRUCTIONS.md → INSTRUCTIONS_OLD_DEPRECATED.md} +75 -1
  4. claude_mpm/agents/OUTPUT_STYLE.md +0 -39
  5. claude_mpm/agents/PM_INSTRUCTIONS.md +122 -0
  6. claude_mpm/agents/WORKFLOW.md +74 -323
  7. claude_mpm/agents/frontmatter_validator.py +20 -12
  8. claude_mpm/agents/templates/nextjs_engineer.json +277 -0
  9. claude_mpm/agents/templates/prompt-engineer.json +294 -0
  10. claude_mpm/agents/templates/python_engineer.json +289 -0
  11. claude_mpm/agents/templates/react_engineer.json +11 -3
  12. claude_mpm/agents/templates/security.json +50 -9
  13. claude_mpm/cli/commands/agents.py +2 -2
  14. claude_mpm/cli/commands/uninstall.py +1 -3
  15. claude_mpm/cli/interactive/agent_wizard.py +3 -3
  16. claude_mpm/cli/parsers/agent_manager_parser.py +3 -3
  17. claude_mpm/cli/parsers/agents_parser.py +1 -1
  18. claude_mpm/constants.py +1 -1
  19. claude_mpm/core/error_handler.py +2 -4
  20. claude_mpm/core/file_utils.py +4 -12
  21. claude_mpm/core/framework_loader.py +72 -24
  22. claude_mpm/core/log_manager.py +60 -5
  23. claude_mpm/core/logger.py +1 -1
  24. claude_mpm/core/logging_utils.py +36 -18
  25. claude_mpm/core/unified_agent_registry.py +18 -4
  26. claude_mpm/dashboard/react/components/DataInspector/DataInspector.module.css +188 -0
  27. claude_mpm/dashboard/react/components/EventViewer/EventViewer.module.css +156 -0
  28. claude_mpm/dashboard/react/components/shared/ConnectionStatus.module.css +38 -0
  29. claude_mpm/dashboard/react/components/shared/FilterBar.module.css +92 -0
  30. claude_mpm/dashboard/static/archive/activity_dashboard_fixed.html +248 -0
  31. claude_mpm/dashboard/static/archive/activity_dashboard_test.html +61 -0
  32. claude_mpm/dashboard/static/archive/test_activity_connection.html +179 -0
  33. claude_mpm/dashboard/static/archive/test_claude_tree_tab.html +68 -0
  34. claude_mpm/dashboard/static/archive/test_dashboard.html +409 -0
  35. claude_mpm/dashboard/static/archive/test_dashboard_fixed.html +519 -0
  36. claude_mpm/dashboard/static/archive/test_dashboard_verification.html +181 -0
  37. claude_mpm/dashboard/static/archive/test_file_data.html +315 -0
  38. claude_mpm/dashboard/static/archive/test_file_tree_empty_state.html +243 -0
  39. claude_mpm/dashboard/static/archive/test_file_tree_fix.html +234 -0
  40. claude_mpm/dashboard/static/archive/test_file_tree_rename.html +117 -0
  41. claude_mpm/dashboard/static/archive/test_file_tree_tab.html +115 -0
  42. claude_mpm/dashboard/static/archive/test_file_viewer.html +224 -0
  43. claude_mpm/dashboard/static/archive/test_final_activity.html +220 -0
  44. claude_mpm/dashboard/static/archive/test_tab_fix.html +139 -0
  45. claude_mpm/dashboard/static/built/assets/events.DjpNxWNo.css +1 -0
  46. claude_mpm/dashboard/static/built/components/activity-tree.js +1 -1
  47. claude_mpm/dashboard/static/built/components/agent-hierarchy.js +777 -0
  48. claude_mpm/dashboard/static/built/components/agent-inference.js +1 -1
  49. claude_mpm/dashboard/static/built/components/build-tracker.js +333 -0
  50. claude_mpm/dashboard/static/built/components/code-simple.js +857 -0
  51. claude_mpm/dashboard/static/built/components/code-tree/tree-breadcrumb.js +353 -0
  52. claude_mpm/dashboard/static/built/components/code-tree/tree-constants.js +235 -0
  53. claude_mpm/dashboard/static/built/components/code-tree/tree-search.js +409 -0
  54. claude_mpm/dashboard/static/built/components/code-tree/tree-utils.js +435 -0
  55. claude_mpm/dashboard/static/built/components/code-viewer.js +2 -1076
  56. claude_mpm/dashboard/static/built/components/connection-debug.js +654 -0
  57. claude_mpm/dashboard/static/built/components/diff-viewer.js +891 -0
  58. claude_mpm/dashboard/static/built/components/event-processor.js +1 -1
  59. claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
  60. claude_mpm/dashboard/static/built/components/export-manager.js +1 -1
  61. claude_mpm/dashboard/static/built/components/file-change-tracker.js +443 -0
  62. claude_mpm/dashboard/static/built/components/file-change-viewer.js +690 -0
  63. claude_mpm/dashboard/static/built/components/file-tool-tracker.js +1 -1
  64. claude_mpm/dashboard/static/built/components/module-viewer.js +1 -1
  65. claude_mpm/dashboard/static/built/components/nav-bar.js +145 -0
  66. claude_mpm/dashboard/static/built/components/page-structure.js +429 -0
  67. claude_mpm/dashboard/static/built/components/session-manager.js +1 -1
  68. claude_mpm/dashboard/static/built/components/ui-state-manager.js +2 -465
  69. claude_mpm/dashboard/static/built/components/working-directory.js +1 -1
  70. claude_mpm/dashboard/static/built/connection-manager.js +536 -0
  71. claude_mpm/dashboard/static/built/dashboard.js +1 -1
  72. claude_mpm/dashboard/static/built/extension-error-handler.js +164 -0
  73. claude_mpm/dashboard/static/built/react/events.js +30 -0
  74. claude_mpm/dashboard/static/built/shared/dom-helpers.js +396 -0
  75. claude_mpm/dashboard/static/built/shared/event-bus.js +330 -0
  76. claude_mpm/dashboard/static/built/shared/event-filter-service.js +540 -0
  77. claude_mpm/dashboard/static/built/shared/logger.js +385 -0
  78. claude_mpm/dashboard/static/built/shared/page-structure.js +251 -0
  79. claude_mpm/dashboard/static/built/shared/tooltip-service.js +253 -0
  80. claude_mpm/dashboard/static/built/socket-client.js +1 -1
  81. claude_mpm/dashboard/static/built/tab-isolation-fix.js +185 -0
  82. claude_mpm/dashboard/static/css/dashboard.css +28 -5
  83. claude_mpm/dashboard/static/dist/assets/events.DjpNxWNo.css +1 -0
  84. claude_mpm/dashboard/static/dist/components/activity-tree.js +1 -1
  85. claude_mpm/dashboard/static/dist/components/agent-inference.js +1 -1
  86. claude_mpm/dashboard/static/dist/components/code-viewer.js +2 -0
  87. claude_mpm/dashboard/static/dist/components/event-processor.js +1 -1
  88. claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
  89. claude_mpm/dashboard/static/dist/components/export-manager.js +1 -1
  90. claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +1 -1
  91. claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
  92. claude_mpm/dashboard/static/dist/components/session-manager.js +1 -1
  93. claude_mpm/dashboard/static/dist/components/working-directory.js +1 -1
  94. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  95. claude_mpm/dashboard/static/dist/react/events.js +30 -0
  96. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  97. claude_mpm/dashboard/static/events.html +607 -0
  98. claude_mpm/dashboard/static/index.html +713 -0
  99. claude_mpm/dashboard/static/js/components/activity-tree.js +3 -17
  100. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +4 -1
  101. claude_mpm/dashboard/static/js/components/agent-inference.js +3 -0
  102. claude_mpm/dashboard/static/js/components/build-tracker.js +8 -0
  103. claude_mpm/dashboard/static/js/components/code-viewer.js +306 -66
  104. claude_mpm/dashboard/static/js/components/event-processor.js +3 -0
  105. claude_mpm/dashboard/static/js/components/event-viewer.js +39 -2
  106. claude_mpm/dashboard/static/js/components/export-manager.js +3 -0
  107. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +30 -10
  108. claude_mpm/dashboard/static/js/components/socket-manager.js +4 -0
  109. claude_mpm/dashboard/static/js/components/ui-state-manager.js +285 -85
  110. claude_mpm/dashboard/static/js/components/working-directory.js +3 -0
  111. claude_mpm/dashboard/static/js/dashboard.js +61 -33
  112. claude_mpm/dashboard/static/js/socket-client.js +12 -8
  113. claude_mpm/dashboard/static/js/stores/dashboard-store.js +562 -0
  114. claude_mpm/dashboard/static/js/tab-isolation-fix.js +185 -0
  115. claude_mpm/dashboard/static/legacy/activity.html +736 -0
  116. claude_mpm/dashboard/static/legacy/agents.html +786 -0
  117. claude_mpm/dashboard/static/legacy/files.html +747 -0
  118. claude_mpm/dashboard/static/legacy/tools.html +831 -0
  119. claude_mpm/dashboard/static/monitors-index.html +218 -0
  120. claude_mpm/dashboard/static/monitors.html +431 -0
  121. claude_mpm/dashboard/static/production/events.html +659 -0
  122. claude_mpm/dashboard/static/production/main.html +715 -0
  123. claude_mpm/dashboard/static/production/monitors.html +483 -0
  124. claude_mpm/dashboard/static/socket.io.min.js +7 -0
  125. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +7 -0
  126. claude_mpm/dashboard/static/test-archive/dashboard.html +635 -0
  127. claude_mpm/dashboard/static/test-archive/debug-events.html +147 -0
  128. claude_mpm/dashboard/static/test-archive/test-navigation.html +256 -0
  129. claude_mpm/dashboard/static/test-archive/test-react-exports.html +180 -0
  130. claude_mpm/dashboard/templates/index.html +79 -9
  131. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +1 -1
  132. claude_mpm/services/agents/deployment/agent_discovery_service.py +3 -0
  133. claude_mpm/services/agents/deployment/agent_template_builder.py +285 -26
  134. claude_mpm/services/agents/deployment/agent_validator.py +3 -0
  135. claude_mpm/services/agents/deployment/validation/template_validator.py +13 -4
  136. claude_mpm/services/agents/local_template_manager.py +2 -7
  137. claude_mpm/services/monitor/daemon.py +1 -2
  138. claude_mpm/services/monitor/daemon_manager.py +2 -7
  139. claude_mpm/services/monitor/event_emitter.py +6 -2
  140. claude_mpm/services/monitor/handlers/code_analysis.py +4 -6
  141. claude_mpm/services/monitor/handlers/hooks.py +2 -6
  142. claude_mpm/services/monitor/server.py +27 -4
  143. claude_mpm/tools/code_tree_analyzer.py +2 -4
  144. claude_mpm/utils/log_cleanup.py +612 -0
  145. {claude_mpm-4.2.44.dist-info → claude_mpm-4.3.0.dist-info}/METADATA +1 -1
  146. {claude_mpm-4.2.44.dist-info → claude_mpm-4.3.0.dist-info}/RECORD +151 -83
  147. claude_mpm/dashboard/static/test-browser-monitor.html +0 -470
  148. claude_mpm/dashboard/static/test-simple.html +0 -97
  149. /claude_mpm/dashboard/static/{test_debug.html → test-archive/test_debug.html} +0 -0
  150. {claude_mpm-4.2.44.dist-info → claude_mpm-4.3.0.dist-info}/WHEEL +0 -0
  151. {claude_mpm-4.2.44.dist-info → claude_mpm-4.3.0.dist-info}/entry_points.txt +0 -0
  152. {claude_mpm-4.2.44.dist-info → claude_mpm-4.3.0.dist-info}/licenses/LICENSE +0 -0
  153. {claude_mpm-4.2.44.dist-info → claude_mpm-4.3.0.dist-info}/top_level.txt +0 -0
claude_mpm/constants.py CHANGED
@@ -229,7 +229,7 @@ class Paths(str, Enum):
229
229
 
230
230
  CLAUDE_AGENTS_DIR = ".claude/agents"
231
231
  CLAUDE_CONFIG_DIR = ".claude"
232
- MPM_LOG_DIR = "logs/mpm"
232
+ MPM_LOG_DIR = ".claude-mpm/logs/mpm"
233
233
  MPM_SESSION_DIR = ".claude-mpm/session"
234
234
  MPM_PROMPTS_DIR = ".claude-mpm/prompts"
235
235
 
@@ -145,6 +145,7 @@ class ErrorHandler:
145
145
  if strategy == ErrorStrategy.TERMINATE:
146
146
  self.logger.critical(f"Terminating due to critical error: {error}")
147
147
  sys.exit(1)
148
+ return None
148
149
 
149
150
  def _log_error(
150
151
  self,
@@ -307,10 +308,7 @@ def handle_error(
307
308
  Returns:
308
309
  Result based on strategy
309
310
  """
310
- if logger:
311
- handler = ErrorHandler(logger=logger)
312
- else:
313
- handler = _global_handler
311
+ handler = ErrorHandler(logger=logger) if logger else _global_handler
314
312
 
315
313
  return handler.handle(
316
314
  error=error,
@@ -11,7 +11,7 @@ import json
11
11
  import os
12
12
  import shutil
13
13
  import tempfile
14
- from contextlib import contextmanager
14
+ from contextlib import contextmanager, suppress
15
15
  from pathlib import Path
16
16
  from typing import Any, List, Optional, Union
17
17
 
@@ -374,10 +374,8 @@ def atomic_write(
374
374
  except OSError as e:
375
375
  logger.error(f"Error in atomic write to {filepath}: {e}")
376
376
  # Clean up temporary file
377
- try:
377
+ with suppress(Exception):
378
378
  os.unlink(temp_path)
379
- except:
380
- pass
381
379
  return False
382
380
 
383
381
 
@@ -703,10 +701,7 @@ def validate_file(
703
701
  return False
704
702
 
705
703
  # Check extension
706
- if extensions and filepath.suffix not in extensions:
707
- return False
708
-
709
- return True
704
+ return not (extensions and filepath.suffix not in extensions)
710
705
 
711
706
 
712
707
  def get_file_hash(
@@ -760,10 +755,7 @@ def find_files(
760
755
  if not directory.exists():
761
756
  return []
762
757
 
763
- if recursive:
764
- paths = directory.rglob(pattern)
765
- else:
766
- paths = directory.glob(pattern)
758
+ paths = directory.rglob(pattern) if recursive else directory.glob(pattern)
767
759
 
768
760
  if file_only:
769
761
  return [p for p in paths if p.is_file()]
@@ -639,7 +639,14 @@ class FrameworkLoader:
639
639
  self._load_packaged_framework_content(content)
640
640
  else:
641
641
  # Load from filesystem for development mode
642
- # Load framework's INSTRUCTIONS.md
642
+ # Try new consolidated PM_INSTRUCTIONS.md first, fall back to INSTRUCTIONS.md
643
+ pm_instructions_path = (
644
+ self.framework_path
645
+ / "src"
646
+ / "claude_mpm"
647
+ / "agents"
648
+ / "PM_INSTRUCTIONS.md"
649
+ )
643
650
  framework_instructions_path = (
644
651
  self.framework_path
645
652
  / "src"
@@ -647,12 +654,25 @@ class FrameworkLoader:
647
654
  / "agents"
648
655
  / "INSTRUCTIONS.md"
649
656
  )
650
- if framework_instructions_path.exists():
657
+
658
+ # Try loading new consolidated file first
659
+ if pm_instructions_path.exists():
651
660
  loaded_content = self._try_load_file(
652
- framework_instructions_path, "framework INSTRUCTIONS.md"
661
+ pm_instructions_path, "consolidated PM_INSTRUCTIONS.md"
653
662
  )
654
663
  if loaded_content:
655
664
  content["framework_instructions"] = loaded_content
665
+ self.logger.info("Loaded consolidated PM_INSTRUCTIONS.md")
666
+ # Fall back to legacy file for backward compatibility
667
+ elif framework_instructions_path.exists():
668
+ loaded_content = self._try_load_file(
669
+ framework_instructions_path, "framework INSTRUCTIONS.md (legacy)"
670
+ )
671
+ if loaded_content:
672
+ content["framework_instructions"] = loaded_content
673
+ self.logger.warning(
674
+ "Using legacy INSTRUCTIONS.md - consider migrating to PM_INSTRUCTIONS.md"
675
+ )
656
676
  content["loaded"] = True
657
677
  # Add framework version to content
658
678
  if self.framework_version:
@@ -717,20 +737,33 @@ class FrameworkLoader:
717
737
  return
718
738
 
719
739
  try:
720
- # Load INSTRUCTIONS.md
721
- instructions_content = self._load_packaged_file("INSTRUCTIONS.md")
722
- if instructions_content:
723
- content["framework_instructions"] = instructions_content
740
+ # Try new consolidated PM_INSTRUCTIONS.md first
741
+ pm_instructions_content = self._load_packaged_file("PM_INSTRUCTIONS.md")
742
+ if pm_instructions_content:
743
+ content["framework_instructions"] = pm_instructions_content
724
744
  content["loaded"] = True
745
+ self.logger.info("Loaded consolidated PM_INSTRUCTIONS.md from package")
725
746
  # Extract and store version/timestamp metadata
726
747
  self._extract_metadata_from_content(
727
- instructions_content, "INSTRUCTIONS.md"
748
+ pm_instructions_content, "PM_INSTRUCTIONS.md"
728
749
  )
729
- if self.framework_version:
730
- content["instructions_version"] = self.framework_version
731
- content["version"] = self.framework_version
732
- if self.framework_last_modified:
733
- content["instructions_last_modified"] = self.framework_last_modified
750
+ else:
751
+ # Fall back to legacy INSTRUCTIONS.md
752
+ instructions_content = self._load_packaged_file("INSTRUCTIONS.md")
753
+ if instructions_content:
754
+ content["framework_instructions"] = instructions_content
755
+ content["loaded"] = True
756
+ self.logger.warning("Using legacy INSTRUCTIONS.md from package")
757
+ # Extract and store version/timestamp metadata
758
+ self._extract_metadata_from_content(
759
+ instructions_content, "INSTRUCTIONS.md"
760
+ )
761
+
762
+ if self.framework_version:
763
+ content["instructions_version"] = self.framework_version
764
+ content["version"] = self.framework_version
765
+ if self.framework_last_modified:
766
+ content["instructions_last_modified"] = self.framework_last_modified
734
767
 
735
768
  # Load BASE_PM.md
736
769
  base_pm_content = self._load_packaged_file("BASE_PM.md")
@@ -757,22 +790,37 @@ class FrameworkLoader:
757
790
  ) -> None:
758
791
  """Load framework content using importlib.resources fallback."""
759
792
  try:
760
- # Load INSTRUCTIONS.md
761
- instructions_content = self._load_packaged_file_fallback(
762
- "INSTRUCTIONS.md", resources
793
+ # Try new consolidated PM_INSTRUCTIONS.md first
794
+ pm_instructions_content = self._load_packaged_file_fallback(
795
+ "PM_INSTRUCTIONS.md", resources
763
796
  )
764
- if instructions_content:
765
- content["framework_instructions"] = instructions_content
797
+ if pm_instructions_content:
798
+ content["framework_instructions"] = pm_instructions_content
766
799
  content["loaded"] = True
800
+ self.logger.info("Loaded consolidated PM_INSTRUCTIONS.md via fallback")
767
801
  # Extract and store version/timestamp metadata
768
802
  self._extract_metadata_from_content(
769
- instructions_content, "INSTRUCTIONS.md"
803
+ pm_instructions_content, "PM_INSTRUCTIONS.md"
770
804
  )
771
- if self.framework_version:
772
- content["instructions_version"] = self.framework_version
773
- content["version"] = self.framework_version
774
- if self.framework_last_modified:
775
- content["instructions_last_modified"] = self.framework_last_modified
805
+ else:
806
+ # Fall back to legacy INSTRUCTIONS.md
807
+ instructions_content = self._load_packaged_file_fallback(
808
+ "INSTRUCTIONS.md", resources
809
+ )
810
+ if instructions_content:
811
+ content["framework_instructions"] = instructions_content
812
+ content["loaded"] = True
813
+ self.logger.warning("Using legacy INSTRUCTIONS.md via fallback")
814
+ # Extract and store version/timestamp metadata
815
+ self._extract_metadata_from_content(
816
+ instructions_content, "INSTRUCTIONS.md"
817
+ )
818
+
819
+ if self.framework_version:
820
+ content["instructions_version"] = self.framework_version
821
+ content["version"] = self.framework_version
822
+ if self.framework_last_modified:
823
+ content["instructions_last_modified"] = self.framework_last_modified
776
824
 
777
825
  # Load BASE_PM.md
778
826
  base_pm_content = self._load_packaged_file_fallback("BASE_PM.md", resources)
@@ -29,6 +29,12 @@ from ..core.constants import SystemLimits
29
29
 
30
30
  logger = logging.getLogger(__name__)
31
31
 
32
+ # Import cleanup utility for automatic cleanup
33
+ try:
34
+ from ..utils.log_cleanup import run_cleanup_on_startup
35
+ except ImportError:
36
+ run_cleanup_on_startup = None
37
+
32
38
 
33
39
  class LogManager:
34
40
  """
@@ -76,6 +82,9 @@ class LogManager:
76
82
  # Start background threads
77
83
  self._start_background_threads()
78
84
 
85
+ # Run automatic cleanup on startup if enabled
86
+ self._run_startup_cleanup()
87
+
79
88
  def _setup_logging_config(self):
80
89
  """Load and setup logging configuration from config."""
81
90
  logging_config = self.config.get("logging", {})
@@ -101,10 +110,55 @@ class LogManager:
101
110
  }
102
111
 
103
112
  # Base directories
104
- self.base_log_dir = Path(logging_config.get("base_directory", "logs"))
113
+ self.base_log_dir = Path(
114
+ logging_config.get("base_directory", ".claude-mpm/logs")
115
+ )
105
116
  if not self.base_log_dir.is_absolute():
106
117
  self.base_log_dir = Path.cwd() / self.base_log_dir
107
118
 
119
+ def _run_startup_cleanup(self):
120
+ """Run automatic log cleanup on startup if enabled."""
121
+ if run_cleanup_on_startup is None:
122
+ return # Cleanup utility not available
123
+
124
+ try:
125
+ # Get cleanup configuration
126
+ cleanup_config = self.config.get("log_cleanup", {})
127
+
128
+ # Check if automatic cleanup is enabled (default: True)
129
+ if not cleanup_config.get("auto_cleanup_enabled", True):
130
+ logger.debug("Automatic log cleanup is disabled")
131
+ return
132
+
133
+ # Convert hours to days for cleanup utility
134
+ cleanup_params = {
135
+ "auto_cleanup_enabled": True,
136
+ "session_retention_days": self.retention_hours.get("sessions", 168)
137
+ // 24,
138
+ "archive_retention_days": cleanup_config.get(
139
+ "archive_retention_days", 30
140
+ ),
141
+ "log_retention_days": cleanup_config.get("log_retention_days", 14),
142
+ }
143
+
144
+ # Run cleanup in background thread to avoid blocking startup
145
+ def cleanup_task():
146
+ try:
147
+ result = run_cleanup_on_startup(self.base_log_dir, cleanup_params)
148
+ if result:
149
+ logger.debug(
150
+ f"Startup cleanup completed: "
151
+ f"Removed {result.get('total_removed', 0)} items"
152
+ )
153
+ except Exception as e:
154
+ logger.debug(f"Startup cleanup failed: {e}")
155
+
156
+ cleanup_thread = Thread(target=cleanup_task, daemon=True)
157
+ cleanup_thread.start()
158
+
159
+ except Exception as e:
160
+ logger.debug(f"Could not run startup cleanup: {e}")
161
+
108
162
  def _start_background_threads(self):
109
163
  """Start background threads for async operations."""
110
164
  with self._lock:
@@ -350,13 +404,14 @@ class LogManager:
350
404
  """
351
405
  One-time migration to move existing MPM logs to new subdirectory.
352
406
 
353
- Moves mpm_*.log files from .claude-mpm/logs/ to logs/mpm/
407
+ Moves mpm_*.log files from old locations to .claude-mpm/logs/mpm/
354
408
  """
355
409
  try:
356
- # Check both old possible locations
410
+ # Check old possible locations (including incorrectly created ones)
357
411
  old_locations = [
358
- Path.cwd() / ".claude-mpm" / "logs", # Old default location
359
- self.base_log_dir, # Current base location (logs/)
412
+ Path.cwd() / "logs", # Incorrectly created in project root
413
+ Path.cwd() / ".claude-mpm" / "logs", # Correct base location
414
+ self.base_log_dir, # Current base location (.claude-mpm/logs/)
360
415
  ]
361
416
  new_location = self.base_log_dir / "mpm"
362
417
 
claude_mpm/core/logger.py CHANGED
@@ -232,7 +232,7 @@ def setup_logging(
232
232
  if log_dir is None:
233
233
  # Use deployment root for logs to keep everything centralized
234
234
  deployment_root = get_project_root()
235
- log_dir = deployment_root / "logs" / "mpm"
235
+ log_dir = deployment_root / ".claude-mpm" / "logs" / "mpm"
236
236
 
237
237
  log_dir.mkdir(parents=True, exist_ok=True)
238
238
 
@@ -42,8 +42,10 @@ class LoggingConfig:
42
42
  ISO_DATE_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
43
43
 
44
44
  # File settings
45
- MAX_BYTES = 10 * 1024 * 1024 # 10MB
45
+ MAX_BYTES = 5 * 1024 * 1024 # 5MB - lowered for better rotation testing
46
46
  BACKUP_COUNT = 5
47
+ ROTATION_INTERVAL = "midnight" # Daily rotation at midnight
48
+ ROTATION_BACKUP_COUNT = 7 # Keep 7 days of daily logs
47
49
 
48
50
  # Component-specific log names
49
51
  COMPONENT_NAMES = {
@@ -76,11 +78,11 @@ class LoggerFactory:
76
78
  @classmethod
77
79
  def initialize(
78
80
  cls,
79
- log_level: str = None,
81
+ log_level: Optional[str] = None,
80
82
  log_dir: Optional[Path] = None,
81
83
  log_to_file: bool = False,
82
- log_format: str = None,
83
- date_format: str = None,
84
+ log_format: Optional[str] = None,
85
+ date_format: Optional[str] = None,
84
86
  ) -> None:
85
87
  """Initialize the logging system globally.
86
88
 
@@ -129,30 +131,46 @@ class LoggerFactory:
129
131
  log_format: Optional[str] = None,
130
132
  date_format: Optional[str] = None,
131
133
  ) -> None:
132
- """Set up file logging handler."""
134
+ """Set up file logging handlers with both size and time-based rotation."""
133
135
  if not cls._log_dir:
134
136
  return
135
137
 
136
138
  # Ensure log directory exists
137
139
  cls._log_dir.mkdir(parents=True, exist_ok=True)
138
140
 
139
- # Create rotating file handler
141
+ formatter = logging.Formatter(
142
+ log_format or LoggingConfig.DETAILED_FORMAT,
143
+ date_format or LoggingConfig.DATE_FORMAT,
144
+ )
145
+
146
+ # 1. Size-based rotating file handler (for current active log)
140
147
  log_file = cls._log_dir / "claude_mpm.log"
141
- file_handler = logging.handlers.RotatingFileHandler(
148
+ size_handler = logging.handlers.RotatingFileHandler(
142
149
  log_file,
143
150
  maxBytes=LoggingConfig.MAX_BYTES,
144
151
  backupCount=LoggingConfig.BACKUP_COUNT,
145
152
  )
146
- file_handler.setLevel(LoggingConfig.LEVELS.get(cls._log_level, logging.INFO))
147
-
148
- file_formatter = logging.Formatter(
149
- log_format or LoggingConfig.DETAILED_FORMAT,
150
- date_format or LoggingConfig.DATE_FORMAT,
153
+ size_handler.setLevel(LoggingConfig.LEVELS.get(cls._log_level, logging.INFO))
154
+ size_handler.setFormatter(formatter)
155
+ logging.getLogger().addHandler(size_handler)
156
+ cls._handlers["file"] = size_handler
157
+
158
+ # 2. Time-based rotating file handler (daily rotation)
159
+ daily_log_file = cls._log_dir / "claude_mpm_daily.log"
160
+ time_handler = logging.handlers.TimedRotatingFileHandler(
161
+ daily_log_file,
162
+ when=LoggingConfig.ROTATION_INTERVAL,
163
+ interval=1,
164
+ backupCount=LoggingConfig.ROTATION_BACKUP_COUNT,
151
165
  )
152
- file_handler.setFormatter(file_formatter)
166
+ time_handler.setLevel(LoggingConfig.LEVELS.get(cls._log_level, logging.INFO))
167
+ time_handler.setFormatter(formatter)
168
+
169
+ # Add suffix to rotated files (e.g., claude_mpm_daily.log.2024-09-18)
170
+ time_handler.suffix = "%Y-%m-%d"
153
171
 
154
- logging.getLogger().addHandler(file_handler)
155
- cls._handlers["file"] = file_handler
172
+ logging.getLogger().addHandler(time_handler)
173
+ cls._handlers["file_daily"] = time_handler
156
174
 
157
175
  @classmethod
158
176
  def get_logger(
@@ -268,10 +286,10 @@ def get_logger(
268
286
 
269
287
 
270
288
  def initialize_logging(
271
- log_level: str = None,
289
+ log_level: Optional[str] = None,
272
290
  log_dir: Optional[Path] = None,
273
291
  log_to_file: bool = False,
274
- log_format: str = None,
292
+ log_format: Optional[str] = None,
275
293
  ) -> None:
276
294
  """Initialize the logging system with standard configuration.
277
295
 
@@ -300,7 +318,7 @@ def set_log_level(level: str) -> None:
300
318
  LoggerFactory.set_level(level)
301
319
 
302
320
 
303
- def get_component_logger(component: str, name: str = None) -> logging.Logger:
321
+ def get_component_logger(component: str, name: Optional[str] = None) -> logging.Logger:
304
322
  """Get a logger for a specific component.
305
323
 
306
324
  Args:
@@ -367,9 +367,17 @@ class UnifiedAgentRegistry:
367
367
  break
368
368
  project_root = project_root.parent
369
369
 
370
- # Extract tags
370
+ # Extract tags (handle both list and comma-separated string formats)
371
371
  if "metadata" in data:
372
- tags = data["metadata"].get("tags", [])
372
+ tags_raw = data["metadata"].get("tags", [])
373
+ if isinstance(tags_raw, str):
374
+ tags = [
375
+ tag.strip()
376
+ for tag in tags_raw.split(",")
377
+ if tag.strip()
378
+ ]
379
+ else:
380
+ tags = tags_raw if isinstance(tags_raw, list) else []
373
381
 
374
382
  except Exception as e:
375
383
  logger.debug(
@@ -462,8 +470,14 @@ class UnifiedAgentRegistry:
462
470
  "specializations", data.get("specializations", [])
463
471
  )
464
472
 
465
- # Extract tags as specializations if present
466
- tags = metadata.get("tags", [])
473
+ # Extract tags as specializations if present (handle both formats)
474
+ tags_raw = metadata.get("tags", [])
475
+ if isinstance(tags_raw, str):
476
+ tags = [
477
+ tag.strip() for tag in tags_raw.split(",") if tag.strip()
478
+ ]
479
+ else:
480
+ tags = tags_raw if isinstance(tags_raw, list) else []
467
481
  if tags and not specializations:
468
482
  specializations = tags
469
483
  else:
@@ -0,0 +1,188 @@
1
+ .dataInspector {
2
+ background: rgba(0, 0, 0, 0.3);
3
+ border-radius: 6px;
4
+ border: 1px solid rgba(255, 255, 255, 0.1);
5
+ overflow: hidden;
6
+ }
7
+
8
+ .inspectorHeader {
9
+ display: flex;
10
+ gap: 10px;
11
+ padding: 10px;
12
+ background: rgba(255, 255, 255, 0.05);
13
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
14
+ align-items: center;
15
+ }
16
+
17
+ .searchInput {
18
+ flex: 1;
19
+ padding: 8px 12px;
20
+ background: rgba(255, 255, 255, 0.05);
21
+ border: 1px solid rgba(255, 255, 255, 0.1);
22
+ border-radius: 4px;
23
+ color: white;
24
+ font-size: 12px;
25
+ }
26
+
27
+ .searchInput::placeholder {
28
+ color: rgba(255, 255, 255, 0.5);
29
+ }
30
+
31
+ .actions {
32
+ display: flex;
33
+ gap: 8px;
34
+ }
35
+
36
+ .actionButton {
37
+ padding: 6px 12px;
38
+ background: rgba(255, 255, 255, 0.1);
39
+ border: none;
40
+ border-radius: 4px;
41
+ color: white;
42
+ font-size: 11px;
43
+ cursor: pointer;
44
+ transition: background 0.2s;
45
+ }
46
+
47
+ .actionButton:hover {
48
+ background: rgba(255, 255, 255, 0.2);
49
+ }
50
+
51
+ .treeContainer {
52
+ overflow-y: auto;
53
+ padding: 10px;
54
+ font-family: 'Courier New', monospace;
55
+ font-size: 13px;
56
+ line-height: 1.4;
57
+ }
58
+
59
+ .treeNode {
60
+ margin-bottom: 2px;
61
+ }
62
+
63
+ .nodeHeader {
64
+ display: flex;
65
+ align-items: center;
66
+ gap: 6px;
67
+ padding: 2px 4px;
68
+ cursor: pointer;
69
+ border-radius: 3px;
70
+ transition: background 0.2s;
71
+ }
72
+
73
+ .nodeHeader:hover {
74
+ background: rgba(255, 255, 255, 0.05);
75
+ }
76
+
77
+ .expandIcon {
78
+ color: #94a3b8;
79
+ font-size: 10px;
80
+ transition: transform 0.2s;
81
+ cursor: pointer;
82
+ user-select: none;
83
+ width: 12px;
84
+ display: inline-block;
85
+ }
86
+
87
+ .expandIcon.expanded {
88
+ transform: rotate(90deg);
89
+ }
90
+
91
+ .leafIcon {
92
+ color: #94a3b8;
93
+ font-size: 8px;
94
+ width: 12px;
95
+ display: inline-block;
96
+ text-align: center;
97
+ }
98
+
99
+ .nodeKey {
100
+ color: #60a5fa;
101
+ font-weight: 500;
102
+ }
103
+
104
+ .nodeValue {
105
+ margin-left: 6px;
106
+ word-break: break-all;
107
+ }
108
+
109
+ .nodeValue.string {
110
+ color: #86efac;
111
+ }
112
+
113
+ .nodeValue.number {
114
+ color: #fbbf24;
115
+ }
116
+
117
+ .nodeValue.boolean {
118
+ color: #a78bfa;
119
+ }
120
+
121
+ .nodeValue.null,
122
+ .nodeValue.undefined {
123
+ color: #f87171;
124
+ font-style: italic;
125
+ }
126
+
127
+ .nodeValue.object,
128
+ .nodeValue.array {
129
+ color: #94a3b8;
130
+ font-style: italic;
131
+ }
132
+
133
+ .copyButton {
134
+ margin-left: auto;
135
+ background: none;
136
+ border: none;
137
+ color: rgba(255, 255, 255, 0.5);
138
+ cursor: pointer;
139
+ padding: 2px;
140
+ font-size: 10px;
141
+ opacity: 0;
142
+ transition: opacity 0.2s;
143
+ }
144
+
145
+ .nodeHeader:hover .copyButton {
146
+ opacity: 1;
147
+ }
148
+
149
+ .copyButton:hover {
150
+ color: rgba(255, 255, 255, 0.8);
151
+ }
152
+
153
+ .nodeChildren {
154
+ border-left: 1px solid rgba(255, 255, 255, 0.1);
155
+ margin-left: 6px;
156
+ }
157
+
158
+ .highlight {
159
+ background: rgba(251, 191, 36, 0.3);
160
+ color: #fbbf24;
161
+ padding: 1px 2px;
162
+ border-radius: 2px;
163
+ }
164
+
165
+ .noData {
166
+ text-align: center;
167
+ color: rgba(255, 255, 255, 0.5);
168
+ padding: 20px;
169
+ font-style: italic;
170
+ }
171
+
172
+ /* Scrollbar styling */
173
+ .treeContainer::-webkit-scrollbar {
174
+ width: 6px;
175
+ }
176
+
177
+ .treeContainer::-webkit-scrollbar-track {
178
+ background: rgba(255, 255, 255, 0.05);
179
+ }
180
+
181
+ .treeContainer::-webkit-scrollbar-thumb {
182
+ background: rgba(255, 255, 255, 0.2);
183
+ border-radius: 3px;
184
+ }
185
+
186
+ .treeContainer::-webkit-scrollbar-thumb:hover {
187
+ background: rgba(255, 255, 255, 0.3);
188
+ }