claude-mpm 5.4.3__py3-none-any.whl → 5.4.21__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 (90) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +4 -0
  3. claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +1 -1
  4. claude_mpm/agents/PM_INSTRUCTIONS.md +166 -21
  5. claude_mpm/agents/agent_loader.py +3 -27
  6. claude_mpm/cli/__main__.py +4 -0
  7. claude_mpm/cli/chrome_devtools_installer.py +175 -0
  8. claude_mpm/cli/commands/agents.py +0 -31
  9. claude_mpm/cli/commands/auto_configure.py +210 -25
  10. claude_mpm/cli/commands/config.py +88 -2
  11. claude_mpm/cli/commands/configure.py +85 -43
  12. claude_mpm/cli/commands/configure_agent_display.py +3 -1
  13. claude_mpm/cli/commands/mpm_init/core.py +2 -45
  14. claude_mpm/cli/commands/skills.py +214 -189
  15. claude_mpm/cli/executor.py +3 -3
  16. claude_mpm/cli/parsers/agents_parser.py +0 -9
  17. claude_mpm/cli/parsers/auto_configure_parser.py +0 -138
  18. claude_mpm/cli/parsers/config_parser.py +153 -83
  19. claude_mpm/cli/parsers/skills_parser.py +3 -2
  20. claude_mpm/cli/startup.py +490 -41
  21. claude_mpm/commands/mpm-config.md +265 -0
  22. claude_mpm/commands/mpm-help.md +14 -95
  23. claude_mpm/commands/mpm-organize.md +350 -153
  24. claude_mpm/core/framework/formatters/content_formatter.py +3 -13
  25. claude_mpm/core/framework_loader.py +4 -2
  26. claude_mpm/core/logger.py +13 -0
  27. claude_mpm/hooks/claude_hooks/event_handlers.py +176 -76
  28. claude_mpm/hooks/claude_hooks/hook_handler.py +2 -0
  29. claude_mpm/hooks/claude_hooks/installer.py +33 -10
  30. claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
  31. claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
  32. claude_mpm/hooks/memory_integration_hook.py +46 -1
  33. claude_mpm/init.py +0 -19
  34. claude_mpm/scripts/claude-hook-handler.sh +58 -18
  35. claude_mpm/scripts/start_activity_logging.py +0 -0
  36. claude_mpm/services/agents/agent_recommendation_service.py +6 -7
  37. claude_mpm/services/agents/agent_review_service.py +280 -0
  38. claude_mpm/services/agents/deployment/agent_discovery_service.py +2 -3
  39. claude_mpm/services/agents/deployment/agent_template_builder.py +1 -0
  40. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +78 -9
  41. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +13 -0
  42. claude_mpm/services/agents/git_source_manager.py +14 -0
  43. claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
  44. claude_mpm/services/agents/toolchain_detector.py +6 -3
  45. claude_mpm/services/command_deployment_service.py +81 -8
  46. claude_mpm/services/git/git_operations_service.py +93 -8
  47. claude_mpm/services/self_upgrade_service.py +120 -12
  48. claude_mpm/services/skills/__init__.py +3 -0
  49. claude_mpm/services/skills/git_skill_source_manager.py +32 -2
  50. claude_mpm/services/skills/selective_skill_deployer.py +704 -0
  51. claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
  52. claude_mpm/services/skills_deployer.py +126 -9
  53. {claude_mpm-5.4.3.dist-info → claude_mpm-5.4.21.dist-info}/METADATA +47 -8
  54. {claude_mpm-5.4.3.dist-info → claude_mpm-5.4.21.dist-info}/RECORD +58 -82
  55. {claude_mpm-5.4.3.dist-info → claude_mpm-5.4.21.dist-info}/entry_points.txt +0 -3
  56. claude_mpm-5.4.21.dist-info/licenses/LICENSE +94 -0
  57. claude_mpm-5.4.21.dist-info/licenses/LICENSE-FAQ.md +153 -0
  58. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
  59. claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
  60. claude_mpm/agents/BASE_ENGINEER.md +0 -658
  61. claude_mpm/agents/BASE_OPS.md +0 -219
  62. claude_mpm/agents/BASE_PM.md +0 -480
  63. claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
  64. claude_mpm/agents/BASE_QA.md +0 -167
  65. claude_mpm/agents/BASE_RESEARCH.md +0 -53
  66. claude_mpm/agents/base_agent.json +0 -31
  67. claude_mpm/agents/base_agent_loader.py +0 -601
  68. claude_mpm/cli/commands/agents_detect.py +0 -380
  69. claude_mpm/cli/commands/agents_recommend.py +0 -309
  70. claude_mpm/cli/ticket_cli.py +0 -35
  71. claude_mpm/commands/mpm-agents-auto-configure.md +0 -278
  72. claude_mpm/commands/mpm-agents-detect.md +0 -177
  73. claude_mpm/commands/mpm-agents-list.md +0 -131
  74. claude_mpm/commands/mpm-agents-recommend.md +0 -223
  75. claude_mpm/commands/mpm-config-view.md +0 -150
  76. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  77. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-313.pyc +0 -0
  78. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
  79. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
  80. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
  81. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
  82. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
  83. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
  84. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
  85. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
  86. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
  87. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
  88. claude_mpm-5.4.3.dist-info/licenses/LICENSE +0 -21
  89. {claude_mpm-5.4.3.dist-info → claude_mpm-5.4.21.dist-info}/WHEEL +0 -0
  90. {claude_mpm-5.4.3.dist-info → claude_mpm-5.4.21.dist-info}/top_level.txt +0 -0
