claude-mpm 5.4.21__py3-none-any.whl → 5.4.59__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.

Potentially problematic release.


This version of claude-mpm might be problematic. Click here for more details.

Files changed (176) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_AGENT.md +164 -0
  3. claude_mpm/agents/BASE_ENGINEER.md +658 -0
  4. claude_mpm/agents/MEMORY.md +1 -1
  5. claude_mpm/agents/PM_INSTRUCTIONS.md +771 -1019
  6. claude_mpm/agents/WORKFLOW.md +5 -254
  7. claude_mpm/agents/agent_loader.py +1 -1
  8. claude_mpm/agents/base_agent.json +31 -0
  9. claude_mpm/agents/frontmatter_validator.py +2 -2
  10. claude_mpm/cli/commands/agent_state_manager.py +10 -10
  11. claude_mpm/cli/commands/agents.py +9 -9
  12. claude_mpm/cli/commands/auto_configure.py +4 -4
  13. claude_mpm/cli/commands/configure.py +1 -1
  14. claude_mpm/cli/commands/configure_agent_display.py +12 -0
  15. claude_mpm/cli/commands/mpm_init/core.py +72 -0
  16. claude_mpm/cli/commands/postmortem.py +1 -1
  17. claude_mpm/cli/commands/profile.py +276 -0
  18. claude_mpm/cli/commands/skills.py +14 -18
  19. claude_mpm/cli/executor.py +10 -0
  20. claude_mpm/cli/interactive/agent_wizard.py +2 -2
  21. claude_mpm/cli/parsers/base_parser.py +7 -0
  22. claude_mpm/cli/parsers/profile_parser.py +147 -0
  23. claude_mpm/cli/parsers/skills_parser.py +0 -6
  24. claude_mpm/cli/startup.py +506 -180
  25. claude_mpm/commands/mpm-config.md +13 -250
  26. claude_mpm/commands/mpm-doctor.md +9 -22
  27. claude_mpm/commands/mpm-help.md +5 -206
  28. claude_mpm/commands/mpm-init.md +81 -507
  29. claude_mpm/commands/mpm-monitor.md +15 -402
  30. claude_mpm/commands/mpm-organize.md +61 -441
  31. claude_mpm/commands/mpm-postmortem.md +6 -108
  32. claude_mpm/commands/mpm-session-resume.md +12 -363
  33. claude_mpm/commands/mpm-status.md +5 -69
  34. claude_mpm/commands/mpm-ticket-view.md +52 -495
  35. claude_mpm/commands/mpm-version.md +5 -107
  36. claude_mpm/core/config.py +2 -4
  37. claude_mpm/core/framework/loaders/agent_loader.py +1 -1
  38. claude_mpm/core/framework/loaders/instruction_loader.py +52 -11
  39. claude_mpm/core/optimized_startup.py +61 -0
  40. claude_mpm/core/shared/config_loader.py +3 -1
  41. claude_mpm/core/unified_agent_registry.py +1 -1
  42. claude_mpm/dashboard/static/svelte-build/_app/env.js +1 -0
  43. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +1 -0
  44. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +1 -0
  45. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +1 -0
  46. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +24 -0
  47. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B0uc0UOD.js +36 -0
  48. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B7RN905-.js +1 -0
  49. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B7xVLGWV.js +2 -0
  50. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BIF9m_hv.js +61 -0
  51. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +1 -0
  52. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BPYeabCQ.js +1 -0
  53. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BQaXIfA_.js +331 -0
  54. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BSNlmTZj.js +1 -0
  55. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Be7GpZd6.js +7 -0
  56. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Bh0LDWpI.js +145 -0
  57. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BofRWZRR.js +10 -0
  58. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BovzEFCE.js +30 -0
  59. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C30mlcqg.js +165 -0
  60. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C4B-KCzX.js +1 -0
  61. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C4JcI4KD.js +122 -0
  62. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CBBdVcY8.js +1 -0
  63. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CDuw-vjf.js +1 -0
  64. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C_Usid8X.js +15 -0
  65. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cfqx1Qun.js +10 -0
  66. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CiIAseT4.js +128 -0
  67. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CmKTTxBW.js +1 -0
  68. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CnA0NrzZ.js +1 -0
  69. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cs_tUR18.js +24 -0
  70. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cu_Erd72.js +261 -0
  71. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CyWMqx4W.js +43 -0
  72. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CzZX-COe.js +220 -0
  73. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CzeYkLYB.js +65 -0
  74. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D3k0OPJN.js +4 -0
  75. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D9lljYKQ.js +1 -0
  76. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DGkLK5U1.js +267 -0
  77. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DI7hHRFL.js +1 -0
  78. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DLVjFsZ3.js +139 -0
  79. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DUrLdbGD.js +89 -0
  80. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DVp1hx9R.js +1 -0
  81. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DY1XQ8fi.js +2 -0
  82. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DZX00Y4g.js +1 -0
  83. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +1 -0
  84. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DaimHw_p.js +68 -0
  85. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +323 -0
  86. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dhb8PKl3.js +1 -0
  87. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dle-35c7.js +64 -0
  88. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DmxopI1J.js +1 -0
  89. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DwBR2MJi.js +60 -0
  90. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/GYwsonyD.js +1 -0
  91. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Gi6I4Gst.js +1 -0
  92. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/NqQ1dWOy.js +1 -0
  93. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/RJiighC3.js +1 -0
  94. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Vzk33B_K.js +2 -0
  95. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/ZGh7QtNv.js +7 -0
  96. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/bT1r9zLR.js +1 -0
  97. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/bTOqqlTd.js +1 -0
  98. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/eNVUfhuA.js +1 -0
  99. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/iEWssX7S.js +162 -0
  100. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/sQeU3Y1z.js +1 -0
  101. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/uuIeMWc-.js +1 -0
  102. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.D6-I5TpK.js +2 -0
  103. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +1 -0
  104. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/0.m1gL8KXf.js +1 -0
  105. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.CgNOuw-d.js +1 -0
  106. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +1 -0
  107. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -0
  108. claude_mpm/dashboard/static/svelte-build/favicon.svg +7 -0
  109. claude_mpm/dashboard/static/svelte-build/index.html +36 -0
  110. claude_mpm/dashboard-svelte/node_modules/katex/src/fonts/generate_fonts.py +58 -0
  111. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_tfms.py +114 -0
  112. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_ttfs.py +122 -0
  113. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/format_json.py +28 -0
  114. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/parse_tfm.py +211 -0
  115. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
  116. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
  117. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  118. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  119. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
  120. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
  121. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  122. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
  123. claude_mpm/hooks/claude_hooks/hook_handler.py +149 -1
  124. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
  125. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  126. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
  127. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  128. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  129. claude_mpm/hooks/claude_hooks/services/connection_manager.py +26 -6
  130. claude_mpm/hooks/kuzu_memory_hook.py +5 -5
  131. claude_mpm/init.py +276 -0
  132. claude_mpm/models/git_repository.py +3 -3
  133. claude_mpm/scripts/start_activity_logging.py +0 -0
  134. claude_mpm/services/agents/agent_builder.py +3 -3
  135. claude_mpm/services/agents/cache_git_manager.py +6 -6
  136. claude_mpm/services/agents/deployment/agent_deployment.py +29 -7
  137. claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -2
  138. claude_mpm/services/agents/deployment/agent_format_converter.py +25 -13
  139. claude_mpm/services/agents/deployment/agent_template_builder.py +31 -19
  140. claude_mpm/services/agents/deployment/agents_directory_resolver.py +2 -2
  141. claude_mpm/services/agents/deployment/async_agent_deployment.py +31 -27
  142. claude_mpm/services/agents/deployment/local_template_deployment.py +3 -1
  143. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +169 -26
  144. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +98 -75
  145. claude_mpm/services/agents/git_source_manager.py +23 -4
  146. claude_mpm/services/agents/recommender.py +5 -3
  147. claude_mpm/services/agents/single_tier_deployment_service.py +2 -2
  148. claude_mpm/services/agents/sources/git_source_sync_service.py +121 -10
  149. claude_mpm/services/agents/startup_sync.py +22 -2
  150. claude_mpm/services/diagnostics/checks/agent_check.py +2 -2
  151. claude_mpm/services/diagnostics/checks/agent_sources_check.py +1 -1
  152. claude_mpm/services/git/git_operations_service.py +8 -8
  153. claude_mpm/services/monitor/management/lifecycle.py +7 -1
  154. claude_mpm/services/monitor/server.py +473 -3
  155. claude_mpm/services/pm_skills_deployer.py +711 -0
  156. claude_mpm/services/profile_manager.py +337 -0
  157. claude_mpm/services/skills/git_skill_source_manager.py +148 -11
  158. claude_mpm/services/skills/selective_skill_deployer.py +97 -48
  159. claude_mpm/services/skills_deployer.py +161 -65
  160. claude_mpm/services/socketio/dashboard_server.py +1 -0
  161. claude_mpm/services/socketio/event_normalizer.py +37 -6
  162. claude_mpm/services/socketio/server/core.py +262 -123
  163. claude_mpm/skills/bundled/security-scanning.md +112 -0
  164. claude_mpm/skills/skill_manager.py +98 -3
  165. claude_mpm/templates/.pre-commit-config.yaml +112 -0
  166. claude_mpm/utils/agent_dependency_loader.py +14 -2
  167. claude_mpm/utils/agent_filters.py +1 -1
  168. claude_mpm/utils/migration.py +4 -4
  169. claude_mpm/utils/robust_installer.py +47 -3
  170. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/METADATA +7 -4
  171. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/RECORD +175 -81
  172. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/WHEEL +0 -0
  173. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/entry_points.txt +0 -0
  174. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/licenses/LICENSE +0 -0
  175. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  176. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/top_level.txt +0 -0
