claude-mpm 5.4.64__py3-none-any.whl → 5.4.96__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 (163) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/CLAUDE_MPM_FOUNDERS_OUTPUT_STYLE.md +405 -0
  3. claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +66 -241
  4. claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +107 -1928
  5. claude_mpm/agents/PM_INSTRUCTIONS.md +82 -686
  6. claude_mpm/cli/__init__.py +5 -1
  7. claude_mpm/cli/commands/agents.py +2 -4
  8. claude_mpm/cli/commands/agents_reconcile.py +197 -0
  9. claude_mpm/cli/commands/autotodos.py +526 -0
  10. claude_mpm/cli/commands/configure.py +620 -21
  11. claude_mpm/cli/commands/monitor.py +2 -2
  12. claude_mpm/cli/commands/mpm_init/core.py +2 -2
  13. claude_mpm/cli/commands/skills.py +166 -14
  14. claude_mpm/cli/executor.py +89 -0
  15. claude_mpm/cli/interactive/__init__.py +10 -0
  16. claude_mpm/cli/interactive/agent_wizard.py +30 -50
  17. claude_mpm/cli/interactive/questionary_styles.py +65 -0
  18. claude_mpm/cli/interactive/skill_selector.py +481 -0
  19. claude_mpm/cli/parsers/base_parser.py +59 -1
  20. claude_mpm/cli/startup.py +202 -367
  21. claude_mpm/cli/startup_display.py +72 -5
  22. claude_mpm/cli/startup_logging.py +2 -2
  23. claude_mpm/commands/mpm-session-resume.md +1 -1
  24. claude_mpm/constants.py +1 -0
  25. claude_mpm/core/claude_runner.py +2 -2
  26. claude_mpm/core/hook_manager.py +51 -3
  27. claude_mpm/core/interactive_session.py +7 -7
  28. claude_mpm/core/output_style_manager.py +21 -13
  29. claude_mpm/core/unified_config.py +50 -8
  30. claude_mpm/core/unified_paths.py +30 -13
  31. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.C33zOoyM.css +1 -0
  32. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.CW1J-YuA.css +1 -0
  33. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cs_tUR18.js → 1WZnGYqX.js} +1 -1
  34. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CDuw-vjf.js → 67pF3qNn.js} +1 -1
  35. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bTOqqlTd.js → 6RxdMKe4.js} +1 -1
  36. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DwBR2MJi.js → 8cZrfX0h.js} +1 -1
  37. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{ZGh7QtNv.js → 9a6T2nm-.js} +1 -1
  38. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D9lljYKQ.js → B443AUzu.js} +1 -1
  39. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{RJiighC3.js → B8AwtY2H.js} +1 -1
  40. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{uuIeMWc-.js → BF15LAsF.js} +1 -1
  41. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D3k0OPJN.js → BRcwIQNr.js} +1 -1
  42. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CyWMqx4W.js → BV6nKitt.js} +1 -1
  43. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CiIAseT4.js → BViJ8lZt.js} +5 -5
  44. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CBBdVcY8.js → BcQ-Q0FE.js} +1 -1
  45. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BovzEFCE.js → Bpyvgze_.js} +1 -1
  46. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BzTRqg-z.js +1 -0
  47. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C0Fr8dve.js +1 -0
  48. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{eNVUfhuA.js → C3rbW_a-.js} +1 -1
  49. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{GYwsonyD.js → C8WYN38h.js} +1 -1
  50. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BIF9m_hv.js → C9I8FlXH.js} +1 -1
  51. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B0uc0UOD.js → CIQcWgO2.js} +3 -3
  52. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Be7GpZd6.js → CIctN7YN.js} +1 -1
  53. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Bh0LDWpI.js → CKrS_JZW.js} +2 -2
  54. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DUrLdbGD.js → CR6P9C4A.js} +1 -1
  55. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7xVLGWV.js → CRRR9MD_.js} +1 -1
  56. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
  57. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dhb8PKl3.js → CSXtMOf0.js} +1 -1
  58. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BPYeabCQ.js → CT-sbxSk.js} +1 -1
  59. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{sQeU3Y1z.js → CWm6DJsp.js} +1 -1
  60. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CnA0NrzZ.js → CpqQ1Kzn.js} +1 -1
  61. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4B-KCzX.js → D2nGpDRe.js} +1 -1
  62. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DGkLK5U1.js → D9iCMida.js} +1 -1
  63. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BofRWZRR.js → D9ykgMoY.js} +1 -1
  64. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DmxopI1J.js → DL2Ldur1.js} +1 -1
  65. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C30mlcqg.js → DPfltzjH.js} +1 -1
  66. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Vzk33B_K.js → DR8nis88.js} +2 -2
  67. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DI7hHRFL.js → DUliQN2b.js} +1 -1
  68. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4JcI4KD.js → DXlhR01x.js} +1 -1
  69. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bT1r9zLR.js → D_lyTybS.js} +1 -1
  70. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DZX00Y4g.js → DngoTTgh.js} +1 -1
  71. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzZX-COe.js → DqkmHtDC.js} +1 -1
  72. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7RN905-.js → DsDh8EYs.js} +1 -1
  73. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DLVjFsZ3.js → DypDmXgd.js} +1 -1
  74. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{iEWssX7S.js → IPYC-LnN.js} +1 -1
  75. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JTLiF7dt.js +24 -0
  76. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DaimHw_p.js → JpevfAFt.js} +1 -1
  77. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DY1XQ8fi.js → R8CEIRAd.js} +1 -1
  78. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dle-35c7.js → Zxy7qc-l.js} +2 -2
  79. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
  80. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C_Usid8X.js → qtd3IeO4.js} +2 -2
  81. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzeYkLYB.js → ulBFON_C.js} +2 -2
  82. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cfqx1Qun.js → wQVh1CoA.js} +1 -1
  83. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/{app.D6-I5TpK.js → app.Dr7t0z2J.js} +2 -2
  84. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
  85. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.m1gL8KXf.js → 0.RgBboRvH.js} +1 -1
  86. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{1.CgNOuw-d.js → 1.DG-KkbDf.js} +1 -1
  87. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.D_jnf-x6.js +1 -0
  88. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -1
  89. claude_mpm/dashboard/static/svelte-build/index.html +9 -9
  90. claude_mpm/hooks/claude_hooks/INTEGRATION_EXAMPLE.md +243 -0
  91. claude_mpm/hooks/claude_hooks/README_AUTO_PAUSE.md +403 -0
  92. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
  93. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  94. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  95. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  96. claude_mpm/hooks/claude_hooks/auto_pause_handler.py +486 -0
  97. claude_mpm/hooks/claude_hooks/event_handlers.py +216 -11
  98. claude_mpm/hooks/claude_hooks/hook_handler.py +28 -4
  99. claude_mpm/hooks/claude_hooks/response_tracking.py +3 -1
  100. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
  101. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  102. claude_mpm/hooks/claude_hooks/services/connection_manager.py +20 -0
  103. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +30 -6
  104. claude_mpm/hooks/session_resume_hook.py +85 -1
  105. claude_mpm/init.py +1 -1
  106. claude_mpm/services/agents/cache_git_manager.py +1 -1
  107. claude_mpm/services/agents/deployment/deployment_reconciler.py +577 -0
  108. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +3 -0
  109. claude_mpm/services/agents/deployment/startup_reconciliation.py +138 -0
  110. claude_mpm/services/agents/startup_sync.py +5 -2
  111. claude_mpm/services/cli/__init__.py +3 -0
  112. claude_mpm/services/cli/incremental_pause_manager.py +561 -0
  113. claude_mpm/services/cli/session_resume_helper.py +10 -2
  114. claude_mpm/services/delegation_detector.py +175 -0
  115. claude_mpm/services/diagnostics/checks/agent_sources_check.py +30 -0
  116. claude_mpm/services/diagnostics/checks/configuration_check.py +24 -0
  117. claude_mpm/services/diagnostics/checks/installation_check.py +22 -0
  118. claude_mpm/services/diagnostics/checks/mcp_services_check.py +23 -0
  119. claude_mpm/services/diagnostics/doctor_reporter.py +31 -1
  120. claude_mpm/services/diagnostics/models.py +14 -1
  121. claude_mpm/services/event_log.py +317 -0
  122. claude_mpm/services/infrastructure/__init__.py +4 -0
  123. claude_mpm/services/infrastructure/context_usage_tracker.py +291 -0
  124. claude_mpm/services/infrastructure/resume_log_generator.py +24 -5
  125. claude_mpm/services/monitor/daemon_manager.py +15 -4
  126. claude_mpm/services/monitor/management/lifecycle.py +8 -2
  127. claude_mpm/services/monitor/server.py +106 -16
  128. claude_mpm/services/pm_skills_deployer.py +177 -83
  129. claude_mpm/services/skills/git_skill_source_manager.py +5 -1
  130. claude_mpm/services/skills/selective_skill_deployer.py +114 -26
  131. claude_mpm/services/socketio/handlers/hook.py +14 -7
  132. claude_mpm/services/socketio/server/main.py +12 -4
  133. claude_mpm/skills/bundled/pm/mpm-agent-update-workflow/SKILL.md +75 -0
  134. claude_mpm/skills/bundled/pm/mpm-bug-reporting/SKILL.md +248 -0
  135. claude_mpm/skills/bundled/pm/mpm-circuit-breaker-enforcement/SKILL.md +476 -0
  136. claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
  137. claude_mpm/skills/bundled/pm/mpm-teaching-mode/SKILL.md +657 -0
  138. claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
  139. claude_mpm/skills/skill_manager.py +4 -4
  140. claude_mpm/utils/agent_dependency_loader.py +103 -4
  141. claude_mpm/utils/robust_installer.py +45 -24
  142. claude_mpm-5.4.96.dist-info/METADATA +377 -0
  143. {claude_mpm-5.4.64.dist-info → claude_mpm-5.4.96.dist-info}/RECORD +153 -131
  144. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +0 -1
  145. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +0 -1
  146. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +0 -1
  147. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +0 -24
  148. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +0 -1
  149. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +0 -1
  150. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +0 -323
  151. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +0 -1
  152. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +0 -1
  153. claude_mpm-5.4.64.dist-info/METADATA +0 -999
  154. /claude_mpm/skills/bundled/pm/{pm-delegation-patterns → mpm-delegation-patterns}/SKILL.md +0 -0
  155. /claude_mpm/skills/bundled/pm/{pm-git-file-tracking → mpm-git-file-tracking}/SKILL.md +0 -0
  156. /claude_mpm/skills/bundled/pm/{pm-pr-workflow → mpm-pr-workflow}/SKILL.md +0 -0
  157. /claude_mpm/skills/bundled/pm/{pm-ticketing-integration → mpm-ticketing-integration}/SKILL.md +0 -0
  158. /claude_mpm/skills/bundled/pm/{pm-verification-protocols → mpm-verification-protocols}/SKILL.md +0 -0
  159. {claude_mpm-5.4.64.dist-info → claude_mpm-5.4.96.dist-info}/WHEEL +0 -0
  160. {claude_mpm-5.4.64.dist-info → claude_mpm-5.4.96.dist-info}/entry_points.txt +0 -0
  161. {claude_mpm-5.4.64.dist-info → claude_mpm-5.4.96.dist-info}/licenses/LICENSE +0 -0
  162. {claude_mpm-5.4.64.dist-info → claude_mpm-5.4.96.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  163. {claude_mpm-5.4.64.dist-info → claude_mpm-5.4.96.dist-info}/top_level.txt +0 -0
@@ -7,7 +7,7 @@ Claude Code hook events.
7
7
 
8
8
  import os
9
9
  import re
10
- import subprocess
10
+ import subprocess # nosec B404 - subprocess used for safe claude CLI version checking only
11
11
  import sys
12
12
  import uuid
13
13
  from datetime import datetime, timezone
@@ -115,7 +115,7 @@ class EventHandlers:
115
115
  f"Stored prompt for comprehensive tracking: session {session_id[:8]}...",
116
116
  file=sys.stderr,
117
117
  )