@@ -100,19 +100,9 @@ class ContentFormatter:
100
100
  instructions += framework_content["actual_memories"]
101
101
  instructions += "\n"
102
102
 
103
- # Add agent memories if available
104
- if framework_content.get("agent_memories"):
105
- agent_memories = framework_content["agent_memories"]
106
- if agent_memories:
107
- instructions += "\n\n## Agent Memories\n\n"
108
- instructions += "**The following are accumulated memories from specialized agents:**\n\n"
109
-
110
- for agent_name in sorted(agent_memories.keys()):
111
- memory_content = agent_memories[agent_name]
112
- if memory_content:
113
- instructions += f"### {agent_name.replace('_', ' ').title()} Agent Memory\n\n"
114
- instructions += memory_content
115
- instructions += "\n\n"
103
+ # NOTE: Agent memories are now injected at agent deployment time
104
+ # in agent_template_builder.py, not in PM instructions.
105
+ # This ensures each agent gets its own memory, not all memories embedded in PM.
116
106
 
117
107
  # Add dynamic agent capabilities section
118
108
  instructions += capabilities_section
@@ -255,10 +255,12 @@ class FrameworkLoader:
255
255
  """Load actual memories using the MemoryManager service."""
256
256
  memories = self._memory_manager.load_memories()
257
257
 
258
+ # Only load PM memories (PM.md)
259
+ # Agent memories are loaded at deployment time in agent_template_builder.py
258
260
  if "actual_memories" in memories:
259
261
  content["actual_memories"] = memories["actual_memories"]
260
- if "agent_memories" in memories:
261
- content["agent_memories"] = memories["agent_memories"]
262
+ # NOTE: agent_memories are no longer loaded for PM instructions
263
+ # They are injected per-agent at deployment time
262
264
 
263
265
  # === Agent Discovery Methods ===
264
266
 
