claude-mpm 5.4.96__py3-none-any.whl → 5.6.17__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 (214) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/{CLAUDE_MPM_FOUNDERS_OUTPUT_STYLE.md → CLAUDE_MPM_RESEARCH_OUTPUT_STYLE.md} +14 -6
  3. claude_mpm/agents/PM_INSTRUCTIONS.md +44 -10
  4. claude_mpm/agents/WORKFLOW.md +2 -0
  5. claude_mpm/agents/templates/circuit-breakers.md +26 -17
  6. claude_mpm/cli/commands/autotodos.py +45 -5
  7. claude_mpm/cli/commands/commander.py +216 -0
  8. claude_mpm/cli/commands/hook_errors.py +60 -60
  9. claude_mpm/cli/commands/run.py +35 -3
  10. claude_mpm/cli/commands/skill_source.py +51 -2
  11. claude_mpm/cli/commands/skills.py +5 -3
  12. claude_mpm/cli/executor.py +32 -17
  13. claude_mpm/cli/parsers/base_parser.py +17 -0
  14. claude_mpm/cli/parsers/commander_parser.py +116 -0
  15. claude_mpm/cli/parsers/run_parser.py +10 -0
  16. claude_mpm/cli/parsers/skill_source_parser.py +4 -0
  17. claude_mpm/cli/parsers/skills_parser.py +5 -0
  18. claude_mpm/cli/startup.py +124 -3
  19. claude_mpm/cli/startup_display.py +2 -1
  20. claude_mpm/cli/utils.py +7 -3
  21. claude_mpm/commander/__init__.py +78 -0
  22. claude_mpm/commander/adapters/__init__.py +60 -0
  23. claude_mpm/commander/adapters/auggie.py +260 -0
  24. claude_mpm/commander/adapters/base.py +288 -0
  25. claude_mpm/commander/adapters/claude_code.py +392 -0
  26. claude_mpm/commander/adapters/codex.py +237 -0
  27. claude_mpm/commander/adapters/communication.py +366 -0
  28. claude_mpm/commander/adapters/example_usage.py +310 -0
  29. claude_mpm/commander/adapters/mpm.py +389 -0
  30. claude_mpm/commander/adapters/registry.py +204 -0
  31. claude_mpm/commander/api/__init__.py +16 -0
  32. claude_mpm/commander/api/app.py +121 -0
  33. claude_mpm/commander/api/errors.py +133 -0
  34. claude_mpm/commander/api/routes/__init__.py +8 -0
  35. claude_mpm/commander/api/routes/events.py +184 -0
  36. claude_mpm/commander/api/routes/inbox.py +171 -0
  37. claude_mpm/commander/api/routes/messages.py +148 -0
  38. claude_mpm/commander/api/routes/projects.py +271 -0
  39. claude_mpm/commander/api/routes/sessions.py +226 -0
  40. claude_mpm/commander/api/routes/work.py +296 -0
  41. claude_mpm/commander/api/schemas.py +186 -0
  42. claude_mpm/commander/chat/__init__.py +7 -0
  43. claude_mpm/commander/chat/cli.py +111 -0
  44. claude_mpm/commander/chat/commands.py +96 -0
  45. claude_mpm/commander/chat/repl.py +310 -0
  46. claude_mpm/commander/config.py +49 -0
  47. claude_mpm/commander/config_loader.py +115 -0
  48. claude_mpm/commander/core/__init__.py +10 -0
  49. claude_mpm/commander/core/block_manager.py +325 -0
  50. claude_mpm/commander/core/response_manager.py +323 -0
  51. claude_mpm/commander/daemon.py +594 -0
  52. claude_mpm/commander/env_loader.py +59 -0
  53. claude_mpm/commander/events/__init__.py +26 -0
  54. claude_mpm/commander/events/manager.py +332 -0
  55. claude_mpm/commander/frameworks/__init__.py +12 -0
  56. claude_mpm/commander/frameworks/base.py +143 -0
  57. claude_mpm/commander/frameworks/claude_code.py +58 -0
  58. claude_mpm/commander/frameworks/mpm.py +62 -0
  59. claude_mpm/commander/inbox/__init__.py +16 -0
  60. claude_mpm/commander/inbox/dedup.py +128 -0
  61. claude_mpm/commander/inbox/inbox.py +224 -0
  62. claude_mpm/commander/inbox/models.py +70 -0
  63. claude_mpm/commander/instance_manager.py +337 -0
  64. claude_mpm/commander/llm/__init__.py +6 -0
  65. claude_mpm/commander/llm/openrouter_client.py +167 -0
  66. claude_mpm/commander/llm/summarizer.py +70 -0
  67. claude_mpm/commander/memory/__init__.py +45 -0
  68. claude_mpm/commander/memory/compression.py +347 -0
  69. claude_mpm/commander/memory/embeddings.py +230 -0
  70. claude_mpm/commander/memory/entities.py +310 -0
  71. claude_mpm/commander/memory/example_usage.py +290 -0
  72. claude_mpm/commander/memory/integration.py +325 -0
  73. claude_mpm/commander/memory/search.py +381 -0
  74. claude_mpm/commander/memory/store.py +657 -0
  75. claude_mpm/commander/models/__init__.py +18 -0
  76. claude_mpm/commander/models/events.py +121 -0
  77. claude_mpm/commander/models/project.py +162 -0
  78. claude_mpm/commander/models/work.py +214 -0
  79. claude_mpm/commander/parsing/__init__.py +20 -0
  80. claude_mpm/commander/parsing/extractor.py +132 -0
  81. claude_mpm/commander/parsing/output_parser.py +270 -0
  82. claude_mpm/commander/parsing/patterns.py +100 -0
  83. claude_mpm/commander/persistence/__init__.py +11 -0
  84. claude_mpm/commander/persistence/event_store.py +274 -0
  85. claude_mpm/commander/persistence/state_store.py +309 -0
  86. claude_mpm/commander/persistence/work_store.py +164 -0
  87. claude_mpm/commander/polling/__init__.py +13 -0
  88. claude_mpm/commander/polling/event_detector.py +104 -0
  89. claude_mpm/commander/polling/output_buffer.py +49 -0
  90. claude_mpm/commander/polling/output_poller.py +153 -0
  91. claude_mpm/commander/project_session.py +268 -0
  92. claude_mpm/commander/proxy/__init__.py +12 -0
  93. claude_mpm/commander/proxy/formatter.py +89 -0
  94. claude_mpm/commander/proxy/output_handler.py +191 -0
  95. claude_mpm/commander/proxy/relay.py +155 -0
  96. claude_mpm/commander/registry.py +410 -0
  97. claude_mpm/commander/runtime/__init__.py +10 -0
  98. claude_mpm/commander/runtime/executor.py +191 -0
  99. claude_mpm/commander/runtime/monitor.py +346 -0
  100. claude_mpm/commander/session/__init__.py +6 -0
  101. claude_mpm/commander/session/context.py +81 -0
  102. claude_mpm/commander/session/manager.py +59 -0
  103. claude_mpm/commander/tmux_orchestrator.py +361 -0
  104. claude_mpm/commander/web/__init__.py +1 -0
  105. claude_mpm/commander/work/__init__.py +30 -0
  106. claude_mpm/commander/work/executor.py +207 -0
  107. claude_mpm/commander/work/queue.py +405 -0
  108. claude_mpm/commander/workflow/__init__.py +27 -0
  109. claude_mpm/commander/workflow/event_handler.py +241 -0
  110. claude_mpm/commander/workflow/notifier.py +146 -0
  111. claude_mpm/commands/mpm-config.md +8 -0
  112. claude_mpm/commands/mpm-doctor.md +8 -0
  113. claude_mpm/commands/mpm-help.md +8 -0
  114. claude_mpm/commands/mpm-init.md +8 -0
  115. claude_mpm/commands/mpm-monitor.md +8 -0
  116. claude_mpm/commands/mpm-organize.md +8 -0
  117. claude_mpm/commands/mpm-postmortem.md +8 -0
  118. claude_mpm/commands/mpm-session-resume.md +8 -0
  119. claude_mpm/commands/mpm-status.md +8 -0
  120. claude_mpm/commands/mpm-ticket-view.md +8 -0
  121. claude_mpm/commands/mpm-version.md +8 -0
  122. claude_mpm/commands/mpm.md +8 -0
  123. claude_mpm/config/agent_presets.py +8 -7
  124. claude_mpm/config/skill_sources.py +16 -0
  125. claude_mpm/core/claude_runner.py +143 -0
  126. claude_mpm/core/config.py +32 -19
  127. claude_mpm/core/logger.py +26 -9
  128. claude_mpm/core/logging_utils.py +35 -11
  129. claude_mpm/core/output_style_manager.py +49 -12
  130. claude_mpm/core/unified_config.py +10 -6
  131. claude_mpm/core/unified_paths.py +68 -80
  132. claude_mpm/experimental/cli_enhancements.py +2 -1
  133. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-312.pyc +0 -0
  134. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-314.pyc +0 -0
  135. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
  136. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-312.pyc +0 -0
  137. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-314.pyc +0 -0
  138. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  139. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-312.pyc +0 -0
  140. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-314.pyc +0 -0
  141. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  142. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-312.pyc +0 -0
  143. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-314.pyc +0 -0
  144. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
  145. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-314.pyc +0 -0
  146. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
  147. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-312.pyc +0 -0
  148. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-314.pyc +0 -0
  149. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  150. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-312.pyc +0 -0
  151. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-314.pyc +0 -0
  152. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-312.pyc +0 -0
  153. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-314.pyc +0 -0
  154. claude_mpm/hooks/claude_hooks/auto_pause_handler.py +29 -30
  155. claude_mpm/hooks/claude_hooks/event_handlers.py +112 -99
  156. claude_mpm/hooks/claude_hooks/hook_handler.py +81 -88
  157. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
  158. claude_mpm/hooks/claude_hooks/installer.py +116 -8
  159. claude_mpm/hooks/claude_hooks/memory_integration.py +51 -31
  160. claude_mpm/hooks/claude_hooks/response_tracking.py +39 -58
  161. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-312.pyc +0 -0
  162. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-314.pyc +0 -0
  163. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
  164. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  165. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-312.pyc +0 -0
  166. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-314.pyc +0 -0
  167. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-312.pyc +0 -0
  168. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-314.pyc +0 -0
  169. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  170. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-312.pyc +0 -0
  171. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-314.pyc +0 -0
  172. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  173. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-312.pyc +0 -0
  174. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-314.pyc +0 -0
  175. claude_mpm/hooks/claude_hooks/services/connection_manager.py +23 -28
  176. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +36 -103
  177. claude_mpm/hooks/claude_hooks/services/state_manager.py +23 -36
  178. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +47 -73
  179. claude_mpm/hooks/session_resume_hook.py +22 -18
  180. claude_mpm/hooks/templates/pre_tool_use_template.py +10 -2
  181. claude_mpm/scripts/claude-hook-handler.sh +43 -16
  182. claude_mpm/scripts/start_activity_logging.py +0 -0
  183. claude_mpm/services/agents/agent_recommendation_service.py +8 -8
  184. claude_mpm/services/agents/agent_selection_service.py +2 -2
  185. claude_mpm/services/agents/loading/framework_agent_loader.py +75 -2
  186. claude_mpm/services/agents/single_tier_deployment_service.py +4 -4
  187. claude_mpm/services/event_log.py +8 -0
  188. claude_mpm/services/pm_skills_deployer.py +84 -6
  189. claude_mpm/services/skills/git_skill_source_manager.py +130 -10
  190. claude_mpm/services/skills/selective_skill_deployer.py +28 -0
  191. claude_mpm/services/skills/skill_discovery_service.py +74 -4
  192. claude_mpm/services/skills_deployer.py +31 -5
  193. claude_mpm/skills/__init__.py +2 -1
  194. claude_mpm/skills/bundled/pm/mpm/SKILL.md +38 -0
  195. claude_mpm/skills/bundled/pm/mpm-config/SKILL.md +29 -0
  196. claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
  197. claude_mpm/skills/bundled/pm/mpm-help/SKILL.md +35 -0
  198. claude_mpm/skills/bundled/pm/mpm-init/SKILL.md +125 -0
  199. claude_mpm/skills/bundled/pm/mpm-monitor/SKILL.md +32 -0
  200. claude_mpm/skills/bundled/pm/mpm-organize/SKILL.md +121 -0
  201. claude_mpm/skills/bundled/pm/mpm-postmortem/SKILL.md +22 -0
  202. claude_mpm/skills/bundled/pm/mpm-session-pause/SKILL.md +170 -0
  203. claude_mpm/skills/bundled/pm/mpm-session-resume/SKILL.md +31 -0
  204. claude_mpm/skills/bundled/pm/mpm-status/SKILL.md +37 -0
  205. claude_mpm/skills/bundled/pm/mpm-ticket-view/SKILL.md +110 -0
  206. claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
  207. claude_mpm/skills/registry.py +295 -90
  208. {claude_mpm-5.4.96.dist-info → claude_mpm-5.6.17.dist-info}/METADATA +22 -6
  209. {claude_mpm-5.4.96.dist-info → claude_mpm-5.6.17.dist-info}/RECORD +213 -83
  210. {claude_mpm-5.4.96.dist-info → claude_mpm-5.6.17.dist-info}/WHEEL +0 -0
  211. {claude_mpm-5.4.96.dist-info → claude_mpm-5.6.17.dist-info}/entry_points.txt +0 -0
  212. {claude_mpm-5.4.96.dist-info → claude_mpm-5.6.17.dist-info}/licenses/LICENSE +0 -0
  213. {claude_mpm-5.4.96.dist-info → claude_mpm-5.6.17.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  214. {claude_mpm-5.4.96.dist-info → claude_mpm-5.6.17.dist-info}/top_level.txt +0 -0
@@ -22,7 +22,7 @@ USAGE:
22
22
  threshold_crossed = auto_pause.on_usage_update(metadata["usage"])
23
23
  if threshold_crossed:
24
24
  warning = auto_pause.emit_threshold_warning(threshold_crossed)
25
- print(f"\n⚠️ {warning}", file=sys.stderr)
25
+ _log(f"\n⚠️ {warning}")
26
26
 
27
27
  # Record actions during pause mode
28
28
  if auto_pause.is_pause_active():
@@ -34,7 +34,6 @@ USAGE:
34
34
  """
35
35
 
36
36
  import os
37
- import sys
38
37
  from datetime import datetime, timezone
39
38
  from pathlib import Path
40
39
  from typing import Any, Dict, Optional
@@ -45,6 +44,15 @@ from claude_mpm.services.infrastructure.context_usage_tracker import (
45
44
  ContextUsageTracker,
46
45
  )
47
46
 
47
+ # Try to import _log from hook_handler, fall back to no-op
48
+ try:
49
+ from claude_mpm.hooks.claude_hooks.hook_handler import _log
50
+ except ImportError:
51
+
52
+ def _log(msg: str) -> None:
53
+ pass # Silent fallback
54
+
55
+
48
56
  logger = get_logger(__name__)
49
57
 
50
58
  # Debug mode
@@ -100,11 +108,10 @@ class AutoPauseHandler:
100
108
  self._previous_threshold = current_state.threshold_reached
101
109
 
102
110
  if DEBUG:
103
- print(
111
+ _log(
104
112
  f"AutoPauseHandler initialized: "
105
113
  f"{current_state.percentage_used:.1f}% context used, "
106
- f"threshold: {current_state.threshold_reached}",
107
- file=sys.stderr,
114
+ f"threshold: {current_state.threshold_reached}"
108
115
  )
109
116
  except Exception as e:
110
117
  logger.error(f"Failed to initialize AutoPauseHandler: {e}")
@@ -169,10 +176,9 @@ class AutoPauseHandler:
169
176
  self._previous_threshold = current_threshold
170
177
 
171
178
  if DEBUG:
172
- print(
179
+ _log(
173
180
  f"Context threshold crossed: {current_threshold} "
174
- f"({state.percentage_used:.1f}%)",
175
- file=sys.stderr,
181
+ f"({state.percentage_used:.1f}%)"
176
182
  )
177
183
 
178
184
  # Trigger auto-pause if threshold reached
@@ -184,7 +190,7 @@ class AutoPauseHandler:
184
190
  except Exception as e:
185
191
  logger.error(f"Failed to update usage: {e}")
186
192
  if DEBUG:
187
- print(f"❌ Usage update failed: {e}", file=sys.stderr)
193
+ _log(f"❌ Usage update failed: {e}")
188
194
  # Don't propagate error - auto-pause is optional
189
195
  return None
190
196
 
@@ -220,12 +226,12 @@ class AutoPauseHandler:
220
226
  )
221
227
 
222
228
  if DEBUG:
223
- print(f"Recorded tool call during pause: {tool_name}", file=sys.stderr)
229
+ _log(f"Recorded tool call during pause: {tool_name}")
224
230
 
225
231
  except Exception as e:
226
232
  logger.error(f"Failed to record tool call: {e}")
227
233
  if DEBUG:
228
- print(f"❌ Failed to record tool call: {e}", file=sys.stderr)
234
+ _log(f"❌ Failed to record tool call: {e}")
229
235
 
230
236
  def on_assistant_response(self, response_summary: str) -> None:
231
237
  """Record an assistant response if auto-pause is active.
@@ -257,15 +263,14 @@ class AutoPauseHandler:
257
263
  )
