claude-mpm 5.4.85__py3-none-any.whl → 5.6.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +8 -5
  3. claude_mpm/agents/{CLAUDE_MPM_FOUNDERS_OUTPUT_STYLE.md → CLAUDE_MPM_RESEARCH_OUTPUT_STYLE.md} +14 -6
  4. claude_mpm/agents/PM_INSTRUCTIONS.md +101 -703
  5. claude_mpm/agents/WORKFLOW.md +2 -0
  6. claude_mpm/agents/templates/circuit-breakers.md +26 -17
  7. claude_mpm/cli/commands/autotodos.py +566 -0
  8. claude_mpm/cli/commands/commander.py +46 -0
  9. claude_mpm/cli/commands/hook_errors.py +60 -60
  10. claude_mpm/cli/commands/monitor.py +2 -2
  11. claude_mpm/cli/commands/mpm_init/core.py +2 -2
  12. claude_mpm/cli/commands/run.py +35 -3
  13. claude_mpm/cli/executor.py +119 -16
  14. claude_mpm/cli/parsers/base_parser.py +71 -1
  15. claude_mpm/cli/parsers/commander_parser.py +83 -0
  16. claude_mpm/cli/parsers/run_parser.py +10 -0
  17. claude_mpm/cli/startup.py +54 -16
  18. claude_mpm/cli/startup_display.py +72 -5
  19. claude_mpm/cli/startup_logging.py +2 -2
  20. claude_mpm/cli/utils.py +7 -3
  21. claude_mpm/commander/__init__.py +72 -0
  22. claude_mpm/commander/adapters/__init__.py +31 -0
  23. claude_mpm/commander/adapters/base.py +191 -0
  24. claude_mpm/commander/adapters/claude_code.py +361 -0
  25. claude_mpm/commander/adapters/communication.py +366 -0
  26. claude_mpm/commander/api/__init__.py +16 -0
  27. claude_mpm/commander/api/app.py +105 -0
  28. claude_mpm/commander/api/errors.py +112 -0
  29. claude_mpm/commander/api/routes/__init__.py +8 -0
  30. claude_mpm/commander/api/routes/events.py +184 -0
  31. claude_mpm/commander/api/routes/inbox.py +171 -0
  32. claude_mpm/commander/api/routes/messages.py +148 -0
  33. claude_mpm/commander/api/routes/projects.py +271 -0
  34. claude_mpm/commander/api/routes/sessions.py +215 -0
  35. claude_mpm/commander/api/routes/work.py +260 -0
  36. claude_mpm/commander/api/schemas.py +182 -0
  37. claude_mpm/commander/chat/__init__.py +7 -0
  38. claude_mpm/commander/chat/cli.py +107 -0
  39. claude_mpm/commander/chat/commands.py +96 -0
  40. claude_mpm/commander/chat/repl.py +310 -0
  41. claude_mpm/commander/config.py +49 -0
  42. claude_mpm/commander/config_loader.py +115 -0
  43. claude_mpm/commander/daemon.py +398 -0
  44. claude_mpm/commander/events/__init__.py +26 -0
  45. claude_mpm/commander/events/manager.py +332 -0
  46. claude_mpm/commander/frameworks/__init__.py +12 -0
  47. claude_mpm/commander/frameworks/base.py +143 -0
  48. claude_mpm/commander/frameworks/claude_code.py +58 -0
  49. claude_mpm/commander/frameworks/mpm.py +62 -0
  50. claude_mpm/commander/inbox/__init__.py +16 -0
  51. claude_mpm/commander/inbox/dedup.py +128 -0
  52. claude_mpm/commander/inbox/inbox.py +224 -0
  53. claude_mpm/commander/inbox/models.py +70 -0
  54. claude_mpm/commander/instance_manager.py +337 -0
  55. claude_mpm/commander/llm/__init__.py +6 -0
  56. claude_mpm/commander/llm/openrouter_client.py +167 -0
  57. claude_mpm/commander/llm/summarizer.py +70 -0
  58. claude_mpm/commander/models/__init__.py +18 -0
  59. claude_mpm/commander/models/events.py +121 -0
  60. claude_mpm/commander/models/project.py +162 -0
  61. claude_mpm/commander/models/work.py +214 -0
  62. claude_mpm/commander/parsing/__init__.py +20 -0
  63. claude_mpm/commander/parsing/extractor.py +132 -0
  64. claude_mpm/commander/parsing/output_parser.py +270 -0
  65. claude_mpm/commander/parsing/patterns.py +100 -0
  66. claude_mpm/commander/persistence/__init__.py +11 -0
  67. claude_mpm/commander/persistence/event_store.py +274 -0
  68. claude_mpm/commander/persistence/state_store.py +309 -0
  69. claude_mpm/commander/persistence/work_store.py +164 -0
  70. claude_mpm/commander/polling/__init__.py +13 -0
  71. claude_mpm/commander/polling/event_detector.py +104 -0
  72. claude_mpm/commander/polling/output_buffer.py +49 -0
  73. claude_mpm/commander/polling/output_poller.py +153 -0
  74. claude_mpm/commander/project_session.py +268 -0
  75. claude_mpm/commander/proxy/__init__.py +12 -0
  76. claude_mpm/commander/proxy/formatter.py +89 -0
  77. claude_mpm/commander/proxy/output_handler.py +191 -0
  78. claude_mpm/commander/proxy/relay.py +155 -0
  79. claude_mpm/commander/registry.py +404 -0
  80. claude_mpm/commander/runtime/__init__.py +10 -0
  81. claude_mpm/commander/runtime/executor.py +191 -0
  82. claude_mpm/commander/runtime/monitor.py +316 -0
  83. claude_mpm/commander/session/__init__.py +6 -0
  84. claude_mpm/commander/session/context.py +81 -0
  85. claude_mpm/commander/session/manager.py +59 -0
  86. claude_mpm/commander/tmux_orchestrator.py +361 -0
  87. claude_mpm/commander/web/__init__.py +1 -0
  88. claude_mpm/commander/work/__init__.py +30 -0
  89. claude_mpm/commander/work/executor.py +189 -0
  90. claude_mpm/commander/work/queue.py +405 -0
  91. claude_mpm/commander/workflow/__init__.py +27 -0
  92. claude_mpm/commander/workflow/event_handler.py +219 -0
  93. claude_mpm/commander/workflow/notifier.py +146 -0
  94. claude_mpm/commands/mpm-config.md +8 -0
  95. claude_mpm/commands/mpm-doctor.md +8 -0
  96. claude_mpm/commands/mpm-help.md +8 -0
  97. claude_mpm/commands/mpm-init.md +8 -0
  98. claude_mpm/commands/mpm-monitor.md +8 -0
  99. claude_mpm/commands/mpm-organize.md +8 -0
  100. claude_mpm/commands/mpm-postmortem.md +8 -0
  101. claude_mpm/commands/mpm-session-resume.md +9 -1
  102. claude_mpm/commands/mpm-status.md +8 -0
  103. claude_mpm/commands/mpm-ticket-view.md +8 -0
  104. claude_mpm/commands/mpm-version.md +8 -0
  105. claude_mpm/commands/mpm.md +8 -0
  106. claude_mpm/config/agent_presets.py +8 -7
  107. claude_mpm/core/config.py +5 -0
  108. claude_mpm/core/hook_manager.py +51 -3
  109. claude_mpm/core/logger.py +10 -7
  110. claude_mpm/core/logging_utils.py +4 -2
  111. claude_mpm/core/output_style_manager.py +15 -5
  112. claude_mpm/core/unified_config.py +10 -6
  113. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.C33zOoyM.css +1 -0
  114. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.CW1J-YuA.css +1 -0
  115. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cs_tUR18.js → 1WZnGYqX.js} +1 -1
  116. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CDuw-vjf.js → 67pF3qNn.js} +1 -1
  117. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bTOqqlTd.js → 6RxdMKe4.js} +1 -1
  118. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DwBR2MJi.js → 8cZrfX0h.js} +1 -1
  119. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{ZGh7QtNv.js → 9a6T2nm-.js} +1 -1
  120. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D9lljYKQ.js → B443AUzu.js} +1 -1
  121. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{RJiighC3.js → B8AwtY2H.js} +1 -1
  122. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{uuIeMWc-.js → BF15LAsF.js} +1 -1
  123. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D3k0OPJN.js → BRcwIQNr.js} +1 -1
  124. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CyWMqx4W.js → BV6nKitt.js} +1 -1
  125. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CiIAseT4.js → BViJ8lZt.js} +5 -5
  126. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CBBdVcY8.js → BcQ-Q0FE.js} +1 -1
  127. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BovzEFCE.js → Bpyvgze_.js} +1 -1
  128. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BzTRqg-z.js +1 -0
  129. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C0Fr8dve.js +1 -0
  130. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{eNVUfhuA.js → C3rbW_a-.js} +1 -1
  131. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{GYwsonyD.js → C8WYN38h.js} +1 -1
  132. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BIF9m_hv.js → C9I8FlXH.js} +1 -1
  133. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B0uc0UOD.js → CIQcWgO2.js} +3 -3
  134. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Be7GpZd6.js → CIctN7YN.js} +1 -1
  135. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Bh0LDWpI.js → CKrS_JZW.js} +2 -2
  136. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DUrLdbGD.js → CR6P9C4A.js} +1 -1
  137. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7xVLGWV.js → CRRR9MD_.js} +1 -1
  138. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
  139. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dhb8PKl3.js → CSXtMOf0.js} +1 -1
  140. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BPYeabCQ.js → CT-sbxSk.js} +1 -1
  141. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{sQeU3Y1z.js → CWm6DJsp.js} +1 -1
  142. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CnA0NrzZ.js → CpqQ1Kzn.js} +1 -1
  143. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4B-KCzX.js → D2nGpDRe.js} +1 -1
  144. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DGkLK5U1.js → D9iCMida.js} +1 -1
  145. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BofRWZRR.js → D9ykgMoY.js} +1 -1
  146. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DmxopI1J.js → DL2Ldur1.js} +1 -1
  147. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C30mlcqg.js → DPfltzjH.js} +1 -1
  148. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Vzk33B_K.js → DR8nis88.js} +2 -2
  149. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DI7hHRFL.js → DUliQN2b.js} +1 -1
  150. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4JcI4KD.js → DXlhR01x.js} +1 -1
  151. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bT1r9zLR.js → D_lyTybS.js} +1 -1
  152. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DZX00Y4g.js → DngoTTgh.js} +1 -1
  153. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzZX-COe.js → DqkmHtDC.js} +1 -1
  154. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7RN905-.js → DsDh8EYs.js} +1 -1
  155. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DLVjFsZ3.js → DypDmXgd.js} +1 -1
  156. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{iEWssX7S.js → IPYC-LnN.js} +1 -1
  157. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JTLiF7dt.js +24 -0
  158. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DaimHw_p.js → JpevfAFt.js} +1 -1
  159. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DY1XQ8fi.js → R8CEIRAd.js} +1 -1
  160. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dle-35c7.js → Zxy7qc-l.js} +2 -2
  161. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
  162. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C_Usid8X.js → qtd3IeO4.js} +2 -2
  163. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzeYkLYB.js → ulBFON_C.js} +2 -2
  164. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cfqx1Qun.js → wQVh1CoA.js} +1 -1
  165. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/{app.D6-I5TpK.js → app.Dr7t0z2J.js} +2 -2
  166. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
  167. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.m1gL8KXf.js → 0.RgBboRvH.js} +1 -1
  168. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{1.CgNOuw-d.js → 1.DG-KkbDf.js} +1 -1
  169. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.D_jnf-x6.js +1 -0
  170. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -1
  171. claude_mpm/dashboard/static/svelte-build/index.html +9 -9
  172. claude_mpm/experimental/cli_enhancements.py +2 -1
  173. claude_mpm/hooks/claude_hooks/INTEGRATION_EXAMPLE.md +243 -0
  174. claude_mpm/hooks/claude_hooks/README_AUTO_PAUSE.md +403 -0
  175. claude_mpm/hooks/claude_hooks/auto_pause_handler.py +486 -0
  176. claude_mpm/hooks/claude_hooks/event_handlers.py +250 -11
  177. claude_mpm/hooks/claude_hooks/hook_handler.py +106 -89
  178. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
  179. claude_mpm/hooks/claude_hooks/installer.py +69 -5
  180. claude_mpm/hooks/claude_hooks/response_tracking.py +3 -1
  181. claude_mpm/hooks/claude_hooks/services/connection_manager.py +20 -0
  182. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +14 -77
  183. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +30 -6
  184. claude_mpm/hooks/session_resume_hook.py +85 -1
  185. claude_mpm/init.py +1 -1
  186. claude_mpm/scripts/claude-hook-handler.sh +36 -10
  187. claude_mpm/services/agents/agent_recommendation_service.py +8 -8
  188. claude_mpm/services/agents/cache_git_manager.py +1 -1
  189. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +3 -0
  190. claude_mpm/services/agents/loading/framework_agent_loader.py +75 -2
  191. claude_mpm/services/cli/__init__.py +3 -0
  192. claude_mpm/services/cli/incremental_pause_manager.py +561 -0
  193. claude_mpm/services/cli/session_resume_helper.py +10 -2
  194. claude_mpm/services/delegation_detector.py +175 -0
  195. claude_mpm/services/diagnostics/checks/agent_sources_check.py +30 -0
  196. claude_mpm/services/diagnostics/checks/configuration_check.py +24 -0
  197. claude_mpm/services/diagnostics/checks/installation_check.py +22 -0
  198. claude_mpm/services/diagnostics/checks/mcp_services_check.py +23 -0
  199. claude_mpm/services/diagnostics/doctor_reporter.py +31 -1
  200. claude_mpm/services/diagnostics/models.py +14 -1
  201. claude_mpm/services/event_log.py +325 -0
  202. claude_mpm/services/infrastructure/__init__.py +4 -0
  203. claude_mpm/services/infrastructure/context_usage_tracker.py +291 -0
  204. claude_mpm/services/infrastructure/resume_log_generator.py +24 -5
  205. claude_mpm/services/monitor/daemon_manager.py +15 -4
  206. claude_mpm/services/monitor/management/lifecycle.py +8 -2
  207. claude_mpm/services/monitor/server.py +106 -16
  208. claude_mpm/services/pm_skills_deployer.py +259 -87
  209. claude_mpm/services/skills/git_skill_source_manager.py +51 -2
  210. claude_mpm/services/skills/selective_skill_deployer.py +114 -16
  211. claude_mpm/services/skills/skill_discovery_service.py +57 -3
  212. claude_mpm/services/socketio/handlers/hook.py +14 -7
  213. claude_mpm/services/socketio/server/main.py +12 -4
  214. claude_mpm/skills/bundled/pm/mpm/SKILL.md +38 -0
  215. claude_mpm/skills/bundled/pm/mpm-agent-update-workflow/SKILL.md +75 -0
  216. claude_mpm/skills/bundled/pm/mpm-circuit-breaker-enforcement/SKILL.md +476 -0
  217. claude_mpm/skills/bundled/pm/mpm-config/SKILL.md +29 -0
  218. claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
  219. claude_mpm/skills/bundled/pm/mpm-help/SKILL.md +35 -0
  220. claude_mpm/skills/bundled/pm/mpm-init/SKILL.md +125 -0
  221. claude_mpm/skills/bundled/pm/mpm-monitor/SKILL.md +32 -0
  222. claude_mpm/skills/bundled/pm/mpm-organize/SKILL.md +121 -0
  223. claude_mpm/skills/bundled/pm/mpm-postmortem/SKILL.md +22 -0
  224. claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
  225. claude_mpm/skills/bundled/pm/mpm-session-resume/SKILL.md +31 -0
  226. claude_mpm/skills/bundled/pm/mpm-status/SKILL.md +37 -0
  227. claude_mpm/skills/bundled/pm/{pm-teaching-mode → mpm-teaching-mode}/SKILL.md +2 -2
  228. claude_mpm/skills/bundled/pm/mpm-ticket-view/SKILL.md +110 -0
  229. claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
  230. claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
  231. claude_mpm/skills/skill_manager.py +4 -4
  232. claude_mpm-5.6.1.dist-info/METADATA +391 -0
  233. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/RECORD +244 -145
  234. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +0 -1
  235. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +0 -1
  236. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +0 -1
  237. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +0 -24
  238. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +0 -1
  239. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +0 -1
  240. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +0 -323
  241. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +0 -1
  242. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +0 -1
  243. claude_mpm-5.4.85.dist-info/METADATA +0 -1023
  244. /claude_mpm/skills/bundled/pm/{pm-bug-reporting/pm-bug-reporting.md → mpm-bug-reporting/SKILL.md} +0 -0
  245. /claude_mpm/skills/bundled/pm/{pm-delegation-patterns → mpm-delegation-patterns}/SKILL.md +0 -0
  246. /claude_mpm/skills/bundled/pm/{pm-git-file-tracking → mpm-git-file-tracking}/SKILL.md +0 -0
  247. /claude_mpm/skills/bundled/pm/{pm-pr-workflow → mpm-pr-workflow}/SKILL.md +0 -0
  248. /claude_mpm/skills/bundled/pm/{pm-ticketing-integration → mpm-ticketing-integration}/SKILL.md +0 -0
  249. /claude_mpm/skills/bundled/pm/{pm-verification-protocols → mpm-verification-protocols}/SKILL.md +0 -0
  250. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/WHEEL +0 -0
  251. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/entry_points.txt +0 -0
  252. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/licenses/LICENSE +0 -0
  253. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  254. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/top_level.txt +0 -0