claude_mpm/core/logger.py CHANGED
@@ -175,6 +175,19 @@ def setup_logging(
175
175
  Returns:
176
176
  Configured logger
177
177
  """
178
+ # Detect deployment context for install-type-aware defaults
179
+ if level == "INFO": # Only override default, not explicit settings
180
+ from claude_mpm.core.unified_paths import DeploymentContext, PathContext
181
+
182
+ context = PathContext.detect_deployment_context()
183
+ if context in (
184
+ DeploymentContext.DEVELOPMENT,
185
+ DeploymentContext.EDITABLE_INSTALL,
186
+ ):
187
+ level = "INFO" # Development: verbose logging
188
+ else:
189
+ level = "OFF" # Production installs: silent by default
190
+
178
191
  logger = logging.getLogger(name)
179
192
 
180
193
  # Handle OFF level
@@ -95,22 +95,29 @@ class EventHandlers:
95
95
  }
96
96
 
97
97
  # Store prompt for comprehensive response tracking if enabled
98
- if (
99
- self.hook_handler.response_tracking_manager.response_tracking_enabled
100
- and self.hook_handler.response_tracking_manager.track_all_interactions
101
- ):
102
- session_id = event.get("session_id", "")
103
- if session_id:
104
- self.hook_handler.pending_prompts[session_id] = {
105
- "prompt": prompt,
106
- "timestamp": datetime.now(timezone.utc).isoformat(),
107
- "working_directory": working_dir,
108
- }
109
- if DEBUG:
110
- print(
111
- f"Stored prompt for comprehensive tracking: session {session_id[:8]}...",
112
- file=sys.stderr,
113
- )
98
+ try:
99
+ rtm = getattr(self.hook_handler, "response_tracking_manager", None)
100
+ if (
101
+ rtm
102
+ and getattr(rtm, "response_tracking_enabled", False)
103
+ and getattr(rtm, "track_all_interactions", False)
104
+ ):
105
+ session_id = event.get("session_id", "")
106
+ if session_id:
107
+ pending_prompts = getattr(self.hook_handler, "pending_prompts", {})
108
+ pending_prompts[session_id] = {
109
+ "prompt": prompt,
110
+ "timestamp": datetime.now(timezone.utc).isoformat(),
111
+ "working_directory": working_dir,
112
+ }
113
+ if DEBUG:
114
+ print(
115
+ f"Stored prompt for comprehensive tracking: session {session_id[:8]}...",
116
+ file=sys.stderr,
117
+ )
118
+ except Exception:
119
+ # Response tracking is optional - silently continue if it fails
120
+ pass
114
121
 
115
122
  # Emit normalized event (namespace no longer needed with normalized events)
116
123
  self.hook_handler._emit_socketio_event("", "user_prompt", prompt_data)
@@ -244,8 +251,11 @@ class EventHandlers:
244
251
  f" - Request data keys: {list(request_data.keys())}",
245
252
  file=sys.stderr,
246
253
  )
254
+ delegation_requests = getattr(
255
+ self.hook_handler, "delegation_requests", {}
256
+ )
247
257
  print(
248
- f" - delegation_requests size: {len(self.hook_handler.delegation_requests)}",
258
+ f" - delegation_requests size: {len(delegation_requests)}",
249
259
  file=sys.stderr,
250
260
  )
251
261
 
@@ -257,9 +267,13 @@ class EventHandlers:
257
267
  )
258
268
 
259
269
  # Trigger memory pre-delegation hook
260
- self.hook_handler.memory_hook_manager.trigger_pre_delegation_hook(
261
- agent_type, tool_input, session_id
262
- )
270
+ try:
271
+ mhm = getattr(self.hook_handler, "memory_hook_manager", None)
272
+ if mhm and hasattr(mhm, "trigger_pre_delegation_hook"):
273
+ mhm.trigger_pre_delegation_hook(agent_type, tool_input, session_id)
274
+ except Exception:
275
+ # Memory hooks are optional
276
+ pass
263
277
 
264
278
  # Emit a subagent_start event for better tracking
265
279
  subagent_start_data = {
@@ -441,6 +455,11 @@ class EventHandlers:
441
455
  ),
442
456
  }
443
457
 
458
+ # Include full output for file operations (Read, Edit, Write)
459
+ # so frontend can display file content
460
+ if tool_name in ("Read", "Edit", "Write", "Grep", "Glob") and "output" in event:
461
+ post_tool_data["output"] = event["output"]
462
+
444
463
  # Add correlation_id if available for correlation with pre_tool
445
464
  if tool_call_id:
446
465
  post_tool_data["correlation_id"] = tool_call_id
@@ -451,14 +470,27 @@ class EventHandlers:
451
470
  agent_type = self.hook_handler._get_delegation_agent_type(session_id)
452
471
 
453
472
  # Trigger memory post-delegation hook
454
- self.hook_handler.memory_hook_manager.trigger_post_delegation_hook(
455
- agent_type, event, session_id
456
- )
473
+ try:
474
+ mhm = getattr(self.hook_handler, "memory_hook_manager", None)
475
+ if mhm and hasattr(mhm, "trigger_post_delegation_hook"):
476
+ mhm.trigger_post_delegation_hook(agent_type, event, session_id)
477
+ except Exception:
478
+ # Memory hooks are optional
479
+ pass
457
480
 
458
481
  # Track agent response if response tracking is enabled
459
- self.hook_handler.response_tracking_manager.track_agent_response(
460
- session_id, agent_type, event, self.hook_handler.delegation_requests
461
- )
482
+ try:
483
+ rtm = getattr(self.hook_handler, "response_tracking_manager", None)
484
+ if rtm and hasattr(rtm, "track_agent_response"):
485
+ delegation_requests = getattr(
486
+ self.hook_handler, "delegation_requests", {}
487
+ )
488
+ rtm.track_agent_response(
489
+ session_id, agent_type, event, delegation_requests
490
+ )
491
+ except Exception:
492
+ # Response tracking is optional
493
+ pass
462
494
 
463
495
  self.hook_handler._emit_socketio_event("", "post_tool", post_tool_data)
464
496
 
@@ -519,9 +551,14 @@ class EventHandlers:
519
551
  self._log_stop_event_debug(event, session_id, metadata)
520
552
 
521
553
  # Track response if enabled
522
- self.hook_handler.response_tracking_manager.track_stop_response(
523
- event, session_id, metadata, self.hook_handler.pending_prompts
524
- )
554
+ try:
555
+ rtm = getattr(self.hook_handler, "response_tracking_manager", None)
556
+ if rtm and hasattr(rtm, "track_stop_response"):
557
+ pending_prompts = getattr(self.hook_handler, "pending_prompts", {})
558
+ rtm.track_stop_response(event, session_id, metadata, pending_prompts)
559
+ except Exception:
560
+ # Response tracking is optional
561
+ pass
525
562
 
526
563
  # Emit stop event to Socket.IO
527
564
  self._emit_stop_event(event, session_id, metadata)
@@ -544,15 +581,27 @@ class EventHandlers:
544
581
  self, event: dict, session_id: str, metadata: dict
545
582
  ) -> None:
546
583
  """Log debug information for stop events."""
584
+ try:
585
+ rtm = getattr(self.hook_handler, "response_tracking_manager", None)
586
+ tracking_enabled = (
587
+ getattr(rtm, "response_tracking_enabled", False) if rtm else False
588
+ )
589
+ tracker_exists = (
590
+ getattr(rtm, "response_tracker", None) is not None if rtm else False
591
+ )
592
+
593
+ print(
594
+ f" - response_tracking_enabled: {tracking_enabled}",
595
+ file=sys.stderr,
596
+ )
597
+ print(
598
+ f" - response_tracker exists: {tracker_exists}",
599
+ file=sys.stderr,
600
+ )
601
+ except Exception:
602
+ # If debug logging fails, just skip it
603
+ pass
547
604
 
548
- print(
549
- f" - response_tracking_enabled: {self.hook_handler.response_tracking_manager.response_tracking_enabled}",
550
- file=sys.stderr,
551
- )
552
- print(
553
- f" - response_tracker exists: {self.hook_handler.response_tracking_manager.response_tracker is not None}",
554
- file=sys.stderr,
555
- )
556
605
  print(
557
606
  f" - session_id: {session_id[:8] if session_id else 'None'}...",
558
607
  file=sys.stderr,
@@ -600,15 +649,22 @@ class EventHandlers:
600
649
  git_branch: str,
601
650
  ):
602
651
  """Handle response tracking for subagent stop events with fuzzy matching."""
603
- if not (
604
- self.hook_handler.response_tracking_manager.response_tracking_enabled
605
- and self.hook_handler.response_tracking_manager.response_tracker
606
- ):
652
+ try:
653
+ rtm = getattr(self.hook_handler, "response_tracking_manager", None)
654
+ if not (
655
+ rtm
656
+ and getattr(rtm, "response_tracking_enabled", False)
657
+ and getattr(rtm, "response_tracker", None)
658
+ ):
659
+ return
660
+ except Exception:
661
+ # Response tracking is optional
607
662
  return
608
663
 
609
664
  try:
610
665
  # Get the original request data (with fuzzy matching fallback)
611
- request_info = self.hook_handler.delegation_requests.get(session_id)
666
+ delegation_requests = getattr(self.hook_handler, "delegation_requests", {})
667
+ request_info = delegation_requests.get(session_id)
612
668
 
613
669
  # If exact match fails, try partial matching
614
670
  if not request_info and session_id:
@@ -618,7 +674,7 @@ class EventHandlers:
618
674
  file=sys.stderr,
619
675
  )
620
676
  # Try to find a session that matches the first 8-16 characters
621
- for stored_sid in list(self.hook_handler.delegation_requests.keys()):
677
+ for stored_sid in list(delegation_requests.keys()):
622
678
  if (
623
679
  stored_sid.startswith(session_id[:8])
624
680
  or session_id.startswith(stored_sid[:8])
@@ -633,17 +689,13 @@ class EventHandlers:
633
689
  f" - ✅ Fuzzy match found: {stored_sid[:16]}...",
634
690
  file=sys.stderr,
635
691
  )
636
- request_info = self.hook_handler.delegation_requests.get(
637
- stored_sid
638
- )
692
+ request_info = delegation_requests.get(stored_sid)
639
693
  # Update the key to use the current session_id for consistency
640
694
  if request_info:
641
- self.hook_handler.delegation_requests[session_id] = (
642
- request_info
643
- )
695
+ delegation_requests[session_id] = request_info
644
696
  # Optionally remove the old key to avoid duplicates
645
697
  if stored_sid != session_id:
646
- del self.hook_handler.delegation_requests[stored_sid]
698
+ del delegation_requests[stored_sid]
647
699
  break
648
700
 
649
701
  if request_info:
@@ -691,23 +743,31 @@ class EventHandlers:
691
743
  )
692
744
 
693
745
  # Track the response
694
- file_path = self.hook_handler.response_tracking_manager.response_tracker.track_response(
695
- agent_name=agent_type,
696
- request=full_request,
697
- response=response_text,
698
- session_id=session_id,
699
- metadata=metadata,
746
+ rtm = getattr(self.hook_handler, "response_tracking_manager", None)
747
+ response_tracker = (
748
+ getattr(rtm, "response_tracker", None) if rtm else None
700
749
  )
701
-
702
- if file_path and DEBUG:
703
- print(
704
- f"✅ Tracked {agent_type} agent response on SubagentStop: {file_path.name}",
705
- file=sys.stderr,
750
+ if response_tracker and hasattr(response_tracker, "track_response"):
751
+ file_path = response_tracker.track_response(
752
+ agent_name=agent_type,
753
+ request=full_request,
754
+ response=response_text,
755
+ session_id=session_id,
756
+ metadata=metadata,
706
757
  )
707
758
 
759
+ if file_path and DEBUG:
760
+ print(
761
+ f"✅ Tracked {agent_type} agent response on SubagentStop: {file_path.name}",
762
+ file=sys.stderr,
763
+ )
764
+
708
765
  # Clean up the request data
709
- if session_id in self.hook_handler.delegation_requests:
710
- del self.hook_handler.delegation_requests[session_id]
766
+ delegation_requests = getattr(
767
+ self.hook_handler, "delegation_requests", {}
768
+ )
769
+ if session_id in delegation_requests:
770
+ del delegation_requests[session_id]
711
771
 
712
772
  elif DEBUG:
713
773
  print(
@@ -731,9 +791,14 @@ class EventHandlers:
731
791
  - Essential for comprehensive monitoring of Claude interactions
732
792
  """
733
793
  # Track the response for logging
734
- self.hook_handler.response_tracking_manager.track_assistant_response(
735
- event, self.hook_handler.pending_prompts
736
- )
794
+ try:
795
+ rtm = getattr(self.hook_handler, "response_tracking_manager", None)
796
+ if rtm and hasattr(rtm, "track_assistant_response"):
797
+ pending_prompts = getattr(self.hook_handler, "pending_prompts", {})
798
+ rtm.track_assistant_response(event, pending_prompts)
799
+ except Exception:
800
+ # Response tracking is optional
801
+ pass
737
802
 
738
803
  # Get working directory and git branch
739
804
  working_dir = event.get("cwd", "")
@@ -763,16 +828,21 @@ class EventHandlers:
763
828
  }