118
- except Exception:
118
+ except Exception: # nosec B110
119
119
  # Response tracking is optional - silently continue if it fails
120
120
  pass
121
121
 
@@ -189,8 +189,34 @@ class EventHandlers:
189
189
  if tool_name == "Task" and isinstance(tool_input, dict):
190
190
  self._handle_task_delegation(tool_input, pre_tool_data, session_id)
191
191
 
192
+ # Record tool call for auto-pause if active
193
+ auto_pause = getattr(self.hook_handler, "auto_pause_handler", None)
194
+ if auto_pause and auto_pause.is_pause_active():
195
+ try:
196
+ auto_pause.on_tool_call(tool_name, tool_input)
197
+ except Exception as e:
198
+ if DEBUG:
199
+ print(f"Auto-pause tool recording error: {e}", file=sys.stderr)
200
+
192
201
  self.hook_handler._emit_socketio_event("", "pre_tool", pre_tool_data)
193
202
 
203
+ # Handle TodoWrite specially - emit dedicated todo_updated event
204
+ # WHY: Frontend expects todo_updated events for dashboard display
205
+ # The broadcaster.todo_updated() method exists but was never called
206
+ if tool_name == "TodoWrite" and tool_params.get("todos"):
207
+ todo_data = {
208
+ "todos": tool_params["todos"],
209
+ "total_count": len(tool_params["todos"]),
210
+ "session_id": session_id,
211
+ "timestamp": timestamp,
212
+ }
213
+ self.hook_handler._emit_socketio_event("", "todo_updated", todo_data)
214
+ 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,
218
+ )
219
+
194
220
  def _handle_task_delegation(
195
221
  self, tool_input: dict, pre_tool_data: dict, session_id: str
196
222
  ):