@@ -22,7 +22,7 @@ import os
22
22
  import re
23
23
  import select
24
24
  import signal
25
- import subprocess
25
+ import subprocess # nosec B404
26
26
  import sys
27
27
  import threading
28
28
  from datetime import datetime, timezone
@@ -31,6 +31,7 @@ from typing import Optional, Tuple
31
31
  # Import extracted modules with fallback for direct execution
32
32
  try:
33
33
  # Try relative imports first (when imported as module)
34
+ from .auto_pause_handler import AutoPauseHandler
34
35
  from .event_handlers import EventHandlers
35
36
  from .memory_integration import MemoryHookManager
36
37
  from .response_tracking import ResponseTrackingManager
@@ -47,6 +48,7 @@ except ImportError:
47
48
  # Add parent directory to path
48
49
  sys.path.insert(0, str(Path(__file__).parent))
49
50
 
51
+ from auto_pause_handler import AutoPauseHandler
50
52
  from event_handlers import EventHandlers
51
53
  from memory_integration import MemoryHookManager
52
54
  from response_tracking import ResponseTrackingManager
@@ -60,14 +62,31 @@ except ImportError:
60
62
  """
61
63
  Debug mode configuration for hook processing.
62
64
 
63
- WHY enabled by default: Hook processing can be complex and hard to debug.
64
- Having debug output available by default helps diagnose issues during development.
65
- Production deployments can disable via environment variable.
65
+ WHY disabled by default: Production users should see clean output without debug noise.
66
+ Hook errors appear less confusing when debug output is minimal.
67
+ Development and debugging can enable via CLAUDE_MPM_HOOK_DEBUG=true.
66
68
 
67
69
  Performance Impact: Debug logging adds ~5-10% overhead but provides crucial
68
- visibility into event flow, timing, and error conditions.
70
+ visibility into event flow, timing, and error conditions when enabled.
69
71
  """