764
829
 
765
830
  # Check if this is a response to a tracked prompt
766
- if session_id in self.hook_handler.pending_prompts:
767
- prompt_data = self.hook_handler.pending_prompts[session_id]
768
- assistant_response_data["original_prompt"] = prompt_data.get("prompt", "")[
769
- :200
770
- ]
771
- assistant_response_data["prompt_timestamp"] = prompt_data.get(
772
- "timestamp", ""
773
- )
774
- assistant_response_data["is_tracked_response"] = True
775
- else:
831
+ try:
832
+ pending_prompts = getattr(self.hook_handler, "pending_prompts", {})
833
+ if session_id in pending_prompts:
834
+ prompt_data = pending_prompts[session_id]
835
+ assistant_response_data["original_prompt"] = prompt_data.get(
836
+ "prompt", ""
837
+ )[:200]
838
+ assistant_response_data["prompt_timestamp"] = prompt_data.get(
839
+ "timestamp", ""
840
+ )
841
+ assistant_response_data["is_tracked_response"] = True
842
+ else:
843
+ assistant_response_data["is_tracked_response"] = False
844
+ except Exception:
845
+ # If prompt lookup fails, just mark as not tracked
776
846
  assistant_response_data["is_tracked_response"] = False
777
847
 
778
848
  # Debug logging