@@ -271,7 +297,7 @@ class EventHandlers:
271
297
  mhm = getattr(self.hook_handler, "memory_hook_manager", None)
272
298
  if mhm and hasattr(mhm, "trigger_pre_delegation_hook"):
273
299
  mhm.trigger_pre_delegation_hook(agent_type, tool_input, session_id)
274
- except Exception:
300
+ except Exception: # nosec B110
275
301
  # Memory hooks are optional
276
302
  pass
277
303
 
@@ -364,7 +390,7 @@ class EventHandlers:
364
390
  os.chdir(working_dir)
365
391
 
366
392
  # Run git command to get current branch
367
- result = subprocess.run(
393
+ result = subprocess.run( # nosec B603 B607
368
394
  ["git", "branch", "--show-current"],
369
395
  capture_output=True,
370
396
  text=True,
@@ -474,7 +500,7 @@ class EventHandlers:
474
500
  mhm = getattr(self.hook_handler, "memory_hook_manager", None)
475
501
  if mhm and hasattr(mhm, "trigger_post_delegation_hook"):
476
502
  mhm.trigger_post_delegation_hook(agent_type, event, session_id)
477
- except Exception:
503
+ except Exception: # nosec B110
478
504
  # Memory hooks are optional
479
505
  pass
480
506
 
@@ -488,7 +514,7 @@ class EventHandlers:
488
514
  rtm.track_agent_response(
489
515
  session_id, agent_type, event, delegation_requests
490
516
  )
491
- except Exception:
517
+ except Exception: # nosec B110
492
518
  # Response tracking is optional
493
519
  pass
494
520
 
@@ -550,13 +576,49 @@ class EventHandlers:
550
576
  if DEBUG:
551
577
  self._log_stop_event_debug(event, session_id, metadata)
552
578
 
579
+ # Auto-pause integration (independent of response tracking)
580
+ # WHY HERE: Auto-pause must work even when response_tracking is disabled
581
+ # Extract usage data directly from event and trigger auto-pause if thresholds crossed
582
+ if "usage" in event:
583
+ auto_pause = getattr(self.hook_handler, "auto_pause_handler", None)
584
+ if auto_pause:
585
+ try:
586
+ usage_data = event["usage"]
587
+ metadata["usage"] = {
588
+ "input_tokens": usage_data.get("input_tokens", 0),
589
+ "output_tokens": usage_data.get("output_tokens", 0),
590
+ "cache_creation_input_tokens": usage_data.get(
591
+ "cache_creation_input_tokens", 0
592
+ ),
593
+ "cache_read_input_tokens": usage_data.get(
594
+ "cache_read_input_tokens", 0
595
+ ),
596
+ }
597
+
598
+ threshold_crossed = auto_pause.on_usage_update(metadata["usage"])
599
+ if threshold_crossed:
600
+ warning = auto_pause.emit_threshold_warning(threshold_crossed)
601
+ print(f"\n⚠️ {warning}", file=sys.stderr)
602
+
603
+ if DEBUG:
604
+ print(
605
+ f" - Auto-pause threshold crossed: {threshold_crossed}",
606
+ file=sys.stderr,
607
+ )
608
+ except Exception as e:
609
+ if DEBUG:
610
+ print(
611
+ f"Auto-pause error in handle_stop_fast: {e}",
612
+ file=sys.stderr,
613
+ )
614
+
553
615
  # Track response if enabled
554
616
  try:
555
617
  rtm = getattr(self.hook_handler, "response_tracking_manager", None)
556
618
  if rtm and hasattr(rtm, "track_stop_response"):
557
619
  pending_prompts = getattr(self.hook_handler, "pending_prompts", {})
558
620
  rtm.track_stop_response(event, session_id, metadata, pending_prompts)
559
- except Exception:
621
+ except Exception: # nosec B110
560
622
  # Response tracking is optional
561
623
  pass
562
624
 
@@ -598,7 +660,7 @@ class EventHandlers:
598
660
  f" - response_tracker exists: {tracker_exists}",
599
661
  file=sys.stderr,
600
662
  )
601
- except Exception:
663
+ except Exception: # nosec B110
602
664
  # If debug logging fails, just skip it
603
665
  pass
604
666
 
@@ -664,7 +726,7 @@ class EventHandlers:
664
726
  try:
665
727
  # Get the original request data (with fuzzy matching fallback)
666
728
  delegation_requests = getattr(self.hook_handler, "delegation_requests", {})
667
- request_info = delegation_requests.get(session_id)
729
+ request_info = delegation_requests.get(session_id) # nosec B113
668
730
 
669
731
  # If exact match fails, try partial matching
670
732
  if not request_info and session_id:
@@ -689,7 +751,7 @@ class EventHandlers:
689
751
  f" - ✅ Fuzzy match found: {stored_sid[:16]}...",
690
752
  file=sys.stderr,
691
753
  )