258
264
 
259
265
  if DEBUG:
260
- print(
261
- f"Recorded assistant response during pause (length: {len(summary)})",
262
- file=sys.stderr,
266
+ _log(
267
+ f"Recorded assistant response during pause (length: {len(summary)})"
263
268
  )
264
269
 
265
270
  except Exception as e:
266
271
  logger.error(f"Failed to record assistant response: {e}")
267
272
  if DEBUG:
268
- print(f"❌ Failed to record assistant response: {e}", file=sys.stderr)
273
+ _log(f"❌ Failed to record assistant response: {e}")
269
274
 
270
275
  def on_user_message(self, message_summary: str) -> None:
271
276
  """Record a user message if auto-pause is active.
@@ -297,15 +302,12 @@ class AutoPauseHandler:
297
302
  )
298
303
 
299
304
  if DEBUG:
300
- print(
301
- f"Recorded user message during pause (length: {len(summary)})",
302
- file=sys.stderr,
303
- )
305
+ _log(f"Recorded user message during pause (length: {len(summary)})")
304
306
 
305
307
  except Exception as e:
306
308
  logger.error(f"Failed to record user message: {e}")
307
309
  if DEBUG:
308
- print(f"❌ Failed to record user message: {e}", file=sys.stderr)
310
+ _log(f"❌ Failed to record user message: {e}")
309
311
 
310
312
  def on_session_end(self) -> Optional[Path]:
311
313
  """Called when session ends. Finalizes any active pause.
@@ -318,7 +320,7 @@ class AutoPauseHandler:
318
320
  """
319
321
  if not self.is_pause_active():
320
322
  if DEBUG:
321
- print("No active pause to finalize", file=sys.stderr)
323
+ _log("No active pause to finalize")
322
324
  return None
323
325
 
324
326
  try:
@@ -326,14 +328,14 @@ class AutoPauseHandler:
326
328
  session_path = self.pause_manager.finalize_pause(create_full_snapshot=True)
327
329
 
328
330
  if session_path and DEBUG:
329
- print(f"✅ Session finalized: {session_path.name}", file=sys.stderr)
331
+ _log(f"✅ Session finalized: {session_path.name}")
330
332
 
331
333
  return session_path
332
334
 
333
335
  except Exception as e:
334
336
  logger.error(f"Failed to finalize pause session: {e}")
335
337
  if DEBUG:
336
- print(f"❌ Failed to finalize pause: {e}", file=sys.stderr)
338
+ _log(f"❌ Failed to finalize pause: {e}")
337
339
  raise
338
340
 
339
341
  def is_pause_active(self) -> bool:
@@ -417,9 +419,7 @@ class AutoPauseHandler:
417
419
  # Check if pause is already active
418
420
  if self.is_pause_active():
419
421
  if DEBUG:
420
- print(
421
- "Auto-pause already active, skipping trigger", file=sys.stderr
422
- )
422
+ _log("Auto-pause already active, skipping trigger")
423
423
  return
424
424
 
425
425
  # Start incremental pause
@@ -429,16 +429,15 @@ class AutoPauseHandler:
429
429
  )
430
430
 
431
431
  if DEBUG:
432
- print(
432
+ _log(
433
433
  f"✅ Auto-pause triggered: {session_id} "
434
- f"({state.percentage_used:.1f}% context used)",
435
- file=sys.stderr,
434
+ f"({state.percentage_used:.1f}% context used)"
436
435
  )
437
436
 
438
437
  except Exception as e:
439
438
  logger.error(f"Failed to trigger auto-pause: {e}")
440
439
  if DEBUG:
441
- print(f"❌ Failed to trigger auto-pause: {e}", file=sys.stderr)
440
+ _log(f"❌ Failed to trigger auto-pause: {e}")
442
441
  # Don't propagate - auto-pause is optional
443
442
 
444
443
  def _summarize_dict(
@@ -8,12 +8,20 @@ Claude Code hook events.
8
8
  import os
9
9
  import re
10
10
  import subprocess # nosec B404 - subprocess used for safe claude CLI version checking only
11
- import sys
12
11
  import uuid
13
12
  from datetime import datetime, timezone
14
13
  from pathlib import Path
15
14
  from typing import Optional
16
15
 
16
+ # Import _log helper to avoid stderr writes (which cause hook errors)
17
+ try:
18
+ from .hook_handler import _log
19
+ except ImportError:
20
+ # Fallback for direct execution
21
+ def _log(message: str) -> None:
22
+ """Fallback logger when hook_handler not available."""
23
+
24
+
17
25
  # Import tool analysis with fallback for direct execution
18
26
  try:
19
27
  # Try relative import first (when imported as module)
@@ -34,8 +42,8 @@ except ImportError:
34
42
  extract_tool_results,
35
43
  )
36
44
 
37
- # Debug mode
38
- DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "true").lower() != "false"
45
+ # Debug mode - MUST match hook_handler.py default (false) to prevent stderr writes
46
+ DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "false").lower() == "true"
39
47
 
40
48
  # Import constants for configuration
41
49
  try:
@@ -111,14 +119,22 @@ class EventHandlers:
111
119
  "working_directory": working_dir,
112
120
  }
113
121
  if DEBUG:
114
- print(
115
- f"Stored prompt for comprehensive tracking: session {session_id[:8]}...",
116
- file=sys.stderr,
122
+ _log(
123
+ f"Stored prompt for comprehensive tracking: session {session_id[:8]}..."
117
124
  )
118
125
  except Exception: # nosec B110
119
126
  # Response tracking is optional - silently continue if it fails
120
127
  pass
121
128
 
129
+ # Record user message for auto-pause if active
130
+ auto_pause = getattr(self.hook_handler, "auto_pause_handler", None)
131
+ if auto_pause and auto_pause.is_pause_active():
132
+ try:
133
+ auto_pause.on_user_message(prompt)
134
+ except Exception as e:
135
+ if DEBUG:
136
+ _log(f"Auto-pause user message recording error: {e}")
137
+
122
138
  # Emit normalized event (namespace no longer needed with normalized events)
123
139
  self.hook_handler._emit_socketio_event("", "user_prompt", prompt_data)
124
140
 
@@ -133,11 +149,8 @@ class EventHandlers:
133
149
  # Enhanced debug logging for session correlation
134
150
  session_id = event.get("session_id", "")
135
151
  if DEBUG:
136
- print(
137
- f" - session_id: {session_id[:16] if session_id else 'None'}...",
138
- file=sys.stderr,
139
- )
140
- print(f" - event keys: {list(event.keys())}", file=sys.stderr)
152
+ _log(f" - session_id: {session_id[:16] if session_id else 'None'}...")
153
+ _log(f" - event keys: {list(event.keys())}")
141
154
 
142
155
  tool_name = event.get("tool_name", "")
143
156
  tool_input = event.get("tool_input", {})
@@ -180,9 +193,8 @@ class EventHandlers:
180
193
 
181
194
  CorrelationManager.store(session_id, tool_call_id, tool_name)
182
195
  if DEBUG:
183
- print(
184
- f" - Generated tool_call_id: {tool_call_id[:8]}... for session {session_id[:8]}...",
185
- file=sys.stderr,
196
+ _log(
197
+ f" - Generated tool_call_id: {tool_call_id[:8]}... for session {session_id[:8]}..."
186
198
  )
187
199
 
188
200
  # Add delegation-specific data if this is a Task tool
@@ -196,7 +208,7 @@ class EventHandlers:
196
208
  auto_pause.on_tool_call(tool_name, tool_input)
197
209
  except Exception as e:
198
210
  if DEBUG:
199
- print(f"Auto-pause tool recording error: {e}", file=sys.stderr)
211
+ _log(f"Auto-pause tool recording error: {e}")
200
212
 
201
213
  self.hook_handler._emit_socketio_event("", "pre_tool", pre_tool_data)
202
214
 
@@ -212,9 +224,8 @@ class EventHandlers:
212
224
  }
213
225
  self.hook_handler._emit_socketio_event("", "todo_updated", todo_data)
214
226
  if DEBUG:
215
- print(
216
- f" - Emitted todo_updated event with {len(tool_params['todos'])} todos for session {session_id[:8]}...",
217
- file=sys.stderr,
227
+ _log(
228
+ f" - Emitted todo_updated event with {len(tool_params['todos'])} todos for session {session_id[:8]}..."
218
229
  )
219
230
 
220
231
  def _handle_task_delegation(
@@ -255,12 +266,9 @@ class EventHandlers:
255
266
 
256
267
  # Track this delegation for SubagentStop correlation and response tracking
257
268
  if DEBUG:
258
- print(
259
- f" - session_id: {session_id[:16] if session_id else 'None'}...",
260
- file=sys.stderr,
261
- )
262
- print(f" - agent_type: {agent_type}", file=sys.stderr)
263
- print(f" - raw_agent_type: {raw_agent_type}", file=sys.stderr)
269
+ _log(f" - session_id: {session_id[:16] if session_id else 'None'}...")
270
+ _log(f" - agent_type: {agent_type}")
271
+ _log(f" - raw_agent_type: {raw_agent_type}")
264
272
 
265
273
  if session_id and agent_type != "unknown":
266
274
  # Prepare request data for response tracking correlation
@@ -272,24 +280,17 @@ class EventHandlers:
272
280
  self.hook_handler._track_delegation(session_id, agent_type, request_data)
273
281
 
274
282
  if DEBUG:
275
- print(" - Delegation tracked successfully", file=sys.stderr)
276
- print(
277
- f" - Request data keys: {list(request_data.keys())}",
278
- file=sys.stderr,
279
- )
283
+ _log(" - Delegation tracked successfully")
284
+ _log(f" - Request data keys: {list(request_data.keys())}")
280
285
  delegation_requests = getattr(
281
286
  self.hook_handler, "delegation_requests", {}
282
287
  )
283
- print(
284
- f" - delegation_requests size: {len(delegation_requests)}",
285
- file=sys.stderr,
286
- )
288
+ _log(f" - delegation_requests size: {len(delegation_requests)}")
287
289
 
288
290
  # Log important delegations for debugging
289
291
  if DEBUG or agent_type in ["research", "engineer", "qa", "documentation"]:
290
- print(
291
- f"Hook handler: Task delegation started - agent: '{agent_type}', session: '{session_id}'",
292
- file=sys.stderr,
292
+ _log(
293
+ f"Hook handler: Task delegation started - agent: '{agent_type}', session: '{session_id}'"
293
294
  )
294
295
 
295
296
  # Trigger memory pre-delegation hook
@@ -359,10 +360,10 @@ class EventHandlers:
359
360
  )
360
361
 
361
362
  if DEBUG:
362
- print(f" - Agent prompt logged for {agent_type}", file=sys.stderr)
363
+ _log(f" - Agent prompt logged for {agent_type}")
363
364
  except Exception as e:
364
365
  if DEBUG:
365
- print(f" - Could not log agent prompt: {e}", file=sys.stderr)
366
+ _log(f" - Could not log agent prompt: {e}")
366
367
 
367
368
  def _get_git_branch(self, working_dir: Optional[str] = None) -> str:
368
369
  """Get git branch for the given directory with caching."""
@@ -450,9 +451,8 @@ class EventHandlers:
450
451
 
451
452
  tool_call_id = CorrelationManager.retrieve(session_id) if session_id else None
452
453
  if DEBUG and tool_call_id:
453
- print(
454
- f" - Retrieved tool_call_id: {tool_call_id[:8]}... for session {session_id[:8]}...",
455
- file=sys.stderr,
454
+ _log(
455
+ f" - Retrieved tool_call_id: {tool_call_id[:8]}... for session {session_id[:8]}..."
456
456
  )
457
457
 
458
458
  post_tool_data = {
@@ -598,19 +598,32 @@ class EventHandlers:
598
598
  threshold_crossed = auto_pause.on_usage_update(metadata["usage"])
599
599
  if threshold_crossed:
600
600
  warning = auto_pause.emit_threshold_warning(threshold_crossed)
601
- print(f"\n⚠️ {warning}", file=sys.stderr)
601
+ # CRITICAL: Never write to stderr unconditionally - causes hook errors
602
+ # Use _log() instead which only writes to file if DEBUG=true
603
+ from . import _log
604
+
605
+ _log(f"⚠️ Auto-pause threshold crossed: {warning}")
602
606
 
603
607
  if DEBUG:
604
- print(
605
- f" - Auto-pause threshold crossed: {threshold_crossed}",
606
- file=sys.stderr,
608
+ _log(
609
+ f" - Auto-pause threshold crossed: {threshold_crossed}"
607
610
  )
608
611
  except Exception as e:
609
612
  if DEBUG:
610
- print(
611
- f"Auto-pause error in handle_stop_fast: {e}",
612
- file=sys.stderr,
613
- )
613
+ _log(f"Auto-pause error in handle_stop_fast: {e}")
614
+
615
+ # Finalize pause session if active
616
+ try:
617
+ if auto_pause.is_pause_active():
618
+ session_file = auto_pause.on_session_end()
619
+ if session_file:
620
+ if DEBUG:
621
+ _log(
622
+ f"✅ Auto-pause session finalized: {session_file.name}"
623
+ )
624
+ except Exception as e:
625
+ if DEBUG:
626
+ _log(f"❌ Failed to finalize auto-pause session: {e}")
614
627
 
615
628
  # Track response if enabled
616
629
  try:
@@ -652,24 +665,15 @@ class EventHandlers:
652
665
  getattr(rtm, "response_tracker", None) is not None if rtm else False
653
666
  )
654
667
 
655
- print(
656
- f" - response_tracking_enabled: {tracking_enabled}",
657
- file=sys.stderr,
658
- )
659
- print(
660
- f" - response_tracker exists: {tracker_exists}",
661
- file=sys.stderr,
662
- )
668
+ _log(f" - response_tracking_enabled: {tracking_enabled}")
669
+ _log(f" - response_tracker exists: {tracker_exists}")
663
670
  except Exception: # nosec B110
664
671
  # If debug logging fails, just skip it
665
672
  pass
666
673
 
667
- print(
668
- f" - session_id: {session_id[:8] if session_id else 'None'}...",
669
- file=sys.stderr,
670
- )
671
- print(f" - reason: {metadata['reason']}", file=sys.stderr)
672
- print(f" - stop_type: {metadata['stop_type']}", file=sys.stderr)
674
+ _log(f" - session_id: {session_id[:8] if session_id else 'None'}...")
675
+ _log(f" - reason: {metadata['reason']}")
676
+ _log(f" - stop_type: {metadata['stop_type']}")
673
677
 
674
678
  def _emit_stop_event(self, event: dict, session_id: str, metadata: dict) -> None:
675
679
  """Emit stop event data to Socket.IO."""
@@ -731,10 +735,7 @@ class EventHandlers:
731
735
  # If exact match fails, try partial matching
732
736
  if not request_info and session_id:
733
737
  if DEBUG:
734
- print(
735
- f" - Trying fuzzy match for session {session_id[:16]}...",
736
- file=sys.stderr,
737
- )
738
+ _log(f" - Trying fuzzy match for session {session_id[:16]}...")
738
739
  # Try to find a session that matches the first 8-16 characters
739
740
  for stored_sid in list(delegation_requests.keys()):
740
741
  if (
@@ -747,10 +748,7 @@ class EventHandlers:
747
748
  )
748
749
  ):
749
750
  if DEBUG:
750
- print(
751
- f" - ✅ Fuzzy match found: {stored_sid[:16]}...",
752
- file=sys.stderr,
753
- )
751
+ _log(f" - ✅ Fuzzy match found: {stored_sid[:16]}...")
754
752
  request_info = delegation_requests.get(stored_sid) # nosec B113
755
753
  # Update the key to use the current session_id for consistency
756
754
  if request_info:
@@ -819,9 +817,8 @@ class EventHandlers:
819
817
  )
820
818
 
821
819
  if file_path and DEBUG:
822
- print(
823
- f"✅ Tracked {agent_type} agent response on SubagentStop: {file_path.name}",
824
- file=sys.stderr,
820
+ _log(
821
+ f"✅ Tracked {agent_type} agent response on SubagentStop: {file_path.name}"
825
822
  )
826
823
 
827
824
  # Clean up the request data
@@ -832,16 +829,13 @@ class EventHandlers:
832
829
  del delegation_requests[session_id]
833
830
 
834
831
  elif DEBUG:
835
- print(
836
- f"No request data for SubagentStop session {session_id[:8]}..., agent: {agent_type}",
837
- file=sys.stderr,
832
+ _log(
833
+ f"No request data for SubagentStop session {session_id[:8]}..., agent: {agent_type}"
838
834
  )
839
835
 
840
836
  except Exception as e:
841
837
  if DEBUG:
842
- print(
843
- f"❌ Failed to track response on SubagentStop: {e}", file=sys.stderr
844
- )
838
+ _log(f"❌ Failed to track response on SubagentStop: {e}")
845
839
 
846
840
  def handle_assistant_response(self, event):
847
841
  """Handle assistant response events for comprehensive response tracking.
@@ -868,7 +862,7 @@ class EventHandlers:
868
862
  self._scan_for_delegation_patterns(event)
869
863
  except Exception as e: # nosec B110
870
864
  if DEBUG:
871
- print(f"Delegation scanning error: {e}", file=sys.stderr)
865
+ _log(f"Delegation scanning error: {e}")
872
866
 
873
867
  # Get working directory and git branch
874
868
  working_dir = event.get("cwd", "")
@@ -917,9 +911,8 @@ class EventHandlers:
917
911
 
918
912
  # Debug logging
919
913
  if DEBUG:
920
- print(
921
- f"Hook handler: Processing AssistantResponse - session: '{session_id}', response_length: {len(response_text)}",
922
- file=sys.stderr,
914
+ _log(
915
+ f"Hook handler: Processing AssistantResponse - session: '{session_id}', response_length: {len(response_text)}"
923
916
  )
924
917
 
925
918
  # Record assistant response for auto-pause if active
@@ -935,7 +928,7 @@ class EventHandlers:
935
928
  auto_pause.on_assistant_response(summary)
936
929
  except Exception as e:
937
930
  if DEBUG:
938
- print(f"Auto-pause response recording error: {e}", file=sys.stderr)
931
+ _log(f"Auto-pause response recording error: {e}")
939
932
 
940
933
  # Emit normalized event
941
934
  self.hook_handler._emit_socketio_event(
@@ -949,6 +942,7 @@ class EventHandlers:
949
942
  - Provides visibility into new conversation sessions
950
943
  - Enables tracking of session lifecycle and duration
951
944
  - Useful for monitoring concurrent sessions and resource usage
945
+ - Auto-inject pending autotodos if enabled in config
952
946
  """
953
947
  session_id = event.get("session_id", "")
954
948
  working_dir = event.get("cwd", "")
@@ -962,12 +956,36 @@ class EventHandlers:
962
956
  "hook_event_name": "SessionStart",
963
957
  }
964
958
 
959
+ # Auto-inject pending autotodos if enabled
960
+ try:
961
+ from pathlib import Path
962
+
963
+ from claude_mpm.cli.commands.autotodos import get_pending_todos
964
+ from claude_mpm.core.config import Config
965
+
966
+ config = Config()
967
+ auto_inject_enabled = config.get("autotodos.auto_inject_on_startup", True)
968
+ max_todos = config.get("autotodos.max_todos_per_session", 10)
969
+
970
+ if auto_inject_enabled:
971
+ # Pass working directory from event to avoid Path.cwd() issues
972
+ working_dir_param = None
973
+ if working_dir:
974
+ working_dir_param = Path(working_dir)
975
+
976
+ pending_todos = get_pending_todos(
977
+ max_todos=max_todos, working_dir=working_dir_param
978
+ )
979
+ if pending_todos:
980
+ session_start_data["pending_autotodos"] = pending_todos
981
+ session_start_data["autotodos_count"] = len(pending_todos)
982
+ _log(f" - Auto-injected {len(pending_todos)} pending autotodos")
983
+ except Exception as e: # nosec B110
984
+ # Auto-injection is optional - continue if it fails
985
+ _log(f" - Failed to auto-inject autotodos: {e}")
986
+
965
987
  # Debug logging
966
- if DEBUG:
967
- print(
968
- f"Hook handler: Processing SessionStart - session: '{session_id}'",
969
- file=sys.stderr,
970
- )
988
+ _log(f"Hook handler: Processing SessionStart - session: '{session_id}'")
971
989
 
972
990
  # Emit normalized event
973
991
  self.hook_handler._emit_socketio_event("", "session_start", session_start_data)
@@ -1010,10 +1028,8 @@ class EventHandlers:
1010
1028
 
1011
1029
  # Debug logging
1012
1030
  if DEBUG:
1013
- print(
1014
- f"Hook handler: SubagentStart - agent_type='{agent_type}', "
1015
- f"agent_id='{agent_id}', session_id='{session_id[:16]}...'",
1016
- file=sys.stderr,
1031
+ _log(
1032
+ f"Hook handler: SubagentStart - agent_type='{agent_type}', agent_id='{agent_id}', session_id='{session_id[:16]}...'"
1017
1033
  )
1018
1034
 
1019
1035
  # Emit to /hook namespace as subagent_start (NOT session_start!)
@@ -1048,7 +1064,7 @@ class EventHandlers:
1048
1064
  from claude_mpm.services.event_log import get_event_log
1049
1065
  except ImportError:
1050
1066
  if DEBUG:
1051
- print("Delegation detector or event log not available", file=sys.stderr)
1067
+ _log("Delegation detector or event log not available")
1052
1068
  return
1053
1069
 
1054
1070
  response_text = event.get("response", "")
@@ -1087,7 +1103,4 @@ class EventHandlers:
1087
1103
  )
1088
1104
 
1089
1105
  if DEBUG:
1090
- print(
1091
- f"⚠️ PM violation detected: {detection['original_text'][:60]}...",
1092
- file=sys.stderr,
1093
- )
1106
+ _log(f"⚠️ PM violation detected: {detection['original_text'][:60]}...")