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.
- 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,1030 @@
|
|
|
1
|
+
"""Code clone detection service using AST-based similarity analysis.
|
|
2
|
+
|
|
3
|
+
This module provides functionality to detect code clones (duplicated or similar code)
|
|
4
|
+
across Python codebases and suggest refactoring opportunities.
|
|
5
|
+
|
|
6
|
+
Extended to support multi-language clone detection using tree-sitter for:
|
|
7
|
+
JavaScript, TypeScript, Go, Rust, Java, Ruby, PHP, C, C++
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import ast
|
|
11
|
+
import difflib
|
|
12
|
+
import importlib.util
|
|
13
|
+
import logging
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any, ClassVar
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
# Check for pylint availability (optional dependency for clone detection)
|
|
21
|
+
PYLINT_AVAILABLE = importlib.util.find_spec("pylint") is not None
|
|
22
|
+
Symilar: Any = None
|
|
23
|
+
if PYLINT_AVAILABLE:
|
|
24
|
+
try:
|
|
25
|
+
from pylint.checkers.symilar import Symilar
|
|
26
|
+
except ImportError:
|
|
27
|
+
PYLINT_AVAILABLE = False
|
|
28
|
+
|
|
29
|
+
# Check for tree-sitter availability
|
|
30
|
+
TREE_SITTER_AVAILABLE = importlib.util.find_spec("tree_sitter") is not None
|
|
31
|
+
if TREE_SITTER_AVAILABLE:
|
|
32
|
+
import tree_sitter # type: ignore[import-not-found]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class CloneReport:
|
|
37
|
+
"""Report of detected code clone between two files.
|
|
38
|
+
|
|
39
|
+
Attributes:
|
|
40
|
+
file1: First file containing cloned code
|
|
41
|
+
file2: Second file containing cloned code
|
|
42
|
+
line_start1: Starting line number in file1
|
|
43
|
+
line_end1: Ending line number in file1
|
|
44
|
+
line_start2: Starting line number in file2
|
|
45
|
+
line_end2: Ending line number in file2
|
|
46
|
+
similarity: Similarity score from 0.0 to 1.0
|
|
47
|
+
clone_type: Type of clone ("exact", "renamed", "modified")
|
|
48
|
+
code_snippet1: Code snippet from file1
|
|
49
|
+
code_snippet2: Code snippet from file2
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
file1: Path
|
|
53
|
+
file2: Path
|
|
54
|
+
line_start1: int
|
|
55
|
+
line_end1: int
|
|
56
|
+
line_start2: int
|
|
57
|
+
line_end2: int
|
|
58
|
+
similarity: float
|
|
59
|
+
clone_type: str
|
|
60
|
+
code_snippet1: str
|
|
61
|
+
code_snippet2: str
|
|
62
|
+
|
|
63
|
+
def __post_init__(self) -> None:
|
|
64
|
+
"""Validate clone report fields."""
|
|
65
|
+
if not 0.0 <= self.similarity <= 1.0:
|
|
66
|
+
raise ValueError(
|
|
67
|
+
f"Similarity must be between 0.0 and 1.0, got {self.similarity}"
|
|
68
|
+
)
|
|
69
|
+
if self.clone_type not in ("exact", "renamed", "modified"):
|
|
70
|
+
raise ValueError(
|
|
71
|
+
f"Clone type must be 'exact', 'renamed', or 'modified', got {self.clone_type}"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
76
|
+
class RefactoringSuggestion:
|
|
77
|
+
"""Suggestion for refactoring detected clones.
|
|
78
|
+
|
|
79
|
+
Attributes:
|
|
80
|
+
description: Human-readable description of the refactoring
|
|
81
|
+
affected_files: List of files that would be affected
|
|
82
|
+
estimated_reduction: Estimated lines of code saved
|
|
83
|
+
suggested_function_name: Suggested name for extracted function
|
|
84
|
+
parameters: List of parameter names for extracted function
|
|
85
|
+
code_template: Template code showing the suggested refactoring
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
description: str
|
|
89
|
+
affected_files: list[Path]
|
|
90
|
+
estimated_reduction: int
|
|
91
|
+
suggested_function_name: str
|
|
92
|
+
parameters: list[str]
|
|
93
|
+
code_template: str
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass
|
|
97
|
+
class SimilarityReport:
|
|
98
|
+
"""Report of similar functions between two files.
|
|
99
|
+
|
|
100
|
+
Attributes:
|
|
101
|
+
file1: First file path
|
|
102
|
+
file2: Second file path
|
|
103
|
+
similar_functions: List of tuples (func1_name, func2_name, similarity_score)
|
|
104
|
+
overall_similarity: Overall similarity between files (0.0 to 1.0)
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
file1: Path
|
|
108
|
+
file2: Path
|
|
109
|
+
similar_functions: list[tuple[str, str, float]] = field(default_factory=list)
|
|
110
|
+
overall_similarity: float = 0.0
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class CloneDetector:
|
|
114
|
+
"""AST-based code clone detector using pycode_similar and pylint.
|
|
115
|
+
|
|
116
|
+
This class provides methods to detect code clones, analyze similarity between
|
|
117
|
+
functions, and suggest refactoring opportunities to reduce code duplication.
|
|
118
|
+
|
|
119
|
+
Features:
|
|
120
|
+
- Exact clone detection (Type-1): Identical code blocks
|
|
121
|
+
- Renamed clone detection (Type-2): Same structure, different identifiers
|
|
122
|
+
- Modified clone detection (Type-3): Similar logic with minor changes
|
|
123
|
+
- Multi-language support: Python, JavaScript, TypeScript, Go, Rust, Java, Ruby, PHP, C, C++
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
# Similarity thresholds for clone classification
|
|
127
|
+
EXACT_THRESHOLD: ClassVar[float] = 0.95
|
|
128
|
+
RENAMED_THRESHOLD: ClassVar[float] = 0.80
|
|
129
|
+
MODIFIED_THRESHOLD: ClassVar[float] = 0.60
|
|
130
|
+
|
|
131
|
+
# Minimum lines for clone detection
|
|
132
|
+
MIN_CLONE_LINES: ClassVar[int] = 4
|
|
133
|
+
|
|
134
|
+
# Language extension mapping
|
|
135
|
+
LANGUAGE_EXTENSIONS: ClassVar[dict[str, list[str]]] = {
|
|
136
|
+
"python": [".py"],
|
|
137
|
+
"javascript": [".js", ".jsx", ".mjs"],
|
|
138
|
+
"typescript": [".ts", ".tsx"],
|
|
139
|
+
"go": [".go"],
|
|
140
|
+
"rust": [".rs"],
|
|
141
|
+
"java": [".java"],
|
|
142
|
+
"ruby": [".rb"],
|
|
143
|
+
"php": [".php"],
|
|
144
|
+
"c": [".c", ".h"],
|
|
145
|
+
"cpp": [".cpp", ".cc", ".cxx", ".hpp", ".hh", ".hxx"],
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
# Tree-sitter language module names
|
|
149
|
+
TREE_SITTER_LANGUAGES: ClassVar[dict[str, str]] = {
|
|
150
|
+
"python": "tree_sitter_python",
|
|
151
|
+
"javascript": "tree_sitter_javascript",
|
|
152
|
+
"typescript": "tree_sitter_typescript",
|
|
153
|
+
"go": "tree_sitter_go",
|
|
154
|
+
"rust": "tree_sitter_rust",
|
|
155
|
+
"java": "tree_sitter_java",
|
|
156
|
+
"ruby": "tree_sitter_ruby",
|
|
157
|
+
"php": "tree_sitter_php",
|
|
158
|
+
"c": "tree_sitter_c",
|
|
159
|
+
"cpp": "tree_sitter_cpp",
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
def __init__(self, min_similarity: float = 0.60, min_lines: int = 4) -> None:
|
|
163
|
+
"""Initialize clone detector.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
min_similarity: Minimum similarity threshold (0.0 to 1.0)
|
|
167
|
+
min_lines: Minimum number of lines to consider for clones
|
|
168
|
+
"""
|
|
169
|
+
if not 0.0 <= min_similarity <= 1.0:
|
|
170
|
+
raise ValueError(
|
|
171
|
+
f"min_similarity must be between 0.0 and 1.0, got {min_similarity}"
|
|
172
|
+
)
|
|
173
|
+
if min_lines < 1:
|
|
174
|
+
raise ValueError(f"min_lines must be >= 1, got {min_lines}")
|
|
175
|
+
|
|
176
|
+
self.min_similarity = min_similarity
|
|
177
|
+
self.min_lines = min_lines
|
|
178
|
+
self._parsers: dict[str, Any] = {}
|
|
179
|
+
self._init_tree_sitter_parsers()
|
|
180
|
+
|
|
181
|
+
def _init_tree_sitter_parsers(self) -> None:
|
|
182
|
+
"""Initialize tree-sitter parsers for supported languages."""
|
|
183
|
+
if not TREE_SITTER_AVAILABLE:
|
|
184
|
+
logger.debug("tree-sitter not available - multi-language support disabled")
|
|
185
|
+
return
|
|
186
|
+
|
|
187
|
+
for lang, module_name in self.TREE_SITTER_LANGUAGES.items():
|
|
188
|
+
try:
|
|
189
|
+
# Try to import language module
|
|
190
|
+
spec = importlib.util.find_spec(module_name)
|
|
191
|
+
if spec is None:
|
|
192
|
+
continue
|
|
193
|
+
|
|
194
|
+
# Dynamic import
|
|
195
|
+
module = importlib.import_module(module_name)
|
|
196
|
+
|
|
197
|
+
# Create parser with language
|
|
198
|
+
parser = tree_sitter.Parser()
|
|
199
|
+
|
|
200
|
+
# Handle different tree-sitter API versions
|
|
201
|
+
if hasattr(module, "language"):
|
|
202
|
+
lang_obj = tree_sitter.Language(module.language())
|
|
203
|
+
if hasattr(parser, "set_language"):
|
|
204
|
+
parser.set_language(lang_obj)
|
|
205
|
+
else:
|
|
206
|
+
# Newer API - create parser with language
|
|
207
|
+
parser = tree_sitter.Parser(lang_obj)
|
|
208
|
+
|
|
209
|
+
self._parsers[lang] = parser
|
|
210
|
+
logger.debug(f"Initialized tree-sitter parser for {lang}")
|
|
211
|
+
|
|
212
|
+
except (ImportError, AttributeError) as e:
|
|
213
|
+
logger.debug(f"Could not load parser for {lang}: {e}")
|
|
214
|
+
continue
|
|
215
|
+
|
|
216
|
+
def _detect_language(self, file_path: Path) -> str | None:
|
|
217
|
+
"""Detect programming language from file extension.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
file_path: Path to source file
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
Language name or None if not supported
|
|
224
|
+
"""
|
|
225
|
+
ext = file_path.suffix.lower()
|
|
226
|
+
for lang, extensions in self.LANGUAGE_EXTENSIONS.items():
|
|
227
|
+
if ext in extensions:
|
|
228
|
+
return lang
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
def detect_clones(
|
|
232
|
+
self, project_path: Path, languages: list[str] | None = None
|
|
233
|
+
) -> list[CloneReport]:
|
|
234
|
+
"""Detect code clones in a project directory.
|
|
235
|
+
|
|
236
|
+
Supports multi-language detection using tree-sitter for non-Python languages
|
|
237
|
+
and pylint for Python files.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
project_path: Root directory of project to analyze
|
|
241
|
+
languages: List of languages to analyze (None = all supported languages)
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
List of CloneReport objects describing detected clones
|
|
245
|
+
|
|
246
|
+
Raises:
|
|
247
|
+
ValueError: If project_path doesn't exist or isn't a directory
|
|
248
|
+
"""
|
|
249
|
+
if not project_path.exists():
|
|
250
|
+
raise ValueError(f"Project path does not exist: {project_path}")
|
|
251
|
+
if not project_path.is_dir():
|
|
252
|
+
raise ValueError(f"Project path is not a directory: {project_path}")
|
|
253
|
+
|
|
254
|
+
logger.info("Detecting clones in project: %s", project_path)
|
|
255
|
+
|
|
256
|
+
# Determine which languages to analyze
|
|
257
|
+
target_languages = (
|
|
258
|
+
languages if languages else list(self.LANGUAGE_EXTENSIONS.keys())
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# Collect files by language
|
|
262
|
+
files_by_language: dict[str, list[Path]] = {
|
|
263
|
+
lang: [] for lang in target_languages
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
# Scan project for files
|
|
267
|
+
for lang in target_languages:
|
|
268
|
+
extensions = self.LANGUAGE_EXTENSIONS.get(lang, [])
|
|
269
|
+
for ext in extensions:
|
|
270
|
+
files_by_language[lang].extend(project_path.rglob(f"*{ext}"))
|
|
271
|
+
|
|
272
|
+
# Remove empty language groups
|
|
273
|
+
files_by_language = {
|
|
274
|
+
lang: files for lang, files in files_by_language.items() if files
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if not files_by_language:
|
|
278
|
+
logger.warning("No supported files found in %s", project_path)
|
|
279
|
+
return []
|
|
280
|
+
|
|
281
|
+
total_files = sum(len(files) for files in files_by_language.values())
|
|
282
|
+
logger.info(
|
|
283
|
+
"Found %d files across %d languages", total_files, len(files_by_language)
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
# Detect clones per language
|
|
287
|
+
all_clones: list[CloneReport] = []
|
|
288
|
+
|
|
289
|
+
for lang, files in files_by_language.items():
|
|
290
|
+
logger.info("Analyzing %d %s files", len(files), lang)
|
|
291
|
+
try:
|
|
292
|
+
if lang == "python":
|
|
293
|
+
# Use pylint for Python
|
|
294
|
+
clones = self._detect_with_pylint(files)
|
|
295
|
+
else:
|
|
296
|
+
# Use tree-sitter for other languages
|
|
297
|
+
clones = self._detect_with_tree_sitter(files, lang)
|
|
298
|
+
|
|
299
|
+
all_clones.extend(clones)
|
|
300
|
+
logger.info("Found %d clones in %s files", len(clones), lang)
|
|
301
|
+
|
|
302
|
+
except Exception as e:
|
|
303
|
+
logger.error("Error detecting clones in %s files: %s", lang, e)
|
|
304
|
+
|
|
305
|
+
logger.info("Detected %d total clones", len(all_clones))
|
|
306
|
+
return all_clones
|
|
307
|
+
|
|
308
|
+
def _detect_with_pylint(self, files: list[Path]) -> list[CloneReport]:
|
|
309
|
+
"""Detect clones using pylint's Similar checker.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
files: List of Python files to analyze
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
List of CloneReport objects
|
|
316
|
+
"""
|
|
317
|
+
clones: list[CloneReport] = []
|
|
318
|
+
|
|
319
|
+
# Create Symilar instance with our minimum line threshold
|
|
320
|
+
similar = Symilar(
|
|
321
|
+
min_lines=self.min_lines,
|
|
322
|
+
ignore_comments=True,
|
|
323
|
+
ignore_docstrings=True,
|
|
324
|
+
ignore_imports=False,
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
# Process files
|
|
328
|
+
for file_path in files:
|
|
329
|
+
try:
|
|
330
|
+
with open(file_path, encoding="utf-8") as f:
|
|
331
|
+
similar.append_stream(str(file_path), f, file_path.name)
|
|
332
|
+
except Exception as e:
|
|
333
|
+
logger.warning("Error reading %s: %s", file_path, e)
|
|
334
|
+
continue
|
|
335
|
+
|
|
336
|
+
# Run similarity analysis
|
|
337
|
+
try:
|
|
338
|
+
similar.run()
|
|
339
|
+
|
|
340
|
+
# Extract clone information from Similar instance
|
|
341
|
+
# Similar stores results in linesets which we need to process
|
|
342
|
+
for duplicate in similar._compute_sims():
|
|
343
|
+
# Each duplicate is ((file1, start1, end1), (file2, start2, end2))
|
|
344
|
+
if len(duplicate) >= 2:
|
|
345
|
+
loc1, loc2 = duplicate[0], duplicate[1]
|
|
346
|
+
file1_path = Path(loc1[0])
|
|
347
|
+
file2_path = Path(loc2[0])
|
|
348
|
+
|
|
349
|
+
# Read code snippets
|
|
350
|
+
snippet1 = self._read_lines(file1_path, loc1[1], loc1[2])
|
|
351
|
+
snippet2 = self._read_lines(file2_path, loc2[1], loc2[2])
|
|
352
|
+
|
|
353
|
+
# Calculate similarity
|
|
354
|
+
similarity = self._calculate_similarity(snippet1, snippet2)
|
|
355
|
+
|
|
356
|
+
# Determine clone type
|
|
357
|
+
clone_type = self._classify_clone_type(similarity)
|
|
358
|
+
|
|
359
|
+
# Only include if meets minimum similarity threshold
|
|
360
|
+
if similarity >= self.min_similarity:
|
|
361
|
+
clone = CloneReport(
|
|
362
|
+
file1=file1_path,
|
|
363
|
+
file2=file2_path,
|
|
364
|
+
line_start1=loc1[1],
|
|
365
|
+
line_end1=loc1[2],
|
|
366
|
+
line_start2=loc2[1],
|
|
367
|
+
line_end2=loc2[2],
|
|
368
|
+
similarity=similarity,
|
|
369
|
+
clone_type=clone_type,
|
|
370
|
+
code_snippet1=snippet1,
|
|
371
|
+
code_snippet2=snippet2,
|
|
372
|
+
)
|
|
373
|
+
clones.append(clone)
|
|
374
|
+
|
|
375
|
+
except Exception as e:
|
|
376
|
+
logger.error("Error running similarity analysis: %s", e)
|
|
377
|
+
|
|
378
|
+
return clones
|
|
379
|
+
|
|
380
|
+
def _detect_with_tree_sitter(
|
|
381
|
+
self, files: list[Path], language: str
|
|
382
|
+
) -> list[CloneReport]:
|
|
383
|
+
"""Detect clones using tree-sitter for non-Python languages.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
files: List of source files to analyze
|
|
387
|
+
language: Programming language
|
|
388
|
+
|
|
389
|
+
Returns:
|
|
390
|
+
List of CloneReport objects
|
|
391
|
+
"""
|
|
392
|
+
if language not in self._parsers:
|
|
393
|
+
logger.warning("No parser available for %s", language)
|
|
394
|
+
return []
|
|
395
|
+
|
|
396
|
+
parser = self._parsers[language]
|
|
397
|
+
clones: list[CloneReport] = []
|
|
398
|
+
|
|
399
|
+
# Extract code blocks from all files
|
|
400
|
+
file_blocks: dict[Path, list[tuple[int, int, str, str]]] = {}
|
|
401
|
+
|
|
402
|
+
for file_path in files:
|
|
403
|
+
try:
|
|
404
|
+
blocks = self._extract_code_blocks(file_path, parser, language)
|
|
405
|
+
if blocks:
|
|
406
|
+
file_blocks[file_path] = blocks
|
|
407
|
+
except Exception as e:
|
|
408
|
+
logger.warning("Error extracting blocks from %s: %s", file_path, e)
|
|
409
|
+
|
|
410
|
+
# Compare all block pairs across files
|
|
411
|
+
file_paths = list(file_blocks.keys())
|
|
412
|
+
for i, file1 in enumerate(file_paths):
|
|
413
|
+
for file2 in file_paths[i + 1 :]:
|
|
414
|
+
clones.extend(
|
|
415
|
+
self._compare_file_blocks(
|
|
416
|
+
file1, file_blocks[file1], file2, file_blocks[file2]
|
|
417
|
+
)
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
return clones
|
|
421
|
+
|
|
422
|
+
def _extract_code_blocks(
|
|
423
|
+
self, file_path: Path, parser: Any, language: str
|
|
424
|
+
) -> list[tuple[int, int, str, str]]:
|
|
425
|
+
"""Extract code blocks from a file using tree-sitter.
|
|
426
|
+
|
|
427
|
+
Args:
|
|
428
|
+
file_path: Path to source file
|
|
429
|
+
parser: Tree-sitter parser for the language
|
|
430
|
+
language: Programming language
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
List of tuples: (start_line, end_line, code_text, normalized_ast)
|
|
434
|
+
"""
|
|
435
|
+
try:
|
|
436
|
+
with open(file_path, "rb") as f:
|
|
437
|
+
source = f.read()
|
|
438
|
+
|
|
439
|
+
tree = parser.parse(source)
|
|
440
|
+
blocks: list[tuple[int, int, str, str]] = []
|
|
441
|
+
|
|
442
|
+
# Extract function/method blocks
|
|
443
|
+
self._walk_tree_for_blocks(tree.root_node, source, language, blocks)
|
|
444
|
+
|
|
445
|
+
return blocks
|
|
446
|
+
|
|
447
|
+
except Exception as e:
|
|
448
|
+
logger.debug("Error parsing %s: %s", file_path, e)
|
|
449
|
+
return []
|
|
450
|
+
|
|
451
|
+
def _walk_tree_for_blocks(
|
|
452
|
+
self,
|
|
453
|
+
node: Any,
|
|
454
|
+
source: bytes,
|
|
455
|
+
language: str,
|
|
456
|
+
blocks: list[tuple[int, int, str, str]],
|
|
457
|
+
) -> None:
|
|
458
|
+
"""Recursively walk tree-sitter AST to extract code blocks.
|
|
459
|
+
|
|
460
|
+
Args:
|
|
461
|
+
node: Tree-sitter node
|
|
462
|
+
source: Source code bytes
|
|
463
|
+
language: Programming language
|
|
464
|
+
blocks: Output list to append blocks to
|
|
465
|
+
"""
|
|
466
|
+
# Define function/method node types per language
|
|
467
|
+
function_types = {
|
|
468
|
+
"javascript": [
|
|
469
|
+
"function_declaration",
|
|
470
|
+
"arrow_function",
|
|
471
|
+
"method_definition",
|
|
472
|
+
],
|
|
473
|
+
"typescript": [
|
|
474
|
+
"function_declaration",
|
|
475
|
+
"arrow_function",
|
|
476
|
+
"method_definition",
|
|
477
|
+
],
|
|
478
|
+
"go": ["function_declaration", "method_declaration"],
|
|
479
|
+
"rust": ["function_item", "impl_item"],
|
|
480
|
+
"java": ["method_declaration", "constructor_declaration"],
|
|
481
|
+
"ruby": ["method", "singleton_method"],
|
|
482
|
+
"php": ["function_definition", "method_declaration"],
|
|
483
|
+
"c": ["function_definition"],
|
|
484
|
+
"cpp": ["function_definition"],
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
target_types = function_types.get(language, ["function_declaration"])
|
|
488
|
+
|
|
489
|
+
# Check if this node is a function/method
|
|
490
|
+
if node.type in target_types:
|
|
491
|
+
# Extract code block
|
|
492
|
+
start_line = node.start_point[0] + 1
|
|
493
|
+
end_line = node.end_point[0] + 1
|
|
494
|
+
line_count = end_line - start_line + 1
|
|
495
|
+
|
|
496
|
+
# Only consider blocks meeting minimum line threshold
|
|
497
|
+
if line_count >= self.min_lines:
|
|
498
|
+
code_text = source[node.start_byte : node.end_byte].decode(
|
|
499
|
+
"utf-8", errors="ignore"
|
|
500
|
+
)
|
|
501
|
+
normalized = self._normalize_ast(node, source, language)
|
|
502
|
+
|
|
503
|
+
blocks.append((start_line, end_line, code_text, normalized))
|
|
504
|
+
|
|
505
|
+
# Recursively process children
|
|
506
|
+
for child in node.children:
|
|
507
|
+
self._walk_tree_for_blocks(child, source, language, blocks)
|
|
508
|
+
|
|
509
|
+
def _normalize_ast(self, node: Any, source: bytes, language: str) -> str:
|
|
510
|
+
"""Normalize AST to detect Type-2 clones (renamed identifiers).
|
|
511
|
+
|
|
512
|
+
Replaces variable names, function names with generic tokens to detect
|
|
513
|
+
structural similarity even when identifiers differ.
|
|
514
|
+
|
|
515
|
+
Args:
|
|
516
|
+
node: Tree-sitter node
|
|
517
|
+
source: Source code bytes
|
|
518
|
+
language: Programming language
|
|
519
|
+
|
|
520
|
+
Returns:
|
|
521
|
+
Normalized AST representation as string
|
|
522
|
+
"""
|
|
523
|
+
# Build normalized representation by replacing identifiers
|
|
524
|
+
# This allows detecting clones where only variable names differ
|
|
525
|
+
|
|
526
|
+
def normalize_node(n: Any) -> str:
|
|
527
|
+
# Replace identifier nodes with generic token
|
|
528
|
+
if n.type == "identifier":
|
|
529
|
+
return "<ID>"
|
|
530
|
+
if n.type in ("string", "string_literal", "char_literal"):
|
|
531
|
+
return "<STR>"
|
|
532
|
+
if n.type in ("number", "integer", "float"):
|
|
533
|
+
return "<NUM>"
|
|
534
|
+
if n.type == "comment":
|
|
535
|
+
return "" # Ignore comments
|
|
536
|
+
if not n.children:
|
|
537
|
+
# Leaf node - use actual text
|
|
538
|
+
try:
|
|
539
|
+
return source[n.start_byte : n.end_byte].decode(
|
|
540
|
+
"utf-8", errors="ignore"
|
|
541
|
+
)
|
|
542
|
+
except Exception:
|
|
543
|
+
return n.type
|
|
544
|
+
else:
|
|
545
|
+
# Non-leaf - recursively normalize children
|
|
546
|
+
parts = [normalize_node(child) for child in n.children]
|
|
547
|
+
return f"({n.type} {' '.join(p for p in parts if p)})"
|
|
548
|
+
|
|
549
|
+
return normalize_node(node)
|
|
550
|
+
|
|
551
|
+
def _compare_file_blocks(
|
|
552
|
+
self,
|
|
553
|
+
file1: Path,
|
|
554
|
+
blocks1: list[tuple[int, int, str, str]],
|
|
555
|
+
file2: Path,
|
|
556
|
+
blocks2: list[tuple[int, int, str, str]],
|
|
557
|
+
) -> list[CloneReport]:
|
|
558
|
+
"""Compare code blocks between two files.
|
|
559
|
+
|
|
560
|
+
Args:
|
|
561
|
+
file1: First file path
|
|
562
|
+
blocks1: Code blocks from first file
|
|
563
|
+
file2: Second file path
|
|
564
|
+
blocks2: Code blocks from second file
|
|
565
|
+
|
|
566
|
+
Returns:
|
|
567
|
+
List of detected clones
|
|
568
|
+
"""
|
|
569
|
+
clones: list[CloneReport] = []
|
|
570
|
+
|
|
571
|
+
for start1, end1, code1, norm1 in blocks1:
|
|
572
|
+
for start2, end2, code2, norm2 in blocks2:
|
|
573
|
+
# Calculate similarity using both raw text and normalized AST
|
|
574
|
+
text_similarity = self._calculate_similarity(code1, code2)
|
|
575
|
+
ast_similarity = self._calculate_similarity(norm1, norm2)
|
|
576
|
+
|
|
577
|
+
# Use max of both similarities to catch Type-2 clones
|
|
578
|
+
similarity = max(text_similarity, ast_similarity)
|
|
579
|
+
|
|
580
|
+
if similarity >= self.min_similarity:
|
|
581
|
+
clone_type = self._classify_clone_type(similarity)
|
|
582
|
+
|
|
583
|
+
clone = CloneReport(
|
|
584
|
+
file1=file1,
|
|
585
|
+
file2=file2,
|
|
586
|
+
line_start1=start1,
|
|
587
|
+
line_end1=end1,
|
|
588
|
+
line_start2=start2,
|
|
589
|
+
line_end2=end2,
|
|
590
|
+
similarity=similarity,
|
|
591
|
+
clone_type=clone_type,
|
|
592
|
+
code_snippet1=code1,
|
|
593
|
+
code_snippet2=code2,
|
|
594
|
+
)
|
|
595
|
+
clones.append(clone)
|
|
596
|
+
|
|
597
|
+
return clones
|
|
598
|
+
|
|
599
|
+
def _read_lines(self, file_path: Path, start_line: int, end_line: int) -> str:
|
|
600
|
+
"""Read specific lines from a file.
|
|
601
|
+
|
|
602
|
+
Args:
|
|
603
|
+
file_path: Path to file
|
|
604
|
+
start_line: Starting line number (1-indexed)
|
|
605
|
+
end_line: Ending line number (1-indexed)
|
|
606
|
+
|
|
607
|
+
Returns:
|
|
608
|
+
String containing the specified lines
|
|
609
|
+
"""
|
|
610
|
+
try:
|
|
611
|
+
with open(file_path, encoding="utf-8") as f:
|
|
612
|
+
lines = f.readlines()
|
|
613
|
+
# Convert to 0-indexed
|
|
614
|
+
return "".join(lines[start_line - 1 : end_line])
|
|
615
|
+
except Exception as e:
|
|
616
|
+
logger.warning("Error reading lines from %s: %s", file_path, e)
|
|
617
|
+
return ""
|
|
618
|
+
|
|
619
|
+
def _calculate_similarity(self, text1: str, text2: str) -> float:
|
|
620
|
+
"""Calculate similarity between two text strings.
|
|
621
|
+
|
|
622
|
+
Uses difflib's SequenceMatcher for similarity calculation.
|
|
623
|
+
|
|
624
|
+
Args:
|
|
625
|
+
text1: First text string
|
|
626
|
+
text2: Second text string
|
|
627
|
+
|
|
628
|
+
Returns:
|
|
629
|
+
Similarity score from 0.0 to 1.0
|
|
630
|
+
"""
|
|
631
|
+
return difflib.SequenceMatcher(None, text1, text2).ratio()
|
|
632
|
+
|
|
633
|
+
def _classify_clone_type(self, similarity: float) -> str:
|
|
634
|
+
"""Classify clone type based on similarity score.
|
|
635
|
+
|
|
636
|
+
Args:
|
|
637
|
+
similarity: Similarity score from 0.0 to 1.0
|
|
638
|
+
|
|
639
|
+
Returns:
|
|
640
|
+
Clone type: "exact", "renamed", or "modified"
|
|
641
|
+
"""
|
|
642
|
+
if similarity >= self.EXACT_THRESHOLD:
|
|
643
|
+
return "exact"
|
|
644
|
+
if similarity >= self.RENAMED_THRESHOLD:
|
|
645
|
+
return "renamed"
|
|
646
|
+
return "modified"
|
|
647
|
+
|
|
648
|
+
def find_similar_functions(self, file1: Path, file2: Path) -> SimilarityReport:
|
|
649
|
+
"""Find similar functions between two files.
|
|
650
|
+
|
|
651
|
+
Uses AST analysis to compare function structures and identify similar
|
|
652
|
+
implementations. Supports multi-language comparison using tree-sitter.
|
|
653
|
+
|
|
654
|
+
Args:
|
|
655
|
+
file1: First source file path
|
|
656
|
+
file2: Second source file path
|
|
657
|
+
|
|
658
|
+
Returns:
|
|
659
|
+
SimilarityReport with function-level similarity analysis
|
|
660
|
+
|
|
661
|
+
Raises:
|
|
662
|
+
ValueError: If files don't exist or have incompatible languages
|
|
663
|
+
"""
|
|
664
|
+
if not file1.exists() or not file2.exists():
|
|
665
|
+
raise ValueError("Both files must exist")
|
|
666
|
+
|
|
667
|
+
# Detect languages
|
|
668
|
+
lang1 = self._detect_language(file1)
|
|
669
|
+
lang2 = self._detect_language(file2)
|
|
670
|
+
|
|
671
|
+
if lang1 is None or lang2 is None:
|
|
672
|
+
raise ValueError(f"Unsupported file types: {file1.suffix}, {file2.suffix}")
|
|
673
|
+
|
|
674
|
+
if lang1 != lang2:
|
|
675
|
+
raise ValueError(f"Cannot compare different languages: {lang1} vs {lang2}")
|
|
676
|
+
|
|
677
|
+
logger.info(
|
|
678
|
+
"Analyzing function similarity between %s and %s (%s)", file1, file2, lang1
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
# Use language-specific analysis
|
|
682
|
+
if lang1 == "python":
|
|
683
|
+
return self._find_similar_functions_python(file1, file2)
|
|
684
|
+
return self._find_similar_functions_tree_sitter(file1, file2, lang1)
|
|
685
|
+
|
|
686
|
+
def _find_similar_functions_python(
|
|
687
|
+
self, file1: Path, file2: Path
|
|
688
|
+
) -> SimilarityReport:
|
|
689
|
+
"""Find similar functions in Python files using AST.
|
|
690
|
+
|
|
691
|
+
Args:
|
|
692
|
+
file1: First Python file
|
|
693
|
+
file2: Second Python file
|
|
694
|
+
|
|
695
|
+
Returns:
|
|
696
|
+
SimilarityReport with function comparisons
|
|
697
|
+
"""
|
|
698
|
+
# Parse AST for both files
|
|
699
|
+
try:
|
|
700
|
+
tree1 = self._parse_file(file1)
|
|
701
|
+
tree2 = self._parse_file(file2)
|
|
702
|
+
except Exception as e:
|
|
703
|
+
logger.error("Error parsing files: %s", e)
|
|
704
|
+
return SimilarityReport(file1=file1, file2=file2)
|
|
705
|
+
|
|
706
|
+
# Extract functions from both files
|
|
707
|
+
funcs1 = self._extract_functions(tree1)
|
|
708
|
+
funcs2 = self._extract_functions(tree2)
|
|
709
|
+
|
|
710
|
+
logger.debug("Found %d functions in %s", len(funcs1), file1)
|
|
711
|
+
logger.debug("Found %d functions in %s", len(funcs2), file2)
|
|
712
|
+
|
|
713
|
+
# Compare all function pairs
|
|
714
|
+
similar_functions: list[tuple[str, str, float]] = []
|
|
715
|
+
for name1, func1 in funcs1.items():
|
|
716
|
+
for name2, func2 in funcs2.items():
|
|
717
|
+
similarity = self._compare_functions(func1, func2)
|
|
718
|
+
if similarity >= self.min_similarity:
|
|
719
|
+
similar_functions.append((name1, name2, similarity))
|
|
720
|
+
|
|
721
|
+
# Calculate overall file similarity
|
|
722
|
+
overall_similarity = 0.0
|
|
723
|
+
if similar_functions:
|
|
724
|
+
overall_similarity = sum(s for _, _, s in similar_functions) / len(
|
|
725
|
+
similar_functions
|
|
726
|
+
)
|
|
727
|
+
|
|
728
|
+
return SimilarityReport(
|
|
729
|
+
file1=file1,
|
|
730
|
+
file2=file2,
|
|
731
|
+
similar_functions=similar_functions,
|
|
732
|
+
overall_similarity=overall_similarity,
|
|
733
|
+
)
|
|
734
|
+
|
|
735
|
+
def _find_similar_functions_tree_sitter(
|
|
736
|
+
self, file1: Path, file2: Path, language: str
|
|
737
|
+
) -> SimilarityReport:
|
|
738
|
+
"""Find similar functions using tree-sitter.
|
|
739
|
+
|
|
740
|
+
Args:
|
|
741
|
+
file1: First source file
|
|
742
|
+
file2: Second source file
|
|
743
|
+
language: Programming language
|
|
744
|
+
|
|
745
|
+
Returns:
|
|
746
|
+
SimilarityReport with function comparisons
|
|
747
|
+
"""
|
|
748
|
+
if language not in self._parsers:
|
|
749
|
+
logger.warning("No parser for %s", language)
|
|
750
|
+
return SimilarityReport(file1=file1, file2=file2)
|
|
751
|
+
|
|
752
|
+
parser = self._parsers[language]
|
|
753
|
+
|
|
754
|
+
# Extract blocks from both files
|
|
755
|
+
blocks1 = self._extract_code_blocks(file1, parser, language)
|
|
756
|
+
blocks2 = self._extract_code_blocks(file2, parser, language)
|
|
757
|
+
|
|
758
|
+
logger.debug("Found %d blocks in %s", len(blocks1), file1)
|
|
759
|
+
logger.debug("Found %d blocks in %s", len(blocks2), file2)
|
|
760
|
+
|
|
761
|
+
# Compare all block pairs
|
|
762
|
+
similar_functions: list[tuple[str, str, float]] = []
|
|
763
|
+
|
|
764
|
+
for i, (start1, end1, code1, norm1) in enumerate(blocks1):
|
|
765
|
+
for j, (start2, end2, code2, norm2) in enumerate(blocks2):
|
|
766
|
+
# Calculate similarity
|
|
767
|
+
text_sim = self._calculate_similarity(code1, code2)
|
|
768
|
+
ast_sim = self._calculate_similarity(norm1, norm2)
|
|
769
|
+
similarity = max(text_sim, ast_sim)
|
|
770
|
+
|
|
771
|
+
if similarity >= self.min_similarity:
|
|
772
|
+
# Use line numbers as function identifiers
|
|
773
|
+
name1 = f"block_{start1}-{end1}"
|
|
774
|
+
name2 = f"block_{start2}-{end2}"
|
|
775
|
+
similar_functions.append((name1, name2, similarity))
|
|
776
|
+
|
|
777
|
+
# Calculate overall similarity
|
|
778
|
+
overall_similarity = 0.0
|
|
779
|
+
if similar_functions:
|
|
780
|
+
overall_similarity = sum(s for _, _, s in similar_functions) / len(
|
|
781
|
+
similar_functions
|
|
782
|
+
)
|
|
783
|
+
|
|
784
|
+
return SimilarityReport(
|
|
785
|
+
file1=file1,
|
|
786
|
+
file2=file2,
|
|
787
|
+
similar_functions=similar_functions,
|
|
788
|
+
overall_similarity=overall_similarity,
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
def _parse_file(self, file_path: Path) -> ast.AST:
|
|
792
|
+
"""Parse Python file into AST.
|
|
793
|
+
|
|
794
|
+
Args:
|
|
795
|
+
file_path: Path to Python file
|
|
796
|
+
|
|
797
|
+
Returns:
|
|
798
|
+
AST node
|
|
799
|
+
|
|
800
|
+
Raises:
|
|
801
|
+
SyntaxError: If file has syntax errors
|
|
802
|
+
"""
|
|
803
|
+
with open(file_path, encoding="utf-8") as f:
|
|
804
|
+
return ast.parse(f.read(), filename=str(file_path))
|
|
805
|
+
|
|
806
|
+
def _extract_functions(self, tree: ast.AST) -> dict[str, ast.FunctionDef]:
|
|
807
|
+
"""Extract function definitions from AST.
|
|
808
|
+
|
|
809
|
+
Args:
|
|
810
|
+
tree: AST root node
|
|
811
|
+
|
|
812
|
+
Returns:
|
|
813
|
+
Dictionary mapping function names to FunctionDef nodes
|
|
814
|
+
"""
|
|
815
|
+
functions: dict[str, ast.FunctionDef] = {}
|
|
816
|
+
for node in ast.walk(tree):
|
|
817
|
+
if isinstance(node, ast.FunctionDef):
|
|
818
|
+
functions[node.name] = node
|
|
819
|
+
return functions
|
|
820
|
+
|
|
821
|
+
def _compare_functions(
|
|
822
|
+
self, func1: ast.FunctionDef, func2: ast.FunctionDef
|
|
823
|
+
) -> float:
|
|
824
|
+
"""Compare two function AST nodes for similarity.
|
|
825
|
+
|
|
826
|
+
Args:
|
|
827
|
+
func1: First function AST node
|
|
828
|
+
func2: Second function AST node
|
|
829
|
+
|
|
830
|
+
Returns:
|
|
831
|
+
Similarity score from 0.0 to 1.0
|
|
832
|
+
"""
|
|
833
|
+
# Convert AST to source code
|
|
834
|
+
code1 = ast.unparse(func1)
|
|
835
|
+
code2 = ast.unparse(func2)
|
|
836
|
+
|
|
837
|
+
# Use difflib for comparison
|
|
838
|
+
return self._calculate_similarity(code1, code2)
|
|
839
|
+
|
|
840
|
+
def suggest_parameterization(
|
|
841
|
+
self, clones: list[CloneReport]
|
|
842
|
+
) -> list[RefactoringSuggestion]:
|
|
843
|
+
"""Suggest parameterization opportunities for detected clones.
|
|
844
|
+
|
|
845
|
+
Analyzes clone groups and suggests how to extract common logic into
|
|
846
|
+
reusable functions.
|
|
847
|
+
|
|
848
|
+
Args:
|
|
849
|
+
clones: List of detected clone reports
|
|
850
|
+
|
|
851
|
+
Returns:
|
|
852
|
+
List of RefactoringSuggestion objects
|
|
853
|
+
"""
|
|
854
|
+
logger.info("Generating refactoring suggestions for %d clones", len(clones))
|
|
855
|
+
|
|
856
|
+
# Group clones by similarity
|
|
857
|
+
clone_groups = self._group_similar_clones(clones)
|
|
858
|
+
|
|
859
|
+
suggestions: list[RefactoringSuggestion] = []
|
|
860
|
+
for group in clone_groups:
|
|
861
|
+
try:
|
|
862
|
+
suggestion = self._create_suggestion(group)
|
|
863
|
+
if suggestion:
|
|
864
|
+
suggestions.append(suggestion)
|
|
865
|
+
except Exception as e:
|
|
866
|
+
logger.warning("Error creating suggestion: %s", e)
|
|
867
|
+
|
|
868
|
+
logger.info("Generated %d refactoring suggestions", len(suggestions))
|
|
869
|
+
return suggestions
|
|
870
|
+
|
|
871
|
+
def _group_similar_clones(
|
|
872
|
+
self, clones: list[CloneReport]
|
|
873
|
+
) -> list[list[CloneReport]]:
|
|
874
|
+
"""Group clones that are similar to each other.
|
|
875
|
+
|
|
876
|
+
Args:
|
|
877
|
+
clones: List of clone reports
|
|
878
|
+
|
|
879
|
+
Returns:
|
|
880
|
+
List of clone groups
|
|
881
|
+
"""
|
|
882
|
+
# Simple grouping: clones with same code are grouped together
|
|
883
|
+
groups: dict[str, list[CloneReport]] = {}
|
|
884
|
+
for clone in clones:
|
|
885
|
+
# Use code snippet as key (normalize whitespace)
|
|
886
|
+
key = " ".join(clone.code_snippet1.split())
|
|
887
|
+
if key not in groups:
|
|
888
|
+
groups[key] = []
|
|
889
|
+
groups[key].append(clone)
|
|
890
|
+
|
|
891
|
+
return list(groups.values())
|
|
892
|
+
|
|
893
|
+
def _create_suggestion(
|
|
894
|
+
self, clone_group: list[CloneReport]
|
|
895
|
+
) -> RefactoringSuggestion | None:
|
|
896
|
+
"""Create refactoring suggestion for a group of clones.
|
|
897
|
+
|
|
898
|
+
Args:
|
|
899
|
+
clone_group: List of similar clones
|
|
900
|
+
|
|
901
|
+
Returns:
|
|
902
|
+
RefactoringSuggestion or None if no suggestion possible
|
|
903
|
+
"""
|
|
904
|
+
if not clone_group:
|
|
905
|
+
return None
|
|
906
|
+
|
|
907
|
+
# Use first clone as representative
|
|
908
|
+
representative = clone_group[0]
|
|
909
|
+
|
|
910
|
+
# Collect all affected files
|
|
911
|
+
affected_files: set[Path] = set()
|
|
912
|
+
for clone in clone_group:
|
|
913
|
+
affected_files.add(clone.file1)
|
|
914
|
+
affected_files.add(clone.file2)
|
|
915
|
+
|
|
916
|
+
# Calculate estimated reduction
|
|
917
|
+
# Each clone instance can be replaced with 1-2 lines (function call)
|
|
918
|
+
lines_per_clone = representative.line_end1 - representative.line_start1 + 1
|
|
919
|
+
estimated_reduction = (len(clone_group) * lines_per_clone) - lines_per_clone
|
|
920
|
+
|
|
921
|
+
# Generate function name suggestion
|
|
922
|
+
suggested_name = self._suggest_function_name(representative)
|
|
923
|
+
|
|
924
|
+
# Analyze code to identify potential parameters
|
|
925
|
+
parameters = self._identify_parameters(representative)
|
|
926
|
+
|
|
927
|
+
# Create code template
|
|
928
|
+
code_template = self._create_code_template(
|
|
929
|
+
suggested_name, parameters, representative.code_snippet1
|
|
930
|
+
)
|
|
931
|
+
|
|
932
|
+
description = (
|
|
933
|
+
f"Extract {len(clone_group)} similar code blocks "
|
|
934
|
+
f"from {len(affected_files)} files into reusable function"
|
|
935
|
+
)
|
|
936
|
+
|
|
937
|
+
return RefactoringSuggestion(
|
|
938
|
+
description=description,
|
|
939
|
+
affected_files=list(affected_files),
|
|
940
|
+
estimated_reduction=estimated_reduction,
|
|
941
|
+
suggested_function_name=suggested_name,
|
|
942
|
+
parameters=parameters,
|
|
943
|
+
code_template=code_template,
|
|
944
|
+
)
|
|
945
|
+
|
|
946
|
+
def _suggest_function_name(self, clone: CloneReport) -> str:
|
|
947
|
+
"""Suggest a function name based on clone code.
|
|
948
|
+
|
|
949
|
+
Args:
|
|
950
|
+
clone: Clone report
|
|
951
|
+
|
|
952
|
+
Returns:
|
|
953
|
+
Suggested function name
|
|
954
|
+
"""
|
|
955
|
+
# Simple heuristic: extract first meaningful identifier
|
|
956
|
+
try:
|
|
957
|
+
tree = ast.parse(clone.code_snippet1)
|
|
958
|
+
for node in ast.walk(tree):
|
|
959
|
+
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name):
|
|
960
|
+
return f"extracted_{node.func.id}"
|
|
961
|
+
except Exception:
|
|
962
|
+
pass
|
|
963
|
+
|
|
964
|
+
return "extracted_function"
|
|
965
|
+
|
|
966
|
+
def _identify_parameters(self, clone: CloneReport) -> list[str]:
|
|
967
|
+
"""Identify potential parameters for extracted function.
|
|
968
|
+
|
|
969
|
+
Args:
|
|
970
|
+
clone: Clone report
|
|
971
|
+
|
|
972
|
+
Returns:
|
|
973
|
+
List of parameter names
|
|
974
|
+
"""
|
|
975
|
+
parameters: list[str] = []
|
|
976
|
+
|
|
977
|
+
try:
|
|
978
|
+
tree = ast.parse(clone.code_snippet1)
|
|
979
|
+
# Collect names that are used but not defined in the snippet
|
|
980
|
+
names_used: set[str] = set()
|
|
981
|
+
names_defined: set[str] = set()
|
|
982
|
+
|
|
983
|
+
for node in ast.walk(tree):
|
|
984
|
+
if isinstance(node, ast.Name):
|
|
985
|
+
if isinstance(node.ctx, ast.Store):
|
|
986
|
+
names_defined.add(node.id)
|
|
987
|
+
elif isinstance(node.ctx, ast.Load):
|
|
988
|
+
names_used.add(node.id)
|
|
989
|
+
|
|
990
|
+
# Parameters are names used but not defined
|
|
991
|
+
parameters = list(names_used - names_defined)
|
|
992
|
+
|
|
993
|
+
except Exception as e:
|
|
994
|
+
logger.debug("Error identifying parameters: %s", e)
|
|
995
|
+
|
|
996
|
+
return parameters or ["data"] # Default parameter if none found
|
|
997
|
+
|
|
998
|
+
def _create_code_template(
|
|
999
|
+
self, func_name: str, parameters: list[str], code: str
|
|
1000
|
+
) -> str:
|
|
1001
|
+
"""Create code template for suggested refactoring.
|
|
1002
|
+
|
|
1003
|
+
Args:
|
|
1004
|
+
func_name: Suggested function name
|
|
1005
|
+
parameters: List of parameter names
|
|
1006
|
+
code: Clone code snippet
|
|
1007
|
+
|
|
1008
|
+
Returns:
|
|
1009
|
+
Code template string
|
|
1010
|
+
"""
|
|
1011
|
+
param_str = ", ".join(parameters)
|
|
1012
|
+
indent = " "
|
|
1013
|
+
|
|
1014
|
+
# Indent code block
|
|
1015
|
+
indented_code = "\n".join(
|
|
1016
|
+
f"{indent}{line}" if line.strip() else "" for line in code.split("\n")
|
|
1017
|
+
)
|
|
1018
|
+
|
|
1019
|
+
return (
|
|
1020
|
+
f"""def {func_name}({param_str}):
|
|
1021
|
+
\"\"\"Extracted common logic from multiple locations.
|
|
1022
|
+
|
|
1023
|
+
Args:
|
|
1024
|
+
{indent}{indent}"""
|
|
1025
|
+
+ f"\n{indent}{indent}".join(f"{p}: Parameter" for p in parameters)
|
|
1026
|
+
+ f"""
|
|
1027
|
+
\"\"\"
|
|
1028
|
+
{indented_code}
|
|
1029
|
+
"""
|
|
1030
|
+
)
|