692
- request_info = delegation_requests.get(stored_sid)
754
+ request_info = delegation_requests.get(stored_sid) # nosec B113
693
755
  # Update the key to use the current session_id for consistency
694
756
  if request_info:
695
757
  delegation_requests[session_id] = request_info
@@ -789,6 +851,7 @@ class EventHandlers:
789
851
  - Captures response content and metadata for analysis
790
852
  - Enables tracking of conversation flow and response patterns
791
853
  - Essential for comprehensive monitoring of Claude interactions
854
+ - Scans for delegation anti-patterns and creates autotodos
792
855
  """
793
856
  # Track the response for logging
794
857
  try:
@@ -796,10 +859,17 @@ class EventHandlers:
796
859
  if rtm and hasattr(rtm, "track_assistant_response"):
797
860
  pending_prompts = getattr(self.hook_handler, "pending_prompts", {})
798
861
  rtm.track_assistant_response(event, pending_prompts)
799
- except Exception:
862
+ except Exception: # nosec B110
800
863
  # Response tracking is optional
801
864
  pass
802
865
 
866
+ # Scan response for delegation anti-patterns and create autotodos
867
+ try:
868
+ self._scan_for_delegation_patterns(event)
869
+ except Exception as e: # nosec B110
870
+ if DEBUG:
871
+ print(f"Delegation scanning error: {e}", file=sys.stderr)
872
+
803
873
  # Get working directory and git branch
804
874
  working_dir = event.get("cwd", "")
805
875
  git_branch = self._get_git_branch(working_dir) if working_dir else "Unknown"
@@ -852,6 +922,21 @@ class EventHandlers:
852
922
  file=sys.stderr,
853
923
  )
854
924
 
925
+ # Record assistant response for auto-pause if active
926
+ auto_pause = getattr(self.hook_handler, "auto_pause_handler", None)
927
+ if auto_pause and auto_pause.is_pause_active():
928
+ try:
929
+ # Summarize response to first 200 chars
930
+ summary = (
931
+ response_text[:200] + "..."
932
+ if len(response_text) > 200
933
+ else response_text
934
+ )
935
+ auto_pause.on_assistant_response(summary)
936
+ except Exception as e:
937
+ if DEBUG:
938
+ print(f"Auto-pause response recording error: {e}", file=sys.stderr)
939
+
855
940
  # Emit normalized event
856
941
  self.hook_handler._emit_socketio_event(
857
942
  "", "assistant_response", assistant_response_data
@@ -886,3 +971,123 @@ class EventHandlers:
886
971
 
887
972
  # Emit normalized event
888
973
  self.hook_handler._emit_socketio_event("", "session_start", session_start_data)
974
+
975
+ def handle_subagent_start_fast(self, event):
976
+ """Handle SubagentStart events with proper agent type extraction.
977
+
978
+ WHY separate from SessionStart:
979
+ - SubagentStart contains agent-specific information
980
+ - Frontend needs agent_type to create distinct agent nodes
981
+ - Multiple engineers should show as separate nodes in hierarchy
982
+ - Research agents must appear in the agent hierarchy
983
+
984
+ Unlike SessionStart, SubagentStart events contain agent-specific
985
+ information that must be preserved and emitted to the dashboard.
986
+ """
987
+ session_id = event.get("session_id", "")
988
+
989
+ # Extract agent type from event - Claude provides this in SubagentStart
990
+ # Try multiple possible field names for compatibility
991
+ agent_type = event.get("agent_type") or event.get("subagent_type") or "unknown"
992
+
993
+ # Generate unique agent ID combining type and session
994
+ agent_id = event.get("agent_id", f"{agent_type}_{session_id[:8]}")
995
+
996
+ # Get working directory and git branch
997
+ working_dir = event.get("cwd", "")
998
+ git_branch = self._get_git_branch(working_dir) if working_dir else "Unknown"
999
+
1000
+ # Build subagent start data with all required fields
1001
+ subagent_start_data = {
1002
+ "session_id": session_id,
1003
+ "agent_type": agent_type,
1004
+ "agent_id": agent_id,
1005
+ "timestamp": datetime.now(timezone.utc).isoformat(),
1006
+ "hook_event_name": "SubagentStart", # Preserve correct hook name
1007
+ "working_directory": working_dir,
1008
+ "git_branch": git_branch,
1009
+ }
1010
+
1011
+ # Debug logging
1012
+ 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,
1017
+ )
1018
+
1019
+ # Emit to /hook namespace as subagent_start (NOT session_start!)
1020
+ self.hook_handler._emit_socketio_event(
1021
+ "", "subagent_start", subagent_start_data
1022
+ )
1023
+
1024
+ def _scan_for_delegation_patterns(self, event):
1025
+ """Scan assistant response for delegation anti-patterns.
1026
+
1027
+ WHY this is needed:
1028
+ - Detect when PM asks user to do something manually instead of delegating
1029
+ - Flag PM behavior violations for immediate correction
1030
+ - Enforce delegation principle in PM workflow
1031
+ - Help PM recognize delegation opportunities
1032
+
1033
+ This method scans the assistant's response text for patterns like:
1034
+ - "Make sure .env.local is in your .gitignore"
1035
+ - "You'll need to run npm install"
1036
+ - "Please run the tests manually"
1037
+
1038
+ When patterns are detected, PM violations are logged as errors/warnings
1039
+ that should be corrected immediately, NOT as todos to delegate.
1040
+
1041
+ DESIGN DECISION: pm.violation vs autotodo.delegation
1042
+ - Delegation patterns = PM doing something WRONG → pm.violation (error)
1043
+ - Script failures = Something BROKEN → autotodo.error (todo)
1044
+ """
1045
+ # Only scan if delegation detector is available
1046
+ try:
1047
+ from claude_mpm.services.delegation_detector import get_delegation_detector
1048
+ from claude_mpm.services.event_log import get_event_log
1049
+ except ImportError:
1050
+ if DEBUG:
1051
+ print("Delegation detector or event log not available", file=sys.stderr)
1052
+ return
1053
+
1054
+ response_text = event.get("response", "")
1055
+ if not response_text:
1056
+ return
1057
+
1058
+ # Get the delegation detector
1059
+ detector = get_delegation_detector()
1060
+
1061
+ # Detect delegation patterns
1062
+ detections = detector.detect_user_delegation(response_text)
1063
+
1064
+ if not detections:
1065
+ return # No patterns detected
1066
+
1067
+ # Get event log for violation recording
1068
+ event_log = get_event_log()
1069
+
1070
+ # Create PM violation events (NOT autotodos)
1071
+ for detection in detections:
1072
+ # Create event log entry as pm.violation
1073
+ event_log.append_event(
1074
+ event_type="pm.violation",
1075
+ payload={
1076
+ "violation_type": "delegation_anti_pattern",
1077
+ "pattern_type": detection["pattern_type"],
1078
+ "original_text": detection["original_text"],
1079
+ "suggested_action": detection["suggested_todo"],
1080
+ "action": detection["action"],
1081
+ "session_id": event.get("session_id", ""),
1082
+ "timestamp": datetime.now(timezone.utc).isoformat(),
1083
+ "severity": "warning", # Not critical, but should be fixed
1084
+ "message": f"PM asked user to do something manually: {detection['original_text'][:80]}...",
1085
+ },
1086
+ status="pending",
1087
+ )
1088
+
1089
+ if DEBUG:
1090
+ print(
1091
+ f"⚠️ PM violation detected: {detection['original_text'][:60]}...",
1092
+ file=sys.stderr,
1093
+ )
@@ -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
@@ -153,7 +155,7 @@ def check_claude_version() -> Tuple[bool, Optional[str]]:
153
155
  """
