claude-mpm 5.4.65__py3-none-any.whl → 5.6.10__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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +66 -241
- claude_mpm/agents/CLAUDE_MPM_RESEARCH_OUTPUT_STYLE.md +413 -0
- claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +107 -1928
- claude_mpm/agents/PM_INSTRUCTIONS.md +119 -689
- claude_mpm/agents/WORKFLOW.md +2 -0
- claude_mpm/agents/templates/circuit-breakers.md +26 -17
- claude_mpm/cli/__init__.py +5 -1
- claude_mpm/cli/commands/agents.py +2 -4
- claude_mpm/cli/commands/agents_reconcile.py +197 -0
- claude_mpm/cli/commands/autotodos.py +566 -0
- claude_mpm/cli/commands/commander.py +46 -0
- claude_mpm/cli/commands/configure.py +620 -21
- 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/commands/skill_source.py +51 -2
- claude_mpm/cli/commands/skills.py +171 -17
- claude_mpm/cli/executor.py +120 -16
- claude_mpm/cli/interactive/__init__.py +10 -0
- claude_mpm/cli/interactive/agent_wizard.py +30 -50
- claude_mpm/cli/interactive/questionary_styles.py +65 -0
- claude_mpm/cli/interactive/skill_selector.py +481 -0
- claude_mpm/cli/parsers/base_parser.py +76 -1
- claude_mpm/cli/parsers/commander_parser.py +83 -0
- claude_mpm/cli/parsers/run_parser.py +10 -0
- claude_mpm/cli/parsers/skill_source_parser.py +4 -0
- claude_mpm/cli/parsers/skills_parser.py +5 -0
- claude_mpm/cli/startup.py +203 -359
- 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 +133 -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 +228 -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/config/skill_sources.py +16 -0
- claude_mpm/constants.py +1 -0
- claude_mpm/core/claude_runner.py +2 -2
- claude_mpm/core/config.py +32 -19
- claude_mpm/core/hook_manager.py +51 -3
- claude_mpm/core/interactive_session.py +7 -7
- claude_mpm/core/logger.py +26 -9
- claude_mpm/core/logging_utils.py +35 -11
- claude_mpm/core/output_style_manager.py +31 -13
- claude_mpm/core/unified_config.py +54 -8
- claude_mpm/core/unified_paths.py +95 -90
- 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/__pycache__/__init__.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/auto_pause_handler.py +485 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +283 -87
- 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 +116 -8
- claude_mpm/hooks/claude_hooks/memory_integration.py +51 -31
- claude_mpm/hooks/claude_hooks/response_tracking.py +42 -59
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +39 -24
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +36 -103
- claude_mpm/hooks/claude_hooks/services/state_manager.py +23 -36
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +73 -75
- claude_mpm/hooks/session_resume_hook.py +89 -1
- claude_mpm/hooks/templates/pre_tool_use_template.py +10 -2
- claude_mpm/init.py +1 -1
- claude_mpm/scripts/claude-hook-handler.sh +43 -16
- claude_mpm/services/agents/agent_recommendation_service.py +8 -8
- claude_mpm/services/agents/agent_selection_service.py +2 -2
- claude_mpm/services/agents/cache_git_manager.py +1 -1
- claude_mpm/services/agents/deployment/deployment_reconciler.py +577 -0
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +3 -0
- claude_mpm/services/agents/deployment/startup_reconciliation.py +138 -0
- claude_mpm/services/agents/loading/framework_agent_loader.py +75 -2
- claude_mpm/services/agents/single_tier_deployment_service.py +4 -4
- claude_mpm/services/agents/startup_sync.py +5 -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 +135 -11
- claude_mpm/services/skills/selective_skill_deployer.py +142 -26
- claude_mpm/services/skills/skill_discovery_service.py +74 -4
- claude_mpm/services/skills_deployer.py +31 -5
- 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-bug-reporting/SKILL.md +248 -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/mpm-teaching-mode/SKILL.md +657 -0
- 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/utils/agent_dependency_loader.py +4 -2
- claude_mpm/utils/robust_installer.py +10 -6
- claude_mpm-5.6.10.dist-info/METADATA +391 -0
- {claude_mpm-5.4.65.dist-info → claude_mpm-5.6.10.dist-info}/RECORD +303 -181
- 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.65.dist-info/METADATA +0 -999
- /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.65.dist-info → claude_mpm-5.6.10.dist-info}/WHEEL +0 -0
- {claude_mpm-5.4.65.dist-info → claude_mpm-5.6.10.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.4.65.dist-info → claude_mpm-5.6.10.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.65.dist-info → claude_mpm-5.6.10.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.4.65.dist-info → claude_mpm-5.6.10.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,12 +10,10 @@ 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
|
|
|
17
|
-
from ...core.logger import get_logger
|
|
18
|
-
|
|
19
17
|
|
|
20
18
|
class HookInstaller:
|
|
21
19
|
"""Manages installation and configuration of Claude MPM hooks."""
|
|
@@ -194,10 +192,17 @@ main "$@"
|
|
|
194
192
|
MIN_CLAUDE_VERSION = "1.0.92"
|
|
195
193
|
# Minimum version for PreToolUse input modification support
|
|
196
194
|
MIN_PRETOOL_MODIFY_VERSION = "2.0.30"
|
|
195
|
+
# Minimum version for user-invocable skills support
|
|
196
|
+
MIN_SKILLS_VERSION = "2.1.3"
|
|
197
197
|
|
|
198
198
|
def __init__(self):
|
|
199
199
|
"""Initialize the hook installer."""
|
|
200
|
-
|
|
200
|
+
# Use __name__ directly to avoid double prefix
|
|
201
|
+
# __name__ is already 'claude_mpm.hooks.claude_hooks.installer'
|
|
202
|
+
# get_logger() adds 'claude_mpm.' prefix, causing duplicate
|
|
203
|
+
import logging
|
|
204
|
+
|
|
205
|
+
self.logger = logging.getLogger(__name__)
|
|
201
206
|
self.claude_dir = Path.home() / ".claude"
|
|
202
207
|
self.hooks_dir = self.claude_dir / "hooks" # Kept for backward compatibility
|
|
203
208
|
# Use settings.json for hooks (Claude Code reads from this file)
|
|
@@ -220,7 +225,7 @@ main "$@"
|
|
|
220
225
|
|
|
221
226
|
try:
|
|
222
227
|
# Run claude --version command
|
|
223
|
-
result = subprocess.run(
|
|
228
|
+
result = subprocess.run( # nosec B607 B603 - Safe: hardcoded command, no user input
|
|
224
229
|
["claude", "--version"],
|
|
225
230
|
capture_output=True,
|
|
226
231
|
text=True,
|
|
@@ -331,6 +336,53 @@ main "$@"
|
|
|
331
336
|
|
|
332
337
|
return True
|
|
333
338
|
|
|
339
|
+
def _version_meets_minimum(self, version: str, min_version: str) -> bool:
|
|
340
|
+
"""Check if a version meets minimum requirements.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
version: Current version string (e.g., "2.1.3")
|
|
344
|
+
min_version: Minimum required version string (e.g., "2.1.3")
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
True if version meets or exceeds minimum, False otherwise
|
|
348
|
+
"""
|
|
349
|
+
|
|
350
|
+
def parse_version(v: str) -> List[int]:
|
|
351
|
+
"""Parse semantic version string to list of integers."""
|
|
352
|
+
try:
|
|
353
|
+
return [int(x) for x in v.split(".")]
|
|
354
|
+
except (ValueError, AttributeError):
|
|
355
|
+
return [0]
|
|
356
|
+
|
|
357
|
+
current = parse_version(version)
|
|
358
|
+
required = parse_version(min_version)
|
|
359
|
+
|
|
360
|
+
# Compare versions
|
|
361
|
+
for i in range(max(len(current), len(required))):
|
|
362
|
+
curr_part = current[i] if i < len(current) else 0
|
|
363
|
+
req_part = required[i] if i < len(required) else 0
|
|
364
|
+
|
|
365
|
+
if curr_part < req_part:
|
|
366
|
+
return False
|
|
367
|
+
if curr_part > req_part:
|
|
368
|
+
return True
|
|
369
|
+
|
|
370
|
+
return True
|
|
371
|
+
|
|
372
|
+
def supports_user_invocable_skills(self) -> bool:
|
|
373
|
+
"""Check if Claude Code version supports user-invocable skills.
|
|
374
|
+
|
|
375
|
+
User-invocable skills were added in Claude Code v2.1.3.
|
|
376
|
+
This feature allows users to invoke skills via slash commands.
|
|
377
|
+
|
|
378
|
+
Returns:
|
|
379
|
+
True if version supports user-invocable skills, False otherwise
|
|
380
|
+
"""
|
|
381
|
+
version = self.get_claude_version()
|
|
382
|
+
if not version:
|
|
383
|
+
return False
|
|
384
|
+
return self._version_meets_minimum(version, self.MIN_SKILLS_VERSION)
|
|
385
|
+
|
|
334
386
|
def get_hook_script_path(self) -> Path:
|
|
335
387
|
"""Get the path to the hook handler script based on installation method.
|
|
336
388
|
|
|
@@ -485,6 +537,44 @@ main "$@"
|
|
|
485
537
|
except Exception as e:
|
|
486
538
|
self.logger.warning(f"Could not clean up old settings file: {e}")
|
|
487
539
|
|
|
540
|
+
def _fix_status_line(self, settings: Dict) -> None:
|
|
541
|
+
"""Fix statusLine command to handle both output style schema formats.
|
|
542
|
+
|
|
543
|
+
The statusLine command receives input in different formats:
|
|
544
|
+
- Newer format: {"activeOutputStyle": "Claude MPM", ...}
|
|
545
|
+
- Older format: {"output_style": {"name": "Claude MPM"}, ...}
|
|
546
|
+
|
|
547
|
+
This method ensures the jq expression checks both locations.
|
|
548
|
+
|
|
549
|
+
Args:
|
|
550
|
+
settings: The settings dictionary to update
|
|
551
|
+
"""
|
|
552
|
+
if "statusLine" not in settings:
|
|
553
|
+
return
|
|
554
|
+
|
|
555
|
+
status_line = settings.get("statusLine", {})
|
|
556
|
+
if "command" not in status_line:
|
|
557
|
+
return
|
|
558
|
+
|
|
559
|
+
command = status_line["command"]
|
|
560
|
+
|
|
561
|
+
# Pattern to match: '.output_style.name // "default"'
|
|
562
|
+
# We need to update it to: '.output_style.name // .activeOutputStyle // "default"'
|
|
563
|
+
old_pattern = r'\.output_style\.name\s*//\s*"default"'
|
|
564
|
+
new_pattern = '.output_style.name // .activeOutputStyle // "default"'
|
|
565
|
+
|
|
566
|
+
# Check if the command needs updating
|
|
567
|
+
if re.search(old_pattern, command) and ".activeOutputStyle" not in command:
|
|
568
|
+
updated_command = re.sub(old_pattern, new_pattern, command)
|
|
569
|
+
settings["statusLine"]["command"] = updated_command
|
|
570
|
+
self.logger.info(
|
|
571
|
+
"Fixed statusLine command to handle both output style schemas"
|
|
572
|
+
)
|
|
573
|
+
else:
|
|
574
|
+
self.logger.debug(
|
|
575
|
+
"StatusLine command already supports both schemas or not present"
|
|
576
|
+
)
|
|
577
|
+
|
|
488
578
|
def _update_claude_settings(self, hook_script_path: Path) -> None:
|
|
489
579
|
"""Update Claude settings to use the installed hook."""
|
|
490
580
|
self.logger.info("Updating Claude settings...")
|
|
@@ -546,6 +636,9 @@ main "$@"
|
|
|
546
636
|
}
|
|
547
637
|
]
|
|
548
638
|
|
|
639
|
+
# Fix statusLine command to handle both output style schemas
|
|
640
|
+
self._fix_status_line(settings)
|
|
641
|
+
|
|
549
642
|
# Write settings to settings.json
|
|
550
643
|
with self.settings_file.open("w") as f:
|
|
551
644
|
json.dump(settings, f, indent=2)
|
|
@@ -556,7 +649,22 @@ main "$@"
|
|
|
556
649
|
self._cleanup_old_settings()
|
|
557
650
|
|
|
558
651
|
def _install_commands(self) -> None:
|
|
559
|
-
"""Install custom commands for Claude Code.
|
|
652
|
+
"""Install custom commands for Claude Code.
|
|
653
|
+
|
|
654
|
+
For Claude Code >= 2.1.3, commands are deployed as skills via PMSkillsDeployerService.
|
|
655
|
+
This method provides backward compatibility for older versions.
|
|
656
|
+
"""
|
|
657
|
+
# Check if skills-based commands are supported
|
|
658
|
+
if self.supports_user_invocable_skills():
|
|
659
|
+
self.logger.info(
|
|
660
|
+
"Claude Code >= 2.1.3 detected. Commands deployed as skills - "
|
|
661
|
+
"skipping legacy command installation."
|
|
662
|
+
)
|
|
663
|
+
return
|
|
664
|
+
|
|
665
|
+
# Legacy installation for older Claude Code versions
|
|
666
|
+
self.logger.info("Installing legacy commands for Claude Code < 2.1.3")
|
|
667
|
+
|
|
560
668
|
# Find commands directory using proper resource resolution
|
|
561
669
|
try:
|
|
562
670
|
from ...core.unified_paths import get_package_resource_path
|
|
@@ -782,7 +890,7 @@ main "$@"
|
|
|
782
890
|
if "hooks" in settings:
|
|
783
891
|
status["configured_events"] = list(settings["hooks"].keys())
|
|
784
892
|
configured_in_local = True
|
|
785
|
-
except Exception:
|
|
893
|
+
except Exception: # nosec B110 - Intentional: ignore errors reading settings file
|
|
786
894
|
pass
|
|
787
895
|
|
|
788
896
|
# Also check old settings file
|
|
@@ -796,7 +904,7 @@ main "$@"
|
|
|
796
904
|
status["warning"] = (
|
|
797
905
|
"Hooks found in settings.local.json but Claude Code reads from settings.json"
|
|
798
906
|
)
|
|
799
|
-
except Exception:
|
|
907
|
+
except Exception: # nosec B110 - Intentional: ignore errors reading old settings file
|
|
800
908
|
pass
|
|
801
909
|
|
|
802
910
|
status["settings_location"] = (
|