@@ -786,3 +856,33 @@ class EventHandlers:
786
856
  self.hook_handler._emit_socketio_event(
787
857
  "", "assistant_response", assistant_response_data
788
858
  )
859
+
860
+ def handle_session_start_fast(self, event):
861
+ """Handle session start events for tracking conversation sessions.
862
+
863
+ WHY track session starts:
864
+ - Provides visibility into new conversation sessions
865
+ - Enables tracking of session lifecycle and duration
866
+ - Useful for monitoring concurrent sessions and resource usage
867
+ """
868
+ session_id = event.get("session_id", "")
869
+ working_dir = event.get("cwd", "")
870
+ git_branch = self._get_git_branch(working_dir) if working_dir else "Unknown"
871
+
872
+ session_start_data = {
873
+ "session_id": session_id,
874
+ "working_directory": working_dir,
875
+ "git_branch": git_branch,
876
+ "timestamp": datetime.now(timezone.utc).isoformat(),
877
+ "hook_event_name": "SessionStart",
878
+ }
879
+
880
+ # Debug logging
881
+ if DEBUG:
882
+ print(
883
+ f"Hook handler: Processing SessionStart - session: '{session_id}'",
884
+ file=sys.stderr,
885
+ )
886
+
887
+ # Emit normalized event
888
+ self.hook_handler._emit_socketio_event("", "session_start", session_start_data)
@@ -417,6 +417,8 @@ class ClaudeHookHandler:
417
417
  "Notification": self.event_handlers.handle_notification_fast,