154
156
  try:
155
157
  # Try to detect Claude Code version
156
- result = subprocess.run(
158
+ result = subprocess.run( # nosec B603 - Safe: hardcoded claude CLI with --version flag, no user input
157
159
  ["claude", "--version"],
158
160
  capture_output=True,
159
161
  text=True,
@@ -230,6 +232,19 @@ class ClaudeHookHandler:
230
232
  self.state_manager, self.response_tracking_manager, self.connection_manager
231
233
  )
232
234
 
235
+ # Initialize auto-pause handler
236
+ try:
237
+ self.auto_pause_handler = AutoPauseHandler()
238
+ # Pass reference to ResponseTrackingManager so it can call auto_pause
239
+ if hasattr(self, "response_tracking_manager"):
240
+ self.response_tracking_manager.auto_pause_handler = (
241
+ self.auto_pause_handler
242
+ )
243
+ except Exception as e:
244
+ self.auto_pause_handler = None
245
+ if DEBUG:
246
+ print(f"Auto-pause initialization failed: {e}", file=sys.stderr)
247
+
233
248
  # Backward compatibility properties for tests
234
249
  self.connection_pool = self.connection_manager.connection_pool
235
250
 
@@ -419,7 +434,7 @@ class ClaudeHookHandler:
419
434
  "Notification": self.event_handlers.handle_notification_fast,
420
435
  "Stop": self.event_handlers.handle_stop_fast,
421
436
  "SubagentStop": self.event_handlers.handle_subagent_stop_fast,
422
- "SubagentStart": self.event_handlers.handle_session_start_fast,
437
+ "SubagentStart": self.event_handlers.handle_subagent_start_fast,
423
438
  "SessionStart": self.event_handlers.handle_session_start_fast,
424
439
  "AssistantResponse": self.event_handlers.handle_assistant_response,
425
440
  }
