claude-mpm 3.4.10__py3-none-any.whl → 5.4.55__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/BUILD_NUMBER +1 -0
- claude_mpm/VERSION +1 -0
- claude_mpm/__init__.py +50 -12
- claude_mpm/__main__.py +7 -2
- claude_mpm/agents/BASE_AGENT.md +164 -0
- claude_mpm/agents/BASE_ENGINEER.md +658 -0
- claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +290 -0
- claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +2002 -0
- claude_mpm/agents/MEMORY.md +72 -0
- claude_mpm/agents/PM_INSTRUCTIONS.md +1402 -0
- claude_mpm/agents/WORKFLOW.md +111 -0
- claude_mpm/agents/__init__.py +92 -80
- claude_mpm/agents/agent-template.yaml +83 -0
- claude_mpm/agents/agent_loader.py +560 -745
- claude_mpm/agents/agent_loader_integration.py +53 -55
- claude_mpm/agents/agents_metadata.py +186 -27
- claude_mpm/agents/async_agent_loader.py +436 -0
- claude_mpm/agents/base_agent.json +8 -4
- claude_mpm/agents/frontmatter_validator.py +754 -0
- claude_mpm/agents/system_agent_config.py +222 -155
- claude_mpm/agents/templates/README.md +465 -0
- claude_mpm/agents/templates/__init__.py +17 -13
- claude_mpm/agents/templates/circuit-breakers.md +1391 -0
- claude_mpm/agents/templates/context-management-examples.md +544 -0
- claude_mpm/agents/templates/git-file-tracking.md +584 -0
- claude_mpm/agents/templates/pm-examples.md +474 -0
- claude_mpm/agents/templates/pm-red-flags.md +310 -0
- claude_mpm/agents/templates/pr-workflow-examples.md +427 -0
- claude_mpm/agents/templates/research-gate-examples.md +669 -0
- claude_mpm/agents/templates/response-format.md +583 -0
- claude_mpm/agents/templates/structured-questions-examples.md +615 -0
- claude_mpm/agents/templates/ticket-completeness-examples.md +139 -0
- claude_mpm/agents/templates/ticketing-examples.md +277 -0
- claude_mpm/agents/templates/validation-templates.md +312 -0
- claude_mpm/cli/__init__.py +90 -128
- claude_mpm/cli/__main__.py +33 -0
- claude_mpm/cli/chrome_devtools_installer.py +175 -0
- claude_mpm/cli/commands/__init__.py +36 -12
- claude_mpm/cli/commands/agent_manager.py +1403 -0
- claude_mpm/cli/commands/agent_source.py +774 -0
- claude_mpm/cli/commands/agent_state_manager.py +335 -0
- claude_mpm/cli/commands/agents.py +2503 -168
- claude_mpm/cli/commands/agents_cleanup.py +210 -0
- claude_mpm/cli/commands/agents_discover.py +338 -0
- claude_mpm/cli/commands/aggregate.py +540 -0
- claude_mpm/cli/commands/analyze.py +553 -0
- claude_mpm/cli/commands/analyze_code.py +528 -0
- claude_mpm/cli/commands/auto_configure.py +1053 -0
- claude_mpm/cli/commands/cleanup.py +588 -0
- claude_mpm/cli/commands/cleanup_orphaned_agents.py +150 -0
- claude_mpm/cli/commands/config.py +586 -0
- claude_mpm/cli/commands/configure.py +2654 -0
- claude_mpm/cli/commands/configure_agent_display.py +282 -0
- claude_mpm/cli/commands/configure_behavior_manager.py +204 -0
- claude_mpm/cli/commands/configure_hook_manager.py +225 -0
- claude_mpm/cli/commands/configure_models.py +18 -0
- claude_mpm/cli/commands/configure_navigation.py +184 -0
- claude_mpm/cli/commands/configure_paths.py +104 -0
- claude_mpm/cli/commands/configure_persistence.py +254 -0
- claude_mpm/cli/commands/configure_startup_manager.py +646 -0
- claude_mpm/cli/commands/configure_template_editor.py +497 -0
- claude_mpm/cli/commands/configure_validators.py +73 -0
- claude_mpm/cli/commands/dashboard.py +286 -0
- claude_mpm/cli/commands/debug.py +1386 -0
- claude_mpm/cli/commands/doctor.py +243 -0
- claude_mpm/cli/commands/hook_errors.py +277 -0
- claude_mpm/cli/commands/info.py +195 -74
- claude_mpm/cli/commands/local_deploy.py +534 -0
- claude_mpm/cli/commands/mcp.py +205 -0
- claude_mpm/cli/commands/mcp_command_router.py +161 -0
- claude_mpm/cli/commands/mcp_config.py +154 -0
- claude_mpm/cli/commands/mcp_config_commands.py +20 -0
- claude_mpm/cli/commands/mcp_external_commands.py +249 -0
- claude_mpm/cli/commands/mcp_install_commands.py +346 -0
- claude_mpm/cli/commands/mcp_pipx_config.py +208 -0
- claude_mpm/cli/commands/mcp_server_commands.py +155 -0
- claude_mpm/cli/commands/mcp_setup_external.py +868 -0
- claude_mpm/cli/commands/mcp_tool_commands.py +34 -0
- claude_mpm/cli/commands/memory.py +585 -846
- claude_mpm/cli/commands/monitor.py +228 -310
- claude_mpm/cli/commands/mpm_init/__init__.py +73 -0
- claude_mpm/cli/commands/mpm_init/core.py +759 -0
- claude_mpm/cli/commands/mpm_init/display.py +341 -0
- claude_mpm/cli/commands/mpm_init/git_activity.py +427 -0
- claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
- claude_mpm/cli/commands/mpm_init/modes.py +397 -0
- claude_mpm/cli/commands/mpm_init/prompts.py +722 -0
- claude_mpm/cli/commands/mpm_init_cli.py +396 -0
- claude_mpm/cli/commands/mpm_init_handler.py +195 -0
- claude_mpm/cli/commands/postmortem.py +401 -0
- claude_mpm/cli/commands/profile.py +276 -0
- claude_mpm/cli/commands/run.py +910 -488
- claude_mpm/cli/commands/search.py +458 -0
- claude_mpm/cli/commands/skill_source.py +694 -0
- claude_mpm/cli/commands/skills.py +1246 -0
- claude_mpm/cli/commands/summarize.py +413 -0
- claude_mpm/cli/commands/tickets.py +536 -53
- claude_mpm/cli/commands/uninstall.py +176 -0
- claude_mpm/cli/commands/upgrade.py +152 -0
- claude_mpm/cli/commands/verify.py +119 -0
- claude_mpm/cli/executor.py +297 -0
- claude_mpm/cli/helpers.py +105 -0
- claude_mpm/cli/interactive/__init__.py +21 -0
- claude_mpm/cli/interactive/agent_wizard.py +1947 -0
- claude_mpm/cli/interactive/skills_wizard.py +491 -0
- claude_mpm/cli/parser.py +87 -563
- claude_mpm/cli/parsers/__init__.py +35 -0
- claude_mpm/cli/parsers/agent_manager_parser.py +393 -0
- claude_mpm/cli/parsers/agent_source_parser.py +171 -0
- claude_mpm/cli/parsers/agents_parser.py +575 -0
- claude_mpm/cli/parsers/analyze_code_parser.py +170 -0
- claude_mpm/cli/parsers/analyze_parser.py +135 -0
- claude_mpm/cli/parsers/auto_configure_parser.py +120 -0
- claude_mpm/cli/parsers/base_parser.py +644 -0
- claude_mpm/cli/parsers/config_parser.py +208 -0
- claude_mpm/cli/parsers/configure_parser.py +138 -0
- claude_mpm/cli/parsers/dashboard_parser.py +113 -0
- claude_mpm/cli/parsers/debug_parser.py +319 -0
- claude_mpm/cli/parsers/local_deploy_parser.py +227 -0
- claude_mpm/cli/parsers/mcp_parser.py +195 -0
- claude_mpm/cli/parsers/memory_parser.py +138 -0
- claude_mpm/cli/parsers/monitor_parser.py +142 -0
- claude_mpm/cli/parsers/mpm_init_parser.py +311 -0
- claude_mpm/cli/parsers/profile_parser.py +147 -0
- claude_mpm/cli/parsers/run_parser.py +157 -0
- claude_mpm/cli/parsers/search_parser.py +245 -0
- claude_mpm/cli/parsers/skill_source_parser.py +169 -0
- claude_mpm/cli/parsers/skills_parser.py +277 -0
- claude_mpm/cli/parsers/source_parser.py +138 -0
- claude_mpm/cli/parsers/tickets_parser.py +203 -0
- claude_mpm/cli/shared/__init__.py +40 -0
- claude_mpm/cli/shared/argument_patterns.py +205 -0
- claude_mpm/cli/shared/base_command.py +242 -0
- claude_mpm/cli/shared/error_handling.py +242 -0
- claude_mpm/cli/shared/output_formatters.py +241 -0
- claude_mpm/cli/startup.py +1743 -0
- claude_mpm/cli/startup_display.py +480 -0
- claude_mpm/cli/startup_logging.py +839 -0
- claude_mpm/cli/utils.py +136 -47
- claude_mpm/cli_module/__init__.py +6 -6
- claude_mpm/cli_module/args.py +188 -140
- claude_mpm/cli_module/commands.py +79 -70
- claude_mpm/cli_module/migration_example.py +42 -64
- claude_mpm/commands/__init__.py +14 -0
- claude_mpm/commands/mpm-config.md +28 -0
- claude_mpm/commands/mpm-doctor.md +20 -0
- claude_mpm/commands/mpm-help.md +20 -0
- claude_mpm/commands/mpm-init.md +120 -0
- claude_mpm/commands/mpm-monitor.md +31 -0
- claude_mpm/commands/mpm-organize.md +120 -0
- claude_mpm/commands/mpm-postmortem.md +21 -0
- claude_mpm/commands/mpm-session-resume.md +30 -0
- claude_mpm/commands/mpm-status.md +20 -0
- claude_mpm/commands/mpm-ticket-view.md +109 -0
- claude_mpm/commands/mpm-version.md +20 -0
- claude_mpm/commands/mpm.md +31 -0
- claude_mpm/config/__init__.py +42 -2
- claude_mpm/config/agent_config.py +402 -0
- claude_mpm/config/agent_presets.py +488 -0
- claude_mpm/config/agent_sources.py +352 -0
- claude_mpm/config/experimental_features.py +217 -0
- claude_mpm/config/model_config.py +428 -0
- claude_mpm/config/paths.py +258 -0
- claude_mpm/config/skill_presets.py +392 -0
- claude_mpm/config/skill_sources.py +590 -0
- claude_mpm/config/socketio_config.py +125 -83
- claude_mpm/constants.py +132 -22
- claude_mpm/core/__init__.py +62 -36
- claude_mpm/core/agent_name_normalizer.py +71 -73
- claude_mpm/core/agent_registry.py +385 -492
- claude_mpm/core/agent_session_manager.py +81 -70
- claude_mpm/core/api_validator.py +330 -0
- claude_mpm/core/base_service.py +159 -122
- claude_mpm/core/cache.py +560 -0
- claude_mpm/core/claude_runner.py +696 -916
- claude_mpm/core/config.py +613 -122
- claude_mpm/core/config_aliases.py +74 -73
- claude_mpm/core/config_constants.py +314 -0
- claude_mpm/core/constants.py +361 -0
- claude_mpm/core/container.py +646 -104
- claude_mpm/core/enums.py +452 -0
- claude_mpm/core/error_handler.py +623 -0
- claude_mpm/core/exceptions.py +536 -0
- claude_mpm/core/factories.py +105 -109
- claude_mpm/core/file_utils.py +764 -0
- claude_mpm/core/framework/__init__.py +25 -0
- claude_mpm/core/framework/formatters/__init__.py +11 -0
- claude_mpm/core/framework/formatters/capability_generator.py +367 -0
- claude_mpm/core/framework/formatters/content_formatter.py +278 -0
- claude_mpm/core/framework/formatters/context_generator.py +185 -0
- claude_mpm/core/framework/loaders/__init__.py +13 -0
- claude_mpm/core/framework/loaders/agent_loader.py +213 -0
- claude_mpm/core/framework/loaders/file_loader.py +176 -0
- claude_mpm/core/framework/loaders/instruction_loader.py +222 -0
- claude_mpm/core/framework/loaders/packaged_loader.py +232 -0
- claude_mpm/core/framework/processors/__init__.py +11 -0
- claude_mpm/core/framework/processors/memory_processor.py +230 -0
- claude_mpm/core/framework/processors/metadata_processor.py +146 -0
- claude_mpm/core/framework/processors/template_processor.py +244 -0
- claude_mpm/core/framework_loader.py +485 -414
- claude_mpm/core/hook_error_memory.py +381 -0
- claude_mpm/core/hook_manager.py +246 -86
- claude_mpm/core/hook_performance_config.py +147 -0
- claude_mpm/core/injectable_service.py +72 -63
- claude_mpm/core/instruction_reinforcement_hook.py +267 -0
- claude_mpm/core/interactive_session.py +670 -0
- claude_mpm/core/interfaces.py +570 -164
- claude_mpm/core/lazy.py +467 -0
- claude_mpm/core/log_manager.py +707 -0
- claude_mpm/core/logger.py +295 -134
- claude_mpm/core/logging_config.py +474 -0
- claude_mpm/core/logging_utils.py +520 -0
- claude_mpm/core/minimal_framework_loader.py +24 -22
- claude_mpm/core/mixins.py +30 -29
- claude_mpm/core/oneshot_session.py +594 -0
- claude_mpm/core/optimized_agent_loader.py +479 -0
- claude_mpm/core/optimized_startup.py +554 -0
- claude_mpm/core/output_style_manager.py +483 -0
- claude_mpm/core/pm_hook_interceptor.py +197 -82
- claude_mpm/core/protocols/__init__.py +23 -0
- claude_mpm/core/protocols/runner_protocol.py +103 -0
- claude_mpm/core/protocols/session_protocol.py +131 -0
- claude_mpm/core/service_registry.py +153 -116
- claude_mpm/core/session_manager.py +179 -64
- claude_mpm/core/shared/__init__.py +17 -0
- claude_mpm/core/shared/config_loader.py +326 -0
- claude_mpm/core/shared/path_resolver.py +281 -0
- claude_mpm/core/shared/singleton_manager.py +221 -0
- claude_mpm/core/socketio_pool.py +400 -137
- claude_mpm/core/system_context.py +38 -0
- claude_mpm/core/tool_access_control.py +64 -57
- claude_mpm/core/types.py +307 -0
- claude_mpm/core/typing_utils.py +553 -0
- claude_mpm/core/unified_agent_registry.py +969 -0
- claude_mpm/core/unified_config.py +570 -0
- claude_mpm/core/unified_paths.py +941 -0
- claude_mpm/dashboard/__init__.py +12 -0
- claude_mpm/dashboard/api/simple_directory.py +261 -0
- claude_mpm/dashboard/static/svelte-build/_app/env.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +24 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B0uc0UOD.js +36 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B7RN905-.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B7xVLGWV.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BIF9m_hv.js +61 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BPYeabCQ.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/BSNlmTZj.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Be7GpZd6.js +7 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Bh0LDWpI.js +145 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BofRWZRR.js +10 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BovzEFCE.js +30 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C30mlcqg.js +165 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C4B-KCzX.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C4JcI4KD.js +122 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CBBdVcY8.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CDuw-vjf.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C_Usid8X.js +15 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cfqx1Qun.js +10 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CiIAseT4.js +128 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CmKTTxBW.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CnA0NrzZ.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cs_tUR18.js +24 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cu_Erd72.js +261 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CyWMqx4W.js +43 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CzZX-COe.js +220 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CzeYkLYB.js +65 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D3k0OPJN.js +4 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D9lljYKQ.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DGkLK5U1.js +267 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DI7hHRFL.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DLVjFsZ3.js +139 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DUrLdbGD.js +89 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DVp1hx9R.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DY1XQ8fi.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DZX00Y4g.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DaimHw_p.js +68 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +323 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dhb8PKl3.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dle-35c7.js +64 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DmxopI1J.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DwBR2MJi.js +60 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/GYwsonyD.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Gi6I4Gst.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/NqQ1dWOy.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/RJiighC3.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Vzk33B_K.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/ZGh7QtNv.js +7 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/bT1r9zLR.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/bTOqqlTd.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/eNVUfhuA.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/iEWssX7S.js +162 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/sQeU3Y1z.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/uuIeMWc-.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.D6-I5TpK.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/0.m1gL8KXf.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.CgNOuw-d.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.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/__init__.py +10 -0
- claude_mpm/experimental/cli_enhancements.py +104 -89
- claude_mpm/generators/__init__.py +1 -1
- claude_mpm/generators/agent_profile_generator.py +76 -66
- claude_mpm/hooks/__init__.py +37 -1
- claude_mpm/hooks/base_hook.py +37 -32
- claude_mpm/hooks/claude_hooks/__init__.py +1 -1
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.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__/installer.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/connection_pool.py +250 -0
- claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +888 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +652 -875
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +10 -7
- claude_mpm/hooks/claude_hooks/installer.py +806 -0
- claude_mpm/hooks/claude_hooks/memory_integration.py +249 -0
- claude_mpm/hooks/claude_hooks/response_tracking.py +412 -0
- claude_mpm/hooks/claude_hooks/services/__init__.py +15 -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__/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 +229 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +254 -0
- claude_mpm/hooks/claude_hooks/services/duplicate_detector.py +106 -0
- claude_mpm/hooks/claude_hooks/services/state_manager.py +284 -0
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +374 -0
- claude_mpm/hooks/claude_hooks/tool_analysis.py +224 -0
- claude_mpm/hooks/failure_learning/__init__.py +54 -0
- claude_mpm/hooks/failure_learning/failure_detection_hook.py +230 -0
- claude_mpm/hooks/failure_learning/fix_detection_hook.py +212 -0
- claude_mpm/hooks/failure_learning/learning_extraction_hook.py +281 -0
- claude_mpm/hooks/instruction_reinforcement.py +301 -0
- claude_mpm/hooks/kuzu_enrichment_hook.py +263 -0
- claude_mpm/hooks/kuzu_memory_hook.py +386 -0
- claude_mpm/hooks/kuzu_response_hook.py +179 -0
- claude_mpm/hooks/memory_integration_hook.py +201 -107
- claude_mpm/hooks/session_resume_hook.py +121 -0
- claude_mpm/hooks/templates/pre_tool_use_simple.py +78 -0
- claude_mpm/hooks/templates/pre_tool_use_template.py +323 -0
- claude_mpm/hooks/tool_call_interceptor.py +92 -76
- claude_mpm/hooks/validation_hooks.py +62 -54
- claude_mpm/init.py +518 -83
- claude_mpm/models/__init__.py +9 -9
- claude_mpm/models/agent_definition.py +40 -23
- claude_mpm/models/agent_session.py +538 -0
- claude_mpm/models/git_repository.py +198 -0
- claude_mpm/models/resume_log.py +340 -0
- claude_mpm/schemas/__init__.py +12 -0
- claude_mpm/scripts/__init__.py +15 -0
- claude_mpm/scripts/claude-hook-handler.sh +227 -0
- claude_mpm/scripts/launch_monitor.py +165 -0
- claude_mpm/scripts/mpm_doctor.py +322 -0
- claude_mpm/scripts/socketio_daemon.py +189 -200
- claude_mpm/scripts/start_activity_logging.py +91 -0
- claude_mpm/services/__init__.py +208 -39
- claude_mpm/services/agent_capabilities_service.py +266 -0
- claude_mpm/services/agents/__init__.py +89 -0
- claude_mpm/services/agents/agent_builder.py +514 -0
- claude_mpm/services/agents/agent_preset_service.py +238 -0
- 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 +484 -0
- claude_mpm/services/agents/auto_config_manager.py +796 -0
- claude_mpm/services/agents/auto_deploy_index_parser.py +569 -0
- claude_mpm/services/agents/cache_git_manager.py +621 -0
- claude_mpm/services/agents/deployment/__init__.py +21 -0
- claude_mpm/services/agents/deployment/agent_config_provider.py +410 -0
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +358 -0
- claude_mpm/services/agents/deployment/agent_definition_factory.py +80 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +1037 -0
- claude_mpm/services/agents/deployment/agent_discovery_service.py +546 -0
- claude_mpm/services/agents/deployment/agent_environment_manager.py +288 -0
- claude_mpm/services/agents/deployment/agent_filesystem_manager.py +383 -0
- claude_mpm/services/agents/deployment/agent_format_converter.py +505 -0
- claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +160 -0
- claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +957 -0
- claude_mpm/services/agents/deployment/agent_metrics_collector.py +273 -0
- claude_mpm/services/agents/deployment/agent_operation_service.py +573 -0
- claude_mpm/services/agents/deployment/agent_record_service.py +418 -0
- claude_mpm/services/agents/deployment/agent_restore_handler.py +84 -0
- claude_mpm/services/agents/deployment/agent_state_service.py +381 -0
- claude_mpm/services/agents/deployment/agent_template_builder.py +1369 -0
- claude_mpm/services/agents/deployment/agent_validator.py +376 -0
- claude_mpm/services/agents/deployment/agent_version_manager.py +322 -0
- claude_mpm/services/{agent_versioning.py → agents/deployment/agent_versioning.py} +10 -13
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +149 -0
- claude_mpm/services/agents/deployment/async_agent_deployment.py +768 -0
- claude_mpm/services/agents/deployment/base_agent_locator.py +132 -0
- claude_mpm/services/agents/deployment/config/__init__.py +13 -0
- claude_mpm/services/agents/deployment/config/deployment_config.py +181 -0
- claude_mpm/services/agents/deployment/config/deployment_config_manager.py +200 -0
- claude_mpm/services/agents/deployment/deployment_config_loader.py +178 -0
- claude_mpm/services/agents/deployment/deployment_results_manager.py +185 -0
- claude_mpm/services/agents/deployment/deployment_type_detector.py +120 -0
- claude_mpm/services/agents/deployment/deployment_wrapper.py +129 -0
- claude_mpm/services/agents/deployment/facade/__init__.py +18 -0
- claude_mpm/services/agents/deployment/facade/async_deployment_executor.py +159 -0
- claude_mpm/services/agents/deployment/facade/deployment_executor.py +70 -0
- claude_mpm/services/agents/deployment/facade/deployment_facade.py +269 -0
- claude_mpm/services/agents/deployment/facade/sync_deployment_executor.py +178 -0
- claude_mpm/services/agents/deployment/interface_adapter.py +226 -0
- claude_mpm/services/agents/deployment/lifecycle_health_checker.py +85 -0
- claude_mpm/services/agents/deployment/lifecycle_performance_tracker.py +100 -0
- claude_mpm/services/agents/deployment/local_template_deployment.py +362 -0
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +1478 -0
- claude_mpm/services/agents/deployment/pipeline/__init__.py +32 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +158 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +162 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +169 -0
- claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +19 -0
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +240 -0
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +110 -0
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +80 -0
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +92 -0
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +101 -0
- claude_mpm/services/agents/deployment/processors/__init__.py +15 -0
- claude_mpm/services/agents/deployment/processors/agent_deployment_context.py +102 -0
- claude_mpm/services/agents/deployment/processors/agent_deployment_result.py +235 -0
- claude_mpm/services/agents/deployment/processors/agent_processor.py +269 -0
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +311 -0
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +862 -0
- claude_mpm/services/agents/deployment/results/__init__.py +13 -0
- claude_mpm/services/agents/deployment/results/deployment_metrics.py +200 -0
- claude_mpm/services/agents/deployment/results/deployment_result_builder.py +249 -0
- claude_mpm/services/agents/deployment/single_agent_deployer.py +315 -0
- claude_mpm/services/agents/deployment/strategies/__init__.py +25 -0
- claude_mpm/services/agents/deployment/strategies/base_strategy.py +113 -0
- claude_mpm/services/agents/deployment/strategies/project_strategy.py +148 -0
- claude_mpm/services/agents/deployment/strategies/strategy_selector.py +117 -0
- claude_mpm/services/agents/deployment/strategies/system_strategy.py +131 -0
- claude_mpm/services/agents/deployment/strategies/user_strategy.py +130 -0
- claude_mpm/services/agents/deployment/system_instructions_deployer.py +228 -0
- claude_mpm/services/agents/deployment/validation/__init__.py +21 -0
- claude_mpm/services/agents/deployment/validation/agent_validator.py +323 -0
- claude_mpm/services/agents/deployment/validation/deployment_validator.py +238 -0
- claude_mpm/services/agents/deployment/validation/template_validator.py +319 -0
- claude_mpm/services/agents/deployment/validation/validation_result.py +214 -0
- claude_mpm/services/agents/git_source_manager.py +682 -0
- claude_mpm/services/agents/loading/__init__.py +11 -0
- claude_mpm/services/{agent_profile_loader.py → agents/loading/agent_profile_loader.py} +306 -228
- claude_mpm/services/{base_agent_manager.py → agents/loading/base_agent_manager.py} +106 -91
- claude_mpm/services/agents/loading/framework_agent_loader.py +433 -0
- claude_mpm/services/agents/local_template_manager.py +784 -0
- claude_mpm/services/agents/management/__init__.py +9 -0
- claude_mpm/services/{agent_capabilities_generator.py → agents/management/agent_capabilities_generator.py} +92 -69
- claude_mpm/services/{agent_management_service.py → agents/management/agent_management_service.py} +219 -168
- claude_mpm/services/agents/memory/__init__.py +22 -0
- claude_mpm/services/agents/memory/agent_memory_manager.py +784 -0
- claude_mpm/services/{agent_persistence_service.py → agents/memory/agent_persistence_service.py} +20 -18
- claude_mpm/services/agents/memory/content_manager.py +470 -0
- claude_mpm/services/agents/memory/memory_categorization_service.py +167 -0
- claude_mpm/services/agents/memory/memory_file_service.py +129 -0
- claude_mpm/services/agents/memory/memory_format_service.py +201 -0
- claude_mpm/services/agents/memory/memory_limits_service.py +101 -0
- claude_mpm/services/agents/memory/template_generator.py +83 -0
- claude_mpm/services/agents/observers.py +547 -0
- claude_mpm/services/agents/recommender.py +617 -0
- claude_mpm/services/agents/registry/__init__.py +30 -0
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +273 -0
- claude_mpm/services/{agent_modification_tracker.py → agents/registry/modification_tracker.py} +370 -295
- claude_mpm/services/agents/single_tier_deployment_service.py +696 -0
- claude_mpm/services/agents/sources/__init__.py +13 -0
- claude_mpm/services/agents/sources/agent_sync_state.py +516 -0
- claude_mpm/services/agents/sources/git_source_sync_service.py +1202 -0
- claude_mpm/services/agents/startup_sync.py +259 -0
- claude_mpm/services/agents/toolchain_detector.py +478 -0
- claude_mpm/services/analysis/__init__.py +35 -0
- claude_mpm/services/analysis/clone_detector.py +1030 -0
- claude_mpm/services/analysis/postmortem_reporter.py +474 -0
- claude_mpm/services/analysis/postmortem_service.py +765 -0
- claude_mpm/services/async_session_logger.py +665 -0
- claude_mpm/services/claude_session_logger.py +321 -0
- claude_mpm/services/cli/__init__.py +18 -0
- claude_mpm/services/cli/agent_cleanup_service.py +408 -0
- claude_mpm/services/cli/agent_dependency_service.py +395 -0
- claude_mpm/services/cli/agent_listing_service.py +463 -0
- claude_mpm/services/cli/agent_output_formatter.py +605 -0
- claude_mpm/services/cli/agent_validation_service.py +590 -0
- claude_mpm/services/cli/memory_crud_service.py +622 -0
- claude_mpm/services/cli/memory_output_formatter.py +604 -0
- claude_mpm/services/cli/resume_service.py +617 -0
- claude_mpm/services/cli/session_manager.py +604 -0
- claude_mpm/services/cli/session_pause_manager.py +504 -0
- claude_mpm/services/cli/session_resume_helper.py +372 -0
- claude_mpm/services/cli/startup_checker.py +362 -0
- claude_mpm/services/cli/unified_dashboard_manager.py +439 -0
- claude_mpm/services/command_deployment_service.py +446 -0
- claude_mpm/services/command_handler_service.py +221 -0
- claude_mpm/services/communication/__init__.py +22 -0
- claude_mpm/services/core/__init__.py +108 -0
- claude_mpm/services/core/base.py +269 -0
- claude_mpm/services/core/cache_manager.py +309 -0
- claude_mpm/services/core/interfaces/__init__.py +273 -0
- claude_mpm/services/core/interfaces/agent.py +514 -0
- claude_mpm/services/core/interfaces/communication.py +316 -0
- claude_mpm/services/core/interfaces/health.py +169 -0
- claude_mpm/services/core/interfaces/infrastructure.py +357 -0
- claude_mpm/services/core/interfaces/model.py +281 -0
- claude_mpm/services/core/interfaces/process.py +372 -0
- claude_mpm/services/core/interfaces/project.py +121 -0
- claude_mpm/services/core/interfaces/restart.py +307 -0
- claude_mpm/services/core/interfaces/service.py +405 -0
- claude_mpm/services/core/interfaces/stability.py +260 -0
- claude_mpm/services/core/interfaces.py +81 -0
- claude_mpm/services/core/memory_manager.py +682 -0
- claude_mpm/services/core/models/__init__.py +70 -0
- claude_mpm/services/core/models/agent_config.py +384 -0
- claude_mpm/services/core/models/health.py +162 -0
- claude_mpm/services/core/models/process.py +239 -0
- claude_mpm/services/core/models/restart.py +302 -0
- claude_mpm/services/core/models/stability.py +264 -0
- claude_mpm/services/core/models/toolchain.py +306 -0
- claude_mpm/services/core/path_resolver.py +517 -0
- claude_mpm/services/core/service_container.py +520 -0
- claude_mpm/services/core/service_interfaces.py +436 -0
- claude_mpm/services/diagnostics/__init__.py +18 -0
- claude_mpm/services/diagnostics/checks/__init__.py +38 -0
- claude_mpm/services/diagnostics/checks/agent_check.py +370 -0
- claude_mpm/services/diagnostics/checks/agent_sources_check.py +577 -0
- claude_mpm/services/diagnostics/checks/base_check.py +60 -0
- claude_mpm/services/diagnostics/checks/claude_code_check.py +270 -0
- claude_mpm/services/diagnostics/checks/common_issues_check.py +363 -0
- claude_mpm/services/diagnostics/checks/configuration_check.py +306 -0
- claude_mpm/services/diagnostics/checks/filesystem_check.py +233 -0
- claude_mpm/services/diagnostics/checks/installation_check.py +520 -0
- claude_mpm/services/diagnostics/checks/instructions_check.py +415 -0
- claude_mpm/services/diagnostics/checks/mcp_check.py +330 -0
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +1058 -0
- claude_mpm/services/diagnostics/checks/monitor_check.py +281 -0
- claude_mpm/services/diagnostics/checks/skill_sources_check.py +587 -0
- claude_mpm/services/diagnostics/checks/startup_log_check.py +319 -0
- claude_mpm/services/diagnostics/diagnostic_runner.py +286 -0
- claude_mpm/services/diagnostics/doctor_reporter.py +578 -0
- claude_mpm/services/diagnostics/models.py +138 -0
- claude_mpm/services/event_aggregator.py +582 -0
- claude_mpm/services/event_bus/__init__.py +18 -0
- claude_mpm/services/event_bus/config.py +186 -0
- claude_mpm/services/event_bus/direct_relay.py +312 -0
- claude_mpm/services/event_bus/event_bus.py +396 -0
- claude_mpm/services/event_bus/relay.py +326 -0
- claude_mpm/services/events/__init__.py +44 -0
- claude_mpm/services/events/consumers/__init__.py +18 -0
- claude_mpm/services/events/consumers/dead_letter.py +306 -0
- claude_mpm/services/events/consumers/logging.py +184 -0
- claude_mpm/services/events/consumers/metrics.py +241 -0
- claude_mpm/services/events/consumers/socketio.py +377 -0
- claude_mpm/services/events/core.py +480 -0
- claude_mpm/services/events/interfaces.py +214 -0
- claude_mpm/services/events/producers/__init__.py +14 -0
- claude_mpm/services/events/producers/hook.py +269 -0
- claude_mpm/services/events/producers/system.py +329 -0
- claude_mpm/services/exceptions.py +433 -353
- claude_mpm/services/framework_claude_md_generator/__init__.py +81 -80
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +74 -67
- claude_mpm/services/framework_claude_md_generator/content_validator.py +66 -62
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +82 -60
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +36 -37
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +41 -40
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +15 -15
- claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +5 -4
- claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +6 -5
- claude_mpm/services/framework_claude_md_generator/section_generators/header.py +8 -7
- claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +5 -4
- claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +6 -5
- claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +9 -8
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +26 -30
- claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +6 -5
- claude_mpm/services/framework_claude_md_generator/section_manager.py +28 -27
- claude_mpm/services/framework_claude_md_generator/version_manager.py +31 -30
- claude_mpm/services/git/__init__.py +21 -0
- claude_mpm/services/git/git_operations_service.py +579 -0
- claude_mpm/services/github/__init__.py +21 -0
- claude_mpm/services/github/github_cli_service.py +397 -0
- claude_mpm/services/hook_installer_service.py +506 -0
- claude_mpm/services/hook_service.py +159 -111
- claude_mpm/services/infrastructure/__init__.py +52 -0
- claude_mpm/services/infrastructure/context_preservation.py +569 -0
- claude_mpm/services/infrastructure/daemon_manager.py +279 -0
- claude_mpm/services/infrastructure/logging.py +209 -0
- claude_mpm/services/infrastructure/monitoring/__init__.py +39 -0
- claude_mpm/services/infrastructure/monitoring/aggregator.py +432 -0
- claude_mpm/services/infrastructure/monitoring/base.py +122 -0
- claude_mpm/services/infrastructure/monitoring/legacy.py +203 -0
- claude_mpm/services/infrastructure/monitoring/network.py +219 -0
- claude_mpm/services/infrastructure/monitoring/process.py +343 -0
- claude_mpm/services/infrastructure/monitoring/resources.py +244 -0
- claude_mpm/services/infrastructure/monitoring/service.py +368 -0
- claude_mpm/services/infrastructure/monitoring.py +71 -0
- claude_mpm/services/infrastructure/resume_log_generator.py +439 -0
- claude_mpm/services/instructions/__init__.py +9 -0
- claude_mpm/services/instructions/instruction_cache_service.py +374 -0
- claude_mpm/services/local_ops/__init__.py +155 -0
- claude_mpm/services/local_ops/crash_detector.py +257 -0
- claude_mpm/services/local_ops/health_checks/__init__.py +26 -0
- claude_mpm/services/local_ops/health_checks/http_check.py +224 -0
- claude_mpm/services/local_ops/health_checks/process_check.py +236 -0
- claude_mpm/services/local_ops/health_checks/resource_check.py +255 -0
- claude_mpm/services/local_ops/health_manager.py +427 -0
- claude_mpm/services/local_ops/log_monitor.py +396 -0
- claude_mpm/services/local_ops/memory_leak_detector.py +294 -0
- claude_mpm/services/local_ops/process_manager.py +595 -0
- claude_mpm/services/local_ops/resource_monitor.py +331 -0
- claude_mpm/services/local_ops/restart_manager.py +401 -0
- claude_mpm/services/local_ops/restart_policy.py +387 -0
- claude_mpm/services/local_ops/state_manager.py +372 -0
- claude_mpm/services/local_ops/unified_manager.py +600 -0
- claude_mpm/services/mcp_config_manager.py +1542 -0
- claude_mpm/services/mcp_service_verifier.py +732 -0
- claude_mpm/services/memory/__init__.py +19 -0
- claude_mpm/services/{memory_builder.py → memory/builder.py} +465 -373
- claude_mpm/services/memory/cache/__init__.py +14 -0
- claude_mpm/services/{shared_prompt_cache.py → memory/cache/shared_prompt_cache.py} +237 -200
- claude_mpm/services/memory/cache/simple_cache.py +331 -0
- claude_mpm/services/memory/failure_tracker.py +578 -0
- claude_mpm/services/memory/indexed_memory.py +648 -0
- claude_mpm/services/{memory_optimizer.py → memory/optimizer.py} +272 -243
- claude_mpm/services/memory/router.py +951 -0
- claude_mpm/services/memory_hook_service.py +470 -0
- claude_mpm/services/model/__init__.py +147 -0
- claude_mpm/services/model/base_provider.py +365 -0
- claude_mpm/services/model/claude_provider.py +412 -0
- claude_mpm/services/model/model_router.py +452 -0
- claude_mpm/services/model/ollama_provider.py +415 -0
- claude_mpm/services/monitor/__init__.py +20 -0
- claude_mpm/services/monitor/daemon.py +698 -0
- claude_mpm/services/monitor/daemon_manager.py +1076 -0
- claude_mpm/services/monitor/event_emitter.py +350 -0
- claude_mpm/services/monitor/handlers/__init__.py +21 -0
- claude_mpm/services/monitor/handlers/code_analysis.py +332 -0
- claude_mpm/services/monitor/handlers/dashboard.py +299 -0
- claude_mpm/services/monitor/handlers/file.py +264 -0
- claude_mpm/services/monitor/handlers/hooks.py +512 -0
- claude_mpm/services/monitor/management/__init__.py +18 -0
- claude_mpm/services/monitor/management/health.py +124 -0
- claude_mpm/services/monitor/management/lifecycle.py +730 -0
- claude_mpm/services/monitor/server.py +1493 -0
- claude_mpm/services/monitor_build_service.py +349 -0
- claude_mpm/services/native_agent_converter.py +356 -0
- claude_mpm/services/orphan_detection.py +786 -0
- claude_mpm/services/pm_skills_deployer.py +707 -0
- claude_mpm/services/port_manager.py +597 -0
- claude_mpm/services/pr/__init__.py +14 -0
- claude_mpm/services/pr/pr_template_service.py +329 -0
- claude_mpm/services/profile_manager.py +337 -0
- claude_mpm/services/project/__init__.py +44 -0
- claude_mpm/services/{project_analyzer.py → project/analyzer.py} +541 -291
- claude_mpm/services/project/analyzer_v2.py +566 -0
- claude_mpm/services/project/architecture_analyzer.py +461 -0
- claude_mpm/services/project/archive_manager.py +1045 -0
- claude_mpm/services/project/dependency_analyzer.py +462 -0
- claude_mpm/services/project/detection_strategies.py +719 -0
- claude_mpm/services/project/documentation_manager.py +554 -0
- claude_mpm/services/project/enhanced_analyzer.py +572 -0
- claude_mpm/services/project/language_analyzer.py +265 -0
- claude_mpm/services/project/metrics_collector.py +407 -0
- claude_mpm/services/project/project_organizer.py +1009 -0
- claude_mpm/services/project/registry.py +636 -0
- claude_mpm/services/project/toolchain_analyzer.py +583 -0
- claude_mpm/services/project_port_allocator.py +596 -0
- claude_mpm/services/recovery_manager.py +293 -240
- claude_mpm/services/response_tracker.py +267 -0
- claude_mpm/services/runner_configuration_service.py +605 -0
- claude_mpm/services/self_upgrade_service.py +608 -0
- claude_mpm/services/session_management_service.py +314 -0
- claude_mpm/services/session_manager.py +380 -0
- claude_mpm/services/shared/__init__.py +21 -0
- claude_mpm/services/shared/async_service_base.py +216 -0
- claude_mpm/services/shared/config_service_base.py +301 -0
- claude_mpm/services/shared/lifecycle_service_base.py +308 -0
- claude_mpm/services/shared/manager_base.py +315 -0
- claude_mpm/services/shared/service_factory.py +309 -0
- claude_mpm/services/skills/__init__.py +21 -0
- claude_mpm/services/skills/git_skill_source_manager.py +1324 -0
- claude_mpm/services/skills/selective_skill_deployer.py +744 -0
- claude_mpm/services/skills/skill_discovery_service.py +568 -0
- claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
- claude_mpm/services/skills_config.py +547 -0
- claude_mpm/services/skills_deployer.py +1168 -0
- claude_mpm/services/socketio/__init__.py +25 -0
- claude_mpm/services/socketio/client_proxy.py +229 -0
- claude_mpm/services/socketio/dashboard_server.py +362 -0
- claude_mpm/services/socketio/event_normalizer.py +798 -0
- claude_mpm/services/socketio/handlers/__init__.py +30 -0
- claude_mpm/services/socketio/handlers/base.py +136 -0
- claude_mpm/services/socketio/handlers/code_analysis.py +682 -0
- claude_mpm/services/socketio/handlers/connection.py +643 -0
- claude_mpm/services/socketio/handlers/connection_handler.py +333 -0
- claude_mpm/services/socketio/handlers/file.py +263 -0
- claude_mpm/services/socketio/handlers/git.py +962 -0
- claude_mpm/services/socketio/handlers/hook.py +211 -0
- claude_mpm/services/socketio/handlers/memory.py +26 -0
- claude_mpm/services/socketio/handlers/project.py +24 -0
- claude_mpm/services/socketio/handlers/registry.py +214 -0
- claude_mpm/services/socketio/migration_utils.py +343 -0
- claude_mpm/services/socketio/monitor_client.py +364 -0
- claude_mpm/services/socketio/server/__init__.py +18 -0
- claude_mpm/services/socketio/server/broadcaster.py +569 -0
- claude_mpm/services/socketio/server/connection_manager.py +579 -0
- claude_mpm/services/socketio/server/core.py +1079 -0
- claude_mpm/services/socketio/server/eventbus_integration.py +245 -0
- claude_mpm/services/socketio/server/main.py +501 -0
- claude_mpm/services/socketio_client_manager.py +173 -143
- claude_mpm/services/socketio_server.py +38 -1657
- claude_mpm/services/subprocess_launcher_service.py +322 -0
- claude_mpm/services/system_instructions_service.py +270 -0
- claude_mpm/services/ticket_manager.py +25 -209
- claude_mpm/services/ticket_services/__init__.py +26 -0
- claude_mpm/services/ticket_services/crud_service.py +328 -0
- claude_mpm/services/ticket_services/formatter_service.py +290 -0
- claude_mpm/services/ticket_services/search_service.py +324 -0
- claude_mpm/services/ticket_services/validation_service.py +303 -0
- claude_mpm/services/ticket_services/workflow_service.py +244 -0
- claude_mpm/services/unified/__init__.py +65 -0
- claude_mpm/services/unified/analyzer_strategies/__init__.py +44 -0
- claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +518 -0
- claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +680 -0
- claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +900 -0
- claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +745 -0
- claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +733 -0
- claude_mpm/services/unified/config_strategies/__init__.py +175 -0
- claude_mpm/services/unified/config_strategies/config_schema.py +731 -0
- claude_mpm/services/unified/config_strategies/context_strategy.py +747 -0
- claude_mpm/services/unified/config_strategies/error_handling_strategy.py +1005 -0
- claude_mpm/services/unified/config_strategies/file_loader_strategy.py +881 -0
- claude_mpm/services/unified/config_strategies/unified_config_service.py +823 -0
- claude_mpm/services/unified/config_strategies/validation_strategy.py +1148 -0
- claude_mpm/services/unified/deployment_strategies/__init__.py +97 -0
- claude_mpm/services/unified/deployment_strategies/base.py +553 -0
- claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +573 -0
- claude_mpm/services/unified/deployment_strategies/local.py +607 -0
- claude_mpm/services/unified/deployment_strategies/utils.py +667 -0
- claude_mpm/services/unified/deployment_strategies/vercel.py +471 -0
- claude_mpm/services/unified/interfaces.py +475 -0
- claude_mpm/services/unified/migration.py +509 -0
- claude_mpm/services/unified/strategies.py +534 -0
- claude_mpm/services/unified/unified_analyzer.py +542 -0
- claude_mpm/services/unified/unified_config.py +691 -0
- claude_mpm/services/unified/unified_deployment.py +466 -0
- claude_mpm/services/utility_service.py +280 -0
- claude_mpm/services/version_control/__init__.py +34 -37
- claude_mpm/services/version_control/branch_strategy.py +26 -17
- claude_mpm/services/version_control/conflict_resolution.py +52 -36
- claude_mpm/services/version_control/git_operations.py +183 -49
- claude_mpm/services/version_control/semantic_versioning.py +172 -61
- claude_mpm/services/version_control/version_parser.py +546 -0
- claude_mpm/services/version_service.py +379 -0
- claude_mpm/services/visualization/__init__.py +15 -0
- claude_mpm/services/visualization/mermaid_generator.py +937 -0
- claude_mpm/skills/__init__.py +42 -0
- claude_mpm/skills/agent_skills_injector.py +324 -0
- claude_mpm/skills/bundled/LICENSE_ATTRIBUTIONS.md +79 -0
- claude_mpm/skills/bundled/__init__.py +6 -0
- claude_mpm/skills/bundled/api-documentation.md +393 -0
- claude_mpm/skills/bundled/async-testing.md +571 -0
- claude_mpm/skills/bundled/code-review.md +143 -0
- claude_mpm/skills/bundled/database-migration.md +199 -0
- claude_mpm/skills/bundled/docker-containerization.md +194 -0
- claude_mpm/skills/bundled/express-local-dev.md +1429 -0
- claude_mpm/skills/bundled/fastapi-local-dev.md +1199 -0
- claude_mpm/skills/bundled/git-workflow.md +414 -0
- claude_mpm/skills/bundled/imagemagick.md +204 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/scripts/validate_env.py +576 -0
- claude_mpm/skills/bundled/json-data-handling.md +223 -0
- claude_mpm/skills/bundled/main/mcp-builder/scripts/connections.py +157 -0
- claude_mpm/skills/bundled/main/mcp-builder/scripts/evaluation.py +425 -0
- claude_mpm/skills/bundled/main/skill-creator/scripts/init_skill.py +303 -0
- claude_mpm/skills/bundled/main/skill-creator/scripts/package_skill.py +113 -0
- claude_mpm/skills/bundled/main/skill-creator/scripts/quick_validate.py +72 -0
- claude_mpm/skills/bundled/nextjs-local-dev.md +807 -0
- claude_mpm/skills/bundled/pdf.md +141 -0
- claude_mpm/skills/bundled/performance-profiling.md +573 -0
- claude_mpm/skills/bundled/refactoring-patterns.md +180 -0
- claude_mpm/skills/bundled/security-scanning.md +439 -0
- claude_mpm/skills/bundled/systematic-debugging.md +473 -0
- claude_mpm/skills/bundled/test-driven-development.md +378 -0
- claude_mpm/skills/bundled/testing/webapp-testing/examples/console_logging.py +35 -0
- claude_mpm/skills/bundled/testing/webapp-testing/examples/element_discovery.py +44 -0
- claude_mpm/skills/bundled/testing/webapp-testing/examples/static_html_automation.py +34 -0
- claude_mpm/skills/bundled/testing/webapp-testing/scripts/with_server.py +129 -0
- claude_mpm/skills/bundled/vite-local-dev.md +1061 -0
- claude_mpm/skills/bundled/web-performance-optimization.md +2305 -0
- claude_mpm/skills/bundled/xlsx.md +157 -0
- claude_mpm/skills/registry.py +286 -0
- claude_mpm/skills/skill_manager.py +405 -0
- claude_mpm/skills/skills_registry.py +347 -0
- claude_mpm/skills/skills_service.py +739 -0
- claude_mpm/storage/__init__.py +9 -0
- claude_mpm/storage/state_storage.py +546 -0
- claude_mpm/templates/.pre-commit-config.yaml +112 -0
- claude_mpm/templates/questions/__init__.py +38 -0
- claude_mpm/templates/questions/base.py +193 -0
- claude_mpm/templates/questions/pr_strategy.py +311 -0
- claude_mpm/templates/questions/project_init.py +385 -0
- claude_mpm/templates/questions/ticket_mgmt.py +394 -0
- claude_mpm/ticket_wrapper.py +2 -2
- claude_mpm/tools/__init__.py +10 -0
- claude_mpm/tools/__main__.py +208 -0
- claude_mpm/tools/code_tree_analyzer/__init__.py +45 -0
- claude_mpm/tools/code_tree_analyzer/analysis.py +299 -0
- claude_mpm/tools/code_tree_analyzer/cache.py +131 -0
- claude_mpm/tools/code_tree_analyzer/core.py +380 -0
- claude_mpm/tools/code_tree_analyzer/discovery.py +403 -0
- claude_mpm/tools/code_tree_analyzer/events.py +168 -0
- claude_mpm/tools/code_tree_analyzer/gitignore.py +308 -0
- claude_mpm/tools/code_tree_analyzer/models.py +39 -0
- claude_mpm/tools/code_tree_analyzer/multilang_analyzer.py +224 -0
- claude_mpm/tools/code_tree_analyzer/python_analyzer.py +284 -0
- claude_mpm/tools/code_tree_builder.py +631 -0
- claude_mpm/tools/code_tree_events.py +420 -0
- claude_mpm/tools/socketio_debug.py +671 -0
- claude_mpm/utils/__init__.py +8 -8
- claude_mpm/utils/agent_dependency_loader.py +1090 -0
- claude_mpm/utils/agent_filters.py +261 -0
- claude_mpm/utils/common.py +544 -0
- claude_mpm/utils/config_manager.py +168 -126
- claude_mpm/utils/console.py +11 -0
- claude_mpm/utils/database_connector.py +298 -0
- claude_mpm/utils/dependency_cache.py +373 -0
- claude_mpm/utils/dependency_manager.py +60 -59
- claude_mpm/utils/dependency_strategies.py +381 -0
- claude_mpm/utils/display_helper.py +260 -0
- claude_mpm/utils/environment_context.py +313 -0
- claude_mpm/utils/error_handler.py +78 -66
- claude_mpm/utils/file_utils.py +305 -0
- claude_mpm/utils/framework_detection.py +12 -11
- claude_mpm/utils/git_analyzer.py +407 -0
- claude_mpm/utils/gitignore.py +244 -0
- claude_mpm/utils/import_migration_example.py +12 -60
- claude_mpm/utils/imports.py +48 -45
- claude_mpm/utils/log_cleanup.py +627 -0
- claude_mpm/utils/migration.py +372 -0
- claude_mpm/utils/path_operations.py +110 -104
- claude_mpm/utils/progress.py +387 -0
- claude_mpm/utils/robust_installer.py +823 -0
- claude_mpm/utils/session_logging.py +121 -0
- claude_mpm/utils/structured_questions.py +619 -0
- claude_mpm/utils/subprocess_utils.py +343 -0
- claude_mpm/validation/__init__.py +1 -1
- claude_mpm/validation/agent_validator.py +214 -108
- claude_mpm/validation/frontmatter_validator.py +252 -0
- claude_mpm-5.4.55.dist-info/METADATA +999 -0
- claude_mpm-5.4.55.dist-info/RECORD +868 -0
- {claude_mpm-3.4.10.dist-info → claude_mpm-5.4.55.dist-info}/entry_points.txt +1 -3
- claude_mpm-5.4.55.dist-info/licenses/LICENSE +94 -0
- claude_mpm-5.4.55.dist-info/licenses/LICENSE-FAQ.md +153 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -88
- claude_mpm/agents/INSTRUCTIONS.md +0 -352
- claude_mpm/agents/backups/INSTRUCTIONS.md +0 -352
- claude_mpm/agents/base_agent_loader.py +0 -529
- claude_mpm/agents/schema/agent_schema.json +0 -314
- claude_mpm/agents/templates/.claude-mpm/memories/README.md +0 -36
- claude_mpm/agents/templates/backup/data_engineer_agent_20250726_234551.json +0 -46
- claude_mpm/agents/templates/backup/documentation_agent_20250726_234551.json +0 -45
- claude_mpm/agents/templates/backup/engineer_agent_20250726_234551.json +0 -49
- claude_mpm/agents/templates/backup/ops_agent_20250726_234551.json +0 -46
- claude_mpm/agents/templates/backup/qa_agent_20250726_234551.json +0 -45
- claude_mpm/agents/templates/backup/research_agent_20250726_234551.json +0 -49
- claude_mpm/agents/templates/backup/security_agent_20250726_234551.json +0 -46
- claude_mpm/agents/templates/backup/version_control_agent_20250726_234551.json +0 -46
- claude_mpm/agents/templates/data_engineer.json +0 -110
- claude_mpm/agents/templates/documentation.json +0 -109
- claude_mpm/agents/templates/engineer.json +0 -113
- claude_mpm/agents/templates/ops.json +0 -109
- claude_mpm/agents/templates/pm.json +0 -25
- claude_mpm/agents/templates/qa.json +0 -111
- claude_mpm/agents/templates/research.json +0 -65
- claude_mpm/agents/templates/security.json +0 -113
- claude_mpm/agents/templates/test_integration.json +0 -112
- claude_mpm/agents/templates/version_control.json +0 -107
- claude_mpm/cli/commands/ui.py +0 -57
- claude_mpm/core/simple_runner.py +0 -1046
- claude_mpm/dashboard/open_dashboard.py +0 -34
- claude_mpm/deployment_paths.py +0 -261
- claude_mpm/hooks/builtin/__init__.py +0 -1
- claude_mpm/hooks/builtin/logging_hook_example.py +0 -165
- claude_mpm/hooks/builtin/memory_hooks_example.py +0 -67
- claude_mpm/hooks/builtin/mpm_command_hook.py +0 -125
- claude_mpm/hooks/builtin/post_delegation_hook_example.py +0 -124
- claude_mpm/hooks/builtin/pre_delegation_hook_example.py +0 -125
- claude_mpm/hooks/builtin/submit_hook_example.py +0 -100
- claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +0 -237
- claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +0 -240
- claude_mpm/hooks/builtin/workflow_start_hook.py +0 -181
- claude_mpm/orchestration/__init__.py +0 -6
- claude_mpm/orchestration/archive/direct_orchestrator.py +0 -195
- claude_mpm/orchestration/archive/factory.py +0 -215
- claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +0 -188
- claude_mpm/orchestration/archive/hook_integration_example.py +0 -178
- claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +0 -826
- claude_mpm/orchestration/archive/orchestrator.py +0 -501
- claude_mpm/orchestration/archive/pexpect_orchestrator.py +0 -252
- claude_mpm/orchestration/archive/pty_orchestrator.py +0 -270
- claude_mpm/orchestration/archive/simple_orchestrator.py +0 -82
- claude_mpm/orchestration/archive/subprocess_orchestrator.py +0 -801
- claude_mpm/orchestration/archive/system_prompt_orchestrator.py +0 -278
- claude_mpm/orchestration/archive/wrapper_orchestrator.py +0 -187
- claude_mpm/schemas/workflow_validator.py +0 -411
- claude_mpm/services/agent_deployment.py +0 -1534
- claude_mpm/services/agent_lifecycle_manager.py +0 -1169
- claude_mpm/services/agent_memory_manager.py +0 -1415
- claude_mpm/services/agent_registry.py +0 -676
- claude_mpm/services/deployed_agent_discovery.py +0 -226
- claude_mpm/services/framework_agent_loader.py +0 -337
- claude_mpm/services/framework_claude_md_generator.py +0 -621
- claude_mpm/services/health_monitor.py +0 -892
- claude_mpm/services/memory_router.py +0 -538
- claude_mpm/services/parent_directory_manager/__init__.py +0 -577
- claude_mpm/services/parent_directory_manager/backup_manager.py +0 -258
- claude_mpm/services/parent_directory_manager/config_manager.py +0 -210
- claude_mpm/services/parent_directory_manager/deduplication_manager.py +0 -279
- claude_mpm/services/parent_directory_manager/framework_protector.py +0 -143
- claude_mpm/services/parent_directory_manager/operations.py +0 -186
- claude_mpm/services/parent_directory_manager/state_manager.py +0 -624
- claude_mpm/services/parent_directory_manager/template_deployer.py +0 -579
- claude_mpm/services/parent_directory_manager/validation_manager.py +0 -378
- claude_mpm/services/parent_directory_manager/version_control_helper.py +0 -339
- claude_mpm/services/parent_directory_manager/version_manager.py +0 -222
- claude_mpm/services/standalone_socketio_server.py +0 -1300
- claude_mpm/services/ticket_manager_di.py +0 -318
- claude_mpm/services/ticketing_service_original.py +0 -508
- claude_mpm/ui/__init__.py +0 -1
- claude_mpm/ui/rich_terminal_ui.py +0 -295
- claude_mpm/ui/terminal_ui.py +0 -328
- claude_mpm/utils/paths.py +0 -289
- claude_mpm-3.4.10.dist-info/METADATA +0 -183
- claude_mpm-3.4.10.dist-info/RECORD +0 -201
- claude_mpm-3.4.10.dist-info/licenses/LICENSE +0 -21
- {claude_mpm-3.4.10.dist-info → claude_mpm-5.4.55.dist-info}/WHEEL +0 -0
- {claude_mpm-3.4.10.dist-info → claude_mpm-5.4.55.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1493 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unified Monitor Server for Claude MPM
|
|
3
|
+
====================================
|
|
4
|
+
|
|
5
|
+
WHY: This server combines HTTP dashboard serving and Socket.IO event handling
|
|
6
|
+
into a single, stable process. It uses real AST analysis instead of mock data
|
|
7
|
+
and provides all monitoring functionality on a single port.
|
|
8
|
+
|
|
9
|
+
DESIGN DECISIONS:
|
|
10
|
+
- Combines aiohttp HTTP server with Socket.IO server
|
|
11
|
+
- Uses real CodeTreeAnalyzer for AST analysis
|
|
12
|
+
- Single port (8765) for all functionality
|
|
13
|
+
- Event-driven architecture with proper handler registration
|
|
14
|
+
- Built for stability and daemon operation
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import asyncio
|
|
18
|
+
import os
|
|
19
|
+
import threading
|
|
20
|
+
import time
|
|
21
|
+
from datetime import datetime, timezone
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Dict, Optional
|
|
24
|
+
|
|
25
|
+
import socketio
|
|
26
|
+
from aiohttp import web
|
|
27
|
+
from watchdog.events import FileSystemEventHandler
|
|
28
|
+
from watchdog.observers import Observer
|
|
29
|
+
|
|
30
|
+
from ...core.enums import ServiceState
|
|
31
|
+
from ...core.logging_config import get_logger
|
|
32
|
+
from .event_emitter import get_event_emitter
|
|
33
|
+
from .handlers.code_analysis import CodeAnalysisHandler
|
|
34
|
+
from .handlers.dashboard import DashboardHandler
|
|
35
|
+
from .handlers.file import FileHandler
|
|
36
|
+
from .handlers.hooks import HookHandler
|
|
37
|
+
|
|
38
|
+
# EventBus integration
|
|
39
|
+
try:
|
|
40
|
+
from ...services.event_bus import EventBus
|
|
41
|
+
|
|
42
|
+
EVENTBUS_AVAILABLE = True
|
|
43
|
+
except ImportError:
|
|
44
|
+
EventBus = None
|
|
45
|
+
EVENTBUS_AVAILABLE = False
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class SvelteBuildWatcher(FileSystemEventHandler):
|
|
49
|
+
"""File watcher for Svelte build directory changes.
|
|
50
|
+
|
|
51
|
+
Watches for file changes in svelte-build directory and triggers
|
|
52
|
+
hot reload via Socket.IO event emission.
|
|
53
|
+
|
|
54
|
+
STABILITY FIX: Added thread lock and stop() method to prevent timer leaks.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self, sio: socketio.AsyncServer, loop: asyncio.AbstractEventLoop, logger
|
|
59
|
+
):
|
|
60
|
+
"""Initialize the file watcher.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
sio: Socket.IO server instance for emitting events
|
|
64
|
+
loop: Event loop for async operations
|
|
65
|
+
logger: Logger instance
|
|
66
|
+
"""
|
|
67
|
+
super().__init__()
|
|
68
|
+
self.sio = sio
|
|
69
|
+
self.loop = loop
|
|
70
|
+
self.logger = logger
|
|
71
|
+
self.debounce_timer = None
|
|
72
|
+
self.debounce_delay = 0.5 # Wait 500ms after last change
|
|
73
|
+
self._timer_lock = threading.Lock() # STABILITY FIX: Prevent race condition
|
|
74
|
+
|
|
75
|
+
def stop(self):
|
|
76
|
+
"""Stop the watcher and cancel any pending timers.
|
|
77
|
+
|
|
78
|
+
STABILITY FIX: Ensures timer is cancelled on shutdown.
|
|
79
|
+
"""
|
|
80
|
+
with self._timer_lock:
|
|
81
|
+
if self.debounce_timer:
|
|
82
|
+
self.debounce_timer.cancel()
|
|
83
|
+
self.debounce_timer = None
|
|
84
|
+
|
|
85
|
+
def on_any_event(self, event):
|
|
86
|
+
"""Handle any file system event.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
event: File system event from watchdog
|
|
90
|
+
"""
|
|
91
|
+
# Ignore directory events and temporary files
|
|
92
|
+
if event.is_directory or event.src_path.endswith((".tmp", ".swp", "~")):
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
self.logger.debug(
|
|
96
|
+
f"File change detected: {event.event_type} - {event.src_path}"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# STABILITY FIX: Use lock to prevent timer race condition
|
|
100
|
+
with self._timer_lock:
|
|
101
|
+
# Cancel existing timer
|
|
102
|
+
if self.debounce_timer:
|
|
103
|
+
self.debounce_timer.cancel()
|
|
104
|
+
|
|
105
|
+
# Schedule reload after debounce delay
|
|
106
|
+
self.debounce_timer = threading.Timer(
|
|
107
|
+
self.debounce_delay, self._trigger_reload
|
|
108
|
+
)
|
|
109
|
+
self.debounce_timer.start()
|
|
110
|
+
|
|
111
|
+
def _trigger_reload(self):
|
|
112
|
+
"""Trigger hot reload by emitting Socket.IO event."""
|
|
113
|
+
try:
|
|
114
|
+
# Schedule the async emit in the event loop
|
|
115
|
+
asyncio.run_coroutine_threadsafe(self._emit_reload_event(), self.loop)
|
|
116
|
+
self.logger.info("Hot reload triggered - Svelte build changed")
|
|
117
|
+
except Exception as e:
|
|
118
|
+
self.logger.error(f"Error triggering reload: {e}")
|
|
119
|
+
|
|
120
|
+
async def _emit_reload_event(self):
|
|
121
|
+
"""Emit the reload event to all connected clients."""
|
|
122
|
+
if self.sio:
|
|
123
|
+
await self.sio.emit(
|
|
124
|
+
"reload",
|
|
125
|
+
{
|
|
126
|
+
"type": "reload",
|
|
127
|
+
"timestamp": datetime.now(timezone.utc).isoformat() + "Z",
|
|
128
|
+
"reason": "svelte-build-updated",
|
|
129
|
+
},
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class UnifiedMonitorServer:
|
|
134
|
+
"""Unified server that combines HTTP dashboard and Socket.IO functionality.
|
|
135
|
+
|
|
136
|
+
WHY: Provides a single server process that handles all monitoring needs.
|
|
137
|
+
Replaces multiple competing server implementations with one stable solution.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
def __init__(
|
|
141
|
+
self, host: str = "localhost", port: int = 8765, enable_hot_reload: bool = False
|
|
142
|
+
):
|
|
143
|
+
"""Initialize the unified monitor server.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
host: Host to bind to
|
|
147
|
+
port: Port to bind to
|
|
148
|
+
enable_hot_reload: Enable file watching and hot reload for development
|
|
149
|
+
"""
|
|
150
|
+
self.host = host
|
|
151
|
+
self.port = port
|
|
152
|
+
self.enable_hot_reload = enable_hot_reload
|
|
153
|
+
self.logger = get_logger(__name__)
|
|
154
|
+
|
|
155
|
+
# Core components
|
|
156
|
+
self.app = None
|
|
157
|
+
self.sio = None
|
|
158
|
+
self.runner = None
|
|
159
|
+
self.site = None
|
|
160
|
+
|
|
161
|
+
# Event handlers
|
|
162
|
+
self.code_analysis_handler = None
|
|
163
|
+
self.dashboard_handler = None
|
|
164
|
+
self.file_handler = None
|
|
165
|
+
self.hook_handler = None
|
|
166
|
+
|
|
167
|
+
# High-performance event emitter
|
|
168
|
+
self.event_emitter = None
|
|
169
|
+
|
|
170
|
+
# File watching (optional for dev mode)
|
|
171
|
+
self.file_observer: Optional[Observer] = None
|
|
172
|
+
self.file_watcher: Optional[SvelteBuildWatcher] = None
|
|
173
|
+
|
|
174
|
+
# State
|
|
175
|
+
self.running = False
|
|
176
|
+
self.loop = None
|
|
177
|
+
self.server_thread = None
|
|
178
|
+
self.startup_error = None # Track startup errors
|
|
179
|
+
|
|
180
|
+
# Heartbeat tracking
|
|
181
|
+
self.heartbeat_task: Optional[asyncio.Task] = None
|
|
182
|
+
self.server_start_time = time.time()
|
|
183
|
+
self.heartbeat_count = 0
|
|
184
|
+
|
|
185
|
+
def start(self) -> bool:
|
|
186
|
+
"""Start the unified monitor server.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
True if started successfully, False otherwise
|
|
190
|
+
"""
|
|
191
|
+
try:
|
|
192
|
+
self.logger.info(
|
|
193
|
+
f"Starting unified monitor server on {self.host}:{self.port}"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# Start in a separate thread to avoid blocking
|
|
197
|
+
self.server_thread = threading.Thread(target=self._run_server, daemon=True)
|
|
198
|
+
self.server_thread.start()
|
|
199
|
+
|
|
200
|
+
# Wait for server to start
|
|
201
|
+
import time
|
|
202
|
+
|
|
203
|
+
for _ in range(50): # Wait up to 5 seconds
|
|
204
|
+
if self.running:
|
|
205
|
+
break
|
|
206
|
+
if self.startup_error:
|
|
207
|
+
# Server thread reported an error
|
|
208
|
+
self.logger.error(f"Server startup failed: {self.startup_error}")
|
|
209
|
+
return False
|
|
210
|
+
time.sleep(0.1)
|
|
211
|
+
|
|
212
|
+
if not self.running:
|
|
213
|
+
error_msg = (
|
|
214
|
+
self.startup_error or "Server failed to start within timeout"
|
|
215
|
+
)
|
|
216
|
+
self.logger.error(error_msg)
|
|
217
|
+
return False
|
|
218
|
+
|
|
219
|
+
self.logger.info("Unified monitor server started successfully")
|
|
220
|
+
return True
|
|
221
|
+
|
|
222
|
+
except Exception as e:
|
|
223
|
+
self.logger.error(f"Failed to start unified monitor server: {e}")
|
|
224
|
+
return False
|
|
225
|
+
|
|
226
|
+
def _run_server(self):
|
|
227
|
+
"""Run the server in its own event loop."""
|
|
228
|
+
loop = None
|
|
229
|
+
try:
|
|
230
|
+
# Create new event loop for this thread
|
|
231
|
+
loop = asyncio.new_event_loop()
|
|
232
|
+
asyncio.set_event_loop(loop)
|
|
233
|
+
self.loop = loop
|
|
234
|
+
|
|
235
|
+
# Run the async server
|
|
236
|
+
loop.run_until_complete(self._start_async_server())
|
|
237
|
+
|
|
238
|
+
except OSError as e:
|
|
239
|
+
# Specific handling for port binding errors
|
|
240
|
+
if "Address already in use" in str(e) or "[Errno 48]" in str(e):
|
|
241
|
+
self.logger.error(f"Port {self.port} is already in use: {e}")
|
|
242
|
+
self.startup_error = f"Port {self.port} is already in use"
|
|
243
|
+
else:
|
|
244
|
+
self.logger.error(f"OS error in server thread: {e}")
|
|
245
|
+
self.startup_error = str(e)
|
|
246
|
+
except Exception as e:
|
|
247
|
+
self.logger.error(f"Error in server thread: {e}")
|
|
248
|
+
self.startup_error = str(e)
|
|
249
|
+
finally:
|
|
250
|
+
# Always ensure loop cleanup happens
|
|
251
|
+
if loop is not None:
|
|
252
|
+
try:
|
|
253
|
+
# Cancel all pending tasks first
|
|
254
|
+
self._cancel_all_tasks(loop)
|
|
255
|
+
|
|
256
|
+
# Give tasks a moment to cancel gracefully
|
|
257
|
+
if not loop.is_closed():
|
|
258
|
+
try:
|
|
259
|
+
loop.run_until_complete(asyncio.sleep(0.1))
|
|
260
|
+
except RuntimeError:
|
|
261
|
+
# Loop might be stopped already, that's ok
|
|
262
|
+
pass
|
|
263
|
+
|
|
264
|
+
except Exception as e:
|
|
265
|
+
self.logger.debug(f"Error during task cancellation: {e}")
|
|
266
|
+
finally:
|
|
267
|
+
try:
|
|
268
|
+
# Clear the loop reference from the instance first
|
|
269
|
+
self.loop = None
|
|
270
|
+
|
|
271
|
+
# Stop the loop if it's still running
|
|
272
|
+
if loop.is_running():
|
|
273
|
+
loop.stop()
|
|
274
|
+
|
|
275
|
+
# CRITICAL: Wait a moment for the loop to stop
|
|
276
|
+
import time
|
|
277
|
+
|
|
278
|
+
time.sleep(0.1)
|
|
279
|
+
|
|
280
|
+
# STABILITY FIX: Give tasks more time to clean up before closing
|
|
281
|
+
time.sleep(0.5)
|
|
282
|
+
|
|
283
|
+
# Clear the event loop from the thread BEFORE closing
|
|
284
|
+
# This prevents other code from accidentally using it
|
|
285
|
+
asyncio.set_event_loop(None)
|
|
286
|
+
|
|
287
|
+
# Now close the loop - this is critical to prevent the kqueue error
|
|
288
|
+
if not loop.is_closed():
|
|
289
|
+
loop.close()
|
|
290
|
+
# Wait for the close to complete
|
|
291
|
+
time.sleep(0.05)
|
|
292
|
+
|
|
293
|
+
except Exception as e:
|
|
294
|
+
self.logger.debug(f"Error during event loop cleanup: {e}")
|
|
295
|
+
|
|
296
|
+
async def _start_async_server(self):
|
|
297
|
+
"""Start the async server components."""
|
|
298
|
+
try:
|
|
299
|
+
# Create Socket.IO server with proper ping configuration
|
|
300
|
+
self.sio = socketio.AsyncServer(
|
|
301
|
+
cors_allowed_origins="*",
|
|
302
|
+
logger=True, # Enable to see Socket.IO events and connection lifecycle
|
|
303
|
+
engineio_logger=True, # Enable to see Engine.IO protocol handshake details
|
|
304
|
+
ping_interval=30, # 30 seconds ping interval (matches client expectation)
|
|
305
|
+
ping_timeout=60, # 60 seconds ping timeout (generous for stability)
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
# Create aiohttp application
|
|
309
|
+
self.app = web.Application()
|
|
310
|
+
|
|
311
|
+
# Attach Socket.IO to the app
|
|
312
|
+
self.sio.attach(self.app)
|
|
313
|
+
|
|
314
|
+
# Setup event handlers
|
|
315
|
+
self._setup_event_handlers()
|
|
316
|
+
|
|
317
|
+
# Setup high-performance event emitter
|
|
318
|
+
await self._setup_event_emitter()
|
|
319
|
+
|
|
320
|
+
self.logger.info(
|
|
321
|
+
"Using high-performance async event architecture with direct calls"
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
# Start heartbeat task
|
|
325
|
+
self.heartbeat_task = asyncio.create_task(self._heartbeat_loop())
|
|
326
|
+
self.logger.info("Heartbeat task started (3-minute interval)")
|
|
327
|
+
|
|
328
|
+
# Setup file watching for hot reload (if enabled)
|
|
329
|
+
if self.enable_hot_reload:
|
|
330
|
+
self._setup_file_watcher()
|
|
331
|
+
|
|
332
|
+
# Setup HTTP routes
|
|
333
|
+
self._setup_http_routes()
|
|
334
|
+
|
|
335
|
+
# Create and start the server
|
|
336
|
+
self.runner = web.AppRunner(self.app)
|
|
337
|
+
await self.runner.setup()
|
|
338
|
+
|
|
339
|
+
try:
|
|
340
|
+
self.site = web.TCPSite(self.runner, self.host, self.port)
|
|
341
|
+
await self.site.start()
|
|
342
|
+
|
|
343
|
+
self.running = True
|
|
344
|
+
self.logger.info(f"Server running on http://{self.host}:{self.port}")
|
|
345
|
+
except OSError as e:
|
|
346
|
+
# Port binding error - make sure it's reported clearly
|
|
347
|
+
# Check for common port binding errors
|
|
348
|
+
if (
|
|
349
|
+
"Address already in use" in str(e)
|
|
350
|
+
or "[Errno 48]" in str(e)
|
|
351
|
+
or "[Errno 98]" in str(e)
|
|
352
|
+
):
|
|
353
|
+
error_msg = f"Port {self.port} is already in use. Another process may be using this port."
|
|
354
|
+
self.logger.error(error_msg)
|
|
355
|
+
self.startup_error = error_msg
|
|
356
|
+
raise OSError(error_msg) from e
|
|
357
|
+
self.logger.error(f"Failed to bind to {self.host}:{self.port}: {e}")
|
|
358
|
+
self.startup_error = str(e)
|
|
359
|
+
raise
|
|
360
|
+
|
|
361
|
+
# Keep the server running
|
|
362
|
+
while self.running:
|
|
363
|
+
await asyncio.sleep(1)
|
|
364
|
+
|
|
365
|
+
except Exception as e:
|
|
366
|
+
self.logger.error(f"Error starting async server: {e}")
|
|
367
|
+
raise
|
|
368
|
+
finally:
|
|
369
|
+
await self._cleanup_async()
|
|
370
|
+
|
|
371
|
+
def _setup_event_handlers(self):
|
|
372
|
+
"""Setup Socket.IO event handlers."""
|
|
373
|
+
try:
|
|
374
|
+
# Create event handlers
|
|
375
|
+
self.code_analysis_handler = CodeAnalysisHandler(self.sio)
|
|
376
|
+
self.dashboard_handler = DashboardHandler(self.sio)
|
|
377
|
+
self.file_handler = FileHandler(self.sio)
|
|
378
|
+
self.hook_handler = HookHandler(self.sio)
|
|
379
|
+
|
|
380
|
+
# Register handlers
|
|
381
|
+
self.code_analysis_handler.register()
|
|
382
|
+
self.dashboard_handler.register()
|
|
383
|
+
self.file_handler.register()
|
|
384
|
+
self.hook_handler.register()
|
|
385
|
+
|
|
386
|
+
self.logger.info("Event handlers registered successfully")
|
|
387
|
+
|
|
388
|
+
except Exception as e:
|
|
389
|
+
self.logger.error(f"Error setting up event handlers: {e}")
|
|
390
|
+
raise
|
|
391
|
+
|
|
392
|
+
async def _setup_event_emitter(self):
|
|
393
|
+
"""Setup high-performance event emitter."""
|
|
394
|
+
try:
|
|
395
|
+
# Get the global event emitter instance
|
|
396
|
+
self.event_emitter = await get_event_emitter()
|
|
397
|
+
|
|
398
|
+
# Register this Socket.IO server for direct event emission
|
|
399
|
+
self.event_emitter.register_socketio_server(self.sio)
|
|
400
|
+
|
|
401
|
+
self.logger.info("Event emitter setup complete - direct calls enabled")
|
|
402
|
+
|
|
403
|
+
except Exception as e:
|
|
404
|
+
self.logger.error(f"Error setting up event emitter: {e}")
|
|
405
|
+
raise
|
|
406
|
+
|
|
407
|
+
def _setup_file_watcher(self):
|
|
408
|
+
"""Setup file watcher for Svelte build directory.
|
|
409
|
+
|
|
410
|
+
Watches for changes in svelte-build and triggers hot reload.
|
|
411
|
+
Only enabled when enable_hot_reload is True.
|
|
412
|
+
"""
|
|
413
|
+
try:
|
|
414
|
+
dashboard_dir = Path(__file__).resolve().parent.parent.parent / "dashboard"
|
|
415
|
+
svelte_build_dir = dashboard_dir / "static" / "svelte-build"
|
|
416
|
+
|
|
417
|
+
if not svelte_build_dir.exists():
|
|
418
|
+
self.logger.warning(
|
|
419
|
+
f"Svelte build directory not found: {svelte_build_dir}. "
|
|
420
|
+
"Hot reload disabled."
|
|
421
|
+
)
|
|
422
|
+
return
|
|
423
|
+
|
|
424
|
+
# Create file watcher with Socket.IO reference
|
|
425
|
+
self.file_watcher = SvelteBuildWatcher(
|
|
426
|
+
sio=self.sio, loop=self.loop, logger=self.logger
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
# Create observer and schedule watching
|
|
430
|
+
self.file_observer = Observer()
|
|
431
|
+
self.file_observer.schedule(
|
|
432
|
+
self.file_watcher, str(svelte_build_dir), recursive=True
|
|
433
|
+
)
|
|
434
|
+
self.file_observer.start()
|
|
435
|
+
|
|
436
|
+
self.logger.info(f"🔥 Hot reload enabled - watching {svelte_build_dir}")
|
|
437
|
+
|
|
438
|
+
except Exception as e:
|
|
439
|
+
self.logger.error(f"Error setting up file watcher: {e}")
|
|
440
|
+
# Don't raise - hot reload is optional
|
|
441
|
+
|
|
442
|
+
def _setup_http_routes(self):
|
|
443
|
+
"""Setup HTTP routes for the dashboard."""
|
|
444
|
+
try:
|
|
445
|
+
# Dashboard static files - use .resolve() for absolute path
|
|
446
|
+
dashboard_dir = Path(__file__).resolve().parent.parent.parent / "dashboard"
|
|
447
|
+
static_dir = dashboard_dir / "static"
|
|
448
|
+
|
|
449
|
+
# Main dashboard route - serve Svelte dashboard
|
|
450
|
+
async def dashboard_index(request):
|
|
451
|
+
svelte_index = static_dir / "svelte-build" / "index.html"
|
|
452
|
+
if svelte_index.exists():
|
|
453
|
+
with svelte_index.open(encoding="utf-8") as f:
|
|
454
|
+
content = f.read()
|
|
455
|
+
return web.Response(text=content, content_type="text/html")
|
|
456
|
+
|
|
457
|
+
# Log error with path details for debugging
|
|
458
|
+
self.logger.error(
|
|
459
|
+
f"Dashboard index.html not found at: {svelte_index.resolve()}"
|
|
460
|
+
)
|
|
461
|
+
return web.Response(
|
|
462
|
+
text=f"Dashboard not found. Expected location: {svelte_index.resolve()}",
|
|
463
|
+
status=404,
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
# Health check
|
|
467
|
+
async def health_check(request):
|
|
468
|
+
# Get version from VERSION file
|
|
469
|
+
version = "1.0.0"
|
|
470
|
+
try:
|
|
471
|
+
version_file = (
|
|
472
|
+
Path(__file__).resolve().parent.parent.parent.parent.parent
|
|
473
|
+
/ "VERSION"
|
|
474
|
+
)
|
|
475
|
+
if version_file.exists():
|
|
476
|
+
version = version_file.read_text().strip()
|
|
477
|
+
except Exception:
|
|
478
|
+
pass
|
|
479
|
+
|
|
480
|
+
return web.json_response(
|
|
481
|
+
{
|
|
482
|
+
"status": ServiceState.RUNNING,
|
|
483
|
+
"service": "claude-mpm-monitor", # Important: must match what is_our_service() checks
|
|
484
|
+
"version": version,
|
|
485
|
+
"port": self.port,
|
|
486
|
+
"pid": os.getpid(),
|
|
487
|
+
"uptime": int(time.time() - self.server_start_time),
|
|
488
|
+
}
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
# Event ingestion endpoint for hook handlers
|
|
492
|
+
async def api_events_handler(request):
|
|
493
|
+
"""Handle HTTP POST events from hook handlers."""
|
|
494
|
+
try:
|
|
495
|
+
data = await request.json()
|
|
496
|
+
|
|
497
|
+
# Extract event data
|
|
498
|
+
data.get("namespace", "hook")
|
|
499
|
+
event = data.get("event", "claude_event")
|
|
500
|
+
event_data = data.get("data", {})
|
|
501
|
+
|
|
502
|
+
# Emit to Socket.IO clients via the appropriate event
|
|
503
|
+
if self.sio:
|
|
504
|
+
await self.sio.emit(event, event_data)
|
|
505
|
+
self.logger.debug(f"HTTP event forwarded to Socket.IO: {event}")
|
|
506
|
+
|
|
507
|
+
return web.Response(status=204) # No content response
|
|
508
|
+
|
|
509
|
+
except Exception as e:
|
|
510
|
+
self.logger.error(f"Error handling HTTP event: {e}")
|
|
511
|
+
return web.Response(text=f"Error: {e!s}", status=500)
|
|
512
|
+
|
|
513
|
+
# File content endpoint for file viewer
|
|
514
|
+
async def api_file_handler(request):
|
|
515
|
+
"""Handle file content requests."""
|
|
516
|
+
import json
|
|
517
|
+
|
|
518
|
+
try:
|
|
519
|
+
data = await request.json()
|
|
520
|
+
file_path = data.get("path", "")
|
|
521
|
+
|
|
522
|
+
# Security check: ensure path is absolute and exists
|
|
523
|
+
if not file_path or not Path(file_path).is_absolute():
|
|
524
|
+
return web.json_response(
|
|
525
|
+
{"success": False, "error": "Invalid file path"}, status=400
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
# Check if file exists and is readable
|
|
529
|
+
if not Path(file_path).exists():
|
|
530
|
+
return web.json_response(
|
|
531
|
+
{"success": False, "error": "File not found"}, status=404
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
if not Path(file_path).is_file():
|
|
535
|
+
return web.json_response(
|
|
536
|
+
{"success": False, "error": "Path is not a file"},
|
|
537
|
+
status=400,
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
# Read file content (with size limit for safety)
|
|
541
|
+
max_size = 10 * 1024 * 1024 # 10MB limit
|
|
542
|
+
file_size = Path(file_path).stat().st_size
|
|
543
|
+
|
|
544
|
+
if file_size > max_size:
|
|
545
|
+
return web.json_response(
|
|
546
|
+
{
|
|
547
|
+
"success": False,
|
|
548
|
+
"error": f"File too large (>{max_size} bytes)",
|
|
549
|
+
},
|
|
550
|
+
status=413,
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
try:
|
|
554
|
+
with Path(file_path).open(
|
|
555
|
+
encoding="utf-8",
|
|
556
|
+
) as f:
|
|
557
|
+
content = f.read()
|
|
558
|
+
lines = content.count("\n") + 1
|
|
559
|
+
except UnicodeDecodeError:
|
|
560
|
+
# Try reading as binary if UTF-8 fails
|
|
561
|
+
return web.json_response(
|
|
562
|
+
{"success": False, "error": "File is not a text file"},
|
|
563
|
+
status=415,
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
# Get file extension for type detection
|
|
567
|
+
file_ext = Path(file_path).suffix.lstrip(".")
|
|
568
|
+
|
|
569
|
+
return web.json_response(
|
|
570
|
+
{
|
|
571
|
+
"success": True,
|
|
572
|
+
"content": content,
|
|
573
|
+
"lines": lines,
|
|
574
|
+
"size": file_size,
|
|
575
|
+
"type": file_ext or "text",
|
|
576
|
+
}
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
except json.JSONDecodeError:
|
|
580
|
+
return web.json_response(
|
|
581
|
+
{"success": False, "error": "Invalid JSON in request"},
|
|
582
|
+
status=400,
|
|
583
|
+
)
|
|
584
|
+
except Exception as e:
|
|
585
|
+
self.logger.error(f"Error reading file: {e}")
|
|
586
|
+
return web.json_response(
|
|
587
|
+
{"success": False, "error": str(e)}, status=500
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
# File listing endpoint for file browser
|
|
591
|
+
async def api_files_handler(request):
|
|
592
|
+
"""List files in a directory for the file browser."""
|
|
593
|
+
try:
|
|
594
|
+
# Get path from query param, default to working directory
|
|
595
|
+
path = request.query.get("path", str(Path.cwd()))
|
|
596
|
+
dir_path = Path(path)
|
|
597
|
+
|
|
598
|
+
if not dir_path.exists():
|
|
599
|
+
return web.json_response(
|
|
600
|
+
{"success": False, "error": "Directory not found"},
|
|
601
|
+
status=404,
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
if not dir_path.is_dir():
|
|
605
|
+
return web.json_response(
|
|
606
|
+
{"success": False, "error": "Path is not a directory"},
|
|
607
|
+
status=400,
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
# Patterns to exclude
|
|
611
|
+
exclude_patterns = {
|
|
612
|
+
".git",
|
|
613
|
+
"node_modules",
|
|
614
|
+
"__pycache__",
|
|
615
|
+
".svelte-kit",
|
|
616
|
+
"venv",
|
|
617
|
+
".venv",
|
|
618
|
+
"dist",
|
|
619
|
+
"build",
|
|
620
|
+
".next",
|
|
621
|
+
".cache",
|
|
622
|
+
".pytest_cache",
|
|
623
|
+
".mypy_cache",
|
|
624
|
+
".ruff_cache",
|
|
625
|
+
"eggs",
|
|
626
|
+
"*.egg-info",
|
|
627
|
+
".tox",
|
|
628
|
+
".nox",
|
|
629
|
+
"htmlcov",
|
|
630
|
+
".coverage",
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
entries = []
|
|
634
|
+
try:
|
|
635
|
+
for entry in sorted(
|
|
636
|
+
dir_path.iterdir(),
|
|
637
|
+
key=lambda x: (not x.is_dir(), x.name.lower()),
|
|
638
|
+
):
|
|
639
|
+
# Skip hidden files and excluded patterns
|
|
640
|
+
if entry.name.startswith(".") and entry.name not in {
|
|
641
|
+
".env",
|
|
642
|
+
".gitignore",
|
|
643
|
+
}:
|
|
644
|
+
if entry.name in {".git", ".svelte-kit", ".cache"}:
|
|
645
|
+
continue
|
|
646
|
+
if entry.name in exclude_patterns:
|
|
647
|
+
continue
|
|
648
|
+
if any(
|
|
649
|
+
entry.name.endswith(p.replace("*", ""))
|
|
650
|
+
for p in exclude_patterns
|
|
651
|
+
if "*" in p
|
|
652
|
+
):
|
|
653
|
+
continue
|
|
654
|
+
|
|
655
|
+
try:
|
|
656
|
+
stat = entry.stat()
|
|
657
|
+
entries.append(
|
|
658
|
+
{
|
|
659
|
+
"name": entry.name,
|
|
660
|
+
"path": str(entry),
|
|
661
|
+
"type": "directory"
|
|
662
|
+
if entry.is_dir()
|
|
663
|
+
else "file",
|
|
664
|
+
"size": stat.st_size if entry.is_file() else 0,
|
|
665
|
+
"modified": stat.st_mtime,
|
|
666
|
+
"extension": entry.suffix.lstrip(".")
|
|
667
|
+
if entry.is_file()
|
|
668
|
+
else None,
|
|
669
|
+
}
|
|
670
|
+
)
|
|
671
|
+
except (PermissionError, OSError):
|
|
672
|
+
continue
|
|
673
|
+
|
|
674
|
+
except PermissionError:
|
|
675
|
+
return web.json_response(
|
|
676
|
+
{"success": False, "error": "Permission denied"},
|
|
677
|
+
status=403,
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
# Separate directories and files
|
|
681
|
+
directories = [e for e in entries if e["type"] == "directory"]
|
|
682
|
+
files = [e for e in entries if e["type"] == "file"]
|
|
683
|
+
|
|
684
|
+
return web.json_response(
|
|
685
|
+
{
|
|
686
|
+
"success": True,
|
|
687
|
+
"path": str(dir_path),
|
|
688
|
+
"directories": directories,
|
|
689
|
+
"files": files,
|
|
690
|
+
"total_directories": len(directories),
|
|
691
|
+
"total_files": len(files),
|
|
692
|
+
}
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
except Exception as e:
|
|
696
|
+
self.logger.error(f"Error listing directory: {e}")
|
|
697
|
+
return web.json_response(
|
|
698
|
+
{"success": False, "error": str(e)}, status=500
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
# File read endpoint (GET) for file browser
|
|
702
|
+
async def api_file_read_handler(request):
|
|
703
|
+
"""Read file content via GET request."""
|
|
704
|
+
import base64
|
|
705
|
+
|
|
706
|
+
try:
|
|
707
|
+
file_path = request.query.get("path", "")
|
|
708
|
+
|
|
709
|
+
if not file_path:
|
|
710
|
+
return web.json_response(
|
|
711
|
+
{"success": False, "error": "Path parameter required"},
|
|
712
|
+
status=400,
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
path = Path(file_path)
|
|
716
|
+
|
|
717
|
+
if not path.exists():
|
|
718
|
+
return web.json_response(
|
|
719
|
+
{"success": False, "error": "File not found"},
|
|
720
|
+
status=404,
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
if not path.is_file():
|
|
724
|
+
return web.json_response(
|
|
725
|
+
{"success": False, "error": "Path is not a file"},
|
|
726
|
+
status=400,
|
|
727
|
+
)
|
|
728
|
+
|
|
729
|
+
# Get file info
|
|
730
|
+
file_size = path.stat().st_size
|
|
731
|
+
file_ext = path.suffix.lstrip(".").lower()
|
|
732
|
+
|
|
733
|
+
# Define image extensions
|
|
734
|
+
image_extensions = {
|
|
735
|
+
"png",
|
|
736
|
+
"jpg",
|
|
737
|
+
"jpeg",
|
|
738
|
+
"gif",
|
|
739
|
+
"svg",
|
|
740
|
+
"webp",
|
|
741
|
+
"ico",
|
|
742
|
+
"bmp",
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
# Check if file is an image
|
|
746
|
+
if file_ext in image_extensions:
|
|
747
|
+
# Read as binary and encode to base64
|
|
748
|
+
try:
|
|
749
|
+
binary_content = path.read_bytes()
|
|
750
|
+
base64_content = base64.b64encode(binary_content).decode(
|
|
751
|
+
"utf-8"
|
|
752
|
+
)
|
|
753
|
+
|
|
754
|
+
# Map extension to MIME type
|
|
755
|
+
mime_types = {
|
|
756
|
+
"png": "image/png",
|
|
757
|
+
"jpg": "image/jpeg",
|
|
758
|
+
"jpeg": "image/jpeg",
|
|
759
|
+
"gif": "image/gif",
|
|
760
|
+
"svg": "image/svg+xml",
|
|
761
|
+
"webp": "image/webp",
|
|
762
|
+
"ico": "image/x-icon",
|
|
763
|
+
"bmp": "image/bmp",
|
|
764
|
+
}
|
|
765
|
+
mime_type = mime_types.get(file_ext, "image/png")
|
|
766
|
+
|
|
767
|
+
return web.json_response(
|
|
768
|
+
{
|
|
769
|
+
"success": True,
|
|
770
|
+
"path": str(path),
|
|
771
|
+
"content": base64_content,
|
|
772
|
+
"size": file_size,
|
|
773
|
+
"type": "image",
|
|
774
|
+
"mime": mime_type,
|
|
775
|
+
"extension": file_ext,
|
|
776
|
+
}
|
|
777
|
+
)
|
|
778
|
+
except Exception as e:
|
|
779
|
+
self.logger.error(f"Error reading image file: {e}")
|
|
780
|
+
return web.json_response(
|
|
781
|
+
{
|
|
782
|
+
"success": False,
|
|
783
|
+
"error": f"Failed to read image: {e!s}",
|
|
784
|
+
},
|
|
785
|
+
status=500,
|
|
786
|
+
)
|
|
787
|
+
|
|
788
|
+
# Read text file content
|
|
789
|
+
try:
|
|
790
|
+
content = path.read_text(encoding="utf-8")
|
|
791
|
+
lines = content.count("\n") + 1
|
|
792
|
+
except UnicodeDecodeError:
|
|
793
|
+
return web.json_response(
|
|
794
|
+
{"success": False, "error": "File is not a text file"},
|
|
795
|
+
status=415,
|
|
796
|
+
)
|
|
797
|
+
|
|
798
|
+
return web.json_response(
|
|
799
|
+
{
|
|
800
|
+
"success": True,
|
|
801
|
+
"path": str(path),
|
|
802
|
+
"content": content,
|
|
803
|
+
"lines": lines,
|
|
804
|
+
"size": file_size,
|
|
805
|
+
"type": file_ext or "text",
|
|
806
|
+
}
|
|
807
|
+
)
|
|
808
|
+
|
|
809
|
+
except Exception as e:
|
|
810
|
+
self.logger.error(f"Error reading file: {e}")
|
|
811
|
+
return web.json_response(
|
|
812
|
+
{"success": False, "error": str(e)}, status=500
|
|
813
|
+
)
|
|
814
|
+
|
|
815
|
+
# Favicon handler
|
|
816
|
+
async def favicon_handler(request):
|
|
817
|
+
"""Serve favicon.svg from static directory."""
|
|
818
|
+
from aiohttp.web_fileresponse import FileResponse
|
|
819
|
+
|
|
820
|
+
favicon_path = static_dir / "svelte-build" / "favicon.svg"
|
|
821
|
+
if favicon_path.exists():
|
|
822
|
+
return FileResponse(
|
|
823
|
+
favicon_path, headers={"Content-Type": "image/svg+xml"}
|
|
824
|
+
)
|
|
825
|
+
raise web.HTTPNotFound()
|
|
826
|
+
|
|
827
|
+
# Version endpoint for dashboard build tracker
|
|
828
|
+
async def version_handler(request):
|
|
829
|
+
"""Serve version information for dashboard build tracker."""
|
|
830
|
+
try:
|
|
831
|
+
# Try to get version from version service
|
|
832
|
+
from claude_mpm.services.version_service import VersionService
|
|
833
|
+
|
|
834
|
+
version_service = VersionService()
|
|
835
|
+
version_info = version_service.get_version_info()
|
|
836
|
+
|
|
837
|
+
return web.json_response(
|
|
838
|
+
{
|
|
839
|
+
"version": version_info.get("base_version", "1.0.0"),
|
|
840
|
+
"build": version_info.get("build_number", 1),
|
|
841
|
+
"formatted_build": f"{version_info.get('build_number', 1):04d}",
|
|
842
|
+
"full_version": version_info.get("version", "v1.0.0-0001"),
|
|
843
|
+
"service": "unified-monitor",
|
|
844
|
+
}
|
|
845
|
+
)
|
|
846
|
+
except Exception as e:
|
|
847
|
+
self.logger.warning(f"Error getting version info: {e}")
|
|
848
|
+
# Return default version info if service fails
|
|
849
|
+
return web.json_response(
|
|
850
|
+
{
|
|
851
|
+
"version": "1.0.0",
|
|
852
|
+
"build": 1,
|
|
853
|
+
"formatted_build": "0001",
|
|
854
|
+
"full_version": "v1.0.0-0001",
|
|
855
|
+
"service": "unified-monitor",
|
|
856
|
+
}
|
|
857
|
+
)
|
|
858
|
+
|
|
859
|
+
# Configuration endpoint for dashboard initialization
|
|
860
|
+
async def config_handler(request):
|
|
861
|
+
"""Return configuration for dashboard initialization."""
|
|
862
|
+
import subprocess
|
|
863
|
+
|
|
864
|
+
config = {
|
|
865
|
+
"workingDirectory": Path.cwd(),
|
|
866
|
+
"gitBranch": "Unknown",
|
|
867
|
+
"serverTime": datetime.now(timezone.utc).isoformat() + "Z",
|
|
868
|
+
"service": "unified-monitor",
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
# Try to get current git branch
|
|
872
|
+
try:
|
|
873
|
+
result = subprocess.run(
|
|
874
|
+
["git", "branch", "--show-current"],
|
|
875
|
+
capture_output=True,
|
|
876
|
+
text=True,
|
|
877
|
+
timeout=2,
|
|
878
|
+
cwd=Path.cwd(),
|
|
879
|
+
check=False,
|
|
880
|
+
)
|
|
881
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
882
|
+
config["gitBranch"] = result.stdout.strip()
|
|
883
|
+
except Exception:
|
|
884
|
+
pass # Keep default "Unknown" value
|
|
885
|
+
|
|
886
|
+
return web.json_response(config)
|
|
887
|
+
|
|
888
|
+
# Working directory endpoint
|
|
889
|
+
async def working_directory_handler(request):
|
|
890
|
+
"""Return the current working directory."""
|
|
891
|
+
return web.json_response(
|
|
892
|
+
{"working_directory": str(Path.cwd()), "success": True}
|
|
893
|
+
)
|
|
894
|
+
|
|
895
|
+
# Monitor page routes
|
|
896
|
+
async def monitor_page_handler(request):
|
|
897
|
+
"""Serve monitor HTML pages."""
|
|
898
|
+
page_name = request.match_info.get("page", "agents")
|
|
899
|
+
static_dir = dashboard_dir / "static"
|
|
900
|
+
file_path = static_dir / f"{page_name}.html"
|
|
901
|
+
|
|
902
|
+
if file_path.exists() and file_path.is_file():
|
|
903
|
+
with Path(file_path).open(
|
|
904
|
+
encoding="utf-8",
|
|
905
|
+
) as f:
|
|
906
|
+
content = f.read()
|
|
907
|
+
return web.Response(text=content, content_type="text/html")
|
|
908
|
+
return web.Response(text="Page not found", status=404)
|
|
909
|
+
|
|
910
|
+
# Git history handler
|
|
911
|
+
async def git_history_handler(request: web.Request) -> web.Response:
|
|
912
|
+
"""Get git history for a file."""
|
|
913
|
+
import subprocess
|
|
914
|
+
|
|
915
|
+
try:
|
|
916
|
+
data = await request.json()
|
|
917
|
+
file_path = data.get("path", "")
|
|
918
|
+
limit = data.get("limit", 10)
|
|
919
|
+
|
|
920
|
+
if not file_path:
|
|
921
|
+
return web.json_response(
|
|
922
|
+
{
|
|
923
|
+
"success": False,
|
|
924
|
+
"error": "No path provided",
|
|
925
|
+
"commits": [],
|
|
926
|
+
},
|
|
927
|
+
status=400,
|
|
928
|
+
)
|
|
929
|
+
|
|
930
|
+
path = Path(file_path)
|
|
931
|
+
if not path.exists():
|
|
932
|
+
return web.json_response(
|
|
933
|
+
{
|
|
934
|
+
"success": False,
|
|
935
|
+
"error": "File not found",
|
|
936
|
+
"commits": [],
|
|
937
|
+
},
|
|
938
|
+
status=404,
|
|
939
|
+
)
|
|
940
|
+
|
|
941
|
+
# Get git log for file
|
|
942
|
+
result = subprocess.run(
|
|
943
|
+
[
|
|
944
|
+
"git",
|
|
945
|
+
"log",
|
|
946
|
+
f"-{limit}",
|
|
947
|
+
"--pretty=format:%H|%an|%ar|%s",
|
|
948
|
+
"--",
|
|
949
|
+
str(path),
|
|
950
|
+
],
|
|
951
|
+
check=False,
|
|
952
|
+
capture_output=True,
|
|
953
|
+
text=True,
|
|
954
|
+
cwd=str(path.parent),
|
|
955
|
+
)
|
|
956
|
+
|
|
957
|
+
commits = []
|
|
958
|
+
if result.returncode == 0 and result.stdout:
|
|
959
|
+
for line in result.stdout.strip().split("\n"):
|
|
960
|
+
if line:
|
|
961
|
+
parts = line.split("|", 3)
|
|
962
|
+
if len(parts) == 4:
|
|
963
|
+
commits.append(
|
|
964
|
+
{
|
|
965
|
+
"hash": parts[0][:7],
|
|
966
|
+
"author": parts[1],
|
|
967
|
+
"date": parts[2],
|
|
968
|
+
"message": parts[3],
|
|
969
|
+
}
|
|
970
|
+
)
|
|
971
|
+
|
|
972
|
+
return web.json_response({"success": True, "commits": commits})
|
|
973
|
+
except Exception as e:
|
|
974
|
+
return web.json_response(
|
|
975
|
+
{"success": False, "error": str(e), "commits": []}, status=500
|
|
976
|
+
)
|
|
977
|
+
|
|
978
|
+
# Git diff handler
|
|
979
|
+
async def git_diff_handler(request: web.Request) -> web.Response:
|
|
980
|
+
"""Get git diff for a file with optional commit selection."""
|
|
981
|
+
import subprocess
|
|
982
|
+
|
|
983
|
+
try:
|
|
984
|
+
file_path = request.query.get("path", "")
|
|
985
|
+
commit_hash = request.query.get(
|
|
986
|
+
"commit", ""
|
|
987
|
+
) # Optional commit hash
|
|
988
|
+
|
|
989
|
+
if not file_path:
|
|
990
|
+
return web.json_response(
|
|
991
|
+
{
|
|
992
|
+
"success": False,
|
|
993
|
+
"error": "No path provided",
|
|
994
|
+
"diff": "",
|
|
995
|
+
"has_changes": False,
|
|
996
|
+
},
|
|
997
|
+
status=400,
|
|
998
|
+
)
|
|
999
|
+
|
|
1000
|
+
path = Path(file_path)
|
|
1001
|
+
if not path.exists():
|
|
1002
|
+
return web.json_response(
|
|
1003
|
+
{
|
|
1004
|
+
"success": False,
|
|
1005
|
+
"error": "File not found",
|
|
1006
|
+
"diff": "",
|
|
1007
|
+
"has_changes": False,
|
|
1008
|
+
},
|
|
1009
|
+
status=404,
|
|
1010
|
+
)
|
|
1011
|
+
|
|
1012
|
+
# Find git repository root
|
|
1013
|
+
git_root_result = subprocess.run(
|
|
1014
|
+
["git", "rev-parse", "--show-toplevel"],
|
|
1015
|
+
check=False,
|
|
1016
|
+
capture_output=True,
|
|
1017
|
+
text=True,
|
|
1018
|
+
cwd=str(path.parent),
|
|
1019
|
+
)
|
|
1020
|
+
|
|
1021
|
+
if git_root_result.returncode != 0:
|
|
1022
|
+
# Not in a git repository
|
|
1023
|
+
return web.json_response(
|
|
1024
|
+
{
|
|
1025
|
+
"success": True,
|
|
1026
|
+
"diff": "",
|
|
1027
|
+
"has_changes": False,
|
|
1028
|
+
"tracked": False,
|
|
1029
|
+
"history": [],
|
|
1030
|
+
"has_uncommitted": False,
|
|
1031
|
+
}
|
|
1032
|
+
)
|
|
1033
|
+
|
|
1034
|
+
git_root = Path(git_root_result.stdout.strip())
|
|
1035
|
+
|
|
1036
|
+
# Check if file is tracked by git
|
|
1037
|
+
ls_files_result = subprocess.run(
|
|
1038
|
+
["git", "ls-files", "--error-unmatch", str(path)],
|
|
1039
|
+
check=False,
|
|
1040
|
+
capture_output=True,
|
|
1041
|
+
text=True,
|
|
1042
|
+
cwd=str(git_root),
|
|
1043
|
+
)
|
|
1044
|
+
|
|
1045
|
+
if ls_files_result.returncode != 0:
|
|
1046
|
+
# File is not tracked by git
|
|
1047
|
+
return web.json_response(
|
|
1048
|
+
{
|
|
1049
|
+
"success": True,
|
|
1050
|
+
"diff": "",
|
|
1051
|
+
"has_changes": False,
|
|
1052
|
+
"tracked": False,
|
|
1053
|
+
"history": [],
|
|
1054
|
+
"has_uncommitted": False,
|
|
1055
|
+
}
|
|
1056
|
+
)
|
|
1057
|
+
|
|
1058
|
+
# Get commit history for this file (last 5 commits)
|
|
1059
|
+
history_result = subprocess.run(
|
|
1060
|
+
[
|
|
1061
|
+
"git",
|
|
1062
|
+
"log",
|
|
1063
|
+
"-5",
|
|
1064
|
+
"--pretty=format:%H|%s|%ar",
|
|
1065
|
+
"--",
|
|
1066
|
+
str(path),
|
|
1067
|
+
],
|
|
1068
|
+
check=False,
|
|
1069
|
+
capture_output=True,
|
|
1070
|
+
text=True,
|
|
1071
|
+
cwd=str(git_root),
|
|
1072
|
+
)
|
|
1073
|
+
|
|
1074
|
+
history = []
|
|
1075
|
+
if history_result.returncode == 0 and history_result.stdout:
|
|
1076
|
+
for line in history_result.stdout.strip().split("\n"):
|
|
1077
|
+
if line:
|
|
1078
|
+
parts = line.split("|", 2)
|
|
1079
|
+
if len(parts) == 3:
|
|
1080
|
+
history.append(
|
|
1081
|
+
{
|
|
1082
|
+
"hash": parts[0][:7], # Short hash
|
|
1083
|
+
"full_hash": parts[0], # Full hash for API
|
|
1084
|
+
"message": parts[1],
|
|
1085
|
+
"time_ago": parts[2],
|
|
1086
|
+
}
|
|
1087
|
+
)
|
|
1088
|
+
|
|
1089
|
+
# Check for uncommitted changes
|
|
1090
|
+
uncommitted_result = subprocess.run(
|
|
1091
|
+
["git", "diff", "HEAD", str(path)],
|
|
1092
|
+
check=False,
|
|
1093
|
+
capture_output=True,
|
|
1094
|
+
text=True,
|
|
1095
|
+
cwd=str(git_root),
|
|
1096
|
+
)
|
|
1097
|
+
|
|
1098
|
+
has_uncommitted = bool(uncommitted_result.stdout.strip())
|
|
1099
|
+
|
|
1100
|
+
# Get diff based on commit parameter
|
|
1101
|
+
if commit_hash:
|
|
1102
|
+
# Get diff for specific commit
|
|
1103
|
+
result = subprocess.run(
|
|
1104
|
+
["git", "show", commit_hash, "--", str(path)],
|
|
1105
|
+
check=False,
|
|
1106
|
+
capture_output=True,
|
|
1107
|
+
text=True,
|
|
1108
|
+
cwd=str(git_root),
|
|
1109
|
+
)
|
|
1110
|
+
diff_output = result.stdout if result.returncode == 0 else ""
|
|
1111
|
+
has_changes = bool(diff_output.strip())
|
|
1112
|
+
else:
|
|
1113
|
+
# Get uncommitted diff (default behavior)
|
|
1114
|
+
diff_output = uncommitted_result.stdout
|
|
1115
|
+
has_changes = has_uncommitted
|
|
1116
|
+
|
|
1117
|
+
return web.json_response(
|
|
1118
|
+
{
|
|
1119
|
+
"success": True,
|
|
1120
|
+
"diff": diff_output,
|
|
1121
|
+
"has_changes": has_changes,
|
|
1122
|
+
"tracked": True,
|
|
1123
|
+
"history": history,
|
|
1124
|
+
"has_uncommitted": has_uncommitted,
|
|
1125
|
+
}
|
|
1126
|
+
)
|
|
1127
|
+
except Exception as e:
|
|
1128
|
+
return web.json_response(
|
|
1129
|
+
{
|
|
1130
|
+
"success": False,
|
|
1131
|
+
"error": str(e),
|
|
1132
|
+
"diff": "",
|
|
1133
|
+
"has_changes": False,
|
|
1134
|
+
"history": [],
|
|
1135
|
+
"has_uncommitted": False,
|
|
1136
|
+
},
|
|
1137
|
+
status=500,
|
|
1138
|
+
)
|
|
1139
|
+
|
|
1140
|
+
# Register routes
|
|
1141
|
+
self.app.router.add_get("/", dashboard_index)
|
|
1142
|
+
self.app.router.add_get("/favicon.svg", favicon_handler)
|
|
1143
|
+
self.app.router.add_get("/health", health_check)
|
|
1144
|
+
self.app.router.add_get("/version.json", version_handler)
|
|
1145
|
+
self.app.router.add_get("/api/config", config_handler)
|
|
1146
|
+
self.app.router.add_get("/api/working-directory", working_directory_handler)
|
|
1147
|
+
self.app.router.add_get("/api/files", api_files_handler)
|
|
1148
|
+
self.app.router.add_get("/api/file/read", api_file_read_handler)
|
|
1149
|
+
self.app.router.add_get("/api/file/diff", git_diff_handler)
|
|
1150
|
+
self.app.router.add_post("/api/events", api_events_handler)
|
|
1151
|
+
self.app.router.add_post("/api/file", api_file_handler)
|
|
1152
|
+
self.app.router.add_post("/api/git-history", git_history_handler)
|
|
1153
|
+
|
|
1154
|
+
# Monitor page routes
|
|
1155
|
+
self.app.router.add_get("/monitor", lambda r: monitor_page_handler(r))
|
|
1156
|
+
self.app.router.add_get(
|
|
1157
|
+
"/monitor/agents", lambda r: monitor_page_handler(r)
|
|
1158
|
+
)
|
|
1159
|
+
self.app.router.add_get("/monitor/tools", lambda r: monitor_page_handler(r))
|
|
1160
|
+
self.app.router.add_get("/monitor/files", lambda r: monitor_page_handler(r))
|
|
1161
|
+
self.app.router.add_get(
|
|
1162
|
+
"/monitor/events", lambda r: monitor_page_handler(r)
|
|
1163
|
+
)
|
|
1164
|
+
|
|
1165
|
+
# Serve Svelte _app assets (compiled JS/CSS)
|
|
1166
|
+
svelte_build_dir = static_dir / "svelte-build"
|
|
1167
|
+
if svelte_build_dir.exists():
|
|
1168
|
+
svelte_app_dir = svelte_build_dir / "_app"
|
|
1169
|
+
if svelte_app_dir.exists():
|
|
1170
|
+
# Serve _app assets with proper caching
|
|
1171
|
+
async def app_assets_handler(request):
|
|
1172
|
+
"""Serve Svelte _app assets."""
|
|
1173
|
+
from aiohttp.web_fileresponse import FileResponse
|
|
1174
|
+
|
|
1175
|
+
rel_path = request.match_info["filepath"]
|
|
1176
|
+
file_path = svelte_app_dir / rel_path
|
|
1177
|
+
|
|
1178
|
+
if not file_path.exists() or not file_path.is_file():
|
|
1179
|
+
raise web.HTTPNotFound()
|
|
1180
|
+
|
|
1181
|
+
response = FileResponse(file_path)
|
|
1182
|
+
|
|
1183
|
+
# Add cache headers for immutable assets
|
|
1184
|
+
if "/immutable/" in str(rel_path):
|
|
1185
|
+
response.headers["Cache-Control"] = (
|
|
1186
|
+
"public, max-age=31536000, immutable"
|
|
1187
|
+
)
|
|
1188
|
+
else:
|
|
1189
|
+
response.headers["Cache-Control"] = (
|
|
1190
|
+
"no-cache, no-store, must-revalidate"
|
|
1191
|
+
)
|
|
1192
|
+
|
|
1193
|
+
return response
|
|
1194
|
+
|
|
1195
|
+
self.app.router.add_get("/_app/{filepath:.*}", app_assets_handler)
|
|
1196
|
+
|
|
1197
|
+
# Legacy static files (for backward compatibility)
|
|
1198
|
+
if static_dir.exists():
|
|
1199
|
+
|
|
1200
|
+
async def static_handler(request):
|
|
1201
|
+
"""Serve legacy static files with cache-control headers for development."""
|
|
1202
|
+
|
|
1203
|
+
from aiohttp.web_fileresponse import FileResponse
|
|
1204
|
+
|
|
1205
|
+
# Get the relative path from the request
|
|
1206
|
+
rel_path = request.match_info["filepath"]
|
|
1207
|
+
file_path = static_dir / rel_path
|
|
1208
|
+
|
|
1209
|
+
if not file_path.exists() or not file_path.is_file():
|
|
1210
|
+
raise web.HTTPNotFound()
|
|
1211
|
+
|
|
1212
|
+
# Create file response
|
|
1213
|
+
response = FileResponse(file_path)
|
|
1214
|
+
|
|
1215
|
+
# Add cache-busting headers for development
|
|
1216
|
+
response.headers["Cache-Control"] = (
|
|
1217
|
+
"no-cache, no-store, must-revalidate"
|
|
1218
|
+
)
|
|
1219
|
+
response.headers["Pragma"] = "no-cache"
|
|
1220
|
+
response.headers["Expires"] = "0"
|
|
1221
|
+
|
|
1222
|
+
return response
|
|
1223
|
+
|
|
1224
|
+
self.app.router.add_get("/static/{filepath:.*}", static_handler)
|
|
1225
|
+
|
|
1226
|
+
# Log dashboard availability
|
|
1227
|
+
if svelte_build_dir.exists():
|
|
1228
|
+
self.logger.info(
|
|
1229
|
+
f"✅ Svelte dashboard available at / (root) (build: {svelte_build_dir})"
|
|
1230
|
+
)
|
|
1231
|
+
else:
|
|
1232
|
+
self.logger.warning(f"Svelte build not found at: {svelte_build_dir}")
|
|
1233
|
+
|
|
1234
|
+
self.logger.info("HTTP routes registered successfully")
|
|
1235
|
+
|
|
1236
|
+
except Exception as e:
|
|
1237
|
+
self.logger.error(f"Error setting up HTTP routes: {e}")
|
|
1238
|
+
raise
|
|
1239
|
+
|
|
1240
|
+
def stop(self):
|
|
1241
|
+
"""Stop the unified monitor server."""
|
|
1242
|
+
try:
|
|
1243
|
+
self.logger.info("Stopping unified monitor server")
|
|
1244
|
+
|
|
1245
|
+
# Signal shutdown first
|
|
1246
|
+
self.running = False
|
|
1247
|
+
|
|
1248
|
+
# If we have a loop, schedule the cleanup
|
|
1249
|
+
if self.loop and not self.loop.is_closed():
|
|
1250
|
+
try:
|
|
1251
|
+
# Use call_soon_threadsafe to schedule cleanup from another thread
|
|
1252
|
+
future = asyncio.run_coroutine_threadsafe(
|
|
1253
|
+
self._graceful_shutdown(), self.loop
|
|
1254
|
+
)
|
|
1255
|
+
# Wait for cleanup to complete (with timeout)
|
|
1256
|
+
future.result(timeout=3)
|
|
1257
|
+
except Exception as e:
|
|
1258
|
+
self.logger.debug(f"Error during graceful shutdown: {e}")
|
|
1259
|
+
|
|
1260
|
+
# Wait for server thread to finish with a reasonable timeout
|
|
1261
|
+
if self.server_thread and self.server_thread.is_alive():
|
|
1262
|
+
self.server_thread.join(timeout=5)
|
|
1263
|
+
|
|
1264
|
+
# If thread is still alive after timeout, log a warning
|
|
1265
|
+
if self.server_thread.is_alive():
|
|
1266
|
+
self.logger.warning("Server thread did not stop within timeout")
|
|
1267
|
+
|
|
1268
|
+
# Clear all references to help with cleanup
|
|
1269
|
+
self.server_thread = None
|
|
1270
|
+
self.app = None
|
|
1271
|
+
self.sio = None
|
|
1272
|
+
self.runner = None
|
|
1273
|
+
self.site = None
|
|
1274
|
+
self.event_emitter = None
|
|
1275
|
+
|
|
1276
|
+
# Give the system a moment to cleanup resources
|
|
1277
|
+
import time
|
|
1278
|
+
|
|
1279
|
+
time.sleep(0.2)
|
|
1280
|
+
|
|
1281
|
+
self.logger.info("Unified monitor server stopped")
|
|
1282
|
+
|
|
1283
|
+
except Exception as e:
|
|
1284
|
+
self.logger.error(f"Error stopping unified monitor server: {e}")
|
|
1285
|
+
|
|
1286
|
+
async def _heartbeat_loop(self):
|
|
1287
|
+
"""Send heartbeat events every 3 minutes."""
|
|
1288
|
+
try:
|
|
1289
|
+
while self.running:
|
|
1290
|
+
# Wait 3 minutes (180 seconds)
|
|
1291
|
+
await asyncio.sleep(180)
|
|
1292
|
+
|
|
1293
|
+
if not self.running:
|
|
1294
|
+
break
|
|
1295
|
+
|
|
1296
|
+
# Increment heartbeat count
|
|
1297
|
+
self.heartbeat_count += 1
|
|
1298
|
+
|
|
1299
|
+
# Calculate server uptime
|
|
1300
|
+
uptime_seconds = int(time.time() - self.server_start_time)
|
|
1301
|
+
uptime_minutes = uptime_seconds // 60
|
|
1302
|
+
uptime_hours = uptime_minutes // 60
|
|
1303
|
+
|
|
1304
|
+
# Format uptime string
|
|
1305
|
+
if uptime_hours > 0:
|
|
1306
|
+
uptime_str = f"{uptime_hours}h {uptime_minutes % 60}m"
|
|
1307
|
+
else:
|
|
1308
|
+
uptime_str = f"{uptime_minutes}m {uptime_seconds % 60}s"
|
|
1309
|
+
|
|
1310
|
+
# Get connected client count
|
|
1311
|
+
connected_clients = 0
|
|
1312
|
+
if self.dashboard_handler:
|
|
1313
|
+
connected_clients = len(self.dashboard_handler.connected_clients)
|
|
1314
|
+
|
|
1315
|
+
# Create heartbeat data
|
|
1316
|
+
heartbeat_data = {
|
|
1317
|
+
"timestamp": datetime.now(timezone.utc).isoformat() + "Z",
|
|
1318
|
+
"type": "heartbeat",
|
|
1319
|
+
"server_uptime": uptime_seconds,
|
|
1320
|
+
"server_uptime_formatted": uptime_str,
|
|
1321
|
+
"connected_clients": connected_clients,
|
|
1322
|
+
"heartbeat_number": self.heartbeat_count,
|
|
1323
|
+
"message": f"Server heartbeat #{self.heartbeat_count} - Socket.IO connection active",
|
|
1324
|
+
"service": "unified-monitor",
|
|
1325
|
+
"port": self.port,
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
# Emit heartbeat event
|
|
1329
|
+
if self.sio:
|
|
1330
|
+
await self.sio.emit("heartbeat", heartbeat_data)
|
|
1331
|
+
self.logger.debug(
|
|
1332
|
+
f"Heartbeat #{self.heartbeat_count} sent - "
|
|
1333
|
+
f"{connected_clients} clients connected, uptime: {uptime_str}"
|
|
1334
|
+
)
|
|
1335
|
+
|
|
1336
|
+
except asyncio.CancelledError:
|
|
1337
|
+
self.logger.debug("Heartbeat task cancelled")
|
|
1338
|
+
except Exception as e:
|
|
1339
|
+
self.logger.error(f"Error in heartbeat loop: {e}")
|
|
1340
|
+
|
|
1341
|
+
async def _cleanup_async(self):
|
|
1342
|
+
"""Cleanup async resources."""
|
|
1343
|
+
try:
|
|
1344
|
+
# Stop file observer if running
|
|
1345
|
+
# STABILITY FIX: Ensure watcher is stopped and verify observer termination
|
|
1346
|
+
if self.file_observer:
|
|
1347
|
+
try:
|
|
1348
|
+
# Stop the watcher first to cancel pending timers
|
|
1349
|
+
if self.file_watcher:
|
|
1350
|
+
self.file_watcher.stop()
|
|
1351
|
+
|
|
1352
|
+
# Stop the observer
|
|
1353
|
+
self.file_observer.stop()
|
|
1354
|
+
self.file_observer.join(timeout=2)
|
|
1355
|
+
|
|
1356
|
+
# Verify observer actually stopped
|
|
1357
|
+
if self.file_observer.is_alive():
|
|
1358
|
+
self.logger.warning("File observer did not stop cleanly")
|
|
1359
|
+
|
|
1360
|
+
self.logger.debug("File observer stopped")
|
|
1361
|
+
except Exception as e:
|
|
1362
|
+
self.logger.debug(f"Error stopping file observer: {e}")
|
|
1363
|
+
finally:
|
|
1364
|
+
self.file_observer = None
|
|
1365
|
+
self.file_watcher = None
|
|
1366
|
+
|
|
1367
|
+
# Cancel heartbeat task if running
|
|
1368
|
+
# STABILITY FIX: Add timeout to prevent infinite wait on cancellation
|
|
1369
|
+
if self.heartbeat_task and not self.heartbeat_task.done():
|
|
1370
|
+
self.heartbeat_task.cancel()
|
|
1371
|
+
try:
|
|
1372
|
+
await asyncio.wait_for(self.heartbeat_task, timeout=2.0)
|
|
1373
|
+
except (asyncio.CancelledError, asyncio.TimeoutError):
|
|
1374
|
+
pass
|
|
1375
|
+
self.logger.debug("Heartbeat task cancelled")
|
|
1376
|
+
|
|
1377
|
+
# Close the Socket.IO server first to stop accepting new connections
|
|
1378
|
+
if self.sio:
|
|
1379
|
+
try:
|
|
1380
|
+
await self.sio.shutdown()
|
|
1381
|
+
self.logger.debug("Socket.IO shutdown complete")
|
|
1382
|
+
except Exception as e:
|
|
1383
|
+
self.logger.debug(f"Error shutting down Socket.IO: {e}")
|
|
1384
|
+
finally:
|
|
1385
|
+
self.sio = None
|
|
1386
|
+
|
|
1387
|
+
# Cleanup event emitter
|
|
1388
|
+
if self.event_emitter:
|
|
1389
|
+
try:
|
|
1390
|
+
if self.sio:
|
|
1391
|
+
self.event_emitter.unregister_socketio_server(self.sio)
|
|
1392
|
+
|
|
1393
|
+
# Use the global cleanup function to ensure proper cleanup
|
|
1394
|
+
from .event_emitter import cleanup_event_emitter
|
|
1395
|
+
|
|
1396
|
+
await cleanup_event_emitter()
|
|
1397
|
+
|
|
1398
|
+
self.logger.info("Event emitter cleaned up")
|
|
1399
|
+
except Exception as e:
|
|
1400
|
+
self.logger.warning(f"Error cleaning up event emitter: {e}")
|
|
1401
|
+
finally:
|
|
1402
|
+
self.event_emitter = None
|
|
1403
|
+
|
|
1404
|
+
# Stop the site (must be done before runner cleanup)
|
|
1405
|
+
if self.site:
|
|
1406
|
+
try:
|
|
1407
|
+
await self.site.stop()
|
|
1408
|
+
self.logger.debug("Site stopped")
|
|
1409
|
+
except Exception as e:
|
|
1410
|
+
self.logger.debug(f"Error stopping site: {e}")
|
|
1411
|
+
finally:
|
|
1412
|
+
self.site = None
|
|
1413
|
+
|
|
1414
|
+
# Cleanup the runner (after site is stopped)
|
|
1415
|
+
if self.runner:
|
|
1416
|
+
try:
|
|
1417
|
+
await self.runner.cleanup()
|
|
1418
|
+
self.logger.debug("Runner cleaned up")
|
|
1419
|
+
except Exception as e:
|
|
1420
|
+
self.logger.debug(f"Error cleaning up runner: {e}")
|
|
1421
|
+
finally:
|
|
1422
|
+
self.runner = None
|
|
1423
|
+
|
|
1424
|
+
# Clear app reference
|
|
1425
|
+
self.app = None
|
|
1426
|
+
|
|
1427
|
+
except Exception as e:
|
|
1428
|
+
self.logger.error(f"Error during async cleanup: {e}")
|
|
1429
|
+
|
|
1430
|
+
def get_status(self) -> Dict:
|
|
1431
|
+
"""Get server status information.
|
|
1432
|
+
|
|
1433
|
+
Returns:
|
|
1434
|
+
Dictionary with server status
|
|
1435
|
+
"""
|
|
1436
|
+
return {
|
|
1437
|
+
"server_running": self.running,
|
|
1438
|
+
"host": self.host,
|
|
1439
|
+
"port": self.port,
|
|
1440
|
+
"handlers": {
|
|
1441
|
+
"code_analysis": self.code_analysis_handler is not None,
|
|
1442
|
+
"dashboard": self.dashboard_handler is not None,
|
|
1443
|
+
"file": self.file_handler is not None,
|
|
1444
|
+
"hooks": self.hook_handler is not None,
|
|
1445
|
+
},
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
def _cancel_all_tasks(self, loop=None):
|
|
1449
|
+
"""Cancel all pending tasks in the event loop."""
|
|
1450
|
+
if loop is None:
|
|
1451
|
+
loop = self.loop
|
|
1452
|
+
|
|
1453
|
+
if not loop or loop.is_closed():
|
|
1454
|
+
return
|
|
1455
|
+
|
|
1456
|
+
try:
|
|
1457
|
+
# Get all tasks in the loop
|
|
1458
|
+
pending = asyncio.all_tasks(loop)
|
|
1459
|
+
|
|
1460
|
+
# Count tasks to cancel
|
|
1461
|
+
tasks_to_cancel = [task for task in pending if not task.done()]
|
|
1462
|
+
|
|
1463
|
+
if tasks_to_cancel:
|
|
1464
|
+
# Cancel each task
|
|
1465
|
+
for task in tasks_to_cancel:
|
|
1466
|
+
task.cancel()
|
|
1467
|
+
|
|
1468
|
+
# Wait for all tasks to complete cancellation
|
|
1469
|
+
gather = asyncio.gather(*tasks_to_cancel, return_exceptions=True)
|
|
1470
|
+
try:
|
|
1471
|
+
loop.run_until_complete(gather)
|
|
1472
|
+
except Exception:
|
|
1473
|
+
# Some tasks might fail to cancel, that's ok
|
|
1474
|
+
pass
|
|
1475
|
+
|
|
1476
|
+
self.logger.debug(f"Cancelled {len(tasks_to_cancel)} pending tasks")
|
|
1477
|
+
except Exception as e:
|
|
1478
|
+
self.logger.debug(f"Error cancelling tasks: {e}")
|
|
1479
|
+
|
|
1480
|
+
async def _graceful_shutdown(self):
|
|
1481
|
+
"""Perform graceful shutdown of async resources."""
|
|
1482
|
+
try:
|
|
1483
|
+
# Stop accepting new connections
|
|
1484
|
+
self.running = False
|
|
1485
|
+
|
|
1486
|
+
# Give ongoing operations a moment to complete
|
|
1487
|
+
await asyncio.sleep(0.5)
|
|
1488
|
+
|
|
1489
|
+
# Then cleanup resources
|
|
1490
|
+
await self._cleanup_async()
|
|
1491
|
+
|
|
1492
|
+
except Exception as e:
|
|
1493
|
+
self.logger.debug(f"Error in graceful shutdown: {e}")
|