70
- DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "true").lower() != "false"
72
+ DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "false").lower() == "true"
73
+
74
+
75
+ def _log(message: str) -> None:
76
+ """Log message to file if DEBUG enabled. Never write to stderr.
77
+
78
+ WHY: Claude Code interprets ANY stderr output as a hook error.
79
+ Writing to stderr causes confusing "hook error" messages even for debug logs.
80
+
81
+ This helper ensures all debug output goes to a log file instead.
82
+ """
83
+ if DEBUG:
84
+ try:
85
+ with open("/tmp/claude-mpm-hook.log", "a") as f: # nosec B108
86
+ f.write(f"[{datetime.now(timezone.utc).isoformat()}] {message}\n")
87
+ except Exception: # nosec B110 - intentional silent failure
88
+ pass # Never disrupt hook execution
89
+
71
90
 
72
91
  """
73
92
  Conditional imports with graceful fallbacks for testing and modularity.
@@ -109,6 +128,8 @@ WHY version checking:
109
128
  Security: Version checking prevents execution on incompatible environments.
110
129
  """
111
130
  MIN_CLAUDE_VERSION = "1.0.92"
131
+ # Minimum version for user-invocable skills support
132
+ MIN_SKILLS_VERSION = "2.1.3"
112
133
 
113
134
 