418
418
  "Stop": self.event_handlers.handle_stop_fast,
419
419
  "SubagentStop": self.event_handlers.handle_subagent_stop_fast,
420
+ "SubagentStart": self.event_handlers.handle_session_start_fast,
421
+ "SessionStart": self.event_handlers.handle_session_start_fast,
420
422
  "AssistantResponse": self.event_handlers.handle_assistant_response,
421
423
  }
422
424
 
@@ -202,8 +202,9 @@ main "$@"
202
202
  self.hooks_dir = self.claude_dir / "hooks" # Kept for backward compatibility
203
203
  # Use settings.json for hooks (Claude Code reads from this file)
204
204
  self.settings_file = self.claude_dir / "settings.json"
205
- # Keep reference to old file for migration
206
- self.old_settings_file = self.claude_dir / "settings.json"
205
+ # There is no legacy settings file - this was a bug where both pointed to same file
206
+ # Setting to None to disable cleanup that was deleting freshly installed hooks
207
+ self.old_settings_file = None
207
208
  self._claude_version: Optional[str] = None
208
209
  self._hook_script_path: Optional[Path] = None
209
210
 
@@ -462,7 +463,9 @@ main "$@"
462
463
 
463
464
  def _cleanup_old_settings(self) -> None:
464
465
  """Remove hooks from old settings.json file if present."""