@@ -32,7 +32,6 @@ except ImportError:
32
32
  # Import VersionService for dynamic version retrieval
33
33
  import contextlib
34
34
 
35
- import claude_mpm
36
35
  from claude_mpm.services.version_service import VersionService
37
36
 
38
37
  from ....core.constants import SystemLimits, TimeoutConfig
@@ -277,9 +276,16 @@ class SocketIOServerCore:
277
276
  # Extract event data from payload (handles both direct and wrapped formats)
278
277
  # ConnectionManagerService sends: {"namespace": "...", "event": "...", "data": {...}}
279
278
  # Direct hook events may send data directly
280
- if "data" in payload and isinstance(payload.get("data"), dict):
279
+ # CRITICAL: Check if payload has the expected event structure (type, subtype, timestamp)
280
+ # If it does, use it directly. Only extract 'data' field if it's a wrapper object.
281
+ if "type" in payload and "subtype" in payload:
282
+ # Payload is already in normalized format, use it directly
283
+ event_data = payload
284
+ elif "data" in payload and isinstance(payload.get("data"), dict):
285
+ # Payload is a wrapper with 'data' field (from ConnectionManagerService)
281
286
  event_data = payload["data"]
282
287
  else:
288
+ # Fallback: use entire payload
283
289
  event_data = payload