114
135
  def check_claude_version() -> Tuple[bool, Optional[str]]:
@@ -153,7 +174,7 @@ def check_claude_version() -> Tuple[bool, Optional[str]]:
153
174
  """
154
175
  try:
155
176
  # Try to detect Claude Code version
156
- result = subprocess.run(
177
+ result = subprocess.run( # nosec B603 B607 - Safe: hardcoded claude CLI with --version flag, no user input
157
178
  ["claude", "--version"],
158
179
  capture_output=True,
159
180
  text=True,
@@ -184,22 +205,17 @@ def check_claude_version() -> Tuple[bool, Optional[str]]:
184
205
  req_part = required[i] if i < len(required) else 0
185
206
 
186
207
  if curr_part < req_part:
187
- if DEBUG:
188
- print(
189
- f"⚠️ Claude Code {version} does not support matcher-based hooks "
190
- f"(requires {MIN_CLAUDE_VERSION}+). Hook monitoring disabled.",
191
- file=sys.stderr,
192
- )
208
+ _log(
209
+ f"⚠️ Claude Code {version} does not support matcher-based hooks "
210
+ f"(requires {MIN_CLAUDE_VERSION}+). Hook monitoring disabled."
211
+ )
193
212
  return False, version
194
213
  if curr_part > req_part:
195
214
  return True, version
196
215
 
197
216
  return True, version
198
217
  except Exception as e:
199
- if DEBUG:
200
- print(
201
- f"Warning: Could not detect Claude Code version: {e}", file=sys.stderr
202
- )
218
+ _log(f"Warning: Could not detect Claude Code version: {e}")
203
219
 
204
220
  return False, None
205
221
 
@@ -230,8 +246,21 @@ class ClaudeHookHandler:
230
246
  self.state_manager, self.response_tracking_manager, self.connection_manager
231
247
  )
232
248
 
249
+ # Initialize auto-pause handler
250
+ try:
251
+ self.auto_pause_handler = AutoPauseHandler()
252
+ # Pass reference to ResponseTrackingManager so it can call auto_pause
253
+ if hasattr(self, "response_tracking_manager"):
254
+ self.response_tracking_manager.auto_pause_handler = (
255
+ self.auto_pause_handler
256
+ )
257
+ except Exception as e:
258
+ self.auto_pause_handler = None
259
+ _log(f"Auto-pause initialization failed: {e}")
260
+
233
261
  # Backward compatibility properties for tests
234
- self.connection_pool = self.connection_manager.connection_pool
262
+ # Note: HTTP-based connection manager doesn't use connection_pool
263
+ self.connection_pool = None # Deprecated: No longer needed with HTTP emission
235
264
 
236
265
  # Expose state manager properties for backward compatibility
237
266
  self.active_delegations = self.state_manager.active_delegations
@@ -260,8 +289,7 @@ class ClaudeHookHandler:
260
289
  def timeout_handler(signum, frame):
261
290
  """Handle timeout by forcing exit."""
262
291
  nonlocal _continue_sent
263
- if DEBUG:
264
- print(f"Hook handler timeout (pid: {os.getpid()})", file=sys.stderr)
292
+ _log(f"Hook handler timeout (pid: {os.getpid()})")
265
293
  if not _continue_sent:
266
294
  self._continue_execution()
267
295
  _continue_sent = True
@@ -282,11 +310,9 @@ class ClaudeHookHandler:
282
310
 
283
311
  # Check for duplicate events (same event within 100ms)
284
312
  if self.duplicate_detector.is_duplicate(event):
285
- if DEBUG:
286
- print(
287
- f"[{datetime.now(timezone.utc).isoformat()}] Skipping duplicate event: {event.get('hook_event_name', 'unknown')} (PID: {os.getpid()})",
288
- file=sys.stderr,
289
- )
313
+ _log(
314
+ f"[{datetime.now(timezone.utc).isoformat()}] Skipping duplicate event: {event.get('hook_event_name', 'unknown')} (PID: {os.getpid()})"
315
+ )
290
316
  # Still need to output continue for this invocation
291
317
  if not _continue_sent:
292
318
  self._continue_execution()
@@ -294,12 +320,10 @@ class ClaudeHookHandler:
294
320
  return
295
321
 
296
322
  # Debug: Log that we're processing an event
297
- if DEBUG:
298
- hook_type = event.get("hook_event_name", "unknown")
299
- print(
300
- f"\n[{datetime.now(timezone.utc).isoformat()}] Processing hook event: {hook_type} (PID: {os.getpid()})",
301
- file=sys.stderr,
302
- )
323
+ hook_type = event.get("hook_event_name", "unknown")
324
+ _log(
325
+ f"\n[{datetime.now(timezone.utc).isoformat()}] Processing hook event: {hook_type} (PID: {os.getpid()})"
326
+ )
303
327
 
304
328
  # Perform periodic cleanup if needed
305
329
  if self.state_manager.increment_events_processed():
@@ -308,11 +332,9 @@ class ClaudeHookHandler:
308
332
  from .correlation_manager import CorrelationManager
309
333
 
310
334
  CorrelationManager.cleanup_old()
311
- if DEBUG:
312
- print(
313
- f"🧹 Performed cleanup after {self.state_manager.events_processed} events",
314
- file=sys.stderr,
315
- )
335
+ _log(
336
+ f"🧹 Performed cleanup after {self.state_manager.events_processed} events"
337
+ )
316
338
 
317
339
  # Route event to appropriate handler
318
340
  # Handlers can optionally return modified input for PreToolUse events
@@ -352,8 +374,7 @@ class ClaudeHookHandler:
352
374
  ready, _, _ = select.select([sys.stdin], [], [], 1.0)
353
375
  if not ready:
354
376
  # No data available within timeout
355
- if DEBUG:
356
- print("No hook event data received within timeout", file=sys.stderr)
377
+ _log("No hook event data received within timeout")
357
378
  return None
358
379
 
359
380
  # Data is available, read it
@@ -364,21 +385,16 @@ class ClaudeHookHandler:
364
385
 
365
386
  parsed = json.loads(event_data)
366
387
  # Debug: Log the actual event format we receive
367
- if DEBUG:
368
- print(
369
- f"Received event with keys: {list(parsed.keys())}", file=sys.stderr
370
- )
371
- for key in ["hook_event_name", "event", "type", "event_type"]:
372
- if key in parsed:
373
- print(f" {key} = '{parsed[key]}'", file=sys.stderr)
388
+ _log(f"Received event with keys: {list(parsed.keys())}")
389
+ for key in ["hook_event_name", "event", "type", "event_type"]:
390
+ if key in parsed:
391
+ _log(f" {key} = '{parsed[key]}'")
374
392
  return parsed
375
393
  except (json.JSONDecodeError, ValueError) as e:
376
- if DEBUG:
377
- print(f"Failed to parse hook event: {e}", file=sys.stderr)
394
+ _log(f"Failed to parse hook event: {e}")
378
395
  return None
379
396
  except Exception as e:
380
- if DEBUG:
381
- print(f"Error reading hook event: {e}", file=sys.stderr)
397
+ _log(f"Error reading hook event: {e}")
382
398
  return None
383
399
 
384
400
  def _route_event(self, event: dict) -> Optional[dict]:
@@ -407,9 +423,9 @@ class ClaudeHookHandler:
407
423
  )
408
424
 
409
425
  # Log the actual event structure for debugging
410
- if DEBUG and hook_type == "unknown":
411
- print(f"Unknown event format, keys: {list(event.keys())}", file=sys.stderr)
412
- print(f"Event sample: {str(event)[:200]}", file=sys.stderr)
426
+ if hook_type == "unknown":
427
+ _log(f"Unknown event format, keys: {list(event.keys())}")
428
+ _log(f"Event sample: {str(event)[:200]}")
413
429
 
414
430
  # Map event types to handlers
415
431
  event_handlers = {
@@ -419,7 +435,7 @@ class ClaudeHookHandler:
419
435
  "Notification": self.event_handlers.handle_notification_fast,
420
436
  "Stop": self.event_handlers.handle_stop_fast,
421
437
  "SubagentStop": self.event_handlers.handle_subagent_stop_fast,
422
- "SubagentStart": self.event_handlers.handle_session_start_fast,
438
+ "SubagentStart": self.event_handlers.handle_subagent_start_fast,
423
439
  "SessionStart": self.event_handlers.handle_session_start_fast,
424
440
  "AssistantResponse": self.event_handlers.handle_assistant_response,
425
441
  }
@@ -445,8 +461,7 @@ class ClaudeHookHandler:
445
461
  except Exception as e:
446
462
  error_message = str(e)
447
463
  return_value = None
448
- if DEBUG:
449
- print(f"Error handling {hook_type}: {e}", file=sys.stderr)
464
+ _log(f"Error handling {hook_type}: {e}")
450
465
  finally:
451
466
  # Calculate duration
452
467
  duration_ms = int((time.time() - start_time) * 1000)
@@ -480,9 +495,12 @@ class ClaudeHookHandler:
480
495
  """