465
- if not self.old_settings_file.exists():
466
+ # No-op: old_settings_file was pointing to same file as settings_file (bug)
467
+ # This was causing freshly installed hooks to be immediately deleted
468
+ if self.old_settings_file is None or not self.old_settings_file.exists():
466
469
  return
467
470
 
468
471
  try:
@@ -518,15 +521,31 @@ main "$@"
518
521
  }
519
522
  ]
520
523
 
521
- # Non-tool events don't need a matcher
522
- non_tool_events = ["UserPromptSubmit", "Stop", "SubagentStop", "SubagentStart"]
523
- for event_type in non_tool_events:
524
+ # Simple events (no subtypes, no matcher needed)
525
+ simple_events = ["Stop", "SubagentStop", "SubagentStart"]
526
+ for event_type in simple_events:
524
527
  settings["hooks"][event_type] = [
525
528
  {
526
529
  "hooks": [hook_command],
527
530
  }
528
531
  ]
529
532
 
533
+ # SessionStart needs matcher for subtypes (startup, resume)
534
+ settings["hooks"]["SessionStart"] = [
535
+ {
536
+ "matcher": "*", # Match all SessionStart subtypes
537
+ "hooks": [hook_command],
538
+ }
539
+ ]
540
+
541
+ # UserPromptSubmit needs matcher for potential subtypes
542
+ settings["hooks"]["UserPromptSubmit"] = [
543
+ {
544
+ "matcher": "*",
545
+ "hooks": [hook_command],
546
+ }
547
+ ]
548
+
530
549
  # Write settings to settings.json
531
550
  with self.settings_file.open("w") as f:
532
551
  json.dump(settings, f, indent=2)
@@ -650,9 +669,13 @@ main "$@"
650
669
  old_script.unlink()
651
670
  self.logger.info(f"Removed old deployed script: {old_script}")
652
671
 
653
- # Remove from Claude settings (both old and new locations)
654
- for settings_path in [self.settings_file, self.old_settings_file]:
655
- if settings_path.exists():
672
+ # Remove from Claude settings
673
+ settings_paths = [self.settings_file]
674
+ if self.old_settings_file is not None:
675
+ settings_paths.append(self.old_settings_file)
676
+
677
+ for settings_path in settings_paths:
678
+ if settings_path and settings_path.exists():
656
679
  with settings_path.open() as f:
657
680
  settings = json.load(f)
658
681
 
@@ -763,7 +786,7 @@ main "$@"
763
786
  pass
764
787
 
765
788
  # Also check old settings file
766
- if self.old_settings_file.exists():
789
+ if self.old_settings_file is not None and self.old_settings_file.exists():
767
790
  try:
768
791
  with self.old_settings_file.open() as f:
769
792
  old_settings = json.load(f)
@@ -6,18 +6,35 @@ including pre and post delegation hooks.
6
6
  """
7
7
 
8
8
  import logging
9
+ import os
9
10
  import sys
10
11
 
11
- # Reconfigure logging to INFO level BEFORE kuzu-memory imports
12
+ # Install-type-aware logging configuration BEFORE kuzu-memory imports
12
13
  # This overrides kuzu-memory's WARNING-level basicConfig (fixes 1M-445)
13
- logging.basicConfig(
14
- level=logging.INFO,
15
- format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
16
- force=True, # Python 3.8+ - reconfigures root logger
17
- stream=sys.stderr,
18
- )
19
-
20
- import os
14
+ # but respects production install silence
15
+ try:
16
+ from claude_mpm.core.unified_paths import DeploymentContext, PathContext
17
+
18
+ context = PathContext.detect_deployment_context()
19
+
20
+ # Only configure verbose logging for development/editable installs
21
+ # Production installs remain silent by default
22
+ if context in (DeploymentContext.DEVELOPMENT, DeploymentContext.EDITABLE_INSTALL):
23
+ logging.basicConfig(
24
+ level=logging.INFO,
25
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
26
+ force=True, # Python 3.8+ - reconfigures root logger
27
+ stream=sys.stderr,
28
+ )
29
+ except ImportError:
30
+ # Fallback: if unified_paths not available, configure logging
31
+ # This maintains backward compatibility
32
+ logging.basicConfig(
33
+ level=logging.INFO,
34
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
35
+ force=True,
36
+ stream=sys.stderr,
37
+ )
21
38
  from datetime import datetime, timezone
22
39
  from typing import Optional
23
40
 
@@ -18,8 +18,7 @@ DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "true").lower() != "false"
18
18
 
19
19
  # Response tracking integration
20
20
  # NOTE: ResponseTracker import moved to _initialize_response_tracking() for lazy loading
21
- # This prevents unnecessary import of base_agent_loader and other heavy dependencies
22
- # when hooks don't need response tracking
21
+ # This prevents unnecessary import of heavy dependencies when hooks don't need response tracking
23
22
  RESPONSE_TRACKING_AVAILABLE = (
24
23
  True # Assume available, will check on actual initialization
25
24
  )
@@ -51,7 +50,7 @@ class ResponseTrackingManager:
51
50
  response tracking without code changes.
52
51
 
53
52
  NOTE: ResponseTracker is imported lazily here to avoid loading
54
- base_agent_loader and other heavy dependencies unless actually needed.
53
+ heavy dependencies unless actually needed.
55
54
  """