284
290
 
285
291
  # Log receipt with more detail
@@ -409,9 +415,23 @@ class SocketIOServerCore:
409
415
  self.event_buffer.append(event_data)
410
416
  self.stats["events_buffered"] = len(self.event_buffer)
411
417
 
412
- # Add to main server's event history
413
- if hasattr(self.main_server, "event_history"):
418
+ # Add to main server's event history UNCONDITIONALLY
419
+ # WHY: event_history is always initialized in SocketIOServer.__init__
420
+ # This ensures events persist for new clients who connect later
421
+ if self.main_server and hasattr(
422
+ self.main_server, "event_history"
423
+ ):
414
424
  self.main_server.event_history.append(event_data)
425
+ self.logger.debug(
426
+ f"Added to history (total: {len(self.main_server.event_history)})"
427
+ )
428
+ else:
429
+ # CRITICAL: Log warning if event_history is not available
430
+ # This indicates a configuration or initialization problem
431
+ self.logger.warning(
432
+ "event_history not initialized on main_server! "
433
+ "Events will not persist for new clients."
434
+ )
415
435
 
416
436
  # Use the broadcaster's sio to emit (it's the same as self.sio)
417
437
  # This ensures the event goes through the proper channels
@@ -445,6 +465,21 @@ class SocketIOServerCore:
445
465
  self.event_buffer.append(event_data)
446
466
  self.stats["events_buffered"] = len(self.event_buffer)
447
467
 
468
+ # Add to main server's event history (fallback path)
469
+ # WHY: Ensure events persist even when broadcaster is unavailable
470
+ if self.main_server and hasattr(
471
+ self.main_server, "event_history"
472
+ ):
473
+ self.main_server.event_history.append(event_data)
474
+ self.logger.debug(
475
+ f"Added to history via fallback (total: {len(self.main_server.event_history)})"
476
+ )
477
+ else:
478
+ self.logger.warning(
479
+ "event_history not initialized on main_server (fallback path)! "
480
+ "Events will not persist for new clients."
481
+ )
482
+
448
483
  # Return 204 No Content for success
449
484
  self.logger.debug(f"✅ HTTP event processed successfully: {event_type}")
450
485
  return web.Response(status=204)
@@ -504,7 +539,7 @@ class SocketIOServerCore:
504
539
  from pathlib import Path
505
540
 
506
541
  try:
507
- working_dir = Path.cwd()
542
+ working_dir = str(Path.cwd())
508
543
  home_dir = str(Path.home())
509
544
 
510
545
  return web.json_response(
@@ -594,6 +629,169 @@ class SocketIOServerCore:
594
629
  self.app.router.add_get("/api/file/read", file_read_handler)
595
630
  self.logger.info("✅ File reading API registered at /api/file/read")
596
631
 
632
+ # Add files listing endpoint for file browser
633
+ async def files_list_handler(request):
634
+ """Handle GET /api/files for listing files in working directory."""
635
+
636
+ try:
637
+ # Get working directory from query params or use current directory
638
+ working_dir = request.query.get("path", str(Path.cwd()))
639
+ abs_working_dir = Path(working_dir).resolve().expanduser()
640
+
641
+ # Security check - ensure directory is accessible
642
+ if not abs_working_dir.exists():
643
+ return web.json_response(
644
+ {"error": "Directory not found"}, status=404
645
+ )
646
+
647
+ if not abs_working_dir.is_dir():
648
+ return web.json_response(
649
+ {"error": "Path is not a directory"}, status=400
650
+ )
651
+
652
+ # Collect files and directories
653
+ files = []
654
+ directories = []
655
+
656
+ # Common patterns to exclude
657
+ exclude_patterns = {
658
+ ".git",
659
+ ".venv",
660
+ "venv",
661
+ "node_modules",
662
+ "__pycache__",
663
+ ".pytest_cache",
664
+ ".mypy_cache",
665
+ "dist",
666
+ "build",
667
+ ".next",
668
+ "coverage",
669
+ ".coverage",
670
+ ".tox",
671
+ ".eggs",
672
+ "*.egg-info",
673
+ }
674
+
675
+ for entry in abs_working_dir.iterdir():
676
+ # Skip hidden files and excluded patterns
677
+ if entry.name.startswith("."):
678
+ # Allow .py, .ts, .md, etc. files but skip directories like .git
679
+ if entry.is_dir():
680
+ continue
681
+
682
+ # Skip excluded directories
683
+ if entry.name in exclude_patterns:
684
+ continue
685
+
686
+ try:
687
+ stat_info = entry.stat()
688
+ entry_data = {
689
+ "name": entry.name,
690
+ "path": str(entry),
691
+ "type": "directory" if entry.is_dir() else "file",
692
+ "size": stat_info.st_size if entry.is_file() else 0,
693
+ "modified": stat_info.st_mtime,
694
+ }
695
+
696
+ if entry.is_dir():
697
+ directories.append(entry_data)
698
+ else:
699
+ # Add file extension for syntax highlighting
700
+ entry_data["extension"] = entry.suffix.lower()
701
+ files.append(entry_data)
702
+ except (PermissionError, OSError):
703
+ # Skip files we can't access
704
+ continue
705
+
706
+ # Sort directories and files alphabetically
707
+ directories.sort(key=lambda x: x["name"].lower())
708
+ files.sort(key=lambda x: x["name"].lower())
709
+
710
+ return web.json_response(
711
+ {
712
+ "success": True,
713
+ "path": str(abs_working_dir),
714
+ "directories": directories,
715
+ "files": files,
716
+ "total_files": len(files),
717
+ "total_directories": len(directories),
718
+ }
719
+ )
720
+
721
+ except Exception as e:
722
+ self.logger.error(f"Error listing files: {e}")
723
+ return web.json_response({"error": str(e)}, status=500)
724
+
725
+ self.app.router.add_get("/api/files", files_list_handler)
726
+ self.logger.info("✅ Files listing API registered at /api/files")
727
+
728
+ # Add git history endpoint
729
+ async def git_history_handler(request):
730
+ """Handle POST /api/git-history for getting file git history."""
731
+ import subprocess
732
+
733
+ try:
734
+ # Parse JSON body
735
+ data = await request.json()
736
+ file_path = data.get("path", "")
737
+ limit = data.get("limit", 10)
738
+
739
+ if not file_path:
740
+ return web.json_response(
741
+ {"success": False, "error": "No path provided", "commits": []},
742
+ status=400,
743
+ )
744
+
745
+ abs_path = Path(Path(file_path).resolve().expanduser())
746
+
747
+ if not Path(abs_path).exists():
748
+ return web.json_response(
749
+ {"success": False, "error": "File not found", "commits": []},
750
+ status=404,
751
+ )
752
+
753
+ # Get git log for file
754
+ result = subprocess.run(
755
+ [
756
+ "git",
757
+ "log",
758
+ f"-{limit}",
759
+ "--pretty=format:%H|%an|%ar|%s",
760
+ "--",
761
+ abs_path,
762
+ ],
763
+ check=False,
764
+ capture_output=True,
765
+ text=True,
766
+ cwd=Path(abs_path).parent,
767
+ )
768
+
769
+ commits = []
770
+ if result.returncode == 0 and result.stdout:
771
+ for line in result.stdout.strip().split("\n"):
772
+ if line:
773
+ parts = line.split("|", 3)
774
+ if len(parts) == 4:
775
+ commits.append(
776
+ {
777
+ "hash": parts[0][:7], # Short hash
778
+ "author": parts[1],
779
+ "date": parts[2],
780
+ "message": parts[3],
781
+ }
782
+ )
783
+
784
+ return web.json_response({"success": True, "commits": commits})
785
+
786
+ except Exception as e:
787
+ self.logger.error(f"Error getting git history: {e}")
788
+ return web.json_response(
789
+ {"success": False, "error": str(e), "commits": []}, status=500
790
+ )
791
+
792
+ self.app.router.add_post("/api/git-history", git_history_handler)
793
+ self.logger.info("✅ Git history API registered at /api/git-history")
794
+
597
795
  def _setup_directory_api(self):
598
796
  """Setup simple directory listing API.
599
797
 
@@ -611,7 +809,7 @@ class SocketIOServerCore:
611
809
  self.logger.error(f"Failed to setup directory API: {e}")
612
810
 
613
811
  def _setup_static_files(self):
614
- """Setup static file serving for the dashboard."""
812
+ """Setup static file serving for the Svelte dashboard."""
615
813
  try:
616
814
  # Add debug logging for deployment context
617
815
  try:
@@ -624,65 +822,43 @@ class SocketIOServerCore:
624
822
  except Exception as e:
625
823
  self.logger.debug(f"Could not detect deployment context: {e}")
626
824
 
627
- self.dashboard_path = self._find_static_path()
825
+ # Find Svelte build directory
826
+ svelte_build_path = self._find_static_path()
628
827
 
629
- if self.dashboard_path and self.dashboard_path.exists():
630
- self.logger.info(f"✅ Dashboard found at: {self.dashboard_path}")
828
+ if svelte_build_path and svelte_build_path.exists():
829
+ self.logger.info(f"✅ Svelte dashboard found at: {svelte_build_path}")
830
+ self.dashboard_path = svelte_build_path
631
831
 
632
- # Serve index.html at root
832
+ # Serve Svelte index.html at root
633
833
  async def index_handler(request):
634
- index_file = self.dashboard_path / "index.html"
834
+ index_file = svelte_build_path / "index.html"
635
835
  if index_file.exists():
636
- self.logger.debug(f"Serving dashboard index from: {index_file}")
836
+ self.logger.debug(
837
+ f"Serving Svelte dashboard from: {index_file}"
838
+ )
637
839
  return web.FileResponse(index_file)
638
- self.logger.warning(
639
- f"Dashboard index.html not found at: {index_file}"
640
- )
840
+ self.logger.warning(f"Svelte index.html not found at: {index_file}")
641
841
  return web.Response(text="Dashboard not available", status=404)
642
842
 
643
843
  self.app.router.add_get("/", index_handler)
644
844
 
645
- # Serve the actual dashboard template at /dashboard
646
- async def dashboard_handler(request):
647
- dashboard_template = (
648
- self.dashboard_path.parent / "templates" / "index.html"
649
- )
650
- if dashboard_template.exists():
651
- self.logger.debug(
652
- f"Serving dashboard template from: {dashboard_template}"
653
- )
654
- return web.FileResponse(dashboard_template)
655
- # Fallback to the main index if template doesn't exist
656
- self.logger.warning(
657
- f"Dashboard template not found at: {dashboard_template}, falling back to index"
845
+ # Serve Svelte app assets at /_app/ (needed for SvelteKit builds)
846
+ svelte_app_path = svelte_build_path / "_app"
847
+ if svelte_app_path.exists():
848
+ self.app.router.add_static(
849
+ "/_app/", svelte_app_path, name="svelte_app"
658
850
  )
659
- return await index_handler(request)
660
-
661
- self.app.router.add_get("/dashboard", dashboard_handler)
662
-
663
- # Serve simple code view template at /code-simple
664
- async def code_simple_handler(request):
665
- code_simple_template = (
666
- self.dashboard_path.parent / "templates" / "code_simple.html"
851
+ self.logger.info(
852
+ f"✅ Svelte dashboard available at http://{self.host}:{self.port}/ (build: {svelte_build_path})"
667
853
  )
668
- if code_simple_template.exists():
669
- self.logger.debug(
670
- f"Serving code simple template from: {code_simple_template}"
671
- )
672
- return web.FileResponse(code_simple_template)
673
- # Return error if template doesn't exist
854
+ else:
674
855
  self.logger.warning(
675
- f"Code simple template not found at: {code_simple_template}"
676
- )
677
- return web.Response(
678
- text="Simple code view not available", status=404
856
+ f"⚠️ Svelte _app directory not found at: {svelte_app_path}"
679
857
  )
680
858
 
681
- self.app.router.add_get("/code-simple", code_simple_handler)
682
-
683
- # Serve version.json from dashboard directory
859
+ # Serve version.json from Svelte build directory
684
860
  async def version_handler(request):
685
- version_file = self.dashboard_path / "version.json"
861
+ version_file = svelte_build_path / "version.json"
686
862
  if version_file.exists():
687
863
  self.logger.debug(f"Serving version.json from: {version_file}")
688
864
  return web.FileResponse(version_file)
@@ -698,55 +874,10 @@ class SocketIOServerCore:
698
874
 
699
875
  self.app.router.add_get("/version.json", version_handler)
700
876
 
701
- # Serve static assets (CSS, JS) from the dashboard static directory
702
- # Use package-relative path (works for both dev and installed package)
703
- package_root = Path(claude_mpm.__file__).parent
704
- dashboard_static_path = package_root / "dashboard" / "static"
705
- if dashboard_static_path.exists():
706
- self.app.router.add_static(
707
- "/static/", dashboard_static_path, name="dashboard_static"
708
- )
709
- self.logger.info(
710
- f"✅ Static assets available at: {dashboard_static_path}"
711
- )
712
- else:
713
- self.logger.warning(
714
- f"⚠️ Static assets directory not found at: {dashboard_static_path}"
715
- )
716
-
717
- # Serve Svelte dashboard build
718
- svelte_build_path = (
719
- package_root / "dashboard" / "static" / "svelte-build"
720
- )
721
- if svelte_build_path.exists():
722
- # Serve Svelte dashboard at /svelte route
723
- async def svelte_handler(request):
724
- svelte_index = svelte_build_path / "index.html"
725
- if svelte_index.exists():
726
- self.logger.debug(
727
- f"Serving Svelte dashboard from: {svelte_index}"
728
- )
729
- return web.FileResponse(svelte_index)
730
- return web.Response(
731
- text="Svelte dashboard not available", status=404
732
- )
733
-
734
- self.app.router.add_get("/svelte", svelte_handler)
735
-
736
- # Serve Svelte app assets at /_app/ (needed for SvelteKit builds)
737
- svelte_app_path = svelte_build_path / "_app"
738
- if svelte_app_path.exists():
739
- self.app.router.add_static(
740
- "/_app/", svelte_app_path, name="svelte_app"
741
- )
742
- self.logger.info(
743
- f"✅ Svelte dashboard available at /svelte (build: {svelte_build_path})"
744
- )
745
- else:
746
- self.logger.debug(f"Svelte build not found at: {svelte_build_path}")
747
-
748
877
  else:
749
- self.logger.warning("⚠️ No dashboard found, serving fallback response")
878
+ self.logger.warning(
879
+ "⚠️ Svelte dashboard not found, serving fallback response"
880
+ )
750
881
 
751
882
  # Fallback handler
752
883
  async def fallback_handler(request):
@@ -773,10 +904,10 @@ class SocketIOServerCore:
773
904
  self.app.router.add_get("/", error_handler)
774
905
 
775
906
  def _find_static_path(self):
776
- """Find the static files directory using multiple approaches.
907
+ """Find the Svelte build directory using multiple approaches.
777
908
 
778
- WHY: The static files location varies depending on how the application
779
- is installed and run. We try multiple common locations to find them.
909
+ WHY: The dashboard is now pure Svelte, located at dashboard/static/svelte-build/.
910
+ We search for this specific structure across different deployment contexts.
780
911
  """
781
912
  # Get deployment-context-aware paths
782
913
  try:
@@ -797,43 +928,45 @@ class SocketIOServerCore:
797
928
  package_root = None
798
929
  project_root = get_project_root()
799
930
 
800
- # Try multiple possible locations for static files and dashboard
931
+ # Try multiple possible locations for Svelte build directory
801
932
  possible_paths = [
802
933
  # Package-based paths (for pipx and pip installations)
803
- package_root / "dashboard" / "templates" if package_root else None,
804
- package_root / "services" / "socketio" / "static" if package_root else None,
805
- package_root / "static" if package_root else None,
934
+ package_root / "dashboard" / "static" / "svelte-build"
935
+ if package_root
936
+ else None,
806
937
  # Project-based paths (for development)
807
- project_root / "src" / "claude_mpm" / "dashboard" / "templates",
808
- project_root / "dashboard" / "templates",
809
- project_root / "src" / "claude_mpm" / "services" / "static",
810
- project_root / "src" / "claude_mpm" / "services" / "socketio" / "static",
811
- project_root / "static",
812
- project_root / "src" / "static",
938
+ project_root
939
+ / "src"
940
+ / "claude_mpm"
941
+ / "dashboard"
942
+ / "static"
943
+ / "svelte-build",
944
+ project_root / "dashboard" / "static" / "svelte-build",
813
945
  # Package installation locations (fallback)
814
- Path(__file__).parent.parent / "static",
815
- Path(__file__).parent / "static",
946
+ Path(__file__).parent.parent.parent
947
+ / "dashboard"
948
+ / "static"
949
+ / "svelte-build",
816
950
  # Scripts directory (for standalone installations)
817
- get_scripts_dir() / "static",
818
- get_scripts_dir() / "socketio" / "static",
951
+ get_scripts_dir() / "dashboard" / "static" / "svelte-build",
819
952
  # Current working directory
820
- Path.cwd() / "static",
821
- Path.cwd() / "socketio" / "static",
953
+ Path.cwd() / "src" / "claude_mpm" / "dashboard" / "static" / "svelte-build",
954
+ Path.cwd() / "dashboard" / "static" / "svelte-build",
822
955
  ]
823
956
 
824
957
  # Filter out None values
825
958
  possible_paths = [p for p in possible_paths if p is not None]
826
959
  self.logger.debug(
827
- f"Searching {len(possible_paths)} possible static file locations"
960
+ f"Searching {len(possible_paths)} possible Svelte build locations"
828
961
  )
829
962
 
830
963
  for path in possible_paths:
831
- self.logger.debug(f"Checking for static files at: {path}")
964
+ self.logger.debug(f"Checking for Svelte build at: {path}")
832
965
  try:
833
966
  if path.exists() and path.is_dir():
834
- # Check if it contains expected files
967
+ # Check if it contains expected Svelte build files
835
968
  if (path / "index.html").exists():
836
- self.logger.info(f"✅ Found static files at: {path}")
969
+ self.logger.info(f"✅ Found Svelte build at: {path}")
837
970
  return path
838
971
  self.logger.debug(f"Directory exists but no index.html: {path}")
839
972
  else:
@@ -842,7 +975,7 @@ class SocketIOServerCore:
842
975
  self.logger.debug(f"Error checking path {path}: {e}")
843
976
 
844
977
  self.logger.warning(
845
- "⚠️ Static files not found - dashboard will not be available"
978
+ "⚠️ Svelte build not found - dashboard will not be available"
846
979
  )
847
980
  self.logger.debug(f"Searched paths: {[str(p) for p in possible_paths]}")
848
981
  return None
@@ -918,9 +1051,15 @@ class SocketIOServerCore:
918
1051
  },