481
496
  if modified_input is not None:
482
497
  # Claude Code v2.0.30+ supports modifying PreToolUse tool inputs
483
- print(json.dumps({"action": "continue", "tool_input": modified_input}))
498
+ print(
499
+ json.dumps({"action": "continue", "tool_input": modified_input}),
500
+ flush=True,
501
+ )
484
502
  else:
485
- print(json.dumps({"action": "continue"}))
503
+ print(json.dumps({"action": "continue"}), flush=True)
486
504
 
487
505
  # Delegation methods for compatibility with event_handlers
488
506
  def _track_delegation(self, session_id: str, agent_type: str, request_data=None):
@@ -535,13 +553,15 @@ class ClaudeHookHandler:
535
553
  # Build hook execution data
536
554
  hook_data = {
537
555
  "hook_name": hook_type,
538
- "hook_type": hook_type,
556
+ "hook_type": hook_type, # Actual hook type (PreToolUse, UserPromptSubmit, etc.)
557
+ "hook_event_type": hook_type, # Additional field for clarity
539
558
  "session_id": session_id,
540
559
  "working_directory": working_dir,
541
560
  "success": success,
542
561
  "duration_ms": duration_ms,
543
562
  "result_summary": summary,
544
563
  "timestamp": datetime.now(timezone.utc).isoformat(),
564
+ "source": "claude_hook_handler", # Explicit source identification
545
565
  }