@@ -535,13 +550,15 @@ class ClaudeHookHandler:
535
550
  # Build hook execution data
536
551
  hook_data = {
537
552
  "hook_name": hook_type,
538
- "hook_type": hook_type,
553
+ "hook_type": hook_type, # Actual hook type (PreToolUse, UserPromptSubmit, etc.)
554
+ "hook_event_type": hook_type, # Additional field for clarity
539
555
  "session_id": session_id,
540
556
  "working_directory": working_dir,
541
557
  "success": success,
542
558
  "duration_ms": duration_ms,
543
559
  "result_summary": summary,
544
560
  "timestamp": datetime.now(timezone.utc).isoformat(),
561
+ "source": "claude_hook_handler", # Explicit source identification
545
562
  }
546
563
 
547
564
  # Add error information if present
@@ -628,12 +645,19 @@ class ClaudeHookHandler:
628
645
 
629
646
  def __del__(self):
630
647
  """Cleanup on handler destruction."""
648
+ # Finalize any active auto-pause session
649
+ if hasattr(self, "auto_pause_handler") and self.auto_pause_handler:
650
+ try:
651
+ self.auto_pause_handler.on_session_end()
652
+ except Exception:
653
+ pass # nosec B110 - Intentionally ignore cleanup errors during handler destruction
654
+
631
655
  # Clean up connection manager if it exists