919
1052
  }
920
1053
 
921
- # Add to event history if main server is available
1054
+ # Add to event history UNCONDITIONALLY
1055
+ # WHY: Heartbeat events should persist for new clients too
922
1056
  if self.main_server and hasattr(self.main_server, "event_history"):
923
1057
  self.main_server.event_history.append(heartbeat_data)
1058
+ self.logger.debug(
1059
+ f"Heartbeat added to history (total: {len(self.main_server.event_history)})"
1060
+ )
1061
+ else:
1062
+ self.logger.warning("event_history not initialized for heartbeat!")
924
1063
 
925
1064
  # Emit heartbeat to all connected clients (already using new schema)
926
1065
  await self.sio.emit("system_event", heartbeat_data)
@@ -81,6 +81,36 @@ API_KEY = "sk-1234567890abcdef" # In code! # pragma: allowlist secret
81
81
 
82
82
  # ✅ Safe: Use environment variables
83
83
  API_KEY = os.getenv("API_KEY")
84
+
85
+ # ❌ CRITICAL: MCP config files with API keys
86
+ # NEVER commit these files:
87
+ # - .mcp-vector-search/config.json (OpenRouter API keys)
88
+ # - .mcp/config.json (MCP server credentials)
89
+ # - openrouter.json, anthropic-config.json
90
+ # - credentials.json, secrets.json, api-keys.json
91
+
92
+ # ✅ Safe: Verify .gitignore before committing
93
+ # Check file is ignored: git check-ignore <file_path>
94
+ # Check file not tracked: git ls-files <file_path>
95
+ ```
96
+
97
+ **MCP Secret File Patterns (High Risk):**
98
+ ```bash
99
+ # Files that commonly contain API keys:
100
+ .mcp-vector-search/config.json # OpenRouter, OpenAI keys
101
+ .mcp/config.json # MCP server credentials
102
+ **/mcp-config.json
103
+ openrouter.json
104
+ anthropic-config.json
105
+ openai-config.json
106
+ credentials.json
107
+ secrets.json
108
+ api-keys.json
109
+
110
+ # ALWAYS add to .gitignore:
111
+ echo ".mcp-vector-search/" >> .gitignore
112
+ echo "credentials.json" >> .gitignore
113
+ echo "secrets.json" >> .gitignore
84
114
  ```
85
115
 
86
116
  ### 4. XML External Entities (XXE)
@@ -178,6 +208,88 @@ if failed_login_count > 5:
178
208
  alert_security_team()
179
209
  ```