546
566
 
547
567
  # Add error information if present
@@ -566,11 +586,9 @@ class ClaudeHookHandler:
566
586
  # This uses the existing event infrastructure
567
587
  self._emit_socketio_event("", "hook_execution", hook_data)
568
588
 
569
- if DEBUG:
570
- print(
571
- f"📊 Hook execution event: {hook_type} - {duration_ms}ms - {'✅' if success else '❌'}",
572
- file=sys.stderr,
573
- )
589
+ _log(
590
+ f"📊 Hook execution event: {hook_type} - {duration_ms}ms - {'✅' if success else '❌'}"
591
+ )
574
592
 
575
593
  def _generate_hook_summary(self, hook_type: str, event: dict, success: bool) -> str:
576
594
  """Generate a human-readable summary of what the hook did.
@@ -628,12 +646,19 @@ class ClaudeHookHandler:
628
646
 
629
647
  def __del__(self):
630
648
  """Cleanup on handler destruction."""
649
+ # Finalize any active auto-pause session
650
+ if hasattr(self, "auto_pause_handler") and self.auto_pause_handler:
651
+ try:
652
+ self.auto_pause_handler.on_session_end()
653
+ except Exception:
654
+ pass # nosec B110 - Intentionally ignore cleanup errors during handler destruction
655
+
631
656
  # Clean up connection manager if it exists
632
657
  if hasattr(self, "connection_manager") and self.connection_manager:
633
658
  try:
634
659
  self.connection_manager.cleanup()
635
660
  except Exception:
636
- pass # Ignore cleanup errors during destruction
661
+ pass # nosec B110 - Intentionally ignore cleanup errors during handler destruction
637
662
 
638
663
 
639
664
  def main():
@@ -646,25 +671,18 @@ def main():
646
671
  if not is_compatible:
647
672
  # Version incompatible - just continue without processing
648
673
  # This prevents errors on older Claude Code versions
649
- if DEBUG and version:
650
- print(
651
- f"Skipping hook processing due to version incompatibility ({version})",
652
- file=sys.stderr,
653
- )
654
- print(json.dumps({"action": "continue"}))
674
+ if version:
675
+ _log(f"Skipping hook processing due to version incompatibility ({version})")
676
+ print(json.dumps({"action": "continue"}), flush=True)
655
677
  sys.exit(0)
656
678
 
657
679
  def cleanup_handler(signum=None, frame=None):
658
680
  """Cleanup handler for signals and exit."""
659
681
  nonlocal _continue_printed
660
- if DEBUG:
661
- print(
662
- f"Hook handler cleanup (pid: {os.getpid()}, signal: {signum})",
663
- file=sys.stderr,
664
- )
682
+ _log(f"Hook handler cleanup (pid: {os.getpid()}, signal: {signum})")
665
683
  # Only output continue if we haven't already (i.e., if interrupted by signal)
666
684
  if signum is not None and not _continue_printed:
667
- print(json.dumps({"action": "continue"}))
685
+ print(json.dumps({"action": "continue"}), flush=True)
668
686
  _continue_printed = True
669
687
  sys.exit(0)
670
688
 
@@ -678,15 +696,10 @@ def main():
678
696
  with _handler_lock:
679
697
  if _global_handler is None:
680
698
  _global_handler = ClaudeHookHandler()
681
- if DEBUG:
682
- print(
683
- f"✅ Created new ClaudeHookHandler singleton (pid: {os.getpid()})",
684
- file=sys.stderr,
685
- )
686
- elif DEBUG:
687
- print(
688
- f"♻️ Reusing existing ClaudeHookHandler singleton (pid: {os.getpid()})",
689
- file=sys.stderr,
699
+ _log(f"✅ Created new ClaudeHookHandler singleton (pid: {os.getpid()})")
700
+ else:
701
+ _log(
702
+ f"♻️ Reusing existing ClaudeHookHandler singleton (pid: {os.getpid()})"
690
703
  )
691
704
 
692
705
  handler = _global_handler
@@ -702,13 +715,17 @@ def main():
702
715
  except Exception as e:
703
716
  # Only output continue if not already printed
704
717
  if not _continue_printed:
705
- print(json.dumps({"action": "continue"}))
718
+ print(json.dumps({"action": "continue"}), flush=True)
706
719
  _continue_printed = True
707
720
  # Log error for debugging
708
- if DEBUG:
709
- print(f"Hook handler error: {e}", file=sys.stderr)
721
+ _log(f"Hook handler error: {e}")
710
722
  sys.exit(0) # Exit cleanly even on error
711
723
 
712
724
 
713
725
  if __name__ == "__main__":
714
- main()
726
+ try:
727
+ main()
728
+ except Exception:
729
+ # Catastrophic failure (import error, etc.) - always output valid JSON
730
+ print(json.dumps({"action": "continue"}), flush=True)
731
+ sys.exit(0)
@@ -48,15 +48,10 @@ echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] PYTHONPATH: $PYTHONPATH" >> /tmp/hook
48
48
  echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] Running: $PYTHON_CMD -m claude_mpm.hooks.claude_hooks.hook_handler" >> /tmp/hook-wrapper.log
49
49
  echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] SOCKETIO_PORT: $CLAUDE_MPM_SOCKETIO_PORT" >> /tmp/hook-wrapper.log
50
50
 
51
- # Run the Python hook handler as a module with error handling
52
- # Use exec to replace the shell process, but wrap in error handling
53
- if ! "$PYTHON_CMD" -m claude_mpm.hooks.claude_hooks.hook_handler "$@" 2>/tmp/hook-error.log; then
54
- # If the Python handler fails, always return continue to not block Claude
55
- echo '{"action": "continue"}'
56
- # Log the error for debugging
57
- echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] Hook handler failed, see /tmp/hook-error.log" >> /tmp/hook-wrapper.log
58
- exit 0
59
- fi
51
+ # Run the Python hook handler as a module
52
+ # Python handler is responsible for ALL stdout output (including error fallback)
53
+ # Redirect stderr to log file for debugging
54
+ "$PYTHON_CMD" -m claude_mpm.hooks.claude_hooks.hook_handler "$@" 2>/tmp/hook-error.log
60
55
 
61
- # Success - Python handler already printed continue, just exit
62
- exit 0
56
+ # Exit with Python's exit code (should always be 0)
57
+ exit $?
@@ -10,7 +10,7 @@ import os
10
10
  import re
11
11
  import shutil
12
12
  import stat
13
- import subprocess
13
+ import subprocess # nosec B404 - Safe: only uses hardcoded 'claude' CLI command, no user input
14
14
  from pathlib import Path
15
15
  from typing import Dict, List, Optional, Tuple
16
16
 
@@ -194,6 +194,8 @@ main "$@"
194
194
  MIN_CLAUDE_VERSION = "1.0.92"
195
195
  # Minimum version for PreToolUse input modification support
196
196
  MIN_PRETOOL_MODIFY_VERSION = "2.0.30"
197
+ # Minimum version for user-invocable skills support
198
+ MIN_SKILLS_VERSION = "2.1.3"
197
199
 
198
200
  def __init__(self):
199
201
  """Initialize the hook installer."""
@@ -220,7 +222,7 @@ main "$@"
220
222
 
221
223
  try:
222
224
  # Run claude --version command
223
- result = subprocess.run(
225
+ result = subprocess.run( # nosec B607 B603 - Safe: hardcoded command, no user input
224
226
  ["claude", "--version"],
225
227
  capture_output=True,
226
228
  text=True,
@@ -331,6 +333,53 @@ main "$@"
331
333
 
332
334
  return True
333
335
 
336
+ def _version_meets_minimum(self, version: str, min_version: str) -> bool:
337
+ """Check if a version meets minimum requirements.
338
+
339
+ Args:
340
+ version: Current version string (e.g., "2.1.3")
341
+ min_version: Minimum required version string (e.g., "2.1.3")
342
+
343
+ Returns:
344
+ True if version meets or exceeds minimum, False otherwise
345
+ """
346
+
347
+ def parse_version(v: str) -> List[int]:
348
+ """Parse semantic version string to list of integers."""
349
+ try:
350
+ return [int(x) for x in v.split(".")]
351
+ except (ValueError, AttributeError):
352
+ return [0]
353
+
354
+ current = parse_version(version)
355
+ required = parse_version(min_version)
356
+
357
+ # Compare versions
358
+ for i in range(max(len(current), len(required))):
359
+ curr_part = current[i] if i < len(current) else 0
360
+ req_part = required[i] if i < len(required) else 0
361
+
362
+ if curr_part < req_part:
363
+ return False
364
+ if curr_part > req_part:
365
+ return True
366
+
367
+ return True
368
+
369
+ def supports_user_invocable_skills(self) -> bool:
370
+ """Check if Claude Code version supports user-invocable skills.
371
+
372
+ User-invocable skills were added in Claude Code v2.1.3.
373
+ This feature allows users to invoke skills via slash commands.
374
+
375
+ Returns:
376
+ True if version supports user-invocable skills, False otherwise
377
+ """
378
+ version = self.get_claude_version()
379
+ if not version:
380
+ return False
381
+ return self._version_meets_minimum(version, self.MIN_SKILLS_VERSION)
382
+
334
383
  def get_hook_script_path(self) -> Path:
335
384
  """Get the path to the hook handler script based on installation method.