632
656
  if hasattr(self, "connection_manager") and self.connection_manager:
633
657
  try:
634
658
  self.connection_manager.cleanup()
635
659
  except Exception:
636
- pass # Ignore cleanup errors during destruction
660
+ pass # nosec B110 - Intentionally ignore cleanup errors during handler destruction
637
661
 
638
662
 
639
663
  def main():
@@ -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"
@@ -67,7 +67,9 @@ class SubagentResponseProcessor:
67
67
  print(" - No stored sessions in delegation_requests!", file=sys.stderr)
68
68
 
69
69
  # Get agent type and other basic info
70
- agent_type, agent_id, reason = self._extract_basic_info(event, session_id)
70
+ agent_type, agent_id, reason, agent_type_inferred = self._extract_basic_info(
71
+ event, session_id
72
+ )
71
73
 
72
74
  # Always log SubagentStop events for debugging
73
75
  if DEBUG or agent_type != "unknown":
@@ -108,6 +110,7 @@ class SubagentResponseProcessor:
108
110
  working_dir,
109
111
  git_branch,
110
112
  structured_response,
113
+ agent_type_inferred,
111
114
  )
112
115
 
113
116
  # Debug log the processed data
@@ -117,11 +120,20 @@ class SubagentResponseProcessor:
117
120
  file=sys.stderr,
118
121
  )
119
122
 
120
- # Emit to /hook namespace with high priority
121
- self.connection_manager.emit_event("/hook", "subagent_stop", subagent_stop_data)
123
+ # Emit to default namespace (consistent with subagent_start)
124
+ self.connection_manager.emit_event("", "subagent_stop", subagent_stop_data)
125
+
126
+ def _extract_basic_info(
127
+ self, event: dict, session_id: str
128
+ ) -> Tuple[str, str, str, bool]:
129
+ """Extract basic info from the event.
130
+
131
+ Returns:
132
+ Tuple of (agent_type, agent_id, reason, agent_type_inferred)
133
+ - agent_type_inferred is True when defaulted to "pm"
134
+ """
135
+ agent_type_inferred = False
122
136
 
123
- def _extract_basic_info(self, event: dict, session_id: str) -> Tuple[str, str, str]:
124
- """Extract basic info from the event."""
125
137
  # First try to get agent type from our tracking
