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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +8 -5
- claude_mpm/agents/{CLAUDE_MPM_FOUNDERS_OUTPUT_STYLE.md → CLAUDE_MPM_RESEARCH_OUTPUT_STYLE.md} +14 -6
- claude_mpm/agents/PM_INSTRUCTIONS.md +101 -703
- claude_mpm/agents/WORKFLOW.md +2 -0
- claude_mpm/agents/templates/circuit-breakers.md +26 -17
- claude_mpm/cli/commands/autotodos.py +566 -0
- claude_mpm/cli/commands/commander.py +46 -0
- claude_mpm/cli/commands/hook_errors.py +60 -60
- claude_mpm/cli/commands/monitor.py +2 -2
- claude_mpm/cli/commands/mpm_init/core.py +2 -2
- claude_mpm/cli/commands/run.py +35 -3
- claude_mpm/cli/executor.py +119 -16
- claude_mpm/cli/parsers/base_parser.py +71 -1
- claude_mpm/cli/parsers/commander_parser.py +83 -0
- claude_mpm/cli/parsers/run_parser.py +10 -0
- claude_mpm/cli/startup.py +54 -16
- claude_mpm/cli/startup_display.py +72 -5
- claude_mpm/cli/startup_logging.py +2 -2
- claude_mpm/cli/utils.py +7 -3
- claude_mpm/commander/__init__.py +72 -0
- claude_mpm/commander/adapters/__init__.py +31 -0
- claude_mpm/commander/adapters/base.py +191 -0
- claude_mpm/commander/adapters/claude_code.py +361 -0
- claude_mpm/commander/adapters/communication.py +366 -0
- claude_mpm/commander/api/__init__.py +16 -0
- claude_mpm/commander/api/app.py +105 -0
- claude_mpm/commander/api/errors.py +112 -0
- claude_mpm/commander/api/routes/__init__.py +8 -0
- claude_mpm/commander/api/routes/events.py +184 -0
- claude_mpm/commander/api/routes/inbox.py +171 -0
- claude_mpm/commander/api/routes/messages.py +148 -0
- claude_mpm/commander/api/routes/projects.py +271 -0
- claude_mpm/commander/api/routes/sessions.py +215 -0
- claude_mpm/commander/api/routes/work.py +260 -0
- claude_mpm/commander/api/schemas.py +182 -0
- claude_mpm/commander/chat/__init__.py +7 -0
- claude_mpm/commander/chat/cli.py +107 -0
- claude_mpm/commander/chat/commands.py +96 -0
- claude_mpm/commander/chat/repl.py +310 -0
- claude_mpm/commander/config.py +49 -0
- claude_mpm/commander/config_loader.py +115 -0
- claude_mpm/commander/daemon.py +398 -0
- claude_mpm/commander/events/__init__.py +26 -0
- claude_mpm/commander/events/manager.py +332 -0
- claude_mpm/commander/frameworks/__init__.py +12 -0
- claude_mpm/commander/frameworks/base.py +143 -0
- claude_mpm/commander/frameworks/claude_code.py +58 -0
- claude_mpm/commander/frameworks/mpm.py +62 -0
- claude_mpm/commander/inbox/__init__.py +16 -0
- claude_mpm/commander/inbox/dedup.py +128 -0
- claude_mpm/commander/inbox/inbox.py +224 -0
- claude_mpm/commander/inbox/models.py +70 -0
- claude_mpm/commander/instance_manager.py +337 -0
- claude_mpm/commander/llm/__init__.py +6 -0
- claude_mpm/commander/llm/openrouter_client.py +167 -0
- claude_mpm/commander/llm/summarizer.py +70 -0
- claude_mpm/commander/models/__init__.py +18 -0
- claude_mpm/commander/models/events.py +121 -0
- claude_mpm/commander/models/project.py +162 -0
- claude_mpm/commander/models/work.py +214 -0
- claude_mpm/commander/parsing/__init__.py +20 -0
- claude_mpm/commander/parsing/extractor.py +132 -0
- claude_mpm/commander/parsing/output_parser.py +270 -0
- claude_mpm/commander/parsing/patterns.py +100 -0
- claude_mpm/commander/persistence/__init__.py +11 -0
- claude_mpm/commander/persistence/event_store.py +274 -0
- claude_mpm/commander/persistence/state_store.py +309 -0
- claude_mpm/commander/persistence/work_store.py +164 -0
- claude_mpm/commander/polling/__init__.py +13 -0
- claude_mpm/commander/polling/event_detector.py +104 -0
- claude_mpm/commander/polling/output_buffer.py +49 -0
- claude_mpm/commander/polling/output_poller.py +153 -0
- claude_mpm/commander/project_session.py +268 -0
- claude_mpm/commander/proxy/__init__.py +12 -0
- claude_mpm/commander/proxy/formatter.py +89 -0
- claude_mpm/commander/proxy/output_handler.py +191 -0
- claude_mpm/commander/proxy/relay.py +155 -0
- claude_mpm/commander/registry.py +404 -0
- claude_mpm/commander/runtime/__init__.py +10 -0
- claude_mpm/commander/runtime/executor.py +191 -0
- claude_mpm/commander/runtime/monitor.py +316 -0
- claude_mpm/commander/session/__init__.py +6 -0
- claude_mpm/commander/session/context.py +81 -0
- claude_mpm/commander/session/manager.py +59 -0
- claude_mpm/commander/tmux_orchestrator.py +361 -0
- claude_mpm/commander/web/__init__.py +1 -0
- claude_mpm/commander/work/__init__.py +30 -0
- claude_mpm/commander/work/executor.py +189 -0
- claude_mpm/commander/work/queue.py +405 -0
- claude_mpm/commander/workflow/__init__.py +27 -0
- claude_mpm/commander/workflow/event_handler.py +219 -0
- claude_mpm/commander/workflow/notifier.py +146 -0
- claude_mpm/commands/mpm-config.md +8 -0
- claude_mpm/commands/mpm-doctor.md +8 -0
- claude_mpm/commands/mpm-help.md +8 -0
- claude_mpm/commands/mpm-init.md +8 -0
- claude_mpm/commands/mpm-monitor.md +8 -0
- claude_mpm/commands/mpm-organize.md +8 -0
- claude_mpm/commands/mpm-postmortem.md +8 -0
- claude_mpm/commands/mpm-session-resume.md +9 -1
- claude_mpm/commands/mpm-status.md +8 -0
- claude_mpm/commands/mpm-ticket-view.md +8 -0
- claude_mpm/commands/mpm-version.md +8 -0
- claude_mpm/commands/mpm.md +8 -0
- claude_mpm/config/agent_presets.py +8 -7
- claude_mpm/core/config.py +5 -0
- claude_mpm/core/hook_manager.py +51 -3
- claude_mpm/core/logger.py +10 -7
- claude_mpm/core/logging_utils.py +4 -2
- claude_mpm/core/output_style_manager.py +15 -5
- claude_mpm/core/unified_config.py +10 -6
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.C33zOoyM.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.CW1J-YuA.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cs_tUR18.js → 1WZnGYqX.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CDuw-vjf.js → 67pF3qNn.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bTOqqlTd.js → 6RxdMKe4.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DwBR2MJi.js → 8cZrfX0h.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{ZGh7QtNv.js → 9a6T2nm-.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D9lljYKQ.js → B443AUzu.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{RJiighC3.js → B8AwtY2H.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{uuIeMWc-.js → BF15LAsF.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D3k0OPJN.js → BRcwIQNr.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CyWMqx4W.js → BV6nKitt.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CiIAseT4.js → BViJ8lZt.js} +5 -5
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CBBdVcY8.js → BcQ-Q0FE.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BovzEFCE.js → Bpyvgze_.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BzTRqg-z.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C0Fr8dve.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{eNVUfhuA.js → C3rbW_a-.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{GYwsonyD.js → C8WYN38h.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BIF9m_hv.js → C9I8FlXH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B0uc0UOD.js → CIQcWgO2.js} +3 -3
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Be7GpZd6.js → CIctN7YN.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Bh0LDWpI.js → CKrS_JZW.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DUrLdbGD.js → CR6P9C4A.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7xVLGWV.js → CRRR9MD_.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dhb8PKl3.js → CSXtMOf0.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BPYeabCQ.js → CT-sbxSk.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{sQeU3Y1z.js → CWm6DJsp.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CnA0NrzZ.js → CpqQ1Kzn.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4B-KCzX.js → D2nGpDRe.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DGkLK5U1.js → D9iCMida.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BofRWZRR.js → D9ykgMoY.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DmxopI1J.js → DL2Ldur1.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C30mlcqg.js → DPfltzjH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Vzk33B_K.js → DR8nis88.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DI7hHRFL.js → DUliQN2b.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4JcI4KD.js → DXlhR01x.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bT1r9zLR.js → D_lyTybS.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DZX00Y4g.js → DngoTTgh.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzZX-COe.js → DqkmHtDC.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7RN905-.js → DsDh8EYs.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DLVjFsZ3.js → DypDmXgd.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{iEWssX7S.js → IPYC-LnN.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JTLiF7dt.js +24 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DaimHw_p.js → JpevfAFt.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DY1XQ8fi.js → R8CEIRAd.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dle-35c7.js → Zxy7qc-l.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C_Usid8X.js → qtd3IeO4.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzeYkLYB.js → ulBFON_C.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cfqx1Qun.js → wQVh1CoA.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/{app.D6-I5TpK.js → app.Dr7t0z2J.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.m1gL8KXf.js → 0.RgBboRvH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{1.CgNOuw-d.js → 1.DG-KkbDf.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.D_jnf-x6.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -1
- claude_mpm/dashboard/static/svelte-build/index.html +9 -9
- claude_mpm/experimental/cli_enhancements.py +2 -1
- claude_mpm/hooks/claude_hooks/INTEGRATION_EXAMPLE.md +243 -0
- claude_mpm/hooks/claude_hooks/README_AUTO_PAUSE.md +403 -0
- claude_mpm/hooks/claude_hooks/auto_pause_handler.py +486 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +250 -11
- claude_mpm/hooks/claude_hooks/hook_handler.py +106 -89
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
- claude_mpm/hooks/claude_hooks/installer.py +69 -5
- claude_mpm/hooks/claude_hooks/response_tracking.py +3 -1
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +20 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +14 -77
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +30 -6
- claude_mpm/hooks/session_resume_hook.py +85 -1
- claude_mpm/init.py +1 -1
- claude_mpm/scripts/claude-hook-handler.sh +36 -10
- claude_mpm/services/agents/agent_recommendation_service.py +8 -8
- claude_mpm/services/agents/cache_git_manager.py +1 -1
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +3 -0
- claude_mpm/services/agents/loading/framework_agent_loader.py +75 -2
- claude_mpm/services/cli/__init__.py +3 -0
- claude_mpm/services/cli/incremental_pause_manager.py +561 -0
- claude_mpm/services/cli/session_resume_helper.py +10 -2
- claude_mpm/services/delegation_detector.py +175 -0
- claude_mpm/services/diagnostics/checks/agent_sources_check.py +30 -0
- claude_mpm/services/diagnostics/checks/configuration_check.py +24 -0
- claude_mpm/services/diagnostics/checks/installation_check.py +22 -0
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +23 -0
- claude_mpm/services/diagnostics/doctor_reporter.py +31 -1
- claude_mpm/services/diagnostics/models.py +14 -1
- claude_mpm/services/event_log.py +325 -0
- claude_mpm/services/infrastructure/__init__.py +4 -0
- claude_mpm/services/infrastructure/context_usage_tracker.py +291 -0
- claude_mpm/services/infrastructure/resume_log_generator.py +24 -5
- claude_mpm/services/monitor/daemon_manager.py +15 -4
- claude_mpm/services/monitor/management/lifecycle.py +8 -2
- claude_mpm/services/monitor/server.py +106 -16
- claude_mpm/services/pm_skills_deployer.py +259 -87
- claude_mpm/services/skills/git_skill_source_manager.py +51 -2
- claude_mpm/services/skills/selective_skill_deployer.py +114 -16
- claude_mpm/services/skills/skill_discovery_service.py +57 -3
- claude_mpm/services/socketio/handlers/hook.py +14 -7
- claude_mpm/services/socketio/server/main.py +12 -4
- claude_mpm/skills/bundled/pm/mpm/SKILL.md +38 -0
- claude_mpm/skills/bundled/pm/mpm-agent-update-workflow/SKILL.md +75 -0
- claude_mpm/skills/bundled/pm/mpm-circuit-breaker-enforcement/SKILL.md +476 -0
- claude_mpm/skills/bundled/pm/mpm-config/SKILL.md +29 -0
- claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
- claude_mpm/skills/bundled/pm/mpm-help/SKILL.md +35 -0
- claude_mpm/skills/bundled/pm/mpm-init/SKILL.md +125 -0
- claude_mpm/skills/bundled/pm/mpm-monitor/SKILL.md +32 -0
- claude_mpm/skills/bundled/pm/mpm-organize/SKILL.md +121 -0
- claude_mpm/skills/bundled/pm/mpm-postmortem/SKILL.md +22 -0
- claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
- claude_mpm/skills/bundled/pm/mpm-session-resume/SKILL.md +31 -0
- claude_mpm/skills/bundled/pm/mpm-status/SKILL.md +37 -0
- claude_mpm/skills/bundled/pm/{pm-teaching-mode → mpm-teaching-mode}/SKILL.md +2 -2
- claude_mpm/skills/bundled/pm/mpm-ticket-view/SKILL.md +110 -0
- claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
- claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
- claude_mpm/skills/skill_manager.py +4 -4
- claude_mpm-5.6.1.dist-info/METADATA +391 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/RECORD +244 -145
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +0 -24
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +0 -323
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +0 -1
- claude_mpm-5.4.85.dist-info/METADATA +0 -1023
- /claude_mpm/skills/bundled/pm/{pm-bug-reporting/pm-bug-reporting.md → mpm-bug-reporting/SKILL.md} +0 -0
- /claude_mpm/skills/bundled/pm/{pm-delegation-patterns → mpm-delegation-patterns}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-git-file-tracking → mpm-git-file-tracking}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-pr-workflow → mpm-pr-workflow}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-ticketing-integration → mpm-ticketing-integration}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-verification-protocols → mpm-verification-protocols}/SKILL.md +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/WHEEL +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {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
|
|
64
|
-
|
|
65
|
-
|
|
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", "
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
-
|
|
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
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
411
|
-
|
|
412
|
-
|
|
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.
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
570
|
-
|
|
571
|
-
|
|
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 #
|
|
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
|
|
650
|
-
|
|
651
|
-
|
|
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
|
-
|
|
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
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
52
|
-
#
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
#
|
|
62
|
-
exit
|
|
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"
|