claude-mpm 5.4.41__py3-none-any.whl → 5.6.72__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 +109 -1925
- claude_mpm/agents/PM_INSTRUCTIONS.md +161 -298
- claude_mpm/agents/WORKFLOW.md +2 -0
- claude_mpm/agents/templates/circuit-breakers.md +26 -17
- claude_mpm/auth/__init__.py +35 -0
- claude_mpm/auth/callback_server.py +328 -0
- claude_mpm/auth/models.py +104 -0
- claude_mpm/auth/oauth_manager.py +266 -0
- claude_mpm/auth/providers/__init__.py +12 -0
- claude_mpm/auth/providers/base.py +165 -0
- claude_mpm/auth/providers/google.py +261 -0
- claude_mpm/auth/token_storage.py +252 -0
- 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 +216 -0
- claude_mpm/cli/commands/configure.py +620 -21
- claude_mpm/cli/commands/configure_agent_display.py +3 -1
- claude_mpm/cli/commands/hook_errors.py +60 -60
- claude_mpm/cli/commands/mcp.py +29 -17
- claude_mpm/cli/commands/mcp_command_router.py +39 -0
- claude_mpm/cli/commands/mcp_service_commands.py +304 -0
- claude_mpm/cli/commands/monitor.py +2 -2
- claude_mpm/cli/commands/mpm_init/core.py +15 -8
- claude_mpm/cli/commands/oauth.py +481 -0
- claude_mpm/cli/commands/profile.py +9 -10
- claude_mpm/cli/commands/run.py +35 -3
- claude_mpm/cli/commands/skill_source.py +51 -2
- claude_mpm/cli/commands/skills.py +182 -32
- claude_mpm/cli/executor.py +129 -16
- claude_mpm/cli/helpers.py +1 -1
- 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 +89 -1
- claude_mpm/cli/parsers/commander_parser.py +116 -0
- claude_mpm/cli/parsers/mcp_parser.py +79 -0
- claude_mpm/cli/parsers/oauth_parser.py +165 -0
- claude_mpm/cli/parsers/profile_parser.py +0 -1
- 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 +2 -3
- claude_mpm/cli/startup.py +662 -524
- claude_mpm/cli/startup_display.py +76 -7
- 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 +149 -0
- claude_mpm/commander/chat/commands.py +122 -0
- claude_mpm/commander/chat/repl.py +1821 -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 +392 -0
- claude_mpm/commander/frameworks/__init__.py +12 -0
- claude_mpm/commander/frameworks/base.py +233 -0
- claude_mpm/commander/frameworks/claude_code.py +58 -0
- claude_mpm/commander/frameworks/mpm.py +57 -0
- claude_mpm/commander/git/__init__.py +5 -0
- claude_mpm/commander/git/worktree_manager.py +212 -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 +865 -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 +127 -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 +403 -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 +362 -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 +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 +6 -0
- claude_mpm/core/claude_runner.py +154 -2
- claude_mpm/core/config.py +35 -22
- claude_mpm/core/config_constants.py +74 -9
- claude_mpm/core/constants.py +56 -12
- claude_mpm/core/hook_manager.py +53 -4
- claude_mpm/core/interactive_session.py +12 -11
- claude_mpm/core/logger.py +26 -9
- claude_mpm/core/logging_utils.py +39 -13
- claude_mpm/core/network_config.py +148 -0
- claude_mpm/core/oneshot_session.py +7 -6
- claude_mpm/core/optimized_startup.py +3 -1
- claude_mpm/core/output_style_manager.py +66 -18
- claude_mpm/core/shared/config_loader.py +3 -1
- claude_mpm/core/socketio_pool.py +47 -15
- 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/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/{uj46x2Wr.js → BSNlmTZj.js} +1 -1
- 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/{N4qtv3Hx.js → DR8nis88.js} +2 -2
- 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/{DjhvlsAc.js → NqQ1dWOy.js} +1 -1
- 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.CAGBuiOw.js → 0.RgBboRvH.js} +1 -1
- 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 -1
- claude_mpm/dashboard/static/svelte-build/index.html +11 -11
- 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__/auto_pause_handler.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/auto_pause_handler.py +485 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +466 -136
- claude_mpm/hooks/claude_hooks/hook_handler.py +204 -104
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
- claude_mpm/hooks/claude_hooks/installer.py +291 -59
- claude_mpm/hooks/claude_hooks/memory_integration.py +52 -32
- claude_mpm/hooks/claude_hooks/response_tracking.py +43 -60
- claude_mpm/hooks/claude_hooks/services/__init__.py +21 -0
- 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__/container.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/protocols.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 +41 -26
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +38 -105
- claude_mpm/hooks/claude_hooks/services/container.py +326 -0
- claude_mpm/hooks/claude_hooks/services/protocols.py +328 -0
- claude_mpm/hooks/claude_hooks/services/state_manager.py +25 -38
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +75 -77
- claude_mpm/hooks/kuzu_memory_hook.py +5 -5
- claude_mpm/hooks/session_resume_hook.py +89 -1
- claude_mpm/hooks/templates/pre_tool_use_simple.py +6 -6
- claude_mpm/hooks/templates/pre_tool_use_template.py +16 -8
- claude_mpm/init.py +224 -4
- claude_mpm/mcp/__init__.py +9 -0
- claude_mpm/mcp/google_workspace_server.py +610 -0
- claude_mpm/scripts/claude-hook-handler.sh +46 -19
- 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/agent_discovery_service.py +3 -1
- claude_mpm/services/agents/deployment/agent_format_converter.py +25 -13
- claude_mpm/services/agents/deployment/agent_template_builder.py +37 -17
- 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 +36 -8
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +50 -26
- claude_mpm/services/agents/deployment/startup_reconciliation.py +138 -0
- claude_mpm/services/agents/git_source_manager.py +21 -2
- 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/sources/git_source_sync_service.py +116 -5
- 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/command_deployment_service.py +44 -26
- 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/hook_installer_service.py +77 -8
- 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/mcp_config_manager.py +99 -19
- claude_mpm/services/mcp_service_registry.py +294 -0
- claude_mpm/services/monitor/daemon_manager.py +15 -4
- claude_mpm/services/monitor/management/lifecycle.py +8 -3
- claude_mpm/services/monitor/server.py +111 -16
- claude_mpm/services/pm_skills_deployer.py +302 -94
- claude_mpm/services/profile_manager.py +10 -4
- claude_mpm/services/skills/git_skill_source_manager.py +192 -29
- claude_mpm/services/skills/selective_skill_deployer.py +211 -46
- claude_mpm/services/skills/skill_discovery_service.py +74 -4
- claude_mpm/services/skills_deployer.py +192 -70
- claude_mpm/services/socketio/handlers/hook.py +14 -7
- claude_mpm/services/socketio/server/main.py +12 -4
- 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 +29 -23
- claude_mpm/templates/.pre-commit-config.yaml +112 -0
- claude_mpm/utils/agent_dependency_loader.py +103 -4
- claude_mpm/utils/robust_installer.py +45 -24
- claude_mpm-5.6.72.dist-info/METADATA +416 -0
- {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.72.dist-info}/RECORD +477 -159
- {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.72.dist-info}/WHEEL +1 -1
- {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.72.dist-info}/entry_points.txt +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.B_FtCwCQ.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.Cl_eSA4x.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BgChzWQ1.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIXEwuWe.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CWc5urbQ.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DMkZpdF2.js +0 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.DTL5mJO-.js +0 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.DzuEhzqh.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.DFLC8jdE.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.DPvEihJJ.js +0 -10
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
- claude_mpm-5.4.41.dist-info/METADATA +0 -998
- {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.72.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.72.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.72.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
"""Project registry for MPM Commander.
|
|
2
|
+
|
|
3
|
+
This module provides thread-safe registration and management of projects,
|
|
4
|
+
including state tracking, session management, and path indexing.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import threading
|
|
9
|
+
import uuid
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
from .models import Project, ProjectState, ToolSession
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ProjectRegistry:
|
|
20
|
+
"""Thread-safe registry for managing projects.
|
|
21
|
+
|
|
22
|
+
Maintains an in-memory registry of all active projects with:
|
|
23
|
+
- Unique project IDs (UUIDs)
|
|
24
|
+
- Path-based indexing for fast lookup
|
|
25
|
+
- Thread-safe access with RLock
|
|
26
|
+
- State management and session tracking
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
>>> registry = ProjectRegistry()
|
|
30
|
+
>>> project = registry.register("/Users/masa/Projects/my-app")
|
|
31
|
+
>>> project.id
|
|
32
|
+
'a3f2c1d4-...'
|
|
33
|
+
>>> registry.update_state(project.id, ProjectState.WORKING)
|
|
34
|
+
>>> registry.get(project.id).state
|
|
35
|
+
<ProjectState.WORKING: 'working'>
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self):
|
|
39
|
+
"""Initialize empty registry with thread-safe lock."""
|
|
40
|
+
self._projects: Dict[str, Project] = {}
|
|
41
|
+
self._path_index: Dict[str, str] = {} # path -> project_id
|
|
42
|
+
self._lock = threading.RLock()
|
|
43
|
+
logger.info("Initialized ProjectRegistry")
|
|
44
|
+
|
|
45
|
+
def register(
|
|
46
|
+
self, path: str, name: Optional[str] = None, project_id: Optional[str] = None
|
|
47
|
+
) -> Project:
|
|
48
|
+
"""Register a new project.
|
|
49
|
+
|
|
50
|
+
Creates a new project with unique UUID (or user-provided ID) and adds it to registry.
|
|
51
|
+
Path must be a valid directory and cannot already be registered.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
path: Absolute filesystem path to project directory
|
|
55
|
+
name: Optional human-readable name (defaults to directory name)
|
|
56
|
+
project_id: Optional project identifier (UUID generated if omitted)
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Newly created Project instance
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
ValueError: If path is invalid, not a directory, or already registered
|
|
63
|
+
|
|
64
|
+
Example:
|
|
65
|
+
>>> registry = ProjectRegistry()
|
|
66
|
+
>>> project = registry.register("/Users/masa/Projects/my-app")
|
|
67
|
+
>>> project.name
|
|
68
|
+
'my-app'
|
|
69
|
+
>>> project.state
|
|
70
|
+
<ProjectState.IDLE: 'idle'>
|
|
71
|
+
"""
|
|
72
|
+
with self._lock:
|
|
73
|
+
# Validate path exists and is directory
|
|
74
|
+
path_obj = Path(path)
|
|
75
|
+
try:
|
|
76
|
+
if not path_obj.exists():
|
|
77
|
+
raise ValueError(f"Path does not exist: {path}")
|
|
78
|
+
if not path_obj.is_dir():
|
|
79
|
+
raise ValueError(f"Path is not a directory: {path}")
|
|
80
|
+
except (OSError, PermissionError) as e:
|
|
81
|
+
raise ValueError(f"Cannot access path: {path}") from e
|
|
82
|
+
|
|
83
|
+
# Resolve to absolute path for consistency
|
|
84
|
+
abs_path = str(path_obj.resolve())
|
|
85
|
+
|
|
86
|
+
# Check for duplicate registration
|
|
87
|
+
if abs_path in self._path_index:
|
|
88
|
+
existing_id = self._path_index[abs_path]
|
|
89
|
+
raise ValueError(
|
|
90
|
+
f"Project already registered at path: {abs_path} "
|
|
91
|
+
f"(project_id: {existing_id})"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Derive name from directory if not provided
|
|
95
|
+
if name is None:
|
|
96
|
+
name = path_obj.name
|
|
97
|
+
|
|
98
|
+
# Generate unique project ID if not provided
|
|
99
|
+
if project_id is None:
|
|
100
|
+
project_id = str(uuid.uuid4())
|
|
101
|
+
elif project_id in self._projects:
|
|
102
|
+
raise ValueError(f"Project ID already exists: {project_id}")
|
|
103
|
+
|
|
104
|
+
# Create project instance
|
|
105
|
+
project = Project(
|
|
106
|
+
id=project_id,
|
|
107
|
+
path=abs_path,
|
|
108
|
+
name=name,
|
|
109
|
+
state=ProjectState.IDLE,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Register in both indexes
|
|
113
|
+
self._projects[project_id] = project
|
|
114
|
+
self._path_index[abs_path] = project_id
|
|
115
|
+
|
|
116
|
+
logger.info(
|
|
117
|
+
"Registered project: id=%s, path=%s, name=%s",
|
|
118
|
+
project_id,
|
|
119
|
+
abs_path,
|
|
120
|
+
name,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
return project
|
|
124
|
+
|
|
125
|
+
def unregister(self, project_id: str) -> None:
|
|
126
|
+
"""Remove project from registry.
|
|
127
|
+
|
|
128
|
+
Removes project from both ID and path indexes.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
project_id: Unique project identifier
|
|
132
|
+
|
|
133
|
+
Raises:
|
|
134
|
+
KeyError: If project_id not found
|
|
135
|
+
|
|
136
|
+
Example:
|
|
137
|
+
>>> registry = ProjectRegistry()
|
|
138
|
+
>>> project = registry.register("/tmp/test-project")
|
|
139
|
+
>>> registry.unregister(project.id)
|
|
140
|
+
>>> registry.get(project.id) is None
|
|
141
|
+
True
|
|
142
|
+
"""
|
|
143
|
+
with self._lock:
|
|
144
|
+
if project_id not in self._projects:
|
|
145
|
+
raise KeyError(f"Project not found: {project_id}")
|
|
146
|
+
|
|
147
|
+
project = self._projects[project_id]
|
|
148
|
+
|
|
149
|
+
# Remove from both indexes
|
|
150
|
+
del self._projects[project_id]
|
|
151
|
+
del self._path_index[project.path]
|
|
152
|
+
|
|
153
|
+
logger.info(
|
|
154
|
+
"Unregistered project: id=%s, path=%s",
|
|
155
|
+
project_id,
|
|
156
|
+
project.path,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
def get(self, project_id: str) -> Optional[Project]:
|
|
160
|
+
"""Get project by ID.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
project_id: Unique project identifier
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Project instance or None if not found
|
|
167
|
+
|
|
168
|
+
Example:
|
|
169
|
+
>>> registry = ProjectRegistry()
|
|
170
|
+
>>> project = registry.register("/tmp/test")
|
|
171
|
+
>>> registry.get(project.id).name
|
|
172
|
+
'test'
|
|
173
|
+
>>> registry.get("invalid-id") is None
|
|
174
|
+
True
|
|
175
|
+
"""
|
|
176
|
+
with self._lock:
|
|
177
|
+
return self._projects.get(project_id)
|
|
178
|
+
|
|
179
|
+
def get_by_path(self, path: str) -> Optional[Project]:
|
|
180
|
+
"""Get project by filesystem path.
|
|
181
|
+
|
|
182
|
+
Resolves path to absolute before lookup.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
path: Filesystem path to project
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Project instance or None if not found
|
|
189
|
+
|
|
190
|
+
Example:
|
|
191
|
+
>>> registry = ProjectRegistry()
|
|
192
|
+
>>> project = registry.register("/tmp/test")
|
|
193
|
+
>>> found = registry.get_by_path("/tmp/test")
|
|
194
|
+
>>> found.id == project.id
|
|
195
|
+
True
|
|
196
|
+
"""
|
|
197
|
+
with self._lock:
|
|
198
|
+
# Resolve to absolute path for consistent lookup
|
|
199
|
+
try:
|
|
200
|
+
abs_path = str(Path(path).resolve())
|
|
201
|
+
except (OSError, ValueError):
|
|
202
|
+
# Invalid path
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
project_id = self._path_index.get(abs_path)
|
|
206
|
+
if project_id is None:
|
|
207
|
+
return None
|
|
208
|
+
|
|
209
|
+
return self._projects.get(project_id)
|
|
210
|
+
|
|
211
|
+
def list_all(self) -> List[Project]:
|
|
212
|
+
"""List all registered projects.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
List of all Project instances (may be empty)
|
|
216
|
+
|
|
217
|
+
Example:
|
|
218
|
+
>>> registry = ProjectRegistry()
|
|
219
|
+
>>> registry.register("/tmp/proj1")
|
|
220
|
+
>>> registry.register("/tmp/proj2")
|
|
221
|
+
>>> len(registry.list_all())
|
|
222
|
+
2
|
|
223
|
+
"""
|
|
224
|
+
with self._lock:
|
|
225
|
+
return list(self._projects.values())
|
|
226
|
+
|
|
227
|
+
def list_by_state(self, state: ProjectState) -> List[Project]:
|
|
228
|
+
"""List projects in specific state.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
state: ProjectState to filter by
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
List of projects in given state (may be empty)
|
|
235
|
+
|
|
236
|
+
Example:
|
|
237
|
+
>>> registry = ProjectRegistry()
|
|
238
|
+
>>> p1 = registry.register("/tmp/proj1")
|
|
239
|
+
>>> p2 = registry.register("/tmp/proj2")
|
|
240
|
+
>>> registry.update_state(p1.id, ProjectState.WORKING)
|
|
241
|
+
>>> working = registry.list_by_state(ProjectState.WORKING)
|
|
242
|
+
>>> len(working)
|
|
243
|
+
1
|
|
244
|
+
>>> working[0].id == p1.id
|
|
245
|
+
True
|
|
246
|
+
"""
|
|
247
|
+
with self._lock:
|
|
248
|
+
return [p for p in self._projects.values() if p.state == state]
|
|
249
|
+
|
|
250
|
+
def update_state(
|
|
251
|
+
self,
|
|
252
|
+
project_id: str,
|
|
253
|
+
state: ProjectState,
|
|
254
|
+
reason: Optional[str] = None,
|
|
255
|
+
) -> None:
|
|
256
|
+
"""Update project state.
|
|
257
|
+
|
|
258
|
+
Updates both state and optional reason, and touches last_activity.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
project_id: Unique project identifier
|
|
262
|
+
state: New ProjectState
|
|
263
|
+
reason: Optional state reason (e.g., error message)
|
|
264
|
+
|
|
265
|
+
Raises:
|
|
266
|
+
KeyError: If project_id not found
|
|
267
|
+
|
|
268
|
+
Example:
|
|
269
|
+
>>> registry = ProjectRegistry()
|
|
270
|
+
>>> project = registry.register("/tmp/test")
|
|
271
|
+
>>> registry.update_state(
|
|
272
|
+
... project.id,
|
|
273
|
+
... ProjectState.ERROR,
|
|
274
|
+
... reason="Connection timeout"
|
|
275
|
+
... )
|
|
276
|
+
>>> project.state
|
|
277
|
+
<ProjectState.ERROR: 'error'>
|
|
278
|
+
>>> project.state_reason
|
|
279
|
+
'Connection timeout'
|
|
280
|
+
"""
|
|
281
|
+
with self._lock:
|
|
282
|
+
if project_id not in self._projects:
|
|
283
|
+
raise KeyError(f"Project not found: {project_id}")
|
|
284
|
+
|
|
285
|
+
project = self._projects[project_id]
|
|
286
|
+
old_state = project.state
|
|
287
|
+
|
|
288
|
+
project.state = state
|
|
289
|
+
project.state_reason = reason
|
|
290
|
+
project.last_activity = datetime.now(timezone.utc)
|
|
291
|
+
|
|
292
|
+
logger.info(
|
|
293
|
+
"State change: project=%s, %s -> %s, reason=%s",
|
|
294
|
+
project_id,
|
|
295
|
+
old_state.value,
|
|
296
|
+
state.value,
|
|
297
|
+
reason,
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
def add_session(self, project_id: str, session: ToolSession) -> None:
|
|
301
|
+
"""Add session to project.
|
|
302
|
+
|
|
303
|
+
Adds tool session to project's session dict and updates last_activity.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
project_id: Unique project identifier
|
|
307
|
+
session: ToolSession to add
|
|
308
|
+
|
|
309
|
+
Raises:
|
|
310
|
+
KeyError: If project_id not found
|
|
311
|
+
|
|
312
|
+
Example:
|
|
313
|
+
>>> registry = ProjectRegistry()
|
|
314
|
+
>>> project = registry.register("/tmp/test")
|
|
315
|
+
>>> session = ToolSession(
|
|
316
|
+
... id="sess-123",
|
|
317
|
+
... project_id=project.id,
|
|
318
|
+
... runtime="claude-code",
|
|
319
|
+
... tmux_target="commander:test-cc"
|
|
320
|
+
... )
|
|
321
|
+
>>> registry.add_session(project.id, session)
|
|
322
|
+
>>> len(project.sessions)
|
|
323
|
+
1
|
|
324
|
+
"""
|
|
325
|
+
with self._lock:
|
|
326
|
+
if project_id not in self._projects:
|
|
327
|
+
raise KeyError(f"Project not found: {project_id}")
|
|
328
|
+
|
|
329
|
+
project = self._projects[project_id]
|
|
330
|
+
project.sessions[session.id] = session
|
|
331
|
+
project.last_activity = datetime.now(timezone.utc)
|
|
332
|
+
|
|
333
|
+
logger.info(
|
|
334
|
+
"Added session: project=%s, session=%s, runtime=%s",
|
|
335
|
+
project_id,
|
|
336
|
+
session.id,
|
|
337
|
+
session.runtime,
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
def remove_session(self, project_id: str, session_id: str) -> None:
|
|
341
|
+
"""Remove session from project.
|
|
342
|
+
|
|
343
|
+
Removes tool session from project's session dict and updates last_activity.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
project_id: Unique project identifier
|
|
347
|
+
session_id: Session ID to remove
|
|
348
|
+
|
|
349
|
+
Raises:
|
|
350
|
+
KeyError: If project_id not found or session_id not in project
|
|
351
|
+
|
|
352
|
+
Example:
|
|
353
|
+
>>> registry = ProjectRegistry()
|
|
354
|
+
>>> project = registry.register("/tmp/test")
|
|
355
|
+
>>> session = ToolSession(
|
|
356
|
+
... id="sess-123",
|
|
357
|
+
... project_id=project.id,
|
|
358
|
+
... runtime="claude-code",
|
|
359
|
+
... tmux_target="commander:test-cc"
|
|
360
|
+
... )
|
|
361
|
+
>>> registry.add_session(project.id, session)
|
|
362
|
+
>>> registry.remove_session(project.id, session.id)
|
|
363
|
+
>>> len(project.sessions)
|
|
364
|
+
0
|
|
365
|
+
"""
|
|
366
|
+
with self._lock:
|
|
367
|
+
if project_id not in self._projects:
|
|
368
|
+
raise KeyError(f"Project not found: {project_id}")
|
|
369
|
+
|
|
370
|
+
project = self._projects[project_id]
|
|
371
|
+
|
|
372
|
+
if session_id not in project.sessions:
|
|
373
|
+
raise KeyError(f"Session not found in project: {session_id}")
|
|
374
|
+
|
|
375
|
+
del project.sessions[session_id]
|
|
376
|
+
project.last_activity = datetime.now(timezone.utc)
|
|
377
|
+
|
|
378
|
+
logger.info(
|
|
379
|
+
"Removed session: project=%s, session=%s",
|
|
380
|
+
project_id,
|
|
381
|
+
session_id,
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
def touch(self, project_id: str) -> None:
|
|
385
|
+
"""Update last_activity timestamp.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
project_id: Unique project identifier
|
|
389
|
+
|
|
390
|
+
Raises:
|
|
391
|
+
KeyError: If project_id not found
|
|
392
|
+
|
|
393
|
+
Example:
|
|
394
|
+
>>> import time
|
|
395
|
+
>>> registry = ProjectRegistry()
|
|
396
|
+
>>> project = registry.register("/tmp/test")
|
|
397
|
+
>>> old_time = project.last_activity
|
|
398
|
+
>>> time.sleep(0.01)
|
|
399
|
+
>>> registry.touch(project.id)
|
|
400
|
+
>>> project.last_activity > old_time
|
|
401
|
+
True
|
|
402
|
+
"""
|
|
403
|
+
with self._lock:
|
|
404
|
+
if project_id not in self._projects:
|
|
405
|
+
raise KeyError(f"Project not found: {project_id}")
|
|
406
|
+
|
|
407
|
+
project = self._projects[project_id]
|
|
408
|
+
project.last_activity = datetime.now(timezone.utc)
|
|
409
|
+
|
|
410
|
+
logger.debug("Touched project: %s", project_id)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Runtime integration for MPM Commander.
|
|
2
|
+
|
|
3
|
+
This module provides components for spawning and monitoring Claude Code instances
|
|
4
|
+
in tmux panes, enabling autonomous task execution with event detection.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .executor import RuntimeExecutor
|
|
8
|
+
from .monitor import RuntimeMonitor
|
|
9
|
+
|
|
10
|
+
__all__ = ["RuntimeExecutor", "RuntimeMonitor"]
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""Runtime executor for spawning and managing Claude Code instances in tmux.
|
|
2
|
+
|
|
3
|
+
This module provides RuntimeExecutor which spawns Claude Code processes in tmux
|
|
4
|
+
panes and manages their lifecycle, including sending messages and terminating.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
from ..models.project import Project
|
|
10
|
+
from ..tmux_orchestrator import TmuxOrchestrator
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RuntimeExecutor:
|
|
16
|
+
"""Spawns and manages Claude Code processes in tmux panes.
|
|
17
|
+
|
|
18
|
+
This class handles the lifecycle of Claude Code instances running in tmux panes,
|
|
19
|
+
providing capabilities to spawn new instances, send messages, check status,
|
|
20
|
+
and terminate processes.
|
|
21
|
+
|
|
22
|
+
Attributes:
|
|
23
|
+
orchestrator: TmuxOrchestrator instance for tmux operations
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
>>> orchestrator = TmuxOrchestrator()
|
|
27
|
+
>>> executor = RuntimeExecutor(orchestrator)
|
|
28
|
+
>>> pane_target = await executor.spawn(project, "claude")
|
|
29
|
+
>>> await executor.send_message(pane_target, "Implement user authentication")
|
|
30
|
+
>>> if executor.is_running(pane_target):
|
|
31
|
+
... await executor.terminate(pane_target)
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, orchestrator: TmuxOrchestrator):
|
|
35
|
+
"""Initialize runtime executor.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
orchestrator: TmuxOrchestrator for tmux operations
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
ValueError: If orchestrator is None
|
|
42
|
+
"""
|
|
43
|
+
if orchestrator is None:
|
|
44
|
+
raise ValueError("Orchestrator cannot be None")
|
|
45
|
+
|
|
46
|
+
self.orchestrator = orchestrator
|
|
47
|
+
logger.debug("RuntimeExecutor initialized")
|
|
48
|
+
|
|
49
|
+
async def spawn(self, project: Project, command: str = "claude") -> str:
|
|
50
|
+
"""Spawn Claude Code in a new tmux pane for the project.
|
|
51
|
+
|
|
52
|
+
Creates a new tmux pane in the project's working directory and spawns
|
|
53
|
+
the specified command (typically "claude" for Claude Code).
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
project: Project instance with working directory
|
|
57
|
+
command: Command to run (default: "claude")
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
The pane target (e.g., '%5') that can be used for subsequent operations
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
RuntimeError: If pane creation fails
|
|
64
|
+
ValueError: If project or project.path is None
|
|
65
|
+
|
|
66
|
+
Example:
|
|
67
|
+
>>> project = Project(id="proj1", path="/path/to/project")
|
|
68
|
+
>>> pane_target = await executor.spawn(project)
|
|
69
|
+
>>> print(f"Spawned in pane: {pane_target}")
|
|
70
|
+
Spawned in pane: %5
|
|
71
|
+
"""
|
|
72
|
+
if project is None:
|
|
73
|
+
raise ValueError("Project cannot be None")
|
|
74
|
+
if not project.path:
|
|
75
|
+
raise ValueError("Project path cannot be None or empty")
|
|
76
|
+
|
|
77
|
+
logger.info(
|
|
78
|
+
"Spawning %s for project %s in %s", command, project.id, project.path
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
# Create tmux session if it doesn't exist
|
|
83
|
+
if not self.orchestrator.session_exists():
|
|
84
|
+
self.orchestrator.create_session()
|
|
85
|
+
logger.debug("Created tmux session")
|
|
86
|
+
|
|
87
|
+
# Create pane with project working directory
|
|
88
|
+
pane_target = self.orchestrator.create_pane(project.id, project.path)
|
|
89
|
+
logger.debug("Created pane: %s", pane_target)
|
|
90
|
+
|
|
91
|
+
# Send command to pane
|
|
92
|
+
self.orchestrator.send_keys(pane_target, command, enter=True)
|
|
93
|
+
logger.info("Spawned %s in pane %s", command, pane_target)
|
|
94
|
+
|
|
95
|
+
return pane_target
|
|
96
|
+
|
|
97
|
+
except Exception as e:
|
|
98
|
+
logger.error(
|
|
99
|
+
"Failed to spawn %s for project %s: %s", command, project.id, e
|
|
100
|
+
)
|
|
101
|
+
raise RuntimeError(f"Failed to spawn {command}: {e}") from e
|
|
102
|
+
|
|
103
|
+
async def send_message(self, pane_target: str, message: str) -> None:
|
|
104
|
+
"""Send a message/command to a running Claude instance.
|
|
105
|
+
|
|
106
|
+
Sends the message to the specified tmux pane, followed by Enter.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
pane_target: Pane target from spawn()
|
|
110
|
+
message: Message to send to Claude Code
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
ValueError: If pane_target or message is None/empty
|
|
114
|
+
RuntimeError: If sending message fails
|
|
115
|
+
|
|
116
|
+
Example:
|
|
117
|
+
>>> await executor.send_message("%5", "Fix the authentication bug")
|
|
118
|
+
"""
|
|
119
|
+
if not pane_target:
|
|
120
|
+
raise ValueError("Pane target cannot be None or empty")
|
|
121
|
+
if not message:
|
|
122
|
+
raise ValueError("Message cannot be None or empty")
|
|
123
|
+
|
|
124
|
+
logger.debug("Sending message to pane %s: %s", pane_target, message[:50])
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
self.orchestrator.send_keys(pane_target, message, enter=True)
|
|
128
|
+
logger.info("Sent message to pane %s", pane_target)
|
|
129
|
+
|
|
130
|
+
except Exception as e:
|
|
131
|
+
logger.error("Failed to send message to pane %s: %s", pane_target, e)
|
|
132
|
+
raise RuntimeError(
|
|
133
|
+
f"Failed to send message to pane {pane_target}: {e}"
|
|
134
|
+
) from e
|
|
135
|
+
|
|
136
|
+
async def terminate(self, pane_target: str) -> None:
|
|
137
|
+
"""Terminate a Claude Code instance.
|
|
138
|
+
|
|
139
|
+
Kills the specified tmux pane, terminating the Claude Code process.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
pane_target: Pane target from spawn()
|
|
143
|
+
|
|
144
|
+
Raises:
|
|
145
|
+
ValueError: If pane_target is None/empty
|
|
146
|
+
RuntimeError: If termination fails
|
|
147
|
+
|
|
148
|
+
Example:
|
|
149
|
+
>>> await executor.terminate("%5")
|
|
150
|
+
"""
|
|
151
|
+
if not pane_target:
|
|
152
|
+
raise ValueError("Pane target cannot be None or empty")
|
|
153
|
+
|
|
154
|
+
logger.info("Terminating pane %s", pane_target)
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
self.orchestrator.kill_pane(pane_target)
|
|
158
|
+
logger.info("Terminated pane %s", pane_target)
|
|
159
|
+
|
|
160
|
+
except Exception as e:
|
|
161
|
+
logger.error("Failed to terminate pane %s: %s", pane_target, e)
|
|
162
|
+
raise RuntimeError(f"Failed to terminate pane {pane_target}: {e}") from e
|
|
163
|
+
|
|
164
|
+
def is_running(self, pane_target: str) -> bool:
|
|
165
|
+
"""Check if a pane is still active.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
pane_target: Pane target from spawn()
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
True if pane exists and is running, False otherwise
|
|
172
|
+
|
|
173
|
+
Example:
|
|
174
|
+
>>> if executor.is_running("%5"):
|
|
175
|
+
... print("Pane is still running")
|
|
176
|
+
"""
|
|
177
|
+
if not pane_target:
|
|
178
|
+
return False
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
# List all panes and check if target exists
|
|
182
|
+
panes = self.orchestrator.list_panes()
|
|
183
|
+
pane_ids = [pane["id"] for pane in panes]
|
|
184
|
+
is_active = pane_target in pane_ids
|
|
185
|
+
|
|
186
|
+
logger.debug("Pane %s running: %s", pane_target, is_active)
|
|
187
|
+
return is_active
|
|
188
|
+
|
|
189
|
+
except Exception as e:
|
|
190
|
+
logger.warning("Error checking if pane %s is running: %s", pane_target, e)
|
|
191
|
+
return False
|