126
138
  agent_type = (
127
139
  self.state_manager.get_delegation_agent_type(session_id)
@@ -146,7 +158,17 @@ class SubagentResponseProcessor:
146
158
  elif "pm" in task_desc or "project" in task_desc:
147
159
  agent_type = "pm"
148
160
 
149
- return agent_type, agent_id, reason
161
+ # Default to "pm" if still unknown (main conversation doesn't use Task tool)
162
+ if agent_type == "unknown":
163
+ agent_type = "pm"
164
+ agent_type_inferred = True
165
+ if DEBUG:
166
+ print(
167
+ " - Inferred agent_type='pm' (no explicit type found)",
168
+ file=sys.stderr,
169
+ )
170
+
171
+ return agent_type, agent_id, reason, agent_type_inferred
150
172
 
151
173
  def _extract_structured_response(
152
174
  self, output: str, agent_type: str
@@ -338,10 +360,12 @@ class SubagentResponseProcessor:
338
360
  working_dir: str,
339
361
  git_branch: str,
340
362
  structured_response: Optional[dict],
363
+ agent_type_inferred: bool,
341
364
  ) -> dict:
342
365
  """Build the subagent stop data for event emission."""
343
366
  subagent_stop_data = {
344
367
  "agent_type": agent_type,
368
+ "agent_type_inferred": agent_type_inferred,
345
369
  "agent_id": agent_id,
346
370
  "reason": reason,
347
371
  "session_id": session_id,
@@ -8,8 +8,11 @@ DESIGN DECISIONS:
8
8
  - Non-blocking: doesn't prevent PM from starting if check fails
9
9
  - Displays context to stdout for user visibility
10
10
  - Integrates with existing session pause/resume infrastructure
11
+ - Checks for ACTIVE-PAUSE.jsonl (incremental auto-pause) before regular paused sessions
11
12
  """
12
13
 
14
+ import json
15
+ import sys
13
16
  from pathlib import Path
14
17
  from typing import Any, Dict, Optional
15
18
 
@@ -31,10 +34,83 @@ class SessionResumeStartupHook:
31
34
  self.project_path = project_path or Path.cwd()
32
35
  self.resume_helper = SessionResumeHelper(self.project_path)
33
36
  self._session_displayed = False
37
+ self.sessions_dir = self.project_path / ".claude-mpm" / "sessions"
38
+
39
+ def check_for_active_pause(self) -> Optional[Dict[str, Any]]:
40
+ """Check for an active incremental pause session.
41
+
42
+ Returns:
43
+ Pause session metadata if ACTIVE-PAUSE.jsonl exists, None otherwise
44
+ """
45
+ active_pause_path = self.sessions_dir / "ACTIVE-PAUSE.jsonl"
46
+
47
+ if not active_pause_path.exists():
48
+ logger.debug("No ACTIVE-PAUSE.jsonl found")
49
+ return None
50
+
51
+ try:
52
+ # Read JSONL file to get first and last actions
53
+ with active_pause_path.open("r") as f:
54
+ lines = f.readlines()
55
+
56
+ if not lines:
57
+ logger.warning("ACTIVE-PAUSE.jsonl is empty")
58
+ return None
59
+
60
+ # Parse first action (session start)
61
+ first_action = json.loads(lines[0])
62
+
63
+ # Parse last action (most recent)
64
+ last_action = json.loads(lines[-1]) if len(lines) > 1 else first_action
65
+
66
+ # Extract metadata
67
+ return {
68
+ "is_incremental": True,
69
+ "session_id": first_action.get("session_id"),
70
+ "started_at": first_action.get("timestamp"),
71
+ "context_at_start": first_action.get("data", {}).get(
72
+ "context_percentage", 0
73
+ ),
74
+ "current_context": last_action.get("context_percentage", 0),
75
+ "action_count": len(lines),
76
+ "file_path": str(active_pause_path),
77
+ }
78
+
79
+ except (json.JSONDecodeError, OSError, KeyError) as e:
80
+ logger.error(f"Failed to parse ACTIVE-PAUSE.jsonl: {e}", exc_info=True)
81
+ return None
82
+
83
+ def display_active_pause_warning(self, pause_info: Dict[str, Any]) -> None:
84
+ """Display warning about active incremental pause session.
85
+
86
+ Args:
87
+ pause_info: Pause session metadata from check_for_active_pause()
88
+ """
89
+ print("\n" + "=" * 60, file=sys.stderr)
90
+ print("⚠️ ACTIVE AUTO-PAUSE SESSION DETECTED", file=sys.stderr)
91
+ print("=" * 60, file=sys.stderr)
92
+ print(f"Session ID: {pause_info['session_id']}", file=sys.stderr)
93
+ print(f"Started at: {pause_info['started_at']}", file=sys.stderr)
94
+ print(
95
+ f"Context at pause: {pause_info['context_at_start']:.1%}", file=sys.stderr
96
+ )
97
+ print(f"Actions recorded: {pause_info['action_count']}", file=sys.stderr)
98
+ print(
99
+ "\nThis session was auto-paused due to high context usage.", file=sys.stderr
100
+ )
101
+ print("Options:", file=sys.stderr)
102
+ print(" 1. Continue (actions will be appended)", file=sys.stderr)
103
+ print(" 2. Use /mpm-init pause --finalize to create snapshot", file=sys.stderr)
104
+ print(" 3. Use /mpm-init pause --discard to abandon", file=sys.stderr)
105
+ print("=" * 60 + "\n", file=sys.stderr)
34
106
 
35
107
  def on_pm_startup(self) -> Optional[Dict[str, Any]]:
36
108
  """Execute on PM startup to check for paused sessions.
37
109
 
110
+ Checks in priority order:
111
+ 1. ACTIVE-PAUSE.jsonl (incremental auto-pause)
112
+ 2. Regular paused sessions (session-*.json)
113
+
38
114
  Returns:
39
115
  Session data if paused session found, None otherwise
40
116
  """
@@ -44,7 +120,15 @@ class SessionResumeStartupHook:
44
120
  logger.debug("Session already displayed, skipping")
45
121
  return None
46
122
 
47
- # Check for paused sessions
123
+ # PRIORITY 1: Check for active incremental pause FIRST
124
+ active_pause_info = self.check_for_active_pause()
125
+ if active_pause_info:
126
+ self.display_active_pause_warning(active_pause_info)
127
+ self._session_displayed = True
128
+ logger.info("Active pause session detected and displayed")
129
+ return active_pause_info
130
+
131
+ # PRIORITY 2: Fall back to regular paused sessions
48
132
  session_data = self.resume_helper.check_and_display_resume_prompt()
49
133
 
50
134
  if session_data:
claude_mpm/init.py CHANGED
@@ -216,7 +216,7 @@ class ProjectInitializer:
216
216
  if deploy_result.deployed:
217
217
  print(
218
218
  f"✓ Deployed {len(deploy_result.deployed)} PM skill(s) "
219
- f"to .claude-mpm/skills/pm/"
219
+ f"to .claude/skills/"
220
220
  )
221
221
  else:
222
222
  self.logger.warning(