336
385
 
@@ -556,7 +605,22 @@ main "$@"
556
605
  self._cleanup_old_settings()
557
606
 
558
607
  def _install_commands(self) -> None:
559
- """Install custom commands for Claude Code."""
608
+ """Install custom commands for Claude Code.
609
+
610
+ For Claude Code >= 2.1.3, commands are deployed as skills via PMSkillsDeployerService.
611
+ This method provides backward compatibility for older versions.
612
+ """
613
+ # Check if skills-based commands are supported
614
+ if self.supports_user_invocable_skills():
615
+ self.logger.info(
616
+ "Claude Code >= 2.1.3 detected. Commands deployed as skills - "
617
+ "skipping legacy command installation."
618
+ )
619
+ return
620
+
621
+ # Legacy installation for older Claude Code versions
622
+ self.logger.info("Installing legacy commands for Claude Code < 2.1.3")
623
+
560
624
  # Find commands directory using proper resource resolution
561
625
  try:
562
626
  from ...core.unified_paths import get_package_resource_path
@@ -782,7 +846,7 @@ main "$@"
782
846
  if "hooks" in settings:
783
847
  status["configured_events"] = list(settings["hooks"].keys())
784
848
  configured_in_local = True
785
- except Exception:
849
+ except Exception: # nosec B110 - Intentional: ignore errors reading settings file
786
850
  pass