56
55
  try:
57
56
  # Lazy import of ResponseTracker to avoid unnecessary dependency loading
@@ -13,6 +13,7 @@ agent outputs because:
13
13
  """
14
14
 
15
15
  import re
16
+ from datetime import datetime
16
17
  from typing import Dict, List
17
18
 
18
19
  from claude_mpm.core.config import Config
@@ -47,6 +48,16 @@ except ImportError as e:
47
48
  SOCKETIO_AVAILABLE = False
48
49
  get_socketio_server = None
49
50
 
51
+ # Try to import event bus with fallback handling
52
+ try:
53
+ from claude_mpm.services.event_bus.event_bus import EventBus
54
+
55
+ EVENT_BUS_AVAILABLE = True
56
+ except ImportError as e:
57
+ logger.debug(f"EventBus not available: {e}")
58
+ EVENT_BUS_AVAILABLE = False
59
+ EventBus = None
60
+
50
61
 
51
62
  class MemoryPreDelegationHook(PreDelegationHook):
52
63
  """Inject agent memory into delegation context.
@@ -83,6 +94,16 @@ class MemoryPreDelegationHook(PreDelegationHook):
83
94
  logger.info("Memory manager not available - hook will be inactive")
84
95
  self.memory_manager = None
85
96
 
97
+ # Initialize event bus for observability
98
+ if EVENT_BUS_AVAILABLE and EventBus:
99
+ try:
100
+ self.event_bus = EventBus.get_instance()
101
+ except Exception as e:
102
+ logger.debug(f"Failed to get EventBus instance: {e}")
103
+ self.event_bus = None
104
+ else:
105
+ self.event_bus = None
106
+
86
107
  def execute(self, context: HookContext) -> HookResult:
87
108
  """Add agent memory to delegation context.
88
109
 
@@ -137,7 +158,31 @@ INSTRUCTIONS: Review your memory above before proceeding. Apply learned patterns
137
158
 
138
159
  logger.info(f"Injected memory for agent '{agent_id}'")
139
160
 
140
- # Emit Socket.IO event for memory injected
161
+ # Calculate memory size for observability
162
+ memory_size = len(memory_content)
163
+
164
+ # Emit event bus event for observability
165
+ if self.event_bus:
166
+ try:
167
+ # Determine memory source (project or user level)
168
+ # This is inferred from the memory manager's behavior
169
+ memory_source = (
170
+ "runtime" # Runtime loading from memory manager
171
+ )
172
+
173
+ self.event_bus.publish(
174
+ "agent.memory.loaded",
175
+ {
176
+ "agent_id": agent_id,
177
+ "memory_source": memory_source,
178
+ "memory_size": memory_size,
179
+ "timestamp": datetime.now(datetime.UTC).isoformat(),
180
+ },
181
+ )
182
+ except Exception as event_error:
183
+ logger.debug(f"EventBus publish failed: {event_error}")
184
+
185
+ # Emit Socket.IO event for memory injected (legacy compatibility)
141
186
  try:
142
187
  socketio_server = get_socketio_server()
143
188
  # Calculate size of injected content