180
210
 
211
+ ## Secret Detection and Prevention
212
+
213
+ ### Pre-commit Hooks with detect-secrets
214
+ ```bash
215
+ # Install detect-secrets
216
+ pip install detect-secrets
217
+
218
+ # Create baseline of existing secrets
219
+ detect-secrets scan > .secrets.baseline
220
+
221
+ # Install pre-commit hooks
222
+ pip install pre-commit
223
+ pre-commit install
224
+
225
+ # Add to .pre-commit-config.yaml:
226
+ # - repo: https://github.com/Yelp/detect-secrets
227
+ # rev: v1.5.0
228
+ # hooks:
229
+ # - id: detect-secrets
230
+ # args: ['--baseline', '.secrets.baseline']
231
+
232
+ # Scan for new secrets
233
+ detect-secrets scan --baseline .secrets.baseline
234
+
235
+ # Audit baseline (mark false positives)
236
+ detect-secrets audit .secrets.baseline
237
+ ```
238
+
239
+ ### Manual Secret Scanning
240
+ ```bash
241
+ # Check if file is ignored by git
242
+ git check-ignore .mcp-vector-search/config.json
243
+ # Exit code 0 = ignored (safe)
244
+ # Exit code 1 = NOT ignored (DANGER!)
245
+
246
+ # Check if file is tracked by git
247
+ git ls-files .mcp-vector-search/config.json
248
+ # Output present = tracked (CRITICAL - remove immediately!)
249
+ # No output = not tracked (safe if also in .gitignore)
250
+
251
+ # Search git history for committed secrets
252
+ git log --all --full-history -- .mcp-vector-search/config.json
253
+
254
+ # Remove file from git history (if accidentally committed)
255
+ git filter-branch --force --index-filter \
256
+ 'git rm --cached --ignore-unmatch .mcp-vector-search/config.json' \
257
+ --prune-empty --tag-name-filter cat -- --all
258
+ ```
259
+
260
+ ### Incident Response: Exposed API Key
261
+ If you've committed an API key to git:
262
+
263
+ 1. **IMMEDIATELY rotate the exposed credential**
264
+ - OpenRouter: https://openrouter.ai/settings/keys
265
+ - Anthropic: https://console.anthropic.com/settings/keys
266
+ - OpenAI: https://platform.openai.com/api-keys
267
+
268
+ 2. **Remove from git history**
269
+ ```bash
270
+ # Using git-filter-repo (recommended)
271
+ git filter-repo --path .mcp-vector-search/config.json --invert-paths
272
+
273
+ # Force push to remote (WARNING: destructive)
274
+ git push origin --force --all
275
+ git push origin --force --tags
276
+ ```
277
+
278
+ 3. **Add to .gitignore** (if not already there)
279
+ ```bash
280
+ echo ".mcp-vector-search/" >> .gitignore
281
+ git add .gitignore
282
+ git commit -m "chore: add MCP config to gitignore"
283
+ ```
284
+
285
+ 4. **Verify cleanup**
286
+ ```bash
287
+ git log --all --full-history -- .mcp-vector-search/config.json
288
+ # Should show no results
289
+ ```
290
+
291
+ 5. **Notify stakeholders** if the key had production access
292
+
181
293
  ## Security Scanning Tools
182
294
 
183
295
  ### Python