claude-mpm 5.0.9__py3-none-any.whl → 5.6.23__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/__init__.py +4 -0
- claude_mpm/agents/BASE_AGENT.md +164 -0
- claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +115 -0
- claude_mpm/agents/CLAUDE_MPM_RESEARCH_OUTPUT_STYLE.md +413 -0
- claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +186 -0
- claude_mpm/agents/MEMORY.md +1 -1
- claude_mpm/agents/PM_INSTRUCTIONS.md +479 -616
- claude_mpm/agents/WORKFLOW.md +6 -253
- claude_mpm/agents/agent_loader.py +13 -44
- claude_mpm/agents/base_agent.json +1 -1
- claude_mpm/agents/frontmatter_validator.py +70 -2
- claude_mpm/agents/templates/circuit-breakers.md +457 -62
- claude_mpm/cli/__init__.py +5 -2
- claude_mpm/cli/__main__.py +4 -0
- claude_mpm/cli/chrome_devtools_installer.py +175 -0
- claude_mpm/cli/commands/agent_state_manager.py +18 -27
- claude_mpm/cli/commands/agents.py +177 -41
- claude_mpm/cli/commands/agents_reconcile.py +197 -0
- claude_mpm/cli/commands/auto_configure.py +723 -236
- claude_mpm/cli/commands/autotodos.py +566 -0
- claude_mpm/cli/commands/commander.py +216 -0
- claude_mpm/cli/commands/config.py +88 -2
- claude_mpm/cli/commands/configure.py +1874 -170
- claude_mpm/cli/commands/configure_agent_display.py +27 -6
- 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 +232 -46
- claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
- claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
- claude_mpm/cli/commands/postmortem.py +1 -1
- claude_mpm/cli/commands/profile.py +276 -0
- claude_mpm/cli/commands/run.py +35 -3
- claude_mpm/cli/commands/skill_source.py +51 -2
- claude_mpm/cli/commands/skills.py +379 -204
- claude_mpm/cli/commands/summarize.py +413 -0
- claude_mpm/cli/executor.py +141 -19
- claude_mpm/cli/interactive/__init__.py +10 -0
- claude_mpm/cli/interactive/agent_wizard.py +115 -60
- claude_mpm/cli/interactive/questionary_styles.py +65 -0
- claude_mpm/cli/interactive/skill_selector.py +481 -0
- claude_mpm/cli/parsers/agents_parser.py +54 -9
- claude_mpm/cli/parsers/auto_configure_parser.py +13 -138
- claude_mpm/cli/parsers/base_parser.py +88 -1
- claude_mpm/cli/parsers/commander_parser.py +116 -0
- claude_mpm/cli/parsers/config_parser.py +153 -83
- claude_mpm/cli/parsers/profile_parser.py +147 -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 +1 -1
- claude_mpm/cli/startup.py +1017 -266
- claude_mpm/cli/startup_display.py +74 -6
- claude_mpm/cli/startup_logging.py +2 -2
- claude_mpm/cli/utils.py +7 -3
- claude_mpm/commander/__init__.py +78 -0
- claude_mpm/commander/adapters/__init__.py +60 -0
- claude_mpm/commander/adapters/auggie.py +260 -0
- claude_mpm/commander/adapters/base.py +288 -0
- claude_mpm/commander/adapters/claude_code.py +392 -0
- claude_mpm/commander/adapters/codex.py +237 -0
- claude_mpm/commander/adapters/communication.py +366 -0
- claude_mpm/commander/adapters/example_usage.py +310 -0
- claude_mpm/commander/adapters/mpm.py +389 -0
- claude_mpm/commander/adapters/registry.py +204 -0
- claude_mpm/commander/api/__init__.py +16 -0
- claude_mpm/commander/api/app.py +121 -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 +226 -0
- claude_mpm/commander/api/routes/work.py +296 -0
- claude_mpm/commander/api/schemas.py +186 -0
- claude_mpm/commander/chat/__init__.py +7 -0
- claude_mpm/commander/chat/cli.py +146 -0
- claude_mpm/commander/chat/commands.py +96 -0
- claude_mpm/commander/chat/repl.py +310 -0
- claude_mpm/commander/config.py +51 -0
- claude_mpm/commander/config_loader.py +115 -0
- claude_mpm/commander/core/__init__.py +10 -0
- claude_mpm/commander/core/block_manager.py +325 -0
- claude_mpm/commander/core/response_manager.py +323 -0
- claude_mpm/commander/daemon.py +603 -0
- claude_mpm/commander/env_loader.py +59 -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 +146 -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 +450 -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/memory/__init__.py +45 -0
- claude_mpm/commander/memory/compression.py +347 -0
- claude_mpm/commander/memory/embeddings.py +230 -0
- claude_mpm/commander/memory/entities.py +310 -0
- claude_mpm/commander/memory/example_usage.py +290 -0
- claude_mpm/commander/memory/integration.py +325 -0
- claude_mpm/commander/memory/search.py +381 -0
- claude_mpm/commander/memory/store.py +657 -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 +410 -0
- claude_mpm/commander/runtime/__init__.py +10 -0
- claude_mpm/commander/runtime/executor.py +191 -0
- claude_mpm/commander/runtime/monitor.py +346 -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 +207 -0
- claude_mpm/commander/work/queue.py +405 -0
- claude_mpm/commander/workflow/__init__.py +27 -0
- claude_mpm/commander/workflow/event_handler.py +241 -0
- claude_mpm/commander/workflow/notifier.py +146 -0
- claude_mpm/commands/mpm-config.md +36 -0
- claude_mpm/commands/mpm-doctor.md +16 -21
- claude_mpm/commands/mpm-help.md +12 -286
- claude_mpm/commands/mpm-init.md +88 -506
- claude_mpm/commands/mpm-monitor.md +22 -401
- claude_mpm/commands/mpm-organize.md +128 -0
- claude_mpm/commands/mpm-postmortem.md +13 -107
- claude_mpm/commands/mpm-session-resume.md +20 -363
- claude_mpm/commands/mpm-status.md +13 -69
- claude_mpm/commands/mpm-ticket-view.md +60 -495
- claude_mpm/commands/mpm-version.md +13 -107
- claude_mpm/commands/mpm.md +8 -0
- claude_mpm/config/agent_presets.py +8 -7
- claude_mpm/config/agent_sources.py +27 -0
- claude_mpm/config/skill_sources.py +16 -0
- claude_mpm/constants.py +1 -0
- claude_mpm/core/claude_runner.py +154 -2
- claude_mpm/core/config.py +37 -26
- claude_mpm/core/config_constants.py +74 -9
- claude_mpm/core/constants.py +56 -12
- claude_mpm/core/framework/formatters/content_formatter.py +3 -13
- claude_mpm/core/framework/loaders/agent_loader.py +8 -5
- claude_mpm/core/framework/loaders/instruction_loader.py +52 -11
- claude_mpm/core/framework_loader.py +4 -2
- claude_mpm/core/hook_manager.py +51 -3
- claude_mpm/core/interactive_session.py +12 -11
- claude_mpm/core/logger.py +39 -9
- claude_mpm/core/logging_utils.py +35 -11
- claude_mpm/core/network_config.py +148 -0
- claude_mpm/core/oneshot_session.py +7 -6
- claude_mpm/core/optimized_startup.py +61 -0
- claude_mpm/core/output_style_manager.py +219 -44
- claude_mpm/core/shared/config_loader.py +3 -1
- claude_mpm/core/socketio_pool.py +16 -8
- claude_mpm/core/unified_agent_registry.py +134 -16
- claude_mpm/core/unified_config.py +76 -8
- claude_mpm/core/unified_paths.py +95 -90
- claude_mpm/dashboard/static/svelte-build/_app/env.js +1 -0
- 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/1WZnGYqX.js +24 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/67pF3qNn.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/6RxdMKe4.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/8cZrfX0h.js +60 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/9a6T2nm-.js +7 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B443AUzu.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B8AwtY2H.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BF15LAsF.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BQaXIfA_.js +331 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BRcwIQNr.js +4 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BSNlmTZj.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BV6nKitt.js +43 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BViJ8lZt.js +128 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BcQ-Q0FE.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Bpyvgze_.js +30 -0
- 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/C3rbW_a-.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C8WYN38h.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C9I8FlXH.js +61 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIQcWgO2.js +36 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIctN7YN.js +7 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CKrS_JZW.js +145 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CR6P9C4A.js +89 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRRR9MD_.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CSXtMOf0.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CT-sbxSk.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CWm6DJsp.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CmKTTxBW.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CpqQ1Kzn.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cu_Erd72.js +261 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D2nGpDRe.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D9iCMida.js +267 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D9ykgMoY.js +10 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DL2Ldur1.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DPfltzjH.js +165 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DR8nis88.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DUliQN2b.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DVp1hx9R.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DXlhR01x.js +122 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D_lyTybS.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DngoTTgh.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DqkmHtDC.js +220 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DsDh8EYs.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DypDmXgd.js +139 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Gi6I4Gst.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/IPYC-LnN.js +162 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JTLiF7dt.js +24 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JpevfAFt.js +68 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/NqQ1dWOy.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/R8CEIRAd.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Zxy7qc-l.js +64 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/qtd3IeO4.js +15 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/ulBFON_C.js +65 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/wQVh1CoA.js +10 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.Dr7t0z2J.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/0.RgBboRvH.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.DG-KkbDf.js +1 -0
- 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 -0
- claude_mpm/dashboard/static/svelte-build/favicon.svg +7 -0
- claude_mpm/dashboard/static/svelte-build/index.html +36 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/fonts/generate_fonts.py +58 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_tfms.py +114 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_ttfs.py +122 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/format_json.py +28 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/parse_tfm.py +211 -0
- 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-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/auto_pause_handler.py +485 -0
- claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +479 -128
- claude_mpm/hooks/claude_hooks/hook_handler.py +254 -83
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
- claude_mpm/hooks/claude_hooks/installer.py +149 -18
- claude_mpm/hooks/claude_hooks/memory_integration.py +67 -19
- claude_mpm/hooks/claude_hooks/response_tracking.py +44 -62
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.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__/duplicate_detector.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +69 -30
- 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/kuzu_memory_hook.py +5 -5
- claude_mpm/hooks/memory_integration_hook.py +46 -1
- claude_mpm/hooks/session_resume_hook.py +89 -1
- claude_mpm/hooks/templates/pre_tool_use_template.py +10 -2
- claude_mpm/init.py +276 -19
- claude_mpm/models/agent_definition.py +7 -0
- claude_mpm/models/git_repository.py +3 -3
- claude_mpm/scripts/claude-hook-handler.sh +87 -20
- claude_mpm/scripts/launch_monitor.py +93 -13
- claude_mpm/scripts/start_activity_logging.py +0 -0
- claude_mpm/services/agents/agent_builder.py +3 -3
- claude_mpm/services/agents/agent_recommendation_service.py +278 -0
- claude_mpm/services/agents/agent_review_service.py +280 -0
- claude_mpm/services/agents/agent_selection_service.py +2 -2
- claude_mpm/services/agents/cache_git_manager.py +7 -7
- claude_mpm/services/agents/deployment/agent_deployment.py +29 -7
- claude_mpm/services/agents/deployment/agent_discovery_service.py +6 -5
- claude_mpm/services/agents/deployment/agent_format_converter.py +25 -13
- claude_mpm/services/agents/deployment/agent_template_builder.py +42 -20
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +2 -2
- claude_mpm/services/agents/deployment/async_agent_deployment.py +31 -27
- claude_mpm/services/agents/deployment/deployment_reconciler.py +577 -0
- claude_mpm/services/agents/deployment/local_template_deployment.py +3 -1
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +348 -29
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +570 -68
- claude_mpm/services/agents/deployment/startup_reconciliation.py +138 -0
- claude_mpm/services/agents/git_source_manager.py +57 -4
- claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
- claude_mpm/services/agents/loading/framework_agent_loader.py +75 -2
- claude_mpm/services/agents/recommender.py +5 -3
- claude_mpm/services/agents/single_tier_deployment_service.py +6 -6
- claude_mpm/services/agents/sources/git_source_sync_service.py +129 -11
- claude_mpm/services/agents/startup_sync.py +27 -4
- claude_mpm/services/agents/toolchain_detector.py +10 -6
- claude_mpm/services/analysis/__init__.py +11 -1
- claude_mpm/services/analysis/clone_detector.py +1030 -0
- 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/command_deployment_service.py +81 -10
- claude_mpm/services/delegation_detector.py +175 -0
- claude_mpm/services/diagnostics/checks/agent_check.py +2 -2
- claude_mpm/services/diagnostics/checks/agent_sources_check.py +31 -1
- 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_bus/config.py +3 -1
- claude_mpm/services/event_log.py +325 -0
- claude_mpm/services/git/git_operations_service.py +101 -16
- 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.py +9 -2
- claude_mpm/services/monitor/daemon_manager.py +54 -7
- claude_mpm/services/monitor/management/lifecycle.py +15 -3
- claude_mpm/services/monitor/server.py +796 -30
- claude_mpm/services/pm_skills_deployer.py +884 -0
- claude_mpm/services/profile_manager.py +337 -0
- claude_mpm/services/project/project_organizer.py +4 -0
- claude_mpm/services/self_upgrade_service.py +120 -12
- claude_mpm/services/skills/__init__.py +3 -0
- claude_mpm/services/skills/git_skill_source_manager.py +303 -12
- claude_mpm/services/skills/selective_skill_deployer.py +869 -0
- claude_mpm/services/skills/skill_discovery_service.py +74 -4
- claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
- claude_mpm/services/skills_deployer.py +294 -55
- claude_mpm/services/socketio/dashboard_server.py +1 -0
- claude_mpm/services/socketio/event_normalizer.py +51 -6
- claude_mpm/services/socketio/handlers/hook.py +14 -7
- claude_mpm/services/socketio/server/core.py +386 -108
- claude_mpm/services/socketio/server/main.py +12 -4
- claude_mpm/services/version_control/git_operations.py +103 -0
- claude_mpm/skills/__init__.py +2 -1
- claude_mpm/skills/bundled/collaboration/brainstorming/SKILL.md +79 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/SKILL.md +178 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/agent-prompts.md +577 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/coordination-patterns.md +467 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/examples.md +537 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/troubleshooting.md +730 -0
- claude_mpm/skills/bundled/collaboration/git-worktrees.md +317 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/SKILL.md +112 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/references/code-reviewer-template.md +146 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/references/review-examples.md +412 -0
- claude_mpm/skills/bundled/collaboration/stacked-prs.md +251 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/SKILL.md +81 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/references/best-practices.md +362 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/references/plan-structure-templates.md +312 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/SKILL.md +152 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/advanced-techniques.md +668 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/examples.md +587 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/integration.md +438 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/tracing-techniques.md +391 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/CREATION-LOG.md +119 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/SKILL.md +148 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/anti-patterns.md +483 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/examples.md +452 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/troubleshooting.md +449 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/workflow.md +411 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-academic.md +14 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-1.md +58 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-2.md +68 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-3.md +69 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/SKILL.md +131 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/gate-function.md +325 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/integration-and-workflows.md +490 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/red-flags-and-failures.md +425 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/verification-patterns.md +499 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/INTEGRATION.md +611 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/README.md +596 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/SKILL.md +260 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/examples/nextjs-env-structure.md +315 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/frameworks.md +436 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/security.md +433 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/synchronization.md +452 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/troubleshooting.md +404 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/validation.md +420 -0
- claude_mpm/skills/bundled/main/artifacts-builder/SKILL.md +86 -0
- claude_mpm/skills/bundled/main/internal-comms/SKILL.md +43 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/3p-updates.md +47 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/company-newsletter.md +65 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/faq-answers.md +30 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/general-comms.md +16 -0
- claude_mpm/skills/bundled/main/mcp-builder/SKILL.md +160 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/design_principles.md +412 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/evaluation.md +602 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/mcp_best_practices.md +915 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/node_mcp_server.md +916 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/python_mcp_server.md +752 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/workflow.md +1237 -0
- claude_mpm/skills/bundled/main/skill-creator/SKILL.md +189 -0
- claude_mpm/skills/bundled/main/skill-creator/references/best-practices.md +500 -0
- claude_mpm/skills/bundled/main/skill-creator/references/creation-workflow.md +464 -0
- claude_mpm/skills/bundled/main/skill-creator/references/examples.md +619 -0
- claude_mpm/skills/bundled/main/skill-creator/references/progressive-disclosure.md +437 -0
- claude_mpm/skills/bundled/main/skill-creator/references/skill-structure.md +231 -0
- claude_mpm/skills/bundled/php/espocrm-development/SKILL.md +170 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/architecture.md +602 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/common-tasks.md +821 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/development-workflow.md +742 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/frontend-customization.md +726 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/hooks-and-services.md +764 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/testing-debugging.md +831 -0
- 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-delegation-patterns/SKILL.md +167 -0
- claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
- claude_mpm/skills/bundled/pm/mpm-git-file-tracking/SKILL.md +113 -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-pr-workflow/SKILL.md +124 -0
- claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
- claude_mpm/skills/bundled/pm/mpm-session-pause/SKILL.md +170 -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-ticketing-integration/SKILL.md +154 -0
- claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
- claude_mpm/skills/bundled/pm/mpm-verification-protocols/SKILL.md +198 -0
- claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
- claude_mpm/skills/bundled/react/flexlayout-react.md +742 -0
- claude_mpm/skills/bundled/rust/desktop-applications/SKILL.md +226 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/architecture-patterns.md +901 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/native-gui-frameworks.md +901 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/platform-integration.md +775 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/state-management.md +937 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/tauri-framework.md +770 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/testing-deployment.md +961 -0
- claude_mpm/skills/bundled/security-scanning.md +112 -0
- claude_mpm/skills/bundled/tauri/tauri-async-patterns.md +495 -0
- claude_mpm/skills/bundled/tauri/tauri-build-deploy.md +599 -0
- claude_mpm/skills/bundled/tauri/tauri-command-patterns.md +535 -0
- claude_mpm/skills/bundled/tauri/tauri-error-handling.md +613 -0
- claude_mpm/skills/bundled/tauri/tauri-event-system.md +648 -0
- claude_mpm/skills/bundled/tauri/tauri-file-system.md +673 -0
- claude_mpm/skills/bundled/tauri/tauri-frontend-integration.md +767 -0
- claude_mpm/skills/bundled/tauri/tauri-performance.md +669 -0
- claude_mpm/skills/bundled/tauri/tauri-state-management.md +573 -0
- claude_mpm/skills/bundled/tauri/tauri-testing.md +384 -0
- claude_mpm/skills/bundled/tauri/tauri-window-management.md +628 -0
- claude_mpm/skills/bundled/testing/condition-based-waiting/SKILL.md +119 -0
- claude_mpm/skills/bundled/testing/condition-based-waiting/references/patterns-and-implementation.md +253 -0
- claude_mpm/skills/bundled/testing/test-driven-development/SKILL.md +145 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/anti-patterns.md +543 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/examples.md +741 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/integration.md +470 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/philosophy.md +458 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/workflow.md +639 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/SKILL.md +458 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/examples/example-inspection-report.md +411 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/assertion-quality.md +317 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/inspection-checklist.md +270 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/red-flags.md +436 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/SKILL.md +140 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/completeness-anti-patterns.md +572 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/core-anti-patterns.md +411 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/detection-guide.md +569 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/tdd-connection.md +695 -0
- claude_mpm/skills/bundled/testing/webapp-testing/SKILL.md +184 -0
- claude_mpm/skills/bundled/testing/webapp-testing/decision-tree.md +459 -0
- claude_mpm/skills/bundled/testing/webapp-testing/playwright-patterns.md +479 -0
- claude_mpm/skills/bundled/testing/webapp-testing/reconnaissance-pattern.md +687 -0
- claude_mpm/skills/bundled/testing/webapp-testing/server-management.md +758 -0
- claude_mpm/skills/bundled/testing/webapp-testing/troubleshooting.md +868 -0
- claude_mpm/skills/registry.py +295 -90
- claude_mpm/skills/skill_manager.py +98 -3
- claude_mpm/templates/.pre-commit-config.yaml +112 -0
- claude_mpm/utils/agent_dependency_loader.py +115 -4
- claude_mpm/utils/agent_filters.py +17 -44
- claude_mpm/utils/gitignore.py +3 -0
- claude_mpm/utils/migration.py +4 -4
- claude_mpm/utils/robust_installer.py +86 -21
- claude_mpm-5.6.23.dist-info/METADATA +393 -0
- {claude_mpm-5.0.9.dist-info → claude_mpm-5.6.23.dist-info}/RECORD +508 -261
- claude_mpm-5.6.23.dist-info/entry_points.txt +5 -0
- claude_mpm-5.6.23.dist-info/licenses/LICENSE +94 -0
- claude_mpm-5.6.23.dist-info/licenses/LICENSE-FAQ.md +153 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
- claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
- claude_mpm/agents/BASE_OPS.md +0 -219
- claude_mpm/agents/BASE_PM.md +0 -480
- claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
- claude_mpm/agents/BASE_QA.md +0 -167
- claude_mpm/agents/BASE_RESEARCH.md +0 -53
- claude_mpm/agents/OUTPUT_STYLE.md +0 -290
- claude_mpm/agents/PM_INSTRUCTIONS_TEACH.md +0 -1322
- claude_mpm/agents/base_agent_loader.py +0 -601
- claude_mpm/cli/commands/agents_detect.py +0 -380
- claude_mpm/cli/commands/agents_recommend.py +0 -309
- claude_mpm/cli/ticket_cli.py +0 -35
- claude_mpm/commands/mpm-agents-auto-configure.md +0 -278
- claude_mpm/commands/mpm-agents-detect.md +0 -177
- claude_mpm/commands/mpm-agents-list.md +0 -131
- claude_mpm/commands/mpm-agents-recommend.md +0 -223
- claude_mpm/commands/mpm-config-view.md +0 -150
- claude_mpm/commands/mpm-ticket-organize.md +0 -304
- claude_mpm/dashboard/analysis_runner.py +0 -455
- claude_mpm/dashboard/index.html +0 -13
- claude_mpm/dashboard/open_dashboard.py +0 -66
- claude_mpm/dashboard/static/css/activity.css +0 -1958
- claude_mpm/dashboard/static/css/connection-status.css +0 -370
- claude_mpm/dashboard/static/css/dashboard.css +0 -4701
- claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
- claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
- claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
- claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
- claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
- claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
- claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
- claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
- claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
- claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
- claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
- claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
- claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
- claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
- claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
- claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
- claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
- claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
- claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
- claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
- claude_mpm/dashboard/static/js/connection-manager.js +0 -536
- claude_mpm/dashboard/static/js/dashboard.js +0 -1914
- claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
- claude_mpm/dashboard/static/js/socket-client.js +0 -1474
- claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
- claude_mpm/dashboard/static/socket.io.min.js +0 -7
- claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
- claude_mpm/dashboard/templates/code_simple.html +0 -153
- claude_mpm/dashboard/templates/index.html +0 -606
- claude_mpm/dashboard/test_dashboard.html +0 -372
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
- claude_mpm/scripts/mcp_server.py +0 -75
- claude_mpm/scripts/mcp_wrapper.py +0 -39
- claude_mpm/services/mcp_gateway/__init__.py +0 -159
- claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
- claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
- claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
- claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
- claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
- claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
- claude_mpm/services/mcp_gateway/core/base.py +0 -312
- claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
- claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
- claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
- claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
- claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
- claude_mpm/services/mcp_gateway/main.py +0 -589
- claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
- claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
- claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
- claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
- claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
- claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
- claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
- claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
- claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
- claude_mpm-5.0.9.dist-info/METADATA +0 -1028
- claude_mpm-5.0.9.dist-info/entry_points.txt +0 -10
- claude_mpm-5.0.9.dist-info/licenses/LICENSE +0 -21
- {claude_mpm-5.0.9.dist-info → claude_mpm-5.6.23.dist-info}/WHEEL +0 -0
- {claude_mpm-5.0.9.dist-info → claude_mpm-5.6.23.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,764 @@
|
|
|
1
|
+
# Hooks and Services Reference
|
|
2
|
+
|
|
3
|
+
## Service Layer Pattern
|
|
4
|
+
|
|
5
|
+
The service layer is where ALL business logic belongs in EspoCRM. Never put business logic in hooks, controllers, or repositories.
|
|
6
|
+
|
|
7
|
+
### Service Layer Hierarchy
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Base Record Service (Espo\Services\Record)
|
|
11
|
+
↓
|
|
12
|
+
Your Custom Service (extends Record)
|
|
13
|
+
↓
|
|
14
|
+
Business Logic Methods
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Creating a Service
|
|
18
|
+
|
|
19
|
+
```php
|
|
20
|
+
<?php
|
|
21
|
+
namespace Espo\Modules\MyModule\Services;
|
|
22
|
+
|
|
23
|
+
use Espo\Services\Record;
|
|
24
|
+
use Espo\ORM\Entity;
|
|
25
|
+
use Espo\Core\Exceptions\{BadRequest, Forbidden, NotFound};
|
|
26
|
+
use Espo\Core\Mail\EmailSender;
|
|
27
|
+
use Espo\Core\Utils\DateTime as DateTimeUtil;
|
|
28
|
+
|
|
29
|
+
class Opportunity extends Record
|
|
30
|
+
{
|
|
31
|
+
public function __construct(
|
|
32
|
+
private EmailSender $emailSender,
|
|
33
|
+
private DateTimeUtil $dateTime
|
|
34
|
+
) {
|
|
35
|
+
parent::__construct();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Override create flow
|
|
39
|
+
protected function beforeCreateEntity(Entity $entity, array $data): void
|
|
40
|
+
{
|
|
41
|
+
parent::beforeCreateEntity($entity, $data);
|
|
42
|
+
|
|
43
|
+
// Business logic: Set expected close date to 30 days from now if not provided
|
|
44
|
+
if (!$entity->get('closeDate')) {
|
|
45
|
+
$closeDate = $this->dateTime->getDateTime()
|
|
46
|
+
->modify('+30 days')
|
|
47
|
+
->format('Y-m-d');
|
|
48
|
+
$entity->set('closeDate', $closeDate);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Business logic: Auto-assign to manager for large opportunities
|
|
52
|
+
if ($entity->get('amount') > 100000 && !$entity->get('assignedUserId')) {
|
|
53
|
+
$managerId = $this->getManagerForLargeDeals();
|
|
54
|
+
$entity->set('assignedUserId', $managerId);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Override update flow
|
|
59
|
+
protected function beforeUpdateEntity(Entity $entity, array $data): void
|
|
60
|
+
{
|
|
61
|
+
parent::beforeUpdateEntity($entity, $data);
|
|
62
|
+
|
|
63
|
+
// Business logic: Track stage changes
|
|
64
|
+
if ($entity->isAttributeChanged('stage')) {
|
|
65
|
+
$this->trackStageChange($entity);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Custom business logic method
|
|
70
|
+
public function markAsWon(string $id): Entity
|
|
71
|
+
{
|
|
72
|
+
$entity = $this->getEntity($id);
|
|
73
|
+
|
|
74
|
+
if (!$entity) {
|
|
75
|
+
throw new NotFound();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!$this->acl->check($entity, 'edit')) {
|
|
79
|
+
throw new Forbidden();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Validate can be marked as won
|
|
83
|
+
if (!$this->canMarkAsWon($entity)) {
|
|
84
|
+
throw new BadRequest('Opportunity cannot be marked as won in current state');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Update entity
|
|
88
|
+
$entity->set([
|
|
89
|
+
'stage' => 'Closed Won',
|
|
90
|
+
'probability' => 100,
|
|
91
|
+
'closeDate' => date('Y-m-d')
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
$this->entityManager->saveEntity($entity);
|
|
95
|
+
|
|
96
|
+
// Additional business logic
|
|
97
|
+
$this->createWinNotification($entity);
|
|
98
|
+
$this->updateAccountRevenue($entity);
|
|
99
|
+
|
|
100
|
+
return $entity;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private function canMarkAsWon(Entity $opportunity): bool
|
|
104
|
+
{
|
|
105
|
+
// Business rules for winning
|
|
106
|
+
$stage = $opportunity->get('stage');
|
|
107
|
+
$allowedStages = ['Proposal', 'Negotiation'];
|
|
108
|
+
|
|
109
|
+
return in_array($stage, $allowedStages);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private function createWinNotification(Entity $opportunity): void
|
|
113
|
+
{
|
|
114
|
+
$assignedUserId = $opportunity->get('assignedUserId');
|
|
115
|
+
|
|
116
|
+
if (!$assignedUserId) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Send email notification
|
|
121
|
+
$emailSender = $this->emailSender->create();
|
|
122
|
+
|
|
123
|
+
$emailSender
|
|
124
|
+
->withSubject('Opportunity Won: ' . $opportunity->get('name'))
|
|
125
|
+
->withBody('Congratulations! Opportunity has been marked as won.')
|
|
126
|
+
->withToUserIdList([$assignedUserId])
|
|
127
|
+
->send();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private function updateAccountRevenue(Entity $opportunity): void
|
|
131
|
+
{
|
|
132
|
+
$accountId = $opportunity->get('accountId');
|
|
133
|
+
|
|
134
|
+
if (!$accountId) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
$account = $this->entityManager->getEntityById('Account', $accountId);
|
|
139
|
+
|
|
140
|
+
if (!$account) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Calculate total won opportunities
|
|
145
|
+
$totalRevenue = $this->entityManager
|
|
146
|
+
->getRDBRepository('Opportunity')
|
|
147
|
+
->where([
|
|
148
|
+
'accountId' => $accountId,
|
|
149
|
+
'stage' => 'Closed Won'
|
|
150
|
+
])
|
|
151
|
+
->select(['SUM:amount'])
|
|
152
|
+
->findOne()
|
|
153
|
+
->get('SUM:amount') ?? 0;
|
|
154
|
+
|
|
155
|
+
$account->set('totalRevenue', $totalRevenue);
|
|
156
|
+
$this->entityManager->saveEntity($account);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private function trackStageChange(Entity $opportunity): void
|
|
160
|
+
{
|
|
161
|
+
$note = $this->entityManager->getNewEntity('Note');
|
|
162
|
+
$note->set([
|
|
163
|
+
'type' => 'Update',
|
|
164
|
+
'parentType' => 'Opportunity',
|
|
165
|
+
'parentId' => $opportunity->getId(),
|
|
166
|
+
'data' => [
|
|
167
|
+
'fields' => ['stage'],
|
|
168
|
+
'attributes' => [
|
|
169
|
+
'stage' => [
|
|
170
|
+
'was' => $opportunity->getFetched('stage'),
|
|
171
|
+
'became' => $opportunity->get('stage')
|
|
172
|
+
]
|
|
173
|
+
]
|
|
174
|
+
]
|
|
175
|
+
]);
|
|
176
|
+
|
|
177
|
+
$this->entityManager->saveEntity($note);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private function getManagerForLargeDeals(): ?string
|
|
181
|
+
{
|
|
182
|
+
// Get sales manager role user
|
|
183
|
+
$manager = $this->entityManager
|
|
184
|
+
->getRDBRepository('User')
|
|
185
|
+
->join('teams')
|
|
186
|
+
->where([
|
|
187
|
+
'teams.name' => 'Sales Management',
|
|
188
|
+
'isActive' => true
|
|
189
|
+
])
|
|
190
|
+
->findOne();
|
|
191
|
+
|
|
192
|
+
return $manager?->getId();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Record Service Hook Points
|
|
198
|
+
|
|
199
|
+
Override these methods to inject custom logic into the standard CRUD flow:
|
|
200
|
+
|
|
201
|
+
```php
|
|
202
|
+
// Before operations
|
|
203
|
+
protected function beforeCreateEntity(Entity $entity, array $data): void
|
|
204
|
+
protected function beforeUpdateEntity(Entity $entity, array $data): void
|
|
205
|
+
protected function beforeDeleteEntity(Entity $entity): void
|
|
206
|
+
|
|
207
|
+
// After operations
|
|
208
|
+
protected function afterCreateEntity(Entity $entity, array $data): void
|
|
209
|
+
protected function afterUpdateEntity(Entity $entity, array $data): void
|
|
210
|
+
protected function afterDeleteEntity(Entity $entity): void
|
|
211
|
+
|
|
212
|
+
// Link operations
|
|
213
|
+
protected function beforeLink(Entity $entity, string $link, Entity $foreign): void
|
|
214
|
+
protected function afterLink(Entity $entity, string $link, Entity $foreign): void
|
|
215
|
+
protected function beforeUnlink(Entity $entity, string $link, Entity $foreign): void
|
|
216
|
+
protected function afterUnlink(Entity $entity, string $link, Entity $foreign): void
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Hook System
|
|
220
|
+
|
|
221
|
+
Hooks are for **validation and side effects ONLY**, not business logic. Business logic belongs in Services.
|
|
222
|
+
|
|
223
|
+
### The 7 Hook Types
|
|
224
|
+
|
|
225
|
+
#### 1. BeforeSave - Validation
|
|
226
|
+
|
|
227
|
+
```php
|
|
228
|
+
<?php
|
|
229
|
+
namespace Espo\Modules\MyModule\Hooks\Account;
|
|
230
|
+
|
|
231
|
+
use Espo\ORM\Entity;
|
|
232
|
+
use Espo\Core\Hook\Hook\BeforeSave;
|
|
233
|
+
use Espo\Core\Exceptions\BadRequest;
|
|
234
|
+
|
|
235
|
+
class ValidateData implements BeforeSave
|
|
236
|
+
{
|
|
237
|
+
public function beforeSave(Entity $entity, array $options): void
|
|
238
|
+
{
|
|
239
|
+
// Validation: Check phone number format
|
|
240
|
+
if ($entity->isAttributeChanged('phoneNumber')) {
|
|
241
|
+
$phone = $entity->get('phoneNumber');
|
|
242
|
+
|
|
243
|
+
if ($phone && !$this->isValidPhone($phone)) {
|
|
244
|
+
throw new BadRequest('Invalid phone number format');
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Validation: Ensure website starts with https
|
|
249
|
+
if ($entity->isAttributeChanged('website')) {
|
|
250
|
+
$website = $entity->get('website');
|
|
251
|
+
|
|
252
|
+
if ($website && !str_starts_with($website, 'https://')) {
|
|
253
|
+
$entity->set('website', 'https://' . ltrim($website, 'http://'));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Validation: Business rule
|
|
258
|
+
if ($entity->get('type') === 'Customer' && !$entity->get('industry')) {
|
|
259
|
+
throw new BadRequest('Industry is required for Customer accounts');
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private function isValidPhone(string $phone): bool
|
|
264
|
+
{
|
|
265
|
+
return preg_match('/^\+?[0-9\s\-\(\)]+$/', $phone);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
#### 2. AfterSave - Side Effects
|
|
271
|
+
|
|
272
|
+
```php
|
|
273
|
+
<?php
|
|
274
|
+
namespace Espo\Modules\MyModule\Hooks\Opportunity;
|
|
275
|
+
|
|
276
|
+
use Espo\ORM\Entity;
|
|
277
|
+
use Espo\Core\Hook\Hook\AfterSave;
|
|
278
|
+
use Espo\ORM\EntityManager;
|
|
279
|
+
|
|
280
|
+
class UpdateAccountStats implements AfterSave
|
|
281
|
+
{
|
|
282
|
+
public function __construct(private EntityManager $entityManager) {}
|
|
283
|
+
|
|
284
|
+
public function afterSave(Entity $entity, array $options): void
|
|
285
|
+
{
|
|
286
|
+
// Side effect: Update account statistics when opportunity stage changes
|
|
287
|
+
if ($entity->isAttributeChanged('stage')) {
|
|
288
|
+
$accountId = $entity->get('accountId');
|
|
289
|
+
|
|
290
|
+
if ($accountId) {
|
|
291
|
+
$this->updateAccountOpportunityStats($accountId);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Side effect: Create activity when opportunity is won
|
|
296
|
+
if ($entity->isAttributeChanged('stage') && $entity->get('stage') === 'Closed Won') {
|
|
297
|
+
$this->createWonActivity($entity);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private function updateAccountOpportunityStats(string $accountId): void
|
|
302
|
+
{
|
|
303
|
+
$account = $this->entityManager->getEntityById('Account', $accountId);
|
|
304
|
+
|
|
305
|
+
if (!$account) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Count opportunities
|
|
310
|
+
$openCount = $this->entityManager
|
|
311
|
+
->getRDBRepository('Opportunity')
|
|
312
|
+
->where([
|
|
313
|
+
'accountId' => $accountId,
|
|
314
|
+
'stage!=' => ['Closed Won', 'Closed Lost']
|
|
315
|
+
])
|
|
316
|
+
->count();
|
|
317
|
+
|
|
318
|
+
$account->set('openOpportunitiesCount', $openCount);
|
|
319
|
+
$this->entityManager->saveEntity($account);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
private function createWonActivity(Entity $opportunity): void
|
|
323
|
+
{
|
|
324
|
+
$meeting = $this->entityManager->getNewEntity('Meeting');
|
|
325
|
+
$meeting->set([
|
|
326
|
+
'name' => 'Follow-up: ' . $opportunity->get('name'),
|
|
327
|
+
'parentType' => 'Opportunity',
|
|
328
|
+
'parentId' => $opportunity->getId(),
|
|
329
|
+
'assignedUserId' => $opportunity->get('assignedUserId'),
|
|
330
|
+
'status' => 'Planned',
|
|
331
|
+
'dateStart' => date('Y-m-d H:i:s', strtotime('+1 week'))
|
|
332
|
+
]);
|
|
333
|
+
|
|
334
|
+
$this->entityManager->saveEntity($meeting);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
#### 3. BeforeRemove - Pre-deletion Validation
|
|
340
|
+
|
|
341
|
+
```php
|
|
342
|
+
<?php
|
|
343
|
+
namespace Espo\Modules\MyModule\Hooks\Account;
|
|
344
|
+
|
|
345
|
+
use Espo\ORM\Entity;
|
|
346
|
+
use Espo\Core\Hook\Hook\BeforeRemove;
|
|
347
|
+
use Espo\Core\Exceptions\Forbidden;
|
|
348
|
+
use Espo\ORM\EntityManager;
|
|
349
|
+
|
|
350
|
+
class PreventDeletionWithOpenOpportunities implements BeforeRemove
|
|
351
|
+
{
|
|
352
|
+
public function __construct(private EntityManager $entityManager) {}
|
|
353
|
+
|
|
354
|
+
public function beforeRemove(Entity $entity, array $options): void
|
|
355
|
+
{
|
|
356
|
+
// Validation: Prevent deletion if account has open opportunities
|
|
357
|
+
$hasOpenOpportunities = $this->entityManager
|
|
358
|
+
->getRDBRepository('Opportunity')
|
|
359
|
+
->where([
|
|
360
|
+
'accountId' => $entity->getId(),
|
|
361
|
+
'stage!=' => ['Closed Won', 'Closed Lost']
|
|
362
|
+
])
|
|
363
|
+
->count() > 0;
|
|
364
|
+
|
|
365
|
+
if ($hasOpenOpportunities) {
|
|
366
|
+
throw new Forbidden('Cannot delete account with open opportunities');
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
#### 4. AfterRemove - Post-deletion Cleanup
|
|
373
|
+
|
|
374
|
+
```php
|
|
375
|
+
<?php
|
|
376
|
+
namespace Espo\Modules\MyModule\Hooks\Account;
|
|
377
|
+
|
|
378
|
+
use Espo\ORM\Entity;
|
|
379
|
+
use Espo\Core\Hook\Hook\AfterRemove;
|
|
380
|
+
use Espo\ORM\EntityManager;
|
|
381
|
+
|
|
382
|
+
class CleanupRelatedData implements AfterRemove
|
|
383
|
+
{
|
|
384
|
+
public function __construct(private EntityManager $entityManager) {}
|
|
385
|
+
|
|
386
|
+
public function afterRemove(Entity $entity, array $options): void
|
|
387
|
+
{
|
|
388
|
+
// Cleanup: Remove orphaned custom records
|
|
389
|
+
$customRecords = $this->entityManager
|
|
390
|
+
->getRDBRepository('CustomEntity')
|
|
391
|
+
->where(['accountId' => $entity->getId()])
|
|
392
|
+
->find();
|
|
393
|
+
|
|
394
|
+
foreach ($customRecords as $record) {
|
|
395
|
+
$this->entityManager->removeEntity($record);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
#### 5. AfterRelate - React to Relationship Creation
|
|
402
|
+
|
|
403
|
+
```php
|
|
404
|
+
<?php
|
|
405
|
+
namespace Espo\Modules\MyModule\Hooks\Contact;
|
|
406
|
+
|
|
407
|
+
use Espo\ORM\Entity;
|
|
408
|
+
use Espo\Core\Hook\Hook\AfterRelate;
|
|
409
|
+
use Espo\ORM\EntityManager;
|
|
410
|
+
|
|
411
|
+
class UpdateAccountContacts implements AfterRelate
|
|
412
|
+
{
|
|
413
|
+
public function __construct(private EntityManager $entityManager) {}
|
|
414
|
+
|
|
415
|
+
public function afterRelate(
|
|
416
|
+
Entity $entity,
|
|
417
|
+
string $relationName,
|
|
418
|
+
Entity $foreign,
|
|
419
|
+
?array $columnData,
|
|
420
|
+
array $options
|
|
421
|
+
): void {
|
|
422
|
+
// React to contact being linked to account
|
|
423
|
+
if ($relationName === 'account') {
|
|
424
|
+
$account = $foreign;
|
|
425
|
+
|
|
426
|
+
// Update account's primary contact if not set
|
|
427
|
+
if (!$account->get('primaryContactId')) {
|
|
428
|
+
$account->set('primaryContactId', $entity->getId());
|
|
429
|
+
$this->entityManager->saveEntity($account);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
#### 6. AfterUnrelate - React to Relationship Removal
|
|
437
|
+
|
|
438
|
+
```php
|
|
439
|
+
<?php
|
|
440
|
+
namespace Espo\Modules\MyModule\Hooks\Contact;
|
|
441
|
+
|
|
442
|
+
use Espo\ORM\Entity;
|
|
443
|
+
use Espo\Core\Hook\Hook\AfterUnrelate;
|
|
444
|
+
use Espo\ORM\EntityManager;
|
|
445
|
+
|
|
446
|
+
class UpdateAccountOnUnlink implements AfterUnrelate
|
|
447
|
+
{
|
|
448
|
+
public function __construct(private EntityManager $entityManager) {}
|
|
449
|
+
|
|
450
|
+
public function afterUnrelate(
|
|
451
|
+
Entity $entity,
|
|
452
|
+
string $relationName,
|
|
453
|
+
Entity $foreign,
|
|
454
|
+
array $options
|
|
455
|
+
): void {
|
|
456
|
+
// React to contact being unlinked from account
|
|
457
|
+
if ($relationName === 'account') {
|
|
458
|
+
$account = $foreign;
|
|
459
|
+
|
|
460
|
+
// Clear primary contact if it was this contact
|
|
461
|
+
if ($account->get('primaryContactId') === $entity->getId()) {
|
|
462
|
+
$account->set('primaryContactId', null);
|
|
463
|
+
$this->entityManager->saveEntity($account);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
#### 7. AfterMassRelate - React to Bulk Relationship Operations
|
|
471
|
+
|
|
472
|
+
```php
|
|
473
|
+
<?php
|
|
474
|
+
namespace Espo\Modules\MyModule\Hooks\Contact;
|
|
475
|
+
|
|
476
|
+
use Espo\ORM\Entity;
|
|
477
|
+
use Espo\Core\Hook\Hook\AfterMassRelate;
|
|
478
|
+
use Espo\ORM\EntityManager;
|
|
479
|
+
|
|
480
|
+
class RecalculateAccountStats implements AfterMassRelate
|
|
481
|
+
{
|
|
482
|
+
public function __construct(private EntityManager $entityManager) {}
|
|
483
|
+
|
|
484
|
+
public function afterMassRelate(
|
|
485
|
+
Entity $entity,
|
|
486
|
+
string $relationName,
|
|
487
|
+
array $params,
|
|
488
|
+
array $options
|
|
489
|
+
): void {
|
|
490
|
+
// React to mass relate operation
|
|
491
|
+
if ($relationName === 'accounts') {
|
|
492
|
+
// Recalculate statistics for all affected accounts
|
|
493
|
+
$this->recalculateStats($entity->getId());
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
private function recalculateStats(string $contactId): void
|
|
498
|
+
{
|
|
499
|
+
// Implementation
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
## Dependency Injection in Hooks
|
|
505
|
+
|
|
506
|
+
### Constructor Injection (CORRECT)
|
|
507
|
+
|
|
508
|
+
```php
|
|
509
|
+
<?php
|
|
510
|
+
namespace Espo\Modules\MyModule\Hooks\Account;
|
|
511
|
+
|
|
512
|
+
use Espo\ORM\Entity;
|
|
513
|
+
use Espo\Core\Hook\Hook\BeforeSave;
|
|
514
|
+
use Espo\ORM\EntityManager;
|
|
515
|
+
use Espo\Core\Utils\Metadata;
|
|
516
|
+
use Espo\Core\Mail\EmailSender;
|
|
517
|
+
use Espo\Core\ServiceFactory;
|
|
518
|
+
|
|
519
|
+
class MyHook implements BeforeSave
|
|
520
|
+
{
|
|
521
|
+
public function __construct(
|
|
522
|
+
private EntityManager $entityManager,
|
|
523
|
+
private Metadata $metadata,
|
|
524
|
+
private EmailSender $emailSender,
|
|
525
|
+
private ServiceFactory $serviceFactory
|
|
526
|
+
) {}
|
|
527
|
+
|
|
528
|
+
public function beforeSave(Entity $entity, array $options): void
|
|
529
|
+
{
|
|
530
|
+
// Use injected dependencies
|
|
531
|
+
$config = $this->metadata->get(['app', 'myConfig']);
|
|
532
|
+
|
|
533
|
+
// Access services
|
|
534
|
+
$service = $this->serviceFactory->create('Account');
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
### NEVER Pass Container
|
|
540
|
+
|
|
541
|
+
```php
|
|
542
|
+
// ❌ WRONG - Never inject Container
|
|
543
|
+
use Espo\Core\Container;
|
|
544
|
+
|
|
545
|
+
class BadHook implements BeforeSave
|
|
546
|
+
{
|
|
547
|
+
public function __construct(private Container $container) {}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// ✅ CORRECT - Inject specific dependencies
|
|
551
|
+
use Espo\ORM\EntityManager;
|
|
552
|
+
|
|
553
|
+
class GoodHook implements BeforeSave
|
|
554
|
+
{
|
|
555
|
+
public function __construct(private EntityManager $entityManager) {}
|
|
556
|
+
}
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
## Transaction Handling
|
|
560
|
+
|
|
561
|
+
Use TransactionManager for operations that must be atomic:
|
|
562
|
+
|
|
563
|
+
```php
|
|
564
|
+
<?php
|
|
565
|
+
namespace Espo\Modules\MyModule\Services;
|
|
566
|
+
|
|
567
|
+
use Espo\Services\Record;
|
|
568
|
+
use Espo\ORM\TransactionManager;
|
|
569
|
+
|
|
570
|
+
class MyEntity extends Record
|
|
571
|
+
{
|
|
572
|
+
public function __construct(private TransactionManager $transactionManager)
|
|
573
|
+
{
|
|
574
|
+
parent::__construct();
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
public function complexOperation(string $id): void
|
|
578
|
+
{
|
|
579
|
+
$this->transactionManager->run(function () use ($id) {
|
|
580
|
+
// All operations in this closure are transactional
|
|
581
|
+
$entity = $this->getEntity($id);
|
|
582
|
+
$entity->set('status', 'Processing');
|
|
583
|
+
$this->entityManager->saveEntity($entity);
|
|
584
|
+
|
|
585
|
+
// Related operation
|
|
586
|
+
$relatedEntity = $this->entityManager->getNewEntity('RelatedEntity');
|
|
587
|
+
$relatedEntity->set('parentId', $id);
|
|
588
|
+
$this->entityManager->saveEntity($relatedEntity);
|
|
589
|
+
|
|
590
|
+
// If any exception is thrown, ALL changes are rolled back
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
## Formula Scripts - Declarative Logic
|
|
597
|
+
|
|
598
|
+
Use Formula scripts for simple field calculations instead of hooks:
|
|
599
|
+
|
|
600
|
+
### Formula in Entity Metadata
|
|
601
|
+
|
|
602
|
+
```json
|
|
603
|
+
{
|
|
604
|
+
"fields": {
|
|
605
|
+
"totalPrice": {
|
|
606
|
+
"type": "currency",
|
|
607
|
+
"formula": "quantity * unitPrice"
|
|
608
|
+
},
|
|
609
|
+
"fullName": {
|
|
610
|
+
"type": "varchar",
|
|
611
|
+
"formula": "string\\concatenate(firstName, ' ', lastName)"
|
|
612
|
+
},
|
|
613
|
+
"daysUntilDue": {
|
|
614
|
+
"type": "int",
|
|
615
|
+
"formula": "datetime\\diff(datetime\\today(), dueDate, 'days')"
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
### Formula vs. Hooks Decision Matrix
|
|
622
|
+
|
|
623
|
+
| Use Case | Solution | Reason |
|
|
624
|
+
|----------|----------|--------|
|
|
625
|
+
| Calculate field from other fields | Formula | Simple, declarative |
|
|
626
|
+
| String concatenation | Formula | Built-in functions |
|
|
627
|
+
| Conditional field values | Formula | If/then logic available |
|
|
628
|
+
| Date calculations | Formula | Date functions available |
|
|
629
|
+
| Complex business rules | Service | Needs multiple entities |
|
|
630
|
+
| External API calls | Service | Needs async/error handling |
|
|
631
|
+
| Multi-entity operations | Service | Transaction support |
|
|
632
|
+
| Validation requiring DB query | Hook (BeforeSave) | Needs EntityManager |
|
|
633
|
+
|
|
634
|
+
### Formula Functions Reference
|
|
635
|
+
|
|
636
|
+
**String Functions:**
|
|
637
|
+
```javascript
|
|
638
|
+
string\concatenate(firstName, ' ', lastName)
|
|
639
|
+
string\substring(name, 0, 10)
|
|
640
|
+
string\length(description)
|
|
641
|
+
string\trim(input)
|
|
642
|
+
string\lowerCase(email)
|
|
643
|
+
string\upperCase(name)
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
**Date Functions:**
|
|
647
|
+
```javascript
|
|
648
|
+
datetime\today()
|
|
649
|
+
datetime\now()
|
|
650
|
+
datetime\diff(date1, date2, 'days')
|
|
651
|
+
datetime\addDays(dateStart, 5)
|
|
652
|
+
datetime\month(dateStart)
|
|
653
|
+
datetime\year(dateStart)
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
**Numeric Functions:**
|
|
657
|
+
```javascript
|
|
658
|
+
number\round(amount, 2)
|
|
659
|
+
number\floor(value)
|
|
660
|
+
number\ceil(value)
|
|
661
|
+
number\abs(value)
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
**Conditional Logic:**
|
|
665
|
+
```javascript
|
|
666
|
+
ifThen(
|
|
667
|
+
status == 'Complete',
|
|
668
|
+
100,
|
|
669
|
+
probability
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
ifThenElse(
|
|
673
|
+
amount > 10000,
|
|
674
|
+
'High',
|
|
675
|
+
'Normal'
|
|
676
|
+
)
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
## Best Practices
|
|
680
|
+
|
|
681
|
+
### 1. Business Logic Placement
|
|
682
|
+
|
|
683
|
+
```php
|
|
684
|
+
// ✅ CORRECT - Business logic in Service
|
|
685
|
+
class OpportunityService extends Record {
|
|
686
|
+
public function calculateCommission(string $id): float {
|
|
687
|
+
$opportunity = $this->getEntity($id);
|
|
688
|
+
$amount = $opportunity->get('amount');
|
|
689
|
+
$stage = $opportunity->get('stage');
|
|
690
|
+
|
|
691
|
+
return $this->getCommissionCalculator()->calculate($amount, $stage);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// ❌ WRONG - Business logic in Hook
|
|
696
|
+
class OpportunityHook implements AfterSave {
|
|
697
|
+
public function afterSave(Entity $entity, array $options): void {
|
|
698
|
+
// Don't put complex business logic here
|
|
699
|
+
$commission = $this->calculateCommission($entity);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
### 2. Hook Complexity
|
|
705
|
+
|
|
706
|
+
```php
|
|
707
|
+
// ✅ CORRECT - Simple side effects in hooks
|
|
708
|
+
class NotificationHook implements AfterSave {
|
|
709
|
+
public function afterSave(Entity $entity, array $options): void {
|
|
710
|
+
if ($entity->isAttributeChanged('assignedUserId')) {
|
|
711
|
+
$this->sendNotification($entity);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// ❌ WRONG - Complex logic in hooks
|
|
717
|
+
class ComplexHook implements AfterSave {
|
|
718
|
+
public function afterSave(Entity $entity, array $options): void {
|
|
719
|
+
// Too much logic - belongs in service
|
|
720
|
+
$this->updateRelatedRecords($entity);
|
|
721
|
+
$this->recalculateMetrics($entity);
|
|
722
|
+
$this->syncWithExternalSystem($entity);
|
|
723
|
+
$this->generateReports($entity);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
### 3. Avoid Recursive Hook Calls
|
|
729
|
+
|
|
730
|
+
```php
|
|
731
|
+
// ✅ CORRECT - Prevent recursive calls
|
|
732
|
+
class MyHook implements AfterSave {
|
|
733
|
+
public function afterSave(Entity $entity, array $options): void {
|
|
734
|
+
// Check if this is a programmatic save to avoid recursion
|
|
735
|
+
if (!empty($options['silent'])) {
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// Make changes to related entity
|
|
740
|
+
$related = $this->entityManager->getEntityById('Related', $entity->get('relatedId'));
|
|
741
|
+
$related->set('updated', true);
|
|
742
|
+
|
|
743
|
+
// Save with 'silent' option to prevent triggering hooks
|
|
744
|
+
$this->entityManager->saveEntity($related, ['silent' => true]);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
### 4. Type Safety
|
|
750
|
+
|
|
751
|
+
```php
|
|
752
|
+
// ✅ CORRECT - Strict types
|
|
753
|
+
declare(strict_types=1);
|
|
754
|
+
|
|
755
|
+
class MyHook implements BeforeSave {
|
|
756
|
+
public function beforeSave(Entity $entity, array $options): void {
|
|
757
|
+
$value = $entity->get('amount');
|
|
758
|
+
|
|
759
|
+
if (!is_numeric($value)) {
|
|
760
|
+
throw new BadRequest('Amount must be numeric');
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
```
|