claude-mpm 4.1.26__py3-none-any.whl → 4.24.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of claude-mpm might be problematic. Click here for more details.
- claude_mpm/BUILD_NUMBER +1 -1
- claude_mpm/VERSION +1 -1
- claude_mpm/__init__.py +20 -5
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +118 -0
- claude_mpm/agents/BASE_DOCUMENTATION.md +53 -0
- claude_mpm/agents/BASE_ENGINEER.md +658 -0
- claude_mpm/agents/BASE_OPS.md +219 -0
- claude_mpm/agents/BASE_PM.md +420 -158
- claude_mpm/agents/BASE_PROMPT_ENGINEER.md +787 -0
- claude_mpm/agents/BASE_QA.md +167 -0
- claude_mpm/agents/BASE_RESEARCH.md +53 -0
- claude_mpm/agents/OUTPUT_STYLE.md +299 -29
- claude_mpm/agents/PM_INSTRUCTIONS.md +1159 -0
- claude_mpm/agents/WORKFLOW.md +355 -191
- claude_mpm/agents/agent_loader.py +40 -10
- claude_mpm/agents/agent_loader_integration.py +3 -2
- claude_mpm/agents/async_agent_loader.py +3 -3
- claude_mpm/agents/base_agent_loader.py +11 -9
- claude_mpm/agents/frontmatter_validator.py +291 -251
- claude_mpm/agents/system_agent_config.py +3 -2
- claude_mpm/agents/templates/README.md +465 -0
- claude_mpm/agents/templates/agent-manager.json +7 -4
- claude_mpm/agents/templates/{agentic_coder_optimizer.json → agentic-coder-optimizer.json} +33 -7
- claude_mpm/agents/templates/api_qa.json +16 -4
- claude_mpm/agents/templates/circuit_breakers.md +638 -0
- claude_mpm/agents/templates/clerk-ops.json +235 -0
- claude_mpm/agents/templates/code_analyzer.json +10 -4
- claude_mpm/agents/templates/content-agent.json +358 -0
- claude_mpm/agents/templates/dart_engineer.json +307 -0
- claude_mpm/agents/templates/data_engineer.json +87 -14
- claude_mpm/agents/templates/documentation.json +76 -13
- claude_mpm/agents/templates/engineer.json +43 -9
- claude_mpm/agents/templates/gcp_ops_agent.json +253 -0
- claude_mpm/agents/templates/git_file_tracking.md +584 -0
- claude_mpm/agents/templates/golang_engineer.json +270 -0
- claude_mpm/agents/templates/imagemagick.json +5 -2
- claude_mpm/agents/templates/java_engineer.json +346 -0
- claude_mpm/agents/templates/javascript_engineer_agent.json +380 -0
- claude_mpm/agents/templates/local_ops_agent.json +1840 -0
- claude_mpm/agents/templates/logs/prompts/agent_engineer_20250901_010124_142.md +400 -0
- claude_mpm/agents/templates/memory_manager.json +6 -3
- claude_mpm/agents/templates/nextjs_engineer.json +285 -0
- claude_mpm/agents/templates/ops.json +14 -4
- claude_mpm/agents/templates/php-engineer.json +287 -0
- claude_mpm/agents/templates/pm_examples.md +474 -0
- claude_mpm/agents/templates/pm_red_flags.md +262 -0
- claude_mpm/agents/templates/product_owner.json +338 -0
- claude_mpm/agents/templates/project_organizer.json +19 -5
- claude_mpm/agents/templates/prompt-engineer.json +737 -0
- claude_mpm/agents/templates/python_engineer.json +387 -0
- claude_mpm/agents/templates/qa.json +25 -5
- claude_mpm/agents/templates/react_engineer.json +239 -0
- claude_mpm/agents/templates/refactoring_engineer.json +15 -5
- claude_mpm/agents/templates/research.json +46 -21
- claude_mpm/agents/templates/response_format.md +583 -0
- claude_mpm/agents/templates/ruby-engineer.json +280 -0
- claude_mpm/agents/templates/rust_engineer.json +275 -0
- claude_mpm/agents/templates/security.json +59 -10
- claude_mpm/agents/templates/svelte-engineer.json +225 -0
- claude_mpm/agents/templates/tauri_engineer.json +274 -0
- claude_mpm/agents/templates/ticketing.json +16 -7
- claude_mpm/agents/templates/typescript_engineer.json +285 -0
- claude_mpm/agents/templates/validation_templates.md +312 -0
- claude_mpm/agents/templates/vercel_ops_agent.json +164 -33
- claude_mpm/agents/templates/version_control.json +16 -4
- claude_mpm/agents/templates/web_qa.json +167 -21
- claude_mpm/agents/templates/web_ui.json +18 -5
- claude_mpm/cli/__init__.py +38 -378
- claude_mpm/cli/commands/__init__.py +2 -0
- claude_mpm/cli/commands/agent_manager.py +675 -20
- claude_mpm/cli/commands/agent_state_manager.py +186 -0
- claude_mpm/cli/commands/agents.py +722 -150
- claude_mpm/cli/commands/agents_detect.py +380 -0
- claude_mpm/cli/commands/agents_recommend.py +309 -0
- claude_mpm/cli/commands/aggregate.py +10 -6
- claude_mpm/cli/commands/analyze.py +15 -10
- claude_mpm/cli/commands/analyze_code.py +8 -4
- claude_mpm/cli/commands/auto_configure.py +570 -0
- claude_mpm/cli/commands/cleanup.py +12 -12
- claude_mpm/cli/commands/config.py +47 -13
- claude_mpm/cli/commands/configure.py +469 -1064
- claude_mpm/cli/commands/configure_agent_display.py +261 -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 +167 -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 +50 -52
- claude_mpm/cli/commands/debug.py +7 -7
- claude_mpm/cli/commands/doctor.py +43 -7
- claude_mpm/cli/commands/info.py +3 -4
- claude_mpm/cli/commands/local_deploy.py +537 -0
- claude_mpm/cli/commands/mcp.py +17 -10
- claude_mpm/cli/commands/mcp_command_router.py +11 -0
- claude_mpm/cli/commands/mcp_config.py +154 -0
- claude_mpm/cli/commands/mcp_external_commands.py +249 -0
- claude_mpm/cli/commands/mcp_install_commands.py +101 -32
- claude_mpm/cli/commands/mcp_pipx_config.py +2 -2
- claude_mpm/cli/commands/mcp_setup_external.py +868 -0
- claude_mpm/cli/commands/memory.py +55 -21
- claude_mpm/cli/commands/monitor.py +160 -70
- claude_mpm/cli/commands/mpm_init/__init__.py +73 -0
- claude_mpm/cli/commands/mpm_init/core.py +525 -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/modes.py +397 -0
- claude_mpm/cli/commands/mpm_init/prompts.py +442 -0
- claude_mpm/cli/commands/mpm_init_cli.py +396 -0
- claude_mpm/cli/commands/mpm_init_handler.py +114 -4
- claude_mpm/cli/commands/run.py +169 -42
- claude_mpm/cli/commands/search.py +458 -0
- claude_mpm/cli/commands/skills.py +488 -0
- 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 +204 -0
- claude_mpm/cli/helpers.py +105 -0
- claude_mpm/cli/interactive/__init__.py +21 -0
- claude_mpm/cli/interactive/agent_wizard.py +962 -0
- claude_mpm/cli/interactive/skills_wizard.py +491 -0
- claude_mpm/cli/parser.py +79 -2
- claude_mpm/cli/parsers/__init__.py +7 -1
- claude_mpm/cli/parsers/agent_manager_parser.py +161 -1
- claude_mpm/cli/parsers/agents_parser.py +116 -0
- claude_mpm/cli/parsers/auto_configure_parser.py +245 -0
- claude_mpm/cli/parsers/base_parser.py +143 -3
- claude_mpm/cli/parsers/configure_parser.py +11 -15
- claude_mpm/cli/parsers/local_deploy_parser.py +227 -0
- claude_mpm/cli/parsers/mcp_parser.py +15 -0
- claude_mpm/cli/parsers/monitor_parser.py +12 -2
- claude_mpm/cli/parsers/mpm_init_parser.py +179 -9
- claude_mpm/cli/parsers/run_parser.py +5 -0
- claude_mpm/cli/parsers/search_parser.py +245 -0
- claude_mpm/cli/parsers/skills_parser.py +137 -0
- claude_mpm/cli/shared/argument_patterns.py +20 -13
- claude_mpm/cli/shared/base_command.py +2 -2
- claude_mpm/cli/shared/output_formatters.py +28 -19
- claude_mpm/cli/startup.py +562 -0
- claude_mpm/cli/startup_logging.py +179 -13
- claude_mpm/cli/utils.py +53 -2
- claude_mpm/commands/mpm-agents-detect.md +168 -0
- claude_mpm/commands/mpm-agents-recommend.md +214 -0
- claude_mpm/commands/mpm-agents.md +118 -8
- claude_mpm/commands/mpm-auto-configure.md +269 -0
- claude_mpm/commands/mpm-config.md +137 -14
- claude_mpm/commands/mpm-help.md +285 -5
- claude_mpm/commands/mpm-init.md +374 -15
- claude_mpm/commands/mpm-monitor.md +409 -0
- claude_mpm/commands/mpm-organize.md +295 -0
- claude_mpm/commands/mpm-resume.md +372 -0
- claude_mpm/commands/mpm-status.md +71 -9
- claude_mpm/commands/mpm-tickets.md +56 -7
- claude_mpm/commands/mpm-version.md +113 -0
- claude_mpm/commands/mpm.md +2 -0
- claude_mpm/config/agent_config.py +4 -4
- claude_mpm/config/experimental_features.py +7 -7
- claude_mpm/config/model_config.py +428 -0
- claude_mpm/config/paths.py +3 -2
- claude_mpm/config/socketio_config.py +3 -3
- claude_mpm/constants.py +15 -1
- claude_mpm/core/__init__.py +53 -17
- claude_mpm/core/agent_name_normalizer.py +3 -2
- claude_mpm/core/agent_registry.py +2 -2
- claude_mpm/core/agent_session_manager.py +10 -10
- claude_mpm/core/api_validator.py +330 -0
- claude_mpm/core/base_service.py +33 -23
- claude_mpm/core/cache.py +9 -9
- claude_mpm/core/claude_runner.py +19 -8
- claude_mpm/core/config.py +85 -8
- claude_mpm/core/config_aliases.py +7 -6
- claude_mpm/core/constants.py +65 -0
- claude_mpm/core/container.py +11 -5
- claude_mpm/core/enums.py +452 -0
- claude_mpm/core/error_handler.py +623 -0
- claude_mpm/core/factories.py +1 -1
- claude_mpm/core/file_utils.py +764 -0
- claude_mpm/core/framework/__init__.py +38 -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 +288 -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 +210 -0
- claude_mpm/core/framework/loaders/file_loader.py +223 -0
- claude_mpm/core/framework/loaders/instruction_loader.py +161 -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 +321 -1631
- claude_mpm/core/hook_manager.py +8 -6
- claude_mpm/core/injectable_service.py +11 -8
- claude_mpm/core/instruction_reinforcement_hook.py +4 -3
- claude_mpm/core/interactive_session.py +55 -8
- claude_mpm/core/interfaces.py +56 -1
- claude_mpm/core/lazy.py +3 -3
- claude_mpm/core/log_manager.py +92 -23
- claude_mpm/core/logger.py +19 -14
- claude_mpm/core/logging_config.py +6 -2
- claude_mpm/core/logging_utils.py +520 -0
- claude_mpm/core/oneshot_session.py +51 -7
- claude_mpm/core/optimized_agent_loader.py +9 -9
- claude_mpm/core/optimized_startup.py +1 -1
- claude_mpm/core/output_style_manager.py +12 -192
- claude_mpm/core/pm_hook_interceptor.py +18 -12
- claude_mpm/core/service_registry.py +7 -3
- claude_mpm/core/session_manager.py +14 -12
- claude_mpm/core/shared/config_loader.py +1 -1
- claude_mpm/core/socketio_pool.py +15 -15
- claude_mpm/core/tool_access_control.py +3 -2
- claude_mpm/core/types.py +4 -11
- claude_mpm/core/typing_utils.py +7 -6
- claude_mpm/core/unified_agent_registry.py +115 -11
- claude_mpm/core/unified_config.py +6 -6
- claude_mpm/core/unified_paths.py +23 -20
- claude_mpm/dashboard/analysis_runner.py +4 -4
- claude_mpm/dashboard/api/simple_directory.py +261 -0
- claude_mpm/dashboard/react/components/DataInspector/DataInspector.module.css +188 -0
- claude_mpm/dashboard/react/components/EventViewer/EventViewer.module.css +156 -0
- claude_mpm/dashboard/react/components/shared/ConnectionStatus.module.css +38 -0
- claude_mpm/dashboard/react/components/shared/FilterBar.module.css +92 -0
- claude_mpm/dashboard/static/archive/activity_dashboard_fixed.html +248 -0
- claude_mpm/dashboard/static/archive/activity_dashboard_test.html +61 -0
- claude_mpm/dashboard/static/archive/test_activity_connection.html +179 -0
- claude_mpm/dashboard/static/archive/test_claude_tree_tab.html +68 -0
- claude_mpm/dashboard/static/archive/test_dashboard.html +409 -0
- claude_mpm/dashboard/static/archive/test_dashboard_fixed.html +519 -0
- claude_mpm/dashboard/static/archive/test_dashboard_verification.html +181 -0
- claude_mpm/dashboard/static/archive/test_file_data.html +315 -0
- claude_mpm/dashboard/static/archive/test_file_tree_empty_state.html +243 -0
- claude_mpm/dashboard/static/archive/test_file_tree_fix.html +234 -0
- claude_mpm/dashboard/static/archive/test_file_tree_rename.html +117 -0
- claude_mpm/dashboard/static/archive/test_file_tree_tab.html +115 -0
- claude_mpm/dashboard/static/archive/test_file_viewer.html +224 -0
- claude_mpm/dashboard/static/archive/test_final_activity.html +220 -0
- claude_mpm/dashboard/static/archive/test_tab_fix.html +139 -0
- claude_mpm/dashboard/static/built/assets/events.DjpNxWNo.css +1 -0
- claude_mpm/dashboard/static/built/components/activity-tree.js +1 -1
- claude_mpm/dashboard/static/built/components/agent-hierarchy.js +777 -0
- claude_mpm/dashboard/static/built/components/agent-inference.js +1 -1
- claude_mpm/dashboard/static/built/components/build-tracker.js +333 -0
- claude_mpm/dashboard/static/built/components/code-simple.js +857 -0
- claude_mpm/dashboard/static/built/components/code-tree/tree-breadcrumb.js +353 -0
- claude_mpm/dashboard/static/built/components/code-tree/tree-constants.js +235 -0
- claude_mpm/dashboard/static/built/components/code-tree/tree-search.js +409 -0
- claude_mpm/dashboard/static/built/components/code-tree/tree-utils.js +435 -0
- claude_mpm/dashboard/static/built/components/code-tree.js +1 -1
- claude_mpm/dashboard/static/built/components/code-viewer.js +1 -1
- claude_mpm/dashboard/static/built/components/connection-debug.js +654 -0
- claude_mpm/dashboard/static/built/components/diff-viewer.js +891 -0
- claude_mpm/dashboard/static/built/components/event-processor.js +1 -1
- claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
- claude_mpm/dashboard/static/built/components/export-manager.js +1 -1
- claude_mpm/dashboard/static/built/components/file-change-tracker.js +443 -0
- claude_mpm/dashboard/static/built/components/file-change-viewer.js +690 -0
- claude_mpm/dashboard/static/built/components/file-tool-tracker.js +1 -1
- claude_mpm/dashboard/static/built/components/file-viewer.js +2 -0
- claude_mpm/dashboard/static/built/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/built/components/nav-bar.js +145 -0
- claude_mpm/dashboard/static/built/components/page-structure.js +429 -0
- claude_mpm/dashboard/static/built/components/session-manager.js +1 -1
- claude_mpm/dashboard/static/built/components/unified-data-viewer.js +1 -1
- claude_mpm/dashboard/static/built/components/working-directory.js +1 -1
- claude_mpm/dashboard/static/built/connection-manager.js +536 -0
- claude_mpm/dashboard/static/built/dashboard.js +1 -1
- claude_mpm/dashboard/static/built/extension-error-handler.js +164 -0
- claude_mpm/dashboard/static/built/react/events.js +30 -0
- claude_mpm/dashboard/static/built/shared/dom-helpers.js +396 -0
- claude_mpm/dashboard/static/built/shared/event-bus.js +330 -0
- claude_mpm/dashboard/static/built/shared/event-filter-service.js +540 -0
- claude_mpm/dashboard/static/built/shared/logger.js +385 -0
- claude_mpm/dashboard/static/built/shared/page-structure.js +249 -0
- claude_mpm/dashboard/static/built/shared/tooltip-service.js +253 -0
- claude_mpm/dashboard/static/built/socket-client.js +1 -1
- claude_mpm/dashboard/static/built/tab-isolation-fix.js +185 -0
- claude_mpm/dashboard/static/css/dashboard.css +588 -6
- claude_mpm/dashboard/static/dist/assets/events.DjpNxWNo.css +1 -0
- claude_mpm/dashboard/static/dist/components/activity-tree.js +1 -1
- claude_mpm/dashboard/static/dist/components/agent-inference.js +1 -1
- claude_mpm/dashboard/static/dist/components/code-tree.js +1 -1
- claude_mpm/dashboard/static/dist/components/code-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/components/event-processor.js +1 -1
- claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/components/export-manager.js +1 -1
- claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +1 -1
- claude_mpm/dashboard/static/dist/components/file-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/components/session-manager.js +1 -1
- claude_mpm/dashboard/static/dist/components/unified-data-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/components/working-directory.js +1 -1
- claude_mpm/dashboard/static/dist/dashboard.js +1 -1
- claude_mpm/dashboard/static/dist/react/events.js +30 -0
- claude_mpm/dashboard/static/dist/socket-client.js +1 -1
- claude_mpm/dashboard/static/events.html +607 -0
- claude_mpm/dashboard/static/index.html +635 -0
- claude_mpm/dashboard/static/js/components/activity-tree.js +3 -17
- claude_mpm/dashboard/static/js/components/agent-hierarchy.js +4 -1
- claude_mpm/dashboard/static/js/components/agent-inference.js +3 -0
- claude_mpm/dashboard/static/js/components/build-tracker.js +8 -0
- claude_mpm/dashboard/static/js/components/code-simple.js +857 -0
- claude_mpm/dashboard/static/js/components/diff-viewer.js +891 -0
- claude_mpm/dashboard/static/js/components/event-processor.js +3 -0
- claude_mpm/dashboard/static/js/components/event-viewer.js +39 -2
- claude_mpm/dashboard/static/js/components/export-manager.js +3 -0
- claude_mpm/dashboard/static/js/components/file-change-tracker.js +443 -0
- claude_mpm/dashboard/static/js/components/file-change-viewer.js +690 -0
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +30 -10
- claude_mpm/dashboard/static/js/components/file-viewer.js +580 -0
- claude_mpm/dashboard/static/js/components/module-viewer.js +26 -0
- claude_mpm/dashboard/static/js/components/session-manager.js +7 -7
- claude_mpm/dashboard/static/js/components/socket-manager.js +4 -0
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +356 -41
- claude_mpm/dashboard/static/js/components/unified-data-viewer.js +455 -23
- claude_mpm/dashboard/static/js/components/working-directory.js +44 -9
- claude_mpm/dashboard/static/js/dashboard.js +245 -132
- claude_mpm/dashboard/static/js/shared/dom-helpers.js +396 -0
- claude_mpm/dashboard/static/js/shared/event-bus.js +330 -0
- claude_mpm/dashboard/static/js/shared/logger.js +385 -0
- claude_mpm/dashboard/static/js/shared/tooltip-service.js +253 -0
- claude_mpm/dashboard/static/js/socket-client.js +49 -22
- claude_mpm/dashboard/static/js/stores/dashboard-store.js +562 -0
- claude_mpm/dashboard/static/js/tab-isolation-fix.js +185 -0
- claude_mpm/dashboard/static/legacy/activity.html +736 -0
- claude_mpm/dashboard/static/legacy/agents.html +786 -0
- claude_mpm/dashboard/static/legacy/files.html +747 -0
- claude_mpm/dashboard/static/legacy/tools.html +831 -0
- claude_mpm/dashboard/static/monitors.html +431 -0
- claude_mpm/dashboard/static/production/events.html +659 -0
- claude_mpm/dashboard/static/production/main.html +698 -0
- claude_mpm/dashboard/static/production/monitors.html +483 -0
- claude_mpm/dashboard/static/socket.io.min.js +7 -0
- claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +7 -0
- claude_mpm/dashboard/static/test-archive/dashboard.html +635 -0
- claude_mpm/dashboard/static/test-archive/debug-events.html +147 -0
- claude_mpm/dashboard/static/test-archive/test-navigation.html +256 -0
- claude_mpm/dashboard/static/test-archive/test-react-exports.html +180 -0
- claude_mpm/dashboard/static/test-archive/test_debug.html +25 -0
- claude_mpm/dashboard/templates/code_simple.html +153 -0
- claude_mpm/dashboard/templates/index.html +112 -109
- claude_mpm/experimental/cli_enhancements.py +4 -2
- claude_mpm/generators/agent_profile_generator.py +5 -3
- claude_mpm/hooks/__init__.py +37 -1
- claude_mpm/hooks/base_hook.py +5 -4
- claude_mpm/hooks/claude_hooks/connection_pool.py +4 -4
- claude_mpm/hooks/claude_hooks/event_handlers.py +21 -18
- claude_mpm/hooks/claude_hooks/hook_handler.py +29 -22
- claude_mpm/hooks/claude_hooks/installer.py +67 -22
- claude_mpm/hooks/claude_hooks/memory_integration.py +3 -3
- claude_mpm/hooks/claude_hooks/response_tracking.py +57 -17
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +62 -64
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +140 -76
- claude_mpm/hooks/claude_hooks/services/state_manager.py +11 -9
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +3 -3
- claude_mpm/hooks/failure_learning/__init__.py +60 -0
- claude_mpm/hooks/failure_learning/failure_detection_hook.py +235 -0
- claude_mpm/hooks/failure_learning/fix_detection_hook.py +217 -0
- claude_mpm/hooks/failure_learning/learning_extraction_hook.py +286 -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 +183 -0
- claude_mpm/hooks/memory_integration_hook.py +1 -1
- 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 +8 -5
- claude_mpm/hooks/validation_hooks.py +3 -3
- claude_mpm/init.py +23 -4
- claude_mpm/models/agent_session.py +8 -6
- claude_mpm/models/resume_log.py +340 -0
- claude_mpm/scripts/claude-hook-handler.sh +33 -7
- claude_mpm/scripts/launch_monitor.py +85 -0
- claude_mpm/scripts/mcp_server.py +3 -5
- claude_mpm/scripts/mpm_doctor.py +3 -2
- claude_mpm/scripts/socketio_daemon.py +159 -512
- claude_mpm/services/__init__.py +144 -160
- claude_mpm/services/agents/__init__.py +18 -5
- claude_mpm/services/agents/agent_builder.py +13 -11
- claude_mpm/services/agents/auto_config_manager.py +796 -0
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +1 -1
- claude_mpm/services/agents/deployment/agent_deployment.py +38 -15
- claude_mpm/services/agents/deployment/agent_discovery_service.py +125 -7
- claude_mpm/services/agents/deployment/agent_filesystem_manager.py +5 -5
- claude_mpm/services/agents/deployment/agent_format_converter.py +56 -12
- claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +4 -2
- claude_mpm/services/agents/deployment/agent_operation_service.py +2 -2
- claude_mpm/services/agents/deployment/agent_record_service.py +4 -4
- claude_mpm/services/agents/deployment/agent_state_service.py +2 -2
- claude_mpm/services/agents/deployment/agent_template_builder.py +715 -47
- claude_mpm/services/agents/deployment/agent_validator.py +31 -7
- claude_mpm/services/agents/deployment/agent_version_manager.py +8 -5
- claude_mpm/services/agents/deployment/agent_versioning.py +1 -1
- claude_mpm/services/agents/deployment/async_agent_deployment.py +1 -1
- claude_mpm/services/agents/deployment/deployment_config_loader.py +131 -7
- claude_mpm/services/agents/deployment/deployment_type_detector.py +10 -14
- claude_mpm/services/agents/deployment/deployment_wrapper.py +58 -0
- claude_mpm/services/agents/deployment/interface_adapter.py +3 -2
- claude_mpm/services/agents/deployment/local_template_deployment.py +360 -0
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +134 -38
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +8 -7
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +7 -16
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +4 -3
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +7 -5
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +6 -5
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +9 -6
- claude_mpm/services/agents/deployment/system_instructions_deployer.py +9 -6
- claude_mpm/services/agents/deployment/validation/__init__.py +3 -1
- claude_mpm/services/agents/deployment/validation/template_validator.py +64 -44
- claude_mpm/services/agents/deployment/validation/validation_result.py +1 -9
- claude_mpm/services/agents/loading/agent_profile_loader.py +10 -9
- claude_mpm/services/agents/loading/base_agent_manager.py +16 -6
- claude_mpm/services/agents/loading/framework_agent_loader.py +2 -2
- claude_mpm/services/agents/local_template_manager.py +744 -0
- claude_mpm/services/agents/management/agent_capabilities_generator.py +3 -2
- claude_mpm/services/agents/management/agent_management_service.py +5 -5
- claude_mpm/services/agents/memory/agent_memory_manager.py +32 -29
- claude_mpm/services/agents/memory/content_manager.py +17 -9
- claude_mpm/services/agents/memory/memory_categorization_service.py +4 -2
- claude_mpm/services/agents/memory/memory_file_service.py +32 -6
- claude_mpm/services/agents/memory/memory_format_service.py +6 -4
- claude_mpm/services/agents/memory/memory_limits_service.py +4 -2
- claude_mpm/services/agents/memory/template_generator.py +3 -3
- claude_mpm/services/agents/observers.py +547 -0
- claude_mpm/services/agents/recommender.py +615 -0
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +3 -3
- claude_mpm/services/agents/registry/modification_tracker.py +30 -19
- claude_mpm/services/async_session_logger.py +141 -98
- claude_mpm/services/claude_session_logger.py +82 -74
- claude_mpm/services/cli/agent_cleanup_service.py +5 -0
- claude_mpm/services/cli/agent_listing_service.py +5 -5
- claude_mpm/services/cli/agent_validation_service.py +3 -1
- claude_mpm/services/cli/memory_crud_service.py +12 -7
- claude_mpm/services/cli/memory_output_formatter.py +2 -2
- claude_mpm/services/cli/resume_service.py +617 -0
- claude_mpm/services/cli/session_manager.py +104 -13
- 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 +13 -10
- claude_mpm/services/cli/unified_dashboard_manager.py +439 -0
- claude_mpm/services/command_deployment_service.py +9 -7
- claude_mpm/services/command_handler_service.py +11 -5
- claude_mpm/services/core/__init__.py +33 -1
- claude_mpm/services/core/base.py +26 -11
- claude_mpm/services/core/interfaces/__init__.py +90 -3
- claude_mpm/services/core/interfaces/agent.py +184 -0
- claude_mpm/services/core/interfaces/health.py +172 -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/stability.py +260 -0
- claude_mpm/services/core/interfaces.py +56 -1
- claude_mpm/services/core/memory_manager.py +92 -47
- claude_mpm/services/core/models/__init__.py +79 -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 +36 -14
- claude_mpm/services/diagnostics/__init__.py +2 -2
- claude_mpm/services/diagnostics/checks/__init__.py +4 -2
- claude_mpm/services/diagnostics/checks/agent_check.py +30 -32
- claude_mpm/services/diagnostics/checks/claude_code_check.py +270 -0
- claude_mpm/services/diagnostics/checks/common_issues_check.py +28 -27
- claude_mpm/services/diagnostics/checks/configuration_check.py +26 -25
- claude_mpm/services/diagnostics/checks/filesystem_check.py +18 -17
- claude_mpm/services/diagnostics/checks/installation_check.py +165 -60
- claude_mpm/services/diagnostics/checks/instructions_check.py +20 -19
- claude_mpm/services/diagnostics/checks/mcp_check.py +57 -43
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +1066 -0
- claude_mpm/services/diagnostics/checks/monitor_check.py +24 -23
- claude_mpm/services/diagnostics/checks/startup_log_check.py +14 -11
- claude_mpm/services/diagnostics/diagnostic_runner.py +22 -13
- claude_mpm/services/diagnostics/doctor_reporter.py +275 -47
- claude_mpm/services/diagnostics/models.py +37 -21
- claude_mpm/services/event_aggregator.py +5 -3
- claude_mpm/services/event_bus/direct_relay.py +8 -4
- claude_mpm/services/event_bus/event_bus.py +51 -9
- claude_mpm/services/event_bus/relay.py +33 -14
- claude_mpm/services/events/consumers/dead_letter.py +7 -5
- claude_mpm/services/events/core.py +5 -6
- claude_mpm/services/events/producers/hook.py +6 -6
- claude_mpm/services/events/producers/system.py +8 -8
- claude_mpm/services/exceptions.py +5 -5
- claude_mpm/services/framework_claude_md_generator/__init__.py +1 -1
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +5 -5
- claude_mpm/services/framework_claude_md_generator/content_validator.py +2 -2
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +3 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +2 -2
- claude_mpm/services/framework_claude_md_generator/version_manager.py +1 -1
- claude_mpm/services/hook_installer_service.py +506 -0
- claude_mpm/services/hook_service.py +5 -6
- claude_mpm/services/infrastructure/context_preservation.py +13 -11
- claude_mpm/services/infrastructure/daemon_manager.py +9 -9
- claude_mpm/services/infrastructure/logging.py +2 -2
- claude_mpm/services/infrastructure/monitoring/__init__.py +1 -1
- claude_mpm/services/infrastructure/monitoring/aggregator.py +12 -12
- claude_mpm/services/infrastructure/monitoring/base.py +5 -13
- claude_mpm/services/infrastructure/monitoring/network.py +7 -6
- claude_mpm/services/infrastructure/monitoring/process.py +13 -12
- claude_mpm/services/infrastructure/monitoring/resources.py +8 -7
- claude_mpm/services/infrastructure/monitoring/service.py +16 -15
- claude_mpm/services/infrastructure/resume_log_generator.py +439 -0
- claude_mpm/services/local_ops/__init__.py +165 -0
- claude_mpm/services/local_ops/crash_detector.py +257 -0
- claude_mpm/services/local_ops/health_checks/__init__.py +28 -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 +430 -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 +1612 -0
- claude_mpm/services/mcp_gateway/__init__.py +97 -93
- claude_mpm/services/mcp_gateway/auto_configure.py +43 -38
- claude_mpm/services/mcp_gateway/config/config_loader.py +3 -3
- claude_mpm/services/mcp_gateway/config/configuration.py +23 -4
- claude_mpm/services/mcp_gateway/core/__init__.py +1 -2
- claude_mpm/services/mcp_gateway/core/base.py +20 -33
- claude_mpm/services/mcp_gateway/core/process_pool.py +585 -31
- claude_mpm/services/mcp_gateway/core/singleton_manager.py +2 -2
- claude_mpm/services/mcp_gateway/core/startup_verification.py +3 -3
- claude_mpm/services/mcp_gateway/main.py +90 -15
- claude_mpm/services/mcp_gateway/registry/service_registry.py +4 -2
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +12 -9
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +4 -4
- claude_mpm/services/mcp_gateway/server/stdio_server.py +9 -15
- claude_mpm/services/mcp_gateway/tools/__init__.py +14 -2
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +15 -15
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +10 -9
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +654 -0
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +36 -34
- claude_mpm/services/mcp_gateway/tools/hello_world.py +8 -8
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +551 -0
- claude_mpm/services/mcp_gateway/utils/__init__.py +14 -0
- claude_mpm/services/mcp_gateway/utils/package_version_checker.py +160 -0
- claude_mpm/services/mcp_gateway/utils/update_preferences.py +170 -0
- claude_mpm/services/mcp_service_verifier.py +729 -0
- claude_mpm/services/memory/builder.py +9 -8
- claude_mpm/services/memory/cache/shared_prompt_cache.py +2 -1
- claude_mpm/services/memory/cache/simple_cache.py +2 -2
- claude_mpm/services/memory/failure_tracker.py +578 -0
- claude_mpm/services/memory/indexed_memory.py +8 -8
- claude_mpm/services/memory/optimizer.py +8 -9
- claude_mpm/services/memory/router.py +3 -3
- claude_mpm/services/memory_hook_service.py +165 -4
- 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 +453 -0
- claude_mpm/services/model/ollama_provider.py +415 -0
- claude_mpm/services/monitor/__init__.py +20 -0
- claude_mpm/services/monitor/daemon.py +671 -0
- claude_mpm/services/monitor/daemon_manager.py +963 -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 +724 -0
- claude_mpm/services/monitor/server.py +817 -0
- claude_mpm/services/monitor_build_service.py +2 -2
- claude_mpm/services/native_agent_converter.py +356 -0
- claude_mpm/services/orphan_detection.py +786 -0
- claude_mpm/services/port_manager.py +2 -2
- claude_mpm/services/project/__init__.py +23 -0
- claude_mpm/services/project/analyzer.py +3 -3
- claude_mpm/services/project/architecture_analyzer.py +5 -5
- claude_mpm/services/project/archive_manager.py +1045 -0
- claude_mpm/services/project/dependency_analyzer.py +4 -4
- claude_mpm/services/project/detection_strategies.py +719 -0
- claude_mpm/services/project/documentation_manager.py +553 -0
- claude_mpm/services/project/enhanced_analyzer.py +572 -0
- claude_mpm/services/project/metrics_collector.py +4 -4
- claude_mpm/services/project/project_organizer.py +1005 -0
- claude_mpm/services/project/registry.py +13 -7
- claude_mpm/services/project/toolchain_analyzer.py +581 -0
- claude_mpm/services/project_port_allocator.py +596 -0
- claude_mpm/services/response_tracker.py +21 -10
- claude_mpm/services/runner_configuration_service.py +1 -0
- claude_mpm/services/self_upgrade_service.py +500 -0
- claude_mpm/services/session_management_service.py +7 -5
- claude_mpm/services/session_manager.py +380 -0
- claude_mpm/services/shared/__init__.py +2 -1
- claude_mpm/services/shared/async_service_base.py +16 -27
- claude_mpm/services/shared/config_service_base.py +17 -14
- claude_mpm/services/shared/lifecycle_service_base.py +1 -14
- claude_mpm/services/shared/service_factory.py +8 -5
- claude_mpm/services/socketio/client_proxy.py +60 -5
- claude_mpm/services/socketio/dashboard_server.py +361 -0
- claude_mpm/services/socketio/event_normalizer.py +10 -6
- claude_mpm/services/socketio/handlers/__init__.py +5 -2
- claude_mpm/services/socketio/handlers/base.py +2 -2
- claude_mpm/services/socketio/handlers/code_analysis.py +90 -27
- claude_mpm/services/socketio/handlers/connection.py +21 -40
- claude_mpm/services/socketio/handlers/connection_handler.py +13 -10
- claude_mpm/services/socketio/handlers/file.py +46 -10
- claude_mpm/services/socketio/handlers/git.py +8 -8
- claude_mpm/services/socketio/handlers/hook.py +29 -17
- claude_mpm/services/socketio/handlers/registry.py +4 -2
- claude_mpm/services/socketio/monitor_client.py +364 -0
- claude_mpm/services/socketio/server/broadcaster.py +9 -7
- claude_mpm/services/socketio/server/connection_manager.py +2 -2
- claude_mpm/services/socketio/server/core.py +141 -4
- claude_mpm/services/socketio/server/eventbus_integration.py +20 -14
- claude_mpm/services/socketio/server/main.py +23 -21
- claude_mpm/services/socketio_client_manager.py +4 -4
- claude_mpm/services/subprocess_launcher_service.py +19 -15
- claude_mpm/services/system_instructions_service.py +2 -2
- claude_mpm/services/ticket_services/formatter_service.py +1 -1
- claude_mpm/services/ticket_services/validation_service.py +5 -5
- 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 +903 -0
- claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +746 -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 +475 -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 +470 -0
- claude_mpm/services/utility_service.py +6 -3
- claude_mpm/services/version_control/branch_strategy.py +2 -2
- claude_mpm/services/version_control/conflict_resolution.py +8 -4
- claude_mpm/services/version_control/git_operations.py +26 -24
- claude_mpm/services/version_control/semantic_versioning.py +14 -14
- claude_mpm/services/version_control/version_parser.py +14 -11
- claude_mpm/services/version_service.py +104 -1
- 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/collaboration/brainstorming/SKILL.md +79 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/SKILL.md +178 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/agent-prompts.md +577 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/coordination-patterns.md +467 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/examples.md +537 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/troubleshooting.md +730 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/SKILL.md +112 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/references/code-reviewer-template.md +146 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/references/review-examples.md +412 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/SKILL.md +81 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/references/best-practices.md +362 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/references/plan-structure-templates.md +312 -0
- claude_mpm/skills/bundled/database-migration.md +199 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/SKILL.md +152 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/advanced-techniques.md +668 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/examples.md +587 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/integration.md +438 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/tracing-techniques.md +391 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/CREATION-LOG.md +119 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/SKILL.md +148 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/anti-patterns.md +483 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/examples.md +452 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/troubleshooting.md +449 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/workflow.md +411 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-academic.md +14 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-1.md +58 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-2.md +68 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-3.md +69 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/SKILL.md +131 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/gate-function.md +325 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/integration-and-workflows.md +490 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/red-flags-and-failures.md +425 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/verification-patterns.md +499 -0
- claude_mpm/skills/bundled/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/artifacts-builder/SKILL.md +86 -0
- claude_mpm/skills/bundled/main/internal-comms/SKILL.md +43 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/3p-updates.md +47 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/company-newsletter.md +65 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/faq-answers.md +30 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/general-comms.md +16 -0
- claude_mpm/skills/bundled/main/mcp-builder/SKILL.md +160 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/design_principles.md +412 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/evaluation.md +602 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/mcp_best_practices.md +915 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/node_mcp_server.md +916 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/python_mcp_server.md +752 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/workflow.md +1237 -0
- claude_mpm/skills/bundled/main/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/SKILL.md +189 -0
- claude_mpm/skills/bundled/main/skill-creator/references/best-practices.md +500 -0
- claude_mpm/skills/bundled/main/skill-creator/references/creation-workflow.md +464 -0
- claude_mpm/skills/bundled/main/skill-creator/references/examples.md +619 -0
- claude_mpm/skills/bundled/main/skill-creator/references/progressive-disclosure.md +437 -0
- claude_mpm/skills/bundled/main/skill-creator/references/skill-structure.md +231 -0
- claude_mpm/skills/bundled/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/php/espocrm-development/SKILL.md +170 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/architecture.md +602 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/common-tasks.md +821 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/development-workflow.md +742 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/frontend-customization.md +726 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/hooks-and-services.md +764 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/testing-debugging.md +831 -0
- claude_mpm/skills/bundled/refactoring-patterns.md +180 -0
- claude_mpm/skills/bundled/rust/desktop-applications/SKILL.md +226 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/architecture-patterns.md +901 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/native-gui-frameworks.md +901 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/platform-integration.md +775 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/state-management.md +937 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/tauri-framework.md +770 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/testing-deployment.md +961 -0
- claude_mpm/skills/bundled/security-scanning.md +327 -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/condition-based-waiting/SKILL.md +119 -0
- claude_mpm/skills/bundled/testing/condition-based-waiting/references/patterns-and-implementation.md +253 -0
- claude_mpm/skills/bundled/testing/test-driven-development/SKILL.md +145 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/anti-patterns.md +543 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/examples.md +741 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/integration.md +470 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/philosophy.md +458 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/workflow.md +639 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/SKILL.md +140 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/completeness-anti-patterns.md +572 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/core-anti-patterns.md +411 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/detection-guide.md +569 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/tdd-connection.md +695 -0
- claude_mpm/skills/bundled/testing/webapp-testing/SKILL.md +184 -0
- claude_mpm/skills/bundled/testing/webapp-testing/decision-tree.md +459 -0
- claude_mpm/skills/bundled/testing/webapp-testing/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/playwright-patterns.md +479 -0
- claude_mpm/skills/bundled/testing/webapp-testing/reconnaissance-pattern.md +687 -0
- claude_mpm/skills/bundled/testing/webapp-testing/scripts/with_server.py +129 -0
- claude_mpm/skills/bundled/testing/webapp-testing/server-management.md +758 -0
- claude_mpm/skills/bundled/testing/webapp-testing/troubleshooting.md +868 -0
- claude_mpm/skills/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 +310 -0
- claude_mpm/skills/skills_registry.py +348 -0
- claude_mpm/skills/skills_service.py +739 -0
- claude_mpm/storage/state_storage.py +31 -31
- claude_mpm/tools/__main__.py +1 -1
- 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 +6 -6
- claude_mpm/tools/code_tree_events.py +14 -10
- claude_mpm/tools/socketio_debug.py +11 -11
- claude_mpm/utils/agent_dependency_loader.py +108 -27
- claude_mpm/utils/common.py +544 -0
- claude_mpm/utils/config_manager.py +12 -6
- claude_mpm/utils/database_connector.py +298 -0
- claude_mpm/utils/dependency_cache.py +2 -2
- claude_mpm/utils/dependency_strategies.py +15 -10
- claude_mpm/utils/display_helper.py +260 -0
- claude_mpm/utils/environment_context.py +4 -3
- claude_mpm/utils/error_handler.py +5 -3
- claude_mpm/utils/file_utils.py +13 -14
- claude_mpm/utils/git_analyzer.py +407 -0
- claude_mpm/utils/log_cleanup.py +627 -0
- claude_mpm/utils/path_operations.py +7 -4
- claude_mpm/utils/robust_installer.py +133 -24
- claude_mpm/utils/session_logging.py +2 -2
- claude_mpm/utils/subprocess_utils.py +9 -8
- claude_mpm/validation/agent_validator.py +6 -6
- claude_mpm/validation/frontmatter_validator.py +6 -6
- claude_mpm-4.24.0.dist-info/METADATA +675 -0
- claude_mpm-4.24.0.dist-info/RECORD +1018 -0
- {claude_mpm-4.1.26.dist-info → claude_mpm-4.24.0.dist-info}/entry_points.txt +1 -0
- claude_mpm/agents/INSTRUCTIONS.md +0 -261
- claude_mpm/agents/templates/agent-manager.md +0 -619
- claude_mpm/cli/commands/configure_tui.py +0 -1927
- claude_mpm/cli/commands/mpm_init.py +0 -594
- claude_mpm/cli/commands/socketio_monitor.py +0 -233
- claude_mpm/dashboard/static/css/code-tree.css +0 -1408
- claude_mpm/dashboard/static/js/components/code-tree.js +0 -3220
- claude_mpm/dashboard/static/js/components/code-viewer.js +0 -480
- claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +0 -425
- claude_mpm/hooks/claude_hooks/hook_handler_original.py +0 -1040
- claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +0 -347
- claude_mpm/scripts/socketio_daemon_hardened.py +0 -937
- claude_mpm/scripts/socketio_daemon_wrapper.py +0 -78
- claude_mpm/scripts/socketio_server_manager.py +0 -349
- claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +0 -575
- claude_mpm/services/cli/dashboard_launcher.py +0 -423
- claude_mpm/services/cli/socketio_manager.py +0 -537
- claude_mpm/services/diagnostics/checks/claude_desktop_check.py +0 -286
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +0 -645
- claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +0 -602
- claude_mpm/services/project/analyzer_refactored.py +0 -450
- claude_mpm/tools/code_tree_analyzer.py +0 -1693
- claude_mpm-4.1.26.dist-info/METADATA +0 -332
- claude_mpm-4.1.26.dist-info/RECORD +0 -606
- {claude_mpm-4.1.26.dist-info → claude_mpm-4.24.0.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.26.dist-info → claude_mpm-4.24.0.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.26.dist-info → claude_mpm-4.24.0.dist-info}/top_level.txt +0 -0
|
@@ -1,3220 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Code Tree Component
|
|
3
|
-
*
|
|
4
|
-
* D3.js-based tree visualization for displaying AST-based code structure.
|
|
5
|
-
* Shows modules, classes, functions, and methods with complexity-based coloring.
|
|
6
|
-
* Provides real-time updates during code analysis.
|
|
7
|
-
*
|
|
8
|
-
* ===== CACHE CLEAR INSTRUCTIONS =====
|
|
9
|
-
* If tree still moves/centers after update:
|
|
10
|
-
* 1. Hard refresh: Ctrl+Shift+R (Windows/Linux) or Cmd+Shift+R (Mac)
|
|
11
|
-
* 2. Or open DevTools (F12) → Network tab → Check "Disable cache"
|
|
12
|
-
* 3. Or clear browser cache: Ctrl+Shift+Delete → Clear cached images and files
|
|
13
|
-
*
|
|
14
|
-
* Version: 2025-08-29T15:30:00Z - ALL CENTERING REMOVED
|
|
15
|
-
* Last Update: Completely disabled tree centering/movement on node clicks
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
class CodeTree {
|
|
19
|
-
constructor() {
|
|
20
|
-
this.container = null;
|
|
21
|
-
this.svg = null;
|
|
22
|
-
this.treeData = null;
|
|
23
|
-
this.root = null;
|
|
24
|
-
this.treeLayout = null;
|
|
25
|
-
this.treeGroup = null;
|
|
26
|
-
this.nodes = new Map();
|
|
27
|
-
this.stats = {
|
|
28
|
-
files: 0,
|
|
29
|
-
classes: 0,
|
|
30
|
-
functions: 0,
|
|
31
|
-
methods: 0,
|
|
32
|
-
lines: 0
|
|
33
|
-
};
|
|
34
|
-
// Radial layout settings
|
|
35
|
-
this.isRadialLayout = true; // Toggle for radial vs linear layout
|
|
36
|
-
this.margin = {top: 20, right: 20, bottom: 20, left: 20};
|
|
37
|
-
this.width = 960 - this.margin.left - this.margin.right;
|
|
38
|
-
this.height = 600 - this.margin.top - this.margin.bottom;
|
|
39
|
-
this.radius = Math.min(this.width, this.height) / 2;
|
|
40
|
-
this.nodeId = 0;
|
|
41
|
-
this.duration = 750;
|
|
42
|
-
this.languageFilter = 'all';
|
|
43
|
-
this.searchTerm = '';
|
|
44
|
-
this.tooltip = null;
|
|
45
|
-
this.initialized = false;
|
|
46
|
-
this.analyzing = false;
|
|
47
|
-
this.selectedNode = null;
|
|
48
|
-
this.socket = null;
|
|
49
|
-
this.autoDiscovered = false; // Track if auto-discovery has been done
|
|
50
|
-
this.zoom = null; // Store zoom behavior
|
|
51
|
-
this.activeNode = null; // Track currently active node
|
|
52
|
-
this.loadingNodes = new Set(); // Track nodes that are loading
|
|
53
|
-
this.bulkLoadMode = false; // Track bulk loading preference
|
|
54
|
-
this.expandedPaths = new Set(); // Track which paths are expanded
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Initialize the code tree visualization
|
|
59
|
-
*/
|
|
60
|
-
initialize() {
|
|
61
|
-
if (this.initialized) {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
this.container = document.getElementById('code-tree-container');
|
|
66
|
-
if (!this.container) {
|
|
67
|
-
console.error('Code tree container not found');
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Check if tab is visible
|
|
72
|
-
const tabPanel = document.getElementById('code-tab');
|
|
73
|
-
if (!tabPanel) {
|
|
74
|
-
console.error('Code tab panel not found');
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Check if working directory is set
|
|
79
|
-
const workingDir = this.getWorkingDirectory();
|
|
80
|
-
if (!workingDir || workingDir === 'Loading...' || workingDir === 'Not selected') {
|
|
81
|
-
this.showNoWorkingDirectoryMessage();
|
|
82
|
-
this.initialized = true;
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Initialize always
|
|
87
|
-
this.setupControls();
|
|
88
|
-
this.initializeTreeData();
|
|
89
|
-
this.subscribeToEvents();
|
|
90
|
-
|
|
91
|
-
// Set initial status message
|
|
92
|
-
const breadcrumbContent = document.getElementById('breadcrumb-content');
|
|
93
|
-
if (breadcrumbContent && !this.analyzing) {
|
|
94
|
-
this.updateActivityTicker('Loading project structure...', 'info');
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Only create visualization if tab is visible
|
|
98
|
-
if (tabPanel.classList.contains('active')) {
|
|
99
|
-
this.createVisualization();
|
|
100
|
-
if (this.root && this.svg) {
|
|
101
|
-
this.update(this.root);
|
|
102
|
-
}
|
|
103
|
-
// Auto-discover root level when tab is active
|
|
104
|
-
this.autoDiscoverRootLevel();
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
this.initialized = true;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Render visualization when tab becomes visible
|
|
112
|
-
*/
|
|
113
|
-
renderWhenVisible() {
|
|
114
|
-
// Check if working directory is set
|
|
115
|
-
const workingDir = this.getWorkingDirectory();
|
|
116
|
-
if (!workingDir || workingDir === 'Loading...' || workingDir === 'Not selected') {
|
|
117
|
-
this.showNoWorkingDirectoryMessage();
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// If no directory message is shown, remove it
|
|
122
|
-
this.removeNoWorkingDirectoryMessage();
|
|
123
|
-
|
|
124
|
-
if (!this.initialized) {
|
|
125
|
-
this.initialize();
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (!this.svg) {
|
|
130
|
-
this.createVisualization();
|
|
131
|
-
if (this.svg && this.treeGroup) {
|
|
132
|
-
this.update(this.root);
|
|
133
|
-
}
|
|
134
|
-
} else {
|
|
135
|
-
// Force update with current data
|
|
136
|
-
if (this.root && this.svg) {
|
|
137
|
-
this.update(this.root);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Auto-discover root level if not done yet
|
|
142
|
-
if (!this.autoDiscovered) {
|
|
143
|
-
this.autoDiscoverRootLevel();
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Set up control event handlers
|
|
149
|
-
*/
|
|
150
|
-
setupControls() {
|
|
151
|
-
// Remove analyze and cancel button handlers since they're no longer in the UI
|
|
152
|
-
|
|
153
|
-
const languageFilter = document.getElementById('language-filter');
|
|
154
|
-
if (languageFilter) {
|
|
155
|
-
languageFilter.addEventListener('change', (e) => {
|
|
156
|
-
this.languageFilter = e.target.value;
|
|
157
|
-
this.filterTree();
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const searchBox = document.getElementById('code-search');
|
|
162
|
-
if (searchBox) {
|
|
163
|
-
searchBox.addEventListener('input', (e) => {
|
|
164
|
-
this.searchTerm = e.target.value.toLowerCase();
|
|
165
|
-
this.filterTree();
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const expandBtn = document.getElementById('code-expand-all');
|
|
170
|
-
if (expandBtn) {
|
|
171
|
-
expandBtn.addEventListener('click', () => this.expandAll());
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const collapseBtn = document.getElementById('code-collapse-all');
|
|
175
|
-
if (collapseBtn) {
|
|
176
|
-
collapseBtn.addEventListener('click', () => this.collapseAll());
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const resetZoomBtn = document.getElementById('code-reset-zoom');
|
|
180
|
-
if (resetZoomBtn) {
|
|
181
|
-
resetZoomBtn.addEventListener('click', () => this.resetZoom());
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const toggleLegendBtn = document.getElementById('code-toggle-legend');
|
|
185
|
-
if (toggleLegendBtn) {
|
|
186
|
-
toggleLegendBtn.addEventListener('click', () => this.toggleLegend());
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Listen for working directory changes
|
|
190
|
-
document.addEventListener('workingDirectoryChanged', (e) => {
|
|
191
|
-
this.onWorkingDirectoryChanged(e.detail.directory);
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Handle working directory change
|
|
197
|
-
*/
|
|
198
|
-
onWorkingDirectoryChanged(newDirectory) {
|
|
199
|
-
if (!newDirectory || newDirectory === 'Loading...' || newDirectory === 'Not selected') {
|
|
200
|
-
// Show no directory message
|
|
201
|
-
this.showNoWorkingDirectoryMessage();
|
|
202
|
-
// Reset tree state
|
|
203
|
-
this.autoDiscovered = false;
|
|
204
|
-
this.analyzing = false;
|
|
205
|
-
this.nodes.clear();
|
|
206
|
-
this.stats = {
|
|
207
|
-
files: 0,
|
|
208
|
-
classes: 0,
|
|
209
|
-
functions: 0,
|
|
210
|
-
methods: 0,
|
|
211
|
-
lines: 0
|
|
212
|
-
};
|
|
213
|
-
this.updateStats();
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Remove any no directory message
|
|
218
|
-
this.removeNoWorkingDirectoryMessage();
|
|
219
|
-
|
|
220
|
-
// Reset discovery state for new directory
|
|
221
|
-
this.autoDiscovered = false;
|
|
222
|
-
this.analyzing = false;
|
|
223
|
-
|
|
224
|
-
// Clear existing data
|
|
225
|
-
this.nodes.clear();
|
|
226
|
-
this.stats = {
|
|
227
|
-
files: 0,
|
|
228
|
-
classes: 0,
|
|
229
|
-
functions: 0,
|
|
230
|
-
methods: 0,
|
|
231
|
-
lines: 0
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
// Re-initialize with new directory
|
|
235
|
-
this.initializeTreeData();
|
|
236
|
-
if (this.svg) {
|
|
237
|
-
this.update(this.root);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Check if Code tab is currently active
|
|
241
|
-
const tabPanel = document.getElementById('code-tab');
|
|
242
|
-
if (tabPanel && tabPanel.classList.contains('active')) {
|
|
243
|
-
// Auto-discover in the new directory
|
|
244
|
-
this.autoDiscoverRootLevel();
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
this.updateStats();
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Show loading spinner
|
|
252
|
-
*/
|
|
253
|
-
showLoading() {
|
|
254
|
-
let loadingDiv = document.getElementById('code-tree-loading');
|
|
255
|
-
if (!loadingDiv) {
|
|
256
|
-
// Create loading element if it doesn't exist
|
|
257
|
-
const container = document.getElementById('code-tree-container');
|
|
258
|
-
if (container) {
|
|
259
|
-
loadingDiv = document.createElement('div');
|
|
260
|
-
loadingDiv.id = 'code-tree-loading';
|
|
261
|
-
loadingDiv.innerHTML = `
|
|
262
|
-
<div class="code-tree-spinner"></div>
|
|
263
|
-
<div class="code-tree-loading-text">Analyzing code structure...</div>
|
|
264
|
-
`;
|
|
265
|
-
container.appendChild(loadingDiv);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
if (loadingDiv) {
|
|
269
|
-
loadingDiv.classList.remove('hidden');
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Hide loading spinner
|
|
275
|
-
*/
|
|
276
|
-
hideLoading() {
|
|
277
|
-
const loadingDiv = document.getElementById('code-tree-loading');
|
|
278
|
-
if (loadingDiv) {
|
|
279
|
-
loadingDiv.classList.add('hidden');
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Create the D3.js visualization
|
|
285
|
-
*/
|
|
286
|
-
createVisualization() {
|
|
287
|
-
if (typeof d3 === 'undefined') {
|
|
288
|
-
console.error('D3.js is not loaded');
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const container = d3.select('#code-tree-container');
|
|
293
|
-
container.selectAll('*').remove();
|
|
294
|
-
|
|
295
|
-
// Add tree controls toolbar
|
|
296
|
-
this.addTreeControls();
|
|
297
|
-
|
|
298
|
-
// Add breadcrumb navigation
|
|
299
|
-
this.addBreadcrumb();
|
|
300
|
-
|
|
301
|
-
if (!container || !container.node()) {
|
|
302
|
-
console.error('Code tree container not found');
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Calculate dimensions
|
|
307
|
-
const containerNode = container.node();
|
|
308
|
-
const containerWidth = containerNode.clientWidth || 960;
|
|
309
|
-
const containerHeight = containerNode.clientHeight || 600;
|
|
310
|
-
|
|
311
|
-
this.width = containerWidth - this.margin.left - this.margin.right;
|
|
312
|
-
this.height = containerHeight - this.margin.top - this.margin.bottom;
|
|
313
|
-
this.radius = Math.min(this.width, this.height) / 2;
|
|
314
|
-
|
|
315
|
-
// Create SVG
|
|
316
|
-
this.svg = container.append('svg')
|
|
317
|
-
.attr('width', containerWidth)
|
|
318
|
-
.attr('height', containerHeight);
|
|
319
|
-
|
|
320
|
-
// Create tree group with appropriate centering
|
|
321
|
-
const centerX = containerWidth / 2;
|
|
322
|
-
const centerY = containerHeight / 2;
|
|
323
|
-
|
|
324
|
-
// Different initial positioning for different layouts
|
|
325
|
-
if (this.isRadialLayout) {
|
|
326
|
-
// Radial: center in the middle of the canvas
|
|
327
|
-
this.treeGroup = this.svg.append('g')
|
|
328
|
-
.attr('transform', `translate(${centerX},${centerY})`);
|
|
329
|
-
} else {
|
|
330
|
-
// Linear: start from left with some margin
|
|
331
|
-
this.treeGroup = this.svg.append('g')
|
|
332
|
-
.attr('transform', `translate(${this.margin.left + 100},${centerY})`);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// Create tree layout with improved spacing
|
|
336
|
-
if (this.isRadialLayout) {
|
|
337
|
-
// Use d3.cluster for better radial distribution
|
|
338
|
-
this.treeLayout = d3.cluster()
|
|
339
|
-
.size([2 * Math.PI, this.radius - 100])
|
|
340
|
-
.separation((a, b) => {
|
|
341
|
-
// Enhanced separation for radial layout
|
|
342
|
-
if (a.parent == b.parent) {
|
|
343
|
-
// Base separation on tree depth for better spacing
|
|
344
|
-
const depthFactor = Math.max(1, 4 - a.depth);
|
|
345
|
-
// Increase spacing for nodes with many siblings
|
|
346
|
-
const siblingCount = a.parent ? (a.parent.children?.length || 1) : 1;
|
|
347
|
-
const siblingFactor = siblingCount > 5 ? 2 : (siblingCount > 3 ? 1.5 : 1);
|
|
348
|
-
// More spacing at outer levels where circumference is larger
|
|
349
|
-
const radiusFactor = 1 + (a.depth * 0.2);
|
|
350
|
-
return (depthFactor * siblingFactor) / (a.depth || 1) * radiusFactor;
|
|
351
|
-
} else {
|
|
352
|
-
// Different parents - ensure enough space
|
|
353
|
-
return 4 / (a.depth || 1);
|
|
354
|
-
}
|
|
355
|
-
});
|
|
356
|
-
} else {
|
|
357
|
-
// Linear layout with dynamic sizing based on node count
|
|
358
|
-
// Use nodeSize for consistent spacing regardless of tree size
|
|
359
|
-
this.treeLayout = d3.tree()
|
|
360
|
-
.nodeSize([30, 200]) // Fixed spacing: 30px vertical, 200px horizontal
|
|
361
|
-
.separation((a, b) => {
|
|
362
|
-
// Consistent separation for linear layout
|
|
363
|
-
if (a.parent == b.parent) {
|
|
364
|
-
// Same parent - standard spacing
|
|
365
|
-
return 1;
|
|
366
|
-
} else {
|
|
367
|
-
// Different parents - slightly more space
|
|
368
|
-
return 1.5;
|
|
369
|
-
}
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// DISABLED: All zoom behavior has been completely disabled to prevent tree movement
|
|
374
|
-
// The tree should remain completely stationary - no zooming, panning, or centering allowed
|
|
375
|
-
this.zoom = null; // Completely disable zoom behavior
|
|
376
|
-
|
|
377
|
-
// Do NOT apply zoom behavior to SVG - this prevents all zoom/pan interactions
|
|
378
|
-
// this.svg.call(this.zoom); // DISABLED
|
|
379
|
-
|
|
380
|
-
console.log('[CodeTree] All zoom and pan behavior disabled - tree is now completely stationary');
|
|
381
|
-
|
|
382
|
-
// Add controls overlay
|
|
383
|
-
this.addVisualizationControls();
|
|
384
|
-
|
|
385
|
-
// Create tooltip
|
|
386
|
-
this.tooltip = d3.select('body').append('div')
|
|
387
|
-
.attr('class', 'code-tree-tooltip')
|
|
388
|
-
.style('opacity', 0)
|
|
389
|
-
.style('position', 'absolute')
|
|
390
|
-
.style('background', 'rgba(0, 0, 0, 0.8)')
|
|
391
|
-
.style('color', 'white')
|
|
392
|
-
.style('padding', '8px')
|
|
393
|
-
.style('border-radius', '4px')
|
|
394
|
-
.style('font-size', '12px')
|
|
395
|
-
.style('pointer-events', 'none');
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* Clear all D3 visualization elements
|
|
400
|
-
*/
|
|
401
|
-
clearD3Visualization() {
|
|
402
|
-
if (this.treeGroup) {
|
|
403
|
-
// Remove all existing nodes and links
|
|
404
|
-
this.treeGroup.selectAll('g.node').remove();
|
|
405
|
-
this.treeGroup.selectAll('path.link').remove();
|
|
406
|
-
}
|
|
407
|
-
// Reset node ID counter for proper tracking
|
|
408
|
-
this.nodeId = 0;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* Initialize tree data structure
|
|
413
|
-
*/
|
|
414
|
-
initializeTreeData() {
|
|
415
|
-
const workingDir = this.getWorkingDirectory();
|
|
416
|
-
const dirName = workingDir ? workingDir.split('/').pop() || 'Project Root' : 'Project Root';
|
|
417
|
-
|
|
418
|
-
// Use '.' as the root path for consistency with relative path handling
|
|
419
|
-
// The actual working directory is retrieved via getWorkingDirectory() when needed
|
|
420
|
-
this.treeData = {
|
|
421
|
-
name: dirName,
|
|
422
|
-
path: '.', // Always use '.' for root to simplify path handling
|
|
423
|
-
type: 'root',
|
|
424
|
-
children: [],
|
|
425
|
-
loaded: false,
|
|
426
|
-
expanded: true // Start expanded
|
|
427
|
-
};
|
|
428
|
-
|
|
429
|
-
if (typeof d3 !== 'undefined') {
|
|
430
|
-
this.root = d3.hierarchy(this.treeData);
|
|
431
|
-
this.root.x0 = this.height / 2;
|
|
432
|
-
this.root.y0 = 0;
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
/**
|
|
437
|
-
* Subscribe to code analysis events
|
|
438
|
-
*/
|
|
439
|
-
subscribeToEvents() {
|
|
440
|
-
if (!this.socket) {
|
|
441
|
-
if (window.socket) {
|
|
442
|
-
this.socket = window.socket;
|
|
443
|
-
this.setupEventHandlers();
|
|
444
|
-
} else if (window.dashboard?.socketClient?.socket) {
|
|
445
|
-
this.socket = window.dashboard.socketClient.socket;
|
|
446
|
-
this.setupEventHandlers();
|
|
447
|
-
} else if (window.socketClient?.socket) {
|
|
448
|
-
this.socket = window.socketClient.socket;
|
|
449
|
-
this.setupEventHandlers();
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
/**
|
|
455
|
-
* Automatically discover root-level objects when tab opens
|
|
456
|
-
*/
|
|
457
|
-
autoDiscoverRootLevel() {
|
|
458
|
-
if (this.autoDiscovered || this.analyzing) {
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// Update activity ticker
|
|
463
|
-
this.updateActivityTicker('🔍 Discovering project structure...', 'info');
|
|
464
|
-
|
|
465
|
-
// Get working directory
|
|
466
|
-
const workingDir = this.getWorkingDirectory();
|
|
467
|
-
if (!workingDir || workingDir === 'Loading...' || workingDir === 'Not selected') {
|
|
468
|
-
console.warn('Cannot auto-discover: no working directory set');
|
|
469
|
-
this.showNoWorkingDirectoryMessage();
|
|
470
|
-
return;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// Ensure we have an absolute path
|
|
474
|
-
if (!workingDir.startsWith('/') && !workingDir.match(/^[A-Z]:\\/)) {
|
|
475
|
-
console.error('Working directory is not absolute:', workingDir);
|
|
476
|
-
this.showNotification('Invalid working directory path', 'error');
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
this.autoDiscovered = true;
|
|
482
|
-
this.analyzing = true;
|
|
483
|
-
|
|
484
|
-
// Clear any existing nodes
|
|
485
|
-
this.nodes.clear();
|
|
486
|
-
this.stats = {
|
|
487
|
-
files: 0,
|
|
488
|
-
classes: 0,
|
|
489
|
-
functions: 0,
|
|
490
|
-
methods: 0,
|
|
491
|
-
lines: 0
|
|
492
|
-
};
|
|
493
|
-
|
|
494
|
-
// Subscribe to events if not already done
|
|
495
|
-
if (this.socket && !this.socket.hasListeners('code:node:found')) {
|
|
496
|
-
this.setupEventHandlers();
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
// Update tree data with working directory as the root
|
|
500
|
-
const dirName = workingDir.split('/').pop() || 'Project Root';
|
|
501
|
-
this.treeData = {
|
|
502
|
-
name: dirName,
|
|
503
|
-
path: '.', // Use '.' for root to maintain consistency with relative path handling
|
|
504
|
-
type: 'root',
|
|
505
|
-
children: [],
|
|
506
|
-
loaded: false,
|
|
507
|
-
expanded: true // Start expanded to show discovered items
|
|
508
|
-
};
|
|
509
|
-
|
|
510
|
-
if (typeof d3 !== 'undefined') {
|
|
511
|
-
this.root = d3.hierarchy(this.treeData);
|
|
512
|
-
this.root.x0 = this.height / 2;
|
|
513
|
-
this.root.y0 = 0;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// Update UI
|
|
517
|
-
this.showLoading();
|
|
518
|
-
this.updateBreadcrumb(`Discovering structure in ${dirName}...`, 'info');
|
|
519
|
-
|
|
520
|
-
// Get selected languages from checkboxes
|
|
521
|
-
const selectedLanguages = [];
|
|
522
|
-
document.querySelectorAll('.language-checkbox:checked').forEach(cb => {
|
|
523
|
-
selectedLanguages.push(cb.value);
|
|
524
|
-
});
|
|
525
|
-
|
|
526
|
-
// Get ignore patterns
|
|
527
|
-
const ignorePatterns = document.getElementById('ignore-patterns')?.value || '';
|
|
528
|
-
|
|
529
|
-
// Enhanced debug logging
|
|
530
|
-
|
|
531
|
-
// Request top-level discovery with working directory
|
|
532
|
-
const requestPayload = {
|
|
533
|
-
path: workingDir, // Use working directory instead of '.'
|
|
534
|
-
depth: 'top_level',
|
|
535
|
-
languages: selectedLanguages,
|
|
536
|
-
ignore_patterns: ignorePatterns,
|
|
537
|
-
request_id: `discover_${Date.now()}` // Add request ID for tracking
|
|
538
|
-
};
|
|
539
|
-
|
|
540
|
-
// Sending top-level discovery request
|
|
541
|
-
|
|
542
|
-
if (this.socket) {
|
|
543
|
-
this.socket.emit('code:discover:top_level', requestPayload);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// Update stats display
|
|
547
|
-
this.updateStats();
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
/**
|
|
551
|
-
* Legacy analyzeCode method - redirects to auto-discovery
|
|
552
|
-
*/
|
|
553
|
-
analyzeCode() {
|
|
554
|
-
if (this.analyzing) {
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
// Redirect to auto-discovery
|
|
559
|
-
this.autoDiscoverRootLevel();
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
/**
|
|
563
|
-
* Cancel ongoing analysis - removed since we no longer have a cancel button
|
|
564
|
-
*/
|
|
565
|
-
cancelAnalysis() {
|
|
566
|
-
this.analyzing = false;
|
|
567
|
-
this.hideLoading();
|
|
568
|
-
|
|
569
|
-
if (this.socket) {
|
|
570
|
-
this.socket.emit('code:analysis:cancel');
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
/**
|
|
575
|
-
* Add tree control toolbar with expand/collapse and other controls
|
|
576
|
-
*/
|
|
577
|
-
addTreeControls() {
|
|
578
|
-
const container = d3.select('#code-tree-container');
|
|
579
|
-
|
|
580
|
-
// Remove any existing controls
|
|
581
|
-
container.select('.tree-controls-toolbar').remove();
|
|
582
|
-
|
|
583
|
-
const toolbar = container.append('div')
|
|
584
|
-
.attr('class', 'tree-controls-toolbar');
|
|
585
|
-
|
|
586
|
-
// Expand All button
|
|
587
|
-
toolbar.append('button')
|
|
588
|
-
.attr('class', 'tree-control-btn')
|
|
589
|
-
.attr('title', 'Expand all loaded directories')
|
|
590
|
-
.text('⊞')
|
|
591
|
-
.on('click', () => this.expandAll());
|
|
592
|
-
|
|
593
|
-
// Collapse All button
|
|
594
|
-
toolbar.append('button')
|
|
595
|
-
.attr('class', 'tree-control-btn')
|
|
596
|
-
.attr('title', 'Collapse all directories')
|
|
597
|
-
.text('⊟')
|
|
598
|
-
.on('click', () => this.collapseAll());
|
|
599
|
-
|
|
600
|
-
// Bulk Load Toggle
|
|
601
|
-
toolbar.append('button')
|
|
602
|
-
.attr('class', 'tree-control-btn')
|
|
603
|
-
.attr('id', 'bulk-load-toggle')
|
|
604
|
-
.attr('title', 'Toggle bulk loading (load 2 levels at once)')
|
|
605
|
-
.text('↕')
|
|
606
|
-
.on('click', () => this.toggleBulkLoad());
|
|
607
|
-
|
|
608
|
-
// Layout Toggle
|
|
609
|
-
toolbar.append('button')
|
|
610
|
-
.attr('class', 'tree-control-btn')
|
|
611
|
-
.attr('title', 'Toggle between radial and linear layouts')
|
|
612
|
-
.text('◎')
|
|
613
|
-
.on('click', () => this.toggleLayout());
|
|
614
|
-
|
|
615
|
-
// Path Search
|
|
616
|
-
const searchInput = toolbar.append('input')
|
|
617
|
-
.attr('class', 'tree-control-btn')
|
|
618
|
-
.attr('type', 'text')
|
|
619
|
-
.attr('placeholder', 'Search...')
|
|
620
|
-
.attr('title', 'Search for files and directories')
|
|
621
|
-
.style('width', '120px')
|
|
622
|
-
.style('text-align', 'left')
|
|
623
|
-
.on('input', (event) => this.searchTree(event.target.value))
|
|
624
|
-
.on('keydown', (event) => {
|
|
625
|
-
if (event.key === 'Escape') {
|
|
626
|
-
event.target.value = '';
|
|
627
|
-
this.searchTree('');
|
|
628
|
-
}
|
|
629
|
-
});
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
/**
|
|
633
|
-
* Add breadcrumb navigation
|
|
634
|
-
*/
|
|
635
|
-
addBreadcrumb() {
|
|
636
|
-
const container = d3.select('#code-tree-container');
|
|
637
|
-
|
|
638
|
-
// Remove any existing breadcrumb
|
|
639
|
-
container.select('.tree-breadcrumb').remove();
|
|
640
|
-
|
|
641
|
-
const breadcrumb = container.append('div')
|
|
642
|
-
.attr('class', 'tree-breadcrumb');
|
|
643
|
-
|
|
644
|
-
const pathDiv = breadcrumb.append('div')
|
|
645
|
-
.attr('class', 'breadcrumb-path')
|
|
646
|
-
.attr('id', 'tree-breadcrumb-path');
|
|
647
|
-
|
|
648
|
-
// Initialize with working directory
|
|
649
|
-
this.updateBreadcrumbPath('/');
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
/**
|
|
653
|
-
* Update breadcrumb path based on current navigation
|
|
654
|
-
*/
|
|
655
|
-
updateBreadcrumbPath(currentPath) {
|
|
656
|
-
const pathDiv = d3.select('#tree-breadcrumb-path');
|
|
657
|
-
pathDiv.selectAll('*').remove();
|
|
658
|
-
|
|
659
|
-
const workingDir = this.getWorkingDirectory();
|
|
660
|
-
if (!workingDir || workingDir === 'Loading...' || workingDir === 'Not selected') {
|
|
661
|
-
pathDiv.text('No project selected');
|
|
662
|
-
return;
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
// Build path segments
|
|
666
|
-
const segments = currentPath === '/' ?
|
|
667
|
-
[workingDir.split('/').pop() || 'Root'] :
|
|
668
|
-
currentPath.split('/').filter(s => s.length > 0);
|
|
669
|
-
|
|
670
|
-
segments.forEach((segment, index) => {
|
|
671
|
-
if (index > 0) {
|
|
672
|
-
pathDiv.append('span')
|
|
673
|
-
.attr('class', 'breadcrumb-separator')
|
|
674
|
-
.text('/');
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
pathDiv.append('span')
|
|
678
|
-
.attr('class', index === segments.length - 1 ? 'breadcrumb-segment current' : 'breadcrumb-segment')
|
|
679
|
-
.text(segment)
|
|
680
|
-
.on('click', () => {
|
|
681
|
-
if (index < segments.length - 1) {
|
|
682
|
-
// Navigate to parent path
|
|
683
|
-
const parentPath = segments.slice(0, index + 1).join('/');
|
|
684
|
-
this.navigateToPath(parentPath);
|
|
685
|
-
}
|
|
686
|
-
});
|
|
687
|
-
});
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
/**
|
|
691
|
-
* Expand all currently loaded directories
|
|
692
|
-
*/
|
|
693
|
-
expandAll() {
|
|
694
|
-
if (!this.root) return;
|
|
695
|
-
|
|
696
|
-
const expandNode = (node) => {
|
|
697
|
-
if (node.data.type === 'directory' && node.data.loaded === true) {
|
|
698
|
-
if (node._children) {
|
|
699
|
-
node.children = node._children;
|
|
700
|
-
node._children = null;
|
|
701
|
-
node.data.expanded = true;
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
if (node.children) {
|
|
705
|
-
node.children.forEach(expandNode);
|
|
706
|
-
}
|
|
707
|
-
};
|
|
708
|
-
|
|
709
|
-
expandNode(this.root);
|
|
710
|
-
this.update(this.root);
|
|
711
|
-
this.showNotification('Expanded all loaded directories', 'success');
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
/**
|
|
715
|
-
* Collapse all directories to root level
|
|
716
|
-
*/
|
|
717
|
-
collapseAll() {
|
|
718
|
-
if (!this.root) return;
|
|
719
|
-
|
|
720
|
-
const collapseNode = (node) => {
|
|
721
|
-
if (node.data.type === 'directory' && node.children) {
|
|
722
|
-
node._children = node.children;
|
|
723
|
-
node.children = null;
|
|
724
|
-
node.data.expanded = false;
|
|
725
|
-
}
|
|
726
|
-
if (node._children) {
|
|
727
|
-
node._children.forEach(collapseNode);
|
|
728
|
-
}
|
|
729
|
-
};
|
|
730
|
-
|
|
731
|
-
collapseNode(this.root);
|
|
732
|
-
this.update(this.root);
|
|
733
|
-
this.showNotification('Collapsed all directories', 'info');
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
/**
|
|
737
|
-
* Toggle bulk loading mode
|
|
738
|
-
*/
|
|
739
|
-
toggleBulkLoad() {
|
|
740
|
-
this.bulkLoadMode = !this.bulkLoadMode;
|
|
741
|
-
const button = d3.select('#bulk-load-toggle');
|
|
742
|
-
|
|
743
|
-
if (this.bulkLoadMode) {
|
|
744
|
-
button.classed('active', true);
|
|
745
|
-
this.showNotification('Bulk load enabled - will load 2 levels deep', 'info');
|
|
746
|
-
} else {
|
|
747
|
-
button.classed('active', false);
|
|
748
|
-
this.showNotification('Bulk load disabled - load 1 level at a time', 'info');
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
/**
|
|
753
|
-
* Navigate to a specific path in the tree
|
|
754
|
-
*/
|
|
755
|
-
navigateToPath(path) {
|
|
756
|
-
// Implementation for navigating to a specific path
|
|
757
|
-
// This would expand the tree to show the specified path
|
|
758
|
-
this.updateBreadcrumbPath(path);
|
|
759
|
-
this.showNotification(`Navigating to: ${path}`, 'info');
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
/**
|
|
763
|
-
* Search the tree for matching files/directories
|
|
764
|
-
*/
|
|
765
|
-
searchTree(query) {
|
|
766
|
-
if (!this.root || !this.treeGroup) return;
|
|
767
|
-
|
|
768
|
-
const searchTerm = query.toLowerCase().trim();
|
|
769
|
-
|
|
770
|
-
// Clear previous search highlights
|
|
771
|
-
this.treeGroup.selectAll('.code-node')
|
|
772
|
-
.classed('search-match', false);
|
|
773
|
-
|
|
774
|
-
if (!searchTerm) {
|
|
775
|
-
return; // No search term, just clear highlights
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
// Find matching nodes
|
|
779
|
-
const matchingNodes = [];
|
|
780
|
-
const searchNode = (node) => {
|
|
781
|
-
const name = (node.data.name || '').toLowerCase();
|
|
782
|
-
const path = (node.data.path || '').toLowerCase();
|
|
783
|
-
|
|
784
|
-
if (name.includes(searchTerm) || path.includes(searchTerm)) {
|
|
785
|
-
matchingNodes.push(node);
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
if (node.children) {
|
|
789
|
-
node.children.forEach(searchNode);
|
|
790
|
-
}
|
|
791
|
-
if (node._children) {
|
|
792
|
-
node._children.forEach(searchNode);
|
|
793
|
-
}
|
|
794
|
-
};
|
|
795
|
-
|
|
796
|
-
searchNode(this.root);
|
|
797
|
-
|
|
798
|
-
// Highlight matching nodes
|
|
799
|
-
if (matchingNodes.length > 0) {
|
|
800
|
-
// Get all current nodes in the tree
|
|
801
|
-
const allNodes = this.treeGroup.selectAll('.code-node').data();
|
|
802
|
-
|
|
803
|
-
matchingNodes.forEach(matchNode => {
|
|
804
|
-
// Find the corresponding DOM node
|
|
805
|
-
const domNode = this.treeGroup.selectAll('.code-node')
|
|
806
|
-
.filter(d => d.data.path === matchNode.data.path);
|
|
807
|
-
domNode.classed('search-match', true);
|
|
808
|
-
|
|
809
|
-
// Expand parent path to show the match
|
|
810
|
-
this.expandPathToNode(matchNode);
|
|
811
|
-
});
|
|
812
|
-
|
|
813
|
-
this.showNotification(`Found ${matchingNodes.length} matches`, 'success');
|
|
814
|
-
|
|
815
|
-
// Auto-center on first match if in radial layout - REMOVED
|
|
816
|
-
// Centering functionality has been disabled to prevent unwanted repositioning
|
|
817
|
-
// if (matchingNodes.length > 0 && this.isRadialLayout) {
|
|
818
|
-
// this.centerOnNode ? this.centerOnNode(matchingNodes[0]) : this.centerOnNodeRadial(matchingNodes[0]);
|
|
819
|
-
// }
|
|
820
|
-
} else {
|
|
821
|
-
this.showNotification('No matches found', 'info');
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
/**
|
|
826
|
-
* Expand the tree path to show a specific node
|
|
827
|
-
*/
|
|
828
|
-
expandPathToNode(targetNode) {
|
|
829
|
-
const pathToExpand = [];
|
|
830
|
-
let current = targetNode.parent;
|
|
831
|
-
|
|
832
|
-
// Build path from node to root
|
|
833
|
-
while (current && current !== this.root) {
|
|
834
|
-
pathToExpand.unshift(current);
|
|
835
|
-
current = current.parent;
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
// Expand each node in the path
|
|
839
|
-
pathToExpand.forEach(node => {
|
|
840
|
-
if (node.data.type === 'directory' && node._children) {
|
|
841
|
-
node.children = node._children;
|
|
842
|
-
node._children = null;
|
|
843
|
-
node.data.expanded = true;
|
|
844
|
-
}
|
|
845
|
-
});
|
|
846
|
-
|
|
847
|
-
// Update the visualization if we expanded anything
|
|
848
|
-
if (pathToExpand.length > 0) {
|
|
849
|
-
this.update(this.root);
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
/**
|
|
854
|
-
* Create the events display area
|
|
855
|
-
*/
|
|
856
|
-
createEventsDisplay() {
|
|
857
|
-
let eventsContainer = document.getElementById('analysis-events');
|
|
858
|
-
if (!eventsContainer) {
|
|
859
|
-
const treeContainer = document.getElementById('code-tree-container');
|
|
860
|
-
if (treeContainer) {
|
|
861
|
-
eventsContainer = document.createElement('div');
|
|
862
|
-
eventsContainer.id = 'analysis-events';
|
|
863
|
-
eventsContainer.className = 'analysis-events';
|
|
864
|
-
eventsContainer.style.display = 'none';
|
|
865
|
-
treeContainer.appendChild(eventsContainer);
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
/**
|
|
871
|
-
* Clear the events display
|
|
872
|
-
*/
|
|
873
|
-
clearEventsDisplay() {
|
|
874
|
-
const eventsContainer = document.getElementById('analysis-events');
|
|
875
|
-
if (eventsContainer) {
|
|
876
|
-
eventsContainer.innerHTML = '';
|
|
877
|
-
eventsContainer.style.display = 'block';
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
/**
|
|
882
|
-
* Add an event to the display
|
|
883
|
-
*/
|
|
884
|
-
addEventToDisplay(message, type = 'info') {
|
|
885
|
-
const eventsContainer = document.getElementById('analysis-events');
|
|
886
|
-
if (eventsContainer) {
|
|
887
|
-
const eventEl = document.createElement('div');
|
|
888
|
-
eventEl.className = 'analysis-event';
|
|
889
|
-
eventEl.style.borderLeftColor = type === 'warning' ? '#f59e0b' :
|
|
890
|
-
type === 'error' ? '#ef4444' : '#3b82f6';
|
|
891
|
-
|
|
892
|
-
const timestamp = new Date().toLocaleTimeString();
|
|
893
|
-
eventEl.innerHTML = `<span style="color: #718096;">[${timestamp}]</span> ${message}`;
|
|
894
|
-
|
|
895
|
-
eventsContainer.appendChild(eventEl);
|
|
896
|
-
// Auto-scroll to bottom
|
|
897
|
-
eventsContainer.scrollTop = eventsContainer.scrollHeight;
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
/**
|
|
902
|
-
* Setup Socket.IO event handlers
|
|
903
|
-
*/
|
|
904
|
-
setupEventHandlers() {
|
|
905
|
-
if (!this.socket) return;
|
|
906
|
-
|
|
907
|
-
// Analysis lifecycle events
|
|
908
|
-
this.socket.on('code:analysis:accepted', (data) => this.onAnalysisAccepted(data));
|
|
909
|
-
this.socket.on('code:analysis:queued', (data) => this.onAnalysisQueued(data));
|
|
910
|
-
this.socket.on('code:analysis:start', (data) => this.onAnalysisStart(data));
|
|
911
|
-
this.socket.on('code:analysis:complete', (data) => this.onAnalysisComplete(data));
|
|
912
|
-
this.socket.on('code:analysis:cancelled', (data) => this.onAnalysisCancelled(data));
|
|
913
|
-
this.socket.on('code:analysis:error', (data) => this.onAnalysisError(data));
|
|
914
|
-
|
|
915
|
-
// Node discovery events
|
|
916
|
-
this.socket.on('code:top_level:discovered', (data) => this.onTopLevelDiscovered(data));
|
|
917
|
-
this.socket.on('code:directory:discovered', (data) => this.onDirectoryDiscovered(data));
|
|
918
|
-
this.socket.on('code:file:discovered', (data) => this.onFileDiscovered(data));
|
|
919
|
-
this.socket.on('code:file:analyzed', (data) => this.onFileAnalyzed(data));
|
|
920
|
-
this.socket.on('code:node:found', (data) => this.onNodeFound(data));
|
|
921
|
-
|
|
922
|
-
// Progress updates
|
|
923
|
-
this.socket.on('code:analysis:progress', (data) => this.onProgressUpdate(data));
|
|
924
|
-
|
|
925
|
-
// Lazy loading responses
|
|
926
|
-
this.socket.on('code:directory:contents', (data) => {
|
|
927
|
-
// Update the requested directory with its contents
|
|
928
|
-
if (data.path) {
|
|
929
|
-
// Convert absolute path back to relative path to match tree nodes
|
|
930
|
-
let searchPath = data.path;
|
|
931
|
-
const workingDir = this.getWorkingDirectory();
|
|
932
|
-
if (workingDir && searchPath.startsWith(workingDir)) {
|
|
933
|
-
// Remove working directory prefix to get relative path
|
|
934
|
-
searchPath = searchPath.substring(workingDir.length).replace(/^\//, '');
|
|
935
|
-
// If empty after removing prefix, it's the root
|
|
936
|
-
if (!searchPath) {
|
|
937
|
-
searchPath = '.';
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
const node = this.findNodeByPath(searchPath);
|
|
942
|
-
if (node && data.children) {
|
|
943
|
-
// Find D3 node and remove loading pulse (use searchPath, not data.path)
|
|
944
|
-
const d3Node = this.findD3NodeByPath(searchPath);
|
|
945
|
-
if (d3Node && this.loadingNodes.has(searchPath)) {
|
|
946
|
-
this.removeLoadingPulse(d3Node);
|
|
947
|
-
this.loadingNodes.delete(searchPath); // Remove from loading set
|
|
948
|
-
console.log('🎯 [SUBDIRECTORY LOADING] Successfully completed and removed from loading set:', searchPath);
|
|
949
|
-
}
|
|
950
|
-
node.children = data.children.map(child => {
|
|
951
|
-
// Construct full path for child by combining parent path with child name
|
|
952
|
-
// The backend now returns just the item name, not the full path
|
|
953
|
-
let childPath;
|
|
954
|
-
if (searchPath === '.' || searchPath === '') {
|
|
955
|
-
// Root level - child path is just the name
|
|
956
|
-
childPath = child.name || child.path;
|
|
957
|
-
} else {
|
|
958
|
-
// Subdirectory - combine parent path with child name
|
|
959
|
-
// Use child.name (backend returns just the name) or fallback to child.path
|
|
960
|
-
const childName = child.name || child.path;
|
|
961
|
-
childPath = `${searchPath}/${childName}`;
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
return {
|
|
965
|
-
...child,
|
|
966
|
-
path: childPath, // Override with constructed path
|
|
967
|
-
loaded: child.type === 'directory' ? false : undefined,
|
|
968
|
-
analyzed: child.type === 'file' ? false : undefined,
|
|
969
|
-
expanded: false,
|
|
970
|
-
children: []
|
|
971
|
-
};
|
|
972
|
-
});
|
|
973
|
-
node.loaded = true;
|
|
974
|
-
node.expanded = true; // Mark as expanded to show children
|
|
975
|
-
|
|
976
|
-
// Update D3 hierarchy and make sure the node is expanded
|
|
977
|
-
if (this.root && this.svg) {
|
|
978
|
-
// Store old root to preserve expansion state
|
|
979
|
-
const oldRoot = this.root;
|
|
980
|
-
|
|
981
|
-
// Recreate hierarchy with updated data
|
|
982
|
-
this.root = d3.hierarchy(this.treeData);
|
|
983
|
-
this.root.x0 = this.height / 2;
|
|
984
|
-
this.root.y0 = 0;
|
|
985
|
-
|
|
986
|
-
// Preserve expansion state from old tree
|
|
987
|
-
this.preserveExpansionState(oldRoot, this.root);
|
|
988
|
-
|
|
989
|
-
// Find the D3 node again after hierarchy recreation
|
|
990
|
-
const updatedD3Node = this.findD3NodeByPath(searchPath);
|
|
991
|
-
if (updatedD3Node) {
|
|
992
|
-
// Ensure children are visible (not collapsed)
|
|
993
|
-
updatedD3Node.children = updatedD3Node._children || updatedD3Node.children;
|
|
994
|
-
updatedD3Node._children = null;
|
|
995
|
-
updatedD3Node.data.expanded = true;
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
this.update(this.root);
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
// Update stats based on discovered contents
|
|
1002
|
-
if (data.stats) {
|
|
1003
|
-
this.stats.files += data.stats.files || 0;
|
|
1004
|
-
this.stats.directories += data.stats.directories || 0;
|
|
1005
|
-
this.updateStats();
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
this.updateBreadcrumb(`Loaded ${data.path}`, 'success');
|
|
1009
|
-
this.hideLoading();
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
});
|
|
1013
|
-
|
|
1014
|
-
// Top level discovery response
|
|
1015
|
-
this.socket.on('code:top_level:discovered', (data) => {
|
|
1016
|
-
if (data.items && Array.isArray(data.items)) {
|
|
1017
|
-
|
|
1018
|
-
// Add discovered items to the root node
|
|
1019
|
-
this.treeData.children = data.items.map(item => ({
|
|
1020
|
-
name: item.name,
|
|
1021
|
-
path: item.path,
|
|
1022
|
-
type: item.type,
|
|
1023
|
-
language: item.type === 'file' ? this.detectLanguage(item.path) : undefined,
|
|
1024
|
-
size: item.size,
|
|
1025
|
-
lines: item.lines,
|
|
1026
|
-
loaded: item.type === 'directory' ? false : undefined,
|
|
1027
|
-
analyzed: item.type === 'file' ? false : undefined,
|
|
1028
|
-
expanded: false,
|
|
1029
|
-
children: []
|
|
1030
|
-
}));
|
|
1031
|
-
|
|
1032
|
-
this.treeData.loaded = true;
|
|
1033
|
-
|
|
1034
|
-
// Update stats
|
|
1035
|
-
if (data.stats) {
|
|
1036
|
-
this.stats = { ...this.stats, ...data.stats };
|
|
1037
|
-
this.updateStats();
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
// Update D3 hierarchy
|
|
1041
|
-
if (typeof d3 !== 'undefined') {
|
|
1042
|
-
// Clear any existing nodes before creating new ones
|
|
1043
|
-
this.clearD3Visualization();
|
|
1044
|
-
|
|
1045
|
-
// Create new hierarchy
|
|
1046
|
-
this.root = d3.hierarchy(this.treeData);
|
|
1047
|
-
this.root.x0 = this.height / 2;
|
|
1048
|
-
this.root.y0 = 0;
|
|
1049
|
-
|
|
1050
|
-
if (this.svg) {
|
|
1051
|
-
this.update(this.root);
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
this.analyzing = false;
|
|
1056
|
-
this.hideLoading();
|
|
1057
|
-
this.updateBreadcrumb(`Discovered ${data.items.length} root items`, 'success');
|
|
1058
|
-
this.showNotification(`Found ${data.items.length} items in project root`, 'success');
|
|
1059
|
-
}
|
|
1060
|
-
});
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
/**
|
|
1064
|
-
* Handle analysis start event
|
|
1065
|
-
*/
|
|
1066
|
-
onAnalysisStart(data) {
|
|
1067
|
-
this.analyzing = true;
|
|
1068
|
-
const message = data.message || 'Starting code analysis...';
|
|
1069
|
-
|
|
1070
|
-
// Update activity ticker
|
|
1071
|
-
this.updateActivityTicker('🚀 Starting analysis...', 'info');
|
|
1072
|
-
|
|
1073
|
-
this.updateBreadcrumb(message, 'info');
|
|
1074
|
-
this.addEventToDisplay(`🚀 ${message}`, 'info');
|
|
1075
|
-
|
|
1076
|
-
// Initialize or clear the tree
|
|
1077
|
-
if (!this.treeData || this.treeData.children.length === 0) {
|
|
1078
|
-
this.initializeTreeData();
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
// Reset stats
|
|
1082
|
-
this.stats = {
|
|
1083
|
-
files: 0,
|
|
1084
|
-
classes: 0,
|
|
1085
|
-
functions: 0,
|
|
1086
|
-
methods: 0,
|
|
1087
|
-
lines: 0
|
|
1088
|
-
};
|
|
1089
|
-
this.updateStats();
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
/**
|
|
1093
|
-
* Handle top-level discovery event (initial root directory scan)
|
|
1094
|
-
*/
|
|
1095
|
-
onTopLevelDiscovered(data) {
|
|
1096
|
-
// Received top-level discovery response
|
|
1097
|
-
|
|
1098
|
-
// Update activity ticker
|
|
1099
|
-
this.updateActivityTicker(`📁 Discovered ${(data.items || []).length} top-level items`, 'success');
|
|
1100
|
-
|
|
1101
|
-
// Add to events display
|
|
1102
|
-
this.addEventToDisplay(`📁 Found ${(data.items || []).length} top-level items in project root`, 'info');
|
|
1103
|
-
|
|
1104
|
-
// The root node (with path '.') should receive the children
|
|
1105
|
-
const rootNode = this.findNodeByPath('.');
|
|
1106
|
-
|
|
1107
|
-
console.log('🔎 Looking for root node with path ".", found:', rootNode ? {
|
|
1108
|
-
name: rootNode.name,
|
|
1109
|
-
path: rootNode.path,
|
|
1110
|
-
currentChildren: rootNode.children ? rootNode.children.length : 0
|
|
1111
|
-
} : 'NOT FOUND');
|
|
1112
|
-
|
|
1113
|
-
if (rootNode && data.items) {
|
|
1114
|
-
console.log('🌳 Populating root node with children');
|
|
1115
|
-
|
|
1116
|
-
// Update the root node with discovered children
|
|
1117
|
-
rootNode.children = data.items.map(child => {
|
|
1118
|
-
// Items at root level get their name as the path
|
|
1119
|
-
const childPath = child.name;
|
|
1120
|
-
|
|
1121
|
-
console.log(` Adding child: ${child.name} with path: ${childPath}`);
|
|
1122
|
-
|
|
1123
|
-
return {
|
|
1124
|
-
name: child.name,
|
|
1125
|
-
path: childPath, // Just the name for top-level items
|
|
1126
|
-
type: child.type,
|
|
1127
|
-
loaded: child.type === 'directory' ? false : undefined,
|
|
1128
|
-
analyzed: child.type === 'file' ? false : undefined,
|
|
1129
|
-
expanded: false,
|
|
1130
|
-
children: child.type === 'directory' ? [] : undefined,
|
|
1131
|
-
size: child.size,
|
|
1132
|
-
has_code: child.has_code
|
|
1133
|
-
};
|
|
1134
|
-
});
|
|
1135
|
-
|
|
1136
|
-
rootNode.loaded = true;
|
|
1137
|
-
rootNode.expanded = true;
|
|
1138
|
-
|
|
1139
|
-
// Update D3 hierarchy and render
|
|
1140
|
-
if (this.root && this.svg) {
|
|
1141
|
-
// Recreate hierarchy with new data
|
|
1142
|
-
this.root = d3.hierarchy(this.treeData);
|
|
1143
|
-
this.root.x0 = this.height / 2;
|
|
1144
|
-
this.root.y0 = 0;
|
|
1145
|
-
|
|
1146
|
-
// Update the tree visualization
|
|
1147
|
-
this.update(this.root);
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
// Hide loading and show success
|
|
1151
|
-
this.hideLoading();
|
|
1152
|
-
this.updateBreadcrumb(`Discovered ${data.items.length} items`, 'success');
|
|
1153
|
-
this.showNotification(`Found ${data.items.length} top-level items`, 'success');
|
|
1154
|
-
} else {
|
|
1155
|
-
console.error('❌ Could not find root node to populate');
|
|
1156
|
-
this.showNotification('Failed to populate root directory', 'error');
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
// Mark analysis as complete
|
|
1160
|
-
this.analyzing = false;
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
/**
|
|
1164
|
-
* Handle directory discovered event
|
|
1165
|
-
*/
|
|
1166
|
-
onDirectoryDiscovered(data) {
|
|
1167
|
-
// Update activity ticker first
|
|
1168
|
-
this.updateActivityTicker(`📁 Discovered: ${data.name || 'directory'}`);
|
|
1169
|
-
|
|
1170
|
-
// Add to events display
|
|
1171
|
-
this.addEventToDisplay(`📁 Found ${(data.children || []).length} items in: ${data.name || data.path}`, 'info');
|
|
1172
|
-
|
|
1173
|
-
console.log('✅ [SUBDIRECTORY LOADING] Received directory discovery response:', {
|
|
1174
|
-
path: data.path,
|
|
1175
|
-
name: data.name,
|
|
1176
|
-
childrenCount: (data.children || []).length,
|
|
1177
|
-
children: (data.children || []).map(c => ({ name: c.name, type: c.type })),
|
|
1178
|
-
workingDir: this.getWorkingDirectory(),
|
|
1179
|
-
fullEventData: data
|
|
1180
|
-
});
|
|
1181
|
-
|
|
1182
|
-
// Convert absolute path back to relative path to match tree nodes
|
|
1183
|
-
let searchPath = data.path;
|
|
1184
|
-
const workingDir = this.getWorkingDirectory();
|
|
1185
|
-
if (workingDir && searchPath.startsWith(workingDir)) {
|
|
1186
|
-
// Remove working directory prefix to get relative path
|
|
1187
|
-
searchPath = searchPath.substring(workingDir.length).replace(/^\//, '');
|
|
1188
|
-
// If empty after removing prefix, it's the root
|
|
1189
|
-
if (!searchPath) {
|
|
1190
|
-
searchPath = '.';
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
console.log('🔎 Searching for node with path:', searchPath);
|
|
1195
|
-
|
|
1196
|
-
// Find the node that was clicked to trigger this discovery
|
|
1197
|
-
const node = this.findNodeByPath(searchPath);
|
|
1198
|
-
|
|
1199
|
-
// Located target node for expansion
|
|
1200
|
-
|
|
1201
|
-
if (node && data.children) {
|
|
1202
|
-
// Update the node with discovered children
|
|
1203
|
-
node.children = data.children.map(child => {
|
|
1204
|
-
// Construct full path for child by combining parent path with child name
|
|
1205
|
-
// The backend now returns just the item name, not the full path
|
|
1206
|
-
let childPath;
|
|
1207
|
-
if (searchPath === '.' || searchPath === '') {
|
|
1208
|
-
// Root level - child path is just the name
|
|
1209
|
-
childPath = child.name || child.path;
|
|
1210
|
-
} else {
|
|
1211
|
-
// Subdirectory - combine parent path with child name
|
|
1212
|
-
// Use child.name (backend returns just the name) or fallback to child.path
|
|
1213
|
-
const childName = child.name || child.path;
|
|
1214
|
-
childPath = `${searchPath}/${childName}`;
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
return {
|
|
1218
|
-
name: child.name,
|
|
1219
|
-
path: childPath, // Use constructed path instead of child.path
|
|
1220
|
-
type: child.type,
|
|
1221
|
-
loaded: child.type === 'directory' ? false : undefined,
|
|
1222
|
-
analyzed: child.type === 'file' ? false : undefined,
|
|
1223
|
-
expanded: false,
|
|
1224
|
-
children: child.type === 'directory' ? [] : undefined,
|
|
1225
|
-
size: child.size,
|
|
1226
|
-
has_code: child.has_code
|
|
1227
|
-
};
|
|
1228
|
-
});
|
|
1229
|
-
node.loaded = true;
|
|
1230
|
-
node.expanded = true;
|
|
1231
|
-
|
|
1232
|
-
// Find D3 node and remove loading pulse (use searchPath, not data.path)
|
|
1233
|
-
const d3Node = this.findD3NodeByPath(searchPath);
|
|
1234
|
-
if (d3Node) {
|
|
1235
|
-
// Remove loading animation
|
|
1236
|
-
if (this.loadingNodes.has(searchPath)) {
|
|
1237
|
-
this.removeLoadingPulse(d3Node);
|
|
1238
|
-
this.loadingNodes.delete(searchPath); // Remove from loading set
|
|
1239
|
-
console.log('🎯 [SUBDIRECTORY LOADING] Successfully completed and removed from loading set (hierarchy update):', searchPath);
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
// Update D3 hierarchy and redraw with expanded node
|
|
1244
|
-
if (this.root && this.svg) {
|
|
1245
|
-
// Store old root to preserve expansion state
|
|
1246
|
-
const oldRoot = this.root;
|
|
1247
|
-
|
|
1248
|
-
// Recreate hierarchy with updated data
|
|
1249
|
-
this.root = d3.hierarchy(this.treeData);
|
|
1250
|
-
|
|
1251
|
-
// Restore positions for smooth animation
|
|
1252
|
-
this.root.x0 = this.height / 2;
|
|
1253
|
-
this.root.y0 = 0;
|
|
1254
|
-
|
|
1255
|
-
// Preserve expansion state from old tree
|
|
1256
|
-
this.preserveExpansionState(oldRoot, this.root);
|
|
1257
|
-
|
|
1258
|
-
// Find the D3 node again after hierarchy recreation
|
|
1259
|
-
const updatedD3Node = this.findD3NodeByPath(searchPath);
|
|
1260
|
-
if (updatedD3Node && updatedD3Node.data.children && updatedD3Node.data.children.length > 0) {
|
|
1261
|
-
// Ensure the node is expanded to show children
|
|
1262
|
-
updatedD3Node.children = updatedD3Node._children || updatedD3Node.children;
|
|
1263
|
-
updatedD3Node._children = null;
|
|
1264
|
-
// Mark data as expanded
|
|
1265
|
-
updatedD3Node.data.expanded = true;
|
|
1266
|
-
}
|
|
1267
|
-
|
|
1268
|
-
this.update(this.root);
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
// Provide better feedback for empty vs populated directories
|
|
1272
|
-
if (node.children.length === 0) {
|
|
1273
|
-
this.updateBreadcrumb(`Empty directory: ${node.name}`, 'info');
|
|
1274
|
-
this.showNotification(`Directory "${node.name}" is empty`, 'info');
|
|
1275
|
-
} else {
|
|
1276
|
-
this.updateBreadcrumb(`Loaded ${node.children.length} items from ${node.name}`, 'success');
|
|
1277
|
-
this.showNotification(`Loaded ${node.children.length} items from "${node.name}"`, 'success');
|
|
1278
|
-
}
|
|
1279
|
-
this.updateStats();
|
|
1280
|
-
} else if (!node) {
|
|
1281
|
-
console.error('❌ [SUBDIRECTORY LOADING] Node not found for path:', {
|
|
1282
|
-
searchPath,
|
|
1283
|
-
originalPath: data.path,
|
|
1284
|
-
workingDir: this.getWorkingDirectory(),
|
|
1285
|
-
allTreePaths: this.getAllTreePaths(this.treeData)
|
|
1286
|
-
});
|
|
1287
|
-
this.showNotification(`Could not find directory "${searchPath}" in tree`, 'error');
|
|
1288
|
-
this.logAllPaths(this.treeData);
|
|
1289
|
-
} else if (node && !data.children) {
|
|
1290
|
-
console.warn('⚠️ [SUBDIRECTORY LOADING] Directory response has no children:', {
|
|
1291
|
-
path: data.path,
|
|
1292
|
-
searchPath,
|
|
1293
|
-
nodeExists: !!node,
|
|
1294
|
-
dataKeys: Object.keys(data),
|
|
1295
|
-
fullData: data
|
|
1296
|
-
});
|
|
1297
|
-
// This might be a top-level directory discovery
|
|
1298
|
-
const pathParts = data.path ? data.path.split('/').filter(p => p) : [];
|
|
1299
|
-
const isTopLevel = pathParts.length === 1;
|
|
1300
|
-
|
|
1301
|
-
if (isTopLevel || data.forceAdd) {
|
|
1302
|
-
const dirNode = {
|
|
1303
|
-
name: data.name || pathParts[pathParts.length - 1] || 'Unknown',
|
|
1304
|
-
path: data.path,
|
|
1305
|
-
type: 'directory',
|
|
1306
|
-
children: [],
|
|
1307
|
-
loaded: false,
|
|
1308
|
-
expanded: false,
|
|
1309
|
-
stats: data.stats || {}
|
|
1310
|
-
};
|
|
1311
|
-
|
|
1312
|
-
this.addNodeToTree(dirNode, data.parent || '');
|
|
1313
|
-
this.updateBreadcrumb(`Discovered: ${data.path}`, 'info');
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
/**
|
|
1319
|
-
* Handle file discovered event
|
|
1320
|
-
*/
|
|
1321
|
-
onFileDiscovered(data) {
|
|
1322
|
-
// Update activity ticker
|
|
1323
|
-
const fileName = data.name || (data.path ? data.path.split('/').pop() : 'file');
|
|
1324
|
-
this.updateActivityTicker(`📄 Found: ${fileName}`);
|
|
1325
|
-
|
|
1326
|
-
// Add to events display
|
|
1327
|
-
this.addEventToDisplay(`📄 Discovered: ${data.path || 'Unknown file'}`, 'info');
|
|
1328
|
-
|
|
1329
|
-
const pathParts = data.path ? data.path.split('/').filter(p => p) : [];
|
|
1330
|
-
const parentPath = pathParts.slice(0, -1).join('/');
|
|
1331
|
-
|
|
1332
|
-
const fileNode = {
|
|
1333
|
-
name: data.name || pathParts[pathParts.length - 1] || 'Unknown',
|
|
1334
|
-
path: data.path,
|
|
1335
|
-
type: 'file',
|
|
1336
|
-
language: data.language || this.detectLanguage(data.path),
|
|
1337
|
-
size: data.size || 0,
|
|
1338
|
-
lines: data.lines || 0,
|
|
1339
|
-
children: [],
|
|
1340
|
-
analyzed: false
|
|
1341
|
-
};
|
|
1342
|
-
|
|
1343
|
-
this.addNodeToTree(fileNode, parentPath);
|
|
1344
|
-
this.stats.files++;
|
|
1345
|
-
this.updateStats();
|
|
1346
|
-
this.updateBreadcrumb(`Found: ${data.path}`, 'info');
|
|
1347
|
-
}
|
|
1348
|
-
|
|
1349
|
-
/**
|
|
1350
|
-
* Handle file analyzed event
|
|
1351
|
-
*/
|
|
1352
|
-
onFileAnalyzed(data) {
|
|
1353
|
-
// Remove loading pulse if this file was being analyzed
|
|
1354
|
-
const d3Node = this.findD3NodeByPath(data.path);
|
|
1355
|
-
if (d3Node && this.loadingNodes.has(data.path)) {
|
|
1356
|
-
this.removeLoadingPulse(d3Node);
|
|
1357
|
-
this.loadingNodes.delete(data.path); // Remove from loading set
|
|
1358
|
-
}
|
|
1359
|
-
// Update activity ticker
|
|
1360
|
-
if (data.path) {
|
|
1361
|
-
const fileName = data.path.split('/').pop();
|
|
1362
|
-
this.updateActivityTicker(`🔍 Analyzed: ${fileName}`);
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
|
-
const fileNode = this.findNodeByPath(data.path);
|
|
1366
|
-
if (fileNode) {
|
|
1367
|
-
fileNode.analyzed = true;
|
|
1368
|
-
fileNode.complexity = data.complexity || 0;
|
|
1369
|
-
fileNode.lines = data.lines || 0;
|
|
1370
|
-
|
|
1371
|
-
// Add code elements as children
|
|
1372
|
-
if (data.elements && Array.isArray(data.elements)) {
|
|
1373
|
-
fileNode.children = data.elements.map(elem => ({
|
|
1374
|
-
name: elem.name,
|
|
1375
|
-
type: elem.type.toLowerCase(),
|
|
1376
|
-
path: `${data.path}#${elem.name}`,
|
|
1377
|
-
line: elem.line,
|
|
1378
|
-
complexity: elem.complexity || 1,
|
|
1379
|
-
docstring: elem.docstring || '',
|
|
1380
|
-
children: elem.methods ? elem.methods.map(m => ({
|
|
1381
|
-
name: m.name,
|
|
1382
|
-
type: 'method',
|
|
1383
|
-
path: `${data.path}#${elem.name}.${m.name}`,
|
|
1384
|
-
line: m.line,
|
|
1385
|
-
complexity: m.complexity || 1,
|
|
1386
|
-
docstring: m.docstring || ''
|
|
1387
|
-
})) : []
|
|
1388
|
-
}));
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
// Update stats
|
|
1392
|
-
if (data.stats) {
|
|
1393
|
-
this.stats.classes += data.stats.classes || 0;
|
|
1394
|
-
this.stats.functions += data.stats.functions || 0;
|
|
1395
|
-
this.stats.methods += data.stats.methods || 0;
|
|
1396
|
-
this.stats.lines += data.stats.lines || 0;
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
this.updateStats();
|
|
1400
|
-
if (this.root) {
|
|
1401
|
-
this.update(this.root);
|
|
1402
|
-
}
|
|
1403
|
-
|
|
1404
|
-
this.updateBreadcrumb(`Analyzed: ${data.path}`, 'success');
|
|
1405
|
-
}
|
|
1406
|
-
}
|
|
1407
|
-
|
|
1408
|
-
/**
|
|
1409
|
-
* Handle node found event
|
|
1410
|
-
*/
|
|
1411
|
-
onNodeFound(data) {
|
|
1412
|
-
// Add to events display with appropriate icon
|
|
1413
|
-
const typeIcon = data.type === 'class' ? '🏛️' :
|
|
1414
|
-
data.type === 'function' ? '⚡' :
|
|
1415
|
-
data.type === 'method' ? '🔧' : '📦';
|
|
1416
|
-
this.addEventToDisplay(`${typeIcon} Found ${data.type || 'node'}: ${data.name || 'Unknown'}`);
|
|
1417
|
-
|
|
1418
|
-
// Extract node info
|
|
1419
|
-
const nodeInfo = {
|
|
1420
|
-
name: data.name || 'Unknown',
|
|
1421
|
-
type: (data.type || 'unknown').toLowerCase(),
|
|
1422
|
-
path: data.path || '',
|
|
1423
|
-
line: data.line || 0,
|
|
1424
|
-
complexity: data.complexity || 1,
|
|
1425
|
-
docstring: data.docstring || ''
|
|
1426
|
-
};
|
|
1427
|
-
|
|
1428
|
-
// Map event types to our internal types
|
|
1429
|
-
const typeMapping = {
|
|
1430
|
-
'class': 'class',
|
|
1431
|
-
'function': 'function',
|
|
1432
|
-
'method': 'method',
|
|
1433
|
-
'module': 'module',
|
|
1434
|
-
'file': 'file',
|
|
1435
|
-
'directory': 'directory'
|
|
1436
|
-
};
|
|
1437
|
-
|
|
1438
|
-
nodeInfo.type = typeMapping[nodeInfo.type] || nodeInfo.type;
|
|
1439
|
-
|
|
1440
|
-
// Determine parent path
|
|
1441
|
-
let parentPath = '';
|
|
1442
|
-
if (data.parent_path) {
|
|
1443
|
-
parentPath = data.parent_path;
|
|
1444
|
-
} else if (data.file_path) {
|
|
1445
|
-
parentPath = data.file_path;
|
|
1446
|
-
} else if (nodeInfo.path.includes('/')) {
|
|
1447
|
-
const parts = nodeInfo.path.split('/');
|
|
1448
|
-
parts.pop();
|
|
1449
|
-
parentPath = parts.join('/');
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
// Update stats based on node type
|
|
1453
|
-
switch(nodeInfo.type) {
|
|
1454
|
-
case 'class':
|
|
1455
|
-
this.stats.classes++;
|
|
1456
|
-
break;
|
|
1457
|
-
case 'function':
|
|
1458
|
-
this.stats.functions++;
|
|
1459
|
-
break;
|
|
1460
|
-
case 'method':
|
|
1461
|
-
this.stats.methods++;
|
|
1462
|
-
break;
|
|
1463
|
-
case 'file':
|
|
1464
|
-
this.stats.files++;
|
|
1465
|
-
break;
|
|
1466
|
-
}
|
|
1467
|
-
|
|
1468
|
-
// Add node to tree
|
|
1469
|
-
this.addNodeToTree(nodeInfo, parentPath);
|
|
1470
|
-
this.updateStats();
|
|
1471
|
-
|
|
1472
|
-
// Show progress in breadcrumb
|
|
1473
|
-
const elementType = nodeInfo.type.charAt(0).toUpperCase() + nodeInfo.type.slice(1);
|
|
1474
|
-
this.updateBreadcrumb(`Found ${elementType}: ${nodeInfo.name}`, 'info');
|
|
1475
|
-
}
|
|
1476
|
-
|
|
1477
|
-
/**
|
|
1478
|
-
* Handle progress update
|
|
1479
|
-
*/
|
|
1480
|
-
onProgressUpdate(data) {
|
|
1481
|
-
const progress = data.progress || 0;
|
|
1482
|
-
const message = data.message || `Processing... ${progress}%`;
|
|
1483
|
-
|
|
1484
|
-
this.updateBreadcrumb(message, 'info');
|
|
1485
|
-
|
|
1486
|
-
// Update progress bar if it exists
|
|
1487
|
-
const progressBar = document.querySelector('.code-tree-progress');
|
|
1488
|
-
if (progressBar) {
|
|
1489
|
-
progressBar.style.width = `${progress}%`;
|
|
1490
|
-
}
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1493
|
-
/**
|
|
1494
|
-
* Handle analysis complete event
|
|
1495
|
-
*/
|
|
1496
|
-
onAnalysisComplete(data) {
|
|
1497
|
-
this.analyzing = false;
|
|
1498
|
-
this.hideLoading();
|
|
1499
|
-
|
|
1500
|
-
// Update activity ticker
|
|
1501
|
-
this.updateActivityTicker('✅ Ready', 'success');
|
|
1502
|
-
|
|
1503
|
-
// Add completion event
|
|
1504
|
-
this.addEventToDisplay('✅ Analysis complete!', 'success');
|
|
1505
|
-
|
|
1506
|
-
// Update tree visualization
|
|
1507
|
-
if (this.root && this.svg) {
|
|
1508
|
-
this.update(this.root);
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1511
|
-
// Update stats from completion data
|
|
1512
|
-
if (data.stats) {
|
|
1513
|
-
this.stats = { ...this.stats, ...data.stats };
|
|
1514
|
-
this.updateStats();
|
|
1515
|
-
}
|
|
1516
|
-
|
|
1517
|
-
const message = data.message || `Analysis complete: ${this.stats.files} files, ${this.stats.classes} classes, ${this.stats.functions} functions`;
|
|
1518
|
-
this.updateBreadcrumb(message, 'success');
|
|
1519
|
-
this.showNotification(message, 'success');
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1522
|
-
/**
|
|
1523
|
-
* Handle analysis error
|
|
1524
|
-
*/
|
|
1525
|
-
onAnalysisError(data) {
|
|
1526
|
-
this.analyzing = false;
|
|
1527
|
-
this.hideLoading();
|
|
1528
|
-
|
|
1529
|
-
const message = data.message || data.error || 'Analysis failed';
|
|
1530
|
-
this.updateBreadcrumb(message, 'error');
|
|
1531
|
-
this.showNotification(message, 'error');
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
/**
|
|
1535
|
-
* Handle analysis accepted
|
|
1536
|
-
*/
|
|
1537
|
-
onAnalysisAccepted(data) {
|
|
1538
|
-
const message = data.message || 'Analysis request accepted';
|
|
1539
|
-
this.updateBreadcrumb(message, 'info');
|
|
1540
|
-
}
|
|
1541
|
-
|
|
1542
|
-
/**
|
|
1543
|
-
* Handle analysis queued
|
|
1544
|
-
*/
|
|
1545
|
-
onAnalysisQueued(data) {
|
|
1546
|
-
const position = data.position || 0;
|
|
1547
|
-
const message = `Analysis queued (position ${position})`;
|
|
1548
|
-
this.updateBreadcrumb(message, 'warning');
|
|
1549
|
-
this.showNotification(message, 'info');
|
|
1550
|
-
}
|
|
1551
|
-
|
|
1552
|
-
/**
|
|
1553
|
-
* Handle INFO events for granular work tracking
|
|
1554
|
-
*/
|
|
1555
|
-
onInfoEvent(data) {
|
|
1556
|
-
// Log to console for debugging
|
|
1557
|
-
|
|
1558
|
-
// Update breadcrumb for certain events
|
|
1559
|
-
if (data.type && data.type.startsWith('discovery.')) {
|
|
1560
|
-
// Discovery events
|
|
1561
|
-
if (data.type === 'discovery.start') {
|
|
1562
|
-
this.updateBreadcrumb(data.message, 'info');
|
|
1563
|
-
} else if (data.type === 'discovery.complete') {
|
|
1564
|
-
this.updateBreadcrumb(data.message, 'success');
|
|
1565
|
-
// Show stats if available
|
|
1566
|
-
if (data.stats) {
|
|
1567
|
-
}
|
|
1568
|
-
} else if (data.type === 'discovery.directory' || data.type === 'discovery.file') {
|
|
1569
|
-
// Quick flash of discovery events
|
|
1570
|
-
this.updateBreadcrumb(data.message, 'info');
|
|
1571
|
-
}
|
|
1572
|
-
} else if (data.type && data.type.startsWith('analysis.')) {
|
|
1573
|
-
// Analysis events
|
|
1574
|
-
if (data.type === 'analysis.start') {
|
|
1575
|
-
this.updateBreadcrumb(data.message, 'info');
|
|
1576
|
-
} else if (data.type === 'analysis.complete') {
|
|
1577
|
-
this.updateBreadcrumb(data.message, 'success');
|
|
1578
|
-
// Show stats if available
|
|
1579
|
-
if (data.stats) {
|
|
1580
|
-
const statsMsg = `Found: ${data.stats.classes || 0} classes, ${data.stats.functions || 0} functions, ${data.stats.methods || 0} methods`;
|
|
1581
|
-
}
|
|
1582
|
-
} else if (data.type === 'analysis.class' || data.type === 'analysis.function' || data.type === 'analysis.method') {
|
|
1583
|
-
// Show found elements briefly
|
|
1584
|
-
this.updateBreadcrumb(data.message, 'info');
|
|
1585
|
-
} else if (data.type === 'analysis.parse') {
|
|
1586
|
-
this.updateBreadcrumb(data.message, 'info');
|
|
1587
|
-
}
|
|
1588
|
-
} else if (data.type && data.type.startsWith('filter.')) {
|
|
1589
|
-
// Filter events - optionally show in debug mode
|
|
1590
|
-
if (window.debugMode || this.showFilterEvents) {
|
|
1591
|
-
console.debug('[FILTER]', data.type, data.path, data.reason);
|
|
1592
|
-
if (this.showFilterEvents) {
|
|
1593
|
-
this.updateBreadcrumb(data.message, 'warning');
|
|
1594
|
-
}
|
|
1595
|
-
}
|
|
1596
|
-
} else if (data.type && data.type.startsWith('cache.')) {
|
|
1597
|
-
// Cache events
|
|
1598
|
-
if (data.type === 'cache.hit') {
|
|
1599
|
-
console.debug('[CACHE HIT]', data.file);
|
|
1600
|
-
if (this.showCacheEvents) {
|
|
1601
|
-
this.updateBreadcrumb(data.message, 'info');
|
|
1602
|
-
}
|
|
1603
|
-
} else if (data.type === 'cache.miss') {
|
|
1604
|
-
console.debug('[CACHE MISS]', data.file);
|
|
1605
|
-
}
|
|
1606
|
-
}
|
|
1607
|
-
|
|
1608
|
-
// Optionally add to an event log display if enabled
|
|
1609
|
-
if (this.eventLogEnabled && data.message) {
|
|
1610
|
-
this.addEventToDisplay(data);
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1613
|
-
|
|
1614
|
-
/**
|
|
1615
|
-
* Add event to display log (if we have one)
|
|
1616
|
-
*/
|
|
1617
|
-
addEventToDisplay(data) {
|
|
1618
|
-
// Could be implemented to show events in a dedicated log area
|
|
1619
|
-
// For now, just maintain a recent events list
|
|
1620
|
-
if (!this.recentEvents) {
|
|
1621
|
-
this.recentEvents = [];
|
|
1622
|
-
}
|
|
1623
|
-
|
|
1624
|
-
this.recentEvents.unshift({
|
|
1625
|
-
timestamp: data.timestamp || new Date().toISOString(),
|
|
1626
|
-
type: data.type,
|
|
1627
|
-
message: data.message,
|
|
1628
|
-
data: data
|
|
1629
|
-
});
|
|
1630
|
-
|
|
1631
|
-
// Keep only last 100 events
|
|
1632
|
-
if (this.recentEvents.length > 100) {
|
|
1633
|
-
this.recentEvents.pop();
|
|
1634
|
-
}
|
|
1635
|
-
|
|
1636
|
-
// Could update a UI element here if we had an event log display
|
|
1637
|
-
}
|
|
1638
|
-
|
|
1639
|
-
/**
|
|
1640
|
-
* Handle analysis cancelled
|
|
1641
|
-
*/
|
|
1642
|
-
onAnalysisCancelled(data) {
|
|
1643
|
-
this.analyzing = false;
|
|
1644
|
-
this.hideLoading();
|
|
1645
|
-
const message = data.message || 'Analysis cancelled';
|
|
1646
|
-
this.updateBreadcrumb(message, 'warning');
|
|
1647
|
-
}
|
|
1648
|
-
|
|
1649
|
-
/**
|
|
1650
|
-
* Show notification toast
|
|
1651
|
-
*/
|
|
1652
|
-
showNotification(message, type = 'info') {
|
|
1653
|
-
const notification = document.createElement('div');
|
|
1654
|
-
notification.className = `code-tree-notification ${type}`;
|
|
1655
|
-
notification.textContent = message;
|
|
1656
|
-
|
|
1657
|
-
// Change from appending to container to positioning absolutely within it
|
|
1658
|
-
const container = document.getElementById('code-tree-container');
|
|
1659
|
-
if (container) {
|
|
1660
|
-
// Position relative to the container
|
|
1661
|
-
notification.style.position = 'absolute';
|
|
1662
|
-
notification.style.top = '10px';
|
|
1663
|
-
notification.style.right = '10px';
|
|
1664
|
-
notification.style.zIndex = '1000';
|
|
1665
|
-
|
|
1666
|
-
// Ensure container is positioned
|
|
1667
|
-
if (!container.style.position || container.style.position === 'static') {
|
|
1668
|
-
container.style.position = 'relative';
|
|
1669
|
-
}
|
|
1670
|
-
|
|
1671
|
-
container.appendChild(notification);
|
|
1672
|
-
|
|
1673
|
-
// Animate out after 3 seconds
|
|
1674
|
-
setTimeout(() => {
|
|
1675
|
-
notification.style.animation = 'slideOutRight 0.3s ease';
|
|
1676
|
-
setTimeout(() => notification.remove(), 300);
|
|
1677
|
-
}, 3000);
|
|
1678
|
-
}
|
|
1679
|
-
}
|
|
1680
|
-
|
|
1681
|
-
/**
|
|
1682
|
-
* Add node to tree structure
|
|
1683
|
-
*/
|
|
1684
|
-
addNodeToTree(nodeInfo, parentPath = '') {
|
|
1685
|
-
// CRITICAL: Validate that nodeInfo.path doesn't contain absolute paths
|
|
1686
|
-
// The backend should only send relative paths now
|
|
1687
|
-
if (nodeInfo.path && nodeInfo.path.startsWith('/')) {
|
|
1688
|
-
console.error('Absolute path detected in node, skipping:', nodeInfo.path);
|
|
1689
|
-
return;
|
|
1690
|
-
}
|
|
1691
|
-
|
|
1692
|
-
// Also validate parent path
|
|
1693
|
-
if (parentPath && parentPath.startsWith('/')) {
|
|
1694
|
-
console.error('Absolute path detected in parent, skipping:', parentPath);
|
|
1695
|
-
return;
|
|
1696
|
-
}
|
|
1697
|
-
|
|
1698
|
-
// Find parent node
|
|
1699
|
-
let parentNode = this.treeData;
|
|
1700
|
-
|
|
1701
|
-
if (parentPath) {
|
|
1702
|
-
parentNode = this.findNodeByPath(parentPath);
|
|
1703
|
-
if (!parentNode) {
|
|
1704
|
-
// CRITICAL: Do NOT create parent structure if it doesn't exist
|
|
1705
|
-
// This prevents creating nodes above the working directory
|
|
1706
|
-
console.warn('Parent node not found, skipping node creation:', parentPath);
|
|
1707
|
-
console.warn('Attempted to add node:', nodeInfo);
|
|
1708
|
-
return;
|
|
1709
|
-
}
|
|
1710
|
-
}
|
|
1711
|
-
|
|
1712
|
-
// Check if node already exists
|
|
1713
|
-
const existingNode = parentNode.children?.find(c =>
|
|
1714
|
-
c.path === nodeInfo.path ||
|
|
1715
|
-
(c.name === nodeInfo.name && c.type === nodeInfo.type)
|
|
1716
|
-
);
|
|
1717
|
-
|
|
1718
|
-
if (existingNode) {
|
|
1719
|
-
// Update existing node
|
|
1720
|
-
Object.assign(existingNode, nodeInfo);
|
|
1721
|
-
return;
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
// Add new node
|
|
1725
|
-
if (!parentNode.children) {
|
|
1726
|
-
parentNode.children = [];
|
|
1727
|
-
}
|
|
1728
|
-
|
|
1729
|
-
// Ensure the node has a children array
|
|
1730
|
-
if (!nodeInfo.children) {
|
|
1731
|
-
nodeInfo.children = [];
|
|
1732
|
-
}
|
|
1733
|
-
|
|
1734
|
-
parentNode.children.push(nodeInfo);
|
|
1735
|
-
|
|
1736
|
-
// Store node reference for quick access
|
|
1737
|
-
this.nodes.set(nodeInfo.path, nodeInfo);
|
|
1738
|
-
|
|
1739
|
-
// Update tree if initialized
|
|
1740
|
-
if (this.root && this.svg) {
|
|
1741
|
-
// Recreate hierarchy with new data
|
|
1742
|
-
this.root = d3.hierarchy(this.treeData);
|
|
1743
|
-
this.root.x0 = this.height / 2;
|
|
1744
|
-
this.root.y0 = 0;
|
|
1745
|
-
|
|
1746
|
-
// Update only if we have a reasonable number of nodes to avoid performance issues
|
|
1747
|
-
if (this.nodes.size < 1000) {
|
|
1748
|
-
this.update(this.root);
|
|
1749
|
-
} else if (this.nodes.size % 100 === 0) {
|
|
1750
|
-
// Update every 100 nodes for large trees
|
|
1751
|
-
this.update(this.root);
|
|
1752
|
-
}
|
|
1753
|
-
}
|
|
1754
|
-
}
|
|
1755
|
-
|
|
1756
|
-
/**
|
|
1757
|
-
* Find node by path in tree
|
|
1758
|
-
*/
|
|
1759
|
-
findNodeByPath(path, node = null) {
|
|
1760
|
-
if (!node) {
|
|
1761
|
-
node = this.treeData;
|
|
1762
|
-
console.log('🔍 [SUBDIRECTORY LOADING] Starting search for path:', path);
|
|
1763
|
-
}
|
|
1764
|
-
|
|
1765
|
-
if (node.path === path) {
|
|
1766
|
-
console.log('✅ [SUBDIRECTORY LOADING] Found node for path:', path);
|
|
1767
|
-
return node;
|
|
1768
|
-
}
|
|
1769
|
-
|
|
1770
|
-
if (node.children) {
|
|
1771
|
-
for (const child of node.children) {
|
|
1772
|
-
const found = this.findNodeByPath(path, child);
|
|
1773
|
-
if (found) {
|
|
1774
|
-
return found;
|
|
1775
|
-
}
|
|
1776
|
-
}
|
|
1777
|
-
}
|
|
1778
|
-
|
|
1779
|
-
if (!node.parent && node === this.treeData) {
|
|
1780
|
-
console.warn('❌ [SUBDIRECTORY LOADING] Path not found in tree:', path);
|
|
1781
|
-
}
|
|
1782
|
-
return null;
|
|
1783
|
-
}
|
|
1784
|
-
|
|
1785
|
-
/**
|
|
1786
|
-
* Helper to log all paths in tree for debugging
|
|
1787
|
-
*/
|
|
1788
|
-
logAllPaths(node, indent = '') {
|
|
1789
|
-
console.log(`${indent}${node.path} (${node.name})`);
|
|
1790
|
-
if (node.children) {
|
|
1791
|
-
for (const child of node.children) {
|
|
1792
|
-
this.logAllPaths(child, indent + ' ');
|
|
1793
|
-
}
|
|
1794
|
-
}
|
|
1795
|
-
}
|
|
1796
|
-
|
|
1797
|
-
/**
|
|
1798
|
-
* Helper to collect all paths in tree for debugging
|
|
1799
|
-
*/
|
|
1800
|
-
getAllTreePaths(node) {
|
|
1801
|
-
const paths = [node.path];
|
|
1802
|
-
if (node.children) {
|
|
1803
|
-
for (const child of node.children) {
|
|
1804
|
-
paths.push(...this.getAllTreePaths(child));
|
|
1805
|
-
}
|
|
1806
|
-
}
|
|
1807
|
-
return paths;
|
|
1808
|
-
}
|
|
1809
|
-
|
|
1810
|
-
/**
|
|
1811
|
-
* Find D3 hierarchy node by path
|
|
1812
|
-
*/
|
|
1813
|
-
findD3NodeByPath(path) {
|
|
1814
|
-
if (!this.root) return null;
|
|
1815
|
-
return this.root.descendants().find(d => d.data.path === path);
|
|
1816
|
-
}
|
|
1817
|
-
|
|
1818
|
-
/**
|
|
1819
|
-
* Preserve expansion state when recreating hierarchy
|
|
1820
|
-
*/
|
|
1821
|
-
preserveExpansionState(oldRoot, newRoot) {
|
|
1822
|
-
if (!oldRoot || !newRoot) return;
|
|
1823
|
-
|
|
1824
|
-
// Create a map of expanded nodes from the old tree
|
|
1825
|
-
const expansionMap = new Map();
|
|
1826
|
-
oldRoot.descendants().forEach(node => {
|
|
1827
|
-
if (node.data.expanded || (node.children && !node._children)) {
|
|
1828
|
-
expansionMap.set(node.data.path, true);
|
|
1829
|
-
}
|
|
1830
|
-
});
|
|
1831
|
-
|
|
1832
|
-
// Apply expansion state to new tree
|
|
1833
|
-
newRoot.descendants().forEach(node => {
|
|
1834
|
-
if (expansionMap.has(node.data.path)) {
|
|
1835
|
-
node.children = node._children || node.children;
|
|
1836
|
-
node._children = null;
|
|
1837
|
-
node.data.expanded = true;
|
|
1838
|
-
}
|
|
1839
|
-
});
|
|
1840
|
-
}
|
|
1841
|
-
|
|
1842
|
-
/**
|
|
1843
|
-
* Update statistics display
|
|
1844
|
-
*/
|
|
1845
|
-
updateStats() {
|
|
1846
|
-
// Update stats display - use correct IDs from HTML
|
|
1847
|
-
const statsElements = {
|
|
1848
|
-
'file-count': this.stats.files,
|
|
1849
|
-
'class-count': this.stats.classes,
|
|
1850
|
-
'function-count': this.stats.functions,
|
|
1851
|
-
'line-count': this.stats.lines
|
|
1852
|
-
};
|
|
1853
|
-
|
|
1854
|
-
for (const [id, value] of Object.entries(statsElements)) {
|
|
1855
|
-
const elem = document.getElementById(id);
|
|
1856
|
-
if (elem) {
|
|
1857
|
-
elem.textContent = value.toLocaleString();
|
|
1858
|
-
}
|
|
1859
|
-
}
|
|
1860
|
-
|
|
1861
|
-
// Update progress text
|
|
1862
|
-
const progressText = document.getElementById('code-progress-text');
|
|
1863
|
-
if (progressText) {
|
|
1864
|
-
const statusText = this.analyzing ?
|
|
1865
|
-
`Analyzing... ${this.stats.files} files processed` :
|
|
1866
|
-
`Ready - ${this.stats.files} files in tree`;
|
|
1867
|
-
progressText.textContent = statusText;
|
|
1868
|
-
}
|
|
1869
|
-
}
|
|
1870
|
-
|
|
1871
|
-
/**
|
|
1872
|
-
* Update breadcrumb trail
|
|
1873
|
-
*/
|
|
1874
|
-
updateBreadcrumb(message, type = 'info') {
|
|
1875
|
-
const breadcrumbContent = document.getElementById('breadcrumb-content');
|
|
1876
|
-
if (breadcrumbContent) {
|
|
1877
|
-
breadcrumbContent.textContent = message;
|
|
1878
|
-
breadcrumbContent.className = `breadcrumb-${type}`;
|
|
1879
|
-
}
|
|
1880
|
-
}
|
|
1881
|
-
|
|
1882
|
-
/**
|
|
1883
|
-
* Detect language from file extension
|
|
1884
|
-
*/
|
|
1885
|
-
detectLanguage(filePath) {
|
|
1886
|
-
const ext = filePath.split('.').pop().toLowerCase();
|
|
1887
|
-
const languageMap = {
|
|
1888
|
-
'py': 'python',
|
|
1889
|
-
'js': 'javascript',
|
|
1890
|
-
'ts': 'typescript',
|
|
1891
|
-
'jsx': 'javascript',
|
|
1892
|
-
'tsx': 'typescript',
|
|
1893
|
-
'java': 'java',
|
|
1894
|
-
'cpp': 'cpp',
|
|
1895
|
-
'c': 'c',
|
|
1896
|
-
'cs': 'csharp',
|
|
1897
|
-
'rb': 'ruby',
|
|
1898
|
-
'go': 'go',
|
|
1899
|
-
'rs': 'rust',
|
|
1900
|
-
'php': 'php',
|
|
1901
|
-
'swift': 'swift',
|
|
1902
|
-
'kt': 'kotlin',
|
|
1903
|
-
'scala': 'scala',
|
|
1904
|
-
'r': 'r',
|
|
1905
|
-
'sh': 'bash',
|
|
1906
|
-
'ps1': 'powershell'
|
|
1907
|
-
};
|
|
1908
|
-
return languageMap[ext] || 'unknown';
|
|
1909
|
-
}
|
|
1910
|
-
|
|
1911
|
-
/**
|
|
1912
|
-
* Add visualization controls for layout toggle
|
|
1913
|
-
*/
|
|
1914
|
-
addVisualizationControls() {
|
|
1915
|
-
const controls = this.svg.append('g')
|
|
1916
|
-
.attr('class', 'viz-controls')
|
|
1917
|
-
.attr('transform', 'translate(10, 10)');
|
|
1918
|
-
|
|
1919
|
-
// Add layout toggle button
|
|
1920
|
-
const toggleButton = controls.append('g')
|
|
1921
|
-
.attr('class', 'layout-toggle')
|
|
1922
|
-
.style('cursor', 'pointer')
|
|
1923
|
-
.on('click', () => this.toggleLayout());
|
|
1924
|
-
|
|
1925
|
-
toggleButton.append('rect')
|
|
1926
|
-
.attr('width', 120)
|
|
1927
|
-
.attr('height', 30)
|
|
1928
|
-
.attr('rx', 5)
|
|
1929
|
-
.attr('fill', '#3b82f6')
|
|
1930
|
-
.attr('opacity', 0.8);
|
|
1931
|
-
|
|
1932
|
-
toggleButton.append('text')
|
|
1933
|
-
.attr('x', 60)
|
|
1934
|
-
.attr('y', 20)
|
|
1935
|
-
.attr('text-anchor', 'middle')
|
|
1936
|
-
.attr('fill', 'white')
|
|
1937
|
-
.style('font-size', '12px')
|
|
1938
|
-
.text(this.isRadialLayout ? 'Switch to Linear' : 'Switch to Radial');
|
|
1939
|
-
}
|
|
1940
|
-
|
|
1941
|
-
/**
|
|
1942
|
-
* Toggle between radial and linear layouts
|
|
1943
|
-
*/
|
|
1944
|
-
toggleLayout() {
|
|
1945
|
-
this.isRadialLayout = !this.isRadialLayout;
|
|
1946
|
-
this.createVisualization();
|
|
1947
|
-
if (this.root) {
|
|
1948
|
-
this.update(this.root);
|
|
1949
|
-
}
|
|
1950
|
-
this.showNotification(
|
|
1951
|
-
this.isRadialLayout ? 'Switched to radial layout' : 'Switched to linear layout',
|
|
1952
|
-
'info'
|
|
1953
|
-
);
|
|
1954
|
-
}
|
|
1955
|
-
|
|
1956
|
-
/**
|
|
1957
|
-
* Convert radial coordinates to Cartesian
|
|
1958
|
-
*/
|
|
1959
|
-
radialPoint(x, y) {
|
|
1960
|
-
return [(y = +y) * Math.cos(x -= Math.PI / 2), y * Math.sin(x)];
|
|
1961
|
-
}
|
|
1962
|
-
|
|
1963
|
-
/**
|
|
1964
|
-
* Update D3 tree visualization
|
|
1965
|
-
*/
|
|
1966
|
-
update(source) {
|
|
1967
|
-
if (!this.treeLayout || !this.treeGroup || !source) {
|
|
1968
|
-
return;
|
|
1969
|
-
}
|
|
1970
|
-
|
|
1971
|
-
// Compute the new tree layout
|
|
1972
|
-
const treeData = this.treeLayout(this.root);
|
|
1973
|
-
const nodes = treeData.descendants();
|
|
1974
|
-
const links = treeData.descendants().slice(1);
|
|
1975
|
-
|
|
1976
|
-
if (this.isRadialLayout) {
|
|
1977
|
-
// Radial layout adjustments
|
|
1978
|
-
nodes.forEach(d => {
|
|
1979
|
-
// Store original x,y for transitions
|
|
1980
|
-
if (d.x0 === undefined) {
|
|
1981
|
-
d.x0 = d.x;
|
|
1982
|
-
d.y0 = d.y;
|
|
1983
|
-
}
|
|
1984
|
-
});
|
|
1985
|
-
} else {
|
|
1986
|
-
// Linear layout with nodeSize doesn't need manual normalization
|
|
1987
|
-
// The tree layout handles spacing automatically
|
|
1988
|
-
}
|
|
1989
|
-
|
|
1990
|
-
// Update nodes
|
|
1991
|
-
const node = this.treeGroup.selectAll('g.node')
|
|
1992
|
-
.data(nodes, d => d.id || (d.id = ++this.nodeId));
|
|
1993
|
-
|
|
1994
|
-
// Enter new nodes
|
|
1995
|
-
const nodeEnter = node.enter().append('g')
|
|
1996
|
-
.attr('class', d => {
|
|
1997
|
-
let classes = ['node', 'code-node'];
|
|
1998
|
-
if (d.data.type === 'directory') {
|
|
1999
|
-
classes.push('directory');
|
|
2000
|
-
if (d.data.loaded === true && d.children) {
|
|
2001
|
-
classes.push('expanded');
|
|
2002
|
-
}
|
|
2003
|
-
if (d.data.loaded === 'loading') {
|
|
2004
|
-
classes.push('loading');
|
|
2005
|
-
}
|
|
2006
|
-
if (d.data.children && d.data.children.length === 0) {
|
|
2007
|
-
classes.push('empty');
|
|
2008
|
-
}
|
|
2009
|
-
} else if (d.data.type === 'file') {
|
|
2010
|
-
classes.push('file');
|
|
2011
|
-
}
|
|
2012
|
-
return classes.join(' ');
|
|
2013
|
-
})
|
|
2014
|
-
.attr('transform', d => {
|
|
2015
|
-
if (this.isRadialLayout) {
|
|
2016
|
-
const [x, y] = this.radialPoint(source.x0 || 0, source.y0 || 0);
|
|
2017
|
-
return `translate(${x},${y})`;
|
|
2018
|
-
} else {
|
|
2019
|
-
return `translate(${source.y0},${source.x0})`;
|
|
2020
|
-
}
|
|
2021
|
-
})
|
|
2022
|
-
.on('click', (event, d) => this.onNodeClick(event, d));
|
|
2023
|
-
|
|
2024
|
-
// Add circles for nodes
|
|
2025
|
-
nodeEnter.append('circle')
|
|
2026
|
-
.attr('class', 'node-circle')
|
|
2027
|
-
.attr('r', 1e-6)
|
|
2028
|
-
.style('fill', d => this.getNodeColor(d))
|
|
2029
|
-
.style('stroke', d => this.getNodeStrokeColor(d))
|
|
2030
|
-
.style('stroke-width', d => d.data.type === 'directory' ? 2 : 1.5)
|
|
2031
|
-
.style('cursor', 'pointer') // Add cursor pointer for visual feedback
|
|
2032
|
-
.on('click', (event, d) => this.onNodeClick(event, d)) // CRITICAL FIX: Add click handler to circles
|
|
2033
|
-
.on('mouseover', (event, d) => this.showTooltip(event, d))
|
|
2034
|
-
.on('mouseout', () => this.hideTooltip());
|
|
2035
|
-
|
|
2036
|
-
// Add expand/collapse icons for directories
|
|
2037
|
-
nodeEnter.filter(d => d.data.type === 'directory')
|
|
2038
|
-
.append('text')
|
|
2039
|
-
.attr('class', 'expand-icon')
|
|
2040
|
-
.attr('x', 0)
|
|
2041
|
-
.attr('y', 0)
|
|
2042
|
-
.attr('text-anchor', 'middle')
|
|
2043
|
-
.attr('dominant-baseline', 'central')
|
|
2044
|
-
.text(d => {
|
|
2045
|
-
if (d.data.loaded === 'loading') return '⟳';
|
|
2046
|
-
if (d.data.loaded === true && d.children) return '▼';
|
|
2047
|
-
return '▶';
|
|
2048
|
-
})
|
|
2049
|
-
.style('font-size', '10px')
|
|
2050
|
-
.style('pointer-events', 'none');
|
|
2051
|
-
|
|
2052
|
-
// Add labels for nodes with smart positioning
|
|
2053
|
-
nodeEnter.append('text')
|
|
2054
|
-
.attr('class', 'node-label')
|
|
2055
|
-
.attr('dy', '.35em')
|
|
2056
|
-
.attr('x', d => {
|
|
2057
|
-
if (this.isRadialLayout) {
|
|
2058
|
-
// For radial layout, initial position
|
|
2059
|
-
return 0;
|
|
2060
|
-
} else {
|
|
2061
|
-
// Linear layout: standard positioning
|
|
2062
|
-
return d.children || d._children ? -13 : 13;
|
|
2063
|
-
}
|
|
2064
|
-
})
|
|
2065
|
-
.attr('text-anchor', d => {
|
|
2066
|
-
if (this.isRadialLayout) {
|
|
2067
|
-
return 'start'; // Will be adjusted in update
|
|
2068
|
-
} else {
|
|
2069
|
-
// Linear layout: standard anchoring
|
|
2070
|
-
return d.children || d._children ? 'end' : 'start';
|
|
2071
|
-
}
|
|
2072
|
-
})
|
|
2073
|
-
.text(d => {
|
|
2074
|
-
// Truncate long names
|
|
2075
|
-
const maxLength = 20;
|
|
2076
|
-
const name = d.data.name || '';
|
|
2077
|
-
return name.length > maxLength ?
|
|
2078
|
-
name.substring(0, maxLength - 3) + '...' : name;
|
|
2079
|
-
})
|
|
2080
|
-
.style('fill-opacity', 1e-6)
|
|
2081
|
-
.style('font-size', '12px')
|
|
2082
|
-
.style('font-family', '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif')
|
|
2083
|
-
.style('text-shadow', '1px 1px 2px rgba(255,255,255,0.8), -1px -1px 2px rgba(255,255,255,0.8)')
|
|
2084
|
-
.on('click', (event, d) => this.onNodeClick(event, d)) // CRITICAL FIX: Add click handler to labels
|
|
2085
|
-
.style('cursor', 'pointer');
|
|
2086
|
-
|
|
2087
|
-
// Add icons for node types (files only, directories use expand icons)
|
|
2088
|
-
nodeEnter.filter(d => d.data.type !== 'directory')
|
|
2089
|
-
.append('text')
|
|
2090
|
-
.attr('class', 'node-icon')
|
|
2091
|
-
.attr('dy', '.35em')
|
|
2092
|
-
.attr('x', 0)
|
|
2093
|
-
.attr('text-anchor', 'middle')
|
|
2094
|
-
.text(d => this.getNodeIcon(d))
|
|
2095
|
-
.style('font-size', '10px')
|
|
2096
|
-
.style('fill', 'white')
|
|
2097
|
-
.on('click', (event, d) => this.onNodeClick(event, d)) // CRITICAL FIX: Add click handler to file icons
|
|
2098
|
-
.style('cursor', 'pointer');
|
|
2099
|
-
|
|
2100
|
-
// Add item count badges for directories
|
|
2101
|
-
nodeEnter.filter(d => d.data.type === 'directory' && d.data.children)
|
|
2102
|
-
.append('text')
|
|
2103
|
-
.attr('class', 'item-count-badge')
|
|
2104
|
-
.attr('x', 12)
|
|
2105
|
-
.attr('y', -8)
|
|
2106
|
-
.attr('text-anchor', 'middle')
|
|
2107
|
-
.text(d => {
|
|
2108
|
-
const count = d.data.children ? d.data.children.length : 0;
|
|
2109
|
-
return count > 0 ? count : '';
|
|
2110
|
-
})
|
|
2111
|
-
.style('font-size', '9px')
|
|
2112
|
-
.style('opacity', 0.7)
|
|
2113
|
-
.on('click', (event, d) => this.onNodeClick(event, d)) // CRITICAL FIX: Add click handler to count badges
|
|
2114
|
-
.style('cursor', 'pointer');
|
|
2115
|
-
|
|
2116
|
-
// Transition to new positions
|
|
2117
|
-
const nodeUpdate = nodeEnter.merge(node);
|
|
2118
|
-
|
|
2119
|
-
// CRITICAL FIX: Ensure ALL nodes (new and existing) have click handlers
|
|
2120
|
-
// This fixes the issue where subdirectory clicks stop working after tree updates
|
|
2121
|
-
nodeUpdate.on('click', (event, d) => this.onNodeClick(event, d));
|
|
2122
|
-
|
|
2123
|
-
nodeUpdate.transition()
|
|
2124
|
-
.duration(this.duration)
|
|
2125
|
-
.attr('transform', d => {
|
|
2126
|
-
if (this.isRadialLayout) {
|
|
2127
|
-
const [x, y] = this.radialPoint(d.x, d.y);
|
|
2128
|
-
return `translate(${x},${y})`;
|
|
2129
|
-
} else {
|
|
2130
|
-
return `translate(${d.y},${d.x})`;
|
|
2131
|
-
}
|
|
2132
|
-
});
|
|
2133
|
-
|
|
2134
|
-
// Update node classes based on current state
|
|
2135
|
-
nodeUpdate.attr('class', d => {
|
|
2136
|
-
let classes = ['node', 'code-node'];
|
|
2137
|
-
if (d.data.type === 'directory') {
|
|
2138
|
-
classes.push('directory');
|
|
2139
|
-
if (d.data.loaded === true && d.children) {
|
|
2140
|
-
classes.push('expanded');
|
|
2141
|
-
}
|
|
2142
|
-
if (d.data.loaded === 'loading') {
|
|
2143
|
-
classes.push('loading');
|
|
2144
|
-
}
|
|
2145
|
-
if (d.data.children && d.data.children.length === 0) {
|
|
2146
|
-
classes.push('empty');
|
|
2147
|
-
}
|
|
2148
|
-
} else if (d.data.type === 'file') {
|
|
2149
|
-
classes.push('file');
|
|
2150
|
-
}
|
|
2151
|
-
return classes.join(' ');
|
|
2152
|
-
});
|
|
2153
|
-
|
|
2154
|
-
nodeUpdate.select('circle.node-circle')
|
|
2155
|
-
.attr('r', d => d.data.type === 'directory' ? 10 : 8)
|
|
2156
|
-
.style('fill', d => this.getNodeColor(d))
|
|
2157
|
-
|
|
2158
|
-
// Update expand/collapse icons
|
|
2159
|
-
nodeUpdate.select('.expand-icon')
|
|
2160
|
-
.text(d => {
|
|
2161
|
-
if (d.data.loaded === 'loading') return '⟳';
|
|
2162
|
-
if (d.data.loaded === true && d.children) return '▼';
|
|
2163
|
-
return '▶';
|
|
2164
|
-
});
|
|
2165
|
-
|
|
2166
|
-
// Update item count badges
|
|
2167
|
-
nodeUpdate.select('.item-count-badge')
|
|
2168
|
-
.text(d => {
|
|
2169
|
-
if (d.data.type !== 'directory') return '';
|
|
2170
|
-
const count = d.data.children ? d.data.children.length : 0;
|
|
2171
|
-
return count > 0 ? count : '';
|
|
2172
|
-
})
|
|
2173
|
-
.style('stroke', d => this.getNodeStrokeColor(d))
|
|
2174
|
-
.attr('cursor', 'pointer');
|
|
2175
|
-
|
|
2176
|
-
// Update text labels with proper rotation for radial layout
|
|
2177
|
-
const isRadial = this.isRadialLayout; // Capture the layout type
|
|
2178
|
-
nodeUpdate.select('text.node-label')
|
|
2179
|
-
.style('fill-opacity', 1)
|
|
2180
|
-
.style('fill', '#333')
|
|
2181
|
-
.each(function(d) {
|
|
2182
|
-
const selection = d3.select(this);
|
|
2183
|
-
|
|
2184
|
-
if (isRadial) {
|
|
2185
|
-
// For radial layout, apply rotation and positioning
|
|
2186
|
-
const angle = (d.x * 180 / Math.PI) - 90; // Convert to degrees
|
|
2187
|
-
|
|
2188
|
-
// Determine if text should be flipped (left side of circle)
|
|
2189
|
-
const shouldFlip = angle > 90 || angle < -90;
|
|
2190
|
-
|
|
2191
|
-
// Calculate text position and rotation
|
|
2192
|
-
if (shouldFlip) {
|
|
2193
|
-
// Text on left side - rotate 180 degrees to read properly
|
|
2194
|
-
selection
|
|
2195
|
-
.attr('transform', `rotate(${angle + 180})`)
|
|
2196
|
-
.attr('x', -15) // Negative offset for flipped text
|
|
2197
|
-
.attr('text-anchor', 'end')
|
|
2198
|
-
.attr('dy', '.35em');
|
|
2199
|
-
} else {
|
|
2200
|
-
// Text on right side - normal orientation
|
|
2201
|
-
selection
|
|
2202
|
-
.attr('transform', `rotate(${angle})`)
|
|
2203
|
-
.attr('x', 15) // Positive offset for normal text
|
|
2204
|
-
.attr('text-anchor', 'start')
|
|
2205
|
-
.attr('dy', '.35em');
|
|
2206
|
-
}
|
|
2207
|
-
} else {
|
|
2208
|
-
// Linear layout - no rotation needed
|
|
2209
|
-
selection
|
|
2210
|
-
.attr('transform', null)
|
|
2211
|
-
.attr('x', d.children || d._children ? -13 : 13)
|
|
2212
|
-
.attr('text-anchor', d.children || d._children ? 'end' : 'start')
|
|
2213
|
-
.attr('dy', '.35em');
|
|
2214
|
-
}
|
|
2215
|
-
});
|
|
2216
|
-
|
|
2217
|
-
// Remove exiting nodes
|
|
2218
|
-
const nodeExit = node.exit().transition()
|
|
2219
|
-
.duration(this.duration)
|
|
2220
|
-
.attr('transform', d => {
|
|
2221
|
-
if (this.isRadialLayout) {
|
|
2222
|
-
const [x, y] = this.radialPoint(source.x, source.y);
|
|
2223
|
-
return `translate(${x},${y})`;
|
|
2224
|
-
} else {
|
|
2225
|
-
return `translate(${source.y},${source.x})`;
|
|
2226
|
-
}
|
|
2227
|
-
})
|
|
2228
|
-
.remove();
|
|
2229
|
-
|
|
2230
|
-
nodeExit.select('circle')
|
|
2231
|
-
.attr('r', 1e-6);
|
|
2232
|
-
|
|
2233
|
-
nodeExit.select('text.node-label')
|
|
2234
|
-
.style('fill-opacity', 1e-6);
|
|
2235
|
-
|
|
2236
|
-
nodeExit.select('text.node-icon')
|
|
2237
|
-
.style('fill-opacity', 1e-6);
|
|
2238
|
-
|
|
2239
|
-
// Update links
|
|
2240
|
-
const link = this.treeGroup.selectAll('path.link')
|
|
2241
|
-
.data(links, d => d.id);
|
|
2242
|
-
|
|
2243
|
-
// Enter new links
|
|
2244
|
-
const linkEnter = link.enter().insert('path', 'g')
|
|
2245
|
-
.attr('class', 'link')
|
|
2246
|
-
.attr('d', d => {
|
|
2247
|
-
const o = {x: source.x0, y: source.y0};
|
|
2248
|
-
return this.isRadialLayout ?
|
|
2249
|
-
this.radialDiagonal(o, o) :
|
|
2250
|
-
this.diagonal(o, o);
|
|
2251
|
-
})
|
|
2252
|
-
.style('fill', 'none')
|
|
2253
|
-
.style('stroke', '#ccc')
|
|
2254
|
-
.style('stroke-width', 2);
|
|
2255
|
-
|
|
2256
|
-
// Transition to new positions
|
|
2257
|
-
const linkUpdate = linkEnter.merge(link);
|
|
2258
|
-
|
|
2259
|
-
linkUpdate.transition()
|
|
2260
|
-
.duration(this.duration)
|
|
2261
|
-
.attr('d', d => this.isRadialLayout ?
|
|
2262
|
-
this.radialDiagonal(d, d.parent) :
|
|
2263
|
-
this.diagonal(d, d.parent));
|
|
2264
|
-
|
|
2265
|
-
// Remove exiting links
|
|
2266
|
-
link.exit().transition()
|
|
2267
|
-
.duration(this.duration)
|
|
2268
|
-
.attr('d', d => {
|
|
2269
|
-
const o = {x: source.x, y: source.y};
|
|
2270
|
-
return this.isRadialLayout ?
|
|
2271
|
-
this.radialDiagonal(o, o) :
|
|
2272
|
-
this.diagonal(o, o);
|
|
2273
|
-
})
|
|
2274
|
-
.remove();
|
|
2275
|
-
|
|
2276
|
-
// Store old positions for transition
|
|
2277
|
-
nodes.forEach(d => {
|
|
2278
|
-
d.x0 = d.x;
|
|
2279
|
-
d.y0 = d.y;
|
|
2280
|
-
});
|
|
2281
|
-
}
|
|
2282
|
-
|
|
2283
|
-
/**
|
|
2284
|
-
* REMOVED: Center the view on a specific node (Linear layout)
|
|
2285
|
-
* This method has been completely disabled to prevent unwanted tree movement.
|
|
2286
|
-
* All centering functionality has been removed from the code tree.
|
|
2287
|
-
*/
|
|
2288
|
-
centerOnNode(d) {
|
|
2289
|
-
// Method disabled - no centering operations will be performed
|
|
2290
|
-
console.log('[CodeTree] centerOnNode called but disabled - no centering will occur');
|
|
2291
|
-
return;
|
|
2292
|
-
}
|
|
2293
|
-
|
|
2294
|
-
/**
|
|
2295
|
-
* REMOVED: Center the view on a specific node (Radial layout)
|
|
2296
|
-
* This method has been completely disabled to prevent unwanted tree movement.
|
|
2297
|
-
* All centering functionality has been removed from the code tree.
|
|
2298
|
-
*/
|
|
2299
|
-
centerOnNodeRadial(d) {
|
|
2300
|
-
// Method disabled - no centering operations will be performed
|
|
2301
|
-
console.log('[CodeTree] centerOnNodeRadial called but disabled - no centering will occur');
|
|
2302
|
-
return;
|
|
2303
|
-
}
|
|
2304
|
-
|
|
2305
|
-
/**
|
|
2306
|
-
* Highlight the active node with larger icon
|
|
2307
|
-
*/
|
|
2308
|
-
highlightActiveNode(d) {
|
|
2309
|
-
// Reset all nodes to normal size and clear parent context
|
|
2310
|
-
// First clear classes on the selection
|
|
2311
|
-
const allCircles = this.treeGroup.selectAll('circle.node-circle');
|
|
2312
|
-
allCircles
|
|
2313
|
-
.classed('active', false)
|
|
2314
|
-
.classed('parent-context', false);
|
|
2315
|
-
|
|
2316
|
-
// Then apply transition separately
|
|
2317
|
-
allCircles
|
|
2318
|
-
.transition()
|
|
2319
|
-
.duration(300)
|
|
2320
|
-
.attr('r', 8)
|
|
2321
|
-
.style('stroke', null)
|
|
2322
|
-
.style('stroke-width', null)
|
|
2323
|
-
.style('opacity', null);
|
|
2324
|
-
|
|
2325
|
-
// Reset all labels to normal
|
|
2326
|
-
this.treeGroup.selectAll('text.node-label')
|
|
2327
|
-
.style('font-weight', 'normal')
|
|
2328
|
-
.style('font-size', '12px');
|
|
2329
|
-
|
|
2330
|
-
// Find and increase size of clicked node - use data matching
|
|
2331
|
-
// Make the size increase MUCH more dramatic: 8 -> 20 (2.5x the size)
|
|
2332
|
-
const activeNodeCircle = this.treeGroup.selectAll('g.node')
|
|
2333
|
-
.filter(node => node === d)
|
|
2334
|
-
.select('circle.node-circle');
|
|
2335
|
-
|
|
2336
|
-
// First set the class (not part of transition)
|
|
2337
|
-
activeNodeCircle.classed('active', true);
|
|
2338
|
-
|
|
2339
|
-
// Then apply the transition with styles - MUCH LARGER
|
|
2340
|
-
activeNodeCircle
|
|
2341
|
-
.transition()
|
|
2342
|
-
.duration(300)
|
|
2343
|
-
.attr('r', 20) // Much larger radius (2.5x)
|
|
2344
|
-
.style('stroke', '#3b82f6')
|
|
2345
|
-
.style('stroke-width', 5) // Thicker border
|
|
2346
|
-
.style('filter', 'drop-shadow(0 0 15px rgba(59, 130, 246, 0.6))'); // Stronger glow effect
|
|
2347
|
-
|
|
2348
|
-
// Also make the label bold
|
|
2349
|
-
this.treeGroup.selectAll('g.node')
|
|
2350
|
-
.filter(node => node === d)
|
|
2351
|
-
.select('text.node-label')
|
|
2352
|
-
.style('font-weight', 'bold')
|
|
2353
|
-
.style('font-size', '14px'); // Slightly larger text
|
|
2354
|
-
|
|
2355
|
-
// Store active node
|
|
2356
|
-
this.activeNode = d;
|
|
2357
|
-
}
|
|
2358
|
-
|
|
2359
|
-
/**
|
|
2360
|
-
* Add pulsing animation for loading state
|
|
2361
|
-
*/
|
|
2362
|
-
addLoadingPulse(d) {
|
|
2363
|
-
// Use consistent selection pattern
|
|
2364
|
-
const node = this.treeGroup.selectAll('g.node')
|
|
2365
|
-
.filter(node => node === d)
|
|
2366
|
-
.select('circle.node-circle');
|
|
2367
|
-
|
|
2368
|
-
// Add to loading set
|
|
2369
|
-
this.loadingNodes.add(d.data.path);
|
|
2370
|
-
|
|
2371
|
-
// Add pulsing class and orange color - separate operations
|
|
2372
|
-
node.classed('loading-pulse', true);
|
|
2373
|
-
node.style('fill', '#fb923c'); // Orange color for loading
|
|
2374
|
-
|
|
2375
|
-
// Create pulse animation
|
|
2376
|
-
const pulseAnimation = () => {
|
|
2377
|
-
if (!this.loadingNodes.has(d.data.path)) return;
|
|
2378
|
-
|
|
2379
|
-
node.transition()
|
|
2380
|
-
.duration(600)
|
|
2381
|
-
.attr('r', 14)
|
|
2382
|
-
.style('opacity', 0.6)
|
|
2383
|
-
.transition()
|
|
2384
|
-
.duration(600)
|
|
2385
|
-
.attr('r', 10)
|
|
2386
|
-
.style('opacity', 1)
|
|
2387
|
-
.on('end', () => {
|
|
2388
|
-
if (this.loadingNodes.has(d.data.path)) {
|
|
2389
|
-
pulseAnimation(); // Continue pulsing
|
|
2390
|
-
}
|
|
2391
|
-
});
|
|
2392
|
-
};
|
|
2393
|
-
|
|
2394
|
-
pulseAnimation();
|
|
2395
|
-
}
|
|
2396
|
-
|
|
2397
|
-
/**
|
|
2398
|
-
* Remove pulsing animation when loading complete
|
|
2399
|
-
*/
|
|
2400
|
-
removeLoadingPulse(d) {
|
|
2401
|
-
// Remove from loading set
|
|
2402
|
-
this.loadingNodes.delete(d.data.path);
|
|
2403
|
-
|
|
2404
|
-
// Use consistent selection pattern
|
|
2405
|
-
const node = this.treeGroup.selectAll('g.node')
|
|
2406
|
-
.filter(node => node === d)
|
|
2407
|
-
.select('circle.node-circle');
|
|
2408
|
-
|
|
2409
|
-
// Clear class first
|
|
2410
|
-
node.classed('loading-pulse', false);
|
|
2411
|
-
|
|
2412
|
-
// Then interrupt and transition
|
|
2413
|
-
node.interrupt() // Stop animation
|
|
2414
|
-
.transition()
|
|
2415
|
-
.duration(300)
|
|
2416
|
-
.attr('r', this.activeNode === d ? 20 : 8) // Use 20 for active node
|
|
2417
|
-
.style('opacity', 1)
|
|
2418
|
-
.style('fill', d => this.getNodeColor(d)); // Restore original color
|
|
2419
|
-
}
|
|
2420
|
-
|
|
2421
|
-
/**
|
|
2422
|
-
* Show parent node alongside for context
|
|
2423
|
-
*/
|
|
2424
|
-
showWithParent(d) {
|
|
2425
|
-
if (!d.parent) return;
|
|
2426
|
-
|
|
2427
|
-
// Make parent more visible
|
|
2428
|
-
const parentNode = this.treeGroup.selectAll('g.node')
|
|
2429
|
-
.filter(node => node === d.parent);
|
|
2430
|
-
|
|
2431
|
-
// Highlight parent with different style - separate class from styles
|
|
2432
|
-
const parentCircle = parentNode.select('circle.node-circle');
|
|
2433
|
-
parentCircle.classed('parent-context', true);
|
|
2434
|
-
parentCircle
|
|
2435
|
-
.style('stroke', '#10b981')
|
|
2436
|
-
.style('stroke-width', 3)
|
|
2437
|
-
.style('opacity', 0.8);
|
|
2438
|
-
|
|
2439
|
-
// REMOVED: Radial zoom adjustment functionality
|
|
2440
|
-
// This section previously adjusted zoom to show parent and clicked node together,
|
|
2441
|
-
// but has been completely disabled to prevent unwanted tree movement/centering.
|
|
2442
|
-
// Only visual highlighting of the parent remains active.
|
|
2443
|
-
|
|
2444
|
-
// if (this.isRadialLayout && d.parent) {
|
|
2445
|
-
// // All zoom.transform operations have been disabled
|
|
2446
|
-
// // to prevent tree movement when nodes are clicked
|
|
2447
|
-
// }
|
|
2448
|
-
}
|
|
2449
|
-
|
|
2450
|
-
/**
|
|
2451
|
-
* Handle node click - implement lazy loading with enhanced visual feedback
|
|
2452
|
-
*/
|
|
2453
|
-
onNodeClick(event, d) {
|
|
2454
|
-
// Handle node click interaction
|
|
2455
|
-
|
|
2456
|
-
// Check event parameter
|
|
2457
|
-
if (event) {
|
|
2458
|
-
try {
|
|
2459
|
-
if (typeof event.stopPropagation === 'function') {
|
|
2460
|
-
event.stopPropagation();
|
|
2461
|
-
} else {
|
|
2462
|
-
}
|
|
2463
|
-
} catch (error) {
|
|
2464
|
-
console.error('[CodeTree] ERROR calling stopPropagation:', error);
|
|
2465
|
-
}
|
|
2466
|
-
} else {
|
|
2467
|
-
}
|
|
2468
|
-
|
|
2469
|
-
// Check d parameter structure
|
|
2470
|
-
if (!d) {
|
|
2471
|
-
console.error('[CodeTree] ERROR: d is null/undefined, cannot continue');
|
|
2472
|
-
return;
|
|
2473
|
-
}
|
|
2474
|
-
|
|
2475
|
-
if (!d.data) {
|
|
2476
|
-
console.error('[CodeTree] ERROR: d.data is null/undefined, cannot continue');
|
|
2477
|
-
return;
|
|
2478
|
-
}
|
|
2479
|
-
|
|
2480
|
-
// Node interaction detected
|
|
2481
|
-
|
|
2482
|
-
// === PHASE 1: Immediate Visual Effects (Synchronous) ===
|
|
2483
|
-
// These execute immediately before any async operations
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
// Center on clicked node (immediate visual effect) - REMOVED
|
|
2487
|
-
// Centering functionality has been disabled to prevent unwanted repositioning
|
|
2488
|
-
// when nodes are clicked. All other click functionality remains intact.
|
|
2489
|
-
// try {
|
|
2490
|
-
// if (this.isRadialLayout) {
|
|
2491
|
-
// if (typeof this.centerOnNodeRadial === 'function') {
|
|
2492
|
-
// this.centerOnNodeRadial(d);
|
|
2493
|
-
// } else {
|
|
2494
|
-
// console.error('[CodeTree] centerOnNodeRadial is not a function!');
|
|
2495
|
-
// }
|
|
2496
|
-
// } else {
|
|
2497
|
-
// if (typeof this.centerOnNode === 'function') {
|
|
2498
|
-
// this.centerOnNode(d);
|
|
2499
|
-
// } else {
|
|
2500
|
-
// console.error('[CodeTree] centerOnNode is not a function!');
|
|
2501
|
-
// }
|
|
2502
|
-
// }
|
|
2503
|
-
// } catch (error) {
|
|
2504
|
-
// console.error('[CodeTree] ERROR during centering:', error, error.stack);
|
|
2505
|
-
// }
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
// Highlight with larger icon (immediate visual effect)
|
|
2509
|
-
try {
|
|
2510
|
-
if (typeof this.highlightActiveNode === 'function') {
|
|
2511
|
-
this.highlightActiveNode(d);
|
|
2512
|
-
} else {
|
|
2513
|
-
console.error('[CodeTree] highlightActiveNode is not a function!');
|
|
2514
|
-
}
|
|
2515
|
-
} catch (error) {
|
|
2516
|
-
console.error('[CodeTree] ERROR during highlightActiveNode:', error, error.stack);
|
|
2517
|
-
}
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
// Show parent context (immediate visual effect)
|
|
2521
|
-
try {
|
|
2522
|
-
if (typeof this.showWithParent === 'function') {
|
|
2523
|
-
this.showWithParent(d);
|
|
2524
|
-
} else {
|
|
2525
|
-
console.error('[CodeTree] showWithParent is not a function!');
|
|
2526
|
-
}
|
|
2527
|
-
} catch (error) {
|
|
2528
|
-
console.error('[CodeTree] ERROR during showWithParent:', error, error.stack);
|
|
2529
|
-
}
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
// Add pulsing animation immediately for directories
|
|
2533
|
-
|
|
2534
|
-
if (d.data.type === 'directory' && !d.data.loaded) {
|
|
2535
|
-
try {
|
|
2536
|
-
if (typeof this.addLoadingPulse === 'function') {
|
|
2537
|
-
this.addLoadingPulse(d);
|
|
2538
|
-
} else {
|
|
2539
|
-
console.error('[CodeTree] addLoadingPulse is not a function!');
|
|
2540
|
-
}
|
|
2541
|
-
} catch (error) {
|
|
2542
|
-
console.error('[CodeTree] ERROR during addLoadingPulse:', error, error.stack);
|
|
2543
|
-
}
|
|
2544
|
-
} else {
|
|
2545
|
-
}
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
// === PHASE 2: Prepare Data (Synchronous) ===
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
// Get selected languages from checkboxes
|
|
2552
|
-
const selectedLanguages = [];
|
|
2553
|
-
const checkboxes = document.querySelectorAll('.language-checkbox:checked');
|
|
2554
|
-
checkboxes.forEach(cb => {
|
|
2555
|
-
selectedLanguages.push(cb.value);
|
|
2556
|
-
});
|
|
2557
|
-
|
|
2558
|
-
// Get ignore patterns
|
|
2559
|
-
const ignorePatternsElement = document.getElementById('ignore-patterns');
|
|
2560
|
-
const ignorePatterns = ignorePatternsElement?.value || '';
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
// === PHASE 3: Async Operations (Delayed) ===
|
|
2564
|
-
// Add a small delay to ensure visual effects are rendered first
|
|
2565
|
-
|
|
2566
|
-
// For directories that haven't been loaded yet, request discovery
|
|
2567
|
-
if (d.data.type === 'directory' && !d.data.loaded) {
|
|
2568
|
-
// Prevent duplicate requests
|
|
2569
|
-
if (this.loadingNodes.has(d.data.path)) {
|
|
2570
|
-
this.showNotification(`Already loading: ${d.data.name}`, 'warning');
|
|
2571
|
-
return;
|
|
2572
|
-
}
|
|
2573
|
-
|
|
2574
|
-
// Mark as loading immediately to prevent duplicate requests
|
|
2575
|
-
d.data.loaded = 'loading';
|
|
2576
|
-
this.loadingNodes.add(d.data.path);
|
|
2577
|
-
|
|
2578
|
-
// Ensure path is absolute or relative to working directory
|
|
2579
|
-
const fullPath = this.ensureFullPath(d.data.path);
|
|
2580
|
-
|
|
2581
|
-
// CRITICAL DEBUG: Log directory loading attempt
|
|
2582
|
-
console.log('🚀 [SUBDIRECTORY LOADING] Attempting to load:', {
|
|
2583
|
-
originalPath: d.data.path,
|
|
2584
|
-
fullPath: fullPath,
|
|
2585
|
-
nodeType: d.data.type,
|
|
2586
|
-
loaded: d.data.loaded,
|
|
2587
|
-
hasSocket: !!this.socket,
|
|
2588
|
-
workingDir: this.getWorkingDirectory()
|
|
2589
|
-
});
|
|
2590
|
-
|
|
2591
|
-
// Sending discovery request for child content
|
|
2592
|
-
|
|
2593
|
-
// Store reference to the D3 node for later expansion
|
|
2594
|
-
const clickedD3Node = d;
|
|
2595
|
-
|
|
2596
|
-
// Delay the socket request to ensure visual effects are rendered
|
|
2597
|
-
setTimeout(() => {
|
|
2598
|
-
|
|
2599
|
-
// Request directory contents via Socket.IO
|
|
2600
|
-
if (this.socket) {
|
|
2601
|
-
console.log('📡 [SUBDIRECTORY LOADING] Emitting WebSocket request:', {
|
|
2602
|
-
event: 'code:discover:directory',
|
|
2603
|
-
data: {
|
|
2604
|
-
path: fullPath,
|
|
2605
|
-
depth: this.bulkLoadMode ? 2 : 1,
|
|
2606
|
-
languages: selectedLanguages,
|
|
2607
|
-
ignore_patterns: ignorePatterns
|
|
2608
|
-
}
|
|
2609
|
-
});
|
|
2610
|
-
|
|
2611
|
-
this.socket.emit('code:discover:directory', {
|
|
2612
|
-
path: fullPath,
|
|
2613
|
-
depth: this.bulkLoadMode ? 2 : 1, // Load 2 levels if bulk mode enabled
|
|
2614
|
-
languages: selectedLanguages,
|
|
2615
|
-
ignore_patterns: ignorePatterns
|
|
2616
|
-
});
|
|
2617
|
-
|
|
2618
|
-
this.updateBreadcrumb(`Loading ${d.data.name}...`, 'info');
|
|
2619
|
-
this.showNotification(`Loading directory: ${d.data.name}`, 'info');
|
|
2620
|
-
} else {
|
|
2621
|
-
console.error('❌ [SUBDIRECTORY LOADING] No WebSocket connection available!');
|
|
2622
|
-
this.showNotification(`Cannot load directory: No connection`, 'error');
|
|
2623
|
-
}
|
|
2624
|
-
}, 100); // 100ms delay to ensure visual effects render first
|
|
2625
|
-
}
|
|
2626
|
-
// For files that haven't been analyzed, request analysis
|
|
2627
|
-
else if (d.data.type === 'file' && !d.data.analyzed) {
|
|
2628
|
-
// Only analyze files of selected languages
|
|
2629
|
-
const fileLanguage = this.detectLanguage(d.data.path);
|
|
2630
|
-
if (!selectedLanguages.includes(fileLanguage) && fileLanguage !== 'unknown') {
|
|
2631
|
-
this.showNotification(`Skipping ${d.data.name} - ${fileLanguage} not selected`, 'warning');
|
|
2632
|
-
return;
|
|
2633
|
-
}
|
|
2634
|
-
|
|
2635
|
-
// Add pulsing animation immediately
|
|
2636
|
-
this.addLoadingPulse(d);
|
|
2637
|
-
|
|
2638
|
-
// Mark as loading immediately
|
|
2639
|
-
d.data.analyzed = 'loading';
|
|
2640
|
-
|
|
2641
|
-
// Ensure path is absolute or relative to working directory
|
|
2642
|
-
const fullPath = this.ensureFullPath(d.data.path);
|
|
2643
|
-
|
|
2644
|
-
// Delay the socket request to ensure visual effects are rendered
|
|
2645
|
-
setTimeout(() => {
|
|
2646
|
-
|
|
2647
|
-
if (this.socket) {
|
|
2648
|
-
this.socket.emit('code:analyze:file', {
|
|
2649
|
-
path: fullPath
|
|
2650
|
-
});
|
|
2651
|
-
|
|
2652
|
-
this.updateBreadcrumb(`Analyzing ${d.data.name}...`, 'info');
|
|
2653
|
-
this.showNotification(`Analyzing: ${d.data.name}`, 'info');
|
|
2654
|
-
}
|
|
2655
|
-
}, 100); // 100ms delay to ensure visual effects render first
|
|
2656
|
-
}
|
|
2657
|
-
// Toggle children visibility for already loaded nodes
|
|
2658
|
-
else if (d.data.type === 'directory' && d.data.loaded === true) {
|
|
2659
|
-
// Directory is loaded, toggle expansion
|
|
2660
|
-
if (d.children) {
|
|
2661
|
-
// Collapse - hide children
|
|
2662
|
-
d._children = d.children;
|
|
2663
|
-
d.children = null;
|
|
2664
|
-
d.data.expanded = false;
|
|
2665
|
-
} else if (d._children) {
|
|
2666
|
-
// Expand - show children
|
|
2667
|
-
d.children = d._children;
|
|
2668
|
-
d._children = null;
|
|
2669
|
-
d.data.expanded = true;
|
|
2670
|
-
} else if (d.data.children && d.data.children.length > 0) {
|
|
2671
|
-
// Children exist in data but not in D3 node, recreate hierarchy
|
|
2672
|
-
this.root = d3.hierarchy(this.treeData);
|
|
2673
|
-
const updatedD3Node = this.findD3NodeByPath(d.data.path);
|
|
2674
|
-
if (updatedD3Node) {
|
|
2675
|
-
updatedD3Node.children = updatedD3Node._children || updatedD3Node.children;
|
|
2676
|
-
updatedD3Node._children = null;
|
|
2677
|
-
updatedD3Node.data.expanded = true;
|
|
2678
|
-
}
|
|
2679
|
-
}
|
|
2680
|
-
this.update(this.root);
|
|
2681
|
-
}
|
|
2682
|
-
// Also handle other nodes that might have children
|
|
2683
|
-
else if (d.children || d._children) {
|
|
2684
|
-
if (d.children) {
|
|
2685
|
-
d._children = d.children;
|
|
2686
|
-
d.children = null;
|
|
2687
|
-
d.data.expanded = false;
|
|
2688
|
-
} else {
|
|
2689
|
-
d.children = d._children;
|
|
2690
|
-
d._children = null;
|
|
2691
|
-
d.data.expanded = true;
|
|
2692
|
-
}
|
|
2693
|
-
this.update(d);
|
|
2694
|
-
} else {
|
|
2695
|
-
}
|
|
2696
|
-
|
|
2697
|
-
// Update selection
|
|
2698
|
-
this.selectedNode = d;
|
|
2699
|
-
try {
|
|
2700
|
-
this.highlightNode(d);
|
|
2701
|
-
} catch (error) {
|
|
2702
|
-
console.error('[CodeTree] ERROR during highlightNode:', error);
|
|
2703
|
-
}
|
|
2704
|
-
|
|
2705
|
-
}
|
|
2706
|
-
|
|
2707
|
-
/**
|
|
2708
|
-
* Ensure path is absolute or relative to working directory
|
|
2709
|
-
*/
|
|
2710
|
-
ensureFullPath(path) {
|
|
2711
|
-
console.log('🔗 ensureFullPath called with:', path);
|
|
2712
|
-
|
|
2713
|
-
if (!path) return path;
|
|
2714
|
-
|
|
2715
|
-
// If already absolute, return as is
|
|
2716
|
-
if (path.startsWith('/')) {
|
|
2717
|
-
console.log(' → Already absolute, returning:', path);
|
|
2718
|
-
return path;
|
|
2719
|
-
}
|
|
2720
|
-
|
|
2721
|
-
// Get working directory
|
|
2722
|
-
const workingDir = this.getWorkingDirectory();
|
|
2723
|
-
console.log(' → Working directory:', workingDir);
|
|
2724
|
-
|
|
2725
|
-
if (!workingDir) {
|
|
2726
|
-
console.log(' → No working directory, returning original:', path);
|
|
2727
|
-
return path;
|
|
2728
|
-
}
|
|
2729
|
-
|
|
2730
|
-
// Special handling for root path
|
|
2731
|
-
if (path === '.') {
|
|
2732
|
-
console.log(' → Root path detected, returning working dir:', workingDir);
|
|
2733
|
-
return workingDir;
|
|
2734
|
-
}
|
|
2735
|
-
|
|
2736
|
-
// If path equals working directory, return as is
|
|
2737
|
-
if (path === workingDir) {
|
|
2738
|
-
console.log(' → Path equals working directory, returning:', workingDir);
|
|
2739
|
-
return workingDir;
|
|
2740
|
-
}
|
|
2741
|
-
|
|
2742
|
-
// Combine working directory with relative path
|
|
2743
|
-
const result = `${workingDir}/${path}`.replace(/\/+/g, '/');
|
|
2744
|
-
console.log(' → Combining with working dir, result:', result);
|
|
2745
|
-
return result;
|
|
2746
|
-
}
|
|
2747
|
-
|
|
2748
|
-
/**
|
|
2749
|
-
* Highlight selected node
|
|
2750
|
-
*/
|
|
2751
|
-
highlightNode(node) {
|
|
2752
|
-
// Remove previous highlights
|
|
2753
|
-
this.treeGroup.selectAll('circle.node-circle')
|
|
2754
|
-
.style('stroke-width', 2)
|
|
2755
|
-
.classed('selected', false);
|
|
2756
|
-
|
|
2757
|
-
// Highlight selected node
|
|
2758
|
-
this.treeGroup.selectAll('circle.node-circle')
|
|
2759
|
-
.filter(d => d === node)
|
|
2760
|
-
.style('stroke-width', 4)
|
|
2761
|
-
.classed('selected', true);
|
|
2762
|
-
}
|
|
2763
|
-
|
|
2764
|
-
/**
|
|
2765
|
-
* Create diagonal path for links
|
|
2766
|
-
*/
|
|
2767
|
-
diagonal(s, d) {
|
|
2768
|
-
return `M ${s.y} ${s.x}
|
|
2769
|
-
C ${(s.y + d.y) / 2} ${s.x},
|
|
2770
|
-
${(s.y + d.y) / 2} ${d.x},
|
|
2771
|
-
${d.y} ${d.x}`;
|
|
2772
|
-
}
|
|
2773
|
-
|
|
2774
|
-
/**
|
|
2775
|
-
* Create radial diagonal path for links
|
|
2776
|
-
*/
|
|
2777
|
-
radialDiagonal(s, d) {
|
|
2778
|
-
const path = d3.linkRadial()
|
|
2779
|
-
.angle(d => d.x)
|
|
2780
|
-
.radius(d => d.y);
|
|
2781
|
-
return path({source: s, target: d});
|
|
2782
|
-
}
|
|
2783
|
-
|
|
2784
|
-
/**
|
|
2785
|
-
* Get node color based on type and complexity
|
|
2786
|
-
*/
|
|
2787
|
-
getNodeColor(d) {
|
|
2788
|
-
const type = d.data.type;
|
|
2789
|
-
const complexity = d.data.complexity || 1;
|
|
2790
|
-
|
|
2791
|
-
// Base colors by type
|
|
2792
|
-
const baseColors = {
|
|
2793
|
-
'root': '#6B7280',
|
|
2794
|
-
'directory': '#3B82F6',
|
|
2795
|
-
'file': '#10B981',
|
|
2796
|
-
'module': '#8B5CF6',
|
|
2797
|
-
'class': '#F59E0B',
|
|
2798
|
-
'function': '#EF4444',
|
|
2799
|
-
'method': '#EC4899'
|
|
2800
|
-
};
|
|
2801
|
-
|
|
2802
|
-
const baseColor = baseColors[type] || '#6B7280';
|
|
2803
|
-
|
|
2804
|
-
// Adjust brightness based on complexity (higher complexity = darker)
|
|
2805
|
-
if (complexity > 10) {
|
|
2806
|
-
return d3.color(baseColor).darker(0.5);
|
|
2807
|
-
} else if (complexity > 5) {
|
|
2808
|
-
return d3.color(baseColor).darker(0.25);
|
|
2809
|
-
}
|
|
2810
|
-
|
|
2811
|
-
return baseColor;
|
|
2812
|
-
}
|
|
2813
|
-
|
|
2814
|
-
/**
|
|
2815
|
-
* Get node stroke color
|
|
2816
|
-
*/
|
|
2817
|
-
getNodeStrokeColor(d) {
|
|
2818
|
-
if (d.data.loaded === 'loading' || d.data.analyzed === 'loading') {
|
|
2819
|
-
return '#FCD34D'; // Yellow for loading
|
|
2820
|
-
}
|
|
2821
|
-
if (d.data.type === 'directory' && !d.data.loaded) {
|
|
2822
|
-
return '#94A3B8'; // Gray for unloaded
|
|
2823
|
-
}
|
|
2824
|
-
if (d.data.type === 'file' && !d.data.analyzed) {
|
|
2825
|
-
return '#CBD5E1'; // Light gray for unanalyzed
|
|
2826
|
-
}
|
|
2827
|
-
return this.getNodeColor(d);
|
|
2828
|
-
}
|
|
2829
|
-
|
|
2830
|
-
/**
|
|
2831
|
-
* Get icon for node type
|
|
2832
|
-
*/
|
|
2833
|
-
getNodeIcon(d) {
|
|
2834
|
-
const icons = {
|
|
2835
|
-
'root': '📦',
|
|
2836
|
-
'directory': '📁',
|
|
2837
|
-
'file': '📄',
|
|
2838
|
-
'module': '📦',
|
|
2839
|
-
'class': 'C',
|
|
2840
|
-
'function': 'ƒ',
|
|
2841
|
-
'method': 'm'
|
|
2842
|
-
};
|
|
2843
|
-
return icons[d.data.type] || '•';
|
|
2844
|
-
}
|
|
2845
|
-
|
|
2846
|
-
/**
|
|
2847
|
-
* Show tooltip on hover
|
|
2848
|
-
*/
|
|
2849
|
-
showTooltip(event, d) {
|
|
2850
|
-
if (!this.tooltip) return;
|
|
2851
|
-
|
|
2852
|
-
const info = [];
|
|
2853
|
-
info.push(`<strong>${d.data.name}</strong>`);
|
|
2854
|
-
info.push(`Type: ${d.data.type}`);
|
|
2855
|
-
|
|
2856
|
-
if (d.data.language) {
|
|
2857
|
-
info.push(`Language: ${d.data.language}`);
|
|
2858
|
-
}
|
|
2859
|
-
if (d.data.complexity) {
|
|
2860
|
-
info.push(`Complexity: ${d.data.complexity}`);
|
|
2861
|
-
}
|
|
2862
|
-
if (d.data.lines) {
|
|
2863
|
-
info.push(`Lines: ${d.data.lines}`);
|
|
2864
|
-
}
|
|
2865
|
-
if (d.data.path) {
|
|
2866
|
-
info.push(`Path: ${d.data.path}`);
|
|
2867
|
-
}
|
|
2868
|
-
|
|
2869
|
-
// Special messages for lazy-loaded nodes
|
|
2870
|
-
if (d.data.type === 'directory' && !d.data.loaded) {
|
|
2871
|
-
info.push('<em>Click to explore contents</em>');
|
|
2872
|
-
} else if (d.data.type === 'file' && !d.data.analyzed) {
|
|
2873
|
-
info.push('<em>Click to analyze file</em>');
|
|
2874
|
-
}
|
|
2875
|
-
|
|
2876
|
-
this.tooltip.transition()
|
|
2877
|
-
.duration(200)
|
|
2878
|
-
.style('opacity', .9);
|
|
2879
|
-
|
|
2880
|
-
this.tooltip.html(info.join('<br>'))
|
|
2881
|
-
.style('left', (event.pageX + 10) + 'px')
|
|
2882
|
-
.style('top', (event.pageY - 28) + 'px');
|
|
2883
|
-
}
|
|
2884
|
-
|
|
2885
|
-
/**
|
|
2886
|
-
* Hide tooltip
|
|
2887
|
-
*/
|
|
2888
|
-
hideTooltip() {
|
|
2889
|
-
if (!this.tooltip) return;
|
|
2890
|
-
|
|
2891
|
-
this.tooltip.transition()
|
|
2892
|
-
.duration(500)
|
|
2893
|
-
.style('opacity', 0);
|
|
2894
|
-
}
|
|
2895
|
-
|
|
2896
|
-
/**
|
|
2897
|
-
* Filter tree based on language and search
|
|
2898
|
-
*/
|
|
2899
|
-
filterTree() {
|
|
2900
|
-
if (!this.root) return;
|
|
2901
|
-
|
|
2902
|
-
// Apply filters
|
|
2903
|
-
this.root.descendants().forEach(d => {
|
|
2904
|
-
d.data._hidden = false;
|
|
2905
|
-
|
|
2906
|
-
// Language filter
|
|
2907
|
-
if (this.languageFilter !== 'all') {
|
|
2908
|
-
if (d.data.type === 'file' && d.data.language !== this.languageFilter) {
|
|
2909
|
-
d.data._hidden = true;
|
|
2910
|
-
}
|
|
2911
|
-
}
|
|
2912
|
-
|
|
2913
|
-
// Search filter
|
|
2914
|
-
if (this.searchTerm) {
|
|
2915
|
-
if (!d.data.name.toLowerCase().includes(this.searchTerm)) {
|
|
2916
|
-
d.data._hidden = true;
|
|
2917
|
-
}
|
|
2918
|
-
}
|
|
2919
|
-
});
|
|
2920
|
-
|
|
2921
|
-
// Update display
|
|
2922
|
-
this.update(this.root);
|
|
2923
|
-
}
|
|
2924
|
-
|
|
2925
|
-
/**
|
|
2926
|
-
* Expand all nodes in the tree
|
|
2927
|
-
*/
|
|
2928
|
-
expandAll() {
|
|
2929
|
-
if (!this.root) return;
|
|
2930
|
-
|
|
2931
|
-
// Recursively expand all nodes
|
|
2932
|
-
const expandRecursive = (node) => {
|
|
2933
|
-
if (node._children) {
|
|
2934
|
-
node.children = node._children;
|
|
2935
|
-
node._children = null;
|
|
2936
|
-
}
|
|
2937
|
-
if (node.children) {
|
|
2938
|
-
node.children.forEach(expandRecursive);
|
|
2939
|
-
}
|
|
2940
|
-
};
|
|
2941
|
-
|
|
2942
|
-
expandRecursive(this.root);
|
|
2943
|
-
this.update(this.root);
|
|
2944
|
-
this.showNotification('All nodes expanded', 'info');
|
|
2945
|
-
}
|
|
2946
|
-
|
|
2947
|
-
/**
|
|
2948
|
-
* Collapse all nodes in the tree
|
|
2949
|
-
*/
|
|
2950
|
-
collapseAll() {
|
|
2951
|
-
if (!this.root) return;
|
|
2952
|
-
|
|
2953
|
-
// Recursively collapse all nodes except root
|
|
2954
|
-
const collapseRecursive = (node) => {
|
|
2955
|
-
if (node.children) {
|
|
2956
|
-
node._children = node.children;
|
|
2957
|
-
node.children = null;
|
|
2958
|
-
}
|
|
2959
|
-
if (node._children) {
|
|
2960
|
-
node._children.forEach(collapseRecursive);
|
|
2961
|
-
}
|
|
2962
|
-
};
|
|
2963
|
-
|
|
2964
|
-
this.root.children?.forEach(collapseRecursive);
|
|
2965
|
-
this.update(this.root);
|
|
2966
|
-
this.showNotification('All nodes collapsed', 'info');
|
|
2967
|
-
}
|
|
2968
|
-
|
|
2969
|
-
/**
|
|
2970
|
-
* Reset zoom to fit the tree
|
|
2971
|
-
*/
|
|
2972
|
-
resetZoom() {
|
|
2973
|
-
// DISABLED: All zoom reset operations have been disabled to prevent tree centering/movement
|
|
2974
|
-
// The tree should remain stationary and not center/move when interacting with nodes
|
|
2975
|
-
console.log('[CodeTree] resetZoom called but disabled - no zoom reset will occur');
|
|
2976
|
-
this.showNotification('Zoom reset disabled - tree remains stationary', 'info');
|
|
2977
|
-
return;
|
|
2978
|
-
}
|
|
2979
|
-
|
|
2980
|
-
/**
|
|
2981
|
-
* REMOVED: Focus on a specific node and its subtree
|
|
2982
|
-
* This method has been completely disabled to prevent unwanted tree movement.
|
|
2983
|
-
* All centering and focus functionality has been removed from the code tree.
|
|
2984
|
-
*/
|
|
2985
|
-
focusOnNode(node) {
|
|
2986
|
-
// Method disabled - no focusing/centering operations will be performed
|
|
2987
|
-
console.log('[CodeTree] focusOnNode called but disabled - no focusing will occur');
|
|
2988
|
-
return;
|
|
2989
|
-
|
|
2990
|
-
// Update breadcrumb with focused path
|
|
2991
|
-
const path = this.getNodePath(node);
|
|
2992
|
-
this.updateBreadcrumb(`Focused: ${path}`, 'info');
|
|
2993
|
-
}
|
|
2994
|
-
|
|
2995
|
-
/**
|
|
2996
|
-
* Get the full path of a node
|
|
2997
|
-
*/
|
|
2998
|
-
getNodePath(node) {
|
|
2999
|
-
const path = [];
|
|
3000
|
-
let current = node;
|
|
3001
|
-
while (current) {
|
|
3002
|
-
if (current.data && current.data.name) {
|
|
3003
|
-
path.unshift(current.data.name);
|
|
3004
|
-
}
|
|
3005
|
-
current = current.parent;
|
|
3006
|
-
}
|
|
3007
|
-
return path.join(' / ');
|
|
3008
|
-
}
|
|
3009
|
-
|
|
3010
|
-
/**
|
|
3011
|
-
* Toggle legend visibility
|
|
3012
|
-
*/
|
|
3013
|
-
toggleLegend() {
|
|
3014
|
-
const legend = document.getElementById('tree-legend');
|
|
3015
|
-
if (legend) {
|
|
3016
|
-
if (legend.style.display === 'none') {
|
|
3017
|
-
legend.style.display = 'block';
|
|
3018
|
-
} else {
|
|
3019
|
-
legend.style.display = 'none';
|
|
3020
|
-
}
|
|
3021
|
-
}
|
|
3022
|
-
}
|
|
3023
|
-
|
|
3024
|
-
/**
|
|
3025
|
-
* Get the current working directory
|
|
3026
|
-
*/
|
|
3027
|
-
getWorkingDirectory() {
|
|
3028
|
-
// Try to get from dashboard's working directory manager
|
|
3029
|
-
if (window.dashboard && window.dashboard.workingDirectoryManager) {
|
|
3030
|
-
return window.dashboard.workingDirectoryManager.getCurrentWorkingDir();
|
|
3031
|
-
}
|
|
3032
|
-
|
|
3033
|
-
// Fallback to checking the DOM element
|
|
3034
|
-
const workingDirPath = document.getElementById('working-dir-path');
|
|
3035
|
-
if (workingDirPath) {
|
|
3036
|
-
const pathText = workingDirPath.textContent.trim();
|
|
3037
|
-
if (pathText && pathText !== 'Loading...' && pathText !== 'Not selected') {
|
|
3038
|
-
return pathText;
|
|
3039
|
-
}
|
|
3040
|
-
}
|
|
3041
|
-
|
|
3042
|
-
return null;
|
|
3043
|
-
}
|
|
3044
|
-
|
|
3045
|
-
/**
|
|
3046
|
-
* Show a message when no working directory is selected
|
|
3047
|
-
*/
|
|
3048
|
-
showNoWorkingDirectoryMessage() {
|
|
3049
|
-
const container = document.getElementById('code-tree-container');
|
|
3050
|
-
if (!container) return;
|
|
3051
|
-
|
|
3052
|
-
// Remove any existing message
|
|
3053
|
-
this.removeNoWorkingDirectoryMessage();
|
|
3054
|
-
|
|
3055
|
-
// Hide loading if shown
|
|
3056
|
-
this.hideLoading();
|
|
3057
|
-
|
|
3058
|
-
// Create message element
|
|
3059
|
-
const messageDiv = document.createElement('div');
|
|
3060
|
-
messageDiv.id = 'no-working-dir-message';
|
|
3061
|
-
messageDiv.className = 'no-working-dir-message';
|
|
3062
|
-
messageDiv.innerHTML = `
|
|
3063
|
-
<div class="message-icon">📁</div>
|
|
3064
|
-
<h3>No Working Directory Selected</h3>
|
|
3065
|
-
<p>Please select a working directory from the top menu to analyze code.</p>
|
|
3066
|
-
<button id="select-working-dir-btn" class="btn btn-primary">
|
|
3067
|
-
Select Working Directory
|
|
3068
|
-
</button>
|
|
3069
|
-
`;
|
|
3070
|
-
messageDiv.style.cssText = `
|
|
3071
|
-
text-align: center;
|
|
3072
|
-
padding: 40px;
|
|
3073
|
-
color: #666;
|
|
3074
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
3075
|
-
`;
|
|
3076
|
-
|
|
3077
|
-
// Style the message elements
|
|
3078
|
-
const messageIcon = messageDiv.querySelector('.message-icon');
|
|
3079
|
-
if (messageIcon) {
|
|
3080
|
-
messageIcon.style.cssText = 'font-size: 48px; margin-bottom: 16px; opacity: 0.5;';
|
|
3081
|
-
}
|
|
3082
|
-
|
|
3083
|
-
const h3 = messageDiv.querySelector('h3');
|
|
3084
|
-
if (h3) {
|
|
3085
|
-
h3.style.cssText = 'margin: 16px 0; color: #333; font-size: 20px;';
|
|
3086
|
-
}
|
|
3087
|
-
|
|
3088
|
-
const p = messageDiv.querySelector('p');
|
|
3089
|
-
if (p) {
|
|
3090
|
-
p.style.cssText = 'margin: 16px 0; color: #666; font-size: 14px;';
|
|
3091
|
-
}
|
|
3092
|
-
|
|
3093
|
-
const button = messageDiv.querySelector('button');
|
|
3094
|
-
if (button) {
|
|
3095
|
-
button.style.cssText = `
|
|
3096
|
-
margin-top: 20px;
|
|
3097
|
-
padding: 10px 20px;
|
|
3098
|
-
background: #3b82f6;
|
|
3099
|
-
color: white;
|
|
3100
|
-
border: none;
|
|
3101
|
-
border-radius: 6px;
|
|
3102
|
-
cursor: pointer;
|
|
3103
|
-
font-size: 14px;
|
|
3104
|
-
transition: background 0.2s;
|
|
3105
|
-
`;
|
|
3106
|
-
button.addEventListener('mouseenter', () => {
|
|
3107
|
-
button.style.background = '#2563eb';
|
|
3108
|
-
});
|
|
3109
|
-
button.addEventListener('mouseleave', () => {
|
|
3110
|
-
button.style.background = '#3b82f6';
|
|
3111
|
-
});
|
|
3112
|
-
button.addEventListener('click', () => {
|
|
3113
|
-
// Trigger working directory selection
|
|
3114
|
-
const changeDirBtn = document.getElementById('change-dir-btn');
|
|
3115
|
-
if (changeDirBtn) {
|
|
3116
|
-
changeDirBtn.click();
|
|
3117
|
-
} else if (window.dashboard && window.dashboard.workingDirectoryManager) {
|
|
3118
|
-
window.dashboard.workingDirectoryManager.showChangeDirDialog();
|
|
3119
|
-
}
|
|
3120
|
-
});
|
|
3121
|
-
}
|
|
3122
|
-
|
|
3123
|
-
container.appendChild(messageDiv);
|
|
3124
|
-
|
|
3125
|
-
// Update breadcrumb
|
|
3126
|
-
this.updateBreadcrumb('Please select a working directory', 'warning');
|
|
3127
|
-
}
|
|
3128
|
-
|
|
3129
|
-
/**
|
|
3130
|
-
* Remove the no working directory message
|
|
3131
|
-
*/
|
|
3132
|
-
removeNoWorkingDirectoryMessage() {
|
|
3133
|
-
const message = document.getElementById('no-working-dir-message');
|
|
3134
|
-
if (message) {
|
|
3135
|
-
message.remove();
|
|
3136
|
-
}
|
|
3137
|
-
}
|
|
3138
|
-
|
|
3139
|
-
/**
|
|
3140
|
-
* Export tree data
|
|
3141
|
-
*/
|
|
3142
|
-
exportTree() {
|
|
3143
|
-
const exportData = {
|
|
3144
|
-
timestamp: new Date().toISOString(),
|
|
3145
|
-
workingDirectory: this.getWorkingDirectory(),
|
|
3146
|
-
stats: this.stats,
|
|
3147
|
-
tree: this.treeData
|
|
3148
|
-
};
|
|
3149
|
-
|
|
3150
|
-
const blob = new Blob([JSON.stringify(exportData, null, 2)],
|
|
3151
|
-
{type: 'application/json'});
|
|
3152
|
-
const url = URL.createObjectURL(blob);
|
|
3153
|
-
const link = document.createElement('a');
|
|
3154
|
-
link.href = url;
|
|
3155
|
-
link.download = `code-tree-${Date.now()}.json`;
|
|
3156
|
-
link.click();
|
|
3157
|
-
URL.revokeObjectURL(url);
|
|
3158
|
-
|
|
3159
|
-
this.showNotification('Tree exported successfully', 'success');
|
|
3160
|
-
}
|
|
3161
|
-
|
|
3162
|
-
/**
|
|
3163
|
-
* Update activity ticker with real-time messages
|
|
3164
|
-
*/
|
|
3165
|
-
updateActivityTicker(message, type = 'info') {
|
|
3166
|
-
const breadcrumb = document.getElementById('breadcrumb-content');
|
|
3167
|
-
if (breadcrumb) {
|
|
3168
|
-
// Add spinning icon for loading states
|
|
3169
|
-
const icon = type === 'info' && message.includes('...') ? '⟳ ' : '';
|
|
3170
|
-
breadcrumb.innerHTML = `${icon}${message}`;
|
|
3171
|
-
breadcrumb.className = `breadcrumb-${type}`;
|
|
3172
|
-
}
|
|
3173
|
-
}
|
|
3174
|
-
|
|
3175
|
-
/**
|
|
3176
|
-
* Update ticker message
|
|
3177
|
-
*/
|
|
3178
|
-
updateTicker(message, type = 'info') {
|
|
3179
|
-
const ticker = document.getElementById('code-tree-ticker');
|
|
3180
|
-
if (ticker) {
|
|
3181
|
-
ticker.textContent = message;
|
|
3182
|
-
ticker.className = `ticker ticker-${type}`;
|
|
3183
|
-
|
|
3184
|
-
// Auto-hide after 5 seconds for non-error messages
|
|
3185
|
-
if (type !== 'error') {
|
|
3186
|
-
setTimeout(() => {
|
|
3187
|
-
ticker.style.opacity = '0';
|
|
3188
|
-
setTimeout(() => {
|
|
3189
|
-
ticker.style.opacity = '1';
|
|
3190
|
-
ticker.textContent = '';
|
|
3191
|
-
}, 300);
|
|
3192
|
-
}, 5000);
|
|
3193
|
-
}
|
|
3194
|
-
}
|
|
3195
|
-
}
|
|
3196
|
-
}
|
|
3197
|
-
|
|
3198
|
-
// Export for use in other modules
|
|
3199
|
-
window.CodeTree = CodeTree;
|
|
3200
|
-
|
|
3201
|
-
// Auto-initialize when DOM is ready
|
|
3202
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
3203
|
-
// Check if we're on a page with code tree container
|
|
3204
|
-
if (document.getElementById('code-tree-container')) {
|
|
3205
|
-
window.codeTree = new CodeTree();
|
|
3206
|
-
|
|
3207
|
-
// Listen for tab changes to initialize when code tab is selected
|
|
3208
|
-
document.addEventListener('click', (e) => {
|
|
3209
|
-
if (e.target.matches('[data-tab="code"]')) {
|
|
3210
|
-
setTimeout(() => {
|
|
3211
|
-
if (window.codeTree && !window.codeTree.initialized) {
|
|
3212
|
-
window.codeTree.initialize();
|
|
3213
|
-
} else if (window.codeTree) {
|
|
3214
|
-
window.codeTree.renderWhenVisible();
|
|
3215
|
-
}
|
|
3216
|
-
}, 100);
|
|
3217
|
-
}
|
|
3218
|
-
});
|
|
3219
|
-
}
|
|
3220
|
-
});/* Cache buster: 1756393851 */
|