787
851
 
788
852
  # Also check old settings file
@@ -796,7 +860,7 @@ main "$@"
796
860
  status["warning"] = (
797
861
  "Hooks found in settings.local.json but Claude Code reads from settings.json"
798
862
  )
799
- except Exception:
863
+ except Exception: # nosec B110 - Intentional: ignore errors reading old settings file
800
864
  pass
801
865
 
802
866
  status["settings_location"] = (
@@ -130,7 +130,7 @@ class ResponseTrackingManager:
130
130
 
131
131
  try:
132
132
  # Get the original request data stored during pre-tool
133
- request_info = delegation_requests.get(session_id)
133
+ request_info = delegation_requests.get(session_id) # nosec B113 - False positive: dict.get(), not requests library
134
134
  if not request_info:
135
135
  if DEBUG:
136
136
  print(
@@ -306,6 +306,8 @@ class ResponseTrackingManager:
306
306
  )
307
307
 
308
308
  # Capture Claude API usage data if available
309
+ # NOTE: Usage data is already captured in metadata by handle_stop_fast()
310
+ # which also handles auto-pause triggering (even when response tracking disabled)
309
311
  if "usage" in event:
310
312
  usage_data = event["usage"]
311
313
  metadata["usage"] = {
@@ -134,6 +134,26 @@ class ConnectionManagerService:
134
134
  # Otherwise use "hook" as the type
135
135
  if event == "hook_execution":
136
136
  hook_type = data.get("hook_type", "unknown")
137
+
138
+ # BUGFIX: Validate hook_type is meaningful (not generic/invalid values)
139
+ # Problem: Dashboard shows "hook hook" instead of "PreToolUse", "UserPromptSubmit", etc.
140
+ # Root cause: hook_type defaults to "hook" or "unknown", providing no useful information
141
+ # Solution: Fallback to hook_name, then to descriptive "hook_execution_untyped"
142
+ if hook_type in ("hook", "unknown", "", None):
143
+ # Try fallback to hook_name field (set by _emit_hook_execution_event)
144
+ hook_type = data.get("hook_name", "unknown_hook")
145
+
146
+ # Final fallback if still generic - use descriptive name
147
+ if hook_type in ("hook", "unknown", "", None):
148
+ hook_type = "hook_execution_untyped"
149
+
150
+ # Debug log when we detect invalid hook_type for troubleshooting
151
+ if DEBUG:
152
+ print(
153
+ f"⚠️ Invalid hook_type detected, using fallback: {hook_type}",
154
+ file=sys.stderr,
155
+ )
156
+
137
157
  event_type = hook_type
138
158
  else:
139
159
  event_type = "hook"